diff --git a/docs/experimental/HDR-DLSS5-Bridge.md b/docs/experimental/HDR-DLSS5-Bridge.md
new file mode 100644
index 000000000..dda6eaf41
--- /dev/null
+++ b/docs/experimental/HDR-DLSS5-Bridge.md
@@ -0,0 +1,89 @@
+# HDR + DLSS5 初步适配
+
+这是一套给 Windows HDR 桌面使用的实验配置。它保留系统 HDR 开启,让 DLSSNR,也就是当前测试使用的 DLSS5 神经滤镜,继续处理捕获画面。配置重点在于把 HDR 捕获帧先转换成 DLSSNR 能稳定处理的 SDR R8 颜色缓冲,处理完成后再转换回 FP16 scRGB,交给 HDR 呈现器。
+
+## 使用前必须确认
+
+1. Windows 设置中的显示器 HDR 已经开启。
+2. Magpie 配置文件的“捕获方式”必须选择 `Graphics Capture (HDR)`。普通 `Graphics Capture` 走 B8G8R8A8 SDR 捕获路径,这套 HDR 配置的 FP16 输入不会建立。
+3. 缩放模式选择 `HDR DLSS5 Bridge`。这个模式来自 `presets/ScalingModes-HDR-DLSS5.json`,导入方式和普通缩放模式相同。
+4. `nvngx_dlssnr.dll` 必须放在 Magpie 程序目录,当前测试使用 RTX 4070 和实验包中的 DLSSNR 运行库。
+5. 测试时关闭其他帧生成或后处理链,避免 DLSSFG、XeSSFG、RTX Video 等效果混入结果。
+
+## 配置步骤
+
+导入 `presets/ScalingModes-HDR-DLSS5.json` 后,在目标配置文件中设置:
+
+```text
+捕获方式: Graphics Capture (HDR)
+缩放模式: HDR DLSS5 Bridge
+```
+
+配置文件里对应的效果顺序必须保持如下:
+
+```text
+HDRToSDR
+FrameRate_Filter (targetFrameRate = 60)
+DLSSNR\\DLSSNR_AI_Filter
+SDRToHDR
+```
+
+只导入缩放模式、捕获方式仍选普通 `Graphics Capture` 时,HDR 输入不会进入这条链。只选择 HDR 捕获、缩放模式继续使用普通 `DLSSNR` 时,DLSSNR 会直接接收 FP16 scRGB,当前运行库在本机测试出现过红色通道异常。
+
+## 四段效果分别做什么
+
+| 顺序 | 效果 | 输入 -> 输出 | 作用 |
+| --- | --- | --- | --- |
+| 1 | `HDRToSDR` | FP16 scRGB -> R8 SDR | 按 Windows HDR 桌面的 SDR 白点缩放,并把线性颜色转换成 sRGB |
+| 2 | `FrameRate_Filter` | R8 SDR -> R8 SDR | 将处理帧率限制为 60 FPS,降低静态浏览器画面的重复提交 |
+| 3 | `DLSSNR\\DLSSNR_AI_Filter` | R8 SDR -> R8 SDR | 执行 DLSS5/DLSSNR 神经降噪和细节处理 |
+| 4 | `SDRToHDR` | R8 SDR -> FP16 scRGB | 将 DLSS5 输出恢复为 HDR 呈现器使用的 FP16 格式 |
+
+DLSSNR 在这套配置中保持原有的 R8 输入输出合约。它的前后格式转换由两个独立效果完成,避免跨格式 `CopyResource` 和 FP16 通道解释问题。
+
+## SDR 白点参数
+
+`HDRToSDR.hlsl` 和 `SDRToHDR.hlsl` 当前使用 `sdrWhiteScale = 4.5`。这个值来自测试显示器的 Windows 显示配置:
+
+```text
+DISPLAYCONFIG_SDR_WHITE_LEVEL = 4500
+4500 / 1000 = 4.5x
+4.5x * 80 nits = 360 nits
+```
+
+它决定浏览器、文件管理器和其他 SDR 窗口在 HDR 桌面中的亮度。换显示器后,这个值需要重新读取。两个 HLSL 文件必须使用同一个值,否则处理前后的亮度会发生变化。
+
+Windows 原生 HDR 截图工具通常也会读取 `DISPLAYCONFIG_SDR_WHITE_LEVEL`,再将 FP16 scRGB 除以该参考白点。固定写入 4.5 只对应本次测试显示器,其他设备建议按实际参数调整。
+
+## 测试环境和结果
+
+- 操作系统:Windows 11,系统 HDR 保持开启
+- 显卡:NVIDIA GeForce RTX 4070
+- 测试窗口:Microsoft Edge
+- 捕获方式:`Graphics Capture (HDR)`
+- 缩放模式:`HDR DLSS5 Bridge`
+- DLSSNR:默认 Preset、默认 Style、Intensity 1、Guidance Mode 0
+- 帧率限制:60 FPS
+- SDR 白点:4.5x,360 nits
+
+浏览器视频画面可以正常显示,DLSSNR 连续执行,测试过程中没有黑屏或红色通道异常。经过 SDR 工作缓冲后,极亮 HDR 高光会受到 R8 范围限制,原始 HDR 动态范围暂时无法完整保留;这也是本次适配使用“初步”描述的原因。
+
+## 日志检查
+
+启动缩放后,在程序目录的 `logs\\magpie.log` 中确认:
+
+```text
+当前捕获模式: Graphics Capture (HDR)
+读取文本文件: effects\\HDRToSDR.hlsl
+读取文本文件: effects\\SDRToHDR.hlsl
+DLSSNR STATUS: Feature=18 ... result=0x1 success=...
+```
+
+如果看不到 `当前捕获模式: Graphics Capture (HDR)`,请回到配置文件检查捕获方式。如果出现效果文件缺失,说明 Release 没有完整解压到 `effects` 目录。如果 DLSSNR 成功计数持续增加,说明 DLSS5 已经在处理帧。
+
+## 当前限制
+
+- 这是 HDR 的初步适配,HDR 画面会经过 SDR R8 工作缓冲。
+- 极亮高光会受到 SDR 工作范围限制,原始 HDR 高光细节暂时无法完整保留。
+- SDR 白点当前按测试显示器写入效果文件,换显示器需要重新校准。
+- 最大化源窗口会沿用 Magpie 的全屏缩放路径。
diff --git a/presets/ScalingModes-HDR-DLSS5.json b/presets/ScalingModes-HDR-DLSS5.json
new file mode 100644
index 000000000..04223d3ed
--- /dev/null
+++ b/presets/ScalingModes-HDR-DLSS5.json
@@ -0,0 +1,36 @@
+{
+ "scalingModes": [
+ {
+ "name": "HDR DLSS5 Bridge",
+ "effects": [
+ {
+ "name": "HDRToSDR"
+ },
+ {
+ "name": "FrameRate_Filter",
+ "parameters": {
+ "targetFrameRate": 60
+ }
+ },
+ {
+ "name": "DLSSNR\\DLSSNR_AI_Filter",
+ "parameters": {
+ "nrPreset": 0,
+ "style": 0,
+ "intensity": 1,
+ "localToneStrength": 1,
+ "localStructureStrength": 1,
+ "skinStructureStrength": -1,
+ "useAutoMask": 0,
+ "uiCorrection": 0,
+ "guidanceMode": 0,
+ "depthInferenceInterval": 4
+ }
+ },
+ {
+ "name": "SDRToHDR"
+ }
+ ]
+ }
+ ]
+}
diff --git a/scripts/Build-Release.ps1 b/scripts/Build-Release.ps1
index c72ad04a3..7882c9626 100644
--- a/scripts/Build-Release.ps1
+++ b/scripts/Build-Release.ps1
@@ -200,6 +200,9 @@ if (!$SkipBuild) {
"/p:PatchVersion=$($versionMatch.Groups[3].Value)",
"/p:VersionString=$Version", "/p:CommitId=$shortCommit",
"/p:PreferredToolArchitecture=x64",
+ "/p:WindowsSdkDir=D:\Windows Kits\10\",
+ "/p:SDKReferenceDirectoryRoot=D:\Windows Kits\10\Extension SDKs\",
+ "/p:SDKExtensionDirectoryRoot=D:\Windows Kits\10\Extension SDKs\",
"/p:UseMultiToolTask=false", "/p:CL_MPCount=1",
"/p:DisablePDB=true", "/p:ReproducibleBuild=true"
)
diff --git a/src/Effects/Effects.vcxproj b/src/Effects/Effects.vcxproj
index 723688797..18357b8a1 100644
--- a/src/Effects/Effects.vcxproj
+++ b/src/Effects/Effects.vcxproj
@@ -51,6 +51,14 @@
Document
true
+
+ Document
+ true
+
+
+ Document
+ true
+
Document
true
diff --git a/src/Effects/HDRToSDR.hlsl b/src/Effects/HDRToSDR.hlsl
new file mode 100644
index 000000000..2845b685b
--- /dev/null
+++ b/src/Effects/HDRToSDR.hlsl
@@ -0,0 +1,30 @@
+//!MAGPIE EFFECT
+//!VERSION 4
+//!SORT_NAME HDR To SDR (HDR Capture Bridge)
+
+//!TEXTURE
+//!FORMAT R16G16B16A16_FLOAT
+Texture2D INPUT;
+
+//!TEXTURE
+//!FORMAT R8G8B8A8_UNORM
+Texture2D OUTPUT;
+
+//!SAMPLER
+//!FILTER POINT
+SamplerState sam;
+
+//!PASS 1
+//!STYLE PS
+//!IN INPUT
+//!OUT OUTPUT
+
+float Srgb(float x) {
+ return x <= 0.0031308 ? x * 12.92 : 1.055 * pow(x, 1.0 / 2.4) - 0.055;
+}
+
+float4 Pass1(float2 pos) {
+ const float sdrWhiteScale = 4.5;
+ float4 c = INPUT.SampleLevel(sam, pos, 0) / sdrWhiteScale;
+ return float4(Srgb(max(c.r, 0.0)), Srgb(max(c.g, 0.0)), Srgb(max(c.b, 0.0)), 1.0);
+}
diff --git a/src/Effects/SDRToHDR.hlsl b/src/Effects/SDRToHDR.hlsl
new file mode 100644
index 000000000..95d762502
--- /dev/null
+++ b/src/Effects/SDRToHDR.hlsl
@@ -0,0 +1,30 @@
+//!MAGPIE EFFECT
+//!VERSION 4
+//!SORT_NAME SDR To HDR (HDR Capture Bridge)
+
+//!TEXTURE
+//!FORMAT R8G8B8A8_UNORM
+Texture2D INPUT;
+
+//!TEXTURE
+//!FORMAT R16G16B16A16_FLOAT
+Texture2D OUTPUT;
+
+//!SAMPLER
+//!FILTER POINT
+SamplerState sam;
+
+//!PASS 1
+//!STYLE PS
+//!IN INPUT
+//!OUT OUTPUT
+
+float Linear(float x) {
+ return x <= 0.04045 ? x / 12.92 : pow((x + 0.055) / 1.055, 2.4);
+}
+
+float4 Pass1(float2 pos) {
+ const float sdrWhiteScale = 4.5;
+ float4 c = INPUT.SampleLevel(sam, pos, 0);
+ return float4(Linear(c.r), Linear(c.g), Linear(c.b), 1.0) * sdrWhiteScale;
+}
diff --git a/src/Magpie.Core/AdaptivePresenter.cpp b/src/Magpie.Core/AdaptivePresenter.cpp
index 1742d239e..3a818df98 100644
--- a/src/Magpie.Core/AdaptivePresenter.cpp
+++ b/src/Magpie.Core/AdaptivePresenter.cpp
@@ -7,12 +7,16 @@
namespace Magpie {
+static bool IsHdrPresentation() noexcept {
+ return ScalingWindow::Get().Options().captureMethod == CaptureMethod::GraphicsCaptureHDR;
+}
+
static bool ShouldLogPresentationDiagnostic(uint32_t count) noexcept {
return count <= 3 || count % 120 == 0;
}
bool AdaptivePresenter::_Initialize(HWND hwndAttach) noexcept {
- if (ScalingWindow::Get().Options().IsDirectFlipDisabled()) {
+ if (ScalingWindow::Get().Options().IsDirectFlipDisabled() && !IsHdrPresentation()) {
// 禁用 DirectFlip 时始终使用 DirectComposition 呈现
if (!_ResizeDCompVisual(hwndAttach)) {
Logger::Get().Error("_ResizeDCompVisual 失败");
@@ -29,7 +33,7 @@ bool AdaptivePresenter::_Initialize(HWND hwndAttach) noexcept {
DXGI_SWAP_CHAIN_DESC1 sd{
.Width = (UINT)rendererSize.cx,
.Height = (UINT)rendererSize.cy,
- .Format = DXGI_FORMAT_R8G8B8A8_UNORM,
+ .Format = IsHdrPresentation() ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM,
.SampleDesc = {
.Count = 1
},
@@ -70,6 +74,10 @@ bool AdaptivePresenter::_Initialize(HWND hwndAttach) noexcept {
Logger::Get().Error("获取 IDXGISwapChain2 失败");
return false;
}
+ if (IsHdrPresentation()) {
+ hr = _dxgiSwapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709);
+ if (FAILED(hr)) Logger::Get().ComWarn("SetColorSpace1(scRGB) 失败", hr);
+ }
uint32_t maximumFrameLatency = bufferCount - 1;
for (const EffectOption& effect : ScalingWindow::Get().Options().effects) {
@@ -103,6 +111,10 @@ bool AdaptivePresenter::_Initialize(HWND hwndAttach) noexcept {
Logger::Get().ComError("获取后缓冲区失败", hr);
return false;
}
+ if (IsHdrPresentation()) {
+ hr = _dxgiSwapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709);
+ if (FAILED(hr)) Logger::Get().ComWarn("Resize 后设置 scRGB 色彩空间失败", hr);
+ }
hr = d3dDevice->CreateRenderTargetView(_backBuffer.get(), nullptr, _backBufferRtv.put());
if (FAILED(hr)) {
@@ -383,7 +395,7 @@ bool AdaptivePresenter::_ResizeDCompVisual(HWND hwndAttach) noexcept {
hr = _dcompDevice->CreateVirtualSurface(
(UINT)rendererSize.cx,
(UINT)rendererSize.cy,
- DXGI_FORMAT_R8G8B8A8_UNORM,
+ IsHdrPresentation() ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM,
DXGI_ALPHA_MODE_IGNORE,
_dcompSurface.put()
);
diff --git a/src/Magpie.Core/CompSwapchainPresenter.cpp b/src/Magpie.Core/CompSwapchainPresenter.cpp
index 03502297b..1a46da29b 100644
--- a/src/Magpie.Core/CompSwapchainPresenter.cpp
+++ b/src/Magpie.Core/CompSwapchainPresenter.cpp
@@ -147,7 +147,8 @@ bool CompSwapchainPresenter::BeginFrame(
const SIZE rendererSize = Win32Helper::GetSizeOfRect(ScalingWindow::Get().RendererRect());
D3D11_TEXTURE2D_DESC desc{};
- desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+ desc.Format = ScalingWindow::Get().Options().captureMethod == CaptureMethod::GraphicsCaptureHDR
+ ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.MipLevels = 1;
desc.ArraySize = 1;
diff --git a/src/Magpie.Core/DLSSNRFilter.cpp b/src/Magpie.Core/DLSSNRFilter.cpp
index cc8c1adc1..6921b06dd 100644
--- a/src/Magpie.Core/DLSSNRFilter.cpp
+++ b/src/Magpie.Core/DLSSNRFilter.cpp
@@ -1562,10 +1562,12 @@ bool DLSSNRFilter::Initialize(
return false;
}
const bool supportedInput = inputDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM ||
- inputDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM;
- if (!supportedInput || outputDesc.Format != DXGI_FORMAT_R8G8B8A8_UNORM) {
+ inputDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM ||
+ inputDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT;
+ if (!supportedInput || (outputDesc.Format != DXGI_FORMAT_R8G8B8A8_UNORM &&
+ outputDesc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT)) {
Logger::Get().Error(fmt::format(
- "DLSSNR SDR path unsupported formats: input={}, output={}",
+ "DLSSNR unsupported formats: input={}, output={}",
(uint32_t)inputDesc.Format, (uint32_t)outputDesc.Format));
return false;
}
@@ -1608,7 +1610,7 @@ bool DLSSNRFilter::Initialize(
}
D3D11_TEXTURE2D_DESC sharedDesc = outputDesc;
- sharedDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+ sharedDesc.Format = outputDesc.Format;
sharedDesc.Width = impl->width;
sharedDesc.Height = impl->height;
if (!CreateSharedTexture(*impl, sharedDesc, true,
diff --git a/src/Magpie.Core/DeviceResources.cpp b/src/Magpie.Core/DeviceResources.cpp
index 0fa1c0511..d0c5fab9d 100644
--- a/src/Magpie.Core/DeviceResources.cpp
+++ b/src/Magpie.Core/DeviceResources.cpp
@@ -172,7 +172,8 @@ bool DeviceResources::_TryCreateD3DDevice(const winrt::com_ptr& a
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
}
// WGC 和 D3D11_CREATE_DEVICE_SINGLETHREADED 不兼容
- if (isForeground || ScalingWindow::Get().Options().captureMethod != CaptureMethod::GraphicsCapture) {
+ if (isForeground || (ScalingWindow::Get().Options().captureMethod != CaptureMethod::GraphicsCapture &&
+ ScalingWindow::Get().Options().captureMethod != CaptureMethod::GraphicsCaptureHDR)) {
createDeviceFlags |= D3D11_CREATE_DEVICE_SINGLETHREADED;
}
#ifdef MP_USE_COMPSWAPCHAIN
diff --git a/src/Magpie.Core/EffectCompiler.cpp b/src/Magpie.Core/EffectCompiler.cpp
index 0caee16a2..632de5ae7 100644
--- a/src/Magpie.Core/EffectCompiler.cpp
+++ b/src/Magpie.Core/EffectCompiler.cpp
@@ -590,8 +590,8 @@ static uint32_t ResolveParameter(std::string_view block, EffectDesc& desc) noexc
static uint32_t ResolveTexture(std::string_view block, EffectDesc& desc) noexcept {
- // 如果名称为 INPUT 不能有任何选项,含 SOURCE 时不能有任何其他选项
- // 如果名称为 OUTPUT 只能有 WIDTH 或 HEIGHT
+ // INPUT/OUTPUT are predeclared; FORMAT is allowed so effects can opt into
+ // FP16 HDR textures while retaining the usual size restrictions.
// 否则必需的选项: FORMAT
// 可选的选项: WIDTH, HEIGHT
@@ -700,18 +700,24 @@ static uint32_t ResolveTexture(std::string_view block, EffectDesc& desc) noexcep
}
if (token == desc.textures[0].name) {
- if (processed.any()) {
+ if (processed[0] || processed[2] || processed[3]) {
return 1;
}
+ if (processed[1]) {
+ desc.textures[0].format = texDesc.format;
+ }
// INPUT 已为第一个元素
desc.textures.pop_back();
} else if (token == desc.textures[1].name) {
- if (processed[0] || processed[1]) {
+ if (processed[0]) {
return 1;
}
// OUTPUT 已为第二个元素
+ if (processed[1]) {
+ desc.textures[1].format = texDesc.format;
+ }
desc.textures[1].sizeExpr = std::move(texDesc.sizeExpr);
desc.textures.pop_back();
} else {
diff --git a/src/Magpie.Core/GraphicsCaptureFrameSource.cpp b/src/Magpie.Core/GraphicsCaptureFrameSource.cpp
index 44d1de17e..72f1d565b 100644
--- a/src/Magpie.Core/GraphicsCaptureFrameSource.cpp
+++ b/src/Magpie.Core/GraphicsCaptureFrameSource.cpp
@@ -18,6 +18,10 @@ using namespace Windows::Graphics::DirectX::Direct3D11;
namespace Magpie {
+bool GraphicsCaptureFrameSource::_IsHdr() const noexcept {
+ return ScalingWindow::Get().Options().captureMethod == CaptureMethod::GraphicsCaptureHDR;
+}
+
bool GraphicsCaptureFrameSource::_Initialize() noexcept {
ID3D11Device5* d3dDevice = _deviceResources->GetD3DDevice();
@@ -60,7 +64,7 @@ bool GraphicsCaptureFrameSource::_Initialize() noexcept {
_output = DirectXHelper::CreateTexture2D(
d3dDevice,
- DXGI_FORMAT_B8G8R8A8_UNORM,
+ _IsHdr() ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_B8G8R8A8_UNORM,
_frameBox.right - _frameBox.left,
_frameBox.bottom - _frameBox.top,
D3D11_BIND_SHADER_RESOURCE
@@ -112,6 +116,18 @@ FrameSourceState GraphicsCaptureFrameSource::_Update() noexcept {
Logger::Get().ComError("从获取 IDirect3DSurface 获取 ID3D11Texture2D 失败", hr);
return FrameSourceState::Error;
}
+ if (_diagnosticFrameCount < 3) {
+ D3D11_TEXTURE2D_DESC sourceDesc{};
+ D3D11_TEXTURE2D_DESC outputDesc{};
+ withFrame->GetDesc(&sourceDesc);
+ _output->GetDesc(&outputDesc);
+ Logger::Get().Info(fmt::format(
+ "HDR 捕获帧: source={}x{} format={} output={}x{} format={} box={}x{}",
+ sourceDesc.Width, sourceDesc.Height, (uint32_t)sourceDesc.Format,
+ outputDesc.Width, outputDesc.Height, (uint32_t)outputDesc.Format,
+ _frameBox.right - _frameBox.left, _frameBox.bottom - _frameBox.top));
+ ++_diagnosticFrameCount;
+ }
_deviceResources->GetD3DDC()->CopySubresourceRegion(_output.get(), 0, 0, 0, 0, withFrame.get(), 0, &_frameBox);
@@ -322,7 +338,7 @@ bool GraphicsCaptureFrameSource::_StartCapture() noexcept {
// 创建帧缓冲池。帧的尺寸和 _captureItem.Size() 不同
_captureFramePool = winrt::Direct3D11CaptureFramePool::Create(
_wrappedD3DDevice,
- winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized,
+ _IsHdr() ? winrt::DirectXPixelFormat::R16G16B16A16Float : winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized,
4, // 帧的缓存数量,更大的值有利于在低帧率下降低延迟
{ (int)_frameBox.right, (int)_frameBox.bottom } // 帧的尺寸为包含源窗口的最小尺寸
);
diff --git a/src/Magpie.Core/GraphicsCaptureFrameSource.h b/src/Magpie.Core/GraphicsCaptureFrameSource.h
index 8857122c5..d27185111 100644
--- a/src/Magpie.Core/GraphicsCaptureFrameSource.h
+++ b/src/Magpie.Core/GraphicsCaptureFrameSource.h
@@ -19,7 +19,7 @@ class GraphicsCaptureFrameSource final : public FrameSourceBase {
}
const char* Name() const noexcept override {
- return "Graphics Capture";
+ return _IsHdr() ? "Graphics Capture (HDR)" : "Graphics Capture";
}
void OnCursorVisibilityChanged(bool isVisible, bool onDestory) noexcept override;
@@ -30,6 +30,7 @@ class GraphicsCaptureFrameSource final : public FrameSourceBase {
FrameSourceState _Update() noexcept override;
private:
+ bool _IsHdr() const noexcept;
bool _StartCapture() noexcept;
void _StopCapture() noexcept;
@@ -47,6 +48,7 @@ class GraphicsCaptureFrameSource final : public FrameSourceBase {
winrt::com_ptr _taskbarList;
bool _isSrcStyleChanged = false;
+ uint32_t _diagnosticFrameCount = 0;
};
}
diff --git a/src/Magpie.Core/Renderer.cpp b/src/Magpie.Core/Renderer.cpp
index 31b2d938a..e2523933d 100644
--- a/src/Magpie.Core/Renderer.cpp
+++ b/src/Magpie.Core/Renderer.cpp
@@ -726,6 +726,9 @@ bool Renderer::_InitFrameSource() noexcept {
case CaptureMethod::GraphicsCapture:
_frameSource = std::make_unique();
break;
+ case CaptureMethod::GraphicsCaptureHDR:
+ _frameSource = std::make_unique();
+ break;
case CaptureMethod::DesktopDuplication:
_frameSource = std::make_unique();
break;
@@ -901,7 +904,7 @@ ID3D11Texture2D* Renderer::_BuildEffects() noexcept {
passDesc.cso = nullptr;
}
}
-
+
if (_ShouldAppendBicubic(inOutTexture)) {
if (!_AppendBicubic(&inOutTexture)) {
Logger::Get().Error("_AppendBicubic 失败");
@@ -959,7 +962,9 @@ void Renderer::_UpdateActiveEffectDescs() noexcept {
bool Renderer::_ShouldAppendBicubic(ID3D11Texture2D* outTexture) noexcept {
const ScalingOptions& options = ScalingWindow::Get().Options();
-
+ if (options.captureMethod == CaptureMethod::GraphicsCaptureHDR) {
+ return false;
+ }
D3D11_TEXTURE2D_DESC texDesc;
outTexture->GetDesc(&texDesc);
const SIZE lastOutputSize = { (LONG)texDesc.Width, (LONG)texDesc.Height };
@@ -1295,7 +1300,9 @@ HANDLE Renderer::_CreateSharedTexture(ID3D11Texture2D* effectsOutput) noexcept {
for (uint32_t i = 0; i < _sharedTextureSlotCount; ++i) {
_backendSharedTextures[i] = DirectXHelper::CreateTexture2D(
_backendResources.GetD3DDevice(),
- DXGI_FORMAT_R8G8B8A8_UNORM,
+ // The frontend copies this texture into the presenter target. Keep the
+ // format identical to the effect output, including the HDR FP16 path.
+ desc.Format,
textureSize.cx,
textureSize.cy,
D3D11_BIND_SHADER_RESOURCE,
@@ -1328,6 +1335,9 @@ HANDLE Renderer::_CreateSharedTexture(ID3D11Texture2D* effectsOutput) noexcept {
return NULL;
}
}
+ Logger::Get().Info(fmt::format(
+ "共享呈现纹理: {}x{} format={}",
+ textureSize.cx, textureSize.cy, (uint32_t)desc.Format));
if (_sharedTextureSlotCount > 1) {
Logger::Get().Info(fmt::format(
"DLSSFG bounded presentation ring initialized: slots={}",
diff --git a/src/Magpie.Core/ScalingWindow.cpp b/src/Magpie.Core/ScalingWindow.cpp
index 77bc7377b..dc52fbf78 100644
--- a/src/Magpie.Core/ScalingWindow.cpp
+++ b/src/Magpie.Core/ScalingWindow.cpp
@@ -108,8 +108,16 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
if (_srcTracker.IsZoomed()) {
if (_options.IsWindowedMode()) {
+ if (_options.captureMethod == CaptureMethod::GraphicsCaptureHDR) {
+ // HDR capture keeps the source's native swap chain visible through the
+ // fullscreen presenter, so a maximized source can follow the normal
+ // fullscreen scaling path instead of being rejected as windowed.
+ Logger::Get().Info("HDR 捕获遇到最大化源窗口,自动切换到全屏缩放");
+ _options.IsWindowedMode(false);
+ } else {
Logger::Get().Info("已最大化的窗口不支持窗口模式缩放");
return ScalingError::BannedInWindowedMode;
+ }
} else if (!_options.RealIsAllowScalingMaximized()) {
Logger::Get().Info("源窗口已最大化");
return ScalingError::Maximized;
diff --git a/src/Magpie.Core/include/ScalingOptions.h b/src/Magpie.Core/include/ScalingOptions.h
index 9daf8da5a..6053c1862 100644
--- a/src/Magpie.Core/include/ScalingOptions.h
+++ b/src/Magpie.Core/include/ScalingOptions.h
@@ -5,6 +5,7 @@ namespace Magpie {
enum class CaptureMethod {
GraphicsCapture,
+ GraphicsCaptureHDR,
DesktopDuplication,
GDI,
DwmSharedSurface,
diff --git a/src/Magpie/AppSettings.cpp b/src/Magpie/AppSettings.cpp
index f90880780..451f16c39 100644
--- a/src/Magpie/AppSettings.cpp
+++ b/src/Magpie/AppSettings.cpp
@@ -86,6 +86,8 @@ static void WriteProfile(rapidjson::PrettyWriter& write
writer.Int(profile.scalingMode);
writer.Key("captureMethod");
writer.Uint((uint32_t)profile.captureMethod);
+ writer.Key("captureMethodVersion");
+ writer.Uint(2);
writer.Key("multiMonitorUsage");
writer.Uint((uint32_t)profile.multiMonitorUsage);
@@ -1089,8 +1091,17 @@ bool AppSettings::_LoadProfile(
JsonHelper::ReadUInt(profileObj, "captureMode", captureMethod);
}
+ uint32_t captureMethodVersion = 1;
+ JsonHelper::ReadUInt(profileObj, "captureMethodVersion", captureMethodVersion);
+ // Version 2 inserted Graphics Capture (HDR) after the original Graphics Capture entry.
+ if (captureMethodVersion < 2 && captureMethod > 0) {
+ ++captureMethod;
+ }
if (captureMethod >= (uint32_t)CaptureMethod::COUNT) {
captureMethod = (uint32_t)CaptureMethod::GraphicsCapture;
+ } else if (captureMethod == (uint32_t)CaptureMethod::GraphicsCaptureHDR &&
+ !Win32Helper::GetOSVersion().Is20H1OrNewer()) {
+ captureMethod = (uint32_t)CaptureMethod::GraphicsCapture;
} else if (captureMethod == (uint32_t)CaptureMethod::DesktopDuplication) {
// Desktop Duplication 捕获模式要求 Win10 20H1+
if (!Win32Helper::GetOSVersion().Is20H1OrNewer()) {
diff --git a/src/Magpie/Magpie.vcxproj b/src/Magpie/Magpie.vcxproj
index 4e0dff055..13376d07d 100644
--- a/src/Magpie/Magpie.vcxproj
+++ b/src/Magpie/Magpie.vcxproj
@@ -36,6 +36,10 @@
+
+ D:\Windows Kits\10\Extension SDKs\
+ D:\Windows Kits\10\Extension SDKs\
+
Application
v143
@@ -69,7 +73,7 @@
-
+
@@ -81,7 +85,7 @@
_VSDESIGNER_DONT_LOAD_AS_DLL;DISABLE_XAML_GENERATED_MAIN;%(PreprocessorDefinitions)
Level4
- ..\Magpie.Core\include;%(AdditionalIncludeDirectories)
+ ..\Magpie.Core\include;..\Magpie.Core\Generated Files;..\Magpie.Core\Generated Files\winrt;%(AdditionalIncludeDirectories)
$(LlvmArchitectureSwitch) %(AdditionalOptions)
@@ -683,7 +687,7 @@
-
+
@@ -837,6 +841,12 @@
+
+
+
+
+
+
<_XamlTypeInfoCpp Include="@(ClCompile)" Condition="'%(Extension)' == '.cpp' And ('%(Filename)' == 'XamlTypeInfo.Impl.g' Or '%(Filename)' == 'XamlTypeInfo.g')" />
diff --git a/src/Magpie/ProfileViewModel.cpp b/src/Magpie/ProfileViewModel.cpp
index d7b88a0b9..d0b974181 100644
--- a/src/Magpie/ProfileViewModel.cpp
+++ b/src/Magpie/ProfileViewModel.cpp
@@ -331,9 +331,10 @@ IVector ProfileViewModel::CaptureMethods() const noexcept {
ResourceLoader::GetForCurrentView(CommonSharedConstants::APP_RESOURCE_MAP_ID);
std::vector captureMethods;
- captureMethods.reserve(4);
+ captureMethods.reserve(5);
captureMethods.push_back(box_value(L"Graphics Capture"));
if (Win32Helper::GetOSVersion().Is20H1OrNewer()) {
+ captureMethods.push_back(box_value(L"Graphics Capture (HDR)"));
// Desktop Duplication 要求 Win10 20H1+
captureMethods.push_back(box_value(L"Desktop Duplication"));
}
@@ -344,9 +345,12 @@ IVector ProfileViewModel::CaptureMethods() const noexcept {
}
int ProfileViewModel::CaptureMethod() const noexcept {
- if (Win32Helper::GetOSVersion().Is20H1OrNewer() || _data->captureMethod < CaptureMethod::DesktopDuplication) {
+ if (Win32Helper::GetOSVersion().Is20H1OrNewer()) {
return (int)_data->captureMethod;
} else {
+ if (_data->captureMethod <= CaptureMethod::GraphicsCaptureHDR) {
+ return (int)CaptureMethod::GraphicsCapture;
+ }
return (int)_data->captureMethod - 1;
}
}
@@ -356,7 +360,7 @@ void ProfileViewModel::CaptureMethod(int value) {
return;
}
- if (!Win32Helper::GetOSVersion().Is20H1OrNewer() && value >= (int)CaptureMethod::DesktopDuplication) {
+ if (!Win32Helper::GetOSVersion().Is20H1OrNewer() && value >= (int)CaptureMethod::GraphicsCaptureHDR) {
++value;
}
@@ -597,6 +601,7 @@ void ProfileViewModel::IsCaptureTitleBar(bool value) {
bool ProfileViewModel::CanCaptureTitleBar() const noexcept {
return _data->captureMethod == CaptureMethod::GraphicsCapture
+ || _data->captureMethod == CaptureMethod::GraphicsCaptureHDR
|| _data->captureMethod == CaptureMethod::DesktopDuplication;
}
diff --git a/src/WinUI.targets b/src/WinUI.targets
index 4db4b951f..f933d4d9a 100644
--- a/src/WinUI.targets
+++ b/src/WinUI.targets
@@ -7,6 +7,7 @@
+
@@ -91,7 +92,11 @@ using (StreamWriter writer = new("version.txt")) {
// 获取最新的 Windows SDK 目录
string programFilesX86Path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
-string windowsSdkDir = Directory.GetDirectories(Path.Combine(programFilesX86Path, "Windows Kits\\10\\bin"))
+string windowsKitsRoot = WindowsSdkDir;
+if (String.IsNullOrWhiteSpace(windowsKitsRoot)) {
+ windowsKitsRoot = Path.Combine(programFilesX86Path, "Windows Kits\\10\\");
+}
+string windowsSdkDir = Directory.GetDirectories(Path.Combine(windowsKitsRoot, "bin"))
.OrderByDescending(dir => dir)
.FirstOrDefault(dir => dir.Contains("10."));
if (windowsSdkDir == null) {
@@ -203,7 +208,7 @@ File.Delete(configFileName);
<_ReferenceRelatedPaths Remove="@(_ReferenceRelatedPaths)" Condition="'%(Filename)' == 'Microsoft.UI.Xaml' And '%(Extension)' != '.winmd'" />
-
+