// Copyright Epic Games, Inc. All Rights Reserved. #include "TSRDepthVelocityAnalysis.ush" #include "TSRClosestOccluder.ush" #include "TSRColorSpace.ush" #include "TSRReprojectionField.ush" //------------------------------------------------------- CONFIG #define TILE_SIZE 8 #define CONFIG_KERNEL 0 #define CONFIG_MANUAL_LDS_SPILL 1 /** Whether to compile the moire reprojection. */ #define CONFIG_MOIRE_REPROJECTION (DIM_MOIRE_REPROJECTION) /** Whether to compile the resurrection reprojection. */ #define CONFIG_RESURRECTION_REPROJECTION (DIM_RESURRECTION_REPROJECTION) /** Whether to compile the thin geometry coverage reprojection.*/ #define CONFIG_THIN_GEOMETRY_COVERAGE_REPROJECTION (DIM_THIN_GEOMETRY_COVERAGE_REPROJECTION) //------------------------------------------------------- PARAMETERS float4x4 RotationalClipToPrevClip; Texture2D DilatedReprojectionVectorTexture; #if CONFIG_RESURRECTION_REPROJECTION Texture2D ClosestDepthTexture; #else Texture2D ClosestDepthTexture; #endif Texture2D DilateMaskTexture; Texture2D DepthErrorTexture; Texture2DArray PrevAtomicTextureArray; Texture2DArray PrevHistoryGuide; Texture2DArray PrevHistoryMoire; #if CONFIG_THIN_GEOMETRY_COVERAGE_REPROJECTION Texture2DArray PrevHistoryCoverage; #endif float2 PrevGuideInfo_Extent; float2 PrevGuideInfo_ExtentInverse; uint2 PrevGuideInfo_ViewportMin; uint2 PrevGuideInfo_ViewportMax; float2 PrevGuideInfo_ViewportSize; float2 PrevGuideInfo_ViewportSizeInverse; float2 PrevGuideInfo_UVViewportBilinearMin; float2 PrevGuideInfo_UVViewportBilinearMax; FScreenTransform InputPixelPosToReprojectScreenPos; FScreenTransform ScreenPosToPrevHistoryGuideBufferUV; FScreenTransform ScreenPosToResurrectionGuideBufferUV; float2 ResurrectionGuideUVViewportBilinearMin; float2 ResurrectionGuideUVViewportBilinearMax; float3 HistoryGuideQuantizationError; float ResurrectionFrameIndex; float PrevFrameIndex; float4x4 ClipToResurrectionClip; RWTexture2DArray ReprojectedHistoryGuideOutput; RWTexture2DArray ReprojectedHistoryMoireOutput; #if CONFIG_THIN_GEOMETRY_COVERAGE_REPROJECTION RWTexture2DArray ReprojectedHistoryCoverageOutput; #endif RWTexture2DArray ReprojectionFieldOutput; RWTexture2D DecimateMaskOutput; //------------------------------------------------------- DEBUG #define DEBUG_ARRAY_SIZE 8 static float4 Debug[DEBUG_ARRAY_SIZE]; //------------------------------------------------------- LDS #if CONFIG_MANUAL_LDS_SPILL groupshared uint SharedArray0[TILE_SIZE * TILE_SIZE]; groupshared float SharedArray1[TILE_SIZE * TILE_SIZE]; #endif //------------------------------------------------------- FUNCTIONS // Correct pre exposure of the guide color. tsr_halfC CorrectGuideColorExposure(tsr_halfC ReprojectedHistoryGuideColor, const float PreExposureCorrection) { ReprojectedHistoryGuideColor = PreExposureCorrectGCS(ReprojectedHistoryGuideColor, PreExposureCorrection); // Removes NaN ReprojectedHistoryGuideColor = -min(-ReprojectedHistoryGuideColor, tsr_half(0.0)); return ReprojectedHistoryGuideColor; } #if CONFIG_MOIRE_REPROJECTION // Sample previous moire history tsr_half4 ReprojectPrevHistoryMoire(tsr_ushort2 InputPixelPos, float2 TextureUV) { uint2 Random = Rand3DPCG16(int3(InputPixelPos - InputInfo_ViewportMin, View.StateFrameIndexMod8)).xy; float2 E = Hammersley16(0, 1, Random).xy; return PrevHistoryMoire.SampleLevel(GlobalPointClampedSampler, float3(TextureUV + (E - 0.5) * PrevGuideInfo_ExtentInverse, 0), 0); } // Correct pre exposure of the moire history. tsr_half4 CorrectMoireExposure(tsr_half4 ReprojectedHistoryMoireColor, const float PreExposureCorrection) { tsr_half PrevFlickeringHistory = ReprojectedHistoryMoireColor[0]; PrevFlickeringHistory = PreExposureCorrectGCS(PrevFlickeringHistory.xxx, PreExposureCorrection).r; // Removes NaN PrevFlickeringHistory = -min(-PrevFlickeringHistory, tsr_half(0.0)); return tsr_half4(PrevFlickeringHistory, ReprojectedHistoryMoireColor.yzw); } #endif #if CONFIG_THIN_GEOMETRY_COVERAGE_REPROJECTION tsr_half ReprojectPrevHistoryCoverage(tsr_ushort2 InputPixelPos, float2 TextureUV) { return PrevHistoryCoverage.SampleLevel(GlobalPointClampedSampler, float3(TextureUV, 0), 0); } #endif //ISOLATE void ReprojectAllPrevTextures( uint2 GroupId, uint GroupThreadIndex, uint EncodedReprojectionVector, float DeviceZ, out uint HistoryClosestDeviceZSamples0[4], out uint HistoryClosestDeviceZSamples1[4], out FCatmullRomFetches ReprojectedHistoryGuideSamples, #if CONFIG_SCENE_COLOR_ALPHA out FCatmullRomFetches ReprojectedHistoryGuideUncertaintySamples, #endif #if CONFIG_MOIRE_REPROJECTION out tsr_half4 ReprojectedHistoryMoireColor, #endif #if CONFIG_THIN_GEOMETRY_COVERAGE_REPROJECTION out tsr_half ReprojectedHistoryCoverage, #endif #if CONFIG_RESURRECTION_REPROJECTION out tsr_halfC ResurrectedHistoryGuideColor, out tsr_half ResurrectedHistoryGuideUncertainty, out bool bIsResurrectionOffScreen, #endif out bool bIsOffScreen) { tsr_ushort2 InputPixelPos = ( Map8x8Tile2x2Lane(GroupThreadIndex) + (tsr_ushort2(InputInfo_ViewportMin) + tsr_ushort2(GroupId) * tsr_ushort2(TILE_SIZE, TILE_SIZE))); { float2 ScreenPos = ApplyScreenTransform(float2(InputPixelPos), InputPixelPosToScreenPos); float2 PrevScreenPos = ScreenPos - DecodeReprojectionVector(EncodedReprojectionVector); LoadPrevAtomicTexturesSamples( PrevAtomicTextureArray, PrevScreenPos, /* out */ HistoryClosestDeviceZSamples0, /* out */ HistoryClosestDeviceZSamples1); } { float2 OutputScreenPos = ApplyScreenTransform(float2(InputPixelPos), InputPixelPosToReprojectScreenPos); float2 PrevOutputScreenPos = OutputScreenPos - DecodeReprojectionVector(EncodedReprojectionVector); bIsOffScreen = ( (bCameraCut != 0) || (PrevOutputScreenPos.x < -1.0) || (PrevOutputScreenPos.y < -1.0) || (PrevOutputScreenPos.x > 1.0) || (PrevOutputScreenPos.y > 1.0)); float2 KernelHistoryBufferUV = ApplyScreenTransform(PrevOutputScreenPos, ScreenPosToPrevHistoryGuideBufferUV); #if CONFIG_SCENE_COLOR_ALPHA ReprojectedHistoryGuideSamples.FetchSamples( PrevHistoryGuide, KernelHistoryBufferUV, PrevFrameIndex * 2.0 + 0.0, PrevGuideInfo_Extent, PrevGuideInfo_ExtentInverse, PrevGuideInfo_UVViewportBilinearMin, PrevGuideInfo_UVViewportBilinearMax); ReprojectedHistoryGuideUncertaintySamples.FetchSamples( PrevHistoryGuide, KernelHistoryBufferUV, PrevFrameIndex * 2.0 + 1.0, PrevGuideInfo_Extent, PrevGuideInfo_ExtentInverse, PrevGuideInfo_UVViewportBilinearMin, PrevGuideInfo_UVViewportBilinearMax); #else ReprojectedHistoryGuideSamples.FetchSamples( PrevHistoryGuide, KernelHistoryBufferUV, PrevFrameIndex, PrevGuideInfo_Extent, PrevGuideInfo_ExtentInverse, PrevGuideInfo_UVViewportBilinearMin, PrevGuideInfo_UVViewportBilinearMax); #endif #if CONFIG_MOIRE_REPROJECTION { ReprojectedHistoryMoireColor = ReprojectPrevHistoryMoire(InputPixelPos, KernelHistoryBufferUV); } #endif #if CONFIG_THIN_GEOMETRY_COVERAGE_REPROJECTION { ReprojectedHistoryCoverage = ReprojectPrevHistoryCoverage(InputPixelPos, KernelHistoryBufferUV); } #endif } #if CONFIG_RESURRECTION_REPROJECTION { float2 OutputScreenPos = ApplyScreenTransform(float2(InputPixelPos), InputPixelPosToReprojectScreenPos); float4 ThisClip = float4(OutputScreenPos, DeviceZ, 1); float4 ResurrectionClip = mul(ThisClip, ClipToResurrectionClip); float2 ResurrectionScreen = ResurrectionClip.xy / ResurrectionClip.w; bIsResurrectionOffScreen = ( (ResurrectionScreen.x < -1.0) || (ResurrectionScreen.y < -1.0) || (ResurrectionScreen.x > 1.0) || (ResurrectionScreen.y > 1.0)); float2 KernelHistoryBufferUV = ApplyScreenTransform(ResurrectionScreen, ScreenPosToResurrectionGuideBufferUV); KernelHistoryBufferUV = fastClamp(KernelHistoryBufferUV, ResurrectionGuideUVViewportBilinearMin, ResurrectionGuideUVViewportBilinearMax); #if CONFIG_SCENE_COLOR_ALPHA ResurrectedHistoryGuideColor = PrevHistoryGuide.SampleLevel(GlobalBilinearClampedSampler, float3(KernelHistoryBufferUV, ResurrectionFrameIndex * 2.0 + 0.0), 0); ResurrectedHistoryGuideUncertainty = PrevHistoryGuide.SampleLevel(GlobalBilinearClampedSampler, float3(KernelHistoryBufferUV, ResurrectionFrameIndex * 2.0 + 1.0), 0).r; #else tsr_half4 RawGuide = PrevHistoryGuide.SampleLevel(GlobalBilinearClampedSampler, float3(KernelHistoryBufferUV, ResurrectionFrameIndex), 0); ResurrectedHistoryGuideColor = RawGuide.rgb; ResurrectedHistoryGuideUncertainty = RawGuide.a; #endif } #endif } // ReprojectAllPrevTextures() //------------------------------------------------------- ENTRY POINT [numthreads(TILE_SIZE * TILE_SIZE, 1, 1)] void MainCS( uint2 GroupId : SV_GroupID, uint GroupThreadIndex : SV_GroupIndex) { for (uint DebugId = 0; DebugId < DEBUG_ARRAY_SIZE; DebugId++) { Debug[DebugId] = 0.0; } // Sample current frame informations. uint DilatedEncodedReprojectionVector; float DeviceZ; float PrevDeviceZ; float DeviceZError; bool bHasPixelAnimation; bool bHasReprojectionOffset; tsr_half ReprojectionEdge; ISOLATE { uint EncodedReprojectionVectorNeighborhood[CONFIG_BUTTERFLY_SAMPLES]; tsr_half PremilinaryReprojectionEdge; // Issue texture fetechs ISOLATE { tsr_ushort2 InputPixelPos = ( Map8x8Tile2x2Lane(GroupThreadIndex) + (tsr_ushort2(InputInfo_ViewportMin) + tsr_ushort2(GroupId) * tsr_ushort2(TILE_SIZE, TILE_SIZE))); uint EncodedDilateMask = DilateMaskTexture[InputPixelPos]; uint EncodedDeviceZError = DepthErrorTexture[InputPixelPos]; #if CONFIG_RESURRECTION_REPROJECTION float2 DeviceZAndPrevDeviceZ = ClosestDepthTexture[InputPixelPos]; DeviceZ = DeviceZAndPrevDeviceZ.g; #else float DeviceZAndPrevDeviceZ = ClosestDepthTexture[InputPixelPos]; DeviceZ = 0.0; #endif bHasReprojectionOffset = (EncodedDilateMask & 0x80u) != 0u; PremilinaryReprojectionEdge = tsr_half(tsr_ushort(EncodedDilateMask & 0x7Fu)) * rcp(tsr_half(127.0)); PrevDeviceZ = abs(DeviceZAndPrevDeviceZ.r); bHasPixelAnimation = DeviceZAndPrevDeviceZ.r < 0.0; DeviceZError = DecodeDeviceZError(PrevDeviceZ, EncodedDeviceZError); FetchReprojectionNeighborhood(DilatedReprojectionVectorTexture, InputPixelPos, /* out */ EncodedReprojectionVectorNeighborhood); DilatedEncodedReprojectionVector = AccessNeighborhoodCenter(EncodedReprojectionVectorNeighborhood); } // LDS spill to save VGPR on the history texture fetches. #if CONFIG_MANUAL_LDS_SPILL ISOLATE { SharedArray0[GroupThreadIndex] = DilatedEncodedReprojectionVector; SharedArray1[GroupThreadIndex] = PrevDeviceZ; } #endif // Detect whether is on velocity edge to clamp the high frequencies ISOLATE { float2 PixelVelocityNeighborhood[CONFIG_BUTTERFLY_SAMPLES]; ComputePixelVelocityNeighborhood(EncodedReprojectionVectorNeighborhood, /* out */ PixelVelocityNeighborhood); ReprojectionEdge = ComputeReprojectionEdge(PixelVelocityNeighborhood); ReprojectionEdge = select(PremilinaryReprojectionEdge > tsr_half(0.9), PremilinaryReprojectionEdge, ReprojectionEdge); ReprojectionEdge = select(bHasReprojectionOffset, ReprojectionEdge, tsr_half(1.0)); } } // Sample the previous frame information uint HistoryClosestDeviceZSamples0[4]; uint HistoryClosestDeviceZSamples1[4]; FCatmullRomFetches ReprojectedHistoryGuideSamples; #if CONFIG_SCENE_COLOR_ALPHA FCatmullRomFetches ReprojectedHistoryGuideUncertaintySamples; #endif #if CONFIG_MOIRE_REPROJECTION tsr_half4 ReprojectedHistoryMoireColor; #endif #if CONFIG_THIN_GEOMETRY_COVERAGE_REPROJECTION tsr_half ReprojectedHistoryCoverage; #endif #if CONFIG_RESURRECTION_REPROJECTION tsr_halfC ResurrectedHistoryGuideColor; tsr_half ResurrectedHistoryGuideUncertainty; bool bIsResurrectionOffScreen; #else const bool bIsResurrectionOffScreen = false; #endif bool bIsOffScreen; // Issues all the overlapping texture fetches for previous frame texture using motion vectors { ReprojectAllPrevTextures( GroupId, GroupThreadIndex, DilatedEncodedReprojectionVector, DeviceZ, /* out */ HistoryClosestDeviceZSamples0, /* out */ HistoryClosestDeviceZSamples1, /* out */ ReprojectedHistoryGuideSamples, #if CONFIG_SCENE_COLOR_ALPHA /* out */ ReprojectedHistoryGuideUncertaintySamples, #endif #if CONFIG_MOIRE_REPROJECTION /* out */ ReprojectedHistoryMoireColor, #endif #if CONFIG_THIN_GEOMETRY_COVERAGE_REPROJECTION /* out */ ReprojectedHistoryCoverage, #endif #if CONFIG_RESURRECTION_REPROJECTION /* out */ ResurrectedHistoryGuideColor, /* out */ ResurrectedHistoryGuideUncertainty, /* out */ bIsResurrectionOffScreen, #endif /* out */ bIsOffScreen); } // Process the history samples tsr_halfC ReprojectedHistoryGuideColor; tsr_half ReprojectedHistoryGuideUncertainty; bool bIsParallaxDisocclusion; bool bReprojectionHollFill; //ISOLATE { #if CONFIG_SCENE_COLOR_ALPHA { ReprojectedHistoryGuideColor = saturate(ReprojectedHistoryGuideSamples.AccumulateSamples()); ReprojectedHistoryGuideUncertainty = ReprojectedHistoryGuideUncertaintySamples.AccumulateSamples().r; } #else { tsr_half4 RawGuide = ReprojectedHistoryGuideSamples.AccumulateSamples(); ReprojectedHistoryGuideColor = RawGuide.rgb; ReprojectedHistoryGuideUncertainty = RawGuide.a; } #endif ReprojectedHistoryGuideColor = CorrectGuideColorExposure(ReprojectedHistoryGuideColor, HistoryPreExposureCorrection); #if CONFIG_MOIRE_REPROJECTION { ReprojectedHistoryMoireColor = CorrectMoireExposure(ReprojectedHistoryMoireColor, HistoryPreExposureCorrection); } #endif #if CONFIG_RESURRECTION_REPROJECTION { ResurrectedHistoryGuideColor = CorrectGuideColorExposure(ResurrectedHistoryGuideColor, ResurrectionPreExposureCorrection); } #endif // LDS despill #if CONFIG_MANUAL_LDS_SPILL { DilatedEncodedReprojectionVector = SharedArray0[GroupThreadIndex]; PrevDeviceZ = SharedArray1[GroupThreadIndex]; } #endif tsr_ushort2 InputPixelPos = ( Map8x8Tile2x2Lane(GroupThreadIndex) + (tsr_ushort2(InputInfo_ViewportMin) + tsr_ushort2(GroupId) * tsr_ushort2(TILE_SIZE, TILE_SIZE))); float2 ScreenPos = ApplyScreenTransform(float2(InputPixelPos), InputPixelPosToScreenPos); float2 ScreenVelocity = DecodeReprojectionVector(DilatedEncodedReprojectionVector); // Compute the parralax rejection mask float2 HoleFillingPixelVelocity; bool bCanHoleFill; ProcessPrevAtomicTexturesSamples( HistoryClosestDeviceZSamples0, HistoryClosestDeviceZSamples1, ScreenPos, ScreenVelocity, PrevDeviceZ, DeviceZError, bIsOffScreen, /* out */ bIsParallaxDisocclusion, /* out */ HoleFillingPixelVelocity, /* out */ bCanHoleFill); // LDS despill #if CONFIG_MANUAL_LDS_SPILL { DilatedEncodedReprojectionVector = SharedArray0[GroupThreadIndex]; } #endif bReprojectionHollFill = bCanHoleFill && bIsParallaxDisocclusion; // If the hole filling velocity has better result that the neighborhood, use that. BRANCH if (bReprojectionHollFill) { float2 HoleFillingReprojectionVector = HoleFillingPixelVelocity * InputPixelVelocityToScreenVelocity; DilatedEncodedReprojectionVector = EncodeReprojectionVector(HoleFillingReprojectionVector); } } // Builds the decimate bitmask tsr_ushort DecimateBitMask = select(bIsOffScreen, tsr_ushort(0x01), tsr_ushort(0x0)) | select(bIsParallaxDisocclusion, tsr_ushort(0x02), tsr_ushort(0x0)) | select(bHasPixelAnimation, tsr_ushort(0x04), tsr_ushort(0x0)) | select(bReprojectionHollFill, tsr_ushort(0x08), tsr_ushort(0x0)) | select(bIsResurrectionOffScreen, tsr_ushort(0x10), tsr_ushort(0x0)); // Output full res ISOLATE { tsr_ushort2 InputPixelPos = ( Map8x8Tile2x2Lane(GroupThreadIndex) + (tsr_ushort2(InputInfo_ViewportMin) + tsr_ushort2(GroupId) * tsr_ushort2(TILE_SIZE, TILE_SIZE))); #if CONFIG_ENABLE_STOCASTIC_QUANTIZATION && 1 { uint2 Random = Rand3DPCG16(int3(InputPixelPos - InputInfo_ViewportMin, View.StateFrameIndexMod8)).xy; tsr_half E = tsr_half(Hammersley16(0, 1, Random).x); ReprojectedHistoryGuideColor = QuantizeForUNormRenderTarget(ReprojectedHistoryGuideColor, E, HistoryGuideQuantizationError); } #endif tsr_short2 OutputPixelPos = InvalidateOutputPixelPos(InputPixelPos, InputInfo_ViewportMax); { #if CONFIG_SCENE_COLOR_ALPHA ReprojectedHistoryGuideOutput[tsr_short3(OutputPixelPos, 0)] = ReprojectedHistoryGuideColor; ReprojectedHistoryGuideOutput[tsr_short3(OutputPixelPos, 1)] = tsr_half4(ReprojectedHistoryGuideUncertainty, 0, 0, 0); #else ReprojectedHistoryGuideOutput[tsr_short3(OutputPixelPos, 0)] = tsr_half4(ReprojectedHistoryGuideColor.xyz, ReprojectedHistoryGuideUncertainty); #endif #if CONFIG_MOIRE_REPROJECTION ReprojectedHistoryMoireOutput[tsr_short3(OutputPixelPos, 0)] = ReprojectedHistoryMoireColor; #endif #if CONFIG_THIN_GEOMETRY_COVERAGE_REPROJECTION ReprojectedHistoryCoverageOutput[tsr_short3(OutputPixelPos, 0)] = ReprojectedHistoryCoverage; #endif } #if CONFIG_RESURRECTION_REPROJECTION { #if CONFIG_SCENE_COLOR_ALPHA ReprojectedHistoryGuideOutput[tsr_short3(OutputPixelPos, 2)] = ResurrectedHistoryGuideColor; ReprojectedHistoryGuideOutput[tsr_short3(OutputPixelPos, 3)] = tsr_half4(ResurrectedHistoryGuideUncertainty, 0, 0, 0); #else ReprojectedHistoryGuideOutput[tsr_short3(OutputPixelPos, 1)] = tsr_half4(ResurrectedHistoryGuideColor.xyz, ResurrectedHistoryGuideUncertainty); #endif } #endif DecimateMaskOutput[OutputPixelPos] = tsr_half2(tsr_half(DecimateBitMask) * rcp(tsr_half(255.0)), ReprojectionEdge); // Selectively overwrite the reprojection field { bool bWriteReprojectionVector = bReprojectionHollFill || true; bool bWriteReprojectionJacobian = bReprojectionHollFill; // Disable the reprojection's jacobian since this is to extrude disoccluded area we don't know how they look anyway. const uint EncodedReprojectionJacobian = EncodeReprojectionJacobian(tsr_half2x2(tsr_half(0.0).xxxx)); ReprojectionFieldOutput[tsr_short3(select(bWriteReprojectionVector, OutputPixelPos.x, -tsr_short(1)), OutputPixelPos.y, kReprojectionVectorOutputIndex)] = DilatedEncodedReprojectionVector; ReprojectionFieldOutput[tsr_short3(select(bWriteReprojectionJacobian, OutputPixelPos.x, -tsr_short(1)), OutputPixelPos.y, kReprojectionJacobianOutputIndex)] = EncodedReprojectionJacobian; } #if DEBUG_OUTPUT for (uint DebugId = 0; DebugId < DEBUG_ARRAY_SIZE; DebugId++) { DebugOutput[tsr_short3(OutputPixelPos, DebugId)] = Debug[DebugId]; } #endif } }