3
votes

I am trying to convert every frame of my DirectX program to YUV (for video encoding). Therefore I need the RGB(A) values of each pixel on every frame first. I need to get these from the backbuffer.

Because there is no glReadPixels in DirectX, I do the following:

  1. Get pointer to the backbuffer's renderTargetView and get the backbuffer resource
  2. Cast this resource to ID3D10Texture2D
  3. Make a staging texture and CopyResource the texture2D from the previous step.

At this point I can use D3DX10SaveTextureToFile and this staging texture will correctly save the backbuffer as an image.

However, I don't want to use the disk as a detour, I want to get the RGB data right away, so I do the following:

  1. Map the staging resource
  2. Read the pData of the mapped texture to get the RGB(A) values

The problem: the RGB values are rubbish. This is an example for pixel (1,1)

(1, 1) = (-170141183460469230000000000000000000000.000000, -170141183460469230000000000000000000000.000000, -170141183460469230000000000000000000000.000000)

This is particularly strange because I use the same code to Map another staging texture (from another offscreen render target) and this code works just fine there.

This is my code:

// Get resource pointer to backbuffer
ID3D10Resource *backbufferRes;
m_D3D->GetRenderTargetView()->GetResource(&backbufferRes);

// Cast backbuffer resource to texture2D
ID3D10Texture2D* tempTexture = 0;
backbufferRes->QueryInterface(__uuidof(ID3D10Texture2D),(LPVOID*) &tempTexture);
backbufferRes->Release();

// Get the descriptor of this texture2D
D3D10_TEXTURE2D_DESC descDefault; 
tempTexture->GetDesc(&descDefault);

// Create a staging texture desc based on the texture of the backbuffer
D3D10_TEXTURE2D_DESC descStaging;
descStaging = descDefault;
descStaging.Usage = D3D10_USAGE_STAGING;
descStaging.CPUAccessFlags = D3D10_CPU_ACCESS_READ;  
descStaging.BindFlags = 0;

// Create the new empty staging texture
ID3D10Texture2D *texture = 0;
m_D3D->GetDevice()->CreateTexture2D( &descStaging, NULL, &texture);

// Copy the backbuffer texture data (tempTexture) to the staging texture (texture)
m_D3D->GetDevice()->CopyResource(texture, tempTexture);

// This call works perfectly, image is correct!
// D3DX10SaveTextureToFile(texture, D3DX10_IFF_BMP, L"D:\\img.bmp");

// We want to avoid disk access, so instead let's map the texture and read its RGB data
D3D10_MAPPED_TEXTURE2D mappedTexture;
hr = texture->Map(D3D10CalcSubresource(0, 0, 1), D3D10_MAP_READ, 0, &mappedTexture);
FLOAT* m_pBits = (FLOAT*) malloc(4 * descStaging.Width * descStaging.Height * sizeof(FLOAT));
if(!FAILED(hr)) {
    memcpy(m_pBits, mappedTexture.pData, 4 * descStaging.Width * descStaging.Height);
    texture->Unmap(D3D10CalcSubresource(0, 0, 1));
}
texture->Release();
tempTexture->Release();

fp = fopen("D:\\output.txt", "a");
for( UINT row = 0; row < descStaging.Height; row++ )
{
    UINT rowStart = row * mappedTexture.RowPitch / 4;
    for( UINT col = 0; col < descStaging.Width; col++ )
    {
        r = m_pBits[rowStart + col*4 + 0]; // Red (X)
        g = m_pBits[rowStart + col*4 + 1]; // Green (Y)
        b = m_pBits[rowStart + col*4 + 2]; // Blue (Z)
        a = m_pBits[rowStart + col*4 + 3]; // Alpha (W)

        // Save pixel values to disk
        fprintf(fp, "%d %d - %f %f %f\n",  col + 1, row + 1, r, g, b);
    }
}
fclose(fp);

Does anyone have an idea on what the problem might be? All help is really appreciated.

1
How about rendering your frame to a texture as a rendertarget and postprocess it with a rgb to yuv shader? That would be much faster then a manual conversion, because of the parallel capabilities of the gpu.Gnietschow
Thanks! This is actually a great idea, I'll try it out. Rendering to a texture was an alternative I had in mind (it fixes the issue at the cost of another render pass), but I'm still wondering why I get those results.Glenn
It worked like a charm. Thanks again!Glenn

1 Answers

2
votes

Old question, but I think this might be the problem:

After you map the texture with texture->Map(), you try to copy it across into m_pBits all in one go. This won't work unless the RowPitch of the mapped texture is the same as its width (see here).

Instead, you must copy across the image row-by-row:

BYTE* source = static_cast<BYTE*>(mappedTexture.pData);
BYTE& dest = m_pBits;
for (int i = 0; i < IMAGE_HEIGHT; ++i) {
    memcpy(dest, source, IMAGE_WIDTH * 4); // for 4 bytes per pixel
    source += mappedTexture.RowPitch;
    dest += IMAGE_WIDTH * 4;
}