small refactor
This commit is contained in:
parent
5958d28ed8
commit
3fe7c0b37d
11 changed files with 11 additions and 237 deletions
132
src/Poe2Trade.Screen/DesktopDuplication.cs
Normal file
132
src/Poe2Trade.Screen/DesktopDuplication.cs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
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);
|
||||
|
||||
var mat = Mat.FromPixelData(h, w, MatType.CV_8UC4, mapped.DataPointer, (int)mapped.RowPitch);
|
||||
|
||||
var duplication = _duplication!;
|
||||
return new ScreenFrame(mat, () =>
|
||||
{
|
||||
_context.Unmap(_staging!, 0);
|
||||
srcTexture.Dispose();
|
||||
resource.Dispose();
|
||||
duplication.ReleaseFrame();
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
37
src/Poe2Trade.Screen/FramePipeline.cs
Normal file
37
src/Poe2Trade.Screen/FramePipeline.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
namespace Poe2Trade.Screen;
|
||||
|
||||
public class FramePipeline : IDisposable
|
||||
{
|
||||
private readonly IScreenCapture _capture;
|
||||
private readonly List<IFrameConsumer> _consumers = [];
|
||||
|
||||
public FramePipeline(IScreenCapture capture)
|
||||
{
|
||||
_capture = capture;
|
||||
}
|
||||
|
||||
public IScreenCapture Capture => _capture;
|
||||
|
||||
public void AddConsumer(IFrameConsumer consumer) => _consumers.Add(consumer);
|
||||
|
||||
/// <summary>
|
||||
/// Capture one frame, dispatch to all consumers in parallel, then dispose frame.
|
||||
/// </summary>
|
||||
public async Task ProcessOneFrame()
|
||||
{
|
||||
using var frame = _capture.CaptureFrame();
|
||||
if (frame == null) return;
|
||||
|
||||
if (_consumers.Count == 1)
|
||||
{
|
||||
_consumers[0].Process(frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
var tasks = _consumers.Select(c => Task.Run(() => c.Process(frame)));
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => _capture.Dispose();
|
||||
}
|
||||
54
src/Poe2Trade.Screen/GdiCapture.cs
Normal file
54
src/Poe2Trade.Screen/GdiCapture.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenCvSharp;
|
||||
using OpenCvSharp.Extensions;
|
||||
using Region = Poe2Trade.Core.Region;
|
||||
|
||||
namespace Poe2Trade.Screen;
|
||||
|
||||
public sealed class GdiCapture : IScreenCapture
|
||||
{
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetSystemMetrics(int nIndex);
|
||||
|
||||
private const int SM_CXSCREEN = 0;
|
||||
private const int SM_CYSCREEN = 1;
|
||||
|
||||
public ScreenFrame? CaptureFrame()
|
||||
{
|
||||
var w = GetSystemMetrics(SM_CXSCREEN);
|
||||
var h = GetSystemMetrics(SM_CYSCREEN);
|
||||
|
||||
var bitmap = new Bitmap(w, h, PixelFormat.Format32bppArgb);
|
||||
using (var g = Graphics.FromImage(bitmap))
|
||||
{
|
||||
g.CopyFromScreen(0, 0, 0, 0, new System.Drawing.Size(w, h),
|
||||
CopyPixelOperation.SourceCopy);
|
||||
}
|
||||
|
||||
var mat = BitmapConverter.ToMat(bitmap);
|
||||
bitmap.Dispose();
|
||||
|
||||
// ToMat returns BGR or BGRA depending on pixel format; ensure BGRA
|
||||
if (mat.Channels() == 3)
|
||||
{
|
||||
var bgra = new Mat();
|
||||
Cv2.CvtColor(mat, bgra, ColorConversionCodes.BGR2BGRA);
|
||||
mat.Dispose();
|
||||
mat = bgra;
|
||||
}
|
||||
|
||||
var ownedMat = mat;
|
||||
return new ScreenFrame(ownedMat, () => ownedMat.Dispose());
|
||||
}
|
||||
|
||||
public Mat? CaptureRegion(Region region)
|
||||
{
|
||||
using var frame = CaptureFrame();
|
||||
if (frame == null) return null;
|
||||
return frame.CropBgr(region);
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
6
src/Poe2Trade.Screen/IFrameConsumer.cs
Normal file
6
src/Poe2Trade.Screen/IFrameConsumer.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
namespace Poe2Trade.Screen;
|
||||
|
||||
public interface IFrameConsumer
|
||||
{
|
||||
void Process(ScreenFrame frame);
|
||||
}
|
||||
10
src/Poe2Trade.Screen/IScreenCapture.cs
Normal file
10
src/Poe2Trade.Screen/IScreenCapture.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
using OpenCvSharp;
|
||||
using Region = Poe2Trade.Core.Region;
|
||||
|
||||
namespace Poe2Trade.Screen;
|
||||
|
||||
public interface IScreenCapture : IDisposable
|
||||
{
|
||||
Mat? CaptureRegion(Region region);
|
||||
ScreenFrame? CaptureFrame();
|
||||
}
|
||||
|
|
@ -10,6 +10,8 @@
|
|||
<PackageReference Include="OpenCvSharp4.Extensions" Version="4.11.0.*" />
|
||||
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.11.0.*" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.12" />
|
||||
<PackageReference Include="Vortice.Direct3D11" Version="3.8.2" />
|
||||
<PackageReference Include="Vortice.DXGI" Version="3.8.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Poe2Trade.Core\Poe2Trade.Core.csproj" />
|
||||
|
|
|
|||
37
src/Poe2Trade.Screen/ScreenFrame.cs
Normal file
37
src/Poe2Trade.Screen/ScreenFrame.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using OpenCvSharp;
|
||||
using Region = Poe2Trade.Core.Region;
|
||||
|
||||
namespace Poe2Trade.Screen;
|
||||
|
||||
public class ScreenFrame : IDisposable
|
||||
{
|
||||
private readonly Mat _bgraMat;
|
||||
private readonly Action _disposeAction;
|
||||
|
||||
public ScreenFrame(Mat bgraMat, Action disposeAction)
|
||||
{
|
||||
_bgraMat = bgraMat;
|
||||
_disposeAction = disposeAction;
|
||||
}
|
||||
|
||||
public int Width => _bgraMat.Width;
|
||||
public int Height => _bgraMat.Height;
|
||||
|
||||
/// <summary>
|
||||
/// Crop region and convert to BGR. Returns a new Mat owned by the caller.
|
||||
/// </summary>
|
||||
public Mat CropBgr(Region region)
|
||||
{
|
||||
using var roi = new Mat(_bgraMat, new Rect(region.X, region.Y, region.Width, region.Height));
|
||||
var bgr = new Mat();
|
||||
Cv2.CvtColor(roi, bgr, ColorConversionCodes.BGRA2BGR);
|
||||
return bgr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a single pixel (B, G, R, A) — useful for mode detection probes.
|
||||
/// </summary>
|
||||
public Vec4b PixelAt(int x, int y) => _bgraMat.At<Vec4b>(y, x);
|
||||
|
||||
public void Dispose() => _disposeAction();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue