Explorar el Código

Merge pull request #112 from godlikepanos/meshv6_2

Mesh v6
Panagiotis Christopoulos Charitos hace 3 años
padre
commit
8617d101be
Se han modificado 100 ficheros con 2946 adiciones y 2917 borrados
  1. 34 0
      AnKi.natvis
  2. 2 1
      AnKi/Core/App.cpp
  3. 2 2
      AnKi/Core/GpuMemoryPools.cpp
  4. 1 1
      AnKi/Core/GpuMemoryPools.h
  5. 3 3
      AnKi/Gr/Common.h
  6. 1 0
      AnKi/Gr/Vulkan/CommandBufferImpl.h
  7. 1 1
      AnKi/Gr/Vulkan/Common.cpp
  8. 4 7
      AnKi/Gr/Vulkan/Pipeline.cpp
  9. 5 8
      AnKi/Gr/Vulkan/Pipeline.h
  10. 147 130
      AnKi/Importer/GltfImporter.cpp
  11. 58 9
      AnKi/Importer/GltfImporter.h
  12. 17 10
      AnKi/Importer/GltfImporterAnimation.cpp
  13. 93 5
      AnKi/Importer/GltfImporterMaterial.cpp
  14. 170 201
      AnKi/Importer/GltfImporterMesh.cpp
  15. 13 8
      AnKi/Importer/ImageImporter.cpp
  16. 19 0
      AnKi/Math/Functions.h
  17. 92 90
      AnKi/Math/Mat.h
  18. 366 366
      AnKi/Math/Vec.h
  19. 5 3
      AnKi/Renderer/ClusterBinning.cpp
  20. 3 5
      AnKi/Renderer/ConfigVars.defs.h
  21. 1 0
      AnKi/Renderer/ForwardShading.cpp
  22. 1 1
      AnKi/Renderer/IndirectDiffuseProbes.cpp
  23. 1 1
      AnKi/Renderer/ProbeReflections.cpp
  24. 1 3
      AnKi/Renderer/RenderQueue.h
  25. 6 0
      AnKi/Renderer/Renderer.cpp
  26. 1 0
      AnKi/Renderer/Renderer.h
  27. 181 435
      AnKi/Renderer/ShadowMapping.cpp
  28. 34 76
      AnKi/Renderer/ShadowMapping.h
  29. 13 9
      AnKi/Renderer/ShadowmapsResolve.cpp
  30. 3 2
      AnKi/Renderer/ShadowmapsResolve.h
  31. 56 49
      AnKi/Renderer/TileAllocator.cpp
  32. 12 10
      AnKi/Renderer/TileAllocator.h
  33. 111 22
      AnKi/Renderer/TraditionalDeferredShading.cpp
  34. 10 3
      AnKi/Renderer/TraditionalDeferredShading.h
  35. 12 11
      AnKi/Renderer/VolumetricLightingAccumulation.cpp
  36. 1 1
      AnKi/Resource/CpuMeshResource.cpp
  37. 32 24
      AnKi/Resource/MeshBinary.h
  38. 15 15
      AnKi/Resource/MeshBinary.xml
  39. 149 80
      AnKi/Resource/MeshBinaryLoader.cpp
  40. 19 28
      AnKi/Resource/MeshBinaryLoader.h
  41. 187 189
      AnKi/Resource/MeshResource.cpp
  42. 50 86
      AnKi/Resource/MeshResource.h
  43. 48 206
      AnKi/Resource/ModelResource.cpp
  44. 25 97
      AnKi/Resource/ModelResource.h
  45. 6 0
      AnKi/Scene/CameraNode.cpp
  46. 11 0
      AnKi/Scene/Common.h
  47. 52 0
      AnKi/Scene/Components/FrustumComponent.cpp
  48. 106 95
      AnKi/Scene/Components/FrustumComponent.h
  49. 6 4
      AnKi/Scene/Components/LightComponent.cpp
  50. 2 1
      AnKi/Scene/Components/RenderComponent.cpp
  51. 2 1
      AnKi/Scene/Components/RenderComponent.h
  52. 6 1
      AnKi/Scene/ConfigVars.defs.h
  53. 10 4
      AnKi/Scene/GlobalIlluminationProbeNode.cpp
  54. 60 33
      AnKi/Scene/ModelNode.cpp
  55. 8 1
      AnKi/Scene/ReflectionProbeNode.cpp
  56. 3 1
      AnKi/Scene/SceneGraph.cpp
  57. 3 1
      AnKi/Scene/SceneGraph.h
  58. 5 0
      AnKi/Scene/SceneNode.cpp
  59. 3 0
      AnKi/Scene/SceneNode.h
  60. 5 2
      AnKi/Scene/Visibility.cpp
  61. 66 58
      AnKi/Script/Scene.cpp
  62. 5 3
      AnKi/Script/Scene.xml
  63. 51 51
      AnKi/ShaderCompiler/ShaderProgramParser.cpp
  64. 0 12
      AnKi/Shaders/Common.glsl
  65. 0 119
      AnKi/Shaders/Evsm.glsl
  66. 0 8
      AnKi/Shaders/EvsmCompute.ankiprog
  67. 5 3
      AnKi/Shaders/ForwardShadingCommon.glsl
  68. 2 1
      AnKi/Shaders/ForwardShadingFog.ankiprog
  69. 1 1
      AnKi/Shaders/ForwardShadingGenericTransparent.ankiprog
  70. 2 2
      AnKi/Shaders/ForwardShadingParticles.ankiprog
  71. 22 2
      AnKi/Shaders/Functions.glsl
  72. 7 6
      AnKi/Shaders/GBufferCommon.glsl
  73. 13 0
      AnKi/Shaders/GBufferGeneric.ankiprog
  74. 0 30
      AnKi/Shaders/Include/ClusteredShadingFunctions.h
  75. 50 50
      AnKi/Shaders/Include/ClusteredShadingTypes.h
  76. 23 3
      AnKi/Shaders/Include/Common.h
  77. 1 0
      AnKi/Shaders/Include/GpuSceneTypes.h
  78. 1 0
      AnKi/Shaders/Include/MaterialTypes.h
  79. 62 19
      AnKi/Shaders/Include/MeshTypes.h
  80. 0 25
      AnKi/Shaders/Include/MiscRendererTypes.h
  81. 12 12
      AnKi/Shaders/Include/ModelTypes.h
  82. 165 102
      AnKi/Shaders/LightFunctions.glsl
  83. 1 1
      AnKi/Shaders/LightShading.ankiprog
  84. 1 1
      AnKi/Shaders/RtShadowsRayGen.ankiprog
  85. 12 2
      AnKi/Shaders/ShadowmappingClearDepth.ankiprog
  86. 95 26
      AnKi/Shaders/ShadowmapsResolve.glsl
  87. 0 1
      AnKi/Shaders/VolumetricFogAccumulation.ankiprog
  88. 17 16
      AnKi/Shaders/VolumetricLightingAccumulation.ankiprog
  89. 4 4
      AnKi/Util/Array.h
  90. 23 5
      AnKi/Util/Enum.h
  91. 1 1
      AnKi/Util/Logger.h
  92. 8 3
      AnKi/Util/String.h
  93. BIN
      EngineAssets/LightMeshes.blend
  94. BIN
      EngineAssets/Plight.ankimesh
  95. BIN
      EngineAssets/Slight.ankimesh
  96. 2 2
      Samples/Common/SampleApp.cpp
  97. 0 5
      Samples/PhysicsPlayground/Assets/Icosphere.ankicl
  98. BIN
      Samples/PhysicsPlayground/Assets/Icosphere_834d64c142beaa13.ankimesh
  99. BIN
      Samples/PhysicsPlayground/Assets/Icosphere_lod0_834d64c142beaa13.ankimesh
  100. 1 1
      Samples/PhysicsPlayground/Assets/Icosphere_walls_ac2438354c62251.ankimdl

+ 34 - 0
AnKi.natvis

@@ -45,4 +45,38 @@
 		</Expand>
 	</Type>
 
+	<Type Name="anki::Array&lt;*,*&gt;">
+		<DisplayString>{{ size={$T2} }}</DisplayString>
+		<Expand>
+			<ExpandedItem>m_data</ExpandedItem>
+		</Expand>
+	</Type>
+
+	<Type Name="anki::CString">
+		<DisplayString Condition="(bool)(m_ptr==0)">*Empty*</DisplayString>
+		<DisplayString>{m_ptr,s}</DisplayString>
+	</Type>
+
+	<Type Name="anki::String">
+		<DisplayString Condition="(bool)(m_data.m_data==0)">*Empty*</DisplayString>
+		<DisplayString>{m_data.m_data,s}</DisplayString>
+	</Type>
+
+	<Type Name="anki::BaseStringRaii&lt;*&gt;">
+		<DisplayString Condition="(bool)(m_data.m_data==0)">*Empty*</DisplayString>
+		<DisplayString>{m_data.m_data,s}</DisplayString>
+	</Type>
+
+	<Type Name="anki::TVec&lt;*,2&gt;">
+		<DisplayString>x={m_carr[0]} y={m_carr[1]}</DisplayString>
+	</Type>
+
+	<Type Name="anki::TVec&lt;*,3&gt;">
+		<DisplayString>x={m_carr[0]} y={m_carr[1]} z={m_carr[2]}</DisplayString>
+	</Type>
+
+	<Type Name="anki::TVec&lt;*,4&gt;">
+		<DisplayString>x={m_carr[0]} y={m_carr[1]} z={m_carr[2]} w={m_carr[3]}</DisplayString>
+	</Type>
+
 </AutoVisualizer>

+ 2 - 1
AnKi/Core/App.cpp

@@ -359,7 +359,8 @@ Error App::initInternal(AllocAlignedCallback allocCb, void* allocCbUserData)
 	m_scene = newInstance<SceneGraph>(m_mainPool);
 
 	ANKI_CHECK(m_scene->init(m_mainPool.getAllocationCallback(), m_mainPool.getAllocationCallbackUserData(),
-							 m_threadHive, m_resources, m_input, m_script, m_ui, m_config, &m_globalTimestamp));
+							 m_threadHive, m_resources, m_input, m_script, m_ui, m_config, &m_globalTimestamp,
+							 m_unifiedGometryMemPool));
 
 	// Inform the script engine about some subsystems
 	m_script->setRenderer(m_renderer);

+ 2 - 2
AnKi/Core/GpuMemoryPools.cpp

@@ -93,8 +93,8 @@ Error StagingGpuMemoryPool::init(GrManager* gr, const ConfigSet& cfg)
 	initBuffer(StagingGpuMemoryType::kStorage,
 			   max(gr->getDeviceCapabilities().m_storageBufferBindOffsetAlignment,
 				   gr->getDeviceCapabilities().m_sbtRecordAlignment),
-			   gr->getDeviceCapabilities().m_storageBufferMaxRange, BufferUsageBit::kAllStorage | BufferUsageBit::kSBT,
-			   *gr);
+			   gr->getDeviceCapabilities().m_storageBufferMaxRange,
+			   BufferUsageBit::kAllStorage | BufferUsageBit::kShaderBindingTable, *gr);
 
 	initBuffer(StagingGpuMemoryType::kVertex, 16, kMaxU32, BufferUsageBit::kVertex | BufferUsageBit::kIndex, *gr);
 

+ 1 - 1
AnKi/Core/GpuMemoryPools.h

@@ -36,7 +36,7 @@ public:
 
 	void free(PtrSize size, U32 alignment, PtrSize offset);
 
-	BufferPtr getVertexBuffer() const
+	const BufferPtr& getVertexBuffer() const
 	{
 		return m_vertBuffer;
 	}

+ 3 - 3
AnKi/Gr/Common.h

@@ -677,7 +677,7 @@ enum class BufferUsageBit : U64
 	kTransferDestination = 1ull << 26ull,
 
 	kAccelerationStructureBuild = 1ull << 27ull, ///< Will be used as a position or index buffer in a BLAS build.
-	kSBT = 1ull << 28ull, ///< Will be used as SBT in a traceRays() command.
+	kShaderBindingTable = 1ull << 28ull, ///< Will be used as SBT in a traceRays() command.
 
 	// Derived
 	kAllUniform = kUniformGeometry | kUniformFragment | kUniformCompute | kUniformTraceRays,
@@ -696,13 +696,13 @@ enum class BufferUsageBit : U64
 	kAllCompute = kUniformCompute | kStorageComputeRead | kStorageComputeWrite | kTextureComputeRead
 				  | kTextureComputeWrite | kIndirectCompute,
 	kAllTraceRays = kUniformTraceRays | kStorageTraceRaysRead | kStorageTraceRaysWrite | kTextureTraceRaysRead
-					| kTextureTraceRaysWrite | kIndirectTraceRays | kSBT,
+					| kTextureTraceRaysWrite | kIndirectTraceRays | kShaderBindingTable,
 
 	kAllRayTracing = kAllTraceRays | kAccelerationStructureBuild,
 	kAllRead = kAllUniform | kStorageGeometryRead | kStorageFragmentRead | kStorageComputeRead | kStorageTraceRaysRead
 			   | kTextureGeometryRead | kTextureFragmentRead | kTextureComputeRead | kTextureTraceRaysRead | kIndex
 			   | kVertex | kIndirectCompute | kIndirectDraw | kIndirectTraceRays | kTransferSource
-			   | kAccelerationStructureBuild | kSBT,
+			   | kAccelerationStructureBuild | kShaderBindingTable,
 	kAllWrite = kStorageGeometryWrite | kStorageFragmentWrite | kStorageComputeWrite | kStorageTraceRaysWrite
 				| kTextureGeometryWrite | kTextureFragmentWrite | kTextureComputeWrite | kTextureTraceRaysWrite
 				| kTransferDestination,

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

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

+ 1 - 1
AnKi/Gr/Vulkan/Common.cpp

@@ -323,7 +323,7 @@ VkBufferUsageFlags convertBufferUsageBit(BufferUsageBit usageMask)
 		out |= VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR;
 	}
 
-	if(!!(usageMask & BufferUsageBit::kSBT))
+	if(!!(usageMask & BufferUsageBit::kShaderBindingTable))
 	{
 		out |= VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR;
 	}

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

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

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

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

+ 147 - 130
AnKi/Importer/GltfImporter.cpp

@@ -214,6 +214,8 @@ Error GltfImporter::init(const GltfImporterInitInfo& initInfo)
 		m_hive = newInstance<ThreadHive>(*m_pool, threadCount, m_pool, true);
 	}
 
+	m_importTextures = initInfo.m_importTextures;
+
 	return Error::kNone;
 }
 
@@ -228,43 +230,151 @@ Error GltfImporter::writeAll()
 	ANKI_CHECK(m_sceneFile.writeText("local scene = getSceneGraph()\nlocal events = getEventManager()\n"));
 
 	// Nodes
-	Error err = Error::kNone;
-	for(const cgltf_scene* scene = m_gltf->scenes; scene < m_gltf->scenes + m_gltf->scenes_count && !err; ++scene)
+	for(const cgltf_scene* scene = m_gltf->scenes; scene < m_gltf->scenes + m_gltf->scenes_count; ++scene)
 	{
-		for(cgltf_node* const* node = scene->nodes; node < scene->nodes + scene->nodes_count && !err; ++node)
+		for(cgltf_node* const* node = scene->nodes; node < scene->nodes + scene->nodes_count; ++node)
 		{
-			err = visitNode(*(*node), Transform::getIdentity(), HashMapRaii<CString, StringRaii>(m_pool));
+			ANKI_CHECK(visitNode(*(*node), Transform::getIdentity(), HashMapRaii<CString, StringRaii>(m_pool)));
 		}
 	}
 
-	if(m_hive)
+	// Fire up all requests
+	for(auto& req : m_meshImportRequests)
 	{
-		m_hive->waitAllTasks();
+		if(m_hive)
+		{
+			m_hive->submitTask(
+				[](void* userData, [[maybe_unused]] U32 threadId, [[maybe_unused]] ThreadHive& hive,
+				   [[maybe_unused]] ThreadHiveSemaphore* signalSemaphore) {
+					ImportRequest<const cgltf_mesh*>* req = static_cast<ImportRequest<const cgltf_mesh*>*>(userData);
+					Error err = req->m_importer->m_errorInThread.load();
+
+					if(!err)
+					{
+						err = req->m_importer->writeMesh(*req->m_value);
+					}
+
+					if(err)
+					{
+						req->m_importer->m_errorInThread.store(err._getCode());
+					}
+				},
+				&req);
+		}
+		else
+		{
+			ANKI_CHECK(writeMesh(*req.m_value));
+		}
 	}
 
-	// Check error
-	if(err)
+	for(auto& req : m_materialImportRequests)
 	{
-		ANKI_IMPORTER_LOGE("Error happened in main thread");
-		return err;
+		if(m_hive)
+		{
+			m_hive->submitTask(
+				[](void* userData, [[maybe_unused]] U32 threadId, [[maybe_unused]] ThreadHive& hive,
+				   [[maybe_unused]] ThreadHiveSemaphore* signalSemaphore) {
+					ImportRequest<MaterialImportRequest>* req =
+						static_cast<ImportRequest<MaterialImportRequest>*>(userData);
+					Error err = req->m_importer->m_errorInThread.load();
+
+					if(!err)
+					{
+						err = req->m_importer->writeMaterial(*req->m_value.m_cgltfMaterial, req->m_value.m_writeRt);
+					}
+
+					if(err)
+					{
+						req->m_importer->m_errorInThread.store(err._getCode());
+					}
+				},
+				&req);
+		}
+		else
+		{
+			ANKI_CHECK(writeMaterial(*req.m_value.m_cgltfMaterial, req.m_value.m_writeRt));
+		}
 	}
 
-	const Error threadErr = m_errorInThread.load();
-	if(threadErr)
+	for(auto& req : m_skinImportRequests)
 	{
-		ANKI_IMPORTER_LOGE("Error happened in a thread");
-		return threadErr;
+		if(m_hive)
+		{
+			m_hive->submitTask(
+				[](void* userData, [[maybe_unused]] U32 threadId, [[maybe_unused]] ThreadHive& hive,
+				   [[maybe_unused]] ThreadHiveSemaphore* signalSemaphore) {
+					ImportRequest<const cgltf_skin*>* req = static_cast<ImportRequest<const cgltf_skin*>*>(userData);
+					Error err = req->m_importer->m_errorInThread.load();
+
+					if(!err)
+					{
+						err = req->m_importer->writeSkeleton(*req->m_value);
+					}
+
+					if(err)
+					{
+						req->m_importer->m_errorInThread.store(err._getCode());
+					}
+				},
+				&req);
+		}
+		else
+		{
+			ANKI_CHECK(writeSkeleton(*req.m_value));
+		}
 	}
 
+	for(auto& req : m_modelImportRequests)
+	{
+		if(m_hive)
+		{
+			m_hive->submitTask(
+				[](void* userData, [[maybe_unused]] U32 threadId, [[maybe_unused]] ThreadHive& hive,
+				   [[maybe_unused]] ThreadHiveSemaphore* signalSemaphore) {
+					ImportRequest<const cgltf_mesh*>* req = static_cast<ImportRequest<const cgltf_mesh*>*>(userData);
+					Error err = req->m_importer->m_errorInThread.load();
+
+					if(!err)
+					{
+						err = req->m_importer->writeModel(*req->m_value);
+					}
+
+					if(err)
+					{
+						req->m_importer->m_errorInThread.store(err._getCode());
+					}
+				},
+				&req);
+		}
+		else
+		{
+			ANKI_CHECK(writeModel(*req.m_value));
+		}
+	}
+
+	if(m_hive)
+	{
+		m_hive->waitAllTasks();
+
+		const Error threadErr = m_errorInThread.load();
+		if(threadErr)
+		{
+			ANKI_IMPORTER_LOGE("Error happened in a thread");
+			return threadErr;
+		}
+	}
+
+	// Animations
 	for(const cgltf_animation* anim = m_gltf->animations; anim < m_gltf->animations + m_gltf->animations_count; ++anim)
 	{
 		ANKI_CHECK(writeAnimation(*anim));
 	}
 
-	return err;
+	ANKI_IMPORTER_LOGV("Importing GLTF has completed");
+	return Error::kNone;
 }
 
-Error GltfImporter::getExtras(const cgltf_extras& extras, HashMapRaii<CString, StringRaii>& out)
+Error GltfImporter::getExtras(const cgltf_extras& extras, HashMapRaii<CString, StringRaii>& out) const
 {
 	cgltf_size extrasSize;
 	cgltf_copy_extras_json(m_gltf, &extras, nullptr, &extrasSize);
@@ -358,7 +468,7 @@ void GltfImporter::populateNodePtrToIdx()
 	}
 }
 
-StringRaii GltfImporter::getNodeName(const cgltf_node& node)
+StringRaii GltfImporter::getNodeName(const cgltf_node& node) const
 {
 	StringRaii out(m_pool);
 
@@ -446,8 +556,6 @@ Error GltfImporter::visitNode(const cgltf_node& node, const Transform& parentTrf
 
 		HashMapRaii<CString, StringRaii>::Iterator it;
 
-		const Bool skipRt = (it = extras.find("no_rt")) != extras.getEnd() && (*it == "true" || *it == "1");
-
 		if((it = extras.find("particles")) != extras.getEnd())
 		{
 			const StringRaii& fname = *it;
@@ -678,95 +786,22 @@ Error GltfImporter::visitNode(const cgltf_node& node, const Transform& parentTrf
 		{
 			// Model node
 
-			// Async because it's slow
-			struct Ctx
-			{
-				GltfImporter* m_importer;
-				const cgltf_mesh* m_mesh;
-				Array<const cgltf_material*, 128> m_materials;
-				U32 m_materialCount = 0;
-				const cgltf_skin* m_skin;
-				Bool m_rayTracing;
-			};
-			Ctx* ctx = newInstance<Ctx>(*m_pool);
-			ctx->m_importer = this;
-			ctx->m_mesh = node.mesh;
+			const Bool skipRt = (it = extras.find("no_rt")) != extras.getEnd() && (*it == "true" || *it == "1");
+
+			addRequest<const cgltf_mesh*>(node.mesh, m_meshImportRequests);
 			for(U32 i = 0; i < node.mesh->primitives_count; ++i)
 			{
-				ctx->m_materials[ctx->m_materialCount++] = node.mesh->primitives[i].material;
+				const MaterialImportRequest req = {node.mesh->primitives[i].material, !skipRt};
+				addRequest<MaterialImportRequest>(req, m_materialImportRequests);
 			}
-			ctx->m_skin = node.skin;
-			ctx->m_rayTracing = !skipRt;
-
-			HashMapRaii<CString, StringRaii>::Iterator it2;
-			const Bool selfCollision = (it2 = extras.find("collision_mesh")) != extras.getEnd() && *it2 == "self";
 
-			U32 maxLod = 0;
-			if(m_lodCount > 1 && !skipMeshLod(*node.mesh, 1))
-			{
-				maxLod = 1;
-			}
-			if(m_lodCount > 2 && !skipMeshLod(*node.mesh, 2))
+			if(node.skin)
 			{
-				maxLod = 2;
+				addRequest<const cgltf_skin*>(node.skin, m_skinImportRequests);
 			}
 
-			// Thread task
-			auto callback = [](void* userData, [[maybe_unused]] U32 threadId, [[maybe_unused]] ThreadHive& hive,
-							   [[maybe_unused]] ThreadHiveSemaphore* signalSemaphore) {
-				Ctx& self = *static_cast<Ctx*>(userData);
-
-				Error err = self.m_importer->m_errorInThread.load();
-
-				// LOD 0
-				if(!err)
-				{
-					err = self.m_importer->writeMesh(*self.m_mesh, 0, self.m_importer->computeLodFactor(0));
-				}
-
-				// LOD 1
-				if(!err && self.m_importer->m_lodCount > 1 && !self.m_importer->skipMeshLod(*self.m_mesh, 1))
-				{
-					err = self.m_importer->writeMesh(*self.m_mesh, 1, self.m_importer->computeLodFactor(1));
-				}
-
-				// LOD 2
-				if(!err && self.m_importer->m_lodCount > 2 && !self.m_importer->skipMeshLod(*self.m_mesh, 2))
-				{
-					err = self.m_importer->writeMesh(*self.m_mesh, 2, self.m_importer->computeLodFactor(2));
-				}
-
-				for(U32 i = 0; i < self.m_materialCount && !err; ++i)
-				{
-					err = self.m_importer->writeMaterial(*self.m_materials[i], self.m_rayTracing);
-				}
-
-				if(!err)
-				{
-					err = self.m_importer->writeModel(*self.m_mesh);
-				}
-
-				if(!err && self.m_skin)
-				{
-					err = self.m_importer->writeSkeleton(*self.m_skin);
-				}
-
-				if(err)
-				{
-					self.m_importer->m_errorInThread.store(err._getCode());
-				}
-
-				deleteInstance(*self.m_importer->m_pool, &self);
-			};
-
-			if(m_hive != nullptr)
-			{
-				m_hive->submitTask(callback, ctx);
-			}
-			else
-			{
-				callback(ctx, 0, *m_hive, nullptr);
-			}
+			HashMapRaii<CString, StringRaii>::Iterator it2;
+			const Bool selfCollision = (it2 = extras.find("collision_mesh")) != extras.getEnd() && *it2 == "self";
 
 			ANKI_CHECK(writeModelNode(node, parentExtras));
 
@@ -781,7 +816,7 @@ Error GltfImporter::visitNode(const cgltf_node& node, const Transform& parentTrf
 
 				ANKI_CHECK(m_sceneFile.writeText("comp = node2:getSceneNodeBase():getBodyComponent()\n"));
 
-				const StringRaii meshFname = computeMeshResourceFilename(*node.mesh, maxLod);
+				const StringRaii meshFname = computeMeshResourceFilename(*node.mesh);
 
 				ANKI_CHECK(
 					m_sceneFile.writeTextf("comp:loadMeshResource(\"%s%s\")\n", m_rpath.cstr(), meshFname.cstr()));
@@ -833,7 +868,7 @@ Error GltfImporter::writeTransform(const Transform& trf)
 	return Error::kNone;
 }
 
-Error GltfImporter::writeModel(const cgltf_mesh& mesh)
+Error GltfImporter::writeModel(const cgltf_mesh& mesh) const
 {
 	const StringRaii modelFname = computeModelResourceFilename(mesh);
 	ANKI_IMPORTER_LOGV("Importing model %s", modelFname.cstr());
@@ -851,30 +886,17 @@ Error GltfImporter::writeModel(const cgltf_mesh& mesh)
 
 	for(U32 primIdx = 0; primIdx < mesh.primitives_count; ++primIdx)
 	{
-		if(mesh.primitives_count == 1)
-		{
-			ANKI_CHECK(file.writeText("\t\t<modelPatch>\n"));
-		}
-		else
-		{
-			ANKI_CHECK(file.writeTextf("\t\t<modelPatch subMeshIndex=\"%u\">\n", primIdx));
-		}
+		ANKI_CHECK(file.writeText("\t\t<modelPatch>\n"));
 
+		const StringRaii meshFname = computeMeshResourceFilename(mesh);
+		if(mesh.primitives_count == 1)
 		{
-			const StringRaii meshFname = computeMeshResourceFilename(mesh);
 			ANKI_CHECK(file.writeTextf("\t\t\t<mesh>%s%s</mesh>\n", m_rpath.cstr(), meshFname.cstr()));
 		}
-
-		if(m_lodCount > 1 && !skipMeshLod(mesh, 1))
-		{
-			const StringRaii meshFname = computeMeshResourceFilename(mesh, 1);
-			ANKI_CHECK(file.writeTextf("\t\t\t<mesh1>%s%s</mesh1>\n", m_rpath.cstr(), meshFname.cstr()));
-		}
-
-		if(m_lodCount > 2 && !skipMeshLod(mesh, 2))
+		else
 		{
-			const StringRaii meshFname = computeMeshResourceFilename(mesh, 2);
-			ANKI_CHECK(file.writeTextf("\t\t\t<mesh2>%s%s</mesh2>\n", m_rpath.cstr(), meshFname.cstr()));
+			ANKI_CHECK(file.writeTextf("\t\t\t<mesh subMeshIndex=\"%u\">%s%s</mesh>\n", primIdx, m_rpath.cstr(),
+									   meshFname.cstr()));
 		}
 
 		HashMapRaii<CString, StringRaii> materialExtras(m_pool);
@@ -900,7 +922,7 @@ Error GltfImporter::writeModel(const cgltf_mesh& mesh)
 	return Error::kNone;
 }
 
-Error GltfImporter::writeSkeleton(const cgltf_skin& skin)
+Error GltfImporter::writeSkeleton(const cgltf_skin& skin) const
 {
 	StringRaii fname(m_pool);
 	fname.sprintf("%s%s", m_outDir.cstr(), computeSkeletonResourceFilename(skin).cstr());
@@ -1122,8 +1144,6 @@ Error GltfImporter::writeCamera(const cgltf_node& node,
 
 	ANKI_CHECK(m_sceneFile.writeTextf("frustumc:setPerspective(%f, %f, getMainRenderer():getAspectRatio() * %f, %f)\n",
 									  cam.znear, cam.zfar, cam.yfov, cam.yfov));
-	ANKI_CHECK(m_sceneFile.writeText("frustumc:setShadowCascadesDistancePower(1.5)\n"));
-	ANKI_CHECK(m_sceneFile.writeTextf("frustumc:setEffectiveShadowDistance(%f)\n", min(cam.zfar, 100.0f)));
 
 	return Error::kNone;
 }
@@ -1173,14 +1193,11 @@ StringRaii GltfImporter::computeModelResourceFilename(const cgltf_mesh& mesh) co
 	return out;
 }
 
-StringRaii GltfImporter::computeMeshResourceFilename(const cgltf_mesh& mesh, U32 lod) const
+StringRaii GltfImporter::computeMeshResourceFilename(const cgltf_mesh& mesh) const
 {
 	const U64 hash = computeHash(mesh.name, strlen(mesh.name));
-
 	StringRaii out(m_pool);
-
-	out.sprintf("%.64s_lod%u_%" PRIx64 ".ankimesh", mesh.name, lod, hash); // Limit the filename size
-
+	out.sprintf("%.64s_%" PRIx64 ".ankimesh", mesh.name, hash); // Limit the filename size
 	return out;
 }
 

+ 58 - 9
AnKi/Importer/GltfImporter.h

@@ -34,6 +34,7 @@ public:
 	F32 m_lightIntensityScale = 1.0f;
 	U32 m_threadCount = kMaxU32;
 	CString m_comment;
+	Bool m_importTextures = false;
 };
 
 /// Import GLTF and spit AnKi scenes.
@@ -59,7 +60,7 @@ private:
 	};
 
 	// Data
-	BaseMemoryPool* m_pool = nullptr;
+	mutable BaseMemoryPool* m_pool = nullptr;
 
 	StringRaii m_inputFname = {m_pool};
 	StringRaii m_outDir = {m_pool};
@@ -74,7 +75,7 @@ private:
 
 	File m_sceneFile;
 
-	Atomic<I32> m_errorInThread{0};
+	mutable Atomic<I32> m_errorInThread = {0};
 
 	HashMapRaii<const void*, U32, PtrHasher> m_nodePtrToIdx = {m_pool}; ///< Need an index for the unnamed nodes.
 
@@ -88,12 +89,60 @@ private:
 	/// Don't generate LODs for meshes with less vertices than this number.
 	U32 m_skipLodVertexCountThreshold = 256;
 
+	Bool m_importTextures = false;
+
+	template<typename T>
+	class ImportRequest
+	{
+	public:
+		const GltfImporter* m_importer = nullptr;
+		T m_value = {};
+	};
+
+	class MaterialImportRequest
+	{
+	public:
+		const cgltf_material* m_cgltfMaterial = nullptr;
+		Bool m_writeRt = true;
+
+		Bool operator==(const MaterialImportRequest& b) const
+		{
+			return m_cgltfMaterial == b.m_cgltfMaterial && m_writeRt == b.m_writeRt;
+		}
+	};
+
+	DynamicArrayRaii<ImportRequest<const cgltf_mesh*>> m_meshImportRequests = {m_pool};
+	DynamicArrayRaii<ImportRequest<MaterialImportRequest>> m_materialImportRequests = {m_pool};
+	DynamicArrayRaii<ImportRequest<const cgltf_skin*>> m_skinImportRequests = {m_pool};
+	DynamicArrayRaii<ImportRequest<const cgltf_mesh*>> m_modelImportRequests = {m_pool};
+
 	// Misc
-	Error getExtras(const cgltf_extras& extras, HashMapRaii<CString, StringRaii>& out);
+	template<typename T>
+	void addRequest(const T& value, DynamicArrayRaii<ImportRequest<T>>& array) const
+	{
+		Bool found = false;
+		for(const auto& req : array)
+		{
+			ANKI_ASSERT(req.m_importer == this);
+			if(req.m_value == value)
+			{
+				found = true;
+				break;
+			}
+		}
+
+		if(!found)
+		{
+			const ImportRequest<T> req = {this, value};
+			array.emplaceBack(req);
+		}
+	}
+
+	Error getExtras(const cgltf_extras& extras, HashMapRaii<CString, StringRaii>& out) const;
 	Error parseArrayOfNumbers(CString str, DynamicArrayRaii<F64>& out, const U32* expectedArraySize = nullptr);
 	void populateNodePtrToIdx();
 	void populateNodePtrToIdxInternal(const cgltf_node& node, U32& idx);
-	StringRaii getNodeName(const cgltf_node& node);
+	StringRaii getNodeName(const cgltf_node& node) const;
 
 	template<typename T, typename TFunc>
 	static void visitAccessor(const cgltf_accessor& accessor, TFunc func);
@@ -129,17 +178,17 @@ private:
 
 	// Compute filenames for various resources. Use a hash to solve the casing issue and remove unwanted special chars
 	StringRaii computeModelResourceFilename(const cgltf_mesh& mesh) const;
-	StringRaii computeMeshResourceFilename(const cgltf_mesh& mesh, U32 lod = 0) const;
+	StringRaii computeMeshResourceFilename(const cgltf_mesh& mesh) const;
 	StringRaii computeMaterialResourceFilename(const cgltf_material& mtl) const;
 	StringRaii computeAnimationResourceFilename(const cgltf_animation& anim) const;
 	StringRaii computeSkeletonResourceFilename(const cgltf_skin& skin) const;
 
 	// Resources
-	Error writeMesh(const cgltf_mesh& mesh, U32 lod, F32 decimateFactor);
-	Error writeMaterial(const cgltf_material& mtl, Bool writeRayTracing);
-	Error writeModel(const cgltf_mesh& mesh);
+	Error writeMesh(const cgltf_mesh& mesh) const;
+	Error writeMaterial(const cgltf_material& mtl, Bool writeRayTracing) const;
+	Error writeModel(const cgltf_mesh& mesh) const;
 	Error writeAnimation(const cgltf_animation& anim);
-	Error writeSkeleton(const cgltf_skin& skin);
+	Error writeSkeleton(const cgltf_skin& skin) const;
 
 	// Scene
 	Error writeTransform(const Transform& trf);

+ 17 - 10
AnKi/Importer/GltfImporterAnimation.cpp

@@ -50,11 +50,12 @@ static void optimizeChannel(DynamicArrayRaii<GltfAnimKey<T>>& arr, const T& iden
 		}
 
 		DynamicArrayRaii<GltfAnimKey<T>> newArr(&arr.getMemoryPool());
-		for(U32 i = 0; i < arr.getSize() - 2; i += 2)
+		U32 it = 0;
+		while(true)
 		{
-			const GltfAnimKey<T>& left = arr[i];
-			const GltfAnimKey<T>& middle = arr[i + 1];
-			const GltfAnimKey<T>& right = arr[i + 2];
+			const GltfAnimKey<T>& left = arr[it];
+			const GltfAnimKey<T>& middle = arr[it + 1];
+			const GltfAnimKey<T>& right = arr[it + 2];
 
 			newArr.emplaceBack(left);
 
@@ -77,8 +78,18 @@ static void optimizeChannel(DynamicArrayRaii<GltfAnimKey<T>>& arr, const T& iden
 				}
 			}
 
-			newArr.emplaceBack(right);
+			it += 2;
+			if(it + 2 >= arr.getSize())
+			{
+				break;
+			}
+		}
+
+		for(; it < arr.getSize(); ++it)
+		{
+			newArr.emplaceBack(arr[it]);
 		}
+
 		ANKI_ASSERT(newArr.getSize() <= arr.getSize());
 
 		// Check if identity
@@ -353,18 +364,14 @@ Error GltfImporter::writeAnimation(const cgltf_animation& anim)
 
 		// Only animate cameras for now
 		const cgltf_node& node = *channel.m_targetNode;
-		if((node.camera == nullptr) || node.name == nullptr)
+		if(node.camera == nullptr || node.name == nullptr)
 		{
 			continue;
 		}
 
-		// ANKI_CHECK(m_sceneFile.writeText("--[[\n"));
-
 		ANKI_CHECK(m_sceneFile.writeTextf("\nnode = scene:tryFindSceneNode(\"%s\")\n", node.name));
 		ANKI_CHECK(m_sceneFile.writeTextf("getEventManager():newAnimationEvent(\"%s%s\", \"%s\", node)\n",
 										  m_rpath.cstr(), animFname.cstr(), node.name));
-
-		// ANKI_CHECK(m_sceneFile.writeText("--]]\n"));
 	}
 
 	return Error::kNone;

+ 93 - 5
AnKi/Importer/GltfImporterMaterial.cpp

@@ -4,9 +4,11 @@
 // http://www.anki3d.org/LICENSE
 
 #include <AnKi/Importer/GltfImporter.h>
+#include <AnKi/Importer/ImageImporter.h>
 #include <AnKi/Resource/ImageLoader.h>
 #include <AnKi/Util/WeakArray.h>
 #include <AnKi/Util/Xml.h>
+#include <AnKi/Util/Filesystem.h>
 
 namespace anki {
 
@@ -98,7 +100,56 @@ static Error findConstantColorsInImage(CString fname, Vec4& constantColor, BaseM
 	return Error::kNone;
 }
 
-Error GltfImporter::writeMaterial(const cgltf_material& mtl, Bool writeRayTracing)
+static Error importImage(BaseMemoryPool& pool, CString in, CString out, Bool alpha)
+{
+	ImageImporterConfig config;
+
+	config.m_pool = &pool;
+
+	Array<CString, 1> inputFnames = {in};
+	config.m_inputFilenames = inputFnames;
+
+	config.m_outFilename = out;
+	config.m_compressions = ImageBinaryDataCompression::kS3tc | ImageBinaryDataCompression::kAstc;
+	config.m_minMipmapDimension = 8;
+	config.m_noAlpha = !alpha;
+
+	StringRaii tmp(&pool);
+	if(getTempDirectory(tmp))
+	{
+		ANKI_IMPORTER_LOGE("getTempDirectory() failed");
+		return 1;
+	}
+	config.m_tempDirectory = tmp;
+
+#if ANKI_OS_WINDOWS
+	config.m_compressonatorFilename =
+		ANKI_SOURCE_DIRECTORY "/ThirdParty/Bin/Windows64/Compressonator/compressonatorcli.exe";
+	config.m_astcencFilename = ANKI_SOURCE_DIRECTORY "/ThirdParty/Bin/Windows64/astcenc-avx2.exe";
+#elif ANKI_OS_LINUX
+	config.m_compressonatorFilename = ANKI_SOURCE_DIRECTORY "/ThirdParty/Bin/Linux64/Compressonator/compressonatorcli";
+	config.m_astcencFilename = ANKI_SOURCE_DIRECTORY "/ThirdParty/Bin/Linux64/astcenc-avx2";
+#else
+#	error "Unupported"
+#endif
+
+	config.m_flipImage = false;
+
+	ANKI_IMPORTER_LOGV("Importing image \"%s\" as \"%s\"", in.cstr(), out.cstr());
+	ANKI_CHECK(importImage(config));
+
+	return Error::kNone;
+}
+
+static void fixImageUri(StringRaii& uri)
+{
+	uri.replaceAll(".tga", ".ankitex");
+	uri.replaceAll(".png", ".ankitex");
+	uri.replaceAll(".jpg", ".ankitex");
+	uri.replaceAll(".jpeg", ".ankitex");
+}
+
+Error GltfImporter::writeMaterial(const cgltf_material& mtl, Bool writeRayTracing) const
 {
 	StringRaii fname(m_pool);
 	fname.sprintf("%s%s", m_outDir.cstr(), computeMaterialResourceFilename(mtl).cstr());
@@ -139,6 +190,14 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, Bool writeRayTracin
 
 		const Bool constantAlpha = constantColor.w() >= 0.0f;
 		xml.replaceAll("%alphaTestMutator%", (constantAlpha) ? "0" : "1");
+
+		if(m_importTextures)
+		{
+			StringRaii out = m_outDir;
+			out.append(fname);
+			fixImageUri(out);
+			ANKI_CHECK(importImage(*m_pool, fname, out, !constantAlpha));
+		}
 	}
 	else
 	{
@@ -196,6 +255,7 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, Bool writeRayTracin
 	}
 
 	// Roughness
+	Bool bRoughnessMetalicTexture = false;
 	if(mtl.pbr_metallic_roughness.metallic_roughness_texture.texture && constantRoughness < 0.0f)
 	{
 		StringRaii uri(m_pool);
@@ -206,6 +266,8 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, Bool writeRayTracin
 					   StringRaii(m_pool).sprintf("<input name=\"m_roughnessTex\" value=\"%s\"/>", uri.cstr()));
 
 		xml.replaceAll("%roughnessTexMutator%", "1");
+
+		bRoughnessMetalicTexture = true;
 	}
 	else
 	{
@@ -230,6 +292,8 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, Bool writeRayTracin
 					   StringRaii(m_pool).sprintf("<input name=\"m_metallicTex\" value=\"%s\"/>", uri.cstr()));
 
 		xml.replaceAll("%metalTexMutator%", "1");
+
+		bRoughnessMetalicTexture = true;
 	}
 	else
 	{
@@ -243,6 +307,15 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, Bool writeRayTracin
 		xml.replaceAll("%metalTexMutator%", "0");
 	}
 
+	if(bRoughnessMetalicTexture && m_importTextures)
+	{
+		CString in = getTextureUri(mtl.pbr_metallic_roughness.metallic_roughness_texture);
+		StringRaii out = m_outDir;
+		out.append(in);
+		fixImageUri(out);
+		ANKI_CHECK(importImage(*m_pool, in, out, false));
+	}
+
 	// Normal texture
 	if(mtl.normal_texture.texture)
 	{
@@ -257,6 +330,15 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, Bool writeRayTracin
 						   StringRaii(m_pool).sprintf("<input name=\"m_normalTex\" value=\"%s\"/>", uri.cstr()));
 
 			xml.replaceAll("%normalTexMutator%", "1");
+
+			if(m_importTextures)
+			{
+				CString in = getTextureUri(mtl.normal_texture);
+				StringRaii out = m_outDir;
+				out.append(in);
+				fixImageUri(out);
+				ANKI_CHECK(importImage(*m_pool, in, out, false));
+			}
 		}
 		else
 		{
@@ -280,6 +362,15 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, Bool writeRayTracin
 					   StringRaii(m_pool).sprintf("<input name=\"m_emissiveTex\" value=\"%s\"/>", uri.cstr()));
 
 		xml.replaceAll("%emissiveTexMutator%", "1");
+
+		if(m_importTextures)
+		{
+			CString in = getTextureUri(mtl.emissive_texture);
+			StringRaii out = m_outDir;
+			out.append(in);
+			fixImageUri(out);
+			ANKI_CHECK(importImage(*m_pool, in, out, false));
+		}
 	}
 	else
 	{
@@ -329,10 +420,7 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, Bool writeRayTracin
 	}
 
 	// Replace texture extensions with .anki
-	xml.replaceAll(".tga", ".ankitex");
-	xml.replaceAll(".png", ".ankitex");
-	xml.replaceAll(".jpg", ".ankitex");
-	xml.replaceAll(".jpeg", ".ankitex");
+	fixImageUri(xml);
 
 	// Write file
 	File file;

+ 170 - 201
AnKi/Importer/GltfImporterMesh.cpp

@@ -8,7 +8,7 @@
 #include <AnKi/Collision/Plane.h>
 #include <AnKi/Collision/Functions.h>
 #include <AnKi/Resource/MeshBinary.h>
-#include <AnKi/Shaders/Include/ModelTypes.h>
+#include <AnKi/Shaders/Include/MeshTypes.h>
 #include <MeshOptimizer/meshoptimizer.h>
 
 namespace anki {
@@ -93,21 +93,6 @@ static Error checkAttribute(const cgltf_attribute& attrib)
 	return Error::kNone;
 }
 
-/// Align after laying a buffer in a file.
-static Error alignBufferInFile(PtrSize bufferSize, File& file)
-{
-	const PtrSize alignedBufferSize = getAlignedRoundUp(kMeshBinaryBufferAlignment, bufferSize);
-	const PtrSize extraBytes = alignedBufferSize - bufferSize;
-
-	for(U32 i = 0; i < extraBytes; ++i)
-	{
-		U8 value = 0;
-		ANKI_CHECK(file.write(&value, sizeof(value)));
-	}
-
-	return Error::kNone;
-}
-
 class TempVertex
 {
 public:
@@ -430,6 +415,20 @@ static Bool isConvex(const List<SubMesh>& submeshes)
 	return convex;
 }
 
+static void writeVertexAttribAndBufferInfoToHeader(VertexStreamId stream, MeshBinaryHeader& header,
+												   const Vec4& scale = Vec4(1.0f), const Vec4& translation = Vec4(0.0f))
+{
+	MeshBinaryVertexAttribute& attrib = header.m_vertexAttributes[stream];
+	attrib.m_bufferIndex = U32(stream);
+	attrib.m_format = kMeshRelatedVertexStreamFormats[stream];
+	attrib.m_relativeOffset = 0;
+	attrib.m_scale = {scale[0], scale[1], scale[2], scale[3]};
+	attrib.m_translation = {translation[0], translation[1], translation[2], translation[3]};
+
+	MeshBinaryVertexBuffer& buff = header.m_vertexBuffers[stream];
+	buff.m_vertexStride = getFormatInfo(attrib.m_format).m_texelSize;
+}
+
 U32 GltfImporter::getMeshTotalVertexCount(const cgltf_mesh& mesh)
 {
 	U32 totalVertexCount = 0;
@@ -443,20 +442,16 @@ U32 GltfImporter::getMeshTotalVertexCount(const cgltf_mesh& mesh)
 	return totalVertexCount;
 }
 
-Error GltfImporter::writeMesh(const cgltf_mesh& mesh, U32 lod, F32 decimateFactor)
+Error GltfImporter::writeMesh(const cgltf_mesh& mesh) const
 {
+	StringRaii meshName = computeMeshResourceFilename(mesh);
 	StringRaii fname(m_pool);
-	fname.sprintf("%s%s", m_outDir.cstr(), computeMeshResourceFilename(mesh, lod).cstr());
-	ANKI_IMPORTER_LOGV("Importing mesh (%s, decimate factor %f): %s",
-					   (m_optimizeMeshes) ? "optimize" : "WON'T optimize", decimateFactor, fname.cstr());
+	fname.sprintf("%s%s", m_outDir.cstr(), meshName.cstr());
+	ANKI_IMPORTER_LOGV("Importing mesh (%s): %s", (m_optimizeMeshes) ? "optimize" : "WON'T optimize", fname.cstr());
 
-	ListRaii<SubMesh> submeshes(m_pool);
-	U32 totalIndexCount = 0;
-	U32 totalVertexCount = 0;
+	Array<ListRaii<SubMesh>, kMaxLodCount> submeshes = {{{m_pool}, {m_pool}, {m_pool}}};
 	Vec3 aabbMin(kMaxF32);
 	Vec3 aabbMax(kMinF32);
-	F32 maxUvDistance = kMinF32;
-	F32 minUvDistance = kMaxF32;
 	Bool hasBoneWeights = false;
 
 	// Iterate primitives. Every primitive is a submesh
@@ -469,7 +464,7 @@ Error GltfImporter::writeMesh(const cgltf_mesh& mesh, U32 lod, F32 decimateFacto
 			return Error::kUserData;
 		}
 
-		SubMesh& submesh = *submeshes.emplaceBack(m_pool);
+		SubMesh& submesh = *submeshes[0].emplaceBack(m_pool);
 
 		U minVertCount = kMaxU;
 		U maxVertCount = kMinU;
@@ -518,8 +513,6 @@ Error GltfImporter::writeMesh(const cgltf_mesh& mesh, U32 lod, F32 decimateFacto
 				U32 count = 0;
 				ANKI_CHECK(checkAttribute<Vec2>(*attrib));
 				visitAccessor<Vec2>(*attrib->data, [&](const Vec2& uv) {
-					maxUvDistance = max(maxUvDistance, max(uv.x(), uv.y()));
-					minUvDistance = min(minUvDistance, min(uv.x(), uv.y()));
 					submesh.m_verts[count++].m_uv = uv;
 				});
 			}
@@ -601,7 +594,7 @@ Error GltfImporter::writeMesh(const cgltf_mesh& mesh, U32 lod, F32 decimateFacto
 		// Re-index meshes now and
 		// - before the tanget calculation because that will create many unique verts
 		// - after normal fix because that will create verts with same attributes
-		if(m_optimizeMeshes || decimateFactor < 1.0f)
+		if(m_optimizeMeshes)
 		{
 			reindexSubmesh(submesh, m_pool);
 			vertCount = submesh.m_verts.getSize();
@@ -616,230 +609,206 @@ Error GltfImporter::writeMesh(const cgltf_mesh& mesh, U32 lod, F32 decimateFacto
 			optimizeSubmesh(submesh, m_pool);
 		}
 
-		// Simplify
-		if(decimateFactor < 1.0f)
-		{
-			decimateSubmesh(decimateFactor, submesh, m_pool);
-		}
-
 		// Finalize
 		if(submesh.m_indices.getSize() == 0 || submesh.m_verts.getSize() == 0)
 		{
-			// Digenerate
-			submeshes.popBack();
-		}
-		else
-		{
-			// Finalize
-			submesh.m_firstIdx = totalIndexCount;
-			submesh.m_idxCount = submesh.m_indices.getSize();
-			totalIndexCount += submesh.m_idxCount;
-			totalVertexCount += submesh.m_verts.getSize();
+			ANKI_UTIL_LOGE("Mesh degenerate: %s", meshName.cstr());
+			return Error::kUserData;
 		}
 	}
 
-	if(submeshes.getSize() == 0)
-	{
-		ANKI_IMPORTER_LOGE("Mesh contains degenerate geometry");
-		return Error::kUserData;
-	}
-
-	// Find if it's a convex shape
-	const Bool convex = isConvex(submeshes);
-
-	// Chose the formats of the attributes
-	MeshBinaryHeader header;
-	memset(&header, 0, sizeof(header));
+	// Generate submeshes for the other LODs
+	ANKI_ASSERT(m_lodCount <= kMaxLodCount && m_lodCount > 0);
+	U32 maxLod = 0;
+	for(U32 lod = 1; lod < m_lodCount; ++lod)
 	{
-		// Positions
-		MeshBinaryVertexAttribute& posa = header.m_vertexAttributes[VertexAttributeId::kPosition];
-		posa.m_bufferBinding = 0;
-		posa.m_format = Format::kR32G32B32_Sfloat;
-		posa.m_relativeOffset = 0;
-		posa.m_scale = 1.0f;
-
-		// Normals
-		MeshBinaryVertexAttribute& na = header.m_vertexAttributes[VertexAttributeId::kNormal];
-		na.m_bufferBinding = 1;
-		na.m_format = Format::kA2B10G10R10_Snorm_Pack32;
-		na.m_relativeOffset = 0;
-		na.m_scale = 1.0f;
-
-		// Tangents
-		MeshBinaryVertexAttribute& ta = header.m_vertexAttributes[VertexAttributeId::kTangent];
-		ta.m_bufferBinding = 1;
-		ta.m_format = Format::kA2B10G10R10_Snorm_Pack32;
-		ta.m_relativeOffset = sizeof(U32);
-		ta.m_scale = 1.0f;
-
-		// UVs
-		MeshBinaryVertexAttribute& uva = header.m_vertexAttributes[VertexAttributeId::kUv0];
-		uva.m_bufferBinding = 1;
-		uva.m_format = Format::kR32G32_Sfloat;
-		uva.m_relativeOffset = sizeof(U32) * 2;
-		uva.m_scale = 1.0f;
-
-		// Bone weight
-		if(hasBoneWeights)
+		if(skipMeshLod(mesh, lod))
 		{
-			MeshBinaryVertexAttribute& bidxa = header.m_vertexAttributes[VertexAttributeId::kBoneIndices];
-			bidxa.m_bufferBinding = 2;
-			bidxa.m_format = Format::kR8G8B8A8_Uint;
-			bidxa.m_relativeOffset = 0;
-			bidxa.m_scale = 1.0f;
-
-			MeshBinaryVertexAttribute& wa = header.m_vertexAttributes[VertexAttributeId::kBoneWeights];
-			wa.m_bufferBinding = 2;
-			wa.m_format = Format::kR8G8B8A8_Unorm;
-			wa.m_relativeOffset = sizeof(U8Vec4);
-			wa.m_scale = 1.0f;
+			break;
 		}
-	}
-
-	// Arange the attributes into vert buffers
-	{
-		// First buff has positions
-		header.m_vertexBuffers[0].m_vertexStride = sizeof(Vec3);
-		++header.m_vertexBufferCount;
-
-		// 2nd buff has normal + tangent + texcoords
-		header.m_vertexBuffers[1].m_vertexStride = sizeof(MainVertex);
-		++header.m_vertexBufferCount;
 
-		// 3rd has bone weights
-		if(hasBoneWeights)
+		for(const SubMesh& lod0Submesh : submeshes[0])
 		{
-			header.m_vertexBuffers[2].m_vertexStride = sizeof(BoneInfoVertex);
-			++header.m_vertexBufferCount;
-		}
-	}
+			SubMesh& newSubmesh = *submeshes[lod].pushBack(m_pool);
+			newSubmesh = lod0Submesh; // Copy LOD0 data to new submesh
 
-	// Write some other header stuff
-	{
-		memcpy(&header.m_magic[0], kMeshMagic, 8);
-		header.m_flags = MeshBinaryFlag::kNone;
-		if(convex)
-		{
-			header.m_flags |= MeshBinaryFlag::kConvex;
+			decimateSubmesh(computeLodFactor(lod), newSubmesh, m_pool);
 		}
-		header.m_indexType = IndexType::kU16;
-		header.m_totalIndexCount = totalIndexCount;
-		header.m_totalVertexCount = totalVertexCount;
-		header.m_subMeshCount = U32(submeshes.getSize());
-		header.m_aabbMin = aabbMin;
-		header.m_aabbMax = aabbMax;
+
+		maxLod = lod;
 	}
 
-	// Open file
+	// Start writing the file
 	File file;
 	ANKI_CHECK(file.open(fname.toCString(), FileOpenFlag::kWrite | FileOpenFlag::kBinary));
 
-	// Write header
-	ANKI_CHECK(file.write(&header, sizeof(header)));
+	// Populate the header
+	MeshBinaryHeader header;
+	memset(&header, 0, sizeof(header));
+	memcpy(&header.m_magic[0], kMeshMagic, 8);
 
-	// Write sub meshes
-	for(const SubMesh& in : submeshes)
+	header.m_flags = MeshBinaryFlag::kNone;
+	if(isConvex(submeshes[0]))
 	{
-		MeshBinarySubMesh out;
-		out.m_firstIndex = in.m_firstIdx;
-		out.m_indexCount = in.m_idxCount;
-		out.m_aabbMin = in.m_aabbMin;
-		out.m_aabbMax = in.m_aabbMax;
-
-		ANKI_CHECK(file.write(&out, sizeof(out)));
+		header.m_flags |= MeshBinaryFlag::kConvex;
+	}
+	header.m_indexType = IndexType::kU16;
+	header.m_subMeshCount = U32(submeshes[0].getSize());
+	header.m_aabbMin = aabbMin;
+	header.m_aabbMax = aabbMax;
+	header.m_lodCount = maxLod + 1;
+
+	// Compute the pos scale and transform. The scale is uniform because it's applied to the model matrix of the object
+	F32 posScale = kMinF32;
+	for(U c = 0; c < 3; c++)
+	{
+		posScale = max(posScale, aabbMax[c] - aabbMin[c]);
+	}
+	posScale = (posScale < 1.0f) ? 1.0f : (1.0f / posScale);
+	const Vec3 posTranslation = -aabbMin;
+
+	writeVertexAttribAndBufferInfoToHeader(VertexStreamId::kPosition, header, Vec4(1.0f / posScale),
+										   (-posTranslation).xyz1());
+	writeVertexAttribAndBufferInfoToHeader(VertexStreamId::kNormal, header);
+	writeVertexAttribAndBufferInfoToHeader(VertexStreamId::kTangent, header);
+	writeVertexAttribAndBufferInfoToHeader(VertexStreamId::kUv, header);
+	if(hasBoneWeights)
+	{
+		writeVertexAttribAndBufferInfoToHeader(VertexStreamId::kBoneIds, header);
+		writeVertexAttribAndBufferInfoToHeader(VertexStreamId::kBoneWeights, header);
 	}
 
-	// Write indices
-	U32 vertCount = 0;
-	for(const SubMesh& submesh : submeshes)
+	// Write sub meshes
+	DynamicArrayRaii<MeshBinarySubMesh> outSubmeshes(m_pool, U32(submeshes[0].getSize()));
+
+	for(U32 submeshIdx = 0; submeshIdx < outSubmeshes.getSize(); ++submeshIdx)
 	{
-		DynamicArrayRaii<U16> indices(m_pool);
-		indices.create(submesh.m_indices.getSize());
-		for(U32 i = 0; i < indices.getSize(); ++i)
+		MeshBinarySubMesh& out = outSubmeshes[submeshIdx];
+		memset(&out, 0, sizeof(out));
+
+		for(U32 lod = 0; lod <= maxLod; ++lod)
 		{
-			const U32 idx = submesh.m_indices[i] + vertCount;
-			if(idx > kMaxU16)
+			const SubMesh& inSubmesh = *(submeshes[lod].getBegin() + submeshIdx);
+
+			if(lod == 0)
 			{
-				ANKI_IMPORTER_LOGE("Only supports 16bit indices for now (%u): %s", idx, fname.cstr());
-				return Error::kUserData;
+				out.m_aabbMin = inSubmesh.m_aabbMin;
+				out.m_aabbMax = inSubmesh.m_aabbMax;
 			}
 
-			indices[i] = U16(idx);
-		}
+			out.m_firstIndices[lod] = header.m_totalIndexCounts[lod];
+			out.m_indexCounts[lod] = inSubmesh.m_indices.getSize();
 
-		ANKI_CHECK(file.write(&indices[0], indices.getSizeInBytes()));
-		vertCount += submesh.m_verts.getSize();
+			header.m_totalIndexCounts[lod] += inSubmesh.m_indices.getSize();
+			header.m_totalVertexCounts[lod] += inSubmesh.m_verts.getSize();
+		}
 	}
 
-	ANKI_CHECK(alignBufferInFile(header.m_totalIndexCount * sizeof(U16), file));
+	ANKI_CHECK(file.write(&header, sizeof(header)));
+	ANKI_CHECK(file.write(&outSubmeshes[0], outSubmeshes.getSizeInBytes()));
 
-	// Write position vert buffer
-	for(const SubMesh& submesh : submeshes)
+	// Write LODs
+	for(I32 lod = I32(maxLod); lod >= 0; --lod)
 	{
-		DynamicArrayRaii<Vec3> positions(m_pool);
-		positions.create(submesh.m_verts.getSize());
-		for(U32 v = 0; v < submesh.m_verts.getSize(); ++v)
+		// Write index buffer
+		U32 vertCount = 0;
+		for(const SubMesh& submesh : submeshes[lod])
 		{
-			positions[v] = submesh.m_verts[v].m_position;
-		}
-		ANKI_CHECK(file.write(&positions[0], positions.getSizeInBytes()));
-	}
+			DynamicArrayRaii<U16> indices(m_pool, submesh.m_indices.getSize());
+			for(U32 i = 0; i < indices.getSize(); ++i)
+			{
+				const U32 idx = submesh.m_indices[i] + vertCount;
+				if(idx > kMaxU16)
+				{
+					ANKI_IMPORTER_LOGE("Only supports 16bit indices for now (%u): %s", idx, fname.cstr());
+					return Error::kUserData;
+				}
 
-	ANKI_CHECK(alignBufferInFile(header.m_totalVertexCount * sizeof(Vec3), file));
+				indices[i] = U16(idx);
+			}
 
-	// Write the 2nd vert buffer
-	for(const SubMesh& submesh : submeshes)
-	{
-		DynamicArrayRaii<MainVertex> verts(m_pool);
-		verts.create(submesh.m_verts.getSize());
+			ANKI_CHECK(file.write(&indices[0], indices.getSizeInBytes()));
+			vertCount += submesh.m_verts.getSize();
+		}
 
-		for(U32 i = 0; i < verts.getSize(); ++i)
+		// Write positions
+		for(const SubMesh& submesh : submeshes[lod])
 		{
-			const Vec3& normal = submesh.m_verts[i].m_normal;
-			const Vec4& tangent = submesh.m_verts[i].m_tangent;
-			const Vec2& uv = submesh.m_verts[i].m_uv;
+			DynamicArrayRaii<U16Vec3> positions(m_pool, submesh.m_verts.getSize());
+			for(U32 v = 0; v < submesh.m_verts.getSize(); ++v)
+			{
+				Vec3 localPos = (submesh.m_verts[v].m_position + posTranslation) * posScale;
+				localPos = localPos.clamp(0.0f, 1.0f);
+				localPos *= F32(kMaxU16);
+				localPos = localPos.round();
+				positions[v] = U16Vec3(localPos);
+			}
 
-			verts[i].m_normal = packColorToR10G10B10A2SNorm(normal.x(), normal.y(), normal.z(), 0.0f);
-			verts[i].m_tangent = packColorToR10G10B10A2SNorm(tangent.x(), tangent.y(), tangent.z(), tangent.w());
-			verts[i].m_uv0 = uv;
+			ANKI_CHECK(file.write(&positions[0], positions.getSizeInBytes()));
 		}
 
-		ANKI_CHECK(file.write(&verts[0], verts.getSizeInBytes()));
-	}
+		// Write normals
+		for(const SubMesh& submesh : submeshes[lod])
+		{
+			DynamicArrayRaii<U32> normals(m_pool, submesh.m_verts.getSize());
+			for(U32 v = 0; v < submesh.m_verts.getSize(); ++v)
+			{
+				normals[v] = packSnorm4x8(submesh.m_verts[v].m_normal.xyz0());
+			}
 
-	ANKI_CHECK(alignBufferInFile(header.m_totalVertexCount * sizeof(MainVertex), file));
+			ANKI_CHECK(file.write(&normals[0], normals.getSizeInBytes()));
+		}
 
-	// Write 3rd vert buffer
-	if(hasBoneWeights)
-	{
-		for(const SubMesh& submesh : submeshes)
+		// Write tangent
+		for(const SubMesh& submesh : submeshes[lod])
 		{
-			DynamicArrayRaii<BoneInfoVertex> verts(m_pool);
-			verts.create(submesh.m_verts.getSize());
+			DynamicArrayRaii<U32> tangents(m_pool, submesh.m_verts.getSize());
+			for(U32 v = 0; v < submesh.m_verts.getSize(); ++v)
+			{
+				tangents[v] = packSnorm4x8(submesh.m_verts[v].m_tangent);
+			}
 
-			for(U32 i = 0; i < verts.getSize(); ++i)
+			ANKI_CHECK(file.write(&tangents[0], tangents.getSizeInBytes()));
+		}
+
+		// Write UV
+		for(const SubMesh& submesh : submeshes[lod])
+		{
+			DynamicArrayRaii<Vec2> uvs(m_pool, submesh.m_verts.getSize());
+			for(U32 v = 0; v < submesh.m_verts.getSize(); ++v)
 			{
-				BoneInfoVertex vert;
+				uvs[v] = submesh.m_verts[v].m_uv;
+			}
 
-				for(U32 c = 0; c < 4; ++c)
-				{
-					if(submesh.m_verts[i].m_boneIds[c] > 0XFF)
-					{
-						ANKI_IMPORTER_LOGE("Only 256 bones are supported");
-						return Error::kUserData;
-					}
+			ANKI_CHECK(file.write(&uvs[0], uvs.getSizeInBytes()));
+		}
 
-					vert.m_boneIndices[c] = U8(submesh.m_verts[i].m_boneIds[c]);
-					vert.m_boneWeights[c] = U8(submesh.m_verts[i].m_boneWeights[c] * F32(kMaxU8));
+		if(hasBoneWeights)
+		{
+			// Bone IDs
+			for(const SubMesh& submesh : submeshes[lod])
+			{
+				DynamicArrayRaii<U8Vec4> boneids(m_pool, submesh.m_verts.getSize());
+				for(U32 v = 0; v < submesh.m_verts.getSize(); ++v)
+				{
+					boneids[v] = U8Vec4(submesh.m_verts[v].m_boneIds);
 				}
 
-				verts[i] = vert;
+				ANKI_CHECK(file.write(&boneids[0], boneids.getSizeInBytes()));
 			}
 
-			ANKI_CHECK(file.write(&verts[0], verts.getSizeInBytes()));
-		}
+			// Bone weights
+			for(const SubMesh& submesh : submeshes[lod])
+			{
+				DynamicArrayRaii<U32> boneWeights(m_pool, submesh.m_verts.getSize());
+				for(U32 v = 0; v < submesh.m_verts.getSize(); ++v)
+				{
+					boneWeights[v] = packSnorm4x8(submesh.m_verts[v].m_boneWeights);
+				}
 
-		ANKI_CHECK(alignBufferInFile(header.m_totalVertexCount * sizeof(BoneInfoVertex), file));
+				ANKI_CHECK(file.write(&boneWeights[0], boneWeights.getSizeInBytes()));
+			}
+		}
 	}
 
 	return Error::kNone;

+ 13 - 8
AnKi/Importer/ImageImporter.cpp

@@ -13,6 +13,8 @@
 
 namespace anki {
 
+static Atomic<U32> g_tempFileIndex = {1}; // Used to create random names
+
 namespace {
 
 class SurfaceOrVolumeData
@@ -322,7 +324,8 @@ static Error resizeImage(CString inImageFilename, U32 outWidth, U32 outHeight, C
 	}
 
 	// Store
-	tmpFilename.sprintf("%s/AnKiImageImporter_%u.%s", tempDirectory.cstr(), U32(std::rand()), (hdr) ? "exr" : "png");
+	tmpFilename.sprintf("%s/AnKiImageImporter_%u.%s", tempDirectory.cstr(), g_tempFileIndex.fetchAdd(1),
+						(hdr) ? "exr" : "png");
 	ANKI_IMPORTER_LOGV("Will store: %s", tmpFilename.cstr());
 
 	if(!hdr)
@@ -615,7 +618,8 @@ static Error compressS3tc(BaseMemoryPool& pool, CString tempDirectory, CString c
 
 	// Create a PNG image to feed to the compressor
 	StringRaii tmpFilename(&pool);
-	tmpFilename.sprintf("%s/AnKiImageImporter_%u.%s", tempDirectory.cstr(), U32(std::rand()), (hdr) ? "exr" : "png");
+	tmpFilename.sprintf("%s/AnKiImageImporter_%u.%s", tempDirectory.cstr(), g_tempFileIndex.fetchAdd(1),
+						(hdr) ? "exr" : "png");
 	ANKI_IMPORTER_LOGV("Will store: %s", tmpFilename.cstr());
 	Bool saveTmpImageOk = false;
 	if(!hdr)
@@ -639,7 +643,7 @@ static Error compressS3tc(BaseMemoryPool& pool, CString tempDirectory, CString c
 
 	// Invoke the compressor process
 	StringRaii ddsFilename(&pool);
-	ddsFilename.sprintf("%s/AnKiImageImporter_%u.dds", tempDirectory.cstr(), U32(std::rand()));
+	ddsFilename.sprintf("%s/AnKiImageImporter_%u.dds", tempDirectory.cstr(), g_tempFileIndex.fetchAdd(1));
 	Process proc;
 	Array<CString, 5> args;
 	U32 argCount = 0;
@@ -649,8 +653,8 @@ static Error compressS3tc(BaseMemoryPool& pool, CString tempDirectory, CString c
 	args[argCount++] = tmpFilename;
 	args[argCount++] = ddsFilename;
 
-	ANKI_IMPORTER_LOGV("Will invoke process: compressonatorcli %s %s %s %s %s", args[0].cstr(), args[1].cstr(),
-					   args[2].cstr(), args[3].cstr(), args[4].cstr());
+	ANKI_IMPORTER_LOGV("Will invoke process: %s %s %s %s %s %s", compressonatorFilename.cstr(), args[0].cstr(),
+					   args[1].cstr(), args[2].cstr(), args[3].cstr(), args[4].cstr());
 	ANKI_CHECK(proc.start(compressonatorFilename, args));
 	CleanupFile ddsCleanup(&pool, ddsFilename);
 	ProcessStatus status;
@@ -727,7 +731,8 @@ static Error compressAstc(BaseMemoryPool& pool, CString tempDirectory, CString a
 
 	// Create a BMP image to feed to the astcebc
 	StringRaii tmpFilename(&pool);
-	tmpFilename.sprintf("%s/AnKiImageImporter_%u.%s", tempDirectory.cstr(), U32(std::rand()), (hdr) ? "exr" : "png");
+	tmpFilename.sprintf("%s/AnKiImageImporter_%u.%s", tempDirectory.cstr(), g_tempFileIndex.fetchAdd(1),
+						(hdr) ? "exr" : "png");
 	ANKI_IMPORTER_LOGV("Will store: %s", tmpFilename.cstr());
 	Bool saveTmpImageOk = false;
 	if(!hdr)
@@ -751,7 +756,7 @@ static Error compressAstc(BaseMemoryPool& pool, CString tempDirectory, CString a
 
 	// Invoke the compressor process
 	StringRaii astcFilename(&pool);
-	astcFilename.sprintf("%s/AnKiImageImporter_%u.astc", tempDirectory.cstr(), U32(std::rand()));
+	astcFilename.sprintf("%s/AnKiImageImporter_%u.astc", tempDirectory.cstr(), g_tempFileIndex.fetchAdd(1));
 	StringRaii blockStr(&pool);
 	blockStr.sprintf("%ux%u", blockSize.x(), blockSize.y());
 	Process proc;
@@ -763,7 +768,7 @@ static Error compressAstc(BaseMemoryPool& pool, CString tempDirectory, CString a
 	args[argCount++] = blockStr;
 	args[argCount++] = "-fast";
 
-	ANKI_IMPORTER_LOGV("Will invoke process: astcenc-avx2 %s %s %s %s %s", args[0].cstr(), args[1].cstr(),
+	ANKI_IMPORTER_LOGV("Will invoke process: %s %s %s %s %s %s", astcencFilename.cstr(), args[0].cstr(), args[1].cstr(),
 					   args[2].cstr(), args[3].cstr(), args[4].cstr());
 	ANKI_CHECK(proc.start(astcencFilename, args));
 

+ 19 - 0
AnKi/Math/Functions.h

@@ -242,6 +242,25 @@ inline U32 packColorToR10G10B10A2SNorm(F32 r, F32 g, F32 b, F32 a)
 	return out.m_packed;
 }
 
+template<typename TVec4>
+inline U32 packSnorm4x8(const TVec4& v)
+{
+	union
+	{
+		I8 in[4];
+		U32 out;
+	} u;
+
+	const TVec4 result = (v.clamp(-1.0f, 1.0f) * 127.0f).round();
+
+	u.in[0] = I8(result[0]);
+	u.in[1] = I8(result[1]);
+	u.in[2] = I8(result[2]);
+	u.in[3] = I8(result[3]);
+
+	return u.out;
+}
+
 /// Compute the abs triangle area.
 template<typename TVec>
 inline F32 computeTriangleArea(const TVec& a, const TVec& b, const TVec& c)

+ 92 - 90
AnKi/Math/Mat.h

@@ -15,33 +15,35 @@ namespace anki {
 
 /// Matrix type.
 /// @tparam T The scalar type. Eg float.
-/// @tparam J The number of rows.
-/// @tparam I The number of columns.
-template<typename T, U J, U I>
-class alignas(MathSimd<T, I>::kAlignment) TMat
+/// @tparam kTRowCount The number of rows.
+/// @tparam kTColumnCount The number of columns.
+template<typename T, U kTRowCount, U kTColumnCount>
+class alignas(MathSimd<T, kTColumnCount>::kAlignment) TMat
 {
 public:
 	using Scalar = T;
-	using Simd = typename MathSimd<T, I>::Type;
+	using Simd = typename MathSimd<T, kTColumnCount>::Type;
 
 #if ANKI_COMPILER_GCC_COMPATIBLE
 #	pragma GCC diagnostic push
 #	pragma GCC diagnostic ignored "-Wignored-attributes"
 #endif
-	using SimdArray = Array<Simd, J>;
+	using SimdArray = Array<Simd, kTRowCount>;
 #if ANKI_COMPILER_GCC_COMPATIBLE
 #	pragma GCC diagnostic pop
 #endif
 
-	using RowVec = TVec<T, I>;
-	using ColumnVec = TVec<T, J>;
+	using RowVec = TVec<T, kTColumnCount>;
+	using ColumnVec = TVec<T, kTRowCount>;
 
-	static constexpr U kRowCount = J; ///< Number of rows
-	static constexpr U kColumnCount = I; ///< Number of columns
-	static constexpr U kSize = J * I; ///< Number of total elements
-	static constexpr Bool kHasSIMD = I == 4 && std::is_same<T, F32>::value && ANKI_ENABLE_SIMD;
-	static constexpr Bool kHasMat4SIMD = J == 4 && I == 4 && std::is_same<T, F32>::value && ANKI_ENABLE_SIMD;
-	static constexpr Bool kHasMat3x4SIMD = J == 3 && I == 4 && std::is_same<T, F32>::value && ANKI_ENABLE_SIMD;
+	static constexpr U kRowCount = kTRowCount; ///< Number of rows
+	static constexpr U kColumnCount = kTColumnCount; ///< Number of columns
+	static constexpr U kSize = kTRowCount * kTColumnCount; ///< Number of total elements
+	static constexpr Bool kHasSIMD = kTColumnCount == 4 && std::is_same<T, F32>::value && ANKI_ENABLE_SIMD;
+	static constexpr Bool kHasMat4SIMD =
+		kTRowCount == 4 && kTColumnCount == 4 && std::is_same<T, F32>::value && ANKI_ENABLE_SIMD;
+	static constexpr Bool kHasMat3x4SIMD =
+		kTRowCount == 3 && kTColumnCount == 4 && std::is_same<T, F32>::value && ANKI_ENABLE_SIMD;
 
 	/// @name Constructors
 	/// @{
@@ -76,7 +78,7 @@ public:
 
 	// 3x3 specific constructors
 
-	ANKI_ENABLE_METHOD(J == 3 && I == 3)
+	ANKI_ENABLE_METHOD(kTRowCount == 3 && kTColumnCount == 3)
 	constexpr TMat(T m00, T m01, T m02, T m10, T m11, T m12, T m20, T m21, T m22)
 	{
 		auto& m = *this;
@@ -91,19 +93,19 @@ public:
 		m(2, 2) = m22;
 	}
 
-	ANKI_ENABLE_METHOD(J == 3 && I == 3)
+	ANKI_ENABLE_METHOD(kTRowCount == 3 && kTColumnCount == 3)
 	explicit constexpr TMat(const TQuat<T>& q)
 	{
 		setRotationPart(q);
 	}
 
-	ANKI_ENABLE_METHOD(J == 3 && I == 3)
+	ANKI_ENABLE_METHOD(kTRowCount == 3 && kTColumnCount == 3)
 	explicit constexpr TMat(const TEuler<T>& e)
 	{
 		setRotationPart(e);
 	}
 
-	ANKI_ENABLE_METHOD(J == 3 && I == 3)
+	ANKI_ENABLE_METHOD(kTRowCount == 3 && kTColumnCount == 3)
 	explicit constexpr TMat(const TAxisang<T>& axisang)
 	{
 		setRotationPart(axisang);
@@ -111,7 +113,7 @@ public:
 
 	// 4x4 specific constructors
 
-	ANKI_ENABLE_METHOD(J == 4 && I == 4)
+	ANKI_ENABLE_METHOD(kTRowCount == 4 && kTColumnCount == 4)
 	constexpr TMat(T m00, T m01, T m02, T m03, T m10, T m11, T m12, T m13, T m20, T m21, T m22, T m23, T m30, T m31,
 				   T m32, T m33)
 	{
@@ -134,7 +136,7 @@ public:
 		m(3, 3) = m33;
 	}
 
-	ANKI_ENABLE_METHOD(J == 4 && I == 4)
+	ANKI_ENABLE_METHOD(kTRowCount == 4 && kTColumnCount == 4)
 	constexpr TMat(const TVec<T, 4>& translation, const TMat<T, 3, 3>& rotation, const T scale = T(1))
 	{
 		if(isZero<T>(scale - T(1)))
@@ -152,14 +154,14 @@ public:
 		m(3, 0) = m(3, 1) = m(3, 2) = T(0);
 	}
 
-	ANKI_ENABLE_METHOD(J == 4 && I == 4)
+	ANKI_ENABLE_METHOD(kTRowCount == 4 && kTColumnCount == 4)
 	explicit constexpr TMat(const TTransform<T>& t)
 		: TMat(t.getOrigin().xyz1(), t.getRotation().getRotationPart(), t.getScale())
 	{
 	}
 
 	/// Set a 4x4 matrix using a 3x4 for the first 3 rows and a vec4 for the 4rth row.
-	ANKI_ENABLE_METHOD(J == 4 && I == 4)
+	ANKI_ENABLE_METHOD(kTRowCount == 4 && kTColumnCount == 4)
 	explicit constexpr TMat(const TMat<T, 3, 4>& m3, const TVec<T, 4>& row3)
 	{
 		setRow(0, m3.getRow(0));
@@ -170,7 +172,7 @@ public:
 
 	// 3x4 specific constructors
 
-	ANKI_ENABLE_METHOD(J == 3 && I == 4)
+	ANKI_ENABLE_METHOD(kTRowCount == 3 && kTColumnCount == 4)
 	constexpr TMat(T m00, T m01, T m02, T m03, T m10, T m11, T m12, T m13, T m20, T m21, T m22, T m23)
 	{
 		auto& m = *this;
@@ -188,7 +190,7 @@ public:
 		m(2, 3) = m23;
 	}
 
-	ANKI_ENABLE_METHOD(J == 3 && I == 4)
+	ANKI_ENABLE_METHOD(kTRowCount == 3 && kTColumnCount == 4)
 	explicit constexpr TMat(const TMat<T, 4, 4>& m4)
 	{
 		auto& m = *this;
@@ -206,7 +208,7 @@ public:
 		m(2, 3) = m4(2, 3);
 	}
 
-	ANKI_ENABLE_METHOD(J == 3 && I == 4)
+	ANKI_ENABLE_METHOD(kTRowCount == 3 && kTColumnCount == 4)
 	explicit constexpr TMat(const TVec<T, 3>& translation, const TMat<T, 3, 3>& rotation, const T scale = T(1))
 	{
 		if(isZero<T>(scale - T(1)))
@@ -221,25 +223,25 @@ public:
 		setTranslationPart(translation);
 	}
 
-	ANKI_ENABLE_METHOD(J == 3 && I == 4)
+	ANKI_ENABLE_METHOD(kTRowCount == 3 && kTColumnCount == 4)
 	explicit constexpr TMat(const TVec<T, 3>& translation, const TQuat<T>& q, const T scale = T(1))
 		: TMat(translation, TMat<T, 3, 3>(q), scale)
 	{
 	}
 
-	ANKI_ENABLE_METHOD(J == 3 && I == 4)
+	ANKI_ENABLE_METHOD(kTRowCount == 3 && kTColumnCount == 4)
 	explicit constexpr TMat(const TVec<T, 3>& translation, const TEuler<T>& b, const T scale = T(1))
 		: TMat(translation, TMat<T, 3, 3>(b), scale)
 	{
 	}
 
-	ANKI_ENABLE_METHOD(J == 3 && I == 4)
+	ANKI_ENABLE_METHOD(kTRowCount == 3 && kTColumnCount == 4)
 	explicit constexpr TMat(const TVec<T, 3>& translation, const TAxisang<T>& b, const T scale = T(1))
 		: TMat(translation, TMat<T, 3, 3>(b), scale)
 	{
 	}
 
-	ANKI_ENABLE_METHOD(J == 3 && I == 4)
+	ANKI_ENABLE_METHOD(kTRowCount == 3 && kTColumnCount == 4)
 	explicit constexpr TMat(const TTransform<T>& t)
 		: TMat(t.getOrigin().xyz(), t.getRotation().getRotationPart(), t.getScale())
 	{
@@ -320,17 +322,17 @@ public:
 		return *this;
 	}
 
-	ANKI_ENABLE_METHOD(J == I && !kHasMat4SIMD)
+	ANKI_ENABLE_METHOD(kTRowCount == kTColumnCount && !kHasMat4SIMD)
 	TMat operator*(const TMat& b) const
 	{
 		TMat out;
 		const TMat& a = *this;
-		for(U j = 0; j < J; j++)
+		for(U j = 0; j < kTRowCount; j++)
 		{
-			for(U i = 0; i < I; i++)
+			for(U i = 0; i < kTColumnCount; i++)
 			{
 				out(j, i) = T(0);
-				for(U k = 0; k < I; k++)
+				for(U k = 0; k < kTColumnCount; k++)
 				{
 					out(j, i) += a(j, k) * b(k, i);
 				}
@@ -499,10 +501,10 @@ public:
 	{
 		const TMat& m = *this;
 		ColumnVec out;
-		for(U j = 0; j < J; j++)
+		for(U j = 0; j < kTRowCount; j++)
 		{
 			T sum = T(0);
-			for(U i = 0; i < I; i++)
+			for(U i = 0; i < kTColumnCount; i++)
 			{
 				sum += m(j, i) * v[i];
 			}
@@ -517,12 +519,12 @@ public:
 	{
 		ColumnVec out;
 #	if ANKI_SIMD_SSE
-		for(U i = 0; i < J; i++)
+		for(U i = 0; i < kTRowCount; i++)
 		{
 			_mm_store_ss(&out[i], _mm_dp_ps(m_simd[i], v.getSimd(), 0xF1));
 		}
 #	else
-		for(U i = 0; i < J; i++)
+		for(U i = 0; i < kTRowCount; i++)
 		{
 			out[i] = RowVec(m_simd[i]).dot(v);
 		}
@@ -546,7 +548,7 @@ public:
 		setRow(2, c);
 	}
 
-	ANKI_ENABLE_METHOD(J > 3)
+	ANKI_ENABLE_METHOD(kTRowCount > 3)
 	void setRows(const RowVec& a, const RowVec& b, const RowVec& c, const RowVec& d)
 	{
 		setRows(a, b, c);
@@ -565,7 +567,7 @@ public:
 		c = getRow(2);
 	}
 
-	ANKI_ENABLE_METHOD(J > 3)
+	ANKI_ENABLE_METHOD(kTRowCount > 3)
 	void getRows(RowVec& a, RowVec& b, RowVec& c, RowVec& d) const
 	{
 		getRows(a, b, c);
@@ -574,7 +576,7 @@ public:
 
 	void setColumn(const U i, const ColumnVec& v)
 	{
-		for(U j = 0; j < J; j++)
+		for(U j = 0; j < kTRowCount; j++)
 		{
 			m_arr2[j][i] = v[j];
 		}
@@ -587,7 +589,7 @@ public:
 		setColumn(2, c);
 	}
 
-	ANKI_ENABLE_METHOD(I > 3)
+	ANKI_ENABLE_METHOD(kTColumnCount > 3)
 	void setColumns(const ColumnVec& a, const ColumnVec& b, const ColumnVec& c, const ColumnVec& d)
 	{
 		setColumns(a, b, c);
@@ -597,7 +599,7 @@ public:
 	ColumnVec getColumn(const U i) const
 	{
 		ColumnVec out;
-		for(U j = 0; j < J; j++)
+		for(U j = 0; j < kTRowCount; j++)
 		{
 			out[j] = m_arr2[j][i];
 		}
@@ -611,7 +613,7 @@ public:
 		c = getColumn(2);
 	}
 
-	ANKI_ENABLE_METHOD(I > 3)
+	ANKI_ENABLE_METHOD(kTColumnCount > 3)
 	void getColumns(ColumnVec& a, ColumnVec& b, ColumnVec& c, ColumnVec& d) const
 	{
 		getColumns(a, b, c);
@@ -927,12 +929,12 @@ public:
 		setColumns(xAxis, yAxis, zAxis);
 	}
 
-	ANKI_ENABLE_METHOD(J == I && !kHasSIMD)
+	ANKI_ENABLE_METHOD(kTRowCount == kTColumnCount && !kHasSIMD)
 	void transpose()
 	{
-		for(U j = 0; j < J; j++)
+		for(U j = 0; j < kTRowCount; j++)
 		{
-			for(U i = j + 1; i < I; i++)
+			for(U i = j + 1; i < kTColumnCount; i++)
 			{
 				T tmp = m_arr2[j][i];
 				m_arr2[j][i] = m_arr2[i][j];
@@ -942,7 +944,7 @@ public:
 	}
 
 #if ANKI_ENABLE_SIMD
-	ANKI_ENABLE_METHOD(J == I && kHasSIMD)
+	ANKI_ENABLE_METHOD(kTRowCount == kTColumnCount && kHasSIMD)
 	void transpose()
 	{
 #	if ANKI_SIMD_SSE
@@ -971,13 +973,13 @@ public:
 		}
 	}
 
-	ANKI_ENABLE_METHOD(I == J)
+	ANKI_ENABLE_METHOD(kTColumnCount == kTRowCount)
 	TMat getTransposed() const
 	{
 		TMat out;
-		for(U j = 0; j < J; j++)
+		for(U j = 0; j < kTRowCount; j++)
 		{
-			for(U i = 0; i < I; i++)
+			for(U i = 0; i < kTColumnCount; i++)
 			{
 				out.m_arr2[i][j] = m_arr2[j][i];
 			}
@@ -985,7 +987,7 @@ public:
 		return out;
 	}
 
-	ANKI_ENABLE_METHOD(I == 3 && J == 3)
+	ANKI_ENABLE_METHOD(kTColumnCount == 3 && kTRowCount == 3)
 	T getDet() const
 	{
 		const auto& m = *this;
@@ -994,7 +996,7 @@ public:
 			   + m(0, 2) * (m(0, 1) * m(2, 1) - m(1, 1) * m(2, 0));
 	}
 
-	ANKI_ENABLE_METHOD(I == 4 && J == 4)
+	ANKI_ENABLE_METHOD(kTColumnCount == 4 && kTRowCount == 4)
 	T getDet() const
 	{
 		const auto& t = *this;
@@ -1012,7 +1014,7 @@ public:
 			   - t(0, 1) * t(1, 0) * t(2, 2) * t(3, 3) + t(0, 0) * t(1, 1) * t(2, 2) * t(3, 3);
 	}
 
-	ANKI_ENABLE_METHOD(I == 3 && J == 3)
+	ANKI_ENABLE_METHOD(kTColumnCount == 3 && kTRowCount == 3)
 	TMat getInverse() const
 	{
 		// Using Gramer's method Inv(A) = (1 / getDet(A)) * Adj(A)
@@ -1045,7 +1047,7 @@ public:
 	}
 
 	/// Invert using Cramer's rule
-	ANKI_ENABLE_METHOD(I == 4 && J == 4)
+	ANKI_ENABLE_METHOD(kTColumnCount == 4 && kTRowCount == 4)
 	TMat getInverse() const
 	{
 		Array<T, 12> tmp;
@@ -1121,14 +1123,14 @@ public:
 	}
 
 	/// See getInverse
-	ANKI_ENABLE_METHOD((I == 4 && J == 4) || (I == 3 && J == 3))
+	ANKI_ENABLE_METHOD((kTColumnCount == 4 && kTRowCount == 4) || (kTColumnCount == 3 && kTRowCount == 3))
 	void invert()
 	{
 		(*this) = getInverse();
 	}
 
 	/// 12 muls, 27 adds. Something like m4 = m0 * m1 but without touching the 4rth row and allot faster
-	ANKI_ENABLE_METHOD(I == 4 && J == 4)
+	ANKI_ENABLE_METHOD(kTColumnCount == 4 && kTRowCount == 4)
 	static TMat combineTransformations(const TMat& m0, const TMat& m1)
 	{
 		// See the clean code in < r664
@@ -1162,7 +1164,7 @@ public:
 	}
 
 	/// Create a new matrix that is equivalent to Mat4(this)*Mat4(b)
-	ANKI_ENABLE_METHOD(J == 3 && I == 4 && !kHasSIMD)
+	ANKI_ENABLE_METHOD(kTRowCount == 3 && kTColumnCount == 4 && !kHasSIMD)
 	TMat combineTransformations(const TMat& b) const
 	{
 		const auto& a = *this;
@@ -1188,7 +1190,7 @@ public:
 	}
 
 #if ANKI_ENABLE_SIMD
-	ANKI_ENABLE_METHOD(J == 3 && I == 4 && kHasSIMD)
+	ANKI_ENABLE_METHOD(kTRowCount == 3 && kTColumnCount == 4 && kHasSIMD)
 	TMat combineTransformations(const TMat& b) const
 	{
 		TMat c;
@@ -1234,7 +1236,7 @@ public:
 #endif
 
 	/// Calculate a perspective projection matrix. The z is mapped in [0, 1] range just like DX and Vulkan.
-	ANKI_ENABLE_METHOD(I == 4 && J == 4)
+	ANKI_ENABLE_METHOD(kTColumnCount == 4 && kTRowCount == 4)
 	[[nodiscard]] static TMat calculatePerspectiveProjectionMatrix(T fovX, T fovY, T near, T far)
 	{
 		ANKI_ASSERT(fovX > T(0) && fovY > T(0) && near > T(0) && far > T(0));
@@ -1263,7 +1265,7 @@ public:
 	}
 
 	/// Calculate an orthographic projection matrix. The z is mapped in [0, 1] range just like DX and Vulkan.
-	ANKI_ENABLE_METHOD(I == 4 && J == 4)
+	ANKI_ENABLE_METHOD(kTColumnCount == 4 && kTRowCount == 4)
 	[[nodiscard]] static TMat calculateOrthographicProjectionMatrix(T right, T left, T top, T bottom, T near, T far)
 	{
 		ANKI_ASSERT(right != T(0) && left != T(0) && top != T(0) && bottom != T(0) && near != T(0) && far != T(0));
@@ -1303,7 +1305,7 @@ public:
 	/// Vec2 xy = ndc.xy() * unprojParams.xy() * z;
 	/// Vec3 posViewSpace(xy, z);
 	/// @endcode
-	ANKI_ENABLE_METHOD(I == 4 && J == 4)
+	ANKI_ENABLE_METHOD(kTColumnCount == 4 && kTRowCount == 4)
 	static TVec<T, 4> calculatePerspectiveUnprojectionParams(T fovX, T fovY, T near, T far)
 	{
 		TVec<T, 4> out;
@@ -1336,7 +1338,7 @@ public:
 
 	/// Assuming this is a projection matrix extract the unprojection parameters. See
 	/// calculatePerspectiveUnprojectionParams for more info.
-	ANKI_ENABLE_METHOD(I == 4 && J == 4)
+	ANKI_ENABLE_METHOD(kTColumnCount == 4 && kTRowCount == 4)
 	TVec<T, 4> extractPerspectiveUnprojectionParams() const
 	{
 		TVec<T, 4> out;
@@ -1349,7 +1351,7 @@ public:
 	}
 
 	/// If we suppose this matrix represents a transformation, return the inverted transformation
-	ANKI_ENABLE_METHOD(I == 4 && J == 4)
+	ANKI_ENABLE_METHOD(kTColumnCount == 4 && kTRowCount == 4)
 	TMat getInverseTransformation() const
 	{
 		const TMat<T, 3, 3> invertedRot = getRotationPart().getTransposed();
@@ -1359,7 +1361,7 @@ public:
 	}
 
 	/// @note 9 muls, 9 adds
-	ANKI_ENABLE_METHOD(I == 4 && J == 4)
+	ANKI_ENABLE_METHOD(kTColumnCount == 4 && kTRowCount == 4)
 	TVec<T, 3> transform(const TVec<T, 3>& v) const
 	{
 		const auto& m = *this;
@@ -1369,7 +1371,7 @@ public:
 	}
 
 	/// Create a new transform matrix position at eye and looking at refPoint.
-	template<U VEC_DIMS, ANKI_ENABLE(J == 3 && I == 4 && VEC_DIMS >= 3)>
+	template<U VEC_DIMS, ANKI_ENABLE(kTRowCount == 3 && kTColumnCount == 4 && VEC_DIMS >= 3)>
 	static TMat lookAt(const TVec<T, VEC_DIMS>& eye, const TVec<T, VEC_DIMS>& refPoint, const TVec<T, VEC_DIMS>& up)
 	{
 		const TVec<T, 3> vdir = (refPoint.xyz() - eye.xyz()).getNormalized();
@@ -1381,7 +1383,7 @@ public:
 	}
 
 	/// Create a new transform matrix position at eye and looking at refPoint.
-	template<U VEC_DIMS, ANKI_ENABLE(J == 4 && I == 4 && VEC_DIMS >= 3)>
+	template<U VEC_DIMS, ANKI_ENABLE(kTRowCount == 4 && kTColumnCount == 4 && VEC_DIMS >= 3)>
 	static TMat lookAt(const TVec<T, VEC_DIMS>& eye, const TVec<T, VEC_DIMS>& refPoint, const TVec<T, VEC_DIMS>& up)
 	{
 		const TVec<T, 4> vdir = (refPoint.xyz0() - eye.xyz0()).getNormalized();
@@ -1407,19 +1409,19 @@ public:
 		*this = getZero();
 	}
 
-	ANKI_ENABLE_METHOD(I == 3 && J == 3)
+	ANKI_ENABLE_METHOD(kTColumnCount == 3 && kTRowCount == 3)
 	static TMat getIdentity()
 	{
 		return TMat(T(1), T(0), T(0), T(0), T(1), T(0), T(0), T(0), T(1));
 	}
 
-	ANKI_ENABLE_METHOD(I == 4 && J == 4)
+	ANKI_ENABLE_METHOD(kTColumnCount == 4 && kTRowCount == 4)
 	static TMat getIdentity()
 	{
 		return TMat(T(1), T(0), T(0), T(0), T(0), T(1), T(0), T(0), T(0), T(0), T(1), T(0), T(0), T(0), T(0), T(1));
 	}
 
-	ANKI_ENABLE_METHOD(I == 4 && J == 3)
+	ANKI_ENABLE_METHOD(kTColumnCount == 4 && kTRowCount == 3)
 	static TMat getIdentity()
 	{
 		return TMat(T(1), T(0), T(0), T(0), T(0), T(1), T(0), T(0), T(0), T(0), T(1), T(0));
@@ -1432,22 +1434,22 @@ public:
 
 	static constexpr U8 getSize()
 	{
-		return U8(I * J);
+		return U8(kTColumnCount * kTRowCount);
 	}
 
 	ANKI_ENABLE_METHOD(std::is_floating_point<T>::value)
 	void toString(StringRaii& str) const
 	{
-		for(U j = 0; j < J; ++j)
+		for(U j = 0; j < kTRowCount; ++j)
 		{
-			for(U i = 0; i < I; ++i)
+			for(U i = 0; i < kTColumnCount; ++i)
 			{
 				CString fmt;
-				if(i == I - 1 && j == J - 1)
+				if(i == kTColumnCount - 1 && j == kTRowCount - 1)
 				{
 					fmt = "%f";
 				}
-				else if(i == I - 1)
+				else if(i == kTColumnCount - 1)
 				{
 					fmt = "%f\n";
 				}
@@ -1462,35 +1464,35 @@ public:
 	/// @}
 
 protected:
-	static constexpr U N = I * J;
+	static constexpr U N = kTColumnCount * kTRowCount;
 
 	/// @name Data members
 	/// @{
 	union
 	{
 		T m_carr1[N]; ///< For easier debugging with gdb
-		T m_carr2[J][I]; ///< For easier debugging with gdb
+		T m_carr2[kTRowCount][kTColumnCount]; ///< For easier debugging with gdb
 		Array<T, N> m_arr1;
-		Array2d<T, J, I> m_arr2;
+		Array2d<T, kTRowCount, kTColumnCount> m_arr2;
 		SimdArray m_simd;
-		Array<RowVec, J> m_rows;
+		Array<RowVec, kTRowCount> m_rows;
 	};
 	/// @}
 };
 
 /// @memberof TMat
-template<typename T, U J, U I>
-TMat<T, J, I> operator+(const T f, const TMat<T, J, I>& m)
+template<typename T, U kTRowCount, U kTColumnCount>
+TMat<T, kTRowCount, kTColumnCount> operator+(const T f, const TMat<T, kTRowCount, kTColumnCount>& m)
 {
 	return m + f;
 }
 
 /// @memberof TMat
-template<typename T, U J, U I>
-TMat<T, J, I> operator-(const T f, const TMat<T, J, I>& m)
+template<typename T, U kTRowCount, U kTColumnCount>
+TMat<T, kTRowCount, kTColumnCount> operator-(const T f, const TMat<T, kTRowCount, kTColumnCount>& m)
 {
-	TMat<T, J, I> out;
-	for(U i = 0; i < J * I; i++)
+	TMat<T, kTRowCount, kTColumnCount> out;
+	for(U i = 0; i < kTRowCount * kTColumnCount; i++)
 	{
 		out[i] = f - m[i];
 	}
@@ -1498,18 +1500,18 @@ TMat<T, J, I> operator-(const T f, const TMat<T, J, I>& m)
 }
 
 /// @memberof TMat
-template<typename T, U J, U I>
-TMat<T, J, I> operator*(const T f, const TMat<T, J, I>& m)
+template<typename T, U kTRowCount, U kTColumnCount>
+TMat<T, kTRowCount, kTColumnCount> operator*(const T f, const TMat<T, kTRowCount, kTColumnCount>& m)
 {
 	return m * f;
 }
 
 /// @memberof TMat
-template<typename T, U J, U I>
-TMat<T, J, I> operator/(const T f, const TMat<T, 3, 3>& m3)
+template<typename T, U kTRowCount, U kTColumnCount>
+TMat<T, kTRowCount, kTColumnCount> operator/(const T f, const TMat<T, 3, 3>& m3)
 {
-	TMat<T, J, I> out;
-	for(U i = 0; i < J * I; i++)
+	TMat<T, kTRowCount, kTColumnCount> out;
+	for(U i = 0; i < kTRowCount * kTColumnCount; i++)
 	{
 		ANKI_ASSERT(m3[i] != T(0));
 		out[i] = f / m3[i];

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 366 - 366
AnKi/Math/Vec.h


+ 5 - 3
AnKi/Renderer/ClusterBinning.cpp

@@ -459,11 +459,13 @@ void ClusterBinning::writeClustererBuffersTask()
 			const DirectionalLightQueueElement& in = rqueue.m_directionalLight;
 
 			out.m_diffuseColor = in.m_diffuseColor;
-			out.m_cascadeCount = in.m_shadowCascadeCount;
+			out.m_shadowCascadeCount = in.m_shadowCascadeCount;
 			out.m_direction = in.m_direction;
 			out.m_active = 1;
-			out.m_effectiveShadowDistance = in.m_effectiveShadowDistance;
-			out.m_shadowCascadesDistancePower = in.m_shadowCascadesDistancePower;
+			for(U32 i = 0; i < kMaxShadowCascades; ++i)
+			{
+				out.m_shadowCascadeDistances[i] = in.m_shadowCascadesDistances[i];
+			}
 			out.m_shadowLayer = (in.hasShadow()) ? in.m_shadowLayer : kMaxU32;
 
 			for(U cascade = 0; cascade < in.m_shadowCascadeCount; ++cascade)

+ 3 - 5
AnKi/Renderer/ConfigVars.defs.h

@@ -59,13 +59,11 @@ ANKI_CONFIG_VAR_F32(RIndirectDiffuseVrsDistanceThreshold, 0.01f, 0.00001f, 10.0f
 					"The meters that control the VRS SRI generation")
 
 // Shadows
-ANKI_CONFIG_VAR_U32(RShadowMappingTileResolution, ((ANKI_PLATFORM_MOBILE) ? 128 : 512), 16, 2048,
+ANKI_CONFIG_VAR_U32(RShadowMappingTileResolution, ((ANKI_PLATFORM_MOBILE) ? 64 : 256), 16, 2048,
 					"Shadowmapping tile resolution")
-ANKI_CONFIG_VAR_U32(RShadowMappingTileCountPerRowOrColumn, 16, 1, 256,
+ANKI_CONFIG_VAR_U32(RShadowMappingTileCountPerRowOrColumn, 32, 1, 256,
 					"Shadowmapping atlas will have this number squared number of tiles")
-ANKI_CONFIG_VAR_U32(RShadowMappingScratchTileCountX, 4 * (kMaxShadowCascades + 2), 1, 256,
-					"Number of tiles of the scratch buffer in X")
-ANKI_CONFIG_VAR_U32(RShadowMappingScratchTileCountY, 4, 1, 256, "Number of tiles of the scratch buffer in Y")
+ANKI_CONFIG_VAR_U32(RShadowMappingPcf, ((ANKI_PLATFORM_MOBILE) ? 0 : 1), 0, 1, "Shadow PCF (0: off, 1: on)")
 
 // Probe reflections
 ANKI_CONFIG_VAR_U32(RProbeReflectionResolution, 128, 4, 2048, "Reflection probe face resolution")

+ 1 - 0
AnKi/Renderer/ForwardShading.cpp

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

+ 1 - 1
AnKi/Renderer/IndirectDiffuseProbes.cpp

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

+ 1 - 1
AnKi/Renderer/ProbeReflections.cpp

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

+ 1 - 3
AnKi/Renderer/RenderQueue.h

@@ -170,8 +170,7 @@ public:
 	U64 m_uuid; ///< Zero means that there is no dir light
 	Vec3 m_diffuseColor;
 	Vec3 m_direction;
-	F32 m_effectiveShadowDistance;
-	F32 m_shadowCascadesDistancePower;
+	Array<F32, kMaxShadowCascades> m_shadowCascadesDistances;
 	U8 m_shadowCascadeCount; ///< Zero means that it doesn't cast any shadows.
 	U8 m_shadowLayer; ///< Renderer internal.
 
@@ -425,7 +424,6 @@ public:
 	F32 m_cameraFar;
 	F32 m_cameraFovX;
 	F32 m_cameraFovY;
-	F32 m_effectiveShadowDistance;
 
 	FillCoverageBufferCallback m_fillCoverageBufferCallback = nullptr;
 	void* m_fillCoverageBufferCallbackUserData = nullptr;

+ 6 - 0
AnKi/Renderer/Renderer.cpp

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

+ 1 - 0
AnKi/Renderer/Renderer.h

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

+ 181 - 435
AnKi/Renderer/ShadowMapping.cpp

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

+ 34 - 76
AnKi/Renderer/ShadowMapping.h

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

+ 13 - 9
AnKi/Renderer/ShadowmapsResolve.cpp

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

+ 3 - 2
AnKi/Renderer/ShadowmapsResolve.h

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

+ 56 - 49
AnKi/Renderer/TileAllocator.cpp

@@ -10,14 +10,14 @@ namespace anki {
 class TileAllocator::Tile
 {
 public:
-	Timestamp m_lightTimestamp = 0; ///< The last timestamp the light got updated
-	Timestamp m_lastUsedTimestamp = 0; ///< The last timestamp this tile was used
+	Timestamp m_lightTimestamp = 0; ///< The last timestamp the light got updated.
+	Timestamp m_lastUsedTimestamp = 0; ///< The last timestamp this tile was used.
 	U64 m_lightUuid = 0;
 	U32 m_lightDrawcallCount = 0;
 	Array<U32, 4> m_viewport = {};
 	Array<U32, 4> m_subTiles = {kMaxU32, kMaxU32, kMaxU32, kMaxU32};
-	U32 m_superTile = kMaxU32;
-	U8 m_lightLod = 0;
+	U32 m_superTile = kMaxU32; ///< The parent.
+	U8 m_lightHierarchy = 0;
 	U8 m_lightFace = 0;
 };
 
@@ -37,68 +37,71 @@ TileAllocator::~TileAllocator()
 {
 	m_lightInfoToTileIdx.destroy(*m_pool);
 	m_allTiles.destroy(*m_pool);
-	m_lodFirstTileIndex.destroy(*m_pool);
+	m_firstTileIdxOfHierarchy.destroy(*m_pool);
 }
 
-void TileAllocator::init(HeapMemoryPool* pool, U32 tileCountX, U32 tileCountY, U32 lodCount, Bool enableCaching)
+void TileAllocator::init(HeapMemoryPool* pool, U32 tileCountX, U32 tileCountY, U32 hierarchyCount, Bool enableCaching)
 {
 	// Preconditions
 	ANKI_ASSERT(tileCountX > 0);
 	ANKI_ASSERT(tileCountY > 0);
-	ANKI_ASSERT(lodCount > 0);
+	ANKI_ASSERT(hierarchyCount > 0);
 
 	// Store some stuff
 	m_tileCountX = U16(tileCountX);
 	m_tileCountY = U16(tileCountY);
-	m_lodCount = U8(lodCount);
+	m_hierarchyCount = U8(hierarchyCount);
 	m_pool = pool;
 	m_cachingEnabled = enableCaching;
-	m_lodFirstTileIndex.create(*m_pool, lodCount + 1);
+	m_firstTileIdxOfHierarchy.create(*m_pool, hierarchyCount + 1);
 
 	// Create the tile array & index ranges
 	U32 tileCount = 0;
-	for(U32 lod = 0; lod < lodCount; ++lod)
+	for(U32 hierarchy = 0; hierarchy < hierarchyCount; ++hierarchy)
 	{
-		const U32 lodTileCountX = tileCountX >> lod;
-		const U32 lodTileCountY = tileCountY >> lod;
-		ANKI_ASSERT((lodTileCountX << lod) == tileCountX && "Every LOD should be power of 2 of its parent LOD");
-		ANKI_ASSERT((lodTileCountY << lod) == tileCountY && "Every LOD should be power of 2 of its parent LOD");
+		const U32 hierarchyTileCountX = tileCountX >> hierarchy;
+		const U32 hierarchyTileCountY = tileCountY >> hierarchy;
+		ANKI_ASSERT((hierarchyTileCountX << hierarchy) == tileCountX
+					&& "Every hierarchy should be power of 2 of its parent hierarchy");
+		ANKI_ASSERT((hierarchyTileCountY << hierarchy) == tileCountY
+					&& "Every hierarchy should be power of 2 of its parent hierarchy");
 
-		m_lodFirstTileIndex[lod] = tileCount;
+		m_firstTileIdxOfHierarchy[hierarchy] = tileCount;
 
-		tileCount += lodTileCountX * lodTileCountY;
+		tileCount += hierarchyTileCountX * hierarchyTileCountY;
 	}
 	ANKI_ASSERT(tileCount >= tileCountX * tileCountY);
 	m_allTiles.create(*m_pool, tileCount);
-	m_lodFirstTileIndex[lodCount] = tileCount - 1;
+	m_firstTileIdxOfHierarchy[hierarchyCount] = tileCount - 1;
 
 	// Init the tiles
 	U32 tileIdx = 0;
-	for(U32 lod = 0; lod < lodCount; ++lod)
+	for(U32 hierarchy = 0; hierarchy < hierarchyCount; ++hierarchy)
 	{
-		const U32 lodTileCountX = tileCountX >> lod;
-		const U32 lodTileCountY = tileCountY >> lod;
+		const U32 hierarchyTileCountX = tileCountX >> hierarchy;
+		const U32 hierarchyTileCountY = tileCountY >> hierarchy;
 
-		for(U32 y = 0; y < lodTileCountY; ++y)
+		for(U32 y = 0; y < hierarchyTileCountY; ++y)
 		{
-			for(U32 x = 0; x < lodTileCountX; ++x)
+			for(U32 x = 0; x < hierarchyTileCountX; ++x)
 			{
-				ANKI_ASSERT(tileIdx >= m_lodFirstTileIndex[lod] && tileIdx <= m_lodFirstTileIndex[lod + 1]);
+				ANKI_ASSERT(tileIdx >= m_firstTileIdxOfHierarchy[hierarchy]
+							&& tileIdx <= m_firstTileIdxOfHierarchy[hierarchy + 1]);
 				Tile& tile = m_allTiles[tileIdx];
 
-				tile.m_viewport[0] = x << lod;
-				tile.m_viewport[1] = y << lod;
-				tile.m_viewport[2] = 1 << lod;
-				tile.m_viewport[3] = 1 << lod;
+				tile.m_viewport[0] = x << hierarchy;
+				tile.m_viewport[1] = y << hierarchy;
+				tile.m_viewport[2] = 1 << hierarchy;
+				tile.m_viewport[3] = 1 << hierarchy;
 
-				if(lod > 0)
+				if(hierarchy > 0)
 				{
 					// Has sub tiles
 					for(U32 j = 0; j < 2; ++j)
 					{
 						for(U32 i = 0; i < 2; ++i)
 						{
-							const U32 subTileIdx = translateTileIdx((x << 1) + i, (y << 1) + j, lod - 1);
+							const U32 subTileIdx = translateTileIdx((x << 1) + i, (y << 1) + j, hierarchy - 1);
 							m_allTiles[subTileIdx].m_superTile = tileIdx;
 
 							tile.m_subTiles[j * 2 + i] = subTileIdx;
@@ -129,7 +132,7 @@ void TileAllocator::updateSubTiles(const Tile& updateFrom)
 		m_allTiles[idx].m_lastUsedTimestamp = updateFrom.m_lastUsedTimestamp;
 		m_allTiles[idx].m_lightUuid = updateFrom.m_lightUuid;
 		m_allTiles[idx].m_lightDrawcallCount = updateFrom.m_lightDrawcallCount;
-		m_allTiles[idx].m_lightLod = updateFrom.m_lightLod;
+		m_allTiles[idx].m_lightHierarchy = updateFrom.m_lightHierarchy;
 		m_allTiles[idx].m_lightFace = updateFrom.m_lightFace;
 
 		updateSubTiles(m_allTiles[idx]);
@@ -146,13 +149,13 @@ void TileAllocator::updateSuperTiles(const Tile& updateFrom)
 	}
 }
 
-Bool TileAllocator::searchTileRecursively(U32 crntTileIdx, U32 crntTileLod, U32 allocationLod, Timestamp crntTimestamp,
-										  U32& emptyTileIdx, U32& toKickTileIdx,
+Bool TileAllocator::searchTileRecursively(U32 crntTileIdx, U32 crntTileHierarchy, U32 allocationHierarchy,
+										  Timestamp crntTimestamp, U32& emptyTileIdx, U32& toKickTileIdx,
 										  Timestamp& tileToKickMinTimestamp) const
 {
 	const Tile& tile = m_allTiles[crntTileIdx];
 
-	if(crntTileLod == allocationLod)
+	if(crntTileHierarchy == allocationHierarchy)
 	{
 		// We may have a candidate
 
@@ -168,12 +171,12 @@ Bool TileAllocator::searchTileRecursively(U32 crntTileIdx, U32 crntTileLod, U32
 	{
 		// Move down the hierarchy
 
-		ANKI_ASSERT(allocationLod < crntTileLod);
+		ANKI_ASSERT(allocationHierarchy < crntTileHierarchy);
 
 		for(const U32 idx : tile.m_subTiles)
 		{
-			const Bool done = searchTileRecursively(idx, crntTileLod >> 1, allocationLod, crntTimestamp, emptyTileIdx,
-													toKickTileIdx, tileToKickMinTimestamp);
+			const Bool done = searchTileRecursively(idx, crntTileHierarchy - 1, allocationHierarchy, crntTimestamp,
+													emptyTileIdx, toKickTileIdx, tileToKickMinTimestamp);
 
 			if(done)
 			{
@@ -218,7 +221,8 @@ Bool TileAllocator::evaluateCandidate(U32 tileIdx, Timestamp crntTimestamp, U32&
 }
 
 TileAllocatorResult TileAllocator::allocate(Timestamp crntTimestamp, Timestamp lightTimestamp, U64 lightUuid,
-											U32 lightFace, U32 drawcallCount, U32 lod, Array<U32, 4>& tileViewport)
+											U32 lightFace, U32 drawcallCount, U32 hierarchy,
+											Array<U32, 4>& tileViewport)
 {
 	// Preconditions
 	ANKI_ASSERT(crntTimestamp > 0);
@@ -226,7 +230,7 @@ TileAllocatorResult TileAllocator::allocate(Timestamp crntTimestamp, Timestamp l
 	ANKI_ASSERT(lightTimestamp <= crntTimestamp);
 	ANKI_ASSERT(lightUuid != 0);
 	ANKI_ASSERT(lightFace < 6);
-	ANKI_ASSERT(lod < m_lodCount);
+	ANKI_ASSERT(hierarchy < m_hierarchyCount);
 
 	// 1) Search if it's already cached
 	HashMapKey key;
@@ -239,19 +243,20 @@ TileAllocatorResult TileAllocator::allocate(Timestamp crntTimestamp, Timestamp l
 		{
 			Tile& tile = m_allTiles[*it];
 
-			if(tile.m_lightUuid != lightUuid || tile.m_lightLod != lod || tile.m_lightFace != lightFace)
+			if(tile.m_lightUuid != lightUuid || tile.m_lightHierarchy != hierarchy || tile.m_lightFace != lightFace)
 			{
 				// Cache entry is wrong, remove it
 				m_lightInfoToTileIdx.erase(*m_pool, it);
 			}
 			else
 			{
-				// Same light & lod & face, found the cache entry.
+				// Same light & hierarchy & face, found the cache entry.
 
 				ANKI_ASSERT(tile.m_lastUsedTimestamp != crntTimestamp
 							&& "Trying to allocate the same thing twice in this timestamp?");
 
-				ANKI_ASSERT(tile.m_lightUuid == lightUuid && tile.m_lightLod == lod && tile.m_lightFace == lightFace);
+				ANKI_ASSERT(tile.m_lightUuid == lightUuid && tile.m_lightHierarchy == hierarchy
+							&& tile.m_lightFace == lightFace);
 
 				tileViewport = {tile.m_viewport[0], tile.m_viewport[1], tile.m_viewport[2], tile.m_viewport[3]};
 
@@ -274,12 +279,13 @@ TileAllocatorResult TileAllocator::allocate(Timestamp crntTimestamp, Timestamp l
 	U32 emptyTileIdx = kMaxU32;
 	U32 toKickTileIdx = kMaxU32;
 	Timestamp tileToKickMinTimestamp = kMaxTimestamp;
-	const U32 maxLod = m_lodCount - 1;
-	if(lod == maxLod)
+	const U32 maxHierarchy = m_hierarchyCount - 1;
+	if(hierarchy == maxHierarchy)
 	{
-		// This search is simple, iterate the tiles of the max LOD
+		// This search is simple, iterate the tiles of the max hierarchy
 
-		for(U32 tileIdx = m_lodFirstTileIndex[maxLod]; tileIdx <= m_lodFirstTileIndex[maxLod + 1]; ++tileIdx)
+		for(U32 tileIdx = m_firstTileIdxOfHierarchy[maxHierarchy];
+			tileIdx <= m_firstTileIdxOfHierarchy[maxHierarchy + 1]; ++tileIdx)
 		{
 			const Bool done =
 				evaluateCandidate(tileIdx, crntTimestamp, emptyTileIdx, toKickTileIdx, tileToKickMinTimestamp);
@@ -294,10 +300,11 @@ TileAllocatorResult TileAllocator::allocate(Timestamp crntTimestamp, Timestamp l
 	{
 		// Need to do a recursive search
 
-		for(U32 tileIdx = m_lodFirstTileIndex[maxLod]; tileIdx <= m_lodFirstTileIndex[maxLod + 1]; ++tileIdx)
+		for(U32 tileIdx = m_firstTileIdxOfHierarchy[maxHierarchy];
+			tileIdx <= m_firstTileIdxOfHierarchy[maxHierarchy + 1]; ++tileIdx)
 		{
-			const Bool done = searchTileRecursively(tileIdx, maxLod, lod, crntTimestamp, emptyTileIdx, toKickTileIdx,
-													tileToKickMinTimestamp);
+			const Bool done = searchTileRecursively(tileIdx, maxHierarchy, hierarchy, crntTimestamp, emptyTileIdx,
+													toKickTileIdx, tileToKickMinTimestamp);
 
 			if(done)
 			{
@@ -329,7 +336,7 @@ TileAllocatorResult TileAllocator::allocate(Timestamp crntTimestamp, Timestamp l
 	allocatedTile.m_lastUsedTimestamp = crntTimestamp;
 	allocatedTile.m_lightUuid = lightUuid;
 	allocatedTile.m_lightDrawcallCount = drawcallCount;
-	allocatedTile.m_lightLod = U8(lod);
+	allocatedTile.m_lightHierarchy = U8(hierarchy);
 	allocatedTile.m_lightFace = U8(lightFace);
 
 	updateTileHierarchy(allocatedTile);

+ 12 - 10
AnKi/Renderer/TileAllocator.h

@@ -33,11 +33,13 @@ public:
 	TileAllocator& operator=(const TileAllocator&) = delete; // Non-copyable
 
 	/// Initialize the allocator.
-	void init(HeapMemoryPool* pool, U32 tileCountX, U32 tileCountY, U32 lodCount, Bool enableCaching);
+	void init(HeapMemoryPool* pool, U32 tileCountX, U32 tileCountY, U32 hierarchyCount, Bool enableCaching);
 
 	/// Allocate some tiles.
+	/// @param hierarchy If it's 0 it chooses the smallest tile.
 	[[nodiscard]] TileAllocatorResult allocate(Timestamp crntTimestamp, Timestamp lightTimestamp, U64 lightUuid,
-											   U32 lightFace, U32 drawcallCount, U32 lod, Array<U32, 4>& tileViewport);
+											   U32 lightFace, U32 drawcallCount, U32 hierarchy,
+											   Array<U32, 4>& tileViewport);
 
 	/// Remove an light from the cache.
 	void invalidateCache(U64 lightUuid, U32 lightFace);
@@ -50,19 +52,19 @@ private:
 
 	HeapMemoryPool* m_pool = nullptr;
 	DynamicArray<Tile> m_allTiles;
-	DynamicArray<U32> m_lodFirstTileIndex;
+	DynamicArray<U32> m_firstTileIdxOfHierarchy;
 
 	HashMap<HashMapKey, U32> m_lightInfoToTileIdx;
 
-	U16 m_tileCountX = 0; ///< Tile count for LOD 0
-	U16 m_tileCountY = 0; ///< Tile count for LOD 0
-	U8 m_lodCount = 0;
+	U16 m_tileCountX = 0; ///< Tile count for hierarchy 0
+	U16 m_tileCountY = 0; ///< Tile count for hierarchy 0
+	U8 m_hierarchyCount = 0;
 	Bool m_cachingEnabled = false;
 
-	U32 translateTileIdx(U32 x, U32 y, U32 lod) const
+	U32 translateTileIdx(U32 x, U32 y, U32 hierarchy) const
 	{
-		const U32 lodWidth = m_tileCountX >> lod;
-		const U32 idx = y * lodWidth + x + m_lodFirstTileIndex[lod];
+		const U32 hierarchyWidth = m_tileCountX >> hierarchy;
+		const U32 idx = y * hierarchyWidth + x + m_firstTileIdxOfHierarchy[hierarchy];
 		ANKI_ASSERT(idx < m_allTiles.getSize());
 		return idx;
 	}
@@ -79,7 +81,7 @@ private:
 	}
 
 	/// Search for a tile recursively.
-	Bool searchTileRecursively(U32 crntTileIdx, U32 crntTileLod, U32 allocationLod, Timestamp crntTimestamp,
+	Bool searchTileRecursively(U32 crntTileIdx, U32 crntTileHierarchy, U32 allocationHierarchy, Timestamp crntTimestamp,
 							   U32& emptyTileIdx, U32& toKickTileIdx, Timestamp& tileToKickMinTimestamp) const;
 
 	Bool evaluateCandidate(U32 tileIdx, Timestamp crntTimestamp, U32& emptyTileIdx, U32& toKickTileIdx,

+ 111 - 22
AnKi/Renderer/TraditionalDeferredShading.cpp

@@ -12,6 +12,50 @@
 
 namespace anki {
 
+// Vertices of 2 shapes
+// - Use the blend file in EngineAssets to output in .obj and then copy paste here
+// - The sphere goes from -1 to 1
+// - The cone's length is 1. Its big side goes from -1 to 1. The point of the cone is in 0,0,0. The direction is in +z
+
+inline constexpr F32 kIcosphereVertices[] = {
+	0.000000f,  -1.067367f, 0.000000f,  0.772355f,  -0.477347f, 0.561142f,  -0.295008f, -0.477348f, 0.907955f,
+	-0.954681f, -0.477343f, 0.000000f,  -0.295008f, -0.477348f, -0.907955f, 0.772355f,  -0.477347f, -0.561142f,
+	0.295008f,  0.477348f,  0.907955f,  -0.772355f, 0.477347f,  0.561142f,  -0.772355f, 0.477347f,  -0.561142f,
+	0.295008f,  0.477348f,  -0.907955f, 0.954681f,  0.477343f,  0.000000f,  0.000000f,  1.067367f,  0.000000f,
+	-0.173400f, -0.907961f, 0.533679f,  0.453976f,  -0.907960f, 0.329829f,  0.280578f,  -0.561155f, 0.863513f,
+	0.907954f,  -0.561153f, 0.000000f,  0.453976f,  -0.907960f, -0.329829f, -0.561147f, -0.907958f, 0.000000f,
+	-0.734551f, -0.561154f, 0.533680f,  -0.173400f, -0.907961f, -0.533679f, -0.734551f, -0.561154f, -0.533680f,
+	0.280578f,  -0.561155f, -0.863513f, 1.015128f,  0.000000f,  0.329830f,  1.015128f,  0.000000f,  -0.329830f,
+	0.000000f,  0.000000f,  1.067367f,  0.627383f,  0.000000f,  0.863518f,  -1.015128f, 0.000000f,  0.329830f,
+	-0.627383f, 0.000000f,  0.863518f,  -0.627383f, 0.000000f,  -0.863518f, -1.015128f, 0.000000f,  -0.329830f,
+	0.627383f,  0.000000f,  -0.863518f, 0.000000f,  0.000000f,  -1.067367f, 0.734551f,  0.561154f,  0.533680f,
+	-0.280578f, 0.561155f,  0.863513f,  -0.907954f, 0.561153f,  0.000000f,  -0.280578f, 0.561155f,  -0.863513f,
+	0.734551f,  0.561154f,  -0.533680f, 0.173400f,  0.907961f,  0.533679f,  0.561147f,  0.907958f,  0.000000f,
+	-0.453976f, 0.907960f,  0.329829f,  -0.453976f, 0.907960f,  -0.329829f, 0.173400f,  0.907961f,  -0.533679f};
+
+inline constexpr U16 kIconsphereIndices[] = {
+	0,  13, 12, 1,  13, 15, 0,  12, 17, 0,  17, 19, 0,  19, 16, 1,  15, 22, 2,  14, 24, 3,  18, 26, 4,  20, 28,
+	5,  21, 30, 1,  22, 25, 2,  24, 27, 3,  26, 29, 4,  28, 31, 5,  30, 23, 6,  32, 37, 7,  33, 39, 8,  34, 40,
+	9,  35, 41, 10, 36, 38, 38, 41, 11, 38, 36, 41, 36, 9,  41, 41, 40, 11, 41, 35, 40, 35, 8,  40, 40, 39, 11,
+	40, 34, 39, 34, 7,  39, 39, 37, 11, 39, 33, 37, 33, 6,  37, 37, 38, 11, 37, 32, 38, 32, 10, 38, 23, 36, 10,
+	23, 30, 36, 30, 9,  36, 31, 35, 9,  31, 28, 35, 28, 8,  35, 29, 34, 8,  29, 26, 34, 26, 7,  34, 27, 33, 7,
+	27, 24, 33, 24, 6,  33, 25, 32, 6,  25, 22, 32, 22, 10, 32, 30, 31, 9,  30, 21, 31, 21, 4,  31, 28, 29, 8,
+	28, 20, 29, 20, 3,  29, 26, 27, 7,  26, 18, 27, 18, 2,  27, 24, 25, 6,  24, 14, 25, 14, 1,  25, 22, 23, 10,
+	22, 15, 23, 15, 5,  23, 16, 21, 5,  16, 19, 21, 19, 4,  21, 19, 20, 4,  19, 17, 20, 17, 3,  20, 17, 18, 3,
+	17, 12, 18, 12, 2,  18, 15, 16, 5,  15, 13, 16, 13, 0,  16, 12, 14, 2,  12, 13, 14, 13, 1,  14};
+
+inline constexpr F32 kConeVertices[] = {
+	0.000000f,  1.000000f,  -1.000000f, 0.000000f,  0.000000f,  0.000000f,  0.500000f,  0.866026f,  -1.000000f,
+	0.866025f,  0.500000f,  -1.000000f, 1.000000f,  0.000000f,  -1.000000f, 0.866025f,  -0.500000f, -1.000000f,
+	0.500000f,  -0.866025f, -1.000000f, 0.000000f,  -1.000000f, -1.000000f, -0.500000f, -0.866025f, -1.000000f,
+	-0.866025f, -0.500000f, -1.000000f, -1.000000f, -0.000000f, -1.000000f, -0.866026f, 0.500000f,  -1.000000f,
+	-0.500001f, 0.866025f,  -1.000000f, 0.000000f,  0.000000f,  -1.000000f};
+
+inline constexpr U16 kConeIndices[] = {0, 1,  2, 2,  1,  3,  3,  1,  4,  4,  1,  5,  5,  1,  6,  6,  1,  7,
+									   7, 1,  8, 8,  1,  9,  9,  1,  10, 10, 1,  11, 11, 1,  12, 12, 1,  0,
+									   2, 13, 0, 0,  13, 12, 3,  13, 2,  4,  13, 3,  5,  13, 4,  6,  13, 5,
+									   7, 13, 6, 12, 13, 11, 11, 13, 10, 10, 13, 9,  9,  13, 8,  7,  8,  13};
+
 TraditionalDeferredLightShading::TraditionalDeferredLightShading(Renderer* r)
 	: RendererObject(r)
 {
@@ -48,9 +92,7 @@ Error TraditionalDeferredLightShading::init()
 		}
 	}
 
-	// Init meshes
-	ANKI_CHECK(getResourceManager().loadResource("EngineAssets/Plight.ankimesh", m_plightMesh, false));
-	ANKI_CHECK(getResourceManager().loadResource("EngineAssets/Slight.ankimesh", m_slightMesh, false));
+	createProxyMeshes();
 
 	// Shadow sampler
 	{
@@ -80,29 +122,76 @@ Error TraditionalDeferredLightShading::init()
 	return Error::kNone;
 }
 
-void TraditionalDeferredLightShading::bindVertexIndexBuffers(MeshResourcePtr& mesh, CommandBufferPtr& cmdb,
-															 U32& indexCount)
+void TraditionalDeferredLightShading::createProxyMeshes()
 {
-	// Attrib
-	U32 bufferBinding;
-	Format fmt;
-	U32 relativeOffset;
-	mesh->getVertexAttributeInfo(VertexAttributeId::kPosition, bufferBinding, fmt, relativeOffset);
+	constexpr PtrSize bufferSize =
+		sizeof(kIcosphereVertices) + sizeof(kIcosphereVertices) + sizeof(kConeVertices) + sizeof(kConeIndices);
 
-	cmdb->setVertexAttribute(0, 0, fmt, relativeOffset);
+	BufferInitInfo buffInit("DeferredShadingPoxies");
+	buffInit.m_size = bufferSize;
+	buffInit.m_usage = BufferUsageBit::kTransferDestination | BufferUsageBit::kIndex | BufferUsageBit::kVertex;
 
-	// Vert buff
-	BufferPtr buff;
-	PtrSize offset, stride;
-	mesh->getVertexBufferInfo(bufferBinding, buff, offset, stride);
+	m_proxyVolumesBuffer = getGrManager().newBuffer(buffInit);
+
+	buffInit.setName("TempTransfer");
+	buffInit.m_usage = BufferUsageBit::kTransferSource;
+	buffInit.m_mapAccess = BufferMapAccessBit::kWrite;
+	BufferPtr transferBuffer = getGrManager().newBuffer(buffInit);
+
+	U8* mappedMem = static_cast<U8*>(transferBuffer->map(0, kMaxPtrSize, BufferMapAccessBit::kWrite));
+
+	// Write verts
+	memcpy(mappedMem, kIcosphereVertices, sizeof(kIcosphereVertices));
+	mappedMem += sizeof(kIcosphereVertices);
+	memcpy(mappedMem, kConeVertices, sizeof(kConeVertices));
+	mappedMem += sizeof(kConeVertices);
+	memcpy(mappedMem, kIconsphereIndices, sizeof(kIconsphereIndices));
+	mappedMem += sizeof(kIconsphereIndices);
+	memcpy(mappedMem, kConeIndices, sizeof(kConeIndices));
 
-	cmdb->bindVertexBuffer(0, buff, offset, stride);
+	transferBuffer->unmap();
+
+	CommandBufferInitInfo cmdbInit("Temp");
+	cmdbInit.m_flags = CommandBufferFlag::kSmallBatch | CommandBufferFlag::kGeneralWork;
+
+	CommandBufferPtr cmdb = getGrManager().newCommandBuffer(cmdbInit);
+	cmdb->copyBufferToBuffer(transferBuffer, 0, m_proxyVolumesBuffer, 0, bufferSize);
+	cmdb->flush();
+
+	getGrManager().finish();
+}
+
+void TraditionalDeferredLightShading::bindVertexIndexBuffers(ProxyType proxyType, CommandBufferPtr& cmdb,
+															 U32& indexCount) const
+{
+	// Attrib
+	cmdb->setVertexAttribute(0, 0, Format::kR32G32B32_Sfloat, 0);
+
+	// Vert buff
+	PtrSize offset;
+	if(proxyType == ProxyType::kProxySphere)
+	{
+		offset = 0;
+	}
+	else
+	{
+		offset = sizeof(kIcosphereVertices);
+	}
+	cmdb->bindVertexBuffer(0, m_proxyVolumesBuffer, offset, sizeof(Vec3));
 
 	// Idx buff
-	IndexType idxType;
-	mesh->getIndexBufferInfo(buff, offset, indexCount, idxType);
+	if(proxyType == ProxyType::kProxySphere)
+	{
+		offset = sizeof(kIcosphereVertices) + sizeof(kConeVertices);
+		indexCount = sizeof(kIconsphereIndices) / sizeof(U16);
+	}
+	else
+	{
+		offset = sizeof(kIcosphereVertices) + sizeof(kConeVertices) + sizeof(kIconsphereIndices);
+		indexCount = sizeof(kConeIndices) / sizeof(U16);
+	}
 
-	cmdb->bindIndexBuffer(buff, offset, idxType);
+	cmdb->bindIndexBuffer(m_proxyVolumesBuffer, offset, IndexType::kU16);
 }
 
 void TraditionalDeferredLightShading::drawLights(TraditionalDeferredLightShadingDrawInfo& info)
@@ -198,7 +287,7 @@ void TraditionalDeferredLightShading::drawLights(TraditionalDeferredLightShading
 		if(info.m_directionalLight->m_shadowCascadeCount > 0)
 		{
 			unis->m_effectiveShadowDistance =
-				info.m_directionalLight->m_shadowRenderQueues[0]->m_effectiveShadowDistance;
+				info.m_directionalLight->m_shadowCascadesDistances[info.m_directionalLight->m_shadowCascadeCount - 1];
 		}
 		else
 		{
@@ -213,7 +302,7 @@ void TraditionalDeferredLightShading::drawLights(TraditionalDeferredLightShading
 
 	// Do point lights
 	U32 indexCount;
-	bindVertexIndexBuffers(m_plightMesh, cmdb, indexCount);
+	bindVertexIndexBuffers(ProxyType::kProxySphere, cmdb, indexCount);
 	cmdb->bindShaderProgram(m_plightGrProg[info.m_computeSpecular]);
 
 	for(const PointLightQueueElement& plightEl : info.m_pointLights)
@@ -244,7 +333,7 @@ void TraditionalDeferredLightShading::drawLights(TraditionalDeferredLightShading
 	}
 
 	// Do spot lights
-	bindVertexIndexBuffers(m_slightMesh, cmdb, indexCount);
+	bindVertexIndexBuffers(ProxyType::kProxyCone, cmdb, indexCount);
 	cmdb->bindShaderProgram(m_slightGrProg[info.m_computeSpecular]);
 
 	for(const SpotLightQueueElement& splightEl : info.m_spotLights)

+ 10 - 3
AnKi/Renderer/TraditionalDeferredShading.h

@@ -57,6 +57,12 @@ public:
 	void drawLights(TraditionalDeferredLightShadingDrawInfo& info);
 
 private:
+	enum class ProxyType
+	{
+		kProxySphere,
+		kProxyCone
+	};
+
 	ShaderProgramResourcePtr m_lightProg;
 	Array<ShaderProgramPtr, 2> m_plightGrProg;
 	Array<ShaderProgramPtr, 2> m_slightGrProg;
@@ -67,13 +73,14 @@ private:
 
 	/// @name Meshes of light volumes.
 	/// @{
-	MeshResourcePtr m_plightMesh;
-	MeshResourcePtr m_slightMesh;
+	BufferPtr m_proxyVolumesBuffer;
 	/// @}
 
 	SamplerPtr m_shadowSampler;
 
-	static void bindVertexIndexBuffers(MeshResourcePtr& mesh, CommandBufferPtr& cmdb, U32& indexCount);
+	void bindVertexIndexBuffers(ProxyType proxyType, CommandBufferPtr& cmdb, U32& indexCount) const;
+
+	void createProxyMeshes();
 };
 /// @}
 } // end namespace anki

+ 12 - 11
AnKi/Renderer/VolumetricLightingAccumulation.cpp

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

+ 1 - 1
AnKi/Resource/CpuMeshResource.cpp

@@ -30,7 +30,7 @@ Error CpuMeshResource::load(const ResourceFilename& filename, [[maybe_unused]] B
 	DynamicArrayRaii<Vec3> tempPositions(&getMemoryPool());
 	DynamicArrayRaii<U32> tempIndices(&getMemoryPool());
 
-	ANKI_CHECK(loader.storeIndicesAndPosition(tempIndices, tempPositions));
+	ANKI_CHECK(loader.storeIndicesAndPosition(0, tempIndices, tempPositions));
 
 	m_indices = std::move(tempIndices);
 	m_positions = std::move(tempPositions);

+ 32 - 24
AnKi/Resource/MeshBinary.h

@@ -9,15 +9,14 @@
 
 #include <AnKi/Resource/Common.h>
 #include <AnKi/Math.h>
+#include <AnKi/Gr/Common.h>
 
 namespace anki {
 
 /// @addtogroup resource
 /// @{
 
-inline constexpr const char* kMeshMagic = "ANKIMES5";
-
-constexpr U32 kMeshBinaryBufferAlignment = 16;
+inline constexpr const char* kMeshMagic = "ANKIMES7";
 
 enum class MeshBinaryFlag : U32
 {
@@ -29,12 +28,11 @@ enum class MeshBinaryFlag : U32
 };
 ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(MeshBinaryFlag)
 
-/// Vertex buffer info. The size of the buffer is m_vertexStride*MeshBinaryHeader::m_totalVertexCount aligned to
-/// kMeshBinaryBufferAlignment.
+/// Vertex buffer info.
 class MeshBinaryVertexBuffer
 {
 public:
-	/// The size of the vertex.
+	/// The size of the vertex. It's zero if the buffer is not present.
 	U32 m_vertexStride;
 
 	template<typename TSerializer, typename TClass>
@@ -60,21 +58,28 @@ public:
 class MeshBinaryVertexAttribute
 {
 public:
-	U32 m_bufferBinding;
+	U32 m_bufferIndex;
 
 	/// If the format is kNone then the attribute is not present.
 	Format m_format;
 
 	U32 m_relativeOffset;
-	F32 m_scale;
+
+	/// Attribute is compressed and needs to be scaled.
+	Array<F32, 4> m_scale;
+
+	/// Attribute is compressed and needs to be translated.
+	Array<F32, 4> m_translation;
 
 	template<typename TSerializer, typename TClass>
 	static void serializeCommon(TSerializer& s, TClass self)
 	{
-		s.doValue("m_bufferBinding", offsetof(MeshBinaryVertexAttribute, m_bufferBinding), self.m_bufferBinding);
+		s.doValue("m_bufferIndex", offsetof(MeshBinaryVertexAttribute, m_bufferIndex), self.m_bufferIndex);
 		s.doValue("m_format", offsetof(MeshBinaryVertexAttribute, m_format), self.m_format);
 		s.doValue("m_relativeOffset", offsetof(MeshBinaryVertexAttribute, m_relativeOffset), self.m_relativeOffset);
-		s.doValue("m_scale", offsetof(MeshBinaryVertexAttribute, m_scale), self.m_scale);
+		s.doArray("m_scale", offsetof(MeshBinaryVertexAttribute, m_scale), &self.m_scale[0], self.m_scale.getSize());
+		s.doArray("m_translation", offsetof(MeshBinaryVertexAttribute, m_translation), &self.m_translation[0],
+				  self.m_translation.getSize());
 	}
 
 	template<typename TDeserializer>
@@ -94,8 +99,8 @@ public:
 class MeshBinarySubMesh
 {
 public:
-	U32 m_firstIndex;
-	U32 m_indexCount;
+	Array<U32, kMaxLodCount> m_firstIndices;
+	Array<U32, kMaxLodCount> m_indexCounts;
 
 	/// Bounding box min.
 	Vec3 m_aabbMin;
@@ -106,8 +111,10 @@ public:
 	template<typename TSerializer, typename TClass>
 	static void serializeCommon(TSerializer& s, TClass self)
 	{
-		s.doValue("m_firstIndex", offsetof(MeshBinarySubMesh, m_firstIndex), self.m_firstIndex);
-		s.doValue("m_indexCount", offsetof(MeshBinarySubMesh, m_indexCount), self.m_indexCount);
+		s.doArray("m_firstIndices", offsetof(MeshBinarySubMesh, m_firstIndices), &self.m_firstIndices[0],
+				  self.m_firstIndices.getSize());
+		s.doArray("m_indexCounts", offsetof(MeshBinarySubMesh, m_indexCounts), &self.m_indexCounts[0],
+				  self.m_indexCounts.getSize());
 		s.doValue("m_aabbMin", offsetof(MeshBinarySubMesh, m_aabbMin), self.m_aabbMin);
 		s.doValue("m_aabbMax", offsetof(MeshBinarySubMesh, m_aabbMax), self.m_aabbMax);
 	}
@@ -125,21 +132,20 @@ public:
 	}
 };
 
-/// The 1st things that appears in a mesh binary. @note The index and vertex buffers are aligned to
-/// kMeshBinaryBufferAlignment bytes.
+/// The 1st things that appears in a mesh binary.
 class MeshBinaryHeader
 {
 public:
 	Array<U8, 8> m_magic;
 	MeshBinaryFlag m_flags;
-	Array<MeshBinaryVertexBuffer, U32(VertexAttributeId::kCount)> m_vertexBuffers;
-	U32 m_vertexBufferCount;
-	Array<MeshBinaryVertexAttribute, U32(VertexAttributeId::kCount)> m_vertexAttributes;
+	Array<MeshBinaryVertexBuffer, kMaxVertexAttributes> m_vertexBuffers;
+	Array<MeshBinaryVertexAttribute, kMaxVertexAttributes> m_vertexAttributes;
 	IndexType m_indexType;
 	Array<U8, 3> m_padding;
-	U32 m_totalIndexCount;
-	U32 m_totalVertexCount;
+	Array<U32, kMaxLodCount> m_totalIndexCounts;
+	Array<U32, kMaxLodCount> m_totalVertexCounts;
 	U32 m_subMeshCount;
+	U32 m_lodCount;
 
 	/// Bounding box min.
 	Vec3 m_aabbMin;
@@ -154,14 +160,16 @@ public:
 		s.doValue("m_flags", offsetof(MeshBinaryHeader, m_flags), self.m_flags);
 		s.doArray("m_vertexBuffers", offsetof(MeshBinaryHeader, m_vertexBuffers), &self.m_vertexBuffers[0],
 				  self.m_vertexBuffers.getSize());
-		s.doValue("m_vertexBufferCount", offsetof(MeshBinaryHeader, m_vertexBufferCount), self.m_vertexBufferCount);
 		s.doArray("m_vertexAttributes", offsetof(MeshBinaryHeader, m_vertexAttributes), &self.m_vertexAttributes[0],
 				  self.m_vertexAttributes.getSize());
 		s.doValue("m_indexType", offsetof(MeshBinaryHeader, m_indexType), self.m_indexType);
 		s.doArray("m_padding", offsetof(MeshBinaryHeader, m_padding), &self.m_padding[0], self.m_padding.getSize());
-		s.doValue("m_totalIndexCount", offsetof(MeshBinaryHeader, m_totalIndexCount), self.m_totalIndexCount);
-		s.doValue("m_totalVertexCount", offsetof(MeshBinaryHeader, m_totalVertexCount), self.m_totalVertexCount);
+		s.doArray("m_totalIndexCounts", offsetof(MeshBinaryHeader, m_totalIndexCounts), &self.m_totalIndexCounts[0],
+				  self.m_totalIndexCounts.getSize());
+		s.doArray("m_totalVertexCounts", offsetof(MeshBinaryHeader, m_totalVertexCounts), &self.m_totalVertexCounts[0],
+				  self.m_totalVertexCounts.getSize());
 		s.doValue("m_subMeshCount", offsetof(MeshBinaryHeader, m_subMeshCount), self.m_subMeshCount);
+		s.doValue("m_lodCount", offsetof(MeshBinaryHeader, m_lodCount), self.m_lodCount);
 		s.doValue("m_aabbMin", offsetof(MeshBinaryHeader, m_aabbMin), self.m_aabbMin);
 		s.doValue("m_aabbMax", offsetof(MeshBinaryHeader, m_aabbMax), self.m_aabbMax);
 	}

+ 15 - 15
AnKi/Resource/MeshBinary.xml

@@ -2,14 +2,13 @@
 	<includes>
 		<include file="&lt;AnKi/Resource/Common.h&gt;"/>
 		<include file="&lt;AnKi/Math.h&gt;"/>
+		<include file="&lt;AnKi/Gr/Common.h&gt;"/>
 	</includes>
 
 	<doxygen_group name="resource"/>
 
 	<prefix_code><![CDATA[
-inline constexpr const char* kMeshMagic = "ANKIMES5";
-
-constexpr U32 kMeshBinaryBufferAlignment = 16;
+inline constexpr const char* kMeshMagic = "ANKIMES7";
 
 enum class MeshBinaryFlag : U32
 {
@@ -23,42 +22,43 @@ ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(MeshBinaryFlag)
 ]]></prefix_code>
 
 	<classes>
-		<class name="MeshBinaryVertexBuffer" comment="Vertex buffer info. The size of the buffer is m_vertexStride*MeshBinaryHeader::m_totalVertexCount aligned to kMeshBinaryBufferAlignment">
+		<class name="MeshBinaryVertexBuffer" comment="Vertex buffer info">
 			<members>
-				<member name="m_vertexStride" type="U32" comment="The size of the vertex"/>
+				<member name="m_vertexStride" type="U32" comment="The size of the vertex. It's zero if the buffer is not present"/>
 			</members>
 		</class>
 
 		<class name="MeshBinaryVertexAttribute" comment="Vertex attribute">
 			<members>
-				<member name="m_bufferBinding" type="U32"/>
+				<member name="m_bufferIndex" type="U32"/>
 				<member name="m_format" type="Format" comment="If the format is kNone then the attribute is not present"/>
 				<member name="m_relativeOffset" type="U32"/>
-				<member name="m_scale" type="F32"/>
+				<member name="m_scale" type="F32" array_size="4" comment="Attribute is compressed and needs to be scaled"/>
+				<member name="m_translation" type="F32" array_size="4" comment="Attribute is compressed and needs to be translated"/>
 			</members>
 		</class>
 
 		<class name="MeshBinarySubMesh">
 			<members>
-				<member name="m_firstIndex" type="U32"/>
-				<member name="m_indexCount" type="U32"/>
+				<member name="m_firstIndices" type="U32" array_size="kMaxLodCount"/>
+				<member name="m_indexCounts" type="U32" array_size="kMaxLodCount"/>
 				<member name="m_aabbMin" type="Vec3" comment="Bounding box min"/>
 				<member name="m_aabbMax" type="Vec3" comment="Bounding box max"/>
 			</members>
 		</class>
 
-		<class name="MeshBinaryHeader" comment="The 1st things that appears in a mesh binary. @note The index and vertex buffers are aligned to kMeshBinaryBufferAlignment bytes">
+		<class name="MeshBinaryHeader" comment="The 1st things that appears in a mesh binary">
 			<members>
 				<member name="m_magic" type="U8" array_size="8"/>
 				<member name="m_flags" type="MeshBinaryFlag"/>
-				<member name="m_vertexBuffers" type="MeshBinaryVertexBuffer" array_size="U32(VertexAttributeId::kCount)"/>
-				<member name="m_vertexBufferCount" type="U32"/>
-				<member name="m_vertexAttributes" type="MeshBinaryVertexAttribute" array_size="U32(VertexAttributeId::kCount)"/>
+				<member name="m_vertexBuffers" type="MeshBinaryVertexBuffer" array_size="kMaxVertexAttributes"/>
+				<member name="m_vertexAttributes" type="MeshBinaryVertexAttribute" array_size="kMaxVertexAttributes"/>
 				<member name="m_indexType" type="IndexType"/>
 				<member name="m_padding" type="U8" array_size="3"/>
-				<member name="m_totalIndexCount" type="U32"/>
-				<member name="m_totalVertexCount" type="U32"/>
+				<member name="m_totalIndexCounts" type="U32" array_size="kMaxLodCount"/>
+				<member name="m_totalVertexCounts" type="U32" array_size="kMaxLodCount"/>
 				<member name="m_subMeshCount" type="U32"/>
+				<member name="m_lodCount" type="U32"/>
 				<member name="m_aabbMin" type="Vec3" comment="Bounding box min"/>
 				<member name="m_aabbMax" type="Vec3" comment="Bounding box max"/>
 			</members>

+ 149 - 80
AnKi/Resource/MeshBinaryLoader.cpp

@@ -20,25 +20,30 @@ MeshBinaryLoader::~MeshBinaryLoader()
 
 Error MeshBinaryLoader::load(const ResourceFilename& filename)
 {
-	BaseMemoryPool& pool = *m_pool;
-
-	// Load header
+	// Load header + submeshes
 	ANKI_CHECK(m_manager->getFilesystem().openFile(filename, m_file));
 	ANKI_CHECK(m_file->read(&m_header, sizeof(m_header)));
 	ANKI_CHECK(checkHeader());
+	ANKI_CHECK(loadSubmeshes());
 
-	// Read submesh info
-	{
-		m_subMeshes.create(pool, m_header.m_subMeshCount);
-		ANKI_CHECK(m_file->read(&m_subMeshes[0], m_subMeshes.getSizeInBytes()));
+	return Error::kNone;
+}
 
-		// Checks
-		const U32 indicesPerFace = !!(m_header.m_flags & MeshBinaryFlag::kQuad) ? 4 : 3;
+Error MeshBinaryLoader::loadSubmeshes()
+{
+	m_subMeshes.create(*m_pool, m_header.m_subMeshCount);
+	ANKI_CHECK(m_file->read(&m_subMeshes[0], m_subMeshes.getSizeInBytes()));
+
+	// Checks
+	const U32 indicesPerFace = !!(m_header.m_flags & MeshBinaryFlag::kQuad) ? 4 : 3;
+
+	for(U32 lod = 0; lod < m_header.m_lodCount; ++lod)
+	{
 		U idxSum = 0;
 		for(U32 i = 0; i < m_subMeshes.getSize(); i++)
 		{
 			const MeshBinarySubMesh& sm = m_subMeshes[i];
-			if(sm.m_firstIndex != idxSum || (sm.m_indexCount % indicesPerFace) != 0)
+			if(sm.m_firstIndices[lod] != idxSum || (sm.m_indexCounts[lod] % indicesPerFace) != 0)
 			{
 				ANKI_RESOURCE_LOGE("Incorrect sub mesh info");
 				return Error::kUserData;
@@ -48,17 +53,17 @@ Error MeshBinaryLoader::load(const ResourceFilename& filename)
 			{
 				if(sm.m_aabbMin[d] >= sm.m_aabbMax[d])
 				{
-					ANKI_RESOURCE_LOGE("Wrong bounding box");
+					ANKI_RESOURCE_LOGE("Wrong submesh bounding box");
 					return Error::kUserData;
 				}
 			}
 
-			idxSum += sm.m_indexCount;
+			idxSum += sm.m_indexCounts[lod];
 		}
 
-		if(idxSum != m_header.m_totalIndexCount)
+		if(idxSum != m_header.m_totalIndexCounts[lod])
 		{
-			ANKI_RESOURCE_LOGE("Incorrect sub mesh info");
+			ANKI_RESOURCE_LOGE("Submesh index count doesn't add up to the total");
 			return Error::kUserData;
 		}
 	}
@@ -66,52 +71,70 @@ Error MeshBinaryLoader::load(const ResourceFilename& filename)
 	return Error::kNone;
 }
 
-Error MeshBinaryLoader::checkFormat(VertexAttributeId type, ConstWeakArray<Format> supportedFormats,
-									U32 vertexBufferIdx, U32 relativeOffset) const
+Error MeshBinaryLoader::checkFormat(VertexStreamId stream, Bool isOptional, Bool canBeTransformed) const
 {
-	const MeshBinaryVertexAttribute& attrib = m_header.m_vertexAttributes[type];
+	const U32 vertexAttribIdx = U32(stream);
+	const U32 vertexBufferIdx = U32(stream);
+	const MeshBinaryVertexAttribute& attrib = m_header.m_vertexAttributes[vertexAttribIdx];
 
 	// Check format
-	Bool found = false;
-	for(Format fmt : supportedFormats)
+	if(isOptional && attrib.m_format == Format::kNone)
 	{
-		if(fmt == attrib.m_format)
-		{
-			found = true;
-			break;
-		}
+		// Attrib is not in use, no more checks
+		return Error::kNone;
 	}
 
-	if(!found)
+	if(attrib.m_format != kMeshRelatedVertexStreamFormats[stream])
 	{
-		ANKI_RESOURCE_LOGE("Vertex attribute %u has unsupported format %u", U32(type),
-						   U32(m_header.m_vertexAttributes[type].m_format));
+		ANKI_RESOURCE_LOGE("Vertex attribute %u has unsupported format %s", vertexAttribIdx,
+						   getFormatInfo(attrib.m_format).m_name);
 		return Error::kUserData;
 	}
 
-	if(!attrib.m_format)
+	if(attrib.m_bufferIndex != vertexBufferIdx)
 	{
-		// Attrib is not in use, no more checks
-		return Error::kNone;
+		ANKI_RESOURCE_LOGE("Vertex attribute %u should belong to the %u vertex buffer", vertexAttribIdx,
+						   vertexBufferIdx);
+		return Error::kUserData;
 	}
 
-	if(attrib.m_bufferBinding != vertexBufferIdx)
+	if(attrib.m_relativeOffset != 0)
 	{
-		ANKI_RESOURCE_LOGE("Vertex attribute %u should belong to the %u vertex buffer", U32(type), vertexBufferIdx);
+		ANKI_RESOURCE_LOGE("Vertex attribute %u should have relative vertex offset equal to 0", vertexAttribIdx);
 		return Error::kUserData;
 	}
 
-	if(attrib.m_relativeOffset != relativeOffset)
+	if(!canBeTransformed && Vec4(attrib.m_scale) != Vec4(1.0f))
 	{
-		ANKI_RESOURCE_LOGE("Vertex attribute %u should have relative vertex offset equal to %u", U32(type),
-						   relativeOffset);
+		ANKI_RESOURCE_LOGE("Vertex attribute %u should have 1.0 scale", vertexAttribIdx);
 		return Error::kUserData;
 	}
 
-	// Scale should be 1.0 for now
-	if(attrib.m_scale != 1.0f)
+	if(canBeTransformed && Vec4(attrib.m_scale) <= kEpsilonf)
 	{
-		ANKI_RESOURCE_LOGE("Vertex attribute %u should have 1.0 scale", U32(type));
+		ANKI_RESOURCE_LOGE("Vertex attribute %u should have positive scale", vertexAttribIdx);
+		return Error::kUserData;
+	}
+
+	if(canBeTransformed
+	   && (attrib.m_scale[0] != attrib.m_scale[1] || attrib.m_scale[0] != attrib.m_scale[2]
+		   || attrib.m_scale[0] != attrib.m_scale[3]))
+	{
+		ANKI_RESOURCE_LOGE("Vertex attribute %u should have uniform scale", vertexAttribIdx);
+		return Error::kUserData;
+	}
+
+	if(!canBeTransformed && Vec4(attrib.m_translation) != Vec4(0.0f))
+	{
+		ANKI_RESOURCE_LOGE("Vertex attribute %u should have 0.0 translation", vertexAttribIdx);
+		return Error::kUserData;
+	}
+
+	const U32 vertexBufferStride = getFormatInfo(attrib.m_format).m_texelSize;
+	if(m_header.m_vertexBuffers[vertexBufferIdx].m_vertexStride != vertexBufferStride)
+	{
+		ANKI_RESOURCE_LOGE("Vertex buffer %u doesn't have the expected stride of %u", vertexBufferIdx,
+						   vertexBufferStride);
 		return Error::kUserData;
 	}
 
@@ -137,27 +160,27 @@ Error MeshBinaryLoader::checkHeader() const
 	}
 
 	// Attributes
-	ANKI_CHECK(checkFormat(VertexAttributeId::kPosition, Array<Format, 1>{{Format::kR32G32B32_Sfloat}}, 0, 0));
-	ANKI_CHECK(checkFormat(VertexAttributeId::kNormal, Array<Format, 1>{{Format::kA2B10G10R10_Snorm_Pack32}}, 1, 0));
-	ANKI_CHECK(checkFormat(VertexAttributeId::kTangent, Array<Format, 1>{{Format::kA2B10G10R10_Snorm_Pack32}}, 1, 4));
-	ANKI_CHECK(checkFormat(VertexAttributeId::kUv0, Array<Format, 1>{{Format::kR32G32_Sfloat}}, 1, 8));
-	ANKI_CHECK(checkFormat(VertexAttributeId::kUv1, Array<Format, 1>{{Format::kNone}}, 1, 0));
-	ANKI_CHECK(
-		checkFormat(VertexAttributeId::kBoneIndices, Array<Format, 2>{{Format::kNone, Format::kR8G8B8A8_Uint}}, 2, 0));
-	ANKI_CHECK(
-		checkFormat(VertexAttributeId::kBoneWeights, Array<Format, 2>{{Format::kNone, Format::kR8G8B8A8_Unorm}}, 2, 4));
+	ANKI_CHECK(checkFormat(VertexStreamId::kPosition, false, true));
+	ANKI_CHECK(checkFormat(VertexStreamId::kNormal, false, false));
+	ANKI_CHECK(checkFormat(VertexStreamId::kTangent, false, false));
+	ANKI_CHECK(checkFormat(VertexStreamId::kUv, false, false));
+	ANKI_CHECK(checkFormat(VertexStreamId::kBoneIds, true, false));
+	ANKI_CHECK(checkFormat(VertexStreamId::kBoneWeights, true, false));
 
 	// Vertex buffers
-	if(m_header.m_vertexBufferCount != 2 + U32(hasBoneInfo()))
+	const Format boneIdxFormat = m_header.m_vertexAttributes[VertexStreamId::kBoneIds].m_format;
+	const Format boneWeightsFormat = m_header.m_vertexAttributes[VertexStreamId::kBoneWeights].m_format;
+	if((boneIdxFormat == Format::kNone && boneWeightsFormat != Format::kNone)
+	   || (boneWeightsFormat == Format::kNone && boneIdxFormat != Format::kNone))
 	{
-		ANKI_RESOURCE_LOGE("Wrong number of vertex buffers");
+		ANKI_RESOURCE_LOGE("Bone buffers are partially present");
 		return Error::kUserData;
 	}
 
-	if(m_header.m_vertexBuffers[0].m_vertexStride != sizeof(Vec3) || m_header.m_vertexBuffers[1].m_vertexStride != 16
-	   || (hasBoneInfo() && m_header.m_vertexBuffers[2].m_vertexStride != 8))
+	// LOD
+	if(h.m_lodCount == 0 || h.m_lodCount >= kMaxLodCount)
 	{
-		ANKI_RESOURCE_LOGE("Some of the vertex buffers have incorrect vertex stride");
+		ANKI_RESOURCE_LOGE("Wrong LOD count");
 		return Error::kUserData;
 	}
 
@@ -169,18 +192,24 @@ Error MeshBinaryLoader::checkHeader() const
 	}
 
 	// m_totalIndexCount
-	const U indicesPerFace = !!(h.m_flags & MeshBinaryFlag::kQuad) ? 4 : 3;
-	if(h.m_totalIndexCount == 0 || (h.m_totalIndexCount % indicesPerFace) != 0)
+	for(U32 lod = 0; lod < h.m_lodCount; ++lod)
 	{
-		ANKI_RESOURCE_LOGE("Wrong index count");
-		return Error::kUserData;
+		const U indicesPerFace = !!(h.m_flags & MeshBinaryFlag::kQuad) ? 4 : 3;
+		if(h.m_totalIndexCounts[lod] == 0 || (h.m_totalIndexCounts[lod] % indicesPerFace) != 0)
+		{
+			ANKI_RESOURCE_LOGE("Wrong index count");
+			return Error::kUserData;
+		}
 	}
 
 	// m_totalVertexCount
-	if(h.m_totalVertexCount == 0)
+	for(U32 lod = 0; lod < h.m_lodCount; ++lod)
 	{
-		ANKI_RESOURCE_LOGE("Wrong vertex count");
-		return Error::kUserData;
+		if(h.m_totalVertexCounts[lod] == 0)
+		{
+			ANKI_RESOURCE_LOGE("Wrong vertex count");
+			return Error::kUserData;
+		}
 	}
 
 	// m_subMeshCount
@@ -202,13 +231,11 @@ Error MeshBinaryLoader::checkHeader() const
 
 	// Check the file size
 	PtrSize totalSize = sizeof(m_header);
-
 	totalSize += sizeof(MeshBinarySubMesh) * m_header.m_subMeshCount;
-	totalSize += getAlignedIndexBufferSize();
 
-	for(U32 i = 0; i < m_header.m_vertexBufferCount; ++i)
+	for(U32 lod = 0; lod < h.m_lodCount; ++lod)
 	{
-		totalSize += getAlignedVertexBufferSize(i);
+		totalSize += getLodBuffersSize(lod);
 	}
 
 	if(totalSize != m_file->getSize())
@@ -220,30 +247,44 @@ Error MeshBinaryLoader::checkHeader() const
 	return Error::kNone;
 }
 
-Error MeshBinaryLoader::storeIndexBuffer(void* ptr, PtrSize size)
+Error MeshBinaryLoader::storeIndexBuffer(U32 lod, void* ptr, PtrSize size)
 {
 	ANKI_ASSERT(ptr);
 	ANKI_ASSERT(isLoaded());
-	ANKI_ASSERT(size == getIndexBufferSize());
+	ANKI_ASSERT(lod < m_header.m_lodCount);
+	ANKI_ASSERT(size == getIndexBufferSize(lod));
+
+	PtrSize seek = sizeof(m_header) + m_subMeshes.getSizeInBytes();
+	for(U32 l = lod + 1; l < m_header.m_lodCount; ++l)
+	{
+		seek += getLodBuffersSize(l);
+	}
 
-	const PtrSize seek = sizeof(m_header) + m_subMeshes.getSizeInBytes();
 	ANKI_CHECK(m_file->seek(seek, FileSeekOrigin::kBeginning));
 	ANKI_CHECK(m_file->read(ptr, size));
 
 	return Error::kNone;
 }
 
-Error MeshBinaryLoader::storeVertexBuffer(U32 bufferIdx, void* ptr, PtrSize size)
+Error MeshBinaryLoader::storeVertexBuffer(U32 lod, U32 bufferIdx, void* ptr, PtrSize size)
 {
 	ANKI_ASSERT(ptr);
 	ANKI_ASSERT(isLoaded());
-	ANKI_ASSERT(bufferIdx < m_header.m_vertexBufferCount);
-	ANKI_ASSERT(size == getVertexBufferSize(bufferIdx));
+	ANKI_ASSERT(size == getVertexBufferSize(lod, bufferIdx));
+	ANKI_ASSERT(lod < m_header.m_lodCount);
+
+	PtrSize seek = sizeof(m_header) + m_subMeshes.getSizeInBytes();
+
+	for(U32 l = lod + 1; l < m_header.m_lodCount; ++l)
+	{
+		seek += getLodBuffersSize(l);
+	}
+
+	seek += getIndexBufferSize(lod);
 
-	PtrSize seek = sizeof(m_header) + m_subMeshes.getSizeInBytes() + getAlignedIndexBufferSize();
 	for(U32 i = 0; i < bufferIdx; ++i)
 	{
-		seek += getAlignedVertexBufferSize(i);
+		seek += getVertexBufferSize(lod, i);
 	}
 
 	ANKI_CHECK(m_file->seek(seek, FileSeekOrigin::kBeginning));
@@ -252,22 +293,24 @@ Error MeshBinaryLoader::storeVertexBuffer(U32 bufferIdx, void* ptr, PtrSize size
 	return Error::kNone;
 }
 
-Error MeshBinaryLoader::storeIndicesAndPosition(DynamicArrayRaii<U32>& indices, DynamicArrayRaii<Vec3>& positions)
+Error MeshBinaryLoader::storeIndicesAndPosition(U32 lod, DynamicArrayRaii<U32>& indices,
+												DynamicArrayRaii<Vec3>& positions)
 {
 	ANKI_ASSERT(isLoaded());
+	ANKI_ASSERT(lod < m_header.m_lodCount);
 
 	// Store indices
 	{
-		indices.resize(m_header.m_totalIndexCount);
+		indices.resize(m_header.m_totalIndexCounts[lod]);
 
 		// Store to staging buff
 		DynamicArrayRaii<U8, PtrSize> staging(m_pool);
-		staging.create(getIndexBufferSize());
-		ANKI_CHECK(storeIndexBuffer(&staging[0], staging.getSizeInBytes()));
+		staging.create(getIndexBufferSize(lod));
+		ANKI_CHECK(storeIndexBuffer(lod, &staging[0], staging.getSizeInBytes()));
 
 		// Copy from staging
 		ANKI_ASSERT(m_header.m_indexType == IndexType::kU16);
-		for(U32 i = 0; i < m_header.m_totalIndexCount; ++i)
+		for(U32 i = 0; i < m_header.m_totalIndexCounts[lod]; ++i)
 		{
 			indices[i] = *reinterpret_cast<U16*>(&staging[PtrSize(i) * 2]);
 		}
@@ -275,13 +318,39 @@ Error MeshBinaryLoader::storeIndicesAndPosition(DynamicArrayRaii<U32>& indices,
 
 	// Store positions
 	{
-		positions.resize(m_header.m_totalVertexCount);
-		const MeshBinaryVertexAttribute& attrib = m_header.m_vertexAttributes[VertexAttributeId::kPosition];
-		ANKI_ASSERT(attrib.m_format == Format::kR32G32B32_Sfloat);
-		ANKI_CHECK(storeVertexBuffer(attrib.m_bufferBinding, &positions[0], positions.getSizeInBytes()));
+		const MeshBinaryVertexAttribute& attrib = m_header.m_vertexAttributes[VertexStreamId::kPosition];
+		DynamicArrayRaii<U16Vec3> tempPositions(m_pool, m_header.m_totalVertexCounts[lod]);
+		static_assert(kMeshRelatedVertexStreamFormats[VertexStreamId::kPosition] == Format::kR16G16B16_Unorm,
+					  "Incorrect format");
+		ANKI_CHECK(storeVertexBuffer(lod, attrib.m_bufferIndex, &tempPositions[0], tempPositions.getSizeInBytes()));
+
+		positions.resize(m_header.m_totalVertexCounts[lod]);
+
+		for(U32 i = 0; i < tempPositions.getSize(); ++i)
+		{
+			positions[i] = Vec3(tempPositions[i]) / F32(kMaxU16);
+			positions[i] *= Vec3(&attrib.m_scale[0]);
+			positions[i] += Vec3(&attrib.m_translation[0]);
+		}
 	}
 
 	return Error::kNone;
 }
 
+PtrSize MeshBinaryLoader::getLodBuffersSize(U32 lod) const
+{
+	ANKI_ASSERT(lod < m_header.m_lodCount);
+
+	PtrSize size = getIndexBufferSize(lod);
+	for(U32 vertBufferIdx = 0; vertBufferIdx < m_header.m_vertexBuffers.getSize(); ++vertBufferIdx)
+	{
+		if(m_header.m_vertexBuffers[vertBufferIdx].m_vertexStride > 0)
+		{
+			size += getVertexBufferSize(lod, vertBufferIdx);
+		}
+	}
+
+	return size;
+}
+
 } // end namespace anki

+ 19 - 28
AnKi/Resource/MeshBinaryLoader.h

@@ -9,6 +9,7 @@
 #include <AnKi/Resource/ResourceFilesystem.h>
 #include <AnKi/Resource/MeshBinary.h>
 #include <AnKi/Util/WeakArray.h>
+#include <AnKi/Shaders/Include/MeshTypes.h>
 
 namespace anki {
 
@@ -16,6 +17,12 @@ namespace anki {
 /// @{
 
 /// This class loads the mesh binary file. It only supports a subset of combinations of vertex formats and buffers.
+/// The file is layed out in memory:
+/// * Header
+/// * Submeshes
+/// * Index buffer of max LOD
+/// * Vertex buffer #0 of max LOD
+/// * etc...
 class MeshBinaryLoader
 {
 public:
@@ -32,12 +39,12 @@ public:
 
 	Error load(const ResourceFilename& filename);
 
-	Error storeIndexBuffer(void* ptr, PtrSize size);
+	Error storeIndexBuffer(U32 lod, void* ptr, PtrSize size);
 
-	Error storeVertexBuffer(U32 bufferIdx, void* ptr, PtrSize size);
+	Error storeVertexBuffer(U32 lod, U32 bufferIdx, void* ptr, PtrSize size);
 
 	/// Instead of calling storeIndexBuffer and storeVertexBuffer use this method to get those buffers into the CPU.
-	Error storeIndicesAndPosition(DynamicArrayRaii<U32>& indices, DynamicArrayRaii<Vec3>& positions);
+	Error storeIndicesAndPosition(U32 lod, DynamicArrayRaii<U32>& indices, DynamicArrayRaii<Vec3>& positions);
 
 	const MeshBinaryHeader& getHeader() const
 	{
@@ -45,12 +52,6 @@ public:
 		return m_header;
 	}
 
-	Bool hasBoneInfo() const
-	{
-		ANKI_ASSERT(isLoaded());
-		return m_header.m_vertexAttributes[VertexAttributeId::kBoneIndices].m_format != Format::kNone;
-	}
-
 	ConstWeakArray<MeshBinarySubMesh> getSubMeshes() const
 	{
 		return ConstWeakArray<MeshBinarySubMesh>(m_subMeshes);
@@ -70,35 +71,25 @@ private:
 		return m_file.get() != nullptr;
 	}
 
-	PtrSize getIndexBufferSize() const
+	PtrSize getIndexBufferSize(U32 lod) const
 	{
 		ANKI_ASSERT(isLoaded());
-		return PtrSize(m_header.m_totalIndexCount) * ((m_header.m_indexType == IndexType::kU16) ? 2 : 4);
+		ANKI_ASSERT(lod < m_header.m_lodCount);
+		return PtrSize(m_header.m_totalIndexCounts[lod]) * getIndexSize(m_header.m_indexType);
 	}
 
-	PtrSize getAlignedIndexBufferSize() const
+	PtrSize getVertexBufferSize(U32 lod, U32 bufferIdx) const
 	{
 		ANKI_ASSERT(isLoaded());
-		return getAlignedRoundUp(kMeshBinaryBufferAlignment, getIndexBufferSize());
+		ANKI_ASSERT(lod < m_header.m_lodCount);
+		return PtrSize(m_header.m_totalVertexCounts[lod]) * PtrSize(m_header.m_vertexBuffers[bufferIdx].m_vertexStride);
 	}
 
-	PtrSize getVertexBufferSize(U32 bufferIdx) const
-	{
-		ANKI_ASSERT(isLoaded());
-		ANKI_ASSERT(bufferIdx < m_header.m_vertexBufferCount);
-		return PtrSize(m_header.m_totalVertexCount) * PtrSize(m_header.m_vertexBuffers[bufferIdx].m_vertexStride);
-	}
-
-	PtrSize getAlignedVertexBufferSize(U32 bufferIdx) const
-	{
-		ANKI_ASSERT(isLoaded());
-		ANKI_ASSERT(bufferIdx < m_header.m_vertexBufferCount);
-		return getAlignedRoundUp(kMeshBinaryBufferAlignment, getVertexBufferSize(bufferIdx));
-	}
+	PtrSize getLodBuffersSize(U32 lod) const;
 
 	Error checkHeader() const;
-	Error checkFormat(VertexAttributeId type, ConstWeakArray<Format> supportedFormats, U32 vertexBufferIdx,
-					  U32 relativeOffset) const;
+	Error checkFormat(VertexStreamId stream, Bool isOptional, Bool canBeTransformed) const;
+	Error loadSubmeshes();
 };
 /// @}
 

+ 187 - 189
AnKi/Resource/MeshResource.cpp

@@ -19,7 +19,7 @@ public:
 	MeshResourcePtr m_mesh;
 	MeshBinaryLoader m_loader;
 
-	LoadContext(const MeshResourcePtr& mesh, BaseMemoryPool* pool)
+	LoadContext(MeshResource* mesh, BaseMemoryPool* pool)
 		: m_mesh(mesh)
 		, m_loader(&mesh->getManager(), pool)
 	{
@@ -32,7 +32,7 @@ class MeshResource::LoadTask : public AsyncLoaderTask
 public:
 	MeshResource::LoadContext m_ctx;
 
-	LoadTask(const MeshResourcePtr& mesh)
+	LoadTask(MeshResource* mesh)
 		: m_ctx(mesh, &mesh->getManager().getAsyncLoader().getMemoryPool())
 	{
 	}
@@ -51,46 +51,52 @@ public:
 MeshResource::MeshResource(ResourceManager* manager)
 	: ResourceObject(manager)
 {
-	memset(&m_meshGpuDescriptor, 0, sizeof(m_meshGpuDescriptor));
 }
 
 MeshResource::~MeshResource()
 {
 	m_subMeshes.destroy(getMemoryPool());
-	m_vertexBufferInfos.destroy(getMemoryPool());
 
-	if(m_vertexBuffersOffset != kMaxPtrSize)
+	for(Lod& lod : m_lods)
 	{
-		getManager().getUnifiedGeometryMemoryPool().free(m_vertexBuffersSize, 4, m_vertexBuffersOffset);
-	}
+		if(lod.m_unifiedGeometryIndexBufferOffset != kMaxPtrSize)
+		{
+			const U32 alignment = getIndexSize(m_indexType);
+			const PtrSize size = lod.m_indexCount * PtrSize(alignment);
+			getManager().getUnifiedGeometryMemoryPool().free(size, alignment, lod.m_unifiedGeometryIndexBufferOffset);
+		}
 
-	if(m_indexBufferOffset != kMaxPtrSize)
-	{
-		const PtrSize indexBufferSize = PtrSize(m_indexCount) * ((m_indexType == IndexType::kU32) ? 4 : 2);
-		getManager().getUnifiedGeometryMemoryPool().free(indexBufferSize, getIndexSize(m_indexType),
-														 m_indexBufferOffset);
+		for(VertexStreamId stream : EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
+		{
+			if(lod.m_unifiedGeometryVertBufferOffsets[stream] != kMaxPtrSize)
+			{
+				const U32 alignment = getFormatInfo(kMeshRelatedVertexStreamFormats[stream]).m_texelSize;
+				const PtrSize size = PtrSize(alignment) * lod.m_vertexCount;
+
+				getManager().getUnifiedGeometryMemoryPool().free(size, alignment,
+																 lod.m_unifiedGeometryVertBufferOffsets[stream]);
+			}
+		}
 	}
-}
 
-Bool MeshResource::isCompatible(const MeshResource& other) const
-{
-	return hasBoneWeights() == other.hasBoneWeights() && getSubMeshCount() == other.getSubMeshCount();
+	m_lods.destroy(getMemoryPool());
 }
 
 Error MeshResource::load(const ResourceFilename& filename, Bool async)
 {
 	UniquePtr<LoadTask> task;
 	LoadContext* ctx;
-	LoadContext localCtx(MeshResourcePtr(this), &getTempMemoryPool());
+	LoadContext localCtx(this, &getTempMemoryPool());
 
 	StringRaii basename(&getTempMemoryPool());
 	getFilepathFilename(filename, basename);
 
 	const Bool rayTracingEnabled = getManager().getGrManager().getDeviceCapabilities().m_rayTracingEnabled;
+	BufferPtr unifiedGeometryBuffer = getManager().getUnifiedGeometryMemoryPool().getVertexBuffer();
 
 	if(async)
 	{
-		task.reset(getManager().getAsyncLoader().newTask<LoadTask>(MeshResourcePtr(this)));
+		task.reset(getManager().getAsyncLoader().newTask<LoadTask>(this));
 		ctx = &task->m_ctx;
 	}
 	else
@@ -104,86 +110,104 @@ Error MeshResource::load(const ResourceFilename& filename, Bool async)
 	ANKI_CHECK(loader.load(filename));
 	const MeshBinaryHeader& header = loader.getHeader();
 
-	//
+	// Misc
+	m_indexType = header.m_indexType;
+	m_aabb.setMin(header.m_aabbMin);
+	m_aabb.setMax(header.m_aabbMax);
+	m_positionsScale = header.m_vertexAttributes[VertexStreamId::kPosition].m_scale[0];
+	m_positionsTranslation = Vec3(&header.m_vertexAttributes[VertexStreamId::kPosition].m_translation[0]);
+
 	// Submeshes
-	//
 	m_subMeshes.create(getMemoryPool(), header.m_subMeshCount);
 	for(U32 i = 0; i < m_subMeshes.getSize(); ++i)
 	{
-		m_subMeshes[i].m_firstIndex = loader.getSubMeshes()[i].m_firstIndex;
-		m_subMeshes[i].m_indexCount = loader.getSubMeshes()[i].m_indexCount;
+		m_subMeshes[i].m_firstIndices = loader.getSubMeshes()[i].m_firstIndices;
+		m_subMeshes[i].m_indexCounts = loader.getSubMeshes()[i].m_indexCounts;
 		m_subMeshes[i].m_aabb.setMin(loader.getSubMeshes()[i].m_aabbMin);
 		m_subMeshes[i].m_aabb.setMax(loader.getSubMeshes()[i].m_aabbMax);
 	}
 
-	//
-	// Index stuff
-	//
-	m_indexCount = header.m_totalIndexCount;
-	ANKI_ASSERT((m_indexCount % 3) == 0 && "Expecting triangles");
-	m_indexType = header.m_indexType;
-
-	const PtrSize indexBufferSize = PtrSize(m_indexCount) * ((m_indexType == IndexType::kU32) ? 4 : 2);
-	ANKI_CHECK(getManager().getUnifiedGeometryMemoryPool().allocate(indexBufferSize, getIndexSize(m_indexType),
-																	m_indexBufferOffset));
-
-	//
-	// Vertex stuff
-	//
-	m_vertexCount = header.m_totalVertexCount;
-	m_vertexBufferInfos.create(getMemoryPool(), header.m_vertexBufferCount);
-
-	m_vertexBuffersSize = 0;
-	for(U32 i = 0; i < header.m_vertexBufferCount; ++i)
-	{
-		alignRoundUp(kMeshBinaryBufferAlignment, m_vertexBuffersSize);
-
-		m_vertexBufferInfos[i].m_offset = m_vertexBuffersSize;
-		m_vertexBufferInfos[i].m_stride = header.m_vertexBuffers[i].m_vertexStride;
-
-		m_vertexBuffersSize += m_vertexCount * m_vertexBufferInfos[i].m_stride;
-	}
-
-	ANKI_CHECK(getManager().getUnifiedGeometryMemoryPool().allocate(m_vertexBuffersSize, 4, m_vertexBuffersOffset));
-
-	// Readjust the individual offset now that we have a global offset
-	for(U32 i = 0; i < header.m_vertexBufferCount; ++i)
+	// LODs
+	m_lods.create(getMemoryPool(), header.m_lodCount);
+	for(I32 l = I32(header.m_lodCount - 1); l >= 0; --l)
 	{
-		m_vertexBufferInfos[i].m_offset += m_vertexBuffersOffset;
-	}
-
-	for(VertexAttributeId attrib = VertexAttributeId::kFirst; attrib < VertexAttributeId::kCount; ++attrib)
-	{
-		AttribInfo& out = m_attributes[attrib];
-		const MeshBinaryVertexAttribute& in = header.m_vertexAttributes[attrib];
+		Lod& lod = m_lods[l];
+
+		// Index stuff
+		lod.m_indexCount = header.m_totalIndexCounts[l];
+		ANKI_ASSERT((lod.m_indexCount % 3) == 0 && "Expecting triangles");
+		const PtrSize indexBufferSize = PtrSize(lod.m_indexCount) * getIndexSize(m_indexType);
+		ANKI_CHECK(getManager().getUnifiedGeometryMemoryPool().allocate(indexBufferSize, getIndexSize(m_indexType),
+																		lod.m_unifiedGeometryIndexBufferOffset));
+
+		// Vertex stuff
+		lod.m_vertexCount = header.m_totalVertexCounts[l];
+		for(VertexStreamId stream : EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
+		{
+			if(header.m_vertexAttributes[stream].m_format == Format::kNone)
+			{
+				lod.m_unifiedGeometryVertBufferOffsets[stream] = kMaxPtrSize;
+				continue;
+			}
+
+			m_presentVertStreams |= VertexStreamMask(1 << stream);
+
+			const U32 texelSize = getFormatInfo(kMeshRelatedVertexStreamFormats[stream]).m_texelSize;
+			const PtrSize vertexBufferSize = PtrSize(lod.m_vertexCount) * texelSize;
+			const U32 alignment = 4;
+			ANKI_CHECK(getManager().getUnifiedGeometryMemoryPool().allocate(
+				vertexBufferSize, alignment, lod.m_unifiedGeometryVertBufferOffsets[stream]));
+		}
 
-		if(!!in.m_format)
+		// BLAS
+		if(rayTracingEnabled)
 		{
-			out.m_format = in.m_format;
-			out.m_relativeOffset = in.m_relativeOffset;
-			out.m_buffIdx = U8(in.m_bufferBinding);
-			ANKI_ASSERT(in.m_scale == 1.0f && "Not supported ATM");
+			AccelerationStructureInitInfo inf(
+				StringRaii(&getTempMemoryPool()).sprintf("%s_%s", "Blas", basename.cstr()));
+			inf.m_type = AccelerationStructureType::kBottomLevel;
+
+			inf.m_bottomLevel.m_indexBuffer = unifiedGeometryBuffer;
+			inf.m_bottomLevel.m_indexBufferOffset = lod.m_unifiedGeometryIndexBufferOffset;
+			inf.m_bottomLevel.m_indexCount = lod.m_indexCount;
+			inf.m_bottomLevel.m_indexType = m_indexType;
+			inf.m_bottomLevel.m_positionBuffer = unifiedGeometryBuffer;
+			inf.m_bottomLevel.m_positionBufferOffset =
+				lod.m_unifiedGeometryVertBufferOffsets[VertexStreamId::kPosition];
+			inf.m_bottomLevel.m_positionStride =
+				getFormatInfo(kMeshRelatedVertexStreamFormats[VertexStreamId::kPosition]).m_texelSize;
+			inf.m_bottomLevel.m_positionsFormat = kMeshRelatedVertexStreamFormats[VertexStreamId::kPosition];
+			inf.m_bottomLevel.m_positionCount = lod.m_vertexCount;
+
+			lod.m_blas = getManager().getGrManager().newAccelerationStructure(inf);
 		}
 	}
 
-	// Other
-	m_aabb.setMin(header.m_aabbMin);
-	m_aabb.setMax(header.m_aabbMax);
-	m_vertexBuffer = getManager().getUnifiedGeometryMemoryPool().getVertexBuffer();
-
-	//
 	// Clear the buffers
-	//
 	if(async)
 	{
-		CommandBufferInitInfo cmdbinit;
+		CommandBufferInitInfo cmdbinit("MeshResourceClear");
 		cmdbinit.m_flags = CommandBufferFlag::kSmallBatch | CommandBufferFlag::kGeneralWork;
 		CommandBufferPtr cmdb = getManager().getGrManager().newCommandBuffer(cmdbinit);
 
-		cmdb->fillBuffer(m_vertexBuffer, m_vertexBuffersOffset, m_vertexBuffersSize, 0);
-		cmdb->fillBuffer(m_vertexBuffer, m_indexBufferOffset, indexBufferSize, 0);
+		for(const Lod& lod : m_lods)
+		{
+			cmdb->fillBuffer(unifiedGeometryBuffer, lod.m_unifiedGeometryIndexBufferOffset,
+							 PtrSize(lod.m_indexCount) * getIndexSize(m_indexType), 0);
+
+			for(VertexStreamId stream :
+				EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
+			{
+				if(header.m_vertexAttributes[stream].m_format != Format::kNone)
+				{
+					cmdb->fillBuffer(unifiedGeometryBuffer, lod.m_unifiedGeometryVertBufferOffsets[stream],
+									 PtrSize(lod.m_vertexCount)
+										 * getFormatInfo(kMeshRelatedVertexStreamFormats[stream]).m_texelSize,
+									 0);
+				}
+			}
+		}
 
-		const BufferBarrierInfo barrier = {m_vertexBuffer.get(), BufferUsageBit::kTransferDestination,
+		const BufferBarrierInfo barrier = {unifiedGeometryBuffer.get(), BufferUsageBit::kTransferDestination,
 										   BufferUsageBit::kVertex, 0, kMaxPtrSize};
 
 		cmdb->setPipelineBarrier({}, {&barrier, 1}, {});
@@ -191,71 +215,6 @@ Error MeshResource::load(const ResourceFilename& filename, Bool async)
 		cmdb->flush();
 	}
 
-	//
-	// Create the BLAS
-	//
-	if(rayTracingEnabled)
-	{
-		AccelerationStructureInitInfo inf(StringRaii(&getTempMemoryPool()).sprintf("%s_%s", "Blas", basename.cstr()));
-		inf.m_type = AccelerationStructureType::kBottomLevel;
-
-		inf.m_bottomLevel.m_indexBuffer = m_vertexBuffer;
-		inf.m_bottomLevel.m_indexBufferOffset = m_indexBufferOffset;
-		inf.m_bottomLevel.m_indexCount = m_indexCount;
-		inf.m_bottomLevel.m_indexType = m_indexType;
-
-		U32 bufferIdx;
-		Format format;
-		U32 relativeOffset;
-		getVertexAttributeInfo(VertexAttributeId::kPosition, bufferIdx, format, relativeOffset);
-
-		BufferPtr buffer;
-		PtrSize offset;
-		PtrSize stride;
-		getVertexBufferInfo(bufferIdx, buffer, offset, stride);
-
-		inf.m_bottomLevel.m_positionBuffer = std::move(buffer);
-		inf.m_bottomLevel.m_positionBufferOffset = offset;
-		inf.m_bottomLevel.m_positionStride = U32(stride);
-		inf.m_bottomLevel.m_positionsFormat = format;
-		inf.m_bottomLevel.m_positionCount = m_vertexCount;
-
-		m_blas = getManager().getGrManager().newAccelerationStructure(inf);
-	}
-
-	// Fill the GPU descriptor
-	if(rayTracingEnabled)
-	{
-		m_meshGpuDescriptor.m_indexBufferPtr = m_vertexBuffer->getGpuAddress() + m_indexBufferOffset;
-
-		U32 bufferIdx;
-		Format format;
-		U32 relativeOffset;
-		getVertexAttributeInfo(VertexAttributeId::kPosition, bufferIdx, format, relativeOffset);
-		BufferPtr buffer;
-		PtrSize offset;
-		PtrSize stride;
-		getVertexBufferInfo(bufferIdx, buffer, offset, stride);
-		m_meshGpuDescriptor.m_vertexBufferPtrs[VertexAttributeBufferId::kPosition] = buffer->getGpuAddress() + offset;
-
-		getVertexAttributeInfo(VertexAttributeId::kNormal, bufferIdx, format, relativeOffset);
-		getVertexBufferInfo(bufferIdx, buffer, offset, stride);
-		m_meshGpuDescriptor.m_vertexBufferPtrs[VertexAttributeBufferId::kNormalTangentUv0] =
-			buffer->getGpuAddress() + offset;
-
-		if(hasBoneWeights())
-		{
-			getVertexAttributeInfo(VertexAttributeId::kBoneWeights, bufferIdx, format, relativeOffset);
-			getVertexBufferInfo(bufferIdx, buffer, offset, stride);
-			m_meshGpuDescriptor.m_vertexBufferPtrs[VertexAttributeBufferId::kBone] = buffer->getGpuAddress() + offset;
-		}
-
-		m_meshGpuDescriptor.m_indexCount = m_indexCount;
-		m_meshGpuDescriptor.m_vertexCount = m_vertexCount;
-		m_meshGpuDescriptor.m_aabbMin = header.m_aabbMin;
-		m_meshGpuDescriptor.m_aabbMax = header.m_aabbMax;
-	}
-
 	// Submit the loading task
 	if(async)
 	{
@@ -275,88 +234,127 @@ Error MeshResource::loadAsync(MeshBinaryLoader& loader) const
 {
 	GrManager& gr = getManager().getGrManager();
 	TransferGpuAllocator& transferAlloc = getManager().getTransferGpuAllocator();
-	Array<TransferGpuAllocatorHandle, 2> handles;
+
+	Array<TransferGpuAllocatorHandle, kMaxLodCount*(U32(VertexStreamId::kMeshRelatedCount) + 1)> handles;
+	U32 handleCount = 0;
+
+	BufferPtr unifiedGeometryBuffer = getManager().getUnifiedGeometryMemoryPool().getVertexBuffer();
+	const BufferUsageBit unifiedGeometryBufferNonTransferUsage =
+		unifiedGeometryBuffer->getBufferUsage() ^ BufferUsageBit::kTransferDestination;
 
 	CommandBufferInitInfo cmdbinit;
 	cmdbinit.m_flags = CommandBufferFlag::kSmallBatch | CommandBufferFlag::kGeneralWork;
 	CommandBufferPtr cmdb = gr.newCommandBuffer(cmdbinit);
 
-	// Set barriers
-	const BufferBarrierInfo barrier = {m_vertexBuffer.get(), BufferUsageBit::kVertex,
+	// Set transfer to transfer barrier because of the clear that happened while sync loading
+	const BufferBarrierInfo barrier = {unifiedGeometryBuffer.get(), unifiedGeometryBufferNonTransferUsage,
 									   BufferUsageBit::kTransferDestination, 0, kMaxPtrSize};
 	cmdb->setPipelineBarrier({}, {&barrier, 1}, {});
 
-	// Write index buffer
+	// Upload index and vertex buffers
+	for(U32 lodIdx = 0; lodIdx < m_lods.getSize(); ++lodIdx)
 	{
-		const PtrSize indexBufferSize = PtrSize(m_indexCount) * ((m_indexType == IndexType::kU32) ? 4 : 2);
+		const Lod& lod = m_lods[lodIdx];
 
-		ANKI_CHECK(transferAlloc.allocate(indexBufferSize, handles[1]));
-		void* data = handles[1].getMappedMemory();
-		ANKI_ASSERT(data);
+		// Upload index buffer
+		{
+			TransferGpuAllocatorHandle& handle = handles[handleCount++];
+			const PtrSize indexBufferSize = PtrSize(lod.m_indexCount) * getIndexSize(m_indexType);
 
-		ANKI_CHECK(loader.storeIndexBuffer(data, indexBufferSize));
+			ANKI_CHECK(transferAlloc.allocate(indexBufferSize, handle));
+			void* data = handle.getMappedMemory();
+			ANKI_ASSERT(data);
 
-		cmdb->copyBufferToBuffer(handles[1].getBuffer(), handles[1].getOffset(), m_vertexBuffer, m_indexBufferOffset,
-								 handles[1].getRange());
-	}
+			ANKI_CHECK(loader.storeIndexBuffer(lodIdx, data, indexBufferSize));
 
-	// Write vert buff
-	{
-		ANKI_CHECK(transferAlloc.allocate(m_vertexBuffersSize, handles[0]));
-		U8* data = static_cast<U8*>(handles[0].getMappedMemory());
-		ANKI_ASSERT(data);
+			cmdb->copyBufferToBuffer(handle.getBuffer(), handle.getOffset(), unifiedGeometryBuffer,
+									 lod.m_unifiedGeometryIndexBufferOffset, handle.getRange());
+		}
 
-		// Load to staging
-		PtrSize offset = 0;
-		for(U32 i = 0; i < m_vertexBufferInfos.getSize(); ++i)
+		// Upload vert buffers
+		for(VertexStreamId stream : EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
 		{
-			alignRoundUp(kMeshBinaryBufferAlignment, offset);
-			ANKI_CHECK(
-				loader.storeVertexBuffer(i, data + offset, PtrSize(m_vertexBufferInfos[i].m_stride) * m_vertexCount));
+			if(!(m_presentVertStreams & VertexStreamMask(1 << stream)))
+			{
+				continue;
+			}
 
-			offset += PtrSize(m_vertexBufferInfos[i].m_stride) * m_vertexCount;
-		}
+			TransferGpuAllocatorHandle& handle = handles[handleCount++];
+			const PtrSize vertexBufferSize =
+				PtrSize(lod.m_vertexCount) * getFormatInfo(kMeshRelatedVertexStreamFormats[stream]).m_texelSize;
+
+			ANKI_CHECK(transferAlloc.allocate(vertexBufferSize, handle));
+			U8* data = static_cast<U8*>(handle.getMappedMemory());
+			ANKI_ASSERT(data);
 
-		ANKI_ASSERT(offset == m_vertexBuffersSize);
+			// Load to staging
+			ANKI_CHECK(loader.storeVertexBuffer(lodIdx, U32(stream), data, vertexBufferSize));
 
-		// Copy
-		cmdb->copyBufferToBuffer(handles[0].getBuffer(), handles[0].getOffset(), m_vertexBuffer, m_vertexBuffersOffset,
-								 handles[0].getRange());
+			// Copy
+			cmdb->copyBufferToBuffer(handle.getBuffer(), handle.getOffset(), unifiedGeometryBuffer,
+									 lod.m_unifiedGeometryVertBufferOffsets[stream], handle.getRange());
+		}
 	}
 
-	// Build the BLAS
 	if(gr.getDeviceCapabilities().m_rayTracingEnabled)
 	{
-		const BufferBarrierInfo buffBarrier = {m_vertexBuffer.get(), BufferUsageBit::kTransferDestination,
-											   BufferUsageBit::kAccelerationStructureBuild | BufferUsageBit::kVertex
-												   | BufferUsageBit::kIndex,
-											   0, kMaxPtrSize};
-		const AccelerationStructureBarrierInfo asBarrier = {m_blas.get(), AccelerationStructureUsageBit::kNone,
-															AccelerationStructureUsageBit::kBuild};
+		// Build BLASes
+
+		// Set the barriers
+		BufferBarrierInfo bufferBarrier;
+		bufferBarrier.m_buffer = unifiedGeometryBuffer.get();
+		bufferBarrier.m_offset = 0;
+		bufferBarrier.m_size = kMaxPtrSize;
+		bufferBarrier.m_previousUsage = BufferUsageBit::kTransferDestination;
+		bufferBarrier.m_nextUsage = unifiedGeometryBufferNonTransferUsage;
+
+		Array<AccelerationStructureBarrierInfo, kMaxLodCount> asBarriers;
+		for(U32 lodIdx = 0; lodIdx < m_lods.getSize(); ++lodIdx)
+		{
+			asBarriers[lodIdx].m_as = m_lods[lodIdx].m_blas.get();
+			asBarriers[lodIdx].m_previousUsage = AccelerationStructureUsageBit::kNone;
+			asBarriers[lodIdx].m_nextUsage = AccelerationStructureUsageBit::kBuild;
+		}
 
-		cmdb->setPipelineBarrier({}, {&buffBarrier, 1}, {&asBarrier, 1});
+		cmdb->setPipelineBarrier({}, {&bufferBarrier, 1}, {&asBarriers[0], m_lods.getSize()});
 
-		cmdb->buildAccelerationStructure(m_blas);
+		// Build BLASes
+		for(U32 lodIdx = 0; lodIdx < m_lods.getSize(); ++lodIdx)
+		{
+			cmdb->buildAccelerationStructure(m_lods[lodIdx].m_blas);
+		}
 
-		const AccelerationStructureBarrierInfo asBarrier2 = {m_blas.get(), AccelerationStructureUsageBit::kBuild,
-															 AccelerationStructureUsageBit::kAllRead};
+		// Barriers again
+		for(U32 lodIdx = 0; lodIdx < m_lods.getSize(); ++lodIdx)
+		{
+			asBarriers[lodIdx].m_as = m_lods[lodIdx].m_blas.get();
+			asBarriers[lodIdx].m_previousUsage = AccelerationStructureUsageBit::kBuild;
+			asBarriers[lodIdx].m_nextUsage = AccelerationStructureUsageBit::kAllRead;
+		}
 
-		cmdb->setPipelineBarrier({}, {}, {&asBarrier2, 1});
+		cmdb->setPipelineBarrier({}, {}, {&asBarriers[0], m_lods.getSize()});
 	}
 	else
 	{
-		const BufferBarrierInfo buffBarrier = {m_vertexBuffer.get(), BufferUsageBit::kTransferDestination,
-											   BufferUsageBit::kVertex | BufferUsageBit::kIndex, 0, kMaxPtrSize};
-
-		cmdb->setPipelineBarrier({}, {&buffBarrier, 1}, {});
+		// Only set a barrier
+		BufferBarrierInfo bufferBarrier;
+		bufferBarrier.m_buffer = unifiedGeometryBuffer.get();
+		bufferBarrier.m_offset = 0;
+		bufferBarrier.m_size = kMaxPtrSize;
+		bufferBarrier.m_previousUsage = BufferUsageBit::kTransferDestination;
+		bufferBarrier.m_nextUsage = unifiedGeometryBufferNonTransferUsage;
+
+		cmdb->setPipelineBarrier({}, {&bufferBarrier, 1}, {});
 	}
 
 	// Finalize
 	FencePtr fence;
 	cmdb->flush({}, &fence);
 
-	transferAlloc.release(handles[0], fence);
-	transferAlloc.release(handles[1], fence);
+	for(U32 i = 0; i < handleCount; ++i)
+	{
+		transferAlloc.release(handles[i], fence);
+	}
 
 	return Error::kNone;
 }

+ 50 - 86
AnKi/Resource/MeshResource.h

@@ -9,7 +9,7 @@
 #include <AnKi/Math.h>
 #include <AnKi/Gr.h>
 #include <AnKi/Collision/Aabb.h>
-#include <AnKi/Shaders/Include/ModelTypes.h>
+#include <AnKi/Shaders/Include/MeshTypes.h>
 
 namespace anki {
 
@@ -28,9 +28,6 @@ public:
 
 	~MeshResource();
 
-	/// Helper function for correct loading
-	Bool isCompatible(const MeshResource& other) const;
-
 	/// Load from a mesh file
 	Error load(const ResourceFilename& filename, Bool async);
 
@@ -40,134 +37,101 @@ public:
 		return m_aabb;
 	}
 
-	/// Get submesh info.
-	void getSubMeshInfo(U32 subMeshId, U32& firstIndex, U32& indexCount, Aabb& aabb) const
-	{
-		const SubMesh& sm = m_subMeshes[subMeshId];
-		firstIndex = sm.m_firstIndex;
-		indexCount = sm.m_indexCount;
-		aabb = sm.m_aabb;
-	}
-
 	U32 getSubMeshCount() const
 	{
 		return m_subMeshes.getSize();
 	}
 
-	/// Get all info around vertex indices.
-	void getIndexBufferInfo(BufferPtr& buff, PtrSize& buffOffset, U32& indexCount, IndexType& indexType) const
+	/// Get submesh info.
+	void getSubMeshInfo(U32 lod, U32 subMeshId, U32& firstIndex, U32& indexCount, Aabb& aabb) const
 	{
-		buff = m_vertexBuffer;
-		buffOffset = m_indexBufferOffset;
-		indexCount = m_indexCount;
-		indexType = m_indexType;
+		const SubMesh& sm = m_subMeshes[subMeshId];
+		firstIndex = sm.m_firstIndices[lod];
+		indexCount = sm.m_indexCounts[lod];
+		aabb = sm.m_aabb;
 	}
 
-	/// Get the number of logical vertex buffers.
-	U32 getVertexBufferCount() const
+	/// Get all info around vertex indices.
+	void getIndexBufferInfo(U32 lod, PtrSize& buffOffset, U32& indexCount, IndexType& indexType) const
 	{
-		return m_vertexBufferInfos.getSize();
+		buffOffset = m_lods[lod].m_unifiedGeometryIndexBufferOffset;
+		ANKI_ASSERT(isAligned(getIndexSize(m_indexType), buffOffset));
+		indexCount = m_lods[lod].m_indexCount;
+		indexType = m_indexType;
 	}
 
 	/// Get vertex buffer info.
-	void getVertexBufferInfo(const U32 buffIdx, BufferPtr& buff, PtrSize& offset, PtrSize& stride) const
+	void getVertexStreamInfo(U32 lod, VertexStreamId stream, PtrSize& bufferOffset, U32& vertexCount) const
 	{
-		buff = m_vertexBuffer;
-		offset = m_vertexBufferInfos[buffIdx].m_offset;
-		stride = m_vertexBufferInfos[buffIdx].m_stride;
+		bufferOffset = m_lods[lod].m_unifiedGeometryVertBufferOffsets[stream];
+		vertexCount = m_lods[lod].m_vertexCount;
 	}
 
-	/// Get attribute info. You need to check if the attribute is preset first (isVertexAttributePresent)
-	void getVertexAttributeInfo(const VertexAttributeId attrib, U32& bufferIdx, Format& format,
-								U32& relativeOffset) const
+	const AccelerationStructurePtr& getBottomLevelAccelerationStructure(U32 lod) const
 	{
-		ANKI_ASSERT(isVertexAttributePresent(attrib));
-		bufferIdx = m_attributes[attrib].m_buffIdx;
-		format = m_attributes[attrib].m_format;
-		relativeOffset = m_attributes[attrib].m_relativeOffset;
+		ANKI_ASSERT(m_lods[lod].m_blas);
+		return m_lods[lod].m_blas;
 	}
 
-	/// Check if a vertex attribute is present.
-	Bool isVertexAttributePresent(const VertexAttributeId attrib) const
+	/// Check if a vertex stream is present.
+	Bool isVertexStreamPresent(const VertexStreamId stream) const
 	{
-		return !!m_attributes[attrib].m_format;
+		return !!(m_presentVertStreams & VertexStreamMask(1 << stream));
 	}
 
-	/// Return true if it has bone weights.
-	Bool hasBoneWeights() const
+	U32 getLodCount() const
 	{
-		return isVertexAttributePresent(VertexAttributeId::kBoneWeights);
+		return m_lods.getSize();
 	}
 
-	AccelerationStructurePtr getBottomLevelAccelerationStructure() const
+	F32 getPositionsScale() const
 	{
-		ANKI_ASSERT(m_blas.isCreated());
-		return m_blas;
+		return m_positionsScale;
 	}
 
-	const MeshGpuDescriptor& getMeshGpuDescriptor() const
+	Vec3 getPositionsTranslation() const
 	{
-		return m_meshGpuDescriptor;
-	}
-
-	/// Get the buffer that contains all the indices of all submesses.
-	BufferPtr getIndexBuffer() const
-	{
-		return m_vertexBuffer;
-	}
-
-	/// Get the buffer that contains all the vertices of all submesses.
-	BufferPtr getVertexBuffer() const
-	{
-		return m_vertexBuffer;
+		return m_positionsTranslation;
 	}
 
 private:
 	class LoadTask;
 	class LoadContext;
 
-	class SubMesh
+	class Lod
 	{
 	public:
-		U32 m_firstIndex;
-		U32 m_indexCount;
-		Aabb m_aabb;
-	};
+		PtrSize m_unifiedGeometryIndexBufferOffset = kMaxPtrSize;
+		Array<PtrSize, U32(VertexStreamId::kMeshRelatedCount)> m_unifiedGeometryVertBufferOffsets;
 
-	class VertBuffInfo
-	{
-	public:
-		PtrSize m_offset; ///< Offset from the base of m_vertexBuffer.
-		U32 m_stride;
+		U32 m_indexCount = 0;
+		U32 m_vertexCount = 0;
+
+		AccelerationStructurePtr m_blas;
+
+		Lod()
+		{
+			m_unifiedGeometryVertBufferOffsets.fill(m_unifiedGeometryVertBufferOffsets.getBegin(),
+													m_unifiedGeometryVertBufferOffsets.getEnd(), kMaxPtrSize);
+		}
 	};
 
-	class AttribInfo
+	class SubMesh
 	{
 	public:
-		Format m_format = Format::kNone;
-		U32 m_relativeOffset = 0;
-		U32 m_buffIdx = 0;
+		Array<U32, kMaxLodCount> m_firstIndices;
+		Array<U32, kMaxLodCount> m_indexCounts;
+		Aabb m_aabb;
 	};
 
 	DynamicArray<SubMesh> m_subMeshes;
-	DynamicArray<VertBuffInfo> m_vertexBufferInfos;
-	Array<AttribInfo, U(VertexAttributeId::kCount)> m_attributes;
-
-	BufferPtr m_vertexBuffer; ///< Contains all data (vertices and indices).
-
-	PtrSize m_vertexBuffersOffset = kMaxPtrSize; ///< Used for deallocation.
-	PtrSize m_vertexBuffersSize = 0; ///< Used for deallocation.
-	U32 m_vertexCount = 0;
-
-	PtrSize m_indexBufferOffset = kMaxPtrSize; ///< The offset from the base of m_vertexBuffer.
-	U32 m_indexCount = 0; ///< Total index count as if all submeshes are a single submesh.
-	IndexType m_indexType;
-
+	DynamicArray<Lod> m_lods;
 	Aabb m_aabb;
+	IndexType m_indexType;
+	VertexStreamMask m_presentVertStreams = VertexStreamMask::kNone;
 
-	// RT
-	AccelerationStructurePtr m_blas;
-	MeshGpuDescriptor m_meshGpuDescriptor;
+	F32 m_positionsScale = 0.0f;
+	Vec3 m_positionsTranslation = Vec3(0.0f);
 
 	Error loadAsync(MeshBinaryLoader& loader) const;
 };

+ 48 - 206
AnKi/Resource/ModelResource.cpp

@@ -11,23 +11,6 @@
 
 namespace anki {
 
-static Bool attributeIsRequired(VertexAttributeId loc, RenderingTechnique technique, Bool hasSkin)
-{
-	if(technique == RenderingTechnique::kGBuffer || technique == RenderingTechnique::kForward)
-	{
-		return true;
-	}
-	else if(!hasSkin)
-	{
-		return loc == VertexAttributeId::kPosition || loc == VertexAttributeId::kUv0;
-	}
-	else
-	{
-		return loc == VertexAttributeId::kPosition || loc == VertexAttributeId::kBoneIndices
-			   || loc == VertexAttributeId::kBoneWeights || loc == VertexAttributeId::kUv0;
-	}
-}
-
 void ModelPatch::getRenderingInfo(const RenderingKey& key, ModelRenderingInfo& inf) const
 {
 	ANKI_ASSERT(!(!supportsSkinning() && key.getSkinned()));
@@ -35,59 +18,17 @@ void ModelPatch::getRenderingInfo(const RenderingKey& key, ModelRenderingInfo& i
 
 	// Vertex attributes & bindings
 	{
-		U32 bufferBindingVisitedMask = 0;
-		Array<U32, kMaxVertexAttributes> realBufferBindingToVirtual;
+		inf.m_indexBufferOffset = m_lodInfos[meshLod].m_indexBufferOffset;
+		inf.m_indexType = IndexType::kU16;
+		inf.m_firstIndex = m_lodInfos[meshLod].m_firstIndex;
+		inf.m_indexCount = m_lodInfos[meshLod].m_indexCount;
 
-		inf.m_vertexAttributeCount = 0;
-		inf.m_vertexBufferBindingCount = 0;
-
-		for(VertexAttributeId loc : EnumIterable<VertexAttributeId>())
+		for(VertexStreamId stream : EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
 		{
-			if(!m_presentVertexAttributes.get(loc)
-			   || !attributeIsRequired(loc, key.getRenderingTechnique(), key.getSkinned()))
-			{
-				continue;
-			}
-
-			// Attribute
-			ModelVertexAttribute& outAttribInfo = inf.m_vertexAttributes[inf.m_vertexAttributeCount++];
-			outAttribInfo.m_location = loc;
-			outAttribInfo.m_bufferBinding = m_vertexAttributeInfos[loc].m_bufferBinding;
-			outAttribInfo.m_relativeOffset = m_vertexAttributeInfos[loc].m_relativeOffset;
-			outAttribInfo.m_format = m_vertexAttributeInfos[loc].m_format;
-
-			// Binding. Also, remove any holes in the bindings
-			if(!(bufferBindingVisitedMask & (1 << outAttribInfo.m_bufferBinding)))
-			{
-				bufferBindingVisitedMask |= 1 << outAttribInfo.m_bufferBinding;
-
-				ModelVertexBufferBinding& outBinding = inf.m_vertexBufferBindings[inf.m_vertexBufferBindingCount];
-				const VertexBufferInfo& inBinding = m_vertexBufferInfos[meshLod][outAttribInfo.m_bufferBinding];
-				outBinding.m_buffer = inBinding.m_buffer;
-				ANKI_ASSERT(outBinding.m_buffer.isCreated());
-				outBinding.m_offset = inBinding.m_offset;
-				ANKI_ASSERT(outBinding.m_offset != kMaxPtrSize);
-				outBinding.m_stride = inBinding.m_stride;
-				ANKI_ASSERT(outBinding.m_stride != kMaxPtrSize);
-
-				realBufferBindingToVirtual[outAttribInfo.m_bufferBinding] = inf.m_vertexBufferBindingCount;
-				++inf.m_vertexBufferBindingCount;
-			}
-
-			// Change the binding of the attrib
-			outAttribInfo.m_bufferBinding = realBufferBindingToVirtual[outAttribInfo.m_bufferBinding];
+			inf.m_vertexBufferOffsets[stream] = m_lodInfos[meshLod].m_vertexBufferOffsets[stream];
 		}
-
-		ANKI_ASSERT(inf.m_vertexAttributeCount != 0 && inf.m_vertexBufferBindingCount != 0);
 	}
 
-	// Index buff
-	inf.m_indexBuffer = m_indexBufferInfos[meshLod].m_buffer;
-	inf.m_indexBufferOffset = m_indexBufferInfos[meshLod].m_offset;
-	inf.m_indexCount = m_indexBufferInfos[meshLod].m_indexCount;
-	inf.m_firstIndex = m_indexBufferInfos[meshLod].m_firstIndex;
-	inf.m_indexType = m_indexType;
-
 	// Get program
 	const MaterialVariant& variant = m_mtl->getOrCreateVariant(key);
 	inf.m_program = variant.getShaderProgram();
@@ -98,21 +39,17 @@ void ModelPatch::getRayTracingInfo(const RenderingKey& key, ModelRayTracingInfo&
 	ANKI_ASSERT(!!(m_mtl->getRenderingTechniques() & RenderingTechniqueBit(1 << key.getRenderingTechnique())));
 
 	// Mesh
-	const MeshResourcePtr& mesh = m_meshes[min(U32(m_meshLodCount - 1), key.getLod())];
-	info.m_bottomLevelAccelerationStructure = mesh->getBottomLevelAccelerationStructure();
+	const U32 meshLod = min<U32>(key.getLod(), m_meshLodCount - 1);
+	info.m_bottomLevelAccelerationStructure = m_mesh->getBottomLevelAccelerationStructure(meshLod);
 
 	// Material
 	const MaterialVariant& variant = m_mtl->getOrCreateVariant(key);
 	info.m_shaderGroupHandleIndex = variant.getRtShaderGroupHandleIndex();
-
-	// Misc
-	info.m_grObjectReferences = m_grObjectRefs;
 }
 
-Error ModelPatch::init(ModelResource* model, ConstWeakArray<CString> meshFNames, const CString& mtlFName,
+Error ModelPatch::init([[maybe_unused]] ModelResource* model, CString meshFName, const CString& mtlFName,
 					   U32 subMeshIndex, Bool async, ResourceManager* manager)
 {
-	ANKI_ASSERT(meshFNames.getSize() > 0);
 #if ANKI_ENABLE_ASSERTIONS
 	m_model = model;
 #endif
@@ -120,115 +57,49 @@ Error ModelPatch::init(ModelResource* model, ConstWeakArray<CString> meshFNames,
 	// Load material
 	ANKI_CHECK(manager->loadResource(mtlFName, m_mtl, async));
 
-	// Gather the material refs
-	if(m_mtl->getAllTextures().getSize())
-	{
-		m_grObjectRefs.resizeStorage(model->getMemoryPool(), m_mtl->getAllTextures().getSize());
+	// Load mesh
+	ANKI_CHECK(manager->loadResource(meshFName, m_mesh, async));
 
-		for(U32 i = 0; i < m_mtl->getAllTextures().getSize(); ++i)
-		{
-			m_grObjectRefs.emplaceBack(model->getMemoryPool(), m_mtl->getAllTextures()[i]);
-		}
+	if(subMeshIndex != kMaxU32 && subMeshIndex >= m_mesh->getSubMeshCount())
+	{
+		ANKI_RESOURCE_LOGE("Wrong subMeshIndex given");
+		return Error::kUserData;
 	}
 
-	// Load meshes
-	m_meshLodCount = 0;
-	for(U32 lod = 0; lod < meshFNames.getSize(); lod++)
+	// Init cached data
+	if(subMeshIndex == kMaxU32)
 	{
-		ANKI_CHECK(manager->loadResource(meshFNames[lod], m_meshes[lod], async));
-
-		// Sanity check
-		if(lod > 0 && !m_meshes[lod]->isCompatible(*m_meshes[lod - 1]))
-		{
-			ANKI_RESOURCE_LOGE("Meshes not compatible");
-			return Error::kUserData;
-		}
-
-		// Submesh index
-		if(subMeshIndex != kMaxU32 && subMeshIndex >= m_meshes[lod]->getSubMeshCount())
-		{
-			ANKI_RESOURCE_LOGE("Wrong subMeshIndex given");
-			return Error::kUserData;
-		}
-
-		++m_meshLodCount;
+		m_aabb = m_mesh->getBoundingShape();
 	}
-
-	// Create the cached items
+	else
 	{
-		// Vertex attributes
-		for(VertexAttributeId attrib : EnumIterable<VertexAttributeId>())
-		{
-			const MeshResource& mesh = *m_meshes[0].get();
-
-			const Bool enabled = mesh.isVertexAttributePresent(attrib);
-			m_presentVertexAttributes.set(U32(attrib), enabled);
-
-			if(!enabled)
-			{
-				continue;
-			}
+		U32 firstIndex, indexCount;
+		m_mesh->getSubMeshInfo(0, subMeshIndex, firstIndex, indexCount, m_aabb);
+	}
 
-			VertexAttributeInfo& outAttribInfo = m_vertexAttributeInfos[attrib];
-			U32 bufferBinding, relativeOffset;
-			mesh.getVertexAttributeInfo(attrib, bufferBinding, outAttribInfo.m_format, relativeOffset);
-			outAttribInfo.m_bufferBinding = bufferBinding & 0xFu;
-			outAttribInfo.m_relativeOffset = relativeOffset & 0xFFFFFFu;
-		}
+	m_meshLodCount = m_mesh->getLodCount();
 
-		// Vertex buffers
-		for(U32 lod = 0; lod < m_meshLodCount; ++lod)
-		{
-			const MeshResource& mesh = *m_meshes[lod].get();
+	for(U32 l = 0; l < m_meshLodCount; ++l)
+	{
+		Lod& lod = m_lodInfos[l];
+		Aabb aabb;
+		m_mesh->getSubMeshInfo(l, (subMeshIndex == kMaxU32) ? 0 : subMeshIndex, lod.m_firstIndex, lod.m_indexCount,
+							   aabb);
 
-			for(VertexAttributeId attrib : EnumIterable<VertexAttributeId>())
-			{
-				if(!m_presentVertexAttributes.get(attrib))
-				{
-					continue;
-				}
-
-				VertexBufferInfo& outVertBufferInfo =
-					m_vertexBufferInfos[lod][m_vertexAttributeInfos[attrib].m_bufferBinding];
-				if(!outVertBufferInfo.m_buffer.isCreated())
-				{
-					PtrSize offset, stride;
-					mesh.getVertexBufferInfo(m_vertexAttributeInfos[attrib].m_bufferBinding, outVertBufferInfo.m_buffer,
-											 offset, stride);
-					outVertBufferInfo.m_offset = offset & 0xFFFFFFFFFFFF;
-					outVertBufferInfo.m_stride = stride & 0xFFFF;
-				}
-			}
-		}
+		U32 totalIndexCount;
+		IndexType indexType;
+		m_mesh->getIndexBufferInfo(l, lod.m_indexBufferOffset, totalIndexCount, indexType);
 
-		// Index buffer
-		for(U32 lod = 0; lod < m_meshLodCount; ++lod)
+		for(VertexStreamId stream : EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
 		{
-			const MeshResource& mesh = *m_meshes[lod].get();
-			IndexBufferInfo& outIndexBufferInfo = m_indexBufferInfos[lod];
-
-			if(subMeshIndex == kMaxU32)
+			if(m_mesh->isVertexStreamPresent(stream))
 			{
-				IndexType indexType;
-				PtrSize offset;
-				mesh.getIndexBufferInfo(outIndexBufferInfo.m_buffer, offset, outIndexBufferInfo.m_indexCount,
-										indexType);
-				outIndexBufferInfo.m_offset = offset;
-				outIndexBufferInfo.m_firstIndex = 0;
-				m_indexType = indexType;
+				U32 vertCount;
+				m_mesh->getVertexStreamInfo(l, stream, lod.m_vertexBufferOffsets[stream], vertCount);
 			}
 			else
 			{
-				IndexType indexType;
-				PtrSize offset;
-				mesh.getIndexBufferInfo(outIndexBufferInfo.m_buffer, offset, outIndexBufferInfo.m_indexCount,
-										indexType);
-				outIndexBufferInfo.m_offset = offset;
-				m_indexType = indexType;
-
-				Aabb aabb;
-				mesh.getSubMeshInfo(subMeshIndex, outIndexBufferInfo.m_firstIndex, outIndexBufferInfo.m_indexCount,
-									aabb);
+				lod.m_vertexBufferOffsets[stream] = kMaxPtrSize;
 			}
 		}
 	}
@@ -243,18 +114,11 @@ ModelResource::ModelResource(ResourceManager* manager)
 
 ModelResource::~ModelResource()
 {
-	for(ModelPatch& patch : m_modelPatches)
-	{
-		patch.m_grObjectRefs.destroy(getMemoryPool());
-	}
-
 	m_modelPatches.destroy(getMemoryPool());
 }
 
 Error ModelResource::load(const ResourceFilename& filename, Bool async)
 {
-	HeapMemoryPool& pool = getMemoryPool();
-
 	// Load
 	//
 	XmlElement el;
@@ -287,54 +151,32 @@ Error ModelResource::load(const ResourceFilename& filename, Bool async)
 		return Error::kUserData;
 	}
 
-	m_modelPatches.create(pool, count);
+	m_modelPatches.create(getMemoryPool(), count);
 
 	count = 0;
 	ANKI_CHECK(modelPatchesEl.getChildElement("modelPatch", modelPatchEl));
 	do
 	{
-		U32 subMeshIndex;
-		Bool subMeshIndexPresent;
-		ANKI_CHECK(modelPatchEl.getAttributeNumberOptional("subMeshIndex", subMeshIndex, subMeshIndexPresent));
-		if(!subMeshIndexPresent)
-		{
-			subMeshIndex = kMaxU32;
-		}
-
 		XmlElement materialEl;
 		ANKI_CHECK(modelPatchEl.getChildElement("material", materialEl));
 
-		Array<CString, 3> meshesFnames;
-		U32 meshesCount = 1;
-
 		XmlElement meshEl;
 		ANKI_CHECK(modelPatchEl.getChildElement("mesh", meshEl));
+		CString meshFname;
+		ANKI_CHECK(meshEl.getText(meshFname));
 
-		XmlElement meshEl1;
-		ANKI_CHECK(modelPatchEl.getChildElementOptional("mesh1", meshEl1));
-
-		XmlElement meshEl2;
-		ANKI_CHECK(modelPatchEl.getChildElementOptional("mesh2", meshEl2));
-
-		ANKI_CHECK(meshEl.getText(meshesFnames[0]));
-
-		if(meshEl1)
-		{
-			++meshesCount;
-			ANKI_CHECK(meshEl1.getText(meshesFnames[1]));
-		}
-
-		if(meshEl2)
+		U32 subMeshIndex;
+		Bool subMeshIndexPresent;
+		ANKI_CHECK(meshEl.getAttributeNumberOptional("subMeshIndex", subMeshIndex, subMeshIndexPresent));
+		if(!subMeshIndexPresent)
 		{
-			++meshesCount;
-			ANKI_CHECK(meshEl2.getText(meshesFnames[2]));
+			subMeshIndex = kMaxU32;
 		}
 
 		CString cstr;
 		ANKI_CHECK(materialEl.getText(cstr));
 
-		ANKI_CHECK(m_modelPatches[count].init(this, ConstWeakArray<CString>(&meshesFnames[0], meshesCount), cstr,
-											  subMeshIndex, async, &getManager()));
+		ANKI_CHECK(m_modelPatches[count].init(this, meshFname, cstr, subMeshIndex, async, &getManager()));
 
 		if(count > 0 && m_modelPatches[count].supportsSkinning() != m_modelPatches[count - 1].supportsSkinning())
 		{
@@ -349,10 +191,10 @@ Error ModelResource::load(const ResourceFilename& filename, Bool async)
 	ANKI_ASSERT(count == m_modelPatches.getSize());
 
 	// Calculate compound bounding volume
-	m_boundingVolume = m_modelPatches[0].m_meshes[0]->getBoundingShape();
+	m_boundingVolume = m_modelPatches[0].m_aabb;
 	for(auto it = m_modelPatches.getBegin() + 1; it != m_modelPatches.getEnd(); ++it)
 	{
-		m_boundingVolume = m_boundingVolume.getCompoundShape((*it).m_meshes[0]->getBoundingShape());
+		m_boundingVolume = m_boundingVolume.getCompoundShape((*it).m_aabb);
 	}
 
 	return Error::kNone;

+ 25 - 97
AnKi/Resource/ModelResource.h

@@ -17,46 +17,6 @@ namespace anki {
 /// @addtogroup resource
 /// @{
 
-/// @memberof ModelResource
-class ModelVertexBufferBinding
-{
-public:
-	BufferPtr m_buffer;
-	PtrSize m_offset;
-	PtrSize m_stride;
-
-	Bool operator==(const ModelVertexBufferBinding& b) const
-	{
-		return m_buffer == b.m_buffer && m_offset == b.m_offset && m_stride == b.m_stride;
-	}
-
-	Bool operator!=(const ModelVertexBufferBinding& b) const
-	{
-		return !(*this == b);
-	}
-};
-
-/// @memberof ModelResource
-class ModelVertexAttribute
-{
-public:
-	VertexAttributeId m_location;
-	Format m_format;
-	U32 m_bufferBinding;
-	U32 m_relativeOffset;
-
-	Bool operator==(const ModelVertexAttribute& b) const
-	{
-		return m_bufferBinding == b.m_bufferBinding && m_format == b.m_format && m_relativeOffset == b.m_relativeOffset
-			   && m_location == b.m_location;
-	}
-
-	Bool operator!=(const ModelVertexAttribute& b) const
-	{
-		return !(*this == b);
-	}
-};
-
 /// @memberof ModelResource
 /// Part of the information required render the model.
 class ModelRenderingInfo
@@ -64,16 +24,13 @@ class ModelRenderingInfo
 public:
 	ShaderProgramPtr m_program;
 
-	Array<ModelVertexBufferBinding, kMaxVertexAttributes> m_vertexBufferBindings;
-	U32 m_vertexBufferBindingCount;
-	Array<ModelVertexAttribute, kMaxVertexAttributes> m_vertexAttributes;
-	U32 m_vertexAttributeCount;
-
-	BufferPtr m_indexBuffer;
 	PtrSize m_indexBufferOffset;
 	IndexType m_indexType;
 	U32 m_firstIndex;
 	U32 m_indexCount;
+
+	/// Offset to the vertex buffer or kMaxPtrSize if stream is not present.
+	Array<PtrSize, U32(VertexStreamId::kMeshRelatedCount)> m_vertexBufferOffsets;
 };
 
 /// Part of the information required to create a TLAS and a SBT.
@@ -83,12 +40,9 @@ class ModelRayTracingInfo
 public:
 	AccelerationStructurePtr m_bottomLevelAccelerationStructure;
 	U32 m_shaderGroupHandleIndex;
-
-	/// Get some pointers to pass to the command buffer for refcounting.
-	ConstWeakArray<GrObjectPtr> m_grObjectReferences;
 };
 
-/// Model patch class. Its very important class and it binds a material with a few mesh (one for each LOD).
+/// Model patch class. Its very important class and it binds a material with a mesh.
 class ModelPatch
 {
 	friend class ModelResource;
@@ -99,14 +53,14 @@ public:
 		return m_mtl;
 	}
 
-	const MeshResourcePtr& getMesh(U32 lod) const
+	const MeshResourcePtr& getMesh() const
 	{
-		return m_meshes[lod];
+		return m_mesh;
 	}
 
 	const Aabb& getBoundingShape() const
 	{
-		return m_meshes[0]->getBoundingShape();
+		return m_aabb;
 	}
 
 	/// Get information for rendering.
@@ -116,57 +70,33 @@ public:
 	void getRayTracingInfo(const RenderingKey& key, ModelRayTracingInfo& info) const;
 
 private:
-#if ANKI_ENABLE_ASSERTIONS
-	ModelResource* m_model = nullptr;
-#endif
-	MaterialResourcePtr m_mtl;
-	Array<MeshResourcePtr, kMaxLodCount> m_meshes; ///< Just keep the references.
-	DynamicArray<GrObjectPtr> m_grObjectRefs;
-
-	// Begin cached data
-	class VertexAttributeInfo
+	class Lod
 	{
 	public:
-		U32 m_bufferBinding : 8;
-		U32 m_relativeOffset : 24;
-		Format m_format = Format::kNone;
-	};
-
-	Array<VertexAttributeInfo, U(VertexAttributeId::kCount)> m_vertexAttributeInfos;
-
-	class VertexBufferInfo
-	{
-	public:
-		BufferPtr m_buffer;
-		PtrSize m_stride : 16;
-		PtrSize m_offset : 48;
-	};
-
-	Array2d<VertexBufferInfo, kMaxLodCount, U(VertexAttributeBufferId::kCount)> m_vertexBufferInfos;
-
-	class IndexBufferInfo
-	{
-	public:
-		BufferPtr m_buffer;
-		PtrSize m_offset;
+		PtrSize m_indexBufferOffset = kMaxPtrSize;
 		U32 m_firstIndex = kMaxU32;
 		U32 m_indexCount = kMaxU32;
-	};
 
-	Array<IndexBufferInfo, kMaxLodCount> m_indexBufferInfos;
-	BitSet<U(VertexAttributeId::kCount)> m_presentVertexAttributes = {false};
-	IndexType m_indexType : 2;
-	// End cached data
+		Array<PtrSize, U32(VertexStreamId::kMeshRelatedCount)> m_vertexBufferOffsets = {};
+	};
 
-	U8 m_meshLodCount : 6;
+#if ANKI_ENABLE_ASSERTIONS
+	ModelResource* m_model = nullptr;
+#endif
+	MaterialResourcePtr m_mtl;
+	MeshResourcePtr m_mesh; ///< Just keep the references.
 
-	Error init(ModelResource* model, ConstWeakArray<CString> meshFNames, const CString& mtlFName, U32 subMeshIndex,
-			   Bool async, ResourceManager* resources);
+	Array<Lod, kMaxLodCount> m_lodInfos;
+	Aabb m_aabb;
+	U32 m_meshLodCount = 0;
 
 	[[nodiscard]] Bool supportsSkinning() const
 	{
-		return m_meshes[0]->hasBoneWeights() && m_mtl->supportsSkinning();
+		return m_mesh->isVertexStreamPresent(VertexStreamId::kBoneIds) && m_mtl->supportsSkinning();
 	}
+
+	Error init(ModelResource* model, CString meshFName, const CString& mtlFName, U32 subMeshIndex, Bool async,
+			   ResourceManager* resources);
 };
 
 /// Model is an entity that acts as a container for other resources. Models are all the non static objects in a map.
@@ -175,10 +105,8 @@ private:
 /// @code
 /// <model>
 /// 	<modelPatches>
-/// 		<modelPatch [subMeshIndex=int]>
-/// 			<mesh>path/to/mesh.mesh</mesh>
-///				[<mesh1>path/to/mesh_lod_1.ankimesh</mesh1>]
-///				[<mesh2>path/to/mesh_lod_2.ankimesh</mesh2>]
+/// 		<modelPatch>
+/// 			<mesh [subMeshIndex=int]>path/to/mesh.ankimesh</mesh>
 /// 			<material>path/to/material.ankimtl</material>
 /// 		</modelPatch>
 /// 		...

+ 6 - 0
AnKi/Scene/CameraNode.cpp

@@ -66,6 +66,12 @@ void CameraNode::initCommon(FrustumType frustumType)
 	frc->setLodDistance(1, getConfig().getLod1MaxDistance());
 	frc->setShadowCascadeCount(getConfig().getSceneShadowCascadeCount());
 
+	static_assert(kMaxShadowCascades == 4);
+	frc->setShadowCascadeDistance(0, getConfig().getSceneShadowCascade0Distance());
+	frc->setShadowCascadeDistance(1, getConfig().getSceneShadowCascade1Distance());
+	frc->setShadowCascadeDistance(2, getConfig().getSceneShadowCascade2Distance());
+	frc->setShadowCascadeDistance(3, getConfig().getSceneShadowCascade3Distance());
+
 	// Extended frustum for RT
 	if(getSceneGraph().getGrManager().getDeviceCapabilities().m_rayTracingEnabled
 	   && getConfig().getSceneRayTracedShadows())

+ 11 - 0
AnKi/Scene/Common.h

@@ -7,6 +7,7 @@
 
 #include <AnKi/Util/String.h>
 #include <AnKi/Scene/Forward.h>
+#include <functional>
 
 namespace anki {
 
@@ -17,6 +18,16 @@ namespace anki {
 #define ANKI_SCENE_LOGE(...) ANKI_LOG("SCEN", kError, __VA_ARGS__)
 #define ANKI_SCENE_LOGW(...) ANKI_LOG("SCEN", kWarning, __VA_ARGS__)
 #define ANKI_SCENE_LOGF(...) ANKI_LOG("SCEN", kFatal, __VA_ARGS__)
+
+#define ANKI_SCENE_ASSERT(expression) \
+	ANKI_LIKELY(std::invoke([&]() -> Bool { \
+		const Bool ok = (expression); \
+		if(ANKI_UNLIKELY(!ok)) \
+		{ \
+			ANKI_SCENE_LOGE("Expression failed: " #expression); \
+		} \
+		return ok; \
+	}))
 /// @}
 
 } // end namespace anki

+ 52 - 0
AnKi/Scene/Components/FrustumComponent.cpp

@@ -16,12 +16,19 @@ FrustumComponent::FrustumComponent(SceneNode* node)
 	, m_node(node)
 	, m_shapeMarkedForUpdate(true)
 	, m_trfMarkedForUpdate(true)
+	, m_miscMarkedForUpdate(true)
 {
 	ANKI_ASSERT(&m_perspective.m_far == &m_ortho.m_far);
 	ANKI_ASSERT(node);
 
 	// Set some default values
 	setFrustumType(FrustumType::kPerspective);
+	for(U i = 0; i < m_misc.m_maxLodDistances.getSize(); ++i)
+	{
+		const F32 dist = (m_common.m_far - m_common.m_near) / F32(kMaxLodCount + 1);
+		m_misc.m_maxLodDistances[i] = m_common.m_near + dist * F32(i + 1);
+	}
+
 	updateInternal();
 }
 
@@ -52,6 +59,12 @@ Bool FrustumComponent::updateInternal()
 	{
 		updated = true;
 
+		// Fix user data
+		if(!ANKI_SCENE_ASSERT(m_common.m_far - m_common.m_near > 1.0_cm))
+		{
+			setFrustumType(m_frustumType);
+		}
+
 		if(m_frustumType == FrustumType::kPerspective)
 		{
 			m_projMat = Mat4::calculatePerspectiveProjectionMatrix(m_perspective.m_fovX, m_perspective.m_fovY,
@@ -109,12 +122,51 @@ Bool FrustumComponent::updateInternal()
 		m_viewMat = Mat3x4(m_trf.getInverse());
 	}
 
+	// Fixup the misc data
+	if(m_miscMarkedForUpdate)
+	{
+		updated = true;
+		const F32 frustumFraction = (m_common.m_far - m_common.m_near) / 100.0f;
+
+		for(U32 i = 0; i < m_misc.m_shadowCascadeCount; ++i)
+		{
+			if(!ANKI_SCENE_ASSERT(m_misc.m_shadowCascadeDistances[i] > m_common.m_near
+								  && m_misc.m_shadowCascadeDistances[i] <= m_common.m_far))
+			{
+				m_misc.m_shadowCascadeDistances[i] =
+					clamp(m_misc.m_shadowCascadeDistances[i], m_common.m_near + kEpsilonf, m_common.m_far);
+			}
+
+			if(i != 0
+			   && !ANKI_SCENE_ASSERT(m_misc.m_shadowCascadeDistances[i - 1] < m_misc.m_shadowCascadeDistances[i]))
+			{
+				m_misc.m_shadowCascadeDistances[i] = m_misc.m_shadowCascadeDistances[i - 1] + frustumFraction;
+			}
+		}
+
+		for(U32 i = 0; i < m_misc.m_maxLodDistances.getSize(); ++i)
+		{
+			if(!ANKI_SCENE_ASSERT(m_misc.m_maxLodDistances[i] > m_common.m_near
+								  && m_misc.m_maxLodDistances[i] <= m_common.m_far))
+			{
+				m_misc.m_maxLodDistances[i] =
+					clamp(m_misc.m_maxLodDistances[i], m_common.m_near + kEpsilonf, m_common.m_far);
+			}
+
+			if(i != 0 && !ANKI_SCENE_ASSERT(m_misc.m_maxLodDistances[i - 1] < m_misc.m_maxLodDistances[i]))
+			{
+				m_misc.m_maxLodDistances[i] = m_misc.m_maxLodDistances[i - 1] + frustumFraction;
+			}
+		}
+	}
+
 	// Updates that are affected by transform & shape updates
 	if(updated)
 	{
 		m_viewProjMat = m_projMat * Mat4(m_viewMat, Vec4(0.0f, 0.0f, 0.0f, 1.0f));
 		m_shapeMarkedForUpdate = false;
 		m_trfMarkedForUpdate = false;
+		m_miscMarkedForUpdate = false;
 
 		if(m_frustumType == FrustumType::kPerspective)
 		{

+ 106 - 95
AnKi/Scene/Components/FrustumComponent.h

@@ -12,7 +12,7 @@
 #include <AnKi/Collision/ConvexHullShape.h>
 #include <AnKi/Collision/Plane.h>
 #include <AnKi/Resource/Common.h>
-#include <AnKi/Shaders/Include/ClusteredShadingFunctions.h>
+#include <AnKi/Shaders/Include/Common.h>
 
 namespace anki {
 
@@ -76,52 +76,55 @@ public:
 	{
 		ANKI_ASSERT(type >= FrustumType::kFirst && type < FrustumType::kCount);
 		m_frustumType = type;
+		setNear(kDefaultNear);
+		setFar(kDefaultFar);
 		if(m_frustumType == FrustumType::kPerspective)
 		{
-			setPerspective(0.1f, 100.0f, toRad(45.0f), toRad(45.0f));
+			setFovX(kDefaultFovAngle);
+			setFovY(kDefaultFovAngle);
 		}
 		else
 		{
-			setOrthographic(0.1f, 100.0f, 5.0f, -5.0f, 5.0f, -5.0f);
+			setLeft(-5.0f);
+			setRight(5.0f);
+			setBottom(-1.0f);
+			setTop(1.0f);
 		}
 	}
 
-	FrustumType getFrustumType() const
-	{
-		ANKI_ASSERT(m_frustumType != FrustumType::kCount);
-		return m_frustumType;
-	}
-
 	void setPerspective(F32 near, F32 far, F32 fovX, F32 fovY)
 	{
-		ANKI_ASSERT(near > 0.0f && far > 0.0f && near < far);
-		ANKI_ASSERT(fovX > 0.0f && fovY > 0.0f && fovX < kPi && fovY < kPi);
 		ANKI_ASSERT(m_frustumType == FrustumType::kPerspective);
-		m_perspective.m_near = near;
-		m_perspective.m_far = far;
-		m_perspective.m_fovX = fovX;
-		m_perspective.m_fovY = fovY;
-		m_shapeMarkedForUpdate = true;
+		setNear(near);
+		setFar(far);
+		setFovX(fovX);
+		setFovY(fovY);
 	}
 
 	void setOrthographic(F32 near, F32 far, F32 right, F32 left, F32 top, F32 bottom)
 	{
-		ANKI_ASSERT(near > 0.0f && far > 0.0f && near < far);
-		ANKI_ASSERT(right > left && top > bottom);
 		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
-		m_ortho.m_near = near;
-		m_ortho.m_far = far;
-		m_ortho.m_right = right;
-		m_ortho.m_left = left;
-		m_ortho.m_top = top;
-		m_ortho.m_bottom = bottom;
-		m_shapeMarkedForUpdate = true;
+		setNear(near);
+		setFar(far);
+		setLeft(left);
+		setRight(right);
+		setTop(top);
+		setBottom(bottom);
+	}
+
+	FrustumType getFrustumType() const
+	{
+		ANKI_ASSERT(m_frustumType != FrustumType::kCount);
+		return m_frustumType;
 	}
 
 	void setNear(F32 near)
 	{
-		m_common.m_near = near;
-		m_shapeMarkedForUpdate = true;
+		if(ANKI_SCENE_ASSERT(near > 0.0f))
+		{
+			m_common.m_near = near;
+			m_shapeMarkedForUpdate = true;
+		}
 	}
 
 	F32 getNear() const
@@ -131,8 +134,11 @@ public:
 
 	void setFar(F32 far)
 	{
-		m_common.m_far = far;
-		m_shapeMarkedForUpdate = true;
+		if(ANKI_SCENE_ASSERT(far > 0.0f))
+		{
+			m_common.m_far = far;
+			m_shapeMarkedForUpdate = true;
+		}
 	}
 
 	F32 getFar() const
@@ -142,76 +148,88 @@ public:
 
 	void setFovX(F32 fovx)
 	{
-		ANKI_ASSERT(m_frustumType == FrustumType::kPerspective);
-		m_shapeMarkedForUpdate = true;
-		m_perspective.m_fovX = fovx;
+		if(ANKI_SCENE_ASSERT(m_frustumType == FrustumType::kPerspective)
+		   && ANKI_SCENE_ASSERT(fovx > 0.0f && fovx < kPi))
+		{
+			m_shapeMarkedForUpdate = true;
+			m_perspective.m_fovX = fovx;
+		}
 	}
 
 	F32 getFovX() const
 	{
-		ANKI_ASSERT(m_frustumType == FrustumType::kPerspective);
-		return m_perspective.m_fovX;
+		return (ANKI_SCENE_ASSERT(m_frustumType == FrustumType::kPerspective)) ? m_perspective.m_fovX : 0.0f;
 	}
 
 	void setFovY(F32 fovy)
 	{
-		ANKI_ASSERT(m_frustumType == FrustumType::kPerspective);
-		m_shapeMarkedForUpdate = true;
-		m_perspective.m_fovY = fovy;
+		if(ANKI_SCENE_ASSERT(m_frustumType == FrustumType::kPerspective)
+		   && ANKI_SCENE_ASSERT(fovy > 0.0f && fovy < kPi))
+		{
+			m_shapeMarkedForUpdate = true;
+			m_perspective.m_fovY = fovy;
+		}
 	}
 
 	F32 getFovY() const
 	{
-		ANKI_ASSERT(m_frustumType == FrustumType::kPerspective);
-		return m_perspective.m_fovY;
+		return (ANKI_SCENE_ASSERT(m_frustumType == FrustumType::kPerspective)) ? m_perspective.m_fovY : 0.0f;
 	}
 
 	F32 getLeft() const
 	{
-		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
-		return m_ortho.m_left;
+		return (ANKI_SCENE_ASSERT(m_frustumType == FrustumType::kOrthographic)) ? m_ortho.m_left : 0.0f;
 	}
 
 	void setLeft(F32 value)
 	{
-		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
-		m_ortho.m_left = value;
+		if(ANKI_SCENE_ASSERT(m_frustumType == FrustumType::kOrthographic))
+		{
+			m_shapeMarkedForUpdate = true;
+			m_ortho.m_left = value;
+		}
 	}
 
 	F32 getRight() const
 	{
-		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
-		return m_ortho.m_right;
+		return (ANKI_SCENE_ASSERT(m_frustumType == FrustumType::kOrthographic)) ? m_ortho.m_right : 0.0f;
 	}
 
 	void setRight(F32 value)
 	{
-		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
-		m_ortho.m_right = value;
+		if(ANKI_SCENE_ASSERT(m_frustumType == FrustumType::kOrthographic))
+		{
+			m_shapeMarkedForUpdate = true;
+			m_ortho.m_right = value;
+		}
 	}
 
 	F32 getTop() const
 	{
-		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
-		return m_ortho.m_top;
+		return (ANKI_SCENE_ASSERT(m_frustumType == FrustumType::kOrthographic)) ? m_ortho.m_top : 0.0f;
 	}
 
 	void setTop(F32 value)
 	{
-		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
-		m_ortho.m_top = value;
+		if(ANKI_SCENE_ASSERT(m_frustumType == FrustumType::kOrthographic))
+		{
+			m_shapeMarkedForUpdate = true;
+			m_ortho.m_top = value;
+		}
 	}
 
 	F32 getBottom() const
 	{
-		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
-		return m_ortho.m_bottom;
+		return (ANKI_SCENE_ASSERT(m_frustumType == FrustumType::kOrthographic)) ? m_ortho.m_bottom : 0.0f;
 	}
 
 	void setBottom(F32 value)
 	{
-		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
-		m_ortho.m_bottom = value;
+		if(ANKI_SCENE_ASSERT(m_frustumType == FrustumType::kOrthographic))
+		{
+			m_shapeMarkedForUpdate = true;
+			m_ortho.m_bottom = value;
+		}
 	}
 
 	const Transform& getWorldTransform() const
@@ -311,47 +329,26 @@ public:
 		}
 	}
 
-	/// Set how far to render shadows for this frustum or set to negative if you want to use the m_frustun's far.
-	void setEffectiveShadowDistance(F32 distance)
+	void setShadowCascadeDistance(U32 cascade, F32 distance)
 	{
-		m_effectiveShadowDistance = distance;
+		m_misc.m_shadowCascadeDistances[cascade] = distance;
+		m_miscMarkedForUpdate = true;
 	}
 
-	/// How far to render shadows for this frustum.
-	F32 getEffectiveShadowDistance() const
+	F32 getShadowCascadeDistance(U32 cascade) const
 	{
-		const F32 distance = (m_effectiveShadowDistance <= 0.0f) ? m_common.m_far : m_effectiveShadowDistance;
-		ANKI_ASSERT(distance > m_common.m_near && distance <= m_common.m_far);
-		return distance;
-	}
-
-	/// See computeShadowCascadeDistance()
-	void setShadowCascadesDistancePower(F32 power)
-	{
-		ANKI_ASSERT(power >= 1.0f);
-		m_shadowCascadesDistancePower = power;
-	}
-
-	/// See computeShadowCascadeDistance()
-	F32 getShadowCascadesDistancePower() const
-	{
-		return m_shadowCascadesDistancePower;
-	}
-
-	F32 computeShadowCascadeDistance(U32 cascadeIdx) const
-	{
-		return anki::computeShadowCascadeDistance(cascadeIdx, m_shadowCascadesDistancePower,
-												  getEffectiveShadowDistance(), getShadowCascadeCount());
+		return m_misc.m_shadowCascadeDistances[cascade];
 	}
 
 	[[nodiscard]] U32 getShadowCascadeCount() const
 	{
-		return m_shadowCascadeCount;
+		return m_misc.m_shadowCascadeCount;
 	}
 
 	void setShadowCascadeCount(U32 count)
 	{
-		m_shadowCascadeCount = U8(min(count, kMaxShadowCascades));
+		m_misc.m_shadowCascadeCount = U8(count);
+		m_miscMarkedForUpdate = true;
 	}
 
 	const ConvexHullShape& getPerspectiveBoundingShapeWorldSpace() const
@@ -374,15 +371,24 @@ public:
 	/// Set the lod distance. It doesn't interact with the far plane.
 	void setLodDistance(U32 lod, F32 maxDistance)
 	{
-		ANKI_ASSERT(maxDistance > 0.0f);
-		m_maxLodDistances[lod] = maxDistance;
+		m_misc.m_maxLodDistances[lod] = maxDistance;
+		m_miscMarkedForUpdate = true;
+	}
+
+	void setLodDistances(const Array<F32, kMaxLodCount>& distances)
+	{
+		for(U i = 0; i < m_misc.m_maxLodDistances.getSize(); ++i)
+		{
+			ANKI_ASSERT(distances[i] > m_common.m_near && distances[i] <= m_common.m_far);
+			m_misc.m_maxLodDistances[i] = distances[i];
+		}
 	}
 
 	/// See setLodDistance.
 	F32 getLodDistance(U32 lod) const
 	{
-		ANKI_ASSERT(m_maxLodDistances[lod] > 0.0f);
-		return m_maxLodDistances[lod];
+		ANKI_ASSERT(m_misc.m_maxLodDistances[lod] > 0.0f);
+		return m_misc.m_maxLodDistances[lod];
 	}
 
 private:
@@ -414,6 +420,10 @@ private:
 		Obb m_obbW; ///< Including shape
 	};
 
+	static constexpr F32 kDefaultNear = 0.1f;
+	static constexpr F32 kDefaultFar = 100.0f;
+	static constexpr F32 kDefaultFovAngle = toRad(45.0f);
+
 	SceneNode* m_node;
 
 	FrustumType m_frustumType = FrustumType::kCount;
@@ -439,15 +449,15 @@ private:
 	Array<Mat4, kPrevMatrixHistory> m_prevProjMats = {Mat4::getIdentity(), Mat4::getIdentity()};
 	Array<Mat4, kPrevMatrixHistory> m_prevViewProjMats = {Mat4::getIdentity(), Mat4::getIdentity()};
 
-	/// How far to render shadows for this frustum. If negative it's the m_frustum's far.
-	F32 m_effectiveShadowDistance = -1.0f;
-
-	/// Defines the the rate of the cascade distances
-	F32 m_shadowCascadesDistancePower = 1.0f;
+	class
+	{
+	public:
+		Array<F32, kMaxShadowCascades> m_shadowCascadeDistances = {};
 
-	Array<F32, kMaxLodCount - 1> m_maxLodDistances = {};
+		Array<F32, kMaxLodCount - 1> m_maxLodDistances = {};
 
-	U8 m_shadowCascadeCount = 0;
+		U8 m_shadowCascadeCount = 0;
+	} m_misc; ///< Misc stuff that this component just holds.
 
 	class
 	{
@@ -460,6 +470,7 @@ private:
 	FrustumComponentVisibilityTestFlag m_flags = FrustumComponentVisibilityTestFlag::kNone;
 	Bool m_shapeMarkedForUpdate : 1;
 	Bool m_trfMarkedForUpdate : 1;
+	Bool m_miscMarkedForUpdate : 1;
 
 	Bool updateInternal();
 };

+ 6 - 4
AnKi/Scene/Components/LightComponent.cpp

@@ -82,8 +82,10 @@ void LightComponent::setupDirectionalLightQueueElement(const FrustumComponent& f
 	el.m_uuid = m_uuid;
 	el.m_diffuseColor = m_diffColor.xyz();
 	el.m_direction = -m_worldtransform.getRotation().getZAxis().xyz();
-	el.m_effectiveShadowDistance = frustumComp.getEffectiveShadowDistance();
-	el.m_shadowCascadesDistancePower = frustumComp.getShadowCascadesDistancePower();
+	for(U32 i = 0; i < shadowCascadeCount; ++i)
+	{
+		el.m_shadowCascadesDistances[i] = frustumComp.getShadowCascadeDistance(i);
+	}
 	el.m_shadowCascadeCount = U8(shadowCascadeCount);
 	el.m_shadowLayer = kMaxU8;
 
@@ -120,9 +122,9 @@ void LightComponent::setupDirectionalLightQueueElement(const FrustumComponent& f
 			// --------------------------> x
 			//           |
 			// The square distance of A-C is equal to B-C. Solve the equation to find the z.
-			const F32 f = frustumComp.computeShadowCascadeDistance(i); // Cascade far
+			const F32 f = frustumComp.getShadowCascadeDistance(i); // Cascade far
 			const F32 n =
-				(i == 0) ? frustumComp.getNear() : frustumComp.computeShadowCascadeDistance(i - 1); // Cascade near
+				(i == 0) ? frustumComp.getNear() : frustumComp.getShadowCascadeDistance(i - 1); // Cascade near
 			const F32 a = f * tan(fovY / 2.0f) * fovX / fovY;
 			const F32 b = n * tan(fovY / 2.0f) * fovX / fovY;
 			const F32 z = (b * b + n * n - a * a - f * f) / (2.0f * (f - n));

+ 2 - 1
AnKi/Scene/Components/RenderComponent.cpp

@@ -16,7 +16,7 @@ ANKI_SCENE_COMPONENT_STATICS(RenderComponent)
 
 void RenderComponent::allocateAndSetupUniforms(const MaterialResourcePtr& mtl, const RenderQueueDrawContext& ctx,
 											   ConstWeakArray<Mat3x4> transforms, ConstWeakArray<Mat3x4> prevTransforms,
-											   StagingGpuMemoryPool& alloc)
+											   StagingGpuMemoryPool& alloc, const Vec4& positionScaleAndTranslation)
 {
 	ANKI_ASSERT(transforms.getSize() <= kMaxInstanceCount);
 	ANKI_ASSERT(prevTransforms.getSize() == transforms.getSize());
@@ -41,6 +41,7 @@ void RenderComponent::allocateAndSetupUniforms(const MaterialResourcePtr& mtl, c
 			memcpy(&renderableGpuViews->m_worldTransform, &transforms[i], sizeof(renderableGpuViews->m_worldTransform));
 			memcpy(&renderableGpuViews->m_previousWorldTransform, &prevTransforms[i],
 				   sizeof(renderableGpuViews->m_previousWorldTransform));
+			renderableGpuViews->m_positionScaleF32AndTranslationVec3 = positionScaleAndTranslation;
 
 			++renderableGpuViews;
 		}

+ 2 - 1
AnKi/Scene/Components/RenderComponent.h

@@ -104,7 +104,8 @@ public:
 	/// Helper function.
 	static void allocateAndSetupUniforms(const MaterialResourcePtr& mtl, const RenderQueueDrawContext& ctx,
 										 ConstWeakArray<Mat3x4> transforms, ConstWeakArray<Mat3x4> prevTransforms,
-										 StagingGpuMemoryPool& alloc);
+										 StagingGpuMemoryPool& alloc,
+										 const Vec4& positionScaleAndTranslation = Vec4(1.0f, 0.0f, 0.0f, 0.0f));
 
 private:
 	RenderQueueDrawCallback m_callback = nullptr;

+ 6 - 1
AnKi/Scene/ConfigVars.defs.h

@@ -7,8 +7,13 @@ ANKI_CONFIG_VAR_GROUP(SCENE)
 
 ANKI_CONFIG_VAR_F32(Lod0MaxDistance, 20.0f, 1.0f, kMaxF32, "Distance that will be used to calculate the LOD 0")
 ANKI_CONFIG_VAR_F32(Lod1MaxDistance, 40.0f, 2.0f, kMaxF32, "Distance that will be used to calculate the LOD 1")
-ANKI_CONFIG_VAR_U8(SceneShadowCascadeCount, (ANKI_OS_ANDROID) ? 2 : kMaxShadowCascades, 1, kMaxShadowCascades,
+
+ANKI_CONFIG_VAR_U8(SceneShadowCascadeCount, (ANKI_PLATFORM_MOBILE) ? 2 : kMaxShadowCascades, 1, kMaxShadowCascades,
 				   "Max number of shadow cascades for directional lights")
+ANKI_CONFIG_VAR_F32(SceneShadowCascade0Distance, 18.0, 1.0, kMaxF32, "The distance of the 1st cascade")
+ANKI_CONFIG_VAR_F32(SceneShadowCascade1Distance, 40.0, 1.0, kMaxF32, "The distance of the 2nd cascade")
+ANKI_CONFIG_VAR_F32(SceneShadowCascade2Distance, 80.0, 1.0, kMaxF32, "The distance of the 3rd cascade")
+ANKI_CONFIG_VAR_F32(SceneShadowCascade3Distance, 200.0, 1.0, kMaxF32, "The distance of the 4th cascade")
 
 ANKI_CONFIG_VAR_U32(SceneOctreeMaxDepth, 5, 2, 10, "The max depth of the octree")
 ANKI_CONFIG_VAR_F32(SceneEarlyZDistance, (ANKI_PLATFORM_MOBILE) ? 0.0f : 10.0f, 0.0f, kMaxF32,

+ 10 - 4
AnKi/Scene/GlobalIlluminationProbeNode.cpp

@@ -90,7 +90,7 @@ GlobalIlluminationProbeNode::GlobalIlluminationProbeNode(SceneGraph* scene, CStr
 
 	// The frustum components
 	constexpr F32 ang = toRad(90.0f);
-	const F32 zNear = kClusterObjectFrustumNearPlane;
+	constexpr F32 zNear = kClusterObjectFrustumNearPlane;
 
 	Mat3 rot;
 	rot = Mat3(Euler(0.0f, -kPi / 2.0f, 0.0f)) * Mat3(Euler(0.0f, 0.0f, kPi));
@@ -113,11 +113,11 @@ GlobalIlluminationProbeNode::GlobalIlluminationProbeNode(SceneGraph* scene, CStr
 
 		FrustumComponent* frc = newComponent<FrustumComponent>();
 		frc->setFrustumType(FrustumType::kPerspective);
-		const F32 tempEffectiveDistance = 1.0f;
-		frc->setPerspective(zNear, tempEffectiveDistance, ang, ang);
+		frc->setPerspective(zNear, 10.0f, ang, ang);
+		frc->setLodDistances({1.0f, 2.0f, 3.0f});
 		frc->setWorldTransform(m_cubeFaceTransforms[i]);
 		frc->setEnabledVisibilityTests(FrustumComponentVisibilityTestFlag::kNone);
-		frc->setEffectiveShadowDistance(getConfig().getSceneReflectionProbeShadowEffectiveDistance());
+		frc->setShadowCascadeDistance(0, 10.0f);
 		frc->setShadowCascadeCount(1);
 	}
 
@@ -159,6 +159,12 @@ void GlobalIlluminationProbeNode::onShapeUpdateOrProbeNeedsRendering()
 
 			frc.setWorldTransform(trf);
 			frc.setFar(effectiveDistance);
+			frc.setShadowCascadeDistance(
+				0, min(effectiveDistance, getConfig().getSceneReflectionProbeShadowEffectiveDistance()));
+
+			// Add something to avoid complains
+			frc.setLodDistances(
+				{frc.getNear() + kEpsilonf, frc.getNear() + 2.0f * kEpsilonf, frc.getNear() + 3.0f * kEpsilonf});
 			++count;
 		});
 

+ 60 - 33
AnKi/Scene/ModelNode.cpp

@@ -43,6 +43,10 @@ class ModelNode::RenderProxy
 {
 public:
 	ModelNode* m_node = nullptr;
+
+	/// Uncompresses the mesh positions to the local view. The scale should be uniform because it will be applied to
+	/// normals and tangents and non-uniform data will cause problems.
+	Mat4 m_compressedToModelTransform = Mat4::getIdentity();
 };
 
 ModelNode::ModelNode(SceneGraph* scene, CString name)
@@ -165,6 +169,7 @@ void ModelNode::initRenderComponents()
 
 	for(U32 patchIdx = 0; patchIdx < model->getModelPatches().getSize(); ++patchIdx)
 	{
+		const ModelPatch& modelPatch = model->getModelPatches()[patchIdx];
 		RenderComponent& rc = getNthComponentOfType<RenderComponent>(patchIdx);
 		rc.initRaster(
 			[](RenderQueueDrawContext& ctx, ConstWeakArray<void*> userData) {
@@ -174,10 +179,9 @@ void ModelNode::initRenderComponents()
 			},
 			&m_renderProxies[patchIdx], modelc.getRenderMergeKeys()[patchIdx]);
 
-		rc.setFlagsFromMaterial(model->getModelPatches()[patchIdx].getMaterial());
+		rc.setFlagsFromMaterial(modelPatch.getMaterial());
 
-		if(!!(model->getModelPatches()[patchIdx].getMaterial()->getRenderingTechniques()
-			  & RenderingTechniqueBit::kAllRt))
+		if(!!(modelPatch.getMaterial()->getRenderingTechniques() & RenderingTechniqueBit::kAllRt))
 		{
 			rc.initRayTracing(
 				[](U32 lod, const void* userData, RayTracingInstanceQueueElement& el) {
@@ -188,7 +192,15 @@ void ModelNode::initRenderComponents()
 				&m_renderProxies[patchIdx]);
 		}
 
-		m_renderProxies[patchIdx].m_node = this;
+		// Init the proxy
+		RenderProxy& proxy = m_renderProxies[patchIdx];
+		proxy.m_node = this;
+
+		const MeshResource& meshResource = *modelPatch.getMesh();
+		proxy.m_compressedToModelTransform.setTranslationPart(meshResource.getPositionsTranslation().xyz1());
+		proxy.m_compressedToModelTransform(0, 0) = meshResource.getPositionsScale();
+		proxy.m_compressedToModelTransform(1, 1) = meshResource.getPositionsScale();
+		proxy.m_compressedToModelTransform(2, 2) = meshResource.getPositionsScale();
 	}
 }
 
@@ -207,11 +219,24 @@ void ModelNode::draw(RenderQueueDrawContext& ctx, ConstWeakArray<void*> userData
 		const SkinComponent& skinc = getFirstComponentOfType<SkinComponent>();
 
 		// Transforms
+		auto computeTranform = [&](const Transform& trf) -> Mat3x4 {
+			if(skinc.isEnabled())
+			{
+				return Mat3x4(trf);
+			}
+			else
+			{
+				// Bake the decompression in the model matrix
+				const Mat4 m4 = Mat4(trf) * m_renderProxies[modelPatchIdx].m_compressedToModelTransform;
+				const Mat3x4 out(m4);
+				return out;
+			}
+		};
 		Array<Mat3x4, kMaxInstanceCount> trfs;
 		Array<Mat3x4, kMaxInstanceCount> prevTrfs;
 		const MoveComponent& movec = getFirstComponentOfType<MoveComponent>();
-		trfs[0] = Mat3x4(movec.getWorldTransform());
-		prevTrfs[0] = Mat3x4(movec.getPreviousWorldTransform());
+		trfs[0] = computeTranform(movec.getWorldTransform());
+		prevTrfs[0] = computeTranform(movec.getPreviousWorldTransform());
 		Bool moved = trfs[0] != prevTrfs[0];
 		for(U32 i = 1; i < instanceCount; ++i)
 		{
@@ -222,8 +247,8 @@ void ModelNode::draw(RenderQueueDrawContext& ctx, ConstWeakArray<void*> userData
 			ANKI_ASSERT(otherNodeModelPatchIdx == modelPatchIdx);
 
 			const MoveComponent& otherNodeMovec = otherNode.getFirstComponentOfType<MoveComponent>();
-			trfs[i] = Mat3x4(otherNodeMovec.getWorldTransform());
-			prevTrfs[i] = Mat3x4(otherNodeMovec.getPreviousWorldTransform());
+			trfs[i] = computeTranform(otherNodeMovec.getWorldTransform());
+			prevTrfs[i] = computeTranform(otherNodeMovec.getPreviousWorldTransform());
 
 			moved = moved || (trfs[i] != prevTrfs[i]);
 		}
@@ -257,29 +282,38 @@ void ModelNode::draw(RenderQueueDrawContext& ctx, ConstWeakArray<void*> userData
 		cmdb->bindShaderProgram(modelInf.m_program);
 
 		// Uniforms
+		const Vec4 positionScaleAndTransform(
+			m_renderProxies[modelPatchIdx].m_compressedToModelTransform(0, 0),
+			m_renderProxies[modelPatchIdx].m_compressedToModelTransform.getTranslationPart().xyz());
 		RenderComponent::allocateAndSetupUniforms(
 			modelc.getModelResource()->getModelPatches()[modelPatchIdx].getMaterial(), ctx,
 			ConstWeakArray<Mat3x4>(&trfs[0], instanceCount), ConstWeakArray<Mat3x4>(&prevTrfs[0], instanceCount),
-			*ctx.m_stagingGpuAllocator);
+			*ctx.m_stagingGpuAllocator, positionScaleAndTransform);
 
-		// Set attributes
-		for(U i = 0; i < modelInf.m_vertexAttributeCount; ++i)
+		// Bind attributes & vertex buffers
+		for(VertexStreamId streamId :
+			EnumIterable<VertexStreamId>(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
 		{
-			const ModelVertexAttribute& attrib = modelInf.m_vertexAttributes[i];
-			ANKI_ASSERT(attrib.m_format != Format::kNone);
-			cmdb->setVertexAttribute(U32(attrib.m_location), attrib.m_bufferBinding, attrib.m_format,
-									 attrib.m_relativeOffset);
-		}
+			if(modelInf.m_vertexBufferOffsets[streamId] == kMaxPtrSize)
+			{
+				continue;
+			}
 
-		// Set vertex buffers
-		for(U32 i = 0; i < modelInf.m_vertexBufferBindingCount; ++i)
-		{
-			const ModelVertexBufferBinding& binding = modelInf.m_vertexBufferBindings[i];
-			cmdb->bindVertexBuffer(i, binding.m_buffer, binding.m_offset, binding.m_stride, VertexStepRate::kVertex);
+			const U32 attribLocation = U32(streamId);
+			const U32 bufferBinding = U32(streamId);
+			const Format fmt = kMeshRelatedVertexStreamFormats[streamId];
+			const U32 relativeOffset = 0;
+			const U32 vertexStride = getFormatInfo(fmt).m_texelSize;
+
+			cmdb->setVertexAttribute(attribLocation, bufferBinding, fmt, relativeOffset);
+
+			cmdb->bindVertexBuffer(bufferBinding, getUnifiedGeometryMemoryPool().getVertexBuffer(),
+								   modelInf.m_vertexBufferOffsets[streamId], vertexStride, VertexStepRate::kVertex);
 		}
 
-		// Index buffer
-		cmdb->bindIndexBuffer(modelInf.m_indexBuffer, modelInf.m_indexBufferOffset, IndexType::kU16);
+		// Bind index buffer
+		cmdb->bindIndexBuffer(getUnifiedGeometryMemoryPool().getVertexBuffer(), modelInf.m_indexBufferOffset,
+							  IndexType::kU16);
 
 		// Draw
 		cmdb->drawElements(PrimitiveTopology::kTriangles, modelInf.m_indexCount, instanceCount, modelInf.m_firstIndex,
@@ -399,18 +433,11 @@ void ModelNode::setupRayTracingInstanceQueueElement(U32 lod, U32 modelPatchIdx,
 	el.m_bottomLevelAccelerationStructure = info.m_bottomLevelAccelerationStructure.get();
 
 	const MoveComponent& movec = getFirstComponentOfType<MoveComponent>();
-	el.m_transform = Mat3x4(movec.getWorldTransform());
 
-	el.m_shaderGroupHandleIndex = info.m_shaderGroupHandleIndex;
+	const Mat4 m4 = Mat4(movec.getWorldTransform()) * m_renderProxies[modelPatchIdx].m_compressedToModelTransform;
+	el.m_transform = Mat3x4(m4);
 
-	// References
-	el.m_grObjectCount = info.m_grObjectReferences.getSize();
-	for(U32 i = 0; i < el.m_grObjectCount; ++i)
-	{
-		// const_cast hack follows. To avoid the const you could copy m_grObjectReferences[i] to a GrObjectPtr and then
-		// call get() on that. But that will cost 2 atomic operations
-		el.m_grObjects[i] = const_cast<GrObject*>(info.m_grObjectReferences[i].get());
-	}
+	el.m_shaderGroupHandleIndex = info.m_shaderGroupHandleIndex;
 }
 
 } // end namespace anki

+ 8 - 1
AnKi/Scene/ReflectionProbeNode.cpp

@@ -105,9 +105,10 @@ ReflectionProbeNode::ReflectionProbeNode(SceneGraph* scene, CString name)
 		FrustumComponent* frc = newComponent<FrustumComponent>();
 		frc->setFrustumType(FrustumType::kPerspective);
 		frc->setPerspective(kClusterObjectFrustumNearPlane, 10.0f, ang, ang);
+		frc->setLodDistances({1.0f, 2.0f, 3.0f});
 		frc->setWorldTransform(m_frustumTransforms[i]);
 		frc->setEnabledVisibilityTests(FrustumComponentVisibilityTestFlag::kNone);
-		frc->setEffectiveShadowDistance(getConfig().getSceneReflectionProbeShadowEffectiveDistance());
+		frc->setShadowCascadeDistance(0, 10.0f);
 		frc->setShadowCascadeCount(1);
 	}
 
@@ -159,6 +160,12 @@ void ReflectionProbeNode::onShapeUpdate(ReflectionProbeComponent& reflc)
 	// Update frustum components
 	iterateComponentsOfType<FrustumComponent>([&](FrustumComponent& frc) {
 		frc.setFar(effectiveDistance);
+		frc.setShadowCascadeDistance(
+			0, min(effectiveDistance, getConfig().getSceneReflectionProbeShadowEffectiveDistance()));
+
+		// Add something to avoid complains
+		frc.setLodDistances(
+			{frc.getNear() + kEpsilonf, frc.getNear() + 2.0f * kEpsilonf, frc.getNear() + 3.0f * kEpsilonf});
 	});
 
 	// Update the spatial comp

+ 3 - 1
AnKi/Scene/SceneGraph.cpp

@@ -54,7 +54,8 @@ SceneGraph::~SceneGraph()
 
 Error SceneGraph::init(AllocAlignedCallback allocCb, void* allocCbData, ThreadHive* threadHive,
 					   ResourceManager* resources, Input* input, ScriptManager* scriptManager, UiManager* uiManager,
-					   ConfigSet* config, const Timestamp* globalTimestamp)
+					   ConfigSet* config, const Timestamp* globalTimestamp,
+					   UnifiedGeometryMemoryPool* unifiedGeometryMemPool)
 {
 	m_globalTimestamp = globalTimestamp;
 	m_threadHive = threadHive;
@@ -65,6 +66,7 @@ Error SceneGraph::init(AllocAlignedCallback allocCb, void* allocCbData, ThreadHi
 	m_scriptManager = scriptManager;
 	m_uiManager = uiManager;
 	m_config = config;
+	m_unifiedGeometryMemPool = unifiedGeometryMemPool;
 
 	m_pool.init(allocCb, allocCbData);
 	m_framePool.init(allocCb, allocCbData, 1 * 1024 * 1024);

+ 3 - 1
AnKi/Scene/SceneGraph.h

@@ -24,6 +24,7 @@ class ConfigSet;
 class PerspectiveCameraNode;
 class Octree;
 class UiManager;
+class UnifiedGeometryMemoryPool;
 
 /// @addtogroup scene
 /// @{
@@ -50,7 +51,7 @@ public:
 
 	Error init(AllocAlignedCallback allocCb, void* allocCbData, ThreadHive* threadHive, ResourceManager* resources,
 			   Input* input, ScriptManager* scriptManager, UiManager* uiManager, ConfigSet* config,
-			   const Timestamp* globalTimestamp);
+			   const Timestamp* globalTimestamp, UnifiedGeometryMemoryPool* unifiedGeometryMemPool);
 
 	Timestamp getGlobalTimestamp() const
 	{
@@ -236,6 +237,7 @@ private:
 	ScriptManager* m_scriptManager = nullptr;
 	UiManager* m_uiManager = nullptr;
 	ConfigSet* m_config = nullptr;
+	UnifiedGeometryMemoryPool* m_unifiedGeometryMemPool = nullptr;
 
 	mutable HeapMemoryPool m_pool;
 	mutable StackMemoryPool m_framePool;

+ 5 - 0
AnKi/Scene/SceneNode.cpp

@@ -77,4 +77,9 @@ const ConfigSet& SceneNode::getConfig() const
 	return m_scene->getConfig();
 }
 
+const UnifiedGeometryMemoryPool& SceneNode::getUnifiedGeometryMemoryPool() const
+{
+	return *m_scene->m_unifiedGeometryMemPool;
+}
+
 } // end namespace anki

+ 3 - 0
AnKi/Scene/SceneNode.h

@@ -17,6 +17,7 @@ namespace anki {
 // Forward
 class ResourceManager;
 class ConfigSet;
+class UnifiedGeometryMemoryPool;
 
 /// @addtogroup scene
 /// @{
@@ -73,6 +74,8 @@ public:
 
 	Timestamp getGlobalTimestamp() const;
 
+	const UnifiedGeometryMemoryPool& getUnifiedGeometryMemoryPool() const;
+
 	Timestamp getComponentMaxTimestamp() const
 	{
 		return m_maxComponentTimestamp;

+ 5 - 2
AnKi/Scene/Visibility.cpp

@@ -107,7 +107,6 @@ void VisibilityContext::submitNewWork(const FrustumComponent& frc, const Frustum
 	{
 		rqueue.m_cameraFovX = rqueue.m_cameraFovY = 0.0f;
 	}
-	rqueue.m_effectiveShadowDistance = frc.getEffectiveShadowDistance();
 
 	StackMemoryPool& pool = m_scene->getFrameMemoryPool();
 
@@ -414,7 +413,11 @@ void VisibilityTestTask::test(ThreadHive& hive, U32 taskId)
 				const Plane& nearPlane = primaryFrc.getViewPlanes()[FrustumPlaneType::kNear];
 				const F32 distFromFrustum = max(0.0f, testPlane(nearPlane, spatialc->getAabbWorldSpace()));
 
-				castsShadow = distFromFrustum < primaryFrc.getEffectiveShadowDistance();
+				const F32 shadowEffectiveDistance =
+					(primaryFrc.getShadowCascadeCount() > 0)
+						? primaryFrc.getShadowCascadeDistance(primaryFrc.getShadowCascadeCount() - 1)
+						: primaryFrc.getFar();
+				castsShadow = distFromFrustum < shadowEffectiveDistance;
 			}
 
 			switch(lc->getLightComponentType())

+ 66 - 58
AnKi/Script/Scene.cpp

@@ -62,7 +62,7 @@ using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 using WeakArrayBodyComponentPtr = WeakArray<BodyComponent*>;
 
 LuaUserDataTypeInfo luaUserDataTypeInfoWeakArraySceneNodePtr = {
-	-8739932090814685424, "WeakArraySceneNodePtr", LuaUserData::computeSizeForGarbageCollected<WeakArraySceneNodePtr>(),
+	2932583432340840328, "WeakArraySceneNodePtr", LuaUserData::computeSizeForGarbageCollected<WeakArraySceneNodePtr>(),
 	nullptr, nullptr};
 
 template<>
@@ -182,7 +182,7 @@ static inline void wrapWeakArraySceneNodePtr(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoWeakArrayBodyComponentPtr = {
-	-7706209051073311304, "WeakArrayBodyComponentPtr",
+	7416622643466936580, "WeakArrayBodyComponentPtr",
 	LuaUserData::computeSizeForGarbageCollected<WeakArrayBodyComponentPtr>(), nullptr, nullptr};
 
 template<>
@@ -301,7 +301,7 @@ static inline void wrapWeakArrayBodyComponentPtr(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoMoveComponent = {-8687627984466261764, "MoveComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoMoveComponent = {-1620086387227254862, "MoveComponent",
 														LuaUserData::computeSizeForGarbageCollected<MoveComponent>(),
 														nullptr, nullptr};
 
@@ -699,7 +699,7 @@ static inline void wrapMoveComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoLightComponent = {7365641557642621447, "LightComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoLightComponent = {-4595315366869427277, "LightComponent",
 														 LuaUserData::computeSizeForGarbageCollected<LightComponent>(),
 														 nullptr, nullptr};
 
@@ -1263,7 +1263,7 @@ static inline void wrapLightComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoDecalComponent = {5204471888737717555, "DecalComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoDecalComponent = {740153017412343544, "DecalComponent",
 														 LuaUserData::computeSizeForGarbageCollected<DecalComponent>(),
 														 nullptr, nullptr};
 
@@ -1467,7 +1467,7 @@ static inline void wrapDecalComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoLensFlareComponent = {
-	3129239218800905411, "LensFlareComponent", LuaUserData::computeSizeForGarbageCollected<LensFlareComponent>(),
+	-2298395752528036249, "LensFlareComponent", LuaUserData::computeSizeForGarbageCollected<LensFlareComponent>(),
 	nullptr, nullptr};
 
 template<>
@@ -1639,7 +1639,7 @@ static inline void wrapLensFlareComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoBodyComponent = {-2321292920370861788, "BodyComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoBodyComponent = {5546317979946767184, "BodyComponent",
 														LuaUserData::computeSizeForGarbageCollected<BodyComponent>(),
 														nullptr, nullptr};
 
@@ -1812,7 +1812,7 @@ static inline void wrapBodyComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoTriggerComponent = {
-	8727234490176981006, "TriggerComponent", LuaUserData::computeSizeForGarbageCollected<TriggerComponent>(), nullptr,
+	2156310951712001431, "TriggerComponent", LuaUserData::computeSizeForGarbageCollected<TriggerComponent>(), nullptr,
 	nullptr};
 
 template<>
@@ -1976,7 +1976,7 @@ static inline void wrapTriggerComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoFogDensityComponent = {
-	-1966916321067375525, "FogDensityComponent", LuaUserData::computeSizeForGarbageCollected<FogDensityComponent>(),
+	5865180144883120478, "FogDensityComponent", LuaUserData::computeSizeForGarbageCollected<FogDensityComponent>(),
 	nullptr, nullptr};
 
 template<>
@@ -2180,7 +2180,7 @@ static inline void wrapFogDensityComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoFrustumComponent = {
-	-1191485642898408008, "FrustumComponent", LuaUserData::computeSizeForGarbageCollected<FrustumComponent>(), nullptr,
+	7838666232094395179, "FrustumComponent", LuaUserData::computeSizeForGarbageCollected<FrustumComponent>(), nullptr,
 	nullptr};
 
 template<>
@@ -2253,14 +2253,14 @@ static int wrapFrustumComponentsetPerspective(lua_State* l)
 	return 0;
 }
 
-/// Pre-wrap method FrustumComponent::setShadowCascadesDistancePower.
-static inline int pwrapFrustumComponentsetShadowCascadesDistancePower(lua_State* l)
+/// Pre-wrap method FrustumComponent::setShadowCascadeDistance.
+static inline int pwrapFrustumComponentsetShadowCascadeDistance(lua_State* l)
 {
 	[[maybe_unused]] LuaUserData* ud;
 	[[maybe_unused]] void* voidp;
 	[[maybe_unused]] PtrSize size;
 
-	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 2)))
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 3)))
 	{
 		return -1;
 	}
@@ -2274,22 +2274,28 @@ static inline int pwrapFrustumComponentsetShadowCascadesDistancePower(lua_State*
 	FrustumComponent* self = ud->getData<FrustumComponent>();
 
 	// Pop arguments
-	F32 arg0;
+	U32 arg0;
 	if(ANKI_UNLIKELY(LuaBinder::checkNumber(l, 2, arg0)))
 	{
 		return -1;
 	}
 
+	F32 arg1;
+	if(ANKI_UNLIKELY(LuaBinder::checkNumber(l, 3, arg1)))
+	{
+		return -1;
+	}
+
 	// Call the method
-	self->setShadowCascadesDistancePower(arg0);
+	self->setShadowCascadeDistance(arg0, arg1);
 
 	return 0;
 }
 
-/// Wrap method FrustumComponent::setShadowCascadesDistancePower.
-static int wrapFrustumComponentsetShadowCascadesDistancePower(lua_State* l)
+/// Wrap method FrustumComponent::setShadowCascadeDistance.
+static int wrapFrustumComponentsetShadowCascadeDistance(lua_State* l)
 {
-	int res = pwrapFrustumComponentsetShadowCascadesDistancePower(l);
+	int res = pwrapFrustumComponentsetShadowCascadeDistance(l);
 	if(res >= 0)
 	{
 		return res;
@@ -2299,8 +2305,8 @@ static int wrapFrustumComponentsetShadowCascadesDistancePower(lua_State* l)
 	return 0;
 }
 
-/// Pre-wrap method FrustumComponent::setEffectiveShadowDistance.
-static inline int pwrapFrustumComponentsetEffectiveShadowDistance(lua_State* l)
+/// Pre-wrap method FrustumComponent::getShadowCascadeDistance.
+static inline int pwrapFrustumComponentgetShadowCascadeDistance(lua_State* l)
 {
 	[[maybe_unused]] LuaUserData* ud;
 	[[maybe_unused]] void* voidp;
@@ -2320,22 +2326,25 @@ static inline int pwrapFrustumComponentsetEffectiveShadowDistance(lua_State* l)
 	FrustumComponent* self = ud->getData<FrustumComponent>();
 
 	// Pop arguments
-	F32 arg0;
+	U32 arg0;
 	if(ANKI_UNLIKELY(LuaBinder::checkNumber(l, 2, arg0)))
 	{
 		return -1;
 	}
 
 	// Call the method
-	self->setEffectiveShadowDistance(arg0);
+	F32 ret = self->getShadowCascadeDistance(arg0);
 
-	return 0;
+	// Push return value
+	lua_pushnumber(l, lua_Number(ret));
+
+	return 1;
 }
 
-/// Wrap method FrustumComponent::setEffectiveShadowDistance.
-static int wrapFrustumComponentsetEffectiveShadowDistance(lua_State* l)
+/// Wrap method FrustumComponent::getShadowCascadeDistance.
+static int wrapFrustumComponentgetShadowCascadeDistance(lua_State* l)
 {
-	int res = pwrapFrustumComponentsetEffectiveShadowDistance(l);
+	int res = pwrapFrustumComponentgetShadowCascadeDistance(l);
 	if(res >= 0)
 	{
 		return res;
@@ -2350,14 +2359,13 @@ static inline void wrapFrustumComponent(lua_State* l)
 {
 	LuaBinder::createClass(l, &luaUserDataTypeInfoFrustumComponent);
 	LuaBinder::pushLuaCFuncMethod(l, "setPerspective", wrapFrustumComponentsetPerspective);
-	LuaBinder::pushLuaCFuncMethod(l, "setShadowCascadesDistancePower",
-								  wrapFrustumComponentsetShadowCascadesDistancePower);
-	LuaBinder::pushLuaCFuncMethod(l, "setEffectiveShadowDistance", wrapFrustumComponentsetEffectiveShadowDistance);
+	LuaBinder::pushLuaCFuncMethod(l, "setShadowCascadeDistance", wrapFrustumComponentsetShadowCascadeDistance);
+	LuaBinder::pushLuaCFuncMethod(l, "getShadowCascadeDistance", wrapFrustumComponentgetShadowCascadeDistance);
 	lua_settop(l, 0);
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoGlobalIlluminationProbeComponent = {
-	-1018126859883492712, "GlobalIlluminationProbeComponent",
+	8598207783043270175, "GlobalIlluminationProbeComponent",
 	LuaUserData::computeSizeForGarbageCollected<GlobalIlluminationProbeComponent>(), nullptr, nullptr};
 
 template<>
@@ -2604,7 +2612,7 @@ static inline void wrapGlobalIlluminationProbeComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoReflectionProbeComponent = {
-	-927979292880454957, "ReflectionProbeComponent",
+	2833571482117312123, "ReflectionProbeComponent",
 	LuaUserData::computeSizeForGarbageCollected<ReflectionProbeComponent>(), nullptr, nullptr};
 
 template<>
@@ -2720,7 +2728,7 @@ static inline void wrapReflectionProbeComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoParticleEmitterComponent = {
-	-6511516637176772129, "ParticleEmitterComponent",
+	-6166671610891958399, "ParticleEmitterComponent",
 	LuaUserData::computeSizeForGarbageCollected<ParticleEmitterComponent>(), nullptr, nullptr};
 
 template<>
@@ -2794,7 +2802,7 @@ static inline void wrapParticleEmitterComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoGpuParticleEmitterComponent = {
-	7059837890409742045, "GpuParticleEmitterComponent",
+	6131347001832418908, "GpuParticleEmitterComponent",
 	LuaUserData::computeSizeForGarbageCollected<GpuParticleEmitterComponent>(), nullptr, nullptr};
 
 template<>
@@ -2867,7 +2875,7 @@ static inline void wrapGpuParticleEmitterComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoModelComponent = {-3624966836559375132, "ModelComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoModelComponent = {5260828216114469509, "ModelComponent",
 														 LuaUserData::computeSizeForGarbageCollected<ModelComponent>(),
 														 nullptr, nullptr};
 
@@ -2940,7 +2948,7 @@ static inline void wrapModelComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoSkinComponent = {-8546506900929446763, "SkinComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoSkinComponent = {2239714295110433902, "SkinComponent",
 														LuaUserData::computeSizeForGarbageCollected<SkinComponent>(),
 														nullptr, nullptr};
 
@@ -3014,7 +3022,7 @@ static inline void wrapSkinComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoSkyboxComponent = {
-	-5875722332241774365, "SkyboxComponent", LuaUserData::computeSizeForGarbageCollected<SkyboxComponent>(), nullptr,
+	-5753471164249885311, "SkyboxComponent", LuaUserData::computeSizeForGarbageCollected<SkyboxComponent>(), nullptr,
 	nullptr};
 
 template<>
@@ -3316,7 +3324,7 @@ static inline void wrapSkyboxComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoSceneNode = {
-	-5266826471794230711, "SceneNode", LuaUserData::computeSizeForGarbageCollected<SceneNode>(), nullptr, nullptr};
+	-296763319637754850, "SceneNode", LuaUserData::computeSizeForGarbageCollected<SceneNode>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<SceneNode>()
@@ -4261,7 +4269,7 @@ static inline void wrapSceneNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoModelNode = {
-	-8050065867798521056, "ModelNode", LuaUserData::computeSizeForGarbageCollected<ModelNode>(), nullptr, nullptr};
+	809850193274775486, "ModelNode", LuaUserData::computeSizeForGarbageCollected<ModelNode>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<ModelNode>()
@@ -4324,7 +4332,7 @@ static inline void wrapModelNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoPerspectiveCameraNode = {
-	4225474333355308316, "PerspectiveCameraNode", LuaUserData::computeSizeForGarbageCollected<PerspectiveCameraNode>(),
+	-1537597433130477556, "PerspectiveCameraNode", LuaUserData::computeSizeForGarbageCollected<PerspectiveCameraNode>(),
 	nullptr, nullptr};
 
 template<>
@@ -4387,7 +4395,7 @@ static inline void wrapPerspectiveCameraNode(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoPointLightNode = {-476103666317335701, "PointLightNode",
+LuaUserDataTypeInfo luaUserDataTypeInfoPointLightNode = {-29245873346871954, "PointLightNode",
 														 LuaUserData::computeSizeForGarbageCollected<PointLightNode>(),
 														 nullptr, nullptr};
 
@@ -4451,7 +4459,7 @@ static inline void wrapPointLightNode(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoSpotLightNode = {-9028941236445099739, "SpotLightNode",
+LuaUserDataTypeInfo luaUserDataTypeInfoSpotLightNode = {-6340019612898178938, "SpotLightNode",
 														LuaUserData::computeSizeForGarbageCollected<SpotLightNode>(),
 														nullptr, nullptr};
 
@@ -4516,7 +4524,7 @@ static inline void wrapSpotLightNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoDirectionalLightNode = {
-	-5885044536233253731, "DirectionalLightNode", LuaUserData::computeSizeForGarbageCollected<DirectionalLightNode>(),
+	-650419786319643186, "DirectionalLightNode", LuaUserData::computeSizeForGarbageCollected<DirectionalLightNode>(),
 	nullptr, nullptr};
 
 template<>
@@ -4580,7 +4588,7 @@ static inline void wrapDirectionalLightNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoStaticCollisionNode = {
-	-3321869028945608148, "StaticCollisionNode", LuaUserData::computeSizeForGarbageCollected<StaticCollisionNode>(),
+	-3227727713593992698, "StaticCollisionNode", LuaUserData::computeSizeForGarbageCollected<StaticCollisionNode>(),
 	nullptr, nullptr};
 
 template<>
@@ -4644,7 +4652,7 @@ static inline void wrapStaticCollisionNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoParticleEmitterNode = {
-	7361250518817995771, "ParticleEmitterNode", LuaUserData::computeSizeForGarbageCollected<ParticleEmitterNode>(),
+	6918622436947614437, "ParticleEmitterNode", LuaUserData::computeSizeForGarbageCollected<ParticleEmitterNode>(),
 	nullptr, nullptr};
 
 template<>
@@ -4708,7 +4716,7 @@ static inline void wrapParticleEmitterNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoGpuParticleEmitterNode = {
-	-2839508838597227498, "GpuParticleEmitterNode",
+	-6059315858530946064, "GpuParticleEmitterNode",
 	LuaUserData::computeSizeForGarbageCollected<GpuParticleEmitterNode>(), nullptr, nullptr};
 
 template<>
@@ -4772,7 +4780,7 @@ static inline void wrapGpuParticleEmitterNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoReflectionProbeNode = {
-	-5375614308316633893, "ReflectionProbeNode", LuaUserData::computeSizeForGarbageCollected<ReflectionProbeNode>(),
+	512701379852882043, "ReflectionProbeNode", LuaUserData::computeSizeForGarbageCollected<ReflectionProbeNode>(),
 	nullptr, nullptr};
 
 template<>
@@ -4836,7 +4844,7 @@ static inline void wrapReflectionProbeNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoDecalNode = {
-	5640592796414838784, "DecalNode", LuaUserData::computeSizeForGarbageCollected<DecalNode>(), nullptr, nullptr};
+	5216427613143872435, "DecalNode", LuaUserData::computeSizeForGarbageCollected<DecalNode>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<DecalNode>()
@@ -4899,7 +4907,7 @@ static inline void wrapDecalNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoTriggerNode = {
-	6454325227279593862, "TriggerNode", LuaUserData::computeSizeForGarbageCollected<TriggerNode>(), nullptr, nullptr};
+	5997852190170793979, "TriggerNode", LuaUserData::computeSizeForGarbageCollected<TriggerNode>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<TriggerNode>()
@@ -4961,7 +4969,7 @@ static inline void wrapTriggerNode(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoFogDensityNode = {-2016225087705583098, "FogDensityNode",
+LuaUserDataTypeInfo luaUserDataTypeInfoFogDensityNode = {-6018324113689458139, "FogDensityNode",
 														 LuaUserData::computeSizeForGarbageCollected<FogDensityNode>(),
 														 nullptr, nullptr};
 
@@ -5026,7 +5034,7 @@ static inline void wrapFogDensityNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoGlobalIlluminationProbeNode = {
-	881842436982629931, "GlobalIlluminationProbeNode",
+	-4557094370355935221, "GlobalIlluminationProbeNode",
 	LuaUserData::computeSizeForGarbageCollected<GlobalIlluminationProbeNode>(), nullptr, nullptr};
 
 template<>
@@ -5090,7 +5098,7 @@ static inline void wrapGlobalIlluminationProbeNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoSkyboxNode = {
-	-2160062479151552786, "SkyboxNode", LuaUserData::computeSizeForGarbageCollected<SkyboxNode>(), nullptr, nullptr};
+	-487731404906855306, "SkyboxNode", LuaUserData::computeSizeForGarbageCollected<SkyboxNode>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<SkyboxNode>()
@@ -5153,7 +5161,7 @@ static inline void wrapSkyboxNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoSceneGraph = {
-	1565953082694138001, "SceneGraph", LuaUserData::computeSizeForGarbageCollected<SceneGraph>(), nullptr, nullptr};
+	-3165171856408475527, "SceneGraph", LuaUserData::computeSizeForGarbageCollected<SceneGraph>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<SceneGraph>()
@@ -6058,7 +6066,7 @@ static inline void wrapSceneGraph(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoEvent = {-127482733468747645, "Event",
+LuaUserDataTypeInfo luaUserDataTypeInfoEvent = {-327249927871287227, "Event",
 												LuaUserData::computeSizeForGarbageCollected<Event>(), nullptr, nullptr};
 
 template<>
@@ -6124,7 +6132,7 @@ static inline void wrapEvent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoLightEvent = {
-	-1511364883147733152, "LightEvent", LuaUserData::computeSizeForGarbageCollected<LightEvent>(), nullptr, nullptr};
+	5617532305066010971, "LightEvent", LuaUserData::computeSizeForGarbageCollected<LightEvent>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<LightEvent>()
@@ -6243,7 +6251,7 @@ static inline void wrapLightEvent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoScriptEvent = {
-	-577692491395731788, "ScriptEvent", LuaUserData::computeSizeForGarbageCollected<ScriptEvent>(), nullptr, nullptr};
+	7471457449365359232, "ScriptEvent", LuaUserData::computeSizeForGarbageCollected<ScriptEvent>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<ScriptEvent>()
@@ -6259,7 +6267,7 @@ static inline void wrapScriptEvent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoJitterMoveEvent = {
-	3496086456905036915, "JitterMoveEvent", LuaUserData::computeSizeForGarbageCollected<JitterMoveEvent>(), nullptr,
+	-3678566306512249386, "JitterMoveEvent", LuaUserData::computeSizeForGarbageCollected<JitterMoveEvent>(), nullptr,
 	nullptr};
 
 template<>
@@ -6334,7 +6342,7 @@ static inline void wrapJitterMoveEvent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoAnimationEvent = {-1032810681493696534, "AnimationEvent",
+LuaUserDataTypeInfo luaUserDataTypeInfoAnimationEvent = {-6310232758572032520, "AnimationEvent",
 														 LuaUserData::computeSizeForGarbageCollected<AnimationEvent>(),
 														 nullptr, nullptr};
 
@@ -6352,7 +6360,7 @@ static inline void wrapAnimationEvent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoEventManager = {
-	330041233457660952, "EventManager", LuaUserData::computeSizeForGarbageCollected<EventManager>(), nullptr, nullptr};
+	7795657307788131623, "EventManager", LuaUserData::computeSizeForGarbageCollected<EventManager>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<EventManager>()

+ 5 - 3
AnKi/Script/Scene.xml

@@ -295,15 +295,17 @@ using WeakArrayBodyComponentPtr = WeakArray<BodyComponent*>;
 						<arg>F32</arg>
 					</args>
 				</method>
-				<method name="setShadowCascadesDistancePower">
+				<method name="setShadowCascadeDistance">
 					<args>
+						<arg>U32</arg>
 						<arg>F32</arg>
 					</args>
 				</method>
-				<method name="setEffectiveShadowDistance">
+				<method name="getShadowCascadeDistance">
 					<args>
-						<arg>F32</arg>
+						<arg>U32</arg>
 					</args>
+					<return>F32</return>
 				</method>
 			</methods>
 		</class>

+ 51 - 51
AnKi/ShaderCompiler/ShaderProgramParser.cpp

@@ -63,7 +63,7 @@ inline constexpr char kShaderHeader[] = R"(#version 460 core
 #extension GL_EXT_nonuniform_qualifier : enable
 #extension GL_EXT_scalar_block_layout : enable
 
-#define ANKI_MAX_BINDLESS_TEXTURES %uu
+#define kMaxBindlessTextures %uu
 #define kMaxBindlessReadonlyTextureBuffers %uu
 
 #if defined(ANKI_RAY_GEN_SHADER) || defined(ANKI_ANY_HIT_SHADER) || defined(ANKI_CLOSEST_HIT_SHADER) || defined(ANKI_MISS_SHADER) || defined(ANKI_INTERSECTION_SHADER) || defined(ANKI_CALLABLE_SHADER)
@@ -71,112 +71,112 @@ inline constexpr char kShaderHeader[] = R"(#version 460 core
 #endif
 
 #define ANKI_BINDLESS_SET(s) \
-	layout(set = s, binding = 0) uniform utexture2D u_bindlessTextures2dU32[ANKI_MAX_BINDLESS_TEXTURES]; \
-	layout(set = s, binding = 0) uniform itexture2D u_bindlessTextures2dI32[ANKI_MAX_BINDLESS_TEXTURES]; \
-	layout(set = s, binding = 0) uniform texture2D u_bindlessTextures2dF32[ANKI_MAX_BINDLESS_TEXTURES]; \
-	layout(set = s, binding = 0) uniform texture2DArray u_bindlessTextures2dArrayF32[ANKI_MAX_BINDLESS_TEXTURES]; \
+	layout(set = s, binding = 0) uniform utexture2D u_bindlessTextures2dU32[kMaxBindlessTextures]; \
+	layout(set = s, binding = 0) uniform itexture2D u_bindlessTextures2dI32[kMaxBindlessTextures]; \
+	layout(set = s, binding = 0) uniform texture2D u_bindlessTextures2dF32[kMaxBindlessTextures]; \
+	layout(set = s, binding = 0) uniform texture2DArray u_bindlessTextures2dArrayF32[kMaxBindlessTextures]; \
 	layout(set = s, binding = 1) uniform textureBuffer u_bindlessTextureBuffers[kMaxBindlessReadonlyTextureBuffers];
 
 #define F32 float
-#define _ANKI_SIZEOF_float 4u
+const uint kSizeof_float = 4u;
 #define Vec2 vec2
-#define _ANKI_SIZEOF_vec2 8u
+const uint kSizeof_vec2 = 8u;
 #define Vec3 vec3
-#define _ANKI_SIZEOF_vec3 12u
+const uint kSizeof_vec3 = 12u;
 #define Vec4 vec4
-#define _ANKI_SIZEOF_vec4 16u
+const uint kSizeof_vec4 = 16u;
 
 #define F16 float16_t
-#define _ANKI_SIZEOF_float16_t 2u
+const uint kSizeof_float16_t = 2u;
 #define HVec2 f16vec2
-#define _ANKI_SIZEOF_f16vec2 4u
+const uint kSizeof_f16vec2 = 4u;
 #define HVec3 f16vec3
-#define _ANKI_SIZEOF_f16vec3 6u
+const uint kSizeof_f16vec3 = 6u;
 #define HVec4 f16vec4
-#define _ANKI_SIZEOF_f16vec4 8u
+const uint kSizeof_f16vec4 = 8u;
 
 #define U8 uint8_t
-#define _ANKI_SIZEOF_uint8_t 1u
+const uint kSizeof_uint8_t = 1u;
 #define U8Vec2 u8vec2
-#define _ANKI_SIZEOF_u8vec2 2u
+const uint kSizeof_u8vec2 = 2u;
 #define U8Vec3 u8vec3
-#define _ANKI_SIZEOF_u8vec3 3u
+const uint kSizeof_u8vec3 = 3u;
 #define U8Vec4 u8vec4
-#define _ANKI_SIZEOF_u8vec4 4u
+const uint kSizeof_u8vec4 = 4u;
 
 #define I8 int8_t
-#define _ANKI_SIZEOF_int8_t 1u
+const uint kSizeof_int8_t = 1u;
 #define I8Vec2 i8vec2
-#define _ANKI_SIZEOF_i8vec2 2u
+const uint kSizeof_i8vec2 = 2u;
 #define I8Vec3 i8vec3
-#define _ANKI_SIZEOF_i8vec3 3u
+const uint kSizeof_i8vec3 = 3u;
 #define I8Vec4 i8vec4
-#define _ANKI_SIZEOF_i8vec4 4u
+const uint kSizeof_i8vec4 = 4u;
 
 #define U16 uint16_t
-#define _ANKI_SIZEOF_uint16_t 2u
+const uint kSizeof_uint16_t = 2u;
 #define U16Vec2 u16vec2
-#define _ANKI_SIZEOF_u16vec2 4u
+const uint kSizeof_u16vec2 = 4u;
 #define U16Vec3 u16vec3
-#define _ANKI_SIZEOF_u16vec3 6u
+const uint kSizeof_u16vec3 = 6u;
 #define U16Vec4 u16vec4
-#define _ANKI_SIZEOF_u16vec4 8u
+const uint kSizeof_u16vec4 = 8u;
 
 #define I16 int16_t
-#define _ANKI_SIZEOF_int16_t 2u
+const uint kSizeof_int16_t = 2u;
 #define I16Vec2 i16vec2
-#define _ANKI_SIZEOF_i16vec2 4u
+const uint kSizeof_i16vec2 = 4u;
 #define I16Vec3 i16vec3
-#define _ANKI_SIZEOF_i16vec3 6u
+const uint kSizeof_i16vec3 = 6u;
 #define i16Vec4 i16vec4
-#define _ANKI_SIZEOF_i16vec4 8u
+const uint kSizeof_i16vec4 = 8u;
 
 #define U32 uint
-#define _ANKI_SIZEOF_uint 4u
+const uint kSizeof_uint = 4u;
 #define UVec2 uvec2
-#define _ANKI_SIZEOF_uvec2 8u
+const uint kSizeof_uvec2 = 8u;
 #define UVec3 uvec3
-#define _ANKI_SIZEOF_uvec3 12u
+const uint kSizeof_uvec3 = 12u;
 #define UVec4 uvec4
-#define _ANKI_SIZEOF_uvec4 16u
+const uint kSizeof_uvec4 = 16u;
 
 #define I32 int
-#define _ANKI_SIZEOF_int 4u
+const uint kSizeof_int = 4u;
 #define IVec2 ivec2
-#define _ANKI_SIZEOF_ivec2 8u
+const uint kSizeof_ivec2 = 8u;
 #define IVec3 ivec3
-#define _ANKI_SIZEOF_ivec3 12u
+const uint kSizeof_ivec3 = 12u;
 #define IVec4 ivec4
-#define _ANKI_SIZEOF_ivec4 16u
+const uint kSizeof_ivec4 = 16u;
 
 #if ANKI_SUPPORTS_64BIT
 #	define U64 uint64_t
-#	define _ANKI_SIZEOF_uint64_t 8u
+const uint kSizeof_uint64_t = 8u;
 #	define U64Vec2 u64vec2
-#	define _ANKI_SIZEOF_u64vec2 16u
+const uint kSizeof_u64vec2 = 16u;
 #	define U64Vec3 u64vec3
-#	define _ANKI_SIZEOF_u64vec3 24u
+const uint kSizeof_u64vec3 = 24u;
 #	define U64Vec4 u64vec4
-#	define _ANKI_SIZEOF_u64vec4 32u
+const uint kSizeof_u64vec4 = 32u;
 
 #	define I64 int64_t
-#	define _ANKI_SIZEOF_int64_t 8u
+const uint kSizeof_int64_t = 8u;
 #	define I64Vec2 i64vec2
-#	define _ANKI_SIZEOF_i64vec2 16u
+const uint kSizeof_i64vec2 = 16u;
 #	define I64Vec3 i64vec3
-#	define _ANKI_SIZEOF_i64vec3 24u
+const uint kSizeof_i64vec3 = 24u;
 #	define I64Vec4 i64vec4
-#	define _ANKI_SIZEOF_i64vec4 32u
+const uint kSizeof_i64vec4 = 32u;
 #endif
 
 #define Mat3 mat3
-#define _ANKI_SIZEOF_mat3 36u
+const uint kSizeof_mat3 = 36u;
 
 #define Mat4 mat4
-#define _ANKI_SIZEOF_mat4 64u
+const uint kSizeof_mat4 = 64u;
 
 #define Mat3x4 mat4x3 // GLSL has the column number first and then the rows
-#define _ANKI_SIZEOF_mat3x4 48u
+const uint kSizeof_mat3x4 = 48u;
 
 #define Bool bool
 
@@ -185,13 +185,13 @@ inline constexpr char kShaderHeader[] = R"(#version 460 core
 #else
 #	define Address UVec2
 #endif
-#define _ANKI_SIZEOF_Address 8u
+const uint kSizeof_Address = 8u;
 
 #define _ANKI_CONCATENATE(a, b) a##b
 #define ANKI_CONCATENATE(a, b) _ANKI_CONCATENATE(a, b)
 
-#define ANKI_SIZEOF(type) _ANKI_CONCATENATE(_ANKI_SIZEOF_, type)
-#define ANKI_ALIGNOF(type) _ANKI_CONCATENATE(_ANKI_ALIGNOF_, type)
+#define ANKI_SIZEOF(type) _ANKI_CONCATENATE(kSizeof_, type)
+#define ANKI_ALIGNOF(type) _ANKI_CONCATENATE(kAlignof_, type)
 
 #define _ANKI_SCONST_X(type, n, id) \
 	layout(constant_id = id) const type n = type(1); \

+ 0 - 12
AnKi/Shaders/Common.glsl

@@ -10,18 +10,6 @@
 #include <AnKi/Shaders/Include/Common.h>
 #include <AnKi/Shaders/TextureFunctions.glsl>
 
-// Constants
-const F32 kEpsilonf = 0.000001;
-const F16 kEpsilonf16 = 0.0001hf; // Divisions by this should be OK according to http://weitz.de/ieee/
-const ANKI_RP F32 kEpsilonRp = F32(kEpsilonf16);
-
-const U32 kMaxU32 = 0xFFFFFFFFu;
-const F32 kMaxF32 = 3.402823e+38;
-const F16 kMaxF16 = 65504.0hf;
-const F16 kMinF16 = 0.00006104hf;
-
-const F32 kPi = 3.14159265358979323846;
-
 // Macros
 #define UV_TO_NDC(x_) ((x_)*2.0 - 1.0)
 #define NDC_TO_UV(x_) ((x_)*0.5 + 0.5)

+ 0 - 119
AnKi/Shaders/Evsm.glsl

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

+ 0 - 8
AnKi/Shaders/EvsmCompute.ankiprog

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

+ 5 - 3
AnKi/Shaders/ForwardShadingCommon.glsl

@@ -8,6 +8,7 @@
 #include <AnKi/Shaders/Common.glsl>
 #include <AnKi/Shaders/Functions.glsl>
 #include <AnKi/Shaders/Include/ModelTypes.h>
+#include <AnKi/Shaders/Include/MeshTypes.h>
 #include <AnKi/Shaders/Include/MaterialTypes.h>
 #include <AnKi/Shaders/Include/GpuSceneTypes.h>
 
@@ -17,7 +18,7 @@ ANKI_BINDLESS_SET(kMaterialSetBindless)
 // Vert
 //
 #if defined(ANKI_VERTEX_SHADER)
-layout(location = kVertexAttributeIdPosition) in Vec3 in_position;
+layout(location = kVertexStreamIdPosition) in Vec3 in_position;
 #endif
 
 //
@@ -28,6 +29,7 @@ layout(location = kVertexAttributeIdPosition) in Vec3 in_position;
 layout(set = kMaterialSetGlobal, binding = kMaterialBindingLinearClampSampler) uniform sampler u_linearAnyClampSampler;
 layout(set = kMaterialSetGlobal, binding = kMaterialBindingDepthRt) uniform texture2D u_gbufferDepthRt;
 layout(set = kMaterialSetGlobal, binding = kMaterialBindingLightVolume) uniform ANKI_RP texture3D u_lightVol;
+layout(set = kMaterialSetGlobal, binding = kMaterialBindingShadowSampler) uniform samplerShadow u_shadowSampler;
 #	define CLUSTERED_SHADING_SET kMaterialSetGlobal
 #	define CLUSTERED_SHADING_UNIFORMS_BINDING kMaterialBindingClusterShadingUniforms
 #	define CLUSTERED_SHADING_LIGHTS_BINDING kMaterialBindingClusterShadingLights
@@ -75,7 +77,7 @@ Vec3 computeLightColorHigh(Vec3 diffCol, Vec3 worldPos)
 		F32 shadow = 1.0;
 		if(light.m_shadowAtlasTileScale >= 0.0)
 		{
-			shadow = computeShadowFactorPointLight(light, frag2Light, u_shadowAtlasTex, u_linearAnyClampSampler);
+			shadow = computeShadowFactorPointLight(light, frag2Light, u_shadowAtlasTex, u_shadowSampler);
 		}
 #	endif
 
@@ -104,7 +106,7 @@ Vec3 computeLightColorHigh(Vec3 diffCol, Vec3 worldPos)
 		F32 shadow = 1.0;
 		ANKI_BRANCH if(light.m_shadowLayer != kMaxU32)
 		{
-			shadow = computeShadowFactorSpotLight(light, worldPos, u_shadowAtlasTex, u_linearAnyClampSampler);
+			shadow = computeShadowFactorSpotLight(light, worldPos, u_shadowAtlasTex, u_shadowSampler);
 		}
 #	endif
 

+ 2 - 1
AnKi/Shaders/ForwardShadingFog.ankiprog

@@ -43,7 +43,8 @@ layout(location = 0) out F32 out_zVSpace;
 
 void main()
 {
-	const Vec3 worldPos = u_renderableGpuViews[0].m_worldTransform * Vec4(in_position, 1.0);
+	const RenderableGpuView renderable = u_renderableGpuViews[0];
+	const Vec3 worldPos = renderable.m_worldTransform * Vec4(in_position, 1.0);
 
 	gl_Position = u_global.m_viewProjectionMatrix * Vec4(worldPos, 1.0);
 

+ 1 - 1
AnKi/Shaders/ForwardShadingGenericTransparent.ankiprog

@@ -40,7 +40,7 @@ layout(set = kMaterialSetGlobal, binding = kMaterialBindingGlobalUniforms) unifo
 
 #pragma anki start vert
 
-layout(location = kVertexAttributeIdUv0) in Vec2 in_uv;
+layout(location = kVertexStreamIdUv) in Vec2 in_uv;
 
 layout(location = 0) out Vec2 out_uv;
 layout(location = 1) out Vec3 out_worldPosition;

+ 2 - 2
AnKi/Shaders/ForwardShadingParticles.ankiprog

@@ -36,8 +36,8 @@ layout(set = kMaterialSetLocal, binding = kMaterialBindingRenderableGpuView) uni
 
 #pragma anki start vert
 
-layout(location = kVertexAttributeIdScale) in F32 in_scale;
-layout(location = kVertexAttributeIdAlpha) in F32 in_alpha;
+layout(location = kVertexStreamIdParticleScale) in F32 in_scale;
+layout(location = kVertexStreamIdParticleAlpha) in F32 in_alpha;
 
 layout(location = 0) flat out ANKI_RP F32 out_alpha;
 layout(location = 1) out Vec2 out_uv;

+ 22 - 2
AnKi/Shaders/Functions.glsl

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

+ 7 - 6
AnKi/Shaders/GBufferCommon.glsl

@@ -11,6 +11,7 @@
 #include <AnKi/Shaders/Include/ModelTypes.h>
 #include <AnKi/Shaders/Include/MaterialTypes.h>
 #include <AnKi/Shaders/Include/GpuSceneTypes.h>
+#include <AnKi/Shaders/Include/MeshTypes.h>
 #include <AnKi/Shaders/Common.glsl>
 
 ANKI_BINDLESS_SET(kMaterialSetBindless)
@@ -30,20 +31,20 @@ ANKI_BINDLESS_SET(kMaterialSetBindless)
 //
 #if defined(ANKI_VERTEX_SHADER)
 
-layout(location = kVertexAttributeIdPosition) in Vec3 in_position;
+layout(location = kVertexStreamIdPosition) in Vec3 in_position;
 
 #	if ANKI_TECHNIQUE == RENDERING_TECHNIQUE_GBUFFER
-layout(location = kVertexAttributeIdNormal) in ANKI_RP Vec3 in_normal;
-layout(location = kVertexAttributeIdTangent) in ANKI_RP Vec4 in_tangent;
+layout(location = kVertexStreamIdNormal) in ANKI_RP Vec3 in_normal;
+layout(location = kVertexStreamIdTangent) in ANKI_RP Vec4 in_tangent;
 #	endif
 
 #	if ANKI_TECHNIQUE == RENDERING_TECHNIQUE_GBUFFER || ALPHA_TEST
-layout(location = kVertexAttributeIdUv0) in Vec2 in_uv;
+layout(location = kVertexStreamIdUv) in Vec2 in_uv;
 #	endif
 
 #	if ANKI_BONES
-layout(location = kVertexAttributeIdBoneWeights) in Vec4 in_boneWeights;
-layout(location = kVertexAttributeIdBoneIndices) in UVec4 in_boneIndices;
+layout(location = kVertexStreamIdBoneIds) in UVec4 in_boneIndices;
+layout(location = kVertexStreamIdBoneWeights) in Vec4 in_boneWeights;
 #	endif
 
 #endif // defined(ANKI_VERTEX_SHADER)

+ 13 - 0
AnKi/Shaders/GBufferGeneric.ankiprog

@@ -89,9 +89,22 @@ layout(set = kMaterialSetLocal, binding = kMaterialBindingPreviousBoneTransforms
 #pragma anki start vert
 
 // Globals (always in local space)
+#if ANKI_BONES
+Vec3 g_position = in_position * u_renderableGpuViews[gl_InstanceIndex].m_positionScaleF32AndTranslationVec3.x
+				  + u_renderableGpuViews[gl_InstanceIndex].m_positionScaleF32AndTranslationVec3.yzw;
+#else
 Vec3 g_position = in_position;
+#endif
+
 #if ANKI_TECHNIQUE == RENDERING_TECHNIQUE_GBUFFER
+
+#	if ANKI_BONES
+Vec3 g_prevPosition = in_position * u_renderableGpuViews[gl_InstanceIndex].m_positionScaleF32AndTranslationVec3.x
+					  + u_renderableGpuViews[gl_InstanceIndex].m_positionScaleF32AndTranslationVec3.yzw;
+#	else
 Vec3 g_prevPosition = in_position;
+#	endif
+
 ANKI_RP Vec3 g_normal = in_normal;
 ANKI_RP Vec4 g_tangent = in_tangent;
 #endif

+ 0 - 30
AnKi/Shaders/Include/ClusteredShadingFunctions.h

@@ -1,30 +0,0 @@
-// Copyright (C) 2009-2022, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#pragma once
-
-#include <AnKi/Shaders/Include/ClusteredShadingTypes.h>
-
-ANKI_BEGIN_NAMESPACE
-
-// Compute the far plane of a shadow cascade. "p" is the power that defines the distance curve.
-// "effectiveShadowDistance" is the far plane of the last cascade.
-ANKI_SHADER_FUNC_INLINE F32 computeShadowCascadeDistance(U32 cascadeIdx, F32 p, F32 effectiveShadowDistance,
-														 U32 shadowCascadeCount)
-{
-	return pow((F32(cascadeIdx) + 1.0f) / F32(shadowCascadeCount), p) * effectiveShadowDistance;
-}
-
-// The reverse of computeShadowCascadeDistance().
-ANKI_SHADER_FUNC_INLINE U32 computeShadowCascadeIndex(F32 distance, F32 p, F32 effectiveShadowDistance,
-													  U32 shadowCascadeCount)
-{
-	const F32 shadowCascadeCountf = F32(shadowCascadeCount);
-	F32 idx = pow(distance / effectiveShadowDistance, 1.0f / p) * shadowCascadeCountf;
-	idx = min(idx, shadowCascadeCountf - 1.0f);
-	return U32(idx);
-}
-
-ANKI_END_NAMESPACE

+ 50 - 50
AnKi/Shaders/Include/ClusteredShadingTypes.h

@@ -12,32 +12,31 @@
 ANKI_BEGIN_NAMESPACE
 
 // Enum of clusterer object types
-const U32 kClusterObjectTypePointLight = 0u;
-const U32 kClusterObjectTypeSpotLight = 1u;
-const U32 kClusterObjectTypeDecal = 2u;
-const U32 kClusterObjectTypeFogDensityVolume = 3u;
-const U32 kClusterObjectTypeReflectionProbe = 4u;
-const U32 kClusterObjectTypeGlobalIlluminationProbe = 5u;
-const U32 kClusterObjectTypeCount = 6u; ///< Point and spot lights, refl and GI probes, decals and fog volumes.
+constexpr U32 kClusterObjectTypePointLight = 0u;
+constexpr U32 kClusterObjectTypeSpotLight = 1u;
+constexpr U32 kClusterObjectTypeDecal = 2u;
+constexpr U32 kClusterObjectTypeFogDensityVolume = 3u;
+constexpr U32 kClusterObjectTypeReflectionProbe = 4u;
+constexpr U32 kClusterObjectTypeGlobalIlluminationProbe = 5u;
+constexpr U32 kClusterObjectTypeCount = 6u; ///< Point and spot lights, refl and GI probes, decals and fog volumes.
 
 // Limits
 #if ANKI_CLUSTERED_SHADING_USE_64BIT
-const U32 kMaxVisiblePointLights = 64u;
-const U32 kMaxVisibleSpotLights = 64u;
-const U32 kMaxVisibleDecals = 64u;
+constexpr U32 kMaxVisiblePointLights = 64u;
+constexpr U32 kMaxVisibleSpotLights = 64u;
+constexpr U32 kMaxVisibleDecals = 64u;
 #else
-const U32 kMaxVisiblePointLights = 32u;
-const U32 kMaxVisibleSpotLights = 32u;
-const U32 kMaxVisibleDecals = 32u;
+constexpr U32 kMaxVisiblePointLights = 32u;
+constexpr U32 kMaxVisibleSpotLights = 32u;
+constexpr U32 kMaxVisibleDecals = 32u;
 #endif
-const U32 kMaxVisibleFogDensityVolumes = 16u;
-const U32 kMaxVisibleReflectionProbes = 16u;
-const U32 kMaxVisibleGlobalIlluminationProbes = 8u;
+constexpr U32 kMaxVisibleFogDensityVolumes = 16u;
+constexpr U32 kMaxVisibleReflectionProbes = 16u;
+constexpr U32 kMaxVisibleGlobalIlluminationProbes = 8u;
 
 // Other consts
-const ANKI_RP F32 kClusterObjectFrustumNearPlane = 0.1f / 4.0f; ///< Near plane of various clusterer object frustums.
-const U32 kMaxShadowCascades = 4u;
-const ANKI_RP F32 kSubsurfaceMin = 0.01f;
+constexpr ANKI_RP F32 kClusterObjectFrustumNearPlane = 0.1f / 4.0f; ///< Near plane of all clusterer object frustums.
+constexpr ANKI_RP F32 kSubsurfaceMin = 0.01f;
 
 /// Point light.
 struct PointLight
@@ -54,8 +53,8 @@ struct PointLight
 
 	Vec4 m_shadowAtlasTileOffsets[6u]; ///< It's a array of Vec2 but because of padding round it up.
 };
-const U32 _ANKI_SIZEOF_PointLight = 9u * ANKI_SIZEOF(Vec4);
-ANKI_SHADER_STATIC_ASSERT(sizeof(PointLight) == _ANKI_SIZEOF_PointLight);
+constexpr U32 kSizeof_PointLight = 9u * ANKI_SIZEOF(Vec4);
+ANKI_SHADER_STATIC_ASSERT(sizeof(PointLight) == kSizeof_PointLight);
 
 /// Spot light.
 struct SpotLight
@@ -78,8 +77,8 @@ struct SpotLight
 
 	Mat4 m_textureMatrix;
 };
-const U32 _ANKI_SIZEOF_SpotLight = 12u * ANKI_SIZEOF(Vec4);
-ANKI_SHADER_STATIC_ASSERT(sizeof(SpotLight) == _ANKI_SIZEOF_SpotLight);
+constexpr U32 kSizeof_SpotLight = 12u * ANKI_SIZEOF(Vec4);
+ANKI_SHADER_STATIC_ASSERT(sizeof(SpotLight) == kSizeof_SpotLight);
 
 /// Spot light for binning. This is the same structure as SpotLight (same signature) but it's used for binning.
 struct SpotLightBinning
@@ -99,28 +98,29 @@ struct SpotLightBinning
 
 	Mat4 m_textureMatrix;
 };
-const U32 _ANKI_SIZEOF_SpotLightBinning = _ANKI_SIZEOF_SpotLight;
-ANKI_SHADER_STATIC_ASSERT(sizeof(SpotLightBinning) == _ANKI_SIZEOF_SpotLightBinning);
+constexpr U32 kSizeof_SpotLightBinning = kSizeof_SpotLight;
+ANKI_SHADER_STATIC_ASSERT(sizeof(SpotLightBinning) == kSizeof_SpotLightBinning);
 ANKI_SHADER_STATIC_ASSERT(sizeof(SpotLight) == sizeof(SpotLightBinning));
 
 /// Directional light (sun).
 struct DirectionalLight
 {
 	ANKI_RP Vec3 m_diffuseColor;
-	U32 m_cascadeCount; ///< If it's zero then it doesn't cast shadow.
+	U32 m_shadowCascadeCount; ///< If it's zero then it doesn't cast shadow.
 
 	ANKI_RP Vec3 m_direction;
 	U32 m_active;
 
-	ANKI_RP F32 m_effectiveShadowDistance;
-	ANKI_RP F32 m_shadowCascadesDistancePower;
+	Vec4 m_shadowCascadeDistances;
+
+	Vec3 m_padding0;
 	U32 m_shadowLayer; ///< Shadow layer used in RT shadows. Also used to show that it doesn't cast shadow.
-	U32 m_padding0;
 
 	Mat4 m_textureMatrices[kMaxShadowCascades];
 };
-const U32 _ANKI_SIZEOF_DirectionalLight = 3u * ANKI_SIZEOF(Vec4) + kMaxShadowCascades * ANKI_SIZEOF(Mat4);
-ANKI_SHADER_STATIC_ASSERT(sizeof(DirectionalLight) == _ANKI_SIZEOF_DirectionalLight);
+constexpr U32 kSizeof_DirectionalLight = 4u * ANKI_SIZEOF(Vec4) + kMaxShadowCascades * ANKI_SIZEOF(Mat4);
+ANKI_SHADER_STATIC_ASSERT(sizeof(DirectionalLight) == kSizeof_DirectionalLight);
+ANKI_SHADER_STATIC_ASSERT(kMaxShadowCascades == 4u); // Because m_shadowCascadeDistances is a Vec4
 
 /// Representation of a reflection probe.
 struct ReflectionProbe
@@ -134,8 +134,8 @@ struct ReflectionProbe
 	Vec3 m_aabbMax;
 	F32 m_padding1;
 };
-const U32 _ANKI_SIZEOF_ReflectionProbe = 3u * ANKI_SIZEOF(Vec4);
-ANKI_SHADER_STATIC_ASSERT(sizeof(ReflectionProbe) == _ANKI_SIZEOF_ReflectionProbe);
+constexpr U32 kSizeof_ReflectionProbe = 3u * ANKI_SIZEOF(Vec4);
+ANKI_SHADER_STATIC_ASSERT(sizeof(ReflectionProbe) == kSizeof_ReflectionProbe);
 
 /// Decal.
 struct Decal
@@ -153,8 +153,8 @@ struct Decal
 	Vec3 m_obbExtend;
 	F32 m_padding0;
 };
-const U32 _ANKI_SIZEOF_Decal = 4u * ANKI_SIZEOF(Vec4) + 2u * ANKI_SIZEOF(Mat4);
-ANKI_SHADER_STATIC_ASSERT(sizeof(Decal) == _ANKI_SIZEOF_Decal);
+constexpr U32 kSizeof_Decal = 4u * ANKI_SIZEOF(Vec4) + 2u * ANKI_SIZEOF(Mat4);
+ANKI_SHADER_STATIC_ASSERT(sizeof(Decal) == kSizeof_Decal);
 
 /// Fog density volume.
 struct FogDensityVolume
@@ -165,8 +165,8 @@ struct FogDensityVolume
 	Vec3 m_aabbMaxOrSphereRadiusSquared;
 	ANKI_RP F32 m_density;
 };
-const U32 _ANKI_SIZEOF_FogDensityVolume = 2u * ANKI_SIZEOF(Vec4);
-ANKI_SHADER_STATIC_ASSERT(sizeof(FogDensityVolume) == _ANKI_SIZEOF_FogDensityVolume);
+constexpr U32 kSizeof_FogDensityVolume = 2u * ANKI_SIZEOF(Vec4);
+ANKI_SHADER_STATIC_ASSERT(sizeof(FogDensityVolume) == kSizeof_FogDensityVolume);
 
 /// Global illumination probe
 struct GlobalIlluminationProbe
@@ -183,8 +183,8 @@ struct GlobalIlluminationProbe
 	ANKI_RP F32 m_fadeDistance;
 	F32 m_padding2;
 };
-const U32 _ANKI_SIZEOF_GlobalIlluminationProbe = 3u * ANKI_SIZEOF(Vec4);
-ANKI_SHADER_STATIC_ASSERT(sizeof(GlobalIlluminationProbe) == _ANKI_SIZEOF_GlobalIlluminationProbe);
+constexpr U32 kSizeof_GlobalIlluminationProbe = 3u * ANKI_SIZEOF(Vec4);
+ANKI_SHADER_STATIC_ASSERT(sizeof(GlobalIlluminationProbe) == kSizeof_GlobalIlluminationProbe);
 
 /// Common matrices.
 struct CommonMatrices
@@ -210,14 +210,14 @@ struct CommonMatrices
 
 	/// To unproject to view space. Jitter not considered.
 	/// @code
-	/// const F32 z = m_unprojectionParameters.z / (m_unprojectionParameters.w + depth);
-	/// const Vec2 xy = ndc * m_unprojectionParameters.xy * z;
+	/// F32 z = m_unprojectionParameters.z / (m_unprojectionParameters.w + depth);
+	/// Vec2 xy = ndc * m_unprojectionParameters.xy * z;
 	/// pos = Vec3(xy, z);
 	/// @endcode
 	Vec4 m_unprojectionParameters;
 };
-const U32 _ANKI_SIZEOF_CommonMatrices = 43u * ANKI_SIZEOF(Vec4);
-ANKI_SHADER_STATIC_ASSERT(sizeof(CommonMatrices) == _ANKI_SIZEOF_CommonMatrices);
+constexpr U32 kSizeof_CommonMatrices = 43u * ANKI_SIZEOF(Vec4);
+ANKI_SHADER_STATIC_ASSERT(sizeof(CommonMatrices) == kSizeof_CommonMatrices);
 
 /// Common uniforms for light shading passes.
 struct ClusteredShadingUniforms
@@ -251,9 +251,9 @@ struct ClusteredShadingUniforms
 	/// This are some additive counts used to map a flat index to the index of the specific object.
 	UVec4 m_objectCountsUpTo[kClusterObjectTypeCount];
 };
-const U32 _ANKI_SIZEOF_ClusteredShadingUniforms = (6u + kClusterObjectTypeCount) * ANKI_SIZEOF(Vec4)
-												  + 2u * ANKI_SIZEOF(CommonMatrices) + ANKI_SIZEOF(DirectionalLight);
-ANKI_SHADER_STATIC_ASSERT(sizeof(ClusteredShadingUniforms) == _ANKI_SIZEOF_ClusteredShadingUniforms);
+constexpr U32 kSizeof_ClusteredShadingUniforms = (6u + kClusterObjectTypeCount) * ANKI_SIZEOF(Vec4)
+												 + 2u * ANKI_SIZEOF(CommonMatrices) + ANKI_SIZEOF(DirectionalLight);
+ANKI_SHADER_STATIC_ASSERT(sizeof(ClusteredShadingUniforms) == kSizeof_ClusteredShadingUniforms);
 
 // Define the type of some cluster object masks
 #if !defined(__cplusplus)
@@ -292,11 +292,11 @@ struct Cluster
 };
 
 #if ANKI_CLUSTERED_SHADING_USE_64BIT
-const U32 _ANKI_SIZEOF_Cluster = 3u * ANKI_SIZEOF(Vec4);
-ANKI_SHADER_STATIC_ASSERT(sizeof(Cluster) == _ANKI_SIZEOF_Cluster);
+constexpr U32 kSizeof_Cluster = 3u * ANKI_SIZEOF(Vec4);
+ANKI_SHADER_STATIC_ASSERT(sizeof(Cluster) == kSizeof_Cluster);
 #else
-const U32 _ANKI_SIZEOF_Cluster = 2u * ANKI_SIZEOF(Vec4);
-ANKI_SHADER_STATIC_ASSERT(sizeof(Cluster) == _ANKI_SIZEOF_Cluster);
+constexpr U32 kSizeof_Cluster = 2u * ANKI_SIZEOF(Vec4);
+ANKI_SHADER_STATIC_ASSERT(sizeof(Cluster) == kSizeof_Cluster);
 #endif
 
 ANKI_END_NAMESPACE

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

@@ -68,15 +68,35 @@ ANKI_END_NAMESPACE
 #	define ScalarVec4 Vec4
 #	define ScalarMat3x4 Mat3x4
 #	define ScalarMat4 Mat4
+
+#	define constexpr const
 #endif
 
 //
-// Various
+// Constants
 //
 ANKI_BEGIN_NAMESPACE
 
+#if !defined(__cplusplus)
+constexpr F32 kEpsilonf = 0.000001f;
+constexpr F16 kEpsilonhf = 0.0001hf; // Divisions by this should be OK according to http://weitz.de/ieee/
+constexpr ANKI_RP F32 kEpsilonRp = F32(kEpsilonhf);
+
+constexpr U32 kMaxU32 = 0xFFFFFFFFu;
+constexpr F32 kMaxF32 = 3.402823e+38;
+constexpr F16 kMaxF16 = 65504.0hf;
+constexpr F16 kMinF16 = 0.00006104hf;
+
+constexpr F32 kPi = 3.14159265358979323846f;
+#endif
+
 /// The renderer will group drawcalls into instances up to this number.
-const U32 kMaxInstanceCount = 64u;
-const U32 kMaxLodCount = 3u;
+constexpr U32 kMaxInstanceCount = 64u;
+
+constexpr U32 kMaxLodCount = 3u;
+constexpr U32 kMaxShadowCascades = 4u;
+
+constexpr F32 kShadowsPolygonOffsetFactor = 1.25f;
+constexpr F32 kShadowsPolygonOffsetUnits = 2.75f;
 
 ANKI_END_NAMESPACE

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

@@ -13,6 +13,7 @@ struct RenderableGpuView
 {
 	Mat3x4 m_worldTransform;
 	Mat3x4 m_previousWorldTransform;
+	Vec4 m_positionScaleF32AndTranslationVec3; ///< The scale and translation that uncompress positions.
 };
 
 struct SkinGpuView

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

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

+ 62 - 19
AnKi/Shaders/Include/MeshTypes.h

@@ -9,28 +9,71 @@
 
 ANKI_BEGIN_NAMESPACE
 
-// For regular geometry
-const U32 kVertexStreamPosition = 0u;
-const U32 kVertexStreamNormal = 1u;
-const U32 kVertexStreamTangent = 2u;
-const U32 kVertexStreamUv = 3u;
-const U32 kVertexStreamBoneIds = 4u;
-const U32 kVertexStreamBoneWeights = 5u;
+#if __cplusplus
+enum class VertexStreamId : U8
+{
+	// For regular geometry
+	kPosition,
+	kNormal,
+	kTangent,
+	kUv,
+	kBoneIds,
+	kBoneWeights,
 
-const U32 kRegularVertexStreamCount = 6u;
+	kMeshRelatedCount,
+	kMeshRelatedFirst = 0,
 
-#if __cplusplus
-inline constexpr Array<Format, kRegularVertexStreamCount> kRegularVertexStreamFormats = {
-	Format::kR32R32G32B32_Sfloat, Format::kR8G8B8A8_Snorm, Format::kR8G8B8A8_Snorm,
-	Format::kR32G32_Sfloat,       Format::kR8G8B8A8_Uint,  Format::kR8G8B8A8_Snorm};
-#endif
+	// For particles
+	kParticlePosition = 0,
+	kParticleScale,
+	kParticleAlpha,
+	kParticleLife,
+	kParticleStartingLife,
+	kParticlePreviousPosition,
+};
+ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(VertexStreamId)
+
+enum class VertexStreamMask : U8
+{
+	kNone,
+
+	kPosition = 1 << 0,
+	kNormal = 1 << 1,
+	kTangent = 1 << 2,
+	kUv = 1 << 3,
+	kBoneIds = 1 << 4,
+	kBoneWeights = 1 << 5,
+
+	kParticlePosition = 1 << 0,
+	kParticleScale = 1 << 1,
+	kParticleAlpha = 1 << 2,
+	kParticleLife = 1 << 3,
+	kParticleStartingLife = 1 << 4,
+	kParticlePreviousPosition = 1 << 5,
+};
+ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(VertexStreamMask)
+
+inline constexpr Array<Format, U32(VertexStreamId::kMeshRelatedCount)> kMeshRelatedVertexStreamFormats = {
+	Format::kR16G16B16_Unorm, Format::kR8G8B8A8_Snorm, Format::kR8G8B8A8_Snorm,
+	Format::kR32G32_Sfloat,   Format::kR8G8B8A8_Uint,  Format::kR8G8B8A8_Snorm};
+
+#else
+
+// For regular geometry
+const U32 kVertexStreamIdPosition = 0u;
+const U32 kVertexStreamIdNormal = 1u;
+const U32 kVertexStreamIdTangent = 2u;
+const U32 kVertexStreamIdUv = 3u;
+const U32 kVertexStreamIdBoneIds = 4u;
+const U32 kVertexStreamIdBoneWeights = 5u;
 
 // For particles
-const U32 kVertexStreamParticlePosition = 0u;
-const U32 kVertexStreamParticleScale = 1u;
-const U32 kVertexStreamParticleAlpha = 2u;
-const U32 kVertexStreamParticleLife = 3u;
-const U32 kVertexStreamParticleStartingLife = 4u;
-const U32 kVertexStreamParticlePreviousPosition = 5u;
+const U32 kVertexStreamIdParticlePosition = 0u;
+const U32 kVertexStreamIdParticleScale = 1u;
+const U32 kVertexStreamIdParticleAlpha = 2u;
+const U32 kVertexStreamIdParticleLife = 3u;
+const U32 kVertexStreamIdParticleStartingLife = 4u;
+const U32 kVertexStreamIdParticlePreviousPosition = 5u;
+#endif
 
 ANKI_END_NAMESPACE

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

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

+ 12 - 12
AnKi/Shaders/Include/ModelTypes.h

@@ -70,9 +70,9 @@ struct MainVertex
 	Vec2 m_uv0;
 };
 
-const U32 _ANKI_SIZEOF_MainVertex = 4u * 4u;
-const U32 _ANKI_ALIGNOF_MainVertex = 4u;
-ANKI_SHADER_STATIC_ASSERT(_ANKI_SIZEOF_MainVertex == sizeof(MainVertex));
+const U32 kSizeof_MainVertex = 4u * 4u;
+const U32 kAlignof_MainVertex = 4u;
+ANKI_SHADER_STATIC_ASSERT(kSizeof_MainVertex == sizeof(MainVertex));
 
 /// The vertex that contains the bone influences.
 struct BoneInfoVertex
@@ -81,9 +81,9 @@ struct BoneInfoVertex
 	U8Vec4 m_boneWeights;
 };
 
-const U32 _ANKI_SIZEOF_BoneInfoVertex = 8u;
-const U32 _ANKI_ALIGNOF_BoneInfoVertex = 1u;
-ANKI_SHADER_STATIC_ASSERT(_ANKI_SIZEOF_BoneInfoVertex == sizeof(BoneInfoVertex));
+const U32 kSizeof_BoneInfoVertex = 8u;
+const U32 kAlignof_BoneInfoVertex = 1u;
+ANKI_SHADER_STATIC_ASSERT(kSizeof_BoneInfoVertex == sizeof(BoneInfoVertex));
 
 /// A structure that contains all the info of a geometry.
 struct MeshGpuDescriptor
@@ -100,9 +100,9 @@ struct MeshGpuDescriptor
 	Vec3 m_aabbMax;
 };
 
-const U32 _ANKI_SIZEOF_MeshGpuDescriptor = 4u * ANKI_SIZEOF(UVec2) + 8u * ANKI_SIZEOF(F32);
-const U32 _ANKI_ALIGNOF_MeshGpuDescriptor = 8u;
-ANKI_SHADER_STATIC_ASSERT(_ANKI_SIZEOF_MeshGpuDescriptor == sizeof(MeshGpuDescriptor));
+const U32 kSizeof_MeshGpuDescriptor = 4u * ANKI_SIZEOF(UVec2) + 8u * ANKI_SIZEOF(F32);
+const U32 kAlignof_MeshGpuDescriptor = 8u;
+ANKI_SHADER_STATIC_ASSERT(kSizeof_MeshGpuDescriptor == sizeof(MeshGpuDescriptor));
 
 #if defined(__cplusplus)
 enum class TextureChannelId : U8
@@ -145,9 +145,9 @@ struct MaterialGpuDescriptor
 	F32 m_metalness;
 };
 
-const U32 _ANKI_SIZEOF_MaterialGpuDescriptor = 8u * ANKI_SIZEOF(U16) + 3u * ANKI_SIZEOF(Vec3) + 2u * ANKI_SIZEOF(F32);
-const U32 _ANKI_ALIGNOF_MaterialGpuDescriptor = 4u;
-ANKI_SHADER_STATIC_ASSERT(_ANKI_SIZEOF_MaterialGpuDescriptor == sizeof(MaterialGpuDescriptor));
+const U32 kSizeof_MaterialGpuDescriptor = 8u * ANKI_SIZEOF(U16) + 3u * ANKI_SIZEOF(Vec3) + 2u * ANKI_SIZEOF(F32);
+const U32 kAlignof_MaterialGpuDescriptor = 4u;
+ANKI_SHADER_STATIC_ASSERT(kSizeof_MaterialGpuDescriptor == sizeof(MaterialGpuDescriptor));
 
 struct ModelGpuDescriptor
 {

+ 165 - 102
AnKi/Shaders/LightFunctions.glsl

@@ -13,60 +13,9 @@
 #include <AnKi/Shaders/Include/ClusteredShadingTypes.h>
 #include <AnKi/Shaders/Include/MiscRendererTypes.h>
 
-// Do some EVSM magic with depth
-Vec2 evsmProcessDepth(F32 depth)
-{
-	depth = 2.0 * depth - 1.0;
-	const F32 pos = exp(kEvsmPositiveConstant * depth);
-	const F32 neg = -exp(kEvsmNegativeConstant * depth);
-	return Vec2(pos, neg);
-}
-
-F32 linstep(F32 a, F32 b, F32 v)
-{
-	return saturate((v - a) / (b - a));
-}
-
-// Reduces VSM light bleedning
-F32 reduceLightBleeding(F32 pMax, F32 amount)
-{
-	// Remove the [0, amount] tail and linearly rescale (amount, 1].
-	return linstep(amount, 1.0, pMax);
-}
-
-F32 chebyshevUpperBound(Vec2 moments, F32 mean, F32 minVariance, F32 lightBleedingReduction)
-{
-	// Compute variance
-	F32 variance = moments.y - (moments.x * moments.x);
-	variance = max(variance, minVariance);
-
-	// Compute probabilistic upper bound
-	const F32 d = mean - moments.x;
-	F32 pMax = variance / (variance + (d * d));
-
-	pMax = reduceLightBleeding(pMax, lightBleedingReduction);
-
-	// One-tailed Chebyshev
-	return (mean <= moments.x) ? 1.0 : pMax;
-}
-
-// Compute the shadow factor of EVSM given the 2 depths
-F32 evsmComputeShadowFactor(F32 occluderDepth, Vec4 shadowMapMoments)
-{
-	const Vec2 evsmOccluderDepths = evsmProcessDepth(occluderDepth);
-	const Vec2 depthScale = kEvsmBias * 0.01 * Vec2(kEvsmPositiveConstant, kEvsmNegativeConstant) * evsmOccluderDepths;
-	const Vec2 minVariance = depthScale * depthScale;
-
-#if !ANKI_EVSM4
-	return chebyshevUpperBound(shadowMapMoments.xy, evsmOccluderDepths.x, minVariance.x, kEvsmLightBleedingReduction);
-#else
-	const F32 pos =
-		chebyshevUpperBound(shadowMapMoments.xy, evsmOccluderDepths.x, minVariance.x, kEvsmLightBleedingReduction);
-	const F32 neg =
-		chebyshevUpperBound(shadowMapMoments.zw, evsmOccluderDepths.y, minVariance.y, kEvsmLightBleedingReduction);
-	return min(pos, neg);
-#endif
-}
+const Vec2 kPoissonDisk[4u] = {Vec2(-0.94201624, -0.39906216), Vec2(0.94558609, -0.76890725),
+							   Vec2(-0.094184101, -0.92938870), Vec2(0.34495938, 0.29387760)};
+const ANKI_RP F32 kPcfScale = 2.0f;
 
 // Fresnel term unreal
 // specular: The specular color aka F0
@@ -162,31 +111,49 @@ ANKI_RP F32 computeSpotFactor(ANKI_RP Vec3 l, ANKI_RP F32 outerCos, ANKI_RP F32
 	return spotFactor;
 }
 
-U32 computeShadowSampleCount(const U32 COUNT, F32 zVSpace)
+ANKI_RP F32 computeShadowFactorSpotLightPcf(SpotLight light, Vec3 worldPos, texture2D shadowTex,
+											samplerShadow shadowMapSampler, ANKI_RP F32 randFactor)
 {
-	const F32 MAX_DISTANCE = 5.0;
+	const Vec4 texCoords4 = light.m_textureMatrix * Vec4(worldPos, 1.0);
+	const Vec3 texCoords3 = texCoords4.xyz / texCoords4.w;
 
-	const F32 z = max(zVSpace, -MAX_DISTANCE);
-	F32 sampleCountf = F32(COUNT) + z * (F32(COUNT) / MAX_DISTANCE);
-	sampleCountf = max(sampleCountf, 1.0);
-	const U32 sampleCount = U32(sampleCountf);
+	const Vec2 smTexelSize = 1.0 / Vec2(textureSize(shadowTex, 0).xy);
+
+	const F32 sinTheta = sin(randFactor * 2.0 * kPi);
+	const F32 cosTheta = cos(randFactor * 2.0 * kPi);
+
+	ANKI_RP F32 shadow = 0.0;
+	ANKI_UNROLL for(U32 i = 0u; i < 4u; ++i)
+	{
+		const Vec2 diskPoint = kPoissonDisk[i] * kPcfScale;
 
-	return sampleCount;
+		// Rotate the disk point
+		Vec2 rotatedDiskPoint;
+		rotatedDiskPoint.x = diskPoint.x * cosTheta - diskPoint.y * sinTheta;
+		rotatedDiskPoint.y = diskPoint.y * cosTheta + diskPoint.x * sinTheta;
+
+		// Offset calculation
+		const Vec2 newUv = texCoords3.xy + rotatedDiskPoint * smTexelSize;
+
+		shadow += textureLod(shadowTex, shadowMapSampler, Vec3(newUv, texCoords3.z), 0.0);
+	}
+
+	shadow /= 4.0;
+
+	return shadow;
 }
 
-ANKI_RP F32 computeShadowFactorSpotLight(SpotLight light, Vec3 worldPos, texture2D spotMap, sampler spotMapSampler)
+ANKI_RP F32 computeShadowFactorSpotLight(SpotLight light, Vec3 worldPos, texture2D shadowTex,
+										 samplerShadow shadowMapSampler)
 {
 	const Vec4 texCoords4 = light.m_textureMatrix * Vec4(worldPos, 1.0);
 	const Vec3 texCoords3 = texCoords4.xyz / texCoords4.w;
-
-	const Vec4 shadowMoments = textureLod(spotMap, spotMapSampler, texCoords3.xy, 0.0);
-
-	return evsmComputeShadowFactor(texCoords3.z, shadowMoments);
+	return textureLod(shadowTex, shadowMapSampler, texCoords3, 0.0);
 }
 
 // Compute the shadow factor of point (omni) lights.
-ANKI_RP F32 computeShadowFactorPointLight(PointLight light, Vec3 frag2Light, texture2D shadowMap,
-										  sampler shadowMapSampler)
+ANKI_RP F32 computeShadowFactorPointLightGeneric(PointLight light, Vec3 frag2Light, texture2D shadowMap,
+												 samplerShadow shadowMapSampler, ANKI_RP F32 randFactor, Bool pcf)
 {
 	const Vec3 dir = -frag2Light;
 	const Vec3 dirabs = abs(dir);
@@ -218,18 +185,56 @@ ANKI_RP F32 computeShadowFactorPointLight(PointLight light, Vec3 frag2Light, tex
 	uv += atlasOffset;
 
 	// Sample
-	const Vec4 shadowMoments = textureLod(shadowMap, shadowMapSampler, uv, 0.0);
+	ANKI_RP F32 shadow;
+	if(pcf)
+	{
+		const Vec2 smTexelSize = 1.0 / Vec2(textureSize(shadowMap, 0).xy);
 
-	// 3) Compare
-	//
-	const ANKI_RP F32 shadowFactor = evsmComputeShadowFactor(z, shadowMoments);
+		const F32 sinTheta = sin(randFactor * 2.0 * kPi);
+		const F32 cosTheta = cos(randFactor * 2.0 * kPi);
 
-	return shadowFactor;
+		shadow = 0.0;
+		ANKI_UNROLL for(U32 i = 0u; i < 4u; ++i)
+		{
+			const Vec2 diskPoint = kPoissonDisk[i] * kPcfScale;
+
+			// Rotate the disk point
+			Vec2 rotatedDiskPoint;
+			rotatedDiskPoint.x = diskPoint.x * cosTheta - diskPoint.y * sinTheta;
+			rotatedDiskPoint.y = diskPoint.y * cosTheta + diskPoint.x * sinTheta;
+
+			// Offset calculation
+			const Vec2 newUv = uv + rotatedDiskPoint * smTexelSize;
+
+			shadow += textureLod(shadowMap, shadowMapSampler, Vec3(newUv, z), 0.0);
+		}
+
+		shadow /= 4.0;
+	}
+	else
+	{
+		shadow = textureLod(shadowMap, shadowMapSampler, Vec3(uv, z), 0.0);
+	}
+
+	return shadow;
+}
+
+ANKI_RP F32 computeShadowFactorPointLight(PointLight light, Vec3 frag2Light, texture2D shadowMap,
+										  samplerShadow shadowMapSampler)
+{
+	return computeShadowFactorPointLightGeneric(light, frag2Light, shadowMap, shadowMapSampler, -1.0, false);
+}
+
+ANKI_RP F32 computeShadowFactorPointLightPcf(PointLight light, Vec3 frag2Light, texture2D shadowMap,
+											 samplerShadow shadowMapSampler, ANKI_RP F32 randFactor)
+{
+	return computeShadowFactorPointLightGeneric(light, frag2Light, shadowMap, shadowMapSampler, randFactor, true);
 }
 
 // Compute the shadow factor of a directional light
-ANKI_RP F32 computeShadowFactorDirLight(DirectionalLight light, U32 cascadeIdx, Vec3 worldPos, texture2D shadowMap,
-										sampler shadowMapSampler)
+ANKI_RP F32 computeShadowFactorDirLightGeneric(DirectionalLight light, U32 cascadeIdx, Vec3 worldPos,
+											   texture2D shadowMap, samplerShadow shadowMapSampler,
+											   ANKI_RP F32 randFactor, Bool pcf)
 {
 #define ANKI_FAST_CASCADES_WORKAROUND 1 // Doesn't make sense but it's super fast
 
@@ -255,11 +260,53 @@ ANKI_RP F32 computeShadowFactorDirLight(DirectionalLight light, U32 cascadeIdx,
 #endif
 
 	const Vec4 texCoords4 = lightProjectionMat * Vec4(worldPos, 1.0);
-	const Vec3 texCoords3 = texCoords4.xyz / texCoords4.w;
+	Vec3 texCoords3 = texCoords4.xyz / texCoords4.w;
+
+	ANKI_RP F32 shadow;
+	if(pcf)
+	{
+		const Vec2 smTexelSize = 1.0 / Vec2(textureSize(shadowMap, 0).xy);
+
+		const F32 sinTheta = sin(randFactor * 2.0 * kPi);
+		const F32 cosTheta = cos(randFactor * 2.0 * kPi);
 
-	const Vec4 shadowMoments = textureLod(shadowMap, shadowMapSampler, texCoords3.xy, 0.0);
+		shadow = 0.0;
+		ANKI_UNROLL for(U32 i = 0u; i < 4u; ++i)
+		{
+			const Vec2 diskPoint = kPoissonDisk[i] * kPcfScale;
 
-	return evsmComputeShadowFactor(texCoords3.z, shadowMoments);
+			// Rotate the disk point
+			Vec2 rotatedDiskPoint;
+			rotatedDiskPoint.x = diskPoint.x * cosTheta - diskPoint.y * sinTheta;
+			rotatedDiskPoint.y = diskPoint.y * cosTheta + diskPoint.x * sinTheta;
+
+			// Offset calculation
+			Vec2 newUv = texCoords3.xy + rotatedDiskPoint * smTexelSize;
+
+			shadow += textureLod(shadowMap, shadowMapSampler, Vec3(newUv, texCoords3.z), 0.0);
+		}
+
+		shadow /= 4.0;
+	}
+	else
+	{
+		shadow = textureLod(shadowMap, shadowMapSampler, texCoords3, 0.0);
+	}
+
+	return shadow;
+}
+
+ANKI_RP F32 computeShadowFactorDirLight(DirectionalLight light, U32 cascadeIdx, Vec3 worldPos, texture2D shadowMap,
+										samplerShadow shadowMapSampler)
+{
+	return computeShadowFactorDirLightGeneric(light, cascadeIdx, worldPos, shadowMap, shadowMapSampler, -1.0, false);
+}
+
+ANKI_RP F32 computeShadowFactorDirLightPcf(DirectionalLight light, U32 cascadeIdx, Vec3 worldPos, texture2D shadowMap,
+										   samplerShadow shadowMapSampler, F32 randFactor)
+{
+	return computeShadowFactorDirLightGeneric(light, cascadeIdx, worldPos, shadowMap, shadowMapSampler, randFactor,
+											  true);
 }
 
 // Compute the shadow factor of a directional light
@@ -391,30 +438,6 @@ ANKI_RP Vec3 sampleGlobalIllumination(const Vec3 worldPos, const Vec3 normal, co
 	return irradiance;
 }
 
-U32 computeShadowCascadeIndex(F32 distance, F32 p, F32 effectiveShadowDistance, U32 shadowCascadeCount)
-{
-	const F32 shadowCascadeCountf = F32(shadowCascadeCount);
-	F32 idx = pow(distance / effectiveShadowDistance, 1.0f / p) * shadowCascadeCountf;
-	idx = min(idx, shadowCascadeCountf - 1.0f);
-	return U32(idx);
-}
-
-/// Bring the indices of the closest cascades and a factor to blend them. To visualize what's going on go to
-/// https://www.desmos.com/calculator/dwlbq2j55i
-UVec2 computeShadowCascadeIndex2(F32 distance, F32 p, F32 effectiveShadowDistance, U32 shadowCascadeCount,
-								 out F32 blendFactor)
-{
-	const F32 shadowCascadeCountf = F32(shadowCascadeCount);
-	const F32 idx = pow(distance / effectiveShadowDistance, 1.0f / p) * shadowCascadeCountf;
-
-	const U32 cascadeA = min(U32(idx), shadowCascadeCount - 1u);
-	const U32 cascadeB = min(cascadeA + 1u, shadowCascadeCount - 1u);
-
-	blendFactor = pow(fract(idx), 24.0);
-
-	return UVec2(cascadeA, cascadeB);
-}
-
 /// To play with it use https://www.shadertoy.com/view/sttSDf
 /// http://jcgt.org/published/0007/04/01/paper.pdf by Eric Heitz
 /// Input v: view direction
@@ -467,3 +490,43 @@ Vec3 sampleReflectionVector(Vec3 viewDir, Vec3 normal, F32 roughness, Vec2 unifo
 	// Transform reflected_direction back to the initial space.
 	return tbn * reflectedDirTbn;
 }
+
+/// Get the index of the cascade given the distance from zero.
+U32 computeShadowCascadeIndex(F32 distance, Vec4 cascadeDistances, U32 shadowCascadeCount)
+{
+	U32 cascade;
+	if(distance < cascadeDistances[0u])
+	{
+		cascade = 0u;
+	}
+	else if(distance < cascadeDistances[1u])
+	{
+		cascade = 1u;
+	}
+	else if(distance < cascadeDistances[2u])
+	{
+		cascade = 2u;
+	}
+	else
+	{
+		cascade = 3u;
+	}
+
+	return min(shadowCascadeCount - 1u, cascade);
+}
+
+/// Bring the indices of the closest cascades and a factor to blend them. To visualize what's going on go to:
+/// https://www.desmos.com/calculator/g1ibye6ebg
+UVec2 computeShadowCascadeIndex2(F32 distance, Vec4 cascadeDistances, U32 shadowCascadeCount, out ANKI_RP F32 factor)
+{
+	const U32 cascade = computeShadowCascadeIndex(distance, cascadeDistances, shadowCascadeCount);
+	const U32 nextCascade = min(cascade + 1u, shadowCascadeCount - 1u);
+
+	const F32 minDist = (cascade == 0u) ? 0.0f : cascadeDistances[cascade - 1u];
+	const F32 maxDist = cascadeDistances[cascade];
+
+	factor = (distance - minDist) / max(kEpsilonf, maxDist - minDist);
+	factor = pow(factor, 16.0f);
+
+	return UVec2(cascade, nextCascade);
+}

+ 1 - 1
AnKi/Shaders/LightShading.ankiprog

@@ -96,7 +96,7 @@ void main()
 	if(dirLight.m_active != 0u)
 	{
 		ANKI_RP F32 shadowFactor;
-		if(dirLight.m_cascadeCount > 0u)
+		if(dirLight.m_shadowCascadeCount > 0u)
 		{
 #if USE_SHADOW_LAYERS
 			shadowFactor = resolvedSm[dirLight.m_shadowLayer];

+ 1 - 1
AnKi/Shaders/RtShadowsRayGen.ankiprog

@@ -103,7 +103,7 @@ void main()
 
 	// Dir light
 	const DirectionalLight dirLight = u_clusteredShading.m_directionalLight;
-	ANKI_BRANCH if(dirLight.m_active != 0u && dirLight.m_cascadeCount > 0u)
+	ANKI_BRANCH if(dirLight.m_active != 0u && dirLight.m_shadowCascadeCount > 0u)
 	{
 		for(I32 i = 0; i < RAYS_PER_PIXEL; ++i)
 		{

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

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

+ 95 - 26
AnKi/Shaders/ShadowmapsResolve.glsl

@@ -3,6 +3,8 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
+#pragma anki mutator PCF 0 1
+
 ANKI_SPECIALIZATION_CONSTANT_UVEC2(kFramebufferSize, 0u);
 ANKI_SPECIALIZATION_CONSTANT_UVEC2(kTileCount, 2u);
 ANKI_SPECIALIZATION_CONSTANT_U32(kZSplitCount, 4u);
@@ -14,18 +16,52 @@ ANKI_SPECIALIZATION_CONSTANT_U32(kTileSize, 5u);
 #define CLUSTERED_SHADING_CLUSTERS_BINDING 4u
 #include <AnKi/Shaders/ClusteredShadingCommon.glsl>
 
+#define DEBUG_CASCADES 0
+
 layout(set = 0, binding = 5) uniform sampler u_linearAnyClampSampler;
-layout(set = 0, binding = 6) uniform texture2D u_depthRt;
+layout(set = 0, binding = 6) uniform samplerShadow u_linearAnyClampShadowSampler;
+layout(set = 0, binding = 7) uniform sampler u_trilinearRepeatSampler;
+layout(set = 0, binding = 8) uniform texture2D u_depthRt;
+layout(set = 0, binding = 9) uniform texture2D u_noiseTex;
 
 #if defined(ANKI_COMPUTE_SHADER)
 const UVec2 kWorkgroupSize = UVec2(8, 8);
 layout(local_size_x = kWorkgroupSize.x, local_size_y = kWorkgroupSize.y, local_size_z = 1) in;
-layout(set = 0, binding = 7, rgba8) writeonly uniform ANKI_RP image2D u_outImg;
+layout(set = 0, binding = 10, rgba8) writeonly uniform ANKI_RP image2D u_outImg;
 #else
 layout(location = 0) in Vec2 in_uv;
 layout(location = 0) out Vec4 out_color;
 #endif
 
+Vec3 computeDebugShadowCascadeColor(U32 cascade)
+{
+	if(cascade == 0u)
+	{
+		return Vec3(0.0f, 1.0f, 0.0f);
+	}
+	else if(cascade == 1u)
+	{
+		return Vec3(0.0f, 0.0f, 1.0f);
+	}
+	else if(cascade == 2u)
+	{
+		return Vec3(0.0f, 1.0f, 1.0f);
+	}
+	else
+	{
+		return Vec3(1.0f, 0.0f, 0.0f);
+	}
+}
+
+void writeOutputColor(ANKI_RP Vec4 shadowFactors)
+{
+#if defined(ANKI_COMPUTE_SHADER)
+	imageStore(u_outImg, IVec2(gl_GlobalInvocationID.xy), shadowFactors);
+#else
+	out_color = shadowFactors;
+#endif
+}
+
 void main()
 {
 #if defined(ANKI_COMPUTE_SHADER)
@@ -38,6 +74,15 @@ void main()
 	const Vec2 uv = in_uv;
 #endif
 
+#if PCF
+	// Noise
+	const Vec2 kNoiseTexSize = Vec2(64.0);
+	const Vec2 noiseUv = Vec2(kFramebufferSize) / kNoiseTexSize * uv;
+	ANKI_RP Vec3 noise = textureLod(u_noiseTex, u_trilinearRepeatSampler, noiseUv, 0.0).rgb;
+	noise = animateBlueNoise(noise, u_clusteredShading.m_frame % 16u);
+	const ANKI_RP F32 randFactor = noise.x;
+#endif
+
 	// World position
 	const Vec2 ndc = UV_TO_NDC(uv);
 	const F32 depth = textureLod(u_depthRt, u_linearAnyClampSampler, uv, 0.0).r;
@@ -51,27 +96,41 @@ void main()
 
 	// Layers
 	U32 shadowCasterCountPerFragment = 0u;
-	const U32 maxShadowCastersPerFragment = 4u;
-	ANKI_RP F32 shadowFactors[maxShadowCastersPerFragment] = F32[](0.0, 0.0, 0.0, 0.0);
+	const U32 kMaxShadowCastersPerFragment = 4u;
+	ANKI_RP Vec4 shadowFactors = Vec4(0.0f);
 
 	// Dir light
 	const DirectionalLight dirLight = u_clusteredShading.m_directionalLight;
-	if(dirLight.m_active != 0u && dirLight.m_cascadeCount > 0u)
+	if(dirLight.m_active != 0u && dirLight.m_shadowCascadeCount > 0u)
 	{
 		const ANKI_RP F32 positiveZViewSpace =
 			testPlanePoint(u_clusteredShading.m_nearPlaneWSpace.xyz, u_clusteredShading.m_nearPlaneWSpace.w, worldPos)
 			+ u_clusteredShading.m_near;
 
+		const F32 lastCascadeDistance = dirLight.m_shadowCascadeDistances[dirLight.m_shadowCascadeCount - 1u];
 		ANKI_RP F32 shadowFactor;
-		if(positiveZViewSpace < dirLight.m_effectiveShadowDistance)
+		if(positiveZViewSpace < lastCascadeDistance)
 		{
-			F32 cascadeBlendFactor;
-			const UVec2 cascadeIndices = computeShadowCascadeIndex2(
-				positiveZViewSpace, dirLight.m_shadowCascadesDistancePower, dirLight.m_effectiveShadowDistance,
-				dirLight.m_cascadeCount, cascadeBlendFactor);
+			ANKI_RP F32 cascadeBlendFactor;
+			const UVec2 cascadeIndices =
+				computeShadowCascadeIndex2(positiveZViewSpace, dirLight.m_shadowCascadeDistances,
+										   dirLight.m_shadowCascadeCount, cascadeBlendFactor);
+
+#if DEBUG_CASCADES
+			const Vec3 debugColorA = computeDebugShadowCascadeColor(cascadeIndices[0]);
+			const Vec3 debugColorB = computeDebugShadowCascadeColor(cascadeIndices[1]);
+			const Vec3 debugColor = mix(debugColorA, debugColorB, cascadeBlendFactor);
+			writeOutputColor(Vec4(debugColor, 0.0f));
+			return;
+#endif
 
-			const F32 shadowFactorCascadeA = computeShadowFactorDirLight(dirLight, cascadeIndices.x, worldPos,
-																		 u_shadowAtlasTex, u_linearAnyClampSampler);
+#if PCF
+			const F32 shadowFactorCascadeA = computeShadowFactorDirLightPcf(
+				dirLight, cascadeIndices.x, worldPos, u_shadowAtlasTex, u_linearAnyClampShadowSampler, randFactor);
+#else
+			const F32 shadowFactorCascadeA = computeShadowFactorDirLight(
+				dirLight, cascadeIndices.x, worldPos, u_shadowAtlasTex, u_linearAnyClampShadowSampler);
+#endif
 
 			if(cascadeBlendFactor < 0.01 || cascadeIndices.x == cascadeIndices.y)
 			{
@@ -80,14 +139,19 @@ void main()
 			}
 			else
 			{
+#if PCF
 				// Blend cascades
-				const F32 shadowFactorCascadeB = computeShadowFactorDirLight(dirLight, cascadeIndices.y, worldPos,
-																			 u_shadowAtlasTex, u_linearAnyClampSampler);
-
+				const F32 shadowFactorCascadeB = computeShadowFactorDirLightPcf(
+					dirLight, cascadeIndices.y, worldPos, u_shadowAtlasTex, u_linearAnyClampShadowSampler, randFactor);
+#else
+				// Blend cascades
+				const F32 shadowFactorCascadeB = computeShadowFactorDirLight(
+					dirLight, cascadeIndices.y, worldPos, u_shadowAtlasTex, u_linearAnyClampShadowSampler);
+#endif
 				shadowFactor = mix(shadowFactorCascadeA, shadowFactorCascadeB, cascadeBlendFactor);
 			}
 
-			ANKI_RP F32 distanceFadeFactor = saturate(positiveZViewSpace / dirLight.m_effectiveShadowDistance);
+			ANKI_RP F32 distanceFadeFactor = saturate(positiveZViewSpace / lastCascadeDistance);
 			distanceFadeFactor = pow(distanceFadeFactor, 8.0);
 			shadowFactor += distanceFadeFactor;
 		}
@@ -111,9 +175,14 @@ void main()
 		{
 			const Vec3 frag2Light = light.m_position - worldPos;
 
+#if PCF
+			const ANKI_RP F32 shadowFactor = computeShadowFactorPointLightPcf(
+				light, frag2Light, u_shadowAtlasTex, u_linearAnyClampShadowSampler, randFactor);
+#else
 			const ANKI_RP F32 shadowFactor =
-				computeShadowFactorPointLight(light, frag2Light, u_shadowAtlasTex, u_linearAnyClampSampler);
-			shadowFactors[min(maxShadowCastersPerFragment - 1u, shadowCasterCountPerFragment++)] = shadowFactor;
+				computeShadowFactorPointLight(light, frag2Light, u_shadowAtlasTex, u_linearAnyClampShadowSampler);
+#endif
+			shadowFactors[min(kMaxShadowCastersPerFragment - 1u, shadowCasterCountPerFragment++)] = shadowFactor;
 		}
 	}
 
@@ -126,17 +195,17 @@ void main()
 
 		ANKI_BRANCH if(light.m_shadowLayer != kMaxU32)
 		{
+#if PCF
+			const ANKI_RP F32 shadowFactor = computeShadowFactorSpotLightPcf(light, worldPos, u_shadowAtlasTex,
+																			 u_linearAnyClampShadowSampler, randFactor);
+#else
 			const ANKI_RP F32 shadowFactor =
-				computeShadowFactorSpotLight(light, worldPos, u_shadowAtlasTex, u_linearAnyClampSampler);
-			shadowFactors[min(maxShadowCastersPerFragment - 1u, shadowCasterCountPerFragment++)] = shadowFactor;
+				computeShadowFactorSpotLight(light, worldPos, u_shadowAtlasTex, u_linearAnyClampShadowSampler);
+#endif
+			shadowFactors[min(kMaxShadowCastersPerFragment - 1u, shadowCasterCountPerFragment++)] = shadowFactor;
 		}
 	}
 
 	// Store
-#if defined(ANKI_COMPUTE_SHADER)
-	imageStore(u_outImg, IVec2(gl_GlobalInvocationID.xy),
-			   Vec4(shadowFactors[0], shadowFactors[1], shadowFactors[2], shadowFactors[3]));
-#else
-	out_color = Vec4(shadowFactors[0], shadowFactors[1], shadowFactors[2], shadowFactors[3]);
-#endif
+	writeOutputColor(shadowFactors);
 }

+ 0 - 1
AnKi/Shaders/VolumetricFogAccumulation.ankiprog

@@ -9,7 +9,6 @@ ANKI_SPECIALIZATION_CONSTANT_U32(kZSplitCount, 4u);
 
 #pragma anki start comp
 
-#include <AnKi/Shaders/Include/ClusteredShadingFunctions.h>
 #include <AnKi/Shaders/Include/MiscRendererTypes.h>
 #include <AnKi/Shaders/Common.glsl>
 

+ 17 - 16
AnKi/Shaders/VolumetricLightingAccumulation.ankiprog

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

+ 4 - 4
AnKi/Util/Array.h

@@ -33,7 +33,7 @@ public:
 
 	/// Access an element using an integer.
 	template<typename TInt, ANKI_ENABLE(!std::is_enum<TInt>::value)>
-	Reference operator[](const TInt n)
+	constexpr Reference operator[](const TInt n)
 	{
 		ANKI_ASSERT(PtrSize(n) < kSize);
 		return m_data[n];
@@ -41,7 +41,7 @@ public:
 
 	/// Access an element using an integer.
 	template<typename TInt, ANKI_ENABLE(!std::is_enum<TInt>::value)>
-	ConstReference operator[](const TInt n) const
+	constexpr ConstReference operator[](const TInt n) const
 	{
 		ANKI_ASSERT(PtrSize(n) < kSize);
 		return m_data[n];
@@ -50,7 +50,7 @@ public:
 	/// Access an element using an enumerant. It's a little bit special and separate from operator[] that accepts
 	/// integer. This to avoid any short of arbitrary integer type casting.
 	template<typename TEnum, ANKI_ENABLE(std::is_enum<TEnum>::value)>
-	Reference operator[](const TEnum n)
+	constexpr Reference operator[](const TEnum n)
 	{
 		return operator[](typename std::underlying_type<TEnum>::type(n));
 	}
@@ -58,7 +58,7 @@ public:
 	/// Access an element using an enumerant. It's a little bit special and separate from operator[] that accepts
 	/// integer. This to avoid any short of arbitrary integer type casting.
 	template<typename TEnum, ANKI_ENABLE(std::is_enum<TEnum>::value)>
-	ConstReference operator[](const TEnum n) const
+	constexpr ConstReference operator[](const TEnum n) const
 	{
 		return operator[](typename std::underlying_type<TEnum>::type(n));
 	}

+ 23 - 5
AnKi/Util/Enum.h

@@ -140,7 +140,7 @@ class EnumIterableIterator
 public:
 	using Type = typename std::underlying_type<TEnum>::type;
 
-	EnumIterableIterator(TEnum val)
+	constexpr EnumIterableIterator(TEnum val)
 		: m_val(static_cast<Type>(val))
 	{
 	}
@@ -177,15 +177,33 @@ class EnumIterable
 public:
 	using Iterator = EnumIterableIterator<TEnum>;
 
-	static Iterator begin()
+	constexpr EnumIterable()
+		: m_begin(TEnum::kFirst)
+		, m_end(TEnum::kCount)
 	{
-		return Iterator(TEnum::kFirst);
+		ANKI_ASSERT(m_begin <= m_end);
 	}
 
-	static Iterator end()
+	constexpr EnumIterable(TEnum begin, TEnum end)
+		: m_begin(begin)
+		, m_end(end)
 	{
-		return Iterator(TEnum::kCount);
+		ANKI_ASSERT(m_begin <= m_end);
 	}
+
+	Iterator begin() const
+	{
+		return Iterator(m_begin);
+	}
+
+	Iterator end() const
+	{
+		return Iterator(m_end);
+	}
+
+public:
+	TEnum m_begin;
+	TEnum m_end;
 };
 /// @}
 

+ 1 - 1
AnKi/Util/Logger.h

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

+ 8 - 3
AnKi/Util/String.h

@@ -429,6 +429,11 @@ public:
 	void create(TMemPool& pool, ConstIterator first, ConstIterator last)
 	{
 		ANKI_ASSERT(first != 0 && last != 0);
+		for(ConstIterator it = first; it < last; ++it)
+		{
+			ANKI_ASSERT(*it != '\0');
+		}
+
 		auto length = last - first;
 		m_data.create(pool, length + 1);
 
@@ -841,7 +846,7 @@ public:
 	{
 		if(!b.isEmpty())
 		{
-			create(b.m_data.getBegin(), b.m_data.getEnd());
+			create(b.m_data.getBegin(), b.m_data.getEnd() - 1);
 		}
 	}
 
@@ -873,7 +878,7 @@ public:
 		m_pool = b.m_pool;
 		if(!b.isEmpty())
 		{
-			create(b.m_data.getBegin(), b.m_data.getEnd());
+			create(b.m_data.getBegin(), b.m_data.getEnd() - 1);
 		}
 		return *this;
 	}
@@ -884,7 +889,7 @@ public:
 		destroy();
 		if(!b.isEmpty())
 		{
-			create(b.getBegin(), b.getEnd());
+			create(b.getBegin(), b.getEnd() - 1);
 		}
 		return *this;
 	}

BIN
EngineAssets/LightMeshes.blend


BIN
EngineAssets/Plight.ankimesh


BIN
EngineAssets/Slight.ankimesh


+ 2 - 2
Samples/Common/SampleApp.cpp

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

+ 0 - 5
Samples/PhysicsPlayground/Assets/Icosphere.ankicl

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<collisionShape>
-	<type>staticMesh</type>
-	<value>assets/Icosphere.ankimesh</value>
-</collisionShape>

BIN
Samples/PhysicsPlayground/Assets/Icosphere_834d64c142beaa13.ankimesh


BIN
Samples/PhysicsPlayground/Assets/Icosphere_lod0_834d64c142beaa13.ankimesh


+ 1 - 1
Samples/PhysicsPlayground/Assets/Icosphere_walls_ac2438354c62251.ankimdl

@@ -1,7 +1,7 @@
 <model>
 	<modelPatches>
 		<modelPatch>
-			<mesh>Assets/Icosphere_lod0_834d64c142beaa13.ankimesh</mesh>
+			<mesh>Assets/Icosphere_834d64c142beaa13.ankimesh</mesh>
 			<material>Assets/walls_9619132fa258d22d.ankimtl</material>
 		</modelPatch>
 	</modelPatches>

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio