diff --git a/src/Poe2Trade.Navigation/MinimapCapture.cs b/src/Poe2Trade.Navigation/MinimapCapture.cs index 1ac80d6..50cdfd2 100644 --- a/src/Poe2Trade.Navigation/MinimapCapture.cs +++ b/src/Poe2Trade.Navigation/MinimapCapture.cs @@ -1,4 +1,5 @@ using OpenCvSharp; +using Poe2Trade.Screen; using Serilog; using Region = Poe2Trade.Core.Region; using Size = OpenCvSharp.Size; diff --git a/src/Poe2Trade.Navigation/NavigationExecutor.cs b/src/Poe2Trade.Navigation/NavigationExecutor.cs index 81fb056..644d409 100644 --- a/src/Poe2Trade.Navigation/NavigationExecutor.cs +++ b/src/Poe2Trade.Navigation/NavigationExecutor.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using Poe2Trade.Core; using Poe2Trade.Game; +using Poe2Trade.Screen; using Serilog; namespace Poe2Trade.Navigation; @@ -42,18 +43,7 @@ public class NavigationExecutor : IDisposable private static IScreenCapture CreateBackend() { - // WGC primary → DXGI fallback → GDI last resort - 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"); - } - + // DXGI primary → GDI fallback try { var dxgi = new DesktopDuplication(); diff --git a/src/Poe2Trade.Navigation/Poe2Trade.Navigation.csproj b/src/Poe2Trade.Navigation/Poe2Trade.Navigation.csproj index a85875e..a14735f 100644 --- a/src/Poe2Trade.Navigation/Poe2Trade.Navigation.csproj +++ b/src/Poe2Trade.Navigation/Poe2Trade.Navigation.csproj @@ -10,8 +10,6 @@ - - diff --git a/src/Poe2Trade.Navigation/WgcCapture.cs b/src/Poe2Trade.Navigation/WgcCapture.cs deleted file mode 100644 index 6eee6d4..0000000 --- a/src/Poe2Trade.Navigation/WgcCapture.cs +++ /dev/null @@ -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(); - 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(); - } -} diff --git a/src/Poe2Trade.Navigation/DesktopDuplication.cs b/src/Poe2Trade.Screen/DesktopDuplication.cs similarity index 99% rename from src/Poe2Trade.Navigation/DesktopDuplication.cs rename to src/Poe2Trade.Screen/DesktopDuplication.cs index ee62d10..4753a2c 100644 --- a/src/Poe2Trade.Navigation/DesktopDuplication.cs +++ b/src/Poe2Trade.Screen/DesktopDuplication.cs @@ -7,7 +7,7 @@ using Vortice.Direct3D11; using Vortice.DXGI; using Region = Poe2Trade.Core.Region; -namespace Poe2Trade.Navigation; +namespace Poe2Trade.Screen; public sealed class DesktopDuplication : IScreenCapture { diff --git a/src/Poe2Trade.Navigation/FramePipeline.cs b/src/Poe2Trade.Screen/FramePipeline.cs similarity index 96% rename from src/Poe2Trade.Navigation/FramePipeline.cs rename to src/Poe2Trade.Screen/FramePipeline.cs index a3a2739..5a1f31e 100644 --- a/src/Poe2Trade.Navigation/FramePipeline.cs +++ b/src/Poe2Trade.Screen/FramePipeline.cs @@ -1,4 +1,4 @@ -namespace Poe2Trade.Navigation; +namespace Poe2Trade.Screen; public class FramePipeline : IDisposable { diff --git a/src/Poe2Trade.Navigation/GdiCapture.cs b/src/Poe2Trade.Screen/GdiCapture.cs similarity index 97% rename from src/Poe2Trade.Navigation/GdiCapture.cs rename to src/Poe2Trade.Screen/GdiCapture.cs index 36881ce..d4329de 100644 --- a/src/Poe2Trade.Navigation/GdiCapture.cs +++ b/src/Poe2Trade.Screen/GdiCapture.cs @@ -5,7 +5,7 @@ using OpenCvSharp; using OpenCvSharp.Extensions; using Region = Poe2Trade.Core.Region; -namespace Poe2Trade.Navigation; +namespace Poe2Trade.Screen; public sealed class GdiCapture : IScreenCapture { diff --git a/src/Poe2Trade.Navigation/IFrameConsumer.cs b/src/Poe2Trade.Screen/IFrameConsumer.cs similarity index 69% rename from src/Poe2Trade.Navigation/IFrameConsumer.cs rename to src/Poe2Trade.Screen/IFrameConsumer.cs index 4931e55..4dbcc99 100644 --- a/src/Poe2Trade.Navigation/IFrameConsumer.cs +++ b/src/Poe2Trade.Screen/IFrameConsumer.cs @@ -1,4 +1,4 @@ -namespace Poe2Trade.Navigation; +namespace Poe2Trade.Screen; public interface IFrameConsumer { diff --git a/src/Poe2Trade.Navigation/IScreenCapture.cs b/src/Poe2Trade.Screen/IScreenCapture.cs similarity index 84% rename from src/Poe2Trade.Navigation/IScreenCapture.cs rename to src/Poe2Trade.Screen/IScreenCapture.cs index b1e2d1b..e7c901e 100644 --- a/src/Poe2Trade.Navigation/IScreenCapture.cs +++ b/src/Poe2Trade.Screen/IScreenCapture.cs @@ -1,7 +1,7 @@ using OpenCvSharp; using Region = Poe2Trade.Core.Region; -namespace Poe2Trade.Navigation; +namespace Poe2Trade.Screen; public interface IScreenCapture : IDisposable { diff --git a/src/Poe2Trade.Screen/Poe2Trade.Screen.csproj b/src/Poe2Trade.Screen/Poe2Trade.Screen.csproj index 6ef586a..65b90fb 100644 --- a/src/Poe2Trade.Screen/Poe2Trade.Screen.csproj +++ b/src/Poe2Trade.Screen/Poe2Trade.Screen.csproj @@ -10,6 +10,8 @@ + + diff --git a/src/Poe2Trade.Navigation/ScreenFrame.cs b/src/Poe2Trade.Screen/ScreenFrame.cs similarity index 96% rename from src/Poe2Trade.Navigation/ScreenFrame.cs rename to src/Poe2Trade.Screen/ScreenFrame.cs index f4d75ef..29110a4 100644 --- a/src/Poe2Trade.Navigation/ScreenFrame.cs +++ b/src/Poe2Trade.Screen/ScreenFrame.cs @@ -1,7 +1,7 @@ using OpenCvSharp; using Region = Poe2Trade.Core.Region; -namespace Poe2Trade.Navigation; +namespace Poe2Trade.Screen; public class ScreenFrame : IDisposable {