1

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:

enter image description here

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.

SMUsamaShah
  • 7,677
  • 22
  • 88
  • 131
Robert Jones
  • 587
  • 7
  • 25
  • I'm gonna guess you have to grab the front buffers format and convert to your off screen buffer format. – paulm Sep 10 '14 at 12:42
  • @paulm Do you mean to change the front buffer format ? Because I think this can't be done. I was thinking using the backbuffer but I don't think it is relevant for my application. Thanks – Robert Jones Sep 10 '14 at 12:52
  • I mean if frontbuffer.format != yourbuffer.format then convert it to yourbuffer.format – paulm Sep 10 '14 at 12:58
  • @paulm Ok I think I get what you mean but how can you convert your frontbuffer format ? Just as indicated on this topic, it is written that the front buffer format can't be specified. http://www.gamedev.net/topic/576570-how-initialize-front-buffer-format-/. Please correct me if I misunderstood what you meant. Thank your for your help – Robert Jones Sep 10 '14 at 14:41
  • Either there's a bug in `GetFrontBufferData` or there's a bug in your code. Is your desktop resolution really 640x480? Is the desktop colour depth 16-bits or 16-colour (4-bits)? – Ross Ridge Sep 12 '14 at 23:31
  • @RossRidge I am pretty sure the problem is coming from my program (99,99% chance ^^). My desktop resolution is 1920x1080 but I put these lines as an example of what I did. And my desktop color desk is 16 bits and not 4 bits. It is a recent display. Thank you for your help. I will edit my post to add more elements – Robert Jones Sep 14 '14 at 09:41
  • 1
    You should download and use PIX for debugging D3D9 calls. – derpface Sep 19 '14 at 13:50

2 Answers2

0

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.

Niello
  • 83
  • 6
  • This is exactly what I do (see my edited post). I have this working up until now for 32 bits witout any problem.Except one thing, the BackBufferFormat doesn't allow me to use any kind of format when Creating the Offscreen surface. I am only able to use 'D3DFMT_A8R8G8B8' otherwise GetFrontBufferData crashes, do you know why ? Thank you for your answer btw ! – Robert Jones Sep 14 '14 at 10:10
  • Sorry, I definitely need to buy a glasses. Still, I would recommend you to use `IDirect3DDevice9::GetDisplayMode` just before creating an offscreen surface, and filling its settings from the structure returned. Also, didn't you try to get default render target (`device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &pSurface)` and then request data from it? I use this way, and it works, but I never tried it in 16bits. – Niello Sep 16 '14 at 02:23
  • I don't know why it crashes, but I wonder why it allows you to use a8r8g8b8 at all. It is a 32-bit format. Try what GetDisplayMode returns you, or some 16-bit surface format. May be that is a reason? – Niello Sep 16 '14 at 02:28
  • Thanks for your comment. I tried to get the default render target but I think I didn't do it right. Do you have any good examples which I could use in this application ? Using `GetBackBuffer`, I've always had a black screen for my screenshots because I guess I wasn't correctly filling my back buffer. In 16 bits, the format returned from `GetAdapterDisplayMode` is `D3DFMT_R5G6B5` (16 bits RGB). However, if I use this format when creating the offScreenSurface, GetFrontBuffer crashes immediately. I'll try the `GetDisplayMode` method and I'll keep you in touch. Thanks for your time again ! – Robert Jones Sep 16 '14 at 07:39
  • 1
    For an example you may look at https://code.google.com/p/deusexmachina/source/browse/branches/Dev/DEM/Src/L1/Render. There is a `CRenderServer::SaveScreenshot()` and `CRenderTarget::CreateDefaultRT()` methods, which is what you need. Again, I never tried to use it with 16-bit display. – Niello Sep 16 '14 at 22:13
  • Thanks for this code. However, when using this method, I always have a black screen and I just don't know why even in 32 bits. Could you tell me what is wrong in my code in my edit3 ? – Robert Jones Sep 17 '14 at 08:46
  • I am pretty confused but quoting MSDN about `GetFrontBufferData()` function: _This method is the only way to capture an antialiased screen shot._ What about `GetBackBuffer() or GetRenderTargetData`? Isn't it considered as a way to capture screenshots ? – Robert Jones Sep 17 '14 at 11:11
  • 1
    Two things to keep in mind, if you still not: 1. Call `GetBackBuffer()` just before a call to `Present()`; 2. For multiple monitors, set a correct swap chain index, or, possibly, get an `IDirect3D9SwapChain` for each display and then use their `GetBackBuffer()` or `GetFrontBufferData()`. What I also suggest is to analyze your bad 16bpp screenshot data in a hex editor, by capturing some test image like purple quad. So you will understand layout of data returned from driver. – Niello Sep 17 '14 at 21:37
  • 1
    There is also a question http://stackoverflow.com/questions/8158027/idirect3ddevice9getfrontbufferdata-fails-with-segmentation-fault where reinstalling driver was helpful. And finally you may look at Ogre3D's `void D3D9RenderWindow::writeContentsToFile()` at https://gitorious.org/ogre/ogre/source/068a5a7a187e6d71a2af35c69810c9dbf7e77baa:RenderSystems/Direct3D9/src/OgreD3D9RenderWindow.cpp – Niello Sep 17 '14 at 21:37
  • Here is some explanation about MSAA and BackBuffer. Though it's about DX11, a theory is pretty much the same, I think. http://stackoverflow.com/questions/18459004/directx-11-front-buffer – Niello Sep 17 '14 at 21:42
  • Thank you very much. I am trying to get it work with `GetBackBuffer()` with just one monitor in 32 bits (best config ever). This way I don't need to create an additional swap chain, right ? Even adding `Present()`, I get a black screen when saving to BMP file. I am confused about `GetBackbuffer` and `GetFrontBuffer`, do I absolutely need to use `GetFrontBufferData` for screenshot capture or can I do it with the default render target only ? On OGRE3D I can see that they use both functions but one to update buffer content (back) and the other one to write content to File (front). – Robert Jones Sep 18 '14 at 08:19
  • Hm. Do you actually render something? If you don't, all this messing with back buffers and render targets has no meaning. Front buffer is what your display shows at the time. Back buffer is an image you rendered, that is to be present to the screen. If you try to capture screen that was no rendered by you, I can't guess how d3d would process it. I see three pathes to go. – Niello Sep 18 '14 at 10:31
  • 1. Install the latest video driver and directx runtime 2. Analyze raw data returned to surface in 16bpp, maybe some shift occured and you will be able to fix it by manual postprocessing 3. Fallback to OpenGL or GDI for the cases when d3d fails. – Niello Sep 18 '14 at 10:32
  • So I guess I'm not rendering anything. Because I just want to capture my desktop (and not a game) so that must be it. 1. I updated my video driver and have the latest directx runtime 2. I will analyze this in a few minutes 3. I was using GDI before and it was working great but I wanted to use DirectX or OpenGL to extend my app possibilities. I can try OpenGl if there are some ways of capturing screenshots. Last question, when you're rendering it means the graphic card is processing data. THerefore if I want to capture my desktop, I'm not using the graphic card for it, right ? Thanks a lot – Robert Jones Sep 18 '14 at 11:03
  • 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. 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. I will update my answer with it. – Niello Sep 19 '14 at 13:39
0

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 !

Robert Jones
  • 587
  • 7
  • 25