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
{