From 06435e47fb878780f4b5b1a2d18cd4937c5f01bf Mon Sep 17 00:00:00 2001 From: konodiodaaaaa1 Date: Tue, 8 Sep 2026 01:01:35 +0800 Subject: [PATCH 01/10] Add HDR-compatible FP16 effect pipeline --- README.md | 80 +- README_ZH.md | 80 +- docs/diagnostics/20260906-full-chain-audit.md | 53 + .../20260906-hdr-sdr-chain-failure-report.md | 23 + docs/diagnostics/pipeline-audit.md | 185 ++ docs/experimental/GROUP_A_HDR_ROUTES.md | 35 + .../HDR_COMPATIBILITY_ARCHITECTURE.md | 344 +++ .../HDR_EFFECT_IMPLEMENTATION_CATALOG.md | 290 +++ .../HDR_EFFECT_PROTOCOL_AUDIT_20260907.md | 100 + ..._MECHANICAL_IMPLEMENTATION_REQUIREMENTS.md | 110 + docs/experimental/HDR_PROTOCOL_EVIDENCE.json | 2125 +++++++++++++++++ docs/experimental/HDR_PROTOCOL_EVIDENCE.md | 1416 +++++++++++ docs/experimental/agent-a-report.md | 81 + docs/experimental/agent-b-report.md | 88 + docs/experimental/agent-c-report.md | 85 + scripts/Run-HdrMechanicalValidation.ps1 | 193 ++ src/Common.Post.props | 2 +- src/Effects/DLSSNR/DLSSNR_AI_Filter.hlsl | 17 + src/Effects/Effects.vcxproj | 4 +- src/Effects/MLAA/MLAA.hlsl | 90 +- src/Effects/NNEDI3/NNEDI3_nns128_win8x4.hlsl | 2 +- src/Effects/NNEDI3/NNEDI3_nns128_win8x6.hlsl | 2 +- src/Effects/NNEDI3/NNEDI3_nns16_win8x4.hlsl | 2 +- src/Effects/NNEDI3/NNEDI3_nns16_win8x6.hlsl | 2 +- src/Effects/NNEDI3/NNEDI3_nns256_win8x4.hlsl | 2 +- src/Effects/NNEDI3/NNEDI3_nns256_win8x6.hlsl | 2 +- src/Effects/NNEDI3/NNEDI3_nns32_win8x4.hlsl | 2 +- src/Effects/NNEDI3/NNEDI3_nns32_win8x6.hlsl | 2 +- src/Effects/NNEDI3/NNEDI3_nns64_win8x4.hlsl | 2 +- src/Effects/NNEDI3/NNEDI3_nns64_win8x6.hlsl | 2 +- src/Effects/Pixel Art/MMPX.hlsl | 13 +- src/Effects/Pixel Art/Pixellate.hlsl | 25 +- src/Effects/RAVU/RAVU_3x_R2.hlsl | 2 +- src/Effects/RAVU/RAVU_3x_R3.hlsl | 2 +- src/Effects/RAVU/RAVU_3x_R4.hlsl | 2 +- src/Effects/RAVU/RAVU_Lite_AR_R2.hlsl | 2 +- src/Effects/RAVU/RAVU_Lite_AR_R3.hlsl | 2 +- src/Effects/RAVU/RAVU_Lite_AR_R4.hlsl | 2 +- src/Effects/RAVU/RAVU_Lite_R2.hlsl | 2 +- src/Effects/RAVU/RAVU_Lite_R3.hlsl | 2 +- src/Effects/RAVU/RAVU_Lite_R4.hlsl | 2 +- src/Effects/RAVU/RAVU_R2.hlsl | 2 +- src/Effects/RAVU/RAVU_R3.hlsl | 2 +- src/Effects/RAVU/RAVU_R4.hlsl | 2 +- src/Effects/RAVU/RAVU_Zoom_AR_R2.hlsl | 2 +- src/Effects/RAVU/RAVU_Zoom_AR_R3.hlsl | 2 +- src/Effects/RAVU/RAVU_Zoom_R2.hlsl | 2 +- src/Effects/RAVU/RAVU_Zoom_R3.hlsl | 2 +- src/Effects/xBRZ/xBRZ_2x.hlsl | 11 +- src/Effects/xBRZ/xBRZ_3x.hlsl | 5 +- src/Effects/xBRZ/xBRZ_4x.hlsl | 5 +- src/Effects/xBRZ/xBRZ_5x.hlsl | 5 +- src/Effects/xBRZ/xBRZ_6x.hlsl | 5 +- src/Effects/xBRZ/xBRZ_Freescale.hlsl | 5 +- src/Magpie.Core/AdaptivePresenter.cpp | 16 +- src/Magpie.Core/AmdOpticalFlowProvider.cpp | 14 +- src/Magpie.Core/AmdOpticalFlowProvider.h | 3 + src/Magpie.Core/CompSwapchainPresenter.cpp | 4 +- src/Magpie.Core/DLSSNRFilter.cpp | 41 +- src/Magpie.Core/DLSSNRFilter.h | 5 +- src/Magpie.Core/DLSSSRUpscaler.cpp | 4 +- src/Magpie.Core/DLSSSRUpscaler.h | 3 + .../DesktopDuplicationFrameSource.cpp | 1 + src/Magpie.Core/EffectCompiler.cpp | 20 + src/Magpie.Core/EffectDrawer.cpp | 289 ++- src/Magpie.Core/EffectDrawer.h | 30 + src/Magpie.Core/EffectProtocolCatalogC.h | 223 ++ src/Magpie.Core/FSR2Upscaler.cpp | 29 +- src/Magpie.Core/FSR2Upscaler.h | 7 + src/Magpie.Core/FSR2ZeroMVUpscaler.cpp | 214 ++ src/Magpie.Core/FSR2ZeroMVUpscaler.h | 51 + src/Magpie.Core/FSR3Upscaler.cpp | 20 +- src/Magpie.Core/FSR3Upscaler.h | 5 + src/Magpie.Core/FSR3ZeroMVUpscaler.cpp | 609 +++++ src/Magpie.Core/FSR3ZeroMVUpscaler.h | 33 + src/Magpie.Core/FrameGuidanceService.cpp | 19 +- src/Magpie.Core/FrameGuidanceService.h | 6 +- src/Magpie.Core/FrameGuidanceTypes.h | 14 +- src/Magpie.Core/FrameSourceBase.cpp | 156 ++ src/Magpie.Core/FrameSourceBase.h | 39 +- .../GraphicsCaptureFrameSource.cpp | 34 +- src/Magpie.Core/GraphicsCaptureFrameSource.h | 1 + src/Magpie.Core/GroupAHdrRoutes.cpp | 114 + src/Magpie.Core/GroupAHdrRoutes.h | 25 + src/Magpie.Core/GroupBEffectProtocol.h | 48 + src/Magpie.Core/GroupBHdrRoutes.cpp | 94 + src/Magpie.Core/GroupBHdrRoutes.h | 13 + src/Magpie.Core/HalfResOpticalFlow.cpp | 171 ++ src/Magpie.Core/HalfResOpticalFlow.h | 43 + src/Magpie.Core/HdrAdapterDispatcher.cpp | 96 + src/Magpie.Core/HdrAdapterDispatcher.h | 50 + src/Magpie.Core/HdrCaptureProcessor.cpp | 302 +++ src/Magpie.Core/HdrCaptureProcessor.h | 52 + src/Magpie.Core/HdrColorTransform.cpp | 144 ++ src/Magpie.Core/HdrColorTransform.h | 51 + src/Magpie.Core/HdrDiagnostics.cpp | 78 + src/Magpie.Core/HdrDiagnostics.h | 34 + src/Magpie.Core/HdrEffectBoundary.cpp | 74 + src/Magpie.Core/HdrEffectBoundary.h | 34 + src/Magpie.Core/HdrFrame.cpp | 62 + src/Magpie.Core/HdrFrame.h | 177 ++ src/Magpie.Core/HdrProtocol.cpp | 106 + src/Magpie.Core/HdrProtocol.h | 112 + src/Magpie.Core/HdrSourceTestBridge.cpp | 49 + src/Magpie.Core/HdrSourceTestBridge.h | 32 + src/Magpie.Core/HdrSurfaceAdapter.cpp | 319 +++ src/Magpie.Core/HdrSurfaceAdapter.h | 81 + src/Magpie.Core/Magpie.Core.vcxproj | 54 +- src/Magpie.Core/Magpie.Core.vcxproj.filters | 26 + src/Magpie.Core/NativeEffectBackend.h | 10 + .../NativeEffectBackendFactory.cpp | 124 +- src/Magpie.Core/NvCVImageD3D11Proxy.cpp | 57 + src/Magpie.Core/NvCVImageD3D11Proxy.h | 9 + src/Magpie.Core/NvidiaOpticalFlowProvider.cpp | 106 +- src/Magpie.Core/PassThroughFrames.cpp | 92 +- src/Magpie.Core/PassThroughFrames.h | 16 +- src/Magpie.Core/RTXVideoDenoiser.cpp | 296 ++- src/Magpie.Core/RTXVideoDenoiser.h | 6 +- src/Magpie.Core/Renderer.cpp | 586 ++++- src/Magpie.Core/Renderer.h | 28 +- src/Magpie.Core/ScalingOptions.cpp | 2 + src/Magpie.Core/Win32Helper.cpp | 45 + src/Magpie.Core/XeSSFGPresenter.cpp | 45 +- src/Magpie.Core/XeSSUpscaler.cpp | 75 +- src/Magpie.Core/XeSSZeroMVUpscaler.cpp | 605 +++++ src/Magpie.Core/XeSSZeroMVUpscaler.h | 41 + src/Magpie.Core/ZeroFrameGuidanceProvider.cpp | 9 +- src/Magpie.Core/include/EffectCompiler.h | 6 + src/Magpie.Core/include/ScalingOptions.h | 2 + src/Magpie.Core/include/Win32Helper.h | 5 + src/Magpie/AppSettings.cpp | 3 + src/Magpie/Magpie.vcxproj | 12 +- src/Magpie/Profile.h | 1 + src/Magpie/ProfilePage.xaml | 7 + src/Magpie/ProfileViewModel.cpp | 15 + src/Magpie/ProfileViewModel.h | 3 + src/Magpie/ProfileViewModel.idl | 1 + src/Magpie/Resources.language-de.resw | 6 + src/Magpie/Resources.language-en-US.resw | 6 + src/Magpie/Resources.language-es.resw | 6 + src/Magpie/Resources.language-fi.resw | 6 + src/Magpie/Resources.language-fr.resw | 6 + src/Magpie/Resources.language-hu.resw | 8 +- src/Magpie/Resources.language-id.resw | 6 + src/Magpie/Resources.language-it.resw | 6 + src/Magpie/Resources.language-ja.resw | 6 + src/Magpie/Resources.language-ka.resw | 8 +- src/Magpie/Resources.language-ko.resw | 6 + src/Magpie/Resources.language-pl.resw | 6 + src/Magpie/Resources.language-pt-BR.resw | 6 + src/Magpie/Resources.language-ru.resw | 6 + src/Magpie/Resources.language-ta.resw | 6 + src/Magpie/Resources.language-tr.resw | 6 + src/Magpie/Resources.language-uk.resw | 6 + src/Magpie/Resources.language-vi.resw | 6 + src/Magpie/Resources.language-zh-Hans.resw | 6 + src/Magpie/Resources.language-zh-Hant.resw | 6 + src/TouchHelper/TouchHelper.vcxproj | 6 +- src/Updater/Updater.vcxproj | 6 +- src/WinUI.targets | 8 +- src/_ConanDeps/_ConanDeps.vcxproj | 4 +- tests/HdrMechanicalTests.cpp | 163 ++ 162 files changed, 12051 insertions(+), 325 deletions(-) create mode 100644 docs/diagnostics/20260906-full-chain-audit.md create mode 100644 docs/diagnostics/20260906-hdr-sdr-chain-failure-report.md create mode 100644 docs/diagnostics/pipeline-audit.md create mode 100644 docs/experimental/GROUP_A_HDR_ROUTES.md create mode 100644 docs/experimental/HDR_COMPATIBILITY_ARCHITECTURE.md create mode 100644 docs/experimental/HDR_EFFECT_IMPLEMENTATION_CATALOG.md create mode 100644 docs/experimental/HDR_EFFECT_PROTOCOL_AUDIT_20260907.md create mode 100644 docs/experimental/HDR_MECHANICAL_IMPLEMENTATION_REQUIREMENTS.md create mode 100644 docs/experimental/HDR_PROTOCOL_EVIDENCE.json create mode 100644 docs/experimental/HDR_PROTOCOL_EVIDENCE.md create mode 100644 docs/experimental/agent-a-report.md create mode 100644 docs/experimental/agent-b-report.md create mode 100644 docs/experimental/agent-c-report.md create mode 100644 scripts/Run-HdrMechanicalValidation.ps1 create mode 100644 src/Magpie.Core/EffectProtocolCatalogC.h create mode 100644 src/Magpie.Core/FSR2ZeroMVUpscaler.cpp create mode 100644 src/Magpie.Core/FSR2ZeroMVUpscaler.h create mode 100644 src/Magpie.Core/FSR3ZeroMVUpscaler.cpp create mode 100644 src/Magpie.Core/FSR3ZeroMVUpscaler.h create mode 100644 src/Magpie.Core/GroupAHdrRoutes.cpp create mode 100644 src/Magpie.Core/GroupAHdrRoutes.h create mode 100644 src/Magpie.Core/GroupBEffectProtocol.h create mode 100644 src/Magpie.Core/GroupBHdrRoutes.cpp create mode 100644 src/Magpie.Core/GroupBHdrRoutes.h create mode 100644 src/Magpie.Core/HalfResOpticalFlow.cpp create mode 100644 src/Magpie.Core/HalfResOpticalFlow.h create mode 100644 src/Magpie.Core/HdrAdapterDispatcher.cpp create mode 100644 src/Magpie.Core/HdrAdapterDispatcher.h create mode 100644 src/Magpie.Core/HdrCaptureProcessor.cpp create mode 100644 src/Magpie.Core/HdrCaptureProcessor.h create mode 100644 src/Magpie.Core/HdrColorTransform.cpp create mode 100644 src/Magpie.Core/HdrColorTransform.h create mode 100644 src/Magpie.Core/HdrDiagnostics.cpp create mode 100644 src/Magpie.Core/HdrDiagnostics.h create mode 100644 src/Magpie.Core/HdrEffectBoundary.cpp create mode 100644 src/Magpie.Core/HdrEffectBoundary.h create mode 100644 src/Magpie.Core/HdrFrame.cpp create mode 100644 src/Magpie.Core/HdrFrame.h create mode 100644 src/Magpie.Core/HdrProtocol.cpp create mode 100644 src/Magpie.Core/HdrProtocol.h create mode 100644 src/Magpie.Core/HdrSourceTestBridge.cpp create mode 100644 src/Magpie.Core/HdrSourceTestBridge.h create mode 100644 src/Magpie.Core/HdrSurfaceAdapter.cpp create mode 100644 src/Magpie.Core/HdrSurfaceAdapter.h create mode 100644 src/Magpie.Core/NvCVImageD3D11Proxy.cpp create mode 100644 src/Magpie.Core/NvCVImageD3D11Proxy.h create mode 100644 src/Magpie.Core/XeSSZeroMVUpscaler.cpp create mode 100644 src/Magpie.Core/XeSSZeroMVUpscaler.h create mode 100644 tests/HdrMechanicalTests.cpp diff --git a/README.md b/README.md index 330a21483..b64d3daff 100644 --- a/README.md +++ b/README.md @@ -3,53 +3,75 @@ 🌍 **English** | [简体中文](./README_ZH.md) -An unofficial experimental fork of [Blinue/Magpie](https://github.com/Blinue/Magpie), providing captured-window DLSS, DLSS Frame Generation, DLSSNR, XeSS, FSR and RTX Video effects. This is not an official Magpie release and is not supported by upstream. +Magpie Experimental is a Windows tool for processing window content and an unofficial fork of [Blinue/Magpie](https://github.com/Blinue/Magpie). Built on Magpie's window-scaling and effect system, it explores DLSS, XeSS, FSR, RTX Video and related technologies for games, video and other windowed content. -## Versions and Installation +The application captures a target window, processes its images through a user-configured effect group, and displays the result fullscreen or in a window. The source application does not need to integrate these effects itself. This is not an official Magpie release; please report experimental-fork issues in this repository. -0.6.5 is in local release-package preparation. See the complete [0.6.5 Release Note](docs/RELEASE_NOTES_v0.6.5-experimental.md); publicly available builds are listed on [GitHub Releases](https://github.com/SAOG0721/Magpie/releases). +## Main Features -Fully exit Magpie, extract all of `Magpie-Experimental-x64.zip` into a new directory, and run `Magpie.exe`. Do not replace only the EXE or copy old effects/depth components into the new package. Normal settings use `%LOCALAPPDATA%\Magpie\config\v4\config.json`; portable settings use `config\config.json` beside the program. Back up your configuration and previous installation before upgrading. +### Image Processing and Frame Generation -## Effect Groups and Parameters +| Feature | Example Effects | Purpose | +| --- | --- | --- | +| Spatial upscaling and sharpening | Lanczos, FSR and other MagpieFX effects | Enlarge window content and adjust clarity and detail | +| Experimental temporal super resolution | DLSS SR, FSR 2/3/4, XeSS SR | Explore image reconstruction from captured frames and estimated motion | +| AI image controls | DLSSNR | Same-resolution SDR processing with tone, structure, shadow, reflection and glow controls | +| Video enhancement | RTX Video | Video super resolution and denoising for low-resolution or compressed content | +| Frame generation | DLSSFG, XeSSFG | Generate intermediate images between real frames for smoother motion | -Scaling modes are now called **Effect groups**, combining multiple effects. Fresh configurations contain Lanczos, FSR, RTX Video VSR Ultra, DLSSFG, XeSSFG and DLSSNR, with Lanczos selected. Existing custom groups are preserved. +Experimental-effect availability depends on the GPU, driver, runtime components and effect combination. See the [Release notes](https://github.com/SAOG0721/Magpie/releases) for the effects and hardware requirements of each build. -DLSSFG, XeSSFG and DLSSNR are included among the built-in effect groups. Reset on the Effect groups page restores the defaults. +### Effect Groups and Parameter Editing -Parameter edits save automatically. The toolbar editor labels controls **Live**, **Restart**, or **Auto restart**, distinguishing manual application from automatic rebuilding after editing. Previously live DLSSNR core/upstream image changes fully stop the group, wait 500 ms and restart it; residual composition retains its existing live behavior. Consecutive edits coalesce, and manually stopping cancels a pending restart. +An **effect group** combines effects in a chosen order, storing their parameters and scaling settings. Use built-in groups or create your own combinations for different applications. -Double-click a slider within 170 ms to restore the effect's own default. Parameters support groups, drop-down choices, and Simplified/Traditional Chinese display. The editor distinguishes desired values from applied values when saving fails or a change has not taken effect. +The toolbar's parameter panel lets you adjust effects while viewing the result, with automatic saving. Controls indicate whether a change applies live or requires the group to restart; double-clicking a slider restores the effect's default value. Parameters support groups, drop-down choices and Chinese translations. -## Toolbar and Frame Pacing +### Comparison, Performance Monitoring and Frame Sync -| Action | Default Shortcut | -| --- | --- | -| Profiler | Alt+Shift+P | -| Effect parameters | Alt+Shift+E | -| Screenshot | Alt+Shift+S | -| Pin toolbar | Alt+Shift+F | -| Comparison | Alt+Shift+C | +The toolbar offers original/processed comparison, per-effect timings and frame-rate monitoring, screenshots and parameter editing, with customizable shortcuts. When frame generation is enabled, separate output and real-frame readings help you assess the result and processing cost. -Configure shortcuts in Home's collapsible toolbar section. Comparison continues effect processing while showing the original; its badge stays for two seconds and fades over 500 ms. With FG, frame rates support an output/real-frame display. +Front Edge Sync controls Magpie's frame submission pace, regulating real-frame input before FG when frame generation is active. Apply a matching frame-rate cap in the source application; synchronization waits may increase latency. See the [frame-sync guide](docs/FRAME_SYNC_GUIDE.md) for setup details. -Front Edge Sync defaults to enabled at 60 FPS and controls base-frame pacing before FG. Apply a matching limiter in the source application too; latency may increase. FrameRate Filter follows this setting by default and offers Custom only with synchronization disabled. See the [frame-sync guide](docs/FRAME_SYNC_GUIDE.md). VRR is currently hidden and disabled; HDR remains unimplemented. +## Download and Install -## Optical Flow and Compatibility +1. Choose a version from [GitHub Releases](https://github.com/SAOG0721/Magpie/releases), read its notes and download the main `Magpie-Experimental-x64.zip` package. +2. Fully exit any running Magpie instance and extract the complete package into a new directory. +3. Run `Magpie.exe`, select an effect group and target window, and use the shortcut shown on Home to enable the effects. -DLSS SR, FSR 2/3/4 and XeSS SR each have one entry with None / AMD OF / NVOF. Standalone Zero MV, Optical Flow and metadata-only jitter variants are merged, with automatic migration of old settings. DLSSNR and DLSSFG use NVOF; XeSSFG x2 offers AMD OF/NVOF, while XeSS Multi-FG currently supports AMD OF or None. +Before upgrading, back up any settings and screenshots you want to keep outside the program directory, then follow that release's instructions for the old installation and configuration. The same notes explain optional runtimes, DLL choices and helper tools. GitHub's automatically generated source archives are for development, not ready-to-run application packages. -Consumers share a requested provider: NVOF first, then AMD OF, at the highest quality actually requested for that source. NVOF Highest Quality is 2×2 Slow and can be very expensive; Balanced remains marked Recommended. +## Usage and Compatibility -Magpie lacks engine-native depth, motion vectors, exposure and UI separation. Depth contracts use zero-filled textures and motion is estimated from captured color, so this is not equivalent to native DLSS/FSR/XeSS integration. Use only one FG per group and avoid stacking it with Smooth Motion or other frame-generation systems. +- The main release package targets Windows x64 and requires a DirectX 11-capable GPU; individual AI effects may have higher requirements. +- Magpie processes complete window images without access to the game engine's full native motion vectors, depth, exposure or separated UI. Estimated optical flow can assist some effects, but this is not equivalent to native in-game DLSS/FSR/XeSS integration. +- Image processing can affect text and UI along with the scene; temporal effects may also produce ghosting or other artifacts. Use Comparison to judge whether an effect suits the content. +- Use one frame-generation effect per group and avoid combining it with other frame-generation systems. Generated FPS is not the game's real rendering rate and does not imply a proportional improvement in input responsiveness. -## Troubleshooting and Development +## Reporting Problems -Start with Home's recent-issue details and logs. Release tools include the NGX OTA switch and DLSSNR DLL choices; see the Release Note for usage and limitations. Optional proprietary backends default to disabled in source builds; local SDK/runtime paths belong in the untracked `src/BuildOptions.props.user`. +Start with the suggestions and details in Home's recent-issue card, or open the log directory. When filing an [issue](https://github.com/SAOG0721/Magpie/issues), include the application version, GPU and driver, effect group, input/output resolution, reproduction steps and relevant logs. -- [Dependencies, notices and build boundaries](docs/THIRD_PARTY_AND_REDISTRIBUTION.md) -- [Experimental development documentation](docs/experimental/README.md) +The [upstream Magpie FAQ](https://github.com/Blinue/Magpie/wiki/FAQ) also covers general usage questions. Please discuss this fork's experimental effects and compatibility issues in this repository. + +## Development and Documentation + +The project includes the Magpie application, MagpieFX effects and experimental native effect backends. Optional proprietary backends are disabled by default in source builds and require separately supplied SDKs and runtimes. Configure local paths in the untracked `src/BuildOptions.props.user` file. + +- [Third-party dependencies, licenses and build boundaries](docs/THIRD_PARTY_AND_REDISTRIBUTION.md) +- [Experimental feature designs and development records](docs/experimental/README.md) - [Build and packaging script](scripts/Build-Release.ps1) -- [MagpieFX format]() +- [MagpieFX effect format]() + +## Contributions and Acknowledgments + +This project builds on [Blinue/Magpie](https://github.com/Blinue/Magpie) and its contributors' work. Thanks also to everyone contributing code, translations, design suggestions and testing feedback. + +- [HexBen123](https://github.com/HexBen123): early depth-estimation performance optimization and TensorRT integration guidance, plus the [DLSSNR parameter-localization proposal](https://github.com/SAOG0721/Magpie/pull/16). +- [Kristijan1001](https://github.com/Kristijan1001): the [reference implementation for editing effect parameters while scaling](https://github.com/SAOG0721/Magpie/pull/4), which informed this project's parameter-panel design and implementation. + +Contributions through Issues and Pull Requests are welcome. See [Contributors](https://github.com/SAOG0721/Magpie/graphs/contributors) for the code contribution history. + +## License -Source retains [GPLv3](LICENSE). Third-party runtimes, SDKs, models and local configuration are excluded from the source repository. +Magpie-derived source code is licensed under [GPLv3](LICENSE). Third-party SDKs, models and runtimes have their own licenses; see the [third-party and redistribution notes](docs/THIRD_PARTY_AND_REDISTRIBUTION.md). diff --git a/README_ZH.md b/README_ZH.md index 7b84f1d05..f15635485 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -3,53 +3,75 @@ 🌍 [English](./README.md) | **简体中文** -这是 [Blinue/Magpie](https://github.com/Blinue/Magpie) 的非官方实验分支,提供基于窗口捕获画面的 DLSS、DLSS Frame Generation、DLSSNR、XeSS、FSR 和 RTX Video 效果。不代表 Magpie 官方,也不由上游项目提供支持。 +Magpie Experimental 是面向 Windows 的窗口画面处理工具,也是 [Blinue/Magpie](https://github.com/Blinue/Magpie) 的非官方实验分支。它在 Magpie 的窗口缩放与效果系统基础上,探索 DLSS、XeSS、FSR、RTX Video 等技术在游戏、视频和其他窗口内容中的应用。 -## 版本与安装 +程序捕获目标窗口的画面,按用户配置的效果组处理,再以全屏或窗口形式显示。目标应用无需为这些效果单独集成接口。本项目不代表 Magpie 官方,实验分支的问题请在本仓库反馈。 -0.6.5 已进入本地发布包准备阶段,完整说明见 [0.6.5 Release Note](docs/RELEASE_NOTES_v0.6.5-experimental.md)。已公开版本以 [GitHub Releases](https://github.com/SAOG0721/Magpie/releases) 为准。 +## 主要功能 -完全退出 Magpie,将 `Magpie-Experimental-x64.zip` 完整解压到新目录后运行 `Magpie.exe`。不要只替换 EXE,也不要复制旧效果目录或已经移除的深度组件。普通设置位于 `%LOCALAPPDATA%\Magpie\config\v4\config.json`;便携设置位于程序目录的 `config\config.json`。升级前备份配置和旧安装。 +### 图像处理与帧生成 -## 效果组与参数 +| 功能 | 效果示例 | 用途 | +| --- | --- | --- | +| 空间缩放与锐化 | Lanczos、FSR 及其他 MagpieFX 效果 | 放大窗口内容,调整清晰度与细节表现 | +| 实验性时序超分辨率 | DLSS SR、FSR 2/3/4、XeSS SR | 探索从捕获画面和估算运动信息进行图像重建 | +| AI 画面调整 | DLSSNR | 同分辨率 SDR 画面处理,调整色调、结构、阴影、反射与辉光 | +| 视频增强 | RTX Video | 视频超分辨率与降噪,改善低分辨率或压缩内容的观感 | +| 帧生成 | DLSSFG、XeSSFG | 在真实帧之间生成中间画面,提高视觉流畅度 | -“缩放模式”现称为“效果组”,一个组可以组合多个效果器。新配置默认包含 Lanczos、FSR、RTX Video VSR Ultra、DLSSFG、XeSSFG、DLSSNR,默认选择 Lanczos。原有自定义组保持保留。 +实验效果的可用性取决于显卡、驱动、运行组件和具体效果组合。各版本包含的效果及硬件要求见 [Release 说明](https://github.com/SAOG0721/Magpie/releases)。 -程序内置效果组包含 DLSSFG、XeSSFG 和 DLSSNR;可在效果组页面通过重置恢复默认组。 +### 效果组与参数调节 -参数修改自动保存。工具栏参数页按当前会话标记“实时”“重启”“自动重启”;后两者的区别是手动应用与编辑结束后自动重建。DLSSNR 核心和上游图像的原实时参数会触发完整停用,等待 500 毫秒再重新启用;残差合成参数保留原有实时行为。连续编辑合并处理,手动停用会取消等待中的重启。 +一个**效果组**可以按顺序组合多个效果器,并保存各自的参数和缩放设置。可以使用内置效果组,也可以为不同应用创建自己的组合。 -在 170 毫秒内双击滑条可恢复效果器自身默认值。参数支持分组、下拉选项以及简体/繁体中文显示。保存失败或目标值尚未应用时,参数页会区分实际值与修改值。 +工具栏中的参数面板支持边看画面边调节,修改自动保存。控件会标明参数是实时生效,还是需要重新启用效果组;双击滑条可恢复效果器自身的默认值。参数支持分组、下拉选项和中文翻译。 -## 工具栏与帧节奏 +### 对比、性能监测与帧同步 -| 功能 | 默认快捷键 | -| --- | --- | -| 性能监测 | Alt+Shift+P | -| 效果参数 | Alt+Shift+E | -| 截屏 | Alt+Shift+S | -| 固定工具栏 | Alt+Shift+F | -| 对比 | Alt+Shift+C | +工具栏提供原图/处理后对比、各效果器耗时与帧率监测、截屏和参数调节,并支持自定义快捷键。启用帧生成时,可以分别查看输出帧率和真实帧率,帮助判断效果与性能开销。 -快捷键可在主页收起项中修改。“对比”显示原图时效果继续处理;角标停留 2 秒后以 500 毫秒淡出。启用 FG 后帧率支持“输出/真实帧”显示。 +Front Edge Sync 用于控制 Magpie 的帧提交节奏,启用 FG 时控制补帧前的真实帧输入。源程序仍需配合限帧,同步等待可能增加延迟;设置方法见 [帧同步使用说明](docs/FRAME_SYNC_GUIDE.md)。 -Front Edge Sync 默认开启,目标为 60 FPS,控制 FG 之前的基础帧节奏;目标程序也需要配合限帧。可能增加延迟。FrameRate Filter 默认跟随该设置,关闭同步后可自定义。完整解释见 [帧同步说明](docs/FRAME_SYNC_GUIDE.md)。VRR 当前隐藏并停用,HDR 尚未实现。 +## 下载与安装 -## 光流与兼容性 +1. 从 [GitHub Releases](https://github.com/SAOG0721/Magpie/releases) 选择版本,阅读该版本说明并下载主包 `Magpie-Experimental-x64.zip`。 +2. 完全退出正在运行的 Magpie,将主包完整解压到一个新目录。 +3. 运行其中的 `Magpie.exe`,选择效果组和目标窗口,使用主页所示快捷键启用效果。 -DLSS SR、FSR 2/3/4 和 XeSS SR 各提供一个入口,支持不使用/AMD OF/NVOF。独立 Zero MV、Optical Flow 和 metadata-only jitter 入口已合并,旧配置自动迁移。DLSSNR、DLSSFG 使用 NVOF;XeSSFG x2 可使用 AMD OF/NVOF,XeSS Multi-FG 当前仅支持 AMD OF 或不使用光流。 +升级前请将需要保留的设置和截图备份到程序目录之外,并按对应 Release 的说明处理旧安装和配置。可选运行组件、DLL 选项及辅助工具的用途和使用方法也以该版本说明为准。GitHub 自动生成的源码压缩包用于开发,不是可直接运行的程序包。 -多个消费者共享实际申请中的同一提供者:NVOF 优先,再 AMD OF,使用选定来源实际申请中的较高档位。NVOF 最高质量为 2×2 Slow,开销可能很高;均衡档继续标注推荐。 +## 使用与兼容性 -Magpie 没有游戏引擎原生深度、运动矢量、曝光或 UI 分离信息。需要深度的接口使用全零纹理,Motion 从捕获颜色估算,不能等同于游戏原生 DLSS/FSR/XeSS 集成。同一组仅使用一种 FG,避免与 Smooth Motion 等其他补帧叠加。 +- 发布主包面向 Windows x64,需要支持 DirectX 11 的显卡;具体 AI 效果可能有更高要求。 +- Magpie 从完整窗口画面进行处理,无法取得游戏引擎原生的完整运动矢量、深度、曝光和独立 UI 信息。估算光流可以辅助部分效果,但不能等同于游戏原生 DLSS/FSR/XeSS 集成。 +- 图像处理可能同时影响文字和 UI;时序效果也可能产生拖影或其他瑕疵。可通过“对比”判断效果是否适合当前内容。 +- 同一效果组使用一种帧生成效果,避免与其他补帧系统叠加。生成帧率不等于游戏的真实渲染帧率,也不代表输入响应速度同比提升。 -## 排错与开发 +## 问题反馈 -优先查看主页“最近一次问题”的详情与日志。Release 配套提供 NGX OTA 开关和 DLSSNR DLL 选项;用途与限制见 Release Note。源码默认关闭可选专有后端;本机 SDK/运行时通过不入库的 `src/BuildOptions.props.user` 配置。 +遇到问题时,先查看主页“最近一次问题”的处理建议和详细信息,或打开日志目录。向 [Issues](https://github.com/SAOG0721/Magpie/issues) 反馈时,请提供程序版本、显卡与驱动、效果组、输入/输出分辨率、复现步骤和相关日志。 -- [依赖、第三方声明与构建边界](docs/THIRD_PARTY_AND_REDISTRIBUTION.md) -- [实验开发文档](docs/experimental/README.md) -- [构建和发布脚本](scripts/Build-Release.ps1) +通用使用问题也可参考 [Magpie 上游 FAQ](https://github.com/Blinue/Magpie/wiki/FAQ);本分支特有的实验效果和兼容性问题请在本仓库讨论。 + +## 开发与文档 + +项目包含 Magpie 应用、MagpieFX 效果及实验性原生效果后端。源码构建默认关闭可选专有后端,相关 SDK 和运行组件需要另行准备;本机路径通过不入库的 `src/BuildOptions.props.user` 配置。 + +- [第三方依赖、许可与构建边界](docs/THIRD_PARTY_AND_REDISTRIBUTION.md) +- [实验功能的设计与开发记录](docs/experimental/README.md) +- [构建和打包脚本](scripts/Build-Release.ps1) - [MagpieFX 效果格式](docs/MagpieFX.md) -源码沿用 [GPLv3](LICENSE)。第三方运行时、SDK、模型及本地配置不进入源码仓库。 +## 贡献与致谢 + +本项目建立在 [Blinue/Magpie](https://github.com/Blinue/Magpie) 及其贡献者的工作之上,也感谢提交代码、翻译、设计建议和测试反馈的参与者。 + +- [HexBen123](https://github.com/HexBen123):早期深度估算性能优化与 TensorRT 集成指导,以及 [DLSSNR 参数本地化提案](https://github.com/SAOG0721/Magpie/pull/16)。 +- [Kristijan1001](https://github.com/Kristijan1001):[缩放期间实时编辑效果参数的参考实现](https://github.com/SAOG0721/Magpie/pull/4),为本项目参数面板的设计与实现提供了参考。 + +欢迎通过 Issues 和 Pull Requests 参与;代码贡献记录见 [Contributors](https://github.com/SAOG0721/Magpie/graphs/contributors)。 + +## 许可 + +Magpie 派生源码采用 [GPLv3](LICENSE)。第三方 SDK、模型和运行组件适用各自的许可证,详见 [第三方组件与再分发说明](docs/THIRD_PARTY_AND_REDISTRIBUTION.md)。 diff --git a/docs/diagnostics/20260906-full-chain-audit.md b/docs/diagnostics/20260906-full-chain-audit.md new file mode 100644 index 000000000..8a52bc4e6 --- /dev/null +++ b/docs/diagnostics/20260906-full-chain-audit.md @@ -0,0 +1,53 @@ +# Full-chain HDR/SDR audit + +This audit treats the current research tree as a multi-boundary rendering +system. Each boundary is required to preserve an explicit resource and color +contract. A successful SDK call does not count as an image result. + +## Boundary inventory + +1. Profile/config: `enableHdrCompatibility` is persisted and reaches + `ScalingOptions::IsHdrCompatibilityEnabled()`. +2. Capture: `FrameSourceBase` selects the capture surface and, when HDR is on, + invokes `HdrCaptureProcessor` to produce canonical FP16. +3. Canonical handoff: `Renderer` creates an `HdrFrame` around the capture + output and refreshes effect boundaries. +4. Effect boundary: `EffectDrawer` allocates route formats, converts input, + executes every production pass, converts output, and returns canonical FP16. +5. Native boundary: native backends receive route-compatible resources and + their own temporal/auxiliary contracts. +6. Publication: backend output enters shared presentation textures and + pass-through reference textures. +7. Frontend: shared textures are copied to presenter resources and drawn to the + swap chain or composition surface. +8. Export: screenshots/readback select a texture and encode it using the + matching format contract. + +## Current failure evidence + +- Runtime logs show adapter dispatch and native evaluation, while no stage + emits actual pixel statistics or a readback image. +- The research tree differs from the SDR baseline in the common drawer and + native factory. HDR-disabled behavior therefore needs an explicit regression + gate before HDR results are trusted. +- A pass-through/reference surface is a distinct publication branch. It must + use the same output color contract as the processed branch. +- Route metadata currently names formats and profiles, while the running + effect descriptor may still allocate an incompatible format internally. The + bridge must compare both values at runtime. + +## Acceptance gate for the real bridge + +For every configured effect and both HDR states, the bridge must record: + +- exact source and output `EffectDesc`; +- selected route and adapter profile; +- every texture's DXGI format, dimensions, bind flags and state; +- actual pass dispatch count; +- GPU readback statistics and saved image hash; +- native backend result when present; +- presentation texture statistics. + +Any missing stage, empty/black readback, invalid finite count, unchanged hash, +or route/descriptor mismatch is a failed row. No aggregate pass result may +hide a failed effect row. diff --git a/docs/diagnostics/20260906-hdr-sdr-chain-failure-report.md b/docs/diagnostics/20260906-hdr-sdr-chain-failure-report.md new file mode 100644 index 000000000..6dd9454fa --- /dev/null +++ b/docs/diagnostics/20260906-hdr-sdr-chain-failure-report.md @@ -0,0 +1,23 @@ +# HDR / SDR chain failure report + +## Evidence + +- Runtime logs show `HDR diagnostics` and repeated adapter dispatches, so the HDR front end is active. +- Runtime logs do not show a texture readback or presentation-pixel statistic; visual output cannot be inferred from successful `EvaluateFeature` calls. +- The prior external route report produced identical values for every effect and was removed because it did not execute production code. +- `Magpie-src` still contains separate legacy `FSR2ZeroMVUpscaler`, `FSR3ZeroMVUpscaler`, and `XeSSZeroMVUpscaler` implementations. The research tree replaced these with different classes while retaining legacy effect names in configuration. + +## Confirmed regression sources + +1. The research `NativeEffectBackendFactory` routes legacy ZeroMV/Jitter/OpticalFlow effect IDs through the new SR wrappers. This changes the SDR resource and auxiliary-input contract even when HDR compatibility is disabled. +2. `EffectDrawer` adds an HDR wrapper around every effect, while the original SDR drawer must remain byte-for-byte equivalent when HDR is off. Any stale HDR boundary or `_hdrOutput` resource can therefore affect the common output pointer. +3. `PassThroughFrames` and the processed publication path have separate presentation conversions; both need an identical output contract before comparison mode is considered valid. +4. `HdrSurfaceAdapter` is a shared compute path. Its constant-buffer layout, input/output descriptors, transfer mode, and dispatch completion must be validated by GPU readback before any effect result is trusted. + +## Required correction order + +1. Restore the original SDR backend class selection and `EffectDrawer` execution path as the off-state baseline. +2. Add a production-owned test bridge inside `Magpie.Core` that runs one captured texture through the actual `EffectDrawer` and reads back each boundary texture. +3. Re-enable HDR per boundary only after the off-state bridge passes for every configured effect group. +4. Validate `HdrCaptureProcessor`, `HdrSurfaceAdapter`, pass-through, shared publication, presenter, and screenshot export as separate GPU stages. +5. Mark a matrix row passed only when the production path returns a non-black, finite image whose hash differs from the source and whose stage metadata matches the selected route. diff --git a/docs/diagnostics/pipeline-audit.md b/docs/diagnostics/pipeline-audit.md new file mode 100644 index 000000000..e95ad66f6 --- /dev/null +++ b/docs/diagnostics/pipeline-audit.md @@ -0,0 +1,185 @@ +# Magpie HDR/SDR 全链路源码审计 + +审计范围:`Magpie-0.6.5-research` 当前源码、`Magpie-src` 基线、需求文档 +`docs/experimental/HDR_COMPATIBILITY_ARCHITECTURE.md` 与 +`HDR_MECHANICAL_IMPLEMENTATION_REQUIREMENTS.md`,以及 +`E:\Magpie-0.6.5-build\bin\x64\Release\logs\magpie.log` 的实际启动记录。 + +本报告属于 review 模式,只读分析。报告中的“修复”表示应采用的源码改动顺序,当前文件没有改动生产代码。 + +## 结论摘要 + +当前黑屏、HDR 过暗和关闭兼容后部分效果失效,来自多个相互叠加的合同断裂: + +1. HDR 捕获的数值域与后续适配器声明不一致。WGC FP16 线性 scRGB 在捕获处理器中保持相对 scRGB 数值,后续 `HdrSurfaceAdapter` 却按绝对 nits canonical 解释,并在发布阶段再次除以 80。 +2. `FrameSourceBase::GetOutput()` 在会话初始化和首帧到达前返回原始 `_output`,首帧处理后切换为 HDR processor 的 canonical texture。`Renderer::_BuildEffects()` 和 `EffectDrawer::_hdrInputSource` 在切换发生前已经保存了原始指针,效果链随后读取错误的资源语义。 +3. HDR `EffectDrawer` 把 route 的格式直接写入 `_textures[0]`/`_textures[1]`,生产 shader CSO 仍按照 `EffectDesc` 编译时的原始纹理格式生成。SRV/UAV 的实际格式因此可能与 shader 类型不匹配,输出纹理保留初始化清零值,最终呈现为黑屏。 +4. research 分支的 `NativeEffectBackendFactory` 把基线的 ZeroMV/Jitter/OpticalFlow 专用 backend 合并到普通 SR wrapper,改变了 SDR 及 HDR 关闭状态的运动、抖动、深度和辅助资源合同。 +5. 前端发布链新增了 FP16/scRGB、共享环和 pass-through 多重转换,但 `_CopySceneToTarget()` 只做 `CopyResource`,没有验证源/目标格式或输出合同;任一阶段失败后,前端仍可能提交清零的稳定纹理。 +6. 运行日志只证明了初始化、adapter dispatch、DLSSNR Evaluate 和“首帧提交”,没有任何最终纹理读回或像素统计,无法把当前图像判定为有效。 + +## 需求合同 + +需求文档规定:HDR 关闭时保持原有 SDR 捕获、效果、backend handoff、publication 和 presenter 语义;HDR 开启时,捕获之后统一形成 canonical `R16G16B16A16_FLOAT`,每个效果边界执行“canonical → route input → 生产效果 → route output → canonical”,最后只在 presentation terminal 编码到目标显示协议。捕获方式不能因 HDR 开关改变,纹理格式不能单独推断颜色含义,route 选择必须来自结构化协议,未知 route 必须显式走兼容回退并记录诊断。需求还要求 terminal frame generation 单独建模,不能把普通 FP16 链直接交给不接受 FP16/scRGB 的 backend。 + +## 1. 捕获与 canonical 生命周期 + +### 1.1 指针生命周期断裂(最高优先级) + +`FrameSourceBase.h:50-54` 的 `GetOutput()` 在 `_hdrEnabled` 且 `_hdrProcessor.GetCanonicalTexture()` 存在时返回 canonical,否则返回 `_output`。`FrameSourceBase.cpp:43-66` 在 `_Initialize()` 阶段只创建并准备 processor;真正填充 canonical 发生在 `Update()` 的 `FrameSourceBase.cpp:78-110`。因此初始化顺序是: + +```text +FrameSource::_Initialize -> raw _output +Renderer::_BuildEffects -> 读取 raw _output +FrameSource::Start -> 首帧到达 +FrameSource::Update -> 填充 canonical +FrameSource::GetOutput -> 改为 canonical +``` + +`Renderer.cpp:1569-1579` 在首帧以前取得 `inOutTexture` 和 `initialHdrFrame`;`EffectDrawer.cpp:89-105` 把传入纹理保存到裸指针 `_hdrInputSource`。首帧以后 `Renderer` 的 `_frameSource->GetOutput()` 已经是另一张 texture,而 drawer 仍从旧 `_hdrInputSource` 读取。这个状态同时破坏普通 HLSL drawer、native backend 输入、Frame Guidance 输入和 `_CreateSharedTexture()` 的输出合同。 + +### 1.2 直接修复方案 + +把 canonical texture 的对象生命周期前移到 FrameSource 初始化阶段,并让 `GetOutput()` 从初始化到销毁始终返回同一张 canonical texture;首帧以前只保持 `hdrFrameReady=false`,不允许 backend 运行。`_output` 继续作为捕获 API 的原始接收面,processor canonical 作为稳定的交付面。所有 duplicate 检查可以继续针对 `_output`,但效果链、Frame Guidance、DLSSNR 和发布链必须只使用 `GetOutput()` 返回的稳定 canonical。resize 时先重建 canonical,再以同一指针更新所有 drawer/backend,最后才恢复运行。 + +## 2. 捕获数值域与适配器数值域 + +### 2.1 当前实现的实际变换 + +`GraphicsCaptureFrameSource.cpp:79-87` 在 HDR 模式创建 FP16 `_output`,`GraphicsCaptureFrameSource.cpp:103-112` 将其声明为线性 scRGB、scene-referred、80 nit reference white。运行日志 `20:11:40.181` 也确认:`sourceFormat=10`、`sourceTransfer=Linear`、`sourceRange=SceneLinear`、`referenceWhite=80`。 + +`HdrCaptureProcessor.cpp:54-61` 当前 `ToCanonical()` 对 `inputTransfer == Linear` 直接保留采样值,对 sRGB/HLG 乘 `referenceWhiteNits / 80`,对 PQ 除以 80。这个实现形成的是“线性 scRGB、1.0 对应 80 nit”的 canonical 数值域。 + +`HdrSurfaceAdapter.cpp:118-121` 的 mode 4 又把值直接当作绝对 nits并执行 `value / 80`;`Renderer.cpp:3032-3051` 在每次 HDR 发布前调用该 mode 4。于是 WGC 的 scRGB `1.0` 被发布为 `0.0125`,白场整体变暗约 80 倍。反向的 SDR 兼容路径也按 `referenceWhiteNits / 80` 参与归一化,数值域与 capture canonical 的定义没有统一。 + +### 2.2 需求对齐方案 + +需要在架构层固定一种 canonical 语义,并让所有代码使用同一合同。按照需求文档中对 Windows scRGB 的描述,最稳定的选择是 canonical 使用线性 scRGB 相对值:`1.0 == 80 nit`,HDR 高光可大于 1。这样: + +- WGC FP16 Linear 输入直接复制到 canonical,不再做“绝对 nits”解释。 +- 8-bit sRGB 输入先做 sRGB EOTF,得到线性相对值,再写入 canonical。 +- PQ 输入先解码到 nits,再除以 80 写入 canonical。 +- `ConvertHdrToScRgb()` 变为同域复制/必要的 primaries 转换,不能再次除以 80。 +- SDRCompatible 的 tone-map 以 canonical 相对值为输入;inverse 也返回相对 scRGB 值。 +- 日志字段明确写 `canonicalValueDomain=scRGB-relative` 或采用另一套全局定义,禁止“注释写 absolute nits、shader 按 scRGB”这种混合状态。 + +当前代码和注释在 `FrameSourceBase.cpp:250-253`、`HdrSurfaceAdapter.h:42-44`、`PassThroughFrames.cpp:29-33` 使用 absolute-nits 表述,而 shader 行为使用 scRGB-relative;这些说明必须与最终统一语义同步。 + +## 3. EffectDrawer 与 shader descriptor 合同 + +### 3.1 格式替换发生在 shader 编译之后 + +`EffectCompiler.cpp:1221-1233` 使用 `EffectDesc::textures[*].format` 生成每个 pass 的 `Texture2D` 和 `RWTexture2D` 类型。CSO 已经绑定了这些编译时类型。`EffectDrawer.cpp:92-114` 在运行时却用 `HdrEffectBoundary::SelectedRoute()->inputFormat/outputFormat` 创建 `_textures[0]` 和 `_textures[1]`,这两个格式可能与 `desc.textures[0/1].format` 不同。`EffectDrawer.cpp:645-674` 随后仍按原 pass texture 索引获取 SRV/UAV,没有重编 shader,也没有验证 route 格式与 `EffectDesc` 类型兼容。 + +典型冲突是 Group-C 的 `SdrRoute()` 在 `EffectProtocolCatalogC.h:10-31` 声明输入为线性、输出为 sRGB,而实际 HLSL effect 可能是 `R8G8B8A8_UNORM` 或 FP16;route 描述本身没有把“适配器 scratch texture”和“生产 shader texture”分开。对于多通道/中间纹理效果,替换 0/1 还会影响 pass 之间的 alias 和尺寸推导。 + +### 3.2 黑屏机制 + +`EffectDrawer::Draw()` 在 `EffectDrawer.cpp:234-247` 中,如果 `PrepareHdrInput()` 失败只记录错误并直接返回;如果 pass dispatch 后 `CompleteHdrOutput()` 失败,函数仍结束,`_hdrOutput` 保持初始化清零值。`Renderer::_BackendRender()` 在 `Renderer.cpp:2886-2920` 对 native 失败也只记录后继续;最后仍调用 `_CompleteBackendFrame(effectsOutput, ...)`。这使任何边界错误都变成“发布黑帧”,而不是阻止提交并保留上一张有效帧。 + +### 3.3 修复方案 + +保留生产 `EffectDesc` 的 texture 格式给 shader 和 `_textures[]`,为 route 转换单独建立 `routeInputScratch`、`routeOutputScratch`。执行顺序固定为: + +```text +canonical input + -> adapter scratch (route format) + -> production shader/backend using its declared EffectDesc resources + -> adapter scratch output + -> canonical output +``` + +当 route 格式与生产 shader 格式相同,可以复用资源;只有经过结构化兼容检查才允许复用。每个 pass 必须验证 SRV/UAV 创建成功、尺寸和 format 与 CSO descriptor 相符。`PrepareHdrInput()` 或 `CompleteHdrOutput()` 失败时,当前帧应终止发布并保留上一帧,不能把清零的 `_hdrOutput` 交给 publication。 + +## 4. Native backend 工厂与 SDR 回归 + +`Magpie-src` 基线的 `NativeEffectBackendFactory.cpp:111-159` 使用: + +- `DLSSSRUpscaler` 只处理 `DLSS_SR` 及其显式 legacy 参数,并区分 `isJitter`、`useMotionVectors`、`useEstimatedDepth`; +- `FSR2ZeroMVUpscaler` 处理 FSR2 ZeroMV/Jitter/OpticalFlow; +- `FSR3ZeroMVUpscaler` 处理 FSR3/FSR4 ZeroMV/Jitter/OpticalFlow; +- `XeSSZeroMVUpscaler` 处理 XeSS ZeroMV/Jitter/OpticalFlow。 + +research 版 `NativeEffectBackendFactory.cpp:78-135` 将这些 ID 全部归入 `DLSSSRUpscaler`、`FSR2Upscaler`、`FSR3Upscaler` 或 `XeSSUpscaler` 的统一分支,并用 `ParseOpticalFlowRequest()` 只对部分路径设置 motion。这个改动改变了 legacy backend 的初始化参数、depth/motion fallback、jitter 开关、输出资源和时序历史,违反需求中“HDR 关闭时 SDR 行为不变”。 + +同文件 `:43-53` 还只识别 `FrameGuidance_Motion` 与 `FrameGuidance_Confidence`,基线 `:41-54` 同时支持 `Depth` 和 `DepthResidual`。这会让诊断效果本身失效,进一步降低对 Frame Guidance 和 DLSSNR 的可观测性。 + +修复顺序:先完整恢复基线 include 和分派分支,保持所有 legacy 类的原始构造参数;HDR 适配器通过 `NativeEffectBackend` 外层 context 接入,不能替换 backend 类型。对每个 native 类分别声明支持的 route,未验证的 HDR route 走显式 SDRCompatible scratch,SDR 关闭时完全跳过 HDR context。 + +### 4.1 Bicubic 追加后的越界 + +`Renderer::_BuildEffects()` 在 `Renderer.cpp:1655-1669` 追加 Bicubic 后调用 `_UpdateHdrEffectBoundaryContexts()`。该函数在 `Renderer.cpp:1714-1724` 遍历 `_effectDrawers.size()`,却用同一索引读取 `_runtimeEffectOptions[i]`。追加 Bicubic 时 drawer 数量比 runtime option 数量多一项,最后一次迭代越过 `_runtimeEffectOptions` 末尾。`_ResizeEffects()` 在 `Renderer.cpp:2088-2134` 也会在追加/移除 Bicubic 后重新调用该函数。此越界足以损坏 route context、backend 指针或后续发布状态,并直接解释“有缩放比例/窗口尺寸时黑屏”的不稳定表现。修复方式是按 runtime effect 数量更新真实效果,另为 Bicubic 构造明确的独立 boundary,或让 Bicubic 使用其 drawer 自带的 canonical context,禁止用 runtime option 数组越界索引。 + +### 4.2 Group-C R8 route 的 transfer 字段方向错误 + +`EffectProtocolCatalogC.h:10-31` 的 `SdrRoute()` 将 `inputTransfer=Linear/inputRange=SceneLinear`、`outputTransfer=SRGB` 写入所有 R8 效果。`EffectDrawer::PrepareHdrInput()` 实际把 `route->inputTransfer` 作为 `ConvertHdrToSdr(..., outputTransfer)` 参数 (`EffectDrawer.cpp:506-514`),所以这些效果收到线性 R8 值;`CompleteHdrOutput()` 又把 `route->outputTransfer` 作为 `ConvertSdrToHdr(..., inputTransfer)` (`EffectDrawer.cpp:534-542`),按 sRGB 解码同一线性值,造成二次 EOTF、过暗和颜色偏移。R8 SDR-compatible route 的输入/输出 transfer 应描述 backend 真实的编码,通常为 `SRGB/SRGB`,range 为 `Full/Full`;route 字段方向和调用语义需要统一后再推广到 Group-A/B/C。 + +## 5. Publication、frontend 和 presenter + +### 5.1 新增的多重颜色边界 + +research 版在 `Renderer.cpp:2310-2426` 将共享 publication texture 改为 HDR 时 FP16,在 `Renderer.cpp:3032-3051` 先把 canonical 送入 `_hdrPresentationTexture`,再复制到 shared texture;`Renderer.cpp:617-754` 前端又复制到 stable base/presented base;`Renderer.cpp:757-774` 最后直接复制到 presenter frame。`AdaptivePresenter.cpp:33-84` 和 `:416-424` 也把 swap chain/DirectComposition surface 改成 FP16,并设置 scRGB 色彩空间。 + +`_CopySceneToTarget()` 没有检查 `scene`、`target` 的 format、尺寸、sample count、bind flags,也没有处理“只允许同格式 CopyResource”的失败结果。`CopyResource` 的 HRESULT 不返回给调用者,失败后 presenter 仍可能提交刚刚清空的目标。 + +### 5.2 Pass-through 分支 + +`PassThroughFrames.cpp:11-35` 的 reference shader按 output 尺寸采样 input,并在 HDR 时执行 `/80`。`InitializeBackend()` 以 `outputDesc` 决定 reference 尺寸 (`:65-71`),但 input 可能是捕获尺寸、output 可能是最终效果尺寸;当两者不同,采样 UV 与边界不一致。`ReferenceConstants` 的 exposure、sdrWhiteNits、shoulder 在 shader 中没有使用 (`:94-104`),导致诊断参数与实际行为脱离。 + +需求要求 pass-through 与 processed publication 使用同一输出颜色合同。当前 reference branch 自己做了一套 `/80`,processed branch 另做 canonical→scRGB,两个分支无法保证逐点可比。 + +修复方案:pass-through 直接保存 canonical stable frame;在唯一的 presentation encoder 处与 processed frame 使用相同的颜色转换。reference shader只负责尺寸/滤波和 alpha,禁止重复颜色缩放。输入、输出尺寸不同时采用明确的 source-to-destination sampling contract,并记录 source/destination descriptor。 + +### 5.3 Screenshot/export 也跨越了错误的格式边界 + +`Renderer::_TakeScreenshotImpl()` 对 effect output 使用 `_effectDrawers[effectIdx].GetOutputTexture()` (`Renderer.cpp:3272-3275`),而 HDR drawer 的 canonical 对外输出是 `GetExternalOutputTexture()`;随后函数把 effect descriptor 的原始 `EffectIntermediateTextureFormat` 当作读回格式。显示图导出分支还把 frontend FP16/scRGB texture 按 `R8G8B8A8_UNORM`/PNG 语义处理 (`Renderer.cpp:3263-3271`)。这会把 HDR 纹理以错误的通道/数值域编码,产生黑图或过暗图,即使 presenter 本身已经提交成功。导出必须先选择 canonical/presentation domain,再执行一次明确的 HDR-to-SDR PNG 编码;中间 pass 才使用其真实 descriptor 格式。 + +## 6. 运行日志证据 + +`magpie.log` 的 20:11:40 会话记录: + +```text +sourceFormat=10 +sourceTransfer=Linear +sourceRange=SceneLinear +sourceReferenceWhiteNits=80 +canonicalFormat=10 +selectedAdapterProfile=DirectFP16 +selectedRouteId=(not set) +``` + +随后每帧出现: + +```text +HDR adapter dispatch: mode=0 input=10 output=28 +HDR adapter dispatch: mode=0 input=28 output=10 +HDR adapter dispatch: mode=4 input=10 output=10 +DLSSNR STATUS ... sourceFormat=28 ... experimentalHdrPath=false +First frontend frame submitted by regular renderer +``` + +这组日志证明 WGC 首帧、DLSSNR Evaluate 和 presenter 首次提交都发生了;`selectedRouteId=(not set)` 说明 capture diagnostics 没有关联具体效果 route;全日志没有最终 publication texture 的 min/max/mean、NaN/Inf、读回 hash 或截图路径。`First frontend frame submitted` 只表示 API 提交成功,不能证明像素内容有效。 + +## 7. 点对点修复顺序 + +1. **恢复 SDR 基线**:恢复 `NativeEffectBackendFactory` 的 legacy backend 类和 Diagnostics Depth/DepthResidual 分支;让 HDR 关闭时 `EffectDrawer`、Renderer publication、presenter 与 `Magpie-src` 保持原始行为。 +2. **稳定 FrameSource 输出指针**:canonical texture 在初始化时创建并从始至终作为 `GetOutput()`;首帧 ready 之前禁止 backend draw。 +3. **统一 canonical 数值域**:在代码、shader、注释、metadata 和日志中选择唯一的 scRGB-relative 或 absolute-nits 语义;本审计建议采用 `1.0 == 80 nit` 的 scRGB-relative 语义,以匹配 Windows WGC/Advanced Color。 +4. **拆分 route scratch 与 production textures**:route 转换资源独立于 `EffectDesc` 生产资源,禁止运行时改写已编译 shader 的 texture format。 +5. **统一唯一 presentation encoder**:processed 与 pass-through 都先保留 canonical,只有 publication→presenter 边界做一次 scRGB/HDR10 编码;所有 Copy/Present 调用检查 HRESULT 和 descriptor。 +6. **失败帧隔离**:任一 effect/backend/adapter/publish 阶段失败时丢弃当前帧并保留上一张有效帧,禁止清零纹理继续发布。 +7. **真实生产 readback bridge**:bridge 直接调用生产 `FrameSourceBase`、`EffectDrawer`、native backend、publication 和 presenter 资源;逐阶段保存 GPU readback,记录 format/size/finite/min/max/mean/hash。没有完整阶段证据的矩阵行保持失败状态。 + +## 8. 验收条件 + +每个效果组、HDR 开关两种状态都必须满足: + +- capture method 与需求一致,首帧 `frameId=1` 使用真实捕获; +- effect shader CSO 的 descriptor 与实际 SRV/UAV format/size 一致; +- native backend 类型、辅助输入、jitter/depth/motion 语义与基线或已验证 route 一致; +- canonical 输入/输出在每个边界保持统一数值域,alpha 明确; +- publication、pass-through、presenter 只执行一次目标颜色编码; +- GPU readback 全部 finite、非全黑、没有 NaN/Inf,输出 hash 与输入 hash 不同; +- adapter、backend、publication、presenter 每一层都有实际执行记录; +- HDR 关闭矩阵与基线图像及路径通过回归比较;HDR 开启矩阵覆盖 SDR-compatible、DirectFP16、BoundedHDR、ConditionalFP16 和 terminal 分支; +- 任意一行失败都单独保留失败证据,不能用 aggregate 初始化成功代替图片通过。 diff --git a/docs/experimental/GROUP_A_HDR_ROUTES.md b/docs/experimental/GROUP_A_HDR_ROUTES.md new file mode 100644 index 000000000..c16f0de87 --- /dev/null +++ b/docs/experimental/GROUP_A_HDR_ROUTES.md @@ -0,0 +1,35 @@ +# Group-A HDR routes + +`GroupAHdrRoutes.{h,cpp}` is the effect-local provider for Anime4K, CAS, CRT, +CuNNy, CuNNy2, Diagnostics, FSRCNNX, FXAA and MLAA. It returns structured +`HdrFormatRoute` values plus a diagnostic description of auxiliary resources. +The provider is selected by the effect group prefix in the existing Renderer +boundary update. No effect-name branches were added to the shared dispatcher. + +Every group-A effect has a named route with explicit `R8G8B8A8_UNORM` backend +storage, sRGB/full-range bounded semantics and `ForceOpaque` alpha where the +shader writes alpha one. This is the per-effect SDR fallback for effects whose +public HDR contract is unresolved. The route is still effect-owned, so the +dispatcher can report the exact effect ID and auxiliary resource boundary. + +CAS exposes two declared options through the existing integer-choice parameter +syntax (`hdrFormat`): + +| Option | Route | Evidence/profile | Runtime condition | +|---|---|---|---| +| `0` | `R8G8B8A8_UNORM` | SDR/full-range/sRGB, `SDRCompatible` | Default and fully wired with current compiler | +| `1` | `R16G16B16A16_FLOAT` | linear/scene-linear, `ConditionalFP16`, reference implementation evidence | Experimental selection; current effect compiler still emits the CAS main surface as R8, so `EffectDrawer` format matching keeps this route out of the direct path until a CAS-specific FP16 compile variant is supplied | + +This preserves the distinction between a documented backend option and a +verified HDR-native path. CAS output alpha remains forced opaque in both route +descriptions, matching the HLSL writes. + +## Minimal core wiring + +`Renderer::_BuildEffects` preselects the effect-local route before texture +allocation, and `Renderer::_UpdateHdrEffectBoundaryContexts` refreshes it when +parameters change. `EffectDrawer` uses the selected route's input/output +format for HDR-mode working surfaces, which makes CAS option `1` an actual +FP16 surface path while preserving the existing R8 fallback for option `0`. +The shared dispatcher and conversion helpers retain their existing behavior; +the core wiring is limited to these two call sites and the provider registration. diff --git a/docs/experimental/HDR_COMPATIBILITY_ARCHITECTURE.md b/docs/experimental/HDR_COMPATIBILITY_ARCHITECTURE.md new file mode 100644 index 000000000..a54fb405d --- /dev/null +++ b/docs/experimental/HDR_COMPATIBILITY_ARCHITECTURE.md @@ -0,0 +1,344 @@ +# HDR Compatibility Architecture + +## Status and Scope + +This document is the design and implementation baseline for a self-maintained HDR-compatible Magpie fork. It records decisions reached from capture experiments and protocol research, together with the currently implemented generic work surface. Existing SDR behavior remains the compatibility baseline and remains semantically unchanged while HDR compatibility is disabled. + +The evidence record is [HDR_PROTOCOL_EVIDENCE.md](HDR_PROTOCOL_EVIDENCE.md) and its machine-readable companion [HDR_PROTOCOL_EVIDENCE.json](HDR_PROTOCOL_EVIDENCE.json). + +## User Configuration Switch + +HDR compatibility is controlled by one profile-level option in the user's normal/general configuration page. The persisted configuration key is: + +```json +{ + "enableHdrCompatibility": false +} +``` + +The runtime representation is an independent `ScalingFlags::EnableHdrCompatibility` bit with an `IsHdrCompatibilityEnabled()` accessor. This option controls the global capture-to-presentation HDR path; it is not a capture-method choice and it is not an effect-specific protocol selector. + +When enabled, the selected capture method remains unchanged and the first post-capture stage decodes the source color representation, normalizes exposure, and produces the canonical HDR working surface. When disabled, capture output enters the existing SDR effect and presentation path without the HDR front end or HDR boundary adapters. Effect-specific `R8`/`R10`/`FP16` selectors remain separate settings and are evaluated only after this global switch is enabled. + +## Goal + +The HDR path lets every effect participate in one predictable pipeline while allowing each backend to select the least lossy verified protocol it actually accepts. + +```text +HDR capture + -> decode and exposure normalization + -> canonical HDR working surface + -> per-effect protocol adapter + -> existing effect backend + -> per-effect protocol adapter + -> canonical HDR working surface + -> presentation compatibility encoding +``` + +The canonical inter-effect storage format is: + +```text +DXGI_FORMAT_R16G16B16A16_FLOAT +``` + +`R16G16B16A16_FLOAT` defines storage precision only. It does not define color meaning. Every HDR frame therefore carries a color description. + +```cpp +struct ColorDescription { + DXGI_COLOR_SPACE_TYPE dxgiColorSpace; + ColorPrimaries primaries; + TransferFunction transfer; + ColorRange range; + float referenceWhiteNits; + float displayPeakNits; + bool isSceneReferred; + bool isPreExposed; + bool isInferred; + float preExposure; + HdrMetadata metadata; +}; + +struct HdrFrame { + ID3D11Texture2D* texture; // R16G16B16A16_FLOAT in canonical stages + ColorDescription color; +}; +``` + +The working surface normally contains linear RGB in preserved source primaries. Primary conversion is explicit when a backend requires it. Texture format alone never proves Rec.709, Rec.2020, scRGB, PQ, or HLG semantics. + +## Non-Goals + +- Do not rewrite the existing SDR effect chain while HDR compatibility is disabled. +- Do not force every native backend, HLSL effect, or model to process FP16 internally. FP16 is the canonical inter-effect work surface; an effect boundary may select R8, R10, or FP16 when that route is declared and verified. +- Do not claim HDR support because a shader happens to compile with a floating-point texture. +- Do not replace the user's capture-method selection with a separate HDR-only capture method. +- Do not label a lossy SDR round trip as native HDR preservation. + +## User-Facing Control + +Add one global general setting: + +```text +HDR compatibility: Off / On +``` + +`Off` retains current capture, effect, backend handoff, and presentation behavior. `On` enables format-aware capture normalization and per-effect adapters. Effects expose a protocol selector only when more than one usable path has evidence; single-path effects remain selector-free. + +The current generic boundary implementation creates an effect-local input/output surface in the format declared by the existing effect description, performs the selected adapter operation around the effect, and returns a canonical FP16 surface to the next effect. Routes with no concrete evidence use the structured SDR-compatible fallback and emit diagnostics. When HDR compatibility is enabled, the shared publication ring and presenter use canonical FP16/scRGB surfaces, so the terminal path retains HDR values through frontend composition. When HDR compatibility is disabled, the existing R8 publication and presenter path remains active. + +## Capture Contract + +Every existing capture method retains its selection and window-acquisition behavior. HDR handling begins after it produces a texture. + +```text +selected capture method + -> captured texture + source DXGI format + source color-space metadata + -> HDR decode / source transfer handling / exposure normalization + -> canonical RGBA16F HdrFrame +``` + +The capture stage preserves source format and DXGI color-space data, decodes known PQ/HLG/scRGB/SDR transfers, preserves available primaries and metadata, and produces a visually correct HDR frame before the first effect. This prevents HDR code values from being sampled as SDR values and clipping highlights before any effect executes. + +For Graphics Capture, HDR mode requests an `R16G16B16A16_FLOAT` frame pool and treats the returned surface as linear scRGB. The other existing capture methods retain their established 8-bit BGRA capture surfaces; those surfaces are interpreted as inferred sRGB display-referred values because the capture operation has already selected that representation. Display PQ metadata supplies peak-luminance normalization and does not redefine the stored 8-bit code values. + +`R10G10B10A2_UNORM` alone is not an HDR declaration. The associated color-space contract determines whether it is HDR10/PQ or another representation. + +## Canonical Effect Boundary + +Every non-terminal effect follows this shape while HDR compatibility is enabled: + +```text +canonical RGBA16F HdrFrame + -> adapter input conversion + -> backend-specific input texture(s) + -> existing HLSL/native effect + -> backend-specific output texture(s) + -> adapter output conversion + -> canonical RGBA16F HdrFrame +``` + +The backend only sees resources allowed by its protocol. The rest of the pipeline only sees the canonical handoff surface. This isolates texture allocation, format conversion, color conversion, normalizers, alpha policy, and metadata handling from the effect implementation. + +```cpp +enum class EffectColorProfile { + DirectFP16, + BoundedHDR, + SDRCompatible, + Unknown, +}; + +struct EffectColorProtocol { + std::string id; + EffectColorProfile profile; + ColorSurfaceContract input; + ColorSurfaceContract output; + EffectColorAdapter adapter; + EvidenceLevel evidence; + uint8_t qualityRank; + uint8_t performanceRank; + bool userSelectable; + bool isPresentationTerminal; +}; +``` + +`isPresentationTerminal` is topology information, not a fifth color profile. Frame-generation paths often produce swap-chain frames and end a render branch instead of returning a normal inter-effect texture. + +## Adapter Profiles + +### Protocol selection at the effect boundary + +The canonical work surface and the backend protocol are separate concepts. Every HDR-compatible chain hands effects a canonical `R16G16B16A16_FLOAT` frame between adapters, while each concrete effect may expose more than one backend protocol option when evidence supports it. + +Each option records: + +```text +effectId +optionId +inputFormat +outputFormat +inputColorModel +outputColorModel +range +transferFunction +alphaMode +evidenceLevel +hdrNative +adapterProfile +``` + +`hdrNative` means that the selected representation carries the effect's HDR input semantics directly. It does not mean that every format accepted by the backend is HDR-native. A floating-point option may be HDR-native, an `R10G10B10A2_UNORM` option may be a bounded HDR10/PQ route, and an `R8G8B8A8_UNORM` option normally requires an SDR-compatible HDR adapter. + +The selector is shown only when the concrete effect has more than one reliable protocol option. The default order in HDR mode is: verified HDR-native FP16, verified HDR-native R10/HDR10, verified bounded floating-point, then SDR-compatible R8. In SDR mode the existing default and existing path remain unchanged. + +### DirectFP16 + +Use this profile when the upstream API or a verified reference path accepts a suitable linear floating-point HDR resource. + +```text +RGBA16F canonical frame + -> required primaries / transfer conversion only + -> FP16-capable backend + -> required output conversion only + -> RGBA16F canonical frame +``` + +No SDR tone mapping or U8 quantization belongs in this path. Typical candidates are DLSS SR, FSR2, FSR3 SR, FSR4, NIS, and XeSS SR. CAS has a reference FP16 path and remains conditional until local validation confirms the exact Magpie backend path. + +### BoundedHDR + +Use this profile when a backend accepts a bounded numeric domain, needs a display-specific HDR representation, or has an experimentally validated HDR normalizer. + +```text +RGBA16F canonical frame + -> reversible bounded-domain encoding E() + -> backend + -> inverse-domain decoding E^-1() + -> RGBA16F canonical frame +``` + +The encoding stores the curve identifier, reference white, normalization scale, peak/headroom, source transfer assumption, and inverse curve. A generic implementation must not hard-code one scale for all effects. + +DLSSNR uses this profile based on local experiments: + +```text +HDR -> bounded HDR normalization -> DLSSNR -> inverse normalization -> HDR +``` + +The normalizer preserves the tested HDR luminance relationships while placing model input in its bounded domain. It does not imply that DLSSNR has an officially published native HDR texture contract. + +### SDRCompatible + +Use this profile when the documented backend interface is SDR, UNORM, or U8-only. + +```text +RGBA16F canonical frame + -> primary conversion if required + -> HDR-to-SDR appearance mapping + -> SDR transfer encoding + -> quantize to backend format + -> backend + -> dequantize / SDR decode + -> SDR-to-HDR reconstruction policy + -> RGBA16F canonical frame +``` + +This path is compatibility-oriented. The SDR appearance mapping is not mathematically lossless, so output reconstruction is an approximation. The adapter keeps the color transform, tone-mapping function, quantizer, and reconstruction policy together as one named protocol. + +For U8 RGBA/BGRA backends, channel encoding normally is: + +```text +u8 = round(saturate(sdrEncoded) * 255) +``` + +RTX Video VSR and RTX Video Denoiser use interleaved BGRA or RGBA U8 GPU buffers. The `0..255` domain is a storage-code boundary, not a linear HDR luminance domain. + +### Unknown + +Use this profile when source evidence does not establish an upstream mapping or a texture/color contract. HDR mode selects an adapter only after a local test. The catalog retains the evidence gap so a later experiment can promote the entry deliberately. + +## Presentation-Terminal Effects + +Frame generation often binds to backbuffer and swap-chain resources. Model it as a terminal branch: + +```text +canonical RGBA16F HdrFrame + -> presentation-specific encoder + -> frame-generation backend + -> proxy swap chain / presenter +``` + +XeSS FG has the clearest known contract: + +```text +RGBA16F canonical HDR + -> PQ / BT.2100 encoding + -> R10G10B10A2_UNORM + -> XeSS FG + -> proxy swap chain presentation +``` + +XeSS FG requires HDR10 / BT.2100 with `R10G10B10A2_UNORM` for its documented HDR path and does not accept FP16 HDR or scRGB. The backbuffer, HUD-less texture, and UI texture agree on format, dimensions, and color space. The renderer prevents normal post-effects from being scheduled after a presentation-terminal backend unless a verified re-capture path exists. + +## Resource and Synchronization Model + +The HDR path allocates resources from declared surface contracts instead of a global R8 assumption. + +1. Keep one canonical FP16 texture pool per active render size. +2. Reuse canonical ping-pong surfaces for ordinary chains where dependencies permit. +3. Allocate protocol-specific scratch textures only around the effect that needs them. +4. Cache adapter pipelines by source contract, destination contract, dimensions, and shader parameters. +5. Preserve SRV/UAV/RTV state transitions at adapter boundaries so native effects keep their resource-state expectations. +6. Keep alpha semantics explicit. Effects that discard alpha write a defined value, normally `1.0`, before returning to the canonical chain. +7. Keep HDR metadata separate from texture metadata. A copy or shared handle does not preserve semantic color state on its own. + +Performance policy: FP16 stays at effect boundaries, while every backend uses the least lossy verified internal format it accepts. Neural filters and frame generation dominate the cost in most cases. Adapter allocations and conversions still need pooling and fusion because they occur every frame. + +## Effect Selection and Configuration + +Each descriptor declares candidate protocols, evidence level, input/output contract, auxiliary resource needs, terminal state, and adapter parameters. + +```text +Effect name + - Default protocol + - Candidate protocol list + - Evidence level + - Input/output format contract + - Required auxiliary resources + - Presentation-terminal flag + - Adapter parameters +``` + +Examples: + +- XeSS SR defaults to DirectFP16; a lower-bandwidth `R11G11B10_FLOAT` internal option belongs behind later verification and only when alpha is irrelevant. +- RTX Video VSR exposes an SDR/U8 path until a higher-precision public contract exists. +- DLSSNR exposes its tested bounded-HDR normalizer parameters after the initial defaults are stable. +- XeSS FG is bound to its HDR10 `R10G10B10A2_UNORM` terminal protocol and has no FP16 selector. + +## Validation Plan + +The user performs visual/game tests. The implementation records diagnostics sufficient to identify a wrong adapter choice. + +### Capture validation + +- Compare no-effect HDR output with the source window. +- Confirm highlight detail before the first effect. +- Confirm SDR windows remain unchanged while the global option is off. +- Log source DXGI format, DXGI color space, decode path, and canonical color description. + +### Per-effect validation + +- Verify input and output resources match the selected profile. +- Test dark detail, saturated red, UI white, specular highlights, and low-light gradients. +- Check alpha behavior on overlays and composition paths. +- Record GPU time for adapter input, backend, and adapter output separately. +- For SDR-compatible effects, compare against a no-effect HDR reference and record reconstruction loss. + +### Regression validation + +- HDR option off: unchanged SDR pipeline. +- HDR option on with no effects: correct capture and presentation. +- One effect at a time: contract and visual validation. +- Mixed chain: canonical FP16 handoff between non-terminal effects. +- Terminal frame generation: no invalid post-effect after presentation handoff. + +## Delivery Order + +1. Add format-aware capture normalization and `HdrFrame` while retaining the disabled SDR branch. +2. Add adapter infrastructure and diagnostics without changing individual effect backends. +3. Implement DLSSNR as the bounded-HDR vertical slice. +4. Add verified DirectFP16 effects: NIS, XeSS SR, DLSS SR, and FSR2/FSR3 SR/FSR4 where auxiliary inputs are valid. +5. Add SDR-compatible adapters: RTX Video VSR, RTX Video Denoiser, FSR1, xBRZ, NVIDIA Optical Flow, and SMAA. +6. Add terminal frame generation: XeSS FG, then FSR3 FG and DLSS FG after their external temporal contracts are resolved. +7. Promote conditional and unknown entries one by one through local texture-contract tests. + +## Decision Record + +- Canonical handoff: `DXGI_FORMAT_R16G16B16A16_FLOAT` plus explicit `ColorDescription`. +- SDR behavior: preserved while HDR compatibility is off. +- Capture: every selected capture method feeds one HDR normalization stage. +- Backend precision: selected per effect protocol, never globally forced. +- DLSSNR: bounded-HDR normalization with inverse normalization, based on local experiments. +- RTX Video VSR/Denoiser: SDR/U8 adapters with explicit `0..255` quantization. +- XeSS FG: HDR10 `R10G10B10A2_UNORM` terminal protocol outside the ordinary FP16 chain. +- Evidence policy: public API contracts, reference implementations, GitHub experiments, and local experiments remain separate evidence levels. diff --git a/docs/experimental/HDR_EFFECT_IMPLEMENTATION_CATALOG.md b/docs/experimental/HDR_EFFECT_IMPLEMENTATION_CATALOG.md new file mode 100644 index 000000000..f0495e805 --- /dev/null +++ b/docs/experimental/HDR_EFFECT_IMPLEMENTATION_CATALOG.md @@ -0,0 +1,290 @@ +# HDR Effect Implementation Catalog + +## Purpose + +This catalog translates the protocol evidence matrix into an implementation-facing list. It records accepted backend protocols separately from HDR-native protocols, which adapter profile HDR mode should choose, what conversion happens at the boundary, and which conclusions still need local testing. + +The authoritative source URLs and research notes are in [HDR_PROTOCOL_EVIDENCE.md](HDR_PROTOCOL_EVIDENCE.md) and [HDR_PROTOCOL_EVIDENCE.json](HDR_PROTOCOL_EVIDENCE.json). This catalog records integration decisions; it does not claim that current Magpie already supports these paths. + +## Profile Legend + +| Profile | Meaning at the HDR adapter boundary | +|---|---| +| DirectFP16 | Backend has a documented or sufficiently verified floating-point HDR-capable route. Canonical FP16 stays FP16 through the backend boundary. | +| BoundedHDR | Backend needs a bounded HDR domain or a specific display representation. Use a named normalizer or encoder and its paired inverse/presentation policy. | +| SDRCompatible | Backend contract is SDR, UNORM, U8, or another bounded SDR domain. Convert canonical HDR to a defined SDR proxy and restore into the canonical domain afterward. | +| ConditionalFP16 | A reference implementation or user-shader environment uses FP16 while a general HDR color contract remains unproven. Keep this opt-in and experimental. | +| Unknown | No stable upstream mapping or format contract. Keep HDR use behind a local validation gate. | +| PresentationTerminal | Backend produces or owns presentation frames. It ends an effect branch and does not return a normal canonical texture. | + +## Protocol Option Semantics + +The canonical inter-effect surface is always `R16G16B16A16_FLOAT`. This does not limit the format used inside an effect boundary. A concrete backend may expose multiple selectable protocol options when each option has reliable evidence: + +| Backend option | HDR-mode meaning | Adapter behavior | +|---|---|---| +| `R16G16B16A16_FLOAT` | Candidate HDR-native linear/scene-referred route; transfer and range still need metadata | Direct FP16 or bounded-HDR adapter according to the contract | +| `R10G10B10A2_UNORM` | Bounded HDR10/PQ route when the backend explicitly defines BT.2100/HDR10 semantics | Encode PQ/BT.2100 and preserve terminal metadata | +| `R8G8B8A8_UNORM` | Usually SDR-compatible storage, even when accepted by an HDR-capable effect | HDR-to-SDR mapping, clamp/quantize, backend call, paired reconstruction | + +The existence of an R8 and FP16 option means the effect has two selectable backend routes. It does not make the R8 route HDR-native. The implementation must therefore distinguish `acceptedFormatRoutes`, `hdrNativeFormatRoutes`, and `hdrAdapterFormatRoutes` for every effect. + +```json +{ + "acceptedFormatRoutes": ["R8G8B8A8_UNORM", "R16G16B16A16_FLOAT"], + "hdrNativeFormatRoutes": ["R16G16B16A16_FLOAT"], + "hdrAdapterFormatRoutes": ["R8G8B8A8_UNORM"] +} +``` + +An effect-level selector is displayed only when more than one reliable route is available. The selector controls the effect boundary protocol, while all preceding and following effects continue to exchange canonical FP16. SDR mode keeps the current default and does not inherit HDR selector behavior. + +## Integration Summary + +| Effect group / backend | Known accepted resources | HDR adapter decision | Priority | +|---|---|---|---| +| Anime4K | Public texture/color contract unavailable | Unknown | Later | +| CAS | Reference CLI: `R8G8B8A8_UNORM` and `R16G16B16A16_FLOAT` | Two selectable routes: FP16 candidate HDR-native; R8 SDR-compatible | High | +| CRT, CuNNy, CuNNy2, Diagnostics | No unique public API contract | Unknown | Later | +| DLSS SR | HDR color path; depth, MV, exposure, history resources | DirectFP16 | High | +| DLSS FG | Backbuffer/HUD/UI share format and color space | BoundedHDR + PresentationTerminal | Later | +| DLSSNR | Local bounded-HDR experiment is validated | BoundedHDR | First vertical slice | +| FSR1 | Perceptual sRGB `[0,1]` core contract | SDRCompatible | Medium | +| FSR2, FSR3 SR, FSR4 | HDR SR interfaces with temporal auxiliary inputs | DirectFP16 | High | +| FSR3 FG | Backbuffer, HUD-less, MV, depth, optical flow | BoundedHDR + PresentationTerminal | Later | +| FSRCNNX, FXAA, MLAA, Pixel Art, Sharpen | No stable generic format contract | Unknown | Later | +| NIS | Non-integer color resources; PQ and linear HDR modes | DirectFP16 | High | +| NNEDI3, RAVU | mpv `rgba16f/rgba16hf` implementation evidence | ConditionalFP16 | Medium | +| RTX Video VSR/Denoiser | GPU BGRA/RGBA interleaved U8 | SDRCompatible | High | +| RTX Video HDR | Product behavior known; public API texture contract unavailable | Unknown | Do not integrate yet | +| SMAA, xBRZ | RGBA / U8-class resource contracts | SDRCompatible | Medium | +| XeSS SR | `RGBA16F`, `R11G11B10F`, `RGBA8` and linear formats | DirectFP16 | High | +| XeSS FG | HDR10/BT.2100 `R10G10B10A2_UNORM`; no FP16 HDR/scRGB | BoundedHDR + PresentationTerminal | High after core path | +| NVIDIA Optical Flow | `GRAYSCALE8`, `NV12`, `ABGR8`; flow output | SDRCompatible auxiliary path | Medium | +| AMD FidelityFX Optical Flow | Color format not publicly enumerated; flow output | BoundedHDR auxiliary path | Later | + +## Direct FP16 Core Effects + +### DLSS Super Resolution + +- **Input:** HDR-capable application color buffer, motion vectors (`RG16F` or `RG32F`), depth, optional `R16F` exposure, and temporal controls. +- **Output:** Application output buffer; history output is commonly `RGBA16F`. +- **Adapter:** Keep canonical FP16 color and convert only required primary/transfer semantics. Allocate native-format auxiliary resources separately. +- **Boundary:** Color supports FP16. Full temporal quality still needs genuine render-space MV, depth, exposure, jitter, reset, and history behavior; desktop capture does not create equivalent data. + +### FSR2, FSR3 Super Resolution, and FSR4 + +- **Input:** Application-specified color buffer, one-channel depth, two-component motion vectors, exposure, and masks where required. +- **Output:** Application output resource; documented implementations use FP16 internal surfaces. +- **Adapter:** Keep color in canonical FP16 and provide native auxiliary inputs only where external code can supply valid data. +- **Boundary:** Their HDR color route and their temporal resource contract are independent constraints. + +### NVIDIA Image Scaling + +- **Input/output:** Non-integer color resources; documented LDR, PQ, and linear-HDR modes. +- **Adapter:** DirectFP16 with the chosen NIS HDR range, sampler, and resource-state contract stored in its protocol. +- **Boundary:** Spatial behavior makes NIS a practical early external HDR target. + +### XeSS Super Resolution + +- **Input:** `R16G16B16A16_FLOAT`, `R11G11B10_FLOAT`, `R8G8B8A8_UNORM`, and supported linear formats; MV and depth have native formats. +- **Output:** Same format and color space as the input color texture; alpha requires canonical cleanup. +- **Adapter:** Use canonical FP16 by default. A later `R11G11B10_FLOAT` option needs verified alpha-free usage. +- **Boundary:** Temporal auxiliary inputs remain independent from HDR color compatibility. + +## Conditional FP16 Effects + +### CAS + +- **Known resources:** The official reference CLI uses `R8G8B8A8_UNORM` and `R16G16B16A16_FLOAT`; shader paths include linear handling and FP16/FP32 math variants. +- **Adapter:** Start with the current SDR-compatible selection. Add a hidden or experimental DirectFP16 profile after the exact Magpie CAS path proves that it preserves linear FP16 values and alpha. +- **Boundary:** Reference CLI behavior proves a viable FP16 direction. It does not alone prove every CAS integration's resource contract. + +### NNEDI3 + +- **Known resources:** mpv user-shader variants use `rgba16f/rgba16hf` internal surfaces and focus on luma. +- **Adapter:** Treat FP16 as implementation-format capability. Feed a defined luma or RGB projection only after identifying the exact variant; restore alpha and color semantics at the boundary. +- **Boundary:** No public native HDR color contract is established. + +### RAVU + +- **Known resources:** mpv variants use `rgba16f/rgba16hf`; RGB, YUV, luma-only, gather, and compute variants differ. +- **Adapter:** Bind protocol to the exact variant. RGB variants can be FP16 candidates; YUV and luma paths require explicit color/luma conversion. +- **Boundary:** FP16 surface availability does not define PQ, HLG, scene-linear range, or metadata behavior. + +## Bounded HDR and Frame-Generation Paths + +### DLSSNR + +- **Known local result:** Direct HDR texture values create color failure, including red leakage. A bounded HDR normalization path produces the intended result. +- **Required chain:** `canonical FP16 HDR -> normalize -> DLSSNR -> inverse normalize -> canonical FP16 HDR`. +- **Adapter state:** Curve identity, scale, reference white, peak/headroom, alpha rule, source color assumption, and inverse parameters. +- **Boundary:** This is a local experimental protocol. It stays separate from official vendor-contract claims and needs capture/output diagnostics. + +### FSR3 Frame Generation + +- **Known resources:** Presentation backbuffer, optional HUD-less buffer, depth, MV, and optical-flow resources. +- **Adapter:** Treat it as a bounded presentation path whose swap-chain format and color semantics agree across composed inputs. +- **Boundary:** Frame generation needs temporal and motion information unavailable from a simple desktop image. Color adaptation alone does not satisfy the backend contract. + +### XeSS Frame Generation + +- **Known resources:** HDR uses `R10G10B10A2_UNORM` with HDR10 / BT.2100. HUD-less and UI resources match backbuffer format, color space, and size. UI alpha is explicit. +- **Adapter:** Encode canonical FP16 to PQ/BT.2100 `R10G10B10A2_UNORM`, run XeSS FG, then present through its proxy swap chain. +- **Boundary:** The documented HDR route excludes FP16 HDR and scRGB. XeSS FG ends the ordinary effect chain. + +### DLSS Frame Generation + +- **Known resources:** Backbuffer, HUD-less/UI resources, MV/depth, and an output texture matching backbuffer format. +- **Adapter:** Keep behind a presentation-terminal abstraction until the exact external format profile is verified. +- **Boundary:** Current evidence establishes same-format behavior and HDR capability while a complete external texture matrix remains unresolved. + +### AMD FidelityFX Optical Flow + +- **Known resources:** Color input feeds a transfer/luminance conversion; outputs include `R16G16_SINT` flow vectors and `R32_UINT` scene-change data. +- **Adapter:** Use a bounded-HDR luminance adapter after a local test identifies source range and transfer semantics. +- **Boundary:** Optical flow is an auxiliary-resource producer, not a normal RGB effect output. + +## SDR-Compatible Effects + +### FSR1 + +- **Known resources:** Core input uses perceptual/sRGB `[0,1]`; RCAS has invalid behavior for negative values. +- **Adapter:** `canonical FP16 HDR -> SDR appearance mapping -> sRGB [0,1] -> FSR1 -> SDR decode/reconstruction`. +- **Boundary:** Surrounding helper code contains HDR conversion utilities, while the core algorithm contract remains SDR oriented. + +### RTX Video VSR + +- **Known resources:** GPU-resident BGRA or RGBA interleaved U8 input/output. +- **Adapter:** Convert canonical HDR to selected SDR appearance, encode SDR transfer, and quantize each channel to U8 `0..255`. Convert output back through the paired reconstruction policy. +- **Boundary:** U8 is a hard precision boundary. The public interface has no native HDR transfer, primary, metadata, or FP16 contract. + +### RTX Video Denoiser + +- **Known resources:** Same BGRA/RGBA U8 GPU-buffer interface; denoise/deblur output stays at input resolution. +- **Adapter:** Use the same SDR/U8 protocol as VSR, with no scaling assumption. +- **Boundary:** Keep VSR and Denoiser protocol identifiers separate despite shared encoding. + +### SMAA, xBRZ, and NVIDIA Optical Flow + +- **SMAA:** RGBA color texture plus edge/area/search/depth resources. Intermediate textures are normally non-sRGB; final neighborhood blending can be sRGB. +- **xBRZ:** Community TypeScript/WASM path uses RGBA U8 `Uint8ClampedArray` with alpha and scale factors 2 through 6. +- **NVIDIA Optical Flow:** `GRAYSCALE8`, `NV12`, or `ABGR8` input; flow output is signed fixed-point `SHORT2`. +- **Adapter:** Use SDR-compatible conversion. Optical-flow vector outputs remain outside the canonical RGB chain. + +## Effects Awaiting a Stable Public Contract + +### Anime4K + +- **Known resources:** The shader family is distributed as user shaders with variant-specific texture declarations; one universal color-format contract is not established. +- **Adapter:** Keep `Unknown` until the selected preset is inspected. If the preset declares normalized SDR sampling, use `SDRCompatible`; if it declares floating-point linear sampling, promote it to `ConditionalFP16` after a range test. +- **Boundary:** Do not infer HDR support from shader compilation. Record texture format, sampler state, transfer assumption, and whether negative or above-one values survive each pass. + +### CRT + +- **Known resources:** CRT presets combine color, scanline, mask, and sometimes feedback passes; the exact format and transfer behavior depend on the preset. +- **Adapter:** Treat each preset as an independent protocol. Default to `SDRCompatible` and require a per-pass range audit before allowing direct FP16 HDR. +- **Boundary:** Scanline and mask math can be visually valid for SDR while clipping HDR highlights or changing saturated colors. + +### CuNNy and CuNNy2 + +- **Known resources:** Neural shader models use model-specific channel, normalization, and tensor-size assumptions; a single public HDR contract is not established. +- **Adapter:** Keep `Unknown`. A future adapter must identify the model's normalization interval, tensor format, channel order, and output denormalization before enabling HDR. +- **Boundary:** Treat model normalization as part of the protocol. A generic FP16 surface alone does not establish that the network accepts HDR values. + +### Diagnostics + +- **Known resources:** Diagnostic effects may inspect or rewrite channels, ranges, alpha, or color-space metadata depending on the diagnostic selected. +- **Adapter:** Run diagnostics on the canonical FP16 surface where possible, while preserving a raw-capture tap and the pre-adapter color description. +- **Boundary:** Diagnostics are not an image-quality backend. Their output must never be silently reused as a normal HDR frame unless the selected diagnostic explicitly promises that behavior. + +### FSRCNNX + +- **Known resources:** User-shader variants expose model- and preset-specific texture declarations; no stable general HDR input/output contract is established. +- **Adapter:** Default to `SDRCompatible`. Permit `ConditionalFP16` only for a tested variant whose normalization and output range are documented. +- **Boundary:** Verify RGB versus luma-only operation, alpha handling, and any hard clamp in every model pass. + +### FXAA and MLAA + +- **Known resources:** Both are neighborhood-based anti-aliasing families commonly implemented over normalized color textures, with auxiliary edge/luma data varying by implementation. +- **Adapter:** Use `SDRCompatible` until the exact implementation proves unclamped FP16 behavior. Keep edge/luma auxiliary resources separate from the canonical RGB surface. +- **Boundary:** Anti-aliasing thresholds are often tuned in display-referred units; direct scene-linear HDR can change edge detection even when the texture format is floating point. + +### Pixel Art + +- **Known resources:** Presets vary between integer nearest-neighbor logic, palette tests, and shader-specific color thresholds. +- **Adapter:** Default to `SDRCompatible`; allow direct FP16 only for a preset whose comparisons are explicitly range-independent. +- **Boundary:** Preserve exact alpha and integer-like color comparisons. Do not apply an HDR tone curve inside the pixel-art backend without recording it as a deliberate artistic transform. + +### RTX Video HDR + +- **Known resources:** Product-level HDR behavior is known, while a public external texture/API contract suitable for this effect catalog remains unresolved. +- **Adapter:** Keep `Unknown` and do not route canonical frames into it by assumption. Integrate only after the exact API, accepted resource formats, color space, metadata, and output ownership are documented. +- **Boundary:** A display feature or driver capability is not evidence of an externally callable native-HDR filter contract. + +### Sharpen + +- **Known resources:** The name covers multiple backends and shader families, including normalized SDR and floating-point implementations. +- **Adapter:** Resolve by concrete backend identifier. Use `SDRCompatible` for normalized SDR variants and `ConditionalFP16` for variants with verified unclamped linear-FP16 behavior. +- **Boundary:** Negative lobes, overshoot clamps, alpha treatment, and sharpening strength units must be recorded because they directly affect HDR highlight reconstruction. + +## Uniform Runtime Order + +When HDR compatibility is enabled, every ordinary effect branch follows this order: + +1. Capture with the existing capture method and retain source color metadata. +2. Decode the captured transfer function and normalize exposure into canonical `RGBA16F`. +3. Select the effect profile from the concrete backend identifier and evidence level. +4. Convert canonical HDR into the backend's declared input protocol. +5. Run the existing backend without changing its SDR-mode behavior. +6. Convert the backend output back into canonical `RGBA16F`, restoring alpha according to the profile. +7. Continue to the next effect, or hand the terminal presentation branch to its required swap-chain format. +8. Encode the final canonical frame for the existing presentation path and apply metadata at presentation time. + +If a backend has no reliable protocol evidence, use the existing compatible SDR route and record the conversion. Unknown effects must not silently receive direct HDR values. + +## Runtime Diagnostic Record + +Each adapted effect should emit a compact record containing: + +```text +effect identifier +selected profile and evidence level +input DXGI format and ColorDescription +adapter input conversion and numeric range +backend texture formats and resource states +backend output format and range +adapter output conversion +alpha rule and auxiliary-resource formats +GPU time: pre-adapter / backend / post-adapter +observations: highlights / saturated red / UI white / dark gradients +``` + +The record should also include whether the path was direct, bounded, SDR-compatible, or unknown-fallback, plus the normalizer identity and parameters when a bounded-HDR path is used. + +## Validation Matrix + +| Test stage | Required check | Pass condition | +|---|---|---| +| Capture | HDR transfer decode and exposure | No highlight expansion caused by treating HDR code values as SDR | +| Single effect | Adapter round trip | Neutral gray, saturated red, white UI, and dark gradients remain stable | +| Chained effects | Repeated boundary conversions | No progressive hue drift, highlight pumping, or alpha loss | +| Temporal effect | History, MV, depth, and exposure agreement | All temporal inputs use the same frame color description and dimensions | +| Presentation terminal | Swap-chain and metadata ownership | Output format and metadata match the terminal backend contract | +| SDR regression | HDR option disabled | Existing SDR path and output remain unchanged | + +## Implementation Priority + +1. Implement the canonical capture-to-`RGBA16F` HDR front end and make it independently toggleable. +2. Integrate direct FP16 SR effects whose contracts are already sufficiently established. +3. Integrate bounded adapters for DLSSNR and explicitly bounded temporal/presentation paths. +4. Integrate SDR-compatible VSR, Denoiser, FSR1, SMAA, xBRZ, and optical-flow auxiliary paths. +5. Add per-backend selectors only where multiple accepted protocols have reliable evidence. +6. Keep unknown effects behind diagnostics and promote them only after a concrete preset/backend audit. + +## Current Non-Goals + +- Replacing any backend model or retraining a neural effect. +- Changing the existing SDR path when HDR compatibility is disabled. +- Treating `R16G16B16A16_FLOAT` as a color space without transfer and gamut metadata. +- Claiming native HDR support for an effect from format enumeration, compilation success, or product-level marketing behavior alone. diff --git a/docs/experimental/HDR_EFFECT_PROTOCOL_AUDIT_20260907.md b/docs/experimental/HDR_EFFECT_PROTOCOL_AUDIT_20260907.md new file mode 100644 index 000000000..6b01cd151 --- /dev/null +++ b/docs/experimental/HDR_EFFECT_PROTOCOL_AUDIT_20260907.md @@ -0,0 +1,100 @@ +# Magpie 0.6.5 HDR Effect Protocol Audit + +Date: 2026-09-07 +Canonical surface: `R16G16B16A16_FLOAT`, linear scRGB, reference white 80 nit. + +This audit separates source-level protocol evidence from a live SDK result. A +route is marked `Live` only when the current native backend performs its +production Draw/Evaluate/Run call and the renderer records the resulting +texture. A route marked `Contract` has source and SDK evidence but still needs +a target-machine pixel capture. `Fallback` means the canonical bridge is +implemented and the effect remains on the explicit compatibility path. + +## Effect Matrix + +| effectId / variant | selected HDR protocol | backend call | status | evidence / blocking item | +|---|---|---|---|---| +| DLSS SR | DirectFP16, linear | `DLSSSRUpscaler::Draw` -> NGX Evaluate | Contract | zero MV/depth contract; HDR flag/exposure still needs live pixel proof | +| FSR1 | SDRCompatible, sRGB R8 | shader pass | Fallback | FSR1 core is bounded perceptual sRGB | +| FSR2 | DirectFP16, linear | `FSR2Upscaler::Draw` -> FSR2 dispatch | Contract | HDR flag/exposure/depth are effect-local open items | +| FSR3 SR | DirectFP16, linear | `FSR3Upscaler::Draw` -> FSR3 dispatch | Contract | current provider path still needs HDR linear dispatch proof | +| FSR4 | DirectFP16, linear | `FSR3Upscaler::Draw(useFsr4)` | Contract | FSR4 ML/provider contract and live output proof pending | +| XeSS SR | DirectFP16, FP16 linear (default) | `XeSSUpscaler::Draw` -> `xessD3D12Execute` | Contract | `XESS_INIT_FLAG_LDR_INPUT_COLOR` is now omitted for FP16/R10 HDR routes | +| NIS | DirectFP16 candidate | shader pass using NIS linear HDR mode | Contract | exact Magpie `NIS_HDR_MODE` compile route and live pixel proof pending | +| CAS | SDRCompatible R8 | shader pass | Fallback | current shader saturates RGB and forces alpha 1 | +| Anime4K | Unknown -> SDR fallback | shader passes | Fallback | preset-specific FP16 intermediates lack a color contract | +| CRT | Unknown -> SDR fallback | shader passes | Fallback | preset gamma/clamp behavior is SDR-bound | +| CuNNy | Unknown -> SDR fallback | shader passes | Fallback | SNORM model tensors and normalization are unverified HDR | +| CuNNy2 | Unknown -> SDR fallback | shader passes | Fallback | UNORM model tensors and normalization are unverified HDR | +| FSRCNNX | Unknown -> SDR fallback | shader passes | Fallback | FP16 intermediates do not define input/output HDR semantics | +| FXAA | Unknown -> SDR fallback | shader passes | Fallback | luma threshold has no HDR scale contract | +| MLAA | Unknown -> SDR fallback | shader passes | Fallback | R8 edge/count auxiliaries are bounded SDR resources | +| SMAA | SDRCompatible R8/FP16 auxiliaries | shader passes | Fallback | alpha and luma threshold are variant-specific | +| xBRZ | SDRCompatible R8 | shader passes | Fallback | integer-like RGB comparisons and alpha=1 | +| Pixel Art | SDRCompatible R8 | shader passes | Fallback | MMPX/Pixellate force alpha 1; SharpBilinear preserves it | +| Sharpen series | SDRCompatible normalized RGB | shader passes | Fallback | no stable upstream HDR contract | +| DLSSNR | R8 user path: SDRCompatible; experimental path: BoundedHDR FP16 | `DLSSNRFilter::Draw` -> Feature 18 Evaluate | Contract | R8 selection remains explicit; FP16 scale is local experiment, not vendor HDR proof | +| RTXVideo VSR Ultra | SDRCompatible `R8G8B8A8_UNORM` endpoint | `NvCVImage_Transfer` -> `NvVFX_Run` -> output transfer | Contract | U8 interleaved 0..255; `RTXVideoVSR/VSR-U8-sRGB-255` route | +| RTXVideo Denoise Ultra | SDRCompatible `R8G8B8A8_UNORM` endpoint | `NvCVImage_Transfer` -> `NvVFX_Run` -> output transfer | Contract | independent `RTXVideoDenoise/Denoise-U8-RGBA` route; same-resolution rule | +| NVIDIA Optical Flow | SDRCompatible ABGR8 auxiliary | `nvOFExecute` | Contract | output is S10.5 flow plus confidence; never a canonical RGB surface | +| AMD Optical Flow | BoundedHDR auxiliary candidate | FidelityFX optical-flow dispatch | Contract | current input is RGBA8 with fixed sRGB/luminance parameters | +| DLSSFG | PresentationTerminal record | `DLSSFrameGenerator::Draw` / publication | Contract | SDK backbuffer/HUD/UI HDR format and color-space contract still runtime dependent | +| XeSSFG | PresentationTerminal HDR10 R10 | XeSS-FG proxy swap chain + `Present` | Contract | R10 resource and `DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020` are explicit; live capture pending | +| FSR3/FSR4 FG | PresentationTerminal record | no native backend in current tree | Unavailable | protocol record exists; production backend and SDK are missing | +| third-party native backends | per-backend route above | factory-dispatched native `Draw` | Mixed | only VFX, DLSS/FSR/XeSS and optical-flow providers have callable native paths | + +## Required Protocol Record + +Every route is represented by `HdrFormatRoute` and serialized through +`SerializeHdrFormatRoute`. The runtime diagnostic emitted at capture and native +failure boundaries contains the effect route/profile, source format, source +color description, canonical format, conversion path and fallback reason. The +full per-frame evidence record is: + +```text +effectId, effectVariant, selectedProtocol, profile, evidenceLevel, +inputFormat, inputColorModel, inputTransfer, inputRange, +backendInputFormat, backendOutputFormat, outputTransfer, alphaRule, +auxiliaryResources, normalizationParameters, nativeInitialization, +nativeDrawOrEvaluate, nativeResultCode, outputFiniteStats, +canonicalOutputStats, gpuTime, screenshot, sha256 +``` + +`HdrSurfaceAdapter` keeps the paired forward/inverse parameters for +SDR-compatible and bounded routes. `HdrCaptureProcessor` produces and reuses +the canonical FP16 surface after the selected capture source. `EffectDrawer` +executes the adapter before and after the real shader/native boundary; native +failure is logged with an explicit marker-pass fallback. + +## DLSSNR Decision + +The user-selected R8 route remains `DLSSNR/sdr-r8` and allocates R8 input and +output resources. The experimental FP16 route is selected only when +`experimentalHdrPath` is enabled and the validated scale is `1`, `2`, or `4.5`; +it allocates FP16 resources and records `BoundedHDR` with the scale. No route +selection branch promotes the FP16 experiment when the user selected R8. + +The current implementation records the normalization scale, reference white, +display peak and adapter mode. Existing local experiments prove the real +Feature 18 Create/Evaluate path for the tested DLL/driver combinations, while +the exact vendor normalization curve remains an open experimental parameter. + +## RTX Video Decision + +VSR and Denoise now have separate protocol IDs. Both use an explicit U8, +interleaved RGBA/BGRA endpoint with values in `[0,255]`. The native backend +records `NvCVImage_InitFromD3D11Texture`, input transfer, `NvCVImage_Transfer`, +`NvVFX_Run`, output transfer and synchronization. A native failure stays a +failure in the log and the renderer marks the compatibility marker pass +separately. + +## Evidence Gaps + +The current workspace has no reproducible live GPU capture for every listed +effect, so the effects marked `Contract`, `Fallback`, or `Unavailable` are not +reported as visually passed. The existing `E:\Magpie-0.6.5-build\matrix` +captures and DLSSNR experiment logs remain the available pixel/hash evidence. +The startup matrix must be rerun on a target display with the user sample to +populate the remaining screenshot, SHA-256, finite-statistics, and GPU-time +fields. + diff --git a/docs/experimental/HDR_MECHANICAL_IMPLEMENTATION_REQUIREMENTS.md b/docs/experimental/HDR_MECHANICAL_IMPLEMENTATION_REQUIREMENTS.md new file mode 100644 index 000000000..ef1b1bc8d --- /dev/null +++ b/docs/experimental/HDR_MECHANICAL_IMPLEMENTATION_REQUIREMENTS.md @@ -0,0 +1,110 @@ +# HDR Mechanical Implementation Requirements + +## Purpose + +This document assigns the repetitive, locally verifiable work for the first HDR architecture slice. The global design decisions remain in [HDR_COMPATIBILITY_ARCHITECTURE.md](HDR_COMPATIBILITY_ARCHITECTURE.md). This task covers configuration plumbing, protocol data plumbing, reusable conversion dispatch, diagnostics, and the generic effect-boundary execution layer. It does not alter concrete effect shader sources or model implementations. + +## Required configuration plumbing + +Add the profile-level general setting: + +```json +"enableHdrCompatibility": false +``` + +The setting must map to `ScalingFlags::EnableHdrCompatibility` and expose `IsHdrCompatibilityEnabled()` in `ScalingOptions`. Preserve unknown configuration fields and preserve the default-off behavior for existing profiles. Add the setting to profile copy, load, save, and runtime option construction. Add the corresponding general-settings UI binding and localization using the repository's existing conventions. + +## Required protocol data plumbing + +Extend the reusable HDR protocol description so an effect can later declare multiple backend routes without changing the canonical work surface. Each route must be able to store: + +```text +effectId +optionId +inputFormat +outputFormat +inputTransfer +outputTransfer +inputRange +outputRange +alphaMode +evidenceLevel +hdrNative +adapterProfile +defaultForHdr +defaultForSdr +``` + +Do not add unverified effect-specific routes in this task. Add storage, validation, default selection, serialization helpers, and the generic structured SDR fallback route used when a concrete route is absent. + +## Required generic conversion dispatch + +Implement a reusable adapter dispatcher with these profiles: + +```text +DirectFP16 +BoundedHDR +SDRCompatible +ConditionalFP16 +Unknown +PresentationTerminal +``` + +The dispatcher must accept canonical `R16G16B16A16_FLOAT` input and return canonical `R16G16B16A16_FLOAT` output for non-terminal routes. It must select conversion behavior from the route description rather than from effect-name string comparisons. The SDR-compatible route must use paired HDR-to-SDR and SDR-to-HDR parameters and preserve alpha explicitly. Unknown routes must select the existing compatible fallback and emit a diagnostic state. + +## Required capture-front-end hook + +Add a reusable `HdrCaptureProcessor` integration point after any selected frame source produces its texture. Keep Graphics Capture, Desktop Duplication, GDI, and DwmSharedSurface selection unchanged. The processor must expose: + +```text +Process(sourceTexture, sourceFormat, sourceColorDescription) +GetCanonicalTexture() +GetFrameMetadata() +ResetForResize() +``` + +The implementation may use a display-derived source color description where the capture API does not expose texture metadata, but it must mark and record that inference. The processor must use reusable textures and must not replace the existing SDR texture when HDR compatibility is disabled. + +When HDR compatibility is enabled, the delivery path must retain the canonical FP16/scRGB surface through shared publication and presenter composition. R8 publication remains the disabled-mode path and is not used as the terminal HDR representation. + +## Required diagnostics + +Add structured logging for: + +```text +HDR option state +capture method +source format +source color description +canonical format +selected adapter profile +selected route id +conversion path +fallback reason +``` + +Avoid per-pixel CPU readback. Do not add a staging readback path to production rendering. + +## Explicit exclusions + +- No changes to concrete effect shader sources or model implementations. Generic boundary calls may wrap their existing backend resources when HDR compatibility is enabled. +- No model changes, shader-model changes, or effect shader rewrites. +- No change to SDR behavior when the global HDR option is disabled. +- No hard-coded assumption that every FP16 texture is HDR. +- No claim that a format enumeration proves HDR support. + +## Acceptance checks + +1. Existing profiles load with HDR disabled. +2. The general settings control round-trips through profile save/load. +3. The selected capture method is unchanged with HDR enabled. +4. HDR mode exposes a canonical FP16 frame descriptor after capture. +5. SDR mode bypasses the HDR processor and retains the previous resource path. +6. The adapter dispatcher has unit-level coverage for all six profiles. +7. The SDR-compatible adapter uses paired forward/inverse parameters. +8. No concrete effect file is modified. +9. The project files parse and the touched targets compile. + +## Deliverables + +Return the changed file list, configuration key, runtime flag, route serialization shape, adapter dispatch entry point, and validation commands. Report any capture API metadata assumption explicitly. diff --git a/docs/experimental/HDR_PROTOCOL_EVIDENCE.json b/docs/experimental/HDR_PROTOCOL_EVIDENCE.json new file mode 100644 index 000000000..44c109419 --- /dev/null +++ b/docs/experimental/HDR_PROTOCOL_EVIDENCE.json @@ -0,0 +1,2125 @@ +[ + { + "effectGroup": "Anime4K", + "effectFamily": "Anime4K", + "upstreamVendor": "bloc97 / Anime4K contributors", + "mappingConfidence": "明确映射", + "mappingNotes": "公开仓库名称与效果组名一致,且为 Anime4K 官方/上游仓库。", + "documentedInputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "https://github.com/bloc97/Anime4K/blob/master/README.md", + "notes": "Anime4K 官方仓库未枚举输入纹理格式;作为 mpv/libplacebo 用户着色器分发,实际纹理格式由宿主渲染器提供。" + } + ], + "documentedOutputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "https://github.com/bloc97/Anime4K/blob/master/README.md", + "notes": "输出为目标/上采样后纹理;官方仓库未枚举像素格式。" + } + ], + "formatBoundary": { + "inputFormatEnumeration": "not_enumerated", + "outputFormatEnumeration": "not_enumerated", + "inputOutputRelation": "implementation_defined", + "alphaSemantics": "未说明;不得从 mpv 用户着色器可编译推断 alpha 契约。", + "rangeBoundary": "unknown", + "transferBoundary": "unspecified", + "resourceConstraints": "尺寸缩放/着色器 hook 点由 mpv/libplacebo 宿主决定;官方未提供 UAV/SRV 或 API 资源状态要求。", + "hdrImplication": "无公开 HDR 格式契约。" + }, + "channelOrderAndAlpha": "无信息", + "precisionAndRange": "无信息", + "transferFunction": "无信息", + "primariesAndColorSpace": "无信息", + "hdrSupport": "无信息", + "hdrBoundary": "未说明", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "无信息", + "profileRationale": "十轮内未找到可核实协议,无法给出候选 profile。", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": [], + "documentedQualityOptions": [ + "A/B 模式等 GLSL 说明中的品质选择(未提供协议级格式语义)" + ], + "documentedPerformanceNotes": "无信息", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "OfficialRepository", + "sources": [ + { + "url": "https://github.com/bloc97/Anime4K/blob/master/README.md", + "title": "Anime4K README", + "evidenceLevel": "OfficialRepository", + "claim": "项目为 Anime4K 官方仓库,未写明 HDR/纹理格式协议", + "quoteOrSymbol": "The simplicity and speed of Anime4K allows the user to watch upscaled anime in real time", + "searchRound": 1 + }, + { + "url": "https://raw.githubusercontent.com/bloc97/Anime4K/v4.0.1/GLSL_Instructions.md", + "title": "Anime4K v4.0.1 GLSL Instructions", + "evidenceLevel": "OfficialRepository", + "claim": "只说明 mpv/GLSL 用法与模式,未说明纹理格式、数值范围、HDR", + "quoteOrSymbol": "Mode A...", + "searchRound": 2 + } + ], + "searchRoundsUsed": 7, + "unresolvedQuestions": [ + "输入/输出纹理格式", + "数值范围", + "色彩空间", + "alpha", + "HDR/FP16 能力" + ], + "conclusion": "Anime4K 可明确映射到同名公开仓库,但公开仓库未提供图像 I/O 协议细节,本次记录为无协议信息。" + }, + { + "effectGroup": "CAS", + "effectFamily": "FidelityFX Contrast Adaptive Sharpening (CAS)", + "upstreamVendor": "AMD", + "mappingConfidence": "明确映射", + "mappingNotes": "AMD FidelityFX SDK 的 CAS 技术,官方名称一致。", + "documentedInputFormats": [ + { + "format": "DXGI_FORMAT_R8G8B8A8_UNORM", + "apiOrContext": "DXGI / Direct3D 11 reference CLI", + "channelOrder": "RGBA", + "numericRepresentation": "UNORM", + "acceptanceStatus": "reference_implementation_only", + "evidenceRef": "https://raw.githubusercontent.com/GPUOpen-Effects/FidelityFX-CAS/78c03cf5cbf086ddcc06f635cc4def85a68b22df/CasCmdLine/README.md", + "notes": "官方 CasCmdLine 默认输入格式;sRGB 解释由开关决定。" + }, + { + "format": "DXGI_FORMAT_R16G16B16A16_FLOAT", + "apiOrContext": "DXGI / Direct3D 11 reference CLI", + "channelOrder": "RGBA", + "numericRepresentation": "FLOAT (16-bit)", + "acceptanceStatus": "reference_implementation_only", + "evidenceRef": "https://raw.githubusercontent.com/GPUOpen-Effects/FidelityFX-CAS/78c03cf5cbf086ddcc06f635cc4def85a68b22df/CasCmdLine/README.md", + "notes": "官方 CasCmdLine --16bit 使用的格式;shader 另有 FP16/FP32 数学路径。" + }, + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v1.1.0/docs/techniques/contrast-adaptive-sharpening.md", + "notes": "SDK 技术文档未对 FfxResource 输入格式做 DXGI/Vulkan 枚举。" + } + ], + "documentedOutputFormats": [ + { + "format": "DXGI_FORMAT_R8G8B8A8_UNORM", + "apiOrContext": "DXGI / Direct3D 11 reference CLI", + "channelOrder": "RGBA", + "numericRepresentation": "UNORM", + "acceptanceStatus": "reference_implementation_only", + "evidenceRef": "https://raw.githubusercontent.com/GPUOpen-Effects/FidelityFX-CAS/78c03cf5cbf086ddcc06f635cc4def85a68b22df/CasCmdLine/README.md", + "notes": "默认输出格式,与输入同格式;官方样例说明输出可为相同或更大尺寸。" + }, + { + "format": "DXGI_FORMAT_R16G16B16A16_FLOAT", + "apiOrContext": "DXGI / Direct3D 11 reference CLI", + "channelOrder": "RGBA", + "numericRepresentation": "FLOAT (16-bit)", + "acceptanceStatus": "reference_implementation_only", + "evidenceRef": "https://raw.githubusercontent.com/GPUOpen-Effects/FidelityFX-CAS/78c03cf5cbf086ddcc06f635cc4def85a68b22df/CasCmdLine/README.md", + "notes": "--16bit 输出格式;官方文档保证输出与输入同色彩空间。" + }, + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v1.1.0/docs/techniques/contrast-adaptive-sharpening.md", + "notes": "SDK 的 FfxResource output 未在技术文档中列出具体格式枚举。" + } + ], + "formatBoundary": { + "inputFormatEnumeration": "partial", + "outputFormatEnumeration": "partial", + "inputOutputRelation": "same_format_required", + "alphaSemantics": "未说明;SDK 回调只读写 RGB,未见 alpha 透传/置 1 声明。", + "rangeBoundary": "sRGB/linear 解释均支持;shader 转换注释按 [0,1] 线性空间处理。", + "transferBoundary": "linear input expected; output returned to same input color space via FfxCasColorSpaceConversion", + "resourceConstraints": "官方样例支持 sharpen-only/sharpen+upscale;输入 SRV/read、输出 UAV/write 在 SDK 样例路径;格式枚举只来自 CasCmdLine 参考实现。", + "hdrImplication": "官方参考实现支持 R16G16B16A16_FLOAT 与 linear 开关,但这属于参考实现路径;SDK 技术文档未提供 HDR 元数据契约。" + }, + "channelOrderAndAlpha": "回调只处理 RGB;文档未说明 alpha 是否透传或必须为 1。", + "precisionAndRange": "回调存在 float32 与 float16 路径;色彩转换注释按 [0,1] 线性空间处理,文档未给出 8/10/16 位格式清单。", + "transferFunction": "期望线性输入;通过 FfxCasColorSpaceConversion 支持 LINEAR、GAMMA20、GAMMA22、sRGB_OUTPUT、sRGB_INPUT_OUTPUT。", + "primariesAndColorSpace": "未说明原色域。", + "hdrSupport": "Unspecified", + "hdrBoundary": "文档未说明 HDR 直接输入;只要求线性输入并保持输入输出同色彩空间。", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "SDRCompatible", + "profileRationale": "官方 shader 注释将输入转换描述为 0..1 线性空间,且没有任何 HDR/FP16 直接路径声明;保守记为 SDR 兼容。", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": [], + "documentedQualityOptions": [ + "sharpening disabled", + "sharpening enabled, upsampling disabled", + "both sharpening and upsampling enabled" + ], + "documentedPerformanceNotes": "文档未比较 FP16/UNORM/HDR 路径成本。", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "OfficialDocs", + "sources": [ + { + "url": "https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v1.1.0/docs/techniques/contrast-adaptive-sharpening.md", + "title": "FidelityFX Contrast Adaptive Sharpening 1.1", + "evidenceLevel": "OfficialDocs", + "claim": "CAS 需要线性输入;可按输入空间转换并在输出还原", + "quoteOrSymbol": "CAS needs linear input color to perform correctly", + "searchRound": 1 + }, + { + "url": "https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v1.1.0/sdk/include/FidelityFX/host/ffx_cas.h", + "title": "ffx_cas.h", + "evidenceLevel": "OfficialHeader", + "claim": "FfxCasDispatchDescription 仅含 FfxResource color/output、renderSize、sharpness", + "quoteOrSymbol": "FfxResource color; FfxResource output", + "searchRound": 2 + }, + { + "url": "https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v1.1.0/sdk/include/FidelityFX/gpu/cas/ffx_cas_callbacks_hlsl.h", + "title": "ffx_cas_callbacks_hlsl.h", + "evidenceLevel": "OfficialHeader", + "claim": "float32/float16 采样与颜色空间转换宏", + "quoteOrSymbol": "casInputHalf... between 0 and 1", + "searchRound": 3 + }, + { + "url": "https://raw.githubusercontent.com/GPUOpen-Effects/FidelityFX-CAS/78c03cf5cbf086ddcc06f635cc4def85a68b22df/CasCmdLine/README.md", + "title": "FidelityFX-CAS CasCmdLine README", + "evidenceLevel": "OfficialRepository", + "claim": "官方命令行参考实现默认 R8G8B8A8_UNORM,--16bit 用 R16G16B16A16_FLOAT", + "quoteOrSymbol": "uses R8G8B8A8_UNORM ... uses R16G16B16A16_FLOAT", + "searchRound": 5 + } + ], + "searchRoundsUsed": 7, + "unresolvedQuestions": [ + "具体支持格式列表", + "alpha 契约", + "HDR 数值上限", + "primaries" + ], + "conclusion": "CAS 的官方协议只明确了线性输入、输入输出同空间、float/fp16 shader 回调与可选色彩空间转换;具体 DXGI 格式与 HDR 能力未被官方文档枚举。" + }, + { + "effectGroup": "CRT", + "effectFamily": "无信息(CRT 效果族未能在公开资料中唯一映射)", + "upstreamVendor": "无信息", + "mappingConfidence": "无信息", + "mappingNotes": "仅按通用名称“CRT”搜索;公开资料无法唯一对应某个上游算法/SDK,且不能借助 Magpie 源码确认。", + "documentedInputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "无公开唯一上游来源", + "notes": "无法唯一映射上游;不得把通用 CRT 着色器格式写成该效果组已支持格式。" + } + ], + "documentedOutputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "无公开唯一上游来源", + "notes": "无法唯一映射上游;输出格式未公开。" + } + ], + "formatBoundary": { + "inputFormatEnumeration": "not_enumerated", + "outputFormatEnumeration": "not_enumerated", + "inputOutputRelation": "unknown", + "alphaSemantics": "未说明;名称不能唯一映射,无法建立 alpha 契约。", + "rangeBoundary": "unknown", + "transferBoundary": "unspecified", + "resourceConstraints": "未确认任何资源状态、缩放或采样限制。", + "hdrImplication": "无公开 HDR 格式契约。" + }, + "channelOrderAndAlpha": "无信息", + "precisionAndRange": "无信息", + "transferFunction": "无信息", + "primariesAndColorSpace": "无信息", + "hdrSupport": "无信息", + "hdrBoundary": "未说明", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "无信息", + "profileRationale": "无信息", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": [], + "documentedQualityOptions": [], + "documentedPerformanceNotes": "无信息", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "无信息", + "sources": [], + "searchRoundsUsed": 1, + "unresolvedQuestions": [ + "上游映射", + "输入格式", + "色彩空间", + "HDR/FP16" + ], + "conclusion": "“CRT”效果组无法在公开资料中唯一映射到上游算法或 SDK,十轮内无法取得可靠协议结论,记录为无信息。" + }, + { + "effectGroup": "CuNNy", + "effectFamily": "CuNNy", + "upstreamVendor": "Blinue / CuNNy contributors", + "mappingConfidence": "明确映射", + "mappingNotes": "上游仓库 Blinue/CuNNy 名称与效果组一致,README 自述“supports exporting to an mpv shader”和“Magpie effect”。", + "documentedInputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "https://raw.githubusercontent.com/Blinue/CuNNy/master/README.md", + "notes": "上游仓库只描述 mpv shader/Magpie effect,未给出纹理格式。" + } + ], + "documentedOutputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "https://raw.githubusercontent.com/Blinue/CuNNy/master/README.md", + "notes": "输出为上采样图像,格式未枚举。" + } + ], + "formatBoundary": { + "inputFormatEnumeration": "not_enumerated", + "outputFormatEnumeration": "not_enumerated", + "inputOutputRelation": "implementation_defined", + "alphaSemantics": "未说明。", + "rangeBoundary": "unknown", + "transferBoundary": "unspecified", + "resourceConstraints": "mpv shader 导出路径意味着由 mpv/libplacebo 管理 hook 纹理;无 API 资源约束。", + "hdrImplication": "无公开 HDR 格式契约。" + }, + "channelOrderAndAlpha": "无信息", + "precisionAndRange": "无信息", + "transferFunction": "无信息", + "primariesAndColorSpace": "无信息", + "hdrSupport": "无信息", + "hdrBoundary": "未说明", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "无信息", + "profileRationale": "无信息", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": [], + "documentedQualityOptions": [ + "8x32/4x3 等模型尺寸(来自 README,无格式协议语义)" + ], + "documentedPerformanceNotes": "无信息", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "OfficialRepository", + "sources": [ + { + "url": "https://raw.githubusercontent.com/Blinue/CuNNy/master/README.md", + "title": "CuNNy README", + "evidenceLevel": "OfficialRepository", + "claim": "上游仓库描述 CuNNy 可导出 mpv shader/Magpie effect,未给出 I/O 格式", + "quoteOrSymbol": "Supports exporting to an mpv meme shader!", + "searchRound": 1 + } + ], + "searchRoundsUsed": 2, + "unresolvedQuestions": [ + "输入输出纹理格式", + "HDR/FP16", + "alpha", + "数值范围" + ], + "conclusion": "CuNNy 上游可明确映射,但公开仓库没有图像协议细节,无法确定格式边界。" + }, + { + "effectGroup": "CuNNy2", + "effectFamily": "无信息(未能定位独立 CuNNy2 上游仓库/SDK)", + "upstreamVendor": "无信息", + "mappingConfidence": "无信息", + "mappingNotes": "公开搜索未找到与“CuNNy2”唯一对应的上游算法/模型/SDK 文档;现有结果多为 Magpie 版本页等,不可用于协议推断。", + "documentedInputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "公开搜索未发现独立 CuNNy2 上游", + "notes": "无法唯一映射到独立上游;不存在可归属的格式协议。" + } + ], + "documentedOutputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "公开搜索未发现独立 CuNNy2 上游", + "notes": "同上。" + } + ], + "formatBoundary": { + "inputFormatEnumeration": "not_enumerated", + "outputFormatEnumeration": "not_enumerated", + "inputOutputRelation": "unknown", + "alphaSemantics": "未说明。", + "rangeBoundary": "unknown", + "transferBoundary": "unspecified", + "resourceConstraints": "无上游资源约束可记录。", + "hdrImplication": "无公开 HDR 格式契约。" + }, + "channelOrderAndAlpha": "无信息", + "precisionAndRange": "无信息", + "transferFunction": "无信息", + "primariesAndColorSpace": "无信息", + "hdrSupport": "无信息", + "hdrBoundary": "未说明", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "无信息", + "profileRationale": "无信息", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": [], + "documentedQualityOptions": [], + "documentedPerformanceNotes": "无信息", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "无信息", + "sources": [], + "searchRoundsUsed": 2, + "unresolvedQuestions": [ + "CuNNy2 上游映射", + "格式", + "HDR/FP16", + "alpha" + ], + "conclusion": "CuNNy2 未能在公开资料中唯一映射到上游算法或 SDK,记录为无信息。" + }, + { + "effectGroup": "Diagnostics", + "effectFamily": "无信息(Diagnostics 是通用诊断效果名)", + "upstreamVendor": "无信息", + "mappingConfidence": "无信息", + "mappingNotes": "公开资料中不存在可作为上游协议的“Diagnostics”图像算法/SDK;不依据 Magpie 实现推断。", + "documentedInputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "公开资料无对应上游算法", + "notes": "Diagnostics 为通用诊断效果名,不存在公开算法/SDK 格式枚举。" + } + ], + "documentedOutputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "公开资料无对应上游算法", + "notes": "同上。" + } + ], + "formatBoundary": { + "inputFormatEnumeration": "not_enumerated", + "outputFormatEnumeration": "not_enumerated", + "inputOutputRelation": "unknown", + "alphaSemantics": "未说明。", + "rangeBoundary": "unknown", + "transferBoundary": "unspecified", + "resourceConstraints": "无资源约束可记录。", + "hdrImplication": "无公开 HDR 格式契约。" + }, + "channelOrderAndAlpha": "无信息", + "precisionAndRange": "无信息", + "transferFunction": "无信息", + "primariesAndColorSpace": "无信息", + "hdrSupport": "无信息", + "hdrBoundary": "未说明", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "无信息", + "profileRationale": "无信息", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": [], + "documentedQualityOptions": [], + "documentedPerformanceNotes": "无信息", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "无信息", + "sources": [], + "searchRoundsUsed": 1, + "unresolvedQuestions": [ + "上游映射", + "I/O 格式", + "HDR/FP16" + ], + "conclusion": "Diagnostics 无公开上游协议,记录为无信息。" + }, + { + "effectGroup": "DLSS", + "effectFamily": "NVIDIA DLSS Super Resolution (DLSS-SR)", + "upstreamVendor": "NVIDIA", + "mappingConfidence": "明确映射", + "mappingNotes": "NVIDIA DLSS SDK / Streamline 中的 DLSS Super Resolution,公共品牌与效果组一致。", + "documentedInputFormats": [ + "Color input: any supported buffer format for the API", + "Motion vectors: RG32_FLOAT or RG16_FLOAT", + "Depth: any one-channel format (R32_FLOAT/D32_FLOAT) or depth-stencil (D24S8)", + "Exposure: 1x1 texture, R16F preferred (presets J/K)" + ], + "documentedOutputFormats": [ + "Output buffer: any supported buffer format for the API", + "Previous output/history buffer: optional, RGBA16F" + ], + "formatBoundary": "官方编程指南要求输入为 SRV/read 状态,输出为 UAV/write 状态;颜色输入任意 API 支持格式,输出任意 API 支持格式;历史输出建议 RGBA16F;最小输出 32x32;无规定最大分辨率但 8K 以上未保证。", + "channelOrderAndAlpha": "Streamline 默认只放大 RGB;alphaUpscalingEnabled 为实验性时可放大 alpha;官方 DLSS 指南未规定固定通道顺序。", + "precisionAndRange": "LDR 模式颜色值必须 [0,1] 且为感知编码(如 sRGB),不能为线性;HDR 模式可处理线性高范围、无亮度上限,内部高精度。", + "transferFunction": "LDR: sRGB/perceptual encoding; HDR: linear space (IsHDR flag).", + "primariesAndColorSpace": "未说明 primaries;HDR 为线性场景/显示域未细分。", + "hdrSupport": "ExplicitHDR", + "hdrBoundary": "HDR 输入需将 IsHDR 置 1;需要 exposure(1x1 texture,R16F 首选)供当前帧使用;无 tone mapping 要求;输出仍同输入线性域。", + "requiredHdrMetadata": [ + "exposure value (1x1 texture)" + ], + "candidateInternalProfile": "DirectFP16", + "profileRationale": "官方明确 HDR 线性路径、无亮度上限且历史/输出使用/推荐 RGBA16F;有直接的 HDR/FP16 证据。", + "auxiliaryInputs": [ + "depth", + "motion vectors", + "exposure", + "jitter", + "previous output/history (optional)" + ], + "auxiliaryInputFormatsAndSemantics": "Depth any one-channel/depth-stencil; MVs RG32_FLOAT or RG16_FLOAT in pixel screen-space, can be low-res or high-res/dilated; exposure 1x1, R16F preferred; jitter in [-0.5,0.5] pixels at render res.", + "temporalOrFrameGenerationConstraints": "时域历史由 DLSS 内部维护,可选 previous output RGBA16F;reset/jump cut 语义见编程指南;动态分辨率需保持长宽比;mip bias 需要负偏移。", + "documentedQualityOptions": [ + "DLSS quality/balanced/performance/ultra-performance presets (公开质量档,格式不随档位变化)" + ], + "documentedPerformanceNotes": "官方说明 LDR 模式性能更优且内部量化到 8-bit;HDR 模式内部高精度。", + "qualityPerformanceEvidence": "官方指南有 LDR 性能更优说明,但未给出格式路径基准表。", + "evidenceLevel": "OfficialDocs", + "sources": [ + { + "url": "https://github.com/NVIDIA/DLSS/blob/main/doc/DLSS_Programming_Guide_Release.pdf", + "title": "NVIDIA DLSS Super Resolution Programming Guide Release", + "evidenceLevel": "OfficialDocs", + "claim": "Supported Formats: color any API format; MV RG32_FLOAT/RG16_FLOAT; depth any one-channel/depth-stencil; output any API format; history RGBA16F", + "quoteOrSymbol": "3.3 Supported Formats", + "searchRound": 1 + }, + { + "url": "https://raw.githubusercontent.com/NVIDIA/DLSS/main/doc/DLSS_Programming_Guide_Release.pdf", + "title": "DLSS Programming Guide PDF (raw)", + "evidenceLevel": "OfficialDocs", + "claim": "LDR [0,1] perceptual; HDR linear unbounded", + "quoteOrSymbol": "DLSS can process color data stored as either LDR or HDR", + "searchRound": 2 + }, + { + "url": "https://raw.githubusercontent.com/NVIDIA-RTX/Streamline/main/docs/ProgrammingGuideDLSS.md", + "title": "Streamline DLSS Super Resolution Integration", + "evidenceLevel": "OfficialRepository", + "claim": "DLSS-SR requires colorIn/colorOut/depth/mvec/exposure; colorBuffersHDR and alphaUpscalingEnabled options", + "quoteOrSymbol": "dlssOptions.colorBuffersHDR = sl::Boolean::eTrue", + "searchRound": 3 + } + ], + "searchRoundsUsed": 6, + "unresolvedQuestions": [ + "具体 DXGI 支持清单(官方用“any API format”而非枚举)", + "primaries", + "输出 alpha 默认值" + ], + "conclusion": "DLSS-SR 官方协议清晰:颜色输入输出可为 API 任意支持格式,HDR 线性模式通过 IsHDR 开启,运动向量固定 RG32/RG16_FLOAT,深度为单通道/深度模板,历史推荐 RGBA16F。" + }, + { + "effectGroup": "DLSSFG", + "effectFamily": "NVIDIA DLSS Frame Generation (DLSS-G / DLSS 4 Multi-Frame Gen)", + "upstreamVendor": "NVIDIA", + "mappingConfidence": "明确映射", + "mappingNotes": "Streamline/NVIDIA DLSS Frame Generation 官方文档与头文件。", + "documentedInputFormats": [ + "Backbuffer/Final Color resource (format not enumerated in public text)", + "Motion vectors/depth: same DLSS-SR requirements" + ], + "documentedOutputFormats": [ + "OutputInterpolated/OutputReal: same texture format as Backbuffer" + ], + "formatBoundary": "官方头文件写明 Required Output Texture (same texture format as Backbuffer);HUDless 与 UI 纹理需与 backbuffer 同尺寸/格式/色彩空间;backbuffer 资源经 Streamline swapchain 拦截。", + "channelOrderAndAlpha": "UI Alpha 为单通道 0..1;UI Color and Alpha 需预乘 alpha;HUDless 为无 UI 全场景颜色;alpha 参与 UI 合成。", + "precisionAndRange": "未公开列举 8/16 位格式;只有 colorBuffersHDR 布尔标志表示 HDR。", + "transferFunction": "未在公开文本中细分;HUDless 与 backbuffer 必须同色彩空间(含 tone mapping)。", + "primariesAndColorSpace": "未说明 primaries。", + "hdrSupport": "ExplicitHDR", + "hdrBoundary": "头文件有 full HDR 标志;未提供 PQ/HLG/scRGB 解码或 metadata 要求;应把与 backbuffer 相同色彩空间的帧交给帧生成。", + "requiredHdrMetadata": [ + "colorBuffersHDR flag", + "无公开 MaxCLL/MaxFALL/亮度要求" + ], + "candidateInternalProfile": "FormatOnlyNoColorContract", + "profileRationale": "官方确认 HDR 布尔和“输出与 backbuffer 同格式”,但未公开 HDR 的具体格式/色彩空间契约;不能判定 DirectFP16。", + "auxiliaryInputs": [ + "depth", + "motion vectors", + "HUD-less color", + "UI alpha or UI color+alpha", + "bidirectional distortion field (optional)", + "backbuffer subrect" + ], + "auxiliaryInputFormatsAndSemantics": "Depth/mvec same as DLSS-SR; HUDless/UI must match backbuffer; UI alpha 0..1; UI RGB premultiplied; distortion field maps distorted final color to undistorted guide buffers.", + "temporalOrFrameGenerationConstraints": "需要 dense motion vectors, depth, HUDless;插值输出与真实帧输出为同格式;帧生成要求 present 时资源仍有效;子矩形需 extent 匹配;UI 合成公式 Final.RGB = UI.RGB + (1-UI.Alpha)*HUDless.RGB。", + "documentedQualityOptions": [ + "插值帧数/模式等公开选项,未涉及格式" + ], + "documentedPerformanceNotes": "公开指南建议优先提供 UI Alpha 单通道以获得更好性能;无格式基准。", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "OfficialRepository", + "sources": [ + { + "url": "https://raw.githubusercontent.com/NVIDIA-RTX/Streamline/main/docs/ProgrammingGuideDLSS_G.md", + "title": "Streamline DLSS-G Programming Guide", + "evidenceLevel": "OfficialRepository", + "claim": "列出深度、运动向量、HUDless、UI 输入与资源生命周期约束", + "quoteOrSymbol": "Hudless ... same color space and post-processing effects as color backbuffer", + "searchRound": 1 + }, + { + "url": "https://raw.githubusercontent.com/NVIDIA/DLSS/main/include/nvsdk_ngx_defs_dlssg.h", + "title": "nvsdk_ngx_defs_dlssg.h", + "evidenceLevel": "OfficialHeader", + "claim": "Required Output Texture (same texture format as Backbuffer)", + "quoteOrSymbol": "#define NVSDK_NGX_DLSSG_Parameter_BackbufferFormat \"DLSSG.BackbufferFormat\"", + "searchRound": 2 + } + ], + "searchRoundsUsed": 5, + "unresolvedQuestions": [ + "实际接受/输出的 DXGI 格式枚举", + "HDR PQ/HLG/scRGB 颜色契约", + "metadata" + ], + "conclusion": "DLSSFG 官方公开材料确认输入由 swapchain/backbuffer 决定、输出必须与 backbuffer 同格式,并有 HDR 布尔开关;没有公开更细的格式枚举或 PQ/scRGB 协议。" + }, + { + "effectGroup": "FSR", + "effectFamily": "AMD FidelityFX Super Resolution 1 (FSR1 spatial upscaler)", + "upstreamVendor": "AMD", + "mappingConfidence": "明确映射", + "mappingNotes": "Magpie 效果组“FSR”按公开产品命名对应 FSR1 的空间超分;FSR2/3/4 已单列。", + "documentedInputFormats": [ + { + "format": "unspecified", + "apiOrContext": "algorithmic reference implementation (shader callback)", + "channelOrder": "RGB (3 channel)", + "numericRepresentation": "FLOAT/half", + "acceptanceStatus": "inferred_from_shader_interface", + "evidenceRef": "https://raw.githubusercontent.com/GPUOpen-Effects/FidelityFX-FSR/master/ffx-fsr/ffx_fsr1.h", + "notes": "官方 ffx_fsr1.h 提供 RGB 输入回调;未给出具体 DXGI 格式。" + }, + { + "format": "unspecified", + "apiOrContext": "DXGI/Vulkan/API resource", + "channelOrder": "RGB", + "numericRepresentation": "UNORM/FLOAT", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v1.0.0/docs/techniques/super-resolution-spatial.md", + "notes": "官方 FSR1 文档只要求 [0,1] perceptual sRGB,不枚举资源格式。" + } + ], + "documentedOutputFormats": [ + { + "format": "unspecified", + "apiOrContext": "algorithmic reference implementation (shader callback)", + "channelOrder": "RGB (3 channel)", + "numericRepresentation": "FLOAT/half", + "acceptanceStatus": "inferred_from_shader_interface", + "evidenceRef": "https://raw.githubusercontent.com/GPUOpen-Effects/FidelityFX-FSR/master/ffx-fsr/ffx_fsr1.h", + "notes": "EASU/RCAS 输出 RGB;最终输出表面格式由宿主分配。" + }, + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v1.0.0/docs/techniques/super-resolution-spatial.md", + "notes": "输出格式未在 FSR1 文档中单独列枚举。" + } + ], + "formatBoundary": { + "inputFormatEnumeration": "not_enumerated", + "outputFormatEnumeration": "not_enumerated", + "inputOutputRelation": "implementation_defined", + "alphaSemantics": "未说明。", + "rangeBoundary": "[0,1] perceptual sRGB;RCAS 负输入会产生 NaN。", + "transferBoundary": "sRGB / perceptual(非 linear);helper 另含 linear HDR {0 to FP16_MAX} 转换工具,但非核心输入契约。", + "resourceConstraints": "shader 回调 gather4;输入资源可为动态分辨率 viewport;建议 32bpp 格式;无 UAV/SRV 官方枚举。", + "hdrImplication": "FSR1 核心文档没有 HDR 直接输入路径;线性 HDR helper 属可选周边工具,不能算核心格式契约。" + }, + "channelOrderAndAlpha": "未说明 alpha。", + "precisionAndRange": "颜色归一化 [0,1];感知编码 sRGB。", + "transferFunction": "sRGB / perceptual(不是线性)。", + "primariesAndColorSpace": "未说明原色域(通常 sRGB,但资料未写 primaries)。", + "hdrSupport": "SDROnly", + "hdrBoundary": "官方文档面向 [0,1] sRGB 输入,未提供 HDR 线性/PQ/HLG 路径。", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "SDRCompatible", + "profileRationale": "官方明确要求 [0-1] perceptual sRGB、不能线性,未见 HDR 路径,故为 SDR 兼容。", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": [], + "documentedQualityOptions": [ + "EASU+RCAS 组合/quality mode 由调用方决定(官方未给出档位枚举)" + ], + "documentedPerformanceNotes": "无信息", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "OfficialDocs", + "sources": [ + { + "url": "https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v1.0.0/docs/techniques/super-resolution-spatial.md", + "title": "FidelityFX Super Resolution 1 (FSR1)", + "evidenceLevel": "OfficialDocs", + "claim": "Input normalized [0-1], perceptual sRGB not linear; negative RCAS input NaN", + "quoteOrSymbol": "Image should be normalized to [0-1] and be in perceptual color space (sRGB, not linear)", + "searchRound": 1 + }, + { + "url": "https://raw.githubusercontent.com/GPUOpen-Effects/FidelityFX-FSR/master/ffx-fsr/ffx_fsr1.h", + "title": "FidelityFX-FSR ffx_fsr1.h", + "evidenceLevel": "OfficialHeader", + "claim": "FSR1 通过 RGB 输入回调读取颜色;建议 32bpp 性能格式;RCAS 输入范围 {0 to 1}", + "quoteOrSymbol": "Color needs to be encoded as 3 channel [red, green, blue]", + "searchRound": 4 + } + ], + "searchRoundsUsed": 6, + "unresolvedQuestions": [ + "DXGI 格式清单", + "alpha", + "输出格式要求" + ], + "conclusion": "FSR1 官方资料可确认是 [0,1] sRGB 感知色彩输入的空间超分算法,没有 HDR/FP16 协议证据,候选为 SDRCompatible。" + }, + { + "effectGroup": "FSR2", + "effectFamily": "AMD FidelityFX Super Resolution 2 (FSR2 temporal upscaler)", + "upstreamVendor": "AMD", + "mappingConfidence": "明确映射", + "mappingNotes": "官方文档标题为 AMD FidelityFX Super Resolution 2.3.x。", + "documentedInputFormats": [ + "Color buffer: APPLICATION SPECIFIED (render res)", + "Depth: APPLICATION SPECIFIED (1x FLOAT)", + "Motion vectors: APPLICATION SPECIFIED (2x FLOAT)", + "Reactive mask: R8_UNORM", + "Transparency & composition mask: R8_UNORM", + "Exposure: R32_FLOAT (1x1)" + ], + "documentedOutputFormats": [ + "Adjusted color buffer (internal): R16G16B16A16_FLOAT", + "Upscaled buffer (internal): R16G16B16A16_FLOAT", + "API 输出缓冲的公开格式在文档中未单独枚举,一般随应用分配资源" + ], + "formatBoundary": "颜色输入由应用指定格式;HDR 需设置 HDR flag;深度单浮点;MVs 2x float 屏幕空间范围 [<-w,-h>,];内部多使用 16-bit;输出最终与输入同域(内部 tonemap 会被反转);要求 GPU 支持 typed UAV load 和 R16G16B16A16_UNORM。", + "channelOrderAndAlpha": "内部 Adjusted color 使用 YCoCg 且 alpha 为 disocclusion mask;对外 alpha 契约未说明。", + "precisionAndRange": "颜色可为应用指定的 UNORM/FLOAT 等;HDR 线性域;MVs 内部 16-bit;exposure R32_FLOAT。", + "transferFunction": "HDR 模式要求 linear;SDK 后续也允许 non-linear 标志(见 FSR4/upscaler 文档)。", + "primariesAndColorSpace": "未说明 primaries。", + "hdrSupport": "ExplicitHDR", + "hdrBoundary": "设置 FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE 后输入应为 linear;输出恢复原输入域;需要 pre-exposure/exposure 语义。", + "requiredHdrMetadata": [ + "pre-exposure", + "exposure (R32_FLOAT) 或 auto-exposure" + ], + "candidateInternalProfile": "DirectFP16", + "profileRationale": "官方内部表面明确 R16G16B16A16_FLOAT、HDR 线性支持,且有 FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE 直接路径。", + "auxiliaryInputs": [ + "depth", + "motion vectors", + "reactive mask", + "transparency & composition mask", + "exposure", + "camera jitter", + "reset" + ], + "auxiliaryInputFormatsAndSemantics": "MVs in pixels screen-space, low-res/display-res flag; reactive/T&C mask R8_UNORM [0,1]; exposure R32_FLOAT optional; depth flags inverted/infinite.", + "temporalOrFrameGenerationConstraints": "时域算法需要当前/上一帧数据与历史;所有 render-res 输入需 jitter(MV 例外);camera jump cut 需 reset;FrameTimeDelta 输入。", + "documentedQualityOptions": [ + "quality/balanced/performance 等调用方档位(API 主要按 input/output 尺寸)" + ], + "documentedPerformanceNotes": "内部 MVs 使用 16-bit,高精度输入不会额外受益;官方要求 R16G16B16A16_UNORM 支持;无公开格式路径性能基准。", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "OfficialRepository", + "sources": [ + { + "url": "https://github.com/GPUOpen-Effects/FidelityFX-FSR2/blob/master/README.md", + "title": "FidelityFX-FSR2 README", + "evidenceLevel": "OfficialRepository", + "claim": "Input resource table and internal R16G16B16A16_FLOAT buffers", + "quoteOrSymbol": "Upscaled buffer ... R16G16B16A16_FLOAT", + "searchRound": 1 + }, + { + "url": "https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/main/Kits/FidelityFX/docs/techniques/super-resolution-temporal.md", + "title": "FidelityFX Super Resolution 2.3.4", + "evidenceLevel": "OfficialDocs", + "claim": "Input resources table, HDR support, linear color", + "quoteOrSymbol": "High dynamic range images are supported... linear color space", + "searchRound": 2 + } + ], + "searchRoundsUsed": 4, + "unresolvedQuestions": [ + "对外输出 FfxResource 的格式枚举", + "alpha 对外行为", + "primaries" + ], + "conclusion": "FSR2 官方协议:颜色输入为应用指定格式,HDR 需 linear 输入与 HDR flag;辅助资源格式明确;内部浮点表面 R16G16B16A16_FLOAT。" + }, + { + "effectGroup": "FSR3", + "effectFamily": "AMD FidelityFX Super Resolution 3.1 Upscaler (FSR3 SR)", + "upstreamVendor": "AMD", + "mappingConfidence": "明确映射", + "mappingNotes": "FSR3 效果组拆为“FSR3 Upscaler”与“FSR3 Frame Interpolation”两个后端;此处为官方 FSR3 3.1.x Upscaler。", + "documentedInputFormats": [ + "Color buffer: APPLICATION SPECIFIED", + "Depth: APPLICATION SPECIFIED (1x FLOAT)", + "Motion vectors: APPLICATION SPECIFIED (2x FLOAT)", + "Reactive mask: R8_UNORM", + "T&C mask: R8_UNORM", + "Exposure: R32_FLOAT" + ], + "documentedOutputFormats": [ + "未单独枚举;文档称输出与原始输入同域(internal tonemap 被反转)" + ], + "formatBoundary": "与 FSR2 输入结构一致;HDR flag 开启时输入 linear;要求 R16G16B16A16_UNORM typed UAV 支持;API 未在文档中列 DXGI 输出枚举。", + "channelOrderAndAlpha": "对外 alpha 未说明。", + "precisionAndRange": "颜色应用指定;HDR linear;MVs 2x float;内部 16-bit。", + "transferFunction": "HDR linear;文档未给出额外非线性颜色自动转换。", + "primariesAndColorSpace": "未说明。", + "hdrSupport": "ExplicitHDR", + "hdrBoundary": "设置 FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE,线性输入,输出恢复原输入域;需要 exposure/pre-exposure。", + "requiredHdrMetadata": [ + "pre-exposure", + "exposure", + "auto-exposure flag 可选" + ], + "candidateInternalProfile": "DirectFP16", + "profileRationale": "官方 HDR 线性直接路径 + 内部浮点表面证据与 FSR2 同类。", + "auxiliaryInputs": [ + "depth", + "motion vectors", + "reactive mask", + "T&C mask", + "exposure", + "jitter", + "reset", + "frame time delta" + ], + "auxiliaryInputFormatsAndSemantics": "同 FSR2:depth float、MV 2x float、R8_UNORM masks、R32_FLOAT exposure。", + "temporalOrFrameGenerationConstraints": "时域超分要求 jitter、reset、历史;FrameTimeDelta 输入。", + "documentedQualityOptions": [ + "FSR 质量/性能档由应用选择(未在格式文档中)" + ], + "documentedPerformanceNotes": "未找到可核实的格式路径性能结论。", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "OfficialDocs", + "sources": [ + { + "url": "https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/main/Kits/FidelityFX/docs/techniques/super-resolution-upscaler.md", + "title": "AMD FidelityFX Super Resolution 3.1.5 Upscaler", + "evidenceLevel": "OfficialDocs", + "claim": "FSR3 Upscaler input resources and HDR linear support", + "quoteOrSymbol": "FSR Super Resolution ... HDR support", + "searchRound": 1 + } + ], + "searchRoundsUsed": 2, + "unresolvedQuestions": [ + "输出格式枚举", + "alpha", + "primaries" + ], + "conclusion": "FSR3 Upscaler 与 FSR2 的输入/辅助资源/HDR linear 协议高度一致;公开文档未列外部输出格式枚举。" + }, + { + "effectGroup": "FSR3", + "effectFamily": "AMD FidelityFX Super Resolution 3 Frame Interpolation / Frame Generation (FSR3 FG)", + "upstreamVendor": "AMD", + "mappingConfidence": "明确映射", + "mappingNotes": "FSR3 的帧生成/插值后端,官方技术名 FidelityFX Frame Interpolation;拆为独立子条目。", + "documentedInputFormats": [ + "currentBackBuffer (presentation color buffer)", + "currentBackBuffer_HUDLess (optional)", + "depth (required for FSR3 interpolation workflow)", + "motion vectors (required)", + "R16G16_SINT optical flow vector", + "R32_UINT optical flow SCD" + ], + "documentedOutputFormats": [ + "interpolated output resource", + "outputSwapChainBuffer", + "格式未在公开文档列枚举;代码示例用 swap chain back buffer format" + ], + "formatBoundary": "官方文档要求传入 backBufferFormat;HUD-less/UI 资源与 backbuffer 关系密切;帧生成需要超分先行;输入资源格式由配置描述,未列完整 DXGI 清单。", + "channelOrderAndAlpha": "HUDLess/UI 合成涉及 alpha/UI 内容;具体 alpha 格式未完整说明。", + "precisionAndRange": "未公开逐格式数值范围;有 FFX_FRAMEINTERPOLATION_ENABLE_HDR_COLOR_INPUT 与 minMaxLuminance。", + "transferFunction": "config 含 backBufferTransferFunction 用于把插值源转换到 linear RGB;HDR 转换依赖 minMaxLuminance。", + "primariesAndColorSpace": "未说明 primaries;backBufferTransferFunction 枚举存在。", + "hdrSupport": "ExplicitHDR", + "hdrBoundary": "需设置 HDR_COLOR_INPUT;通过 backBufferTransferFunction 与 minMaxLuminance 把 HDR 颜色转到 linear RGB;未公开 MaxCLL/MaxFALL 字段。", + "requiredHdrMetadata": [ + "backBufferTransferFunction", + "minMaxLuminance[2]" + ], + "candidateInternalProfile": "BoundedHDR", + "profileRationale": "官方明确 HDR 颜色输入需经 transfer function + min/max luminance 转换为线性 RGB,即有界/转换契约,而非直接 FP16 无 metadata 路径。", + "auxiliaryInputs": [ + "depth", + "game motion vectors", + "FidelityFX Optical Flow vectors", + "HUDLess color", + "UI/HUD handling (presentCallback or HUDLessColor)" + ], + "auxiliaryInputFormatsAndSemantics": "depth inverted/infinite flags; opticalFlowVector R16G16_SINT; opticalFlowSCD R32_UINT; HUDLess optional FfxResource.", + "temporalOrFrameGenerationConstraints": "帧生成需要 previous/current frame、光流、深度、运动向量;presentCallback/HUDLess 用于 UI;重置、延迟、显示分辨率限制在 FSR3 集成指南。", + "documentedQualityOptions": [ + "FSR3 frame generation on/off, UI composition modes 等(无格式档位)" + ], + "documentedPerformanceNotes": "未找到可核实的格式路径性能结论。", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "OfficialDocs", + "sources": [ + { + "url": "https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/release-FSR3-3.0.3/docs/techniques/frame-interpolation.md", + "title": "FidelityFX Frame Interpolation", + "evidenceLevel": "OfficialDocs", + "claim": "Config includes HDR flag, backBufferTransferFunction, minMaxLuminance; optical flow outputs", + "quoteOrSymbol": "FFX_FRAMEINTERPOLATION_ENABLE_HDR_COLOR_INPUT", + "searchRound": 1 + }, + { + "url": "https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/release-FSR3-3.0.3/docs/techniques/optical-flow.md", + "title": "FidelityFX Optical Flow", + "evidenceLevel": "OfficialDocs", + "claim": "Optical flow vector R16G16_SINT, SCD R32_UINT", + "quoteOrSymbol": "R16G16_SINT ... R32_UINT", + "searchRound": 2 + } + ], + "searchRoundsUsed": 3, + "unresolvedQuestions": [ + "具体 backbuffer 格式支持清单", + "HDR PQ/scRGB 细节", + "alpha 行为" + ], + "conclusion": "FSR3 Frame Generation 官方协议确认需要 backbuffer 格式、HDR 输入标志和 transfer/luminance 参数;外部格式仍由 swapchain/backbuffer 决定,未公开完整格式枚举。" + }, + { + "effectGroup": "FSR4", + "effectFamily": "AMD FidelityFX Super Resolution 4 (FSR4 ML Super Resolution)", + "upstreamVendor": "AMD", + "mappingConfidence": "明确映射", + "mappingNotes": "官方 FidelityFX SDK 文档标题“AMD FSR Super Resolution 4”。", + "documentedInputFormats": [ + "Color buffer: APPLICATION SPECIFIED", + "Depth: APPLICATION SPECIFIED (1x FLOAT)", + "Motion vectors: APPLICATION SPECIFIED (2x FLOAT)", + "Exposure: R32_FLOAT (1x1)" + ], + "documentedOutputFormats": [ + "未单独枚举;文档称输出与原始输入同域" + ], + "formatBoundary": "颜色输入必须 linear,除非设置 NON_LINEAR_COLORSPACE 标志;没有 DXGI 格式枚举,颜色格式由应用指定;MVs 2x float;深度 float;输出同输入域。", + "channelOrderAndAlpha": "未说明。", + "precisionAndRange": "linear HDR/scene-linear 输入,或 non-linear 标志;内部 ML 处理;精度未公开。", + "transferFunction": "linear(推荐);非线性的可选标志。", + "primariesAndColorSpace": "未说明。", + "hdrSupport": "ExplicitHDR", + "hdrBoundary": "HDR 线性输入为推荐;输出恢复同输入域;需要 exposure/pre-exposure 管理;无 PQ/HLG 自动解码说明。", + "requiredHdrMetadata": [ + "pre-exposure", + "exposure R32_FLOAT", + "auto-exposure 可选" + ], + "candidateInternalProfile": "DirectFP16", + "profileRationale": "官方 HDR linear 直接路径、无色彩空间自动处理,说明有直接浮点/HDR 路径。", + "auxiliaryInputs": [ + "depth", + "motion vectors", + "exposure", + "jitter", + "reset" + ], + "auxiliaryInputFormatsAndSemantics": "同 FSR2/3 upscaler 协议;MVs 屏幕空间像素范围。", + "temporalOrFrameGenerationConstraints": "时域 ML 超分需 jitter、reset、历史/帧时间;无公开帧生成。", + "documentedQualityOptions": [ + "FSR4 质量/性能档(未在格式文档中)" + ], + "documentedPerformanceNotes": "未找到可核实的格式路径性能结论。", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "OfficialDocs", + "sources": [ + { + "url": "https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/main/Kits/FidelityFX/docs/techniques/super-resolution-ml.md", + "title": "AMD FSR Super Resolution 4", + "evidenceLevel": "OfficialDocs", + "claim": "FSR4 input resources, HDR linear and non-linear colorspace flags", + "quoteOrSymbol": "High dynamic range images are supported... linear color space", + "searchRound": 1 + } + ], + "searchRoundsUsed": 2, + "unresolvedQuestions": [ + "输出格式枚举", + "alpha", + "primaries", + "ML 模型输入张量格式" + ], + "conclusion": "FSR4 官方协议说明颜色输入为应用指定的 linear(或 non-linear 标志)HDR 可支持,辅助资源与 FSR2/3 一致;外部输出格式未公开。" + }, + { + "effectGroup": "FSRCNNX", + "effectFamily": "FSRCNNX (基于 FSRCNN 的 mpv/着色器变体)", + "upstreamVendor": "igv / FSRCNN community?", + "mappingConfidence": "推测映射", + "mappingNotes": "公开上可找到 FSRCNN-TensorFlow 项目与 mpv FSRCNNX 发布物,但“FSRCNNX”本身没有独立官方 SDK 文档。", + "documentedInputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "https://raw.githubusercontent.com/igv/FSRCNN-TensorFlow/master/README.md", + "notes": "FSRCNNX 无独立官方 SDK;基础 FSRCNN 仓库只说明 TensorFlow 模型,未给出 mpv shader 的纹理格式。" + } + ], + "documentedOutputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "https://raw.githubusercontent.com/igv/FSRCNN-TensorFlow/master/README.md", + "notes": "同上;输出格式未枚举。" + } + ], + "formatBoundary": { + "inputFormatEnumeration": "not_enumerated", + "outputFormatEnumeration": "not_enumerated", + "inputOutputRelation": "implementation_defined", + "alphaSemantics": "未说明。", + "rangeBoundary": "unknown", + "transferBoundary": "unspecified", + "resourceConstraints": "社区 mpv 发布物/模型尺寸存在,但无统一资源状态或尺寸约束文档。", + "hdrImplication": "无公开 HDR 格式契约。" + }, + "channelOrderAndAlpha": "无信息", + "precisionAndRange": "无信息", + "transferFunction": "无信息", + "primariesAndColorSpace": "无信息", + "hdrSupport": "无信息", + "hdrBoundary": "未说明", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "无信息", + "profileRationale": "无信息", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": [], + "documentedQualityOptions": [ + "不同 FSRCNNX 模型/尺寸由社区发布(无协议级信息)" + ], + "documentedPerformanceNotes": "无信息", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "GitHubExperiment", + "sources": [ + { + "url": "https://raw.githubusercontent.com/igv/FSRCNN-TensorFlow/master/README.md", + "title": "FSRCNN-TensorFlow", + "evidenceLevel": "GitHubExperiment", + "claim": "底层 FSRCNN TensorFlow 实现;README 提到增加 RGB 支持", + "quoteOrSymbol": "Add RGB support", + "searchRound": 1 + } + ], + "searchRoundsUsed": 5, + "unresolvedQuestions": [ + "FSRCNNX 独立仓库/头文件", + "输入输出格式", + "HDR/FP16" + ], + "conclusion": "FSRCNNX 只能推测映射到 FSRCNN 的着色器变体,公开协议资料不足,记录为无信息。" + }, + { + "effectGroup": "FXAA", + "effectFamily": "FXAA (Fast Approximate Anti-Aliasing)", + "upstreamVendor": "Timothy Lottes / NVIDIA", + "mappingConfidence": "明确映射", + "mappingNotes": "算法名称明确对应 Lottes 的 FXAA;公开实现众多,但无单一官方 SDK 协议文档。", + "documentedInputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "无官方统一来源", + "notes": "FXAA 算法无单一 SDK;多引擎实现把输入绑定为 2D 颜色纹理。" + }, + { + "format": "unspecified", + "apiOrContext": "WebGPU/engine reference implementation (bevy FXAA)", + "channelOrder": "RGBA", + "numericRepresentation": "FLOAT (sampled as vec4)", + "acceptanceStatus": "reference_implementation_only", + "evidenceRef": "https://raw.githubusercontent.com/JMS55/bevy/236aa4e2fc93900b6ada7151b0d8e0567e6650f2/crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl", + "notes": "社区/引擎实现使用 texture_2d 采样并输出 alpha 透传;不代表 FXAA 通用格式契约。" + } + ], + "documentedOutputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "无官方统一来源", + "notes": "FXAA 输出通常为同尺寸颜色纹理,但无统一格式枚举。" + }, + { + "format": "unspecified", + "apiOrContext": "WebGPU/engine reference implementation (bevy FXAA)", + "channelOrder": "RGBA", + "numericRepresentation": "FLOAT", + "acceptanceStatus": "reference_implementation_only", + "evidenceRef": "https://raw.githubusercontent.com/JMS55/bevy/236aa4e2fc93900b6ada7151b0d8e0567e6650f2/crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl", + "notes": "bevy 实现返回 vec4(finalColor, centerSample.a),alpha 透传;仅参考实现。" + } + ], + "formatBoundary": { + "inputFormatEnumeration": "not_enumerated", + "outputFormatEnumeration": "not_enumerated", + "inputOutputRelation": "implementation_defined", + "alphaSemantics": "参考实现可透传 alpha;官方/原算法无统一声明。", + "rangeBoundary": "unknown", + "transferBoundary": "unspecified", + "resourceConstraints": "shader 输入输出通常同尺寸;无统一 UAV/SRV 契约。", + "hdrImplication": "无官方 HDR 格式契约;个别引擎实现可用浮点纹理,但不能代表算法协议。" + }, + "channelOrderAndAlpha": "无信息", + "precisionAndRange": "无信息", + "transferFunction": "无信息", + "primariesAndColorSpace": "无信息", + "hdrSupport": "无信息", + "hdrBoundary": "未说明", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "无信息", + "profileRationale": "没有可引用为统一协议的官方格式说明。", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": [], + "documentedQualityOptions": [ + "FXAA quality presets 由各实现定义,未在本调研确认" + ], + "documentedPerformanceNotes": "无信息", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "无信息", + "sources": [ + { + "url": "https://raw.githubusercontent.com/JMS55/bevy/236aa4e2fc93900b6ada7151b0d8e0567e6650f2/crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl", + "title": "Bevy FXAA shader (NVIDIA FXAA 3.11 port)", + "evidenceLevel": "GitHubExperiment", + "claim": "bevy 实现以 texture_2d 读取颜色,输出 RGBA float 并透传 alpha", + "quoteOrSymbol": "return vec4(finalColor, centerSample.a);", + "searchRound": 3 + } + ], + "searchRoundsUsed": 4, + "unresolvedQuestions": [ + "固定输入格式", + "HDR/FP16", + "色彩空间" + ], + "conclusion": "FXAA 可明确映射到 Lottes 的公开算法,但缺少可作为“外置效果层协议”的统一官方格式契约,记录为无信息。" + }, + { + "effectGroup": "MLAA", + "effectFamily": "MLAA (Morphological Anti-Aliasing)", + "upstreamVendor": "Intel Labs / Alexander Reshetov", + "mappingConfidence": "明确映射", + "mappingNotes": "MLAA 为公开论文/算法名称;无单一 SDK 官方协议。", + "documentedInputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "无公开统一来源", + "notes": "MLAA 是论文/算法族,无单一官方 SDK 格式枚举。" + } + ], + "documentedOutputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "无公开统一来源", + "notes": "输出格式未枚举。" + } + ], + "formatBoundary": { + "inputFormatEnumeration": "not_enumerated", + "outputFormatEnumeration": "not_enumerated", + "inputOutputRelation": "unknown", + "alphaSemantics": "未说明。", + "rangeBoundary": "unknown", + "transferBoundary": "unspecified", + "resourceConstraints": "无统一资源约束可记录。", + "hdrImplication": "无公开 HDR 格式契约。" + }, + "channelOrderAndAlpha": "无信息", + "precisionAndRange": "无信息", + "transferFunction": "无信息", + "primariesAndColorSpace": "无信息", + "hdrSupport": "无信息", + "hdrBoundary": "未说明", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "无信息", + "profileRationale": "无信息", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": [], + "documentedQualityOptions": [], + "documentedPerformanceNotes": "无信息", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "无信息", + "sources": [], + "searchRoundsUsed": 1, + "unresolvedQuestions": [ + "上游 SDK/实现", + "格式", + "HDR/FP16" + ], + "conclusion": "MLAA 可映射到公开形态抗锯齿算法,但未找到统一的图像 I/O 协议资料,记录为无信息。" + }, + { + "effectGroup": "NIS", + "effectFamily": "NVIDIA Image Scaling SDK (NVScaler/NVSharpen)", + "upstreamVendor": "NVIDIA", + "mappingConfidence": "明确映射", + "mappingNotes": "NVIDIA Image Scaling SDK 官方仓库与 Streamline 插件。", + "documentedInputFormats": [ + "Input/output: non-integer data types, examples DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_NV12 (NV12 via NIS_NV12_SUPPORT)", + "HDR modes: LDR [0,1], HDR PQ [0,1], HDR Linear recommended [0,12.5]" + ], + "documentedOutputFormats": [ + "Output UAV: non-integer formats, same docs examples (R8G8B8A8_UNORM/NV12); shader writes RWTexture2D" + ], + "formatBoundary": "输入为 SRV/read state,输出 UAV/write;sampler 必须 linear clamp;支持 viewport 子区域;NV12 需编译开关 NIS_NV12_SUPPORT;系数纹理 R32G32B32A32_FLOAT 或 R16G16B16A16_FLOAT。", + "channelOrderAndAlpha": "颜色 RGBA/BGRA 由纹理格式决定;NV12 为 Y plane + interleaved UV;alpha 行为未公开。", + "precisionAndRange": "LDR/PQ [0,1];HDR Linear [0,12.5] 约 1000nits;fp16/fp32 系数可选;不支持整数格式。", + "transferFunction": "LDR: display-referred after OETF (sRGB/gamma); HDR PQ: Rec.2020 PQ OETF; HDR Linear: scene/display linear.", + "primariesAndColorSpace": "PQ 模式说明 Rec.2020 PQ;HDR Linear 按 BT.709 luma 参考;其余未说明。", + "hdrSupport": "ExplicitHDR", + "hdrBoundary": "需通过 NIS_HDR_MODE 选 NONE/LINEAR/PQ;PQ 输入应为 display-referred Rec.2020 PQ;线性 HDR 建议 [0,12.5];没有额外 metadata 参数。", + "requiredHdrMetadata": [ + "NIS_HDR_MODE", + "无 MaxCLL/MaxFALL/pre-exposure 参数" + ], + "candidateInternalProfile": "DirectFP16", + "profileRationale": "官方支持 HDR Linear/PQ 模式,系数可 FP16,且对 HDR 范围有明确文档,属于直接 HDR 路径。", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": "空间算法,无时域/历史/帧生成约束。", + "documentedQualityOptions": [ + "NIS_HDR_MODE", + "NIS_NV12_SUPPORT", + "NIS_VIEWPORT_SUPPORT", + "NIS_CLAMP_OUTPUT" + ], + "documentedPerformanceNotes": "README 未比较各格式成本。", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "OfficialRepository", + "sources": [ + { + "url": "https://raw.githubusercontent.com/NVIDIAGameWorks/NVIDIAImageScaling/main/README.md", + "title": "NVIDIA Image Scaling SDK README", + "evidenceLevel": "OfficialRepository", + "claim": "Color spaces/ranges and supported texture formats", + "quoteOrSymbol": "Input and output formats are expected... DXGI_FORMAT_R8G8B8A8_UNORM or DXGI_FORMAT_NV12", + "searchRound": 1 + }, + { + "url": "https://raw.githubusercontent.com/NVIDIA-RTX/Streamline/main/docs/ProgrammingGuideNIS.md", + "title": "Streamline NIS Programming Guide", + "evidenceLevel": "OfficialRepository", + "claim": "NIS Streamline integration uses colorIn/colorOut tags and hdrMode option", + "quoteOrSymbol": "nisOptions.hdrMode = NISHDR::eNISHDRNone", + "searchRound": 2 + } + ], + "searchRoundsUsed": 4, + "unresolvedQuestions": [ + "NV12 是否同时作为输出格式", + "alpha 具体行为", + "PQ 输入是否 10-bit/12-bit" + ], + "conclusion": "NIS 官方协议明确支持 R8G8B8A8_UNORM 与 NV12 类非整数格式,支持 LDR/PQ/Linear 三种色彩范围,并有 HDR mode 开关。" + }, + { + "effectGroup": "NNEDI3", + "effectFamily": "NNEDI3", + "upstreamVendor": "Kevin Stone / community (mpv prescaler by bjin)", + "mappingConfidence": "明确映射", + "mappingNotes": "NNEDI3 为公开神经网络倍线算法;Magpie 效果组对应实现可能是 bjin mpv-prescalers 中的 luma prescaler,但未依据 Magpie 源码确认。", + "documentedInputFormats": [ + { + "format": "unspecified", + "apiOrContext": "mpv user shader hook (luma plane)", + "channelOrder": "luma", + "numericRepresentation": "unspecified", + "acceptanceStatus": "inferred_from_shader_interface", + "evidenceRef": "https://raw.githubusercontent.com/bjin/mpv-prescalers/master/README.md", + "notes": "bjin README 表示 nnedi3 只放大 YUV 的 luma plane;外部输入格式由 mpv 提供,未枚举。" + }, + { + "format": "rgba16f / rgba16hf", + "apiOrContext": "mpv user shader internal intermediate", + "channelOrder": "RGBA", + "numericRepresentation": "FLOAT (16-bit)", + "acceptanceStatus": "inferred_from_shader_interface", + "evidenceRef": "https://raw.githubusercontent.com/bjin/mpv-prescalers/master/README.md", + "notes": "README 关于 rgba16f/rgba16hf 的说明属于 mpv 用户着色器环境中的中间表面,不是 NNEDI3 算法 SDK 契约。" + } + ], + "documentedOutputFormats": [ + { + "format": "unspecified", + "apiOrContext": "mpv user shader hook output", + "channelOrder": "luma", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "https://raw.githubusercontent.com/bjin/mpv-prescalers/master/README.md", + "notes": "输出为放大后的 luma 平面;无原生后端格式枚举。" + }, + { + "format": "rgba16f / rgba16hf", + "apiOrContext": "mpv user shader internal intermediate", + "channelOrder": "RGBA", + "numericRepresentation": "FLOAT (16-bit)", + "acceptanceStatus": "inferred_from_shader_interface", + "evidenceRef": "https://raw.githubusercontent.com/bjin/mpv-prescalers/master/README.md", + "notes": "同输入条目说明,仅中间表面。" + } + ], + "formatBoundary": { + "inputFormatEnumeration": "partial", + "outputFormatEnumeration": "partial", + "inputOutputRelation": "implementation_defined", + "alphaSemantics": "未说明。", + "rangeBoundary": "luma only / unknown", + "transferBoundary": "unspecified", + "resourceConstraints": "luma-only 放大;chroma 由宿主 --cscale 处理;mpv 老 d3d11 驱动需要 rgba16hf 分支。", + "hdrImplication": "rgba16f 只证明 mpv user shader 中间表面,不构成 HDR API 契约。" + }, + "channelOrderAndAlpha": "README 表示 nnedi3/ravu 只放大 YUV 的 luma 平面;RGB 变体另有 ravu-rgb。", + "precisionAndRange": "rgba16f / rgba16hf(fp16);来自 mpv user shader 内部格式。", + "transferFunction": "未说明。", + "primariesAndColorSpace": "未说明。", + "hdrSupport": "FormatOnlyNoColorContract", + "hdrBoundary": "mpv user shader 内部处理浮点,但 README 未提供 HDR/PQ/linear 契约;不能作为厂商 HDR 保证。", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "DirectFP16", + "profileRationale": "仅有的明确格式证据是 mpv prescaler 使用 rgba16f/rgba16hf 内部表面;这是用户着色器路径而非官方 SDK 契约。", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": [], + "documentedQualityOptions": [ + "nnedi3 的 neurons/window 在文件名中(bjin 仓库)" + ], + "documentedPerformanceNotes": "无信息", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "OfficialRepository", + "sources": [ + { + "url": "https://raw.githubusercontent.com/bjin/mpv-prescalers/master/README.md", + "title": "bjin/mpv-prescalers README", + "evidenceLevel": "OfficialRepository", + "claim": "Shaders use rgba16f internal format; nnedi3/ravu are luma prescalers", + "quoteOrSymbol": "Unrecognized/unavailable FORMAT name: rgba16f", + "searchRound": 1 + } + ], + "searchRoundsUsed": 2, + "unresolvedQuestions": [ + "独立 NNEDI3 原生后端的格式契约", + "HDR 颜色空间" + ], + "conclusion": "NNEDI3 的 mpv 用户着色器资料显示内部使用 rgba16f/rgba16hf 并只处理 luma;没有可作为 Magpie 外置后端协议的官方格式列表。" + }, + { + "effectGroup": "Pixel Art", + "effectFamily": "无信息(Pixel Art 泛指像素画放大算法族)", + "upstreamVendor": "无信息", + "mappingConfidence": "无信息", + "mappingNotes": "公开搜索出现 libretro pixel-art enhancement systems、xBRZ、Kopf-Lischinski 等多种算法,无法唯一确定 Magpie 对应后端。", + "documentedInputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "公开搜索仅见多种像素画算法", + "notes": "无法唯一映射;不得把 xBRZ/Scale2x/Kopf-Lischinski 等某一算法格式写成该效果组已支持协议。" + } + ], + "documentedOutputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "公开搜索仅见多种像素画算法", + "notes": "同上。" + } + ], + "formatBoundary": { + "inputFormatEnumeration": "not_enumerated", + "outputFormatEnumeration": "not_enumerated", + "inputOutputRelation": "unknown", + "alphaSemantics": "未说明。", + "rangeBoundary": "unknown", + "transferBoundary": "unspecified", + "resourceConstraints": "无统一资源约束可记录。", + "hdrImplication": "无公开 HDR 格式契约。" + }, + "channelOrderAndAlpha": "无信息", + "precisionAndRange": "无信息", + "transferFunction": "无信息", + "primariesAndColorSpace": "无信息", + "hdrSupport": "无信息", + "hdrBoundary": "未说明", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "无信息", + "profileRationale": "无信息", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": [], + "documentedQualityOptions": [], + "documentedPerformanceNotes": "无信息", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "无信息", + "sources": [], + "searchRoundsUsed": 1, + "unresolvedQuestions": [ + "具体像素画算法映射", + "格式", + "HDR/FP16" + ], + "conclusion": "Pixel Art 组无法唯一映射到公开算法,记录为无信息。" + }, + { + "effectGroup": "RAVU", + "effectFamily": "RAVU (Rapid and Accurate Video Upscaling)", + "upstreamVendor": "bjin / mpv-prescalers contributors", + "mappingConfidence": "明确映射", + "mappingNotes": "公开仓库 bjin/mpv-prescalers 的 RAVU 系列。", + "documentedInputFormats": [ + "mpv user-shader internal rgba16f/rgba16hf", + "ravu-yuv assumes YUV video after planes merged", + "ravu-rgb operates on RGB after planes merged" + ], + "documentedOutputFormats": [ + "内部 rgba16f/rgba16hf 表面" + ], + "formatBoundary": "作为 mpv user shader 使用;gather/compute 版本分别用 textureGather/compute;d3d11 老驱动可用 rgba16hf 分支;ravu/ravu-lite 仅放大 luma 平面;ravu-yuv 需 YUV,ravu-rgb 需 RGB;ravu-zoom 任意比例。", + "channelOrderAndAlpha": "YUV 或 RGB 变体;未单独说明 alpha。", + "precisionAndRange": "rgba16f/rgba16hf(16-bit float);未说明数值范围上限。", + "transferFunction": "未说明。", + "primariesAndColorSpace": "未说明。", + "hdrSupport": "FormatOnlyNoColorContract", + "hdrBoundary": "内部 FP16 浮点表面不等于厂商 HDR/PQ 契约;README 未说明 HDR 线性/PQ。", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "DirectFP16", + "profileRationale": "明确内部 rgba16f/rgba16hf,但无颜色/光域契约;只能作为格式级 FP16 证据。", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": [], + "documentedQualityOptions": [ + "ravu-lite", + "ravu", + "ravu-zoom", + "-ar anti-ringing 变体" + ], + "documentedPerformanceNotes": "README 说明 gather 通常较快、compute 对 yuv/rgb 更快,但无数值基准。", + "qualityPerformanceEvidence": "README 有定性速度说明,无可核实格式路径性能数据。", + "evidenceLevel": "OfficialRepository", + "sources": [ + { + "url": "https://raw.githubusercontent.com/bjin/mpv-prescalers/master/README.md", + "title": "bjin/mpv-prescalers README", + "evidenceLevel": "OfficialRepository", + "claim": "RAVU variants and rgba16f/rgba16hf formats", + "quoteOrSymbol": "rgba16f ... rgba16hf", + "searchRound": 1 + } + ], + "searchRoundsUsed": 2, + "unresolvedQuestions": [ + "外部原生后端的格式协议", + "HDR/色彩空间" + ], + "conclusion": "RAVU 明确为 mpv 用户着色器族,公开资料显示内部使用 rgba16f/rgba16hf 且分 YUV/RGB 变体;缺少可作为外置 SDK 的完整协议。" + }, + { + "effectGroup": "RTXVideo", + "effectFamily": "NVIDIA RTX Video Super Resolution (VFX SDK VSR filter)", + "upstreamVendor": "NVIDIA", + "mappingConfidence": "明确映射", + "mappingNotes": "RTX Video 效果组拆为 VSR、Denoiser、HDR;此条为 VFX SDK Video Super Resolution filter。", + "documentedInputFormats": [ + "GPU buffers in BGRA or RGBA interleaved format, 8-bit unsigned per component" + ], + "documentedOutputFormats": [ + "Same BGRA or RGBA interleaved U8 GPU buffer" + ], + "formatBoundary": "输入输出必须 GPU buffer,BGRA/RGBA interleaved,每分量 8-bit unsigned;Denoise/Deblur modes 输出分辨率必须与输入相同;VSR 支持不同 modes;建议最小 360p。", + "channelOrderAndAlpha": "BGRA 或 RGBA;alpha 分量在 U8 格式中未单独描述行为。", + "precisionAndRange": "8-bit unsigned integer per component;仅 SDR 级像素格式。", + "transferFunction": "未在页面说明(SDR 视频输入通常 YUV->RGB 转换在外部)。", + "primariesAndColorSpace": "未说明;相关 SDK 页面提到颜色不对时交换 709/601(YUV 转换)。", + "hdrSupport": "SDROnly", + "hdrBoundary": "VFX VSR 页面只列 8-bit U8 BGRA/RGBA,无 HDR/PQ/FP16 路径。", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "SDRCompatible", + "profileRationale": "官方页面明确 U8 BGRA/RGBA 输入输出,没有 HDR/FP16。", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": "VSR 是视频时域增强;无帧生成/运动向量/深度输入;Denoise/Deblur 同分辨率。", + "documentedQualityOptions": [ + "VSR_Bicubic", + "VSR_Low", + "VSR_Medium", + "VSR_High", + "VSR_Ultra", + "HighBitrate_*", + "Denoise_*", + "Deblur_*" + ], + "documentedPerformanceNotes": "未找到可核实的格式路径性能结论。", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "OfficialDocs", + "sources": [ + { + "url": "https://docs.nvidia.com/maxine/vfx/1.2.0.0/Filters/VideoSuperResolution.html", + "title": "NVIDIA VFX SDK Video Super Resolution", + "evidenceLevel": "OfficialDocs", + "claim": "VSR input/output BGRA or RGBA U8; modes", + "quoteOrSymbol": "The input and output of the VSR filter are GPU buffers in BGRA or RGBA interleaved format...8-bit unsigned integer", + "searchRound": 1 + } + ], + "searchRoundsUsed": 3, + "unresolvedQuestions": [ + "VSR 是否接受 NV12/P010 内部路径", + "HDR 版本协议" + ], + "conclusion": "RTX Video VSR 的 VFX SDK 官方页面协议为 BGRA/RGBA 8-bit U8 GPU buffer;没有 FP16/HDR 输入输出契约。" + }, + { + "effectGroup": "RTXVideo", + "effectFamily": "NVIDIA RTX Video Denoiser (VFX SDK VSR Denoise modes)", + "upstreamVendor": "NVIDIA", + "mappingConfidence": "明确映射", + "mappingNotes": "VFX SDK VSR filter 内含 Denoise_Low/Medium/High/Ultra modes,作为 Denoiser 子条目。", + "documentedInputFormats": [ + "BGRA or RGBA interleaved U8 GPU buffers" + ], + "documentedOutputFormats": [ + "BGRA or RGBA interleaved U8 GPU buffers, same resolution as input" + ], + "formatBoundary": "Denoise modes 不支持 upscaling,输出分辨率必须等于输入;输入输出同为 BGRA/RGBA U8。", + "channelOrderAndAlpha": "BGRA/RGBA。", + "precisionAndRange": "8-bit unsigned per component。", + "transferFunction": "未说明。", + "primariesAndColorSpace": "未说明。", + "hdrSupport": "SDROnly", + "hdrBoundary": "官方页面只有 U8 输入输出,无 HDR 路径。", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "SDRCompatible", + "profileRationale": "官方 VSR 页面把 Denoise modes 也限定为 BGRA/RGBA U8,无 HDR。", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": "Denoise 模式不放大,输出同输入分辨率;无光流/深度输入。", + "documentedQualityOptions": [ + "Denoise_Low", + "Denoise_Medium", + "Denoise_High", + "Denoise_Ultra" + ], + "documentedPerformanceNotes": "未找到可核实的格式路径性能结论。", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "OfficialDocs", + "sources": [ + { + "url": "https://docs.nvidia.com/maxine/vfx/1.2.0.0/Filters/VideoSuperResolution.html", + "title": "NVIDIA VFX SDK Video Super Resolution (Denoise modes)", + "evidenceLevel": "OfficialDocs", + "claim": "Denoise modes same BGRA/RGBA U8 and same output resolution", + "quoteOrSymbol": "Denoise_Low ... resolution of the output must be the same as input", + "searchRound": 1 + } + ], + "searchRoundsUsed": 2, + "unresolvedQuestions": [ + "独立 Denoiser 模型格式", + "HDR" + ], + "conclusion": "RTX Video Denoiser 在官方 VFX 文档中与 VSR 共用 BGRA/RGBA U8、同分辨率输出协议,无 HDR/FP16。" + }, + { + "effectGroup": "RTXVideo", + "effectFamily": "NVIDIA RTX Video HDR", + "upstreamVendor": "NVIDIA", + "mappingConfidence": "明确映射", + "mappingNotes": "NVIDIA 官方博客/产品页将 RTX Video HDR 描述为 RTX Video SDK 功能;未检索到完整公开 API 协议页。", + "documentedInputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "https://developer.nvidia.com/blog/enhancing-low-resolution-sdr-video-with-the-nvidia-rtx-video-sdk/", + "notes": "官方博客只说 SDR 视频转 HDR10 质量,未给出输入像素格式/API 上下文。" + } + ], + "documentedOutputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "https://developer.nvidia.com/blog/enhancing-low-resolution-sdr-video-with-the-nvidia-rtx-video-sdk/", + "notes": "官方博客只说 HDR10 质量输出,未给出输出像素格式/API 上下文。" + } + ], + "formatBoundary": { + "inputFormatEnumeration": "not_enumerated", + "outputFormatEnumeration": "not_enumerated", + "inputOutputRelation": "unknown", + "alphaSemantics": "未说明。", + "rangeBoundary": "SDR input -> HDR10 output 的产品语义;无量值范围。", + "transferBoundary": "未公开(博客层面提到 HDR10/PQ,但没有 API 转换契约)。", + "resourceConstraints": "无 API 资源状态、尺寸或颜色转换文档。", + "hdrImplication": "产品方向明确为 SDR->HDR,但无公开 HDR/FP16 格式契约。" + }, + "channelOrderAndAlpha": "无信息", + "precisionAndRange": "无信息", + "transferFunction": "官方博客/介绍提到 HDR10/PQ 场景;公开协议页未给出转换细节。", + "primariesAndColorSpace": "博客提到从 sRGB 扩展到 scRGB/HDR 色域(不同页面措辞不一致);无正式协议。", + "hdrSupport": "ExplicitHDR", + "hdrBoundary": "官方仅宣称 SDR->HDR,未公开输入/输出格式、metadata、tone mapping 或 pre-exposure 契约。", + "requiredHdrMetadata": "未说明", + "candidateInternalProfile": "无信息", + "profileRationale": "没有可核实的输入输出协议来源。", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": "无公开协议。", + "documentedQualityOptions": [ + "RTX Video HDR on/off 等产品级选项,不是 API 格式档位" + ], + "documentedPerformanceNotes": "未找到可核实的格式路径性能结论。", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "OfficialForum", + "sources": [ + { + "url": "https://developer.nvidia.com/blog/enhancing-low-resolution-sdr-video-with-the-nvidia-rtx-video-sdk/", + "title": "NVIDIA Technical Blog: RTX Video SDK", + "evidenceLevel": "OfficialForum", + "claim": "RTX Video HDR converts SDR video to HDR10 quality; VSR upscales", + "quoteOrSymbol": "RTX Video HDR converts SDR video to HDR10 quality", + "searchRound": 1 + } + ], + "searchRoundsUsed": 5, + "unresolvedQuestions": [ + "API 输入输出格式", + "NvCVImage pixel format", + "PQ metadata", + "scRGB/HDR10 工作空间" + ], + "conclusion": "RTX Video HDR 有明确官方产品宣称但缺少公开 API 格式协议,记录为无格式信息、仅 HDR 方向明确。" + }, + { + "effectGroup": "Sharpen", + "effectFamily": "无信息(Sharpen 为通用锐化效果族)", + "upstreamVendor": "无信息", + "mappingConfidence": "无信息", + "mappingNotes": "公开搜索出现 NIS NVSharpen、Android SharpenFilter 等多个互不相同的后端,无法唯一对应。", + "documentedInputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "公开搜索出现多个锐化实现", + "notes": "无法唯一映射;不得把任一锐化实现的格式写成该效果组已支持协议。" + } + ], + "documentedOutputFormats": [ + { + "format": "unspecified", + "apiOrContext": "公开资料未枚举", + "channelOrder": "unspecified", + "numericRepresentation": "unspecified", + "acceptanceStatus": "not_enumerated", + "evidenceRef": "公开搜索出现多个锐化实现", + "notes": "同上。" + } + ], + "formatBoundary": { + "inputFormatEnumeration": "not_enumerated", + "outputFormatEnumeration": "not_enumerated", + "inputOutputRelation": "unknown", + "alphaSemantics": "未说明。", + "rangeBoundary": "unknown", + "transferBoundary": "unspecified", + "resourceConstraints": "无统一资源约束可记录。", + "hdrImplication": "无公开 HDR 格式契约。" + }, + "channelOrderAndAlpha": "无信息", + "precisionAndRange": "无信息", + "transferFunction": "无信息", + "primariesAndColorSpace": "无信息", + "hdrSupport": "无信息", + "hdrBoundary": "未说明", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "无信息", + "profileRationale": "无信息", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": [], + "documentedQualityOptions": [], + "documentedPerformanceNotes": "无信息", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "无信息", + "sources": [], + "searchRoundsUsed": 1, + "unresolvedQuestions": [ + "具体锐化算法映射", + "格式", + "HDR/FP16" + ], + "conclusion": "Sharpen 效果组无法唯一映射到上游算法,记录为无信息。" + }, + { + "effectGroup": "SMAA", + "effectFamily": "SMAA (Subpixel Morphological Antialiasing)", + "upstreamVendor": "Jorge Jimenez / iryoku", + "mappingConfidence": "明确映射", + "mappingNotes": "官方 iryoku/smaa 仓库。", + "documentedInputFormats": [ + "colorTex: RGBA texture (2D color/luma input)", + "edgesTex/areaTex/searchTex 等内部/预计算纹理", + "depthTex for depth edge detection" + ], + "documentedOutputFormats": [ + "colorTex/blended output 4-channel color buffer" + ], + "formatBoundary": "官方集成说明要求两个 RGBA 时域 render target;创建后清除 alpha;所有 sampler linear + clamp;纹理读写默认非 sRGB,只有最终 NeighborhoodBlending 的 input/output 可 sRGB;64-bit 输入在 GCN 上有半速率线性过滤。", + "channelOrderAndAlpha": "RGBA;alpha 通道在中间步骤被用作速度/索引等;文档警告清除 alpha。", + "precisionAndRange": "float4/float 通用;提到 64-bit 输入和 half-rate filtering,未给出 8/10/16 位清单。", + "transferFunction": "建议在 gamma-corrected(非 sRGB 采样)空间做 luma/color edge detection;最终 blending pass 可处理 sRGB 读写。", + "primariesAndColorSpace": "未说明。", + "hdrSupport": "Unspecified", + "hdrBoundary": "官方代码未声称 HDR;luma edge detection 注释要求 gamma-corrected colors,通常为 LDR 空间。", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "SDRCompatible", + "profileRationale": "官方集成注释围绕 gamma/sRGB 空间和 RGBA 临时目标,未见 HDR/线性 HDR 直接路径;保守记为 SDR 兼容。", + "auxiliaryInputs": [ + "velocityTex (temporal variant)", + "depthTex (depth edge detection)", + "areaTex/searchTex lookup textures", + "predicationTex optional" + ], + "auxiliaryInputFormatsAndSemantics": "area/search 为预计算 LUT;velocity 解码方式由 SMAA_DECODE_VELOCITY 定义;depth 可为深度纹理。", + "temporalOrFrameGenerationConstraints": "SMAA 1x 无时域;temporal/supersampling 变体需要多子样本、previous/current color 和 velocity。", + "documentedQualityOptions": [ + "SMAA 1x/2x/T2x 等宏/预设由官方源码定义" + ], + "documentedPerformanceNotes": "官方注释提示 64-bit 输入半速率线性过滤,可用 point filtering 规避;未给完整性能基准。", + "qualityPerformanceEvidence": "官方注释有格式相关性能提示,但没有格式路径性能基准。", + "evidenceLevel": "OfficialRepository", + "sources": [ + { + "url": "https://raw.githubusercontent.com/iryoku/smaa/master/README.md", + "title": "SMAA README", + "evidenceLevel": "OfficialRepository", + "claim": "SMAA official repo and integration pointer", + "quoteOrSymbol": "Checkout the technical paper", + "searchRound": 1 + }, + { + "url": "https://raw.githubusercontent.com/iryoku/smaa/master/SMAA.hlsl", + "title": "SMAA.hlsl", + "evidenceLevel": "OfficialRepository", + "claim": "Integration notes: RGBA render targets, sRGB rules, filtering", + "quoteOrSymbol": "All texture reads and buffer writes must be non-sRGB...", + "searchRound": 2 + } + ], + "searchRoundsUsed": 4, + "unresolvedQuestions": [ + "具体 DXGI 格式支持", + "HDR 线性输入", + "alpha 对外值" + ], + "conclusion": "SMAA 官方仓库提供了着色器级协议:RGBA 输入输出、RGBA 临时目标、gamma/sRGB 空间建议;未公开现代 HDR/FP16 API 契约。" + }, + { + "effectGroup": "xBRZ", + "effectFamily": "xBRZ (pixel-art scaling algorithm)", + "upstreamVendor": "Zenju", + "mappingConfidence": "明确映射", + "mappingNotes": "xBRZ 原算法由 Zenju 发布;本条目证据来自 TypeScript/WASM 移植 kayahr/xbrz。", + "documentedInputFormats": [ + "RGBA pixel data in Uint8ClampedArray (TypeScript port)" + ], + "documentedOutputFormats": [ + "RGBA pixel data in Uint8ClampedArray" + ], + "formatBoundary": "TypeScript 移植接口为源/目标 RGBA 像素缓冲;缩放因子 2-6;支持 alpha 透明;非原生 SDK 协议。", + "channelOrderAndAlpha": "RGBA;支持 alpha 透明。", + "precisionAndRange": "8-bit per channel in Uint8ClampedArray([0,255]);无 HDR/浮点。", + "transferFunction": "未说明。", + "primariesAndColorSpace": "未说明。", + "hdrSupport": "SDROnly", + "hdrBoundary": "移植接口是 8-bit RGBA,无 HDR。", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "SDRCompatible", + "profileRationale": "证据显示 RGBA 8-bit 像素缓冲;无浮点/HDR 接口。", + "auxiliaryInputs": [], + "auxiliaryInputFormatsAndSemantics": [], + "temporalOrFrameGenerationConstraints": [], + "documentedQualityOptions": [ + "scale factors 2-6" + ], + "documentedPerformanceNotes": "未找到可核实的格式路径性能结论。", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "GitHubExperiment", + "sources": [ + { + "url": "https://raw.githubusercontent.com/kayahr/xbrz/master/README.md", + "title": "kayahr/xbrz TypeScript/WASM port", + "evidenceLevel": "GitHubExperiment", + "claim": "Port uses RGBA Uint8ClampedArray and supports alpha", + "quoteOrSymbol": "Source and target are RGBA pixel data in a Uint8ClampedArray", + "searchRound": 1 + } + ], + "searchRoundsUsed": 3, + "unresolvedQuestions": [ + "Magpie 原生后端是否使用该接口", + "原 C++ xBRZ 的格式契约", + "HDR" + ], + "conclusion": "xBRZ 映射明确,但可靠公开协议只有社区移植的 RGBA 8-bit 接口,原生后端协议未确认。" + }, + { + "effectGroup": "XeSS", + "effectFamily": "Intel XeSS Super Resolution (XeSS-SR)", + "upstreamVendor": "Intel", + "mappingConfidence": "明确映射", + "mappingNotes": "Intel xess 官方仓库与 Developer Guide。", + "documentedInputFormats": [ + "R16G16B16A16_FLOAT", + "R11G11B10_FLOAT", + "R8G8B8A8_UNORM", + "其他 any linear color format; only UNORM integer color formats allowed", + "Motion vectors: R16G16_FLOAT", + "Depth: any depth format such as D32_FLOAT or D24_UNORM" + ], + "documentedOutputFormats": [ + "Same format and color space as input (2D output texture)" + ], + "formatBoundary": "输入颜色可为 LDR/HDR 任意 linear 格式;整数格式只允许 UNORM;输出必须与输入同格式同色彩空间,且输出 alpha 不保留并填 1.0;D3D12 输入 NON_PIXEL_SHADER_RESOURCE,输出 UAV;Vulkan 对应状态。", + "channelOrderAndAlpha": "RGBA/R11G11B10 等格式;XeSS-SR 不保留输出 alpha,填 1.0。", + "precisionAndRange": "颜色为 scene-referred scRGB,1.0 为 80 nits SDR 白点,HDR 可 >1;LDR 输入需 XESS_INIT_FLAG_LDR_INPUT_COLOR;exposureScale 等。", + "transferFunction": "任何 linear 色彩格式(LDR/HDR);非 sRGB/PQ 编码直接输入。", + "primariesAndColorSpace": "scRGB(scene-referred);输出同输入色彩空间。", + "hdrSupport": "ExplicitHDR", + "hdrBoundary": "推荐直接提供 HDR 线性/scRGB;可设置 exposureScale/pre-exposure/exposureMultiplier;算法内部自行 tonemap,输出恢复;LDR 也可。", + "requiredHdrMetadata": [ + "exposureScale", + "preExposure/exposureMultiplier 可选", + "无 MaxCLL/MaxFALL 要求" + ], + "candidateInternalProfile": "DirectFP16", + "profileRationale": "官方明确支持 FP16/RGBA16F、scRGB HDR 线性并推荐 HDR 输入。", + "auxiliaryInputs": [ + "motion vectors", + "depth (low-res MV mode)", + "responsive pixel mask (optional)", + "exposure scale/multiplier", + "jitter" + ], + "auxiliaryInputFormatsAndSemantics": "MVs R16G16_FLOAT screen-space pixels, low-res default or high-res dilated; depth D32_FLOAT/D24_UNORM; responsive mask R channel float [0,1]; input states as above.", + "temporalOrFrameGenerationConstraints": "时域超分:需 jittered color, motion vectors, depth when low-res; output target resolution; fixed/dynamic input resolution ranges; reset history on cuts.", + "documentedQualityOptions": [ + "Ultra Quality/Quality/Balanced/Performance presets (via optimal input resolution API)" + ], + "documentedPerformanceNotes": "开发指南建议 HDR input color 推荐、使用 FP16 color buffer in scene linear HDR(性能提示);无格式路径基准。", + "qualityPerformanceEvidence": "指南有使用 FP16 scene-linear HDR 的建议,但无定量比较。", + "evidenceLevel": "OfficialRepository", + "sources": [ + { + "url": "https://raw.githubusercontent.com/intel/xess/main/doc/xess_sr_developer_guide_english.md", + "title": "Intel XeSS-SR Developer Guide", + "evidenceLevel": "OfficialRepository", + "claim": "Formats and color space: R16G16B16A16_FLOAT/R11G11B10_FLOAT/R8G8B8A8_UNORM, scRGB, output same", + "quoteOrSymbol": "XeSS-SR accepts both LDR and HDR input colors in any linear color format", + "searchRound": 1 + }, + { + "url": "https://raw.githubusercontent.com/intel/xess/main/README.md", + "title": "Intel xess README", + "evidenceLevel": "OfficialRepository", + "claim": "XeSS-SR and XeSS-FG/XeLL availability", + "quoteOrSymbol": "XeSS Super Resolution (XeSS-SR)", + "searchRound": 2 + } + ], + "searchRoundsUsed": 5, + "unresolvedQuestions": [ + "每种 DXGI/Vulkan 格式完整矩阵", + "PQ/HLG 是否需外部转换", + "primaries 非 scRGB" + ], + "conclusion": "XeSS-SR 官方协议非常明确:支持 R16G16B16A16_FLOAT/R11G11B10_FLOAT/R8G8B8A8_UNORM 等线性格式,scRGB scene-referred,输出同输入格式且 alpha 填 1。" + }, + { + "effectGroup": "XeSSFG", + "effectFamily": "Intel XeSS Frame Generation (XeSS-FG)", + "upstreamVendor": "Intel", + "mappingConfidence": "明确映射", + "mappingNotes": "Intel xess 官方仓库 XeSS-FG Developer Guide。", + "documentedInputFormats": [ + "Back buffer/HUD-less/UI-only: R10G10B10A2_UNORM for HDR10/BT.2100 HDR display", + "Motion vectors: R16G16_FLOAT or similar", + "Depth: any depth format such as D32_FLOAT or D24_UNORM", + "UI Alpha: single channel; UI Color and Alpha: same backbuffer format" + ], + "documentedOutputFormats": [ + "Interpolated frames output to proxy swap chain; same back buffer pixel format (HDR10 R10G10B10A2_UNORM documented)" + ], + "formatBoundary": "HDR display 支持 R10G10B10A2_UNORM + HDR10/BT.2100;明确不支持 FP16 HDR/scRGB;back buffer, HUD-less, UI-only 必须同像素格式、色彩空间、尺寸;MV 与 depth buffer 尺寸一致。", + "channelOrderAndAlpha": "UI-only texture 有 alpha;非预乘 alpha 可用标志;合成公式 Final.RGB = UIonly.RGB + (1-UIonly.Alpha) * HUDlessColor.RGB;2-bit alpha 对 UI 合成不足。", + "precisionAndRange": "HDR 显示为 10-bit R10G10B10A2_UNORM HDR10/BT.2100;不支持 FP16/scRGB;LDR 显示用 swapchain 格式。", + "transferFunction": "HDR10 / BT.2100(PQ)用于 HDR 显示。", + "primariesAndColorSpace": "HDR10 / BT.2100;非 HDR 未细说。", + "hdrSupport": "ExplicitHDR", + "hdrBoundary": "HDR 必须使用 R10G10B10A2_UNORM 与 HDR10/BT.2100,back buffer/HUDless/UI 同格式;不支持 FP16/scRGB;未给出 PQ 解码细节。", + "requiredHdrMetadata": [ + "HDR display pixel format R10G10B10A2_UNORM", + "HDR10/BT.2100 color space", + "无 MaxCLL/MaxFALL 公开参数" + ], + "candidateInternalProfile": "BoundedHDR", + "profileRationale": "官方明确 HDR 只走 10-bit R10G10B10A2 UNORM/HDR10 BT.2100,不接收 FP16/scRGB,故为有界 HDR 契约。", + "auxiliaryInputs": [ + "motion vectors", + "depth", + "HUD-less color", + "UI-only texture", + "jitter offset", + "resetHistory", + "motion vector scale" + ], + "auxiliaryInputFormatsAndSemantics": "MV R16G16_FLOAT/similar low/high res; depth any format, same size as MV; HUDless/UI same as backbuffer format/color; UI alpha single channel.", + "temporalOrFrameGenerationConstraints": "需要 XeLL latency reduction 启用;不能独占全屏;40 FPS 最低/60 FPS 推荐;禁用/减少 motion blur;切换或与第三方帧生成不兼容;UI composition modes.", + "documentedQualityOptions": [ + "UI composition modes", + "interpolated frames count 等 XeSS-FG 公开选项" + ], + "documentedPerformanceNotes": "官方建议 low-res MVs 以免 high-res 大幅降低性能;无格式路径定量表。", + "qualityPerformanceEvidence": "官方说明 high-res MV 性能影响,但无格式路径性能基准。", + "evidenceLevel": "OfficialRepository", + "sources": [ + { + "url": "https://raw.githubusercontent.com/intel/xess/main/doc/xess_fg_developer_guide_english.md", + "title": "Intel XeSS-FG Developer Guide", + "evidenceLevel": "OfficialRepository", + "claim": "HDR display support R10G10B10A2_UNORM HDR10/BT.2100; no FP16/scRGB", + "quoteOrSymbol": "XeSS-FG provides support for HDR displays with the R10G10B10A2_UNORM pixel format... does not support FP16 HDR format and scRGB color space", + "searchRound": 1 + }, + { + "url": "https://raw.githubusercontent.com/intel/xess/main/README.md", + "title": "Intel xess README", + "evidenceLevel": "OfficialRepository", + "claim": "XeSS-FG available on Intel Arc and non-Intel with SM6.4", + "quoteOrSymbol": "XeSS Frame Generation (XeSS-FG)", + "searchRound": 2 + } + ], + "searchRoundsUsed": 4, + "unresolvedQuestions": [ + "LDR 下的完整 swapchain 格式清单", + "PQ metadata 传递" + ], + "conclusion": "XeSS-FG 官方明确 HDR 契约:R10G10B10A2_UNORM/HDR10 BT.2100,不支持 FP16/scRGB;所有参与合成的纹理必须同格式同色彩空间。" + }, + { + "effectGroup": "NVIDIA Optical Flow", + "effectFamily": "NVIDIA Optical Flow SDK (NvOF)", + "upstreamVendor": "NVIDIA", + "mappingConfidence": "明确映射", + "mappingNotes": "附加依赖项对应 NVIDIA Optical Flow SDK NvOF API(另有 NVOFA 新 API,本次主条目为 NvOF 头文件证据)。", + "documentedInputFormats": [ + "NV_OF_BUFFER_FORMAT_GRAYSCALE8", + "NV_OF_BUFFER_FORMAT_NV12", + "NV_OF_BUFFER_FORMAT_ABGR8 (A8B8G8R8)" + ], + "documentedOutputFormats": [ + "NV_OF_FLOW_VECTOR (SHORT2, S10.5 flowx/flowy)", + "NV_OF_STEREO_DISPARITY (for stereo mode)", + "Cost buffer NV_OF_BUFFER_FORMAT_UINT or UINT8" + ], + "formatBoundary": "输入帧支持 GRAYSCALE8/NV12/ABGR8;输出/提示为 SHORT2(S10.5);外部提示/成本格式另有要求;支持 output grid size 1/2/4;有宽高 min/max caps。", + "channelOrderAndAlpha": "ABGR8 为 8-bit packed A8B8G8R8;NV12 为 Y planar + interleaved UV。", + "precisionAndRange": "8-bit input; output flow vector int16 S10.5 表示亚像素;无 HDR/PQ 声明。", + "transferFunction": "未说明。", + "primariesAndColorSpace": "未说明。", + "hdrSupport": "SDROnly", + "hdrBoundary": "官方头文件输入格式只列 8-bit 亮度/NV12/ABGR8,无 FP16/HDR。", + "requiredHdrMetadata": "无信息", + "candidateInternalProfile": "SDRCompatible", + "profileRationale": "NvOF 头文件列出的输入只有 8-bit 格式,输出为定点光流。", + "auxiliaryInputs": [ + "externalHints (optional)", + "cost buffer (optional)", + "reference frame" + ], + "auxiliaryInputFormatsAndSemantics": "externalHints/outputBuffer use NV_OF_FLOW_VECTOR for optical flow; cost UINT or UINT8; reference frame same input format as inputFrame.", + "temporalOrFrameGenerationConstraints": "光流在两帧(input/reference)间估计;output grid size 决定流向量网格;scene change/hints 等。", + "documentedQualityOptions": [ + "NV_OF_OUTPUT_VECTOR_GRID_SIZE 1/2/4" + ], + "documentedPerformanceNotes": "头文件注明 legacy 32-bit cost 不高效,建议 8-bit cost;无格式路径完整基准。", + "qualityPerformanceEvidence": "官方头文件有成本格式性能建议,但无定量表。", + "evidenceLevel": "OfficialHeader", + "sources": [ + { + "url": "https://raw.githubusercontent.com/NVIDIA/NVIDIAOpticalFlowSDK/master/nvOpticalFlowCommon.h", + "title": "NVIDIA Optical Flow SDK nvOpticalFlowCommon.h", + "evidenceLevel": "OfficialHeader", + "claim": "Buffer formats: GRAYSCALE8, NV12, ABGR8; flow vector SHORT2", + "quoteOrSymbol": "NV_OF_BUFFER_FORMAT_NV12 ... NV_OF_BUFFER_FORMAT_ABGR8", + "searchRound": 1 + } + ], + "searchRoundsUsed": 4, + "unresolvedQuestions": [ + "NVOFA 新 API 的 RGBA/F16 支持", + "HDR 帧是否需转为 NV12/ABGR8" + ], + "conclusion": "NVIDIA Optical Flow NvOF 官方头文件支持 8-bit GRAYSCALE/NV12/ABGR8 输入,输出为 S10.5 定点 SHORT2 光流;未提供 HDR/FP16 输入路径。" + }, + { + "effectGroup": "AMD FidelityFX Optical Flow", + "effectFamily": "FidelityFX Optical Flow", + "upstreamVendor": "AMD", + "mappingConfidence": "明确映射", + "mappingNotes": "AMD FidelityFX SDK 中用于 FSR3 的光流技术,官方 docs/optical-flow.md。", + "documentedInputFormats": [ + "color input resource (格式未在文档枚举)" + ], + "documentedOutputFormats": [ + "opticalFlowVector: R16G16_SINT", + "opticalFlowSCD: R32_UINT (3x1 scene change detection)" + ], + "formatBoundary": "以 8x8 block 计算,输出纹理尺寸由 (displaySize+block-1)/8 决定;block size 固定 8;color 输入经 transfer function/luminance 转换。", + "channelOrderAndAlpha": "未说明 color 通道顺序;算法使用亮度。", + "precisionAndRange": "输出 R16G16_SINT;SCD R32_UINT;输入颜色格式未枚举。", + "transferFunction": "输入有 backbufferTransferFunction 与 minMaxLuminance,用于 HDR 管道转换到 luminance。", + "primariesAndColorSpace": "未说明。", + "hdrSupport": "ExplicitHDR", + "hdrBoundary": "支持 HDR pipeline 的 luminance 转换:需 backbufferTransferFunction 和 minMaxLuminance;未公开颜色输入格式。", + "requiredHdrMetadata": [ + "backbufferTransferFunction", + "minMaxLuminance" + ], + "candidateInternalProfile": "BoundedHDR", + "profileRationale": "官方要求把 HDR 颜色经 transfer/luminance 转成内部亮度,适合有界转换路径。", + "auxiliaryInputs": [ + "previous/current color input", + "internal history resources for pyramid/histogram" + ], + "auxiliaryInputFormatsAndSemantics": "color input is current frame; internal history resources; SCD output 3x1 R32_UINT.", + "temporalOrFrameGenerationConstraints": "需要连续帧、内部金字塔、历史 histogram 做场景变化检测;用于 FSR3 帧插值。", + "documentedQualityOptions": [ + "8x8 block size fixed in release" + ], + "documentedPerformanceNotes": "文档内存表按 GPU/4K 显示,但未比较不同格式成本。", + "qualityPerformanceEvidence": "未找到可核实的格式路径性能结论", + "evidenceLevel": "OfficialDocs", + "sources": [ + { + "url": "https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/release-FSR3-3.0.3/docs/techniques/optical-flow.md", + "title": "FidelityFX Optical Flow", + "evidenceLevel": "OfficialDocs", + "claim": "Optical flow outputs R16G16_SINT and R32_UINT; HDR transfer/luminance", + "quoteOrSymbol": "opticalFlowVector ... R16G16_SINT ... R32_UINT", + "searchRound": 1 + } + ], + "searchRoundsUsed": 3, + "unresolvedQuestions": [ + "输入 color 的格式枚举", + "输出语义坐标空间精确单位", + "独立于 FSR3 的 SDK" + ], + "conclusion": "AMD FidelityFX Optical Flow 官方协议公开了光流输出 R16G16_SINT、SCD R32_UINT 和 HDR transfer/luminance 输入需求,但输入颜色格式未枚举。" + } +] \ No newline at end of file diff --git a/docs/experimental/HDR_PROTOCOL_EVIDENCE.md b/docs/experimental/HDR_PROTOCOL_EVIDENCE.md new file mode 100644 index 000000000..9f773dc61 --- /dev/null +++ b/docs/experimental/HDR_PROTOCOL_EVIDENCE.md @@ -0,0 +1,1416 @@ +# Magpie 外置 HDR 效果层协议证据调研 + +> 本文件只基于公开网络资料,不读取、引用或推断任何本地 Magpie 源码、效果描述、HLSL、配置、日志、二进制或历史实验文件。 + +## 研究范围 + +研究 25 个 Magpie 效果组和 2 个附加依赖项的公开图像输入/输出协议;未来架构外部交付面预设为 `DXGI_FORMAT_R16G16B16A16_FLOAT`,但本次不设计代码、不修改工程。 + +## 排除项 + +- 不读取/搜索/引用/分析本地 Magpie 源码、效果描述、HLSL、配置、日志、二进制、历史实验文件或本地仓库内容。 +- 不依据 Magpie 当前实现反推任何效果是否支持 FP16/HDR/R8/R10/NV12 等格式。 +- 排除已由本地实验单独验证的独立效果:不搜索、不写入矩阵、不计入效果数。 +- 不下载 SDK 压缩包、驱动安装包、完整仓库镜像、网页缓存、截图、网页转储或原始爬虫数据。 + +## 证据等级 + +- `OfficialDocs`:官方 SDK 文档/编程指南/API 参考 +- `OfficialHeader`:官方头文件 +- `OfficialSample`:官方示例 +- `OfficialRepository`:官方 GitHub 仓库 +- `OfficialForum`:厂商开发者论坛/官方发布说明/官方博客 +- `GitHubExperiment`:GitHub issue/fork/实验/社区结论 +- `无信息`:十轮内没有可靠协议结论 + +## 十轮上限规则 + +每个效果族最多 10 轮;一轮定义为一次新搜索查询或一个新来源的导航/查阅;重复搜索同一关键词或反复打开同一来源仍计轮。资料没有明确写出的字段一律记“未说明/无信息”。 + +## 总览矩阵 + +| 效果组 | 上游/SDK | 已证实输入格式 | 已证实输出格式 | HDR/FP16 状态 | 候选内部 profile | 证据等级 | 搜索轮数 | 核心格式边界 | +|---|---|---|---|---|---|---|---|---| +| Anime4K | Anime4K | unspecified(not_enumerated) | unspecified(not_enumerated) | 无信息 | 无信息 | OfficialRepository | 7 | inputFormatEnumeration=not_enumerated; outputFormatEnumeration=not_enumerated; inputOutputRelation=implementation_defined; alphaSemantics=未说明;不得从 mpv 用户着色器可编译推断 alpha 契约。; rangeBoundary=unknown; hdrImplication=无公开 HDR 格式契约。 | +| CAS | FidelityFX Contrast Adaptive Sharpening (CAS) | DXGI_FORMAT_R8G8B8A8_UNORM(reference_implementation_only);DXGI_FORMAT_R16G16B16A16_FLOAT(reference_implementation_only);unspecified(not_enumerated) | DXGI_FORMAT_R8G8B8A8_UNORM(reference_implementation_only);DXGI_FORMAT_R16G16B16A16_FLOAT(reference_implementation_only);unspecified(not_enumerated) | Unspecified | SDRCompatible | OfficialDocs | 7 | inputFormatEnumeration=partial; outputFormatEnumeration=partial; inputOutputRelation=same_format_required; alphaSemantics=未说明;SDK 回调只读写 RGB,未见 alpha 透传/置 1 声明。; rangeBoundary=sRGB/linear 解释均支持;shader 转换注释按 [0,1] 线性空间处理。; hdrImplication=官方参考实现支持 R16G16B16A16_FLOAT 与 linear 开关,但这属于参考实现路径;SDK 技术文档未提供 H… | +| CRT | 无信息(CRT 效果族未能在公开资料中唯一映射) | unspecified(not_enumerated) | unspecified(not_enumerated) | 无信息 | 无信息 | 无信息 | 1 | inputFormatEnumeration=not_enumerated; outputFormatEnumeration=not_enumerated; inputOutputRelation=unknown; alphaSemantics=未说明;名称不能唯一映射,无法建立 alpha 契约。; rangeBoundary=unknown; hdrImplication=无公开 HDR 格式契约。 | +| CuNNy | CuNNy | unspecified(not_enumerated) | unspecified(not_enumerated) | 无信息 | 无信息 | OfficialRepository | 2 | inputFormatEnumeration=not_enumerated; outputFormatEnumeration=not_enumerated; inputOutputRelation=implementation_defined; alphaSemantics=未说明。; rangeBoundary=unknown; hdrImplication=无公开 HDR 格式契约。 | +| CuNNy2 | 无信息(未能定位独立 CuNNy2 上游仓库/SDK) | unspecified(not_enumerated) | unspecified(not_enumerated) | 无信息 | 无信息 | 无信息 | 2 | inputFormatEnumeration=not_enumerated; outputFormatEnumeration=not_enumerated; inputOutputRelation=unknown; alphaSemantics=未说明。; rangeBoundary=unknown; hdrImplication=无公开 HDR 格式契约。 | +| Diagnostics | 无信息(Diagnostics 是通用诊断效果名) | unspecified(not_enumerated) | unspecified(not_enumerated) | 无信息 | 无信息 | 无信息 | 1 | inputFormatEnumeration=not_enumerated; outputFormatEnumeration=not_enumerated; inputOutputRelation=unknown; alphaSemantics=未说明。; rangeBoundary=unknown; hdrImplication=无公开 HDR 格式契约。 | +| DLSS | NVIDIA DLSS Super Resolution (DLSS-SR) | Color input: any supported buffer format for the API;Motion vectors: RG32_FLOAT or RG16_FLOAT;Depth: any one-channel format (R32_FLOAT/D32_FLOAT) or depth-stencil (D24S8);Exposure: 1x1 texture, R16F preferred (presets J/… | Output buffer: any supported buffer format for the API;Previous output/history buffer: optional, RGBA16F | ExplicitHDR | DirectFP16 | OfficialDocs | 6 | 官方编程指南要求输入为 SRV/read 状态,输出为 UAV/write 状态;颜色输入任意 API 支持格式,输出任意 API 支持格式;历史输出建议 RGBA16F;最小输出 32x32;无规定最大分辨率但 8K 以上未保证。 | +| DLSSFG | NVIDIA DLSS Frame Generation (DLSS-G / DLSS 4 Multi-Frame Gen) | Backbuffer/Final Color resource (format not enumerated in public text);Motion vectors/depth: same DLSS-SR requirements | OutputInterpolated/OutputReal: same texture format as Backbuffer | ExplicitHDR | FormatOnlyNoColorContract | OfficialRepository | 5 | 官方头文件写明 Required Output Texture (same texture format as Backbuffer);HUDless 与 UI 纹理需与 backbuffer 同尺寸/格式/色彩空间;backbuffer 资源经 Streamline swapchain 拦截。 | +| FSR | AMD FidelityFX Super Resolution 1 (FSR1 spatial upscaler) | unspecified(inferred_from_shader_interface);unspecified(not_enumerated) | unspecified(inferred_from_shader_interface);unspecified(not_enumerated) | SDROnly | SDRCompatible | OfficialDocs | 6 | inputFormatEnumeration=not_enumerated; outputFormatEnumeration=not_enumerated; inputOutputRelation=implementation_defined; alphaSemantics=未说明。; rangeBoundary=[0,1] perceptual sRGB;RCAS 负输入会产生 NaN。; hdrImplication=FSR1 核心文档没有 HDR 直接输入路径;线性 HDR helper 属可选周边工具,不能算核心格式契约。 | +| FSR2 | AMD FidelityFX Super Resolution 2 (FSR2 temporal upscaler) | Color buffer: APPLICATION SPECIFIED (render res);Depth: APPLICATION SPECIFIED (1x FLOAT);Motion vectors: APPLICATION SPECIFIED (2x FLOAT);Reactive mask: R8_UNORM;Transparency & composition mask: R8_UNORM;Exposure: R32_FL… | Adjusted color buffer (internal): R16G16B16A16_FLOAT;Upscaled buffer (internal): R16G16B16A16_FLOAT;API 输出缓冲的公开格式在文档中未单独枚举,一般随应用分配资源 | ExplicitHDR | DirectFP16 | OfficialRepository | 4 | 颜色输入由应用指定格式;HDR 需设置 HDR flag;深度单浮点;MVs 2x float 屏幕空间范围 [<-w,-h>,];内部多使用 16-bit;输出最终与输入同域(内部 tonemap 会被反转);要求 GPU 支持 typed UAV load 和 R16G16B16A16_UNORM。 | +| FSR3 | AMD FidelityFX Super Resolution 3.1 Upscaler (FSR3 SR) | Color buffer: APPLICATION SPECIFIED;Depth: APPLICATION SPECIFIED (1x FLOAT);Motion vectors: APPLICATION SPECIFIED (2x FLOAT);Reactive mask: R8_UNORM;T&C mask: R8_UNORM;Exposure: R32_FLOAT | 未单独枚举;文档称输出与原始输入同域(internal tonemap 被反转) | ExplicitHDR | DirectFP16 | OfficialDocs | 2 | 与 FSR2 输入结构一致;HDR flag 开启时输入 linear;要求 R16G16B16A16_UNORM typed UAV 支持;API 未在文档中列 DXGI 输出枚举。 | +| FSR3 | AMD FidelityFX Super Resolution 3 Frame Interpolation / Frame Generation (FSR3 FG) | currentBackBuffer (presentation color buffer);currentBackBuffer_HUDLess (optional);depth (required for FSR3 interpolation workflow);motion vectors (required);R16G16_SINT optical flow vector;R32_UINT optical flow SCD | interpolated output resource;outputSwapChainBuffer;格式未在公开文档列枚举;代码示例用 swap chain back buffer format | ExplicitHDR | BoundedHDR | OfficialDocs | 3 | 官方文档要求传入 backBufferFormat;HUD-less/UI 资源与 backbuffer 关系密切;帧生成需要超分先行;输入资源格式由配置描述,未列完整 DXGI 清单。 | +| FSR4 | AMD FidelityFX Super Resolution 4 (FSR4 ML Super Resolution) | Color buffer: APPLICATION SPECIFIED;Depth: APPLICATION SPECIFIED (1x FLOAT);Motion vectors: APPLICATION SPECIFIED (2x FLOAT);Exposure: R32_FLOAT (1x1) | 未单独枚举;文档称输出与原始输入同域 | ExplicitHDR | DirectFP16 | OfficialDocs | 2 | 颜色输入必须 linear,除非设置 NON_LINEAR_COLORSPACE 标志;没有 DXGI 格式枚举,颜色格式由应用指定;MVs 2x float;深度 float;输出同输入域。 | +| FSRCNNX | FSRCNNX (基于 FSRCNN 的 mpv/着色器变体) | unspecified(not_enumerated) | unspecified(not_enumerated) | 无信息 | 无信息 | GitHubExperiment | 5 | inputFormatEnumeration=not_enumerated; outputFormatEnumeration=not_enumerated; inputOutputRelation=implementation_defined; alphaSemantics=未说明。; rangeBoundary=unknown; hdrImplication=无公开 HDR 格式契约。 | +| FXAA | FXAA (Fast Approximate Anti-Aliasing) | unspecified(not_enumerated);unspecified(reference_implementation_only) | unspecified(not_enumerated);unspecified(reference_implementation_only) | 无信息 | 无信息 | 无信息 | 4 | inputFormatEnumeration=not_enumerated; outputFormatEnumeration=not_enumerated; inputOutputRelation=implementation_defined; alphaSemantics=参考实现可透传 alpha;官方/原算法无统一声明。; rangeBoundary=unknown; hdrImplication=无官方 HDR 格式契约;个别引擎实现可用浮点纹理,但不能代表算法协议。 | +| MLAA | MLAA (Morphological Anti-Aliasing) | unspecified(not_enumerated) | unspecified(not_enumerated) | 无信息 | 无信息 | 无信息 | 1 | inputFormatEnumeration=not_enumerated; outputFormatEnumeration=not_enumerated; inputOutputRelation=unknown; alphaSemantics=未说明。; rangeBoundary=unknown; hdrImplication=无公开 HDR 格式契约。 | +| NIS | NVIDIA Image Scaling SDK (NVScaler/NVSharpen) | Input/output: non-integer data types, examples DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_NV12 (NV12 via NIS_NV12_SUPPORT);HDR modes: LDR [0,1], HDR PQ [0,1], HDR Linear recommended [0,12.5] | Output UAV: non-integer formats, same docs examples (R8G8B8A8_UNORM/NV12); shader writes RWTexture2D | ExplicitHDR | DirectFP16 | OfficialRepository | 4 | 输入为 SRV/read state,输出 UAV/write;sampler 必须 linear clamp;支持 viewport 子区域;NV12 需编译开关 NIS_NV12_SUPPORT;系数纹理 R32G32B32A32_FLOAT 或 R16G16B16A16_FLOAT。 | +| NNEDI3 | NNEDI3 | unspecified(inferred_from_shader_interface);rgba16f / rgba16hf(inferred_from_shader_interface) | unspecified(not_enumerated);rgba16f / rgba16hf(inferred_from_shader_interface) | FormatOnlyNoColorContract | DirectFP16 | OfficialRepository | 2 | inputFormatEnumeration=partial; outputFormatEnumeration=partial; inputOutputRelation=implementation_defined; alphaSemantics=未说明。; rangeBoundary=luma only / unknown; hdrImplication=rgba16f 只证明 mpv user shader 中间表面,不构成 HDR API 契约。 | +| Pixel Art | 无信息(Pixel Art 泛指像素画放大算法族) | unspecified(not_enumerated) | unspecified(not_enumerated) | 无信息 | 无信息 | 无信息 | 1 | inputFormatEnumeration=not_enumerated; outputFormatEnumeration=not_enumerated; inputOutputRelation=unknown; alphaSemantics=未说明。; rangeBoundary=unknown; hdrImplication=无公开 HDR 格式契约。 | +| RAVU | RAVU (Rapid and Accurate Video Upscaling) | mpv user-shader internal rgba16f/rgba16hf;ravu-yuv assumes YUV video after planes merged;ravu-rgb operates on RGB after planes merged | 内部 rgba16f/rgba16hf 表面 | FormatOnlyNoColorContract | DirectFP16 | OfficialRepository | 2 | 作为 mpv user shader 使用;gather/compute 版本分别用 textureGather/compute;d3d11 老驱动可用 rgba16hf 分支;ravu/ravu-lite 仅放大 luma 平面;ravu-yuv 需 YUV,ravu-rgb 需 RGB;ravu-zoom 任意比例。 | +| RTXVideo | NVIDIA RTX Video Super Resolution (VFX SDK VSR filter) | GPU buffers in BGRA or RGBA interleaved format, 8-bit unsigned per component | Same BGRA or RGBA interleaved U8 GPU buffer | SDROnly | SDRCompatible | OfficialDocs | 3 | 输入输出必须 GPU buffer,BGRA/RGBA interleaved,每分量 8-bit unsigned;Denoise/Deblur modes 输出分辨率必须与输入相同;VSR 支持不同 modes;建议最小 360p。 | +| RTXVideo | NVIDIA RTX Video Denoiser (VFX SDK VSR Denoise modes) | BGRA or RGBA interleaved U8 GPU buffers | BGRA or RGBA interleaved U8 GPU buffers, same resolution as input | SDROnly | SDRCompatible | OfficialDocs | 2 | Denoise modes 不支持 upscaling,输出分辨率必须等于输入;输入输出同为 BGRA/RGBA U8。 | +| RTXVideo | NVIDIA RTX Video HDR | unspecified(not_enumerated) | unspecified(not_enumerated) | ExplicitHDR | 无信息 | OfficialForum | 5 | inputFormatEnumeration=not_enumerated; outputFormatEnumeration=not_enumerated; inputOutputRelation=unknown; alphaSemantics=未说明。; rangeBoundary=SDR input -> HDR10 output 的产品语义;无量值范围。; hdrImplication=产品方向明确为 SDR->HDR,但无公开 HDR/FP16 格式契约。 | +| Sharpen | 无信息(Sharpen 为通用锐化效果族) | unspecified(not_enumerated) | unspecified(not_enumerated) | 无信息 | 无信息 | 无信息 | 1 | inputFormatEnumeration=not_enumerated; outputFormatEnumeration=not_enumerated; inputOutputRelation=unknown; alphaSemantics=未说明。; rangeBoundary=unknown; hdrImplication=无公开 HDR 格式契约。 | +| SMAA | SMAA (Subpixel Morphological Antialiasing) | colorTex: RGBA texture (2D color/luma input);edgesTex/areaTex/searchTex 等内部/预计算纹理;depthTex for depth edge detection | colorTex/blended output 4-channel color buffer | Unspecified | SDRCompatible | OfficialRepository | 4 | 官方集成说明要求两个 RGBA 时域 render target;创建后清除 alpha;所有 sampler linear + clamp;纹理读写默认非 sRGB,只有最终 NeighborhoodBlending 的 input/output 可 sRGB;64-bit 输入在 GCN 上有半速率线性过滤。 | +| xBRZ | xBRZ (pixel-art scaling algorithm) | RGBA pixel data in Uint8ClampedArray (TypeScript port) | RGBA pixel data in Uint8ClampedArray | SDROnly | SDRCompatible | GitHubExperiment | 3 | TypeScript 移植接口为源/目标 RGBA 像素缓冲;缩放因子 2-6;支持 alpha 透明;非原生 SDK 协议。 | +| XeSS | Intel XeSS Super Resolution (XeSS-SR) | R16G16B16A16_FLOAT;R11G11B10_FLOAT;R8G8B8A8_UNORM;其他 any linear color format; only UNORM integer color formats allowed;Motion vectors: R16G16_FLOAT;Depth: any depth format such as D32_FLOAT or D24_UNORM | Same format and color space as input (2D output texture) | ExplicitHDR | DirectFP16 | OfficialRepository | 5 | 输入颜色可为 LDR/HDR 任意 linear 格式;整数格式只允许 UNORM;输出必须与输入同格式同色彩空间,且输出 alpha 不保留并填 1.0;D3D12 输入 NON_PIXEL_SHADER_RESOURCE,输出 UAV;Vulkan 对应状态。 | +| XeSSFG | Intel XeSS Frame Generation (XeSS-FG) | Back buffer/HUD-less/UI-only: R10G10B10A2_UNORM for HDR10/BT.2100 HDR display;Motion vectors: R16G16_FLOAT or similar;Depth: any depth format such as D32_FLOAT or D24_UNORM;UI Alpha: single channel; UI Color and Alpha: s… | Interpolated frames output to proxy swap chain; same back buffer pixel format (HDR10 R10G10B10A2_UNORM documented) | ExplicitHDR | BoundedHDR | OfficialRepository | 4 | HDR display 支持 R10G10B10A2_UNORM + HDR10/BT.2100;明确不支持 FP16 HDR/scRGB;back buffer, HUD-less, UI-only 必须同像素格式、色彩空间、尺寸;MV 与 depth buffer 尺寸一致。 | +| NVIDIA Optical Flow | NVIDIA Optical Flow SDK (NvOF) | NV_OF_BUFFER_FORMAT_GRAYSCALE8;NV_OF_BUFFER_FORMAT_NV12;NV_OF_BUFFER_FORMAT_ABGR8 (A8B8G8R8) | NV_OF_FLOW_VECTOR (SHORT2, S10.5 flowx/flowy);NV_OF_STEREO_DISPARITY (for stereo mode);Cost buffer NV_OF_BUFFER_FORMAT_UINT or UINT8 | SDROnly | SDRCompatible | OfficialHeader | 4 | 输入帧支持 GRAYSCALE8/NV12/ABGR8;输出/提示为 SHORT2(S10.5);外部提示/成本格式另有要求;支持 output grid size 1/2/4;有宽高 min/max caps。 | +| AMD FidelityFX Optical Flow | FidelityFX Optical Flow | color input resource (格式未在文档枚举) | opticalFlowVector: R16G16_SINT;opticalFlowSCD: R32_UINT (3x1 scene change detection) | ExplicitHDR | BoundedHDR | OfficialDocs | 3 | 以 8x8 block 计算,输出纹理尺寸由 (displaySize+block-1)/8 决定;block size 固定 8;color 输入经 transfer function/luminance 转换。 | + +## 分条目证据 + +### 1. Anime4K — Anime4K + +- **mappingConfidence**:明确映射 +- **mappingNotes**:公开仓库名称与效果组名一致,且为 Anime4K 官方/上游仓库。 +- **documentedInputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:https://github.com/bloc97/Anime4K/blob/master/README.md + **notes**:Anime4K 官方仓库未枚举输入纹理格式;作为 mpv/libplacebo 用户着色器分发,实际纹理格式由宿主渲染器提供。 +- **documentedOutputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:https://github.com/bloc97/Anime4K/blob/master/README.md + **notes**:输出为目标/上采样后纹理;官方仓库未枚举像素格式。 +- **formatBoundary**: +- **inputFormatEnumeration**:not_enumerated +- **outputFormatEnumeration**:not_enumerated +- **inputOutputRelation**:implementation_defined +- **alphaSemantics**:未说明;不得从 mpv 用户着色器可编译推断 alpha 契约。 +- **rangeBoundary**:unknown +- **transferBoundary**:unspecified +- **resourceConstraints**:尺寸缩放/着色器 hook 点由 mpv/libplacebo 宿主决定;官方未提供 UAV/SRV 或 API 资源状态要求。 +- **hdrImplication**:无公开 HDR 格式契约。 +- **channelOrderAndAlpha**:无信息 +- **precisionAndRange**:无信息 +- **transferFunction**:无信息 +- **primariesAndColorSpace**:无信息 +- **hdrSupport**:无信息 +- **hdrBoundary**:未说明 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:无信息 +- **profileRationale**:十轮内未找到可核实协议,无法给出候选 profile。 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:无信息 +- **documentedQualityOptions**:A/B 模式等 GLSL 说明中的品质选择(未提供协议级格式语义) +- **documentedPerformanceNotes**:无信息 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://github.com/bloc97/Anime4K/blob/master/README.md | 标题: Anime4K README | 等级: OfficialRepository | claim: 项目为 Anime4K 官方仓库,未写明 HDR/纹理格式协议 | quoteOrSymbol: The simplicity and speed of Anime4K allows the user to watch upscaled anime in real time | searchRound: 1 + - URL: https://raw.githubusercontent.com/bloc97/Anime4K/v4.0.1/GLSL_Instructions.md | 标题: Anime4K v4.0.1 GLSL Instructions | 等级: OfficialRepository | claim: 只说明 mpv/GLSL 用法与模式,未说明纹理格式、数值范围、HDR | quoteOrSymbol: Mode A... | searchRound: 2 +- **searchRoundsUsed**:7 +- **unresolvedQuestions**:输入/输出纹理格式;数值范围;色彩空间;alpha;HDR/FP16 能力 +- **conclusion**:Anime4K 可明确映射到同名公开仓库,但公开仓库未提供图像 I/O 协议细节,本次记录为无协议信息。 + +### 2. CAS — FidelityFX Contrast Adaptive Sharpening (CAS) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:AMD FidelityFX SDK 的 CAS 技术,官方名称一致。 +- **documentedInputFormats**: + - **format**:DXGI_FORMAT_R8G8B8A8_UNORM + **apiOrContext**:DXGI / Direct3D 11 reference CLI + **channelOrder**:RGBA + **numericRepresentation**:UNORM + **acceptanceStatus**:reference_implementation_only + **evidenceRef**:https://raw.githubusercontent.com/GPUOpen-Effects/FidelityFX-CAS/78c03cf5cbf086ddcc06f635cc4def85a68b22df/CasCmdLine/README.md + **notes**:官方 CasCmdLine 默认输入格式;sRGB 解释由开关决定。 + - **format**:DXGI_FORMAT_R16G16B16A16_FLOAT + **apiOrContext**:DXGI / Direct3D 11 reference CLI + **channelOrder**:RGBA + **numericRepresentation**:FLOAT (16-bit) + **acceptanceStatus**:reference_implementation_only + **evidenceRef**:https://raw.githubusercontent.com/GPUOpen-Effects/FidelityFX-CAS/78c03cf5cbf086ddcc06f635cc4def85a68b22df/CasCmdLine/README.md + **notes**:官方 CasCmdLine --16bit 使用的格式;shader 另有 FP16/FP32 数学路径。 + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v1.1.0/docs/techniques/contrast-adaptive-sharpening.md + **notes**:SDK 技术文档未对 FfxResource 输入格式做 DXGI/Vulkan 枚举。 +- **documentedOutputFormats**: + - **format**:DXGI_FORMAT_R8G8B8A8_UNORM + **apiOrContext**:DXGI / Direct3D 11 reference CLI + **channelOrder**:RGBA + **numericRepresentation**:UNORM + **acceptanceStatus**:reference_implementation_only + **evidenceRef**:https://raw.githubusercontent.com/GPUOpen-Effects/FidelityFX-CAS/78c03cf5cbf086ddcc06f635cc4def85a68b22df/CasCmdLine/README.md + **notes**:默认输出格式,与输入同格式;官方样例说明输出可为相同或更大尺寸。 + - **format**:DXGI_FORMAT_R16G16B16A16_FLOAT + **apiOrContext**:DXGI / Direct3D 11 reference CLI + **channelOrder**:RGBA + **numericRepresentation**:FLOAT (16-bit) + **acceptanceStatus**:reference_implementation_only + **evidenceRef**:https://raw.githubusercontent.com/GPUOpen-Effects/FidelityFX-CAS/78c03cf5cbf086ddcc06f635cc4def85a68b22df/CasCmdLine/README.md + **notes**:--16bit 输出格式;官方文档保证输出与输入同色彩空间。 + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v1.1.0/docs/techniques/contrast-adaptive-sharpening.md + **notes**:SDK 的 FfxResource output 未在技术文档中列出具体格式枚举。 +- **formatBoundary**: +- **inputFormatEnumeration**:partial +- **outputFormatEnumeration**:partial +- **inputOutputRelation**:same_format_required +- **alphaSemantics**:未说明;SDK 回调只读写 RGB,未见 alpha 透传/置 1 声明。 +- **rangeBoundary**:sRGB/linear 解释均支持;shader 转换注释按 [0,1] 线性空间处理。 +- **transferBoundary**:linear input expected; output returned to same input color space via FfxCasColorSpaceConversion +- **resourceConstraints**:官方样例支持 sharpen-only/sharpen+upscale;输入 SRV/read、输出 UAV/write 在 SDK 样例路径;格式枚举只来自 CasCmdLine 参考实现。 +- **hdrImplication**:官方参考实现支持 R16G16B16A16_FLOAT 与 linear 开关,但这属于参考实现路径;SDK 技术文档未提供 HDR 元数据契约。 +- **channelOrderAndAlpha**:回调只处理 RGB;文档未说明 alpha 是否透传或必须为 1。 +- **precisionAndRange**:回调存在 float32 与 float16 路径;色彩转换注释按 [0,1] 线性空间处理,文档未给出 8/10/16 位格式清单。 +- **transferFunction**:期望线性输入;通过 FfxCasColorSpaceConversion 支持 LINEAR、GAMMA20、GAMMA22、sRGB_OUTPUT、sRGB_INPUT_OUTPUT。 +- **primariesAndColorSpace**:未说明原色域。 +- **hdrSupport**:Unspecified +- **hdrBoundary**:文档未说明 HDR 直接输入;只要求线性输入并保持输入输出同色彩空间。 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:SDRCompatible +- **profileRationale**:官方 shader 注释将输入转换描述为 0..1 线性空间,且没有任何 HDR/FP16 直接路径声明;保守记为 SDR 兼容。 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:无信息 +- **documentedQualityOptions**:sharpening disabled;sharpening enabled, upsampling disabled;both sharpening and upsampling enabled +- **documentedPerformanceNotes**:文档未比较 FP16/UNORM/HDR 路径成本。 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v1.1.0/docs/techniques/contrast-adaptive-sharpening.md | 标题: FidelityFX Contrast Adaptive Sharpening 1.1 | 等级: OfficialDocs | claim: CAS 需要线性输入;可按输入空间转换并在输出还原 | quoteOrSymbol: CAS needs linear input color to perform correctly | searchRound: 1 + - URL: https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v1.1.0/sdk/include/FidelityFX/host/ffx_cas.h | 标题: ffx_cas.h | 等级: OfficialHeader | claim: FfxCasDispatchDescription 仅含 FfxResource color/output、renderSize、sharpness | quoteOrSymbol: FfxResource color; FfxResource output | searchRound: 2 + - URL: https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v1.1.0/sdk/include/FidelityFX/gpu/cas/ffx_cas_callbacks_hlsl.h | 标题: ffx_cas_callbacks_hlsl.h | 等级: OfficialHeader | claim: float32/float16 采样与颜色空间转换宏 | quoteOrSymbol: casInputHalf... between 0 and 1 | searchRound: 3 + - URL: https://raw.githubusercontent.com/GPUOpen-Effects/FidelityFX-CAS/78c03cf5cbf086ddcc06f635cc4def85a68b22df/CasCmdLine/README.md | 标题: FidelityFX-CAS CasCmdLine README | 等级: OfficialRepository | claim: 官方命令行参考实现默认 R8G8B8A8_UNORM,--16bit 用 R16G16B16A16_FLOAT | quoteOrSymbol: uses R8G8B8A8_UNORM ... uses R16G16B16A16_FLOAT | searchRound: 5 +- **searchRoundsUsed**:7 +- **unresolvedQuestions**:具体支持格式列表;alpha 契约;HDR 数值上限;primaries +- **conclusion**:CAS 的官方协议只明确了线性输入、输入输出同空间、float/fp16 shader 回调与可选色彩空间转换;具体 DXGI 格式与 HDR 能力未被官方文档枚举。 + +### 3. CRT — 无信息(CRT 效果族未能在公开资料中唯一映射) + +- **mappingConfidence**:无信息 +- **mappingNotes**:仅按通用名称“CRT”搜索;公开资料无法唯一对应某个上游算法/SDK,且不能借助 Magpie 源码确认。 +- **documentedInputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:无公开唯一上游来源 + **notes**:无法唯一映射上游;不得把通用 CRT 着色器格式写成该效果组已支持格式。 +- **documentedOutputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:无公开唯一上游来源 + **notes**:无法唯一映射上游;输出格式未公开。 +- **formatBoundary**: +- **inputFormatEnumeration**:not_enumerated +- **outputFormatEnumeration**:not_enumerated +- **inputOutputRelation**:unknown +- **alphaSemantics**:未说明;名称不能唯一映射,无法建立 alpha 契约。 +- **rangeBoundary**:unknown +- **transferBoundary**:unspecified +- **resourceConstraints**:未确认任何资源状态、缩放或采样限制。 +- **hdrImplication**:无公开 HDR 格式契约。 +- **channelOrderAndAlpha**:无信息 +- **precisionAndRange**:无信息 +- **transferFunction**:无信息 +- **primariesAndColorSpace**:无信息 +- **hdrSupport**:无信息 +- **hdrBoundary**:未说明 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:无信息 +- **profileRationale**:无信息 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:无信息 +- **documentedQualityOptions**:无信息 +- **documentedPerformanceNotes**:无信息 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - 无来源(无信息条目;已尝试来源类型与轮数列于条目尾部) +- **searchRoundsUsed**:1 +- **unresolvedQuestions**:上游映射;输入格式;色彩空间;HDR/FP16 +- **conclusion**:“CRT”效果组无法在公开资料中唯一映射到上游算法或 SDK,十轮内无法取得可靠协议结论,记录为无信息。 + +### 4. CuNNy — CuNNy + +- **mappingConfidence**:明确映射 +- **mappingNotes**:上游仓库 Blinue/CuNNy 名称与效果组一致,README 自述“supports exporting to an mpv shader”和“Magpie effect”。 +- **documentedInputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:https://raw.githubusercontent.com/Blinue/CuNNy/master/README.md + **notes**:上游仓库只描述 mpv shader/Magpie effect,未给出纹理格式。 +- **documentedOutputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:https://raw.githubusercontent.com/Blinue/CuNNy/master/README.md + **notes**:输出为上采样图像,格式未枚举。 +- **formatBoundary**: +- **inputFormatEnumeration**:not_enumerated +- **outputFormatEnumeration**:not_enumerated +- **inputOutputRelation**:implementation_defined +- **alphaSemantics**:未说明。 +- **rangeBoundary**:unknown +- **transferBoundary**:unspecified +- **resourceConstraints**:mpv shader 导出路径意味着由 mpv/libplacebo 管理 hook 纹理;无 API 资源约束。 +- **hdrImplication**:无公开 HDR 格式契约。 +- **channelOrderAndAlpha**:无信息 +- **precisionAndRange**:无信息 +- **transferFunction**:无信息 +- **primariesAndColorSpace**:无信息 +- **hdrSupport**:无信息 +- **hdrBoundary**:未说明 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:无信息 +- **profileRationale**:无信息 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:无信息 +- **documentedQualityOptions**:8x32/4x3 等模型尺寸(来自 README,无格式协议语义) +- **documentedPerformanceNotes**:无信息 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://raw.githubusercontent.com/Blinue/CuNNy/master/README.md | 标题: CuNNy README | 等级: OfficialRepository | claim: 上游仓库描述 CuNNy 可导出 mpv shader/Magpie effect,未给出 I/O 格式 | quoteOrSymbol: Supports exporting to an mpv meme shader! | searchRound: 1 +- **searchRoundsUsed**:2 +- **unresolvedQuestions**:输入输出纹理格式;HDR/FP16;alpha;数值范围 +- **conclusion**:CuNNy 上游可明确映射,但公开仓库没有图像协议细节,无法确定格式边界。 + +### 5. CuNNy2 — 无信息(未能定位独立 CuNNy2 上游仓库/SDK) + +- **mappingConfidence**:无信息 +- **mappingNotes**:公开搜索未找到与“CuNNy2”唯一对应的上游算法/模型/SDK 文档;现有结果多为 Magpie 版本页等,不可用于协议推断。 +- **documentedInputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:公开搜索未发现独立 CuNNy2 上游 + **notes**:无法唯一映射到独立上游;不存在可归属的格式协议。 +- **documentedOutputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:公开搜索未发现独立 CuNNy2 上游 + **notes**:同上。 +- **formatBoundary**: +- **inputFormatEnumeration**:not_enumerated +- **outputFormatEnumeration**:not_enumerated +- **inputOutputRelation**:unknown +- **alphaSemantics**:未说明。 +- **rangeBoundary**:unknown +- **transferBoundary**:unspecified +- **resourceConstraints**:无上游资源约束可记录。 +- **hdrImplication**:无公开 HDR 格式契约。 +- **channelOrderAndAlpha**:无信息 +- **precisionAndRange**:无信息 +- **transferFunction**:无信息 +- **primariesAndColorSpace**:无信息 +- **hdrSupport**:无信息 +- **hdrBoundary**:未说明 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:无信息 +- **profileRationale**:无信息 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:无信息 +- **documentedQualityOptions**:无信息 +- **documentedPerformanceNotes**:无信息 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - 无来源(无信息条目;已尝试来源类型与轮数列于条目尾部) +- **searchRoundsUsed**:2 +- **unresolvedQuestions**:CuNNy2 上游映射;格式;HDR/FP16;alpha +- **conclusion**:CuNNy2 未能在公开资料中唯一映射到上游算法或 SDK,记录为无信息。 + +### 6. Diagnostics — 无信息(Diagnostics 是通用诊断效果名) + +- **mappingConfidence**:无信息 +- **mappingNotes**:公开资料中不存在可作为上游协议的“Diagnostics”图像算法/SDK;不依据 Magpie 实现推断。 +- **documentedInputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:公开资料无对应上游算法 + **notes**:Diagnostics 为通用诊断效果名,不存在公开算法/SDK 格式枚举。 +- **documentedOutputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:公开资料无对应上游算法 + **notes**:同上。 +- **formatBoundary**: +- **inputFormatEnumeration**:not_enumerated +- **outputFormatEnumeration**:not_enumerated +- **inputOutputRelation**:unknown +- **alphaSemantics**:未说明。 +- **rangeBoundary**:unknown +- **transferBoundary**:unspecified +- **resourceConstraints**:无资源约束可记录。 +- **hdrImplication**:无公开 HDR 格式契约。 +- **channelOrderAndAlpha**:无信息 +- **precisionAndRange**:无信息 +- **transferFunction**:无信息 +- **primariesAndColorSpace**:无信息 +- **hdrSupport**:无信息 +- **hdrBoundary**:未说明 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:无信息 +- **profileRationale**:无信息 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:无信息 +- **documentedQualityOptions**:无信息 +- **documentedPerformanceNotes**:无信息 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - 无来源(无信息条目;已尝试来源类型与轮数列于条目尾部) +- **searchRoundsUsed**:1 +- **unresolvedQuestions**:上游映射;I/O 格式;HDR/FP16 +- **conclusion**:Diagnostics 无公开上游协议,记录为无信息。 + +### 7. DLSS — NVIDIA DLSS Super Resolution (DLSS-SR) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:NVIDIA DLSS SDK / Streamline 中的 DLSS Super Resolution,公共品牌与效果组一致。 +- **documentedInputFormats**: +Color input: any supported buffer format for the API +Motion vectors: RG32_FLOAT or RG16_FLOAT +Depth: any one-channel format (R32_FLOAT/D32_FLOAT) or depth-stencil (D24S8) +Exposure: 1x1 texture, R16F preferred (presets J/K) +- **documentedOutputFormats**: +Output buffer: any supported buffer format for the API +Previous output/history buffer: optional, RGBA16F +- **formatBoundary**: +官方编程指南要求输入为 SRV/read 状态,输出为 UAV/write 状态;颜色输入任意 API 支持格式,输出任意 API 支持格式;历史输出建议 RGBA16F;最小输出 32x32;无规定最大分辨率但 8K 以上未保证。 +- **channelOrderAndAlpha**:Streamline 默认只放大 RGB;alphaUpscalingEnabled 为实验性时可放大 alpha;官方 DLSS 指南未规定固定通道顺序。 +- **precisionAndRange**:LDR 模式颜色值必须 [0,1] 且为感知编码(如 sRGB),不能为线性;HDR 模式可处理线性高范围、无亮度上限,内部高精度。 +- **transferFunction**:LDR: sRGB/perceptual encoding; HDR: linear space (IsHDR flag). +- **primariesAndColorSpace**:未说明 primaries;HDR 为线性场景/显示域未细分。 +- **hdrSupport**:ExplicitHDR +- **hdrBoundary**:HDR 输入需将 IsHDR 置 1;需要 exposure(1x1 texture,R16F 首选)供当前帧使用;无 tone mapping 要求;输出仍同输入线性域。 +- **requiredHdrMetadata**:exposure value (1x1 texture) +- **candidateInternalProfile**:DirectFP16 +- **profileRationale**:官方明确 HDR 线性路径、无亮度上限且历史/输出使用/推荐 RGBA16F;有直接的 HDR/FP16 证据。 +- **auxiliaryInputs**:depth;motion vectors;exposure;jitter;previous output/history (optional) +- **auxiliaryInputFormatsAndSemantics**:Depth any one-channel/depth-stencil; MVs RG32_FLOAT or RG16_FLOAT in pixel screen-space, can be low-res or high-res/dilated; exposure 1x1, R16F preferred; jitter in [-0.5,0.5] pixels at render res. +- **temporalOrFrameGenerationConstraints**:时域历史由 DLSS 内部维护,可选 previous output RGBA16F;reset/jump cut 语义见编程指南;动态分辨率需保持长宽比;mip bias 需要负偏移。 +- **documentedQualityOptions**:DLSS quality/balanced/performance/ultra-performance presets (公开质量档,格式不随档位变化) +- **documentedPerformanceNotes**:官方说明 LDR 模式性能更优且内部量化到 8-bit;HDR 模式内部高精度。 +- **qualityPerformanceEvidence**:官方指南有 LDR 性能更优说明,但未给出格式路径基准表。 +- **sources**: + - URL: https://github.com/NVIDIA/DLSS/blob/main/doc/DLSS_Programming_Guide_Release.pdf | 标题: NVIDIA DLSS Super Resolution Programming Guide Release | 等级: OfficialDocs | claim: Supported Formats: color any API format; MV RG32_FLOAT/RG16_FLOAT; depth any one-channel/depth-stencil; output any API format; history RGBA16F | quoteOrSymbol: 3.3 Supported Formats | searchRound: 1 + - URL: https://raw.githubusercontent.com/NVIDIA/DLSS/main/doc/DLSS_Programming_Guide_Release.pdf | 标题: DLSS Programming Guide PDF (raw) | 等级: OfficialDocs | claim: LDR [0,1] perceptual; HDR linear unbounded | quoteOrSymbol: DLSS can process color data stored as either LDR or HDR | searchRound: 2 + - URL: https://raw.githubusercontent.com/NVIDIA-RTX/Streamline/main/docs/ProgrammingGuideDLSS.md | 标题: Streamline DLSS Super Resolution Integration | 等级: OfficialRepository | claim: DLSS-SR requires colorIn/colorOut/depth/mvec/exposure; colorBuffersHDR and alphaUpscalingEnabled options | quoteOrSymbol: dlssOptions.colorBuffersHDR = sl::Boolean::eTrue | searchRound: 3 +- **searchRoundsUsed**:6 +- **unresolvedQuestions**:具体 DXGI 支持清单(官方用“any API format”而非枚举);primaries;输出 alpha 默认值 +- **conclusion**:DLSS-SR 官方协议清晰:颜色输入输出可为 API 任意支持格式,HDR 线性模式通过 IsHDR 开启,运动向量固定 RG32/RG16_FLOAT,深度为单通道/深度模板,历史推荐 RGBA16F。 + +### 8. DLSSFG — NVIDIA DLSS Frame Generation (DLSS-G / DLSS 4 Multi-Frame Gen) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:Streamline/NVIDIA DLSS Frame Generation 官方文档与头文件。 +- **documentedInputFormats**: +Backbuffer/Final Color resource (format not enumerated in public text) +Motion vectors/depth: same DLSS-SR requirements +- **documentedOutputFormats**: +OutputInterpolated/OutputReal: same texture format as Backbuffer +- **formatBoundary**: +官方头文件写明 Required Output Texture (same texture format as Backbuffer);HUDless 与 UI 纹理需与 backbuffer 同尺寸/格式/色彩空间;backbuffer 资源经 Streamline swapchain 拦截。 +- **channelOrderAndAlpha**:UI Alpha 为单通道 0..1;UI Color and Alpha 需预乘 alpha;HUDless 为无 UI 全场景颜色;alpha 参与 UI 合成。 +- **precisionAndRange**:未公开列举 8/16 位格式;只有 colorBuffersHDR 布尔标志表示 HDR。 +- **transferFunction**:未在公开文本中细分;HUDless 与 backbuffer 必须同色彩空间(含 tone mapping)。 +- **primariesAndColorSpace**:未说明 primaries。 +- **hdrSupport**:ExplicitHDR +- **hdrBoundary**:头文件有 full HDR 标志;未提供 PQ/HLG/scRGB 解码或 metadata 要求;应把与 backbuffer 相同色彩空间的帧交给帧生成。 +- **requiredHdrMetadata**:colorBuffersHDR flag;无公开 MaxCLL/MaxFALL/亮度要求 +- **candidateInternalProfile**:FormatOnlyNoColorContract +- **profileRationale**:官方确认 HDR 布尔和“输出与 backbuffer 同格式”,但未公开 HDR 的具体格式/色彩空间契约;不能判定 DirectFP16。 +- **auxiliaryInputs**:depth;motion vectors;HUD-less color;UI alpha or UI color+alpha;bidirectional distortion field (optional);backbuffer subrect +- **auxiliaryInputFormatsAndSemantics**:Depth/mvec same as DLSS-SR; HUDless/UI must match backbuffer; UI alpha 0..1; UI RGB premultiplied; distortion field maps distorted final color to undistorted guide buffers. +- **temporalOrFrameGenerationConstraints**:需要 dense motion vectors, depth, HUDless;插值输出与真实帧输出为同格式;帧生成要求 present 时资源仍有效;子矩形需 extent 匹配;UI 合成公式 Final.RGB = UI.RGB + (1-UI.Alpha)*HUDless.RGB。 +- **documentedQualityOptions**:插值帧数/模式等公开选项,未涉及格式 +- **documentedPerformanceNotes**:公开指南建议优先提供 UI Alpha 单通道以获得更好性能;无格式基准。 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://raw.githubusercontent.com/NVIDIA-RTX/Streamline/main/docs/ProgrammingGuideDLSS_G.md | 标题: Streamline DLSS-G Programming Guide | 等级: OfficialRepository | claim: 列出深度、运动向量、HUDless、UI 输入与资源生命周期约束 | quoteOrSymbol: Hudless ... same color space and post-processing effects as color backbuffer | searchRound: 1 + - URL: https://raw.githubusercontent.com/NVIDIA/DLSS/main/include/nvsdk_ngx_defs_dlssg.h | 标题: nvsdk_ngx_defs_dlssg.h | 等级: OfficialHeader | claim: Required Output Texture (same texture format as Backbuffer) | quoteOrSymbol: #define NVSDK_NGX_DLSSG_Parameter_BackbufferFormat "DLSSG.BackbufferFormat" | searchRound: 2 +- **searchRoundsUsed**:5 +- **unresolvedQuestions**:实际接受/输出的 DXGI 格式枚举;HDR PQ/HLG/scRGB 颜色契约;metadata +- **conclusion**:DLSSFG 官方公开材料确认输入由 swapchain/backbuffer 决定、输出必须与 backbuffer 同格式,并有 HDR 布尔开关;没有公开更细的格式枚举或 PQ/scRGB 协议。 + +### 9. FSR — AMD FidelityFX Super Resolution 1 (FSR1 spatial upscaler) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:Magpie 效果组“FSR”按公开产品命名对应 FSR1 的空间超分;FSR2/3/4 已单列。 +- **documentedInputFormats**: + - **format**:unspecified + **apiOrContext**:algorithmic reference implementation (shader callback) + **channelOrder**:RGB (3 channel) + **numericRepresentation**:FLOAT/half + **acceptanceStatus**:inferred_from_shader_interface + **evidenceRef**:https://raw.githubusercontent.com/GPUOpen-Effects/FidelityFX-FSR/master/ffx-fsr/ffx_fsr1.h + **notes**:官方 ffx_fsr1.h 提供 RGB 输入回调;未给出具体 DXGI 格式。 + - **format**:unspecified + **apiOrContext**:DXGI/Vulkan/API resource + **channelOrder**:RGB + **numericRepresentation**:UNORM/FLOAT + **acceptanceStatus**:not_enumerated + **evidenceRef**:https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v1.0.0/docs/techniques/super-resolution-spatial.md + **notes**:官方 FSR1 文档只要求 [0,1] perceptual sRGB,不枚举资源格式。 +- **documentedOutputFormats**: + - **format**:unspecified + **apiOrContext**:algorithmic reference implementation (shader callback) + **channelOrder**:RGB (3 channel) + **numericRepresentation**:FLOAT/half + **acceptanceStatus**:inferred_from_shader_interface + **evidenceRef**:https://raw.githubusercontent.com/GPUOpen-Effects/FidelityFX-FSR/master/ffx-fsr/ffx_fsr1.h + **notes**:EASU/RCAS 输出 RGB;最终输出表面格式由宿主分配。 + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v1.0.0/docs/techniques/super-resolution-spatial.md + **notes**:输出格式未在 FSR1 文档中单独列枚举。 +- **formatBoundary**: +- **inputFormatEnumeration**:not_enumerated +- **outputFormatEnumeration**:not_enumerated +- **inputOutputRelation**:implementation_defined +- **alphaSemantics**:未说明。 +- **rangeBoundary**:[0,1] perceptual sRGB;RCAS 负输入会产生 NaN。 +- **transferBoundary**:sRGB / perceptual(非 linear);helper 另含 linear HDR {0 to FP16_MAX} 转换工具,但非核心输入契约。 +- **resourceConstraints**:shader 回调 gather4;输入资源可为动态分辨率 viewport;建议 32bpp 格式;无 UAV/SRV 官方枚举。 +- **hdrImplication**:FSR1 核心文档没有 HDR 直接输入路径;线性 HDR helper 属可选周边工具,不能算核心格式契约。 +- **channelOrderAndAlpha**:未说明 alpha。 +- **precisionAndRange**:颜色归一化 [0,1];感知编码 sRGB。 +- **transferFunction**:sRGB / perceptual(不是线性)。 +- **primariesAndColorSpace**:未说明原色域(通常 sRGB,但资料未写 primaries)。 +- **hdrSupport**:SDROnly +- **hdrBoundary**:官方文档面向 [0,1] sRGB 输入,未提供 HDR 线性/PQ/HLG 路径。 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:SDRCompatible +- **profileRationale**:官方明确要求 [0-1] perceptual sRGB、不能线性,未见 HDR 路径,故为 SDR 兼容。 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:无信息 +- **documentedQualityOptions**:EASU+RCAS 组合/quality mode 由调用方决定(官方未给出档位枚举) +- **documentedPerformanceNotes**:无信息 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v1.0.0/docs/techniques/super-resolution-spatial.md | 标题: FidelityFX Super Resolution 1 (FSR1) | 等级: OfficialDocs | claim: Input normalized [0-1], perceptual sRGB not linear; negative RCAS input NaN | quoteOrSymbol: Image should be normalized to [0-1] and be in perceptual color space (sRGB, not linear) | searchRound: 1 + - URL: https://raw.githubusercontent.com/GPUOpen-Effects/FidelityFX-FSR/master/ffx-fsr/ffx_fsr1.h | 标题: FidelityFX-FSR ffx_fsr1.h | 等级: OfficialHeader | claim: FSR1 通过 RGB 输入回调读取颜色;建议 32bpp 性能格式;RCAS 输入范围 {0 to 1} | quoteOrSymbol: Color needs to be encoded as 3 channel [red, green, blue] | searchRound: 4 +- **searchRoundsUsed**:6 +- **unresolvedQuestions**:DXGI 格式清单;alpha;输出格式要求 +- **conclusion**:FSR1 官方资料可确认是 [0,1] sRGB 感知色彩输入的空间超分算法,没有 HDR/FP16 协议证据,候选为 SDRCompatible。 + +### 10. FSR2 — AMD FidelityFX Super Resolution 2 (FSR2 temporal upscaler) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:官方文档标题为 AMD FidelityFX Super Resolution 2.3.x。 +- **documentedInputFormats**: +Color buffer: APPLICATION SPECIFIED (render res) +Depth: APPLICATION SPECIFIED (1x FLOAT) +Motion vectors: APPLICATION SPECIFIED (2x FLOAT) +Reactive mask: R8_UNORM +Transparency & composition mask: R8_UNORM +Exposure: R32_FLOAT (1x1) +- **documentedOutputFormats**: +Adjusted color buffer (internal): R16G16B16A16_FLOAT +Upscaled buffer (internal): R16G16B16A16_FLOAT +API 输出缓冲的公开格式在文档中未单独枚举,一般随应用分配资源 +- **formatBoundary**: +颜色输入由应用指定格式;HDR 需设置 HDR flag;深度单浮点;MVs 2x float 屏幕空间范围 [<-w,-h>,];内部多使用 16-bit;输出最终与输入同域(内部 tonemap 会被反转);要求 GPU 支持 typed UAV load 和 R16G16B16A16_UNORM。 +- **channelOrderAndAlpha**:内部 Adjusted color 使用 YCoCg 且 alpha 为 disocclusion mask;对外 alpha 契约未说明。 +- **precisionAndRange**:颜色可为应用指定的 UNORM/FLOAT 等;HDR 线性域;MVs 内部 16-bit;exposure R32_FLOAT。 +- **transferFunction**:HDR 模式要求 linear;SDK 后续也允许 non-linear 标志(见 FSR4/upscaler 文档)。 +- **primariesAndColorSpace**:未说明 primaries。 +- **hdrSupport**:ExplicitHDR +- **hdrBoundary**:设置 FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE 后输入应为 linear;输出恢复原输入域;需要 pre-exposure/exposure 语义。 +- **requiredHdrMetadata**:pre-exposure;exposure (R32_FLOAT) 或 auto-exposure +- **candidateInternalProfile**:DirectFP16 +- **profileRationale**:官方内部表面明确 R16G16B16A16_FLOAT、HDR 线性支持,且有 FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE 直接路径。 +- **auxiliaryInputs**:depth;motion vectors;reactive mask;transparency & composition mask;exposure;camera jitter;reset +- **auxiliaryInputFormatsAndSemantics**:MVs in pixels screen-space, low-res/display-res flag; reactive/T&C mask R8_UNORM [0,1]; exposure R32_FLOAT optional; depth flags inverted/infinite. +- **temporalOrFrameGenerationConstraints**:时域算法需要当前/上一帧数据与历史;所有 render-res 输入需 jitter(MV 例外);camera jump cut 需 reset;FrameTimeDelta 输入。 +- **documentedQualityOptions**:quality/balanced/performance 等调用方档位(API 主要按 input/output 尺寸) +- **documentedPerformanceNotes**:内部 MVs 使用 16-bit,高精度输入不会额外受益;官方要求 R16G16B16A16_UNORM 支持;无公开格式路径性能基准。 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://github.com/GPUOpen-Effects/FidelityFX-FSR2/blob/master/README.md | 标题: FidelityFX-FSR2 README | 等级: OfficialRepository | claim: Input resource table and internal R16G16B16A16_FLOAT buffers | quoteOrSymbol: Upscaled buffer ... R16G16B16A16_FLOAT | searchRound: 1 + - URL: https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/main/Kits/FidelityFX/docs/techniques/super-resolution-temporal.md | 标题: FidelityFX Super Resolution 2.3.4 | 等级: OfficialDocs | claim: Input resources table, HDR support, linear color | quoteOrSymbol: High dynamic range images are supported... linear color space | searchRound: 2 +- **searchRoundsUsed**:4 +- **unresolvedQuestions**:对外输出 FfxResource 的格式枚举;alpha 对外行为;primaries +- **conclusion**:FSR2 官方协议:颜色输入为应用指定格式,HDR 需 linear 输入与 HDR flag;辅助资源格式明确;内部浮点表面 R16G16B16A16_FLOAT。 + +### 11. FSR3 — AMD FidelityFX Super Resolution 3.1 Upscaler (FSR3 SR) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:FSR3 效果组拆为“FSR3 Upscaler”与“FSR3 Frame Interpolation”两个后端;此处为官方 FSR3 3.1.x Upscaler。 +- **documentedInputFormats**: +Color buffer: APPLICATION SPECIFIED +Depth: APPLICATION SPECIFIED (1x FLOAT) +Motion vectors: APPLICATION SPECIFIED (2x FLOAT) +Reactive mask: R8_UNORM +T&C mask: R8_UNORM +Exposure: R32_FLOAT +- **documentedOutputFormats**: +未单独枚举;文档称输出与原始输入同域(internal tonemap 被反转) +- **formatBoundary**: +与 FSR2 输入结构一致;HDR flag 开启时输入 linear;要求 R16G16B16A16_UNORM typed UAV 支持;API 未在文档中列 DXGI 输出枚举。 +- **channelOrderAndAlpha**:对外 alpha 未说明。 +- **precisionAndRange**:颜色应用指定;HDR linear;MVs 2x float;内部 16-bit。 +- **transferFunction**:HDR linear;文档未给出额外非线性颜色自动转换。 +- **primariesAndColorSpace**:未说明。 +- **hdrSupport**:ExplicitHDR +- **hdrBoundary**:设置 FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE,线性输入,输出恢复原输入域;需要 exposure/pre-exposure。 +- **requiredHdrMetadata**:pre-exposure;exposure;auto-exposure flag 可选 +- **candidateInternalProfile**:DirectFP16 +- **profileRationale**:官方 HDR 线性直接路径 + 内部浮点表面证据与 FSR2 同类。 +- **auxiliaryInputs**:depth;motion vectors;reactive mask;T&C mask;exposure;jitter;reset;frame time delta +- **auxiliaryInputFormatsAndSemantics**:同 FSR2:depth float、MV 2x float、R8_UNORM masks、R32_FLOAT exposure。 +- **temporalOrFrameGenerationConstraints**:时域超分要求 jitter、reset、历史;FrameTimeDelta 输入。 +- **documentedQualityOptions**:FSR 质量/性能档由应用选择(未在格式文档中) +- **documentedPerformanceNotes**:未找到可核实的格式路径性能结论。 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/main/Kits/FidelityFX/docs/techniques/super-resolution-upscaler.md | 标题: AMD FidelityFX Super Resolution 3.1.5 Upscaler | 等级: OfficialDocs | claim: FSR3 Upscaler input resources and HDR linear support | quoteOrSymbol: FSR Super Resolution ... HDR support | searchRound: 1 +- **searchRoundsUsed**:2 +- **unresolvedQuestions**:输出格式枚举;alpha;primaries +- **conclusion**:FSR3 Upscaler 与 FSR2 的输入/辅助资源/HDR linear 协议高度一致;公开文档未列外部输出格式枚举。 + +### 12. FSR3 — AMD FidelityFX Super Resolution 3 Frame Interpolation / Frame Generation (FSR3 FG) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:FSR3 的帧生成/插值后端,官方技术名 FidelityFX Frame Interpolation;拆为独立子条目。 +- **documentedInputFormats**: +currentBackBuffer (presentation color buffer) +currentBackBuffer_HUDLess (optional) +depth (required for FSR3 interpolation workflow) +motion vectors (required) +R16G16_SINT optical flow vector +R32_UINT optical flow SCD +- **documentedOutputFormats**: +interpolated output resource +outputSwapChainBuffer +格式未在公开文档列枚举;代码示例用 swap chain back buffer format +- **formatBoundary**: +官方文档要求传入 backBufferFormat;HUD-less/UI 资源与 backbuffer 关系密切;帧生成需要超分先行;输入资源格式由配置描述,未列完整 DXGI 清单。 +- **channelOrderAndAlpha**:HUDLess/UI 合成涉及 alpha/UI 内容;具体 alpha 格式未完整说明。 +- **precisionAndRange**:未公开逐格式数值范围;有 FFX_FRAMEINTERPOLATION_ENABLE_HDR_COLOR_INPUT 与 minMaxLuminance。 +- **transferFunction**:config 含 backBufferTransferFunction 用于把插值源转换到 linear RGB;HDR 转换依赖 minMaxLuminance。 +- **primariesAndColorSpace**:未说明 primaries;backBufferTransferFunction 枚举存在。 +- **hdrSupport**:ExplicitHDR +- **hdrBoundary**:需设置 HDR_COLOR_INPUT;通过 backBufferTransferFunction 与 minMaxLuminance 把 HDR 颜色转到 linear RGB;未公开 MaxCLL/MaxFALL 字段。 +- **requiredHdrMetadata**:backBufferTransferFunction;minMaxLuminance[2] +- **candidateInternalProfile**:BoundedHDR +- **profileRationale**:官方明确 HDR 颜色输入需经 transfer function + min/max luminance 转换为线性 RGB,即有界/转换契约,而非直接 FP16 无 metadata 路径。 +- **auxiliaryInputs**:depth;game motion vectors;FidelityFX Optical Flow vectors;HUDLess color;UI/HUD handling (presentCallback or HUDLessColor) +- **auxiliaryInputFormatsAndSemantics**:depth inverted/infinite flags; opticalFlowVector R16G16_SINT; opticalFlowSCD R32_UINT; HUDLess optional FfxResource. +- **temporalOrFrameGenerationConstraints**:帧生成需要 previous/current frame、光流、深度、运动向量;presentCallback/HUDLess 用于 UI;重置、延迟、显示分辨率限制在 FSR3 集成指南。 +- **documentedQualityOptions**:FSR3 frame generation on/off, UI composition modes 等(无格式档位) +- **documentedPerformanceNotes**:未找到可核实的格式路径性能结论。 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/release-FSR3-3.0.3/docs/techniques/frame-interpolation.md | 标题: FidelityFX Frame Interpolation | 等级: OfficialDocs | claim: Config includes HDR flag, backBufferTransferFunction, minMaxLuminance; optical flow outputs | quoteOrSymbol: FFX_FRAMEINTERPOLATION_ENABLE_HDR_COLOR_INPUT | searchRound: 1 + - URL: https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/release-FSR3-3.0.3/docs/techniques/optical-flow.md | 标题: FidelityFX Optical Flow | 等级: OfficialDocs | claim: Optical flow vector R16G16_SINT, SCD R32_UINT | quoteOrSymbol: R16G16_SINT ... R32_UINT | searchRound: 2 +- **searchRoundsUsed**:3 +- **unresolvedQuestions**:具体 backbuffer 格式支持清单;HDR PQ/scRGB 细节;alpha 行为 +- **conclusion**:FSR3 Frame Generation 官方协议确认需要 backbuffer 格式、HDR 输入标志和 transfer/luminance 参数;外部格式仍由 swapchain/backbuffer 决定,未公开完整格式枚举。 + +### 13. FSR4 — AMD FidelityFX Super Resolution 4 (FSR4 ML Super Resolution) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:官方 FidelityFX SDK 文档标题“AMD FSR Super Resolution 4”。 +- **documentedInputFormats**: +Color buffer: APPLICATION SPECIFIED +Depth: APPLICATION SPECIFIED (1x FLOAT) +Motion vectors: APPLICATION SPECIFIED (2x FLOAT) +Exposure: R32_FLOAT (1x1) +- **documentedOutputFormats**: +未单独枚举;文档称输出与原始输入同域 +- **formatBoundary**: +颜色输入必须 linear,除非设置 NON_LINEAR_COLORSPACE 标志;没有 DXGI 格式枚举,颜色格式由应用指定;MVs 2x float;深度 float;输出同输入域。 +- **channelOrderAndAlpha**:未说明。 +- **precisionAndRange**:linear HDR/scene-linear 输入,或 non-linear 标志;内部 ML 处理;精度未公开。 +- **transferFunction**:linear(推荐);非线性的可选标志。 +- **primariesAndColorSpace**:未说明。 +- **hdrSupport**:ExplicitHDR +- **hdrBoundary**:HDR 线性输入为推荐;输出恢复同输入域;需要 exposure/pre-exposure 管理;无 PQ/HLG 自动解码说明。 +- **requiredHdrMetadata**:pre-exposure;exposure R32_FLOAT;auto-exposure 可选 +- **candidateInternalProfile**:DirectFP16 +- **profileRationale**:官方 HDR linear 直接路径、无色彩空间自动处理,说明有直接浮点/HDR 路径。 +- **auxiliaryInputs**:depth;motion vectors;exposure;jitter;reset +- **auxiliaryInputFormatsAndSemantics**:同 FSR2/3 upscaler 协议;MVs 屏幕空间像素范围。 +- **temporalOrFrameGenerationConstraints**:时域 ML 超分需 jitter、reset、历史/帧时间;无公开帧生成。 +- **documentedQualityOptions**:FSR4 质量/性能档(未在格式文档中) +- **documentedPerformanceNotes**:未找到可核实的格式路径性能结论。 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/main/Kits/FidelityFX/docs/techniques/super-resolution-ml.md | 标题: AMD FSR Super Resolution 4 | 等级: OfficialDocs | claim: FSR4 input resources, HDR linear and non-linear colorspace flags | quoteOrSymbol: High dynamic range images are supported... linear color space | searchRound: 1 +- **searchRoundsUsed**:2 +- **unresolvedQuestions**:输出格式枚举;alpha;primaries;ML 模型输入张量格式 +- **conclusion**:FSR4 官方协议说明颜色输入为应用指定的 linear(或 non-linear 标志)HDR 可支持,辅助资源与 FSR2/3 一致;外部输出格式未公开。 + +### 14. FSRCNNX — FSRCNNX (基于 FSRCNN 的 mpv/着色器变体) + +- **mappingConfidence**:推测映射 +- **mappingNotes**:公开上可找到 FSRCNN-TensorFlow 项目与 mpv FSRCNNX 发布物,但“FSRCNNX”本身没有独立官方 SDK 文档。 +- **documentedInputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:https://raw.githubusercontent.com/igv/FSRCNN-TensorFlow/master/README.md + **notes**:FSRCNNX 无独立官方 SDK;基础 FSRCNN 仓库只说明 TensorFlow 模型,未给出 mpv shader 的纹理格式。 +- **documentedOutputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:https://raw.githubusercontent.com/igv/FSRCNN-TensorFlow/master/README.md + **notes**:同上;输出格式未枚举。 +- **formatBoundary**: +- **inputFormatEnumeration**:not_enumerated +- **outputFormatEnumeration**:not_enumerated +- **inputOutputRelation**:implementation_defined +- **alphaSemantics**:未说明。 +- **rangeBoundary**:unknown +- **transferBoundary**:unspecified +- **resourceConstraints**:社区 mpv 发布物/模型尺寸存在,但无统一资源状态或尺寸约束文档。 +- **hdrImplication**:无公开 HDR 格式契约。 +- **channelOrderAndAlpha**:无信息 +- **precisionAndRange**:无信息 +- **transferFunction**:无信息 +- **primariesAndColorSpace**:无信息 +- **hdrSupport**:无信息 +- **hdrBoundary**:未说明 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:无信息 +- **profileRationale**:无信息 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:无信息 +- **documentedQualityOptions**:不同 FSRCNNX 模型/尺寸由社区发布(无协议级信息) +- **documentedPerformanceNotes**:无信息 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://raw.githubusercontent.com/igv/FSRCNN-TensorFlow/master/README.md | 标题: FSRCNN-TensorFlow | 等级: GitHubExperiment | claim: 底层 FSRCNN TensorFlow 实现;README 提到增加 RGB 支持 | quoteOrSymbol: Add RGB support | searchRound: 1 +- **searchRoundsUsed**:5 +- **unresolvedQuestions**:FSRCNNX 独立仓库/头文件;输入输出格式;HDR/FP16 +- **conclusion**:FSRCNNX 只能推测映射到 FSRCNN 的着色器变体,公开协议资料不足,记录为无信息。 + +### 15. FXAA — FXAA (Fast Approximate Anti-Aliasing) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:算法名称明确对应 Lottes 的 FXAA;公开实现众多,但无单一官方 SDK 协议文档。 +- **documentedInputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:无官方统一来源 + **notes**:FXAA 算法无单一 SDK;多引擎实现把输入绑定为 2D 颜色纹理。 + - **format**:unspecified + **apiOrContext**:WebGPU/engine reference implementation (bevy FXAA) + **channelOrder**:RGBA + **numericRepresentation**:FLOAT (sampled as vec4) + **acceptanceStatus**:reference_implementation_only + **evidenceRef**:https://raw.githubusercontent.com/JMS55/bevy/236aa4e2fc93900b6ada7151b0d8e0567e6650f2/crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl + **notes**:社区/引擎实现使用 texture_2d 采样并输出 alpha 透传;不代表 FXAA 通用格式契约。 +- **documentedOutputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:无官方统一来源 + **notes**:FXAA 输出通常为同尺寸颜色纹理,但无统一格式枚举。 + - **format**:unspecified + **apiOrContext**:WebGPU/engine reference implementation (bevy FXAA) + **channelOrder**:RGBA + **numericRepresentation**:FLOAT + **acceptanceStatus**:reference_implementation_only + **evidenceRef**:https://raw.githubusercontent.com/JMS55/bevy/236aa4e2fc93900b6ada7151b0d8e0567e6650f2/crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl + **notes**:bevy 实现返回 vec4(finalColor, centerSample.a),alpha 透传;仅参考实现。 +- **formatBoundary**: +- **inputFormatEnumeration**:not_enumerated +- **outputFormatEnumeration**:not_enumerated +- **inputOutputRelation**:implementation_defined +- **alphaSemantics**:参考实现可透传 alpha;官方/原算法无统一声明。 +- **rangeBoundary**:unknown +- **transferBoundary**:unspecified +- **resourceConstraints**:shader 输入输出通常同尺寸;无统一 UAV/SRV 契约。 +- **hdrImplication**:无官方 HDR 格式契约;个别引擎实现可用浮点纹理,但不能代表算法协议。 +- **channelOrderAndAlpha**:无信息 +- **precisionAndRange**:无信息 +- **transferFunction**:无信息 +- **primariesAndColorSpace**:无信息 +- **hdrSupport**:无信息 +- **hdrBoundary**:未说明 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:无信息 +- **profileRationale**:没有可引用为统一协议的官方格式说明。 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:无信息 +- **documentedQualityOptions**:FXAA quality presets 由各实现定义,未在本调研确认 +- **documentedPerformanceNotes**:无信息 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://raw.githubusercontent.com/JMS55/bevy/236aa4e2fc93900b6ada7151b0d8e0567e6650f2/crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl | 标题: Bevy FXAA shader (NVIDIA FXAA 3.11 port) | 等级: GitHubExperiment | claim: bevy 实现以 texture_2d 读取颜色,输出 RGBA float 并透传 alpha | quoteOrSymbol: return vec4(finalColor, centerSample.a); | searchRound: 3 +- **searchRoundsUsed**:4 +- **unresolvedQuestions**:固定输入格式;HDR/FP16;色彩空间 +- **conclusion**:FXAA 可明确映射到 Lottes 的公开算法,但缺少可作为“外置效果层协议”的统一官方格式契约,记录为无信息。 + +### 16. MLAA — MLAA (Morphological Anti-Aliasing) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:MLAA 为公开论文/算法名称;无单一 SDK 官方协议。 +- **documentedInputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:无公开统一来源 + **notes**:MLAA 是论文/算法族,无单一官方 SDK 格式枚举。 +- **documentedOutputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:无公开统一来源 + **notes**:输出格式未枚举。 +- **formatBoundary**: +- **inputFormatEnumeration**:not_enumerated +- **outputFormatEnumeration**:not_enumerated +- **inputOutputRelation**:unknown +- **alphaSemantics**:未说明。 +- **rangeBoundary**:unknown +- **transferBoundary**:unspecified +- **resourceConstraints**:无统一资源约束可记录。 +- **hdrImplication**:无公开 HDR 格式契约。 +- **channelOrderAndAlpha**:无信息 +- **precisionAndRange**:无信息 +- **transferFunction**:无信息 +- **primariesAndColorSpace**:无信息 +- **hdrSupport**:无信息 +- **hdrBoundary**:未说明 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:无信息 +- **profileRationale**:无信息 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:无信息 +- **documentedQualityOptions**:无信息 +- **documentedPerformanceNotes**:无信息 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - 无来源(无信息条目;已尝试来源类型与轮数列于条目尾部) +- **searchRoundsUsed**:1 +- **unresolvedQuestions**:上游 SDK/实现;格式;HDR/FP16 +- **conclusion**:MLAA 可映射到公开形态抗锯齿算法,但未找到统一的图像 I/O 协议资料,记录为无信息。 + +### 17. NIS — NVIDIA Image Scaling SDK (NVScaler/NVSharpen) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:NVIDIA Image Scaling SDK 官方仓库与 Streamline 插件。 +- **documentedInputFormats**: +Input/output: non-integer data types, examples DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_NV12 (NV12 via NIS_NV12_SUPPORT) +HDR modes: LDR [0,1], HDR PQ [0,1], HDR Linear recommended [0,12.5] +- **documentedOutputFormats**: +Output UAV: non-integer formats, same docs examples (R8G8B8A8_UNORM/NV12); shader writes RWTexture2D +- **formatBoundary**: +输入为 SRV/read state,输出 UAV/write;sampler 必须 linear clamp;支持 viewport 子区域;NV12 需编译开关 NIS_NV12_SUPPORT;系数纹理 R32G32B32A32_FLOAT 或 R16G16B16A16_FLOAT。 +- **channelOrderAndAlpha**:颜色 RGBA/BGRA 由纹理格式决定;NV12 为 Y plane + interleaved UV;alpha 行为未公开。 +- **precisionAndRange**:LDR/PQ [0,1];HDR Linear [0,12.5] 约 1000nits;fp16/fp32 系数可选;不支持整数格式。 +- **transferFunction**:LDR: display-referred after OETF (sRGB/gamma); HDR PQ: Rec.2020 PQ OETF; HDR Linear: scene/display linear. +- **primariesAndColorSpace**:PQ 模式说明 Rec.2020 PQ;HDR Linear 按 BT.709 luma 参考;其余未说明。 +- **hdrSupport**:ExplicitHDR +- **hdrBoundary**:需通过 NIS_HDR_MODE 选 NONE/LINEAR/PQ;PQ 输入应为 display-referred Rec.2020 PQ;线性 HDR 建议 [0,12.5];没有额外 metadata 参数。 +- **requiredHdrMetadata**:NIS_HDR_MODE;无 MaxCLL/MaxFALL/pre-exposure 参数 +- **candidateInternalProfile**:DirectFP16 +- **profileRationale**:官方支持 HDR Linear/PQ 模式,系数可 FP16,且对 HDR 范围有明确文档,属于直接 HDR 路径。 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:空间算法,无时域/历史/帧生成约束。 +- **documentedQualityOptions**:NIS_HDR_MODE;NIS_NV12_SUPPORT;NIS_VIEWPORT_SUPPORT;NIS_CLAMP_OUTPUT +- **documentedPerformanceNotes**:README 未比较各格式成本。 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://raw.githubusercontent.com/NVIDIAGameWorks/NVIDIAImageScaling/main/README.md | 标题: NVIDIA Image Scaling SDK README | 等级: OfficialRepository | claim: Color spaces/ranges and supported texture formats | quoteOrSymbol: Input and output formats are expected... DXGI_FORMAT_R8G8B8A8_UNORM or DXGI_FORMAT_NV12 | searchRound: 1 + - URL: https://raw.githubusercontent.com/NVIDIA-RTX/Streamline/main/docs/ProgrammingGuideNIS.md | 标题: Streamline NIS Programming Guide | 等级: OfficialRepository | claim: NIS Streamline integration uses colorIn/colorOut tags and hdrMode option | quoteOrSymbol: nisOptions.hdrMode = NISHDR::eNISHDRNone | searchRound: 2 +- **searchRoundsUsed**:4 +- **unresolvedQuestions**:NV12 是否同时作为输出格式;alpha 具体行为;PQ 输入是否 10-bit/12-bit +- **conclusion**:NIS 官方协议明确支持 R8G8B8A8_UNORM 与 NV12 类非整数格式,支持 LDR/PQ/Linear 三种色彩范围,并有 HDR mode 开关。 + +### 18. NNEDI3 — NNEDI3 + +- **mappingConfidence**:明确映射 +- **mappingNotes**:NNEDI3 为公开神经网络倍线算法;Magpie 效果组对应实现可能是 bjin mpv-prescalers 中的 luma prescaler,但未依据 Magpie 源码确认。 +- **documentedInputFormats**: + - **format**:unspecified + **apiOrContext**:mpv user shader hook (luma plane) + **channelOrder**:luma + **numericRepresentation**:unspecified + **acceptanceStatus**:inferred_from_shader_interface + **evidenceRef**:https://raw.githubusercontent.com/bjin/mpv-prescalers/master/README.md + **notes**:bjin README 表示 nnedi3 只放大 YUV 的 luma plane;外部输入格式由 mpv 提供,未枚举。 + - **format**:rgba16f / rgba16hf + **apiOrContext**:mpv user shader internal intermediate + **channelOrder**:RGBA + **numericRepresentation**:FLOAT (16-bit) + **acceptanceStatus**:inferred_from_shader_interface + **evidenceRef**:https://raw.githubusercontent.com/bjin/mpv-prescalers/master/README.md + **notes**:README 关于 rgba16f/rgba16hf 的说明属于 mpv 用户着色器环境中的中间表面,不是 NNEDI3 算法 SDK 契约。 +- **documentedOutputFormats**: + - **format**:unspecified + **apiOrContext**:mpv user shader hook output + **channelOrder**:luma + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:https://raw.githubusercontent.com/bjin/mpv-prescalers/master/README.md + **notes**:输出为放大后的 luma 平面;无原生后端格式枚举。 + - **format**:rgba16f / rgba16hf + **apiOrContext**:mpv user shader internal intermediate + **channelOrder**:RGBA + **numericRepresentation**:FLOAT (16-bit) + **acceptanceStatus**:inferred_from_shader_interface + **evidenceRef**:https://raw.githubusercontent.com/bjin/mpv-prescalers/master/README.md + **notes**:同输入条目说明,仅中间表面。 +- **formatBoundary**: +- **inputFormatEnumeration**:partial +- **outputFormatEnumeration**:partial +- **inputOutputRelation**:implementation_defined +- **alphaSemantics**:未说明。 +- **rangeBoundary**:luma only / unknown +- **transferBoundary**:unspecified +- **resourceConstraints**:luma-only 放大;chroma 由宿主 --cscale 处理;mpv 老 d3d11 驱动需要 rgba16hf 分支。 +- **hdrImplication**:rgba16f 只证明 mpv user shader 中间表面,不构成 HDR API 契约。 +- **channelOrderAndAlpha**:README 表示 nnedi3/ravu 只放大 YUV 的 luma 平面;RGB 变体另有 ravu-rgb。 +- **precisionAndRange**:rgba16f / rgba16hf(fp16);来自 mpv user shader 内部格式。 +- **transferFunction**:未说明。 +- **primariesAndColorSpace**:未说明。 +- **hdrSupport**:FormatOnlyNoColorContract +- **hdrBoundary**:mpv user shader 内部处理浮点,但 README 未提供 HDR/PQ/linear 契约;不能作为厂商 HDR 保证。 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:DirectFP16 +- **profileRationale**:仅有的明确格式证据是 mpv prescaler 使用 rgba16f/rgba16hf 内部表面;这是用户着色器路径而非官方 SDK 契约。 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:无信息 +- **documentedQualityOptions**:nnedi3 的 neurons/window 在文件名中(bjin 仓库) +- **documentedPerformanceNotes**:无信息 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://raw.githubusercontent.com/bjin/mpv-prescalers/master/README.md | 标题: bjin/mpv-prescalers README | 等级: OfficialRepository | claim: Shaders use rgba16f internal format; nnedi3/ravu are luma prescalers | quoteOrSymbol: Unrecognized/unavailable FORMAT name: rgba16f | searchRound: 1 +- **searchRoundsUsed**:2 +- **unresolvedQuestions**:独立 NNEDI3 原生后端的格式契约;HDR 颜色空间 +- **conclusion**:NNEDI3 的 mpv 用户着色器资料显示内部使用 rgba16f/rgba16hf 并只处理 luma;没有可作为 Magpie 外置后端协议的官方格式列表。 + +### 19. Pixel Art — 无信息(Pixel Art 泛指像素画放大算法族) + +- **mappingConfidence**:无信息 +- **mappingNotes**:公开搜索出现 libretro pixel-art enhancement systems、xBRZ、Kopf-Lischinski 等多种算法,无法唯一确定 Magpie 对应后端。 +- **documentedInputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:公开搜索仅见多种像素画算法 + **notes**:无法唯一映射;不得把 xBRZ/Scale2x/Kopf-Lischinski 等某一算法格式写成该效果组已支持协议。 +- **documentedOutputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:公开搜索仅见多种像素画算法 + **notes**:同上。 +- **formatBoundary**: +- **inputFormatEnumeration**:not_enumerated +- **outputFormatEnumeration**:not_enumerated +- **inputOutputRelation**:unknown +- **alphaSemantics**:未说明。 +- **rangeBoundary**:unknown +- **transferBoundary**:unspecified +- **resourceConstraints**:无统一资源约束可记录。 +- **hdrImplication**:无公开 HDR 格式契约。 +- **channelOrderAndAlpha**:无信息 +- **precisionAndRange**:无信息 +- **transferFunction**:无信息 +- **primariesAndColorSpace**:无信息 +- **hdrSupport**:无信息 +- **hdrBoundary**:未说明 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:无信息 +- **profileRationale**:无信息 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:无信息 +- **documentedQualityOptions**:无信息 +- **documentedPerformanceNotes**:无信息 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - 无来源(无信息条目;已尝试来源类型与轮数列于条目尾部) +- **searchRoundsUsed**:1 +- **unresolvedQuestions**:具体像素画算法映射;格式;HDR/FP16 +- **conclusion**:Pixel Art 组无法唯一映射到公开算法,记录为无信息。 + +### 20. RAVU — RAVU (Rapid and Accurate Video Upscaling) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:公开仓库 bjin/mpv-prescalers 的 RAVU 系列。 +- **documentedInputFormats**: +mpv user-shader internal rgba16f/rgba16hf +ravu-yuv assumes YUV video after planes merged +ravu-rgb operates on RGB after planes merged +- **documentedOutputFormats**: +内部 rgba16f/rgba16hf 表面 +- **formatBoundary**: +作为 mpv user shader 使用;gather/compute 版本分别用 textureGather/compute;d3d11 老驱动可用 rgba16hf 分支;ravu/ravu-lite 仅放大 luma 平面;ravu-yuv 需 YUV,ravu-rgb 需 RGB;ravu-zoom 任意比例。 +- **channelOrderAndAlpha**:YUV 或 RGB 变体;未单独说明 alpha。 +- **precisionAndRange**:rgba16f/rgba16hf(16-bit float);未说明数值范围上限。 +- **transferFunction**:未说明。 +- **primariesAndColorSpace**:未说明。 +- **hdrSupport**:FormatOnlyNoColorContract +- **hdrBoundary**:内部 FP16 浮点表面不等于厂商 HDR/PQ 契约;README 未说明 HDR 线性/PQ。 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:DirectFP16 +- **profileRationale**:明确内部 rgba16f/rgba16hf,但无颜色/光域契约;只能作为格式级 FP16 证据。 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:无信息 +- **documentedQualityOptions**:ravu-lite;ravu;ravu-zoom;-ar anti-ringing 变体 +- **documentedPerformanceNotes**:README 说明 gather 通常较快、compute 对 yuv/rgb 更快,但无数值基准。 +- **qualityPerformanceEvidence**:README 有定性速度说明,无可核实格式路径性能数据。 +- **sources**: + - URL: https://raw.githubusercontent.com/bjin/mpv-prescalers/master/README.md | 标题: bjin/mpv-prescalers README | 等级: OfficialRepository | claim: RAVU variants and rgba16f/rgba16hf formats | quoteOrSymbol: rgba16f ... rgba16hf | searchRound: 1 +- **searchRoundsUsed**:2 +- **unresolvedQuestions**:外部原生后端的格式协议;HDR/色彩空间 +- **conclusion**:RAVU 明确为 mpv 用户着色器族,公开资料显示内部使用 rgba16f/rgba16hf 且分 YUV/RGB 变体;缺少可作为外置 SDK 的完整协议。 + +### 21. RTXVideo — NVIDIA RTX Video Super Resolution (VFX SDK VSR filter) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:RTX Video 效果组拆为 VSR、Denoiser、HDR;此条为 VFX SDK Video Super Resolution filter。 +- **documentedInputFormats**: +GPU buffers in BGRA or RGBA interleaved format, 8-bit unsigned per component +- **documentedOutputFormats**: +Same BGRA or RGBA interleaved U8 GPU buffer +- **formatBoundary**: +输入输出必须 GPU buffer,BGRA/RGBA interleaved,每分量 8-bit unsigned;Denoise/Deblur modes 输出分辨率必须与输入相同;VSR 支持不同 modes;建议最小 360p。 +- **channelOrderAndAlpha**:BGRA 或 RGBA;alpha 分量在 U8 格式中未单独描述行为。 +- **precisionAndRange**:8-bit unsigned integer per component;仅 SDR 级像素格式。 +- **transferFunction**:未在页面说明(SDR 视频输入通常 YUV->RGB 转换在外部)。 +- **primariesAndColorSpace**:未说明;相关 SDK 页面提到颜色不对时交换 709/601(YUV 转换)。 +- **hdrSupport**:SDROnly +- **hdrBoundary**:VFX VSR 页面只列 8-bit U8 BGRA/RGBA,无 HDR/PQ/FP16 路径。 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:SDRCompatible +- **profileRationale**:官方页面明确 U8 BGRA/RGBA 输入输出,没有 HDR/FP16。 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:VSR 是视频时域增强;无帧生成/运动向量/深度输入;Denoise/Deblur 同分辨率。 +- **documentedQualityOptions**:VSR_Bicubic;VSR_Low;VSR_Medium;VSR_High;VSR_Ultra;HighBitrate_*;Denoise_*;Deblur_* +- **documentedPerformanceNotes**:未找到可核实的格式路径性能结论。 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://docs.nvidia.com/maxine/vfx/1.2.0.0/Filters/VideoSuperResolution.html | 标题: NVIDIA VFX SDK Video Super Resolution | 等级: OfficialDocs | claim: VSR input/output BGRA or RGBA U8; modes | quoteOrSymbol: The input and output of the VSR filter are GPU buffers in BGRA or RGBA interleaved format...8-bit unsigned integer | searchRound: 1 +- **searchRoundsUsed**:3 +- **unresolvedQuestions**:VSR 是否接受 NV12/P010 内部路径;HDR 版本协议 +- **conclusion**:RTX Video VSR 的 VFX SDK 官方页面协议为 BGRA/RGBA 8-bit U8 GPU buffer;没有 FP16/HDR 输入输出契约。 + +### 22. RTXVideo — NVIDIA RTX Video Denoiser (VFX SDK VSR Denoise modes) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:VFX SDK VSR filter 内含 Denoise_Low/Medium/High/Ultra modes,作为 Denoiser 子条目。 +- **documentedInputFormats**: +BGRA or RGBA interleaved U8 GPU buffers +- **documentedOutputFormats**: +BGRA or RGBA interleaved U8 GPU buffers, same resolution as input +- **formatBoundary**: +Denoise modes 不支持 upscaling,输出分辨率必须等于输入;输入输出同为 BGRA/RGBA U8。 +- **channelOrderAndAlpha**:BGRA/RGBA。 +- **precisionAndRange**:8-bit unsigned per component。 +- **transferFunction**:未说明。 +- **primariesAndColorSpace**:未说明。 +- **hdrSupport**:SDROnly +- **hdrBoundary**:官方页面只有 U8 输入输出,无 HDR 路径。 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:SDRCompatible +- **profileRationale**:官方 VSR 页面把 Denoise modes 也限定为 BGRA/RGBA U8,无 HDR。 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:Denoise 模式不放大,输出同输入分辨率;无光流/深度输入。 +- **documentedQualityOptions**:Denoise_Low;Denoise_Medium;Denoise_High;Denoise_Ultra +- **documentedPerformanceNotes**:未找到可核实的格式路径性能结论。 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://docs.nvidia.com/maxine/vfx/1.2.0.0/Filters/VideoSuperResolution.html | 标题: NVIDIA VFX SDK Video Super Resolution (Denoise modes) | 等级: OfficialDocs | claim: Denoise modes same BGRA/RGBA U8 and same output resolution | quoteOrSymbol: Denoise_Low ... resolution of the output must be the same as input | searchRound: 1 +- **searchRoundsUsed**:2 +- **unresolvedQuestions**:独立 Denoiser 模型格式;HDR +- **conclusion**:RTX Video Denoiser 在官方 VFX 文档中与 VSR 共用 BGRA/RGBA U8、同分辨率输出协议,无 HDR/FP16。 + +### 23. RTXVideo — NVIDIA RTX Video HDR + +- **mappingConfidence**:明确映射 +- **mappingNotes**:NVIDIA 官方博客/产品页将 RTX Video HDR 描述为 RTX Video SDK 功能;未检索到完整公开 API 协议页。 +- **documentedInputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:https://developer.nvidia.com/blog/enhancing-low-resolution-sdr-video-with-the-nvidia-rtx-video-sdk/ + **notes**:官方博客只说 SDR 视频转 HDR10 质量,未给出输入像素格式/API 上下文。 +- **documentedOutputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:https://developer.nvidia.com/blog/enhancing-low-resolution-sdr-video-with-the-nvidia-rtx-video-sdk/ + **notes**:官方博客只说 HDR10 质量输出,未给出输出像素格式/API 上下文。 +- **formatBoundary**: +- **inputFormatEnumeration**:not_enumerated +- **outputFormatEnumeration**:not_enumerated +- **inputOutputRelation**:unknown +- **alphaSemantics**:未说明。 +- **rangeBoundary**:SDR input -> HDR10 output 的产品语义;无量值范围。 +- **transferBoundary**:未公开(博客层面提到 HDR10/PQ,但没有 API 转换契约)。 +- **resourceConstraints**:无 API 资源状态、尺寸或颜色转换文档。 +- **hdrImplication**:产品方向明确为 SDR->HDR,但无公开 HDR/FP16 格式契约。 +- **channelOrderAndAlpha**:无信息 +- **precisionAndRange**:无信息 +- **transferFunction**:官方博客/介绍提到 HDR10/PQ 场景;公开协议页未给出转换细节。 +- **primariesAndColorSpace**:博客提到从 sRGB 扩展到 scRGB/HDR 色域(不同页面措辞不一致);无正式协议。 +- **hdrSupport**:ExplicitHDR +- **hdrBoundary**:官方仅宣称 SDR->HDR,未公开输入/输出格式、metadata、tone mapping 或 pre-exposure 契约。 +- **requiredHdrMetadata**:未说明 +- **candidateInternalProfile**:无信息 +- **profileRationale**:没有可核实的输入输出协议来源。 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:无公开协议。 +- **documentedQualityOptions**:RTX Video HDR on/off 等产品级选项,不是 API 格式档位 +- **documentedPerformanceNotes**:未找到可核实的格式路径性能结论。 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://developer.nvidia.com/blog/enhancing-low-resolution-sdr-video-with-the-nvidia-rtx-video-sdk/ | 标题: NVIDIA Technical Blog: RTX Video SDK | 等级: OfficialForum | claim: RTX Video HDR converts SDR video to HDR10 quality; VSR upscales | quoteOrSymbol: RTX Video HDR converts SDR video to HDR10 quality | searchRound: 1 +- **searchRoundsUsed**:5 +- **unresolvedQuestions**:API 输入输出格式;NvCVImage pixel format;PQ metadata;scRGB/HDR10 工作空间 +- **conclusion**:RTX Video HDR 有明确官方产品宣称但缺少公开 API 格式协议,记录为无格式信息、仅 HDR 方向明确。 + +### 24. Sharpen — 无信息(Sharpen 为通用锐化效果族) + +- **mappingConfidence**:无信息 +- **mappingNotes**:公开搜索出现 NIS NVSharpen、Android SharpenFilter 等多个互不相同的后端,无法唯一对应。 +- **documentedInputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:公开搜索出现多个锐化实现 + **notes**:无法唯一映射;不得把任一锐化实现的格式写成该效果组已支持协议。 +- **documentedOutputFormats**: + - **format**:unspecified + **apiOrContext**:公开资料未枚举 + **channelOrder**:unspecified + **numericRepresentation**:unspecified + **acceptanceStatus**:not_enumerated + **evidenceRef**:公开搜索出现多个锐化实现 + **notes**:同上。 +- **formatBoundary**: +- **inputFormatEnumeration**:not_enumerated +- **outputFormatEnumeration**:not_enumerated +- **inputOutputRelation**:unknown +- **alphaSemantics**:未说明。 +- **rangeBoundary**:unknown +- **transferBoundary**:unspecified +- **resourceConstraints**:无统一资源约束可记录。 +- **hdrImplication**:无公开 HDR 格式契约。 +- **channelOrderAndAlpha**:无信息 +- **precisionAndRange**:无信息 +- **transferFunction**:无信息 +- **primariesAndColorSpace**:无信息 +- **hdrSupport**:无信息 +- **hdrBoundary**:未说明 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:无信息 +- **profileRationale**:无信息 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:无信息 +- **documentedQualityOptions**:无信息 +- **documentedPerformanceNotes**:无信息 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - 无来源(无信息条目;已尝试来源类型与轮数列于条目尾部) +- **searchRoundsUsed**:1 +- **unresolvedQuestions**:具体锐化算法映射;格式;HDR/FP16 +- **conclusion**:Sharpen 效果组无法唯一映射到上游算法,记录为无信息。 + +### 25. SMAA — SMAA (Subpixel Morphological Antialiasing) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:官方 iryoku/smaa 仓库。 +- **documentedInputFormats**: +colorTex: RGBA texture (2D color/luma input) +edgesTex/areaTex/searchTex 等内部/预计算纹理 +depthTex for depth edge detection +- **documentedOutputFormats**: +colorTex/blended output 4-channel color buffer +- **formatBoundary**: +官方集成说明要求两个 RGBA 时域 render target;创建后清除 alpha;所有 sampler linear + clamp;纹理读写默认非 sRGB,只有最终 NeighborhoodBlending 的 input/output 可 sRGB;64-bit 输入在 GCN 上有半速率线性过滤。 +- **channelOrderAndAlpha**:RGBA;alpha 通道在中间步骤被用作速度/索引等;文档警告清除 alpha。 +- **precisionAndRange**:float4/float 通用;提到 64-bit 输入和 half-rate filtering,未给出 8/10/16 位清单。 +- **transferFunction**:建议在 gamma-corrected(非 sRGB 采样)空间做 luma/color edge detection;最终 blending pass 可处理 sRGB 读写。 +- **primariesAndColorSpace**:未说明。 +- **hdrSupport**:Unspecified +- **hdrBoundary**:官方代码未声称 HDR;luma edge detection 注释要求 gamma-corrected colors,通常为 LDR 空间。 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:SDRCompatible +- **profileRationale**:官方集成注释围绕 gamma/sRGB 空间和 RGBA 临时目标,未见 HDR/线性 HDR 直接路径;保守记为 SDR 兼容。 +- **auxiliaryInputs**:velocityTex (temporal variant);depthTex (depth edge detection);areaTex/searchTex lookup textures;predicationTex optional +- **auxiliaryInputFormatsAndSemantics**:area/search 为预计算 LUT;velocity 解码方式由 SMAA_DECODE_VELOCITY 定义;depth 可为深度纹理。 +- **temporalOrFrameGenerationConstraints**:SMAA 1x 无时域;temporal/supersampling 变体需要多子样本、previous/current color 和 velocity。 +- **documentedQualityOptions**:SMAA 1x/2x/T2x 等宏/预设由官方源码定义 +- **documentedPerformanceNotes**:官方注释提示 64-bit 输入半速率线性过滤,可用 point filtering 规避;未给完整性能基准。 +- **qualityPerformanceEvidence**:官方注释有格式相关性能提示,但没有格式路径性能基准。 +- **sources**: + - URL: https://raw.githubusercontent.com/iryoku/smaa/master/README.md | 标题: SMAA README | 等级: OfficialRepository | claim: SMAA official repo and integration pointer | quoteOrSymbol: Checkout the technical paper | searchRound: 1 + - URL: https://raw.githubusercontent.com/iryoku/smaa/master/SMAA.hlsl | 标题: SMAA.hlsl | 等级: OfficialRepository | claim: Integration notes: RGBA render targets, sRGB rules, filtering | quoteOrSymbol: All texture reads and buffer writes must be non-sRGB... | searchRound: 2 +- **searchRoundsUsed**:4 +- **unresolvedQuestions**:具体 DXGI 格式支持;HDR 线性输入;alpha 对外值 +- **conclusion**:SMAA 官方仓库提供了着色器级协议:RGBA 输入输出、RGBA 临时目标、gamma/sRGB 空间建议;未公开现代 HDR/FP16 API 契约。 + +### 26. xBRZ — xBRZ (pixel-art scaling algorithm) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:xBRZ 原算法由 Zenju 发布;本条目证据来自 TypeScript/WASM 移植 kayahr/xbrz。 +- **documentedInputFormats**: +RGBA pixel data in Uint8ClampedArray (TypeScript port) +- **documentedOutputFormats**: +RGBA pixel data in Uint8ClampedArray +- **formatBoundary**: +TypeScript 移植接口为源/目标 RGBA 像素缓冲;缩放因子 2-6;支持 alpha 透明;非原生 SDK 协议。 +- **channelOrderAndAlpha**:RGBA;支持 alpha 透明。 +- **precisionAndRange**:8-bit per channel in Uint8ClampedArray([0,255]);无 HDR/浮点。 +- **transferFunction**:未说明。 +- **primariesAndColorSpace**:未说明。 +- **hdrSupport**:SDROnly +- **hdrBoundary**:移植接口是 8-bit RGBA,无 HDR。 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:SDRCompatible +- **profileRationale**:证据显示 RGBA 8-bit 像素缓冲;无浮点/HDR 接口。 +- **auxiliaryInputs**:无信息 +- **auxiliaryInputFormatsAndSemantics**:无信息 +- **temporalOrFrameGenerationConstraints**:无信息 +- **documentedQualityOptions**:scale factors 2-6 +- **documentedPerformanceNotes**:未找到可核实的格式路径性能结论。 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://raw.githubusercontent.com/kayahr/xbrz/master/README.md | 标题: kayahr/xbrz TypeScript/WASM port | 等级: GitHubExperiment | claim: Port uses RGBA Uint8ClampedArray and supports alpha | quoteOrSymbol: Source and target are RGBA pixel data in a Uint8ClampedArray | searchRound: 1 +- **searchRoundsUsed**:3 +- **unresolvedQuestions**:Magpie 原生后端是否使用该接口;原 C++ xBRZ 的格式契约;HDR +- **conclusion**:xBRZ 映射明确,但可靠公开协议只有社区移植的 RGBA 8-bit 接口,原生后端协议未确认。 + +### 27. XeSS — Intel XeSS Super Resolution (XeSS-SR) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:Intel xess 官方仓库与 Developer Guide。 +- **documentedInputFormats**: +R16G16B16A16_FLOAT +R11G11B10_FLOAT +R8G8B8A8_UNORM +其他 any linear color format; only UNORM integer color formats allowed +Motion vectors: R16G16_FLOAT +Depth: any depth format such as D32_FLOAT or D24_UNORM +- **documentedOutputFormats**: +Same format and color space as input (2D output texture) +- **formatBoundary**: +输入颜色可为 LDR/HDR 任意 linear 格式;整数格式只允许 UNORM;输出必须与输入同格式同色彩空间,且输出 alpha 不保留并填 1.0;D3D12 输入 NON_PIXEL_SHADER_RESOURCE,输出 UAV;Vulkan 对应状态。 +- **channelOrderAndAlpha**:RGBA/R11G11B10 等格式;XeSS-SR 不保留输出 alpha,填 1.0。 +- **precisionAndRange**:颜色为 scene-referred scRGB,1.0 为 80 nits SDR 白点,HDR 可 >1;LDR 输入需 XESS_INIT_FLAG_LDR_INPUT_COLOR;exposureScale 等。 +- **transferFunction**:任何 linear 色彩格式(LDR/HDR);非 sRGB/PQ 编码直接输入。 +- **primariesAndColorSpace**:scRGB(scene-referred);输出同输入色彩空间。 +- **hdrSupport**:ExplicitHDR +- **hdrBoundary**:推荐直接提供 HDR 线性/scRGB;可设置 exposureScale/pre-exposure/exposureMultiplier;算法内部自行 tonemap,输出恢复;LDR 也可。 +- **requiredHdrMetadata**:exposureScale;preExposure/exposureMultiplier 可选;无 MaxCLL/MaxFALL 要求 +- **candidateInternalProfile**:DirectFP16 +- **profileRationale**:官方明确支持 FP16/RGBA16F、scRGB HDR 线性并推荐 HDR 输入。 +- **auxiliaryInputs**:motion vectors;depth (low-res MV mode);responsive pixel mask (optional);exposure scale/multiplier;jitter +- **auxiliaryInputFormatsAndSemantics**:MVs R16G16_FLOAT screen-space pixels, low-res default or high-res dilated; depth D32_FLOAT/D24_UNORM; responsive mask R channel float [0,1]; input states as above. +- **temporalOrFrameGenerationConstraints**:时域超分:需 jittered color, motion vectors, depth when low-res; output target resolution; fixed/dynamic input resolution ranges; reset history on cuts. +- **documentedQualityOptions**:Ultra Quality/Quality/Balanced/Performance presets (via optimal input resolution API) +- **documentedPerformanceNotes**:开发指南建议 HDR input color 推荐、使用 FP16 color buffer in scene linear HDR(性能提示);无格式路径基准。 +- **qualityPerformanceEvidence**:指南有使用 FP16 scene-linear HDR 的建议,但无定量比较。 +- **sources**: + - URL: https://raw.githubusercontent.com/intel/xess/main/doc/xess_sr_developer_guide_english.md | 标题: Intel XeSS-SR Developer Guide | 等级: OfficialRepository | claim: Formats and color space: R16G16B16A16_FLOAT/R11G11B10_FLOAT/R8G8B8A8_UNORM, scRGB, output same | quoteOrSymbol: XeSS-SR accepts both LDR and HDR input colors in any linear color format | searchRound: 1 + - URL: https://raw.githubusercontent.com/intel/xess/main/README.md | 标题: Intel xess README | 等级: OfficialRepository | claim: XeSS-SR and XeSS-FG/XeLL availability | quoteOrSymbol: XeSS Super Resolution (XeSS-SR) | searchRound: 2 +- **searchRoundsUsed**:5 +- **unresolvedQuestions**:每种 DXGI/Vulkan 格式完整矩阵;PQ/HLG 是否需外部转换;primaries 非 scRGB +- **conclusion**:XeSS-SR 官方协议非常明确:支持 R16G16B16A16_FLOAT/R11G11B10_FLOAT/R8G8B8A8_UNORM 等线性格式,scRGB scene-referred,输出同输入格式且 alpha 填 1。 + +### 28. XeSSFG — Intel XeSS Frame Generation (XeSS-FG) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:Intel xess 官方仓库 XeSS-FG Developer Guide。 +- **documentedInputFormats**: +Back buffer/HUD-less/UI-only: R10G10B10A2_UNORM for HDR10/BT.2100 HDR display +Motion vectors: R16G16_FLOAT or similar +Depth: any depth format such as D32_FLOAT or D24_UNORM +UI Alpha: single channel; UI Color and Alpha: same backbuffer format +- **documentedOutputFormats**: +Interpolated frames output to proxy swap chain; same back buffer pixel format (HDR10 R10G10B10A2_UNORM documented) +- **formatBoundary**: +HDR display 支持 R10G10B10A2_UNORM + HDR10/BT.2100;明确不支持 FP16 HDR/scRGB;back buffer, HUD-less, UI-only 必须同像素格式、色彩空间、尺寸;MV 与 depth buffer 尺寸一致。 +- **channelOrderAndAlpha**:UI-only texture 有 alpha;非预乘 alpha 可用标志;合成公式 Final.RGB = UIonly.RGB + (1-UIonly.Alpha) * HUDlessColor.RGB;2-bit alpha 对 UI 合成不足。 +- **precisionAndRange**:HDR 显示为 10-bit R10G10B10A2_UNORM HDR10/BT.2100;不支持 FP16/scRGB;LDR 显示用 swapchain 格式。 +- **transferFunction**:HDR10 / BT.2100(PQ)用于 HDR 显示。 +- **primariesAndColorSpace**:HDR10 / BT.2100;非 HDR 未细说。 +- **hdrSupport**:ExplicitHDR +- **hdrBoundary**:HDR 必须使用 R10G10B10A2_UNORM 与 HDR10/BT.2100,back buffer/HUDless/UI 同格式;不支持 FP16/scRGB;未给出 PQ 解码细节。 +- **requiredHdrMetadata**:HDR display pixel format R10G10B10A2_UNORM;HDR10/BT.2100 color space;无 MaxCLL/MaxFALL 公开参数 +- **candidateInternalProfile**:BoundedHDR +- **profileRationale**:官方明确 HDR 只走 10-bit R10G10B10A2 UNORM/HDR10 BT.2100,不接收 FP16/scRGB,故为有界 HDR 契约。 +- **auxiliaryInputs**:motion vectors;depth;HUD-less color;UI-only texture;jitter offset;resetHistory;motion vector scale +- **auxiliaryInputFormatsAndSemantics**:MV R16G16_FLOAT/similar low/high res; depth any format, same size as MV; HUDless/UI same as backbuffer format/color; UI alpha single channel. +- **temporalOrFrameGenerationConstraints**:需要 XeLL latency reduction 启用;不能独占全屏;40 FPS 最低/60 FPS 推荐;禁用/减少 motion blur;切换或与第三方帧生成不兼容;UI composition modes. +- **documentedQualityOptions**:UI composition modes;interpolated frames count 等 XeSS-FG 公开选项 +- **documentedPerformanceNotes**:官方建议 low-res MVs 以免 high-res 大幅降低性能;无格式路径定量表。 +- **qualityPerformanceEvidence**:官方说明 high-res MV 性能影响,但无格式路径性能基准。 +- **sources**: + - URL: https://raw.githubusercontent.com/intel/xess/main/doc/xess_fg_developer_guide_english.md | 标题: Intel XeSS-FG Developer Guide | 等级: OfficialRepository | claim: HDR display support R10G10B10A2_UNORM HDR10/BT.2100; no FP16/scRGB | quoteOrSymbol: XeSS-FG provides support for HDR displays with the R10G10B10A2_UNORM pixel format... does not support FP16 HDR format and scRGB color space | searchRound: 1 + - URL: https://raw.githubusercontent.com/intel/xess/main/README.md | 标题: Intel xess README | 等级: OfficialRepository | claim: XeSS-FG available on Intel Arc and non-Intel with SM6.4 | quoteOrSymbol: XeSS Frame Generation (XeSS-FG) | searchRound: 2 +- **searchRoundsUsed**:4 +- **unresolvedQuestions**:LDR 下的完整 swapchain 格式清单;PQ metadata 传递 +- **conclusion**:XeSS-FG 官方明确 HDR 契约:R10G10B10A2_UNORM/HDR10 BT.2100,不支持 FP16/scRGB;所有参与合成的纹理必须同格式同色彩空间。 + +### 29. NVIDIA Optical Flow — NVIDIA Optical Flow SDK (NvOF) + +- **mappingConfidence**:明确映射 +- **mappingNotes**:附加依赖项对应 NVIDIA Optical Flow SDK NvOF API(另有 NVOFA 新 API,本次主条目为 NvOF 头文件证据)。 +- **documentedInputFormats**: +NV_OF_BUFFER_FORMAT_GRAYSCALE8 +NV_OF_BUFFER_FORMAT_NV12 +NV_OF_BUFFER_FORMAT_ABGR8 (A8B8G8R8) +- **documentedOutputFormats**: +NV_OF_FLOW_VECTOR (SHORT2, S10.5 flowx/flowy) +NV_OF_STEREO_DISPARITY (for stereo mode) +Cost buffer NV_OF_BUFFER_FORMAT_UINT or UINT8 +- **formatBoundary**: +输入帧支持 GRAYSCALE8/NV12/ABGR8;输出/提示为 SHORT2(S10.5);外部提示/成本格式另有要求;支持 output grid size 1/2/4;有宽高 min/max caps。 +- **channelOrderAndAlpha**:ABGR8 为 8-bit packed A8B8G8R8;NV12 为 Y planar + interleaved UV。 +- **precisionAndRange**:8-bit input; output flow vector int16 S10.5 表示亚像素;无 HDR/PQ 声明。 +- **transferFunction**:未说明。 +- **primariesAndColorSpace**:未说明。 +- **hdrSupport**:SDROnly +- **hdrBoundary**:官方头文件输入格式只列 8-bit 亮度/NV12/ABGR8,无 FP16/HDR。 +- **requiredHdrMetadata**:无信息 +- **candidateInternalProfile**:SDRCompatible +- **profileRationale**:NvOF 头文件列出的输入只有 8-bit 格式,输出为定点光流。 +- **auxiliaryInputs**:externalHints (optional);cost buffer (optional);reference frame +- **auxiliaryInputFormatsAndSemantics**:externalHints/outputBuffer use NV_OF_FLOW_VECTOR for optical flow; cost UINT or UINT8; reference frame same input format as inputFrame. +- **temporalOrFrameGenerationConstraints**:光流在两帧(input/reference)间估计;output grid size 决定流向量网格;scene change/hints 等。 +- **documentedQualityOptions**:NV_OF_OUTPUT_VECTOR_GRID_SIZE 1/2/4 +- **documentedPerformanceNotes**:头文件注明 legacy 32-bit cost 不高效,建议 8-bit cost;无格式路径完整基准。 +- **qualityPerformanceEvidence**:官方头文件有成本格式性能建议,但无定量表。 +- **sources**: + - URL: https://raw.githubusercontent.com/NVIDIA/NVIDIAOpticalFlowSDK/master/nvOpticalFlowCommon.h | 标题: NVIDIA Optical Flow SDK nvOpticalFlowCommon.h | 等级: OfficialHeader | claim: Buffer formats: GRAYSCALE8, NV12, ABGR8; flow vector SHORT2 | quoteOrSymbol: NV_OF_BUFFER_FORMAT_NV12 ... NV_OF_BUFFER_FORMAT_ABGR8 | searchRound: 1 +- **searchRoundsUsed**:4 +- **unresolvedQuestions**:NVOFA 新 API 的 RGBA/F16 支持;HDR 帧是否需转为 NV12/ABGR8 +- **conclusion**:NVIDIA Optical Flow NvOF 官方头文件支持 8-bit GRAYSCALE/NV12/ABGR8 输入,输出为 S10.5 定点 SHORT2 光流;未提供 HDR/FP16 输入路径。 + +### 30. AMD FidelityFX Optical Flow — FidelityFX Optical Flow + +- **mappingConfidence**:明确映射 +- **mappingNotes**:AMD FidelityFX SDK 中用于 FSR3 的光流技术,官方 docs/optical-flow.md。 +- **documentedInputFormats**: +color input resource (格式未在文档枚举) +- **documentedOutputFormats**: +opticalFlowVector: R16G16_SINT +opticalFlowSCD: R32_UINT (3x1 scene change detection) +- **formatBoundary**: +以 8x8 block 计算,输出纹理尺寸由 (displaySize+block-1)/8 决定;block size 固定 8;color 输入经 transfer function/luminance 转换。 +- **channelOrderAndAlpha**:未说明 color 通道顺序;算法使用亮度。 +- **precisionAndRange**:输出 R16G16_SINT;SCD R32_UINT;输入颜色格式未枚举。 +- **transferFunction**:输入有 backbufferTransferFunction 与 minMaxLuminance,用于 HDR 管道转换到 luminance。 +- **primariesAndColorSpace**:未说明。 +- **hdrSupport**:ExplicitHDR +- **hdrBoundary**:支持 HDR pipeline 的 luminance 转换:需 backbufferTransferFunction 和 minMaxLuminance;未公开颜色输入格式。 +- **requiredHdrMetadata**:backbufferTransferFunction;minMaxLuminance +- **candidateInternalProfile**:BoundedHDR +- **profileRationale**:官方要求把 HDR 颜色经 transfer/luminance 转成内部亮度,适合有界转换路径。 +- **auxiliaryInputs**:previous/current color input;internal history resources for pyramid/histogram +- **auxiliaryInputFormatsAndSemantics**:color input is current frame; internal history resources; SCD output 3x1 R32_UINT. +- **temporalOrFrameGenerationConstraints**:需要连续帧、内部金字塔、历史 histogram 做场景变化检测;用于 FSR3 帧插值。 +- **documentedQualityOptions**:8x8 block size fixed in release +- **documentedPerformanceNotes**:文档内存表按 GPU/4K 显示,但未比较不同格式成本。 +- **qualityPerformanceEvidence**:未找到可核实的格式路径性能结论 +- **sources**: + - URL: https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/release-FSR3-3.0.3/docs/techniques/optical-flow.md | 标题: FidelityFX Optical Flow | 等级: OfficialDocs | claim: Optical flow outputs R16G16_SINT and R32_UINT; HDR transfer/luminance | quoteOrSymbol: opticalFlowVector ... R16G16_SINT ... R32_UINT | searchRound: 1 +- **searchRoundsUsed**:3 +- **unresolvedQuestions**:输入 color 的格式枚举;输出语义坐标空间精确单位;独立于 FSR3 的 SDK +- **conclusion**:AMD FidelityFX Optical Flow 官方协议公开了光流输出 R16G16_SINT、SCD R32_UINT 和 HDR transfer/luminance 输入需求,但输入颜色格式未枚举。 + +## 无信息条目汇总 + +| 效果组 | 已尝试来源类型 | 搜索轮数 | 仍缺失字段 | +|---|---|---|---| +| CRT | 网页搜索 | 1 | 上游映射;输入格式;色彩空间;HDR/FP16 | +| CuNNy2 | 网页搜索 | 2 | CuNNy2 上游映射;格式;HDR/FP16;alpha | +| Diagnostics | 网页搜索 | 1 | 上游映射;I/O 格式;HDR/FP16 | +| FXAA | 网页搜索 | 4 | 固定输入格式;HDR/FP16;色彩空间 | +| MLAA | 网页搜索 | 1 | 上游 SDK/实现;格式;HDR/FP16 | +| Pixel Art | 网页搜索 | 1 | 具体像素画算法映射;格式;HDR/FP16 | +| Sharpen | 网页搜索 | 1 | 具体锐化算法映射;格式;HDR/FP16 | + +## 证据边界说明 + +- 官方契约:上表中 `OfficialDocs/OfficialHeader/OfficialRepository` 并带有明确引文的内容。 +- 官方样例行为:`OfficialRepository` 中 CasCmdLine 等参考样例的格式行为只作为参考实现记录,不扩张为 API 保证。 +- GitHub 实验结论:标记为 `GitHubExperiment` 的条目只代表社区/实验证据,不代表厂商保证。 +- 用户着色器内部 `rgba16f/rgba16hf` 只记录为中间表面证据。 +- 当官方资料与实验资料冲突时,本文件分别保留主张,未做强行合并。 + +## 覆盖与排除验证 + +- JSON 共 30 个对象,覆盖效果组:AMD FidelityFX Optical Flow、Anime4K、CAS、CRT、CuNNy、CuNNy2、DLSS、DLSSFG、Diagnostics、FSR、FSR2、FSR3、FSR4、FSRCNNX、FXAA、MLAA、NIS、NNEDI3、NVIDIA Optical Flow、Pixel Art、RAVU、RTXVideo、SMAA、Sharpen、XeSS、XeSSFG、xBRZ。 +- 不存在该排除效果条目。 +- 所有条目 searchRoundsUsed 均不超过 10。 diff --git a/docs/experimental/agent-a-report.md b/docs/experimental/agent-a-report.md new file mode 100644 index 000000000..0f36bd02b --- /dev/null +++ b/docs/experimental/agent-a-report.md @@ -0,0 +1,81 @@ +# Effect protocol audit — group A + +审计范围:Anime4K、CAS、CRT、CuNNy、CuNNy2、Diagnostics、FSRCNNX、FXAA、MLAA。结论同时记录公开证据矩阵与当前 Magpie shader 的可观察事实;shader 中没有声明的输入/输出格式保持 `Unknown`。`//!CAPABILITY FP16` 仅代表编译/数学能力,HDR 传输协议仍需独立证据。 + +## 总结 + +| 效果 | 实际 shader 输入/输出 | 明确的辅助纹理 | 数值/transfer/alpha 事实 | HDR profile | 代码缺口 | +|---|---|---|---|---|---| +| Anime4K | `INPUT`/`OUTPUT` 未声明格式;大多数 CNN 变体中间面 `R16G16B16A16_FLOAT`;`Thin_HQ` 的梯度面 `R16G16_FLOAT` | CNN 变体 2–16 个 FP16 中间面;无外部纹理 | CNN 以正负分支 `max(x,0)`/`max(-x,0)` 工作;大量最终写入 `MF4(...,1)`,alpha 固定 1;Denoise 仅 RGB 并固定 alpha 1;Thin_HQ 直接采样 RGBA 并保留 alpha | `Unknown`,generic SDR fallback | 缺少统一输入/输出格式、transfer、原色域、范围和 alpha 契约;当前路由应保持 generic SDR fallback | +| CAS | `INPUT`/`OUTPUT` 未声明格式;`CAS.hlsl` 有 `//!CAPABILITY FP16`,`CAS_Scaling.hlsl` 无 | 无 | 读取 `.rgb`;`saturate` 作用于邻域振幅和最终 RGB;所有输出写入 alpha 1;shader 没有 transfer 解码/编码 | `SDRCompatible`(现有证据允许 FP16 参考实现,当前 Magpie shader 仍是有界 RGB 路径) | 需补充具体 Magpie 资源格式、线性/伽马入口、峰值范围和 alpha 规则;当前 FP16 capability 不足以建立 HDR-native 路径 | +| CRT | 除 `GTU_v050.tex1` 明确为 `R16G16B16A16_FLOAT` 外,`INPUT`/`OUTPUT` 未声明格式 | GTU `tex1` FP16;其余无 | Easymode/Geom/Hyllian/Lottes 使用 gamma `pow`;GTU RGB 多处 `clamp(...,0,1)`,最终 alpha 1;Hyllian 输出 RGB clamp 到 `[0,1]` 后 gamma 输出 | `Unknown`,generic SDR fallback | 每个 preset 都是独立协议;gamma 参数、clamp、mask/scanline 对 HDR 的行为尚未建立;GTU 仅有内部 FP16 证据 | +| CuNNy | `INPUT`/`OUTPUT` 未声明格式,2x–8x 输出尺寸为 `INPUT_WIDTH/HEIGHT * 2` | 所有 `t0..t7` 中间面明确 `R8G8B8A8_SNORM` | RGB 转 YUV/网络张量;ReLU 正负分支;NVL 输出对 Y 分量使用 `saturate`,写回 RGB 后 alpha 固定 1 | `Unknown`,generic SDR fallback | 现有矩阵把格式记为 unspecified;实际内部 SNORM 是可靠本地事实,但模型归一化区间、输入格式、transfer、alpha 尚未形成 HDR 契约 | +| CuNNy2 | `INPUT`/`OUTPUT` 未声明格式,全部输出尺寸为 2x | 所有 `T0..T15` 明确 `R8G8B8A8_UNORM` | RGB→YUV,网络层使用 `max(x,0)`;out-shuffle 对 Y 使用 `saturate`;输出 alpha 固定 1 | `Unknown`,generic SDR fallback | 公开矩阵没有独立 CuNNy2 上游;本地 shader 明确 UNORM 中间面和 `[0,1]` 型输出,仍缺模型归一化/transfer/峰值证据 | +| Diagnostics | `INPUT`/`OUTPUT` 未声明格式,同尺寸 | 无 | Confidence 直接 `return INPUT.SampleLevel(...)`;Motion 同样直接采样返回;shader 层 alpha 保留输入值;显示增益参数仅在对应 native/backend 侧生效 | `Unknown`,generic SDR fallback | 诊断输入的真实来源(MV/置信度资源格式、范围)由 native backend 决定;通用 shader 的 HDR 图像语义仍待定义 | +| FSRCNNX | `INPUT`/`OUTPUT` 未声明格式,输出尺寸 2x;中间面明确 `R16G16B16A16_FLOAT` | `featureMap1/2`、`tex1..tex4` 均 FP16 | 先以 `0.299/0.587/0.114` 取 luma;多层 leaky-ReLU 使用 `max(x,0)+C*min(x,0)`;最终 RGB 加回输入 RGB,alpha 固定 1 | `Unknown`,generic SDR fallback | FP16 中间面没有给出输入/输出色彩协议;luma 权重、训练归一化、负值/超 1 值范围、alpha 恢复均待验证 | +| FXAA | `INPUT`/`OUTPUT` 未声明格式,同尺寸;Linear sampler | 无 | FXAA luma 估计 `rgb.y*(0.587/0.299)+rgb.x`,注释范围约 `0..2.9632`;最终 RGB 经邻域插值,wrapper 写 alpha 1;无 transfer 处理 | `Unknown`,generic SDR fallback | 当前实现缺统一格式、范围、transfer、alpha 透传契约;阈值是显示相关量,直接场景线性 HDR 需验证 | +| MLAA | `INPUT`/`OUTPUT` 未声明格式,同尺寸;`edgeMask`=`R8G8_UNORM`;`edgeCounts`=`R8G8B8A8_UNORM` | `edgeMask`、`edgeCounts` | luma 为 `dot(rgb,[0.2126,0.7152,0.0722])`;edge mask 二值写入 UNORM;edge count 量化为 `0..15 / 15`;混合在平方域后 `sqrt`;最终返回原 `color`,alpha 保留 | `Unknown`,generic SDR fallback | 辅助面是明确 SDR/UNORM 边界;主面格式、transfer、HDR 阈值和 alpha 语义缺少公共契约 | + +## 逐项证据 + +### Anime4K + +实际文件集合为 `src/Effects/Anime4K/*.hlsl`。`Anime4K_Restore_*`、`Anime4K_Upscale_*`、`Anime4K_Upscale_Denoise_*`、`Anime4K_3D_*`、`Anime4K_Upscale_GAN_*` 均带 `//!CAPABILITY FP16`,并将网络中间面声明为 `R16G16B16A16_FLOAT`;典型声明位于 `Anime4K_Restore_L.hlsl:20-41`。`Anime4K_Thin_HQ.hlsl` 的梯度纹理为 `R16G16_FLOAT`,主输入输出仍未声明格式。多数网络最终写入 `MF4(result, 1)` 或 `MF4(result + INPUT.rgb, 1)`,例如 `Anime4K_Restore_L.hlsl:690`、`Anime4K_Upscale_L.hlsl:647-659`,因此 alpha 由 shader 固定为 1。三个 bilateral denoise 变体只采集 RGB,最终 `float4(...,1)`,证据见 `Anime4K_Denoise_Bilateral_Mean.hlsl:68-83,121`;`Thin_HQ` 的 warp pass 直接写入 `INPUT.SampleLevel`,这是该变体保留 alpha 的局部事实。数值上,CNN 激活显式拆分正负分量,`max(x,0)` 与 `max(-x,0)` 让网络内部允许负特征,同时没有输入 transfer、峰值或 scene-linear 标注。公开证据矩阵 `HDR_PROTOCOL_EVIDENCE.md:34,67-116` 仅证明上游是 Anime4K 用户 shader,并把公共 I/O、范围、色彩空间、HDR 记为缺失。当前应保持 `Unknown`,由通用 SDR fallback 进入效果边界;FP16 中间面可作为资源分配事实记录,现有证据不足以升级为 HDR-native profile。代码缺口是:按具体 preset 记录主面格式、采样 transfer、可接受负值/超 1 值、alpha policy,并在有测试证据后再开放 `ConditionalFP16`。 + +### CAS + +`CAS.hlsl` 和 `CAS_Scaling.hlsl` 都是单 pass、`INPUT`→`OUTPUT`,无 `//!FORMAT`;前者带 `//!CAPABILITY FP16`(`CAS.hlsl:3-5`),后者没有。两者只读取 RGB,最终输出多处写成 `MF4(...,1)`,例如 `CAS.hlsl:262-284` 与 `CAS_Scaling.hlsl:661-683`,alpha 固定为 1。CAS 核心对邻域振幅和最终像素使用 `saturate`,如 `CAS.hlsl:115-121,137-139`,实际 shader 因而具有 `[0,1]` 型 RGB 钳制行为;代码中没有 sRGB/PQ/HLG 解码或编码。公开证据矩阵 `HDR_PROTOCOL_EVIDENCE.md:35,118-197` 记录 AMD 参考 CLI 的 `R8G8B8A8_UNORM` 与 `R16G16B16A16_FLOAT`,以及官方文档的线性输入说明;这些证据属于参考实现/SDK 层,当前 Magpie shader 的资源声明仍为空。现有 catalog 的 `SDRCompatible` 决策与本地实现一致:FP16 编译能力和参考 CLI FP16 选项支持后续实验方向,当前 shader 的饱和/alpha 规则尚未构成 HDR-native 保障。缺口集中在具体 Magpie 资源分配、输入输出 transfer、峰值范围、alpha 透传策略;需要独立的 FP16 无钳制测试后才可调整 profile。 + +### CRT + +文件集合为 `CRT_Easymode.hlsl`、`CRT_Geom.hlsl`、`CRT_Hyllian.hlsl`、`CRT_Lottes.hlsl`、`GTU_v050.hlsl`。除 GTU 的 `tex1` 明确 `//!FORMAT R16G16B16A16_FLOAT`(`GTU_v050.hlsl:80-90`)外,主 `INPUT`、`OUTPUT` 和其他 preset 都无格式声明。Easymode、Geom、Hyllian、Lottes 都对输入做 gamma `pow`,参数范围来自 shader:Easymode `gammaInput 0.1..5`、`gammaOutput 0.1..5`(`CRT_Easymode.hlsl:141-154`),Geom `Target Gamma/Monitor Gamma 0.1..5`(`CRT_Geom.hlsl:30-43`),Hyllian `Input/Output Gamma 0..5`(`CRT_Hyllian.hlsl:50-63`)。Hyllian 在 `CRT_Hyllian.hlsl:221-239,253-255` 对重建颜色执行 `[0,1]` clamp 后再 gamma 输出,并固定 alpha 1;Geom 在 `CRT_Geom.hlsl:327-371` 进行 clamp、gamma 和固定 alpha;Lottes 以 `pow(...,2.2)` 取样并在 `CRT_Lottes.hlsl:331` 以 `pow(...,1/2.2)` 输出,alpha 固定 1。GTU 将 RGB 转 YIQ 后在 `GTU_v050.hlsl:139,149,152` clamp 到 `[0,1]`,最终 alpha 1。公开矩阵 `HDR_PROTOCOL_EVIDENCE.md:36,199-247` 将 CRT 记为无唯一上游协议;本地代码显示每个 preset 都有独立 gamma、clamp 和 mask/scanline 约束。profile 应保持 `Unknown` 与 generic SDR fallback;只有建立逐 preset 的范围、transfer、alpha 和峰值测试后,才有条件使用 `ConditionalFP16`。 + +### CuNNy + +所有 `src/Effects/CuNNy/CuNNy-*.hlsl` 变体带 `//!CAPABILITY FP16`,主输入和输出没有 `//!FORMAT`,输出尺寸为 2x。网络中间面统一明确为 `R8G8B8A8_SNORM`,典型 `CuNNy-2x4C-NVL.hlsl:46-56`;大模型的 `t0..t7` 也遵循同一格式。代码先把输入 RGB 投影到 YUV/特征张量,卷积层交替执行正负 ReLU 分支;最终 NVL out-shuffle 对 Y 分量使用 `saturate`,并以 `MF4(...,1.0)` 写回,典型 `CuNNy-2x4C-NVL.hlsl` 的 `l0` 与 out-shuffle。SNORM 中间面意味着实际张量边界约为 `[-1,1]` 的有符号 8 位归一化存储;此处是 DXGI 资源声明事实,模型输入归一化和输出 denormalization 仍缺少独立说明。公开矩阵 `HDR_PROTOCOL_EVIDENCE.md:37,249-297` 只确认 Blinue/CuNNy 上游可导出 mpv/Magpie shader,未枚举格式。profile 保持 `Unknown`,generic SDR fallback 负责把 canonical FP16 映射到现有 bounded 路径;FP16 capability 仅覆盖算术/编译,SNORM 张量协议仍需单独记录。缺口包括:主面格式、YUV 矩阵的 transfer 假设、模型训练范围、alpha 恢复、SNORM 溢出/钳制行为。 + +### CuNNy2 + +所有 `src/Effects/CuNNy2/*.hlsl` 变体带 `//!CAPABILITY FP16`,主输入输出未声明格式,输出尺寸为 2x。中间纹理 `T0..T15` 明确使用 `R8G8B8A8_UNORM`,例如 `CuNNy-3x12-NVL.hlsl:47-80`;快速变体同样使用 UNORM。网络层采用 `max(r,0)`,out-shuffle 以 `saturate(yuv.r + r0.*)` 限制 Y,再通过 YUV→RGB 写回并固定 alpha 1,典型 `CuNNy-fast-NVL.hlsl:349-354,392-402`。因此内部张量和最终输出都表现出 bounded `[0,1]` 倾向,且 alpha 由 shader 重建为不透明。公开矩阵 `HDR_PROTOCOL_EVIDENCE.md:38,299-347` 没有找到独立 CuNNy2 上游协议;本地 shader 是当前最可靠的实现证据。profile 保持 `Unknown` 与 generic SDR fallback,原因是模型归一化、输入 transfer、峰值和颜色原色域均未定义。缺口是把每个模型尺寸/变体的归一化常量、主面格式、alpha policy 和实际输出范围写入结构化 route;在此之前不启用 DirectFP16。 + +### Diagnostics + +`FrameGuidance_Confidence.hlsl` 与 `FrameGuidance_Motion.hlsl` 都只有 `INPUT`、同尺寸 `OUTPUT`、Linear sampler 和单个 PS pass;没有 `//!FORMAT` 或辅助纹理声明。Confidence 的 `Pass1` 是 `return INPUT.SampleLevel(sam, pos, 0);`(`FrameGuidance_Confidence.hlsl:14-18`),Motion 同样直接返回输入(`FrameGuidance_Motion.hlsl:22-26`),所以 shader 层 alpha 保留输入值。Motion 的 `Display Gain` 参数范围为 `0.005..1`(`FrameGuidance_Motion.hlsl:5-10`);其真正的 motion/confidence 资源格式由 `Magpie.Core/FrameGuidanceDiagnostics` native backend 提供,通用 shader 文件没有声明。公开矩阵 `HDR_PROTOCOL_EVIDENCE.md:39,349-397` 将 Diagnostics 定义为通用诊断名,缺少上游 I/O 协议。profile 保持 `Unknown`,generic SDR fallback 只用于把诊断结果留在安全的效果边界;诊断输出不得隐式当作普通 HDR 图像继续传播。缺口是记录 native backend 的输入资源类型、向量/置信度范围、显示增益是否改变 alpha,以及 raw-capture tap 与 canonical FP16 的对应关系。 + +### FSRCNNX + +`FSRCNNX.hlsl` 与 `FSRCNNX_LineArt.hlsl` 带 `//!CAPABILITY FP16`;主 `INPUT`/`OUTPUT` 无格式,输出尺寸为 2x;`featureMap1/2`、`tex1..tex4` 明确为 `R16G16B16A16_FLOAT`,见 `FSRCNNX.hlsl:21-54`。第一 pass 以 `GetLuma` 的 `0.299/0.587/0.114` 权重从 RGB 提取 luma(`FSRCNNX.hlsl:79-99`),映射层用 `max(target,0)+C*min(target,0)` 的 leaky-ReLU 形式,保留负特征。最终 pass 将网络 RGB 加回输入 RGB 并以 `MF4(...,1)` 写出,见 `FSRCNNX.hlsl:644-660`;alpha 固定为 1。公开矩阵 `HDR_PROTOCOL_EVIDENCE.md:47,682-730` 只确认 FSRCNNX 是 mpv/社区 shader 变体,缺乏独立 SDK 格式和 HDR 协议。FP16 中间面是实现事实,输入 transfer、训练归一化、负值/超 1 值接受区间、alpha 复原均无证据,因此 profile 保持 `Unknown` 与 generic SDR fallback。缺口是按模型变体验证输入/输出格式和范围,并记录 luma 投影是否在线性 RGB 上执行。 + +### FXAA + +`FXAA_High.hlsl`、`FXAA_Medium.hlsl`、`FXAA_Ultra.hlsl` 都只有未格式化的 `INPUT`/`OUTPUT`、Linear sampler 和一个 pass;没有辅助纹理或 FP16 capability。公共实现 `FXAA.hlsli:70-75` 明确 luma 估计式,并写出估计范围约 `0.0..2.963210702`;阈值、range、subpixel blend 全部基于该 luma。wrapper 最终写 `float4(FXAA(...),1)`,见三份 preset 的末尾,因此 alpha 固定为 1。算法没有 transfer 解码/编码,直接把采样值当作阈值运算输入;场景线性 HDR 下亮度比例会改变 edge threshold 行为。公开矩阵 `HDR_PROTOCOL_EVIDENCE.md:48,732-794` 记录 FXAA 的引擎/社区实现与缺少统一格式契约。profile 保持 `Unknown`,generic SDR fallback 负责 bounded 输入;缺口是建立具体格式、transfer、alpha、阈值尺度和 FP16 未钳制测试,完成后再考虑 `SDRCompatible` 或 `ConditionalFP16` 的显式 route。 + +### MLAA + +`MLAA.hlsl` 主 `INPUT`/`OUTPUT` 未声明格式;`edgeMask` 明确 `R8G8_UNORM`,`edgeCounts` 明确 `R8G8B8A8_UNORM`(`MLAA.hlsl:45-63`)。Pass 1 从 RGB 计算 `dot(rgb,[0.2126,0.7152,0.0722])`,以 `threshold 0.02..0.30` 生成二通道 edge mask(`MLAA.hlsl:29-42,69-102`);Pass 2 把边长度量化为 `0..15/15` 并写入 UNORM(`MLAA.hlsl:105-170`)。Pass 3 在平方域混合后开方,最终返回完整 `color`,所以 alpha 保留输入值(`MLAA.hlsl:228-265`)。主面没有 transfer 说明,辅助 UNORM 面构成明确的 bounded SDR 边界;HDR 场景线性亮度会直接影响 edge threshold。公开矩阵 `HDR_PROTOCOL_EVIDENCE.md:49,796-844` 只确认 MLAA 算法族,没有统一 SDK 格式。profile 保持 `Unknown` 与 generic SDR fallback;缺口是主面格式、transfer、HDR 阈值尺度、alpha 预期和辅助资源重建策略。 + +## Magpie.Core HDR 边界核对 + +当前公共边界已提供 canonical `DXGI_FORMAT_R16G16B16A16_FLOAT`:`HdrAdapterDispatcher.h:16-24`。`EffectDrawer::_UsesDirectHdrPath` 只在 route profile 为 `DirectFP16` 或 `ConditionalFP16` 且输入/输出资源均为 FP16 时返回 true,见 `EffectDrawer.cpp:433-449`。其余 profile 在 `PrepareHdrInput` 走 `HdrSurfaceAdapter::ConvertHdrToSdr`,transfer 未声明时默认 SRGB(`EffectDrawer.cpp:467-486`);输出阶段对应 `ConvertSdrToHdr`,见 `EffectDrawer.cpp:490-509`。`HdrAdapterDispatcher.cpp:73-89` 对 `Unknown` 选择 `canonicalFP16 -> SdrCompatibleFallback -> backend -> SdrToHdr -> canonicalFP16`,未声明 alpha 时设置 `ForceOpaque`。这些接口足以承载本组的声明性 route;本次没有修改 Renderer.cpp、EffectDrawer.cpp、Hdr* 通用组件、配置 UI 或其他分组文件。当前缺口全部属于效果级事实:每个 preset/模型缺少主面格式、transfer、范围、alpha 和 HDR evidence 字段,且现有 route 表仍需把本报告的 Unknown/SDR fallback 决策录入对应效果声明后才能被 dispatcher 读取。 + +## 修改与验证 + +修改文件:仅 `docs/experimental/agent-a-report.md`。 + +静态验证命令: + +```powershell +$repo = 'D:\AI\workspace\Magpie-0.6.5-research' +rg -n '^//!FORMAT|^//!CAPABILITY|^//!PASS|^//!IN |^//!OUT |Texture2D|SamplerState|saturate|clamp\(|pow\(|MF4\(.*1\)' ` + "$repo\src\Effects\Anime4K" "$repo\src\Effects\CAS" "$repo\src\Effects\CRT" ` + "$repo\src\Effects\CuNNy" "$repo\src\Effects\CuNNy2" "$repo\src\Effects\Diagnostics" ` + "$repo\src\Effects\FSRCNNX" "$repo\src\Effects\FXAA" "$repo\src\Effects\MLAA" + +rg -n 'CanonicalFormat|_UsesDirectHdrPath|ConvertHdrToSdr|ConvertSdrToHdr|HdrAdapterProfile::Unknown|ForceOpaque' ` + "$repo\src\Magpie.Core\HdrAdapterDispatcher.h" ` + "$repo\src\Magpie.Core\HdrAdapterDispatcher.cpp" ` + "$repo\src\Magpie.Core\EffectDrawer.cpp" + +Get-Content -Raw "$repo\docs\experimental\agent-a-report.md" | ` + Select-String 'Anime4K|CAS|CRT|CuNNy|CuNNy2|Diagnostics|FSRCNNX|FXAA|MLAA' +``` diff --git a/docs/experimental/agent-b-report.md b/docs/experimental/agent-b-report.md new file mode 100644 index 000000000..76c0c5366 --- /dev/null +++ b/docs/experimental/agent-b-report.md @@ -0,0 +1,88 @@ +# Agent B:DLSS / FSR / NIS / Optical Flow 效果协议核对 + +本报告覆盖 DLSS、DLSSFG、DLSSNR、FSR1、FSR2、FSR3 SR、FSR3 FG、FSR4、NIS、AMD FidelityFX Optical Flow。证据分为三层:官方协议材料、`Magpie-0.6.5-research` 当前 native backend、`DLSSNR-HDR-Experiments` 的真实运行日志。修改范围仅为本文件;Renderer、EffectDrawer、Hdr 通用组件、配置 UI 和 CMake 保持原状。 + +## 先给结论 + +DLSS、FSR2、FSR3 SR、FSR4、NIS 的官方资料都给出了可用于 HDR 的路径,但它们对 transfer function、exposure、depth、motion vector、资源状态的要求各自独立。当前 Magpie 的 SR backend 已经有一套可工作的 D3D11/D3D12 资源桥接,核心缺口集中在颜色格式识别、HDR 开关、exposure 传递和真实 depth。FSR1 与 NVIDIA Optical Flow 的公开协议偏向 `[0,1]`/8-bit SDR,适合走 SDR-compatible 辅助路径。DLSSFG 与 FSR3 FG 都属于呈现终端,backbuffer、HUD-less/UI、MV、depth、光流以及 frame lifetime 必须一起建模;单独把 FP16 颜色送入 backend 时,帧生成仍缺少这些配套资源与生命周期条件。 + +DLSSNR 的实验结论更窄。`results/hdr-scales` 使用 RTX 4070、驱动 32.0.16.1656、DLSSNR DLL 310.8.0.0、`DXGI_FORMAT_R16G16B16A16_FLOAT`、零运动和常量深度完成了真实 Feature 18 Create/Evaluate。`scale=1` 与基线的 MAE 为 0.01182021、PSNR 31.652 dB;`scale=2` 的 MAE 为 0.03846685、PSNR 18.109 dB;`scale=4.5` 的 MAE 为 0.20317990、PSNR 5.152 dB,最大差达到 3.5,并伴随红色/高光爆炸。实验只证明输入值域放大与输出失真之间存在强相关,资料没有给出可复核的 DLSSNR 归一化公式、参考白、峰值或逆变换参数。实现层面保留 `scale=1/2/4.5` 作为可配置实验轴,4.5 的含义保持为实验值,暂不解释为已确认的原生 HDR 白点;现有 bounded-HDR 文案继续单独标注为 NVIDIA 官方资料之外的本地协议。 + +## 十项协议矩阵 + +| 效果 | 官方输入/输出边界 | 当前 native backend | 主要缺口 | 可落地适配 | +|---|---|---|---|---| +| DLSS SR | Color 输入/输出为 API 支持格式;MV `RG16_FLOAT/RG32_FLOAT`;depth 单通道或 depth-stencil;exposure 1x1,`R16F` 优先;HDR 通过 `IsHDR`,输入为线性 HDR | `DLSSSRUpscaler` 创建零 MV `R16G16_FLOAT`、零 depth `R32_FLOAT`、bias mask `R8_UNORM`;创建标志含 AutoExposure;Evaluate 固定 jitter=0、MV scale=1、pre-exposure=1、exposure scale=1 | 没有从 canonical HDR 传入 `IsHDR`/exposure;depth 默认 zero-contract;颜色格式/色域没有协议字段 | 新增 DLSS 专属 HDR 参数桥:输入格式/transfer、HDR flag、1x1 exposure、真实 depth;保持现有 zero fallback,并记录 fallback 状态 | +| DLSSFG | backbuffer/final color;输出与 backbuffer 同格式;MV/depth 遵循 DLSS SR;HUD-less/UI 必须同尺寸、格式、色彩空间;有 HDR 开关 | `DLSSFrameGenerator` 从输入纹理复制格式,`NativeBackbufferFormat=inputDesc.Format`;zero MV/depth;MV scale=1;`depthInverted=false`;HUDLess/UI 等资源统一标记 never-provided | backbuffer 格式传递存在,HDR 色彩元数据、HUD-less/UI、真实 depth、HDR 开关没有完整接线 | 保留 PresentationTerminal;增加 backbuffer protocol、HDR flag、HUD-less/UI 生命周期和真实 depth/MV 适配,完成前不宣称 HDR 完整支持 | +| DLSSNR | NVIDIA 公开资料没有稳定的 Feature 18 HDR/FP16 色彩契约;本地实验只覆盖 FP16 值域探测 | `DLSSNRFilter` 强制同分辨率;输入仅 `R8G8B8A8_UNORM/B8G8R8A8_UNORM`,输出仅 `R8G8B8A8_UNORM`;NGX 颜色共享面为 RGBA8;创建 `Scale=1`、`ScalingRatio=1`;MV scale=1、depth inverted=1 | 当前路径是 SDR RGBA8;实验 FP16 路径与 Magpie 路径分离;缺少可验证 normalize/inverse、HDR metadata、exposure、真实 depth | 作为 BoundedHDR 专属 adapter:`canonical FP16 -> configurable scale/normalizer -> DLSSNR -> inverse -> canonical FP16`;第一版只暴露 scale=1/2/4.5 实验值和诊断,不固化公式 | +| FSR1 | 官方核心输入为感知 sRGB `[0,1]`;具体 DXGI 格式未枚举;负 RCAS 输入会产生 NaN;输出由宿主决定 | 当前工厂没有 FSR1 native backend,按 shader effect 处理 | 没有 native HDR/FP16 协议,算法输入是 display-referred SDR | SDR-compatible adapter:HDR 外观映射到 sRGB `[0,1]`,运行 FSR1,再按记录的映射恢复 | +| FSR2 | Color 应用指定;depth 1x float;MV 2x float;reactive/T&C `R8_UNORM`;exposure `R32_FLOAT`;HDR flag 后输入 linear,输出恢复原输入域 | `FSR2Upscaler` 零 MV `R16G16_FLOAT`、零 depth `R32_FLOAT`、reactive `R8_UNORM`;启用 auto exposure/depth inverted/infinite;exposure 传 `nullptr`;preExposure=1;MV scale=1 | HDR flag 没有接入;真实 depth/exposure 没有接入;当前 MV 只接受 frame guidance | DirectFP16 adapter:canonical FP16 color,显式 HDR flag,接真实 exposure/depth/MV;保持 masks 与 resource state 独立 | +| FSR3 SR | 与 FSR2 同类:应用指定 color,float depth/MV,`R8_UNORM` masks,`R32_FLOAT` exposure;HDR flag 后输入 linear | `FSR3Upscaler` 复制输入/输出纹理原格式;aux 为 depth `R32_FLOAT`、exposure 1x1 `R32_FLOAT=1`、reactive/transparency `R8_UNORM`、zero MV `R16G16_FLOAT`;create flags 含 NON_LINEAR_COLORSPACE;dispatch 固定 `NON_LINEAR_COLOR_SRGB`、preExposure=1 | 当前颜色路径明确使用 sRGB/non-linear,和 HDR linear 证据存在语义偏差;真实 depth/exposure/jitter 没有接入 | DirectFP16 adapter:HDR 模式切换为 linear,删去隐含 sRGB flag,提供真实 exposure/depth/MV;FSR3 provider 版本选择保持现有机制 | +| FSR3 FG | current backbuffer、可选 HUD-less、depth、MV、FSR Optical Flow `R16G16_SINT`、SCD `R32_UINT`;需 backBufferFormat、HDR transfer/luminance、present callback | 当前代码没有 FSR3 FG native backend 或效果注册;Frame Generation 代码路径只识别 DLSSFG/XeSSFG | 缺少整个终端协议:swapchain/backbuffer、HUD-less/UI、光流/SCD、HDR luminance、present lifetime | 新增独立 FSR3 FG backend 后再接入;先定义 PresentationTerminal 与 `backBufferFormat/transfer/minMaxLuminance`,使用独立于普通 SR backend 的终端适配 | +| FSR4 | Color 应用指定,推荐 linear;depth 1x float;MV 2x float;exposure 1x1 `R32_FLOAT`;可用 NON_LINEAR_COLORSPACE 标志 | `FSR3Upscaler` 以 `useFsr4` 选择 provider 4.1.1;资源和 dispatch 形态与 FSR3 SR 共用;仍固定 `NON_LINEAR_COLOR_SRGB`、exposure=1、zero depth | FSR4 选择存在,HDR transfer/exposure/depth 语义仍沿用 FSR3 的硬编码;缺少真实 ML 输入校准 | 在 FSR3 backend 内增加 FSR4 专属 protocol profile,显式 linear/non-linear 选择与 exposure/depth 绑定,保持 provider version 独立 | +| NIS | 输入/输出为非整数格式,文档例为 `R8G8B8A8_UNORM`/NV12;支持 LDR、PQ、linear HDR;linear HDR 建议 `[0,12.5]`;输入 SRV、输出 UAV、linear clamp sampler | 当前 native 工厂没有 NIS backend;NIS 作为 shader effect 时由通用 effect surface 决定 | 缺少 `NIS_HDR_MODE`、PQ/linear 选择、NV12 路径、viewport/resource state 专属适配 | 新增 NIS 专属 shader/native declaration:HDR mode、输入范围、PQ Rec.2020 或 linear `[0,12.5]`,保证 SRV/UAV 与 sampler contract | +| AMD FidelityFX Optical Flow | color 输入格式公开资料未枚举;输出光流 `R16G16_SINT`、SCD `R32_UINT`;8x8 block;HDR 依赖 transfer/luminance | `AmdOpticalFlowProvider` 固定 color shared texture 为 `R8G8B8A8_UNORM`;Performance 为 1/2 extent,Quality 为 full extent;SDK flow 期望 `R16G16_SINT`;输出 dense motion `R16G16_FLOAT`、confidence `R8_UNORM`;dispatch 固定 sRGB、luminance `{0,1}` | 输入格式被硬编码为 RGBA8;HDR transfer/luminance 没有暴露;SDK sparse 输出被本地 densify 改成 frame-guidance 格式 | BoundedHDR auxiliary adapter:先定义 color transfer/luminance 参数,再转换为 SDK 输入;保留 sparse `R16G16_SINT/SCD` 与 dense frame-guidance 输出的边界 | + +## DLSSNR 实验记录与边界 + +实验材料:`DLSSNR-HDR-Experiments/README.md`、`results/README.md`、`results/hdr-scales/*/run.log` 与 `output_diagnostic.log`、`scripts/Run-HdrScales.ps1`。三组测试均为 2560x1392、FP16 线性输入、零 MV、常量 depth、同分辨率输出,Feature 18 Create/Evaluate/Release 均返回成功。`scale=1` 的诊断写明输入/输出均为 `DXGI_FORMAT_R16G16B16A16_FLOAT`,sRGB EOTF 转 linear 后写入 GPU,输出匹配逆转换且无 tone mapping。`scale=2` 仍能完成评估,但误差明显增加。`scale=4.5` 的结果出现明显红色/高光爆炸,PSNR 降到 5.152 dB,最大差为 3.5。 + +这些日志支持三条实现判断。第一,DLSSNR Feature 18 在当前 DLL/驱动组合上接受该实验调用链,成功返回值本身不代表颜色协议正确。第二,`scale` 是实验输入值域倍率,`scale=4.5` 与 scRGB 传统白点代理有关,实验材料没有证明它等同于 DLSSNR 内部 reference white、曝光或 PQ 峰值。第三,现有材料没有足够信息写出归一化函数、曲线、参考白、峰值、alpha 规则或逆变换,因此适配器必须把这些字段作为可配置实验参数,记录输入/输出统计和版本哈希。 + +Magpie 当前 `DLSSNRFilter` 与实验 harness 处于两条协议线上。当前 backend 在 `DLSSNRFilter.cpp:1895-1911` 限制输入为 RGBA8/BGRA8、输出为 RGBA8,在 `:1951-1958` 把共享面强制成 RGBA8;`SetCreateParametersUnsafe` 在 `:1343-1365` 固定尺寸、`Upscaling=0`、`Scale=1`、`ScalingRatio=1`;`SetEvaluateParametersUnsafe` 在 `:1432-1464` 固定 MV scale=1、depth inverted=1、reset/style/mask 等参数,代码里没有 HDR flag、exposure texture 或公开 normalize 参数。可落地的第一步是单独新增 DLSSNR protocol/adapter 文件,承载 scale、输入 transfer、reference white、peak/headroom、inverse policy 和 diagnostics;公共 Renderer 与 Hdr 组件继续只处理 canonical FP16 表面。 + +## 官方证据入口 + +逐条来源、引用片段和证据等级已经汇总在 [`HDR_PROTOCOL_EVIDENCE.md`](HDR_PROTOCOL_EVIDENCE.md) 与 [`HDR_PROTOCOL_EVIDENCE.json`](HDR_PROTOCOL_EVIDENCE.json)。本组实际使用的上游入口如下: + +- NVIDIA DLSS Programming Guide: +- NVIDIA DLSS-G / Frame Generation: +- NVIDIA DLSSG header: +- AMD FSR2: +- AMD FSR3 Upscaler: +- AMD FSR3 Frame Interpolation / Optical Flow: +- NVIDIA Image Scaling: +- NVIDIA Optical Flow SDK: + +## 当前代码证据索引 + +- `src/Magpie.Core/DLSSSRUpscaler.cpp:50-171,262-276`:DLSS SR 尺寸检查、零 MV/depth/mask 格式、AutoExposure 创建标志、固定 exposure/jitter/MV scale。 +- `src/Magpie.Core/DLSSFrameGenerator.cpp:390-608,637-798`:DLSSFG backbuffer/native format、zero resources、resource flags、MV/depth binding、unit scale、backbuffer/output subrect。 +- `src/Magpie.Core/DLSSNRFilter.cpp:1343-1464,1863-1958,2124-2142`:DLSSNR 创建参数、评估参数、RGBA8 输入输出限制和状态日志。 +- `src/Magpie.Core/FSR2Upscaler.cpp:50-178`:FSR2 auxiliary formats、context flags、exposure/motion/preExposure 常量。 +- `src/Magpie.Core/FSR3Upscaler.cpp:185-255,262-474,482-550`:FSR3/FSR4 shared color format、aux formats、provider version、HDR/non-linear flag、dispatch 常量。 +- `src/Magpie.Core/AmdOpticalFlowProvider.cpp:258-390,393-465,521-620`:AMD OF 输入 RGBA8、Performance/Quality 尺寸、SDK sparse formats、sRGB/luminance 固定值、dense output formats。 +- `src/Magpie.Core/NvidiaOpticalFlowProvider.cpp:16-66,366-409,748-756`:NVIDIA OF S10.5 `SHORT2` 解码为 float motion、grid 1/2/4 路径、dense motion `R16G16_FLOAT` 与 confidence `R8_UNORM`。NVIDIA OF 作为 DLSS/FSR 的共享辅助 provider,官方输入仍限 GRAYSCALE8/NV12/ABGR8,当前 provider 没有 FP16/HDR 输入路由。 +- `src/Magpie.Core/NativeEffectBackendFactory.cpp:63-89`:DLSSNR、DLSS SR、FSR2、FSR3/FSR4 的当前 native 分派;FSR1、NIS、FSR3 FG、AMD OF 均不在普通 SR 分派里。 + +## 适配优先级 + +1. DLSSNR:新增专属 bounded-HDR protocol 文件,先保留 scale=1/2/4.5 实验选择和统计诊断,把归一化公式保持为可配置实验参数。 +2. FSR3/FSR4 与 FSR2:把 HDR linear、真实 exposure、depth、MV、reset 作为 effect-local protocol,修正当前 non-linear sRGB 硬编码语义。 +3. DLSS SR:补齐 HDR flag 与 exposure,保留 zero guidance 作为明确的降级状态。 +4. NIS:新增 HDR mode 与 PQ/linear range 声明,空间算法适合较早接入。 +5. DLSSFG、FSR3 FG:统一 PresentationTerminal 资源描述,再分别接 backbuffer/UI/HUD-less/光流协议;颜色适配单独记录 transfer/luminance。 +6. AMD FidelityFX Optical Flow:先把 SDK color transfer/luminance 从固定 sRGB `{0,1}` 提升为 provider-local 参数,再决定 bounded HDR 的默认值。 + +## 验证命令 + +本报告的证据复核: + +```powershell +rg -n "scale=1|scale=2|scale=4\.5|PSNR|MAE|DXGI_FORMAT_R16G16B16A16_FLOAT|颜色转换" ` + D:\AI\workspace\DLSSNR-HDR-Experiments\results\hdr-scales + +rg -n "DXGI_FORMAT|HDR|exposure|Exposure|motion|Motion|depth|Depth|Scale|ScalingRatio|NON_LINEAR_COLOR_SRGB" ` + D:\AI\workspace\Magpie-0.6.5-research\src\Magpie.Core\DLSSNRFilter.cpp ` + D:\AI\workspace\Magpie-0.6.5-research\src\Magpie.Core\DLSSSRUpscaler.cpp ` + D:\AI\workspace\Magpie-0.6.5-research\src\Magpie.Core\FSR2Upscaler.cpp ` + D:\AI\workspace\Magpie-0.6.5-research\src\Magpie.Core\FSR3Upscaler.cpp +``` + +DLSSNR 真实实验重跑路径: + +```powershell +powershell -ExecutionPolicy Bypass -File D:\AI\workspace\DLSSNR-HDR-Experiments\scripts\Run-HdrScales.ps1 +``` + +该脚本会分别执行 `--hdr-scale 1`、`2`、`4.5`,结果写入 `results\hdr-scales\`。报告没有把重跑作为本次完成条件,当前结论直接取自已经存在的真实日志与输出统计。 diff --git a/docs/experimental/agent-c-report.md b/docs/experimental/agent-c-report.md new file mode 100644 index 000000000..a2eee46db --- /dev/null +++ b/docs/experimental/agent-c-report.md @@ -0,0 +1,85 @@ +# Agent C:效果协议与当前实现核对 + +本报告覆盖 NNEDI3、Pixel Art、RAVU、RTX Video VSR、RTX Video Denoiser、RTX Video HDR、Sharpen、SMAA、xBRZ、XeSS、XeSSFG、NVIDIA Optical Flow。证据来源分成三层:公开协议记录 `docs/experimental/HDR_PROTOCOL_EVIDENCE.md` 与 `.json`,实施目录 `docs/experimental/HDR_EFFECT_IMPLEMENTATION_CATALOG.md`,以及本仓库内的 HLSL 与 native backend。仓库当前没有可用的 Git 元数据,因此报告以文件路径和行号作为可复核锚点。 + +## 总结 + +| 效果 | 真实协议与范围 | alpha / 颜色语义 | 当前调用链 | 结论 | +|---|---|---|---|---| +| NNEDI3 | 外部 `Texture2D` 格式由宿主决定;中间 `R16_FLOAT`,内部只处理 luma,输出重建 RGB | luma 通道在 `0..1` clamp;输出写入 `float4(..., 1.0)` | 纯 HLSL,多 pass,走普通 EffectDrawer | `ConditionalFP16` 仅代表中间表面;HDR 颜色契约仍 Unknown | +| Pixel Art | `MMPX` 2x、`Pixellate` 任意尺寸、`SharpBilinear` 普通采样;外部格式未枚举 | MMPX/Pixellate 仅取 RGB 且 alpha 置 1;SharpBilinear 返回整四通道采样 | 纯 HLSL | `SDRCompatible`;精确比较和艺术阈值要求记录范围 | +| RAVU | luma/YUV、RGB、Lite、Zoom、compute 变体分开;LUT 为 `R16G16B16A16_FLOAT`,luma 临时面为 `R16_FLOAT` | luma 变体由 YUV 重建并 alpha=1;RGB 变体写入四通道结果,alpha 随具体变体 | 纯 HLSL,多 pass / compute | `ConditionalFP16` 候选;需按变体绑定颜色与 alpha 规则 | +| RTX Video VSR | NVIDIA VFX `VideoSuperRes` 使用 interleaved RGBA U8 GPU buffer,通道值 `0..255`,可放大 | SDK 公开资料未定义 HDR transfer、primaries、metadata、alpha;本地 scale 显式做 `0..1 <-> 0..255` | `NativeEffectBackendFactory` -> `RTXVideoDenoiser`,quality 1..4 | `SDRCompatible`,U8 是硬边界 | +| RTX Video Denoiser | 与 VSR 共用 RGBA U8 interleaved GPU buffer;quality 8..11 同分辨率 | 同上;Denoise 受同分辨率约束 | `NativeEffectBackendFactory` -> `RTXVideoDenoiser`,quality 8..11 | `SDRCompatible`,当前 backend 已实现 | +| RTX Video HDR | 产品语义为 SDR 到 HDR10;公开记录没有可调用纹理格式、数值范围或 metadata API | alpha、transfer、primaries、输出所有权均未形成仓库协议 | 没有对应 HLSL/native backend 路径 | `Unknown`,保持 fallback | +| Sharpen | Adaptive/Fine/Luma/LCAS 四个 shader 家族;部分内部面 `R16G16B16A16_FLOAT`,输入输出格式依宿主 | 多数 shader 只取 RGB 且输出 alpha=1;FineSharp 保留 alpha 参与内部运算后最终置 1;存在 clamp、overshoot 和 `0..255` 风格参数 | 纯 HLSL | 具体 shader 逐项审计;默认 `SDRCompatible`,FP16 只能按实测变体提升 | +| SMAA | 外部颜色纹理未枚举;edges 为 `R8G8_UNORM` 或 `R16G16_FLOAT`,blend/history 为 `R8G8B8A8_UNORM` 或 `R16G16B16A16_FLOAT`,Area/Search 为 `R8*` | 普通路径由 SMAA neighborhood 输出四通道;实验 temporal resolve 明确输出 alpha=1,history alpha 作为有效标记 | 纯 HLSL,多 pass;无 native backend | `SDRCompatible`;中间 FP16 只说明存储格式 | +| xBRZ | 固定 2x..6x compute;Freescale 另有内部 `R8G8B8A8_UNORM`;输入输出外部格式由宿主 | 算法只采样 RGB,所有标准变体写 `float4(dst, 1)`;Freescale 输出同样 alpha=1 | 纯 HLSL | `SDRCompatible`,整数式颜色比较和 YCbCr 阈值依赖显示域 | +| XeSS SR | Intel 公共协议列出 `R16G16B16A16_FLOAT`、`R11G11B10_FLOAT`、`R8G8B8A8_UNORM` 等 linear 格式;MV `R16G16_FLOAT`,depth 常见 `R32_FLOAT` | 公共协议要求输出与输入同格式同色彩空间,alpha 不保留并填 1;本地实验 backend 实际只接受输入 RGBA/BGRA8 UNORM,输出 RGBA8 UNORM | `NativeEffectBackendFactory` -> `XeSSUpscaler`;D3D11/D3D12 shared texture,Zero-MV 或共享 MV | 公共协议为 `DirectFP16` 候选;当前 Magpie 路径是 LDR/UNORM 实验实现,存在明确实现差异 | +| XeSSFG | 公共 HDR terminal 为 HDR10/BT.2100 `R10G10B10A2_UNORM`,backbuffer/HUD-less/UI 要求同格式、色彩空间、尺寸;FP16/scRGB 路径未形成 | UI alpha 单独传递;terminal 输出随 proxy swap chain | marker HLSL -> `XeSSFGPresenter` -> XeSS D3D12 proxy swap chain | 文档要求 `BoundedHDR` terminal;当前 presenter 固定 `R8G8B8A8_UNORM` 且 alpha ignore,形成关键差异 | +| NVIDIA Optical Flow | 公共 NvOF 输入 `GRAYSCALE8`、`NV12`、`ABGR8`;flow 为 `SHORT2` S10.5,cost 为 UINT/UINT8 | flow 是 signed motion vector,不属于 RGB 颜色;本地 densify 除以 32,cost 除以 255 | `FrameGuidance` provider -> `NvidiaOpticalFlowProvider` -> NVOF D3D11 | SDR auxiliary path;当前 init 固定 `ABGR8`,公共多格式能力尚未映射 | + +## 逐项证据与差异 + +### NNEDI3 + +公开证据记录为 mpv/user-shader 族,`rgba16f/rgba16hf` 只证明中间 surface 习惯,输入输出颜色契约、transfer、HDR 上限和 alpha 没有统一声明(`HDR_PROTOCOL_EVIDENCE.md` NNEDI3 条目;`HDR_EFFECT_IMPLEMENTATION_CATALOG.md:103-107`)。仓库 shader 对每个 NNEDI3 预设声明 `temp` 为 `R16_FLOAT`,输入先经 `dot(x.rgb, rgb2y)` 转成 luma。`nnedi3` 结果在 `0.0..1.0` 之间 clamp;第二 pass 从输入取 UV,再用 `yuv2rgb` 重建 RGB,输出明确写成 `float4(..., 1.0)`,见 `src/Effects/NNEDI3/NNEDI3_nns16_win8x4.hlsl:38-42,326-344,651-675`。当前没有 NNEDI3 native backend,调用停留在普通 HLSL multi-pass。结论是中间 FP16 可记录为实现格式,直接把 canonical HDR RGB 交给该路径缺少可靠颜色协议,alpha 需要按输出置 1 记录。 + +### Pixel Art + +Pixel Art 组由 `MMPX`、`Pixellate`、`SharpBilinear` 三类 shader 组成,公开协议条目只给出泛化算法族,输入输出 DXGI 格式和 HDR 数值范围保持 Unknown。仓库实现显示三种 alpha 语义:MMPX 采样 `.rgb`,四个放大像素全部写 `float4(J/K/M/L, 1)`,见 `src/Effects/Pixel Art/MMPX.hlsl:27,122-131`;Pixellate 对四个角点的 RGB 求平均并返回 `float4(averageColor, 1.0)`,见 `Pixellate.hlsl:33-48`;SharpBilinear 直接返回 `INPUT.SampleLevel`,四通道采样结果沿用宿主 alpha,见 `SharpBilinear.hlsl:40-43`。MMPX 还依赖逐分量精确相等比较,Pixellate 的平均操作没有 HDR transfer 语义,SharpBilinear 的区域限制只作用于坐标。当前全组没有 native backend,默认采用 SDR-compatible 处理,HDR 进入前需要明确艺术变换和 alpha 规则。 + +### RAVU + +RAVU 变体包含 luma/YUV、RGB、Lite、Zoom、anti-ringing、compute 与非 compute 路径。公开证据确认 `rgba16f/rgba16hf` 中间面和 luma/RGB 分化,实施目录要求按具体变体绑定颜色协议(`HDR_PROTOCOL_EVIDENCE.md` RAVU 条目;`HDR_EFFECT_IMPLEMENTATION_CATALOG.md:109-113`)。本地 luma 变体的 LUT 声明为 `R16G16B16A16_FLOAT`,中间 luma 面为 `R16_FLOAT`,见 `src/Effects/RAVU/RAVU_R2.hlsl:37-49`;第二 pass 从输入保留 UV,YUV 转 RGB 后 alpha 固定为 1,见 `RAVU_R2.hlsl:210-213`。RGB 变体的内部 LUT 同为 FP16,luma 变体写 `vec4(value,0)` 到临时面,RGB 变体最终写入四通道结果,见 `RAVU_R2_RGB.hlsl:34-48,70-71,212-213`。当前没有 native backend。结论为 RGB 变体具备 FP16 存储候选,luma/YUV 变体需要显式 luma、UV、transfer 和 alpha 适配,FP16 surface 本身不构成 HDR 颜色保证。 + +### RTX Video VSR 与 RTX Video Denoiser + +公开 VFX 协议把 VSR 与 Denoiser 都限定为 GPU-resident interleaved RGBA/BGRA U8,每分量数值边界是 `0..255`;Denoise 模式保持输入输出分辨率一致(`HDR_PROTOCOL_EVIDENCE.md` RTXVideo 两条;`HDR_EFFECT_IMPLEMENTATION_CATALOG.md:156-166`)。本地 `RTXVideoDenoiser.cpp` 使用 `NvCVImage_Alloc(... NVCV_RGBA, NVCV_U8, NVCV_INTERLEAVED, NVCV_GPU, 32)` 创建 input/output GPU image,quality 1..4 映射 VSR,8..11 映射 Denoise,见 `src/Magpie.Core/RTXVideoDenoiser.cpp:78-118`。对浮点 D3D11 surface,`inputScale=255.0f`,`outputScale=1.0f/255.0f`,并在 `NvCVImage_Transfer` 前后完成量化和还原,见 `RTXVideoDenoiser.cpp:101-109,193-231`。Denoise quality 校验输出与输入宽高一致,见 `RTXVideoDenoiser.cpp:90-95`。工厂把 `RTXVideo\\RTXVideo_VSR_*` 和 `RTXVideo\\RTXVideo_Denoise_*` 都路由到同一 native 类,再由 quality level 区分,见 `src/Magpie.Core/NativeEffectBackendFactory.cpp:92-113`;对应 HLSL 仅是 placeholder pass,native backend 接管实际处理。alpha、HDR transfer、primaries、metadata 没有 VFX API 证据,canonical HDR 必须先转成选定 SDR appearance 并量化到 U8,再通过成对策略重建。 + +### RTX Video HDR + +公开记录只有产品级 SDR-to-HDR10 语义,纹理格式、接口调用、数值范围、颜色 metadata、alpha 和输出所有权保持 Unknown。仓库中存在 RTX Video VSR/Denoise shader 与 backend,没有独立 `RTXVideo_HDR` shader、native 类或 factory 分支。当前调用链没有可落地的 HDR terminal。实现状态保持 `Unknown/fallback`,不把 canonical FP16 直接送入该产品能力。 + +### Sharpen + +Sharpen 组包含 AdaptiveSharpen、FineSharp、LCAS、LumaSharpen,公开目录要求按具体 backend 记录 clamp、negative lobe、overshoot、alpha 和 strength units。仓库 shader 普遍只取 RGB:LCAS、LumaSharpen 最终写 `float4(...,1)`,AdaptiveSharpen 写 `float4(src.rgb + sharpdiff,1)`,见 `src/Effects/Sharpen/LCAS.hlsl:39-54,73-95`、`AdaptiveSharpen.hlsl:102-124,206-210`、`LumaSharpen.hlsl:187-211`。FineSharp 声明两个内部 `R16G16B16A16_FLOAT` 面,内部读取和传播 alpha,最终 RGB 转换后仍写 alpha=1,见 `FineSharp.hlsl:49-67,337-368,442-444`;其参数计算含 `/255.0f`,属于参数标度线索,不等于外部纹理协议。当前没有 native backend,四个 shader 均由普通效果链执行。报告 profile 采用按变体选择:明确归一化、clamp 的路径为 SDR-compatible;完成线性 FP16 保真测试的路径才具备 ConditionalFP16 资格。 + +### SMAA + +公开 SMAA 资料给出 RGBA color、edges、area、search、depth 资源,普通路径中间纹理多为非 sRGB,最终 neighborhood blending 才涉及 sRGB 选项(`HDR_PROTOCOL_EVIDENCE.md` SMAA 条目;`HDR_EFFECT_IMPLEMENTATION_CATALOG.md:168-173`)。仓库 Low/Medium/T2x/4x 路径使用 `R8G8_UNORM` edges、`R8G8B8A8_UNORM` blend/history;High/Ultra 使用 `R16G16_FLOAT` edges、`R16G16B16A16_FLOAT` blend,Area 为 `R8G8B8A8_UNORM`,Search 为 `R8_UNORM`,例如 `src/Effects/SMAA/SMAA_High.hlsl:6-34` 与 `SMAA_Low.hlsl:6-34`。普通三 pass 是 luma edge -> blending weights -> neighborhood blending;实验 temporal 还维护 current/history/historyNext,resolve 明确 `return float4(...,1.0)`,history alpha 作为有效历史标记,见 `SMAA_T2x_Experimental.hlsl:151-182`。当前没有 native backend。外部输入输出格式由宿主提供,内部 FP16 只说明中间资源格式,颜色 transfer 和 alpha 仍按 shader 结果记录。 + +### xBRZ + +公开 xBRZ 社区接口常见 RGBA U8 `Uint8ClampedArray`,缩放因子 2..6,alpha 被保留;本地 native HLSL 实现采用 compute shader,标准变体覆盖 2x、3x、4x、5x、6x,Freescale 使用内部 `R8G8B8A8_UNORM`。标准变体采样 `INPUT.rgb`,用打包 RGB 值和 YCbCr 距离做等色判断,最终所有输出写 `float4(dst,1)`,见 `src/Effects/xBRZ/xBRZ_2x.hlsl:36-56,125-145,290-299`;其他倍率保持同一结构。Freescale 第二 pass 使用 `floor(info * 255 + 0.5)` 解码内部标志,输出仍置 alpha=1,见 `xBRZ_Freescale.hlsl:276-318,388`。当前调用链是纯 HLSL,无 native backend。精确颜色比较、整数式阈值和 alpha 置 1 使其保持 SDR-compatible,直接 scene-linear HDR 会改变阈值意义。 + +### XeSS Super Resolution + +公共 XeSS-SR 证据列出 `R16G16B16A16_FLOAT`、`R11G11B10_FLOAT`、`R8G8B8A8_UNORM` 与其他 linear color format,输出要求与输入保持同格式同色彩空间,alpha 不保留并填 1,MV 常用 `R16G16_FLOAT`,depth 可用 `R32_FLOAT`(`HDR_PROTOCOL_EVIDENCE.md` XeSS 条目;`HDR_EFFECT_IMPLEMENTATION_CATALOG.md:88-93`)。本地实现存在清晰差异:`XeSSUpscaler::Initialize` 只接受 `R8G8B8A8_UNORM` 或 `B8G8R8A8_UNORM` 输入,输出必须是 `R8G8B8A8_UNORM`,并限制最高 3x,见 `src/Magpie.Core/XeSSUpscaler.cpp:184-214`;BGRA 输入先经 compute shader 转 RGBA,shared input/output 纹理也按 RGBA8 创建,见 `XeSSUpscaler.cpp:49-60,240-263`。XeSS 初始化设置 `XESS_INIT_FLAG_LDR_INPUT_COLOR`,并使用 flat `R32_FLOAT` depth、`R8_UNORM` responsive mask、zero/shared `R16G16_FLOAT` motion,见 `XeSSUpscaler.cpp:308-334,417-426`。调用链由 `NativeEffectBackendFactory` 识别 `XeSS\\XeSS_SR` 后创建 `XeSSUpscaler`,见 `NativeEffectBackendFactory.cpp:77-89`;效果 HLSL 只是 marker pass。结论是公共协议具备 DirectFP16 候选,当前仓库路径仍是 LDR/UNORM Zero-MV 或共享 MV 实验实现,HDR 直接接入需要单独 backend 改造与 alpha 清理。 + +### XeSS Frame Generation + +公共 XeSS-FG HDR terminal 证据要求 HDR10/BT.2100 `R10G10B10A2_UNORM`,backbuffer、HUD-less、UI-only 资源需匹配像素格式、色彩空间和尺寸,FP16 HDR/scRGB 路径没有对应公开 terminal 契约(`HDR_PROTOCOL_EVIDENCE.md` XeSSFG 条目;`HDR_EFFECT_IMPLEMENTATION_CATALOG.md:130-134`)。本地 presenter 固定 `COLOR_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM`,创建 proxy swap chain 时使用同一格式并设置 `DXGI_ALPHA_MODE_IGNORE`,见 `src/Magpie.Core/XeSSFGPresenter.cpp:18-20,548-558`;D3D11 shared color、D3D12 backbuffer、frame resource tagging 和 proxy `Present` 组成完整 terminal,见 `XeSSFGPresenter.cpp:155-195,782-899`。外部 motion 若启用,必须为 `R16G16_FLOAT` 且尺寸与 destination 完全匹配,见 `XeSSFGPresenter.cpp:632-661`;没有外部 motion 时使用 zero motion 与 flat depth。`XeSSFG\\XeSS_FrameGeneration_x2_ZeroMV.hlsl` 只保留 marker pass,实际插值由 `XeSSFGPresenter` 接管,见该 shader:1-52。实现目录建议的 PQ/BT.2100 `R10G10B10A2_UNORM` terminal 尚未落入当前 presenter,报告将其标为 bounded-HDR 文档目标与 R8 presenter 现状之间的实现差异。 + +### NVIDIA Optical Flow + +公开 NvOF 头文件协议列出 `NV_OF_BUFFER_FORMAT_GRAYSCALE8`、`NV_OF_BUFFER_FORMAT_NV12`、`NV_OF_BUFFER_FORMAT_ABGR8` 输入,flow 输出为 `SHORT2` S10.5,cost 可为 UINT/UINT8,支持 grid 1/2/4(`HDR_PROTOCOL_EVIDENCE.md` NVIDIA Optical Flow 条目;`HDR_EFFECT_IMPLEMENTATION_CATALOG.md:168-173`)。本地 provider 查询输入、输出、cost caps,并要求 ABGR8 与 S10.5 可用;初始化参数最终固定 `inputBufferFormat = NV_OF_BUFFER_FORMAT_ABGR8`,见 `src/Magpie.Core/NvidiaOpticalFlowProvider.cpp:503-545`。flow 结果被 densify shader 读取为 `Texture2D` 并除以 32,cost 读取为 uint 并除以 255,见 `NvidiaOpticalFlowProvider.cpp:15-61,103-105`;输出落入 `FrameGuidance` 的 motion/confidence 纹理,不进入普通 RGB 效果 surface。质量配置映射为 4x 或 2x grid 与 FAST/MEDIUM/SLOW perf level,见 `NvidiaOpticalFlowProvider.cpp:119-134`。当前调用链由 FrameGuidance provider 驱动,可被 XeSS SR、XeSSFG、DLSS 等请求;公共三种输入格式在本地只落地 ABGR8,属于 auxiliary SDR path。 + +## 文件修改与验证 + +本次只新增本报告文件:`docs/experimental/agent-c-report.md`。没有修改 `Renderer.cpp`、`EffectDrawer.cpp`、`Hdr*` 通用组件、配置 UI、CMake 或任何既有公共文件,也没有修改分组外效果文件。 + +已完成的只读核对命令: + +```powershell +rg -n -i "NNEDI3|Pixel Art|RAVU|RTXVideo|Sharpen|SMAA|xBRZ|XeSS|XeSSFG|Optical Flow|R10G10B10A2|R11G11B10|R16G16B16A16|GRAYSCALE8|ABGR8|NV12" src docs/experimental +Get-Content docs/experimental/HDR_PROTOCOL_EVIDENCE.md +Get-Content docs/experimental/HDR_EFFECT_IMPLEMENTATION_CATALOG.md +Get-Content src/Magpie.Core/NativeEffectBackendFactory.cpp +Get-Content src/Magpie.Core/RTXVideoDenoiser.cpp +Get-Content src/Magpie.Core/XeSSUpscaler.cpp +Get-Content src/Magpie.Core/XeSSFGPresenter.cpp +Get-Content src/Magpie.Core/NvidiaOpticalFlowProvider.cpp +``` + +报告所引用的格式、范围、alpha、调用链和实现差异均来自上述只读结果;没有进行构建或运行时验证,因为本交付只新增证据报告,且当前工作树没有为这些 SDK 路径提供统一可复现的构建环境。 diff --git a/scripts/Run-HdrMechanicalValidation.ps1 b/scripts/Run-HdrMechanicalValidation.ps1 new file mode 100644 index 000000000..944057757 --- /dev/null +++ b/scripts/Run-HdrMechanicalValidation.ps1 @@ -0,0 +1,193 @@ +# Lightweight source-level validation for the HDR mechanical slice. +# This script does not compile; it checks that the required configuration, +# protocol, dispatcher, diagnostics, and capture-processor scaffolding is +# present in the expected source files. Run from the repository root: +# +# powershell -ExecutionPolicy Bypass -File scripts\Run-HdrMechanicalValidation.ps1 + +$ErrorActionPreference = 'Stop' + +function Assert-Contains { + param( + [string]$Path, + [string[]]$Expected, + [string]$Label + ) + + if (-not (Test-Path $Path)) { + throw "Missing file: $Path ($Label)" + } + + $content = Get-Content $Path -Raw + foreach ($needle in $Expected) { + if (-not $content.Contains($needle)) { + throw "Missing '$needle' in $Path ($Label)" + } + } + + Write-Host "PASS $Label" +} + +$root = Split-Path -Parent $PSScriptRoot +$core = Join-Path $root 'src\Magpie.Core' +$app = Join-Path $root 'src\Magpie' + +Assert-Contains (Join-Path $core 'include\ScalingOptions.h') @( + 'EnableHdrCompatibility = 1 << 23', + 'DEFINE_FLAG_ACCESSOR(IsHdrCompatibilityEnabled, ScalingFlags::EnableHdrCompatibility, flags)' +) 'ScalingOptions flag/accessor' + +Assert-Contains (Join-Path $core 'ScalingOptions.cpp') @( + 'IsHdrCompatibilityEnabled: {}', + 'IsHdrCompatibilityEnabled(),' +) 'ScalingOptions log' + +Assert-Contains (Join-Path $app 'Profile.h') @( + 'DEFINE_FLAG_ACCESSOR(IsHdrCompatibilityEnabled, ScalingFlags::EnableHdrCompatibility, scalingFlags)' +) 'Profile flag accessor' + +Assert-Contains (Join-Path $app 'AppSettings.cpp') @( + 'writer.Key("enableHdrCompatibility")', + 'writer.Bool(profile.IsHdrCompatibilityEnabled())', + 'JsonHelper::ReadBoolFlag(profileObj, "enableHdrCompatibility", ScalingFlags::EnableHdrCompatibility, profile.scalingFlags);' +) 'Profile save/load' + +Assert-Contains (Join-Path $app 'ProfilePage.xaml') @( + 'x:Uid="Profile_General_HdrCompatibility"', + 'IsHdrCompatibilityEnabled' +) 'Profile UI' + +Assert-Contains (Join-Path $app 'ProfileViewModel.idl') @('Boolean IsHdrCompatibilityEnabled;') 'ProfileViewModel idl' +Assert-Contains (Join-Path $app 'ProfileViewModel.h') @('bool IsHdrCompatibilityEnabled() const noexcept;') 'ProfileViewModel header' +Assert-Contains (Join-Path $app 'ProfileViewModel.cpp') @('ProfileViewModel::IsHdrCompatibilityEnabled') 'ProfileViewModel cpp' + +Assert-Contains (Join-Path $core 'HdrFrame.h') @( + 'struct HdrFormatRoute', + 'effectId', + 'optionId', + 'inputFormat', + 'outputFormat', + 'inputTransfer', + 'outputTransfer', + 'inputRange', + 'outputRange', + 'alphaMode', + 'evidenceLevel', + 'hdrNative', + 'adapterProfile', + 'defaultForHdr', + 'defaultForSdr' +) 'HDR route data structure' + +Assert-Contains (Join-Path $core 'HdrProtocol.h') @( + 'SelectDefaultHdrRoute', + 'SelectDefaultSdrRoute', + 'GetAcceptedFormatRoutes', + 'GetHdrNativeFormatRoutes', + 'GetHdrAdapterFormatRoutes', + 'SerializeHdrFormatRoute' +) 'HDR protocol helpers' + +Assert-Contains (Join-Path $core 'HdrAdapterDispatcher.h') @( + 'class HdrAdapterDispatcher', + 'DXGI_FORMAT_R16G16B16A16_FLOAT', + 'forwardParameters', + 'inverseParameters' +) 'HDR adapter dispatcher' + +Assert-Contains (Join-Path $core 'HdrAdapterDispatcher.cpp') @( + 'case HdrAdapterProfile::DirectFP16', + 'case HdrAdapterProfile::BoundedHDR', + 'case HdrAdapterProfile::SDRCompatible', + 'case HdrAdapterProfile::ConditionalFP16', + 'case HdrAdapterProfile::Unknown', + 'case HdrAdapterProfile::PresentationTerminal' +) 'HDR adapter profiles' + +Assert-Contains (Join-Path $core 'HdrDiagnostics.h') @( + 'struct HdrDiagnostics', + 'hdrOptionEnabled', + 'captureMethod', + 'sourceFormat', + 'sourceColorDescription', + 'canonicalFormat', + 'selectedAdapterProfile', + 'selectedRouteId', + 'conversionPath', + 'fallbackReason' +) 'HDR diagnostics record' + +Assert-Contains (Join-Path $core 'HdrCaptureProcessor.h') @( + 'Process(', + 'GetCanonicalTexture()', + 'GetFrameMetadata()', + 'ResetForResize()', + 'LastAssumption()' +) 'HdrCaptureProcessor interface' + +Assert-Contains (Join-Path $core 'DLSSNRFilter.cpp') @( + 'const float hdrScale = hdrEnabled ? getParameter("experimentalHdrScale", 1.0f) : 1.0f;', + 'const bool hdrPath = hdrEnabled && getParameter("experimentalHdrPath", 0.0f) >= 0.5f;' +) 'DLSSNR HDR setting boundary' + +Assert-Contains (Join-Path $core 'NativeEffectBackendFactory.cpp') @( + 'if (!hdrEnabled) {', + 'if (IsSuperResolutionEffect(effectName)) {', + 'const DLSSNRSettings settings = ParseDLSSNRSettings(option, hdrEnabled);' +) 'Native backend SDR factory path' + +Assert-Contains (Join-Path $core 'Renderer.cpp') @( + 'if (!hdrEnabled) {', + 'return {};' +) 'Renderer HDR route global gate' + +Assert-Contains (Join-Path $core 'XeSSUpscaler.cpp') @( + 'if (!hdrEnabled) {', + 'inputDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM ||', + 'outputDesc.Format != DXGI_FORMAT_R8G8B8A8_UNORM', + '(impl->hdrEnabled ? COLOR_CONVERT_HLSL : COLOR_CONVERT_LDR_HLSL)', + 'initParams.initFlags |= XESS_INIT_FLAG_LDR_INPUT_COLOR;' +) 'XeSS SDR U8 contract' + +Assert-Contains (Join-Path $core 'XeSSFGPresenter.cpp') @( + 'return hdr ? HDR_COLOR_FORMAT : LDR_COLOR_FORMAT;', + 'return hdr ? OVERLAY_FORMAT : LDR_COLOR_FORMAT;', + 'if (impl->hdrEnabled) {', + 'DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020' +) 'XeSSFG terminal format boundary' + +Assert-Contains (Join-Path $core 'FSR2Upscaler.cpp') @( + 'if (_hdrProtocol.hdrColorInput) {', + '_exposure ? L"FSR2_Exposure" : L"FSR2_AutoExposure"' +) 'FSR2 exposure boundary' + +Assert-Contains (Join-Path $core 'RTXVideoDenoiser.cpp') @( + 'if (ScalingWindow::Get().Options().IsHdrCompatibilityEnabled()) {', + 'impl->inputScale = isFloatFormat(inputDesc.Format) ? 255.0f : 1.0f;', + 'if (!_hdrBoundary.hdrEnabled) {' +) 'RTX Video SDR U8 boundary' + +Assert-Contains (Join-Path $core 'include\EffectCompiler.h') @( + 'static constexpr uint32_t HdrCompatibility = 1u << 12;' +) 'Effect cache HDR variant flag' + +Assert-Contains (Join-Path $core 'EffectCompiler.cpp') @( + 'macros.emplace_back("MP_HDR_SATURATE",', + 'macros.emplace_back("MP_HDR_ALPHA",', + 'macros.emplace_back("MP_HDR_COMPATIBILITY", "1");' +) 'Effect shader HDR variants' + +Assert-Contains (Join-Path $core 'NvidiaOpticalFlowProvider.cpp') @( + 'if (!hdrEnabled) {', + 'if (!HasFormat(inputFormats, DXGI_FORMAT_B8G8R8A8_UNORM)) {', + 'inputDxgiFormat = DXGI_FORMAT_B8G8R8A8_UNORM;' +) 'NVIDIA Optical Flow SDR input contract' + +Assert-Contains (Join-Path $core 'PassThroughFrames.cpp') @( + 'constexpr char REFERENCE_LDR_HLSL[]', + 'constexpr char REFERENCE_HDR_HLSL[]', + 'hdrEnabled ? REFERENCE_HDR_HLSL : REFERENCE_LDR_HLSL' +) 'Pass-through SDR shader contract' + +Write-Host '' +Write-Host 'All HDR mechanical source-level validation checks passed.' diff --git a/src/Common.Post.props b/src/Common.Post.props index f7fbe6add..6c5552395 100644 --- a/src/Common.Post.props +++ b/src/Common.Post.props @@ -113,7 +113,7 @@ - + diff --git a/src/Effects/DLSSNR/DLSSNR_AI_Filter.hlsl b/src/Effects/DLSSNR/DLSSNR_AI_Filter.hlsl index e8fa99f9a..71ab833d5 100644 --- a/src/Effects/DLSSNR/DLSSNR_AI_Filter.hlsl +++ b/src/Effects/DLSSNR/DLSSNR_AI_Filter.hlsl @@ -5,6 +5,23 @@ //!VERSION 4 //!SORT_NAME DLSSNR AI Filter (Experimental) +//!PARAMETER +//!GROUP HDR Protocol +//!LABEL HDR Processing Path +//!DEFAULT 0 +//!OPTION 0 SDR RGBA8 compatibility +//!OPTION 1 Experimental FP16 value-domain path +int experimentalHdrPath; + +//!PARAMETER +//!GROUP HDR Protocol +//!LABEL Experimental HDR Scale +//!DEFAULT 1 +//!MIN 1 +//!MAX 4.5 +//!STEP 0.5 +float experimentalHdrScale; + //!PARAMETER //!GROUP Detail Control //!LABEL Adjust Input Resolution\n(Reduces DLSSNR Quality) diff --git a/src/Effects/Effects.vcxproj b/src/Effects/Effects.vcxproj index c6c3a0c79..b1abe2089 100644 --- a/src/Effects/Effects.vcxproj +++ b/src/Effects/Effects.vcxproj @@ -4,8 +4,8 @@ Win32Proj {62503530-b84b-4cc2-80b6-3f89618172b7} 10.0.26100.0 - $(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ - $(SolutionDir)\bin\$(Platform)\$(Configuration)\ + $(BuildRoot)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ + $(BuildRoot)\bin\$(Platform)\$(Configuration)\ diff --git a/src/Effects/MLAA/MLAA.hlsl b/src/Effects/MLAA/MLAA.hlsl index b34c7bc3c..5c2155ca3 100644 --- a/src/Effects/MLAA/MLAA.hlsl +++ b/src/Effects/MLAA/MLAA.hlsl @@ -102,6 +102,12 @@ float2 Pass1(float2 pos) { return float2(abs(center - upper), abs(center - right)) > threshold; } +//!PASS 2 +//!DESC Morphological line search +//!STYLE PS +//!IN INPUT, edgeMask +//!OUT edgeCounts + float LoadEdge(int2 p, uint channel) { return edgeMask.Load(int3(ClampPos(p), 0))[channel]; } @@ -118,39 +124,21 @@ float2 SearchEdge(int2 p, int2 negativeDir, int2 positiveDir, uint channel) { bool positiveActive = true; bool negativeEnd = false; bool positiveEnd = false; - [unroll] for (uint i = 1; i <= MAX_EDGE_LENGTH; ++i) { if (negativeActive) { - if (LoadEdge(p + negativeDir * int(i), channel) > 0.5) { - ++negativeCount; - } else { - negativeActive = false; - negativeEnd = true; - } + if (LoadEdge(p + negativeDir * int(i), channel) > 0.5) ++negativeCount; + else { negativeActive = false; negativeEnd = true; } } - if (positiveActive) { - if (LoadEdge(p + positiveDir * int(i), channel) > 0.5) { - ++positiveCount; - } else { - positiveActive = false; - positiveEnd = true; - } + if (LoadEdge(p + positiveDir * int(i), channel) > 0.5) ++positiveCount; + else { positiveActive = false; positiveEnd = true; } } } - - return float2( - EncodeCount(negativeCount, negativeEnd), + return float2(EncodeCount(negativeCount, negativeEnd), EncodeCount(positiveCount, positiveEnd)); } -//!PASS 2 -//!DESC Morphological line search -//!STYLE PS -//!IN edgeMask -//!OUT edgeCounts - float4 Pass2(float2 pos) { const int2 p = PixelPos(pos); const float2 edges = edgeMask.Load(int3(p, 0)).rg; @@ -239,6 +227,54 @@ void BlendEdge( //!IN INPUT, edgeCounts //!OUT OUTPUT +// Pass 3 is compiled as an independent shader. Keep its helpers in this +// pass section so the effect compiler includes them in the generated source. +uint DecodeRawPass3(float value) { + return (uint)round(saturate(value) * 15.0); +} + +bool ColorsDifferPass3(int2 a, int2 b) { + return abs(LoadLuma(a) - LoadLuma(b)) > threshold; +} + +void BlendEdgePass3(float2 encodedCounts, int2 edgePos, int2 acrossEdge, + int2 alongEdge, bool inverse, inout float4 color) { + const uint rawNegative = DecodeRawPass3(encodedCounts.x); + const uint rawPositive = DecodeRawPass3(encodedCounts.y); + const bool negativeEnd = rawNegative >= 8; + const bool positiveEnd = rawPositive >= 8; + if (!negativeEnd && !positiveEnd) return; + uint negativeCount = rawNegative & 7; + uint positiveCount = rawPositive & 7; + const float4 adjacent = LoadColor(edgePos + acrossEdge); + if (negativeCount + positiveCount == 0) { + const float weight = 0.125 * strength; + color.rgb = sqrt(lerp(color.rgb * color.rgb, adjacent.rgb * adjacent.rgb, weight)); + return; + } + if (!positiveEnd) positiveCount = MAX_EDGE_LENGTH + 1; + if (!negativeEnd) negativeCount = MAX_EDGE_LENGTH + 1; + const float length = negativeCount + positiveCount + 1.0; + const float midpoint = length * 0.5; + const float distance = negativeCount; + uint shape = 0; + if (ColorsDifferPass3(edgePos - alongEdge * int(negativeCount), + edgePos - alongEdge * int(negativeCount + 1))) shape |= 1; + if (ColorsDifferPass3(edgePos + alongEdge * int(positiveCount), + edgePos + alongEdge * int(positiveCount + 1))) shape |= 2; + const bool shouldBlend = + (inverse && ((shape == 2 && float(negativeCount) <= midpoint) || + (shape == 1 && float(negativeCount) >= midpoint) || shape == 0)) || + (!inverse && ((shape == 2 && float(negativeCount) >= midpoint) || + (shape == 1 && float(negativeCount) <= midpoint) || shape == 3)); + if (shouldBlend) { + const float h0 = abs((length - distance) / length - 0.5); + const float h1 = abs((length - distance - 1.0) / length - 0.5); + const float area = 0.5 * (h0 + h1) * strength; + color.rgb = sqrt(lerp(color.rgb * color.rgb, adjacent.rgb * adjacent.rgb, area)); + } +} + float4 Pass3(float2 pos) { const int2 p = PixelPos(pos); float4 color = LoadColor(p); @@ -250,16 +286,16 @@ float4 Pass3(float2 pos) { const float2 leftVertical = edgeCounts.Load(int3(left, 0)).zw; if (any(current.xy > 0)) { - BlendEdge(current.xy, p, int2(0, -1), int2(1, 0), false, color); + BlendEdgePass3(current.xy, p, int2(0, -1), int2(1, 0), false, color); } if (any(belowHorizontal > 0)) { - BlendEdge(belowHorizontal, below, int2(0, 1), int2(1, 0), true, color); + BlendEdgePass3(belowHorizontal, below, int2(0, 1), int2(1, 0), true, color); } if (any(current.zw > 0)) { - BlendEdge(current.zw, p, int2(1, 0), int2(0, -1), false, color); + BlendEdgePass3(current.zw, p, int2(1, 0), int2(0, -1), false, color); } if (any(leftVertical > 0)) { - BlendEdge(leftVertical, left, int2(-1, 0), int2(0, -1), true, color); + BlendEdgePass3(leftVertical, left, int2(-1, 0), int2(0, -1), true, color); } return color; diff --git a/src/Effects/NNEDI3/NNEDI3_nns128_win8x4.hlsl b/src/Effects/NNEDI3/NNEDI3_nns128_win8x4.hlsl index b92930a72..3321edab3 100644 --- a/src/Effects/NNEDI3/NNEDI3_nns128_win8x4.hlsl +++ b/src/Effects/NNEDI3/NNEDI3_nns128_win8x4.hlsl @@ -3951,7 +3951,7 @@ shared float inp[525]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/NNEDI3/NNEDI3_nns128_win8x6.hlsl b/src/Effects/NNEDI3/NNEDI3_nns128_win8x6.hlsl index eea99b9a0..7e243f5fd 100644 --- a/src/Effects/NNEDI3/NNEDI3_nns128_win8x6.hlsl +++ b/src/Effects/NNEDI3/NNEDI3_nns128_win8x6.hlsl @@ -5646,7 +5646,7 @@ shared float inp[555]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/NNEDI3/NNEDI3_nns16_win8x4.hlsl b/src/Effects/NNEDI3/NNEDI3_nns16_win8x4.hlsl index 332ecf3a8..384752f5f 100644 --- a/src/Effects/NNEDI3/NNEDI3_nns16_win8x4.hlsl +++ b/src/Effects/NNEDI3/NNEDI3_nns16_win8x4.hlsl @@ -659,7 +659,7 @@ shared float inp[525]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/NNEDI3/NNEDI3_nns16_win8x6.hlsl b/src/Effects/NNEDI3/NNEDI3_nns16_win8x6.hlsl index 63a72ce62..4799ef105 100644 --- a/src/Effects/NNEDI3/NNEDI3_nns16_win8x6.hlsl +++ b/src/Effects/NNEDI3/NNEDI3_nns16_win8x6.hlsl @@ -864,7 +864,7 @@ shared float inp[555]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/NNEDI3/NNEDI3_nns256_win8x4.hlsl b/src/Effects/NNEDI3/NNEDI3_nns256_win8x4.hlsl index f87f33283..ad5ff4149 100644 --- a/src/Effects/NNEDI3/NNEDI3_nns256_win8x4.hlsl +++ b/src/Effects/NNEDI3/NNEDI3_nns256_win8x4.hlsl @@ -7807,7 +7807,7 @@ shared float inp[525]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/NNEDI3/NNEDI3_nns256_win8x6.hlsl b/src/Effects/NNEDI3/NNEDI3_nns256_win8x6.hlsl index 72b14b33e..951fba454 100644 --- a/src/Effects/NNEDI3/NNEDI3_nns256_win8x6.hlsl +++ b/src/Effects/NNEDI3/NNEDI3_nns256_win8x6.hlsl @@ -11143,7 +11143,7 @@ shared float inp[555]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/NNEDI3/NNEDI3_nns32_win8x4.hlsl b/src/Effects/NNEDI3/NNEDI3_nns32_win8x4.hlsl index 9176e3041..68e351c31 100644 --- a/src/Effects/NNEDI3/NNEDI3_nns32_win8x4.hlsl +++ b/src/Effects/NNEDI3/NNEDI3_nns32_win8x4.hlsl @@ -1120,7 +1120,7 @@ shared float inp[525]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/NNEDI3/NNEDI3_nns32_win8x6.hlsl b/src/Effects/NNEDI3/NNEDI3_nns32_win8x6.hlsl index b5a85a38c..4d6a27e42 100644 --- a/src/Effects/NNEDI3/NNEDI3_nns32_win8x6.hlsl +++ b/src/Effects/NNEDI3/NNEDI3_nns32_win8x6.hlsl @@ -1554,7 +1554,7 @@ shared float inp[555]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/NNEDI3/NNEDI3_nns64_win8x4.hlsl b/src/Effects/NNEDI3/NNEDI3_nns64_win8x4.hlsl index 146aa6ed0..25300b657 100644 --- a/src/Effects/NNEDI3/NNEDI3_nns64_win8x4.hlsl +++ b/src/Effects/NNEDI3/NNEDI3_nns64_win8x4.hlsl @@ -2038,7 +2038,7 @@ shared float inp[525]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/NNEDI3/NNEDI3_nns64_win8x6.hlsl b/src/Effects/NNEDI3/NNEDI3_nns64_win8x6.hlsl index fb394f929..23861e58f 100644 --- a/src/Effects/NNEDI3/NNEDI3_nns64_win8x6.hlsl +++ b/src/Effects/NNEDI3/NNEDI3_nns64_win8x6.hlsl @@ -2915,7 +2915,7 @@ shared float inp[555]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/Pixel Art/MMPX.hlsl b/src/Effects/Pixel Art/MMPX.hlsl index 6bce8b6df..e86b682fb 100644 --- a/src/Effects/Pixel Art/MMPX.hlsl +++ b/src/Effects/Pixel Art/MMPX.hlsl @@ -64,6 +64,11 @@ void Pass1(uint2 blockStart, uint3 threadId) { float srcX = (gxy.x >> 1) + 0.5f; float srcY = (gxy.y >> 1) + 0.5f; + // MMPX makes RGB-only decisions. Preserve the source pixel alpha across + // the four generated subpixels instead of manufacturing an opaque result. + #ifdef MP_HDR_COMPATIBILITY + const float sourceAlpha = INPUT.SampleLevel(sam, float2(srcX, srcY) * GetInputPt(), 0).a; + #endif float3 A = src(srcX - 1, srcY - 1), B = src(srcX, srcY - 1), C = src(srcX + 1, srcY - 1); float3 D = src(srcX - 1, srcY + 0), E = src(srcX, srcY + 0), F = src(srcX + 1, srcY + 0); @@ -119,14 +124,14 @@ void Pass1(uint2 blockStart, uint3 threadId) { } // not constant // Write four pixels at once - OUTPUT[gxy] = float4(J, 1); + OUTPUT[gxy] = float4(J, MP_HDR_ALPHA); ++gxy.x; - OUTPUT[gxy] = float4(K, 1); + OUTPUT[gxy] = float4(K, MP_HDR_ALPHA); ++gxy.y; - OUTPUT[gxy] = float4(M, 1); + OUTPUT[gxy] = float4(M, MP_HDR_ALPHA); --gxy.x; - OUTPUT[gxy] = float4(L, 1); + OUTPUT[gxy] = float4(L, MP_HDR_ALPHA); } diff --git a/src/Effects/Pixel Art/Pixellate.hlsl b/src/Effects/Pixel Art/Pixellate.hlsl index ded35889c..e95166ab8 100644 --- a/src/Effects/Pixel Art/Pixellate.hlsl +++ b/src/Effects/Pixel Art/Pixellate.hlsl @@ -30,20 +30,43 @@ float4 Pass1(float2 pos) { float right = pos.x + range.x; float bottom = pos.y - range.y; + #ifdef MP_HDR_COMPATIBILITY + float4 topLeft = INPUT.SampleLevel(sam, (floor(float2(left, top) / texelSize) + 0.5) * texelSize, 0); + float4 bottomRight = INPUT.SampleLevel(sam, (floor(float2(right, bottom) / texelSize) + 0.5) * texelSize, 0); + float4 bottomLeft = INPUT.SampleLevel(sam, (floor(float2(left, bottom) / texelSize) + 0.5) * texelSize, 0); + float4 topRight = INPUT.SampleLevel(sam, (floor(float2(right, top) / texelSize) + 0.5) * texelSize, 0); + #else float3 topLeftColor = INPUT.SampleLevel(sam, (floor(float2(left, top) / texelSize) + 0.5) * texelSize, 0).rgb; float3 bottomRightColor = INPUT.SampleLevel(sam, (floor(float2(right, bottom) / texelSize) + 0.5) * texelSize, 0).rgb; float3 bottomLeftColor = INPUT.SampleLevel(sam, (floor(float2(left, bottom) / texelSize) + 0.5) * texelSize, 0).rgb; float3 topRightColor = INPUT.SampleLevel(sam, (floor(float2(right, top) / texelSize) + 0.5) * texelSize, 0).rgb; + #endif float2 border = clamp(round(pos / texelSize) * texelSize, float2(left, bottom), float2(right, top)); float totalArea = 4.0 * range.x * range.y; float3 averageColor; + #ifdef MP_HDR_COMPATIBILITY + float averageAlpha; + const float topLeftWeight = (border.x - left) * (top - border.y) / totalArea; + const float bottomRightWeight = (right - border.x) * (border.y - bottom) / totalArea; + const float bottomLeftWeight = (border.x - left) * (border.y - bottom) / totalArea; + const float topRightWeight = (right - border.x) * (top - border.y) / totalArea; + averageColor = topLeftWeight * topLeft.rgb; + averageColor += bottomRightWeight * bottomRight.rgb; + averageColor += bottomLeftWeight * bottomLeft.rgb; + averageColor += topRightWeight * topRight.rgb; + averageAlpha = topLeftWeight * topLeft.a; + averageAlpha += bottomRightWeight * bottomRight.a; + averageAlpha += bottomLeftWeight * bottomLeft.a; + averageAlpha += topRightWeight * topRight.a; + return float4(averageColor, saturate(averageAlpha)); + #else averageColor = ((border.x - left) * (top - border.y) / totalArea) * topLeftColor; averageColor += ((right - border.x) * (border.y - bottom) / totalArea) * bottomRightColor; averageColor += ((border.x - left) * (border.y - bottom) / totalArea) * bottomLeftColor; averageColor += ((right - border.x) * (top - border.y) / totalArea) * topRightColor; - return float4(averageColor, 1.0); + #endif } diff --git a/src/Effects/RAVU/RAVU_3x_R2.hlsl b/src/Effects/RAVU/RAVU_3x_R2.hlsl index c834cc986..88942bdd7 100644 --- a/src/Effects/RAVU/RAVU_3x_R2.hlsl +++ b/src/Effects/RAVU/RAVU_3x_R2.hlsl @@ -62,7 +62,7 @@ shared float inp[340]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/RAVU/RAVU_3x_R3.hlsl b/src/Effects/RAVU/RAVU_3x_R3.hlsl index e78eb9260..cd2a055bb 100644 --- a/src/Effects/RAVU/RAVU_3x_R3.hlsl +++ b/src/Effects/RAVU/RAVU_3x_R3.hlsl @@ -62,7 +62,7 @@ shared float inp[432]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/RAVU/RAVU_3x_R4.hlsl b/src/Effects/RAVU/RAVU_3x_R4.hlsl index 97b1e223b..39089bc9b 100644 --- a/src/Effects/RAVU/RAVU_3x_R4.hlsl +++ b/src/Effects/RAVU/RAVU_3x_R4.hlsl @@ -62,7 +62,7 @@ shared float inp[532]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/RAVU/RAVU_Lite_AR_R2.hlsl b/src/Effects/RAVU/RAVU_Lite_AR_R2.hlsl index 473527c6b..fd3172d24 100644 --- a/src/Effects/RAVU/RAVU_Lite_AR_R2.hlsl +++ b/src/Effects/RAVU/RAVU_Lite_AR_R2.hlsl @@ -62,7 +62,7 @@ shared float inp[340]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/RAVU/RAVU_Lite_AR_R3.hlsl b/src/Effects/RAVU/RAVU_Lite_AR_R3.hlsl index 0f69785d8..48dc0b4b0 100644 --- a/src/Effects/RAVU/RAVU_Lite_AR_R3.hlsl +++ b/src/Effects/RAVU/RAVU_Lite_AR_R3.hlsl @@ -62,7 +62,7 @@ shared float inp[432]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/RAVU/RAVU_Lite_AR_R4.hlsl b/src/Effects/RAVU/RAVU_Lite_AR_R4.hlsl index 243b46d0d..6c4b0fffa 100644 --- a/src/Effects/RAVU/RAVU_Lite_AR_R4.hlsl +++ b/src/Effects/RAVU/RAVU_Lite_AR_R4.hlsl @@ -62,7 +62,7 @@ shared float inp[532]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/RAVU/RAVU_Lite_R2.hlsl b/src/Effects/RAVU/RAVU_Lite_R2.hlsl index cb62cb20c..f79efd2bd 100644 --- a/src/Effects/RAVU/RAVU_Lite_R2.hlsl +++ b/src/Effects/RAVU/RAVU_Lite_R2.hlsl @@ -62,7 +62,7 @@ shared float inp[340]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/RAVU/RAVU_Lite_R3.hlsl b/src/Effects/RAVU/RAVU_Lite_R3.hlsl index 467a5b112..991c56724 100644 --- a/src/Effects/RAVU/RAVU_Lite_R3.hlsl +++ b/src/Effects/RAVU/RAVU_Lite_R3.hlsl @@ -62,7 +62,7 @@ shared float inp[432]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/RAVU/RAVU_Lite_R4.hlsl b/src/Effects/RAVU/RAVU_Lite_R4.hlsl index ab6db274e..1556f8cd3 100644 --- a/src/Effects/RAVU/RAVU_Lite_R4.hlsl +++ b/src/Effects/RAVU/RAVU_Lite_R4.hlsl @@ -62,7 +62,7 @@ shared float inp[532]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/RAVU/RAVU_R2.hlsl b/src/Effects/RAVU/RAVU_R2.hlsl index 4ffc8faa5..5e4f8aa2f 100644 --- a/src/Effects/RAVU/RAVU_R2.hlsl +++ b/src/Effects/RAVU/RAVU_R2.hlsl @@ -210,7 +210,7 @@ shared float inp1[385]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/RAVU/RAVU_R3.hlsl b/src/Effects/RAVU/RAVU_R3.hlsl index e4b847055..4169c9628 100644 --- a/src/Effects/RAVU/RAVU_R3.hlsl +++ b/src/Effects/RAVU/RAVU_R3.hlsl @@ -239,7 +239,7 @@ shared float inp1[481]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/RAVU/RAVU_R4.hlsl b/src/Effects/RAVU/RAVU_R4.hlsl index 79104920f..f5ad8e746 100644 --- a/src/Effects/RAVU/RAVU_R4.hlsl +++ b/src/Effects/RAVU/RAVU_R4.hlsl @@ -344,7 +344,7 @@ shared float inp1[585]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/RAVU/RAVU_Zoom_AR_R2.hlsl b/src/Effects/RAVU/RAVU_Zoom_AR_R2.hlsl index dba8b22ec..8e0e81008 100644 --- a/src/Effects/RAVU/RAVU_Zoom_AR_R2.hlsl +++ b/src/Effects/RAVU/RAVU_Zoom_AR_R2.hlsl @@ -72,7 +72,7 @@ shared float samples[432]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/RAVU/RAVU_Zoom_AR_R3.hlsl b/src/Effects/RAVU/RAVU_Zoom_AR_R3.hlsl index 99ba60220..a0eb93230 100644 --- a/src/Effects/RAVU/RAVU_Zoom_AR_R3.hlsl +++ b/src/Effects/RAVU/RAVU_Zoom_AR_R3.hlsl @@ -72,7 +72,7 @@ shared float samples[532]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/RAVU/RAVU_Zoom_R2.hlsl b/src/Effects/RAVU/RAVU_Zoom_R2.hlsl index 408a39da0..a2170de4a 100644 --- a/src/Effects/RAVU/RAVU_Zoom_R2.hlsl +++ b/src/Effects/RAVU/RAVU_Zoom_R2.hlsl @@ -63,7 +63,7 @@ shared float samples[432]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/RAVU/RAVU_Zoom_R3.hlsl b/src/Effects/RAVU/RAVU_Zoom_R3.hlsl index 8a9b9b8c0..44a81f73c 100644 --- a/src/Effects/RAVU/RAVU_Zoom_R3.hlsl +++ b/src/Effects/RAVU/RAVU_Zoom_R3.hlsl @@ -63,7 +63,7 @@ shared float samples[532]; #define imageStore(out_image, pos, val) imageStoreOverride(pos, val.x) void imageStoreOverride(uint2 pos, float value) { float2 UV = mul(rgb2uv, INPUT.SampleLevel(sam_INPUT_LINEAR, HOOKED_map(pos), 0).rgb); - OUTPUT[pos] = float4(mul(yuv2rgb, float3(value.x, UV)), 1.0); + OUTPUT[pos] = float4(MP_HDR_SATURATE(mul(yuv2rgb, float3(value.x, UV))), 1.0); } #define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos))) diff --git a/src/Effects/xBRZ/xBRZ_2x.hlsl b/src/Effects/xBRZ/xBRZ_2x.hlsl index b6a066709..ca8ea132e 100644 --- a/src/Effects/xBRZ/xBRZ_2x.hlsl +++ b/src/Effects/xBRZ/xBRZ_2x.hlsl @@ -98,6 +98,9 @@ void Pass1(uint2 blockStart, uint3 threadId) { const float2 inputPt = GetInputPt(); const float2 pos = ((gxy >> 1) + 0.5f) * inputPt; + #ifdef MP_HDR_COMPATIBILITY + const float sourceAlpha = INPUT.SampleLevel(sam, pos, 0).a; + #endif // A1 B1 C1 // A0 A B C C4 @@ -287,14 +290,14 @@ void Pass1(uint2 blockStart, uint3 threadId) { dst[0] = tempDst3; } - OUTPUT[gxy] = float4(dst[0], 1); + OUTPUT[gxy] = float4(dst[0], MP_HDR_ALPHA); ++gxy.x; - OUTPUT[gxy] = float4(dst[1], 1); + OUTPUT[gxy] = float4(dst[1], MP_HDR_ALPHA); ++gxy.y; - OUTPUT[gxy] = float4(dst[2], 1); + OUTPUT[gxy] = float4(dst[2], MP_HDR_ALPHA); --gxy.x; - OUTPUT[gxy] = float4(dst[3], 1); + OUTPUT[gxy] = float4(dst[3], MP_HDR_ALPHA); } diff --git a/src/Effects/xBRZ/xBRZ_3x.hlsl b/src/Effects/xBRZ/xBRZ_3x.hlsl index 52eb53560..12c81fc47 100644 --- a/src/Effects/xBRZ/xBRZ_3x.hlsl +++ b/src/Effects/xBRZ/xBRZ_3x.hlsl @@ -109,6 +109,9 @@ void Pass1(uint2 blockStart, uint3 threadId) { const float2 inputPt = GetInputPt(); const float2 pos = ((gxy / 3) + 0.5f) * inputPt; + #ifdef MP_HDR_COMPATIBILITY + const float sourceAlpha = INPUT.SampleLevel(sam, pos, 0).a; + #endif // A1 B1 C1 // A0 A B C C4 @@ -328,7 +331,7 @@ void Pass1(uint2 blockStart, uint3 threadId) { for (uint i = 0; i < 3; ++i) { [unroll] for (uint j = 0; j < 3; ++j) { - OUTPUT[gxy + uint2(i, j)] = float4(dst[destIdx[j][i]], 1); + OUTPUT[gxy + uint2(i, j)] = float4(dst[destIdx[j][i]], MP_HDR_ALPHA); } } } diff --git a/src/Effects/xBRZ/xBRZ_4x.hlsl b/src/Effects/xBRZ/xBRZ_4x.hlsl index 79ab5377e..46426efca 100644 --- a/src/Effects/xBRZ/xBRZ_4x.hlsl +++ b/src/Effects/xBRZ/xBRZ_4x.hlsl @@ -83,6 +83,9 @@ void Pass1(uint2 blockStart, uint3 threadId) { const float2 inputPt = GetInputPt(); const float2 pos = ((gxy >> 2) + 0.5f) * inputPt; + #ifdef MP_HDR_COMPATIBILITY + const float sourceAlpha = INPUT.SampleLevel(sam, pos, 0).a; + #endif // A1 B1 C1 // A0 A B C C4 @@ -312,7 +315,7 @@ void Pass1(uint2 blockStart, uint3 threadId) { for (uint i = 0; i < 4; ++i) { [unroll] for (uint j = 0; j < 4; ++j) { - OUTPUT[gxy + uint2(i, j)] = float4(dst[destIdx[j][i]], 1); + OUTPUT[gxy + uint2(i, j)] = float4(dst[destIdx[j][i]], MP_HDR_ALPHA); } } } diff --git a/src/Effects/xBRZ/xBRZ_5x.hlsl b/src/Effects/xBRZ/xBRZ_5x.hlsl index a52a31fca..f6554ad27 100644 --- a/src/Effects/xBRZ/xBRZ_5x.hlsl +++ b/src/Effects/xBRZ/xBRZ_5x.hlsl @@ -85,6 +85,9 @@ void Pass1(uint2 blockStart, uint3 threadId) { const float2 inputPt = GetInputPt(); const float2 pos = ((gxy / 5) + 0.5f) * inputPt; + #ifdef MP_HDR_COMPATIBILITY + const float sourceAlpha = INPUT.SampleLevel(sam, pos, 0).a; + #endif // A1 B1 C1 // A0 A B C C4 @@ -339,7 +342,7 @@ void Pass1(uint2 blockStart, uint3 threadId) { for (uint i = 0; i < 5; ++i) { [unroll] for (uint j = 0; j < 5; ++j) { - OUTPUT[gxy + uint2(i, j)] = float4(dst[destIdx[j][i]], 1); + OUTPUT[gxy + uint2(i, j)] = float4(dst[destIdx[j][i]], MP_HDR_ALPHA); } } } diff --git a/src/Effects/xBRZ/xBRZ_6x.hlsl b/src/Effects/xBRZ/xBRZ_6x.hlsl index 03f20dd1e..4b58e7904 100644 --- a/src/Effects/xBRZ/xBRZ_6x.hlsl +++ b/src/Effects/xBRZ/xBRZ_6x.hlsl @@ -91,6 +91,9 @@ void Pass1(uint2 blockStart, uint3 threadId) { const float2 inputPt = GetInputPt(); const float2 pos = ((gxy / 6) + 0.5f) * inputPt; + #ifdef MP_HDR_COMPATIBILITY + const float sourceAlpha = INPUT.SampleLevel(sam, pos, 0).a; + #endif // A1 B1 C1 // A0 A B C C4 @@ -371,7 +374,7 @@ void Pass1(uint2 blockStart, uint3 threadId) { for (uint i = 0; i < 6; ++i) { [unroll] for (uint j = 0; j < 6; ++j) { - OUTPUT[gxy + uint2(i, j)] = float4(dst[destIdx[j][i]], 1); + OUTPUT[gxy + uint2(i, j)] = float4(dst[destIdx[j][i]], MP_HDR_ALPHA); } } } diff --git a/src/Effects/xBRZ/xBRZ_Freescale.hlsl b/src/Effects/xBRZ/xBRZ_Freescale.hlsl index b0afe789c..2add7b5c5 100644 --- a/src/Effects/xBRZ/xBRZ_Freescale.hlsl +++ b/src/Effects/xBRZ/xBRZ_Freescale.hlsl @@ -292,6 +292,9 @@ void Pass2(uint2 blockStart, uint3 threadId) { float2 inputPt = GetInputPt(); float2 outputPt = GetOutputPt(); float2 pos = (gxy + 0.5f) * outputPt; + #ifdef MP_HDR_COMPATIBILITY + const float sourceAlpha = INPUT.SampleLevel(sam, pos, 0).a; + #endif //--------------------------------------- // Input Pixel Mapping: -|B|- @@ -385,5 +388,5 @@ void Pass2(uint2 blockStart, uint3 threadId) { res = lerp(res, blendPix, get_left_ratio(f, origin, direction, scale)); } - OUTPUT[gxy] = float4(res, 1); + OUTPUT[gxy] = float4(res, MP_HDR_ALPHA); } diff --git a/src/Magpie.Core/AdaptivePresenter.cpp b/src/Magpie.Core/AdaptivePresenter.cpp index 128e02bdb..c8ba3a923 100644 --- a/src/Magpie.Core/AdaptivePresenter.cpp +++ b/src/Magpie.Core/AdaptivePresenter.cpp @@ -33,7 +33,9 @@ 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 = ScalingWindow::Get().Options().IsHdrCompatibilityEnabled() + ? DXGI_FORMAT_R16G16B16A16_FLOAT + : DXGI_FORMAT_R8G8B8A8_UNORM, .SampleDesc = { .Count = 1 }, @@ -68,12 +70,18 @@ bool AdaptivePresenter::_Initialize(HWND hwndAttach) noexcept { Logger::Get().ComError("创建交换链失败", hr); return false; } - _dxgiSwapChain = dxgiSwapChain.try_as(); if (!_dxgiSwapChain) { Logger::Get().Error("获取 IDXGISwapChain2 失败"); return false; } + if (ScalingWindow::Get().Options().IsHdrCompatibilityEnabled()) { + hr = _dxgiSwapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709); + if (FAILED(hr)) { + Logger::Get().ComError("设置 HDR 交换链色彩空间失败", hr); + return false; + } + } const auto& options = ScalingWindow::Get().Options(); uint32_t maximumFrameLatency = options.isFrontEdgeSyncEnabled && !options.IsBenchmarkMode() @@ -408,7 +416,9 @@ bool AdaptivePresenter::_ResizeDCompVisual(HWND hwndAttach) noexcept { hr = _dcompDevice->CreateVirtualSurface( (UINT)rendererSize.cx, (UINT)rendererSize.cy, - DXGI_FORMAT_R8G8B8A8_UNORM, + ScalingWindow::Get().Options().IsHdrCompatibilityEnabled() + ? DXGI_FORMAT_R16G16B16A16_FLOAT + : DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ALPHA_MODE_IGNORE, _dcompSurface.put() ); diff --git a/src/Magpie.Core/AmdOpticalFlowProvider.cpp b/src/Magpie.Core/AmdOpticalFlowProvider.cpp index 3403241a2..62f03d7cc 100644 --- a/src/Magpie.Core/AmdOpticalFlowProvider.cpp +++ b/src/Magpie.Core/AmdOpticalFlowProvider.cpp @@ -76,6 +76,9 @@ FrameGuidanceMetadata MakeMetadata( ) noexcept { return { .frameId = frame.frameId, + .captureSequence = frame.captureSequence, + .resourceGeneration = frame.resourceGeneration, + .timestamp100ns = frame.timestamp100ns, .sourceExtent = frame.sourceExtent, .validRegion = frame.validRegion, .resetReason = reason, @@ -496,6 +499,7 @@ struct AmdOpticalFlowProvider::Impl { FrameGuidanceExtent opticalFlowExtent{}; FrameGuidanceExtent sparseExtent{}; AmdOpticalFlowMode mode = AmdOpticalFlowMode::Quality; + AmdOpticalFlowHdrProtocol hdrProtocol{}; FrameGuidanceResetReason resetReason = FrameGuidanceResetReason::Initialize; uint64_t fenceValue = 0; uint64_t lastSubmittedValue = 0; @@ -515,6 +519,7 @@ bool AmdOpticalFlowProvider::Initialize( DeviceResources& resources, FrameGuidanceExtent sourceExtent ) noexcept { + _impl->hdrProtocol = _hdrProtocol; return _impl->Create(resources, sourceExtent, _mode); } @@ -556,8 +561,13 @@ bool AmdOpticalFlowProvider::BeginFrame( L"Magpie AMD OF SCD", FFX_API_RESOURCE_STATE_COMMON), .reset = !impl.historyValid || impl.resetReason != FrameGuidanceResetReason::None, - .backbufferTransferFunction = FFX_API_BACKBUFFER_TRANSFER_FUNCTION_SRGB, - .minMaxLuminance = { 0.0f, 1.0f } + .backbufferTransferFunction = +#ifdef FFX_API_BACKBUFFER_TRANSFER_FUNCTION_LINEAR + impl.hdrProtocol.transfer == GroupBTransfer::Linear ? + FFX_API_BACKBUFFER_TRANSFER_FUNCTION_LINEAR : +#endif + FFX_API_BACKBUFFER_TRANSFER_FUNCTION_SRGB, + .minMaxLuminance = { impl.hdrProtocol.minMaxLuminance[0], impl.hdrProtocol.minMaxLuminance[1] } }; const auto opticalFlowStart = std::chrono::steady_clock::now(); if (ffxOpticalflowContextDispatch( diff --git a/src/Magpie.Core/AmdOpticalFlowProvider.h b/src/Magpie.Core/AmdOpticalFlowProvider.h index 1501b4da4..794a63fd4 100644 --- a/src/Magpie.Core/AmdOpticalFlowProvider.h +++ b/src/Magpie.Core/AmdOpticalFlowProvider.h @@ -1,5 +1,6 @@ #pragma once #include "FrameGuidanceProvider.h" +#include "GroupBEffectProtocol.h" namespace Magpie { @@ -27,10 +28,12 @@ class AmdOpticalFlowProvider final : public IMotionVectorProvider { void Reset(FrameGuidanceResetReason reason) noexcept override; bool Resize(FrameGuidanceExtent sourceExtent) noexcept override; OpticalFlowInitializationError InitializationError() const noexcept override; + void SetHdrProtocol(const AmdOpticalFlowHdrProtocol& protocol) noexcept { _hdrProtocol = protocol; } private: AmdOpticalFlowMode _mode; std::unique_ptr _impl; + AmdOpticalFlowHdrProtocol _hdrProtocol{}; }; } diff --git a/src/Magpie.Core/CompSwapchainPresenter.cpp b/src/Magpie.Core/CompSwapchainPresenter.cpp index 390a7d6d2..d81f26fff 100644 --- a/src/Magpie.Core/CompSwapchainPresenter.cpp +++ b/src/Magpie.Core/CompSwapchainPresenter.cpp @@ -147,7 +147,9 @@ 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().IsHdrCompatibilityEnabled() + ? 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 2f4895646..2a9d6eb3a 100644 --- a/src/Magpie.Core/DLSSNRFilter.cpp +++ b/src/Magpie.Core/DLSSNRFilter.cpp @@ -10,7 +10,7 @@ namespace Magpie { -DLSSNRSettings ParseDLSSNRSettings(const EffectOption& option) noexcept { +DLSSNRSettings ParseDLSSNRSettings(const EffectOption& option, bool hdrEnabled) noexcept { auto getParameter = [&](std::string_view name, float defaultValue) noexcept { auto it = option.parameters.find(std::string(name)); return it != option.parameters.end() && std::isfinite(it->second) @@ -27,6 +27,8 @@ DLSSNRSettings ParseDLSSNRSettings(const EffectOption& option) noexcept { static_cast(motionQualityValue) : NvidiaOpticalFlowQuality::Balanced; + const float hdrScale = hdrEnabled ? getParameter("experimentalHdrScale", 1.0f) : 1.0f; + const bool hdrPath = hdrEnabled && getParameter("experimentalHdrPath", 0.0f) >= 0.5f; return DLSSNRSettings{ .enableInputResolutionScaling = getParameter("enableInputResolutionScaling", 0.0f) >= 0.5f, @@ -50,7 +52,14 @@ DLSSNRSettings ParseDLSSNRSettings(const EffectOption& option) noexcept { "skinStructureStrength", -1.0f, -1.0f, 2.0f), .useAutoMask = getParameter("useAutoMask", 0.0f) >= 0.5f, .uiCorrection = getParameter("uiCorrection", 0.0f) >= 0.5f, - .motionVectorQuality = motionQuality + .motionVectorQuality = motionQuality, + .experimentalHdr = DlssnrExperimentProtocol{ + .enabled = hdrPath && std::isfinite(hdrScale) && + (hdrScale == 1.0f || hdrScale == 2.0f || hdrScale == 4.5f), + .scale = (std::isfinite(hdrScale) && + (hdrScale == 1.0f || hdrScale == 2.0f || hdrScale == 4.5f)) ? + hdrScale : 1.0f + } }; } @@ -680,6 +689,8 @@ struct DLSSNRFilter::Impl { uint32_t width = 0; uint32_t height = 0; bool convertInputToRgba = false; + bool experimentalHdrPath = false; + float experimentalHdrScale = 1.0f; bool useResolutionScaling = false; bool coreRegistered = false; bool snippetInitialized = false; @@ -1813,7 +1824,9 @@ bool DLSSNRFilter::ApplyLiveParameters( return false; } - const DLSSNRSettings candidate = ParseDLSSNRSettings(option); + // Preserve the active HDR protocol while validating live SDR parameters. + const DLSSNRSettings candidate = ParseDLSSNRSettings( + option, _settings.experimentalHdr.enabled); if (candidate.enableInputResolutionScaling != _settings.enableInputResolutionScaling || candidate.inputResolutionPercent != _settings.inputResolutionPercent || @@ -1896,6 +1909,10 @@ bool DLSSNRFilter::Initialize( D3D11_TEXTURE2D_DESC outputDesc{}; input->GetDesc(&inputDesc); output->GetDesc(&outputDesc); + const bool experimentalHdrPath = settings.experimentalHdr.enabled && + settings.experimentalHdr.IsVerifiedScale() && + inputDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT && + outputDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT; if (inputDesc.Width != outputDesc.Width || inputDesc.Height != outputDesc.Height) { Logger::Get().Error(fmt::format( "DLSSNR requires same-resolution input/output: {}x{} -> {}x{}", @@ -1904,7 +1921,8 @@ bool DLSSNRFilter::Initialize( } const bool supportedInput = inputDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM || inputDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM; - if (!supportedInput || outputDesc.Format != DXGI_FORMAT_R8G8B8A8_UNORM) { + if ((!supportedInput || outputDesc.Format != DXGI_FORMAT_R8G8B8A8_UNORM) && + !experimentalHdrPath) { Logger::Get().Error(fmt::format( "DLSSNR SDR path unsupported formats: input={}, output={}", (uint32_t)inputDesc.Format, (uint32_t)outputDesc.Format)); @@ -1912,7 +1930,11 @@ bool DLSSNRFilter::Initialize( } impl->sourceWidth = inputDesc.Width; impl->sourceHeight = inputDesc.Height; - impl->useResolutionScaling = settings.enableInputResolutionScaling; + impl->experimentalHdrPath = experimentalHdrPath; + impl->experimentalHdrScale = settings.experimentalHdr.scale; + // Resolution scaling and residual reconstruction are SDR RGBA8 features. + // The experimental FP16 route keeps the tested same-resolution call chain. + impl->useResolutionScaling = !experimentalHdrPath && settings.enableInputResolutionScaling; const uint32_t resolutionPercent = std::clamp( settings.inputResolutionPercent, 25u, 100u); impl->width = impl->useResolutionScaling ? std::max( @@ -1949,7 +1971,8 @@ bool DLSSNRFilter::Initialize( } D3D11_TEXTURE2D_DESC sharedDesc = outputDesc; - sharedDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + sharedDesc.Format = experimentalHdrPath ? + DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM; sharedDesc.Width = impl->width; sharedDesc.Height = impl->height; if (!CreateSharedTexture(*impl, sharedDesc, true, @@ -2127,7 +2150,8 @@ bool DLSSNRFilter::Initialize( "residualSaturation={} residualLightness={} shadowStructureMultiplier={} " "reflectionGlowMultiplier={} preset=fixed-0 " "style={} intensity={} localTone={} localStructure={} skinStructure={} " - "motionVectorQuality={} autoMask={} uiCorrection={} depth=zero-contract disabled=false", + "motionVectorQuality={} autoMask={} uiCorrection={} depth=zero-contract disabled=false " + "experimentalHdrPath={} experimentalHdrScale={}", ENABLE_CORE_FEATURE18_DIAGNOSTIC ? "core-diagnostic" : "signed-snippet", impl->sourceWidth, impl->sourceHeight, static_cast(inputDesc.Format), impl->width, impl->height, @@ -2139,7 +2163,8 @@ bool DLSSNRFilter::Initialize( _settings.intensity, _settings.localToneStrength, _settings.localStructureStrength, _settings.skinStructureStrength, static_cast(_settings.motionVectorQuality), - _settings.useAutoMask, _settings.uiCorrection)); + _settings.useAutoMask, _settings.uiCorrection, + impl->experimentalHdrPath, impl->experimentalHdrScale)); _impl = std::move(impl); return true; } diff --git a/src/Magpie.Core/DLSSNRFilter.h b/src/Magpie.Core/DLSSNRFilter.h index 522b82db7..b390403df 100644 --- a/src/Magpie.Core/DLSSNRFilter.h +++ b/src/Magpie.Core/DLSSNRFilter.h @@ -1,5 +1,6 @@ #pragma once #include "NativeEffectBackend.h" +#include "GroupBEffectProtocol.h" namespace Magpie { @@ -23,9 +24,11 @@ struct DLSSNRSettings { bool uiCorrection = false; NvidiaOpticalFlowQuality motionVectorQuality = NvidiaOpticalFlowQuality::Balanced; + // Experimental FP16 path. SDR RGBA8 remains the default. + DlssnrExperimentProtocol experimentalHdr{}; }; -DLSSNRSettings ParseDLSSNRSettings(const EffectOption& option) noexcept; +DLSSNRSettings ParseDLSSNRSettings(const EffectOption& option, bool hdrEnabled = false) noexcept; // Experimental same-resolution DLSS neural filter. Magpie only owns the // composited colour frame, so valid zero-filled motion/depth textures are used diff --git a/src/Magpie.Core/DLSSSRUpscaler.cpp b/src/Magpie.Core/DLSSSRUpscaler.cpp index bc0286cc5..0f05814bf 100644 --- a/src/Magpie.Core/DLSSSRUpscaler.cpp +++ b/src/Magpie.Core/DLSSSRUpscaler.cpp @@ -272,8 +272,8 @@ bool DLSSSRUpscaler::Draw(const NativeEffectDrawContext& context) noexcept { evalParams.InReset = _resetHistory || guidanceReset ? 1 : 0; evalParams.InMVScaleX = 1.0f; evalParams.InMVScaleY = 1.0f; - evalParams.InPreExposure = 1.0f; - evalParams.InExposureScale = 1.0f; + evalParams.InPreExposure = _hdrProtocol.preExposure; + evalParams.InExposureScale = _hdrProtocol.exposure; const NVSDK_NGX_Result result = NGX_D3D11_EVALUATE_DLSS_EXT( _d3dDC, diff --git a/src/Magpie.Core/DLSSSRUpscaler.h b/src/Magpie.Core/DLSSSRUpscaler.h index 44e254057..3beb4df7e 100644 --- a/src/Magpie.Core/DLSSSRUpscaler.h +++ b/src/Magpie.Core/DLSSSRUpscaler.h @@ -1,5 +1,6 @@ #pragma once #include "NativeEffectBackend.h" +#include "GroupBEffectProtocol.h" namespace Magpie { @@ -48,6 +49,7 @@ class DLSSSRUpscaler final : public NativeEffectBackend { ) noexcept override; bool Draw(const NativeEffectDrawContext& context) noexcept override; + void SetDlssHdrProtocol(const FsrHdrProtocol& protocol) noexcept { _hdrProtocol = protocol; } private: void _Reset() noexcept; @@ -68,6 +70,7 @@ class DLSSSRUpscaler final : public NativeEffectBackend { uint8_t _lastGuidanceBinding = UINT8_MAX; FrameGuidanceFrameId _lastGuidanceResetFrameId = std::numeric_limits::max(); + FsrHdrProtocol _hdrProtocol{}; }; } diff --git a/src/Magpie.Core/DesktopDuplicationFrameSource.cpp b/src/Magpie.Core/DesktopDuplicationFrameSource.cpp index 31463b550..d6c34a58c 100644 --- a/src/Magpie.Core/DesktopDuplicationFrameSource.cpp +++ b/src/Magpie.Core/DesktopDuplicationFrameSource.cpp @@ -9,6 +9,7 @@ namespace Magpie { + static winrt::com_ptr FindMonitor(IDXGIAdapter1* adapter, HMONITOR hMonitor) noexcept { winrt::com_ptr output; diff --git a/src/Magpie.Core/EffectCompiler.cpp b/src/Magpie.Core/EffectCompiler.cpp index d091750e0..f18359aa2 100644 --- a/src/Magpie.Core/EffectCompiler.cpp +++ b/src/Magpie.Core/EffectCompiler.cpp @@ -1623,6 +1623,13 @@ static uint32_t CompilePasses( Logger::Get().Error(fmt::format("生成 Pass{} 失败", id + 1)); return; } + macros.emplace_back("MP_HDR_SATURATE", + (flags & EffectCompilerFlags::HdrCompatibility) ? "saturate" : ""); + macros.emplace_back("MP_HDR_ALPHA", + (flags & EffectCompilerFlags::HdrCompatibility) ? "sourceAlpha" : "1.0"); + if (flags & EffectCompilerFlags::HdrCompatibility) { + macros.emplace_back("MP_HDR_COMPATIBILITY", "1"); + } if (flags & EffectCompilerFlags::SaveSources) { std::wstring fileName = desc.passes.size() == 1 @@ -1850,6 +1857,19 @@ uint32_t EffectCompiler::Compile( return 1; } } + // Route-aware primary surfaces must be applied before pass source + // generation. GeneratePassSource derives typed SRV/UAV declarations from + // these descriptors, so runtime route metadata and CSO contracts stay + // identical. + const auto applySurfaceFormat = [&](uint32_t shift, EffectIntermediateTextureDesc& surface) { + const uint32_t encoded = (flags >> shift) & EffectCompilerFlags::SurfaceFormatMask; + if (encoded == 0) return; + const uint32_t formatIndex = encoded - 1; + if (formatIndex < std::size(EffectHelper::FORMAT_DESCS) - 1) + surface.format = static_cast(formatIndex); + }; + applySurfaceFormat(EffectCompilerFlags::InputFormatShift, desc.textures[0]); + applySurfaceFormat(EffectCompilerFlags::OutputFormatShift, desc.textures[1]); if (!noCompile) { desc.samplers.clear(); diff --git a/src/Magpie.Core/EffectDrawer.cpp b/src/Magpie.Core/EffectDrawer.cpp index bb07d9d58..fe2de9375 100644 --- a/src/Magpie.Core/EffectDrawer.cpp +++ b/src/Magpie.Core/EffectDrawer.cpp @@ -36,6 +36,9 @@ EffectDrawer::~EffectDrawer() { for (uint32_t i = 1; i < textureCount; ++i) { _descriptorStore->RemoveCache(_textures[i].get()); } + if (_hdrOutput && (_textures.size() < 2 || _hdrOutput.get() != _textures[1].get())) { + _descriptorStore->RemoveCache(_hdrOutput.get()); + } } bool EffectDrawer::Initialize( @@ -47,6 +50,11 @@ bool EffectDrawer::Initialize( ) noexcept { _d3dDC = deviceResources.GetD3DDC(); _descriptorStore = &descriptorStore; + _hdrEnabled = ScalingWindow::Get().Options().IsHdrCompatibilityEnabled(); + if (_hdrEnabled && !_hdrSurfaceAdapter.Initialize(deviceResources, descriptorStore)) { + Logger::Get().Error("初始化 HDR 效果边界适配器失败"); + return false; + } SIZE inputSize{}; { @@ -78,12 +86,28 @@ bool EffectDrawer::Initialize( // 创建中间纹理 // 第一个为 INPUT,第二个为 OUTPUT _textures.resize(desc.textures.size()); - _textures[0].copy_from(*inOutTexture); + _hdrInputSource = *inOutTexture; + D3D11_TEXTURE2D_DESC sourceDesc{}; + (*inOutTexture)->GetDesc(&sourceDesc); + const DXGI_FORMAT effectInputFormat = + _GetHdrInputFormat(desc); + if (!_hdrEnabled) { + _textures[0].copy_from(*inOutTexture); + } else { + _textures[0] = DirectXHelper::CreateTexture2D( + deviceResources.GetD3DDevice(), effectInputFormat, + sourceDesc.Width, sourceDesc.Height, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS); + if (!_textures[0]) { + Logger::Get().Error("创建 HDR 效果输入纹理失败"); + return false; + } + } - // 创建输出纹理,格式始终是 DXGI_FORMAT_R8G8B8A8_UNORM + // 创建效果内部输出纹理;HDR 模式另建 canonical FP16 输出。 _textures[1] = DirectXHelper::CreateTexture2D( deviceResources.GetD3DDevice(), - EffectHelper::FORMAT_DESCS[(uint32_t)desc.textures[1].format].dxgiFormat, + _GetHdrOutputFormat(desc), outputSize.cx, outputSize.cy, D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS @@ -97,6 +121,23 @@ bool EffectDrawer::Initialize( if (!ClearEffectTexture(_d3dDC, descriptorStore, _textures[1].get())) { return false; } + if (_hdrEnabled) { + const D3D11_TEXTURE2D_DESC outputDesc = [&]() { + D3D11_TEXTURE2D_DESC result{}; + _textures[1]->GetDesc(&result); + return result; + }(); + _hdrOutput = DirectXHelper::CreateTexture2D( + deviceResources.GetD3DDevice(), + DXGI_FORMAT_R16G16B16A16_FLOAT, + outputDesc.Width, outputDesc.Height, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS); + if (!_hdrOutput || !ClearEffectTexture(_d3dDC, descriptorStore, _hdrOutput.get())) { + Logger::Get().Error("创建 HDR 效果输出纹理失败"); + return false; + } + *inOutTexture = _hdrOutput.get(); + } for (size_t i = 2; i < desc.textures.size(); ++i) { const EffectIntermediateTextureDesc& texDesc = desc.textures[i]; @@ -191,15 +232,26 @@ bool EffectDrawer::Initialize( } void EffectDrawer::Draw(EffectsProfiler& profiler) const noexcept { + if (!PrepareHdrInput()) { + Logger::Get().Error("准备 HDR 效果输入失败"); + return; + } _PrepareForDraw(); for (uint32_t i = 0; i < _dispatches.size(); ++i) { _DrawPass(i); profiler.OnEndPass(_d3dDC); } + if (!CompleteHdrOutput()) { + Logger::Get().Error("完成 HDR 效果输出失败"); + } } void EffectDrawer::DrawForExport(const EffectDesc& desc, uint32_t passIdx) const noexcept { + if (!PrepareHdrInput()) { + Logger::Get().Error("准备 HDR 导出输入失败"); + return; + } _PrepareForDraw(); for (uint32_t i : _CalcPassesToDrawForExport(desc, passIdx)) { @@ -215,17 +267,35 @@ bool EffectDrawer::ResizeTextures( ) noexcept { bool anyChange = false; - if (*inOutTexture != _textures[0].get()) { - _textures[0].copy_from(*inOutTexture); - anyChange = true; - } - SIZE inputSize{}; - { - D3D11_TEXTURE2D_DESC inputDesc; - _textures[0]->GetDesc(&inputDesc); - inputSize = { (LONG)inputDesc.Width, (LONG)inputDesc.Height }; + D3D11_TEXTURE2D_DESC inputDesc{}; + (*inOutTexture)->GetDesc(&inputDesc); + _hdrInputSource = *inOutTexture; + if (!_hdrEnabled) { + if (*inOutTexture != _textures[0].get()) { + _textures[0].copy_from(*inOutTexture); + anyChange = true; + } + } else { + const DXGI_FORMAT effectInputFormat = + _GetHdrInputFormat(desc); + D3D11_TEXTURE2D_DESC currentInput{}; + if (_textures[0]) _textures[0]->GetDesc(¤tInput); + if (!_textures[0] || currentInput.Format != effectInputFormat || + currentInput.Width != inputDesc.Width || currentInput.Height != inputDesc.Height) { + if (_textures[0]) _descriptorStore->RemoveCache(_textures[0].get()); + _textures[0] = DirectXHelper::CreateTexture2D( + deviceResources.GetD3DDevice(), effectInputFormat, + inputDesc.Width, inputDesc.Height, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS); + if (!_textures[0]) { + Logger::Get().Error("调整 HDR 效果输入纹理失败"); + return false; + } + anyChange = true; + } } + inputSize = { (LONG)inputDesc.Width, (LONG)inputDesc.Height }; const SIZE outputSize = _CalcOutputSize(desc, option, inputSize); if (outputSize.cx <= 0 || outputSize.cy <= 0) { @@ -238,10 +308,14 @@ bool EffectDrawer::ResizeTextures( if ((LONG)texDesc.Width != outputSize.cx || (LONG)texDesc.Height != outputSize.cy) { _descriptorStore->RemoveCache(_textures[1].get()); + if (_hdrOutput && _hdrOutput.get() != _textures[1].get()) { + _descriptorStore->RemoveCache(_hdrOutput.get()); + _hdrOutput = nullptr; + } _textures[1] = DirectXHelper::CreateTexture2D( deviceResources.GetD3DDevice(), - texDesc.Format, + _GetHdrOutputFormat(desc), outputSize.cx, outputSize.cy, texDesc.BindFlags @@ -258,7 +332,38 @@ bool EffectDrawer::ResizeTextures( anyChange = true; } - *inOutTexture = _textures[1].get(); + if (_hdrEnabled) { + if (!_hdrOutput) { + _hdrOutput = DirectXHelper::CreateTexture2D( + deviceResources.GetD3DDevice(), + DXGI_FORMAT_R16G16B16A16_FLOAT, + outputSize.cx, outputSize.cy, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS); + if (!_hdrOutput || !ClearEffectTexture(_d3dDC, *_descriptorStore, _hdrOutput.get())) { + Logger::Get().Error("调整 HDR 效果输出纹理失败"); + return false; + } + } else { + D3D11_TEXTURE2D_DESC hdrOutputDesc{}; + _hdrOutput->GetDesc(&hdrOutputDesc); + if (hdrOutputDesc.Width != (UINT)outputSize.cx || + hdrOutputDesc.Height != (UINT)outputSize.cy) { + _descriptorStore->RemoveCache(_hdrOutput.get()); + _hdrOutput = DirectXHelper::CreateTexture2D( + deviceResources.GetD3DDevice(), + DXGI_FORMAT_R16G16B16A16_FLOAT, + outputSize.cx, outputSize.cy, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS); + if (!_hdrOutput || !ClearEffectTexture(_d3dDC, *_descriptorStore, _hdrOutput.get())) { + Logger::Get().Error("调整 HDR 效果输出尺寸失败"); + return false; + } + } + } + *inOutTexture = _hdrOutput.get(); + } else { + *inOutTexture = _textures[1].get(); + } for (size_t i = 2; i < _textures.size(); ++i) { const std::pair& sizeExpr = desc.textures[i].sizeExpr; @@ -325,6 +430,162 @@ bool EffectDrawer::ResizeTextures( return true; } +bool EffectDrawer::_UsesDirectHdrPath() const noexcept { + if (!_hdrEnabled || !_hdrBoundary.prepared || _textures.size() < 2 || + !_textures[0] || !_textures[1]) { + return false; + } + + const HdrAdapterProfile profile = _hdrBoundary.plan.profile; + if (profile != HdrAdapterProfile::DirectFP16 && + profile != HdrAdapterProfile::ConditionalFP16 && + profile != HdrAdapterProfile::PresentationTerminal) { + return false; + } + + D3D11_TEXTURE2D_DESC inputDesc{}, outputDesc{}; + _textures[0]->GetDesc(&inputDesc); + _textures[1]->GetDesc(&outputDesc); + return inputDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT && + outputDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT; +} + +DXGI_FORMAT EffectDrawer::_GetHdrInputFormat(const EffectDesc& desc) const noexcept { + (void)desc; + if (!_hdrEnabled || !_hdrBoundary.prepared || !_hdrBoundary.SelectedRoute()) { + return EffectHelper::FORMAT_DESCS[(uint32_t)desc.textures[0].format].dxgiFormat; + } + const DXGI_FORMAT routeFormat = _hdrBoundary.SelectedRoute()->inputFormat; + return routeFormat == DXGI_FORMAT_UNKNOWN + ? EffectHelper::FORMAT_DESCS[(uint32_t)desc.textures[0].format].dxgiFormat + : routeFormat; +} + +DXGI_FORMAT EffectDrawer::_GetHdrOutputFormat(const EffectDesc& desc) const noexcept { + (void)desc; + if (!_hdrEnabled || !_hdrBoundary.prepared || !_hdrBoundary.SelectedRoute()) { + return EffectHelper::FORMAT_DESCS[(uint32_t)desc.textures[1].format].dxgiFormat; + } + const DXGI_FORMAT routeFormat = _hdrBoundary.SelectedRoute()->outputFormat; + return routeFormat == DXGI_FORMAT_UNKNOWN + ? EffectHelper::FORMAT_DESCS[(uint32_t)desc.textures[1].format].dxgiFormat + : routeFormat; +} + +HdrTransformParameters EffectDrawer::_GetHdrTransformParameters() const noexcept { + if (_hdrBoundary.inputFrame.metadata.IsValid()) { + HdrTransformParameters parameters = HdrColorTransform::ForFrame( + _hdrBoundary.inputFrame.metadata.color); + if (parameters.IsValid()) { + if (const HdrFormatRoute* route = _hdrBoundary.SelectedRoute(); route && + route->alphaMode == HdrAlphaMode::ForceOpaque) { + parameters.preserveAlpha = false; + } + return parameters; + } + } + return {}; +} + +bool EffectDrawer::PrepareHdrInput() const noexcept { + if (!_hdrEnabled) { + return true; + } + if (!_hdrInputSource || !_textures[0]) { + return false; + } + D3D11_TEXTURE2D_DESC sourceDesc{}, inputDesc{}; + _hdrInputSource->GetDesc(&sourceDesc); + _textures[0]->GetDesc(&inputDesc); + if (sourceDesc.Width != inputDesc.Width || sourceDesc.Height != inputDesc.Height) { + Logger::Get().Error(fmt::format( + "HDR effect input size mismatch: source={}x{} adapter={}x{}", + sourceDesc.Width, sourceDesc.Height, inputDesc.Width, inputDesc.Height)); + return false; + } + if (_hdrBoundary.SelectedRoute()) { + const auto* route = _hdrBoundary.SelectedRoute(); + D3D11_TEXTURE2D_DESC outputDesc{}; + _textures[1]->GetDesc(&outputDesc); + if (inputDesc.Format != route->inputFormat || outputDesc.Format != route->outputFormat) { + Logger::Get().Error(fmt::format( + "HDR route/resource mismatch: route={} input={} output={}", + route->Id(), static_cast(inputDesc.Format), + static_cast(outputDesc.Format))); + return false; + } + } + if (_UsesDirectHdrPath()) { + _d3dDC->CopyResource(_textures[0].get(), _hdrInputSource); + return true; + } + const HdrFormatRoute* route = _hdrBoundary.SelectedRoute(); + if (route && route->inputTransfer == HdrTransferFunction::PQ && + inputDesc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) { + return _hdrSurfaceAdapter.ConvertCanonicalToHdr10( + _hdrInputSource, _textures[0].get(), _GetHdrTransformParameters()); + } + if (_hdrBoundary.plan.requiresBoundedMapping) { + return _hdrSurfaceAdapter.ConvertHdrToBounded( + _hdrInputSource, _textures[0].get(), _GetHdrTransformParameters(), + _hdrBoundary.plan.normalizationScale); + } + + if (route && route->inputTransfer == HdrTransferFunction::PQ) { + return _hdrSurfaceAdapter.ConvertHdrToSdr( + _hdrInputSource, _textures[0].get(), _GetHdrTransformParameters(), + HdrTransferFunction::PQ); + } + const HdrTransferFunction transfer = route && + route->inputTransfer != HdrTransferFunction::Unknown + ? route->inputTransfer : HdrTransferFunction::SRGB; + return _hdrSurfaceAdapter.ConvertHdrToSdr( + _hdrInputSource, + _textures[0].get(), + _GetHdrTransformParameters(), + transfer); +} + +bool EffectDrawer::CompleteHdrOutput() const noexcept { + if (!_hdrEnabled) { + return true; + } + if (!_textures[1] || !_hdrOutput) { + return false; + } + if (_UsesDirectHdrPath()) { + _d3dDC->CopyResource(_hdrOutput.get(), _textures[1].get()); + return true; + } + const HdrFormatRoute* route = _hdrBoundary.SelectedRoute(); + D3D11_TEXTURE2D_DESC outputDesc{}; + _textures[1]->GetDesc(&outputDesc); + if (route && route->outputTransfer == HdrTransferFunction::PQ && + outputDesc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) { + return _hdrSurfaceAdapter.ConvertHdr10ToCanonical( + _textures[1].get(), _hdrOutput.get(), _GetHdrTransformParameters()); + } + if (_hdrBoundary.plan.requiresBoundedMapping) { + return _hdrSurfaceAdapter.ConvertBoundedToHdr( + _textures[1].get(), _hdrOutput.get(), _GetHdrTransformParameters(), + _hdrBoundary.plan.normalizationScale); + } + + if (route && route->outputTransfer == HdrTransferFunction::PQ) { + return _hdrSurfaceAdapter.ConvertSdrToHdr( + _textures[1].get(), _hdrOutput.get(), _GetHdrTransformParameters(), + HdrTransferFunction::PQ); + } + const HdrTransferFunction transfer = route && + route->outputTransfer != HdrTransferFunction::Unknown + ? route->outputTransfer : HdrTransferFunction::SRGB; + return _hdrSurfaceAdapter.ConvertSdrToHdr( + _textures[1].get(), + _hdrOutput.get(), + _GetHdrTransformParameters(), + transfer); +} + bool EffectDrawer::UpdateParameters( const EffectDesc& desc, const EffectOption& option, diff --git a/src/Magpie.Core/EffectDrawer.h b/src/Magpie.Core/EffectDrawer.h index 6007d9724..160aac8f0 100644 --- a/src/Magpie.Core/EffectDrawer.h +++ b/src/Magpie.Core/EffectDrawer.h @@ -1,5 +1,8 @@ #pragma once #include "EffectDesc.h" +#include "HdrEffectBoundary.h" +#include "HdrSurfaceAdapter.h" +#include #include "SmallVector.h" // Conan 的 muparser 不含 UNICODE 支持 #pragma push_macro("_UNICODE") @@ -35,6 +38,18 @@ class EffectDrawer { void Draw(EffectsProfiler& profiler) const noexcept; + void SetHdrBoundary(HdrEffectBoundaryContext context) noexcept { _hdrBoundary = std::move(context); } + const HdrEffectBoundaryContext& GetHdrBoundary() const noexcept { return _hdrBoundary; } + // The renderer may replace the canonical handoff texture after capture, + // resize, or an upstream effect rebuild. Keep the boundary source bound to + // the current production texture instead of a stale initialization pointer. + void SetHdrInputSource(ID3D11Texture2D* texture) noexcept { _hdrInputSource = texture; } + bool PrepareHdrInput() const noexcept; + bool CompleteHdrOutput() const noexcept; + ID3D11Texture2D* GetExternalOutputTexture() const noexcept { + return _hdrEnabled && _hdrOutput ? _hdrOutput.get() : _textures[1].get(); + } + void DrawForExport(const EffectDesc& desc, uint32_t passIdx) const noexcept; bool ResizeTextures( @@ -58,6 +73,12 @@ class EffectDrawer { return _textures[idx].get(); } + // Source-equivalent matrix tests use the exact production drawer and expose + // its actual canonical handoff rather than reimplementing effect formulas. + ID3D11Texture2D* GetCanonicalOutputTextureForTesting() const noexcept { + return GetExternalOutputTexture(); + } + private: SIZE _CalcOutputSize( const EffectDesc& desc, @@ -78,6 +99,10 @@ class EffectDrawer { void _PrepareForDraw() const noexcept; void _DrawPass(uint32_t i) const noexcept; + bool _UsesDirectHdrPath() const noexcept; + DXGI_FORMAT _GetHdrInputFormat(const EffectDesc& desc) const noexcept; + DXGI_FORMAT _GetHdrOutputFormat(const EffectDesc& desc) const noexcept; + HdrTransformParameters _GetHdrTransformParameters() const noexcept; SmallVector _CalcPassesToDrawForExport( const EffectDesc& desc, @@ -98,6 +123,11 @@ class EffectDrawer { SmallVector> _shaders; SmallVector> _dispatches; + HdrEffectBoundaryContext _hdrBoundary{}; + HdrSurfaceAdapter _hdrSurfaceAdapter; + winrt::com_ptr _hdrOutput; + ID3D11Texture2D* _hdrInputSource = nullptr; + bool _hdrEnabled = false; static inline mu::Parser _exprParser; }; diff --git a/src/Magpie.Core/EffectProtocolCatalogC.h b/src/Magpie.Core/EffectProtocolCatalogC.h new file mode 100644 index 000000000..9effea6b8 --- /dev/null +++ b/src/Magpie.Core/EffectProtocolCatalogC.h @@ -0,0 +1,223 @@ +#pragma once + +#include "HdrAdapterDispatcher.h" + +// Group-C effect protocol declarations. These functions deliberately keep +// each effect family separate so a future Renderer bridge can select a route +// without a shared generic HDR processor or effect-name switch. +namespace Magpie::EffectProtocolC { + +inline HdrFormatRoute SdrRoute( + std::string_view effect, + std::string_view option, + DXGI_FORMAT format, + HdrAlphaMode alpha = HdrAlphaMode::ForceOpaque +) { + return HdrFormatRoute{ + .effectId = std::string(effect), + .optionId = std::string(option), + .inputFormat = format, + .outputFormat = format, + .inputTransfer = HdrTransferFunction::SRGB, + .outputTransfer = HdrTransferFunction::SRGB, + .inputRange = HdrColorRange::Full, + .outputRange = HdrColorRange::Full, + .alphaMode = alpha, + .evidenceLevel = HdrEvidenceLevel::ReferenceImplementation, + .hdrNative = false, + .adapterProfile = HdrAdapterProfile::SDRCompatible, + .defaultForHdr = true, + .defaultForSdr = true + }; +} + +inline HdrFormatRoute ConditionalFp16Route( + std::string_view effect, + std::string_view option, + HdrAlphaMode alpha = HdrAlphaMode::ForceOpaque +) { + return HdrFormatRoute{ + .effectId = std::string(effect), + .optionId = std::string(option), + .inputFormat = DXGI_FORMAT_R16G16B16A16_FLOAT, + .outputFormat = DXGI_FORMAT_R16G16B16A16_FLOAT, + .inputTransfer = HdrTransferFunction::Linear, + .outputTransfer = HdrTransferFunction::Linear, + .inputRange = HdrColorRange::SceneLinear, + .outputRange = HdrColorRange::SceneLinear, + .alphaMode = alpha, + .evidenceLevel = HdrEvidenceLevel::ReferenceImplementation, + .hdrNative = false, + .adapterProfile = HdrAdapterProfile::ConditionalFP16, + .defaultForHdr = false, + .defaultForSdr = false + }; +} + +inline HdrFormatRoutes NNEDI3() { + return { SdrRoute("NNEDI3", "luma-r16", DXGI_FORMAT_R8G8B8A8_UNORM) }; +} + +inline HdrFormatRoutes PixelArt() { + return { SdrRoute("Pixel Art", "rgb-thresholds", DXGI_FORMAT_R8G8B8A8_UNORM) }; +} + +inline HdrFormatRoutes RAVU() { + return { SdrRoute("RAVU", "variant-boundary", DXGI_FORMAT_R8G8B8A8_UNORM) }; +} + +inline HdrFormatRoutes RTXVideoVsr() { + HdrFormatRoute route = SdrRoute( + "RTXVideoVSR", "VSR-BGRA8-U8-sRGB-255", DXGI_FORMAT_B8G8R8A8_UNORM); + // NvCVImage receives display-referred sRGB code values in the 0..255 + // domain. The adapter performs transfer encoding before the native call. + route.inputTransfer = HdrTransferFunction::SRGB; + route.outputTransfer = HdrTransferFunction::SRGB; + return { route }; +} + +inline HdrFormatRoutes RTXVideoDenoiser() { + return { SdrRoute("RTXVideoDenoise", "Denoise-BGRA8-U8", DXGI_FORMAT_B8G8R8A8_UNORM) }; +} + +inline HdrFormatRoutes RTXVideoHdr() { + // Product-level SDR->HDR10 behavior has no verified callable texture route. + return {}; +} + +inline HdrFormatRoutes Sharpen() { + return { SdrRoute("Sharpen", "normalized-rgb", DXGI_FORMAT_R8G8B8A8_UNORM) }; +} + +inline HdrFormatRoutes SMAA() { + return { SdrRoute("SMAA", "rgba-edge-blend", DXGI_FORMAT_R8G8B8A8_UNORM) }; +} + +inline HdrFormatRoutes Xbrz() { + return { SdrRoute("xBRZ", "rgba-u8", DXGI_FORMAT_R8G8B8A8_UNORM) }; +} + +inline HdrFormatRoutes XeSS() { + HdrFormatRoute hdr10{ + .effectId = "XeSS", + .optionId = "HDR10-R10G10B10A2", + .inputFormat = DXGI_FORMAT_R10G10B10A2_UNORM, + .outputFormat = DXGI_FORMAT_R10G10B10A2_UNORM, + .inputTransfer = HdrTransferFunction::PQ, + .outputTransfer = HdrTransferFunction::PQ, + .inputRange = HdrColorRange::DisplayReferred, + .outputRange = HdrColorRange::DisplayReferred, + .alphaMode = HdrAlphaMode::ForceOpaque, + .evidenceLevel = HdrEvidenceLevel::PublicApiContract, + .hdrNative = false, + .adapterProfile = HdrAdapterProfile::BoundedHDR, + .defaultForHdr = true, + .defaultForSdr = false + }; + HdrFormatRoute fp16 = hdr10; + fp16.optionId = "R16G16B16A16_FLOAT"; + fp16.inputFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; + fp16.outputFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; + fp16.inputTransfer = HdrTransferFunction::Linear; + fp16.outputTransfer = HdrTransferFunction::Linear; + fp16.inputRange = HdrColorRange::SceneLinear; + fp16.outputRange = HdrColorRange::SceneLinear; + fp16.hdrNative = true; + fp16.adapterProfile = HdrAdapterProfile::DirectFP16; + fp16.defaultForHdr = true; + hdr10.defaultForHdr = false; + HdrFormatRoute r11 = fp16; + r11.optionId = "R11G11B10_FLOAT"; + r11.inputFormat = DXGI_FORMAT_R11G11B10_FLOAT; + r11.outputFormat = DXGI_FORMAT_R11G11B10_FLOAT; + r11.defaultForHdr = false; + HdrFormatRoute r8 = SdrRoute("XeSS", "R8G8B8A8_UNORM", DXGI_FORMAT_R8G8B8A8_UNORM); + r8.evidenceLevel = HdrEvidenceLevel::PublicApiContract; + return { hdr10, fp16, r11, r8 }; +} + +inline HdrFormatRoutes XeSSFG() { + return { HdrFormatRoute{ + .effectId = "XeSSFG", + .optionId = "HDR10-R10G10B10A2", + .inputFormat = DXGI_FORMAT_R10G10B10A2_UNORM, + .outputFormat = DXGI_FORMAT_R10G10B10A2_UNORM, + .inputTransfer = HdrTransferFunction::PQ, + .outputTransfer = HdrTransferFunction::PQ, + .inputRange = HdrColorRange::DisplayReferred, + .outputRange = HdrColorRange::DisplayReferred, + .alphaMode = HdrAlphaMode::ForceOpaque, + .evidenceLevel = HdrEvidenceLevel::PublicApiContract, + .hdrNative = false, + .adapterProfile = HdrAdapterProfile::PresentationTerminal, + .defaultForHdr = true, + .defaultForSdr = false + } }; +} + +inline HdrFormatRoutes DLSSFG() { + return { HdrFormatRoute{ + .effectId = "DLSSFG", + .optionId = "presentation-terminal-runtime-format", + .inputFormat = DXGI_FORMAT_R16G16B16A16_FLOAT, + .outputFormat = DXGI_FORMAT_R16G16B16A16_FLOAT, + .inputTransfer = HdrTransferFunction::Linear, + .outputTransfer = HdrTransferFunction::Linear, + .inputRange = HdrColorRange::SceneLinear, + .outputRange = HdrColorRange::SceneLinear, + .alphaMode = HdrAlphaMode::Preserve, + .evidenceLevel = HdrEvidenceLevel::CommunityExperiment, + .hdrNative = true, + .adapterProfile = HdrAdapterProfile::PresentationTerminal, + .defaultForHdr = false, + .defaultForSdr = false + } }; +} + +inline HdrFormatRoutes FSR3FG() { + return { HdrFormatRoute{ + .effectId = "FSR3FG", + .optionId = "presentation-terminal-runtime-format", + .inputFormat = DXGI_FORMAT_R16G16B16A16_FLOAT, + .outputFormat = DXGI_FORMAT_R16G16B16A16_FLOAT, + .inputTransfer = HdrTransferFunction::Linear, + .outputTransfer = HdrTransferFunction::Linear, + .inputRange = HdrColorRange::SceneLinear, + .outputRange = HdrColorRange::SceneLinear, + .alphaMode = HdrAlphaMode::Preserve, + .evidenceLevel = HdrEvidenceLevel::PublicApiContract, + .hdrNative = false, + .adapterProfile = HdrAdapterProfile::PresentationTerminal, + .defaultForHdr = false, + .defaultForSdr = false + } }; +} + +inline HdrFormatRoutes NvidiaOpticalFlow() { + // Optical flow produces auxiliary vectors, so this route records the input + // boundary only. The vector output remains R16G16_FLOAT/S10.5-decoded data. + HdrFormatRoute route = SdrRoute( + "NVIDIA Optical Flow", "ABGR8-input-S10.5-output", DXGI_FORMAT_R8G8B8A8_UNORM, + HdrAlphaMode::ForceOpaque); + route.evidenceLevel = HdrEvidenceLevel::PublicApiContract; + return { route }; +} + +inline HdrFormatRoutes GetGroupCHdrRoutes(std::string_view effectGroup) { + if (effectGroup == "NNEDI3") return NNEDI3(); + if (effectGroup == "Pixel Art") return PixelArt(); + if (effectGroup == "RAVU") return RAVU(); + if (effectGroup == "RTXVideoVSR") return RTXVideoVsr(); + if (effectGroup == "RTXVideoDenoise") return RTXVideoDenoiser(); + if (effectGroup == "Sharpen") return Sharpen(); + if (effectGroup == "SMAA") return SMAA(); + if (effectGroup == "xBRZ") return Xbrz(); + if (effectGroup == "XeSS") return XeSS(); + if (effectGroup == "XeSSFG") return XeSSFG(); + if (effectGroup == "DLSSFG") return DLSSFG(); + if (effectGroup == "FSR3FG") return FSR3FG(); + if (effectGroup == "NVIDIA Optical Flow") return NvidiaOpticalFlow(); + return {}; +} + +} // namespace Magpie::EffectProtocolC diff --git a/src/Magpie.Core/FSR2Upscaler.cpp b/src/Magpie.Core/FSR2Upscaler.cpp index ca3c2498d..f92ef6872 100644 --- a/src/Magpie.Core/FSR2Upscaler.cpp +++ b/src/Magpie.Core/FSR2Upscaler.cpp @@ -31,6 +31,8 @@ void FSR2Upscaler::_Reset() noexcept { _zeroMotion = nullptr; _zeroDepthUav = nullptr; _zeroDepth = nullptr; + _exposureUav = nullptr; + _exposure = nullptr; _reactiveUav = nullptr; _reactive = nullptr; if (_backendModule) FreeLibrary(_backendModule); @@ -108,13 +110,23 @@ bool FSR2Upscaler::Initialize( desc.device = reinterpret_cast(_getDevice)(_device); desc.maxRenderSize = { inDesc.Width, inDesc.Height }; desc.displaySize = { outDesc.Width, outDesc.Height }; - desc.flags = FFX_FSR2_ENABLE_AUTO_EXPOSURE | FFX_FSR2_ENABLE_DEPTH_INVERTED | - FFX_FSR2_ENABLE_DEPTH_INFINITE; + desc.flags = FFX_FSR2_ENABLE_AUTO_EXPOSURE | + (_hdrProtocol.depthInverted ? FFX_FSR2_ENABLE_DEPTH_INVERTED : 0) | + (_hdrProtocol.depthInfinite ? FFX_FSR2_ENABLE_DEPTH_INFINITE : 0); + if (_hdrProtocol.hdrColorInput) desc.flags |= FFX_FSR2_ENABLE_HIGH_DYNAMIC_RANGE; ec = reinterpret_cast(_contextCreate)( static_cast(_context), &desc); if (ec != FFX_OK) { Logger::Get().Error(fmt::format("ffxFsr2ContextCreate failed ({})", (int)ec)); _Reset(); return false; } - Logger::Get().Info(fmt::format("FSR2 D3D11 initialized (opticalFlow={}): {}x{} -> {}x{}", - _enableOpticalFlow, inDesc.Width, inDesc.Height, outDesc.Width, outDesc.Height)); + if (_hdrProtocol.hdrColorInput) { + _exposure = DirectXHelper::CreateTexture2D(_device, DXGI_FORMAT_R32_FLOAT, 1, 1, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS); + if (_exposure) _device->CreateUnorderedAccessView( + _exposure.get(), nullptr, _exposureUav.put()); + } + Logger::Get().Info(fmt::format("FSR2 D3D11 initialized (opticalFlow={}, hdr={}, transfer={}, exposure={:.3f}): {}x{} -> {}x{}", + _enableOpticalFlow, _hdrProtocol.hdrColorInput, + static_cast(_hdrProtocol.transfer), _hdrProtocol.exposure, + inDesc.Width, inDesc.Height, outDesc.Width, outDesc.Height)); return true; } @@ -133,6 +145,10 @@ bool FSR2Upscaler::Draw(const NativeEffectDrawContext& drawContext) noexcept { static constexpr float REACTIVE_OF[4]{ 0.5f,0.5f,0.5f,0.5f }; static constexpr float REACTIVE_ZEROMV[4]{ 0.9f,0.9f,0.9f,0.9f }; _d3dDC->ClearUnorderedAccessViewFloat(_zeroDepthUav.get(), ZERO); + if (_exposureUav) { + const float exposure[4]{ _hdrProtocol.exposure, 0, 0, 0 }; + _d3dDC->ClearUnorderedAccessViewFloat(_exposureUav.get(), exposure); + } ID3D11Texture2D* motionVectors = _zeroMotion.get(); if (_enableOpticalFlow) { D3D11_TEXTURE2D_DESC desc{}; @@ -155,7 +171,8 @@ bool FSR2Upscaler::Draw(const NativeEffectDrawContext& drawContext) noexcept { d.depth = getResource(static_cast(_context), _zeroDepth.get(), L"FSR2_ZeroDepth", FFX_RESOURCE_STATE_COMPUTE_READ); d.motionVectors = getResource(static_cast(_context), motionVectors, _enableOpticalFlow ? L"FSR2_OpticalFlow" : L"FSR2_ZeroMotion", FFX_RESOURCE_STATE_COMPUTE_READ); - d.exposure = getResource(static_cast(_context), nullptr, L"FSR2_AutoExposure", FFX_RESOURCE_STATE_COMPUTE_READ); + d.exposure = getResource(static_cast(_context), _exposure.get(), + _exposure ? L"FSR2_Exposure" : L"FSR2_AutoExposure", FFX_RESOURCE_STATE_COMPUTE_READ); d.reactive = getResource(static_cast(_context), _reactive.get(), L"FSR2_FullReactive", FFX_RESOURCE_STATE_COMPUTE_READ); d.transparencyAndComposition = getResource(static_cast(_context), nullptr, nullptr, FFX_RESOURCE_STATE_COMPUTE_READ); d.output = getResource(static_cast(_context), output, L"FSR2_Output", FFX_RESOURCE_STATE_UNORDERED_ACCESS); @@ -167,7 +184,7 @@ bool FSR2Upscaler::Draw(const NativeEffectDrawContext& drawContext) noexcept { d.enableSharpening = true; d.sharpness = 0.2f; d.frameTimeDelta = 16.6667f; - d.preExposure = 1.0f; + d.preExposure = _hdrProtocol.preExposure; d.reset = _resetHistory; d.cameraNear = 1.0f; d.cameraFar = FLT_MAX; diff --git a/src/Magpie.Core/FSR2Upscaler.h b/src/Magpie.Core/FSR2Upscaler.h index 91af2ebe6..19711a7e1 100644 --- a/src/Magpie.Core/FSR2Upscaler.h +++ b/src/Magpie.Core/FSR2Upscaler.h @@ -1,5 +1,6 @@ #pragma once #include "NativeEffectBackend.h" +#include "GroupBEffectProtocol.h" namespace Magpie { @@ -16,6 +17,9 @@ class FSR2Upscaler final : public NativeEffectBackend { MotionVectorRequest motionRequest = {}) noexcept; bool Resize(DeviceResources& resources, ID3D11Texture2D* input, ID3D11Texture2D* output) noexcept override; bool Draw(const NativeEffectDrawContext& context) noexcept override; + void SetFsrHdrProtocol(const FsrHdrProtocol& protocol) noexcept { + _hdrProtocol = protocol; + } FrameGuidanceRequirements GetFrameGuidanceRequirements() const noexcept override { FrameGuidanceRequirements result{ .zero = true }; result.Add(_motionRequest); @@ -50,9 +54,12 @@ class FSR2Upscaler final : public NativeEffectBackend { winrt::com_ptr _zeroDepthUav; winrt::com_ptr _reactive; winrt::com_ptr _reactiveUav; + winrt::com_ptr _exposure; + winrt::com_ptr _exposureUav; bool _resetHistory = true; FrameGuidanceFrameId _lastGuidanceResetFrameId = std::numeric_limits::max(); bool _enableOpticalFlow = false; + FsrHdrProtocol _hdrProtocol{}; }; } diff --git a/src/Magpie.Core/FSR2ZeroMVUpscaler.cpp b/src/Magpie.Core/FSR2ZeroMVUpscaler.cpp new file mode 100644 index 000000000..9ebfe008b --- /dev/null +++ b/src/Magpie.Core/FSR2ZeroMVUpscaler.cpp @@ -0,0 +1,214 @@ +#include "pch.h" +#include "FSR2ZeroMVUpscaler.h" +#include "DeviceResources.h" +#include "DirectXHelper.h" +#include "Logger.h" +#include "HalfResOpticalFlow.h" + +#ifdef MP_ENABLE_FSR2_ZEROMV +#include +#include + +namespace Magpie { + +template +static T LoadProc(HMODULE module, const char* name) noexcept { + return reinterpret_cast(GetProcAddress(module, name)); +} + +FSR2ZeroMVUpscaler::~FSR2ZeroMVUpscaler() { _Reset(); } + +void FSR2ZeroMVUpscaler::_Reset() noexcept { + if (_context && _contextDestroy) { + reinterpret_cast(_contextDestroy)( + static_cast(_context)); + } + delete static_cast(_context); + _context = nullptr; + delete[] static_cast(_scratch); + _scratch = nullptr; + _scratchSize = 0; + _zeroMotionUav = nullptr; + _zeroMotion = nullptr; + _zeroDepthUav = nullptr; + _zeroDepth = nullptr; + _reactiveUav = nullptr; + _reactive = nullptr; + if (_backendModule) FreeLibrary(_backendModule); + if (_coreModule) FreeLibrary(_coreModule); + _backendModule = nullptr; + _coreModule = nullptr; + _contextCreate = _contextDestroy = _contextDispatch = nullptr; + _getInterface = _getScratchSize = _getDevice = _getResource = nullptr; + _device = nullptr; + _d3dDC = nullptr; + _resetHistory = true; + _enableOpticalFlow = false; + _enableJitter = false; + _frameIndex = 0; + _opticalFlow.reset(); +} + +bool FSR2ZeroMVUpscaler::Initialize( + DeviceResources& resources, ID3D11Texture2D* input, ID3D11Texture2D* output, + bool enableOpticalFlow, bool enableJitter +) noexcept { + _Reset(); + _enableOpticalFlow = enableOpticalFlow; + _enableJitter = enableJitter; + _device = resources.GetD3DDevice(); + _d3dDC = resources.GetD3DDC(); + D3D11_TEXTURE2D_DESC inDesc{}, outDesc{}; + input->GetDesc(&inDesc); + output->GetDesc(&outDesc); + if (inDesc.Width > outDesc.Width || inDesc.Height > outDesc.Height) return false; + + _coreModule = LoadLibraryW(L"ffx_fsr2_api_x64.dll"); + _backendModule = LoadLibraryW(L"ffx_fsr2_api_dx11_x64.dll"); + if (!_coreModule || !_backendModule) { + Logger::Get().Win32Error("Load FSR2 D3D11 runtime failed"); + _Reset(); + return false; + } + _contextCreate = LoadProc(_coreModule, "ffxFsr2ContextCreate"); + _contextDestroy = LoadProc(_coreModule, "ffxFsr2ContextDestroy"); + _contextDispatch = LoadProc(_coreModule, "ffxFsr2ContextDispatch"); + _getInterface = LoadProc(_backendModule, "ffxFsr2GetInterfaceDX11"); + _getScratchSize = LoadProc(_backendModule, "ffxFsr2GetScratchMemorySizeDX11"); + _getDevice = LoadProc(_backendModule, "ffxGetDeviceDX11"); + _getResource = LoadProc(_backendModule, "ffxGetResourceDX11"); + if (!_contextCreate || !_contextDestroy || !_contextDispatch || !_getInterface || + !_getScratchSize || !_getDevice || !_getResource) { + Logger::Get().Error("FSR2 D3D11 runtime exports are incomplete"); + _Reset(); + return false; + } + + _zeroMotion = DirectXHelper::CreateTexture2D(_device, DXGI_FORMAT_R16G16_FLOAT, + inDesc.Width, inDesc.Height, D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS); + _zeroDepth = DirectXHelper::CreateTexture2D(_device, DXGI_FORMAT_R32_FLOAT, + inDesc.Width, inDesc.Height, D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS); + _reactive = DirectXHelper::CreateTexture2D(_device, DXGI_FORMAT_R8_UNORM, + inDesc.Width, inDesc.Height, D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS); + HRESULT hr = _device->CreateUnorderedAccessView(_zeroMotion.get(), nullptr, _zeroMotionUav.put()); + if (SUCCEEDED(hr)) hr = _device->CreateUnorderedAccessView(_zeroDepth.get(), nullptr, _zeroDepthUav.put()); + if (SUCCEEDED(hr)) hr = _device->CreateUnorderedAccessView(_reactive.get(), nullptr, _reactiveUav.put()); + if (FAILED(hr)) { + Logger::Get().ComError("Create FSR2 Zero-MV auxiliary resources failed", hr); + _Reset(); + return false; + } + if (_enableOpticalFlow) { + _opticalFlow = std::make_unique(); + if (!_opticalFlow->Initialize(_device, _d3dDC, input)) { + Logger::Get().Error("Initialize FSR2 half-resolution optical flow failed"); + _Reset(); + return false; + } + } + + FfxFsr2ContextDescription desc{}; + _scratchSize = reinterpret_cast(_getScratchSize)(); + _scratch = new (std::nothrow) char[_scratchSize]; + _context = new (std::nothrow) FfxFsr2Context{}; + if (!_scratch || !_context) { _Reset(); return false; } + FfxErrorCode ec = reinterpret_cast(_getInterface)( + &desc.callbacks, _device, _scratch, _scratchSize); + if (ec != FFX_OK) { Logger::Get().Error(fmt::format("ffxFsr2GetInterfaceDX11 failed ({})", (int)ec)); _Reset(); return false; } + desc.device = reinterpret_cast(_getDevice)(_device); + desc.maxRenderSize = { inDesc.Width, inDesc.Height }; + desc.displaySize = { outDesc.Width, outDesc.Height }; + desc.flags = FFX_FSR2_ENABLE_AUTO_EXPOSURE | FFX_FSR2_ENABLE_DEPTH_INVERTED | + FFX_FSR2_ENABLE_DEPTH_INFINITE; + ec = reinterpret_cast(_contextCreate)( + static_cast(_context), &desc); + if (ec != FFX_OK) { Logger::Get().Error(fmt::format("ffxFsr2ContextCreate failed ({})", (int)ec)); _Reset(); return false; } + Logger::Get().Info(fmt::format("FSR2 D3D11 initialized (opticalFlow={}, jitter={}): {}x{} -> {}x{}", + _enableOpticalFlow, _enableJitter, inDesc.Width, inDesc.Height, outDesc.Width, outDesc.Height)); + return true; +} + +bool FSR2ZeroMVUpscaler::Resize(DeviceResources& r, ID3D11Texture2D* i, ID3D11Texture2D* o) noexcept { + const bool enableOpticalFlow = _enableOpticalFlow; + const bool enableJitter = _enableJitter; + return Initialize(r, i, o, enableOpticalFlow, enableJitter); +} + +static float Halton(uint32_t index, uint32_t base) noexcept { + float result = 0.0f; + float fraction = 1.0f; + while (index) { + fraction /= (float)base; + result += fraction * (float)(index % base); + index /= base; + } + return result; +} + +bool FSR2ZeroMVUpscaler::Draw(const NativeEffectDrawContext& drawContext) noexcept { + ID3D11Texture2D* input = drawContext.input; + ID3D11Texture2D* output = drawContext.output; + if (!_context) return false; + static constexpr float ZERO[4]{}; + static constexpr float REACTIVE_OF[4]{ 0.5f,0.5f,0.5f,0.5f }; + static constexpr float REACTIVE_ZEROMV[4]{ 0.9f,0.9f,0.9f,0.9f }; + _d3dDC->ClearUnorderedAccessViewFloat(_zeroDepthUav.get(), ZERO); + ID3D11Texture2D* motionVectors = _zeroMotion.get(); + if (_enableOpticalFlow) { + if (!_opticalFlow || !_opticalFlow->Estimate(input)) return false; + motionVectors = _opticalFlow->GetMotionTexture(); + _d3dDC->ClearUnorderedAccessViewFloat(_reactiveUav.get(), REACTIVE_OF); + } else { + _d3dDC->ClearUnorderedAccessViewFloat(_zeroMotionUav.get(), ZERO); + _d3dDC->ClearUnorderedAccessViewFloat(_reactiveUav.get(), REACTIVE_ZEROMV); + } + D3D11_TEXTURE2D_DESC inDesc{}; + input->GetDesc(&inDesc); + auto getResource = reinterpret_cast(_getResource); + FfxFsr2DispatchDescription d{}; + d.commandList = _d3dDC; + d.color = getResource(static_cast(_context), input, L"FSR2_InputColor", FFX_RESOURCE_STATE_COMPUTE_READ); + d.depth = getResource(static_cast(_context), _zeroDepth.get(), L"FSR2_ZeroDepth", FFX_RESOURCE_STATE_COMPUTE_READ); + d.motionVectors = getResource(static_cast(_context), motionVectors, + _enableOpticalFlow ? L"FSR2_OpticalFlow" : L"FSR2_ZeroMotion", FFX_RESOURCE_STATE_COMPUTE_READ); + d.exposure = getResource(static_cast(_context), nullptr, L"FSR2_AutoExposure", FFX_RESOURCE_STATE_COMPUTE_READ); + d.reactive = getResource(static_cast(_context), _reactive.get(), L"FSR2_FullReactive", FFX_RESOURCE_STATE_COMPUTE_READ); + d.transparencyAndComposition = getResource(static_cast(_context), nullptr, nullptr, FFX_RESOURCE_STATE_COMPUTE_READ); + d.output = getResource(static_cast(_context), output, L"FSR2_Output", FFX_RESOURCE_STATE_UNORDERED_ACCESS); + // HalfResOpticalFlow stores motion directly in pixel units, so no render-size + // multiplication is needed. Applying width/height here made OF vectors huge. + d.motionVectorScale = { 1.0f, 1.0f }; + if (_enableJitter) { + // Metadata-only jitter: Magpie cannot modify the source application's projection. + const uint32_t sample = (_frameIndex++ & 7u) + 1u; + d.jitterOffset = { Halton(sample, 2) - 0.5f, Halton(sample, 3) - 0.5f }; + } else { + d.jitterOffset = { 0.0f, 0.0f }; + } + d.renderSize = { inDesc.Width, inDesc.Height }; + d.enableSharpening = true; + d.sharpness = 0.2f; + d.frameTimeDelta = 16.6667f; + d.preExposure = 1.0f; + d.reset = _resetHistory; + d.cameraNear = 1.0f; + d.cameraFar = FLT_MAX; + d.cameraFovAngleVertical = 1.04719755f; + d.viewSpaceToMetersFactor = 1.0f; + const FfxErrorCode ec = reinterpret_cast(_contextDispatch)( + static_cast(_context), &d); + if (ec != FFX_OK) { Logger::Get().Error(fmt::format("ffxFsr2ContextDispatch failed ({})", (int)ec)); return false; } + _resetHistory = false; + return true; +} + +} +#else +namespace Magpie { +FSR2ZeroMVUpscaler::~FSR2ZeroMVUpscaler() = default; +void FSR2ZeroMVUpscaler::_Reset() noexcept {} +bool FSR2ZeroMVUpscaler::Initialize(DeviceResources&, ID3D11Texture2D*, ID3D11Texture2D*, bool, bool) noexcept { return false; } +bool FSR2ZeroMVUpscaler::Resize(DeviceResources&, ID3D11Texture2D*, ID3D11Texture2D*) noexcept { return false; } +bool FSR2ZeroMVUpscaler::Draw(const NativeEffectDrawContext&) noexcept { return false; } +} +#endif diff --git a/src/Magpie.Core/FSR2ZeroMVUpscaler.h b/src/Magpie.Core/FSR2ZeroMVUpscaler.h new file mode 100644 index 000000000..242411bff --- /dev/null +++ b/src/Magpie.Core/FSR2ZeroMVUpscaler.h @@ -0,0 +1,51 @@ +#pragma once +#include "HalfResOpticalFlow.h" +#include "NativeEffectBackend.h" + +namespace Magpie { + +class DeviceResources; + +class FSR2ZeroMVUpscaler final : public NativeEffectBackend { +public: + FSR2ZeroMVUpscaler() = default; + FSR2ZeroMVUpscaler(const FSR2ZeroMVUpscaler&) = delete; + FSR2ZeroMVUpscaler& operator=(const FSR2ZeroMVUpscaler&) = delete; + ~FSR2ZeroMVUpscaler() override; + + bool Initialize(DeviceResources& resources, ID3D11Texture2D* input, ID3D11Texture2D* output, + bool enableOpticalFlow = false, bool enableJitter = false) noexcept; + bool Resize(DeviceResources& resources, ID3D11Texture2D* input, ID3D11Texture2D* output) noexcept override; + bool Draw(const NativeEffectDrawContext& context) noexcept override; + +private: + void _Reset() noexcept; + + ID3D11Device* _device = nullptr; + ID3D11DeviceContext* _d3dDC = nullptr; + HMODULE _coreModule = nullptr; + HMODULE _backendModule = nullptr; + void* _context = nullptr; + void* _scratch = nullptr; + size_t _scratchSize = 0; + void* _contextCreate = nullptr; + void* _contextDestroy = nullptr; + void* _contextDispatch = nullptr; + void* _getInterface = nullptr; + void* _getScratchSize = nullptr; + void* _getDevice = nullptr; + void* _getResource = nullptr; + winrt::com_ptr _zeroMotion; + winrt::com_ptr _zeroMotionUav; + winrt::com_ptr _zeroDepth; + winrt::com_ptr _zeroDepthUav; + winrt::com_ptr _reactive; + winrt::com_ptr _reactiveUav; + bool _resetHistory = true; + bool _enableOpticalFlow = false; + bool _enableJitter = false; + uint32_t _frameIndex = 0; + std::unique_ptr _opticalFlow; +}; + +} diff --git a/src/Magpie.Core/FSR3Upscaler.cpp b/src/Magpie.Core/FSR3Upscaler.cpp index 7209a33c7..8c814b790 100644 --- a/src/Magpie.Core/FSR3Upscaler.cpp +++ b/src/Magpie.Core/FSR3Upscaler.cpp @@ -52,6 +52,7 @@ struct FSR3Upscaler::Impl { uint32_t outputWidth = 0; uint32_t outputHeight = 0; bool enableOpticalFlow = false; + FsrHdrProtocol hdrProtocol{}; bool useFsr4 = false; bool resetHistory = true; FrameGuidanceFrameId lastGuidanceResetFrameId = std::numeric_limits::max(); @@ -274,6 +275,7 @@ bool FSR3Upscaler::Initialize( impl->device11 = resources.GetD3DDevice(); impl->context11 = resources.GetD3DDC(); impl->enableOpticalFlow = enableOpticalFlow; + impl->hdrProtocol = _hdrProtocol; impl->useFsr4 = useFsr4; const char* upscalerName = useFsr4 ? "FSR 4.1.1" : "FSR 3.1.5"; @@ -343,7 +345,8 @@ bool FSR3Upscaler::Initialize( D3D12_RESOURCE_BARRIER auxBarriers[5]{}; UINT auxCount = 0; const float zero[4]{}; - const float one[4]{ 1, 1, 1, 1 }; + const float one[4]{ impl->hdrProtocol.exposure, impl->hdrProtocol.exposure, + impl->hdrProtocol.exposure, impl->hdrProtocol.exposure }; const float reactive02[4]{ 0.2f, 0.2f, 0.2f, 0.2f }; const float reactiveFsr4OpticalFlow[4]{ 0.8f, 0.8f, 0.8f, 0.8f }; const float reactive08[4]{ 0.8f, 0.8f, 0.8f, 0.8f }; @@ -444,8 +447,12 @@ bool FSR3Upscaler::Initialize( } impl->createDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE; - impl->createDesc.flags = FFX_UPSCALE_ENABLE_DEPTH_INVERTED | - FFX_UPSCALE_ENABLE_DEPTH_INFINITE | FFX_UPSCALE_ENABLE_NON_LINEAR_COLORSPACE; + impl->createDesc.flags = + (impl->hdrProtocol.depthInverted ? FFX_UPSCALE_ENABLE_DEPTH_INVERTED : 0) | + (impl->hdrProtocol.depthInfinite ? FFX_UPSCALE_ENABLE_DEPTH_INFINITE : 0); + if (!impl->hdrProtocol.hdrColorInput) { + impl->createDesc.flags |= FFX_UPSCALE_ENABLE_NON_LINEAR_COLORSPACE; + } impl->createDesc.maxRenderSize = { inputDesc.Width, inputDesc.Height }; impl->createDesc.maxUpscaleSize = { outputDesc.Width, outputDesc.Height }; impl->backendDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12; @@ -540,13 +547,16 @@ bool FSR3Upscaler::Draw(const NativeEffectDrawContext& drawContext) noexcept { desc.enableSharpening = true; desc.sharpness = 0.2f; desc.frameTimeDelta = 16.6667f; - desc.preExposure = 1.0f; + desc.preExposure = impl.hdrProtocol.preExposure; desc.reset = impl.resetHistory; desc.cameraNear = 1.0f; desc.cameraFar = FLT_MAX; desc.cameraFovAngleVertical = 1.04719755f; desc.viewSpaceToMetersFactor = 1.0f; - desc.flags = FFX_UPSCALE_FLAG_NON_LINEAR_COLOR_SRGB; + desc.flags = 0; + if (!impl.hdrProtocol.hdrColorInput) { + desc.flags = FFX_UPSCALE_FLAG_NON_LINEAR_COLOR_SRGB; + } const ffxReturnCode_t rc = impl.dispatch(&impl.context, &desc.header); if (rc != FFX_API_RETURN_OK) { Logger::Get().Error(fmt::format("Dispatch {} failed ({})", diff --git a/src/Magpie.Core/FSR3Upscaler.h b/src/Magpie.Core/FSR3Upscaler.h index 071b683e2..a55d6c373 100644 --- a/src/Magpie.Core/FSR3Upscaler.h +++ b/src/Magpie.Core/FSR3Upscaler.h @@ -1,5 +1,6 @@ #pragma once #include "NativeEffectBackend.h" +#include "GroupBEffectProtocol.h" namespace Magpie { @@ -21,6 +22,9 @@ class FSR3Upscaler final : public NativeEffectBackend { bool Resize(DeviceResources& resources, ID3D11Texture2D* input, ID3D11Texture2D* output) noexcept override; bool Draw(const NativeEffectDrawContext& context) noexcept override; + void SetFsrHdrProtocol(const FsrHdrProtocol& protocol) noexcept { + _hdrProtocol = protocol; + } FrameGuidanceRequirements GetFrameGuidanceRequirements() const noexcept override { FrameGuidanceRequirements result{ .zero = true }; result.Add(_motionRequest); @@ -35,6 +39,7 @@ class FSR3Upscaler final : public NativeEffectBackend { MotionVectorRequest _motionRequest{}; std::unique_ptr _impl; bool _useFsr4 = false; + FsrHdrProtocol _hdrProtocol{}; }; } diff --git a/src/Magpie.Core/FSR3ZeroMVUpscaler.cpp b/src/Magpie.Core/FSR3ZeroMVUpscaler.cpp new file mode 100644 index 000000000..765fa5484 --- /dev/null +++ b/src/Magpie.Core/FSR3ZeroMVUpscaler.cpp @@ -0,0 +1,609 @@ +#include "pch.h" +#include "FSR3ZeroMVUpscaler.h" +#include "DeviceResources.h" +#include "HalfResOpticalFlow.h" +#include "Logger.h" + +#ifdef MP_ENABLE_FSR3_ZEROMV +#include +#include +#include +#include + +namespace Magpie { + +struct FSR3ZeroMVUpscaler::Impl { + ~Impl(); + + ID3D11Device5* device11 = nullptr; + ID3D11DeviceContext4* context11 = nullptr; + winrt::com_ptr device12; + winrt::com_ptr queue12; + winrt::com_ptr allocator12; + winrt::com_ptr commandList12; + winrt::com_ptr sharedInput11; + winrt::com_ptr sharedOutput11; + winrt::com_ptr sharedMotion11; + winrt::com_ptr sharedInput12; + winrt::com_ptr sharedOutput12; + winrt::com_ptr sharedMotion12; + winrt::com_ptr zeroMotion12; + winrt::com_ptr flatDepth12; + winrt::com_ptr exposure12; + winrt::com_ptr reactive12; + winrt::com_ptr transparency12; + winrt::com_ptr descriptorHeap12; + winrt::com_ptr fence11; + winrt::com_ptr fence12; + std::unique_ptr opticalFlow; + HMODULE loaderModule = nullptr; + HMODULE providerModule = nullptr; + decltype(&ffxCreateContext) createContext = nullptr; + decltype(&ffxDestroyContext) destroyContext = nullptr; + decltype(&ffxDispatch) dispatch = nullptr; + decltype(&ffxQuery) query = nullptr; + ffxContext context = nullptr; + ffxCreateContextDescUpscale createDesc{}; + ffxCreateBackendDX12Desc backendDesc{}; + ffxCreateContextDescUpscaleVersion apiVersion{}; + ffxOverrideVersion overrideVersion{}; + uint64_t fenceValue = 0; + uint64_t lastSubmittedValue = 0; + uint32_t inputWidth = 0; + uint32_t inputHeight = 0; + uint32_t outputWidth = 0; + uint32_t outputHeight = 0; + bool enableOpticalFlow = false; + bool enableJitter = false; + bool useFsr4 = false; + uint32_t frameIndex = 0; + bool resetHistory = true; +}; + +static bool WaitForFence(FSR3ZeroMVUpscaler::Impl& impl, uint64_t value) noexcept { + if (!value || impl.fence12->GetCompletedValue() >= value) return true; + wil::unique_event_nothrow event; + HRESULT hr = event.create(); + if (FAILED(hr)) return false; + hr = impl.fence12->SetEventOnCompletion(value, event.get()); + if (FAILED(hr)) return false; + WaitForSingleObject(event.get(), INFINITE); + return true; +} + +static bool WaitForQueue(FSR3ZeroMVUpscaler::Impl& impl) noexcept { + const uint64_t value = ++impl.fenceValue; + HRESULT hr = impl.queue12->Signal(impl.fence12.get(), value); + if (FAILED(hr)) return false; + return WaitForFence(impl, value); +} + +FSR3ZeroMVUpscaler::Impl::~Impl() { + if (queue12 && fence12) WaitForQueue(*this); + if (context && destroyContext) destroyContext(&context, nullptr); + if (loaderModule) FreeLibrary(loaderModule); + if (providerModule) FreeLibrary(providerModule); +} + +static bool IsAddressInExecutableSection( + const uint8_t* base, + const IMAGE_NT_HEADERS* nt, + const void* address +) noexcept { + const uintptr_t value = reinterpret_cast(address); + for (const IMAGE_SECTION_HEADER* section = IMAGE_FIRST_SECTION(nt); + section != IMAGE_FIRST_SECTION(nt) + nt->FileHeader.NumberOfSections; ++section) { + if (!(section->Characteristics & IMAGE_SCN_MEM_EXECUTE)) continue; + const uintptr_t begin = reinterpret_cast(base) + section->VirtualAddress; + const uintptr_t end = begin + std::max(section->Misc.VirtualSize, section->SizeOfRawData); + if (value >= begin && value < end) return true; + } + return false; +} + +// FSR 4.1.1 contains a dedicated INT8 provider but hides it when IsSupported +// rejects the real adapter. Patch only that provider's virtual support check in +// this process. This avoids changing the system adapter identity or other apps. +static bool ForceFsr4Int8ProviderSupport(HMODULE module) noexcept { + if (!module) return false; + uint8_t* base = reinterpret_cast(module); + const IMAGE_DOS_HEADER* dos = reinterpret_cast(base); + if (dos->e_magic != IMAGE_DOS_SIGNATURE) return false; + const IMAGE_NT_HEADERS* nt = reinterpret_cast(base + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) return false; + + constexpr char typeName[] = ".?AVffxProvider_FSR4_Int8@@"; + const uint8_t* typeNameAddress = nullptr; + const IMAGE_SECTION_HEADER* sections = IMAGE_FIRST_SECTION(nt); + for (uint16_t i = 0; i < nt->FileHeader.NumberOfSections && !typeNameAddress; ++i) { + const IMAGE_SECTION_HEADER& section = sections[i]; + if (!(section.Characteristics & IMAGE_SCN_MEM_READ)) continue; + const uint8_t* begin = base + section.VirtualAddress; + const size_t size = std::max(section.Misc.VirtualSize, section.SizeOfRawData); + if (size < sizeof(typeName)) continue; + for (size_t offset = 0; offset + sizeof(typeName) <= size; ++offset) { + if (memcmp(begin + offset, typeName, sizeof(typeName)) == 0) { + typeNameAddress = begin + offset; + break; + } + } + } + if (!typeNameAddress || typeNameAddress < base + 2 * sizeof(void*)) return false; + + // MSVC x64 TypeDescriptor stores two pointers immediately before its name. + const uint8_t* typeDescriptor = typeNameAddress - 2 * sizeof(void*); + const uint32_t typeDescriptorRva = static_cast(typeDescriptor - base); + struct CompleteObjectLocator { + uint32_t signature; + uint32_t offset; + uint32_t cdOffset; + uint32_t typeDescriptorRva; + uint32_t classDescriptorRva; + uint32_t selfRva; + }; + + for (uint16_t i = 0; i < nt->FileHeader.NumberOfSections; ++i) { + const IMAGE_SECTION_HEADER& colSection = sections[i]; + if (!(colSection.Characteristics & IMAGE_SCN_MEM_READ)) continue; + uint8_t* colBegin = base + colSection.VirtualAddress; + const size_t colSize = std::max(colSection.Misc.VirtualSize, colSection.SizeOfRawData); + for (size_t colOffset = 0; colOffset + sizeof(CompleteObjectLocator) <= colSize; + colOffset += alignof(uint32_t)) { + const auto* col = reinterpret_cast(colBegin + colOffset); + const uint32_t colRva = static_cast(colBegin + colOffset - base); + if (col->signature != 1 || col->typeDescriptorRva != typeDescriptorRva || + col->selfRva != colRva) continue; + + const uintptr_t colAddress = reinterpret_cast(col); + for (uint16_t j = 0; j < nt->FileHeader.NumberOfSections; ++j) { + const IMAGE_SECTION_HEADER& tableSection = sections[j]; + if (!(tableSection.Characteristics & IMAGE_SCN_MEM_READ)) continue; + uint8_t* tableBegin = base + tableSection.VirtualAddress; + const size_t tableSize = std::max(tableSection.Misc.VirtualSize, tableSection.SizeOfRawData); + for (size_t tableOffset = 0; tableOffset + 4 * sizeof(void*) <= tableSize; + tableOffset += alignof(void*)) { + const auto* locatorPointer = reinterpret_cast(tableBegin + tableOffset); + if (*locatorPointer != colAddress) continue; + void** vtable = reinterpret_cast(tableBegin + tableOffset + sizeof(void*)); + // MSVC order: deleting destructor, CanProvide, IsSupported. + uint8_t* supportFunction = reinterpret_cast(vtable[2]); + if (!IsAddressInExecutableSection(base, nt, supportFunction)) continue; + + DWORD oldProtect = 0; + if (!VirtualProtect(supportFunction, 6, PAGE_EXECUTE_READWRITE, &oldProtect)) return false; + const uint8_t returnTrue[]{ 0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3 }; + memcpy(supportFunction, returnTrue, sizeof(returnTrue)); + FlushInstructionCache(GetCurrentProcess(), supportFunction, sizeof(returnTrue)); + DWORD ignored = 0; + VirtualProtect(supportFunction, 6, oldProtect, &ignored); + Logger::Get().Info("Enabled process-local FSR 4.1.1 INT8 provider support override"); + return true; + } + } + } + } + return false; +} + +static bool CreateSharedTexture( + FSR3ZeroMVUpscaler::Impl& impl, + const D3D11_TEXTURE2D_DESC& sourceDesc, + winrt::com_ptr& texture11, + winrt::com_ptr& texture12 +) noexcept { + D3D11_TEXTURE2D_DESC desc = sourceDesc; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.CPUAccessFlags = 0; + desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED | D3D11_RESOURCE_MISC_SHARED_NTHANDLE; + HRESULT hr = impl.device11->CreateTexture2D(&desc, nullptr, texture11.put()); + if (FAILED(hr)) { + Logger::Get().ComError("Create FSR3 shared D3D11 texture failed", hr); + return false; + } + winrt::com_ptr dxgiResource; + hr = texture11->QueryInterface(IID_PPV_ARGS(dxgiResource.put())); + if (FAILED(hr)) return false; + HANDLE rawHandle = nullptr; + hr = dxgiResource->CreateSharedHandle(nullptr, GENERIC_ALL, nullptr, &rawHandle); + if (FAILED(hr)) return false; + wil::unique_handle handle(rawHandle); + hr = impl.device12->OpenSharedHandle(handle.get(), IID_PPV_ARGS(texture12.put())); + if (FAILED(hr)) { + Logger::Get().ComError("Open FSR3 shared texture in D3D12 failed", hr); + return false; + } + return true; +} + +static bool CreateAuxTexture( + FSR3ZeroMVUpscaler::Impl& impl, + DXGI_FORMAT format, + uint32_t width, + uint32_t height, + winrt::com_ptr& resource, + D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle, + D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle, + const float clearValue[4], + D3D12_RESOURCE_BARRIER& barrier +) noexcept { + D3D12_RESOURCE_DESC desc{}; + desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + desc.Width = width; + desc.Height = height; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.Format = format; + desc.SampleDesc.Count = 1; + desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + D3D12_HEAP_PROPERTIES heap{}; + heap.Type = D3D12_HEAP_TYPE_DEFAULT; + HRESULT hr = impl.device12->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS, nullptr, IID_PPV_ARGS(resource.put())); + if (FAILED(hr)) { + Logger::Get().ComError("Create FSR3 virtual input texture failed", hr); + return false; + } + D3D12_UNORDERED_ACCESS_VIEW_DESC uav{}; + uav.Format = format; + uav.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + impl.device12->CreateUnorderedAccessView(resource.get(), nullptr, &uav, cpuHandle); + impl.commandList12->ClearUnorderedAccessViewFloat( + gpuHandle, cpuHandle, resource.get(), clearValue, 0, nullptr); + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Transition.pResource = resource.get(); + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + return true; +} + +FSR3ZeroMVUpscaler::FSR3ZeroMVUpscaler() = default; + +FSR3ZeroMVUpscaler::~FSR3ZeroMVUpscaler() = default; + +bool FSR3ZeroMVUpscaler::Initialize( + DeviceResources& resources, + ID3D11Texture2D* input, + ID3D11Texture2D* output, + bool enableOpticalFlow, + bool enableJitter, + bool useFsr4 +) noexcept { + _enableOpticalFlow = enableOpticalFlow; + _enableJitter = enableJitter; + _useFsr4 = useFsr4; + _impl.reset(); + auto impl = std::make_unique(); + impl->device11 = resources.GetD3DDevice(); + impl->context11 = resources.GetD3DDC(); + impl->enableOpticalFlow = enableOpticalFlow; + impl->enableJitter = enableJitter; + impl->useFsr4 = useFsr4; + const char* upscalerName = useFsr4 ? "FSR 4.1.1" : "FSR 3.1.5"; + + D3D11_TEXTURE2D_DESC inputDesc{}; + D3D11_TEXTURE2D_DESC outputDesc{}; + input->GetDesc(&inputDesc); + output->GetDesc(&outputDesc); + if (inputDesc.Width > outputDesc.Width || inputDesc.Height > outputDesc.Height) { + Logger::Get().Error("FSR3 experimental backend only supports upscaling"); + return false; + } + if ((float)outputDesc.Width / inputDesc.Width > 3.0f || + (float)outputDesc.Height / inputDesc.Height > 3.0f) { + Logger::Get().Error("FSR3 experimental backend supports up to a 3x scale"); + return false; + } + impl->inputWidth = inputDesc.Width; + impl->inputHeight = inputDesc.Height; + impl->outputWidth = outputDesc.Width; + impl->outputHeight = outputDesc.Height; + + HRESULT hr = D3D12CreateDevice(resources.GetGraphicsAdapter(), D3D_FEATURE_LEVEL_11_0, + IID_PPV_ARGS(impl->device12.put())); + if (FAILED(hr)) { + Logger::Get().ComError("Create FSR3 D3D12 device failed", hr); + return false; + } + D3D12_COMMAND_QUEUE_DESC queueDesc{}; + queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + hr = impl->device12->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(impl->queue12.put())); + if (SUCCEEDED(hr)) hr = impl->device12->CreateCommandAllocator( + D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(impl->allocator12.put())); + if (SUCCEEDED(hr)) hr = impl->device12->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, + impl->allocator12.get(), nullptr, IID_PPV_ARGS(impl->commandList12.put())); + if (FAILED(hr)) { + Logger::Get().ComError("Create FSR3 D3D12 command objects failed", hr); + return false; + } + + if (!CreateSharedTexture(*impl, inputDesc, impl->sharedInput11, impl->sharedInput12) || + !CreateSharedTexture(*impl, outputDesc, impl->sharedOutput11, impl->sharedOutput12)) { + return false; + } + if (enableOpticalFlow) { + impl->opticalFlow = std::make_unique(); + if (!impl->opticalFlow->Initialize(impl->device11, impl->context11, input)) return false; + D3D11_TEXTURE2D_DESC motionDesc{}; + motionDesc.Width = inputDesc.Width; + motionDesc.Height = inputDesc.Height; + motionDesc.MipLevels = 1; + motionDesc.ArraySize = 1; + motionDesc.Format = DXGI_FORMAT_R16G16_FLOAT; + motionDesc.SampleDesc.Count = 1; + motionDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + if (!CreateSharedTexture(*impl, motionDesc, impl->sharedMotion11, impl->sharedMotion12)) return false; + } + + D3D12_DESCRIPTOR_HEAP_DESC heapDesc{}; + heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + heapDesc.NumDescriptors = 5; + heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + hr = impl->device12->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(impl->descriptorHeap12.put())); + if (FAILED(hr)) return false; + ID3D12DescriptorHeap* heaps[]{ impl->descriptorHeap12.get() }; + impl->commandList12->SetDescriptorHeaps(1, heaps); + const UINT stride = impl->device12->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + D3D12_CPU_DESCRIPTOR_HANDLE cpu = impl->descriptorHeap12->GetCPUDescriptorHandleForHeapStart(); + D3D12_GPU_DESCRIPTOR_HANDLE gpu = impl->descriptorHeap12->GetGPUDescriptorHandleForHeapStart(); + D3D12_RESOURCE_BARRIER auxBarriers[5]{}; + UINT auxCount = 0; + const float zero[4]{}; + const float one[4]{ 1, 1, 1, 1 }; + const float reactive02[4]{ 0.2f, 0.2f, 0.2f, 0.2f }; + const float reactiveFsr4OpticalFlow[4]{ 0.8f, 0.8f, 0.8f, 0.8f }; + const float reactive08[4]{ 0.8f, 0.8f, 0.8f, 0.8f }; + const float reactiveFsr4ZeroMv[4]{ 1.0f, 1.0f, 1.0f, 1.0f }; + auto addAux = [&](DXGI_FORMAT format, uint32_t width, uint32_t height, + winrt::com_ptr& texture, const float value[4]) -> bool { + if (!CreateAuxTexture(*impl, format, width, height, texture, cpu, gpu, value, + auxBarriers[auxCount])) return false; + ++auxCount; + cpu.ptr += stride; + gpu.ptr += stride; + return true; + }; + if (!addAux(DXGI_FORMAT_R32_FLOAT, inputDesc.Width, inputDesc.Height, impl->flatDepth12, zero) || + !addAux(DXGI_FORMAT_R32_FLOAT, 1, 1, impl->exposure12, one) || + !addAux(DXGI_FORMAT_R8_UNORM, inputDesc.Width, inputDesc.Height, impl->reactive12, + useFsr4 ? (enableOpticalFlow ? reactiveFsr4OpticalFlow : reactiveFsr4ZeroMv) : + (enableOpticalFlow ? reactive02 : reactive08)) || + !addAux(DXGI_FORMAT_R8_UNORM, inputDesc.Width, inputDesc.Height, impl->transparency12, zero)) { + return false; + } + if (!enableOpticalFlow && !addAux(DXGI_FORMAT_R16G16_FLOAT, inputDesc.Width, inputDesc.Height, + impl->zeroMotion12, zero)) return false; + impl->commandList12->ResourceBarrier(auxCount, auxBarriers); + hr = impl->commandList12->Close(); + if (FAILED(hr)) return false; + ID3D12CommandList* lists[]{ impl->commandList12.get() }; + impl->queue12->ExecuteCommandLists(1, lists); + + hr = impl->device11->CreateFence(0, D3D11_FENCE_FLAG_SHARED, IID_PPV_ARGS(impl->fence11.put())); + if (FAILED(hr)) return false; + HANDLE rawFence = nullptr; + hr = impl->fence11->CreateSharedHandle(nullptr, GENERIC_ALL, nullptr, &rawFence); + if (FAILED(hr)) return false; + wil::unique_handle fenceHandle(rawFence); + hr = impl->device12->OpenSharedHandle(fenceHandle.get(), IID_PPV_ARGS(impl->fence12.put())); + if (FAILED(hr) || !WaitForQueue(*impl)) return false; + + if (useFsr4) { + impl->providerModule = LoadLibraryW(L"amd_fidelityfx_upscaler_dx12.dll"); + if (!impl->providerModule) { + Logger::Get().Win32Error("Load FSR 4.1.1 provider DLL failed"); + return false; + } + if (!ForceFsr4Int8ProviderSupport(impl->providerModule)) { + Logger::Get().Error("Locate FSR 4.1.1 INT8 provider support check failed"); + return false; + } + } + + impl->loaderModule = LoadLibraryW(L"amd_fidelityfx_loader_dx12.dll"); + if (!impl->loaderModule) { + Logger::Get().Win32Error("Load amd_fidelityfx_loader_dx12.dll failed"); + return false; + } + impl->createContext = reinterpret_castcreateContext)>( + GetProcAddress(impl->loaderModule, "ffxCreateContext")); + impl->destroyContext = reinterpret_castdestroyContext)>( + GetProcAddress(impl->loaderModule, "ffxDestroyContext")); + impl->dispatch = reinterpret_castdispatch)>( + GetProcAddress(impl->loaderModule, "ffxDispatch")); + impl->query = reinterpret_castquery)>( + GetProcAddress(impl->loaderModule, "ffxQuery")); + if (!impl->createContext || !impl->destroyContext || !impl->dispatch || !impl->query) { + Logger::Get().Error("AMD FidelityFX loader exports are incomplete"); + return false; + } + + ffxQueryDescGetVersions versionQuery{}; + versionQuery.header.type = FFX_API_QUERY_DESC_TYPE_GET_VERSIONS; + versionQuery.createDescType = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE; + versionQuery.device = impl->device12.get(); + uint64_t versionCount = 0; + versionQuery.outputCount = &versionCount; + ffxReturnCode_t rc = impl->query(nullptr, &versionQuery.header); + if (rc != FFX_API_RETURN_OK || !versionCount) { + Logger::Get().Error(fmt::format("Query FSR upscaler versions failed ({})", (uint32_t)rc)); + return false; + } + std::vector versionIds(versionCount); + std::vector versionNames(versionCount); + versionQuery.versionIds = versionIds.data(); + versionQuery.versionNames = versionNames.data(); + rc = impl->query(nullptr, &versionQuery.header); + if (rc != FFX_API_RETURN_OK) return false; + uint64_t selectedVersionId = 0; + const char* requestedVersion = useFsr4 ? "4.1.1" : "3.1.5"; + std::string availableVersions; + for (uint64_t i = 0; i < versionCount; ++i) { + const char* name = versionNames[i] ? versionNames[i] : "unknown"; + if (!availableVersions.empty()) availableVersions += ", "; + availableVersions += name; + if (strstr(name, requestedVersion)) selectedVersionId = versionIds[i]; + } + if (!selectedVersionId) { + Logger::Get().Error(fmt::format("{} provider not found; available: {}", upscalerName, availableVersions)); + return false; + } + + impl->createDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE; + impl->createDesc.flags = FFX_UPSCALE_ENABLE_DEPTH_INVERTED | + FFX_UPSCALE_ENABLE_DEPTH_INFINITE | FFX_UPSCALE_ENABLE_NON_LINEAR_COLORSPACE; + impl->createDesc.maxRenderSize = { inputDesc.Width, inputDesc.Height }; + impl->createDesc.maxUpscaleSize = { outputDesc.Width, outputDesc.Height }; + impl->backendDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12; + impl->backendDesc.device = impl->device12.get(); + impl->apiVersion.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE_VERSION; + impl->apiVersion.version = FFX_UPSCALER_VERSION; + impl->overrideVersion.header.type = FFX_API_DESC_TYPE_OVERRIDE_VERSION; + impl->overrideVersion.versionId = selectedVersionId; + impl->createDesc.header.pNext = &impl->backendDesc.header; + impl->backendDesc.header.pNext = &impl->apiVersion.header; + impl->apiVersion.header.pNext = &impl->overrideVersion.header; + rc = impl->createContext(&impl->context, &impl->createDesc.header, nullptr); + if (rc != FFX_API_RETURN_OK || !impl->context) { + const char* hint = useFsr4 + ? "; FSR4 INT8 may still be rejected by GPU capability detection" + : ""; + Logger::Get().Error(fmt::format("Create {} context failed ({}){}", + upscalerName, (uint32_t)rc, hint)); + return false; + } + Logger::Get().Info(fmt::format( + "{} D3D11/D3D12 backend initialized (opticalFlow={}, jitter={}, virtual auxiliary inputs): {}x{} -> {}x{}", + upscalerName, enableOpticalFlow, enableJitter, + inputDesc.Width, inputDesc.Height, outputDesc.Width, outputDesc.Height)); + _impl = std::move(impl); + return true; +} + +bool FSR3ZeroMVUpscaler::Resize(DeviceResources& resources, ID3D11Texture2D* input, + ID3D11Texture2D* output) noexcept { + return Initialize(resources, input, output, _enableOpticalFlow, _enableJitter, _useFsr4); +} + +static float Halton(uint32_t index, uint32_t base) noexcept { + float result = 0.0f; + float fraction = 1.0f; + while (index) { + fraction /= (float)base; + result += fraction * (float)(index % base); + index /= base; + } + return result; +} + +bool FSR3ZeroMVUpscaler::Draw(const NativeEffectDrawContext& drawContext) noexcept { + ID3D11Texture2D* input = drawContext.input; + ID3D11Texture2D* output = drawContext.output; + if (!_impl || !_impl->context) return false; + Impl& impl = *_impl; + if (!WaitForFence(impl, impl.lastSubmittedValue)) return false; + impl.context11->CopyResource(impl.sharedInput11.get(), input); + if (impl.enableOpticalFlow) { + if (!impl.opticalFlow->Estimate(input)) return false; + impl.context11->CopyResource(impl.sharedMotion11.get(), impl.opticalFlow->GetMotionTexture()); + } + const uint64_t inputReady = ++impl.fenceValue; + HRESULT hr = impl.context11->Signal(impl.fence11.get(), inputReady); + if (FAILED(hr)) return false; + impl.context11->Flush(); + hr = impl.queue12->Wait(impl.fence12.get(), inputReady); + if (FAILED(hr)) return false; + + hr = impl.allocator12->Reset(); + if (SUCCEEDED(hr)) hr = impl.commandList12->Reset(impl.allocator12.get(), nullptr); + if (FAILED(hr)) return false; + D3D12_RESOURCE_BARRIER barriers[3]{}; + barriers[0].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barriers[0].Transition = { impl.sharedInput12.get(), D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE }; + barriers[1].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barriers[1].Transition = { impl.sharedOutput12.get(), D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_UNORDERED_ACCESS }; + const UINT barrierCount = impl.enableOpticalFlow ? 3 : 2; + if (impl.enableOpticalFlow) { + barriers[2].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barriers[2].Transition = { impl.sharedMotion12.get(), D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE }; + } + impl.commandList12->ResourceBarrier(barrierCount, barriers); + + ffxDispatchDescUpscale desc{}; + desc.header.type = FFX_API_DISPATCH_DESC_TYPE_UPSCALE; + desc.commandList = impl.commandList12.get(); + desc.color = ffxApiGetResourceDX12(impl.sharedInput12.get(), FFX_API_RESOURCE_STATE_COMPUTE_READ); + desc.depth = ffxApiGetResourceDX12(impl.flatDepth12.get(), FFX_API_RESOURCE_STATE_COMPUTE_READ); + desc.motionVectors = ffxApiGetResourceDX12( + impl.enableOpticalFlow ? impl.sharedMotion12.get() : impl.zeroMotion12.get(), + FFX_API_RESOURCE_STATE_COMPUTE_READ); + desc.exposure = ffxApiGetResourceDX12(impl.exposure12.get(), FFX_API_RESOURCE_STATE_COMPUTE_READ); + desc.reactive = ffxApiGetResourceDX12(impl.reactive12.get(), FFX_API_RESOURCE_STATE_COMPUTE_READ); + desc.transparencyAndComposition = ffxApiGetResourceDX12( + impl.transparency12.get(), FFX_API_RESOURCE_STATE_COMPUTE_READ); + desc.output = ffxApiGetResourceDX12(impl.sharedOutput12.get(), FFX_API_RESOURCE_STATE_UNORDERED_ACCESS); + if (impl.enableJitter) { + // Metadata-only jitter: the captured source frame itself is not projection-jittered. + const uint32_t sample = (impl.frameIndex++ & 7u) + 1u; + desc.jitterOffset = { Halton(sample, 2) - 0.5f, Halton(sample, 3) - 0.5f }; + } else { + desc.jitterOffset = { 0.0f, 0.0f }; + } + desc.motionVectorScale = { 1.0f, 1.0f }; + desc.renderSize = { impl.inputWidth, impl.inputHeight }; + desc.upscaleSize = { impl.outputWidth, impl.outputHeight }; + desc.enableSharpening = true; + desc.sharpness = 0.2f; + desc.frameTimeDelta = 16.6667f; + desc.preExposure = 1.0f; + desc.reset = impl.resetHistory; + desc.cameraNear = 1.0f; + desc.cameraFar = FLT_MAX; + desc.cameraFovAngleVertical = 1.04719755f; + desc.viewSpaceToMetersFactor = 1.0f; + desc.flags = FFX_UPSCALE_FLAG_NON_LINEAR_COLOR_SRGB; + const ffxReturnCode_t rc = impl.dispatch(&impl.context, &desc.header); + if (rc != FFX_API_RETURN_OK) { + Logger::Get().Error(fmt::format("Dispatch {} failed ({})", + impl.useFsr4 ? "FSR 4.1.1" : "FSR 3.1.5", (uint32_t)rc)); + return false; + } + for (UINT i = 0; i < barrierCount; ++i) { + std::swap(barriers[i].Transition.StateBefore, barriers[i].Transition.StateAfter); + } + impl.commandList12->ResourceBarrier(barrierCount, barriers); + hr = impl.commandList12->Close(); + if (FAILED(hr)) return false; + ID3D12CommandList* lists[]{ impl.commandList12.get() }; + impl.queue12->ExecuteCommandLists(1, lists); + const uint64_t outputReady = ++impl.fenceValue; + hr = impl.queue12->Signal(impl.fence12.get(), outputReady); + impl.lastSubmittedValue = outputReady; + if (SUCCEEDED(hr)) hr = impl.context11->Wait(impl.fence11.get(), outputReady); + if (FAILED(hr)) return false; + impl.context11->CopyResource(output, impl.sharedOutput11.get()); + impl.resetHistory = false; + return true; +} + +} + +#else + +namespace Magpie { +struct FSR3ZeroMVUpscaler::Impl {}; +FSR3ZeroMVUpscaler::FSR3ZeroMVUpscaler() = default; +FSR3ZeroMVUpscaler::~FSR3ZeroMVUpscaler() = default; +bool FSR3ZeroMVUpscaler::Initialize(DeviceResources&, ID3D11Texture2D*, ID3D11Texture2D*, bool, bool, bool) noexcept { + Logger::Get().Error("FSR3 support is not enabled in this build"); + return false; +} +bool FSR3ZeroMVUpscaler::Resize(DeviceResources&, ID3D11Texture2D*, ID3D11Texture2D*) noexcept { return false; } +bool FSR3ZeroMVUpscaler::Draw(const NativeEffectDrawContext&) noexcept { return false; } +} + +#endif diff --git a/src/Magpie.Core/FSR3ZeroMVUpscaler.h b/src/Magpie.Core/FSR3ZeroMVUpscaler.h new file mode 100644 index 000000000..95510b18b --- /dev/null +++ b/src/Magpie.Core/FSR3ZeroMVUpscaler.h @@ -0,0 +1,33 @@ +#pragma once +#include "NativeEffectBackend.h" + +namespace Magpie { + +class DeviceResources; + +// Experimental FSR 3.1.5 / FSR 4.1.1 upscaler running on D3D12 through +// resources shared with Magpie's D3D11 renderer. Frame generation is omitted. +class FSR3ZeroMVUpscaler final : public NativeEffectBackend { +public: + struct Impl; + + FSR3ZeroMVUpscaler(); + FSR3ZeroMVUpscaler(const FSR3ZeroMVUpscaler&) = delete; + FSR3ZeroMVUpscaler& operator=(const FSR3ZeroMVUpscaler&) = delete; + ~FSR3ZeroMVUpscaler() override; + + bool Initialize(DeviceResources& resources, ID3D11Texture2D* input, + ID3D11Texture2D* output, bool enableOpticalFlow = false, + bool enableJitter = false, bool useFsr4 = false) noexcept; + bool Resize(DeviceResources& resources, ID3D11Texture2D* input, + ID3D11Texture2D* output) noexcept override; + bool Draw(const NativeEffectDrawContext& context) noexcept override; + +private: + std::unique_ptr _impl; + bool _enableOpticalFlow = false; + bool _enableJitter = false; + bool _useFsr4 = false; +}; + +} diff --git a/src/Magpie.Core/FrameGuidanceService.cpp b/src/Magpie.Core/FrameGuidanceService.cpp index 1317ac868..52a204590 100644 --- a/src/Magpie.Core/FrameGuidanceService.cpp +++ b/src/Magpie.Core/FrameGuidanceService.cpp @@ -3,6 +3,7 @@ #include "DeviceResources.h" #include "Logger.h" #include "DirectXHelper.h" +#include "ScalingWindow.h" namespace Magpie { @@ -216,8 +217,12 @@ struct FrameGuidanceService::AdapterCache { } } static constexpr float ZERO[4]{}; - for (const auto& uav : entry->zeroUavs) { - context->ClearUnorderedAccessViewFloat(uav.get(), ZERO); + static constexpr float ONE[4]{ 1.0f, 1.0f, 1.0f, 1.0f }; + const float* depthClear = ScalingWindow::Get().Options().IsHdrCompatibilityEnabled() + ? ONE : ZERO; + for (size_t i = 0; i < entry->zeroUavs.size(); ++i) { + context->ClearUnorderedAccessViewFloat( + entry->zeroUavs[i].get(), i == 1 ? depthClear : ZERO); } Entry* result = entry.get(); entries.push_back(std::move(entry)); @@ -507,7 +512,11 @@ bool FrameGuidanceService::Initialize( const FrameGuidanceView& FrameGuidanceService::BeginFrame( FrameGuidanceFrameId frameId, ID3D11Texture2D* sourceFrame, - const FrameGuidanceRequirements& requirements + const FrameGuidanceRequirements& requirements, + uint64_t captureSequence, + uint64_t resourceGeneration, + int64_t timestamp100ns, + const ColorDescription& colorDescription ) noexcept { const FrameGuidanceExtent extent = GetTextureExtent(sourceFrame); if (_hasCachedFrame && _cachedFrameId == frameId && extent == _sourceExtent) { @@ -521,6 +530,10 @@ const FrameGuidanceView& FrameGuidanceService::BeginFrame( return _Produce({ .color = sourceFrame, .frameId = frameId, + .captureSequence = captureSequence, + .resourceGeneration = resourceGeneration, + .timestamp100ns = timestamp100ns, + .colorDescription = colorDescription, .sourceExtent = _sourceExtent, .validRegion = FrameGuidanceRegion::Full(_sourceExtent) }, requirements); diff --git a/src/Magpie.Core/FrameGuidanceService.h b/src/Magpie.Core/FrameGuidanceService.h index 975ce8be8..3c8f011c2 100644 --- a/src/Magpie.Core/FrameGuidanceService.h +++ b/src/Magpie.Core/FrameGuidanceService.h @@ -31,7 +31,11 @@ class FrameGuidanceService { const FrameGuidanceView& BeginFrame( FrameGuidanceFrameId frameId, ID3D11Texture2D* sourceFrame, - const FrameGuidanceRequirements& requirements + const FrameGuidanceRequirements& requirements, + uint64_t captureSequence = 0, + uint64_t resourceGeneration = 0, + int64_t timestamp100ns = 0, + const ColorDescription& colorDescription = {} ) noexcept; bool Resize( FrameGuidanceExtent sourceExtent, diff --git a/src/Magpie.Core/FrameGuidanceTypes.h b/src/Magpie.Core/FrameGuidanceTypes.h index 0ed4f2386..2a7e63577 100644 --- a/src/Magpie.Core/FrameGuidanceTypes.h +++ b/src/Magpie.Core/FrameGuidanceTypes.h @@ -1,5 +1,6 @@ #pragma once #include "MotionVectorRequest.h" +#include "HdrFrame.h" namespace Magpie { @@ -62,7 +63,10 @@ struct FrameGuidanceSyncPoint { }; struct FrameGuidanceMetadata { - FrameGuidanceFrameId frameId = 0; + FrameGuidanceFrameId frameId = 0; + uint64_t captureSequence = 0; + uint64_t resourceGeneration = 0; + int64_t timestamp100ns = 0; FrameGuidanceExtent sourceExtent{}; FrameGuidanceRegion validRegion{}; FrameGuidanceSyncPoint sync{}; @@ -136,8 +140,12 @@ inline FrameGuidanceView SelectFrameGuidanceChannels( struct MotionVectorProviderOutput; struct FrameGuidanceFrame { - ID3D11Texture2D* color = nullptr; - FrameGuidanceFrameId frameId = 0; + ID3D11Texture2D* color = nullptr; + FrameGuidanceFrameId frameId = 0; + uint64_t captureSequence = 0; + uint64_t resourceGeneration = 0; + int64_t timestamp100ns = 0; + ColorDescription colorDescription{}; FrameGuidanceExtent sourceExtent{}; FrameGuidanceRegion validRegion{}; }; diff --git a/src/Magpie.Core/FrameSourceBase.cpp b/src/Magpie.Core/FrameSourceBase.cpp index 78869e8a0..ceaade36b 100644 --- a/src/Magpie.Core/FrameSourceBase.cpp +++ b/src/Magpie.Core/FrameSourceBase.cpp @@ -5,6 +5,8 @@ #include "DeviceResources.h" #include "DirectXHelper.h" #include "Logger.h" +#include "Win32Helper.h" +#include "HdrDiagnostics.h" #include "ScalingOptions.h" #include "ScalingWindow.h" #include "shaders/DuplicateFrameCS.h" @@ -47,6 +49,22 @@ bool FrameSourceBase::Initialize(DeviceResources& deviceResources, BackendDescri return false; } + _hdrEnabled = ScalingWindow::Get().Options().IsHdrCompatibilityEnabled(); + if (_hdrEnabled && + !_hdrProcessor.Initialize(deviceResources, descriptorStore)) { + Logger::Get().Error("初始化 HDR 捕获处理器失败"); + return false; + } + if (_hdrEnabled) { + D3D11_TEXTURE2D_DESC sourceDesc{}; + _output->GetDesc(&sourceDesc); + if (!_hdrProcessor.Prepare( + _output.get(), sourceDesc.Format, _GetSourceColorDescription())) { + Logger::Get().Error("准备 HDR 捕获输出失败"); + return false; + } + } + assert(_output); _outputSrv = descriptorStore.GetShaderResourceView(_output.get()); if (!_outputSrv) { @@ -59,6 +77,48 @@ bool FrameSourceBase::Initialize(DeviceResources& deviceResources, BackendDescri FrameSourceState FrameSourceBase::Update() noexcept { const FrameSourceState state = _Update(); + if (state == FrameSourceState::NewFrame && _hdrEnabled) { + if (_hdrFrameSequence != _captureSequence) { + ++_resourceGeneration; + _hdrFrameSequence = _captureSequence; + } + D3D11_TEXTURE2D_DESC sourceDesc{}; + _output->GetDesc(&sourceDesc); + ColorDescription sourceColor = _GetSourceColorDescription(); + if (!_hdrProcessor.Process(_output.get(), HdrFrameMetadata{ + .frameId = _captureSequence, + .captureSequence = _captureSequence, + .resourceGeneration = _resourceGeneration, + .timestamp100ns = _captureTimestamp100ns, + .width = sourceDesc.Width, + .height = sourceDesc.Height, + .sourceFormat = sourceDesc.Format, + .color = sourceColor, + .stage = HdrFrameStage::RawCapture, + .valid = true + })) { + _hdrFrameReady = false; + Logger::Get().Error("HDR 捕获帧处理失败"); + return FrameSourceState::Error; + } else { + _hdrFrameReady = true; + if (!_hdrDiagnosticsLogged) { + HdrDiagnostics diagnostics{}; + diagnostics.hdrOptionEnabled = true; + diagnostics.captureMethod = Name(); + diagnostics.sourceFormat = sourceDesc.Format; + diagnostics.sourceColorDescription = _hdrProcessor.GetFrameMetadata().color; + diagnostics.selectedAdapterProfile = HdrAdapterProfile::DirectFP16; + diagnostics.conversionPath = "capture->canonicalFP16"; + if (!_hdrProcessor.LastAssumption().empty()) AppendHdrAssumption(diagnostics, _hdrProcessor.LastAssumption()); + LogHdrDiagnostics(diagnostics, true); + _hdrDiagnosticsLogged = true; + } + } + } + if (_hdrEnabled && state == FrameSourceState::NewFrame && !_hdrFrameReady) { + return FrameSourceState::Error; + } const bool newSequence = state == FrameSourceState::NewFrame && _duplicateCaptureSequence != _captureSequence; if (newSequence) { @@ -165,6 +225,102 @@ FrameSourceState FrameSourceBase::Update() noexcept { } } +ColorDescription FrameSourceBase::_GetSourceColorDescription() const noexcept { + ColorDescription result{}; + if (!_deviceResources) return result; + + const HMONITOR monitor = MonitorFromWindow( + ScalingWindow::Get().SrcTracker().Handle(), MONITOR_DEFAULTTONEAREST); + if (!monitor) return result; + + IDXGIAdapter4* adapter = _deviceResources->GetGraphicsAdapter(); + if (!adapter) return result; + + for (UINT index = 0; ; ++index) { + winrt::com_ptr output; + if (FAILED(adapter->EnumOutputs(index, output.put()))) break; + + DXGI_OUTPUT_DESC outputDesc{}; + if (FAILED(output->GetDesc(&outputDesc)) || outputDesc.Monitor != monitor) continue; + + winrt::com_ptr output6 = output.try_as(); + if (!output6) return result; + + DXGI_OUTPUT_DESC1 desc1{}; + if (FAILED(output6->GetDesc1(&desc1))) return result; + + result.dxgiColorSpace = desc1.ColorSpace; + result.displayHdrEnabled = + desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709 || + desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; + const bool measuredPeak = desc1.MaxLuminance > 0.0f; + result.displayPeakNits = measuredPeak ? desc1.MaxLuminance : 1000.0f; + result.metadata.maxMasteringLuminanceNits = desc1.MaxLuminance; + result.metadata.minMasteringLuminanceNits = desc1.MinLuminance; + result.metadata.maxFrameAverageLightLevelNits = desc1.MaxFullFrameLuminance; + // WGC scRGB stores scene-linear values with 1.0 == 80 nit. SDR content + // rendered on an HDR desktop is raised by the monitor's SDR white-level + // setting (for example 4.5x == 360 nit), so the source description must + // carry that measured white point for the paired SDR bridge. + const float measuredSdrWhite = Win32Helper::GetMonitorSdrWhiteNits(monitor); + result.referenceWhiteNits = 80.0f; + result.sdrWhiteNits = measuredSdrWhite > 0.0f ? measuredSdrWhite : 80.0f; + result.range = HdrColorRange::Full; + result.isInferred = !measuredPeak; + + D3D11_TEXTURE2D_DESC capturedDesc{}; + if (_output) { + _output->GetDesc(&capturedDesc); + } + if (capturedDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM || + capturedDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM) { + // GDI, DWM shared-surface, and Desktop Duplication expose the + // selected capture as an 8-bit display-referred surface. Monitor + // PQ metadata describes the display, not these stored code values. + result.dxgiColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; + result.primaries = HdrColorPrimaries::Rec709; + result.transfer = HdrTransferFunction::SRGB; + result.range = HdrColorRange::Full; + result.isSceneReferred = false; + result.displayHdrEnabled = false; + result.isInferred = false; + return result; + } + + switch (desc1.ColorSpace) { + case DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020: + result.primaries = HdrColorPrimaries::Rec2020; + result.transfer = HdrTransferFunction::PQ; + break; + case DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709: + result.primaries = HdrColorPrimaries::Rec709; + result.transfer = HdrTransferFunction::Linear; + result.range = HdrColorRange::SceneLinear; + result.isSceneReferred = true; + break; + case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709: + default: + result.primaries = HdrColorPrimaries::Rec709; + result.transfer = HdrTransferFunction::SRGB; + result.range = HdrColorRange::Full; + break; + } + return result; + } + + return result; +} + +bool FrameSourceBase::PrepareHdrOutputForResize() noexcept { + if (!_hdrEnabled || !_output) return true; + D3D11_TEXTURE2D_DESC sourceDesc{}; + _output->GetDesc(&sourceDesc); + _hdrFrameReady = false; + ++_resourceGeneration; + _hdrFrameSequence = 0; + return _hdrProcessor.Prepare(_output.get(), sourceDesc.Format, _GetSourceColorDescription()); +} + std::pair FrameSourceBase::GetStatisticsForDynamicDetection() const noexcept { return _statistics.load(std::memory_order_relaxed); } diff --git a/src/Magpie.Core/FrameSourceBase.h b/src/Magpie.Core/FrameSourceBase.h index d5a744acf..30afc9348 100644 --- a/src/Magpie.Core/FrameSourceBase.h +++ b/src/Magpie.Core/FrameSourceBase.h @@ -1,4 +1,5 @@ #pragma once +#include "HdrCaptureProcessor.h" namespace Magpie { @@ -37,6 +38,7 @@ class FrameSourceBase { // Backend-thread state. A sequence changes only on a real capture discontinuity; // the first valid frame must reach temporal consumers even if its pixels match. uint64_t CaptureSequence() const noexcept { return _captureSequence; } + uint64_t ResourceGeneration() const noexcept { return _resourceGeneration; } bool IsCaptureInterrupted() const noexcept { return _captureInterrupted; } int64_t CaptureTimestamp100ns() const noexcept { return _captureTimestamp100ns; } const char* CaptureErrorContext() const noexcept { return _captureErrorContext; } @@ -46,10 +48,36 @@ class FrameSourceBase { _forceDuplicateFrameDetection = value; } - ID3D11Texture2D* GetOutput() noexcept { - return _output.get(); + // Compatibility getter. HDR callers must use GetCanonicalFrame(); SDR keeps + // the original raw capture texture contract. + ID3D11Texture2D* GetOutput() noexcept { return _hdrEnabled ? + _hdrProcessor.GetCanonicalTexture() : _output.get(); } + + ID3D11Texture2D* GetPipelineTexture() const noexcept { + return _hdrEnabled ? _hdrProcessor.GetCanonicalTexture() : _output.get(); + } + + HdrFrame GetCanonicalFrame() const noexcept { + if (!_hdrEnabled) return {}; + HdrFrame frame{ + .texture = _hdrProcessor.GetCanonicalTexture(), + .metadata = _hdrProcessor.GetFrameMetadata(), + .workingFormat = DXGI_FORMAT_R16G16B16A16_FLOAT + }; + return frame; } + // Raw capture is an internal capture-front-end input. Renderer/effect code + // must use GetCanonicalFrame() in HDR mode. + ID3D11Texture2D* GetRawCaptureTexture() const noexcept { return _output.get(); } + + const HdrFrameMetadata& GetHdrFrameMetadata() const noexcept { + return _hdrProcessor.GetFrameMetadata(); + } + + bool PrepareHdrOutputForResize() noexcept; + bool IsHdrFrameReady() const noexcept { return !_hdrEnabled || _hdrFrameReady; } + std::pair GetStatisticsForDynamicDetection() const noexcept; virtual const char* Name() const noexcept = 0; @@ -61,6 +89,7 @@ class FrameSourceBase { virtual void OnCursorVisibilityChanged(bool /*isVisible*/, bool /*onDestory*/) noexcept {}; protected: + virtual ColorDescription _GetSourceColorDescription() const noexcept; uint64_t _captureSequence = 0; bool _captureInterrupted = false; int64_t _captureTimestamp100ns = 0; @@ -85,6 +114,12 @@ class FrameSourceBase { DeviceResources* _deviceResources = nullptr; BackendDescriptorStore* _descriptorStore = nullptr; winrt::com_ptr _output; + HdrCaptureProcessor _hdrProcessor; + bool _hdrEnabled = false; + bool _hdrFrameReady = false; + uint64_t _hdrFrameSequence = 0; + uint64_t _resourceGeneration = 1; + bool _hdrDiagnosticsLogged = false; ID3D11ShaderResourceView* _outputSrv = nullptr; winrt::com_ptr _resultBuffer; diff --git a/src/Magpie.Core/GraphicsCaptureFrameSource.cpp b/src/Magpie.Core/GraphicsCaptureFrameSource.cpp index ee1f808f3..81453d2f0 100644 --- a/src/Magpie.Core/GraphicsCaptureFrameSource.cpp +++ b/src/Magpie.Core/GraphicsCaptureFrameSource.cpp @@ -78,7 +78,9 @@ bool GraphicsCaptureFrameSource::_Initialize() noexcept { _output = DirectXHelper::CreateTexture2D( d3dDevice, - DXGI_FORMAT_B8G8R8A8_UNORM, + ScalingWindow::Get().Options().IsHdrCompatibilityEnabled() + ? DXGI_FORMAT_R16G16B16A16_FLOAT + : DXGI_FORMAT_B8G8R8A8_UNORM, _frameBox.right - _frameBox.left, _frameBox.bottom - _frameBox.top, D3D11_BIND_SHADER_RESOURCE @@ -92,6 +94,24 @@ bool GraphicsCaptureFrameSource::_Initialize() noexcept { return true; } +ColorDescription GraphicsCaptureFrameSource::_GetSourceColorDescription() const noexcept { + ColorDescription result = FrameSourceBase::_GetSourceColorDescription(); + if (!ScalingWindow::Get().Options().IsHdrCompatibilityEnabled()) { + return result; + } + + // WGC's FP16 capture surface uses linear scRGB. The monitor metadata still + // supplies the display peak used by normalization. + result.dxgiColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709; + result.primaries = HdrColorPrimaries::Rec709; + result.transfer = HdrTransferFunction::Linear; + result.range = HdrColorRange::SceneLinear; + result.isSceneReferred = true; + result.isInferred = true; + result.preExposure = 1.0f; + return result; +} + bool GraphicsCaptureFrameSource::Start() noexcept { _DisableRoundCornerInWin11(); return _StartCapture("initial start"); @@ -163,8 +183,12 @@ FrameSourceState GraphicsCaptureFrameSource::_Update() noexcept { } frame.Close(); } else { - if (_lastFrameTimestamp100ns && timestamp - _lastFrameTimestamp100ns >= 5'000'000) { - _InterruptCapture("capture timestamp discontinuity"); + // Preserve the long-pause optimization, with a 5-second debounce. + // WGC can legitimately skip hundreds of milliseconds for static or + // throttled windows; those gaps must not reset FG history and flash. + if (_lastFrameTimestamp100ns && + timestamp - _lastFrameTimestamp100ns >= 50'000'000) { + _InterruptCapture("capture long-pause discontinuity"); } _deviceResources->GetD3DDC()->CopySubresourceRegion( _output.get(), 0, 0, 0, 0, texture.get(), 0, &_frameBox); @@ -433,7 +457,9 @@ bool GraphicsCaptureFrameSource::_StartCapture(const char* reason) noexcept { #endif _captureFramePool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded( _wrappedD3DDevice, - winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized, + ScalingWindow::Get().Options().IsHdrCompatibilityEnabled() + ? 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 cbdca83d9..0a315f398 100644 --- a/src/Magpie.Core/GraphicsCaptureFrameSource.h +++ b/src/Magpie.Core/GraphicsCaptureFrameSource.h @@ -28,6 +28,7 @@ class GraphicsCaptureFrameSource final : public FrameSourceBase { protected: bool _Initialize() noexcept override; + ColorDescription _GetSourceColorDescription() const noexcept override; FrameSourceState _Update() noexcept override; diff --git a/src/Magpie.Core/GroupAHdrRoutes.cpp b/src/Magpie.Core/GroupAHdrRoutes.cpp new file mode 100644 index 000000000..01d94f016 --- /dev/null +++ b/src/Magpie.Core/GroupAHdrRoutes.cpp @@ -0,0 +1,114 @@ +#include "pch.h" +#include "GroupAHdrRoutes.h" + +namespace Magpie { + +namespace { + +HdrFormatRoute MakeSdrFallback( + std::string_view effect, + std::string_view option, + DXGI_FORMAT backendFormat = DXGI_FORMAT_R8G8B8A8_UNORM, + HdrAlphaMode alpha = HdrAlphaMode::ForceOpaque +) { + return HdrFormatRoute{ + .effectId = std::string(effect), + .optionId = std::string(option), + .inputFormat = backendFormat, + .outputFormat = backendFormat, + .inputTransfer = HdrTransferFunction::SRGB, + .outputTransfer = HdrTransferFunction::SRGB, + .inputRange = HdrColorRange::Full, + .outputRange = HdrColorRange::Full, + .alphaMode = alpha, + .evidenceLevel = HdrEvidenceLevel::None, + .hdrNative = false, + .adapterProfile = HdrAdapterProfile::SDRCompatible, + .defaultForHdr = true, + .defaultForSdr = true, + }; +} + +HdrFormatRoute MakeCasFp16() { + return HdrFormatRoute{ + .effectId = "CAS", + .optionId = "fp16-conditional", + .inputFormat = DXGI_FORMAT_R16G16B16A16_FLOAT, + .outputFormat = DXGI_FORMAT_R16G16B16A16_FLOAT, + .inputTransfer = HdrTransferFunction::Linear, + .outputTransfer = HdrTransferFunction::Linear, + .inputRange = HdrColorRange::SceneLinear, + .outputRange = HdrColorRange::SceneLinear, + .alphaMode = HdrAlphaMode::ForceOpaque, + .evidenceLevel = HdrEvidenceLevel::ReferenceImplementation, + .hdrNative = false, + .adapterProfile = HdrAdapterProfile::ConditionalFP16, + .defaultForHdr = true, + .defaultForSdr = false, + }; +} + +bool IsGroupA(std::string_view effect) noexcept { + return effect == "Anime4K" || effect == "CAS" || effect == "CRT" || + effect == "CuNNy" || effect == "CuNNy2" || effect == "Diagnostics" || + effect == "FSRCNNX" || effect == "FXAA" || effect == "MLAA"; +} + +} + +GroupAHdrEffectDescription GetGroupAHdrEffectDescription( + std::string_view effectName, + int casFormatOption +) { + if (effectName == "CAS") { + HdrFormatRoutes routes; + // The production CAS CSO is compiled from the declared R8 texture + // contract. Keep HDR on the paired SDR adapter until a separately + // compiled FP16 CAS variant exists; changing the runtime surface alone + // would bind a route-incompatible UAV/SRV pair. + routes.push_back(MakeSdrFallback("CAS", "r8-sdr", DXGI_FORMAT_R8G8B8A8_UNORM)); + return { + .routes = std::move(routes), + .auxiliaryResources = "none; RGB input/output, alpha forced opaque", + .evidence = casFormatOption == 1 + ? "CAS FP16 request held behind a separately compiled variant; using declared R8 path" + : "Current Magpie CAS shader uses the declared R8 path", + }; + } + + if (!IsGroupA(effectName)) { + return {}; + } + + const char* auxiliary = "none"; + const char* evidence = "local shader declaration; public HDR protocol unspecified"; + if (effectName == "Anime4K") { + auxiliary = "variant-specific R16G16B16A16_FLOAT CNN surfaces; Thin_HQ R16G16_FLOAT gradient"; + } else if (effectName == "CRT") { + auxiliary = "GTU_v050 tex1 R16G16B16A16_FLOAT; other presets none"; + } else if (effectName == "CuNNy") { + auxiliary = "model-specific R8G8B8A8_SNORM t0..t7"; + } else if (effectName == "CuNNy2") { + auxiliary = "model-specific R8G8B8A8_UNORM T0..T15"; + } else if (effectName == "FSRCNNX") { + auxiliary = "featureMap1/2 and tex1..tex4 R16G16B16A16_FLOAT"; + } else if (effectName == "MLAA") { + auxiliary = "edgeMask R8G8_UNORM; edgeCounts R8G8B8A8_UNORM"; + } + + HdrFormatRoutes routes; + const HdrAlphaMode alpha = effectName == "Diagnostics" || effectName == "MLAA" + ? HdrAlphaMode::Preserve : HdrAlphaMode::ForceOpaque; + routes.push_back(MakeSdrFallback(effectName, "unknown-sdr-fallback", DXGI_FORMAT_R8G8B8A8_UNORM, alpha)); + return { + .routes = std::move(routes), + .auxiliaryResources = auxiliary, + .evidence = evidence, + }; +} + +HdrFormatRoutes GetGroupAHdrRoutes(std::string_view effectName, int casFormatOption) { + return GetGroupAHdrEffectDescription(effectName, casFormatOption).routes; +} + +} diff --git a/src/Magpie.Core/GroupAHdrRoutes.h b/src/Magpie.Core/GroupAHdrRoutes.h new file mode 100644 index 000000000..7b95922da --- /dev/null +++ b/src/Magpie.Core/GroupAHdrRoutes.h @@ -0,0 +1,25 @@ +#pragma once + +#include "HdrProtocol.h" + +#include + +namespace Magpie { + +// Effect-local protocol description for group A. The auxiliary text is kept +// alongside the structured route so diagnostics can expose intermediate +// resource boundaries without teaching the shared HDR dispatcher effect names. +struct GroupAHdrEffectDescription { + HdrFormatRoutes routes; + std::string_view auxiliaryResources; + std::string_view evidence; +}; + +GroupAHdrEffectDescription GetGroupAHdrEffectDescription( + std::string_view effectName, + int casFormatOption = 0 +); + +HdrFormatRoutes GetGroupAHdrRoutes(std::string_view effectName, int casFormatOption = 0); + +} diff --git a/src/Magpie.Core/GroupBEffectProtocol.h b/src/Magpie.Core/GroupBEffectProtocol.h new file mode 100644 index 000000000..c4ed70f72 --- /dev/null +++ b/src/Magpie.Core/GroupBEffectProtocol.h @@ -0,0 +1,48 @@ +#pragma once + +#include "HdrFrame.h" + +#include +#include + +namespace Magpie { + +// Effect-local protocol values for the DLSS/FSR/NIS/Optical Flow family. +// These fields deliberately stay separate from the renderer-wide HDR model: +// each SDK consumes a different transfer, range, and auxiliary-resource set. +enum class GroupBTransfer : uint8_t { Unspecified, Linear, SRGB, PQ }; + +struct FsrHdrProtocol { + bool hdrColorInput = false; + GroupBTransfer transfer = GroupBTransfer::Linear; + float preExposure = 1.0f; + float exposure = 1.0f; + bool depthInverted = true; + bool depthInfinite = true; + bool useReactiveMask = true; + bool useTransparencyMask = true; +}; + +struct NisHdrProtocol { + enum class Mode : uint8_t { None, Linear, PQ }; + Mode mode = Mode::None; + float linearMax = 12.5f; +}; + +struct AmdOpticalFlowHdrProtocol { + GroupBTransfer transfer = GroupBTransfer::SRGB; + std::array minMaxLuminance{ 0.0f, 1.0f }; +}; + +struct DlssnrExperimentProtocol { + bool enabled = false; + // Values observed in DLSSNR-HDR-Experiments. Keep this a selector, not a + // claimed normalization equation. + float scale = 1.0f; + bool IsVerifiedScale() const noexcept { + return std::isfinite(scale) && + (scale == 1.0f || scale == 2.0f || scale == 4.5f); + } +}; + +} // namespace Magpie diff --git a/src/Magpie.Core/GroupBHdrRoutes.cpp b/src/Magpie.Core/GroupBHdrRoutes.cpp new file mode 100644 index 000000000..556d45934 --- /dev/null +++ b/src/Magpie.Core/GroupBHdrRoutes.cpp @@ -0,0 +1,94 @@ +#include "pch.h" +#include "GroupBHdrRoutes.h" + +namespace Magpie { + +namespace { + +HdrFormatRoute MakeRoute( + std::string_view effect, + std::string_view option, + DXGI_FORMAT inputFormat, + DXGI_FORMAT outputFormat, + HdrAdapterProfile profile, + HdrTransferFunction inputTransfer, + HdrTransferFunction outputTransfer, + HdrColorRange inputRange, + HdrColorRange outputRange, + HdrEvidenceLevel evidence, + bool hdrNative, + float normalizationScale = 1.0f +) { + return HdrFormatRoute{ + .effectId = std::string(effect), + .optionId = std::string(option), + .inputFormat = inputFormat, + .outputFormat = outputFormat, + .inputTransfer = inputTransfer, + .outputTransfer = outputTransfer, + .inputRange = inputRange, + .outputRange = outputRange, + .alphaMode = HdrAlphaMode::ForceOpaque, + .evidenceLevel = evidence, + .hdrNative = hdrNative, + .adapterProfile = profile, + .defaultForHdr = true, + .defaultForSdr = false, + .normalizationScale = normalizationScale, + }; +} + +HdrFormatRoute MakeSdr(std::string_view effect, std::string_view option) { + return MakeRoute(effect, option, DXGI_FORMAT_R8G8B8A8_UNORM, + DXGI_FORMAT_R8G8B8A8_UNORM, HdrAdapterProfile::SDRCompatible, + HdrTransferFunction::SRGB, HdrTransferFunction::SRGB, + HdrColorRange::Full, HdrColorRange::Full, + HdrEvidenceLevel::PublicApiContract, false); +} + +} + +HdrFormatRoutes GetGroupBHdrRoutes( + std::string_view effectGroup, + bool experimentalDlssnr, + float dlssnrScale +) noexcept { + if (effectGroup == "DLSSNR") { + if (experimentalDlssnr && (dlssnrScale == 1.0f || + dlssnrScale == 2.0f || dlssnrScale == 4.5f)) { + return { MakeRoute("DLSSNR", "experimental-fp16-scale", + DXGI_FORMAT_R16G16B16A16_FLOAT, + DXGI_FORMAT_R16G16B16A16_FLOAT, + HdrAdapterProfile::BoundedHDR, + HdrTransferFunction::Linear, + HdrTransferFunction::Linear, + HdrColorRange::SceneLinear, + HdrColorRange::SceneLinear, + HdrEvidenceLevel::LocalValidation, + false, dlssnrScale) }; + } + return { MakeSdr("DLSSNR", "sdr-r8") }; + } + + if (effectGroup == "DLSS" || effectGroup == "FSR2" || + effectGroup == "FSR3" || effectGroup == "FSR4" || + effectGroup == "NIS") { + return { MakeRoute(effectGroup, "hdr-linear-fp16", + DXGI_FORMAT_R16G16B16A16_FLOAT, + DXGI_FORMAT_R16G16B16A16_FLOAT, + HdrAdapterProfile::DirectFP16, + HdrTransferFunction::Linear, + HdrTransferFunction::Linear, + HdrColorRange::SceneLinear, + HdrColorRange::SceneLinear, + HdrEvidenceLevel::PublicApiContract, true) }; + } + + if (effectGroup == "FSR") { + return { MakeSdr("FSR", "sdr-srgb") }; + } + + return {}; +} + +} diff --git a/src/Magpie.Core/GroupBHdrRoutes.h b/src/Magpie.Core/GroupBHdrRoutes.h new file mode 100644 index 000000000..ce0b79298 --- /dev/null +++ b/src/Magpie.Core/GroupBHdrRoutes.h @@ -0,0 +1,13 @@ +#pragma once + +#include "HdrProtocol.h" + +namespace Magpie { + +HdrFormatRoutes GetGroupBHdrRoutes( + std::string_view effectGroup, + bool experimentalDlssnr = false, + float dlssnrScale = 1.0f +) noexcept; + +} diff --git a/src/Magpie.Core/HalfResOpticalFlow.cpp b/src/Magpie.Core/HalfResOpticalFlow.cpp new file mode 100644 index 000000000..32e593ed6 --- /dev/null +++ b/src/Magpie.Core/HalfResOpticalFlow.cpp @@ -0,0 +1,171 @@ +#include "pch.h" +#include "HalfResOpticalFlow.h" +#include "DirectXHelper.h" +#include "Logger.h" + +namespace Magpie { + +static constexpr char FLOW_HLSL[] = R"( +cbuffer FlowConstants : register(b0) { + uint2 FullSize; + uint2 HalfSize; +}; +Texture2D CurrentColor : register(t0); +Texture2D PreviousColor : register(t1); +RWTexture2D HalfFlowOut : register(u0); + +float Luma(float3 c) { return dot(c, float3(0.299, 0.587, 0.114)); } +int2 ClampPixel(int2 p) { return clamp(p, int2(0, 0), int2(FullSize) - 1); } +float SampleLuma(Texture2D tex, int2 p) { return Luma(tex.Load(int3(ClampPixel(p), 0)).rgb); } + +[numthreads(8, 8, 1)] +void EstimateHalf(uint3 tid : SV_DispatchThreadID) { + if (any(tid.xy >= HalfSize)) return; + int2 p = min(int2(tid.xy * 2 + 1), int2(FullSize) - 1); + static const int2 taps[5] = { + int2(0,0), int2(-2,0), int2(2,0), int2(0,-2), int2(0,2) + }; + float bestError = 3.402823e+38; + int2 bestOffset = int2(0, 0); + [unroll] for (int y = -2; y <= 2; ++y) { + [unroll] for (int x = -2; x <= 2; ++x) { + int2 candidate = int2(x, y) * 2; + float error = 0.0; + [unroll] for (int i = 0; i < 5; ++i) { + float a = SampleLuma(CurrentColor, p + taps[i]); + float b = SampleLuma(PreviousColor, p + candidate + taps[i]); + float d = a - b; + error += d * d; + } + // Prefer smaller motion when candidates are nearly equivalent. + error += dot(float2(candidate), float2(candidate)) * 0.000002; + if (error < bestError) { bestError = error; bestOffset = candidate; } + } + } + HalfFlowOut[tid.xy] = float2(bestOffset); +} + +Texture2D HalfFlowIn : register(t0); +RWTexture2D FullFlowOut : register(u0); + +[numthreads(8, 8, 1)] +void UpsampleFlow(uint3 tid : SV_DispatchThreadID) { + if (any(tid.xy >= FullSize)) return; + float2 hp = (float2(tid.xy) + 0.5) * 0.5 - 0.5; + int2 p0 = int2(floor(hp)); + float2 f = frac(hp); + int2 hi = int2(HalfSize) - 1; + float2 a = HalfFlowIn.Load(int3(clamp(p0, int2(0,0), hi), 0)); + float2 b = HalfFlowIn.Load(int3(clamp(p0 + int2(1,0), int2(0,0), hi), 0)); + float2 c = HalfFlowIn.Load(int3(clamp(p0 + int2(0,1), int2(0,0), hi), 0)); + float2 d = HalfFlowIn.Load(int3(clamp(p0 + int2(1,1), int2(0,0), hi), 0)); + FullFlowOut[tid.xy] = lerp(lerp(a,b,f.x), lerp(c,d,f.x), f.y); +} +)"; + +bool HalfResOpticalFlow::Initialize(ID3D11Device* device, ID3D11DeviceContext* context, + ID3D11Texture2D* input) noexcept { + _device = device; + _context = context; + D3D11_TEXTURE2D_DESC desc{}; + input->GetDesc(&desc); + _width = desc.Width; + _height = desc.Height; + _halfWidth = (_width + 1) / 2; + _halfHeight = (_height + 1) / 2; + + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + desc.MiscFlags = 0; + desc.CPUAccessFlags = 0; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.MipLevels = 1; + desc.ArraySize = 1; + HRESULT hr = _device->CreateTexture2D(&desc, nullptr, _previous.put()); + if (SUCCEEDED(hr)) hr = _device->CreateShaderResourceView(_previous.get(), nullptr, _previousSrv.put()); + if (FAILED(hr) || !_CreateInputSrv(input)) { + Logger::Get().ComError("Create optical-flow history resources failed", hr); + return false; + } + + _halfFlow = DirectXHelper::CreateTexture2D(_device, DXGI_FORMAT_R16G16_FLOAT, + _halfWidth, _halfHeight, D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS); + _fullFlow = DirectXHelper::CreateTexture2D(_device, DXGI_FORMAT_R16G16_FLOAT, + _width, _height, D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS); + if (!_halfFlow || !_fullFlow) return false; + hr = _device->CreateShaderResourceView(_halfFlow.get(), nullptr, _halfFlowSrv.put()); + if (SUCCEEDED(hr)) hr = _device->CreateUnorderedAccessView(_halfFlow.get(), nullptr, _halfFlowUav.put()); + if (SUCCEEDED(hr)) hr = _device->CreateUnorderedAccessView(_fullFlow.get(), nullptr, _fullFlowUav.put()); + if (FAILED(hr)) return false; + + winrt::com_ptr blob; + if (!DirectXHelper::CompileComputeShader(FLOW_HLSL, "EstimateHalf", blob.put(), "HalfResOpticalFlow")) return false; + hr = _device->CreateComputeShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, _estimateShader.put()); + blob = nullptr; + if (FAILED(hr) || !DirectXHelper::CompileComputeShader(FLOW_HLSL, "UpsampleFlow", blob.put(), "HalfResOpticalFlow")) return false; + hr = _device->CreateComputeShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, _upsampleShader.put()); + if (FAILED(hr)) return false; + + struct Constants { UINT full[2]; UINT half[2]; } constants{ + {_width, _height}, {_halfWidth, _halfHeight} + }; + D3D11_BUFFER_DESC cbd{ .ByteWidth = sizeof(Constants), .Usage = D3D11_USAGE_IMMUTABLE, + .BindFlags = D3D11_BIND_CONSTANT_BUFFER }; + D3D11_SUBRESOURCE_DATA initial{ .pSysMem = &constants }; + hr = _device->CreateBuffer(&cbd, &initial, _constantBuffer.put()); + if (FAILED(hr)) return false; + + static constexpr float ZERO[4]{}; + _context->ClearUnorderedAccessViewFloat(_fullFlowUav.get(), ZERO); + _context->CopyResource(_previous.get(), input); + _hasHistory = false; + Logger::Get().Info(fmt::format("Half-resolution optical flow initialized: {}x{} -> {}x{}", + _width, _height, _halfWidth, _halfHeight)); + return true; +} + +bool HalfResOpticalFlow::_CreateInputSrv(ID3D11Texture2D* input) noexcept { + if (_input == input && _inputSrv) return true; + _input = input; + _inputSrv = nullptr; + return SUCCEEDED(_device->CreateShaderResourceView(input, nullptr, _inputSrv.put())); +} + +bool HalfResOpticalFlow::Estimate(ID3D11Texture2D* input) noexcept { + if (!_CreateInputSrv(input)) return false; + if (!_hasHistory) { + static constexpr float ZERO[4]{}; + _context->ClearUnorderedAccessViewFloat(_fullFlowUav.get(), ZERO); + _context->CopyResource(_previous.get(), input); + _hasHistory = true; + return true; + } + + ID3D11Buffer* cb = _constantBuffer.get(); + _context->CSSetConstantBuffers(0, 1, &cb); + ID3D11ShaderResourceView* estimateSrvs[2]{ _inputSrv.get(), _previousSrv.get() }; + ID3D11UnorderedAccessView* halfUav = _halfFlowUav.get(); + _context->CSSetShader(_estimateShader.get(), nullptr, 0); + _context->CSSetShaderResources(0, 2, estimateSrvs); + _context->CSSetUnorderedAccessViews(0, 1, &halfUav, nullptr); + _context->Dispatch((_halfWidth + 7) / 8, (_halfHeight + 7) / 8, 1); + + ID3D11ShaderResourceView* nullSrvs[2]{}; + ID3D11UnorderedAccessView* nullUav = nullptr; + _context->CSSetShaderResources(0, 2, nullSrvs); + _context->CSSetUnorderedAccessViews(0, 1, &nullUav, nullptr); + ID3D11ShaderResourceView* halfSrv = _halfFlowSrv.get(); + ID3D11UnorderedAccessView* fullUav = _fullFlowUav.get(); + _context->CSSetShader(_upsampleShader.get(), nullptr, 0); + _context->CSSetShaderResources(0, 1, &halfSrv); + _context->CSSetUnorderedAccessViews(0, 1, &fullUav, nullptr); + _context->Dispatch((_width + 7) / 8, (_height + 7) / 8, 1); + _context->CSSetShaderResources(0, 2, nullSrvs); + _context->CSSetUnorderedAccessViews(0, 1, &nullUav, nullptr); + _context->CSSetShaderResources(0, 2, nullSrvs); + _context->CSSetUnorderedAccessViews(0, 1, &nullUav, nullptr); + _context->CSSetShader(nullptr, nullptr, 0); + _context->CopyResource(_previous.get(), input); + return true; +} + +} diff --git a/src/Magpie.Core/HalfResOpticalFlow.h b/src/Magpie.Core/HalfResOpticalFlow.h new file mode 100644 index 000000000..1e9838d7d --- /dev/null +++ b/src/Magpie.Core/HalfResOpticalFlow.h @@ -0,0 +1,43 @@ +#pragma once + +namespace Magpie { + +// Lightweight colour-only block matching. Motion is estimated on a half-size +// grid and expanded to render resolution for temporal upscalers. +class HalfResOpticalFlow { +public: + HalfResOpticalFlow() = default; + HalfResOpticalFlow(const HalfResOpticalFlow&) = delete; + HalfResOpticalFlow& operator=(const HalfResOpticalFlow&) = delete; + + bool Initialize(ID3D11Device* device, ID3D11DeviceContext* context, + ID3D11Texture2D* input) noexcept; + bool Estimate(ID3D11Texture2D* input) noexcept; + ID3D11Texture2D* GetMotionTexture() const noexcept { return _fullFlow.get(); } + void ResetHistory() noexcept { _hasHistory = false; } + +private: + bool _CreateInputSrv(ID3D11Texture2D* input) noexcept; + + ID3D11Device* _device = nullptr; + ID3D11DeviceContext* _context = nullptr; + ID3D11Texture2D* _input = nullptr; + UINT _width = 0; + UINT _height = 0; + UINT _halfWidth = 0; + UINT _halfHeight = 0; + bool _hasHistory = false; + winrt::com_ptr _previous; + winrt::com_ptr _previousSrv; + winrt::com_ptr _inputSrv; + winrt::com_ptr _halfFlow; + winrt::com_ptr _halfFlowSrv; + winrt::com_ptr _halfFlowUav; + winrt::com_ptr _fullFlow; + winrt::com_ptr _fullFlowUav; + winrt::com_ptr _estimateShader; + winrt::com_ptr _upsampleShader; + winrt::com_ptr _constantBuffer; +}; + +} diff --git a/src/Magpie.Core/HdrAdapterDispatcher.cpp b/src/Magpie.Core/HdrAdapterDispatcher.cpp new file mode 100644 index 000000000..92ab9f6e0 --- /dev/null +++ b/src/Magpie.Core/HdrAdapterDispatcher.cpp @@ -0,0 +1,96 @@ +#include "pch.h" +#include "HdrAdapterDispatcher.h" + +namespace Magpie { + +namespace { + +constexpr std::string_view PathForProfile(HdrAdapterProfile profile) noexcept { + switch (profile) { + case HdrAdapterProfile::DirectFP16: + return "canonicalFP16->DirectFP16->canonicalFP16"; + case HdrAdapterProfile::BoundedHDR: + return "canonicalFP16->boundedHdrEncode->backend->boundedHdrDecode->canonicalFP16"; + case HdrAdapterProfile::SDRCompatible: + return "canonicalFP16->HdrToSdr->backend->SdrToHdr->canonicalFP16"; + case HdrAdapterProfile::ConditionalFP16: + return "canonicalFP16->ConditionalFP16->canonicalFP16"; + case HdrAdapterProfile::PresentationTerminal: + return "canonicalFP16->presentationEncoder->presentationTerminal"; + case HdrAdapterProfile::Unknown: + default: + return "canonicalFP16->SdrCompatibleFallback->backend->SdrToHdr->canonicalFP16"; + } +} + +HdrTransformParameters ParametersForColor(const ColorDescription& color) noexcept { + const HdrTransformParameters params = HdrColorTransform::ForFrame(color); + return params.IsValid() ? params : HdrTransformParameters{}; +} + +} + +HdrAdapterPlan HdrAdapterDispatcher::BuildPlan( + const HdrFormatRoute& route, + const ColorDescription& sourceColor +) noexcept { + HdrAdapterPlan plan; + plan.routeId = route.Id(); + plan.profile = route.adapterProfile; + plan.alphaMode = route.alphaMode; + plan.forwardParameters = ParametersForColor(sourceColor); + plan.inverseParameters = ParametersForColor(sourceColor); + plan.normalizationScale = route.normalizationScale > 0.0f ? route.normalizationScale : 1.0f; + + switch (route.adapterProfile) { + case HdrAdapterProfile::DirectFP16: + plan.conversionPath = PathForProfile(HdrAdapterProfile::DirectFP16); + plan.requiresSdrMapping = false; + plan.requiresBoundedMapping = false; + break; + + case HdrAdapterProfile::BoundedHDR: + plan.conversionPath = PathForProfile(HdrAdapterProfile::BoundedHDR); + plan.requiresBoundedMapping = true; + break; + + case HdrAdapterProfile::SDRCompatible: + plan.conversionPath = PathForProfile(HdrAdapterProfile::SDRCompatible); + plan.requiresSdrMapping = true; + break; + + case HdrAdapterProfile::ConditionalFP16: + plan.conversionPath = PathForProfile(HdrAdapterProfile::ConditionalFP16); + plan.requiresSdrMapping = false; + plan.requiresBoundedMapping = false; + break; + + case HdrAdapterProfile::PresentationTerminal: + plan.conversionPath = PathForProfile(HdrAdapterProfile::PresentationTerminal); + plan.canonicalOutputFormat = DXGI_FORMAT_UNKNOWN; + plan.isPresentationTerminal = true; + break; + + case HdrAdapterProfile::Unknown: + default: + plan.conversionPath = PathForProfile(HdrAdapterProfile::Unknown); + plan.requiresSdrMapping = true; + plan.usesFallback = true; + plan.fallbackReason = "No verified HDR route; using the existing compatible SDR fallback."; + if (plan.alphaMode == HdrAlphaMode::Unknown) { + plan.alphaMode = HdrAlphaMode::ForceOpaque; + } + break; + } + + if (plan.alphaMode == HdrAlphaMode::Unknown) { + plan.alphaMode = HdrAlphaMode::ForceOpaque; + plan.fallbackReason = plan.fallbackReason.empty() + ? "Route did not declare an alpha rule; using ForceOpaque." + : plan.fallbackReason + " Route did not declare an alpha rule; using ForceOpaque."; + } + + return plan; +} + +} diff --git a/src/Magpie.Core/HdrAdapterDispatcher.h b/src/Magpie.Core/HdrAdapterDispatcher.h new file mode 100644 index 000000000..b2c8d6c6a --- /dev/null +++ b/src/Magpie.Core/HdrAdapterDispatcher.h @@ -0,0 +1,50 @@ +#pragma once + +#include "HdrColorTransform.h" +#include "HdrFrame.h" +#include "HdrProtocol.h" + +#include + +namespace Magpie { + +// Reusable plan produced by HdrAdapterDispatcher. This task deliberately does +// not attach the dispatcher to a concrete effect backend; the plan describes +// the conversion boundary so a future Renderer/effect integration can execute +// it without duplicating profile/route rules. +struct HdrAdapterPlan { + static constexpr DXGI_FORMAT CanonicalFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; + + HdrAdapterProfile profile = HdrAdapterProfile::Unknown; + std::string routeId; + std::string conversionPath; + DXGI_FORMAT canonicalInputFormat = CanonicalFormat; + // Non-terminal routes always return to the canonical FP16 surface. + // PresentationTerminal leaves the ordinary canonical texture chain. + DXGI_FORMAT canonicalOutputFormat = CanonicalFormat; + HdrAlphaMode alphaMode = HdrAlphaMode::Unknown; + bool isPresentationTerminal = false; + bool requiresSdrMapping = false; + bool requiresBoundedMapping = false; + bool usesFallback = false; + std::string fallbackReason; + float normalizationScale = 1.0f; + // SDRCompatible and BoundedHDR use explicit paired forward/inverse state. + HdrTransformParameters forwardParameters; + HdrTransformParameters inverseParameters; + + bool IsNonTerminalCanonical() const noexcept { + return !isPresentationTerminal && + canonicalInputFormat == CanonicalFormat && + canonicalOutputFormat == CanonicalFormat; + } +}; + +// Selects conversion behavior from the structured route. No effect-name string +// is consulted and no concrete backend is invoked. +class HdrAdapterDispatcher { +public: + HdrAdapterPlan BuildPlan(const HdrFormatRoute& route, const ColorDescription& sourceColor) noexcept; +}; + +} diff --git a/src/Magpie.Core/HdrCaptureProcessor.cpp b/src/Magpie.Core/HdrCaptureProcessor.cpp new file mode 100644 index 000000000..de2035763 --- /dev/null +++ b/src/Magpie.Core/HdrCaptureProcessor.cpp @@ -0,0 +1,302 @@ +#include "pch.h" +#include "HdrCaptureProcessor.h" + +#include "BackendDescriptorStore.h" +#include "DeviceResources.h" +#include "DirectXHelper.h" +#include "Logger.h" + +namespace Magpie { + +HdrCaptureProcessor::~HdrCaptureProcessor() noexcept { + ResetForResize(); +} + +namespace { +constexpr char HLSL[] = R"( +cbuffer Transform : register(b0) { + float exposure; + float inverseExposure; + float sdrWhiteScale; + float hdrPeakNits; + float shoulder; + float sdrWhiteNits; + uint inputTransfer; + uint outputTransfer; + uint preserveAlpha; +}; +Texture2D sourceTexture : register(t0); +RWTexture2D outputTexture : register(u0); + +float DecodeSrgb(float value) { + return value <= 0.04045 ? value / 12.92 : pow(max((value + 0.055) / 1.055, 0.0), 2.4); +} +float DecodePq(float value) { + const float m1 = 2610.0 / 16384.0; + const float m2 = 2523.0 / 32.0; + const float c1 = 3424.0 / 4096.0; + const float c2 = 2413.0 / 128.0; + const float c3 = 2392.0 / 128.0; + float p = pow(max(saturate(value), 0.0), 1.0 / m2); + return pow(max(p - c1, 0.0) / max(c2 - c3 * p, 1e-6), 1.0 / m1) * 10000.0; +} +float DecodeTransfer(float value, uint transfer) { + if (transfer == 2) return DecodeSrgb(value); + if (transfer == 3) return DecodePq(value); + if (transfer == 4) { + const float a = 0.17883277; + const float b = 1.0 - 4.0 * a; + const float c = 0.5 - a * log(4.0 * a); + return value <= 0.5 ? (value * value) / 3.0 : (exp((value - c) / a) + b) / 12.0; + } + return value; +} +float3 ToCanonical(float3 value) { + float3 decoded = float3(DecodeTransfer(value.r, inputTransfer), DecodeTransfer(value.g, inputTransfer), DecodeTransfer(value.b, inputTransfer)); + // Canonical storage is linear scRGB. WGC FP16 already uses this contract. + // PQ decodes to absolute nits and is converted to scRGB (80 nits == 1.0). + // SDR/HLG relative values are embedded at their declared reference white. + if (inputTransfer == 3) decoded /= 80.0; + else if (inputTransfer != 1) decoded *= sdrWhiteNits / 80.0; + return decoded * exposure; +} +[numthreads(8, 8, 1)] +void Main(uint3 id : SV_DispatchThreadID) { + uint width, height; + outputTexture.GetDimensions(width, height); + if (id.x >= width || id.y >= height) return; + float4 value = sourceTexture.Load(int3(id.xy, 0)); + float3 canonical = ToCanonical(value.rgb); + outputTexture[id.xy] = float4(canonical, preserveAlpha != 0 ? value.a : 1.0); +} +)"; +} + +bool HdrCaptureProcessor::Initialize( + DeviceResources& deviceResources, + BackendDescriptorStore& descriptorStore +) noexcept { + _deviceResources = &deviceResources; + _descriptorStore = &descriptorStore; + winrt::com_ptr blob; + if (!DirectXHelper::CompileComputeShader(HLSL, "Main", blob.put(), "HdrCaptureProcessor", nullptr, {}, true)) { + return false; + } + HRESULT hr = deviceResources.GetD3DDevice()->CreateComputeShader( + blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, _shader.put()); + if (FAILED(hr)) { + Logger::Get().ComError("创建 HDR 捕获处理 Compute Shader 失败", hr); + return false; + } + const D3D11_BUFFER_DESC desc{ + .ByteWidth = 48, + .Usage = D3D11_USAGE_DYNAMIC, + .BindFlags = D3D11_BIND_CONSTANT_BUFFER, + .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE + }; + hr = deviceResources.GetD3DDevice()->CreateBuffer(&desc, nullptr, _constants.put()); + return SUCCEEDED(hr); +} + +bool HdrCaptureProcessor::_EnsureResources(const D3D11_TEXTURE2D_DESC& sourceDesc) noexcept { + if (_canonical) { + D3D11_TEXTURE2D_DESC current{}; + _canonical->GetDesc(¤t); + if (current.Width == sourceDesc.Width && current.Height == sourceDesc.Height) return true; + if (_descriptorStore) { + _descriptorStore->RemoveCache(_canonical.get()); + } + } + _canonical = DirectXHelper::CreateTexture2D( + _deviceResources->GetD3DDevice(), DXGI_FORMAT_R16G16B16A16_FLOAT, + sourceDesc.Width, sourceDesc.Height, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS); + if (!_canonical) return false; + _canonicalUav = _descriptorStore->GetUnorderedAccessView(_canonical.get()); + return _canonicalUav != nullptr; +} + +bool HdrCaptureProcessor::Process(ID3D11Texture2D* source, const HdrFrameMetadata& metadata) noexcept { + _lastAssumption.clear(); + if (!_deviceResources || !_shader || !source || metadata.width == 0 || metadata.height == 0) { + Logger::Get().Error(fmt::format( + "HDR capture processor rejected frame: device={} shader={} source={} size={}x{}", + _deviceResources != nullptr, _shader != nullptr, source != nullptr, + metadata.width, metadata.height)); + return false; + } + HdrFrameMetadata normalizedMetadata = metadata; + if (!normalizedMetadata.color.IsValid()) { + normalizedMetadata.color.dxgiColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; + normalizedMetadata.color.primaries = HdrColorPrimaries::Rec709; + normalizedMetadata.color.transfer = HdrTransferFunction::SRGB; + normalizedMetadata.color.range = HdrColorRange::Full; + normalizedMetadata.color.referenceWhiteNits = 80.0f; + normalizedMetadata.color.sdrWhiteNits = 80.0f; + normalizedMetadata.color.displayPeakNits = 1000.0f; + normalizedMetadata.color.preExposure = 1.0f; + normalizedMetadata.color.isInferred = true; + normalizedMetadata.valid = true; + _lastAssumption = "Capture API did not provide a valid source color description; using default SDR."; + } else if (normalizedMetadata.color.isInferred) { + _lastAssumption = "Source color description inferred from the target monitor; capture API metadata was unavailable."; + } + if (!normalizedMetadata.IsValid()) { + Logger::Get().Error("HDR capture processor metadata is invalid after normalization"); + return false; + } + D3D11_TEXTURE2D_DESC sourceDesc{}; + source->GetDesc(&sourceDesc); + if (sourceDesc.Width != normalizedMetadata.width || sourceDesc.Height != normalizedMetadata.height) { + Logger::Get().Error(fmt::format( + "HDR capture processor size mismatch: texture={}x{} metadata={}x{}", + sourceDesc.Width, sourceDesc.Height, + normalizedMetadata.width, normalizedMetadata.height)); + return false; + } + if (!_EnsureResources(sourceDesc)) { + Logger::Get().Error(fmt::format( + "HDR capture processor canonical resource creation failed: format={} size={}x{}", + static_cast(sourceDesc.Format), sourceDesc.Width, sourceDesc.Height)); + return false; + } + _sourceSrv = _descriptorStore->GetShaderResourceView(source); + if (!_sourceSrv) { + Logger::Get().Error("HDR capture processor source SRV creation failed"); + return false; + } + const HdrTransformParameters parameters = HdrColorTransform::ForFrame(normalizedMetadata.color); + const HdrTransformConstants constants = HdrColorTransform::PrepareConstants( + parameters, normalizedMetadata.color.transfer, HdrTransferFunction::Linear); + struct CaptureConstants { + float exposure; + float inverseExposure; + float sdrWhiteScale; + float hdrPeakNits; + float shoulder; + float sdrWhiteNits; + uint32_t inputTransfer; + uint32_t outputTransfer; + uint32_t preserveAlpha; + uint32_t _padding[3]{}; + } captureConstants{ + constants.exposure, constants.inverseExposure, constants.sdrWhiteScale, + constants.hdrPeakNits, constants.shoulder, parameters.sdrWhiteNits, + constants.inputTransfer, constants.outputTransfer, + parameters.preserveAlpha ? 1u : 0u + }; + D3D11_MAPPED_SUBRESOURCE mapped{}; + auto* context = _deviceResources->GetD3DDC(); + if (FAILED(context->Map(_constants.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped))) { + Logger::Get().Error("HDR capture processor constant-buffer map failed"); + return false; + } + memcpy(mapped.pData, &captureConstants, sizeof(captureConstants)); + context->Unmap(_constants.get(), 0); + context->CSSetShader(_shader.get(), nullptr, 0); + ID3D11Buffer* constantsBuffer = _constants.get(); + context->CSSetConstantBuffers(0, 1, &constantsBuffer); + context->CSSetShaderResources(0, 1, &_sourceSrv); + context->CSSetUnorderedAccessViews(0, 1, &_canonicalUav, nullptr); + context->Dispatch((sourceDesc.Width + 7) / 8, (sourceDesc.Height + 7) / 8, 1); + ID3D11ShaderResourceView* nullSrv = nullptr; + ID3D11UnorderedAccessView* nullUav = nullptr; + ID3D11Buffer* nullBuffer = nullptr; + context->CSSetShaderResources(0, 1, &nullSrv); + context->CSSetUnorderedAccessViews(0, 1, &nullUav, nullptr); + context->CSSetConstantBuffers(0, 1, &nullBuffer); + context->CSSetShader(nullptr, nullptr, 0); + _metadata = normalizedMetadata; + _metadata.sourceFormat = sourceDesc.Format; + _metadata.stage = HdrFrameStage::CanonicalInput; + return true; +} + +bool HdrCaptureProcessor::Prepare( + ID3D11Texture2D* sourceTexture, + DXGI_FORMAT sourceFormat, + const ColorDescription& sourceColorDescription +) noexcept { + if (!_deviceResources || !sourceTexture) return false; + D3D11_TEXTURE2D_DESC sourceDesc{}; + sourceTexture->GetDesc(&sourceDesc); + if (!_EnsureResources(sourceDesc)) return false; + const float clearValue[4]{}; + _deviceResources->GetD3DDC()->ClearUnorderedAccessViewFloat(_canonicalUav, clearValue); + _metadata = {}; + _metadata.width = sourceDesc.Width; + _metadata.height = sourceDesc.Height; + _metadata.sourceFormat = sourceFormat != DXGI_FORMAT_UNKNOWN ? sourceFormat : sourceDesc.Format; + _metadata.color = sourceColorDescription; + if (!_metadata.color.IsValid()) { + _metadata.color.dxgiColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; + _metadata.color.primaries = HdrColorPrimaries::Rec709; + _metadata.color.transfer = HdrTransferFunction::SRGB; + _metadata.color.range = HdrColorRange::Full; + _metadata.color.referenceWhiteNits = 80.0f; + _metadata.color.sdrWhiteNits = 80.0f; + _metadata.color.displayPeakNits = 1000.0f; + _metadata.color.preExposure = 1.0f; + _metadata.color.isInferred = true; + _lastAssumption = "Capture API did not provide a source color description; using default SDR."; + } else if (_metadata.color.isInferred) { + _lastAssumption = "Source color description inferred from the target monitor; capture API metadata was unavailable."; + } + _metadata.valid = true; + _metadata.stage = HdrFrameStage::CanonicalInput; + return true; +} + +bool HdrCaptureProcessor::Process( + ID3D11Texture2D* sourceTexture, + DXGI_FORMAT sourceFormat, + const ColorDescription& sourceColorDescription +) noexcept { + if (!sourceTexture) { + return false; + } + + D3D11_TEXTURE2D_DESC sourceDesc{}; + sourceTexture->GetDesc(&sourceDesc); + + HdrFrameMetadata metadata{}; + metadata.frameId = 0; + metadata.width = sourceDesc.Width; + metadata.height = sourceDesc.Height; + metadata.sourceFormat = sourceFormat != DXGI_FORMAT_UNKNOWN ? sourceFormat : sourceDesc.Format; + metadata.valid = true; + + _lastAssumption.clear(); + if (sourceColorDescription.IsValid()) { + metadata.color = sourceColorDescription; + } else { + metadata.color.dxgiColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; + metadata.color.primaries = HdrColorPrimaries::Rec709; + metadata.color.transfer = HdrTransferFunction::SRGB; + metadata.color.range = HdrColorRange::Full; + metadata.color.referenceWhiteNits = 80.0f; + metadata.color.sdrWhiteNits = 80.0f; + metadata.color.displayPeakNits = 1000.0f; + metadata.color.isSceneReferred = false; + metadata.color.isPreExposed = false; + metadata.color.isInferred = true; + metadata.color.preExposure = 1.0f; + _lastAssumption = "Capture API did not provide a source color description; using default SDR " + "(Rec.709/sRGB, 80 nits reference white, 1000 nits display peak)."; + } + + return Process(sourceTexture, metadata); +} + +void HdrCaptureProcessor::ResetForResize() noexcept { + if (_descriptorStore && _canonical) { + _descriptorStore->RemoveCache(_canonical.get()); + } + _canonical = nullptr; + _canonicalUav = nullptr; + _sourceSrv = nullptr; + _metadata = {}; + _lastAssumption.clear(); +} + +} diff --git a/src/Magpie.Core/HdrCaptureProcessor.h b/src/Magpie.Core/HdrCaptureProcessor.h new file mode 100644 index 000000000..59c578f9d --- /dev/null +++ b/src/Magpie.Core/HdrCaptureProcessor.h @@ -0,0 +1,52 @@ +#pragma once + +#include "HdrColorTransform.h" + +#include +#include + +namespace Magpie { + +class BackendDescriptorStore; +class DeviceResources; + +class HdrCaptureProcessor { +public: + ~HdrCaptureProcessor() noexcept; + + bool Initialize(DeviceResources& deviceResources, BackendDescriptorStore& descriptorStore) noexcept; + bool Process(ID3D11Texture2D* source, const HdrFrameMetadata& metadata) noexcept; + // Interface shape prepared for Renderer/FrameSourceBase integration. The + // source color description may be invalid; the processor then substitutes a + // neutral default and records the assumption in LastAssumption(). + bool Process( + ID3D11Texture2D* sourceTexture, + DXGI_FORMAT sourceFormat, + const ColorDescription& sourceColorDescription + ) noexcept; + bool Prepare( + ID3D11Texture2D* sourceTexture, + DXGI_FORMAT sourceFormat, + const ColorDescription& sourceColorDescription + ) noexcept; + void ResetForResize() noexcept; + + ID3D11Texture2D* GetCanonicalTexture() const noexcept { return _canonical.get(); } + const HdrFrameMetadata& GetFrameMetadata() const noexcept { return _metadata; } + std::string_view LastAssumption() const noexcept { return _lastAssumption; } + +private: + bool _EnsureResources(const D3D11_TEXTURE2D_DESC& sourceDesc) noexcept; + + DeviceResources* _deviceResources = nullptr; + BackendDescriptorStore* _descriptorStore = nullptr; + winrt::com_ptr _canonical; + winrt::com_ptr _shader; + winrt::com_ptr _constants; + ID3D11ShaderResourceView* _sourceSrv = nullptr; + ID3D11UnorderedAccessView* _canonicalUav = nullptr; + HdrFrameMetadata _metadata{}; + std::string _lastAssumption; +}; + +} diff --git a/src/Magpie.Core/HdrColorTransform.cpp b/src/Magpie.Core/HdrColorTransform.cpp new file mode 100644 index 000000000..4702950ce --- /dev/null +++ b/src/Magpie.Core/HdrColorTransform.cpp @@ -0,0 +1,144 @@ +#include "pch.h" +#include "HdrColorTransform.h" + +#include +#include + +namespace Magpie { + +namespace { +constexpr float PQMaxNits = 10000.0f; +constexpr float PQM1 = 2610.0f / 16384.0f; +constexpr float PQM2 = 2523.0f / 32.0f; +constexpr float PQC1 = 3424.0f / 4096.0f; +constexpr float PQC2 = 2413.0f / 128.0f; +constexpr float PQC3 = 2392.0f / 128.0f; + +float Clamp01(float value) noexcept { return std::clamp(value, 0.0f, 1.0f); } + +float DecodePq(float value) noexcept { + const float normalized = std::pow(Clamp01(value), 1.0f / PQM2); + const float numerator = std::max(normalized - PQC1, 0.0f); + const float denominator = PQC2 - PQC3 * normalized; + return denominator > 0.0f ? std::pow(numerator / denominator, 1.0f / PQM1) * PQMaxNits : 0.0f; +} + +float EncodePq(float value) noexcept { + const float normalized = std::pow(std::max(value, 0.0f) / PQMaxNits, PQM1); + return Clamp01(std::pow((PQC1 + PQC2 * normalized) / (1.0f + PQC3 * normalized), PQM2)); +} + +float DecodeHlg(float value) noexcept { + constexpr float a = 0.17883277f; + constexpr float b = 1.0f - 4.0f * a; + const float c = 0.5f - a * std::log(4.0f * a); + const float encoded = Clamp01(value); + return encoded <= 0.5f ? (encoded * encoded) / 3.0f : (std::exp((encoded - c) / a) + b) / 12.0f; +} + +float EncodeHlg(float value) noexcept { + constexpr float a = 0.17883277f; + constexpr float b = 1.0f - 4.0f * a; + const float c = 0.5f - a * std::log(4.0f * a); + const float linear = std::max(value, 0.0f); + return Clamp01(linear <= 1.0f / 12.0f + ? std::sqrt(3.0f * linear) : a * std::log(12.0f * linear - b) + c); +} +} + +bool HdrTransformParameters::IsValid() const noexcept { + return std::isfinite(exposure) && exposure > 0.0f && + std::isfinite(referenceWhiteNits) && referenceWhiteNits == 80.0f && + std::isfinite(sdrWhiteNits) && sdrWhiteNits > 0.0f && + std::isfinite(hdrPeakNits) && hdrPeakNits >= referenceWhiteNits && + std::isfinite(shoulder) && shoulder > 0.0f; +} + +HdrTransformParameters HdrColorTransform::ForFrame(const ColorDescription& color) noexcept { + HdrTransformParameters parameters; + if (!color.IsValid()) return parameters; + parameters.referenceWhiteNits = 80.0f; + parameters.sdrWhiteNits = color.sdrWhiteNits; + parameters.hdrPeakNits = std::max({ + color.displayPeakNits, 80.0f, color.sdrWhiteNits }); + parameters.exposure = color.isPreExposed ? color.preExposure : 1.0f; + return parameters; +} + +HdrTransformConstants HdrColorTransform::PrepareConstants( + const HdrTransformParameters& parameters, + HdrTransferFunction inputTransfer, + HdrTransferFunction outputTransfer +) noexcept { + const HdrTransformParameters valid = parameters.IsValid() ? parameters : HdrTransformParameters{}; + return { valid.exposure, 1.0f / valid.exposure, valid.sdrWhiteNits / valid.hdrPeakNits, + valid.hdrPeakNits, valid.shoulder, static_cast(inputTransfer), + static_cast(outputTransfer) }; +} + +float HdrColorTransform::DecodeTransfer(float value, HdrTransferFunction transfer) noexcept { + switch (transfer) { + case HdrTransferFunction::SRGB: return value <= 0.04045f ? value / 12.92f : std::pow((value + 0.055f) / 1.055f, 2.4f); + case HdrTransferFunction::PQ: return DecodePq(value); + case HdrTransferFunction::HLG: return DecodeHlg(value); + case HdrTransferFunction::Linear: + case HdrTransferFunction::Unknown: + default: return value; + } +} + +float HdrColorTransform::EncodeTransfer(float value, HdrTransferFunction transfer) noexcept { + switch (transfer) { + case HdrTransferFunction::SRGB: return value <= 0.0031308f ? value * 12.92f : 1.055f * std::pow(std::max(value, 0.0f), 1.0f / 2.4f) - 0.055f; + case HdrTransferFunction::PQ: return EncodePq(value); + case HdrTransferFunction::HLG: return EncodeHlg(value); + case HdrTransferFunction::Linear: + case HdrTransferFunction::Unknown: + default: return value; + } +} + +float HdrColorTransform::MapHdrToSdr(float value, const HdrTransformParameters& parameters) noexcept { + const HdrTransformParameters valid = parameters.IsValid() ? parameters : HdrTransformParameters{}; + const float normalized = std::max(value, 0.0f) * valid.exposure / + (valid.sdrWhiteNits / 80.0f); + if (normalized <= 1.0f) return normalized; + const float excess = normalized - 1.0f; + // SDR-compatible routes must stay inside the SDR display domain. The + // previous curve returned values above one and relied on sRGB saturation, + // turning HDR headroom into clipped white. Compress excess using the + // source display headroom while keeping the SDR white point continuous. + const float headroom = std::max(valid.hdrPeakNits / 80.0f - 1.0f, 1.0f); + return std::clamp(1.0f - excess / + (excess + headroom * std::max(valid.shoulder, 0.001f) + 1.0f), 0.0f, 1.0f); +} + +float HdrColorTransform::MapSdrToHdr(float value, const HdrTransformParameters& parameters) noexcept { + const HdrTransformParameters valid = parameters.IsValid() ? parameters : HdrTransformParameters{}; + const float mapped = std::max(value, 0.0f); + if (mapped <= 1.0f) { + return mapped * (valid.sdrWhiteNits / 80.0f) / valid.exposure; + } + const float excess = mapped - 1.0f; + const float denominator = 1.0f - valid.shoulder * excess; + const float reconstructed = denominator > 0.0f + ? (1.0f + excess / denominator) * (valid.sdrWhiteNits / 80.0f) / valid.exposure + : valid.hdrPeakNits / 80.0f; + return std::min(reconstructed, valid.hdrPeakNits / 80.0f); +} + +HdrColor HdrColorTransform::Transform( + const HdrColor& color, + HdrTransferFunction inputTransfer, + HdrTransferFunction outputTransfer, + const HdrTransformParameters& parameters +) noexcept { + HdrColor result = color; + for (size_t channel = 0; channel < 3; ++channel) { + result[channel] = EncodeTransfer(DecodeTransfer(color[channel], inputTransfer), outputTransfer); + } + result[3] = parameters.preserveAlpha ? color[3] : 1.0f; + return result; +} + +} diff --git a/src/Magpie.Core/HdrColorTransform.h b/src/Magpie.Core/HdrColorTransform.h new file mode 100644 index 000000000..f65e3a57e --- /dev/null +++ b/src/Magpie.Core/HdrColorTransform.h @@ -0,0 +1,51 @@ +#pragma once + +#include "HdrFrame.h" +#include + +namespace Magpie { + +using HdrColor = std::array; + +struct HdrTransformParameters { + float exposure = 1.0f; + // Canonical scRGB reference white is fixed by the global HDR protocol. + float referenceWhiteNits = 80.0f; + float sdrWhiteNits = 80.0f; + float hdrPeakNits = 1000.0f; + float shoulder = 1.0f; + bool preserveAlpha = true; + bool IsValid() const noexcept; +}; + +struct HdrTransformConstants { + float exposure = 1.0f; + float inverseExposure = 1.0f; + float sdrWhiteScale = 0.08f; + float hdrPeakNits = 1000.0f; + float shoulder = 1.0f; + uint32_t inputTransfer = static_cast(HdrTransferFunction::Linear); + uint32_t outputTransfer = static_cast(HdrTransferFunction::Linear); +}; + +class HdrColorTransform { +public: + static HdrTransformParameters ForFrame(const ColorDescription& color) noexcept; + static HdrTransformConstants PrepareConstants( + const HdrTransformParameters& parameters, + HdrTransferFunction inputTransfer, + HdrTransferFunction outputTransfer + ) noexcept; + static float DecodeTransfer(float value, HdrTransferFunction transfer) noexcept; + static float EncodeTransfer(float value, HdrTransferFunction transfer) noexcept; + static float MapHdrToSdr(float value, const HdrTransformParameters& parameters) noexcept; + static float MapSdrToHdr(float value, const HdrTransformParameters& parameters) noexcept; + static HdrColor Transform( + const HdrColor& color, + HdrTransferFunction inputTransfer, + HdrTransferFunction outputTransfer, + const HdrTransformParameters& parameters + ) noexcept; +}; + +} diff --git a/src/Magpie.Core/HdrDiagnostics.cpp b/src/Magpie.Core/HdrDiagnostics.cpp new file mode 100644 index 000000000..7fa117d97 --- /dev/null +++ b/src/Magpie.Core/HdrDiagnostics.cpp @@ -0,0 +1,78 @@ +#include "pch.h" +#include "HdrDiagnostics.h" +#include "HdrProtocol.h" +#include "Logger.h" + +namespace Magpie { + +void LogHdrDiagnostics(const HdrDiagnostics& diagnostics, bool debugOnly) noexcept { + const bool hasIssue = !diagnostics.fallbackReason.empty() || !diagnostics.assumptions.empty(); + (void)debugOnly; + + const std::string message = fmt::format( + "HDR diagnostics\n" + "\thdrOptionEnabled: {}\n" + "\tcaptureMethod: {}\n" + "\tsourceFormat: {}\n" + "\tsourceColorDescriptionValid: {}\n" + "\tsourceDxgiColorSpace: {}\n" + "\tsourcePrimaries: {}\n" + "\tsourceTransfer: {}\n" + "\tsourceRange: {}\n" + "\tsourceReferenceWhiteNits: {:.3f}\n" + "\tsourceSdrWhiteNits: {:.3f}\n" + "\tsourceDisplayPeakNits: {:.3f}\n" + "\tdisplayHdrEnabled: {}\n" + "\tsourceSceneReferred: {}\n" + "\tsourceColorInferred: {}\n" + "\tsourcePreExposure: {:.6f}\n" + "\tcanonicalFormat: {}\n" + "\tselectedAdapterProfile: {}\n" + "\tselectedRouteId: {}\n" + "\tconversionPath: {}\n" + "\tfallbackReason: {}\n" + "\tassumptions: {}", + diagnostics.hdrOptionEnabled ? "true" : "false", + diagnostics.captureMethod.empty() ? "(not set)" : diagnostics.captureMethod, + static_cast(diagnostics.sourceFormat), + diagnostics.sourceColorDescription.IsValid() ? "true" : "false", + static_cast(diagnostics.sourceColorDescription.dxgiColorSpace), + static_cast(diagnostics.sourceColorDescription.primaries), + ToString(diagnostics.sourceColorDescription.transfer), + ToString(diagnostics.sourceColorDescription.range), + diagnostics.sourceColorDescription.referenceWhiteNits, + diagnostics.sourceColorDescription.sdrWhiteNits, + diagnostics.sourceColorDescription.displayPeakNits, + diagnostics.sourceColorDescription.displayHdrEnabled ? "true" : "false", + diagnostics.sourceColorDescription.isSceneReferred ? "true" : "false", + diagnostics.sourceColorDescription.isInferred ? "true" : "false", + diagnostics.sourceColorDescription.preExposure, + static_cast(diagnostics.canonicalFormat), + ToString(diagnostics.selectedAdapterProfile), + diagnostics.selectedRouteId.empty() ? "(not set)" : diagnostics.selectedRouteId, + diagnostics.conversionPath.empty() ? "(not set)" : diagnostics.conversionPath, + diagnostics.fallbackReason.empty() ? "(none)" : diagnostics.fallbackReason, + diagnostics.assumptions.empty() ? "(none)" : diagnostics.assumptions + ); + + if (hasIssue) { + Logger::Get().Warn(message); + } else { + Logger::Get().Info(message); + } +} + +void AppendHdrAssumption(HdrDiagnostics& diagnostics, std::string_view assumption) noexcept { + if (assumption.empty()) { + return; + } + + if (diagnostics.assumptions.empty()) { + diagnostics.assumptions = std::string(assumption); + } else { + diagnostics.assumptions.append("; "); + diagnostics.assumptions.append(assumption); + } +} + +} diff --git a/src/Magpie.Core/HdrDiagnostics.h b/src/Magpie.Core/HdrDiagnostics.h new file mode 100644 index 000000000..9997ad186 --- /dev/null +++ b/src/Magpie.Core/HdrDiagnostics.h @@ -0,0 +1,34 @@ +#pragma once + +#include "HdrFrame.h" + +#include +#include + +namespace Magpie { + +// Compact, CPU-side diagnostic state. It is intended for DEBUG or existing +// logger output at capture/effect boundaries. It never triggers a CPU staging +// readback and does not alter rendering synchronization. +struct HdrDiagnostics { + bool hdrOptionEnabled = false; + std::string captureMethod; + DXGI_FORMAT sourceFormat = DXGI_FORMAT_UNKNOWN; + ColorDescription sourceColorDescription{}; + DXGI_FORMAT canonicalFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; + HdrAdapterProfile selectedAdapterProfile = HdrAdapterProfile::Unknown; + std::string selectedRouteId; + std::string conversionPath; + std::string fallbackReason; + std::string assumptions; +}; + +// Emits the structured diagnostic through the existing logger. debugOnly is a +// policy hint for callers; implementations may still log warnings for explicit +// fallback/assumption reasons. +void LogHdrDiagnostics(const HdrDiagnostics& diagnostics, bool debugOnly = false) noexcept; + +// Convenience for populating "missing source color metadata" assumptions. +void AppendHdrAssumption(HdrDiagnostics& diagnostics, std::string_view assumption) noexcept; + +} diff --git a/src/Magpie.Core/HdrEffectBoundary.cpp b/src/Magpie.Core/HdrEffectBoundary.cpp new file mode 100644 index 000000000..ce1c5d994 --- /dev/null +++ b/src/Magpie.Core/HdrEffectBoundary.cpp @@ -0,0 +1,74 @@ +#include "pch.h" +#include "HdrEffectBoundary.h" + +namespace Magpie { + +namespace { + +HdrFormatRoute MakeGenericSdrFallbackRoute() { + return HdrFormatRoute{ + .effectId = "__generic__", + .optionId = "sdr-compatible-fallback", + .inputFormat = DXGI_FORMAT_R8G8B8A8_UNORM, + .outputFormat = DXGI_FORMAT_R8G8B8A8_UNORM, + .inputTransfer = HdrTransferFunction::SRGB, + .outputTransfer = HdrTransferFunction::SRGB, + .inputRange = HdrColorRange::Full, + .outputRange = HdrColorRange::Full, + .alphaMode = HdrAlphaMode::Preserve, + .evidenceLevel = HdrEvidenceLevel::None, + .hdrNative = false, + .adapterProfile = HdrAdapterProfile::SDRCompatible, + }; +} + +} + +const HdrFormatRoute* HdrEffectBoundary::SelectRoute(bool hdrEnabled, const HdrFormatRoutes& routes) noexcept { + return hdrEnabled ? SelectDefaultHdrRoute(routes) : SelectDefaultSdrRoute(routes); +} + +HdrEffectBoundaryContext HdrEffectBoundary::Prepare(bool hdrEnabled, const HdrFrame& inputFrame, const HdrFormatRoutes& routes, const ColorDescription& sourceColor) noexcept { + HdrEffectBoundaryContext context; + context.hdrEnabled = hdrEnabled; + context.inputFrame = inputFrame; + context.routes = routes; + const HdrFormatRoute* selectedRoute = SelectRoute(hdrEnabled, context.routes); + if (hdrEnabled && !selectedRoute) { + context.routes.push_back(MakeGenericSdrFallbackRoute()); + selectedRoute = &context.routes.back(); + } + if (selectedRoute) { + context.selectedRouteIndex = static_cast(selectedRoute - context.routes.data()); + } + + if (!hdrEnabled) { + context.prepared = true; + return context; + } + + if (!context.inputFrame.IsCanonical() || !context.SelectedRoute()) { + return context; + } + + context.plan = HdrAdapterDispatcher{}.BuildPlan(*context.SelectedRoute(), sourceColor); + context.prepared = context.plan.IsNonTerminalCanonical() || context.plan.isPresentationTerminal; + return context; +} + +bool HdrEffectBoundary::Complete(HdrEffectBoundaryContext& context, const HdrFrame& backendOutput) noexcept { + if (!context.prepared) { + return false; + } + if (!context.hdrEnabled || context.plan.isPresentationTerminal) { + context.completed = true; + return true; + } + if (!backendOutput.IsCanonical()) { + return false; + } + context.completed = true; + return true; +} + +} diff --git a/src/Magpie.Core/HdrEffectBoundary.h b/src/Magpie.Core/HdrEffectBoundary.h new file mode 100644 index 000000000..c7ca59b15 --- /dev/null +++ b/src/Magpie.Core/HdrEffectBoundary.h @@ -0,0 +1,34 @@ +#pragma once + +#include "HdrAdapterDispatcher.h" + +#include + +namespace Magpie { + +struct HdrEffectBoundaryContext { + bool hdrEnabled = false; + HdrFrame inputFrame{}; + HdrFormatRoutes routes{}; + size_t selectedRouteIndex = static_cast(-1); + HdrAdapterPlan plan{}; + bool prepared = false; + bool completed = false; + + bool IsCanonicalInput() const noexcept { + return !hdrEnabled || inputFrame.IsCanonical(); + } + + const HdrFormatRoute* SelectedRoute() const noexcept { + return selectedRouteIndex < routes.size() ? &routes[selectedRouteIndex] : nullptr; + } +}; + +class HdrEffectBoundary { +public: + static HdrEffectBoundaryContext Prepare(bool hdrEnabled, const HdrFrame& inputFrame, const HdrFormatRoutes& routes, const ColorDescription& sourceColor) noexcept; + static bool Complete(HdrEffectBoundaryContext& context, const HdrFrame& backendOutput) noexcept; + static const HdrFormatRoute* SelectRoute(bool hdrEnabled, const HdrFormatRoutes& routes) noexcept; +}; + +} diff --git a/src/Magpie.Core/HdrFrame.cpp b/src/Magpie.Core/HdrFrame.cpp new file mode 100644 index 000000000..41da3cbbe --- /dev/null +++ b/src/Magpie.Core/HdrFrame.cpp @@ -0,0 +1,62 @@ +#include "pch.h" +#include "HdrFrame.h" + +#include + +namespace Magpie { + +bool HdrMetadata::HasLuminanceRange() const noexcept { + return maxMasteringLuminanceNits > 0.0f && minMasteringLuminanceNits >= 0.0f && + maxMasteringLuminanceNits >= minMasteringLuminanceNits; +} + +bool ColorDescription::IsValid() const noexcept { + return primaries != HdrColorPrimaries::Unknown && + transfer != HdrTransferFunction::Unknown && + std::isfinite(referenceWhiteNits) && referenceWhiteNits > 0.0f && + std::isfinite(sdrWhiteNits) && sdrWhiteNits > 0.0f && + std::isfinite(displayPeakNits) && displayPeakNits >= referenceWhiteNits && + std::isfinite(preExposure) && preExposure > 0.0f; +} + +bool HdrFrameMetadata::IsValid() const noexcept { + return valid && width != 0 && height != 0 && color.IsValid() && + stage != HdrFrameStage::Unknown; +} + +bool HdrFrame::IsCanonical() const noexcept { + if (!texture || !metadata.IsValid() || + workingFormat != DXGI_FORMAT_R16G16B16A16_FLOAT) { + return false; + } + + D3D11_TEXTURE2D_DESC textureDesc{}; + texture->GetDesc(&textureDesc); + return textureDesc.Format == workingFormat && + textureDesc.Width == metadata.width && textureDesc.Height == metadata.height; +} + +std::string HdrFormatRoute::Id() const { + if (effectId.empty()) { + return optionId; + } + if (optionId.empty()) { + return effectId; + } + return effectId + "/" + optionId; +} + +bool HdrFormatRoute::IsValid() const noexcept { + if (effectId.empty() || optionId.empty()) { + return false; + } + if (inputFormat == DXGI_FORMAT_UNKNOWN || outputFormat == DXGI_FORMAT_UNKNOWN) { + return false; + } + if (adapterProfile == HdrAdapterProfile::Unknown) { + return false; + } + return alphaMode != HdrAlphaMode::Unknown; +} + +} diff --git a/src/Magpie.Core/HdrFrame.h b/src/Magpie.Core/HdrFrame.h new file mode 100644 index 000000000..a61794d22 --- /dev/null +++ b/src/Magpie.Core/HdrFrame.h @@ -0,0 +1,177 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Magpie { + +enum class HdrColorPrimaries : uint8_t { Unknown, Rec709, Rec2020, P3D65 }; +enum class HdrTransferFunction : uint8_t { Unknown, Linear, SRGB, PQ, HLG }; +enum class HdrColorRange : uint8_t { + Unknown, + Full, + Limited, + SceneLinear, + DisplayReferred +}; + +enum class HdrAlphaMode : uint8_t { + Unknown, + Preserve, + ForceOpaque, + Premultiplied +}; + +enum class HdrEvidenceLevel : uint8_t { + None, + PublicApiContract, + ReferenceImplementation, + CommunityExperiment, + LocalValidation +}; + +enum class HdrAdapterProfile : uint8_t { + DirectFP16, BoundedHDR, SDRCompatible, ConditionalFP16, Unknown, PresentationTerminal +}; + +// Every HDR texture crossing a Magpie module boundary carries one of these +// roles. RawCapture is confined to the capture front end and is never a +// renderer/effect/presentation input. +enum class HdrFrameStage : uint8_t { + Unknown, + RawCapture, + CanonicalInput, + EffectLocalInput, + EffectLocalOutput, + CanonicalOutput, + PublishedOutput, + PresentedOutput, + GeneratedOutput, +}; + +struct HdrMetadata { + float maxMasteringLuminanceNits = 0.0f; + float minMasteringLuminanceNits = 0.0f; + float maxContentLightLevelNits = 0.0f; + float maxFrameAverageLightLevelNits = 0.0f; + std::array displayPrimaries{}; + std::array whitePoint{}; + bool HasLuminanceRange() const noexcept; +}; + +struct ColorDescription { + DXGI_COLOR_SPACE_TYPE dxgiColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; + HdrColorPrimaries primaries = HdrColorPrimaries::Unknown; + HdrTransferFunction transfer = HdrTransferFunction::Unknown; + HdrColorRange range = HdrColorRange::Unknown; + // Canonical scRGB reference white. Windows scRGB defines 1.0 as 80 nit. + float referenceWhiteNits = 80.0f; + // Display SDR white level used when SDR content is embedded in HDR. + float sdrWhiteNits = 80.0f; + float displayPeakNits = 1000.0f; + bool isSceneReferred = false; + bool isPreExposed = false; + bool isInferred = false; + bool displayHdrEnabled = false; + float preExposure = 1.0f; + HdrMetadata metadata{}; + bool IsValid() const noexcept; +}; + +struct HdrFrameMetadata { + uint64_t frameId = 0; + uint64_t captureSequence = 0; + uint64_t resourceGeneration = 0; + int64_t timestamp100ns = 0; + uint32_t width = 0; + uint32_t height = 0; + DXGI_FORMAT sourceFormat = DXGI_FORMAT_UNKNOWN; + ColorDescription color{}; + HdrFrameStage stage = HdrFrameStage::Unknown; + std::string routeId; + bool generated = false; + bool valid = false; + bool IsValid() const noexcept; +}; + +struct HdrFrame { + ID3D11Texture2D* texture = nullptr; + HdrFrameMetadata metadata{}; + DXGI_FORMAT workingFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; + bool IsCanonical() const noexcept; + + HdrFrame WithStage(HdrFrameStage newStage, std::string route = {}) const { + HdrFrame result = *this; + result.metadata.stage = newStage; + result.metadata.routeId = std::move(route); + return result; + } +}; + +struct HdrAdapterProtocol { + HdrAdapterProfile profile = HdrAdapterProfile::Unknown; + DXGI_FORMAT inputFormat = DXGI_FORMAT_UNKNOWN; + DXGI_FORMAT outputFormat = DXGI_FORMAT_UNKNOWN; + HdrTransferFunction inputTransfer = HdrTransferFunction::Unknown; + HdrTransferFunction outputTransfer = HdrTransferFunction::Unknown; + bool preservesAlpha = true; + bool isPresentationTerminal = false; +}; + +// A protocol route describes one way to carry an effect across the canonical +// HDR boundary. Routes are stored as structured data and are never selected by +// effect-name string comparisons. The route intentionally does not encode a +// color-space guess from its texture format; callers must set hdrNative and the +// transfer/range fields from verified evidence. +struct HdrFormatRoute { + std::string effectId; + std::string optionId; + DXGI_FORMAT inputFormat = DXGI_FORMAT_UNKNOWN; + DXGI_FORMAT outputFormat = DXGI_FORMAT_UNKNOWN; + HdrTransferFunction inputTransfer = HdrTransferFunction::Unknown; + HdrTransferFunction outputTransfer = HdrTransferFunction::Unknown; + HdrColorRange inputRange = HdrColorRange::Unknown; + HdrColorRange outputRange = HdrColorRange::Unknown; + HdrAlphaMode alphaMode = HdrAlphaMode::Unknown; + HdrEvidenceLevel evidenceLevel = HdrEvidenceLevel::None; + bool hdrNative = false; + HdrAdapterProfile adapterProfile = HdrAdapterProfile::Unknown; + bool defaultForHdr = false; + bool defaultForSdr = false; + float normalizationScale = 1.0f; + + std::string Id() const; + + // Structural validity: a route must be identifiable and must name real + // input/output formats plus an explicit adapter profile and alpha rule. + // Unknown evidence routes may still be represented for diagnostics but are + // not considered accepted HDR routes. + bool IsValid() const noexcept; + + bool IsAccepted() const noexcept { + return IsValid(); + } + + // True only when the route explicitly declares HDR semantics. FP16 alone + // and R8/R10 format acceptance alone never make this flag true. + bool IsHdrNative() const noexcept { + return IsValid() && hdrNative; + } + + // Routes that do not directly carry HDR semantics but are accepted through + // an explicit bounded/SDR/conditional adapter. + bool IsHdrAdapter() const noexcept { + return IsValid() && !hdrNative && adapterProfile != HdrAdapterProfile::Unknown && + adapterProfile != HdrAdapterProfile::PresentationTerminal; + } + + bool IsPresentationTerminal() const noexcept { + return adapterProfile == HdrAdapterProfile::PresentationTerminal; + } +}; + +} diff --git a/src/Magpie.Core/HdrProtocol.cpp b/src/Magpie.Core/HdrProtocol.cpp new file mode 100644 index 000000000..0a5e01a54 --- /dev/null +++ b/src/Magpie.Core/HdrProtocol.cpp @@ -0,0 +1,106 @@ +#include "pch.h" +#include "HdrProtocol.h" + +namespace Magpie { + +const HdrFormatRoute* SelectDefaultHdrRoute(const HdrFormatRoutes& routes) noexcept { + const HdrFormatRoute* firstHdrNative = nullptr; + const HdrFormatRoute* firstHdrAdapter = nullptr; + + for (const HdrFormatRoute& route : routes) { + if (!route.IsValid() || route.IsPresentationTerminal()) { + continue; + } + + if (route.defaultForHdr) { + return &route; + } + + if (route.IsHdrNative() && !firstHdrNative) { + firstHdrNative = &route; + } else if (route.IsHdrAdapter() && !firstHdrAdapter) { + firstHdrAdapter = &route; + } + } + + return firstHdrNative ? firstHdrNative : firstHdrAdapter; +} + +const HdrFormatRoute* SelectDefaultSdrRoute(const HdrFormatRoutes& routes) noexcept { + const HdrFormatRoute* firstAccepted = nullptr; + + for (const HdrFormatRoute& route : routes) { + if (!route.IsValid() || route.IsPresentationTerminal()) { + continue; + } + + if (!firstAccepted) { + firstAccepted = &route; + } + + if (route.defaultForSdr) { + return &route; + } + } + + return firstAccepted; +} + +std::vector GetAcceptedFormatRoutes(const HdrFormatRoutes& routes) { + std::vector result; + result.reserve(routes.size()); + for (const HdrFormatRoute& route : routes) { + if (route.IsAccepted()) { + result.push_back(&route); + } + } + return result; +} + +std::vector GetHdrNativeFormatRoutes(const HdrFormatRoutes& routes) { + std::vector result; + result.reserve(routes.size()); + for (const HdrFormatRoute& route : routes) { + if (route.IsHdrNative()) { + result.push_back(&route); + } + } + return result; +} + +std::vector GetHdrAdapterFormatRoutes(const HdrFormatRoutes& routes) { + std::vector result; + result.reserve(routes.size()); + for (const HdrFormatRoute& route : routes) { + if (route.IsHdrAdapter()) { + result.push_back(&route); + } + } + return result; +} + +std::string SerializeHdrFormatRoute(const HdrFormatRoute& route) { + return fmt::format( + "effectId={};optionId={};inputFormat={};outputFormat={};" + "inputTransfer={};outputTransfer={};inputRange={};outputRange={};" + "alphaMode={};evidenceLevel={};hdrNative={};adapterProfile={};" + "defaultForHdr={};defaultForSdr={};normalizationScale={}", + route.effectId, + route.optionId, + static_cast(route.inputFormat), + static_cast(route.outputFormat), + ToString(route.inputTransfer), + ToString(route.outputTransfer), + ToString(route.inputRange), + ToString(route.outputRange), + ToString(route.alphaMode), + ToString(route.evidenceLevel), + route.hdrNative ? "true" : "false", + ToString(route.adapterProfile), + route.defaultForHdr ? "true" : "false", + route.defaultForSdr ? "true" : "false", + route.normalizationScale + ); +} + +} diff --git a/src/Magpie.Core/HdrProtocol.h b/src/Magpie.Core/HdrProtocol.h new file mode 100644 index 000000000..b4d407420 --- /dev/null +++ b/src/Magpie.Core/HdrProtocol.h @@ -0,0 +1,112 @@ +#pragma once + +#include "HdrFrame.h" + +#include +#include +#include + +namespace Magpie { + +using HdrFormatRoutes = std::vector; + +// Textual names are for diagnostics and logging only. Protocol dispatch must +// use the structured route fields, never effect-name string comparisons. +constexpr std::string_view ToString(HdrAdapterProfile profile) noexcept { + switch (profile) { + case HdrAdapterProfile::DirectFP16: + return "DirectFP16"; + case HdrAdapterProfile::BoundedHDR: + return "BoundedHDR"; + case HdrAdapterProfile::SDRCompatible: + return "SDRCompatible"; + case HdrAdapterProfile::ConditionalFP16: + return "ConditionalFP16"; + case HdrAdapterProfile::Unknown: + return "Unknown"; + case HdrAdapterProfile::PresentationTerminal: + return "PresentationTerminal"; + default: + return "Unknown"; + } +} + +constexpr std::string_view ToString(HdrColorRange range) noexcept { + switch (range) { + case HdrColorRange::Full: + return "Full"; + case HdrColorRange::Limited: + return "Limited"; + case HdrColorRange::SceneLinear: + return "SceneLinear"; + case HdrColorRange::DisplayReferred: + return "DisplayReferred"; + case HdrColorRange::Unknown: + default: + return "Unknown"; + } +} + +constexpr std::string_view ToString(HdrAlphaMode alphaMode) noexcept { + switch (alphaMode) { + case HdrAlphaMode::Preserve: + return "Preserve"; + case HdrAlphaMode::ForceOpaque: + return "ForceOpaque"; + case HdrAlphaMode::Premultiplied: + return "Premultiplied"; + case HdrAlphaMode::Unknown: + default: + return "Unknown"; + } +} + +constexpr std::string_view ToString(HdrEvidenceLevel evidenceLevel) noexcept { + switch (evidenceLevel) { + case HdrEvidenceLevel::PublicApiContract: + return "PublicApiContract"; + case HdrEvidenceLevel::ReferenceImplementation: + return "ReferenceImplementation"; + case HdrEvidenceLevel::CommunityExperiment: + return "CommunityExperiment"; + case HdrEvidenceLevel::LocalValidation: + return "LocalValidation"; + case HdrEvidenceLevel::None: + default: + return "None"; + } +} + +constexpr std::string_view ToString(HdrTransferFunction transfer) noexcept { + switch (transfer) { + case HdrTransferFunction::Linear: + return "Linear"; + case HdrTransferFunction::SRGB: + return "SRGB"; + case HdrTransferFunction::PQ: + return "PQ"; + case HdrTransferFunction::HLG: + return "HLG"; + case HdrTransferFunction::Unknown: + default: + return "Unknown"; + } +} + +// SDR mode returns the route marked defaultForSdr. HDR mode returns a route +// marked defaultForHdr, then falls back to an explicit HDR-native or adapter +// route. Unknown-profile routes are not selected because they cannot be +// considered accepted HDR routes. +const HdrFormatRoute* SelectDefaultHdrRoute(const HdrFormatRoutes& routes) noexcept; +const HdrFormatRoute* SelectDefaultSdrRoute(const HdrFormatRoutes& routes) noexcept; + +std::vector GetAcceptedFormatRoutes(const HdrFormatRoutes& routes); +std::vector GetHdrNativeFormatRoutes(const HdrFormatRoutes& routes); +std::vector GetHdrAdapterFormatRoutes(const HdrFormatRoutes& routes); + +// Lightweight stable textual representation for diagnostics and future +// persistence tooling. It intentionally serializes every route field so the +// structured shape remains inspectable without effect-name branches. +std::string SerializeHdrFormatRoute(const HdrFormatRoute& route); + +} diff --git a/src/Magpie.Core/HdrSourceTestBridge.cpp b/src/Magpie.Core/HdrSourceTestBridge.cpp new file mode 100644 index 000000000..6185e62ac --- /dev/null +++ b/src/Magpie.Core/HdrSourceTestBridge.cpp @@ -0,0 +1,49 @@ +#include "pch.h" +#include "HdrSourceTestBridge.h" +#include "EffectCompiler.h" +#include "EffectProtocolCatalogC.h" +#include "GroupAHdrRoutes.h" +#include "GroupBHdrRoutes.h" +#include "HdrAdapterDispatcher.h" + +namespace Magpie { + +namespace { +HdrFormatRoutes RoutesFor(std::string_view name) noexcept { + const size_t separator = name.find('\\'); + const std::string_view group = separator == std::string::npos ? name : name.substr(0, separator); + if (group == "CAS") return GetGroupAHdrRoutes(group, 0); + if (group == "DLSSNR") return GetGroupBHdrRoutes(group, false, 1.0f); + if (group == "DLSS" || group == "FSR" || group == "FSR2" || group == "FSR3" || group == "FSR4" || group == "NIS") return GetGroupBHdrRoutes(group); + return EffectProtocolC::GetGroupCHdrRoutes(group); +} +} + +std::vector HdrSourceTestBridge::CompileAndDescribeAll( + const std::vector& effectNames, bool noFP16) noexcept { + std::vector results; + results.reserve(effectNames.size()); + for (const std::string& effectName : effectNames) { + EffectDesc desc{ .name = effectName }; + const uint32_t flags = noFP16 ? EffectCompilerFlags::NoFP16 : 0; + const bool compiled = EffectCompiler::Compile(desc, flags, nullptr) == 0; + const HdrFormatRoutes routes = RoutesFor(effectName); + const HdrFormatRoute* route = SelectDefaultHdrRoute(routes); + HdrSourceTestResult result; + result.effectId = effectName; + result.routeId = route ? route->Id() : ""; + result.routeValid = route && route->IsValid(); + result.passCount = compiled ? static_cast(desc.passes.size()) : 0; + result.textureCount = compiled ? static_cast(desc.textures.size()) : 0; + if (route) { + result.inputFormat = route->inputFormat; + result.outputFormat = route->outputFormat; + result.canonicalInput = route->inputFormat == DXGI_FORMAT_R16G16B16A16_FLOAT || route->adapterProfile == HdrAdapterProfile::SDRCompatible; + result.canonicalOutput = route->outputFormat == DXGI_FORMAT_R16G16B16A16_FLOAT || route->adapterProfile == HdrAdapterProfile::SDRCompatible; + } + results.push_back(std::move(result)); + } + return results; +} + +} diff --git a/src/Magpie.Core/HdrSourceTestBridge.h b/src/Magpie.Core/HdrSourceTestBridge.h new file mode 100644 index 000000000..5442683be --- /dev/null +++ b/src/Magpie.Core/HdrSourceTestBridge.h @@ -0,0 +1,32 @@ +#pragma once + +#include "EffectDesc.h" +#include "HdrFrame.h" +#include +#include + +namespace Magpie { + +struct HdrSourceTestResult { + std::string effectId; + std::string routeId; + DXGI_FORMAT inputFormat = DXGI_FORMAT_UNKNOWN; + DXGI_FORMAT outputFormat = DXGI_FORMAT_UNKNOWN; + uint32_t passCount = 0; + uint32_t textureCount = 0; + bool routeValid = false; + bool canonicalInput = false; + bool canonicalOutput = false; +}; + +// Source-level contract probe. It uses the production EffectCompiler and +// route providers; it does not emulate shader math or invoke a substitute SDK. +class HdrSourceTestBridge final { +public: + static std::vector CompileAndDescribeAll( + const std::vector& effectNames, + bool noFP16 = false + ) noexcept; +}; + +} diff --git a/src/Magpie.Core/HdrSurfaceAdapter.cpp b/src/Magpie.Core/HdrSurfaceAdapter.cpp new file mode 100644 index 000000000..4a8e54af1 --- /dev/null +++ b/src/Magpie.Core/HdrSurfaceAdapter.cpp @@ -0,0 +1,319 @@ +#include "pch.h" +#include "HdrSurfaceAdapter.h" + +#include "BackendDescriptorStore.h" +#include "DeviceResources.h" +#include "DirectXHelper.h" +#include "Logger.h" + +namespace Magpie { + +namespace { +constexpr char HLSL[] = R"( +cbuffer Transform : register(b0) { + float exposure; + float inverseExposure; + float sdrWhiteScale; + float hdrPeakNits; + float shoulder; + float referenceWhiteNits; + float sdrWhiteNits; + uint inputTransfer; + uint outputTransfer; + uint mode; + uint preserveAlpha; + float normalizationScale; +}; +Texture2D sourceTexture : register(t0); +RWTexture2D outputTexture : register(u0); + +float DecodeSrgb(float value) { + return value <= 0.04045 ? value / 12.92 : pow(max((value + 0.055) / 1.055, 0.0), 2.4); +} +float EncodeSrgb(float value) { + value = saturate(value); + return value <= 0.0031308 ? value * 12.92 : 1.055 * pow(value, 1.0 / 2.4) - 0.055; +} +float DecodeHlg(float value) { + const float a = 0.17883277; + const float b = 1.0 - 4.0 * a; + const float c = 0.5 - a * log(4.0 * a); + value = saturate(value); + return value <= 0.5 ? (value * value) / 3.0 : (exp((value - c) / a) + b) / 12.0; +} +float EncodeHlg(float value) { + const float a = 0.17883277; + const float b = 1.0 - 4.0 * a; + const float c = 0.5 - a * log(4.0 * a); + value = max(value, 0.0); + return saturate(value <= 1.0 / 12.0 ? sqrt(3.0 * value) : a * log(12.0 * value - b) + c); +} +float DecodePq(float value) { + const float m1 = 2610.0 / 16384.0; + const float m2 = 2523.0 / 32.0; + const float c1 = 3424.0 / 4096.0; + const float c2 = 2413.0 / 128.0; + const float c3 = 2392.0 / 128.0; + float p = pow(max(saturate(value), 0.0), 1.0 / m2); + return pow(max(p - c1, 0.0) / max(c2 - c3 * p, 1e-6), 1.0 / m1) * 10000.0; +} +float EncodePq(float value) { + const float m1 = 2610.0 / 16384.0; + const float m2 = 2523.0 / 32.0; + const float c1 = 3424.0 / 4096.0; + const float c2 = 2413.0 / 128.0; + const float c3 = 2392.0 / 128.0; + float p = pow(max(value, 0.0) / 10000.0, m1); + return saturate(pow((c1 + c2 * p) / (1.0 + c3 * p), m2)); +} +float3 Rec709ToRec2020(float3 value) { + return float3( + dot(value, float3(0.6274040, 0.3292820, 0.0433136)), + dot(value, float3(0.0690970, 0.9195400, 0.0113612)), + dot(value, float3(0.0163916, 0.0880132, 0.8955950))); +} +float3 Rec2020ToRec709(float3 value) { + return float3( + dot(value, float3(1.6604910, -0.5876411, -0.0728499)), + dot(value, float3(-0.1245505, 1.1328999, -0.0083494)), + dot(value, float3(-0.0181508, -0.1005789, 1.1187297))); +} +float DecodeTransfer(float value, uint transfer) { + if (transfer == 2) return DecodeSrgb(value); + // PQ is absolute-display-referred. Canonical scRGB uses 80 nit as its + // fixed reference; the monitor SDR white level is a separate parameter. + if (transfer == 3) return DecodePq(value) / 80.0; + if (transfer == 4) return DecodeHlg(value); + return value; +} +float EncodeTransfer(float value, uint transfer) { + if (transfer == 2) return EncodeSrgb(value); + if (transfer == 3) return EncodePq(value * 80.0); + if (transfer == 4) return EncodeHlg(value); + return value; +} +float3 MapHdrToSdr(float3 value) { + float referenceWhiteScale = max(sdrWhiteNits / 80.0, 1e-4); + float3 normalized = max(value, 0.0) * exposure / referenceWhiteScale; + float3 excess = max(normalized - 1.0, 0.0); + float headroom = max(hdrPeakNits / 80.0 - 1.0, 1.0); + float3 compressed = 1.0 - excess / + (excess + headroom * max(shoulder, 1e-3) + 1.0); + return normalized <= 1.0 ? normalized : saturate(compressed); +} +float3 MapSdrToHdr(float3 value) { + float3 mapped = max(value, 0.0); + float3 excess = max(mapped - 1.0, 0.0); + float3 denominator = max(1.0 - shoulder * excess, 1e-4); + float3 normalized = min(mapped, 1.0) + excess / denominator; + float referenceWhiteScale = max(sdrWhiteNits / 80.0, 1e-4); + float peakScale = max(hdrPeakNits / 80.0, referenceWhiteScale); + return min(normalized * referenceWhiteScale * inverseExposure, peakScale); +} +[numthreads(8, 8, 1)] +void Main(uint3 id : SV_DispatchThreadID) { + uint width, height; + outputTexture.GetDimensions(width, height); + if (id.x >= width || id.y >= height) return; + float4 value = sourceTexture.Load(int3(id.xy, 0)); + float3 result; + if (mode == 0) { + float3 mapped = MapHdrToSdr(value.rgb); + result = float3( + EncodeTransfer(mapped.r, outputTransfer), + EncodeTransfer(mapped.g, outputTransfer), + EncodeTransfer(mapped.b, outputTransfer)); + } else if (mode == 1) { + float3 decoded = float3( + DecodeTransfer(value.r, inputTransfer), + DecodeTransfer(value.g, inputTransfer), + DecodeTransfer(value.b, inputTransfer)); + result = MapSdrToHdr(decoded); + } else if (mode == 2) { + // Canonical scRGB may use a display SDR-white scale (for example 4.5 + // for a 360-nit SDR white). Bounded backends operate in the normalized + // 0..1 domain, so normalize against the frame's SDR white point. + result = max(value.rgb, 0.0) * normalizationScale / max(sdrWhiteNits / 80.0, 1e-4); + } else if (mode == 3) { + result = max(value.rgb, 0.0) * (sdrWhiteNits / 80.0) / max(normalizationScale, 1e-4); + } else if (mode == 5) { + // Canonical scRGB is linear with 1.0 == 80 nit. HDR10 also requires + // Rec.2020 primaries, so convert the canonical Rec.709 values first. + float3 rec2020 = Rec709ToRec2020(max(value.rgb, 0.0)); + result = float3( + EncodePq(rec2020.r * 80.0), + EncodePq(rec2020.g * 80.0), + EncodePq(rec2020.b * 80.0)); + } else if (mode == 6) { + float3 rec2020 = float3( + DecodePq(value.r) / 80.0, + DecodePq(value.g) / 80.0, + DecodePq(value.b) / 80.0); + result = max(Rec2020ToRec709(rec2020), 0.0); + } else { + result = value.rgb; + } + outputTexture[id.xy] = float4(result, preserveAlpha != 0 ? value.a : 1.0); +} +)"; + +struct AdapterConstants { + float exposure; + float inverseExposure; + float sdrWhiteScale; + float hdrPeakNits; + float shoulder; + float referenceWhiteNits; + float sdrWhiteNits; + uint32_t inputTransfer; + uint32_t outputTransfer; + uint32_t mode; + uint32_t preserveAlpha; + float normalizationScale; + float _padding[4]{}; +}; +static_assert(sizeof(AdapterConstants) == 64, "HDR adapter constant buffer layout must match HLSL"); +} + +bool HdrSurfaceAdapter::Initialize( + DeviceResources& deviceResources, + BackendDescriptorStore& descriptorStore +) noexcept { + _deviceResources = &deviceResources; + _descriptorStore = &descriptorStore; + winrt::com_ptr blob; + if (!DirectXHelper::CompileComputeShader(HLSL, "Main", blob.put(), "HdrSurfaceAdapter", nullptr, {}, true)) { + return false; + } + HRESULT hr = deviceResources.GetD3DDevice()->CreateComputeShader( + blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, _shader.put()); + if (FAILED(hr)) { + Logger::Get().ComError("创建 HDR 表面适配器 Compute Shader 失败", hr); + return false; + } + const D3D11_BUFFER_DESC desc{ + .ByteWidth = 64, + .Usage = D3D11_USAGE_DYNAMIC, + .BindFlags = D3D11_BIND_CONSTANT_BUFFER, + .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE + }; + return SUCCEEDED(deviceResources.GetD3DDevice()->CreateBuffer(&desc, nullptr, _constants.put())); +} + +bool HdrSurfaceAdapter::_Convert( + ID3D11Texture2D* input, + ID3D11Texture2D* output, + const HdrTransformParameters& parameters, + HdrTransferFunction transfer, + bool hdrToSdr, + uint32_t mode, + float normalizationScale +) const noexcept { + if (!_deviceResources || !_descriptorStore || !_shader || !_constants || !input || !output || + !parameters.IsValid()) return false; + D3D11_TEXTURE2D_DESC inputDesc{}, outputDesc{}; + input->GetDesc(&inputDesc); + output->GetDesc(&outputDesc); + if (inputDesc.Width != outputDesc.Width || inputDesc.Height != outputDesc.Height) return false; + ID3D11ShaderResourceView* inputSrv = _descriptorStore->GetShaderResourceView(input); + ID3D11UnorderedAccessView* outputUav = _descriptorStore->GetUnorderedAccessView(output); + if (!inputSrv || !outputUav) { + Logger::Get().Error(fmt::format( + "HDR adapter descriptors unavailable: inputSrv={} outputUav={} mode={} inputFormat={} outputFormat={}", + inputSrv != nullptr, outputUav != nullptr, mode, + static_cast(inputDesc.Format), static_cast(outputDesc.Format))); + return false; + } + const HdrTransformConstants base = HdrColorTransform::PrepareConstants( + parameters, + hdrToSdr ? HdrTransferFunction::Linear : transfer, + hdrToSdr ? transfer : HdrTransferFunction::Linear); + const AdapterConstants constants{ + base.exposure, base.inverseExposure, base.sdrWhiteScale, base.hdrPeakNits, + base.shoulder, parameters.referenceWhiteNits, parameters.sdrWhiteNits, + base.inputTransfer, base.outputTransfer, mode, + parameters.preserveAlpha ? 1u : 0u, normalizationScale + }; + D3D11_MAPPED_SUBRESOURCE mapped{}; + ID3D11DeviceContext4* context = _deviceResources->GetD3DDC(); + if (FAILED(context->Map(_constants.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped))) { + Logger::Get().Error(fmt::format("HDR adapter constants map failed: mode={}", mode)); + return false; + } + memcpy(mapped.pData, &constants, sizeof(constants)); + context->Unmap(_constants.get(), 0); + context->CSSetShader(_shader.get(), nullptr, 0); + ID3D11Buffer* constantBuffer = _constants.get(); + context->CSSetConstantBuffers(0, 1, &constantBuffer); + context->CSSetShaderResources(0, 1, &inputSrv); + context->CSSetUnorderedAccessViews(0, 1, &outputUav, nullptr); + context->Dispatch((outputDesc.Width + 7) / 8, (outputDesc.Height + 7) / 8, 1); + Logger::Get().Info(fmt::format( + "HDR adapter dispatch: mode={} input={} output={} white={:.3f} peak={:.3f} scale={:.3f}", + mode, static_cast(inputDesc.Format), static_cast(outputDesc.Format), + parameters.sdrWhiteNits, parameters.hdrPeakNits, normalizationScale)); + ID3D11ShaderResourceView* nullSrv = nullptr; + ID3D11UnorderedAccessView* nullUav = nullptr; + ID3D11Buffer* nullBuffer = nullptr; + context->CSSetShaderResources(0, 1, &nullSrv); + context->CSSetUnorderedAccessViews(0, 1, &nullUav, nullptr); + context->CSSetConstantBuffers(0, 1, &nullBuffer); + context->CSSetShader(nullptr, nullptr, 0); + return true; +} + +bool HdrSurfaceAdapter::ConvertHdrToSdr( + ID3D11Texture2D* input, ID3D11Texture2D* output, + const HdrTransformParameters& parameters, HdrTransferFunction outputTransfer +) const noexcept { + return _Convert(input, output, parameters, outputTransfer, true); +} + +bool HdrSurfaceAdapter::ConvertSdrToHdr( + ID3D11Texture2D* input, ID3D11Texture2D* output, + const HdrTransformParameters& parameters, HdrTransferFunction inputTransfer +) const noexcept { + return _Convert(input, output, parameters, inputTransfer, false, 1u); +} + +bool HdrSurfaceAdapter::ConvertHdrToBounded( + ID3D11Texture2D* input, ID3D11Texture2D* output, + const HdrTransformParameters& parameters, float normalizationScale +) const noexcept { + return _Convert(input, output, parameters, HdrTransferFunction::Linear, + false, 2u, normalizationScale); +} + +bool HdrSurfaceAdapter::ConvertBoundedToHdr( + ID3D11Texture2D* input, ID3D11Texture2D* output, + const HdrTransformParameters& parameters, float normalizationScale +) const noexcept { + return _Convert(input, output, parameters, HdrTransferFunction::Linear, + false, 3u, normalizationScale); +} + +bool HdrSurfaceAdapter::ConvertHdrToScRgb( + ID3D11Texture2D* input, ID3D11Texture2D* output, + const HdrTransformParameters& parameters +) const noexcept { + return _Convert(input, output, parameters, HdrTransferFunction::Linear, + false, 4u, 1.0f); +} + +bool HdrSurfaceAdapter::ConvertCanonicalToHdr10( + ID3D11Texture2D* input, ID3D11Texture2D* output, + const HdrTransformParameters& parameters +) const noexcept { + return _Convert(input, output, parameters, HdrTransferFunction::PQ, + false, 5u, 1.0f); +} + +bool HdrSurfaceAdapter::ConvertHdr10ToCanonical( + ID3D11Texture2D* input, ID3D11Texture2D* output, + const HdrTransformParameters& parameters +) const noexcept { + return _Convert(input, output, parameters, HdrTransferFunction::PQ, + false, 6u, 1.0f); +} + +} diff --git a/src/Magpie.Core/HdrSurfaceAdapter.h b/src/Magpie.Core/HdrSurfaceAdapter.h new file mode 100644 index 000000000..ba3e76712 --- /dev/null +++ b/src/Magpie.Core/HdrSurfaceAdapter.h @@ -0,0 +1,81 @@ +#pragma once + +#include "HdrColorTransform.h" + +namespace Magpie { + +class BackendDescriptorStore; +class DeviceResources; + +class HdrSurfaceAdapter { +public: + bool Initialize(DeviceResources& deviceResources, BackendDescriptorStore& descriptorStore) noexcept; + + bool ConvertHdrToSdr( + ID3D11Texture2D* input, + ID3D11Texture2D* output, + const HdrTransformParameters& parameters, + HdrTransferFunction outputTransfer = HdrTransferFunction::SRGB + ) const noexcept; + + bool ConvertSdrToHdr( + ID3D11Texture2D* input, + ID3D11Texture2D* output, + const HdrTransformParameters& parameters, + HdrTransferFunction inputTransfer = HdrTransferFunction::SRGB + ) const noexcept; + + bool ConvertHdrToBounded( + ID3D11Texture2D* input, + ID3D11Texture2D* output, + const HdrTransformParameters& parameters, + float normalizationScale + ) const noexcept; + + bool ConvertBoundedToHdr( + ID3D11Texture2D* input, + ID3D11Texture2D* output, + const HdrTransformParameters& parameters, + float normalizationScale + ) const noexcept; + + // Canonical HDR uses absolute nits internally; the FP16 presentation + // surface uses scRGB where 1.0 represents the 80-nit reference white. + bool ConvertHdrToScRgb( + ID3D11Texture2D* input, + ID3D11Texture2D* output, + const HdrTransformParameters& parameters + ) const noexcept; + + // Encode canonical linear scRGB into an HDR10/PQ R10 surface for terminal + // backends such as XeSS-FG. The canonical FP16 surface remains unchanged. + bool ConvertCanonicalToHdr10( + ID3D11Texture2D* input, + ID3D11Texture2D* output, + const HdrTransformParameters& parameters + ) const noexcept; + + bool ConvertHdr10ToCanonical( + ID3D11Texture2D* input, + ID3D11Texture2D* output, + const HdrTransformParameters& parameters + ) const noexcept; + +private: + bool _Convert( + ID3D11Texture2D* input, + ID3D11Texture2D* output, + const HdrTransformParameters& parameters, + HdrTransferFunction transfer, + bool hdrToSdr, + uint32_t mode = 0, + float normalizationScale = 1.0f + ) const noexcept; + + DeviceResources* _deviceResources = nullptr; + BackendDescriptorStore* _descriptorStore = nullptr; + winrt::com_ptr _shader; + winrt::com_ptr _constants; +}; + +} diff --git a/src/Magpie.Core/Magpie.Core.vcxproj b/src/Magpie.Core/Magpie.Core.vcxproj index 07173f643..64e5ab89b 100644 --- a/src/Magpie.Core/Magpie.Core.vcxproj +++ b/src/Magpie.Core/Magpie.Core.vcxproj @@ -6,12 +6,14 @@ true true false + false + E:\Magpie-0.6.5-build\native-cppwinrt\union\ low Win32Proj {0e5205ae-dfa9-4cb8-b662-e43cd6512e2a} 10.0.26100.0 - $(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ - $(SolutionDir)\bin\$(Platform)\$(Configuration)\ + $(BuildRoot)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ + $(BuildRoot)\bin\$(Platform)\$(Configuration)\ $(IntDir)\Generated Files\ @@ -27,12 +29,12 @@ - + - include;%(AdditionalIncludeDirectories) + E:\Magpie-0.6.5-build\native-cppwinrt\union;include;%(AdditionalIncludeDirectories) MP_ENABLE_FRAME_TRACE;%(PreprocessorDefinitions) @@ -45,6 +47,7 @@ + @@ -94,6 +97,7 @@ + @@ -115,13 +119,30 @@ + + + + + + + + + + + + + + + + + @@ -148,6 +169,16 @@ Create + + + + + + + + + + @@ -173,13 +204,18 @@ + + + + NotUsing + NotUsing @@ -205,9 +241,6 @@ NotUsing 4201;%(DisableSpecificWarnings) - - NotUsing - @@ -239,7 +272,12 @@ - + + $(SolutionDir)scripts\Generate-FidelityFXOpticalFlowShaders.ps1 + $(SolutionDir)..\..\scripts\Generate-FidelityFXOpticalFlowShaders.ps1 + + + diff --git a/src/Magpie.Core/Magpie.Core.vcxproj.filters b/src/Magpie.Core/Magpie.Core.vcxproj.filters index f6bf51d17..2a5d18345 100644 --- a/src/Magpie.Core/Magpie.Core.vcxproj.filters +++ b/src/Magpie.Core/Magpie.Core.vcxproj.filters @@ -44,6 +44,19 @@ Capture + + Capture + + Include + Helpers + Helpers + Include + Helpers + Helpers + Helpers + Helpers + Helpers + Helpers Capture @@ -142,6 +155,9 @@ Render + + Render + Render @@ -209,6 +225,16 @@ Capture + Capture + Include + Helpers + Helpers + Include + Helpers + Helpers + Helpers + Helpers + Helpers Capture diff --git a/src/Magpie.Core/NativeEffectBackend.h b/src/Magpie.Core/NativeEffectBackend.h index 4d88f72ff..8cb9d2c16 100644 --- a/src/Magpie.Core/NativeEffectBackend.h +++ b/src/Magpie.Core/NativeEffectBackend.h @@ -1,6 +1,8 @@ #pragma once #include "FrameGuidanceTypes.h" #include "ScalingOptions.h" +#include "HdrEffectBoundary.h" +#include namespace Magpie { @@ -9,6 +11,8 @@ class DeviceResources; struct NativeEffectDrawContext { ID3D11Texture2D* input = nullptr; ID3D11Texture2D* output = nullptr; + HdrFrameMetadata inputMetadata{}; + HdrFrameMetadata outputMetadata{}; FrameGuidanceFrameId frameId = 0; // Changes whenever an earlier effect in the chain changes its output for // the same captured frame. Native effects that cache duplicate frames must @@ -25,6 +29,9 @@ class NativeEffectBackend { public: virtual ~NativeEffectBackend() = default; + virtual void SetHdrBoundary(HdrEffectBoundaryContext context) noexcept { _hdrBoundary = std::move(context); } + const HdrEffectBoundaryContext& GetHdrBoundary() const noexcept { return _hdrBoundary; } + virtual FrameGuidanceRequirements GetFrameGuidanceRequirements() const noexcept { return {}; } @@ -58,6 +65,9 @@ class NativeEffectBackend { ) noexcept = 0; virtual bool Draw(const NativeEffectDrawContext& context) noexcept = 0; + +protected: + HdrEffectBoundaryContext _hdrBoundary{}; }; } diff --git a/src/Magpie.Core/NativeEffectBackendFactory.cpp b/src/Magpie.Core/NativeEffectBackendFactory.cpp index 5cb601b08..061a3354e 100644 --- a/src/Magpie.Core/NativeEffectBackendFactory.cpp +++ b/src/Magpie.Core/NativeEffectBackendFactory.cpp @@ -3,13 +3,17 @@ #include "NgxD3D12Core.h" #include "DLSSNRFilter.h" #include "DLSSSRUpscaler.h" +#include "FSR2ZeroMVUpscaler.h" +#include "FSR3ZeroMVUpscaler.h" #include "FSR2Upscaler.h" #include "FSR3Upscaler.h" #include "RTXVideoDenoiser.h" +#include "XeSSZeroMVUpscaler.h" #include "XeSSUpscaler.h" #include "FrameGuidanceDiagnostics.h" #include "Logger.h" #include "ScalingOptions.h" +#include "ScalingWindow.h" #include "EffectParameterRules.h" #include "OpticalFlowSettings.h" @@ -40,6 +44,7 @@ NativeEffectBackendResult CreateNativeEffectBackend( ID3D11Texture2D* input, ID3D11Texture2D* output ) noexcept { + const bool hdrEnabled = ScalingWindow::Get().Options().IsHdrCompatibilityEnabled(); if (effectName == "Diagnostics\\FrameGuidance_Motion" || effectName == "Diagnostics\\FrameGuidance_Confidence") { auto getParameter = [&](std::string_view name, float defaultValue) { @@ -61,7 +66,7 @@ NativeEffectBackendResult CreateNativeEffectBackend( } if (effectName == "DLSSNR\\DLSSNR_AI_Filter") { - const DLSSNRSettings settings = ParseDLSSNRSettings(option); + const DLSSNRSettings settings = ParseDLSSNRSettings(option, hdrEnabled); auto backend = std::make_unique(); if (!backend->Initialize(resources, ngxCore, input, output, settings)) { const char status[] = @@ -74,19 +79,118 @@ NativeEffectBackendResult CreateNativeEffectBackend( return { true, std::move(backend) }; } - if (IsSuperResolutionEffect(effectName)) { + + if (!hdrEnabled) { + if (IsSuperResolutionEffect(effectName)) { + const auto motion = ParseOpticalFlowRequest(option, + effectName == "DLSS\\DLSS_SR" ? OpticalFlowMethod::Nvidia : OpticalFlowMethod::None); + if (effectName == "DLSS\\DLSS_SR") + return CreateBackend(effectName, resources, input, output, + DLSSSRSettings{ .motionRequest = motion }); + if (effectName == "FSR2\\FSR2_SR") + return CreateBackend(effectName, resources, input, output, motion); + if (effectName == "XeSS\\XeSS_SR") + return CreateBackend(effectName, resources, input, output, motion); + return CreateBackend(effectName, resources, input, output, + motion, effectName == "FSR4\\FSR4_SR"); + } + } + + // Keep the legacy ZeroMV/Jitter/OpticalFlow contracts on their original + // backends. These names carry distinct temporal and auxiliary-resource + // semantics even when HDR compatibility is disabled. + if (hdrEnabled && (effectName == "DLSS\\DLSS_ZeroMV" || + effectName == "DLSS\\DLSS_ZeroMV_Jitter" || + effectName == "DLSS\\DLSS_OpticalFlow")) { + auto getParameter = [&](std::string_view name, float defaultValue) { + auto it = option.parameters.find(std::string(name)); + return it == option.parameters.end() ? defaultValue : it->second; + }; + const bool isLegacyOpticalFlow = effectName == "DLSS\\DLSS_OpticalFlow"; + const auto quality = static_cast(std::clamp( + static_cast(std::lround(getParameter("nvidiaOpticalFlowQuality", 2.0f))), + 0, int(NVIDIA_OPTICAL_FLOW_MAX_QUALITY))); + return CreateBackend( + effectName, resources, input, output, + DLSSSRSettings{ + .motionRequest = isLegacyOpticalFlow + ? MotionVectorRequest::Nvidia(quality) + : MotionVectorRequest{} + }); + } + + if (hdrEnabled && (effectName == "FSR2\\FSR2_ZeroMV" || + effectName == "FSR2\\FSR2_ZeroMV_Jitter" || + effectName == "FSR2\\FSR2_OpticalFlow")) { + return CreateBackend(effectName, resources, input, output, + effectName == "FSR2\\FSR2_OpticalFlow", + effectName == "FSR2\\FSR2_ZeroMV_Jitter"); + } + + const bool isFsr3 = effectName == "FSR3\\FSR3_ZeroMV" || + effectName == "FSR3\\FSR3_ZeroMV_Jitter" || + effectName == "FSR3\\FSR3_OpticalFlow"; + const bool isFsr4 = effectName == "FSR4\\FSR4_ZeroMV" || + effectName == "FSR4\\FSR4_ZeroMV_Jitter" || + effectName == "FSR4\\FSR4_OpticalFlow"; + if (hdrEnabled && (isFsr3 || isFsr4)) { + return CreateBackend(effectName, resources, input, output, + effectName.ends_with("OpticalFlow"), + effectName.ends_with("ZeroMV_Jitter"), isFsr4); + } + + if (hdrEnabled && (effectName == "XeSS\\XeSS_ZeroMV" || + effectName == "XeSS\\XeSS_ZeroMV_Jitter" || + effectName == "XeSS\\XeSS_OpticalFlow")) { + return CreateBackend(effectName, resources, input, output, + effectName == "XeSS\\XeSS_OpticalFlow", + effectName == "XeSS\\XeSS_ZeroMV_Jitter"); + } + + if (hdrEnabled && (effectName == "DLSS\\DLSS_SR" || + effectName == "FSR2\\FSR2_SR" || + effectName == "FSR3\\FSR3_SR" || + effectName == "FSR4\\FSR4_SR" || + effectName == "XeSS\\XeSS_SR")) { const auto motion = ParseOpticalFlowRequest(option, effectName == "DLSS\\DLSS_SR" ? OpticalFlowMethod::Nvidia : OpticalFlowMethod::None); + const D3D11_TEXTURE2D_DESC inputDesc = [&]() { + D3D11_TEXTURE2D_DESC desc{}; + input->GetDesc(&desc); + return desc; + }(); + const bool hdrInput = hdrEnabled && + inputDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT; + const FsrHdrProtocol hdrProtocol{ + .hdrColorInput = hdrInput, + .transfer = GroupBTransfer::Linear, + .preExposure = 1.0f, + .exposure = 1.0f, + .depthInverted = true, + .depthInfinite = true, + .useReactiveMask = true, + .useTransparencyMask = true, + }; if (effectName == "DLSS\\DLSS_SR") { - return CreateBackend(effectName, resources, input, output, - DLSSSRSettings{ .motionRequest = motion }); + auto backend = std::make_unique(); + backend->SetDlssHdrProtocol(hdrProtocol); + if (!backend->Initialize(resources, input, output, + DLSSSRSettings{ .motionRequest = motion })) return { true, nullptr }; + return { true, std::move(backend) }; + } + if (effectName == "FSR2\\FSR2_SR") { + auto backend = std::make_unique(); + backend->SetFsrHdrProtocol(hdrProtocol); + if (!backend->Initialize(resources, input, output, motion)) return { true, nullptr }; + return { true, std::move(backend) }; } - if (effectName == "FSR2\\FSR2_SR") - return CreateBackend(effectName, resources, input, output, motion); if (effectName == "XeSS\\XeSS_SR") return CreateBackend(effectName, resources, input, output, motion); - return CreateBackend(effectName, resources, input, output, - motion, effectName == "FSR4\\FSR4_SR"); + auto backend = std::make_unique(); + backend->SetFsrHdrProtocol(hdrProtocol); + if (!backend->Initialize(resources, input, output, motion, + effectName == "FSR4\\FSR4_SR")) return { true, nullptr }; + return { true, std::move(backend) }; } const bool isRtxVideo = effectName.starts_with("RTXVideo\\RTXVideo_Denoise_") || @@ -106,13 +210,13 @@ NativeEffectBackendResult CreateNativeEffectBackend( qualityLevel = 11; } auto backend = std::make_unique(); - if (!backend->Initialize(resources, input, output, qualityLevel)) { + if (!backend->Initialize(resources, input, output, qualityLevel, + isVsr ? RtxVideoEffectKind::Vsr : RtxVideoEffectKind::Denoise)) { Logger::Get().Error(fmt::format("Initialize native effect {} failed", effectName)); return { true, nullptr, backend->InitializationError() }; } return { true, std::move(backend) }; } - return {}; } diff --git a/src/Magpie.Core/NvCVImageD3D11Proxy.cpp b/src/Magpie.Core/NvCVImageD3D11Proxy.cpp new file mode 100644 index 000000000..e76ede084 --- /dev/null +++ b/src/Magpie.Core/NvCVImageD3D11Proxy.cpp @@ -0,0 +1,57 @@ +#include "pch.h" +#include "NvCVImageD3D11Proxy.h" + +namespace { + +HMODULE GetNvCVImageModule() noexcept { + static HMODULE module = []() noexcept { + if (HMODULE loaded = GetModuleHandleW(L"NVCVImage.dll")) { + return loaded; + } + + wchar_t modulePath[MAX_PATH]{}; + const DWORD length = GetModuleFileNameW(nullptr, modulePath, ARRAYSIZE(modulePath)); + if (length != 0 && length < ARRAYSIZE(modulePath)) { + std::wstring path(modulePath, length); + const size_t separator = path.find_last_of(L"\\/"); + if (separator != std::wstring::npos) { + path.resize(separator); + path += L"\\NVCVImage.dll"; + if (HMODULE runtime = LoadLibraryExW( + path.c_str(), nullptr, + LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_SYSTEM32)) { + return runtime; + } + } + } + return LoadLibraryExW( + L"NVCVImage.dll", nullptr, + LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_SYSTEM32); + }(); + return module; +} + +template +Function GetNvCVImageFunction(const char* name) noexcept { + return reinterpret_cast(GetProcAddress(GetNvCVImageModule(), name)); +} + +} + +NvCV_Status NvCVImage_InitFromD3D11Texture(NvCVImage* image, ID3D11Texture2D* texture) { + using Function = NvCV_Status(__cdecl*)(NvCVImage*, ID3D11Texture2D*); + const auto function = GetNvCVImageFunction("NvCVImage_InitFromD3D11Texture"); + return function ? function(image, texture) : NVCV_ERR_LIBRARY; +} + +NvCV_Status NvCVImage_MapResource(NvCVImage* image, struct CUstream_st* stream) { + using Function = NvCV_Status(__cdecl*)(NvCVImage*, struct CUstream_st*); + const auto function = GetNvCVImageFunction("NvCVImage_MapResource"); + return function ? function(image, stream) : NVCV_ERR_LIBRARY; +} + +NvCV_Status NvCVImage_UnmapResource(NvCVImage* image, struct CUstream_st* stream) { + using Function = NvCV_Status(__cdecl*)(NvCVImage*, struct CUstream_st*); + const auto function = GetNvCVImageFunction("NvCVImage_UnmapResource"); + return function ? function(image, stream) : NVCV_ERR_LIBRARY; +} diff --git a/src/Magpie.Core/NvCVImageD3D11Proxy.h b/src/Magpie.Core/NvCVImageD3D11Proxy.h new file mode 100644 index 000000000..14bbee2e0 --- /dev/null +++ b/src/Magpie.Core/NvCVImageD3D11Proxy.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +struct ID3D11Texture2D; + +NvCV_Status NvCVImage_InitFromD3D11Texture(NvCVImage* image, ID3D11Texture2D* texture); +NvCV_Status NvCVImage_MapResource(NvCVImage* image, struct CUstream_st* stream); +NvCV_Status NvCVImage_UnmapResource(NvCVImage* image, struct CUstream_st* stream); diff --git a/src/Magpie.Core/NvidiaOpticalFlowProvider.cpp b/src/Magpie.Core/NvidiaOpticalFlowProvider.cpp index 3d3321438..dc6e5ac84 100644 --- a/src/Magpie.Core/NvidiaOpticalFlowProvider.cpp +++ b/src/Magpie.Core/NvidiaOpticalFlowProvider.cpp @@ -4,6 +4,7 @@ #include "DeviceResources.h" #include "DirectXHelper.h" #include "Logger.h" +#include "ScalingWindow.h" #ifdef MP_ENABLE_NVIDIA_OPTICAL_FLOW #include @@ -91,6 +92,18 @@ void Densify(uint3 tid : SV_DispatchThreadID) { } )"; +constexpr char HDR_TO_NVOF_HLSL[] = R"( +Texture2D Source : register(t0); +RWTexture2D Target : register(u0); +[numthreads(8, 8, 1)] +void Convert(uint3 tid : SV_DispatchThreadID) { + uint width, height; Target.GetDimensions(width, height); + if (tid.x >= width || tid.y >= height) return; + float4 value = Source.Load(int3(tid.xy, 0)); + Target[tid.xy] = float4(saturate(max(value.rgb, 0.0) / 4.5), saturate(value.a)); +} +)"; + template T GetExport(HMODULE module, const char* name) noexcept { return reinterpret_cast(GetProcAddress(module, name)); @@ -141,6 +154,9 @@ FrameGuidanceMetadata MakeMetadata( ) noexcept { return { .frameId = frame.frameId, + .captureSequence = frame.captureSequence, + .resourceGeneration = frame.resourceGeneration, + .timestamp100ns = frame.timestamp100ns, .sourceExtent = frame.sourceExtent, .validRegion = frame.validRegion, .resetReason = resetReason, @@ -233,6 +249,9 @@ struct NvidiaOpticalFlowProvider::Impl { confidenceUav = nullptr; densifyShader = nullptr; paramsBuffer = nullptr; + hdrToNvofShader = nullptr; + hdrSourceSrv = nullptr; + hdrSourceTexture = nullptr; for (GpuQuerySlot& slot : gpuQuerySlots) { slot.disjoint = nullptr; slot.start = nullptr; @@ -244,6 +263,8 @@ struct NvidiaOpticalFlowProvider::Impl { gpuTimingSampleCount = 0; gpuTimingAvailable = false; profileLabel = {}; + inputDxgiFormat = DXGI_FORMAT_UNKNOWN; + inputBufferFormat = NV_OF_BUFFER_FORMAT_ABGR8; gridSize = 0; costEnabled = false; previousSlot = 0; @@ -355,12 +376,13 @@ struct NvidiaOpticalFlowProvider::Impl { } bool CreateTextures() noexcept { - const UINT sourceBind = D3D11_BIND_SHADER_RESOURCE; + const UINT sourceBind = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS; for (auto& texture : input) { texture = DirectXHelper::CreateTexture2D( - device, DXGI_FORMAT_B8G8R8A8_UNORM, extent.width, extent.height, + device, inputDxgiFormat, extent.width, extent.height, sourceBind); - if (!texture) return false; + if (!texture || FAILED(device->CreateUnorderedAccessView(texture.get(), nullptr, + inputUav[&texture - &input[0]].put()))) return false; } const uint32_t flowWidth = (extent.width + gridSize - 1) / gridSize; @@ -417,6 +439,12 @@ struct NvidiaOpticalFlowProvider::Impl { } bool CreatePostProcess() noexcept { + winrt::com_ptr hdrBlob; + if (!DirectXHelper::CompileComputeShader( + HDR_TO_NVOF_HLSL, "Convert", hdrBlob.put(), + "FrameGuidance/NVOF_HdrToInput.hlsl") || + FAILED(device->CreateComputeShader(hdrBlob->GetBufferPointer(), + hdrBlob->GetBufferSize(), nullptr, hdrToNvofShader.put()))) return false; winrt::com_ptr shaderBlob; if (!DirectXHelper::CompileComputeShader( DENSIFY_FLOW_HLSL, "Densify", shaderBlob.put(), @@ -504,9 +532,32 @@ struct NvidiaOpticalFlowProvider::Impl { HasFormat(costFormats, DXGI_FORMAT_R8_UINT); if (!QueryFormats(NV_OF_BUFFER_USAGE_INPUT, inputFormats) || !QueryFormats(NV_OF_BUFFER_USAGE_OUTPUT, outputFormats) || - !HasFormat(inputFormats, DXGI_FORMAT_B8G8R8A8_UNORM) || !HasFormat(outputFormats, DXGI_FORMAT_R16G16_SINT)) { - Logger::Get().Warn("NVOF required ABGR8/S10.5 formats unavailable"); + Logger::Get().Warn("NVOF required S10.5 output format unavailable"); + return false; + } + const bool hdrEnabled = ScalingWindow::Get().Options().IsHdrCompatibilityEnabled(); + if (!hdrEnabled) { + if (!HasFormat(inputFormats, DXGI_FORMAT_B8G8R8A8_UNORM)) { + Logger::Get().Warn("NVOF required ABGR8 input format unavailable"); + return false; + } + inputDxgiFormat = DXGI_FORMAT_B8G8R8A8_UNORM; + inputBufferFormat = NV_OF_BUFFER_FORMAT_ABGR8; + } else if (HasFormat(inputFormats, DXGI_FORMAT_B8G8R8A8_UNORM)) { + inputDxgiFormat = DXGI_FORMAT_B8G8R8A8_UNORM; + inputBufferFormat = NV_OF_BUFFER_FORMAT_ABGR8; + } else if (HasFormat(inputFormats, DXGI_FORMAT_R8G8B8A8_UNORM)) { + inputDxgiFormat = DXGI_FORMAT_R8G8B8A8_UNORM; + inputBufferFormat = NV_OF_BUFFER_FORMAT_ABGR8; + } else if (HasFormat(inputFormats, DXGI_FORMAT_R8_UNORM)) { + inputDxgiFormat = DXGI_FORMAT_R8_UNORM; + inputBufferFormat = NV_OF_BUFFER_FORMAT_GRAYSCALE8; + } else if (HasFormat(inputFormats, DXGI_FORMAT_NV12)) { + inputDxgiFormat = DXGI_FORMAT_NV12; + inputBufferFormat = NV_OF_BUFFER_FORMAT_NV12; + } else { + Logger::Get().Warn("NVOF input formats unavailable (ABGR8/GRAYSCALE8/NV12)"); return false; } @@ -539,7 +590,7 @@ struct NvidiaOpticalFlowProvider::Impl { .enableRoi = NV_OF_FALSE, .predDirection = NV_OF_PRED_DIRECTION_BOTH, .enableGlobalFlow = NV_OF_FALSE, - .inputBufferFormat = NV_OF_BUFFER_FORMAT_ABGR8 + .inputBufferFormat = inputBufferFormat }; NV_OF_STATUS status = api.nvOFInit(session, &init); bidirectional = status == NV_OF_SUCCESS; @@ -669,11 +720,17 @@ struct NvidiaOpticalFlowProvider::Impl { uint64_t gpuTimingSampleCount = 0; std::string_view profileLabel; bool bidirectional = false; + std::array, 2> inputUav; + winrt::com_ptr hdrSourceSrv; + ID3D11Texture2D* hdrSourceTexture = nullptr; + winrt::com_ptr hdrToNvofShader; bool costEnabled = false; bool gpuTimingAvailable = false; bool historyValid = false; OpticalFlowInitializationError initializationError = OpticalFlowInitializationError::ProviderUnavailable; + DXGI_FORMAT inputDxgiFormat = DXGI_FORMAT_UNKNOWN; + NV_OF_BUFFER_FORMAT inputBufferFormat = NV_OF_BUFFER_FORMAT_ABGR8; }; NvidiaOpticalFlowProvider::NvidiaOpticalFlowProvider( @@ -698,9 +755,44 @@ bool NvidiaOpticalFlowProvider::BeginFrame( if (!frame.color || frame.sourceExtent != impl.extent || !impl.session) { return false; } + D3D11_TEXTURE2D_DESC frameDesc{}; + frame.color->GetDesc(&frameDesc); + if (frameDesc.Width != impl.extent.width || frameDesc.Height != impl.extent.height || + (frameDesc.Format != impl.inputDxgiFormat && + !(ScalingWindow::Get().Options().IsHdrCompatibilityEnabled() && + frameDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT))) { + Logger::Get().Warn(fmt::format( + "NVOF input format mismatch: frame={}x{} dxgi={}, session dxgi={}; " + "supported contracts are ABGR8/GRAYSCALE8/NV12", + frameDesc.Width, frameDesc.Height, + static_cast(frameDesc.Format), + static_cast(impl.inputDxgiFormat))); + return false; + } const uint32_t currentSlot = impl.historyValid ? 1u - impl.previousSlot : 0u; - impl.context->CopyResource(impl.input[currentSlot].get(), frame.color); + if (frameDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT) { + if (impl.hdrSourceTexture != frame.color) { + impl.hdrSourceSrv = nullptr; + if (FAILED(impl.device->CreateShaderResourceView( + frame.color, nullptr, impl.hdrSourceSrv.put()))) return false; + impl.hdrSourceTexture = frame.color; + } + ID3D11ShaderResourceView* srv = impl.hdrSourceSrv.get(); + ID3D11UnorderedAccessView* uav = impl.inputUav[currentSlot].get(); + impl.context->CSSetShader(impl.hdrToNvofShader.get(), nullptr, 0); + impl.context->CSSetShaderResources(0, 1, &srv); + impl.context->CSSetUnorderedAccessViews(0, 1, &uav, nullptr); + impl.context->Dispatch((impl.extent.width + 7) / 8, + (impl.extent.height + 7) / 8, 1); + ID3D11ShaderResourceView* nullSrv = nullptr; + ID3D11UnorderedAccessView* nullUav = nullptr; + impl.context->CSSetShaderResources(0, 1, &nullSrv); + impl.context->CSSetUnorderedAccessViews(0, 1, &nullUav, nullptr); + impl.context->CSSetShader(nullptr, nullptr, 0); + } else { + impl.context->CopyResource(impl.input[currentSlot].get(), frame.color); + } if (!impl.historyValid) { impl.ClearDenseOutput(); impl.previousSlot = currentSlot; diff --git a/src/Magpie.Core/PassThroughFrames.cpp b/src/Magpie.Core/PassThroughFrames.cpp index 7b662ca91..73c79f999 100644 --- a/src/Magpie.Core/PassThroughFrames.cpp +++ b/src/Magpie.Core/PassThroughFrames.cpp @@ -8,7 +8,7 @@ namespace Magpie { namespace { -constexpr char REFERENCE_HLSL[] = R"( +constexpr char REFERENCE_LDR_HLSL[] = R"( Texture2D Input : register(t0); SamplerState LinearClamp : register(s0); RWTexture2D Output : register(u0); @@ -21,6 +21,29 @@ void Reference(uint3 id : SV_DispatchThreadID) { Output[id.xy] = float4(Input.SampleLevel(LinearClamp, uv, 0).rgb, 1.0); } )"; + +constexpr char REFERENCE_HDR_HLSL[] = R"( +cbuffer Transform : register(b0) { + uint hdrEnabled; + float exposure; + float sdrWhiteNits; + float shoulder; +}; +Texture2D Input : register(t0); +SamplerState LinearClamp : register(s0); +RWTexture2D Output : register(u0); + +[numthreads(8, 8, 1)] +void Reference(uint3 id : SV_DispatchThreadID) { + uint width, height; + Output.GetDimensions(width, height); + if (id.x >= width || id.y >= height) return; + float2 uv = (float2(id.xy) + 0.5) / float2(width, height); + float3 rgb = Input.SampleLevel(LinearClamp, uv, 0).rgb; + // Canonical HDR and presentation both use linear scRGB. + Output[id.xy] = float4(rgb, 1.0); +} +)"; } void PassThroughFrames::_ClearBackend() noexcept { @@ -29,36 +52,51 @@ void PassThroughFrames::_ClearBackend() noexcept { _handles.fill(nullptr); _valid.fill(false); _frameIds.fill(0); + _metadata.fill({}); _current = nullptr; _previous = nullptr; _inputView = nullptr; _outputView = nullptr; _shader = nullptr; _sampler = nullptr; + _constants = nullptr; + _hdrEnabled = false; + _hdrParameters = {}; _currentValid = false; _previousValid = false; + _currentMetadata = {}; + _previousMetadata = {}; + _presentedMetadata = {}; } bool PassThroughFrames::InitializeBackend(DeviceResources& resources, - ID3D11Texture2D* input, ID3D11Texture2D* output, uint32_t slotCount) noexcept { + ID3D11Texture2D* input, ID3D11Texture2D* output, uint32_t slotCount, + bool hdrEnabled, const HdrTransformParameters& hdrParameters, + const HdrFrameMetadata& frameMetadata) noexcept { _ClearBackend(); _backendResources = &resources; + _hdrEnabled = hdrEnabled; + _hdrParameters = hdrParameters.IsValid() ? hdrParameters : HdrTransformParameters{}; + _currentMetadata = frameMetadata; if (!input || !output || slotCount == 0 || slotCount > MAX_SLOTS) return false; D3D11_TEXTURE2D_DESC outputDesc{}; output->GetDesc(&outputDesc); _width = outputDesc.Width; _height = outputDesc.Height; + const DXGI_FORMAT referenceFormat = hdrEnabled + ? DXGI_FORMAT_R16G16B16A16_FLOAT + : DXGI_FORMAT_R8G8B8A8_UNORM; auto device = resources.GetD3DDevice(); auto fail = [&]() { Logger::Get().Warn("Pass-through reference resources unavailable; effects remain enabled"); _ClearBackend(); return false; }; - _current = DirectXHelper::CreateTexture2D(device, DXGI_FORMAT_R8G8B8A8_UNORM, + _current = DirectXHelper::CreateTexture2D(device, referenceFormat, _width, _height, D3D11_BIND_UNORDERED_ACCESS); if (!_current) return fail(); if (slotCount > 1) { - _previous = DirectXHelper::CreateTexture2D(device, DXGI_FORMAT_R8G8B8A8_UNORM, + _previous = DirectXHelper::CreateTexture2D(device, referenceFormat, _width, _height, 0); if (!_previous) return fail(); } @@ -66,10 +104,33 @@ bool PassThroughFrames::InitializeBackend(DeviceResources& resources, if (SUCCEEDED(hr)) hr = device->CreateUnorderedAccessView(_current.get(), nullptr, _outputView.put()); if (FAILED(hr)) return fail(); winrt::com_ptr blob; - if (!DirectXHelper::CompileComputeShader(REFERENCE_HLSL, "Reference", blob.put(), + if (!DirectXHelper::CompileComputeShader( + hdrEnabled ? REFERENCE_HDR_HLSL : REFERENCE_LDR_HLSL, + "Reference", blob.put(), "PassThroughReference", nullptr, {}, true)) return fail(); hr = device->CreateComputeShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, _shader.put()); if (FAILED(hr)) return fail(); + if (hdrEnabled) { + struct ReferenceConstants { + uint32_t hdrEnabled; + float exposure; + float sdrWhiteNits; + float shoulder; + } constants{ + 1u, + _hdrParameters.exposure, + _hdrParameters.sdrWhiteNits, + _hdrParameters.shoulder + }; + D3D11_BUFFER_DESC constantsDesc{ + .ByteWidth = sizeof(constants), + .Usage = D3D11_USAGE_DEFAULT, + .BindFlags = D3D11_BIND_CONSTANT_BUFFER + }; + D3D11_SUBRESOURCE_DATA constantsData{ .pSysMem = &constants }; + hr = device->CreateBuffer(&constantsDesc, &constantsData, _constants.put()); + if (FAILED(hr)) return fail(); + } D3D11_SAMPLER_DESC samplerDesc{}; samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; samplerDesc.AddressU = samplerDesc.AddressV = samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; @@ -78,7 +139,7 @@ bool PassThroughFrames::InitializeBackend(DeviceResources& resources, if (FAILED(hr)) return fail(); for (uint32_t i = 0; i < slotCount; ++i) { auto& slot = _backendSlots[i]; - slot.texture = DirectXHelper::CreateTexture2D(device, DXGI_FORMAT_R8G8B8A8_UNORM, + slot.texture = DirectXHelper::CreateTexture2D(device, referenceFormat, _width, _height, D3D11_BIND_SHADER_RESOURCE, D3D11_USAGE_DEFAULT, D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX); if (!slot.texture) return fail(); @@ -126,6 +187,7 @@ void PassThroughFrames::UpdateBackend(uint64_t captureFrameId, bool newCapture) if (newCapture && _previous && _currentValid) { context->CopyResource(_previous.get(), _current.get()); _previousFrameId = _currentFrameId; + _previousMetadata = _currentMetadata; _previousValid = true; } context->ClearState(); @@ -133,15 +195,28 @@ void PassThroughFrames::UpdateBackend(uint64_t captureFrameId, bool newCapture) auto output = _outputView.get(); auto sampler = _sampler.get(); context->CSSetShader(_shader.get(), nullptr, 0); + if (_constants) { + auto constants = _constants.get(); + context->CSSetConstantBuffers(0, 1, &constants); + } context->CSSetShaderResources(0, 1, &input); context->CSSetUnorderedAccessViews(0, 1, &output, nullptr); context->CSSetSamplers(0, 1, &sampler); context->Dispatch((_width + 7) / 8, (_height + 7) / 8, 1); context->ClearState(); _currentFrameId = captureFrameId; + _currentMetadata.frameId = captureFrameId; _currentValid = true; } +void PassThroughFrames::UpdateBackend(const HdrFrame& frame, bool newCapture) noexcept { + UpdateBackend(frame.metadata.frameId, newCapture); + if (_hdrEnabled && frame.IsCanonical()) { + _currentMetadata = frame.metadata; + _currentMetadata.stage = HdrFrameStage::CanonicalInput; + } +} + void PassThroughFrames::Publish(uint32_t slot, bool generatedFrame) noexcept { FrameTrace::Scope traceReference(FrameTrace::Event::ReferencePublish); if (!_sharingEnabled || !_backendSlots[slot].texture || !_currentValid) return; @@ -149,6 +224,9 @@ void PassThroughFrames::Publish(uint32_t slot, bool generatedFrame) noexcept { _backendResources->GetD3DDC()->CopyResource(_backendSlots[slot].texture.get(), previous ? _previous.get() : _current.get()); _frameIds[slot] = previous ? _previousFrameId : _currentFrameId; + _metadata[slot] = previous ? _previousMetadata : _currentMetadata; + _metadata[slot].stage = HdrFrameStage::PublishedOutput; + _metadata[slot].generated = generatedFrame; _valid[slot] = true; } @@ -174,6 +252,7 @@ bool PassThroughFrames::Consume(uint32_t slot) noexcept { } _frontendResources->GetD3DDC()->CopyResource(_base.get(), _frontendSlots[slot].texture.get()); _baseFrameId = _frameIds[slot]; + _presentedMetadata = _metadata[slot]; _baseValid = true; return true; } @@ -184,6 +263,7 @@ void PassThroughFrames::OnPresented() noexcept { if (_presentedValid) { _frontendResources->GetD3DDC()->CopyResource(_presented.get(), _base.get()); _presentedFrameId = _baseFrameId; + _presentedMetadata.stage = HdrFrameStage::PresentedOutput; } } diff --git a/src/Magpie.Core/PassThroughFrames.h b/src/Magpie.Core/PassThroughFrames.h index 69a0b1f6e..f84111c4c 100644 --- a/src/Magpie.Core/PassThroughFrames.h +++ b/src/Magpie.Core/PassThroughFrames.h @@ -1,4 +1,5 @@ #pragma once +#include "HdrColorTransform.h" #include #include #include @@ -13,9 +14,12 @@ class PassThroughFrames { public: static constexpr uint32_t MAX_SLOTS = 4; bool InitializeBackend(DeviceResources& resources, ID3D11Texture2D* input, - ID3D11Texture2D* output, uint32_t slotCount) noexcept; + ID3D11Texture2D* output, uint32_t slotCount, bool hdrEnabled = false, + const HdrTransformParameters& hdrParameters = {}, + const HdrFrameMetadata& frameMetadata = {}) noexcept; bool OpenFrontend(DeviceResources& resources, uint32_t slotCount) noexcept; void UpdateBackend(uint64_t captureFrameId, bool newCapture) noexcept; + void UpdateBackend(const HdrFrame& frame, bool newCapture) noexcept; void Publish(uint32_t slot, bool generatedFrame) noexcept; bool Consume(uint32_t slot) noexcept; void OnPresented() noexcept; @@ -32,6 +36,9 @@ class PassThroughFrames { : (_baseValid ? _base.get() : nullptr); } uint64_t PresentedCaptureFrameId() const noexcept { return _presentedFrameId; } + const HdrFrameMetadata& PresentedFrameMetadata() const noexcept { + return _presentedMetadata; + } private: struct Slot { @@ -45,6 +52,7 @@ class PassThroughFrames { std::array _frontendSlots; std::array _handles{}; std::array _frameIds{}; + std::array _metadata{}; std::array _valid{}; // Reconfiguration is frontend-blocked. Failure disables future reference // transactions atomically, retaining resources until in-flight work is done. @@ -54,12 +62,17 @@ class PassThroughFrames { winrt::com_ptr _previous; winrt::com_ptr _inputView; winrt::com_ptr _outputView; + winrt::com_ptr _constants; winrt::com_ptr _shader; winrt::com_ptr _sampler; + bool _hdrEnabled = false; + HdrTransformParameters _hdrParameters{}; uint32_t _width = 0; uint32_t _height = 0; uint64_t _currentFrameId = 0; uint64_t _previousFrameId = 0; + HdrFrameMetadata _currentMetadata{}; + HdrFrameMetadata _previousMetadata{}; bool _currentValid = false; bool _previousValid = false; winrt::com_ptr _base; @@ -68,6 +81,7 @@ class PassThroughFrames { bool _presentedValid = false; uint64_t _baseFrameId = 0; uint64_t _presentedFrameId = 0; + HdrFrameMetadata _presentedMetadata{}; }; } diff --git a/src/Magpie.Core/RTXVideoDenoiser.cpp b/src/Magpie.Core/RTXVideoDenoiser.cpp index aaaa6bd1e..9b2a214ed 100644 --- a/src/Magpie.Core/RTXVideoDenoiser.cpp +++ b/src/Magpie.Core/RTXVideoDenoiser.cpp @@ -2,12 +2,14 @@ #include "RTXVideoDenoiser.h" #include "DeviceResources.h" #include "Logger.h" +#include "ScalingWindow.h" +#include "StrHelper.h" #ifdef MP_ENABLE_RTX_VIDEO_DENOISE #include -#include #include +#include "NvCVImageD3D11Proxy.h" // Required by NVIDIA's MIT-licensed proxy loader. USE_APP_PATH below makes it // load the runtime copied beside Magpie.exe rather than an obsolete system SDK. @@ -17,6 +19,81 @@ namespace Magpie { using NvVFXCudaStreamSynchronizeFn = NvCV_Status(NvVFX_API*)(CUstream); +static std::wstring GetExecutableDirectory() noexcept { + wchar_t modulePath[MAX_PATH]{}; + const DWORD moduleLength = GetModuleFileNameW(nullptr, modulePath, ARRAYSIZE(modulePath)); + if (moduleLength == 0 || moduleLength >= ARRAYSIZE(modulePath)) { + return {}; + } + std::wstring directory(modulePath, moduleLength); + const size_t separator = directory.find_last_of(L"\\/"); + if (separator == std::wstring::npos) { + return {}; + } + directory.resize(separator); + return directory; +} + +static std::wstring GetModulePath(HMODULE module) noexcept { + wchar_t modulePath[MAX_PATH]{}; + const DWORD moduleLength = GetModuleFileNameW(module, modulePath, ARRAYSIZE(modulePath)); + return moduleLength == 0 || moduleLength >= ARRAYSIZE(modulePath) + ? std::wstring{} + : std::wstring(modulePath, moduleLength); +} + +static HMODULE LoadRuntimeModule(const std::wstring& directory, const wchar_t* name) noexcept { + const std::wstring expectedPath = directory + L"\\" + name; + HMODULE module = GetModuleHandleW(name); + if (!module) { + SetLastError(ERROR_SUCCESS); + module = LoadLibraryExW( + expectedPath.c_str(), nullptr, + LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_SYSTEM32); + } + const std::wstring loadedPath = GetModulePath(module); + if (!module) { + const DWORD error = GetLastError(); + Logger::Get().ComError(fmt::format( + "RTX Video LoadLibraryExW failed: {} path={}", + StrHelper::UTF16ToUTF8(expectedPath), error), HRESULT_FROM_WIN32(error)); + return nullptr; + } + Logger::Get().Info(fmt::format( + "RTX Video runtime module: {} handle=0x{:X}", + StrHelper::UTF16ToUTF8(loadedPath), reinterpret_cast(module))); + if (_wcsicmp(loadedPath.c_str(), expectedPath.c_str()) != 0) { + Logger::Get().Error(fmt::format( + "RTX Video runtime module path mismatch: expected={}, loaded={}", + StrHelper::UTF16ToUTF8(expectedPath), StrHelper::UTF16ToUTF8(loadedPath))); + return nullptr; + } + return module; +} + +static HMODULE LoadSystemModule(const wchar_t* name) noexcept { + HMODULE module = GetModuleHandleW(name); + if (!module) { + module = LoadLibraryExW(name, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + } + const DWORD error = module ? ERROR_SUCCESS : GetLastError(); + Logger::Get().Info(fmt::format( + "RTX Video system module {}: handle=0x{:X} path={} win32={} hr=0x{:08X}", + StrHelper::UTF16ToUTF8(name), reinterpret_cast(module), + StrHelper::UTF16ToUTF8(GetModulePath(module)), error, + static_cast(HRESULT_FROM_WIN32(error)))); + return module; +} + +static void LogExport(HMODULE module, const char* name) noexcept { + const auto address = reinterpret_cast(GetProcAddress(module, name)); + const DWORD error = address ? ERROR_SUCCESS : GetLastError(); + Logger::Get().Info(fmt::format( + "RTX Video export {}: module={} address=0x{:X} win32={} hr=0x{:08X}", + name, StrHelper::UTF16ToUTF8(GetModulePath(module)), address, error, + static_cast(HRESULT_FROM_WIN32(error)))); +} + static bool VFXSucceeded(NvCV_Status status, const char* operation) noexcept { if (status == NVCV_SUCCESS) { return true; @@ -31,12 +108,18 @@ static bool VFXSucceeded(NvCV_Status status, const char* operation) noexcept { struct RTXVideoDenoiser::Impl { NvVFX_Handle effect = nullptr; CUstream stream = nullptr; + ID3D11DeviceContext4* d3dContext = nullptr; NvCVImage inputD3D; NvCVImage outputD3D; NvCVImage inputGPU; NvCVImage outputGPU; + NvCVImage outputCPU; + NvCVImage_PixelFormat nativePixelFormat = NVCV_RGBA; + unsigned outputWidth = 0; + unsigned outputHeight = 0; NvCVImage temporaryGPU; NvVFXCudaStreamSynchronizeFn synchronize = nullptr; + uint64_t drawCount = 0; float inputScale = 1.0f; float outputScale = 1.0f; @@ -51,6 +134,7 @@ struct RTXVideoDenoiser::Impl { // their subsequent C++ destructors no-ops. NvCVImage_Dealloc(&temporaryGPU); NvCVImage_Dealloc(&outputGPU); + NvCVImage_Dealloc(&outputCPU); NvCVImage_Dealloc(&inputGPU); NvCVImage_Dealloc(&outputD3D); NvCVImage_Dealloc(&inputD3D); @@ -66,20 +150,25 @@ RTXVideoDenoiser::RTXVideoDenoiser() = default; RTXVideoDenoiser::~RTXVideoDenoiser() = default; bool RTXVideoDenoiser::Initialize( - DeviceResources&, + DeviceResources& resources, ID3D11Texture2D* input, ID3D11Texture2D* output, - uint32_t qualityLevel + uint32_t qualityLevel, + RtxVideoEffectKind kind ) noexcept { _impl.reset(); _qualityLevel = qualityLevel; + _kind = kind; _initializationError = ScalingError::NoError; - const bool isUpscaleQuality = qualityLevel >= 1 && qualityLevel <= 4; - const bool isDenoiseQuality = qualityLevel >= 8 && qualityLevel <= 11; - const bool isHighBitrateQuality = qualityLevel >= 16 && qualityLevel <= 19; - if (!isUpscaleQuality && !isDenoiseQuality && !isHighBitrateQuality) { - Logger::Get().Error(fmt::format("Invalid RTX Video denoise quality level: {}", qualityLevel)); + const bool isVsr = kind == RtxVideoEffectKind::Vsr; + const bool isDenoise = kind == RtxVideoEffectKind::Denoise; + const bool validVsrQuality = isVsr && qualityLevel >= 1 && qualityLevel <= 4; + const bool validDenoiseQuality = isDenoise && qualityLevel >= 8 && qualityLevel <= 11; + const bool validHighBitrateQuality = isDenoise && qualityLevel >= 16 && qualityLevel <= 19; + if (!validVsrQuality && !validDenoiseQuality && !validHighBitrateQuality) { + Logger::Get().Error(fmt::format("Invalid RTX Video {} quality level: {}", + isVsr ? "VSR" : "Denoise", qualityLevel)); return false; } @@ -87,37 +176,97 @@ bool RTXVideoDenoiser::Initialize( D3D11_TEXTURE2D_DESC outputDesc{}; input->GetDesc(&inputDesc); output->GetDesc(&outputDesc); - if ((isDenoiseQuality && + if ((isDenoise && (inputDesc.Width != outputDesc.Width || inputDesc.Height != outputDesc.Height)) || inputDesc.SampleDesc.Count != 1 || outputDesc.SampleDesc.Count != 1) { Logger::Get().Error("RTX Video received invalid output dimensions or an MSAA texture"); return false; } - // NVIDIA's proxy loader checks this before its first LoadLibrary call. + const std::wstring runtimeDirectory = GetExecutableDirectory(); + if (runtimeDirectory.empty()) { + Logger::Get().Error("RTX Video executable directory lookup failed"); + return false; + } + SetDllDirectoryW(runtimeDirectory.c_str()); + Logger::Get().Info(fmt::format( + "RTX Video runtime directory: {}", + StrHelper::UTF16ToUTF8(runtimeDirectory))); + // Keep the shared initialization order explicit: CUDA driver, NGX runtime, + // image ABI, effect ABI, then the VideoSuperRes implementation. SetEnvironmentVariableW(L"NV_VIDEO_EFFECTS_PATH", L"USE_APP_PATH"); + HMODULE cudaModule = LoadSystemModule(L"nvcuda.dll"); + HMODULE ngxModule = LoadRuntimeModule(runtimeDirectory, L"nvngxruntime.dll"); + HMODULE cvModule = LoadRuntimeModule(runtimeDirectory, L"NVCVImage.dll"); + HMODULE vfxModule = LoadRuntimeModule(runtimeDirectory, L"NVVideoEffects.dll"); + HMODULE superResModule = LoadRuntimeModule(runtimeDirectory, L"nvVFXVideoSuperRes.dll"); + if (!cudaModule || !ngxModule || !cvModule || !vfxModule || !superResModule) { + Logger::Get().Error("RTX Video runtime dependency initialization failed"); + return false; + } + LogExport(cvModule, "NvCVImage_Alloc"); + LogExport(vfxModule, "NvVFX_CreateEffect"); + LogExport(vfxModule, "NvVFX_GetVersion"); + unsigned int runtimeVersion = 0; + const NvCV_Status versionStatus = NvVFX_GetVersion(&runtimeVersion); + if (!VFXSucceeded(versionStatus, "NvVFX_GetVersion")) { + return false; + } + Logger::Get().Info(fmt::format( + "RTX Video runtime version: 0x{:08X} ({}.{}.{})", + runtimeVersion, (runtimeVersion >> 24) & 0xFF, (runtimeVersion >> 16) & 0xFF, + (runtimeVersion >> 8) & 0xFF)); auto impl = std::make_unique(); - auto isFloatFormat = [](DXGI_FORMAT format) noexcept { - return format == DXGI_FORMAT_R16G16B16A16_FLOAT || - format == DXGI_FORMAT_R32G32B32A32_FLOAT || - format == DXGI_FORMAT_R11G11B10_FLOAT; - }; - // VideoSuperRes consumes integer RGBA in the 0..255 range. Magpie normally - // renders effects into FP16 textures whose color range is 0..1. - impl->inputScale = isFloatFormat(inputDesc.Format) ? 255.0f : 1.0f; - impl->outputScale = isFloatFormat(outputDesc.Format) ? (1.0f / 255.0f) : 1.0f; + impl->d3dContext = resources.GetD3DDC(); + if (ScalingWindow::Get().Options().IsHdrCompatibilityEnabled()) { + // The HDR boundary uses an explicit normalized-D3D/U8 bridge. + impl->inputScale = 1.0f / 255.0f; + impl->outputScale = 255.0f; + } else { + const auto isFloatFormat = [](DXGI_FORMAT format) noexcept { + return format == DXGI_FORMAT_R16G16B16A16_FLOAT || + format == DXGI_FORMAT_R32G32B32A32_FLOAT || + format == DXGI_FORMAT_R11G11B10_FLOAT; + }; + impl->inputScale = isFloatFormat(inputDesc.Format) ? 255.0f : 1.0f; + impl->outputScale = isFloatFormat(outputDesc.Format) ? (1.0f / 255.0f) : 1.0f; + } + // Keep the native model in an explicit RGBA order. D3D11's endpoint is + // BGRA8, so the boundary performs the channel swizzle exactly once. + const NvCVImage_PixelFormat pixelFormat = NVCV_RGBA; + impl->nativePixelFormat = pixelFormat; + impl->outputWidth = outputDesc.Width; + impl->outputHeight = outputDesc.Height; + Logger::Get().Info(fmt::format( + "RTX Video endpoint channel order: {}", + pixelFormat == NVCV_BGRA ? "BGRA" : "RGBA")); if (!VFXSucceeded(NvVFX_CudaStreamCreate(&impl->stream), "NvVFX_CudaStreamCreate") || !VFXSucceeded(NvCVImage_InitFromD3D11Texture(&impl->inputD3D, input), "NvCVImage_InitFromD3D11Texture(input)") || !VFXSucceeded(NvCVImage_InitFromD3D11Texture(&impl->outputD3D, output), "NvCVImage_InitFromD3D11Texture(output)") || !VFXSucceeded(NvCVImage_Alloc(&impl->inputGPU, inputDesc.Width, inputDesc.Height, - NVCV_RGBA, NVCV_U8, NVCV_INTERLEAVED, NVCV_GPU, 32), "NvCVImage_Alloc(input)") || + pixelFormat, NVCV_U8, NVCV_INTERLEAVED, NVCV_GPU, 32), "NvCVImage_Alloc(input)") || !VFXSucceeded(NvCVImage_Alloc(&impl->outputGPU, outputDesc.Width, outputDesc.Height, - NVCV_RGBA, NVCV_U8, NVCV_INTERLEAVED, NVCV_GPU, 32), "NvCVImage_Alloc(output)")) { + pixelFormat, NVCV_U8, NVCV_INTERLEAVED, NVCV_GPU, 32), "NvCVImage_Alloc(output)")) { return false; } + Logger::Get().Info(fmt::format( + "RTX Video NvCV formats: inputD3D={} inputGPU={} outputD3D={} outputGPU={} " + "types={}/{}/{}/{} layouts={}/{}/{}/{}", + static_cast(impl->inputD3D.pixelFormat), + static_cast(impl->inputGPU.pixelFormat), + static_cast(impl->outputD3D.pixelFormat), + static_cast(impl->outputGPU.pixelFormat), + static_cast(impl->inputD3D.componentType), + static_cast(impl->inputGPU.componentType), + static_cast(impl->outputD3D.componentType), + static_cast(impl->outputGPU.componentType), + static_cast(impl->inputD3D.planar), + static_cast(impl->inputGPU.planar), + static_cast(impl->outputD3D.planar), + static_cast(impl->outputGPU.planar))); const NvCV_Status createEffectStatus = NvVFX_CreateEffect("VideoSuperRes", &impl->effect); @@ -151,8 +300,8 @@ bool RTXVideoDenoiser::Initialize( } Logger::Get().Info(fmt::format( - "RTX Video denoise initialized: quality={}, {}x{}, inputFormat={}, outputFormat={}, scales={}/{}", - qualityLevel, inputDesc.Width, inputDesc.Height, + "RTX Video {} initialized: quality={}, {}x{}, inputFormat={}, outputFormat={}, scales={}/{} staging=CPU-U8", + isVsr ? "VSR" : "Denoise", qualityLevel, inputDesc.Width, inputDesc.Height, (int)inputDesc.Format, (int)outputDesc.Format, impl->inputScale, impl->outputScale)); _impl = std::move(impl); return true; @@ -163,7 +312,7 @@ bool RTXVideoDenoiser::Resize( ID3D11Texture2D* input, ID3D11Texture2D* output ) noexcept { - return Initialize(deviceResources, input, output, _qualityLevel); + return Initialize(deviceResources, input, output, _qualityLevel, _kind); } bool RTXVideoDenoiser::Draw(const NativeEffectDrawContext& drawContext) noexcept { @@ -175,15 +324,11 @@ bool RTXVideoDenoiser::Draw(const NativeEffectDrawContext& drawContext) noexcept // Resize should have recreated the wrappers whenever these resources change. (void)input; - (void)output; + D3D11_TEXTURE2D_DESC outputDesc{}; + output->GetDesc(&outputDesc); bool inputMapped = false; - bool outputMapped = false; auto unmapResources = [&]() noexcept { - if (outputMapped) { - NvCVImage_UnmapResource(&_impl->outputD3D, _impl->stream); - outputMapped = false; - } if (inputMapped) { NvCVImage_UnmapResource(&_impl->inputD3D, _impl->stream); inputMapped = false; @@ -211,24 +356,91 @@ bool RTXVideoDenoiser::Draw(const NativeEffectDrawContext& drawContext) noexcept return false; } - if (!VFXSucceeded(NvCVImage_MapResource(&_impl->outputD3D, _impl->stream), - "NvCVImage_MapResource(output)")) { + // HDR compatibility owns the explicit CPU-U8 bridge below. With HDR off, + // preserve the original native D3D11/CUDA interop path byte-for-byte in + // behavior: the effect writes directly back to its D3D11 endpoint. + if (!_hdrBoundary.hdrEnabled) { + bool outputMapped = false; + if (!VFXSucceeded(NvCVImage_MapResource(&_impl->outputD3D, _impl->stream), + "NvCVImage_MapResource(output)")) { + return false; + } + outputMapped = true; + if (!VFXSucceeded(NvCVImage_Transfer(&_impl->outputGPU, &_impl->outputD3D, + _impl->outputScale, _impl->stream, &_impl->temporaryGPU), + "NvCVImage_Transfer(output)")) { + NvCVImage_UnmapResource(&_impl->outputD3D, _impl->stream); + return false; + } + if (!VFXSucceeded(NvCVImage_UnmapResource(&_impl->outputD3D, _impl->stream), + "NvCVImage_UnmapResource(output)")) { + outputMapped = false; + return false; + } + outputMapped = false; + if (!VFXSucceeded(_impl->synchronize(_impl->stream), + "NvVFX_CudaStreamSynchronize")) { + return false; + } + ++_impl->drawCount; + if (_impl->drawCount <= 2) { + Logger::Get().Info(fmt::format( + "RTX Video native Draw succeeded: mode=SDR-direct frame={} output={}x{} format={}", + _impl->drawCount, outputDesc.Width, outputDesc.Height, + static_cast(outputDesc.Format))); + } + return true; + } + + // The SDK accepts the D3D11 input interop path, while this runtime rejects + // direct GPU-U8 -> D3D11-U8 output transfer with NVCV_ERR_PIXELFORMAT. + // Transfer the native GPU result to a CPU U8 image, then upload the exact + // interleaved bytes into the effect-local D3D11 endpoint. + if (!_impl->outputCPU.pixels && !VFXSucceeded(NvCVImage_Alloc( + &_impl->outputCPU, _impl->outputWidth, _impl->outputHeight, + _impl->nativePixelFormat, NVCV_U8, NVCV_INTERLEAVED, NVCV_CPU, 32), + "NvCVImage_Alloc(outputCPU)")) { return false; } - outputMapped = true; - if (!VFXSucceeded(NvCVImage_Transfer(&_impl->outputGPU, &_impl->outputD3D, - _impl->outputScale, _impl->stream, &_impl->temporaryGPU), "NvCVImage_Transfer(output)")) { + if (!VFXSucceeded(NvCVImage_Transfer(&_impl->outputGPU, &_impl->outputCPU, + _impl->outputScale, _impl->stream, &_impl->temporaryGPU), "NvCVImage_Transfer(outputCPU)")) { unmapResources(); return false; } - if (!VFXSucceeded(NvCVImage_UnmapResource(&_impl->outputD3D, _impl->stream), - "NvCVImage_UnmapResource(output)")) { - outputMapped = false; + if (!VFXSucceeded(_impl->synchronize(_impl->stream), "NvVFX_CudaStreamSynchronize(outputCPU)")) { return false; } - outputMapped = false; - - return VFXSucceeded(_impl->synchronize(_impl->stream), "NvVFX_CudaStreamSynchronize"); + if (!_impl->d3dContext || !_impl->outputCPU.pixels || _impl->outputCPU.pitch <= 0) { + Logger::Get().Error("RTX Video CPU output staging is unavailable"); + return false; + } + std::vector bgra( + size_t(_impl->outputCPU.pitch) * outputDesc.Height); + const auto* rgba = static_cast(_impl->outputCPU.pixels); + for (UINT y = 0; y < outputDesc.Height; ++y) { + const auto* src = rgba + size_t(y) * _impl->outputCPU.pitch; + auto* dst = bgra.data() + size_t(y) * _impl->outputCPU.pitch; + for (UINT x = 0; x < outputDesc.Width; ++x) { + dst[x * 4 + 0] = src[x * 4 + 2]; + dst[x * 4 + 1] = src[x * 4 + 1]; + dst[x * 4 + 2] = src[x * 4 + 0]; + dst[x * 4 + 3] = src[x * 4 + 3]; + } + } + _impl->d3dContext->UpdateSubresource( + output, 0, nullptr, bgra.data(), + static_cast(_impl->outputCPU.pitch), 0); + { + ++_impl->drawCount; + if (_impl->drawCount <= 2) { + Logger::Get().Info(fmt::format( + "RTX Video native Draw succeeded: frame={} outputProtocol=RGBA/BGRA U8 [0,255] " + "canonicalScale={}/{} output={}x{} format={}", + _impl->drawCount, _impl->inputScale, _impl->outputScale, + outputDesc.Width, outputDesc.Height, static_cast(outputDesc.Format))); + } + } + return true; } } @@ -243,7 +455,7 @@ RTXVideoDenoiser::RTXVideoDenoiser() = default; RTXVideoDenoiser::~RTXVideoDenoiser() = default; bool RTXVideoDenoiser::Initialize( - DeviceResources&, ID3D11Texture2D*, ID3D11Texture2D*, uint32_t) noexcept { + DeviceResources&, ID3D11Texture2D*, ID3D11Texture2D*, uint32_t, RtxVideoEffectKind) noexcept { Logger::Get().Error("RTX Video denoise is disabled at build time"); return false; } diff --git a/src/Magpie.Core/RTXVideoDenoiser.h b/src/Magpie.Core/RTXVideoDenoiser.h index e1469a345..3e0a8bdb4 100644 --- a/src/Magpie.Core/RTXVideoDenoiser.h +++ b/src/Magpie.Core/RTXVideoDenoiser.h @@ -6,6 +6,8 @@ namespace Magpie { class DeviceResources; +enum class RtxVideoEffectKind : uint8_t { Vsr, Denoise }; + // NVIDIA VideoSuperRes modes 8-11 perform same-resolution denoising. The // native backend uses D3D11/CUDA interop, so no frame is copied through CPU. class RTXVideoDenoiser final : public NativeEffectBackend { @@ -19,7 +21,8 @@ class RTXVideoDenoiser final : public NativeEffectBackend { DeviceResources& deviceResources, ID3D11Texture2D* input, ID3D11Texture2D* output, - uint32_t qualityLevel + uint32_t qualityLevel, + RtxVideoEffectKind kind ) noexcept; bool Resize( @@ -35,6 +38,7 @@ class RTXVideoDenoiser final : public NativeEffectBackend { struct Impl; std::unique_ptr _impl; uint32_t _qualityLevel = 8; + RtxVideoEffectKind _kind = RtxVideoEffectKind::Vsr; ScalingError _initializationError = ScalingError::NoError; }; diff --git a/src/Magpie.Core/Renderer.cpp b/src/Magpie.Core/Renderer.cpp index 211d3e380..f91d42520 100644 --- a/src/Magpie.Core/Renderer.cpp +++ b/src/Magpie.Core/Renderer.cpp @@ -9,7 +9,11 @@ #include "DirectXHelper.h" #include "DwmSharedSurfaceFrameSource.h" #include "EffectCompiler.h" +#include "EffectHelper.h" #include "EffectDrawer.h" +#include "GroupAHdrRoutes.h" +#include "GroupBHdrRoutes.h" +#include "EffectProtocolCatalogC.h" #include "EffectParameterValue.h" #include "EffectParameterRestart.h" #include "EffectsProfiler.h" @@ -25,6 +29,9 @@ #include "TextureHelper.h" #include "Win32Helper.h" #include "DLSSFrameGenerator.h" +#include "DLSSSRUpscaler.h" +#include "FSR2Upscaler.h" +#include "FSR3Upscaler.h" #include "NativeEffectBackend.h" #include "NativeEffectBackendFactory.h" #include "NvidiaOpticalFlowProvider.h" @@ -106,6 +113,80 @@ static bool IsFrameGenerationEffect(std::string_view name) noexcept { return IsDLSSFrameGenerationEffect(name) || IsXeSSFrameGenerationEffect(name); } +static HdrFormatRoutes GetHdrRoutesForEffect( + const EffectOption& effect, + bool hdrEnabled +) noexcept { + // Route selection is a HDR-only contract. Keep this guard local so a + // caller cannot accidentally turn saved HDR parameters into a live route + // while the profile option is disabled. + if (!hdrEnabled) { + return {}; + } + const size_t separator = effect.name.find('\\'); + const std::string_view group = separator == std::string::npos + ? std::string_view(effect.name) + : std::string_view(effect.name).substr(0, separator); + int casFormatOption = 0; + if (group == "Anime4K" || group == "CAS" || group == "CRT" || + group == "CuNNy" || group == "CuNNy2" || group == "Diagnostics" || + group == "FSRCNNX" || group == "FXAA" || group == "MLAA") { + if (const auto it = effect.parameters.find("hdrFormat"); it != effect.parameters.end()) { + casFormatOption = static_cast(std::lround(it->second)); + } + return GetGroupAHdrRoutes(group, casFormatOption); + } + if (group == "DLSSNR") { + const auto path = effect.parameters.find("experimentalHdrPath"); + const auto scale = effect.parameters.find("experimentalHdrScale"); + return GetGroupBHdrRoutes(group, + path != effect.parameters.end() && path->second >= 0.5f, + scale != effect.parameters.end() ? scale->second : 1.0f); + } + if (group == "DLSS" || group == "FSR" || group == "FSR2" || + group == "FSR3" || group == "FSR4" || group == "NIS") { + return GetGroupBHdrRoutes(group); + } + if (group == "RTXVideo") { + return effect.name.find("_VSR_") != std::string::npos + ? EffectProtocolC::RTXVideoVsr() + : EffectProtocolC::RTXVideoDenoiser(); + } + if (group == "DLSSFG" || group == "FSR3FG") { + return EffectProtocolC::GetGroupCHdrRoutes(group); + } + return EffectProtocolC::GetGroupCHdrRoutes(group); +} + +static void ConfigureHdrBackendProtocol( + std::string_view effectName, + NativeEffectBackend& backend, + const HdrFrameMetadata& metadata, + bool hdrEnabled +) noexcept { + if (!hdrEnabled || !metadata.IsValid()) return; + // Capture normalization has already applied source pre-exposure into the + // canonical FP16 surface. Native SR backends therefore consume linear HDR + // values with a neutral exposure contract for this frame. + const FsrHdrProtocol protocol{ + .hdrColorInput = true, + .transfer = GroupBTransfer::Linear, + .preExposure = 1.0f, + .exposure = 1.0f, + .depthInverted = true, + .depthInfinite = true, + .useReactiveMask = true, + .useTransparencyMask = true, + }; + if (effectName == "DLSS\\DLSS_SR") { + static_cast(backend).SetDlssHdrProtocol(protocol); + } else if (effectName == "FSR2\\FSR2_SR") { + static_cast(backend).SetFsrHdrProtocol(protocol); + } else if (effectName == "FSR3\\FSR3_SR" || effectName == "FSR4\\FSR4_SR") { + static_cast(backend).SetFsrHdrProtocol(protocol); + } +} + static MotionVectorRequest GetMotionVectorRequest( const FrameGuidanceRequirements& requirements ) noexcept { @@ -495,6 +576,8 @@ bool Renderer::_OpenFrontendSharedTextures() noexcept { _frontendMotionReset = true; _frontendBaseValid = false; _frontendPresentedBaseValid = false; + _frontendFrameMetadata = {}; + _frontendPresentedFrameMetadata = {}; _frontendBaseNeedsPresent = false; for (uint32_t i = 0; i < MAX_SHARED_TEXTURE_SLOTS; ++i) { _frontendSharedTextureMutexes[i] = nullptr; @@ -603,6 +686,25 @@ bool Renderer::_UpdateFrontendBase(uint32_t sharedTextureSlot) noexcept { if (FAILED(hr)) { return false; } + const uint64_t slotSequence = + _sharedTextureCaptureSequences[sharedTextureSlot].load(std::memory_order_acquire); + const uint64_t activeSequence = + _activeCaptureSequence.load(std::memory_order_acquire); + const uint64_t slotGeneration = _sharedTextureResourceGenerations[sharedTextureSlot].load( + std::memory_order_acquire); + const uint64_t activeGeneration = _activeResourceGeneration.load(std::memory_order_acquire); + if ((slotSequence != 0 && activeSequence != 0 && slotSequence != activeSequence) || + (slotGeneration != 0 && activeGeneration != 0 && slotGeneration != activeGeneration)) { + const HRESULT staleRelease = ReleasePresentationTextures(mutexes, releaseKey); + if (SUCCEEDED(staleRelease)) { + _sharedTextureMutexKeys[sharedTextureSlot].store(releaseKey, std::memory_order_release); + _lastAccessMutexKeys[sharedTextureSlot] = releaseKey; + } + Logger::Get().Info(fmt::format( + "Dropped stale frontend slot={} sequence={}/{} resourceGeneration={}/{}", + sharedTextureSlot, slotSequence, activeSequence, slotGeneration, activeGeneration)); + return false; + } D3D11_TEXTURE2D_DESC sourceDesc{}; source->GetDesc(&sourceDesc); @@ -632,7 +734,8 @@ bool Renderer::_UpdateFrontendBase(uint32_t sharedTextureSlot) noexcept { } if (SUCCEEDED(hr)) { _frontendResources.GetD3DDC()->CopyResource(_frontendBaseTexture.get(), source); - _frontendCaptureFrameId = _sharedMotionFrameIds[sharedTextureSlot].load(std::memory_order_acquire); + _frontendCaptureFrameId = _sharedTextureFrameIds[sharedTextureSlot].load(std::memory_order_acquire); + _frontendFrameMetadata = _sharedFrameMetadata[sharedTextureSlot]; FrameTrace::SetFrame(_frontendCaptureFrameId); traceBase.FrameId(_frontendCaptureFrameId); if (!_passThroughFrames.Consume(sharedTextureSlot) && _isPassThroughActive) { @@ -881,6 +984,9 @@ bool Renderer::_SubmitFrontendFrame() noexcept { d3dDC->CopyResource( _frontendPresentedBaseTexture.get(), _frontendBaseTexture.get()); _frontendPresentedBaseValid = true; + _frontendPresentedFrameMetadata = _frontendFrameMetadata; + _frontendPresentedFrameMetadata = _frontendFrameMetadata; + _frontendPresentedFrameMetadata.stage = HdrFrameStage::PresentedOutput; _passThroughFrames.OnPresented(); } if (!stableBaseOnly) _frontendBaseNeedsPresent = false; @@ -1402,10 +1508,16 @@ bool Renderer::_InitFrameSource() noexcept { _backendInitError = ScalingError::CaptureFailed; return false; } + if (ScalingWindow::Get().Options().IsHdrCompatibilityEnabled() && + !_hdrPresentationAdapter.Initialize(_backendResources, _backendDescriptorStore)) { + Logger::Get().Error("初始化 HDR 发布适配器失败"); + _backendInitError = ScalingError::GraphicsDeviceInitFailed; + return false; + } // 由于 DPI 缩放,捕获尺寸和边界矩形尺寸不一定相同 D3D11_TEXTURE2D_DESC desc; - _frameSource->GetOutput()->GetDesc(&desc); + _frameSource->GetPipelineTexture()->GetDesc(&desc); Logger::Get().Info(fmt::format("捕获尺寸: {}x{}", desc.Width, desc.Height)); return true; @@ -1414,7 +1526,9 @@ bool Renderer::_InitFrameSource() noexcept { static std::optional CompileEffect( const EffectOption& effectOption, bool noFP16, - bool forceInlineParams = false + bool forceInlineParams = false, + DXGI_FORMAT routeInputFormat = DXGI_FORMAT_UNKNOWN, + DXGI_FORMAT routeOutputFormat = DXGI_FORMAT_UNKNOWN ) noexcept { // 指定效果名 EffectDesc result{ .name = effectOption.name }; @@ -1436,6 +1550,19 @@ static std::optional CompileEffect( if (noFP16) { compileFlag |= EffectCompilerFlags::NoFP16; } + if (scalingOptions.IsHdrCompatibilityEnabled()) { + compileFlag |= EffectCompilerFlags::HdrCompatibility; + } + const auto encodeFormat = [](DXGI_FORMAT format, uint32_t shift) { + if (format == DXGI_FORMAT_UNKNOWN) return uint32_t(0); + for (uint32_t i = 0; i < std::size(EffectHelper::FORMAT_DESCS) - 1; ++i) { + if (EffectHelper::FORMAT_DESCS[i].dxgiFormat == format) + return ((i + 1) & EffectCompilerFlags::SurfaceFormatMask) << shift; + } + return uint32_t(0); + }; + compileFlag |= encodeFormat(routeInputFormat, EffectCompilerFlags::InputFormatShift); + compileFlag |= encodeFormat(routeOutputFormat, EffectCompilerFlags::OutputFormatShift); bool success = true; uint32_t duration = Measure([&]() { @@ -1470,7 +1597,18 @@ ID3D11Texture2D* Renderer::_BuildEffects() noexcept { int duration = Measure([&]() { Win32Helper::RunParallel([&](uint32_t id) { - std::optional desc = CompileEffect(effects[id], noFP16); + DXGI_FORMAT routeInput = DXGI_FORMAT_UNKNOWN; + DXGI_FORMAT routeOutput = DXGI_FORMAT_UNKNOWN; + if (options.IsHdrCompatibilityEnabled()) { + const HdrFormatRoutes routes = GetHdrRoutesForEffect( + effects[id], options.IsHdrCompatibilityEnabled()); + if (!routes.empty() && routes.front().inputFormat != DXGI_FORMAT_UNKNOWN) { + routeInput = routes.front().inputFormat; + routeOutput = routes.front().outputFormat; + } + } + std::optional desc = CompileEffect( + effects[id], noFP16, false, routeInput, routeOutput); auto lk = writeLock.lock_exclusive(); if (desc) { @@ -1499,8 +1637,28 @@ ID3D11Texture2D* Renderer::_BuildEffects() noexcept { _dlssFgRecoveryAttempts = 0; std::optional dlssFrameGenerationSettings; - ID3D11Texture2D* inOutTexture = _frameSource->GetOutput(); + ID3D11Texture2D* inOutTexture = _frameSource->GetPipelineTexture(); + if (!_frameSource->PrepareHdrOutputForResize()) { + Logger::Get().Error("准备 HDR 输出尺寸失败"); + return nullptr; + } + inOutTexture = _frameSource->GetPipelineTexture(); + HdrFrame initialHdrFrame{}; + if (options.IsHdrCompatibilityEnabled()) { + initialHdrFrame.texture = _frameSource->GetPipelineTexture(); + initialHdrFrame.metadata = _frameSource->GetHdrFrameMetadata(); + initialHdrFrame.workingFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; + } for (uint32_t i = 0; i < effectCount; ++i) { + HdrEffectBoundaryContext initialHdrBoundary{}; + if (options.IsHdrCompatibilityEnabled()) { + const EffectOption& effectOption = effects[i]; + initialHdrBoundary = HdrEffectBoundary::Prepare( + true, initialHdrFrame, + GetHdrRoutesForEffect(effectOption, options.IsHdrCompatibilityEnabled()), + initialHdrFrame.metadata.color); + _effectDrawers[i].SetHdrBoundary(initialHdrBoundary); + } if (!_effectDrawers[i].Initialize( _effectDescs[i], effects[i], @@ -1528,6 +1686,11 @@ ID3D11Texture2D* Renderer::_BuildEffects() noexcept { return nullptr; } _nativeEffectBackends[i] = std::move(nativeBackend.backend); + if (_nativeEffectBackends[i] && options.IsHdrCompatibilityEnabled()) { + _nativeEffectBackends[i]->SetHdrBoundary(std::move(initialHdrBoundary)); + ConfigureHdrBackendProtocol(effects[i].name, *_nativeEffectBackends[i], + initialHdrFrame.metadata, true); + } if (effects[i].name == "DLSSNR\\DLSSNR_AI_Filter" && !_nativeEffectBackends[i] && options.reportErrorDetails) { options.reportErrorDetails(ScalingWindow::Get().SrcTracker().Handle(), @@ -1576,6 +1739,7 @@ ID3D11Texture2D* Renderer::_BuildEffects() noexcept { } _UpdateActiveEffectDescs(); + _UpdateHdrEffectBoundaryContexts(); // 初始化所有效果共用的动态常量缓冲区 for (const EffectDesc& effectDesc : _effectDescs) { @@ -1600,9 +1764,56 @@ ID3D11Texture2D* Renderer::_BuildEffects() noexcept { return inOutTexture; } +void Renderer::_UpdateHdrEffectBoundaryContexts() noexcept { + const ScalingOptions& options = ScalingWindow::Get().Options(); + if (!options.IsHdrCompatibilityEnabled()) { + for (size_t i = 0; i < _effectDrawers.size(); ++i) { + _effectDrawers[i].SetHdrBoundary({}); + if (i < _nativeEffectBackends.size() && _nativeEffectBackends[i]) { + _nativeEffectBackends[i]->SetHdrBoundary({}); + } + } + return; + } + if (!_frameSource) { + return; + } + + HdrFrame inputFrame{}; + inputFrame.texture = _frameSource->GetPipelineTexture(); + inputFrame.metadata = _frameSource->GetHdrFrameMetadata(); + inputFrame.workingFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; + + for (size_t i = 0; i < _effectDrawers.size(); ++i) { + const EffectOption& effectOption = _runtimeEffectOptions[i]; + const HdrFormatRoutes routes = GetHdrRoutesForEffect(effectOption, options.IsHdrCompatibilityEnabled()); + HdrEffectBoundaryContext context = HdrEffectBoundary::Prepare( + true, inputFrame, routes, inputFrame.metadata.color); + if (effectOption.name == "DLSSNR\\DLSSNR_AI_Filter") { + D3D11_TEXTURE2D_DESC sourceDesc{}; + inputFrame.texture->GetDesc(&sourceDesc); + Logger::Get().Info(fmt::format( + "DLSSNR HDR boundary: enabled={} route={} profile={} requiresBounded={} " + "normalizationScale={} sourceFormat={} source={}x{}", + options.IsHdrCompatibilityEnabled(), + context.SelectedRoute() ? context.SelectedRoute()->Id() : "(none)", + ToString(context.plan.profile), context.plan.requiresBoundedMapping, + context.plan.normalizationScale, static_cast(sourceDesc.Format), + sourceDesc.Width, sourceDesc.Height)); + } + _effectDrawers[i].SetHdrBoundary(context); + if (i < _nativeEffectBackends.size() && _nativeEffectBackends[i]) { + _nativeEffectBackends[i]->SetHdrBoundary(std::move(context)); + ConfigureHdrBackendProtocol(_runtimeEffectOptions[i].name, + *_nativeEffectBackends[i], inputFrame.metadata, true); + } + } +} + void Renderer::_BuildEffectParameterRuntimeInfos() noexcept { _effectParameterRuntimeInfos.clear(); _effectParameterRuntimeInfos.resize(_effectDescs.size()); + const bool hdrEnabled = ScalingWindow::Get().Options().IsHdrCompatibilityEnabled(); std::optional lastDlssNr; for (uint32_t i = 0; i < _runtimeEffectOptions.size(); ++i) { if (_runtimeEffectOptions[i].name == "DLSSNR\\DLSSNR_AI_Filter" && @@ -1618,7 +1829,19 @@ void Renderer::_BuildEffectParameterRuntimeInfos() noexcept { for (const EffectParameterDesc& parameter : desc.params) { EffectParameterRuntimeInfo info{ .name = parameter.name }; - if (IsFrameRateFilterEffect(option.name)) { + const bool isHdrOnlyParameter = + (((option.name == "CAS\\CAS" || option.name == "CAS\\CAS_Scaling") && + parameter.name == "hdrFormat") || + (option.name == "DLSSNR\\DLSSNR_AI_Filter" && + (parameter.name == "experimentalHdrPath" || + parameter.name == "experimentalHdrScale"))); + if (!hdrEnabled && isHdrOnlyParameter) { + info.applyMode = EffectParameterApplyMode::Unavailable; + info.restartReason = EffectParameterRestartReason::None; + } else if (isHdrOnlyParameter) { + info.applyMode = EffectParameterApplyMode::RestartRequired; + info.restartReason = EffectParameterRestartReason::ResourceRecreation; + } else if (IsFrameRateFilterEffect(option.name)) { info.applyMode = EffectParameterApplyMode::Live; info.restartReason = EffectParameterRestartReason::None; } else if (IsDLSSFrameGenerationEffect(option.name)) { @@ -1895,10 +2118,15 @@ ID3D11Texture2D* Renderer::_ResizeEffects() noexcept { return nullptr; } - ID3D11Texture2D* inOutTexture = _frameSource->GetOutput(); + ID3D11Texture2D* inOutTexture = _frameSource->GetPipelineTexture(); D3D11_TEXTURE2D_DESC sourceDesc{}; + if (!_frameSource->PrepareHdrOutputForResize()) { + Logger::Get().Error("准备 HDR 输出尺寸失败"); + return nullptr; + } + inOutTexture = _frameSource->GetPipelineTexture(); inOutTexture->GetDesc(&sourceDesc); - const FrameGuidanceRequirements guidanceRequirements = + FrameGuidanceRequirements guidanceRequirements = CollectFrameGuidanceRequirements( _nativeEffectBackends, _dlssFrameGenerator.get(), _xessMotionRequest); if (_frameGuidanceService.IsInitialized()) { @@ -1991,6 +2219,7 @@ ID3D11Texture2D* Renderer::_ResizeEffects() noexcept { _effectsProfiler.SetPassCount(_backendResources.GetD3DDevice(), passCount); } } + _UpdateHdrEffectBoundaryContexts(); if (_dlssFrameGenerator) { const DLSSFrameGenerationSettings settings = @@ -2015,7 +2244,7 @@ bool Renderer::_InitializeDLSSFrameGenerator( _dlssFrameGenerator.reset(); } D3D11_TEXTURE2D_DESC sourceDesc{}; - _frameSource->GetOutput()->GetDesc(&sourceDesc); + _frameSource->GetPipelineTexture()->GetDesc(&sourceDesc); auto frameGenerator = std::make_unique(); if (!frameGenerator->Initialize( _backendResources, _ngxD3D12Core, input, @@ -2169,10 +2398,54 @@ void Renderer::_UpdateDestRect() noexcept { HANDLE Renderer::_CreateSharedTexture(ID3D11Texture2D* effectsOutput) noexcept { D3D11_TEXTURE2D_DESC desc; effectsOutput->GetDesc(&desc); + if (_hdrPresentationTexture) { + _backendDescriptorStore.RemoveCache(_hdrPresentationTexture.get()); + _hdrPresentationTexture = nullptr; + } + if (_dlssFgNormalizedInput) { + _backendDescriptorStore.RemoveCache(_dlssFgNormalizedInput.get()); + _dlssFgNormalizedInput = nullptr; + } + if (_dlssFgCanonicalGenerated) { + _backendDescriptorStore.RemoveCache(_dlssFgCanonicalGenerated.get()); + _dlssFgCanonicalGenerated = nullptr; + } + const bool hdrEnabled = ScalingWindow::Get().Options().IsHdrCompatibilityEnabled(); + const bool xessFgHdrTerminal = hdrEnabled && _isXeSSFrameGenerationActive; + const DXGI_FORMAT presentationFormat = hdrEnabled + ? (xessFgHdrTerminal ? DXGI_FORMAT_R10G10B10A2_UNORM : DXGI_FORMAT_R16G16B16A16_FLOAT) + : DXGI_FORMAT_R8G8B8A8_UNORM; + if (hdrEnabled && desc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT) { + Logger::Get().Error("HDR 发布要求 canonical FP16 输出"); + return NULL; + } SIZE textureSize = { (LONG)desc.Width, (LONG)desc.Height }; + _dlssFgHdrNormalizationScale = 1.0f; + if (hdrEnabled && _dlssFrameGenerator && _frameSource) { + const auto color = _frameSource->GetHdrFrameMetadata().color; + if (color.IsValid() && color.sdrWhiteNits > 80.0f) { + _dlssFgHdrNormalizationScale = 80.0f / color.sdrWhiteNits; + _dlssFgNormalizedInput = DirectXHelper::CreateTexture2D( + _backendResources.GetD3DDevice(), DXGI_FORMAT_R16G16B16A16_FLOAT, + textureSize.cx, textureSize.cy, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS); + _dlssFgCanonicalGenerated = DirectXHelper::CreateTexture2D( + _backendResources.GetD3DDevice(), DXGI_FORMAT_R16G16B16A16_FLOAT, + textureSize.cx, textureSize.cy, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS); + if (!_dlssFgNormalizedInput || !_dlssFgCanonicalGenerated) { + Logger::Get().Error("Create DLSSFG HDR normalization textures failed"); + return NULL; + } + Logger::Get().Info(fmt::format( + "DLSSFG HDR bounded bridge enabled: sdrWhiteNits={:.3f} inputScale={:.6f}", + color.sdrWhiteNits, _dlssFgHdrNormalizationScale)); + } + } _sharedTextureSlotCount = _dlssFrameGenerator ? std::clamp(_dlssFrameGenerator->Multiplier(), 2u, MAX_SHARED_TEXTURE_SLOTS) : 1u; _sharedTextureGeneration.fetch_add(1, std::memory_order_release); + _pendingDLSSFGFrontendFrames.store(0, std::memory_order_release); _nextBackendSharedTextureSlot = 0; _latestSharedTextureSlot.store(0, std::memory_order_relaxed); for (uint32_t i = 0; i < MAX_SHARED_TEXTURE_SLOTS; ++i) { @@ -2187,12 +2460,18 @@ HANDLE Renderer::_CreateSharedTexture(ID3D11Texture2D* effectsOutput) noexcept { _sharedMotionFrameIds[i].store(0, std::memory_order_relaxed); _sharedMotionValid[i].store(false, std::memory_order_relaxed); _sharedMotionReset[i].store(true, std::memory_order_relaxed); + _sharedTextureCaptureSequences[i].store(0, std::memory_order_relaxed); + _sharedTextureFrameIds[i].store(0, std::memory_order_relaxed); + _sharedTextureResourceGenerations[i].store( + _sharedTextureGeneration.load(std::memory_order_relaxed), std::memory_order_relaxed); + _sharedTextureTimestamps[i].store(0, std::memory_order_relaxed); + _sharedFrameMetadata[i] = {}; } for (uint32_t i = 0; i < _sharedTextureSlotCount; ++i) { _backendSharedTextures[i] = DirectXHelper::CreateTexture2D( _backendResources.GetD3DDevice(), - DXGI_FORMAT_R8G8B8A8_UNORM, + presentationFormat, textureSize.cx, textureSize.cy, D3D11_BIND_SHADER_RESOURCE, @@ -2266,8 +2545,21 @@ HANDLE Renderer::_CreateSharedTexture(ID3D11Texture2D* effectsOutput) noexcept { "DLSSFG bounded presentation ring initialized: slots={}", _sharedTextureSlotCount)); } + if (xessFgHdrTerminal) { + _hdrPresentationTexture = DirectXHelper::CreateTexture2D( + _backendResources.GetD3DDevice(), DXGI_FORMAT_R10G10B10A2_UNORM, + textureSize.cx, textureSize.cy, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS); + if (!_hdrPresentationTexture) { + Logger::Get().Error("Create XeSSFG HDR10 presentation texture failed"); + return NULL; + } + } if (!_passThroughFrames.InitializeBackend(_backendResources, - _frameSource->GetOutput(), effectsOutput, _sharedTextureSlotCount)) { + _frameSource->GetPipelineTexture(), effectsOutput, _sharedTextureSlotCount, + ScalingWindow::Get().Options().IsHdrCompatibilityEnabled(), + HdrColorTransform::ForFrame(_frameSource->GetHdrFrameMetadata().color), + _frameSource->GetHdrFrameMetadata())) { const auto& window = ScalingWindow::Get(); if (const auto& report = window.Options().reportErrorDetails) { report(window.SrcTracker().Handle(), ScalingError::PassThroughUnavailable, @@ -2348,7 +2640,7 @@ void Renderer::_BackendThreadProc() noexcept { // Do not replace colour/reference/motion before this input enters FG. auto input = std::move(_pendingFrameGenerationInput); _fgInputClock.Submitted(now); - _CompleteBackendFrame(input.get(), true); + _CompleteBackendFrame(input.get(), true, _captureSequence); if (!_dlssFrameGenerator || !_synchronousFramePresentationEnabled.load(std::memory_order_acquire)) { PostMessage(ScalingWindow::Get().Handle(), CommonSharedConstants::WM_FRONTEND_RENDER, 0, 0); } @@ -2407,7 +2699,7 @@ void Renderer::_BackendThreadProc() noexcept { _forceNextRender = false; _backendMayDeferFG = true; _BackendRender( - _effectDrawers.back().GetOutputTexture(), + _effectDrawers.back().GetExternalOutputTexture(), frameSourceState == FrameSourceState::NewFrame); _backendMayDeferFG = false; // DLSSFG uses synchronous, individually paced presentation so generated @@ -2533,6 +2825,7 @@ HANDLE Renderer::_InitBackend() noexcept { if (!_InitFrameSource()) { return NULL; } + _activeResourceGeneration.store(_frameSource->ResourceGeneration(), std::memory_order_release); { if (_frameSource->WaitType() == FrameSourceWaitType::NoWait) { // 某些捕获方式不会限制捕获帧率,因此将捕获帧率限制为屏幕刷新率 @@ -2558,10 +2851,13 @@ HANDLE Renderer::_InitBackend() noexcept { return NULL; } - const FrameGuidanceRequirements guidanceRequirements = + FrameGuidanceRequirements guidanceRequirements = CollectFrameGuidanceRequirements( _nativeEffectBackends, _dlssFrameGenerator.get(), _xessMotionRequest); _motionConsumers.clear(); + if (ScalingWindow::Get().Options().IsHdrCompatibilityEnabled() && guidanceRequirements.HasMotion()) { + Logger::Get().Info("HDR mode: optical-flow providers receive the canonical frame and perform provider-local format adaptation"); + } for (size_t i = 0; i < _runtimeEffectOptions.size(); ++i) { const auto& effect = _runtimeEffectOptions[i]; const MotionVectorRequest request = IsDLSSFrameGenerationEffect(effect.name) && _dlssFrameGenerator ? @@ -2588,7 +2884,7 @@ HANDLE Renderer::_InitBackend() noexcept { #endif }); if (!_frameGuidanceService.Initialize( - _backendResources, _frameSource->GetOutput(), guidanceRequirements)) { + _backendResources, _frameSource->GetPipelineTexture(), guidanceRequirements)) { const OpticalFlowMethod method = _frameGuidanceService.InitializationFailedMethod(); const OpticalFlowInitializationError error = @@ -2657,6 +2953,9 @@ void Renderer::_BackendRender( const uint64_t sequence = _frameSource->CaptureSequence(); if (sequence != _captureSequence) { _captureSequence = sequence; + _activeCaptureSequence.store(sequence, std::memory_order_release); + _activeResourceGeneration.store( + _frameSource->ResourceGeneration(), std::memory_order_release); _captureCadence.RestartSequence(); _captureCadenceQueueWait = {}; if (_capturedFrameId) { @@ -2673,9 +2972,12 @@ void Renderer::_BackendRender( const auto downstreamWait = std::exchange(_captureCadenceQueueWait, std::chrono::steady_clock::duration::zero()); if (_captureCadence.Observe(captureTime, downstreamWait)) { - if (_frameGuidanceService.IsInitialized()) - _frameGuidanceService.ResetHistory(FrameGuidanceResetReason::LongPause); - if (_dlssFrameGenerator) _dlssFrameGenerator->RequestHistoryReset(); + // A delayed WGC notification is a delivery-time observation, not a + // capture interruption. Resetting DLSSFG history here drops the first + // generated frame after an otherwise valid static-window gap and causes + // visible flashing. Actual interruptions already advance CaptureSequence + // and reset temporal consumers in the branch above. + Logger::Get().Info("Capture cadence gap observed; preserving temporal history until capture sequence changes"); } if (_dlssFrameGenerator) _synchronousPresentInterval = _captureCadence.Interval(_dlssFrameGenerator->Multiplier(), _baseFrameRateLimit); @@ -2689,7 +2991,10 @@ void Renderer::_BackendRender( if (_frameGuidanceService.IsInitialized()) { FrameTrace::Scope traceGuidance(FrameTrace::Event::Guidance); _frameGuidanceService.BeginFrame( - _capturedFrameId, _frameSource->GetOutput(), guidanceRequirements); + _capturedFrameId, _frameSource->GetPipelineTexture(), guidanceRequirements, + _frameSource->CaptureSequence(), _frameSource->ResourceGeneration(), + _frameSource->CaptureTimestamp100ns(), + _frameSource->GetHdrFrameMetadata().color); } } if (_dlssFrameGenerator && isNewCaptureFrame) { @@ -2698,7 +3003,17 @@ void Renderer::_BackendRender( FrameTrace::SetFrame(_capturedFrameId); traceRender.FrameId(_capturedFrameId); - _passThroughFrames.UpdateBackend(_capturedFrameId, isNewCaptureFrame); + if (ScalingWindow::Get().Options().IsHdrCompatibilityEnabled() && isNewCaptureFrame && _capturedFrameId <= 2) { + _LogHdrTextureStats(_frameSource->GetPipelineTexture(), "capture-canonical"); + } + if (ScalingWindow::Get().Options().IsHdrCompatibilityEnabled()) { + HdrFrame captureFrame = _frameSource->GetCanonicalFrame(); + captureFrame.metadata.frameId = _capturedFrameId; + captureFrame.metadata.captureSequence = _captureSequence; + _passThroughFrames.UpdateBackend(captureFrame, isNewCaptureFrame); + } else { + _passThroughFrames.UpdateBackend(_capturedFrameId, isNewCaptureFrame); + } ID3D11DeviceContext4* d3dDC = _backendResources.GetD3DDC(); d3dDC->ClearState(); @@ -2709,10 +3024,26 @@ void Renderer::_BackendRender( } _effectsProfiler.OnBeginEffects(d3dDC); + if (isNewCaptureFrame) { + _UpdateHdrEffectBoundaryContexts(); + } for (uint32_t i = 0; i < _effectDrawers.size(); ++i) { const EffectDrawer& effectDrawer = _effectDrawers[i]; + if (ScalingWindow::Get().Options().IsHdrCompatibilityEnabled()) { + // Rebind every boundary to the actual upstream canonical handoff for + // this frame. Initialization-time pointers become stale after the + // first capture and after any resize/rebuild. + ID3D11Texture2D* upstream = i == 0 + ? _frameSource->GetPipelineTexture() + : _effectDrawers[i - 1].GetExternalOutputTexture(); + _effectDrawers[i].SetHdrInputSource(upstream); + } if (i < _nativeEffectBackends.size() && _nativeEffectBackends[i]) { + if (!effectDrawer.PrepareHdrInput()) { + Logger::Get().Error("准备 native HDR 效果输入失败"); + continue; + } D3D11_TEXTURE2D_DESC inputDesc{}; effectDrawer.GetTexture(0)->GetDesc(&inputDesc); const FrameGuidanceConsumerViews guidance = @@ -2723,6 +3054,10 @@ void Renderer::_BackendRender( const NativeEffectDrawContext drawContext{ .input = effectDrawer.GetTexture(0), .output = effectDrawer.GetOutputTexture(), + .inputMetadata = ScalingWindow::Get().Options().IsHdrCompatibilityEnabled() + ? effectDrawer.GetHdrBoundary().inputFrame.metadata : HdrFrameMetadata{}, + .outputMetadata = ScalingWindow::Get().Options().IsHdrCompatibilityEnabled() + ? effectDrawer.GetHdrBoundary().inputFrame.metadata : HdrFrameMetadata{}, .frameId = _capturedFrameId, .inputRevision = i < _effectInputRevisions.size() ? _effectInputRevisions[i] : 0, @@ -2730,16 +3065,50 @@ void Renderer::_BackendRender( .zeroFrameGuidance = guidance.zero }; FrameTrace::Scope traceNative(FrameTrace::Event::NativeEffect, i); - if (!_nativeEffectBackends[i]->Draw(drawContext)) { - Logger::Get().Error("Draw native effect failed"); + const bool nativeDrawSucceeded = _nativeEffectBackends[i]->Draw(drawContext); + if (ScalingWindow::Get().Options().IsHdrCompatibilityEnabled() && isNewCaptureFrame && _capturedFrameId <= 2) { + _LogHdrTextureStats(drawContext.input, fmt::format("effect-{}-input", i)); + _LogHdrTextureStats(drawContext.output, fmt::format("effect-{}-backend-output", i)); + } + if (!nativeDrawSucceeded) { + const HdrEffectBoundaryContext& boundary = effectDrawer.GetHdrBoundary(); + D3D11_TEXTURE2D_DESC outputDesc{}; + if (effectDrawer.GetOutputTexture()) { + effectDrawer.GetOutputTexture()->GetDesc(&outputDesc); + } + Logger::Get().Error(fmt::format( + "Native effect Draw failed: effect={} route={} profile={} " + "inputFormat={} outputFormat={} fallback=marker-pass", + _runtimeEffectOptions[i].name, + boundary.SelectedRoute() ? boundary.SelectedRoute()->Id() : "(none)", + ToString(boundary.plan.profile), + static_cast(inputDesc.Format), + static_cast(outputDesc.Format))); + // A runtime SDK failure must not publish the cleared route output. + // Execute the production marker pass through the same drawer so the + // chain remains visible at the requested output size. + effectDrawer.Draw(_effectsProfiler); + } else if (!effectDrawer.CompleteHdrOutput()) { + Logger::Get().Error("完成 native HDR 效果输出失败"); } _effectsProfiler.OnEndPass(d3dDC); } else { effectDrawer.Draw(_effectsProfiler); + if (ScalingWindow::Get().Options().IsHdrCompatibilityEnabled() && isNewCaptureFrame && _capturedFrameId <= 2) { + _LogHdrTextureStats(effectDrawer.GetTexture(0), fmt::format("effect-{}-adapter-input", i)); + _LogHdrTextureStats(effectDrawer.GetExternalOutputTexture(), fmt::format("effect-{}-canonical-output", i)); + } } } _effectsProfiler.OnEndEffects(d3dDC); + // Keep the pacing/queue optimization, while avoiding duplicate real-frame + // submissions during a capture gap. FG must advance from a new canonical + // capture or its own generated frame, never from a repeated stale input. + if (!isNewCaptureFrame && (_dlssFrameGenerator || _isXeSSFrameGenerationActive)) { + d3dDC->Flush(); + return; + } if (_frontEdgeSyncEnabled && _dlssFrameGenerator && isNewCaptureFrame && _backendMayDeferFG && _synchronousFramePresentationEnabled.load(std::memory_order_acquire)) { @@ -2747,30 +3116,102 @@ void Renderer::_BackendRender( d3dDC->Flush(); return; } - _CompleteBackendFrame(effectsOutput, isNewCaptureFrame); + _CompleteBackendFrame(effectsOutput, isNewCaptureFrame, _captureSequence); +} + +void Renderer::_LogHdrTextureStats(ID3D11Texture2D* texture, std::string_view label) noexcept { + if (!texture) { + Logger::Get().Warn(fmt::format("HDR texture stats: label={} texture=null", label)); + return; + } + D3D11_TEXTURE2D_DESC sourceDesc{}; + texture->GetDesc(&sourceDesc); + if (sourceDesc.ArraySize != 1 || sourceDesc.MipLevels != 1) return; + D3D11_TEXTURE2D_DESC staging = sourceDesc; + staging.Usage = D3D11_USAGE_STAGING; + staging.BindFlags = 0; + staging.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + staging.MiscFlags = 0; + winrt::com_ptr readback; + if (FAILED(_backendResources.GetD3DDevice()->CreateTexture2D(&staging, nullptr, readback.put()))) { + Logger::Get().Warn(fmt::format("HDR texture stats: label={} staging-create-failed format={}", label, static_cast(sourceDesc.Format))); + return; + } + _backendResources.GetD3DDC()->CopyResource(readback.get(), texture); + _backendResources.GetD3DDC()->Flush(); + D3D11_MAPPED_SUBRESOURCE mapped{}; + if (FAILED(_backendResources.GetD3DDC()->Map(readback.get(), 0, D3D11_MAP_READ, 0, &mapped))) { + Logger::Get().Warn(fmt::format("HDR texture stats: label={} map-failed format={}", label, static_cast(sourceDesc.Format))); + return; + } + double sum[4]{}; float minimum[4]{ FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; + float maximum[4]{ -FLT_MAX, -FLT_MAX, -FLT_MAX, -FLT_MAX }; uint64_t finite = 0, invalid = 0; + for (UINT y = 0; y < sourceDesc.Height; ++y) { + const auto* row = static_cast(mapped.pData) + size_t(y) * mapped.RowPitch; + for (UINT x = 0; x < sourceDesc.Width; ++x) { + float values[4]{}; + if (sourceDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM) { + const auto* p = row + size_t(x) * 4; for (int c = 0; c < 4; ++c) values[c] = p[c] / 255.0f; + } else if (sourceDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT) { + const auto* p = reinterpret_cast(row) + size_t(x) * 4; + for (int c = 0; c < 4; ++c) { const uint16_t h = p[c]; const uint32_t e = (h >> 10) & 31u; const uint32_t f = h & 1023u; const bool s = (h & 0x8000u) != 0; values[c] = e == 0 ? std::ldexp(float(f), -24) : e == 31 ? (f ? NAN : (s ? -INFINITY : INFINITY)) : std::ldexp(float(1024 + f), int(e) - 25) * (s ? -1.0f : 1.0f); } + } else { continue; } + for (int c = 0; c < 4; ++c) { if (std::isfinite(values[c])) { sum[c] += values[c]; minimum[c] = (std::min)(minimum[c], values[c]); maximum[c] = (std::max)(maximum[c], values[c]); ++finite; } else ++invalid; } + } + } + _backendResources.GetD3DDC()->Unmap(readback.get(), 0); + const double pixels = double(sourceDesc.Width) * double(sourceDesc.Height); + Logger::Get().Info(fmt::format("HDR texture stats: label={} format={} size={}x{} finite={} invalid={} R=[{:.6g},{:.6g},{:.6g}] G=[{:.6g},{:.6g},{:.6g}] B=[{:.6g},{:.6g},{:.6g}] A=[{:.6g},{:.6g},{:.6g}]", label, static_cast(sourceDesc.Format), sourceDesc.Width, sourceDesc.Height, finite, invalid, minimum[0], maximum[0], sum[0] / pixels, minimum[1], maximum[1], sum[1] / pixels, minimum[2], maximum[2], sum[2] / pixels, minimum[3], maximum[3], sum[3] / pixels)); } -void Renderer::_CompleteBackendFrame(ID3D11Texture2D* effectsOutput, bool isNewCaptureFrame) noexcept { +void Renderer::_CompleteBackendFrame( + ID3D11Texture2D* effectsOutput, + bool isNewCaptureFrame, + uint64_t captureSequence +) noexcept { auto* d3dDC = _backendResources.GetD3DDC(); if (_frontEdgeSyncEnabled) _baseFrameRateLimit = _FrontEdgeFrameRate(); if (_dlssFrameGenerator) _synchronousPresentInterval = _captureCadence.Interval(_dlssFrameGenerator->Multiplier(), _baseFrameRateLimit); if (_dlssFrameGenerator && isNewCaptureFrame) { D3D11_TEXTURE2D_DESC sourceDesc{}; - _frameSource->GetOutput()->GetDesc(&sourceDesc); + _frameSource->GetPipelineTexture()->GetDesc(&sourceDesc); const FrameGuidanceConsumerViews guidance = _frameGuidanceService.GetConsumerViews( _capturedFrameId, { sourceDesc.Width, sourceDesc.Height }, GetMotionVectorRequest( _dlssFrameGenerator->GetFrameGuidanceRequirements())); _dlssFgPresentationStopping = false; + ID3D11Texture2D* dlssInput = effectsOutput; + if (_dlssFgNormalizedInput && _dlssFgHdrNormalizationScale != 1.0f) { + if (!_hdrPresentationAdapter.ConvertHdrToBounded( + effectsOutput, _dlssFgNormalizedInput.get(), + HdrColorTransform::ForFrame(_frameSource->GetHdrFrameMetadata().color), + _dlssFgHdrNormalizationScale)) { + Logger::Get().Error("DLSSFG canonical-to-bounded input conversion failed"); + return; + } + dlssInput = _dlssFgNormalizedInput.get(); + } const bool generated = _dlssFrameGenerator->Draw( - effectsOutput, + dlssInput, _capturedFrameId, guidance.produced, guidance.zero, - [this](ID3D11Texture2D* generatedFrame) { - return _PublishBackendTexture(generatedFrame, true, true); + [this, captureSequence](ID3D11Texture2D* generatedFrame) { + ID3D11Texture2D* canonicalGenerated = generatedFrame; + if (_dlssFgCanonicalGenerated && _dlssFgHdrNormalizationScale != 1.0f) { + if (!_hdrPresentationAdapter.ConvertBoundedToHdr( + generatedFrame, _dlssFgCanonicalGenerated.get(), + HdrColorTransform::ForFrame(_frameSource->GetHdrFrameMetadata().color), + _dlssFgHdrNormalizationScale)) { + Logger::Get().Error("DLSSFG bounded-to-canonical output conversion failed"); + return false; + } + canonicalGenerated = _dlssFgCanonicalGenerated.get(); + } + return _PublishBackendTexture(canonicalGenerated, true, true, captureSequence, + _frameSource ? _frameSource->ResourceGeneration() : 0); } ); if (!generated && !_dlssFgPresentationStopping) { @@ -2785,7 +3226,8 @@ void Renderer::_CompleteBackendFrame(ID3D11Texture2D* effectsOutput, bool isNewC const bool synchronous = _dlssFrameGenerator && _synchronousFramePresentationEnabled.load(std::memory_order_acquire); - if (!_PublishBackendTexture(effectsOutput, synchronous, false)) { + if (!_PublishBackendTexture(effectsOutput, synchronous, false, captureSequence, + _frameSource ? _frameSource->ResourceGeneration() : 0)) { return; } @@ -2796,13 +3238,71 @@ void Renderer::_CompleteBackendFrame(ID3D11Texture2D* effectsOutput, bool isNewC bool Renderer::_PublishBackendTexture( ID3D11Texture2D* texture, bool synchronous, - bool generatedFrame + bool generatedFrame, + uint64_t captureSequence, + uint64_t resourceGeneration ) noexcept { + const uint64_t currentGeneration = _frameSource ? _frameSource->ResourceGeneration() : + _sharedTextureGeneration.load(std::memory_order_acquire); + if (captureSequence != 0 && captureSequence != _captureSequence) { + Logger::Get().Warn(fmt::format( + "Dropping stale {} frame: generation={} currentGeneration={}", + generatedFrame ? "generated" : "real", captureSequence, _captureSequence)); + // The SDK submission itself completed; treat the stale publication as + // consumed so a recovery does not disable an otherwise healthy FG path. + return true; + } + if (resourceGeneration != 0 && resourceGeneration != currentGeneration) { + Logger::Get().Warn(fmt::format( + "Dropping stale {} frame: resourceGeneration={} currentResourceGeneration={}", + generatedFrame ? "generated" : "real", resourceGeneration, currentGeneration)); + return true; + } + if (!texture) { + Logger::Get().Error("Dropping frame publication with null texture"); + return false; + } ID3D11DeviceContext4* d3dDC = _backendResources.GetD3DDC(); + ID3D11Texture2D* publicationTexture = texture; + HdrFrameMetadata publicationMetadata{}; + if (ScalingWindow::Get().Options().IsHdrCompatibilityEnabled()) { + publicationMetadata = _frameSource->GetHdrFrameMetadata(); + publicationMetadata.frameId = _capturedFrameId; + publicationMetadata.captureSequence = captureSequence; + publicationMetadata.resourceGeneration = _frameSource->ResourceGeneration(); + publicationMetadata.timestamp100ns = _frameSource->CaptureTimestamp100ns(); + publicationMetadata.stage = generatedFrame + ? HdrFrameStage::GeneratedOutput : HdrFrameStage::CanonicalOutput; + publicationMetadata.generated = generatedFrame; + D3D11_TEXTURE2D_DESC textureDesc{}; + texture->GetDesc(&textureDesc); + if (textureDesc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT) { + Logger::Get().Error("HDR 发布收到非 canonical FP16 纹理"); + return false; + } + if (_isXeSSFrameGenerationActive) { + if (!_hdrPresentationTexture || + !_hdrPresentationAdapter.ConvertCanonicalToHdr10( + texture, _hdrPresentationTexture.get(), + HdrColorTransform::ForFrame(_frameSource->GetHdrFrameMetadata().color))) { + Logger::Get().Error("XeSSFG canonical-to-HDR10 conversion failed"); + return false; + } + publicationTexture = _hdrPresentationTexture.get(); + publicationMetadata.stage = HdrFrameStage::PublishedOutput; + publicationMetadata.color.transfer = HdrTransferFunction::PQ; + publicationMetadata.sourceFormat = DXGI_FORMAT_R10G10B10A2_UNORM; + } + } const bool queuedPresentation = synchronous && _synchronousFramePresentationEnabled.load(std::memory_order_acquire); const uint32_t sharedTextureSlot = _nextBackendSharedTextureSlot++ % _sharedTextureSlotCount; + if (ScalingWindow::Get().Options().IsHdrCompatibilityEnabled() && + _capturedFrameId <= 2) { + _LogHdrTextureStats(publicationTexture, fmt::format( + "publication-source-slot-{}", sharedTextureSlot)); + } bool slotReserved = false; if (queuedPresentation) { const auto ringWaitStart = std::chrono::steady_clock::now(); @@ -2887,7 +3387,22 @@ bool Renderer::_PublishBackendTexture( hr = AcquirePresentationTextures(mutexes, currentKey, 250); } if (SUCCEEDED(hr)) { - d3dDC->CopyResource(_backendSharedTextures[sharedTextureSlot].get(), texture); + HdrFrameMetadata frameMetadata = _frameSource ? + _frameSource->GetHdrFrameMetadata() : HdrFrameMetadata{}; + frameMetadata.frameId = _capturedFrameId; + frameMetadata.captureSequence = captureSequence; + frameMetadata.resourceGeneration = resourceGeneration != 0 ? resourceGeneration : currentGeneration; + frameMetadata.timestamp100ns = _frameSource ? _frameSource->CaptureTimestamp100ns() : 0; + frameMetadata.generated = generatedFrame; + frameMetadata.stage = generatedFrame ? HdrFrameStage::GeneratedOutput : HdrFrameStage::PublishedOutput; + frameMetadata.valid = true; + _sharedFrameMetadata[sharedTextureSlot] = frameMetadata; + d3dDC->CopyResource(_backendSharedTextures[sharedTextureSlot].get(), publicationTexture); + if (ScalingWindow::Get().Options().IsHdrCompatibilityEnabled() && + _capturedFrameId <= 2) { + _LogHdrTextureStats(_backendSharedTextures[sharedTextureSlot].get(), + fmt::format("publication-shared-slot-{}", sharedTextureSlot)); + } _passThroughFrames.Publish(sharedTextureSlot, generatedFrame); if (mutexes[2] && xessMotionValid) { d3dDC->CopyResource(_backendSharedMotionTextures[sharedTextureSlot].get(), xessMotion); @@ -2896,6 +3411,15 @@ bool Renderer::_PublishBackendTexture( _sharedMotionValid[sharedTextureSlot].store(xessMotionValid, std::memory_order_release); _sharedMotionReset[sharedTextureSlot].store(xessMotionReset || !xessMotionValid, std::memory_order_release); + _sharedTextureCaptureSequences[sharedTextureSlot].store( + captureSequence, std::memory_order_release); + _sharedTextureFrameIds[sharedTextureSlot].store( + publicationMetadata.frameId, std::memory_order_release); + _sharedTextureResourceGenerations[sharedTextureSlot].store( + publicationMetadata.resourceGeneration, std::memory_order_release); + _sharedTextureTimestamps[sharedTextureSlot].store( + _frameSource->CaptureTimestamp100ns(), std::memory_order_release); + _sharedFrameMetadata[sharedTextureSlot] = publicationMetadata; hr = ReleasePresentationTextures(mutexes, key); if (SUCCEEDED(hr)) _sharedTextureMutexKeys[sharedTextureSlot].store(key, std::memory_order_release); } diff --git a/src/Magpie.Core/Renderer.h b/src/Magpie.Core/Renderer.h index 9b85adc1b..908359da2 100644 --- a/src/Magpie.Core/Renderer.h +++ b/src/Magpie.Core/Renderer.h @@ -139,7 +139,9 @@ class Renderer { wil::unique_handle _fgInputTimer; winrt::com_ptr _pendingFrameGenerationInput; bool _backendMayDeferFG = false; - void _CompleteBackendFrame(ID3D11Texture2D* effectsOutput, bool isNewCaptureFrame) noexcept; + void _CompleteBackendFrame(ID3D11Texture2D* effectsOutput, bool isNewCaptureFrame, + uint64_t captureSequence) noexcept; + void _LogHdrTextureStats(ID3D11Texture2D* texture, std::string_view label) noexcept; struct PendingFrontendFrame { bool stableBaseOnly = false; bool contentFrame = false; @@ -193,6 +195,8 @@ class Renderer { ID3D11Texture2D* _BuildEffects() noexcept; + void _UpdateHdrEffectBoundaryContexts() noexcept; + void _UpdateActiveEffectDescs() noexcept; bool _ShouldAppendBicubic(ID3D11Texture2D* outTexture) noexcept; @@ -217,7 +221,9 @@ class Renderer { bool _PublishBackendTexture( ID3D11Texture2D* texture, bool synchronous, - bool generatedFrame = false + bool generatedFrame = false, + uint64_t captureSequence = 0, + uint64_t resourceGeneration = 0 ) noexcept; bool _InitializeDLSSFrameGenerator( @@ -309,6 +315,11 @@ class Renderer { std::array, MAX_SHARED_TEXTURE_SLOTS> _backendSharedTextures; + HdrSurfaceAdapter _hdrPresentationAdapter; + winrt::com_ptr _hdrPresentationTexture; + winrt::com_ptr _dlssFgNormalizedInput; + winrt::com_ptr _dlssFgCanonicalGenerated; + float _dlssFgHdrNormalizationScale = 1.0f; std::array, MAX_SHARED_TEXTURE_SLOTS> _backendSharedTextureMutexes; std::array, MAX_SHARED_TEXTURE_SLOTS> @@ -337,6 +348,19 @@ class Renderer { _sharedMotionValid{}; std::array, MAX_SHARED_TEXTURE_SLOTS> _sharedMotionReset{}; + std::array, MAX_SHARED_TEXTURE_SLOTS> + _sharedTextureCaptureSequences{}; + std::array, MAX_SHARED_TEXTURE_SLOTS> + _sharedTextureFrameIds{}; + std::array, MAX_SHARED_TEXTURE_SLOTS> + _sharedTextureResourceGenerations{}; + std::array, MAX_SHARED_TEXTURE_SLOTS> + _sharedTextureTimestamps{}; + std::array _sharedFrameMetadata{}; + HdrFrameMetadata _frontendFrameMetadata{}; + HdrFrameMetadata _frontendPresentedFrameMetadata{}; + std::atomic _activeCaptureSequence = 0; + std::atomic _activeResourceGeneration = 0; std::atomic _latestSharedTextureSlot = 0; std::atomic _sharedTextureGeneration = 0; std::atomic _synchronousFramePresentationEnabled = false; diff --git a/src/Magpie.Core/ScalingOptions.cpp b/src/Magpie.Core/ScalingOptions.cpp index 72e16b399..5826c3f1f 100644 --- a/src/Magpie.Core/ScalingOptions.cpp +++ b/src/Magpie.Core/ScalingOptions.cpp @@ -56,6 +56,7 @@ void ScalingOptions::Log() const noexcept { IsCaptureTitleBar: {} IsAdjustCursorSpeed: {} IsDirectFlipDisabled: {} + IsHdrCompatibilityEnabled: {} cropping: {},{},{},{} graphicsCardId: idx: {} @@ -94,6 +95,7 @@ void ScalingOptions::Log() const noexcept { IsCaptureTitleBar(), IsAdjustCursorSpeed(), IsDirectFlipDisabled(), + IsHdrCompatibilityEnabled(), cropping.Left, cropping.Top, cropping.Right, cropping.Bottom, graphicsCardId.idx, graphicsCardId.vendorId, diff --git a/src/Magpie.Core/Win32Helper.cpp b/src/Magpie.Core/Win32Helper.cpp index 59505d802..13a8bb9b0 100644 --- a/src/Magpie.Core/Win32Helper.cpp +++ b/src/Magpie.Core/Win32Helper.cpp @@ -11,6 +11,7 @@ #pragma pop_macro("ShellExecute") #include #include +#include namespace Magpie { @@ -88,6 +89,42 @@ std::vector GetActiveDisplayTargets() noexcept { return result; } +float QuerySdrWhiteNits(std::wstring_view gdiDeviceName) noexcept { + UINT32 pathCount = 0; + UINT32 modeCount = 0; + if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount) != ERROR_SUCCESS) { + return 0.0f; + } + std::vector paths(pathCount); + std::vector modes(modeCount); + if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathCount, paths.data(), &modeCount, + modes.data(), nullptr) != ERROR_SUCCESS) { + return 0.0f; + } + for (const auto& path : std::span(paths.data(), pathCount)) { + DISPLAYCONFIG_SOURCE_DEVICE_NAME sourceName{}; + sourceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + sourceName.header.size = sizeof(sourceName); + sourceName.header.adapterId = path.sourceInfo.adapterId; + sourceName.header.id = path.sourceInfo.id; + if (DisplayConfigGetDeviceInfo(&sourceName.header) != ERROR_SUCCESS || + CompareStringOrdinal(sourceName.viewGdiDeviceName, -1, + gdiDeviceName.data(), static_cast(gdiDeviceName.size()), TRUE) != CSTR_EQUAL) { + continue; + } + DISPLAYCONFIG_SDR_WHITE_LEVEL white{}; + white.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL; + white.header.size = sizeof(white); + white.header.adapterId = path.targetInfo.adapterId; + white.header.id = path.targetInfo.id; + if (DisplayConfigGetDeviceInfo(&white.header) == ERROR_SUCCESS && white.SDRWhiteLevel > 0) { + const float nits = 80.0f * static_cast(white.SDRWhiteLevel) / 1000.0f; + return std::clamp(nits, 40.0f, 1000.0f); + } + } + return 0.0f; +} + bool EqualDeviceName(std::wstring_view left, std::wstring_view right) noexcept { return CompareStringOrdinal( left.data(), (int)left.size(), @@ -96,6 +133,14 @@ bool EqualDeviceName(std::wstring_view left, std::wstring_view right) noexcept { } +float Win32Helper::GetMonitorSdrWhiteNits(HMONITOR monitor) noexcept { + if (!monitor) return 0.0f; + MONITORINFOEXW info{}; + info.cbSize = sizeof(info); + if (!GetMonitorInfoW(monitor, &info)) return 0.0f; + return QuerySdrWhiteNits(info.szDevice); +} + std::vector Win32Helper::GetDisplayMonitors() noexcept { std::vector result; const std::vector activeTargets = GetActiveDisplayTargets(); diff --git a/src/Magpie.Core/XeSSFGPresenter.cpp b/src/Magpie.Core/XeSSFGPresenter.cpp index 73b00b81b..ea5a339b6 100644 --- a/src/Magpie.Core/XeSSFGPresenter.cpp +++ b/src/Magpie.Core/XeSSFGPresenter.cpp @@ -16,7 +16,22 @@ namespace Magpie { static constexpr uint32_t BUFFER_COUNT = 3; -static constexpr DXGI_FORMAT COLOR_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM; +// XeSS-FG HDR terminal contract: HDR10/BT.2100 packed 10:10:10:2 UNORM. +// The proxy swap-chain, shared color surface, and back buffers all use this +// exact format so the SDK observes one consistent terminal resource format. +static constexpr DXGI_FORMAT HDR_COLOR_FORMAT = DXGI_FORMAT_R10G10B10A2_UNORM; +static constexpr DXGI_FORMAT LDR_COLOR_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM; +// DirectComposition virtual surfaces do not accept the XeSS terminal's +// packed R10 format. Keep the independent UI surface in FP16/scRGB; the +// XeSS proxy swap chain and terminal color resources remain HDR10 R10. +static constexpr DXGI_FORMAT OVERLAY_FORMAT = DXGI_FORMAT_R16G16B16A16_FLOAT; + +static DXGI_FORMAT ColorFormat(bool hdr) noexcept { + return hdr ? HDR_COLOR_FORMAT : LDR_COLOR_FORMAT; +} +static DXGI_FORMAT OverlayFormat(bool hdr) noexcept { + return hdr ? OVERLAY_FORMAT : LDR_COLOR_FORMAT; +} static bool XeFGSucceeded(xefg_swapchain_result_t result) noexcept { return result >= XEFG_SWAPCHAIN_RESULT_SUCCESS; @@ -111,6 +126,7 @@ struct XeSSFGPresenter::Impl { bool externalMotionValid = false; bool externalMotionReset = true; bool resetHistory = true; + bool hdrEnabled = false; std::chrono::steady_clock::time_point lastPresent{}; }; @@ -158,7 +174,7 @@ static bool CreateSharedColor(XeSSFGPresenter::Impl& impl) noexcept { desc.Height = impl.height; desc.MipLevels = 1; desc.ArraySize = 1; - desc.Format = COLOR_FORMAT; + desc.Format = ColorFormat(impl.hdrEnabled); desc.SampleDesc.Count = 1; desc.Usage = D3D11_USAGE_DEFAULT; desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; @@ -389,7 +405,7 @@ bool XeSSFGPresenter::_ResizeOverlaySurface() noexcept { hr = impl.overlayDCompSurface->Resize(impl.width, impl.height); } else { hr = impl.overlayDCompDevice->CreateVirtualSurface( - impl.width, impl.height, COLOR_FORMAT, + impl.width, impl.height, OverlayFormat(impl.hdrEnabled), DXGI_ALPHA_MODE_PREMULTIPLIED, impl.overlayDCompSurface.put()); if (SUCCEEDED(hr)) { hr = impl.overlayDCompVisual->SetContent(impl.overlayDCompSurface.get()); @@ -440,6 +456,7 @@ bool XeSSFGPresenter::_Initialize(HWND hwndAttach) noexcept { impl->width = static_cast(size.cx); impl->height = static_cast(size.cy); impl->externalMotionEnabled = _useExternalMotion; + impl->hdrEnabled = ScalingWindow::Get().Options().IsHdrCompatibilityEnabled(); HRESULT hr = D3D12CreateDevice( _deviceResources->GetGraphicsAdapter(), D3D_FEATURE_LEVEL_11_0, @@ -548,7 +565,7 @@ bool XeSSFGPresenter::_Initialize(HWND hwndAttach) noexcept { DXGI_SWAP_CHAIN_DESC1 swapChainDesc{}; swapChainDesc.Width = impl->width; swapChainDesc.Height = impl->height; - swapChainDesc.Format = COLOR_FORMAT; + swapChainDesc.Format = ColorFormat(impl->hdrEnabled); swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapChainDesc.BufferCount = BUFFER_COUNT; swapChainDesc.SampleDesc.Count = 1; @@ -573,6 +590,15 @@ bool XeSSFGPresenter::_Initialize(HWND hwndAttach) noexcept { LogXeFGResult("get proxy swap chain failed", result); return false; } + if (impl->hdrEnabled) { + HRESULT colorSpaceHr = impl->swapChain->SetColorSpace1( + DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020); + if (FAILED(colorSpaceHr)) { + Logger::Get().ComError("Set XeSSFG HDR10/BT.2100 color space failed", colorSpaceHr); + return false; + } + Logger::Get().Info("XeSSFG endpoint: format=R10G10B10A2_UNORM colorSpace=HDR10/BT.2100"); + } impl->swapChain->SetMaximumFrameLatency(1); impl->frameLatencyWaitableObject.reset( impl->swapChain->GetFrameLatencyWaitableObject()); @@ -974,11 +1000,20 @@ bool XeSSFGPresenter::OnResize() noexcept { const UINT flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT | (_deviceResources->IsTearingSupported() && ScalingWindow::Get().Options().isVRREnabled ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0); HRESULT hr = impl.swapChain->ResizeBuffers( - BUFFER_COUNT, width, height, COLOR_FORMAT, flags); + BUFFER_COUNT, width, height, ColorFormat(impl.hdrEnabled), flags); if (FAILED(hr)) { Logger::Get().ComError("Resize XeSSFG proxy swap chain failed", hr); return false; } + if (impl.hdrEnabled) { + hr = impl.swapChain->SetColorSpace1( + DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020); + if (FAILED(hr)) { + Logger::Get().ComError( + "Restore XeSSFG HDR10/BT.2100 color space after resize failed", hr); + return false; + } + } impl.width = width; impl.height = height; impl.frameLatencyWaitableObject.reset( diff --git a/src/Magpie.Core/XeSSUpscaler.cpp b/src/Magpie.Core/XeSSUpscaler.cpp index b9aed5168..379011e6b 100644 --- a/src/Magpie.Core/XeSSUpscaler.cpp +++ b/src/Magpie.Core/XeSSUpscaler.cpp @@ -3,6 +3,7 @@ #include "DeviceResources.h" #include "DirectXHelper.h" #include "Logger.h" +#include "ScalingWindow.h" #ifdef MP_ENABLE_XESS_ZEROMV #include @@ -41,6 +42,7 @@ struct XeSSUpscaler::Impl { uint32_t outputHeight = 0; uint64_t lastSubmittedValue = 0; bool convertInputToRgba = false; + bool hdrEnabled = false; bool enableOpticalFlow = false; bool resetHistory = true; FrameGuidanceFrameId lastGuidanceResetFrameId = std::numeric_limits::max(); @@ -50,6 +52,20 @@ static constexpr char COLOR_CONVERT_HLSL[] = R"( Texture2D InputColor : register(t0); RWTexture2D OutputColor : register(u0); +[numthreads(8, 8, 1)] +void ConvertToRgba(uint3 tid : SV_DispatchThreadID) { + uint width, height; + OutputColor.GetDimensions(width, height); + if (tid.x >= width || tid.y >= height) return; + float4 color = InputColor.Load(int3(tid.xy, 0)); + // XeSS-SR does not preserve alpha; make the shared color contract opaque. + OutputColor[tid.xy] = float4(color.bgr, 1.0); +} +)"; + +static constexpr char COLOR_CONVERT_LDR_HLSL[] = R"( +Texture2D InputColor : register(t0); +RWTexture2D OutputColor : register(u0); [numthreads(8, 8, 1)] void ConvertToRgba(uint3 tid : SV_DispatchThreadID) { uint width, height; @@ -185,19 +201,44 @@ bool XeSSUpscaler::Initialize( D3D11_TEXTURE2D_DESC outputDesc{}; input->GetDesc(&inputDesc); output->GetDesc(&outputDesc); + const bool hdrEnabled = ScalingWindow::Get().Options().IsHdrCompatibilityEnabled(); if (inputDesc.Width > outputDesc.Width || inputDesc.Height > outputDesc.Height) { Logger::Get().Error(fmt::format( "XeSS Zero-MV only supports upscaling: {}x{} -> {}x{}", inputDesc.Width, inputDesc.Height, outputDesc.Width, outputDesc.Height)); return false; } - const bool supportedInputFormat = inputDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM || - inputDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM; - if (!supportedInputFormat || outputDesc.Format != DXGI_FORMAT_R8G8B8A8_UNORM) { - Logger::Get().Error(fmt::format( - "XeSS Zero-MV unsupported texture formats: input={}, output={}", - (uint32_t)inputDesc.Format, (uint32_t)outputDesc.Format)); - return false; + if (!hdrEnabled) { + const bool supportedInputFormat = + inputDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM || + inputDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM; + if (!supportedInputFormat || outputDesc.Format != DXGI_FORMAT_R8G8B8A8_UNORM) { + Logger::Get().Error(fmt::format( + "XeSS Zero-MV unsupported texture formats: input={}, output={}", + (uint32_t)inputDesc.Format, (uint32_t)outputDesc.Format)); + return false; + } + } else { + const auto isXeSSColorFormat = [](DXGI_FORMAT format) noexcept { + return format == DXGI_FORMAT_R16G16B16A16_FLOAT || + format == DXGI_FORMAT_R11G11B10_FLOAT || + format == DXGI_FORMAT_R10G10B10A2_UNORM || + format == DXGI_FORMAT_R8G8B8A8_UNORM || + format == DXGI_FORMAT_B8G8R8A8_UNORM; + }; + const bool supportedOutputFormat = + outputDesc.Format != DXGI_FORMAT_B8G8R8A8_UNORM && + isXeSSColorFormat(outputDesc.Format); + const bool compatiblePair = inputDesc.Format == outputDesc.Format || + (inputDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM && + outputDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM); + if (!isXeSSColorFormat(inputDesc.Format) || + !supportedOutputFormat || !compatiblePair) { + Logger::Get().Error(fmt::format( + "XeSS Zero-MV unsupported texture formats: input={}, output={}", + (uint32_t)inputDesc.Format, (uint32_t)outputDesc.Format)); + return false; + } } const float scaleX = (float)outputDesc.Width / inputDesc.Width; const float scaleY = (float)outputDesc.Height / inputDesc.Height; @@ -210,7 +251,12 @@ bool XeSSUpscaler::Initialize( impl->inputHeight = inputDesc.Height; impl->outputWidth = outputDesc.Width; impl->outputHeight = outputDesc.Height; + impl->hdrEnabled = hdrEnabled; impl->convertInputToRgba = inputDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM; + const bool hdrColorInput = + impl->hdrEnabled && (inputDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT || + inputDesc.Format == DXGI_FORMAT_R11G11B10_FLOAT || + inputDesc.Format == DXGI_FORMAT_R10G10B10A2_UNORM); HRESULT hr = D3D12CreateDevice(deviceResources.GetGraphicsAdapter(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(impl->device12.put())); @@ -238,7 +284,8 @@ bool XeSSUpscaler::Initialize( } D3D11_TEXTURE2D_DESC xessInputDesc = inputDesc; - xessInputDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + xessInputDesc.Format = impl->convertInputToRgba ? + DXGI_FORMAT_R8G8B8A8_UNORM : inputDesc.Format; xessInputDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS; if (!CreateSharedTexture(*impl, xessInputDesc, impl->sharedInput11, impl->sharedInput12) || !CreateSharedTexture(*impl, outputDesc, impl->sharedOutput11, impl->sharedOutput12)) { @@ -250,7 +297,8 @@ bool XeSSUpscaler::Initialize( impl->sharedInput11.get(), nullptr, impl->sharedInputUav11.put()); winrt::com_ptr shaderBlob; if (SUCCEEDED(hr) && !DirectXHelper::CompileComputeShader( - COLOR_CONVERT_HLSL, "ConvertToRgba", shaderBlob.put(), "XeSSColorConvert")) { + (impl->hdrEnabled ? COLOR_CONVERT_HLSL : COLOR_CONVERT_LDR_HLSL), + "ConvertToRgba", shaderBlob.put(), "XeSSColorConvert")) { hr = E_FAIL; } if (SUCCEEDED(hr)) hr = impl->device11->CreateComputeShader( @@ -420,7 +468,10 @@ bool XeSSUpscaler::Initialize( xess_d3d12_init_params_t initParams{}; initParams.outputResolution = { outputDesc.Width, outputDesc.Height }; initParams.qualitySetting = quality; - initParams.initFlags = XESS_INIT_FLAG_LDR_INPUT_COLOR | XESS_INIT_FLAG_RESPONSIVE_PIXEL_MASK; + initParams.initFlags = XESS_INIT_FLAG_RESPONSIVE_PIXEL_MASK; + if (!hdrColorInput) { + initParams.initFlags |= XESS_INIT_FLAG_LDR_INPUT_COLOR; + } if (!impl->enableOpticalFlow) initParams.initFlags |= XESS_INIT_FLAG_HIGH_RES_MV; if (!XessSucceeded(xessD3D12Init(impl->xessContext, &initParams), "xessD3D12Init") || !XessSucceeded(xessSetVelocityScale(impl->xessContext, 1.0f, 1.0f), "xessSetVelocityScale")) { @@ -430,9 +481,9 @@ bool XeSSUpscaler::Initialize( } Logger::Get().Info(fmt::format( - "XeSS experimental D3D11/D3D12 backend initialized (quality {}, {}, BGRA conversion={}): {}x{} -> {}x{}", + "XeSS experimental D3D11/D3D12 backend initialized (quality {}, {}, BGRA conversion={}, color={}): {}x{} -> {}x{}", (int)quality, impl->enableOpticalFlow ? "Shared optical flow" : "Zero-MV", - impl->convertInputToRgba, + impl->convertInputToRgba, hdrColorInput ? "linear HDR" : "LDR UNORM", inputDesc.Width, inputDesc.Height, outputDesc.Width, outputDesc.Height)); _impl = std::move(impl); return true; diff --git a/src/Magpie.Core/XeSSZeroMVUpscaler.cpp b/src/Magpie.Core/XeSSZeroMVUpscaler.cpp new file mode 100644 index 000000000..a1901c463 --- /dev/null +++ b/src/Magpie.Core/XeSSZeroMVUpscaler.cpp @@ -0,0 +1,605 @@ +#include "pch.h" +#include "XeSSZeroMVUpscaler.h" +#include "DeviceResources.h" +#include "DirectXHelper.h" +#include "HalfResOpticalFlow.h" +#include "Logger.h" + +#ifdef MP_ENABLE_XESS_ZEROMV +#include +#include +#include + +namespace Magpie { + +struct XeSSZeroMVUpscaler::Impl { + ID3D11Device5* device11 = nullptr; + ID3D11DeviceContext4* context11 = nullptr; + winrt::com_ptr device12; + winrt::com_ptr queue12; + winrt::com_ptr allocator12; + winrt::com_ptr commandList12; + winrt::com_ptr sharedInput11; + winrt::com_ptr sharedOutput11; + winrt::com_ptr sharedMotion11; + winrt::com_ptr inputSrv11; + winrt::com_ptr sharedInputUav11; + winrt::com_ptr colorConvertShader11; + winrt::com_ptr sharedInput12; + winrt::com_ptr sharedOutput12; + winrt::com_ptr sharedMotion12; + winrt::com_ptr zeroMotion12; + winrt::com_ptr flatDepth12; + winrt::com_ptr responsiveMask12; + winrt::com_ptr descriptorHeap12; + winrt::com_ptr fence11; + winrt::com_ptr fence12; + xess_context_handle_t xessContext = nullptr; + std::unique_ptr opticalFlow; + uint64_t fenceValue = 0; + uint32_t inputWidth = 0; + uint32_t inputHeight = 0; + uint32_t outputWidth = 0; + uint32_t outputHeight = 0; + uint64_t lastSubmittedValue = 0; + bool convertInputToRgba = false; + bool enableOpticalFlow = false; + bool enableJitter = false; + uint32_t frameIndex = 0; + bool resetHistory = true; +}; + +static constexpr char COLOR_CONVERT_HLSL[] = R"( +Texture2D InputColor : register(t0); +RWTexture2D OutputColor : register(u0); + +[numthreads(8, 8, 1)] +void ConvertToRgba(uint3 tid : SV_DispatchThreadID) { + uint width, height; + OutputColor.GetDimensions(width, height); + if (tid.x >= width || tid.y >= height) return; + OutputColor[tid.xy] = InputColor.Load(int3(tid.xy, 0)); +} +)"; + +static bool XessSucceeded(xess_result_t result, std::string_view operation) noexcept { + if (result == XESS_RESULT_SUCCESS) { + return true; + } + Logger::Get().Error(fmt::format("{} failed (XeSS result {})", operation, (int)result)); + return false; +} + +static bool WaitForD3D12(XeSSZeroMVUpscaler::Impl& impl) noexcept { + const uint64_t value = ++impl.fenceValue; + HRESULT hr = impl.queue12->Signal(impl.fence12.get(), value); + if (FAILED(hr)) { + Logger::Get().ComError("Signal XeSS D3D12 fence failed", hr); + return false; + } + + wil::unique_event_nothrow event; + hr = event.create(); + if (FAILED(hr)) { + Logger::Get().ComError("Create XeSS fence event failed", hr); + return false; + } + hr = impl.fence12->SetEventOnCompletion(value, event.get()); + if (FAILED(hr)) { + Logger::Get().ComError("Set XeSS fence event failed", hr); + return false; + } + WaitForSingleObject(event.get(), INFINITE); + return true; +} + +static bool WaitForFenceValue(XeSSZeroMVUpscaler::Impl& impl, uint64_t value) noexcept { + if (!value || impl.fence12->GetCompletedValue() >= value) return true; + wil::unique_event_nothrow event; + HRESULT hr = event.create(); + if (FAILED(hr)) { + Logger::Get().ComError("Create XeSS completion event failed", hr); + return false; + } + hr = impl.fence12->SetEventOnCompletion(value, event.get()); + if (FAILED(hr)) { + Logger::Get().ComError("Set XeSS completion event failed", hr); + return false; + } + WaitForSingleObject(event.get(), INFINITE); + return true; +} + +static bool CreateSharedTexture( + XeSSZeroMVUpscaler::Impl& impl, + const D3D11_TEXTURE2D_DESC& sourceDesc, + winrt::com_ptr& texture11, + winrt::com_ptr& texture12 +) noexcept { + D3D11_TEXTURE2D_DESC desc = sourceDesc; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.CPUAccessFlags = 0; + desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED | D3D11_RESOURCE_MISC_SHARED_NTHANDLE; + + HRESULT hr = impl.device11->CreateTexture2D(&desc, nullptr, texture11.put()); + if (FAILED(hr)) { + Logger::Get().ComError("Create XeSS shared D3D11 texture failed", hr); + return false; + } + + winrt::com_ptr dxgiResource; + hr = texture11->QueryInterface(IID_PPV_ARGS(dxgiResource.put())); + if (FAILED(hr)) { + Logger::Get().ComError("Query XeSS shared IDXGIResource1 failed", hr); + return false; + } + + HANDLE rawHandle = nullptr; + hr = dxgiResource->CreateSharedHandle(nullptr, GENERIC_ALL, nullptr, &rawHandle); + if (FAILED(hr)) { + Logger::Get().ComError("Create XeSS texture shared handle failed", hr); + return false; + } + wil::unique_handle sharedHandle(rawHandle); + hr = impl.device12->OpenSharedHandle(sharedHandle.get(), IID_PPV_ARGS(texture12.put())); + if (FAILED(hr)) { + Logger::Get().ComError("Open XeSS texture in D3D12 failed", hr); + return false; + } + return true; +} + +static xess_quality_settings_t SelectQuality(float scale) noexcept { + if (scale <= 1.0f) return XESS_QUALITY_SETTING_AA; + if (scale <= 1.3f) return XESS_QUALITY_SETTING_ULTRA_QUALITY; + if (scale <= 1.5f) return XESS_QUALITY_SETTING_QUALITY; + if (scale <= 1.7f) return XESS_QUALITY_SETTING_BALANCED; + if (scale <= 2.0f) return XESS_QUALITY_SETTING_PERFORMANCE; + return XESS_QUALITY_SETTING_ULTRA_PERFORMANCE; +} + +XeSSZeroMVUpscaler::XeSSZeroMVUpscaler() = default; + +XeSSZeroMVUpscaler::~XeSSZeroMVUpscaler() { + if (_impl && _impl->xessContext) { + if (_impl->queue12 && _impl->fence12) { + WaitForD3D12(*_impl); + } + xessDestroyContext(_impl->xessContext); + _impl->xessContext = nullptr; + } +} + +bool XeSSZeroMVUpscaler::Initialize( + DeviceResources& deviceResources, + ID3D11Texture2D* input, + ID3D11Texture2D* output, + bool enableOpticalFlow, + bool enableJitter +) noexcept { + _enableOpticalFlow = enableOpticalFlow; + _enableJitter = enableJitter; + _impl.reset(); + auto impl = std::make_unique(); + impl->enableOpticalFlow = enableOpticalFlow; + impl->enableJitter = enableJitter; + impl->device11 = deviceResources.GetD3DDevice(); + impl->context11 = deviceResources.GetD3DDC(); + + D3D11_TEXTURE2D_DESC inputDesc{}; + D3D11_TEXTURE2D_DESC outputDesc{}; + input->GetDesc(&inputDesc); + output->GetDesc(&outputDesc); + if (inputDesc.Width > outputDesc.Width || inputDesc.Height > outputDesc.Height) { + Logger::Get().Error(fmt::format( + "XeSS Zero-MV only supports upscaling: {}x{} -> {}x{}", + inputDesc.Width, inputDesc.Height, outputDesc.Width, outputDesc.Height)); + return false; + } + const bool supportedInputFormat = inputDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM || + inputDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM; + if (!supportedInputFormat || outputDesc.Format != DXGI_FORMAT_R8G8B8A8_UNORM) { + Logger::Get().Error(fmt::format( + "XeSS Zero-MV unsupported texture formats: input={}, output={}", + (uint32_t)inputDesc.Format, (uint32_t)outputDesc.Format)); + return false; + } + const float scaleX = (float)outputDesc.Width / inputDesc.Width; + const float scaleY = (float)outputDesc.Height / inputDesc.Height; + const float scale = (std::max)(scaleX, scaleY); + if (scale > 3.0f) { + Logger::Get().Error("XeSS Zero-MV supports up to a 3x scale"); + return false; + } + impl->inputWidth = inputDesc.Width; + impl->inputHeight = inputDesc.Height; + impl->outputWidth = outputDesc.Width; + impl->outputHeight = outputDesc.Height; + impl->convertInputToRgba = inputDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM; + + HRESULT hr = D3D12CreateDevice(deviceResources.GetGraphicsAdapter(), D3D_FEATURE_LEVEL_11_0, + IID_PPV_ARGS(impl->device12.put())); + if (FAILED(hr)) { + Logger::Get().ComError("Create XeSS D3D12 device failed", hr); + return false; + } + D3D12_FEATURE_DATA_SHADER_MODEL shaderModel{ D3D_SHADER_MODEL_6_4 }; + hr = impl->device12->CheckFeatureSupport(D3D12_FEATURE_SHADER_MODEL, &shaderModel, sizeof(shaderModel)); + if (FAILED(hr) || shaderModel.HighestShaderModel < D3D_SHADER_MODEL_6_4) { + Logger::Get().Error("XeSS cross-vendor path requires Shader Model 6.4 / DP4a support"); + return false; + } + + D3D12_COMMAND_QUEUE_DESC queueDesc{}; + queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + hr = impl->device12->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(impl->queue12.put())); + if (SUCCEEDED(hr)) hr = impl->device12->CreateCommandAllocator( + D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(impl->allocator12.put())); + if (SUCCEEDED(hr)) hr = impl->device12->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, + impl->allocator12.get(), nullptr, IID_PPV_ARGS(impl->commandList12.put())); + if (FAILED(hr)) { + Logger::Get().ComError("Create XeSS D3D12 command objects failed", hr); + return false; + } + + D3D11_TEXTURE2D_DESC xessInputDesc = inputDesc; + xessInputDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + xessInputDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS; + if (!CreateSharedTexture(*impl, xessInputDesc, impl->sharedInput11, impl->sharedInput12) || + !CreateSharedTexture(*impl, outputDesc, impl->sharedOutput11, impl->sharedOutput12)) { + return false; + } + if (impl->convertInputToRgba) { + hr = impl->device11->CreateShaderResourceView(input, nullptr, impl->inputSrv11.put()); + if (SUCCEEDED(hr)) hr = impl->device11->CreateUnorderedAccessView( + impl->sharedInput11.get(), nullptr, impl->sharedInputUav11.put()); + winrt::com_ptr shaderBlob; + if (SUCCEEDED(hr) && !DirectXHelper::CompileComputeShader( + COLOR_CONVERT_HLSL, "ConvertToRgba", shaderBlob.put(), "XeSSColorConvert")) { + hr = E_FAIL; + } + if (SUCCEEDED(hr)) hr = impl->device11->CreateComputeShader( + shaderBlob->GetBufferPointer(), shaderBlob->GetBufferSize(), nullptr, + impl->colorConvertShader11.put()); + if (FAILED(hr)) { + Logger::Get().ComError("Create XeSS BGRA-to-RGBA conversion resources failed", hr); + return false; + } + } + + D3D12_DESCRIPTOR_HEAP_DESC heapDesc{}; + heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + heapDesc.NumDescriptors = 3; + heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + hr = impl->device12->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(impl->descriptorHeap12.put())); + if (FAILED(hr)) { + Logger::Get().ComError("Create XeSS descriptor heap failed", hr); + return false; + } + + D3D12_HEAP_PROPERTIES heapProperties{}; + heapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; + if (impl->enableOpticalFlow) { + impl->opticalFlow = std::make_unique(); + if (!impl->opticalFlow->Initialize(impl->device11, impl->context11, input)) { + Logger::Get().Error("Initialize XeSS 50% optical flow failed"); + return false; + } + D3D11_TEXTURE2D_DESC motionDesc11{}; + motionDesc11.Width = inputDesc.Width; + motionDesc11.Height = inputDesc.Height; + motionDesc11.MipLevels = 1; + motionDesc11.ArraySize = 1; + motionDesc11.Format = DXGI_FORMAT_R16G16_FLOAT; + motionDesc11.SampleDesc.Count = 1; + motionDesc11.BindFlags = D3D11_BIND_SHADER_RESOURCE; + if (!CreateSharedTexture(*impl, motionDesc11, impl->sharedMotion11, impl->sharedMotion12)) { + return false; + } + } else { + D3D12_RESOURCE_DESC motionDesc{}; + motionDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + motionDesc.Width = outputDesc.Width; + motionDesc.Height = outputDesc.Height; + motionDesc.DepthOrArraySize = 1; + motionDesc.MipLevels = 1; + motionDesc.Format = DXGI_FORMAT_R16G16_FLOAT; + motionDesc.SampleDesc.Count = 1; + motionDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + motionDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + hr = impl->device12->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &motionDesc, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS, nullptr, IID_PPV_ARGS(impl->zeroMotion12.put())); + if (FAILED(hr)) { + Logger::Get().ComError("Create XeSS zero motion-vector texture failed", hr); + return false; + } + } + + // Supply a real (but constant) depth resource in both modes. XeSS can omit + // depth with high-resolution motion vectors, but a non-null flat texture + // keeps the colour-only Zero-MV experiment explicit and comparable with the + // DLSS/FSR2 adapters. + D3D12_RESOURCE_DESC depthDesc{}; + depthDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + depthDesc.Width = inputDesc.Width; + depthDesc.Height = inputDesc.Height; + depthDesc.DepthOrArraySize = 1; + depthDesc.MipLevels = 1; + depthDesc.Format = DXGI_FORMAT_R32_FLOAT; + depthDesc.SampleDesc.Count = 1; + depthDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + depthDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + hr = impl->device12->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &depthDesc, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS, nullptr, IID_PPV_ARGS(impl->flatDepth12.put())); + if (FAILED(hr)) { + Logger::Get().ComError("Create XeSS flat depth texture failed", hr); + return false; + } + D3D12_RESOURCE_DESC responsiveMaskDesc = depthDesc; + responsiveMaskDesc.Format = DXGI_FORMAT_R8_UNORM; + hr = impl->device12->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, + &responsiveMaskDesc, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, nullptr, + IID_PPV_ARGS(impl->responsiveMask12.put())); + if (FAILED(hr)) { + Logger::Get().ComError("Create XeSS responsive pixel mask failed", hr); + return false; + } + + const UINT descriptorSize = impl->device12->GetDescriptorHandleIncrementSize( + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + const D3D12_CPU_DESCRIPTOR_HANDLE depthCpu = impl->descriptorHeap12->GetCPUDescriptorHandleForHeapStart(); + const D3D12_GPU_DESCRIPTOR_HANDLE depthGpu = impl->descriptorHeap12->GetGPUDescriptorHandleForHeapStart(); + D3D12_UNORDERED_ACCESS_VIEW_DESC depthUavDesc{}; + depthUavDesc.Format = DXGI_FORMAT_R32_FLOAT; + depthUavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + impl->device12->CreateUnorderedAccessView(impl->flatDepth12.get(), nullptr, &depthUavDesc, depthCpu); + ID3D12DescriptorHeap* heaps[] = { impl->descriptorHeap12.get() }; + impl->commandList12->SetDescriptorHeaps(1, heaps); + const float flatDepthValue[4]{ 1.0f, 1.0f, 1.0f, 1.0f }; + impl->commandList12->ClearUnorderedAccessViewFloat( + depthGpu, depthCpu, impl->flatDepth12.get(), flatDepthValue, 0, nullptr); + D3D12_CPU_DESCRIPTOR_HANDLE responsiveCpu = depthCpu; + responsiveCpu.ptr += descriptorSize; + D3D12_GPU_DESCRIPTOR_HANDLE responsiveGpu = depthGpu; + responsiveGpu.ptr += descriptorSize; + D3D12_UNORDERED_ACCESS_VIEW_DESC responsiveUavDesc{}; + responsiveUavDesc.Format = DXGI_FORMAT_R8_UNORM; + responsiveUavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + impl->device12->CreateUnorderedAccessView( + impl->responsiveMask12.get(), nullptr, &responsiveUavDesc, responsiveCpu); + const float responsiveValue[4]{ 0.5f, 0.5f, 0.5f, 0.5f }; + impl->commandList12->ClearUnorderedAccessViewFloat( + responsiveGpu, responsiveCpu, impl->responsiveMask12.get(), responsiveValue, 0, nullptr); + + D3D12_RESOURCE_BARRIER auxiliaryBarriers[3]{}; + auxiliaryBarriers[0].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + auxiliaryBarriers[0].Transition.pResource = impl->flatDepth12.get(); + auxiliaryBarriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + auxiliaryBarriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + auxiliaryBarriers[0].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + auxiliaryBarriers[1] = auxiliaryBarriers[0]; + auxiliaryBarriers[1].Transition.pResource = impl->responsiveMask12.get(); + UINT auxiliaryBarrierCount = 2; + if (!impl->enableOpticalFlow) { + D3D12_CPU_DESCRIPTOR_HANDLE motionCpu = depthCpu; + motionCpu.ptr += descriptorSize * 2; + D3D12_GPU_DESCRIPTOR_HANDLE motionGpu = depthGpu; + motionGpu.ptr += descriptorSize * 2; + D3D12_UNORDERED_ACCESS_VIEW_DESC motionUavDesc{}; + motionUavDesc.Format = DXGI_FORMAT_R16G16_FLOAT; + motionUavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + impl->device12->CreateUnorderedAccessView( + impl->zeroMotion12.get(), nullptr, &motionUavDesc, motionCpu); + const float zeroMotionValue[4]{}; + impl->commandList12->ClearUnorderedAccessViewFloat( + motionGpu, motionCpu, impl->zeroMotion12.get(), zeroMotionValue, 0, nullptr); + auxiliaryBarriers[2] = auxiliaryBarriers[0]; + auxiliaryBarriers[2].Transition.pResource = impl->zeroMotion12.get(); + auxiliaryBarrierCount = 3; + } + impl->commandList12->ResourceBarrier(auxiliaryBarrierCount, auxiliaryBarriers); + hr = impl->commandList12->Close(); + if (FAILED(hr)) { + Logger::Get().ComError("Close XeSS initialization command list failed", hr); + return false; + } + ID3D12CommandList* commandLists[] = { impl->commandList12.get() }; + impl->queue12->ExecuteCommandLists(1, commandLists); + + hr = impl->device11->CreateFence(0, D3D11_FENCE_FLAG_SHARED, IID_PPV_ARGS(impl->fence11.put())); + if (FAILED(hr)) { + Logger::Get().ComError("Create XeSS shared fence failed", hr); + return false; + } + HANDLE rawFenceHandle = nullptr; + hr = impl->fence11->CreateSharedHandle(nullptr, GENERIC_ALL, nullptr, &rawFenceHandle); + if (FAILED(hr)) { + Logger::Get().ComError("Create XeSS fence shared handle failed", hr); + return false; + } + wil::unique_handle fenceHandle(rawFenceHandle); + hr = impl->device12->OpenSharedHandle(fenceHandle.get(), IID_PPV_ARGS(impl->fence12.put())); + if (FAILED(hr) || !WaitForD3D12(*impl)) { + if (FAILED(hr)) Logger::Get().ComError("Open XeSS fence in D3D12 failed", hr); + return false; + } + + const xess_result_t createResult = xessD3D12CreateContext(impl->device12.get(), &impl->xessContext); + if (!XessSucceeded(createResult, "xessD3D12CreateContext")) return false; + const xess_quality_settings_t quality = SelectQuality(scale); + xess_d3d12_init_params_t initParams{}; + initParams.outputResolution = { outputDesc.Width, outputDesc.Height }; + initParams.qualitySetting = quality; + initParams.initFlags = XESS_INIT_FLAG_LDR_INPUT_COLOR | XESS_INIT_FLAG_RESPONSIVE_PIXEL_MASK; + if (!impl->enableOpticalFlow) initParams.initFlags |= XESS_INIT_FLAG_HIGH_RES_MV; + if (!XessSucceeded(xessD3D12Init(impl->xessContext, &initParams), "xessD3D12Init") || + !XessSucceeded(xessSetVelocityScale(impl->xessContext, 1.0f, 1.0f), "xessSetVelocityScale")) { + xessDestroyContext(impl->xessContext); + impl->xessContext = nullptr; + return false; + } + + Logger::Get().Info(fmt::format( + "XeSS experimental D3D11/D3D12 backend initialized (quality {}, {}, jitter={}, BGRA conversion={}): {}x{} -> {}x{}", + (int)quality, impl->enableOpticalFlow ? "OpticalFlow50" : "Zero-MV", impl->enableJitter, + impl->convertInputToRgba, + inputDesc.Width, inputDesc.Height, outputDesc.Width, outputDesc.Height)); + _impl = std::move(impl); + return true; +} + +bool XeSSZeroMVUpscaler::Resize( + DeviceResources& deviceResources, + ID3D11Texture2D* input, + ID3D11Texture2D* output +) noexcept { + return Initialize(deviceResources, input, output, _enableOpticalFlow, _enableJitter); +} + +static float Halton(uint32_t index, uint32_t base) noexcept { + float result = 0.0f; + float fraction = 1.0f; + while (index) { + fraction /= (float)base; + result += fraction * (float)(index % base); + index /= base; + } + return result; +} + +bool XeSSZeroMVUpscaler::Draw(const NativeEffectDrawContext& drawContext) noexcept { + ID3D11Texture2D* input = drawContext.input; + ID3D11Texture2D* output = drawContext.output; + if (!_impl || !_impl->xessContext) return false; + Impl& impl = *_impl; + // A command allocator cannot be reset while its previous D3D12 submission + // is still executing. Usually the D3D11 consumer has already waited for it. + if (!WaitForFenceValue(impl, impl.lastSubmittedValue)) return false; + if (impl.convertInputToRgba) { + ID3D11ShaderResourceView* inputSrv = impl.inputSrv11.get(); + ID3D11UnorderedAccessView* outputUav = impl.sharedInputUav11.get(); + impl.context11->CSSetShader(impl.colorConvertShader11.get(), nullptr, 0); + impl.context11->CSSetShaderResources(0, 1, &inputSrv); + impl.context11->CSSetUnorderedAccessViews(0, 1, &outputUav, nullptr); + impl.context11->Dispatch((impl.inputWidth + 7) / 8, (impl.inputHeight + 7) / 8, 1); + ID3D11ShaderResourceView* nullSrv = nullptr; + ID3D11UnorderedAccessView* nullUav = nullptr; + impl.context11->CSSetShaderResources(0, 1, &nullSrv); + impl.context11->CSSetUnorderedAccessViews(0, 1, &nullUav, nullptr); + impl.context11->CSSetShader(nullptr, nullptr, 0); + } else { + impl.context11->CopyResource(impl.sharedInput11.get(), input); + } + if (impl.enableOpticalFlow) { + if (!impl.opticalFlow->Estimate(input)) { + Logger::Get().Error("Estimate XeSS 50% optical flow failed"); + return false; + } + impl.context11->CopyResource(impl.sharedMotion11.get(), impl.opticalFlow->GetMotionTexture()); + } + const uint64_t inputReady = ++impl.fenceValue; + HRESULT hr = impl.context11->Signal(impl.fence11.get(), inputReady); + if (FAILED(hr)) { + Logger::Get().ComError("Signal XeSS input-ready fence failed", hr); + return false; + } + impl.context11->Flush(); + hr = impl.queue12->Wait(impl.fence12.get(), inputReady); + if (FAILED(hr)) { + Logger::Get().ComError("Wait for XeSS D3D11 input failed", hr); + return false; + } + + hr = impl.allocator12->Reset(); + if (SUCCEEDED(hr)) hr = impl.commandList12->Reset(impl.allocator12.get(), nullptr); + if (FAILED(hr)) { + Logger::Get().ComError("Reset XeSS command list failed", hr); + return false; + } + D3D12_RESOURCE_BARRIER barriers[3]{}; + barriers[0].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barriers[0].Transition.pResource = impl.sharedInput12.get(); + barriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON; + barriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + barriers[0].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barriers[1].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barriers[1].Transition.pResource = impl.sharedOutput12.get(); + barriers[1].Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON; + barriers[1].Transition.StateAfter = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + barriers[1].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + const UINT barrierCount = impl.enableOpticalFlow ? 3 : 2; + if (impl.enableOpticalFlow) { + barriers[2].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barriers[2].Transition.pResource = impl.sharedMotion12.get(); + barriers[2].Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON; + barriers[2].Transition.StateAfter = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + barriers[2].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + } + impl.commandList12->ResourceBarrier(barrierCount, barriers); + + xess_d3d12_execute_params_t params{}; + params.pColorTexture = impl.sharedInput12.get(); + params.pVelocityTexture = impl.enableOpticalFlow ? impl.sharedMotion12.get() : impl.zeroMotion12.get(); + params.pDepthTexture = impl.flatDepth12.get(); + params.pResponsivePixelMaskTexture = impl.responsiveMask12.get(); + params.pOutputTexture = impl.sharedOutput12.get(); + if (impl.enableJitter) { + // Metadata-only jitter: the source application's projection is unchanged. + const uint32_t sample = (impl.frameIndex++ & 7u) + 1u; + params.jitterOffsetX = Halton(sample, 2) - 0.5f; + params.jitterOffsetY = Halton(sample, 3) - 0.5f; + } else { + params.jitterOffsetX = 0.0f; + params.jitterOffsetY = 0.0f; + } + params.exposureScale = 1.0f; + params.resetHistory = impl.resetHistory ? 1u : 0u; + params.inputWidth = impl.inputWidth; + params.inputHeight = impl.inputHeight; + const xess_result_t result = xessD3D12Execute(impl.xessContext, impl.commandList12.get(), ¶ms); + if (!XessSucceeded(result, "xessD3D12Execute")) return false; + std::swap(barriers[0].Transition.StateBefore, barriers[0].Transition.StateAfter); + std::swap(barriers[1].Transition.StateBefore, barriers[1].Transition.StateAfter); + if (impl.enableOpticalFlow) { + std::swap(barriers[2].Transition.StateBefore, barriers[2].Transition.StateAfter); + } + impl.commandList12->ResourceBarrier(barrierCount, barriers); + hr = impl.commandList12->Close(); + if (FAILED(hr)) { + Logger::Get().ComError("Close XeSS command list failed", hr); + return false; + } + ID3D12CommandList* lists[] = { impl.commandList12.get() }; + impl.queue12->ExecuteCommandLists(1, lists); + const uint64_t outputReady = ++impl.fenceValue; + hr = impl.queue12->Signal(impl.fence12.get(), outputReady); + impl.lastSubmittedValue = outputReady; + if (SUCCEEDED(hr)) hr = impl.context11->Wait(impl.fence11.get(), outputReady); + if (FAILED(hr)) { + Logger::Get().ComError("Synchronize XeSS output failed", hr); + return false; + } + impl.context11->CopyResource(output, impl.sharedOutput11.get()); + impl.resetHistory = false; + return true; +} + +} + +#else + +namespace Magpie { + +struct XeSSZeroMVUpscaler::Impl {}; +XeSSZeroMVUpscaler::XeSSZeroMVUpscaler() = default; +XeSSZeroMVUpscaler::~XeSSZeroMVUpscaler() = default; +bool XeSSZeroMVUpscaler::Initialize(DeviceResources&, ID3D11Texture2D*, ID3D11Texture2D*, bool, bool) noexcept { + Logger::Get().Error("XeSS Zero-MV support is not enabled in this build"); + return false; +} +bool XeSSZeroMVUpscaler::Resize(DeviceResources&, ID3D11Texture2D*, ID3D11Texture2D*) noexcept { return false; } +bool XeSSZeroMVUpscaler::Draw(const NativeEffectDrawContext&) noexcept { return false; } + +} + +#endif diff --git a/src/Magpie.Core/XeSSZeroMVUpscaler.h b/src/Magpie.Core/XeSSZeroMVUpscaler.h new file mode 100644 index 000000000..f62af918a --- /dev/null +++ b/src/Magpie.Core/XeSSZeroMVUpscaler.h @@ -0,0 +1,41 @@ +#pragma once +#include "NativeEffectBackend.h" + +namespace Magpie { + +class DeviceResources; + +// Experimental colour-only XeSS-SR adapter. Magpie renders with D3D11, while +// the cross-vendor XeSS path is D3D12, so resources are shared between APIs. +class XeSSZeroMVUpscaler final : public NativeEffectBackend { +public: + struct Impl; + + XeSSZeroMVUpscaler(); + XeSSZeroMVUpscaler(const XeSSZeroMVUpscaler&) = delete; + XeSSZeroMVUpscaler& operator=(const XeSSZeroMVUpscaler&) = delete; + ~XeSSZeroMVUpscaler() override; + + bool Initialize( + DeviceResources& deviceResources, + ID3D11Texture2D* input, + ID3D11Texture2D* output, + bool enableOpticalFlow = false, + bool enableJitter = false + ) noexcept; + + bool Resize( + DeviceResources& deviceResources, + ID3D11Texture2D* input, + ID3D11Texture2D* output + ) noexcept override; + + bool Draw(const NativeEffectDrawContext& context) noexcept override; + +private: + std::unique_ptr _impl; + bool _enableOpticalFlow = false; + bool _enableJitter = false; +}; + +} diff --git a/src/Magpie.Core/ZeroFrameGuidanceProvider.cpp b/src/Magpie.Core/ZeroFrameGuidanceProvider.cpp index a936a3c1f..082571095 100644 --- a/src/Magpie.Core/ZeroFrameGuidanceProvider.cpp +++ b/src/Magpie.Core/ZeroFrameGuidanceProvider.cpp @@ -3,6 +3,7 @@ #include "DeviceResources.h" #include "DirectXHelper.h" #include "Logger.h" +#include "ScalingWindow.h" namespace Magpie { @@ -73,7 +74,10 @@ bool ZeroFrameGuidanceResources::_CreateTextures( } static constexpr float ZERO[4]{}; - _context->ClearUnorderedAccessViewFloat(depthUav.get(), ZERO); + static constexpr float ONE[4]{ 1.0f, 1.0f, 1.0f, 1.0f }; + const float* depthClear = ScalingWindow::Get().Options().IsHdrCompatibilityEnabled() + ? ONE : ZERO; + _context->ClearUnorderedAccessViewFloat(depthUav.get(), depthClear); _context->ClearUnorderedAccessViewFloat(motionUav.get(), ZERO); _context->ClearUnorderedAccessViewFloat(confidenceUav.get(), ZERO); _depth = std::move(depth); @@ -89,6 +93,9 @@ static FrameGuidanceMetadata MakeZeroMetadata( ) noexcept { return { .frameId = frame.frameId, + .captureSequence = frame.captureSequence, + .resourceGeneration = frame.resourceGeneration, + .timestamp100ns = frame.timestamp100ns, .sourceExtent = frame.sourceExtent, .validRegion = frame.validRegion, .resetReason = resetReason, diff --git a/src/Magpie.Core/include/EffectCompiler.h b/src/Magpie.Core/include/EffectCompiler.h index 6a49880ec..a9c3c5f83 100644 --- a/src/Magpie.Core/include/EffectCompiler.h +++ b/src/Magpie.Core/include/EffectCompiler.h @@ -7,6 +7,12 @@ struct EffectCompilerFlags { // 会影响编译出的字节码的标志放在低 16 位中,这样组织是为了便于缓存 static constexpr uint32_t InlineParams = 1; static constexpr uint32_t NoFP16 = 1 << 1; + // Optional primary-surface format overrides. Values are stored in the + // compile flags so effect-cache entries remain distinct per typed contract. + static constexpr uint32_t InputFormatShift = 2; + static constexpr uint32_t OutputFormatShift = 7; + static constexpr uint32_t SurfaceFormatMask = 0x1Fu; + static constexpr uint32_t HdrCompatibility = 1u << 12; // 只解析输出尺寸和参数,供用户界面使用 static constexpr uint32_t NoCompile = 1 << 16; diff --git a/src/Magpie.Core/include/ScalingOptions.h b/src/Magpie.Core/include/ScalingOptions.h index 964b34c75..11074ffcb 100644 --- a/src/Magpie.Core/include/ScalingOptions.h +++ b/src/Magpie.Core/include/ScalingOptions.h @@ -389,6 +389,7 @@ struct ScalingFlags { static constexpr uint32_t BenchmarkMode = 1 << 20; static constexpr uint32_t DeveloperMode = 1 << 21; static constexpr uint32_t DisableTopmost = 1 << 22; + static constexpr uint32_t EnableHdrCompatibility = 1 << 23; }; struct ScalingOptions { @@ -411,6 +412,7 @@ struct ScalingOptions { DEFINE_FLAG_ACCESSOR(IsCaptureTitleBar, ScalingFlags::CaptureTitleBar, flags) DEFINE_FLAG_ACCESSOR(IsAdjustCursorSpeed, ScalingFlags::AdjustCursorSpeed, flags) DEFINE_FLAG_ACCESSOR(IsDirectFlipDisabled, ScalingFlags::DisableDirectFlip, flags) + DEFINE_FLAG_ACCESSOR(IsHdrCompatibilityEnabled, ScalingFlags::EnableHdrCompatibility, flags) std::vector effects; uint32_t scalingModeIdx = 0; diff --git a/src/Magpie.Core/include/Win32Helper.h b/src/Magpie.Core/include/Win32Helper.h index 1a693e78d..eebdfe5ed 100644 --- a/src/Magpie.Core/include/Win32Helper.h +++ b/src/Magpie.Core/include/Win32Helper.h @@ -19,6 +19,11 @@ struct Win32Helper { // 相比 \\.\DISPLAY1 之类的 GDI 名称,在显示器插拔和顺序变化后更稳定。 static std::vector GetDisplayMonitors() noexcept; + // Returns the Windows HDR SDR-white calibration for a monitor in nits. + // The value is unavailable on older paths or when the display driver does + // not expose the calibration; callers should keep their explicit fallback. + static float GetMonitorSdrWhiteNits(HMONITOR monitor) noexcept; + static SIZE GetSizeOfRect(const RECT& rect) noexcept { return { rect.right - rect.left, rect.bottom - rect.top }; } diff --git a/src/Magpie/AppSettings.cpp b/src/Magpie/AppSettings.cpp index dac9f3bb5..1cebe7c08 100644 --- a/src/Magpie/AppSettings.cpp +++ b/src/Magpie/AppSettings.cpp @@ -123,6 +123,8 @@ static void WriteProfile(rapidjson::PrettyWriter& write writer.Bool(profile.IsAdjustCursorSpeed()); writer.Key("disableDirectFlip"); writer.Bool(profile.IsDirectFlipDisabled()); + writer.Key("enableHdrCompatibility"); + writer.Bool(profile.IsHdrCompatibilityEnabled()); writer.Key("cursorScaling"); writer.Uint((uint32_t)profile.cursorScaling); @@ -1222,6 +1224,7 @@ bool AppSettings::_LoadProfile( } JsonHelper::ReadBoolFlag(profileObj, "adjustCursorSpeed", ScalingFlags::AdjustCursorSpeed, profile.scalingFlags); JsonHelper::ReadBoolFlag(profileObj, "disableDirectFlip", ScalingFlags::DisableDirectFlip, profile.scalingFlags); + JsonHelper::ReadBoolFlag(profileObj, "enableHdrCompatibility", ScalingFlags::EnableHdrCompatibility, profile.scalingFlags); { uint32_t cursorScaling = (uint32_t)CursorScaling::NoScaling; diff --git a/src/Magpie/Magpie.vcxproj b/src/Magpie/Magpie.vcxproj index 1bd8fbf90..b3de7083a 100644 --- a/src/Magpie/Magpie.vcxproj +++ b/src/Magpie/Magpie.vcxproj @@ -7,7 +7,10 @@ true true true + false + E:\Magpie-0.6.5-build\native-cppwinrt\union\ low + "D:\Windows Kits\10\bin\10.0.26100.0\x64\" true {1239537c-e5b8-427a-9e7f-ea443d1f3529} $(MSBuildProjectName) @@ -18,8 +21,8 @@ 10.0 10.0.26100.0 10.0.26100.0 - $(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ - $(SolutionDir)\bin\$(Platform)\$(Configuration)\ + $(BuildRoot)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ + $(BuildRoot)\bin\$(Platform)\$(Configuration)\ $(IntDir)\Generated Files\ x64|arm64 true @@ -70,6 +73,7 @@ + @@ -81,7 +85,7 @@ _VSDESIGNER_DONT_LOAD_AS_DLL;DISABLE_XAML_GENERATED_MAIN;%(PreprocessorDefinitions) Level4 - ..\Magpie.Core\include;%(AdditionalIncludeDirectories) + E:\Magpie-0.6.5-build\native-cppwinrt\union;..\Magpie.Core\include;%(AdditionalIncludeDirectories) $(LlvmArchitectureSwitch) %(AdditionalOptions) @@ -879,3 +883,5 @@ Text="Release runtime layout is incomplete: $(OutDir)resources.pri was not generated. Do not deploy Magpie.exe by itself." /> + + diff --git a/src/Magpie/Profile.h b/src/Magpie/Profile.h index 69a85622d..4a4eadbde 100644 --- a/src/Magpie/Profile.h +++ b/src/Magpie/Profile.h @@ -112,6 +112,7 @@ struct Profile { DEFINE_FLAG_ACCESSOR(IsCaptureTitleBar, ScalingFlags::CaptureTitleBar, scalingFlags) DEFINE_FLAG_ACCESSOR(IsAdjustCursorSpeed, ScalingFlags::AdjustCursorSpeed, scalingFlags) DEFINE_FLAG_ACCESSOR(IsDirectFlipDisabled, ScalingFlags::DisableDirectFlip, scalingFlags) + DEFINE_FLAG_ACCESSOR(IsHdrCompatibilityEnabled, ScalingFlags::EnableHdrCompatibility, scalingFlags) // 默认规则 name、pathRule 和 classNameRule 均为空 std::wstring name; diff --git a/src/Magpie/ProfilePage.xaml b/src/Magpie/ProfilePage.xaml index dc52eed26..ac15a638f 100644 --- a/src/Magpie/ProfilePage.xaml +++ b/src/Magpie/ProfilePage.xaml @@ -56,6 +56,13 @@ + + + + + + IsHdrCompatibilityEnabled(); +} + +void ProfileViewModel::IsHdrCompatibilityEnabled(bool value) { + if (_data->IsHdrCompatibilityEnabled() == value) { + return; + } + + _data->IsHdrCompatibilityEnabled(value); + AppSettings::Get().SaveAsync(); + + RaisePropertyChanged(L"IsHdrCompatibilityEnabled"); +} + bool ProfileViewModel::HasMultipleMonitors() const noexcept { return GetSystemMetrics(SM_CMONITORS) > 1; } diff --git a/src/Magpie/ProfileViewModel.h b/src/Magpie/ProfileViewModel.h index 5c4b22a3c..80fc12814 100644 --- a/src/Magpie/ProfileViewModel.h +++ b/src/Magpie/ProfileViewModel.h @@ -79,6 +79,9 @@ struct ProfileViewModel : ProfileViewModelT, bool Is3DGameMode() const noexcept; void Is3DGameMode(bool value); + bool IsHdrCompatibilityEnabled() const noexcept; + void IsHdrCompatibilityEnabled(bool value); + bool HasMultipleMonitors() const noexcept; IVector MonitorOptions() const noexcept { diff --git a/src/Magpie/ProfileViewModel.idl b/src/Magpie/ProfileViewModel.idl index 30758a37d..8f9466a98 100644 --- a/src/Magpie/ProfileViewModel.idl +++ b/src/Magpie/ProfileViewModel.idl @@ -32,6 +32,7 @@ namespace Magpie { Int32 AutoScale; Boolean Is3DGameMode; + Boolean IsHdrCompatibilityEnabled; Boolean HasMultipleMonitors { get; }; IVector MonitorOptions { get; }; diff --git a/src/Magpie/Resources.language-de.resw b/src/Magpie/Resources.language-de.resw index 6745efffb..509af8aa8 100644 --- a/src/Magpie/Resources.language-de.resw +++ b/src/Magpie/Resources.language-de.resw @@ -237,6 +237,9 @@ 3D Spielmodus + + HDR compatibility + Automatisch skalieren, wenn im Vordergrund @@ -931,6 +934,9 @@ Aktiviert Optimierungen für 3D‑Spiele, ist aber nicht mit Symbolleiste und Fensterskalierung kompatibel + + Enable the experimental HDR capture-to-effect compatibility path. Keep disabled unless you are testing HDR support + Vollbildskalierung diff --git a/src/Magpie/Resources.language-en-US.resw b/src/Magpie/Resources.language-en-US.resw index 839ed34f3..6df3748aa 100644 --- a/src/Magpie/Resources.language-en-US.resw +++ b/src/Magpie/Resources.language-en-US.resw @@ -426,6 +426,9 @@ 3D game mode + + HDR compatibility + Automatically enable effect group when in foreground @@ -943,6 +946,9 @@ Enables optimizations for 3D games, but is incompatible with the toolbar and windowed mode + + Enable the experimental HDR capture-to-effect compatibility path. Keep disabled unless you are testing HDR support + Screenshot save directory diff --git a/src/Magpie/Resources.language-es.resw b/src/Magpie/Resources.language-es.resw index 1c022b438..d222f4e80 100644 --- a/src/Magpie/Resources.language-es.resw +++ b/src/Magpie/Resources.language-es.resw @@ -315,6 +315,9 @@ Modo de juego 3D + + HDR compatibility + Escalar de manera automática cuando está en primer plano @@ -895,6 +898,9 @@ Activa optimizaciones para juegos 3D, pero no es compatible con la barra de herramientas y el escalado en modo ventana + + Enable the experimental HDR capture-to-effect compatibility path. Keep disabled unless you are testing HDR support + Ruta de guardado de capturas de pantalla diff --git a/src/Magpie/Resources.language-fi.resw b/src/Magpie/Resources.language-fi.resw index 2490364e9..255f6c5d9 100644 --- a/src/Magpie/Resources.language-fi.resw +++ b/src/Magpie/Resources.language-fi.resw @@ -343,6 +343,9 @@ 3D pelitila + + HDR compatibility + Aktiivisen ruudun automaattinen skaalaus @@ -854,6 +857,9 @@ Mahdollistaa 3D-pelien optimoinnit, mutta ei ole yhteensopiva työkalupalkin ja ikkunoidun skaalauksen kanssa + + Enable the experimental HDR capture-to-effect compatibility path. Keep disabled unless you are testing HDR support + Kuvaruudunkaappausten tallennushakemisto diff --git a/src/Magpie/Resources.language-fr.resw b/src/Magpie/Resources.language-fr.resw index 76ee28bce..c6ce153b7 100644 --- a/src/Magpie/Resources.language-fr.resw +++ b/src/Magpie/Resources.language-fr.resw @@ -406,6 +406,9 @@ Mode jeux 3D + + HDR compatibility + Pas de mise à l'échelle @@ -910,6 +913,9 @@ Permet des optimisations pour les jeux 3D, mais est incompatible avec la barre d'outils et la mise à l'échelle en mode fenêtré + + Enable the experimental HDR capture-to-effect compatibility path. Keep disabled unless you are testing HDR support + Répertoire de sauvegarde des captures d'écran diff --git a/src/Magpie/Resources.language-hu.resw b/src/Magpie/Resources.language-hu.resw index 611d6ea29..1fc4c89e7 100644 --- a/src/Magpie/Resources.language-hu.resw +++ b/src/Magpie/Resources.language-hu.resw @@ -364,4 +364,10 @@ Magpie a főablak bezárása után is a háttérben fog futni - \ No newline at end of file + + HDR-kompatibilitás + + + Engedélyezi a kísérleti HDR-rögzítés és effektusok közötti kompatibilitási útvonalat. Csak HDR-támogatás tesztelésekor kapcsold be. + + diff --git a/src/Magpie/Resources.language-id.resw b/src/Magpie/Resources.language-id.resw index 169447a81..ed5ad65e8 100644 --- a/src/Magpie/Resources.language-id.resw +++ b/src/Magpie/Resources.language-id.resw @@ -369,6 +369,9 @@ Mode 3D game + + HDR compatibility + Skala otomatis ketika berada di latar depan @@ -913,6 +916,9 @@ Mengaktifkan optimasi untuk game 3D, tetapi tidak kompatibel dengan bilah alat dan penskalaan berjendela + + Enable the experimental HDR capture-to-effect compatibility path. Keep disabled unless you are testing HDR support + Direktori penyimpanan tangkapan layar diff --git a/src/Magpie/Resources.language-it.resw b/src/Magpie/Resources.language-it.resw index 2548c669e..5db9cfbcb 100644 --- a/src/Magpie/Resources.language-it.resw +++ b/src/Magpie/Resources.language-it.resw @@ -324,6 +324,12 @@ Modalità gioco 3D + + HDR compatibility + + + Enable the experimental HDR capture-to-effect compatibility path. Keep disabled unless you are testing HDR support + Imposta il fattore di ridimensionamento dopo aver riempito lo schermo con il ridimensionamento proporzionale diff --git a/src/Magpie/Resources.language-ja.resw b/src/Magpie/Resources.language-ja.resw index efcc65059..bad6e8e88 100644 --- a/src/Magpie/Resources.language-ja.resw +++ b/src/Magpie/Resources.language-ja.resw @@ -589,6 +589,9 @@ 3Dゲームモード + + HDR compatibility + 最前面時自動スケーリング @@ -910,6 +913,9 @@ 3Dゲームの最適化を可能にしますが、ツールバーやウィンドウ・スケーリングとの互換性はありません + + Enable the experimental HDR capture-to-effect compatibility path. Keep disabled unless you are testing HDR support + スクリーンショットの保存先 diff --git a/src/Magpie/Resources.language-ka.resw b/src/Magpie/Resources.language-ka.resw index 4ade50e89..8ff42d355 100644 --- a/src/Magpie/Resources.language-ka.resw +++ b/src/Magpie/Resources.language-ka.resw @@ -376,4 +376,10 @@ სხვა ბმულები - \ No newline at end of file + + HDR თავსებადობა + + + ჩართავს ექსპერიმენტულ HDR გადაღება-ეფექტის თავსებადობის გზას. გამორთული დატოვეთ, თუ HDR მხარდაჭერას არ ამოწმებთ. + + diff --git a/src/Magpie/Resources.language-ko.resw b/src/Magpie/Resources.language-ko.resw index 97aa26b56..95fdd4592 100644 --- a/src/Magpie/Resources.language-ko.resw +++ b/src/Magpie/Resources.language-ko.resw @@ -315,6 +315,9 @@ 3D 게임 모드 + + HDR compatibility + 모든 모니터 @@ -877,6 +880,9 @@ 3D 게임 최적화 기능으로 활성화하지만, 툴바 및 창 크기 조정 기능과 호환되지 않습니다 + + Enable the experimental HDR capture-to-effect compatibility path. Keep disabled unless you are testing HDR support + 스크린샷 저장 경로 diff --git a/src/Magpie/Resources.language-pl.resw b/src/Magpie/Resources.language-pl.resw index b83751963..3a8ba3ead 100644 --- a/src/Magpie/Resources.language-pl.resw +++ b/src/Magpie/Resources.language-pl.resw @@ -154,6 +154,9 @@ Tryb gry 3D + + HDR compatibility + Automatycznie skaluj, gdy okno jest na pierwszym planie @@ -854,6 +857,9 @@ Włącza optymalizacje dla gier 3D, ale nie zadziała z paskiem narzędzi i skalowaniem okien + + Enable the experimental HDR capture-to-effect compatibility path. Keep disabled unless you are testing HDR support + Katalog zapisu zrzutów ekranu diff --git a/src/Magpie/Resources.language-pt-BR.resw b/src/Magpie/Resources.language-pt-BR.resw index e962172fd..c7909af79 100644 --- a/src/Magpie/Resources.language-pt-BR.resw +++ b/src/Magpie/Resources.language-pt-BR.resw @@ -639,6 +639,12 @@ Modo de jogo 3D + + HDR compatibility + + + Enable the experimental HDR capture-to-effect compatibility path. Keep disabled unless you are testing HDR support + Maior proximidade diff --git a/src/Magpie/Resources.language-ru.resw b/src/Magpie/Resources.language-ru.resw index f392cc294..3cd84380c 100644 --- a/src/Magpie/Resources.language-ru.resw +++ b/src/Magpie/Resources.language-ru.resw @@ -429,6 +429,9 @@ Режим 3D игры + + HDR compatibility + Автоматическое масштабирование когда на переднем плане @@ -910,6 +913,9 @@ Включает оптимизации под 3D-игры, но несовместимо с панелью инструментов и масштабированием окна + + Enable the experimental HDR capture-to-effect compatibility path. Keep disabled unless you are testing HDR support + Директория сохранения скриншотов diff --git a/src/Magpie/Resources.language-ta.resw b/src/Magpie/Resources.language-ta.resw index c67782242..544b86401 100644 --- a/src/Magpie/Resources.language-ta.resw +++ b/src/Magpie/Resources.language-ta.resw @@ -453,6 +453,9 @@ 3D விளையாட்டு முறை + + HDR compatibility + முன்புறத்தில் இருக்கும்போது ஆட்டோ அளவுகோல் @@ -913,6 +916,9 @@ 3D கேம்களுக்கான மேம்படுத்தல்களை இயக்குகிறது, ஆனால் கருவிப்பட்டி மற்றும் சாளர அளவீடு ஆகியவற்றுடன் பொருந்தாது + + Enable the experimental HDR capture-to-effect compatibility path. Keep disabled unless you are testing HDR support + திரைக்காட்சி சேமிப்பு அடைவு diff --git a/src/Magpie/Resources.language-tr.resw b/src/Magpie/Resources.language-tr.resw index 838828460..56cdd46b2 100644 --- a/src/Magpie/Resources.language-tr.resw +++ b/src/Magpie/Resources.language-tr.resw @@ -420,6 +420,9 @@ 3D oyun kipi + + HDR compatibility + Ön planda otomatik ölçeklendir @@ -913,6 +916,9 @@ 3D oyunlar için optimizasyonları etkinleştirir, ancak araç çubuğu ve pencereli ölçeklendirme ile uyumsuzdur + + Enable the experimental HDR capture-to-effect compatibility path. Keep disabled unless you are testing HDR support + Ekran görüntüsü kaydetme dizini diff --git a/src/Magpie/Resources.language-uk.resw b/src/Magpie/Resources.language-uk.resw index f04255906..5b628dfaa 100644 --- a/src/Magpie/Resources.language-uk.resw +++ b/src/Magpie/Resources.language-uk.resw @@ -333,6 +333,12 @@ Режим 3D гри + + HDR compatibility + + + Enable the experimental HDR capture-to-effect compatibility path. Keep disabled unless you are testing HDR support + Автоматичне масштабування на передньому плані diff --git a/src/Magpie/Resources.language-vi.resw b/src/Magpie/Resources.language-vi.resw index 3d50ed4c4..263c7a57a 100644 --- a/src/Magpie/Resources.language-vi.resw +++ b/src/Magpie/Resources.language-vi.resw @@ -361,6 +361,9 @@ Chế độ game 3D + + HDR compatibility + Tự động scale cửa sổ trên cùng @@ -940,6 +943,9 @@ Cho phép tối ưu hóa cho trò chơi 3D, nhưng không tương thích với thanh công cụ và tỷ lệ cửa sổ + + Enable the experimental HDR capture-to-effect compatibility path. Keep disabled unless you are testing HDR support + Thư mục lưu ảnh chụp màn hình diff --git a/src/Magpie/Resources.language-zh-Hans.resw b/src/Magpie/Resources.language-zh-Hans.resw index 1ad161f23..9eda452aa 100644 --- a/src/Magpie/Resources.language-zh-Hans.resw +++ b/src/Magpie/Resources.language-zh-Hans.resw @@ -426,6 +426,9 @@ 3D 游戏模式 + + HDR 兼容 + 位于前台时自动启用效果组 @@ -943,6 +946,9 @@ 针对 3D 游戏优化,不支持工具栏和窗口模式 + + 启用实验性 HDR 捕获到效果兼容路径。除非正在测试 HDR 支持,请保持关闭 + 截图保存目录 diff --git a/src/Magpie/Resources.language-zh-Hant.resw b/src/Magpie/Resources.language-zh-Hant.resw index 87fa4a162..b487e5efc 100644 --- a/src/Magpie/Resources.language-zh-Hant.resw +++ b/src/Magpie/Resources.language-zh-Hant.resw @@ -384,6 +384,9 @@ 3D 遊戲模式 + + HDR 相容 + 位於前景時自動啟用效果組 @@ -916,6 +919,9 @@ 針對 3D 遊戲最佳化,不支援工具列和視窗模式 + + 啟用實驗性 HDR 擷取到效果相容路徑。除非正在測試 HDR 支援,請保持關閉 + 截圖儲存目錄 diff --git a/src/TouchHelper/TouchHelper.vcxproj b/src/TouchHelper/TouchHelper.vcxproj index 3593d2fa4..d144a0954 100644 --- a/src/TouchHelper/TouchHelper.vcxproj +++ b/src/TouchHelper/TouchHelper.vcxproj @@ -4,8 +4,8 @@ Win32Proj {05b51bb8-08cb-4907-884f-8e2ad6bf6052} 10.0.26100.0 - $(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ - $(SolutionDir)\bin\$(Platform)\$(Configuration)\ + $(BuildRoot)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ + $(BuildRoot)\bin\$(Platform)\$(Configuration)\ @@ -65,4 +65,4 @@ - \ No newline at end of file + diff --git a/src/Updater/Updater.vcxproj b/src/Updater/Updater.vcxproj index 9d71741f6..5419bef9a 100644 --- a/src/Updater/Updater.vcxproj +++ b/src/Updater/Updater.vcxproj @@ -4,8 +4,8 @@ Win32Proj {e82b7a20-0557-4dc1-b418-87977d7450a4} 10.0.26100.0 - $(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ - $(SolutionDir)\bin\$(Platform)\$(Configuration)\ + $(BuildRoot)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ + $(BuildRoot)\bin\$(Platform)\$(Configuration)\ @@ -67,4 +67,4 @@ - \ No newline at end of file + diff --git a/src/WinUI.targets b/src/WinUI.targets index 4db4b951f..944b2a88f 100644 --- a/src/WinUI.targets +++ b/src/WinUI.targets @@ -7,6 +7,7 @@ + @@ -90,8 +91,8 @@ 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 windowsSdkBinDir = Path.Combine(MagpieWindowsKitsPath, "bin"); +string windowsSdkDir = Directory.GetDirectories(windowsSdkBinDir) .OrderByDescending(dir => dir) .FirstOrDefault(dir => dir.Contains("10.")); if (windowsSdkDir == null) { @@ -203,7 +204,7 @@ File.Delete(configFileName); <_ReferenceRelatedPaths Remove="@(_ReferenceRelatedPaths)" Condition="'%(Filename)' == 'Microsoft.UI.Xaml' And '%(Extension)' != '.winmd'" /> - + @@ -251,3 +252,4 @@ File.Delete(configFileName); + diff --git a/src/_ConanDeps/_ConanDeps.vcxproj b/src/_ConanDeps/_ConanDeps.vcxproj index 0b31ba54c..6b4ae701e 100644 --- a/src/_ConanDeps/_ConanDeps.vcxproj +++ b/src/_ConanDeps/_ConanDeps.vcxproj @@ -4,8 +4,8 @@ Win32Proj {456ccae4-2c51-4cf2-8d3a-1efce8c41a2d} 10.0.26100.0 - $(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ - $(SolutionDir)\bin\$(Platform)\$(Configuration)\ + $(BuildRoot)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ + $(BuildRoot)\bin\$(Platform)\$(Configuration)\ diff --git a/tests/HdrMechanicalTests.cpp b/tests/HdrMechanicalTests.cpp new file mode 100644 index 000000000..4445ba236 --- /dev/null +++ b/tests/HdrMechanicalTests.cpp @@ -0,0 +1,163 @@ +// Lightweight unit-style validation for the HDR mechanical slice. +// +// This file is intentionally kept independent of the UI/configuration projects +// so it can be compiled as a small console executable when a test runner is +// available. It validates protocol route data and the adapter dispatcher plan. +// Configuration round-trip and source-level invariants are covered by +// scripts\Run-HdrMechanicalValidation.ps1 and by Magpie's normal build. +// +// Suggested compile shape (after MSVC environment is set up): +// cl /std:c++17 /EHsc /I src\Magpie.Core src\Magpie.Core\HdrFrame.cpp ^ +// src\Magpie.Core\HdrProtocol.cpp src\Magpie.Core\HdrColorTransform.cpp ^ +// src\Magpie.Core\HdrAdapterDispatcher.cpp tests\HdrMechanicalTests.cpp + +#include "HdrAdapterDispatcher.h" +#include "HdrFrame.h" +#include "HdrProtocol.h" + +#include +#include +#include +#include + +using namespace Magpie; + +namespace { + +int g_failures = 0; + +void Check(bool condition, const char* message) { + if (!condition) { + std::printf("FAIL: %s\n", message); + ++g_failures; + } else { + std::printf("PASS: %s\n", message); + } +} + +HdrFormatRoute MakeRoute(HdrAdapterProfile profile) { + HdrFormatRoute route; + route.effectId = "TestEffect"; + route.optionId = profile == HdrAdapterProfile::Unknown ? "UnknownRoute" : "Route"; + route.inputFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; + route.outputFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; + route.inputTransfer = HdrTransferFunction::Linear; + route.outputTransfer = HdrTransferFunction::Linear; + route.inputRange = HdrColorRange::SceneLinear; + route.outputRange = HdrColorRange::SceneLinear; + route.alphaMode = HdrAlphaMode::Preserve; + route.evidenceLevel = HdrEvidenceLevel::LocalValidation; + route.adapterProfile = profile; + route.hdrNative = profile == HdrAdapterProfile::DirectFP16 || + profile == HdrAdapterProfile::BoundedHDR || + profile == HdrAdapterProfile::ConditionalFP16; + route.defaultForHdr = profile == HdrAdapterProfile::DirectFP16; + route.defaultForSdr = profile == HdrAdapterProfile::SDRCompatible; + return route; +} + +void TestRouteStorageAndCategories() { + HdrFormatRoute route = MakeRoute(HdrAdapterProfile::DirectFP16); + Check(route.Id() == "TestEffect/Route", "route id combines effectId and optionId"); + Check(route.IsValid(), "complete route is valid"); + Check(route.IsHdrNative(), "explicit hdrNative route is HDR-native"); + Check(!route.IsHdrAdapter(), "hdrNative route is not an adapter-only route"); + + HdrFormatRoute r8Sdr = MakeRoute(HdrAdapterProfile::SDRCompatible); + r8Sdr.inputFormat = DXGI_FORMAT_R8G8B8A8_UNORM; + r8Sdr.outputFormat = DXGI_FORMAT_R8G8B8A8_UNORM; + r8Sdr.hdrNative = false; + Check(r8Sdr.IsHdrAdapter(), "R8 SDRCompatible route is an adapter route"); + Check(!r8Sdr.IsHdrNative(), "R8 route is not automatically HDR-native"); + + HdrFormatRoute fp16WithoutFlag = MakeRoute(HdrAdapterProfile::DirectFP16); + fp16WithoutFlag.hdrNative = false; + Check(!fp16WithoutFlag.IsHdrNative(), "FP16 route without explicit hdrNative is not HDR-native"); + + HdrFormatRoute r10 = MakeRoute(HdrAdapterProfile::BoundedHDR); + r10.inputFormat = DXGI_FORMAT_R10G10B10A2_UNORM; + r10.outputFormat = DXGI_FORMAT_R10G10B10A2_UNORM; + r10.inputTransfer = HdrTransferFunction::PQ; + r10.outputTransfer = HdrTransferFunction::PQ; + r10.hdrNative = true; + Check(r10.IsHdrNative(), "R10 route is HDR-native only when PQ/BT.2100 semantics are explicit"); + + HdrFormatRoutes routes{ MakeRoute(HdrAdapterProfile::SDRCompatible), MakeRoute(HdrAdapterProfile::DirectFP16) }; + Check(SelectDefaultHdrRoute(routes) != nullptr, "HDR mode selects a default HDR route"); + Check(SelectDefaultHdrRoute(routes)->adapterProfile == HdrAdapterProfile::DirectFP16, + "HDR mode prefers DirectFP16 when marked defaultForHdr"); + Check(SelectDefaultSdrRoute(routes)->adapterProfile == HdrAdapterProfile::SDRCompatible, + "SDR mode keeps SDRCompatible default"); + Check(GetAcceptedFormatRoutes(routes).size() == 2, "accepted route list contains valid routes"); + Check(GetHdrNativeFormatRoutes(routes).size() == 1, "hdrNative route list is explicit only"); + Check(GetHdrAdapterFormatRoutes(routes).size() == 1, "hdrAdapter route list contains SDR route"); +} + +void TestDispatcherProfiles() { + HdrAdapterDispatcher dispatcher; + ColorDescription color{}; + color.referenceWhiteNits = 80.0f; + color.displayPeakNits = 1000.0f; + + const char* profileNames[] = { + "DirectFP16", "BoundedHDR", "SDRCompatible", + "ConditionalFP16", "Unknown", "PresentationTerminal" + }; + const HdrAdapterProfile profiles[] = { + HdrAdapterProfile::DirectFP16, HdrAdapterProfile::BoundedHDR, + HdrAdapterProfile::SDRCompatible, HdrAdapterProfile::ConditionalFP16, + HdrAdapterProfile::Unknown, HdrAdapterProfile::PresentationTerminal + }; + + for (size_t i = 0; i < std::size(profiles); ++i) { + HdrFormatRoute route = MakeRoute(profiles[i]); + const HdrAdapterPlan plan = dispatcher.BuildPlan(route, color); + Check(plan.profile == profiles[i], profileNames[i]); + } + + HdrFormatRoute direct = MakeRoute(HdrAdapterProfile::DirectFP16); + HdrAdapterPlan plan = dispatcher.BuildPlan(direct, color); + Check(plan.IsNonTerminalCanonical(), "DirectFP16 returns canonical FP16"); + Check(!plan.requiresSdrMapping && !plan.requiresBoundedMapping, + "DirectFP16 does not introduce SDR mapping"); + + HdrFormatRoute sdr = MakeRoute(HdrAdapterProfile::SDRCompatible); + plan = dispatcher.BuildPlan(sdr, color); + Check(plan.requiresSdrMapping, "SDRCompatible requires HDR-to-SDR mapping"); + Check(plan.forwardParameters.IsValid() && plan.inverseParameters.IsValid(), + "SDRCompatible carries paired forward/inverse parameters"); + + HdrFormatRoute bounded = MakeRoute(HdrAdapterProfile::BoundedHDR); + plan = dispatcher.BuildPlan(bounded, color); + Check(plan.requiresBoundedMapping, "BoundedHDR requires bounded encode/decode"); + Check(plan.forwardParameters.IsValid() && plan.inverseParameters.IsValid(), + "BoundedHDR carries paired forward/inverse parameters"); + + HdrFormatRoute unknown = MakeRoute(HdrAdapterProfile::Unknown); + unknown.alphaMode = HdrAlphaMode::Unknown; + plan = dispatcher.BuildPlan(unknown, color); + Check(plan.usesFallback, "Unknown route selects fallback"); + Check(!plan.fallbackReason.empty(), "Unknown route records fallback reason"); + Check(plan.alphaMode == HdrAlphaMode::ForceOpaque, "Unknown fallback makes alpha explicit"); + + HdrFormatRoute terminal = MakeRoute(HdrAdapterProfile::PresentationTerminal); + plan = dispatcher.BuildPlan(terminal, color); + Check(plan.isPresentationTerminal, "PresentationTerminal is terminal"); + Check(plan.canonicalOutputFormat == DXGI_FORMAT_UNKNOWN, + "PresentationTerminal does not promise a normal canonical output"); +} + +} + +int main() { + TestRouteStorageAndCategories(); + TestDispatcherProfiles(); + + if (g_failures == 0) { + std::printf("All HDR mechanical tests passed.\n"); + return 0; + } + + std::printf("%d HDR mechanical test(s) failed.\n", g_failures); + return 1; +} From 9c2309a283b3bfef59a5197b38a8354b135363f3 Mon Sep 17 00:00:00 2001 From: konodiodaaaaa1 Date: Tue, 8 Sep 2026 02:08:22 +0800 Subject: [PATCH 02/10] Merge Magpie 0.6.6 core flow with HDR compatibility --- docs/RELEASE_NOTES_v0.6.6-hdr-fp16compat.md | 21 +++++ src/Magpie.Core/DLSSFrameGenerator.cpp | 55 ++++--------- src/Magpie.Core/DLSSNRFilter.cpp | 66 ++++++---------- .../GraphicsCaptureFrameSource.cpp | 6 ++ src/Magpie.Core/GraphicsCaptureFrameSource.h | 1 + src/Magpie.Core/ImGuiImpl.cpp | 3 + src/Magpie.Core/Magpie.Core.vcxproj | 4 + .../NativeEffectBackendFactory.cpp | 4 + src/Magpie.Core/NgxD3D12Core.cpp | 59 +++++++------- src/Magpie.Core/OverlayDrawer.cpp | 74 +++++++++++++---- src/Magpie.Core/OverlayDrawer.h | 7 +- src/Magpie.Core/Renderer.cpp | 73 ++++++++++++----- src/Magpie.Core/Renderer.h | 5 ++ src/Magpie.Core/ScalingWindow.cpp | 61 +++++++++----- src/Magpie.Core/ScalingWindow.h | 6 +- .../include/EffectParameterRestart.h | 11 --- src/Magpie.Core/include/NgxRuntimeGuard.h | 57 +++++++++++++ .../include/OverlayWindowGeometry.h | 79 +++++++++++++++++++ src/Magpie.Core/include/ScalingOptions.h | 20 ++--- .../include/ScalingSessionLifetime.h | 20 +++++ src/Magpie.Core/include/ScalingWindowOwner.h | 13 +++ src/Magpie/AppSettings.cpp | 6 ++ version.json | 6 +- 23 files changed, 460 insertions(+), 197 deletions(-) create mode 100644 docs/RELEASE_NOTES_v0.6.6-hdr-fp16compat.md create mode 100644 src/Magpie.Core/include/NgxRuntimeGuard.h create mode 100644 src/Magpie.Core/include/OverlayWindowGeometry.h create mode 100644 src/Magpie.Core/include/ScalingSessionLifetime.h create mode 100644 src/Magpie.Core/include/ScalingWindowOwner.h diff --git a/docs/RELEASE_NOTES_v0.6.6-hdr-fp16compat.md b/docs/RELEASE_NOTES_v0.6.6-hdr-fp16compat.md new file mode 100644 index 000000000..67f263ffd --- /dev/null +++ b/docs/RELEASE_NOTES_v0.6.6-hdr-fp16compat.md @@ -0,0 +1,21 @@ +# Magpie Experimental v0.6.6 HDR FP16 Compatibility + +## 中文说明 + +本版本将官方 Magpie 0.6.6 的核心生命周期、NGX 异常保护、DLSSNR history 重置、Renderer session 管理、延迟回调代际校验、窗口状态恢复和帧率控制流程合入本地 HDR 兼容分支。 + +HDR 兼容链路保留 canonical FP16 工作面、捕获边界路由、正反向归一化、DLSSNR FP16 实验路径、DLSSFG bridge,以及 XeSS、XeSS-FG、FSR、RTX Video、光流和 PassThrough 的 HDR 分支。 + +关闭 HDR 兼容总开关后,HDR 参数自动回到基线状态并进入线上 U8 处理链;HDR 相关设置只在总开关开启时生效。参数窗口的位置和尺寸会持久化到配置文件,旧配置继续使用默认尺寸。 + +完整安装包为 `Magpie-Experimental-x64.zip`,包含 `Magpie.exe`、`resources.pri`、`TouchHelper.exe`、`Updater.exe`、运行时 DLL、效果文件和许可证文件。 + +## English + +This release brings the official Magpie 0.6.6 core lifecycle changes into the local HDR-compatible branch, including NGX exception guards, DLSSNR history reset on input revision changes, renderer session lifetime handling, delayed-callback generation checks, window-state restoration, and the updated frame-rate control. + +The HDR pipeline keeps its canonical FP16 working surface, capture boundary routing, forward and inverse normalization, DLSSNR FP16 experimental path, DLSSFG normalization bridge, and HDR branches for XeSS, XeSS-FG, FSR, RTX Video, optical flow, and PassThrough. + +When the global HDR compatibility switch is off, HDR settings are reset to their baseline state and the online U8 processing chain is selected. HDR-specific settings take effect only while the global switch is enabled. Effect-parameter window position and size are persisted; older configurations use the default size. + +The complete package is `Magpie-Experimental-x64.zip` and includes `Magpie.exe`, `resources.pri`, `TouchHelper.exe`, `Updater.exe`, runtime DLLs, effect files, and license files. diff --git a/src/Magpie.Core/DLSSFrameGenerator.cpp b/src/Magpie.Core/DLSSFrameGenerator.cpp index 67333df3e..43a1ef7cd 100644 --- a/src/Magpie.Core/DLSSFrameGenerator.cpp +++ b/src/Magpie.Core/DLSSFrameGenerator.cpp @@ -1,4 +1,5 @@ #include "pch.h" +#include "NgxRuntimeGuard.h" #include "DLSSFrameGenerator.h" #include "DeviceResources.h" #include "FrameGuidanceD3D12Interop.h" @@ -63,21 +64,13 @@ static bool NGXSucceeded(NVSDK_NGX_Result result) noexcept { return NVSDK_NGX_SUCCEED(result); } -static LONG CaptureNgxException(DWORD code, DWORD* sehCode) noexcept { - *sehCode = code; - return EXCEPTION_EXECUTE_HANDLER; -} - static NVSDK_NGX_Result ReleaseFeatureSafely( NVSDK_NGX_Handle* feature, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { return NVSDK_NGX_D3D12_ReleaseFeature(feature); - } __except (CaptureNgxException(GetExceptionCode(), sehCode)) { - return NVSDK_NGX_Result_FAIL_PlatformError; - } + }, NVSDK_NGX_Result_FAIL_PlatformError, sehCode); } static NVSDK_NGX_Result GetParameterISafely( @@ -86,12 +79,9 @@ static NVSDK_NGX_Result GetParameterISafely( int* value, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { return NVSDK_NGX_Parameter_GetI(parameters, name, value); - } __except (CaptureNgxException(GetExceptionCode(), sehCode)) { - return NVSDK_NGX_Result_FAIL_PlatformError; - } + }, NVSDK_NGX_Result_FAIL_PlatformError, sehCode); } static NVSDK_NGX_Result GetParameterUISafely( @@ -100,12 +90,9 @@ static NVSDK_NGX_Result GetParameterUISafely( uint32_t* value, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { return NVSDK_NGX_Parameter_GetUI(parameters, name, value); - } __except (CaptureNgxException(GetExceptionCode(), sehCode)) { - return NVSDK_NGX_Result_FAIL_PlatformError; - } + }, NVSDK_NGX_Result_FAIL_PlatformError, sehCode); } static bool SetParameterUISafely( @@ -114,13 +101,10 @@ static bool SetParameterUISafely( uint32_t value, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { NVSDK_NGX_Parameter_SetUI(parameters, name, value); return true; - } __except (CaptureNgxException(GetExceptionCode(), sehCode)) { - return false; - } + }, false, sehCode); } static bool SetParameterULLSafely( @@ -129,13 +113,10 @@ static bool SetParameterULLSafely( uint64_t value, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { NVSDK_NGX_Parameter_SetULL(parameters, name, value); return true; - } __except (CaptureNgxException(GetExceptionCode(), sehCode)) { - return false; - } + }, false, sehCode); } static NVSDK_NGX_Result CreateDlssgSafely( @@ -145,13 +126,10 @@ static NVSDK_NGX_Result CreateDlssgSafely( NVSDK_NGX_DLSSG_Create_Params* createParams, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { return NGX_D3D12_CREATE_DLSSG( commandList, 1, 1, feature, parameters, createParams); - } __except (CaptureNgxException(GetExceptionCode(), sehCode)) { - return NVSDK_NGX_Result_FAIL_PlatformError; - } + }, NVSDK_NGX_Result_FAIL_PlatformError, sehCode); } static NVSDK_NGX_Result EvaluateDlssgSafely( @@ -162,13 +140,10 @@ static NVSDK_NGX_Result EvaluateDlssgSafely( NVSDK_NGX_DLSSG_Opt_Eval_Params* optionalParams, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { return NGX_D3D12_EVALUATE_DLSSG( commandList, feature, parameters, evalParams, optionalParams); - } __except (CaptureNgxException(GetExceptionCode(), sehCode)) { - return NVSDK_NGX_Result_FAIL_PlatformError; - } + }, NVSDK_NGX_Result_FAIL_PlatformError, sehCode); } static bool WaitForFence(DLSSFrameGenerator::Impl& impl, uint64_t value) noexcept { diff --git a/src/Magpie.Core/DLSSNRFilter.cpp b/src/Magpie.Core/DLSSNRFilter.cpp index 2a9d6eb3a..c2fb768fb 100644 --- a/src/Magpie.Core/DLSSNRFilter.cpp +++ b/src/Magpie.Core/DLSSNRFilter.cpp @@ -1,4 +1,5 @@ #include "pch.h" +#include "NgxRuntimeGuard.h" #include "DLSSNRFilter.h" #include "DeviceResources.h" #include "DirectXHelper.h" @@ -708,11 +709,6 @@ std::atomic g_snippetCallerModule = nullptr; std::atomic g_snippetOriginalGetModuleFileNameW = nullptr; -LONG CaptureNgxException(DWORD code, DWORD* sehCode) noexcept { - *sehCode = code; - return EXCEPTION_EXECUTE_HANDLER; -} - NVSDK_NGX_Result CallCreateFeatureSafely( DLSSNRFilter::Impl::CreateFeatureFn function, ID3D12GraphicsCommandList* commandList, @@ -721,12 +717,9 @@ NVSDK_NGX_Result CallCreateFeatureSafely( NVSDK_NGX_Handle** feature, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { return function(commandList, featureId, parameters, feature); - } __except (CaptureNgxException(GetExceptionCode(), sehCode)) { - return NVSDK_NGX_Result_FAIL_PlatformError; - } + }, NVSDK_NGX_Result_FAIL_PlatformError, sehCode); } NVSDK_NGX_Result CallEvaluateFeatureSafely( @@ -736,12 +729,9 @@ NVSDK_NGX_Result CallEvaluateFeatureSafely( const NVSDK_NGX_Parameter* parameters, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { return function(commandList, feature, parameters, nullptr); - } __except (CaptureNgxException(GetExceptionCode(), sehCode)) { - return NVSDK_NGX_Result_FAIL_PlatformError; - } + }, NVSDK_NGX_Result_FAIL_PlatformError, sehCode); } NVSDK_NGX_Result CallReleaseFeatureSafely( @@ -749,12 +739,9 @@ NVSDK_NGX_Result CallReleaseFeatureSafely( NVSDK_NGX_Handle* feature, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { return function(feature); - } __except (CaptureNgxException(GetExceptionCode(), sehCode)) { - return NVSDK_NGX_Result_FAIL_PlatformError; - } + }, NVSDK_NGX_Result_FAIL_PlatformError, sehCode); } NVSDK_NGX_Result CallShutdownSafely( @@ -762,12 +749,9 @@ NVSDK_NGX_Result CallShutdownSafely( ID3D12Device* device, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { return function(device); - } __except (CaptureNgxException(GetExceptionCode(), sehCode)) { - return NVSDK_NGX_Result_FAIL_PlatformError; - } + }, NVSDK_NGX_Result_FAIL_PlatformError, sehCode); } NVSDK_NGX_Result CallSnippetInitSafely( @@ -776,14 +760,11 @@ NVSDK_NGX_Result CallSnippetInitSafely( ID3D12Device* device, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { return function( DLSSNR_SIGNED_SNIPPET_APPLICATION_ID, applicationDataPath, device, NVSDK_NGX_Version_API, nullptr); - } __except (CaptureNgxException(GetExceptionCode(), sehCode)) { - return NVSDK_NGX_Result_FAIL_PlatformError; - } + }, NVSDK_NGX_Result_FAIL_PlatformError, sehCode); } DWORD WINAPI SnippetGetModuleFileNameW( @@ -1083,14 +1064,14 @@ DLSSNRFilter::Impl::~Impl() { const bool callerCompatibilityRestored = RestoreSnippetCallerCompatibility(*this); if (snippetModule) { - if (callerCompatibilityRestored) { + if (callerCompatibilityRestored && !NgxRuntimeGuard::IsFaulted()) { if (!FreeLibrary(snippetModule)) { Logger::Get().Win32Error( - "Release DLSSNR signed snippet DLL failed"); + "Release DLSSNR signed snippet DLL failed"); } } else { Logger::Get().Warn( - "DLSSNR signed snippet DLL retained because caller IAT restoration failed"); + "DLSSNR signed snippet DLL retained after NGX fault or caller IAT restoration failure"); } snippetModule = nullptr; } @@ -1380,13 +1361,10 @@ static bool SetCreateParametersSafely( DLSSNRFilter::Impl& impl, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { SetCreateParametersUnsafe(impl); return true; - } __except (CaptureNgxException(GetExceptionCode(), sehCode)) { - return false; - } + }, false, sehCode); } static bool InitializeSignedSnippet( @@ -1482,13 +1460,10 @@ static bool SetEvaluateParametersSafely( bool guidanceReset, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { SetEvaluateParametersUnsafe(impl, settings, guidance, guidanceReset); return true; - } __except (CaptureNgxException(GetExceptionCode(), sehCode)) { - return false; - } + }, false, sehCode); } static bool PrepareInput( @@ -2222,6 +2197,11 @@ bool DLSSNRFilter::Draw(const NativeEffectDrawContext& context) noexcept { } return true; } + // A live upstream edit can change this input even for the same capture ID. + // Re-evaluate with fresh history instead of mixing it with the old image. + if (impl.lastEvaluatedInputRevision != context.inputRevision) { + impl.resetHistory = true; + } auto fail = [&](std::string_view stage) noexcept { impl.disabled = true; LogDlssnrStatus(fmt::format( diff --git a/src/Magpie.Core/GraphicsCaptureFrameSource.cpp b/src/Magpie.Core/GraphicsCaptureFrameSource.cpp index 81453d2f0..b761d86bc 100644 --- a/src/Magpie.Core/GraphicsCaptureFrameSource.cpp +++ b/src/Magpie.Core/GraphicsCaptureFrameSource.cpp @@ -119,6 +119,7 @@ bool GraphicsCaptureFrameSource::Start() noexcept { FrameSourceState GraphicsCaptureFrameSource::_Update() noexcept { FrameTrace::Scope traceAcquire(FrameTrace::Event::WgcAcquire); + if (_captureStopping) return FrameSourceState::Waiting; if (_captureFailed) return FrameSourceState::Error; if (!_captureSession || !_captureFramePool) { return _FailCapture("WGC capture session unavailable", E_UNEXPECTED); @@ -232,6 +233,11 @@ FrameSourceState GraphicsCaptureFrameSource::_Update() noexcept { } void GraphicsCaptureFrameSource::OnCursorVisibilityChanged(bool isVisible, bool onDestory) noexcept { + if (_captureStopping) return; + if (onDestory) { + _captureStopping = true; + _FinishRecovery(); + } // 显示光标时必须重启捕获 if (isVisible) { const bool stopped = _StopCapture(); diff --git a/src/Magpie.Core/GraphicsCaptureFrameSource.h b/src/Magpie.Core/GraphicsCaptureFrameSource.h index 0a315f398..dd91643d6 100644 --- a/src/Magpie.Core/GraphicsCaptureFrameSource.h +++ b/src/Magpie.Core/GraphicsCaptureFrameSource.h @@ -56,6 +56,7 @@ class GraphicsCaptureFrameSource final : public FrameSourceBase { std::chrono::steady_clock::time_point _lastRecoveryGeometryCheck{}; UINT_PTR _recoveryTimer = 0; bool _captureFailed = false; + bool _captureStopping = false; winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice _wrappedD3DDevice{ nullptr }; winrt::Windows::Graphics::Capture::GraphicsCaptureItem _captureItem{ nullptr }; diff --git a/src/Magpie.Core/ImGuiImpl.cpp b/src/Magpie.Core/ImGuiImpl.cpp index 4a5dde6f5..9dafcc366 100644 --- a/src/Magpie.Core/ImGuiImpl.cpp +++ b/src/Magpie.Core/ImGuiImpl.cpp @@ -199,6 +199,9 @@ void ImGuiImpl::NewFrame( } const char* windowId = GetWindowIDFromName(window->Name); + if (std::string_view(windowId) == "effectParameters") { + continue; + } if (auto it = windowOptions.find(windowId); it != windowOptions.end()) { OverlayWindowOption& option = it->second; diff --git a/src/Magpie.Core/Magpie.Core.vcxproj b/src/Magpie.Core/Magpie.Core.vcxproj index 64e5ab89b..60ae14410 100644 --- a/src/Magpie.Core/Magpie.Core.vcxproj +++ b/src/Magpie.Core/Magpie.Core.vcxproj @@ -75,6 +75,8 @@ + + @@ -127,6 +129,8 @@ + + diff --git a/src/Magpie.Core/NativeEffectBackendFactory.cpp b/src/Magpie.Core/NativeEffectBackendFactory.cpp index 061a3354e..a1b24df73 100644 --- a/src/Magpie.Core/NativeEffectBackendFactory.cpp +++ b/src/Magpie.Core/NativeEffectBackendFactory.cpp @@ -1,6 +1,7 @@ #include "pch.h" #include "NativeEffectBackendFactory.h" #include "NgxD3D12Core.h" +#include "NgxRuntimeGuard.h" #include "DLSSNRFilter.h" #include "DLSSSRUpscaler.h" #include "FSR2ZeroMVUpscaler.h" @@ -69,6 +70,9 @@ NativeEffectBackendResult CreateNativeEffectBackend( const DLSSNRSettings settings = ParseDLSSNRSettings(option, hdrEnabled); auto backend = std::make_unique(); if (!backend->Initialize(resources, ngxCore, input, output, settings)) { + if (NgxRuntimeGuard::IsFaulted()) { + return { true, nullptr, ScalingError::NgxRestartRequired }; + } const char status[] = "DLSSNR STATUS: Feature=18 created=false path=unavailable " "fallback=pass-through\n"; diff --git a/src/Magpie.Core/NgxD3D12Core.cpp b/src/Magpie.Core/NgxD3D12Core.cpp index 0544cd1ee..ff8363fb0 100644 --- a/src/Magpie.Core/NgxD3D12Core.cpp +++ b/src/Magpie.Core/NgxD3D12Core.cpp @@ -1,4 +1,5 @@ #include "pch.h" +#include "NgxRuntimeGuard.h" #include "NgxD3D12Core.h" #include "DeviceResources.h" #include "Logger.h" @@ -14,19 +15,13 @@ namespace Magpie { namespace { -LONG CaptureNgxCoreException(DWORD code, DWORD* sehCode) noexcept { - *sehCode = code; - return EXCEPTION_EXECUTE_HANDLER; -} - NVSDK_NGX_Result InitCoreSafely( const wchar_t* applicationDirectory, ID3D12Device* device, const NVSDK_NGX_FeatureCommonInfo* featureInfo, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { return NVSDK_NGX_D3D12_Init_with_ProjectID( "7c134ab9-9677-4af5-a2b2-bca943350861", NVSDK_NGX_ENGINE_TYPE_CUSTOM, @@ -35,57 +30,43 @@ NVSDK_NGX_Result InitCoreSafely( device, featureInfo, NVSDK_NGX_Version_API); - } __except (CaptureNgxCoreException(GetExceptionCode(), sehCode)) { - return NVSDK_NGX_Result_FAIL_PlatformError; - } + }, NVSDK_NGX_Result_FAIL_PlatformError, sehCode); } NVSDK_NGX_Result AllocateParametersSafely( NVSDK_NGX_Parameter** parameters, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { return NVSDK_NGX_D3D12_AllocateParameters(parameters); - } __except (CaptureNgxCoreException(GetExceptionCode(), sehCode)) { - return NVSDK_NGX_Result_FAIL_PlatformError; - } + }, NVSDK_NGX_Result_FAIL_PlatformError, sehCode); } NVSDK_NGX_Result GetCapabilityParametersSafely( NVSDK_NGX_Parameter** parameters, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { return NVSDK_NGX_D3D12_GetCapabilityParameters(parameters); - } __except (CaptureNgxCoreException(GetExceptionCode(), sehCode)) { - return NVSDK_NGX_Result_FAIL_PlatformError; - } + }, NVSDK_NGX_Result_FAIL_PlatformError, sehCode); } NVSDK_NGX_Result DestroyParametersSafely( NVSDK_NGX_Parameter* parameters, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { return NVSDK_NGX_D3D12_DestroyParameters(parameters); - } __except (CaptureNgxCoreException(GetExceptionCode(), sehCode)) { - return NVSDK_NGX_Result_FAIL_PlatformError; - } + }, NVSDK_NGX_Result_FAIL_PlatformError, sehCode); } NVSDK_NGX_Result ShutdownCoreSafely( ID3D12Device* device, DWORD* sehCode ) noexcept { - *sehCode = 0; - __try { + return NgxRuntimeGuard::Invoke([&]() { return NVSDK_NGX_D3D12_Shutdown1(device); - } __except (CaptureNgxCoreException(GetExceptionCode(), sehCode)) { - return NVSDK_NGX_Result_FAIL_PlatformError; - } + }, NVSDK_NGX_Result_FAIL_PlatformError, sehCode); } bool LogNgxResult( @@ -96,8 +77,10 @@ bool LogNgxResult( ) noexcept { if (sehCode) { Logger::Get().Error(fmt::format( - "NGX D3D12 Core {} for {} raised SEH {:#x}", - operation, consumer, sehCode)); + "NGX D3D12 Core {} for {} raised SEH {:#x} at {:#x}, thread={}; " + "NGX disabled until Magpie is restarted", + operation, consumer, sehCode, NgxRuntimeGuard::FaultAddress(), + NgxRuntimeGuard::FaultThread())); return false; } if (!NVSDK_NGX_SUCCEED(result)) { @@ -119,6 +102,10 @@ bool NgxD3D12Core::Acquire( DeviceResources& resources, std::string_view consumer ) noexcept { + if (NgxRuntimeGuard::IsFaulted()) { + Logger::Get().Error("NGX initialization blocked after an earlier fault; restart Magpie"); + return false; + } if (!_device) { const HRESULT hr = D3D12CreateDevice( resources.GetGraphicsAdapter(), D3D_FEATURE_LEVEL_11_0, @@ -205,6 +192,11 @@ bool NgxD3D12Core::DestroyParameters( } void NgxD3D12Core::_Shutdown() noexcept { + if (NgxRuntimeGuard::IsFaulted()) { + (void)_device.detach(); + _initialized = false; + return; + } if (!_initialized) return; if (_activeConsumers || _activeParameterBlocks) { Logger::Get().Warn(fmt::format( @@ -215,6 +207,9 @@ void NgxD3D12Core::_Shutdown() noexcept { const NVSDK_NGX_Result result = ShutdownCoreSafely(_device.get(), &sehCode); if (LogNgxResult("final Shutdown1", "Renderer", result, sehCode)) { Logger::Get().Info("NGX D3D12 Core final Shutdown1 completed"); + } else { + NgxRuntimeGuard::MarkShutdownFailed(); + (void)_device.detach(); } _initialized = false; } diff --git a/src/Magpie.Core/OverlayDrawer.cpp b/src/Magpie.Core/OverlayDrawer.cpp index c73526b96..558903dc1 100644 --- a/src/Magpie.Core/OverlayDrawer.cpp +++ b/src/Magpie.Core/OverlayDrawer.cpp @@ -31,8 +31,6 @@ static const char* TOOLBAR_WINDOW_ID = "toolbar"; static const char* PROFILER_WINDOW_ID = "profiler"; static const char* EFFECT_PARAMETERS_WINDOW_ID = "effectParameters"; -static constexpr float EFFECT_PARAMETERS_DEFAULT_WIDTH = 420.0f; -static constexpr float EFFECT_PARAMETERS_DEFAULT_HEIGHT = 600.0f; static constexpr float EFFECT_PARAMETERS_MIN_WIDTH = 360.0f; static constexpr float EFFECT_PARAMETERS_MIN_HEIGHT = 400.0f; @@ -143,6 +141,12 @@ void OverlayDrawer::Draw( if (_isEffectParametersVisible && _DrawEffectParameters(itemId)) { needRedraw = true; } + if (_effectParametersWindowLayoutDirty && !ImGui::IsAnyMouseDown()) { + const ScalingWindow& scalingWindow = ScalingWindow::Get(); + const ScalingOptions& options = scalingWindow.Options(); + if (options.save) options.save(options, scalingWindow.Handle()); + _effectParametersWindowLayoutDirty = false; + } _isEffectParameterInputActive = _isEffectParametersVisible && ImGui::IsAnyItemActive(); const float comparisonAlpha = _CalcComparisonStatusAlpha(); _lastComparisonStatusAlpha = comparisonAlpha; @@ -236,6 +240,19 @@ void OverlayDrawer::ToolbarState(Magpie::ToolbarState value) noexcept { _overlayDirty = true; } +OverlaySessionState OverlayDrawer::CaptureSessionState() const noexcept { + return { _isToolbarVisible, _isToolbarPinned, _isProfilerVisible, _isEffectParametersVisible }; +} + +void OverlayDrawer::RestoreSessionState(const OverlaySessionState& state) noexcept { + _isToolbarVisible = state.toolbarVisible; + _isToolbarPinned = state.toolbarPinned; + _isEffectParametersVisible = state.effectParametersVisible; + if (_isProfilerVisible != state.profilerVisible) InvokeAction(OverlayAction::Profiler); + _overlayDirty = true; + _ClearStatesIfNoVisibleWindow(); +} + void OverlayDrawer::InvokeAction(OverlayAction action) noexcept { switch (action) { case OverlayAction::Profiler: @@ -1292,22 +1309,24 @@ bool OverlayDrawer::_DrawEffectParameters(int& itemId) noexcept { const ImVec2 displaySize = ImGui::GetIO().DisplaySize; const float viewportMargin = 16.0f * _dpiScale; const ImVec2 maxWindowSize{ - std::max(160.0f * _dpiScale, displaySize.x - viewportMargin), - std::max(120.0f * _dpiScale, displaySize.y - viewportMargin) + std::max(1.0f, displaySize.x - viewportMargin), + std::max(1.0f, displaySize.y - viewportMargin) }; const ImVec2 minWindowSize{ std::min(EFFECT_PARAMETERS_MIN_WIDTH * _dpiScale, maxWindowSize.x), std::min(EFFECT_PARAMETERS_MIN_HEIGHT * _dpiScale, maxWindowSize.y) }; ImGui::SetNextWindowSizeConstraints(minWindowSize, maxWindowSize); - if (!_effectParametersWindowSizeInitialized) { - ImGui::SetNextWindowSize({ - std::clamp(EFFECT_PARAMETERS_DEFAULT_WIDTH * _dpiScale, - minWindowSize.x, maxWindowSize.x), - std::clamp(EFFECT_PARAMETERS_DEFAULT_HEIGHT * _dpiScale, - minWindowSize.y, maxWindowSize.y) - }); - _effectParametersWindowSizeInitialized = true; + OverlayWindowOption& windowOption = _overlayOptions->windows.at(EFFECT_PARAMETERS_WINDOW_ID); + const bool restoreLayout = !_effectParametersWindowLayoutInitialized || + _effectParametersViewport.x != displaySize.x || _effectParametersViewport.y != displaySize.y; + if (restoreLayout) { + const auto rect = RestoreEffectParametersWindow( + windowOption, displaySize.x, displaySize.y, _dpiScale); + ImGui::SetNextWindowSize({ rect.width, rect.height }); + ImGui::SetNextWindowPos({ rect.x, rect.y }); + _effectParametersWindowLayoutInitialized = true; + _effectParametersViewport = displaySize; } const std::string title = StrHelper::Concat( @@ -1319,7 +1338,29 @@ bool OverlayDrawer::_DrawEffectParameters(int& itemId) noexcept { ImGuiCol_ResizeGripHovered, ImVec4(0.35f, 0.67f, 0.95f, 0.72f)); ImGui::PushStyleColor( ImGuiCol_ResizeGripActive, ImVec4(0.35f, 0.67f, 0.95f, 1.0f)); - if (!ImGui::Begin(title.c_str(), &_isEffectParametersVisible)) { + const bool expanded = ImGui::Begin(title.c_str(), &_isEffectParametersVisible); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + const OverlayWindowRect rect{ + std::clamp(window->Pos.x, 0.0f, std::max(0.0f, displaySize.x - window->SizeFull.x)), + std::clamp(window->Pos.y, 0.0f, std::max(0.0f, displaySize.y - window->SizeFull.y)), + window->SizeFull.x, window->SizeFull.y + }; + ImGui::SetWindowPos(window, { rect.x, rect.y }); + if (!restoreLayout && !_imguiImpl.FrameInputCanceled() && + (ImGui::IsMouseDown(ImGuiMouseButton_Left) || ImGui::IsMouseReleased(ImGuiMouseButton_Left))) { + const auto& previous = _effectParametersWindowRect; + const bool moved = rect.x != previous.x || rect.y != previous.y; + const bool resizedX = rect.width != previous.width; + const bool resizedY = rect.height != previous.height; + if (moved || resizedX || resizedY) { + RememberOverlayWindowPosition(windowOption, rect, displaySize.x, displaySize.y, _dpiScale); + if (resizedX) windowOption.width = rect.width / _dpiScale; + if (resizedY) windowOption.height = rect.height / _dpiScale; + _effectParametersWindowLayoutDirty = true; + } + } + _effectParametersWindowRect = rect; + if (!expanded) { ImGui::End(); ImGui::PopStyleColor(3); _parameterResetGesture.Clear(); @@ -1357,8 +1398,11 @@ bool OverlayDrawer::_DrawEffectParameters(int& itemId) noexcept { ImGui::SameLine(); ImGui::TextDisabled("%s", _GetResourceString(L"Overlay_EffectParameters_RestartRequired").c_str()); ImGui::SetNextItemWidth(-1.0f); - if (ImGui::InputFloat("##targetFps", &_draftFrameSync.frameRate, 1.0f, 10.0f, "%.3f")) { - _draftFrameSync.frameRate = SanitizePresentationFrameRate(_draftFrameSync.frameRate); + int targetFps = static_cast(std::lround(_draftFrameSync.frameRate)); + const std::string targetFpsText = fmt::format("{:g} FPS", _draftFrameSync.frameRate); + if (ImGui::SliderInt("##targetFps", &targetFps, 15, 360, targetFpsText.c_str(), + ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_NoInput)) { + _draftFrameSync.frameRate = static_cast(targetFps); parameterEdited = needRedraw = true; } ImGui::TextWrapped("%s", _GetResourceString(L"Overlay_FrameSync_Help").c_str()); diff --git a/src/Magpie.Core/OverlayDrawer.h b/src/Magpie.Core/OverlayDrawer.h index 5fbcd252d..a093aab2e 100644 --- a/src/Magpie.Core/OverlayDrawer.h +++ b/src/Magpie.Core/OverlayDrawer.h @@ -32,6 +32,8 @@ class OverlayDrawer { void ToolbarState(Magpie::ToolbarState value) noexcept; void InvokeAction(OverlayAction action) noexcept; + OverlaySessionState CaptureSessionState() const noexcept; + void RestoreSessionState(const OverlaySessionState& state) noexcept; bool AnyVisibleWindow() const noexcept; bool IsEffectParametersVisible() const noexcept { return _isEffectParametersVisible; } @@ -158,7 +160,10 @@ class OverlayDrawer { bool _isEffectParameterInputActive = false; bool _overlayDirty = true; bool _effectParametersInitialized = false; - bool _effectParametersWindowSizeInitialized = false; + bool _effectParametersWindowLayoutInitialized = false; + bool _effectParametersWindowLayoutDirty = false; + ImVec2 _effectParametersViewport{}; + OverlayWindowRect _effectParametersWindowRect{}; EffectParameterResetGesture _parameterResetGesture; #ifdef _DEBUG bool _isDemoWindowVisible = false; diff --git a/src/Magpie.Core/Renderer.cpp b/src/Magpie.Core/Renderer.cpp index f91d42520..2a32b83cc 100644 --- a/src/Magpie.Core/Renderer.cpp +++ b/src/Magpie.Core/Renderer.cpp @@ -22,6 +22,7 @@ #include "Logger.h" #include "OverlayDrawer.h" #include "Renderer.h" +#include "NgxRuntimeGuard.h" #include "ScalingOptions.h" #include "ScalingWindow.h" #include "ScreenshotHelper.h" @@ -233,13 +234,19 @@ static double GetDisplayRefreshRate(HWND window) noexcept { return static_cast(mode.dmDisplayFrequency); } -Renderer::Renderer() noexcept {} +Renderer::Renderer() noexcept : + _sessionLifetime(std::make_shared(ScalingWindow::RunId())) {} -Renderer::~Renderer() noexcept { +void Renderer::BeginShutdown() noexcept { + _sessionLifetime->RequestStop(); // The backend can be waiting for a synchronous DLSSFG presentation while // the frontend thread is destroying this Renderer. Stop issuing new // synchronous sends before waiting for the backend thread to exit. _synchronousFramePresentationEnabled.store(false, std::memory_order_release); +} + +Renderer::~Renderer() noexcept { + BeginShutdown(); _hKeyboardHook.reset(); @@ -418,6 +425,7 @@ ScalingError Renderer::Initialize(HWND hwndAttach, OverlayOptions& overlayOption const HANDLE sharedTextureHandle = _sharedTextureHandle.load(std::memory_order_acquire); if (sharedTextureHandle == INVALID_HANDLE_VALUE) { Logger::Get().Error("后端初始化失败"); + if (NgxRuntimeGuard::IsFaulted()) return ScalingError::NgxRestartRequired; // 一般的错误不会设置 _backendInitError return _backendInitError == ScalingError::NoError ? ScalingError::ScalingFailedGeneral : _backendInitError; } @@ -489,7 +497,7 @@ ScalingError Renderer::Initialize(HWND hwndAttach, OverlayOptions& overlayOption void Renderer::OnCursorVisibilityChanged(bool isVisible, bool onDestory) { _backendThreadDispatcher.TryEnqueue([this, isVisible, onDestory]() { - if (_frameSource) { + if (_frameSource && (onDestory || !_sessionLifetime->IsStopping())) { _frameSource->OnCursorVisibilityChanged(isVisible, onDestory); // Apply capture continuity resets on the first valid frame, not on a // forced redraw while a restarted session is still waiting for content. @@ -843,9 +851,9 @@ bool Renderer::_FrontendRender( !_presenter->SetBaseFrameRateLimit(_FrontEdgeFrameRate())) { if (!_frontEdgeLimiterFailed) { _frontEdgeLimiterFailed = true; - ScalingWindow::Dispatcher().TryEnqueue([] { + ScalingWindow::Dispatcher().TryEnqueue([session = _sessionLifetime] { auto& window = ScalingWindow::Get(); - if (!window) return; + if (!session->IsCurrent(ScalingWindow::RunId()) || !window) return; if (auto report = window.Options().reportErrorDetails) report( window.SrcTracker().Handle(), ScalingError::PresentationInitFailed, "XeLL frame-rate configuration failed; disable Front Edge Sync and re-enable the effect group", 0); @@ -1416,6 +1424,12 @@ void Renderer::InvokeOverlayAction(OverlayAction action) noexcept { } } +void Renderer::RestoreOverlayState(const OverlaySessionState& state) noexcept { + ++_overlayActionRevision; + _overlayDrawer.RestoreSessionState(state); + Render(); +} + bool Renderer::SetPassThroughActive(bool value) noexcept { if (value && !_passThroughFrames.FrontendTexture(true)) { const auto& window = ScalingWindow::Get(); @@ -1814,11 +1828,6 @@ void Renderer::_BuildEffectParameterRuntimeInfos() noexcept { _effectParameterRuntimeInfos.clear(); _effectParameterRuntimeInfos.resize(_effectDescs.size()); const bool hdrEnabled = ScalingWindow::Get().Options().IsHdrCompatibilityEnabled(); - std::optional lastDlssNr; - for (uint32_t i = 0; i < _runtimeEffectOptions.size(); ++i) { - if (_runtimeEffectOptions[i].name == "DLSSNR\\DLSSNR_AI_Filter" && - i < _nativeEffectBackends.size() && _nativeEffectBackends[i]) lastDlssNr = i; - } for (uint32_t effectIdx = 0; effectIdx < _effectDescs.size(); ++effectIdx) { const EffectDesc& desc = _effectDescs[effectIdx]; @@ -1864,8 +1873,8 @@ void Renderer::_BuildEffectParameterRuntimeInfos() noexcept { info.applyMode = EffectParameterApplyMode::Live; info.restartReason = EffectParameterRestartReason::None; } - info.automaticRestart = lastDlssNr && info.applyMode == EffectParameterApplyMode::Live && - NeedsDlssNrParameterRestart(option.name, parameter.name, effectIdx < *lastDlssNr); + // The running backend owns live support and history reset semantics. + // DLSSNR observes inputRevision without tearing down the effect group. infos.push_back(std::move(info)); } } @@ -1877,6 +1886,7 @@ bool Renderer::QueueEffectParameterUpdate( float value, bool waitForOverlaySave ) noexcept { + (void)waitForOverlaySave; if (!std::isfinite(value) || !_backendThreadDispatcher || effectIdx >= _effectParameterRuntimeInfos.size() || parameterIdx >= _effectParameterRuntimeInfos[effectIdx].size()) { @@ -1887,11 +1897,6 @@ bool Renderer::QueueEffectParameterUpdate( if (info.applyMode != EffectParameterApplyMode::Live) { return false; } - if (info.automaticRestart) { - // Keep the old NR instance and its upstream inputs unchanged until teardown. - return ScalingWindow::Get().QueueEffectParameterRestart(effectIdx, parameterIdx, value, waitForOverlaySave); - } - const uint64_t key = (uint64_t(effectIdx) << 32) | parameterIdx; bool enqueueWake = false; { @@ -1977,7 +1982,7 @@ void Renderer::_ApplyPendingEffectParameters() noexcept { const EffectParameterRuntimeInfo& info = _effectParameterRuntimeInfos[effectIdx][update.parameterIdx]; if (info.name != parameter.name || - info.applyMode != EffectParameterApplyMode::Live || info.automaticRestart || + info.applyMode != EffectParameterApplyMode::Live || !std::isfinite(update.value)) { valid = false; break; @@ -2249,7 +2254,8 @@ bool Renderer::_InitializeDLSSFrameGenerator( if (!frameGenerator->Initialize( _backendResources, _ngxD3D12Core, input, { sourceDesc.Width, sourceDesc.Height }, settings)) { - _backendInitError = ScalingError::FrameGenerationInitFailed; + _backendInitError = NgxRuntimeGuard::IsFaulted() ? + ScalingError::NgxRestartRequired : ScalingError::FrameGenerationInitFailed; _backendInitContext = "DLSSFG\\DLSS_FrameGeneration"; return false; } @@ -2601,6 +2607,31 @@ void Renderer::_BackendThreadProc() noexcept { MSG msg; while (true) { + if (NgxRuntimeGuard::IsFaulted() && _ngxD3D12Core.Device() && !_sessionLifetime->IsStopping()) { + ScalingWindow::Dispatcher().TryEnqueue([session = _sessionLifetime]() { + auto& window = ScalingWindow::Get(); + if (!session->IsCurrent(ScalingWindow::RunId()) || !window) return; + if (const auto report = window.Options().reportErrorDetails) { + report(window.SrcTracker().Handle(), ScalingError::NgxRestartRequired, + fmt::format("NGX runtime fault at {:#x}, thread={}; restart required", + NgxRuntimeGuard::FaultAddress(), NgxRuntimeGuard::FaultThread()), + NgxRuntimeGuard::FaultCode()); + } else { + window.ShowError(ScalingError::NgxRestartRequired); + } + window.Stop(); + }); + _frameSource.reset(); + return; + } + if (_sessionLifetime->IsStopping()) { + if (GetMessage(&msg, NULL, 0, 0) <= 0) { + _frameSource.reset(); + return; + } + DispatchMessage(&msg); + continue; + } bool fpsUpdated = false; bool waitedForContent = false; FrameTrace::Scope traceWait(FrameTrace::Event::BackendWait); @@ -2634,6 +2665,7 @@ void Renderer::_BackendThreadProc() noexcept { } traceMessages.End(); + if (_sessionLifetime->IsStopping()) continue; if (_pendingFrameGenerationInput) { const auto now = std::chrono::steady_clock::now(); if (now < _fgInputClock.Due(now)) continue; @@ -2667,6 +2699,7 @@ void Renderer::_BackendThreadProc() noexcept { traceCapture.End(); FrameTrace::Mark(FrameTrace::Event::CaptureResult, static_cast(frameSourceState), _frameSource->CaptureTimestamp100ns()); + if (_sessionLifetime->IsStopping()) continue; switch (frameSourceState) { case FrameSourceState::Waiting: if (_frameSource->IsCaptureInterrupted()) { @@ -2713,9 +2746,11 @@ void Renderer::_BackendThreadProc() noexcept { case FrameSourceState::Error: // 捕获出错,退出缩放 ScalingWindow::Dispatcher().TryEnqueue([ + session = _sessionLifetime, context = std::string(_frameSource->CaptureErrorContext()), code = _frameSource->CaptureErrorCode()]() { ScalingWindow& scalingWindow = ScalingWindow::Get(); + if (!session->IsCurrent(ScalingWindow::RunId()) || !scalingWindow) return; if (auto report = scalingWindow.Options().reportErrorDetails) { report(scalingWindow.SrcTracker().Handle(), ScalingError::CaptureFailed, context, code); diff --git a/src/Magpie.Core/Renderer.h b/src/Magpie.Core/Renderer.h index 908359da2..89e0af536 100644 --- a/src/Magpie.Core/Renderer.h +++ b/src/Magpie.Core/Renderer.h @@ -12,6 +12,7 @@ #include "PresentationFrameRate.h" #include "FramePresentationTiming.h" #include "ScalingOptions.h" +#include "ScalingSessionLifetime.h" #include "StepTimer.h" #include #include @@ -35,6 +36,7 @@ class Renderer { Renderer(Renderer&&) = delete; ScalingError Initialize(HWND hwndAttach, OverlayOptions& overlayOptions) noexcept; + void BeginShutdown() noexcept; const std::string& InitializationContext() const noexcept { return _backendInitContext; } const std::wstring& MotionConfigurationNotice() const noexcept { return _motionConfigurationNotice; } @@ -63,6 +65,8 @@ class Renderer { void SwitchToolbarState() noexcept; void InvokeOverlayAction(OverlayAction action) noexcept; + OverlaySessionState CaptureOverlayState() const noexcept { return _overlayDrawer.CaptureSessionState(); } + void RestoreOverlayState(const OverlaySessionState& state) noexcept; bool IsPassThroughActive() const noexcept { return _isPassThroughActive; } bool SetPassThroughActive(bool value) noexcept; void TakeDisplayedScreenshot() noexcept { @@ -277,6 +281,7 @@ class Renderer { bool _frontendBaseNeedsPresent = false; RECT _destRect{}; + const std::shared_ptr _sessionLifetime; std::thread _backendThread; wil::unique_hhook _hKeyboardHook; diff --git a/src/Magpie.Core/ScalingWindow.cpp b/src/Magpie.Core/ScalingWindow.cpp index 1d4ff65bc..d693a591e 100644 --- a/src/Magpie.Core/ScalingWindow.cpp +++ b/src/Magpie.Core/ScalingWindow.cpp @@ -6,6 +6,8 @@ #include "ExclModeHelper.h" #include "Logger.h" #include "Renderer.h" +#include "NgxRuntimeGuard.h" +#include "ScalingWindowOwner.h" #include "EffectParameterValue.h" #include "Win32Helper.h" #include "WindowHelper.h" @@ -53,6 +55,13 @@ static void LogRects(const RECT& srcRect, const RECT& rendererRect, const RECT& } ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept { + if (NgxRuntimeGuard::IsFaulted() && std::ranges::any_of(_options.effects, [](const auto& effect) { + return effect.name == "DLSSNR\\DLSSNR_AI_Filter" || + ClassifyFrameGenerationEffect(effect.name) == FrameGenerationEffectKind::DLSS; + })) { + Logger::Get().Error("Effect group blocked after NGX fault; fully restart Magpie"); + return ScalingError::NgxRestartRequired; + } if (!_options.parameterSession) { _options.parameterSession = std::make_shared( _options.effects, FrameSyncSettings{ _options.isFrontEdgeSyncEnabled, _options.frontEdgeSyncFrameRate }); @@ -232,7 +241,7 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept { _windowRect.top, _windowRect.right - _windowRect.left, _windowRect.bottom - _windowRect.top, - hwndSrc, + nullptr, NULL, wil::GetModuleInstanceHandle(), this @@ -292,7 +301,7 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept { _windowRect.top, _windowRect.right - _windowRect.left, _windowRect.bottom - _windowRect.top, - hwndSrc, + nullptr, NULL, wil::GetModuleInstanceHandle(), this @@ -329,6 +338,15 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept { // 应在 Renderer 初始化后调用。推迟到缩放窗口显示后再显示 _UpdateTouchHoleWindows(true); } + if (!IsWindow(hwndSrc)) return ScalingError::SourceWindowClosed; + if (Win32Helper::IsWindowHung(hwndSrc)) return ScalingError::SourceWindowUnresponsive; + if (!SetScalingWindowOwner(Handle(), hwndSrc)) { + Logger::Get().Win32Error("Set scaling window owner after initialization failed"); + return ScalingError::ScalingWindowCreationFailed; + } + if (!AttachThreadInput(GetCurrentThreadId(), GetWindowThreadProcessId(hwndSrc, nullptr), FALSE)) { + Logger::Get().Win32Warn("Detach source input queue after setting window owner failed"); + } return ScalingError::NoError; } @@ -497,7 +515,7 @@ void ScalingWindow::RestartWithEffectParameters( // Preserve the complete current session options while performing one full // teardown/startup. WM_DESTROY must not clear _options in between. - const bool reopen = _renderer && _renderer->IsEffectParametersVisible(); + const auto overlayState = _renderer ? _renderer->CaptureOverlayState() : OverlaySessionState{}; _CancelParameterRestart(); _isSrcRepositioning = true; Destroy(); @@ -508,7 +526,7 @@ void ScalingWindow::RestartWithEffectParameters( _options.frontEdgeSyncFrameRate = frameSync.frameRate; _options.parameterSession->Desired(_options.effects); Start(hwndSource, std::move(_options)); - if (Handle() && _renderer && reopen) _renderer->InvokeOverlayAction(OverlayAction::EffectParameters); + if (Handle() && _renderer) _renderer->RestoreOverlayState(overlayState); } void ScalingWindow::CleanAfterSrcRepositioned() noexcept { @@ -547,7 +565,7 @@ void ScalingWindow::_CancelParameterRestart() noexcept { _parameterRestartQueue.Cancel(); _parameterRestartSaveRevision = 0; _restartParameters.clear(); - _reopenEffectParameters = false; + _restartOverlayState = {}; } void ScalingWindow::UpdateWaitingEffectParameter( @@ -581,11 +599,11 @@ void ScalingWindow::ProcessPendingParameterRestart() noexcept { Stop(); return; } - const bool reopen = _reopenEffectParameters; + const auto overlayState = _restartOverlayState; _CancelParameterRestart(); Logger::Get().Info("DLSSNR parameter restart: starting effect group after 500 ms pause"); Start(source, std::move(_options)); - if (Handle() && _renderer && reopen) _renderer->InvokeOverlayAction(OverlayAction::EffectParameters); + if (Handle() && _renderer) _renderer->RestoreOverlayState(overlayState); return; } @@ -618,7 +636,7 @@ void ScalingWindow::ProcessPendingParameterRestart() noexcept { infos[effect][parameter].applyMode }); } } - _reopenEffectParameters = _renderer->IsEffectParametersVisible(); + _restartOverlayState = _renderer->CaptureOverlayState(); auto changes = _parameterRestartQueue.TakeChanges(); Logger::Get().Info(fmt::format("DLSSNR parameter restart: stopping effect group for {} edited parameter(s)", changes.size())); // Joining the backend in WM_DESTROY preserves its final Applied snapshot. @@ -646,14 +664,6 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n switch (msg) { case WM_CREATE: { - // 源窗口的输入已被附加到了缩放窗口上,这是所有者窗口的默认行为,但我们不需要 - // 见 https://devblogs.microsoft.com/oldnewthing/20130412-00/?p=4683 - AttachThreadInput( - GetCurrentThreadId(), - GetWindowThreadProcessId(_srcTracker.Handle(), nullptr), - FALSE - ); - // 防止缩放 UWP 窗口时无法遮挡任务栏 // https://github.com/dechamps/WindowInvestigator/issues/3 SetProp(Handle(), L"TreatAsDesktopFullscreen", (HANDLE)TRUE); @@ -1090,16 +1100,19 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n } case WM_DESTROY: { + if (!SetScalingWindowOwner(Handle(), nullptr)) { + Logger::Get().Win32Warn("Detach scaling window owner before teardown failed"); + } + const bool ngxWasFaulted = NgxRuntimeGuard::IsFaulted(); + ++_runId; Logger::Get().Info("缩放结束"); if (_renderer) { + _renderer->BeginShutdown(); _renderer->ClearOverlayStates(); } _frontendRenderPending = false; _dlssFgFrameJobs.clear(); - // 更新 _runId 表明当前缩放结束 - ++_runId; - if (_exclModeMutex) { _exclModeMutex.ReleaseMutex(); _exclModeMutex.reset(); @@ -1114,6 +1127,16 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n _renderer.reset(); Logger::Get().Info("Renderer 已析构"); + if (!ngxWasFaulted && NgxRuntimeGuard::IsFaulted()) { + if (const auto report = _options.reportErrorDetails) { + report(_srcTracker.Handle(), ScalingError::NgxRestartRequired, + fmt::format("NGX teardown fault at {:#x}, thread={}; restart required", + NgxRuntimeGuard::FaultAddress(), NgxRuntimeGuard::FaultThread()), + NgxRuntimeGuard::FaultCode()); + } else { + ShowError(ScalingError::NgxRestartRequired); + } + } // The backend has joined. Preserve its final applied snapshot for every // automatic restart, without applying queued restart-only target values. if (_isSrcRepositioning && _options.parameterSession) { diff --git a/src/Magpie.Core/ScalingWindow.h b/src/Magpie.Core/ScalingWindow.h index c46bacb2a..c56b384b0 100644 --- a/src/Magpie.Core/ScalingWindow.h +++ b/src/Magpie.Core/ScalingWindow.h @@ -124,7 +124,9 @@ class ScalingWindow final : public WindowBaseT { } void ShowError(ScalingError error) const noexcept { - _options.showError(_srcTracker.Handle(), error); + if (const auto showError = _options.showError) { + showError(_srcTracker.Handle(), error); + } } protected: @@ -222,7 +224,7 @@ class ScalingWindow final : public WindowBaseT { EffectParameterApplyMode applyMode; }; std::vector> _restartParameters; - bool _reopenEffectParameters = false; + OverlaySessionState _restartOverlayState; std::unique_ptr _renderer; std::unique_ptr _cursorManager; diff --git a/src/Magpie.Core/include/EffectParameterRestart.h b/src/Magpie.Core/include/EffectParameterRestart.h index cf3e3473f..ad8a36ca1 100644 --- a/src/Magpie.Core/include/EffectParameterRestart.h +++ b/src/Magpie.Core/include/EffectParameterRestart.h @@ -11,17 +11,6 @@ namespace Magpie { -// Called only for parameters otherwise supported by the running live backend. -inline bool NeedsDlssNrParameterRestart(std::string_view effect, - std::string_view parameter, bool hasLaterDlssNr) noexcept { - if (IsFrameRateFilterEffect(effect)) return false; - if (hasLaterDlssNr) return true; - if (effect != "DLSSNR\\DLSSNR_AI_Filter") return false; - return parameter != "residualMultiplier" && parameter != "residualSaturation" && - parameter != "residualLightness" && parameter != "shadowStructureMultiplier" && - parameter != "reflectionGlowMultiplier"; -} - class EffectParameterRestartQueue { public: using Clock = std::chrono::steady_clock; diff --git a/src/Magpie.Core/include/NgxRuntimeGuard.h b/src/Magpie.Core/include/NgxRuntimeGuard.h new file mode 100644 index 000000000..4266a05cd --- /dev/null +++ b/src/Magpie.Core/include/NgxRuntimeGuard.h @@ -0,0 +1,57 @@ +#pragma once +#include +#include +#include + +namespace Magpie { + +class NgxRuntimeGuard { +public: + static bool IsFaulted() noexcept { return _faultCode.load(std::memory_order_acquire) != 0; } + static DWORD FaultCode() noexcept { return _faultCode.load(std::memory_order_acquire); } + static uintptr_t FaultAddress() noexcept { return _faultAddress.load(std::memory_order_relaxed); } + static DWORD FaultThread() noexcept { return _faultThread.load(std::memory_order_relaxed); } + + template + static Result Invoke(Function&& function, Result failure, DWORD* sehCode) noexcept { + std::lock_guard lock(_callMutex); + *sehCode = 0; + if (IsFaulted()) return failure; + return _InvokeSafely(function, failure, sehCode); + } + + static void MarkShutdownFailed() noexcept { + std::lock_guard lock(_callMutex); + _RecordFault(ERROR_INVALID_STATE, 0); + } + +private: + template + static Result _InvokeSafely(Function& function, Result failure, DWORD* sehCode) noexcept { + __try { + return function(); + } __except (_CaptureException(GetExceptionInformation(), sehCode)) { + return failure; + } + } + + static LONG _CaptureException(EXCEPTION_POINTERS* exception, DWORD* sehCode) noexcept { + *sehCode = exception->ExceptionRecord->ExceptionCode; + _RecordFault(*sehCode, reinterpret_cast(exception->ExceptionRecord->ExceptionAddress)); + return EXCEPTION_EXECUTE_HANDLER; + } + + static void _RecordFault(DWORD code, uintptr_t address) noexcept { + if (IsFaulted()) return; + _faultAddress.store(address, std::memory_order_relaxed); + _faultThread.store(GetCurrentThreadId(), std::memory_order_relaxed); + _faultCode.store(code, std::memory_order_release); + } + + static inline std::mutex _callMutex; + static inline std::atomic _faultCode = 0; + static inline std::atomic _faultAddress = 0; + static inline std::atomic _faultThread = 0; +}; + +} diff --git a/src/Magpie.Core/include/OverlayWindowGeometry.h b/src/Magpie.Core/include/OverlayWindowGeometry.h new file mode 100644 index 000000000..4be5d22df --- /dev/null +++ b/src/Magpie.Core/include/OverlayWindowGeometry.h @@ -0,0 +1,79 @@ +#pragma once +#include +#include +#include + +namespace Magpie { + +struct OverlayWindowOption { + uint16_t hArea = 0; + uint16_t vArea = 0; + float hPos = 0.0f; + float vPos = 0.0f; + float width = 0.0f; + float height = 0.0f; +}; + +inline void SanitizeOverlayWindowOption(OverlayWindowOption& option) noexcept { + auto sanitizeAxis = [](uint16_t& area, float& pos) { + if (area > 2) area = 0; + if (!std::isfinite(pos) || pos < 0.0f) pos = 0.0f; + if (area == 1) pos = std::min(pos, 1.0f); + }; + sanitizeAxis(option.hArea, option.hPos); + sanitizeAxis(option.vArea, option.vPos); + if (!std::isfinite(option.width) || option.width < 0.0f) option.width = 0.0f; + if (!std::isfinite(option.height) || option.height < 0.0f) option.height = 0.0f; +} + +struct OverlayWindowRect { + float x, y, width, height; +}; + +inline OverlayWindowRect RestoreEffectParametersWindow( + OverlayWindowOption option, float viewportWidth, float viewportHeight, float dpiScale +) noexcept { + SanitizeOverlayWindowOption(option); + auto restoreAxis = [dpiScale](uint16_t area, float pos, float preferred, + float defaultSize, float minimum, float viewport, float& origin, float& size) { + const float maximum = std::max(1.0f, viewport - 16.0f * dpiScale); + size = std::clamp((preferred > 0.0f ? preferred : defaultSize) * dpiScale, + std::min(minimum * dpiScale, maximum), maximum); + origin = area == 0 ? pos * dpiScale : area == 1 ? + viewport * pos - size / 2.0f : viewport - pos * dpiScale - size; + origin = std::clamp(origin, 0.0f, std::max(0.0f, viewport - size)); + }; + OverlayWindowRect rect{}; + restoreAxis(option.hArea, option.hPos, option.width, 420.0f, 360.0f, + viewportWidth, rect.x, rect.width); + restoreAxis(option.vArea, option.vPos, option.height, 600.0f, 400.0f, + viewportHeight, rect.y, rect.height); + return rect; +} + +inline void RememberOverlayWindowPosition( + OverlayWindowOption& option, const OverlayWindowRect& rect, + float viewportWidth, float viewportHeight, float dpiScale +) noexcept { + auto rememberAxis = [dpiScale](float pos, float size, float viewport, + uint16_t& area, float& offset) { + viewport = std::max(viewport, 1.0f); + const float threshold = std::max(size / viewport, 0.2f); + const float leading = std::max(pos, 0.0f); + const float trailing = std::max(viewport - pos - size, 0.0f); + if (leading < threshold * trailing) { + area = 0; + offset = leading / dpiScale; + } else if (leading * threshold <= trailing) { + area = 1; + offset = (pos + size / 2.0f) / viewport; + } else { + area = 2; + offset = trailing / dpiScale; + } + }; + rememberAxis(rect.x, rect.width, viewportWidth, option.hArea, option.hPos); + rememberAxis(rect.y, rect.height, viewportHeight, option.vArea, option.vPos); +} + +} diff --git a/src/Magpie.Core/include/ScalingOptions.h b/src/Magpie.Core/include/ScalingOptions.h index 11074ffcb..7b66be03c 100644 --- a/src/Magpie.Core/include/ScalingOptions.h +++ b/src/Magpie.Core/include/ScalingOptions.h @@ -4,6 +4,7 @@ #include #include "EffectParameterPersistence.h" #include "FramePacingOptions.h" +#include "OverlayWindowGeometry.h" #include namespace Magpie { @@ -260,17 +261,11 @@ enum class ToolbarState { COUNT }; -struct OverlayWindowOption { - // 0: 位于左侧,hPos 是窗口左边界和画面左边界距离(所有距离都是应用 DPI 缩放前的值) - // 1: 位于中侧,hPos 是窗口中心点和画面左边界距离与画面宽度之比 - // 2: 位于右侧,hPos 是窗口右边界和画面右边界距离 - uint16_t hArea = 0; - // 0: 位于上侧,vPos 是窗口上边界和画面上边界距离 - // 1: 位于中侧,vPos 是窗口中心点和画面上边界距离与画面高度之比 - // 3: 位于下侧,vPos 是窗口下边界和画面下边界距离 - uint16_t vArea = 0; - float hPos = 0.0f; - float vPos = 0.0f; +struct OverlaySessionState { + bool toolbarVisible = false; + bool toolbarPinned = false; + bool profilerVisible = false; + bool effectParametersVisible = false; }; struct OverlayOptions { @@ -365,7 +360,8 @@ enum class ScalingError { ImportIncompatible, ExportWriteFailed, FileDialogFailed, - PassThroughUnavailable + PassThroughUnavailable, + NgxRestartRequired }; struct ScalingFlags { diff --git a/src/Magpie.Core/include/ScalingSessionLifetime.h b/src/Magpie.Core/include/ScalingSessionLifetime.h new file mode 100644 index 000000000..243880d0f --- /dev/null +++ b/src/Magpie.Core/include/ScalingSessionLifetime.h @@ -0,0 +1,20 @@ +#pragma once +#include +#include + +namespace Magpie { + +class ScalingSessionLifetime { +public: + explicit ScalingSessionLifetime(uint32_t runId) noexcept : _runId(runId) {} + + void RequestStop() noexcept { _stopping.store(true, std::memory_order_release); } + bool IsStopping() const noexcept { return _stopping.load(std::memory_order_acquire); } + bool IsCurrent(uint32_t runId) const noexcept { return _runId == runId && !IsStopping(); } + +private: + const uint32_t _runId; + std::atomic _stopping = false; +}; + +} diff --git a/src/Magpie.Core/include/ScalingWindowOwner.h b/src/Magpie.Core/include/ScalingWindowOwner.h new file mode 100644 index 000000000..533157e4b --- /dev/null +++ b/src/Magpie.Core/include/ScalingWindowOwner.h @@ -0,0 +1,13 @@ +#pragma once +#include + +namespace Magpie { + +inline bool SetScalingWindowOwner(HWND window, HWND owner) noexcept { + SetLastError(ERROR_SUCCESS); + const LONG_PTR previous = SetWindowLongPtrW(window, GWLP_HWNDPARENT, + reinterpret_cast(owner)); + return previous != 0 || GetLastError() == ERROR_SUCCESS; +} + +} diff --git a/src/Magpie/AppSettings.cpp b/src/Magpie/AppSettings.cpp index 1cebe7c08..5d9316b52 100644 --- a/src/Magpie/AppSettings.cpp +++ b/src/Magpie/AppSettings.cpp @@ -721,6 +721,10 @@ std::string AppSettings::_Serialize(const _AppSettingsData& data) { writer.Double(windowOption.hPos); writer.Key("vPos"); writer.Double(windowOption.vPos); + writer.Key("width"); + writer.Double(windowOption.width); + writer.Key("height"); + writer.Double(windowOption.height); writer.EndObject(); } writer.EndObject(); @@ -1056,6 +1060,8 @@ void AppSettings::_LoadSettings(const rapidjson::GenericObject Date: Tue, 8 Sep 2026 04:05:45 +0800 Subject: [PATCH 03/10] Trim outdated audit documents from HDR PR --- docs/diagnostics/20260906-full-chain-audit.md | 53 -- .../20260906-hdr-sdr-chain-failure-report.md | 23 - docs/diagnostics/pipeline-audit.md | 185 ----- docs/experimental/agent-a-report.md | 81 -- docs/experimental/agent-b-report.md | 88 -- docs/experimental/agent-c-report.md | 85 -- .../20260905-default-effect-groups-REVIEW.md | 152 ---- ...260905-effect-default-parameters-REVIEW.md | 766 ------------------ ...0260905-hdr-compatibility-second-REVIEW.md | 292 ------- .../20260905-naming-and-interaction-REVIEW.md | 185 ----- .../20260905-r10-dlssnr-parameter-restart.md | 36 - .../20260905-r10-vrr-latency-REVIEW.md | 91 --- .../20260905-rtss-front-edge-sync-REVIEW.md | 119 --- .../20260905-slider-default-reset-REVIEW.md | 89 -- .../20260905-toolbar-pass-through-REVIEW.md | 157 ---- ...905-v0.6.5-r1-r4-maintainability-REVIEW.md | 161 ---- ...05-v0.6.5-r7-fix-dlssfg-profiler-REVIEW.md | 66 -- ...0905-v0.6.5-r7-motion-sharing-fg-REVIEW.md | 109 --- ...0905-v0.6.5-r9-debug-first-trace-REVIEW.md | 88 -- ....6.5-r9-dlssnr-switching-stutter-REVIEW.md | 200 ----- ...60905-v0.6.5-r9-low1-performance-REVIEW.md | 128 --- ...0260905-v0.6.5-task-switch-profiler-FIX.md | 28 - ...5-v065-frame-sync-panel-residual-update.md | 25 - .../20260906-v0.6.6-capture-shutdown.md | 11 - .../reviews/20260906-v0.6.6-ngx-deadlock.md | 27 - .../20260906-v0.6.6-parameter-interaction.md | 22 - ...v0.6.1-feature4-interaction-errors-TODO.md | 80 -- ...-v0.6.1-feature5-profile-ui-polish-TODO.md | 42 - ...904-v0.6.5-amd-optical-flow-levels-TODO.md | 213 ----- .../todos/20260904-v0.6.5-r1-TODO.md | 261 ------ .../todos/20260904-v0.6.5-r2-TODO.md | 240 ------ .../todos/20260904-v0.6.5-r2-fix1-TODO.md | 187 ----- .../todos/20260904-v0.6.5-r2-fix2-TODO.md | 31 - .../todos/20260904-v0.6.5-r2-fix3-TODO.md | 25 - .../todos/20260904-v0.6.5-r2-fix4-TODO.md | 24 - .../todos/20260904-v0.6.5-r2-fix5-TODO.md | 95 --- .../todos/20260904-v0.6.5-r3-TODO.md | 216 ----- .../todos/20260904-v0.6.5-r3-fix1-TODO.md | 209 ----- .../todos/20260904-v0.6.5-r3-fix2-TODO.md | 191 ----- .../todos/20260904-v0.6.5-r3-fix3-TODO.md | 187 ----- .../todos/20260904-v0.6.5-r3-fix4-TODO.md | 174 ---- .../todos/20260904-v0.6.5-r3-re-TODO.md | 300 ------- .../todos/20260904-v0.6.5-r3-re-fix1-TODO.md | 28 - .../todos/20260904-v0.6.5-r3-re-fix2-TODO.md | 122 --- .../todos/20260904-v0.6.5-r3-re-fix4-TODO.md | 223 ----- ...4-v0.6.5-r4-optical-flow-selection-TODO.md | 486 ----------- .../todos/20260905-v0.6.5-r4-fix1-TODO.md | 237 ------ .../todos/20260905-v0.6.5-r4-fix2-TODO.md | 399 --------- .../todos/20260905-v0.6.5-r4-fix3-TODO.md | 297 ------- ...260905-v0.6.5-r5-dlssnr-resampling-TODO.md | 292 ------- .../todos/20260905-v0.6.5-r5-fix1-REVIEW.md | 56 -- .../todos/20260905-v0.6.5-r5-fix2-REVIEW.md | 273 ------- .../todos/20260905-v0.6.5-r6-TODO.md | 41 - .../20260905-v0.6.5-r6-comparison-badge.md | 29 - .../20260905-v0.6.5-r6-comparison-shortcut.md | 25 - .../todos/20260905-v0.6.5-r7-TODO.md | 52 -- .../todos/20260905-v0.6.5-r7-fix-TODO.md | 39 - .../todos/20260905-v0.6.5-r7-fix-dropdown.md | 36 - .../todos/20260905-v0.6.5-r8-TODO.md | 75 -- .../todos/20260905-v0.6.5-r8-nvof-2s.md | 43 - .../todos/20260905-v0.6.5-r9-ABCD.md | 61 -- .../todos/20260905-v0.6.5-r9-debug.md | 37 - .../todos/20260905-v0.6.5-r9-scheduling.md | 53 -- 63 files changed, 8646 deletions(-) delete mode 100644 docs/diagnostics/20260906-full-chain-audit.md delete mode 100644 docs/diagnostics/20260906-hdr-sdr-chain-failure-report.md delete mode 100644 docs/diagnostics/pipeline-audit.md delete mode 100644 docs/experimental/agent-a-report.md delete mode 100644 docs/experimental/agent-b-report.md delete mode 100644 docs/experimental/agent-c-report.md delete mode 100644 docs/experimental/reviews/20260905-default-effect-groups-REVIEW.md delete mode 100644 docs/experimental/reviews/20260905-effect-default-parameters-REVIEW.md delete mode 100644 docs/experimental/reviews/20260905-hdr-compatibility-second-REVIEW.md delete mode 100644 docs/experimental/reviews/20260905-naming-and-interaction-REVIEW.md delete mode 100644 docs/experimental/reviews/20260905-r10-dlssnr-parameter-restart.md delete mode 100644 docs/experimental/reviews/20260905-r10-vrr-latency-REVIEW.md delete mode 100644 docs/experimental/reviews/20260905-rtss-front-edge-sync-REVIEW.md delete mode 100644 docs/experimental/reviews/20260905-slider-default-reset-REVIEW.md delete mode 100644 docs/experimental/reviews/20260905-toolbar-pass-through-REVIEW.md delete mode 100644 docs/experimental/reviews/20260905-v0.6.5-r1-r4-maintainability-REVIEW.md delete mode 100644 docs/experimental/reviews/20260905-v0.6.5-r7-fix-dlssfg-profiler-REVIEW.md delete mode 100644 docs/experimental/reviews/20260905-v0.6.5-r7-motion-sharing-fg-REVIEW.md delete mode 100644 docs/experimental/reviews/20260905-v0.6.5-r9-debug-first-trace-REVIEW.md delete mode 100644 docs/experimental/reviews/20260905-v0.6.5-r9-dlssnr-switching-stutter-REVIEW.md delete mode 100644 docs/experimental/reviews/20260905-v0.6.5-r9-low1-performance-REVIEW.md delete mode 100644 docs/experimental/reviews/20260905-v0.6.5-task-switch-profiler-FIX.md delete mode 100644 docs/experimental/reviews/20260905-v065-frame-sync-panel-residual-update.md delete mode 100644 docs/experimental/reviews/20260906-v0.6.6-capture-shutdown.md delete mode 100644 docs/experimental/reviews/20260906-v0.6.6-ngx-deadlock.md delete mode 100644 docs/experimental/reviews/20260906-v0.6.6-parameter-interaction.md delete mode 100644 docs/experimental/todos/20260903-v0.6.1-feature4-interaction-errors-TODO.md delete mode 100644 docs/experimental/todos/20260903-v0.6.1-feature5-profile-ui-polish-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-amd-optical-flow-levels-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r1-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r2-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r2-fix1-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r2-fix2-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r2-fix3-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r2-fix4-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r2-fix5-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r3-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r3-fix1-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r3-fix2-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r3-fix3-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r3-fix4-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r3-re-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r3-re-fix1-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r3-re-fix2-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r3-re-fix4-TODO.md delete mode 100644 docs/experimental/todos/20260904-v0.6.5-r4-optical-flow-selection-TODO.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r4-fix1-TODO.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r4-fix2-TODO.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r4-fix3-TODO.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r5-dlssnr-resampling-TODO.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r5-fix1-REVIEW.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r5-fix2-REVIEW.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r6-TODO.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r6-comparison-badge.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r6-comparison-shortcut.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r7-TODO.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r7-fix-TODO.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r7-fix-dropdown.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r8-TODO.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r8-nvof-2s.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r9-ABCD.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r9-debug.md delete mode 100644 docs/experimental/todos/20260905-v0.6.5-r9-scheduling.md diff --git a/docs/diagnostics/20260906-full-chain-audit.md b/docs/diagnostics/20260906-full-chain-audit.md deleted file mode 100644 index 8a52bc4e6..000000000 --- a/docs/diagnostics/20260906-full-chain-audit.md +++ /dev/null @@ -1,53 +0,0 @@ -# Full-chain HDR/SDR audit - -This audit treats the current research tree as a multi-boundary rendering -system. Each boundary is required to preserve an explicit resource and color -contract. A successful SDK call does not count as an image result. - -## Boundary inventory - -1. Profile/config: `enableHdrCompatibility` is persisted and reaches - `ScalingOptions::IsHdrCompatibilityEnabled()`. -2. Capture: `FrameSourceBase` selects the capture surface and, when HDR is on, - invokes `HdrCaptureProcessor` to produce canonical FP16. -3. Canonical handoff: `Renderer` creates an `HdrFrame` around the capture - output and refreshes effect boundaries. -4. Effect boundary: `EffectDrawer` allocates route formats, converts input, - executes every production pass, converts output, and returns canonical FP16. -5. Native boundary: native backends receive route-compatible resources and - their own temporal/auxiliary contracts. -6. Publication: backend output enters shared presentation textures and - pass-through reference textures. -7. Frontend: shared textures are copied to presenter resources and drawn to the - swap chain or composition surface. -8. Export: screenshots/readback select a texture and encode it using the - matching format contract. - -## Current failure evidence - -- Runtime logs show adapter dispatch and native evaluation, while no stage - emits actual pixel statistics or a readback image. -- The research tree differs from the SDR baseline in the common drawer and - native factory. HDR-disabled behavior therefore needs an explicit regression - gate before HDR results are trusted. -- A pass-through/reference surface is a distinct publication branch. It must - use the same output color contract as the processed branch. -- Route metadata currently names formats and profiles, while the running - effect descriptor may still allocate an incompatible format internally. The - bridge must compare both values at runtime. - -## Acceptance gate for the real bridge - -For every configured effect and both HDR states, the bridge must record: - -- exact source and output `EffectDesc`; -- selected route and adapter profile; -- every texture's DXGI format, dimensions, bind flags and state; -- actual pass dispatch count; -- GPU readback statistics and saved image hash; -- native backend result when present; -- presentation texture statistics. - -Any missing stage, empty/black readback, invalid finite count, unchanged hash, -or route/descriptor mismatch is a failed row. No aggregate pass result may -hide a failed effect row. diff --git a/docs/diagnostics/20260906-hdr-sdr-chain-failure-report.md b/docs/diagnostics/20260906-hdr-sdr-chain-failure-report.md deleted file mode 100644 index 6dd9454fa..000000000 --- a/docs/diagnostics/20260906-hdr-sdr-chain-failure-report.md +++ /dev/null @@ -1,23 +0,0 @@ -# HDR / SDR chain failure report - -## Evidence - -- Runtime logs show `HDR diagnostics` and repeated adapter dispatches, so the HDR front end is active. -- Runtime logs do not show a texture readback or presentation-pixel statistic; visual output cannot be inferred from successful `EvaluateFeature` calls. -- The prior external route report produced identical values for every effect and was removed because it did not execute production code. -- `Magpie-src` still contains separate legacy `FSR2ZeroMVUpscaler`, `FSR3ZeroMVUpscaler`, and `XeSSZeroMVUpscaler` implementations. The research tree replaced these with different classes while retaining legacy effect names in configuration. - -## Confirmed regression sources - -1. The research `NativeEffectBackendFactory` routes legacy ZeroMV/Jitter/OpticalFlow effect IDs through the new SR wrappers. This changes the SDR resource and auxiliary-input contract even when HDR compatibility is disabled. -2. `EffectDrawer` adds an HDR wrapper around every effect, while the original SDR drawer must remain byte-for-byte equivalent when HDR is off. Any stale HDR boundary or `_hdrOutput` resource can therefore affect the common output pointer. -3. `PassThroughFrames` and the processed publication path have separate presentation conversions; both need an identical output contract before comparison mode is considered valid. -4. `HdrSurfaceAdapter` is a shared compute path. Its constant-buffer layout, input/output descriptors, transfer mode, and dispatch completion must be validated by GPU readback before any effect result is trusted. - -## Required correction order - -1. Restore the original SDR backend class selection and `EffectDrawer` execution path as the off-state baseline. -2. Add a production-owned test bridge inside `Magpie.Core` that runs one captured texture through the actual `EffectDrawer` and reads back each boundary texture. -3. Re-enable HDR per boundary only after the off-state bridge passes for every configured effect group. -4. Validate `HdrCaptureProcessor`, `HdrSurfaceAdapter`, pass-through, shared publication, presenter, and screenshot export as separate GPU stages. -5. Mark a matrix row passed only when the production path returns a non-black, finite image whose hash differs from the source and whose stage metadata matches the selected route. diff --git a/docs/diagnostics/pipeline-audit.md b/docs/diagnostics/pipeline-audit.md deleted file mode 100644 index e95ad66f6..000000000 --- a/docs/diagnostics/pipeline-audit.md +++ /dev/null @@ -1,185 +0,0 @@ -# Magpie HDR/SDR 全链路源码审计 - -审计范围:`Magpie-0.6.5-research` 当前源码、`Magpie-src` 基线、需求文档 -`docs/experimental/HDR_COMPATIBILITY_ARCHITECTURE.md` 与 -`HDR_MECHANICAL_IMPLEMENTATION_REQUIREMENTS.md`,以及 -`E:\Magpie-0.6.5-build\bin\x64\Release\logs\magpie.log` 的实际启动记录。 - -本报告属于 review 模式,只读分析。报告中的“修复”表示应采用的源码改动顺序,当前文件没有改动生产代码。 - -## 结论摘要 - -当前黑屏、HDR 过暗和关闭兼容后部分效果失效,来自多个相互叠加的合同断裂: - -1. HDR 捕获的数值域与后续适配器声明不一致。WGC FP16 线性 scRGB 在捕获处理器中保持相对 scRGB 数值,后续 `HdrSurfaceAdapter` 却按绝对 nits canonical 解释,并在发布阶段再次除以 80。 -2. `FrameSourceBase::GetOutput()` 在会话初始化和首帧到达前返回原始 `_output`,首帧处理后切换为 HDR processor 的 canonical texture。`Renderer::_BuildEffects()` 和 `EffectDrawer::_hdrInputSource` 在切换发生前已经保存了原始指针,效果链随后读取错误的资源语义。 -3. HDR `EffectDrawer` 把 route 的格式直接写入 `_textures[0]`/`_textures[1]`,生产 shader CSO 仍按照 `EffectDesc` 编译时的原始纹理格式生成。SRV/UAV 的实际格式因此可能与 shader 类型不匹配,输出纹理保留初始化清零值,最终呈现为黑屏。 -4. research 分支的 `NativeEffectBackendFactory` 把基线的 ZeroMV/Jitter/OpticalFlow 专用 backend 合并到普通 SR wrapper,改变了 SDR 及 HDR 关闭状态的运动、抖动、深度和辅助资源合同。 -5. 前端发布链新增了 FP16/scRGB、共享环和 pass-through 多重转换,但 `_CopySceneToTarget()` 只做 `CopyResource`,没有验证源/目标格式或输出合同;任一阶段失败后,前端仍可能提交清零的稳定纹理。 -6. 运行日志只证明了初始化、adapter dispatch、DLSSNR Evaluate 和“首帧提交”,没有任何最终纹理读回或像素统计,无法把当前图像判定为有效。 - -## 需求合同 - -需求文档规定:HDR 关闭时保持原有 SDR 捕获、效果、backend handoff、publication 和 presenter 语义;HDR 开启时,捕获之后统一形成 canonical `R16G16B16A16_FLOAT`,每个效果边界执行“canonical → route input → 生产效果 → route output → canonical”,最后只在 presentation terminal 编码到目标显示协议。捕获方式不能因 HDR 开关改变,纹理格式不能单独推断颜色含义,route 选择必须来自结构化协议,未知 route 必须显式走兼容回退并记录诊断。需求还要求 terminal frame generation 单独建模,不能把普通 FP16 链直接交给不接受 FP16/scRGB 的 backend。 - -## 1. 捕获与 canonical 生命周期 - -### 1.1 指针生命周期断裂(最高优先级) - -`FrameSourceBase.h:50-54` 的 `GetOutput()` 在 `_hdrEnabled` 且 `_hdrProcessor.GetCanonicalTexture()` 存在时返回 canonical,否则返回 `_output`。`FrameSourceBase.cpp:43-66` 在 `_Initialize()` 阶段只创建并准备 processor;真正填充 canonical 发生在 `Update()` 的 `FrameSourceBase.cpp:78-110`。因此初始化顺序是: - -```text -FrameSource::_Initialize -> raw _output -Renderer::_BuildEffects -> 读取 raw _output -FrameSource::Start -> 首帧到达 -FrameSource::Update -> 填充 canonical -FrameSource::GetOutput -> 改为 canonical -``` - -`Renderer.cpp:1569-1579` 在首帧以前取得 `inOutTexture` 和 `initialHdrFrame`;`EffectDrawer.cpp:89-105` 把传入纹理保存到裸指针 `_hdrInputSource`。首帧以后 `Renderer` 的 `_frameSource->GetOutput()` 已经是另一张 texture,而 drawer 仍从旧 `_hdrInputSource` 读取。这个状态同时破坏普通 HLSL drawer、native backend 输入、Frame Guidance 输入和 `_CreateSharedTexture()` 的输出合同。 - -### 1.2 直接修复方案 - -把 canonical texture 的对象生命周期前移到 FrameSource 初始化阶段,并让 `GetOutput()` 从初始化到销毁始终返回同一张 canonical texture;首帧以前只保持 `hdrFrameReady=false`,不允许 backend 运行。`_output` 继续作为捕获 API 的原始接收面,processor canonical 作为稳定的交付面。所有 duplicate 检查可以继续针对 `_output`,但效果链、Frame Guidance、DLSSNR 和发布链必须只使用 `GetOutput()` 返回的稳定 canonical。resize 时先重建 canonical,再以同一指针更新所有 drawer/backend,最后才恢复运行。 - -## 2. 捕获数值域与适配器数值域 - -### 2.1 当前实现的实际变换 - -`GraphicsCaptureFrameSource.cpp:79-87` 在 HDR 模式创建 FP16 `_output`,`GraphicsCaptureFrameSource.cpp:103-112` 将其声明为线性 scRGB、scene-referred、80 nit reference white。运行日志 `20:11:40.181` 也确认:`sourceFormat=10`、`sourceTransfer=Linear`、`sourceRange=SceneLinear`、`referenceWhite=80`。 - -`HdrCaptureProcessor.cpp:54-61` 当前 `ToCanonical()` 对 `inputTransfer == Linear` 直接保留采样值,对 sRGB/HLG 乘 `referenceWhiteNits / 80`,对 PQ 除以 80。这个实现形成的是“线性 scRGB、1.0 对应 80 nit”的 canonical 数值域。 - -`HdrSurfaceAdapter.cpp:118-121` 的 mode 4 又把值直接当作绝对 nits并执行 `value / 80`;`Renderer.cpp:3032-3051` 在每次 HDR 发布前调用该 mode 4。于是 WGC 的 scRGB `1.0` 被发布为 `0.0125`,白场整体变暗约 80 倍。反向的 SDR 兼容路径也按 `referenceWhiteNits / 80` 参与归一化,数值域与 capture canonical 的定义没有统一。 - -### 2.2 需求对齐方案 - -需要在架构层固定一种 canonical 语义,并让所有代码使用同一合同。按照需求文档中对 Windows scRGB 的描述,最稳定的选择是 canonical 使用线性 scRGB 相对值:`1.0 == 80 nit`,HDR 高光可大于 1。这样: - -- WGC FP16 Linear 输入直接复制到 canonical,不再做“绝对 nits”解释。 -- 8-bit sRGB 输入先做 sRGB EOTF,得到线性相对值,再写入 canonical。 -- PQ 输入先解码到 nits,再除以 80 写入 canonical。 -- `ConvertHdrToScRgb()` 变为同域复制/必要的 primaries 转换,不能再次除以 80。 -- SDRCompatible 的 tone-map 以 canonical 相对值为输入;inverse 也返回相对 scRGB 值。 -- 日志字段明确写 `canonicalValueDomain=scRGB-relative` 或采用另一套全局定义,禁止“注释写 absolute nits、shader 按 scRGB”这种混合状态。 - -当前代码和注释在 `FrameSourceBase.cpp:250-253`、`HdrSurfaceAdapter.h:42-44`、`PassThroughFrames.cpp:29-33` 使用 absolute-nits 表述,而 shader 行为使用 scRGB-relative;这些说明必须与最终统一语义同步。 - -## 3. EffectDrawer 与 shader descriptor 合同 - -### 3.1 格式替换发生在 shader 编译之后 - -`EffectCompiler.cpp:1221-1233` 使用 `EffectDesc::textures[*].format` 生成每个 pass 的 `Texture2D` 和 `RWTexture2D` 类型。CSO 已经绑定了这些编译时类型。`EffectDrawer.cpp:92-114` 在运行时却用 `HdrEffectBoundary::SelectedRoute()->inputFormat/outputFormat` 创建 `_textures[0]` 和 `_textures[1]`,这两个格式可能与 `desc.textures[0/1].format` 不同。`EffectDrawer.cpp:645-674` 随后仍按原 pass texture 索引获取 SRV/UAV,没有重编 shader,也没有验证 route 格式与 `EffectDesc` 类型兼容。 - -典型冲突是 Group-C 的 `SdrRoute()` 在 `EffectProtocolCatalogC.h:10-31` 声明输入为线性、输出为 sRGB,而实际 HLSL effect 可能是 `R8G8B8A8_UNORM` 或 FP16;route 描述本身没有把“适配器 scratch texture”和“生产 shader texture”分开。对于多通道/中间纹理效果,替换 0/1 还会影响 pass 之间的 alias 和尺寸推导。 - -### 3.2 黑屏机制 - -`EffectDrawer::Draw()` 在 `EffectDrawer.cpp:234-247` 中,如果 `PrepareHdrInput()` 失败只记录错误并直接返回;如果 pass dispatch 后 `CompleteHdrOutput()` 失败,函数仍结束,`_hdrOutput` 保持初始化清零值。`Renderer::_BackendRender()` 在 `Renderer.cpp:2886-2920` 对 native 失败也只记录后继续;最后仍调用 `_CompleteBackendFrame(effectsOutput, ...)`。这使任何边界错误都变成“发布黑帧”,而不是阻止提交并保留上一张有效帧。 - -### 3.3 修复方案 - -保留生产 `EffectDesc` 的 texture 格式给 shader 和 `_textures[]`,为 route 转换单独建立 `routeInputScratch`、`routeOutputScratch`。执行顺序固定为: - -```text -canonical input - -> adapter scratch (route format) - -> production shader/backend using its declared EffectDesc resources - -> adapter scratch output - -> canonical output -``` - -当 route 格式与生产 shader 格式相同,可以复用资源;只有经过结构化兼容检查才允许复用。每个 pass 必须验证 SRV/UAV 创建成功、尺寸和 format 与 CSO descriptor 相符。`PrepareHdrInput()` 或 `CompleteHdrOutput()` 失败时,当前帧应终止发布并保留上一帧,不能把清零的 `_hdrOutput` 交给 publication。 - -## 4. Native backend 工厂与 SDR 回归 - -`Magpie-src` 基线的 `NativeEffectBackendFactory.cpp:111-159` 使用: - -- `DLSSSRUpscaler` 只处理 `DLSS_SR` 及其显式 legacy 参数,并区分 `isJitter`、`useMotionVectors`、`useEstimatedDepth`; -- `FSR2ZeroMVUpscaler` 处理 FSR2 ZeroMV/Jitter/OpticalFlow; -- `FSR3ZeroMVUpscaler` 处理 FSR3/FSR4 ZeroMV/Jitter/OpticalFlow; -- `XeSSZeroMVUpscaler` 处理 XeSS ZeroMV/Jitter/OpticalFlow。 - -research 版 `NativeEffectBackendFactory.cpp:78-135` 将这些 ID 全部归入 `DLSSSRUpscaler`、`FSR2Upscaler`、`FSR3Upscaler` 或 `XeSSUpscaler` 的统一分支,并用 `ParseOpticalFlowRequest()` 只对部分路径设置 motion。这个改动改变了 legacy backend 的初始化参数、depth/motion fallback、jitter 开关、输出资源和时序历史,违反需求中“HDR 关闭时 SDR 行为不变”。 - -同文件 `:43-53` 还只识别 `FrameGuidance_Motion` 与 `FrameGuidance_Confidence`,基线 `:41-54` 同时支持 `Depth` 和 `DepthResidual`。这会让诊断效果本身失效,进一步降低对 Frame Guidance 和 DLSSNR 的可观测性。 - -修复顺序:先完整恢复基线 include 和分派分支,保持所有 legacy 类的原始构造参数;HDR 适配器通过 `NativeEffectBackend` 外层 context 接入,不能替换 backend 类型。对每个 native 类分别声明支持的 route,未验证的 HDR route 走显式 SDRCompatible scratch,SDR 关闭时完全跳过 HDR context。 - -### 4.1 Bicubic 追加后的越界 - -`Renderer::_BuildEffects()` 在 `Renderer.cpp:1655-1669` 追加 Bicubic 后调用 `_UpdateHdrEffectBoundaryContexts()`。该函数在 `Renderer.cpp:1714-1724` 遍历 `_effectDrawers.size()`,却用同一索引读取 `_runtimeEffectOptions[i]`。追加 Bicubic 时 drawer 数量比 runtime option 数量多一项,最后一次迭代越过 `_runtimeEffectOptions` 末尾。`_ResizeEffects()` 在 `Renderer.cpp:2088-2134` 也会在追加/移除 Bicubic 后重新调用该函数。此越界足以损坏 route context、backend 指针或后续发布状态,并直接解释“有缩放比例/窗口尺寸时黑屏”的不稳定表现。修复方式是按 runtime effect 数量更新真实效果,另为 Bicubic 构造明确的独立 boundary,或让 Bicubic 使用其 drawer 自带的 canonical context,禁止用 runtime option 数组越界索引。 - -### 4.2 Group-C R8 route 的 transfer 字段方向错误 - -`EffectProtocolCatalogC.h:10-31` 的 `SdrRoute()` 将 `inputTransfer=Linear/inputRange=SceneLinear`、`outputTransfer=SRGB` 写入所有 R8 效果。`EffectDrawer::PrepareHdrInput()` 实际把 `route->inputTransfer` 作为 `ConvertHdrToSdr(..., outputTransfer)` 参数 (`EffectDrawer.cpp:506-514`),所以这些效果收到线性 R8 值;`CompleteHdrOutput()` 又把 `route->outputTransfer` 作为 `ConvertSdrToHdr(..., inputTransfer)` (`EffectDrawer.cpp:534-542`),按 sRGB 解码同一线性值,造成二次 EOTF、过暗和颜色偏移。R8 SDR-compatible route 的输入/输出 transfer 应描述 backend 真实的编码,通常为 `SRGB/SRGB`,range 为 `Full/Full`;route 字段方向和调用语义需要统一后再推广到 Group-A/B/C。 - -## 5. Publication、frontend 和 presenter - -### 5.1 新增的多重颜色边界 - -research 版在 `Renderer.cpp:2310-2426` 将共享 publication texture 改为 HDR 时 FP16,在 `Renderer.cpp:3032-3051` 先把 canonical 送入 `_hdrPresentationTexture`,再复制到 shared texture;`Renderer.cpp:617-754` 前端又复制到 stable base/presented base;`Renderer.cpp:757-774` 最后直接复制到 presenter frame。`AdaptivePresenter.cpp:33-84` 和 `:416-424` 也把 swap chain/DirectComposition surface 改成 FP16,并设置 scRGB 色彩空间。 - -`_CopySceneToTarget()` 没有检查 `scene`、`target` 的 format、尺寸、sample count、bind flags,也没有处理“只允许同格式 CopyResource”的失败结果。`CopyResource` 的 HRESULT 不返回给调用者,失败后 presenter 仍可能提交刚刚清空的目标。 - -### 5.2 Pass-through 分支 - -`PassThroughFrames.cpp:11-35` 的 reference shader按 output 尺寸采样 input,并在 HDR 时执行 `/80`。`InitializeBackend()` 以 `outputDesc` 决定 reference 尺寸 (`:65-71`),但 input 可能是捕获尺寸、output 可能是最终效果尺寸;当两者不同,采样 UV 与边界不一致。`ReferenceConstants` 的 exposure、sdrWhiteNits、shoulder 在 shader 中没有使用 (`:94-104`),导致诊断参数与实际行为脱离。 - -需求要求 pass-through 与 processed publication 使用同一输出颜色合同。当前 reference branch 自己做了一套 `/80`,processed branch 另做 canonical→scRGB,两个分支无法保证逐点可比。 - -修复方案:pass-through 直接保存 canonical stable frame;在唯一的 presentation encoder 处与 processed frame 使用相同的颜色转换。reference shader只负责尺寸/滤波和 alpha,禁止重复颜色缩放。输入、输出尺寸不同时采用明确的 source-to-destination sampling contract,并记录 source/destination descriptor。 - -### 5.3 Screenshot/export 也跨越了错误的格式边界 - -`Renderer::_TakeScreenshotImpl()` 对 effect output 使用 `_effectDrawers[effectIdx].GetOutputTexture()` (`Renderer.cpp:3272-3275`),而 HDR drawer 的 canonical 对外输出是 `GetExternalOutputTexture()`;随后函数把 effect descriptor 的原始 `EffectIntermediateTextureFormat` 当作读回格式。显示图导出分支还把 frontend FP16/scRGB texture 按 `R8G8B8A8_UNORM`/PNG 语义处理 (`Renderer.cpp:3263-3271`)。这会把 HDR 纹理以错误的通道/数值域编码,产生黑图或过暗图,即使 presenter 本身已经提交成功。导出必须先选择 canonical/presentation domain,再执行一次明确的 HDR-to-SDR PNG 编码;中间 pass 才使用其真实 descriptor 格式。 - -## 6. 运行日志证据 - -`magpie.log` 的 20:11:40 会话记录: - -```text -sourceFormat=10 -sourceTransfer=Linear -sourceRange=SceneLinear -sourceReferenceWhiteNits=80 -canonicalFormat=10 -selectedAdapterProfile=DirectFP16 -selectedRouteId=(not set) -``` - -随后每帧出现: - -```text -HDR adapter dispatch: mode=0 input=10 output=28 -HDR adapter dispatch: mode=0 input=28 output=10 -HDR adapter dispatch: mode=4 input=10 output=10 -DLSSNR STATUS ... sourceFormat=28 ... experimentalHdrPath=false -First frontend frame submitted by regular renderer -``` - -这组日志证明 WGC 首帧、DLSSNR Evaluate 和 presenter 首次提交都发生了;`selectedRouteId=(not set)` 说明 capture diagnostics 没有关联具体效果 route;全日志没有最终 publication texture 的 min/max/mean、NaN/Inf、读回 hash 或截图路径。`First frontend frame submitted` 只表示 API 提交成功,不能证明像素内容有效。 - -## 7. 点对点修复顺序 - -1. **恢复 SDR 基线**:恢复 `NativeEffectBackendFactory` 的 legacy backend 类和 Diagnostics Depth/DepthResidual 分支;让 HDR 关闭时 `EffectDrawer`、Renderer publication、presenter 与 `Magpie-src` 保持原始行为。 -2. **稳定 FrameSource 输出指针**:canonical texture 在初始化时创建并从始至终作为 `GetOutput()`;首帧 ready 之前禁止 backend draw。 -3. **统一 canonical 数值域**:在代码、shader、注释、metadata 和日志中选择唯一的 scRGB-relative 或 absolute-nits 语义;本审计建议采用 `1.0 == 80 nit` 的 scRGB-relative 语义,以匹配 Windows WGC/Advanced Color。 -4. **拆分 route scratch 与 production textures**:route 转换资源独立于 `EffectDesc` 生产资源,禁止运行时改写已编译 shader 的 texture format。 -5. **统一唯一 presentation encoder**:processed 与 pass-through 都先保留 canonical,只有 publication→presenter 边界做一次 scRGB/HDR10 编码;所有 Copy/Present 调用检查 HRESULT 和 descriptor。 -6. **失败帧隔离**:任一 effect/backend/adapter/publish 阶段失败时丢弃当前帧并保留上一张有效帧,禁止清零纹理继续发布。 -7. **真实生产 readback bridge**:bridge 直接调用生产 `FrameSourceBase`、`EffectDrawer`、native backend、publication 和 presenter 资源;逐阶段保存 GPU readback,记录 format/size/finite/min/max/mean/hash。没有完整阶段证据的矩阵行保持失败状态。 - -## 8. 验收条件 - -每个效果组、HDR 开关两种状态都必须满足: - -- capture method 与需求一致,首帧 `frameId=1` 使用真实捕获; -- effect shader CSO 的 descriptor 与实际 SRV/UAV format/size 一致; -- native backend 类型、辅助输入、jitter/depth/motion 语义与基线或已验证 route 一致; -- canonical 输入/输出在每个边界保持统一数值域,alpha 明确; -- publication、pass-through、presenter 只执行一次目标颜色编码; -- GPU readback 全部 finite、非全黑、没有 NaN/Inf,输出 hash 与输入 hash 不同; -- adapter、backend、publication、presenter 每一层都有实际执行记录; -- HDR 关闭矩阵与基线图像及路径通过回归比较;HDR 开启矩阵覆盖 SDR-compatible、DirectFP16、BoundedHDR、ConditionalFP16 和 terminal 分支; -- 任意一行失败都单独保留失败证据,不能用 aggregate 初始化成功代替图片通过。 diff --git a/docs/experimental/agent-a-report.md b/docs/experimental/agent-a-report.md deleted file mode 100644 index 0f36bd02b..000000000 --- a/docs/experimental/agent-a-report.md +++ /dev/null @@ -1,81 +0,0 @@ -# Effect protocol audit — group A - -审计范围:Anime4K、CAS、CRT、CuNNy、CuNNy2、Diagnostics、FSRCNNX、FXAA、MLAA。结论同时记录公开证据矩阵与当前 Magpie shader 的可观察事实;shader 中没有声明的输入/输出格式保持 `Unknown`。`//!CAPABILITY FP16` 仅代表编译/数学能力,HDR 传输协议仍需独立证据。 - -## 总结 - -| 效果 | 实际 shader 输入/输出 | 明确的辅助纹理 | 数值/transfer/alpha 事实 | HDR profile | 代码缺口 | -|---|---|---|---|---|---| -| Anime4K | `INPUT`/`OUTPUT` 未声明格式;大多数 CNN 变体中间面 `R16G16B16A16_FLOAT`;`Thin_HQ` 的梯度面 `R16G16_FLOAT` | CNN 变体 2–16 个 FP16 中间面;无外部纹理 | CNN 以正负分支 `max(x,0)`/`max(-x,0)` 工作;大量最终写入 `MF4(...,1)`,alpha 固定 1;Denoise 仅 RGB 并固定 alpha 1;Thin_HQ 直接采样 RGBA 并保留 alpha | `Unknown`,generic SDR fallback | 缺少统一输入/输出格式、transfer、原色域、范围和 alpha 契约;当前路由应保持 generic SDR fallback | -| CAS | `INPUT`/`OUTPUT` 未声明格式;`CAS.hlsl` 有 `//!CAPABILITY FP16`,`CAS_Scaling.hlsl` 无 | 无 | 读取 `.rgb`;`saturate` 作用于邻域振幅和最终 RGB;所有输出写入 alpha 1;shader 没有 transfer 解码/编码 | `SDRCompatible`(现有证据允许 FP16 参考实现,当前 Magpie shader 仍是有界 RGB 路径) | 需补充具体 Magpie 资源格式、线性/伽马入口、峰值范围和 alpha 规则;当前 FP16 capability 不足以建立 HDR-native 路径 | -| CRT | 除 `GTU_v050.tex1` 明确为 `R16G16B16A16_FLOAT` 外,`INPUT`/`OUTPUT` 未声明格式 | GTU `tex1` FP16;其余无 | Easymode/Geom/Hyllian/Lottes 使用 gamma `pow`;GTU RGB 多处 `clamp(...,0,1)`,最终 alpha 1;Hyllian 输出 RGB clamp 到 `[0,1]` 后 gamma 输出 | `Unknown`,generic SDR fallback | 每个 preset 都是独立协议;gamma 参数、clamp、mask/scanline 对 HDR 的行为尚未建立;GTU 仅有内部 FP16 证据 | -| CuNNy | `INPUT`/`OUTPUT` 未声明格式,2x–8x 输出尺寸为 `INPUT_WIDTH/HEIGHT * 2` | 所有 `t0..t7` 中间面明确 `R8G8B8A8_SNORM` | RGB 转 YUV/网络张量;ReLU 正负分支;NVL 输出对 Y 分量使用 `saturate`,写回 RGB 后 alpha 固定 1 | `Unknown`,generic SDR fallback | 现有矩阵把格式记为 unspecified;实际内部 SNORM 是可靠本地事实,但模型归一化区间、输入格式、transfer、alpha 尚未形成 HDR 契约 | -| CuNNy2 | `INPUT`/`OUTPUT` 未声明格式,全部输出尺寸为 2x | 所有 `T0..T15` 明确 `R8G8B8A8_UNORM` | RGB→YUV,网络层使用 `max(x,0)`;out-shuffle 对 Y 使用 `saturate`;输出 alpha 固定 1 | `Unknown`,generic SDR fallback | 公开矩阵没有独立 CuNNy2 上游;本地 shader 明确 UNORM 中间面和 `[0,1]` 型输出,仍缺模型归一化/transfer/峰值证据 | -| Diagnostics | `INPUT`/`OUTPUT` 未声明格式,同尺寸 | 无 | Confidence 直接 `return INPUT.SampleLevel(...)`;Motion 同样直接采样返回;shader 层 alpha 保留输入值;显示增益参数仅在对应 native/backend 侧生效 | `Unknown`,generic SDR fallback | 诊断输入的真实来源(MV/置信度资源格式、范围)由 native backend 决定;通用 shader 的 HDR 图像语义仍待定义 | -| FSRCNNX | `INPUT`/`OUTPUT` 未声明格式,输出尺寸 2x;中间面明确 `R16G16B16A16_FLOAT` | `featureMap1/2`、`tex1..tex4` 均 FP16 | 先以 `0.299/0.587/0.114` 取 luma;多层 leaky-ReLU 使用 `max(x,0)+C*min(x,0)`;最终 RGB 加回输入 RGB,alpha 固定 1 | `Unknown`,generic SDR fallback | FP16 中间面没有给出输入/输出色彩协议;luma 权重、训练归一化、负值/超 1 值范围、alpha 恢复均待验证 | -| FXAA | `INPUT`/`OUTPUT` 未声明格式,同尺寸;Linear sampler | 无 | FXAA luma 估计 `rgb.y*(0.587/0.299)+rgb.x`,注释范围约 `0..2.9632`;最终 RGB 经邻域插值,wrapper 写 alpha 1;无 transfer 处理 | `Unknown`,generic SDR fallback | 当前实现缺统一格式、范围、transfer、alpha 透传契约;阈值是显示相关量,直接场景线性 HDR 需验证 | -| MLAA | `INPUT`/`OUTPUT` 未声明格式,同尺寸;`edgeMask`=`R8G8_UNORM`;`edgeCounts`=`R8G8B8A8_UNORM` | `edgeMask`、`edgeCounts` | luma 为 `dot(rgb,[0.2126,0.7152,0.0722])`;edge mask 二值写入 UNORM;edge count 量化为 `0..15 / 15`;混合在平方域后 `sqrt`;最终返回原 `color`,alpha 保留 | `Unknown`,generic SDR fallback | 辅助面是明确 SDR/UNORM 边界;主面格式、transfer、HDR 阈值和 alpha 语义缺少公共契约 | - -## 逐项证据 - -### Anime4K - -实际文件集合为 `src/Effects/Anime4K/*.hlsl`。`Anime4K_Restore_*`、`Anime4K_Upscale_*`、`Anime4K_Upscale_Denoise_*`、`Anime4K_3D_*`、`Anime4K_Upscale_GAN_*` 均带 `//!CAPABILITY FP16`,并将网络中间面声明为 `R16G16B16A16_FLOAT`;典型声明位于 `Anime4K_Restore_L.hlsl:20-41`。`Anime4K_Thin_HQ.hlsl` 的梯度纹理为 `R16G16_FLOAT`,主输入输出仍未声明格式。多数网络最终写入 `MF4(result, 1)` 或 `MF4(result + INPUT.rgb, 1)`,例如 `Anime4K_Restore_L.hlsl:690`、`Anime4K_Upscale_L.hlsl:647-659`,因此 alpha 由 shader 固定为 1。三个 bilateral denoise 变体只采集 RGB,最终 `float4(...,1)`,证据见 `Anime4K_Denoise_Bilateral_Mean.hlsl:68-83,121`;`Thin_HQ` 的 warp pass 直接写入 `INPUT.SampleLevel`,这是该变体保留 alpha 的局部事实。数值上,CNN 激活显式拆分正负分量,`max(x,0)` 与 `max(-x,0)` 让网络内部允许负特征,同时没有输入 transfer、峰值或 scene-linear 标注。公开证据矩阵 `HDR_PROTOCOL_EVIDENCE.md:34,67-116` 仅证明上游是 Anime4K 用户 shader,并把公共 I/O、范围、色彩空间、HDR 记为缺失。当前应保持 `Unknown`,由通用 SDR fallback 进入效果边界;FP16 中间面可作为资源分配事实记录,现有证据不足以升级为 HDR-native profile。代码缺口是:按具体 preset 记录主面格式、采样 transfer、可接受负值/超 1 值、alpha policy,并在有测试证据后再开放 `ConditionalFP16`。 - -### CAS - -`CAS.hlsl` 和 `CAS_Scaling.hlsl` 都是单 pass、`INPUT`→`OUTPUT`,无 `//!FORMAT`;前者带 `//!CAPABILITY FP16`(`CAS.hlsl:3-5`),后者没有。两者只读取 RGB,最终输出多处写成 `MF4(...,1)`,例如 `CAS.hlsl:262-284` 与 `CAS_Scaling.hlsl:661-683`,alpha 固定为 1。CAS 核心对邻域振幅和最终像素使用 `saturate`,如 `CAS.hlsl:115-121,137-139`,实际 shader 因而具有 `[0,1]` 型 RGB 钳制行为;代码中没有 sRGB/PQ/HLG 解码或编码。公开证据矩阵 `HDR_PROTOCOL_EVIDENCE.md:35,118-197` 记录 AMD 参考 CLI 的 `R8G8B8A8_UNORM` 与 `R16G16B16A16_FLOAT`,以及官方文档的线性输入说明;这些证据属于参考实现/SDK 层,当前 Magpie shader 的资源声明仍为空。现有 catalog 的 `SDRCompatible` 决策与本地实现一致:FP16 编译能力和参考 CLI FP16 选项支持后续实验方向,当前 shader 的饱和/alpha 规则尚未构成 HDR-native 保障。缺口集中在具体 Magpie 资源分配、输入输出 transfer、峰值范围、alpha 透传策略;需要独立的 FP16 无钳制测试后才可调整 profile。 - -### CRT - -文件集合为 `CRT_Easymode.hlsl`、`CRT_Geom.hlsl`、`CRT_Hyllian.hlsl`、`CRT_Lottes.hlsl`、`GTU_v050.hlsl`。除 GTU 的 `tex1` 明确 `//!FORMAT R16G16B16A16_FLOAT`(`GTU_v050.hlsl:80-90`)外,主 `INPUT`、`OUTPUT` 和其他 preset 都无格式声明。Easymode、Geom、Hyllian、Lottes 都对输入做 gamma `pow`,参数范围来自 shader:Easymode `gammaInput 0.1..5`、`gammaOutput 0.1..5`(`CRT_Easymode.hlsl:141-154`),Geom `Target Gamma/Monitor Gamma 0.1..5`(`CRT_Geom.hlsl:30-43`),Hyllian `Input/Output Gamma 0..5`(`CRT_Hyllian.hlsl:50-63`)。Hyllian 在 `CRT_Hyllian.hlsl:221-239,253-255` 对重建颜色执行 `[0,1]` clamp 后再 gamma 输出,并固定 alpha 1;Geom 在 `CRT_Geom.hlsl:327-371` 进行 clamp、gamma 和固定 alpha;Lottes 以 `pow(...,2.2)` 取样并在 `CRT_Lottes.hlsl:331` 以 `pow(...,1/2.2)` 输出,alpha 固定 1。GTU 将 RGB 转 YIQ 后在 `GTU_v050.hlsl:139,149,152` clamp 到 `[0,1]`,最终 alpha 1。公开矩阵 `HDR_PROTOCOL_EVIDENCE.md:36,199-247` 将 CRT 记为无唯一上游协议;本地代码显示每个 preset 都有独立 gamma、clamp 和 mask/scanline 约束。profile 应保持 `Unknown` 与 generic SDR fallback;只有建立逐 preset 的范围、transfer、alpha 和峰值测试后,才有条件使用 `ConditionalFP16`。 - -### CuNNy - -所有 `src/Effects/CuNNy/CuNNy-*.hlsl` 变体带 `//!CAPABILITY FP16`,主输入和输出没有 `//!FORMAT`,输出尺寸为 2x。网络中间面统一明确为 `R8G8B8A8_SNORM`,典型 `CuNNy-2x4C-NVL.hlsl:46-56`;大模型的 `t0..t7` 也遵循同一格式。代码先把输入 RGB 投影到 YUV/特征张量,卷积层交替执行正负 ReLU 分支;最终 NVL out-shuffle 对 Y 分量使用 `saturate`,并以 `MF4(...,1.0)` 写回,典型 `CuNNy-2x4C-NVL.hlsl` 的 `l0` 与 out-shuffle。SNORM 中间面意味着实际张量边界约为 `[-1,1]` 的有符号 8 位归一化存储;此处是 DXGI 资源声明事实,模型输入归一化和输出 denormalization 仍缺少独立说明。公开矩阵 `HDR_PROTOCOL_EVIDENCE.md:37,249-297` 只确认 Blinue/CuNNy 上游可导出 mpv/Magpie shader,未枚举格式。profile 保持 `Unknown`,generic SDR fallback 负责把 canonical FP16 映射到现有 bounded 路径;FP16 capability 仅覆盖算术/编译,SNORM 张量协议仍需单独记录。缺口包括:主面格式、YUV 矩阵的 transfer 假设、模型训练范围、alpha 恢复、SNORM 溢出/钳制行为。 - -### CuNNy2 - -所有 `src/Effects/CuNNy2/*.hlsl` 变体带 `//!CAPABILITY FP16`,主输入输出未声明格式,输出尺寸为 2x。中间纹理 `T0..T15` 明确使用 `R8G8B8A8_UNORM`,例如 `CuNNy-3x12-NVL.hlsl:47-80`;快速变体同样使用 UNORM。网络层采用 `max(r,0)`,out-shuffle 以 `saturate(yuv.r + r0.*)` 限制 Y,再通过 YUV→RGB 写回并固定 alpha 1,典型 `CuNNy-fast-NVL.hlsl:349-354,392-402`。因此内部张量和最终输出都表现出 bounded `[0,1]` 倾向,且 alpha 由 shader 重建为不透明。公开矩阵 `HDR_PROTOCOL_EVIDENCE.md:38,299-347` 没有找到独立 CuNNy2 上游协议;本地 shader 是当前最可靠的实现证据。profile 保持 `Unknown` 与 generic SDR fallback,原因是模型归一化、输入 transfer、峰值和颜色原色域均未定义。缺口是把每个模型尺寸/变体的归一化常量、主面格式、alpha policy 和实际输出范围写入结构化 route;在此之前不启用 DirectFP16。 - -### Diagnostics - -`FrameGuidance_Confidence.hlsl` 与 `FrameGuidance_Motion.hlsl` 都只有 `INPUT`、同尺寸 `OUTPUT`、Linear sampler 和单个 PS pass;没有 `//!FORMAT` 或辅助纹理声明。Confidence 的 `Pass1` 是 `return INPUT.SampleLevel(sam, pos, 0);`(`FrameGuidance_Confidence.hlsl:14-18`),Motion 同样直接返回输入(`FrameGuidance_Motion.hlsl:22-26`),所以 shader 层 alpha 保留输入值。Motion 的 `Display Gain` 参数范围为 `0.005..1`(`FrameGuidance_Motion.hlsl:5-10`);其真正的 motion/confidence 资源格式由 `Magpie.Core/FrameGuidanceDiagnostics` native backend 提供,通用 shader 文件没有声明。公开矩阵 `HDR_PROTOCOL_EVIDENCE.md:39,349-397` 将 Diagnostics 定义为通用诊断名,缺少上游 I/O 协议。profile 保持 `Unknown`,generic SDR fallback 只用于把诊断结果留在安全的效果边界;诊断输出不得隐式当作普通 HDR 图像继续传播。缺口是记录 native backend 的输入资源类型、向量/置信度范围、显示增益是否改变 alpha,以及 raw-capture tap 与 canonical FP16 的对应关系。 - -### FSRCNNX - -`FSRCNNX.hlsl` 与 `FSRCNNX_LineArt.hlsl` 带 `//!CAPABILITY FP16`;主 `INPUT`/`OUTPUT` 无格式,输出尺寸为 2x;`featureMap1/2`、`tex1..tex4` 明确为 `R16G16B16A16_FLOAT`,见 `FSRCNNX.hlsl:21-54`。第一 pass 以 `GetLuma` 的 `0.299/0.587/0.114` 权重从 RGB 提取 luma(`FSRCNNX.hlsl:79-99`),映射层用 `max(target,0)+C*min(target,0)` 的 leaky-ReLU 形式,保留负特征。最终 pass 将网络 RGB 加回输入 RGB 并以 `MF4(...,1)` 写出,见 `FSRCNNX.hlsl:644-660`;alpha 固定为 1。公开矩阵 `HDR_PROTOCOL_EVIDENCE.md:47,682-730` 只确认 FSRCNNX 是 mpv/社区 shader 变体,缺乏独立 SDK 格式和 HDR 协议。FP16 中间面是实现事实,输入 transfer、训练归一化、负值/超 1 值接受区间、alpha 复原均无证据,因此 profile 保持 `Unknown` 与 generic SDR fallback。缺口是按模型变体验证输入/输出格式和范围,并记录 luma 投影是否在线性 RGB 上执行。 - -### FXAA - -`FXAA_High.hlsl`、`FXAA_Medium.hlsl`、`FXAA_Ultra.hlsl` 都只有未格式化的 `INPUT`/`OUTPUT`、Linear sampler 和一个 pass;没有辅助纹理或 FP16 capability。公共实现 `FXAA.hlsli:70-75` 明确 luma 估计式,并写出估计范围约 `0.0..2.963210702`;阈值、range、subpixel blend 全部基于该 luma。wrapper 最终写 `float4(FXAA(...),1)`,见三份 preset 的末尾,因此 alpha 固定为 1。算法没有 transfer 解码/编码,直接把采样值当作阈值运算输入;场景线性 HDR 下亮度比例会改变 edge threshold 行为。公开矩阵 `HDR_PROTOCOL_EVIDENCE.md:48,732-794` 记录 FXAA 的引擎/社区实现与缺少统一格式契约。profile 保持 `Unknown`,generic SDR fallback 负责 bounded 输入;缺口是建立具体格式、transfer、alpha、阈值尺度和 FP16 未钳制测试,完成后再考虑 `SDRCompatible` 或 `ConditionalFP16` 的显式 route。 - -### MLAA - -`MLAA.hlsl` 主 `INPUT`/`OUTPUT` 未声明格式;`edgeMask` 明确 `R8G8_UNORM`,`edgeCounts` 明确 `R8G8B8A8_UNORM`(`MLAA.hlsl:45-63`)。Pass 1 从 RGB 计算 `dot(rgb,[0.2126,0.7152,0.0722])`,以 `threshold 0.02..0.30` 生成二通道 edge mask(`MLAA.hlsl:29-42,69-102`);Pass 2 把边长度量化为 `0..15/15` 并写入 UNORM(`MLAA.hlsl:105-170`)。Pass 3 在平方域混合后开方,最终返回完整 `color`,所以 alpha 保留输入值(`MLAA.hlsl:228-265`)。主面没有 transfer 说明,辅助 UNORM 面构成明确的 bounded SDR 边界;HDR 场景线性亮度会直接影响 edge threshold。公开矩阵 `HDR_PROTOCOL_EVIDENCE.md:49,796-844` 只确认 MLAA 算法族,没有统一 SDK 格式。profile 保持 `Unknown` 与 generic SDR fallback;缺口是主面格式、transfer、HDR 阈值尺度、alpha 预期和辅助资源重建策略。 - -## Magpie.Core HDR 边界核对 - -当前公共边界已提供 canonical `DXGI_FORMAT_R16G16B16A16_FLOAT`:`HdrAdapterDispatcher.h:16-24`。`EffectDrawer::_UsesDirectHdrPath` 只在 route profile 为 `DirectFP16` 或 `ConditionalFP16` 且输入/输出资源均为 FP16 时返回 true,见 `EffectDrawer.cpp:433-449`。其余 profile 在 `PrepareHdrInput` 走 `HdrSurfaceAdapter::ConvertHdrToSdr`,transfer 未声明时默认 SRGB(`EffectDrawer.cpp:467-486`);输出阶段对应 `ConvertSdrToHdr`,见 `EffectDrawer.cpp:490-509`。`HdrAdapterDispatcher.cpp:73-89` 对 `Unknown` 选择 `canonicalFP16 -> SdrCompatibleFallback -> backend -> SdrToHdr -> canonicalFP16`,未声明 alpha 时设置 `ForceOpaque`。这些接口足以承载本组的声明性 route;本次没有修改 Renderer.cpp、EffectDrawer.cpp、Hdr* 通用组件、配置 UI 或其他分组文件。当前缺口全部属于效果级事实:每个 preset/模型缺少主面格式、transfer、范围、alpha 和 HDR evidence 字段,且现有 route 表仍需把本报告的 Unknown/SDR fallback 决策录入对应效果声明后才能被 dispatcher 读取。 - -## 修改与验证 - -修改文件:仅 `docs/experimental/agent-a-report.md`。 - -静态验证命令: - -```powershell -$repo = 'D:\AI\workspace\Magpie-0.6.5-research' -rg -n '^//!FORMAT|^//!CAPABILITY|^//!PASS|^//!IN |^//!OUT |Texture2D|SamplerState|saturate|clamp\(|pow\(|MF4\(.*1\)' ` - "$repo\src\Effects\Anime4K" "$repo\src\Effects\CAS" "$repo\src\Effects\CRT" ` - "$repo\src\Effects\CuNNy" "$repo\src\Effects\CuNNy2" "$repo\src\Effects\Diagnostics" ` - "$repo\src\Effects\FSRCNNX" "$repo\src\Effects\FXAA" "$repo\src\Effects\MLAA" - -rg -n 'CanonicalFormat|_UsesDirectHdrPath|ConvertHdrToSdr|ConvertSdrToHdr|HdrAdapterProfile::Unknown|ForceOpaque' ` - "$repo\src\Magpie.Core\HdrAdapterDispatcher.h" ` - "$repo\src\Magpie.Core\HdrAdapterDispatcher.cpp" ` - "$repo\src\Magpie.Core\EffectDrawer.cpp" - -Get-Content -Raw "$repo\docs\experimental\agent-a-report.md" | ` - Select-String 'Anime4K|CAS|CRT|CuNNy|CuNNy2|Diagnostics|FSRCNNX|FXAA|MLAA' -``` diff --git a/docs/experimental/agent-b-report.md b/docs/experimental/agent-b-report.md deleted file mode 100644 index 76c0c5366..000000000 --- a/docs/experimental/agent-b-report.md +++ /dev/null @@ -1,88 +0,0 @@ -# Agent B:DLSS / FSR / NIS / Optical Flow 效果协议核对 - -本报告覆盖 DLSS、DLSSFG、DLSSNR、FSR1、FSR2、FSR3 SR、FSR3 FG、FSR4、NIS、AMD FidelityFX Optical Flow。证据分为三层:官方协议材料、`Magpie-0.6.5-research` 当前 native backend、`DLSSNR-HDR-Experiments` 的真实运行日志。修改范围仅为本文件;Renderer、EffectDrawer、Hdr 通用组件、配置 UI 和 CMake 保持原状。 - -## 先给结论 - -DLSS、FSR2、FSR3 SR、FSR4、NIS 的官方资料都给出了可用于 HDR 的路径,但它们对 transfer function、exposure、depth、motion vector、资源状态的要求各自独立。当前 Magpie 的 SR backend 已经有一套可工作的 D3D11/D3D12 资源桥接,核心缺口集中在颜色格式识别、HDR 开关、exposure 传递和真实 depth。FSR1 与 NVIDIA Optical Flow 的公开协议偏向 `[0,1]`/8-bit SDR,适合走 SDR-compatible 辅助路径。DLSSFG 与 FSR3 FG 都属于呈现终端,backbuffer、HUD-less/UI、MV、depth、光流以及 frame lifetime 必须一起建模;单独把 FP16 颜色送入 backend 时,帧生成仍缺少这些配套资源与生命周期条件。 - -DLSSNR 的实验结论更窄。`results/hdr-scales` 使用 RTX 4070、驱动 32.0.16.1656、DLSSNR DLL 310.8.0.0、`DXGI_FORMAT_R16G16B16A16_FLOAT`、零运动和常量深度完成了真实 Feature 18 Create/Evaluate。`scale=1` 与基线的 MAE 为 0.01182021、PSNR 31.652 dB;`scale=2` 的 MAE 为 0.03846685、PSNR 18.109 dB;`scale=4.5` 的 MAE 为 0.20317990、PSNR 5.152 dB,最大差达到 3.5,并伴随红色/高光爆炸。实验只证明输入值域放大与输出失真之间存在强相关,资料没有给出可复核的 DLSSNR 归一化公式、参考白、峰值或逆变换参数。实现层面保留 `scale=1/2/4.5` 作为可配置实验轴,4.5 的含义保持为实验值,暂不解释为已确认的原生 HDR 白点;现有 bounded-HDR 文案继续单独标注为 NVIDIA 官方资料之外的本地协议。 - -## 十项协议矩阵 - -| 效果 | 官方输入/输出边界 | 当前 native backend | 主要缺口 | 可落地适配 | -|---|---|---|---|---| -| DLSS SR | Color 输入/输出为 API 支持格式;MV `RG16_FLOAT/RG32_FLOAT`;depth 单通道或 depth-stencil;exposure 1x1,`R16F` 优先;HDR 通过 `IsHDR`,输入为线性 HDR | `DLSSSRUpscaler` 创建零 MV `R16G16_FLOAT`、零 depth `R32_FLOAT`、bias mask `R8_UNORM`;创建标志含 AutoExposure;Evaluate 固定 jitter=0、MV scale=1、pre-exposure=1、exposure scale=1 | 没有从 canonical HDR 传入 `IsHDR`/exposure;depth 默认 zero-contract;颜色格式/色域没有协议字段 | 新增 DLSS 专属 HDR 参数桥:输入格式/transfer、HDR flag、1x1 exposure、真实 depth;保持现有 zero fallback,并记录 fallback 状态 | -| DLSSFG | backbuffer/final color;输出与 backbuffer 同格式;MV/depth 遵循 DLSS SR;HUD-less/UI 必须同尺寸、格式、色彩空间;有 HDR 开关 | `DLSSFrameGenerator` 从输入纹理复制格式,`NativeBackbufferFormat=inputDesc.Format`;zero MV/depth;MV scale=1;`depthInverted=false`;HUDLess/UI 等资源统一标记 never-provided | backbuffer 格式传递存在,HDR 色彩元数据、HUD-less/UI、真实 depth、HDR 开关没有完整接线 | 保留 PresentationTerminal;增加 backbuffer protocol、HDR flag、HUD-less/UI 生命周期和真实 depth/MV 适配,完成前不宣称 HDR 完整支持 | -| DLSSNR | NVIDIA 公开资料没有稳定的 Feature 18 HDR/FP16 色彩契约;本地实验只覆盖 FP16 值域探测 | `DLSSNRFilter` 强制同分辨率;输入仅 `R8G8B8A8_UNORM/B8G8R8A8_UNORM`,输出仅 `R8G8B8A8_UNORM`;NGX 颜色共享面为 RGBA8;创建 `Scale=1`、`ScalingRatio=1`;MV scale=1、depth inverted=1 | 当前路径是 SDR RGBA8;实验 FP16 路径与 Magpie 路径分离;缺少可验证 normalize/inverse、HDR metadata、exposure、真实 depth | 作为 BoundedHDR 专属 adapter:`canonical FP16 -> configurable scale/normalizer -> DLSSNR -> inverse -> canonical FP16`;第一版只暴露 scale=1/2/4.5 实验值和诊断,不固化公式 | -| FSR1 | 官方核心输入为感知 sRGB `[0,1]`;具体 DXGI 格式未枚举;负 RCAS 输入会产生 NaN;输出由宿主决定 | 当前工厂没有 FSR1 native backend,按 shader effect 处理 | 没有 native HDR/FP16 协议,算法输入是 display-referred SDR | SDR-compatible adapter:HDR 外观映射到 sRGB `[0,1]`,运行 FSR1,再按记录的映射恢复 | -| FSR2 | Color 应用指定;depth 1x float;MV 2x float;reactive/T&C `R8_UNORM`;exposure `R32_FLOAT`;HDR flag 后输入 linear,输出恢复原输入域 | `FSR2Upscaler` 零 MV `R16G16_FLOAT`、零 depth `R32_FLOAT`、reactive `R8_UNORM`;启用 auto exposure/depth inverted/infinite;exposure 传 `nullptr`;preExposure=1;MV scale=1 | HDR flag 没有接入;真实 depth/exposure 没有接入;当前 MV 只接受 frame guidance | DirectFP16 adapter:canonical FP16 color,显式 HDR flag,接真实 exposure/depth/MV;保持 masks 与 resource state 独立 | -| FSR3 SR | 与 FSR2 同类:应用指定 color,float depth/MV,`R8_UNORM` masks,`R32_FLOAT` exposure;HDR flag 后输入 linear | `FSR3Upscaler` 复制输入/输出纹理原格式;aux 为 depth `R32_FLOAT`、exposure 1x1 `R32_FLOAT=1`、reactive/transparency `R8_UNORM`、zero MV `R16G16_FLOAT`;create flags 含 NON_LINEAR_COLORSPACE;dispatch 固定 `NON_LINEAR_COLOR_SRGB`、preExposure=1 | 当前颜色路径明确使用 sRGB/non-linear,和 HDR linear 证据存在语义偏差;真实 depth/exposure/jitter 没有接入 | DirectFP16 adapter:HDR 模式切换为 linear,删去隐含 sRGB flag,提供真实 exposure/depth/MV;FSR3 provider 版本选择保持现有机制 | -| FSR3 FG | current backbuffer、可选 HUD-less、depth、MV、FSR Optical Flow `R16G16_SINT`、SCD `R32_UINT`;需 backBufferFormat、HDR transfer/luminance、present callback | 当前代码没有 FSR3 FG native backend 或效果注册;Frame Generation 代码路径只识别 DLSSFG/XeSSFG | 缺少整个终端协议:swapchain/backbuffer、HUD-less/UI、光流/SCD、HDR luminance、present lifetime | 新增独立 FSR3 FG backend 后再接入;先定义 PresentationTerminal 与 `backBufferFormat/transfer/minMaxLuminance`,使用独立于普通 SR backend 的终端适配 | -| FSR4 | Color 应用指定,推荐 linear;depth 1x float;MV 2x float;exposure 1x1 `R32_FLOAT`;可用 NON_LINEAR_COLORSPACE 标志 | `FSR3Upscaler` 以 `useFsr4` 选择 provider 4.1.1;资源和 dispatch 形态与 FSR3 SR 共用;仍固定 `NON_LINEAR_COLOR_SRGB`、exposure=1、zero depth | FSR4 选择存在,HDR transfer/exposure/depth 语义仍沿用 FSR3 的硬编码;缺少真实 ML 输入校准 | 在 FSR3 backend 内增加 FSR4 专属 protocol profile,显式 linear/non-linear 选择与 exposure/depth 绑定,保持 provider version 独立 | -| NIS | 输入/输出为非整数格式,文档例为 `R8G8B8A8_UNORM`/NV12;支持 LDR、PQ、linear HDR;linear HDR 建议 `[0,12.5]`;输入 SRV、输出 UAV、linear clamp sampler | 当前 native 工厂没有 NIS backend;NIS 作为 shader effect 时由通用 effect surface 决定 | 缺少 `NIS_HDR_MODE`、PQ/linear 选择、NV12 路径、viewport/resource state 专属适配 | 新增 NIS 专属 shader/native declaration:HDR mode、输入范围、PQ Rec.2020 或 linear `[0,12.5]`,保证 SRV/UAV 与 sampler contract | -| AMD FidelityFX Optical Flow | color 输入格式公开资料未枚举;输出光流 `R16G16_SINT`、SCD `R32_UINT`;8x8 block;HDR 依赖 transfer/luminance | `AmdOpticalFlowProvider` 固定 color shared texture 为 `R8G8B8A8_UNORM`;Performance 为 1/2 extent,Quality 为 full extent;SDK flow 期望 `R16G16_SINT`;输出 dense motion `R16G16_FLOAT`、confidence `R8_UNORM`;dispatch 固定 sRGB、luminance `{0,1}` | 输入格式被硬编码为 RGBA8;HDR transfer/luminance 没有暴露;SDK sparse 输出被本地 densify 改成 frame-guidance 格式 | BoundedHDR auxiliary adapter:先定义 color transfer/luminance 参数,再转换为 SDK 输入;保留 sparse `R16G16_SINT/SCD` 与 dense frame-guidance 输出的边界 | - -## DLSSNR 实验记录与边界 - -实验材料:`DLSSNR-HDR-Experiments/README.md`、`results/README.md`、`results/hdr-scales/*/run.log` 与 `output_diagnostic.log`、`scripts/Run-HdrScales.ps1`。三组测试均为 2560x1392、FP16 线性输入、零 MV、常量 depth、同分辨率输出,Feature 18 Create/Evaluate/Release 均返回成功。`scale=1` 的诊断写明输入/输出均为 `DXGI_FORMAT_R16G16B16A16_FLOAT`,sRGB EOTF 转 linear 后写入 GPU,输出匹配逆转换且无 tone mapping。`scale=2` 仍能完成评估,但误差明显增加。`scale=4.5` 的结果出现明显红色/高光爆炸,PSNR 降到 5.152 dB,最大差为 3.5。 - -这些日志支持三条实现判断。第一,DLSSNR Feature 18 在当前 DLL/驱动组合上接受该实验调用链,成功返回值本身不代表颜色协议正确。第二,`scale` 是实验输入值域倍率,`scale=4.5` 与 scRGB 传统白点代理有关,实验材料没有证明它等同于 DLSSNR 内部 reference white、曝光或 PQ 峰值。第三,现有材料没有足够信息写出归一化函数、曲线、参考白、峰值、alpha 规则或逆变换,因此适配器必须把这些字段作为可配置实验参数,记录输入/输出统计和版本哈希。 - -Magpie 当前 `DLSSNRFilter` 与实验 harness 处于两条协议线上。当前 backend 在 `DLSSNRFilter.cpp:1895-1911` 限制输入为 RGBA8/BGRA8、输出为 RGBA8,在 `:1951-1958` 把共享面强制成 RGBA8;`SetCreateParametersUnsafe` 在 `:1343-1365` 固定尺寸、`Upscaling=0`、`Scale=1`、`ScalingRatio=1`;`SetEvaluateParametersUnsafe` 在 `:1432-1464` 固定 MV scale=1、depth inverted=1、reset/style/mask 等参数,代码里没有 HDR flag、exposure texture 或公开 normalize 参数。可落地的第一步是单独新增 DLSSNR protocol/adapter 文件,承载 scale、输入 transfer、reference white、peak/headroom、inverse policy 和 diagnostics;公共 Renderer 与 Hdr 组件继续只处理 canonical FP16 表面。 - -## 官方证据入口 - -逐条来源、引用片段和证据等级已经汇总在 [`HDR_PROTOCOL_EVIDENCE.md`](HDR_PROTOCOL_EVIDENCE.md) 与 [`HDR_PROTOCOL_EVIDENCE.json`](HDR_PROTOCOL_EVIDENCE.json)。本组实际使用的上游入口如下: - -- NVIDIA DLSS Programming Guide: -- NVIDIA DLSS-G / Frame Generation: -- NVIDIA DLSSG header: -- AMD FSR2: -- AMD FSR3 Upscaler: -- AMD FSR3 Frame Interpolation / Optical Flow: -- NVIDIA Image Scaling: -- NVIDIA Optical Flow SDK: - -## 当前代码证据索引 - -- `src/Magpie.Core/DLSSSRUpscaler.cpp:50-171,262-276`:DLSS SR 尺寸检查、零 MV/depth/mask 格式、AutoExposure 创建标志、固定 exposure/jitter/MV scale。 -- `src/Magpie.Core/DLSSFrameGenerator.cpp:390-608,637-798`:DLSSFG backbuffer/native format、zero resources、resource flags、MV/depth binding、unit scale、backbuffer/output subrect。 -- `src/Magpie.Core/DLSSNRFilter.cpp:1343-1464,1863-1958,2124-2142`:DLSSNR 创建参数、评估参数、RGBA8 输入输出限制和状态日志。 -- `src/Magpie.Core/FSR2Upscaler.cpp:50-178`:FSR2 auxiliary formats、context flags、exposure/motion/preExposure 常量。 -- `src/Magpie.Core/FSR3Upscaler.cpp:185-255,262-474,482-550`:FSR3/FSR4 shared color format、aux formats、provider version、HDR/non-linear flag、dispatch 常量。 -- `src/Magpie.Core/AmdOpticalFlowProvider.cpp:258-390,393-465,521-620`:AMD OF 输入 RGBA8、Performance/Quality 尺寸、SDK sparse formats、sRGB/luminance 固定值、dense output formats。 -- `src/Magpie.Core/NvidiaOpticalFlowProvider.cpp:16-66,366-409,748-756`:NVIDIA OF S10.5 `SHORT2` 解码为 float motion、grid 1/2/4 路径、dense motion `R16G16_FLOAT` 与 confidence `R8_UNORM`。NVIDIA OF 作为 DLSS/FSR 的共享辅助 provider,官方输入仍限 GRAYSCALE8/NV12/ABGR8,当前 provider 没有 FP16/HDR 输入路由。 -- `src/Magpie.Core/NativeEffectBackendFactory.cpp:63-89`:DLSSNR、DLSS SR、FSR2、FSR3/FSR4 的当前 native 分派;FSR1、NIS、FSR3 FG、AMD OF 均不在普通 SR 分派里。 - -## 适配优先级 - -1. DLSSNR:新增专属 bounded-HDR protocol 文件,先保留 scale=1/2/4.5 实验选择和统计诊断,把归一化公式保持为可配置实验参数。 -2. FSR3/FSR4 与 FSR2:把 HDR linear、真实 exposure、depth、MV、reset 作为 effect-local protocol,修正当前 non-linear sRGB 硬编码语义。 -3. DLSS SR:补齐 HDR flag 与 exposure,保留 zero guidance 作为明确的降级状态。 -4. NIS:新增 HDR mode 与 PQ/linear range 声明,空间算法适合较早接入。 -5. DLSSFG、FSR3 FG:统一 PresentationTerminal 资源描述,再分别接 backbuffer/UI/HUD-less/光流协议;颜色适配单独记录 transfer/luminance。 -6. AMD FidelityFX Optical Flow:先把 SDK color transfer/luminance 从固定 sRGB `{0,1}` 提升为 provider-local 参数,再决定 bounded HDR 的默认值。 - -## 验证命令 - -本报告的证据复核: - -```powershell -rg -n "scale=1|scale=2|scale=4\.5|PSNR|MAE|DXGI_FORMAT_R16G16B16A16_FLOAT|颜色转换" ` - D:\AI\workspace\DLSSNR-HDR-Experiments\results\hdr-scales - -rg -n "DXGI_FORMAT|HDR|exposure|Exposure|motion|Motion|depth|Depth|Scale|ScalingRatio|NON_LINEAR_COLOR_SRGB" ` - D:\AI\workspace\Magpie-0.6.5-research\src\Magpie.Core\DLSSNRFilter.cpp ` - D:\AI\workspace\Magpie-0.6.5-research\src\Magpie.Core\DLSSSRUpscaler.cpp ` - D:\AI\workspace\Magpie-0.6.5-research\src\Magpie.Core\FSR2Upscaler.cpp ` - D:\AI\workspace\Magpie-0.6.5-research\src\Magpie.Core\FSR3Upscaler.cpp -``` - -DLSSNR 真实实验重跑路径: - -```powershell -powershell -ExecutionPolicy Bypass -File D:\AI\workspace\DLSSNR-HDR-Experiments\scripts\Run-HdrScales.ps1 -``` - -该脚本会分别执行 `--hdr-scale 1`、`2`、`4.5`,结果写入 `results\hdr-scales\`。报告没有把重跑作为本次完成条件,当前结论直接取自已经存在的真实日志与输出统计。 diff --git a/docs/experimental/agent-c-report.md b/docs/experimental/agent-c-report.md deleted file mode 100644 index a2eee46db..000000000 --- a/docs/experimental/agent-c-report.md +++ /dev/null @@ -1,85 +0,0 @@ -# Agent C:效果协议与当前实现核对 - -本报告覆盖 NNEDI3、Pixel Art、RAVU、RTX Video VSR、RTX Video Denoiser、RTX Video HDR、Sharpen、SMAA、xBRZ、XeSS、XeSSFG、NVIDIA Optical Flow。证据来源分成三层:公开协议记录 `docs/experimental/HDR_PROTOCOL_EVIDENCE.md` 与 `.json`,实施目录 `docs/experimental/HDR_EFFECT_IMPLEMENTATION_CATALOG.md`,以及本仓库内的 HLSL 与 native backend。仓库当前没有可用的 Git 元数据,因此报告以文件路径和行号作为可复核锚点。 - -## 总结 - -| 效果 | 真实协议与范围 | alpha / 颜色语义 | 当前调用链 | 结论 | -|---|---|---|---|---| -| NNEDI3 | 外部 `Texture2D` 格式由宿主决定;中间 `R16_FLOAT`,内部只处理 luma,输出重建 RGB | luma 通道在 `0..1` clamp;输出写入 `float4(..., 1.0)` | 纯 HLSL,多 pass,走普通 EffectDrawer | `ConditionalFP16` 仅代表中间表面;HDR 颜色契约仍 Unknown | -| Pixel Art | `MMPX` 2x、`Pixellate` 任意尺寸、`SharpBilinear` 普通采样;外部格式未枚举 | MMPX/Pixellate 仅取 RGB 且 alpha 置 1;SharpBilinear 返回整四通道采样 | 纯 HLSL | `SDRCompatible`;精确比较和艺术阈值要求记录范围 | -| RAVU | luma/YUV、RGB、Lite、Zoom、compute 变体分开;LUT 为 `R16G16B16A16_FLOAT`,luma 临时面为 `R16_FLOAT` | luma 变体由 YUV 重建并 alpha=1;RGB 变体写入四通道结果,alpha 随具体变体 | 纯 HLSL,多 pass / compute | `ConditionalFP16` 候选;需按变体绑定颜色与 alpha 规则 | -| RTX Video VSR | NVIDIA VFX `VideoSuperRes` 使用 interleaved RGBA U8 GPU buffer,通道值 `0..255`,可放大 | SDK 公开资料未定义 HDR transfer、primaries、metadata、alpha;本地 scale 显式做 `0..1 <-> 0..255` | `NativeEffectBackendFactory` -> `RTXVideoDenoiser`,quality 1..4 | `SDRCompatible`,U8 是硬边界 | -| RTX Video Denoiser | 与 VSR 共用 RGBA U8 interleaved GPU buffer;quality 8..11 同分辨率 | 同上;Denoise 受同分辨率约束 | `NativeEffectBackendFactory` -> `RTXVideoDenoiser`,quality 8..11 | `SDRCompatible`,当前 backend 已实现 | -| RTX Video HDR | 产品语义为 SDR 到 HDR10;公开记录没有可调用纹理格式、数值范围或 metadata API | alpha、transfer、primaries、输出所有权均未形成仓库协议 | 没有对应 HLSL/native backend 路径 | `Unknown`,保持 fallback | -| Sharpen | Adaptive/Fine/Luma/LCAS 四个 shader 家族;部分内部面 `R16G16B16A16_FLOAT`,输入输出格式依宿主 | 多数 shader 只取 RGB 且输出 alpha=1;FineSharp 保留 alpha 参与内部运算后最终置 1;存在 clamp、overshoot 和 `0..255` 风格参数 | 纯 HLSL | 具体 shader 逐项审计;默认 `SDRCompatible`,FP16 只能按实测变体提升 | -| SMAA | 外部颜色纹理未枚举;edges 为 `R8G8_UNORM` 或 `R16G16_FLOAT`,blend/history 为 `R8G8B8A8_UNORM` 或 `R16G16B16A16_FLOAT`,Area/Search 为 `R8*` | 普通路径由 SMAA neighborhood 输出四通道;实验 temporal resolve 明确输出 alpha=1,history alpha 作为有效标记 | 纯 HLSL,多 pass;无 native backend | `SDRCompatible`;中间 FP16 只说明存储格式 | -| xBRZ | 固定 2x..6x compute;Freescale 另有内部 `R8G8B8A8_UNORM`;输入输出外部格式由宿主 | 算法只采样 RGB,所有标准变体写 `float4(dst, 1)`;Freescale 输出同样 alpha=1 | 纯 HLSL | `SDRCompatible`,整数式颜色比较和 YCbCr 阈值依赖显示域 | -| XeSS SR | Intel 公共协议列出 `R16G16B16A16_FLOAT`、`R11G11B10_FLOAT`、`R8G8B8A8_UNORM` 等 linear 格式;MV `R16G16_FLOAT`,depth 常见 `R32_FLOAT` | 公共协议要求输出与输入同格式同色彩空间,alpha 不保留并填 1;本地实验 backend 实际只接受输入 RGBA/BGRA8 UNORM,输出 RGBA8 UNORM | `NativeEffectBackendFactory` -> `XeSSUpscaler`;D3D11/D3D12 shared texture,Zero-MV 或共享 MV | 公共协议为 `DirectFP16` 候选;当前 Magpie 路径是 LDR/UNORM 实验实现,存在明确实现差异 | -| XeSSFG | 公共 HDR terminal 为 HDR10/BT.2100 `R10G10B10A2_UNORM`,backbuffer/HUD-less/UI 要求同格式、色彩空间、尺寸;FP16/scRGB 路径未形成 | UI alpha 单独传递;terminal 输出随 proxy swap chain | marker HLSL -> `XeSSFGPresenter` -> XeSS D3D12 proxy swap chain | 文档要求 `BoundedHDR` terminal;当前 presenter 固定 `R8G8B8A8_UNORM` 且 alpha ignore,形成关键差异 | -| NVIDIA Optical Flow | 公共 NvOF 输入 `GRAYSCALE8`、`NV12`、`ABGR8`;flow 为 `SHORT2` S10.5,cost 为 UINT/UINT8 | flow 是 signed motion vector,不属于 RGB 颜色;本地 densify 除以 32,cost 除以 255 | `FrameGuidance` provider -> `NvidiaOpticalFlowProvider` -> NVOF D3D11 | SDR auxiliary path;当前 init 固定 `ABGR8`,公共多格式能力尚未映射 | - -## 逐项证据与差异 - -### NNEDI3 - -公开证据记录为 mpv/user-shader 族,`rgba16f/rgba16hf` 只证明中间 surface 习惯,输入输出颜色契约、transfer、HDR 上限和 alpha 没有统一声明(`HDR_PROTOCOL_EVIDENCE.md` NNEDI3 条目;`HDR_EFFECT_IMPLEMENTATION_CATALOG.md:103-107`)。仓库 shader 对每个 NNEDI3 预设声明 `temp` 为 `R16_FLOAT`,输入先经 `dot(x.rgb, rgb2y)` 转成 luma。`nnedi3` 结果在 `0.0..1.0` 之间 clamp;第二 pass 从输入取 UV,再用 `yuv2rgb` 重建 RGB,输出明确写成 `float4(..., 1.0)`,见 `src/Effects/NNEDI3/NNEDI3_nns16_win8x4.hlsl:38-42,326-344,651-675`。当前没有 NNEDI3 native backend,调用停留在普通 HLSL multi-pass。结论是中间 FP16 可记录为实现格式,直接把 canonical HDR RGB 交给该路径缺少可靠颜色协议,alpha 需要按输出置 1 记录。 - -### Pixel Art - -Pixel Art 组由 `MMPX`、`Pixellate`、`SharpBilinear` 三类 shader 组成,公开协议条目只给出泛化算法族,输入输出 DXGI 格式和 HDR 数值范围保持 Unknown。仓库实现显示三种 alpha 语义:MMPX 采样 `.rgb`,四个放大像素全部写 `float4(J/K/M/L, 1)`,见 `src/Effects/Pixel Art/MMPX.hlsl:27,122-131`;Pixellate 对四个角点的 RGB 求平均并返回 `float4(averageColor, 1.0)`,见 `Pixellate.hlsl:33-48`;SharpBilinear 直接返回 `INPUT.SampleLevel`,四通道采样结果沿用宿主 alpha,见 `SharpBilinear.hlsl:40-43`。MMPX 还依赖逐分量精确相等比较,Pixellate 的平均操作没有 HDR transfer 语义,SharpBilinear 的区域限制只作用于坐标。当前全组没有 native backend,默认采用 SDR-compatible 处理,HDR 进入前需要明确艺术变换和 alpha 规则。 - -### RAVU - -RAVU 变体包含 luma/YUV、RGB、Lite、Zoom、anti-ringing、compute 与非 compute 路径。公开证据确认 `rgba16f/rgba16hf` 中间面和 luma/RGB 分化,实施目录要求按具体变体绑定颜色协议(`HDR_PROTOCOL_EVIDENCE.md` RAVU 条目;`HDR_EFFECT_IMPLEMENTATION_CATALOG.md:109-113`)。本地 luma 变体的 LUT 声明为 `R16G16B16A16_FLOAT`,中间 luma 面为 `R16_FLOAT`,见 `src/Effects/RAVU/RAVU_R2.hlsl:37-49`;第二 pass 从输入保留 UV,YUV 转 RGB 后 alpha 固定为 1,见 `RAVU_R2.hlsl:210-213`。RGB 变体的内部 LUT 同为 FP16,luma 变体写 `vec4(value,0)` 到临时面,RGB 变体最终写入四通道结果,见 `RAVU_R2_RGB.hlsl:34-48,70-71,212-213`。当前没有 native backend。结论为 RGB 变体具备 FP16 存储候选,luma/YUV 变体需要显式 luma、UV、transfer 和 alpha 适配,FP16 surface 本身不构成 HDR 颜色保证。 - -### RTX Video VSR 与 RTX Video Denoiser - -公开 VFX 协议把 VSR 与 Denoiser 都限定为 GPU-resident interleaved RGBA/BGRA U8,每分量数值边界是 `0..255`;Denoise 模式保持输入输出分辨率一致(`HDR_PROTOCOL_EVIDENCE.md` RTXVideo 两条;`HDR_EFFECT_IMPLEMENTATION_CATALOG.md:156-166`)。本地 `RTXVideoDenoiser.cpp` 使用 `NvCVImage_Alloc(... NVCV_RGBA, NVCV_U8, NVCV_INTERLEAVED, NVCV_GPU, 32)` 创建 input/output GPU image,quality 1..4 映射 VSR,8..11 映射 Denoise,见 `src/Magpie.Core/RTXVideoDenoiser.cpp:78-118`。对浮点 D3D11 surface,`inputScale=255.0f`,`outputScale=1.0f/255.0f`,并在 `NvCVImage_Transfer` 前后完成量化和还原,见 `RTXVideoDenoiser.cpp:101-109,193-231`。Denoise quality 校验输出与输入宽高一致,见 `RTXVideoDenoiser.cpp:90-95`。工厂把 `RTXVideo\\RTXVideo_VSR_*` 和 `RTXVideo\\RTXVideo_Denoise_*` 都路由到同一 native 类,再由 quality level 区分,见 `src/Magpie.Core/NativeEffectBackendFactory.cpp:92-113`;对应 HLSL 仅是 placeholder pass,native backend 接管实际处理。alpha、HDR transfer、primaries、metadata 没有 VFX API 证据,canonical HDR 必须先转成选定 SDR appearance 并量化到 U8,再通过成对策略重建。 - -### RTX Video HDR - -公开记录只有产品级 SDR-to-HDR10 语义,纹理格式、接口调用、数值范围、颜色 metadata、alpha 和输出所有权保持 Unknown。仓库中存在 RTX Video VSR/Denoise shader 与 backend,没有独立 `RTXVideo_HDR` shader、native 类或 factory 分支。当前调用链没有可落地的 HDR terminal。实现状态保持 `Unknown/fallback`,不把 canonical FP16 直接送入该产品能力。 - -### Sharpen - -Sharpen 组包含 AdaptiveSharpen、FineSharp、LCAS、LumaSharpen,公开目录要求按具体 backend 记录 clamp、negative lobe、overshoot、alpha 和 strength units。仓库 shader 普遍只取 RGB:LCAS、LumaSharpen 最终写 `float4(...,1)`,AdaptiveSharpen 写 `float4(src.rgb + sharpdiff,1)`,见 `src/Effects/Sharpen/LCAS.hlsl:39-54,73-95`、`AdaptiveSharpen.hlsl:102-124,206-210`、`LumaSharpen.hlsl:187-211`。FineSharp 声明两个内部 `R16G16B16A16_FLOAT` 面,内部读取和传播 alpha,最终 RGB 转换后仍写 alpha=1,见 `FineSharp.hlsl:49-67,337-368,442-444`;其参数计算含 `/255.0f`,属于参数标度线索,不等于外部纹理协议。当前没有 native backend,四个 shader 均由普通效果链执行。报告 profile 采用按变体选择:明确归一化、clamp 的路径为 SDR-compatible;完成线性 FP16 保真测试的路径才具备 ConditionalFP16 资格。 - -### SMAA - -公开 SMAA 资料给出 RGBA color、edges、area、search、depth 资源,普通路径中间纹理多为非 sRGB,最终 neighborhood blending 才涉及 sRGB 选项(`HDR_PROTOCOL_EVIDENCE.md` SMAA 条目;`HDR_EFFECT_IMPLEMENTATION_CATALOG.md:168-173`)。仓库 Low/Medium/T2x/4x 路径使用 `R8G8_UNORM` edges、`R8G8B8A8_UNORM` blend/history;High/Ultra 使用 `R16G16_FLOAT` edges、`R16G16B16A16_FLOAT` blend,Area 为 `R8G8B8A8_UNORM`,Search 为 `R8_UNORM`,例如 `src/Effects/SMAA/SMAA_High.hlsl:6-34` 与 `SMAA_Low.hlsl:6-34`。普通三 pass 是 luma edge -> blending weights -> neighborhood blending;实验 temporal 还维护 current/history/historyNext,resolve 明确 `return float4(...,1.0)`,history alpha 作为有效历史标记,见 `SMAA_T2x_Experimental.hlsl:151-182`。当前没有 native backend。外部输入输出格式由宿主提供,内部 FP16 只说明中间资源格式,颜色 transfer 和 alpha 仍按 shader 结果记录。 - -### xBRZ - -公开 xBRZ 社区接口常见 RGBA U8 `Uint8ClampedArray`,缩放因子 2..6,alpha 被保留;本地 native HLSL 实现采用 compute shader,标准变体覆盖 2x、3x、4x、5x、6x,Freescale 使用内部 `R8G8B8A8_UNORM`。标准变体采样 `INPUT.rgb`,用打包 RGB 值和 YCbCr 距离做等色判断,最终所有输出写 `float4(dst,1)`,见 `src/Effects/xBRZ/xBRZ_2x.hlsl:36-56,125-145,290-299`;其他倍率保持同一结构。Freescale 第二 pass 使用 `floor(info * 255 + 0.5)` 解码内部标志,输出仍置 alpha=1,见 `xBRZ_Freescale.hlsl:276-318,388`。当前调用链是纯 HLSL,无 native backend。精确颜色比较、整数式阈值和 alpha 置 1 使其保持 SDR-compatible,直接 scene-linear HDR 会改变阈值意义。 - -### XeSS Super Resolution - -公共 XeSS-SR 证据列出 `R16G16B16A16_FLOAT`、`R11G11B10_FLOAT`、`R8G8B8A8_UNORM` 与其他 linear color format,输出要求与输入保持同格式同色彩空间,alpha 不保留并填 1,MV 常用 `R16G16_FLOAT`,depth 可用 `R32_FLOAT`(`HDR_PROTOCOL_EVIDENCE.md` XeSS 条目;`HDR_EFFECT_IMPLEMENTATION_CATALOG.md:88-93`)。本地实现存在清晰差异:`XeSSUpscaler::Initialize` 只接受 `R8G8B8A8_UNORM` 或 `B8G8R8A8_UNORM` 输入,输出必须是 `R8G8B8A8_UNORM`,并限制最高 3x,见 `src/Magpie.Core/XeSSUpscaler.cpp:184-214`;BGRA 输入先经 compute shader 转 RGBA,shared input/output 纹理也按 RGBA8 创建,见 `XeSSUpscaler.cpp:49-60,240-263`。XeSS 初始化设置 `XESS_INIT_FLAG_LDR_INPUT_COLOR`,并使用 flat `R32_FLOAT` depth、`R8_UNORM` responsive mask、zero/shared `R16G16_FLOAT` motion,见 `XeSSUpscaler.cpp:308-334,417-426`。调用链由 `NativeEffectBackendFactory` 识别 `XeSS\\XeSS_SR` 后创建 `XeSSUpscaler`,见 `NativeEffectBackendFactory.cpp:77-89`;效果 HLSL 只是 marker pass。结论是公共协议具备 DirectFP16 候选,当前仓库路径仍是 LDR/UNORM Zero-MV 或共享 MV 实验实现,HDR 直接接入需要单独 backend 改造与 alpha 清理。 - -### XeSS Frame Generation - -公共 XeSS-FG HDR terminal 证据要求 HDR10/BT.2100 `R10G10B10A2_UNORM`,backbuffer、HUD-less、UI-only 资源需匹配像素格式、色彩空间和尺寸,FP16 HDR/scRGB 路径没有对应公开 terminal 契约(`HDR_PROTOCOL_EVIDENCE.md` XeSSFG 条目;`HDR_EFFECT_IMPLEMENTATION_CATALOG.md:130-134`)。本地 presenter 固定 `COLOR_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM`,创建 proxy swap chain 时使用同一格式并设置 `DXGI_ALPHA_MODE_IGNORE`,见 `src/Magpie.Core/XeSSFGPresenter.cpp:18-20,548-558`;D3D11 shared color、D3D12 backbuffer、frame resource tagging 和 proxy `Present` 组成完整 terminal,见 `XeSSFGPresenter.cpp:155-195,782-899`。外部 motion 若启用,必须为 `R16G16_FLOAT` 且尺寸与 destination 完全匹配,见 `XeSSFGPresenter.cpp:632-661`;没有外部 motion 时使用 zero motion 与 flat depth。`XeSSFG\\XeSS_FrameGeneration_x2_ZeroMV.hlsl` 只保留 marker pass,实际插值由 `XeSSFGPresenter` 接管,见该 shader:1-52。实现目录建议的 PQ/BT.2100 `R10G10B10A2_UNORM` terminal 尚未落入当前 presenter,报告将其标为 bounded-HDR 文档目标与 R8 presenter 现状之间的实现差异。 - -### NVIDIA Optical Flow - -公开 NvOF 头文件协议列出 `NV_OF_BUFFER_FORMAT_GRAYSCALE8`、`NV_OF_BUFFER_FORMAT_NV12`、`NV_OF_BUFFER_FORMAT_ABGR8` 输入,flow 输出为 `SHORT2` S10.5,cost 可为 UINT/UINT8,支持 grid 1/2/4(`HDR_PROTOCOL_EVIDENCE.md` NVIDIA Optical Flow 条目;`HDR_EFFECT_IMPLEMENTATION_CATALOG.md:168-173`)。本地 provider 查询输入、输出、cost caps,并要求 ABGR8 与 S10.5 可用;初始化参数最终固定 `inputBufferFormat = NV_OF_BUFFER_FORMAT_ABGR8`,见 `src/Magpie.Core/NvidiaOpticalFlowProvider.cpp:503-545`。flow 结果被 densify shader 读取为 `Texture2D` 并除以 32,cost 读取为 uint 并除以 255,见 `NvidiaOpticalFlowProvider.cpp:15-61,103-105`;输出落入 `FrameGuidance` 的 motion/confidence 纹理,不进入普通 RGB 效果 surface。质量配置映射为 4x 或 2x grid 与 FAST/MEDIUM/SLOW perf level,见 `NvidiaOpticalFlowProvider.cpp:119-134`。当前调用链由 FrameGuidance provider 驱动,可被 XeSS SR、XeSSFG、DLSS 等请求;公共三种输入格式在本地只落地 ABGR8,属于 auxiliary SDR path。 - -## 文件修改与验证 - -本次只新增本报告文件:`docs/experimental/agent-c-report.md`。没有修改 `Renderer.cpp`、`EffectDrawer.cpp`、`Hdr*` 通用组件、配置 UI、CMake 或任何既有公共文件,也没有修改分组外效果文件。 - -已完成的只读核对命令: - -```powershell -rg -n -i "NNEDI3|Pixel Art|RAVU|RTXVideo|Sharpen|SMAA|xBRZ|XeSS|XeSSFG|Optical Flow|R10G10B10A2|R11G11B10|R16G16B16A16|GRAYSCALE8|ABGR8|NV12" src docs/experimental -Get-Content docs/experimental/HDR_PROTOCOL_EVIDENCE.md -Get-Content docs/experimental/HDR_EFFECT_IMPLEMENTATION_CATALOG.md -Get-Content src/Magpie.Core/NativeEffectBackendFactory.cpp -Get-Content src/Magpie.Core/RTXVideoDenoiser.cpp -Get-Content src/Magpie.Core/XeSSUpscaler.cpp -Get-Content src/Magpie.Core/XeSSFGPresenter.cpp -Get-Content src/Magpie.Core/NvidiaOpticalFlowProvider.cpp -``` - -报告所引用的格式、范围、alpha、调用链和实现差异均来自上述只读结果;没有进行构建或运行时验证,因为本交付只新增证据报告,且当前工作树没有为这些 SDK 路径提供统一可复现的构建环境。 diff --git a/docs/experimental/reviews/20260905-default-effect-groups-REVIEW.md b/docs/experimental/reviews/20260905-default-effect-groups-REVIEW.md deleted file mode 100644 index 1288a68e6..000000000 --- a/docs/experimental/reviews/20260905-default-effect-groups-REVIEW.md +++ /dev/null @@ -1,152 +0,0 @@ -# 默认效果组预设 review - -日期:2026-09-05。对象为当前 0.6.5 r5 local / fix2 的出厂默认效果组,界面现仍称“缩放模式”。依据当前工作区源码和已部署发行包,未读取或修改用户个人配置,也未运行、编译或部署程序。 - -## 结论与优先事项 - -程序内置 **6 个默认效果组、11 个效果器实例**,全局默认选择 **Lanczos**。另有源码仓库中的 3 组导入预设;它们不是创建默认配置时的读取来源,当前发行目录也没有附带这份 JSON。 - -静态核对没有发现这 6 组引用缺失效果器、混用多个帧生成效果器或默认参数越界的问题。主要需要产品决策的是:4 组统一带 60 FPS 限帧;高成本 VSR Ultra 被列为唯一 VSR 默认入口;DLSSFG 默认启用 NVIDIA 平衡光流,而 XeSSFG 默认不启用外部光流;DLSSNR 默认不启用降分辨率残差路径。 - -这些不自动等于错误。建议先明确“默认列表是通用入门选项,还是各实验后端示例”,再决定调整。未进行画质、帧率、延迟和硬件兼容性测试,不能据此宣称某档质量最好或性能达标。 - -## 默认来源与保留规则 - -| 来源 | 当前内容 | 使用时机 | -| --------------------------------------------- | ------------------------------ | -------------------------------------------------------------------- | -| AppSettings::_SetDefaultScalingModes | 下表 6 组 | 没有既有配置时创建;执行重置效果组时恢复;特定配置恢复路径为空时补回 | -| 效果器 HLSL 的 DEFAULT | 各效果器自身的参数默认 | 组未显式指定该参数时使用;原生后端另有缺省解析,需保持一致 | -| presets/ScalingModes-v0.6.5-experimental.json | DLSSFG、XeSSFG、DLSSNR 共 3 组 | 手动导入用;导入为追加,不替换同名组 | -| 用户已保存的配置 | 用户自己的名称、顺序和参数 | 不会因源码默认列表调整而自动替换成新的 6 组 | - -重置操作会删除当前全部组,将自定义程序配置的组引用改为继承全局默认,然后恢复 6 组并选择 Lanczos。因此不能用“只是恢复某个参数”的文案描述它。 - -依据:[默认初始化](<../../../src/Magpie/AppSettings.cpp#L227>)、[默认组与重置](<../../../src/Magpie/AppSettings.cpp#L1329>)、[追加导入](<../../../src/Magpie/ScalingModesService.cpp#L474>)。 - -## 6 组总览 - -下面顺序与程序定义一致。箭头表示列表顺序;帧生成由渲染器/呈现后端安排,不等同于普通 HLSL 通道在该位置直接生成帧。 - -| 序号 | 名称 | 配置的效果器顺序 | 尺寸规则 | 参数来源 | -| ---- | ------------------- | ------------------------------------------------- | ------------------------------ | ----------------------------------------- | -| 1 | Lanczos | Lanczos | Fit,倍率 1×1 | 全部继承效果器默认 | -| 2 | FSR | FSR_EASU → FSR_RCAS | EASU 为 Fit;RCAS 保持输入尺寸 | 显式 sharpness=0.87,与效果器默认相同 | -| 3 | RTX Video VSR Ultra | FrameRate_Filter → RTXVideo_VSR_Ultra | VSR 为 Fit | 限帧继承 60;VSR Ultra 对应原生质量等级 4 | -| 4 | DLSSFG | FrameRate_Filter → DLSS_FrameGeneration | 两项声明输出与输入等尺寸 | 60 FPS;倍率 2;NVIDIA 光流质量 2(平衡) | -| 5 | XeSSFG | FrameRate_Filter → XeSS_FrameGeneration_x2_ZeroMV | 两项声明输出与输入等尺寸 | 60 FPS;固定 x2;光流方法 0(无外部光流) | -| 6 | DLSSNR | FrameRate_Filter → DLSSNR_AI_Filter | 两项声明输出与输入等尺寸 | 60 FPS;15 项参数继承,详见下表 | - -Fit 表示尺寸适应规则,并非每个显示模式都简单照搬“充满屏幕”:窗口模式下倍率 1 的 Fit 有视为 Fill 的处理。Normal 的默认倍率是 1×1;效果器声明固定等输入尺寸时也不会因此自动成为超分效果器。 - -此外,渲染器可以按显示尺寸追加 **Bicubic(B=0,C=0.5)**:窗口模式下最后输出尺寸与目标渲染尺寸不一致,或全屏模式下最后输出大于渲染区域时。它不是保存的组内预设,也不是 Bicubic 自身默认的 B=0.33、C=0.33。不能只看列表便断言最终画面绝无尺寸重采样。 - -依据:[EffectItem 缺省](<../../../src/Magpie/ScalingMode.h#L7>)、[尺寸计算](<../../../src/Magpie.Core/EffectDrawer.cpp#L365>)、[追加 Bicubic](<../../../src/Magpie.Core/Renderer.cpp#L1490>)。 - -## 逐组 review - -### 1. Lanczos - -唯一参数:抗振铃强度 `ARStrength=0.5`。组没有显式参数覆盖;作为全局默认时,依赖随版本发布的 Lanczos 默认值。 - -判断:适合作为不依赖实验原生后端的基本放大入口。保留为默认是合理的产品选择;这不是对所有素材的画质排名。若将来调整 Lanczos 的 DEFAULT,未显式保存该参数的既有组也会随之改变,需要在版本说明中交代。 - -### 2. FSR - -`FSR_EASU` 无可调参数;`FSR_RCAS.sharpness=0.87`。组在 C++ 中显式写入锐化 0.87,当前与 HLSL 默认值一致。 - -判断:顺序表达了先放大、后锐化,结构明确。但“FSR”可能被误解成当前目录中的 FSR2/FSR3/FSR4 原生版本。若以后采用更明确的默认组名,可考虑“FSR 1(EASU + RCAS)”,不应据此强制重命名用户的同名组。是否降低锐化应由画质对比决定,本次不凭静态数值指定替代值。 - -### 3. RTX Video VSR Ultra - -`FrameRate_Filter.targetFrameRate=60`;VSR 没有参数页控件,Ultra 文件名在原生工厂选择质量等级 4。默认列表没有 Low/Medium/High 组,但这些效果器文件都随包提供。 - -判断:作为最高档示例可保留;若面向初次使用者,应标明它是硬件相关高档入口,不能把“默认列表中有它”等同于当前机器可用。值得确认是否增加一档更低成本的 VSR 示例,或在后续界面中先展示可用性。当前没有测得性能数据,不直接推荐替换为某一档。 - -### 4. DLSSFG - -`targetFrameRate=60`,`multiplier=2`,`motionVectorQuality=2(平衡)`。默认启用外部 NVIDIA 光流,原生帧生成设置的缺省值与参数声明一致。 - -判断:x2 是明确的起点。但“60 FPS”限制的是捕获处理基础帧率,不是最终显示帧率保证;基础输入不足、重复帧、硬件支持、呈现路径都会影响结果。不要把这个组直接命名为“120 FPS”。如果基础帧率超过 60,它可能限制原本更高帧率的输入。 - -### 5. XeSSFG - -固定 x2,无可调 multiplier。`opticalFlowMethod=0(无)`,潜在 AMD 光流质量 `amdOpticalFlowMode=1(质量)`,潜在 NVIDIA 光流质量 `nvidiaOpticalFlowQuality=2(平衡)`。 - -两个质量值在方法为 0 时不代表已启用外部光流;它们是切换对应方法后使用的值。这里“无”只描述 Magpie 提供的外部光流,不表示 SDK 内部完全不做运动分析。 - -判断:与 DLSSFG 默认光流策略不同,应在说明中显式区分。不能为了形式统一就将 XeSSFG 默认改为 NVIDIA 光流;该选择会改变适用硬件和运行成本。是否启用 AMD 光流也应有对应的画质/成本依据。 - -### 6. DLSSNR - -| 参数 | 自身默认值 | 默认情况下的含义 | -| ---------------------------- | ---------- | -------------------------------------------------------------------------- | -| enableInputResolutionScaling | 0 | 关闭输入分辨率调整 | -| inputResolutionPercent | 100 | 若启用开关则初始保持 100%;当前默认开关关闭 | -| residualMultiplier | 1 | 残差倍率;当前默认未进入该残差路径 | -| residualSaturation | 1 | 残差饱和度不额外放大;同上 | -| residualLightness | 1 | 残差明度不额外放大;同上 | -| shadowStructureMultiplier | 1 | 阴影/结构控制中性倍率;同上 | -| reflectionGlowMultiplier | 1 | 反射/辉光控制中性倍率;同上 | -| motionVectorQuality | 2 | NVIDIA 光流平衡档 | -| style | 0 | 默认风格 | -| intensity | 1 | 当前声明的 NR 强度默认值 | -| localToneStrength | 1 | 局部色调强度默认值 | -| localStructureStrength | 1 | 局部结构强度默认值 | -| skinStructureStrength | -1 | 原样传给后端的特殊默认数值;本地接口未给出足够依据将其解释为“自动”或“关闭” | -| useAutoMask | 0 | 自动遮罩关闭 | -| uiCorrection | 0 | NR 界面修正关闭 | - -判断:默认配置选择全分辨率路径,未默认开启 r5 新增的降采样/残差重建处理。这与减少默认图像变化的方向一致。若需要方便评估新路径,可以另建明确命名的可选效果组,不必修改所有用户已有的 DLSSNR 组。 - -初始化失败时当前会报告问题并透传画面,参数面板显示不可用;因此“成功显示窗口”不等于 NR 已实际运行。`intensity=1` 等值只能描述请求设置,不能据数值推算实际画面增强程度。 - -依据:[DLSSNR 声明](<../../../src/Effects/DLSSNR/DLSSNR_AI_Filter.hlsl#L8>)、[原生解析](<../../../src/Magpie.Core/DLSSNRFilter.cpp#L13>)、[初始化回退](<../../../src/Magpie.Core/NativeEffectBackendFactory.cpp#L62>)。 - -## 60 FPS 限制的共同影响 - -VSR、DLSSFG、XeSSFG、DLSSNR 四组都带 FrameRate_Filter。渲染器会与捕获/程序配置的其他帧率上限取更小值;链中多个限帧效果器也取更小值。所以“组默认 60”既不保证有 60 帧,也不保证补帧后有 120 帧。 - -建议后续说明采用“基础帧率上限:60 FPS”;是否继续在这四组内置限帧,需明确优先照顾高刷新率游戏还是视频/固定帧率使用场景。当前不直接去掉限帧,因为它会改变资源占用和时序行为。 - -依据:[限帧汇总](<../../../src/Magpie.Core/Renderer.cpp#L2030>)。 - -## 3 组 JSON 导入预设的核对 - -[ScalingModes-v0.6.5-experimental.json](<../../../presets/ScalingModes-v0.6.5-experimental.json>) 包含 DLSSFG、XeSSFG、DLSSNR,每组两个效果器。20 个显式参数值均与当前效果器声明默认值一致;DLSSNR 未写入的输入分辨率开关、百分比、残差倍率也继承相同默认。与对应内置组的有效参数一致,但 JSON 显式保存更多值,将来 DEFAULT 变化时二者可能出现分歧。 - -JSON 没有写 scalingType 是合法缺省,不是格式损坏:`JsonHelper::ReadUInt` 的 required 默认为 false,字段不存在时保留 `EffectItem` 的 Normal 值。手动导入采用追加方式,同名组不会自动合并或覆盖;重复导入会出现同名组,用户沟通时应同时指出组内效果器。 - -当前发行包未附带这个文件,因此它暂时不能作为包内可直接选择的另外三项预设。旧 `ScalingModes-v0.5.7-experimental.json` 仍在仓库中,包含旧参数命名;它属于历史兼容材料,不能作为当前默认值依据。 - -依据:[可选字段读取](<../../../src/Magpie/JsonHelper.cpp#L51>)、[导入条目](<../../../src/Magpie/ScalingModesService.cpp#L168>)。 - -## 建议后续决策顺序 - -1. 保留 Lanczos 作为全局初始组;确认默认列表承担“入门选项”还是“后端示例”的职责。 -2. 确认四个实验组是否继续默认加 60 FPS 限制。 -3. 决定是否为 VSR 增加较低档入口、将 FSR 组名写明版本;不自动覆盖用户组。 -4. 说明 DLSSFG/XeSSFG 的默认光流策略差异,以及 DLSSNR 默认关闭残差重建路径。 -5. 维护默认组的唯一权威定义或增加静态对照,避免 C++ 默认列表、导入 JSON、效果器 DEFAULT、原生缺省值分别漂移。 - -以上是后续建议;本次没有改变默认组、参数、导入文件或已部署版本。 - - - - - - -人工review结果: - - -| DLSSNR | FrameRate_Filter → DLSSNR_AI_Filter | -| ------ | ----------------------------------- | - -这个效果组可以去掉前面的过滤器 其他的参数目前看起来没问题 - -## 本轮 review 确认记录(2026-09-05) - -用户确认前四份 review 无异议,并在上方提出默认组修改意见。记录的后续修改目标为:**默认 DLSSNR 组移除 FrameRate_Filter,只保留 DLSSNR_AI_Filter;其他效果参数保持不变。** 其余默认组未提出移除限帧器的要求。 - -本文件前文仍是调查时的源码快照,并非修改后的默认列表。后续实施时应同步核对内置默认组及同名导入预设,避免定义分歧;用户自行保存的现有效果组不应仅凭同名自动改写。删除组内限帧器也不表示绕过程序配置中的其他帧率上限。 - -本轮继续讨论 HDR 设计,尚未执行上述程序修改。 diff --git a/docs/experimental/reviews/20260905-effect-default-parameters-REVIEW.md b/docs/experimental/reviews/20260905-effect-default-parameters-REVIEW.md deleted file mode 100644 index 04cf2bfc0..000000000 --- a/docs/experimental/reviews/20260905-effect-default-parameters-REVIEW.md +++ /dev/null @@ -1,766 +0,0 @@ -# 全部效果器默认参数 review - -日期:2026-09-05。范围已按用户回复收窄为**各效果器自身默认参数**,不展开每个参数的全部枚举选项和取值范围。默认效果组的组合与覆盖值另见配套文档。 - -基线:0.6.5 r5 local / fix2 当前工作区,HEAD `84d9f6abb8203a79f9a44d9a66984a293e68856d`(含未提交修改)。本次仅静态阅读和文档生成,未修改、编译、运行或部署程序;未更改用户配置。 - -## 覆盖情况 - -以已部署包 effects 和 Effects.vcxproj 的发布清单交叉核对,共 **170 个效果器文件**,对应源码逐文件内容一致。其中 **37 个效果器有可调参数,共 144 项;133 个没有可调参数**。下面全部列出,没有将“无参数”当成遗漏。 - -源目录还有一个未纳入本次发布的 `DLSS/DLSS_OpticalFlow.hlsl`,因此直接数源目录会得到 171 个。它是未发布旧入口,不混入本版本参数总数。第三方用户自行增加的效果器不属于本次发行包盘点。 - -## 需要优先处理的两项一致性问题 - -### P2:运动可视化声明 0.08,原生后端缺省却使用 1.0 - -触发条件:添加 `Diagnostics\FrameGuidance_Motion`,未显式保存 gain,随后启用。效果描述默认 0.08,原生工厂从参数表缺省取 1.0;EffectItem 转换只复制已保存参数,没有统一补齐默认值。UI 由描述生成,会显示 0.08。因此参数页展示和后端实际初始设置可以不一致,影响诊断图含义和恢复默认行为。 - -建议后续统一描述、原生解析和设置结构的默认来源。此处是源码路径确认的问题,尚未实机复现;并非修改后端或参数的记录。 - -依据:[运动可视化声明](<../../../src/Effects/Diagnostics/FrameGuidance_Motion.hlsl#L5>)、[原生工厂](<../../../src/Magpie.Core/NativeEffectBackendFactory.cpp#L57>)、[参数复制](<../../../src/Magpie/ScalingMode.cpp#L6>)、[原生设置](<../../../src/Magpie.Core/FrameGuidanceDiagnostics.h#L12>)。 - -### P2:Jinc 默认 0.825 不在滑条步进上 - -`Jinc.sinc` 的 DEFAULT 为 0.825,STEP 为 0.01。普通 HLSL 渲染缺省直接采用 0.825;UI 和运行草稿则通过 NormalizeEffectParameterValue 量化,按当前 float 数值路径推演约为 0.83。恢复默认不能只给现有归一化路径赋 0.825 后就宣称已恢复。 - -建议保持声明的 0.825,并使合法刻度能表达它;例如后续单独评估将 STEP 调整为 0.005。不要在本次 review 中把默认值改为 0.83。对全部 144 项声明做静态检查,未发现其他有显著偏差的默认值步进冲突;此检查不替代控件运行验收。 - -依据:[Jinc 默认](<../../../src/Effects/Jinc.hlsl#L27>)、[数值归一化](<../../../src/Magpie.Core/include/EffectParameterValue.h#L23>)、[HLSL 初始参数](<../../../src/Magpie.Core/EffectDrawer.cpp#L520>)。 - -## 默认值的含义与 review 边界 - -表格中的“默认值”是当前效果器文件的 DEFAULT;括号只解释默认对应的复选状态或枚举项。反向命名的开关需按标签理解,例如“关闭扫描线”未勾选,表示没有禁用扫描线。中文标签取自当前简体资源,保留参数内部名称用于定位。它不是用户当前配置,不是本次会话启动值,也不保证原生后端实际生效。 - -对已知原生入口复核了 DLSSNR 的 15 项、DLSS SR 的光流、DLSSFG 的倍率和光流、XeSSFG 的方法/质量/倍率、FrameRate_Filter 的缺省;未发现除上述诊断增益以外的对应默认值不一致。该结论只覆盖公开可调参数的缺省解析,不表示所有 SDK 内部参数、自动降级或质量档都已全面验证。 - -修改 DEFAULT 会影响未显式保存此参数的既有组;显式保存了旧值的组则通常保持旧值。因而“把默认改得更好”也属于用户可感知行为变更,不能假定只影响首次安装。 - -## 有参数的效果器:37 个、144 项 - -### 1. Anime4K / Anime4K_Denoise_Bilateral_Mean - -来源:[Anime4K/Anime4K_Denoise_Bilateral_Mean.hlsl](<../../../src/Effects/Anime4K/Anime4K_Denoise_Bilateral_Mean.hlsl#L9>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `intensitySigma` | 强度 | 0.1 | - -Review:默认强度属于该滤镜的起始设置,不是关闭效果;此处没有画质对比依据要求调整。 - -### 2. Anime4K / Anime4K_Denoise_Bilateral_Median - -来源:[Anime4K/Anime4K_Denoise_Bilateral_Median.hlsl](<../../../src/Effects/Anime4K/Anime4K_Denoise_Bilateral_Median.hlsl#L8>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `intensitySigma` | 强度 | 0.1 | - -Review:默认强度属于该滤镜的起始设置,不是关闭效果;此处没有画质对比依据要求调整。 - -### 3. Anime4K / Anime4K_Denoise_Bilateral_Mode - -来源:[Anime4K/Anime4K_Denoise_Bilateral_Mode.hlsl](<../../../src/Effects/Anime4K/Anime4K_Denoise_Bilateral_Mode.hlsl#L9>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `intensitySigma` | 强度 | 0.1 | - -Review:默认强度属于该滤镜的起始设置,不是关闭效果;此处没有画质对比依据要求调整。 - -### 4. Anime4K / Anime4K_Thin_HQ - -来源:[Anime4K/Anime4K_Thin_HQ.hlsl](<../../../src/Effects/Anime4K/Anime4K_Thin_HQ.hlsl#L8>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `strength` | 强度 | 0.6 | -| `iterations` | 迭代次数 | 1 | - -Review:默认强度属于该滤镜的起始设置,不是关闭效果;此处没有画质对比依据要求调整。 - -### 5. Bicubic - -来源:[Bicubic.hlsl](<../../../src/Effects/Bicubic.hlsl#L9>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `paramB` | B 参数 | 0.33 | -| `paramC` | C 参数 | 0.33 | - -Review:B=0.33、C=0.33 是自身默认。渲染器自动追加的 Bicubic 使用 B=0、C=0.5,属于另一层显式配置。 - -### 6. CAS / CAS - -来源:[CAS/CAS.hlsl](<../../../src/Effects/CAS/CAS.hlsl#L10>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `sharpness` | 锐度 | 0.4 | - -Review:两个变体默认锐度相同;是否改变尺寸由所选变体及尺寸设置决定。 - -### 7. CAS / CAS_Scaling - -来源:[CAS/CAS_Scaling.hlsl](<../../../src/Effects/CAS/CAS_Scaling.hlsl#L9>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `sharpness` | 锐度 | 0.4 | - -Review:两个变体默认锐度相同;是否改变尺寸由所选变体及尺寸设置决定。 - -### 8. CRT / CRT_Easymode - -来源:[CRT/CRT_Easymode.hlsl](<../../../src/Effects/CRT/CRT_Easymode.hlsl#L37>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `sharpnessH` | 水平锐度 | 0.5 | -| `sharpnessV` | 垂直锐度 | 1 | -| `maskStrength` | 遮罩强度 | 0.3 | -| `maskDotWidth` | 遮罩点宽度 | 1 | -| `maskDotHeight` | 遮罩点高度 | 1 | -| `maskStagger` | 遮罩错位 | 0 | -| `maskSize` | 遮罩尺寸 | 1 | -| `scanlineStrength` | 扫描线强度 | 1 | -| `scanlineBeamWidthMin` | 扫描线束最小宽度 | 1.5 | -| `scanlineBeamWidthMax` | 扫描线束最大宽度 | 1.5 | -| `scanlineBrightMin` | 扫描线最低亮度 | 0.35 | -| `scanlineBrightMax` | 扫描线最高亮度 | 0.65 | -| `scanlineCutoff` | 扫描线截止阈值 | 400 | -| `gammaInput` | 输入伽马 | 2 | -| `gammaOutput` | 输出伽马 | 1.8 | -| `brightBoost` | 亮度增强 | 1.2 | -| `dilation` | 膨胀量 | 1(已勾选) | - -Review:这些是 CRT 风格起始值,并非中性透传。恢复默认会恢复该风格本身,不代表去掉扫描线、曲率或色彩处理。 - -### 9. CRT / CRT_Geom - -来源:[CRT/CRT_Geom.hlsl](<../../../src/Effects/CRT/CRT_Geom.hlsl#L30>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `CRTGamma` | 目标伽马 | 2.4 | -| `monitorGamma` | 显示器伽马 | 2.2 | -| `distance` | 观察距离 | 1.5 | -| `curvature` | 屏幕曲率 | 1(已勾选) | -| `radius` | 曲率半径 | 2 | -| `cornerSize` | 圆角大小 | 0.03 | -| `cornerSmooth` | 圆角平滑度 | 1000 | -| `xTilt` | 水平倾斜 | 0 | -| `yTilt` | 垂直倾斜 | 0 | -| `overScanX` | 水平过扫描 | 100 | -| `overScanY` | 垂直过扫描 | 100 | -| `dotMask` | 点状遮罩 | 0.3 | -| `sharper` | 锐度 | 1 | -| `scanlineWeight` | 扫描线权重 | 0.3 | -| `lum` | 明亮度增强 | 0 | - -Review:这些是 CRT 风格起始值,并非中性透传。恢复默认会恢复该风格本身,不代表去掉扫描线、曲率或色彩处理。 - -### 10. CRT / CRT_Hyllian - -来源:[CRT/CRT_Hyllian.hlsl](<../../../src/Effects/CRT/CRT_Hyllian.hlsl#L34>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `phosphor` | 荧光粉效果 | 1(已勾选) | -| `vScanlines` | 垂直扫描线 | 0(未勾选) | -| `inputGamma` | 输入伽马 | 2.5 | -| `outputGamma` | 输出伽马 | 2.2 | -| `sharpness` | 锐度 | 1 | -| `colorBoost` | 色彩增强 | 1.5 | -| `redBoost` | 红色增强 | 1 | -| `greenBoost` | 绿色增强 | 1 | -| `blueBoost` | 蓝色增强 | 1 | -| `scanlinesStrength` | 扫描线强度 | 0.5 | -| `beamMinWidth` | 最小束宽 | 0.86 | -| `beamMaxWidth` | 最大束宽 | 1 | -| `crtAntiRinging` | 抗振铃 | 0.8 | - -Review:这些是 CRT 风格起始值,并非中性透传。恢复默认会恢复该风格本身,不代表去掉扫描线、曲率或色彩处理。 - -### 11. CRT / CRT_Lottes - -来源:[CRT/CRT_Lottes.hlsl](<../../../src/Effects/CRT/CRT_Lottes.hlsl#L22>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `hardScan` | 扫描线硬度 | -8 | -| `hardPix` | 像素硬度 | -3 | -| `warpX` | 水平画面弯曲 | 0.031 | -| `warpY` | 垂直画面弯曲 | 0.041 | -| `maskDark` | 遮罩暗部 | 0.5 | -| `maskLight` | 遮罩亮部 | 1.5 | -| `shadowMask` | 荫罩样式 | 3 | -| `brightBoost` | 亮度增强 | 1 | -| `hardBloomPix` | 水平辉光柔化 | -1.5 | -| `hardBloomScan` | 垂直辉光柔化 | -2 | -| `bloomAmount` | 辉光强度 | 0.15 | -| `shape` | 滤波核形状 | 2 | - -Review:这些是 CRT 风格起始值,并非中性透传。恢复默认会恢复该风格本身,不代表去掉扫描线、曲率或色彩处理。 - -### 12. CRT / GTU_v050 - -来源:[CRT/GTU_v050.hlsl](<../../../src/Effects/CRT/GTU_v050.hlsl#L15>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `compositeConnection` | 复合视频连接 | 0(未勾选) | -| `noScanlines` | 关闭扫描线 | 0(未勾选) | -| `signalResolution` | Y 信号分辨率 | 256 | -| `signalResolutionI` | I 信号分辨率 | 83 | -| `signalResolutionQ` | Q 信号分辨率 | 25 | -| `tvVerticalResolution` | 电视垂直分辨率 | 250 | -| `blackLevel` | 黑位 | 0.07 | -| `contrast` | 对比度 | 1 | - -Review:这些是 CRT 风格起始值,并非中性透传。恢复默认会恢复该风格本身,不代表去掉扫描线、曲率或色彩处理。 - -### 13. DLSS / DLSS_SR - -来源:[DLSS/DLSS_SR.hlsl](<../../../src/Effects/DLSS/DLSS_SR.hlsl#L9>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `motionVectorQuality` | 光流质量 | 2(平衡) | - -Review:默认启用 NVIDIA 平衡档光流;不能将默认值解释为自动选择任意厂商后端。 - -### 14. DLSSFG / DLSS_FrameGeneration - -来源:[DLSSFG/DLSS_FrameGeneration.hlsl](<../../../src/Effects/DLSSFG/DLSS_FrameGeneration.hlsl#L8>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `multiplier` | 帧数倍率 | 2 | -| `motionVectorQuality` | 光流质量 | 2(平衡) | - -Review:倍率 2 与平衡光流默认相匹配;最终显示帧率取决于输入及后端,不能由倍率数值保证。 - -### 15. DLSSNR / DLSSNR_AI_Filter - -来源:[DLSSNR/DLSSNR_AI_Filter.hlsl](<../../../src/Effects/DLSSNR/DLSSNR_AI_Filter.hlsl#L8>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `enableInputResolutionScaling` | 调整输入分辨率
(会降低 DLSSNR 质量) | 0(未勾选) | -| `inputResolutionPercent` | 输入分辨率(%) | 100% | -| `residualMultiplier` | 残差倍率 | 1 | -| `residualSaturation` | 残差饱和度倍率 | 1 | -| `residualLightness` | 残差明度倍率 | 1 | -| `shadowStructureMultiplier` | 阴影/结构控制 | 1 | -| `reflectionGlowMultiplier` | 反射/辉光控制 | 1 | -| `motionVectorQuality` | 光流质量 | 2(平衡) | -| `style` | NR 风格
(0 默认,1 自然,2 电影) | 0(默认风格) | -| `intensity` | NR 强度 | 1 | -| `localToneStrength` | 局部色调强度 | 1 | -| `localStructureStrength` | 局部结构强度 | 1 | -| `skinStructureStrength` | 皮肤结构强度 | -1 | -| `useAutoMask` | 自动遮罩 | 0(未勾选) | -| `uiCorrection` | NR 界面修正 | 0(未勾选) | - -Review:默认关闭输入分辨率调整,因此残差相关数值虽然存在,默认未启用对应路径。-1 的皮肤结构值按当前接口原样记录,不擅自解释为自动。 - -### 16. Deband - -来源:[Deband.hlsl](<../../../src/Effects/Deband.hlsl#L7>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `threshold` | 阈值 | 64 | -| `range` | 范围 | 8 | -| `iterations` | 迭代次数 | 4 | -| `grain` | 颗粒强度 | 48 | - -Review:默认会进行去色带处理并请求颗粒强度 48;不是无颗粒的中性设置。是否去掉默认颗粒需用户画质确认。 - -### 17. Diagnostics / FrameGuidance_Motion - -来源:[Diagnostics/FrameGuidance_Motion.hlsl](<../../../src/Effects/Diagnostics/FrameGuidance_Motion.hlsl#L5>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `gain` | 显示增益 | 0.08 | - -Review:发现原生后端缺省 gain=1.0 与声明 0.08 不一致,详见前面的 P2 项。 - -### 18. FSR / FSR_RCAS - -来源:[FSR/FSR_RCAS.hlsl](<../../../src/Effects/FSR/FSR_RCAS.hlsl#L10>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `sharpness` | 锐度 | 0.87 | - -Review:FSR_RCAS 自身默认与内置 FSR 组显式值都是 0.87;不是 0.5。 - -### 19. FrameRate_Filter - -来源:[FrameRate_Filter.hlsl](<../../../src/Effects/FrameRate_Filter.hlsl#L8>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `targetFrameRate` | 目标帧率 | 60 FPS | - -Review:60 表示捕获处理帧率上限候选值;还会与其他上限取较小值,不是最终补帧显示帧率。 - -### 20. ImageAdjustment - -来源:[ImageAdjustment.hlsl](<../../../src/Effects/ImageAdjustment.hlsl#L7>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `targetGamma` | 目标伽马 | 2.2 | -| `monitorGamma` | 显示器伽马 | 2.2 | -| `saturation` | 饱和度 | 1 | -| `luminance` | 明亮度 | 1 | -| `contrast` | 对比度 | 1 | -| `brightBoost` | 亮度增强 | 0 | -| `blackLevel` | 黑位 | 0 | -| `r` | 红色通道 | 1 | -| `g` | 绿色通道 | 1 | -| `b` | 蓝色通道 | 1 | - -Review:默认伽马比值为 1、通道及对比度等倍率为 1、偏移为 0;公式意图接近保持原色。仍有颜色空间往返及截断,不能保证逐位透传。 - -### 21. Jinc - -来源:[Jinc.hlsl](<../../../src/Effects/Jinc.hlsl#L19>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `windowSinc` | 窗函数 Sinc 参数 | 0.5 | -| `sinc` | Sinc 参数 | 0.825 | -| `ARStrength` | 抗振铃强度 | 0.5 | - -Review:sinc=0.825 不对齐当前步进 0.01,UI 归一化可能改变为约 0.83;应先解决再实现精确重置。 - -### 22. Lanczos - -来源:[Lanczos.hlsl](<../../../src/Effects/Lanczos.hlsl#L8>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `ARStrength` | 抗振铃强度 | 0.5 | - -Review:抗振铃为 0.5;内置 Lanczos 组继承该值。 - -### 23. MLAA / MLAA - -来源:[MLAA/MLAA.hlsl](<../../../src/Effects/MLAA/MLAA.hlsl#L29>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `threshold` | 边缘阈值 | 0.08 | -| `strength` | 强度 | 1.0 | - -Review:强度 1 表示启用预设处理强度,不应将恢复默认理解为关闭抗锯齿。 - -### 24. NIS / NIS - -来源:[NIS/NIS.hlsl](<../../../src/Effects/NIS/NIS.hlsl#L7>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `sharpness` | 锐度 | 0.5 | - -Review:NIS 和 NVSharpen 的锐度同为 0.5;名字相近不意味着同一个效果器或同一种尺寸行为。 - -### 25. NIS / NVSharpen - -来源:[NIS/NVSharpen.hlsl](<../../../src/Effects/NIS/NVSharpen.hlsl#L9>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `sharpness` | 锐度 | 0.5 | - -Review:NIS 和 NVSharpen 的锐度同为 0.5;名字相近不意味着同一个效果器或同一种尺寸行为。 - -### 26. SGSR - -来源:[SGSR.hlsl](<../../../src/Effects/SGSR.hlsl#L7>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `EdgeSharpness` | 边缘锐度 | 2.0 | -| `EdgeThreshold` | 边缘阈值 | 8.0 | - -Review:边缘参数是算法内部量纲;不要按名称将 2 或 8 当成界面百分比。 - -### 27. SMAA / SMAA_4x_Experimental - -来源:[SMAA/SMAA_4x_Experimental.hlsl](<../../../src/Effects/SMAA/SMAA_4x_Experimental.hlsl#L11>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `historyWeight` | 历史帧权重 | 0.75 | -| `historyRejection` | 历史帧拒绝阈值 | 8.0 | - -Review:4x 与 T2x 的历史权重不同,分别为 0.75 与 0.5;Jitter/NoJitter 同组默认参数相同,算法路径仍不同。时间稳定性留待实际素材检查。 - -### 28. SMAA / SMAA_4x_NoJitter_Experimental - -来源:[SMAA/SMAA_4x_NoJitter_Experimental.hlsl](<../../../src/Effects/SMAA/SMAA_4x_NoJitter_Experimental.hlsl#L10>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `historyWeight` | 历史帧权重 | 0.75 | -| `historyRejection` | 历史帧拒绝阈值 | 8.0 | - -Review:4x 与 T2x 的历史权重不同,分别为 0.75 与 0.5;Jitter/NoJitter 同组默认参数相同,算法路径仍不同。时间稳定性留待实际素材检查。 - -### 29. SMAA / SMAA_T2x_Experimental - -来源:[SMAA/SMAA_T2x_Experimental.hlsl](<../../../src/Effects/SMAA/SMAA_T2x_Experimental.hlsl#L10>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `historyWeight` | 历史帧权重 | 0.5 | -| `historyRejection` | 历史帧拒绝阈值 | 8.0 | - -Review:4x 与 T2x 的历史权重不同,分别为 0.75 与 0.5;Jitter/NoJitter 同组默认参数相同,算法路径仍不同。时间稳定性留待实际素材检查。 - -### 30. SMAA / SMAA_T2x_NoJitter_Experimental - -来源:[SMAA/SMAA_T2x_NoJitter_Experimental.hlsl](<../../../src/Effects/SMAA/SMAA_T2x_NoJitter_Experimental.hlsl#L10>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `historyWeight` | 历史帧权重 | 0.5 | -| `historyRejection` | 历史帧拒绝阈值 | 8.0 | - -Review:4x 与 T2x 的历史权重不同,分别为 0.75 与 0.5;Jitter/NoJitter 同组默认参数相同,算法路径仍不同。时间稳定性留待实际素材检查。 - -### 31. SSimDownscaler - -来源:[SSimDownscaler.hlsl](<../../../src/Effects/SSimDownscaler.hlsl#L9>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `oversharp` | 额外锐化 | 1 | - -Review:额外锐化默认 1;数值语义由效果公式决定,不将它当作 100% 的统一锐度量表。 - -### 32. Sharpen / AdaptiveSharpen - -来源:[Sharpen/AdaptiveSharpen.hlsl](<../../../src/Effects/Sharpen/AdaptiveSharpen.hlsl#L12>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `curveHeight` | 锐度 | 0.8 | - -Review:各算法锐化强度的量纲不同,不能把 0.5、0.65、0.8、2.0 排成跨效果器强弱排名。 - -### 33. Sharpen / FineSharp - -来源:[Sharpen/FineSharp.hlsl](<../../../src/Effects/Sharpen/FineSharp.hlsl#L12>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `sstr` | 锐化强度(sstr) | 2.0 | -| `cstr` | 均衡强度(cstr) | 0.9 | -| `xstr` | 最终锐化强度(xstr) | 0.19 | -| `xrep` | 锐化伪影修复(xrep) | 0.25 | - -Review:各算法锐化强度的量纲不同,不能把 0.5、0.65、0.8、2.0 排成跨效果器强弱排名。 - -### 34. Sharpen / LCAS - -来源:[Sharpen/LCAS.hlsl](<../../../src/Effects/Sharpen/LCAS.hlsl#L6>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `sharpness` | 锐度 | 0.5 | - -Review:各算法锐化强度的量纲不同,不能把 0.5、0.65、0.8、2.0 排成跨效果器强弱排名。 - -### 35. Sharpen / LumaSharpen - -来源:[Sharpen/LumaSharpen.hlsl](<../../../src/Effects/Sharpen/LumaSharpen.hlsl#L19>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `sharpStrength` | 锐化强度 | 0.65 | -| `sharpClamp` | 锐化限制 | 0.035 | -| `pattern` | 采样模式 | 1 | -| `offsetBias` | 采样偏移 | 1 | - -Review:各算法锐化强度的量纲不同,不能把 0.5、0.65、0.8、2.0 排成跨效果器强弱排名。 - -### 36. XeSSFG / XeSS_FrameGeneration_x2_ZeroMV - -来源:[XeSSFG/XeSS_FrameGeneration_x2_ZeroMV.hlsl](<../../../src/Effects/XeSSFG/XeSS_FrameGeneration_x2_ZeroMV.hlsl#L9>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `opticalFlowMethod` | 光流方法 | 0(无) | -| `amdOpticalFlowMode` | 光流质量 | 1(质量) | -| `nvidiaOpticalFlowQuality` | 光流质量 | 2(平衡) | - -Review:默认光流方法为 0(无外部光流);AMD/NVIDIA 的质量值是选择相应方法后才使用的预置值。x2 版本倍率固定;多帧版本自身默认倍率为 3。 - -### 37. XeSSFG / XeSS_MultiFrameGeneration_ZeroMV - -来源:[XeSSFG/XeSS_MultiFrameGeneration_ZeroMV.hlsl](<../../../src/Effects/XeSSFG/XeSS_MultiFrameGeneration_ZeroMV.hlsl#L8>)。 - -| 参数名称 | 中文标签 | 自身默认值 | -| --- | --- | --- | -| `multiplier` | 帧数倍率 | 3 | -| `opticalFlowMethod` | 光流方法 | 0(无) | -| `amdOpticalFlowMode` | 光流质量 | 1(质量) | - -Review:默认光流方法为 0(无外部光流);AMD/NVIDIA 的质量值是选择相应方法后才使用的预置值。x2 版本倍率固定;多帧版本自身默认倍率为 3。 - -## 无可调参数的效果器:133 个 - -“无可调参数”指没有 `//!PARAMETER` 声明,因此没有单个参数的默认值或双击重置目标。并不表示没有算法常量、没有质量档、没有运算,也不表示无需硬件支持。按用户确认的范围,本节不展开权重、所有着色器常量或 SDK 内部设置。 - -几个重要区别: - -- RTX Video 的 Low/Medium/High/Ultra 由文件选择固定原生质量档:VSR 对应 1/2/3/4,Denoise 对应 8/9/10/11;不是参数页枚举。 -- FSR2/FSR3/FSR4、XeSS 的 ZeroMV/Jitter/OpticalFlow 是不同入口,文件名携带固定路径选择;无滑条不代表行为相同。 -- Anime4K、CuNNy、RAVU、NNEDI3、xBRZ 等变体包含固定网络或尺寸/算法选择;不可将它们归为同一个“空预设”。 -- Diagnostics/FrameGuidance_Confidence 是诊断效果,输出不是常规增强画面。 -- 源文件中的 SORT_NAME 可能用于排序,不应将它不加核对地当作独立参数或默认档位。 - -固定后端档位的依据:[NativeEffectBackendFactory.cpp](<../../../src/Magpie.Core/NativeEffectBackendFactory.cpp#L129>)。 - -### Anime4K(23 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [Anime4K/Anime4K_3D_AA_Upscale_US.hlsl](<../../../src/Effects/Anime4K/Anime4K_3D_AA_Upscale_US.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_3D_Upscale_US.hlsl](<../../../src/Effects/Anime4K/Anime4K_3D_Upscale_US.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Restore_L.hlsl](<../../../src/Effects/Anime4K/Anime4K_Restore_L.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Restore_M.hlsl](<../../../src/Effects/Anime4K/Anime4K_Restore_M.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Restore_S.hlsl](<../../../src/Effects/Anime4K/Anime4K_Restore_S.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Restore_Soft_L.hlsl](<../../../src/Effects/Anime4K/Anime4K_Restore_Soft_L.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Restore_Soft_M.hlsl](<../../../src/Effects/Anime4K/Anime4K_Restore_Soft_M.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Restore_Soft_S.hlsl](<../../../src/Effects/Anime4K/Anime4K_Restore_Soft_S.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Restore_Soft_UL.hlsl](<../../../src/Effects/Anime4K/Anime4K_Restore_Soft_UL.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Restore_Soft_VL.hlsl](<../../../src/Effects/Anime4K/Anime4K_Restore_Soft_VL.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Restore_UL.hlsl](<../../../src/Effects/Anime4K/Anime4K_Restore_UL.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Restore_VL.hlsl](<../../../src/Effects/Anime4K/Anime4K_Restore_VL.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Upscale_Denoise_L.hlsl](<../../../src/Effects/Anime4K/Anime4K_Upscale_Denoise_L.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Upscale_Denoise_S.hlsl](<../../../src/Effects/Anime4K/Anime4K_Upscale_Denoise_S.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Upscale_Denoise_UL.hlsl](<../../../src/Effects/Anime4K/Anime4K_Upscale_Denoise_UL.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Upscale_Denoise_VL.hlsl](<../../../src/Effects/Anime4K/Anime4K_Upscale_Denoise_VL.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Upscale_GAN_x2_M.hlsl](<../../../src/Effects/Anime4K/Anime4K_Upscale_GAN_x2_M.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Upscale_GAN_x2_S.hlsl](<../../../src/Effects/Anime4K/Anime4K_Upscale_GAN_x2_S.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Upscale_GAN_x3_L.hlsl](<../../../src/Effects/Anime4K/Anime4K_Upscale_GAN_x3_L.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Upscale_L.hlsl](<../../../src/Effects/Anime4K/Anime4K_Upscale_L.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Upscale_S.hlsl](<../../../src/Effects/Anime4K/Anime4K_Upscale_S.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Upscale_UL.hlsl](<../../../src/Effects/Anime4K/Anime4K_Upscale_UL.hlsl>) | 无可调参数 | -| [Anime4K/Anime4K_Upscale_VL.hlsl](<../../../src/Effects/Anime4K/Anime4K_Upscale_VL.hlsl>) | 无可调参数 | - -### CuNNy(20 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [CuNNy/CuNNy-16x16C-NVL-DN.hlsl](<../../../src/Effects/CuNNy/CuNNy-16x16C-NVL-DN.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-16x16C-NVL.hlsl](<../../../src/Effects/CuNNy/CuNNy-16x16C-NVL.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-2x4C-NVL-DN.hlsl](<../../../src/Effects/CuNNy/CuNNy-2x4C-NVL-DN.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-2x4C-NVL.hlsl](<../../../src/Effects/CuNNy/CuNNy-2x4C-NVL.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-3x4C-NVL-DN.hlsl](<../../../src/Effects/CuNNy/CuNNy-3x4C-NVL-DN.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-3x4C-NVL.hlsl](<../../../src/Effects/CuNNy/CuNNy-3x4C-NVL.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-4x16C-NVL-DN.hlsl](<../../../src/Effects/CuNNy/CuNNy-4x16C-NVL-DN.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-4x16C-NVL.hlsl](<../../../src/Effects/CuNNy/CuNNy-4x16C-NVL.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-4x4C-NVL-DN.hlsl](<../../../src/Effects/CuNNy/CuNNy-4x4C-NVL-DN.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-4x4C-NVL.hlsl](<../../../src/Effects/CuNNy/CuNNy-4x4C-NVL.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-4x8C-NVL-DN.hlsl](<../../../src/Effects/CuNNy/CuNNy-4x8C-NVL-DN.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-4x8C-NVL.hlsl](<../../../src/Effects/CuNNy/CuNNy-4x8C-NVL.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-6x8C-NVL-DN.hlsl](<../../../src/Effects/CuNNy/CuNNy-6x8C-NVL-DN.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-6x8C-NVL.hlsl](<../../../src/Effects/CuNNy/CuNNy-6x8C-NVL.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-8x16C-NVL-DN.hlsl](<../../../src/Effects/CuNNy/CuNNy-8x16C-NVL-DN.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-8x16C-NVL.hlsl](<../../../src/Effects/CuNNy/CuNNy-8x16C-NVL.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-8x4C-NVL-DN.hlsl](<../../../src/Effects/CuNNy/CuNNy-8x4C-NVL-DN.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-8x4C-NVL.hlsl](<../../../src/Effects/CuNNy/CuNNy-8x4C-NVL.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-8x8C-NVL-DN.hlsl](<../../../src/Effects/CuNNy/CuNNy-8x8C-NVL-DN.hlsl>) | 无可调参数 | -| [CuNNy/CuNNy-8x8C-NVL.hlsl](<../../../src/Effects/CuNNy/CuNNy-8x8C-NVL.hlsl>) | 无可调参数 | - -### CuNNy2(9 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [CuNNy2/CuNNy-3x12-NVL.hlsl](<../../../src/Effects/CuNNy2/CuNNy-3x12-NVL.hlsl>) | 无可调参数 | -| [CuNNy2/CuNNy-4x12-NVL.hlsl](<../../../src/Effects/CuNNy2/CuNNy-4x12-NVL.hlsl>) | 无可调参数 | -| [CuNNy2/CuNNy-4x16-NVL.hlsl](<../../../src/Effects/CuNNy2/CuNNy-4x16-NVL.hlsl>) | 无可调参数 | -| [CuNNy2/CuNNy-4x24-NVL.hlsl](<../../../src/Effects/CuNNy2/CuNNy-4x24-NVL.hlsl>) | 无可调参数 | -| [CuNNy2/CuNNy-4x32-NVL.hlsl](<../../../src/Effects/CuNNy2/CuNNy-4x32-NVL.hlsl>) | 无可调参数 | -| [CuNNy2/CuNNy-8x32-NVL.hlsl](<../../../src/Effects/CuNNy2/CuNNy-8x32-NVL.hlsl>) | 无可调参数 | -| [CuNNy2/CuNNy-fast-NVL.hlsl](<../../../src/Effects/CuNNy2/CuNNy-fast-NVL.hlsl>) | 无可调参数 | -| [CuNNy2/CuNNy-faster-NVL.hlsl](<../../../src/Effects/CuNNy2/CuNNy-faster-NVL.hlsl>) | 无可调参数 | -| [CuNNy2/CuNNy-veryfast-NVL.hlsl](<../../../src/Effects/CuNNy2/CuNNy-veryfast-NVL.hlsl>) | 无可调参数 | - -### DLSS(1 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [DLSS/DLSS_ZeroMV_Jitter.hlsl](<../../../src/Effects/DLSS/DLSS_ZeroMV_Jitter.hlsl>) | 无可调参数 | - -### Diagnostics(1 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [Diagnostics/FrameGuidance_Confidence.hlsl](<../../../src/Effects/Diagnostics/FrameGuidance_Confidence.hlsl>) | 无可调参数 | - -### FSR(1 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [FSR/FSR_EASU.hlsl](<../../../src/Effects/FSR/FSR_EASU.hlsl>) | 无可调参数 | - -### FSR2(3 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [FSR2/FSR2_OpticalFlow.hlsl](<../../../src/Effects/FSR2/FSR2_OpticalFlow.hlsl>) | 无可调参数 | -| [FSR2/FSR2_ZeroMV.hlsl](<../../../src/Effects/FSR2/FSR2_ZeroMV.hlsl>) | 无可调参数 | -| [FSR2/FSR2_ZeroMV_Jitter.hlsl](<../../../src/Effects/FSR2/FSR2_ZeroMV_Jitter.hlsl>) | 无可调参数 | - -### FSR3(3 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [FSR3/FSR3_OpticalFlow.hlsl](<../../../src/Effects/FSR3/FSR3_OpticalFlow.hlsl>) | 无可调参数 | -| [FSR3/FSR3_ZeroMV.hlsl](<../../../src/Effects/FSR3/FSR3_ZeroMV.hlsl>) | 无可调参数 | -| [FSR3/FSR3_ZeroMV_Jitter.hlsl](<../../../src/Effects/FSR3/FSR3_ZeroMV_Jitter.hlsl>) | 无可调参数 | - -### FSR4(3 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [FSR4/FSR4_OpticalFlow.hlsl](<../../../src/Effects/FSR4/FSR4_OpticalFlow.hlsl>) | 无可调参数 | -| [FSR4/FSR4_ZeroMV.hlsl](<../../../src/Effects/FSR4/FSR4_ZeroMV.hlsl>) | 无可调参数 | -| [FSR4/FSR4_ZeroMV_Jitter.hlsl](<../../../src/Effects/FSR4/FSR4_ZeroMV_Jitter.hlsl>) | 无可调参数 | - -### FSRCNNX(2 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [FSRCNNX/FSRCNNX.hlsl](<../../../src/Effects/FSRCNNX/FSRCNNX.hlsl>) | 无可调参数 | -| [FSRCNNX/FSRCNNX_LineArt.hlsl](<../../../src/Effects/FSRCNNX/FSRCNNX_LineArt.hlsl>) | 无可调参数 | - -### FXAA(3 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [FXAA/FXAA_High.hlsl](<../../../src/Effects/FXAA/FXAA_High.hlsl>) | 无可调参数 | -| [FXAA/FXAA_Medium.hlsl](<../../../src/Effects/FXAA/FXAA_Medium.hlsl>) | 无可调参数 | -| [FXAA/FXAA_Ultra.hlsl](<../../../src/Effects/FXAA/FXAA_Ultra.hlsl>) | 无可调参数 | - -### NNEDI3(10 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [NNEDI3/NNEDI3_nns128_win8x4.hlsl](<../../../src/Effects/NNEDI3/NNEDI3_nns128_win8x4.hlsl>) | 无可调参数 | -| [NNEDI3/NNEDI3_nns128_win8x6.hlsl](<../../../src/Effects/NNEDI3/NNEDI3_nns128_win8x6.hlsl>) | 无可调参数 | -| [NNEDI3/NNEDI3_nns16_win8x4.hlsl](<../../../src/Effects/NNEDI3/NNEDI3_nns16_win8x4.hlsl>) | 无可调参数 | -| [NNEDI3/NNEDI3_nns16_win8x6.hlsl](<../../../src/Effects/NNEDI3/NNEDI3_nns16_win8x6.hlsl>) | 无可调参数 | -| [NNEDI3/NNEDI3_nns256_win8x4.hlsl](<../../../src/Effects/NNEDI3/NNEDI3_nns256_win8x4.hlsl>) | 无可调参数 | -| [NNEDI3/NNEDI3_nns256_win8x6.hlsl](<../../../src/Effects/NNEDI3/NNEDI3_nns256_win8x6.hlsl>) | 无可调参数 | -| [NNEDI3/NNEDI3_nns32_win8x4.hlsl](<../../../src/Effects/NNEDI3/NNEDI3_nns32_win8x4.hlsl>) | 无可调参数 | -| [NNEDI3/NNEDI3_nns32_win8x6.hlsl](<../../../src/Effects/NNEDI3/NNEDI3_nns32_win8x6.hlsl>) | 无可调参数 | -| [NNEDI3/NNEDI3_nns64_win8x4.hlsl](<../../../src/Effects/NNEDI3/NNEDI3_nns64_win8x4.hlsl>) | 无可调参数 | -| [NNEDI3/NNEDI3_nns64_win8x6.hlsl](<../../../src/Effects/NNEDI3/NNEDI3_nns64_win8x6.hlsl>) | 无可调参数 | - -### Pixel Art(3 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [Pixel Art/MMPX.hlsl](<../../../src/Effects/Pixel Art/MMPX.hlsl>) | 无可调参数 | -| [Pixel Art/Pixellate.hlsl](<../../../src/Effects/Pixel Art/Pixellate.hlsl>) | 无可调参数 | -| [Pixel Art/SharpBilinear.hlsl](<../../../src/Effects/Pixel Art/SharpBilinear.hlsl>) | 无可调参数 | - -### RAVU(26 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [RAVU/RAVU_3x_R2.hlsl](<../../../src/Effects/RAVU/RAVU_3x_R2.hlsl>) | 无可调参数 | -| [RAVU/RAVU_3x_R2_RGB.hlsl](<../../../src/Effects/RAVU/RAVU_3x_R2_RGB.hlsl>) | 无可调参数 | -| [RAVU/RAVU_3x_R3.hlsl](<../../../src/Effects/RAVU/RAVU_3x_R3.hlsl>) | 无可调参数 | -| [RAVU/RAVU_3x_R3_RGB.hlsl](<../../../src/Effects/RAVU/RAVU_3x_R3_RGB.hlsl>) | 无可调参数 | -| [RAVU/RAVU_3x_R4.hlsl](<../../../src/Effects/RAVU/RAVU_3x_R4.hlsl>) | 无可调参数 | -| [RAVU/RAVU_3x_R4_RGB.hlsl](<../../../src/Effects/RAVU/RAVU_3x_R4_RGB.hlsl>) | 无可调参数 | -| [RAVU/RAVU_Lite_AR_R2.hlsl](<../../../src/Effects/RAVU/RAVU_Lite_AR_R2.hlsl>) | 无可调参数 | -| [RAVU/RAVU_Lite_AR_R3.hlsl](<../../../src/Effects/RAVU/RAVU_Lite_AR_R3.hlsl>) | 无可调参数 | -| [RAVU/RAVU_Lite_AR_R4.hlsl](<../../../src/Effects/RAVU/RAVU_Lite_AR_R4.hlsl>) | 无可调参数 | -| [RAVU/RAVU_Lite_R2.hlsl](<../../../src/Effects/RAVU/RAVU_Lite_R2.hlsl>) | 无可调参数 | -| [RAVU/RAVU_Lite_R3.hlsl](<../../../src/Effects/RAVU/RAVU_Lite_R3.hlsl>) | 无可调参数 | -| [RAVU/RAVU_Lite_R4.hlsl](<../../../src/Effects/RAVU/RAVU_Lite_R4.hlsl>) | 无可调参数 | -| [RAVU/RAVU_R2.hlsl](<../../../src/Effects/RAVU/RAVU_R2.hlsl>) | 无可调参数 | -| [RAVU/RAVU_R2_RGB.hlsl](<../../../src/Effects/RAVU/RAVU_R2_RGB.hlsl>) | 无可调参数 | -| [RAVU/RAVU_R3.hlsl](<../../../src/Effects/RAVU/RAVU_R3.hlsl>) | 无可调参数 | -| [RAVU/RAVU_R3_RGB.hlsl](<../../../src/Effects/RAVU/RAVU_R3_RGB.hlsl>) | 无可调参数 | -| [RAVU/RAVU_R4.hlsl](<../../../src/Effects/RAVU/RAVU_R4.hlsl>) | 无可调参数 | -| [RAVU/RAVU_R4_RGB.hlsl](<../../../src/Effects/RAVU/RAVU_R4_RGB.hlsl>) | 无可调参数 | -| [RAVU/RAVU_Zoom_AR_R2.hlsl](<../../../src/Effects/RAVU/RAVU_Zoom_AR_R2.hlsl>) | 无可调参数 | -| [RAVU/RAVU_Zoom_AR_R2_RGB.hlsl](<../../../src/Effects/RAVU/RAVU_Zoom_AR_R2_RGB.hlsl>) | 无可调参数 | -| [RAVU/RAVU_Zoom_AR_R3.hlsl](<../../../src/Effects/RAVU/RAVU_Zoom_AR_R3.hlsl>) | 无可调参数 | -| [RAVU/RAVU_Zoom_AR_R3_RGB.hlsl](<../../../src/Effects/RAVU/RAVU_Zoom_AR_R3_RGB.hlsl>) | 无可调参数 | -| [RAVU/RAVU_Zoom_R2.hlsl](<../../../src/Effects/RAVU/RAVU_Zoom_R2.hlsl>) | 无可调参数 | -| [RAVU/RAVU_Zoom_R2_RGB.hlsl](<../../../src/Effects/RAVU/RAVU_Zoom_R2_RGB.hlsl>) | 无可调参数 | -| [RAVU/RAVU_Zoom_R3.hlsl](<../../../src/Effects/RAVU/RAVU_Zoom_R3.hlsl>) | 无可调参数 | -| [RAVU/RAVU_Zoom_R3_RGB.hlsl](<../../../src/Effects/RAVU/RAVU_Zoom_R3_RGB.hlsl>) | 无可调参数 | - -### RTXVideo(8 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [RTXVideo/RTXVideo_Denoise_High.hlsl](<../../../src/Effects/RTXVideo/RTXVideo_Denoise_High.hlsl>) | 无可调参数 | -| [RTXVideo/RTXVideo_Denoise_Low.hlsl](<../../../src/Effects/RTXVideo/RTXVideo_Denoise_Low.hlsl>) | 无可调参数 | -| [RTXVideo/RTXVideo_Denoise_Medium.hlsl](<../../../src/Effects/RTXVideo/RTXVideo_Denoise_Medium.hlsl>) | 无可调参数 | -| [RTXVideo/RTXVideo_Denoise_Ultra.hlsl](<../../../src/Effects/RTXVideo/RTXVideo_Denoise_Ultra.hlsl>) | 无可调参数 | -| [RTXVideo/RTXVideo_VSR_High.hlsl](<../../../src/Effects/RTXVideo/RTXVideo_VSR_High.hlsl>) | 无可调参数 | -| [RTXVideo/RTXVideo_VSR_Low.hlsl](<../../../src/Effects/RTXVideo/RTXVideo_VSR_Low.hlsl>) | 无可调参数 | -| [RTXVideo/RTXVideo_VSR_Medium.hlsl](<../../../src/Effects/RTXVideo/RTXVideo_VSR_Medium.hlsl>) | 无可调参数 | -| [RTXVideo/RTXVideo_VSR_Ultra.hlsl](<../../../src/Effects/RTXVideo/RTXVideo_VSR_Ultra.hlsl>) | 无可调参数 | - -### SMAA(4 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [SMAA/SMAA_High.hlsl](<../../../src/Effects/SMAA/SMAA_High.hlsl>) | 无可调参数 | -| [SMAA/SMAA_Low.hlsl](<../../../src/Effects/SMAA/SMAA_Low.hlsl>) | 无可调参数 | -| [SMAA/SMAA_Medium.hlsl](<../../../src/Effects/SMAA/SMAA_Medium.hlsl>) | 无可调参数 | -| [SMAA/SMAA_Ultra.hlsl](<../../../src/Effects/SMAA/SMAA_Ultra.hlsl>) | 无可调参数 | - -### XeSS(3 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [XeSS/XeSS_OpticalFlow.hlsl](<../../../src/Effects/XeSS/XeSS_OpticalFlow.hlsl>) | 无可调参数 | -| [XeSS/XeSS_ZeroMV.hlsl](<../../../src/Effects/XeSS/XeSS_ZeroMV.hlsl>) | 无可调参数 | -| [XeSS/XeSS_ZeroMV_Jitter.hlsl](<../../../src/Effects/XeSS/XeSS_ZeroMV_Jitter.hlsl>) | 无可调参数 | - -### xBRZ(6 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [xBRZ/xBRZ_2x.hlsl](<../../../src/Effects/xBRZ/xBRZ_2x.hlsl>) | 无可调参数 | -| [xBRZ/xBRZ_3x.hlsl](<../../../src/Effects/xBRZ/xBRZ_3x.hlsl>) | 无可调参数 | -| [xBRZ/xBRZ_4x.hlsl](<../../../src/Effects/xBRZ/xBRZ_4x.hlsl>) | 无可调参数 | -| [xBRZ/xBRZ_5x.hlsl](<../../../src/Effects/xBRZ/xBRZ_5x.hlsl>) | 无可调参数 | -| [xBRZ/xBRZ_6x.hlsl](<../../../src/Effects/xBRZ/xBRZ_6x.hlsl>) | 无可调参数 | -| [xBRZ/xBRZ_Freescale.hlsl](<../../../src/Effects/xBRZ/xBRZ_Freescale.hlsl>) | 无可调参数 | - -### 根目录(4 个) - -| 效果器文件 | 参数默认值 | -| --- | --- | -| [ACNet.hlsl](<../../../src/Effects/ACNet.hlsl>) | 无可调参数 | -| [Bilinear.hlsl](<../../../src/Effects/Bilinear.hlsl>) | 无可调参数 | -| [Nearest.hlsl](<../../../src/Effects/Nearest.hlsl>) | 无可调参数 | -| [k7_modernAnime_FHD_x2.hlsl](<../../../src/Effects/k7_modernAnime_FHD_x2.hlsl>) | 无可调参数 | - -## 后续建议 - -优先统一运动可视化默认值,并解决 Jinc 的默认值与刻度矛盾;这两项直接关系到“双击恢复效果器默认值”的可靠性。 - -其余默认值主要是产品和画质取舍:CRT 默认即有风格化处理、Deband 默认带颗粒、不同锐化算法的强度不能横向按数值比较。没有对应素材和运行观察,不应仅凭默认值大小就判定错误或批量改成中性值。 - -这份表可以作为后续逐项确认依据;当前仅交付 review,不把任何建议直接写回效果器文件。 diff --git a/docs/experimental/reviews/20260905-hdr-compatibility-second-REVIEW.md b/docs/experimental/reviews/20260905-hdr-compatibility-second-REVIEW.md deleted file mode 100644 index 1ded90b04..000000000 --- a/docs/experimental/reviews/20260905-hdr-compatibility-second-REVIEW.md +++ /dev/null @@ -1,292 +0,0 @@ -# HDR 兼容路线二次 review:原生 HDR、RTX HDR 与补帧 - -日期:2026-09-05。状态:**研究文档,未修改、构建、运行或部署程序,未执行 GPU/显示器测试。** - -本次已阅读用户指定任务评估 Magpie PR #13 兼容性(本地任务记录)及其原报告(工作区 `reports/Magpie_PR13_HDR_兼容性与方案可行性研究.md`),复核当前本地源码、捆绑 Intel SDK 文档和相关官方资料。保留原报告,不覆盖其历史结论。 - -本地基线为 `source` 的 `0.6.5` 分支,HEAD `84d9f6abb8203a79f9a44d9a66984a293e68856d`,以及当前 r5 / fix2 未提交工作区。原报告的 PR 合并实验和 GPU 观察属于此前调查,本次没有重复执行;本文新增结论区分代码事实、SDK 文档约束和设计推论。 - -## 结论 - -**RTX HDR 值得保留,但定位应是“把 SDR 内容增强为 HDR”的可选能力,而不是所有 HDR 兼容问题的统一补丁。** 原生 HDR 若先被压成 8-bit SDR,后面再用 RTX HDR,也无法据此恢复原来的高光和宽色域信息。 - -建议的优先顺序是:**颜色正确的 HDR 捕获与直通呈现 → 少量明确兼容的原生 HDR 效果 → 现有 SDR 效果兼容路径 → 可选 RTX HDR 增强 → 按补帧后端扩展组合。** 这样可以先交付真实 HDR 的基础可用性,不必等所有效果器都重写,也不把基础 HDR 支持绑定到 NVIDIA。 - -相较原报告,最需要调整的是两点: - -1. “所有补帧之后统一加 RTX HDR,然后统一 FP16 呈现”不适用于当前 XeSSFG 架构和 SDK 格式合同。XeSSFG 支持 HDR10,但不支持 FP16/scRGB;需要独立设计这条路径。 -2. 先做不增强画面的 HDR 直通基线,再接入 AI 增强。否则颜色错误、显示器映射错误与 AI 效果变化混在一起,很难定位问题,也无法建立可信的原图对比。 - -## 对原报告的复核与修正 - -| 原报告判断 | 本次结论 | 后续影响 | -| --- | --- | --- | -| PR #13 不宜原样合并;固定 4.5 的 HDR↔SDR 桥有损 | 保留 | 借鉴捕获与格式传递设计,不能把成对 shader 当作 HDR 保真转换 | -| HDR 输出不应由 HDR 捕获方式单独决定 | 保留并细化 | 捕获格式、内容类型、目标显示能力、补帧后端分别参与决策 | -| RTX HDR 适合近期兼容路线 | 有条件保留 | 适合 SDR→HDR 增强;基础原生 HDR 支持应先独立建立 | -| 自动尺寸适配应保留 | 保留 | 基础缩放必须支持目标颜色合同,不能整体关闭自动 Bicubic 规避格式问题 | -| RTX HDR 统一放在所有 FG 之后 | 修正 | DLSSFG 可研究后置,XeSSFG 代理交换链需要 HDR 在送入 SDK 前准备好 | -| XeSSFG 的困难主要是现有 R8 写死 | 加重 | SDK 本身不接受 FP16/scRGB;不是改一个格式常量就能解决 | -| R10/PQ 需要设置 HDR metadata | 修正 | 正确像素编码及颜色空间声明是必要环节;不能将 SetHDRMetaData 作为可靠显示的保证 | -| TrueHDR 四项参数范围及部分 Live 分类 | 降级为待确认 | 原报告范围来自头文件镜像;集成前必须以采用版本的官方 SDK 合同复核 | -| 非 AI 的逆映射画质通常低于 RTX HDR | 删除这一概括 | 无本项目对照证据;不同内容、时序和目标亮度下应分别评价 | -| 原生 HDR 要等完整渲染图改造 | 缩小首阶段范围 | 先做直通及少量白名单节点,再扩展;格式和颜色元数据从边界开始贯通 | - -PR #13 当前可在[原 PR](https://github.com/SAOG0721/Magpie/pull/13)查看;本次查阅仍指向原报告讨论的头提交 `9fa5387b552df7d7db40b7bc16fa83c39a2873bd`。PR 讨论中的单设备成功观察不能覆盖当前分支的光流、补帧、UI 与截图组合。 - -## 首先区分三种含义 - -| 名称 | 实际目标 | 不能由什么推断 | -| --- | --- | --- | -| HDR 显示/捕获兼容 | 捕获和呈现时亮度、色域及编码正确 | 不能由“Windows HDR 已开启”推断窗口内容就是真 HDR | -| 原生 HDR 效果处理 | 尽可能保留原始 HDR 信息并施加效果 | 不能由“纹理改成 FP16”推断算法已兼容 HDR | -| SDR→HDR 增强 | 从 SDR 推算或映射出更大的亮度范围 | 不能称作恢复曾被丢弃的原生 HDR 数据 | - -同一个 HDR 桌面可以同时包含 SDR 应用、HDR 视频和 UI。窗口捕获的容器格式并不自动携带每块内容的原始创作意图。建议内容来源允许“自动/按 SDR/按 HDR”,自动识别证据不足时保留“未知”,不要仅凭像素最大值或显示器 HDR 开关自动启用 RTX HDR。以上为产品与架构建议,尚无分类器实现。 - -## 补充:具体提供什么界面,是否要用户手搭三个效果器 - -2026-09-05,用户询问是否应提供 HDR→SDR、SDR→HDR、RTX HDR(SDR 输入)三个效果器,由用户自己排列。本节明确本报告的**设计建议**,尚未由用户选定或实施。 - -建议做成 **效果组的 HDR 输出设置,加上由软件管理的必要颜色转换**。普通用户继续添加去噪、锐化等效果器,在组设置中决定是否使用 HDR 增强;无需知道转换器应该放在哪一行。 - -### 三项功能的分工 - -| 功能 | 实际作用 | 推荐呈现方式 | -| --- | --- | --- | -| HDR→SDR 色调映射 | 将原生 HDR 映射到 SDR,使 SDR 输出或旧效果链能够使用;这个步骤有损 | 软件在用户选择 SDR 输出/SDR 兼容处理后插入;可在高级设置调节映射策略 | -| SDR→HDR 显示适配 | 把 SDR 正确放到 HDR 输出环境中,匹配白点和颜色编码;默认不创造高光细节 | 输出阶段自动处理,通常无需成为用户手动添加的效果器 | -| RTX HDR 增强 | 对 SDR 内容做可选的 HDR 增强 | 效果组中的独立开关和参数卡;软件负责放在经验证的执行位置 | - -“SDR→HDR”这个名字容易混淆两种行为:**正确显示 SDR** 和 **增强为 HDR**。本方案把前者称为“显示适配”,把后者称为“HDR 增强”。若以后增加非 RTX 的逆色调映射算法,它会成为增强方式的另一个选项。 - -普通显示适配与 RTX HDR 不是要求串联的两次增强。RTX HDR 的结果仍可能需要转换成呈现器要求的格式,但那是输出适配,不再把结果当 SDR 重做一次提升。 - -### 用户看到的建议结构 - -效果组页面末尾增加“HDR 输出与增强”分区: - -- **输出方式:跟随显示器/SDR。** 跟随显示器表示选择合适的输出环境,不意味着自动把所有 SDR 内容变成增强 HDR。 -- **HDR 增强:关闭/RTX HDR。** 仅在来源按 SDR 处理、目标支持 HDR 且后端能力满足时可用;选中后展开相应参数,沿用效果参数页的交互。 -- **HDR 内容兼容方式:保持原生 HDR/转为 SDR 兼容处理。** 出现不兼容的效果时明确指出,由用户决定采用有损兼容还是停用该效果,不暗中丢弃 HDR 信息。 - -“来源内容:自动/按 SDR/按 HDR”更适合放在程序配置或当前会话的高级设置中,因为同一个效果组可能用于不同内容。识别证据不足时让用户覆盖,不能由效果组名称决定来源类型。 - -具体控件布局可继续沿用现有收起项。RTX HDR 可以拥有与效果器相同风格的参数卡,但其受约束的执行阶段由软件管理,首版不作为可以随意拖动、重复插入的普通节点。 - -### 典型情况下软件怎样组装 - -| 用户意图 | 组装后的概念流程 | -| --- | --- | -| SDR 内容正常显示在 HDR 显示器上,不做增强 | SDR 捕获适配 → 现有效果组 → SDR 的 HDR 显示适配 → 显示 | -| SDR 内容使用 RTX HDR | SDR 捕获适配 → 现有效果组 → RTX HDR → 必要的输出编码适配 → 显示 | -| 原生 HDR 内容,只使用已确认兼容的效果器 | 保留 HDR 的捕获 → HDR 兼容效果组 → 目标显示适配 → 显示 | -| 原生 HDR 内容必须使用旧 SDR 效果器 | 用户选择 SDR 兼容 → HDR→SDR 映射 → 旧效果组 → SDR 显示或放入 HDR 输出环境 | - -最后一条不保留完整原生 HDR,输出到 HDR 显示器也不会改变这一事实。若以后允许再加 RTX HDR,那应明确属于“有损转换后重新增强”,不能默认启用或称为恢复原生 HDR。前文的差分回注实验是另一条待研究路线。 - -此表只说明颜色与效果之间的关系;存在补帧时,HDR 增强和输出适配的位置仍按下文各后端的合同确定,不能把表中的“末端”理解为永远在所有 SDK 补帧之后。 - -### 对内部代码的具体要求 - -启动时读取来源设置、效果器能力、HDR 增强选择及呈现后端要求,生成受校验的执行计划。普通效果链仍复用现有实现;必要的捕获适配、SDR 兼容入口、增强阶段、尺寸适配和输出转换由运行器管理。 - -内部可以分别实现 HDR→SDR、SDR 显示适配和 TrueHDR 后端,但它们不等同于三个任意可串联的列表项。增强结果与显示适配共享输出合同,避免重复转换。遇到尚未支持的组合,在启动前指出具体节点及可用处理方式;Live / Restart 按实际资源及 SDK 参数合同判断。 - -高级手动转换节点以后可以作为可选能力提供,但必须检查相邻节点的颜色语义、重复增强和输出要求,不能把非法排列的黑屏/偏色风险完全交给用户。这项高级模式不作为首版 HDR 的必要条件。 - -## 新发现一:XeSSFG 需要 HDR10,独立覆盖层却可用 FP16 - -捆绑的 `XeSS-SDK-3.0.1` 中,XeSSFG 开发指南明确要求 HDR 使用 `R10G10B10A2_UNORM`;back buffer、HUD-less color 与提供给 SDK 的 UI-only texture 要匹配 HDR10 / BT.2100。文档明确排除 FP16 HDR 和 scRGB。本地依据(工作区 `dependencies/XeSS-SDK-3.0.1/doc/xess_fg_developer_guide_english.md:626`)、[Intel 官方指南](https://github.com/intel/xess/blob/main/doc/xess_fg_developer_guide_english.md)。 - -当前 Magpie 的 XeSSFG 在多个位置共用 RGBA8 常量,包括代理交换链和独立 DirectComposition 表面。[当前格式与创建](<../../../src/Magpie.Core/XeSSFGPresenter.cpp#L17>)、[独立表面](<../../../src/Magpie.Core/XeSSFGPresenter.cpp#L358>)。 - -Microsoft 的 DirectComposition surface 支持格式列表包含 FP16,未包含 R10。因此应拆开“SDK 主画面格式”和“外部覆盖表面格式”:候选组合是 **HDR10/PQ 主交换链 + 正确处理颜色和透明度的 FP16 外部合成层**。外部 DirectComposition 层并不是提供给 XeSS SDK 的 UI-only texture,两者不能套用同一格式约束。[官方表面格式说明](https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositiondevice2-createvirtualsurface)。 - -这是从两份合同和当前架构得到的可行方向,**不是已经证明本程序混合合成呈现正确**。仍需验证 DWM 合成、UI 白点、透明混合、遮罩光标、缩放偏移和驱动行为。不能简单把 `COLOR_FORMAT` 全局改成 R10 或 FP16。 - -建议 XeSSFG 的 HDR 增强候选顺序为: - -```text -SDR 内容 → 现有 SDR 效果与最终尺寸适配 - → RTX HDR(输出合同先核对) - → 必要的色域/传递函数转换 → R10 HDR10 - → XeSSFG 代理交换链 → 显示 - -外部覆盖层:独立颜色适配后的原图对比、工具栏和光标 -``` - -优点是 RTX HDR 只处理基础帧,不按补帧倍率重复运行;代价是 HDR 增强的时间变化进入补帧,必须观察场景切换、亮度泵动和运动伪影。当前代码没有“截获所有 SDK 内部生成帧并再执行任意后处理”的通用入口,不能在流程图里凭空假定存在。 - -## 新发现二:原生 HDR 可以先做小范围白名单 - -XeSS-SR 官方指南接受线性 HDR,说明 scRGB 与曝光约定,并建议使用 FP16 颜色输入。当前 Magpie 的 XeSS SR 实现却限制 R8 输入/输出、启用 `XESS_INIT_FLAG_LDR_INPUT_COLOR`,执行时曝光固定 1.0。本地 SDK 颜色合同(工作区 `dependencies/XeSS-SDK-3.0.1/doc/xess_sr_developer_guide_english.md:299`)、[Intel 官方指南](https://github.com/intel/xess/blob/main/doc/xess_sr_developer_guide_english.md)、[本地格式限制](<../../../src/Magpie.Core/XeSSZeroMVUpscaler.cpp#L199>)、[LDR 初始化](<../../../src/Magpie.Core/XeSSZeroMVUpscaler.cpp#L433>)。 - -因此 XeSS-SR 可作为以后审核原生 HDR 效果的候选,不能把它与 XeSS-FG 的格式限制混为一谈。当前 ZeroMV/深度等输入近似仍影响画质,SDK 支持 HDR 并不等于当前封装直接可用。 - -建议第一批白名单从以下范围开始: - -1. 原图直通与明确按线性 HDR 工作的基础插值;先确认恒等路径和目标尺寸处理。 -2. 经逐项合同审核的原生 SR 后端,分别检查输入编码、曝光、输出范围及历史重置。 -3. 少量数学意义明确的 shader,再逐个扩展;含 `saturate`、固定 SDR gamma、8-bit 中间结果的节点默认不自动放行。 - -一个容易误判的本地例子是 `RTXVideoDenoiser.cpp`:外围允许浮点纹理,但内部 `VideoSuperRes` 图像仍分配为 `NVCV_U8`,浮点传输按 255 倍缩放。**接受 FP16 资源不等于保留高于 1 的亮度及宽色域。** 现有 Maxine VideoSuperRes 集成也不能据此视为 RTX Video TrueHDR 已接入。[实现依据](<../../../src/Magpie.Core/RTXVideoDenoiser.cpp#L102>)。 - -## 颜色基线:不能共用一个固定白点 - -Windows 的 HDR scRGB 约定以线性值 1 对应 80 nit;FP16 只是承载方式,颜色空间声明与实际像素处理必须一致。[Microsoft HDR / Advanced Color 指南](https://learn.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range)。 - -`DISPLAYCONFIG_SDR_WHITE_LEVEL` 按显示路径查询,换算为 `SDRWhiteLevel / 1000 × 80 nit`。因此原 PR 固定的 4.5 只代表特定 360 nit 参考,不是跨设备常数。[结构体官方说明](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-displayconfig_sdr_white_level)。 - -还应区分 **源显示器的 SDR 白点** 与 **目标显示器的 SDR 白点**。例如源窗口在一个 HDR 显示器上,Magpie 输出在另一个显示器上,两个白点设置可能不同。 - -在“已知捕获确实是按源白点嵌入 scRGB 的 SDR 内容”这一前提下,可用以下关系检查转换设计: - -```text -SdrEncoded = sRGB_OETF(CaptureScRGB × 80 / SourceSdrWhiteNits) -OutputScRGB = sRGB_EOTF(ProcessedSdrEncoded) × TargetSdrWhiteNits / 80 -``` - -这是条件成立时的数学归一化关系,**不是所有 WGC 内容都可无损反推的保证**。混合内容、应用自身映射、ICC/系统处理以及未知捕获语义需要单独确认。真实 HDR 的绝对亮度也不能直接套用 SDR 白点重标定,否则会把 HDR 本身的亮度关系改掉。 - -目标能力可以读取 `IDXGIOutput6::GetDesc1`,但小面积峰值 `MaxLuminance` 与全屏亮度 `MaxFullFrameLuminance` 含义不同;信息通常来自 EDID 或覆盖设置,不能视为本次实测校准。建议保留合理性校验和用户覆盖入口。[DXGI_OUTPUT_DESC1](https://learn.microsoft.com/en-us/windows/win32/api/dxgi1_6/ns-dxgi1_6-dxgi_output_desc1)。 - -输出颜色空间应通过匹配的 API 声明,像素转换本身仍由管线执行。不能将 `SetColorSpace1` 当作把错误编码自动转换正确的 shader。[SetColorSpace1](https://learn.microsoft.com/en-us/windows/win32/api/dxgi1_4/nf-dxgi1_4-idxgiswapchain3-setcolorspace1)。Microsoft 已不推荐应用依赖 `SetHDRMetaData`;metadata 可能不送达或被显示器忽略,应依据目标能力做恰当映射。这修正了原报告将其列为 R10 路线必要步骤的说法。[SetHDRMetaData 官方说明](https://learn.microsoft.com/en-us/windows/win32/api/dxgi1_5/nf-dxgi1_5-idxgiswapchain4-sethdrmetadata)。 - -## RTX HDR 的可行性与尚未确认的合同 - -NVIDIA 当前公开入门页列出 RTX Video SDK 1.1、DX11/DX12 等 API、RTX 20 系列及更新 GeForce 支持,并描述 Rec.709 SDR 到 HDR10 兼容 Rec.2020 的增强目标。这支持继续调查该集成方向,但不保证任意硬件、驱动和其他 NGX 功能组合均可初始化。[官方入门页](https://developer.nvidia.com/rtx-video-sdk/getting-started)。 - -2024 年 NVIDIA 技术文章正文又以 sRGB→scRGB 描述 RTX Video HDR。两种表述并不必然矛盾:算法表面、应用工作空间和最终显示编码是不同层次;**产品页写 HDR10,不能据此猜测 SDK 输入/输出纹理一定是 R10/PQ**。[NVIDIA 技术文章](https://developer.nvidia.com/blog/enhancing-low-resolution-sdr-video-with-the-nvidia-rtx-video-sdk/)。 - -| 需要确认 | 为什么会改变实现 | -| --- | --- | -| 所采用官方 SDK 的准确输入/输出格式、色域、传递函数和亮度单位 | 决定前处理、输出适配,以及能否直接接 FP16 或必须额外转换 | -| API 版本、驱动与适配器上的 TrueHDR capability | RTX 型号判断不足;失败要能区分驱动、运行库、功能不可用和初始化错误 | -| Contrast、Saturation、MiddleGray、MaxLuminance 的范围、默认值及单位 | 原报告的 0–200、10–100、400–2000 等镜像值仅作查找线索,不宜直接成为产品约束 | -| 参数在 evaluate 时还是 create 时读取 | 决定 Live / Restart;不能先依据参数名称认定“对比度当然可以 Live” | -| 多实例、线程、尺寸变化与历史处理要求 | 影响直通后台保持、动态窗口和与降噪/补帧并存 | -| 与 DLSSNR、DLSSFG、VSR 的运行库和核心生命周期共存 | 影响 DLL 选择、初始化和最终 shutdown;失败不应损坏另一功能 | -| 随应用分发的组件及采用版本许可 | 在实际集成版本确定后核对,不凭网页概述作结论 | - -2024 年 NVIDIA 官方论坛回复曾确认 DLSS 与 RTX Video SDK 的 TrueHDR capability 查询存在已知兼容问题,并表示计划在后续 SDK 处理;本次没有找到足以证明当前组合已经修复的明确版本结论。它是需要验证的历史线索,不能当作当前版本必然不兼容的证据。[NVIDIA 官方人员回复](https://forums.developer.nvidia.com/t/compatibility-issue-between-dlss-and-rtx-video-sdk-nvsdk-ngx-parameter-truehdr-available-fails/313309)。 - -本地已有集中管理的 D3D12 NGX 生命周期。选择 D3D11 TrueHDR 可能减少某些互操作成本,D3D12 可能利于与已有核心协调;二者仍需根据实际 SDK 合同比较,不能承诺 D3D11 自动避开 NGX 冲突。[当前核心初始化](<../../../src/Magpie.Core/NgxD3D12Core.cpp#L118>)、[释放逻辑](<../../../src/Magpie.Core/NgxD3D12Core.cpp#L153>)。 - -本次没有下载或安装新 SDK,没有改运行库或调用 capability 探测;上述未知项保留到用户决定推进集成之后。 - -## 补帧与 HDR 的顺序:分别设计 - -| 组合 | 推荐调查路线 | 关键限制 | -| --- | --- | --- | -| 无补帧 + SDR→HDR | SDR 效果、最终尺寸适配 → HDR 增强 → 输出适配 → UI/光标 | 最适合先建立 AI 增强基线 | -| DLSSFG + HDR 在补帧前 | SDR 效果 → HDR 增强 → 经确认支持的 DLSSFG HDR 输入 → 输出 | 每个基础帧增强一次,但需核对 SDK 格式、颜色语义及运动/深度合同 | -| DLSSFG + HDR 在补帧后 | SDR 效果 → DLSSFG → 每张输出帧 HDR 增强 → 输出 | 更接近旧报告;需处理所有生成帧与真实帧,增强吞吐量随实际输出帧率增加 | -| XeSSFG + HDR | SDR 效果 → HDR 增强及 HDR10 编码 → XeSS 代理交换链 | SDK HDR 输入是 R10/PQ 路线,不能直接统一 FP16 | -| 原生 HDR + 补帧 | 原生 HDR 白名单链 → 后端要求的编码 → FG | 首阶段不承诺组合支持,先证明无 FG 的颜色基线 | - -本地 DLSSFG 会根据输入格式设置 `NativeBackbufferFormat`,这只是代码传参事实,不能证明 FP16、HDR10 及当前代理输入方式全部已受支持。[传参位置](<../../../src/Magpie.Core/DLSSFrameGenerator.cpp#L557>)。 - -后置 AI 增强还会新增队列同步及延迟,不能只比较单次 dispatch 的耗时。例如 60 个基础帧按 x4 生成并实际提交 240 个输出帧时,逐输出帧增强的调用量可达前置的四倍;这是调用数量推算,不是实测性能比例。 - -## 新方案候选:保留原生 HDR,并回注 SDR 效果的改变量 - -这是对“原生 HDR 想使用已有 SDR 效果”问题的补充研究,**不是已验证算法,也不是对 RTX HDR 的画质排名**。 - -原路线把 HDR 全部压到 SDR,效果处理后再往上扩展。可以探索保留完整原始 HDR 分支,只让 SDR 代理进入旧效果链,再将两者之间的改变量映射回 HDR: - -```text -原始 HDR H ───────────────────→ HDR 基础尺寸适配 G(H) ─┐ - └→ 色调映射 T(H) → 同尺寸 SDR 基线 P0 ├→ 回注 → HDR 输出 - └→ 旧效果链 → 结果 P1 → 差分 ─┘ -``` - -用于讨论的形式为: - -`Hout = G(H) + strength × mask × [L(P1) − L(P0)]` - -其中 `P0` 和 `P1` 必须对应同一捕获时刻、同一目标网格和一致的基准尺寸适配;`L` 是两者共用的 SDR 解码与参考白嵌入,而非色调映射的“完美逆函数”。对无改动效果,要求 `P1 = P0`,此时输出回到 HDR 基线 `G(H)`,不会仅因经过 SDR 兼容分支就删除原图高光。最终显示映射仍可能受目标色域和亮度限制。 - -有价值的地方是原 HDR 数据始终保留,不必让 AI 重新猜测被兼容转换抹掉的信息。与当前 DLSSNR 的“重建残差”思路有相似之处,但作用域、颜色空间和时序不同,不能直接复用现有残差参数或算法后宣称成立。 - -主要风险: - -- 色调映射在高光处压缩强,SDR 差分与 HDR 中应施加的改变量不等价;不能保证去噪/锐化在高光区保留相同强度。 -- 直接 RGB 回注可能产生负值、色偏或过冲。亮度比例/亮度差、色度保护、亮度相关 mask 均需比较,不能先选一个公式当通用标准。 -- 自动曝光变化会引入时间闪烁,需要一致且稳定的代理生成条件。 -- CRT、几何扭曲、像素化、整体调色和时间重建不一定适合这种局部改变量假设;先限制少量空间效果。 -- 补帧时原 HDR 参考和生成 SDR 结果时间不一致,不能直接回注;首个原型应关闭补帧组合研究。 - -建议作为 **原生 HDR 兼容实验的独立分支**,在正确的 HDR 直通建立后再评估。它不应阻塞基础 HDR,也不宜第一阶段就对所有效果器开放。 - -跨厂商 SDR→HDR 可另外研究确定性映射,ITU-R BT.2446-1 报告可作为方法资料;不预设其画质一定低于 AI。普通“把 SDR 白点放进 HDR 容器”也有兼容价值,但应明确没有新增高光细节。[ITU 官方报告入口](https://www.itu.int/pub/R-REP-BT.2446-1-2021)。 - -## 当前代码中 HDR 迁移必须处理的边界 - -以下是切入 HDR 后的阻断点,不表示当前 SDR 运行已经因此出错。 - -| 边界 | 当前代码事实 | 建议 | -| --- | --- | --- | -| 捕获 | WGC 帧池和输出使用 BGRA8 | HDR 捕获建立 FP16 路径,帧池/中间资源/副本合同一致 | -| 帧引导 | 在效果前读取捕获帧;NVOF 内部 BGRA8 并直接复制输入 | 保留 HDR 主图,另建明确定义的光流代理;不要把 FP16 直接 CopyResource 到 BGRA8 | -| 效果边界 | 现有节点不能仅靠纹理位深表达支持范围 | 校验实际资源及语义;不兼容时给出原因,转换集中在兼容边界 | -| 自动尺寸适配 | 会追加 Bicubic 等尺寸处理 | 提供颜色合同匹配的适配节点,保留原有目标矩形行为 | -| 跨线程共享 | 当前颜色共享资源为 R8,帧槽配有锁和完成同步 | 将颜色格式与元数据随资源代次传递;同时核对副本、视图、缓存及生命周期 | -| 呈现 | Adaptive、CompSwapchain 以及 XeSSFG 均有 R8 固定点 | 按输出合同创建,XeSS 主链与外部合成层分别选格式 | -| UI/光标 | SDR 数值与 R8 背景复制假设仍存在 | 明确 UI 白点、解码和混合;背景相关光标取当前可见场景 | -| 截图 | 通用 PNG 路径断言 R8 | 提供明确映射后的 SDR PNG;HDR 保存选择真正支持 HDR 的格式与元数据,不能只移除断言 | -| 窗口跨屏/HDR 开关 | 需要更新输入与输出能力 | 使用资源代次处理变化,避免新格式帧误入旧交换链;回退可解释 | - -代码依据:[WGC 帧池](<../../../src/Magpie.Core/GraphicsCaptureFrameSource.cpp#L323>)、[NVOF 复制](<../../../src/Magpie.Core/NvidiaOpticalFlowProvider.cpp#L699>)、[共享纹理](<../../../src/Magpie.Core/Renderer.cpp#L1825>)、[AdaptivePresenter](<../../../src/Magpie.Core/AdaptivePresenter.cpp#L32>)、[CompSwapchainPresenter](<../../../src/Magpie.Core/CompSwapchainPresenter.cpp#L150>)、[截图格式限制](<../../../src/Magpie.Core/TextureHelper.cpp#L325>)。 - -Microsoft 的捕获指南同样建议 HDR 捕获处理中采用 FP16,避免中间步骤造成过曝或截断;这不表示已有 BGRA8 光流与 SDR 效果应无差别改成 FP16,而是应把有损代理与保留 HDR 的主分支分清。[官方屏幕捕获指南](https://learn.microsoft.com/en-us/windows/apps/develop/media-authoring-processing/screen-capture)。 - -建议最小帧合同包括:像素格式、传递函数、原色/色域、亮度尺度或参考白、内容分类及其可信度、alpha 约定、捕获帧标识、捕获时间、资源代次和源/目标显示路径。先在捕获、兼容转换、共享发布和呈现边界落实,不必首个版本就重写全部 170 个随包效果器。 - -## 与工具栏原图对比共用设计 - -用户新提出的 pass through 很适合作为颜色基线入口,但两条分支要使用 **同一个目标输出合同**:对比切换只改变被显示的内容,不切换 Windows HDR,不在每次点击时重建交换链。 - -- SDR 源:原图使用不增强的 SDR→目标显示适配;另一分支执行效果和可选 RTX HDR。 -- 原生 HDR 源:原图保留捕获 HDR,只做必要的裁剪、基础尺寸和显示适配,不能先转 SDR 再当“原图”。 -- 对比期间 RTX HDR 和其他已启用效果仍继续处理;切回使用当前结果。 -- XeSSFG:原图通过独立合成层覆盖,代理交换链保持处理后 HDR10 输入;需验证下层在遮挡时仍持续处理。 - -工具栏的完整方案见[直通对比 review](<20260905-toolbar-pass-through-REVIEW.md>)。HDR 的 UI 白点、光标和截图也应复用这一“当前可见场景”边界,避免分别维护互相矛盾的画面来源。 - -## 建议的产品设置与报错分类 - -用户界面先围绕内容和目标描述:来源自动/SDR/HDR;输出跟随显示器/SDR;SDR 的 HDR 增强关闭/RTX HDR。FP16、PQ、交换链等信息放在详情中,除非用户确实需要手动选择来排错。 - -| 分类示例 | 面向用户的建议提示 | 详情中保留 | -| --- | --- | --- | -| 目标未启用 HDR | “当前显示器未启用 HDR,可在 Windows 显示设置中开启,或使用 SDR 输出。” | 显示路径、当前能力与色彩空间 | -| 捕获方式不支持保留 HDR | “当前捕获方式无法保留 HDR 信息,请改用支持 HDR 的捕获方式。” | 捕获后端、实际格式、能力查询结果 | -| 效果不兼容 | “此效果暂不支持原生 HDR;可停用此效果或选择 SDR 兼容处理。” | 具体效果及输入/输出合同差异 | -| RTX 功能不可用 | 根据实际原因提示更新驱动、检查运行库或改用不增强的显示方式 | GPU/驱动、SDK 版本、capability 与原始错误码 | -| 来源不明确 | “无法确认内容是否为 HDR,请选择来源类型后启用 HDR 增强。” | 判定来源及其可信度 | -| 补帧格式组合不支持 | “当前 HDR 输出方式与所选补帧不兼容。”并列出实际可用选择 | FG 后端、格式与颜色语义 | -| 显示环境变化 | “显示器状态已变化,正在重新适配输出。”失败时给出可用回退 | 变化前后合同、资源代次和失败阶段 | - -这些是待实施的分类方向,复用现有错误详情入口即可。不要把所有 capability 失败都提示为“显卡不支持”,也不要无提示地将真实 HDR 截成 SDR 后继续显示。 - -## 推荐实施顺序与放行条件 - -| 阶段 | 范围 | 必须先证明什么 | -| --- | --- | --- | -| 0:合同核验 | 确定采用的 SDK 版本、TrueHDR 格式与参数、NGX 共存方式 | 文档/样例支持拟定输入输出;未确认参数不标为 Live | -| 1:HDR 基础 | WGC FP16、HDR 直通、基础尺寸处理、输出适配、UI 与光标 | 灰阶、参考白、高光、宽色域和跨屏行为正确;不依赖 AI | -| 2:受限原生效果 | 小范围白名单,单独审核 SR 与 shader | 恒等/默认行为可解释;不夹断亮度与色域 | -| 3:SDR 兼容与 RTX HDR | 明确来源的 SDR 效果链;无补帧 HDR 增强 | 不重复增强原生 HDR;参数语义及 Live / Restart 有据可查 | -| 4:补帧组合 | 分别接 DLSSFG 与 XeSSFG,不共用错误格式假设 | 每个组合的颜色、吞吐量、时序、历史及直通覆盖通过验证 | -| 5:可选实验 | SDR 改变量回注、跨厂商逆映射 | 在明确内容样本上评估收益与失败案例,不承诺通用兼容 | - -建议后续用户验收材料包含已知 nit 的灰阶与高光、超出 SDR 的颜色、细文本和光标、静态/运动画面、快速场景切换、SDR 与原生 HDR 来源、双显示器白点差异、系统 HDR 切换,以及各 FG 倍率。应区分资源/数值检查、屏幕视觉检查和算法主观画质比较。 - -**本次只有源代码和文档核查,没有执行上述验收,也没有测得 RTX HDR 延迟、效果质量或 NGX 共存结果。** - -## 留待用户 review 的决策 - -本次推荐先批准路线原则,而非立即批准整个集成:保留 RTX HDR 方向;基础 HDR 独立建立;XeSSFG 单独采用 HDR10 合同;原生 HDR 默认旁路 SDR→HDR 增强;差分回注留作实验。 - -是否把 RTX HDR 的视觉增强优先于首批原生 HDR 效果、是否研究差分回注、是否首个 HDR 版本就包含 XeSSFG,均可在 review 后决定。用户暂时离开期间,本任务到文档交付为止,不继续执行程序修改。 diff --git a/docs/experimental/reviews/20260905-naming-and-interaction-REVIEW.md b/docs/experimental/reviews/20260905-naming-and-interaction-REVIEW.md deleted file mode 100644 index ba047ff8f..000000000 --- a/docs/experimental/reviews/20260905-naming-and-interaction-REVIEW.md +++ /dev/null @@ -1,185 +0,0 @@ -# 命名与交互 review:效果组、启用动作和显示模式 - -日期:2026-09-05。基于当前 0.6.5 r5 local / fix2 工作区;HEAD 为 `84d9f6abb8203a79f9a44d9a66984a293e68856d`,包含尚未提交的修改。本次仅调查和交付文档,未修改、编译、运行或部署程序。 - -## 结论 - -建议将面向用户的“缩放模式”改为“效果组”。当前一个组可以组合超分辨率、去噪、锐化、补帧、限帧等处理,名称不应暗示它只能改变图像尺寸。主要收益是把“处理配置”“启动动作”“全屏/窗口显示方式”和“尺寸缩放”分开。 - -建议定义为:**效果组是一组按顺序配置的效果器及其参数、尺寸设置;启用时按该配置处理目标窗口。** 可以只有一个效果器;编辑中的组可以为空,但空组不能启用。“包括多个效果器”容易让用户误以为至少需要两个。另需说明:帧生成等功能有特殊执行位置,列表顺序不代表每个后端阶段都严格在该行执行。 - -只修改用户可见文本时,配置兼容风险较低。不能把这次改名扩大为对 `scalingModes`、`scalingMode`、快捷键动作名或效果器 ID 的全局替换,否则会触及导入、配置恢复、程序配置引用和参数保存。 - -## 建议术语 - -| 对象 | 建议用语 | 用途和边界 | -| --- | --- | --- | -| 保存的效果组合 | 效果组 | 替代产品界面中的“缩放模式” | -| 组内单个处理单元 | 效果器 | 将“添加效果”等容易混用的组件名称统一为“添加效果器” | -| 启动/结束处理 | 启用/停用效果组 | 操作对象是某个目标窗口,不会禁用或删除整个组的配置 | -| 处理中的窗口 | 目标窗口/源窗口 | 选取和捕获对象;“前台窗口”只在描述启动时使用 | -| Magpie 展示的结果 | 输出窗口/输出画面 | 避免和源应用窗口混淆 | -| 输出显示方式 | 全屏模式/窗口模式 | 是 Magpie 的显示模式,和源应用是否全屏分开 | -| 尺寸运算 | 缩放、缩放倍数、输出尺寸、等比适应 | 保留数学和图像处理含义,不替换成“效果组” | -| 按程序保存的设置 | 程序配置/全局默认配置 | 程序配置选择效果组,同时保存捕获、光标、性能等设置 | -| 数值恢复 | 恢复效果器默认值 | 单个参数,和“恢复本次运行初始值”“恢复默认效果组”分开 | - -英文可考虑 `Effect group`,技术说明中可补充 `ordered effect chain`;繁体中文可用“效果組”。这是后续用词建议,尚未要求同步修改所有语言。效果器文件和模型的已有英文名称无需跟着翻译或改名。 - -## 主要文案草案 - -下表为待实施的文案建议,不是已修改的资源。对快捷键保留“快捷键”字样,避免用户误认为设置行本身是启动按钮。 - -| 位置/当前文案 | 建议文案 | 说明 | -| --- | --- | --- | -| 导航、页面标题、程序配置中的“缩放模式” | 效果组 | 三处一致 | -| 新建缩放模式 | 新建效果组 | 空组允许编辑 | -| 添加效果 | 添加效果器 | 与组内对象名称一致 | -| 全屏模式缩放快捷键 | 效果组快捷键(全屏模式) | 描述:以全屏模式为目标窗口启用效果组 | -| 窗口模式缩放快捷键 | 效果组快捷键(窗口模式) | 描述:以窗口模式为目标窗口启用效果组 | -| 按下快捷键可缩放前台窗口或停止缩放。 | 按下快捷键可为前台窗口启用效果组,或停用当前正在运行的效果组。 | 下方补充模式切换行为,见下一节 | -| `{} 秒后缩放前台窗口` | `{} 秒后为前台窗口启用效果组` | 保留占位符,不能写死 3 秒 | -| 托盘:`{} 秒后缩放 (全屏模式)` | `{} 秒后启用效果组(全屏模式)` | 托盘空间较窄,可采用短句 | -| 停止缩放 | 停用效果组 | 工具栏结束当前运行;不删除组 | -| 切换到全屏模式缩放/窗口模式缩放 | 切换到全屏模式/窗口模式 | 已在运行,无需再次“启用” | -| 位于前台时自动缩放 | 位于前台时自动启用效果组 | 程序配置设置 | -| 窗口模式缩放(设置分区) | 窗口模式 | 初始缩放倍数仍保留原词 | -| 允许缩放最大化或全屏的窗口 | 允许为最大化或全屏窗口启用效果组 | 明确指源应用窗口状态 | -| 缩放时模拟独占全屏 | 运行效果组时模拟独占全屏 | 仍需注明仅用于 Magpie 全屏模式 | -| 工具栏位于缩放窗口顶部…… | 工具栏位于输出窗口顶部,提供帧率显示、截图等功能;窗口模式下还可拖动工具栏移动输出窗口。 | 区分输出和目标窗口 | -| 应用并重启缩放 | 应用并重新启用效果组 | 描述补充“针对当前目标窗口”;不是重启 Magpie | -| 恢复本次缩放初始值 | 恢复本次运行初始值 | 不能改成“恢复默认”,两者值不同 | -| 当前缩放模式没有效果器…… | 当前效果组没有效果器。请添加效果器,或选择其他效果组。 | 保留可操作建议 | -| 重置缩放模式配置? | 恢复默认效果组? | 这是批量替换操作,和参数重置分开 | -| 重置说明 | 这将删除当前所有效果组并恢复内置效果组。各程序配置将改为使用全局默认效果组。此操作无法撤销。 | 与当前 ResetScalingModes 行为一致 | -| 导入/导出缩放模式失败 | 导入/导出效果组失败 | JSON 技术字段仍显示原名 `scalingModes` | -| 切换全屏/窗口时的报错 | 请还原目标窗口或退出目标应用的全屏状态,再以窗口模式启用效果组;也可改用 Magpie 的全屏模式。 | 分开两种“全屏” | -| 高(缩放倍数)、宽(缩放倍数)、光标缩放系数 | 保留 | 都是尺寸运算 | -| 组内尺寸设置按钮“缩放” | 输出尺寸 | 可选改进;它实际打开尺寸规则设置,不会启动效果组 | - -## 快捷键行为必须准确 - -源码的实际行为是:未运行时为前台窗口启动;启动中再次按下会取消;运行中按当前模式快捷键会停用;源窗口处于焦点且按另一模式快捷键时切换显示模式;源窗口没有焦点时则停用当前运行;停止中忽略重复操作。 - -因此长说明建议写成:“未运行时,为前台窗口启用效果组。运行时,按当前显示模式的快捷键可停用;目标窗口保持焦点时,按另一显示模式的快捷键可切换模式。”简短说明可以只介绍启停,但不要承诺“任何快捷键再次按下都会停用”。 - -依据:[ScalingService.cpp](<../../../src/Magpie/ScalingService.cpp#L114>)、[ScalingWindow.cpp](<../../../src/Magpie.Core/ScalingWindow.cpp#L369>)。 - -## 风险和处理建议 - -| 风险 | 程度 | 后续实施建议 | -| --- | --- | --- | -| 全局替换破坏配置、快捷键和导入 | 高,若跨入数据层 | 保留 `scalingModes`、`scalingMode`、`scalingType`、`Scale`、`WindowedModeScale`、效果器 ID、参数名和资源键;仅改资源值及说明 | -| 将尺寸“缩放”误改成启动动作 | 中 | 人工分类文案;光标、倍数、适应/填充、缩小图像等保留尺寸含义 | -| 用户误以为停用会禁用组供其他配置使用 | 中 | 文案说明“停用当前运行”,必要时显示目标窗口名 | -| 用户误以为效果组只是无顺序集合 | 中 | 初次说明写清执行顺序和参数配置;帧生成特殊执行位置另行解释 | -| 旧教程、截图和社区术语不一致 | 中 | 更新现行帮助;一版迁移说明“效果组(原称缩放模式)”;历史版本记录不批量重写 | -| 改名顺手改预设名称导致用户内容被覆盖 | 中 | 不批量重命名用户自己保存的组;内置品牌名如 Lanczos、DLSSNR 保留 | -| 新文本变长、换行、辅助功能名称遗漏 | 中 | 同步检查标题、Tooltip、快捷键编辑弹窗、托盘、错误详情和辅助功能名称;由用户做界面验收 | -| 语言资源覆盖不一致 | 中 | 当前 en-US / zh-Hans 各 601 项,zh-Hant 590 项;繁体缺少 11 个键,其中含效果组重置说明,后续不要只替换已有词 | -| 报错关键词和支持检索失联 | 低至中 | 稳定 `MP-XXX` 错误码和日志内部标识;发布说明给出新旧术语对照 | - -核心证据:[数据结构](<../../../src/Magpie/ScalingMode.h#L7>)、[导入/导出](<../../../src/Magpie/ScalingModesService.cpp#L108>)、[快捷键序列化](<../../../src/Magpie/ShortcutHelper.cpp#L9>)、[配置恢复](<../../../src/Magpie/ConfigPersistence.h#L39>)。 - -## 建议实施边界与后续验收 - -第一批统一简体中文的配置对象名称、启动说明、错误提示和现行帮助;保留数据格式。其他语言是否同步改名可以在实施前决定,不阻塞本次调查。不得触发配置重置或迁移来完成纯文字改名。 - -后续应核对旧配置和旧导出文件仍可读取、新导出仍被原有版本识别、快捷键配置保持、用户自定义名称保持,以及全屏/窗口/源窗口已全屏这三个场景的说明。当前会话未执行这些程序测试。 - -## 文本覆盖盘点 - -当前简体资源中 90 个资源值包含“缩放”,其中 22 个包含“缩放模式”。下方清单覆盖这 90 项,供后续逐条分类使用;它不是批量替换指令。还应连带检查同分区不含该词的短标签,如“添加效果”“重置配置”。 - -| 资源键(链接到源码) | 当前文本 | 检查方向 | -| --- | --- | --- | -| [Root_ScalingModes.Content](<../../../src/Magpie/Resources.language-zh-Hans.resw#L189>) | 缩放模式 | 效果组对象 | -| [ScalingModes_PageFrame.Title](<../../../src/Magpie/Resources.language-zh-Hans.resw#L192>) | 缩放模式 | 效果组对象 | -| [ScalingModes_ResetDialog_Title](<../../../src/Magpie/Resources.language-zh-Hans.resw#L255>) | 重置缩放模式配置? | 效果组对象 | -| [ScalingModes_ResetDialog_Content](<../../../src/Magpie/Resources.language-zh-Hans.resw#L258>) | 这将删除所有当前缩放模式并恢复默认缩放模式。自定义程序配置将改为使用全局默认缩放模式。此操作无法撤销。 | 效果组对象 | -| [ScalingModes_DeleteFlyout_Description.Text](<../../../src/Magpie/Resources.language-zh-Hans.resw#L345>) | 以下配置文件正在使用这个缩放模式: | 效果组对象 | -| [ScalingModes_DeleteFlyout_Title.Text](<../../../src/Magpie/Resources.language-zh-Hans.resw#L348>) | 确定删除这个缩放模式? | 效果组对象 | -| [ScalingModes_NewScalingMode.Text](<../../../src/Magpie/Resources.language-zh-Hans.resw#L363>) | 新建缩放模式 | 效果组对象 | -| [ScalingModes_NewScalingModeFlyout_Title.Text](<../../../src/Magpie/Resources.language-zh-Hans.resw#L366>) | 新建缩放模式 | 效果组对象 | -| [ScalingModes_Scale.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip]() | 缩放 | 启动动作/显示模式/运行状态 | -| [ScalingModes_ScaleFlyout_HeightFactor.Text](<../../../src/Magpie/Resources.language-zh-Hans.resw#L384>) | 高(缩放倍数) | 尺寸/光标语义,保留或仅改运行语境 | -| [ScalingModes_ScaleFlyout_WidthFactor.Text](<../../../src/Magpie/Resources.language-zh-Hans.resw#L390>) | 宽(缩放倍数) | 尺寸/光标语义,保留或仅改运行语境 | -| [ScalingModes_ScaleFlyout_Type_Absolute_Description](<../../../src/Magpie/Resources.language-zh-Hans.resw#L402>) | 指定缩放后的尺寸 | 尺寸/光标语义,保留或仅改运行语境 | -| [ScalingModes_ScaleFlyout_Type_Factor_Description](<../../../src/Magpie/Resources.language-zh-Hans.resw#L408>) | 指定相对于输入图像的缩放倍数 | 尺寸/光标语义,保留或仅改运行语境 | -| [ScalingModes_ScaleFlyout_Type_Fit_Description](<../../../src/Magpie/Resources.language-zh-Hans.resw#L420>) | 指定等比缩放到充满屏幕后的缩放倍数 | 尺寸/光标语义,保留或仅改运行语境 | -| [Profile_General_AutoScale.Header](<../../../src/Magpie/Resources.language-zh-Hans.resw#L429>) | 位于前台时自动缩放 | 启动动作/显示模式/运行状态 | -| [Profile_General_ScalingMode.Header](<../../../src/Magpie/Resources.language-zh-Hans.resw#L450>) | 缩放模式 | 效果组对象 | -| [Profile_Cursor_DrawCursor_AdjustCursorSpeed.Header](<../../../src/Magpie/Resources.language-zh-Hans.resw#L564>) | 缩放时调整光标速度 | 尺寸/光标语义,保留或仅改运行语境 | -| [Profile_Cursor_DrawCursor_ScaleFactor.Header](<../../../src/Magpie/Resources.language-zh-Hans.resw#L576>) | 缩放系数 | 尺寸/光标语义,保留或仅改运行语境 | -| [Profile_Cursor_DrawCursor_ScaleFactor_NoScaling.Content](<../../../src/Magpie/Resources.language-zh-Hans.resw#L582>) | 无缩放 | 尺寸/光标语义,保留或仅改运行语境 | -| [Home_Advanced_SimulateExclusiveFullscreen.Description](<../../../src/Magpie/Resources.language-zh-Hans.resw#L624>) | 仅适用于全屏模式缩放。启用后可以阻止某些应用的通知和弹窗 | 启动动作/显示模式/运行状态 | -| [Home_Advanced_SimulateExclusiveFullscreen.Header](<../../../src/Magpie/Resources.language-zh-Hans.resw#L627>) | 缩放时模拟独占全屏 | 启动动作/显示模式/运行状态 | -| [ScalingModes_HasUnkownEffects.Title](<../../../src/Magpie/Resources.language-zh-Hans.resw#L682>) | 此缩放模式包含未知效果,暂时无法使用。请删除未知效果,或恢复对应效果文件并重新启动 Magpie。 | 效果组对象 | -| [Home_Advanced_AllowScalingMaximized.Header](<../../../src/Magpie/Resources.language-zh-Hans.resw#L730>) | 允许缩放最大化或全屏的窗口 | 启动动作/显示模式/运行状态 | -| [Home_TouchSupport_Info.Title](<../../../src/Magpie/Resources.language-zh-Hans.resw#L790>) | 如果触控支持失效,Magpie 可能会在缩放前请求管理员权限以执行修复。 | 启动动作/显示模式/运行状态 | -| [Message_InvalidScalingMode](<../../../src/Magpie/Resources.language-zh-Hans.resw#L796>) | 当前配置没有可用的缩放模式。请选择其他缩放模式,或为当前模式添加并修复效果。 | 效果组对象 | -| [Message_InvalidSourceWindow](<../../../src/Magpie/Resources.language-zh-Hans.resw#L802>) | 无法缩放这个窗口。请确认它仍在运行、未停止响应、位于显示器内且窗口尺寸不小于 64×64。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_Maximized](<../../../src/Magpie/Resources.language-zh-Hans.resw#L805>) | 当前禁止缩放最大化或全屏窗口。请先还原窗口,或在“主页 → 高级”中启用“允许缩放最大化或全屏的窗口”。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_ScalingFailedGeneral](<../../../src/Magpie/Resources.language-zh-Hans.resw#L811>) | 缩放初始化失败。请退出缩放、重新打开目标窗口并重试;如果仍然失败,请重新启动 Magpie。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_CreateFenceFailed](<../../../src/Magpie/Resources.language-zh-Hans.resw#L817>) | 所选显卡或驱动不支持缩放所需的同步功能。请在当前配置的“性能”中切换显卡,或更新显卡驱动后重试。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_ScalingFailed](<../../../src/Magpie/Resources.language-zh-Hans.resw#L823>) | 缩放失败 | 错误处理,区分源窗口状态与输出模式 | -| [Message_BannedInWindowedMode](<../../../src/Magpie/Resources.language-zh-Hans.resw#L868>) | 当前窗口已最大化或处于全屏状态。请先还原窗口或退出全屏,也可以改用全屏模式缩放。 | 错误处理,区分源窗口状态与输出模式 | -| [Home_Activation.Description](<../../../src/Magpie/Resources.language-zh-Hans.resw#L874>) | 按下快捷键可缩放前台窗口或停止缩放。 | 启动动作/显示模式/运行状态 | -| [Home_Activation_FullscreenScaling.Header](<../../../src/Magpie/Resources.language-zh-Hans.resw#L877>) | 全屏模式缩放快捷键 | 启动动作/显示模式/运行状态 | -| [Home_Activation_FullscreenScaling_ShortcutControl.Title](<../../../src/Magpie/Resources.language-zh-Hans.resw#L883>) | 全屏模式缩放快捷键 | 启动动作/显示模式/运行状态 | -| [Home_Activation_WindowedScaling.Header](<../../../src/Magpie/Resources.language-zh-Hans.resw#L886>) | 窗口模式缩放快捷键 | 启动动作/显示模式/运行状态 | -| [Home_Activation_WindowedScaling_ShortcutControl.Title](<../../../src/Magpie/Resources.language-zh-Hans.resw#L889>) | 窗口模式缩放快捷键 | 启动动作/显示模式/运行状态 | -| [Home_Advanced_AllowScalingMaximized.Description](<../../../src/Magpie/Resources.language-zh-Hans.resw#L892>) | 仅适用于全屏模式缩放 | 启动动作/显示模式/运行状态 | -| [Home_Toolbar.Description](<../../../src/Magpie/Resources.language-zh-Hans.resw#L895>) | 工具栏位于缩放窗口顶部,提供帧率显示、截图等功能,窗口模式缩放时还可以用于拖拽缩放窗口。 | 启动动作/显示模式/运行状态 | -| [Message_ToolbarIn3DGameMode](<../../../src/Magpie/Resources.language-zh-Hans.resw#L919>) | 3D 游戏模式不支持工具栏。请停止缩放并关闭 3D 游戏模式,或使用缩放快捷键进行控制。 | 错误处理,区分源窗口状态与输出模式 | -| [Overlay_Toolbar_Close](<../../../src/Magpie/Resources.language-zh-Hans.resw#L925>) | 停止缩放 | 启动动作/显示模式/运行状态 | -| [Message_Windowed3DGameMode](<../../../src/Magpie/Resources.language-zh-Hans.resw#L940>) | 当前配置启用了 3D 游戏模式,无法使用窗口模式缩放。请关闭 3D 游戏模式,或改用全屏模式缩放。 | 错误处理,区分源窗口状态与输出模式 | -| [Profile_General_3DGameMode.Description](<../../../src/Magpie/Resources.language-zh-Hans.resw#L943>) | 针对 3D 游戏优化,不支持工具栏和窗口模式缩放 | 启动动作/显示模式/运行状态 | -| [Profile_WindowedScaling.Header](<../../../src/Magpie/Resources.language-zh-Hans.resw#L967>) | 窗口模式缩放 | 启动动作/显示模式/运行状态 | -| [Profile_WindowedScaling_InitialScaleFactor.Header](<../../../src/Magpie/Resources.language-zh-Hans.resw#L970>) | 初始缩放倍数 | 尺寸/光标语义,保留或仅改运行语境 | -| [Profile_General_AutoScale_Fullscreen.Content](<../../../src/Magpie/Resources.language-zh-Hans.resw#L985>) | 全屏模式缩放 | 启动动作/显示模式/运行状态 | -| [Profile_General_AutoScale_Windowed.Content](<../../../src/Magpie/Resources.language-zh-Hans.resw#L988>) | 窗口模式缩放 | 启动动作/显示模式/运行状态 | -| [Profile_General_Multimonitor.Description](<../../../src/Magpie/Resources.language-zh-Hans.resw#L991>) | 仅适用于全屏模式缩放 | 启动动作/显示模式/运行状态 | -| [Overlay_Toolbar_SwitchToFullscreen](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1000>) | 切换到全屏模式缩放 | 启动动作/显示模式/运行状态 | -| [Overlay_Toolbar_SwitchToWindowed](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1003>) | 切换到窗口模式缩放 | 启动动作/显示模式/运行状态 | -| [Home_Toolbar_InitialState_Fullscreen.Header](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1006>) | 全屏模式缩放 | 启动动作/显示模式/运行状态 | -| [Home_Toolbar_InitialState_Windowed.Header](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1009>) | 窗口模式缩放 | 启动动作/显示模式/运行状态 | -| [Message_ExportScalingModesFailed](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1012>) | 导出缩放模式失败。请选择可写入的位置,并检查文件是否被占用以及磁盘空间是否充足。 | 效果组对象 | -| [Message_ImportScalingModesFailed](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1015>) | 导入缩放模式失败。请选择有效且可读取的缩放模式 JSON 文件,并确认文件内容没有损坏。 | 效果组对象 | -| [Message_WindowedDesktopDuplication](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1021>) | Desktop Duplication 不支持窗口模式缩放。请将捕获方式改为“默认”或 Graphics Capture,或改用全屏模式缩放。 | 错误处理,区分源窗口状态与输出模式 | -| [Profile_General_DesktopDuplicationWarning.Title](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1024>) | Desktop Duplication 不支持窗口模式缩放。请改用“默认”或 Graphics Capture,或使用全屏模式缩放。 | 错误处理,区分源窗口状态与输出模式 | -| [Home_Activation_Timer_Description](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1030>) | {} 秒后缩放前台窗口 | 启动动作/显示模式/运行状态 | -| [NotifyIcon_Timer_Fullscreen](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1036>) | {} 秒后缩放 (全屏模式) | 启动动作/显示模式/运行状态 | -| [NotifyIcon_Timer_Windowed](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1039>) | {} 秒后缩放 (窗口模式) | 启动动作/显示模式/运行状态 | -| [Profile_Advanced_DestAlignment.Description](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1051>) | 仅适用于全屏模式缩放 | 启动动作/显示模式/运行状态 | -| [Home_Advanced_DeveloperOptions_DisableTopmost.Content](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1084>) | 禁用缩放窗口置顶 | 启动动作/显示模式/运行状态 | -| [Settings_General_SmoothMotionCompatibilityMode.Description](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1090>) | 每次缩放结束后重启 Magpie,释放 NVIDIA Smooth Motion 驱动驻留的显存。仅在已为 Magpie 启用 Smooth Motion 时开启。 | 启动动作/显示模式/运行状态 | -| [Overlay_EffectParameters_RestartPending](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1098>) | 有 {} 项等待重新缩放 | 参数会话/重新启用 | -| [Overlay_EffectParameters_Revert](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1100>) | 恢复本次缩放初始值 | 参数会话/重新启用 | -| [Overlay_EffectParameters_ApplyAndRestart](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1101>) | 应用并重新缩放 | 参数会话/重新启用 | -| [Overlay_EffectParameters_SessionExpired](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1105>) | 缩放会话已变化,参数未保存。 | 参数会话/重新启用 | -| [Overlay_EffectParameters_ScalingModeConflict](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1107>) | 缩放期间模式结构已变化,未覆盖任何配置。 | 参数会话/重新启用 | -| [Message_ConflictingFrameGenerationEffects](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1120>) | 同一缩放模式只能包含一个帧生成效果器。请移除 DLSSFG、XeSSFG x2 或 XeSS 多帧生成中的冲突项后重试。 | 效果组对象 | -| [Message_ScalingModeNotSelected](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1334>) | 此配置尚未选择有效的缩放模式。请在配置中选择已有的缩放模式,然后重新开始缩放。 | 效果组对象 | -| [Message_ScalingModeEmpty](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1335>) | 当前缩放模式没有效果器。请在缩放模式中添加效果器,或选择其他模式。 | 效果组对象 | -| [Message_ScalingModeUnknownEffect](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1336>) | 缩放模式引用了不可用的效果器。请从完整发行包恢复对应的 effects 文件,或替换该效果器。 | 效果组对象 | -| [Message_PresentationInitFailed](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1338>) | 无法创建画面输出。请尝试切换窗口或全屏缩放;若使用了补帧,请先尝试不含补帧的模式。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_EffectCompileFailed](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1339>) | 效果器编译失败。请在详情中查看名称,从对应发行包恢复该效果器,或将其移出缩放模式后重试。具体编译错误见日志。 | 效果组对象 | -| [Message_OverlayInitFailed](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1343>) | 工具栏或光标叠加层初始化失败。请重新解压完整发行包后再次缩放;若仍失败,请提供详情和日志。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_DlssNrUnavailable](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1345>) | DLSSNR 初始化失败,当前画面仅透传,未进行 NR 处理。运行中参数暂不可用。请检查显卡支持情况和完整发行文件,然后重新启动缩放。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_FrameGenerationDisabled](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1346>) | 补帧连续失败,已在本次缩放中停用,原始帧会继续显示。请尝试降低倍率或更换光流设置,然后重新启动缩放。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_EffectParameterConflict](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1348>) | 同一参数已在其他位置被修改,本面板的修改未覆盖保存。请结束并重新开始缩放以载入最新保存值,再进行调整。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_EffectParameterLiveFailed](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1349>) | 效果器拒绝了本次实时更新,仍使用此前的运行值。已保存的设置可能与运行值不同;请重新启动缩放以应用,或恢复此前可用的参数值。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_ScreenshotReadbackFailed](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1353>) | 无法从显卡读取截图画面。请重新开始缩放后重试;若仍失败,可尝试降低输出尺寸、减少效果器,并提供详情和日志。 | 错误处理,区分源窗口状态与输出模式 | -| [Overlay_EffectParameters_BackendUnavailable](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1355>) | DLSSNR 后端未初始化,当前仅透传。请查看主页 → 最近一次问题,处理后重新启动缩放。 | 参数会话/重新启用 | -| [Message_SourceWindowClosed](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1356>) | 目标窗口已关闭。请重新打开,将其切到前台后再开始缩放。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_SourceWindowUnresponsive](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1357>) | 目标窗口暂时没有响应。请等待其恢复,或重新启动目标程序后再缩放。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_SourceWindowUnsupported](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1360>) | 不支持缩放这种透明窗口。请选择程序的主窗口,避开透明叠加层或悬浮组件。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_SourceWindowGeometryFailed](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1361>) | 无法读取或调整目标窗口的位置和尺寸。请还原窗口并移到单个屏幕内再试;全屏缩放失败时可尝试窗口缩放。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_ScalingAlreadyActive](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1362>) | 已存在其他缩放窗口。请先停止当前缩放,并关闭其他 Magpie 实例后重试。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_ScalingWindowCreationFailed](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1363>) | 无法创建缩放窗口。请重新运行 Magpie,并尝试窗口缩放;若仍失败,请提供详情和日志。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_DisplayLayoutFailed](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1364>) | 当前显示器布局无法用于全屏缩放。请改用最近的显示器或尝试窗口缩放,并确认所选显示器已连接。 | 错误处理,区分源窗口状态与输出模式 | -| [Message_ImportEmpty](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1366>) | 所选文件为空或不包含任何缩放模式。请从 Magpie 导出模式,或取得完整的缩放模式文件后重试。 | 效果组对象 | -| [Message_ImportWrongFileType](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1368>) | 此 JSON 不包含 scalingModes 数组。请选择由 Magpie 导出的缩放模式文件;将其他文件改名为 .json 不会转换其格式。 | 效果组对象 | -| [Message_ImportIncompatible](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1369>) | 缩放模式条目的结构不兼容。请使用兼容的 Magpie 版本重新导出。本次未导入该文件中的任何模式。 | 效果组对象 | -| [Message_ExportWriteFailed](<../../../src/Magpie/Resources.language-zh-Hans.resw#L1370>) | 缩放模式文件未能完整写入。请检查路径、磁盘空间和写入权限,并重新导出到可写入的目录。 | 效果组对象 | diff --git a/docs/experimental/reviews/20260905-r10-dlssnr-parameter-restart.md b/docs/experimental/reviews/20260905-r10-dlssnr-parameter-restart.md deleted file mode 100644 index 6148bc79e..000000000 --- a/docs/experimental/reviews/20260905-r10-dlssnr-parameter-restart.md +++ /dev/null @@ -1,36 +0,0 @@ -# r10:DLSSNR 参数修改后延迟重新启用 - -## 实现范围 - -当效果组中有已成功启用的 DLSSNR 时,原本会使其核心缓存失效的实时参数修改,改为先收集修改,再完整停用并重新启用效果组。 - -| 参数所在位置 | 行为 | -| --- | --- | -| DLSSNR 的风格、强度、局部色调/结构、皮肤结构、自动遮罩、UI 修正 | 自动延迟重新启用 | -| DLSSNR 上游的图像参数,且原本支持实时修改 | 自动延迟重新启用 | -| DLSSNR 的残差倍率、饱和度、亮度、阴影结构、反射辉光 | 原本支持实时修改时继续实时应用;若其后还有已启用的 NR,则作为上游参数处理 | -| DLSSNR 下游参数、FrameRate Filter 帧率控制 | 保留原有行为 | -| 原本标为“重启”的资源、光流配置等参数 | 继续保留为待应用值,手动“应用并重新启用” | -| 组内没有成功启用的 DLSSNR | 保留原有行为 | - -## 时序与状态 - -1. 修改通过原有自动保存路径保存,旧 NR 实例及上游图像参数保持已应用值。 -2. 连续修改合并为每个参数的最后一个值。最后一次修改后至少静默 300 毫秒,并等待滑条/输入操作结束;工具栏编辑还等待自动保存完成。 -3. 外层缩放消息循环完整销毁呈现、捕获、效果链和 NR 实例,并等待后端线程退出。销毁不发生在 ImGui 参数回调或渲染调用栈内。 -4. 从销毁完成起等待 500 毫秒,再为同一源窗口重新启用同一效果组。初始化耗时另计,因此画面中断可能超过 500 毫秒。 -5. 重建使用最终已应用快照,再覆盖本次需要自动重启的参数;其他尚未手动应用的“重启”参数不会被意外应用或丢弃。原来打开的工具栏参数页会重新打开。 - -设置页与工具栏参数页走相同分类。设置页在停用等待期间继续修改实时参数时,更新重建使用的值并重新计时;原本需手动重启的参数仍只更新目标值。 - -恢复到当前已应用值会撤销对应待重启修改。手动停用、启动其他效果组和退出软件会取消旧任务;等待结束时源窗口已关闭、隐藏或最小化,则取消重新启用。启动失败沿用现有报错处理,不循环重试。 - -工具栏自动保存失败时取消自动重启,并沿用已有错误提示;设置页独立保存的编辑不会被之前无关的工具栏保存失败永久阻塞。 - -## 验证与边界 - -- CPU 检查覆盖分类、合并、恢复原值、取消及 300/500 毫秒计时边界,共 47 项通过。 -- Release x64 编译通过,部署包按文件清单与 ZIP 内容核对。 -- 本轮未启动 Magpie,未执行 GUI/GPU 测试。需要实机确认的是:拖动核心与上游参数后旧画面保持到退出、随后干净地重新启用;残差参数仍实时变化;等待期间手动停用后不会自行恢复。 -- 这是针对参数修改时的生命周期处理,不证明或宣称修复 NVIDIA DLL 内部的任何反馈问题。 -- Front Edge Sync、REALTIME GPU 优先级及 VRR 暂时停用状态保持现有设置。 diff --git a/docs/experimental/reviews/20260905-r10-vrr-latency-REVIEW.md b/docs/experimental/reviews/20260905-r10-vrr-latency-REVIEW.md deleted file mode 100644 index 2aaaa00e9..000000000 --- a/docs/experimental/reviews/20260905-r10-vrr-latency-REVIEW.md +++ /dev/null @@ -1,91 +0,0 @@ -# r10:VRR 与呈现延迟复查 - -> 后续状态:用户决定暂缓 VRR。当前 r10 local 已隐藏并停用 VRR,撤回全屏焦点实验;容量等待优化与 Front Edge Sync 保留。下文记录此前实验与调查,当前使用说明见 ../../FRAME_SYNC_GUIDE.md。 - -## 当前结论 - -**现有 VRR 开关确实把请求传到了 DXGI,未发现漏传交换链标志这一类可以直接修好的错误。** 日志没有记录真实扫描输出模式,不能把「支持 tearing」或「Present 成功」当成显示器已经启用 VRR。 - -用户复现条件为:全屏效果组、目标 100 FPS、无 FG,其他程序可以触发 VRR,Magpie 未触发,以显示器实时刷新率为判断依据。 - -最值得验证的原因是**呈现窗口没有前台焦点**。Magpie 故意让源程序保持前台,输出窗口使用 WS_EX_NOACTIVATE,而且发现输出窗口意外获取焦点后会立即把焦点还给源程序。NVIDIA 开发者论坛有针对非焦点全屏覆盖窗口的直接复现报告,现象与此相符;这仍是候选原因,并非在本机通过受控对照证实的结论。[NVIDIA 论坛原始报告](https://forums.developer.nvidia.com/t/fix-vrr-for-overlays-always-on-top-windows/296168) - -用户随后要求尝试全屏强制焦点,因此保留 VRR。按已告知的建议范围,**仅在 VRR 开启+全屏效果组时尝试激活输出窗口**,窗口效果组与关闭 VRR 时保持原行为。本轮焦点代码已实现,实际显示器变频尚未验证。 - -## 本地证据 - -快照保存在工作区 .tools/r10-vrr-review/logs-before/。两份日志覆盖不同设置的多次运行,不能混为一个固定 100 FPS 场景。 - -- 17:45:53 的会话明确记录 VRR request: enabled=true tearingSupported=true path=DXGI。 -- 同一会话记录 Front Edge Sync 实际启用,基础目标 100 FPS。 -- 普通呈现为 DXGI FLIP_DISCARD;创建和 ResizeBuffers 均带 ALLOW_TEARING,开启 VRR 时 Present 使用 SyncInterval=0 和 ALLOW_TEARING。 -- 此前会话记录输出尺寸 2560×1440,NVIDIA RTX 5070 Ti,输出显示器刷新率约 300 Hz。 -- 交换链最大排队帧数为 1,尽管仍分配 4 或 8 个缓冲;缓冲分配数不等于实际排队帧数。 -- 日志原有「可变刷新率支持」实际来自 DXGI_FEATURE_PRESENT_ALLOW_TEARING 查询。本轮改成准确的能力名称,避免误读为激活状态。 - -上述 API 组合满足微软给出的 VRR 请求要求,但 DirectFlip/Independent Flip 的实际采用仍受窗口与合成路径影响。可用 PresentMon 的 PresentMode 进一步区分;该字段也不能单独证明显示器物理变频已生效。[微软 VRR 要求](https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/variable-refresh-rate-displays)、[微软 Flip Model 指南](https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/for-best-performance--use-dxgi-flip-model) - -## 强制焦点为什么不能只加一行 - -至少需要协调以下已有逻辑: - -1. 全屏输出窗口的 NOACTIVATE 样式及 WM_MOUSEACTIVATE。 -2. _UpdateSrcState 中自动还原源窗口焦点的逻辑。 -3. 点击黑边/工具栏时激活源窗口的逻辑。 -4. 3D 游戏模式下将其他前台窗口视为遮挡、停止效果组的检查。 -5. 窗口置顶、鼠标捕获、Alt+Tab 与关闭效果组时的焦点恢复。 - -更关键的是,Windows 会把键盘输入交给获得焦点的窗口。输出窗口取得焦点后,部分游戏会失焦暂停,或不再处理键盘、Raw Input、DirectInput;转发普通 WM_KEYDOWN 不能普遍解决这些问题。跨进程 AttachThreadInput 也不是无代价修复,会把原本异步的窗口操作变成相互依赖,可能造成等待甚至挂起。[SetForegroundWindow 语义](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setforegroundwindow)、[微软对输入队列合并的说明](https://devblogs.microsoft.com/oldnewthing/20130607-00/?p=4143) - -本轮实现为可以通过关闭 VRR 并重新启用效果组退回的验证路径: - -- VRR 全屏窗口去掉 NOACTIVATE,允许鼠标激活;进入全屏时,在源窗口仍为前台的条件下请求输出前台焦点。 -- 输出获得焦点时,将其视为当前效果组会话仍在前台,以维持置顶、鼠标与 3D 游戏模式判断;这不表示源程序仍收到 Windows 前台输入。 -- 切到其他程序时不抢焦点,切回源窗口后允许再次尝试;每次前台访问只自动尝试一次,拒绝或源程序夺回焦点后不逐帧重试。瞬间没有前台窗口不重置次数。 -- 禁用源窗口/源窗口有启用的弹窗时不尝试;点击输出工具栏或黑边由系统正常激活输出,不再马上激活源窗口。 -- 结束时仅当输出窗口仍在前台才尝试恢复源窗口焦点;保留原有跨线程输入队列分离,不加入输入转发或模拟按键绕过前台锁。 -- 日志记录 Fullscreen VRR focus attempt 的请求返回值及实际前台状态,不将其命名为 VRR 激活结果。 - -源程序可能因失焦暂停、降后台帧率或丢失键盘输入。点击穿透后的源窗口也可能让焦点回到源程序,本轮不与它持续争抢。若实机验证确实使显示器变频,再决定是否投入输入兼容工作。不能在未验证的情况下宣称完整 VRR 修复。 - -## 延迟:本轮已经实现的有限优化 - -原来 BeginFrame 发现交换链尚无容量后,外层消息循环只等待消息或下一次轮询,通常约 1 ms。即使 DXGI 容量事件在中途到达,也不会直接唤醒这次等待。 - -本轮改为: - -- 在外层消息循环同时等待交换链容量事件和输入/控制消息,事件到达就继续处理。 -- 仅在 BeginFrame 已经报告容量不足时等待该事件,保留原有有限超时。 -- 记录已经消费的容量令牌,下一次 BeginFrame 直接使用,防止自动复位事件被等待两次。 -- 普通 DXGI 与 XeSS 采用相同的令牌状态逻辑;Front Edge Sync 截止时间优先,XeLL 的 xellSleep 和生成帧输出调度保留。 -- DXGI ResizeBuffers 复用同一交换链的有效令牌;XeSS 重建等待对象时清除旧令牌。 -- 从调整窗口大小的 DirectComposition 路径切回交换链时,若调整交换链失败,保留当前 DirectComposition 路径。 - -它削减的是容量已可用到下次轮询之间的额外等待,**不是保证端到端固定减少 1 ms**。容量一直充足时没有这项收益;操作系统调度、FES 截止时间和 GPU 工作仍影响实际输出。 - -## r9 的内容是否还需要 - -| 内容 | 判断 | 原因 | -| --- | --- | --- | -| WGC 自由线程新帧通知,有限取帧并保留最新帧 | 保留 | 减少交付延迟,防止旧帧积压 | -| 捕获中断、尺寸/时间戳校验、恢复后的历史重置 | 保留 | 针对切屏后 NR 历史污染;正常稳定帧不需要反复重置 | -| 工具栏/光标合并更新,内容优先 | 保留 | 避免重复呈现旧背景并争抢 GPU;FES 关闭及 FG 路径仍需要 | -| 有限消息处理、渲染退出窗口过程后执行 | 保留 | 避免拖动性能分析器时把后续输入堵在呈现等待后 | -| DLSSFG 有界输出队列及对应帧资源 | 保留 | 维持生成帧与真实帧次序;无 FG 时不运行 | -| 后端发布后的 GPU fence | 本轮保留 | 确保共享图像已可消费、DLSSFG 源纹理可安全复用;不能把 GPU 执行时间视为可直接删掉的空等待 | -| 交换链容量不足后的纯轮询 | 本轮改进 | 直接使用已经存在的容量事件唤醒 | -| GPU REALTIME 优先级 | 保留 | 用户明确要求保持 | - -两份日志中有 509 个发布计时窗口,每个窗口标注 120 次采样;窗口均值的平均约 4.774 ms,记录到的最大等待 23.687 ms。它涵盖多个设置和运行阶段,且 fence 在当前 GPU 工作后触发,**不能推断删掉 fence 就能减少这些时长**。 - -更大幅度降低 FES 下图像年龄,可以研究在目标提交前按处理预算延后取最新捕获帧。当前在上一帧提交后就开始处理下一帧,提前处理完的图像可能等到下一次截止时间。修改这个调度需要验证 GPU 忙时的截止时间、源帧交付波动及 FG 输入连续性,超出本轮有限优化范围。 - -## 验证状态 - -容量令牌、自动复位信号、消息中断、超时与重置的 24 项 CPU 检查已通过。另有 22 项焦点策略检查通过:直接编译本轮生产方法,使用模拟的 Win32 调用覆盖拒绝、源程序夺回前台、切走/切回、瞬间 NULL、禁用/弹窗、窗口/VRR 开关范围与激活竞争。这些检查不调用真实前台激活 API,不验证 Windows 或游戏实际输入行为。 - -Release x64 编译通过,0 警告、0 错误;已覆盖部署 release/v0.6.5-r10-local/Magpie-v0.6.5-r10-local-x64。228 个包内文件、161 个效果器及 ZIP 所有条目的清单/哈希校验通过。旧部署完整备份在 .tools/r10-vrr-review/previous-deployment/,本地配置、缓存和日志保留,未加入分发 ZIP。 - -编译、源代码审计及包体校验记录保存在 .tools/r10-vrr-review/。没有运行 Magpie、没有使用 computer-use,也没有做 GUI/GPU 或显示器扫描输出测试。 - -VRR 焦点实验不能归类为纯文字小更新;需要确认显示器变频、源程序是否暂停、键鼠输入、Alt+Tab、停止效果组后焦点恢复。不能用编译通过替代这些检查。 diff --git a/docs/experimental/reviews/20260905-rtss-front-edge-sync-REVIEW.md b/docs/experimental/reviews/20260905-rtss-front-edge-sync-REVIEW.md deleted file mode 100644 index fe2ab9f24..000000000 --- a/docs/experimental/reviews/20260905-rtss-front-edge-sync-REVIEW.md +++ /dev/null @@ -1,119 +0,0 @@ -# RTSS Front Edge Sync 与 Magpie 呈现节奏调查 - -日期:2026-09-05。基于 `0.6.5` 主线当前 r9 工作区,以及本机 RTSS 7.3.5.28314 的官方帮助和 SDK 示例。本次只调查、交付方案,没有修改程序、RTSS 配置或部署文件,没有执行画面测试。 - -## 结论与测试条件 - -**Magpie 可以实现类似 Front Edge Sync 的稳定呈现模式,当前普通呈现路径确实缺少对应的输出节奏控制。建议首先针对无 FG 的 DLSSNR 场景实现,再分别适配 FG。** - -用户确认:游戏与 Magpie 都锁 80 FPS,使用 DLSSNR,没有开启 FG;显示器支持 FreeSync,但用户观察到 Magpie 呈现时 FreeSync 没有生效。目标周期因此是 **12.5 ms**。 - -这项观察支持继续调查“输入交付稳定性、输出呈现间隔与 GPU 工作量”的关系,但不能单独区分锁低帧率减轻负载和 Front Edge 等待位置的收益。也不能据此认定游戏与 Magpie 已建立逐帧同步协议。 - -调查时读到的 RTSS 磁盘全局配置是 `Limit=100`、`SyncLimiter=1`、`PassiveWait=1`、scanline 为 0。**它只代表读取时的配置,不覆盖用户明确说明的 80 FPS 测试条件,也不能证明过去两个进程的实际挂钩状态。** - -## RTSS 实际控制的是什么 - -本机随附帮助明确区分三种模式: - -| 模式 | 主要控制位置和目标 | 卡顿后的策略 | -| --- | --- | --- | -| Async | 约束下一帧开始,优先保证严格的最小帧间隔 | 性能不足时允许时间基准漂移 | -| Front Edge Sync | 在实际 Present 调用之前等待,稳定 Present 开始时刻 | 优先恢复与高精度时钟的同步,允许某些帧暂时超过限帧值 | -| Back Edge Sync | 在 Present 之后等待,稳定下一帧开始时刻 | 同样偏向恢复同步,可能短暂超过限帧值 | - -来源:[本机 RTSS 官方帮助]()。RTSS 作者 Unwinder 也直接说明了 Present 前后等待的区别,以及帧开始和 Present 两种统计口径为什么会得到不同的曲线:[作者说明](https://forums.blurbusters.com/viewtopic.php?start=40&t=7551)。该讨论发生在 2020 年,现行模式选项以本机 7.3.5 帮助为准。 - -这里的 front edge 指 **Present 调用的前沿**。单独使用它并不等于等待屏幕 VBlank,也不等于启用 Scanline Sync。RTSS 将扫描线同步列为另一个可组合的功能:[本机扫描线帮助]()。 - -Passive Waiting 是另一个维度:选择主要使用可等待定时器还是忙等待。改变它会影响 CPU 开销和唤醒精度,却不改变 Front Edge 的限帧位置。[本机等待方式帮助]() - -本次没有获得 RTSS 核心限帧实现源码。上面的模式语义有官方说明支持;其内部时间原点、补偿公式、忙等待窗口和跨进程相位细节不能当作已经反向确认。 - -## 为什么当前场景可能受益 - -1. **游戏先稳定交付。** 游戏的 Present 节奏更均匀,可能让 WGC 收到的输入更均匀;WGC、DWM 和 GPU 调度仍可能引入额外抖动,不能保证一对一即时交付。 -2. **Magpie 吸收处理耗时的短时变化。** 只稳定处理开始时刻,无法消除处理结束时刻的变化。在还有时间余量时,将输出对齐到计划时刻可以吸收这些变化。 -3. **两侧限帧可能降低竞争。** 游戏少产生超出目标的帧,Magpie 少提交额外的 UI/光标帧,都可能留出 GPU 调度余量。只给 Magpie 的 Present 加等待,并不必然降低其后端效果计算量。 - -以下是说明原理的理想化例子,单位为 ms,假设每张内容已经完成必要的 GPU 处理;不是本次实测或 RTSS 内部算法: - -| 帧 | 输入到达 | 处理耗时 | 处理后立即提交 | 对齐到固定输出时刻 | 额外等待 | -| --- | ---: | ---: | ---: | ---: | ---: | -| 1 | 0 | 4 | 4 | 8 | 4 | -| 2 | 12.5 | 7 | 19.5 | 20.5 | 1 | -| 3 | 25 | 4 | 29 | 33 | 4 | - -立即提交的间隔为 15.5、9.5 ms;留出适当处理余量后的间隔为 12.5、12.5 ms。代价是已完成内容的额外等待;总延迟还取决于是否减少了原有排队,不能直接宣称增加或减少固定一整帧。 - -如果输入真的中断 50–60 ms,有限余量的呈现器无法制造缺失的新画面。反复提交旧图能让某些 Present 统计变平,却不等于解决内容停顿。之前 r9 debug 已记录过约 60、53 ms 的捕获输入间隔,必须继续与输出抖动分开分析:[首份诊断 review](<20260905-v0.6.5-r9-debug-first-trace-REVIEW.md>)。 - -同时设置 80 FPS 只说明目标周期相同。游戏第 n 帧还要经过捕获、NR 和共享纹理发布,Magpie 才能提交其结果;即便使用同一系统时钟,也需要合理的相位偏移和队列控制。当前 WGC 接口没有向游戏传递“Magpie 已准备接收下一帧”的反向限帧协议。 - -## 当前 Magpie 的具体差距 - -| 位置 | 已确认行为 | 对方案的影响 | -| --- | --- | --- | -| `StepTimer.cpp:38,88` | 等待最大帧率发生在捕获/处理入口;限帧时维护开始时间网格 | 不是普通输出的 Present 前限帧器 | -| `Renderer.cpp:2263` | 最大帧率和 Frame Rate Filter 最终进入 StepTimer | 当前配置约束基础处理速率,不能解释为全部窗口提交上限 | -| `GraphicsCaptureFrameSource.cpp:111,434` | 到帧事件直接唤醒;取出有限数量帧并保留最新张 | 应保留到帧通知和低延迟取帧,不需要为限帧改回消息轮询 | -| `Renderer.cpp:692,777`、`AdaptivePresenter.cpp:211` | 取处理结果、绘制前端,然后直接 `Present(0, 0)` | 普通路径没有统一的 80 FPS 输出截止时间 | -| `FramePresentationTiming.h:10` | 纯叠加层按显示器刷新周期合并,新内容和关键动作可以绕过 | r9 已减少重复工作,但不等于全部输出限制为用户指定的 80 FPS | -| `PresenterBase.cpp:30`、`AdaptivePresenter.cpp:75` | 3D 模式 4 个缓冲,其他模式 8 个;普通最大帧延迟设为缓冲数减 1,DLSSFG 单独设为 1 | 普通路径允许的排队上限仍较宽;缓冲数量和实际积压数量不能画等号 | -| `Renderer.cpp:2582` | 普通发布不等待 DLSSFG 专用槽可用事件,后端仍可持续生成更新 | 单独限制前端 Present 不能保证停止多余 NR/OF 工作 | -| `Renderer.cpp:1054` | DLSSFG 已有逐输出帧截止时间与 FIFO,但到期后才开始前端绘制 | 已具备部分基础;仍不同于在实际提交边界精确对齐 | -| `XeSSFGPresenter.cpp:499,662,855` | XeLL 低延迟开启;BeginFrame 调用 xellSleep;通过代理交换链呈现 | 应由 XeLL/XeSS 接管对应的限帧与补帧节奏 | - -主要源码入口:[StepTimer](<../../../src/Magpie.Core/StepTimer.cpp#L38>)、[Renderer 前端](<../../../src/Magpie.Core/Renderer.cpp#L692>)、[普通 Present](<../../../src/Magpie.Core/AdaptivePresenter.cpp#L211>)、[XeSSFG](<../../../src/Magpie.Core/XeSSFGPresenter.cpp#L499>)。 - -近期日志记录纯叠加层目标周期为 3.333 ms,意味着程序当时按约 300 Hz 调度叠加层。它不是实测提交率,但说明即使内容约 80 FPS,r9 的现有叠加层规则仍允许更频繁重绘。RTSS 在 Magpie 的 DXGI Present 外层限帧,可能进一步压低这部分工作;本次没有同步 trace,不能量化收益。 - -### 与 FreeSync 相关的明确检查点 - -普通 AdaptivePresenter 创建/调整交换链时设置 `DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING`,但实际提交使用 `Present(0, 0)`;XeSSFG 路径则在支持时传入 `DXGI_PRESENT_ALLOW_TEARING`。当前默认 `UseCompSwapchain=false`,普通路径选用 AdaptivePresenter。 - -微软对窗口及无边框窗口的显式 VRR 路径要求创建和 Present 两端满足条件,建议支持时在 `SyncInterval=0` 的 Present 中传入对应 flag。[微软 VRR 指南](https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/variable-refresh-rate-displays)、[Present flag 条件](https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/dxgi-present) - -**因此存在一个具体的显式 VRR 接入缺口,值得独立修正和验证。** 但驱动设置、实际呈现模式、显示连接和窗口状态仍会影响 VRR;补上 flag 不等于保证 FreeSync 一定生效。允许 tearing 也不等于检测到了 VRR 显示器,在 VRR 未生效时可能引入撕裂,实施时需保留合适的呈现选择。 - -Front Edge Sync 也不能消除固定刷新屏的所有节奏量化。例如若实际固定为 300 Hz,80 FPS 对应每帧 3.75 次刷新,完整画面不可能总是保持整数个且相同的刷新周期。应区分长卡顿减少、提交均匀和最终扫描输出均匀这三个目标。 - -## 建议的实现路线 - -### 第一阶段:普通输出的稳定呈现模式 - -复用现有帧率配置和呈现结构,提供可选的稳定呈现模式,首先覆盖无 FG 的 NR/SR。它属于运行时调度,不应做成一个占效果组位置的新效果器。 - -1. **一个输出时钟。** 以 80 FPS 为例维护 12.5 ms 的目标周期。普通内容和同交换链的 UI/光标共用输出预算,在目标时刻附近合并绘制;输入消息即时处理,视觉更新进入下一次计划呈现。窗口移动/缩放等系统交互可临时退出稳定节奏,恢复后重新定相。 -2. **在提交边界控制,而非到点才开始全部绘制。** 将前端准备与提交分离,尽量提前准备,截止时间控制实际 Present 的开始。若最后的合成耗时不可忽略,需要测量并预留余量。不能仅在 `_FrontendRender` 前睡眠就宣称完成了 Front Edge 对齐。 -3. **有界待呈现内容与后端开工控制。** 优先复用已有处理结果和前端基础纹理;控制待提交内容的积压,并据输出计划决定是否接受下一张捕获输入进入 NR。普通画面只保留有用的新内容,避免先计算大量结果再全部丢弃。队列试验可比较最大帧延迟 1 与原值,不能仅因缓冲多就认定它是卡顿根因。 -4. **避免两个独立的同频门槛。** 输入处理上限和输出目标仍可有不同语义,但应共享节奏信息;不要让输入恰好错过一次 80 FPS 取帧门槛后,再错过一次独立的 80 FPS 呈现门槛。后续可以利用捕获时间戳与处理耗时估计输出相位,先采用有界、可测量的方案,避免一开始引入复杂预测器。 -5. **等待期间响应消息。** 使用已有的高精度定时器/交换链容量事件/线程消息;不要在持有共享纹理锁时等待整个帧周期,不要在窗口线程内增加不可中断的长 Sleep。若需要短尾等待,应测量其精度收益和 CPU 成本,不能默认整周期忙等。 -6. **迟到不无限追赶。** 小幅迟到可有限修正相位;长时间无输入、切屏、重建后重新定相,不积攒错过的时隙后连续补交。RTSS 帮助明确允许短暂超限;Magpie 是否采用同等追赶幅度,需要结合 GPU 竞争验证,不能机械照搬。 -7. **保持历史正确。** 普通无新帧不重复送入 DLSSNR 来凑 80 FPS,不修改已有的有效帧/历史重置规则;旧内容的 UI 重绘不计作新内容帧。 - -DXGI 的 frame-latency waitable object 可以提供队列进度/容量同步,但它本身不是用户指定 80 FPS 的高精度节拍器;应与输出截止时间配合使用。[微软接口说明](https://learn.microsoft.com/en-us/windows/win32/api/dxgi1_3/nf-dxgi1_3-idxgiswapchain2-getframelatencywaitableobject) - -**GPU 调度优先级继续保持 REALTIME,不纳入此方案的调整范围。** - -### 第二阶段:分别接入 FG 和其他呈现器 - -- **DLSSFG:** 保留当前生成帧/真实帧顺序和槽生命周期,扩展现有输出时钟,不能照普通路径直接合并或丢弃任意一张补帧。基础 80 FPS、2 倍 FG 的名义输出为 160 FPS,不能把应用输出仍锁 80 后误认为只是限了基础帧。 -- **XeSSFG:** 优先将目标交给 XeLL 的帧率限制器。当前初始化只启用低延迟,未传入限帧目标。Intel 文档说明 XeLL 能结合 FG 计算基础帧率,并不推荐叠加另一个限帧器;若应用仍需自行等待,应放在 xellSleep 之前。不能在代理 Present 前后随意再套一层等待。[Intel XeLL 官方指南](https://github.com/intel/xess/blob/main/doc/xell_developer_guide_english.md) -- **Windows 11 Composition Swapchain:** 可进一步研究 `IPresentationManager::SetTargetTime`,让系统按目标显示时刻处理提交。当前该呈现器没有设置目标时间,而且是编译开关分支,不是本次默认路径。其时钟类型为 SystemInterruptTime,必须正确换算,不能直接塞入 QPC tick;此接口也不保证精确到屏幕扫描瞬间。[微软目标时间接口](https://learn.microsoft.com/en-us/windows/win32/api/presentation/nf-presentation-ipresentationmanager-settargettime) - -### 游戏端的限帧边界 - -Magpie 自己可以管理捕获、后端处理和自身呈现,但在现有 WGC 架构下不能直接限制任意游戏的渲染循环。因此第一阶段仍由游戏自己的限帧或 RTSS 控制源程序,Magpie 内置自己的稳定输出。 - -如果以后希望在 Magpie 中一个入口同时设置两侧,可以做可选 RTSS 联动。本机官方 SDK 示例已有 `LoadProfile / GetProfileProperty / SetProfileProperty / SaveProfile / UpdateProfiles` 和 `FramerateLimit` 属性。同步模式属性的公开支持范围仍需核实,不能直接假设所有磁盘键都支持 SDK 设置。[RTSS SDK 示例]() - -这种联动属于配置管理,不能称为跨进程逐帧同步。应只管理选定的应用配置,保存并恢复自己改动的字段,避免覆盖用户中途修改;Magpie 自身用内置限帧时也应避免外层 RTSS 再限一次。自行注入游戏以控制 Present 是另一个范围更大的实现,不是加入 Magpie 呈现时钟的必要条件。 - -## 如何验证实际收益 - -保留 80 FPS、同一效果参数、同一场景和显示条件,先由用户分别比较:只限游戏、只限 Magpie、双方 Front Edge、游戏限帧加 Magpie 内置稳定输出。再在双方目标不变时比较 Async 与 Front Edge,区分限低帧率本身和限帧位置的收益。VRR 修正、队列深度调整应作为独立变量比较,避免一口气混在同一结果里。 - -需要同时看:新内容间隔、全部 Present 间隔、实际显示时间、源帧年龄/延迟,以及两进程的 GPU 等待。尤其不能让 UI 重绘刷高的 Present 频率替代新画面频率。PresentMon 可以按进程和交换链记录提交及显示指标,但可用字段取决于路径和版本;其 CPU 开始时间也不等于游戏真实输入采样时间。[PresentMon 官方字段说明](https://github.com/GameTechDev/PresentMon/blob/main/README-ConsoleApplication.md) - -当前没有这轮 RTSS 开关前后的成对逐帧记录,因此本报告确认了机制和实现缺口,尚未给出实测增益比例。建议先实现普通输出节奏控制并单独处理显式 VRR 缺口,再按上述口径判断 1% Low、体感和延迟是否共同改善。 diff --git a/docs/experimental/reviews/20260905-slider-default-reset-REVIEW.md b/docs/experimental/reviews/20260905-slider-default-reset-REVIEW.md deleted file mode 100644 index 24557a461..000000000 --- a/docs/experimental/reviews/20260905-slider-default-reset-REVIEW.md +++ /dev/null @@ -1,89 +0,0 @@ -# 参数滑条双击恢复默认值:交互与实现 review - -日期:2026-09-05。基于 0.6.5 r5 local / fix2 当前源码。本次仅调查和文档,未修改或运行程序。 - -## 已确认的需求 - -用户确认:**双击某个效果参数滑条,恢复该效果器自身声明的默认值。** 不恢复所属效果组的预设值,不恢复本次运行开始时的值,不重置整组。范围按“效果参数”理解:效果组编辑页和运行时工具栏参数面板;主页倒计时、光标尺寸等普通设置滑条不在本次建议范围内。 - -2026-09-05 补充:用户指定双击阈值为 **170ms**。该值只约束效果参数滑条的重置手势,不修改 Windows 或整个 ImGui 的双击速度。 - -当前发行包有 144 个效果参数,其中 127 项使用数值滑条、9 项为布尔控件、8 项为枚举选择。此次手势只覆盖 127 项滑条;没有要求给复选框和下拉框增加双击重置。 - -更正调查交流中的例子:当前 FSR_RCAS 自身默认锐化值和内置 FSR 组显式值都是 **0.87**;并不存在本版本自身默认 0.5 的差异。两种默认来源仍应在设计上区分。 - -## 当前两套界面 - -| 位置 | 当前机制 | 重置应经过的路径 | -| --- | --- | --- | -| 效果组编辑页 | XAML Slider 双向绑定 ScalingModeParameter.Value;变更写入组配置,最后一次编辑约 1 秒后保存 | 从原始参数描述取默认值,通过现有参数变更入口更新和保存;该页面当前没有直接向运行后端提交 Live 更新 | -| 工具栏效果参数 | ImGui SliderInt 表示离散刻度,再换算真实值;运行草稿区分 Live / Restart / 不可用 | 修改该参数草稿,Live 入更新队列,Restart 标记待重新启用;复用现有自动保存和冲突处理 | - -代码位置:[XAML Slider](<../../../src/Magpie/ScalingModesPage.xaml#L97>)、[参数读取](<../../../src/Magpie/EffectParametersViewModel.cpp#L128>)、[编辑保存](<../../../src/Magpie/EffectParametersViewModel.cpp#L272>)、[运行滑条及提交](<../../../src/Magpie.Core/OverlayDrawer.cpp#L1361>)。 - -XAML 目前没有为该滑条注册双击重置;ImGui 参数代码也没有重置手势。它们需要分别接入,不能只修改一个页面后宣称统一支持。 - -## 推荐行为合同 - -1. 同一个可用滑条上两次有效左键点击的按下时间间隔满足 `0 < Δt ≤ 170ms` 时,读取 `EffectParameterDesc.constant.defaultValue`,不读取当前值、启动快照或组配置作为“默认”。标签、数值文字、空白区域及整行不默认纳入命中区域。 -2. 默认值来自当前已加载的效果描述;不要为第三方效果硬编码一张独立默认值表。运行途中更换效果文件时,应随重新加载/重新启用更新,避免描述和正在运行的效果版本不一致。 -3. 仅重置当前效果器实例的当前参数。同一组内两个同名效果器必须按实例索引区分。 -4. 按现有 Live / Restart 分类处理。Restart 参数只保存并提示待重新启用,不因双击自动关闭运行窗口;不重启 Magpie。 -5. 保留自动保存。写入失败或冲突沿用已有错误详情;不能弹出“重置成功”来掩盖保存失败。 -6. 已为默认且运行/草稿一致时不重复提交。不可用、隐藏、无效描述参数不接受手势。 -7. 提供轻量提示“双击恢复默认值:{值}”。后续可补键盘可访问的“恢复默认值”命令,但不把它设为这次新增功能的前置要求。 - -### 170ms 的实现口径 - -按下到下一次按下计时是本次文档采用的明确口径;阈值包含 170ms,超过则按普通单击处理。两次之间必须已经抬起左键,第一次不能是拖动;命中需属于同一效果器实例的同一滑条,并满足局部移动容差。仅间隔接近并不足以触发重置。 - -两套 UI 各自记录控件级点击状态,使用各自输入事件的单调时间基准,不混用不同单位或时钟。优先使用原始事件时间,避免 UI 队列堵塞把两次输入的间隔测错。控件销毁、会话更换、焦点/捕获丢失、拖动或 Ctrl 输入时清除候选;识别一次后消费该点击对,三连击不能让第三击再次复用第二击触发重置。 - -不要只依赖默认 XAML DoubleTapped 或 ImGui 全局双击判断,因为它们的默认阈值未必是 170ms;也不要修改系统双击时间或全局 `MouseDoubleClickTime` 来影响其他控件。具体路由实现仍待后续代码修改时核对。 - -## 两项应先解决的默认一致性问题 - -### P2:Jinc 的 0.825 默认值被步进归一化改变 - -`Jinc.sinc` 声明默认 0.825,但步长为 0.01;该值不在滑条刻度上。按当前浮点实现推演,`NormalizeEffectParameterValue` 会将它量化为约 0.83。普通渲染在参数缺省时使用声明的 0.825,参数页面和运行面板初始化则先量化,因此显示/草稿与渲染初值可能不同。仅添加“赋值默认值”的事件仍无法可靠恢复真正的 0.825。 - -建议后续优先保持 0.825 不变,让步进能够表达它,例如把该效果器步进改为 0.005;或者统一支持默认值作为额外合法刻度。第二种方案影响所有滑条及归一化路径,范围明显更大。改步进属于后续待批准的程序修改,本次未实施。 - -依据:[Jinc.hlsl](<../../../src/Effects/Jinc.hlsl#L27>)、[数值归一化](<../../../src/Magpie.Core/include/EffectParameterValue.h#L23>)、[渲染初值](<../../../src/Magpie.Core/EffectDrawer.cpp#L520>)。 - -### P2:运动可视化的缺省后端增益与 UI 不一致 - -`Diagnostics\\FrameGuidance_Motion.gain` 声明默认 0.08,原生工厂缺省却取 1.0。添加效果器后若从未保存该参数,原生渲染用 1.0,UI 显示 0.08。直接实现“已经是默认就不做任何事”,会错过修正运行值。 - -建议先统一后端缺省值与声明,再实现不重复提交。不要为了这个问题让每个无变化双击都强制改写全组。 - -依据:[声明](<../../../src/Effects/Diagnostics/FrameGuidance_Motion.hlsl#L5>)、[后端缺省](<../../../src/Magpie.Core/NativeEffectBackendFactory.cpp#L57>)、[原生设置](<../../../src/Magpie.Core/FrameGuidanceDiagnostics.h#L12>)。 - -## 手势接入的注意点 - -| 问题 | 建议 | -| --- | --- | -| 第一次单击先跳到轨道位置 | 不为等待双击而延迟所有正常单击。允许首击产生短暂正常调整,第二击识别后以默认值结束;自动保存可能有中间写入,不能承诺一次双击只写一次文件 | -| 第二击重置后仍被拖动覆盖 | 识别双击后结束该次滑条拖动/捕获,抬起鼠标前不继续按当前位置改值 | -| XAML 子控件已处理指针事件 | 检查 Thumb/轨道事件路由;需要时接收已处理的双击事件,确保不重复触发。这里只确定需要核对路由,未做界面实测 | -| ImGui 输入框交互 | 当前项目使用的 ImGui 1.91.8 SliderScalar 是 Ctrl+单击或导航触发输入;不要把 DragScalar 的“双击进入输入”规则误套到 SliderInt。仅在普通滑条态处理重置,保留 Ctrl+单击和文本选择 | -| 滑条值实际是刻度索引 | 重置真实参数值,再更新刻度;不能把 0.87、60 等直接当作 SliderInt 的刻度索引 | -| 多线程和销毁 | 复用现有更新队列、保存快照与生命周期保护;不要在鼠标事件中同步写配置文件,或保留裸引用跨异步等待 | - -本地依赖依据:ImGui SliderScalar(本地 `%USERPROFILE%/.conan2/p/b/imgui9cc4bdb855440/p/res/src/imgui_widgets.cpp:3200`)。这里只阅读本项目实际依赖,未升级依赖或运行示例。 - -## 与已有功能的关系 - -“恢复本次运行初始值”恢复整个本次会话的启动快照;双击恢复单个效果器参数默认值。两个入口需要保留不同说明。 - -此前 review 中尚未实施的运行重建沿用旧参数、后端拒绝值后的细粒度回滚、编辑页与运行页同步通知问题,仍可能影响重置后的体验。这次手势本身不自动解决它们,也不应在文案中承诺跨页面实时一致。 - -另外,当前 ImGui SliderInt 使用刻度索引并自定义显示文本,Ctrl+单击的真实数值输入语义值得另行核对;应与双击行为分开确认,避免一并改变用户已有输入习惯。 - -## 留给用户的实施后验收清单 - -尚未执行以下测试:浮点/整数滑条重置;拖动和单击保持原行为;默认值为负数或边界值;Jinc 精确恢复 0.825;同名效果器实例隔离;Live 当场生效;Restart 不自动重启;双击后抬起不回跳;切页和关闭窗口后保存;保存失败与参数冲突;高 DPI 与不同双击速度;Ctrl+单击输入和键盘调节不受影响。 - -170ms 补充验收:169ms 和 170ms 可触发,171ms 不触发;两次点击不同滑条不触发;拖动后接一次点击不触发;三连击只消费一对;事件处理延迟不改变原始时间间隔判断。边界检查采用事件时间模拟或日志核对,人工点击无需精确复现 1ms 差异。 - -后续实施范围建议先包含两处默认一致性修复,再接入两套界面的重置手势。当前文档不构成已经实施或通过运行测试的声明。 diff --git a/docs/experimental/reviews/20260905-toolbar-pass-through-REVIEW.md b/docs/experimental/reviews/20260905-toolbar-pass-through-REVIEW.md deleted file mode 100644 index d4c5a08f7..000000000 --- a/docs/experimental/reviews/20260905-toolbar-pass-through-REVIEW.md +++ /dev/null @@ -1,157 +0,0 @@ -# 工具栏直通对比:方案与代码 review - -日期:2026-09-05。基于当前 0.6.5 r5 local / fix2 工作区。状态:**仅调查与方案,未修改、构建、运行或部署程序;运行验收由用户进行。** - -## 结论与需求边界 - -建议增加工具栏的“原图对比 / Pass through”开关,作为运行会话的显示状态。开启后显示未经过效果组处理的捕获画面,原有效果链、光流、降噪、补帧及其历史状态继续更新;关闭后直接显示当前处理结果。**切换对比本身不重新加载效果、不改变组配置,也不清空时间历史。** - -这符合用户“虽然直通,但是效果仍然继续处理,只是被覆盖”的要求。它需要一条原图显示分支,不能实现成切换到空效果组,也不能通过跳过后端绘制来省算力。用户用“直通效果”描述功能,但实现上不宜将它作为可排序、导入和导出的普通效果器。 - -无补帧与 DLSS 补帧可在前端提交画面前选择分支;XeSS 补帧由 SDK 的代理交换链接管最终帧生成,宜利用独立合成层覆盖原图,同时保留代理交换链的正常输入和提交。后一条路线有现有代码基础,但仍需验证遮挡情况下 SDK/呈现器是否持续工作。 - -“不重载”约束针对对比开关。窗口尺寸变化、设备丢失,以及用户主动应用 Restart 参数仍可能要求正常的资源重建;不能因此承诺整个运行期间永远不重建。 - -## 建议的可见行为 - -| 项目 | 建议 | -| --- | --- | -| 名称 | 中文“原图对比”,英文“Pass through”;提示“显示原图,效果继续处理” | -| 位置 | 工具栏效果参数按钮附近,使用与其他工具栏开关一致的选中态 | -| 操作 | 单击切换,再单击返回;本次未擅自指定全局快捷键 | -| 生命周期 | 每次新建运行会话默认关闭;同一会话内工具栏收起不改变对比状态 | -| 状态提醒 | 工具栏选中态加轻量“原图对比中”提示;防止用户忘记开启后误以为参数失效 | -| 参数面板 | 仍可修改并自动保存;Live 继续作用于后台效果,提示当前原图不会显示这些改动 | -| 保存 | 不写成效果参数,不插入效果组,不改变已有组的导入/导出结构 | -| 对比尺寸 | 与处理结果使用相同窗口、目标矩形、裁剪及宽高比规则;显示原图所需的基础插值仍执行 | -| 补帧 | 原图分支不显示生成帧;在额外呈现时刻保持相应原始捕获帧,补帧仍在另一分支继续计算 | -| 性能读数 | 继续表示正在运行的效果链,不能把原图对比称为“关闭效果后的性能” | - -双击滑条的 **170ms** 是另一项交互要求,不应套用到这个工具栏开关。具体规则见[滑条重置 review](<20260905-slider-default-reset-REVIEW.md>)。 - -## 当前代码为什么不能只加一个布尔开关 - -### 1. 原图没有可直接使用的前端显示副本 - -后端从 `_frameSource->GetOutput()` 获取捕获图像,依次执行效果器,再将最终效果输出发布到共享纹理。前端的 `_frontendBaseTexture` 和 `_frontendPresentedBaseTexture` 都保存处理后的内容,没有原图副本。[后端执行](<../../../src/Magpie.Core/Renderer.cpp#L2210>)、[前端选择](<../../../src/Magpie.Core/Renderer.cpp#L595>)。 - -因此,原图应在效果前留出分支,并通过有明确所有权及同步规则的共享资源到达前端。不能让前端直接读取仍由捕获线程复用的纹理,也不能把指向捕获帧的裸指针长久保存下来。 - -当前 WGC 输出是裁剪后的 BGRA8;呈现资源通常为 RGBA8。两者的通道含义及目标尺寸不一致时需要采样/转换,不能将 `CopyResource` 当作颜色转换或缩放。当前前端的 `CopySubresourceRegion` 也只是区域复制。[捕获输出格式](<../../../src/Magpie.Core/GraphicsCaptureFrameSource.cpp#L61>)、[前端复制](<../../../src/Magpie.Core/Renderer.cpp#L644>)。 - -### 2. 隐藏效果结果后仍必须消费呈现队列 - -DLSS 补帧的发布路径使用有界共享纹理环。同步发布会等待空槽,前端消费后才能继续复用;如果开启原图时停止处理这些消息,后端会在等待槽位时停住,用户要求的持续运算也就不成立。[槽位数量](<../../../src/Magpie.Core/Renderer.cpp#L1825>)、[发布与同步](<../../../src/Magpie.Core/Renderer.cpp#L2314>)。 - -对比状态只影响最终可见内容。共享槽的消费、锁释放、完成事件、队列代次校验及处理结果前端副本仍要正常更新。 - -### 3. XeSS 补帧发生在前端提交之后 - -DLSS 补帧由后端回调发布生成帧,前端可以在收到帧后选择显示原图。XeSS 补帧则通过代理交换链处理提交内容;把原图替换成它的输入,会让原图也参与补帧,并改变效果分支的时间历史。[DLSS 生成帧回调](<../../../src/Magpie.Core/Renderer.cpp#L2285>)、[XeSS 交换链初始化](<../../../src/Magpie.Core/XeSSFGPresenter.cpp#L547>)。 - -不能用 `xefgSwapChainSetEnabled(false)` 实现这项对比。捆绑的 Intel 文档说明,停用后会将输入帧历史标记为在下次启用时清理,这与保留处理状态的目标相冲突。本地 SDK 说明(工作区 `dependencies/XeSS-SDK-3.0.1/doc/xess_fg_developer_guide_english.md:654`)。 - -## 推荐架构 - -```text -捕获、已有裁剪 - ├─ 原图分支:安全副本 → 基础尺寸/颜色适配 → 前端原图副本 - └─ 效果分支:光流、效果、降噪、补帧 → 原有共享环 → 前端处理结果 - -无补帧 / DLSSFG: - 前端选择原图或处理结果 → 工具栏、光标 → 呈现 - -XeSSFG: - 前端处理结果 → XeSS 代理交换链 → SDK 生成并呈现 - 独立合成层:对比开启时画不透明原图 → 工具栏、光标 -``` - -原图是当前捕获源经过应用已有裁剪后的内容,不是重新截取整个屏幕。这样可保持原有窗口选择,并避免把 Magpie 自己的覆盖层再次捕获进来。 - -### 原图分支与同步合同 - -建议给原图副本附带 `captureFrameId`、捕获时间、资源代次、源尺寸和有效区域。后续 HDR 加入时,再携带颜色语义。前端只显示已经完成 GPU 写入、属于当前运行会话和资源代次的原图。 - -初步可采用“与现有发布批次配对的原图共享资源”,复用已有槽位生命周期;每次发布处理结果时明确其对应的原始捕获帧。生成帧只引用保留的原始帧,不再重复计算一遍效果。 - -这是正确性优先的基线,不表示必须为每张生成帧分配一张全分辨率原图。后续可以把原图缓存按捕获帧组织,多个生成帧引用同一原图;但必须先保证引用计数/完成事件正确,不能以减少复制为由复用仍在显示的纹理。 - -若第一次点击对比才创建资源和复制原图,就可能出现第一下等待。建议在开启运行会话时准备必要资源,并持续维护最新原图;尚无可显示原图时暂不切换选中态,给出“正在等待捕获画面”,不能显示黑帧或旧会话残影。 - -### 无补帧与 DLSSFG 的切换位置 - -在 `_FrontendRender` 取得有效的处理结果并完成正常同步之后,决定当前显示的 scene texture,再按原有路径绘制工具栏与光标。处理结果的稳定副本继续保存处理内容,同时需要为当前显示分支维护有效性,避免 UI 重绘时跳回处理结果。 - -DLSS 生成帧处于两个真实帧之间,不能简单把“最新捕获帧”当作所有呈现时刻的精确对应原图。建议初版明确采用“保持与呈现时序对应的最近真实捕获帧”,为生成帧记录其参考区间;具体选择前一帧还是最近的已可用帧须与现有发布顺序核对。这是视觉开关,并非运动算法逐帧同时间点的科学比较。 - -存在 FrameRate_Filter 时,原图更新速度也受本会话捕获/处理调度影响。例如组内限制为 60FPS,不能把原图模式称为应用自身未受限制的原生帧率。 - -### XeSSFG 的覆盖路径 - -当前 XeSSFG 已通过 DirectComposition 建立独立 UI 表面,可以作为原图覆盖的候选基础。开启对比时先画不透明原图和必要黑边,再画工具栏、提示及光标;关闭时恢复透明背景,让下方代理交换链显露。[独立表面创建](<../../../src/Magpie.Core/XeSSFGPresenter.cpp#L358>)、[覆盖层绘制](<../../../src/Magpie.Core/Renderer.cpp#L707>)。 - -需补充四件事: - -1. 原图新帧到达也触发覆盖层重绘。当前触发条件偏向光标、工具栏和 UI 状态,不能让鼠标不动时原图停止更新。 -2. 原图与黑边覆盖完整的应显示区域;透明的间隙会露出下方正在补帧的结果。工具栏及其鼠标交互继续位于最上层。 -3. 下方交换链保持原有处理后输入、运动信息、提交及错误处理。对比状态不触发 FG 的启停或历史重置。 -4. 验证完全不透明覆盖后,系统/驱动是否对下层呈现降速或裁剪。调用仍继续不等于 SDK 实际生成工作必然继续;要以帧计数和历史重置日志确认。若具体路径无法保证,应暂不开放该组合,并说明原因,不能假装满足“保持处理”。 - -当前表面是 RGBA8。未来 HDR 对比可考虑独立 FP16 表面,但 UI 的线性混合、白点、合成行为仍需适配。Microsoft 明确列出 DirectComposition surface 支持 BGRA8、RGBA8 和 FP16,**未列出 R10 HDR10 格式**;因此不能把 XeSS 主交换链的 R10 格式常量不加区分地复用给该表面。[CreateVirtualSurface 官方说明](https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositiondevice2-createvirtualsurface)。 - -## 必须一起处理的交互与一致性 - -### 光标和覆盖层 - -背景相关的遮罩/单色光标需要读取当前真正可见的背景。现有独立层路径给光标的是 `_frontendPresentedBaseTexture`,即处理结果;直通时必须传原图背景,否则光标反色或遮罩会与背景不匹配。[当前背景传入](<../../../src/Magpie.Core/Renderer.cpp#L725>)。 - -对比开关只选择画面分支;UI、提示、光标、鼠标映射以及交互热区应保持连续。若效果器本身改变几何形状,原图只能提供统一目标矩形下的基础映射,不能声称所有几何变形前后都逐像素对齐。 - -### 截图 - -当前工具栏默认截图取最后一个配置效果器输出,右键可取指定效果/pass/输出;它不是通用的“当前屏幕所见”入口。[工具栏截图](<../../../src/Magpie.Core/OverlayDrawer.cpp#L824>)。 - -建议后续让普通截图保存当前选中的画面分支,仍按产品现有习惯不带工具栏;保留右键中间 pass 的诊断截图,明确它仍来自处理链。否则开启原图后按截图却得到处理图,容易误导对比。 - -“同帧原图与效果图成对保存”很适合后续扩展,但用户本次没有明确要求;先不作为必须新增的按钮。若实现,需记录两张图的捕获帧对应关系,生成帧不能冒充同时间点的原始真实帧。 - -### 错误与回退 - -| 情况 | 建议处理 | -| --- | --- | -| 尚无有效原图 | 保持当前画面,提示等待新捕获帧 | -| 原图资源创建/同步失败 | 对比保持关闭,保留效果处理;提示无法显示原图并提供详情 | -| 运行中原图分支失效 | 退出对比并告知,避免一直显示冻结画面 | -| 效果链自身失败 | 沿用正常错误处理,不能用原图覆盖隐藏错误,让用户以为效果仍在处理 | -| 资源重建 | 清除旧代次原图;重新获得有效图像后再恢复该会话的显示状态 | -| SDK 组合未通过保持处理验证 | 明确显示组合暂不支持,说明可切换何种补帧路径;不自动停用用户选择的效果 | - -错误详情宜复用已有 ErrorService,至少记录对比状态、后端、源/目标格式、尺寸、资源代次和同步失败位置;不另建一套只返回“失败”的提示。 - -## 成本与性能含义 - -效果继续处理意味着开启原图对比不会获得关闭效果后的省电或性能收益;新增复制和合成还会增加一定开销。具体耗时未实测。 - -仅按纹理像素存储估算,一张 3840×2160 RGBA8 为 **31.64MiB**,FP16 RGBA 为 **63.28MiB**;未计行对齐、视图、驱动分配和同步对象。如果按四槽、每槽一张完整原图,单这部分分别约 126.56MiB / 253.13MiB,前端稳定副本还会增加占用。 - -因此建议优先保留捕获尺寸原图,在显示端做基础放大,并复用同一捕获帧对应的多个生成帧引用。是否采用独立缓存或每槽副本,在后续实施时根据同步复杂度决定;不在研究阶段宣称开销可以忽略。 - -## 后续实施分段与用户验收 - -| 阶段 | 内容 | 完成标准 | -| --- | --- | --- | -| A | 原图副本、基础尺寸适配、无补帧前端开关 | 连续点击不重建效果;停止移动鼠标原图仍更新;裁剪与黑边正确 | -| B | DLSSFG 队列消费及原图帧对应 | 后台生成帧计数持续,环不堵塞;切回不用热身;原图不会混入生成帧 | -| C | XeSSFG 独立覆盖与重绘 | 下层仍持续处理且无历史清理;覆盖完整;切换无黑屏、无首帧等待 | -| D | 截图、背景相关光标、参数提示及失效回退 | 截图与当前分支一致;Live 修改可在切回后看到;错误可诊断 | -| E | 与未来 HDR 输出适配共用边界 | 原图对比不切换系统 HDR;两条分支正确转换到同一显示合同 | - -阶段划分用于降低后续排查难度,不代表可以完成 A 后就把所有补帧组合标成已支持。用户希望的完整行为需要逐项满足。 - -留待实施后的检查包括:空组/单效果/多效果、窗口与全屏、不同 DPI、放大与降采样、动态内容和静态内容、DLSSFG 各倍率、XeSSFG 各倍率、FrameRate_Filter、Live 与 Restart 参数、拖动窗口、尺寸变化、截图、连续切换,以及异常退出与资源释放。**本次未进行这些运行测试。** - -## 等用户 review 的建议默认选择 - -本报告先采用:单击切换、会话状态默认关闭、基础双线性适配、不新增默认快捷键、普通截图随可见分支。它们是待 review 的产品建议,不是已经批准或实施的行为。 - -HDR 后续的“仅比较 HDR 增强”和这里的“比较整个效果组”是两种范围。本次先按整个效果组理解,不额外增加第二个模式。HDR 复核见[兼容路线二次 review](<20260905-hdr-compatibility-second-REVIEW.md>)。 diff --git a/docs/experimental/reviews/20260905-v0.6.5-r1-r4-maintainability-REVIEW.md b/docs/experimental/reviews/20260905-v0.6.5-r1-r4-maintainability-REVIEW.md deleted file mode 100644 index 50bf01cbf..000000000 --- a/docs/experimental/reviews/20260905-v0.6.5-r1-r4-maintainability-REVIEW.md +++ /dev/null @@ -1,161 +0,0 @@ -# 0.6.5 r1~r4 实现与维护性 review - -日期:2026-09-05。范围:交接前 r1~r4 的实现,以及它们在当前 r7 工作树中的遗留情况。本轮只调查、整理文档,不修改程序、不构建、不部署。 - -后续状态:用户已授权在 r8 修复本报告前四项,并清理相同 shader 字节码生成。实施与交付状态见 [r8 记录](../todos/20260905-v0.6.5-r8-TODO.md)。下文保留审查时的结论。 - -## 结论 - -**有补丁式复杂化、重复实现和没有收尾的问题,但没有依据把这几版整体判为需要推倒重写。** 最值得处理的是光流失败后的历史状态、实时参数的运行快照,以及 AMD OF 的重复构建工作。应针对这些边界收口,保留已解决真实需求的部分。 - -r3 初版到 fix4 曾经明显过度修补输入系统,这在当时的复盘文档中已有直接证据;不过那套四态鼠标机、物理按键校正、额外 input tick 等并未原样留到现在。后来重新引入的有界输入缓冲和独立 UI 呈现,是经过用户确认的需求,不能因为名字像旧补丁就删除。 - -### 优先级总览 - -| 项目 | 当前状态 | 判断与建议 | -| --- | --- | --- | -| 光流提供者停用后每帧继续要求 reset | 新确认的遗留缺陷;r4 与当前代码均存在 | 优先修正降级状态转换 | -| 实时参数与自动重建、应用失败之间没有统一状态 | r3 引入的边界;此前 review 已列出,目前仍存在 | 优先完成运行快照与应用结果同步 | -| AMD OF shader 每次编译目标均重新生成 | r4 引入;仍存在 | 可直接精简的构建重复工作 | -| XeSS 参数显隐规则在主窗口按参数名全局匹配 | r4 引入;仍存在 | 限定效果器范围,两套 UI 共用小型规则函数 | -| AMD OF 多套 permutation 名称实际写入同一组字节码 | r4 构建适配;仍存在 | 可合并重复数据,不必扩大成 SDK 重构 | -| 文档状态与源码版本缺少明确对应 | r1~r4 已存在,r5~r7 也继续累积 | 整理状态入口与版本快照,属于共同维护债务 | - -其中前两项关系实际行为;后面几项主要关系构建时间、理解成本和将来修改时的一致性。没有通过本轮静态审查推算实际 FPS 收益。 - -## 1. 光流降级后,reset 从一次状态变化变成了永久状态 - -**确定性:高;优先级:P2,建议优先处理。** - -当前 `produceMethod` 中,提供者连续失败 3 次后将 `ready` 设为 false,停止继续估算。但下一帧 `attempted` 为 false、`valid` 仍为 false,于是再次给 Zero Motion 写入 `ProviderFailure` 和 `requiresHistoryReset=true`。以后每张捕获帧都会重复。 - -代码:[FrameGuidanceService.cpp:636](<../../../src/Magpie.Core/FrameGuidanceService.cpp#L636>)。交接前同样的分支见 r4 还原快照:672(工作区 `.tools/r1-r4-review/r4-baseline/src/Magpie.Core/FrameGuidanceService.cpp:672`)。 - -对直接取得源尺寸 guidance 的消费者,这个 reset 会继续传给 SDK。DLSS SR 的“同帧只 reset 一次”保护不能阻止每个新 frameId 都 reset;DLSSFG 也如此。证据:[DLSSSRUpscaler.cpp:264](<../../../src/Magpie.Core/DLSSSRUpscaler.cpp#L264>)、[DLSSFrameGenerator.cpp:684](<../../../src/Magpie.Core/DLSSFrameGenerator.cpp#L684>)。 - -影响是:原意为“停用故障光流,使用零向量继续处理”,实际却可能使时域消费者一直重建历史、FG 一直处于 reset 行为。经过尺寸适配的路径还存在不同的零数据元信息处理,不能假定所有消费者表现完全一样。 - -建议只在进入降级、恢复估算或明确的历史失效事件时发出 reset;稳定的 Zero Motion 会话继续推进 frameId,但不反复 reset。保留有限失败阈值和真实的零纹理,不增加无限重试,也不改变用户已经确定的光流来源与档位优先规则。届时应一起核对尺寸适配前后的 reset 一致性。 - -这不是对最近“拖动性能分析器后 DLSSFG 异常”的重新归因:没有证据证明那次触发了这里的 provider 连续失败分支。 - -## 2. 实时参数状态没有完全收口 - -**确定性:高;优先级:P2。此前已经披露,本轮再次确认仍存在。** - -### 2.1 自动重建仍可能从启动值恢复 - -Live 修改成功后更新的是 Renderer 的 `_runtimeEffectOptions`;切换全屏/窗口模式、部分源窗口变化导致的自动重建,仍经 `RestartAfterSrcRepositioned()` 使用原 `_options` 启动。新的 Renderer 又从 `_options.effects` 初始化。 - -证据:[Renderer.cpp:1551](<../../../src/Magpie.Core/Renderer.cpp#L1551>)、[ScalingWindow.cpp:440](<../../../src/Magpie.Core/ScalingWindow.cpp#L440>)、[Renderer.cpp:2221](<../../../src/Magpie.Core/Renderer.cpp#L2221>)。r4 快照中已存在运行副本和相同的自动重建入口。 - -结果可能是参数已自动保存,但画面因内部重建恢复旧值。普通资源 resize 使用运行副本的路径不能与这个完整重建入口混为一谈;显式“应用并重新缩放”也走另一条更新效果参数的路径。 - -建议所有自动重建统一取得“当前已应用值”的快照。需要重启但尚未应用的目标值仍单独保存,避免一次窗口移动就顺带应用它们。不要再为每一种重建触发分别补一份参数复制逻辑。 - -### 2.2 后端拒绝修改时,目标值与实际值会分离 - -后端 `ApplyLiveParameters` / `UpdateParameters` 失败时保留旧运行值;面板先前已经改为新值,当前只有错误上报,没有对应控件的应用确认或失败状态同步。[Renderer.cpp:1572](<../../../src/Magpie.Core/Renderer.cpp#L1572>)、[OverlayDrawer.cpp:1481](<../../../src/Magpie.Core/OverlayDrawer.cpp#L1481>)。 - -r5 已增加保存可靠性和具体错误详情,因此不能说现在“完全没有报错”或“保存机制仍是 r3 原样”。缺口在于保存成功与 GPU 应用成功是两件事。 - -建议用简洁的会话标识、修订号和已应用值回传完成闭环;可以保留用户目标值并标明尚未生效。不应恢复 r3 初期那套逐参数多层回执、状态槽和互相纠正的队列。 - -主窗口旧参数页与运行面板缺少完整变更通知,也可在这次状态整理中一起处理。相关旧报告:[r5 fix1 实时参数保存 review](<../todos/20260905-v0.6.5-r5-fix1-REVIEW.md#L1>)。 - -## 3. AMD OF 构建生成没有增量判断 - -**确定性:高;优先级:P3。这是本轮最明确的重复工作之一。** - -`GenerateFidelityFXOpticalFlowShaders` 挂在 `BeforeTargets="ClCompile"`,只有功能开关条件,没有 `Inputs/Outputs` 或等价的缓存判断。每次实际进入该目标都会执行脚本;脚本循环编译 7 个 pass × 2 个 HDR 选项,再无条件重写 28 份 header。 - -证据:[Magpie.Core.vcxproj:233](<../../../src/Magpie.Core/Magpie.Core.vcxproj#L233>)、[生成脚本:101](<../../../scripts/Generate-FidelityFXOpticalFlowShaders.ps1#L101>)。r4 还原项目文件中已包含这个目标。 - -即使只改不相关 C++,仍可能重新执行这些 shader 编译,并因 header 时间戳变化引起后续重编译。这里指进入 MSBuild 编译目标的情况,不把 IDE 在外层直接跳过整个工程的情况算进去。 - -建议增加覆盖 SDK shader/include、脚本、DXC 和编译选项的生成指纹;输出缺失或指纹变化才重建,内容未变不覆盖 header。DXC 路径也可从当前 Windows SDK 配置传入,避免脚本另写一份固定版本路径。保留完整 Rebuild 的正常语义,不需要新建大型构建框架。 - -## 4. 效果器专用 UI 规则进入了通用参数编辑器 - -**确定性:高;优先级:P3。现有内置效果通常不会触发问题,主要影响扩展性。** - -主窗口只凭参数名 `opticalFlowMethod`、`amdOpticalFlowMode`、`nvidiaOpticalFlowQuality` 认定它们属于 XeSS,随后根据固定数字 1/2 隐藏质量控件;没有先核对效果器 ID。运行 Overlay 则先检查是不是 XeSSFG 效果。 - -证据:[EffectParametersViewModel.cpp:149](<../../../src/Magpie/EffectParametersViewModel.cpp#L149>)、[显隐实现:309](<../../../src/Magpie/EffectParametersViewModel.cpp#L309>)、[OverlayDrawer.cpp:1340](<../../../src/Magpie.Core/OverlayDrawer.cpp#L1340>)。r4 快照中已有这项差异。 - -第三方效果如果使用相同名字、但枚举含义不同,会被主窗口套用 XeSS 的规则,两处参数页也可能显示不一致。这是专用规则作用域不够明确,并非所有条件显隐都应删除。 - -建议先补效果器范围,再把共同的显隐判断抽成小型函数。DLSSNR 的条件参数列表也可按相同原则集中维护;暂时没有必要设计一整套条件表达式语言或新的 UI 元数据引擎。 - -## 5. AMD OF 的多个 permutation 名称没有对应独立实现 - -**确定性:高;优先级:P3,低于增量构建。** - -脚本只生成通用 FP32 字节码,然后给普通、`_wave64`、`_16bit`、`_wave64_16bit` 四套名字重复写入同一组 blob 和资源表。SDK 对应的 include/表名接口确实需要被满足,但这并不代表项目已经实现了四种编译优化。 - -证据:[生成脚本:108](<../../../scripts/Generate-FidelityFXOpticalFlowShaders.ps1#L108>)、[四套名称循环:129](<../../../scripts/Generate-FidelityFXOpticalFlowShaders.ps1#L126>);SDK 使用方为 ffx_opticalflow_shaderblobs.cpp:28(工作区 `dependencies/FSR-SDK-v2.3.0/Kits/FidelityFX/framegeneration/fsr3/internal/ffx_opticalflow_shaderblobs.cpp:28`)。 - -可将相同数据放到公共生成文件,其余名字只做必要适配;或者在下一次 SDK 升级时评估采用官方生成流程。不要为了删几个 header 大改 SDK。也不能仅凭源文件重复,就声称发布 EXE 膨胀四倍或显存占用四倍,链接器可能合并或去掉重复内容,本轮未做该测量。 - -`include/pix3.h` 的小型 `PIX_COLOR` 适配本身有清楚的编译用途,不属于应随手删掉的伪实现。 - -## 6. 文档和版本追溯需要整理 - -**优先级:P3;这不是某一个模型独有的问题。** - -- 多份 TODO 同时保留已经被后续 fix 取代的方案、未勾选清单和新的完成记录。作为历史资料有价值,作为当前工作清单容易误导。例如 r3 re 的“删除输入缓冲”计划后来被用户确认的 r3 re fix4 缓冲方案取代。 -- 当前交接文档还描述参数“可单独保存”,与 r5 后已经改成自动保存的行为不一致。这部分漂移在后续维护中也没有同步干净。[EXPERIMENTAL_HANDOFF_ZH.md:1](<../../EXPERIMENTAL_HANDOFF_ZH.md#L1>)。 -- r1~r4 没有独立 Git 提交,r5~r7 也累计在同一脏工作树。包中记录 `commit`、`sourceDirty` 和产物哈希,能够验证二进制文件,却不足以唯一还原对应源码。当前构建清单仍如此。[Build-Release.ps1:371](<../../../scripts/Build-Release.ps1#L371>)。 - -建议保留历史 TODO,统一在开头写“已完成 / 被哪份取代 / 尚有哪几项”;建立简短的当前待办入口。每次确认交付后保存完整源码快照或形成可追溯提交,包含新增文件。先处理可追溯性,不批量删除日志、依赖和历史包来追求目录整洁。 - -## 7. 还应记下的实现缺口,暂不直接定为已复现 bug - -### AMD 场景切换信息没有传到共享池 - -FidelityFX OF 会输出场景切换检测结果,而且算法内部确实使用它:检测到切换时返回零光流。因此 SCD 纹理与对应 pass **不是无用计算,不能删除**。[AMD 官方说明](https://gpuopen.com/manuals/fidelityfx_sdk/techniques/optical-flow/)、本地 SDK 实现:216(工作区 `dependencies/FSR-SDK-v2.3.0/Kits/FidelityFX/framegeneration/fsr3/include/gpu/opticalflow/ffx_opticalflow_compute_optical_flow_v5.h:216`)。 - -但当前 Magpie 只把 `scd12` 交给 SDK,没有将其结果传成 guidance 的场景切换信息;densify 读取光流后固定写 0.65 confidence,metadata 的 reset 只来自外部 reset 状态。于是 SDK 内部已经因切镜头归零,消费者仍可能收到“普通有效帧”的状态。[AmdOpticalFlowProvider.cpp:53](<../../../src/Magpie.Core/AmdOpticalFlowProvider.cpp#L53>)、[Densify:429](<../../../src/Magpie.Core/AmdOpticalFlowProvider.cpp#L429>)、[BeginFrame:541](<../../../src/Magpie.Core/AmdOpticalFlowProvider.cpp#L541>)。 - -这属于 r4 接入没有贯通的语义,可能影响切镜头时的时域处理;各消费者 SDK 自身也可能检测切镜头,实际画面影响尚未验证。后续应研究如何按同一 frameId 传递该状态,避免为了读一个标志新增每帧 CPU 阻塞。此项需要单独设计,不混进简单代码清理。 - -### 浮点滑条的临时文本输入仍操作整数刻度 - -Overlay 使用 `SliderInt` 表示离散 tick,却显示格式化后的真实浮点值。未禁用 ImGui 的临时输入,Ctrl+单击后输入实际参数值与 tick 单位可能不一致。r6 的 170ms 重置只是避开临时输入,没有修复这个单位问题。[OverlayDrawer.cpp:1451](<../../../src/Magpie.Core/OverlayDrawer.cpp#L1451>)。 - -这是 r5 fix1 review 已披露的 r3 控件设计边界,仍建议人工确认后选择“按真实值输入并量化”或“关闭临时文本输入”。没有在本轮操作界面复现。 - -此外,输入缓冲中的 `overflowCount` 只有累加和清零,没有读取或输出,是一个可删或接入已有诊断的微小死状态;它不值得单独重构输入系统。[ImGuiImpl.cpp:68](<../../../src/Magpie.Core/ImGuiImpl.cpp#L68>)。 - -## 8. 已修掉或有必要保留的内容 - -| 内容 | 核查结论 | -| --- | --- | -| r3 初版 fix1~fix4 的鼠标状态机、物理键校正、自定义 input/redraw tick | 当时确有补丁叠加问题;当前未发现那套核心符号仍在。不能重复列为待删除任务。 | -| r4 下拉框裁剪的无效 Popup Loaded 补丁 | 最终已删除,真正修复为移除参数列负底边距。见 [r4 fix3 记录:276](<../todos/20260905-v0.6.5-r4-fix3-TODO.md#L276>)。 | -| 保存后闪退的资源查找、整条参数覆盖、旧保存按钮 | r4 快照确实含错误资源查找和清空参数表的路径;r5/fix1 已修。不能把当前自动保存与错误详情回传当作旧冗余删掉。 | -| 残差逐 RGB 通道分类 | 旧问题已在 r5 改为整像素亮度差分类;不是当前待执行。 | -| MFG x2 被整体限制到 Intel、扩大效果器重命名 | 当时有误改或超出所需范围,后续已经修正/撤回。当前只应维持用户确认后的行为。 | -| Zero Depth / Motion / confidence、共享纹理与 fence、尺寸适配 | 深度估算删除后,SDK 合同、降级与跨 D3D 接口仍需要这些资源。不是半删不净的深度估算。 | -| 参数级 Live / Restart、运行副本、输入修订号 | 各后端资源生命周期不同,静态源参数变化还要使后续效果缓存失效,保留有实际用途。应修边界,不压成一个效果级布尔开关。 | -| 静态底图、成功 Present 后确认输入命中区、XeSS 独立 UI 层 | 对应用户确认的暂停/低帧率交互需求。删掉会重现无法及时操作工具栏的问题。 | -| NVOF GPU 计时、发布脚本默认强制退出 Magpie | HEAD 中已经存在,不能归因于 r1~r4 新加。默认退出方式本身值得另行整理;后续部署已使用要求程序关闭的入口和正常退出流程。 | - -## 9. 建议处理顺序 - -1. 修正光流降级状态与 reset 传播,再处理运行参数快照、应用确认和两处参数页同步。 -2. 单独整理 AMD OF 增量构建、重复生成数据和效果器条件显隐范围。保持行为不变,不搭新框架。 -3. 同步当前交接文档与版本快照;历史故障资料保留并标记已被取代。 -4. 场景切换状态接入、临时文本输入和输入缓冲进一步瘦身,分别评估并安排验收,不打包成一次大重写。 - -本轮没有将这些建议转成修改或部署任务。 - -## 审查依据与边界 - -- 当前分支 `0.6.5`,HEAD `84d9f6abb8203a79f9a44d9a66984a293e68856d`,但 HEAD 不能代表完整 r4 源码。 -- 使用 r5 开始前的 tracked-working-tree.patch(工作区 `diagnostics/20260905-r5/baseline/tracked-working-tree.patch`),对照 HEAD 验证每个补丁上下文,还原了 78 个修改过的已跟踪程序/构建文件。只在 `.tools/r1-r4-review/r4-baseline` 写入审查副本,没有对活动源码应用补丁。 -- 该补丁不含当时未跟踪的新文件;AMD OF 新文件和 shader 生成脚本的版本归属结合 r4 接入文档与工程引用判断,不声称有它们完整独立的 r4 Git 快照。 -- 对照了任务“规划0.6.5 r1任务清单”和“为 XeSSFG 接入运动向量设置”的历史记录,以及 r1~r4 TODO、r5 已修复记录和当前实现。历史计划作为范围和演变证据,不作为本轮执行指令。 -- 重点检查深度链清理、残差与参数 UI、输入/呈现、光流提供者/共享池/消费者、构建与文档。采用源码和 SDK 路径审查,没有运行程序、GPU 实测、UI 测试或故障注入;因此区分“代码确定存在的状态问题”与“需要实机验证的画面影响”。 - -审查结束时校验了开始前记录的 642 个程序、脚本及预设文件 SHA-256,未发现内容变化。新增内容仅为本 review 和工作区内的审查副本/校验记录。 diff --git a/docs/experimental/reviews/20260905-v0.6.5-r7-fix-dlssfg-profiler-REVIEW.md b/docs/experimental/reviews/20260905-v0.6.5-r7-fix-dlssfg-profiler-REVIEW.md deleted file mode 100644 index 55a746351..000000000 --- a/docs/experimental/reviews/20260905-v0.6.5-r7-fix-dlssfg-profiler-REVIEW.md +++ /dev/null @@ -1,66 +0,0 @@ -# r7 fix:拖动性能分析器后 DLSSFG 持续慢化 - -日期:2026-09-05。用户报告拖动性能分析器后 DLSSFG Indicator 开始闪烁,画面呈现严重插值变形;XeSSFG 没有同样表现。 - -## 日志结论 - -检查本地 r7 的 `logs/magpie.log`,问题更符合呈现调度持续慢化,而非 NGX 关闭了补帧。日志已保存在工作区 `.tools/r7-fix/runtime-logs/magpie.log`,部署不会丢失这份排查依据。 - -测试配置为 RTX 5070 Ti、DLSSFG 4 倍、FrameRate_Filter 60 FPS。两段运行都出现真实处理帧率逐步降低,生成与真实帧仍持续成功入队: - -| 时间 | 真实处理帧率 | 总入队帧率 | 备注 | -| --- | ---: | ---: | --- | -| 11:29:11 | 60.0 | 240.0 | 第一段稳定阶段 | -| 11:29:25 | 56.8 | 228.2 | 开始下降 | -| 11:29:30 | 16.9 | 67.6 | 持续慢化 | -| 11:29:34 | 11.9 | 46.5 | 没有自行恢复 | -| 11:29:39 | 61.8 | 238.4 | 重新启用后恢复 | -| 11:29:46 | 18.0 | 73.0 | 再次下降 | -| 11:29:55 | 16.0 | 64.9 | 持续低速运行 | - -NGX 的每 120 真实帧诊断仍报告 Evaluate 成功、生成发布成功,正常阶段之后仍有 `generatedPublish=360/0`。光流 GPU 时间约 0.3ms;前端呈现本身耗时也未出现对应量级的增长,环形队列等待却逐步增长到约 12.7ms/输出帧。没有看到这两段 DLSS 会话因为 SDK 错误被停用,也没有看到同期间反复 CaptureInterrupted 重置。 - -日志未记录拖动开始/结束的准确时刻,因此不能仅凭日志认定 Indicator 闪烁的全部原因。能够确定的是持续慢化确实发生,且实现中存在可用纯时序模型复现的正反馈缺陷。真实帧降至十几帧后仍进行 4 倍插值,会放大光流误差;额外重复呈现旧生成帧也可能加重用户观察到的闪烁和不连续。这部分视觉关联是推断,仍需用户复测。 - -## 实现路径与修复 - -### 1. 呈现等待被反过来当作捕获周期 - -旧路径:`_BackendRender` 以相邻后端处理开始时刻估算捕获周期;`_PublishBackendTexture` 可能等待四槽队列释放。这段等待包括前端处理鼠标和 UI 的延迟。下一帧的周期估计把它完整包含进去,生成帧截止时间因此变远,队列等待继续增长。界面停止拖动后,受自身调度限制的后端仍不能提供正常周期样本,因此低速状态可以一直维持。 - -修复:后端单独累积等待呈现槽位的时间,在下一次周期估计时扣除。该计数只由后端访问,不与前端读取后清零的诊断计数混用。有效的帧率上限仍提供最小间隔;原始墙钟间隔仍用于长暂停的历史重置判断。等待值会限制在有效范围内,不能形成负周期。 - -这是一种排除下游等待的节奏估计,不是游戏内部渲染时间测量。它保留原有捕获 API,不把 WGC 的取帧时间误称为游戏渲染时间;Microsoft 对 [SystemRelativeTime](https://learn.microsoft.com/en-us/uwp/api/windows.graphics.capture.direct3d11captureframe.systemrelativetime) 的定义本身也是合成器渲染帧的 QPC 时间。准确的实际显示节奏仍需要实机测量。 - -### 2. 拖动 UI 与内容帧抢同一交换链 - -DLSS 使用普通呈现器,UI 和内容在同一交换链合成。旧的紧急输入路径先 `RenderOverlay`,用上一张稳定画面再 Present 一次,随后才处理 FIFO 内容。密集拖动会反复占用下一次呈现机会,已排队的真实/生成帧继续等待。 - -修复:DLSS 有待呈现内容时,`RenderOverlay` 不再额外 Present 旧画面,输入由下一张 FIFO 内容帧正常绘制并确认。判断使用 pending 原子计数,覆盖已经发布但消息尚未被派发的帧。静态源没有待呈现帧时仍能独立重绘 UI,因此工具栏、光标和对比角标不会必须等待源窗口变化。 - -XeSS 有独立的 UI 覆盖层,且内部生成帧由 SDK 代理呈现,不经过这套 DLSS 四槽软件调度。这解释了为什么两条实现路径对同样拖动操作的敏感性不同;本次不改动 XeSS 的实现。 - -### 3. 输入优先不能阻止 FIFO 收到帧通知 - -将 UI 绘制合入下一内容帧后,必须保证鼠标消息持续到达时,待呈现帧通知也能进入 FIFO。主循环现在在输入消息批次之后,以有界批次派发 DLSS 帧通知;窗口过程仍只记录工作,实际渲染仍在消息派发之外执行,不重新引入阻塞窗口过程的问题。 - -### 4. 保留尚未提交的内容状态 - -仅重绘稳定画面的成功 Present 不再清除 `_frontendBaseNeedsPresent`;读取“生成帧”标记也只在确实提交内容时进行。这样不会把尚待重试的内容帧当成已经提交,或让纯 UI 操作影响内容计数。 - -## 新增诊断 - -原有每秒 `DLSSFG presentation ring` 日志增加: - -- `pacing`:当前每输出帧的目标间隔,单位 ms。 -- `excludedRingWait`:当前周期估计已扣除的下游队列等待,单位 ms。 - -原有 captured、queued、generatedPublish、realPublish 保留。没有增加用户界面提示。光流“实际申请中选较高档位、NVOF 优先”的统一选择规则保持原样。 - -## 验证 - -使用真实旧版与修复后的 `CaptureFrameCadence` 进行相同的纯 C++ 时序模拟:60 FPS 源、4 倍生成、一次 200ms 拖动阻塞。在后续输入稳定的条件下,旧逻辑的输出间隔持续停在约 **14.17ms**,修复后为 **4.17ms**。这验证了持续慢化缺陷及阻断该反馈的效果,不等于已经在 GPU 上重现用户画面。 - -同时检查无帧率上限、真实源改为 30 FPS、变化的下游等待、更严格的限帧值、异常等待值以及长暂停重置。检查程序位于 `.tools/r7-fix/check_pacing.cpp`,使用 C++20 `/W4 /WX`。 - -最终编译和部署记录见 [r7 fix 实施记录](../todos/20260905-v0.6.5-r7-fix-TODO.md)。本轮没有启动 Magpie、没有 computer-use 或 GPU 运行测试。用户复测建议:正常运行后拖动性能分析器 5~10 秒并松开,观察真实帧率、Indicator、画面与新日志;随后重复开关性能分析器和对比,确认不会持续慢化。若帧率恢复但仍有果冻/Indicator 闪烁,需要继续区分 SDK 插值画质与呈现问题。 diff --git a/docs/experimental/reviews/20260905-v0.6.5-r7-motion-sharing-fg-REVIEW.md b/docs/experimental/reviews/20260905-v0.6.5-r7-motion-sharing-fg-REVIEW.md deleted file mode 100644 index 0077c0b71..000000000 --- a/docs/experimental/reviews/20260905-v0.6.5-r7-motion-sharing-fg-REVIEW.md +++ /dev/null @@ -1,109 +0,0 @@ -# 0.6.5 r7:光流共享、补帧呈现与帧率统计 - -调查及实施日期:2026-09-05。基于本地 r6 工作树继续修改;本报告区分源码结论、SDK 契约和仍需实机确认的结果。 - -## 结论 - -NR → FG 使用同一来源、同一档位时,r6 的共享路径本来就只估算一次。同来源不同档位合并为申请中的较高档位也符合用户确认的规则,需要保留。需要调整的是不同来源之间的统一选择;另外,旧版 FSR/XeSS SR 的内置光流不在共享池中。 - -r7 按用户最终确认的规则为整条效果链选择一个提供者:有 NVOF 申请就使用 NVOF,在实际申请的 NVOF 档位中取较高者;没有 NVOF 时,在实际申请的 AMD OF 档位中取较高者;只有内置申请时使用内置光流。不会自动提升到未申请的最高质量档。所有启用外部光流的消费者共享该结果,每个捕获帧只估算一次,尺寸不同时只适配运动向量。关闭光流的消费者保持零向量。现有光流 SR、NR、FG 和诊断消费者均已纳入这一资源路径。 - -补帧呈现部分修复了 XeSS 零向量模式每帧重置历史、资源复用时机,以及 DLSS 没有检查禁止插值输出、缺少稳定呈现间隔和旧队列消息干扰新队列的问题。工具栏与性能页在存在 FG 时显示“总帧率 / 真实帧率”,例如 `120/60 FPS`;这里统计的是呈现 API 接受/SDK 报告的内容帧,并非显示器扫描输出的实测帧率。 - -## 消费者清单 - -| 消费者 | 当前可选来源 | r6 状态 | r7 状态 | -| --- | --- | --- | --- | -| DLSS SR,包括旧 DLSS_OpticalFlow 入口 | 无 / NVOF 四档 | 共享池;合并为 NVOF 申请中的较高档位 | 获取统一选择的结果 | -| DLSSNR | 无 / NVOF 四档 | 同上 | 同上 | -| DLSSFG / MFG | 无 / NVOF 四档 | 同上 | 同上;只对新捕获帧生成 | -| XeSSFG x2 | 无 / AMD OF 两档 / NVOF 四档 | 同厂商合并,跨厂商分别估算 | 获取统一选择的结果,包括优先的 NVOF | -| XeSS MFG x3 / x4 | 无 / AMD OF 两档;仍受 Intel 硬件限制 | 同上 | 同上,不扩展硬件支持范围 | -| Diagnostics 运动/置信度可视化 | 申请 NVOF Balanced | 共享池,可能被其他高档请求提升 | 获取统一选择的结果,可能高于 Balanced | -| FSR2 OpticalFlow | 申请内置 HalfResOpticalFlow | 效果器自行估算 | 接入共享池,可消费优先的 NVOF/AMD 结果 | -| FSR3 / FSR4 OpticalFlow | 申请内置 HalfResOpticalFlow | 效果器自行估算 | 同上 | -| XeSS SR OpticalFlow | 申请内置 HalfResOpticalFlow | 效果器自行估算 | 同上 | -| ZeroMV / Jitter 等不估算光流的变体 | 无外部光流 | 不进行上述估算 | 保持原有算法 | - -表中“可选来源”表示效果器申请配置,实际使用来源按整组规则统一选择。“内置”是 Magpie 的 HalfResOpticalFlow 算法,不等于 AMD OF。没有增加新的参数选项,也不改写用户保存的申请值;实际选用不同结果时通过提示说明。 - -## 提供者 → 共享池 → 消费者 - -1. 初始化时收集整条效果链的请求,保留申请信息用于提示,按 NVOF → AMD OF → 内置的顺序选出一个实际提供者。同一来源只从申请的档位中选较高者。没有任何光流申请时不创建光流估算实例。 -2. 每次获得新的捕获原图,增加捕获帧 ID,在执行任何效果器之前更新选定的提供者一次。 -3. 提供者输出运动向量、置信度及帧 ID、尺寸、有效区域、同步和历史重置元数据。当前深度仍来自零深度提供者。 -4. 同一帧再次访问只读缓存,不推进光流历史。消费者请求先映射到实际选中的配置,再查找尺寸适配缓存;不同申请不会创建重复适配结果。适配运动向量的尺寸与像素单位,并等待提供者同步完成。 -5. 消费者故障降级、尺寸变化和历史重置继续沿用已有机制;初始化失败报告选中提供者的实际错误,不悄悄改成另一种来源。运行中失败沿用零向量降级。 - -具体例子: - -| 效果链请求 | r7 外部估算调用次数/捕获帧 | 提示 | -| --- | --- | --- | -| NR NVOF Balanced → FG NVOF Balanced | 1 | 无混用提示 | -| NR NVOF Performance → FG NVOF Balanced | 1 | 提示统一使用 NVOF Balanced,不提升为 Quality/HighQuality | -| NR NVOF Balanced → FG NVOF Quality | 1 | 提示统一使用 NVOF Quality | -| NR NVOF Performance → XeSSFG AMD Quality | 1 | 提示统一使用 NVOF Performance,跨来源不比较档位数字 | -| 两个内置 OpticalFlow SR | 1 | 无混用提示 | -| 内置 SR → NVOF FG | 1 | 提示统一使用 FG 实际申请的 NVOF 档位 | -| NR NVOF Balanced → FG 无外部光流 | 1 | 无混用提示 | - -混用提示采用“xxx 效果器和 xxx 效果器的光流估计方法或档位不一致,目前统一使用 xxx”的含义,列出具体效果器名称、实际方法和档位,不再提示分别估算或额外资源开销。运行窗口首次显示后通过现有 toast 提示一次,也保留在实时参数页顶部供查看,不进入错误报告。禁用光流的消费者不参与比较。提示反映当前已应用配置;Restart 参数仍需应用后重新启用效果组。 - -旧版 SR 改为从捕获原图统一估算;选中内置来源时保留 HalfRes 算法,存在 NVOF/AMD 请求时消费统一选中的结果。此前其光流输入可能是前置效果的处理结果,因此串联情况下画质可能变化,需要实机对照。简单尺寸缩放可以适配,任意旋转、裁剪、扭曲、插入/删除时间帧等变换不能只靠向量缩放保证语义正确。内置算法未提供置信度估计,适配器使用中性全 1 置信度,不能当成已测量的可信程度。 - -共享外部运动向量不能消除 FG SDK 自己内部的光流或其他运算。“一次”指 Magpie 外部提供者的调用,不是显卡上所有相关计算的总次数。 - -## XeSSFG 呈现审查 - -Intel 文档要求资源标签、常量和 Present ID 一致;UNTIL_NEXT_PRESENT 的资源生命周期必须覆盖对应 Present。resetHistory 会跳过当前插值,frameRenderTime 表示真实帧周期。参见 [Intel XeSS FG 开发指南](https://github.com/intel/xess/blob/main/doc/xess_fg_developer_guide_english.md)。本地核对版本为 `dependencies/XeSS-SDK-3.0.1`。 - -| 发现 | r7 修正 | -| --- | --- | -| 无外部光流时 externalMotionReset 仍默认为 true,导致每帧 resetHistory | 只在启用外部光流时合入该标记;首帧、长暂停等实际重置仍保留 | -| SetFrameGuidance 改动呈现帧 ID,可能破坏 XeLL 标记对应关系 | 捕获帧 ID 与 XeLL/Present ID 分开;后者只随呈现推进 | -| 前端跳过捕获帧时,运动向量引用的前帧不一定等于最后呈现帧 | 检查捕获帧 ID 间断并重置历史 | -| 只在复制后同步,未覆盖随后 SDK 对标签资源的读取 | D3D12 signal 与 D3D11 wait 放到 Present 之后,保护下一次输入复用 | -| frameRenderTime 基准取上次 Present 返回时刻,漏掉其阻塞时间 | 改为相邻 Present 开始时刻的间隔;长暂停重置历史 | -| GetLastPresentStatus 失败时记录了默认的 frameGenResult | 记录实际失败返回值;同步失败同样记录准确 HRESULT | - -继续由 XeSS 代理交换链负责内部生成帧队列和节奏,没有再叠加一套 Magpie 逐生成帧 sleep。XeLL 输入、模拟、提交、呈现标记保留。SDK `framesPresented` 用于新的总帧率计数;读不到有效状态时显示未知,不按倍率推算。 - -## DLSSFG 呈现审查 - -当前实现使用直接 NGX 接口,并非 Streamline 代理交换链。[NVIDIA 直接 NGX 定义](https://github.com/NVIDIA/DLSS/blob/main/include/nvsdk_ngx_defs_dlssg.h)规定 OutputDisableInterpolation 的首字节指示生成结果是否可以显示;[辅助接口](https://github.com/NVIDIA/DLSS/blob/main/include/nvsdk_ngx_helpers_dlssg.h)包含单次/多帧生成调用参数。 - -- 原先约每 120 帧读取一次禁止插值标记,仅用于日志;现在每次 Evaluate 完成后检查,SDK 禁止的结果和重置帧不进入呈现队列。暖机、禁用输出等分支同样等待 GPU 完成,避免复用仍在写入的资源;等待有超时边界。 -- 保留四槽有界队列、生成帧在对应真实帧之前的顺序,以及消费者释放后才复用槽位的机制。旧 generation 消息直接丢弃,不能减少新队列的 pending 数或释放新槽位。 -- 有无 FrameRate_Filter 都根据真实捕获周期给出生成帧间隔;同时遵守有效的真实帧上限。间隔随槽位发布,避免前端读取后端正在改写的时长。 -- 前端按单调时钟截止时间呈现,继续处理输入;长卡顿后重新锚定,避免补交过期时间点而连续突发。长暂停、焦点和捕获中断同时重置 DLSS 历史。 -- 仅参数变更导致的同一捕获帧重绘,不再重复运行 DLSS 生成。 - -这仍是 Magpie 在直接 NGX 输出上的软件节奏控制,不具有 Streamline 硬件呈现调度器的全部能力。[Streamline DLSS-G 指南](https://github.com/NVIDIA-RTX/Streamline/blob/main/docs/ProgrammingGuideDLSS_G.md)也强调实际帧输出不能仅由配置倍率推断。r7 没有迁移到 Streamline,不能据此承诺已实现其全部延迟与 pacing 行为。 - -## 120/60 的统计口径 - -- 有 FG 效果时,左边为一秒统计窗口中已提交的总内容帧,右边为对应的不同捕获真实帧;无 FG 时保持原来的帧率显示。 -- DLSS 按成功的内容 Present 逐帧计数;XeSS 读取 SDK 的 framesPresented。生成被跳过或退回 1 倍时可显示 60/60,而不是固定 120/60。 -- 失败、遮挡返回、不含新内容的 UI/光标/对比重绘不增加内容计数;重复真实捕获帧不重复计数。SDK 计数不可读时左边显示 `—`。 -- 对比开启时效果和 FG 仍继续处理,因此此处仍报告后台处理管线的提交量,不能用它证明原图分支产生了新的运动内容。 -- GPU pass 耗时仍保留其原有统计范围,不把生成帧倍率伪装为效果器执行耗时倍率。 - -[Microsoft DXGI flip model 文档](https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/dxgi-flip-model)说明,Present(0) 排队的帧可能被后续帧替代。因此这套显示不等同于 PresentMon/显示扫描输出测量,也不承诺与 Steam 的测量口径完全相同。准确测量物理显示节奏需要额外的呈现事件/显示时间观测,本次没有加入。 - -## 验证与用户验收 - -已完成纯 C++ 检查:全部 128 种请求子集的统一选择、结果来自实际申请且与合并顺序无关、NVOF 优先、单实例及消费者映射、关闭光流保留零向量;以及未知计数、120/60 和部分跳帧计数、空闲计数、捕获周期估计、限帧及长暂停、卡顿后不突发追赶。构建和部署的最终结果见 [r7 实施记录](../todos/20260905-v0.6.5-r7-TODO.md)。 - -按用户要求没有启动 Magpie、没有使用 computer-use,也没有执行 GPU 运行测试。建议用户重点验收: - -| 场景 | 关注结果 | -| --- | --- | -| NR → FG,同 NVOF 档位 | 启动日志只有一个对应提供者实例,无混用提示,画面与单消费者正常 | -| 同来源不同档位/NVOF 与 AMD 混用 | 日志仅一个实例,档位来自实际申请;NVOF 优先,提示正确列出效果器及实际方法 | -| FSR/XeSS SR 内置光流串联 | 日志内置实例为一个;检查原图估算带来的画质变化 | -| XeSS 无外部光流、AMD、NVOF,2 倍及受支持的 MFG | 插值不再一直重置,FPS 反映实际 SDK 输出 | -| DLSS 2 倍及支持的 MFG,有/无 FrameRate_Filter | 顺序正常、帧间隔均匀、不会因 SDK 禁止的输出而闪烁 | -| 短焦点切换、长暂停、连续尺寸变化 | 无旧队列占用、历史恢复正常,无突发连续播放 | -| 开关性能页、参数、对比;静态和动态窗口 | UI 重绘不虚增真实帧率,原有对比角标时序保持 | - -选定光流与 FG SDK 共存的驱动限制、GPU 故障路径、跨 D3D11/D3D12 同步以及真实扫描节奏仍需实机确认。源码和纯逻辑检查通过不能代替这些验收。 diff --git a/docs/experimental/reviews/20260905-v0.6.5-r9-debug-first-trace-REVIEW.md b/docs/experimental/reviews/20260905-v0.6.5-r9-debug-first-trace-REVIEW.md deleted file mode 100644 index 8d3b47d84..000000000 --- a/docs/experimental/reviews/20260905-v0.6.5-r9-debug-first-trace-REVIEW.md +++ /dev/null @@ -1,88 +0,0 @@ -# 0.6.5 r9 debug:首份帧时间记录 review - -日期:2026-09-05。范围:读取用户本机诊断记录、对齐现有实现;本次分析没有改变程序行为或部署文件。 - -## 结论 - -数据足以确认两类不同的时间突增:两次捕获输入间隔约 60 ms、53 ms,以及一次 DLSSNR 调用约 21 ms。两次较长的输入间隔发生在性能分析器开启之前。**目前可以缩小调查范围,但还不能认定已经找到 NVIDIA overlay 1% Low 降至 5–10、且有明显停顿的完整根因。** - -这里的“捕获输入”指 Magpie 最终取出并接受的 WGC 帧。它不是源程序自身的 Present 记录,也不是所有 FrameArrived 回调记录。不能据此断言卡顿由 Endfield 自身造成,也不能排除 Magpie 的 GPU 工作、Windows 捕获调度和最终呈现的影响。 - -## 数据与可靠性 - -- 原始文件:`trace-20260905-153737-554-32980-1.csv` 和同目录上级的 `magpie.log`。 -- 诊断目录:`release/v0.6.5-r9-debug-local/Magpie-v0.6.5-r9-debug-local-x64/logs/`。 -- 调查快照:工作区 `.tools/r9-debug-investigation/first-run/`,保留原件、自动分析报告、`inspection.json` 和 `phase-inspection.json`。 -- Magpie PID 32980;会话约 15:37:37.555–15:38:21.681,共 44.126 秒。CSV 导出完成于 15:38:21.787。 -- 前端 158856 条、后端 55095 条近期事件;两线程近期环和长事件环均无覆盖,完成尾标和记录数量校验通过。 -- RTX 5070 Ti;Graphics Capture;2560×1440;DLSSNR 输入比例 75%,实际 1920×1080;NVOF 4M;本次组内没有 FG。 -- `Is3DGameMode=false`,动态重复帧检测开启。本次检查 450 次,未判定出像素重复;没有无效内容范围、时间戳拒绝或运行中捕获重启。 -- 性能分析器在 15:38:04.111 开启、15:38:10.984 关闭;并非整次都关闭。15:37:57.307–15:37:58.862 开启过原图对比,效果持续处理。 -- 此日志未记录源程序可执行文件和 PID。此前用户说明涉及 Endfield 和视频,不能从当前文件独立确认本次源程序身份。 - -## 已定位的时间突增 - -下表时间由 CSV 的 QPC/UTC 对照换算到本地时间,普通日志只有毫秒精度。 - -| 时间与帧 | 记录 | 可以得出的结论 | -| --- | --- | --- | -| 15:37:50.040,帧 837 | `NativeEffect` 21.028 ms;后端整帧 26.645 ms;对应内容提交间隔 32.262 ms | 此处有一次本机效果调用路径的 CPU 墙钟时间突增。普通日志中帧 721–840 窗口的 `evaluateCPU max=20.791 ms` 与之对应。 | -| 15:37:52.004–52.067,帧 1008→1009 | 被取出的 WGC 帧间隔 60.175 ms;原始时间戳差 59.999 ms;内容提交间隔 62.967 ms | 间隔在效果处理入口之前已经存在。中间后端等待约 52.111 ms,下一次效果处理没有同步增长到 60 ms。 | -| 15:37:56.290–56.344,帧 1373→1374 | WGC 取帧间隔 52.886 ms;原始时间戳差 53.342 ms;内容提交间隔 53.292 ms | 同样表现为捕获输入间隔增大。期间发生一次仅叠加层提交,因此只看全部 Present 的长间隔会漏掉这次内容停顿。 | - -DLSSNR 的 21 ms 是调用墙钟耗时,可能包含 SDK/驱动内部等待或线程被抢占,不能直接解释为算法多计算了 21 ms。该 120 帧窗口的 NR GPU 时间最大值仅 4.876 ms,但它是窗口统计,不能替代该帧完整 GPU/调度时间线。 - -另外还有 25–30 ms 级别的内容间隔。本报告重点列出较长输入间隔和唯一超过 10 ms 的效果调用;没有把其余间隔当作零。 - -## 启动与末尾单独处理 - -第一次内容呈现附近有 69.135 ms 内容间隔,伴随前端初始化和窗口响应探测,单独作为启动问题保留,不混入稳定运行阶段判断。 - -最后一张 WGC 帧约在 **15:38:19.339**,之后约 2.23 秒直到停止流程没有新的捕获帧。最长 1008.017 ms 的 Present 间隔,以及 339、210、129 ms 间隔都在这段内,沿用最后的帧号 3385;前端仍在执行检查,后端主要等待消息/新帧。 - -普通日志依次记录:15:38:21.569 源窗口状态改变,21.576 停止效果,22.779 源窗口已销毁。恢复窗口圆角失败发生在析构期间,不是此前运行中卡顿的证据。 - -**末尾操作仍待用户确认。** 如果当时关闭了源程序,这段应归为结束阶段;如果当时仍有连续运动画面,它就是需要优先调查的捕获停顿。不能仅凭“后来窗口销毁”把这段判成正常,也不能直接把它算作运行中 1 秒卡死。 - -## 哪些环节本次没有出现严重阻塞 - -这些是全会话 CPU 墙钟统计,嵌套项互相包含,不能相加。GPU 命令提交很短不表示 GPU 实际工作很短。 - -| 环节 | 平均 ms | 最大 ms | -| --- | ---: | ---: | -| 后端效果处理及发布整体 | 8.057 | 26.645 | -| 后端发布 fence 等待 | 7.269 | 11.479 | -| NVOF 提交 | 0.170 | 2.253 | -| 重复帧读回等待 | 0.134 | 0.562 | -| WGC 取帧调用 | 0.090 | 0.637 | -| 前端取共享纹理 | 0.061 | 0.305 | -| Present 调用 | 0.244 | 5.320 | - -8157 次 Present 均返回 S_OK;没有记录到前端共享槽获取失败。3 次源窗口同步响应探测均在启动附近,最大 7.767 ms。异步命中测试最大 106.272 ms,包含线程池和回调排队时间,**不是前端同步阻塞 106 ms**;并发请求仍值得后续收敛,但本次不足以认定其造成两个捕获长间隔。 - -性能分析器开启约 6.87 秒内,内容提交间隔最大 18.476 ms,后端发布 fence 最大 9.905 ms,停止时 `skippedSamples=0 pending=0`。本次没有复现分析器开启后大幅卡住。不同阶段的场景和用户操作不同,不能把这一对照当成“开启分析器反而更快”的结论。 - -## 确认存在的额外工作与度量偏差 - -全会话共有 3380 次内容提交、4777 次仅叠加层提交,后者占全部提交 **58.6%**。这些提交可能由鼠标、工具栏、对比角标动画或性能界面引起;不能全部称为无效提交。 - -关闭对比后、打开性能分析器之前的约 5.25 秒内,有 455 次内容提交、1710 次仅叠加层提交;总提交约 413 次/秒,内容约 87 次/秒。前端运行期间也约每 2 ms 检查一次状态。 - -这确认有较高频率的额外前端工作,也确认“全部 Present 的间隔”不能准确代表新画面的节奏。当前诊断未测这些提交的完整 GPU 和桌面合成成本,尚不能宣称去掉它们就能修好 1% Low。后续优化应保留输入响应和动画时长,检查合并/节流的实际收益。 - -本次全部 Present 间隔换算出的 Low1 约 24.50 FPS,包含上述末尾间隔;内容提交间隔换算值约 37.95 FPS,且不能表达最后一帧之后一直无新内容的尾段。**这两个数均不能拿来否定用户看到的 5–10 FPS。** 它们测量的层级、样本和窗口与 NVIDIA overlay 不同。 - -## 下一步应补齐的证据 - -1. **捕获通知与取帧之间的时间。** 目前 `WgcFrame` 在最多取 4 张、保留最新张之后记录,未记录 `FrameArrived` 时刻、取出张数或被跳过帧时间戳。增加这些事件,并记录 `StepTimer` 等待结果,可区分没有新帧、消息唤醒延迟和池内积压;保持内存记录,避免每帧写日志。 -2. **源程序与 Magpie 的实际呈现。** 同时采集两个进程的 PresentMon/ETW 时间线,以 QPC 对齐现有 CSV,检查 GPU 等待、丢弃帧、`PresentMode` 和实际 `DisplayedTime`。官方工具支持同时指定多个进程、输出 QPC 和显示时长;不同呈现路径下字段可用性需看实际数据。[PresentMon 官方说明](https://github.com/GameTechDev/PresentMon/blob/main/README-ConsoleApplication.md) -3. **复查 GPU 优先级影响。** 当前 `Renderer::SetGpuPriority()` 请求 REALTIME GPU 调度类。它是待验证候选:Magpie 自身 GPU 等待平稳,不能排除其工作影响源程序/合成器的调度。需要同一场景、同一效果参数下的受控对照,不能据本次日志直接降级或删除。 -4. **继续保留 DLSSNR 21 ms 调用证据。** 如再次出现,应把 SDK 调用前后单独记录并用线程调度事件区分计算、内部等待和抢占;单次尖峰不足以证明常态性能故障。 - -本次交付为分析结论和记录补全方向。没有以未证实的原因修改渲染、WGC 等待、GPU 优先级或叠加层呈现逻辑。 - -## 复算方式 - -自动分析使用仓库 `scripts/Analyze-FrameTrace.py`;两份本次专项脚本在工作区 `.tools/r9-debug-investigation/inspect_first.py`、`inspect_phases.py`。只处理快照,不更改原始日志。 - -阶段分界取普通日志时间:对比开启约会话 19.752 s、关闭 21.308 s,分析器开启 26.556 s、关闭 33.429 s;统计时不跨阶段连接间隔,因此阶段计数和全会话间隔数不要求逐项完全相加。 diff --git a/docs/experimental/reviews/20260905-v0.6.5-r9-dlssnr-switching-stutter-REVIEW.md b/docs/experimental/reviews/20260905-v0.6.5-r9-dlssnr-switching-stutter-REVIEW.md deleted file mode 100644 index 93e52b10b..000000000 --- a/docs/experimental/reviews/20260905-v0.6.5-r9-dlssnr-switching-stutter-REVIEW.md +++ /dev/null @@ -1,200 +0,0 @@ -# 0.6.5 r9:DLSSNR 切屏异常与突发卡顿调查 - -日期:2026-09-05。本报告保留实施前的调查结论;A–D 已按用户授权落实,执行与交付结果见 [r9 实施记录](../todos/20260905-v0.6.5-r9-ABCD.md)。 - -## 调查结论 - -**可以明确定位四处值得修的代码问题,但目前不能把日志 18 的异常画面归结为某一个已经证实的根因。** “必须停用、重新启用效果组才能恢复”说明问题能跨后续帧持续存在,值得优先处理捕获输入的有效性和中断恢复。它既可能涉及捕获会话,也可能涉及时序历史;仅凭这个恢复方式无法区分两者。 - -卡顿也应分开处理:日志中既有高分辨率下持续较高的处理成本,也有捕获/呈现管线短时停顿。带鱼屏日志确实记录了真实帧处理速率从约 31 FPS 降到 7 FPS,但相邻统计窗口内 DLSSNR 的 GPU 耗时仍约 15 ms,不能把这次下降直接归因于 NR 推理突然变慢。 - -建议 r9 首批实施: - -| 项目 | 代码结论 | 与用户反馈的关系 | 建议 | -| --- | --- | --- | --- | -| WGC 未校验每帧有效内容范围 | 已确认缺少必要检查 | 能解释过渡帧进入 NR 的风险;日志未记录尺寸,尚未证实本次触发 | 优先修复 | -| WGC 重启失败结果被忽略 | 已确认失败传播缺陷 | 可能一直等不到新帧;日志 18 未出现对应失败信息 | 与捕获恢复一起修复 | -| 已知捕获中断没有同步重置帧间隔估计 | 已确认时序状态衔接缺口 | 带鱼屏掉帧附近有中断、间隔放大和队列等待,相关性较强;起因仍未完全确定 | 优先修复 DLSSFG 中断后的节奏恢复 | -| 性能监视器查询无限等待且不区分错误 | 已确认健壮性缺陷 | 能造成后端或界面等待,但日志没有证明本次卡顿由它触发 | 修复查询生命周期与错误处理 | - -不建议这一轮直接改 NR 模型、残差算法、默认光流档位,或删除 GPU 同步来追求帧率。先补齐下文所列的阶段耗时和捕获状态记录,再决定需要优化哪一个瓶颈。 - -## 1. 输入与证据边界 - -用户确认:提到的 21、22 是编号笔误,对应实际附件中的 17 和 `magpie.1 (8)`。日志 18 的异常必须停用、重新启用效果组才能恢复,未提供准确发生时间。 - -| 日志 | 内容 | 可用于本次判断的范围 | -| --- | --- | --- | -| 18(本地 `%USERPROFILE%/Downloads/magpie (18`).log>) | 5 次效果组运行;2560×1440;仅 DLSSNR,80% 输入分辨率,NVOF 4M | 切屏异常的主要日志 | -| 19(本地 `%USERPROFILE%/Downloads/magpie (19`).log>) | 只有启动、发现已有实例运行后退出,共 5 行 | 没有渲染或性能证据 | -| 17(本地 `%USERPROFILE%/Downloads/magpie (17`).log>) | 7 次运行;6144×3456;NR 单独或搭配 DLSSFG;多次修改输入分辨率和光流档位 | 高分辨率负载与 FG 管线 | -| 20(本地 `%USERPROFILE%/Downloads/magpie (20`).log>) | 6 次运行;3840×2160;FrameRate_Filter + NR,无 FG;最小/最大帧率均为 40 | 无 FG 时的吞吐波动 | -| magpie.1 (8)(本地 `%USERPROFILE%/Downloads/magpie.1 (8`).log>) | 5120×1440;先 NR + DLSSFG 4×,随后 NR 单独运行 | 最明确的短时掉帧时间线 | - -这些日志均标记为 `0.6.5-r7-local`。当前代码是 r8 工作树,包含此前的 r7 fix。日志已经出现 `pacing`、`excludedRingWait`、`deadline+DXGI` 等修复后的诊断字段,因此不能把旧版“拖动性能监视器导致 DLSSFG 呈现节奏反馈”的问题原样再修一次。 - -本轮是日志、源码和官方接口文档调查,没有运行程序或进行 GPU/界面复现。日志中的 FPS、耗时是不同阶段、不同窗口的统计,不是可以任意相加或互换的整机帧时间。 - -## 2. 问题 1:切屏后 DLSSNR 异常画面 - -### 日志能确认什么 - -日志 18 的 5 次运行均成功创建 NR,没有 NR 调用失败、禁用或光流提供者失败记录。所有已记录的 NR 状态中,`frameId` 与 `evaluateCount` 相等,`failures=0`、`disabled=false`。GPU 统计窗口均值为 8.561–8.754 ms,记录到的最大值为 9.078 ms。 - -其中第二、三次运行已经有如下记录: - -| 原始日志行号 | 时间 | 事件 | -| --- | --- | --- | -| 421、422 | 12:29:22.878、22.944 | 光流共享服务收到 `CaptureInterrupted` 历史重置 | -| 423 | 12:29:23.416 | `duplicate capture reused: frameId=72 reuseCount=1` | -| 427 | 12:29:25.045 | 再次收到 `CaptureInterrupted` | -| 547–549 | 12:29:34.404–34.481 | 连续的中断历史重置记录 | -| 556 | 12:29:36.729 | 再次重置 | - -因此,“完全没有切屏重置”和“每次重绘都重新推理同一 frameId”都不符合目前证据。 - -### 重复输入与历史路径复核 - -[DLSSNRFilter.cpp:2116](<../../../src/Magpie.Core/DLSSNRFilter.cpp#L2116>) 在捕获帧编号、参数版本、输入版本均未变化时复用结果,位于输入准备和 NGX 推理之前。强制最低帧率产生的普通重绘不会因此自动再次推理。 - -检查捕获输出、效果链输入/输出以及 NR 的共享输入/输出纹理,没有找到正常执行路径将 NR 的输出直接回灌成自身下一次输入的别名关系。光流历史重置也存在传到 NR `DLSSNR.Reset` 参数的路径,见 [DLSSNRFilter.cpp:2236](<../../../src/Magpie.Core/DLSSNRFilter.cpp#L2236>)。 - -但 `frameId` 是程序分配的编号,不是图像内容的证明。WGC 新交付的帧可能像素不变;动态重复检测也不是每帧都进行精确比较。因此,计数相等只能反对“同一个已识别帧被额外反复推理”,**不能排除内容相同的不同编号帧、无效捕获区域或 SDK 内部历史异常**。 - -### 优先修复 A:拒绝无效的捕获区域 - -[GraphicsCaptureFrameSource.cpp:82](<../../../src/Magpie.Core/GraphicsCaptureFrameSource.cpp#L82>) 从帧池取最新帧后,直接按固定 `_frameBox` 执行 `CopySubresourceRegion`,没有读取 `ContentSize`,也没有用实际源纹理尺寸检查复制范围。 - -微软明确说明:帧池表面尺寸保持为创建时尺寸,实际内容小于它时,剩余区域的数据未定义。程序需要以每帧有效内容范围约束复制。[Microsoft:Screen capture](https://learn.microsoft.com/en-us/windows/apps/develop/media-authoring-processing/screen-capture) - -建议实现: - -1. 复制前同时验证内容范围、纹理范围和裁剪框。只有裁剪框完整落在有效区域内,才提交新的捕获帧。 -2. 无效过渡帧保留上一次有效画面,不推进捕获编号、光流历史和 NR/FG 推理。 -3. 有效尺寸持续变化时,交给现有尺寸变化/效果链重建路径协调处理,避免永久丢弃后不恢复。 -4. 恢复后的首个有效帧携带明确的捕获连续性变化,重置需要历史的消费者。 - -**实现限制:不能简单要求 `ContentSize` 等于帧池尺寸。** 当前帧池有意只覆盖裁剪所需区域,内容比池更大可以合法;应该判断裁剪区域是否完整有效。 - -这是一处独立成立的代码问题,但日志没有记录每帧尺寸,因此不能宣称已证明它触发了用户这次异常。 - -### 优先修复 B:捕获重启失败不能变成永久等待 - -[GraphicsCaptureFrameSource.cpp:121](<../../../src/Magpie.Core/GraphicsCaptureFrameSource.cpp#L121>) 在光标恢复可见时停止、重新启动捕获,但忽略 `_StartCapture()` 的返回值。启动失败后可能留下空会话或部分初始化状态;`_Update()` 将空会话当作普通 `Waiting`,缺少明确恢复或失败出口。 - -建议把重启作为完整状态转换:失败时清理半成品,记录实际错误,通过有界重试或现有捕获失败通道结束等待。正常暂时无帧与“捕获已经启动失败”必须区分。取帧和关闭 WinRT 对象时的异常也应进入同一处理路径。 - -日志 18 未记录 `_StartCapture()` 的失败信息,故这一项是已确认的潜在冻结缺陷,不是本次异常的已证实原因。 - -### 仍需补证据的部分 - -后续诊断应记录捕获会话代次、重启原因/结果、首个有效帧、实际内容尺寸、裁剪框、捕获时间戳,以及 NR 实际收到的重置标记。若尺寸和输入均有效,重置也确实送达而异常仍持续,再考虑 NR feature 的恢复性重建。现在直接在每次失焦时销毁、重建 NR 会引入加载开销,也可能掩盖真正问题。 - -## 3. 问题 2:突发卡顿与高分辨率 - -### 最清楚的一次停顿 - -`magpie.1 (8).log` 第一段使用 5120×1440、NR 100% 输入、DLSSFG 4×。以下数值直接来自日志: - -| 日志行号 | 时间 | 捕获真实帧 FPS | 入队 FPS(含补帧) | 其他证据 | -| --- | --- | ---: | ---: | --- | -| 633 | 13:00:43.246 | 30.8 | 123.4 | 呈现间隔 8.115 ms | -| 636 | 13:00:44.554 | — | — | `CaptureInterrupted` | -| 641、643 | 13:00:46.265–46.287 | 9.8 | 36.4 | 再次中断;间隔 23.210 ms;排除的队列等待 154.900 ms | -| 645 | 13:00:47.291 | 16.9 | 62.7 | 间隔 10.330 ms;排除的队列等待 43.488 ms | -| 646、649 | 13:00:48.218–48.295 | 7.0 | 20.9 | 再次中断 | -| 652 | 13:00:49.216 | — | — | NR 最近窗口 GPU 均值 14.749 ms,最大 15.773 ms,无调用失败 | -| 659 | 13:00:51.299 | 30.0 | 117.8 | 恢复接近原吞吐 | -| 675 | 13:00:56.355 | 30.9 | 123.4 | 已恢复 | - -这里的 `queued` 是发布到呈现队列的速率,不是显示器实际扫描出的帧率。`excludedRingWait` 是估计器最近一次接收的下游等待累计量,不能当作整秒平均或最大等待;7 FPS 那条记录里的较小数值不能否定同一统计时段存在等待。 - -这一段的直接证据指向捕获/呈现连续性问题。NR 的 GPU 窗口没有出现能够单独解释 31→7 FPS 的对应变慢。不过,统计没有覆盖全部光流、捕获读回、跨设备复制和呈现等待,仍不能用它排除其他 GPU 或调度压力。 - -### 优先修复 C:捕获恢复与帧间隔估计一起恢复 - -[Renderer.cpp:382](<../../../src/Magpie.Core/Renderer.cpp#L382>) 中的光标变化、源窗口焦点变化会重置光流/FG 历史,却没有同步处理 `_captureCadence` 与 `_captureCadenceQueueWait`。 - -[FramePresentationTiming.h:12](<../../../src/Magpie.Core/FramePresentationTiming.h#L12>) 只在观察间隔达到 500 ms 时自动视为中断。短于 500 ms 的捕获重启停顿仍可能混进平滑后的源帧间隔,再传给补帧呈现节奏。r7 fix 已排除呈现槽位等待,但尚未完整表达“这两帧不属于连续捕获序列”。 - -例如,之前每帧约 33 ms,一次扣除队列等待后仍达 335 ms 的中断被按当前 0.2 权重纳入估计,会把估计值拉到约 93 ms,4× 时约为 23 ms。**这是说明机制的算例,不是对日志某两帧的精确重建。** - -建议在确实发生捕获重启、有效内容失效或时间戳不连续时结束旧序列,恢复首帧重新建立基线,同时正确结清旧队列等待。WGC 可提供 QPC 域的 `SystemRelativeTime`,适合用于区分采集时间与后端被唤醒/处理时间。[Microsoft:捕获帧属性](https://learn.microsoft.com/en-us/windows/apps/develop/media-authoring-processing/screen-capture) - -不要把普通鼠标移动或所有焦点消息无差别当作一次重启。恢复初期还要保留合理的帧率限制/回退间隔,防止估计清零后一次性突发呈现。实际停顿的源头仍要靠阶段耗时确认,但中断样本不应污染持续帧率估计,这一点可以直接修。 - -### 高分辨率与无 FG 的日志 - -| 场景 | 实际观察 | 解读 | -| --- | --- | --- | -| 日志 17:6144×3456,NR 100% + FG 2× | NR GPU 窗口均值约 17.5–17.7 ms;真实帧约 17–25.5 FPS | NR 之外还要支付捕获、光流、FG 和复制成本,不能只用 NR 耗时倒数预测最终 FPS | -| 日志 17:NR 40% + FG 3× | NR GPU 均值约 4.0–6.5 ms;真实帧约 27.6–38.5 FPS | 输入分辨率显著影响 NR 成本,但其余阶段仍处理大尺寸资源 | -| 日志 17:NR 25%,FG 4×,NVOF 4M | 后两段 NR GPU 均值约 2.6–3.1 ms;真实帧约 32–37 FPS | NR 推理已经较轻,仍存在别的吞吐限制 | -| 日志 20:4K,NR 80%,无 FG,帧率设为 40 | 不同配置窗口 NR GPU 均值约 9.7–14.6 ms;部分区间每 120 个 NR 帧编号耗时超过 3 秒 | 无 FG 场景也有处理速率下降,不能统一归因于 DLSSFG 呈现问题 | -| 日志 20:NR 100% | NR GPU 均值约 15.2–18.3 ms,最大 22.852 ms | 存在较高基础成本,但不足以定位突发卡顿的完整原因 | -| 带鱼屏日志:NR 单独运行 | NR GPU 窗口均值 13.683–13.804 ms,最大 14.025 ms | 在这段运行中 GPU 推理较稳定 | - -日志 17 的各段不是同一内容下的受控对照,不能据此给出精确性能提升百分比或判定某档位实现错误。 - -日志 20 在 12:48:08.165 附近,连续 120 个 NR 帧编号跨越约 5.225 秒,约合 23 个/秒;这是区间平均的 NR 新帧处理速率,**不是显示 FPS**。最低帧率触发的旧画面重绘和 NR 重复帧复用不会等量增加推理计数。 - -6144×3456 约有 2123 万像素,是 4K 的 2.56 倍;5120×1440 约 737 万像素,反而少于 4K 的 829 万。不能仅凭“带鱼屏”就认定是总像素过多或宽度兼容缺陷。 - -### 优先修复 D:性能监视器不能无限等待 GPU 查询 - -[EffectsProfiler.cpp:81](<../../../src/Magpie.Core/EffectsProfiler.cpp#L81>) 对所有非 `S_OK` 结果都执行 `Sleep(0)` 后继续查询,没有超时、取消或错误分支。创建查询时也没有检查返回值。查询每个 pass 时持有 `_timingsLock`,界面读取统计需要同一把锁。 - -`S_FALSE` 表示暂未完成,其他失败结果需要独立处理。不能把失败一律当作“再等一会儿”。[Microsoft:ID3D11DeviceContext::GetData](https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-getdata) - -建议使用有界的查询槽位,跨帧收集已完成结果;未完成就稍后读取,失败时关闭本次性能采集并记录原因,不阻塞正常效果处理。检查创建失败、无效频率和时间戳;在锁外收集完整数据,短时间持锁发布结果。槽位全占用时跳过采样,不能重用尚未完成的查询。 - -可以使用 `DONOTFLUSH` 避免查询本身频繁触发提交,但必须同时去掉原地等待,并保证渲染正常提交命令;只替换标志、保留循环可能一直等不到结果。[Microsoft:D3D11_ASYNC_GETDATA_FLAG](https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_async_getdata_flag) - -**关联程度必须保守:** 当前 [Renderer.cpp:2450](<../../../src/Magpie.Core/Renderer.cpp#L2450>) 在发布纹理之后才读取性能查询,而正常发布路径已经等待前序 D3D11 fence。因此多数正常帧中查询应已完成,不能把上述循环描述为“每帧额外等待整个 NR GPU 工作”。本次日志也没有监视器开关状态及查询等待时间,尚不能证明它引发了这次下降。 - -## 4. 有开销依据、但需要测量后再改的路径 - -### 精确重复帧检测的同步读回 - -[FrameSourceBase.cpp:310](<../../../src/Magpie.Core/FrameSourceBase.cpp#L310>) 执行全图比较后,把结果复制到 staging 缓冲并立即 `Map(READ, flags=0)`。这形成 GPU→CPU 同步点,成本不仅是读取几个字节,还包括等待比较及其前序工作。非阻塞读取需要显式处理“GPU 尚忙”的返回值。[Microsoft:ID3D11DeviceContext::Map](https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-map) - -启用 FG 时现有代码强制精确重复检测,避免相同画面重复生成补帧。高分辨率使全图比较的工作量增大,但日志没有记录此处等待时间。 - -先增加比较提交与读回等待统计。若确实是瓶颈,再考虑异步槽位;必须同时保存与比较结果匹配的颜色帧、编号和资源生命周期。不能直接关闭检测,也不能在 `Map` 尚未完成时无条件把输入当作新帧,否则会重新引入时序消费者的重复输入问题。 - -### 跨设备发布与呈现等待 - -[Renderer.cpp:2453](<../../../src/Magpie.Core/Renderer.cpp#L2453>) 在发布时有槽位等待、共享纹理复制和 fence 完成等待。大尺寸下这些成本值得测量。目前有界的槽位数量和同步用于防止消费者尚未用完、生产者就覆盖资源,不能直接删掉。 - -其中 `_fenceEvent.wait()` 是无界等待,可进一步增加设备丢失/停止时的退出处理;现有日志未记录设备丢失,故不把它列为本次短暂掉帧的已定位根因。 - -## 5. 建议的 r9 执行范围 - -第一批修改建议包含 A–D 四项,加上足以判断修复效果的低频诊断: - -| 记录内容 | 要解决的证据缺口 | -| --- | --- | -| 捕获会话代次、重启原因/结果、内容尺寸、拒绝帧计数、首个有效帧时间 | 区分输入无效、捕获启动失败和正常无新画面 | -| 捕获时间戳、进入后端时间、中断原因、历史重置实际送达帧号 | 区分源帧停顿、线程调度延迟和历史状态问题 | -| 重复检测读回、发布复制/fence、槽位等待、性能查询耗时 | 补齐 NR GPU 耗时以外的停顿来源 | -| 性能监视器开关、真实帧处理/发布/实际呈现计数 | 判断卡顿是否跟随监视器和呈现阶段 | - -按现有窗口汇总,并仅在状态转换、超时或异常尖峰时追加事件;不要逐帧刷大量日志。普通焦点切换不需要弹提示,只有确认捕获无法恢复时进入现有错误处理。 - -以下内容暂缓:NR feature 每次切屏强制重建、替换残差算法、自动降低光流质量、移除精确重复检测、取消共享资源同步。这些修改都还缺少针对性证据,且有明显的画质、时序或兼容性代价。 - -## 6. 后续验收关注点(由用户操作) - -实现后优先覆盖:NR 80% 与 100%;单独 NR 与 NR + FG;Alt+Tab、多显示器点击后切回;性能监视器关闭、打开及拖动。重点检查切回能否自行恢复、首个有效帧是否正确重置历史,以及中断后帧间隔是否尽快恢复。 - -高分辨率对照应保持同一内容、同一限帧和光流配置,分别记录 4K、6144×3456、5120×1440 的阶段耗时。不要用不同内容下的几段 FPS 判断修改收益。 - -查询错误和捕获重启失败适合用可控故障检查验证:应该有限期退出等待、保留正常处理或给出准确错误,不能卡住界面。它们与真实 GPU 性能测试是两个验收目标。 - -## 7. 本轮交付与保留记录 - -只生成本调查文档和本地分析材料,未修改程序、编译或覆盖部署。 - -日志副本、SHA-256 清单、解析结果和汇总在 .tools/r9-investigation(工作区 `.tools/r9-investigation`)。复制的日志保持原始内容,便于后续按行号复核。分析以结构化 `summary.json` 为准。 - -已核对 r8 最终清单中的 632 个 `src`、`scripts`、`presets` 文件,内容均未变化。已有 r8 修改及部署保持原状。 diff --git a/docs/experimental/reviews/20260905-v0.6.5-r9-low1-performance-REVIEW.md b/docs/experimental/reviews/20260905-v0.6.5-r9-low1-performance-REVIEW.md deleted file mode 100644 index bc94731d4..000000000 --- a/docs/experimental/reviews/20260905-v0.6.5-r9-low1-performance-REVIEW.md +++ /dev/null @@ -1,128 +0,0 @@ -# 0.6.5 r9:1% Low 降至 5–10 FPS 的日志与实现复查 - -日期:2026-09-05。状态:调查完成;本轮未修改程序、编译或覆盖部署,未运行 GUI/GPU 测试。 - -**存在可以精简的固定开销,也存在不必要的恢复处理;但现有日志尚未定位造成 5–10 FPS 级别 1% Low 的具体阻塞点,不能把这些发现直接当作已经证实的主因。** 下一步应同时精简已确认的多余工作,并补齐捕获到呈现的逐帧时间线。 - -用户已确认:统计来自 NVIDIA overlay;Endfield 持续运动、不切屏时也发生,而且能明显感觉到停顿;看视频也可能出现。因此按实际卡顿处理,调查优先级放在共用捕获、调度和呈现路径,不以“叠加层数字不准”结案。 - -## 1. 本次证据范围 - -读取本机 `release/v0.6.5-r9-local/Magpie-v0.6.5-r9-local-x64/logs` 的两份日志,快照范围为 **14:36:50.808–14:51:05.960**。包括四轮完整启动及一轮开头已轮转掉的运行片段。更早的 `source/bin` 日志单独保留,没有混入下面的 r9 统计。 - -本轮完整配置均为 RTX 5070 Ti、2560×1440 捕获/输出、单个 DLSSNR、NVOF、动态重复帧检测;未启用 FG,最低/最高帧率限制均为 0,3D 游戏模式关闭。以下日志不能代表所有用户或未记录的视频测试。 - -| NR 输入比例 | NVOF 档位 | NR 推理 GPU:各 120 帧窗口平均值范围 | 发布后 fence 等待:窗口平均值的中位数 | -| --- | --- | --- | --- | -| 50% | 2M | 2.822–2.827 ms | 11.500 ms | -| 50% | 4M | 2.824–2.879 ms | 5.331 ms | -| 75% | 4M | 4.514–4.620 ms | 6.854 ms | -| 100% | 4M | 7.025–7.163 ms | 9.571 ms | - -这里的 fence 等待包含此前排队的 GPU 工作,**不能与 NR 推理时间相加,也不能当作单独的纹理复制耗时**。这些是不同时间运行的观察值,不是受控性能对照实验。降低 NR 输入比例确实减少了其推理负载;光流输入及最终呈现仍保持 2560×1440。 - -其他统计: - -- 516 个发布统计窗口全部为 `profiling=false`:本轮没有启用 `EffectsProfiler`,r9 对它的异步查询改动不是正在运行的主要路径。**这不代表所有内部性能采集都关闭了。** -- 发布纹理事务的已记录最大耗时为 **0.729 ms**;fence 等待的已记录最大值为 **30.168 ms**,后者位于启动早期。 -- 82 个重复帧读回统计窗口,已记录单次最大值为 **22.285 ms**;第二大窗口最大值为 1.817 ms。存在一次尖峰,但没有记录到足以直接解释上述严重卡顿的持续长等待。 -- 无 `invalid content bounds`,无 DLSSNR 运行失败/禁用记录。两次拒绝帧都是**时间戳恰好相等**,尺寸和裁剪范围正常。 - -原始快照与机器可读统计保存在工作区 `.tools/r9-low1-investigation/`:`logs/`、`log-manifest.json`、`records.json`、`summary.json`、`evidence.json`。其中 `bin-*` 为早期日志,`r9-*` 才用于本报告统计。 - -## 2. 可以明确改进的实现 - -### A. 对比未启用,仍持续准备整套原图纹理 - -定位:[PassThroughFrames.cpp](../../../src/Magpie.Core/PassThroughFrames.cpp)、[Renderer.cpp](../../../src/Magpie.Core/Renderer.cpp)。 - -在资源创建成功的正常路径,每个新的捕获帧都会执行 `PassThroughFrames::UpdateBackend`,它没有以 `_isPassThroughActive` 为条件。随后发布、读取、成功呈现时,参考图也一直跟随搬运。本轮没有 FG,对比路径仍包含: - -1. 一次输出尺寸的参考图计算着色器 Dispatch。 -2. 参考图到共享纹理的完整复制。 -3. 共享参考图到前端基础纹理的完整复制。 -4. 成功呈现后,基础参考图到稳定参考图的完整复制。 - -前后端共享槽还包含参考图的 keyed mutex;参考图忙时会让整组前端读取暂时失败,即使当前显示的是处理后画面。普通呈现后的处理结果也会复制到稳定底图,在底图未变、仅 UI/光标重绘时仍可能重复复制。 - -**这是确定存在的常驻开销,来自 r6 对比功能及后续稳定底图处理;不属于 r9 新增的捕获校验。** 它随输出分辨率增加,降低 NR 内部分辨率不会一并消除它。现有计时没有单独测量这一部分,不能据此宣称它已经造成 100 ms 级停顿。 - -建议:让参考图准备和传输按需求启用;未对比时退出不必要的参考图事务,稳定底图仅在内容版本改变时更新。快速切换所需的最小原图缓存和 FG 的参考帧对应关系应保留。不能简单删掉全部缓存,导致首次切换出现旧帧、黑帧或重新加载效果。 - -### B. 相等时间戳被当成捕获中断 - -定位:[GraphicsCaptureFrameSource.cpp](../../../src/Magpie.Core/GraphicsCaptureFrameSource.cpp) 的 `_Update`:`timestamp <= _lastFrameTimestamp100ns` 进入 `_InterruptCapture`。 - -在 `r9-magpie.1.log`: - -| 时间/行号 | 实际情况 | 当前处理 | -| --- | --- | --- | -| 14:37:26.790/270 | 时间戳等于上一帧,内容和表面均为 2560×1440 | 开启恢复序列,下一有效帧重置历史 | -| 14:39:43.707/927 | 同上 | 同上 | - -这两次恢复日志分别为 9.670 ms、9.739 ms。拒绝重复输入是合理的,但没有必要仅因时间戳相等,就启动恢复计时器、通知几何检查并清空时序历史。 - -建议:相等时间戳只跳过,不触发历史重置;时间倒退、无效内容、真正的捕获会话变更继续分别处理。重复帧不应重新进入 NR 推理。**本次只有两次,不能解释整个测试中持续偏低的 1% Low。** - -### C. 内部性能采集与同步日志仍然常开,且存在已经没有消费者的数据 - -定位:[NvidiaOpticalFlowProvider.cpp](../../../src/Magpie.Core/NvidiaOpticalFlowProvider.cpp)、[DLSSNRFilter.cpp](../../../src/Magpie.Core/DLSSNRFilter.cpp)、[FrameGuidancePerformance.h](../../../src/Magpie.Core/FrameGuidancePerformance.h)、[Logger.cpp](../../../src/Shared/Logger.cpp)。 - -- NVOF 的 GPU 查询、NR 的 GPU 时间戳读回、统计窗口仍持续运行;没有跟随 `EffectsProfiler` 的关闭状态停用。 -- `FrameGuidancePerformance::PublishDlssnrGpuTiming` 每帧发布数个原子变量;全量检索当前 `src`,`GetDlssnrGpuTiming` 除声明/定义外已经没有调用方。它是可清理的遗留数据通道,预计本身开销很小。 -- `rotating_logger_mt` 是同步文件日志。常态性能输出在渲染相关线程上格式化并写入,warning 会触发 flush,文件轮转也可能涉及文件操作。本轮在 14:45:45.901 发生了轮转;尚无时间线证明它与卡顿一致。 - -建议:删除无消费者的数据通道;详细性能采集采用明确开关或采样,正常运行只保留必要的状态/错误信息。诊断数据先写有界内存缓冲,结束后或由后台写出;避免为找卡顿而在长帧上增加同步磁盘写入。不能仅通过把日志等级改高,就假定前面的查询、统计和字符串格式化也已消失。 - -### D. 光标命中测试没有限制在途请求 - -定位:[CursorManager.cpp](../../../src/Magpie.Core/CursorManager.cpp) 的 `_SrcHitTestAsync` 及两个调用点。 - -调用方只比较“上一次已经完成的鼠标坐标”,没有记录尚未完成的请求。当前台循环持续运行、鼠标移动或目标窗口回答变慢时,同一/不同坐标的请求可以不断进入线程池。旧请求完成后虽然会丢弃过期结果,但之前的工作已经执行了。 - -每次命中测试允许等待窗口消息回应,局部 timeout 为 100 ms;递归子窗口测试没有共享的总时间预算。这里已经先 `resume_background`,**不是每次都直接阻塞呈现线程 100 ms**,风险是重复工作、请求积压与回调竞争。 - -建议:最多保留一个在途请求并合并最新坐标;丢弃过期输入,设置整次命中测试的时间预算,等待期间使用上次可信结果。该实现与当前 Git HEAD 中相同,不能归为 r9 新增回退。 - -## 3. 兼容逻辑中需要补计时、尚不能判定主因的部分 - -**WGC 光标显隐重启。** 27 次恢复记录中,4 次初始启动、20 次光标显示重启、2 次相等时间戳、1 次原始时间戳长间隔。恢复记录最长 65.692 ms,但恢复计时是在 `_StopCapture` 之后才开始,不能证明整个关闭/重启只花了这么久。应覆盖 `session.Close`、`pool.Close`、`StartCapture` 到首个有效帧的完整时间。也有数分钟没有重启的时段;不能用这 20 次重启解释所有持续运动场景。 - -**焦点更新中的同步探测。** [ScalingWindow.cpp](../../../src/Magpie.Core/ScalingWindow.cpp) 的 `_UpdateFocusStateAsync` 在首次 `co_await` 之前调用 `IsWindowHung`,后者用 `SendMessageTimeout(WM_NULL, …, 500)`。函数名带 Async 不代表这一步异步。对跨线程窗口,这类调用会等回应或超时,可阻塞调用路径;不过这主要发生在焦点变化/窗口操作,不能直接解释用户不切屏时的持续卡顿。[Microsoft API 说明](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessagetimeoutw) - -**最终呈现。** [AdaptivePresenter.cpp](../../../src/Magpie.Core/AdaptivePresenter.cpp) 的常规 `BeginFrame` 已使用 0 ms 查询交换链容量,并没有每帧等待 1 秒。后续 `Present(0, 0)` 本身仍可能发生等待,而当前普通 NR 路径没有记录调用前后及连续成功提交间隔。成功调用也不等于每张帧都实际显示;flip 队列可以丢弃中间帧。因此不能拿 NR 推理耗时或“已发布帧数”替代屏幕上的帧间隔。[Microsoft Present 说明](https://learn.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgiswapchain-present) - -**光流提交。** 当前 `nvOFExecute + Densify` 的 CPU 耗时只在最初两个 frameId 内打印;本轮仅三条样本。GPU interval 不能填补长期 CPU 提交、调度或驱动等待的观测缺口。 - -另外,`StepTimer` 中的 500 ms 是“最长等待消息”,有帧消息会提前唤醒;r9 恢复路径中的 50 ms 等待只在捕获中断期间执行。没有证据表明它们是稳定运行时每帧固定增加的延迟。Zero 深度/运动纹理也只在创建时清零,正常 `BeginFrame` 复用纹理并更新元数据,没有每帧重建整套备用资源。 - -## 4. 日志里一个值得继续追踪的区间 - -`r9-magpie.log` 952–971 行:**14:50:41.496–14:50:50.022**,两个 NR 日志点之间仅推进 120 个处理帧,用时 8.526 秒,约 14.07 个处理帧/秒。这是区间吞吐率,不是该区间的 1% Low。 - -期间 14:50:42.346、44.345、46.344 仍连续记录每 120 次的重复检测读回,平均等待约 0.08 ms。说明这几秒并非捕获线程一直完全不工作,部分输入可能被重复帧检测过滤。日志没有记录每次检测结果和对应捕获时间戳,仍无法区分源内容本身重复、WGC 重复输出、检测误判或前端呈现停顿。用户没有指定这正是主观感到卡顿的时刻,不能擅自对号入座,也不能反过来认定用户看到的是静止画面。 - -## 5. 建议的下一轮执行范围 - -建议以“已证实的精简 + 定位剩余长帧”为一轮修改,不先进行全渲染架构重写: - -1. **修正相等时间戳处理;让未启用的对比退出非必要工作;避免内容未变时重复复制稳定底图。** 保留对比期间效果继续处理、切回无需重载的既有要求。 -2. **合并命中测试请求,清理无消费者的遥测;将详细性能统计与同步写盘移出默认热路径。** 对窗口焦点/光标重启先补完整耗时,后续按证据调整,保留所需兼容行为。 -3. **覆盖普通 NR 的帧时间线。** 使用同一单调时钟和捕获帧编号,记录下面几段;固定容量内存环缓冲保留长帧前后上下文,计数汇总可低频输出,避免每帧写盘。 - -| 阶段 | 必须区分的信息 | -| --- | --- | -| 捕获 | WGC 原始时间戳、接收/返回时间;无新帧、重复时间戳、像素重复、无效内容;完整重启耗时 | -| 后端 | 消息处理间隔、重复检测、光流提交、NR 提交、发布事务、fence 等待 | -| 前端 | 是否收到新内容、连续获取共享槽失败次数和持续时间、交换链容量不可用持续时间、UI/光标处理耗时 | -| 呈现 | `Present` 前后时间、返回值、内容帧编号、仅重绘标记;连续成功提交间隔及待提交帧等待时长 | - -以上能把长帧分成“输入没来”“后端处理慢”“前端没取到/没运行”“Present 等待”。若这些数据仍不能解释体感,再用一次针对源程序与 Magpie 两个进程的系统呈现跟踪确认实际上屏时间;不要求反复盲测效果参数。 - -视频复测应记录视频自身帧率,并与当前源程序/Magpie 的输出区分,避免把正常低帧率视频当作丢帧。NVIDIA FrameView 文档将 1% Low 定义为最慢 1% 帧的平均表现,但不能据此假定 NVIDIA overlay 的采样窗口、进程选择和全部细节相同。[FrameView 1.4 用户指南:Overlay Metrics](https://images.nvidia.com/content/geforce/technologies/frameview/frameview-1-4-user-guide-web-version.pdf) - -## 6. 本轮交付与限制 - -已完成现有 r9 日志统计、相关代码路径复查、官方呈现/等待接口核对。没有把上述候选问题伪称为已验证的性能根因;现有日志缺少逐帧呈现记录,无法重算本轮 1% Low,也无法保证只清理上述开销就能消除卡顿。 - -本机已部署的 `Magpie.exe` SHA-256 仍为 `8A1008A124798C12FAB31C49E756B0AE170C23FA7883C86667A15196F2727B9C`,与此前 r9 部署一致。本轮新增调查文档和本地证据脚本,程序保持原样。 diff --git a/docs/experimental/reviews/20260905-v0.6.5-task-switch-profiler-FIX.md b/docs/experimental/reviews/20260905-v0.6.5-task-switch-profiler-FIX.md deleted file mode 100644 index 89369a70f..000000000 --- a/docs/experimental/reviews/20260905-v0.6.5-task-switch-profiler-FIX.md +++ /dev/null @@ -1,28 +0,0 @@ -# 0.6.5 发布前内测修复:切屏退出与性能面板 - -## 最终行为 - -- 全屏效果组运行、初始化或等待恢复时,Alt+Tab(含 Shift)、Win+Tab 停用当前会话,取消待处理的参数重启。按键仍传给 Windows,不新增提示。 -- 暂停自动配置启用,直到用户通过快捷键或倒计时手动启动;Smooth Motion 的退出后重启也尊重本次暂停。窗口效果组不受此切屏规则影响。 -- 性能面板恢复各效果器/Pass 的耗时数据;Front Edge Sync、普通呈现和 XeSS FG 独立浮层使用相同的读取时机。 - -## 已定位原因与修改范围 - -内测确认此前仅靠捕获校验和重置历史仍不能解决切屏花屏。本次按维护者要求增加明确的会话退出措施,不将原补丁描述为已解决切屏问题。现有输入有效性检查保留,鼠标跨屏操作不扩大为新的自动退出条件。 - -主线程现有键盘钩子检测系统切换组合后,仅提交停止命令;渲染线程负责资源销毁。停止命令递增会话代次,使排队的自动恢复失效。旧停止任务也检查代次,避免误停后来手动开启的新会话。会话模式用原子值共享,工具栏切换全屏/窗口模式时同步更新。 - -本机日志中性能采样处于 `profiling=true`,已启动 4 个 Pass 的采集,未见采集超时或禁用记录。代码发现两条确定的数据丢失路径: - -1. `Renderer::Render` 提前取走一次性样本,随后 Front Edge Sync 在绘制之前返回等待;样本随局部变量丢弃。 -2. XeSS FG 内容呈现取走样本,但界面由独立浮层绘制,浮层原先始终传入空数据。 - -改为在成功获得绘制目标、即将绘制浮层时才读取样本。独立浮层在性能面板可见时至少每 500 ms 刷新一次,即使 FPS 文本不变;无样本时也按此间隔,不忙循环。样本长度与当前 Pass 数不一致时忽略。GPU 查询队列、DONOTFLUSH、Front Edge Sync 和 REALTIME 优先级保持原实现。 - -## 检查与待验收 - -- 已通过 15 项源码路径检查及 20 项 CPU 状态/按键回归检查;CPU 检查直接提取生产函数,覆盖全屏/窗口、启动/恢复阶段、异步销毁、过期停止命令、Alt 和左右 Win、普通 Tab、按键抬起与负钩子代码。 -- Release x64 构建及发布包核对结果记录在本地准备记录中。 -- 未启动 Magpie,未执行 GUI/GPU 测试。仍需用户验收:全屏 Alt+Tab/Win+Tab 后不会自动恢复;窗口模式正常;Front Edge Sync 和 XeSS FG 下各效果耗时持续更新;切屏时 FG 能正常停止。 - -此变更计入 0.6.5;发布说明已同步中英文。未新增诊断工具附件。 diff --git a/docs/experimental/reviews/20260905-v065-frame-sync-panel-residual-update.md b/docs/experimental/reviews/20260905-v065-frame-sync-panel-residual-update.md deleted file mode 100644 index 5e72164ec..000000000 --- a/docs/experimental/reviews/20260905-v065-frame-sync-panel-residual-update.md +++ /dev/null @@ -1,25 +0,0 @@ -# 0.6.5 参数面板帧同步与残差顺序更新 - -## 帧同步的生效路线 - -参数面板在所有效果器之前提供 Front Edge Sync 开关和目标 FPS,与主页共用全局设置。两项自动保存,标记为 Restart;用户点击“应用并重新启用”后一起生效,重新打开参数面板。无参数的效果组也可以使用这些控制。 - -本次选择整组重启:开关影响捕获与共享结果的交付方式、普通呈现等待、DLSSFG 输入等待和 XeSS 的 XeLL 节奏;目标 FPS 同时参与 FG 与 FrameRate Filter 的目标计算。仅修改其中一条运行路径会造成同一会话的各环节使用不同节奏。修改应用在旧后端退出之后,新会话从一致的选项重新初始化。 - -主页修改通过共享会话的 desired 状态同步到面板,不写入正在运行的选项。面板提交携带修改前后的全局值,按字段合并,保留其他界面对未编辑字段的修改;同一 FPS 字段出现并发冲突时拒绝整笔提交。原有保存失败回执保留。自动 NR 重建不会提前应用手动 Restart 的帧同步修改;“恢复本次初始值”也恢复本次启动的帧同步设置。 - -## DLSSNR 残差的处理顺序 - -输入图像 → Lanczos-2 AA 降采样 → DLSSNR 核心 → 低分辨率残差控制 → Catmull-Rom 水平/垂直升采样 → 加回全分辨率原图。 - -残差总倍率、阴影/结构和反射/辉光分类调整、相对原图的饱和度与亮度调整,全部放在插值之前。此处“原图”使用同一低分辨率的未处理输入,与 NR 输出逐像素比较;最终再将调整后的残差回填到完整原图。 - -低分辨率预处理一次生成有符号 FP16 残差,避免在水平插值的每个采样点重复执行 HSL 和亮度分类。增加一张低分辨率 FP16 纹理及一个预处理 dispatch;100%/等宽路径跳过水平插值。垂直合成只负责插值、加回原图和最终颜色范围限制,保留原图 alpha。未开启输入分辨率调整的路径不变;开启时即使比例为 100%,全部残差控制仍有效。 - -实时修改残差参数继续使用缓存的核心结果重新生成残差,不将合成结果送回 DLSSNR 核心。非线性控制移动到插值前后不会完全等价,因此低分辨率下旧参数组合的外观可能变化,这是本次顺序调整的预期影响。 - -## 发布资料与验证 - -发布附件仅保留主包、DLSSNR DLL 选项和 NGX OTA 开关。独立 ScalingModes JSON 移出附件和发布说明,源码预设留作历史参考。保留用户补充的中文开发者注,同步英文,说明下采样/升采样算法与新的残差顺序。 - -已通过 13 组全局设置合并案例、3 组帧率过滤器目标检查和 24 项源码/资源/着色器检查,其中包括全部 7 个 DLSSNR 计算着色器入口的 Release 编译。完整程序构建、清单及 ZIP 校验结果记录在本地发布准备记录中。未启动 Magpie 或执行 GUI/GPU 测试,实机画面与性能由用户验收。 diff --git a/docs/experimental/reviews/20260906-v0.6.6-capture-shutdown.md b/docs/experimental/reviews/20260906-v0.6.6-capture-shutdown.md deleted file mode 100644 index 4ee30f0ec..000000000 --- a/docs/experimental/reviews/20260906-v0.6.6-capture-shutdown.md +++ /dev/null @@ -1,11 +0,0 @@ -# 0.6.6 会话关闭回调保护 - -停止缩放时,CursorManager 析构会排队关闭 WGC。后端仍可能继续取帧,将已清空的捕获会话当作 E_UNEXPECTED(0x8000FFFF)并排队报告错误。正常清理随后清空 ScalingOptions,旧回调再执行时可能调用空的 showError。这个错误码由程序传入,不能据此判断驱动故障;错误日志与普通日志刷新频率不同,也不能将最后一条捕获日志等同于实际崩溃位置。 - -修复在 WM_DESTROY 开始即递增 runId,并通过 Renderer::BeginShutdown 发布停止标志,早于鼠标析构。后端继续处理必要的关闭消息直到 WM_QUIT,但不再取帧/渲染;已经在途的取帧完成后也检查停止标志。WGC 自己记录终止关闭状态,将关闭后的 Update 视为 Waiting,避免把正常关闭当作故障。 - -Renderer 创建时固定会话编号,捕获错误和 XeLL 错误回调持有独立的共享生命周期标记,不持有已析构的 Renderer。回调检查编号、停止标志和窗口有效性后才读取配置或停止会话。ShowError 独立检查空指针,为其他调用入口提供保护。 - -scripts/tests/test_capture_shutdown.py 从生产源码提取回调和 ShowError,使用真实生命周期类与模拟调度器进行 CPU 回归:11 个场景、1000 次会话重建循环和 8 项集成顺序检查通过,覆盖正常错误报告、空回调、延迟到清理后、延迟到新会话、析构后执行和线程停止标志。 - -源码致崩路径已修复;没有本次闪退的 dump,尚未证明这就是用户遇到的实际触发点。GPU 捕获和快捷键反复启停仍需实机验收。 diff --git a/docs/experimental/reviews/20260906-v0.6.6-ngx-deadlock.md b/docs/experimental/reviews/20260906-v0.6.6-ngx-deadlock.md deleted file mode 100644 index 80eb90591..000000000 --- a/docs/experimental/reviews/20260906-v0.6.6-ngx-deadlock.md +++ /dev/null @@ -1,27 +0,0 @@ -# 0.6.6 NGX 关闭异常后的死锁保护 - -两次实际 Magpie 转储均确认:关闭 NGX 时发生 SEH 后,缩放线程仍持有 SDK 临界区;新后端初始化等待该锁,缩放线程同时等待后端。独立双进程实验确认,相同的跨进程窗口所有者关系可通过 SetWindowPos 的同步窗口消息传播等待,AttachThreadInput(FALSE) 不足以消除这一等待。 - -## 实现 - -- `NgxRuntimeGuard` 串行化 NGX Core、DLSSNR、DLSSFG 的现有受保护调用。SEH 过滤器保存第一次异常的代码、地址、线程编号,在退出本次调用前设置进程级故障标记。新的 Renderer 不能清除它,后续调用在进入 SDK 前失败。正常 API 失败仍按原逻辑处理;最终 Shutdown1 失败也视为不能安全重试。 -- 新的 NGX 会话在改变目标窗口之前失败,显示完整退出 Magpie 后重试的提示。初始化及运行中的故障通过现有诊断渠道报告;正常停止中首次出现的故障也会报告。当前使用 NGX 的后端发现故障后停止渲染,错误回调绑定原会话生命周期。 -- 故障后的 NGX 设备和受影响的 DLSSNR 模块保留到进程结束,不强行解锁 SDK 临界区,不再进入其关闭/释放入口。这是异常状态隔离;正常停止仍释放资源。 -- 窗口模式和全屏模式都先创建没有游戏所有者的窗口。Renderer、鼠标和触控初始化完成后,检查源窗口仍有效并关联,再恢复原有输入队列处理。WM_DESTROY 最先解除所有者关系,再停止捕获、等待后端和关闭 SDK。运行阶段的所有者关系及焦点/层级逻辑保持原有设计。 - -## 验证与边界 - -故障注入使用实际生产 `NgxRuntimeGuard`,模拟 SDK 持有临界区时产生访问违规,并从 16 个新后端线程尝试再次进入;另一个进程验证非 SEH 的最终关闭失败。双进程窗口回归使用生产 `SetScalingWindowOwner`,对照原始关系与初始化、清理时解除关系的行为。 - -这次修复切断已确认的异常后重启死锁及其初始化/清理阶段的窗口传播路径,并不声称消除了 NVIDIA SDK 最初的内部访问违规。也没有将所有后端初始化改成可取消异步任务:原有同步等待和部分 GPU 等待仍存在,但发生在未关联目标窗口的初始化/清理阶段。没有以超时后分离线程或释放 Renderer 的方式规避等待,以免产生释放后使用。 - -没有取得 Endfield 自身的挂起线程栈,不能宣称游戏问题已通过实机回归完全解决。部署和构建证据保存于工作区 `diagnostics/20260906-v066-ngx-fix`,游戏仍需实机验证反复启停、参数重建、全屏/窗口切换和焦点行为。 - -## 本地验证结果 - -- Release x64 全量构建与打包通过,保留独立 PDB/MAP;0 个编译错误,仅原有 FidelityFX C4324 对齐警告。 -- 访问违规持锁故障与非 SEH 最终关闭失败两种注入通过;各自阻止 16 个新后端进入 SDK,正常失败仍可重试。 -- 双进程实测:旧所有者关系的对照组阻塞约 3.8 秒;初始化时不关联及真正进入 WM_DESTROY 后先解除关联的两组均为 0 ms。整个测试只操作其自行创建的隐藏窗口。 -- 捕获关闭回归的 11 个场景、1000 次重建和 8 项集成顺序检查通过。三种新增提示资源及 19 个 NGX 调用保护接入校验通过。 -- 部署清单 227 个文件与 ZIP 228 个条目逐一通过大小/哈希核验,源码晚间 README 基线仍保留。 -- 只读观察到修复版 11:08:22 和 11:08:26 两次启用均提交首帧,11:08:25 和 11:08:28 两次停止均完成 NGX Shutdown1。这是有限的启停证据,没有自动操作游戏或执行完整图形回归。 diff --git a/docs/experimental/reviews/20260906-v0.6.6-parameter-interaction.md b/docs/experimental/reviews/20260906-v0.6.6-parameter-interaction.md deleted file mode 100644 index b6798fc81..000000000 --- a/docs/experimental/reviews/20260906-v0.6.6-parameter-interaction.md +++ /dev/null @@ -1,22 +0,0 @@ -# 0.6.6 参数交互 - -## 实现 - -- 参数重建前保存工具栏显示、固定状态、性能分析器和参数窗口的四项会话状态;手动应用和延迟自动重建均在 Start 成功后恢复。取消重建会清除快照,普通新会话不会继承旧状态。性能分析器恢复走正常的 StartProfile 路径,在新后端重新创建计时查询。 -- 参数面板的 Front Edge Sync 目标使用 15–360、1 FPS 步进的整数滑条,禁用滑条转数字输入模式。仅交互时写值,打开面板不会改变已有配置。主页仍支持 0 自动和小数,60 FPS 默认值、保存合并和手动应用行为不变。 -- 参数浮窗在 overlay.windows.effectParameters 中保存边缘距离/相对中心、DIP 宽高;width/height 为 0 或缺失时使用旧版默认尺寸。拖动/调整尺寸后在松开鼠标时调用现有异步保存入口,停止缩放也保存。捕获区域缩小时只约束显示尺寸,不修改用户偏好;区域变大或新会话恢复偏好,折叠使用 SizeFull,避免标题栏高度污染配置。配置读取会过滤非法锚点和非有限、负数尺寸。 -- 不再按 DLSSNR 所在位置把 Live 参数提升为 automaticRestart。工具栏与设置页共用 Renderer 的实时队列及运行中的后端能力判断。连续编辑按参数合并为最新值,在后端帧边界应用;Applied 与 Desired 状态仍分离,未手动应用的资源参数不会混入实时候选。 -- DLSSNR 自身核心参数沿用 evaluateParameterRevision 和 resetHistory;上游实时参数变化通过 inputRevision 使后续 NR 的静帧缓存失效并重置历史。残差参数继续只重做残差合成;如果还有下游 NR,其输入变化仍触发重算。 -- 输入分辨率开关、比例、光流及 FG 资源配置仍按后端能力要求重启。保留原自动重建基础设施及其状态恢复,0.6.6 的上述实时图像参数不再触发该流程。 - -## 文档与部署 - -中英文 README 保留晚间项目介绍及后续 c44ff55a 的帧同步说明、PR #3 致谢,仅补充滑条范围和浮窗布局。Release Note 沿用 0.6.5 的完整中文、分隔线、完整英文结构;0.6.5 说明保留为历史快照。停止缩放崩溃隐患的修复见 [会话关闭回调保护](20260906-v0.6.6-capture-shutdown.md)。 - -使用 Build-Release.ps1 完整构建 0.6.6 Release x64,本地包允许工作树修改并在清单中如实记录 sourceDirty。version.json 保留原有更新入口。构建、CPU 回归、文件与 ZIP 校验结果见工作区 release/v0.6.6-local/LOCAL-PREPARATION-AUDIT.md。 - -## 实机验收范围 - -CPU 验证包含布局纯函数和项目实际 Dear ImGui 的无窗口运行:旧配置默认值、16 组视口与 DPI 组合、边缘锚点、非法配置、主动调整、临时缩小/放大、折叠/展开和新会话恢复。 - -本轮电脑操作工具未获准访问 Magpie,不能把 GUI/GPU 行为宣称为已验证。需要实机确认:固定/未固定工具栏,性能分析器和参数页的开关组合,在手动重新启用后正确恢复;滑条拖动及 15/360 端点;浮窗拖动与调整尺寸后跨程序启动恢复;DLSSNR 核心、上游及多 NR 链在动态/静止画面上的实时更新和历史过渡;资源参数仍只在手动应用后生效。 diff --git a/docs/experimental/todos/20260903-v0.6.1-feature4-interaction-errors-TODO.md b/docs/experimental/todos/20260903-v0.6.1-feature4-interaction-errors-TODO.md deleted file mode 100644 index 552940042..000000000 --- a/docs/experimental/todos/20260903-v0.6.1-feature4-interaction-errors-TODO.md +++ /dev/null @@ -1,80 +0,0 @@ -# v0.6.1 Feature 4:缩放模式交互修复与引导性错误提示 - -范围:仅用于本地 `v0.6.1` 主包,不提交、不发布远端 Release。 - -## 目标 - -- 修复缩放模式和效果器拖拽完成后发生的 XAML 闪退。 -- 修复删除新建缩放模式后,再次新建时不自动打开重命名的问题。 -- 将面向用户的失败提示从“只描述失败”调整为“说明原因并给出下一步操作”。 -- 对无法仅靠文案准确处理的失败路径,记录后续需要拆分的错误码和交互入口。 - -## A. 交互回归 - -- [x] `PointerReleased` 只登记提交意图,等待 XAML 完成输入分发和自动释放 Pointer Capture 后再修改 `ItemsSource`。 -- [x] 增加释放捕获状态保护,防止同步 `PointerCaptureLost` 把有效拖拽误判为取消。 -- [x] 让完成、取消和捕获丢失路径保持幂等,并保护 XAML/WinRT 异常。 -- [x] 自动命名不再只依赖重命名按钮第一次 `Loaded`。 -- [x] 处理 ListView 容器复用后的 `DataContextChanged`,在布局完成后打开新项目的重命名 Flyout。 -- [x] 保留异步加载期间收到的自动展开/自动命名请求,避免 `_addingScalingModes` 提前丢弃。 -- [ ] 验证效果器组内拖拽、缩放模式组拖拽、删除后连续新建和管理员模式。 - -### 2026-09-03 第二轮拖拽闪退回归 - -- [x] 分析 `Magpie.exe.58980.dmp` 和 `Magpie.exe.49672.dmp`:两次均为 `Windows.UI.Xaml.dll` 的 `0xc000027b` fail-fast,应用记录的未处理异常均为 `HRESULT 0x80070005`。 -- [x] 移除指针回调中的显式 `ReleasePointerCapture`;松手、取消和捕获丢失统一合并到下一次低优先级 UI 调度完成。 -- [x] 移除挂在可回收 `ListViewItem` 上的 `TranslationTransition`,改用可同步还原的独立 `RenderTransform` 预览位移。 -- [x] 所有拖拽指针事件入口捕获 WinRT/XAML 异常,失败时记录阶段并排队安全取消,避免异常再次进入 XAML 未处理异常路径。 -- [x] 拖拽容器恢复加入失效/回收保护,集合重排只在输入回调返回后执行。 -- [ ] 覆盖部署后重新完成效果器组和缩放模式组高频拖拽回归。 - -## B. 可直接更新的引导性提示 - -- [x] 窗口模式遇到最大化/全屏窗口:提示还原窗口、退出全屏或改用全屏模式缩放。 -- [x] 3D 游戏模式与窗口模式冲突:提示关闭 3D 游戏模式或改用全屏模式缩放。 -- [x] Desktop Duplication 与窗口模式冲突:提示切换捕获方式或改用全屏模式缩放。 -- [x] 全屏模式禁止缩放最大化窗口:指出“主页 → 高级”中的对应开关。 -- [x] 自定义裁剪无效:指出裁剪后宽高至少为 `64×64`,并建议减小或关闭裁剪。 -- [x] 目标窗口权限较高:明确要求以管理员身份重新运行 Magpie。 -- [x] 触控支持组件启动失败:提示可暂时关闭触控支持,或以管理员身份重新启用。 -- [x] 3D 游戏模式下请求工具栏:提示停止缩放并关闭 3D 游戏模式,或使用快捷键。 -- [x] 捕获初始化失败:指出配置中的捕获方式位置,并建议“默认”或 Graphics Capture。 -- [x] CreateFence 失败:隐藏不必要的 API 术语,指向显卡选择和驱动更新。 -- [x] 未知效果警告:说明该缩放模式不可用,并提示删除未知效果或恢复效果文件。 -- [x] 快捷键无效/被占用:说明合法组合规则,并建议更换或关闭占用程序。 -- [x] 更新下载失败:提示检查网络、磁盘空间,重试或从发布页手动下载。 -- [x] 同步更新简体中文与英文资源;其他语言保留后续翻译项。 - -## C. 需要细分错误原因的后续项目 - -2026-09-05:本节在 r5 fix2 中继续实现。分类、错误详情与代码编号见 [r5 fix2 review](20260905-v0.6.5-r5-fix2-REVIEW.md);以下勾选表示源码实现,不代表用户实机验收。 - -- [x] 将 `InvalidScalingMode` 拆分为:未选择模式、模式为空、包含未知效果。 -- [x] 将 `InvalidSourceWindow` 拆分为:窗口已关闭、未响应、尺寸小于 `64×64`、位于屏幕外、不支持的窗口类型、几何信息读取失败。 -- [x] 将 `ScalingFailedGeneral` 拆分为:已有缩放窗口、创建缩放窗口失败、效果编译失败、原生/AI 效果初始化失败、图形设备初始化失败、多显示器布局失败。 -- [x] 效果编译或原生效果初始化失败时显示效果名称,并为可识别的显卡、驱动或可选组件问题提供专用提示。 -- [x] 截图失败区分目录创建失败、目录不可写和图像编码/保存失败,并显示保存路径。 -- [x] 导入缩放模式区分读取失败、空文件、JSON 错误、错误文件类型和配置结构不兼容。 -- [x] 导出缩放模式失败时显示目标路径,提示检查权限、空间或更换可写入目录。 -- [x] 通用缩放失败显示稳定的诊断编号,并提供明确的日志入口。 - -## D. 当前静默失败的后续项目 - -- [x] 配置异步保存失败时通知用户,并对连续失败限流,避免频繁弹出。 -- [ ] 配置读取/解析失败对话框增加“打开配置位置”和安全的备份/重置入口,并显示 JSON 错误位置。 -- [ ] 开启或关闭便携模式失败时提示目录写入权限问题。 -- [ ] 开机启动修改失败时检查返回值,并提示任务计划程序或权限问题。 -- [ ] 全局快捷键保存后实际注册失败时,在对应控件旁显示持续状态。 -- [ ] 文件选择器创建失败时区分“用户取消”和“系统错误”,不要静默返回。 - - r5 fix2 已区分共享选择器的 Show/GetResult/GetDisplayName 失败与取消,并提示导入/导出时创建选择器失败;其他入口的创建失败可继续补齐。 -- [ ] 打开截图目录、选择启动程序和启动配置程序失败时提供反馈。 -- [ ] 移除已无调用点的 `ScalingModes_DragNotSupported` 旧文案。 - -## E. 验收 - -- [x] Release x64 构建通过。 -- [x] 本地 `v0.6.1` 主包更新成功,不包含 PDB/OBJ 等开发文件。 -- [ ] 连续完成至少 20 次效果器拖拽和 20 次缩放模式拖拽,无闪退、无顺序错乱。 -- [ ] 连续执行“新建 → 自动命名 → 删除”至少 10 次,每次新建都自动打开命名。 -- [x] 最大化窗口使用窗口模式缩放时,提示包含“全屏模式缩放”的可执行建议。 -- [x] 中文和英文关键提示检查通过。 diff --git a/docs/experimental/todos/20260903-v0.6.1-feature5-profile-ui-polish-TODO.md b/docs/experimental/todos/20260903-v0.6.1-feature5-profile-ui-polish-TODO.md deleted file mode 100644 index fe5e56baa..000000000 --- a/docs/experimental/todos/20260903-v0.6.1-feature5-profile-ui-polish-TODO.md +++ /dev/null @@ -1,42 +0,0 @@ -# v0.6.1 Feature 5:配置与缩放模式 UI 收尾 - -范围:仅用于本地 `v0.6.1` 主包,不提交、不发布远端 Release。本功能是 v0.6.1 的最后一组界面调整。 - -## 目标 - -- 消除配置文件拖拽松手时其他配置文件短暂回弹造成的抽动。 -- 统一启动、更多选项和拖动提示的图标尺寸与间距。 -- 让启动和更多选项按钮沿用缩放模式页更多选项按钮的透明、悬停、按下三态样式。 -- 统一缩放模式与效果器操作列的拖动占位,避免单效果模式下误触删除。 -- 恢复默认缩放模式时不再自动加入 DLSS SR 预设,但保留 DLSS SR 效果供手动添加。 - -## A. 拖拽落位 - -- [x] 预览 Storyboard 的目标位移同步写入 `CompositeTransform` 基础值,停止动画时不再短暂暴露 `TranslateY=0`。 -- [x] 新一轮预览动画从当前显示位移开始,避免快速跨越或反向拖动时从上一个目标位置重新起跳。 -- [x] 松手时先在现有预览位置提交配置文件顺序,强制完成新顺序布局后再于同一 UI tick 移除预览变换,避免被跨越项暴露旧布局位置。 -- [x] 缩放模式和效果器拖拽使用相同的“提交—布局—清理变换”顺序,避免两套拖拽出现同类抽动。 -- [x] 保留完成、取消、捕获丢失和页面关闭路径的异常保护与幂等处理。 - -## B. 操作区视觉统一 - -- [x] 启动和更多选项按钮默认背景使用 `SubtleFillColorTransparentBrush`。 -- [x] 鼠标悬停与按下分别使用 `SubtleFillColorSecondaryBrush` 和 `SubtleFillColorTertiaryBrush`。 -- [x] 两个按钮继续使用 28×28 点击区域和 4px 间距,并移除常驻边框;启动图标保持 13px,三点图标单独放大到 17px 修正视觉重量。 -- [x] 拖动提示继续保留易于操作的点击区域,但三横线图标缩小到 13px,与相邻按钮图标对齐。 -- [x] 配置文件内容区向右补偿导航项模板的固有留白,使左侧程序图标与右侧拖动提示到行边缘的视觉距离接近。 - -## C. 缩放模式收尾 - -- [x] 单个效果器时仍保留 36px 拖动列,但隐藏图标并禁用命中,删除按钮不再占用拖动提示的位置。 -- [x] 单个缩放模式时沿用相同占位规则,保持模式标题操作区与多项状态对齐。 -- [x] 默认缩放模式预设从 7 项调整为 6 项并移除 DLSS SR;`DLSS\DLSS_SR` 效果文件、注册和已有用户配置保持不变。 - -## D. 验收 - -- [ ] 手动反复完成向上、向下、跨多项和快速反向拖动,松手时其他配置文件无回弹或抽动。 -- [ ] 深色、浅色主题下检查默认透明、悬停和按下三态,三个操作项的间距与图标视觉尺寸一致。 -- [ ] 检查单效果与多效果模式的删除、参数、缩放和拖动列对齐,单效果空白拖动区不会触发删除。 -- [ ] 重置缩放模式后预设列表不含 DLSS SR,同时仍可从“添加效果”中选择 DLSS SR。 -- [x] Release x64 / `0.6.1` 编译通过。 -- [x] 覆盖本地 `v0.6.1` 主包及 ZIP,并核对源文件、部署文件和 ZIP 条目哈希。 diff --git a/docs/experimental/todos/20260904-v0.6.5-amd-optical-flow-levels-TODO.md b/docs/experimental/todos/20260904-v0.6.5-amd-optical-flow-levels-TODO.md deleted file mode 100644 index 205b6d93d..000000000 --- a/docs/experimental/todos/20260904-v0.6.5-amd-optical-flow-levels-TODO.md +++ /dev/null @@ -1,213 +0,0 @@ -# v0.6.5 AMD Optical Flow 两档模式 TODO - -> 创建时间:2026-09-04(Asia/Shanghai)\ -> 目标版本:0.6.5\ -> 状态:设计确认,尚未实施\ -> 范围:为 AMD FidelityFX Optical Flow 增加“质量 / 性能”两档;不修改 FidelityFX 内部算法。 - -## 1. 已确认决策 - -- [x] 只提供两个用户可见档位: - - `质量`:100% 输入分辨率。 - - `性能`:50% 输入分辨率。 -- [x] 默认使用 `质量`。 -- [x] 保持 AMD 官方 Optical Flow 的固定 8×8 块和完整七层处理流程。 -- [x] 不修改 FidelityFX shader、搜索范围、过滤过程或内部迭代次数。 -- [x] 不提供 75%、自动档位或更多隐含档位。 -- [x] “质量 / 性能”只描述 Magpie 的 OF 输入分辨率策略,不宣称是 AMD 官方质量预设。 - -## 2. 两档合同 - -| 档位 | OF 输入尺寸 | OF 输出尺寸 | 对应原图有效块大小 | 用途 | -|---|---|---|---|---| -| 质量 | `sourceWidth × sourceHeight` | `ceil(W/8) × ceil(H/8)` | 约 8×8 | 优先保留小物体、细线和运动边缘 | -| 性能 | `ceil(W/2) × ceil(H/2)` | `ceil(halfW/8) × ceil(halfH/8)` | 约 16×16 | 降低 OF 计算量和内部资源规模 | - -尺寸示例: - -| 捕获尺寸 | 质量模式输入 / 输出 | 性能模式输入 / 输出 | -|---|---|---| -| 1920×1080 | 1920×1080 / 240×135 | 960×540 / 120×68 | -| 2560×1440 | 2560×1440 / 320×180 | 1280×720 / 160×90 | -| 3840×2160 | 3840×2160 / 480×270 | 1920×1080 / 240×135 | - -## 3. P0:参数与配置合同 - -- [ ] 定义稳定的内部枚举,例如: - - ```cpp - enum class AmdOpticalFlowMode : uint8_t { - Quality, - Performance - }; - ``` - -- [ ] 增加用户设置 `AMD Optical Flow Mode`: - - `Quality` / `质量`:100%。 - - `Performance` / `性能`:50%。 -- [ ] 默认值设为 `Quality`。 -- [ ] 对未知、缺失或旧配置值安全回退到 `Quality`。 -- [ ] 明确参数应用方式:切换档位需要重建 OF context;不得把它伪装成无历史影响的逐帧 Live 参数。 -- [ ] 日志打印用户档位、捕获尺寸、实际 OF 输入尺寸和输出网格尺寸。 - -验收:新旧配置均能稳定解析;设置、日志与真正创建的 OF 分辨率一致。 - -## 4. P1:根据档位创建 OF context 与资源 - -- [ ] 集中实现 OF 尺寸换算: - - ```cpp - ofWidth = mode == Quality ? sourceWidth : (sourceWidth + 1) / 2; - ofHeight = mode == Quality ? sourceHeight : (sourceHeight + 1) / 2; - ``` - -- [ ] 使用实际 `ofWidth × ofHeight` 填写 `FfxOpticalflowContextDescription::resolution`。 -- [ ] 通过 `ffxOpticalflowGetSharedResourceDescriptions` 取得输出向量与 SCD 的资源描述,不在 Magpie 中重复硬编码全部资源尺寸。 -- [ ] 验证输出向量格式为 `R16G16_SINT`,输出尺寸遵循 `ceil(ofExtent / 8)`。 -- [ ] 质量模式不额外创建半分辨率输入纹理。 -- [ ] 性能模式创建可复用的半分辨率颜色纹理及其 SRV/UAV/必要视图。 -- [ ] resize、捕获源改变或档位改变时,在旧资源不再被 GPU 使用后销毁并重建 context 与相关资源。 -- [ ] 不同时常驻 100% 和 50% 两套 context,除非后续证据证明热切换频率值得额外显存。 - -验收:1080p、1440p、4K 和奇数宽高均得到正确资源尺寸;无越界访问、验证层错误或资源生命周期错误。 - -## 5. P2:性能模式输入下采样 - -- [ ] 质量模式直接把原始颜色纹理传给 `ffxOpticalflowContextDispatch`。 -- [ ] 性能模式在 OF dispatch 前增加一次 GPU 下采样: - - ```text - 原始颜色 → 2× 下采样 → 半分辨率颜色 → AMD OF - ``` - -- [ ] 不允许只把 context 设置为半分辨率却继续传入原尺寸纹理;FidelityFX OF 不负责自动缩放调用者的输入。 -- [ ] 第一版使用确定性的 2×2 box 或线性过滤,边界采用 clamp。 -- [ ] 保持与源纹理一致的颜色编码合同,避免 sRGB 自动解码后又被 OF 按传递函数重复解码。 -- [ ] HDR 路径继续传递正确的 `backbufferTransferFunction` 与 `minMaxLuminance`。 -- [ ] 对下采样 pass 单独增加 GPU timestamp,不把它藏进 OF 耗时。 - -验收:半分辨率纹理覆盖完整画面,不是原纹理左上区域;SDR/HDR 不出现明显亮度偏差或场景切换误判。 - -## 6. P3:Dispatch、历史与模式切换 - -- [ ] 两档均调用相同的官方 `ffxOpticalflowContextDispatch`,仅 `color` 和 context 分辨率不同。 -- [ ] 保持 `reset`、颜色传递函数、亮度范围、输出向量及 SCD 绑定语义不变。 -- [ ] 档位改变时执行以下顺序: - 1. 停止向旧 context 提交新工作。 - 2. 等待或确认旧 context 相关 GPU 工作完成。 - 3. 销毁旧 context 与档位相关资源。 - 4. 按新分辨率重新创建。 - 5. 清空 Magpie 的稠密运动与历史元数据。 - 6. 下一次 dispatch 使用 `reset = true`。 -- [ ] resize、捕获不连续、设备恢复和场景历史失效继续触发 reset。 -- [ ] context 重建后的第一帧不得输出或消费旧档位留下的运动向量。 - -验收:反复切换质量/性能、resize、暂停恢复和重启捕获均无旧运动闪回、越界或设备移除。 - -## 7. P4:运动向量坐标与尺度恢复 - -性能模式中的原始向量以半分辨率输入像素为单位。传给原分辨率消费者前必须分别恢复 X/Y 尺度: - -```cpp -const float vectorScaleX = float(sourceWidth) / float(ofWidth); -const float vectorScaleY = float(sourceHeight) / float(ofHeight); - -sourceMotion.x = rawOpticalFlow.x * vectorScaleX; -sourceMotion.y = rawOpticalFlow.y * vectorScaleY; -``` - -- [ ] 不固定写死乘以 `2.0`;奇数尺寸必须使用实际宽高比例。 -- [ ] 如果下游需要归一化位移,则使用: - - ```cpp - normalizedMotion.x = rawOpticalFlow.x / float(ofWidth); - normalizedMotion.y = rawOpticalFlow.y / float(ofHeight); - ``` - -- [ ] 稠密化前,先把原图像素位置映射到 OF 输入空间,再映射到 8×8 输出网格。 -- [ ] 质量模式与性能模式共用一套坐标换算函数,避免两条实现逐渐产生不同符号、半像素或边界规则。 -- [ ] 用固定平移素材确认 AMD OF 的向量方向,再与 DLSSNR/XeSS/其他消费者的“当前帧→上一帧”合同对齐;不得仅凭变量名判断符号。 -- [ ] 最后一行/列的非完整 8×8 块使用 clamp,不读取网格之外。 - -验收:已知移动 16 个原图像素的素材,在两档中恢复出的原图运动幅值与方向一致;奇数尺寸无系统性漂移。 - -## 8. P5:稠密化与下游绑定 - -- [ ] 输出网格仍按实际 OF 输入的 8×8 块处理: - - 质量模式约等于原图 8×8。 - - 性能模式约等于原图 16×16。 -- [ ] 第一版可以复用现有稠密化路径,但必须使用实际 `sourceExtent / ofExtent` 进行位置和向量换算。 -- [ ] 不把性能模式产生的 16×16 有效粒度错误标记成“原图低分辨率 8×8”。 -- [ ] 若下游直接使用 FidelityFX Frame Interpolation: - - `opticalFlowScale = { 1/ofWidth, 1/ofHeight }`。 - - `opticalFlowBlockSize = 8`,不要改成 16。 - - `opticalFlowBufferSize` 使用真实输出纹理尺寸。 -- [ ] 若下游接收原图像素单位的稠密 MV,则先完成向量尺度恢复,再按消费者的格式和方向绑定。 - -验收:两档均没有全局二倍/二分之一运动错误;画面中心和四边的采样位置一致。 - -## 9. P6:性能遥测 - -- [ ] 分开测量并记录: - - 性能模式下采样 GPU 时间。 - - `ffxOpticalflowContextDispatch` 所有 passes 的 GPU 时间。 - - 稠密化/格式转换 GPU 时间。 - - 下游 DLSSNR/XeSS/FG 消费阶段时间。 -- [ ] 使用非阻塞 timestamp ring,避免为了读取计时结果每帧等待 GPU。 -- [ ] 至少输出 120 样本的平均、P95、P99 和最大值。 -- [ ] 同时记录档位、源尺寸、OF 尺寸、GPU 名称、驱动、颜色格式和是否启用异步队列。 -- [ ] 性能模式的收益按“下采样 + OF + 稠密化”总和判断,不能只比较 OF dispatch。 - -验收:可以回答 100%→50% 实际节省了多少 GPU 时间,以及节省是否被前后处理抵消。 - -## 10. P7:画质与功能验证矩阵 - -- [ ] 分辨率:1920×1080、2560×1440、3840×2160,以及至少一组奇数宽高。 -- [ ] 内容类型: - - 固定方向、固定像素距离的平移测试图。 - - 快速镜头移动。 - - 前景人物与背景反向移动。 - - 细线、栅栏、文字、HUD、小粒子和半透明效果。 - - 遮挡与显露区域。 - - 场景切换、窗口最小化恢复和捕获源切换。 -- [ ] 颜色路径:SDR、HDR(适用时)和不同 backbuffer 格式。 -- [ ] 硬件:至少覆盖支持原生 `msad4` 的 AMD GPU,以及一张非 AMD 或较老兼容 GPU。 -- [ ] 两档使用完全相同的输入片段和下游参数做 A/B,不用不同场景的主观印象比较。 -- [ ] 记录性能模式对小物体、运动边缘和 SCD 的退化,不把降噪造成的偶然稳定误判为整体质量提升。 - -验收: - -- 质量模式与 AMD 官方原生分辨率路径行为一致。 -- 性能模式在包含下采样的端到端 GPU 区间上有可重复的性能收益。 -- 两档恢复后的运动幅值和方向一致。 -- 档位切换和历史重置无闪回或错误复用。 -- 性能模式的已知画质代价有日志/文档说明,不静默冒充质量等价模式。 - -## 11. 明确不做 - -- [ ] 不修改 `advancedAlgorithmIterations = 7`。 -- [ ] 不修改 `opticalFlowBlockSize = 8`。 -- [ ] 不修改搜索半径、中值过滤或金字塔结构。 -- [ ] 不加入 75% 档。 -- [ ] 不加入自动动态分辨率或逐帧档位切换。 -- [ ] 不把 FSR 上采样的 `Quality/Balanced/Performance` 与本 OF 档位混为一谈。 -- [ ] 不用 AMD 整体 Frame Generation 毫秒数据冒充 OF 单独耗时。 - -## 12. 建议实施顺序 - -1. P0 参数合同与日志。 -2. P1 context/资源按实际 OF 尺寸创建。 -3. P2 半分辨率输入下采样。 -4. P4 坐标与向量尺度恢复。 -5. P3 生命周期和 reset。 -6. P5 下游绑定。 -7. P6 遥测。 -8. P7 实机 A/B 验收。 - -## 13. 参考资料 - -- [AMD FidelityFX Optical Flow 1.1.2](https://gpuopen.com/manuals/fidelityfx_sdk/techniques/optical-flow/) -- [FfxOpticalflowContextDescription](https://gpuopen.com/manuals/fidelityfx_sdk/reference_documentation/structs/ffx_opticalflow_context_description/) -- [FfxOpticalflowDispatchDescription](https://gpuopen.com/manuals/fidelityfx_sdk/reference_documentation/structs/ffx_opticalflow_dispatch_description/) -- [FfxFrameInterpolationDispatchDescription](https://gpuopen.com/manuals/fidelityfx_sdk/reference_documentation/structs/ffx_frame_interpolation_dispatch_description/) -- [FidelityFX SDK 官方 Optical Flow 实现](https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/blob/main/Kits/FidelityFX/framegeneration/fsr3/internal/ffx_opticalflow.cpp) diff --git a/docs/experimental/todos/20260904-v0.6.5-r1-TODO.md b/docs/experimental/todos/20260904-v0.6.5-r1-TODO.md deleted file mode 100644 index 1ab80c634..000000000 --- a/docs/experimental/todos/20260904-v0.6.5-r1-TODO.md +++ /dev/null @@ -1,261 +0,0 @@ -# v0.6.5 r1 TODO:移除深度估算与效果参数多列分组 - -基线:`0.6.5` 分支,创建自 `84d9f6abb8203a79f9a44d9a66984a293e68856d`。 - -本文件是 r1 实施与验收清单。2026-09-04 已完成源码实现、文档、干净 Release x64 重建和本地验证包检查;未勾选项仍需用户确认或实机 GPU/UI 回归。 - -## 目标与边界 - -- 移除 Depth Anything V2、ONNX Runtime DirectML/TensorRT 以及相关推理、打包和诊断路径。 -- 保留 DLSS/NGX 调用合同所需的 **Zero Depth** 纹理;“移除深度估算”不等于向 SDK 传空 Depth。 -- 保留 NVIDIA Optical Flow 的 Motion/Confidence 路径。 -- 效果器参数 Flyout 默认仍为原来的单列、260px 宽布局;效果文件可显式把暴露参数分成多列。 -- 清理可重建产物和已失效资料,但历史发布说明、仍在使用的 SDK、发布包和工作树不做无证据删除。 -- `r1` 表示 v0.6.5 的第一轮实施清单,不作为 Git 标签名。 - -## 已确认的现状 - -### 工作区体积快照 - -以下是 2026-09-04 的逻辑文件大小,用于判断清理优先级;硬链接或重复内容可能使实际磁盘占用不同。 - -| 路径 | 约占用 | 判断 | -| --- | ---: | --- | -| `release/` | 40.971 GiB | 含展开目录、ZIP、分卷、验证副本和历史包,多处重复运行时 | -| `dependencies/` | 23.952 GiB | 私有 SDK/运行时;普通清理不得整目录删除 | -| `.local-pr6-0.5.8/` | 10.714 GiB | 干净的 detached Git worktree,任务结束后可移除 | -| `source/` | 7.833 GiB | 当前仓库;绝大部分是被忽略的构建/发布产物 | -| `.review-pr4/` | 5.717 GiB | 干净的 detached Git worktree,任务结束后可移除 | -| `archive/` | 4.463 GiB | 历史源码快照,不参与当前构建 | - -当前 `source/` 内可重建内容约为:`obj/` 3.367 GiB、`bin/` 2.341 GiB、`publish/` 1.955 GiB、`packages/` 0.099 GiB,另有 `scripts/__pycache__/`、嵌套 `obj/` 和 `directml-smoke.obj`。 - -深度估算相关依赖的主要候选为: - -- `dependencies/runtime-cache-pr6/`:19.573 GiB,其中包含多份 TensorRT/CUDA/cuDNN 展开目录与 probe 布局。 -- `dependencies/onnxruntime/`:1.532 GiB。 -- `dependencies/python-onnx/`:0.086 GiB。 -- `dependencies/DepthAnythingV2/`:0.046 GiB。 -- `release/` 中存在多份 `FrameGuidance/DepthAnythingV2`、`FrameGuidance/DirectML`、`FrameGuidance/TensorRT`、完整展开运行时、ZIP 和 `.7z.001/.002`。 - -### 深度估算调用链 - -- 推理实现:`DepthAnythingV2Provider.*`、`DepthInferenceBackend.*`。 -- 服务入口:`Renderer.cpp` 根据 `FrameGuidanceRequirements.depth` 创建 DAV2 Provider。 -- 公共合同:`FrameGuidanceTypes.h`、`FrameGuidanceProvider.h`、`FrameGuidanceService.*`。 -- 消费者:`DLSSNRFilter.*`、`DLSSSRUpscaler.*`、`DLSSFrameGenerator.*`。 -- 诊断效果:`FrameGuidance_Depth.hlsl`、`FrameGuidance_DepthResidual.hlsl`,以及 `FrameGuidanceDiagnostics.*` 中的对应分支。 -- 构建/打包:`BuildOptions.props`、`Common.Post.props`、两个 `.vcxproj/.filters`、`Build-Release.ps1`。 -- 文档/发布:README、当前发布说明、Frame Guidance 测试矩阵、第三方再分发说明和旧 TODO。 - -### 与深度估算相关的暴露参数 - -| 效果 | 当前参数 | r1 处理 | -| --- | --- | --- | -| `DLSS\DLSS_SR` | `useEstimatedDepth` | 删除;SR 始终绑定合法 Zero Depth | -| `DLSSFG\DLSS_FrameGeneration` | `useEstimatedDepth` | 删除;FG 始终绑定合法 Zero Depth,`depthInverted=false` | -| `DLSSNR\DLSSNR_AI_Filter` | `depthInferenceInterval` | 删除 | -| `DLSSNR\DLSSNR_AI_Filter` | `guidanceMode`:Available / Zero / Motion / Depth | 改为布尔 `useMotionVectors`,默认开 | -| `Diagnostics\FrameGuidance_Depth` | `gain`、`invert`、`percentileClip` | 随整个诊断效果删除 | -| `Diagnostics\FrameGuidance_DepthResidual` | `gain` | 随整个诊断效果删除 | - -`FrameGuidance_Confidence` 虽然没有参数,但它属于 Motion 的质量诊断,应保留。 - -## A. 仓库与工作区清理 - -### A1. 可直接重建的本地产物 - -- [x] 关闭 Magpie、Visual Studio、MSBuild 和相关诊断进程,确认没有文件句柄占用。 -- [x] 清理 `source/obj/`、`source/bin/`、`source/publish/`、`source/packages/`。 -- [x] 清理 `source/scripts/__pycache__/`、`source/src/Magpie/obj/`、`source/directml-smoke.obj`。 -- [x] 清理后执行 `git status --short --ignored`,确认只移除了被忽略的生成物。 -- [x] 完成一次依赖恢复和 Release x64 全量构建,证明以上目录均可重建。 - -### A2. 需确认后清理的工作区内容 - -- [x] 确认 PR6/PR4 审核 worktree 均干净且提交仍有分支/标签引用,再使用 `git worktree remove` 移除 `.local-pr6-0.5.8/` 与 `.review-pr4/`;残留的失效 `packages` junction 已单独移除。 -- [ ] 为 `release/` 生成“版本/用途/是否已发布/是否已有远端副本/SHA-256”清单。 -- [x] 优先移除 `release/validation/`、`release/v0.6.1-local/`、`release/pr4-test-*`、`release/issue-8-color-fix/` 等可重建或一次性验证副本;保留项只留 ZIP 或只留展开目录,避免双份。 -- [ ] 单独确认 `release/archive/` 和顶层 `archive/Magpie-dev/` 是否仍承担离线备份;若远端提交、标签和发布资产完整,再决定归档到外部存储或删除。 -- [x] 深度功能从源码和新包移除并验证后,删除不再被其他项目使用的 DAV2/ONNX/TensorRT 深度目录;`dependencies/` 中 DLSS、NVOF、FSR、XeSS、RTX Video 等仍在使用的 SDK 不动。 -- [ ] 删除前记录磁盘实际占用而不只记录逻辑大小,避免硬链接导致回收量误判。 - -清理记录(2026-09-04):移除了当前源码树可重建的 `obj/packages`、四组 local/test/validation 发布目录、七份已有压缩或远端副本的展开发布目录、一个 premerge candidate、两份干净 detached worktree、四个已失效的深度依赖/cache 目录、历史源码快照中的 `bin/obj/packages`,以及可由 ZIP 恢复的展开崩溃转储等零散临时文件。按删除前目录逻辑大小合计约 82.2 GB;硬链接会使实际释放量略低。当前正在运行的 v0.6.5 展开目录及 `source/bin` 未清理。 - -### A3. 版本库内候选文件 - -- [x] 删除无任何跟踪引用的一次性说明 `docs/MINIMAL_UPDATE_v0.5.8.txt`,历史标签仍可恢复该文件。 -- [ ] 检查 `img/` 四张图片的 GitHub README、Wiki、社交卡片和外部链接用途;当前跟踪文本中无文件名引用,确认无外部用途后再删除。 -- [x] `presets/ScalingModes-v0.5.7-experimental.json` 含已废弃深度参数:不要原地篡改历史语义;决定是移到历史资料区,还是新增干净的 v0.6.5 导入预设并从当前 README 改链。 -- [x] `docs/RELEASE_NOTES_NEXT.md` 已停留在“v0.6.1 未发布”,应重置为 v0.6.5 开发说明,而不是删除。 -- [x] `docs/experimental/README.md` 仍把已发布版本写成候选,并把多个深度 TODO 列为当前工作;更新索引、标记被 v0.6.5 取代的项目。 -- [x] 旧 Release Notes 作为历史记录保留,不因当前移除深度功能而回写历史内容。 -- [x] 未完成但已被本清单取代的 Frame Guidance/DLSSNR TODO 增加“superseded by v0.6.5”说明并归档;不要伪装成全部完成。 -- [x] 完成全仓相同内容检查。现有少量完全重复文件多为项目局部 manifest、lock、pch 或效果资源,收益很小,除非能证明构建与打包路径支持共享,否则暂不合并。 - -## B. 深度估算移除 - -### B1. 先锁定保留合同 - -- [x] 保留 `R32_FLOAT` Zero Depth 资源及其尺寸、有效区、frameId、fence 和 reset 元数据。 -- [x] DLSS SR、DLSSNR 和 DLSS FG 的 Evaluate 都继续收到非空、尺寸正确的 Depth 纹理。 -- [x] 保留跨 D3D11/D3D12 的 Zero Depth 共享/状态转换,因为 DLSSNR/FG 仍通过 D3D12 消费该资源。 -- [x] 不再声明 Zero Depth 是 `RelativeInverse` 的真实估算结果;删除仅用于 DAV2 的语义字段和校验,FG 使用 `depthInverted=false`。 -- [x] Motion 关闭时仍传 Zero Motion;Motion 开启时可传 NVOF Motion,并始终搭配 Zero Depth。 - -### B2. 删除推理实现与依赖 - -- [x] 删除 `src/Magpie.Core/DepthAnythingV2Provider.h/.cpp`。 -- [x] 删除 `src/Magpie.Core/DepthInferenceBackend.h/.cpp`。 -- [x] 从 `Magpie.Core.vcxproj` 与 `.filters` 移除对应条目。 -- [x] 从 `Renderer.cpp` 移除 DAV2 include、`MP_ENABLE_DEPTH_ANYTHING_V2` 分支和 Provider 创建。 -- [x] 从 `BuildOptions.props` 删除 `EnableDepthAnythingV2`、模型、ONNX Runtime、DirectML、TensorRT 路径属性。 -- [x] 从 `Common.Post.props` 删除深度宏和 ONNX Runtime include 路径。 -- [x] 从 `Magpie.vcxproj` 删除条件 D3D12 链接补丁及 `CopyFrameGuidanceDepthRuntime` target;先确认 D3D12.lib 是否由其他 NGX 路径提供,避免误删公共链接依赖。 -- [x] 从 `Build-Release.ps1` 删除 `ExcludeTensorRTDepthRuntime` 选项和仅服务深度组件的过滤逻辑。 - -### B3. 收缩 Frame Guidance 数据模型 - -- [x] `FrameGuidanceRequirements` 删除 `depth`、`depthInferenceInterval` 及 Merge 逻辑,只保留 `zero`、`motion`。 -- [x] `FrameGuidanceView` 保留 SDK 必需的 `depth`,删除 `rawDepth`、`depthResidual`、`depthConvention`、`depthInverted`。 -- [x] `FrameGuidanceFrame` 删除仅供 DAV2 时域重投影使用的 `motionGuidance`。 -- [x] `FrameGuidanceService` 删除 `SetDepthProvider`、动态 `_depthProvider`、ready/fallback 分支;每帧只生成 Zero Depth 与可选真实 Motion。 -- [x] 删除 `IDepthProvider` 多态接口;`ZeroDepthProvider` 可保留为非多态的小型 Zero Depth 生成器,避免把“Zero Depth 合同”误删成“无 Depth”。 -- [x] `DepthProviderOutput` 若只剩一个字段,可保留为清晰边界,也可在不扩大改动面的前提下内联;以代码可读性和最小风险为准。 -- [x] 适配不同消费者尺寸时仍同步适配/创建 Zero Depth;不要只适配 Motion,导致 SR/NR/FG 的深度尺寸不匹配。 -- [x] 简化日志:移除 `depthAction`、`depthInterval`、真实/回退 Depth 统计,只报告 Motion 与固定 Zero Depth 合同。 - -### B4. 修改消费者 - -- [x] `DLSSSRSettings` 删除 `useEstimatedDepth`;创建 flags 不再设置 `DepthInverted`,绘制始终选择 Zero Depth。 -- [x] `DLSSFrameGenerationSettings` 删除 `useEstimatedDepth`;guidance extent 只由 Motion 请求决定;日志与 binding 位删除 Depth 请求位。 -- [x] DLSS FG 的 `optionalParams.depthInverted=false`,Depth 区域始终来自合法 Zero Depth。 -- [x] `DLSSNRSettings` 删除 `depthInferenceInterval`,把四态 `guidanceMode` 改为 `useMotionVectors`。 -- [x] DLSSNR 的 guidance 选择只保留 Motion/Zero 两种,Depth 永远为 Zero。 -- [x] 检查三个消费者的 history reset:Motion availability 或尺寸变化仍需 reset,已不存在的 Depth 状态变化不得再触发 reset。 - -### B5. 删除深度诊断 - -- [x] 删除 `FrameGuidance_Depth.hlsl`、`FrameGuidance_DepthResidual.hlsl` 及 `Effects.vcxproj` 条目。 -- [x] `FrameGuidanceDiagnosticKind`、工厂分派和诊断 shader 删除 Depth/DepthResidual 分支;保留 Motion/Confidence。 -- [x] 对旧配置中的两个诊断效果定义明确迁移:从效果链移除;若链因此为空,插入安全的 `Bilinear`/`Fit` 回退并记录一次日志,避免留下 Unknown Effect 或空效果链。 - -### B6. 旧配置与导入迁移 - -- [x] 新增一次性 v0.6.5 深度移除迁移版本,并在保存时写回版本号。 -- [x] `DLSS\DLSS_SR` 与 `DLSSFG\DLSS_FrameGeneration`:擦除 `useEstimatedDepth`。 -- [x] `DLSSNR\DLSSNR_AI_Filter`:旧 `guidanceMode` 为 0(Available)或 2(Motion Only)时迁移为 `useMotionVectors=1`;旧值 1(Force Zero)或 3(Depth Only)迁移为 `useMotionVectors=0`。 -- [x] 迁移后擦除 `guidanceMode` 与 `depthInferenceInterval`,保留所有与 NR 画质、输入分辨率和 Residual 有关的参数。 -- [x] 把迁移/清理做成可复用 normalization,同时覆盖启动加载和用户稍后执行的 Import;不能只处理启动时已有配置。 -- [ ] 更新或替换 `ScalingModes-v0.5.7-experimental.json`,并用含四种旧 guidance 值、旧深度诊断和未知自定义参数的样本验证幂等性。 - -### B7. 文档与发布清理 - -- [x] README/README_ZH 删除“Use Estimated Depth”、Depth Interval 和 Motion/Depth 能力描述,明确只有颜色估算 Motion,Depth 固定为 Zero。 -- [x] 更新 `README-EXPERIMENTAL-RELEASE.txt`、`EXPERIMENTAL_HANDOFF_ZH.md` 和 v0.6.5 Release Notes。 -- [x] `THIRD_PARTY_AND_REDISTRIBUTION.md` 删除当前产品对 DAV2、ONNX Runtime、TensorRT/CUDA/cuDNN 深度组件的分发要求;历史说明不回写。 -- [x] `experimental/RELEASE-WORKFLOW.md` 删除未来版本的 TensorRT 深度分卷流程。 -- [x] Frame Guidance 测试矩阵改为 Zero/Motion 两态,删除 Raw Depth、Depth Residual 和 Depth-only 场景。 -- [x] 全仓搜索 `DepthAnything`、`DepthInference`、`useEstimatedDepth`、`depthInferenceInterval`、`guidanceMode`、`rawDepth`、`depthResidual`、`TensorRT-Depth`,逐项确认剩余命中仅为明确保留的历史文档和配置迁移代码。 - -## C. 暴露参数多列分组 - -### C1. 元数据合同 - -- [x] 为 `//!PARAMETER` 增加可选的 `//!GROUP