using System.Runtime.InteropServices; using OpenCvSharp; using Serilog; using SharpGen.Runtime; using Vortice.Direct3D; using Vortice.Direct3D11; using Vortice.DXGI; using Region = Poe2Trade.Core.Region; namespace Poe2Trade.Screen; public sealed class DesktopDuplication : IScreenCapture { private readonly ID3D11Device _device; private readonly ID3D11DeviceContext _context; private IDXGIOutputDuplication? _duplication; private ID3D11Texture2D? _staging; private int _stagingW, _stagingH; private bool _needsRecreate; public DesktopDuplication() { D3D11.D3D11CreateDevice( null, DriverType.Hardware, DeviceCreationFlags.BgraSupport, [FeatureLevel.Level_11_0], out _device!, out _context!).CheckError(); CreateDuplication(); } private void CreateDuplication() { using var dxgiDevice = _device.QueryInterface(); using var adapter = dxgiDevice.GetAdapter(); adapter.EnumOutputs(0, out var output); using var _ = output; using var output1 = output.QueryInterface(); _duplication = output1.DuplicateOutput(_device); _needsRecreate = false; Log.Debug("DXGI Desktop Duplication created"); } public unsafe ScreenFrame? CaptureFrame() { if (_duplication == null) return null; if (_needsRecreate) { try { _duplication.Dispose(); CreateDuplication(); } catch (Exception ex) { Log.Warning(ex, "Failed to recreate DXGI duplication"); return null; } } IDXGIResource? resource = null; try { _duplication.AcquireNextFrame(100, out _, out resource); } catch (SharpGenException ex) { if (ex.ResultCode == Vortice.DXGI.ResultCode.AccessLost) _needsRecreate = true; return null; } var srcTexture = resource!.QueryInterface(); var desc = srcTexture.Description; var w = (int)desc.Width; var h = (int)desc.Height; EnsureStaging(w, h); _context.CopySubresourceRegion(_staging!, 0, 0, 0, 0, srcTexture, 0); var mapped = _context.Map(_staging!, 0, MapMode.Read); // GPU copy is complete once Map returns — release DXGI frame immediately // so the DWM compositor can recycle the buffer (~0.5ms hold vs ~2.5ms before). srcTexture.Dispose(); resource.Dispose(); _duplication!.ReleaseFrame(); // CPU copy from our staging texture (no DXGI dependency, ~1.5ms for 2560×1440) var mat = new Mat(h, w, MatType.CV_8UC4); unsafe { var src = (byte*)mapped.DataPointer; var dst = (byte*)mat.Data; var rowBytes = w * 4; var pitch = (int)mapped.RowPitch; for (var row = 0; row < h; row++) Buffer.MemoryCopy(src + row * pitch, dst + row * rowBytes, rowBytes, rowBytes); } _context.Unmap(_staging!, 0); return new ScreenFrame(mat, () => mat.Dispose()); } public unsafe Mat? CaptureRegion(Region region) { using var frame = CaptureFrame(); if (frame == null) return null; return frame.CropBgr(region); } private void EnsureStaging(int w, int h) { if (_staging != null && _stagingW == w && _stagingH == h) return; _staging?.Dispose(); _staging = _device.CreateTexture2D(new Texture2DDescription { Width = (uint)w, Height = (uint)h, MipLevels = 1, ArraySize = 1, Format = Format.B8G8R8A8_UNorm, SampleDescription = new SampleDescription(1, 0), Usage = ResourceUsage.Staging, CPUAccessFlags = CpuAccessFlags.Read, }); _stagingW = w; _stagingH = h; } public void Dispose() { _staging?.Dispose(); _duplication?.Dispose(); _context?.Dispose(); _device?.Dispose(); } }