Browse Source

Remove EVSM and start using plain SM with PCF

Panagiotis Christopoulos Charitos 3 years ago
parent
commit
8a0979e1bb

+ 1 - 0
AnKi/Gr/Vulkan/CommandBufferImpl.h

@@ -155,6 +155,7 @@ public:
 	{
 		commandCommon();
 		m_state.setPolygonOffset(factor, units);
+		vkCmdSetDepthBias(m_handle, factor, 0.0f, units);
 	}
 
 	void setStencilOperationsInternal(FaceSelectionBit face, StencilOperation stencilFail,

+ 4 - 7
AnKi/Gr/Vulkan/Pipeline.cpp

@@ -278,11 +278,7 @@ const VkGraphicsPipelineCreateInfo& PipelineStateTracker::updatePipelineCreateIn
 	rastCi.polygonMode = convertFillMode(m_state.m_rasterizer.m_fillMode);
 	rastCi.cullMode = convertCullMode(m_state.m_rasterizer.m_cullMode);
 	rastCi.frontFace = (!m_defaultFb) ? VK_FRONT_FACE_CLOCKWISE : VK_FRONT_FACE_COUNTER_CLOCKWISE; // For viewport flip
-	rastCi.depthBiasEnable =
-		m_state.m_rasterizer.m_depthBiasConstantFactor != 0.0 && m_state.m_rasterizer.m_depthBiasSlopeFactor != 0.0;
-	rastCi.depthBiasConstantFactor = m_state.m_rasterizer.m_depthBiasConstantFactor;
-	rastCi.depthBiasClamp = 0.0;
-	rastCi.depthBiasSlopeFactor = m_state.m_rasterizer.m_depthBiasSlopeFactor;
+	rastCi.depthBiasEnable = m_state.m_rasterizer.m_depthBiasEnabled;
 	rastCi.lineWidth = 1.0;
 	ci.pRasterizationState = &rastCi;
 
@@ -381,10 +377,11 @@ const VkGraphicsPipelineCreateInfo& PipelineStateTracker::updatePipelineCreateIn
 	dynCi.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
 
 	// Almost all state is dynamic. Depth bias is static
-	static constexpr Array<VkDynamicState, 9> kDyn = {
+	static constexpr Array<VkDynamicState, 10> kDyn = {
 		{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_BLEND_CONSTANTS,
 		 VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK, VK_DYNAMIC_STATE_STENCIL_WRITE_MASK,
-		 VK_DYNAMIC_STATE_STENCIL_REFERENCE, VK_DYNAMIC_STATE_LINE_WIDTH, VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR}};
+		 VK_DYNAMIC_STATE_STENCIL_REFERENCE, VK_DYNAMIC_STATE_LINE_WIDTH, VK_DYNAMIC_STATE_DEPTH_BIAS,
+		 VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR}};
 
 	dynCi.dynamicStateCount = kDyn.getSize();
 	dynCi.pDynamicStates = &kDyn[0];

+ 5 - 8
AnKi/Gr/Vulkan/Pipeline.h

@@ -81,11 +81,9 @@ public:
 	FillMode m_fillMode = FillMode::kSolid;
 	FaceSelectionBit m_cullMode = FaceSelectionBit::kBack;
 	RasterizationOrder m_rasterizationOrder = RasterizationOrder::kOrdered;
-	U8 m_padding = 0;
-	F32 m_depthBiasConstantFactor = 0.0f;
-	F32 m_depthBiasSlopeFactor = 0.0f;
+	Bool m_depthBiasEnabled = false;
 };
-static_assert(sizeof(RasterizerPipelineState) == sizeof(U32) * 3, "Packed because it will be hashed");
+static_assert(sizeof(RasterizerPipelineState) == sizeof(U32), "Packed because it will be hashed");
 
 class DepthPipelineState
 {
@@ -222,11 +220,10 @@ public:
 
 	void setPolygonOffset(F32 factor, F32 units)
 	{
-		if(m_state.m_rasterizer.m_depthBiasConstantFactor != factor
-		   || m_state.m_rasterizer.m_depthBiasSlopeFactor != units)
+		const Bool depthBiasEnabled = factor != 0.0f || units != 0.0f;
+		if(depthBiasEnabled != m_state.m_rasterizer.m_depthBiasEnabled)
 		{
-			m_state.m_rasterizer.m_depthBiasConstantFactor = factor;
-			m_state.m_rasterizer.m_depthBiasSlopeFactor = units;
+			m_state.m_rasterizer.m_depthBiasEnabled = depthBiasEnabled;
 			m_dirty.m_rasterizer = true;
 		}
 	}

+ 1 - 0
AnKi/Renderer/ConfigVars.defs.h

@@ -66,6 +66,7 @@ ANKI_CONFIG_VAR_U32(RShadowMappingTileCountPerRowOrColumn, 16, 1, 256,
 ANKI_CONFIG_VAR_U32(RShadowMappingScratchTileCountX, 4 * (kMaxShadowCascades + 2), 1, 256,
 					"Number of tiles of the scratch buffer in X")
 ANKI_CONFIG_VAR_U32(RShadowMappingScratchTileCountY, 4, 1, 256, "Number of tiles of the scratch buffer in Y")
+ANKI_CONFIG_VAR_BOOL(RShadowMappingPcf, (ANKI_PLATFORM_MOBILE) ? false : true, "Enable or not PCF")
 
 // Probe reflections
 ANKI_CONFIG_VAR_U32(RProbeReflectionResolution, 128, 4, 2048, "Reflection probe face resolution")

+ 1 - 0
AnKi/Renderer/ForwardShading.cpp

@@ -37,6 +37,7 @@ void ForwardShading::run(const RenderingContext& ctx, RenderPassWorkContext& rgr
 		const ClusteredShadingContext& rsrc = ctx.m_clusteredShading;
 		const U32 set = kMaterialSetGlobal;
 		cmdb->bindSampler(set, kMaterialBindingLinearClampSampler, m_r->getSamplers().m_trilinearClamp);
+		cmdb->bindSampler(set, kMaterialBindingShadowSampler, m_r->getSamplers().m_trilinearClampShadow);
 
 		rgraphCtx.bindTexture(set, kMaterialBindingDepthRt, m_r->getDepthDownscale().getHiZRt(), kHiZHalfSurface);
 		rgraphCtx.bindColorTexture(set, kMaterialBindingLightVolume, m_r->getVolumetricLightingAccumulation().getRt());

+ 1 - 1
AnKi/Renderer/IndirectDiffuseProbes.cpp

@@ -606,7 +606,7 @@ void IndirectDiffuseProbes::runShadowmappingInThread(RenderPassWorkContext& rgra
 	end = I32(endu);
 
 	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
-	cmdb->setPolygonOffset(1.0f, 1.0f);
+	cmdb->setPolygonOffset(kShadowsPolygonOffsetFactor, kShadowsPolygonOffsetUnits);
 
 	I32 drawcallCount = 0;
 	for(U32 faceIdx = 0; faceIdx < 6; ++faceIdx)

+ 1 - 1
AnKi/Renderer/ProbeReflections.cpp

@@ -704,7 +704,7 @@ void ProbeReflections::runShadowMapping(RenderPassWorkContext& rgraphCtx)
 	end = I32(endu);
 
 	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
-	cmdb->setPolygonOffset(1.0f, 1.0f);
+	cmdb->setPolygonOffset(kShadowsPolygonOffsetFactor, kShadowsPolygonOffsetUnits);
 
 	I32 drawcallCount = 0;
 	for(U32 faceIdx = 0; faceIdx < 6; ++faceIdx)

+ 6 - 0
AnKi/Renderer/Renderer.cpp

@@ -286,6 +286,12 @@ Error Renderer::initInternal(UVec2 swapchainResolution)
 
 		sinit.m_lodBias = scalingMipBias;
 		m_samplers.m_trilinearRepeatAnisoResolutionScalingBias = m_gr->newSampler(sinit);
+
+		sinit = {};
+		sinit.m_minMagFilter = SamplingFilter::kLinear;
+		sinit.m_mipmapFilter = SamplingFilter::kLinear;
+		sinit.m_compareOperation = CompareOperation::kLessEqual;
+		m_samplers.m_trilinearClampShadow = m_gr->newSampler(sinit);
 	}
 
 	for(U32 i = 0; i < m_jitterOffsets.getSize(); ++i)

+ 1 - 0
AnKi/Renderer/Renderer.h

@@ -33,6 +33,7 @@ public:
 	SamplerPtr m_trilinearRepeat;
 	SamplerPtr m_trilinearRepeatAniso;
 	SamplerPtr m_trilinearRepeatAnisoResolutionScalingBias;
+	SamplerPtr m_trilinearClampShadow;
 };
 
 /// Offscreen renderer.

+ 168 - 422
AnKi/Renderer/ShadowMapping.cpp

@@ -12,325 +12,126 @@
 
 namespace anki {
 
-class ShadowMapping::Scratch::WorkItem
+class ShadowMapping::LightToRenderTempInfo
 {
 public:
 	UVec4 m_viewport;
 	RenderQueue* m_renderQueue;
-	U32 m_firstRenderableElement;
-	U32 m_renderableElementCount;
-	U32 m_threadPoolTaskIdx;
+	U32 m_drawcallCount;
 	U32 m_renderQueueElementsLod;
 };
 
-class ShadowMapping::Scratch::LightToRenderToScratchInfo
+class ShadowMapping::ThreadWorkItem
 {
 public:
 	UVec4 m_viewport;
 	RenderQueue* m_renderQueue;
-	U32 m_drawcallCount;
+	U32 m_firstRenderableElement;
+	U32 m_renderableElementCount;
+	U32 m_threadPoolTaskIdx;
 	U32 m_renderQueueElementsLod;
 };
 
-class ShadowMapping::Atlas::ResolveWorkItem
-{
-public:
-	Vec4 m_uvInBounds; ///< Bounds used to avoid blurring neighbour tiles.
-	Vec4 m_uvIn; ///< UV + size that point to the scratch buffer.
-	UVec4 m_viewportOut; ///< Viewport in the atlas RT.
-	Bool m_blur;
-};
-
 ShadowMapping::~ShadowMapping()
 {
 }
 
 Error ShadowMapping::init()
 {
-	ANKI_R_LOGV("Initializing shadowmapping")
-
 	const Error err = initInternal();
 	if(err)
 	{
 		ANKI_R_LOGE("Failed to initialize shadowmapping");
 	}
-	else
-	{
-		ANKI_R_LOGV("Shadowmapping initialized. Scratch size %ux%u, atlas size %ux%u",
-					m_scratch.m_tileCountX * m_scratch.m_tileResolution,
-					m_scratch.m_tileCountY * m_scratch.m_tileResolution,
-					m_atlas.m_tileCountBothAxis * m_atlas.m_tileResolution,
-					m_atlas.m_tileCountBothAxis * m_atlas.m_tileResolution);
-	}
 
 	return err;
 }
 
-Error ShadowMapping::initScratch()
-{
-	// Init the shadowmaps and FBs
-	{
-		m_scratch.m_tileCountX = getConfig().getRShadowMappingScratchTileCountX();
-		m_scratch.m_tileCountY = getConfig().getRShadowMappingScratchTileCountY();
-		m_scratch.m_tileResolution = getConfig().getRShadowMappingTileResolution();
-
-		// RT
-		m_scratch.m_rtDescr = m_r->create2DRenderTargetDescription(m_scratch.m_tileResolution * m_scratch.m_tileCountX,
-																   m_scratch.m_tileResolution * m_scratch.m_tileCountY,
-																   m_r->getDepthNoStencilFormat(), "SM scratch");
-		m_scratch.m_rtDescr.bake();
-
-		// FB
-		m_scratch.m_fbDescr.m_depthStencilAttachment.m_loadOperation = AttachmentLoadOperation::kClear;
-		m_scratch.m_fbDescr.m_depthStencilAttachment.m_clearValue.m_depthStencil.m_depth = 1.0f;
-		m_scratch.m_fbDescr.m_depthStencilAttachment.m_aspect = DepthStencilAspectBit::kDepth;
-		m_scratch.m_fbDescr.bake();
-	}
-
-	m_scratch.m_tileAlloc.init(&getMemoryPool(), m_scratch.m_tileCountX, m_scratch.m_tileCountY, kMaxLodCount, false);
-
-	return Error::kNone;
-}
-
-Error ShadowMapping::initAtlas()
+Error ShadowMapping::initInternal()
 {
-	const Bool preferCompute = getConfig().getRPreferCompute();
-
 	// Init RT
 	{
-		m_atlas.m_tileResolution = getConfig().getRShadowMappingTileResolution();
-		m_atlas.m_tileCountBothAxis = getConfig().getRShadowMappingTileCountPerRowOrColumn();
+		m_tileResolution = getConfig().getRShadowMappingTileResolution();
+		m_tileCountBothAxis = getConfig().getRShadowMappingTileCountPerRowOrColumn();
+
+		ANKI_R_LOGV("Initializing shadowmapping. Atlas resolution %ux%u", m_tileResolution * m_tileCountBothAxis,
+					m_tileResolution * m_tileCountBothAxis);
 
 		// RT
-		const Format texFormat = (ANKI_EVSM4) ? Format::kR32G32B32A32_Sfloat : Format::kR32G32_Sfloat;
-		TextureUsageBit usage = TextureUsageBit::kSampledFragment | TextureUsageBit::kSampledCompute;
-		usage |= (preferCompute) ? TextureUsageBit::kImageComputeWrite : TextureUsageBit::kAllFramebuffer;
-		TextureInitInfo texinit = m_r->create2DRenderTargetInitInfo(
-			m_atlas.m_tileResolution * m_atlas.m_tileCountBothAxis,
-			m_atlas.m_tileResolution * m_atlas.m_tileCountBothAxis, texFormat, usage, "SM atlas");
+		const TextureUsageBit usage =
+			TextureUsageBit::kSampledFragment | TextureUsageBit::kSampledCompute | TextureUsageBit::kAllFramebuffer;
+		TextureInitInfo texinit = m_r->create2DRenderTargetInitInfo(m_tileResolution * m_tileCountBothAxis,
+																	m_tileResolution * m_tileCountBothAxis,
+																	Format::kD16_Unorm, usage, "ShadowAtlas");
 		ClearValue clearVal;
 		clearVal.m_colorf[0] = 1.0f;
-		m_atlas.m_tex = m_r->createAndClearRenderTarget(texinit, TextureUsageBit::kSampledFragment, clearVal);
+		m_atlasTex = m_r->createAndClearRenderTarget(texinit, TextureUsageBit::kSampledFragment, clearVal);
 	}
 
 	// Tiles
-	m_atlas.m_tileAlloc.init(&getMemoryPool(), m_atlas.m_tileCountBothAxis, m_atlas.m_tileCountBothAxis, kMaxLodCount,
-							 true);
+	m_tileAlloc.init(&getMemoryPool(), m_tileCountBothAxis, m_tileCountBothAxis, kTileAllocLodCount, true);
 
-	// Programs and shaders
-	{
-		ANKI_CHECK(getResourceManager().loadResource((preferCompute) ? "ShaderBinaries/EvsmCompute.ankiprogbin"
-																	 : "ShaderBinaries/EvsmRaster.ankiprogbin",
-													 m_atlas.m_resolveProg));
-
-		ShaderProgramResourceVariantInitInfo variantInitInfo(m_atlas.m_resolveProg);
-		variantInitInfo.addConstant("kInputTextureSize", UVec2(m_scratch.m_tileCountX * m_scratch.m_tileResolution,
-															   m_scratch.m_tileCountY * m_scratch.m_tileResolution));
-
-		if(!preferCompute)
-		{
-			variantInitInfo.addConstant("kFramebufferSize",
-										UVec2(m_atlas.m_tileCountBothAxis * m_atlas.m_tileResolution));
-		}
+	m_fbDescr.m_depthStencilAttachment.m_aspect = DepthStencilAspectBit::kDepth;
+	m_fbDescr.m_depthStencilAttachment.m_loadOperation = AttachmentLoadOperation::kLoad;
+	m_fbDescr.bake();
 
-		const ShaderProgramResourceVariant* variant;
-		m_atlas.m_resolveProg->getOrCreateVariant(variantInitInfo, variant);
-		m_atlas.m_resolveGrProg = variant->getProgram();
-	}
+	ANKI_CHECK(
+		getResourceManager().loadResource("ShaderBinaries/ShadowmappingClearDepth.ankiprogbin", m_clearDepthProg));
+	const ShaderProgramResourceVariant* variant;
+	m_clearDepthProg->getOrCreateVariant(variant);
+	m_clearDepthGrProg = variant->getProgram();
 
-	m_atlas.m_fbDescr.m_colorAttachmentCount = 1;
-	m_atlas.m_fbDescr.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::kLoad;
-	m_atlas.m_fbDescr.bake();
-
-	return Error::kNone;
-}
-
-Error ShadowMapping::initInternal()
-{
-	ANKI_CHECK(initScratch());
-	ANKI_CHECK(initAtlas());
 	return Error::kNone;
 }
 
-void ShadowMapping::runAtlas(RenderPassWorkContext& rgraphCtx)
+void ShadowMapping::populateRenderGraph(RenderingContext& ctx)
 {
-	ANKI_ASSERT(m_atlas.m_resolveWorkItems.getSize());
 	ANKI_TRACE_SCOPED_EVENT(R_SM);
 
-	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
-
-	// Allocate and populate uniforms
-	EvsmResolveUniforms* uniforms = allocateAndBindStorage<EvsmResolveUniforms*>(
-		m_atlas.m_resolveWorkItems.getSize() * sizeof(EvsmResolveUniforms), cmdb, 0, 0);
-	for(U32 i = 0; i < m_atlas.m_resolveWorkItems.getSize(); ++i)
-	{
-		EvsmResolveUniforms& uni = uniforms[i];
-		const Atlas::ResolveWorkItem& workItem = m_atlas.m_resolveWorkItems[i];
-
-		uni.m_viewportXY = IVec2(workItem.m_viewportOut.xy());
-		uni.m_viewportZW = Vec2(workItem.m_viewportOut.zw());
-
-		uni.m_uvScale = workItem.m_uvIn.zw();
-		uni.m_uvTranslation = workItem.m_uvIn.xy();
-
-		uni.m_uvMin = workItem.m_uvInBounds.xy();
-		uni.m_uvMax = workItem.m_uvInBounds.xy() + workItem.m_uvInBounds.zw();
-
-		uni.m_blur = workItem.m_blur;
-	}
-
-	cmdb->bindShaderProgram(m_atlas.m_resolveGrProg);
-
-	// Continue
-	cmdb->bindSampler(0, 1, m_r->getSamplers().m_trilinearClamp);
-	rgraphCtx.bindTexture(0, 2, m_scratch.m_rt, TextureSubresourceInfo(DepthStencilAspectBit::kDepth));
+	RenderGraphDescription& rgraph = ctx.m_renderGraphDescr;
 
-	if(getConfig().getRPreferCompute())
+	// Import
+	if(ANKI_LIKELY(m_rtImportedOnce))
 	{
-		rgraphCtx.bindImage(0, 3, m_atlas.m_rt);
-
-		constexpr U32 workgroupSize = 8;
-		ANKI_ASSERT(m_atlas.m_tileResolution >= workgroupSize && (m_atlas.m_tileResolution % workgroupSize) == 0);
-
-		cmdb->dispatchCompute(m_atlas.m_tileResolution / workgroupSize, m_atlas.m_tileResolution / workgroupSize,
-							  m_atlas.m_resolveWorkItems.getSize());
+		m_runCtx.m_rt = rgraph.importRenderTarget(m_atlasTex);
 	}
 	else
 	{
-		cmdb->setViewport(0, 0, m_atlas.m_tex->getWidth(), m_atlas.m_tex->getHeight());
-
-		cmdb->drawArrays(PrimitiveTopology::kTriangles, 6, m_atlas.m_resolveWorkItems.getSize());
+		m_runCtx.m_rt = rgraph.importRenderTarget(m_atlasTex, TextureUsageBit::kSampledFragment);
+		m_rtImportedOnce = true;
 	}
-}
-
-void ShadowMapping::runShadowMapping(RenderPassWorkContext& rgraphCtx)
-{
-	ANKI_ASSERT(m_scratch.m_workItems.getSize());
-	ANKI_TRACE_SCOPED_EVENT(R_SM);
-
-	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
-	const U threadIdx = rgraphCtx.m_currentSecondLevelCommandBufferIndex;
-
-	for(Scratch::WorkItem& work : m_scratch.m_workItems)
-	{
-		if(work.m_threadPoolTaskIdx != threadIdx)
-		{
-			continue;
-		}
-
-		// Set state
-		cmdb->setViewport(work.m_viewport[0], work.m_viewport[1], work.m_viewport[2], work.m_viewport[3]);
-		cmdb->setScissor(work.m_viewport[0], work.m_viewport[1], work.m_viewport[2], work.m_viewport[3]);
-
-		RenderableDrawerArguments args;
-		args.m_viewMatrix = work.m_renderQueue->m_viewMatrix;
-		args.m_cameraTransform = Mat3x4::getIdentity(); // Don't care
-		args.m_viewProjectionMatrix = work.m_renderQueue->m_viewProjectionMatrix;
-		args.m_previousViewProjectionMatrix = Mat4::getIdentity(); // Don't care
-		args.m_sampler = m_r->getSamplers().m_trilinearRepeatAniso;
-		args.m_minLod = args.m_maxLod = work.m_renderQueueElementsLod;
-
-		m_r->getSceneDrawer().drawRange(RenderingTechnique::kShadow, args,
-										work.m_renderQueue->m_renderables.getBegin() + work.m_firstRenderableElement,
-										work.m_renderQueue->m_renderables.getBegin() + work.m_firstRenderableElement
-											+ work.m_renderableElementCount,
-										cmdb);
-	}
-}
-
-void ShadowMapping::populateRenderGraph(RenderingContext& ctx)
-{
-	ANKI_TRACE_SCOPED_EVENT(R_SM);
 
 	// First process the lights
-	U32 threadCountForScratchPass = 0;
-	processLights(ctx, threadCountForScratchPass);
+	U32 threadCountForPass = 0;
+	processLights(ctx, threadCountForPass);
 
 	// Build the render graph
-	RenderGraphDescription& rgraph = ctx.m_renderGraphDescr;
-	if(m_scratch.m_workItems.getSize())
+	if(m_runCtx.m_workItems.getSize())
 	{
 		// Will have to create render passes
 
-		// Scratch pass
-		{
-			// Compute render area
-			const U32 minx = 0, miny = 0;
-			const U32 height = m_scratch.m_maxViewportHeight;
-			const U32 width = m_scratch.m_maxViewportWidth;
-
-			GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("SM scratch");
-
-			m_scratch.m_rt = rgraph.newRenderTarget(m_scratch.m_rtDescr);
-			pass.setFramebufferInfo(m_scratch.m_fbDescr, {}, m_scratch.m_rt, {}, minx, miny, width, height);
-			ANKI_ASSERT(threadCountForScratchPass
-						&& threadCountForScratchPass <= m_r->getThreadHive().getThreadCount());
-			pass.setWork(threadCountForScratchPass, [this](RenderPassWorkContext& rgraphCtx) {
-				runShadowMapping(rgraphCtx);
-			});
-
-			TextureSubresourceInfo subresource = TextureSubresourceInfo(DepthStencilAspectBit::kDepth);
-			pass.newTextureDependency(m_scratch.m_rt, TextureUsageBit::kAllFramebuffer, subresource);
-		}
+		// Compute render area
+		const U32 minx = m_runCtx.m_fullViewport[0];
+		const U32 miny = m_runCtx.m_fullViewport[1];
+		const U32 width = m_runCtx.m_fullViewport[2] - m_runCtx.m_fullViewport[0];
+		const U32 height = m_runCtx.m_fullViewport[3] - m_runCtx.m_fullViewport[1];
 
-		// Atlas pass
-		{
-			if(ANKI_LIKELY(m_atlas.m_rtImportedOnce))
-			{
-				m_atlas.m_rt = rgraph.importRenderTarget(m_atlas.m_tex);
-			}
-			else
-			{
-				m_atlas.m_rt = rgraph.importRenderTarget(m_atlas.m_tex, TextureUsageBit::kSampledFragment);
-				m_atlas.m_rtImportedOnce = true;
-			}
+		GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("ShadowMapping");
 
-			if(getConfig().getRPreferCompute())
-			{
-				ComputeRenderPassDescription& pass = rgraph.newComputeRenderPass("EVSM resolve");
-
-				pass.setWork([this](RenderPassWorkContext& rgraphCtx) {
-					runAtlas(rgraphCtx);
-				});
-
-				pass.newTextureDependency(m_scratch.m_rt, TextureUsageBit::kSampledCompute,
-										  TextureSubresourceInfo(DepthStencilAspectBit::kDepth));
-				pass.newTextureDependency(m_atlas.m_rt, TextureUsageBit::kImageComputeWrite);
-			}
-			else
-			{
-				GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("EVSM resolve");
-				pass.setFramebufferInfo(m_atlas.m_fbDescr, {m_atlas.m_rt});
+		pass.setFramebufferInfo(m_fbDescr, {}, m_runCtx.m_rt, {}, minx, miny, width, height);
+		ANKI_ASSERT(threadCountForPass && threadCountForPass <= m_r->getThreadHive().getThreadCount());
+		pass.setWork(threadCountForPass, [this](RenderPassWorkContext& rgraphCtx) {
+			runShadowMapping(rgraphCtx);
+		});
 
-				pass.setWork([this](RenderPassWorkContext& rgraphCtx) {
-					runAtlas(rgraphCtx);
-				});
-
-				pass.newTextureDependency(m_scratch.m_rt, TextureUsageBit::kSampledFragment,
-										  TextureSubresourceInfo(DepthStencilAspectBit::kDepth));
-				pass.newTextureDependency(m_atlas.m_rt,
-										  TextureUsageBit::kFramebufferRead | TextureUsageBit::kFramebufferWrite);
-			}
-		}
-	}
-	else
-	{
-		// No need for shadowmapping passes, just import the atlas
-		if(ANKI_LIKELY(m_atlas.m_rtImportedOnce))
-		{
-			m_atlas.m_rt = rgraph.importRenderTarget(m_atlas.m_tex);
-		}
-		else
-		{
-			m_atlas.m_rt = rgraph.importRenderTarget(m_atlas.m_tex, TextureUsageBit::kSampledFragment);
-			m_atlas.m_rtImportedOnce = true;
-		}
+		TextureSubresourceInfo subresource = TextureSubresourceInfo(DepthStencilAspectBit::kDepth);
+		pass.newTextureDependency(m_runCtx.m_rt, TextureUsageBit::kAllFramebuffer, subresource);
 	}
 }
 
 Mat4 ShadowMapping::createSpotLightTextureMatrix(const UVec4& viewport) const
 {
-	const F32 atlasSize = F32(m_atlas.m_tileResolution * m_atlas.m_tileCountBothAxis);
+	const F32 atlasSize = F32(m_tileResolution * m_tileCountBothAxis);
 #if ANKI_COMPILER_GCC_COMPATIBLE
 #	pragma GCC diagnostic push
 #	pragma GCC diagnostic ignored "-Wpedantic" // Because GCC and clang throw an incorrect warning
@@ -348,27 +149,25 @@ Mat4 ShadowMapping::createSpotLightTextureMatrix(const UVec4& viewport) const
 				0.0f, 0.0f, 0.0f, 1.0f);
 }
 
-void ShadowMapping::chooseLod(const Vec4& cameraOrigin, const PointLightQueueElement& light, Bool& blurAtlas,
-							  U32& tileBufferLod, U32& renderQueueElementsLod) const
+void ShadowMapping::chooseLods(const Vec4& cameraOrigin, const PointLightQueueElement& light, U32& tileBufferLod,
+							   U32& renderQueueElementsLod) const
 {
 	const F32 distFromTheCamera = (cameraOrigin - light.m_worldPosition.xyz0()).getLength() - light.m_radius;
 	if(distFromTheCamera < getConfig().getLod0MaxDistance())
 	{
-		ANKI_ASSERT(m_pointLightsMaxLod == 1);
-		blurAtlas = true;
+		ANKI_ASSERT(kPointLightMaxTileLod == 1);
 		tileBufferLod = 1;
 		renderQueueElementsLod = 0;
 	}
 	else
 	{
-		blurAtlas = false;
 		tileBufferLod = 0;
 		renderQueueElementsLod = kMaxLodCount - 1;
 	}
 }
 
-void ShadowMapping::chooseLod(const Vec4& cameraOrigin, const SpotLightQueueElement& light, Bool& blurAtlas,
-							  U32& tileBufferLod, U32& renderQueueElementsLod) const
+void ShadowMapping::chooseLods(const Vec4& cameraOrigin, const SpotLightQueueElement& light, U32& tileBufferLod,
+							   U32& renderQueueElementsLod) const
 {
 	// Get some data
 	const Vec4 coneOrigin = light.m_worldTransform.getTranslationPart().xyz0();
@@ -383,29 +182,24 @@ void ShadowMapping::chooseLod(const Vec4& cameraOrigin, const SpotLightQueueElem
 
 	if(distFromTheCamera < getConfig().getLod0MaxDistance())
 	{
-		blurAtlas = true;
 		tileBufferLod = 2;
 		renderQueueElementsLod = 0;
 	}
 	else if(distFromTheCamera < getConfig().getLod1MaxDistance())
 	{
-		blurAtlas = false;
 		tileBufferLod = 1;
 		renderQueueElementsLod = kMaxLodCount - 1;
 	}
 	else
 	{
-		blurAtlas = false;
 		tileBufferLod = 0;
 		renderQueueElementsLod = kMaxLodCount - 1;
 	}
 }
 
-TileAllocatorResult ShadowMapping::allocateTilesAndScratchTiles(U64 lightUuid, U32 faceCount, const U64* faceTimestamps,
-																const U32* faceIndices, const U32* drawcallsCount,
-																const U32* lods, UVec4* atlasTileViewports,
-																UVec4* scratchTileViewports,
-																TileAllocatorResult* subResults)
+Bool ShadowMapping::allocateAtlasTiles(U64 lightUuid, U32 faceCount, const U64* faceTimestamps, const U32* faceIndices,
+									   const U32* drawcallsCount, const U32* lods, UVec4* atlasTileViewports,
+									   TileAllocatorResult* subResults)
 {
 	ANKI_ASSERT(lightUuid > 0);
 	ANKI_ASSERT(faceCount > 0);
@@ -414,16 +208,13 @@ TileAllocatorResult ShadowMapping::allocateTilesAndScratchTiles(U64 lightUuid, U
 	ANKI_ASSERT(drawcallsCount);
 	ANKI_ASSERT(lods);
 
-	TileAllocatorResult res = TileAllocatorResult::kAllocationFailed;
-
-	// Allocate atlas tiles first. They may be cached and that will affect how many scratch tiles we'll need
 	for(U i = 0; i < faceCount; ++i)
 	{
-		Array<U32, 4> tileRanges;
-		res = m_atlas.m_tileAlloc.allocate(m_r->getGlobalTimestamp(), faceTimestamps[i], lightUuid, faceIndices[i],
-										   drawcallsCount[i], lods[i], tileRanges);
+		Array<U32, 4> tileViewport;
+		subResults[i] = m_tileAlloc.allocate(m_r->getGlobalTimestamp(), faceTimestamps[i], lightUuid, faceIndices[i],
+											 drawcallsCount[i], lods[i], tileViewport);
 
-		if(res == TileAllocatorResult::kAllocationFailed)
+		if(subResults[i] == TileAllocatorResult::kAllocationFailed)
 		{
 			ANKI_R_LOGW("There is not enough space in the shadow atlas for more shadow maps. "
 						"Increase the RShadowMappingTileCountPerRowOrColumn or decrease the scene's shadow casters");
@@ -431,79 +222,55 @@ TileAllocatorResult ShadowMapping::allocateTilesAndScratchTiles(U64 lightUuid, U
 			// Invalidate cache entries for what we already allocated
 			for(U j = 0; j < i; ++j)
 			{
-				m_atlas.m_tileAlloc.invalidateCache(lightUuid, faceIndices[j]);
+				m_tileAlloc.invalidateCache(lightUuid, faceIndices[j]);
 			}
 
-			return res;
+			return false;
 		}
 
-		subResults[i] = res;
-
 		// Set viewport
-		atlasTileViewports[i] = UVec4(tileRanges) * m_atlas.m_tileResolution;
-	}
-
-	// Allocate scratch tiles
-	for(U i = 0; i < faceCount; ++i)
-	{
-		if(subResults[i] == TileAllocatorResult::kCached)
-		{
-			continue;
-		}
-
-		ANKI_ASSERT(subResults[i] == TileAllocatorResult::kAllocationSucceded);
-
-		Array<U32, 4> tileRanges;
-		res = m_scratch.m_tileAlloc.allocate(m_r->getGlobalTimestamp(), faceTimestamps[i], lightUuid, faceIndices[i],
-											 drawcallsCount[i], lods[i], tileRanges);
-
-		if(res == TileAllocatorResult::kAllocationFailed)
-		{
-			ANKI_R_LOGW("Don't have enough space in the scratch shadow mapping buffer. "
-						"If you see this message too often increase RShadowMappingScratchTileCountX/Y");
-
-			// Invalidate atlas tiles
-			for(U j = 0; j < faceCount; ++j)
-			{
-				m_atlas.m_tileAlloc.invalidateCache(lightUuid, faceIndices[j]);
-			}
+		const UVec4 viewport = UVec4(tileViewport) * m_tileResolution;
+		atlasTileViewports[i] = viewport;
 
-			return res;
-		}
-
-		// Fix viewport
-		scratchTileViewports[i] = UVec4(tileRanges) * m_scratch.m_tileResolution;
-
-		// Update the max view width
-		m_scratch.m_maxViewportWidth =
-			max(m_scratch.m_maxViewportWidth, scratchTileViewports[i][0] + scratchTileViewports[i][2]);
-		m_scratch.m_maxViewportHeight =
-			max(m_scratch.m_maxViewportHeight, scratchTileViewports[i][1] + scratchTileViewports[i][3]);
+		m_runCtx.m_fullViewport[0] = min(m_runCtx.m_fullViewport[0], viewport[0]);
+		m_runCtx.m_fullViewport[1] = min(m_runCtx.m_fullViewport[1], viewport[1]);
+		m_runCtx.m_fullViewport[2] = max(m_runCtx.m_fullViewport[2], viewport[0] + viewport[2]);
+		m_runCtx.m_fullViewport[3] = max(m_runCtx.m_fullViewport[3], viewport[1] + viewport[3]);
 	}
 
-	return res;
+	return true;
+}
+
+void ShadowMapping::newWorkItems(const UVec4& atlasViewport, RenderQueue* lightRenderQueue, U32 renderQueueElementsLod,
+								 DynamicArrayRaii<LightToRenderTempInfo>& workItems, U32& drawcallCount) const
+{
+	LightToRenderTempInfo toRender;
+	toRender.m_renderQueue = lightRenderQueue;
+	toRender.m_viewport = atlasViewport;
+	toRender.m_drawcallCount = lightRenderQueue->m_renderables.getSize();
+	toRender.m_renderQueueElementsLod = renderQueueElementsLod;
+
+	workItems.emplaceBack(toRender);
+	drawcallCount += toRender.m_drawcallCount;
 }
 
-void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScratchPass)
+void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForPass)
 {
-	// Reset the scratch viewport width
-	m_scratch.m_maxViewportWidth = 0;
-	m_scratch.m_maxViewportHeight = 0;
+	m_runCtx.m_fullViewport = UVec4(kMaxU32, kMaxU32, kMinU32, kMinU32);
 
 	// Vars
 	const Vec4 cameraOrigin = ctx.m_renderQueue->m_cameraTransform.getTranslationPart().xyz0();
-	DynamicArrayRaii<Scratch::LightToRenderToScratchInfo> lightsToRender(ctx.m_tempPool);
+	DynamicArrayRaii<LightToRenderTempInfo> lightsToRender(ctx.m_tempPool);
 	U32 drawcallCount = 0;
-	DynamicArrayRaii<Atlas::ResolveWorkItem> atlasWorkItems(ctx.m_tempPool);
 
 	// First thing, allocate an empty tile for empty faces of point lights
 	UVec4 emptyTileViewport;
 	{
-		Array<U32, 4> tileRange;
+		Array<U32, 4> tileViewport;
 		[[maybe_unused]] const TileAllocatorResult res =
-			m_atlas.m_tileAlloc.allocate(m_r->getGlobalTimestamp(), 1, kMaxU64, 0, 1, m_pointLightsMaxLod, tileRange);
+			m_tileAlloc.allocate(m_r->getGlobalTimestamp(), 1, kMaxU64, 0, 1, kPointLightMaxTileLod, tileViewport);
 
-		emptyTileViewport = UVec4(tileRange);
+		emptyTileViewport = UVec4(tileViewport);
 
 #if ANKI_ENABLE_ASSERTIONS
 		static Bool firstRun = true;
@@ -528,11 +295,9 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 		Array<U32, kMaxShadowCascades> cascadeIndices;
 		Array<U32, kMaxShadowCascades> drawcallCounts;
 		Array<UVec4, kMaxShadowCascades> atlasViewports;
-		Array<UVec4, kMaxShadowCascades> scratchViewports;
 		Array<TileAllocatorResult, kMaxShadowCascades> subResults;
 		Array<U32, kMaxShadowCascades> lods;
 		Array<U32, kMaxShadowCascades> renderQueueElementsLods;
-		Array<Bool, kMaxShadowCascades> blurAtlass;
 
 		U32 activeCascades = 0;
 
@@ -548,7 +313,6 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 				drawcallCounts[activeCascades] = 1; // Doesn't matter
 
 				// Change the quality per cascade
-				blurAtlass[activeCascades] = (cascade <= 1);
 				lods[activeCascades] = (cascade <= 1) ? (kMaxLodCount - 1) : (lods[0] - 1);
 				renderQueueElementsLods[activeCascades] = (cascade == 0) ? 0 : (kMaxLodCount - 1);
 
@@ -558,10 +322,8 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 
 		const Bool allocationFailed =
 			activeCascades == 0
-			|| allocateTilesAndScratchTiles(light.m_uuid, activeCascades, &timestamps[0], &cascadeIndices[0],
-											&drawcallCounts[0], &lods[0], &atlasViewports[0], &scratchViewports[0],
-											&subResults[0])
-				   == TileAllocatorResult::kAllocationFailed;
+			|| !allocateAtlasTiles(light.m_uuid, activeCascades, &timestamps[0], &cascadeIndices[0], &drawcallCounts[0],
+								   &lods[0], &atlasViewports[0], &subResults[0]);
 
 		if(!allocationFailed)
 		{
@@ -578,10 +340,8 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 						createSpotLightTextureMatrix(atlasViewports[activeCascades]) * light.m_textureMatrices[cascade];
 
 					// Push work
-					newScratchAndAtlasResloveRenderWorkItems(
-						atlasViewports[activeCascades], scratchViewports[activeCascades], blurAtlass[activeCascades],
-						light.m_shadowRenderQueues[cascade], renderQueueElementsLods[activeCascades], lightsToRender,
-						atlasWorkItems, drawcallCount);
+					newWorkItems(atlasViewports[activeCascades], light.m_shadowRenderQueues[cascade],
+								 renderQueueElementsLods[activeCascades], lightsToRender, drawcallCount);
 
 					++activeCascades;
 				}
@@ -615,14 +375,12 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 		Array<U32, 6> faceIndices;
 		Array<U32, 6> drawcallCounts;
 		Array<UVec4, 6> atlasViewports;
-		Array<UVec4, 6> scratchViewports;
 		Array<TileAllocatorResult, 6> subResults;
 		Array<U32, 6> lods;
 		U32 numOfFacesThatHaveDrawcalls = 0;
 
-		Bool blurAtlas;
 		U32 lod, renderQueueElementsLod;
-		chooseLod(cameraOrigin, light, blurAtlas, lod, renderQueueElementsLod);
+		chooseLods(cameraOrigin, light, lod, renderQueueElementsLod);
 
 		for(U32 face = 0; face < 6; ++face)
 		{
@@ -645,18 +403,27 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 
 		const Bool allocationFailed =
 			numOfFacesThatHaveDrawcalls == 0
-			|| allocateTilesAndScratchTiles(light.m_uuid, numOfFacesThatHaveDrawcalls, &timestamps[0], &faceIndices[0],
-											&drawcallCounts[0], &lods[0], &atlasViewports[0], &scratchViewports[0],
-											&subResults[0])
-				   == TileAllocatorResult::kAllocationFailed;
+			|| !allocateAtlasTiles(light.m_uuid, numOfFacesThatHaveDrawcalls, &timestamps[0], &faceIndices[0],
+								   &drawcallCounts[0], &lods[0], &atlasViewports[0], &subResults[0]);
 
 		if(!allocationFailed)
 		{
 			// All good, update the lights
 
-			const F32 atlasResolution = F32(m_atlas.m_tileResolution * m_atlas.m_tileCountBothAxis);
+			// Remove a few texels to avoid bilinear filtering bleeding
+			F32 texelsBorder;
+			if(getConfig().getRShadowMappingPcf())
+			{
+				texelsBorder = 2.0f; // 2 texels
+			}
+			else
+			{
+				texelsBorder = 0.5f; // Half texel
+			}
+
+			const F32 atlasResolution = F32(m_tileResolution * m_tileCountBothAxis);
 			F32 superTileSize = F32(atlasViewports[0][2]); // Should be the same for all tiles and faces
-			superTileSize -= 1.0f; // Remove 2 half texels to avoid bilinear filtering bleeding
+			superTileSize -= texelsBorder * 2.0f; // Remove from both sides
 
 			light.m_shadowAtlasTileSize = superTileSize / atlasResolution;
 
@@ -668,17 +435,15 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 					// Has drawcalls, asigned it to a tile
 
 					const UVec4& atlasViewport = atlasViewports[numOfFacesThatHaveDrawcalls];
-					const UVec4& scratchViewport = scratchViewports[numOfFacesThatHaveDrawcalls];
 
 					// Add a half texel to the viewport's start to avoid bilinear filtering bleeding
-					light.m_shadowAtlasTileOffsets[face].x() = (F32(atlasViewport[0]) + 0.5f) / atlasResolution;
-					light.m_shadowAtlasTileOffsets[face].y() = (F32(atlasViewport[1]) + 0.5f) / atlasResolution;
+					light.m_shadowAtlasTileOffsets[face].x() = (F32(atlasViewport[0]) + texelsBorder) / atlasResolution;
+					light.m_shadowAtlasTileOffsets[face].y() = (F32(atlasViewport[1]) + texelsBorder) / atlasResolution;
 
 					if(subResults[numOfFacesThatHaveDrawcalls] != TileAllocatorResult::kCached)
 					{
-						newScratchAndAtlasResloveRenderWorkItems(
-							atlasViewport, scratchViewport, blurAtlas, light.m_shadowRenderQueues[face],
-							renderQueueElementsLod, lightsToRender, atlasWorkItems, drawcallCount);
+						newWorkItems(atlasViewport, light.m_shadowRenderQueues[face], renderQueueElementsLod,
+									 lightsToRender, drawcallCount);
 					}
 
 					++numOfFacesThatHaveDrawcalls;
@@ -691,8 +456,8 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 					atlasViewport[2] = U32(superTileSize);
 					atlasViewport[3] = U32(superTileSize);
 
-					light.m_shadowAtlasTileOffsets[face].x() = (F32(atlasViewport[0]) + 0.5f) / atlasResolution;
-					light.m_shadowAtlasTileOffsets[face].y() = (F32(atlasViewport[1]) + 0.5f) / atlasResolution;
+					light.m_shadowAtlasTileOffsets[face].x() = (F32(atlasViewport[0]) + texelsBorder) / atlasResolution;
+					light.m_shadowAtlasTileOffsets[face].y() = (F32(atlasViewport[1]) + texelsBorder) / atlasResolution;
 				}
 			}
 		}
@@ -718,16 +483,13 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 		UVec4 scratchViewport;
 		const U32 localDrawcallCount = light.m_shadowRenderQueue->m_renderables.getSize();
 
-		Bool blurAtlas;
 		U32 lod, renderQueueElementsLod;
-		chooseLod(cameraOrigin, light, blurAtlas, lod, renderQueueElementsLod);
+		chooseLods(cameraOrigin, light, lod, renderQueueElementsLod);
 
 		const Bool allocationFailed =
 			localDrawcallCount == 0
-			|| allocateTilesAndScratchTiles(
-				   light.m_uuid, 1, &light.m_shadowRenderQueue->m_shadowRenderablesLastUpdateTimestamp, &faceIdx,
-				   &localDrawcallCount, &lod, &atlasViewport, &scratchViewport, &subResult)
-				   == TileAllocatorResult::kAllocationFailed;
+			|| !allocateAtlasTiles(light.m_uuid, 1, &light.m_shadowRenderQueue->m_shadowRenderablesLastUpdateTimestamp,
+								   &faceIdx, &localDrawcallCount, &lod, &atlasViewport, &subResult);
 
 		if(!allocationFailed)
 		{
@@ -738,9 +500,8 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 
 			if(subResult != TileAllocatorResult::kCached)
 			{
-				newScratchAndAtlasResloveRenderWorkItems(atlasViewport, scratchViewport, blurAtlas,
-														 light.m_shadowRenderQueue, renderQueueElementsLod,
-														 lightsToRender, atlasWorkItems, drawcallCount);
+				newWorkItems(atlasViewport, light.m_shadowRenderQueue, renderQueueElementsLod, lightsToRender,
+							 drawcallCount);
 			}
 		}
 		else
@@ -753,13 +514,13 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 	// Split the work that will happen in the scratch buffer
 	if(lightsToRender.getSize())
 	{
-		DynamicArrayRaii<Scratch::WorkItem> workItems(ctx.m_tempPool);
-		Scratch::LightToRenderToScratchInfo* lightToRender = lightsToRender.getBegin();
+		DynamicArrayRaii<ThreadWorkItem> workItems(ctx.m_tempPool);
+		LightToRenderTempInfo* lightToRender = lightsToRender.getBegin();
 		U32 lightToRenderDrawcallCount = lightToRender->m_drawcallCount;
-		const Scratch::LightToRenderToScratchInfo* lightToRenderEnd = lightsToRender.getEnd();
+		const LightToRenderTempInfo* lightToRenderEnd = lightsToRender.getEnd();
 
 		const U32 threadCount = computeNumberOfSecondLevelCommandBuffers(drawcallCount);
-		threadCountForScratchPass = threadCount;
+		threadCountForPass = threadCount;
 		for(U32 taskId = 0; taskId < threadCount; ++taskId)
 		{
 			U32 start, end;
@@ -774,7 +535,7 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 				ANKI_ASSERT(lightToRender != lightToRenderEnd);
 				const U32 workItemDrawcallCount = min(lightToRenderDrawcallCount, taskDrawcallCount);
 
-				Scratch::WorkItem workItem;
+				ThreadWorkItem workItem;
 				workItem.m_viewport = lightToRender->m_viewport;
 				workItem.m_renderQueue = lightToRender->m_renderQueue;
 				workItem.m_firstRenderableElement = lightToRender->m_drawcallCount - lightToRenderDrawcallCount;
@@ -803,76 +564,61 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 		ANKI_ASSERT(lightsToRender.getSize() <= workItems.getSize());
 
 		// All good, store the work items for the threads to pick up
-		{
-			Scratch::WorkItem* items;
-			U32 itemSize;
-			U32 itemStorageSize;
-			workItems.moveAndReset(items, itemSize, itemStorageSize);
-
-			ANKI_ASSERT(items && itemSize && itemStorageSize);
-			m_scratch.m_workItems = WeakArray<Scratch::WorkItem>(items, itemSize);
-
-			Atlas::ResolveWorkItem* atlasItems;
-			atlasWorkItems.moveAndReset(atlasItems, itemSize, itemStorageSize);
-			ANKI_ASSERT(atlasItems && itemSize && itemStorageSize);
-			m_atlas.m_resolveWorkItems = WeakArray<Atlas::ResolveWorkItem>(atlasItems, itemSize);
-		}
+		workItems.moveAndReset(m_runCtx.m_workItems);
 	}
 	else
 	{
-		m_scratch.m_workItems = WeakArray<Scratch::WorkItem>();
-		m_atlas.m_resolveWorkItems = WeakArray<Atlas::ResolveWorkItem>();
+		m_runCtx.m_workItems = {};
 	}
 }
 
-void ShadowMapping::newScratchAndAtlasResloveRenderWorkItems(
-	const UVec4& atlasViewport, const UVec4& scratchVewport, Bool blurAtlas, RenderQueue* lightRenderQueue,
-	U32 renderQueueElementsLod, DynamicArrayRaii<Scratch::LightToRenderToScratchInfo>& scratchWorkItem,
-	DynamicArrayRaii<Atlas::ResolveWorkItem>& atlasResolveWorkItem, U32& drawcallCount) const
+void ShadowMapping::runShadowMapping(RenderPassWorkContext& rgraphCtx)
 {
-	// Scratch work item
-	{
-		Scratch::LightToRenderToScratchInfo toRender;
-		toRender.m_renderQueue = lightRenderQueue;
-		toRender.m_viewport = scratchVewport;
-		toRender.m_drawcallCount = lightRenderQueue->m_renderables.getSize();
-		toRender.m_renderQueueElementsLod = renderQueueElementsLod;
-
-		scratchWorkItem.emplaceBack(toRender);
-		drawcallCount += lightRenderQueue->m_renderables.getSize();
-	}
+	ANKI_ASSERT(m_runCtx.m_workItems.getSize());
+	ANKI_TRACE_SCOPED_EVENT(R_SM);
 
-	// Atlas resolve work items
-	const U32 tilesX = scratchVewport[2] / m_scratch.m_tileResolution;
-	const U32 tilesY = scratchVewport[3] / m_scratch.m_tileResolution;
-	for(U32 x = 0; x < tilesX; ++x)
-	{
-		for(U32 y = 0; y < tilesY; ++y)
-		{
-			const F32 scratchAtlasWidth = F32(m_scratch.m_tileCountX * m_scratch.m_tileResolution);
-			const F32 scratchAtlasHeight = F32(m_scratch.m_tileCountY * m_scratch.m_tileResolution);
+	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
+	const U threadIdx = rgraphCtx.m_currentSecondLevelCommandBufferIndex;
 
-			Atlas::ResolveWorkItem atlasItem;
+	cmdb->setPolygonOffset(kShadowsPolygonOffsetFactor, kShadowsPolygonOffsetUnits);
 
-			atlasItem.m_uvInBounds[0] = F32(scratchVewport[0]) / scratchAtlasWidth;
-			atlasItem.m_uvInBounds[1] = F32(scratchVewport[1]) / scratchAtlasHeight;
-			atlasItem.m_uvInBounds[2] = F32(scratchVewport[2]) / scratchAtlasWidth;
-			atlasItem.m_uvInBounds[3] = F32(scratchVewport[3]) / scratchAtlasHeight;
+	for(ThreadWorkItem& work : m_runCtx.m_workItems)
+	{
+		if(work.m_threadPoolTaskIdx != threadIdx)
+		{
+			continue;
+		}
 
-			atlasItem.m_uvIn[0] = F32(scratchVewport[0] + scratchVewport[2] / tilesX * x) / scratchAtlasWidth;
-			atlasItem.m_uvIn[1] = F32(scratchVewport[1] + scratchVewport[3] / tilesY * y) / scratchAtlasHeight;
-			atlasItem.m_uvIn[2] = F32(scratchVewport[2] / tilesX) / scratchAtlasWidth;
-			atlasItem.m_uvIn[3] = F32(scratchVewport[3] / tilesY) / scratchAtlasHeight;
+		// Set state
+		cmdb->setViewport(work.m_viewport[0], work.m_viewport[1], work.m_viewport[2], work.m_viewport[3]);
+		cmdb->setScissor(work.m_viewport[0], work.m_viewport[1], work.m_viewport[2], work.m_viewport[3]);
 
-			atlasItem.m_viewportOut[0] = atlasViewport[0] + atlasViewport[2] / tilesX * x;
-			atlasItem.m_viewportOut[1] = atlasViewport[1] + atlasViewport[3] / tilesY * y;
-			atlasItem.m_viewportOut[2] = atlasViewport[2] / tilesX;
-			atlasItem.m_viewportOut[3] = atlasViewport[3] / tilesY;
+		// The 1st drawcall will clear the depth buffer
+		if(work.m_firstRenderableElement == 0)
+		{
+			cmdb->bindShaderProgram(m_clearDepthGrProg);
+			cmdb->setDepthCompareOperation(CompareOperation::kAlways);
+			cmdb->setPolygonOffset(0.0f, 0.0f);
+			cmdb->drawArrays(PrimitiveTopology::kTriangles, 3, 1);
+
+			// Restore state
+			cmdb->setDepthCompareOperation(CompareOperation::kLess);
+			cmdb->setPolygonOffset(kShadowsPolygonOffsetFactor, kShadowsPolygonOffsetUnits);
+		}
 
-			atlasItem.m_blur = blurAtlas;
+		RenderableDrawerArguments args;
+		args.m_viewMatrix = work.m_renderQueue->m_viewMatrix;
+		args.m_cameraTransform = Mat3x4::getIdentity(); // Don't care
+		args.m_viewProjectionMatrix = work.m_renderQueue->m_viewProjectionMatrix;
+		args.m_previousViewProjectionMatrix = Mat4::getIdentity(); // Don't care
+		args.m_sampler = m_r->getSamplers().m_trilinearRepeatAniso;
+		args.m_minLod = args.m_maxLod = work.m_renderQueueElementsLod;
 
-			atlasResolveWorkItem.emplaceBack(atlasItem);
-		}
+		m_r->getSceneDrawer().drawRange(RenderingTechnique::kShadow, args,
+										work.m_renderQueue->m_renderables.getBegin() + work.m_firstRenderableElement,
+										work.m_renderQueue->m_renderables.getBegin() + work.m_firstRenderableElement
+											+ work.m_renderableElementCount,
+										cmdb);
 	}
 }
 

+ 32 - 74
AnKi/Renderer/ShadowMapping.h

@@ -33,100 +33,58 @@ public:
 
 	RenderTargetHandle getShadowmapRt() const
 	{
-		return m_atlas.m_rt;
+		return m_runCtx.m_rt;
 	}
 
 private:
-	/// @name Atlas stuff
-	/// @{
+	class LightToRenderTempInfo;
+	class ThreadWorkItem;
 
-	class Atlas
-	{
-	public:
-		class ResolveWorkItem;
-
-		TileAllocator m_tileAlloc;
-
-		TexturePtr m_tex; ///<  Size (m_tileResolution*m_tileCountBothAxis)^2
-		RenderTargetHandle m_rt;
-		Bool m_rtImportedOnce = false;
+	static constexpr U32 kPointLightMaxTileLod = 1;
 
-		U32 m_tileResolution = 0; ///< Tile resolution.
-		U32 m_tileCountBothAxis = 0;
+	TileAllocator m_tileAlloc;
+	static constexpr U32 kTileAllocLodCount = 3;
 
-		ShaderProgramResourcePtr m_resolveProg;
-		ShaderProgramPtr m_resolveGrProg;
+	TexturePtr m_atlasTex; ///<  Size (m_tileResolution*m_tileCountBothAxis)^2
+	Bool m_rtImportedOnce = false;
 
-		FramebufferDescription m_fbDescr;
+	U32 m_tileResolution = 0; ///< Tile resolution.
+	U32 m_tileCountBothAxis = 0;
 
-		WeakArray<ResolveWorkItem> m_resolveWorkItems;
-	} m_atlas;
+	FramebufferDescription m_fbDescr;
 
-	Error initAtlas();
+	ShaderProgramResourcePtr m_clearDepthProg;
+	ShaderProgramPtr m_clearDepthGrProg;
 
-	inline Mat4 createSpotLightTextureMatrix(const UVec4& viewport) const;
-
-	void runAtlas(RenderPassWorkContext& rgraphCtx);
-	/// @}
-
-	/// @name Scratch buffer stuff
-	/// @{
-
-	class Scratch
+	class
 	{
 	public:
-		class WorkItem;
-		class LightToRenderToScratchInfo;
-
-		TileAllocator m_tileAlloc;
-
-		RenderTargetHandle m_rt; ///< Size of the RT is (m_tileSize * m_tileCount, m_tileSize).
-		FramebufferDescription m_fbDescr; ///< FB info.
-		RenderTargetDescription m_rtDescr; ///< Render target.
-
-		U32 m_tileCountX = 0;
-		U32 m_tileCountY = 0;
-		U32 m_tileResolution = 0;
-
-		WeakArray<WorkItem> m_workItems;
-		U32 m_maxViewportWidth = 0;
-		U32 m_maxViewportHeight = 0;
-	} m_scratch;
+		RenderTargetHandle m_rt;
+		WeakArray<ThreadWorkItem> m_workItems;
+		UVec4 m_fullViewport; ///< Calculate the viewport that contains all of the work items. Mobile optimization.
+	} m_runCtx;
 
-	Error initScratch();
+	Error initInternal();
 
-	void runShadowMapping(RenderPassWorkContext& rgraphCtx);
-	/// @}
+	void processLights(RenderingContext& ctx, U32& threadCountForScratchPass);
 
-	/// @name Misc & common
-	/// @{
+	Bool allocateAtlasTiles(U64 lightUuid, U32 faceCount, const U64* faceTimestamps, const U32* faceIndices,
+							const U32* drawcallsCount, const U32* lods, UVec4* atlasTileViewports,
+							TileAllocatorResult* subResults);
 
-	static constexpr U32 m_pointLightsMaxLod = 1;
+	Mat4 createSpotLightTextureMatrix(const UVec4& viewport) const;
 
 	/// Find the lod of the light
-	void chooseLod(const Vec4& cameraOrigin, const PointLightQueueElement& light, Bool& blurAtlas, U32& tileBufferLod,
-				   U32& renderQueueElementsLod) const;
+	void chooseLods(const Vec4& cameraOrigin, const PointLightQueueElement& light, U32& tileBufferLod,
+					U32& renderQueueElementsLod) const;
 	/// Find the lod of the light
-	void chooseLod(const Vec4& cameraOrigin, const SpotLightQueueElement& light, Bool& blurAtlas, U32& tileBufferLod,
-				   U32& renderQueueElementsLod) const;
-
-	/// Try to allocate a number of scratch tiles and regular tiles.
-	TileAllocatorResult allocateTilesAndScratchTiles(U64 lightUuid, U32 faceCount, const U64* faceTimestamps,
-													 const U32* faceIndices, const U32* drawcallsCount, const U32* lods,
-													 UVec4* atlasTileViewports, UVec4* scratchTileViewports,
-													 TileAllocatorResult* subResults);
-
-	/// Add new work to render to scratch buffer and atlas buffer.
-	void newScratchAndAtlasResloveRenderWorkItems(
-		const UVec4& atlasViewport, const UVec4& scratchVewport, Bool blurAtlas, RenderQueue* lightRenderQueue,
-		U32 renderQueueElementsLod, DynamicArrayRaii<Scratch::LightToRenderToScratchInfo>& scratchWorkItem,
-		DynamicArrayRaii<Atlas::ResolveWorkItem>& atlasResolveWorkItem, U32& drawcallCount) const;
-
-	/// Iterate lights and create work items.
-	void processLights(RenderingContext& ctx, U32& threadCountForScratchPass);
+	void chooseLods(const Vec4& cameraOrigin, const SpotLightQueueElement& light, U32& tileBufferLod,
+					U32& renderQueueElementsLod) const;
 
-	Error initInternal();
-	/// @}
+	void newWorkItems(const UVec4& atlasViewport, RenderQueue* lightRenderQueue, U32 renderQueueElementsLod,
+					  DynamicArrayRaii<LightToRenderTempInfo>& workItems, U32& drawcallCount) const;
+
+	void runShadowMapping(RenderPassWorkContext& rgraphCtx);
 };
 /// @}
 

+ 13 - 9
AnKi/Renderer/ShadowmapsResolve.cpp

@@ -48,17 +48,17 @@ Error ShadowmapsResolve::initInternal()
 													 : "ShaderBinaries/ShadowmapsResolveRaster.ankiprogbin",
 												 m_prog));
 	ShaderProgramResourceVariantInitInfo variantInitInfo(m_prog);
-	if(getConfig().getRPreferCompute())
-	{
-		variantInitInfo.addConstant("kFramebufferSize", UVec2(width, height));
-	}
+	variantInitInfo.addConstant("kFramebufferSize", UVec2(width, height));
 	variantInitInfo.addConstant("kTileCount", m_r->getTileCounts());
 	variantInitInfo.addConstant("kZSplitCount", m_r->getZSplitCount());
 	variantInitInfo.addConstant("kTileSize", m_r->getTileSize());
+	variantInitInfo.addMutation("PCF", getConfig().getRShadowMappingPcf());
 	const ShaderProgramResourceVariant* variant;
 	m_prog->getOrCreateVariant(variantInitInfo, variant);
 	m_grProg = variant->getProgram();
 
+	ANKI_CHECK(getResourceManager().loadResource("EngineAssets/BlueNoise_Rgba8_64x64.png", m_noiseImage));
+
 	return Error::kNone;
 }
 
@@ -69,7 +69,7 @@ void ShadowmapsResolve::populateRenderGraph(RenderingContext& ctx)
 
 	if(getConfig().getRPreferCompute())
 	{
-		ComputeRenderPassDescription& rpass = rgraph.newComputeRenderPass("SM resolve");
+		ComputeRenderPassDescription& rpass = rgraph.newComputeRenderPass("ResolveShadows");
 
 		rpass.setWork([this, &ctx](RenderPassWorkContext& rgraphCtx) {
 			run(ctx, rgraphCtx);
@@ -85,7 +85,7 @@ void ShadowmapsResolve::populateRenderGraph(RenderingContext& ctx)
 	}
 	else
 	{
-		GraphicsRenderPassDescription& rpass = rgraph.newGraphicsRenderPass("SM resolve");
+		GraphicsRenderPassDescription& rpass = rgraph.newGraphicsRenderPass("ResolveShadows");
 		rpass.setFramebufferInfo(m_fbDescr, {m_runCtx.m_rt});
 
 		rpass.setWork([this, &ctx](RenderPassWorkContext& rgraphCtx) {
@@ -116,20 +116,24 @@ void ShadowmapsResolve::run(const RenderingContext& ctx, RenderPassWorkContext&
 	bindStorage(cmdb, 0, 4, rsrc.m_clustersToken);
 
 	cmdb->bindSampler(0, 5, m_r->getSamplers().m_trilinearClamp);
+	cmdb->bindSampler(0, 6, m_r->getSamplers().m_trilinearClampShadow);
+	cmdb->bindSampler(0, 7, m_r->getSamplers().m_trilinearRepeat);
+
 	if(m_quarterRez)
 	{
-		rgraphCtx.bindTexture(0, 6, m_r->getDepthDownscale().getHiZRt(),
+		rgraphCtx.bindTexture(0, 8, m_r->getDepthDownscale().getHiZRt(),
 							  TextureSubresourceInfo(TextureSurfaceInfo(0, 0, 0, 0)));
 	}
 	else
 	{
-		rgraphCtx.bindTexture(0, 6, m_r->getGBuffer().getDepthRt(),
+		rgraphCtx.bindTexture(0, 8, m_r->getGBuffer().getDepthRt(),
 							  TextureSubresourceInfo(DepthStencilAspectBit::kDepth));
 	}
+	cmdb->bindTexture(0, 9, m_noiseImage->getTextureView());
 
 	if(getConfig().getRPreferCompute())
 	{
-		rgraphCtx.bindImage(0, 7, m_runCtx.m_rt, TextureSubresourceInfo());
+		rgraphCtx.bindImage(0, 10, m_runCtx.m_rt, TextureSubresourceInfo());
 		dispatchPPCompute(cmdb, 8, 8, m_rtDescr.m_width, m_rtDescr.m_height);
 	}
 	else

+ 3 - 2
AnKi/Renderer/ShadowmapsResolve.h

@@ -21,7 +21,7 @@ public:
 	ShadowmapsResolve(Renderer* r)
 		: RendererObject(r)
 	{
-		registerDebugRenderTarget("SM_resolve");
+		registerDebugRenderTarget("ResolvedShadows");
 	}
 
 	~ShadowmapsResolve();
@@ -34,7 +34,7 @@ public:
 							  Array<RenderTargetHandle, kMaxDebugRenderTargets>& handles,
 							  [[maybe_unused]] ShaderProgramPtr& optionalShaderProgram) const override
 	{
-		ANKI_ASSERT(rtName == "SM_resolve");
+		ANKI_ASSERT(rtName == "ResolvedShadows");
 		handles[0] = m_runCtx.m_rt;
 	}
 
@@ -49,6 +49,7 @@ public:
 	RenderTargetDescription m_rtDescr;
 	FramebufferDescription m_fbDescr;
 	Bool m_quarterRez = false;
+	ImageResourcePtr m_noiseImage;
 
 	class
 	{

+ 12 - 11
AnKi/Renderer/VolumetricLightingAccumulation.cpp

@@ -106,23 +106,24 @@ void VolumetricLightingAccumulation::run(const RenderingContext& ctx, RenderPass
 	// Bind all
 	cmdb->bindSampler(0, 0, m_r->getSamplers().m_trilinearRepeat);
 	cmdb->bindSampler(0, 1, m_r->getSamplers().m_trilinearClamp);
+	cmdb->bindSampler(0, 2, m_r->getSamplers().m_trilinearClampShadow);
 
-	rgraphCtx.bindImage(0, 2, m_runCtx.m_rts[1], TextureSubresourceInfo());
+	rgraphCtx.bindImage(0, 3, m_runCtx.m_rts[1], TextureSubresourceInfo());
 
-	cmdb->bindTexture(0, 3, m_noiseImage->getTextureView());
+	cmdb->bindTexture(0, 4, m_noiseImage->getTextureView());
 
-	rgraphCtx.bindColorTexture(0, 4, m_runCtx.m_rts[0]);
+	rgraphCtx.bindColorTexture(0, 5, m_runCtx.m_rts[0]);
 
-	bindUniforms(cmdb, 0, 5, rsrc.m_clusteredShadingUniformsToken);
-	bindUniforms(cmdb, 0, 6, rsrc.m_pointLightsToken);
-	bindUniforms(cmdb, 0, 7, rsrc.m_spotLightsToken);
-	rgraphCtx.bindColorTexture(0, 8, m_r->getShadowMapping().getShadowmapRt());
+	bindUniforms(cmdb, 0, 6, rsrc.m_clusteredShadingUniformsToken);
+	bindUniforms(cmdb, 0, 7, rsrc.m_pointLightsToken);
+	bindUniforms(cmdb, 0, 8, rsrc.m_spotLightsToken);
+	rgraphCtx.bindColorTexture(0, 9, m_r->getShadowMapping().getShadowmapRt());
 
-	m_r->getIndirectDiffuseProbes().bindVolumeTextures(ctx, rgraphCtx, 0, 9);
-	bindUniforms(cmdb, 0, 10, rsrc.m_globalIlluminationProbesToken);
+	m_r->getIndirectDiffuseProbes().bindVolumeTextures(ctx, rgraphCtx, 0, 10);
+	bindUniforms(cmdb, 0, 11, rsrc.m_globalIlluminationProbesToken);
 
-	bindUniforms(cmdb, 0, 11, rsrc.m_fogDensityVolumesToken);
-	bindStorage(cmdb, 0, 12, rsrc.m_clustersToken);
+	bindUniforms(cmdb, 0, 12, rsrc.m_fogDensityVolumesToken);
+	bindStorage(cmdb, 0, 13, rsrc.m_clustersToken);
 
 	VolumetricLightingUniforms unis;
 	const SkyboxQueueElement& queueEl = ctx.m_renderQueue->m_skybox;

+ 0 - 119
AnKi/Shaders/Evsm.glsl

@@ -1,119 +0,0 @@
-// Copyright (C) 2009-2022, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-ANKI_SPECIALIZATION_CONSTANT_UVEC2(kInputTextureSize, 0u);
-ANKI_SPECIALIZATION_CONSTANT_UVEC2(kFramebufferSize, 2u);
-
-#include <AnKi/Shaders/Include/MiscRendererTypes.h>
-
-layout(set = 0, binding = 0) readonly buffer b_unis
-{
-	EvsmResolveUniforms u_uniforms[];
-};
-
-#if defined(ANKI_VERTEX_SHADER)
-#	include <AnKi/Shaders/Common.glsl>
-
-layout(location = 0) out Vec2 out_uv;
-layout(location = 1) flat out I32 out_instanceIndex;
-
-void main()
-{
-	const EvsmResolveUniforms uni = u_uniforms[gl_InstanceIndex];
-
-	const Vec2 uv = Vec2(((gl_VertexID + 2) / 3) % 2, ((gl_VertexID + 1) / 3) % 2);
-
-	out_uv = uv * uni.m_uvScale + uni.m_uvTranslation;
-
-	const Vec2 pos = UV_TO_NDC((Vec2(uni.m_viewportXY) + uni.m_viewportZW * uv) / Vec2(kFramebufferSize));
-	gl_Position = Vec4(pos, 0.0, 1.0);
-
-	out_instanceIndex = gl_InstanceIndex;
-}
-
-#else // !defined(ANKI_VERTEX_SHADER)
-
-#	include <AnKi/Shaders/GaussianBlurCommon.glsl>
-#	include <AnKi/Shaders/LightFunctions.glsl>
-
-const F32 kOffset = 1.25;
-
-layout(set = 0, binding = 1) uniform sampler u_linearAnyClampSampler;
-layout(set = 0, binding = 2) uniform texture2D u_inputTex;
-
-#	if defined(ANKI_COMPUTE_SHADER)
-layout(set = 0, binding = 3) uniform writeonly image2D u_outImg;
-
-layout(local_size_x = 8, local_size_y = 8) in;
-#	else
-layout(location = 0) in Vec2 in_uv;
-layout(location = 1) flat in I32 in_instanceIndex;
-layout(location = 0) out Vec4 out_moments;
-#	endif
-
-Vec4 computeMoments(Vec2 uv)
-{
-	const F32 d = textureLod(u_inputTex, u_linearAnyClampSampler, uv, 0.0).r;
-	const Vec2 posAndNeg = evsmProcessDepth(d);
-	return Vec4(posAndNeg.x, posAndNeg.x * posAndNeg.x, posAndNeg.y, posAndNeg.y * posAndNeg.y);
-}
-
-void main()
-{
-#	if defined(ANKI_COMPUTE_SHADER)
-	const EvsmResolveUniforms uni = u_uniforms[gl_GlobalInvocationID.z];
-
-	Vec2 uv = (Vec2(gl_GlobalInvocationID.xy) + 0.5) / uni.m_viewportZW; // in [0, 1]
-	uv = uv * uni.m_uvScale + uni.m_uvTranslation;
-#	else
-	const EvsmResolveUniforms uni = u_uniforms[in_instanceIndex];
-
-	Vec2 uv = in_uv;
-#	endif
-
-	// Compute the UV limits. We can't sample beyond those
-	const Vec2 kTexelSize = 1.0 / Vec2(kInputTextureSize);
-	const Vec2 kHalfTexelSize = kTexelSize / 2.0;
-	const Vec2 maxUv = uni.m_uvMax - kHalfTexelSize;
-	const Vec2 minUv = uni.m_uvMin + kHalfTexelSize;
-
-	// Sample
-	const Vec2 kUvOffset = kOffset * kTexelSize;
-	const F32 w0 = kBoxWeights[0u];
-	const F32 w1 = kBoxWeights[1u];
-	const F32 w2 = kBoxWeights[2u];
-	Vec4 moments;
-	if(uni.m_blur != 0u)
-	{
-		moments = computeMoments(uv) * w0;
-		moments += computeMoments(clamp(uv + Vec2(kUvOffset.x, 0.0), minUv, maxUv)) * w1;
-		moments += computeMoments(clamp(uv + Vec2(-kUvOffset.x, 0.0), minUv, maxUv)) * w1;
-		moments += computeMoments(clamp(uv + Vec2(0.0, kUvOffset.y), minUv, maxUv)) * w1;
-		moments += computeMoments(clamp(uv + Vec2(0.0, -kUvOffset.y), minUv, maxUv)) * w1;
-		moments += computeMoments(clamp(uv + Vec2(kUvOffset.x, kUvOffset.y), minUv, maxUv)) * w2;
-		moments += computeMoments(clamp(uv + Vec2(-kUvOffset.x, kUvOffset.y), minUv, maxUv)) * w2;
-		moments += computeMoments(clamp(uv + Vec2(kUvOffset.x, -kUvOffset.y), minUv, maxUv)) * w2;
-		moments += computeMoments(clamp(uv + Vec2(-kUvOffset.x, -kUvOffset.y), minUv, maxUv)) * w2;
-	}
-	else
-	{
-		moments = computeMoments(uv);
-	}
-
-	// Write the results
-#	if ANKI_EVSM4
-	const Vec4 outColor = moments;
-#	else
-	const Vec4 outColor = Vec4(moments.xy, 0.0, 0.0);
-#	endif
-
-#	if defined(ANKI_COMPUTE_SHADER)
-	imageStore(u_outImg, IVec2(gl_GlobalInvocationID.xy) + uni.m_viewportXY, outColor);
-#	else
-	out_moments = outColor;
-#	endif
-}
-
-#endif // !defined(ANKI_VERTEX_SHADER)

+ 0 - 8
AnKi/Shaders/EvsmCompute.ankiprog

@@ -1,8 +0,0 @@
-// Copyright (C) 2009-2022, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#pragma anki start comp
-#include <AnKi/Shaders/Evsm.glsl>
-#pragma anki end

+ 3 - 2
AnKi/Shaders/ForwardShadingCommon.glsl

@@ -29,6 +29,7 @@ layout(location = kVertexStreamIdPosition) in Vec3 in_position;
 layout(set = kMaterialSetGlobal, binding = kMaterialBindingLinearClampSampler) uniform sampler u_linearAnyClampSampler;
 layout(set = kMaterialSetGlobal, binding = kMaterialBindingDepthRt) uniform texture2D u_gbufferDepthRt;
 layout(set = kMaterialSetGlobal, binding = kMaterialBindingLightVolume) uniform ANKI_RP texture3D u_lightVol;
+layout(set = kMaterialSetGlobal, binding = kMaterialBindingShadowSampler) uniform samplerShadow u_shadowSampler;
 #	define CLUSTERED_SHADING_SET kMaterialSetGlobal
 #	define CLUSTERED_SHADING_UNIFORMS_BINDING kMaterialBindingClusterShadingUniforms
 #	define CLUSTERED_SHADING_LIGHTS_BINDING kMaterialBindingClusterShadingLights
@@ -76,7 +77,7 @@ Vec3 computeLightColorHigh(Vec3 diffCol, Vec3 worldPos)
 		F32 shadow = 1.0;
 		if(light.m_shadowAtlasTileScale >= 0.0)
 		{
-			shadow = computeShadowFactorPointLight(light, frag2Light, u_shadowAtlasTex, u_linearAnyClampSampler);
+			shadow = computeShadowFactorPointLight(light, frag2Light, u_shadowAtlasTex, u_shadowSampler);
 		}
 #	endif
 
@@ -105,7 +106,7 @@ Vec3 computeLightColorHigh(Vec3 diffCol, Vec3 worldPos)
 		F32 shadow = 1.0;
 		ANKI_BRANCH if(light.m_shadowLayer != kMaxU32)
 		{
-			shadow = computeShadowFactorSpotLight(light, worldPos, u_shadowAtlasTex, u_linearAnyClampSampler);
+			shadow = computeShadowFactorSpotLight(light, worldPos, u_shadowAtlasTex, u_shadowSampler);
 		}
 #	endif
 

+ 22 - 2
AnKi/Shaders/Functions.glsl

@@ -301,9 +301,9 @@ Vec2 convertCubeUvsu(const Vec3 v, out U32 faceIndex)
 	return 0.5 / mag * uv + 0.5;
 }
 
-Vec3 grayScale(const Vec3 col)
+ANKI_RP Vec3 grayScale(const ANKI_RP Vec3 col)
 {
-	const F32 grey = (col.r + col.g + col.b) * (1.0 / 3.0);
+	const ANKI_RP F32 grey = (col.r + col.g + col.b) * (1.0 / 3.0);
 	return Vec3(grey);
 }
 
@@ -684,3 +684,23 @@ ANKI_RP Vec3 filmGrain(ANKI_RP Vec3 color, Vec2 uv, ANKI_RP F32 strength, ANKI_R
 	const F32 grain = 1.0 - (mod((mod(x, 13.0) + 1.0) * (mod(x, 123.0) + 1.0), 0.01) - 0.005) * strength;
 	return color * grain;
 }
+
+/// Sin approximation: https://www.desmos.com/calculator/svgcjfskne
+ANKI_RP F32 fastSin(ANKI_RP F32 x)
+{
+	const ANKI_RP F32 k2Pi = 2.0 * kPi;
+	const ANKI_RP F32 kPiOver2 = kPi / 2.0;
+
+	x = (x + kPiOver2) / (k2Pi) + 0.75;
+	x = fract(x);
+	x = x * 2.0 - 1.0;
+	x = x * abs(x) - x;
+	x *= 4.0;
+	return x;
+}
+
+/// Cos approximation
+ANKI_RP F32 fastCos(ANKI_RP F32 x)
+{
+	return fastSin(x + kPi / 2.0);
+}

+ 3 - 0
AnKi/Shaders/Include/Common.h

@@ -79,4 +79,7 @@ ANKI_BEGIN_NAMESPACE
 const U32 kMaxInstanceCount = 64u;
 const U32 kMaxLodCount = 3u;
 
+const F32 kShadowsPolygonOffsetFactor = 1.25f;
+const F32 kShadowsPolygonOffsetUnits = 2.75f;
+
 ANKI_END_NAMESPACE

+ 1 - 0
AnKi/Shaders/Include/MaterialTypes.h

@@ -34,6 +34,7 @@ const U32 kMaterialBindingLightVolume = 4u;
 const U32 kMaterialBindingClusterShadingUniforms = 5u;
 const U32 kMaterialBindingClusterShadingLights = 6u;
 const U32 kMaterialBindingClusters = 9u;
+const U32 kMaterialBindingShadowSampler = 10u;
 // End global bindings
 
 // Begin local bindings

+ 0 - 25
AnKi/Shaders/Include/MiscRendererTypes.h

@@ -9,31 +9,6 @@
 
 ANKI_BEGIN_NAMESPACE
 
-// EVSM
-#define ANKI_EVSM4 0 // 2 component EVSM or 4 component EVSM
-
-const F32 kEvsmPositiveConstant = 40.0f; // EVSM positive constant
-const F32 kEvsmNegativeConstant = 5.0f; // EVSM negative constant
-const F32 kEvsmBias = 0.01f;
-const F32 kEvsmLightBleedingReduction = 0.05f;
-
-struct EvsmResolveUniforms
-{
-	IVec2 m_viewportXY;
-	Vec2 m_viewportZW;
-
-	Vec2 m_uvScale;
-	Vec2 m_uvTranslation;
-
-	Vec2 m_uvMin;
-	Vec2 m_uvMax;
-
-	U32 m_blur;
-	U32 m_padding0;
-	U32 m_padding1;
-	U32 m_padding2;
-};
-
 // RT shadows
 const U32 kMaxRtShadowLayers = 8u;
 

+ 125 - 78
AnKi/Shaders/LightFunctions.glsl

@@ -13,60 +13,9 @@
 #include <AnKi/Shaders/Include/ClusteredShadingTypes.h>
 #include <AnKi/Shaders/Include/MiscRendererTypes.h>
 
-// Do some EVSM magic with depth
-Vec2 evsmProcessDepth(F32 depth)
-{
-	depth = 2.0 * depth - 1.0;
-	const F32 pos = exp(kEvsmPositiveConstant * depth);
-	const F32 neg = -exp(kEvsmNegativeConstant * depth);
-	return Vec2(pos, neg);
-}
-
-F32 linstep(F32 a, F32 b, F32 v)
-{
-	return saturate((v - a) / (b - a));
-}
-
-// Reduces VSM light bleedning
-F32 reduceLightBleeding(F32 pMax, F32 amount)
-{
-	// Remove the [0, amount] tail and linearly rescale (amount, 1].
-	return linstep(amount, 1.0, pMax);
-}
-
-F32 chebyshevUpperBound(Vec2 moments, F32 mean, F32 minVariance, F32 lightBleedingReduction)
-{
-	// Compute variance
-	F32 variance = moments.y - (moments.x * moments.x);
-	variance = max(variance, minVariance);
-
-	// Compute probabilistic upper bound
-	const F32 d = mean - moments.x;
-	F32 pMax = variance / (variance + (d * d));
-
-	pMax = reduceLightBleeding(pMax, lightBleedingReduction);
-
-	// One-tailed Chebyshev
-	return (mean <= moments.x) ? 1.0 : pMax;
-}
-
-// Compute the shadow factor of EVSM given the 2 depths
-F32 evsmComputeShadowFactor(F32 occluderDepth, Vec4 shadowMapMoments)
-{
-	const Vec2 evsmOccluderDepths = evsmProcessDepth(occluderDepth);
-	const Vec2 depthScale = kEvsmBias * 0.01 * Vec2(kEvsmPositiveConstant, kEvsmNegativeConstant) * evsmOccluderDepths;
-	const Vec2 minVariance = depthScale * depthScale;
-
-#if !ANKI_EVSM4
-	return chebyshevUpperBound(shadowMapMoments.xy, evsmOccluderDepths.x, minVariance.x, kEvsmLightBleedingReduction);
-#else
-	const F32 pos =
-		chebyshevUpperBound(shadowMapMoments.xy, evsmOccluderDepths.x, minVariance.x, kEvsmLightBleedingReduction);
-	const F32 neg =
-		chebyshevUpperBound(shadowMapMoments.zw, evsmOccluderDepths.y, minVariance.y, kEvsmLightBleedingReduction);
-	return min(pos, neg);
-#endif
-}
+const Vec2 kPoissonDisk[4u] = {Vec2(-0.94201624, -0.39906216), Vec2(0.94558609, -0.76890725),
+							   Vec2(-0.094184101, -0.92938870), Vec2(0.34495938, 0.29387760)};
+const ANKI_RP F32 kPcfScale = 2.0f;
 
 // Fresnel term unreal
 // specular: The specular color aka F0
@@ -162,31 +111,49 @@ ANKI_RP F32 computeSpotFactor(ANKI_RP Vec3 l, ANKI_RP F32 outerCos, ANKI_RP F32
 	return spotFactor;
 }
 
-U32 computeShadowSampleCount(const U32 COUNT, F32 zVSpace)
+ANKI_RP F32 computeShadowFactorSpotLightPcf(SpotLight light, Vec3 worldPos, texture2D shadowTex,
+											samplerShadow shadowMapSampler, ANKI_RP F32 randFactor)
 {
-	const F32 MAX_DISTANCE = 5.0;
+	const Vec4 texCoords4 = light.m_textureMatrix * Vec4(worldPos, 1.0);
+	const Vec3 texCoords3 = texCoords4.xyz / texCoords4.w;
+
+	const Vec2 smTexelSize = 1.0 / Vec2(textureSize(shadowTex, 0).xy);
+
+	const F32 sinTheta = sin(randFactor * 2.0 * kPi);
+	const F32 cosTheta = cos(randFactor * 2.0 * kPi);
+
+	ANKI_RP F32 shadow = 0.0;
+	ANKI_UNROLL for(U32 i = 0u; i < 4u; ++i)
+	{
+		const Vec2 diskPoint = kPoissonDisk[i] * kPcfScale;
+
+		// Rotate the disk point
+		Vec2 rotatedDiskPoint;
+		rotatedDiskPoint.x = diskPoint.x * cosTheta - diskPoint.y * sinTheta;
+		rotatedDiskPoint.y = diskPoint.y * cosTheta + diskPoint.x * sinTheta;
 
-	const F32 z = max(zVSpace, -MAX_DISTANCE);
-	F32 sampleCountf = F32(COUNT) + z * (F32(COUNT) / MAX_DISTANCE);
-	sampleCountf = max(sampleCountf, 1.0);
-	const U32 sampleCount = U32(sampleCountf);
+		// Offset calculation
+		const Vec2 newUv = texCoords3.xy + rotatedDiskPoint * smTexelSize;
 
-	return sampleCount;
+		shadow += textureLod(shadowTex, shadowMapSampler, Vec3(newUv, texCoords3.z), 0.0);
+	}
+
+	shadow /= 4.0;
+
+	return shadow;
 }
 
-ANKI_RP F32 computeShadowFactorSpotLight(SpotLight light, Vec3 worldPos, texture2D spotMap, sampler spotMapSampler)
+ANKI_RP F32 computeShadowFactorSpotLight(SpotLight light, Vec3 worldPos, texture2D shadowTex,
+										 samplerShadow shadowMapSampler)
 {
 	const Vec4 texCoords4 = light.m_textureMatrix * Vec4(worldPos, 1.0);
 	const Vec3 texCoords3 = texCoords4.xyz / texCoords4.w;
-
-	const Vec4 shadowMoments = textureLod(spotMap, spotMapSampler, texCoords3.xy, 0.0);
-
-	return evsmComputeShadowFactor(texCoords3.z, shadowMoments);
+	return textureLod(shadowTex, shadowMapSampler, texCoords3, 0.0);
 }
 
 // Compute the shadow factor of point (omni) lights.
-ANKI_RP F32 computeShadowFactorPointLight(PointLight light, Vec3 frag2Light, texture2D shadowMap,
-										  sampler shadowMapSampler)
+ANKI_RP F32 computeShadowFactorPointLightGeneric(PointLight light, Vec3 frag2Light, texture2D shadowMap,
+												 samplerShadow shadowMapSampler, ANKI_RP F32 randFactor, Bool pcf)
 {
 	const Vec3 dir = -frag2Light;
 	const Vec3 dirabs = abs(dir);
@@ -218,18 +185,56 @@ ANKI_RP F32 computeShadowFactorPointLight(PointLight light, Vec3 frag2Light, tex
 	uv += atlasOffset;
 
 	// Sample
-	const Vec4 shadowMoments = textureLod(shadowMap, shadowMapSampler, uv, 0.0);
+	ANKI_RP F32 shadow;
+	if(pcf)
+	{
+		const Vec2 smTexelSize = 1.0 / Vec2(textureSize(shadowMap, 0).xy);
 
-	// 3) Compare
-	//
-	const ANKI_RP F32 shadowFactor = evsmComputeShadowFactor(z, shadowMoments);
+		const F32 sinTheta = sin(randFactor * 2.0 * kPi);
+		const F32 cosTheta = cos(randFactor * 2.0 * kPi);
 
-	return shadowFactor;
+		shadow = 0.0;
+		ANKI_UNROLL for(U32 i = 0u; i < 4u; ++i)
+		{
+			const Vec2 diskPoint = kPoissonDisk[i] * kPcfScale;
+
+			// Rotate the disk point
+			Vec2 rotatedDiskPoint;
+			rotatedDiskPoint.x = diskPoint.x * cosTheta - diskPoint.y * sinTheta;
+			rotatedDiskPoint.y = diskPoint.y * cosTheta + diskPoint.x * sinTheta;
+
+			// Offset calculation
+			const Vec2 newUv = uv + rotatedDiskPoint * smTexelSize;
+
+			shadow += textureLod(shadowMap, shadowMapSampler, Vec3(newUv, z), 0.0);
+		}
+
+		shadow /= 4.0;
+	}
+	else
+	{
+		shadow = textureLod(shadowMap, shadowMapSampler, Vec3(uv, z), 0.0);
+	}
+
+	return shadow;
+}
+
+ANKI_RP F32 computeShadowFactorPointLight(PointLight light, Vec3 frag2Light, texture2D shadowMap,
+										  samplerShadow shadowMapSampler)
+{
+	return computeShadowFactorPointLightGeneric(light, frag2Light, shadowMap, shadowMapSampler, -1.0, false);
+}
+
+ANKI_RP F32 computeShadowFactorPointLightPcf(PointLight light, Vec3 frag2Light, texture2D shadowMap,
+											 samplerShadow shadowMapSampler, ANKI_RP F32 randFactor)
+{
+	return computeShadowFactorPointLightGeneric(light, frag2Light, shadowMap, shadowMapSampler, randFactor, true);
 }
 
 // Compute the shadow factor of a directional light
-ANKI_RP F32 computeShadowFactorDirLight(DirectionalLight light, U32 cascadeIdx, Vec3 worldPos, texture2D shadowMap,
-										sampler shadowMapSampler)
+ANKI_RP F32 computeShadowFactorDirLightGeneric(DirectionalLight light, U32 cascadeIdx, Vec3 worldPos,
+											   texture2D shadowMap, samplerShadow shadowMapSampler,
+											   ANKI_RP F32 randFactor, Bool pcf)
 {
 #define ANKI_FAST_CASCADES_WORKAROUND 1 // Doesn't make sense but it's super fast
 
@@ -255,11 +260,53 @@ ANKI_RP F32 computeShadowFactorDirLight(DirectionalLight light, U32 cascadeIdx,
 #endif
 
 	const Vec4 texCoords4 = lightProjectionMat * Vec4(worldPos, 1.0);
-	const Vec3 texCoords3 = texCoords4.xyz / texCoords4.w;
+	Vec3 texCoords3 = texCoords4.xyz / texCoords4.w;
 
-	const Vec4 shadowMoments = textureLod(shadowMap, shadowMapSampler, texCoords3.xy, 0.0);
+	ANKI_RP F32 shadow;
+	if(pcf)
+	{
+		const Vec2 smTexelSize = 1.0 / Vec2(textureSize(shadowMap, 0).xy);
 
-	return evsmComputeShadowFactor(texCoords3.z, shadowMoments);
+		const F32 sinTheta = sin(randFactor * 2.0 * kPi);
+		const F32 cosTheta = cos(randFactor * 2.0 * kPi);
+
+		shadow = 0.0;
+		ANKI_UNROLL for(U32 i = 0u; i < 4u; ++i)
+		{
+			const Vec2 diskPoint = kPoissonDisk[i] * kPcfScale;
+
+			// Rotate the disk point
+			Vec2 rotatedDiskPoint;
+			rotatedDiskPoint.x = diskPoint.x * cosTheta - diskPoint.y * sinTheta;
+			rotatedDiskPoint.y = diskPoint.y * cosTheta + diskPoint.x * sinTheta;
+
+			// Offset calculation
+			Vec2 newUv = texCoords3.xy + rotatedDiskPoint * smTexelSize;
+
+			shadow += textureLod(shadowMap, shadowMapSampler, Vec3(newUv, texCoords3.z), 0.0);
+		}
+
+		shadow /= 4.0;
+	}
+	else
+	{
+		shadow = textureLod(shadowMap, shadowMapSampler, texCoords3, 0.0);
+	}
+
+	return shadow;
+}
+
+ANKI_RP F32 computeShadowFactorDirLight(DirectionalLight light, U32 cascadeIdx, Vec3 worldPos, texture2D shadowMap,
+										samplerShadow shadowMapSampler)
+{
+	return computeShadowFactorDirLightGeneric(light, cascadeIdx, worldPos, shadowMap, shadowMapSampler, -1.0, false);
+}
+
+ANKI_RP F32 computeShadowFactorDirLightPcf(DirectionalLight light, U32 cascadeIdx, Vec3 worldPos, texture2D shadowMap,
+										   samplerShadow shadowMapSampler, F32 randFactor)
+{
+	return computeShadowFactorDirLightGeneric(light, cascadeIdx, worldPos, shadowMap, shadowMapSampler, randFactor,
+											  true);
 }
 
 // Compute the shadow factor of a directional light

+ 12 - 2
AnKi/Shaders/EvsmRaster.ankiprog → AnKi/Shaders/ShadowmappingClearDepth.ankiprog

@@ -4,9 +4,19 @@
 // http://www.anki3d.org/LICENSE
 
 #pragma anki start vert
-#include <AnKi/Shaders/Evsm.glsl>
+
+void main()
+{
+	const Vec2 uv = Vec2(gl_VertexID & 1, gl_VertexID >> 1) * 2.0;
+	const Vec2 pos = uv * 2.0 - 1.0;
+
+	gl_Position = Vec4(pos, 1.0, 1.0);
+}
+
 #pragma anki end
 
 #pragma anki start frag
-#include <AnKi/Shaders/Evsm.glsl>
+void main()
+{
+}
 #pragma anki end

+ 47 - 8
AnKi/Shaders/ShadowmapsResolve.glsl

@@ -3,6 +3,8 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
+#pragma anki mutator PCF 0 1
+
 ANKI_SPECIALIZATION_CONSTANT_UVEC2(kFramebufferSize, 0u);
 ANKI_SPECIALIZATION_CONSTANT_UVEC2(kTileCount, 2u);
 ANKI_SPECIALIZATION_CONSTANT_U32(kZSplitCount, 4u);
@@ -15,12 +17,15 @@ ANKI_SPECIALIZATION_CONSTANT_U32(kTileSize, 5u);
 #include <AnKi/Shaders/ClusteredShadingCommon.glsl>
 
 layout(set = 0, binding = 5) uniform sampler u_linearAnyClampSampler;
-layout(set = 0, binding = 6) uniform texture2D u_depthRt;
+layout(set = 0, binding = 6) uniform samplerShadow u_linearAnyClampShadowSampler;
+layout(set = 0, binding = 7) uniform sampler u_trilinearRepeatSampler;
+layout(set = 0, binding = 8) uniform texture2D u_depthRt;
+layout(set = 0, binding = 9) uniform texture2D u_noiseTex;
 
 #if defined(ANKI_COMPUTE_SHADER)
 const UVec2 kWorkgroupSize = UVec2(8, 8);
 layout(local_size_x = kWorkgroupSize.x, local_size_y = kWorkgroupSize.y, local_size_z = 1) in;
-layout(set = 0, binding = 7, rgba8) writeonly uniform ANKI_RP image2D u_outImg;
+layout(set = 0, binding = 10, rgba8) writeonly uniform ANKI_RP image2D u_outImg;
 #else
 layout(location = 0) in Vec2 in_uv;
 layout(location = 0) out Vec4 out_color;
@@ -38,6 +43,15 @@ void main()
 	const Vec2 uv = in_uv;
 #endif
 
+#if PCF
+	// Noise
+	const Vec2 kNoiseTexSize = Vec2(64.0);
+	const Vec2 noiseUv = Vec2(kFramebufferSize) / kNoiseTexSize * uv;
+	ANKI_RP Vec3 noise = textureLod(u_noiseTex, u_trilinearRepeatSampler, noiseUv, 0.0).rgb;
+	noise = animateBlueNoise(noise, u_clusteredShading.m_frame % 16u);
+	const ANKI_RP F32 randFactor = noise.x;
+#endif
+
 	// World position
 	const Vec2 ndc = UV_TO_NDC(uv);
 	const F32 depth = textureLod(u_depthRt, u_linearAnyClampSampler, uv, 0.0).r;
@@ -70,8 +84,23 @@ void main()
 				positiveZViewSpace, dirLight.m_shadowCascadesDistancePower, dirLight.m_effectiveShadowDistance,
 				dirLight.m_cascadeCount, cascadeBlendFactor);
 
-			const F32 shadowFactorCascadeA = computeShadowFactorDirLight(dirLight, cascadeIndices.x, worldPos,
-																		 u_shadowAtlasTex, u_linearAnyClampSampler);
+#if PCF
+			F32 shadowFactorCascadeA;
+			if(cascadeIndices.x == 0u)
+			{
+				const ANKI_RP F32 rand = noise.x;
+				shadowFactorCascadeA = computeShadowFactorDirLightPcf(
+					dirLight, cascadeIndices.x, worldPos, u_shadowAtlasTex, u_linearAnyClampShadowSampler, rand);
+			}
+			else
+			{
+				shadowFactorCascadeA = computeShadowFactorDirLight(dirLight, cascadeIndices.x, worldPos,
+																   u_shadowAtlasTex, u_linearAnyClampShadowSampler);
+			}
+#else
+			const F32 shadowFactorCascadeA = computeShadowFactorDirLight(
+				dirLight, cascadeIndices.x, worldPos, u_shadowAtlasTex, u_linearAnyClampShadowSampler);
+#endif
 
 			if(cascadeBlendFactor < 0.01 || cascadeIndices.x == cascadeIndices.y)
 			{
@@ -81,8 +110,8 @@ void main()
 			else
 			{
 				// Blend cascades
-				const F32 shadowFactorCascadeB = computeShadowFactorDirLight(dirLight, cascadeIndices.y, worldPos,
-																			 u_shadowAtlasTex, u_linearAnyClampSampler);
+				const F32 shadowFactorCascadeB = computeShadowFactorDirLight(
+					dirLight, cascadeIndices.y, worldPos, u_shadowAtlasTex, u_linearAnyClampShadowSampler);
 
 				shadowFactor = mix(shadowFactorCascadeA, shadowFactorCascadeB, cascadeBlendFactor);
 			}
@@ -111,8 +140,13 @@ void main()
 		{
 			const Vec3 frag2Light = light.m_position - worldPos;
 
+#if PCF
+			const ANKI_RP F32 shadowFactor = computeShadowFactorPointLightPcf(
+				light, frag2Light, u_shadowAtlasTex, u_linearAnyClampShadowSampler, randFactor);
+#else
 			const ANKI_RP F32 shadowFactor =
-				computeShadowFactorPointLight(light, frag2Light, u_shadowAtlasTex, u_linearAnyClampSampler);
+				computeShadowFactorPointLight(light, frag2Light, u_shadowAtlasTex, u_linearAnyClampShadowSampler);
+#endif
 			shadowFactors[min(maxShadowCastersPerFragment - 1u, shadowCasterCountPerFragment++)] = shadowFactor;
 		}
 	}
@@ -126,8 +160,13 @@ void main()
 
 		ANKI_BRANCH if(light.m_shadowLayer != kMaxU32)
 		{
+#if PCF
+			const ANKI_RP F32 shadowFactor = computeShadowFactorSpotLightPcf(light, worldPos, u_shadowAtlasTex,
+																			 u_linearAnyClampShadowSampler, randFactor);
+#else
 			const ANKI_RP F32 shadowFactor =
-				computeShadowFactorSpotLight(light, worldPos, u_shadowAtlasTex, u_linearAnyClampSampler);
+				computeShadowFactorSpotLight(light, worldPos, u_shadowAtlasTex, u_linearAnyClampShadowSampler);
+#endif
 			shadowFactors[min(maxShadowCastersPerFragment - 1u, shadowCasterCountPerFragment++)] = shadowFactor;
 		}
 	}

+ 13 - 12
AnKi/Shaders/VolumetricLightingAccumulation.ankiprog

@@ -21,17 +21,18 @@ layout(local_size_x = kWorkgroupSize.x, local_size_y = kWorkgroupSize.y, local_s
 
 layout(set = 0, binding = 0) uniform sampler u_linearAnyRepeatSampler;
 layout(set = 0, binding = 1) uniform sampler u_linearAnyClampSampler;
+layout(set = 0, binding = 2) uniform samplerShadow u_linearAnyClampShadowSampler;
 
-layout(set = 0, binding = 2) writeonly uniform image3D u_volume;
-layout(set = 0, binding = 3) uniform texture2D u_noiseTex;
-layout(set = 0, binding = 4) uniform texture3D u_prevVolume;
+layout(set = 0, binding = 3) writeonly uniform image3D u_volume;
+layout(set = 0, binding = 4) uniform texture2D u_noiseTex;
+layout(set = 0, binding = 5) uniform texture3D u_prevVolume;
 
 #define CLUSTERED_SHADING_SET 0u
-#define CLUSTERED_SHADING_UNIFORMS_BINDING 5u
-#define CLUSTERED_SHADING_LIGHTS_BINDING 6u
-#define CLUSTERED_SHADING_GI_BINDING 9u
-#define CLUSTERED_SHADING_FOG_BINDING 11u
-#define CLUSTERED_SHADING_CLUSTERS_BINDING 12u
+#define CLUSTERED_SHADING_UNIFORMS_BINDING 6u
+#define CLUSTERED_SHADING_LIGHTS_BINDING 7u
+#define CLUSTERED_SHADING_GI_BINDING 10u
+#define CLUSTERED_SHADING_FOG_BINDING 12u
+#define CLUSTERED_SHADING_CLUSTERS_BINDING 13u
 #include <AnKi/Shaders/ClusteredShadingCommon.glsl>
 #include <AnKi/Shaders/Include/MiscRendererTypes.h>
 
@@ -114,8 +115,8 @@ Vec4 accumulateLightsAndFog(Cluster cluster, Vec3 worldPos, F32 negativeZViewSpa
 				computeShadowCascadeIndex(negativeZViewSpace, dirLight.m_shadowCascadesDistancePower,
 										  dirLight.m_effectiveShadowDistance, dirLight.m_cascadeCount);
 
-			factor *=
-				computeShadowFactorDirLight(dirLight, cascadeIdx, worldPos, u_shadowAtlasTex, u_linearAnyClampSampler);
+			factor *= computeShadowFactorDirLight(dirLight, cascadeIdx, worldPos, u_shadowAtlasTex,
+												  u_linearAnyClampShadowSampler);
 		}
 #endif
 
@@ -137,7 +138,7 @@ Vec4 accumulateLightsAndFog(Cluster cluster, Vec3 worldPos, F32 negativeZViewSpa
 #if ENABLE_SHADOWS
 		if(light.m_shadowAtlasTileScale >= 0.0)
 		{
-			factor *= computeShadowFactorPointLight(light, frag2Light, u_shadowAtlasTex, u_linearAnyClampSampler);
+			factor *= computeShadowFactorPointLight(light, frag2Light, u_shadowAtlasTex, u_linearAnyClampShadowSampler);
 		}
 #endif
 
@@ -163,7 +164,7 @@ Vec4 accumulateLightsAndFog(Cluster cluster, Vec3 worldPos, F32 negativeZViewSpa
 #if ENABLE_SHADOWS
 		if(light.m_shadowLayer != kMaxU32)
 		{
-			factor *= computeShadowFactorSpotLight(light, worldPos, u_shadowAtlasTex, u_linearAnyClampSampler);
+			factor *= computeShadowFactorSpotLight(light, worldPos, u_shadowAtlasTex, u_linearAnyClampShadowSampler);
 		}
 #endif
 

+ 1 - 1
AnKi/Util/Logger.h

@@ -119,7 +119,7 @@ using LoggerSingleton = Singleton<Logger>;
 	{ \
 		LoggerSingleton::get().writeFormated(ANKI_FILE, __LINE__, ANKI_FUNC, subsystem_, LoggerMessageType::t, \
 											 Thread::getCurrentThreadName(), __VA_ARGS__); \
-	} while(false);
+	} while(false)
 /// @}
 
 /// @addtogroup util_logging

+ 2 - 2
Samples/Common/SampleApp.cpp

@@ -80,8 +80,8 @@ Error SampleApp::userMainLoop(Bool& quit, Second elapsedTime)
 
 	if(in.getKey(KeyCode::kO) == 1)
 	{
-		renderer.setCurrentDebugRenderTarget((renderer.getCurrentDebugRenderTarget() == "SM_resolve") ? ""
-																									  : "SM_resolve");
+		renderer.setCurrentDebugRenderTarget(
+			(renderer.getCurrentDebugRenderTarget() == "ResolvedShadows") ? "" : "ResolvedShadows");
 	}
 
 	if(in.getKey(KeyCode::kP) == 1)

+ 2 - 2
Sandbox/Main.cpp

@@ -367,8 +367,8 @@ Error MyApp::userMainLoop(Bool& quit, Second elapsedTime)
 
 	if(in.getKey(KeyCode::kO) == 1)
 	{
-		renderer.setCurrentDebugRenderTarget((renderer.getCurrentDebugRenderTarget() == "SM_resolve") ? ""
-																									  : "SM_resolve");
+		renderer.setCurrentDebugRenderTarget(
+			(renderer.getCurrentDebugRenderTarget() == "ResolvedShadows") ? "" : "ResolvedShadows");
 	}
 
 	if(in.getKey(KeyCode::kH) == 1)