mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-03-18 08:13:20 +08:00
* BitBlt 优化 * BitBlt恢复Top-down * 渲染时翻转图像 * CaptureSession引用计数 * 恢复成无拷贝Mat * 合法性检查 * 优化截图预览窗口 * 保存截图文件必要时需要克隆一份Mat * BitBlt内存池 * 返回拷贝就不用对Session做引用计数了 * 移除CaptureImageRes * 优化DirectX * 更好地处理padding * BitBlt去掉padding 1920*1080的游戏窗口是4字节对齐的,因此不会有性能影响。这里主要用于测试。 * 修复修改窗口大小 * 合并CreateStagingTexture * 修复设备丢失崩溃 * WGC截图支持HDR * fix typo * CodeQA * 去掉1px窗口边框 * DirectX截图去掉A通道 * HDR转换使用GPU加速 --------- Co-authored-by: 辉鸭蛋 <huiyadanli@gmail.com>
292 lines
8.9 KiB
C#
292 lines
8.9 KiB
C#
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;
|
||
using SharpDX.D3DCompiler;
|
||
|
||
namespace Fischless.GameCapture.Graphics;
|
||
|
||
public class GraphicsCapture(bool captureHdr = false) : IGameCapture
|
||
{
|
||
private nint _hWnd;
|
||
|
||
private Direct3D11CaptureFramePool? _captureFramePool;
|
||
private GraphicsCaptureItem? _captureItem;
|
||
|
||
private GraphicsCaptureSession? _captureSession;
|
||
|
||
private IDirect3DDevice? _d3dDevice;
|
||
|
||
public bool IsCapturing { get; private set; }
|
||
|
||
private ResourceRegion? _region;
|
||
|
||
// HDR相关
|
||
private bool _isHdrEnabled = captureHdr;
|
||
private DirectXPixelFormat _pixelFormat;
|
||
private Texture2D? _hdrOutputTexture;
|
||
private ComputeShader? _hdrComputeShader;
|
||
|
||
// 最新帧的存储
|
||
private Mat? _latestFrame;
|
||
private readonly ReaderWriterLockSlim _frameAccessLock = new();
|
||
|
||
// 用于获取帧数据的临时纹理和暂存资源
|
||
private Texture2D? _stagingTexture;
|
||
|
||
private long _lastFrameTime;
|
||
|
||
private readonly Stopwatch _frameTimer = new();
|
||
|
||
public void Dispose()
|
||
{
|
||
Stop();
|
||
GC.SuppressFinalize(this);
|
||
}
|
||
|
||
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();
|
||
|
||
// 创建帧池
|
||
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;
|
||
_captureFramePool.FrameArrived += OnFrameArrived;
|
||
|
||
_captureSession = _captureFramePool.CreateCaptureSession(_captureItem);
|
||
if (ApiInformation.IsPropertyPresent("Windows.Graphics.Capture.GraphicsCaptureSession",
|
||
nameof(GraphicsCaptureSession.IsCursorCaptureEnabled)))
|
||
{
|
||
_captureSession.IsCursorCaptureEnabled = false;
|
||
}
|
||
|
||
if (ApiInformation.IsWriteablePropertyPresent("Windows.Graphics.Capture.GraphicsCaptureSession",
|
||
nameof(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;
|
||
User32.ClientToScreen(_hWnd, ref point);
|
||
|
||
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;
|
||
}
|
||
|
||
private Texture2D ProcessHdrTexture(Texture2D hdrTexture)
|
||
{
|
||
var device = hdrTexture.Device;
|
||
var context = device.ImmediateContext;
|
||
|
||
var width = hdrTexture.Description.Width;
|
||
var height = hdrTexture.Description.Height;
|
||
|
||
_hdrOutputTexture ??= Direct3D11Helper.CreateOutputTexture(device, width, height);
|
||
_hdrComputeShader ??= new ComputeShader(device, ShaderBytecode.Compile(HdrToSdrShader.Content, "CS_HDRtoSDR", "cs_5_0"));
|
||
|
||
using var inputSrv = new ShaderResourceView(device, hdrTexture);
|
||
using var outputUav = new UnorderedAccessView(device, _hdrOutputTexture);
|
||
|
||
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)
|
||
{
|
||
// 使用写锁更新最新帧
|
||
_frameAccessLock.EnterWriteLock();
|
||
try
|
||
{
|
||
if (_hWnd == 0)
|
||
{
|
||
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);
|
||
}
|
||
|
||
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();
|
||
_latestFrame = mat;
|
||
}
|
||
catch (SharpDXException e)
|
||
{
|
||
Debug.WriteLine($"SharpDXException: {e.Descriptor}");
|
||
_latestFrame = null;
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
_frameAccessLock.ExitWriteLock();
|
||
}
|
||
}
|
||
|
||
public Mat? Capture()
|
||
{
|
||
// 使用读锁获取最新帧
|
||
_frameAccessLock.EnterReadLock();
|
||
try
|
||
{
|
||
// 返回最新帧的副本(这里我们必须克隆,因为Mat是不线程安全的)
|
||
return _latestFrame?.Clone();
|
||
}
|
||
finally
|
||
{
|
||
_frameAccessLock.ExitReadLock();
|
||
}
|
||
}
|
||
|
||
public void Stop()
|
||
{
|
||
_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();
|
||
}
|
||
}
|