C++ Direct3D9 GetFrontBufferData with 16 bits colo

2019-08-12 06:14发布

问题:

I am currently developing a little screenshot application which records both of my screen's desktop in a file. I am using the GetFrontBufferData() function and it is working great.

Unfortunately when changing the screen color depth from 32 to 16 bits (to perform some tests) I have a bad image (purple image with changed resolution) and the recorded screenshot has a very poor quality:

Does someone know if there is a way to use GetFrontBufferData() with a 16 bits screen ?

edit:

My init direct3D:

    ZeroMemory(&d3dPresentationParameters,sizeof(D3DPRESENT_PARAMETERS));//Fills a block of memory with zeros.
    d3dPresentationParameters.Windowed = TRUE;
    d3dPresentationParameters.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
    d3dPresentationParameters.BackBufferFormat = d3dFormat;//d3dDisplayMode.Format;//D3DFMT_A8R8G8B8;
    d3dPresentationParameters.BackBufferCount = 1;
    d3dPresentationParameters.BackBufferHeight = gScreenRect.bottom = uiHeight;
    d3dPresentationParameters.BackBufferWidth = gScreenRect.right = uiWidth;
    d3dPresentationParameters.MultiSampleType = D3DMULTISAMPLE_NONE;
    d3dPresentationParameters.MultiSampleQuality = 0;
    d3dPresentationParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dPresentationParameters.hDeviceWindow = hWnd;
    d3dPresentationParameters.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
    d3dPresentationParameters.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;

The thread I use to capture screenshots:

    CreateOffscreenPlainSurface(uiWidth, uiHeight, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, pBackBuffer, NULL)) != D3D_OK )
    {
        DBG("Error: CreateOffscreenPlainSurface failed = 0x%x", iRes);
        break;
    }

    GetFrontBufferData(0, pCaptureSurface)) != D3D_OK)
    {
        DBG("Error: GetFrontBufferData failed = 0x%x", iRes);
        break;
    }    

    //D3DXSaveSurfaceToFile("Desktop.bmp", D3DXIFF_BMP, pBackBuffer,NULL, NULL); //Test purposes

    ZeroMemory(lockedRect, sizeof(D3DLOCKED_RECT));     
    LockRect(lockedRect, NULL, D3DLOCK_READONLY)) != D3D_OK )
    {
        DBG("Error: LockRect failed = 0x%x", iRes);
        break;
    }

    if( (iRes = UnlockRect()) != D3D_OK )
    {
        DBG("Error: UnlockRect failed = 0x%x", iRes);
        break;
    }   
/**/        
  • This code is perfectly working with 32 bits color depth but not with 16bits.
  • When creating the device I create 2 devices for both screens (iScreenNber). This is also working in 32bits (not in 16).
  • When saving the captured screenshot into 2 bmp files for testing (in 16 bits), I have one screen which represents the main display perfectly and the other screen is black.
  • When using memcpy to use pData, I have the above screenshot with purple color and bad resolution

edit2:

I noticed the following:

  • When saving Offscreen surface to a BMP file, I get the main display (on 1.bmp) which is refreshed each frame (so it is working just fine). For the second display, I just get the first frame then nothing more.
  • Quoting MSDN for GetFrontBufferData "The buffer pointed to by pDestSurface will be filled with a representation of the front buffer, converted to the standard 32 bits per pixel format D3DFMT_A8R8G8B8." I guess this is a problem for 16 bits color depth.
  • The first problem comes from the memcpy which does not handle properly the 16 bits color depth and I still don't know why ----> Help needed for this !!
  • Second problem is the second display which is not working and I don't why either

What am I doing wrong here ? I just get a black image on my Desktop N°xx.bmp file

Thank you very much for your help.

回答1:

This is how I create a surface to capture screenshots:

IDirect3DSurface9* pCaptureSurface = NULL;
HRESULT hr = pD3DDevice->CreateOffscreenPlainSurface(
    D3DPresentParams.BackBufferWidth,
    D3DPresentParams.BackBufferHeight,
    D3DPresentParams.BackBufferFormat,
    D3DPOOL_SYSTEMMEM,
    &pCaptureSurface,
    NULL);
pD3DDevice->GetFrontBufferData(0, pCaptureSurface);

If you didn't store D3DPresentParams anywhere, you can use IDirect3DDevice9::GetDisplayMode to obtain width, height and format of your swap chain. All operations of resizing and format conversion you can perform after capturing a front buffer. Also, as I know, display format doesn't support alpha channel, so it typically is D3DFMT_X8R8G8B8, not D3DFMT_A8R8G8B8.

Update: Actually, you try to capture a whole screen by using d3d device, without rendering anything. A purpose of d3d/opengl is to create or process images and do it GPU-accelerated. Taking a screenshot is just copying some video memory, it doesn't use all GPU power. So, using any GPU API brings no significant gain. Moreover, when you capture front buffer rendered not by yourself, strange things occur, you see. To extend your app you may capture image by GDI and then load it into texture and do any GPU postprocessing.



回答2:

So i found some answers to my problem.

1) Second monitor wasn't working and I was unable to capture screenshot from it in 16 bits

This comes from the memcpy(..) line in the code. Because I am working with a 16 bits monitor, when executing the memcpy, the surface memory is corrupt and this leads to a black screen.

I still didn't find the solution for this but I'm working on.

2) The colors of the screenshot are wrong

This is, without any surprise, due to the 16 bits color depth. Because I am using GetFrontBufferData, and I am quoting Microsoft: The buffer pointed to by pDestSurface will be filled with a representation of the front buffer, converted to the standard 32 bits per pixel format D3DFMT_A8R8G8B8. This means, if I want to use the pixel data from LockRect(...), I have to "re-convert" my data into 16 bits mode. Therefore, I need to convert my pData data from D3DFMT_A8R8G8B8 to D3DFMT_R5G6B5 which is pretty simple.

3) How to debug the application ?

Thanks to your comments, I've been told that I should analyze pScreeInfo->pData content when I was in 16bits (thanks to Niello). Therefore, I've created a simple method using raw data from pScreeInfo->pData and copying in a .bmp:

    HRESULT hr;
    DWORD dwBytesRead;
    UINT uiSize = 1920 * 1080 * 4;
    HANDLE hFile;

    hFile = CreateFile(TEXT("data.raw"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    BOOL bOk = ReadFile(hFile, pData, uiSize, &dwBytesRead, NULL);

    if(!bOk)
        exit(0);

    pTexture = NULL;
    hr = pScreenInfo->g_pD3DDevice->CreateTexture(width, height, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &pTexture, NULL);

    D3DLOCKED_RECT lockedRect;

    hr = pTexture->LockRect(0, &lockedRect, NULL, D3DLOCK_READONLY);

    memcpy(lockedRect.pBits, pData, lockedRect.Pitch * height);

    hr = pTexture->UnlockRect(0);

    hr = D3DXSaveTextureToFile(test, D3DXIFF_BMP, pTexture,NULL);

    bOk = CloseHandle(hFile);

    SAFE_RELEASE(pTexture);

This piece of code allowed me to notice that pData data was correct and I could get a good .bmp file at the end which means that GetFrontBufferData(...) was correctly working and the problem comes from the memcpy(...)

4) Remaining problems

I am still trying to know how I can solve the memcpy issue to see where the problem comes from. This is the last problem since the colors are good now (thanks to the 32bits to 16 bits conversion)

Thank everybody for your helpful comments !