views:

208

answers:

3

Im running through a set of DirectX tutorials online and I have the following structure:

struct CUSTOMVERTEX
{
FLOAT x, y, z, rhw; // from the D3DFVF_XYZRHW flag
DWORD color;        // from the D3DFVF_DIFFUSE flag
}

My basic understanding of directX leads me to thing tha color is made up of 8-bit alpha, red, green and blue channels.

I am attempting to get east access to these channels. Rather than write the following code numerous times (within the CUSTOMVERTEX structure):

public: int red()
{
    return (color & 0x00FF0000) >> 16;
}

I could write a more elegant somution with a combination of a union and a structure e.g.

struct CUSTOMVERTEX
{
    FLOAT x, y, z, rhw; // from the D3DFVF_XYZRHW flag

    #pragma pack(2)
    union 
    {
        DWORD color;        // from the D3DFVF_DIFFUSE flag

        struct
        {
            char a;
            char r;
            char g;
            char b;
        };
    };
}

However this does not appear to function as expected, the values in r, g, & b almost appear the reverse of whats in color e.g. if color is 0x12345678 a = 0x78, r = 0x56. Is this an endieness issue?

Also what other problems could I be expecting from this solution? e.g. overflow from the color members?

I guess what Im asking is ... is there a better way to do this?!

+1  A: 

If Your DWORD color is 0x12345678, the byte at the address &color is 0x78 in a little-endian system.

The trick with the union technically leads to an undefined behaviour. What's wrong with the accessor method?

Maciej Hehl
There is *nothing* wrong with it per say ... I was just wondering whether I could get away without having to write a seperate getter and setter function for each channel. Im looking for the most elegant solution I can get away with. As Im kinda new to c++ and DirectX I thought that Id try and refine my code a little rather than just ploughing on with tutorials! Im trying to get the most elegant solution possible and I thought that the union might provide that.
TK
I see. Well the endianess issue is easy to fix by rearanging fields. It looks cleaner with the union indeed and it's unlikely to surprise You with some nasty behaviour, but theoretically it can.
Maciej Hehl
This particular union trick actually has well-defined; however it is defined as reading the chars of the DWORD in machine order in the same way a memcpy onto a char array would have.
Joshua
@Joshua I dont suppose you could provide a few sources that I could read up on?
TK
TK: The stock definition of memcpy is: void *memcpy(void *ss, const void *tt, size_t n) { char *s = ss, *t = tt; while (n--) *s++ = *t++; return ss; } When combined with the union prefix rule and struct packing this yields a complete definition.
Joshua
+2  A: 

Yes, this is an endianness issue. If you are supporting just one platform, you can lay out the struct members according to the architecture's endianness. If you are dealing with multiple architectures, you will need #define multiple struct member layouts, dependent on endianness.

Structs and unions always look more elegant to me, but bitwise operations are the more portable of the two. When I do stuff like this, I stick an assert into my startup code to make sure the struct is the size I expect it to me, before indeterminate nastiness ensues.

In C++, you might write a class that encapsulates your data and performs your operations for you.

WhirlWind
+1  A: 

To make a union work for this you need to make sure you have the endianess issues correct, and you need to make sure that padding issues are correct (which will require non-standard #pragma statements or such). Even if you're not interested in portability (DirectX being Windows only), I'd still suggest that you use the inline functions for this purpose. You may need to have several very similar ones, but I might argue that the added complexity of those several functions is still less than the union. They're probably easier to get right, will have less chance of breaking (especially breaking silently) if you change compilers, and will be easier for the next guy reading the code to be confident that they're doing what he expects.

As an aside - why did you use #pragma pack(2) instead of #pragma pack(1)?

And if you do use #pragma pack don't forget to 'save & restore' the original pack settings so other stuff doesn't behave unexpectedly:

#pragma pack(push)
#pragma pack(2)
//...
#pragma pack(pop)
Michael Burr
Good point about resetting the packing options ... I didn't know you had to do that! As for the #pragma pack(2) issue, this was left over from an example on msdn and I forgot to change the number. I already have a working solution which uses a jumble of masks and bit shifts but I know from experience that this can baffle new developers (myself included when I started!) I was aiming for something more akin to c# propertieswith the union. I guess its back to the drawing board then!
TK
Why use #pragma pack at all?
Goz