web-dev-qa-db-fra.com

Capture d'écran à l'aide de DirectX

Je sais comment utiliser GDI pour capturer l'écran, mais il est très lent (il capture à peine 10 fps)

J'ai lu que DirectX offre la meilleure vitesse. Mais avant de commencer à apprendre DirectX, je voulais tester un échantillon pour voir s'il était vraiment aussi rapide.

J'ai trouvé ceci question qui offre un exemple de code pour le faire:

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();
}

J'ai essayé d'inclure les fichiers requis. Cependant, tous ne peuvent pas être inclus (par exemple #include <D3dx9tex.h>).

Quelqu'un peut-il fournir un exemple de travail contenant toutes les inclusions requises ou me diriger vers les bibliothèques à installer?.

J'utilise Visual C++ 2010 Express sur Windows 7 Ultimate (x64).


Modifier:

De plus, ce code n'est pas complet, par exemple quel est l'identifiant Device?!

13
user4592590

Voici un exemple de code pour capturer l'écran avec DirectX 9. Vous ne devriez pas avoir à installer de SDK (à l'exception des fichiers standard fournis avec Visual Studio, même si je n'ai pas testé VS 2010).

Créez simplement une application console Win32 simple, ajoutez ce qui suit dans le fichier stdafx.h:

  #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

Voici l'exemple d'implémentation principale

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

Cela permettra de capturer 10 fois l'écran et d'enregistrer les images "cap% i.png" sur le disque. Il affichera également le temps nécessaire pour cela (l'enregistrement des images n'est pas compté pendant ce temps, seules les captures d'écran). Sur ma machine (Windows 8 - Dell Precision M2800/i7-4810MQ-2.80GHz/Intel HD 4600 qui est une machine assez merdique ...), il faut 100 captures de 1920x1080 en ~ 4sec, donc environ 20/25 ips.

  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;
  }

Notez que ce code lie implicitement à WIC (une bibliothèque d'imagerie incluse avec Windows depuis un certain temps maintenant) pour enregistrer les fichiers image (vous n'avez donc pas besoin de la célèbres D3DXSaveSurfaceToFile qui nécessitent l'installation d'anciens SDK DirectX):

  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;
  }

Et quelques macros que j'ai utilisées:

  #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;}}

Remarque: pour les clients Windows 8+, tous ces éléments (sauf WIC) doivent être supprimés au profit de Desktop Duplication API .

19
Simon Mourier

Vous n'avez pas indiqué les exigences pour les versions Windows cibles. Si vous n'avez pas besoin de prendre en charge Windows 7, Windows 8 comprend une nouvelle interface DXGI sympa IDXGIOutputDuplication qui permet de créer un objet COM qui duplique la sortie d'une carte vidéo et fournit un accès CPU à la mémoire vidéo via IDXGIOutputDuplication :: MapDesktopSurface . MSDN a tout à fait un bon exemple qui capture le bureau à travers cela et le dessine à l'intérieur d'un formulaire et fonctionne bien et en douceur. Donc, si Windows 7 n'est pas un must have, je vous suggère de regarder cela.

4

Vous pouvez obtenir le SDK DirectX à partir de Microsoft.com/en-ca/download/details.aspx?id=6812 (publié par @ymes). Ce SDK est compatible avec toutes les versions de Windows, y compris XP. Reportez-vous à sa documentation pour savoir comment inclure/lier avec D3D9.

Dans votre exemple, Device est un IDirect3DDevice9. Chaque application D3D9 doit en créer une. Il est très facile de trouver un exemple de code sur la façon d'en créer un (par exemple https://msdn.Microsoft.com/en-us/library/windows/desktop/bb204867%28v=vs.85%29.aspx ).

Dans votre exemple de code, seuls les contenus rendus dans DirectX sont capturés, ce qui, je suppose, n'est pas votre intention. Pour capturer la totalité de l'écran (dont je suppose qu'il s'agit de l'objectif), au lieu d'utiliser IDirect3DDevice9::GetRenderTarget, Tu devrais utiliser IDirect3DDevice9::GetFrontBufferData, comme dans ce tutoriel ( http://dim-i.net/2008/01/29/taking-screenshots-with-directx-and-dev-cpp/ ). Si vous recherchez la vitesse, vous ne devez pas recréer la surface hors écran à chaque image, comme dans votre exemple et dans ce didacticiel. Le pool de mémoire doit être D3DPOOL_SYSTEMMEM dans ce cas, pas D3DPOOL_SCRATCH. Selon la taille de l'écran, le goulot d'étranglement probable sera l'écriture des images sur le disque.

Notez également que l'écran capturé sera pour l'adaptateur utilisé pour créer le IDirect3DDevice9. Il s'agit du premier paramètre à IDirect3D9::CreateDevice. Ce n'est une préoccupation que si la capture de plusieurs moniteurs est possible.

1
MuertoExcobito