Browse Source

GPU particles: Add the skeleton of the new scene component

Panagiotis Christopoulos Charitos 1 month ago
parent
commit
c9c73d5f36

+ 5 - 0
AnKi/GpuMemory/GpuSceneBuffer.h

@@ -45,6 +45,11 @@ public:
 		return *this;
 	}
 
+	explicit operator Bool() const
+	{
+		return isValid();
+	}
+
 	Bool isValid() const
 	{
 		return m_token.m_offset != kMaxPtrSize;

+ 25 - 0
AnKi/Resource/ParticleEmitterResource2.cpp

@@ -39,6 +39,31 @@ Error ParticleEmitterResource2::load(const ResourceFilename& filename, Bool asyn
 	ANKI_CHECK(rootEl.getChildElement("shaderProgram", shaderProgramEl));
 	ANKI_CHECK(parseShaderProgram(shaderProgramEl, async));
 
+	// <particleCount>
+	XmlElement particleCountEl;
+	ANKI_CHECK(rootEl.getChildElement("particleCount", particleCountEl));
+	ANKI_CHECK(particleCountEl.getAttributeNumber("value", m_commonProps.m_particleCount));
+	if(m_commonProps.m_particleCount == 0 || m_commonProps.m_particleCount > kMaxU16) // kMaxU16 is arbitary
+	{
+		ANKI_RESOURCE_LOGE("Can't accept particleCount of: %u", m_commonProps.m_particleCount);
+		return Error::kUserData;
+	}
+
+	// <emissionPeriod>
+	XmlElement emissionPeriodEl;
+	ANKI_CHECK(rootEl.getChildElement("emissionPeriod", emissionPeriodEl));
+	ANKI_CHECK(emissionPeriodEl.getAttributeNumber("value", m_commonProps.m_emissionPeriod));
+
+	// <particlesPerEmission>
+	XmlElement particlesPerEmissionEl;
+	ANKI_CHECK(rootEl.getChildElement("particlesPerEmission", particlesPerEmissionEl));
+	ANKI_CHECK(particlesPerEmissionEl.getAttributeNumber("value", m_commonProps.m_particlesPerEmission));
+	if(m_commonProps.m_particlesPerEmission == 0)
+	{
+		ANKI_RESOURCE_LOGE("Can't accept particlesPerEmission of: %u", m_commonProps.m_particlesPerEmission);
+		return Error::kUserData;
+	}
+
 	// Inputs
 	ANKI_CHECK(rootEl.getChildElementOptional("inputs", el));
 	if(el)

+ 45 - 26
AnKi/Resource/ParticleEmitterResource2.h

@@ -13,18 +13,14 @@ namespace anki {
 class XmlElement;
 class ShaderProgramResourceVariantInitInfo;
 
-/// @addtogroup resource
-/// @{
-
-/// @memberof ParticleEmitterResource2
-class ParticleEmitterProperty
+class ParticleEmitterResourceProperty
 {
 public:
-	ParticleEmitterProperty() = default;
+	ParticleEmitterResourceProperty() = default;
 
-	ParticleEmitterProperty(const ParticleEmitterProperty&) = delete; // Non-copyable
+	ParticleEmitterResourceProperty(const ParticleEmitterResourceProperty&) = delete; // Non-copyable
 
-	ParticleEmitterProperty& operator=(const ParticleEmitterProperty&) = delete; // Non-copyable
+	ParticleEmitterResourceProperty& operator=(const ParticleEmitterResourceProperty&) = delete; // Non-copyable
 
 	CString getName() const
 	{
@@ -46,10 +42,10 @@ private:
 	};
 };
 
-// Specialize the ParticleEmitterProperty::getValue
+// Specialize the ParticleEmitterResourceProperty::getValue
 #define ANKI_SPECIALIZE_GET_VALUE(type, member) \
 	template<> \
-	inline const type& ParticleEmitterProperty::getValue<type>() const \
+	inline const type& ParticleEmitterResourceProperty::getValue<type>() const \
 	{ \
 		ANKI_ASSERT(m_dataType == ShaderVariableDataType::k##type); \
 		return member; \
@@ -58,21 +54,33 @@ private:
 #include <AnKi/Gr/ShaderVariableDataType.def.h>
 #undef ANKI_SPECIALIZE_GET_VALUE
 
-/// This is the a particle emitter resource containing shader and properties.
-/// XML format:
-/// @code
-///	<particleEmitter>
-/// 	<shaderProgram name="name of the shader" />
-///			[<mutation>
-///				<mutator name="str" value="value"/>
-///			</mutation>]
-///		</shaderProgram>
-///
-///		[<inputs>
-///			<input name="name in AnKiParticleEmitterProperties struct" value="value(s)"/>
-///		</inputs>]
-///	</particleEmitter>
-/// @endcode
+// Common properties for each emitter. The rest of the properties are up to the user
+class ParticleEmitterResourceCommonProperties
+{
+public:
+	U32 m_particleCount = 0;
+	F32 m_emissionPeriod = 0.0;
+	U32 m_particlesPerEmission = 0;
+};
+
+// This is the a particle emitter resource containing shader and properties.
+// XML format:
+//	<particleEmitter>
+//		<shaderProgram name="name of the shader" />
+//			[<mutation>
+//				<mutator name="str" value="value"/>
+//			</mutation>]
+//		</shaderProgram>
+//
+//		<!-- Common properties -->
+//		<particleCount value="value" />
+//		<emissionPeriod value="value" />
+//		<particlesPerEmission value="value" />
+//
+//		[<inputs>
+//			<input name="name in AnKiParticleEmitterProperties struct" value="value(s)"/>
+//		</inputs>]
+// </particleEmitter>
 class ParticleEmitterResource2 : public ResourceObject
 {
 public:
@@ -86,18 +94,29 @@ public:
 	/// Load it
 	Error load(const ResourceFilename& filename, Bool async);
 
+	const ParticleEmitterResourceCommonProperties& getCommonProperties() const
+	{
+		return m_commonProps;
+	}
+
+	ConstWeakArray<U8> getPrefilledAnKiParticleEmitterProperties() const
+	{
+		return m_prefilledAnKiParticleEmitterProperties;
+	}
+
 private:
 	ResourceDynamicArray<U8> m_prefilledAnKiParticleEmitterProperties;
 
 	ShaderProgramResourcePtr m_prog;
 	ShaderProgramPtr m_grProg;
 
+	ParticleEmitterResourceCommonProperties m_commonProps;
+
 	Error parseShaderProgram(XmlElement shaderProgramEl, Bool async);
 
 	Error parseMutators(XmlElement mutatorsEl, ShaderProgramResourceVariantInitInfo& variantInitInfo);
 
 	Error parseInput(XmlElement inputEl);
 };
-/// @}
 
 } // end namespace anki

+ 234 - 0
AnKi/Scene/Components/ParticleEmitter2Component.cpp

@@ -0,0 +1,234 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <AnKi/Scene/Components/ParticleEmitter2Component.h>
+#include <AnKi/Scene/Components/MeshComponent.h>
+#include <AnKi/Resource/ParticleEmitterResource2.h>
+#include <AnKi/Resource/ResourceManager.h>
+#include <AnKi/GpuMemory/RebarTransientMemoryPool.h>
+
+namespace anki {
+
+// Contains some shared geometry data and it's a singleton because we don't want to be wasting time and memory for every emitter
+class ParticleEmitter2Component::ParticleEmitterQuadGeometry : public MakeSingleton<ParticleEmitterQuadGeometry>
+{
+public:
+	UnifiedGeometryBufferAllocation m_quadPositions;
+	UnifiedGeometryBufferAllocation m_quadUvs;
+	UnifiedGeometryBufferAllocation m_quadIndices;
+
+	U32 m_userCount = 0;
+	SpinLock m_mtx;
+
+	void init()
+	{
+		// Allocate and populate a quad
+		const U32 vertCount = 4;
+		const U32 indexCount = 6;
+
+		m_quadPositions = UnifiedGeometryBuffer::getSingleton().allocateFormat(kMeshRelatedVertexStreamFormats[VertexStreamId::kPosition], vertCount);
+		m_quadUvs = UnifiedGeometryBuffer::getSingleton().allocateFormat(kMeshRelatedVertexStreamFormats[VertexStreamId::kUv], vertCount);
+		m_quadIndices = UnifiedGeometryBuffer::getSingleton().allocateFormat(Format::kR16_Uint, indexCount);
+
+		static_assert(kMeshRelatedVertexStreamFormats[VertexStreamId::kPosition] == Format::kR16G16B16A16_Unorm);
+		WeakArray<U16Vec4> transientPositions;
+		const BufferView positionsAlloc = RebarTransientMemoryPool::getSingleton().allocateCopyBuffer(vertCount, transientPositions);
+		transientPositions[0] = U16Vec4(0, 0, 0, 0);
+		transientPositions[1] = U16Vec4(kMaxU16, 0, 0, 0);
+		transientPositions[2] = U16Vec4(kMaxU16, kMaxU16, 0, 0);
+		transientPositions[3] = U16Vec4(0, kMaxU16, 0, 0);
+
+		static_assert(kMeshRelatedVertexStreamFormats[VertexStreamId::kUv] == Format::kR32G32_Sfloat);
+		WeakArray<Vec2> transientUvs;
+		const BufferView uvsAlloc = RebarTransientMemoryPool::getSingleton().allocateCopyBuffer(vertCount, transientUvs);
+		transientUvs[0] = Vec2(0.0f);
+		transientUvs[1] = Vec2(1.0f, 0.0f);
+		transientUvs[2] = Vec2(1.0f, 1.0f);
+		transientUvs[3] = Vec2(0.0f, 1.0f);
+
+		WeakArray<U16> transientIndices;
+		const BufferView indicesAlloc = RebarTransientMemoryPool::getSingleton().allocateCopyBuffer(indexCount, transientIndices);
+		transientIndices[0] = 0;
+		transientIndices[1] = 1;
+		transientIndices[2] = 3;
+		transientIndices[3] = 1;
+		transientIndices[4] = 2;
+		transientIndices[5] = 3;
+
+		CommandBufferInitInfo cmdbInit("Particle quad upload");
+		cmdbInit.m_flags |= CommandBufferFlag::kSmallBatch;
+		CommandBufferPtr cmdb = GrManager::getSingleton().newCommandBuffer(cmdbInit);
+		Buffer* dstBuff = &UnifiedGeometryBuffer::getSingleton().getBuffer();
+		cmdb->copyBufferToBuffer(positionsAlloc, m_quadPositions);
+		cmdb->copyBufferToBuffer(uvsAlloc, m_quadUvs);
+		cmdb->copyBufferToBuffer(indicesAlloc, m_quadIndices);
+		BufferBarrierInfo barrier;
+		barrier.m_bufferView = BufferView(dstBuff);
+		barrier.m_previousUsage = BufferUsageBit::kCopyDestination;
+		barrier.m_nextUsage = dstBuff->getBufferUsage();
+		cmdb->setPipelineBarrier({}, {&barrier, 1}, {});
+		cmdb->endRecording();
+
+		GrManager::getSingleton().submit(cmdb.get());
+	}
+
+	void deinit()
+	{
+		UnifiedGeometryBuffer::getSingleton().deferredFree(m_quadPositions);
+		UnifiedGeometryBuffer::getSingleton().deferredFree(m_quadUvs);
+		UnifiedGeometryBuffer::getSingleton().deferredFree(m_quadIndices);
+	}
+
+	void addUser()
+	{
+		LockGuard lock(m_mtx);
+
+		if(m_userCount == 0)
+		{
+			init();
+		}
+
+		++m_userCount;
+	}
+
+	void removeUser()
+	{
+		LockGuard lock(m_mtx);
+
+		ANKI_ASSERT(m_userCount > 0);
+		if(m_userCount == 1)
+		{
+			deinit();
+		}
+
+		--m_userCount;
+	}
+};
+
+ParticleEmitter2Component::ParticleEmitter2Component(SceneNode* node)
+	: SceneComponent(node, kClassType)
+{
+	ParticleEmitterQuadGeometry::getSingleton().addUser();
+}
+
+ParticleEmitter2Component::~ParticleEmitter2Component()
+{
+	ParticleEmitterQuadGeometry::getSingleton().removeUser();
+}
+
+ParticleEmitter2Component& ParticleEmitter2Component::setParticleEmitterFilename(CString fname)
+{
+	ParticleEmitterResource2Ptr newRsrc;
+	const Error err = ResourceManager::getSingleton().loadResource(fname, newRsrc);
+	if(err)
+	{
+		ANKI_SCENE_LOGE("Failed to load resource: %s", fname.cstr());
+	}
+	else
+	{
+		m_particleEmitterResource = std::move(newRsrc);
+		m_resourceDirty = true;
+	}
+
+	return *this;
+}
+
+CString ParticleEmitter2Component::getParticleEmitterFilename() const
+{
+	if(m_particleEmitterResource)
+	{
+		return m_particleEmitterResource->getFilename();
+	}
+	else
+	{
+		return "*Error*";
+	}
+}
+
+void ParticleEmitter2Component::onOtherComponentRemovedOrAdded(SceneComponent* other, Bool added)
+{
+	ANKI_ASSERT(other);
+
+	if(other->getType() == SceneComponentType::kMesh)
+	{
+		bookkeepComponent(m_meshComponents, other, added, m_meshComponentDirty);
+	}
+}
+
+void ParticleEmitter2Component::update(SceneComponentUpdateInfo& info, Bool& updated)
+{
+	if(!isValid())
+	{
+		return;
+	}
+
+	if(!m_resourceDirty && !m_geomTypeDirty && !m_meshComponentDirty) [[likely]]
+	{
+		return;
+	}
+
+	// From now on it's dirty and needs some kind of update
+
+	updated = true;
+
+	// Streams
+	if(m_resourceDirty)
+	{
+		for(ParticleProperty prop : EnumIterable<ParticleProperty>())
+		{
+			GpuSceneBuffer::getSingleton().deferredFree(m_gpuScene.m_particleStreams[prop]);
+			m_gpuScene.m_particleStreams[prop] = GpuSceneBuffer::getSingleton().allocate(kParticlePropertySize[prop], alignof(U32));
+		}
+	}
+
+	// Alive particles index buffer
+	if(m_resourceDirty)
+	{
+		const ParticleEmitterResourceCommonProperties& commonProps = m_particleEmitterResource->getCommonProperties();
+		GpuSceneBuffer::getSingleton().deferredFree(m_gpuScene.m_aliveParticleIndices);
+		m_gpuScene.m_aliveParticleIndices = GpuSceneBuffer::getSingleton().allocate(commonProps.m_particleCount * sizeof(U32), alignof(U32));
+	}
+
+	// AnKiParticleEmitterProperties
+	if(m_resourceDirty)
+	{
+		GpuSceneBuffer::getSingleton().deferredFree(m_gpuScene.m_anKiParticleEmitterProperties);
+
+		ConstWeakArray<U8> prefilled = m_particleEmitterResource->getPrefilledAnKiParticleEmitterProperties();
+		m_gpuScene.m_anKiParticleEmitterProperties = GpuSceneBuffer::getSingleton().allocate(prefilled.getSizeInBytes(), alignof(U32));
+
+		GpuSceneMicroPatcher::getSingleton().newCopy(*info.m_framePool, m_gpuScene.m_anKiParticleEmitterProperties, prefilled);
+	}
+
+	// GpuSceneParticleEmitter2
+	const Bool updateGpuSceneEmitter = m_resourceDirty || m_meshComponentDirty || m_geomTypeDirty;
+	if(updateGpuSceneEmitter)
+	{
+		if(!m_gpuScene.m_gpuSceneParticleEmitter)
+		{
+			m_gpuScene.m_gpuSceneParticleEmitter.allocate();
+		}
+
+		const ParticleEmitterResourceCommonProperties& commonProps = m_particleEmitterResource->getCommonProperties();
+
+		GpuSceneParticleEmitter2 emitter;
+		zeroMemory(emitter);
+
+		for(ParticleProperty prop : EnumIterable<ParticleProperty>())
+		{
+			emitter.m_particleStateSteamOffsets[U32(prop)] = m_gpuScene.m_particleStreams[prop].getOffset();
+		}
+		emitter.m_aliveParticleIndicesOffset = m_gpuScene.m_aliveParticleIndices.getOffset();
+		emitter.m_particleCount = commonProps.m_particleCount;
+
+		emitter.m_emissionPeriod = commonProps.m_emissionPeriod;
+		emitter.m_particlesPerEmission = commonProps.m_particlesPerEmission;
+		emitter.m_particleEmitterPropertiesOffset = m_gpuScene.m_anKiParticleEmitterProperties.getOffset();
+
+		// TODO
+	}
+}
+
+} // end namespace anki

+ 88 - 0
AnKi/Scene/Components/ParticleEmitter2Component.h

@@ -0,0 +1,88 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <AnKi/Scene/Components/SceneComponent.h>
+#include <AnKi/Scene/RenderStateBucket.h>
+#include <AnKi/Scene/GpuSceneArray.h>
+#include <AnKi/Resource/ParticleEmitterResource.h>
+#include <AnKi/GpuMemory/UnifiedGeometryBuffer.h>
+#include <AnKi/Collision/Aabb.h>
+#include <AnKi/Util/WeakArray.h>
+
+namespace anki {
+
+enum class ParticleGeometryType : U8
+{
+	kQuad,
+	kMeshComponent,
+	kCount
+};
+
+// Contains a particle emitter resource and maybe connects to a mesh component
+class ParticleEmitter2Component : public SceneComponent
+{
+	ANKI_SCENE_COMPONENT(ParticleEmitter2Component)
+
+public:
+	ParticleEmitter2Component(SceneNode* node);
+
+	~ParticleEmitter2Component();
+
+	ParticleEmitter2Component& setParticleEmitterFilename(CString filename);
+
+	CString getParticleEmitterFilename() const;
+
+	ParticleEmitter2Component& setParticleGeometryType(ParticleGeometryType type)
+	{
+		if(type != m_geomType && ANKI_EXPECT(type < ParticleGeometryType::kCount))
+		{
+			m_geomType = type;
+			m_geomTypeDirty = true;
+		}
+
+		return *this;
+	}
+
+	ParticleGeometryType getParticleGeometryType() const
+	{
+		return m_geomType;
+	}
+
+	Bool isValid() const
+	{
+		Bool invalid = !m_particleEmitterResource;
+		invalid = invalid || (m_geomType == ParticleGeometryType::kMeshComponent && m_meshComponents.getSize() == 0);
+		return !invalid;
+	}
+
+private:
+	class ParticleEmitterQuadGeometry;
+
+	ParticleEmitterResource2Ptr m_particleEmitterResource;
+
+	SceneDynamicArray<MeshComponent*> m_meshComponents;
+
+	class
+	{
+	public:
+		GpuSceneArrays::ParticleEmitter2::Allocation m_gpuSceneParticleEmitter;
+		Array<GpuSceneBufferAllocation, U32(ParticleProperty::kCount)> m_particleStreams;
+		GpuSceneBufferAllocation m_aliveParticleIndices;
+		GpuSceneBufferAllocation m_anKiParticleEmitterProperties;
+	} m_gpuScene;
+
+	ParticleGeometryType m_geomType = ParticleGeometryType::kQuad;
+	Bool m_resourceDirty = true;
+	Bool m_meshComponentDirty = true;
+	Bool m_geomTypeDirty = true;
+
+	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
+
+	void onOtherComponentRemovedOrAdded(SceneComponent* other, Bool added) override;
+};
+
+} // end namespace anki

+ 32 - 0
AnKi/Scene/Components/SceneComponent.h

@@ -158,6 +158,38 @@ public:
 protected:
 	U32 regenerateUuid();
 
+	// A convenience function for components to keep tabs on other components of a SceneNode
+	template<typename TComponent>
+	static void bookkeepComponent(SceneDynamicArray<TComponent*>& arr, SceneComponent* other, Bool added, Bool& firstDirty)
+	{
+		ANKI_ASSERT(other);
+		if(added)
+		{
+			for(auto it = arr.getBegin(); it != arr.getEnd(); ++it)
+			{
+				ANKI_ASSERT(*it != other);
+			}
+
+			arr.emplaceBack(static_cast<TComponent*>(other));
+			firstDirty = arr.getSize() == 1;
+		}
+		else
+		{
+			Bool found = false;
+			for(auto it = arr.getBegin(); it != arr.getEnd(); ++it)
+			{
+				if(*it == other)
+				{
+					firstDirty = it == arr.getBegin();
+					arr.erase(it);
+					found = true;
+					break;
+				}
+			}
+			ANKI_ASSERT(found);
+		}
+	}
+
 private:
 	Timestamp m_timestamp = 1; ///< Indicates when an update happened
 	U32 m_uuid = 0;

+ 3 - 0
AnKi/Scene/Components/SceneComponentClasses.def.h

@@ -29,6 +29,9 @@ ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_DEFINE_SCENE_COMPONENT(Mesh, 50.0f, VECTOR_POLYGON)
 ANKI_SCENE_COMPONENT_SEPARATOR
 
+ANKI_DEFINE_SCENE_COMPONENT(ParticleEmitter2, 60.0f, FOUNTAIN)
+ANKI_SCENE_COMPONENT_SEPARATOR
+
 ANKI_DEFINE_SCENE_COMPONENT(Material, 100.0f, TEXTURE_BOX)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_DEFINE_SCENE_COMPONENT(ParticleEmitter, 100.0f, FOUNTAIN)

+ 5 - 0
AnKi/Scene/GpuSceneArray.h

@@ -50,6 +50,11 @@ public:
 		return *this;
 	}
 
+	explicit operator Bool() const
+	{
+		return isValid();
+	}
+
 	U32 getIndex() const
 	{
 		ANKI_ASSERT(isValid());

+ 2 - 0
AnKi/Scene/GpuSceneArrays.def.h

@@ -19,6 +19,8 @@ ANKI_CAT_TYPE(MeshLod, ANKI_MESH_ARR, 0, g_cvarSceneMinGpuSceneMeshes)
 ANKI_CAT_SEPARATOR
 ANKI_CAT_TYPE(ParticleEmitter, GpuSceneParticleEmitter, 0, g_cvarSceneMinGpuSceneParticleEmitters)
 ANKI_CAT_SEPARATOR
+ANKI_CAT_TYPE(ParticleEmitter2, GpuSceneParticleEmitter2, 0, g_cvarSceneMinGpuSceneParticleEmitters)
+ANKI_CAT_SEPARATOR
 ANKI_CAT_TYPE(LightVisibleRenderablesHash, GpuSceneLightVisibleRenderablesHash, 0, g_cvarSceneMinGpuSceneLights)
 ANKI_CAT_SEPARATOR
 ANKI_CAT_TYPE(Light, GpuSceneLight, 0, g_cvarSceneMinGpuSceneLights)

+ 1 - 0
AnKi/Scene/SceneGraph.cpp

@@ -25,6 +25,7 @@
 #include <AnKi/Scene/Components/LightComponent.h>
 #include <AnKi/Scene/Components/MoveComponent.h>
 #include <AnKi/Scene/Components/ParticleEmitterComponent.h>
+#include <AnKi/Scene/Components/ParticleEmitter2Component.h>
 #include <AnKi/Scene/Components/PlayerControllerComponent.h>
 #include <AnKi/Scene/Components/ReflectionProbeComponent.h>
 #include <AnKi/Scene/Components/ScriptComponent.h>

+ 1 - 0
AnKi/Scene/SceneNode.cpp

@@ -18,6 +18,7 @@
 #include <AnKi/Scene/Components/LightComponent.h>
 #include <AnKi/Scene/Components/MoveComponent.h>
 #include <AnKi/Scene/Components/ParticleEmitterComponent.h>
+#include <AnKi/Scene/Components/ParticleEmitter2Component.h>
 #include <AnKi/Scene/Components/PlayerControllerComponent.h>
 #include <AnKi/Scene/Components/ReflectionProbeComponent.h>
 #include <AnKi/Scene/Components/ScriptComponent.h>

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

@@ -107,14 +107,13 @@ struct GpuSceneParticleEmitter2
 	U32 m_particleEmitterPropertiesOffset; // Points to a AnKiParticleEmitterProperties struct that is located in the GPU scene
 
 	Vec3 m_particleAabbMin;
-	U32 m_renderableIndex;
+	U32 m_reinitializeOnNextUpdate ANKI_CPP_CODE(= 1); // Re-init all particles on next update
 
 	Vec3 m_particleAabbMax;
 	U32 m_worldTransformsIndex;
 
 	U32 m_boundingVolumeOffset; // Points to its GpuSceneRenderableBoundingVolume. It's an offset because there are many arrays with bvolumes.
-	U32 m_reinitializeOnNextUpdate ANKI_CPP_CODE(= 1); // Re-init all particles on next update
-	U32 m_padding[2];
+	U32 m_padding[3];
 };
 static_assert(sizeof(GpuSceneParticleEmitter2) % sizeof(Vec4) == 0);
 

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

@@ -55,6 +55,12 @@ enum class ParticleProperty
 	kFirst = 0
 };
 
+#if defined(__cplusplus)
+inline constexpr Array<U32, U32(ParticleProperty::kCount)> kParticlePropertySize = {
+	sizeof(Vec3), sizeof(F32),  sizeof(F32), sizeof(Vec3), sizeof(Vec3), sizeof(Vec4), sizeof(Vec3),
+	sizeof(Vec4), sizeof(Vec3), sizeof(F32), sizeof(Vec4), sizeof(Vec4), sizeof(Vec4)};
+#endif
+
 // SRV
 #define ANKI_PARTICLE_SIM_DEPTH_BUFFER 0
 #define ANKI_PARTICLE_SIM_NORMAL_BUFFER 1

+ 14 - 2
AnKi/Util/Singleton.h

@@ -6,7 +6,7 @@
 #pragma once
 
 #include <AnKi/Util/StdTypes.h>
-#include <AnKi/Util/Assert.h>
+#include <AnKi/Util/Thread.h>
 
 namespace anki {
 
@@ -128,21 +128,33 @@ template<typename T>
 T MakeSingletonSimple<T>::m_global;
 
 /// If class inherits that it will become a singleton. This is a simple version with implicit init.
-template<typename T>
+template<typename T, Bool kThreadsafe = true>
 class MakeSingletonLazyInit
 {
 public:
 	ANKI_FORCE_INLINE static T& getSingleton()
 	{
+		if constexpr(kThreadsafe)
+		{
+			m_mtx.lock();
+		}
+
 		if(m_global == nullptr) [[unlikely]]
 		{
 			m_global = new T;
 		}
+
+		if constexpr(kThreadsafe)
+		{
+			m_mtx.unlock();
+		}
+
 		return *m_global;
 	}
 
 private:
 	static inline T* m_global = nullptr;
+	static inline SpinLock m_mtx;
 };
 /// @}