Browse Source

GPU particles: Work on the material component

Panagiotis Christopoulos Charitos 1 month ago
parent
commit
751a7f2ee0

+ 153 - 79
AnKi/Scene/Components/MaterialComponent.cpp

@@ -7,6 +7,7 @@
 #include <AnKi/Scene/Components/SkinComponent.h>
 #include <AnKi/Scene/Components/MeshComponent.h>
 #include <AnKi/Scene/Components/MoveComponent.h>
+#include <AnKi/Scene/Components/ParticleEmitter2Component.h>
 #include <AnKi/Resource/MeshResource.h>
 #include <AnKi/Resource/MaterialResource.h>
 #include <AnKi/Resource/ResourceManager.h>
@@ -35,7 +36,7 @@ MaterialComponent& MaterialComponent::setMaterialFilename(CString fname)
 	}
 	else
 	{
-		m_resourceDirty = !m_resource || (m_resource->getUuid() != newRsrc->getUuid());
+		m_anyDirty = !m_resource || (m_resource->getUuid() != newRsrc->getUuid());
 		m_resource = std::move(newRsrc);
 		m_castsShadow = m_resource->castsShadow();
 	}
@@ -60,7 +61,7 @@ MaterialComponent& MaterialComponent::setSubmeshIndex(U32 submeshIdx)
 	if(m_submeshIdx != submeshIdx)
 	{
 		m_submeshIdx = submeshIdx;
-		m_submeshIdxDirty = true;
+		m_anyDirty = true;
 	}
 
 	return *this;
@@ -72,42 +73,69 @@ void MaterialComponent::onOtherComponentRemovedOrAdded(SceneComponent* other, Bo
 
 	if(other->getType() == SceneComponentType::kMesh)
 	{
-		Bool dirty;
-		bookkeepComponent(m_meshComponents, other, added, dirty);
-		m_meshComponentDirty = dirty;
+		bookkeepComponent(m_meshComponent, other, added);
+		m_anyDirty = true;
 	}
 
 	if(other->getType() == SceneComponentType::kSkin)
 	{
-		Bool dirty;
-		bookkeepComponent(m_skinComponents, other, added, dirty);
-		m_skinDirty = dirty;
+		bookkeepComponent(m_skinComponent, other, added);
+		m_anyDirty = true;
+	}
+
+	if(other->getType() == SceneComponentType::kParticleEmitter2)
+	{
+		bookkeepComponent(m_emitterComponent, other, added);
+		m_anyDirty = true;
 	}
 }
 
-Aabb MaterialComponent::computeAabb(U32 submeshIndex, const SceneNode& node) const
+Bool MaterialComponent::isValid() const
 {
-	U32 firstIndex, indexCount, firstMeshlet, meshletCount;
-	Aabb aabbLocal;
-	m_meshComponents[0]->getMeshResource().getSubMeshInfo(0, submeshIndex, firstIndex, indexCount, firstMeshlet, meshletCount, aabbLocal);
+	Bool valid = !!m_resource && m_resource->isLoaded();
 
-	if(m_skinComponents.getSize() && m_skinComponents[0]->isValid())
+	if(m_meshComponent)
 	{
-		aabbLocal = m_skinComponents[0]->getBoneBoundingVolumeLocalSpace().getCompoundShape(aabbLocal);
+		valid = valid && m_meshComponent->isValid();
 	}
 
-	const Aabb aabbWorld = aabbLocal.getTransformed(node.getWorldTransform());
-	return aabbWorld;
+	if(m_emitterComponent)
+	{
+		valid = valid && m_emitterComponent->isValid();
+	}
+
+	valid = valid && (m_meshComponent || m_emitterComponent);
+
+	if(m_skinComponent)
+	{
+		valid = valid && m_skinComponent->isValid();
+	}
+	return valid;
 }
 
-Bool MaterialComponent::isValid() const
+Aabb MaterialComponent::computeAabb(const SceneNode& node) const
 {
-	Bool valid = !!m_resource && m_resource->isLoaded() && m_meshComponents.getSize() && m_meshComponents[0]->isValid();
-	if(m_skinComponents.getSize())
+	const Bool prioritizeEmitter = m_emitterComponent != nullptr;
+	Aabb aabbWorld;
+	if(prioritizeEmitter)
 	{
-		valid = valid && m_skinComponents[0]->isValid();
+		aabbWorld = Aabb(m_emitterComponent->getBoundingVolume()[0], m_emitterComponent->getBoundingVolume()[1]);
 	}
-	return valid;
+	else
+	{
+		Aabb aabbLocal;
+		U32 firstIndex, indexCount, firstMeshlet, meshletCount;
+		m_meshComponent->getMeshResource().getSubMeshInfo(0, m_submeshIdx, firstIndex, indexCount, firstMeshlet, meshletCount, aabbLocal);
+
+		if(m_skinComponent && m_skinComponent->isValid())
+		{
+			aabbLocal = m_skinComponent->getBoneBoundingVolumeLocalSpace().getCompoundShape(aabbLocal);
+		}
+
+		aabbWorld = aabbLocal.getTransformed(node.getWorldTransform());
+	}
+
+	return aabbWorld;
 }
 
 void MaterialComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
@@ -129,30 +157,81 @@ void MaterialComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 
 	// From now on the component is considered valid
 
-	const Bool mtlResourceChanged = m_resourceDirty;
-	const Bool meshChanged = m_meshComponentDirty || m_meshComponents[0]->gpuSceneReallocationsThisFrame();
-	const Bool moved = info.m_node->movedThisFrame() || m_firstTimeUpdate;
-	const Bool movedLastFrame = m_movedLastFrame || m_firstTimeUpdate;
-	const Bool hasSkin = m_skinComponents.getSize();
-	const Bool skinChanged = m_skinDirty || (hasSkin && m_skinComponents[0]->gpuSceneReallocationsThisFrame());
-	const Bool submeshChanged = m_submeshIdxDirty;
-
-	updated = mtlResourceChanged || meshChanged || moved || skinChanged || submeshChanged;
-
-	m_resourceDirty = false;
-	m_firstTimeUpdate = false;
-	m_meshComponentDirty = false;
+	const Bool moved = info.m_node->movedThisFrame();
+	const Bool movedLastFrame = m_movedLastFrame;
 	m_movedLastFrame = moved;
-	m_skinDirty = false;
-	m_submeshIdxDirty = false;
 
+	Bool dirty = m_anyDirty || moved != movedLastFrame;
+
+	const Bool prioritizeEmitter = !!m_emitterComponent;
 	const MaterialResource& mtl = *m_resource;
-	const MeshResource& mesh = m_meshComponents[0]->getMeshResource();
-	const U32 submeshIdx = min(mesh.getSubMeshCount() - 1, m_submeshIdx);
+
+	if(m_skinComponent)
+	{
+		dirty = dirty || m_skinComponent->gpuSceneReallocationsThisFrame();
+	}
+
+	if(m_emitterComponent)
+	{
+		dirty = dirty || m_emitterComponent->gpuSceneReallocationsThisFrame();
+	}
+
+	if(m_meshComponent)
+	{
+		dirty = dirty || m_meshComponent->gpuSceneReallocationsThisFrame();
+	}
+
+	if(!dirty) [[likely]]
+	{
+		// Update Scene bounds
+		if(info.m_forceUpdateSceneBounds) [[unlikely]]
+		{
+			const Aabb aabbWorld = computeAabb(*info.m_node);
+			info.updateSceneBounds(aabbWorld.getMin().xyz(), aabbWorld.getMax().xyz());
+		}
+
+		// Update the GPU scene AABBs
+		if(prioritizeEmitter)
+		{
+			const Aabb aabbWorld = computeAabb(*info.m_node);
+			for(RenderingTechnique t : EnumBitsIterable<RenderingTechnique, RenderingTechniqueBit>(mtl.getRenderingTechniques()))
+			{
+				const GpuSceneRenderableBoundingVolume gpuVolume = initGpuSceneRenderableBoundingVolume(
+					aabbWorld.getMin().xyz(), aabbWorld.getMax().xyz(), m_gpuSceneRenderable.getIndex(), m_renderStateBucketIndices[t].get());
+
+				switch(t)
+				{
+				case RenderingTechnique::kGBuffer:
+					m_gpuSceneRenderableAabbGBuffer.uploadToGpuScene(gpuVolume);
+					break;
+				case RenderingTechnique::kDepth:
+					m_gpuSceneRenderableAabbDepth.uploadToGpuScene(gpuVolume);
+					break;
+				case RenderingTechnique::kForward:
+					m_gpuSceneRenderableAabbForward.uploadToGpuScene(gpuVolume);
+					break;
+				case RenderingTechnique::kRtMaterialFetch:
+					m_gpuSceneRenderableAabbRt.uploadToGpuScene(gpuVolume);
+					break;
+				default:
+					ANKI_ASSERT(0);
+				}
+			}
+		}
+
+		return;
+	}
+
+	// From now on something is dirty, update everything
+
+	updated = true;
+	m_anyDirty = false;
+
+	// Sanitize
+	m_submeshIdx = min(m_submeshIdx, (m_meshComponent) ? (m_meshComponent->getMeshResource().getSubMeshCount() - 1) : 0);
 
 	// Extract the diffuse color
 	Vec3 averageDiffuse(0.0f);
-	if(mtlResourceChanged)
 	{
 		const MaterialVariable* diffuseRelatedMtlVar = nullptr;
 
@@ -201,8 +280,6 @@ void MaterialComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 	}
 
 	// Update the constants
-	Bool constantsReallocated = false;
-	if(mtlResourceChanged) [[unlikely]]
 	{
 		ConstWeakArray<U8> preallocatedConsts = mtl.getPrefilledLocalConstants();
 
@@ -210,7 +287,6 @@ void MaterialComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 		{
 			GpuSceneBuffer::getSingleton().deferredFree(m_gpuSceneConstants);
 			m_gpuSceneConstants = GpuSceneBuffer::getSingleton().allocate(preallocatedConsts.getSizeInBytes(), 4);
-			constantsReallocated = true;
 		}
 
 		GpuSceneMicroPatcher::getSingleton().newCopy(*info.m_framePool, m_gpuSceneConstants.getOffset(), m_gpuSceneConstants.getAllocatedSize(),
@@ -218,16 +294,16 @@ void MaterialComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 	}
 
 	// Update renderable
-	if(mtlResourceChanged || constantsReallocated || skinChanged || meshChanged || submeshChanged) [[unlikely]]
 	{
 		const MoveComponent& movec = info.m_node->getFirstComponentOfType<MoveComponent>();
 
 		GpuSceneRenderable gpuRenderable = {};
-		gpuRenderable.m_worldTransformsIndex = movec.getGpuSceneTransforms().getIndex() * 2;
+		gpuRenderable.m_worldTransformsIndex = movec.getGpuSceneTransformsIndex();
 		gpuRenderable.m_constantsOffset = m_gpuSceneConstants.getOffset();
-		gpuRenderable.m_meshLodsIndex = m_meshComponents[0]->getGpuSceneMeshLods(submeshIdx).getIndex() * kMaxLodCount;
-		gpuRenderable.m_boneTransformsOffset = (hasSkin) ? m_skinComponents[0]->getBoneTransformsGpuSceneOffset() : 0;
-		gpuRenderable.m_particleEmitterIndex = kMaxU32;
+		gpuRenderable.m_meshLodsIndex =
+			(prioritizeEmitter) ? m_emitterComponent->getGpuSceneMeshLodIndex(m_submeshIdx) : m_meshComponent->getGpuSceneMeshLodsIndex(m_submeshIdx);
+		gpuRenderable.m_boneTransformsOffset = (m_skinComponent) ? m_skinComponent->getBoneTransformsGpuSceneOffset() : 0;
+		gpuRenderable.m_particleEmitterIndex = (prioritizeEmitter) ? m_emitterComponent->getGpuSceneParticleEmitter2Index() : kMaxU32;
 		if(!!(mtl.getRenderingTechniques() & RenderingTechniqueBit::kRtShadow))
 		{
 			const RenderingKey key(RenderingTechnique::kRtShadow, 0, false, false, false);
@@ -248,55 +324,53 @@ void MaterialComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 		m_gpuSceneRenderable.uploadToGpuScene(gpuRenderable);
 	}
 
-	// Scene bounds update
-	const Bool aabbUpdated = moved || meshChanged || submeshChanged || hasSkin;
-	if(aabbUpdated || info.m_forceUpdateSceneBounds) [[unlikely]]
+	// Update scene bounds
 	{
-		const Aabb aabbWorld = computeAabb(submeshIdx, *info.m_node);
+		const Aabb aabbWorld = computeAabb(*info.m_node);
 		info.updateSceneBounds(aabbWorld.getMin().xyz(), aabbWorld.getMax().xyz());
 	}
 
 	// Update the buckets
-	const Bool bucketsNeedUpdate = mtlResourceChanged || submeshChanged || moved != movedLastFrame || skinChanged;
-	if(bucketsNeedUpdate) [[unlikely]]
+	for(RenderingTechnique t : EnumIterable<RenderingTechnique>())
 	{
-		for(RenderingTechnique t : EnumIterable<RenderingTechnique>())
-		{
-			RenderStateBucketContainer::getSingleton().removeUser(m_renderStateBucketIndices[t]);
+		RenderStateBucketContainer::getSingleton().removeUser(m_renderStateBucketIndices[t]);
 
-			if(!(RenderingTechniqueBit(1 << t) & mtl.getRenderingTechniques()))
-			{
-				continue;
-			}
+		if(!(RenderingTechniqueBit(1 << t) & mtl.getRenderingTechniques()))
+		{
+			continue;
+		}
 
-			// Fill the state
-			RenderingKey key;
-			key.setLod(0); // Materials don't care
-			key.setRenderingTechnique(t);
-			key.setSkinned(hasSkin);
-			key.setVelocity(moved);
-			key.setMeshletRendering(GrManager::getSingleton().getDeviceCapabilities().m_meshShaders || g_cvarCoreMeshletRendering);
+		// Fill the state
+		RenderingKey key;
+		key.setLod(0); // Materials don't care
+		key.setRenderingTechnique(t);
+		key.setSkinned(m_skinComponent != nullptr);
+		key.setVelocity(moved);
+		key.setMeshletRendering(!prioritizeEmitter
+								&& (GrManager::getSingleton().getDeviceCapabilities().m_meshShaders || g_cvarCoreMeshletRendering));
 
-			const MaterialVariant& mvariant = mtl.getOrCreateVariant(key);
+		const MaterialVariant& mvariant = mtl.getOrCreateVariant(key);
 
-			RenderStateInfo state;
-			state.m_primitiveTopology = PrimitiveTopology::kTriangles;
-			state.m_program = mvariant.getShaderProgram();
+		RenderStateInfo state;
+		state.m_primitiveTopology = PrimitiveTopology::kTriangles;
+		state.m_program = mvariant.getShaderProgram();
 
-			U32 firstIndex, indexCount, firstMeshlet, meshletCount;
+		Bool wantsMesletCount = false;
+		U32 meshletCount = 0;
+		if(!prioritizeEmitter)
+		{
+			U32 firstIndex, indexCount, firstMeshlet;
 			Aabb aabb;
-			mesh.getSubMeshInfo(0, submeshIdx, firstIndex, indexCount, firstMeshlet, meshletCount, aabb);
-			const Bool wantsMesletCount = key.getMeshletRendering() && !(RenderingTechniqueBit(1 << t) & RenderingTechniqueBit::kAllRt);
-
-			m_renderStateBucketIndices[t] = RenderStateBucketContainer::getSingleton().addUser(state, t, (wantsMesletCount) ? meshletCount : 0);
+			m_meshComponent->getMeshResource().getSubMeshInfo(0, m_submeshIdx, firstIndex, indexCount, firstMeshlet, meshletCount, aabb);
+			wantsMesletCount = key.getMeshletRendering() && !(RenderingTechniqueBit(1 << t) & RenderingTechniqueBit::kAllRt);
 		}
+
+		m_renderStateBucketIndices[t] = RenderStateBucketContainer::getSingleton().addUser(state, t, (wantsMesletCount) ? meshletCount : 0);
 	}
 
 	// Upload the AABBs to the GPU scene
-	const Bool gpuSceneAabbsNeedUpdate = aabbUpdated || bucketsNeedUpdate;
-	if(gpuSceneAabbsNeedUpdate) [[unlikely]]
 	{
-		const Aabb aabbWorld = computeAabb(submeshIdx, *info.m_node);
+		const Aabb aabbWorld = computeAabb(*info.m_node);
 
 		// Raster
 		for(RenderingTechnique t : EnumBitsIterable<RenderingTechnique, RenderingTechniqueBit>(RenderingTechniqueBit::kAllRaster))

+ 6 - 13
AnKi/Scene/Components/MaterialComponent.h

@@ -15,10 +15,7 @@ namespace anki {
 // Forward
 class Aabb;
 
-/// @addtogroup scene
-/// @{
-
-/// Holds geometry information.
+// Connects the material resource with geometry based components (mesh or particle emitter)
 class MaterialComponent final : public SceneComponent
 {
 	ANKI_SCENE_COMPONENT(MaterialComponent)
@@ -58,25 +55,21 @@ private:
 
 	MaterialResourcePtr m_resource;
 
-	SceneDynamicArray<SkinComponent*> m_skinComponents;
-	SceneDynamicArray<MeshComponent*> m_meshComponents;
+	SkinComponent* m_skinComponent = nullptr;
+	MeshComponent* m_meshComponent = nullptr;
+	ParticleEmitter2Component* m_emitterComponent = nullptr;
 
 	U32 m_submeshIdx = 0;
 
-	Bool m_resourceDirty : 1 = true;
-	Bool m_skinDirty : 1 = true;
-	Bool m_meshComponentDirty : 1 = true;
-	Bool m_firstTimeUpdate : 1 = true; ///< Extra flag in case the component is added in a node that hasn't been moved.
+	Bool m_anyDirty : 1 = true; // A compound flag because it's too difficult to track everything
 	Bool m_movedLastFrame : 1 = true;
-	Bool m_submeshIdxDirty : 1 = true;
 	Bool m_castsShadow : 1 = false;
 
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
 
 	void onOtherComponentRemovedOrAdded(SceneComponent* other, Bool added) override;
 
-	Aabb computeAabb(U32 submeshIndex, const SceneNode& node) const;
+	Aabb computeAabb(const SceneNode& node) const;
 };
-/// @}
 
 } // end namespace anki

+ 2 - 2
AnKi/Scene/Components/MeshComponent.h

@@ -40,10 +40,10 @@ public:
 		return *m_resource;
 	}
 
-	ANKI_INTERNAL const GpuSceneArrays::MeshLod::Allocation& getGpuSceneMeshLods(U32 submeshIdx) const
+	ANKI_INTERNAL U32 getGpuSceneMeshLodsIndex(U32 submeshIdx) const
 	{
 		ANKI_ASSERT(isValid());
-		return m_gpuSceneMeshLods[submeshIdx];
+		return m_gpuSceneMeshLods[submeshIdx].getIndex() * kMaxLodCount;
 	}
 
 	ANKI_INTERNAL Bool gpuSceneReallocationsThisFrame() const

+ 4 - 1
AnKi/Scene/Components/MoveComponent.cpp

@@ -10,7 +10,10 @@ namespace anki {
 
 void MoveComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 {
-	updated = info.m_node->updateTransform();
+	const Bool movedThisFrame = info.m_node->updateTransform();
+	const Bool movedLastFrame = m_movedLastFrame;
+	m_movedLastFrame = movedThisFrame;
+	updated = movedThisFrame || movedLastFrame != movedThisFrame;
 
 	if(updated) [[unlikely]]
 	{

+ 4 - 2
AnKi/Scene/Components/MoveComponent.h

@@ -30,14 +30,16 @@ public:
 		m_gpuSceneTransforms.free();
 	}
 
-	ANKI_INTERNAL const GpuSceneArrays::Transform::Allocation& getGpuSceneTransforms() const
+	ANKI_INTERNAL U32 getGpuSceneTransformsIndex() const
 	{
-		return m_gpuSceneTransforms;
+		return m_gpuSceneTransforms.getIndex() * 2;
 	}
 
 private:
 	GpuSceneArrays::Transform::Allocation m_gpuSceneTransforms;
 
+	Bool m_movedLastFrame = true;
+
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
 };
 /// @}

+ 9 - 2
AnKi/Scene/Components/ParticleEmitter2Component.cpp

@@ -212,9 +212,12 @@ void ParticleEmitter2Component::update(SceneComponentUpdateInfo& info, Bool& upd
 		GpuSceneBuffer::getSingleton().deferredFree(m_gpuScene.m_aliveParticleIndices);
 		GpuSceneBuffer::getSingleton().deferredFree(m_gpuScene.m_anKiParticleEmitterProperties);
 		m_gpuScene.m_gpuSceneParticleEmitter.free();
+		m_gpuSceneReallocationsThisFrame = true;
 		return;
 	}
 
+	m_gpuSceneReallocationsThisFrame = false;
+
 	if(!m_resourceDirty && !m_geomTypeDirty && !m_meshComponentDirty) [[likely]]
 	{
 		return;
@@ -222,6 +225,8 @@ void ParticleEmitter2Component::update(SceneComponentUpdateInfo& info, Bool& upd
 
 	// From now on it's dirty and needs some kind of update
 
+	const Bool gpuSceneMeshLodsChanged = m_geomTypeDirty;
+	m_gpuSceneReallocationsThisFrame = gpuSceneMeshLodsChanged;
 	updated = true;
 	const ParticleEmitterResourceCommonProperties& commonProps = m_particleEmitterResource->getCommonProperties();
 
@@ -260,6 +265,7 @@ void ParticleEmitter2Component::update(SceneComponentUpdateInfo& info, Bool& upd
 	{
 		if(!m_gpuScene.m_gpuSceneParticleEmitter)
 		{
+			m_gpuSceneReallocationsThisFrame = true;
 			m_gpuScene.m_gpuSceneParticleEmitter.allocate();
 		}
 
@@ -290,7 +296,8 @@ void ParticleEmitter2Component::update(SceneComponentUpdateInfo& info, Bool& upd
 		}
 
 		emitter.m_reinitializeOnNextUpdate = 1;
-		emitter.m_worldTransformsIndex = info.m_node->getFirstComponentOfType<MoveComponent>().getGpuSceneTransforms().getIndex();
+		emitter.m_worldTransformsIndex = info.m_node->getFirstComponentOfType<MoveComponent>().getGpuSceneTransformsIndex();
+		emitter.m_uuid = getUuid();
 
 		m_gpuScene.m_gpuSceneParticleEmitter.uploadToGpuScene(emitter);
 	}
@@ -305,7 +312,7 @@ U32 ParticleEmitter2Component::getGpuSceneMeshLodIndex(U32 submeshIdx) const
 	}
 	else
 	{
-		return m_meshComponent->getGpuSceneMeshLods(submeshIdx).getIndex();
+		return m_meshComponent->getGpuSceneMeshLodsIndex(submeshIdx);
 	}
 }
 

+ 32 - 3
AnKi/Scene/Components/ParticleEmitter2Component.h

@@ -56,6 +56,32 @@ public:
 
 	ANKI_INTERNAL U32 getGpuSceneMeshLodIndex(U32 submeshIdx) const;
 
+	ANKI_INTERNAL U32 getGpuSceneParticleEmitter2Index() const
+	{
+		ANKI_ASSERT(isValid());
+		return m_gpuScene.m_gpuSceneParticleEmitter.getIndex();
+	}
+
+	// Return true if the above indices changed this frame
+	ANKI_INTERNAL Bool gpuSceneReallocationsThisFrame() const
+	{
+		ANKI_ASSERT(isValid());
+		return m_gpuSceneReallocationsThisFrame;
+	}
+
+	// The renderer will call it to update the bounds of the emitter
+	ANKI_INTERNAL void updateBoundingVolume(Vec3 min, Vec3 max)
+	{
+		ANKI_ASSERT(min < max);
+		m_boundingVolume = {min, max};
+	}
+
+	ANKI_INTERNAL Array<Vec3, 2> getBoundingVolume() const
+	{
+		ANKI_ASSERT(isValid());
+		return m_boundingVolume;
+	}
+
 private:
 	class ParticleEmitterQuadGeometry;
 
@@ -72,10 +98,13 @@ private:
 		GpuSceneArrays::ParticleEmitter2::Allocation m_gpuSceneParticleEmitter;
 	} m_gpuScene;
 
+	Array<Vec3, 2> m_boundingVolume;
+
 	ParticleGeometryType m_geomType = ParticleGeometryType::kQuad;
-	Bool m_resourceDirty = true;
-	Bool m_meshComponentDirty = true;
-	Bool m_geomTypeDirty = true;
+	Bool m_resourceDirty : 1 = true;
+	Bool m_meshComponentDirty : 1 = true;
+	Bool m_geomTypeDirty : 1 = true;
+	Bool m_gpuSceneReallocationsThisFrame : 1 = true; // Only tracks the memory shared externally
 
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
 

+ 1 - 0
AnKi/Scene/SceneNode.h

@@ -383,6 +383,7 @@ public:
 		return m_transformUpdatedThisFrame;
 	}
 
+	// Returns true if the transform got updated
 	ANKI_INTERNAL Bool updateTransform();
 	/// @}
 

+ 1 - 2
AnKi/Shaders/GpuParticlesCommon.hlsl

@@ -23,8 +23,7 @@ RWStructuredBuffer<GpuSceneParticleEmitter2> g_gpuSceneParticleEmitters :
 
 RWStructuredBuffer<ParticleSimulationScratch> g_scratch : register(ANKI_CONCATENATE(u, ANKI_PARTICLE_SIM_SCRATCH));
 
-// It's an array of offsets to GpuSceneRenderableBoundingVolume in the GPU scene
-StructuredBuffer<U32> g_gpuSceneBoundingVolumeList : register(ANKI_CONCATENATE(t, ANKI_PARTICLE_SIM_GPU_SCENE_BOUNDING_VOLUME_LIST));
+RWStructuredBuffer<ParticleSimulationCpuFeedback> g_cpuFeedback : register(ANKI_CONCATENATE(u, ANKI_PARTICLE_SIM_CPU_FEEDBACK));
 
 static U32 g_particleIdx;
 static U32 g_randomNumber;

+ 6 - 19
AnKi/Shaders/GpuParticlesGass.ankiprog

@@ -199,26 +199,13 @@ void initializeParticle(AnKiParticleEmitterProperties props, GpuSceneParticleEmi
 
 		if(lastThreadExecuting)
 		{
-			// Update the bounding volume of the renderable
+			// Inform about the bounding volume
 			const F32 toMeters = 1.0 / 100.0;
-			const Vec3 aabbMin = g_scratch[0].m_aabbMin * toMeters;
-			const Vec3 aabbMax = g_scratch[0].m_aabbMax * toMeters;
-
-			const Vec3 center = (aabbMax + aabbMin) / 2.0;
-			const F32 radius = length(aabbMax - center);
-
-			const U32 bvolumeCount = getStructuredBufferElementCount(g_gpuSceneBoundingVolumeList);
-			for(U32 i = 0; i < bvolumeCount; ++i)
-			{
-				const U32 offset = SBUFF(g_gpuSceneBoundingVolumeList, i);
-
-				GpuSceneRenderableBoundingVolume bvol = BAB_LOAD(g_gpuScene, GpuSceneRenderableBoundingVolume, offset);
-				bvol.m_aabbMin = aabbMin;
-				bvol.m_aabbMax = aabbMax;
-				bvol.m_sphereRadius = radius;
-
-				BAB_STORE(g_gpuScene, GpuSceneRenderableBoundingVolume, offset, bvol);
-			}
+			ParticleSimulationCpuFeedback feedback;
+			feedback.m_aabbMin = g_scratch[0].m_aabbMin * toMeters;
+			feedback.m_aabbMax = g_scratch[0].m_aabbMax * toMeters;
+			feedback.m_uuid = emitter.m_uuid;
+			g_cpuFeedback[0] = feedback;
 
 			// Reset the scratch stract
 			g_scratch[0].m_aabbMin = kMaxI32;

+ 8 - 3
AnKi/Shaders/Include/GpuSceneTypes.h

@@ -27,7 +27,7 @@ struct GpuSceneRenderable
 	U32 m_particleEmitterIndex; // Index to the GpuSceneParticleEmitter array or kMaxU32 if it's not an emitter.
 	U32 m_rtShadowsShaderHandleIndex; // The index of the shader handle in the array of library's handles.
 	U32 m_rtMaterialFetchShaderHandleIndex; // The index of the shader handle in the array of library's handles.
-	U32 m_uuid;
+	U32 m_uuid; // A UUID specific for this renderable. Don't come from some scene object
 
 	U32 m_diffuseColor : 24; // The average diffuse color of the renderable. Blue is in low bits.
 	U32 m_padding : 8;
@@ -95,10 +95,10 @@ struct GpuSceneParticleEmitter
 };
 static_assert(sizeof(GpuSceneParticleEmitter) == sizeof(Vec4) * 2);
 
-// Contains common properties for all particle emitters
+// Contains common properties for all particle emitters. Primary use is for the simulation
 struct GpuSceneParticleEmitter2
 {
-	U32 m_particleStateSteamOffsets[(U32)ParticleProperty::kCount]; // Points to arrays of data
+	U32 m_particleStateSteamOffsets[(U32)ParticleProperty::kCount]; // Points to arrays of particle properties. In the GPU scene
 	U32 m_aliveParticleCount; // The number of the alive particles
 	U32 m_aliveParticleIndicesOffset; // Points to arrays of indices of the alive particles. Used when rendering
 	U32 m_particleCount; // The total number of particles
@@ -113,6 +113,11 @@ struct GpuSceneParticleEmitter2
 
 	Vec3 m_particleAabbMax;
 	U32 m_worldTransformsIndex;
+
+	U32 m_uuid; // This is the UUID of the ParticleEmitterComponent
+	U32 m_padding0;
+	U32 m_padding1;
+	U32 m_padding2;
 };
 static_assert(sizeof(GpuSceneParticleEmitter2) % sizeof(Vec4) == 0);
 

+ 11 - 0
AnKi/Shaders/Include/ParticleTypes.h

@@ -34,6 +34,16 @@ struct ParticleSimulationConstants
 	U32 m_padding0;
 };
 
+// Contains a few data that are filled by the GPU and fed to the CPU
+struct ParticleSimulationCpuFeedback
+{
+	Vec3 m_aabbMin;
+	U32 m_uuid; // This is the UUID of the ParticleEmitterComponent
+
+	Vec3 m_aabbMax;
+	U32 m_padding;
+};
+
 /// The various properties of a GPU particle.
 enum class ParticleProperty
 {
@@ -71,6 +81,7 @@ inline constexpr Array<U32, U32(ParticleProperty::kCount)> kParticlePropertySize
 #define ANKI_PARTICLE_SIM_SCRATCH 0
 #define ANKI_PARTICLE_SIM_GPU_SCENE 1
 #define ANKI_PARTICLE_SIM_GPU_SCENE_PARTICLE_EMITTERS 2
+#define ANKI_PARTICLE_SIM_CPU_FEEDBACK 3
 
 // CBV
 #define ANKI_PARTICLE_SIM_CONSTANTS 0