Take screenshot of DirectX full-screen application
Asked Answered
E

7

18

This boggles me. DirectX bypasses everything and talks directly to the device driver, thus GDI and other usual methods won't work - unless Aero is disabled (or unavailable), all that appears is a black rectangle at the top left of the screen. I have tried what other have suggested on several forums, using DirectX to get the back buffer and save it, but I get the same result:

device->GetFrontBufferData(0, surface); D3DXSaveSurfaceToFile("fileName", D3DXIFF_BMP, surface, NULL, NULL);

Is there any way to get a screenshot of another full-screen DirectX application when Aero is enabled?

Excommunicate answered 25/12, 2009 at 22:50 Comment(4)
Without Aero you code works well? E.g. on any DirectX game?Ambrosius
Yep, it does. Except until the application uses antialising.Excommunicate
With Aero enabled you could use BitBlt ... I'm just sayin'Highness
@Highness Are you sure? I just tried that, my BitBlt code works when the capture source is in window mode but not in fullscreen. In fullscreen the result bitmap is only black pixels. (Windows 8.1)Calculating
P
23

Have a look at Detours.

Using Detours, you can instrument calls like Direct3DCreate9, IDirect3D9::CreateDevice and IDirect3D9::Present in which you perform the operations necessary to setup and then do a frame capture.

Puton answered 26/12, 2009 at 0:3 Comment(1)
there is no reason for a downvote, this is really how it's donePuton
C
9

Here is a C# example of hooking IDirect3DDevice9 objects via DLL injection and function hooking using EasyHook (like Microsoft Detours). This is similar to how FRAPS works.

This allows you to capture the screen in windowed / fullscreen mode and uses the back buffer which is much faster than trying to retrieve data from the front buffer.

A small C++ helper DLL is used to determine the methods of the IDirect3DDevice9 object to hook at runtime.

Update: for DirectX 10/11 see Screen capture and overlays for D3D 9, 10 and 11

Cargill answered 15/5, 2010 at 2:2 Comment(2)
your link is bad nowEvanthe
Thanks @PaxBeach, fixedCargill
C
3

This is a snippet of the code I used as test just now, it seems to work.

width and height are the size of the SCREEN in windowed mode not the windows. So for me they are set to 1280 x 1024 and not the window I'm rendering to's size.

You'd need to replace mEngine->getDevice() with some way of getting your IDirect3DDevice9 too. I just inserted this code into a random d3d app I had to make it easier to test. But I can confirm that it captures both the output from that app AND another d3d app running at the same time.

Oh I've assumed this is D3D9 as you didn't say, I'm not sure about d3d10 or 11

IDirect3DSurface9* surface;
mEngine->getDevice()->CreateOffscreenPlainSurface(width, height, D3DFMT_A8R8G8B8,     D3DPOOL_SCRATCH, &surface, NULL);
mEngine->getDevice()->GetFrontBufferData(0, surface);
D3DXSaveSurfaceToFile("c:\\tmp\\output.jpg", D3DXIFF_JPG, surface, NULL, NULL);
surface->Release();
Clarendon answered 26/12, 2009 at 14:54 Comment(1)
I should say that I just got a black rectange too unless the width, height were correctly set to the full screen size, and it seems necessary to use that exact D3DFMT as well for your buffer format, at least I couldn't get it to work with a different format.Clarendon
S
2

There is an open source program like fraps: taksi but looks outdated

Square answered 26/12, 2009 at 13:23 Comment(0)
B
2

Here is some discussion of how Fraps works. It is not simple.

http://www.woodmann.com/forum/archive/index.php/t-11023.html

Any trick that tries to read the front buffer from a different DirectX device, I suspect may only occasionally work due to luck of uninitialized memory.

Binky answered 29/12, 2009 at 7:5 Comment(0)
P
2

Following J99's answer, I made the code work for both windowed and fullscreen modes. It is also done in D3D9.

  IDirect3DSurface9* surface;
  D3DDISPLAYMODE mode;
  pDev->GetDisplayMode(0, &mode); // pDev is my *IDirect3DDevice
  // we can capture only the entire screen,
  // so width and height must match current display mode
  pDev->CreateOffscreenPlainSurface(mode.Width, mode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &surface, NULL);
  if(pDev->GetFrontBufferData(0, surface)==D3D_OK)
  {
    if(bWindowed) // a global config variable
    {
      // get client area in desktop coordinates
      // this might need to be changed to support multiple screens
      RECT r;
      GetClientRect(hWnd, &r); // hWnd is our window handle
      POINT p = {0, 0};
      ClientToScreen(hWnd, &p);
      SetRect(&r, p.x, p.y, p.x+r.right, p.y+r.bottom);
      D3DXSaveSurfaceToFile(szFilename, D3DXIFF_JPG, surface, NULL, &r);
    }
    else
      D3DXSaveSurfaceToFile(szFilename, D3DXIFF_JPG, surface, NULL, NULL);
  }
  surface->Release();

It looks like format and pool parameters of CreateOffscreenPlainSurface must be exactly the same.

Pogy answered 2/10, 2012 at 11:33 Comment(0)
A
1

You might want to take a look at my Investigo project.

It uses a DirectX proxy DLL to intercept DirectX API functions.

There is already code in there to take screenshots during the call to Present. Although it isn't yet accessible from the UI. You should be able to enable the code easily though.

http://www.codeproject.com/Articles/448756/Introducing-Investigo-Using-a-Proxy-DLL-and-embedd

Appanage answered 10/11, 2012 at 8:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.