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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions OptiScaler.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions OptiScaler/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down Expand Up @@ -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());
Expand Down
2 changes: 2 additions & 0 deletions OptiScaler/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool> DlssNrEnabled { false };
// Applies DLSS NR before SR takes place on the immediate frame DLSS SR receives, instead of afterwards on the upscaled frame.
CustomOptional<bool> DlssNrRunBeforeSr { false };
// Toggles the pass in game. Unbound by default -- a key that does something unexpected is worse
// than one that does nothing.
CustomOptional<int> DlssNrToggleKey { UnboundKey };
Expand Down
108 changes: 85 additions & 23 deletions OptiScaler/dlssnr/DlssNrFeature_Dx12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::mutex> nrLock(g_nrMutex);
const Config& cfg = *Config::Instance();
Expand All @@ -782,15 +782,25 @@ 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");

// Without all three there is nothing to run on. This is not a failure -- some evaluates legitimately
// 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;
Expand All @@ -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.
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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 : ""; }
Expand Down
4 changes: 4 additions & 0 deletions OptiScaler/dlssnr/DlssNrFeature_Dx12.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions OptiScaler/dlssnr/DlssNr_Menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 6 additions & 0 deletions OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down
1 change: 1 addition & 0 deletions OptiScaler/upscalers/IFeature_Dx11wDx12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions OptiScaler/upscalers/IFeature_VkwDx12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down