Files
better-genshin-impact/Fischless.GameCapture/BitBlt/BitBltCapture.cs
Takaranoao 8951cc1adc bitblt截图修改:复用缓冲区以减少每次截图时的内存分配 (#1387)
* Refactor BitBltCapture and add BitBltSession for improved thread safety and resource management

* 删掉无用的锁

* Fix session invalidation handling in BitBltCapture and add IsInvalid method to BitBltSession

* Refactor resource release logic in BitBltSession to ensure proper order of operations

* Refactor IsInvalid method and clean up resource release logic in BitBltSession

* 析构函数加满

* 截图失败的时候重置会话避免卡死,改善锁的范围

* Use static Console for error logging in BitBltCapture

* Improve error logging format in BitBltCapture for session creation failure
2025-04-03 01:46:20 +08:00

175 lines
4.5 KiB
C#

using System.Diagnostics;
using OpenCvSharp;
using Vanara.PInvoke;
using static System.Console;
namespace Fischless.GameCapture.BitBlt;
public class BitBltCapture : IGameCapture
{
public CaptureModes Mode => CaptureModes.BitBlt;
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 _needResetSession;
public void Dispose() => Stop();
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>
/// <returns></returns>
private bool CheckSession()
{
if (!_lockSlim.TryEnterWriteLock(TimeSpan.FromSeconds(0.5)))
{
return false;
}
try
{
if (_session is not null && (_session.IsInvalid() || _needResetSession)) // 窗口状态变化可能会导致会话失效
{
_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 false;
}
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)
{
return true;
}
// 窗口尺寸被改变,释放资源
_session.Dispose();
_session = new BitBltSession(_hWnd, width, height);
return true;
}
catch (Exception e)
{
Error.WriteLine("Failed to create bitblt session:{0}", e);
return false;
}
finally
{
_lockSlim.ExitWriteLock();
}
}
public Mat? Capture()
{
if (_hWnd == IntPtr.Zero)
{
return null;
}
if (!_sizeCheckTimer.IsRunning)
{
_sizeCheckTimer.Start();
}
if (_sizeCheckTimer.ElapsedMilliseconds > 1000)
{
_sizeCheckTimer.Reset();
// 不会经常调整窗口尺寸的,所以隔一段时间检查一次就行
if (!CheckSession())
{
return null;
}
}
try
{
_lockSlim.EnterReadLock();
using var mat = _session?.BitBlt();
if (mat is null || mat.Empty())
{
if (_session is not null && !_session.IsInvalid())
{
// 有时候会出现截图失败的情况,这时候重置会话
_needResetSession = true;
}
return null;
}
var bgrMat = new Mat();
Cv2.CvtColor(mat, bgrMat, ColorConversionCodes.BGRA2BGR);
return bgrMat;
}
catch (Exception)
{
return null;
}
finally
{
_lockSlim.ExitReadLock();
}
}
public void Stop()
{
_lockSlim.EnterWriteLock();
try
{
_hWnd = IntPtr.Zero;
_sizeCheckTimer.Stop();
if (_session != null)
{
_session.Dispose();
_session = null;
}
}
finally
{
_lockSlim.ExitWriteLock();
}
IsCapturing = false;
}
}