Pack two 8 bits into a 16 bits channel _ HLSL

Started by
4 comments, last by longlong9 10 years, 3 months ago

Hey .

I am using a R16G16B16A16_Float format for my render target. I have filled R,G, and B channel. So the alpha channel is left and I have two more variables to store. These varibales are both 8 bits. So far I got this :



half Pack( uint a, uint b ) // a and b both range from 0 to 255
{
    a*=256;//shifting 8 bits
    uint packed = a|b;
    
    ////// How to put this to a half ? 
}

So how can I put the exact bits into the alpha channel ? And then convert the float to uint again to get back a and b values ?

Advertisement

I think hlsl is missing the function you'd need to do this. If it was 32-bit floats you could just use asfloat() to handle it.

I guess you could change the render target to an integer one, and use f32tof16 to encode the float data, but it's not ideal.

You could also sacrifice some precision and simply do "return (a * 256) + b;".

Any encoding you use would mean you'd need to use point sampling when reading back from the render target as any texture filtering would mess up the encoding.

The other option is to use MRT to write extra values out to a second render target, which I suspect would be quicker and easier than encoding them into the existing one. It also avoids the texture filtering issues.

Thanks Adam . Your encoding sounds weired to me . If I want to sacrifice precision are these good encoding and decoding functions ?

half encode(uint a, uint b )
{
Return ( a + (b/1000.0f) );
}

Void decode( out uint a , out uint b , half packed)
{
a = (uint) packed;

b = (packed-a)*1000;
}

I am not using MRT because i think intuitively would be slower that way. I have to sample twice to get Gbuffer data . Am i thinking right ?

Thanks again for your time.

Coming up with this on the fly, but assuming your inputs are both in the range 0-1, you should be able to pack the 2 values by storing one value as integer, and the second as fractional, into your alpha:


float Pack( in float a, in float b )
{
  return floor(a * 255) + b; // stores A as integer, B as fractional
}

And to unpack, you can use:


float Unpack( in float c, inout float a, inout float b )
{
  a = floor(c) / 255; // removes the fractional value and scales back to 0-1
  b = frac(c); // removes the integer value, leaving B (0-1)
}

Alternatively, this might be faster for unpacking:


float Unpack( in float c, inout float a, inout float b )
{
  b = frac(c); // removes the integer value, leaving B (0-1)
  a = (c - b) / 255; // removes the fractional value (B) and scales back to 0-1
}

I haven't tested these at all, but I think they should work. smile.png

Edit: Just re-read the inital post and noticed you're using values from 0-255 uint, instead of float 0-1. The concept should be the same, you can either scale from 0-255 to 0-1, or you can just invert the scaling I applied (instead of increasing A, decrease B).

The reasons I picked "(a * 256) + b" were:

- The biggest finite value you can store in a half float is 65504 which is just over 255*256. 255 * 1000 would end up as infinity.

- You don't want unused values in the range, or you're throwing away more precision than necessary.

That leaves two obvious options, which are roughly equally good: "(a * 256) + b" and "(a / 256.0) + b". If you care more about precision than performance you can use the sign bit of the value for extra storage, for example by encoding as ((a * 256) + b) - 32768).

MRT will be slower if the performance of what you're doing is limited by memory bandwidth. It should be quicker if you're ALU bound as it will avoid the extra encoding and decoding work.

I'd suggest implementing the simple option first, you can always come back to it later if your profiling says need to optimize it.

u can pack as this (float)((a << 256) / max_short) + ( (float)(b / max_short))

This topic is closed to new replies.

Advertisement