private void SafeInvokeScreenshotRequested(ScreenshotRequest eventArgs)
        {
            if (ScreenshotRequested == null)
            {
                return; //No Listeners
            }
            ScreenshotRequestedEvent listener = null;
            var dels = ScreenshotRequested.GetInvocationList();

            foreach (var del in dels)
            {
                try
                {
                    listener = (ScreenshotRequestedEvent)del;
                    listener.Invoke(eventArgs);
                }
                catch (Exception)
                {
                    //Could not reach the destination, so remove it
                    //from the list
                    ScreenshotRequested -= listener;
                }
            }
        }
Example #2
0
        /// <summary>
        ///     Our present hook that will grab a copy of the backbuffer when requested. Note: this supports multi-sampling
        ///     (anti-aliasing)
        /// </summary>
        /// <param name="swapChainPtr"></param>
        /// <param name="syncInterval"></param>
        /// <param name="flags"></param>
        /// <returns>The HRESULT of the original method</returns>
        private int PresentHook(IntPtr swapChainPtr, int syncInterval, PresentFlags flags)
        {
            Frame();
            var swapChain = (SwapChain) swapChainPtr;
            try
            {
                #region Screenshot Request

                if (Request != null)
                {
                    DebugMessage("PresentHook: Request Start");
                    var startTime = DateTime.Now;
                    using (var currentRT = SharpDX.Direct3D11.Resource.FromSwapChain<Texture2D>(swapChain, 0))
                    {
                        #region Determine region to capture

                        var captureRegion = new Rectangle(0, 0, currentRT.Description.Width,
                            currentRT.Description.Height);

                        if (Request.RegionToCapture.Width > 0)
                        {
                            captureRegion = new Rectangle(Request.RegionToCapture.Left, Request.RegionToCapture.Top,
                                Request.RegionToCapture.Right, Request.RegionToCapture.Bottom);
                        }
                        else if (Request.Resize.HasValue)
                        {
                            captureRegion = new Rectangle(0, 0, Request.Resize.Value.Width, Request.Resize.Value.Height);
                        }

                        #endregion

                        // Create / Recreate resources as necessary
                        EnsureResources(currentRT.Device, currentRT.Description, captureRegion, Request);

                        Texture2D sourceTexture = null;

                        // If texture is multisampled, then we can use ResolveSubresource to copy it into a non-multisampled texture
                        if (currentRT.Description.SampleDescription.Count > 1 || Request.Resize.HasValue)
                        {
                            if (Request.Resize.HasValue)
                                DebugMessage("PresentHook: resizing texture");
                            else
                                DebugMessage("PresentHook: resolving multi-sampled texture");

                            // Resolve into _resolvedRT
                            if (_resolvedRTKeyedMutex != null)
                                _resolvedRTKeyedMutex.Acquire(0, int.MaxValue);
                            currentRT.Device.ImmediateContext.ResolveSubresource(currentRT, 0, _resolvedRT, 0,
                                _resolvedRT.Description.Format);
                            if (_resolvedRTKeyedMutex != null)
                                _resolvedRTKeyedMutex.Release(1);

                            if (Request.Resize.HasValue)
                            {
                                lock (_lock)
                                {
                                    if (_resolvedRTKeyedMutex_Dev2 != null)
                                        _resolvedRTKeyedMutex_Dev2.Acquire(1, int.MaxValue);
                                    _saQuad.ShaderResource = _resolvedSharedSRV;
                                    _saQuad.RenderTargetView = _resizedRTV;
                                    _saQuad.RenderTarget = _resizedRT;
                                    _saQuad.Render();
                                    if (_resolvedRTKeyedMutex_Dev2 != null)
                                        _resolvedRTKeyedMutex_Dev2.Release(0);
                                }

                                // set sourceTexture to the resized RT
                                sourceTexture = _resizedRT;
                            }
                            else
                            {
                                // Make sourceTexture be the resolved texture
                                sourceTexture = _resolvedRTShared;
                            }
                        }
                        else
                        {
                            // Copy the resource into the shared texture
                            if (_resolvedRTKeyedMutex != null) _resolvedRTKeyedMutex.Acquire(0, int.MaxValue);
                            currentRT.Device.ImmediateContext.CopySubresourceRegion(currentRT, 0, null, _resolvedRT, 0);
                            if (_resolvedRTKeyedMutex != null) _resolvedRTKeyedMutex.Release(1);
                            sourceTexture = _resolvedRTShared;
                        }

                        // Copy to memory and send back to host process on a background thread so that we do not cause any delay in the rendering pipeline
                        _requestCopy = Request.Clone();
                            // this.Request gets set to null, so copy the Request for use in the thread

                        // Prevent the request from being processed a second time
                        Request = null;

                        var acquireLock = sourceTexture == _resolvedRTShared;

                        ThreadPool.QueueUserWorkItem(o =>
                        {
                            // Acquire lock on second device
                            if (acquireLock && _resolvedRTKeyedMutex_Dev2 != null)
                                _resolvedRTKeyedMutex_Dev2.Acquire(1, int.MaxValue);

                            lock (_lock)
                            {
                                // Copy the subresource region, we are dealing with a flat 2D texture with no MipMapping, so 0 is the subresource index
                                sourceTexture.Device.ImmediateContext.CopySubresourceRegion(sourceTexture, 0,
                                    new ResourceRegion
                                    {
                                        Top = captureRegion.Top,
                                        Bottom = captureRegion.Bottom,
                                        Left = captureRegion.Left,
                                        Right = captureRegion.Right,
                                        Front = 0,
                                        Back = 1 // Must be 1 or only black will be copied
                                    }, _finalRT, 0, 0, 0, 0);

                                // Release lock upon shared surface on second device
                                if (acquireLock && _resolvedRTKeyedMutex_Dev2 != null)
                                    _resolvedRTKeyedMutex_Dev2.Release(0);

                                _finalRT.Device.ImmediateContext.End(_query);
                                _queryIssued = true;
                                while (!_finalRT.Device.ImmediateContext.GetData(_query).ReadBoolean())
                                {
                                    // Spin (usually no spin takes place)
                                }

                                var startCopyToSystemMemory = DateTime.Now;
                                try
                                {
                                    var db = default(DataBox);
                                    if (_requestCopy.Format == ImageFormat.PixelData)
                                    {
                                        db = _finalRT.Device.ImmediateContext.MapSubresource(_finalRT, 0, MapMode.Read,
                                            MapFlags.DoNotWait);
                                        _finalRTMapped = true;
                                    }
                                    _queryIssued = false;

                                    try
                                    {
                                        using (var ms = new MemoryStream())
                                        {
                                            switch (_requestCopy.Format)
                                            {
                                                case ImageFormat.Bitmap:
                                                    SharpDX.Direct3D11.Resource.ToStream(
                                                        _finalRT.Device.ImmediateContext, _finalRT, ImageFileFormat.Bmp,
                                                        ms);
                                                    break;
                                                case ImageFormat.Jpeg:
                                                    SharpDX.Direct3D11.Resource.ToStream(
                                                        _finalRT.Device.ImmediateContext, _finalRT, ImageFileFormat.Jpg,
                                                        ms);
                                                    break;
                                                case ImageFormat.Png:
                                                    SharpDX.Direct3D11.Resource.ToStream(
                                                        _finalRT.Device.ImmediateContext, _finalRT, ImageFileFormat.Png,
                                                        ms);
                                                    break;
                                                case ImageFormat.PixelData:
                                                    if (db.DataPointer != IntPtr.Zero)
                                                    {
                                                        ProcessCapture(_finalRT.Description.Width,
                                                            _finalRT.Description.Height, db.RowPitch,
                                                            PixelFormat.Format32bppArgb, db.DataPointer, _requestCopy);
                                                    }
                                                    return;
                                            }
                                            ms.Position = 0;
                                            ProcessCapture(ms, _requestCopy);
                                        }
                                    }
                                    finally
                                    {
                                        DebugMessage("PresentHook: Copy to System Memory time: " +
                                                     (DateTime.Now - startCopyToSystemMemory).ToString());
                                    }

                                    if (_finalRTMapped)
                                    {
                                        lock (_lock)
                                        {
                                            _finalRT.Device.ImmediateContext.UnmapSubresource(_finalRT, 0);
                                            _finalRTMapped = false;
                                        }
                                    }
                                }
                                catch (SharpDXException exc)
                                {
                                    // Catch DXGI_ERROR_WAS_STILL_DRAWING and ignore - the data isn't available yet
                                }
                            }
                        });


                        // Note: it would be possible to capture multiple frames and process them in a background thread
                    }
                    DebugMessage("PresentHook: Copy BackBuffer time: " + (DateTime.Now - startTime));
                    DebugMessage("PresentHook: Request End");
                }

                #endregion

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

                if (Config.ShowOverlay)
                {
                    // Initialise Overlay Engine
                    if (_swapChainPointer != swapChain.NativePointer 
                        || _overlayEngine == null
                        || IsOverlayUpdatePending)
                    {
                        if (_overlayEngine != null)
                            _overlayEngine.Dispose();

                        _overlayEngine = new DXOverlayEngine();
                        _overlayEngine.Overlays.Add(new Overlay
                        {
                            Elements = OverlayElements
                        });
                        _overlayEngine.Initialise(swapChain);

                        _swapChainPointer = swapChain.NativePointer;
                    }
                    // Draw Overlay(s)
                    else if (_overlayEngine != null)
                    {
                        foreach (var overlay in _overlayEngine.Overlays)
                            overlay.Frame();
                        _overlayEngine.Draw();
                    }
                }

                #endregion
            }
            catch (Exception e)
            {
                // If there is an error we do not want to crash the hooked application, so swallow the exception
                DebugMessage("PresentHook: Exeception: " + e.GetType().FullName + ": " + e);
                //return unchecked((int)0x8000FFFF); //E_UNEXPECTED
            }

            // As always we need to call the original method, note that EasyHook will automatically skip the hook and call the original method
            // i.e. calling it here will not cause a stack overflow into this function
            return DXGISwapChain_PresentHook.Original(swapChainPtr, syncInterval, flags);
        }
Example #3
0
        private void EnsureResources(Device device, Texture2DDescription description, Rectangle captureRegion,
            ScreenshotRequest request)
        {
            if (_device != null && request.Resize != null &&
                (_resizedRT == null ||
                 (_resizedRT.Device.NativePointer != _device.NativePointer ||
                  _resizedRT.Description.Width != request.Resize.Value.Width ||
                  _resizedRT.Description.Height != request.Resize.Value.Height)))
            {
                // Create/Recreate resources for resizing
                RemoveAndDispose(ref _resizedRT);
                RemoveAndDispose(ref _resizedRTV);
                RemoveAndDispose(ref _saQuad);

                _resizedRT = ToDispose(new Texture2D(_device, new Texture2DDescription
                {
                    Format = Format.R8G8B8A8_UNorm, // Supports BMP/PNG/etc
                    Height = request.Resize.Value.Height,
                    Width = request.Resize.Value.Width,
                    ArraySize = 1,
                    SampleDescription = new SampleDescription(1, 0),
                    BindFlags = BindFlags.RenderTarget,
                    MipLevels = 1,
                    Usage = ResourceUsage.Default,
                    OptionFlags = ResourceOptionFlags.None
                }));

                _resizedRTV = ToDispose(new RenderTargetView(_device, _resizedRT));

                _saQuad = ToDispose(new ScreenAlignedQuadRenderer());
                _saQuad.Initialize(new DeviceManager(_device));
            }

            // Check if _resolvedRT or _finalRT require creation
            if (_finalRT != null && _finalRT.Device.NativePointer == _device.NativePointer &&
                _finalRT.Description.Height == captureRegion.Height && _finalRT.Description.Width == captureRegion.Width &&
                _resolvedRT != null && _resolvedRT.Description.Height == description.Height &&
                _resolvedRT.Description.Width == description.Width &&
                _resolvedRT.Device.NativePointer == device.NativePointer &&
                _resolvedRT.Description.Format == description.Format
                )
            {
                return;
            }

            RemoveAndDispose(ref _query);
            RemoveAndDispose(ref _resolvedRT);
            RemoveAndDispose(ref _resolvedSharedSRV);
            RemoveAndDispose(ref _finalRT);
            RemoveAndDispose(ref _resolvedRTShared);

            _query = new Query(_device, new QueryDescription
            {
                Flags = QueryFlags.None,
                Type = QueryType.Event
            });
            _queryIssued = false;

            _resolvedRT = ToDispose(new Texture2D(device, new Texture2DDescription
            {
                CpuAccessFlags = CpuAccessFlags.None,
                Format = description.Format, // for multisampled backbuffer, this must be same format
                Height = description.Height,
                Usage = ResourceUsage.Default,
                Width = description.Width,
                ArraySize = 1,
                SampleDescription = new SampleDescription(1, 0), // Ensure single sample
                BindFlags = BindFlags.ShaderResource,
                MipLevels = 1,
                OptionFlags = ResourceOptionFlags.SharedKeyedmutex
            }));

            // Retrieve reference to the keyed mutex
            _resolvedRTKeyedMutex = ToDispose(_resolvedRT.QueryInterfaceOrNull<KeyedMutex>());

            using (var resource = _resolvedRT.QueryInterface<Resource>())
            {
                _resolvedRTShared = ToDispose(_device.OpenSharedResource<Texture2D>(resource.SharedHandle));
                _resolvedRTKeyedMutex_Dev2 = ToDispose(_resolvedRTShared.QueryInterfaceOrNull<KeyedMutex>());
            }

            // SRV for use if resizing
            _resolvedSharedSRV = ToDispose(new ShaderResourceView(_device, _resolvedRTShared));

            _finalRT = ToDispose(new Texture2D(_device, new Texture2DDescription
            {
                CpuAccessFlags = CpuAccessFlags.Read,
                Format = description.Format,
                Height = captureRegion.Height,
                Usage = ResourceUsage.Staging,
                Width = captureRegion.Width,
                ArraySize = 1,
                SampleDescription = new SampleDescription(1, 0),
                BindFlags = BindFlags.None,
                MipLevels = 1,
                OptionFlags = ResourceOptionFlags.None
            }));
            _finalRTMapped = false;
        }
Example #4
0
        /// <summary>
        ///     Implementation of capturing from the render target of the Direct3D9 Device (or DeviceEx)
        /// </summary>
        /// <param name="device"></param>
        private void DoCaptureRenderTarget(Device device, string hook)
        {
            Frame();

            try
            {
                #region Screenshot Request

                // If we have issued the command to copy data to our render target, check if it is complete
                bool qryResult;
                if (_queryIssued && _requestCopy != null && _query.GetData(out qryResult, false))
                {
                    // The GPU has finished copying data to _renderTargetCopy, we can now lock
                    // the data and access it on another thread.

                    _queryIssued = false;

                    // Lock the render target
                    Rectangle rect;
                    var lockedRect = LockRenderTarget(_renderTargetCopy, out rect);
                    _renderTargetCopyLocked = true;

                    // Copy the data from the render target
                    Task.Factory.StartNew(() =>
                    {
                        lock (_lockRenderTarget)
                        {
                            ProcessCapture(rect.Width, rect.Height, lockedRect.Pitch,
                                _renderTargetCopy.Description.Format.ToPixelFormat(), lockedRect.DataPointer,
                                _requestCopy);
                        }
                    });
                }

                // Single frame capture request
                if (Request != null)
                {
                    var start = DateTime.Now;
                    try
                    {
                        using (var renderTarget = device.GetRenderTarget(0))
                        {
                            int width, height;

                            // If resizing of the captured image, determine correct dimensions
                            if (Request.Resize != null &&
                                (renderTarget.Description.Width > Request.Resize.Value.Width ||
                                 renderTarget.Description.Height > Request.Resize.Value.Height))
                            {
                                if (renderTarget.Description.Width > Request.Resize.Value.Width)
                                {
                                    width = Request.Resize.Value.Width;
                                    height =
                                        (int)
                                            Math.Round((renderTarget.Description.Height*
                                                        (Request.Resize.Value.Width/
                                                         (double) renderTarget.Description.Width)));
                                }
                                else
                                {
                                    height = Request.Resize.Value.Height;
                                    width =
                                        (int)
                                            Math.Round((renderTarget.Description.Width*
                                                        (Request.Resize.Value.Height/
                                                         (double) renderTarget.Description.Height)));
                                }
                            }
                            else
                            {
                                width = renderTarget.Description.Width;
                                height = renderTarget.Description.Height;
                            }

                            // If existing _renderTargetCopy, ensure that it is the correct size and format
                            if (_renderTargetCopy != null &&
                                (_renderTargetCopy.Description.Width != width ||
                                 _renderTargetCopy.Description.Height != height ||
                                 _renderTargetCopy.Description.Format != renderTarget.Description.Format))
                            {
                                // Cleanup resources
                                Cleanup();
                            }

                            // Ensure that we have something to put the render target data into
                            if (!_resourcesInitialised || _renderTargetCopy == null)
                            {
                                CreateResources(device, width, height, renderTarget.Description.Format);
                            }

                            // Resize from render target Surface to resolvedSurface (also deals with resolving multi-sampling)
                            device.StretchRectangle(renderTarget, _resolvedTarget, TextureFilter.None);
                        }

                        // If the render target is locked from a previous request unlock it
                        if (_renderTargetCopyLocked)
                        {
                            // Wait for the the ProcessCapture thread to finish with it
                            lock (_lockRenderTarget)
                            {
                                if (_renderTargetCopyLocked)
                                {
                                    _renderTargetCopy.UnlockRectangle();
                                    _renderTargetCopyLocked = false;
                                }
                            }
                        }

                        // Copy data from resolved target to our render target copy
                        device.GetRenderTargetData(_resolvedTarget, _renderTargetCopy);

                        _requestCopy = Request.Clone();
                        _query.Issue(Issue.End);
                        _queryIssued = true;
                    }
                    finally
                    {
                        // We have completed the request - mark it as null so we do not continue to try to capture the same request
                        // Note: If you are after high frame rates, consider implementing buffers here to capture more frequently
                        //         and send back to the host application as needed. The IPC overhead significantly slows down
                        //         the whole process if sending frame by frame.
                        Request = null;
                    }
                    var end = DateTime.Now;
                    DebugMessage(hook + ": Capture time: " + (end - start));
                }

                #endregion

                if (Config.ShowOverlay)
                {
                    #region Draw Overlay

                    // Check if overlay needs to be initialised
                    if (_overlayEngine == null
                        || _overlayEngine.Device.NativePointer != device.NativePointer
                        || IsOverlayUpdatePending)
                    {
                        // Cleanup if necessary
                        if (_overlayEngine != null)
                            _overlayEngine.Dispose();

                        _overlayEngine = ToDispose(new DXOverlayEngine());

                        // Create Overlay
                        _overlayEngine.Overlays.Add(new Overlay
                        {
                            Elements = OverlayElements
                        });

                        _overlayEngine.Initialise(device);

                        IsOverlayUpdatePending = false;
                    }
                    // Draw Overlay(s)
                    else if (_overlayEngine != null)
                    {
                        foreach (var overlay in _overlayEngine.Overlays)
                            overlay.Frame();
                        _overlayEngine.Draw();
                    }

                    #endregion
                }
            }
            catch (Exception e)
            {
                DebugMessage(e.ToString());
            }
        }
 public void ScreenshotRequestedProxyHandler(ScreenshotRequest request)
 {
     if (ScreenshotRequested != null)
         ScreenshotRequested(request);
 }
        private void SafeInvokeScreenshotRequested(ScreenshotRequest eventArgs)
        {
            if (ScreenshotRequested == null)
                return; //No Listeners

            ScreenshotRequestedEvent listener = null;
            var dels = ScreenshotRequested.GetInvocationList();

            foreach (var del in dels)
            {
                try
                {
                    listener = (ScreenshotRequestedEvent) del;
                    listener.Invoke(eventArgs);
                }
                catch (Exception)
                {
                    //Could not reach the destination, so remove it
                    //from the list
                    ScreenshotRequested -= listener;
                }
            }
        }
Example #7
0
 protected void ProcessCapture(byte[] bitmapData, ScreenshotRequest request)
 {
     try
     {
         if (request != null)
         {
             Interface.SendScreenshotResponse(new Screenshot(request.RequestId, bitmapData)
             {
                 Format = request.Format
             });
         }
         LastCaptureTime = Timer.Elapsed;
     }
     catch (RemotingException)
     {
         // Ignore remoting exceptions
         // .NET Remoting will throw an exception if the host application is unreachable
     }
     catch (Exception e)
     {
         DebugMessage(e.ToString());
     }
 }
Example #8
0
 protected void ProcessCapture(Stream stream, ScreenshotRequest request)
 {
     ProcessCapture(ReadFullStream(stream), request);
 }
Example #9
0
        /// <summary>
        ///     Process the capture based on the requested format.
        /// </summary>
        /// <param name="width">image width</param>
        /// <param name="height">image height</param>
        /// <param name="pitch">data pitch (bytes per row)</param>
        /// <param name="format">target format</param>
        /// <param name="pBits">IntPtr to the image data</param>
        /// <param name="request">The original requets</param>
        protected void ProcessCapture(int width, int height, int pitch, PixelFormat format, IntPtr pBits,
            ScreenshotRequest request)
        {
            if (request == null)
                return;

            if (format == PixelFormat.Undefined)
            {
                DebugMessage("Unsupported render target format");
                return;
            }

            // Copy the image data from the buffer
            var size = height*pitch;
            var data = new byte[size];
            Marshal.Copy(pBits, data, 0, size);

            // Prepare the response
            Screenshot response = null;

            if (request.Format == Direct3DHookLib.Interface.ImageFormat.PixelData)
            {
                // Return the raw data
                response = new Screenshot(request.RequestId, data)
                {
                    Format = request.Format,
                    PixelFormat = format,
                    Height = height,
                    Width = width,
                    Stride = pitch
                };
            }
            else
            {
                // Return an image
                using (var bm = data.ToBitmap(width, height, pitch, format))
                {
                    var imgFormat = ImageFormat.Bmp;
                    switch (request.Format)
                    {
                        case Direct3DHookLib.Interface.ImageFormat.Jpeg:
                            imgFormat = ImageFormat.Jpeg;
                            break;
                        case Direct3DHookLib.Interface.ImageFormat.Png:
                            imgFormat = ImageFormat.Png;
                            break;
                    }

                    response = new Screenshot(request.RequestId, bm.ToByteArray(imgFormat))
                    {
                        Format = request.Format,
                        Height = bm.Height,
                        Width = bm.Width
                    };
                }
            }

            // Send the response
            SendResponse(response);
        }
Example #10
0
 protected virtual void InterfaceEventProxy_ScreenshotRequested(ScreenshotRequest request)
 {
     Request = request;
 }