Capture screen using DirectX
Asked Answered
Q

3

22

I know how to use GDI to capture screen, however it is very slow (it barely captures 10 fps)

I have read that DirectX offers the best speed. But before I start learning DirectX I wanted to test a sample to see if it is really that fast.

I have found this question that offers a sample code to do that:

void dump_buffer()
{
   IDirect3DSurface9* pRenderTarget=NULL;
   IDirect3DSurface9* pDestTarget=NULL;
     const char file[] = "Pickture.bmp";
   // sanity checks.
   if (Device == NULL)
      return;

   // get the render target surface.
   HRESULT hr = Device->GetRenderTarget(0, &pRenderTarget);
   // get the current adapter display mode.
   //hr = pDirect3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,&d3ddisplaymode);

   // create a destination surface.
   hr = Device->CreateOffscreenPlainSurface(DisplayMde.Width,
                         DisplayMde.Height,
                         DisplayMde.Format,
                         D3DPOOL_SYSTEMMEM,
                         &pDestTarget,
                         NULL);
   //copy the render target to the destination surface.
   hr = Device->GetRenderTargetData(pRenderTarget, pDestTarget);
   //save its contents to a bitmap file.
   hr = D3DXSaveSurfaceToFile(file,
                              D3DXIFF_BMP,
                              pDestTarget,
                              NULL,
                              NULL);

   // clean up.
   pRenderTarget->Release();
   pDestTarget->Release();
}

I have tried to include the required files. However not all of them can be included (for example #include <D3dx9tex.h>).

Can anyone provide a working example that has all of the required includes or point me to what libraries I should install.

I am using Visual C++ 2010 Express on Windows 7 Ultimate (x64).


Edit:

Also, this code is not complete, for example what is the Device identifier?!

Quickfreeze answered 4/5, 2015 at 1:14 Comment(6)
@yms I am unable to find a download link for DirectX SDK for Windows 7! Also, why am I able to include this library: #include <D3D9.h>, do I have an older version of DirectX SDK installed or something?Quickfreeze
I tried different methods of screen capturing including DirectX one, while DirectX one was faster than others it didn't provide significant speedup (to cover additional complexity and time spent). Sorry I don't remember any details or numbers.Hammerhead
@Andy T How many screenshots were you able to capture in 1 second?Quickfreeze
I had quite low requirements, something like 10fps, it's enough for desktop capturing but not for game/video capturingHammerhead
Do you want to support Windows 7 as a target platform?Tombola
My new article about this, Windows 8+. codeproject.com/Articles/5256890/…Shaffer
T
33

Here is some sample code to capture the screen with DirectX 9. You shouldn't have to install any SDK (except the standard files that come with Visual Studio, although I didn't tested VS 2010).

Just create a simple Win32 console application, add the following in the stdafx.h file:

  #include <Wincodec.h>             // we use WIC for saving images
  #include <d3d9.h>                 // DirectX 9 header
  #pragma comment(lib, "d3d9.lib")  // link to DirectX 9 library

Here is the sample main implementation

  int _tmain(int argc, _TCHAR* argv[])
  {
    HRESULT hr = Direct3D9TakeScreenshots(D3DADAPTER_DEFAULT, 10);
    return 0;
  }

What this will do is capture 10 times the screen, and save "cap%i.png" images on the disk. It will also display the time taken for this (saving images is not counted in that time, only screen captures). On my (desktop windows 8 - Dell Precision M2800/i7-4810MQ-2.80GHz/Intel HD 4600 which is a pretty crappy machine...) machine, it takes 100 1920x1080 captures in ~4sec, so around 20/25 fps.

  HRESULT Direct3D9TakeScreenshots(UINT adapter, UINT count)
  {
    HRESULT hr = S_OK;
    IDirect3D9 *d3d = nullptr;
    IDirect3DDevice9 *device = nullptr;
    IDirect3DSurface9 *surface = nullptr;
    D3DPRESENT_PARAMETERS parameters = { 0 };
    D3DDISPLAYMODE mode;
    D3DLOCKED_RECT rc;
    UINT pitch;
    SYSTEMTIME st;
    LPBYTE *shots = nullptr;

    // init D3D and get screen size
    d3d = Direct3DCreate9(D3D_SDK_VERSION);
    HRCHECK(d3d->GetAdapterDisplayMode(adapter, &mode));

    parameters.Windowed = TRUE;
    parameters.BackBufferCount = 1;
    parameters.BackBufferHeight = mode.Height;
    parameters.BackBufferWidth = mode.Width;
    parameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
    parameters.hDeviceWindow = NULL;

    // create device & capture surface
    HRCHECK(d3d->CreateDevice(adapter, D3DDEVTYPE_HAL, NULL, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &parameters, &device));
    HRCHECK(device->CreateOffscreenPlainSurface(mode.Width, mode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &surface, nullptr));

    // compute the required buffer size
    HRCHECK(surface->LockRect(&rc, NULL, 0));
    pitch = rc.Pitch;
    HRCHECK(surface->UnlockRect());

    // allocate screenshots buffers
    shots = new LPBYTE[count];
    for (UINT i = 0; i < count; i++)
    {
      shots[i] = new BYTE[pitch * mode.Height];
    }

    GetSystemTime(&st); // measure the time we spend doing <count> captures
    wprintf(L"%i:%i:%i.%i\n", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
    for (UINT i = 0; i < count; i++)
    {
      // get the data
      HRCHECK(device->GetFrontBufferData(0, surface));

      // copy it into our buffers
      HRCHECK(surface->LockRect(&rc, NULL, 0));
      CopyMemory(shots[i], rc.pBits, rc.Pitch * mode.Height);
      HRCHECK(surface->UnlockRect());
    }
    GetSystemTime(&st);
    wprintf(L"%i:%i:%i.%i\n", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);

    // save all screenshots
    for (UINT i = 0; i < count; i++)
    {
      WCHAR file[100];
      wsprintf(file, L"cap%i.png", i);
      HRCHECK(SavePixelsToFile32bppPBGRA(mode.Width, mode.Height, pitch, shots[i], file, GUID_ContainerFormatPng));
    }

  cleanup:
    if (shots != nullptr)
    {
      for (UINT i = 0; i < count; i++)
      {
        delete shots[i];
      }
      delete[] shots;
    }
    RELEASE(surface);
    RELEASE(device);
    RELEASE(d3d);
    return hr;
  }

Note this code implicitely links to WIC (an imaging library included with Windows for quite a time now) to save the image files (so you don't need the famous D3DXSaveSurfaceToFile that require old DirectX SDKs to be installed):

  HRESULT SavePixelsToFile32bppPBGRA(UINT width, UINT height, UINT stride, LPBYTE pixels, LPWSTR filePath, const GUID &format)
  {
    if (!filePath || !pixels)
      return E_INVALIDARG;

    HRESULT hr = S_OK;
    IWICImagingFactory *factory = nullptr;
    IWICBitmapEncoder *encoder = nullptr;
    IWICBitmapFrameEncode *frame = nullptr;
    IWICStream *stream = nullptr;
    GUID pf = GUID_WICPixelFormat32bppPBGRA;
    BOOL coInit = CoInitialize(nullptr);

    HRCHECK(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory)));
    HRCHECK(factory->CreateStream(&stream));
    HRCHECK(stream->InitializeFromFilename(filePath, GENERIC_WRITE));
    HRCHECK(factory->CreateEncoder(format, nullptr, &encoder));
    HRCHECK(encoder->Initialize(stream, WICBitmapEncoderNoCache));
    HRCHECK(encoder->CreateNewFrame(&frame, nullptr)); // we don't use options here
    HRCHECK(frame->Initialize(nullptr)); // we dont' use any options here
    HRCHECK(frame->SetSize(width, height));
    HRCHECK(frame->SetPixelFormat(&pf));
    HRCHECK(frame->WritePixels(height, stride, stride * height, pixels));
    HRCHECK(frame->Commit());
    HRCHECK(encoder->Commit());

  cleanup:
    RELEASE(stream);
    RELEASE(frame);
    RELEASE(encoder);
    RELEASE(factory);
    if (coInit) CoUninitialize();
    return hr;
  }

And some macros I used:

  #define WIDEN2(x) L ## x
  #define WIDEN(x) WIDEN2(x)
  #define __WFILE__ WIDEN(__FILE__)
  #define HRCHECK(__expr) {hr=(__expr);if(FAILED(hr)){wprintf(L"FAILURE 0x%08X (%i)\n\tline: %u file: '%s'\n\texpr: '" WIDEN(#__expr) L"'\n",hr, hr, __LINE__,__WFILE__);goto cleanup;}}
  #define RELEASE(__p) {if(__p!=nullptr){__p->Release();__p=nullptr;}}

Note: for Windows 8+ clients, all these (except WIC) should be dropped in favor of the Desktop Duplication API.

Note: for .NET users, there's a C# version here: DirectN

Tombola answered 9/5, 2015 at 10:12 Comment(10)
I tried to change your code to capture jpeg. The result was not satisfactory as the image will have vertical lines overlay and slow processing. Anyway to speed it up and improve the image quality please?Kentledge
@FeiHapLee - ask another questionTombola
@Simon, posted. Thanks. Please refer #33754412Kentledge
can I use this with a DX11 game?Atoll
This code is great, but when I switch to full screen directx application it takes a screen shot of the screen when I switch and continuously that same image over and over. Any advice?Ferrosilicon
@ChristopherTownsend - you should ask another questionTombola
You can take picture of a game, but the problem if you are in windowed mode, you screenshow the desktop as well.Commissure
Is it possible to save captured image into bitmap in memory ?Staffard
@SimonMourier How can I take a screenshot of a given window instead of entire desktop? I tried setting hDeviceWindow to the HWND of the window and also pass this HWND to CreateDevice() but it didn't help, whole desktop was still captured.Waterway
@Waterway - to capture a specific Window on Windows 10 and higher, use WinRT's WindowsGraphicsCapture blogs.windows.com/windowsdeveloper/2019/09/16/… on lower systems, there's nothing built in.Tombola
P
5

You have not stated the requirements for the target Windows versions. If you do not need to support Windows 7, Windows 8 includes a nice new DXGI interface IDXGIOutputDuplication that allows to create a COM object that duplicates the output of a video adapter and provides CPU access to the video memory through IDXGIOutputDuplication::MapDesktopSurface. MSDN has quite a nice sample that captures the desktop through this and draws it inside a form and works nice and smooth. So if Windows 7 is not a must have, I'd suggest you look at this.

Psalms answered 8/5, 2015 at 7:50 Comment(2)
Sorry, I forgot to mention this. I actually even wants Windows XP support :-)Quickfreeze
@Quickfreeze that is only my subjective opinion but I doubt that you will be able to do this on XP in any other way than GDI which is slow:(Psalms
J
1

You can get the DirectX SDK from microsoft.com/en-ca/download/details.aspx?id=6812 (posted by @yms). This SDK is compatible with all versions Windows, including XP. Refer to its documentation on how to include/link with D3D9.

In your example Device is an IDirect3DDevice9. Every D3D9 application must create one of these. It's very easy to find example code on how to create one (eg. https://msdn.microsoft.com/en-us/library/windows/desktop/bb204867%28v=vs.85%29.aspx).

In your example code, only the contents being rendered in DirectX are being captured, which I assume is not your intention. To capture the entire screen (which I'm assuming is the goal), instead of using IDirect3DDevice9::GetRenderTarget, you should use IDirect3DDevice9::GetFrontBufferData, as in this tutorial (http://dim-i.net/2008/01/29/taking-screenshots-with-directx-and-dev-cpp/). If you're looking for speed, you should not recreate the offscreen surface every frame, as in both your example and this tutorial. The memory pool should be D3DPOOL_SYSTEMMEM in this case, not D3DPOOL_SCRATCH. Depending on the size of the screen, the likely bottleneck will be writing the images to disk.

Also note that the screen captured from this will be for the adapter used to create the IDirect3DDevice9. This is the first parameter to IDirect3D9::CreateDevice. This is only a concern if capturing multiple monitors is a possibility.

Jealousy answered 8/5, 2015 at 13:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.