Browse Source

Merge pull request #48 from godlikepanos/gi

New (Difuse) Global Illumination
Panagiotis Christopoulos Charitos 6 years ago
parent
commit
4d578de519
90 changed files with 4161 additions and 1217 deletions
  1. BIN
      samples/physics_playground/assets/physics_playground.blend
  2. 5 2
      samples/physics_playground/assets/scene.lua
  3. 11 0
      samples/sponza/assets/scene.lua
  4. BIN
      samples/sponza/assets/sponza_379.ankimesh
  5. BIN
      samples/sponza/assets/sponza_382.ankimesh
  6. 38 25
      shaders/ApplyIrradianceToReflection.glslp
  7. 13 1
      shaders/ClearTextureCompute.glslp
  8. 32 15
      shaders/ClusteredShadingCommon.glsl
  9. 1 0
      shaders/Common.glsl
  10. 36 3
      shaders/Functions.glsl
  11. 1 1
      shaders/GBufferCommonFrag.glsl
  12. 135 0
      shaders/GlobalIlluminationClipmapPopulation.glslp
  13. 0 64
      shaders/Irradiance.glslp
  14. 218 0
      shaders/IrradianceDice.glslp
  15. 57 0
      shaders/LightFunctions.glsl
  16. 115 50
      shaders/LightShading.glslp
  17. 1 1
      shaders/Pack.glsl
  18. 10 4
      shaders/TraditionalDeferredShading.glslp
  19. 67 39
      shaders/VolumetricLightingAccumulation.glslp
  20. 29 3
      shaders/glsl_cpp_common/ClusteredShading.h
  21. 13 14
      shaders/glsl_cpp_common/TraditionalDeferredShading.h
  22. 7 1
      src/anki/Config.h.cmake
  23. 2 1
      src/anki/Renderer.h
  24. 2 0
      src/anki/Scene.h
  25. 10 0
      src/anki/core/Config.cpp
  26. 1 1
      src/anki/gr/Common.h
  27. 129 18
      src/anki/gr/RenderGraph.cpp
  28. 42 113
      src/anki/gr/RenderGraph.h
  29. 127 0
      src/anki/gr/RenderGraph.inl.h
  30. 28 21
      src/anki/gr/ShaderCompiler.cpp
  31. 16 6
      src/anki/gr/ShaderCompiler.h
  32. 12 4
      src/anki/gr/vulkan/DescriptorSet.cpp
  33. 1 7
      src/anki/gr/vulkan/GrManagerImpl.cpp
  34. 18 0
      src/anki/gr/vulkan/TextureImpl.cpp
  35. 2 10
      src/anki/gr/vulkan/TextureImpl.h
  36. 13 8
      src/anki/math/Vec.h
  37. 64 8
      src/anki/renderer/ClusterBin.cpp
  38. 3 2
      src/anki/renderer/ClusterBin.h
  39. 1 1
      src/anki/renderer/Common.cpp
  40. 65 2
      src/anki/renderer/Common.h
  41. 22 5
      src/anki/renderer/Dbg.cpp
  42. 8 10
      src/anki/renderer/Drawer.cpp
  43. 2 1
      src/anki/renderer/Drawer.h
  44. 1 1
      src/anki/renderer/ForwardShading.cpp
  45. 1 1
      src/anki/renderer/GBuffer.cpp
  46. 2 2
      src/anki/renderer/GBufferPost.cpp
  47. 741 0
      src/anki/renderer/GlobalIllumination.cpp
  48. 120 0
      src/anki/renderer/GlobalIllumination.h
  49. 22 18
      src/anki/renderer/LightShading.cpp
  50. 6 11
      src/anki/renderer/MainRenderer.cpp
  51. 0 3
      src/anki/renderer/MainRenderer.h
  52. 214 368
      src/anki/renderer/ProbeReflections.cpp
  53. 15 43
      src/anki/renderer/ProbeReflections.h
  54. 49 6
      src/anki/renderer/RenderQueue.h
  55. 24 8
      src/anki/renderer/Renderer.cpp
  56. 18 6
      src/anki/renderer/Renderer.h
  57. 2 1
      src/anki/renderer/ShadowMapping.cpp
  58. 54 65
      src/anki/renderer/TraditionalDeferredShading.cpp
  59. 25 15
      src/anki/renderer/TraditionalDeferredShading.h
  60. 8 9
      src/anki/renderer/VolumetricLightingAccumulation.cpp
  61. 0 1
      src/anki/resource/AnimationResource.cpp
  62. 1 0
      src/anki/resource/ResourceManager.cpp
  63. 6 0
      src/anki/resource/ResourceManager.h
  64. 2 1
      src/anki/resource/ShaderProgramResource.cpp
  65. 5 0
      src/anki/resource/ShaderProgramResource.h
  66. 2 1
      src/anki/scene/CameraNode.cpp
  67. 54 0
      src/anki/scene/DebugDrawer.cpp
  68. 8 0
      src/anki/scene/DebugDrawer.h
  69. 202 0
      src/anki/scene/GlobalIlluminationProbeNode.cpp
  70. 45 0
      src/anki/scene/GlobalIlluminationProbeNode.h
  71. 6 50
      src/anki/scene/ModelNode.cpp
  72. 2 2
      src/anki/scene/ReflectionProbeNode.cpp
  73. 0 2
      src/anki/scene/ReflectionProbeNode.h
  74. 42 0
      src/anki/scene/Visibility.cpp
  75. 1 0
      src/anki/scene/VisibilityInternal.h
  76. 10 12
      src/anki/scene/components/FrustumComponent.cpp
  77. 16 19
      src/anki/scene/components/FrustumComponent.h
  78. 88 0
      src/anki/scene/components/GlobalIlluminationProbeComponent.cpp
  79. 152 0
      src/anki/scene/components/GlobalIlluminationProbeComponent.h
  80. 1 0
      src/anki/scene/components/SceneComponent.h
  81. 571 0
      src/anki/script/Scene.cpp
  82. 57 0
      src/anki/script/Scene.xml
  83. 1 0
      src/anki/util/Assert.h
  84. 119 106
      src/anki/util/Functions.h
  85. 3 1
      src/anki/util/Memory.cpp
  86. 1 1
      thirdparty
  87. 33 21
      tools/blender_anki_additions.patch
  88. 1 1
      tools/blender_anki_additions_readme.txt
  89. 62 1
      tools/scene/Exporter.cpp
  90. 11 0
      tools/scene/Exporter.h

BIN
samples/physics_playground/assets/physics_playground.blend


+ 5 - 2
samples/physics_playground/assets/scene.lua

@@ -14,9 +14,12 @@ trf:setRotation(rot)
 trf:setScale(1)
 node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
 
-node = scene:newReflectionProbeNode("reflprobe1", Vec4.new(-2.65221, -3.25716, -11.7323, 0), Vec4.new(2.65221, 3.25716, 11.7324, 0))
+node = scene:newGlobalIlluminationProbeNode("giprobe0")
+comp = node:getSceneNodeBase():getGlobalIlluminationProbeComponent()
+comp:setBoundingBox(Vec4.new(-128.498, -128.498, -128.498, 0), Vec4.new(128.498, 128.498, 128.498, 0))
+comp:setCellSize(32)
 trf = Transform.new()
-trf:setOrigin(Vec4.new(39.81, 2.89296, 3.00294, 0))
+trf:setOrigin(Vec4.new(-0.0683718, 9.1926, -0.126609, 0))
 rot = Mat3x4.new()
 rot:setAll(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0)
 trf:setRotation(rot)

+ 11 - 0
samples/sponza/assets/scene.lua

@@ -140,6 +140,17 @@ trf:setRotation(rot)
 trf:setScale(1)
 node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
 
+node = scene:newGlobalIlluminationProbeNode("giprobe0")
+comp = node:getSceneNodeBase():getGlobalIlluminationProbeComponent()
+comp:setBoundingBox(Vec4.new(-24.7947, -12.9703, -11.62, 0), Vec4.new(24.7947, 12.9703, 11.62, 0))
+trf = Transform.new()
+trf:setOrigin(Vec4.new(-1.0849, 12.3116, -0.533418, 0))
+rot = Mat3x4.new()
+rot:setAll(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0)
+trf:setRotation(rot)
+trf:setScale(1)
+node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
+
 node = scene:newModelNode("sponza_277leaf-materialnone0", "assets/sponza_277leaf-material.ankimdl")
 trf = Transform.new()
 trf:setOrigin(Vec4.new(-10.8267, 3.02038, -4.74626, 0))

BIN
samples/sponza/assets/sponza_379.ankimesh


BIN
samples/sponza/assets/sponza_382.ankimesh


+ 38 - 25
shaders/ApplyIrradianceToReflection.glslp

@@ -3,50 +3,63 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#pragma anki start vert
-#include <shaders/QuadVert.glsl>
-#pragma anki end
-
-#pragma anki start frag
+#pragma anki start comp
 #include <shaders/Pack.glsl>
+#include <shaders/LightFunctions.glsl>
 
-layout(location = 0) in Vec2 in_uv;
-
-layout(location = 0) out Vec3 out_color;
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 6) in;
 
 layout(set = 0, binding = 0) uniform sampler u_nearestAnyClampSampler;
-layout(set = 0, binding = 1) uniform sampler u_linearAnyClampSampler;
-
-layout(set = 0, binding = 2) uniform texture2D u_gbufferTex0;
-layout(set = 0, binding = 3) uniform texture2D u_gbufferTex1;
-layout(set = 0, binding = 4) uniform texture2D u_gbufferTex2;
+layout(set = 0, binding = 1) uniform texture2D u_gbufferTex[3u];
 
-layout(set = 0, binding = 5) uniform textureCubeArray u_irradianceTex;
-
-layout(push_constant, std430) uniform pc_
+layout(set = 0, binding = 2) buffer readonly ssbo_
 {
-	Vec3 u_padding;
-	F32 u_faceIdx;
+	Vec4 u_irradianceDice[6u];
 };
 
+layout(set = 0, binding = 3) uniform imageCube u_cubeTex;
+
 void main()
 {
+	const Vec2 cubeSize = Vec2(imageSize(u_cubeTex));
+	if(gl_GlobalInvocationID.x >= cubeSize.x || gl_GlobalInvocationID.y >= cubeSize.y)
+	{
+		return;
+	}
+
+	const U32 faceIdx = gl_LocalInvocationID.z;
+
 	// Compute the UVs to read the gbuffer from
-	Vec2 sampleUv = in_uv;
+	Vec2 sampleUv = (Vec2(gl_GlobalInvocationID.xy) + 0.5) / Vec2(cubeSize);
 	sampleUv.x *= (1.0 / 6.0);
-	sampleUv.x += (1.0 / 6.0) * u_faceIdx;
+	sampleUv.x += (1.0 / 6.0) * F32(faceIdx);
 
 	// Read the gbuffer
 	GbufferInfo gbuffer;
-	readGBuffer(u_gbufferTex0, u_gbufferTex1, u_gbufferTex2, u_nearestAnyClampSampler, sampleUv, 0.0, gbuffer);
+	readGBuffer(u_gbufferTex[0u], u_gbufferTex[1u], u_gbufferTex[2u], u_nearestAnyClampSampler, sampleUv, 0.0, gbuffer);
 
-	// Read the irradiance. Use the layer 0 because C++ will set the appropriate texture view
-	const Vec3 irradiance = textureLod(u_irradianceTex, u_linearAnyClampSampler, Vec4(gbuffer.m_normal, 0.0), 0.0).rgb;
+	// Sample
+	const Vec3 irradiance = sampleAmbientDice(u_irradianceDice[0u].xyz,
+		u_irradianceDice[1u].xyz,
+		u_irradianceDice[2u].xyz,
+		u_irradianceDice[3u].xyz,
+		u_irradianceDice[4u].xyz,
+		u_irradianceDice[5u].xyz,
+		gbuffer.m_normal);
 
 	// Compute the indirect term
 	const Vec3 indirect = gbuffer.m_diffuse * irradiance;
 
-	// Write it
-	out_color = indirect;
+	// Read the prev color and apply indirect
+	const IVec3 coords = IVec3(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y, faceIdx);
+	const Vec3 prevColor = imageLoad(u_cubeTex, coords).xyz;
+	const Vec3 prevColorWithIndirectDiffuse = prevColor + gbuffer.m_diffuse * indirect;
+
+	// Barrier just in case
+	memoryBarrierImage();
+	barrier();
+
+	// Write it back
+	imageStore(u_cubeTex, coords, Vec4(prevColorWithIndirectDiffuse, 0.0));
 }
 #pragma anki end

+ 13 - 1
shaders/ClearTextureCompute.glslp

@@ -10,7 +10,7 @@
 #pragma anki start comp
 #include <shaders/Common.glsl>
 
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+layout(local_size_x = 8, local_size_y = 8, local_size_z = (IS_2D == 1) ? 1 : 8) in;
 
 layout(push_constant) uniform pc_
 {
@@ -26,8 +26,20 @@ layout(set = 0, binding = 0) uniform writeonly image3D u_img;
 void main()
 {
 #if IS_2D
+	const UVec2 size = UVec2(imageSize(u_img));
+	if(gl_GlobalInvocationID.x >= size.x || gl_GlobalInvocationID.y >= size.y)
+	{
+		return;
+	}
+
 	imageStore(u_img, IVec2(gl_GlobalInvocationID.xy), u_clearColor);
 #else
+	const UVec3 size = UVec3(imageSize(u_img));
+	if(gl_GlobalInvocationID.x >= size.x || gl_GlobalInvocationID.y >= size.y || gl_GlobalInvocationID.z >= size.z)
+	{
+		return;
+	}
+
 	imageStore(u_img, IVec3(gl_GlobalInvocationID), u_clearColor);
 #endif
 }

+ 32 - 15
shaders/ClusteredShadingCommon.glsl

@@ -58,17 +58,16 @@ layout(set = LIGHT_SET, binding = LIGHT_LIGHTS_BINDING + 2) uniform highp textur
 #endif
 
 //
-// Indirect uniforms (4)
+// Indirect uniforms (3)
 //
-#if defined(LIGHT_INDIRECT_BINDING)
-layout(std140, row_major, set = LIGHT_SET, binding = LIGHT_INDIRECT_BINDING) uniform u3_
+#if defined(LIGHT_INDIRECT_SPECULAR_BINDING)
+layout(std140, row_major, set = LIGHT_SET, binding = LIGHT_INDIRECT_SPECULAR_BINDING) uniform u3_
 {
 	ReflectionProbe u_reflectionProbes[UBO_MAX_SIZE / SIZEOF_REFLECTION_PROBE];
 };
 
-layout(set = LIGHT_SET, binding = LIGHT_INDIRECT_BINDING + 1) uniform textureCubeArray u_reflectionsTex;
-layout(set = LIGHT_SET, binding = LIGHT_INDIRECT_BINDING + 2) uniform textureCubeArray u_irradianceTex;
-layout(set = LIGHT_SET, binding = LIGHT_INDIRECT_BINDING + 3) uniform texture2D u_integrationLut;
+layout(set = LIGHT_SET, binding = LIGHT_INDIRECT_SPECULAR_BINDING + 1) uniform textureCubeArray u_reflectionsTex;
+layout(set = LIGHT_SET, binding = LIGHT_INDIRECT_SPECULAR_BINDING + 2) uniform texture2D u_integrationLut;
 #endif
 
 //
@@ -85,7 +84,7 @@ layout(set = LIGHT_SET, binding = LIGHT_DECALS_BINDING + 2) uniform texture2D u_
 #endif
 
 //
-// Fog density uniforms
+// Fog density uniforms (1)
 //
 #if defined(LIGHT_FOG_DENSITY_VOLUMES_BINDING)
 layout(std140, row_major, set = LIGHT_SET, binding = LIGHT_FOG_DENSITY_VOLUMES_BINDING) uniform u5_
@@ -94,6 +93,19 @@ layout(std140, row_major, set = LIGHT_SET, binding = LIGHT_FOG_DENSITY_VOLUMES_B
 };
 #endif
 
+//
+// GI (2)
+//
+#if defined(LIGHT_GLOBAL_ILLUMINATION_BINDING)
+layout(set = LIGHT_SET, binding = LIGHT_GLOBAL_ILLUMINATION_BINDING) uniform texture3D
+	u_globalIlluminationTextures[MAX_VISIBLE_GLOBAL_ILLUMINATION_PROBES];
+
+layout(set = LIGHT_SET, binding = LIGHT_GLOBAL_ILLUMINATION_BINDING + 1) uniform ugi_
+{
+	GlobalIlluminationProbe u_giProbes[MAX_VISIBLE_GLOBAL_ILLUMINATION_PROBES];
+};
+#endif
+
 //
 // Cluster uniforms
 //
@@ -110,36 +122,41 @@ layout(set = LIGHT_SET, binding = LIGHT_CLUSTERS_BINDING + 1, std430) readonly b
 #endif
 
 // Debugging function
-Vec3 lightHeatmap(U32 firstIndex, U32 maxLights, Bool decals, Bool plights, Bool slights, Bool probes, Bool fogVolumes)
+Vec3 lightHeatmap(U32 firstIndex, U32 maxObjects, U32 typeMask)
 {
 	U32 count = 0;
 	U32 idx;
 
 	while((idx = u_lightIndices[firstIndex++]) != MAX_U32)
 	{
-		count += (plights) ? 1u : 0u;
+		count += ((typeMask & (1u << 0u)) != 0u) ? 1u : 0u;
+	}
+
+	while((idx = u_lightIndices[firstIndex++]) != MAX_U32)
+	{
+		count += ((typeMask & (1u << 1u)) != 0u) ? 1u : 0u;
 	}
 
 	while((idx = u_lightIndices[firstIndex++]) != MAX_U32)
 	{
-		count += (slights) ? 1u : 0u;
+		count += ((typeMask & (1u << 2u)) != 0u) ? 1u : 0u;
 	}
 
 	while((idx = u_lightIndices[firstIndex++]) != MAX_U32)
 	{
-		count += (probes) ? 1u : 0u;
+		count += ((typeMask & (1u << 3u)) != 0u) ? 1u : 0u;
 	}
 
 	while((idx = u_lightIndices[firstIndex++]) != MAX_U32)
 	{
-		count += (decals) ? 1u : 0u;
+		count += ((typeMask & (1u << 4u)) != 0u) ? 1u : 0u;
 	}
 
 	while((idx = u_lightIndices[firstIndex++]) != MAX_U32)
 	{
-		count += (fogVolumes) ? 1u : 0u;
+		count += ((typeMask & (1u << 5u)) != 0u) ? 1u : 0u;
 	}
 
-	const F32 factor = min(1.0, F32(count) / F32(maxLights));
+	const F32 factor = min(1.0, F32(count) / F32(maxObjects));
 	return heatmap(factor);
-}
+}

+ 1 - 0
shaders/Common.glsl

@@ -41,6 +41,7 @@ const U32 MAX_U32 = 0xFFFFFFFFu;
 
 const F32 PI = 3.14159265358979323846;
 const U32 UBO_MAX_SIZE = 16384u;
+#define MAX_SHARED_MEMORY (32u * 1024u)
 
 // Macros
 #define UV_TO_NDC(x_) ((x_)*2.0 - 1.0)

+ 36 - 3
shaders/Functions.glsl

@@ -419,7 +419,7 @@ Vec3 readErosion(texture2D tex, sampler sampl, const Vec2 uv)
 }
 
 // 5 color heatmap from a factor.
-Vec3 heatmap(F32 factor)
+Vec3 heatmap(const F32 factor)
 {
 	F32 intPart;
 	const F32 fractional = modf(factor * 4.0, intPart);
@@ -442,12 +442,39 @@ Vec3 heatmap(F32 factor)
 	}
 }
 
-Bool incorrectColor(Vec3 c)
+// Return a color per cubemap face. The +X is red, -X dark red, +Y green, -Y dark green, +Z blue, -Z dark blue
+Vec3 colorPerCubeFace(const U32 dir)
+{
+	Vec3 color;
+	switch(dir)
+	{
+	case 0:
+		color = Vec3(1.0, 0.0, 0.0);
+		break;
+	case 1:
+		color = Vec3(0.25, 0.0, 0.0);
+		break;
+	case 2:
+		color = Vec3(0.0, 1.0, 0.0);
+		break;
+	case 3:
+		color = Vec3(0.0, 0.25, 0.0);
+		break;
+	case 4:
+		color = Vec3(0.0, 0.0, 1.0);
+		break;
+	default:
+		color = Vec3(0.0, 0.0, 0.25);
+	}
+	return color;
+}
+
+Bool incorrectColor(const Vec3 c)
 {
 	return isnan(c.x) || isnan(c.y) || isnan(c.z) || isinf(c.x) || isinf(c.y) || isinf(c.z);
 }
 
-F32 areaElement(F32 x, F32 y)
+F32 areaElement(const F32 x, const F32 y)
 {
 	return atan(x * y, sqrt(x * x + y * y + 1.0));
 }
@@ -475,6 +502,12 @@ F32 rayAabbIntersectionInside(Vec3 rayOrigin, Vec3 rayDir, Vec3 aabbMin, Vec3 aa
 	return distToIntersect;
 }
 
+// Return true if to AABBs overlap
+Bool aabbsOverlap(const Vec3 aMin, const Vec3 aMax, const Vec3 bMin, const Vec3 bMax)
+{
+	return all(lessThan(aMin, bMax)) && all(lessThan(bMin, aMax));
+}
+
 // A convenience macro to skip out of bounds invocations on post-process compute shaders.
 #define SKIP_OUT_OF_BOUNDS_INVOCATIONS() \
 	if((FB_SIZE.x % WORKGROUP_SIZE.x) != 0u || (FB_SIZE.y % WORKGROUP_SIZE.y) != 0u) \

+ 1 - 1
shaders/GBufferCommonFrag.glsl

@@ -95,7 +95,7 @@ Vec2 computeTextureCoordParallax(texture2D heightMap, sampler sampl, Vec2 uv, F3
 
 	const F32 factor0 = -dot(E, normTangentSpace);
 	const F32 factor1 = in_distFromTheCamera / -MAX_EFFECTIVE_DISTANCE;
-	const F32 factor = (1.0 - factor0) * (1.0 - factor1);
+	const F32 factor = saturate((1.0 - factor0) * (1.0 - factor1));
 	const F32 sampleCountf = mix(F32(MIN_SAMPLES), F32(MAX_SAMPLES), factor);
 
 	const F32 stepSize = 1.0 / sampleCountf;

+ 135 - 0
shaders/GlobalIlluminationClipmapPopulation.glslp

@@ -0,0 +1,135 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+// Populates a clipmap with the irradiance values of probes.
+
+#pragma anki input const UVec3 WORKGROUP_SIZE
+#pragma anki input const U32 PROBE_COUNT
+#pragma anki mutator HAS_PROBES 0 1
+
+#pragma anki start comp
+
+#include <shaders/glsl_cpp_common/ClusteredShading.h>
+#include <shaders/Functions.glsl>
+
+#define DEBUG_MODE 0 // 0: disabled, 1: draw different shade per dir per level, 2 draw differend shade per cell
+
+layout(local_size_x = WORKGROUP_SIZE.x, local_size_y = WORKGROUP_SIZE.y, local_size_z = WORKGROUP_SIZE.z) in;
+
+struct Clipmap
+{
+	Vec3 m_aabbMin;
+	F32 m_cellSize;
+	Vec3 m_aabbMax;
+	F32 m_padding0;
+	UVec3 m_cellCounts;
+	U32 m_padding2;
+};
+
+layout(set = 0, binding = 0, std140) uniform ubo_
+{
+	Clipmap u_clipmaps[GLOBAL_ILLUMINATION_CLIPMAP_LEVEL_COUNT];
+	Vec3 u_cameraPos;
+	U32 u_padding;
+};
+
+layout(set = 0, binding = 1) uniform writeonly image3D u_clipmapImages[6u * GLOBAL_ILLUMINATION_CLIPMAP_LEVEL_COUNT];
+
+#if HAS_PROBES
+layout(set = 0, binding = 2) buffer ssbo_
+{
+	GlobalIlluminationProbe u_probes[];
+};
+
+layout(set = 0, binding = 3) uniform sampler u_linearAnyClampSampler;
+layout(set = 0, binding = 4) uniform texture3D u_probeIrradianceTextures[6u * PROBE_COUNT];
+#endif
+
+Bool aabbsOverlap(const Clipmap clipmap, const GlobalIlluminationProbe probe)
+{
+	return aabbsOverlap(clipmap.m_aabbMin, clipmap.m_aabbMax, probe.m_aabbMin, probe.m_aabbMax);
+}
+
+// Compute the texture coordinates inside a probe
+Vec3 computeUvwCoordsInsideProbe(const GlobalIlluminationProbe probe, const Vec3 positionInsideTheProbe)
+{
+	const Vec3 probeSize = probe.m_aabbMax - probe.m_aabbMin;
+	const Vec3 uvw = (positionInsideTheProbe - probe.m_aabbMin) / probeSize;
+	return uvw;
+}
+
+void main()
+{
+	// Populate all clipmaps
+	ANKI_UNROLL for(U32 clipmapIdx = 0u; clipmapIdx < GLOBAL_ILLUMINATION_CLIPMAP_LEVEL_COUNT; ++clipmapIdx)
+	{
+		const Clipmap clipmap = u_clipmaps[clipmapIdx];
+
+		// Check bounds
+		if(any(greaterThanEqual(gl_GlobalInvocationID, clipmap.m_cellCounts)))
+		{
+			continue;
+		}
+
+#if HAS_PROBES
+		// For all probes
+		F32 weight = EPSILON;
+		Vec3 accumulatedIrradiance[6u] = Vec3[](Vec3(0.0), Vec3(0.0), Vec3(0.0), Vec3(0.0), Vec3(0.0), Vec3(0.0));
+		ANKI_UNROLL for(U32 probeIdx = 0u; probeIdx < PROBE_COUNT; ++probeIdx)
+		{
+			const GlobalIlluminationProbe probe = u_probes[probeIdx];
+
+			ANKI_FLATTEN if(aabbsOverlap(clipmap, probe))
+			{
+				// Compute the world position of the cell in the clipmap
+				Vec3 cellPosition = Vec3(gl_GlobalInvocationID) * clipmap.m_cellSize;
+				cellPosition += clipmap.m_cellSize / 2.0; // Move to the center of the cell
+				cellPosition += clipmap.m_aabbMin;
+
+				// Compute the UVW coords
+				const Vec3 texCoordsInProbe = computeUvwCoordsInsideProbe(probe, cellPosition);
+
+				// Read the texture
+				ANKI_UNROLL for(U32 dirIdx = 0u; dirIdx < 6u; ++dirIdx)
+				{
+					// Read the color from the probe
+					const U32 inputTexIdx = probeIdx * 6u + dirIdx;
+					const Vec3 inColor = textureLod(
+						u_probeIrradianceTextures[inputTexIdx], u_linearAnyClampSampler, texCoordsInProbe, 0.0)
+											 .rgb;
+
+					accumulatedIrradiance[dirIdx] += inColor;
+				}
+
+				weight += 1.0;
+			}
+		}
+#endif
+
+		// Write the result
+		ANKI_UNROLL for(U32 dirIdx = 0u; dirIdx < 6u; ++dirIdx)
+		{
+			const U32 clipmapImageIdx = clipmapIdx * 6u + dirIdx;
+
+#if DEBUG_MODE == 1
+			const Vec3 dirColor = colorPerCubeFace(dirIdx);
+			const F32 clipmapFactor = F32(GLOBAL_ILLUMINATION_CLIPMAP_LEVEL_COUNT - clipmapIdx)
+									  / F32(GLOBAL_ILLUMINATION_CLIPMAP_LEVEL_COUNT);
+			const Vec3 storedColor = dirColor * clipmapFactor;
+#elif DEBUG_MODE == 2
+			const F32 factor =
+				F32(gl_LocalInvocationIndex) / F32(WORKGROUP_SIZE.x * WORKGROUP_SIZE.y * WORKGROUP_SIZE.z);
+			const Vec3 storedColor = heatmap(factor);
+#elif HAS_PROBES
+			const Vec3 storedColor = accumulatedIrradiance[dirIdx] / weight;
+#else
+			const Vec3 storedColor = Vec3(0.0);
+#endif
+			imageStore(u_clipmapImages[clipmapImageIdx], IVec3(gl_GlobalInvocationID), Vec4(storedColor, 0.0));
+		}
+	}
+}
+
+#pragma anki end

+ 0 - 64
shaders/Irradiance.glslp

@@ -1,64 +0,0 @@
-// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-// Compute the irradiance given an environment map
-
-#pragma anki input const U32 ENV_TEX_TILE_SIZE
-#pragma anki input const F32 ENV_TEX_MIP
-
-#pragma anki start vert
-#include <shaders/QuadVert.glsl>
-#pragma anki end
-
-#pragma anki start frag
-#include <shaders/Functions.glsl>
-
-layout(location = 0) in Vec2 in_uv;
-layout(location = 0) out Vec3 out_color;
-
-layout(set = 0, binding = 0) uniform sampler u_linearAnyClampSampler;
-layout(set = 0, binding = 1) uniform textureCubeArray u_envTex;
-
-layout(push_constant, std430) uniform pc_
-{
-	UVec4 u_faceIdxPad3;
-};
-
-// Integrate the environment map to compute the irradiance for a single fragment
-void main()
-{
-	const U32 face = u_faceIdxPad3.x;
-	const F32 texArrIdx = 0.0; // The C++ code gives the layer idx using a tex view
-
-	// Get the r coordinate of the current face and fragment
-	const Vec3 ri = getCubemapDirection(UV_TO_NDC(in_uv), face);
-
-	Vec3 outCol = Vec3(0.0);
-
-	// For all the faces and texels of the environment map calculate a color sum
-	ANKI_LOOP for(U32 f = 0u; f < 6u; ++f)
-	{
-		ANKI_LOOP for(U32 i = 0u; i < ENV_TEX_TILE_SIZE; ++i)
-		{
-			ANKI_LOOP for(U32 j = 0u; j < ENV_TEX_TILE_SIZE; ++j)
-			{
-				const Vec2 uv = Vec2(j, i) / F32(ENV_TEX_TILE_SIZE);
-				const Vec2 ndc = UV_TO_NDC(uv);
-
-				const Vec3 r = getCubemapDirection(ndc, f);
-				const F32 lambert = dot(r, ri);
-
-				if(lambert > 0.0)
-				{
-					const Vec3 col = textureLod(u_envTex, u_linearAnyClampSampler, Vec4(r, texArrIdx), ENV_TEX_MIP).rgb;
-					outCol += col * lambert * cubeCoordSolidAngle(ndc, F32(ENV_TEX_TILE_SIZE));
-				}
-			}
-		}
-	}
-
-	out_color = outCol / PI;
-}
-#pragma anki end

+ 218 - 0
shaders/IrradianceDice.glslp

@@ -0,0 +1,218 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+// Compute the irradiance given a light shading result. The irradiance will be stored in an ambient dice.
+
+#pragma anki input const U32 WORKGROUP_SIZE
+#pragma anki mutator LIGHT_SHADING_TEX 0 1 // 0: texture2D, 1: textureCubeArray
+#pragma anki mutator STORE_LOCATION 0 1 // 0: in a 3D texture, 1: In an SSBO
+#pragma anki mutator SECOND_BOUNCE 0 1
+
+#pragma anki start comp
+
+#include <shaders/Functions.glsl>
+#include <shaders/Pack.glsl>
+#include <shaders/LightFunctions.glsl>
+
+#define DEBUG_MODE 0 // 0: disable, 1: different color per dice face, 2: different color per cell
+
+layout(local_size_x = WORKGROUP_SIZE, local_size_y = WORKGROUP_SIZE, local_size_z = 1) in;
+
+layout(set = 0, binding = 0) uniform sampler u_nearestAnyClampSampler;
+#if LIGHT_SHADING_TEX == 0
+layout(set = 0, binding = 1) uniform texture2D u_lightShadingTex;
+#else
+layout(set = 0, binding = 1) uniform textureCube u_lightShadingTex;
+#endif
+
+#if SECOND_BOUNCE == 1
+layout(set = 0, binding = 2) uniform texture2D u_gbufferTex[3u];
+#endif
+
+// This is a temporary buffer used instead of shared memory because the needs are too large
+layout(set = 0, binding = 3, std430) buffer ssbo_
+{
+	Vec4 u_integrationResults[6u][WORKGROUP_SIZE * WORKGROUP_SIZE];
+};
+
+#if STORE_LOCATION == 0
+layout(set = 0, binding = 4) uniform writeonly image3D u_irradianceVolume;
+
+layout(push_constant, std430) uniform pc_
+{
+	IVec3 u_volumeTexel;
+	I32 u_nextTexelOffsetInU;
+};
+#else
+layout(set = 0, binding = 4, std430) writeonly buffer ssbo1_
+{
+	Vec4 u_irradianceDisceResults[6u];
+};
+#endif
+
+shared Vec3 s_diceIrradiance[6u];
+
+Vec3 sampleLightShadingTexture(const U32 face)
+{
+#if LIGHT_SHADING_TEX == 0
+	const Vec2 INPUT_TEXTURES_SIZE = Vec2(WORKGROUP_SIZE * 6u, WORKGROUP_SIZE);
+	const Vec2 uv =
+		(Vec2(gl_LocalInvocationID.x + WORKGROUP_SIZE * face, gl_LocalInvocationID.y) + 0.5) / INPUT_TEXTURES_SIZE;
+
+	return textureLod(u_lightShadingTex, u_nearestAnyClampSampler, uv, 0.0).rgb;
+#else
+	const Vec2 uv = (Vec2(gl_LocalInvocationID.x, gl_LocalInvocationID.y) + 0.5) / F32(WORKGROUP_SIZE);
+	const Vec2 ndc = UV_TO_NDC(uv);
+	const Vec3 cubeUvw = getCubemapDirection(ndc, face);
+
+	return textureLod(u_lightShadingTex, u_nearestAnyClampSampler, cubeUvw, 0.0).rgb;
+#endif
+}
+
+void main()
+{
+	const F32 WORKGROUP_SIZE_F = F32(WORKGROUP_SIZE);
+
+	// Compute the NDC used in cubeCoordSolidAngle
+	const Vec2 faceUv = (Vec2(gl_LocalInvocationID.xy) + 0.5) / WORKGROUP_SIZE_F;
+	const Vec2 ndc = UV_TO_NDC(faceUv);
+
+	// Initialize
+	ANKI_UNROLL for(U32 f = 0u; f < 6u; ++f)
+	{
+		// Get the direction of the dice face
+		const Vec3 diceDir = getCubemapDirection(Vec2(0.0), f);
+
+		const Vec3 r = getCubemapDirection(ndc, f);
+
+		// Compute integral part
+		const F32 lambert = max(0.0, dot(r, diceDir));
+		const Vec3 lightShading = sampleLightShadingTexture(f);
+		const Vec3 irradiance = lightShading * lambert * cubeCoordSolidAngle(ndc, WORKGROUP_SIZE_F);
+
+		// Store
+		u_integrationResults[f][gl_LocalInvocationID.y * WORKGROUP_SIZE + gl_LocalInvocationID.x] = irradiance.xyzx;
+	}
+
+	memoryBarrierBuffer();
+	barrier();
+
+	// Reduce using prefix sum
+	const U32 WG_SIZE = WORKGROUP_SIZE * WORKGROUP_SIZE;
+	ANKI_LOOP for(U32 s = WG_SIZE / 2u; s > 0u; s >>= 1u)
+	{
+		if(gl_LocalInvocationIndex < s)
+		{
+			ANKI_UNROLL for(U32 f = 0u; f < 6u; ++f)
+			{
+				u_integrationResults[f][gl_LocalInvocationIndex] +=
+					u_integrationResults[f][gl_LocalInvocationIndex + s];
+			}
+		}
+
+		memoryBarrierBuffer();
+		barrier();
+	}
+
+	if(gl_LocalInvocationIndex < 6u)
+	{
+		s_diceIrradiance[gl_LocalInvocationIndex] = u_integrationResults[gl_LocalInvocationIndex][0].xyz;
+	}
+
+	memoryBarrierShared();
+	barrier();
+
+#if SECOND_BOUNCE == 1
+	// Initialize again for the 2nd bounce
+	ANKI_UNROLL for(U32 f = 0u; f < 6u; ++f)
+	{
+		// Get the direction of the dice face
+		const Vec3 diceDir = getCubemapDirection(Vec2(0.0), f);
+
+		const Vec3 r = getCubemapDirection(ndc, f);
+
+		// Compute integral part
+		const F32 lambert = max(0.0, dot(r, diceDir));
+
+		// Read the gbuffer
+		Vec2 gbufferUv = (Vec2(gl_LocalInvocationID.xy) + 0.5) / WORKGROUP_SIZE_F;
+		gbufferUv.x *= 1.0 / 6.0;
+		gbufferUv.x += (1.0 / 6.0) * F32(f);
+		GbufferInfo gbuffer;
+		readGBuffer(
+			u_gbufferTex[0u], u_gbufferTex[1u], u_gbufferTex[2u], u_nearestAnyClampSampler, gbufferUv, 0.0, gbuffer);
+
+		// Sample irradiance
+		Vec3 firstBounceIrradiance = sampleAmbientDice(s_diceIrradiance[0],
+			s_diceIrradiance[1],
+			s_diceIrradiance[2],
+			s_diceIrradiance[3],
+			s_diceIrradiance[4],
+			s_diceIrradiance[5],
+			gbuffer.m_normal);
+		firstBounceIrradiance = gbuffer.m_diffuse * firstBounceIrradiance;
+
+		// Compute 2nd bounce
+		const Vec3 lightShading = sampleLightShadingTexture(f);
+		const Vec3 irradiance =
+			(firstBounceIrradiance + lightShading * lambert) * cubeCoordSolidAngle(ndc, WORKGROUP_SIZE_F);
+
+		// Store
+		u_integrationResults[f][gl_LocalInvocationID.y * WORKGROUP_SIZE + gl_LocalInvocationID.x] = irradiance.xyzx;
+	}
+
+	memoryBarrierBuffer();
+	barrier();
+
+	// Reduce using prefix sum again
+	ANKI_LOOP for(U32 s = WG_SIZE / 2u; s > 0u; s >>= 1u)
+	{
+		if(gl_LocalInvocationIndex < s)
+		{
+			ANKI_UNROLL for(U32 f = 0u; f < 6u; ++f)
+			{
+				u_integrationResults[f][gl_LocalInvocationIndex] +=
+					u_integrationResults[f][gl_LocalInvocationIndex + s];
+			}
+		}
+
+		memoryBarrierBuffer();
+		barrier();
+	}
+#endif
+
+	// Store the results
+	if(gl_LocalInvocationIndex < 6u)
+	{
+		const U32 f = gl_LocalInvocationIndex;
+
+#if DEBUG_MODE == 0
+#	if SECOND_BOUNCE == 1
+		Vec3 irradiance = u_integrationResults[f][0].xyz;
+#	else
+		Vec3 irradiance = s_diceIrradiance[f];
+#	endif
+		const Vec3 toStoreValue = irradiance;
+#elif DEBUG_MODE == 1
+		const Vec3 toStoreValue = colorPerCubeFace(f);
+#else
+		const UVec3 volumeSize = UVec3(imageSize(u_irradianceVolume));
+		const UVec3 subvolumeSize = UVec3(volumeSize.x / 6u, volumeSize.y, volumeSize.z);
+		const U32 cellIdx =
+			u_volumeTexel.z * subvolumeSize.x * subvolumeSize.y + u_volumeTexel.y * subvolumeSize.x + u_volumeTexel.x;
+		const F32 headmapFactor = F32(cellIdx) / F32(subvolumeSize.x * subvolumeSize.y * subvolumeSize.z);
+		const Vec3 toStoreValue = heatmap(headmapFactor);
+#endif
+
+#if STORE_LOCATION == 0
+		const IVec3 storeUvw = IVec3(u_volumeTexel.x + I32(f) * u_nextTexelOffsetInU, u_volumeTexel.y, u_volumeTexel.z);
+		imageStore(u_irradianceVolume, storeUvw, Vec4(toStoreValue, 0.0));
+#else
+		u_irradianceDisceResults[f] = toStoreValue.xyzx;
+#endif
+	}
+}
+
+#pragma anki end

+ 57 - 0
shaders/LightFunctions.glsl

@@ -254,3 +254,60 @@ F32 computeProbeBlendWeight(Vec3 fragPos, // Doesn't need to be inside the AABB
 	// Use saturate because minDist might be negative.
 	return saturate(minDist / fadeDistance);
 }
+
+// Given the value of the 6 faces of the dice and a normal, sample the correct weighted value.
+// https://www.shadertoy.com/view/XtcBDB
+Vec3 sampleAmbientDice(Vec3 posx, Vec3 negx, Vec3 posy, Vec3 negy, Vec3 posz, Vec3 negz, Vec3 normal)
+{
+	const Vec3 axisWeights = abs(normal);
+	const Vec3 uv = NDC_TO_UV(normal);
+
+	Vec3 col = mix(negx, posx, uv.x) * axisWeights.x;
+	col += mix(negy, posy, uv.y) * axisWeights.y;
+	col += mix(negz, posz, uv.z) * axisWeights.z;
+
+	// Divide by weight
+	col /= axisWeights.x + axisWeights.y + axisWeights.z + EPSILON;
+
+	return col;
+}
+
+// Sample the irradiance term from the clipmap
+Vec3 sampleGlobalIllumination(const Vec3 worldPos,
+	const Vec3 normal,
+	const GlobalIlluminationProbe probe,
+	texture3D textures[MAX_VISIBLE_GLOBAL_ILLUMINATION_PROBES],
+	sampler linearAnyClampSampler)
+{
+	// Find the UVW
+	Vec3 uvw = (worldPos - probe.m_aabbMin) / (probe.m_aabbMax - probe.m_aabbMin);
+
+	// The U contains the 6 directions so divide
+	uvw.x /= 6.0;
+
+	// Calmp it to avoid direction leaking
+	uvw.x = clamp(uvw.x, probe.m_halfTexelSizeU, (1.0 / 6.0) - probe.m_halfTexelSizeU);
+
+	// Read the irradiance
+	Vec3 irradiancePerDir[6u];
+	ANKI_UNROLL for(U32 dir = 0u; dir < 6u; ++dir)
+	{
+		// Point to the correct UV
+		Vec3 shiftedUVw = uvw;
+		shiftedUVw.x += (1.0 / 6.0) * F32(dir);
+
+		irradiancePerDir[dir] =
+			textureLod(textures[nonuniformEXT(probe.m_textureIndex)], linearAnyClampSampler, shiftedUVw, 0.0).rgb;
+	}
+
+	// Sample the irradiance
+	const Vec3 irradiance = sampleAmbientDice(irradiancePerDir[0],
+		irradiancePerDir[1],
+		irradiancePerDir[2],
+		irradiancePerDir[3],
+		irradiancePerDir[4],
+		irradiancePerDir[5],
+		normal);
+
+	return irradiance;
+}

+ 115 - 50
shaders/LightShading.glslp

@@ -37,19 +37,20 @@ void main()
 #define LIGHT_SET 0
 #define LIGHT_COMMON_UNIS_BINDING 0
 #define LIGHT_LIGHTS_BINDING 1
-#define LIGHT_INDIRECT_BINDING 4
-#define LIGHT_CLUSTERS_BINDING 8
+#define LIGHT_INDIRECT_SPECULAR_BINDING 4
+#define LIGHT_GLOBAL_ILLUMINATION_BINDING 7
+#define LIGHT_CLUSTERS_BINDING 9
 #include <shaders/ClusteredShadingCommon.glsl>
 
-layout(set = 0, binding = 10) uniform sampler u_nearestAnyClampSampler;
-layout(set = 0, binding = 11) uniform sampler u_trilinearClampSampler;
+layout(set = 0, binding = 11) uniform sampler u_nearestAnyClampSampler;
+layout(set = 0, binding = 12) uniform sampler u_trilinearClampSampler;
 
-layout(set = 0, binding = 12) uniform texture2D u_msRt0;
-layout(set = 0, binding = 13) uniform texture2D u_msRt1;
-layout(set = 0, binding = 14) uniform texture2D u_msRt2;
-layout(set = 0, binding = 15) uniform texture2D u_msDepthRt;
-layout(set = 0, binding = 16) uniform texture2D u_ssrRt;
-layout(set = 0, binding = 17) uniform texture2D u_ssaoRt;
+layout(set = 0, binding = 13) uniform texture2D u_msRt0;
+layout(set = 0, binding = 14) uniform texture2D u_msRt1;
+layout(set = 0, binding = 15) uniform texture2D u_msRt2;
+layout(set = 0, binding = 16) uniform texture2D u_msDepthRt;
+layout(set = 0, binding = 17) uniform texture2D u_ssrRt;
+layout(set = 0, binding = 18) uniform texture2D u_ssaoRt;
 
 layout(location = 0) in Vec2 in_uv;
 layout(location = 1) in Vec2 in_clusterIJ;
@@ -83,7 +84,7 @@ void main()
 
 		idxOffset = u_clusters[clusterIdx];
 
-		// out_color = lightHeatmap(idxOffset, 5, false, true, false, false, false); return;
+		// out_color = lightHeatmap(idxOffset, 5, 1u << 3); return;
 	}
 
 	// Decode GBuffer
@@ -117,12 +118,12 @@ void main()
 			shadowFactor = 1.0;
 		}
 
-		Vec3 l = -u_dirLight.m_dir;
+		const Vec3 l = -u_dirLight.m_dir;
 
-		F32 lambert = max(gbuffer.m_subsurface, dot(l, gbuffer.m_normal));
+		const F32 lambert = max(gbuffer.m_subsurface, dot(l, gbuffer.m_normal));
 
-		Vec3 diffC = diffuseLambert(gbuffer.m_diffuse);
-		Vec3 specC = computeSpecularColorBrdf(gbuffer, viewDir, l);
+		const Vec3 diffC = diffuseLambert(gbuffer.m_diffuse);
+		const Vec3 specC = computeSpecularColorBrdf(gbuffer, viewDir, l);
 
 		out_color += (diffC + specC) * u_dirLight.m_diffuseColor * (shadowFactor * lambert);
 	}
@@ -137,7 +138,7 @@ void main()
 
 		ANKI_BRANCH if(light.m_shadowAtlasTileScale >= 0.0)
 		{
-			F32 shadow = computeShadowFactorPointLight(light, frag2Light, u_shadowTex, u_trilinearClampSampler);
+			const F32 shadow = computeShadowFactorPointLight(light, frag2Light, u_shadowTex, u_trilinearClampSampler);
 			lambert *= shadow;
 		}
 
@@ -151,70 +152,134 @@ void main()
 
 		LIGHTING_COMMON_BRDF();
 
-		F32 spot = computeSpotFactor(l, light.m_outerCos, light.m_innerCos, light.m_dir);
+		const F32 spot = computeSpotFactor(l, light.m_outerCos, light.m_innerCos, light.m_dir);
 
-		F32 shadowmapLayerIdx = light.m_shadowmapId;
+		const F32 shadowmapLayerIdx = light.m_shadowmapId;
 		ANKI_BRANCH if(shadowmapLayerIdx >= 0.0)
 		{
-			F32 shadow = computeShadowFactorSpotLight(light, worldPos, u_shadowTex, u_trilinearClampSampler);
+			const F32 shadow = computeShadowFactorSpotLight(light, worldPos, u_shadowTex, u_trilinearClampSampler);
 			lambert *= shadow;
 		}
 
 		out_color += (diffC + specC) * light.m_diffuseColor * (att * spot * max(gbuffer.m_subsurface, lambert));
 	}
 
-	// Refl & indirect
+	// Indirect specular
 	{
 		// Do the probe read
 		Vec3 specIndirect = Vec3(0.0);
-		Vec3 diffIndirect = Vec3(0.0);
 
 		const Vec3 reflDir = reflect(-viewDir, gbuffer.m_normal);
 		const F32 reflLod = F32(IR_MIPMAP_COUNT - 1u) * gbuffer.m_roughness;
-		F32 totalBlendWeight = EPSILON;
 
-		// Loop probes
-		ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
+		if(subgroupAll(u_lightIndices[idxOffset] != MAX_U32 && u_lightIndices[idxOffset + 1u] == MAX_U32))
 		{
-			const ReflectionProbe probe = u_reflectionProbes[idx];
-			const Vec3 aabbMin = probe.m_aabbMin;
-			const Vec3 aabbMax = probe.m_aabbMax;
-			const Vec3 probeOrigin = probe.m_position;
-			const F32 cubemapIndex = probe.m_cubemapIndex;
-
-			// Compute blend weight
-			const F32 blendWeight = computeProbeBlendWeight(worldPos, aabbMin, aabbMax, 0.2);
-			totalBlendWeight += blendWeight;
-
-			// Sample reflections
-			Vec3 cubeUv = intersectProbe(worldPos, reflDir, aabbMin, aabbMax, probeOrigin);
-			Vec4 cubeArrUv = Vec4(cubeUv, cubemapIndex);
-			Vec3 c = textureLod(u_reflectionsTex, u_trilinearClampSampler, cubeArrUv, reflLod).rgb;
-			specIndirect += c * blendWeight;
-
-			// Sample irradiance
-			cubeUv = intersectProbe(worldPos, gbuffer.m_normal, aabbMin, aabbMax, probeOrigin);
-			cubeArrUv = Vec4(cubeUv, cubemapIndex);
-			c = textureLod(u_irradianceTex, u_trilinearClampSampler, cubeArrUv, 0.0).rgb;
-			diffIndirect += c * blendWeight;
+			// Only one probe, do a fast path without blend weight
+
+			const ReflectionProbe probe = u_reflectionProbes[u_lightIndices[idxOffset]];
+			idxOffset += 2u;
+
+			// Sample
+			const Vec3 cubeUv = intersectProbe(worldPos, reflDir, probe.m_aabbMin, probe.m_aabbMax, probe.m_position);
+			const Vec4 cubeArrUv = Vec4(cubeUv, probe.m_cubemapIndex);
+			specIndirect = textureLod(u_reflectionsTex, u_trilinearClampSampler, cubeArrUv, reflLod).rgb;
 		}
+		else
+		{
+			// Zero or more than one probes, do a slow path that blends them together
+
+			F32 totalBlendWeight = EPSILON;
+
+			// Loop probes
+			ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
+			{
+				const ReflectionProbe probe = u_reflectionProbes[idx];
 
-		// Normalize the colors
-		specIndirect /= totalBlendWeight;
-		diffIndirect /= totalBlendWeight;
+				// Compute blend weight
+				const F32 blendWeight = computeProbeBlendWeight(worldPos, probe.m_aabbMin, probe.m_aabbMax, 0.2);
+				totalBlendWeight += blendWeight;
+
+				// Sample reflections
+				const Vec3 cubeUv =
+					intersectProbe(worldPos, reflDir, probe.m_aabbMin, probe.m_aabbMax, probe.m_position);
+				const Vec4 cubeArrUv = Vec4(cubeUv, probe.m_cubemapIndex);
+				Vec3 c = textureLod(u_reflectionsTex, u_trilinearClampSampler, cubeArrUv, reflLod).rgb;
+				specIndirect += c * blendWeight;
+			}
+
+			// Normalize the colors
+			specIndirect /= totalBlendWeight;
+		}
 
 		// Read the SSL result
 		const Vec4 ssr = textureLod(u_ssrRt, u_trilinearClampSampler, in_uv, 0.0);
 
 		// Combine the SSR and probe reflections and write the result
-		const Vec3 finalRefl = specIndirect * ssr.a + ssr.rgb;
+		const Vec3 finalSpecIndirect = specIndirect * ssr.a + ssr.rgb;
 
 		// Compute env BRDF
 		const F32 NoV = max(EPSILON, dot(gbuffer.m_normal, viewDir));
 		const Vec3 env =
 			envBRDF(gbuffer.m_specular, gbuffer.m_roughness, u_integrationLut, u_trilinearClampSampler, NoV);
 
-		out_color += diffIndirect * gbuffer.m_diffuse + finalRefl * env;
+		out_color += finalSpecIndirect * env;
+	}
+
+	// Indirect diffuse
+	{
+		Vec3 diffIndirect;
+
+		const U32 crntProbeIdx = u_lightIndices[idxOffset];
+		if(subgroupAllEqual(crntProbeIdx)
+			&& subgroupAll(crntProbeIdx != MAX_U32 && u_lightIndices[idxOffset + 1u] == MAX_U32))
+		{
+			// All subgroups point to the same probe and there is only one probe, do a fast path without blend weight
+
+			GlobalIlluminationProbe probe = u_giProbes[subgroupBroadcastFirst(crntProbeIdx)]; // It should be uniform
+
+			// Sample
+			diffIndirect = sampleGlobalIllumination(
+				worldPos, gbuffer.m_normal, probe, u_globalIlluminationTextures, u_trilinearClampSampler);
+		}
+		else
+		{
+			// Zero or more than one probes, do a slow path that blends them together
+
+			F32 totalBlendWeight = EPSILON;
+			diffIndirect = Vec3(0.0);
+
+			UVec4 execMask = UVec4(0u);
+			ANKI_LOOP while((execMask & gl_SubgroupEqMask) == UVec4(0u))
+			{
+				U32 uniformIdxOffset = subgroupBroadcastFirst(idxOffset); // Should be uniform
+				const UVec4 laneMask = subgroupBallot(uniformIdxOffset == idxOffset);
+				execMask |= laneMask;
+
+				if(uniformIdxOffset == idxOffset)
+				{
+					// Loop probes
+					ANKI_LOOP while((idx = u_lightIndices[uniformIdxOffset++]) != MAX_U32)
+					{
+						GlobalIlluminationProbe probe = u_giProbes[idx];
+
+						// Compute blend weight
+						const F32 blendWeight =
+							computeProbeBlendWeight(worldPos, probe.m_aabbMin, probe.m_aabbMax, probe.m_fadeDistance);
+						totalBlendWeight += blendWeight;
+
+						// Sample
+						const Vec3 c = sampleGlobalIllumination(
+							worldPos, gbuffer.m_normal, probe, u_globalIlluminationTextures, u_trilinearClampSampler);
+						diffIndirect += c * blendWeight;
+					}
+				}
+			}
+
+			// Normalize
+			diffIndirect /= totalBlendWeight;
+		}
+
+		out_color += diffIndirect * gbuffer.m_diffuse;
 	}
 }
 #pragma anki end

+ 1 - 1
shaders/Pack.glsl

@@ -189,5 +189,5 @@ void readGBuffer(texture2D rt0, texture2D rt1, texture2D rt2, sampler sampl, Vec
 	g.m_specular = mix(g.m_specular, g.m_diffuse, g.m_metallic);
 
 	// Compute diffuse
-	g.m_diffuse = g.m_diffuse - g.m_diffuse * g.m_metallic;
+	g.m_diffuse *= 1.0 - g.m_metallic;
 }

+ 10 - 4
shaders/TraditionalDeferredShading.glslp

@@ -6,6 +6,7 @@
 // Classic deferred lighting shader
 
 #pragma anki mutator LIGHT_TYPE 0 1 2
+#pragma anki mutator SPECULAR 0 1
 
 #define POINT_LIGHT_TYPE 0
 #define SPOT_LIGHT_TYPE 1
@@ -77,8 +78,8 @@ layout(set = 0, binding = 8) uniform texture2D u_shadowMap;
 void main()
 {
 	// Compute UV coordinates
-	const Vec2 uv = Vec2(gl_FragCoord.xy) / u_unis.m_fbSize;
-	const Vec2 uvToRead = fma(uv, u_unis.m_inputTexUvScale, u_unis.m_inputTexUvOffset);
+	const Vec2 uvToRead = fma(Vec2(gl_FragCoord.xy), u_unis.m_inputTexUvScale, u_unis.m_inputTexUvBias);
+	const Vec2 uvToWrite = fma(Vec2(gl_FragCoord.xy), u_unis.m_fbUvScale, u_unis.m_fbUvBias);
 
 	const F32 depth = textureLod(u_msDepthRt, u_msSampler, uvToRead, 0.0).r;
 
@@ -95,7 +96,7 @@ void main()
 	readGBuffer(u_msRt0, u_msRt1, u_msRt2, u_msSampler, uvToRead, 0.0, gbuffer);
 	gbuffer.m_subsurface = max(gbuffer.m_subsurface, SUBSURFACE_MIN * 8.0);
 
-	const Vec4 worldPos4 = u_unis.m_invViewProjMat * Vec4(UV_TO_NDC(uv), depth, 1.0);
+	const Vec4 worldPos4 = u_unis.m_invViewProjMat * Vec4(UV_TO_NDC(uvToWrite), depth, 1.0);
 	const Vec3 worldPos = worldPos4.xyz / worldPos4.w;
 
 	// Compute diff
@@ -110,7 +111,12 @@ void main()
 	const Vec3 l = normalize(frag2Light);
 	const F32 nol = max(0.0, dot(gbuffer.m_normal, l));
 #endif
+
+#if SPECULAR == 1
 	const Vec3 specC = computeSpecularColorBrdf(gbuffer, viewDir, l);
+#else
+	const Vec3 specC = Vec3(0.0);
+#endif
 
 	// Compute factors
 #if LIGHT_TYPE == POINT_LIGHT_TYPE
@@ -144,4 +150,4 @@ void main()
 	out_color += (specC + diffC) * u_unis.m_diffuseColor * factor;
 }
 
-#pragma anki end
+#pragma anki end

+ 67 - 39
shaders/VolumetricLightingAccumulation.glslp

@@ -39,9 +39,9 @@ layout(push_constant, std430) uniform pc_
 #define LIGHT_SET 0
 #define LIGHT_COMMON_UNIS_BINDING 5
 #define LIGHT_LIGHTS_BINDING 6
-#define LIGHT_INDIRECT_BINDING 9
-#define LIGHT_FOG_DENSITY_VOLUMES_BINDING 13
-#define LIGHT_CLUSTERS_BINDING 14
+#define LIGHT_GLOBAL_ILLUMINATION_BINDING 9
+#define LIGHT_FOG_DENSITY_VOLUMES_BINDING 11
+#define LIGHT_CLUSTERS_BINDING 12
 #include <shaders/ClusteredShadingCommon.glsl>
 
 Vec3 g_globalInvocationID = Vec3(gl_GlobalInvocationID);
@@ -169,51 +169,79 @@ Vec4 accumulateLightsAndFog(U32 clusterIdx, Vec3 worldPos, F32 linearDepth)
 		color += light.m_diffuseColor * factor;
 	}
 
-	// Probes
-	F32 totalBlendWeight = EPSILON;
-	Vec3 diffIndirect = Vec3(0.0);
-	ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
+	// Indirect diffuse GI
 	{
-		const ReflectionProbe probe = u_reflectionProbes[idx];
-		const Vec3 aabbMin = probe.m_aabbMin;
-		const Vec3 aabbMax = probe.m_aabbMax;
-		const Vec3 probeOrigin = probe.m_position;
-		const F32 cubemapIndex = probe.m_cubemapIndex;
-
-		const F32 blendWeight = computeProbeBlendWeight(worldPos, aabbMin, aabbMax, 0.2);
-		totalBlendWeight += blendWeight;
-
-		Vec3 c = textureLod(u_irradianceTex, u_linearAnyClampSampler, Vec4(viewDir, cubemapIndex), 0.0).rgb;
-		c *= PI; // Irradiance is pre-divided with PI so fix it
-		diffIndirect += c * blendWeight;
-	}
+		idxOffset = u_clusters[clusterIdx];
+		idxOffset = u_lightIndices[idxOffset - 3u];
+		Vec3 diffIndirect;
 
-	diffIndirect /= totalBlendWeight;
-	color += diffIndirect;
+		if(subgroupAll(u_lightIndices[idxOffset] != MAX_U32 && u_lightIndices[idxOffset + 1u] == MAX_U32))
+		{
+			// Only one probe, do a fast path without blend weight
 
-	// Fog density
-	F32 fogDensity = 0.0;
-	idxOffset = u_clusters[clusterIdx];
-	idxOffset = u_lightIndices[idxOffset - 1u];
-	ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
-	{
-		const FogDensityVolume vol = u_fogDensityVolumes[idx];
+			GlobalIlluminationProbe probe = u_giProbes[u_lightIndices[idxOffset]];
 
-		F32 factor;
-		ANKI_BRANCH if(vol.m_isBox == 1u)
-		{
-			factor =
-				computeProbeBlendWeight(worldPos, vol.m_aabbMinOrSphereCenter, vol.m_aabbMaxOrSphereRadiusSquared, 0.2);
+			// Sample
+			diffIndirect = sampleGlobalIllumination(
+				worldPos, viewDir, probe, u_globalIlluminationTextures, u_linearAnyClampSampler);
 		}
 		else
 		{
-			const Vec3 diff = worldPos - vol.m_aabbMinOrSphereCenter;
-			F32 distSq = dot(diff, diff) / vol.m_aabbMaxOrSphereRadiusSquared.x;
-			distSq = min(1.0, distSq);
-			factor = 1.0 - distSq;
+			// Zero or more than one probes, do a slow path that blends them together
+
+			F32 totalBlendWeight = EPSILON;
+			diffIndirect = Vec3(0.0);
+
+			// Loop probes
+			ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
+			{
+				GlobalIlluminationProbe probe = u_giProbes[idx];
+
+				// Compute blend weight
+				const F32 blendWeight =
+					computeProbeBlendWeight(worldPos, probe.m_aabbMin, probe.m_aabbMax, probe.m_fadeDistance);
+				totalBlendWeight += blendWeight;
+
+				// Sample
+				const Vec3 c = sampleGlobalIllumination(
+					worldPos, viewDir, probe, u_globalIlluminationTextures, u_linearAnyClampSampler);
+				diffIndirect += c * blendWeight;
+			}
+
+			// Normalize
+			diffIndirect /= totalBlendWeight;
 		}
 
-		fogDensity += vol.m_density * factor;
+		diffIndirect *= PI; // Irradiance is pre-divided with PI so fix it
+
+		color += diffIndirect;
+	}
+
+	// Fog density
+	F32 fogDensity = 0.0;
+	{
+		idxOffset = u_clusters[clusterIdx];
+		idxOffset = u_lightIndices[idxOffset - 1u];
+		ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
+		{
+			const FogDensityVolume vol = u_fogDensityVolumes[idx];
+
+			F32 factor;
+			ANKI_BRANCH if(vol.m_isBox == 1u)
+			{
+				factor = computeProbeBlendWeight(
+					worldPos, vol.m_aabbMinOrSphereCenter, vol.m_aabbMaxOrSphereRadiusSquared, 0.2);
+			}
+			else
+			{
+				const Vec3 diff = worldPos - vol.m_aabbMinOrSphereCenter;
+				F32 distSq = dot(diff, diff) / vol.m_aabbMaxOrSphereRadiusSquared.x;
+				distSq = min(1.0, distSq);
+				factor = 1.0 - distSq;
+			}
+
+			fogDensity += vol.m_density * factor;
+		}
 	}
 
 	return Vec4(color, fogDensity);

+ 29 - 3
shaders/glsl_cpp_common/ClusteredShading.h

@@ -12,11 +12,12 @@
 ANKI_BEGIN_NAMESPACE
 
 // Consts
-const U32 TYPED_OBJECT_COUNT = 5u;
+const U32 TYPED_OBJECT_COUNT = 6u; // Point lights, spot lights, refl probes, GI probes, decals and fog volumes
 const F32 INVALID_TEXTURE_INDEX = -1.0f;
 const F32 LIGHT_FRUSTUM_NEAR_PLANE = 0.1f / 4.0f; // The near plane on the shadow map frustums.
 const U32 MAX_SHADOW_CASCADES = 4u;
 const F32 SUBSURFACE_MIN = 0.05f;
+const U32 MAX_VISIBLE_GLOBAL_ILLUMINATION_PROBES = 8u; // Global illumination clipmap count.
 
 // See the documentation in the ClustererBin class.
 struct ClustererMagicValues
@@ -101,23 +102,47 @@ struct FogDensityVolume
 	Vec3 m_aabbMaxOrSphereRadiusSquared;
 	F32 m_density;
 };
-const U32 SIZEOF_FOG_DENSITY_VOLUME = 2 * SIZEOF_VEC4;
+const U32 SIZEOF_FOG_DENSITY_VOLUME = 2u * SIZEOF_VEC4;
 ANKI_SHADER_STATIC_ASSERT(sizeof(FogDensityVolume) == SIZEOF_FOG_DENSITY_VOLUME)
 
+// Global illumination probe
+struct GlobalIlluminationProbe
+{
+	Vec3 m_aabbMin;
+	U32 m_textureIndex;
+
+	Vec3 m_aabbMax;
+	F32 m_halfTexelSizeU; // (1.0 / textureSize(texArr[m_textureIndex]).x) / 2.0
+
+	// Used to calculate a factor that is zero when fragPos is close to AABB bounds and 1.0 at m_fadeDistance and less.
+	F32 m_fadeDistance;
+	F32 m_padding0;
+	F32 m_padding1;
+	F32 m_padding2;
+};
+const U32 SIZEOF_GLOBAL_ILLUMINATION_PROBE = 3u * SIZEOF_VEC4;
+ANKI_SHADER_STATIC_ASSERT(sizeof(GlobalIlluminationProbe) == SIZEOF_GLOBAL_ILLUMINATION_PROBE)
+
 // Common uniforms for light shading passes
 struct LightingUniforms
 {
 	Vec4 m_unprojectionParams;
+
 	Vec2 m_rendererSize;
 	F32 m_time;
 	F32 m_near;
+
 	Vec3 m_cameraPos;
 	F32 m_far;
+
 	ClustererMagicValues m_clustererMagicValues;
 	ClustererMagicValues m_prevClustererMagicValues;
+
 	UVec4 m_clusterCount;
+
 	Vec3 m_padding;
 	U32 m_lightVolumeLastCluster;
+
 	Mat4 m_viewMat;
 	Mat4 m_invViewMat;
 	Mat4 m_projMat;
@@ -126,9 +151,10 @@ struct LightingUniforms
 	Mat4 m_invViewProjMat;
 	Mat4 m_prevViewProjMat;
 	Mat4 m_prevViewProjMatMulInvViewProjMat; // Used to re-project previous frames
+
 	DirectionalLight m_dirLight;
 };
-const U32 SIZEOF_LIGHTING_UNIFORMS = 9 * SIZEOF_VEC4 + 8 * SIZEOF_MAT4 + SIZEOF_DIR_LIGHT;
+const U32 SIZEOF_LIGHTING_UNIFORMS = 9u * SIZEOF_VEC4 + 8u * SIZEOF_MAT4 + SIZEOF_DIR_LIGHT;
 ANKI_SHADER_STATIC_ASSERT(sizeof(LightingUniforms) == SIZEOF_LIGHTING_UNIFORMS)
 
 ANKI_SHADER_FUNC_INLINE F32 computeClusterKf(ClustererMagicValues magic, Vec3 worldPos)

+ 13 - 14
shaders/glsl_cpp_common/TraditionalDeferredShading.h

@@ -13,16 +13,16 @@ struct DeferredPointLightUniforms
 {
 	// Use these to get the correct face UVs
 	Vec2 m_inputTexUvScale;
-	Vec2 m_inputTexUvOffset;
+	Vec2 m_inputTexUvBias;
+
+	Vec2 m_fbUvScale;
+	Vec2 m_fbUvBias;
 
 	Mat4 m_invViewProjMat;
 
 	Vec3 m_camPos;
 	F32 m_padding;
 
-	Vec2 m_fbSize;
-	Vec2 m_padding1;
-
 	// Light props
 	Vec3 m_position;
 	F32 m_oneOverSquareRadius; // 1/radius^2
@@ -35,16 +35,16 @@ struct DeferredSpotLightUniforms
 {
 	// Use these to get the correct face UVs
 	Vec2 m_inputTexUvScale;
-	Vec2 m_inputTexUvOffset;
+	Vec2 m_inputTexUvBias;
+
+	Vec2 m_fbUvScale;
+	Vec2 m_fbUvBias;
 
 	Mat4 m_invViewProjMat;
 
 	Vec3 m_camPos;
 	F32 m_padding;
 
-	Vec2 m_fbSize;
-	Vec2 m_padding1;
-
 	// Light props
 	Vec3 m_position;
 	F32 m_oneOverSquareRadius; // 1/radius^2
@@ -60,20 +60,19 @@ struct DeferredDirectionalLightUniforms
 {
 	// Use these to get the correct face UVs
 	Vec2 m_inputTexUvScale;
-	Vec2 m_inputTexUvOffset;
+	Vec2 m_inputTexUvBias;
+
+	Vec2 m_fbUvScale;
+	Vec2 m_fbUvBias;
 
 	Mat4 m_invViewProjMat;
 
 	Vec3 m_camPos;
-	F32 m_padding;
-
-	Vec2 m_fbSize;
 	F32 m_near;
-	F32 m_far;
 
 	// Light props
 	Vec3 m_diffuseColor;
-	F32 m_padding2;
+	F32 m_far;
 
 	Vec3 m_lightDir;
 	F32 m_effectiveShadowDistance;

+ 7 - 1
src/anki/Config.h.cmake

@@ -156,7 +156,9 @@
 #	define ANKI_UNUSED __attribute__((__unused__))
 #	define ANKI_COLD __attribute__((cold, optimize("Os")))
 #	define ANKI_HOT __attribute__ ((hot))
-#else
+#	define ANKI_UNREACHABLE() __builtin_unreachable()
+#	define ANKI_PREFETCH_MEMORY(addr) __builtin_prefetch(addr)
+#elif defined(_MSC_VER)
 #	define ANKI_LIKELY(x) ((x) == 1)
 #	define ANKI_UNLIKELY(x) ((x) == 1)
 #	define ANKI_RESTRICT
@@ -166,6 +168,10 @@
 #	define ANKI_UNUSED
 #	define ANKI_COLD
 #	define ANKI_HOT
+#	define ANKI_UNREACHABLE() __assume(false)
+#	define ANKI_PREFETCH_MEMORY(addr) (void)(addr)
+#else
+#	error Unsupported compiler
 #endif
 
 // Pack structs

+ 2 - 1
src/anki/Renderer.h

@@ -21,7 +21,7 @@
 #include <anki/renderer/TemporalAA.h>
 #include <anki/renderer/RenderQueue.h>
 #include <anki/renderer/Ssr.h>
-#include <anki/renderer/Indirect.h>
+#include <anki/renderer/ProbeReflections.h>
 #include <anki/renderer/Dbg.h>
 #include <anki/renderer/Ssao.h>
 #include <anki/renderer/Drawer.h>
@@ -30,5 +30,6 @@
 #include <anki/renderer/RendererObject.h>
 #include <anki/renderer/Bloom.h>
 #include <anki/renderer/VolumetricLightingAccumulation.h>
+#include <anki/renderer/GlobalIllumination.h>
 
 /// @defgroup renderer Renderering system

+ 2 - 0
src/anki/Scene.h

@@ -21,6 +21,7 @@
 #include <anki/scene/PhysicsDebugNode.h>
 #include <anki/scene/TriggerNode.h>
 #include <anki/scene/FogDensityNode.h>
+#include <anki/scene/GlobalIlluminationProbeNode.h>
 
 #include <anki/scene/components/MoveComponent.h>
 #include <anki/scene/components/RenderComponent.h>
@@ -38,6 +39,7 @@
 #include <anki/scene/components/JointComponent.h>
 #include <anki/scene/components/TriggerComponent.h>
 #include <anki/scene/components/FogDensityComponent.h>
+#include <anki/scene/components/GlobalIlluminationProbeComponent.h>
 
 #include <anki/scene/events/EventManager.h>
 #include <anki/scene/events/Event.h>

+ 10 - 0
src/anki/core/Config.cpp

@@ -42,9 +42,18 @@ Config::Config()
 	newOption("r.bloom.scale", 2.5);
 
 	newOption("r.indirect.reflectionResolution", 128);
+	newOption("r.indirect.irradianceResolution", 16);
 	newOption("r.indirect.maxSimultaneousProbeCount", 32);
 	newOption("r.indirect.shadowMapResolution", 64);
 
+	newOption("r.gi.tileResolution", 32);
+	newOption("r.gi.shadowMapResolution", 128);
+	newOption("r.gi.maxCachedProbes", 16);
+	newOption("r.gi.maxVisibleProbes", 8);
+	newOption("r.gi.firstClipmapLevelCellSize", 1.0);
+	newOption("r.gi.secondClipmapLevelCellSize", 8.0);
+	newOption("r.gi.firstClipmapMaxDistance", 20.0);
+
 	newOption("r.motionBlur.maxSamples", 32);
 
 	newOption("r.ssr.maxSteps", 64);
@@ -65,6 +74,7 @@ Config::Config()
 
 	// Resource
 	newOption("rsrc.maxTextureSize", 1024 * 1024);
+	newOption("rsrc.dumpShaderSources", false);
 	newOption("rsrc.dataPaths", ".", "The engine loads assets only in from these paths. Separate them with :");
 	newOption("rsrc.transferScratchMemorySize", 256_MB);
 

+ 1 - 1
src/anki/gr/Common.h

@@ -46,7 +46,7 @@ const U MAX_BINDLESS_IMAGES = 32;
 
 const U MAX_FRAMES_IN_FLIGHT = 3; ///< Triple buffering.
 
-const U MAX_GR_OBJECT_NAME_LENGTH = 15;
+const U MAX_GR_OBJECT_NAME_LENGTH = 31;
 
 /// The number of commands in a command buffer that make it a small batch command buffer.
 const U COMMAND_BUFFER_SMALL_BATCH_MAX_COMMANDS = 100;

+ 129 - 18
src/anki/gr/RenderGraph.cpp

@@ -19,6 +19,11 @@ namespace anki
 
 #define ANKI_DBG_RENDER_GRAPH 0
 
+static inline U getTextureSurfOrVolCount(const TexturePtr& tex)
+{
+	return tex->getMipmapCount() * tex->getLayerCount() * (textureTypeIsCube(tex->getTextureType()) ? 6 : 1);
+}
+
 /// Contains some extra things for render targets.
 class RenderGraph::RT
 {
@@ -26,6 +31,7 @@ public:
 	DynamicArray<TextureUsageBit> m_surfOrVolUsages;
 	DynamicArray<U16> m_lastBatchThatTransitionedIt;
 	TexturePtr m_texture; ///< Hold a reference.
+	Bool m_imported;
 };
 
 /// Same as RT but for buffers.
@@ -246,13 +252,51 @@ RenderGraph* RenderGraph::newInstance(GrManager* manager)
 
 void RenderGraph::reset()
 {
+	ANKI_TRACE_SCOPED_EVENT(GR_RENDER_GRAPH_RESET);
+
 	if(!m_ctx)
 	{
 		return;
 	}
 
+	if((m_version % PERIODIC_CLEANUP_EVERY) == 0)
+	{
+		// Do cleanup
+		periodicCleanup();
+	}
+
+	// Extract the final usage of the imported RTs and clean all RTs
 	for(RT& rt : m_ctx->m_rts)
 	{
+		if(rt.m_imported)
+		{
+			const U surfOrVolumeCount = getTextureSurfOrVolCount(rt.m_texture);
+
+			// Create a new hash because our hash map dislikes concurent keys.
+			const U64 uuid = rt.m_texture->getUuid();
+			const U64 hash = computeHash(&uuid, sizeof(uuid));
+
+			auto it = m_importedRenderTargets.find(hash);
+			if(it != m_importedRenderTargets.getEnd())
+			{
+				// Found
+				ANKI_ASSERT(it->m_surfOrVolLastUsages.getSize() == surfOrVolumeCount);
+				ANKI_ASSERT(rt.m_surfOrVolUsages.getSize() == surfOrVolumeCount);
+			}
+			else
+			{
+				// Not found, create
+				it = m_importedRenderTargets.emplace(getAllocator(), hash);
+				it->m_surfOrVolLastUsages.create(getAllocator(), surfOrVolumeCount);
+			}
+
+			// Update the usage
+			for(U surfOrVolIdx = 0; surfOrVolIdx < surfOrVolumeCount; ++surfOrVolIdx)
+			{
+				it->m_surfOrVolLastUsages[surfOrVolIdx] = rt.m_surfOrVolUsages[surfOrVolIdx];
+			}
+		}
+
 		rt.m_texture.reset(nullptr);
 	}
 
@@ -556,39 +600,61 @@ RenderGraph::BakeContext* RenderGraph::newContext(const RenderGraphDescription&
 	for(U rtIdx = 0; rtIdx < ctx->m_rts.getSize(); ++rtIdx)
 	{
 		RT& outRt = ctx->m_rts[rtIdx];
+		const RenderGraphDescription::RT& inRt = descr.m_renderTargets[rtIdx];
 
-		TexturePtr tex;
-		Bool imported = descr.m_renderTargets[rtIdx].m_importedTex.isCreated();
+		const Bool imported = inRt.m_importedTex.isCreated();
 		if(imported)
 		{
 			// It's imported
-			tex = descr.m_renderTargets[rtIdx].m_importedTex;
+			outRt.m_texture = inRt.m_importedTex;
 		}
 		else
 		{
 			// Need to create new
 
 			// Create a new TextureInitInfo with the derived usage
-			TextureInitInfo initInf = descr.m_renderTargets[rtIdx].m_initInfo;
-			initInf.m_usage = descr.m_renderTargets[rtIdx].m_usageDerivedByDeps;
+			TextureInitInfo initInf = inRt.m_initInfo;
+			initInf.m_usage = inRt.m_usageDerivedByDeps;
 			ANKI_ASSERT(initInf.m_usage != TextureUsageBit::NONE);
 
 			// Create the new hash
-			const U64 hash = appendHash(&initInf.m_usage, sizeof(initInf.m_usage), descr.m_renderTargets[rtIdx].m_hash);
+			const U64 hash = appendHash(&initInf.m_usage, sizeof(initInf.m_usage), inRt.m_hash);
 
 			// Get or create the texture
-			tex = getOrCreateRenderTarget(initInf, hash);
+			outRt.m_texture = getOrCreateRenderTarget(initInf, hash);
 		}
 
-		outRt.m_texture = tex;
+		// Init the usage
+		const U surfOrVolumeCount = getTextureSurfOrVolCount(outRt.m_texture);
+		outRt.m_surfOrVolUsages.create(alloc, surfOrVolumeCount, TextureUsageBit::NONE);
+		if(imported && inRt.m_importedAndUndefinedUsage)
+		{
+			// Get the usage from previous frames
+
+			// Create a new hash because our hash map dislikes concurent keys.
+			const U64 uuid = outRt.m_texture->getUuid();
+			const U64 hash = computeHash(&uuid, sizeof(uuid));
+
+			auto it = m_importedRenderTargets.find(hash);
+			ANKI_ASSERT(it != m_importedRenderTargets.getEnd() && "Can't find the imported RT");
+
+			ANKI_ASSERT(it->m_surfOrVolLastUsages.getSize() == surfOrVolumeCount);
+			for(U surfOrVolIdx = 0; surfOrVolIdx < surfOrVolumeCount; ++surfOrVolIdx)
+			{
+				outRt.m_surfOrVolUsages[surfOrVolIdx] = it->m_surfOrVolLastUsages[surfOrVolIdx];
+			}
+		}
+		else if(imported)
+		{
+			// Set the usage that was given by the user
+			for(U surfOrVolIdx = 0; surfOrVolIdx < surfOrVolumeCount; ++surfOrVolIdx)
+			{
+				outRt.m_surfOrVolUsages[surfOrVolIdx] = inRt.m_importedLastKnownUsage;
+			}
+		}
 
-		// Init the surfs or volumes
-		const U surfOrVolumeCount =
-			tex->getMipmapCount() * tex->getLayerCount() * (textureTypeIsCube(tex->getTextureType()) ? 6 : 1);
-		outRt.m_surfOrVolUsages.create(alloc,
-			surfOrVolumeCount,
-			(imported) ? descr.m_renderTargets[rtIdx].m_importedLastKnownUsage : TextureUsageBit::NONE);
 		outRt.m_lastBatchThatTransitionedIt.create(alloc, surfOrVolumeCount, MAX_U16);
+		outRt.m_imported = imported;
 	}
 
 	// Buffers
@@ -963,7 +1029,7 @@ void RenderGraph::setBatchBarriers(const RenderGraphDescription& descr)
 
 void RenderGraph::compileNewGraph(const RenderGraphDescription& descr, StackAllocator<U8>& alloc)
 {
-	ANKI_TRACE_SCOPED_EVENT(GR_RENDER_GRAPH);
+	ANKI_TRACE_SCOPED_EVENT(GR_RENDER_GRAPH_COMPILE);
 
 	// Init the context
 	BakeContext& ctx = *newContext(descr, alloc);
@@ -1003,7 +1069,7 @@ BufferPtr RenderGraph::getBuffer(RenderPassBufferHandle handle) const
 
 void RenderGraph::runSecondLevel(U32 threadIdx)
 {
-	ANKI_TRACE_SCOPED_EVENT(GR_RENDER_GRAPH);
+	ANKI_TRACE_SCOPED_EVENT(GR_RENDER_GRAPH_2ND_LEVEL);
 	ANKI_ASSERT(m_ctx);
 
 	RenderPassWorkContext ctx;
@@ -1025,7 +1091,11 @@ void RenderGraph::runSecondLevel(U32 threadIdx)
 			ctx.m_userData = p.m_userData;
 
 			ANKI_ASSERT(ctx.m_commandBuffer.isCreated());
-			p.m_callback(ctx);
+
+			{
+				ANKI_TRACE_SCOPED_EVENT(GR_RENDER_GRAPH_CALLBACK);
+				p.m_callback(ctx);
+			}
 
 			ctx.m_commandBuffer->flush();
 		}
@@ -1034,7 +1104,7 @@ void RenderGraph::runSecondLevel(U32 threadIdx)
 
 void RenderGraph::run() const
 {
-	ANKI_TRACE_SCOPED_EVENT(GR_RENDER_GRAPH);
+	ANKI_TRACE_SCOPED_EVENT(GR_RENDER_GRAPH_RUN);
 	ANKI_ASSERT(m_ctx);
 
 	RenderPassWorkContext ctx;
@@ -1090,6 +1160,7 @@ void RenderGraph::run() const
 				ctx.m_passIdx = passIdx;
 				ctx.m_batchIdx = pass.m_batchIdx;
 
+				ANKI_TRACE_SCOPED_EVENT(GR_RENDER_GRAPH_CALLBACK);
 				pass.m_callback(ctx);
 			}
 			else
@@ -1110,6 +1181,8 @@ void RenderGraph::run() const
 
 void RenderGraph::flush()
 {
+	ANKI_TRACE_SCOPED_EVENT(GR_RENDER_GRAPH_FLUSH);
+
 	for(CommandBufferPtr& cmdb : m_ctx->m_graphicsCmdbs)
 	{
 		cmdb->flush();
@@ -1135,6 +1208,44 @@ void RenderGraph::getCrntUsage(
 	}
 }
 
+void RenderGraph::periodicCleanup()
+{
+	U rtsCleanedCount = 0;
+	for(RenderTargetCacheEntry& entry : m_renderTargetCache)
+	{
+		if(entry.m_texturesInUse < entry.m_textures.getSize())
+		{
+			// Should cleanup
+
+			rtsCleanedCount += entry.m_textures.getSize() - entry.m_texturesInUse;
+
+			// New array
+			DynamicArray<TexturePtr> newArray;
+			if(entry.m_texturesInUse > 0)
+			{
+				newArray.create(getAllocator(), entry.m_texturesInUse);
+			}
+
+			// Populate the new array
+			for(U i = 0; i < newArray.getSize(); ++i)
+			{
+				newArray[i] = std::move(entry.m_textures[i]);
+			}
+
+			// Destroy the old array and the rest of the textures
+			entry.m_textures.destroy(getAllocator());
+
+			// Move new array
+			entry.m_textures = std::move(newArray);
+		}
+	}
+
+	if(rtsCleanedCount > 0)
+	{
+		ANKI_GR_LOGI("Cleaned %u render targets", rtsCleanedCount);
+	}
+}
+
 #if ANKI_DBG_RENDER_GRAPH
 StringAuto RenderGraph::textureUsageToStr(StackAllocator<U8>& alloc, TextureUsageBit usage)
 {

+ 42 - 113
src/anki/gr/RenderGraph.h

@@ -178,25 +178,26 @@ public:
 	}
 
 	/// Convenience method to bind the whole texture as color.
-	void bindColorTexture(U32 set, U32 binding, RenderTargetHandle handle)
+	void bindColorTexture(U32 set, U32 binding, RenderTargetHandle handle, U arrayIdx = 0)
 	{
 		TexturePtr tex = getTexture(handle);
 		TextureViewInitInfo viewInit(tex); // Use the whole texture
 		TextureUsageBit usage;
 		getRenderTargetState(handle, viewInit, tex, usage);
 		TextureViewPtr view = m_commandBuffer->getManager().newTextureView(viewInit);
-		m_commandBuffer->bindTexture(set, binding, view, usage);
+		m_commandBuffer->bindTexture(set, binding, view, usage, arrayIdx);
 	}
 
 	/// Convenience method.
-	void bindImage(U32 set, U32 binding, RenderTargetHandle handle, const TextureSubresourceInfo& subresource)
+	void bindImage(
+		U32 set, U32 binding, RenderTargetHandle handle, const TextureSubresourceInfo& subresource, U arrayIdx = 0)
 	{
 		TexturePtr tex;
 		TextureUsageBit usage;
 		getRenderTargetState(handle, subresource, tex, usage);
 		TextureViewInitInfo viewInit(tex, subresource, "TmpRenderGraph");
 		TextureViewPtr view = m_commandBuffer->getManager().newTextureView(viewInit);
-		m_commandBuffer->bindImage(set, binding, view);
+		m_commandBuffer->bindImage(set, binding, view, arrayIdx);
 	}
 
 	/// Convenience method.
@@ -403,40 +404,10 @@ public:
 	void setFramebufferInfo(const FramebufferDescription& fbInfo,
 		const Array<RenderTargetHandle, MAX_COLOR_ATTACHMENTS>& colorRenderTargetHandles,
 		RenderTargetHandle depthStencilRenderTargetHandle,
-		U32 minx = 0,
-		U32 miny = 0,
-		U32 maxx = MAX_U32,
-		U32 maxy = MAX_U32)
-	{
-#if ANKI_EXTRA_CHECKS
-		ANKI_ASSERT(fbInfo.isBacked() && "Forgot call GraphicsRenderPassFramebufferInfo::bake");
-		for(U i = 0; i < colorRenderTargetHandles.getSize(); ++i)
-		{
-			if(i >= fbInfo.m_colorAttachmentCount)
-			{
-				ANKI_ASSERT(!colorRenderTargetHandles[i].isValid());
-			}
-			else
-			{
-				ANKI_ASSERT(colorRenderTargetHandles[i].isValid());
-			}
-		}
-
-		if(!fbInfo.m_depthStencilAttachment.m_aspect)
-		{
-			ANKI_ASSERT(!depthStencilRenderTargetHandle.isValid());
-		}
-		else
-		{
-			ANKI_ASSERT(depthStencilRenderTargetHandle.isValid());
-		}
-#endif
-
-		m_fbDescr = fbInfo;
-		memcpy(&m_rtHandles[0], &colorRenderTargetHandles[0], sizeof(colorRenderTargetHandles));
-		m_rtHandles[MAX_COLOR_ATTACHMENTS] = depthStencilRenderTargetHandle;
-		m_fbRenderArea = {{minx, miny, maxx, maxy}};
-	}
+		U32 minx,
+		U32 miny,
+		U32 maxx,
+		U32 maxy);
 
 private:
 	Array<RenderTargetHandle, MAX_COLOR_ATTACHMENTS + 1> m_rtHandles;
@@ -481,81 +452,26 @@ public:
 	{
 	}
 
-	~RenderGraphDescription()
-	{
-		for(RenderPassDescriptionBase* pass : m_passes)
-		{
-			m_alloc.deleteInstance(pass);
-		}
-		m_passes.destroy(m_alloc);
-		m_renderTargets.destroy(m_alloc);
-		m_buffers.destroy(m_alloc);
-	}
+	~RenderGraphDescription();
 
 	/// Create a new graphics render pass.
-	GraphicsRenderPassDescription& newGraphicsRenderPass(CString name)
-	{
-		GraphicsRenderPassDescription* pass = m_alloc.newInstance<GraphicsRenderPassDescription>(this);
-		pass->m_alloc = m_alloc;
-		pass->setName(name);
-		m_passes.emplaceBack(m_alloc, pass);
-		return *pass;
-	}
+	GraphicsRenderPassDescription& newGraphicsRenderPass(CString name);
 
 	/// Create a new compute render pass.
-	ComputeRenderPassDescription& newComputeRenderPass(CString name)
-	{
-		ComputeRenderPassDescription* pass = m_alloc.newInstance<ComputeRenderPassDescription>(this);
-		pass->m_alloc = m_alloc;
-		pass->setName(name);
-		m_passes.emplaceBack(m_alloc, pass);
-		return *pass;
-	}
+	ComputeRenderPassDescription& newComputeRenderPass(CString name);
 
-	/// Import an existing render target.
-	RenderTargetHandle importRenderTarget(TexturePtr tex, TextureUsageBit usage)
-	{
-		RT& rt = *m_renderTargets.emplaceBack(m_alloc);
-		rt.m_importedTex = tex;
-		rt.m_importedLastKnownUsage = usage;
-		rt.m_usageDerivedByDeps = TextureUsageBit::NONE;
-		rt.setName(tex->getName());
+	/// Import an existing render target and let the render graph know about it's up-to-date usage.
+	RenderTargetHandle importRenderTarget(TexturePtr tex, TextureUsageBit usage);
 
-		RenderTargetHandle out;
-		out.m_idx = m_renderTargets.getSize() - 1;
-		return out;
-	}
+	/// Import an existing render target and let the render graph find it's current usage by looking at the previous
+	/// frame.
+	RenderTargetHandle importRenderTarget(TexturePtr tex);
 
 	/// Get or create a new render target.
-	RenderTargetHandle newRenderTarget(const RenderTargetDescription& initInf)
-	{
-		ANKI_ASSERT(initInf.m_hash && "Forgot to call RenderTargetDescription::bake");
-		ANKI_ASSERT(
-			initInf.m_usage == TextureUsageBit::NONE && "Don't need to supply the usage. Render grap will find it");
-		RT& rt = *m_renderTargets.emplaceBack(m_alloc);
-		rt.m_initInfo = initInf;
-		rt.m_hash = initInf.m_hash;
-		rt.m_importedLastKnownUsage = TextureUsageBit::NONE;
-		rt.m_usageDerivedByDeps = TextureUsageBit::NONE;
-		rt.setName(initInf.getName());
-
-		RenderTargetHandle out;
-		out.m_idx = m_renderTargets.getSize() - 1;
-		return out;
-	}
+	RenderTargetHandle newRenderTarget(const RenderTargetDescription& initInf);
 
 	/// Import a buffer.
-	RenderPassBufferHandle importBuffer(BufferPtr buff, BufferUsageBit usage)
-	{
-		Buffer& b = *m_buffers.emplaceBack(m_alloc);
-		b.setName(buff->getName());
-		b.m_usage = usage;
-		b.m_importedBuff = buff;
-
-		RenderPassBufferHandle out;
-		out.m_idx = m_buffers.getSize() - 1;
-		return out;
-	}
+	RenderPassBufferHandle importBuffer(BufferPtr buff, BufferUsageBit usage);
 
 private:
 	class Resource
@@ -578,7 +494,8 @@ private:
 		U64 m_hash = 0;
 		TexturePtr m_importedTex;
 		TextureUsageBit m_importedLastKnownUsage;
-		TextureUsageBit m_usageDerivedByDeps; ///< XXX
+		TextureUsageBit m_usageDerivedByDeps; ///< Derived by the deps of this RT and will be used to set its usage.
+		Bool m_importedAndUndefinedUsage = false;
 	};
 
 	class Buffer : public Resource
@@ -645,6 +562,16 @@ public:
 	/// @}
 
 private:
+	static constexpr U PERIODIC_CLEANUP_EVERY = 60; ///< How many frames between cleanups.
+
+	// Forward declarations of internal classes.
+	class BakeContext;
+	class Pass;
+	class Batch;
+	class RT;
+	class Buffer;
+	class Barrier;
+
 	/// Render targets of the same type+size+format.
 	class RenderTargetCacheEntry
 	{
@@ -653,17 +580,16 @@ private:
 		U32 m_texturesInUse = 0;
 	};
 
-	HashMap<U64, RenderTargetCacheEntry> m_renderTargetCache; ///< Non-imported render targets.
+	/// Info on imported render targets that are kept between runs.
+	class ImportedRenderTargetInfo
+	{
+	public:
+		DynamicArray<TextureUsageBit> m_surfOrVolLastUsages; ///< Last TextureUsageBit of the imported RT.
+	};
 
+	HashMap<U64, RenderTargetCacheEntry> m_renderTargetCache; ///< Non-imported render targets.
 	HashMap<U64, FramebufferPtr> m_fbCache; ///< Framebuffer cache.
-
-	// Forward declarations
-	class BakeContext;
-	class Pass;
-	class Batch;
-	class RT;
-	class Buffer;
-	class Barrier;
+	HashMap<U64, ImportedRenderTargetInfo> m_importedRenderTargets;
 
 	BakeContext* m_ctx = nullptr;
 	U64 m_version = 0;
@@ -686,6 +612,9 @@ private:
 		CString name,
 		Bool& drawsToPresentableTex);
 
+	/// Every N number of frames clean unused cached items.
+	void periodicCleanup();
+
 	ANKI_HOT static Bool passADependsOnB(const RenderPassDescriptionBase& a, const RenderPassDescriptionBase& b);
 
 	static Bool overlappingTextureSubresource(const TextureSubresourceInfo& suba, const TextureSubresourceInfo& subb);

+ 127 - 0
src/anki/gr/RenderGraph.inl.h

@@ -128,4 +128,131 @@ inline void RenderPassDescriptionBase::newDependency(const RenderPassDependency&
 	}
 }
 
+inline void GraphicsRenderPassDescription::setFramebufferInfo(const FramebufferDescription& fbInfo,
+	const Array<RenderTargetHandle, MAX_COLOR_ATTACHMENTS>& colorRenderTargetHandles,
+	RenderTargetHandle depthStencilRenderTargetHandle,
+	U32 minx = 0,
+	U32 miny = 0,
+	U32 maxx = MAX_U32,
+	U32 maxy = MAX_U32)
+{
+#if ANKI_ASSERTS_ENABLED
+	ANKI_ASSERT(fbInfo.isBacked() && "Forgot call GraphicsRenderPassFramebufferInfo::bake");
+	for(U i = 0; i < colorRenderTargetHandles.getSize(); ++i)
+	{
+		if(i >= fbInfo.m_colorAttachmentCount)
+		{
+			ANKI_ASSERT(!colorRenderTargetHandles[i].isValid());
+		}
+		else
+		{
+			ANKI_ASSERT(colorRenderTargetHandles[i].isValid());
+		}
+	}
+
+	if(!fbInfo.m_depthStencilAttachment.m_aspect)
+	{
+		ANKI_ASSERT(!depthStencilRenderTargetHandle.isValid());
+	}
+	else
+	{
+		ANKI_ASSERT(depthStencilRenderTargetHandle.isValid());
+	}
+#endif
+
+	m_fbDescr = fbInfo;
+	memcpy(&m_rtHandles[0], &colorRenderTargetHandles[0], sizeof(colorRenderTargetHandles));
+	m_rtHandles[MAX_COLOR_ATTACHMENTS] = depthStencilRenderTargetHandle;
+	m_fbRenderArea = {{minx, miny, maxx, maxy}};
+}
+
+inline RenderGraphDescription::~RenderGraphDescription()
+{
+	for(RenderPassDescriptionBase* pass : m_passes)
+	{
+		m_alloc.deleteInstance(pass);
+	}
+	m_passes.destroy(m_alloc);
+	m_renderTargets.destroy(m_alloc);
+	m_buffers.destroy(m_alloc);
+}
+
+inline GraphicsRenderPassDescription& RenderGraphDescription::newGraphicsRenderPass(CString name)
+{
+	GraphicsRenderPassDescription* pass = m_alloc.newInstance<GraphicsRenderPassDescription>(this);
+	pass->m_alloc = m_alloc;
+	pass->setName(name);
+	m_passes.emplaceBack(m_alloc, pass);
+	return *pass;
+}
+
+inline ComputeRenderPassDescription& RenderGraphDescription::newComputeRenderPass(CString name)
+{
+	ComputeRenderPassDescription* pass = m_alloc.newInstance<ComputeRenderPassDescription>(this);
+	pass->m_alloc = m_alloc;
+	pass->setName(name);
+	m_passes.emplaceBack(m_alloc, pass);
+	return *pass;
+}
+
+inline RenderTargetHandle RenderGraphDescription::importRenderTarget(TexturePtr tex, TextureUsageBit usage)
+{
+	for(const RT& rt : m_renderTargets)
+	{
+		(void)rt;
+		ANKI_ASSERT(rt.m_importedTex != tex && "Already imported");
+	}
+
+	RT& rt = *m_renderTargets.emplaceBack(m_alloc);
+	rt.m_importedTex = tex;
+	rt.m_importedLastKnownUsage = usage;
+	rt.m_usageDerivedByDeps = TextureUsageBit::NONE;
+	rt.setName(tex->getName());
+
+	RenderTargetHandle out;
+	out.m_idx = m_renderTargets.getSize() - 1;
+	return out;
+}
+
+inline RenderTargetHandle RenderGraphDescription::importRenderTarget(TexturePtr tex)
+{
+	RenderTargetHandle out = importRenderTarget(tex, TextureUsageBit::NONE);
+	m_renderTargets.getBack().m_importedAndUndefinedUsage = true;
+	return out;
+}
+
+inline RenderTargetHandle RenderGraphDescription::newRenderTarget(const RenderTargetDescription& initInf)
+{
+	ANKI_ASSERT(initInf.m_hash && "Forgot to call RenderTargetDescription::bake");
+	ANKI_ASSERT(initInf.m_usage == TextureUsageBit::NONE && "Don't need to supply the usage. Render grap will find it");
+	RT& rt = *m_renderTargets.emplaceBack(m_alloc);
+	rt.m_initInfo = initInf;
+	rt.m_hash = initInf.m_hash;
+	rt.m_importedLastKnownUsage = TextureUsageBit::NONE;
+	rt.m_usageDerivedByDeps = TextureUsageBit::NONE;
+	rt.setName(initInf.getName());
+
+	RenderTargetHandle out;
+	out.m_idx = m_renderTargets.getSize() - 1;
+	return out;
+}
+
+inline RenderPassBufferHandle RenderGraphDescription::importBuffer(BufferPtr buff, BufferUsageBit usage)
+{
+	for(const Buffer& bb : m_buffers)
+	{
+		(void)bb;
+		ANKI_ASSERT(bb.m_importedBuff != buff && "Already imported");
+	}
+
+	Buffer& b = *m_buffers.emplaceBack(m_alloc);
+	b.setName(buff->getName());
+	b.m_usage = usage;
+	b.m_importedBuff = buff;
+
+	RenderPassBufferHandle out;
+	out.m_idx = m_buffers.getSize() - 1;
+	return out;
+}
+
 } // end namespace anki

+ 28 - 21
src/anki/gr/ShaderCompiler.cpp

@@ -65,6 +65,7 @@ static const char* SHADER_HEADER = R"(#version 450 core
 #	endif
 #	extension GL_EXT_samplerless_texture_functions : require
 #	extension GL_EXT_shader_image_load_formatted : require
+#	extension GL_EXT_nonuniform_qualifier : enable
 #
 #	define ANKI_MAX_BINDLESS_TEXTURES %u
 #	define ANKI_MAX_BINDLESS_IMAGES %u
@@ -381,7 +382,10 @@ Error ShaderCompiler::preprocess(
 	return Error::NONE;
 }
 
-Error ShaderCompiler::compile(CString source, const ShaderCompilerOptions& options, DynamicArrayAuto<U8>& bin) const
+Error ShaderCompiler::compile(CString source,
+	const ShaderCompilerOptions& options,
+	DynamicArrayAuto<U8>& bin,
+	CString finalSourceDumpFilename) const
 {
 	ANKI_ASSERT(!source.isEmpty() && source.getLength() > 0);
 	Error err = Error::NONE;
@@ -420,23 +424,13 @@ Error ShaderCompiler::compile(CString source, const ShaderCompilerOptions& optio
 		ANKI_CHECK(genSpirv(finalSrc.toCString(), options, bin));
 	}
 
-#if 0
 	// Dump
+	if(finalSourceDumpFilename)
 	{
-		static I id = 0;
-
-		String homeDir;
-		ANKI_CHECK(getHomeDirectory(m_alloc, homeDir));
-
 		File file;
-		ANKI_CHECK(
-			file.open(StringAuto(m_alloc).sprintf("%s/.anki/cache/%d.dump.glsl", homeDir.cstr(), id++).toCString(),
-				FileOpenFlag::WRITE));
-		ANKI_CHECK(file.write(finalSrc.cstr(), finalSrc.getLength() + 1));
-
-		homeDir.destroy(m_alloc);
+		ANKI_CHECK(file.open(finalSourceDumpFilename, FileOpenFlag::WRITE));
+		ANKI_CHECK(file.write(finalSrc.cstr(), finalSrc.getLength() - 1));
 	}
-#endif
 
 	return err;
 }
@@ -477,10 +471,13 @@ void ShaderCompiler::logShaderErrorCode(CString error, CString source, GenericMe
 		&error[0]);
 }
 
-Error ShaderCompilerCache::compile(
-	CString source, U64* hash, const ShaderCompilerOptions& options, DynamicArrayAuto<U8>& bin) const
+Error ShaderCompilerCache::compile(CString source,
+	U64* hash,
+	const ShaderCompilerOptions& options,
+	DynamicArrayAuto<U8>& bin,
+	Bool dumpShaderSource) const
 {
-	Error err = compileInternal(source, hash, options, bin);
+	const Error err = compileInternal(source, hash, options, bin, dumpShaderSource);
 	if(err)
 	{
 		ANKI_GR_LOGE("Failed to compile or retrieve shader from the cache");
@@ -489,8 +486,11 @@ Error ShaderCompilerCache::compile(
 	return err;
 }
 
-Error ShaderCompilerCache::compileInternal(
-	CString source, U64* hash, const ShaderCompilerOptions& options, DynamicArrayAuto<U8>& bin) const
+Error ShaderCompilerCache::compileInternal(CString source,
+	U64* hash,
+	const ShaderCompilerOptions& options,
+	DynamicArrayAuto<U8>& bin,
+	Bool dumpShaderSource) const
 {
 	ANKI_ASSERT(!source.isEmpty() && source.getLength() > 0);
 
@@ -522,11 +522,18 @@ Error ShaderCompilerCache::compileInternal(
 	}
 	else
 	{
-		ANKI_CHECK(m_compiler.compile(source, options, bin));
+		ANKI_GR_LOGI("%s not found in cache. Will compile", fname.cstr());
+
+		StringAuto dumpSrcFname(m_alloc);
+		if(dumpShaderSource)
+		{
+			dumpSrcFname.sprintf("%s/%llu.glsl", m_cacheDir.cstr(), fhash);
+		}
+
+		ANKI_CHECK(m_compiler.compile(source, options, bin, (dumpSrcFname) ? dumpSrcFname.toCString() : CString()));
 
 		File file;
 		ANKI_CHECK(file.open(fname.toCString(), FileOpenFlag::WRITE | FileOpenFlag::BINARY));
-
 		ANKI_CHECK(file.write(&bin[0], bin.getSize()));
 	}
 

+ 16 - 6
src/anki/gr/ShaderCompiler.h

@@ -56,8 +56,11 @@ public:
 	/// @param source The source in GLSL.
 	/// @param options Compile options.
 	/// @param bin The output binary.
-	ANKI_USE_RESULT Error compile(
-		CString source, const ShaderCompilerOptions& options, DynamicArrayAuto<U8>& bin) const;
+	/// @param finalSourceDumpFilename Write the final source to this filename if it's not empty.
+	ANKI_USE_RESULT Error compile(CString source,
+		const ShaderCompilerOptions& options,
+		DynamicArrayAuto<U8>& bin,
+		CString finalSourceDumpFilename = {}) const;
 
 	ANKI_USE_RESULT Error preprocess(
 		CString source, const ShaderCompilerOptions& options, const StringList& defines, StringAuto& out) const;
@@ -99,16 +102,23 @@ public:
 	/// @param sourceHash Optional hash of the source. If it's nullptr then the @a source will be hashed.
 	/// @param options Compile options.
 	/// @param bin The output binary.
-	ANKI_USE_RESULT Error compile(
-		CString source, U64* hash, const ShaderCompilerOptions& options, DynamicArrayAuto<U8>& bin) const;
+	/// @param dumpShaderSource If true dump the shaders' source as well.
+	ANKI_USE_RESULT Error compile(CString source,
+		U64* hash,
+		const ShaderCompilerOptions& options,
+		DynamicArrayAuto<U8>& bin,
+		Bool dumpShaderSource = false) const;
 
 private:
 	GenericMemoryPoolAllocator<U8> m_alloc;
 	ShaderCompiler m_compiler;
 	String m_cacheDir;
 
-	ANKI_USE_RESULT Error compileInternal(
-		CString source, U64* hash, const ShaderCompilerOptions& options, DynamicArrayAuto<U8>& bin) const;
+	ANKI_USE_RESULT Error compileInternal(CString source,
+		U64* hash,
+		const ShaderCompilerOptions& options,
+		DynamicArrayAuto<U8>& bin,
+		Bool dumpShaderSource) const;
 };
 /// @}
 

+ 12 - 4
src/anki/gr/vulkan/DescriptorSet.cpp

@@ -277,8 +277,9 @@ void DSThreadAllocator::writeSet(const Array<AnyBindingExtended, MAX_BINDINGS_PE
 	{
 		if(m_layoutEntry->m_activeBindings.get(bindingIdx))
 		{
-			for(U arrIdx = 0; arrIdx < bindings[bindingIdx].m_arraySize; ++arrIdx)
+			for(U arrIdx = 0; arrIdx < m_layoutEntry->m_bindingArraySize[bindingIdx]; ++arrIdx)
 			{
+				ANKI_ASSERT(bindings[bindingIdx].m_arraySize >= m_layoutEntry->m_bindingArraySize[bindingIdx]);
 				const AnyBinding& b = (bindings[bindingIdx].m_arraySize == 1) ? bindings[bindingIdx].m_single
 																			  : bindings[bindingIdx].m_array[arrIdx];
 
@@ -346,7 +347,7 @@ void DSThreadAllocator::writeSet(const Array<AnyBindingExtended, MAX_BINDINGS_PE
 	{
 		if(m_layoutEntry->m_activeBindings.get(bindingIdx))
 		{
-			for(U arrIdx = 0; arrIdx < bindings[bindingIdx].m_arraySize; ++arrIdx)
+			for(U arrIdx = 0; arrIdx < m_layoutEntry->m_bindingArraySize[bindingIdx]; ++arrIdx)
 			{
 				const AnyBinding& b = (bindings[bindingIdx].m_arraySize == 1) ? bindings[bindingIdx].m_single
 																			  : bindings[bindingIdx].m_array[arrIdx];
@@ -445,7 +446,7 @@ Error DSLayoutCacheEntry::init(const DescriptorBinding* bindings, U bindingCount
 		{
 			if(m_poolSizesCreateInf[j].type == convertDescriptorType(bindings[i].m_type))
 			{
-				++m_poolSizesCreateInf[j].descriptorCount;
+				m_poolSizesCreateInf[j].descriptorCount += bindings[i].m_arraySizeMinusOne + 1;
 				break;
 			}
 		}
@@ -453,7 +454,7 @@ Error DSLayoutCacheEntry::init(const DescriptorBinding* bindings, U bindingCount
 		if(j == poolSizeCount)
 		{
 			m_poolSizesCreateInf[poolSizeCount].type = convertDescriptorType(bindings[i].m_type);
-			m_poolSizesCreateInf[poolSizeCount].descriptorCount = 1;
+			m_poolSizesCreateInf[poolSizeCount].descriptorCount = bindings[i].m_arraySizeMinusOne + 1;
 			++poolSizeCount;
 		}
 	}
@@ -979,6 +980,13 @@ Error BindlessDescriptorSet::initDeviceFeatures(
 
 	vkGetPhysicalDeviceFeatures2(pdev, &features);
 
+	if(!indexingFeatures.shaderSampledImageArrayNonUniformIndexing
+		|| !indexingFeatures.shaderStorageImageArrayNonUniformIndexing)
+	{
+		ANKI_VK_LOGE("Non uniform indexing is not supported by the device");
+		return Error::FUNCTION_FAILED;
+	}
+
 	if(!indexingFeatures.descriptorBindingSampledImageUpdateAfterBind
 		|| !indexingFeatures.descriptorBindingStorageImageUpdateAfterBind)
 	{

+ 1 - 7
src/anki/gr/vulkan/GrManagerImpl.cpp

@@ -232,13 +232,7 @@ Error GrManagerImpl::initInstance(const GrManagerInitInfo& init)
 	ci.pApplicationInfo = &app;
 
 	// Layers
-	static Array<const char*, 7> LAYERS = {{"VK_LAYER_LUNARG_core_validation",
-		"VK_LAYER_LUNARG_swapchain",
-		"VK_LAYER_GOOGLE_threading",
-		"VK_LAYER_LUNARG_parameter_validation",
-		"VK_LAYER_LUNARG_object_tracker",
-		"VK_LAYER_LUNARG_standard_validation",
-		"VK_LAYER_GOOGLE_unique_objects"}};
+	static Array<const char*, 1> LAYERS = {{"VK_LAYER_KHRONOS_validation"}};
 	Array<const char*, LAYERS.getSize()> layersToEnable; // Keep it alive in the stack
 	if(init.m_config->getNumber("window.debugContext"))
 	{

+ 18 - 0
src/anki/gr/vulkan/TextureImpl.cpp

@@ -708,4 +708,22 @@ const MicroImageView& TextureImpl::getOrCreateView(const TextureSubresourceInfo&
 	return *it;
 }
 
+TextureType TextureImpl::computeNewTexTypeOfSubresource(const TextureSubresourceInfo& subresource) const
+{
+	ANKI_ASSERT(isSubresourceValid(subresource));
+	if(textureTypeIsCube(m_texType))
+	{
+		if(subresource.m_faceCount != 6)
+		{
+			ANKI_ASSERT(subresource.m_faceCount == 1);
+			return (subresource.m_layerCount > 1) ? TextureType::_2D_ARRAY : TextureType::_2D;
+		}
+		else if(subresource.m_layerCount == 1)
+		{
+			return TextureType::CUBE;
+		}
+	}
+	return m_texType;
+}
+
 } // end namespace anki

+ 2 - 10
src/anki/gr/vulkan/TextureImpl.h

@@ -174,11 +174,7 @@ public:
 
 		// Fixup the image view type
 		newTextureType = computeNewTexTypeOfSubresource(subresource);
-		if(newTextureType == TextureType::_2D)
-		{
-			// Change that anyway
-			viewCi.viewType = VK_IMAGE_VIEW_TYPE_2D;
-		}
+		viewCi.viewType = convertTextureViewType(newTextureType);
 	}
 
 	const MicroImageView& getOrCreateView(const TextureSubresourceInfo& subresource) const;
@@ -209,11 +205,7 @@ private:
 	void updateUsageState(TextureUsageBit usage, StackAllocator<U8>& alloc, TextureUsageState& state) const;
 
 	/// Compute the new type of a texture view.
-	TextureType computeNewTexTypeOfSubresource(const TextureSubresourceInfo& subresource) const
-	{
-		ANKI_ASSERT(isSubresourceValid(subresource));
-		return (textureTypeIsCube(m_texType) && subresource.m_faceCount != 6) ? TextureType::_2D : m_texType;
-	}
+	TextureType computeNewTexTypeOfSubresource(const TextureSubresourceInfo& subresource) const;
 
 	ANKI_USE_RESULT Error initInternal(VkImage externalImage, const TextureInitInfo& init);
 };

+ 13 - 8
src/anki/math/Vec.h

@@ -28,11 +28,13 @@ public:
 
 	/// @name Constructors
 	/// @{
+
+	/// Defaut constructor. IT WILL NOT INITIALIZE ANYTHING.
 	TVec()
 	{
 	}
 
-	// Copy
+	/// Copy.
 	TVec(ANKI_ENABLE_ARG(const TVec&, !HAS_VEC4_SIMD) b)
 	{
 		for(U i = 0; i < N; i++)
@@ -41,14 +43,15 @@ public:
 		}
 	}
 
-	// Copy
+	/// Copy.
 	TVec(ANKI_ENABLE_ARG(const TVec&, HAS_VEC4_SIMD) b)
 	{
 		m_simd = b.m_simd;
 	}
 
-	template<typename Y>
-	TVec(const TVec<Y, N>& b)
+	/// Convert from another type.
+	template<typename Y, ANKI_ENABLE(!std::is_same<Y, T>::value)>
+	explicit TVec(const TVec<Y, N>& b)
 	{
 		for(U i = 0; i < N; i++)
 		{
@@ -2808,11 +2811,13 @@ public:
 		ANKI_ASSERT(w() == T(0));
 		ANKI_ASSERT(b.w() == T(0));
 		const auto& a = *this;
-		const int mask0 = _MM_SHUFFLE(3, 0, 2, 1);
-		const int mask1 = _MM_SHUFFLE(3, 1, 0, 2);
+		constexpr unsigned int mask0 = _MM_SHUFFLE(3, 0, 2, 1);
+		constexpr unsigned int mask1 = _MM_SHUFFLE(3, 1, 0, 2);
 
-		__m128 tmp0 = _mm_mul_ps(_mm_shuffle_ps(a.m_simd, a.m_simd, mask0), _mm_shuffle_ps(b.m_simd, b.m_simd, mask1));
-		__m128 tmp1 = _mm_mul_ps(_mm_shuffle_ps(a.m_simd, a.m_simd, mask1), _mm_shuffle_ps(b.m_simd, b.m_simd, mask0));
+		const __m128 tmp0 =
+			_mm_mul_ps(_mm_shuffle_ps(a.m_simd, a.m_simd, U8(mask0)), _mm_shuffle_ps(b.m_simd, b.m_simd, U8(mask1)));
+		const __m128 tmp1 =
+			_mm_mul_ps(_mm_shuffle_ps(a.m_simd, a.m_simd, U8(mask1)), _mm_shuffle_ps(b.m_simd, b.m_simd, U8(mask0)));
 
 		return TVec(_mm_sub_ps(tmp0, tmp1));
 	}

+ 64 - 8
src/anki/renderer/ClusterBin.cpp

@@ -336,7 +336,7 @@ void ClusterBin::binTile(U32 tileIdx, BinCtx& ctx, TileCtx& tileCtx)
 
 #define ANKI_SET_IDX(typeIdx) \
 	ClusterBin::TileCtx::ClusterMetaInfo& inf = tileCtx.m_clusterInfos[clusterZ]; \
-	if(ANKI_UNLIKELY(inf.m_offset + 1 >= m_avgObjectsPerCluster)) \
+	if(ANKI_UNLIKELY(U32(inf.m_offset) + 1 >= m_avgObjectsPerCluster)) \
 	{ \
 		ANKI_R_LOGW("Out of cluster indices. Increase r.avgObjectsPerCluster"); \
 		continue; \
@@ -431,6 +431,32 @@ void ClusterBin::binTile(U32 tileIdx, BinCtx& ctx, TileCtx& tileCtx)
 		}
 	}
 
+	// GI probes
+	{
+		Aabb probeBox;
+		for(U i = 0; i < ctx.m_in->m_renderQueue->m_giProbes.getSize(); ++i)
+		{
+			const GlobalIlluminationProbeQueueElement& probe = ctx.m_in->m_renderQueue->m_giProbes[i];
+			probeBox.setMin(probe.m_aabbMin);
+			probeBox.setMax(probe.m_aabbMax);
+
+			if(!insideClusterFrustum(frustumPlanes, probeBox))
+			{
+				continue;
+			}
+
+			for(U clusterZ = 0; clusterZ < m_clusterCounts[2]; ++clusterZ)
+			{
+				if(!testCollision(probeBox, clusterBoxes[clusterZ]))
+				{
+					continue;
+				}
+
+				ANKI_SET_IDX(3);
+			}
+		}
+	}
+
 	// Decals
 	{
 		Obb decalBox;
@@ -453,7 +479,7 @@ void ClusterBin::binTile(U32 tileIdx, BinCtx& ctx, TileCtx& tileCtx)
 					continue;
 				}
 
-				ANKI_SET_IDX(3);
+				ANKI_SET_IDX(4);
 			}
 		}
 	}
@@ -482,7 +508,7 @@ void ClusterBin::binTile(U32 tileIdx, BinCtx& ctx, TileCtx& tileCtx)
 						continue;
 					}
 
-					ANKI_SET_IDX(4);
+					ANKI_SET_IDX(5);
 				}
 			}
 			else
@@ -503,7 +529,7 @@ void ClusterBin::binTile(U32 tileIdx, BinCtx& ctx, TileCtx& tileCtx)
 						continue;
 					}
 
-					ANKI_SET_IDX(4);
+					ANKI_SET_IDX(5);
 				}
 			}
 		}
@@ -698,8 +724,10 @@ void ClusterBin::writeTypedObjectsToGpuBuffers(BinCtx& ctx) const
 	const U visibleProbeCount = rqueue.m_reflectionProbes.getSize();
 	if(visibleProbeCount)
 	{
-		ReflectionProbe* data = static_cast<ReflectionProbe*>(ctx.m_in->m_stagingMem->allocateFrame(
-			sizeof(ReflectionProbe) * visibleProbeCount, StagingGpuMemoryType::UNIFORM, ctx.m_out->m_probesToken));
+		ReflectionProbe* data = static_cast<ReflectionProbe*>(
+			ctx.m_in->m_stagingMem->allocateFrame(sizeof(ReflectionProbe) * visibleProbeCount,
+				StagingGpuMemoryType::UNIFORM,
+				ctx.m_out->m_reflectionProbesToken));
 
 		WeakArray<ReflectionProbe> gpuProbes(data, visibleProbeCount);
 
@@ -716,7 +744,7 @@ void ClusterBin::writeTypedObjectsToGpuBuffers(BinCtx& ctx) const
 	}
 	else
 	{
-		ctx.m_out->m_probesToken.markUnused();
+		ctx.m_out->m_reflectionProbesToken.markUnused();
 	}
 
 	// Fog volumes
@@ -754,6 +782,34 @@ void ClusterBin::writeTypedObjectsToGpuBuffers(BinCtx& ctx) const
 	{
 		ctx.m_out->m_fogDensityVolumesToken.markUnused();
 	}
+
+	// Write the probes
+	const U visibleGiProbeCount = rqueue.m_giProbes.getSize();
+	if(visibleGiProbeCount)
+	{
+		GlobalIlluminationProbe* data = static_cast<GlobalIlluminationProbe*>(
+			ctx.m_in->m_stagingMem->allocateFrame(sizeof(GlobalIlluminationProbe) * visibleGiProbeCount,
+				StagingGpuMemoryType::UNIFORM,
+				ctx.m_out->m_globalIlluminationProbesToken));
+
+		WeakArray<GlobalIlluminationProbe> gpuProbes(data, visibleGiProbeCount);
+
+		for(U i = 0; i < visibleGiProbeCount; ++i)
+		{
+			const GlobalIlluminationProbeQueueElement& in = rqueue.m_giProbes[i];
+			GlobalIlluminationProbe& out = gpuProbes[i];
+
+			out.m_aabbMin = in.m_aabbMin;
+			out.m_aabbMax = in.m_aabbMax;
+			out.m_textureIndex = &in - &rqueue.m_giProbes.getFront();
+			out.m_halfTexelSizeU = 1.0f / in.m_cellCounts.x() / 2.0f;
+			out.m_fadeDistance = in.m_fadeDistance;
+		}
+	}
+	else
+	{
+		ctx.m_out->m_globalIlluminationProbesToken.markUnused();
+	}
 }
 
-} // end namespace anki
+} // end namespace anki

+ 3 - 2
src/anki/renderer/ClusterBin.h

@@ -38,9 +38,10 @@ class ClusterBinOut
 public:
 	StagingGpuMemoryToken m_pointLightsToken;
 	StagingGpuMemoryToken m_spotLightsToken;
-	StagingGpuMemoryToken m_probesToken;
+	StagingGpuMemoryToken m_reflectionProbesToken;
 	StagingGpuMemoryToken m_decalsToken;
 	StagingGpuMemoryToken m_fogDensityVolumesToken;
+	StagingGpuMemoryToken m_globalIlluminationProbesToken;
 	StagingGpuMemoryToken m_clustersToken;
 	StagingGpuMemoryToken m_indicesToken;
 
@@ -82,4 +83,4 @@ private:
 };
 /// @}
 
-} // end namespace anki
+} // end namespace anki

+ 1 - 1
src/anki/renderer/Common.cpp

@@ -8,7 +8,7 @@
 namespace anki
 {
 
-const Array<Format, GBUFFER_COLOR_ATTACHMENT_COUNT> MS_COLOR_ATTACHMENT_PIXEL_FORMATS = {
+const Array<Format, GBUFFER_COLOR_ATTACHMENT_COUNT> GBUFFER_COLOR_ATTACHMENT_PIXEL_FORMATS = {
 	{Format::R8G8B8A8_UNORM, Format::R8G8B8A8_UNORM, Format::A2B10G10R10_UNORM_PACK32, Format::R16G16_SNORM}};
 
 } // end namespace anki

+ 65 - 2
src/anki/renderer/Common.h

@@ -30,7 +30,7 @@ class Tonemapping;
 class Bloom;
 class FinalComposite;
 class Dbg;
-class Indirect;
+class ProbeReflections;
 class DownscaleBlur;
 class VolumetricFog;
 class DepthDownscale;
@@ -38,6 +38,7 @@ class TemporalAA;
 class UiStage;
 class Ssr;
 class VolumetricLightingAccumulation;
+class GlobalIllumination;
 
 class RenderingContext;
 class DebugDrawer;
@@ -95,7 +96,7 @@ const U DOWNSCALE_BLUR_DOWN_TO = 32;
 /// Use this size of render target for the avg lum calculation.
 const U AVERAGE_LUMINANCE_RENDER_TARGET_SIZE = 128;
 
-extern const Array<Format, GBUFFER_COLOR_ATTACHMENT_COUNT> MS_COLOR_ATTACHMENT_PIXEL_FORMATS;
+extern const Array<Format, GBUFFER_COLOR_ATTACHMENT_COUNT> GBUFFER_COLOR_ATTACHMENT_PIXEL_FORMATS;
 
 const Format GBUFFER_DEPTH_ATTACHMENT_PIXEL_FORMAT = Format::D32_SFLOAT;
 
@@ -107,6 +108,68 @@ const Format DBG_COLOR_ATTACHMENT_PIXEL_FORMAT = Format::R8G8B8_UNORM;
 
 const Format SHADOW_DEPTH_PIXEL_FORMAT = Format::D32_SFLOAT;
 const Format SHADOW_COLOR_PIXEL_FORMAT = Format::R16_UNORM;
+
+/// A convenience function to find empty cache entries. Used for various probes.
+template<typename THashMap, typename TCacheEntryArray, typename TAlloc>
+U32 findBestCacheEntry(U64 uuid, Timestamp crntTimestamp, const TCacheEntryArray& entries, THashMap& map, TAlloc alloc)
+{
+	ANKI_ASSERT(uuid > 0);
+
+	// First, try to see if the UUID is in the cache
+	auto it = map.find(uuid);
+	if(ANKI_LIKELY(it != map.getEnd()))
+	{
+		const U32 cacheEntryIdx = *it;
+		if(ANKI_LIKELY(entries[cacheEntryIdx].m_uuid == uuid))
+		{
+			// Found it
+			return cacheEntryIdx;
+		}
+		else
+		{
+			// Cache entry is wrong, remove it
+			map.erase(alloc, it);
+		}
+	}
+
+	// 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 < entries.getSize(); ++cacheEntryIdx)
+	{
+		if(entries[cacheEntryIdx].m_uuid == 0)
+		{
+			// Found an empty
+			emptyCacheEntryIdx = cacheEntryIdx;
+			break;
+		}
+		else if(entries[cacheEntryIdx].m_lastUsedTimestamp != crntTimestamp
+				&& entries[cacheEntryIdx].m_lastUsedTimestamp < cacheEntryIdxToKickMinTimestamp)
+		{
+			// Found some with low timestamp
+			cacheEntryIdxToKick = cacheEntryIdx;
+			cacheEntryIdxToKickMinTimestamp = entries[cacheEntryIdx].m_lastUsedTimestamp;
+		}
+	}
+
+	U32 outCacheEntryIdx;
+	if(emptyCacheEntryIdx != MAX_U32)
+	{
+		outCacheEntryIdx = emptyCacheEntryIdx;
+	}
+	else if(cacheEntryIdxToKick != MAX_U32)
+	{
+		outCacheEntryIdx = cacheEntryIdxToKick;
+	}
+	else
+	{
+		// We are out of cache entries. Return OOM
+		outCacheEntryIdx = MAX_U32;
+	}
+
+	return outCacheEntryIdx;
+}
 /// @}
 
 } // end namespace anki

+ 22 - 5
src/anki/renderer/Dbg.cpp

@@ -70,7 +70,7 @@ void Dbg::run(RenderPassWorkContext& rgraphCtx, const RenderingContext& ctx)
 	RenderQueueDrawContext dctx;
 	dctx.m_viewMatrix = ctx.m_renderQueue->m_viewMatrix;
 	dctx.m_viewProjectionMatrix = ctx.m_renderQueue->m_viewProjectionMatrix;
-	dctx.m_projectionMatrix = Mat4::getIdentity(); // TODO
+	dctx.m_projectionMatrix = ctx.m_renderQueue->m_projectionMatrix;
 	dctx.m_cameraTransform = ctx.m_renderQueue->m_viewMatrix.getInverse();
 	dctx.m_stagingGpuAllocator = &m_r->getStagingGpuMemoryManager();
 	dctx.m_commandBuffer = cmdb;
@@ -79,11 +79,28 @@ void Dbg::run(RenderPassWorkContext& rgraphCtx, const RenderingContext& ctx)
 	dctx.m_debugDraw = true;
 	dctx.m_debugDrawFlags = m_debugDrawFlags;
 
-	// Draw
-	for(const RenderableQueueElement& el : ctx.m_renderQueue->m_renderables)
+	// Draw renderables
+	const U threadId = rgraphCtx.m_currentSecondLevelCommandBufferIndex;
+	const U threadCount = rgraphCtx.m_secondLevelCommandBufferCount;
+	const U problemSize = ctx.m_renderQueue->m_renderables.getSize();
+	U32 start, end;
+	splitThreadedProblem(threadId, threadCount, problemSize, start, end);
+
+	for(U i = start; i < end; ++i)
 	{
+		const RenderableQueueElement& el = ctx.m_renderQueue->m_renderables[i];
 		Array<void*, 1> a = {{const_cast<void*>(el.m_userData)}};
-		el.m_callback(dctx, {&a[0], 1});
+		el.m_callback(dctx, a);
+	}
+
+	// Draw probes
+	if(threadId == 0)
+	{
+		for(const GlobalIlluminationProbeQueueElement& el : ctx.m_renderQueue->m_giProbes)
+		{
+			Array<void*, 1> a = {{const_cast<void*>(el.m_userData)}};
+			el.m_debugDrawCallback(dctx, a);
+		}
 	}
 }
 
@@ -113,7 +130,7 @@ void Dbg::populateRenderGraph(RenderingContext& ctx)
 			self->run(rgraphCtx, *self->m_runCtx.m_ctx);
 		},
 		this,
-		0);
+		computeNumberOfSecondLevelCommandBuffers(ctx.m_renderQueue->m_renderables.getSize()));
 	pass.setFramebufferInfo(m_fbDescr, {{m_runCtx.m_rt}}, m_r->getGBuffer().getDepthRt());
 
 	pass.newDependency({m_runCtx.m_rt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE});

+ 8 - 10
src/anki/renderer/Drawer.cpp

@@ -26,6 +26,7 @@ public:
 	Array<U8, MAX_INSTANCES> m_cachedRenderElementLods;
 	Array<const void*, MAX_INSTANCES> m_userData;
 	U m_cachedRenderElementCount = 0;
+	U m_minLod = 0;
 };
 
 /// Check if the drawcalls can be merged.
@@ -45,7 +46,8 @@ void RenderableDrawer::drawRange(Pass pass,
 	CommandBufferPtr cmdb,
 	SamplerPtr sampler,
 	const RenderableQueueElement* begin,
-	const RenderableQueueElement* end)
+	const RenderableQueueElement* end,
+	U minLod)
 {
 	ANKI_ASSERT(begin && end && begin < end);
 
@@ -61,6 +63,9 @@ void RenderableDrawer::drawRange(Pass pass,
 	ctx.m_queueCtx.m_key = RenderingKey(pass, 0, 1, false, false);
 	ctx.m_queueCtx.m_debugDraw = false;
 
+	ANKI_ASSERT(minLod < MAX_LOD_COUNT);
+	ctx.m_minLod = minLod;
+
 	for(; begin != end; ++begin)
 	{
 		ctx.m_renderableElement = begin;
@@ -97,15 +102,8 @@ void RenderableDrawer::drawSingle(DrawContext& ctx)
 
 	const RenderableQueueElement& rqel = *ctx.m_renderableElement;
 
-	U8 lod;
-	if(ctx.m_queueCtx.m_key.m_pass == Pass::SM)
-	{
-		lod = MAX_LOD_COUNT - 1;
-	}
-	else
-	{
-		lod = min<U8>(m_r->calculateLod(rqel.m_distanceFromCamera), MAX_LOD_COUNT - 1);
-	}
+	U8 lod = min<U8>(m_r->calculateLod(rqel.m_distanceFromCamera), MAX_LOD_COUNT - 1);
+	lod = max<U8>(lod, ctx.m_minLod);
 
 	const Bool shouldFlush =
 		ctx.m_cachedRenderElementCount > 0

+ 2 - 1
src/anki/renderer/Drawer.h

@@ -39,7 +39,8 @@ public:
 		CommandBufferPtr cmdb,
 		SamplerPtr sampler,
 		const RenderableQueueElement* begin,
-		const RenderableQueueElement* end);
+		const RenderableQueueElement* end,
+		U minLod = 0);
 
 private:
 	Renderer* m_r;

+ 1 - 1
src/anki/renderer/ForwardShading.cpp

@@ -32,7 +32,7 @@ void ForwardShading::run(const RenderingContext& ctx, RenderPassWorkContext& rgr
 	const U threadId = rgraphCtx.m_currentSecondLevelCommandBufferIndex;
 	const U threadCount = rgraphCtx.m_secondLevelCommandBufferCount;
 	const U problemSize = ctx.m_renderQueue->m_forwardShadingRenderables.getSize();
-	PtrSize start, end;
+	U start, end;
 	splitThreadedProblem(threadId, threadCount, problemSize, start, end);
 
 	if(start != end)

+ 1 - 1
src/anki/renderer/GBuffer.cpp

@@ -43,7 +43,7 @@ Error GBuffer::initInternal(const ConfigSet& initializer)
 	for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
 	{
 		m_colorRtDescrs[i] = m_r->create2DRenderTargetDescription(
-			m_r->getWidth(), m_r->getHeight(), MS_COLOR_ATTACHMENT_PIXEL_FORMATS[i], rtNames[i]);
+			m_r->getWidth(), m_r->getHeight(), GBUFFER_COLOR_ATTACHMENT_PIXEL_FORMATS[i], rtNames[i]);
 		m_colorRtDescrs[i].bake();
 	}
 

+ 2 - 2
src/anki/renderer/GBufferPost.cpp

@@ -96,11 +96,11 @@ void GBufferPost::run(RenderPassWorkContext& rgraphCtx)
 
 	cmdb->bindTexture(0,
 		5,
-		(rsrc.m_diffDecalTexView) ? rsrc.m_diffDecalTexView : m_r->getDummyTextureView(),
+		(rsrc.m_diffDecalTexView) ? rsrc.m_diffDecalTexView : m_r->getDummyTextureView2d(),
 		TextureUsageBit::SAMPLED_FRAGMENT);
 	cmdb->bindTexture(0,
 		6,
-		(rsrc.m_specularRoughnessDecalTexView) ? rsrc.m_specularRoughnessDecalTexView : m_r->getDummyTextureView(),
+		(rsrc.m_specularRoughnessDecalTexView) ? rsrc.m_specularRoughnessDecalTexView : m_r->getDummyTextureView2d(),
 		TextureUsageBit::SAMPLED_FRAGMENT);
 
 	bindStorage(cmdb, 0, 7, rsrc.m_clustersToken);

+ 741 - 0
src/anki/renderer/GlobalIllumination.cpp

@@ -0,0 +1,741 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/renderer/GlobalIllumination.h>
+#include <anki/renderer/Renderer.h>
+#include <anki/renderer/RenderQueue.h>
+#include <anki/misc/ConfigSet.h>
+#include <anki/core/Trace.h>
+#include <anki/collision/Aabb.h>
+#include <anki/collision/Functions.h>
+
+namespace anki
+{
+
+/// Given a cell index compute its world position.
+static Vec3 computeProbeCellPosition(U cellIdx, const GlobalIlluminationProbeQueueElement& probe)
+{
+	ANKI_ASSERT(cellIdx < probe.m_totalCellCount);
+
+	UVec3 cellCoords;
+	unflatten3dArrayIndex(probe.m_cellCounts.z(),
+		probe.m_cellCounts.y(),
+		probe.m_cellCounts.x(),
+		cellIdx,
+		cellCoords.z(),
+		cellCoords.y(),
+		cellCoords.x());
+
+	const Vec3 halfCellSize = probe.m_cellSizes / 2.0f;
+	const Vec3 cellPos = Vec3(cellCoords) * probe.m_cellSizes + halfCellSize + probe.m_aabbMin;
+	ANKI_ASSERT(cellPos < probe.m_aabbMax);
+
+	return cellPos;
+}
+
+class GlobalIllumination::InternalContext
+{
+public:
+	GlobalIllumination* m_gi ANKI_DEBUG_CODE(= numberToPtr<GlobalIllumination*>(1));
+	RenderingContext* m_ctx ANKI_DEBUG_CODE(= numberToPtr<RenderingContext*>(1));
+
+	GlobalIlluminationProbeQueueElement* m_probeToUpdateThisFrame ANKI_DEBUG_CODE(
+		= numberToPtr<GlobalIlluminationProbeQueueElement*>(1));
+	UVec3 m_cellOfTheProbeToUpdateThisFrame ANKI_DEBUG_CODE(= UVec3(MAX_U32));
+
+	Array<RenderTargetHandle, GBUFFER_COLOR_ATTACHMENT_COUNT> m_gbufferColorRts;
+	RenderTargetHandle m_gbufferDepthRt;
+	RenderTargetHandle m_shadowsRt;
+	RenderTargetHandle m_lightShadingRt;
+	WeakArray<RenderTargetHandle> m_irradianceProbeRts;
+
+	static void foo()
+	{
+		static_assert(std::is_trivially_destructible<InternalContext>::value, "See file");
+	}
+};
+
+GlobalIllumination::~GlobalIllumination()
+{
+	m_cacheEntries.destroy(getAllocator());
+	m_probeUuidToCacheEntryIdx.destroy(getAllocator());
+}
+
+const RenderTargetHandle& GlobalIllumination::getVolumeRenderTarget(
+	const GlobalIlluminationProbeQueueElement& probe) const
+{
+	ANKI_ASSERT(m_giCtx);
+	ANKI_ASSERT(&probe >= &m_giCtx->m_ctx->m_renderQueue->m_giProbes.getFront()
+				&& &probe <= &m_giCtx->m_ctx->m_renderQueue->m_giProbes.getBack());
+	const U idx = &probe - &m_giCtx->m_ctx->m_renderQueue->m_giProbes.getFront();
+	return m_giCtx->m_irradianceProbeRts[idx];
+}
+
+void GlobalIllumination::setRenderGraphDependencies(
+	RenderingContext& ctx, RenderPassDescriptionBase& pass, TextureUsageBit usage) const
+{
+	for(U idx = 0; idx < ctx.m_renderQueue->m_giProbes.getSize(); ++idx)
+	{
+		pass.newDependency({getVolumeRenderTarget(ctx.m_renderQueue->m_giProbes[idx]), usage});
+	}
+}
+
+void GlobalIllumination::bindVolumeTextures(
+	const RenderingContext& ctx, RenderPassWorkContext& rgraphCtx, U32 set, U32 binding) const
+{
+	for(U idx = 0; idx < MAX_VISIBLE_GLOBAL_ILLUMINATION_PROBES; ++idx)
+	{
+		if(idx < ctx.m_renderQueue->m_giProbes.getSize())
+		{
+			rgraphCtx.bindColorTexture(set, binding, getVolumeRenderTarget(ctx.m_renderQueue->m_giProbes[idx]), idx);
+		}
+		else
+		{
+			rgraphCtx.m_commandBuffer->bindTexture(
+				set, binding, m_r->getDummyTextureView3d(), TextureUsageBit::SAMPLED_ALL, idx);
+		}
+	}
+}
+
+Error GlobalIllumination::init(const ConfigSet& cfg)
+{
+	ANKI_R_LOGI("Initializing global illumination");
+
+	const Error err = initInternal(cfg);
+	if(err)
+	{
+		ANKI_R_LOGE("Failed to initialize global illumination");
+	}
+
+	return err;
+}
+
+Error GlobalIllumination::initInternal(const ConfigSet& cfg)
+{
+	m_tileSize = cfg.getNumber("r.gi.tileResolution");
+	m_cacheEntries.create(getAllocator(), cfg.getNumber("r.gi.maxCachedProbes"));
+	m_maxVisibleProbes = cfg.getNumber("r.gi.maxVisibleProbes");
+	ANKI_ASSERT(m_maxVisibleProbes <= MAX_VISIBLE_GLOBAL_ILLUMINATION_PROBES);
+	ANKI_ASSERT(m_cacheEntries.getSize() >= m_maxVisibleProbes);
+
+	ANKI_CHECK(initGBuffer(cfg));
+	ANKI_CHECK(initLightShading(cfg));
+	ANKI_CHECK(initShadowMapping(cfg));
+	ANKI_CHECK(initIrradiance(cfg));
+
+	return Error::NONE;
+}
+
+Error GlobalIllumination::initGBuffer(const ConfigSet& cfg)
+{
+	// Create RT descriptions
+	{
+		RenderTargetDescription texinit = m_r->create2DRenderTargetDescription(
+			m_tileSize * 6, m_tileSize, GBUFFER_COLOR_ATTACHMENT_PIXEL_FORMATS[0], "GI GBuffer");
+
+		// Create color RT descriptions
+		for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
+		{
+			texinit.m_format = GBUFFER_COLOR_ATTACHMENT_PIXEL_FORMATS[i];
+			m_gbuffer.m_colorRtDescrs[i] = texinit;
+			m_gbuffer.m_colorRtDescrs[i].setName(StringAuto(getAllocator()).sprintf("GI GBuff Col #%u", i).toCString());
+			m_gbuffer.m_colorRtDescrs[i].bake();
+		}
+
+		// Create depth RT
+		texinit.m_format = GBUFFER_DEPTH_ATTACHMENT_PIXEL_FORMAT;
+		texinit.setName("GI GBuff Depth");
+		m_gbuffer.m_depthRtDescr = texinit;
+		m_gbuffer.m_depthRtDescr.bake();
+	}
+
+	// Create FB descr
+	{
+		m_gbuffer.m_fbDescr.m_colorAttachmentCount = GBUFFER_COLOR_ATTACHMENT_COUNT;
+
+		for(U j = 0; j < GBUFFER_COLOR_ATTACHMENT_COUNT; ++j)
+		{
+			m_gbuffer.m_fbDescr.m_colorAttachments[j].m_loadOperation = AttachmentLoadOperation::CLEAR;
+		}
+
+		m_gbuffer.m_fbDescr.m_depthStencilAttachment.m_aspect = DepthStencilAspectBit::DEPTH;
+		m_gbuffer.m_fbDescr.m_depthStencilAttachment.m_loadOperation = AttachmentLoadOperation::CLEAR;
+		m_gbuffer.m_fbDescr.m_depthStencilAttachment.m_clearValue.m_depthStencil.m_depth = 1.0f;
+
+		m_gbuffer.m_fbDescr.bake();
+	}
+
+	return Error::NONE;
+}
+
+Error GlobalIllumination::initShadowMapping(const ConfigSet& cfg)
+{
+	const U resolution = cfg.getNumber("r.gi.shadowMapResolution");
+	ANKI_ASSERT(resolution > 8);
+
+	// RT descr
+	m_shadowMapping.m_rtDescr =
+		m_r->create2DRenderTargetDescription(resolution * 6, resolution, Format::D32_SFLOAT, "GI SM");
+	m_shadowMapping.m_rtDescr.bake();
+
+	// FB descr
+	m_shadowMapping.m_fbDescr.m_colorAttachmentCount = 0;
+	m_shadowMapping.m_fbDescr.m_depthStencilAttachment.m_aspect = DepthStencilAspectBit::DEPTH;
+	m_shadowMapping.m_fbDescr.m_depthStencilAttachment.m_clearValue.m_depthStencil.m_depth = 1.0f;
+	m_shadowMapping.m_fbDescr.m_depthStencilAttachment.m_loadOperation = AttachmentLoadOperation::CLEAR;
+	m_shadowMapping.m_fbDescr.bake();
+
+	// Shadow sampler
+	{
+		SamplerInitInfo inf;
+		inf.m_compareOperation = CompareOperation::LESS_EQUAL;
+		inf.m_addressing = SamplingAddressing::CLAMP;
+		inf.m_mipmapFilter = SamplingFilter::BASE;
+		inf.m_minMagFilter = SamplingFilter::LINEAR;
+		m_shadowMapping.m_shadowSampler = getGrManager().newSampler(inf);
+	}
+
+	return Error::NONE;
+}
+
+Error GlobalIllumination::initLightShading(const ConfigSet& cfg)
+{
+	// Init RT descr
+	{
+		m_lightShading.m_rtDescr = m_r->create2DRenderTargetDescription(
+			m_tileSize * 6, m_tileSize, LIGHT_SHADING_COLOR_ATTACHMENT_PIXEL_FORMAT, "GI LS");
+		m_lightShading.m_rtDescr.bake();
+	}
+
+	// Create FB descr
+	{
+		m_lightShading.m_fbDescr.m_colorAttachmentCount = 1;
+		m_lightShading.m_fbDescr.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::CLEAR;
+		m_lightShading.m_fbDescr.bake();
+	}
+
+	// Init deferred
+	ANKI_CHECK(m_lightShading.m_deferred.init());
+
+	return Error::NONE;
+}
+
+Error GlobalIllumination::initIrradiance(const ConfigSet& cfg)
+{
+	ANKI_CHECK(m_r->getResourceManager().loadResource("shaders/IrradianceDice.glslp", m_irradiance.m_prog));
+
+	ShaderProgramResourceConstantValueInitList<1> consts(m_irradiance.m_prog);
+	consts.add("WORKGROUP_SIZE", U32(m_tileSize));
+
+	ShaderProgramResourceMutationInitList<3> mutations(m_irradiance.m_prog);
+	mutations.add("LIGHT_SHADING_TEX", 0);
+	mutations.add("STORE_LOCATION", 0);
+	mutations.add("SECOND_BOUNCE", 1);
+
+	const ShaderProgramResourceVariant* variant;
+	m_irradiance.m_prog->getOrCreateVariant(mutations.get(), consts.get(), variant);
+	m_irradiance.m_grProg = variant->getProgram();
+
+	return Error::NONE;
+}
+
+void GlobalIllumination::populateRenderGraph(RenderingContext& rctx)
+{
+	ANKI_TRACE_SCOPED_EVENT(R_GI);
+
+	InternalContext* giCtx = rctx.m_tempAllocator.newInstance<InternalContext>();
+	giCtx->m_gi = this;
+	giCtx->m_ctx = &rctx;
+	RenderGraphDescription& rgraph = rctx.m_renderGraphDescr;
+	m_giCtx = giCtx;
+
+	// Prepare the probes
+	prepareProbes(*giCtx);
+	const Bool haveProbeToRender = giCtx->m_probeToUpdateThisFrame != nullptr;
+	if(!haveProbeToRender)
+	{
+		// Early exit
+		return;
+	}
+
+	// GBuffer
+	{
+		// RTs
+		for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
+		{
+			giCtx->m_gbufferColorRts[i] = rgraph.newRenderTarget(m_gbuffer.m_colorRtDescrs[i]);
+		}
+		giCtx->m_gbufferDepthRt = rgraph.newRenderTarget(m_gbuffer.m_depthRtDescr);
+
+		// Pass
+		GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("GI gbuff");
+		pass.setFramebufferInfo(m_gbuffer.m_fbDescr, giCtx->m_gbufferColorRts, giCtx->m_gbufferDepthRt);
+		pass.setWork(
+			[](RenderPassWorkContext& rgraphCtx) {
+				InternalContext* giCtx = static_cast<InternalContext*>(rgraphCtx.m_userData);
+				giCtx->m_gi->runGBufferInThread(rgraphCtx, *giCtx);
+			},
+			giCtx,
+			6);
+
+		for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
+		{
+			pass.newDependency({giCtx->m_gbufferColorRts[i], TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE});
+		}
+
+		TextureSubresourceInfo subresource(DepthStencilAspectBit::DEPTH);
+		pass.newDependency({giCtx->m_gbufferDepthRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE, subresource});
+	}
+
+	// Shadow pass. Optional
+	if(giCtx->m_probeToUpdateThisFrame->m_renderQueues[0]->m_directionalLight.m_uuid
+		&& giCtx->m_probeToUpdateThisFrame->m_renderQueues[0]->m_directionalLight.m_shadowCascadeCount > 0)
+	{
+		// Update light matrices
+		for(U i = 0; i < 6; ++i)
+		{
+			ANKI_ASSERT(
+				giCtx->m_probeToUpdateThisFrame->m_renderQueues[i]->m_directionalLight.m_uuid
+				&& giCtx->m_probeToUpdateThisFrame->m_renderQueues[i]->m_directionalLight.m_shadowCascadeCount == 1);
+
+			const F32 xScale = 1.0f / 6.0f;
+			const F32 yScale = 1.0f;
+			const F32 xOffset = F32(i) * (1.0f / 6.0f);
+			const F32 yOffset = 0.0f;
+			const Mat4 atlasMtx(xScale,
+				0.0f,
+				0.0f,
+				xOffset,
+				0.0f,
+				yScale,
+				0.0f,
+				yOffset,
+				0.0f,
+				0.0f,
+				1.0f,
+				0.0f,
+				0.0f,
+				0.0f,
+				0.0f,
+				1.0f);
+
+			Mat4& lightMat =
+				giCtx->m_probeToUpdateThisFrame->m_renderQueues[i]->m_directionalLight.m_textureMatrices[0];
+			lightMat = atlasMtx * lightMat;
+		}
+
+		// RT
+		giCtx->m_shadowsRt = rgraph.newRenderTarget(m_shadowMapping.m_rtDescr);
+
+		// Pass
+		GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("GI SM");
+		pass.setFramebufferInfo(m_shadowMapping.m_fbDescr, {}, giCtx->m_shadowsRt);
+		pass.setWork(
+			[](RenderPassWorkContext& rgraphCtx) {
+				InternalContext* giCtx = static_cast<InternalContext*>(rgraphCtx.m_userData);
+				giCtx->m_gi->runShadowmappingInThread(rgraphCtx, *giCtx);
+			},
+			giCtx,
+			6);
+
+		TextureSubresourceInfo subresource(DepthStencilAspectBit::DEPTH);
+		pass.newDependency({giCtx->m_shadowsRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE, subresource});
+	}
+	else
+	{
+		giCtx->m_shadowsRt = {};
+	}
+
+	// Light shading pass
+	{
+		// RT
+		giCtx->m_lightShadingRt = rgraph.newRenderTarget(m_lightShading.m_rtDescr);
+
+		// Pass
+		GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("GI LS");
+		pass.setFramebufferInfo(m_lightShading.m_fbDescr, {{giCtx->m_lightShadingRt}}, {});
+		pass.setWork(
+			[](RenderPassWorkContext& rgraphCtx) {
+				InternalContext* giCtx = static_cast<InternalContext*>(rgraphCtx.m_userData);
+				giCtx->m_gi->runLightShading(rgraphCtx, *giCtx);
+			},
+			giCtx,
+			6);
+
+		pass.newDependency({giCtx->m_lightShadingRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE});
+
+		for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
+		{
+			pass.newDependency({giCtx->m_gbufferColorRts[i], TextureUsageBit::SAMPLED_FRAGMENT});
+		}
+		pass.newDependency({giCtx->m_gbufferDepthRt,
+			TextureUsageBit::SAMPLED_FRAGMENT,
+			TextureSubresourceInfo(DepthStencilAspectBit::DEPTH)});
+
+		if(giCtx->m_shadowsRt.isValid())
+		{
+			pass.newDependency({giCtx->m_shadowsRt, TextureUsageBit::SAMPLED_FRAGMENT});
+		}
+	}
+
+	// Irradiance pass. First & 2nd bounce
+	{
+		ComputeRenderPassDescription& pass = rgraph.newComputeRenderPass("GI IR");
+
+		pass.setWork(
+			[](RenderPassWorkContext& rgraphCtx) {
+				InternalContext* giCtx = static_cast<InternalContext*>(rgraphCtx.m_userData);
+				giCtx->m_gi->runIrradiance(rgraphCtx, *giCtx);
+			},
+			giCtx,
+			0);
+
+		pass.newDependency({giCtx->m_lightShadingRt, TextureUsageBit::SAMPLED_COMPUTE});
+
+		for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT - 1; ++i)
+		{
+			pass.newDependency({giCtx->m_gbufferColorRts[i], TextureUsageBit::SAMPLED_COMPUTE});
+		}
+
+		const U probeIdx = giCtx->m_probeToUpdateThisFrame - &giCtx->m_ctx->m_renderQueue->m_giProbes.getFront();
+		pass.newDependency({giCtx->m_irradianceProbeRts[probeIdx], TextureUsageBit::IMAGE_COMPUTE_WRITE});
+	}
+}
+
+void GlobalIllumination::prepareProbes(InternalContext& giCtx)
+{
+	RenderingContext& ctx = *giCtx.m_ctx;
+	giCtx.m_probeToUpdateThisFrame = nullptr;
+
+	if(ANKI_UNLIKELY(ctx.m_renderQueue->m_giProbes.getSize() == 0))
+	{
+		return;
+	}
+
+	// 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<GlobalIlluminationProbeQueueElement> newListOfProbes;
+	newListOfProbes.create(ctx.m_tempAllocator, ctx.m_renderQueue->m_giProbes.getSize());
+	DynamicArray<RenderTargetHandle> volumeRts;
+	volumeRts.create(ctx.m_tempAllocator, ctx.m_renderQueue->m_giProbes.getSize());
+	U newListOfProbeCount = 0;
+	Bool foundProbeToUpdateNextFrame = false;
+	for(U32 probeIdx = 0; probeIdx < ctx.m_renderQueue->m_giProbes.getSize(); ++probeIdx)
+	{
+		if(newListOfProbeCount + 1 >= m_maxVisibleProbes)
+		{
+			ANKI_R_LOGW("Can't have more that %u visible probes. Increase the r.gi.maxVisibleProbes or (somehow) "
+						"decrease the visible probes",
+				m_maxVisibleProbes);
+			break;
+		}
+
+		GlobalIlluminationProbeQueueElement& probe = ctx.m_renderQueue->m_giProbes[probeIdx];
+
+		// Find cache entry
+		const U32 cacheEntryIdx = findBestCacheEntry(
+			probe.m_uuid, m_r->getGlobalTimestamp(), m_cacheEntries, m_probeUuidToCacheEntryIdx, getAllocator());
+		if(ANKI_UNLIKELY(cacheEntryIdx == MAX_U32))
+		{
+			// Failed
+			ANKI_R_LOGW("There is not enough space in the indirect lighting atlas for more probes. "
+						"Increase the r.gi.maxCachedProbes or (somehow) decrease the visible probes");
+			continue;
+		}
+
+		CacheEntry& entry = m_cacheEntries[cacheEntryIdx];
+
+		const Bool cacheEntryDirty = entry.m_uuid != probe.m_uuid || entry.m_volumeSize != probe.m_cellCounts
+									 || entry.m_probeAabbMin != probe.m_aabbMin
+									 || entry.m_probeAabbMax != probe.m_aabbMax;
+		const Bool needsUpdate = cacheEntryDirty || entry.m_renderedCells < probe.m_totalCellCount;
+
+		if(ANKI_LIKELY(!needsUpdate))
+		{
+			// It's updated, early exit
+
+			entry.m_lastUsedTimestamp = m_r->getGlobalTimestamp();
+			volumeRts[newListOfProbeCount] =
+				ctx.m_renderGraphDescr.importRenderTarget(entry.m_volumeTex, TextureUsageBit::SAMPLED_FRAGMENT);
+			newListOfProbes[newListOfProbeCount++] = probe;
+			continue;
+		}
+
+		// It needs update
+
+		const Bool canUpdateThisFrame = giCtx.m_probeToUpdateThisFrame == nullptr && probe.m_renderQueues[0] != nullptr;
+		const Bool canUpdateNextFrame = !foundProbeToUpdateNextFrame;
+
+		if(!canUpdateThisFrame && canUpdateNextFrame)
+		{
+			// Probe will (possibly) be updated next frame, inform the probe
+
+			foundProbeToUpdateNextFrame = true;
+
+			const U cellToRender = (cacheEntryDirty) ? 0 : entry.m_renderedCells;
+			const Vec3 cellPos = computeProbeCellPosition(cellToRender, probe);
+			probe.m_feedbackCallback(true, probe.m_userData, cellPos.xyz0());
+			continue;
+		}
+		else if(!canUpdateThisFrame)
+		{
+			// Can't be updated this frame so remove it from the list
+			continue;
+		}
+
+		// All good, can use this probe in this frame
+
+		if(cacheEntryDirty)
+		{
+			entry.m_renderedCells = 0;
+			entry.m_uuid = probe.m_uuid;
+			entry.m_probeAabbMin = probe.m_aabbMin;
+			entry.m_probeAabbMax = probe.m_aabbMax;
+			entry.m_volumeSize = probe.m_cellCounts;
+			m_probeUuidToCacheEntryIdx.emplace(getAllocator(), probe.m_uuid, cacheEntryIdx);
+		}
+
+		// Update the cache entry
+		entry.m_lastUsedTimestamp = m_r->getGlobalTimestamp();
+
+		// Init the cache entry textures
+		const Bool shouldInitTextures = !entry.m_volumeTex.isCreated() || entry.m_volumeSize != probe.m_cellCounts;
+		if(shouldInitTextures)
+		{
+			TextureInitInfo texInit;
+			texInit.m_type = TextureType::_3D;
+			texInit.m_format = Format::B10G11R11_UFLOAT_PACK32;
+			texInit.m_width = probe.m_cellCounts.x() * 6;
+			texInit.m_height = probe.m_cellCounts.y();
+			texInit.m_depth = probe.m_cellCounts.z();
+			texInit.m_usage = TextureUsageBit::ALL_COMPUTE | TextureUsageBit::SAMPLED_ALL;
+			texInit.m_initialUsage = TextureUsageBit::SAMPLED_FRAGMENT;
+
+			entry.m_volumeTex = m_r->createAndClearRenderTarget(texInit);
+		}
+
+		// Compute the render position
+		const U cellToRender = entry.m_renderedCells++;
+		ANKI_ASSERT(cellToRender < probe.m_totalCellCount);
+		unflatten3dArrayIndex(probe.m_cellCounts.z(),
+			probe.m_cellCounts.y(),
+			probe.m_cellCounts.x(),
+			cellToRender,
+			giCtx.m_cellOfTheProbeToUpdateThisFrame.z(),
+			giCtx.m_cellOfTheProbeToUpdateThisFrame.y(),
+			giCtx.m_cellOfTheProbeToUpdateThisFrame.x());
+
+		// Inform probe about its next frame
+		if(entry.m_renderedCells == probe.m_totalCellCount)
+		{
+			// Don't gather renderables next frame if it's done
+			probe.m_feedbackCallback(false, probe.m_userData, Vec4(0.0f));
+		}
+		else if(!foundProbeToUpdateNextFrame)
+		{
+			// Gather rendederables from the same probe next frame
+			foundProbeToUpdateNextFrame = true;
+			const Vec3 cellPos = computeProbeCellPosition(entry.m_renderedCells, probe);
+			probe.m_feedbackCallback(true, probe.m_userData, cellPos.xyz0());
+		}
+
+		// Push the probe to the new list
+		giCtx.m_probeToUpdateThisFrame = &newListOfProbes[newListOfProbeCount];
+		newListOfProbes[newListOfProbeCount] = probe;
+		volumeRts[newListOfProbeCount] =
+			ctx.m_renderGraphDescr.importRenderTarget(entry.m_volumeTex, TextureUsageBit::SAMPLED_FRAGMENT);
+		++newListOfProbeCount;
+	}
+
+	// Replace the probe list in the queue
+	if(newListOfProbeCount > 0)
+	{
+		GlobalIlluminationProbeQueueElement* firstProbe;
+		PtrSize probeCount, storage;
+		newListOfProbes.moveAndReset(firstProbe, probeCount, storage);
+		ctx.m_renderQueue->m_giProbes = WeakArray<GlobalIlluminationProbeQueueElement>(firstProbe, newListOfProbeCount);
+
+		RenderTargetHandle* firstRt;
+		volumeRts.moveAndReset(firstRt, probeCount, storage);
+		m_giCtx->m_irradianceProbeRts = WeakArray<RenderTargetHandle>(firstRt, newListOfProbeCount);
+	}
+	else
+	{
+		ctx.m_renderQueue->m_giProbes = WeakArray<GlobalIlluminationProbeQueueElement>();
+		newListOfProbes.destroy(ctx.m_tempAllocator);
+		volumeRts.destroy(ctx.m_tempAllocator);
+	}
+}
+
+void GlobalIllumination::runGBufferInThread(RenderPassWorkContext& rgraphCtx, InternalContext& giCtx) const
+{
+	ANKI_ASSERT(giCtx.m_probeToUpdateThisFrame);
+	ANKI_TRACE_SCOPED_EVENT(R_GI);
+
+	const GlobalIlluminationProbeQueueElement& probe = *giCtx.m_probeToUpdateThisFrame;
+	const U faceIdx = rgraphCtx.m_currentSecondLevelCommandBufferIndex;
+	ANKI_ASSERT(faceIdx < 6);
+	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
+
+	const U32 viewportX = faceIdx * m_tileSize;
+	cmdb->setViewport(viewportX, 0, m_tileSize, m_tileSize);
+	cmdb->setScissor(viewportX, 0, m_tileSize, m_tileSize);
+
+	/// Draw
+	ANKI_ASSERT(probe.m_renderQueues[faceIdx]);
+	const RenderQueue& rqueue = *probe.m_renderQueues[faceIdx];
+
+	if(!rqueue.m_renderables.isEmpty())
+	{
+		m_r->getSceneDrawer().drawRange(Pass::GB,
+			rqueue.m_viewMatrix,
+			rqueue.m_viewProjectionMatrix,
+			Mat4::getIdentity(), // Don't care about prev mats since we don't care about velocity
+			cmdb,
+			m_r->getSamplers().m_trilinearRepeat,
+			rqueue.m_renderables.getBegin(),
+			rqueue.m_renderables.getEnd(),
+			MAX_LOD_COUNT - 1);
+	}
+
+	// It's secondary, no need to restore the state
+}
+
+void GlobalIllumination::runShadowmappingInThread(RenderPassWorkContext& rgraphCtx, InternalContext& giCtx) const
+{
+	const U faceIdx = rgraphCtx.m_currentSecondLevelCommandBufferIndex;
+	ANKI_ASSERT(faceIdx < 6);
+
+	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
+	cmdb->setPolygonOffset(1.0f, 1.0f);
+
+	ANKI_ASSERT(giCtx.m_probeToUpdateThisFrame);
+	ANKI_ASSERT(giCtx.m_probeToUpdateThisFrame->m_renderQueues[faceIdx]);
+	const RenderQueue& faceRenderQueue = *giCtx.m_probeToUpdateThisFrame->m_renderQueues[faceIdx];
+	ANKI_ASSERT(faceRenderQueue.m_directionalLight.m_uuid != 0);
+	ANKI_ASSERT(faceRenderQueue.m_directionalLight.m_shadowCascadeCount == 1);
+
+	ANKI_ASSERT(faceRenderQueue.m_directionalLight.m_shadowRenderQueues[0]);
+	const RenderQueue& cascadeRenderQueue = *faceRenderQueue.m_directionalLight.m_shadowRenderQueues[0];
+
+	if(cascadeRenderQueue.m_renderables.getSize() != 0)
+	{
+		const U rez = m_shadowMapping.m_rtDescr.m_height;
+		cmdb->setViewport(rez * faceIdx, 0, rez, rez);
+		cmdb->setScissor(rez * faceIdx, 0, rez, rez);
+
+		m_r->getSceneDrawer().drawRange(Pass::SM,
+			cascadeRenderQueue.m_viewMatrix,
+			cascadeRenderQueue.m_viewProjectionMatrix,
+			Mat4::getIdentity(), // Don't care about prev matrices here
+			cmdb,
+			m_r->getSamplers().m_trilinearRepeatAniso,
+			cascadeRenderQueue.m_renderables.getBegin(),
+			cascadeRenderQueue.m_renderables.getEnd(),
+			MAX_LOD_COUNT - 1);
+	}
+
+	// It's secondary, no need to restore the state
+}
+
+void GlobalIllumination::runLightShading(RenderPassWorkContext& rgraphCtx, InternalContext& giCtx)
+{
+	ANKI_TRACE_SCOPED_EVENT(R_GI);
+
+	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
+	ANKI_ASSERT(giCtx.m_probeToUpdateThisFrame);
+	const GlobalIlluminationProbeQueueElement& probe = *giCtx.m_probeToUpdateThisFrame;
+	const U faceIdx = rgraphCtx.m_currentSecondLevelCommandBufferIndex;
+	ANKI_ASSERT(faceIdx < 6);
+
+	ANKI_ASSERT(probe.m_renderQueues[faceIdx]);
+	const RenderQueue& rqueue = *probe.m_renderQueues[faceIdx];
+
+	const U rez = m_tileSize;
+	cmdb->setScissor(rez * faceIdx, 0, rez, rez);
+
+	// Set common state for all lights
+	// NOTE: Use nearest sampler because we don't want the result to sample the near tiles
+	cmdb->bindSampler(0, 2, m_r->getSamplers().m_nearestNearestClamp);
+
+	rgraphCtx.bindColorTexture(0, 3, giCtx.m_gbufferColorRts[0]);
+	rgraphCtx.bindColorTexture(0, 4, giCtx.m_gbufferColorRts[1]);
+	rgraphCtx.bindColorTexture(0, 5, giCtx.m_gbufferColorRts[2]);
+
+	rgraphCtx.bindTexture(0, 6, giCtx.m_gbufferDepthRt, TextureSubresourceInfo(DepthStencilAspectBit::DEPTH));
+
+	// Get shadowmap info
+	const Bool hasDirLight = probe.m_renderQueues[0]->m_directionalLight.m_uuid;
+	if(hasDirLight)
+	{
+		ANKI_ASSERT(giCtx.m_shadowsRt.isValid());
+
+		cmdb->bindSampler(0, 7, m_shadowMapping.m_shadowSampler);
+
+		rgraphCtx.bindTexture(0, 8, giCtx.m_shadowsRt, TextureSubresourceInfo(DepthStencilAspectBit::DEPTH));
+	}
+
+	TraditionalDeferredLightShadingDrawInfo dsInfo;
+	dsInfo.m_viewProjectionMatrix = rqueue.m_viewProjectionMatrix;
+	dsInfo.m_invViewProjectionMatrix = rqueue.m_viewProjectionMatrix.getInverse();
+	dsInfo.m_cameraPosWSpace = rqueue.m_cameraTransform.getTranslationPart();
+	dsInfo.m_viewport = UVec4(faceIdx * m_tileSize, 0, m_tileSize, m_tileSize);
+	dsInfo.m_gbufferTexCoordsScale = Vec2(1.0f / F32(m_tileSize * 6), 1.0f / F32(m_tileSize));
+	dsInfo.m_gbufferTexCoordsBias = Vec2(0.0f, 0.0f);
+	dsInfo.m_lightbufferTexCoordsScale = Vec2(1.0f / F32(m_tileSize), 1.0f / F32(m_tileSize));
+	dsInfo.m_lightbufferTexCoordsBias = Vec2(-F32(faceIdx), 0.0f);
+	dsInfo.m_cameraNear = probe.m_renderQueues[faceIdx]->m_cameraNear;
+	dsInfo.m_cameraFar = probe.m_renderQueues[faceIdx]->m_cameraFar;
+	dsInfo.m_directionalLight = (hasDirLight) ? &probe.m_renderQueues[faceIdx]->m_directionalLight : nullptr;
+	dsInfo.m_pointLights = rqueue.m_pointLights;
+	dsInfo.m_spotLights = rqueue.m_spotLights;
+	dsInfo.m_commandBuffer = cmdb;
+	m_lightShading.m_deferred.drawLights(dsInfo);
+}
+
+void GlobalIllumination::runIrradiance(RenderPassWorkContext& rgraphCtx, InternalContext& giCtx)
+{
+	ANKI_TRACE_SCOPED_EVENT(R_GI);
+
+	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
+	ANKI_ASSERT(giCtx.m_probeToUpdateThisFrame);
+	const GlobalIlluminationProbeQueueElement& probe = *giCtx.m_probeToUpdateThisFrame;
+	const U probeIdx = &probe - &giCtx.m_ctx->m_renderQueue->m_giProbes.getFront();
+
+	cmdb->bindShaderProgram(m_irradiance.m_grProg);
+
+	// Bind resources
+	cmdb->bindSampler(0, 0, m_r->getSamplers().m_nearestNearestClamp);
+	rgraphCtx.bindColorTexture(0, 1, giCtx.m_lightShadingRt);
+
+	for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT - 1; ++i)
+	{
+		rgraphCtx.bindColorTexture(0, 2, giCtx.m_gbufferColorRts[i], i);
+	}
+
+	// Bind temporary memory
+	allocateAndBindStorage<void*>(sizeof(Vec4) * 6 * m_tileSize * m_tileSize, cmdb, 0, 3);
+
+	rgraphCtx.bindImage(0, 4, giCtx.m_irradianceProbeRts[probeIdx], TextureSubresourceInfo());
+
+	struct
+	{
+		IVec3 m_volumeTexel;
+		I32 m_nextTexelOffsetInU;
+	} unis;
+
+	unis.m_volumeTexel = IVec3(giCtx.m_cellOfTheProbeToUpdateThisFrame.x(),
+		giCtx.m_cellOfTheProbeToUpdateThisFrame.y(),
+		giCtx.m_cellOfTheProbeToUpdateThisFrame.z());
+	unis.m_nextTexelOffsetInU = probe.m_cellCounts.x();
+	cmdb->setPushConstants(&unis, sizeof(unis));
+
+	// Dispatch
+	cmdb->dispatchCompute(1, 1, 1);
+}
+
+} // end namespace anki

+ 120 - 0
src/anki/renderer/GlobalIllumination.h

@@ -0,0 +1,120 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/renderer/RendererObject.h>
+#include <anki/renderer/TraditionalDeferredShading.h>
+#include <anki/renderer/RenderQueue.h>
+#include <anki/collision/Forward.h>
+
+namespace anki
+{
+
+/// @addtogroup renderer
+/// @{
+
+/// Ambient global illumination passes.
+///
+/// It builds a volume clipmap with ambient GI information.
+class GlobalIllumination : public RendererObject
+{
+anki_internal:
+	GlobalIllumination(Renderer* r)
+		: RendererObject(r)
+		, m_lightShading(r)
+	{
+	}
+
+	~GlobalIllumination();
+
+	ANKI_USE_RESULT Error init(const ConfigSet& cfg);
+
+	/// Populate the rendergraph.
+	void populateRenderGraph(RenderingContext& ctx);
+
+	/// Return the volume RT given a cache entry index.
+	const RenderTargetHandle& getVolumeRenderTarget(const GlobalIlluminationProbeQueueElement& probe) const;
+
+	/// Set the render graph dependencies.
+	void setRenderGraphDependencies(
+		RenderingContext& ctx, RenderPassDescriptionBase& pass, TextureUsageBit usage) const;
+
+	/// Bind the volume textures to a command buffer.
+	void bindVolumeTextures(const RenderingContext& ctx, RenderPassWorkContext& rgraphCtx, U32 set, U32 binding) const;
+
+private:
+	class InternalContext;
+
+	class CacheEntry
+	{
+	public:
+		U64 m_uuid; ///< Probe UUID.
+		Timestamp m_lastUsedTimestamp = 0; ///< When it was last seen by the renderer.
+		TexturePtr m_volumeTex; ///< Contains the 6 directions.
+		UVec3 m_volumeSize = UVec3(0u);
+		Vec3 m_probeAabbMin = Vec3(0.0f);
+		Vec3 m_probeAabbMax = Vec3(0.0f);
+		U32 m_renderedCells = 0;
+	};
+
+	class
+	{
+	public:
+		Array<RenderTargetDescription, GBUFFER_COLOR_ATTACHMENT_COUNT> m_colorRtDescrs;
+		RenderTargetDescription m_depthRtDescr;
+		FramebufferDescription m_fbDescr;
+	} m_gbuffer; ///< G-buffer pass.
+
+	class
+	{
+	public:
+		RenderTargetDescription m_rtDescr;
+		FramebufferDescription m_fbDescr;
+		SamplerPtr m_shadowSampler;
+	} m_shadowMapping;
+
+	class LS
+	{
+	public:
+		RenderTargetDescription m_rtDescr;
+		FramebufferDescription m_fbDescr;
+		TraditionalDeferredLightShading m_deferred;
+
+		LS(Renderer* r)
+			: m_deferred(r)
+		{
+		}
+	} m_lightShading; ///< Light shading.
+
+	class
+	{
+	public:
+		ShaderProgramResourcePtr m_prog;
+		ShaderProgramPtr m_grProg;
+	} m_irradiance; ///< Irradiance.
+
+	InternalContext* m_giCtx = nullptr;
+	DynamicArray<CacheEntry> m_cacheEntries;
+	HashMap<U64, U32> m_probeUuidToCacheEntryIdx;
+	U32 m_tileSize = 0;
+	U32 m_maxVisibleProbes = 0;
+
+	ANKI_USE_RESULT Error initInternal(const ConfigSet& cfg);
+	ANKI_USE_RESULT Error initGBuffer(const ConfigSet& cfg);
+	ANKI_USE_RESULT Error initShadowMapping(const ConfigSet& cfg);
+	ANKI_USE_RESULT Error initLightShading(const ConfigSet& cfg);
+	ANKI_USE_RESULT Error initIrradiance(const ConfigSet& cfg);
+
+	void runGBufferInThread(RenderPassWorkContext& rgraphCtx, InternalContext& giCtx) const;
+	void runShadowmappingInThread(RenderPassWorkContext& rgraphCtx, InternalContext& giCtx) const;
+	void runLightShading(RenderPassWorkContext& rgraphCtx, InternalContext& giCtx);
+	void runIrradiance(RenderPassWorkContext& rgraphCtx, InternalContext& giCtx);
+
+	void prepareProbes(InternalContext& giCtx);
+};
+/// @}
+
+} // end namespace anki

+ 22 - 18
src/anki/renderer/LightShading.cpp

@@ -6,7 +6,7 @@
 #include <anki/renderer/LightShading.h>
 #include <anki/renderer/Renderer.h>
 #include <anki/renderer/ShadowMapping.h>
-#include <anki/renderer/Indirect.h>
+#include <anki/renderer/ProbeReflections.h>
 #include <anki/renderer/GBuffer.h>
 #include <anki/renderer/RenderQueue.h>
 #include <anki/renderer/ForwardShading.h>
@@ -14,6 +14,7 @@
 #include <anki/renderer/DepthDownscale.h>
 #include <anki/renderer/Ssao.h>
 #include <anki/renderer/Ssr.h>
+#include <anki/renderer/GlobalIllumination.h>
 #include <anki/misc/ConfigSet.h>
 #include <anki/util/HighRezTimer.h>
 
@@ -57,7 +58,7 @@ Error LightShading::initLightShading(const ConfigSet& config)
 		.add("CLUSTER_COUNT_Y", U32(m_r->getClusterCount()[1]))
 		.add("CLUSTER_COUNT_Z", U32(m_r->getClusterCount()[2]))
 		.add("CLUSTER_COUNT", U32(m_r->getClusterCount()[3]))
-		.add("IR_MIPMAP_COUNT", U32(m_r->getIndirect().getReflectionTextureMipmapCount()));
+		.add("IR_MIPMAP_COUNT", U32(m_r->getProbeReflections().getReflectionTextureMipmapCount()));
 
 	const ShaderProgramResourceVariant* variant;
 	m_lightShading.m_prog->getOrCreateVariant(consts.get(), variant);
@@ -114,23 +115,25 @@ void LightShading::run(RenderPassWorkContext& rgraphCtx)
 		bindUniforms(cmdb, 0, 2, rsrc.m_spotLightsToken);
 		rgraphCtx.bindColorTexture(0, 3, m_r->getShadowMapping().getShadowmapRt());
 
-		bindUniforms(cmdb, 0, 4, rsrc.m_probesToken);
-		rgraphCtx.bindColorTexture(0, 5, m_r->getIndirect().getReflectionRt());
-		rgraphCtx.bindColorTexture(0, 6, m_r->getIndirect().getIrradianceRt());
-		cmdb->bindTexture(0, 7, m_r->getIndirect().getIntegrationLut(), TextureUsageBit::SAMPLED_FRAGMENT);
+		bindUniforms(cmdb, 0, 4, rsrc.m_reflectionProbesToken);
+		rgraphCtx.bindColorTexture(0, 5, m_r->getProbeReflections().getReflectionRt());
+		cmdb->bindTexture(0, 6, m_r->getProbeReflections().getIntegrationLut(), TextureUsageBit::SAMPLED_FRAGMENT);
 
-		bindStorage(cmdb, 0, 8, rsrc.m_clustersToken);
-		bindStorage(cmdb, 0, 9, rsrc.m_indicesToken);
+		m_r->getGlobalIllumination().bindVolumeTextures(ctx, rgraphCtx, 0, 7);
+		bindUniforms(cmdb, 0, 8, rsrc.m_globalIlluminationProbesToken);
 
-		cmdb->bindSampler(0, 10, m_r->getSamplers().m_nearestNearestClamp);
-		cmdb->bindSampler(0, 11, m_r->getSamplers().m_trilinearRepeat);
-		rgraphCtx.bindColorTexture(0, 12, m_r->getGBuffer().getColorRt(0));
-		rgraphCtx.bindColorTexture(0, 13, m_r->getGBuffer().getColorRt(1));
-		rgraphCtx.bindColorTexture(0, 14, m_r->getGBuffer().getColorRt(2));
+		bindStorage(cmdb, 0, 9, rsrc.m_clustersToken);
+		bindStorage(cmdb, 0, 10, rsrc.m_indicesToken);
+
+		cmdb->bindSampler(0, 11, m_r->getSamplers().m_nearestNearestClamp);
+		cmdb->bindSampler(0, 12, m_r->getSamplers().m_trilinearClamp);
+		rgraphCtx.bindColorTexture(0, 13, m_r->getGBuffer().getColorRt(0));
+		rgraphCtx.bindColorTexture(0, 14, m_r->getGBuffer().getColorRt(1));
+		rgraphCtx.bindColorTexture(0, 15, m_r->getGBuffer().getColorRt(2));
 		rgraphCtx.bindTexture(
-			0, 15, m_r->getGBuffer().getDepthRt(), TextureSubresourceInfo(DepthStencilAspectBit::DEPTH));
-		rgraphCtx.bindColorTexture(0, 16, m_r->getSsr().getRt());
-		rgraphCtx.bindColorTexture(0, 17, m_r->getSsao().getRt());
+			0, 16, m_r->getGBuffer().getDepthRt(), TextureSubresourceInfo(DepthStencilAspectBit::DEPTH));
+		rgraphCtx.bindColorTexture(0, 17, m_r->getSsr().getRt());
+		rgraphCtx.bindColorTexture(0, 18, m_r->getSsao().getRt());
 
 		// Draw
 		drawQuad(cmdb);
@@ -202,8 +205,9 @@ void LightShading::populateRenderGraph(RenderingContext& ctx)
 
 	// Refl & indirect
 	pass.newDependency({m_r->getSsr().getRt(), TextureUsageBit::SAMPLED_FRAGMENT});
-	pass.newDependency({m_r->getIndirect().getReflectionRt(), TextureUsageBit::SAMPLED_FRAGMENT});
-	pass.newDependency({m_r->getIndirect().getIrradianceRt(), TextureUsageBit::SAMPLED_FRAGMENT});
+	pass.newDependency({m_r->getProbeReflections().getReflectionRt(), TextureUsageBit::SAMPLED_FRAGMENT});
+
+	m_r->getGlobalIllumination().setRenderGraphDependencies(ctx, pass, TextureUsageBit::SAMPLED_FRAGMENT);
 
 	// Fog
 	pass.newDependency({m_r->getVolumetricFog().getRt(), TextureUsageBit::SAMPLED_FRAGMENT});

+ 6 - 11
src/anki/renderer/MainRenderer.cpp

@@ -8,7 +8,6 @@
 #include <anki/renderer/FinalComposite.h>
 #include <anki/renderer/Dbg.h>
 #include <anki/renderer/GBuffer.h>
-#include <anki/renderer/Indirect.h>
 #include <anki/renderer/RenderQueue.h>
 #include <anki/util/Logger.h>
 #include <anki/util/File.h>
@@ -163,7 +162,12 @@ Error MainRenderer::render(RenderQueue& rqueue, TexturePtr presentTex)
 	for(U i = 0; i < m_r->getThreadHive().getThreadCount(); ++i)
 	{
 		tasks[i].m_argument = this;
-		tasks[i].m_callback = executeSecondaryCallback;
+		tasks[i].m_callback = [](void* userData, U32 threadId, ThreadHive& hive, ThreadHiveSemaphore* signalSemaphore) {
+			MainRenderer& self = *static_cast<MainRenderer*>(userData);
+
+			const U taskId = self.m_runCtx.m_secondaryTaskId.fetchAdd(1);
+			self.m_rgraph->runSecondLevel(taskId);
+		};
 	}
 	m_r->getThreadHive().submitTasks(&tasks[0], m_r->getThreadHive().getThreadCount());
 	m_r->getThreadHive().waitAllTasks();
@@ -185,15 +189,6 @@ Error MainRenderer::render(RenderQueue& rqueue, TexturePtr presentTex)
 	return Error::NONE;
 }
 
-void MainRenderer::executeSecondaryCallback(
-	void* userData, U32 threadId, ThreadHive& hive, ThreadHiveSemaphore* signalSemaphore)
-{
-	MainRenderer& self = *static_cast<MainRenderer*>(userData);
-
-	const U taskId = self.m_runCtx.m_secondaryTaskId.fetchAdd(1);
-	self.m_rgraph->runSecondLevel(taskId);
-}
-
 void MainRenderer::runBlit(RenderPassWorkContext& rgraphCtx)
 {
 	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;

+ 0 - 3
src/anki/renderer/MainRenderer.h

@@ -96,9 +96,6 @@ private:
 
 	void runBlit(RenderPassWorkContext& rgraphCtx);
 	void present(RenderPassWorkContext& rgraphCtx);
-
-	static void executeSecondaryCallback(
-		void* userData, U32 threadId, ThreadHive& hive, ThreadHiveSemaphore* signalSemaphore);
 };
 /// @}
 

+ 214 - 368
src/anki/renderer/Indirect.cpp → src/anki/renderer/ProbeReflections.cpp

@@ -3,7 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#include <anki/renderer/Indirect.h>
+#include <anki/renderer/ProbeReflections.h>
 #include <anki/renderer/LightShading.h>
 #include <anki/renderer/FinalComposite.h>
 #include <anki/renderer/GBuffer.h>
@@ -16,23 +16,23 @@
 namespace anki
 {
 
-Indirect::Indirect(Renderer* r)
+ProbeReflections::ProbeReflections(Renderer* r)
 	: RendererObject(r)
 	, m_lightShading(r)
 {
 }
 
-Indirect::~Indirect()
+ProbeReflections::~ProbeReflections()
 {
 	m_cacheEntries.destroy(getAllocator());
 	m_probeUuidToCacheEntryIdx.destroy(getAllocator());
 }
 
-Error Indirect::init(const ConfigSet& config)
+Error ProbeReflections::init(const ConfigSet& config)
 {
 	ANKI_R_LOGI("Initializing image reflections");
 
-	Error err = initInternal(config);
+	const Error err = initInternal(config);
 	if(err)
 	{
 		ANKI_R_LOGE("Failed to initialize image reflections");
@@ -41,7 +41,7 @@ Error Indirect::init(const ConfigSet& config)
 	return err;
 }
 
-Error Indirect::initInternal(const ConfigSet& config)
+Error ProbeReflections::initInternal(const ConfigSet& config)
 {
 	// Init cache entries
 	m_cacheEntries.create(getAllocator(), config.getNumber("r.indirect.maxSimultaneousProbeCount"));
@@ -66,27 +66,30 @@ Error Indirect::initInternal(const ConfigSet& config)
 	return Error::NONE;
 }
 
-Error Indirect::initGBuffer(const ConfigSet& config)
+Error ProbeReflections::initGBuffer(const ConfigSet& config)
 {
 	m_gbuffer.m_tileSize = config.getNumber("r.indirect.reflectionResolution");
 
 	// Create RT descriptions
 	{
-		RenderTargetDescription texinit = m_r->create2DRenderTargetDescription(
-			m_gbuffer.m_tileSize * 6, m_gbuffer.m_tileSize, MS_COLOR_ATTACHMENT_PIXEL_FORMATS[0], "GI GBuffer");
+		RenderTargetDescription texinit = m_r->create2DRenderTargetDescription(m_gbuffer.m_tileSize * 6,
+			m_gbuffer.m_tileSize,
+			GBUFFER_COLOR_ATTACHMENT_PIXEL_FORMATS[0],
+			"CubeRefl GBuffer");
 
 		// Create color RT descriptions
 		for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
 		{
-			texinit.m_format = MS_COLOR_ATTACHMENT_PIXEL_FORMATS[i];
+			texinit.m_format = GBUFFER_COLOR_ATTACHMENT_PIXEL_FORMATS[i];
 			m_gbuffer.m_colorRtDescrs[i] = texinit;
-			m_gbuffer.m_colorRtDescrs[i].setName(StringAuto(getAllocator()).sprintf("GI GBuff Col #%u", i).toCString());
+			m_gbuffer.m_colorRtDescrs[i].setName(
+				StringAuto(getAllocator()).sprintf("CubeRefl GBuff Col #%u", i).toCString());
 			m_gbuffer.m_colorRtDescrs[i].bake();
 		}
 
 		// Create depth RT
 		texinit.m_format = GBUFFER_DEPTH_ATTACHMENT_PIXEL_FORMAT;
-		texinit.setName("GI GBuff Depth");
+		texinit.setName("CubeRefl GBuff Depth");
 		m_gbuffer.m_depthRtDescr = texinit;
 		m_gbuffer.m_depthRtDescr.bake();
 	}
@@ -110,7 +113,7 @@ Error Indirect::initGBuffer(const ConfigSet& config)
 	return Error::NONE;
 }
 
-Error Indirect::initLightShading(const ConfigSet& config)
+Error ProbeReflections::initLightShading(const ConfigSet& config)
 {
 	m_lightShading.m_tileSize = config.getNumber("r.indirect.reflectionResolution");
 	m_lightShading.m_mipCount = computeMaxMipmapCount2d(m_lightShading.m_tileSize, m_lightShading.m_tileSize, 8);
@@ -121,8 +124,9 @@ Error Indirect::initLightShading(const ConfigSet& config)
 			m_lightShading.m_tileSize,
 			LIGHT_SHADING_COLOR_ATTACHMENT_PIXEL_FORMAT,
 			TextureUsageBit::SAMPLED_FRAGMENT | TextureUsageBit::SAMPLED_COMPUTE
-				| TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE | TextureUsageBit::GENERATE_MIPMAPS,
-			"GI refl");
+				| TextureUsageBit::IMAGE_COMPUTE_READ_WRITE | TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE
+				| TextureUsageBit::GENERATE_MIPMAPS,
+			"CubeRefl refl");
 		texinit.m_mipmapCount = m_lightShading.m_mipCount;
 		texinit.m_type = TextureType::CUBE_ARRAY;
 		texinit.m_layerCount = m_cacheEntries.getSize();
@@ -137,44 +141,39 @@ Error Indirect::initLightShading(const ConfigSet& config)
 	return Error::NONE;
 }
 
-Error Indirect::initIrradiance(const ConfigSet& config)
+Error ProbeReflections::initIrradiance(const ConfigSet& config)
 {
-	// 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::SAMPLED_COMPUTE
-				| TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
-			"GI irr");
-
-		texinit.m_layerCount = m_cacheEntries.getSize();
-		texinit.m_type = TextureType::CUBE_ARRAY;
-		texinit.m_initialUsage = TextureUsageBit::SAMPLED_FRAGMENT;
-
-		m_irradiance.m_cubeArr = m_r->createAndClearRenderTarget(texinit);
-	}
+	m_irradiance.m_workgroupSize = config.getNumber("r.indirect.irradianceResolution");
 
 	// Create prog
 	{
-		ANKI_CHECK(m_r->getResourceManager().loadResource("shaders/Irradiance.glslp", m_irradiance.m_prog));
+		ANKI_CHECK(m_r->getResourceManager().loadResource("shaders/IrradianceDice.glslp", m_irradiance.m_prog));
 
-		const F32 envMapReadMip = computeMaxMipmapCount2d(
-			m_lightShading.m_tileSize, m_lightShading.m_tileSize, m_irradiance.m_envMapReadSize);
+		ShaderProgramResourceConstantValueInitList<1> consts(m_irradiance.m_prog);
+		consts.add("WORKGROUP_SIZE", U32(m_irradiance.m_workgroupSize));
 
-		ShaderProgramResourceConstantValueInitList<2> consts(m_irradiance.m_prog);
-		consts.add("ENV_TEX_TILE_SIZE", U32(m_irradiance.m_envMapReadSize));
-		consts.add("ENV_TEX_MIP", envMapReadMip);
+		ShaderProgramResourceMutationInitList<3> mutations(m_irradiance.m_prog);
+		mutations.add("LIGHT_SHADING_TEX", 1);
+		mutations.add("STORE_LOCATION", 1);
+		mutations.add("SECOND_BOUNCE", 0);
 
 		const ShaderProgramResourceVariant* variant;
-		m_irradiance.m_prog->getOrCreateVariant(consts.get(), variant);
+		m_irradiance.m_prog->getOrCreateVariant(mutations.get(), consts.get(), variant);
 		m_irradiance.m_grProg = variant->getProgram();
 	}
 
+	// Create buff
+	{
+		BufferInitInfo init;
+		init.m_usage = BufferUsageBit::STORAGE_ALL;
+		init.m_size = 6 * sizeof(Vec4);
+		m_irradiance.m_diceValuesBuff = getGrManager().newBuffer(init);
+	}
+
 	return Error::NONE;
 }
 
-Error Indirect::initIrradianceToRefl(const ConfigSet& cfg)
+Error ProbeReflections::initIrradianceToRefl(const ConfigSet& cfg)
 {
 	// Create program
 	ANKI_CHECK(
@@ -187,14 +186,14 @@ Error Indirect::initIrradianceToRefl(const ConfigSet& cfg)
 	return Error::NONE;
 }
 
-Error Indirect::initShadowMapping(const ConfigSet& cfg)
+Error ProbeReflections::initShadowMapping(const ConfigSet& cfg)
 {
 	const U resolution = cfg.getNumber("r.indirect.shadowMapResolution");
 	ANKI_ASSERT(resolution > 8);
 
 	// RT descr
 	m_shadowMapping.m_rtDescr =
-		m_r->create2DRenderTargetDescription(resolution * 6, resolution, Format::D32_SFLOAT, "GI SM");
+		m_r->create2DRenderTargetDescription(resolution * 6, resolution, Format::D32_SFLOAT, "CubeRefl SM");
 	m_shadowMapping.m_rtDescr.bake();
 
 	// FB descr
@@ -217,52 +216,29 @@ Error Indirect::initShadowMapping(const ConfigSet& cfg)
 	return Error::NONE;
 }
 
-void Indirect::initCacheEntry(U32 cacheEntryIdx)
+void ProbeReflections::initCacheEntry(U32 cacheEntryIdx)
 {
 	CacheEntry& cacheEntry = m_cacheEntries[cacheEntryIdx];
 
 	for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
 	{
 		// Light pass FB
-		{
-			FramebufferDescription& fbDescr = cacheEntry.m_lightShadingFbDescrs[faceIdx];
-			ANKI_ASSERT(!fbDescr.isBacked());
-			fbDescr.m_colorAttachmentCount = 1;
-			fbDescr.m_colorAttachments[0].m_surface.m_layer = cacheEntryIdx;
-			fbDescr.m_colorAttachments[0].m_surface.m_face = faceIdx;
-			fbDescr.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::CLEAR;
-			fbDescr.bake();
-		}
-
-		// Irradiance FB
-		{
-			FramebufferDescription& fbDescr = cacheEntry.m_irradianceFbDescrs[faceIdx];
-			ANKI_ASSERT(!fbDescr.isBacked());
-			fbDescr.m_colorAttachmentCount = 1;
-			fbDescr.m_colorAttachments[0].m_surface.m_layer = cacheEntryIdx;
-			fbDescr.m_colorAttachments[0].m_surface.m_face = faceIdx;
-			fbDescr.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::DONT_CARE;
-			fbDescr.bake();
-		}
-
-		// Irradiance to Refl FB
-		{
-			FramebufferDescription& fbDescr = cacheEntry.m_irradianceToReflFbDescrs[faceIdx];
-			ANKI_ASSERT(!fbDescr.isBacked());
-			fbDescr.m_colorAttachmentCount = 1;
-			fbDescr.m_colorAttachments[0].m_surface.m_layer = cacheEntryIdx;
-			fbDescr.m_colorAttachments[0].m_surface.m_face = faceIdx;
-			fbDescr.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::LOAD;
-			fbDescr.bake();
-		}
+		FramebufferDescription& fbDescr = cacheEntry.m_lightShadingFbDescrs[faceIdx];
+		ANKI_ASSERT(!fbDescr.isBacked());
+		fbDescr.m_colorAttachmentCount = 1;
+		fbDescr.m_colorAttachments[0].m_surface.m_layer = cacheEntryIdx;
+		fbDescr.m_colorAttachments[0].m_surface.m_face = faceIdx;
+		fbDescr.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::CLEAR;
+		fbDescr.bake();
 	}
 }
 
-void Indirect::prepareProbes(
-	RenderingContext& ctx, ReflectionProbeQueueElement*& probeToUpdate, U32& probeToUpdateCacheEntryIdx)
+void ProbeReflections::prepareProbes(RenderingContext& ctx,
+	ReflectionProbeQueueElement*& probeToUpdateThisFrame,
+	U32& probeToUpdateThisFrameCacheEntryIdx)
 {
-	probeToUpdate = nullptr;
-	probeToUpdateCacheEntryIdx = MAX_U32;
+	probeToUpdateThisFrame = nullptr;
+	probeToUpdateThisFrameCacheEntryIdx = MAX_U32;
 
 	if(ANKI_UNLIKELY(ctx.m_renderQueue->m_reflectionProbes.getSize() == 0))
 	{
@@ -276,44 +252,55 @@ void Indirect::prepareProbes(
 	DynamicArray<ReflectionProbeQueueElement> newListOfProbes;
 	newListOfProbes.create(ctx.m_tempAllocator, ctx.m_renderQueue->m_reflectionProbes.getSize());
 	U newListOfProbeCount = 0;
-
-	Bool foundProbeToRenderNextFrame = false;
+	Bool foundProbeToUpdateNextFrame = 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))
+		const U32 cacheEntryIdx = findBestCacheEntry(
+			probe.m_uuid, m_r->getGlobalTimestamp(), m_cacheEntries, m_probeUuidToCacheEntryIdx, getAllocator());
+		if(ANKI_UNLIKELY(cacheEntryIdx == MAX_U32))
 		{
+			// Failed
+			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");
 			continue;
 		}
 
+		const Bool probeFoundInCache = m_cacheEntries[cacheEntryIdx].m_uuid == probe.m_uuid;
+
 		// 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 needsUpdate = !probeFoundInCache;
+		if(ANKI_UNLIKELY(needsUpdate))
 		{
-			const Bool updateFailed = probeToUpdate != nullptr || probe.m_renderQueues[0] == nullptr;
+			const Bool canUpdateThisFrame = probeToUpdateThisFrame == nullptr && probe.m_renderQueues[0] != nullptr;
+			const Bool canUpdateNextFrame = !foundProbeToUpdateNextFrame;
 
-			if(updateFailed && !foundProbeToRenderNextFrame)
+			if(!canUpdateThisFrame && canUpdateNextFrame)
 			{
 				// Probe will be updated next frame
-				foundProbeToRenderNextFrame = true;
+				foundProbeToUpdateNextFrame = true;
 				probe.m_feedbackCallback(true, probe.m_userData);
+				continue;
 			}
-
-			if(updateFailed)
+			else if(!canUpdateThisFrame)
 			{
+				// Can't be updated this frame so remove it from the list
 				continue;
 			}
+			else
+			{
+				// Can be updated this frame so continue with it
+				probeToUpdateThisFrameCacheEntryIdx = cacheEntryIdx;
+				probeToUpdateThisFrame = &newListOfProbes[newListOfProbeCount];
+			}
 		}
 
 		// 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_uuid = probe.m_uuid;
 		m_cacheEntries[cacheEntryIdx].m_lastUsedTimestamp = m_r->getGlobalTimestamp();
 
 		// Update the probe
@@ -323,7 +310,7 @@ void Indirect::prepareProbes(
 		newListOfProbes[newListOfProbeCount++] = probe;
 
 		// Update cache map
-		if(!cacheEntryFoundInCache)
+		if(!probeFoundInCache)
 		{
 			m_probeUuidToCacheEntryIdx.emplace(getAllocator(), probe.m_uuid, cacheEntryIdx);
 		}
@@ -333,15 +320,6 @@ void Indirect::prepareProbes(
 		{
 			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];
-		}
 	}
 
 	// Replace the probe list in the queue
@@ -359,44 +337,41 @@ void Indirect::prepareProbes(
 	}
 }
 
-void Indirect::runGBuffer(CommandBufferPtr& cmdb)
+void ProbeReflections::runGBuffer(U32 faceIdx, CommandBufferPtr& cmdb)
 {
 	ANKI_ASSERT(m_ctx.m_probe);
-	ANKI_TRACE_SCOPED_EVENT(R_IR);
+	ANKI_TRACE_SCOPED_EVENT(R_CUBE_REFL);
 	const ReflectionProbeQueueElement& probe = *m_ctx.m_probe;
 
-	// For each face
-	for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
-	{
-		const U32 viewportX = faceIdx * m_gbuffer.m_tileSize;
-		cmdb->setViewport(viewportX, 0, m_gbuffer.m_tileSize, m_gbuffer.m_tileSize);
-		cmdb->setScissor(viewportX, 0, m_gbuffer.m_tileSize, m_gbuffer.m_tileSize);
+	const U32 viewportX = faceIdx * m_gbuffer.m_tileSize;
+	cmdb->setViewport(viewportX, 0, m_gbuffer.m_tileSize, m_gbuffer.m_tileSize);
+	cmdb->setScissor(viewportX, 0, m_gbuffer.m_tileSize, m_gbuffer.m_tileSize);
 
-		/// Draw
-		ANKI_ASSERT(probe.m_renderQueues[faceIdx]);
-		const RenderQueue& rqueue = *probe.m_renderQueues[faceIdx];
+	// Draw
+	ANKI_ASSERT(probe.m_renderQueues[faceIdx]);
+	const RenderQueue& rqueue = *probe.m_renderQueues[faceIdx];
 
-		if(!rqueue.m_renderables.isEmpty())
-		{
-			m_r->getSceneDrawer().drawRange(Pass::GB,
-				rqueue.m_viewMatrix,
-				rqueue.m_viewProjectionMatrix,
-				Mat4::getIdentity(), // Don't care about prev mats
-				cmdb,
-				m_r->getSamplers().m_trilinearRepeatAniso,
-				rqueue.m_renderables.getBegin(),
-				rqueue.m_renderables.getEnd());
-		}
+	if(!rqueue.m_renderables.isEmpty())
+	{
+		m_r->getSceneDrawer().drawRange(Pass::GB,
+			rqueue.m_viewMatrix,
+			rqueue.m_viewProjectionMatrix,
+			Mat4::getIdentity(), // Don't care about prev mats
+			cmdb,
+			m_r->getSamplers().m_trilinearRepeatAniso,
+			rqueue.m_renderables.getBegin(),
+			rqueue.m_renderables.getEnd(),
+			MAX_LOD_COUNT - 1);
 	}
 
 	// Restore state
 	cmdb->setScissor(0, 0, MAX_U32, MAX_U32);
 }
 
-void Indirect::runLightShading(U32 faceIdx, RenderPassWorkContext& rgraphCtx)
+void ProbeReflections::runLightShading(U32 faceIdx, RenderPassWorkContext& rgraphCtx)
 {
 	ANKI_ASSERT(faceIdx <= 6);
-	ANKI_TRACE_SCOPED_EVENT(R_IR);
+	ANKI_TRACE_SCOPED_EVENT(R_CUBE_REFL);
 
 	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
 
@@ -425,26 +400,32 @@ void Indirect::runLightShading(U32 faceIdx, RenderPassWorkContext& rgraphCtx)
 		rgraphCtx.bindTexture(0, 8, m_ctx.m_shadowMapRt, TextureSubresourceInfo(DepthStencilAspectBit::DEPTH));
 	}
 
-	m_lightShading.m_deferred.drawLights(rqueue.m_viewProjectionMatrix,
-		rqueue.m_viewProjectionMatrix.getInverse(),
-		rqueue.m_cameraTransform.getTranslationPart(),
-		UVec4(0, 0, m_lightShading.m_tileSize, m_lightShading.m_tileSize),
-		Vec2(faceIdx * (1.0f / 6.0f), 0.0f),
-		Vec2((faceIdx + 1) * (1.0f / 6.0f), 1.0f),
-		probe.m_renderQueues[faceIdx]->m_cameraNear,
-		probe.m_renderQueues[faceIdx]->m_cameraFar,
-		(hasDirLight) ? &probe.m_renderQueues[faceIdx]->m_directionalLight : nullptr,
-		rqueue.m_pointLights,
-		rqueue.m_spotLights,
-		cmdb);
+	TraditionalDeferredLightShadingDrawInfo dsInfo;
+	dsInfo.m_viewProjectionMatrix = rqueue.m_viewProjectionMatrix;
+	dsInfo.m_invViewProjectionMatrix = rqueue.m_viewProjectionMatrix.getInverse();
+	dsInfo.m_cameraPosWSpace = rqueue.m_cameraTransform.getTranslationPart();
+	dsInfo.m_viewport = UVec4(0, 0, m_lightShading.m_tileSize, m_lightShading.m_tileSize);
+	dsInfo.m_gbufferTexCoordsScale =
+		Vec2(1.0f / F32(m_lightShading.m_tileSize * 6), 1.0f / F32(m_lightShading.m_tileSize));
+	dsInfo.m_gbufferTexCoordsBias = Vec2(F32(faceIdx) * (1.0f / 6.0f), 0.0f);
+	dsInfo.m_lightbufferTexCoordsScale =
+		Vec2(1.0f / F32(m_lightShading.m_tileSize), 1.0f / F32(m_lightShading.m_tileSize));
+	dsInfo.m_lightbufferTexCoordsBias = Vec2(0.0f, 0.0f);
+	dsInfo.m_cameraNear = probe.m_renderQueues[faceIdx]->m_cameraNear;
+	dsInfo.m_cameraFar = probe.m_renderQueues[faceIdx]->m_cameraFar;
+	dsInfo.m_directionalLight = (hasDirLight) ? &probe.m_renderQueues[faceIdx]->m_directionalLight : nullptr;
+	dsInfo.m_pointLights = rqueue.m_pointLights;
+	dsInfo.m_spotLights = rqueue.m_spotLights;
+	dsInfo.m_commandBuffer = cmdb;
+	m_lightShading.m_deferred.drawLights(dsInfo);
 }
 
-void Indirect::runMipmappingOfLightShading(U32 faceIdx, RenderPassWorkContext& rgraphCtx)
+void ProbeReflections::runMipmappingOfLightShading(U32 faceIdx, RenderPassWorkContext& rgraphCtx)
 {
 	ANKI_ASSERT(faceIdx < 6);
 	ANKI_ASSERT(m_ctx.m_cacheEntryIdx < m_cacheEntries.getSize());
 
-	ANKI_TRACE_SCOPED_EVENT(R_IR);
+	ANKI_TRACE_SCOPED_EVENT(R_CUBE_REFL);
 
 	TextureSubresourceInfo subresource(TextureSurfaceInfo(0, 0, faceIdx, m_ctx.m_cacheEntryIdx));
 	subresource.m_mipmapCount = m_lightShading.m_mipCount;
@@ -457,76 +438,64 @@ void Indirect::runMipmappingOfLightShading(U32 faceIdx, RenderPassWorkContext& r
 	rgraphCtx.m_commandBuffer->generateMipmaps2d(getGrManager().newTextureView(viewInit));
 }
 
-void Indirect::runIrradiance(U32 faceIdx, RenderPassWorkContext& rgraphCtx)
+void ProbeReflections::runIrradiance(RenderPassWorkContext& rgraphCtx)
 {
-	ANKI_ASSERT(faceIdx < 6);
-	ANKI_TRACE_SCOPED_EVENT(R_IR);
+	ANKI_TRACE_SCOPED_EVENT(R_CUBE_REFL);
 	const U32 cacheEntryIdx = m_ctx.m_cacheEntryIdx;
 	ANKI_ASSERT(cacheEntryIdx < m_cacheEntries.getSize());
 
 	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
 
-	// Set state
-	cmdb->setViewport(0, 0, m_irradiance.m_tileSize, m_irradiance.m_tileSize);
 	cmdb->bindShaderProgram(m_irradiance.m_grProg);
 
-	cmdb->bindSampler(0, 0, m_r->getSamplers().m_trilinearClamp);
+	// Bind stuff
+	cmdb->bindSampler(0, 0, m_r->getSamplers().m_nearestNearestClamp);
 
 	TextureSubresourceInfo subresource;
 	subresource.m_faceCount = 6;
 	subresource.m_firstLayer = cacheEntryIdx;
 	rgraphCtx.bindTexture(0, 1, m_ctx.m_lightShadingRt, subresource);
 
-	// Set uniforms
-	UVec4 pushConsts(faceIdx);
-	cmdb->setPushConstants(&pushConsts, sizeof(pushConsts));
+	allocateAndBindStorage<void*>(
+		sizeof(Vec4) * 6 * m_irradiance.m_workgroupSize * m_irradiance.m_workgroupSize, cmdb, 0, 3);
+
+	cmdb->bindStorageBuffer(0, 4, m_irradiance.m_diceValuesBuff, 0, m_irradiance.m_diceValuesBuff->getSize());
 
 	// Draw
-	drawQuad(cmdb);
+	cmdb->dispatchCompute(1, 1, 1);
 }
 
-void Indirect::runIrradianceToRefl(U32 faceIdx, RenderPassWorkContext& rgraphCtx)
+void ProbeReflections::runIrradianceToRefl(RenderPassWorkContext& rgraphCtx)
 {
-	ANKI_ASSERT(faceIdx < 6);
-	ANKI_TRACE_SCOPED_EVENT(R_IR);
+	ANKI_TRACE_SCOPED_EVENT(R_CUBE_REFL);
 
 	const U32 cacheEntryIdx = m_ctx.m_cacheEntryIdx;
 	ANKI_ASSERT(cacheEntryIdx < m_cacheEntries.getSize());
 
 	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
 
-	// Set state
-	cmdb->setViewport(0, 0, m_lightShading.m_tileSize, m_lightShading.m_tileSize);
-	cmdb->setBlendFactors(0, BlendFactor::ONE, BlendFactor::ONE);
-
 	cmdb->bindShaderProgram(m_irradianceToRefl.m_grProg);
 
 	// Bind resources
 	cmdb->bindSampler(0, 0, m_r->getSamplers().m_nearestNearestClamp);
-	cmdb->bindSampler(0, 1, m_r->getSamplers().m_trilinearClamp);
 
-	rgraphCtx.bindColorTexture(0, 2, m_ctx.m_gbufferColorRts[0]);
-	rgraphCtx.bindColorTexture(0, 3, m_ctx.m_gbufferColorRts[1]);
-	rgraphCtx.bindColorTexture(0, 4, m_ctx.m_gbufferColorRts[2]);
+	rgraphCtx.bindColorTexture(0, 1, m_ctx.m_gbufferColorRts[0], 0);
+	rgraphCtx.bindColorTexture(0, 1, m_ctx.m_gbufferColorRts[1], 1);
+	rgraphCtx.bindColorTexture(0, 1, m_ctx.m_gbufferColorRts[2], 2);
+
+	cmdb->bindStorageBuffer(0, 2, m_irradiance.m_diceValuesBuff, 0, m_irradiance.m_diceValuesBuff->getSize());
 
 	TextureSubresourceInfo subresource;
 	subresource.m_faceCount = 6;
 	subresource.m_firstLayer = cacheEntryIdx;
-	rgraphCtx.bindTexture(0, 5, m_ctx.m_irradianceRt, subresource);
-
-	Vec4 pushConsts(faceIdx);
-	cmdb->setPushConstants(&pushConsts, sizeof(pushConsts));
-
-	// Draw
-	drawQuad(cmdb);
+	rgraphCtx.bindImage(0, 3, m_ctx.m_lightShadingRt, subresource);
 
-	// Restore state
-	cmdb->setBlendFactors(0, BlendFactor::ONE, BlendFactor::ZERO);
+	dispatchPPCompute(cmdb, 8, 8, m_lightShading.m_tileSize, m_lightShading.m_tileSize);
 }
 
-void Indirect::populateRenderGraph(RenderingContext& rctx)
+void ProbeReflections::populateRenderGraph(RenderingContext& rctx)
 {
-	ANKI_TRACE_SCOPED_EVENT(R_IR);
+	ANKI_TRACE_SCOPED_EVENT(R_CUBE_REFL);
 
 #if ANKI_EXTRA_CHECKS
 	m_ctx = {};
@@ -544,8 +513,6 @@ void Indirect::populateRenderGraph(RenderingContext& rctx)
 		// Just import and exit
 
 		m_ctx.m_lightShadingRt = rgraph.importRenderTarget(m_lightShading.m_cubeArr, TextureUsageBit::SAMPLED_FRAGMENT);
-		m_ctx.m_irradianceRt = rgraph.importRenderTarget(m_irradiance.m_cubeArr, TextureUsageBit::SAMPLED_FRAGMENT);
-
 		return;
 	}
 
@@ -569,14 +536,15 @@ void Indirect::populateRenderGraph(RenderingContext& rctx)
 		m_ctx.m_gbufferDepthRt = rgraph.newRenderTarget(m_gbuffer.m_depthRtDescr);
 
 		// Pass
-		GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("GI gbuff");
+		GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("CubeRefl gbuff");
 		pass.setFramebufferInfo(m_gbuffer.m_fbDescr, rts, m_ctx.m_gbufferDepthRt);
 		pass.setWork(
 			[](RenderPassWorkContext& rgraphCtx) {
-				static_cast<Indirect*>(rgraphCtx.m_userData)->runGBuffer(rgraphCtx.m_commandBuffer);
+				static_cast<ProbeReflections*>(rgraphCtx.m_userData)
+					->runGBuffer(rgraphCtx.m_currentSecondLevelCommandBufferIndex, rgraphCtx.m_commandBuffer);
 			},
 			this,
-			0);
+			6);
 
 		for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
 		{
@@ -626,14 +594,15 @@ void Indirect::populateRenderGraph(RenderingContext& rctx)
 		m_ctx.m_shadowMapRt = rgraph.newRenderTarget(m_shadowMapping.m_rtDescr);
 
 		// Pass
-		GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("GI SM");
+		GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("CubeRefl SM");
 		pass.setFramebufferInfo(m_shadowMapping.m_fbDescr, {}, m_ctx.m_shadowMapRt);
 		pass.setWork(
 			[](RenderPassWorkContext& rgraphCtx) {
-				static_cast<Indirect*>(rgraphCtx.m_userData)->runShadowMapping(rgraphCtx.m_commandBuffer);
+				static_cast<ProbeReflections*>(rgraphCtx.m_userData)
+					->runShadowMapping(rgraphCtx.m_currentSecondLevelCommandBufferIndex, rgraphCtx.m_commandBuffer);
 			},
 			this,
-			0);
+			6);
 
 		TextureSubresourceInfo subresource(DepthStencilAspectBit::DEPTH);
 		pass.newDependency({m_ctx.m_shadowMapRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE, subresource});
@@ -656,12 +625,12 @@ void Indirect::populateRenderGraph(RenderingContext& rctx)
 		m_ctx.m_lightShadingRt = rgraph.importRenderTarget(m_lightShading.m_cubeArr, TextureUsageBit::SAMPLED_FRAGMENT);
 
 		// Passes
-		static const Array<CString, 6> passNames = {{"GI LightShad #0",
-			"GI LightShad #1",
-			"GI LightShad #2",
-			"GI LightShad #3",
-			"GI LightShad #4",
-			"GI LightShad #5"}};
+		static const Array<CString, 6> passNames = {{"CubeRefl LightShad #0",
+			"CubeRefl LightShad #1",
+			"CubeRefl LightShad #2",
+			"CubeRefl LightShad #3",
+			"CubeRefl LightShad #4",
+			"CubeRefl LightShad #5"}};
 		for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
 		{
 			GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass(passNames[faceIdx]);
@@ -690,109 +659,49 @@ void Indirect::populateRenderGraph(RenderingContext& rctx)
 
 	// Irradiance passes
 	{
-		static const Array<RenderPassWorkCallback, 6> callbacks = {{runIrradianceCallback<0>,
-			runIrradianceCallback<1>,
-			runIrradianceCallback<2>,
-			runIrradianceCallback<3>,
-			runIrradianceCallback<4>,
-			runIrradianceCallback<5>}};
-
-		// Rt
-		m_ctx.m_irradianceRt = rgraph.importRenderTarget(m_irradiance.m_cubeArr, TextureUsageBit::SAMPLED_FRAGMENT);
-
-		static const Array<CString, 6> passNames = {
-			{"GI Irr/ce #0", "GI Irr/ce #1", "GI Irr/ce #2", "GI Irr/ce #3", "GI Irr/ce #4", "GI Irr/ce #5"}};
-		for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
-		{
-			GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass(passNames[faceIdx]);
+		m_ctx.m_irradianceDiceValuesBuffHandle =
+			rgraph.importBuffer(m_irradiance.m_diceValuesBuff, BufferUsageBit::NONE);
 
-			pass.setFramebufferInfo(
-				m_cacheEntries[probeToUpdateCacheEntryIdx].m_irradianceFbDescrs[faceIdx], {{m_ctx.m_irradianceRt}}, {});
+		ComputeRenderPassDescription& pass = rgraph.newComputeRenderPass("CubeRefl Irradiance");
 
-			pass.setWork(callbacks[faceIdx], this, 0);
+		pass.setWork(
+			[](RenderPassWorkContext& rgraphCtx) {
+				static_cast<ProbeReflections*>(rgraphCtx.m_userData)->runIrradiance(rgraphCtx);
+			},
+			this,
+			0);
 
-			// Read a cube but only one layer and level
-			TextureSubresourceInfo readSubresource;
-			readSubresource.m_faceCount = 6;
-			readSubresource.m_firstLayer = probeToUpdateCacheEntryIdx;
-			pass.newDependency({m_ctx.m_lightShadingRt, TextureUsageBit::SAMPLED_FRAGMENT, readSubresource});
+		// Read a cube but only one layer and level
+		TextureSubresourceInfo readSubresource;
+		readSubresource.m_faceCount = 6;
+		readSubresource.m_firstLayer = probeToUpdateCacheEntryIdx;
+		pass.newDependency({m_ctx.m_lightShadingRt, TextureUsageBit::SAMPLED_COMPUTE, readSubresource});
 
-			TextureSubresourceInfo writeSubresource(TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
-			pass.newDependency({m_ctx.m_irradianceRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE, writeSubresource});
-		}
+		pass.newDependency({m_ctx.m_irradianceDiceValuesBuffHandle, BufferUsageBit::STORAGE_COMPUTE_WRITE});
 	}
 
 	// Write irradiance back to refl
 	{
-		static const Array<RenderPassWorkCallback, 6> callbacks = {{runIrradianceToReflCallback<0>,
-			runIrradianceToReflCallback<1>,
-			runIrradianceToReflCallback<2>,
-			runIrradianceToReflCallback<3>,
-			runIrradianceToReflCallback<4>,
-			runIrradianceToReflCallback<5>}};
-
-		// Rt
-		static const Array<CString, 6> passNames = {{"GI Irr2Refl #0",
-			"GI Irr2Refl #1",
-			"GI Irr2Refl #2",
-			"GI Irr2Refl #3",
-			"GI Irr2Refl #4",
-			"GI Irr2Refl #5"}};
-		for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
-		{
-			GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass(passNames[faceIdx]);
-
-			pass.setFramebufferInfo(m_cacheEntries[probeToUpdateCacheEntryIdx].m_irradianceToReflFbDescrs[faceIdx],
-				{{m_ctx.m_lightShadingRt}},
-				{});
-
-			pass.setWork(callbacks[faceIdx], this, 0);
-
-			for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
-			{
-				pass.newDependency({m_ctx.m_gbufferColorRts[i], TextureUsageBit::SAMPLED_FRAGMENT});
-			}
-
-			TextureSubresourceInfo readSubresource;
-			readSubresource.m_faceCount = 6;
-			readSubresource.m_firstLayer = probeToUpdateCacheEntryIdx;
-			pass.newDependency({m_ctx.m_irradianceRt, TextureUsageBit::SAMPLED_FRAGMENT, readSubresource});
+		ComputeRenderPassDescription& pass = rgraph.newComputeRenderPass("CubeRefl apply indirect");
 
-			TextureSubresourceInfo writeSubresource(TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
-			pass.newDependency(
-				{m_ctx.m_lightShadingRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE, writeSubresource});
-		}
-	}
+		pass.setWork(
+			[](RenderPassWorkContext& rgraphCtx) {
+				static_cast<ProbeReflections*>(rgraphCtx.m_userData)->runIrradianceToRefl(rgraphCtx);
+			},
+			this,
+			0);
 
-	// Irradiance passes 2nd bounce
-	{
-		static const Array<RenderPassWorkCallback, 6> callbacks = {{runIrradianceCallback<0>,
-			runIrradianceCallback<1>,
-			runIrradianceCallback<2>,
-			runIrradianceCallback<3>,
-			runIrradianceCallback<4>,
-			runIrradianceCallback<5>}};
-
-		static const Array<CString, 6> passNames = {
-			{"GI Irr 2nd #0", "GI Irr 2nd #1", "GI Irr 2nd #2", "GI Irr 2nd #3", "GI Irr 2nd #4", "GI Irr 2nd #5"}};
-		for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
+		for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT - 1; ++i)
 		{
-			GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass(passNames[faceIdx]);
-
-			pass.setFramebufferInfo(
-				m_cacheEntries[probeToUpdateCacheEntryIdx].m_irradianceFbDescrs[faceIdx], {{m_ctx.m_irradianceRt}}, {});
-
-			pass.setWork(callbacks[faceIdx], this, 0);
+			pass.newDependency({m_ctx.m_gbufferColorRts[i], TextureUsageBit::SAMPLED_COMPUTE});
+		}
 
-			// Read a cube but only one layer and level
-			TextureSubresourceInfo readSubresource;
-			readSubresource.m_faceCount = 6;
-			readSubresource.m_firstLayer = probeToUpdateCacheEntryIdx;
-			pass.newDependency({m_ctx.m_lightShadingRt, TextureUsageBit::SAMPLED_FRAGMENT, readSubresource});
+		TextureSubresourceInfo subresource;
+		subresource.m_faceCount = 6;
+		subresource.m_firstLayer = probeToUpdateCacheEntryIdx;
+		pass.newDependency({m_ctx.m_lightShadingRt, TextureUsageBit::IMAGE_COMPUTE_READ_WRITE, subresource});
 
-			TextureSubresourceInfo writeSubresource(TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
-			pass.newDependency({m_ctx.m_irradianceRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE, writeSubresource});
-		}
+		pass.newDependency({m_ctx.m_irradianceDiceValuesBuffHandle, BufferUsageBit::STORAGE_COMPUTE_READ});
 	}
 
 	// Mipmapping "passes"
@@ -804,8 +713,12 @@ void Indirect::populateRenderGraph(RenderingContext& rctx)
 			runMipmappingOfLightShadingCallback<4>,
 			runMipmappingOfLightShadingCallback<5>}};
 
-		static const Array<CString, 6> passNames = {
-			{"GI Mip #0", "GI Mip #1", "GI Mip #2", "GI Mip #3", "GI Mip #4", "GI Mip #5"}};
+		static const Array<CString, 6> passNames = {{"CubeRefl Mip #0",
+			"CubeRefl Mip #1",
+			"CubeRefl Mip #2",
+			"CubeRefl Mip #3",
+			"CubeRefl Mip #4",
+			"CubeRefl Mip #5"}};
 		for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
 		{
 			GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass(passNames[faceIdx]);
@@ -819,104 +732,37 @@ void Indirect::populateRenderGraph(RenderingContext& rctx)
 	}
 }
 
-Bool Indirect::findBestCacheEntry(U64 probeUuid, U32& cacheEntryIdxAllocated, Bool& cacheEntryFound)
+void ProbeReflections::runShadowMapping(U32 faceIdx, CommandBufferPtr& cmdb)
 {
-	ANKI_ASSERT(probeUuid > 0);
+	cmdb->setPolygonOffset(1.0f, 1.0f);
 
-	// First, try to see if the probe is in the cache
-	auto it = m_probeUuidToCacheEntryIdx.find(probeUuid);
-	if(it != m_probeUuidToCacheEntryIdx.getEnd())
-	{
-		const U32 cacheEntryIdx = *it;
-		if(m_cacheEntries[cacheEntryIdx].m_probeUuid == probeUuid)
-		{
-			// Found it
-			cacheEntryIdxAllocated = cacheEntryIdx;
-			cacheEntryFound = true;
-			return false;
-		}
-		else
-		{
-			// Cache entry is wrong, remove it
-			m_probeUuidToCacheEntryIdx.erase(getAllocator(), it);
-		}
-	}
-	cacheEntryFound = false;
+	ANKI_ASSERT(m_ctx.m_probe);
+	ANKI_ASSERT(m_ctx.m_probe->m_renderQueues[faceIdx]);
+	const RenderQueue& faceRenderQueue = *m_ctx.m_probe->m_renderQueues[faceIdx];
+	ANKI_ASSERT(faceRenderQueue.m_directionalLight.m_uuid != 0);
+	ANKI_ASSERT(faceRenderQueue.m_directionalLight.m_shadowCascadeCount == 1);
 
-	// 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)
-		{
-			// Found an empty
-			emptyCacheEntryIdx = cacheEntryIdx;
-			break;
-		}
-		else if(m_cacheEntries[cacheEntryIdx].m_lastUsedTimestamp != m_r->getGlobalTimestamp()
-				&& m_cacheEntries[cacheEntryIdx].m_lastUsedTimestamp < cacheEntryIdxToKickMinTimestamp)
-		{
-			// Found some with low timestamp
-			cacheEntryIdxToKick = cacheEntryIdx;
-			cacheEntryIdxToKickMinTimestamp = m_cacheEntries[cacheEntryIdx].m_lastUsedTimestamp;
-		}
-	}
+	ANKI_ASSERT(faceRenderQueue.m_directionalLight.m_shadowRenderQueues[0]);
+	const RenderQueue& cascadeRenderQueue = *faceRenderQueue.m_directionalLight.m_shadowRenderQueues[0];
 
-	Bool failed = false;
-	if(emptyCacheEntryIdx != MAX_U32)
-	{
-		cacheEntryIdxAllocated = emptyCacheEntryIdx;
-	}
-	else if(cacheEntryIdxToKick != MAX_U32)
-	{
-		cacheEntryIdxAllocated = cacheEntryIdxToKick;
-	}
-	else
+	if(cascadeRenderQueue.m_renderables.getSize() == 0)
 	{
-		// 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");
+		return;
 	}
 
-	return failed;
-}
-
-void Indirect::runShadowMapping(CommandBufferPtr& cmdb)
-{
-	cmdb->setPolygonOffset(1.0f, 1.0f);
-
-	for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
-	{
-		ANKI_ASSERT(m_ctx.m_probe);
-		ANKI_ASSERT(m_ctx.m_probe->m_renderQueues[faceIdx]);
-		const RenderQueue& faceRenderQueue = *m_ctx.m_probe->m_renderQueues[faceIdx];
-		ANKI_ASSERT(faceRenderQueue.m_directionalLight.m_uuid != 0);
-		ANKI_ASSERT(faceRenderQueue.m_directionalLight.m_shadowCascadeCount == 1);
-
-		ANKI_ASSERT(faceRenderQueue.m_directionalLight.m_shadowRenderQueues[0]);
-		const RenderQueue& cascadeRenderQueue = *faceRenderQueue.m_directionalLight.m_shadowRenderQueues[0];
-
-		if(cascadeRenderQueue.m_renderables.getSize() == 0)
-		{
-			continue;
-		}
-
-		const U rez = m_shadowMapping.m_rtDescr.m_height;
-		cmdb->setViewport(rez * faceIdx, 0, rez, rez);
-		cmdb->setScissor(rez * faceIdx, 0, rez, rez);
-
-		m_r->getSceneDrawer().drawRange(Pass::SM,
-			cascadeRenderQueue.m_viewMatrix,
-			cascadeRenderQueue.m_viewProjectionMatrix,
-			Mat4::getIdentity(), // Don't care about prev matrices here
-			cmdb,
-			m_r->getSamplers().m_trilinearRepeatAniso,
-			cascadeRenderQueue.m_renderables.getBegin(),
-			cascadeRenderQueue.m_renderables.getEnd());
-	}
+	const U rez = m_shadowMapping.m_rtDescr.m_height;
+	cmdb->setViewport(rez * faceIdx, 0, rez, rez);
+	cmdb->setScissor(rez * faceIdx, 0, rez, rez);
+
+	m_r->getSceneDrawer().drawRange(Pass::SM,
+		cascadeRenderQueue.m_viewMatrix,
+		cascadeRenderQueue.m_viewProjectionMatrix,
+		Mat4::getIdentity(), // Don't care about prev matrices here
+		cmdb,
+		m_r->getSamplers().m_trilinearRepeatAniso,
+		cascadeRenderQueue.m_renderables.getBegin(),
+		cascadeRenderQueue.m_renderables.getEnd(),
+		MAX_LOD_COUNT - 1);
 
 	cmdb->setPolygonOffset(0.0f, 0.0f);
 }

+ 15 - 43
src/anki/renderer/Indirect.h → src/anki/renderer/ProbeReflections.h

@@ -17,15 +17,15 @@ namespace anki
 /// @addtogroup renderer
 /// @{
 
-/// Probe reflections and irradiance.
-class Indirect : public RendererObject
+/// Probe reflections.
+class ProbeReflections : public RendererObject
 {
 	friend class IrTask;
 
 anki_internal:
-	Indirect(Renderer* r);
+	ProbeReflections(Renderer* r);
 
-	~Indirect();
+	~ProbeReflections();
 
 	ANKI_USE_RESULT Error init(const ConfigSet& cfg);
 
@@ -52,11 +52,6 @@ anki_internal:
 		return m_ctx.m_lightShadingRt;
 	}
 
-	RenderTargetHandle getIrradianceRt() const
-	{
-		return m_ctx.m_irradianceRt;
-	}
-
 private:
 	class
 	{
@@ -85,12 +80,10 @@ private:
 	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;
-
 		ShaderProgramResourcePtr m_prog;
 		ShaderProgramPtr m_grProg;
+		BufferPtr m_diceValuesBuff;
+		U32 m_workgroupSize = 16;
 	} m_irradiance; ///< Irradiance.
 
 	class
@@ -111,12 +104,10 @@ private:
 	class CacheEntry
 	{
 	public:
-		U64 m_probeUuid;
-		Timestamp m_lastUsedTimestamp = 0; ///< When it was rendered.
+		U64 m_uuid; ///< Probe UUID.
+		Timestamp m_lastUsedTimestamp = 0; ///< When it was last seen by the renderer.
 
 		Array<FramebufferDescription, 6> m_lightShadingFbDescrs;
-		Array<FramebufferDescription, 6> m_irradianceFbDescrs;
-		Array<FramebufferDescription, 6> m_irradianceToReflFbDescrs;
 	};
 
 	DynamicArray<CacheEntry> m_cacheEntries;
@@ -135,7 +126,7 @@ private:
 		Array<RenderTargetHandle, GBUFFER_COLOR_ATTACHMENT_COUNT> m_gbufferColorRts;
 		RenderTargetHandle m_gbufferDepthRt;
 		RenderTargetHandle m_lightShadingRt;
-		RenderTargetHandle m_irradianceRt;
+		RenderPassBufferHandle m_irradianceDiceValuesBuffHandle;
 		RenderTargetHandle m_shadowMapRt;
 	} m_ctx; ///< Runtime context.
 
@@ -152,21 +143,18 @@ private:
 	void prepareProbes(
 		RenderingContext& ctx, ReflectionProbeQueueElement*& probeToUpdate, U32& probeToUpdateCacheEntryIdx);
 
-	/// Find or allocate a new cache entry.
-	Bool findBestCacheEntry(U64 probeUuid, U32& cacheEntryIdx, Bool& cacheEntryFound);
-
-	void runGBuffer(CommandBufferPtr& cmdb);
-	void runShadowMapping(CommandBufferPtr& cmdb);
+	void runGBuffer(U32 faceIdx, CommandBufferPtr& cmdb);
+	void runShadowMapping(U32 faceIdx, CommandBufferPtr& cmdb);
 	void runLightShading(U32 faceIdx, RenderPassWorkContext& rgraphCtx);
 	void runMipmappingOfLightShading(U32 faceIdx, RenderPassWorkContext& rgraphCtx);
-	void runIrradiance(U32 faceIdx, RenderPassWorkContext& rgraphCtx);
-	void runIrradianceToRefl(U32 faceIdx, RenderPassWorkContext& rgraphCtx);
+	void runIrradiance(RenderPassWorkContext& rgraphCtx);
+	void runIrradianceToRefl(RenderPassWorkContext& rgraphCtx);
 
 	// A RenderPassWorkCallback for the light shading pass into a single face.
 	template<U faceIdx>
 	static void runLightShadingCallback(RenderPassWorkContext& rgraphCtx)
 	{
-		Indirect* const self = static_cast<Indirect*>(rgraphCtx.m_userData);
+		ProbeReflections* const self = static_cast<ProbeReflections*>(rgraphCtx.m_userData);
 		self->runLightShading(faceIdx, rgraphCtx);
 	}
 
@@ -174,25 +162,9 @@ private:
 	template<U faceIdx>
 	static void runMipmappingOfLightShadingCallback(RenderPassWorkContext& rgraphCtx)
 	{
-		Indirect* const self = static_cast<Indirect*>(rgraphCtx.m_userData);
+		ProbeReflections* const self = static_cast<ProbeReflections*>(rgraphCtx.m_userData);
 		self->runMipmappingOfLightShading(faceIdx, rgraphCtx);
 	}
-
-	// A RenderPassWorkCallback for the irradiance calculation of a single cube face.
-	template<U faceIdx>
-	static void runIrradianceCallback(RenderPassWorkContext& rgraphCtx)
-	{
-		Indirect* const self = static_cast<Indirect*>(rgraphCtx.m_userData);
-		self->runIrradiance(faceIdx, rgraphCtx);
-	}
-
-	// A RenderPassWorkCallback to apply the irradiance back to the reflection.
-	template<U faceIdx>
-	static void runIrradianceToReflCallback(RenderPassWorkContext& rgraphCtx)
-	{
-		Indirect* const self = static_cast<Indirect*>(rgraphCtx.m_userData);
-		self->runIrradianceToRefl(faceIdx, rgraphCtx);
-	}
 };
 /// @}
 

+ 49 - 6
src/anki/renderer/RenderQueue.h

@@ -144,21 +144,21 @@ static_assert(
 
 /// Normally the visibility tests don't perform tests on the reflection probes because probes dont change that often.
 /// This callback will be used by the renderer to inform a reflection probe that on the next frame it will be rendererd.
-/// In that case the probe should fill the render queues.
+/// In that case the visibility tests should fill the render queues of the probe.
 using ReflectionProbeQueueElementFeedbackCallback = void (*)(Bool fillRenderQueuesOnNextFrame, void* userData);
 
 /// Reflection probe render queue element.
 class ReflectionProbeQueueElement final
 {
 public:
+	U64 m_uuid;
 	ReflectionProbeQueueElementFeedbackCallback m_feedbackCallback;
 	RenderQueueDrawCallback m_drawCallback;
 	void* m_userData;
-	U64 m_uuid;
+	Array<RenderQueue*, 6> m_renderQueues;
 	Vec3 m_worldPosition;
 	Vec3 m_aabbMin;
 	Vec3 m_aabbMax;
-	Array<RenderQueue*, 6> m_renderQueues;
 	U32 m_textureArrayIndex; ///< Renderer internal.
 
 	ReflectionProbeQueueElement()
@@ -169,17 +169,57 @@ public:
 static_assert(
 	std::is_trivially_destructible<ReflectionProbeQueueElement>::value == true, "Should be trivially destructible");
 
+/// See ReflectionProbeQueueElementFeedbackCallback for its purpose.
+using GlobalIlluminationProbeQueueElementFeedbackCallback = void (*)(
+	Bool fillRenderQueuesOnNextFrame, void* userData, const Vec4& eyeWorldPosition);
+
+// Probe for global illumination.
+class GlobalIlluminationProbeQueueElement final
+{
+public:
+	U64 m_uuid;
+	GlobalIlluminationProbeQueueElementFeedbackCallback m_feedbackCallback;
+	RenderQueueDrawCallback m_debugDrawCallback;
+	void* m_userData;
+	Array<RenderQueue*, 6> m_renderQueues;
+	Vec3 m_aabbMin;
+	Vec3 m_aabbMax;
+	UVec3 m_cellCounts;
+	U32 m_totalCellCount;
+	Vec3 m_cellSizes; ///< The cells might not be cubes so have different sizes per dimension.
+	F32 m_fadeDistance;
+
+	GlobalIlluminationProbeQueueElement()
+	{
+	}
+
+	Bool operator<(const GlobalIlluminationProbeQueueElement& b) const
+	{
+		if(m_cellSizes.x() != b.m_cellSizes.x())
+		{
+			return m_cellSizes.x() < b.m_cellSizes.x();
+		}
+		else
+		{
+			return m_totalCellCount < b.m_totalCellCount;
+		}
+	}
+};
+
+static_assert(std::is_trivially_destructible<GlobalIlluminationProbeQueueElement>::value == true,
+	"Should be trivially destructible");
+
 /// Lens flare render queue element.
 class LensFlareQueueElement final
 {
 public:
-	Vec3 m_worldPosition;
-	Vec2 m_firstFlareSize;
-	Vec4 m_colorMultiplier;
 	/// Totaly unsafe but we can't have a smart ptr in here since there will be no deletion.
 	const TextureView* m_textureView;
 	const void* m_userData;
 	RenderQueueDrawCallback m_drawCallback;
+	Vec3 m_worldPosition;
+	Vec2 m_firstFlareSize;
+	Vec4 m_colorMultiplier;
 
 	LensFlareQueueElement()
 	{
@@ -274,6 +314,7 @@ public:
 	DirectionalLightQueueElement m_directionalLight;
 	WeakArray<SpotLightQueueElement*> m_shadowSpotLights; ///< Points to elements in m_spotLights.
 	WeakArray<ReflectionProbeQueueElement> m_reflectionProbes;
+	WeakArray<GlobalIlluminationProbeQueueElement> m_giProbes;
 	WeakArray<LensFlareQueueElement> m_lensFlares;
 	WeakArray<DecalQueueElement> m_decals;
 	WeakArray<FogDensityQueueElement> m_fogDensityVolumes;
@@ -284,6 +325,8 @@ public:
 
 	F32 m_cameraNear;
 	F32 m_cameraFar;
+	F32 m_cameraFovX;
+	F32 m_cameraFovY;
 	F32 m_effectiveShadowDistance;
 
 	FillCoverageBufferCallback m_fillCoverageBufferCallback = nullptr;

+ 24 - 8
src/anki/renderer/Renderer.cpp

@@ -8,8 +8,9 @@
 #include <anki/core/Trace.h>
 #include <anki/misc/ConfigSet.h>
 #include <anki/util/HighRezTimer.h>
+#include <anki/collision/Aabb.h>
 
-#include <anki/renderer/Indirect.h>
+#include <anki/renderer/ProbeReflections.h>
 #include <anki/renderer/GBuffer.h>
 #include <anki/renderer/GBufferPost.h>
 #include <anki/renderer/LightShading.h>
@@ -28,6 +29,7 @@
 #include <anki/renderer/UiStage.h>
 #include <anki/renderer/Ssr.h>
 #include <anki/renderer/VolumetricLightingAccumulation.h>
+#include <anki/renderer/GlobalIllumination.h>
 #include <shaders/glsl_cpp_common/ClusteredShading.h>
 
 namespace anki
@@ -101,11 +103,17 @@ Error Renderer::initInternal(const ConfigSet& config)
 		texinit.m_width = texinit.m_height = 4;
 		texinit.m_usage = TextureUsageBit::SAMPLED_ALL;
 		texinit.m_format = Format::R8G8B8A8_UNORM;
-		texinit.m_initialUsage = TextureUsageBit::SAMPLED_FRAGMENT;
+		texinit.m_initialUsage = TextureUsageBit::SAMPLED_ALL;
 		TexturePtr tex = getGrManager().newTexture(texinit);
 
 		TextureViewInitInfo viewinit(tex);
-		m_dummyTexView = getGrManager().newTextureView(viewinit);
+		m_dummyTexView2d = getGrManager().newTextureView(viewinit);
+
+		texinit.m_depth = 4;
+		texinit.m_type = TextureType::_3D;
+		tex = getGrManager().newTexture(texinit);
+		viewinit = TextureViewInitInfo(tex);
+		m_dummyTexView3d = getGrManager().newTextureView(viewinit);
 	}
 
 	m_dummyBuff = getGrManager().newBuffer(BufferInitInfo(
@@ -117,8 +125,11 @@ Error Renderer::initInternal(const ConfigSet& config)
 	m_volLighting.reset(m_alloc.newInstance<VolumetricLightingAccumulation>(this));
 	ANKI_CHECK(m_volLighting->init(config));
 
-	m_indirect.reset(m_alloc.newInstance<Indirect>(this));
-	ANKI_CHECK(m_indirect->init(config));
+	m_gi.reset(m_alloc.newInstance<GlobalIllumination>(this));
+	ANKI_CHECK(m_gi->init(config));
+
+	m_probeReflections.reset(m_alloc.newInstance<ProbeReflections>(this));
+	ANKI_CHECK(m_probeReflections->init(config));
 
 	m_gbuffer.reset(m_alloc.newInstance<GBuffer>(this));
 	ANKI_CHECK(m_gbuffer->init(config));
@@ -286,7 +297,8 @@ Error Renderer::populateRenderGraph(RenderingContext& ctx)
 
 	// Populate render graph. WARNING Watch the order
 	m_shadowMapping->populateRenderGraph(ctx);
-	m_indirect->populateRenderGraph(ctx);
+	m_gi->populateRenderGraph(ctx);
+	m_probeReflections->populateRenderGraph(ctx);
 	m_volLighting->populateRenderGraph(ctx);
 	m_gbuffer->populateRenderGraph(ctx);
 	m_gbufferPost->populateRenderGraph(ctx);
@@ -513,8 +525,12 @@ TexturePtr Renderer::createAndClearRenderTarget(const TextureInitInfo& inf, cons
 					cmdb->setTextureSurfaceBarrier(
 						tex, TextureUsageBit::NONE, TextureUsageBit::IMAGE_COMPUTE_WRITE, surf);
 
-					const U wgSizeZ = (inf.m_type == TextureType::_3D) ? (tex->getDepth() >> mip) : 1;
-					cmdb->dispatchCompute(tex->getWidth() >> mip, tex->getHeight() >> mip, wgSizeZ);
+					UVec3 wgSize;
+					wgSize.x() = (8 - 1 + (tex->getWidth() >> mip)) / 8;
+					wgSize.y() = (8 - 1 + (tex->getHeight() >> mip)) / 8;
+					wgSize.z() = (inf.m_type == TextureType::_3D) ? ((8 - 1 + (tex->getDepth() >> mip)) / 8) : 1;
+
+					cmdb->dispatchCompute(wgSize.x(), wgSize.y(), wgSize.z());
 
 					if(!!inf.m_initialUsage)
 					{

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

@@ -96,9 +96,9 @@ public:
 
 	~Renderer();
 
-	Indirect& getIndirect()
+	ProbeReflections& getProbeReflections()
 	{
-		return *m_indirect;
+		return *m_probeReflections;
 	}
 
 	VolumetricLightingAccumulation& getVolumetricLightingAccumulation()
@@ -181,6 +181,11 @@ public:
 		return *m_lensFlare;
 	}
 
+	const GlobalIllumination& getGlobalIllumination() const
+	{
+		return *m_gi;
+	}
+
 	UiStage& getUiStage()
 	{
 		return *m_uiStage;
@@ -317,9 +322,14 @@ anki_internal:
 		return m_resourcesDirty;
 	}
 
-	TextureViewPtr getDummyTextureView() const
+	TextureViewPtr getDummyTextureView2d() const
+	{
+		return m_dummyTexView2d;
+	}
+
+	TextureViewPtr getDummyTextureView3d() const
 	{
-		return m_dummyTexView;
+		return m_dummyTexView3d;
 	}
 
 	BufferPtr getDummyBuffer() const
@@ -367,7 +377,8 @@ private:
 	/// @name Rendering stages
 	/// @{
 	UniquePtr<VolumetricLightingAccumulation> m_volLighting;
-	UniquePtr<Indirect> m_indirect;
+	UniquePtr<GlobalIllumination> m_gi;
+	UniquePtr<ProbeReflections> m_probeReflections;
 	UniquePtr<ShadowMapping> m_shadowMapping; ///< Shadow mapping.
 	UniquePtr<GBuffer> m_gbuffer; ///< Material rendering stage
 	UniquePtr<GBufferPost> m_gbufferPost;
@@ -409,7 +420,8 @@ private:
 	Array<Mat4, 16> m_jitteredMats16x;
 	Array<Mat4, 8> m_jitteredMats8x;
 
-	TextureViewPtr m_dummyTexView;
+	TextureViewPtr m_dummyTexView2d;
+	TextureViewPtr m_dummyTexView3d;
 	BufferPtr m_dummyBuff;
 
 	RendererPrecreatedSamplers m_samplers;

+ 2 - 1
src/anki/renderer/ShadowMapping.cpp

@@ -229,7 +229,8 @@ void ShadowMapping::runShadowMapping(RenderPassWorkContext& rgraphCtx)
 			m_r->getSamplers().m_trilinearRepeatAniso,
 			work.m_renderQueue->m_renderables.getBegin() + work.m_firstRenderableElement,
 			work.m_renderQueue->m_renderables.getBegin() + work.m_firstRenderableElement
-				+ work.m_renderableElementCount);
+				+ work.m_renderableElementCount,
+			MAX_LOD_COUNT - 1);
 	}
 }
 

+ 54 - 65
src/anki/renderer/TraditionalDeferredShading.cpp

@@ -28,20 +28,24 @@ Error TraditionalDeferredLightShading::init()
 	{
 		ANKI_CHECK(getResourceManager().loadResource("shaders/TraditionalDeferredShading.glslp", m_lightProg));
 
-		ShaderProgramResourceMutationInitList<1> mutators(m_lightProg);
-		mutators.add("LIGHT_TYPE", 0);
+		for(U32 specular = 0; specular <= 1; ++specular)
+		{
+			ShaderProgramResourceMutationInitList<2> mutators(m_lightProg);
+			mutators.add("LIGHT_TYPE", 0);
+			mutators.add("SPECULAR", specular);
 
-		const ShaderProgramResourceVariant* variant;
-		m_lightProg->getOrCreateVariant(mutators.get(), variant);
-		m_plightGrProg = variant->getProgram();
+			const ShaderProgramResourceVariant* variant;
+			m_lightProg->getOrCreateVariant(mutators.get(), variant);
+			m_plightGrProg[specular] = variant->getProgram();
 
-		mutators[0].m_value = 1;
-		m_lightProg->getOrCreateVariant(mutators.get(), variant);
-		m_slightGrProg = variant->getProgram();
+			mutators[0].m_value = 1;
+			m_lightProg->getOrCreateVariant(mutators.get(), variant);
+			m_slightGrProg[specular] = variant->getProgram();
 
-		mutators[0].m_value = 2;
-		m_lightProg->getOrCreateVariant(mutators.get(), variant);
-		m_dirLightGrProg = variant->getProgram();
+			mutators[0].m_value = 2;
+			m_lightProg->getOrCreateVariant(mutators.get(), variant);
+			m_dirLightGrProg[specular] = variant->getProgram();
+		}
 	}
 
 	// Init meshes
@@ -76,58 +80,41 @@ void TraditionalDeferredLightShading::bindVertexIndexBuffers(
 	cmdb->bindIndexBuffer(buff, offset, idxType);
 }
 
-void TraditionalDeferredLightShading::drawLights(const Mat4& vpMat,
-	const Mat4& invViewProjMat,
-	const Vec4& cameraPosWSpace,
-	const UVec4& viewport,
-	const Vec2& gbufferTexCoordsMin,
-	const Vec2& gbufferTexCoordsMax,
-	F32 cameraNear,
-	F32 cameraFar,
-	DirectionalLightQueueElement* directionalLight,
-	ConstWeakArray<PointLightQueueElement> plights,
-	ConstWeakArray<SpotLightQueueElement> slights,
-	CommandBufferPtr& cmdb)
+void TraditionalDeferredLightShading::drawLights(TraditionalDeferredLightShadingDrawInfo& info)
 {
-	ANKI_ASSERT(gbufferTexCoordsMin < gbufferTexCoordsMax);
-
-	// Compute coords
-	Vec4 inputTexUvScaleAndOffset;
-	inputTexUvScaleAndOffset.x() = gbufferTexCoordsMax.x() - gbufferTexCoordsMin.x();
-	inputTexUvScaleAndOffset.y() = gbufferTexCoordsMax.y() - gbufferTexCoordsMin.y();
-	inputTexUvScaleAndOffset.z() = gbufferTexCoordsMin.x();
-	inputTexUvScaleAndOffset.w() = gbufferTexCoordsMin.y();
-
 	// Set common state for all lights
+	CommandBufferPtr& cmdb = info.m_commandBuffer;
 	cmdb->setBlendFactors(0, BlendFactor::ONE, BlendFactor::ONE);
-	cmdb->setViewport(viewport.x(), viewport.y(), viewport.z(), viewport.w());
+	cmdb->setViewport(info.m_viewport.x(), info.m_viewport.y(), info.m_viewport.z(), info.m_viewport.w());
 
 	// Dir light
-	if(directionalLight)
+	if(info.m_directionalLight)
 	{
-		ANKI_ASSERT(directionalLight->m_uuid && directionalLight->m_shadowCascadeCount == 1);
+		ANKI_ASSERT(info.m_directionalLight->m_uuid && info.m_directionalLight->m_shadowCascadeCount == 1);
 
-		cmdb->bindShaderProgram(m_dirLightGrProg);
+		cmdb->bindShaderProgram(m_dirLightGrProg[info.m_computeSpecular]);
 
 		DeferredDirectionalLightUniforms* unis = allocateAndBindUniforms<DeferredDirectionalLightUniforms*>(
 			sizeof(DeferredDirectionalLightUniforms), cmdb, 0, 1);
 
-		unis->m_inputTexUvScale = inputTexUvScaleAndOffset.xy();
-		unis->m_inputTexUvOffset = inputTexUvScaleAndOffset.zw();
-		unis->m_invViewProjMat = invViewProjMat;
-		unis->m_camPos = cameraPosWSpace.xyz();
-		unis->m_fbSize = Vec2(viewport.z(), viewport.w());
+		unis->m_inputTexUvScale = info.m_gbufferTexCoordsScale;
+		unis->m_inputTexUvBias = info.m_gbufferTexCoordsBias;
+		unis->m_fbUvScale = info.m_lightbufferTexCoordsScale;
+		unis->m_fbUvBias = info.m_lightbufferTexCoordsBias;
+		unis->m_invViewProjMat = info.m_invViewProjectionMatrix;
+		unis->m_camPos = info.m_cameraPosWSpace.xyz();
 
-		unis->m_diffuseColor = directionalLight->m_diffuseColor;
-		unis->m_lightDir = directionalLight->m_direction;
-		unis->m_lightMatrix = directionalLight->m_textureMatrices[0];
+		unis->m_diffuseColor = info.m_directionalLight->m_diffuseColor;
+		unis->m_lightDir = info.m_directionalLight->m_direction;
+		unis->m_lightMatrix = info.m_directionalLight->m_textureMatrices[0];
 
-		unis->m_near = cameraNear;
-		unis->m_far = cameraFar;
+		unis->m_near = info.m_cameraNear;
+		unis->m_far = info.m_cameraFar;
 
-		if(directionalLight->m_shadowCascadeCount > 0)
+		if(info.m_directionalLight->m_shadowCascadeCount > 0)
 		{
-			unis->m_effectiveShadowDistance = directionalLight->m_shadowRenderQueues[0]->m_effectiveShadowDistance;
+			unis->m_effectiveShadowDistance =
+				info.m_directionalLight->m_shadowRenderQueues[0]->m_effectiveShadowDistance;
 		}
 		else
 		{
@@ -143,9 +130,9 @@ void TraditionalDeferredLightShading::drawLights(const Mat4& vpMat,
 	// Do point lights
 	U32 indexCount;
 	bindVertexIndexBuffers(m_plightMesh, cmdb, indexCount);
-	cmdb->bindShaderProgram(m_plightGrProg);
+	cmdb->bindShaderProgram(m_plightGrProg[info.m_computeSpecular]);
 
-	for(const PointLightQueueElement& plightEl : plights)
+	for(const PointLightQueueElement& plightEl : info.m_pointLights)
 	{
 		// Update uniforms
 		DeferredVertexUniforms* vert =
@@ -153,16 +140,17 @@ void TraditionalDeferredLightShading::drawLights(const Mat4& vpMat,
 
 		Mat4 modelM(plightEl.m_worldPosition.xyz1(), Mat3::getIdentity(), plightEl.m_radius);
 
-		vert->m_mvp = vpMat * modelM;
+		vert->m_mvp = info.m_viewProjectionMatrix * modelM;
 
 		DeferredPointLightUniforms* light =
 			allocateAndBindUniforms<DeferredPointLightUniforms*>(sizeof(DeferredPointLightUniforms), cmdb, 0, 1);
 
-		light->m_inputTexUvScale = inputTexUvScaleAndOffset.xy();
-		light->m_inputTexUvOffset = inputTexUvScaleAndOffset.zw();
-		light->m_invViewProjMat = invViewProjMat;
-		light->m_camPos = cameraPosWSpace.xyz();
-		light->m_fbSize = Vec2(viewport.z(), viewport.w());
+		light->m_inputTexUvScale = info.m_gbufferTexCoordsScale;
+		light->m_inputTexUvBias = info.m_gbufferTexCoordsBias;
+		light->m_fbUvScale = info.m_lightbufferTexCoordsScale;
+		light->m_fbUvBias = info.m_lightbufferTexCoordsBias;
+		light->m_invViewProjMat = info.m_invViewProjectionMatrix;
+		light->m_camPos = info.m_cameraPosWSpace.xyz();
 		light->m_position = plightEl.m_worldPosition;
 		light->m_oneOverSquareRadius = 1.0f / (plightEl.m_radius * plightEl.m_radius);
 		light->m_diffuseColor = plightEl.m_diffuseColor;
@@ -173,9 +161,9 @@ void TraditionalDeferredLightShading::drawLights(const Mat4& vpMat,
 
 	// Do spot lights
 	bindVertexIndexBuffers(m_slightMesh, cmdb, indexCount);
-	cmdb->bindShaderProgram(m_slightGrProg);
+	cmdb->bindShaderProgram(m_slightGrProg[info.m_computeSpecular]);
 
-	for(const SpotLightQueueElement& splightEl : slights)
+	for(const SpotLightQueueElement& splightEl : info.m_spotLights)
 	{
 		// Compute the model matrix
 		//
@@ -193,17 +181,18 @@ void TraditionalDeferredLightShading::drawLights(const Mat4& vpMat,
 		// Update vertex uniforms
 		DeferredVertexUniforms* vert =
 			allocateAndBindUniforms<DeferredVertexUniforms*>(sizeof(DeferredVertexUniforms), cmdb, 0, 0);
-		vert->m_mvp = vpMat * modelM;
+		vert->m_mvp = info.m_viewProjectionMatrix * modelM;
 
 		// Update fragment uniforms
 		DeferredSpotLightUniforms* light =
 			allocateAndBindUniforms<DeferredSpotLightUniforms*>(sizeof(DeferredSpotLightUniforms), cmdb, 0, 1);
 
-		light->m_inputTexUvScale = inputTexUvScaleAndOffset.xy();
-		light->m_inputTexUvOffset = inputTexUvScaleAndOffset.zw();
-		light->m_invViewProjMat = invViewProjMat;
-		light->m_camPos = cameraPosWSpace.xyz();
-		light->m_fbSize = Vec2(viewport.z(), viewport.w());
+		light->m_inputTexUvScale = info.m_gbufferTexCoordsScale;
+		light->m_inputTexUvBias = info.m_gbufferTexCoordsBias;
+		light->m_fbUvScale = info.m_lightbufferTexCoordsScale;
+		light->m_fbUvBias = info.m_lightbufferTexCoordsBias;
+		light->m_invViewProjMat = info.m_invViewProjectionMatrix;
+		light->m_camPos = info.m_cameraPosWSpace.xyz();
 
 		light->m_position = splightEl.m_worldTransform.getTranslationPart().xyz();
 		light->m_oneOverSquareRadius = 1.0f / (splightEl.m_distance * splightEl.m_distance);
@@ -223,4 +212,4 @@ void TraditionalDeferredLightShading::drawLights(const Mat4& vpMat,
 	cmdb->setCullMode(FaceSelectionBit::BACK);
 }
 
-} // end namespace anki
+} // end namespace anki

+ 25 - 15
src/anki/renderer/TraditionalDeferredShading.h

@@ -13,6 +13,27 @@ namespace anki
 /// @addtogroup renderer
 /// @{
 
+/// Parameters to be passed to TraditionalDeferredLightShading::drawLights.
+class TraditionalDeferredLightShadingDrawInfo
+{
+public:
+	Mat4 m_viewProjectionMatrix;
+	Mat4 m_invViewProjectionMatrix;
+	Vec4 m_cameraPosWSpace;
+	UVec4 m_viewport;
+	Vec2 m_gbufferTexCoordsScale;
+	Vec2 m_gbufferTexCoordsBias;
+	Vec2 m_lightbufferTexCoordsScale;
+	Vec2 m_lightbufferTexCoordsBias;
+	F32 m_cameraNear;
+	F32 m_cameraFar;
+	DirectionalLightQueueElement* m_directionalLight ANKI_DEBUG_CODE(= numberToPtr<DirectionalLightQueueElement*>(1));
+	ConstWeakArray<PointLightQueueElement> m_pointLights;
+	ConstWeakArray<SpotLightQueueElement> m_spotLights;
+	CommandBufferPtr m_commandBuffer;
+	Bool m_computeSpecular = false;
+};
+
 /// Helper for drawing using traditional deferred shading.
 class TraditionalDeferredLightShading : public RendererObject
 {
@@ -25,24 +46,13 @@ public:
 
 	/// Run the light shading. It will iterate over the lights and draw them. It doesn't bind anything related to
 	/// GBuffer or the output buffer.
-	void drawLights(const Mat4& vpMat,
-		const Mat4& invViewProjMat,
-		const Vec4& cameraPosWSpace,
-		const UVec4& viewport,
-		const Vec2& gbufferTexCoordsMin,
-		const Vec2& gbufferTexCoordsMax,
-		F32 cameraNear,
-		F32 cameraFar,
-		DirectionalLightQueueElement* directionalLight,
-		ConstWeakArray<PointLightQueueElement> plights,
-		ConstWeakArray<SpotLightQueueElement> slights,
-		CommandBufferPtr& cmdb);
+	void drawLights(TraditionalDeferredLightShadingDrawInfo& info);
 
 private:
 	ShaderProgramResourcePtr m_lightProg;
-	ShaderProgramPtr m_plightGrProg;
-	ShaderProgramPtr m_slightGrProg;
-	ShaderProgramPtr m_dirLightGrProg;
+	Array<ShaderProgramPtr, 2> m_plightGrProg;
+	Array<ShaderProgramPtr, 2> m_slightGrProg;
+	Array<ShaderProgramPtr, 2> m_dirLightGrProg;
 
 	/// @name Meshes of light volumes.
 	/// @{

+ 8 - 9
src/anki/renderer/VolumetricLightingAccumulation.cpp

@@ -5,7 +5,7 @@
 
 #include <anki/renderer/VolumetricLightingAccumulation.h>
 #include <anki/renderer/ShadowMapping.h>
-#include <anki/renderer/Indirect.h>
+#include <anki/renderer/GlobalIllumination.h>
 #include <anki/renderer/Renderer.h>
 #include <anki/resource/TextureResource.h>
 #include <anki/misc/ConfigSet.h>
@@ -96,7 +96,8 @@ void VolumetricLightingAccumulation::populateRenderGraph(RenderingContext& ctx)
 	pass.newDependency({m_runCtx.m_rts[0], TextureUsageBit::SAMPLED_COMPUTE});
 	pass.newDependency({m_runCtx.m_rts[1], TextureUsageBit::IMAGE_COMPUTE_WRITE});
 	pass.newDependency({m_r->getShadowMapping().getShadowmapRt(), TextureUsageBit::SAMPLED_COMPUTE});
-	pass.newDependency({m_r->getIndirect().getIrradianceRt(), TextureUsageBit::SAMPLED_COMPUTE});
+
+	m_r->getGlobalIllumination().setRenderGraphDependencies(ctx, pass, TextureUsageBit::SAMPLED_COMPUTE);
 }
 
 void VolumetricLightingAccumulation::run(RenderPassWorkContext& rgraphCtx)
@@ -122,14 +123,12 @@ void VolumetricLightingAccumulation::run(RenderPassWorkContext& rgraphCtx)
 	bindUniforms(cmdb, 0, 7, rsrc.m_spotLightsToken);
 	rgraphCtx.bindColorTexture(0, 8, m_r->getShadowMapping().getShadowmapRt());
 
-	bindUniforms(cmdb, 0, 9, rsrc.m_probesToken);
-	cmdb->bindTexture(0, 10, m_r->getDummyTextureView(), TextureUsageBit::SAMPLED_COMPUTE);
-	rgraphCtx.bindColorTexture(0, 11, m_r->getIndirect().getIrradianceRt());
-	cmdb->bindTexture(0, 12, m_r->getDummyTextureView(), TextureUsageBit::SAMPLED_COMPUTE);
+	m_r->getGlobalIllumination().bindVolumeTextures(ctx, rgraphCtx, 0, 9);
+	bindUniforms(cmdb, 0, 10, rsrc.m_globalIlluminationProbesToken);
 
-	bindUniforms(cmdb, 0, 13, rsrc.m_fogDensityVolumesToken);
-	bindStorage(cmdb, 0, 14, rsrc.m_clustersToken);
-	bindStorage(cmdb, 0, 15, rsrc.m_indicesToken);
+	bindUniforms(cmdb, 0, 11, rsrc.m_fogDensityVolumesToken);
+	bindStorage(cmdb, 0, 12, rsrc.m_clustersToken);
+	bindStorage(cmdb, 0, 13, rsrc.m_indicesToken);
 
 	struct PushConsts
 	{

+ 0 - 1
src/anki/resource/AnimationResource.cpp

@@ -27,7 +27,6 @@ AnimationResource::~AnimationResource()
 Error AnimationResource::load(const ResourceFilename& filename, Bool async)
 {
 	XmlElement el;
-	I64 tmp;
 	Second ftmp;
 
 	m_startTime = MAX_SECOND;

+ 1 - 0
src/anki/resource/ResourceManager.cpp

@@ -48,6 +48,7 @@ Error ResourceManager::init(ResourceManagerInitInfo& init)
 
 	// Init some constants
 	m_maxTextureSize = init.m_config->getNumber("rsrc.maxTextureSize");
+	m_dumpShaderSource = init.m_config->getNumber("rsrc.dumpShaderSources");
 
 	// Init type resource managers
 #define ANKI_INSTANTIATE_RESOURCE(rsrc_, ptr_) TypeResourceManager<rsrc_>::init(m_alloc);

+ 6 - 0
src/anki/resource/ResourceManager.h

@@ -133,6 +133,11 @@ anki_internal:
 		return m_maxTextureSize;
 	}
 
+	Bool getDumpShaderSource() const
+	{
+		return m_dumpShaderSource;
+	}
+
 	ResourceAllocator<U8>& getAllocator()
 	{
 		return m_alloc;
@@ -222,6 +227,7 @@ private:
 	U64 m_loadRequestCount = 0;
 	TransferGpuAllocator* m_transferGpuAlloc = nullptr;
 	ShaderCompilerCache* m_shaderCompiler = nullptr;
+	Bool m_dumpShaderSource = false;
 };
 /// @}
 

+ 2 - 1
src/anki/resource/ShaderProgramResource.cpp

@@ -605,7 +605,8 @@ void ShaderProgramResource::initVariant(ConstWeakArray<ShaderProgramResourceMuta
 		compileOptions.setFromGrManager(getManager().getGrManager());
 		compileOptions.m_shaderType = i;
 
-		Error err = getManager().getShaderCompiler().compile(src.toCString(), nullptr, compileOptions, bin);
+		const Error err = getManager().getShaderCompiler().compile(
+			src.toCString(), nullptr, compileOptions, bin, getManager().getDumpShaderSource());
 		if(err)
 		{
 			ANKI_RESOURCE_LOGF("Shader compilation failed");

+ 5 - 0
src/anki/resource/ShaderProgramResource.h

@@ -407,6 +407,11 @@ public:
 		return ConstWeakArray<ShaderProgramResourceConstantValue>(&m_constantValues[0], m_count);
 	}
 
+	ShaderProgramResourceConstantValue& operator[](U idx)
+	{
+		return m_constantValues[idx];
+	}
+
 private:
 	ShaderProgramResourcePtr m_ptr;
 	U m_count = 0;

+ 2 - 1
src/anki/scene/CameraNode.cpp

@@ -83,7 +83,8 @@ Error CameraNode::init(FrustumType frustumType)
 		| FrustumComponentVisibilityTestFlag::LENS_FLARE_COMPONENTS
 		| FrustumComponentVisibilityTestFlag::REFLECTION_PROBES | FrustumComponentVisibilityTestFlag::REFLECTION_PROXIES
 		| FrustumComponentVisibilityTestFlag::OCCLUDERS | FrustumComponentVisibilityTestFlag::DECALS
-		| FrustumComponentVisibilityTestFlag::FOG_DENSITY_COMPONENTS | FrustumComponentVisibilityTestFlag::EARLY_Z
+		| FrustumComponentVisibilityTestFlag::FOG_DENSITY_COMPONENTS
+		| FrustumComponentVisibilityTestFlag::GLOBAL_ILLUMINATION_PROBES | FrustumComponentVisibilityTestFlag::EARLY_Z
 		| FrustumComponentVisibilityTestFlag::ALL_SHADOWS_ENABLED);
 
 	// Feedback component #2

+ 54 - 0
src/anki/scene/DebugDrawer.cpp

@@ -238,4 +238,58 @@ void PhysicsDebugDrawer::drawLines(const Vec3* lines, const U32 vertCount, const
 	}
 }
 
+void allocateAndPopulateDebugBox(StagingGpuMemoryManager& stagingGpuAllocator,
+	StagingGpuMemoryToken& vertsToken,
+	StagingGpuMemoryToken& indicesToken,
+	U32& indexCount)
+{
+	Vec3* verts = static_cast<Vec3*>(
+		stagingGpuAllocator.allocateFrame(sizeof(Vec3) * 8, StagingGpuMemoryType::VERTEX, vertsToken));
+
+	const F32 SIZE = 1.0f;
+	verts[0] = Vec3(SIZE, SIZE, SIZE); // front top right
+	verts[1] = Vec3(-SIZE, SIZE, SIZE); // front top left
+	verts[2] = Vec3(-SIZE, -SIZE, SIZE); // front bottom left
+	verts[3] = Vec3(SIZE, -SIZE, SIZE); // front bottom right
+	verts[4] = Vec3(SIZE, SIZE, -SIZE); // back top right
+	verts[5] = Vec3(-SIZE, SIZE, -SIZE); // back top left
+	verts[6] = Vec3(-SIZE, -SIZE, -SIZE); // back bottom left
+	verts[7] = Vec3(SIZE, -SIZE, -SIZE); // back bottom right
+
+	const U INDEX_COUNT = 12 * 2;
+	U16* indices = static_cast<U16*>(
+		stagingGpuAllocator.allocateFrame(sizeof(U16) * INDEX_COUNT, StagingGpuMemoryType::VERTEX, indicesToken));
+
+	U c = 0;
+	indices[c++] = 0;
+	indices[c++] = 1;
+	indices[c++] = 1;
+	indices[c++] = 2;
+	indices[c++] = 2;
+	indices[c++] = 3;
+	indices[c++] = 3;
+	indices[c++] = 0;
+
+	indices[c++] = 4;
+	indices[c++] = 5;
+	indices[c++] = 5;
+	indices[c++] = 6;
+	indices[c++] = 6;
+	indices[c++] = 7;
+	indices[c++] = 7;
+	indices[c++] = 4;
+
+	indices[c++] = 0;
+	indices[c++] = 4;
+	indices[c++] = 1;
+	indices[c++] = 5;
+	indices[c++] = 2;
+	indices[c++] = 6;
+	indices[c++] = 3;
+	indices[c++] = 7;
+
+	ANKI_ASSERT(c == INDEX_COUNT);
+	indexCount = INDEX_COUNT;
+}
+
 } // end namespace anki

+ 8 - 0
src/anki/scene/DebugDrawer.h

@@ -17,6 +17,8 @@ namespace anki
 
 // Forward
 class RenderQueueDrawContext;
+class StagingGpuMemoryManager;
+class StagingGpuMemoryToken;
 
 /// @addtogroup renderer
 /// @{
@@ -118,6 +120,12 @@ public:
 private:
 	DebugDrawer* m_dbg; ///< The debug drawer
 };
+
+/// Allocate memory for a line cube and populate it.
+void allocateAndPopulateDebugBox(StagingGpuMemoryManager& stagingGpuAllocator,
+	StagingGpuMemoryToken& vertsToken,
+	StagingGpuMemoryToken& indicesToken,
+	U32& indexCount);
 /// @}
 
 } // end namespace anki

+ 202 - 0
src/anki/scene/GlobalIlluminationProbeNode.cpp

@@ -0,0 +1,202 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/scene/GlobalIlluminationProbeNode.h>
+#include <anki/scene/SceneGraph.h>
+#include <anki/scene/components/FrustumComponent.h>
+#include <anki/scene/components/MoveComponent.h>
+#include <anki/scene/components/SpatialComponent.h>
+#include <anki/scene/components/GlobalIlluminationProbeComponent.h>
+
+namespace anki
+{
+
+const FrustumComponentVisibilityTestFlag FRUSTUM_TEST_FLAGS =
+	FrustumComponentVisibilityTestFlag::RENDER_COMPONENTS | FrustumComponentVisibilityTestFlag::LIGHT_COMPONENTS
+	| FrustumComponentVisibilityTestFlag::DIRECTIONAL_LIGHT_SHADOWS_1_CASCADE;
+
+/// Feedback component
+class GlobalIlluminationProbeNode::MoveFeedbackComponent : public SceneComponent
+{
+public:
+	MoveFeedbackComponent()
+		: SceneComponent(SceneComponentType::NONE)
+	{
+	}
+
+	Error update(SceneNode& node, Second prevTime, Second crntTime, Bool& updated) override
+	{
+		updated = false;
+
+		MoveComponent& move = node.getComponent<MoveComponent>();
+		if(move.getTimestamp() == node.getGlobalTimestamp())
+		{
+			// Move updated
+			GlobalIlluminationProbeNode& dnode = static_cast<GlobalIlluminationProbeNode&>(node);
+			dnode.onMoveUpdate(move);
+		}
+
+		return Error::NONE;
+	}
+};
+
+/// Feedback component
+class GlobalIlluminationProbeNode::ShapeFeedbackComponent : public SceneComponent
+{
+public:
+	ShapeFeedbackComponent()
+		: SceneComponent(SceneComponentType::NONE)
+	{
+	}
+
+	Error update(SceneNode& node, Second prevTime, Second crntTime, Bool& updated) override
+	{
+		updated = false;
+
+		GlobalIlluminationProbeComponent& probec = node.getComponent<GlobalIlluminationProbeComponent>();
+		if(probec.getTimestamp() == node.getGlobalTimestamp() || probec.getMarkedForRendering())
+		{
+			// Move updated
+			GlobalIlluminationProbeNode& dnode = static_cast<GlobalIlluminationProbeNode&>(node);
+			dnode.onShapeUpdateOrProbeNeedsRendering();
+		}
+
+		return Error::NONE;
+	}
+};
+
+GlobalIlluminationProbeNode::~GlobalIlluminationProbeNode()
+{
+}
+
+Error GlobalIlluminationProbeNode::init()
+{
+	// Move component first
+	newComponent<MoveComponent>();
+
+	// Feedback component
+	newComponent<MoveFeedbackComponent>();
+
+	// GI probe comp
+	GlobalIlluminationProbeComponent* giprobec =
+		newComponent<GlobalIlluminationProbeComponent>(getSceneGraph().getNewUuid());
+	ANKI_CHECK(giprobec->init(getResourceManager()));
+	giprobec->setBoundingBox(m_spatialAabb.getMin(), m_spatialAabb.getMax());
+
+	// Second feedback component
+	newComponent<ShapeFeedbackComponent>();
+
+	// The frustum components
+	const F32 ang = toRad(90.0f);
+	const F32 zNear = LIGHT_FRUSTUM_NEAR_PLANE;
+
+	Mat3 rot;
+	rot = Mat3(Euler(0.0f, -PI / 2.0f, 0.0f)) * Mat3(Euler(0.0f, 0.0f, PI));
+	m_cubeFaceTransforms[0].setRotation(Mat3x4(rot));
+	rot = Mat3(Euler(0.0f, PI / 2.0f, 0.0f)) * Mat3(Euler(0.0f, 0.0f, PI));
+	m_cubeFaceTransforms[1].setRotation(Mat3x4(rot));
+	rot = Mat3(Euler(PI / 2.0f, 0.0f, 0.0f));
+	m_cubeFaceTransforms[2].setRotation(Mat3x4(rot));
+	rot = Mat3(Euler(-PI / 2.0f, 0.0f, 0.0f));
+	m_cubeFaceTransforms[3].setRotation(Mat3x4(rot));
+	rot = Mat3(Euler(0.0f, PI, 0.0f)) * Mat3(Euler(0.0f, 0.0f, PI));
+	m_cubeFaceTransforms[4].setRotation(Mat3x4(rot));
+	rot = Mat3(Euler(0.0f, 0.0f, PI));
+	m_cubeFaceTransforms[5].setRotation(Mat3x4(rot));
+
+	for(U i = 0; i < 6; ++i)
+	{
+		m_cubeFaceTransforms[i].setOrigin(Vec4(0.0f));
+		m_cubeFaceTransforms[i].setScale(1.0f);
+
+		FrustumComponent* frc = newComponent<FrustumComponent>(this, FrustumType::PERSPECTIVE);
+		const F32 tempEffectiveDistance = 1.0f;
+		frc->setPerspective(zNear, tempEffectiveDistance, ang, ang);
+		frc->setTransform(m_cubeFaceTransforms[i]);
+		frc->setEnabledVisibilityTests(FrustumComponentVisibilityTestFlag::NONE);
+		frc->setEffectiveShadowDistance(getSceneGraph().getLimits().m_reflectionProbeShadowEffectiveDistance);
+	}
+
+	// Spatial component
+	SpatialComponent* spatialc = newComponent<SpatialComponent>(this, &m_spatialAabb);
+	spatialc->setUpdateOctreeBounds(false);
+
+	return Error::NONE;
+}
+
+void GlobalIlluminationProbeNode::onMoveUpdate(MoveComponent& move)
+{
+	// Update the probe comp
+	const Vec4 displacement = move.getWorldTransform().getOrigin() - m_previousPosition;
+	m_previousPosition = move.getWorldTransform().getOrigin();
+
+	GlobalIlluminationProbeComponent& gic = getComponent<GlobalIlluminationProbeComponent>();
+	gic.setBoundingBox(gic.getAlignedBoundingBoxMin() + displacement, gic.getAlignedBoundingBoxMax() + displacement);
+}
+
+void GlobalIlluminationProbeNode::onShapeUpdateOrProbeNeedsRendering()
+{
+	GlobalIlluminationProbeComponent& gic = getComponent<GlobalIlluminationProbeComponent>();
+
+	// Update the frustum component if the shape needs rendering
+	if(gic.getMarkedForRendering())
+	{
+		// Compute effective distance
+		F32 effectiveDistance = gic.getAlignedBoundingBoxMax().x() - gic.getAlignedBoundingBoxMin().x();
+		effectiveDistance =
+			max(effectiveDistance, gic.getAlignedBoundingBoxMax().y() - gic.getAlignedBoundingBoxMin().y());
+		effectiveDistance =
+			max(effectiveDistance, gic.getAlignedBoundingBoxMax().z() - gic.getAlignedBoundingBoxMin().z());
+		effectiveDistance = max(effectiveDistance, getSceneGraph().getLimits().m_reflectionProbeEffectiveDistance);
+
+		// Update frustum components
+		U count = 0;
+		Error err = iterateComponentsOfType<FrustumComponent>([&](FrustumComponent& frc) -> Error {
+			Transform trf = m_cubeFaceTransforms[count];
+			trf.setOrigin(gic.getRenderPosition());
+
+			frc.setTransform(trf);
+			frc.setFar(effectiveDistance);
+			++count;
+
+			return Error::NONE;
+		});
+
+		ANKI_ASSERT(count == 6);
+		(void)err;
+	}
+
+	// Update the spatial comp
+	const Bool shapeWasUpdated = gic.getTimestamp() == getGlobalTimestamp();
+	if(shapeWasUpdated)
+	{
+		// Update only when the shape was actually update
+
+		SpatialComponent& sp = getComponent<SpatialComponent>();
+		sp.markForUpdate();
+		sp.setSpatialOrigin((m_spatialAabb.getMax() + m_spatialAabb.getMin()) / 2.0f);
+		m_spatialAabb.setMin(gic.getAlignedBoundingBoxMin());
+		m_spatialAabb.setMax(gic.getAlignedBoundingBoxMax());
+	}
+}
+
+Error GlobalIlluminationProbeNode::frameUpdate(Second prevUpdateTime, Second crntTime)
+{
+	// Check the reflection probe component and if it's marked for rendering enable the frustum components
+	const GlobalIlluminationProbeComponent& gic = getComponent<GlobalIlluminationProbeComponent>();
+
+	const FrustumComponentVisibilityTestFlag testFlags =
+		gic.getMarkedForRendering() ? FRUSTUM_TEST_FLAGS : FrustumComponentVisibilityTestFlag::NONE;
+
+	const Error err = iterateComponentsOfType<FrustumComponent>([testFlags](FrustumComponent& frc) -> Error {
+		frc.setEnabledVisibilityTests(testFlags);
+		return Error::NONE;
+	});
+	(void)err;
+
+	return Error::NONE;
+}
+
+} // end namespace anki

+ 45 - 0
src/anki/scene/GlobalIlluminationProbeNode.h

@@ -0,0 +1,45 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/scene/SceneNode.h>
+#include <anki/collision/Aabb.h>
+
+namespace anki
+{
+
+/// @addtogroup scene
+/// @{
+
+/// Probe used in global illumination.
+class GlobalIlluminationProbeNode : public SceneNode
+{
+public:
+	GlobalIlluminationProbeNode(SceneGraph* scene, CString name)
+		: SceneNode(scene, name)
+	{
+	}
+
+	~GlobalIlluminationProbeNode();
+
+	ANKI_USE_RESULT Error init();
+
+	ANKI_USE_RESULT Error frameUpdate(Second prevUpdateTime, Second crntTime) override;
+
+private:
+	class MoveFeedbackComponent;
+	class ShapeFeedbackComponent;
+
+	Array<Transform, 6> m_cubeFaceTransforms;
+	Aabb m_spatialAabb = Aabb(Vec3(-1.0f), Vec3(1.0f));
+	Vec4 m_previousPosition = Vec4(0.0f);
+
+	void onMoveUpdate(MoveComponent& move);
+	void onShapeUpdateOrProbeNeedsRendering();
+};
+/// @}
+
+} // end namespace anki

+ 6 - 50
src/anki/scene/ModelNode.cpp

@@ -5,6 +5,7 @@
 
 #include <anki/scene/ModelNode.h>
 #include <anki/scene/SceneGraph.h>
+#include <anki/scene/DebugDrawer.h>
 #include <anki/scene/components/BodyComponent.h>
 #include <anki/scene/components/SkinComponent.h>
 #include <anki/scene/components/RenderComponent.h>
@@ -126,7 +127,7 @@ void ModelNode::draw(RenderQueueDrawContext& ctx, ConstWeakArray<void*> userData
 
 	CommandBufferPtr& cmdb = ctx.m_commandBuffer;
 
-	if(!ctx.m_debugDraw)
+	if(ANKI_LIKELY(!ctx.m_debugDraw))
 	{
 		const ModelPatch* patch = m_model->getModelPatches()[m_modelPatchIdx];
 
@@ -210,54 +211,9 @@ void ModelNode::draw(RenderQueueDrawContext& ctx, ConstWeakArray<void*> userData
 		// Draw the bounding volumes
 
 		// Allocate staging memory
-		StagingGpuMemoryToken vertToken;
-		Vec3* verts = static_cast<Vec3*>(
-			ctx.m_stagingGpuAllocator->allocateFrame(sizeof(Vec3) * 8, StagingGpuMemoryType::VERTEX, vertToken));
-
-		const F32 SIZE = 1.0f;
-		verts[0] = Vec3(SIZE, SIZE, SIZE); // front top right
-		verts[1] = Vec3(-SIZE, SIZE, SIZE); // front top left
-		verts[2] = Vec3(-SIZE, -SIZE, SIZE); // front bottom left
-		verts[3] = Vec3(SIZE, -SIZE, SIZE); // front bottom right
-		verts[4] = Vec3(SIZE, SIZE, -SIZE); // back top right
-		verts[5] = Vec3(-SIZE, SIZE, -SIZE); // back top left
-		verts[6] = Vec3(-SIZE, -SIZE, -SIZE); // back bottom left
-		verts[7] = Vec3(SIZE, -SIZE, -SIZE); // back bottom right
-
-		StagingGpuMemoryToken indicesToken;
-		const U INDEX_COUNT = 12 * 2;
-		U16* indices = static_cast<U16*>(ctx.m_stagingGpuAllocator->allocateFrame(
-			sizeof(U16) * INDEX_COUNT, StagingGpuMemoryType::VERTEX, indicesToken));
-
-		U c = 0;
-		indices[c++] = 0;
-		indices[c++] = 1;
-		indices[c++] = 1;
-		indices[c++] = 2;
-		indices[c++] = 2;
-		indices[c++] = 3;
-		indices[c++] = 3;
-		indices[c++] = 0;
-
-		indices[c++] = 4;
-		indices[c++] = 5;
-		indices[c++] = 5;
-		indices[c++] = 6;
-		indices[c++] = 6;
-		indices[c++] = 7;
-		indices[c++] = 7;
-		indices[c++] = 4;
-
-		indices[c++] = 0;
-		indices[c++] = 4;
-		indices[c++] = 1;
-		indices[c++] = 5;
-		indices[c++] = 2;
-		indices[c++] = 6;
-		indices[c++] = 3;
-		indices[c++] = 7;
-
-		ANKI_ASSERT(c == INDEX_COUNT);
+		StagingGpuMemoryToken vertToken, indicesToken;
+		U32 indexCount;
+		allocateAndPopulateDebugBox(*ctx.m_stagingGpuAllocator, vertToken, indicesToken, indexCount);
 
 		// Set the uniforms
 		StagingGpuMemoryToken unisToken;
@@ -311,7 +267,7 @@ void ModelNode::draw(RenderQueueDrawContext& ctx, ConstWeakArray<void*> userData
 
 		cmdb->bindUniformBuffer(1, 0, unisToken.m_buffer, unisToken.m_offset, unisToken.m_range);
 
-		cmdb->drawElements(PrimitiveTopology::LINES, INDEX_COUNT, userData.getSize());
+		cmdb->drawElements(PrimitiveTopology::LINES, indexCount, userData.getSize());
 
 		// Restore state
 		if(!enableDepthTest)

+ 2 - 2
src/anki/scene/ReflectionProbeNode.cpp

@@ -147,10 +147,10 @@ Error ReflectionProbeNode::frameUpdate(Second prevUpdateTime, Second crntTime)
 	// Check the reflection probe component and if it's marked for rendering enable the frustum components
 	const ReflectionProbeComponent& reflc = getComponent<ReflectionProbeComponent>();
 
-	FrustumComponentVisibilityTestFlag testFlags =
+	const FrustumComponentVisibilityTestFlag testFlags =
 		reflc.getMarkedForRendering() ? FRUSTUM_TEST_FLAGS : FrustumComponentVisibilityTestFlag::NONE;
 
-	Error err = iterateComponentsOfType<FrustumComponent>([testFlags](FrustumComponent& frc) -> Error {
+	const Error err = iterateComponentsOfType<FrustumComponent>([testFlags](FrustumComponent& frc) -> Error {
 		frc.setEnabledVisibilityTests(testFlags);
 		return Error::NONE;
 	});

+ 0 - 2
src/anki/scene/ReflectionProbeNode.h

@@ -6,9 +6,7 @@
 #pragma once
 
 #include <anki/scene/SceneNode.h>
-#include <anki/collision/Sphere.h>
 #include <anki/collision/Aabb.h>
-#include <anki/Gr.h>
 
 namespace anki
 {

+ 42 - 0
src/anki/scene/Visibility.cpp

@@ -15,6 +15,7 @@
 #include <anki/scene/components/FogDensityComponent.h>
 #include <anki/scene/components/LightComponent.h>
 #include <anki/scene/components/SpatialComponent.h>
+#include <anki/scene/components/GlobalIlluminationProbeComponent.h>
 #include <anki/renderer/MainRenderer.h>
 #include <anki/util/Logger.h>
 #include <anki/util/ThreadHive.h>
@@ -61,6 +62,15 @@ void VisibilityContext::submitNewWork(const FrustumComponent& frc, RenderQueue&
 	rqueue.m_previousViewProjectionMatrix = frc.getPreviousViewProjectionMatrix();
 	rqueue.m_cameraNear = frc.getNear();
 	rqueue.m_cameraFar = frc.getFar();
+	if(frc.getFrustumType() == FrustumType::PERSPECTIVE)
+	{
+		rqueue.m_cameraFovX = frc.getFovX();
+		rqueue.m_cameraFovY = frc.getFovY();
+	}
+	else
+	{
+		rqueue.m_cameraFovX = rqueue.m_cameraFovY = 0.0f;
+	}
 	rqueue.m_effectiveShadowDistance = frc.getEffectiveShadowDistance();
 
 	auto alloc = m_scene->getFrameAllocator();
@@ -246,6 +256,9 @@ void VisibilityTestTask::test(ThreadHive& hive, U32 taskId)
 	const Bool wantsFogDensityComponents =
 		testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::FOG_DENSITY_COMPONENTS);
 
+	const Bool wantsGiProbeCoponents =
+		testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::GLOBAL_ILLUMINATION_PROBES);
+
 	const Bool wantsEarlyZ = testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::EARLY_Z)
 							 && m_frcCtx->m_visCtx->m_earlyZDist > 0.0f;
 
@@ -286,6 +299,9 @@ void VisibilityTestTask::test(ThreadHive& hive, U32 taskId)
 		const FogDensityComponent* fogc = nullptr;
 		wantNode |= wantsFogDensityComponents && (fogc = node.tryGetComponent<FogDensityComponent>());
 
+		GlobalIlluminationProbeComponent* giprobec = nullptr;
+		wantNode |= wantsGiProbeCoponents && (giprobec = node.tryGetComponent<GlobalIlluminationProbeComponent>());
+
 		if(ANKI_UNLIKELY(!wantNode))
 		{
 			// Skip node
@@ -541,6 +557,29 @@ void VisibilityTestTask::test(ThreadHive& hive, U32 taskId)
 			fogc->setupFogDensityQueueElement(*el);
 		}
 
+		if(giprobec)
+		{
+			GlobalIlluminationProbeQueueElement* el = result.m_giProbes.newElement(alloc);
+			giprobec->setupGlobalIlluminationProbeQueueElement(*el);
+
+			if(giprobec->getMarkedForRendering())
+			{
+				RenderQueue* a = alloc.newArray<RenderQueue>(6);
+				nextQueues = WeakArray<RenderQueue>(a, 6);
+
+				el->m_renderQueues[0] = &nextQueues[0];
+				el->m_renderQueues[1] = &nextQueues[1];
+				el->m_renderQueues[2] = &nextQueues[2];
+				el->m_renderQueues[3] = &nextQueues[3];
+				el->m_renderQueues[4] = &nextQueues[4];
+				el->m_renderQueues[5] = &nextQueues[5];
+			}
+			else
+			{
+				el->m_renderQueues = {{nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}};
+			}
+		}
+
 		// Add more frustums to the list
 		if(nextQueues.getSize() > 0)
 		{
@@ -625,6 +664,7 @@ void CombineResultsTask::combine()
 	ANKI_VIS_COMBINE(LensFlareQueueElement, m_lensFlares);
 	ANKI_VIS_COMBINE(DecalQueueElement, m_decals);
 	ANKI_VIS_COMBINE(FogDensityQueueElement, m_fogDensityVolumes);
+	ANKI_VIS_COMBINE(GlobalIlluminationProbeQueueElement, m_giProbes);
 
 	for(U i = 0; i < threadCount; ++i)
 	{
@@ -660,6 +700,8 @@ void CombineResultsTask::combine()
 		results.m_forwardShadingRenderables.getEnd(),
 		RevDistanceSortFunctor<RenderableQueueElement>());
 
+	std::sort(results.m_giProbes.getBegin(), results.m_giProbes.getEnd());
+
 	// Cleanup
 	if(m_frcCtx->m_r)
 	{

+ 1 - 0
src/anki/scene/VisibilityInternal.h

@@ -115,6 +115,7 @@ public:
 	TRenderQueueElementStorage<LensFlareQueueElement> m_lensFlares;
 	TRenderQueueElementStorage<DecalQueueElement> m_decals;
 	TRenderQueueElementStorage<FogDensityQueueElement> m_fogDensityVolumes;
+	TRenderQueueElementStorage<GlobalIlluminationProbeQueueElement> m_giProbes;
 
 	Timestamp m_timestamp = 0;
 

+ 10 - 12
src/anki/scene/components/FrustumComponent.cpp

@@ -19,9 +19,6 @@ FrustumComponent::FrustumComponent(SceneNode* node, FrustumType frustumType)
 	ANKI_ASSERT(node);
 	ANKI_ASSERT(frustumType < FrustumType::COUNT);
 
-	m_flags.set(SHAPE_MARKED_FOR_UPDATE | TRANSFORM_MARKED_FOR_UPDATE);
-	setEnabledVisibilityTests(FrustumComponentVisibilityTestFlag::NONE);
-
 	// Set some default values
 	if(frustumType == FrustumType::PERSPECTIVE)
 	{
@@ -48,7 +45,7 @@ Bool FrustumComponent::updateInternal()
 	m_prevViewProjMat = m_viewProjMat;
 
 	// Update the shape
-	if(m_flags.get(SHAPE_MARKED_FOR_UPDATE))
+	if(m_shapeMarkedForUpdate)
 	{
 		updated = true;
 
@@ -105,7 +102,7 @@ Bool FrustumComponent::updateInternal()
 	}
 
 	// Update transform related things
-	if(m_flags.get(TRANSFORM_MARKED_FOR_UPDATE))
+	if(m_trfMarkedForUpdate)
 	{
 		updated = true;
 		m_viewMat = Mat4(m_trf.getInverse());
@@ -115,7 +112,8 @@ Bool FrustumComponent::updateInternal()
 	if(updated)
 	{
 		m_viewProjMat = m_projMat * m_viewMat;
-		m_flags.unset(SHAPE_MARKED_FOR_UPDATE | TRANSFORM_MARKED_FOR_UPDATE);
+		m_shapeMarkedForUpdate = false;
+		m_trfMarkedForUpdate = false;
 
 		if(m_frustumType == FrustumType::PERSPECTIVE)
 		{
@@ -156,15 +154,15 @@ void FrustumComponent::fillCoverageBufferCallback(void* userData, F32* depthValu
 
 void FrustumComponent::setEnabledVisibilityTests(FrustumComponentVisibilityTestFlag bits)
 {
-	m_flags.unset(FrustumComponentVisibilityTestFlag::ALL);
-	m_flags.set(bits, true);
+	m_flags = FrustumComponentVisibilityTestFlag::NONE;
+	m_flags |= bits;
 
 #if ANKI_ASSERTS_ENABLED
-	if(m_flags.get(FrustumComponentVisibilityTestFlag::RENDER_COMPONENTS)
-		|| m_flags.get(FrustumComponentVisibilityTestFlag::SHADOW_CASTERS))
+	if(!!(m_flags & FrustumComponentVisibilityTestFlag::RENDER_COMPONENTS)
+		|| !!(m_flags & FrustumComponentVisibilityTestFlag::SHADOW_CASTERS))
 	{
-		if(m_flags.get(FrustumComponentVisibilityTestFlag::RENDER_COMPONENTS)
-			== m_flags.get(FrustumComponentVisibilityTestFlag::SHADOW_CASTERS))
+		if((m_flags & FrustumComponentVisibilityTestFlag::RENDER_COMPONENTS)
+			== (m_flags & FrustumComponentVisibilityTestFlag::SHADOW_CASTERS))
 		{
 			ANKI_ASSERT(0 && "Cannot have them both");
 		}

+ 16 - 19
src/anki/scene/components/FrustumComponent.h

@@ -36,13 +36,15 @@ enum class FrustumComponentVisibilityTestFlag : U16
 	OCCLUDERS = 1 << 10,
 	DECALS = 1 << 11,
 	FOG_DENSITY_COMPONENTS = 1 << 12,
-	EARLY_Z = 1 << 13,
+	GLOBAL_ILLUMINATION_PROBES = 1 << 13,
+	EARLY_Z = 1 << 14,
 
 	LAST = EARLY_Z,
 
 	ALL = RENDER_COMPONENTS | LIGHT_COMPONENTS | LENS_FLARE_COMPONENTS | SHADOW_CASTERS | POINT_LIGHT_SHADOWS_ENABLED
 		  | SPOT_LIGHT_SHADOWS_ENABLED | DIRECTIONAL_LIGHT_SHADOWS_ALL_CASCADES | DIRECTIONAL_LIGHT_SHADOWS_1_CASCADE
-		  | REFLECTION_PROBES | REFLECTION_PROXIES | OCCLUDERS | DECALS | FOG_DENSITY_COMPONENTS | EARLY_Z,
+		  | REFLECTION_PROBES | REFLECTION_PROXIES | OCCLUDERS | DECALS | FOG_DENSITY_COMPONENTS
+		  | GLOBAL_ILLUMINATION_PROBES | EARLY_Z,
 
 	ALL_SHADOWS_ENABLED =
 		POINT_LIGHT_SHADOWS_ENABLED | SPOT_LIGHT_SHADOWS_ENABLED | DIRECTIONAL_LIGHT_SHADOWS_ALL_CASCADES
@@ -78,7 +80,7 @@ public:
 		m_perspective.m_far = far;
 		m_perspective.m_fovX = fovX;
 		m_perspective.m_fovY = fovY;
-		m_flags.set(SHAPE_MARKED_FOR_UPDATE);
+		m_shapeMarkedForUpdate = true;
 	}
 
 	void setOrthographic(F32 near, F32 far, F32 right, F32 left, F32 top, F32 bottom)
@@ -92,13 +94,13 @@ public:
 		m_ortho.m_left = left;
 		m_ortho.m_top = top;
 		m_ortho.m_bottom = bottom;
-		m_flags.set(SHAPE_MARKED_FOR_UPDATE);
+		m_shapeMarkedForUpdate = true;
 	}
 
 	void setNear(F32 near)
 	{
 		m_common.m_near = near;
-		m_flags.set(SHAPE_MARKED_FOR_UPDATE);
+		m_shapeMarkedForUpdate = true;
 	}
 
 	F32 getNear() const
@@ -109,7 +111,7 @@ public:
 	void setFar(F32 far)
 	{
 		m_common.m_far = far;
-		m_flags.set(SHAPE_MARKED_FOR_UPDATE);
+		m_shapeMarkedForUpdate = true;
 	}
 
 	F32 getFar() const
@@ -120,7 +122,7 @@ public:
 	void setFovX(F32 fovx)
 	{
 		ANKI_ASSERT(m_frustumType == FrustumType::PERSPECTIVE);
-		m_flags.set(SHAPE_MARKED_FOR_UPDATE);
+		m_shapeMarkedForUpdate = true;
 		m_perspective.m_fovX = fovx;
 	}
 
@@ -133,7 +135,7 @@ public:
 	void setFovY(F32 fovy)
 	{
 		ANKI_ASSERT(m_frustumType == FrustumType::PERSPECTIVE);
-		m_flags.set(SHAPE_MARKED_FOR_UPDATE);
+		m_shapeMarkedForUpdate = true;
 		m_perspective.m_fovY = fovy;
 	}
 
@@ -156,7 +158,7 @@ public:
 	void setTransform(const Transform& trf)
 	{
 		m_trf = trf;
-		m_flags.set(TRANSFORM_MARKED_FOR_UPDATE);
+		m_trfMarkedForUpdate = true;
 	}
 
 	const Mat4& getProjectionMatrix() const
@@ -205,12 +207,12 @@ public:
 
 	Bool visibilityTestsEnabled(FrustumComponentVisibilityTestFlag bits) const
 	{
-		return m_flags.get(bits);
+		return !!(m_flags & bits);
 	}
 
 	Bool anyVisibilityTestEnabled() const
 	{
-		return m_flags.getAny(FrustumComponentVisibilityTestFlag::ALL);
+		return !!(m_flags & FrustumComponentVisibilityTestFlag::ALL);
 	}
 
 	/// The type is FillCoverageBufferCallback.
@@ -267,13 +269,6 @@ public:
 	}
 
 private:
-	enum Flags : U16
-	{
-		SHAPE_MARKED_FOR_UPDATE = static_cast<U16>(FrustumComponentVisibilityTestFlag::LAST) << 1,
-		TRANSFORM_MARKED_FOR_UPDATE = static_cast<U16>(FrustumComponentVisibilityTestFlag::LAST) << 2,
-	};
-	ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(Flags, friend)
-
 	class Common
 	{
 	public:
@@ -334,7 +329,9 @@ private:
 		U32 m_depthMapHeight = 0;
 	} m_coverageBuff; ///< Coverage buffer for extra visibility tests.
 
-	BitMask<U16> m_flags = {0};
+	FrustumComponentVisibilityTestFlag m_flags = FrustumComponentVisibilityTestFlag::NONE;
+	Bool m_shapeMarkedForUpdate = true;
+	Bool m_trfMarkedForUpdate = true;
 
 	Bool updateInternal();
 };

+ 88 - 0
src/anki/scene/components/GlobalIlluminationProbeComponent.cpp

@@ -0,0 +1,88 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/scene/components/GlobalIlluminationProbeComponent.h>
+#include <anki/scene/DebugDrawer.h>
+#include <anki/resource/ResourceManager.h>
+
+namespace anki
+{
+
+Error GlobalIlluminationProbeComponent::init(ResourceManager& rsrc)
+{
+	return rsrc.loadResource("shaders/SceneDebug.glslp", m_dbgProg);
+}
+
+void GlobalIlluminationProbeComponent::debugDraw(RenderQueueDrawContext& ctx, ConstWeakArray<void*> userData)
+{
+	StagingGpuMemoryToken vertToken, indicesToken;
+	U32 indexCount;
+	allocateAndPopulateDebugBox(*ctx.m_stagingGpuAllocator, vertToken, indicesToken, indexCount);
+
+	// Set the uniforms
+	StagingGpuMemoryToken unisToken;
+	Mat4* mvps = static_cast<Mat4*>(ctx.m_stagingGpuAllocator->allocateFrame(
+		sizeof(Mat4) * userData.getSize() + sizeof(Vec4), StagingGpuMemoryType::UNIFORM, unisToken));
+
+	for(U i = 0; i < userData.getSize(); ++i)
+	{
+		const GlobalIlluminationProbeComponent& self =
+			*static_cast<const GlobalIlluminationProbeComponent*>(userData[i]);
+
+		const Vec3 tsl = (self.m_aabbMin + self.m_aabbMax) / 2.0f;
+		const Vec3 scale = (tsl - self.m_aabbMin);
+
+		// Set non uniform scale.
+		Mat3 rot = Mat3::getIdentity();
+		rot(0, 0) *= scale.x();
+		rot(1, 1) *= scale.y();
+		rot(2, 2) *= scale.z();
+
+		*mvps = ctx.m_viewProjectionMatrix * Mat4(tsl.xyz1(), rot, 1.0f);
+		++mvps;
+	}
+
+	Vec4* color = reinterpret_cast<Vec4*>(mvps);
+	*color = Vec4(0.729f, 0.635f, 0.196f, 1.0f);
+
+	// Setup state
+	const GlobalIlluminationProbeComponent& self = *static_cast<const GlobalIlluminationProbeComponent*>(userData[0]);
+	ShaderProgramResourceMutationInitList<2> mutators(self.m_dbgProg);
+	mutators.add("COLOR_TEXTURE", 0);
+	mutators.add("DITHERED_DEPTH_TEST", ctx.m_debugDrawFlags.get(RenderQueueDebugDrawFlag::DITHERED_DEPTH_TEST_ON));
+	ShaderProgramResourceConstantValueInitList<1> consts(self.m_dbgProg);
+	consts.add("INSTANCE_COUNT", U32(userData.getSize()));
+	const ShaderProgramResourceVariant* variant;
+	self.m_dbgProg->getOrCreateVariant(mutators.get(), consts.get(), variant);
+
+	CommandBufferPtr& cmdb = ctx.m_commandBuffer;
+	cmdb->bindShaderProgram(variant->getProgram());
+
+	const Bool enableDepthTest = ctx.m_debugDrawFlags.get(RenderQueueDebugDrawFlag::DEPTH_TEST_ON);
+	if(enableDepthTest)
+	{
+		cmdb->setDepthCompareOperation(CompareOperation::LESS);
+	}
+	else
+	{
+		cmdb->setDepthCompareOperation(CompareOperation::ALWAYS);
+	}
+
+	cmdb->setVertexAttribute(0, 0, Format::R32G32B32_SFLOAT, 0);
+	cmdb->bindVertexBuffer(0, vertToken.m_buffer, vertToken.m_offset, sizeof(Vec3));
+	cmdb->bindIndexBuffer(indicesToken.m_buffer, indicesToken.m_offset, IndexType::U16);
+
+	cmdb->bindUniformBuffer(1, 0, unisToken.m_buffer, unisToken.m_offset, unisToken.m_range);
+
+	cmdb->drawElements(PrimitiveTopology::LINES, indexCount, userData.getSize());
+
+	// Restore state
+	if(!enableDepthTest)
+	{
+		cmdb->setDepthCompareOperation(CompareOperation::LESS);
+	}
+}
+
+} // end namespace anki

+ 152 - 0
src/anki/scene/components/GlobalIlluminationProbeComponent.h

@@ -0,0 +1,152 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/scene/components/SceneComponent.h>
+#include <anki/renderer/RenderQueue.h>
+
+namespace anki
+{
+
+/// @addtogroup scene
+/// @{
+
+/// Global illumination probe component. It's an axis aligned box divided into cells.
+class GlobalIlluminationProbeComponent : public SceneComponent
+{
+public:
+	static const SceneComponentType CLASS_TYPE = SceneComponentType::GLOBAL_ILLUMINATION_PROBE;
+
+	GlobalIlluminationProbeComponent(U64 uuid)
+		: SceneComponent(CLASS_TYPE)
+		, m_uuid(uuid)
+	{
+		ANKI_ASSERT(uuid > 0);
+	}
+
+	ANKI_USE_RESULT Error init(ResourceManager& rsrc);
+
+	/// Set the bounding box in world coordinates.
+	void setBoundingBox(const Vec4& min, const Vec4& max)
+	{
+		ANKI_ASSERT(min.xyz() < max.xyz());
+		m_aabbMin = min.xyz();
+		m_aabbMax = max.xyz();
+		updateMembers();
+		m_dirty = true;
+	}
+
+	/// Set the cell size in meters.
+	void setCellSize(F32 cellSize)
+	{
+		ANKI_ASSERT(cellSize > 0.0f);
+		m_cellSize = cellSize;
+		updateMembers();
+		m_dirty = true;
+	}
+
+	F32 getCellSize() const
+	{
+		return m_cellSize;
+	}
+
+	Vec4 getAlignedBoundingBoxMin() const
+	{
+		return m_aabbMin.xyz0();
+	}
+
+	Vec4 getAlignedBoundingBoxMax() const
+	{
+		return m_aabbMax.xyz0();
+	}
+
+	F32 getFadeDistance() const
+	{
+		return m_fadeDistance;
+	}
+
+	void setFadeDistance(F32 dist)
+	{
+		ANKI_ASSERT(dist >= 0.0f);
+		m_fadeDistance = dist;
+	}
+
+	/// Returns true if it's marked for update this frame.
+	Bool getMarkedForRendering() const
+	{
+		return m_markedForRendering;
+	}
+
+	/// Get the cell position that will be rendered this frame.
+	Vec4 getRenderPosition() const
+	{
+		ANKI_ASSERT(m_renderPosition > m_aabbMin && m_renderPosition < m_aabbMax);
+		ANKI_ASSERT(m_markedForRendering);
+		return m_renderPosition.xyz0();
+	}
+
+	void setupGlobalIlluminationProbeQueueElement(GlobalIlluminationProbeQueueElement& el)
+	{
+		el.m_uuid = m_uuid;
+		el.m_feedbackCallback = giProbeQueueElementFeedbackCallback;
+		el.m_debugDrawCallback = debugDrawCallback;
+		el.m_userData = this;
+		el.m_renderQueues = {};
+		el.m_aabbMin = m_aabbMin;
+		el.m_aabbMax = m_aabbMax;
+		el.m_cellCounts = m_cellCounts;
+		el.m_totalCellCount = m_cellCounts.x() * m_cellCounts.y() * m_cellCounts.z();
+		el.m_cellSizes = (m_aabbMax - m_aabbMin) / Vec3(m_cellCounts);
+		el.m_fadeDistance = m_fadeDistance;
+	}
+
+	ANKI_USE_RESULT Error update(SceneNode& node, Second prevTime, Second crntTime, Bool& updated) override
+	{
+		updated = m_dirty;
+		m_dirty = false;
+		return Error::NONE;
+	}
+
+private:
+	ShaderProgramResourcePtr m_dbgProg;
+	U64 m_uuid;
+	Vec3 m_aabbMin = Vec3(-1.0f);
+	Vec3 m_aabbMax = Vec3(+1.0f);
+	Vec3 m_renderPosition = Vec3(0.0f);
+	UVec3 m_cellCounts = UVec3(2u);
+	F32 m_cellSize = 4.0f; ///< Cell size in meters.
+	F32 m_fadeDistance = 0.2f;
+	Bool m_markedForRendering = false;
+	Bool m_dirty = true;
+
+	static void giProbeQueueElementFeedbackCallback(
+		Bool fillRenderQueuesOnNextFrame, void* userData, const Vec4& eyeWorldPosition)
+	{
+		ANKI_ASSERT(userData);
+		GlobalIlluminationProbeComponent& self = *static_cast<GlobalIlluminationProbeComponent*>(userData);
+		ANKI_ASSERT(eyeWorldPosition.xyz() > self.m_aabbMin && eyeWorldPosition.xyz() < self.m_aabbMax);
+		self.m_markedForRendering = fillRenderQueuesOnNextFrame;
+		self.m_renderPosition = eyeWorldPosition.xyz();
+	}
+
+	static void debugDrawCallback(RenderQueueDrawContext& ctx, ConstWeakArray<void*> userData)
+	{
+		debugDraw(ctx, userData);
+	}
+
+	static void debugDraw(RenderQueueDrawContext& ctx, ConstWeakArray<void*> userData);
+
+	/// Recalc come values.
+	void updateMembers()
+	{
+		const Vec3 dist = m_aabbMax - m_aabbMin;
+		m_cellCounts = UVec3(dist / m_cellSize);
+		m_cellCounts = m_cellCounts.max(UVec3(1));
+	}
+};
+/// @}
+
+} // end namespace anki

+ 1 - 0
src/anki/scene/components/SceneComponent.h

@@ -27,6 +27,7 @@ enum class SceneComponentType : U16
 	LENS_FLARE,
 	BODY,
 	REFLECTION_PROBE,
+	GLOBAL_ILLUMINATION_PROBE,
 	OCCLUDER,
 	DECAL,
 	SKIN,

+ 571 - 0
src/anki/script/Scene.cpp

@@ -1948,6 +1948,385 @@ static inline void wrapFrustumComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
+LuaUserDataTypeInfo luaUserDataTypeInfoGlobalIlluminationProbeComponent = {-8635406553972724905,
+	"GlobalIlluminationProbeComponent",
+	LuaUserData::computeSizeForGarbageCollected<GlobalIlluminationProbeComponent>(),
+	nullptr,
+	nullptr};
+
+template<>
+const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<GlobalIlluminationProbeComponent>()
+{
+	return luaUserDataTypeInfoGlobalIlluminationProbeComponent;
+}
+
+/// Pre-wrap method GlobalIlluminationProbeComponent::setBoundingBox.
+static inline int pwrapGlobalIlluminationProbeComponentsetBoundingBox(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 3)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoGlobalIlluminationProbeComponent, ud))
+	{
+		return -1;
+	}
+
+	GlobalIlluminationProbeComponent* self = ud->getData<GlobalIlluminationProbeComponent>();
+
+	// Pop arguments
+	extern LuaUserDataTypeInfo luaUserDataTypeInfoVec4;
+	if(ANKI_UNLIKELY(LuaBinder::checkUserData(l, 2, luaUserDataTypeInfoVec4, ud)))
+	{
+		return -1;
+	}
+
+	Vec4* iarg0 = ud->getData<Vec4>();
+	const Vec4& arg0(*iarg0);
+
+	extern LuaUserDataTypeInfo luaUserDataTypeInfoVec4;
+	if(ANKI_UNLIKELY(LuaBinder::checkUserData(l, 3, luaUserDataTypeInfoVec4, ud)))
+	{
+		return -1;
+	}
+
+	Vec4* iarg1 = ud->getData<Vec4>();
+	const Vec4& arg1(*iarg1);
+
+	// Call the method
+	self->setBoundingBox(arg0, arg1);
+
+	return 0;
+}
+
+/// Wrap method GlobalIlluminationProbeComponent::setBoundingBox.
+static int wrapGlobalIlluminationProbeComponentsetBoundingBox(lua_State* l)
+{
+	int res = pwrapGlobalIlluminationProbeComponentsetBoundingBox(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+/// Pre-wrap method GlobalIlluminationProbeComponent::getAlignedBoundingBoxMin.
+static inline int pwrapGlobalIlluminationProbeComponentgetAlignedBoundingBoxMin(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 1)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoGlobalIlluminationProbeComponent, ud))
+	{
+		return -1;
+	}
+
+	GlobalIlluminationProbeComponent* self = ud->getData<GlobalIlluminationProbeComponent>();
+
+	// Call the method
+	Vec4 ret = self->getAlignedBoundingBoxMin();
+
+	// Push return value
+	size = LuaUserData::computeSizeForGarbageCollected<Vec4>();
+	voidp = lua_newuserdata(l, size);
+	luaL_setmetatable(l, "Vec4");
+	ud = static_cast<LuaUserData*>(voidp);
+	extern LuaUserDataTypeInfo luaUserDataTypeInfoVec4;
+	ud->initGarbageCollected(&luaUserDataTypeInfoVec4);
+	::new(ud->getData<Vec4>()) Vec4(std::move(ret));
+
+	return 1;
+}
+
+/// Wrap method GlobalIlluminationProbeComponent::getAlignedBoundingBoxMin.
+static int wrapGlobalIlluminationProbeComponentgetAlignedBoundingBoxMin(lua_State* l)
+{
+	int res = pwrapGlobalIlluminationProbeComponentgetAlignedBoundingBoxMin(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+/// Pre-wrap method GlobalIlluminationProbeComponent::getAlignedBoundingBoxMax.
+static inline int pwrapGlobalIlluminationProbeComponentgetAlignedBoundingBoxMax(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 1)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoGlobalIlluminationProbeComponent, ud))
+	{
+		return -1;
+	}
+
+	GlobalIlluminationProbeComponent* self = ud->getData<GlobalIlluminationProbeComponent>();
+
+	// Call the method
+	Vec4 ret = self->getAlignedBoundingBoxMax();
+
+	// Push return value
+	size = LuaUserData::computeSizeForGarbageCollected<Vec4>();
+	voidp = lua_newuserdata(l, size);
+	luaL_setmetatable(l, "Vec4");
+	ud = static_cast<LuaUserData*>(voidp);
+	extern LuaUserDataTypeInfo luaUserDataTypeInfoVec4;
+	ud->initGarbageCollected(&luaUserDataTypeInfoVec4);
+	::new(ud->getData<Vec4>()) Vec4(std::move(ret));
+
+	return 1;
+}
+
+/// Wrap method GlobalIlluminationProbeComponent::getAlignedBoundingBoxMax.
+static int wrapGlobalIlluminationProbeComponentgetAlignedBoundingBoxMax(lua_State* l)
+{
+	int res = pwrapGlobalIlluminationProbeComponentgetAlignedBoundingBoxMax(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+/// Pre-wrap method GlobalIlluminationProbeComponent::setCellSize.
+static inline int pwrapGlobalIlluminationProbeComponentsetCellSize(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 2)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoGlobalIlluminationProbeComponent, ud))
+	{
+		return -1;
+	}
+
+	GlobalIlluminationProbeComponent* self = ud->getData<GlobalIlluminationProbeComponent>();
+
+	// Pop arguments
+	F32 arg0;
+	if(ANKI_UNLIKELY(LuaBinder::checkNumber(l, 2, arg0)))
+	{
+		return -1;
+	}
+
+	// Call the method
+	self->setCellSize(arg0);
+
+	return 0;
+}
+
+/// Wrap method GlobalIlluminationProbeComponent::setCellSize.
+static int wrapGlobalIlluminationProbeComponentsetCellSize(lua_State* l)
+{
+	int res = pwrapGlobalIlluminationProbeComponentsetCellSize(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+/// Pre-wrap method GlobalIlluminationProbeComponent::getCellSize.
+static inline int pwrapGlobalIlluminationProbeComponentgetCellSize(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 1)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoGlobalIlluminationProbeComponent, ud))
+	{
+		return -1;
+	}
+
+	GlobalIlluminationProbeComponent* self = ud->getData<GlobalIlluminationProbeComponent>();
+
+	// Call the method
+	F32 ret = self->getCellSize();
+
+	// Push return value
+	lua_pushnumber(l, ret);
+
+	return 1;
+}
+
+/// Wrap method GlobalIlluminationProbeComponent::getCellSize.
+static int wrapGlobalIlluminationProbeComponentgetCellSize(lua_State* l)
+{
+	int res = pwrapGlobalIlluminationProbeComponentgetCellSize(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+/// Pre-wrap method GlobalIlluminationProbeComponent::setFadeDistance.
+static inline int pwrapGlobalIlluminationProbeComponentsetFadeDistance(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 2)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoGlobalIlluminationProbeComponent, ud))
+	{
+		return -1;
+	}
+
+	GlobalIlluminationProbeComponent* self = ud->getData<GlobalIlluminationProbeComponent>();
+
+	// Pop arguments
+	F32 arg0;
+	if(ANKI_UNLIKELY(LuaBinder::checkNumber(l, 2, arg0)))
+	{
+		return -1;
+	}
+
+	// Call the method
+	self->setFadeDistance(arg0);
+
+	return 0;
+}
+
+/// Wrap method GlobalIlluminationProbeComponent::setFadeDistance.
+static int wrapGlobalIlluminationProbeComponentsetFadeDistance(lua_State* l)
+{
+	int res = pwrapGlobalIlluminationProbeComponentsetFadeDistance(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+/// Pre-wrap method GlobalIlluminationProbeComponent::getFadeDistance.
+static inline int pwrapGlobalIlluminationProbeComponentgetFadeDistance(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 1)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoGlobalIlluminationProbeComponent, ud))
+	{
+		return -1;
+	}
+
+	GlobalIlluminationProbeComponent* self = ud->getData<GlobalIlluminationProbeComponent>();
+
+	// Call the method
+	F32 ret = self->getFadeDistance();
+
+	// Push return value
+	lua_pushnumber(l, ret);
+
+	return 1;
+}
+
+/// Wrap method GlobalIlluminationProbeComponent::getFadeDistance.
+static int wrapGlobalIlluminationProbeComponentgetFadeDistance(lua_State* l)
+{
+	int res = pwrapGlobalIlluminationProbeComponentgetFadeDistance(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+/// Wrap class GlobalIlluminationProbeComponent.
+static inline void wrapGlobalIlluminationProbeComponent(lua_State* l)
+{
+	LuaBinder::createClass(l, &luaUserDataTypeInfoGlobalIlluminationProbeComponent);
+	LuaBinder::pushLuaCFuncMethod(l, "setBoundingBox", wrapGlobalIlluminationProbeComponentsetBoundingBox);
+	LuaBinder::pushLuaCFuncMethod(
+		l, "getAlignedBoundingBoxMin", wrapGlobalIlluminationProbeComponentgetAlignedBoundingBoxMin);
+	LuaBinder::pushLuaCFuncMethod(
+		l, "getAlignedBoundingBoxMax", wrapGlobalIlluminationProbeComponentgetAlignedBoundingBoxMax);
+	LuaBinder::pushLuaCFuncMethod(l, "setCellSize", wrapGlobalIlluminationProbeComponentsetCellSize);
+	LuaBinder::pushLuaCFuncMethod(l, "getCellSize", wrapGlobalIlluminationProbeComponentgetCellSize);
+	LuaBinder::pushLuaCFuncMethod(l, "setFadeDistance", wrapGlobalIlluminationProbeComponentsetFadeDistance);
+	LuaBinder::pushLuaCFuncMethod(l, "getFadeDistance", wrapGlobalIlluminationProbeComponentgetFadeDistance);
+	lua_settop(l, 0);
+}
+
 LuaUserDataTypeInfo luaUserDataTypeInfoSceneNode = {
 	-2220074417980276571, "SceneNode", LuaUserData::computeSizeForGarbageCollected<SceneNode>(), nullptr, nullptr};
 
@@ -2481,6 +2860,62 @@ static int wrapSceneNodegetFrustumComponent(lua_State* l)
 	return 0;
 }
 
+/// Pre-wrap method SceneNode::tryGetComponent<GlobalIlluminationProbeComponent>.
+static inline int pwrapSceneNodegetGlobalIlluminationProbeComponent(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 1)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoSceneNode, ud))
+	{
+		return -1;
+	}
+
+	SceneNode* self = ud->getData<SceneNode>();
+
+	// Call the method
+	GlobalIlluminationProbeComponent* ret = self->tryGetComponent<GlobalIlluminationProbeComponent>();
+
+	// Push return value
+	if(ANKI_UNLIKELY(ret == nullptr))
+	{
+		lua_pushstring(l, "Glue code returned nullptr");
+		return -1;
+	}
+
+	voidp = lua_newuserdata(l, sizeof(LuaUserData));
+	ud = static_cast<LuaUserData*>(voidp);
+	luaL_setmetatable(l, "GlobalIlluminationProbeComponent");
+	extern LuaUserDataTypeInfo luaUserDataTypeInfoGlobalIlluminationProbeComponent;
+	ud->initPointed(
+		&luaUserDataTypeInfoGlobalIlluminationProbeComponent, const_cast<GlobalIlluminationProbeComponent*>(ret));
+
+	return 1;
+}
+
+/// Wrap method SceneNode::tryGetComponent<GlobalIlluminationProbeComponent>.
+static int wrapSceneNodegetGlobalIlluminationProbeComponent(lua_State* l)
+{
+	int res = pwrapSceneNodegetGlobalIlluminationProbeComponent(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
 /// Wrap class SceneNode.
 static inline void wrapSceneNode(lua_State* l)
 {
@@ -2495,6 +2930,8 @@ static inline void wrapSceneNode(lua_State* l)
 	LuaBinder::pushLuaCFuncMethod(l, "getTriggerComponent", wrapSceneNodegetTriggerComponent);
 	LuaBinder::pushLuaCFuncMethod(l, "getFogDensityComponent", wrapSceneNodegetFogDensityComponent);
 	LuaBinder::pushLuaCFuncMethod(l, "getFrustumComponent", wrapSceneNodegetFrustumComponent);
+	LuaBinder::pushLuaCFuncMethod(
+		l, "getGlobalIlluminationProbeComponent", wrapSceneNodegetGlobalIlluminationProbeComponent);
 	lua_settop(l, 0);
 }
 
@@ -3376,6 +3813,75 @@ static inline void wrapFogDensityNode(lua_State* l)
 	lua_settop(l, 0);
 }
 
+LuaUserDataTypeInfo luaUserDataTypeInfoGlobalIlluminationProbeNode = {7117765190519964845,
+	"GlobalIlluminationProbeNode",
+	LuaUserData::computeSizeForGarbageCollected<GlobalIlluminationProbeNode>(),
+	nullptr,
+	nullptr};
+
+template<>
+const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<GlobalIlluminationProbeNode>()
+{
+	return luaUserDataTypeInfoGlobalIlluminationProbeNode;
+}
+
+/// Pre-wrap method GlobalIlluminationProbeNode::getSceneNodeBase.
+static inline int pwrapGlobalIlluminationProbeNodegetSceneNodeBase(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 1)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoGlobalIlluminationProbeNode, ud))
+	{
+		return -1;
+	}
+
+	GlobalIlluminationProbeNode* self = ud->getData<GlobalIlluminationProbeNode>();
+
+	// Call the method
+	SceneNode& ret = *self;
+
+	// Push return value
+	voidp = lua_newuserdata(l, sizeof(LuaUserData));
+	ud = static_cast<LuaUserData*>(voidp);
+	luaL_setmetatable(l, "SceneNode");
+	extern LuaUserDataTypeInfo luaUserDataTypeInfoSceneNode;
+	ud->initPointed(&luaUserDataTypeInfoSceneNode, const_cast<SceneNode*>(&ret));
+
+	return 1;
+}
+
+/// Wrap method GlobalIlluminationProbeNode::getSceneNodeBase.
+static int wrapGlobalIlluminationProbeNodegetSceneNodeBase(lua_State* l)
+{
+	int res = pwrapGlobalIlluminationProbeNodegetSceneNodeBase(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+/// Wrap class GlobalIlluminationProbeNode.
+static inline void wrapGlobalIlluminationProbeNode(lua_State* l)
+{
+	LuaBinder::createClass(l, &luaUserDataTypeInfoGlobalIlluminationProbeNode);
+	LuaBinder::pushLuaCFuncMethod(l, "getSceneNodeBase", wrapGlobalIlluminationProbeNodegetSceneNodeBase);
+	lua_settop(l, 0);
+}
+
 LuaUserDataTypeInfo luaUserDataTypeInfoSceneGraph = {
 	-7754439619132389154, "SceneGraph", LuaUserData::computeSizeForGarbageCollected<SceneGraph>(), nullptr, nullptr};
 
@@ -4124,6 +4630,68 @@ static int wrapSceneGraphnewTriggerNode(lua_State* l)
 	return 0;
 }
 
+/// Pre-wrap method SceneGraph::newGlobalIlluminationProbeNode.
+static inline int pwrapSceneGraphnewGlobalIlluminationProbeNode(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 2)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoSceneGraph, ud))
+	{
+		return -1;
+	}
+
+	SceneGraph* self = ud->getData<SceneGraph>();
+
+	// Pop arguments
+	const char* arg0;
+	if(ANKI_UNLIKELY(LuaBinder::checkString(l, 2, arg0)))
+	{
+		return -1;
+	}
+
+	// Call the method
+	GlobalIlluminationProbeNode* ret = newSceneNode<GlobalIlluminationProbeNode>(self, arg0);
+
+	// Push return value
+	if(ANKI_UNLIKELY(ret == nullptr))
+	{
+		lua_pushstring(l, "Glue code returned nullptr");
+		return -1;
+	}
+
+	voidp = lua_newuserdata(l, sizeof(LuaUserData));
+	ud = static_cast<LuaUserData*>(voidp);
+	luaL_setmetatable(l, "GlobalIlluminationProbeNode");
+	extern LuaUserDataTypeInfo luaUserDataTypeInfoGlobalIlluminationProbeNode;
+	ud->initPointed(&luaUserDataTypeInfoGlobalIlluminationProbeNode, const_cast<GlobalIlluminationProbeNode*>(ret));
+
+	return 1;
+}
+
+/// Wrap method SceneGraph::newGlobalIlluminationProbeNode.
+static int wrapSceneGraphnewGlobalIlluminationProbeNode(lua_State* l)
+{
+	int res = pwrapSceneGraphnewGlobalIlluminationProbeNode(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
 /// Pre-wrap method SceneGraph::setActiveCameraNode.
 static inline int pwrapSceneGraphsetActiveCameraNode(lua_State* l)
 {
@@ -4191,6 +4759,7 @@ static inline void wrapSceneGraph(lua_State* l)
 	LuaBinder::pushLuaCFuncMethod(l, "newOccluderNode", wrapSceneGraphnewOccluderNode);
 	LuaBinder::pushLuaCFuncMethod(l, "newDecalNode", wrapSceneGraphnewDecalNode);
 	LuaBinder::pushLuaCFuncMethod(l, "newTriggerNode", wrapSceneGraphnewTriggerNode);
+	LuaBinder::pushLuaCFuncMethod(l, "newGlobalIlluminationProbeNode", wrapSceneGraphnewGlobalIlluminationProbeNode);
 	LuaBinder::pushLuaCFuncMethod(l, "setActiveCameraNode", wrapSceneGraphsetActiveCameraNode);
 	lua_settop(l, 0);
 }
@@ -4590,6 +5159,7 @@ void wrapModuleScene(lua_State* l)
 	wrapTriggerComponent(l);
 	wrapFogDensityComponent(l);
 	wrapFrustumComponent(l);
+	wrapGlobalIlluminationProbeComponent(l);
 	wrapSceneNode(l);
 	wrapModelNode(l);
 	wrapPerspectiveCameraNode(l);
@@ -4603,6 +5173,7 @@ void wrapModuleScene(lua_State* l)
 	wrapDecalNode(l);
 	wrapTriggerNode(l);
 	wrapFogDensityNode(l);
+	wrapGlobalIlluminationProbeNode(l);
 	wrapSceneGraph(l);
 	wrapEvent(l);
 	wrapLightEvent(l);

+ 57 - 0
src/anki/script/Scene.xml

@@ -114,6 +114,7 @@ using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 				</method>
 			</methods>
 		</class>
+
 		<class name="LightComponent">
 			<methods>
 				<method name="setDiffuseColor">
@@ -166,6 +167,7 @@ using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 				</method>
 			</methods>
 		</class>
+
 		<class name="DecalComponent">
 			<methods>
 				<method name="setDiffuseDecal">
@@ -193,6 +195,7 @@ using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 				</method>
 			</methods>
 		</class>
+
 		<class name="LensFlareComponent">
 			<methods>
 				<method name="setFirstFlareSize">
@@ -207,6 +210,7 @@ using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 				</method>
 			</methods>
 		</class>
+
 		<class name="TriggerComponent">
 			<methods>
 				<method name="getContactSceneNodes">
@@ -214,6 +218,7 @@ using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 				</method>
 			</methods>
 		</class>
+
 		<class name="FogDensityComponent">
 			<methods>
 				<method name="setAabb">
@@ -236,6 +241,8 @@ using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 					<return>F32</return>
 				</method>
 			</methods>
+		</class>
+
 		<class name="FrustumComponent">
 			<methods>
 				<method name="setPerspective">
@@ -248,6 +255,38 @@ using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 				</method>
 			</methods>
 		</class>
+
+		<class name="GlobalIlluminationProbeComponent">
+			<methods>
+				<method name="setBoundingBox">
+					<args>
+						<arg>const Vec4&amp;</arg>
+						<arg>const Vec4&amp;</arg>
+					</args>
+				</method>
+				<method name="getAlignedBoundingBoxMin">
+					<return>Vec4</return>
+				</method>
+				<method name="getAlignedBoundingBoxMax">
+					<return>Vec4</return>
+				</method>
+				<method name="setCellSize">
+					<args>
+						<arg>F32</arg>
+					</args>
+				</method>
+				<method name="getCellSize">
+					<return>F32</return>
+				</method>
+				<method name="setFadeDistance">
+					<args>
+						<arg>F32</arg>
+					</args>
+				</method>
+				<method name="getFadeDistance">
+					<return>F32</return>
+				</method>
+			</methods>
 		</class>
 
 		<!-- Nodes -->
@@ -283,6 +322,9 @@ using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 				<method name="tryGetComponent&lt;FrustumComponent&gt;" alias="getFrustumComponent">
 					<return>FrustumComponent*</return>
 				</method>
+				<method name="tryGetComponent&lt;GlobalIlluminationProbeComponent&gt;" alias="getGlobalIlluminationProbeComponent">
+					<return>GlobalIlluminationProbeComponent*</return>
+				</method>
 			</methods>
 		</class>
 		<class name="ModelNode">
@@ -387,6 +429,14 @@ using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 				</method>
 			</methods>
 		</class>
+		<class name="GlobalIlluminationProbeNode">
+			<methods>
+				<method name="getSceneNodeBase">
+					<overrideCall>SceneNode&amp; ret = *self;</overrideCall>
+					<return>SceneNode&amp;</return>
+				</method>
+			</methods>
+		</class>
 		<class name="SceneGraph">
 			<methods>
 				<method name="newPerspectiveCameraNode">
@@ -474,6 +524,13 @@ using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 					</args>
 					<return>TriggerNode*</return>
 				</method>
+				<method name="newGlobalIlluminationProbeNode">
+					<overrideCall><![CDATA[GlobalIlluminationProbeNode* ret = newSceneNode<GlobalIlluminationProbeNode>(self, arg0);]]></overrideCall>
+					<args>
+						<arg>const CString&amp;</arg>
+					</args>
+					<return>GlobalIlluminationProbeNode*</return>
+				</method>
 				<method name="setActiveCameraNode">
 					<args>
 						<arg>SceneNode*</arg>

+ 1 - 0
src/anki/util/Assert.h

@@ -26,6 +26,7 @@ void akassert(const char* exprTxt, const char* file, int line, const char* func)
 			if(!(x)) \
 			{ \
 				anki::akassert(#x, ANKI_FILE, __LINE__, ANKI_FUNC); \
+				ANKI_UNREACHABLE(); \
 			} \
 		} while(0)
 #	define ANKI_ASSERTS_ENABLED 1

+ 119 - 106
src/anki/util/Functions.h

@@ -23,6 +23,83 @@ namespace anki
 /// @addtogroup util_other
 /// @{
 
+#define _ANKI_CONCATENATE(a, b) a##b
+
+/// Concatenate 2 preprocessor tokens.
+#define ANKI_CONCATENATE(a, b) _ANKI_CONCATENATE(a, b)
+
+#define _ANKI_STRINGIZE(a) #a
+
+/// Make a preprocessor token a string.
+#define ANKI_STRINGIZE(a) _ANKI_STRINGIZE(a)
+
+// ANKI_ENABLE_METHOD & ANKI_ENABLE_ARG trickery copied from Tick library
+template<bool B>
+struct RequiresBool
+{
+	static constexpr bool VALUE = B;
+};
+
+template<typename T, int N>
+struct RequiresUnwrap : T
+{
+};
+
+template<int N>
+struct PrivateEnum
+{
+	enum class Type
+	{
+		NA
+	};
+};
+
+template<typename T, int N>
+struct DummyType
+{
+};
+
+#if defined(_MSC_VER)
+#	define ANKI_REQUIRES_BOOL(line, ...) RequiresUnwrap<decltype(RequiresBool<(__VA_ARGS__)>{}), line>::VALUE
+
+#	define ANKI_ENABLE_INTERNAL(line, ...) \
+		typename PrivateEnum<line>::Type ANKI_CONCATENATE( \
+			privateEnum, line) = PrivateEnum<line>::Type::NA, \
+						 bool ANKI_CONCATENATE(privateBool, line) = true, \
+						 typename = typename std::enable_if_t<( \
+							 ANKI_CONCATENATE(privateBool, line) && ANKI_REQUIRES_BOOL(line, __VA_ARGS__))>
+#else
+
+#	define ANKI_ENABLE_INTERNAL(line, ...) \
+		bool privateBool##line = true, typename std::enable_if_t<(privateBool##line && __VA_ARGS__), int> = 0
+
+#endif
+
+/// Use it to enable a method based on a constant expression.
+/// @code
+/// template<int N> class Foo {
+/// 	ANKI_ENABLE_METHOD(N == 10)
+/// 	void foo() {}
+///	};
+/// @endcode
+#define ANKI_ENABLE_METHOD(...) template<ANKI_ENABLE_INTERNAL(__LINE__, __VA_ARGS__)>
+
+/// Use it to enable a method based on a constant expression.
+/// @code
+/// class Foo {
+/// 	void foo(ANKI_ENABLE_ARG(Boo, expr) b) {}
+///	};
+/// @endcode
+#define ANKI_ENABLE_ARG(type_, expression) \
+	typename std::conditional<(expression), type_, DummyType<type_, __LINE__>>::type
+
+/// Use it to enable a method based on a constant expression.
+/// @code
+/// template<typename T, ANKI_ENABLE(std::is_whatever<T>::value)>
+/// void foo(T x) {}
+/// @endcode
+#define ANKI_ENABLE(...) ANKI_ENABLE_INTERNAL(__LINE__, __VA_ARGS__)
+
 /// Pick a random number from min to max
 extern I32 randRange(I32 min, I32 max);
 
@@ -58,15 +135,15 @@ inline T clamp(T v, T minv, T maxv)
 	return min<T>(max<T>(minv, v), maxv);
 }
 
-/// Check if a number os a power of 2
-template<typename Int>
+/// Check if a number is a power of 2
+template<typename Int, ANKI_ENABLE(std::is_integral<Int>::value)>
 inline Bool isPowerOfTwo(Int x)
 {
 	return !(x == 0) && !(x & (x - 1));
 }
 
 /// Get the next power of two number. For example if x is 130 this will return 256.
-template<typename Int>
+template<typename Int, ANKI_ENABLE(std::is_integral<Int>::value)>
 inline Int nextPowerOfTwo(Int x)
 {
 	return pow(2, ceil(log(x) / log(2)));
@@ -75,20 +152,30 @@ inline Int nextPowerOfTwo(Int x)
 /// Get the aligned number rounded up.
 /// @param alignment The bytes of alignment
 /// @param value The value to align
-template<typename Type>
-inline Type getAlignedRoundUp(PtrSize alignment, Type value)
+template<typename TInt, ANKI_ENABLE(std::is_integral<TInt>::value)>
+inline TInt getAlignedRoundUp(PtrSize alignment, TInt value)
 {
 	ANKI_ASSERT(alignment > 0);
 	PtrSize v = PtrSize(value);
 	v = ((v + alignment - 1) / alignment) * alignment;
-	return Type(v);
+	return TInt(v);
+}
+
+/// Get the aligned number rounded up.
+/// @param alignment The bytes of alignment
+/// @param value The value to align
+template<typename TFloat, ANKI_ENABLE(std::is_floating_point<TFloat>::value)>
+inline TFloat getAlignedRoundUp(TFloat alignment, TFloat value)
+{
+	ANKI_ASSERT(alignment > TFloat(0.0));
+	return ceil(value / alignment) * alignment;
 }
 
 /// Align number
 /// @param alignment The bytes of alignment
 /// @param value The value to align
-template<typename Type>
-inline void alignRoundUp(PtrSize alignment, Type& value)
+template<typename TAlignment, typename TValue>
+inline void alignRoundUp(TAlignment alignment, TValue& value)
 {
 	value = getAlignedRoundUp(alignment, value);
 }
@@ -96,20 +183,30 @@ inline void alignRoundUp(PtrSize alignment, Type& value)
 /// Get the aligned number rounded down.
 /// @param alignment The bytes of alignment
 /// @param value The value to align
-template<typename Type>
-inline Type getAlignedRoundDown(PtrSize alignment, Type value)
+template<typename TInt, ANKI_ENABLE(std::is_integral<TInt>::value)>
+inline TInt getAlignedRoundDown(PtrSize alignment, TInt value)
 {
 	ANKI_ASSERT(alignment > 0);
 	PtrSize v = PtrSize(value);
 	v = (v / alignment) * alignment;
-	return Type(v);
+	return TInt(v);
+}
+
+/// Get the aligned number rounded down.
+/// @param alignment The bytes of alignment
+/// @param value The value to align
+template<typename TFloat, ANKI_ENABLE(std::is_floating_point<TFloat>::value)>
+inline TFloat getAlignedRoundDown(TFloat alignment, TFloat value)
+{
+	ANKI_ASSERT(alignment > TFloat(0.0));
+	return floor(value / alignment) * alignment;
 }
 
 /// Align number
 /// @param alignment The bytes of alignment
 /// @param value The value to align
-template<typename Type>
-inline void alignRoundDown(PtrSize alignment, Type& value)
+template<typename TAlignment, typename TValue>
+inline void alignRoundDown(TAlignment alignment, TValue& value)
 {
 	value = getAlignedRoundDown(alignment, value);
 }
@@ -164,25 +261,6 @@ struct RemovePointer<T*>
 	typedef T Type;
 };
 
-/// Check if types are the same.
-template<class T, class Y>
-struct TypesAreTheSame
-{
-	enum
-	{
-		m_value = 0
-	};
-};
-
-template<class T>
-struct TypesAreTheSame<T, T>
-{
-	enum
-	{
-		m_value = 1
-	};
-};
-
 template<typename T>
 void memorySet(T* dest, T value, PtrSize count)
 {
@@ -219,7 +297,9 @@ constexpr Bool isPacked()
 /// Unflatten 3D array index.
 /// Imagine an array [sizeA][sizeB][sizeC] and a flat index in that array. Then this function will compute the unflatten
 /// indices.
-inline void unflatten3dArrayIndex(const U sizeA, const U sizeB, const U sizeC, const U flatIdx, U& a, U& b, U& c)
+template<typename T, typename TI, typename TOut>
+inline void unflatten3dArrayIndex(
+	const T sizeA, const T sizeB, const T sizeC, const TI flatIdx, TOut& a, TOut& b, TOut& c)
 {
 	ANKI_ASSERT(flatIdx < (sizeA * sizeB * sizeC));
 	a = (flatIdx / (sizeB * sizeC)) % sizeA;
@@ -227,86 +307,19 @@ inline void unflatten3dArrayIndex(const U sizeA, const U sizeB, const U sizeC, c
 	c = flatIdx % sizeC;
 }
 
-/// Given a threaded problem split it into smaller ones.
+/// Given a threaded problem split it into smaller ones. This function accepts the number of threads and the size of
+/// the threaded problem. Then given a thread index it chooses a range that the thread can operate into. That range is
+/// supposed to be as evenly split as possible across threads.
+template<typename TThreadId, typename TThreadCount, typename TProblemSize, typename TResult>
 inline void splitThreadedProblem(
-	PtrSize threadId, PtrSize threadCount, PtrSize problemSize, PtrSize& start, PtrSize& end)
+	TThreadId threadId, TThreadCount threadCount, TProblemSize problemSize, TResult& start, TResult& end)
 {
 	ANKI_ASSERT(threadCount > 0 && threadId < threadCount);
-	const PtrSize div = problemSize / threadCount;
+	const auto div = problemSize / threadCount;
 	start = threadId * div;
 	end = (threadId == threadCount - 1) ? problemSize : (threadId + 1u) * div;
 	ANKI_ASSERT(!(threadId == threadCount - 1 && end != problemSize));
 }
-
-#define _ANKI_CONCATENATE(a, b) a##b
-
-/// Concatenate 2 preprocessor tokens.
-#define ANKI_CONCATENATE(a, b) _ANKI_CONCATENATE(a, b)
-
-#define _ANKI_STRINGIZE(a) #a
-
-/// Make a preprocessor token a string.
-#define ANKI_STRINGIZE(a) _ANKI_STRINGIZE(a)
-
-// ANKI_ENABLE_METHOD & ANKI_ENABLE_ARG trickery copied from Tick library
-template<bool B>
-struct RequiresBool
-{
-	static constexpr bool VALUE = B;
-};
-
-template<typename T, int N>
-struct RequiresUnwrap : T
-{
-};
-
-template<int N>
-struct PrivateEnum
-{
-	enum class Type
-	{
-		NA
-	};
-};
-
-template<typename T, int N>
-struct DummyType
-{
-};
-
-#if defined(_MSC_VER)
-#	define ANKI_REQUIRES_BOOL(line, ...) RequiresUnwrap<decltype(RequiresBool<(__VA_ARGS__)>{}), line>::VALUE
-
-#	define ANKI_ENABLE_METHOD_INTERNAL(line, ...) \
-		typename PrivateEnum<line>::Type ANKI_CONCATENATE( \
-			TickPrivateEnum, line) = PrivateEnum<line>::Type::NA, \
-							 bool ANKI_CONCATENATE(privateBool, line) = true, \
-							 typename = typename std::enable_if_t<( \
-								 ANKI_CONCATENATE(privateBool, line) && ANKI_REQUIRES_BOOL(line, __VA_ARGS__))>
-#else
-
-#	define ANKI_ENABLE_METHOD_INTERNAL(line, ...) \
-		bool privateBool##line = true, typename std::enable_if_t<(privateBool##line && __VA_ARGS__), int> = 0
-
-#endif
-
-/// Use it to enable a method based on a constant expression.
-/// @code
-/// template<int N> class Foo {
-/// 	ANKI_ENABLE_METHOD(N == 10)
-/// 	void foo() {}
-///	};
-/// @endcode
-#define ANKI_ENABLE_METHOD(...) template<ANKI_ENABLE_METHOD_INTERNAL(__LINE__, __VA_ARGS__)>
-
-/// Use it to enable a method based on a constant expression.
-/// @code
-/// class Foo {
-/// 	void foo(ANKI_ENABLE_ARG(Boo, expr) b) {}
-///	};
-/// @endcode
-#define ANKI_ENABLE_ARG(type_, expression) \
-	typename std::conditional<(expression), type_, DummyType<type_, __LINE__>>::type
 /// @}
 
 } // end namespace anki

+ 3 - 1
src/anki/util/Memory.cpp

@@ -726,7 +726,9 @@ void* ChainMemoryPool::allocateFromChunk(Chunk* ch, PtrSize size, PtrSize alignm
 	ANKI_ASSERT(ch->m_top <= ch->m_memory + ch->m_memsize);
 
 	U8* mem = ch->m_top;
-	alignRoundUp(m_alignmentBytes, mem);
+	PtrSize memV = ptrToNumber(mem);
+	alignRoundUp(m_alignmentBytes, memV);
+	mem = numberToPtr<U8*>(memV);
 	U8* newTop = mem + m_headerSize + size;
 
 	if(newTop <= ch->m_memory + ch->m_memsize)

+ 1 - 1
thirdparty

@@ -1 +1 @@
-Subproject commit 903a4e96a27bb0ba522d8e81634701f898d62736
+Subproject commit 7903de6abf731388fb5dd6090aae9abbde4bbe93

+ 33 - 21
tools/blender_anki_additions.patch

@@ -1,8 +1,21 @@
+diff --git a/build_files/build_environment/install_deps.sh b/build_files/build_environment/install_deps.sh
+index 6cd725494b3..6ce61b8c7d6 100755
+--- a/build_files/build_environment/install_deps.sh
++++ b/build_files/build_environment/install_deps.sh
+@@ -340,7 +340,7 @@ LLVM_FORCE_REBUILD=false
+ LLVM_SKIP=false
+ 
+ # OSL needs to be compiled for now!
+-OSL_VERSION="1.7.5"
++OSL_VERSION="1.8.10"
+ OSL_VERSION_MIN=$OSL_VERSION
+ OSL_FORCE_BUILD=false
+ OSL_FORCE_REBUILD=false
 diff --git a/source/blender/collada/EffectExporter.cpp b/source/blender/collada/EffectExporter.cpp
-index 76b5114..11ea02e 100644
+index d33ce725e58..bf36943b324 100644
 --- a/source/blender/collada/EffectExporter.cpp
 +++ b/source/blender/collada/EffectExporter.cpp
-@@ -47,7 +47,9 @@ extern "C" {
+@@ -46,7 +46,9 @@ extern "C" {
  	#include "BKE_customdata.h"
  	#include "BKE_mesh.h"
  	#include "BKE_material.h"
@@ -10,9 +23,9 @@ index 76b5114..11ea02e 100644
  }
 +#include <sstream>
  
- // OB_MESH is assumed
- static std::string getActiveUVLayerName(Object *ob)
-@@ -170,6 +172,26 @@ void EffectsExporter::writeTextures(COLLADASW::EffectProfile &ep,
+ EffectsExporter::EffectsExporter(COLLADASW::StreamWriter *sw, const ExportSettings *export_settings) : COLLADASW::LibraryEffects(sw), export_settings(export_settings) {
+ }
+@@ -196,6 +198,26 @@ void EffectsExporter::writeTextures(
  		texture.setChildElementName("bump");
  		ep.addExtraTechniqueColorOrTexture(COLLADASW::ColorOrTexture(texture));
  	}
@@ -22,7 +35,7 @@ index 76b5114..11ea02e 100644
 +		texture.setTexcoord(uvname);
 +		texture.setSampler(*sampler);
 +		texture.setProfileName("blender");
-+		texture.setChildElementName("roughness");
++		texture.setChildElementName("roughness_tex");
 +		ep.addExtraTechniqueColorOrTexture(COLLADASW::ColorOrTexture(texture));
 +	}
 +	if (t->mapto & MAP_DISPLACE) {
@@ -39,8 +52,8 @@ index 76b5114..11ea02e 100644
  }
  
  void EffectsExporter::operator()(Material *ma, Object *ob)
-@@ -398,6 +420,68 @@ void EffectsExporter::operator()(Material *ma, Object *ob)
- 		}
+@@ -358,6 +380,68 @@ void EffectsExporter::operator()(Material *ma, Object *ob)
+ 		writeTextures(ep, key, sampler, t, ima, uvname);
  	}
  
 +	// AnKi: Export extra properties
@@ -109,7 +122,7 @@ index 76b5114..11ea02e 100644
  	ep.addProfileElements();
  	bool twoSided = false;
 diff --git a/source/blender/collada/GeometryExporter.cpp b/source/blender/collada/GeometryExporter.cpp
-index 7c7c57f..b02ba47 100644
+index 4b693332715..04fb3ee71f3 100644
 --- a/source/blender/collada/GeometryExporter.cpp
 +++ b/source/blender/collada/GeometryExporter.cpp
 @@ -47,6 +47,7 @@ extern "C" {
@@ -120,12 +133,10 @@ index 7c7c57f..b02ba47 100644
  }
  
  #include "collada_internal.h"
-@@ -143,13 +144,133 @@ void GeometryExporter::operator()(Object *ob)
- 			createPolylist(0, has_uvs, has_color, ob, me, geom_id, norind);
+@@ -154,12 +155,135 @@ void GeometryExporter::operator()(Object *ob)
  		}
  	}
--	
-+
+ 
 +	// AnKi: Export mesh properties
 +	{
 +		static const char *property_names[] = {
@@ -136,6 +147,9 @@ index 7c7c57f..b02ba47 100644
 +			"lod1",
 +			"skip",
 +			"reflection_probe",
++			"gi_probe",
++			"gi_probe_fade_distance",
++			"gi_probe_cell_size",
 +			"reflection_proxy",
 +			"occluder",
 +			"collision_mesh",
@@ -204,7 +218,7 @@ index 7c7c57f..b02ba47 100644
 +	}
 +
  	closeMesh();
- 	
+ 
  	if (me->flag & ME_TWOSIDED) {
  		mSW->appendTextBlock("<extra><technique profile=\"MAYA\"><double_sided>1</double_sided></technique></extra>");
  	}
@@ -256,7 +270,7 @@ index 7c7c57f..b02ba47 100644
  
  	if (this->export_settings->include_shapekeys) {
 diff --git a/source/blender/collada/LightExporter.cpp b/source/blender/collada/LightExporter.cpp
-index ff50abf..205f687 100644
+index 02c5438ec47..6db17d8d082 100644
 --- a/source/blender/collada/LightExporter.cpp
 +++ b/source/blender/collada/LightExporter.cpp
 @@ -31,6 +31,9 @@
@@ -277,12 +291,10 @@ index ff50abf..205f687 100644
  		addLight(cla);
  	}
  	// lamp
-@@ -190,6 +194,49 @@ bool LightsExporter::exportBlenderProfile(COLLADASW::Light &cla, Lamp *la)
- 	cla.addExtraTechniqueParameter("blender", "skyblendfac", la->skyblendfac);
+@@ -191,5 +195,48 @@ bool LightsExporter::exportBlenderProfile(COLLADASW::Light &cla, Lamp *la)
  	cla.addExtraTechniqueParameter("blender", "sky_exposure", la->sky_exposure);
  	cla.addExtraTechniqueParameter("blender", "sky_colorspace", la->sky_colorspace);
--	
-+
+ 
 +	// AnKi: Export properties
 +	static const char *property_names[] = {
 +		"lens_flare",
@@ -329,7 +341,7 @@ index ff50abf..205f687 100644
  	return true;
  }
 diff --git a/source/blender/collada/SceneExporter.cpp b/source/blender/collada/SceneExporter.cpp
-index 30cd6dd..17563dd 100644
+index 4e08548449f..4eb2580dd6b 100644
 --- a/source/blender/collada/SceneExporter.cpp
 +++ b/source/blender/collada/SceneExporter.cpp
 @@ -28,6 +28,7 @@ extern "C" {
@@ -340,7 +352,7 @@ index 30cd6dd..17563dd 100644
  }
  
  #include "SceneExporter.h"
-@@ -234,6 +235,14 @@ void SceneExporter::writeNodes(Object *ob, Scene *sce)
+@@ -232,6 +233,14 @@ void SceneExporter::writeNodes(bContext *C, Object *ob, Scene *sce)
  		}
  	}
  

+ 1 - 1
tools/blender_anki_additions_readme.txt

@@ -4,4 +4,4 @@ Follow the blender build instuctions. Use the following to build the external:
 
 The command above will give CMAKE options. Add this as well -DOPENCOLLADA_ROOT_DIR
 
-Commit the patch is based upon: c05363e8895a8cd6daa2241706d357c47ed4a83e
+Commit the patch is based upon: 78a8d3685bd3487eb0b5dd55793f94fe7235e377

+ 62 - 1
tools/scene/Exporter.cpp

@@ -793,6 +793,8 @@ void Exporter::visitNode(const aiNode* ainode)
 		std::string lod1MeshName;
 		std::string collisionMesh;
 		bool special = false;
+		GiProbe giProbe;
+		bool isGiProbe = false;
 		for(const auto& prop : m_scene->mMeshes[meshIndex]->mProperties)
 		{
 			if(prop.first == "particles")
@@ -833,6 +835,30 @@ void Exporter::visitNode(const aiNode* ainode)
 
 				special = true;
 			}
+			else if(prop.first == "gi_probe" && prop.second == "true")
+			{
+				GiProbe& probe = giProbe;
+				aiMatrix4x4 trf = toAnkiMatrix(ainode->mTransformation);
+				probe.m_position = aiVector3D(trf.a4, trf.b4, trf.c4);
+
+				aiVector3D scale(trf.a1, trf.b2, trf.c3);
+				assert(scale.x > 0.0f && scale.y > 0.0f && scale.z > 0.0f);
+
+				aiVector3D half = scale;
+				probe.m_aabbMin = probe.m_position - half - probe.m_position;
+				probe.m_aabbMax = probe.m_position + half - probe.m_position;
+
+				isGiProbe = true;
+				special = true;
+			}
+			else if(prop.first == "gi_probe_fade_distance")
+			{
+				giProbe.m_fadeDistance = std::strtof(prop.second.c_str(), nullptr);
+			}
+			else if(prop.first == "gi_probe_cell_size")
+			{
+				giProbe.m_cellSize = std::strtof(prop.second.c_str(), nullptr);
+			}
 			else if(prop.first == "reflection_proxy" && prop.second == "true")
 			{
 				ReflectionProxy proxy;
@@ -921,6 +947,11 @@ void Exporter::visitNode(const aiNode* ainode)
 			}
 		}
 
+		if(isGiProbe)
+		{
+			m_giProbes.push_back(giProbe);
+		}
+
 		if(special)
 		{
 			continue;
@@ -1015,7 +1046,7 @@ void Exporter::exportAll()
 	}
 
 	//
-	// Export probes
+	// Export refl probes
 	//
 	i = 0;
 	for(const ReflectionProbe& probe : m_reflectionProbes)
@@ -1032,6 +1063,36 @@ void Exporter::exportAll()
 		++i;
 	}
 
+	//
+	// Export GI probes
+	//
+	i = 0;
+	for(const GiProbe& probe : m_giProbes)
+	{
+		std::string name = "giprobe" + std::to_string(i);
+		file << "\nnode = scene:newGlobalIlluminationProbeNode(\"" << name << "\")\n";
+
+		file << "comp = node:getSceneNodeBase():getGlobalIlluminationProbeComponent()\n";
+		file << "comp:setBoundingBox(Vec4.new(" << probe.m_aabbMin.x << ", " << probe.m_aabbMin.y << ", "
+			 << probe.m_aabbMin.z << ", 0), Vec4.new(" << probe.m_aabbMax.x << ", " << probe.m_aabbMax.y << ", "
+			 << probe.m_aabbMax.z << ", 0))\n";
+
+		if(probe.m_fadeDistance >= 0.0f)
+		{
+			file << "comp:setFadeDistance(" << probe.m_fadeDistance << ")\n";
+		}
+
+		if(probe.m_cellSize > 0.0f)
+		{
+			file << "comp:setCellSize(" << probe.m_cellSize << ")\n";
+		}
+
+		aiMatrix4x4 trf;
+		aiMatrix4x4::Translation(probe.m_position, trf);
+		writeNodeTransform("node", trf);
+		++i;
+	}
+
 	//
 	// Export proxies
 	//

+ 11 - 0
tools/scene/Exporter.h

@@ -72,6 +72,16 @@ public:
 	aiVector3D m_aabbMax;
 };
 
+class GiProbe
+{
+public:
+	aiVector3D m_position;
+	aiVector3D m_aabbMin;
+	aiVector3D m_aabbMax;
+	float m_fadeDistance = -1.0f;
+	float m_cellSize = -1.0f;
+};
+
 class ReflectionProxy
 {
 public:
@@ -122,6 +132,7 @@ public:
 	std::vector<StaticCollisionNode> m_staticCollisionNodes;
 	std::vector<ParticleEmitter> m_particleEmitters;
 	std::vector<ReflectionProbe> m_reflectionProbes;
+	std::vector<GiProbe> m_giProbes;
 	std::vector<ReflectionProxy> m_reflectionProxies;
 	std::vector<OccluderNode> m_occluders;
 	std::vector<DecalNode> m_decals;