diff --git a/BetterGenshinImpact/GameTask/AutoFishing/BehaviourTreeExtensions.cs b/BetterGenshinImpact/GameTask/AutoFishing/BehaviourTreeExtensions.cs
index 1106a51d..deec3f59 100644
--- a/BetterGenshinImpact/GameTask/AutoFishing/BehaviourTreeExtensions.cs
+++ b/BetterGenshinImpact/GameTask/AutoFishing/BehaviourTreeExtensions.cs
@@ -166,7 +166,6 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
Directory.CreateDirectory(path);
}
- var bitmap = imageRegion.SrcBitmap;
if (String.IsNullOrWhiteSpace(name))
{
name = $@"{DateTime.Now:yyyyMMddHHmmssffff}.png";
@@ -176,17 +175,25 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
var mat = imageRegion.SrcMat;
if (TaskContext.Instance().Config.CommonConfig.ScreenshotUidCoverEnabled)
{
- var assetScale = TaskContext.Instance().SystemInfo.ScaleTo1080PRatio;
- var rect = new Rect((int)(mat.Width - MaskWindowConfig.UidCoverRightBottomRect.X * assetScale),
- (int)(mat.Height - MaskWindowConfig.UidCoverRightBottomRect.Y * assetScale),
- (int)(MaskWindowConfig.UidCoverRightBottomRect.Width * assetScale),
- (int)(MaskWindowConfig.UidCoverRightBottomRect.Height * assetScale));
- mat.Rectangle(rect, Scalar.White, -1);
+ new Task(() =>
+ {
+ using var mat2 = mat.Clone();
+ var assetScale = TaskContext.Instance().SystemInfo.ScaleTo1080PRatio;
+ var rect = new Rect((int)(mat.Width - MaskWindowConfig.UidCoverRightBottomRect.X * assetScale),
+ (int)(mat.Height - MaskWindowConfig.UidCoverRightBottomRect.Y * assetScale),
+ (int)(MaskWindowConfig.UidCoverRightBottomRect.Width * assetScale),
+ (int)(MaskWindowConfig.UidCoverRightBottomRect.Height * assetScale));
+ mat2.Rectangle(rect, Scalar.White, -1);
+ Cv2.ImWrite(savePath, mat2);
+ }).Start();
}
- new Task(() =>
+ else
{
- Cv2.ImWrite(savePath, mat);
- }).Start();
+ new Task(() =>
+ {
+ Cv2.ImWrite(savePath, mat);
+ }).Start();
+ }
}
public void Reset()
diff --git a/BetterGenshinImpact/GameTask/CaptureContent.cs b/BetterGenshinImpact/GameTask/CaptureContent.cs
index e7d644b5..5f238b59 100644
--- a/BetterGenshinImpact/GameTask/CaptureContent.cs
+++ b/BetterGenshinImpact/GameTask/CaptureContent.cs
@@ -1,7 +1,5 @@
using BetterGenshinImpact.GameTask.Model.Area;
using System;
-using System.Drawing;
-using Fischless.GameCapture;
using OpenCvSharp;
namespace BetterGenshinImpact.GameTask;
@@ -18,9 +16,9 @@ public class CaptureContent : IDisposable
public int FrameRate => (int)(1000 / TimerInterval);
- public ImageRegion CaptureRectArea { get; private set; }
+ public ImageRegion CaptureRectArea { get; }
- public CaptureContent(CaptureImageRes image, int frameIndex, double interval)
+ public CaptureContent(Mat image, int frameIndex, double interval)
{
FrameIndex = frameIndex;
TimerInterval = interval;
@@ -42,5 +40,11 @@ public class CaptureContent : IDisposable
public void Dispose()
{
CaptureRectArea.Dispose();
+ GC.SuppressFinalize(this);
}
-}
\ No newline at end of file
+
+ ~CaptureContent()
+ {
+ Dispose();
+ }
+}
diff --git a/BetterGenshinImpact/GameTask/Common/TaskControl.cs b/BetterGenshinImpact/GameTask/Common/TaskControl.cs
index 19819251..a23092fa 100644
--- a/BetterGenshinImpact/GameTask/Common/TaskControl.cs
+++ b/BetterGenshinImpact/GameTask/Common/TaskControl.cs
@@ -182,7 +182,7 @@ public class TaskControl
}
}
- public static CaptureImageRes CaptureGameImage(IGameCapture? gameCapture)
+ public static Mat CaptureGameImage(IGameCapture? gameCapture)
{
var image = gameCapture?.Capture();
if (image == null)
@@ -208,7 +208,7 @@ public class TaskControl
}
}
- public static CaptureImageRes? CaptureGameImageNoRetry(IGameCapture? gameCapture)
+ public static Mat? CaptureGameImageNoRetry(IGameCapture? gameCapture)
{
return gameCapture?.Capture();
}
diff --git a/BetterGenshinImpact/GameTask/Model/Area/DesktopRegion.cs b/BetterGenshinImpact/GameTask/Model/Area/DesktopRegion.cs
index 651460e2..f16b13b9 100644
--- a/BetterGenshinImpact/GameTask/Model/Area/DesktopRegion.cs
+++ b/BetterGenshinImpact/GameTask/Model/Area/DesktopRegion.cs
@@ -69,7 +69,7 @@ public class DesktopRegion : Region
Simulation.SendInput.Mouse.MoveMouseBy((int)dx, (int)dy);
}
- public GameCaptureRegion Derive(CaptureImageRes captureMat, int x, int y)
+ public GameCaptureRegion Derive(Mat captureMat, int x, int y)
{
return new GameCaptureRegion(captureMat, x, y, this, new TranslationConverter(x, y));
}
diff --git a/BetterGenshinImpact/GameTask/Model/Area/GameCaptureRegion.cs b/BetterGenshinImpact/GameTask/Model/Area/GameCaptureRegion.cs
index 5e4def3d..65898223 100644
--- a/BetterGenshinImpact/GameTask/Model/Area/GameCaptureRegion.cs
+++ b/BetterGenshinImpact/GameTask/Model/Area/GameCaptureRegion.cs
@@ -12,19 +12,8 @@ namespace BetterGenshinImpact.GameTask.Model.Area;
/// 游戏捕获区域类
/// 主要用于转换到遮罩窗口的坐标
///
-public class GameCaptureRegion : ImageRegion
+public class GameCaptureRegion(Mat mat, int initX, int initY, Region? owner = null, INodeConverter? converter = null, DrawContent? drawContent = null) : ImageRegion(mat, initX, initY, owner, converter, drawContent)
{
- public GameCaptureRegion(Mat mat, int initX, int initY, Region? owner = null, INodeConverter? converter = null,
- DrawContent? drawContent = null) : base(mat, initX, initY, owner, converter, drawContent)
- {
- }
-
- public GameCaptureRegion(CaptureImageRes image, int initX, int initY, Region? owner = null,
- INodeConverter? converter = null, DrawContent? drawContent = null) : base(image, initX, initY, owner, converter, drawContent)
- {
- }
-
-
///
/// 在游戏捕获图像的坐标维度进行转换到遮罩窗口的坐标维度
///
diff --git a/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs b/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs
index a57e7ff7..f3aa4601 100644
--- a/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs
+++ b/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs
@@ -78,23 +78,6 @@ public class ImageRegion : Region
_srcMat = mat;
}
- public ImageRegion(CaptureImageRes image, int x, int y, Region? owner = null, INodeConverter? converter = null,
- DrawContent? drawContent = null) : base(x, y, image.Width, image.Height, owner, converter, drawContent)
- {
- if (image.Bitmap != null)
- {
- _srcBitmap = image.Bitmap;
- }
- else if (image.Mat != null)
- {
- _srcMat = image.Mat;
- }
- else
- {
- throw new Exception("ImageRegion的构造函数参数错误");
- }
- }
-
private bool HasImage()
{
return _srcBitmap != null || _srcMat != null;
diff --git a/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs b/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs
index 4b256e49..bdab1edf 100644
--- a/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs
+++ b/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs
@@ -386,9 +386,12 @@ namespace BetterGenshinImpact.GameTask
Directory.CreateDirectory(path);
}
- var image = TaskControl.CaptureGameImage(GameCapture);
- var mat = image.ForceGetMat();
- if (mat == null)
+ Mat mat;
+ try
+ {
+ mat = TaskControl.CaptureGameImage(GameCapture);
+ }
+ catch (Exception)
{
_logger.LogInformation("截图失败,未获取到图像");
return;
@@ -410,6 +413,8 @@ namespace BetterGenshinImpact.GameTask
Cv2.ImWrite(savePath, mat);
}
+ mat.Dispose();
+
_logger.LogInformation("截图已保存: {Name}", name);
}
catch (Exception e)
diff --git a/BetterGenshinImpact/Helpers/Extensions/BitmapExtension.cs b/BetterGenshinImpact/Helpers/Extensions/BitmapExtension.cs
index 81896c80..54214dd2 100644
--- a/BetterGenshinImpact/Helpers/Extensions/BitmapExtension.cs
+++ b/BetterGenshinImpact/Helpers/Extensions/BitmapExtension.cs
@@ -1,12 +1,55 @@
using OpenCvSharp;
+using System;
using System.Drawing;
using System.IO;
+using System.Windows.Media;
using System.Windows.Media.Imaging;
+using Color = System.Drawing.Color;
namespace BetterGenshinImpact.Helpers.Extensions
{
public static class BitmapExtension
{
+ public static BitmapSource ToBitmapSource(this Bitmap bitmap)
+ {
+ return bitmap.ToBitmapSource(out _);
+ }
+
+ public static BitmapSource ToBitmapSource(this Bitmap bitmap, out bool bottomUp)
+ {
+ var bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
+ System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);
+
+ var stride = bitmapData.Stride;
+ var buffer = bitmapData.Scan0;
+ if (stride < 0)
+ {
+ bottomUp = true;
+ stride = -stride;
+ buffer -= stride * (bitmapData.Height - 1);
+ }
+ else
+ {
+ bottomUp = false;
+ }
+
+ var pixelFormat = bitmap.PixelFormat switch
+ {
+ System.Drawing.Imaging.PixelFormat.Format24bppRgb => PixelFormats.Bgr24,
+ System.Drawing.Imaging.PixelFormat.Format32bppArgb => PixelFormats.Bgra32,
+ _ => throw new NotSupportedException($"Unsupported pixel format {bitmap.PixelFormat}")
+ };
+
+ var bitmapSource = BitmapSource.Create(
+ bitmapData.Width, bitmapData.Height,
+ bitmap.HorizontalResolution, bitmap.VerticalResolution,
+ pixelFormat, null,
+ buffer, stride * bitmapData.Height, stride);
+
+ bitmap.UnlockBits(bitmapData);
+
+ return bitmapSource;
+ }
public static BitmapImage ToBitmapImage(this Bitmap bitmap)
{
diff --git a/BetterGenshinImpact/Helpers/Extensions/EnumExtensions.cs b/BetterGenshinImpact/Helpers/Extensions/EnumExtensions.cs
index 64e61239..6e5d5cb5 100644
--- a/BetterGenshinImpact/Helpers/Extensions/EnumExtensions.cs
+++ b/BetterGenshinImpact/Helpers/Extensions/EnumExtensions.cs
@@ -17,11 +17,23 @@ public static class EnumExtensions
.FirstOrDefault()
?.Description ?? value.ToString();
}
-
+
+ public static int GetOrder(this Enum value)
+ {
+ return value.GetType()
+ .GetField(value.ToString())
+ ?.GetCustomAttributes(typeof(DefaultValueAttribute), false)
+ .Cast()
+ .FirstOrDefault()
+ ?.Value as int? ?? 0;
+ }
+
public static IEnumerable> ToEnumItems() where T : Enum
{
return Enum.GetValues(typeof(T))
.Cast()
- .Select(EnumItem.Create);
+ .Select(EnumItem.Create)
+ .OrderBy(x => x.Order)
+ .ThenBy(x => x.Value);
}
-}
\ No newline at end of file
+}
diff --git a/BetterGenshinImpact/Model/EnumItem.cs b/BetterGenshinImpact/Model/EnumItem.cs
index 43043a29..7daf5c4b 100644
--- a/BetterGenshinImpact/Model/EnumItem.cs
+++ b/BetterGenshinImpact/Model/EnumItem.cs
@@ -7,6 +7,7 @@ public class EnumItem where T : Enum
{
public T Value { get; set; }
public string DisplayName { get; set; }
+ public int Order { get; set; }
public string EnumName => Value.ToString();
// 提供一个静态工厂方法来创建实例
@@ -15,7 +16,10 @@ public class EnumItem where T : Enum
return new EnumItem
{
Value = value,
- DisplayName = value.GetDescription() // 使用扩展方法获取Description
+ DisplayName = value.GetDescription(), // 使用扩展方法获取Description
+ Order = value.GetOrder()
};
}
-}
\ No newline at end of file
+
+
+}
diff --git a/BetterGenshinImpact/Service/Notification/NotificationService.cs b/BetterGenshinImpact/Service/Notification/NotificationService.cs
index 47b0bd05..b6dff1ca 100644
--- a/BetterGenshinImpact/Service/Notification/NotificationService.cs
+++ b/BetterGenshinImpact/Service/Notification/NotificationService.cs
@@ -406,19 +406,8 @@ public class NotificationService : IHostedService, IDisposable
try
{
- var image = TaskControl.CaptureGameImageNoRetry(TaskTriggerDispatcher.GlobalGameCapture);
-
- if (image != null)
- {
- if (image.Bitmap != null)
- {
- notificationData.Screenshot = image.Bitmap;
- }
- else if (image.Mat != null)
- {
- notificationData.Screenshot = image.Mat.ToBitmap();
- }
- }
+ var mat = TaskControl.CaptureGameImageNoRetry(TaskTriggerDispatcher.GlobalGameCapture);
+ if (mat != null) notificationData.Screenshot = mat.ToBitmap();
}
catch (Exception ex)
{
diff --git a/BetterGenshinImpact/View/CaptureTestWindow.xaml b/BetterGenshinImpact/View/CaptureTestWindow.xaml
index 2dc8eece..139ffd05 100644
--- a/BetterGenshinImpact/View/CaptureTestWindow.xaml
+++ b/BetterGenshinImpact/View/CaptureTestWindow.xaml
@@ -12,6 +12,5 @@
-
diff --git a/BetterGenshinImpact/View/CaptureTestWindow.xaml.cs b/BetterGenshinImpact/View/CaptureTestWindow.xaml.cs
index 62d66548..6831a6ec 100644
--- a/BetterGenshinImpact/View/CaptureTestWindow.xaml.cs
+++ b/BetterGenshinImpact/View/CaptureTestWindow.xaml.cs
@@ -1,20 +1,22 @@
using BetterGenshinImpact.GameTask;
-using BetterGenshinImpact.Helpers.Extensions;
using Fischless.GameCapture;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
+using System.Windows.Media.Imaging;
using BetterGenshinImpact.Helpers;
-using OpenCvSharp.WpfExtensions;
+using OpenCvSharp;
using Wpf.Ui.Violeta.Controls;
+using Size = OpenCvSharp.Size;
namespace BetterGenshinImpact.View;
-public partial class CaptureTestWindow : Window
+public partial class CaptureTestWindow
{
private IGameCapture? _capture;
+ private Size _cacheSize;
private long _captureTime;
private long _transferTime;
@@ -57,23 +59,75 @@ public partial class CaptureTestWindow : Window
CompositionTarget.Rendering += Loop;
}
+ private static WriteableBitmap ToWriteableBitmap(Mat mat)
+ {
+ PixelFormat pixelFormat;
+ var type = mat.Type();
+ if (type == MatType.CV_8UC3)
+ {
+ pixelFormat = PixelFormats.Bgr24;
+ }
+ else if (type == MatType.CV_8UC4)
+ {
+ pixelFormat = PixelFormats.Bgra32;
+ }
+ else
+ {
+ throw new NotSupportedException($"Unsupported pixel format {type}");
+ }
+
+ var bitmap = new WriteableBitmap(mat.Width, mat.Height, 96, 96, pixelFormat, null);
+ UpdateWriteableBitmap(bitmap, mat);
+
+ return bitmap;
+ }
+
+ private static unsafe void UpdateWriteableBitmap(WriteableBitmap bitmap, Mat mat)
+ {
+ bitmap.Lock();
+ var stride = bitmap.BackBufferStride;
+ var step = mat.Step();
+ if (stride == step)
+ {
+ var length = stride * bitmap.PixelHeight;
+ Buffer.MemoryCopy(mat.Data.ToPointer(), bitmap.BackBuffer.ToPointer(), length, length);
+ }
+ else
+ {
+ var length = Math.Min(stride, step);
+ for (var i = 0; i < bitmap.PixelHeight; i++)
+ {
+ Buffer.MemoryCopy((void*)(mat.Data + i * step), (void*)(bitmap.BackBuffer + i * stride), length, length);
+ }
+ }
+ bitmap.AddDirtyRect(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight));
+ bitmap.Unlock();
+ }
+
private void Loop(object? sender, EventArgs e)
{
var sw = new Stopwatch();
sw.Start();
- var image = _capture?.Capture();
+ using var mat = _capture?.Capture();
sw.Stop();
Debug.WriteLine("截图耗时:" + sw.ElapsedMilliseconds);
_captureTime += sw.ElapsedMilliseconds;
- var bitmap = image?.ForceGetBitmap();
- if (bitmap != null)
+ if (mat != null)
{
- Debug.WriteLine($"Bitmap:{bitmap.Width}x{bitmap.Height}");
+ Debug.WriteLine($"Bitmap:{mat.Width}x{mat.Height}");
_captureCount++;
sw.Reset();
sw.Start();
- DisplayCaptureResultImage.Source = bitmap.ToBitmapSource();
+ if (_cacheSize != mat.Size())
+ {
+ DisplayCaptureResultImage.Source = ToWriteableBitmap(mat);
+ _cacheSize = mat.Size();
+ }
+ else
+ {
+ UpdateWriteableBitmap((WriteableBitmap)DisplayCaptureResultImage.Source, mat);
+ }
sw.Stop();
Debug.WriteLine("转换耗时:" + sw.ElapsedMilliseconds);
_transferTime += sw.ElapsedMilliseconds;
diff --git a/BetterGenshinImpact/View/Pages/HomePage.xaml b/BetterGenshinImpact/View/Pages/HomePage.xaml
index 3ece3250..7fcf220b 100644
--- a/BetterGenshinImpact/View/Pages/HomePage.xaml
+++ b/BetterGenshinImpact/View/Pages/HomePage.xaml
@@ -132,7 +132,7 @@
Stop();
+ public void Dispose()
+ {
+ Stop();
+ GC.SuppressFinalize(this);
+ }
- public CaptureImageRes? Capture() => Capture(false);
+ public Mat? Capture() => Capture(false);
public void Start(nint hWnd, Dictionary? settings = null)
{
@@ -50,9 +54,8 @@ public class BitBltCapture : IGameCapture
///
- /// 检查窗口大小,如果改变则更新截图尺寸。返回是否成功。
+ /// 检查窗口大小,如果改变则更新截图尺寸。
///
- ///
private void CheckSession()
{
if (_lockSlim.WaitingWriteCount > 0 || !_lockSlim.TryEnterWriteLock(TimeSpan.FromSeconds(0.5)))
@@ -66,7 +69,7 @@ public class BitBltCapture : IGameCapture
{
// 窗口状态变化可能会导致会话失效
// 上次截图失败则重置会话,避免一直截图失败
- if (_session is not null && (_session.IsInvalid() || _lastCaptureFailed))
+ if (_session is not null && (_session.Invalid || _lastCaptureFailed))
{
_session.Dispose();
_session = null;
@@ -84,15 +87,18 @@ public class BitBltCapture : IGameCapture
var width = windowRect.right - windowRect.left;
var height = windowRect.bottom - windowRect.top;
- _session ??= new BitBltSession(_hWnd, width, height);
- if (_session.Width == width && _session.Height == height)
+ if (_session != null)
{
- // 窗口大小没有改变
- return;
+ if (_session.Width == width && _session.Height == height)
+ {
+ // 窗口大小没有改变
+ return;
+ }
+
+ // 窗口尺寸被改变,释放资源,然后重新创建会话
+ _session.Dispose();
}
- // 窗口尺寸被改变,释放资源,然后重新创建会话
- _session.Dispose();
_session = new BitBltSession(_hWnd, width, height);
}
catch (Exception e)
@@ -110,7 +116,7 @@ public class BitBltCapture : IGameCapture
///
/// 递归标志
/// 截图
- private CaptureImageRes? Capture(bool recursive)
+ private Mat? Capture(bool recursive)
{
if (_hWnd == IntPtr.Zero)
{
@@ -139,13 +145,13 @@ public class BitBltCapture : IGameCapture
{
// 成功截图
_lastCaptureFailed = false;
- return CaptureImageRes.BuildNullable(result);
+ return result;
}
- else if (result is null)
+ else
{
- if (_lastCaptureFailed) return CaptureImageRes.BuildNullable(result); // 这不是首次失败,不再进行尝试
+ if (_lastCaptureFailed) return result; // 这不是首次失败,不再进行尝试
_lastCaptureFailed = true; // 设置失败标志
- if (recursive) return CaptureImageRes.BuildNullable(result); // 已设置递归标志,说明也不是首次失败
+ if (recursive) return result; // 已设置递归标志,说明也不是首次失败
}
}
finally
@@ -165,24 +171,15 @@ public class BitBltCapture : IGameCapture
/// 截图功能的实现。需要加锁后调用,一般只由 Capture 方法调用。
///
///
- private Bitmap? Capture0()
+ private Mat? Capture0()
{
- Bitmap? bitmap = null;
try
{
- if (_session is null)
- {
- // 没有成功创建会话,直接返回空
- return null;
- }
- bitmap = _session.GetImage();
- return bitmap;
+ return _session?.GetImage();
}
catch (Exception e)
{
// 理论这里不应出现异常,除非窗口不存在了或者有什么bug
- // 出现异常的时候释放内存
- bitmap?.Dispose();
Error.WriteLine("[BitBlt]Failed to capture image {0}", e);
return null;
}
@@ -208,4 +205,4 @@ public class BitBltCapture : IGameCapture
IsCapturing = false;
}
-}
\ No newline at end of file
+}
diff --git a/Fischless.GameCapture/BitBlt/BitBltMat.cs b/Fischless.GameCapture/BitBlt/BitBltMat.cs
new file mode 100644
index 00000000..f2ed0c80
--- /dev/null
+++ b/Fischless.GameCapture/BitBlt/BitBltMat.cs
@@ -0,0 +1,32 @@
+using OpenCvSharp;
+using OpenCvSharp.Internal;
+
+namespace Fischless.GameCapture.BitBlt;
+
+public class BitBltMat : Mat
+{
+ private readonly BitBltSession _session;
+ private readonly IntPtr _data;
+
+ private BitBltMat(IntPtr ptr, BitBltSession session, IntPtr data)
+ {
+ if (ptr == IntPtr.Zero)
+ throw new OpenCvSharpException("Native object address is NULL");
+ this.ptr = ptr;
+ _session = session;
+ _data = data;
+ }
+
+ public static Mat FromPixelData(BitBltSession session, int rows, int cols, MatType type, IntPtr data, long step = 0)
+ {
+ NativeMethods.HandleException(
+ NativeMethods.core_Mat_new8(rows, cols, type, data, new IntPtr(step), out var ptr));
+ return new BitBltMat(ptr, session, data);
+ }
+
+ protected override void DisposeUnmanaged()
+ {
+ base.DisposeUnmanaged();
+ _session.ReleaseBuffer(_data);
+ }
+}
diff --git a/Fischless.GameCapture/BitBlt/BitBltOldCapture.cs b/Fischless.GameCapture/BitBlt/BitBltOldCapture.cs
deleted file mode 100644
index 64875683..00000000
--- a/Fischless.GameCapture/BitBlt/BitBltOldCapture.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using System.Diagnostics;
-using Vanara.PInvoke;
-
-namespace Fischless.GameCapture.BitBlt;
-
-public class BitBltOldCapture : IGameCapture
-{
- private nint _hWnd;
-
- public static object LockObject { get; } = new();
-
- public bool IsCapturing { get; private set; }
-
- public void Dispose() => Stop();
-
- public void Start(nint hWnd, Dictionary? settings = null)
- {
- _hWnd = hWnd;
- IsCapturing = true;
- if (settings != null && settings.TryGetValue("autoFixWin11BitBlt", out var value))
- {
- if (value is true)
- {
- BitBltRegistryHelper.SetDirectXUserGlobalSettings();
- }
- }
- }
-
- public CaptureImageRes? Capture()
- {
- if (_hWnd == IntPtr.Zero)
- {
- return null;
- }
-
- try
- {
- lock (LockObject)
- {
- User32.GetClientRect(_hWnd, out var windowRect);
- int x = default, y = default;
- var width = windowRect.right - windowRect.left;
- var height = windowRect.bottom - windowRect.top;
-
- Bitmap bitmap = new(width, height);
- using System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap);
- var hdcDest = g.GetHdc();
- var hdcSrc = User32.GetDC(_hWnd == IntPtr.Zero ? User32.GetDesktopWindow() : _hWnd);
- Gdi32.StretchBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, width, height,
- Gdi32.RasterOperationMode.SRCCOPY);
- g.ReleaseHdc();
- Gdi32.DeleteDC(hdcDest);
- Gdi32.DeleteDC(hdcSrc);
- return CaptureImageRes.BuildNullable(bitmap);
- }
- }
- catch (Exception e)
- {
- Debug.WriteLine(e);
- }
-
- return null!;
- }
-
- public void Stop()
- {
- _hWnd = IntPtr.Zero;
- IsCapturing = false;
- }
-}
\ No newline at end of file
diff --git a/Fischless.GameCapture/BitBlt/BitBltSession.cs b/Fischless.GameCapture/BitBlt/BitBltSession.cs
index 3714d6d1..79520926 100644
--- a/Fischless.GameCapture/BitBlt/BitBltSession.cs
+++ b/Fischless.GameCapture/BitBlt/BitBltSession.cs
@@ -1,3 +1,4 @@
+using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.InteropServices;
using OpenCvSharp;
@@ -7,17 +8,6 @@ namespace Fischless.GameCapture.BitBlt;
public class BitBltSession : IDisposable
{
- // 用于过滤alpha通道
- private static readonly Gdi32.BLENDFUNCTION BlendFunction = new()
- {
- BlendOp = 0, //Gdi32.BlendOperation.AC_SRC_OVER,
- BlendFlags = 0,
- SourceConstantAlpha = 255,
- AlphaFormat = 0
- };
-
- private readonly int _height;
-
// 窗口句柄
private readonly HWND _hWnd;
@@ -25,13 +15,17 @@ public class BitBltSession : IDisposable
// 大小计算好的宽高,截图用这个不会爆炸
private readonly int _width;
-
- // 位图指针,这个指针会在位图释放时自动释放
- private IntPtr _bitsPtr;
+ private readonly int _height;
// 位图句柄
private Gdi32.SafeHBITMAP _hBitmap;
+ // 位图数据指针,这个指针会在位图释放时自动释放
+ private IntPtr _bitsPtr;
+
+ // 位图数据一行字节数
+ private readonly int _stride;
+
// 缓冲区 CompatibleDC
private Gdi32.SafeHDC _hdcDest;
@@ -41,17 +35,24 @@ public class BitBltSession : IDisposable
// 旧位图,析构时一起释放掉
private HGDIOBJ _oldBitmap;
+ // Bitmap buffer 大小
+ private readonly int _bufferSize;
+
+ // Bitmap 内存池
+ private readonly ConcurrentStack _bufferPool = [];
+
+ // 窗口原宽高
+ public int Width { get; }
+ public int Height { get; }
+
+ ///
+ /// 不是所有的失效情况都能被检测到
+ ///
+ ///
+ public bool Invalid => _hWnd.IsNull || _hdcSrc.IsInvalid || _hdcDest.IsInvalid || _hBitmap.IsInvalid || _bitsPtr == 0;
public BitBltSession(HWND hWnd, int w, int h)
{
- // 分配全空对象 避免 System.NullReferenceException
- // 不能直接在上面写,不然zz编译器会报warning,感觉抑制warning也不优雅
- _oldBitmap = Gdi32.SafeHBITMAP.Null;
- _hBitmap = Gdi32.SafeHBITMAP.Null;
- _hdcDest = Gdi32.SafeHDC.Null;
- _hdcSrc = User32.SafeReleaseHDC.Null;
- _bitsPtr = IntPtr.Zero;
-
if (hWnd.IsNull) throw new Exception("hWnd is invalid");
_hWnd = hWnd;
@@ -67,7 +68,12 @@ public class BitBltSession : IDisposable
try
{
_hdcSrc = User32.GetDC(_hWnd);
- if (_hdcSrc.IsInvalid) throw new Exception("Failed to get DC for {_hWnd}");
+ if (_hdcSrc.IsInvalid) throw new Exception($"Failed to get DC for {_hWnd}");
+
+ var hdcRasterCaps = Gdi32.GetDeviceCaps(_hdcSrc, Gdi32.DeviceCap.RASTERCAPS);
+ if ((hdcRasterCaps | 0x800) == 0) // RC_STRETCHBLT
+ // 设备不支持 BitBlt
+ throw new Exception("BitBlt not supported");
var hdcSrcPixel = Gdi32.GetDeviceCaps(_hdcSrc, Gdi32.DeviceCap.BITSPIXEL);
// 颜色位数
@@ -87,9 +93,14 @@ public class BitBltSession : IDisposable
throw new Exception("Device does not support clipping");
_hdcDest = Gdi32.CreateCompatibleDC(_hdcSrc);
+ if (_hdcDest.IsInvalid)
+ {
+ Debug.Fail("Failed to create CompatibleDC");
+ throw new Exception($"Failed to create CompatibleDC for {_hWnd}");
+ }
+
var maxW = Gdi32.GetDeviceCaps(_hdcDest, Gdi32.DeviceCap.HORZRES);
var maxH = Gdi32.GetDeviceCaps(_hdcDest, Gdi32.DeviceCap.VERTRES);
-
if (maxW <= 0 || maxH <= 0)
// 显示器不见啦
throw new Exception("Can not get display size");
@@ -98,12 +109,6 @@ public class BitBltSession : IDisposable
_width = Math.Min(maxW, Width);
_height = Math.Min(maxH, Height);
- if (_hdcSrc.IsInvalid)
- {
- Debug.Fail("Failed to create CompatibleDC");
- throw new Exception("Failed to create CompatibleDC for {_hWnd}");
- }
-
var bmi = new Gdi32.BITMAPINFO
{
@@ -114,21 +119,27 @@ public class BitBltSession : IDisposable
biHeight = -_height, // Top-down image
biPlanes = (ushort)hdcSrcPlanes,
biBitCount = (ushort)(hdcSrcPixel - 8), //RGBA->RGB
- biCompression = 0, // BI_RGB
+ biCompression = Gdi32.BitmapCompressionMode.BI_RGB,
biSizeImage = 0
}
};
- _hBitmap = Gdi32.CreateDIBSection(_hdcDest, bmi, Gdi32.DIBColorMode.DIB_RGB_COLORS, out var bits,
+ _hBitmap = Gdi32.CreateDIBSection(_hdcDest, bmi, Gdi32.DIBColorMode.DIB_RGB_COLORS, out _bitsPtr,
IntPtr.Zero);
- if (_hBitmap.IsInvalid || bits == 0)
+ if (_hBitmap.IsInvalid || _bitsPtr == 0)
{
if (!_hBitmap.IsInvalid) Gdi32.DeleteObject(_hBitmap);
- throw new Exception($"Failed to create dIB section for {_hWnd}");
+ throw new Exception($"Failed to create dIB section for {_hdcDest}");
}
- _bitsPtr = bits;
+ var bitmap = Gdi32.GetObject(_hBitmap);
+ if (bitmap.bmPlanes != 1 || bitmap.bmBitsPixel != 24)
+ throw new Exception("Unsupported bitmap format");
+
+ _stride = bitmap.bmWidthBytes;
+ _bufferSize = bitmap.bmWidth * bitmap.bmHeight * 3;
+
_oldBitmap = Gdi32.SelectObject(_hdcDest, _hBitmap);
if (_oldBitmap.IsNull) throw new Exception("Failed to select object");
}
@@ -144,73 +155,58 @@ public class BitBltSession : IDisposable
}
}
- //原宽高,用来喂上层
- public int Width { get; }
- public int Height { get; }
-
-
public void Dispose()
{
lock (_lockObject)
{
ReleaseResources();
- GC.SuppressFinalize(this);
}
+ GC.SuppressFinalize(this);
}
-
///
- /// 调用GDI复制到缓冲区并返回新Image
+ /// 调用GDI复制到缓冲区并返回新Mat
///
- public Bitmap? GetImage()
+ public unsafe Mat? GetImage()
{
lock (_lockObject)
{
- Gdi32.GdiFlush();
- if (_hBitmap.IsInvalid)
- // 位图指针无效
- return null;
-
- Gdi32.BITMAP bitmap;
- try
- {
- // 获取位图详情,这应该能抵挡住奇奇怪怪的bug
- bitmap = Gdi32.GetObject(_hBitmap);
- }
- catch (ArgumentException)
- {
- // 可能是位图已经被释放了
- return null;
- }
-
- // 这个是bitmap的结构信息,不用手动释放
- if (bitmap.bmPlanes != 1 || bitmap.bmBitsPixel != 24)
- // 不支持的位图格式
- return null;
- // 直接返回转换后的位图
- var success = Gdi32.AlphaBlend(_hdcDest, 0, 0, _width, _height, _hdcSrc, 0, 0,
- _width, _height, BlendFunction);
+ // 截图
+ var success = Gdi32.StretchBlt(_hdcDest, 0, 0, _width, _height,
+ _hdcSrc, 0, 0, _width, _height, Gdi32.RasterOperationMode.SRCCOPY);
if (!success || !Gdi32.GdiFlush()) return null;
- // return Mat.FromPixelData(bitmap.bmHeight, bitmap.bmWidth, MatType.CV_8UC3, bitmap.bmBits,bitmap.bmWidthBytes);
- return _hBitmap.ToBitmap();
-
- // 原始宏 ((((biWidth * biBitCount) + 31) & ~31) >> 3) => (biWidth * biBitCount + 3) & ~3) (在总位数是8的倍数时,两者等价)
- // 对齐 https://learn.microsoft.com/zh-cn/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
-
- // 适用于32位(RGBA)
- // return Gdi32.BitBlt(_hdcDest, 0, 0, Width, Height, _hdcSrc, 0, 0, Gdi32.RasterOperationMode.SRCCOPY ) ? Mat.FromPixelData(Height, Width, MatType.CV_8UC3, _bitsPtr) : null;
+ // 新Mat
+ var buffer = AcquireBuffer();
+ var step = _width * 3;
+ if (_stride == step)
+ {
+ Buffer.MemoryCopy(_bitsPtr.ToPointer(), buffer.ToPointer(), _bufferSize, _bufferSize);
+ }
+ else
+ {
+ for (var i = 0; i < _height; i++)
+ {
+ Buffer.MemoryCopy((void*)(_bitsPtr + _stride * i), (void*)(buffer + step * i), step, step);
+ }
+ }
+ return BitBltMat.FromPixelData(this, _height, _width, MatType.CV_8UC3, buffer, step);
}
}
-
- ///
- /// 不是所有的失效情况都能被检测到
- ///
- ///
- public bool IsInvalid()
+ private IntPtr AcquireBuffer()
{
- return _hWnd.IsNull || _hdcSrc.IsInvalid || _hdcDest.IsInvalid || _hBitmap.IsInvalid || _bitsPtr == IntPtr.Zero;
+ if (!_bufferPool.TryPop(out var buffer))
+ {
+ buffer = Marshal.AllocHGlobal(_bufferSize);
+ }
+
+ return buffer;
+ }
+
+ public void ReleaseBuffer(IntPtr buffer)
+ {
+ _bufferPool.Push(buffer);
}
///
@@ -218,6 +214,8 @@ public class BitBltSession : IDisposable
///
private void ReleaseResources()
{
+ Gdi32.GdiFlush();
+
if (!_oldBitmap.IsNull)
{
// 先选回资源再释放_hBitmap
@@ -245,7 +243,11 @@ public class BitBltSession : IDisposable
_hdcSrc = User32.SafeReleaseHDC.Null;
}
- Gdi32.GdiFlush();
_bitsPtr = IntPtr.Zero;
+
+ foreach (var buffer in _bufferPool)
+ {
+ Marshal.FreeHGlobal(buffer);
+ }
}
-}
\ No newline at end of file
+}
diff --git a/Fischless.GameCapture/CaptureImageRes.cs b/Fischless.GameCapture/CaptureImageRes.cs
deleted file mode 100644
index 412de1ce..00000000
--- a/Fischless.GameCapture/CaptureImageRes.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-using OpenCvSharp;
-using OpenCvSharp.Extensions;
-
-namespace Fischless.GameCapture;
-
-///
-/// 捕获的图像
-///
-public class CaptureImageRes : IDisposable
-{
- public Bitmap? Bitmap { get; set; }
- public Mat? Mat { get; set; }
-
- public int Width => Mat?.Width ?? Bitmap?.Width ?? 0;
- public int Height => Mat?.Height ?? Bitmap?.Height ?? 0;
-
- public CaptureImageRes(Mat mat)
- {
- Mat = mat;
- }
-
- public CaptureImageRes(Bitmap bitmap)
- {
- Bitmap = bitmap;
- }
-
- public static CaptureImageRes? BuildNullable(Bitmap? bitmap)
- {
- if (bitmap == null)
- {
- return null;
- }
- return new CaptureImageRes(bitmap);
- }
-
- public static CaptureImageRes? BuildNullable(Mat? mat)
- {
- if (mat == null)
- {
- return null;
- }
- return new CaptureImageRes(mat);
- }
-
- ///
- /// 非特殊情况不要使用这个方法,会造成额外的性能消耗
- ///
- ///
- public Mat? ForceGetMat()
- {
- if (Mat == null)
- {
- Mat = Bitmap?.ToMat();
- }
- return Mat;
- }
-
- ///
- /// 非特殊情况不要使用这个方法,会造成额外的性能消耗
- ///
- ///
- public Bitmap? ForceGetBitmap()
- {
- if (Bitmap == null)
- {
- Bitmap = Mat?.ToBitmap();
- }
- return Bitmap;
- }
-
- public void Dispose()
- {
- Bitmap?.Dispose();
- Mat?.Dispose();
- }
-}
\ No newline at end of file
diff --git a/Fischless.GameCapture/CaptureModes.cs b/Fischless.GameCapture/CaptureModes.cs
index eeade079..a383e892 100644
--- a/Fischless.GameCapture/CaptureModes.cs
+++ b/Fischless.GameCapture/CaptureModes.cs
@@ -4,15 +4,19 @@ namespace Fischless.GameCapture;
public enum CaptureModes
{
- // [Description("BitBlt(稳定)")]
- // BitBlt,
-
[Description("BitBlt")]
- BitBlt,
-
- [Description("WindowsGraphicsCapture")]
- WindowsGraphicsCapture,
-
+ [DefaultValue(0)]
+ BitBlt = 0,
+
[Description("DwmGetDxSharedSurface")]
- DwmGetDxSharedSurface
+ [DefaultValue(1)]
+ DwmGetDxSharedSurface = 2,
+
+ [Description("WindowsGraphicsCapture")]
+ [DefaultValue(2)]
+ WindowsGraphicsCapture = 1,
+
+ [Description("WindowsGraphicsCapture(HDR)")]
+ [DefaultValue(3)]
+ WindowsGraphicsCaptureHdr = 3,
}
diff --git a/Fischless.GameCapture/DwmSharedSurface/Helpers/NativeMethods.cs b/Fischless.GameCapture/DwmSharedSurface/Helpers/NativeMethods.cs
deleted file mode 100644
index adfc4e44..00000000
--- a/Fischless.GameCapture/DwmSharedSurface/Helpers/NativeMethods.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System.Runtime.InteropServices;
-using Vanara.PInvoke;
-
-namespace Fischless.GameCapture.DwmSharedSurface.Helpers;
-
-internal class NativeMethods
-{
-
- public delegate bool DwmGetDxSharedSurfaceDelegate(IntPtr hWnd, out IntPtr phSurface, out long pAdapterLuid, out long pFmtWindow, out long pPresentFlags, out long pWin32KUpdateId);
-
- public static DwmGetDxSharedSurfaceDelegate DwmGetDxSharedSurface;
-
- static NativeMethods()
- {
- var ptr = Kernel32.GetProcAddress(Kernel32.GetModuleHandle("user32"), "DwmGetDxSharedSurface");
- DwmGetDxSharedSurface = Marshal.GetDelegateForFunctionPointer(ptr);
- }
-}
\ No newline at end of file
diff --git a/Fischless.GameCapture/DwmSharedSurface/SharedSurfaceCapture.cs b/Fischless.GameCapture/DwmSharedSurface/SharedSurfaceCapture.cs
index 0ca6ec43..86cbfbcf 100644
--- a/Fischless.GameCapture/DwmSharedSurface/SharedSurfaceCapture.cs
+++ b/Fischless.GameCapture/DwmSharedSurface/SharedSurfaceCapture.cs
@@ -1,128 +1,143 @@
-using Fischless.GameCapture.DwmSharedSurface.Helpers;
-using Fischless.GameCapture.Graphics.Helpers;
+using Fischless.GameCapture.Graphics.Helpers;
using SharpDX.Direct3D11;
-using SharpDX.DXGI;
using System.Diagnostics;
+using System.Runtime.InteropServices;
using OpenCvSharp;
-using OpenCvSharp.Extensions;
+using SharpDX;
using Vanara.PInvoke;
using Device = SharpDX.Direct3D11.Device;
-namespace Fischless.GameCapture.DwmSharedSurface
+namespace Fischless.GameCapture.DwmSharedSurface;
+
+public partial class SharedSurfaceCapture : IGameCapture
{
- public class SharedSurfaceCapture : IGameCapture
+ // 窗口句柄
+ private nint _hWnd;
+
+ private static readonly object LockObject = new();
+
+ // D3D 设备
+ private Device? _d3dDevice;
+
+ // 截图区域
+ private ResourceRegion? _region;
+
+ // 暂存贴图
+ private Texture2D? _stagingTexture;
+
+ // Surface 大小
+ private int _surfaceWidth;
+ private int _surfaceHeight;
+
+ public bool IsCapturing { get; private set; }
+
+ [LibraryImport("user32.dll", EntryPoint = "DwmGetDxSharedSurface", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static partial bool DwmGetDxSharedSurface(IntPtr hWnd, out IntPtr phSurface, out long pAdapterLuid, out long pFmtWindow, out long pPresentFlags, out long pWin32KUpdateId);
+
+ public void Dispose()
{
- private nint _hWnd;
- private static readonly object LockObject = new object();
- private Device? _d3dDevice;
+ Stop();
+ GC.SuppressFinalize(this);
+ }
- public void Dispose() => Stop();
+ public void Start(nint hWnd, Dictionary? settings = null)
+ {
+ _hWnd = hWnd;
+ User32.ShowWindow(hWnd, ShowWindowCommand.SW_RESTORE);
+ _region = GetGameScreenRegion(hWnd);
+ _d3dDevice = new Device(SharpDX.Direct3D.DriverType.Hardware, DeviceCreationFlags.BgraSupport); // Software/Hardware
- public bool IsCapturing { get; private set; }
+ IsCapturing = true;
+ }
- private ResourceRegion? _region;
-
-
- public void Start(nint hWnd, Dictionary? settings = null)
+ ///
+ /// 从 GetWindowRect 的带窗口阴影面积矩形 截取出 GetClientRect的矩形(游戏区域)
+ ///
+ ///
+ ///
+ private ResourceRegion? GetGameScreenRegion(nint hWnd)
+ {
+ var exStyle = User32.GetWindowLong(hWnd, User32.WindowLongFlags.GWL_EXSTYLE);
+ if ((exStyle & (int)User32.WindowStylesEx.WS_EX_TOPMOST) != 0)
{
- _hWnd = hWnd;
- User32.ShowWindow(hWnd, ShowWindowCommand.SW_RESTORE);
- User32.SetForegroundWindow(hWnd);
- _region = GetGameScreenRegion(hWnd);
- _d3dDevice = new Device(SharpDX.Direct3D.DriverType.Hardware, DeviceCreationFlags.BgraSupport); // Software/Hardware
-
- //var device = Direct3D11Helper.CreateDevice();
- //_d3dDevice = Direct3D11Helper.CreateSharpDXDevice(device);
-
- IsCapturing = true;
+ return null;
}
- ///
- /// 从 GetWindowRect 的带窗口阴影面积矩形 截取出 GetClientRect的矩形(游戏区域)
- ///
- ///
- ///
- private ResourceRegion? GetGameScreenRegion(nint hWnd)
+ ResourceRegion region = new();
+ User32.GetWindowRect(hWnd, out var windowWithShadowRect);
+ DwmApi.DwmGetWindowAttribute(hWnd, DwmApi.DWMWINDOWATTRIBUTE.DWMWA_EXTENDED_FRAME_BOUNDS, out var windowRect);
+ User32.GetClientRect(_hWnd, out var clientRect);
+
+ region.Left = windowRect.Left - windowWithShadowRect.Left;
+ // 标题栏 windowRect.Height - clientRect.Height 上阴影 windowRect.Top - windowWithShadowRect.Top
+ region.Top = windowRect.Height - clientRect.Height + windowRect.Top - windowWithShadowRect.Top;
+ region.Right = region.Left + clientRect.Width;
+ region.Bottom = region.Top + clientRect.Height;
+ region.Front = 0;
+ region.Back = 1;
+
+ return region;
+ }
+
+ public Mat? Capture()
+ {
+ lock (LockObject)
{
- var exStyle = User32.GetWindowLong(hWnd, User32.WindowLongFlags.GWL_EXSTYLE);
- if ((exStyle & (int)User32.WindowStylesEx.WS_EX_TOPMOST) != 0)
+ if (_d3dDevice == null)
+ {
+ Debug.WriteLine("D3Device is null.");
+ return null;
+ }
+
+ if (!DwmGetDxSharedSurface(_hWnd, out var phSurface, out _, out _, out _, out _))
+ {
+ return null;
+ }
+ if (phSurface == 0)
{
return null;
}
- ResourceRegion region = new();
- User32.GetWindowRect(hWnd, out var windowWithShadowRect);
- DwmApi.DwmGetWindowAttribute(hWnd, DwmApi.DWMWINDOWATTRIBUTE.DWMWA_EXTENDED_FRAME_BOUNDS, out var windowRect);
- User32.GetClientRect(_hWnd, out var clientRect);
-
- region.Left = windowRect.Left - windowWithShadowRect.Left;
- // 标题栏 windowRect.Height - clientRect.Height 上阴影 windowRect.Top - windowWithShadowRect.Top
- region.Top = windowRect.Height - clientRect.Height + windowRect.Top - windowWithShadowRect.Top;
- region.Right = region.Left + clientRect.Width;
- region.Bottom = region.Top + clientRect.Height;
- region.Front = 0;
- region.Back = 1;
-
- return region;
- }
-
- public CaptureImageRes? Capture()
- {
- if (_hWnd == nint.Zero)
+ try
{
- return null;
- }
-
- lock (LockObject)
- {
- NativeMethods.DwmGetDxSharedSurface(_hWnd, out var phSurface, out _, out _, out _, out _);
- if (phSurface == nint.Zero)
- {
- return null;
- }
-
-
- if (_d3dDevice == null)
- {
- Debug.WriteLine("D3Device is null.");
- return null;
- }
-
using var surfaceTexture = _d3dDevice.OpenSharedResource(phSurface);
- using var stagingTexture = CreateStagingTexture(surfaceTexture, _d3dDevice);
- var mat = stagingTexture.CreateMat(_d3dDevice, surfaceTexture, _region);
- if (mat == null)
+
+ if (_stagingTexture == null || _surfaceWidth != surfaceTexture.Description.Width ||
+ _surfaceHeight != surfaceTexture.Description.Height)
{
- return null;
+ _stagingTexture?.Dispose();
+ _stagingTexture = null;
+ _surfaceWidth = surfaceTexture.Description.Width;
+ _surfaceHeight = surfaceTexture.Description.Height;
+ _region = GetGameScreenRegion(_hWnd);
}
- var bgrMat = new Mat();
- Cv2.CvtColor(mat, bgrMat, ColorConversionCodes.BGRA2BGR);
- return CaptureImageRes.BuildNullable(bgrMat);
+
+ _stagingTexture ??= Direct3D11Helper.CreateStagingTexture(_d3dDevice, _surfaceWidth, _surfaceHeight, _region);
+ var mat = _stagingTexture.CreateMat(_d3dDevice, surfaceTexture, _region);
+ return mat;
}
- }
-
-
- private Texture2D CreateStagingTexture(Texture2D surfaceTexture, Device device)
- {
- return new Texture2D(device, new Texture2DDescription
+ catch (SharpDXException e)
{
- Width = _region == null ? surfaceTexture.Description.Width : _region.Value.Right - _region.Value.Left,
- Height = _region == null ? surfaceTexture.Description.Height : _region.Value.Bottom - _region.Value.Top,
- MipLevels = 1,
- ArraySize = 1,
- Format = Format.B8G8R8A8_UNorm,
- Usage = ResourceUsage.Staging,
- SampleDescription = new SampleDescription(1, 0),
- BindFlags = BindFlags.None,
- CpuAccessFlags = CpuAccessFlags.Read,
- OptionFlags = ResourceOptionFlags.None
- });
- }
+ Debug.WriteLine($"SharpDXException: {e.Descriptor}");
+ _d3dDevice?.Dispose();
+ _d3dDevice = new Device(SharpDX.Direct3D.DriverType.Hardware, DeviceCreationFlags.BgraSupport);
+ }
- public void Stop()
+ return null;
+ }
+ }
+
+ public void Stop()
+ {
+ lock (LockObject)
{
- _hWnd = nint.Zero;
+ _stagingTexture?.Dispose();
+ _stagingTexture = null;
+ _d3dDevice?.Dispose();
+ _d3dDevice = null;
+ _hWnd = 0;
IsCapturing = false;
}
}
-}
\ No newline at end of file
+}
diff --git a/Fischless.GameCapture/Fischless.GameCapture.csproj b/Fischless.GameCapture/Fischless.GameCapture.csproj
index aba49767..2e118352 100644
--- a/Fischless.GameCapture/Fischless.GameCapture.csproj
+++ b/Fischless.GameCapture/Fischless.GameCapture.csproj
@@ -12,6 +12,7 @@
+
diff --git a/Fischless.GameCapture/GameCaptureFactory.cs b/Fischless.GameCapture/GameCaptureFactory.cs
index 5e3baa3a..b330a35d 100644
--- a/Fischless.GameCapture/GameCaptureFactory.cs
+++ b/Fischless.GameCapture/GameCaptureFactory.cs
@@ -12,9 +12,9 @@ public class GameCaptureFactory
return mode switch
{
CaptureModes.BitBlt => new BitBlt.BitBltCapture(),
- // CaptureModes.BitBlt => new BitBlt.BitBltOldCapture(),
- CaptureModes.WindowsGraphicsCapture => new Graphics.GraphicsCapture(),
CaptureModes.DwmGetDxSharedSurface => new DwmSharedSurface.SharedSurfaceCapture(),
+ CaptureModes.WindowsGraphicsCapture => new Graphics.GraphicsCapture(),
+ CaptureModes.WindowsGraphicsCaptureHdr => new Graphics.GraphicsCapture(true),
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
};
}
diff --git a/Fischless.GameCapture/Graphics/GraphicsCapture.cs b/Fischless.GameCapture/Graphics/GraphicsCapture.cs
index 8cb1b33f..2a003429 100644
--- a/Fischless.GameCapture/Graphics/GraphicsCapture.cs
+++ b/Fischless.GameCapture/Graphics/GraphicsCapture.cs
@@ -7,31 +7,31 @@ using Windows.Graphics.Capture;
using Windows.Graphics.DirectX;
using Windows.Graphics.DirectX.Direct3D11;
using OpenCvSharp;
-using SharpDX.DXGI;
-using Device = SharpDX.Direct3D11.Device;
-using MapFlags = SharpDX.Direct3D11.MapFlags;
+using SharpDX;
+using SharpDX.D3DCompiler;
namespace Fischless.GameCapture.Graphics;
-public class GraphicsCapture : IGameCapture
+public class GraphicsCapture(bool captureHdr = false) : IGameCapture
{
private nint _hWnd;
- private Direct3D11CaptureFramePool _captureFramePool = null!;
- private GraphicsCaptureItem _captureItem = null!;
+ private Direct3D11CaptureFramePool? _captureFramePool;
+ private GraphicsCaptureItem? _captureItem;
- private GraphicsCaptureSession _captureSession = null!;
+ private GraphicsCaptureSession? _captureSession;
- private IDirect3DDevice _d3dDevice = null!;
+ private IDirect3DDevice? _d3dDevice;
public bool IsCapturing { get; private set; }
private ResourceRegion? _region;
// HDR相关
- private bool _isHdrEnabled;
- private DirectXPixelFormat _pixelFormat = DirectXPixelFormat.B8G8R8A8UIntNormalized;
-
+ private bool _isHdrEnabled = captureHdr;
+ private DirectXPixelFormat _pixelFormat;
+ private Texture2D? _hdrOutputTexture;
+ private ComputeShader? _hdrComputeShader;
// 最新帧的存储
private Mat? _latestFrame;
@@ -40,11 +40,15 @@ public class GraphicsCapture : IGameCapture
// 用于获取帧数据的临时纹理和暂存资源
private Texture2D? _stagingTexture;
- private long _lastFrameTime = 0;
+ private long _lastFrameTime;
- private readonly Stopwatch _frameTimer = new Stopwatch();
+ private readonly Stopwatch _frameTimer = new();
- public void Dispose() => Stop();
+ public void Dispose()
+ {
+ Stop();
+ GC.SuppressFinalize(this);
+ }
public void Start(nint hWnd, Dictionary? settings = null)
{
@@ -65,16 +69,33 @@ public class GraphicsCapture : IGameCapture
// 创建D3D设备
_d3dDevice = Direct3D11Helper.CreateDevice();
- // 检测HDR状态
- _isHdrEnabled = false;
- _pixelFormat = _isHdrEnabled ? DirectXPixelFormat.R16G16B16A16Float : DirectXPixelFormat.B8G8R8A8UIntNormalized;
-
// 创建帧池
- _captureFramePool = Direct3D11CaptureFramePool.Create(
- _d3dDevice,
- _pixelFormat,
- 2,
- _captureItem.Size);
+ try
+ {
+ if (!_isHdrEnabled)
+ {
+ // 不处理 HDR,直接抛异常走 SDR 分支
+ throw new Exception();
+ }
+
+ _pixelFormat = DirectXPixelFormat.R16G16B16A16Float;
+ _captureFramePool = Direct3D11CaptureFramePool.CreateFreeThreaded(
+ _d3dDevice,
+ _pixelFormat,
+ 2,
+ _captureItem.Size);
+ }
+ catch (Exception)
+ {
+ // Fallback
+ _pixelFormat = DirectXPixelFormat.B8G8R8A8UIntNormalized;
+ _captureFramePool = Direct3D11CaptureFramePool.CreateFreeThreaded(
+ _d3dDevice,
+ _pixelFormat,
+ 2,
+ _captureItem.Size);
+ _isHdrEnabled = false;
+ }
_captureItem.Closed += CaptureItemOnClosed;
@@ -82,13 +103,13 @@ public class GraphicsCapture : IGameCapture
_captureSession = _captureFramePool.CreateCaptureSession(_captureItem);
if (ApiInformation.IsPropertyPresent("Windows.Graphics.Capture.GraphicsCaptureSession",
- "IsCursorCaptureEnabled"))
+ nameof(GraphicsCaptureSession.IsCursorCaptureEnabled)))
{
_captureSession.IsCursorCaptureEnabled = false;
}
if (ApiInformation.IsWriteablePropertyPresent("Windows.Graphics.Capture.GraphicsCaptureSession",
- "IsBorderRequired"))
+ nameof(GraphicsCaptureSession.IsBorderRequired)))
{
_captureSession.IsBorderRequired = false;
}
@@ -111,186 +132,124 @@ public class GraphicsCapture : IGameCapture
return null;
}
-
ResourceRegion region = new();
DwmApi.DwmGetWindowAttribute(hWnd, DwmApi.DWMWINDOWATTRIBUTE.DWMWA_EXTENDED_FRAME_BOUNDS,
out var windowRect);
User32.GetClientRect(_hWnd, out var clientRect);
- //POINT point = default; // 这个点和 DwmGetWindowAttribute 结果差1
- //User32.ClientToScreen(hWnd, ref point);
+ POINT point = default;
+ User32.ClientToScreen(_hWnd, ref point);
- region.Left = 0;
- region.Top = windowRect.Height - clientRect.Height;
- region.Right = clientRect.Width;
- region.Bottom = windowRect.Height;
+ region.Left = point.X > windowRect.Left ? point.X - windowRect.Left : 0;
+ region.Top = point.Y > windowRect.Top ? point.Y - windowRect.Top : 0;
+ region.Right = region.Left + clientRect.Width;
+ region.Bottom = region.Top + clientRect.Height;
region.Front = 0;
region.Back = 1;
return region;
}
- public static bool IsHdrEnabled(nint hWnd)
+ private Texture2D ProcessHdrTexture(Texture2D hdrTexture)
{
- try
- {
- var hdc = User32.GetDC(hWnd);
- if (hdc != IntPtr.Zero)
- {
- int bitsPerPixel = Gdi32.GetDeviceCaps(hdc, Gdi32.DeviceCap.BITSPIXEL);
- User32.ReleaseDC(hWnd, hdc);
+ var device = hdrTexture.Device;
+ var context = device.ImmediateContext;
- // 如果位深大于等于32位,认为支持HDR
- return bitsPerPixel >= 32;
- }
+ var width = hdrTexture.Description.Width;
+ var height = hdrTexture.Description.Height;
- return false;
- }
- catch
- {
- return false;
- }
- }
+ _hdrOutputTexture ??= Direct3D11Helper.CreateOutputTexture(device, width, height);
+ _hdrComputeShader ??= new ComputeShader(device, ShaderBytecode.Compile(HdrToSdrShader.Content, "CS_HDRtoSDR", "cs_5_0"));
- private Texture2D CreateStagingTexture(Direct3D11CaptureFrame frame, Device device)
- {
- // 创建可以用于CPU读取的暂存纹理
- var textureDesc = new Texture2DDescription
- {
- CpuAccessFlags = CpuAccessFlags.Read,
- BindFlags = BindFlags.None,
- Format = _isHdrEnabled ? Format.R16G16B16A16_Float : Format.B8G8R8A8_UNorm,
- Width = _region == null ? frame.ContentSize.Width : _region.Value.Right - _region.Value.Left,
- Height = _region == null ? frame.ContentSize.Height : _region.Value.Bottom - _region.Value.Top,
- OptionFlags = ResourceOptionFlags.None,
- MipLevels = 1,
- ArraySize = 1,
- SampleDescription = { Count = 1, Quality = 0 },
- Usage = ResourceUsage.Staging
- };
+ using var inputSrv = new ShaderResourceView(device, hdrTexture);
+ using var outputUav = new UnorderedAccessView(device, _hdrOutputTexture);
- return new Texture2D(device, textureDesc);
+ context.ComputeShader.Set(_hdrComputeShader);
+ context.ComputeShader.SetShaderResource(0, inputSrv);
+ context.ComputeShader.SetUnorderedAccessView(0, outputUav);
+
+ var threadGroupCountX = (int)Math.Ceiling(width / 16.0);
+ var threadGroupCountY = (int)Math.Ceiling(height / 16.0);
+
+ context.Dispatch(threadGroupCountX, threadGroupCountY, 1);
+
+ return _hdrOutputTexture;
}
private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args)
{
- using var frame = sender.TryGetNextFrame();
- if (frame == null)
- {
- return;
- }
-
- // 限制最高处理帧率为62fps
- if (_frameTimer.ElapsedMilliseconds - _lastFrameTime < 16)
- {
- return;
- }
- _lastFrameTime = _frameTimer.ElapsedMilliseconds;
-
- var frameSize = _captureItem.Size;
-
- // 检查帧大小是否变化 // 不会被访问到的代码
- if (frameSize.Width != frame.ContentSize.Width ||
- frameSize.Height != frame.ContentSize.Height)
- {
- frameSize = frame.ContentSize;
- _captureFramePool.Recreate(
- _d3dDevice,
- _pixelFormat,
- 2,
- frameSize
- );
- _stagingTexture = null;
- }
-
- // 从捕获的帧创建一个可以被访问的纹理
- using var surfaceTexture = Direct3D11Helper.CreateSharpDXTexture2D(frame.Surface);
- var d3dDevice = surfaceTexture.Device;
-
- _stagingTexture ??= CreateStagingTexture(frame, d3dDevice);
- var stagingTexture = _stagingTexture;
-
- // 将捕获的纹理复制到暂存纹理
- if (_region != null)
- {
- d3dDevice.ImmediateContext.CopySubresourceRegion(surfaceTexture, 0, _region, stagingTexture, 0);
- }
- else
- {
- d3dDevice.ImmediateContext.CopyResource(surfaceTexture, stagingTexture);
- }
-
-
- // 映射纹理以便CPU读取
- var dataBox = d3dDevice.ImmediateContext.MapSubresource(
- stagingTexture,
- 0,
- MapMode.Read,
- MapFlags.None);
-
+ // 使用写锁更新最新帧
+ _frameAccessLock.EnterWriteLock();
try
{
- // 创建一个新的Mat
- var newFrame = Mat.FromPixelData(stagingTexture.Description.Height, stagingTexture.Description.Width,
- _isHdrEnabled ? MatType.MakeType(7, 4) : MatType.CV_8UC4, dataBox.DataPointer);
-
- // 如果是HDR,进行HDR到SDR的转换
- if (_isHdrEnabled)
+ if (_hWnd == 0)
{
- // rgb -> bgr
- newFrame = ConvertHdrToSdr(newFrame);
+ return;
+ }
+
+ using var frame = sender.TryGetNextFrame();
+ if (frame == null)
+ {
+ return;
+ }
+
+ // 限制最高处理帧率为62fps
+ if (_frameTimer.ElapsedMilliseconds - _lastFrameTime < 16)
+ {
+ return;
+ }
+ _lastFrameTime = _frameTimer.ElapsedMilliseconds;
+
+ var frameSize = _captureItem!.Size;
+
+ // 检查帧大小是否变化
+ if (frameSize.Width != frame.ContentSize.Width || frameSize.Height != frame.ContentSize.Height)
+ {
+ frameSize = frame.ContentSize;
+ _captureFramePool!.Recreate(
+ _d3dDevice,
+ _pixelFormat,
+ 2,
+ frameSize
+ );
+ _stagingTexture?.Dispose();
+ _stagingTexture = null;
+ _region = GetGameScreenRegion(_hWnd);
}
- // 使用写锁更新最新帧
- _frameAccessLock.EnterWriteLock();
try
{
- // 释放之前的帧
+ // 从捕获的帧创建一个可以被访问的纹理
+ using var surfaceTexture = Direct3D11Helper.CreateSharpDXTexture2D(frame.Surface);
+ var sourceTexture = _isHdrEnabled ? ProcessHdrTexture(surfaceTexture) : surfaceTexture;
+ var d3dDevice = surfaceTexture.Device;
+
+ _stagingTexture ??= Direct3D11Helper.CreateStagingTexture(d3dDevice, frame.ContentSize.Width, frame.ContentSize.Height, _region);
+ var mat = _stagingTexture.CreateMat(d3dDevice, sourceTexture, _region);
+
+ // 释放之前的帧,然后更新
_latestFrame?.Dispose();
- // 克隆新帧以保持对其的引用(因为dataBox.DataPointer将被释放)
- _latestFrame = newFrame.Clone();
+ _latestFrame = mat;
}
- finally
+ catch (SharpDXException e)
{
- newFrame.Dispose();
- _frameAccessLock.ExitWriteLock();
+ Debug.WriteLine($"SharpDXException: {e.Descriptor}");
+ _latestFrame = null;
}
}
finally
{
- // 取消映射纹理
- d3dDevice.ImmediateContext.UnmapSubresource(surfaceTexture, 0);
+ _frameAccessLock.ExitWriteLock();
}
}
- private static Mat ConvertHdrToSdr(Mat hdrMat)
- {
- // 创建一个目标 8UC4 Mat
- Mat sdkMat = new Mat(hdrMat.Size(), MatType.CV_8UC4);
-
- // 将 32FC4 缩放到 0-255 范围并转换为 8UC4
- // 注意:这种简单缩放可能不会保留 HDR 的所有细节
- hdrMat.ConvertTo(sdkMat, MatType.CV_8UC4, 255.0);
-
- // 将 HDR 的 RGB 通道转换为 BGR
- Cv2.CvtColor(sdkMat, sdkMat, ColorConversionCodes.RGBA2BGRA);
-
- return sdkMat;
- }
-
- public CaptureImageRes? Capture()
+ public Mat? Capture()
{
// 使用读锁获取最新帧
_frameAccessLock.EnterReadLock();
try
{
- // 如果没有可用帧则返回null
- if (_latestFrame == null)
- {
- return null;
- }
-
// 返回最新帧的副本(这里我们必须克隆,因为Mat是不线程安全的)
- return CaptureImageRes.BuildNullable(_latestFrame.Clone());
+ return _latestFrame?.Clone();
}
finally
{
@@ -300,20 +259,33 @@ public class GraphicsCapture : IGameCapture
public void Stop()
{
- _captureSession?.Dispose();
- _captureFramePool?.Dispose();
- _captureSession = null!;
- _captureFramePool = null!;
- _captureItem = null!;
- _stagingTexture?.Dispose();
- _d3dDevice?.Dispose();
-
- _hWnd = IntPtr.Zero;
- IsCapturing = false;
+ _frameAccessLock.EnterWriteLock();
+ try
+ {
+ _captureSession?.Dispose();
+ _captureSession = null;
+ _captureFramePool?.Dispose();
+ _captureFramePool = null;
+ _captureItem = null;
+ _stagingTexture?.Dispose();
+ _stagingTexture = null;
+ _hdrOutputTexture?.Dispose();
+ _hdrOutputTexture = null;
+ _hdrComputeShader?.Dispose();
+ _hdrComputeShader = null;
+ _d3dDevice?.Dispose();
+ _d3dDevice = null;
+ _hWnd = 0;
+ IsCapturing = false;
+ }
+ finally
+ {
+ _frameAccessLock.ExitWriteLock();
+ }
}
private void CaptureItemOnClosed(GraphicsCaptureItem sender, object args)
{
Stop();
}
-}
\ No newline at end of file
+}
diff --git a/Fischless.GameCapture/Graphics/Helpers/Direct3D11Helper.cs b/Fischless.GameCapture/Graphics/Helpers/Direct3D11Helper.cs
index ca1f0449..98be8218 100644
--- a/Fischless.GameCapture/Graphics/Helpers/Direct3D11Helper.cs
+++ b/Fischless.GameCapture/Graphics/Helpers/Direct3D11Helper.cs
@@ -1,15 +1,14 @@
using System.Runtime.InteropServices;
using Windows.Graphics.DirectX.Direct3D11;
+using SharpDX.Direct3D11;
+using SharpDX.DXGI;
using WinRT;
+using Device = SharpDX.Direct3D11.Device;
namespace Fischless.GameCapture.Graphics.Helpers;
public static class Direct3D11Helper
{
- internal static Guid IInspectable = new("AF86E2E0-B12D-4c6a-9C5A-D7AA65101E90");
- internal static Guid ID3D11Resource = new("dc8e63f3-d12b-4952-b47b-5e45026a862d");
- internal static Guid IDXGIAdapter3 = new("645967A4-1392-4310-A798-8053CE3E93FD");
- internal static Guid ID3D11Device = new("db6f6ddb-ac77-4e88-8253-819df9bbf140");
internal static Guid ID3D11Texture2D = new("6f15aaf2-d208-4e89-9ab4-489535d34f9c");
[ComImport]
@@ -31,33 +30,23 @@ public static class Direct3D11Helper
)]
static extern uint CreateDirect3D11DeviceFromDXGIDevice(nint dxgiDevice, out nint graphicsDevice);
- [DllImport(
- "d3d11.dll",
- EntryPoint = "CreateDirect3D11SurfaceFromDXGISurface",
- SetLastError = true,
- CharSet = CharSet.Unicode,
- ExactSpelling = true,
- CallingConvention = CallingConvention.StdCall
- )]
- static extern uint CreateDirect3D11SurfaceFromDXGISurface(nint dxgiSurface, out nint graphicsSurface);
-
public static IDirect3DDevice CreateDevice()
{
return CreateDevice(false);
}
- private static SharpDX.Direct3D11.Device? d3dDevice;
+ private static Device? d3dDevice;
public static IDirect3DDevice CreateDevice(bool useWARP)
{
- d3dDevice ??= new SharpDX.Direct3D11.Device(
+ d3dDevice ??= new Device(
useWARP ? SharpDX.Direct3D.DriverType.Software : SharpDX.Direct3D.DriverType.Hardware,
- SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport);
+ DeviceCreationFlags.BgraSupport);
var device = CreateDirect3DDeviceFromSharpDXDevice(d3dDevice);
return device;
}
- public static IDirect3DDevice CreateDirect3DDeviceFromSharpDXDevice(SharpDX.Direct3D11.Device d3dDevice)
+ public static IDirect3DDevice CreateDirect3DDeviceFromSharpDXDevice(Device d3dDevice)
{
IDirect3DDevice device = default!;
@@ -77,39 +66,45 @@ public static class Direct3D11Helper
return device;
}
- public static IDirect3DSurface CreateDirect3DSurfaceFromSharpDXTexture(SharpDX.Direct3D11.Texture2D texture)
- {
- IDirect3DSurface surface = default!;
-
- // Acquire the DXGI interface for the Direct3D surface.
- using (var dxgiSurface = texture.QueryInterface())
- {
- // Wrap the native device using a WinRT interop object.
- uint hr = CreateDirect3D11SurfaceFromDXGISurface(dxgiSurface.NativePointer, out nint pUnknown);
-
- if (hr == 0)
- {
- surface = Marshal.GetObjectForIUnknown(pUnknown) as IDirect3DSurface;
- Marshal.Release(pUnknown);
- }
- }
-
- return surface;
- }
-
- public static SharpDX.Direct3D11.Device CreateSharpDXDevice(IDirect3DDevice device)
- {
- var access = device.As();
- var d3dPointer = access.GetInterface(ID3D11Device);
- var d3dDevice = new SharpDX.Direct3D11.Device(d3dPointer);
- return d3dDevice;
- }
-
- public static SharpDX.Direct3D11.Texture2D CreateSharpDXTexture2D(IDirect3DSurface surface)
+ public static Texture2D CreateSharpDXTexture2D(IDirect3DSurface surface)
{
var access = surface.As();
var d3dPointer = access.GetInterface(ID3D11Texture2D);
- var d3dSurface = new SharpDX.Direct3D11.Texture2D(d3dPointer);
+ var d3dSurface = new Texture2D(d3dPointer);
return d3dSurface;
}
+
+ public static Texture2D CreateStagingTexture(Device device, int width, int height, ResourceRegion? region)
+ {
+ return new Texture2D(device, new Texture2DDescription
+ {
+ Width = region == null ? width : region.Value.Right - region.Value.Left,
+ Height = region == null ? height : region.Value.Bottom - region.Value.Top,
+ MipLevels = 1,
+ ArraySize = 1,
+ Format = Format.B8G8R8A8_UNorm,
+ Usage = ResourceUsage.Staging,
+ SampleDescription = new SampleDescription(1, 0),
+ BindFlags = BindFlags.None,
+ CpuAccessFlags = CpuAccessFlags.Read,
+ OptionFlags = ResourceOptionFlags.None
+ });
+ }
+
+ public static Texture2D CreateOutputTexture(Device device, int width, int height)
+ {
+ return new Texture2D(device, new Texture2DDescription
+ {
+ Width = width,
+ Height = height,
+ MipLevels = 1,
+ ArraySize = 1,
+ Format = Format.B8G8R8A8_UNorm,
+ Usage = ResourceUsage.Default,
+ SampleDescription = new SampleDescription(1, 0),
+ BindFlags = BindFlags.UnorderedAccess | BindFlags.ShaderResource,
+ CpuAccessFlags = CpuAccessFlags.None,
+ OptionFlags = ResourceOptionFlags.None
+ });
+ }
}
diff --git a/Fischless.GameCapture/Graphics/Helpers/HdrToSdrShader.cs b/Fischless.GameCapture/Graphics/Helpers/HdrToSdrShader.cs
new file mode 100644
index 00000000..410a9399
--- /dev/null
+++ b/Fischless.GameCapture/Graphics/Helpers/HdrToSdrShader.cs
@@ -0,0 +1,27 @@
+namespace Fischless.GameCapture.Graphics.Helpers;
+
+public static class HdrToSdrShader
+{
+ public static string Content =>
+"""
+// HLSL Compute Shader
+Texture2D hdrTexture : register(t0);
+RWTexture2D sdrTexture : register(u0);
+
+[numthreads(16, 16, 1)]
+void CS_HDRtoSDR(uint3 id : SV_DispatchThreadID)
+{
+ // Load color
+ half4 hdrColor = hdrTexture[id.xy];
+
+ // HDR -> SDR (exposure)
+ float4 exposedColor = float4(hdrColor.rgb * 0.25, hdrColor.a);
+
+ // Linear RGB -> sRGB
+ float4 srgbColor = float4(pow(exposedColor.rgb, 1 / 2.2), exposedColor.a);
+
+ // Store color
+ sdrTexture[id.xy] = (unorm float4)saturate(srgbColor);
+}
+""";
+}
diff --git a/Fischless.GameCapture/Graphics/Helpers/Texture2DExtensions.cs b/Fischless.GameCapture/Graphics/Helpers/Texture2DExtensions.cs
index 2957a74b..ab932c97 100644
--- a/Fischless.GameCapture/Graphics/Helpers/Texture2DExtensions.cs
+++ b/Fischless.GameCapture/Graphics/Helpers/Texture2DExtensions.cs
@@ -1,75 +1,12 @@
-using SharpDX;
-using SharpDX.Direct3D11;
-using SharpDX.DXGI;
+using SharpDX.Direct3D11;
using System.Diagnostics;
-using System.Drawing.Imaging;
-using Windows.Graphics.Capture;
using OpenCvSharp;
namespace Fischless.GameCapture.Graphics.Helpers;
public static class Texture2DExtensions
{
- public static Bitmap? ToBitmap(this Direct3D11CaptureFrame frame, ResourceRegion? region = null)
- {
- var texture2dBitmap = Direct3D11Helper.CreateSharpDXTexture2D(frame.Surface);
-
- var d3dDevice = texture2dBitmap.Device;
-
- // Create texture copy
- var staging = new Texture2D(d3dDevice, new Texture2DDescription
- {
- Width = region == null ? frame.ContentSize.Width : region.Value.Right - region.Value.Left,
- Height = region == null ? frame.ContentSize.Height : region.Value.Bottom - region.Value.Top,
- MipLevels = 1,
- ArraySize = 1,
- Format = texture2dBitmap.Description.Format,
- Usage = ResourceUsage.Staging,
- SampleDescription = new SampleDescription(1, 0),
- BindFlags = BindFlags.None,
- CpuAccessFlags = CpuAccessFlags.Read,
- OptionFlags = ResourceOptionFlags.None
- });
-
- return staging.CreateBitmap(d3dDevice, texture2dBitmap, region);
- }
-
- public static Bitmap? CreateBitmap(this Texture2D staging, SharpDX.Direct3D11.Device d3dDevice, Texture2D surfaceTexture, ResourceRegion? region = null)
- {
- try
- {
- // Copy data
- if (region != null)
- {
- d3dDevice.ImmediateContext.CopySubresourceRegion(surfaceTexture, 0, region, staging, 0);
- }
- else
- {
- d3dDevice.ImmediateContext.CopyResource(surfaceTexture, staging);
- }
-
- var dataBox = d3dDevice.ImmediateContext.MapSubresource(staging, 0, 0, MapMode.Read,
- SharpDX.Direct3D11.MapFlags.None,
- out DataStream stream);
-
- var bitmap = new Bitmap(staging.Description.Width, staging.Description.Height, dataBox.RowPitch,
- PixelFormat.Format32bppArgb, dataBox.DataPointer);
-
- return bitmap;
- }
- catch (Exception e)
- {
- Debug.WriteLine("Failed to copy texture to bitmap.");
- Debug.WriteLine(e.StackTrace);
- return null;
- }
- finally
- {
- staging.Dispose();
- }
- }
-
- public static Mat? CreateMat(this Texture2D staging, SharpDX.Direct3D11.Device d3dDevice, Texture2D surfaceTexture, ResourceRegion? region = null)
+ public static Mat? CreateMat(this Texture2D staging, Device d3dDevice, Texture2D surfaceTexture, ResourceRegion? region = null)
{
try
{
@@ -88,10 +25,18 @@ public static class Texture2DExtensions
staging,
0,
MapMode.Read,
- SharpDX.Direct3D11.MapFlags.None);
+ MapFlags.None);
- var mat = Mat.FromPixelData(staging.Description.Height, staging.Description.Width, MatType.CV_8UC4, dataBox.DataPointer);
- return mat;
+ try
+ {
+ using var mat = Mat.FromPixelData(staging.Description.Height, staging.Description.Width,
+ MatType.CV_8UC4, dataBox.DataPointer, dataBox.RowPitch);
+ return mat.CvtColor(ColorConversionCodes.BGRA2BGR);
+ }
+ finally
+ {
+ d3dDevice.ImmediateContext.UnmapSubresource(staging, 0);
+ }
}
catch (Exception e)
{
@@ -99,9 +44,5 @@ public static class Texture2DExtensions
Debug.WriteLine(e.StackTrace);
return null;
}
- finally
- {
- staging.Dispose();
- }
}
-}
\ No newline at end of file
+}
diff --git a/Fischless.GameCapture/IGameCapture.cs b/Fischless.GameCapture/IGameCapture.cs
index 07dbf145..c2783fd9 100644
--- a/Fischless.GameCapture/IGameCapture.cs
+++ b/Fischless.GameCapture/IGameCapture.cs
@@ -8,7 +8,7 @@ public interface IGameCapture : IDisposable
public void Start(nint hWnd, Dictionary? settings = null);
- public CaptureImageRes? Capture();
+ public Mat? Capture();
public void Stop();
}