diff --git a/OptiScaler.ini b/OptiScaler.ini index b23116311..d309f14d4 100644 --- a/OptiScaler.ini +++ b/OptiScaler.ini @@ -1561,6 +1561,10 @@ OutputResourceBarrier=auto ; true or false - Default (auto) is false Enabled=auto +; Apply DLSS NR before SR takes place on the immediate frame DLSS SR receives, instead of afterwards on the upscaled frame +; true or false - Default (auto) is false +RunBeforeSR=auto + ; How far the frame moves toward the model's picture. Its answer is not added to the frame: it is a ; complete picture, rescaled so its luminance sits where the original says it should, and this blends ; between the two. 0 gives back exactly what the upscaler produced, 1 is the model's picture, and above diff --git a/OptiScaler/Config.cpp b/OptiScaler/Config.cpp index 77f5bce5c..ea011f31e 100644 --- a/OptiScaler/Config.cpp +++ b/OptiScaler/Config.cpp @@ -317,6 +317,7 @@ bool Config::Reload(std::filesystem::path iniPath) // --- DLSS 5 Neural Rendering (OptiScaler/dlssnr) --- DlssNrEnabled.set_from_config(readBool("DlssNr", "Enabled")); + DlssNrRunBeforeSr.set_from_config(readBool("DlssNr", "RunBeforeSR")); DlssNrToggleKey.set_from_config(readInt("DlssNr", "ToggleKey")); DlssNrTransferStrength.set_from_config(readFloat("DlssNr", "TransferStrength")); DlssNrColourStrength.set_from_config(readFloat("DlssNr", "ColourStrength")); @@ -1168,6 +1169,7 @@ bool Config::SaveIni() // --- DLSS 5 Neural Rendering (OptiScaler/dlssnr) --- ini.SetValue("DlssNr", "Enabled", GetBoolValue(Instance()->DlssNrEnabled.value_for_config()).c_str()); + ini.SetValue("DlssNr", "RunBeforeSR", GetBoolValue(Instance()->DlssNrRunBeforeSr.value_for_config()).c_str()); { auto toggle = Instance()->DlssNrToggleKey.value_for_config(); ini.SetValue("DlssNr", "ToggleKey", GetIntValue(toggle, toggle > 0).c_str()); diff --git a/OptiScaler/Config.h b/OptiScaler/Config.h index 0006faf0b..cf251415c 100644 --- a/OptiScaler/Config.h +++ b/OptiScaler/Config.h @@ -257,6 +257,8 @@ class Config // DLSS Neural Rendering: a detail-synthesis pass over the upscaler's output. Off by default -- it is // an undocumented feature driven directly through its snippet, not something NVIDIA exposes. CustomOptional DlssNrEnabled { false }; + // Applies DLSS NR before SR takes place on the immediate frame DLSS SR receives, instead of afterwards on the upscaled frame. + CustomOptional DlssNrRunBeforeSr { false }; // Toggles the pass in game. Unbound by default -- a key that does something unexpected is worse // than one that does nothing. CustomOptional DlssNrToggleKey { UnboundKey }; diff --git a/OptiScaler/dlssnr/DlssNrFeature_Dx12.cpp b/OptiScaler/dlssnr/DlssNrFeature_Dx12.cpp index 568be4833..25e56de35 100644 --- a/OptiScaler/dlssnr/DlssNrFeature_Dx12.cpp +++ b/OptiScaler/dlssnr/DlssNrFeature_Dx12.cpp @@ -763,8 +763,8 @@ void ReportSkipOnce(const char* reason) LOG_INFO("DLSS-NR did not run: {}", reason); } -void EvaluateAfterUpscale(ID3D12GraphicsCommandList* cmdList, NVSDK_NGX_Parameter* params, - ID3D12CommandQueue* timingQueue) +void EvaluateInternal(ID3D12GraphicsCommandList* cmdList, NVSDK_NGX_Parameter* params, bool beforeUpscale, + ID3D12CommandQueue* timingQueue) { std::lock_guard nrLock(g_nrMutex); const Config& cfg = *Config::Instance(); @@ -782,7 +782,16 @@ void EvaluateAfterUpscale(ID3D12GraphicsCommandList* cmdList, NVSDK_NGX_Paramete return; } - ID3D12Resource* target = GetResource(params, NVSDK_NGX_Parameter_Output, "DLSSD.Output"); + if (cfg.DlssNrRunBeforeSr.value_or_default() != beforeUpscale) + return; + + // Color and Output can use different surface formats even though DLSS treats them as the same + // frame color space. Use Output as the shared authority so switching the injection point does not + // silently change the HDR/sRGB branch just because the selected target has a different format. + ID3D12Resource* output = GetResource(params, NVSDK_NGX_Parameter_Output, "DLSSD.Output"); + ID3D12Resource* target = beforeUpscale + ? GetResource(params, NVSDK_NGX_Parameter_Color, "DLSSD.Color") + : GetResource(params, NVSDK_NGX_Parameter_Output, "DLSSD.Output"); ID3D12Resource* depth = GetResource(params, NVSDK_NGX_Parameter_Depth, "DLSSD.Depth"); ID3D12Resource* motion = GetResource(params, NVSDK_NGX_Parameter_MotionVectors, "DLSSD.MotionVectors"); @@ -790,7 +799,8 @@ void EvaluateAfterUpscale(ID3D12GraphicsCommandList* cmdList, NVSDK_NGX_Paramete // carry none of it -- so it stays quiet and tries again next frame. if (target == nullptr || depth == nullptr || motion == nullptr) { - ReportSkipOnce(target == nullptr ? "the parameters carried no output texture" + ReportSkipOnce(target == nullptr ? (beforeUpscale ? "the parameters carried no color texture" + : "the parameters carried no output texture") : depth == nullptr ? "the parameters carried no depth" : "the parameters carried no motion vectors"); return; @@ -800,27 +810,33 @@ void EvaluateAfterUpscale(ID3D12GraphicsCommandList* cmdList, NVSDK_NGX_Paramete if (FAILED(target->GetDevice(IID_PPV_ARGS(&device))) || device == nullptr) { - ReportSkipOnce("the output texture belongs to no D3D12 device"); + ReportSkipOnce("the target texture belongs to no D3D12 device"); return; } const D3D12_RESOURCE_DESC desc = target->GetDesc(); const auto width = (unsigned int) desc.Width; const auto height = desc.Height; + const bool targetSupportsUav = (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS) != 0; - // Depth and motion vectors are the upscaler's inputs and so are at render resolution, while colour - // and output are at display resolution. The model takes that as a subrect per resource rather than - // needing them resampled, which is why nothing here rescales anything. unsigned int guideWidth = 0; unsigned int guideHeight = 0; - params->Get(NVSDK_NGX_Parameter_Width, &guideWidth); - params->Get(NVSDK_NGX_Parameter_Height, &guideHeight); - - if (guideWidth == 0 || guideHeight == 0) + if (beforeUpscale) { guideWidth = width; guideHeight = height; } + else + { + params->Get(NVSDK_NGX_Parameter_Width, &guideWidth); + params->Get(NVSDK_NGX_Parameter_Height, &guideHeight); + + if (guideWidth == 0 || guideHeight == 0) + { + guideWidth = width; + guideHeight = height; + } + } // The game states its depth convention in the flags it created its own feature with, so there is no // reason to assume one. @@ -1004,9 +1020,10 @@ void EvaluateAfterUpscale(ID3D12GraphicsCommandList* cmdList, NVSDK_NGX_Paramete unsigned int dlssFlags = 0; params->Get(NVSDK_NGX_Parameter_DLSS_Feature_Create_Flags, &dlssFlags); // Both have to agree: the flag says what the game intends, the format says what the surface can - // actually hold. + // actually hold. Output is used for both injection points so the color-space decision is stable. const bool gameSaysHdr = (dlssFlags & NVSDK_NGX_DLSS_Feature_Flags_IsHDR) != 0; - const bool isHdrBuffer = gameSaysHdr && FormatCanHoldLinearHdr(desc.Format); + const bool isHdrBuffer = gameSaysHdr && FormatCanHoldLinearHdr(output != nullptr ? output->GetDesc().Format + : desc.Format); static bool reportedHdr = false; @@ -1079,19 +1096,25 @@ void EvaluateAfterUpscale(ID3D12GraphicsCommandList* cmdList, NVSDK_NGX_Paramete encodeParams.Width = width; encodeParams.Height = height; - Barrier(cmdList, target, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, - D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + if (!beforeUpscale) + Barrier(cmdList, target, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + g_compose->Dispatch(cmdList, encodeParams, target, nullptr, nullptr, nullptr, nullptr, g_nr.colorCopy, g_nr.hdrCopy); - Barrier(cmdList, target, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, - D3D12_RESOURCE_STATE_UNORDERED_ACCESS); // The transitions double as the wait for the encode's writes. Barrier(cmdList, g_nr.colorCopy, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); Barrier(cmdList, g_nr.hdrCopy, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + // Preserve the state the post-upscale path used to expose to its caller until resolve completes. + // A Color input is already readable and remains so until the pre-SR resolve writes it. + if (!beforeUpscale && targetSupportsUav) + Barrier(cmdList, target, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + // Below full resolution the model is shown a filtered shrink of the proxy; the edit it returns is // enlarged during the resolve while the frame underneath stays full size and untouched. ID3D12Resource* modelInput = g_nr.colorCopy; @@ -1226,10 +1249,37 @@ void EvaluateAfterUpscale(ID3D12GraphicsCommandList* cmdList, NVSDK_NGX_Paramete Barrier(cmdList, g_nr.output, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); - g_compose->Dispatch(cmdList, resolveParams, modelInput, g_nr.output, g_nr.hdrCopy, motionIn, - nullptr, target, nullptr); - Barrier(cmdList, g_nr.output, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, - D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + + ID3D12Resource* resolveOriginal = targetSupportsUav ? g_nr.hdrCopy : target; + ID3D12Resource* resolveTarget = targetSupportsUav ? target : g_nr.hdrCopy; + + if (beforeUpscale && targetSupportsUav) + Barrier(cmdList, target, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + else if (!targetSupportsUav) + Barrier(cmdList, g_nr.hdrCopy, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + + g_compose->Dispatch(cmdList, resolveParams, modelInput, g_nr.output, resolveOriginal, motionIn, + nullptr, resolveTarget, nullptr); + + if (targetSupportsUav) + { + Barrier(cmdList, target, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + } + else + { + Barrier(cmdList, g_nr.hdrCopy, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + D3D12_RESOURCE_STATE_COPY_SOURCE); + Barrier(cmdList, target, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, + D3D12_RESOURCE_STATE_COPY_DEST); + cmdList->CopyResource(target, g_nr.hdrCopy); + Barrier(cmdList, target, D3D12_RESOURCE_STATE_COPY_DEST, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + Barrier(cmdList, g_nr.hdrCopy, D3D12_RESOURCE_STATE_COPY_SOURCE, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + } // On-demand capture works in this path too: the staging copy still holds the frame as the // upscaler produced it, and the edited frame is the output itself. The write happens a few @@ -1239,7 +1289,7 @@ void EvaluateAfterUpscale(ID3D12GraphicsCommandList* cmdList, NVSDK_NGX_Paramete { g_capture.record(cmdList, device, g_nr.colorCopy, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, target, - D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); if (g_capture.readyToWrite() && g_captureWriteAtFrame == 0) g_captureWriteAtFrame = g_frames + 8; @@ -1295,6 +1345,18 @@ void EvaluateAfterUpscale(ID3D12GraphicsCommandList* cmdList, NVSDK_NGX_Paramete device->Release(); } +void EvaluateAfterUpscale(ID3D12GraphicsCommandList* cmdList, NVSDK_NGX_Parameter* params, + ID3D12CommandQueue* timingQueue) +{ + EvaluateInternal(cmdList, params, false, timingQueue); +} + +void EvaluateBeforeUpscale(ID3D12GraphicsCommandList* cmdList, NVSDK_NGX_Parameter* params, + ID3D12CommandQueue* timingQueue) +{ + EvaluateInternal(cmdList, params, true, timingQueue); +} + bool IsRunning() { return g_nr.feature != nullptr && !g_nr.failed; } const char* FailureReason() { return g_nr.failed ? g_nr.reason : ""; } diff --git a/OptiScaler/dlssnr/DlssNrFeature_Dx12.h b/OptiScaler/dlssnr/DlssNrFeature_Dx12.h index 81a1456e6..8373413e5 100644 --- a/OptiScaler/dlssnr/DlssNrFeature_Dx12.h +++ b/OptiScaler/dlssnr/DlssNrFeature_Dx12.h @@ -31,6 +31,10 @@ namespace DlssNr void EvaluateAfterUpscale(ID3D12GraphicsCommandList* cmdList, NVSDK_NGX_Parameter* params, ID3D12CommandQueue* timingQueue = nullptr); +// Runs the model over Color on the same command list, immediately before the upscaler consumes it. +void EvaluateBeforeUpscale(ID3D12GraphicsCommandList* cmdList, NVSDK_NGX_Parameter* params, + ID3D12CommandQueue* timingQueue = nullptr); + // Frame generation titles tag their UI layer through Streamline; a copy of it makes the HUD mask // exact at the finished frame. Called at tag time. diff --git a/OptiScaler/dlssnr/DlssNr_Menu.cpp b/OptiScaler/dlssnr/DlssNr_Menu.cpp index 54d910ee7..6a0edafa5 100644 --- a/OptiScaler/dlssnr/DlssNr_Menu.cpp +++ b/OptiScaler/dlssnr/DlssNr_Menu.cpp @@ -41,6 +41,15 @@ void RenderMenu(Config* config, float menuResScale) if (ImGui::Checkbox("Enable Neural Rendering", &enabled)) config->DlssNrEnabled = enabled; + bool beforeSr = config->DlssNrRunBeforeSr.value_or_default(); + if (ImGui::Checkbox("Apply before SR", &beforeSr)) + config->DlssNrRunBeforeSr = beforeSr; + + HelpMarker("Applies Neural Rendering to the input frame immediately before DLSS Super Resolution " + "\nruns (at render resolution), instead of afterwards on the upscaled frame." + "\n\nAllows synthesised detail to pass through DLSS SR's temporal accumulation, and " + "\ncosts render-resolution compute instead of display-resolution compute."); + HelpMarker("Synthesises detail in the upscaler's output, before frame generation sees it." "\n\nNeeds two similarly named files beside OptiScaler, one character apart:" "\n nvngx_dlssnr.dll NVIDIA's model (~165 MB) -- you supply it" diff --git a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp index a0c4ed2b3..9147cf019 100644 --- a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp +++ b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp @@ -1149,6 +1149,9 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_D3D12_EvaluateFeature(ID3D12GraphicsCom { LOG_DEBUG("Passthrough to native DLSS EvaluateFeature for handle {}", handleId); + if (feature != NVSDK_NGX_Feature_FrameGeneration) + DlssNr::EvaluateBeforeUpscale(InCmdList, InParameters); + NVSDK_NGX_Result result = NVNGXProxy::D3D12_EvaluateFeature()(InCmdList, InFeatureHandle, InParameters, InCallback); LOG_DEBUG("Native DLSS EvaluateFeature result: 0x{:X}", (uint32_t) result); @@ -1181,6 +1184,9 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_D3D12_EvaluateFeature(ID3D12GraphicsCom if (lastDlssgCameraFar.has_value()) InParameters->Set("DLSSG.CameraFar", lastDlssgCameraFar.value()); + if (feature != NVSDK_NGX_Feature_FrameGeneration) + DlssNr::EvaluateBeforeUpscale(InCmdList, InParameters); + // OptiScaler internal handling const NVSDK_NGX_Result optiResult = TryEvaluateOptiFeature(InCmdList, InFeatureHandle, InParameters, InCallback); diff --git a/OptiScaler/upscalers/IFeature_Dx11wDx12.cpp b/OptiScaler/upscalers/IFeature_Dx11wDx12.cpp index 8c81090e5..01e64e108 100644 --- a/OptiScaler/upscalers/IFeature_Dx11wDx12.cpp +++ b/OptiScaler/upscalers/IFeature_Dx11wDx12.cpp @@ -402,6 +402,7 @@ bool IFeature_Dx11wDx12::Evaluate(ID3D11DeviceContext* InDeviceContext, NVSDK_NG (void*) dx11Reactive.Dx12Resource); LOG_DEBUG("Dispatch!!"); + DlssNr::EvaluateBeforeUpscale(cmdList, InParameters, Dx12CommandQueue); dx12EvalResult = dx12Feature->Evaluate(cmdList, InParameters); // DLSS 5 Neural Rendering rides the bridge: at this moment the block carries the D3D12 copies diff --git a/OptiScaler/upscalers/IFeature_VkwDx12.cpp b/OptiScaler/upscalers/IFeature_VkwDx12.cpp index c4c40f3ef..9b2081d5a 100644 --- a/OptiScaler/upscalers/IFeature_VkwDx12.cpp +++ b/OptiScaler/upscalers/IFeature_VkwDx12.cpp @@ -2095,6 +2095,7 @@ bool IFeature_VkwDx12::Evaluate(VkCommandBuffer InCmdBuffer, NVSDK_NGX_Parameter InParameters->Set(NVSDK_NGX_Parameter_DLSS_Input_Bias_Current_Color_Mask, (void*) vkReactive.Dx12Resource); LOG_DEBUG("Dispatch!!"); + DlssNr::EvaluateBeforeUpscale(cmdList, InParameters, Dx12CommandQueue); dx12EvalResult = dx12Feature->Evaluate(cmdList, InParameters); // The parameter block still holds the D3D12 resources written above -- the Vulkan handles are