DXGI fast screen capture

Task at hand was capturing desktop or monitor contents under Windows really fast.
Actually, the faster is better because the real contents of the buffer I am writing to will be displayed/rendered in real time.

There are several options of screen capture in Windows. The oldest and the easiest one is using GDI. The minus of it is that it's really slow. The second way of performing capture is DirectX Graphics Infrastructure - you command video card to store the whole screen or one/several monitors's contents inside separate part of its memory (surface). After that, you can either command video card to draw it somewhere else (for example, on texture). Or lock this surface and copy its contents to the main memory. Looks a lot more promising, huh? Of course I chose the DXGI way!

To keep the actual DXGI-based implementation away from the main sources, I had created DXGIManager object incapsulating the actual capture stuff and opened up only a few basic methods:

HRESULT SetCaptureSource(CaptureSource type);
Setting capture source: desktop, monitor1 or monitor2 (milti-mon configuration)
HRESULT GetOutputRect(RECT& rc);
Getting output rect for selected CaptureSource
HRESULT GetOutputBits(BYTE* pBits, RECT& rcDest);
Getting the actual bits for OutputRect
Main time-consuming tasks were:

  • Make the DXGIManager model: in DXGI, there are adapters, every adapter has one or many outputs (monitors). I decided the output to be the basic element of the model which has info about adapter and duplication info. So I am making a vector of structs containing all this data by enumerating all the outputs (IDXGIAdapter1::EnumOutputs) for all the adapters (DXGIFactory1::EnumAdapters1) in the system.
  • Learn the duplication mechanism (DXGIOutputDuplication::AcquireNextFrame)
  • Implementing correct rotation of the frame (DXGI_MODE_ROTATION_IDENTITY, DXGI_MODE_ROTATION_ROTATE90, DXGI_MODE_ROTATION_ROTATE180, DXGI_MODE_ROTATION_ROTATE270) - frame is not rotated by default. I simply exchanged bytes in correct places before returning the bits pointer.
  • Mouse pointer! There is no mouse on the resulting DXGI output and, alas, there is no "show pointer" flag  in the API. So if you want the cursor, please, draw it by hand. Fortunately, there is a great example in here. Although this example is using Visual Studio 2012 and shaders (Visual Studio 2010 does not have the built-in shader compiler), the one can understand what call goes where and it is not a problem to implement the similar stuff (in the current example, it is not working well though the base code is there - in DrawMousePointer).
  • Learn WIC. To save the resulting bitmap to file, I decided to use Windows Imaging Component - it was just something new and interesting to work with... nothing complicated.
Source code is available, as usual, at the GitHub

Although the sample is not working well in 100% cases, it does its job of fast screen capture through DXGI.
Problem solved!

Comments

  1. Hello Pavel, for this code, what is the license for this.

    Thanks,

    Chinh Cao

    ReplyDelete
  2. Hi Chinh,

    I updated the license in the GitHub - it's MIT now.

    ReplyDelete
  3. Hello Pavel,
    I want to modify for shot only one application window content but I failed. Have you any idea for my problem? Thanks Gabor

    ReplyDelete
  4. Hi Gabor,
    The easiest way I see is to capture everything and then - cut out only the window you want to see by its desktop coordinates.
    Otherwise, you will need to play with the code in GetOutputBits and somehow deal with the case where window is spanned on multiple outputs...

    ReplyDelete
  5. Only for windows 8.1 or above? right?

    ReplyDelete
    Replies
    1. Hi Kelvin,

      I haven't specifically tested but as soon as IDXGIOutputDuplication is Windows 8 and up, this solution should have the same limitation.

      Delete
  6. Good work. However, it doesn't capture file explorer's background contents. How to capture the missing contents, should it need shader effects ?

    ReplyDelete
  7. Hi Muhammad,
    File explorer's background? Could you add an issue to the GitHub/attach the screenshot?

    ReplyDelete
    Replies
    1. Hi Pavel,
      Thanks for immediate attention. The matter stands resolved as the code runs fine. The background error seems to be occurring due to format conversion at my end.
      Regards,

      Delete
  8. This comment has been removed by the author.

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. I'm trying to modify this to continually get frames, and recover when a screen changes resolution, but I'm at a loss. I've got it properly running in a loop, but it stops working when a resolution changes. I've tried calling DuplicateOutput() again, but I don't think it likes that. I've also tried entirely destroying the DXGIManager and creating a new one, but it crashes in the destructor, at GdiplusShutdown(). Any chance you could help at all?

    ReplyDelete
    Replies
    1. If you are handling GDIPlus lifecycle yourself, you can try removing GDIPlus init/shutdown calls from DXGIManager, other than that, approach of recreating it on resolution change sounds reasonable

      Delete
    2. I've done that, but then it throws a read access violation in the destructor when trying to check m_pBuf. If I remove that and the next one, then it crashes in atilcomcli.h in the CComPtrBase destructor, also with a read access violation. I don't know how to find out what it's trying to access.

      I'm wondering if it's related to the WIC factory. If I don't care about saving to an image, do I need this at all?

      Delete
    3. Sorry, I was away for some time. Have you sorted this out? WIC factory is used to scale the image to the output rect, if rcDest is not equal to rcOutput. If you are just capturing the whole thing, you can just remove all references to WIC.
      Could you pull request your code so I can look into it?

      Delete
  11. It's good~.

    desktop screen is captured very well.

    but, game screen is captured to black screen

    please, show me some method to capture game screen;

    ReplyDelete

Post a Comment

Popular posts from this blog

Kubuntu 16.04 and Dell Inspiron 7559

Visual Studio Code: Fixing Python Intellisense