justinstenning / Direct3DHook

DirectX Capture and Overlays by using Direct3D API hooks
http://spazzarama.com/2011/03/14/c-screen-capture-and-overlays-for-direct3d-9-10-and-11-using-api-hooks
MIT License
582 stars 176 forks source link

Support for MemoryMappedFile approach to retrieving captured image #33

Open justinstenning opened 9 years ago

justinstenning commented 9 years ago

Use the SharedMemory library to access captured image data in host process rather than IPC.

MathewSachin commented 8 years ago

Can SharedMemory be used with EasyHook?

MathewSachin commented 8 years ago

I am working on a cut-down version of your repository: https://github.com/MathewSachin/Direct3DHook The Capturing process is now working fine. Thanks to your great work. I understand that SharedMemory can make the Capturing faster. Can you give me some directions on implementing the same? Please.

remcoros commented 8 years ago

Here's a (very specific) implementation using shared memory:

https://github.com/HearthstoneTracker/HearthstoneTracker/blob/master/Capture/Hook/DXHookD3D9SharedMem.cs

And the client: https://github.com/HearthstoneTracker/HearthstoneTracker/blob/master/HearthCap.Core/GameCapture/AutoCaptureEngine.cs#L675

Be ware though, this is pretty advanced stuff, with ring buffer and inter-process locking.

Also, this code is locked to the specific use case here. You'll have to examine it and re-create it to your use case.

Also 2: If you want to look at a real high performance capture engine. Look at OBS / obs-studio (oss, gpl licensed): https://github.com/jp9000/obs-studio

justinstenning commented 8 years ago

When I add it into this project I will be using my SharedMemory library referenced above to create a circular buffer (aka ring buffer) with enough room to store a few screenshots. This library hides all the complexity for you.

The publisher will be created within the injected library, and the reader will be opened in the host (e.g. TestScreenshot or whatever is being used). An example of how this is done can be found here: http://sharedmemory.codeplex.com/documentation and also there are client/server examples in the source of the project.

justinstenning commented 8 years ago

The example from the ShareMemory documentation:

using (var producer = new SharedMemory.CircularBuffer(name: "MySharedMemory", nodeCount: 3, nodeBufferSize: 4))
using (var consumer = new SharedMemory.CircularBuffer(name: "MySharedMemory"))
{
    // nodeCount must be one larger than the number
    // of writes that must fit in the buffer at any one time
    producer.Write<int>(new int[] { 123 });
    producer.Write<int>(new int[] { 456 });

    int[] data = new int[1];
    consumer.Read<int>(data);
    Console.WriteLine(data[0]);
    consumer.Read<int>(data);
    Console.WriteLine(data[0]);
}

Although this is all in the same process, providing that the name matches and the producer remains connected, the consumer can be in any process, no synchronisation/locking required.

You would be grabbing an IntPtr to the Direct3D surface/texture, then write that to the producer (using the appropriate size). The nodeBufferSize would also need to be set to the correct size.

GeeWizWow commented 3 years ago

Hey, bit of a necro : I

Was going to take look at implementing this approach, thought I'd ask before I started if you had made any progress on your end/ had any pointers?

Cheers 😄

justinstenning commented 3 years ago

@GeeWizWow sure. FYI I wrote a library to be used for this that you can use to help: https://github.com/spazzarama/sharedmemory

Provides a couple of supporting structures using MMF, eg ring-buffer, array

GeeWizWow commented 3 years ago

Thanks, I was up and running in no time, after looking at the HearthstoneTracker implementation, I'm glad I didn't have to re-implement that from scratch, nice work!

Somewhat unrelated, but I've been looking at your code in this repo, and the Hearthstonetracker code posted above, and I can't figure out how you guys are getting thread safety when accessing resources from a single d3 device. Any attempts I make always end in D3 complaining about a single context being accessed across multiple threads, really basic example below with lock, am I missing anything?


lock (_lockObj) {
     _renderTarget.Device.ImmediateContext.CopyResource(_renderTarget, _otherTarget);
}

// Kick off a new background thread
ThreadPool.QueueUserWorkItem(new WaitCallback((o) => {

      lock(_lockObj) {
            // Fails
            _otherTarget.Device.ImmediateContext.MapResource(....)
      }
}));

Present();             
justinstenning commented 3 years ago

@GeeWizWow we are using a shared resource to access the render target from another D3D device (we don't use the same device as you stated). You shouldn't need to change most of that code in order to implement your solution (see DXHookD3D11.cs). The copying of the RT remains unchanged, it is just what you do with the bytes when you get them that will differ.

i.e. everything remains the same within PresentHook except what happens in the call to ProcessCapture here https://github.com/spazzarama/Direct3DHook/blob/master/Capture/Hook/DXHookD3D11.cs#L509 and here https://github.com/spazzarama/Direct3DHook/blob/master/Capture/Hook/DXHookD3D11.cs#L514

Actually you don't need to edit DXHookD3Dxx.cs, instead just edit the ProcessCapture implementation within the BaseDXHook.cs: https://github.com/spazzarama/Direct3DHook/blob/master/Capture/Hook/BaseDXHook.cs#L194