1

Before starting:

A2B10G10R10 (2 bits for the alpha, 10 bits for each color channels)
A8B8G8R8 (8 bits for every channels)

Correct me if I'm wrong, but is it right that the A2B10G10R10 pixel format cannot be displayed directly on screens ?

If so, I would like to convert my A2B10G10R10 image to a displayable A8B8G8R8 one either using OpenCV, Direct3D9 or even manually but I'm really bad when it comes to bitwise operation that's why I need your help.

So here I am:

// Get the texture bits pointer
offscreenSurface->LockRect(&memDesc, NULL, 0);
// Copy the texture bits to a cv::Mat 
cv::Mat m(desc.Height, desc.Width, CV_8UC4, memDesc.pBits, memDesc.Pitch);
// Convert from A2B10G10R10 to A8B8G8R8
???

Here how I think I should do for every 32 bits pack:

  1. Copy the first original 2 bits into the first converted 8 bits
  2. Scale every original 10 bits to every converted 8 bits (How to do that ?) for every other channels

Note:

  • The cv::cvtColor doesn't seem to propose the format conversion I need
  • I can't use IDirect3DDevice9::StretchRect method
  • Even Google seems to be lost on this subject

So to resume, the question is: How to convert a A2B10G10R10 pixel format texture to a A8B8G8R8 one ?

Thanks. Best regards.

2 Answers2

4

I'm not sure why you are using legacy Direct3D 9 instead of DirectX 11. In any case, the naming scheme between Direct3D 9 era D3DFMT and the modern DXGI_FORMAT is flipped, so it can be a bit confusing.

  • D3DFMT_A8B8G8R8 is the same as DXGI_FORMAT_R8G8B8A8_UNORM
  • D3DFMT_A2B10G10R10 is the same as DXGI_FORMAT_R10G10B10A2_UNORM
  • D3DFMT_A8R8G8B8 is the same as DXGI_FORMAT_B8G8R8A8_UNORM

  • There is no direct equivalent of D3DFMT_A2R10G10B10 in DXGI but you can swap the red/blue channels to get it.

There's also a long-standing bug in the deprecated D3DX9, D3DX10, and D3DX11 helper libraries where the DDS file format DDPIXELFORMAT have the red and blue masks backwards for both 10:10:10:2 formats. My DDS texture readers solve this by flipping the mapping of the masks to the formats on read, and always writing DDS files using the more modern DX10 header where I explicitly use DXGI_FORMAT_R10G10B10A2_UNORM . See this post for more details.

The biggest problem with converting 10:10:10:2 to 8:8:8:8 is that you are losing 2 bits of data from the R, G, B color channels. You can do a naïve bit-shift, but the results are usually crap. To handle the color conversion where you are losing precision, you want to use something like error diffusion or ordered dithering.

Furthermore for the 2-bit alpha, you don't want 3 (11) to map to 192 (11000000) because in 2-bit alpha 3 "11" is fully opaque while 255 (11111111) is in 8-bit alpha.

Take a look at DirectXTex which is an open source library that does conversions for every DXGI_FORMAT and can handle legacy conversions of most D3DFMT. It implements all the stuff I just mentioned.

The library uses float4 intermediate values because it's built on DirectXMath and that provides a more general solution than having a bunch of special-case conversion combinations. For special-case high-performance use, you could write a direct 10-bit to 8-bit converter with all the dithering, but that's a pretty unusual situation.

With all that discussion of format image conversion out of the way, you can in fact render a 10:10:10:2 texture onto a 8:8:8:8 render target for display. You can use 10:10:10:2 as a render target backbuffer format as well, and it will get converted to 8:8:8:8 as part of the present. Hardware support for 10:10:10:2 is optional on Direct3D 9, but required for Direct3D Feature Level 10 or better cards when using DirectX 11. You can even get true 10-bit display scan-out when using the "exclusive" full screen rendering mode, and Windows 10 is implementing HDR display out natively later this year.

Chuck Walbourn
  • 38,259
  • 2
  • 58
  • 81
  • Wow, thanks for this complete answer. To elabortate about Direct3D 9, I'm just using an Engine which is based on it (VBS3 - The Arma 3 engine). And also, I've never worked with Direct3D API. Anyway, it seems very tricky to do what I want ! I will look at DirectXTex. –  Apr 01 '16 at 07:33
1

There's a general solution to this, and it's nice to be able to do it at the drop of a hat without needing to incorporate a bulky library, or introduce new rendering code (sometimes not even practical).

First, rip it apart. I can never keep track of which order the RGBA fields are in, so I just try it every way until one works, a strategy which reliably works every time.. eventually. But you may as well trust your analysis for your first attempt. The docs I found said D3D is listing them from MSB to LSB, so in this case we have %AA RRRRRRRRRR GGGGGGGGGG BBBBBBBBBB (but I have no idea if that's right)

b = (src>> 0) & 0x3FF;
g = (src>>10) & 0x3FF;
r = (src>>20) & 0x3FF;
a = (src>>30) & 0x003;

Next, you fix the precision. Naive bit-shift frequently works fine. If the results are 8 bits per channel, you're no worse off than you are with most images. A shift down from 10 bits to 3 would look bad without dithering but from 10 to 8 can look alright.

r >>= 2; g >>= 2; b >>= 2;

Now the alpha component does get tricky because it's shifting the other way. As @chuck-walbourn said you need to consider how you want the alpha values to map. Here's what you probably want:

%00 -> %00000000
%01 -> %01010101
%10 -> %10101010
%11 -> %11111111

Although a lookup table with size 4 probably makes the most sense here, there's a more general way of doing it. What you do is shove your small value to the top of the big value and then replicate it. Here it is with your scenario and one other more interesting scenario:

%Aa -> %Aa Aa Aa Aa 
%xyz -> %xyz xyz xy

Let's examine what would happen for xyz with a lookup table:

%000 -> %000 000 00 (0)
%001 -> %001 001 00 (36) +36
%010 -> %010 010 01 (73) +37
%011 -> %011 011 01 (109) +36
%100 -> %100 100 10 (146) +37
%101 -> %101 101 10 (182) +36
%110 -> %110 110 11 (219) +37
%111 -> %111 111 11 (255) +36

As you can see, we get some good characteristics with this approach. Naturally having a %00000000 and %11111111 result is of paramount importance.

Next we pack the results:

dst = (a<<24)|(r<<16)|(g<<8)|(b<<0);

And then we decide if we need to optimize it or look at what the compiler does and scratch our heads.

zeromus
  • 1,648
  • 13
  • 14
  • Thank you a lot. I think I got the idea ! It was mostly for debugging purpose so I didn't implement it (overkill). But I hope it think it will help others people. –  Apr 15 '16 at 09:08