Преглед на файлове

Renderer: Improve indirect performance a bit

Panagiotis Christopoulos Charitos преди 8 години
родител
ревизия
63dc0d5597

+ 16 - 15
programs/DeferredShading.ankiprog

@@ -76,6 +76,7 @@ layout(ANKI_TEX_BINDING(0, 3)) uniform sampler2D u_msDepthRt;
 
 layout(ANKI_UBO_BINDING(0, 1)) uniform u1_
 {
+	vec4 u_inputTexUvScaleAndOffset; // Use this to get the correct face UVs
 #if LIGHT_TYPE == POINT_LIGHT_TYPE
 	PointLight u_light;
 #elif LIGHT_TYPE == SPOT_LIGHT_TYPE
@@ -93,28 +94,28 @@ layout(ANKI_UBO_BINDING(0, 1)) uniform u1_
 #define u_lspec u_light.specularColorInnerCos.xyz
 #endif
 
-vec3 readPosition(in vec2 uv)
-{
-	vec3 fragPosVspace;
-
-	float depth = texture(u_msDepthRt, uv).r;
-	fragPosVspace.z = u_light.projectionParams.z / (u_light.projectionParams.w + depth);
-
-	fragPosVspace.xy = (2.0 * uv - 1.0) * u_light.projectionParams.xy * fragPosVspace.z;
-
-	return fragPosVspace;
-}
-
 void main()
 {
-	// Read G-buffer
+	// Compute UV coordinates
 	vec2 uv = vec2(gl_FragCoord.xy) / vec2(FB_SIZE.x, FB_SIZE.y);
+	vec2 uvToRead = fma(uv, u_inputTexUvScaleAndOffset.xy, u_inputTexUvScaleAndOffset.zw);
+
+	// Do manual depth test
+	float depth = texture(u_msDepthRt, uvToRead).r;
+	if(gl_FragCoord.z < depth)
+	{
+		discard;
+	}
+
 	GbufferInfo gbuffer;
-	readGBuffer(u_msRt0, u_msRt1, u_msRt2, uv, 0.0, gbuffer);
+	readGBuffer(u_msRt0, u_msRt1, u_msRt2, uvToRead, 0.0, gbuffer);
 	float a2 = pow(gbuffer.roughness, 2.0);
 
 	// Calculate the light color
-	vec3 fragPos = readPosition(uv);
+	vec3 fragPos;
+	fragPos.z = u_light.projectionParams.z / (u_light.projectionParams.w + depth);
+	fragPos.xy = UV_TO_NDC(uv) * u_light.projectionParams.xy * fragPos.z;
+
 	vec3 viewDir = normalize(-fragPos);
 	vec3 frag2Light = u_light.posRadius.xyz - fragPos;
 	vec3 l = normalize(frag2Light);

+ 6 - 5
programs/Irradiance.ankiprog

@@ -14,7 +14,8 @@ http://www.anki3d.org/LICENSE
 
 		<shader type="frag">
 			<inputs>
-				<input name="FACE_SIZE" type="uint" const="1"/>
+				<input name="ENV_TEX_TILE_SIZE" type="uint" const="1"/>
+				<input name="ENV_TEX_MIP" type="float" const="1"/>
 			</inputs>
 
 			<source><![CDATA[
@@ -49,17 +50,17 @@ void main()
 	// For all the faces and texels of the environment map calculate a color sum
 	for(uint f = 0u; f < 6u; ++f)
 	{
-		for(uint i = 0u; i < FACE_SIZE; ++i)
+		for(uint i = 0u; i < ENV_TEX_TILE_SIZE; ++i)
 		{
-			for(uint j = 0u; j < FACE_SIZE; ++j)
+			for(uint j = 0u; j < ENV_TEX_TILE_SIZE; ++j)
 			{
-				vec2 uv = vec2(j, i) / float(FACE_SIZE);
+				vec2 uv = vec2(j, i) / float(ENV_TEX_TILE_SIZE);
 				vec3 r = getCubemapDirection(UV_TO_NDC(uv), f);
 
 				float lambert = dot(r, ri);
 				if(lambert > 0.0)
 				{
-					vec3 col = textureLod(u_envTex, vec4(r, texArrIdx), 0.0).rgb;
+					vec3 col = textureLod(u_envTex, vec4(r, texArrIdx), ENV_TEX_MIP).rgb;
 
 					outCol += col * lambert;
 					weight += lambert;

+ 2 - 2
src/anki/core/Config.cpp

@@ -33,8 +33,8 @@ Config::Config()
 
 	newOption("r.finalComposite.sharpen", false);
 
-	newOption("r.indirect.reflectionSize", 128);
-	newOption("r.indirect.maxProbeCount", 32);
+	newOption("r.indirect.reflectionResolution", 128);
+	newOption("r.indirect.maxSimultaneousProbeCount", 32);
 
 	newOption("r.dbg.enabled", false);
 

+ 487 - 391
src/anki/renderer/Indirect.cpp

@@ -15,24 +15,23 @@
 namespace anki
 {
 
-class IrVertex
+struct Indirect::LightPassVertexUniforms
 {
-public:
 	Mat4 m_mvp;
 };
 
-class IrPointLight
+struct Indirect::LightPassPointLightUniforms
 {
-public:
+	Vec4 m_inputTexUvScaleAndOffset;
 	Vec4 m_projectionParams;
 	Vec4 m_posRadius;
 	Vec4 m_diffuseColorPad1;
 	Vec4 m_specularColorPad1;
 };
 
-class IrSpotLight
+struct Indirect::LightPassSpotLightUniforms
 {
-public:
+	Vec4 m_inputTexUvScaleAndOffset;
 	Vec4 m_projectionParams;
 	Vec4 m_posRadius;
 	Vec4 m_diffuseColorOuterCos;
@@ -48,6 +47,7 @@ Indirect::Indirect(Renderer* r)
 Indirect::~Indirect()
 {
 	m_cacheEntries.destroy(getAllocator());
+	m_probeUuidToCacheEntryIdx.destroy(getAllocator());
 }
 
 Error Indirect::init(const ConfigSet& config)
@@ -65,19 +65,14 @@ Error Indirect::init(const ConfigSet& config)
 
 Error Indirect::initInternal(const ConfigSet& config)
 {
-	m_fbSize = config.getNumber("r.indirect.reflectionSize");
-	m_cubemapArrSize = config.getNumber("r.indirect.maxProbeCount");
-
-	if(m_cubemapArrSize < 2)
+	// Init cache entries
 	{
-		ANKI_R_LOGE("Too low ir.cubemapTextureArraySize");
-		return Error::USER_DATA;
+		m_cacheEntries.create(getAllocator(), config.getNumber("r.indirect.maxSimultaneousProbeCount"));
 	}
 
-	m_cacheEntries.create(getAllocator(), m_cubemapArrSize);
-
-	ANKI_CHECK(initIs());
-	ANKI_CHECK(initIrradiance());
+	ANKI_CHECK(initGBuffer(config));
+	ANKI_CHECK(initLightShading(config));
+	ANKI_CHECK(initIrradiance(config));
 
 	// Load split sum integration LUT
 	ANKI_CHECK(getResourceManager().loadResource("engine_data/SplitSumIntegration.ankitex", m_integrationLut));
@@ -143,515 +138,616 @@ Error Indirect::loadMesh(CString fname, BufferPtr& vert, BufferPtr& idx, U32& id
 	return Error::NONE;
 }
 
-void Indirect::initFaceInfo(U cacheEntryIdx, U faceIdx)
+Error Indirect::initGBuffer(const ConfigSet& config)
 {
-	FaceInfo& face = m_cacheEntries[cacheEntryIdx].m_faces[faceIdx];
-	ANKI_ASSERT(!face.created());
-
-	TextureInitInfo texinit;
-
-	texinit.m_width = m_fbSize;
-	texinit.m_height = m_fbSize;
-	texinit.m_layerCount = 1;
-	texinit.m_depth = 1;
-	texinit.m_type = TextureType::_2D;
-	texinit.m_mipmapsCount = 1;
-	texinit.m_samples = 1;
-	texinit.m_sampling.m_minMagFilter = SamplingFilter::NEAREST;
-	texinit.m_sampling.m_mipmapFilter = SamplingFilter::NEAREST;
-	texinit.m_usage = TextureUsageBit::SAMPLED_FRAGMENT | TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE;
-
-	// Create color attachments
-	for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
+	m_gbuffer.m_tileSize = config.getNumber("r.indirect.reflectionResolution");
+
+	// Create attachments
 	{
-		texinit.m_format = MS_COLOR_ATTACHMENT_PIXEL_FORMATS[i];
+		TextureInitInfo texinit = m_r->create2DRenderTargetInitInfo(m_gbuffer.m_tileSize * 6,
+			m_gbuffer.m_tileSize,
+			MS_COLOR_ATTACHMENT_PIXEL_FORMATS[0],
+			TextureUsageBit::SAMPLED_FRAGMENT | TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
+			SamplingFilter::NEAREST, // Because we don't want the light pass to bleed to near faces
+			1,
+			"GI_gbuff");
+
+		// Create color attachments
+		for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
+		{
+			texinit.m_format = MS_COLOR_ATTACHMENT_PIXEL_FORMATS[i];
+			m_gbuffer.m_colorRts[i] = m_r->createAndClearRenderTarget(texinit);
+		}
 
-		face.m_gbufferColorRts[i] = m_r->createAndClearRenderTarget(texinit);
+		// Create depth attachment
+		texinit.m_usage |= TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ;
+		texinit.m_format = GBUFFER_DEPTH_ATTACHMENT_PIXEL_FORMAT;
+		m_gbuffer.m_depthRt = m_r->createAndClearRenderTarget(texinit);
 	}
 
-	// Create depth attachment
-	texinit.m_usage |= TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ;
-	texinit.m_format = GBUFFER_DEPTH_ATTACHMENT_PIXEL_FORMAT;
-	face.m_gbufferDepthRt = m_r->createAndClearRenderTarget(texinit);
+	// Create FB
+	{
+		FramebufferInitInfo fbInit("GI_gbuff");
+		fbInit.m_colorAttachmentCount = GBUFFER_COLOR_ATTACHMENT_COUNT;
 
-	// Create MS FB
-	FramebufferInitInfo fbInit;
-	fbInit.m_colorAttachmentCount = GBUFFER_COLOR_ATTACHMENT_COUNT;
+		for(U j = 0; j < GBUFFER_COLOR_ATTACHMENT_COUNT; ++j)
+		{
+			fbInit.m_colorAttachments[j].m_texture = m_gbuffer.m_colorRts[j];
+			fbInit.m_colorAttachments[j].m_loadOperation = AttachmentLoadOperation::DONT_CARE;
+		}
 
-	for(U j = 0; j < GBUFFER_COLOR_ATTACHMENT_COUNT; ++j)
-	{
-		fbInit.m_colorAttachments[j].m_texture = face.m_gbufferColorRts[j];
-		fbInit.m_colorAttachments[j].m_loadOperation = AttachmentLoadOperation::DONT_CARE;
+		fbInit.m_depthStencilAttachment.m_texture = m_gbuffer.m_depthRt;
+		fbInit.m_depthStencilAttachment.m_aspect = DepthStencilAspectBit::DEPTH;
+		fbInit.m_depthStencilAttachment.m_loadOperation = AttachmentLoadOperation::CLEAR;
+		fbInit.m_depthStencilAttachment.m_clearValue.m_depthStencil.m_depth = 1.0;
+
+		m_gbuffer.m_fb = getGrManager().newInstance<Framebuffer>(fbInit);
 	}
 
-	fbInit.m_depthStencilAttachment.m_texture = face.m_gbufferDepthRt;
-	fbInit.m_depthStencilAttachment.m_aspect = DepthStencilAspectBit::DEPTH;
-	fbInit.m_depthStencilAttachment.m_loadOperation = AttachmentLoadOperation::CLEAR;
-	fbInit.m_depthStencilAttachment.m_clearValue.m_depthStencil.m_depth = 1.0;
-
-	face.m_gbufferFb = getGrManager().newInstance<Framebuffer>(fbInit);
-
-	// Create IS FB
-	ANKI_ASSERT(m_is.m_lightRt.isCreated());
-	fbInit = FramebufferInitInfo();
-	fbInit.m_colorAttachmentCount = 1;
-	fbInit.m_colorAttachments[0].m_texture = m_is.m_lightRt;
-	fbInit.m_colorAttachments[0].m_surface.m_layer = cacheEntryIdx;
-	fbInit.m_colorAttachments[0].m_surface.m_face = faceIdx;
-	fbInit.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::CLEAR;
-	fbInit.m_depthStencilAttachment.m_loadOperation = AttachmentLoadOperation::LOAD;
-	fbInit.m_depthStencilAttachment.m_texture = face.m_gbufferDepthRt;
-	fbInit.m_depthStencilAttachment.m_aspect = DepthStencilAspectBit::DEPTH;
-
-	face.m_lightShadingFb = getGrManager().newInstance<Framebuffer>(fbInit);
-
-	// Create irradiance FB
-	fbInit = FramebufferInitInfo();
-	fbInit.m_colorAttachmentCount = 1;
-	fbInit.m_colorAttachments[0].m_texture = m_irradiance.m_cubeArr;
-	fbInit.m_colorAttachments[0].m_surface.m_layer = cacheEntryIdx;
-	fbInit.m_colorAttachments[0].m_surface.m_face = faceIdx;
-	fbInit.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::DONT_CARE;
-
-	face.m_irradianceFb = getGrManager().newInstance<Framebuffer>(fbInit);
+	return Error::NONE;
 }
 
-Error Indirect::initIs()
+Error Indirect::initLightShading(const ConfigSet& config)
 {
-	m_is.m_lightRtMipCount = computeMaxMipmapCount2d(m_fbSize, m_fbSize, 4);
-
-	// Init texture
-	TextureInitInfo texinit;
-	texinit.m_width = m_fbSize;
-	texinit.m_height = m_fbSize;
-	texinit.m_layerCount = m_cubemapArrSize;
-	texinit.m_depth = 1;
-	texinit.m_type = TextureType::CUBE_ARRAY;
-	texinit.m_mipmapsCount = m_is.m_lightRtMipCount;
-	texinit.m_samples = 1;
-	texinit.m_sampling.m_minMagFilter = SamplingFilter::LINEAR;
-	texinit.m_sampling.m_mipmapFilter = SamplingFilter::LINEAR;
-	texinit.m_usage = TextureUsageBit::SAMPLED_FRAGMENT | TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE
-		| TextureUsageBit::CLEAR | TextureUsageBit::GENERATE_MIPMAPS;
-	texinit.m_format = LIGHT_SHADING_COLOR_ATTACHMENT_PIXEL_FORMAT;
-
-	m_is.m_lightRt = m_r->createAndClearRenderTarget(texinit);
-
-	// Init shaders
-	ANKI_CHECK(getResourceManager().loadResource("programs/DeferredShading.ankiprog", m_is.m_lightProg));
-
-	ShaderProgramResourceMutationInitList<1> mutators(m_is.m_lightProg);
-	mutators.add("LIGHT_TYPE", 0);
-
-	ShaderProgramResourceConstantValueInitList<1> consts(m_is.m_lightProg);
-	consts.add("FB_SIZE", UVec2(m_fbSize, m_fbSize));
-
-	const ShaderProgramResourceVariant* variant;
-	m_is.m_lightProg->getOrCreateVariant(mutators.get(), consts.get(), variant);
-	m_is.m_plightGrProg = variant->getProgram();
-
-	mutators[0].m_value = 1;
-	m_is.m_lightProg->getOrCreateVariant(mutators.get(), consts.get(), variant);
-	m_is.m_slightGrProg = variant->getProgram();
+	m_lightShading.m_tileSize = config.getNumber("r.indirect.reflectionResolution");
+	m_lightShading.m_mipCount = computeMaxMipmapCount2d(m_lightShading.m_tileSize, m_lightShading.m_tileSize, 8);
+
+	// Init cube arr
+	{
+		TextureInitInfo texinit = m_r->create2DRenderTargetInitInfo(m_lightShading.m_tileSize,
+			m_lightShading.m_tileSize,
+			LIGHT_SHADING_COLOR_ATTACHMENT_PIXEL_FORMAT,
+			TextureUsageBit::SAMPLED_FRAGMENT | TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
+			SamplingFilter::LINEAR,
+			m_lightShading.m_mipCount,
+			"GI_refl");
+		texinit.m_type = TextureType::CUBE_ARRAY;
+		texinit.m_layerCount = m_cacheEntries.getSize();
+
+		m_lightShading.m_cubeArr = m_r->createAndClearRenderTarget(texinit);
+	}
+
+	// Init progs
+	{
+		ANKI_CHECK(getResourceManager().loadResource("programs/DeferredShading.ankiprog", m_lightShading.m_lightProg));
+
+		ShaderProgramResourceMutationInitList<1> mutators(m_lightShading.m_lightProg);
+		mutators.add("LIGHT_TYPE", 0);
+
+		ShaderProgramResourceConstantValueInitList<1> consts(m_lightShading.m_lightProg);
+		consts.add("FB_SIZE", UVec2(m_lightShading.m_tileSize, m_lightShading.m_tileSize));
+
+		const ShaderProgramResourceVariant* variant;
+		m_lightShading.m_lightProg->getOrCreateVariant(mutators.get(), consts.get(), variant);
+		m_lightShading.m_plightGrProg = variant->getProgram();
+
+		mutators[0].m_value = 1;
+		m_lightShading.m_lightProg->getOrCreateVariant(mutators.get(), consts.get(), variant);
+		m_lightShading.m_slightGrProg = variant->getProgram();
+	}
 
 	// Init vert/idx buffers
-	ANKI_CHECK(
-		loadMesh("engine_data/Plight.ankimesh", m_is.m_plightPositions, m_is.m_plightIndices, m_is.m_plightIdxCount));
+	ANKI_CHECK(loadMesh("engine_data/Plight.ankimesh",
+		m_lightShading.m_plightPositions,
+		m_lightShading.m_plightIndices,
+		m_lightShading.m_plightIdxCount));
 
-	ANKI_CHECK(
-		loadMesh("engine_data/Slight.ankimesh", m_is.m_slightPositions, m_is.m_slightIndices, m_is.m_slightIdxCount));
+	ANKI_CHECK(loadMesh("engine_data/Slight.ankimesh",
+		m_lightShading.m_slightPositions,
+		m_lightShading.m_slightIndices,
+		m_lightShading.m_slightIdxCount));
 
 	return Error::NONE;
 }
 
-Error Indirect::initIrradiance()
+Error Indirect::initIrradiance(const ConfigSet& config)
 {
-	m_irradiance.m_cubeArrMipCount = computeMaxMipmapCount2d(IRRADIANCE_TEX_SIZE, IRRADIANCE_TEX_SIZE, 4);
-
-	// Init texture
-	TextureInitInfo texinit;
-	texinit.m_width = IRRADIANCE_TEX_SIZE;
-	texinit.m_height = IRRADIANCE_TEX_SIZE;
-	texinit.m_layerCount = m_cubemapArrSize;
-	texinit.m_depth = 1;
-	texinit.m_type = TextureType::CUBE_ARRAY;
-	texinit.m_mipmapsCount = m_irradiance.m_cubeArrMipCount;
-	texinit.m_samples = 1;
-	texinit.m_sampling.m_minMagFilter = SamplingFilter::LINEAR;
-	texinit.m_sampling.m_mipmapFilter = SamplingFilter::LINEAR;
-	texinit.m_usage = TextureUsageBit::SAMPLED_FRAGMENT | TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE
-		| TextureUsageBit::CLEAR | TextureUsageBit::GENERATE_MIPMAPS;
-	texinit.m_format = LIGHT_SHADING_COLOR_ATTACHMENT_PIXEL_FORMAT;
-
-	m_irradiance.m_cubeArr = m_r->createAndClearRenderTarget(texinit);
+	// Init atlas
+	{
+		TextureInitInfo texinit = m_r->create2DRenderTargetInitInfo(m_irradiance.m_tileSize,
+			m_irradiance.m_tileSize,
+			LIGHT_SHADING_COLOR_ATTACHMENT_PIXEL_FORMAT,
+			TextureUsageBit::SAMPLED_FRAGMENT | TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
+			SamplingFilter::LINEAR,
+			1,
+			"GI_irr");
+
+		texinit.m_layerCount = m_cacheEntries.getSize();
+		texinit.m_type = TextureType::CUBE_ARRAY;
+
+		m_irradiance.m_cubeArr = m_r->createAndClearRenderTarget(texinit);
+	}
 
 	// Create prog
-	ANKI_CHECK(m_r->getResourceManager().loadResource("programs/Irradiance.ankiprog", m_irradiance.m_prog));
+	{
+		ANKI_CHECK(m_r->getResourceManager().loadResource("programs/Irradiance.ankiprog", m_irradiance.m_prog));
 
-	ShaderProgramResourceConstantValueInitList<1> consts(m_irradiance.m_prog);
-	consts.add("FACE_SIZE", U32(IRRADIANCE_TEX_SIZE));
+		const F32 envMapReadMip = computeMaxMipmapCount2d(
+			m_lightShading.m_tileSize, m_lightShading.m_tileSize, m_irradiance.m_envMapReadSize);
 
-	const ShaderProgramResourceVariant* variant;
-	m_irradiance.m_prog->getOrCreateVariant(consts.get(), variant);
-	m_irradiance.m_grProg = variant->getProgram();
+		ShaderProgramResourceConstantValueInitList<2> consts(m_irradiance.m_prog);
+		consts.add("ENV_TEX_TILE_SIZE", U32(m_irradiance.m_envMapReadSize));
+		consts.add("ENV_TEX_MIP", envMapReadMip);
+
+		const ShaderProgramResourceVariant* variant;
+		m_irradiance.m_prog->getOrCreateVariant(consts.get(), variant);
+		m_irradiance.m_grProg = variant->getProgram();
+	}
 
 	return Error::NONE;
 }
 
-void Indirect::runMs(RenderingContext& rctx, const RenderQueue& rqueue, U layer, U faceIdx)
+void Indirect::initCacheEntry(U32 cacheEntryIdx)
 {
-	CommandBufferPtr& cmdb = rctx.m_commandBuffer;
+	CacheEntry& cacheEntry = m_cacheEntries[cacheEntryIdx];
 
-	FaceInfo& face = m_cacheEntries[layer].m_faces[faceIdx];
-
-	if(!face.created())
+	for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
 	{
-		initFaceInfo(layer, faceIdx);
+		FramebufferInitInfo fbInit("GI_refl");
+		fbInit.m_colorAttachmentCount = 1;
+		fbInit.m_colorAttachments[0].m_texture = m_lightShading.m_cubeArr;
+		fbInit.m_colorAttachments[0].m_surface.m_layer = cacheEntryIdx;
+		fbInit.m_colorAttachments[0].m_surface.m_face = faceIdx;
+		fbInit.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::CLEAR;
+
+		ANKI_ASSERT(!cacheEntry.m_lightShadingFbs[faceIdx].isCreated());
+		cacheEntry.m_lightShadingFbs[faceIdx] = getGrManager().newInstance<Framebuffer>(fbInit);
+
+		fbInit.m_colorAttachments[0].m_texture = m_irradiance.m_cubeArr;
+		fbInit.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::DONT_CARE;
+
+		ANKI_ASSERT(!cacheEntry.m_irradianceFbs[faceIdx].isCreated());
+		cacheEntry.m_irradianceFbs[faceIdx] = getGrManager().newInstance<Framebuffer>(fbInit);
 	}
+}
 
-	// Set barriers
-	for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
+void Indirect::prepareProbes(
+	RenderingContext& ctx, ReflectionProbeQueueElement*& probeToUpdate, U32& probeToUpdateCacheEntryIdx)
+{
+	probeToUpdate = nullptr;
+	probeToUpdateCacheEntryIdx = MAX_U32;
+
+	if(ANKI_UNLIKELY(ctx.m_renderQueue->m_reflectionProbes.getSize() == 0))
 	{
-		cmdb->setTextureSurfaceBarrier(face.m_gbufferColorRts[i],
-			TextureUsageBit::NONE,
-			TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
-			TextureSurfaceInfo(0, 0, 0, 0));
+		return;
 	}
 
-	cmdb->setTextureSurfaceBarrier(face.m_gbufferDepthRt,
-		TextureUsageBit::NONE,
-		TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE,
-		TextureSurfaceInfo(0, 0, 0, 0));
+	// Iterate the probes and:
+	// - Find a probe to update this frame
+	// - Find a probe to update next frame
+	// - Find the cache entries for each probe
+	DynamicArray<ReflectionProbeQueueElement> newListOfProbes;
+	newListOfProbes.create(ctx.m_tempAllocator, ctx.m_renderQueue->m_reflectionProbes.getSize());
+	U newListOfProbeCount = 0;
+
+	Bool foundProbeToRenderNextFrame = false;
+	for(U32 probeIdx = 0; probeIdx < ctx.m_renderQueue->m_reflectionProbes.getSize(); ++probeIdx)
+	{
+		ReflectionProbeQueueElement& probe = ctx.m_renderQueue->m_reflectionProbes[probeIdx];
+
+		// Find cache entry
+		U32 cacheEntryIdx = MAX_U32;
+		Bool cacheEntryFoundInCache;
+		const Bool allocFailed = findBestCacheEntry(probe.m_uuid, cacheEntryIdx, cacheEntryFoundInCache);
+		if(ANKI_UNLIKELY(allocFailed))
+		{
+			continue;
+		}
 
-	// Start render pass
-	cmdb->beginRenderPass(face.m_gbufferFb);
-	cmdb->setViewport(0, 0, m_fbSize, m_fbSize);
+		// Check if we _should_ and _can_ update the probe
+		const Bool probeNeedsUpdate = m_cacheEntries[cacheEntryIdx].m_probeUuid != probe.m_uuid;
+		if(ANKI_UNLIKELY(probeNeedsUpdate))
+		{
+			const Bool updateFailed = probeToUpdate != nullptr || probe.m_renderQueues[0] == nullptr;
 
-	/// Draw
-	m_r->getSceneDrawer().drawRange(Pass::GB_FS,
-		rqueue.m_viewMatrix,
-		rqueue.m_viewProjectionMatrix,
-		cmdb,
-		rqueue.m_renderables.getBegin(),
-		rqueue.m_renderables.getEnd());
+			if(updateFailed && !foundProbeToRenderNextFrame)
+			{
+				// Probe will be updated next frame
+				foundProbeToRenderNextFrame = true;
+				probe.m_feedbackCallback(true, probe.m_userData);
+			}
 
-	// End and set barriers
-	cmdb->endRenderPass();
+			if(updateFailed)
+			{
+				continue;
+			}
+		}
 
-	for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
-	{
-		cmdb->setTextureSurfaceBarrier(face.m_gbufferColorRts[i],
-			TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
-			TextureUsageBit::SAMPLED_FRAGMENT,
-			TextureSurfaceInfo(0, 0, 0, 0));
+		// All good, can use this probe in this frame
+
+		// Update the cache entry
+		m_cacheEntries[cacheEntryIdx].m_probeUuid = probe.m_uuid;
+		m_cacheEntries[cacheEntryIdx].m_lastUsedTimestamp = m_r->getGlobalTimestamp();
+
+		// Update the probe
+		probe.m_textureArrayIndex = cacheEntryIdx;
+
+		// Push the probe to the new list
+		newListOfProbes[newListOfProbeCount++] = probe;
+
+		// Update cache map
+		if(!cacheEntryFoundInCache)
+		{
+			m_probeUuidToCacheEntryIdx.pushBack(getAllocator(), probe.m_uuid, cacheEntryIdx);
+		}
+
+		// Don't gather renderables next frame
+		if(probe.m_renderQueues[0] != nullptr)
+		{
+			probe.m_feedbackCallback(false, probe.m_userData);
+		}
+
+		// Inform about the probe to update this frame
+		if(probeNeedsUpdate)
+		{
+			ANKI_ASSERT(probe.m_renderQueues[0] != nullptr && probeToUpdate == nullptr);
+
+			probeToUpdateCacheEntryIdx = cacheEntryIdx;
+			probeToUpdate = &newListOfProbes[newListOfProbeCount - 1];
+		}
 	}
 
-	cmdb->setTextureSurfaceBarrier(face.m_gbufferDepthRt,
-		TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE,
-		TextureUsageBit::SAMPLED_FRAGMENT | TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ,
-		TextureSurfaceInfo(0, 0, 0, 0));
+	// Replace the probe list in the queue
+	if(newListOfProbeCount > 0)
+	{
+		ReflectionProbeQueueElement* firstProbe;
+		PtrSize probeCount, storage;
+		newListOfProbes.moveAndReset(firstProbe, probeCount, storage);
+		ctx.m_renderQueue->m_reflectionProbes = WeakArray<ReflectionProbeQueueElement>(firstProbe, newListOfProbeCount);
+	}
+	else
+	{
+		ctx.m_renderQueue->m_reflectionProbes = WeakArray<ReflectionProbeQueueElement>();
+		newListOfProbes.destroy(ctx.m_tempAllocator);
+	}
 }
 
-void Indirect::runIs(RenderingContext& rctx, const RenderQueue& rqueue, U layer, U faceIdx)
+void Indirect::runGBuffer(RenderingContext& rctx, const ReflectionProbeQueueElement& probe)
 {
 	CommandBufferPtr& cmdb = rctx.m_commandBuffer;
-	FaceInfo& face = m_cacheEntries[layer].m_faces[faceIdx];
 
-	// Set barriers
-	cmdb->setTextureSurfaceBarrier(m_is.m_lightRt,
-		TextureUsageBit::NONE,
-		TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
-		TextureSurfaceInfo(0, 0, faceIdx, layer));
+	cmdb->beginRenderPass(m_gbuffer.m_fb);
+
+	// For each face
+	for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
+	{
+		const U32 viewportX = faceIdx * m_gbuffer.m_tileSize;
+		cmdb->setViewport(viewportX, 0, viewportX + m_gbuffer.m_tileSize, m_gbuffer.m_tileSize);
+		cmdb->setScissor(viewportX, 0, viewportX + m_gbuffer.m_tileSize, m_gbuffer.m_tileSize);
+
+		/// Draw
+		ANKI_ASSERT(probe.m_renderQueues[faceIdx]);
+		const RenderQueue& rqueue = *probe.m_renderQueues[faceIdx];
+
+		m_r->getSceneDrawer().drawRange(Pass::GB_FS,
+			rqueue.m_viewMatrix,
+			rqueue.m_viewProjectionMatrix,
+			cmdb,
+			rqueue.m_renderables.getBegin(),
+			rqueue.m_renderables.getEnd());
+	}
 
-	// Set state
-	cmdb->beginRenderPass(face.m_lightShadingFb);
+	cmdb->endRenderPass();
 
-	cmdb->bindTexture(0, 0, face.m_gbufferColorRts[0]);
-	cmdb->bindTexture(0, 1, face.m_gbufferColorRts[1]);
-	cmdb->bindTexture(0, 2, face.m_gbufferColorRts[2]);
-	cmdb->bindTexture(0, 3, face.m_gbufferDepthRt);
+	// Restore state
+	cmdb->setScissor(0, 0, MAX_U16, MAX_U16);
+}
 
-	cmdb->setVertexAttribute(0, 0, PixelFormat(ComponentFormat::R32G32B32, TransformFormat::FLOAT), 0);
+void Indirect::runLightShading(RenderingContext& rctx, const ReflectionProbeQueueElement& probe, CacheEntry& cacheEntry)
+{
+	CommandBufferPtr& cmdb = rctx.m_commandBuffer;
 
+	// Set common state
+	for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
+	{
+		cmdb->bindTexture(0, i, m_gbuffer.m_colorRts[i]);
+	}
+	cmdb->bindTexture(0, GBUFFER_COLOR_ATTACHMENT_COUNT, m_gbuffer.m_depthRt);
+	cmdb->setVertexAttribute(0, 0, PixelFormat(ComponentFormat::R32G32B32, TransformFormat::FLOAT), 0);
+	cmdb->setViewport(0, 0, m_lightShading.m_tileSize, m_lightShading.m_tileSize);
 	cmdb->setBlendFactors(0, BlendFactor::ONE, BlendFactor::ONE);
-	cmdb->setDepthCompareOperation(CompareOperation::GREATER);
-	cmdb->setDepthWrite(false);
 	cmdb->setCullMode(FaceSelectionBit::FRONT);
 
-	// Process all lights
-	const Mat4& vpMat = rqueue.m_viewProjectionMatrix;
-	const Mat4& vMat = rqueue.m_viewMatrix;
-
-	cmdb->bindShaderProgram(m_is.m_plightGrProg);
-	cmdb->bindVertexBuffer(0, m_is.m_plightPositions, 0, sizeof(F32) * 3);
-	cmdb->bindIndexBuffer(m_is.m_plightIndices, 0, IndexType::U16);
-
-	const PointLightQueueElement* plightEl = rqueue.m_pointLights.getBegin();
-	const PointLightQueueElement* end = rqueue.m_pointLights.getEnd();
-	while(plightEl != end)
+	// For each face
+	for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
 	{
-		// Update uniforms
-		IrVertex* vert = allocateAndBindUniforms<IrVertex*>(sizeof(IrVertex), cmdb, 0, 0);
+		ANKI_ASSERT(probe.m_renderQueues[faceIdx]);
+		const RenderQueue& rqueue = *probe.m_renderQueues[faceIdx];
 
-		Mat4 modelM(plightEl->m_worldPosition.xyz1(), Mat3::getIdentity(), plightEl->m_radius);
+		const Mat4& vpMat = rqueue.m_viewProjectionMatrix;
+		const Mat4& vMat = rqueue.m_viewMatrix;
 
-		vert->m_mvp = vpMat * modelM;
+		// Set per face state
+		cmdb->beginRenderPass(cacheEntry.m_lightShadingFbs[faceIdx]);
 
-		IrPointLight* light = allocateAndBindUniforms<IrPointLight*>(sizeof(IrPointLight), cmdb, 0, 1);
+		// Do point lights
+		cmdb->bindShaderProgram(m_lightShading.m_plightGrProg);
+		cmdb->bindVertexBuffer(0, m_lightShading.m_plightPositions, 0, sizeof(F32) * 3);
+		cmdb->bindIndexBuffer(m_lightShading.m_plightIndices, 0, IndexType::U16);
 
-		Vec4 pos = vMat * plightEl->m_worldPosition.xyz1();
+		const PointLightQueueElement* plightEl = rqueue.m_pointLights.getBegin();
+		while(plightEl != rqueue.m_pointLights.getEnd())
+		{
+			// Update uniforms
+			LightPassVertexUniforms* vert =
+				allocateAndBindUniforms<LightPassVertexUniforms*>(sizeof(LightPassVertexUniforms), cmdb, 0, 0);
 
-		light->m_projectionParams = rqueue.m_projectionMatrix.extractPerspectiveUnprojectionParams();
-		light->m_posRadius = Vec4(pos.xyz(), 1.0 / (plightEl->m_radius * plightEl->m_radius));
-		light->m_diffuseColorPad1 = plightEl->m_diffuseColor.xyz0();
-		light->m_specularColorPad1 = plightEl->m_specularColor.xyz0();
+			Mat4 modelM(plightEl->m_worldPosition.xyz1(), Mat3::getIdentity(), plightEl->m_radius);
 
-		// Draw
-		cmdb->drawElements(PrimitiveTopology::TRIANGLES, m_is.m_plightIdxCount);
+			vert->m_mvp = vpMat * modelM;
 
-		++plightEl;
-	}
+			LightPassPointLightUniforms* light =
+				allocateAndBindUniforms<LightPassPointLightUniforms*>(sizeof(LightPassPointLightUniforms), cmdb, 0, 1);
 
-	cmdb->bindShaderProgram(m_is.m_slightGrProg);
-	cmdb->bindVertexBuffer(0, m_is.m_slightPositions, 0, sizeof(F32) * 3);
-	cmdb->bindIndexBuffer(m_is.m_slightIndices, 0, IndexType::U16);
+			Vec4 pos = vMat * plightEl->m_worldPosition.xyz1();
 
-	const SpotLightQueueElement* splightEl = rqueue.m_spotLights.getBegin();
-	while(splightEl != rqueue.m_spotLights.getEnd())
-	{
-		// Compute the model matrix
-		//
-		Mat4 modelM(splightEl->m_worldTransform.getTranslationPart().xyz1(),
-			splightEl->m_worldTransform.getRotationPart(),
-			1.0f);
+			light->m_inputTexUvScaleAndOffset = Vec4(1.0f / 6.0f, 1.0f, faceIdx * (1.0f / 6.0f), 0.0f);
+			light->m_projectionParams = rqueue.m_projectionMatrix.extractPerspectiveUnprojectionParams();
+			light->m_posRadius = Vec4(pos.xyz(), 1.0f / (plightEl->m_radius * plightEl->m_radius));
+			light->m_diffuseColorPad1 = plightEl->m_diffuseColor.xyz0();
+			light->m_specularColorPad1 = plightEl->m_specularColor.xyz0();
 
-		// Calc the scale of the cone
-		Mat4 scaleM(Mat4::getIdentity());
-		scaleM(0, 0) = tan(splightEl->m_outerAngle / 2.0f) * splightEl->m_distance;
-		scaleM(1, 1) = scaleM(0, 0);
-		scaleM(2, 2) = splightEl->m_distance;
+			// Draw
+			cmdb->drawElements(PrimitiveTopology::TRIANGLES, m_lightShading.m_plightIdxCount);
 
-		modelM = modelM * scaleM;
+			++plightEl;
+		}
 
-		// Update vertex uniforms
-		IrVertex* vert = allocateAndBindUniforms<IrVertex*>(sizeof(IrVertex), cmdb, 0, 0);
-		vert->m_mvp = vpMat * modelM;
+		// Do spot lights
+		cmdb->bindShaderProgram(m_lightShading.m_slightGrProg);
+		cmdb->bindVertexBuffer(0, m_lightShading.m_slightPositions, 0, sizeof(F32) * 3);
+		cmdb->bindIndexBuffer(m_lightShading.m_slightIndices, 0, IndexType::U16);
 
-		// Update fragment uniforms
-		IrSpotLight* light = allocateAndBindUniforms<IrSpotLight*>(sizeof(IrSpotLight), cmdb, 0, 1);
+		const SpotLightQueueElement* splightEl = rqueue.m_spotLights.getBegin();
+		while(splightEl != rqueue.m_spotLights.getEnd())
+		{
+			// Compute the model matrix
+			//
+			Mat4 modelM(splightEl->m_worldTransform.getTranslationPart().xyz1(),
+				splightEl->m_worldTransform.getRotationPart(),
+				1.0f);
 
-		light->m_projectionParams = rqueue.m_projectionMatrix.extractPerspectiveUnprojectionParams();
+			// Calc the scale of the cone
+			Mat4 scaleM(Mat4::getIdentity());
+			scaleM(0, 0) = tan(splightEl->m_outerAngle / 2.0f) * splightEl->m_distance;
+			scaleM(1, 1) = scaleM(0, 0);
+			scaleM(2, 2) = splightEl->m_distance;
 
-		Vec4 pos = vMat * splightEl->m_worldTransform.getTranslationPart().xyz1();
-		light->m_posRadius = Vec4(pos.xyz(), 1.0 / (splightEl->m_distance * splightEl->m_distance));
+			modelM = modelM * scaleM;
 
-		light->m_diffuseColorOuterCos = Vec4(splightEl->m_diffuseColor, cos(splightEl->m_outerAngle / 2.0f));
+			// Update vertex uniforms
+			LightPassVertexUniforms* vert =
+				allocateAndBindUniforms<LightPassVertexUniforms*>(sizeof(LightPassVertexUniforms), cmdb, 0, 0);
+			vert->m_mvp = vpMat * modelM;
 
-		light->m_specularColorInnerCos = Vec4(splightEl->m_specularColor, cos(splightEl->m_innerAngle / 2.0f));
+			// Update fragment uniforms
+			LightPassSpotLightUniforms* light =
+				allocateAndBindUniforms<LightPassSpotLightUniforms*>(sizeof(LightPassSpotLightUniforms), cmdb, 0, 1);
 
-		Vec3 lightDir = -splightEl->m_worldTransform.getZAxis().xyz();
-		lightDir = vMat.getRotationPart() * lightDir;
-		light->m_lightDirPad1 = lightDir.xyz0();
+			light->m_inputTexUvScaleAndOffset = Vec4(1.0f / 6.0f, 1.0f, faceIdx * (1.0f / 6.0f), 0.0f);
+			light->m_projectionParams = rqueue.m_projectionMatrix.extractPerspectiveUnprojectionParams();
 
-		// Draw
-		cmdb->drawElements(PrimitiveTopology::TRIANGLES, m_is.m_slightIdxCount);
+			Vec4 pos = vMat * splightEl->m_worldTransform.getTranslationPart().xyz1();
+			light->m_posRadius = Vec4(pos.xyz(), 1.0f / (splightEl->m_distance * splightEl->m_distance));
 
-		++splightEl;
-	}
+			light->m_diffuseColorOuterCos = Vec4(splightEl->m_diffuseColor, cos(splightEl->m_outerAngle / 2.0f));
 
-	// Generate mips
-	cmdb->endRenderPass();
+			light->m_specularColorInnerCos = Vec4(splightEl->m_specularColor, cos(splightEl->m_innerAngle / 2.0f));
 
-	cmdb->setTextureSurfaceBarrier(m_is.m_lightRt,
-		TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
-		TextureUsageBit::GENERATE_MIPMAPS,
-		TextureSurfaceInfo(0, 0, faceIdx, layer));
+			Vec3 lightDir = -splightEl->m_worldTransform.getZAxis().xyz();
+			lightDir = vMat.getRotationPart() * lightDir;
+			light->m_lightDirPad1 = lightDir.xyz0();
 
-	cmdb->generateMipmaps2d(m_is.m_lightRt, faceIdx, layer);
+			// Draw
+			cmdb->drawElements(PrimitiveTopology::TRIANGLES, m_lightShading.m_slightIdxCount);
 
-	for(U i = 0; i < m_is.m_lightRtMipCount; ++i)
-	{
-		cmdb->setTextureSurfaceBarrier(m_is.m_lightRt,
-			TextureUsageBit::GENERATE_MIPMAPS,
-			TextureUsageBit::SAMPLED_FRAGMENT,
-			TextureSurfaceInfo(i, 0, faceIdx, layer));
+			++splightEl;
+		}
+
+		cmdb->endRenderPass();
 	}
 
 	// Restore state
 	cmdb->setBlendFactors(0, BlendFactor::ONE, BlendFactor::ZERO);
-	cmdb->setDepthCompareOperation(CompareOperation::LESS);
-	cmdb->setDepthWrite(true);
 	cmdb->setCullMode(FaceSelectionBit::BACK);
 }
 
-void Indirect::computeIrradiance(RenderingContext& rctx, U layer, U faceIdx)
+void Indirect::runIrradiance(RenderingContext& rctx, U32 cacheEntryIdx)
 {
 	CommandBufferPtr& cmdb = rctx.m_commandBuffer;
-	FaceInfo& face = m_cacheEntries[layer].m_faces[faceIdx];
-
-	// Set barrier
-	cmdb->setTextureSurfaceBarrier(m_irradiance.m_cubeArr,
-		TextureUsageBit::NONE,
-		TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
-		TextureSurfaceInfo(0, 0, faceIdx, layer));
-
-	// Set state and draw
-	cmdb->setViewport(0, 0, IRRADIANCE_TEX_SIZE, IRRADIANCE_TEX_SIZE);
 
-	UVec4* faceIdxArrayIdx = allocateAndBindUniforms<UVec4*>(sizeof(UVec4), cmdb, 0, 0);
-	faceIdxArrayIdx->x() = faceIdx;
-	faceIdxArrayIdx->y() = layer;
-
-	cmdb->informTextureCurrentUsage(m_is.m_lightRt, TextureUsageBit::SAMPLED_FRAGMENT);
-	cmdb->bindTexture(0, 0, m_is.m_lightRt);
+	// Set common state
 	cmdb->bindShaderProgram(m_irradiance.m_grProg);
-	cmdb->beginRenderPass(face.m_irradianceFb);
+	cmdb->bindTexture(0, 0, m_lightShading.m_cubeArr);
 
-	m_r->drawQuad(cmdb);
-	cmdb->endRenderPass();
+	// For each face
+	for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
+	{
+		cmdb->beginRenderPass(m_cacheEntries[cacheEntryIdx].m_irradianceFbs[faceIdx]);
 
-	// Gen mips
-	cmdb->setTextureSurfaceBarrier(m_irradiance.m_cubeArr,
-		TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
-		TextureUsageBit::GENERATE_MIPMAPS,
-		TextureSurfaceInfo(0, 0, faceIdx, layer));
+		cmdb->setViewport(0, 0, m_irradiance.m_tileSize, m_irradiance.m_tileSize);
 
-	cmdb->generateMipmaps2d(m_irradiance.m_cubeArr, faceIdx, layer);
+		// Set uniforms
+		UVec4* faceIdxArrayIdx = allocateAndBindUniforms<UVec4*>(sizeof(UVec4), cmdb, 0, 0);
+		faceIdxArrayIdx->x() = faceIdx;
+		faceIdxArrayIdx->y() = cacheEntryIdx;
 
-	for(U i = 0; i < m_irradiance.m_cubeArrMipCount; ++i)
-	{
-		cmdb->setTextureSurfaceBarrier(m_irradiance.m_cubeArr,
-			TextureUsageBit::GENERATE_MIPMAPS,
-			TextureUsageBit::SAMPLED_FRAGMENT,
-			TextureSurfaceInfo(i, 0, faceIdx, layer));
+		m_r->drawQuad(cmdb);
+		cmdb->endRenderPass();
 	}
 }
 
 void Indirect::run(RenderingContext& rctx)
 {
 	ANKI_TRACE_SCOPED_EVENT(RENDER_IR);
+	CommandBufferPtr& cmdb = rctx.m_commandBuffer;
+
+	// Prepare the probes
+	ReflectionProbeQueueElement* probeToUpdate;
+	U32 probeToUpdateCacheEntryIdx;
+	prepareProbes(rctx, probeToUpdate, probeToUpdateCacheEntryIdx);
 
-	if(rctx.m_renderQueue->m_reflectionProbes.getSize() > m_cubemapArrSize)
+	// Update a probe if needed
+	if(probeToUpdate)
 	{
-		ANKI_R_LOGW("Increase the ir.cubemapTextureArraySize");
-	}
+		if(!m_cacheEntries[probeToUpdateCacheEntryIdx].m_lightShadingFbs[0].isCreated())
+		{
+			initCacheEntry(probeToUpdateCacheEntryIdx);
+		}
 
-	// Render some of the probes
-	ReflectionProbeQueueElement* it = rctx.m_renderQueue->m_reflectionProbes.getBegin();
-	const ReflectionProbeQueueElement* end = rctx.m_renderQueue->m_reflectionProbes.getEnd();
+		// Barriers
+		for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
+		{
+			cmdb->setTextureSurfaceBarrier(m_gbuffer.m_colorRts[i],
+				TextureUsageBit::NONE,
+				TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
+				TextureSurfaceInfo(0, 0, 0, 0));
+		}
 
-	U probesRendered = 0;
-	while(it != end)
-	{
-		// Write and render probe
+		cmdb->setTextureSurfaceBarrier(m_gbuffer.m_depthRt,
+			TextureUsageBit::NONE,
+			TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE,
+			TextureSurfaceInfo(0, 0, 0, 0));
 
-		Bool render = false;
-		U entry;
-		findCacheEntry(it->m_uuid, entry, render);
-		it->m_textureArrayIndex = entry;
+		// Run g-buffer pass
+		runGBuffer(rctx, *probeToUpdate);
 
-		if(it->m_renderQueues[0] && probesRendered < MAX_PROBE_RENDERS_PER_FRAME)
+		// Barriers
+		for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
 		{
-			++probesRendered;
-			renderReflection(*it, rctx, entry);
-
-			// Rendered, no need to render it again next frame
-			it->m_feedbackCallback(false, it->m_userData);
+			cmdb->setTextureSurfaceBarrier(m_gbuffer.m_colorRts[i],
+				TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
+				TextureUsageBit::SAMPLED_FRAGMENT,
+				TextureSurfaceInfo(0, 0, 0, 0));
 		}
 
-		// If you need to render it mark it for the next frame
-		if(render)
+		cmdb->setTextureSurfaceBarrier(m_gbuffer.m_depthRt,
+			TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE,
+			TextureUsageBit::SAMPLED_FRAGMENT,
+			TextureSurfaceInfo(0, 0, 0, 0));
+
+		for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
 		{
-			it->m_feedbackCallback(true, it->m_userData);
+			cmdb->setTextureSurfaceBarrier(m_lightShading.m_cubeArr,
+				TextureUsageBit::NONE,
+				TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
+				TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
 		}
 
-		// Advance
-		++it;
-	}
+		// Run light shading pass
+		runLightShading(rctx, *probeToUpdate, m_cacheEntries[probeToUpdateCacheEntryIdx]);
 
-	// Inform on tex usage
-	CommandBufferPtr& cmdb = rctx.m_commandBuffer;
-	cmdb->informTextureCurrentUsage(m_irradiance.m_cubeArr, TextureUsageBit::SAMPLED_FRAGMENT);
-	cmdb->informTextureCurrentUsage(m_is.m_lightRt, TextureUsageBit::SAMPLED_FRAGMENT);
-}
+		// Barriers
+		for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
+		{
+			cmdb->setTextureSurfaceBarrier(m_lightShading.m_cubeArr,
+				TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
+				TextureUsageBit::GENERATE_MIPMAPS,
+				TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
+		}
 
-void Indirect::renderReflection(const ReflectionProbeQueueElement& probeEl, RenderingContext& ctx, U cubemapIdx)
-{
-	ANKI_TRACE_INC_COUNTER(RENDERER_REFLECTIONS, 1);
+		// Run the mipmaping passes
+		for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
+		{
+			cmdb->generateMipmaps2d(m_lightShading.m_cubeArr, faceIdx, probeToUpdateCacheEntryIdx);
+		}
 
-	// Render cubemap
-	for(U i = 0; i < 6; ++i)
-	{
-		const RenderQueue* rqueue = probeEl.m_renderQueues[i];
-		ANKI_ASSERT(rqueue);
+		// Barriers
+		for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
+		{
+			cmdb->setTextureSurfaceBarrier(m_lightShading.m_cubeArr,
+				TextureUsageBit::GENERATE_MIPMAPS,
+				TextureUsageBit::SAMPLED_FRAGMENT,
+				TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
+
+			cmdb->setTextureSurfaceBarrier(m_irradiance.m_cubeArr,
+				TextureUsageBit::NONE,
+				TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
+				TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
+		}
 
-		runMs(ctx, *rqueue, cubemapIdx, i);
-		runIs(ctx, *rqueue, cubemapIdx, i);
-	}
+		// Run irradiance
+		runIrradiance(rctx, probeToUpdateCacheEntryIdx);
 
-	for(U i = 0; i < 6; ++i)
+		// Barriers
+		for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
+		{
+			cmdb->setTextureSurfaceBarrier(m_irradiance.m_cubeArr,
+				TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
+				TextureUsageBit::SAMPLED_FRAGMENT,
+				TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
+		}
+	}
+	else
 	{
-		computeIrradiance(ctx, cubemapIdx, i);
+		cmdb->informTextureCurrentUsage(m_lightShading.m_cubeArr, TextureUsageBit::SAMPLED_FRAGMENT);
+		cmdb->informTextureCurrentUsage(m_irradiance.m_cubeArr, TextureUsageBit::SAMPLED_FRAGMENT);
 	}
 }
 
-void Indirect::findCacheEntry(U64 uuid, U& entry, Bool& render)
+Bool Indirect::findBestCacheEntry(U64 probeUuid, U32& cacheEntryIdxAllocated, Bool& cacheEntryFound)
 {
-	// Try find a candidate cache entry
-	CacheEntry* candidate = nullptr;
-	auto it = m_uuidToCacheEntry.find(uuid);
-	if(it != m_uuidToCacheEntry.getEnd())
-	{
-		// Found
+	ANKI_ASSERT(probeUuid > 0);
 
-		render = m_r->resourcesLoaded();
-		candidate = &(*it);
-	}
-	else
+	// First, try to see if the probe is in the cache
+	auto it = m_probeUuidToCacheEntryIdx.find(probeUuid);
+	if(it != m_probeUuidToCacheEntryIdx.getEnd())
 	{
-		// Not found
-
-		render = true;
-
-		// Iterate to find an empty or someone to kick
-		CacheEntry* empty = nullptr;
-		CacheEntry* kick = nullptr;
-		for(auto& entry : m_cacheEntries)
+		const U32 cacheEntryIdx = *it;
+		if(m_cacheEntries[cacheEntryIdx].m_probeUuid == probeUuid)
 		{
-			if(entry.m_nodeUuid == 0)
-			{
-				empty = &entry;
-				break;
-			}
-			else if(kick == nullptr || entry.m_timestamp < kick->m_timestamp)
-			{
-				kick = &entry;
-			}
+			// Found it
+			cacheEntryIdxAllocated = cacheEntryIdx;
+			cacheEntryFound = true;
+			return false;
+		}
+		else
+		{
+			// Cache entry is wrong, remove it
+			m_probeUuidToCacheEntryIdx.erase(getAllocator(), it);
 		}
+	}
+	cacheEntryFound = false;
 
-		if(empty)
+	// 2nd and 3rd choice, find an empty entry or some entry to re-use
+	U32 emptyCacheEntryIdx = MAX_U32;
+	U32 cacheEntryIdxToKick = MAX_U32;
+	Timestamp cacheEntryIdxToKickMinTimestamp = MAX_TIMESTAMP;
+	for(U32 cacheEntryIdx = 0; cacheEntryIdx < m_cacheEntries.getSize(); ++cacheEntryIdx)
+	{
+		if(m_cacheEntries[cacheEntryIdx].m_probeUuid == 0)
 		{
-			candidate = empty;
+			// Found an empty
+			emptyCacheEntryIdx = cacheEntryIdx;
+			break;
 		}
-		else
+		else if(m_cacheEntries[cacheEntryIdx].m_lastUsedTimestamp != m_r->getGlobalTimestamp()
+			&& m_cacheEntries[cacheEntryIdx].m_lastUsedTimestamp < cacheEntryIdxToKickMinTimestamp)
 		{
-			ANKI_ASSERT(kick);
-
-			candidate = kick;
-
-			// Remove from the map
-			m_uuidToCacheEntry.erase(kick);
+			// Found some with low timestamp
+			cacheEntryIdxToKick = cacheEntryIdx;
+			cacheEntryIdxToKickMinTimestamp = m_cacheEntries[cacheEntryIdx].m_lastUsedTimestamp;
 		}
-
-		candidate->m_nodeUuid = uuid;
-		m_uuidToCacheEntry.pushBack(uuid, candidate);
 	}
 
-	ANKI_ASSERT(candidate);
-	ANKI_ASSERT(candidate->m_nodeUuid == uuid);
-	candidate->m_timestamp = m_r->getFrameCount();
+	Bool failed = false;
+	if(emptyCacheEntryIdx != MAX_U32)
+	{
+		cacheEntryIdxAllocated = emptyCacheEntryIdx;
+	}
+	else if(cacheEntryIdxToKick != MAX_U32)
+	{
+		cacheEntryIdxAllocated = cacheEntryIdxToKick;
+	}
+	else
+	{
+		// We have a problem
+		failed = true;
+		ANKI_R_LOGW("There is not enough space in the indirect lighting atlas for more probes. "
+					"Increase the r.indirect.maxSimultaneousProbeCount or decrease the scene's probes");
+	}
 
-	entry = candidate - &m_cacheEntries[0];
+	return failed;
 }
 
 } // end namespace anki

+ 44 - 56
src/anki/renderer/Indirect.h

@@ -13,11 +13,6 @@
 namespace anki
 {
 
-// Forward
-struct IrShaderReflectionProbe;
-class IrRunContext;
-class IrTaskContext;
-
 /// @addtogroup renderer
 /// @{
 
@@ -37,7 +32,7 @@ anki_internal:
 
 	U getReflectionTextureMipmapCount() const
 	{
-		return m_is.m_lightRtMipCount;
+		return m_lightShading.m_mipCount;
 	}
 
 	TexturePtr getIrradianceTexture() const
@@ -47,7 +42,7 @@ anki_internal:
 
 	TexturePtr getReflectionTexture() const
 	{
-		return m_is.m_lightRt;
+		return m_lightShading.m_cubeArr;
 	}
 
 	TexturePtr getIntegrationLut() const
@@ -61,93 +56,86 @@ anki_internal:
 	}
 
 private:
-	class FaceInfo
-	{
-	public:
-		// MS
-		Array<TexturePtr, GBUFFER_COLOR_ATTACHMENT_COUNT> m_gbufferColorRts;
-		TexturePtr m_gbufferDepthRt;
-		FramebufferPtr m_gbufferFb;
-
-		// IS
-		FramebufferPtr m_lightShadingFb;
-
-		// Irradiance
-		FramebufferPtr m_irradianceFb;
+	struct LightPassVertexUniforms;
+	struct LightPassPointLightUniforms;
+	struct LightPassSpotLightUniforms;
 
-		Bool created() const
-		{
-			return m_lightShadingFb.isCreated();
-		}
-	};
-
-	class CacheEntry : public IntrusiveHashMapEnabled<CacheEntry>
+	class
 	{
 	public:
-		U64 m_nodeUuid;
-		Timestamp m_timestamp = 0; ///< When last accessed.
-
-		Array<FaceInfo, 6> m_faces;
-	};
-
-	static const U IRRADIANCE_TEX_SIZE = 16;
-	static const U MAX_PROBE_RENDERS_PER_FRAME = 1;
-
-	U16 m_cubemapArrSize = 0;
-	U16 m_fbSize = 0;
+		U32 m_tileSize = 0;
+		Array<TexturePtr, GBUFFER_COLOR_ATTACHMENT_COUNT> m_colorRts;
+		TexturePtr m_depthRt;
+		FramebufferPtr m_fb;
+	} m_gbuffer; ///< G-buffer pass.
 
-	// IS
 	class
 	{
 	public:
-		TexturePtr m_lightRt; ///< Cube array.
-		U32 m_lightRtMipCount = 0;
+		U32 m_tileSize = 0;
+		U32 m_mipCount = 0;
+		TexturePtr m_cubeArr;
 
 		ShaderProgramResourcePtr m_lightProg;
 		ShaderProgramPtr m_plightGrProg;
 		ShaderProgramPtr m_slightGrProg;
 
+		/// @name Vertex & index buffer of light volumes.
+		/// @{
 		BufferPtr m_plightPositions;
 		BufferPtr m_plightIndices;
 		U32 m_plightIdxCount;
 		BufferPtr m_slightPositions;
 		BufferPtr m_slightIndices;
 		U32 m_slightIdxCount;
-	} m_is;
+		/// @}
+	} m_lightShading; ///< Light shading.
 
-	// Irradiance
 	class
 	{
 	public:
+		U32 m_tileSize = 8;
+		U32 m_envMapReadSize = 16; ///< This controls the iterations that will be used to calculate the irradiance.
 		TexturePtr m_cubeArr;
-		U32 m_cubeArrMipCount = 0;
 
 		ShaderProgramResourcePtr m_prog;
 		ShaderProgramPtr m_grProg;
-	} m_irradiance;
+	} m_irradiance; ///< Irradiance.
+
+	class CacheEntry
+	{
+	public:
+		U64 m_probeUuid;
+		Timestamp m_lastUsedTimestamp = 0; ///< When it was rendered.
+
+		Array<FramebufferPtr, 6> m_lightShadingFbs;
+		Array<FramebufferPtr, 6> m_irradianceFbs;
+	};
 
 	DynamicArray<CacheEntry> m_cacheEntries;
-	IntrusiveHashMap<U64, CacheEntry> m_uuidToCacheEntry;
+	HashMap<U64, U32> m_probeUuidToCacheEntryIdx;
 
 	// Other
 	TextureResourcePtr m_integrationLut;
 	SamplerPtr m_integrationLutSampler;
 
-	// Init
 	ANKI_USE_RESULT Error initInternal(const ConfigSet& cfg);
-	ANKI_USE_RESULT Error initIs();
-	ANKI_USE_RESULT Error initIrradiance();
-	void initFaceInfo(U cacheEntryIdx, U faceIdx);
+	ANKI_USE_RESULT Error initGBuffer(const ConfigSet& cfg);
+	ANKI_USE_RESULT Error initLightShading(const ConfigSet& cfg);
+	ANKI_USE_RESULT Error initIrradiance(const ConfigSet& cfg);
 	ANKI_USE_RESULT Error loadMesh(CString fname, BufferPtr& vert, BufferPtr& idx, U32& idxCount);
 
-	void runMs(RenderingContext& rctx, const RenderQueue& rqueue, U layer, U faceIdx);
-	void runIs(RenderingContext& rctx, const RenderQueue& rqueue, U layer, U faceIdx);
-	void computeIrradiance(RenderingContext& rctx, U layer, U faceIdx);
+	/// Lazily init the cache entry
+	void initCacheEntry(U32 cacheEntryIdx);
 
-	void renderReflection(const ReflectionProbeQueueElement& probeEl, RenderingContext& ctx, U cubemapIdx);
+	void prepareProbes(
+		RenderingContext& ctx, ReflectionProbeQueueElement*& probeToUpdate, U32& probeToUpdateCacheEntryIdx);
+	void runGBuffer(RenderingContext& rctx, const ReflectionProbeQueueElement& probe);
+	void runLightShading(RenderingContext& rctx, const ReflectionProbeQueueElement& probe, CacheEntry& cacheEntry);
+	void runIrradiance(RenderingContext& rctx, U32 cacheEntryIdx);
 
-	/// Find a cache entry to store the reflection.
-	void findCacheEntry(U64 nodeUuid, U& entry, Bool& render);
+	/// Find or allocate a new cache entry.
+	Bool findBestCacheEntry(U64 probeUuid, U32& cacheEntryIdx, Bool& cacheEntryFound);
 };
 /// @}
 

+ 1 - 1
src/anki/renderer/RenderQueue.h

@@ -110,7 +110,7 @@ class ReflectionProbeQueueElement final
 public:
 	ReflectionProbeQueueElementFeedbackCallback m_feedbackCallback;
 	RenderQueueDrawCallback m_drawCallback;
-	void* m_userData; // TODO Shouldn't be const void*?
+	void* m_userData;
 	U64 m_uuid;
 	Vec3 m_worldPosition;
 	F32 m_radius;

+ 5 - 5
src/anki/renderer/ShadowMapping.cpp

@@ -620,7 +620,7 @@ Bool ShadowMapping::allocateTilesAndScratchTiles(U64 lightUuid,
 			Tile& tile = m_tiles[tileIndices[i]];
 			tile.m_face = faceIndices[i];
 			tile.m_lightUuid = lightUuid;
-			tile.m_timestamp = m_r->getGlobalTimestamp();
+			tile.m_lastUsedTimestamp = m_r->getGlobalTimestamp();
 
 			// Update the cache
 			if(!inTheCache[i])
@@ -637,7 +637,7 @@ Bool ShadowMapping::allocateTilesAndScratchTiles(U64 lightUuid,
 
 Bool ShadowMapping::shouldRenderTile(U64 lightTimestamp, U64 lightUuid, U32 face, const Tile& tileIdx)
 {
-	if(tileIdx.m_face == face && tileIdx.m_lightUuid == lightUuid && tileIdx.m_timestamp >= lightTimestamp)
+	if(tileIdx.m_face == face && tileIdx.m_lightUuid == lightUuid && tileIdx.m_lastUsedTimestamp >= lightTimestamp)
 	{
 		return false;
 	}
@@ -688,12 +688,12 @@ Bool ShadowMapping::allocateTile(U64 lightTimestamp, U64 lightUuid, U32 face, U3
 			emptyTile = tileIdx;
 			break;
 		}
-		else if(m_tiles[tileIdx].m_timestamp != m_r->getGlobalTimestamp()
-			&& m_tiles[tileIdx].m_timestamp < tileToKickMinTimestamp)
+		else if(m_tiles[tileIdx].m_lastUsedTimestamp != m_r->getGlobalTimestamp()
+			&& m_tiles[tileIdx].m_lastUsedTimestamp < tileToKickMinTimestamp)
 		{
 			// Found some with low timestamp
 			tileToKick = tileIdx;
-			tileToKickMinTimestamp = m_tiles[tileIdx].m_timestamp;
+			tileToKickMinTimestamp = m_tiles[tileIdx].m_lastUsedTimestamp;
 		}
 	}
 

+ 1 - 1
src/anki/renderer/ShadowMapping.h

@@ -49,7 +49,7 @@ private:
 	class Tile
 	{
 	public:
-		U64 m_timestamp = 0;
+		U64 m_lastUsedTimestamp = 0;
 		U64 m_lightUuid = 0;
 		U8 m_face = 0;
 		Bool8 m_pinned = false; ///< If true we cannot allocate from it.

+ 1 - 1
src/anki/resource/Material.h

@@ -179,7 +179,7 @@ private:
 /// <material
 ///		[shadow="0 | 1"]
 ///		[forwardShading="0 | 1"]
-///		shaderProgram="path"/>
+///		<shaderProgram="path"/>
 ///
 ///		[<mutators>
 ///			<mutator name="str" value="value"/>

+ 4 - 1
src/anki/scene/SceneNode.h

@@ -95,7 +95,10 @@ public:
 	{
 		m_sectorVisitedBitset.unsetAll();
 		m_maxComponentTimestamp = maxComponentTimestamp;
-		ANKI_ASSERT(maxComponentTimestamp > 0);
+		if(m_componentCount > 0)
+		{
+			ANKI_ASSERT(maxComponentTimestamp > 0);
+		}
 		return frameUpdate(prevUpdateTime, crntTime);
 	}