C# – Screen capture and Overlays for Direct3D 9, 10 and 11 using API Hooks
0 votes, 0.00 avg. rating (0% score)

So it’s been almost a year and I have finally got around to finishing a new version of my screen capture project that supports Direct3D 9, 10, and 11! This solution still uses SlimDX for the Direct3D API wrapper along with EasyHook to perform the remote process hooking and IPC between the host process and target process.

Some of the changes since the previous version:

  1. 100% C# implementation
  2. Added Direct3D 10 and 11 support
  3. Capturing multi-sampled/anti-aliased images (for 10 & 11) is supported
  4. Re-organised code making it easier to support multiple D3D versions
  5. Implemented a new and improved test bed application
  6. Provided example overlays for D3D 9 and 10
  7. Improved debug messaging from Target application to Host (mostly removed when compiled with “Release” configuration)

Prerequisites

Like the previous post you need the SlimDX June 2010 SDK or Runtime, and it is useful to have the DirectX SDK handy to try screenshots on their samples.

The download already includes the EasyHook binaries, but if you want to download them yourself you can find them at CodePlex here.

C# only Implementation

Previously I had a C++ helper DLL to get the VTable addresses.

This has been replaced with the following C# implementation:

        protected IntPtr[] GetVTblAddresses(IntPtr pointer, int numberOfMethods)
        {
            List vtblAddresses = new List();

            IntPtr vTable = Marshal.ReadIntPtr(pointer);
            for (int i = 0; i < numberOfMethods; i++)
                vtblAddresses.Add(Marshal.ReadIntPtr(vTable, i * IntPtr.Size)); // using IntPtr.Size allows us to support both 32 and 64-bit processes

            return vtblAddresses.ToArray();
        }

Adding Direct3D 10 and 11 Support

Direct3D 10 and Direct3D 11 have a completely different rendering pipeline compared to Direct3D 9, making use of the DirectX Graphics Infrastructure (DXGI). We now hook the IDXGISwapChain.Present method to capture in D3D 10 & 11.

The capture method used in Direct3D 10 and 11 is also more thread-safe allowing us to extract data from the resulting texture on a background thread. This reduces the impact to the frame rate.

We are also able to copy only the region of the backbuffer that we are actually interested in – making the capture of smaller regions quicker as less data needs to be copied to system memory. This also will make capturing a sequence of frames for use in video production much easier.

Note: I believe that D3D9Ex (Vista+ shared surfaces – e.g. DWM) also uses the DXGI Present but haven’t looked into this in greater detail as the EndScene hook in Direct3D 9 achieves what we are after also.

Support capturing Multi-Sampled (anti-aliased) Images

Direct3D 10 and 11 both provide the ResolveSubresource method (Direct3D10.Device.ResolveSubresource, and Direct3D11.Device.ImmediateContext.ResolveSubresource), that makes it easy to resolve a multi-sampled texture down into a single sample for copying into a Bitmap.

D3D 10 code:

    // If texture is multisampled, then we can use ResolveSubresource to copy it into a non-multisampled texture
    Texture2D textureResolved = null;
    if (texture.Description.SampleDescription.Count > 1)
    {
        this.DebugMessage("PresentHook: resolving multi-sampled texture");
        // texture is multi-sampled, lets resolve it down to a single sample
        textureResolved = new Texture2D(texture.Device, new Texture2DDescription()
        {
            CpuAccessFlags = CpuAccessFlags.None,
            Format = texture.Description.Format,
            Height = texture.Description.Height,
            Usage = ResourceUsage.Default,
            Width = texture.Description.Width,
            ArraySize = 1,
            SampleDescription = new SlimDX.DXGI.SampleDescription(1, 0), // Ensure single sample
            BindFlags = BindFlags.None,
            MipLevels = 1,
            OptionFlags = texture.Description.OptionFlags
        });
        // Resolve into textureResolved
        texture.Device.ResolveSubresource(texture, 0, textureResolved, 0, texture.Description.Format);
    }

Direct3D 9 and 10 Overlays

I have implemented two sample overlays for Direct3D 9 and 10.

The D3D 9 example displays the frame rate and draws a box with a cross in the middle to identify the region that was captured – this fades after 1sec.

#region Example: Draw Overlay (after screenshot so we don't capture overlay as well)

#region Draw fading lines based on last screencapture request
if (_lastRequestTime != null && _lineVectors != null)
{
    TimeSpan timeSinceRequest = DateTime.Now - _lastRequestTime.Value;
    if (timeSinceRequest.TotalMilliseconds < 1000.0)
    {
        using (Line line = new Line(device))
        {
            _lineAlpha = (float)((1000.0 - timeSinceRequest.TotalMilliseconds) / 1000.0); // This is our fade out
            line.Antialias = true;
            line.Width = 1.0f;
            line.Begin();
            line.Draw(_lineVectors, new SlimDX.Color4(_lineAlpha, 0.5f, 0.5f, 1.0f));
            line.End();
        }
    }
    else
    {
        _lineVectors = null;
    }
}
#endregion

#region Draw frame rate
using (SlimDX.Direct3D9.Font font = new SlimDX.Direct3D9.Font(device, new System.Drawing.Font("Times New Roman", 16.0f)))
{
    if (_lastFrame != null)
    {
        font.DrawString(null, String.Format("{0:N1} fps", (1000.0 / (DateTime.Now - _lastFrame.Value).TotalMilliseconds)), 100, 100, System.Drawing.Color.Red);
    }
    _lastFrame = DateTime.Now;
}
#endregion

#endregion

The D3D 10 example simply displays the current date and time:

#region Example: Draw overlay (after screenshot so we don't capture overlay as well)

using (Texture2D texture = Texture2D.FromSwapChain(swapChain, 0))
{
    if (_lastFrame != null)
    {
        FontDescription fd = new SlimDX.Direct3D10.FontDescription()
        {
            Height = 16,
            FaceName = "Times New Roman",
            IsItalic = false,
            Width = 0,
            MipLevels = 1,
            CharacterSet = SlimDX.Direct3D10.FontCharacterSet.Default,
            Precision = SlimDX.Direct3D10.FontPrecision.Default,
            Quality = SlimDX.Direct3D10.FontQuality.Antialiased,
            PitchAndFamily = FontPitchAndFamily.Default | FontPitchAndFamily.DontCare
        };

        using (Font font = new Font(texture.Device, fd))
        {
            DrawText(font, new Vector2(100, 100), String.Format("{0}", DateTime.Now), new Color4(System.Drawing.Color.Red));
        }
    }
    _lastFrame = DateTime.Now;
}

#endregion

Unfortunately Direct3D 11 does not provide any support for Direct2D / Fonts for our overlay, so you would need to create a D3D 10 device with a shared texture, and blend this into the D3D 11 backbuffer (see http://forums.create.msdn.com/forums/t/38961.aspx and http://www.gamedev.net/topic/547920-how-to-use-d2d-with-d3d11/).

Issues

  1. I have found that often a target 64-bit process will hang if your host application (e.g. the test bed) closes before the target process, I could not determine what the cause was, but I’m guessing it has something to do with EasyHook  (unconfirmed)
  2. No example overlay for Direct3D 11 yet
  3. If you try to inject / capture using the wrong Direct3D version you could crash your host or target application
  4. Load test in D3D11 would be slow if host application (e.g. test bed) does not have focus – not sure where the delay is there

Some Numbers

Direct3D 9 – SDK Sample Blobs (640×480)

  • Time spent in render pipeline ~8ms
  • Total time to request and retrieve a capture ~50ms

Direct3D 10 – SDK Sample CubeMapGS (32-bit) (640×480)

  • Time spent inside render pipeline ~1-2ms
  • Total time to copy backbuffer, copy result into system memory and send back response ~70ms
  • Total time to request and retrieve a capture ~100-130ms

Direct3D 11 – SDK Sample SubD11 (640×480)

  • Time spent inside render pipeline ~1-2ms
  • Total time to copy backbuffer, copy result into system memory and send back response ~70-80ms
  • Total time to request and retrieve a capture ~150-200ms

Examples

Direct3D 9: SDK Sample Blobs

D3D9 Blobs Overlay and Capture

Direct3D 10: SDK Sample CubeMapGS

D3D10 CubeMapGS Overlay
D3D10 CubeMapGS Capture

Direct3D 11: SDK Sample SubD11

D3D11 SubD11 Capture

Download

You can download the updated sample here

It’s also recommended you read the previous post