Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions docs/experimental/HDR-DLSS5-Bridge.md
Original file line number Diff line number Diff line change
@@ -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 的全屏缩放路径。
36 changes: 36 additions & 0 deletions presets/ScalingModes-HDR-DLSS5.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
]
}
3 changes: 3 additions & 0 deletions scripts/Build-Release.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down
8 changes: 8 additions & 0 deletions src/Effects/Effects.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@
<FileType>Document</FileType>
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
<CopyFileToFolders Include="HDRToSDR.hlsl">
<FileType>Document</FileType>
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
<CopyFileToFolders Include="SDRToHDR.hlsl">
<FileType>Document</FileType>
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
<CopyFileToFolders Include="FrameRate_Filter.hlsl">
<FileType>Document</FileType>
<DeploymentContent>true</DeploymentContent>
Expand Down
30 changes: 30 additions & 0 deletions src/Effects/HDRToSDR.hlsl
Original file line number Diff line number Diff line change
@@ -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);
}
30 changes: 30 additions & 0 deletions src/Effects/SDRToHDR.hlsl
Original file line number Diff line number Diff line change
@@ -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;
}
18 changes: 15 additions & 3 deletions src/Magpie.Core/AdaptivePresenter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 失败");
Expand All @@ -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
},
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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()
);
Expand Down
3 changes: 2 additions & 1 deletion src/Magpie.Core/CompSwapchainPresenter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 6 additions & 4 deletions src/Magpie.Core/DLSSNRFilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion src/Magpie.Core/DeviceResources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ bool DeviceResources::_TryCreateD3DDevice(const winrt::com_ptr<IDXGIAdapter1>& 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
Expand Down
14 changes: 10 additions & 4 deletions src/Magpie.Core/EffectCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 {
Expand Down
Loading