poe2-bot/src/Automata.Screen/DesktopDuplication.cs
2026-02-28 15:13:22 -05:00

142 lines
4.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<IDXGIDevice>();
using var adapter = dxgiDevice.GetAdapter();
adapter.EnumOutputs(0, out var output);
using var _ = output;
using var output1 = output.QueryInterface<IDXGIOutput1>();
_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<ID3D11Texture2D>();
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();
}
}