From 3c14c3eb43d3e13cc7aa3b26da6158fb3bd477ac Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 13 Apr 2026 21:05:36 -0700 Subject: [PATCH 1/2] perf(VR): reduce copy operations --- .../Shaders/Upscaling/EncodeTexturesCS.hlsl | 36 +++- src/Features/Upscaling.cpp | 177 +++++++++++++----- src/Features/Upscaling.h | 22 ++- src/Features/Upscaling/FidelityFX.cpp | 4 +- src/Features/Upscaling/Streamline.cpp | 2 +- 5 files changed, 173 insertions(+), 68 deletions(-) diff --git a/features/Upscaling/Shaders/Upscaling/EncodeTexturesCS.hlsl b/features/Upscaling/Shaders/Upscaling/EncodeTexturesCS.hlsl index 63870d0a79..34ea8250a8 100644 --- a/features/Upscaling/Shaders/Upscaling/EncodeTexturesCS.hlsl +++ b/features/Upscaling/Shaders/Upscaling/EncodeTexturesCS.hlsl @@ -2,8 +2,9 @@ cbuffer UpscalingData : register(b0) { - float2 TrueSamplingDim; // BufferDim.xy * ResolutionScale - float2 pad0; + float2 TrueSamplingDim; // per-eye render dim in VR, full render dim otherwise + uint EyeOffsetX; // X offset into stereo source buffers; 0 for non-VR / left eye + uint pad0; }; Texture2D TAAMask : register(t0); @@ -14,21 +15,27 @@ Texture2D DepthMask : register(t3); RWTexture2D ReactiveMask : register(u0); RWTexture2D TransparencyCompositionMask : register(u1); RWTexture2D MotionVectorOutput : register(u2); +#if defined(DEPTH_OUTPUT) +RWTexture2D DepthOutput : register(u3); +#endif [numthreads(8, 8, 1)] void main(uint3 dispatchID : SV_DispatchThreadID) { - // Early exit if dispatch thread is outside true sampling dimensions + // Bounds check in per-eye space; EyeOffsetX=0 makes this identical to the old path for non-VR if (any(dispatchID.xy >= uint2(TrueSamplingDim))) return; - float2 taaMask = TAAMask[dispatchID.xy]; - float transparencyCompositionMask = NormalsWaterMask[dispatchID.xy].z; + // All source reads are in full stereo space; outputs are 0-based (per-eye or full-frame) + uint2 srcCoord = dispatchID.xy + uint2(EyeOffsetX, 0); + + float2 taaMask = TAAMask[srcCoord]; + float transparencyCompositionMask = NormalsWaterMask[srcCoord].z; #if defined(DLSS) - float depth = DepthMask[dispatchID.xy]; + float depth = DepthMask[srcCoord]; float nearFactor = smoothstep(4096.0 * 2.5, 0.0, SharedData::GetScreenDepth(depth)); // Find longest motion vector in 5x5 neighborhood - float2 motionVector = MotionVectorMask[dispatchID.xy]; + float2 motionVector = MotionVectorMask[srcCoord]; float2 longestMotionVector = motionVector; float maxMotionLengthSq = dot(motionVector, motionVector); @@ -38,15 +45,18 @@ RWTexture2D MotionVectorOutput : register(u2); { int2 samplePos = int2(dispatchID.xy) + int2(x, y); - // Skip samples outside true sampling dimensions + // Bounds check stays in per-eye space — prevents cross-eye contamination in VR + // and out-of-bounds reads in non-VR (EyeOffsetX=0 makes these equivalent) if (any(samplePos < 0) || any(samplePos >= int2(TrueSamplingDim))) continue; - float neighborDepth = DepthMask[samplePos]; + // Source read uses full stereo offset + int2 srcPos = samplePos + int2(EyeOffsetX, 0); + float neighborDepth = DepthMask[srcPos]; // Take neighbor if it's longer AND closer if (neighborDepth < depth) { - float2 neighborMotionVector = MotionVectorMask[samplePos]; + float2 neighborMotionVector = MotionVectorMask[srcPos]; // Square motion vector for length float motionLengthSq = dot(neighborMotionVector, neighborMotionVector); @@ -62,6 +72,12 @@ RWTexture2D MotionVectorOutput : register(u2); MotionVectorOutput[dispatchID.xy] = lerp(longestMotionVector, motionVector, nearFactor); #endif +#if defined(DEPTH_OUTPUT) + // Copy depth as R32_FLOAT so FSR DX11 backend receives a typed format. + // The raw depth resource is R24G8_TYPELESS in VR which maps to FFX_SURFACE_FORMAT_UNKNOWN. + DepthOutput[dispatchID.xy] = DepthMask[srcCoord]; +#endif + float reactiveMask = taaMask.x * 0.1 + taaMask.y; ReactiveMask[dispatchID.xy] = reactiveMask; diff --git a/src/Features/Upscaling.cpp b/src/Features/Upscaling.cpp index 2f0c90310b..243fb51760 100644 --- a/src/Features/Upscaling.cpp +++ b/src/Features/Upscaling.cpp @@ -791,6 +791,7 @@ void Upscaling::CheckResources(UpscaleMethod a_upscalemethod) vrIntermediateColorIn[i].reset(); vrIntermediateColorOut[i].reset(); vrIntermediateDepth[i].reset(); + vrIntermediateLinearDepth[i].reset(); vrIntermediateMotionVectors[i].reset(); vrIntermediateReactiveMask[i].reset(); vrIntermediateTransparencyMask[i].reset(); @@ -818,6 +819,20 @@ ID3D11ComputeShader* Upscaling::GetEncodeTexturesCS() auto upscaleMethod = GetUpscaleMethod(); uint methodIndex = (uint)upscaleMethod; + // VR FSR needs a separate variant: DEPTH_OUTPUT converts the R24G8_TYPELESS game depth to + // R32_FLOAT so GetFfxResourceDescriptionDX11() returns a valid format instead of UNKNOWN. + if (globals::game::isVR && upscaleMethod == UpscaleMethod::kFSR) { + if (!encodeTexturesCSDepthOutput) { + logger::debug("Compiling EncodeTexturesCS.hlsl for VR FSR (FSR + DEPTH_OUTPUT)"); + std::vector> defines = { + { "FSR", "" }, + { "DEPTH_OUTPUT", "" } + }; + encodeTexturesCSDepthOutput.attach((ID3D11ComputeShader*)Util::CompileShader(L"Data/Shaders/Upscaling/EncodeTexturesCS.hlsl", defines, "cs_5_0")); + } + return encodeTexturesCSDepthOutput.get(); + } + if (!encodeTexturesCS[methodIndex]) { logger::debug("Compiling EncodeTexturesCS.hlsl for upscale method {}", methodIndex); @@ -926,15 +941,16 @@ void Upscaling::CreateVRIntermediateTextures(uint32_t inWidth, uint32_t inHeight vrIntermediateColorIn[i] = CreateTextureFromSource(colorSrc, inWidth, inHeight, false, true, true, ("Upscale_ColorIn_" + suffix).c_str()); vrIntermediateColorOut[i] = CreateTextureFromSource(colorSrc, outWidth, outHeight, false, true, false, ("Upscale_ColorOut_" + suffix).c_str()); - // Depth: R32_TYPELESS base (matches kMAIN), with R32_FLOAT SRV for ClearHMDMaskCS. - // CopySubresourceRegion requires matching typeless formats; SRV reinterprets as R32_FLOAT. + // Depth: R24G8_TYPELESS matches the game's D24S8_TYPELESS cast group so that + // CopySubresourceRegion can copy from the game depth buffer without format errors. + // R32_TYPELESS is a different cast group and produces silent zero-copy failures. { D3D11_TEXTURE2D_DESC depthDesc = {}; depthDesc.Width = inWidth; depthDesc.Height = inHeight; depthDesc.MipLevels = 1; depthDesc.ArraySize = 1; - depthDesc.Format = DXGI_FORMAT_R32_TYPELESS; + depthDesc.Format = DXGI_FORMAT_R24G8_TYPELESS; depthDesc.SampleDesc.Count = 1; depthDesc.Usage = D3D11_USAGE_DEFAULT; depthDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; @@ -943,33 +959,60 @@ void Upscaling::CreateVRIntermediateTextures(uint32_t inWidth, uint32_t inHeight Util::SetResourceName(vrIntermediateDepth[i]->resource.get(), ("Upscale_Depth_" + suffix).c_str()); D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; - srvDesc.Format = DXGI_FORMAT_R32_FLOAT; + srvDesc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = 1; vrIntermediateDepth[i]->CreateSRV(srvDesc); } - vrIntermediateMotionVectors[i] = CreateTextureFromSource(mvecSrc, inWidth, inHeight, false, true, false, ("Upscale_MVec_" + suffix).c_str()); - vrIntermediateReactiveMask[i] = CreateTextureFromSource(reactiveSrc, inWidth, inHeight, false, true, false, ("Upscale_Reactive_" + suffix).c_str()); - vrIntermediateTransparencyMask[i] = CreateTextureFromSource(transparencySrc, inWidth, inHeight, false, true, false, ("Upscale_Transparency_" + suffix).c_str()); + // Linear depth: R32_FLOAT so FSR's GetFfxResourceDescriptionDX11() returns a valid format. + // EncodeTexturesCS reads from the raw per-eye depth (via vrIntermediateDepth SRV) and + // writes the same non-linear value as R32_FLOAT. Kept separate from vrIntermediateDepth + // so DLSS can continue using the R24G8_TYPELESS copy via Streamline. + { + D3D11_TEXTURE2D_DESC ldDesc = {}; + ldDesc.Width = inWidth; + ldDesc.Height = inHeight; + ldDesc.MipLevels = 1; + ldDesc.ArraySize = 1; + ldDesc.Format = DXGI_FORMAT_R32_FLOAT; + ldDesc.SampleDesc.Count = 1; + ldDesc.Usage = D3D11_USAGE_DEFAULT; + ldDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS; + vrIntermediateLinearDepth[i] = eastl::make_unique(ldDesc); + + Util::SetResourceName(vrIntermediateLinearDepth[i]->resource.get(), ("Upscale_LinearDepth_" + suffix).c_str()); + + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc2 = {}; + srvDesc2.Format = DXGI_FORMAT_R32_FLOAT; + srvDesc2.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srvDesc2.Texture2D.MipLevels = 1; + vrIntermediateLinearDepth[i]->CreateSRV(srvDesc2); + + D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc2 = {}; + uavDesc2.Format = DXGI_FORMAT_R32_FLOAT; + uavDesc2.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D; + uavDesc2.Texture2D.MipSlice = 0; + vrIntermediateLinearDepth[i]->CreateUAV(uavDesc2); + } + + // UAV required: EncodeTexturesCS writes directly into these per-eye buffers + vrIntermediateMotionVectors[i] = CreateTextureFromSource(mvecSrc, inWidth, inHeight, false, true, true, ("Upscale_MVec_" + suffix).c_str()); + vrIntermediateReactiveMask[i] = CreateTextureFromSource(reactiveSrc, inWidth, inHeight, false, true, true, ("Upscale_Reactive_" + suffix).c_str()); + vrIntermediateTransparencyMask[i] = CreateTextureFromSource(transparencySrc, inWidth, inHeight, false, true, true, ("Upscale_Transparency_" + suffix).c_str()); } logger::info("[Upscaling] Created VR intermediate textures: per-eye in {}x{}, out {}x{}", inWidth, inHeight, outWidth, outHeight); } -void Upscaling::PreparePerEyeInputs(ID3D11Resource* colorSrc, ID3D11Resource* depthSrc, ID3D11Resource* mvecSrc, - ID3D11Resource* reactiveSrc, ID3D11Resource* transparencySrc) +void Upscaling::EnsureVRIntermediateTextures() { - if (!globals::game::isVR) - return; - - auto state = globals::state; - if (state->frameAnnotations) - state->BeginPerfEvent("VR Upscaling Prepare"); + auto renderer = globals::game::renderer; + auto& main = renderer->GetRuntimeData().renderTargets[RE::RENDER_TARGETS::kMAIN]; + auto& motionVectorRT = renderer->GetRuntimeData().renderTargets[RE::RENDER_TARGETS::kMOTION_VECTOR]; - auto context = globals::d3d::context; - auto screenSize = state->screenSize; + auto screenSize = globals::state->screenSize; auto renderSize = Util::ConvertToDynamic(screenSize); uint32_t eyeWidthOut = (uint32_t)(screenSize.x / 2); @@ -977,7 +1020,7 @@ void Upscaling::PreparePerEyeInputs(ID3D11Resource* colorSrc, ID3D11Resource* de uint32_t eyeWidthIn = (uint32_t)(renderSize.x / 2); uint32_t eyeHeightIn = (uint32_t)renderSize.y; - bool needsRecreate = !vrIntermediateColorIn[0] || !vrIntermediateColorOut[0]; + bool needsRecreate = !vrIntermediateColorIn[0] || !vrIntermediateColorOut[0] || !vrIntermediateLinearDepth[0]; if (!needsRecreate) { needsRecreate = (vrIntermediateColorIn[0]->desc.Width != eyeWidthIn || vrIntermediateColorIn[0]->desc.Height != eyeHeightIn || @@ -988,27 +1031,46 @@ void Upscaling::PreparePerEyeInputs(ID3D11Resource* colorSrc, ID3D11Resource* de logger::info("[Upscaling] (Re)creating VR intermediates: per-eye in {}x{}, out {}x{}", eyeWidthIn, eyeHeightIn, eyeWidthOut, eyeHeightOut); CreateVRIntermediateTextures(eyeWidthIn, eyeHeightIn, eyeWidthOut, eyeHeightOut, - colorSrc, mvecSrc, reactiveSrc, transparencySrc); + main.texture, motionVectorRT.texture, + reactiveMaskTexture->resource.get(), transparencyCompositionMaskTexture->resource.get()); } +} + +void Upscaling::PreparePerEyeInputs(ID3D11Resource* colorSrc, ID3D11Resource* depthSrc) +{ + if (!globals::game::isVR) + return; + + auto state = globals::state; + if (state->frameAnnotations) + state->BeginPerfEvent("VR Upscaling Prepare"); + + auto context = globals::d3d::context; + auto renderSize = Util::ConvertToDynamic(globals::state->screenSize); + + uint32_t eyeWidthIn = (uint32_t)(renderSize.x / 2); + uint32_t eyeHeightIn = (uint32_t)renderSize.y; + + // Textures guaranteed to exist: EnsureVRIntermediateTextures() was called in Upscale() + // Read the original game depth SRV for ClearHMDMask — the combined stereo buffer is + // definitively valid here, whereas the per-eye copy may silently produce zeros on some + // depth-stencil format / driver combinations. + auto& depthTexture = globals::game::renderer->GetDepthStencilData().depthStencils[RE::RENDER_TARGETS_DEPTHSTENCIL::kMAIN]; + auto& motionVectorRT = globals::game::renderer->GetRuntimeData().renderTargets[RE::RENDER_TARGETS::kMOTION_VECTOR]; - // Extract both eyes' inputs from combined stereo buffers for (uint32_t i = 0; i < 2; ++i) { uint32_t offsetXIn = (i == 1) ? eyeWidthIn : 0; D3D11_BOX srcBox = { offsetXIn, 0, 0, offsetXIn + eyeWidthIn, eyeHeightIn, 1 }; context->CopySubresourceRegion(vrIntermediateColorIn[i]->resource.get(), 0, 0, 0, 0, colorSrc, 0, &srcBox); + // Depth copy keeps vrIntermediateDepth populated for DLSS (Streamline handles R24G8_TYPELESS). + // FSR uses vrIntermediateLinearDepth (R32_FLOAT) written by EncodeTexturesCS instead. context->CopySubresourceRegion(vrIntermediateDepth[i]->resource.get(), 0, 0, 0, 0, depthSrc, 0, &srcBox); - context->CopySubresourceRegion(vrIntermediateMotionVectors[i]->resource.get(), 0, 0, 0, 0, mvecSrc, 0, &srcBox); - context->CopySubresourceRegion(vrIntermediateTransparencyMask[i]->resource.get(), 0, 0, 0, 0, transparencySrc, 0, &srcBox); - context->CopySubresourceRegion(vrIntermediateReactiveMask[i]->resource.get(), 0, 0, 0, 0, reactiveSrc, 0, &srcBox); - } + // DLSS motion vectors are written per-eye by EncodeTexturesCS with 5x5 dilation. + // FSR uses a raw copy here since it does not use the dilated output. + if (GetUpscaleMethod() != UpscaleMethod::kDLSS) + context->CopySubresourceRegion(vrIntermediateMotionVectors[i]->resource.get(), 0, 0, 0, 0, motionVectorRT.texture, 0, &srcBox); - // Zero color where depth == 0 (HMD hidden area) in each per-eye buffer. - // Depth is read from the combined stereo SRV at the per-eye offset; color is written - // to the isolated per-eye UAV (ColorOffsetX = 0). - auto& depthTexture = globals::game::renderer->GetDepthStencilData().depthStencils[RE::RENDER_TARGETS_DEPTHSTENCIL::kMAIN]; - - for (uint32_t i = 0; i < 2; ++i) { uint32_t depthOffset = (i == 1) ? eyeWidthIn : 0; ClearHMDMask(vrIntermediateColorIn[i]->uav.get(), depthTexture.depthSRV, eyeWidthIn, eyeHeightIn, depthOffset, 0); @@ -1299,6 +1361,7 @@ void Upscaling::ClearShaderCache() for (int i = 0; i < 5; ++i) { encodeTexturesCS[i] = nullptr; // com_ptr automatically releases } + encodeTexturesCSDepthOutput = nullptr; depthRefractionUpscalePS = nullptr; // com_ptr automatically releases underwaterMaskUpscalePS = nullptr; // com_ptr automatically releases @@ -1639,8 +1702,6 @@ void Upscaling::Upscale() auto& main = renderer->GetRuntimeData().renderTargets[RE::RENDER_TARGETS::kMAIN]; auto& motionVector = renderer->GetRuntimeData().renderTargets[RE::RENDER_TARGETS::kMOTION_VECTOR]; - auto dispatchCount = Util::GetScreenDispatchCount(true); - { state->BeginPerfEvent("Encode Upscaling Textures"); @@ -1648,35 +1709,53 @@ void Upscaling::Upscale() auto& normals = renderer->GetRuntimeData().renderTargets[globals::deferred->forwardRenderTargets[2]]; auto& depth = renderer->GetDepthStencilData().depthStencils[RE::RENDER_TARGETS_DEPTHSTENCIL::kMAIN]; - { - // Set up upscaling data constant buffer - auto renderSize = Util::ConvertToDynamic(globals::state->screenSize); - UpscalingDataCB upscalingData; - upscalingData.trueSamplingDim = renderSize; + // VR: ensure per-eye intermediate textures exist before the dispatch writes into them + if (globals::game::isVR) + EnsureVRIntermediateTextures(); + + auto renderSize = Util::ConvertToDynamic(globals::state->screenSize); + uint32_t numEyes = globals::game::isVR ? 2 : 1; + uint32_t eyeRenderWidth = (uint32_t)(renderSize.x / numEyes); + uint32_t eyeRenderHeight = (uint32_t)renderSize.y; + + // Sources are the same combined stereo buffers for both VR and non-VR. + // The shader applies EyeOffsetX to sample the correct half. + ID3D11ShaderResourceView* views[4] = { temporalAAMask.SRV, normals.SRV, motionVector.SRV, depth.depthSRV }; + context->CSSetShaderResources(0, ARRAYSIZE(views), views); + context->CSSetShader(GetEncodeTexturesCS(), nullptr, 0); + + for (uint32_t i = 0; i < numEyes; ++i) { + uint32_t offsetX = i * eyeRenderWidth; + UpscalingDataCB upscalingData; + upscalingData.trueSamplingDim = float2((float)eyeRenderWidth, (float)eyeRenderHeight); + upscalingData.eyeOffsetX = offsetX; upscalingDataCB->Update(upscalingData); auto upscalingBuffer = upscalingDataCB->CB(); context->CSSetConstantBuffers(0, 1, &upscalingBuffer); - ID3D11ShaderResourceView* views[4] = { temporalAAMask.SRV, normals.SRV, motionVector.SRV, depth.depthSRV }; - context->CSSetShaderResources(0, ARRAYSIZE(views), views); - - ID3D11UnorderedAccessView* uavs[3] = { reactiveMaskTexture->uav.get(), transparencyCompositionMaskTexture->uav.get(), upscaleMethod == UpscaleMethod::kDLSS ? motionVectorCopyTexture->uav.get() : nullptr }; + // u2 (MotionVectorOutput): DLSS only — 5x5 dilated MVec for ghosting reduction. + // u3 (DepthOutput): VR FSR only — converts R24G8_TYPELESS to R32_FLOAT so + // GetFfxResourceDescriptionDX11() returns a valid format. DLSS uses vrIntermediateDepth. + ID3D11UnorderedAccessView* uavs[4] = { + globals::game::isVR ? vrIntermediateReactiveMask[i]->uav.get() : reactiveMaskTexture->uav.get(), + globals::game::isVR ? vrIntermediateTransparencyMask[i]->uav.get() : transparencyCompositionMaskTexture->uav.get(), + (upscaleMethod == UpscaleMethod::kDLSS) ? (globals::game::isVR ? vrIntermediateMotionVectors[i]->uav.get() : motionVectorCopyTexture->uav.get()) : nullptr, + (upscaleMethod == UpscaleMethod::kFSR && globals::game::isVR) ? vrIntermediateLinearDepth[i]->uav.get() : nullptr + }; context->CSSetUnorderedAccessViews(0, ARRAYSIZE(uavs), uavs, nullptr); - context->CSSetShader(GetEncodeTexturesCS(), nullptr, 0); - - context->Dispatch(dispatchCount.x, dispatchCount.y, 1); + context->Dispatch((eyeRenderWidth + 7) / 8, (eyeRenderHeight + 7) / 8, 1); } - ID3D11ShaderResourceView* views[4] = { nullptr, nullptr, nullptr, nullptr }; - context->CSSetShaderResources(0, ARRAYSIZE(views), views); + ID3D11ShaderResourceView* nullViews[4] = { nullptr, nullptr, nullptr, nullptr }; + context->CSSetShaderResources(0, ARRAYSIZE(nullViews), nullViews); - ID3D11UnorderedAccessView* uavs[3] = { nullptr, nullptr, nullptr }; - context->CSSetUnorderedAccessViews(0, ARRAYSIZE(uavs), uavs, nullptr); + ID3D11UnorderedAccessView* nullUAVs[4] = { nullptr, nullptr, nullptr, nullptr }; + context->CSSetUnorderedAccessViews(0, ARRAYSIZE(nullUAVs), nullUAVs, nullptr); ID3D11Buffer* nullBuffer = nullptr; - context->CSSetConstantBuffers(7, 1, &nullBuffer); + context->CSSetConstantBuffers(0, 1, &nullBuffer); ID3D11ComputeShader* shader = nullptr; context->CSSetShader(shader, nullptr, 0); diff --git a/src/Features/Upscaling.h b/src/Features/Upscaling.h index cc7ce86a53..bdec199c0e 100644 --- a/src/Features/Upscaling.h +++ b/src/Features/Upscaling.h @@ -76,8 +76,9 @@ struct Upscaling : Feature struct UpscalingDataCB { - float2 trueSamplingDim; // BufferDim.xy * ResolutionScale - float2 pad0; + float2 trueSamplingDim; // per-eye render dim in VR, full render dim otherwise + uint eyeOffsetX; // X offset into stereo source buffers; 0 for non-VR / left eye + uint pad0; }; ConstantBuffer* jitterCB = nullptr; @@ -122,7 +123,8 @@ struct Upscaling : Feature void CreateUpscalingTextureResources(UpscaleMethod a_upscalemethod); void DestroyUpscalingTextureResources(UpscaleMethod a_upscalemethod); - winrt::com_ptr encodeTexturesCS[5]; // One for each UpscaleMethod + winrt::com_ptr encodeTexturesCS[5]; // One for each UpscaleMethod + winrt::com_ptr encodeTexturesCSDepthOutput; // FSR + VR: converts R24G8_TYPELESS depth to R32_FLOAT ID3D11ComputeShader* GetEncodeTexturesCS(); winrt::com_ptr depthRefractionUpscalePS; @@ -149,7 +151,8 @@ struct Upscaling : Feature // Owned here so both Streamline (DLSS) and FidelityFX (FSR) can use them. eastl::unique_ptr vrIntermediateColorIn[2]; // per-eye render resolution eastl::unique_ptr vrIntermediateColorOut[2]; // per-eye output resolution - eastl::unique_ptr vrIntermediateDepth[2]; // per-eye render resolution + eastl::unique_ptr vrIntermediateDepth[2]; // per-eye render resolution (R24G8_TYPELESS, for DLSS) + eastl::unique_ptr vrIntermediateLinearDepth[2]; // per-eye render resolution (R32_FLOAT, for FSR) eastl::unique_ptr vrIntermediateMotionVectors[2]; // per-eye render resolution eastl::unique_ptr vrIntermediateReactiveMask[2]; // per-eye render resolution eastl::unique_ptr vrIntermediateTransparencyMask[2]; // per-eye render resolution @@ -163,8 +166,15 @@ struct Upscaling : Feature bool copyBindFlags = false, bool createSRV = false, bool createUAV = false, const char* name = nullptr); // Shared Pipeline Steps - void PreparePerEyeInputs(ID3D11Resource* colorSrc, ID3D11Resource* depthSrc, ID3D11Resource* mvecSrc, - ID3D11Resource* reactiveSrc, ID3D11Resource* transparencySrc); + + /// Ensures VR per-eye intermediate textures exist at the correct resolution. + /// Must be called before any per-eye EncodeTexturesCS dispatch or PreparePerEyeInputs. + void EnsureVRIntermediateTextures(); + + /// Splits the combined stereo color/depth buffers into per-eye intermediates, copies motion + /// vectors for non-DLSS paths, and clears the HMD hidden area. + /// Reactive/transparency masks are written by EncodeTexturesCS. + void PreparePerEyeInputs(ID3D11Resource* colorSrc, ID3D11Resource* depthSrc); void FinalizePerEyeOutputs(ID3D11Resource* colorDst); void ConfigureTAA(); diff --git a/src/Features/Upscaling/FidelityFX.cpp b/src/Features/Upscaling/FidelityFX.cpp index ac3565df62..a2cc640435 100644 --- a/src/Features/Upscaling/FidelityFX.cpp +++ b/src/Features/Upscaling/FidelityFX.cpp @@ -413,14 +413,14 @@ void FidelityFX::Upscale(ID3D11Resource* a_upscalingTexture, ID3D11Resource* a_r if (globals::game::isVR) { // Prepare per-eye inputs and clear mask - upscaling.PreparePerEyeInputs(a_upscalingTexture, depthTexture.texture, a_motionVectors, a_reactiveMask, a_transparencyCompositionMask); + upscaling.PreparePerEyeInputs(a_upscalingTexture, depthTexture.texture); uint32_t numViews = 2; uint32_t eyeWidth = (uint32_t)(renderSize.x / 2); for (uint32_t i = 0; i < numViews; ++i) { DispatchFSR(i, upscaling.vrIntermediateColorIn[i]->resource.get(), - upscaling.vrIntermediateDepth[i]->resource.get(), + upscaling.vrIntermediateLinearDepth[i]->resource.get(), upscaling.vrIntermediateMotionVectors[i]->resource.get(), upscaling.vrIntermediateReactiveMask[i]->resource.get(), upscaling.vrIntermediateTransparencyMask[i]->resource.get(), diff --git a/src/Features/Upscaling/Streamline.cpp b/src/Features/Upscaling/Streamline.cpp index 3381d7302c..f5b5ca4599 100644 --- a/src/Features/Upscaling/Streamline.cpp +++ b/src/Features/Upscaling/Streamline.cpp @@ -622,7 +622,7 @@ void Streamline::Upscale(ID3D11Resource* a_upscalingTexture, ID3D11Resource* a_r uint32_t eyeWidthIn = (uint32_t)(renderSize.x / 2); uint32_t eyeHeightIn = (uint32_t)renderSize.y; - upscaling.PreparePerEyeInputs(a_upscalingTexture, depthTexture.texture, a_motionVectors, a_reactiveMask, a_transparencyCompositionMask); + upscaling.PreparePerEyeInputs(a_upscalingTexture, depthTexture.texture); for (uint32_t i = 0; i < 2; ++i) { sl::ViewportHandle vp = (i == 1) ? viewportRight : viewport; From 0c648597fefceaf59fe0f83d74bbb74dd2a08718 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Tue, 14 Apr 2026 22:57:56 -0700 Subject: [PATCH 2/2] chore: add dlss guard for copy --- src/Features/Upscaling.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Features/Upscaling.cpp b/src/Features/Upscaling.cpp index 243fb51760..8fa989a97f 100644 --- a/src/Features/Upscaling.cpp +++ b/src/Features/Upscaling.cpp @@ -1065,7 +1065,8 @@ void Upscaling::PreparePerEyeInputs(ID3D11Resource* colorSrc, ID3D11Resource* de context->CopySubresourceRegion(vrIntermediateColorIn[i]->resource.get(), 0, 0, 0, 0, colorSrc, 0, &srcBox); // Depth copy keeps vrIntermediateDepth populated for DLSS (Streamline handles R24G8_TYPELESS). // FSR uses vrIntermediateLinearDepth (R32_FLOAT) written by EncodeTexturesCS instead. - context->CopySubresourceRegion(vrIntermediateDepth[i]->resource.get(), 0, 0, 0, 0, depthSrc, 0, &srcBox); + if (GetUpscaleMethod() == UpscaleMethod::kDLSS) + context->CopySubresourceRegion(vrIntermediateDepth[i]->resource.get(), 0, 0, 0, 0, depthSrc, 0, &srcBox); // DLSS motion vectors are written per-eye by EncodeTexturesCS with 5x5 dilation. // FSR uses a raw copy here since it does not use the dilated output. if (GetUpscaleMethod() != UpscaleMethod::kDLSS)