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

Make use of bx::bit_cast #3212

Merged
merged 1 commit into from
Feb 24, 2024
Merged

Make use of bx::bit_cast #3212

merged 1 commit into from
Feb 24, 2024

Conversation

phniix
Copy link
Contributor

@phniix phniix commented Dec 2, 2023

Use bx::bit_cast() where appropriate for type punning, and applying packed struct for arrays when necessary.

bx::bit_cast() is introduced in bkaradzic/bx#313 to address the similar issue of type punning in bx (bkaradzic/bx#312).

This PR also uses reinterpret_cast for direct void* translations for integer handles.
Within renderer_vk.cpp, we remove the union when casting from void* to xcb_window_t, it isn't necessary.

@bkaradzic
Copy link
Owner

My suggestion is to reduce do smaller PR, where you use only bx::bit_cast.

For example, you can replace this castfu function with bx::bit_cast, not implementation of it, rather function itself:

	inline uint32_t castfu(float _value)
	{
		union {	float fl; uint32_t ui; } un;
		un.fl = _value;
		return un.ui;
	}

@phniix
Copy link
Contributor Author

phniix commented Feb 10, 2024

My suggestion is to reduce do smaller PR, where you use only bx::bit_cast.

For example, you can replace this castfu function with bx::bit_cast, not implementation of it, rather function itself:

	inline uint32_t castfu(float _value)
	{
		union {	float fl; uint32_t ui; } un;
		un.fl = _value;
		return un.ui;
	}

Sure yes, no worries. Let me know if the latest pushes are what you had in mind.

@@ -830,7 +825,7 @@ namespace entry
{
case SDL_USER_WINDOW_CREATE:
{
WindowHandle handle = getWindowHandle(uev);
WindowHandle handle = reinterpret_cast<WindowHandle>(_uev.data1);
Copy link
Owner

Choose a reason for hiding this comment

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

I prefer you leave getWindowHandle instead this reinterpret_cast. Basically ideally no reinterpret_cast anywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure thing!

union { void* p; WindowHandle h; } cast;
cast.h = _handle;
uev.data1 = cast.p;
uev.data1 = reinterpret_cast<void*>(_handle);
Copy link
Owner

Choose a reason for hiding this comment

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

One benefit of union here is that type-safety will actually error if handle doesn't match. Where reinterpret_cast will just do whatever.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You bring up an excellent point about the importance of type checking. It's crucial to ensure stability, especially in scenarios where _handle might change its type unexpectedly. For instance, if the API were to transition WindowHandle to an integer type like uint64_t, it could lead to complications if the cast to void* isn't adjusted accordingly.

I think we can enhance our type checking to be more robust and explicit by incorporating bx::isSame<Ty, Uy>(). This approach emphasizes clarity and intentionality, making it easier to understand the expected behavior. I'll demonstrate this further with an example in an upcoming commit.

Copy link
Owner

@bkaradzic bkaradzic left a comment

Choose a reason for hiding this comment

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

In almost every use of reinterpret_cast in your PR you're making code worse than before IMO. Not sure what's your goal here?!

union { uint32_t color; uint8_t bgra[4]; } cast = { m_lumBgra8 };
float exponent = cast.bgra[3]/255.0f * 255.0f - 128.0f;
float lumAvg = cast.bgra[2]/255.0f * bx::exp2(exponent);
struct packed { uint8_t bgra[4]; } arr = bx::bit_cast<packed>(m_lumBgra8);
Copy link
Owner

Choose a reason for hiding this comment

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

Code style is uppercase for struct/class, so Packed.


cast.ptr = _thread->pop();
if (UINTPTR_MAX == cast.id)
uintptr_t id = reinterpret_cast<uintptr_t>(_thread->pop());
Copy link
Owner

Choose a reason for hiding this comment

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

Still don't like reinterpret cast... This is not better than union thing.

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 should have probably marked the current this variable const in the commit. The difference we'd have is that we can mark uintptr_t id const, but not the same with union {} cast.

union { void* p; WindowHandle h; } cast;
cast.h = _handle;
uev.data1 = cast.p;
BX_STATIC_ASSERT((bx::isSame<WindowHandle, decltype(_handle)>()), "_handle must be of the type WindowHandle");
Copy link
Owner

Choose a reason for hiding this comment

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

This is getting more complicated than union cast 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.

This approach emphasizes clarity and intentionality, making it easier to understand the expected behavior.

The added type checking is a nice insurance policy in the event someone might inadvertently change the type of the input parameter. I wonder if we can get away with not having any such insurance policy, as it seems very likely that any type change to _handle would force a rethink of what is being stored and retrieved from SDL_UserEvent.data1..

cast c;
c.w = window;
m_eventQueue.postWindowEvent(_handle, c.p);
m_eventQueue.postWindowEvent(_handle, reinterpret_cast<void*>(window));
Copy link
Owner

Choose a reason for hiding this comment

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

Don't like reinterpret cast here.

@@ -2952,7 +2952,7 @@ namespace bgfx { namespace d3d12

static void patchCb0(DxbcInstruction& _instruction, void* _userData)
{
union { void* ptr; uint32_t offset; } cast = { _userData };
const uint32_t offset = uint32_t(reinterpret_cast<uintptr_t>(_userData));
Copy link
Owner

Choose a reason for hiding this comment

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

You need to realize that I consciously didn't want reinterpret_cast when I wrote this code. I was aware that reinterpret_cast exist before...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It isn't something that appears super clear. As a reader, and user, I'm interpreting the usage as a reflex action (for example when we see the usage on line 3159), kind of like a C in C++ approach. Thank you for sharing your thought and decision process.

Copy link
Contributor Author

@phniix phniix left a comment

Choose a reason for hiding this comment

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

Not sure what's your goal here?!

I had thought that the PR title and description were clear in stating the goal. Apologies if they haven't been clear enough.

I'll update this PR to reflect only the bx::bit_cast changes, as there seems to be a strong desire to maintain the stylistic choice of using unions for pointer to integer conversions.

union { void* p; WindowHandle h; } cast;
cast.h = _handle;
uev.data1 = cast.p;
BX_STATIC_ASSERT((bx::isSame<WindowHandle, decltype(_handle)>()), "_handle must be of the type WindowHandle");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This approach emphasizes clarity and intentionality, making it easier to understand the expected behavior.

The added type checking is a nice insurance policy in the event someone might inadvertently change the type of the input parameter. I wonder if we can get away with not having any such insurance policy, as it seems very likely that any type change to _handle would force a rethink of what is being stored and retrieved from SDL_UserEvent.data1..

@@ -2952,7 +2952,7 @@ namespace bgfx { namespace d3d12

static void patchCb0(DxbcInstruction& _instruction, void* _userData)
{
union { void* ptr; uint32_t offset; } cast = { _userData };
const uint32_t offset = uint32_t(reinterpret_cast<uintptr_t>(_userData));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It isn't something that appears super clear. As a reader, and user, I'm interpreting the usage as a reflex action (for example when we see the usage on line 3159), kind of like a C in C++ approach. Thank you for sharing your thought and decision process.


cast.ptr = _thread->pop();
if (UINTPTR_MAX == cast.id)
uintptr_t id = reinterpret_cast<uintptr_t>(_thread->pop());
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 should have probably marked the current this variable const in the commit. The difference we'd have is that we can mark uintptr_t id const, but not the same with union {} cast.

@phniix phniix changed the title Remove type punning using unions Make use of bx::bit_cast Feb 24, 2024
@bkaradzic bkaradzic merged commit 0b049d4 into bkaradzic:master Feb 24, 2024
11 checks passed
mipek pushed a commit to mipek/bgfx that referenced this pull request Mar 2, 2024
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.

2 participants