Files
better-genshin-impact/Fischless.GameCapture/BitBlt/BitBltCapture.cs
Shatyuka 0bea2d095a 截图优化 (#1480)
* 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>
2025-05-11 01:17:18 +08:00

209 lines
5.7 KiB
C#
Raw 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 OpenCvSharp;
using Vanara.PInvoke;
using static System.Console;
namespace Fischless.GameCapture.BitBlt;
public class BitBltCapture : IGameCapture
{
public bool IsCapturing { get; private set; }
private readonly Stopwatch _sizeCheckTimer = new();
private readonly ReaderWriterLockSlim _lockSlim = new();
private volatile nint _hWnd; // 需要加锁
private BitBltSession? _session; // 需要加锁
private volatile bool _lastCaptureFailed;
public void Dispose()
{
Stop();
GC.SuppressFinalize(this);
}
public Mat? Capture() => Capture(false);
public void Start(nint hWnd, Dictionary<string, object>? settings = null)
{
if (settings == null || !settings.TryGetValue("autoFixWin11BitBlt", out var value)) return;
if (value is true)
{
BitBltRegistryHelper.SetDirectXUserGlobalSettings();
}
_lockSlim.EnterWriteLock();
try
{
_hWnd = hWnd;
if (_hWnd == IntPtr.Zero)
{
return;
}
_session?.Dispose();
_session = null;
IsCapturing = true;
}
finally
{
_lockSlim.ExitWriteLock();
}
CheckSession();
}
/// <summary>
/// 检查窗口大小,如果改变则更新截图尺寸。
/// </summary>
private void CheckSession()
{
if (_lockSlim.WaitingWriteCount > 0 || !_lockSlim.TryEnterWriteLock(TimeSpan.FromSeconds(0.5)))
{
// 写锁持有只会有两种情况:start和CheckSession。无论哪一种都会检查&更新session。
// 所以当有线程等待更新时,将直接返回
return;
}
try
{
// 窗口状态变化可能会导致会话失效
// 上次截图失败则重置会话,避免一直截图失败
if (_session is not null && (_session.Invalid || _lastCaptureFailed))
{
_session.Dispose();
_session = null;
}
if (!User32.GetClientRect(_hWnd, out var windowRect) || windowRect == default)
{
// Debug.Fail("Failed to get client rectangle");
// 窗口获取不到或者最小化
_session?.Dispose();
_session = null;
return;
}
var width = windowRect.right - windowRect.left;
var height = windowRect.bottom - windowRect.top;
if (_session != null)
{
if (_session.Width == width && _session.Height == height)
{
// 窗口大小没有改变
return;
}
// 窗口尺寸被改变,释放资源,然后重新创建会话
_session.Dispose();
}
_session = new BitBltSession(_hWnd, width, height);
}
catch (Exception e)
{
Error.WriteLine("[BitBlt]Failed to create session:{0}", e);
}
finally
{
_lockSlim.ExitWriteLock();
}
}
/// <summary>
/// 递归只尝试一次,会设置标志,正常调用置假即可
/// </summary>
/// <param name="recursive">递归标志</param>
/// <returns>截图</returns>
private Mat? Capture(bool recursive)
{
if (_hWnd == IntPtr.Zero)
{
return null;
}
if (!_sizeCheckTimer.IsRunning)
{
_sizeCheckTimer.Start();
}
// 不会经常调整窗口尺寸的,所以隔一段时间检查一次就行
// 上次如果截图失败的话忽略计时器,避免重复截图失败
// 递归标志也说明上次截图失败
if (_lastCaptureFailed || recursive || _sizeCheckTimer.ElapsedMilliseconds > 1000)
{
_sizeCheckTimer.Reset();
CheckSession();
}
try
{
_lockSlim.EnterReadLock();
var result = Capture0();
if (result is not null)
{
// 成功截图
_lastCaptureFailed = false;
return result;
}
else
{
if (_lastCaptureFailed) return result; // 这不是首次失败,不再进行尝试
_lastCaptureFailed = true; // 设置失败标志
if (recursive) return result; // 已设置递归标志,说明也不是首次失败
}
}
finally
{
if (_lockSlim.IsReadLockHeld)
{
_lockSlim.ExitReadLock();
}
}
// 首次出现截图异常会跳到这里
// 首次出现错误重试截图,尽可能不出现截图失败(递归)
return Capture(true);
}
/// <summary>
/// 截图功能的实现。需要加锁后调用,一般只由 Capture 方法调用。
/// </summary>
/// <returns></returns>
private Mat? Capture0()
{
try
{
return _session?.GetImage();
}
catch (Exception e)
{
// 理论这里不应出现异常除非窗口不存在了或者有什么bug
Error.WriteLine("[BitBlt]Failed to capture image {0}", e);
return null;
}
}
public void Stop()
{
_lockSlim.EnterWriteLock();
try
{
_hWnd = IntPtr.Zero;
_sizeCheckTimer.Stop();
if (_session != null)
{
_session.Dispose();
_session = null;
}
}
finally
{
_lockSlim.ExitWriteLock();
}
IsCapturing = false;
}
}