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'" /> - +