浏览代码

Shadowmaps resolve

Panagiotis Christopoulos Charitos 5 年之前
父节点
当前提交
23d6ab498b

+ 6 - 0
samples/common/Framework.cpp

@@ -71,6 +71,12 @@ Error SampleApp::userMainLoop(Bool& quit)
 		renderer.setCurrentDebugRenderTarget((renderer.getCurrentDebugRenderTarget() == "SSR") ? "" : "SSR");
 		renderer.setCurrentDebugRenderTarget((renderer.getCurrentDebugRenderTarget() == "SSR") ? "" : "SSR");
 	}
 	}
 
 
+	if(in.getKey(KeyCode::O) == 1)
+	{
+		renderer.setCurrentDebugRenderTarget(
+			(renderer.getCurrentDebugRenderTarget() == "SM_resolve") ? "" : "SM_resolve");
+	}
+
 	if(!getDisplayDeveloperConsole())
 	if(!getDisplayDeveloperConsole())
 	{
 	{
 		in.hideCursor(true);
 		in.hideCursor(true);

+ 14 - 18
sandbox/Main.cpp

@@ -216,26 +216,22 @@ Error MyApp::userMainLoop(Bool& quit)
 	}
 	}
 #endif
 #endif
 
 
+	if(in.getKey(KeyCode::U) == 1)
 	{
 	{
-		static Bool pressed = false;
-		Bool somethingPressed = false;
-		if(in.getKey(KeyCode::U) == 1)
-		{
-			pressed = !pressed;
-			somethingPressed = true;
-		}
+		renderer.getOffscreenRenderer().setCurrentDebugRenderTarget(
+			(renderer.getOffscreenRenderer().getCurrentDebugRenderTarget() == "SSGI") ? "" : "SSGI");
+	}
 
 
-		if(somethingPressed)
-		{
-			if(pressed)
-			{
-				renderer.getOffscreenRenderer().setCurrentDebugRenderTarget("SSGI");
-			}
-			else
-			{
-				renderer.getOffscreenRenderer().setCurrentDebugRenderTarget("");
-			}
-		}
+	if(in.getKey(KeyCode::I) == 1)
+	{
+		renderer.getOffscreenRenderer().setCurrentDebugRenderTarget(
+			(renderer.getOffscreenRenderer().getCurrentDebugRenderTarget() == "SSR") ? "" : "SSR");
+	}
+
+	if(in.getKey(KeyCode::O) == 1)
+	{
+		renderer.getOffscreenRenderer().setCurrentDebugRenderTarget(
+			(renderer.getOffscreenRenderer().getCurrentDebugRenderTarget() == "SM_resolve") ? "" : "SM_resolve");
 	}
 	}
 
 
 	if(in.getEvent(InputEvent::WINDOW_CLOSED))
 	if(in.getEvent(InputEvent::WINDOW_CLOSED))

+ 13 - 12
shaders/LightShading.ankiprog

@@ -52,6 +52,7 @@ layout(set = 0, binding = 16) uniform texture2D u_msDepthRt;
 layout(set = 0, binding = 17) uniform texture2D u_ssrRt;
 layout(set = 0, binding = 17) uniform texture2D u_ssrRt;
 layout(set = 0, binding = 18) uniform texture2D u_ssaoRt;
 layout(set = 0, binding = 18) uniform texture2D u_ssaoRt;
 layout(set = 0, binding = 19) uniform texture2D u_ssgiRt;
 layout(set = 0, binding = 19) uniform texture2D u_ssgiRt;
+layout(set = 0, binding = 20) uniform texture2D u_resolvedSm;
 
 
 layout(location = 0) in Vec2 in_uv;
 layout(location = 0) in Vec2 in_uv;
 layout(location = 1) in Vec2 in_clusterIJ;
 layout(location = 1) in Vec2 in_clusterIJ;
@@ -69,12 +70,12 @@ layout(location = 0) out Vec3 out_color;
 
 
 void main()
 void main()
 {
 {
-	F32 depth = textureLod(u_msDepthRt, u_nearestAnyClampSampler, in_uv, 0.0).r;
-	Vec2 ndc = UV_TO_NDC(in_uv);
+	const F32 depth = textureLod(u_msDepthRt, u_nearestAnyClampSampler, in_uv, 0.0).r;
+	const Vec2 ndc = UV_TO_NDC(in_uv);
 
 
 	// Get world position
 	// Get world position
-	Vec4 worldPos4 = u_invViewProjMat * Vec4(ndc, depth, 1.0);
-	Vec3 worldPos = worldPos4.xyz / worldPos4.w;
+	const Vec4 worldPos4 = u_invViewProjMat * Vec4(ndc, depth, 1.0);
+	const Vec3 worldPos = worldPos4.xyz / worldPos4.w;
 
 
 	// Get first light index
 	// Get first light index
 	U32 idxOffset;
 	U32 idxOffset;
@@ -93,6 +94,10 @@ void main()
 	readGBuffer(u_msRt0, u_msRt1, u_msRt2, u_nearestAnyClampSampler, in_uv, 0.0, gbuffer);
 	readGBuffer(u_msRt0, u_msRt1, u_msRt2, u_nearestAnyClampSampler, in_uv, 0.0, gbuffer);
 	gbuffer.m_subsurface = max(gbuffer.m_subsurface, SUBSURFACE_MIN);
 	gbuffer.m_subsurface = max(gbuffer.m_subsurface, SUBSURFACE_MIN);
 
 
+	// SM
+	Vec4 resolvedSm = textureLod(u_resolvedSm, u_trilinearClampSampler, in_uv, 0.0);
+	U32 resolvedSmIdx = 0;
+
 	// SSAO
 	// SSAO
 	const F32 ssao = textureLod(u_ssaoRt, u_trilinearClampSampler, in_uv, 0.0).r;
 	const F32 ssao = textureLod(u_ssaoRt, u_trilinearClampSampler, in_uv, 0.0).r;
 	gbuffer.m_diffuse *= ssao;
 	gbuffer.m_diffuse *= ssao;
@@ -107,12 +112,8 @@ void main()
 		F32 shadowFactor;
 		F32 shadowFactor;
 		if(u_dirLight.m_cascadeCount > 0)
 		if(u_dirLight.m_cascadeCount > 0)
 		{
 		{
-			const F32 linearDepth = linearizeDepth(depth, u_near, u_far);
-			const F32 cascadeCountf = F32(u_dirLight.m_cascadeCount);
-			const U32 cascadeIdx = min(U32(linearDepth * cascadeCountf), u_dirLight.m_cascadeCount - 1u);
-
-			shadowFactor =
-				computeShadowFactorDirLight(u_dirLight, cascadeIdx, worldPos, u_shadowTex, u_trilinearClampSampler);
+			shadowFactor = resolvedSm[0];
+			++resolvedSmIdx;
 		}
 		}
 		else
 		else
 		{
 		{
@@ -139,7 +140,7 @@ void main()
 
 
 		ANKI_BRANCH if(light.m_shadowAtlasTileScale >= 0.0)
 		ANKI_BRANCH if(light.m_shadowAtlasTileScale >= 0.0)
 		{
 		{
-			const F32 shadow = computeShadowFactorPointLight(light, frag2Light, u_shadowTex, u_trilinearClampSampler);
+			const F32 shadow = resolvedSm[resolvedSmIdx++];
 			lambert *= shadow;
 			lambert *= shadow;
 		}
 		}
 
 
@@ -158,7 +159,7 @@ void main()
 		const F32 shadowmapLayerIdx = light.m_shadowmapId;
 		const F32 shadowmapLayerIdx = light.m_shadowmapId;
 		ANKI_BRANCH if(shadowmapLayerIdx >= 0.0)
 		ANKI_BRANCH if(shadowmapLayerIdx >= 0.0)
 		{
 		{
-			const F32 shadow = computeShadowFactorSpotLight(light, worldPos, u_shadowTex, u_trilinearClampSampler);
+			const F32 shadow = resolvedSm[resolvedSmIdx++];
 			lambert *= shadow;
 			lambert *= shadow;
 		}
 		}
 
 

+ 93 - 0
shaders/ShadowmapsResolve.ankiprog

@@ -0,0 +1,93 @@
+// Copyright (C) 2009-2020, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma anki start comp
+
+ANKI_SPECIALIZATION_CONSTANT_UVEC2(FB_SIZE, 0, UVec2(1));
+ANKI_SPECIALIZATION_CONSTANT_U32(CLUSTER_COUNT_X, 2, 1u);
+ANKI_SPECIALIZATION_CONSTANT_U32(CLUSTER_COUNT_Y, 3, 1u);
+
+#define LIGHT_SET 0
+#define LIGHT_COMMON_UNIS_BINDING 3
+#define LIGHT_LIGHTS_BINDING 4
+#define LIGHT_CLUSTERS_BINDING 7
+#include <shaders/ClusteredShadingCommon.glsl>
+
+const UVec2 WORKGROUP_SIZE = UVec2(8, 8);
+layout(local_size_x = WORKGROUP_SIZE.x, local_size_y = WORKGROUP_SIZE.y, local_size_z = 1) in;
+
+layout(set = 0, binding = 0, rgba8) uniform image2D out_img;
+layout(set = 0, binding = 1) uniform sampler u_linearAnyClampSampler;
+layout(set = 0, binding = 2) uniform texture2D u_depthRt;
+
+void main()
+{
+	SKIP_OUT_OF_BOUNDS_INVOCATIONS();
+
+	// World position
+	const Vec2 uv = (Vec2(gl_GlobalInvocationID.xy) + 0.5) / Vec2(FB_SIZE);
+	const Vec2 ndc = UV_TO_NDC(uv);
+	const F32 depth = textureLod(u_depthRt, u_linearAnyClampSampler, uv, 0.0).r;
+	const Vec4 worldPos4 = u_invViewProjMat * Vec4(ndc, depth, 1.0);
+	const Vec3 worldPos = worldPos4.xyz / worldPos4.w;
+
+	// Cluster
+	const U32 clusterIdx = computeClusterIndex(u_clustererMagic, uv, worldPos, CLUSTER_COUNT_X, CLUSTER_COUNT_Y);
+	U32 idxOffset = u_clusters[clusterIdx];
+
+	U32 shadowCasterCountPerFragment = 0;
+	const U32 maxShadowCastersPerFragment = 4;
+	F32 shadowFactors[maxShadowCastersPerFragment] = F32[](0.0, 0.0, 0.0, 0.0);
+
+	// Dir light
+	if(u_dirLight.m_active != 0u && u_dirLight.m_cascadeCount > 0)
+	{
+		const F32 linearDepth = linearizeDepth(depth, u_near, u_far);
+		const F32 cascadeCountf = F32(u_dirLight.m_cascadeCount);
+		const U32 cascadeIdx = min(U32(linearDepth * cascadeCountf), u_dirLight.m_cascadeCount - 1u);
+
+		const F32 shadowFactor =
+			computeShadowFactorDirLight(u_dirLight, cascadeIdx, worldPos, u_shadowTex, u_linearAnyClampSampler);
+
+		shadowFactors[0] = shadowFactor;
+		++shadowCasterCountPerFragment;
+	}
+
+	// Point lights
+	U32 idx;
+	ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
+	{
+		PointLight light = u_pointLights[idx];
+
+		ANKI_BRANCH if(light.m_shadowAtlasTileScale >= 0.0)
+		{
+			const Vec3 frag2Light = light.m_position - worldPos;
+
+			const F32 shadowFactor =
+				computeShadowFactorPointLight(light, frag2Light, u_shadowTex, u_linearAnyClampSampler);
+			shadowFactors[min(maxShadowCastersPerFragment - 1, shadowCasterCountPerFragment++)] = shadowFactor;
+		}
+	}
+
+	// Spot lights
+	ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
+	{
+		SpotLight light = u_spotLights[idx];
+
+		ANKI_BRANCH if(light.m_shadowmapId >= 0.0)
+		{
+			const F32 shadowFactor =
+				computeShadowFactorSpotLight(light, worldPos, u_shadowTex, u_linearAnyClampSampler);
+			shadowFactors[min(maxShadowCastersPerFragment - 1, shadowCasterCountPerFragment++)] = shadowFactor;
+		}
+	}
+
+	// Store
+	imageStore(out_img,
+		IVec2(gl_GlobalInvocationID.xy),
+		Vec4(shadowFactors[0], shadowFactors[1], shadowFactors[2], shadowFactors[3]));
+}
+
+#pragma anki end

+ 2 - 0
src/anki/Renderer.h

@@ -25,6 +25,7 @@
 #include <anki/renderer/ProbeReflections.h>
 #include <anki/renderer/ProbeReflections.h>
 #include <anki/renderer/Dbg.h>
 #include <anki/renderer/Dbg.h>
 #include <anki/renderer/Ssao.h>
 #include <anki/renderer/Ssao.h>
+#include <anki/renderer/Ssgi.h>
 #include <anki/renderer/Drawer.h>
 #include <anki/renderer/Drawer.h>
 #include <anki/renderer/UiStage.h>
 #include <anki/renderer/UiStage.h>
 #include <anki/renderer/Tonemapping.h>
 #include <anki/renderer/Tonemapping.h>
@@ -33,5 +34,6 @@
 #include <anki/renderer/VolumetricLightingAccumulation.h>
 #include <anki/renderer/VolumetricLightingAccumulation.h>
 #include <anki/renderer/GlobalIllumination.h>
 #include <anki/renderer/GlobalIllumination.h>
 #include <anki/renderer/GenericCompute.h>
 #include <anki/renderer/GenericCompute.h>
+#include <anki/renderer/ShadowmapsResolve.h>
 
 
 /// @defgroup renderer Renderering system
 /// @defgroup renderer Renderering system

+ 1 - 0
src/anki/renderer/Common.h

@@ -43,6 +43,7 @@ class Ssgi;
 class VolumetricLightingAccumulation;
 class VolumetricLightingAccumulation;
 class GlobalIllumination;
 class GlobalIllumination;
 class GenericCompute;
 class GenericCompute;
+class ShadowmapsResolve;
 
 
 class RenderingContext;
 class RenderingContext;
 class DebugDrawer;
 class DebugDrawer;

+ 2 - 0
src/anki/renderer/ConfigDefs.h

@@ -54,3 +54,5 @@ ANKI_CONFIG_OPTION(r_avgObjectsPerCluster, 16, 16, 256)
 
 
 ANKI_CONFIG_OPTION(r_bloomThreshold, 2.5, 0.0, 256.0)
 ANKI_CONFIG_OPTION(r_bloomThreshold, 2.5, 0.0, 256.0)
 ANKI_CONFIG_OPTION(r_bloomScale, 2.5, 0.0, 256.0)
 ANKI_CONFIG_OPTION(r_bloomScale, 2.5, 0.0, 256.0)
+
+ANKI_CONFIG_OPTION(r_smResolveFactor, 0.5, 0.25, 1.0)

+ 3 - 0
src/anki/renderer/LightShading.cpp

@@ -16,6 +16,7 @@
 #include <anki/renderer/Ssr.h>
 #include <anki/renderer/Ssr.h>
 #include <anki/renderer/Ssgi.h>
 #include <anki/renderer/Ssgi.h>
 #include <anki/renderer/GlobalIllumination.h>
 #include <anki/renderer/GlobalIllumination.h>
+#include <anki/renderer/ShadowmapsResolve.h>
 #include <anki/core/ConfigSet.h>
 #include <anki/core/ConfigSet.h>
 #include <anki/util/HighRezTimer.h>
 #include <anki/util/HighRezTimer.h>
 
 
@@ -137,6 +138,7 @@ void LightShading::run(RenderPassWorkContext& rgraphCtx)
 		rgraphCtx.bindColorTexture(0, 17, m_r->getSsr().getRt());
 		rgraphCtx.bindColorTexture(0, 17, m_r->getSsr().getRt());
 		rgraphCtx.bindColorTexture(0, 18, m_r->getSsao().getRt());
 		rgraphCtx.bindColorTexture(0, 18, m_r->getSsao().getRt());
 		rgraphCtx.bindColorTexture(0, 19, m_r->getSsgi().getRt());
 		rgraphCtx.bindColorTexture(0, 19, m_r->getSsgi().getRt());
+		rgraphCtx.bindColorTexture(0, 20, m_r->getShadowmapsResolve().getRt());
 
 
 		// Draw
 		// Draw
 		drawQuad(cmdb);
 		drawQuad(cmdb);
@@ -206,6 +208,7 @@ void LightShading::populateRenderGraph(RenderingContext& ctx)
 	pass.newDependency({m_r->getShadowMapping().getShadowmapRt(), TextureUsageBit::SAMPLED_FRAGMENT});
 	pass.newDependency({m_r->getShadowMapping().getShadowmapRt(), TextureUsageBit::SAMPLED_FRAGMENT});
 	pass.newDependency({m_r->getSsao().getRt(), TextureUsageBit::SAMPLED_FRAGMENT});
 	pass.newDependency({m_r->getSsao().getRt(), TextureUsageBit::SAMPLED_FRAGMENT});
 	pass.newDependency({m_r->getSsgi().getRt(), TextureUsageBit::SAMPLED_FRAGMENT});
 	pass.newDependency({m_r->getSsgi().getRt(), TextureUsageBit::SAMPLED_FRAGMENT});
+	pass.newDependency({m_r->getShadowmapsResolve().getRt(), TextureUsageBit::SAMPLED_FRAGMENT});
 
 
 	// Refl & indirect
 	// Refl & indirect
 	pass.newDependency({m_r->getSsr().getRt(), TextureUsageBit::SAMPLED_FRAGMENT});
 	pass.newDependency({m_r->getSsr().getRt(), TextureUsageBit::SAMPLED_FRAGMENT});

+ 5 - 0
src/anki/renderer/Renderer.cpp

@@ -32,6 +32,7 @@
 #include <anki/renderer/VolumetricLightingAccumulation.h>
 #include <anki/renderer/VolumetricLightingAccumulation.h>
 #include <anki/renderer/GlobalIllumination.h>
 #include <anki/renderer/GlobalIllumination.h>
 #include <anki/renderer/GenericCompute.h>
 #include <anki/renderer/GenericCompute.h>
+#include <anki/renderer/ShadowmapsResolve.h>
 #include <shaders/glsl_cpp_common/ClusteredShading.h>
 #include <shaders/glsl_cpp_common/ClusteredShading.h>
 
 
 namespace anki
 namespace anki
@@ -196,6 +197,9 @@ Error Renderer::initInternal(const ConfigSet& config)
 	m_uiStage.reset(m_alloc.newInstance<UiStage>(this));
 	m_uiStage.reset(m_alloc.newInstance<UiStage>(this));
 	ANKI_CHECK(m_uiStage->init(config));
 	ANKI_CHECK(m_uiStage->init(config));
 
 
+	m_smResolve.reset(m_alloc.newInstance<ShadowmapsResolve>(this));
+	ANKI_CHECK(m_smResolve->init(config));
+
 	// Init samplers
 	// Init samplers
 	{
 	{
 		SamplerInitInfo sinit("Renderer");
 		SamplerInitInfo sinit("Renderer");
@@ -319,6 +323,7 @@ Error Renderer::populateRenderGraph(RenderingContext& ctx)
 	m_gbuffer->populateRenderGraph(ctx);
 	m_gbuffer->populateRenderGraph(ctx);
 	m_gbufferPost->populateRenderGraph(ctx);
 	m_gbufferPost->populateRenderGraph(ctx);
 	m_depth->populateRenderGraph(ctx);
 	m_depth->populateRenderGraph(ctx);
+	m_smResolve->populateRenderGraph(ctx);
 	m_volFog->populateRenderGraph(ctx);
 	m_volFog->populateRenderGraph(ctx);
 	m_ssao->populateRenderGraph(ctx);
 	m_ssao->populateRenderGraph(ctx);
 	m_lensFlare->populateRenderGraph(ctx);
 	m_lensFlare->populateRenderGraph(ctx);

+ 6 - 0
src/anki/renderer/Renderer.h

@@ -190,6 +190,11 @@ public:
 		return *m_uiStage;
 		return *m_uiStage;
 	}
 	}
 
 
+	ShadowmapsResolve& getShadowmapsResolve()
+	{
+		return *m_smResolve;
+	}
+
 	Ssr& getSsr()
 	Ssr& getSsr()
 	{
 	{
 		return *m_ssr;
 		return *m_ssr;
@@ -416,6 +421,7 @@ private:
 	UniquePtr<Dbg> m_dbg; ///< Debug stage.
 	UniquePtr<Dbg> m_dbg; ///< Debug stage.
 	UniquePtr<UiStage> m_uiStage;
 	UniquePtr<UiStage> m_uiStage;
 	UniquePtr<GenericCompute> m_genericCompute;
 	UniquePtr<GenericCompute> m_genericCompute;
+	UniquePtr<ShadowmapsResolve> m_smResolve;
 	/// @}
 	/// @}
 
 
 	Array<U32, 4> m_clusterCount;
 	Array<U32, 4> m_clusterCount;

+ 94 - 0
src/anki/renderer/ShadowmapsResolve.cpp

@@ -0,0 +1,94 @@
+// Copyright (C) 2009-2020, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/renderer/ShadowmapsResolve.h>
+#include <anki/renderer/Renderer.h>
+#include <anki/renderer/GBuffer.h>
+#include <anki/renderer/ShadowMapping.h>
+#include <anki/core/ConfigSet.h>
+
+namespace anki
+{
+
+ShadowmapsResolve::~ShadowmapsResolve()
+{
+}
+
+Error ShadowmapsResolve::init(const ConfigSet& cfg)
+{
+	const Error err = initInternal(cfg);
+	if(err)
+	{
+		ANKI_R_LOGE("Failed to initialize shadow resolve pass");
+	}
+
+	return Error::NONE;
+}
+
+Error ShadowmapsResolve::initInternal(const ConfigSet& cfg)
+{
+	U32 width = U32(cfg.getNumberF32("r_smResolveFactor") * F32(m_r->getWidth()));
+	width = min(m_r->getWidth(), getAlignedRoundUp(4, width));
+	U32 height = U32(cfg.getNumberF32("r_smResolveFactor") * F32(m_r->getHeight()));
+	height = min(m_r->getHeight(), getAlignedRoundUp(4, height));
+	ANKI_R_LOGI("Initializing shadow resolve pass. Size %ux%u", width, height);
+
+	m_rtDescr = m_r->create2DRenderTargetDescription(width, height, Format::R8G8B8A8_UNORM, "SM_resolve");
+	m_rtDescr.bake();
+
+	ANKI_CHECK(getResourceManager().loadResource("shaders/ShadowmapsResolve.ankiprog", m_prog));
+	ShaderProgramResourceVariantInitInfo variantInitInfo(m_prog);
+	variantInitInfo.addConstant("CLUSTER_COUNT_X", U32(m_r->getClusterCount()[0]));
+	variantInitInfo.addConstant("CLUSTER_COUNT_Y", U32(m_r->getClusterCount()[1]));
+	variantInitInfo.addConstant("FB_SIZE", UVec2(width, height));
+	const ShaderProgramResourceVariant* variant;
+	m_prog->getOrCreateVariant(variantInitInfo, variant);
+	m_grProg = variant->getProgram();
+
+	return Error::NONE;
+}
+
+void ShadowmapsResolve::populateRenderGraph(RenderingContext& ctx)
+{
+	RenderGraphDescription& rgraph = ctx.m_renderGraphDescr;
+	m_runCtx.m_ctx = &ctx;
+	m_runCtx.m_rt = rgraph.newRenderTarget(m_rtDescr);
+
+	ComputeRenderPassDescription& rpass = rgraph.newComputeRenderPass("SM_resolve");
+	rpass.setWork(
+		[](RenderPassWorkContext& rgraphCtx) { static_cast<ShadowmapsResolve*>(rgraphCtx.m_userData)->run(rgraphCtx); },
+		this,
+		0);
+
+	rpass.newDependency({m_runCtx.m_rt, TextureUsageBit::IMAGE_COMPUTE_WRITE});
+	rpass.newDependency({m_r->getGBuffer().getDepthRt(), TextureUsageBit::SAMPLED_COMPUTE});
+}
+
+void ShadowmapsResolve::run(RenderPassWorkContext& rgraphCtx)
+{
+	const RenderingContext& ctx = *m_runCtx.m_ctx;
+	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
+	const ClusterBinOut& rsrc = ctx.m_clusterBinOut;
+
+	cmdb->bindShaderProgram(m_grProg);
+
+	rgraphCtx.bindImage(0, 0, m_runCtx.m_rt, TextureSubresourceInfo());
+	cmdb->bindSampler(0, 1, m_r->getSamplers().m_trilinearClamp);
+
+	rgraphCtx.bindTexture(0, 2, m_r->getGBuffer().getDepthRt(), TextureSubresourceInfo(DepthStencilAspectBit::DEPTH));
+
+	bindUniforms(cmdb, 0, 3, ctx.m_lightShadingUniformsToken);
+
+	bindUniforms(cmdb, 0, 4, rsrc.m_pointLightsToken);
+	bindUniforms(cmdb, 0, 5, rsrc.m_spotLightsToken);
+	rgraphCtx.bindColorTexture(0, 6, m_r->getShadowMapping().getShadowmapRt());
+
+	bindStorage(cmdb, 0, 7, rsrc.m_clustersToken);
+	bindStorage(cmdb, 0, 8, rsrc.m_indicesToken);
+
+	dispatchPPCompute(cmdb, 8, 8, m_rtDescr.m_width, m_rtDescr.m_height);
+}
+
+} // end namespace anki

+ 63 - 0
src/anki/renderer/ShadowmapsResolve.h

@@ -0,0 +1,63 @@
+// Copyright (C) 2009-2020, 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>
+#include <anki/resource/TextureResource.h>
+#include <anki/Gr.h>
+
+namespace anki
+{
+
+/// @addtogroup renderer
+/// @{
+
+/// Resolves shadowmaps into a single texture.
+class ShadowmapsResolve : public RendererObject
+{
+public:
+	ShadowmapsResolve(Renderer* r)
+		: RendererObject(r)
+	{
+		registerDebugRenderTarget("SM_resolve");
+	}
+
+	~ShadowmapsResolve();
+
+	ANKI_USE_RESULT Error init(const ConfigSet& cfg);
+
+	void populateRenderGraph(RenderingContext& ctx);
+
+	void getDebugRenderTarget(CString rtName, RenderTargetHandle& handle) const override
+	{
+		ANKI_ASSERT(rtName == "SM_resolve");
+		handle = m_runCtx.m_rt;
+	}
+
+	RenderTargetHandle getRt() const
+	{
+		return m_runCtx.m_rt;
+	}
+
+public:
+	ShaderProgramResourcePtr m_prog;
+	ShaderProgramPtr m_grProg;
+	RenderTargetDescription m_rtDescr;
+
+	class
+	{
+	public:
+		RenderTargetHandle m_rt;
+		RenderingContext* m_ctx = nullptr;
+	} m_runCtx;
+
+	ANKI_USE_RESULT Error initInternal(const ConfigSet& cfg);
+
+	void run(RenderPassWorkContext& rgraphCtx);
+};
+/// @}
+
+} // end namespace

+ 1 - 2
src/anki/util/String.h

@@ -496,8 +496,7 @@ public:
 	/// Return the CString.
 	/// Return the CString.
 	CStringType toCString() const
 	CStringType toCString() const
 	{
 	{
-		checkInit();
-		return CStringType(&m_data[0]);
+		return (!isEmpty()) ? CStringType(&m_data[0]) : CStringType();
 	}
 	}
 
 
 	/// Return true if it's empty.
 	/// Return true if it's empty.