feat: WGC 支持脏矩形 (p1)

This commit is contained in:
Xu 2025-12-24 20:32:27 +08:00
commit b23964c2a8
9 changed files with 329 additions and 33 deletions

View file

@ -328,6 +328,10 @@ void CursorManager::_RestoreCursorSpeed() noexcept {
//
// 这个函数使用 ClipCursor 将光标限制在目标位置一段时间,等待系统将输入队列处理完毕。
void CursorManager::_ReliableSetCursorPos(POINT pos) const noexcept {
if (ScalingWindow::Get().Options().IsDebugMode()) {
return;
}
RECT originClipRect;
GetClipCursor(&originClipRect);

View file

@ -172,10 +172,9 @@ bool DuplicateFrameChecker::Initialize(
HRESULT DuplicateFrameChecker::CheckFrame(
ID3D12Resource* frameResource,
const SmallVectorImpl<Rect>& dirtyRects,
bool& isDuplicate
SmallVectorImpl<Rect>& dirtyRects
) noexcept {
assert(!dirtyRects.empty() && !isDuplicate);
assert(!dirtyRects.empty());
#ifdef _DEBUG
{
@ -195,7 +194,6 @@ HRESULT DuplicateFrameChecker::CheckFrame(
}
if (_isFirstFrame) {
isDuplicate = false;
return S_OK;
}
@ -243,8 +241,7 @@ HRESULT DuplicateFrameChecker::CheckFrame(
{.uintVal = dirtyRect.left},
{.uintVal = dirtyRect.top}
};
_commandList->SetComputeRoot32BitConstants(
0, (UINT)std::size(constants), constants, 3 * sizeof(DirectXHelper::Constant32));
_commandList->SetComputeRoot32BitConstants(0, (UINT)std::size(constants), constants, 3);
}
constexpr uint32_t BLOCK_SIZE = 16;
@ -288,6 +285,7 @@ HRESULT DuplicateFrameChecker::CheckFrame(
}
// 读取结果
SmallVector<uint32_t, 4> removeList;
{
CD3DX12_RANGE range(0, dirtyRectCount * sizeof(uint32_t));
@ -298,10 +296,10 @@ HRESULT DuplicateFrameChecker::CheckFrame(
return hr;
}
isDuplicate = true;
for (uint32_t i = 0; i < dirtyRectCount; ++i) {
if (((uint32_t*)pData)[i] == _curTargetValue) {
isDuplicate = false;
if (((uint32_t*)pData)[i] != _curTargetValue) {
// 此矩形内画面无变化
removeList.push_back(i);
}
}
@ -309,6 +307,14 @@ HRESULT DuplicateFrameChecker::CheckFrame(
_resultReadbackBuffer->Unmap(0, &range);
}
if (!removeList.empty()) {
// 从后向前删除
std::sort(removeList.begin(), removeList.end(), std::greater<uint32_t>());
for (uint32_t idx : removeList) {
dirtyRects.erase(dirtyRects.begin() + idx);
}
}
return S_OK;
}

View file

@ -18,11 +18,7 @@ public:
Size frameSize
) noexcept;
HRESULT CheckFrame(
ID3D12Resource* frameResource,
const SmallVectorImpl<Rect>& dirtyRects,
bool& isDuplicate
) noexcept;
HRESULT CheckFrame(ID3D12Resource* frameResource, SmallVectorImpl<Rect>& dirtyRects) noexcept;
void OnFrameAdopted() noexcept;

View file

@ -7,6 +7,7 @@
#include "Logger.h"
#include "ScalingWindow.h"
#include "Win32Helper.h"
#include "RectHelper.h"
#include <dwmapi.h>
#include <Windows.Graphics.Capture.Interop.h>
#include <windows.graphics.directx.direct3d11.interop.h>
@ -20,6 +21,211 @@ using namespace Windows::Graphics::DirectX::Direct3D11;
namespace Magpie {
static bool IsCornerInRect(Point p, const Rect& r) noexcept {
return p.x >= r.left && p.x <= r.right && p.y >= r.top && p.y <= r.bottom;
}
static bool OptimizeDirtyRectPair(Rect& rect1, Rect& rect2, bool reversed = false) noexcept {
if (RectHelper::IsEmpty(rect1) || RectHelper::IsEmpty(rect2)) {
return false;
}
// 计算 rect2 有几个角在 rect1 内
bool lt = IsCornerInRect(Point{ rect2.left, rect2.top }, rect1);
bool rt = IsCornerInRect(Point{ rect2.right, rect2.top }, rect1);
bool rb = IsCornerInRect(Point{ rect2.right, rect2.bottom }, rect1);
bool lb = IsCornerInRect(Point{ rect2.left, rect2.bottom }, rect1);
uint32_t count = (uint32_t)lt + (uint32_t)rt + (uint32_t)rb + (uint32_t)lb;
if (count == 0) {
// 尝试反向
if (!reversed) {
return OptimizeDirtyRectPair(rect2, rect1, true);
}
} else if (count == 2) {
// rect2 有两个角在 rect1 内时可以合并或裁剪
if (lt) {
if (rt) {
if (rect2.left == rect1.left && rect2.right == rect1.right) {
// rect2 合并进 rect1
rect1.bottom = rect2.bottom;
rect2.right = rect2.left;
return true;
} else if (rect2.top != rect1.bottom) {
// 裁剪 rect2
rect2.top = rect1.bottom;
assert(rect2.bottom >= rect2.top);
return true;
}
} else {
assert(lb);
if (rect2.top == rect1.top && rect2.bottom == rect1.bottom) {
rect1.right = rect2.right;
rect2.right = rect2.left;
return true;
} else if (rect2.left != rect1.right) {
rect2.left = rect1.right;
assert(rect2.right >= rect2.left);
return true;
}
}
} else {
assert(rb);
if (rt) {
if (rect2.top == rect1.top && rect2.bottom == rect1.bottom) {
rect1.left = rect2.left;
rect2.right = rect2.left;
return true;
} else if (rect2.right != rect1.left) {
rect2.right = rect1.left;
assert(rect2.right >= rect2.left);
return true;
}
} else {
if (rect2.left == rect1.left && rect2.right == rect1.right) {
rect1.top = rect2.top;
rect2.right = rect2.left;
return true;
} else if (rect2.bottom != rect1.top) {
rect2.bottom = rect1.top;
assert(rect2.bottom >= rect2.top);
return true;
}
}
}
} else if (count == 4) {
// rect2 在 rect1 内
rect2.right = rect2.left;
return true;
}
return false;
}
static void OptimizeDirtyRects(SmallVectorImpl<Rect>& dirtyRects) noexcept {
// 持续循环直到不再能优化
while (true) {
const uint32_t count = (uint32_t)dirtyRects.size();
assert(count > 0);
bool optimized = false;
for (uint32_t i = 0; i < count; ++i) {
for (uint32_t j = i + 1; j < count; ++j) {
if (OptimizeDirtyRectPair(dirtyRects[i], dirtyRects[j])) {
optimized = true;
}
}
}
if (!optimized) {
return;
}
// 从后向前删除空矩形
for (int i = int(count - 1); i >= 0; --i) {
const Rect& rect = dirtyRects[i];
if (RectHelper::IsEmpty(rect)) {
dirtyRects.erase(dirtyRects.begin() + i);
}
}
}
}
static void OptimizeDirtyRects2(SmallVectorImpl<Rect>& dirtyRects) noexcept {
OptimizeDirtyRects(dirtyRects);
while (true) {
const uint32_t count = (uint32_t)dirtyRects.size();
uint32_t originTotalPixels = 0;
for (uint32_t i = 0; i < count; ++i) {
originTotalPixels += RectHelper::CalcArea(dirtyRects[i]);
}
uint32_t minTotalPixels = std::numeric_limits<uint32_t>::max();
uint32_t targetRectCount = 0;
uint32_t targetI = 0;
uint32_t targetJ = 0;
// 遍历所有两两合并的方式找出总像素数最少的
for (uint32_t i = 0; i < count; ++i) {
for (uint32_t j = i + 1; j < count; ++j) {
Rect unionedRect = RectHelper::Union(dirtyRects[i], dirtyRects[j]);
uint32_t totalPixels = 0;
uint32_t rectCount = 0;
// 为了降低复杂度这里只优化一轮,而不是调用 OptimizeDirtyRects
for (uint32_t k = 0; k < count; ++k) {
if (k == i || k == j) {
continue;
}
Rect curRect = dirtyRects[k];
OptimizeDirtyRectPair(curRect, unionedRect);
if (!RectHelper::IsEmpty(curRect)) {
totalPixels += RectHelper::CalcArea(curRect);
++rectCount;
}
}
if (!RectHelper::IsEmpty(unionedRect)) {
totalPixels += RectHelper::CalcArea(unionedRect);
++rectCount;
}
if (totalPixels < minTotalPixels || (totalPixels == minTotalPixels && rectCount < targetRectCount)) {
minTotalPixels = totalPixels;
targetRectCount = rectCount;
targetI = i;
targetJ = j;
}
}
}
if (minTotalPixels > originTotalPixels && count <= MAX_CAPTURE_DIRTY_RECTS) {
break;
}
Rect unionedRect = RectHelper::Union(dirtyRects[targetI], dirtyRects[targetJ]);
dirtyRects.erase(dirtyRects.begin() + targetJ);
dirtyRects.erase(dirtyRects.begin() + targetI);
dirtyRects.push_back(unionedRect);
OptimizeDirtyRects(dirtyRects);
if (minTotalPixels > originTotalPixels && dirtyRects.size() <= MAX_CAPTURE_DIRTY_RECTS) {
break;
}
}
}
#ifdef _DEBUG
static Ignore _ = [] {
auto rectComp = [](const Rect& l, const Rect& r) {
return std::tuple(l.left, l.top, l.right, l.bottom) <
std::tuple(r.left, r.top, r.right, r.bottom);
};
SmallVector<Rect, 0> dirtyRects;
dirtyRects.reserve(16);
dirtyRects.emplace_back(0, 0, 2, 2);
dirtyRects.emplace_back(1, 1, 3, 4);
dirtyRects.emplace_back(2, 1, 4, 3);
dirtyRects.emplace_back(0, 1, 3, 2);
dirtyRects.emplace_back(3, 3, 4, 4);
OptimizeDirtyRects(dirtyRects);
std::sort(dirtyRects.begin(), dirtyRects.end(), rectComp);
assert(dirtyRects.size() == 2);
assert((dirtyRects[0] == Rect{ 0, 0, 2, 2 }));
assert((dirtyRects[1] == Rect{ 1, 1, 4, 4 }));
return Ignore();
}();
#endif
GraphicsCaptureFrameSource::~GraphicsCaptureFrameSource() noexcept {
if (_captureSession) {
_StopCapture();
@ -150,6 +356,10 @@ bool GraphicsCaptureFrameSource::Initialize(
return false;
}
// 从 Win11 24H2 开始支持
_isDirtyRegionSupported = winrt::ApiInformation::IsPropertyPresent(
winrt::name_of<winrt::GraphicsCaptureSession>(), L"DirtyRegionMode");
{
RECT frameBounds;
if (!CalcWindowCapturedFrameBounds(ScalingWindow::Get().SrcHandle(), frameBounds)) {
@ -311,6 +521,7 @@ HRESULT GraphicsCaptureFrameSource::CheckForNewFrame(bool& isNewFrameAvailable)
if (_latestFrame) {
_newFrame = std::move(_latestFrame);
_newFrameDirtyRects = std::move(_latestFrameDirtyRects);
_latestFrame = nullptr;
} else {
isNewFrameAvailable = (bool)_newFrame;
@ -331,22 +542,23 @@ HRESULT GraphicsCaptureFrameSource::CheckForNewFrame(bool& isNewFrameAvailable)
return S_OK;
}
bool isDuplicate = false;
SmallVector<Rect, 1> dirtyRects = {
Rect{_frameBox.left, _frameBox.top, _frameBox.right, _frameBox.bottom}
};
hr = _duplicateFrameChecker->CheckFrame(_newFrameResource.get(), dirtyRects, isDuplicate);
// 不支持脏矩形时检查整个捕获区域
if (!_isDirtyRegionSupported) {
_newFrameDirtyRects.emplace_back(_frameBox.left, _frameBox.top, _frameBox.right, _frameBox.bottom);
}
hr = _duplicateFrameChecker->CheckFrame(_newFrameResource.get(), _newFrameDirtyRects);
if (FAILED(hr)) {
Logger::Get().ComError("DuplicateFrameChecker::CheckFrame 失败", hr);
return hr;
}
if (isDuplicate) {
isNewFrameAvailable = !_newFrameDirtyRects.empty();
if (!isNewFrameAvailable) {
_newFrame = nullptr;
_newFrameResource = nullptr;
}
isNewFrameAvailable = !isDuplicate;
return S_OK;
}
@ -894,23 +1106,48 @@ void GraphicsCaptureFrameSource::_Direct3D11CaptureFramePool_FrameArrived(
const winrt::Direct3D11CaptureFramePool& pool,
const winrt::IInspectable&
) {
winrt::Direct3D11CaptureFrame frame = pool.TryGetNextFrame();
if (!frame) {
return;
}
winrt::Direct3D11CaptureFrame frame{ nullptr };
SmallVector<Rect> dirtyRects;
// 取最新帧
while (true) {
if (winrt::Direct3D11CaptureFrame nextFrame = pool.TryGetNextFrame()) {
frame = std::move(nextFrame);
} else {
winrt::Direct3D11CaptureFrame nextFrame = pool.TryGetNextFrame();
if (!nextFrame) {
break;
}
frame = std::move(nextFrame);
if (_isDirtyRegionSupported) {
for (const winrt::RectInt32& dirtyRect : frame.DirtyRegions()) {
RECT clipped = {
std::max(dirtyRect.X, (int)_frameBox.left),
std::max(dirtyRect.Y, (int)_frameBox.top),
std::min(dirtyRect.X + dirtyRect.Width, (int)_frameBox.right),
std::min(dirtyRect.Y + dirtyRect.Height, (int)_frameBox.bottom),
};
if (clipped.right <= clipped.left || clipped.bottom <= clipped.top) {
continue;
}
dirtyRects.emplace_back((uint32_t)clipped.left, (uint32_t)clipped.top,
(uint32_t)clipped.right, (uint32_t)clipped.bottom);
}
}
}
if (!frame || (_isDirtyRegionSupported && dirtyRects.empty())) {
return;
}
if (dirtyRects.size() > 1) {
OptimizeDirtyRects2(dirtyRects);
}
{
auto lk = _latestFrameLock.lock_exclusive();
_latestFrame = std::move(frame);
_latestFrameDirtyRects = std::move(dirtyRects);
#ifdef MP_DEBUG_INFO
{

View file

@ -1,4 +1,5 @@
#pragma once
#include "SmallVector.h"
#include <ShlObj.h>
#include <winrt/Windows.Graphics.Capture.h>
@ -86,6 +87,7 @@ private:
std::unique_ptr<DuplicateFrameChecker> _duplicateFrameChecker;
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame _newFrame{ nullptr };
SmallVector<Rect> _newFrameDirtyRects;
winrt::com_ptr<ID3D12Resource> _newFrameResource;
struct _FrameCrossAdapterResourceSlot {
@ -104,6 +106,7 @@ private:
wil::srwlock _latestFrameLock;
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame _latestFrame{ nullptr };
SmallVector<Rect> _latestFrameDirtyRects;
std::atomic<DWORD> _producerThreadId;
@ -123,6 +126,7 @@ private:
bool _isScRGB = false;
bool _isSrcStyleChanged = false;
bool _isRoundCornerDisabled = false;
bool _isDirtyRegionSupported = false;
};
}

View file

@ -84,6 +84,7 @@
<ClInclude Include="include\Event.h" />
<ClInclude Include="OverlayDrawer.h" />
<ClInclude Include="PresenterBase.h" />
<ClInclude Include="RectHelper.h" />
<ClInclude Include="Renderer.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="Renderer2.h" />

View file

@ -143,9 +143,6 @@
<ClInclude Include="GraphicsContext.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="ColorInfo.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="FrameProducer.h">
<Filter>Render</Filter>
</ClInclude>
@ -161,6 +158,10 @@
<ClInclude Include="CatumullRomEffectDrawer.h">
<Filter>EffectDrawer</Filter>
</ClInclude>
<ClInclude Include="DuplicateFrameChecker.h">
<Filter>Capture</Filter>
</ClInclude>
<ClInclude Include="RectHelper.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="ScalingRuntime.cpp" />
@ -270,6 +271,9 @@
<ClCompile Include="CatumullRomEffectDrawer.cpp">
<Filter>EffectDrawer</Filter>
</ClCompile>
<ClCompile Include="DuplicateFrameChecker.cpp">
<Filter>Capture</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<FxCompile Include="shaders\SimpleVS.hlsl">

View file

@ -0,0 +1,33 @@
#pragma once
namespace Magpie {
struct RectHelper {
static bool IsOverlap(const Rect& r1, const Rect& r2) noexcept {
return r1.right > r2.left && r1.bottom > r2.top && r1.left < r2.right && r1.top < r2.bottom;
}
static bool Contains(const Rect& r1, const Rect& r2) noexcept {
return r1.left <= r2.left && r1.top <= r2.top && r1.right >= r2.right && r1.bottom >= r2.bottom;
}
static bool Contains(const Rect& rect, Point p) noexcept {
return p.x >= rect.left && p.x < rect.right && p.y >= rect.top && p.y < rect.bottom;
}
static bool IsEmpty(const Rect& rect) noexcept {
return rect.left == rect.right || rect.top == rect.bottom;
}
static Rect Union(const Rect& r1, const Rect& r2) noexcept {
return Rect{ std::min(r1.left, r2.left), std::min(r1.top, r2.top),
std::max(r1.right, r2.right), std::max(r1.bottom, r2.bottom) };
}
static uint32_t CalcArea(const Rect& rect) noexcept {
assert(rect.right >= rect.left && rect.bottom >= rect.top);
return (rect.right - rect.left) * (rect.bottom - rect.top);
}
};
}

View file

@ -77,6 +77,15 @@ enum class ComponentState {
struct Size {
uint32_t width;
uint32_t height;
bool operator==(const Size& other) const = default;
};
struct Point {
uint32_t x;
uint32_t y;
bool operator==(const Point& other) const = default;
};
struct Rect {
@ -84,16 +93,18 @@ struct Rect {
uint32_t top;
uint32_t right;
uint32_t bottom;
bool operator==(const Rect& other) const = default;
};
struct ColorInfo {
bool operator==(const ColorInfo& other) const = default;
winrt::AdvancedColorKind kind = winrt::AdvancedColorKind::StandardDynamicRange;
// HDR 模式下最大亮度缩放
float maxLuminance = 1.0f;
// HDR 模式下 SDR 内容亮度缩放
float sdrWhiteLevel = 1.0f;
bool operator==(const ColorInfo& other) const = default;
};
}