Browse Source

Add history length pass

Panagiotis Christopoulos Charitos 6 months ago
parent
commit
981569ced6

+ 106 - 0
AnKi/Renderer/HistoryLength.cpp

@@ -0,0 +1,106 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <AnKi/Renderer/HistoryLength.h>
+#include <AnKi/Renderer/GBuffer.h>
+#include <AnKi/Renderer/MotionVectors.h>
+
+namespace anki {
+
+Error HistoryLength::init()
+{
+	for(U32 i = 0; i < 2; ++i)
+	{
+		TextureUsageBit texUsage = TextureUsageBit::kAllSrv;
+		texUsage |= (g_preferComputeCVar) ? TextureUsageBit::kUavCompute : TextureUsageBit::kRtvDsvWrite;
+
+		const TextureInitInfo init =
+			getRenderer().create2DRenderTargetInitInfo(getRenderer().getInternalResolution().x(), getRenderer().getInternalResolution().y(),
+													   Format::kR8_Unorm, texUsage, generateTempPassName("HistoryLen #%d", i));
+
+		m_historyLenTextures[i] = getRenderer().createAndClearRenderTarget(init, TextureUsageBit::kSrvCompute);
+	}
+
+	ANKI_CHECK(loadShaderProgram("ShaderBinaries/HistoryLength.ankiprogbin", m_prog, m_grProg));
+
+	return Error::kNone;
+}
+
+void HistoryLength::populateRenderGraph(RenderingContext& ctx)
+{
+	RenderGraphBuilder& rgraph = ctx.m_renderGraphDescr;
+
+	RenderTargetHandle history;
+	RenderTargetHandle current;
+	U32 readTex = getRenderer().getFrameCount() & 1;
+	U32 writeTex = !readTex;
+	if(m_texturesImportedOnce) [[likely]]
+	{
+		history = rgraph.importRenderTarget(m_historyLenTextures[readTex].get());
+		current = rgraph.importRenderTarget(m_historyLenTextures[writeTex].get());
+	}
+	else
+	{
+		history = rgraph.importRenderTarget(m_historyLenTextures[readTex].get(), TextureUsageBit::kSrvCompute);
+		current = rgraph.importRenderTarget(m_historyLenTextures[writeTex].get(), TextureUsageBit::kSrvCompute);
+		m_texturesImportedOnce = true;
+	}
+
+	m_runCtx.m_rt = current;
+
+	RenderPassBase* pass;
+	TextureUsageBit readTexUsage;
+	TextureUsageBit writeTexUsage;
+	if(g_preferComputeCVar)
+	{
+		pass = &rgraph.newNonGraphicsRenderPass("History length");
+		readTexUsage = TextureUsageBit::kSrvCompute;
+		writeTexUsage = TextureUsageBit::kUavCompute;
+	}
+	else
+	{
+		GraphicsRenderPass& rpass = rgraph.newGraphicsRenderPass("History length");
+		rpass.setRenderpassInfo({GraphicsRenderPassTargetDesc(current)});
+		pass = &rpass;
+
+		readTexUsage = TextureUsageBit::kSrvPixel;
+		writeTexUsage = TextureUsageBit::kRtvDsvWrite;
+	}
+
+	pass->newTextureDependency(getGBuffer().getDepthRt(), readTexUsage);
+	pass->newTextureDependency(getGBuffer().getPreviousFrameDepthRt(), readTexUsage);
+	pass->newTextureDependency(getMotionVectors().getMotionVectorsRt(), readTexUsage);
+	pass->newTextureDependency(history, readTexUsage);
+	pass->newTextureDependency(current, writeTexUsage);
+
+	pass->setWork([this, &ctx, history, current](RenderPassWorkContext& rgraphCtx) {
+		CommandBuffer& cmdb = *rgraphCtx.m_commandBuffer;
+
+		cmdb.bindShaderProgram(m_grProg.get());
+
+		rgraphCtx.bindSrv(0, 0, getGBuffer().getDepthRt());
+		rgraphCtx.bindSrv(1, 0, getGBuffer().getPreviousFrameDepthRt());
+		rgraphCtx.bindSrv(2, 0, getMotionVectors().getMotionVectorsRt());
+		rgraphCtx.bindSrv(3, 0, history);
+
+		cmdb.bindConstantBuffer(0, 0, ctx.m_globalRenderingConstantsBuffer);
+
+		cmdb.bindSampler(0, 0, getRenderer().getSamplers().m_trilinearClamp.get());
+		cmdb.bindSampler(1, 0, getRenderer().getSamplers().m_nearestNearestClamp.get());
+
+		if(g_preferComputeCVar)
+		{
+			rgraphCtx.bindUav(0, 0, current);
+			dispatchPPCompute(cmdb, 8, 8, getRenderer().getInternalResolution().x(), getRenderer().getInternalResolution().y());
+		}
+		else
+		{
+			cmdb.setViewport(0, 0, getRenderer().getInternalResolution().x(), getRenderer().getInternalResolution().y());
+			drawQuad(cmdb);
+		}
+	});
+}
+
+} // end namespace anki

+ 49 - 0
AnKi/Renderer/HistoryLength.h

@@ -0,0 +1,49 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <AnKi/Renderer/RendererObject.h>
+
+namespace anki {
+
+/// @addtogroup renderer
+/// @{
+
+/// XXX
+class HistoryLength : public RendererObject
+{
+public:
+	HistoryLength()
+	{
+		registerDebugRenderTarget("HistoryLen");
+	}
+
+	Error init();
+
+	void populateRenderGraph(RenderingContext& ctx);
+
+	void getDebugRenderTarget([[maybe_unused]] CString rtName, Array<RenderTargetHandle, kMaxDebugRenderTargets>& handles,
+							  [[maybe_unused]] ShaderProgramPtr& optionalShaderProgram) const override
+	{
+		handles[0] = m_runCtx.m_rt;
+	}
+
+private:
+	ShaderProgramResourcePtr m_prog;
+	ShaderProgramPtr m_grProg;
+
+	Array<TexturePtr, 2> m_historyLenTextures;
+	Bool m_texturesImportedOnce = false;
+
+	class
+	{
+	public:
+		RenderTargetHandle m_rt;
+	} m_runCtx;
+};
+/// @}
+
+} // namespace anki

+ 2 - 0
AnKi/Renderer/Renderer.cpp

@@ -50,6 +50,7 @@
 #include <AnKi/Renderer/Reflections.h>
 #include <AnKi/Renderer/Reflections.h>
 #include <AnKi/Renderer/IndirectDiffuse.h>
 #include <AnKi/Renderer/IndirectDiffuse.h>
 #include <AnKi/Renderer/IndirectDiffuseClipmaps.h>
 #include <AnKi/Renderer/IndirectDiffuseClipmaps.h>
+#include <AnKi/Renderer/HistoryLength.h>
 #include <AnKi/Renderer/Utils/Drawer.h>
 #include <AnKi/Renderer/Utils/Drawer.h>
 #include <AnKi/Renderer/Utils/GpuVisibility.h>
 #include <AnKi/Renderer/Utils/GpuVisibility.h>
 #include <AnKi/Renderer/Utils/MipmapGenerator.h>
 #include <AnKi/Renderer/Utils/MipmapGenerator.h>
@@ -324,6 +325,7 @@ Error Renderer::populateRenderGraph(RenderingContext& ctx)
 	m_probeReflections->populateRenderGraph(ctx);
 	m_probeReflections->populateRenderGraph(ctx);
 	m_volumetricLightingAccumulation->populateRenderGraph(ctx);
 	m_volumetricLightingAccumulation->populateRenderGraph(ctx);
 	m_motionVectors->populateRenderGraph(ctx);
 	m_motionVectors->populateRenderGraph(ctx);
+	m_historyLength->populateRenderGraph(ctx);
 	m_gbufferPost->populateRenderGraph(ctx);
 	m_gbufferPost->populateRenderGraph(ctx);
 	if(m_rtShadows)
 	if(m_rtShadows)
 	{
 	{

+ 1 - 0
AnKi/Renderer/RendererObject.def.h

@@ -19,6 +19,7 @@ ANKI_RENDERER_OBJECT_DEF(VolumetricFog, volumetricFog, 1)
 ANKI_RENDERER_OBJECT_DEF(DepthDownscale, depthDownscale, 1)
 ANKI_RENDERER_OBJECT_DEF(DepthDownscale, depthDownscale, 1)
 ANKI_RENDERER_OBJECT_DEF(TemporalAA, temporalAA, 1)
 ANKI_RENDERER_OBJECT_DEF(TemporalAA, temporalAA, 1)
 ANKI_RENDERER_OBJECT_DEF(UiStage, uiStage, 1)
 ANKI_RENDERER_OBJECT_DEF(UiStage, uiStage, 1)
+ANKI_RENDERER_OBJECT_DEF(HistoryLength, historyLength, 1)
 ANKI_RENDERER_OBJECT_DEF(IndirectDiffuseProbes, indirectDiffuseProbes,
 ANKI_RENDERER_OBJECT_DEF(IndirectDiffuseProbes, indirectDiffuseProbes,
 						 !(GrManager::getSingleton().getDeviceCapabilities().m_rayTracingEnabled && g_rtIndirectDiffuseClipmapsCVar))
 						 !(GrManager::getSingleton().getDeviceCapabilities().m_rayTracingEnabled && g_rtIndirectDiffuseClipmapsCVar))
 ANKI_RENDERER_OBJECT_DEF(IndirectDiffuseClipmaps, indirectDiffuseClipmaps,
 ANKI_RENDERER_OBJECT_DEF(IndirectDiffuseClipmaps, indirectDiffuseClipmaps,

+ 43 - 29
AnKi/Shaders/Common.hlsl

@@ -48,6 +48,7 @@ RF32 getEpsilon()
 
 
 constexpr U32 kMaxU32 = 0xFFFFFFFFu;
 constexpr U32 kMaxU32 = 0xFFFFFFFFu;
 constexpr F32 kMaxF32 = 3.402823e+38;
 constexpr F32 kMaxF32 = 3.402823e+38;
+constexpr F32 kMinF32 = -3.402823e+38;
 #if !ANKI_SUPPORTS_16BIT_TYPES
 #if !ANKI_SUPPORTS_16BIT_TYPES
 constexpr RF32 kMaxRF32 = 65504.0f; // Max half float value according to wikipedia
 constexpr RF32 kMaxRF32 = 65504.0f; // Max half float value according to wikipedia
 #endif
 #endif
@@ -158,37 +159,50 @@ U32 checkStructuredBuffer(T buff, U32 idx)
 // Safely access a structured buffer. Throw an assertion if it's out of bounds
 // Safely access a structured buffer. Throw an assertion if it's out of bounds
 #define SBUFF(buff, idx) buff[checkStructuredBuffer(buff, idx)]
 #define SBUFF(buff, idx) buff[checkStructuredBuffer(buff, idx)]
 
 
-UVec3 checkTexture(RWTexture3D<Vec4> tex, UVec3 coords)
-{
-	UVec3 size;
-	tex.GetDimensions(size.x, size.y, size.z);
-	ANKI_ASSERT(coords.x < size.x && coords.y < size.y && coords.z < size.z);
-	return coords;
-}
-
-UVec2 checkTexture(RWTexture2D<Vec4> tex, UVec2 coords)
-{
-	UVec2 size;
-	tex.GetDimensions(size.x, size.y);
-	ANKI_ASSERT(coords.x < size.x && coords.y < size.y);
-	return coords;
-}
+#define CHECK_TEXTURE_3D(textureType) \
+	UVec3 checkTexture(textureType tex, UVec3 coords) \
+	{ \
+		UVec3 size; \
+		tex.GetDimensions(size.x, size.y, size.z); \
+		ANKI_ASSERT(coords.x < size.x && coords.y < size.y && coords.z < size.z); \
+		return coords; \
+	}
 
 
-UVec3 checkTexture(Texture3D<Vec4> tex, UVec3 coords)
-{
-	UVec3 size;
-	tex.GetDimensions(size.x, size.y, size.z);
-	ANKI_ASSERT(coords.x < size.x && coords.y < size.y && coords.z < size.z);
-	return coords;
-}
+#define CHECK_TEXTURE_2D(textureType) \
+	UVec2 checkTexture(textureType tex, UVec2 coords) \
+	{ \
+		UVec2 size; \
+		tex.GetDimensions(size.x, size.y); \
+		ANKI_ASSERT(coords.x < size.x && coords.y < size.y); \
+		return coords; \
+	}
 
 
-UVec2 checkTexture(Texture2D<Vec4> tex, UVec2 coords)
-{
-	UVec2 size;
-	tex.GetDimensions(size.x, size.y);
-	ANKI_ASSERT(coords.x < size.x && coords.y < size.y);
-	return coords;
-}
+#define CHECK_TEXTURE_1(vectorType) \
+	CHECK_TEXTURE_3D(RWTexture3D<vectorType>) \
+	CHECK_TEXTURE_3D(Texture3D<vectorType>) \
+	CHECK_TEXTURE_2D(RWTexture2D<vectorType>) \
+	CHECK_TEXTURE_2D(Texture2D<vectorType>)
+
+#define CHECK_TEXTURE_2(componentCount) \
+	CHECK_TEXTURE_1(Vec##componentCount) \
+	CHECK_TEXTURE_1(UVec##componentCount) \
+	CHECK_TEXTURE_1(IVec##componentCount)
+
+#define CHECK_TEXTURE_3() \
+	CHECK_TEXTURE_2(2) \
+	CHECK_TEXTURE_2(3) \
+	CHECK_TEXTURE_2(4) \
+	CHECK_TEXTURE_1(F32) \
+	CHECK_TEXTURE_1(U32) \
+	CHECK_TEXTURE_1(I32)
+
+CHECK_TEXTURE_3()
+
+#undef CHECK_TEXTURE_3
+#undef CHECK_TEXTURE_2
+#undef CHECK_TEXTURE_1
+#undef CHECK_TEXTURE_2D
+#undef CHECK_TEXTURE_3D
 
 
 /// Safely access a UAV or SRV texture. Throw an assertion if it's out of bounds
 /// Safely access a UAV or SRV texture. Throw an assertion if it's out of bounds
 #define TEX(tex, coords) tex[checkTexture(tex, coords)]
 #define TEX(tex, coords) tex[checkTexture(tex, coords)]

+ 5 - 5
AnKi/Shaders/Functions.hlsl

@@ -466,7 +466,6 @@ Mat3 rotationFromDirection(Vec3 zAxis)
 	return o;
 	return o;
 }
 }
 
 
-#if ANKI_COMPUTE_SHADER && ANKI_GLSL
 // See getOptimalGlobalInvocationId8x8Amd
 // See getOptimalGlobalInvocationId8x8Amd
 U32 _ABfiM(U32 src, U32 ins, U32 bits)
 U32 _ABfiM(U32 src, U32 ins, U32 bits)
 {
 {
@@ -488,17 +487,18 @@ UVec2 _ARmpRed8x8(U32 a)
 }
 }
 
 
 // https://github.com/GPUOpen-Effects/FidelityFX-CAS/blob/master/ffx-cas/ffx_a.h
 // https://github.com/GPUOpen-Effects/FidelityFX-CAS/blob/master/ffx-cas/ffx_a.h
-UVec2 getOptimalGlobalInvocationId8x8Amd()
+UVec2 getOptimalDispatchThreadId8x8Amd(U32 svGroupIndex, UVec2 svGroupId)
 {
 {
-	const UVec2 localInvocationId = _ARmpRed8x8(gl_LocalInvocationIndex);
-	return gl_WorkGroupID.xy * UVec2(8u) + localInvocationId;
+	const UVec2 localInvocationId = _ARmpRed8x8(svGroupIndex);
+	return svGroupId * 8u + localInvocationId;
 }
 }
 
 
+#if ANKI_COMPUTE_SHADER && ANKI_GLSL
 // https://github.com/LouisBavoil/ThreadGroupIDSwizzling/blob/master/ThreadGroupTilingX.hlsl
 // https://github.com/LouisBavoil/ThreadGroupIDSwizzling/blob/master/ThreadGroupTilingX.hlsl
 UVec2 getOptimalGlobalInvocationId8x8Nvidia()
 UVec2 getOptimalGlobalInvocationId8x8Nvidia()
 {
 {
 	const U32 maxTileWidth = 8u;
 	const U32 maxTileWidth = 8u;
-	const UVec2 workgroupSize = UVec2(8u);
+	const UVec2 workgroupSize = 8u;
 
 
 	const U32 workgroupsInAPerfectTile = maxTileWidth * gl_NumWorkGroups.y;
 	const U32 workgroupsInAPerfectTile = maxTileWidth * gl_NumWorkGroups.y;
 
 

+ 139 - 0
AnKi/Shaders/HistoryLength.ankiprog

@@ -0,0 +1,139 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+// Calculates the disocclusion length. The longer the length the better the pixel is for temporal accumulation
+
+#pragma anki 16bit
+
+#pragma anki technique comp vert pixel
+
+#include <AnKi/Shaders/Functions.hlsl>
+#include <AnKi/Shaders/Include/MiscRendererTypes.h>
+#include <AnKi/Shaders/QuadVert.hlsl>
+
+constexpr F32 kZDistanceLimit = 0.05; // In meters
+constexpr F32 kMaxHistoryLength = 16.0;
+
+Texture2D<F32> g_depthTex : register(t0);
+Texture2D<F32> g_historyDepthTex : register(t1);
+Texture2D<Vec2> g_motionVectorsTex : register(t2);
+Texture2D<F32> g_prevHistoryLengthTex : register(t3);
+
+RWTexture2D<F32> g_historyLengthTex : register(u0);
+
+ConstantBuffer<GlobalRendererConstants> g_globalRendererConsts : register(b0);
+
+SamplerState g_linearAnyClampSampler : register(s0);
+
+Vec3 unproject(Vec2 ndc, F32 d)
+{
+	return cheapPerspectiveUnprojection(g_globalRendererConsts.m_matrices.m_unprojectionParameters, ndc, d);
+}
+
+F32 computeLength(Vec2 coord)
+{
+	Vec2 viewport;
+	g_depthTex.GetDimensions(viewport.x, viewport.y);
+
+	const Vec2 uv = (coord + 0.5) / viewport;
+	const Vec2 ndc = uvToNdc(uv);
+	const Vec2 historyUv = uv + TEX(g_motionVectorsTex, coord);
+
+	// Compute length
+	F32 good = 0.0; // Zero means "new" pixel this frame
+	if(any(historyUv < 0.0) || any(historyUv > 1.0))
+	{
+		good = 0.0;
+	}
+	else
+	{
+		// Read neighbours to find min and max depth
+		// +-+-+-+
+		// |6|7|8|
+		// +-+-+-+
+		// |3|4|5|
+		// +-+-+-+
+		// |0|1|2|
+		// +-+-+-+
+		// "uv" points to the middle of 4
+		const Vec2 halfTexelSize = (1.0 / viewport) / 2.0;
+		Vec4 depth4 = g_depthTex.GatherRed(g_linearAnyClampSampler, uv + halfTexelSize); // Read 4, 5, 1, 2
+		F32 minDepth = min4(depth4);
+		F32 maxDepth = max4(depth4);
+		depth4 = g_depthTex.GatherRed(g_linearAnyClampSampler, uv - halfTexelSize); // Read 6, 7, 3, 4
+		minDepth = min(minDepth, min4(depth4));
+		maxDepth = max(maxDepth, max4(depth4));
+		F32 d = g_depthTex[clamp(coord + Vec2(1.0, -1.0), 0.0, viewport - 1.0)]; // Read 8
+		minDepth = min(minDepth, d);
+		maxDepth = max(maxDepth, d);
+		d = g_depthTex[clamp(coord + Vec2(-1.0, 1.0), 0.0, viewport - 1.0)]; // Read 0
+		minDepth = min(minDepth, d);
+		maxDepth = max(maxDepth, d);
+
+		// Compute the AABB from the min and max depth
+		const Vec3 boundA = unproject(ndc, minDepth);
+		const Vec3 boundB = unproject(ndc, maxDepth);
+		const Vec3 aabbMinVspace = min(boundA, boundB);
+		const Vec3 aabbMaxVspace = max(boundA, boundB);
+
+		// Read history
+		const Vec2 historyNdc = uvToNdc(historyUv);
+		const F32 historyDepth = g_historyDepthTex.SampleLevel(g_linearAnyClampSampler, historyUv, 0.0f);
+		Vec4 v = mul(g_globalRendererConsts.m_previousMatrices.m_invertedViewProjection, Vec4(historyNdc, historyDepth, 1.0));
+		const Vec3 historyWspace = v.xyz / v.w;
+		const Vec3 historyVspace = mul(g_globalRendererConsts.m_matrices.m_view, Vec4(historyWspace, 1.0));
+
+		F32 maxDist = 0.0;
+		[unroll] for(U32 i = 0; i < 3; ++i)
+		{
+			if(historyVspace[i] < aabbMinVspace[i])
+			{
+				maxDist = max(maxDist, aabbMinVspace[i] - historyVspace[i]);
+			}
+			else if(historyVspace[i] > aabbMaxVspace[i])
+			{
+				maxDist = max(maxDist, historyVspace[i] - aabbMaxVspace[i]);
+			}
+		}
+
+		const F32 factor = maxDist / kZDistanceLimit;
+		good = 1.0 - min(factor, 1.0);
+	}
+
+	F32 len = good;
+	if(good > 0.1)
+	{
+		const F32 prevLen = g_prevHistoryLengthTex.SampleLevel(g_linearAnyClampSampler, historyUv, 0.0f) * kMaxHistoryLength;
+		len += prevLen;
+		len = min(len, kMaxHistoryLength);
+	}
+
+	return len / kMaxHistoryLength;
+}
+
+#if ANKI_COMPUTE_SHADER
+[NumThreads(64, 1, 1)] void main(COMPUTE_ARGS)
+{
+	const Vec2 coord = getOptimalDispatchThreadId8x8Amd(svGroupIndex, svGroupId.xy);
+
+	Vec2 viewport;
+	g_historyLengthTex.GetDimensions(viewport.x, viewport.y);
+	if(any(coord >= viewport))
+	{
+		return;
+	}
+
+	const F32 len = computeLength(coord);
+	TEX(g_historyLengthTex, coord) = len;
+}
+#endif
+
+#if ANKI_PIXEL_SHADER
+F32 main(VertOut input) : SV_TARGET0
+{
+	const Vec2 coord = floor(input.m_svPosition.xy);
+	return computeLength(coord);
+}
+#endif

+ 1 - 1
Samples/Common/SampleApp.cpp

@@ -80,7 +80,7 @@ Error SampleApp::userMainLoop(Bool& quit, Second elapsedTime)
 
 
 	if(in.getKey(KeyCode::kO) == 1)
 	if(in.getKey(KeyCode::kO) == 1)
 	{
 	{
-		renderer.setCurrentDebugRenderTarget((renderer.getCurrentDebugRenderTarget() == "RtMaterialFetchDbg") ? "" : "RtMaterialFetchDbg");
+		renderer.setCurrentDebugRenderTarget((renderer.getCurrentDebugRenderTarget() == "HistoryLen") ? "" : "HistoryLen");
 	}
 	}
 
 
 	static Bool timeOfDay = false;
 	static Bool timeOfDay = false;