small refactor
This commit is contained in:
parent
5958d28ed8
commit
3fe7c0b37d
11 changed files with 11 additions and 237 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
using OpenCvSharp;
|
using OpenCvSharp;
|
||||||
|
using Poe2Trade.Screen;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Region = Poe2Trade.Core.Region;
|
using Region = Poe2Trade.Core.Region;
|
||||||
using Size = OpenCvSharp.Size;
|
using Size = OpenCvSharp.Size;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Poe2Trade.Core;
|
using Poe2Trade.Core;
|
||||||
using Poe2Trade.Game;
|
using Poe2Trade.Game;
|
||||||
|
using Poe2Trade.Screen;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Poe2Trade.Navigation;
|
namespace Poe2Trade.Navigation;
|
||||||
|
|
@ -42,18 +43,7 @@ public class NavigationExecutor : IDisposable
|
||||||
|
|
||||||
private static IScreenCapture CreateBackend()
|
private static IScreenCapture CreateBackend()
|
||||||
{
|
{
|
||||||
// WGC primary → DXGI fallback → GDI last resort
|
// DXGI primary → GDI fallback
|
||||||
try
|
|
||||||
{
|
|
||||||
var wgc = new WgcCapture();
|
|
||||||
Log.Information("Screen capture: WGC (Windows Graphics Capture)");
|
|
||||||
return wgc;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Warning(ex, "WGC unavailable, trying DXGI Desktop Duplication");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var dxgi = new DesktopDuplication();
|
var dxgi = new DesktopDuplication();
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@
|
||||||
<PackageReference Include="OpenCvSharp4.Extensions" Version="4.11.0.*" />
|
<PackageReference Include="OpenCvSharp4.Extensions" Version="4.11.0.*" />
|
||||||
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.11.0.*" />
|
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.11.0.*" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="8.0.12" />
|
<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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Poe2Trade.Core\Poe2Trade.Core.csproj" />
|
<ProjectReference Include="..\Poe2Trade.Core\Poe2Trade.Core.csproj" />
|
||||||
|
|
|
||||||
|
|
@ -1,217 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using OpenCvSharp;
|
|
||||||
using Serilog;
|
|
||||||
using Vortice.Direct3D;
|
|
||||||
using Vortice.Direct3D11;
|
|
||||||
using Vortice.DXGI;
|
|
||||||
using Windows.Graphics.Capture;
|
|
||||||
using Windows.Graphics.DirectX;
|
|
||||||
using Windows.Graphics.DirectX.Direct3D11;
|
|
||||||
using Region = Poe2Trade.Core.Region;
|
|
||||||
|
|
||||||
namespace Poe2Trade.Navigation;
|
|
||||||
|
|
||||||
public sealed class WgcCapture : IScreenCapture
|
|
||||||
{
|
|
||||||
// IGraphicsCaptureItemInterop extends IInspectable (not IUnknown),
|
|
||||||
// so we need 3 padding methods for the IInspectable vtable slots.
|
|
||||||
[ComImport, Guid("3628E81B-3CAC-4C60-B7F4-23CE0E0C3356")]
|
|
||||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
||||||
private interface IGraphicsCaptureItemInterop
|
|
||||||
{
|
|
||||||
void _pakGetIids();
|
|
||||||
void _pakGetRuntimeClassName();
|
|
||||||
void _pakGetTrustLevel();
|
|
||||||
IntPtr CreateForWindow([In] IntPtr window, [In] ref Guid iid);
|
|
||||||
IntPtr CreateForMonitor([In] IntPtr monitor, [In] ref Guid iid);
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDirect3DDxgiInterfaceAccess extends IUnknown directly — no padding needed.
|
|
||||||
[ComImport, Guid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")]
|
|
||||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
||||||
private interface IDirect3DDxgiInterfaceAccess
|
|
||||||
{
|
|
||||||
IntPtr GetInterface([In] ref Guid iid);
|
|
||||||
}
|
|
||||||
|
|
||||||
[DllImport("d3d11.dll", EntryPoint = "CreateDirect3D11DeviceFromDXGIDevice")]
|
|
||||||
private static extern int CreateDirect3D11DeviceFromDXGIDevice(IntPtr dxgiDevice, out IntPtr graphicsDevice);
|
|
||||||
|
|
||||||
[DllImport("user32.dll")]
|
|
||||||
private static extern IntPtr MonitorFromPoint(POINT pt, uint dwFlags);
|
|
||||||
|
|
||||||
[DllImport("combase.dll")]
|
|
||||||
private static extern int RoGetActivationFactory(IntPtr classId, ref Guid iid, out IntPtr factory);
|
|
||||||
|
|
||||||
[DllImport("combase.dll")]
|
|
||||||
private static extern int WindowsCreateString(
|
|
||||||
[MarshalAs(UnmanagedType.LPWStr)] string src, int len, out IntPtr hstr);
|
|
||||||
|
|
||||||
[DllImport("combase.dll")]
|
|
||||||
private static extern int WindowsDeleteString(IntPtr hstr);
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
private struct POINT { public int X, Y; }
|
|
||||||
|
|
||||||
private const uint MONITOR_DEFAULTTOPRIMARY = 1;
|
|
||||||
|
|
||||||
private readonly ID3D11Device _device;
|
|
||||||
private readonly ID3D11DeviceContext _context;
|
|
||||||
private readonly Direct3D11CaptureFramePool _framePool;
|
|
||||||
private readonly GraphicsCaptureSession _session;
|
|
||||||
private ID3D11Texture2D? _staging;
|
|
||||||
private int _stagingW, _stagingH;
|
|
||||||
|
|
||||||
public WgcCapture()
|
|
||||||
{
|
|
||||||
D3D11.D3D11CreateDevice(
|
|
||||||
null, DriverType.Hardware, DeviceCreationFlags.BgraSupport,
|
|
||||||
[FeatureLevel.Level_11_0],
|
|
||||||
out _device!, out _context!).CheckError();
|
|
||||||
|
|
||||||
// Wrap D3D11 device as WinRT IDirect3DDevice
|
|
||||||
using var dxgiDev = _device.QueryInterface<IDXGIDevice>();
|
|
||||||
Marshal.ThrowExceptionForHR(
|
|
||||||
CreateDirect3D11DeviceFromDXGIDevice(dxgiDev.NativePointer, out var inspectable));
|
|
||||||
var winrtDevice = (IDirect3DDevice)Marshal.GetObjectForIUnknown(inspectable);
|
|
||||||
Marshal.Release(inspectable);
|
|
||||||
|
|
||||||
// Get primary monitor
|
|
||||||
var hMon = MonitorFromPoint(default, MONITOR_DEFAULTTOPRIMARY);
|
|
||||||
|
|
||||||
// Create GraphicsCaptureItem from HMONITOR via interop factory
|
|
||||||
var item = CreateCaptureItemForMonitor(hMon);
|
|
||||||
|
|
||||||
// Free-threaded pool: no STA required, supports polling via TryGetNextFrame
|
|
||||||
_framePool = Direct3D11CaptureFramePool.CreateFreeThreaded(
|
|
||||||
winrtDevice, DirectXPixelFormat.B8G8R8A8UIntNormalized,
|
|
||||||
1, item.Size);
|
|
||||||
|
|
||||||
_session = _framePool.CreateCaptureSession(item);
|
|
||||||
_session.StartCapture();
|
|
||||||
Log.Debug("WGC capture started ({W}x{H})", item.Size.Width, item.Size.Height);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GraphicsCaptureItem CreateCaptureItemForMonitor(IntPtr hMonitor)
|
|
||||||
{
|
|
||||||
const string className = "Windows.Graphics.Capture.GraphicsCaptureItem";
|
|
||||||
var interopIid = new Guid("3628E81B-3CAC-4C60-B7F4-23CE0E0C3356");
|
|
||||||
|
|
||||||
WindowsCreateString(className, className.Length, out var hString);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Marshal.ThrowExceptionForHR(
|
|
||||||
RoGetActivationFactory(hString, ref interopIid, out var factoryPtr));
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var interop = (IGraphicsCaptureItemInterop)Marshal.GetObjectForIUnknown(factoryPtr);
|
|
||||||
// IGraphicsCaptureItem GUID
|
|
||||||
var itemIid = new Guid("79C3F95B-31F7-4EC2-A464-632EF5D30760");
|
|
||||||
var itemPtr = interop.CreateForMonitor(hMonitor, ref itemIid);
|
|
||||||
var item = (GraphicsCaptureItem)Marshal.GetObjectForIUnknown(itemPtr);
|
|
||||||
Marshal.Release(itemPtr);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Marshal.Release(factoryPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
WindowsDeleteString(hString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe ScreenFrame? CaptureFrame()
|
|
||||||
{
|
|
||||||
var frame = _framePool.TryGetNextFrame();
|
|
||||||
if (frame == null) return null;
|
|
||||||
|
|
||||||
var srcTexture = GetTextureFromSurface(frame.Surface);
|
|
||||||
if (srcTexture == null) { frame.Dispose(); return null; }
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Zero-copy: Mat wraps mapped staging pointer, RowPitch handles GPU row padding
|
|
||||||
var mat = Mat.FromPixelData(h, w, MatType.CV_8UC4, mapped.DataPointer, (int)mapped.RowPitch);
|
|
||||||
|
|
||||||
return new ScreenFrame(mat, () =>
|
|
||||||
{
|
|
||||||
_context.Unmap(_staging!, 0);
|
|
||||||
srcTexture.Dispose();
|
|
||||||
frame.Dispose();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe Mat? CaptureRegion(Region region)
|
|
||||||
{
|
|
||||||
using var frame = CaptureFrame();
|
|
||||||
if (frame == null) return null;
|
|
||||||
return frame.CropBgr(region);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ID3D11Texture2D? GetTextureFromSurface(IDirect3DSurface surface)
|
|
||||||
{
|
|
||||||
var unknown = Marshal.GetIUnknownForObject(surface);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var accessIid = new Guid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1");
|
|
||||||
var hr = Marshal.QueryInterface(unknown, ref accessIid, out var accessPtr);
|
|
||||||
if (hr != 0) return null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var access = (IDirect3DDxgiInterfaceAccess)Marshal.GetObjectForIUnknown(accessPtr);
|
|
||||||
// ID3D11Texture2D GUID
|
|
||||||
var texIid = new Guid("6f15aaf2-d208-4e89-9ab4-489535d34f9c");
|
|
||||||
var texPtr = access.GetInterface(ref texIid);
|
|
||||||
return new ID3D11Texture2D(texPtr);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Marshal.Release(accessPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Marshal.Release(unknown);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
_session?.Dispose();
|
|
||||||
_framePool?.Dispose();
|
|
||||||
_staging?.Dispose();
|
|
||||||
_context?.Dispose();
|
|
||||||
_device?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -7,7 +7,7 @@ using Vortice.Direct3D11;
|
||||||
using Vortice.DXGI;
|
using Vortice.DXGI;
|
||||||
using Region = Poe2Trade.Core.Region;
|
using Region = Poe2Trade.Core.Region;
|
||||||
|
|
||||||
namespace Poe2Trade.Navigation;
|
namespace Poe2Trade.Screen;
|
||||||
|
|
||||||
public sealed class DesktopDuplication : IScreenCapture
|
public sealed class DesktopDuplication : IScreenCapture
|
||||||
{
|
{
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace Poe2Trade.Navigation;
|
namespace Poe2Trade.Screen;
|
||||||
|
|
||||||
public class FramePipeline : IDisposable
|
public class FramePipeline : IDisposable
|
||||||
{
|
{
|
||||||
|
|
@ -5,7 +5,7 @@ using OpenCvSharp;
|
||||||
using OpenCvSharp.Extensions;
|
using OpenCvSharp.Extensions;
|
||||||
using Region = Poe2Trade.Core.Region;
|
using Region = Poe2Trade.Core.Region;
|
||||||
|
|
||||||
namespace Poe2Trade.Navigation;
|
namespace Poe2Trade.Screen;
|
||||||
|
|
||||||
public sealed class GdiCapture : IScreenCapture
|
public sealed class GdiCapture : IScreenCapture
|
||||||
{
|
{
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace Poe2Trade.Navigation;
|
namespace Poe2Trade.Screen;
|
||||||
|
|
||||||
public interface IFrameConsumer
|
public interface IFrameConsumer
|
||||||
{
|
{
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using OpenCvSharp;
|
using OpenCvSharp;
|
||||||
using Region = Poe2Trade.Core.Region;
|
using Region = Poe2Trade.Core.Region;
|
||||||
|
|
||||||
namespace Poe2Trade.Navigation;
|
namespace Poe2Trade.Screen;
|
||||||
|
|
||||||
public interface IScreenCapture : IDisposable
|
public interface IScreenCapture : IDisposable
|
||||||
{
|
{
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
<PackageReference Include="OpenCvSharp4.Extensions" Version="4.11.0.*" />
|
<PackageReference Include="OpenCvSharp4.Extensions" Version="4.11.0.*" />
|
||||||
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.11.0.*" />
|
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.11.0.*" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="8.0.12" />
|
<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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Poe2Trade.Core\Poe2Trade.Core.csproj" />
|
<ProjectReference Include="..\Poe2Trade.Core\Poe2Trade.Core.csproj" />
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using OpenCvSharp;
|
using OpenCvSharp;
|
||||||
using Region = Poe2Trade.Core.Region;
|
using Region = Poe2Trade.Core.Region;
|
||||||
|
|
||||||
namespace Poe2Trade.Navigation;
|
namespace Poe2Trade.Screen;
|
||||||
|
|
||||||
public class ScreenFrame : IDisposable
|
public class ScreenFrame : IDisposable
|
||||||
{
|
{
|
||||||
Loading…
Add table
Add a link
Reference in a new issue