Files
2025-04-15 01:45:48 +08:00

319 lines
9.5 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Diagnostics;
using Fischless.GameCapture.Graphics.Helpers;
using SharpDX.Direct3D11;
using Vanara.PInvoke;
using Windows.Foundation.Metadata;
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;
namespace Fischless.GameCapture.Graphics;
public class GraphicsCapture : IGameCapture
{
private nint _hWnd;
private Direct3D11CaptureFramePool _captureFramePool = null!;
private GraphicsCaptureItem _captureItem = null!;
private GraphicsCaptureSession _captureSession = null!;
private IDirect3DDevice _d3dDevice = null!;
public bool IsCapturing { get; private set; }
private ResourceRegion? _region;
// HDR相关
private bool _isHdrEnabled;
private DirectXPixelFormat _pixelFormat = DirectXPixelFormat.B8G8R8A8UIntNormalized;
// 最新帧的存储
private Mat? _latestFrame;
private readonly ReaderWriterLockSlim _frameAccessLock = new();
// 用于获取帧数据的临时纹理和暂存资源
private Texture2D? _stagingTexture;
private long _lastFrameTime = 0;
private readonly Stopwatch _frameTimer = new Stopwatch();
public void Dispose() => Stop();
public void Start(nint hWnd, Dictionary<string, object>? settings = null)
{
_hWnd = hWnd;
_region = GetGameScreenRegion(hWnd);
IsCapturing = true;
_captureItem = CaptureHelper.CreateItemForWindow(_hWnd);
if (_captureItem == null)
{
throw new InvalidOperationException("Failed to create capture item.");
}
// 创建D3D设备
_d3dDevice = Direct3D11Helper.CreateDevice();
// 检测HDR状态
_isHdrEnabled = false;
_pixelFormat = _isHdrEnabled ? DirectXPixelFormat.R16G16B16A16Float : DirectXPixelFormat.B8G8R8A8UIntNormalized;
// 创建帧池
_captureFramePool = Direct3D11CaptureFramePool.Create(
_d3dDevice,
_pixelFormat,
2,
_captureItem.Size);
_captureItem.Closed += CaptureItemOnClosed;
_captureFramePool.FrameArrived += OnFrameArrived;
_captureSession = _captureFramePool.CreateCaptureSession(_captureItem);
if (ApiInformation.IsPropertyPresent("Windows.Graphics.Capture.GraphicsCaptureSession",
"IsCursorCaptureEnabled"))
{
_captureSession.IsCursorCaptureEnabled = false;
}
if (ApiInformation.IsWriteablePropertyPresent("Windows.Graphics.Capture.GraphicsCaptureSession",
"IsBorderRequired"))
{
_captureSession.IsBorderRequired = false;
}
_frameTimer.Start();
_captureSession.StartCapture();
IsCapturing = true;
}
/// <summary>
/// 从 DwmGetWindowAttribute 的矩形 截取出 GetClientRect的矩形游戏区域
/// </summary>
/// <param name="hWnd"></param>
/// <returns></returns>
private ResourceRegion? GetGameScreenRegion(nint hWnd)
{
var exStyle = User32.GetWindowLong(hWnd, User32.WindowLongFlags.GWL_EXSTYLE);
if ((exStyle & (int)User32.WindowStylesEx.WS_EX_TOPMOST) != 0)
{
return null;
}
ResourceRegion region = new();
DwmApi.DwmGetWindowAttribute<RECT>(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);
region.Left = 0;
region.Top = windowRect.Height - clientRect.Height;
region.Right = clientRect.Width;
region.Bottom = windowRect.Height;
region.Front = 0;
region.Back = 1;
return region;
}
public static bool IsHdrEnabled(nint hWnd)
{
try
{
var hdc = User32.GetDC(hWnd);
if (hdc != IntPtr.Zero)
{
int bitsPerPixel = Gdi32.GetDeviceCaps(hdc, Gdi32.DeviceCap.BITSPIXEL);
User32.ReleaseDC(hWnd, hdc);
// 如果位深大于等于32位认为支持HDR
return bitsPerPixel >= 32;
}
return false;
}
catch
{
return false;
}
}
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
};
return new Texture2D(device, textureDesc);
}
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);
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)
{
// rgb -> bgr
newFrame = ConvertHdrToSdr(newFrame);
}
// 使用写锁更新最新帧
_frameAccessLock.EnterWriteLock();
try
{
// 释放之前的帧
_latestFrame?.Dispose();
// 克隆新帧以保持对其的引用因为dataBox.DataPointer将被释放
_latestFrame = newFrame.Clone();
}
finally
{
newFrame.Dispose();
_frameAccessLock.ExitWriteLock();
}
}
finally
{
// 取消映射纹理
d3dDevice.ImmediateContext.UnmapSubresource(surfaceTexture, 0);
}
}
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()
{
// 使用读锁获取最新帧
_frameAccessLock.EnterReadLock();
try
{
// 如果没有可用帧则返回null
if (_latestFrame == null)
{
return null;
}
// 返回最新帧的副本这里我们必须克隆因为Mat是不线程安全的
return CaptureImageRes.BuildNullable(_latestFrame.Clone());
}
finally
{
_frameAccessLock.ExitReadLock();
}
}
public void Stop()
{
_captureSession?.Dispose();
_captureFramePool?.Dispose();
_captureSession = null!;
_captureFramePool = null!;
_captureItem = null!;
_stagingTexture?.Dispose();
_d3dDevice?.Dispose();
_hWnd = IntPtr.Zero;
IsCapturing = false;
}
private void CaptureItemOnClosed(GraphicsCaptureItem sender, object args)
{
Stop();
}
}