Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented some additional API functions related to crate::core_type… #729

Merged
merged 2 commits into from Apr 30, 2021

Conversation

jacobsky
Copy link
Contributor

…s::Color

I needed some additional functionality from the Color class in godot and noticed that there were a lot of APIs that weren't accessible from rust.

This PR plumbs in the functions from godot and adds some tests that can be used to sanity check them.

This commit adds the API functionality and some basic sanity tests that I verified are passing in the engine. If additional tests are required, please let me know.
Signed-off-by: Jacobsky cael.jacobsen@gmail.com

…s::Color

Signed-off-by: Jacobsky <cael.jacobsen@gmail.com>
Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Thanks for the PR!

bors r+

bors bot added a commit that referenced this pull request Apr 28, 2021
729: Implemented some additional API functions related to crate::core_type… r=toasteater a=jacobsky

…s::Color

I needed some additional functionality from the Color class in godot and noticed that there were a lot of APIs that weren't accessible from rust. 

This PR plumbs in the functions from godot and adds some tests that can be used to sanity check them.

This commit adds the API functionality and some basic sanity tests that I verified are passing in the engine. If additional tests are required, please let me know.
Signed-off-by: Jacobsky <cael.jacobsen@gmail.com>

Co-authored-by: Jacobsky <cael.jacobsen@gmail.com>
@bors
Copy link
Contributor

bors bot commented Apr 28, 2021

Build failed:

Color::from_sys(unsafe { (get_api().godot_color_darkened)(self.sys(), amount) })
}
#[inline]
pub fn from_hsv(h: f32, s: f32, v: f32, a: f32) -> Color {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this maybe be named hsv(), consistent with the rgb() constructor -- or rename the latter?

Also, I would add overloads for HSV and HSVA.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll go ahead and add those.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to rename rgb() here: from_thing is a common naming pattern for constructors in Rust.

Copy link
Contributor Author

@jacobsky jacobsky Apr 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my latest commit, I changed the name to hsv() and hsva() in in 41969dd.

I can change it to follow either pattern as they both make sense to me.

Copy link
Contributor Author

@jacobsky jacobsky Apr 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@toasteater @Bromeon after thinking this over, I was wondering if it would be possible to use the from_* naming pattern, doing so would likely break compatibility with anyone that is currently using color.

To avoid breaking compability, I could mark leave the rgb() and rgba() methods in while marking them as deprecated. Does that sound reasonable?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jacobsky That sounds reasonable! FWIW, we're working on a 0.10 release on master, so breaking compatibility is acceptable here, although this is useful to note since we might want to make a 0.9.4 release too. I think adding from_* but deprecating the old methods is good for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@toasteater Thanks for the feedback. I'll get to work on that. I noticed that there were a few clippy lints that failed when I added the deprecated to rgb() and rgba(), so I want to take a little more time to ensure that I don't miss any of those changes.

gdnative-core/src/core_types/color.rs Show resolved Hide resolved
gdnative-core/src/core_types/color.rs Outdated Show resolved Hide resolved
@ghost
Copy link

ghost commented Apr 28, 2021

Please also remember to fix clippy lints: you're missing a few #[inline]s.

@Demindiro
Copy link
Contributor

Demindiro commented Apr 28, 2021

u64 is better for optimizations, it's very likely that [u16; 4] will use either 4 registers or stack allocation vs just one register with u64. it seems the compiler is actually capable of optimizing it.

As for the casting issue: there are to_le_bytes/to_be_bytes which are architecture independent.

@Bromeon
Copy link
Member

Bromeon commented Apr 28, 2021

Just replied again (devil-advocating my own statement).

I don't think optimization matters much, a compiler is likely to recognize static arrays of small sizes and treat them as double/quadruple words. To me it's more about clarity. There's not a huge difference between the two though.

[Edit]: As concluded here, let's go with [u8; 4] and [u16; 4] for now.

@jacobsky
Copy link
Contributor Author

Completed changes related to #729 (comment) with commit 88d50d4

If you'd like any changes to the methods or anything looks odd, please let me know.

@Bromeon
Copy link
Member

Bromeon commented Apr 28, 2021

Not 100% sure about endianess. What is the memory layout of Godot's integers returned by to_*{32|64}?

That determines whether we can use transmute (aka C++ reinterpret_cast) at all...

@jacobsky
Copy link
Contributor Author

jacobsky commented Apr 28, 2021

That's a good question. There's not really any docs directly related to endianess, but the implementation is as follows

uint32_t Color::to_ARGB32() const {
	uint32_t c = (uint8_t)(a * 255);
	c <<= 8;
	c |= (uint8_t)(r * 255);
	c <<= 8;
	c |= (uint8_t)(g * 255);
	c <<= 8;
	c |= (uint8_t)(b * 255);

	return c;
}

And

uint64_t Color::to_ABGR64() const {
	uint64_t c = (uint16_t)(a * 65535);
	c <<= 16;
	c |= (uint16_t)(b * 65535);
	c <<= 16;
	c |= (uint16_t)(g * 65535);
	c <<= 16;
	c |= (uint16_t)(r * 65535);

	return c;
}

So it doesn't look like the engine considers endianess in their encoding. Unless I'm mistaken, that would means that it's encoded as the native endianess.

So I think this should work.

@Bromeon
Copy link
Member

Bromeon commented Apr 29, 2021

Thanks for looking up the implementation! So they use shift, which means bits can be ordered differently depending on endianess. The most significant bit (for to_ARGB32) is the alpha component, which is either at the beginning (big endian) or end (little endian) of the 4 bytes.

So I think this should work.

No. u32::to_ne_bytes() also uses mem::transmute() internally, which is simply reinterpreting the bit pattern.

Reinterpreting the bit pattern means whatever comes first in memory will be the alpha value (for ARGB). As written above, that alpha value can however be at the beginning or at the end in memory.

The correct, and most understandable way, would be to simply undo the shift operations.

@jacobsky
Copy link
Contributor Author

@Bromeon thanks for the insight.

I also did some more research into these functions and it looks like these are intended for turning into pixel color data in various byte formats such as RGBA8888.

https://en.m.wikipedia.org/wiki/RGBA_color_model#RGBA8888

As this is the case, the pixel data is likely only going to be useful when attempting to write pixel data or texture data. Those would use the byte pool arrays under the hood as well.

https://docs.godotengine.org/en/stable/classes/class_poolbytearray.html#class-poolbytearray

Based on this, I'm starting to think that it would be better to return the u32/u64 directly and leave this up to users that need to manipulate these values.

For now, I'll go ahead and implement the unshift operations.

@ghost
Copy link

ghost commented Apr 29, 2021

I also did some more research into these functions and it looks like these are intended for turning into pixel color data in various byte formats such as RGBA8888.

@jacobsky I think this is a good point. Perhaps it's best to leave the data in u32/u64 form if this is the intended use case.

@Bromeon
Copy link
Member

Bromeon commented Apr 29, 2021

Ah, ARGB32 is specifically a format that defines encoding in terms of most/least significant bits and not memory addresses, I see.
When converting it into byte arrays (PoolByteArray), we might run into the endianness issues again though, depending on what the destination format expects.

I was also under the impression that they use ints because arrays are so expensive in GDScript (dynamic + ref-counted).

Either way, let's document the methods. Otherwise all the knowledge we figured out in this thread will be lost, and people need to go through the same hoops again.

@jacobsky
Copy link
Contributor Author

@Bromeon @toasteater I went ahead and just ported the conversions directly into the rust code as when calling the APIs there seemed to be an issue with the 64bit versions. From the outset it looks like the API wrapper is returning the value as an i32 which resulted in the wrong final values being pulled from the API.

I wrote a couple of test cases and while the code itself isn't exactly elegant, it seems to work just fine. I also went ahead and added some documentation about each of the methods with the endianess information in mind.

The changes are in 1988289

Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again for the changes! I've made a few comments, we can also wait for @toasteater to avoid any more back-and-forth modifications.

gdnative-core/src/core_types/color.rs Outdated Show resolved Hide resolved
Comment on lines 84 to 110
pub fn gray(&self) -> f32 {
// Implemented as described in godot docs
(self.r + self.b + self.g) / 3.0
}

#[inline]
pub fn inverted(&self) -> Color {
// Implementation as described in godot docs.
Color {
r: 1.0f32 - self.r,
g: 1.0f32 - self.g,
b: 1.0f32 - self.b,
a: self.a,
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two methods have a simple implementation, but is there a reason to not call the GDNative API? Or where do we draw the border?

For the case with to_abgr64, where the wrong type is returned by GDNative, it clearly makes sense.

Copy link

@ghost ghost Apr 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to inline simple implementations on small types to avoid the FFI overhead. We do this for Vector2 and Vector3, and Color is essentially a Vector4. I don't see how this is problematic. That does not mean that we need to actively go port everything -- it only means that it's fine and welcome to do so. Let's say that we draw the border at Color / Vector4, maybe?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I think that's a good boundary 👍🏼
And yes, let's leave the other methods as is for now, no need to change what's not broken.

The only small risk is that if a Godot method ever got a small change in semantics or bugfix, it wouldn't be carried over, but for most of the methods this is unlikely.

One subtlety I could imagine is how the [0.0, 1.0] float range is mapped to the integer interval [0, 255] -- just multiplying by 255 does not create even bins. All values in [0.0, 1/255] are mapped to 0, but only the exact value 1.0 is mapped to 255. In other words, in a uniform distribution, it is infinitely improbable for a color to have a component with value 255 (in theory at least).

Copy link

@ghost ghost Apr 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Bromeon Uniform [0, 1] distributions do exist. The distribution used by Godot's RandomNumberGenerator class is one: https://github.com/godotengine/godot/blob/28f56e2cbf03a164741f2eade17f9515f887482c/core/math/random_pcg.h#L90 Nevermind, I understand what you mean now. Still, multiplying by 255 is a common way to do this, and if it's consistent with the Godot implementation I think it should be fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely. My point was more that if Godot ever decides to change how this is implemented, we will be deviating slightly and could cause subtle bugs for users.

But I don't think it's something worthwhile to worry about, we could even add tests for such cases (later, not in this PR).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. I implemented these by hand initially because they were trivial and I wasn't sure how to call the API. If there's no significant performance hit, it'd probably be better to call the api. I'll test it later when I'm back at my PC.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I didn't mean you should change it. As toasteater said, it's OK in these cases 🙂

I think you've had enough obstacles in this PR already -- if we really feel this could become a problem one day, we can simply add regression tests where we compare the built-in vs. the API function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all of the information/discussion. I'll work on implementing a basic regression test for the custom implementation for this type and include it in another PR a little later.

gdnative-core/src/core_types/color.rs Outdated Show resolved Hide resolved
gdnative-core/src/core_types/color.rs Outdated Show resolved Hide resolved
Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the changes look good to me now. One little thing is that most of the methods can indeed take self instead of &self, since Color implements Copy and isn't really large (16 bytes). Most of Vector2 and Vector3 methods take self, too, and I don't think the situation is much different here. Otherwise, I agree with Bromeon's points. Thanks again for taking this so far and having patience with us!

gdnative-core/src/core_types/color.rs Outdated Show resolved Hide resolved
gdnative-core/src/core_types/color.rs Outdated Show resolved Hide resolved
@jacobsky
Copy link
Contributor Author

Thank you for all of the insight. I think I managed to include all of the requested changes as of e1ffa8e. If you have any additional changes or see any issues, please let me know.

Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great to me. Thanks for all the effort! Squash the commits and we should be good to go!

@jacobsky
Copy link
Contributor Author

jacobsky commented Apr 30, 2021

@toasteater Alrighty. I went ahead and squashed it on commit c608e1a, I hope I did it correctly.

@ghost
Copy link

ghost commented Apr 30, 2021

It's OK. Thank you!

bors r+

@bors
Copy link
Contributor

bors bot commented Apr 30, 2021

Build succeeded:

@bors bors bot merged commit d95b8fa into godot-rust:master Apr 30, 2021
@ghost
Copy link

ghost commented Apr 30, 2021

🎉 Sorry it took so long!

@jacobsky
Copy link
Contributor Author

jacobsky commented May 1, 2021

Not at all! I'm glad that it was fun learning about some of the inner-workings of godot/rust! Thank you for all of the help!

@ghost ghost mentioned this pull request May 1, 2021
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants