// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #pragma once #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/StringView.h" #include "Engine/Core/Collections/Array.h" #include "Types.h" // The maximum amount of particle attributes to use #define PARTICLE_ATTRIBUTES_MAX_COUNT 32 // The maximum amount of particle ribbon modules used per context #define PARTICLE_EMITTER_MAX_RIBBONS 4 class ParticleEmitter; class Particles; class GPUBuffer; class DynamicIndexBuffer; class DynamicVertexBuffer; /// /// The particle attribute that defines a single particle layout component. /// struct ParticleAttribute { enum class ValueTypes { Float, Float2, Float3, Float4, Int, Uint, }; /// /// The attribute value container type. /// ValueTypes ValueType; /// /// The attribute offset from particle data start (in bytes). /// int32 Offset; /// /// The attribute name. /// String Name; /// /// Gets the size of the attribute (in bytes). /// /// The size (in bytes). int32 GetSize() const { switch (ValueType) { case ValueTypes::Float2: return 8; case ValueTypes::Float3: return 12; case ValueTypes::Float4: return 16; case ValueTypes::Float: case ValueTypes::Int: case ValueTypes::Uint: return 4; default: return 0; } } }; /// /// The particle attributes layout descriptor. /// class ParticleLayout { public: /// /// The total particle data stride size (in bytes). Defines the required memory amount for a single particle. /// int32 Size; /// /// The attributes set. /// Array> Attributes; public: /// /// Clears the layout data. /// void Clear() { Size = 0; Attributes.Clear(); } /// /// Updates the attributes layout (calculates offset) and updates the total size of the layout. /// void UpdateLayout() { Size = 0; for (int32 i = 0; i < Attributes.Count(); i++) { Attributes[i].Offset = Size; Size += Attributes[i].GetSize(); } } /// /// Finds the attribute by the name. /// /// The name. /// The attribute index or -1 if cannot find it. int32 FindAttribute(const StringView& name) const { for (int32 i = 0; i < Attributes.Count(); i++) { if (name == Attributes[i].Name) { return i; } } return -1; } /// /// Finds the attribute by the name and type. /// /// The name. /// The type. /// The attribute index or -1 if cannot find it. int32 FindAttribute(const StringView& name, ParticleAttribute::ValueTypes valueType) const { for (int32 i = 0; i < Attributes.Count(); i++) { if (Attributes[i].ValueType == valueType && name == Attributes[i].Name) { return i; } } return -1; } /// /// Finds the attribute offset by the name. /// /// The name. /// The fallback value to return if attribute is missing. /// The attribute offset or fallback value if cannot find it. int32 FindAttributeOffset(const StringView& name, int32 fallbackValue = 0) const { for (int32 i = 0; i < Attributes.Count(); i++) { if (name == Attributes[i].Name) { return Attributes[i].Offset; } } return fallbackValue; } /// /// Finds the attribute offset by the name. /// /// The name. /// The type. /// The fallback value to return if attribute is missing. /// The attribute offset or fallback value if cannot find it. int32 FindAttributeOffset(const StringView& name, ParticleAttribute::ValueTypes valueType, int32 fallbackValue = 0) const { for (int32 i = 0; i < Attributes.Count(); i++) { if (Attributes[i].ValueType == valueType && name == Attributes[i].Name) { return Attributes[i].Offset; } } return fallbackValue; } /// /// Gets the attribute offset by the attribute index. /// /// The attribute index. /// The fallback value to return if attribute is missing. /// The attribute offset or fallback value if cannot find it. FORCE_INLINE int32 GetAttributeOffset(int32 index, int32 fallbackValue = -1) const { return index != -1 ? Attributes[index].Offset : fallbackValue; } /// /// Adds the attribute with the given name and value type. /// /// The name. /// The value type. /// The attribute index or -1 if cannot find it. int32 AddAttribute(const StringView& name, ParticleAttribute::ValueTypes valueType) { auto& a = Attributes.AddOne(); a.Name = String(*name, name.Length()); a.ValueType = valueType; return Attributes.Count() - 1; } }; /// /// The particles simulation data container. Can allocated and used many times. It's designed to hold CPU or GPU particles data for the simulation and rendering. /// class FLAXENGINE_API ParticleBuffer { public: /// /// The emitter graph version (cached on Init). Used to discard pooled buffers that has been created for older emitter graph. /// uint32 Version; /// /// The buffer capacity (maximum amount of particles it can hold). /// int32 Capacity; /// /// The stride (size of the particle structure data). /// int32 Stride; /// /// The simulation mode. /// ParticlesSimulationMode Mode; /// /// The emitter. /// ParticleEmitter* Emitter = nullptr; /// /// The layout descriptor. /// ParticleLayout* Layout = nullptr; struct { /// /// The active particles count. /// int32 Count; /// /// The particles data buffer (CPU side). /// Array Buffer; /// /// The sorted ribbon particles indices (CPU side). Cached after system update and reused during rendering (batched for all ribbon modules). /// Array RibbonOrder; } CPU; struct { /// /// The particles data buffer (GPU side). /// /// /// Contains particles data for GPU simulation and rendering. If simulation mode is GPU then this buffer is dynamic and updated before rendering, otherwise it's in default allocation mode. /// GPUBuffer* Buffer = nullptr; /// /// The particles secondary data buffer (GPU side). Used as a write destination for GPU particles and swapped with Buffer after simulation. /// GPUBuffer* BufferSecondary = nullptr; /// /// The indirect draw command arguments buffer used by the GPU particles to invoke drawing on a GPU based on the particles amount (instances count). /// GPUBuffer* IndirectDrawArgsBuffer = nullptr; /// /// The GPU particles sorting buffer. Contains structure of particle index and the sorting key for every particle. Used to sort particles. /// GPUBuffer* SortingKeysBuffer = nullptr; /// /// The particles indices buffer (GPU side). /// /// /// Contains sorted particles indices for GPU rendering. Each sorting module from the emitter uses a dedicated range of this buffer. /// GPUBuffer* SortedIndices = nullptr; /// /// The ribbon particles rendering index buffer (dynamic GPU access). /// DynamicIndexBuffer* RibbonIndexBufferDynamic = nullptr; /// /// The ribbon particles rendering vertex buffer (dynamic GPU access). /// DynamicVertexBuffer* RibbonVertexBufferDynamic = nullptr; /// /// The flag used to indicate that GPU buffers data should be cleared before next simulation. /// bool PendingClear; /// /// The flag used to indicate that GPU buffers data contains a valid particles count. /// bool HasValidCount; /// /// The particle counter (uint type) offset. Located in Buffer and BufferSecondary if GPU particles simulation is used to store the particles count in the buffer. Placed after the particle attributes. /// uint32 ParticleCounterOffset; /// /// The maximum amount of particles that 'might' be in the buffer. During every simulation update we spawn a certain amount of particles and update existing ones. We can estimate limit for the current particles count to dispatch less threads for particles update. /// int32 ParticlesCountMax; } GPU; public: /// /// Initializes a new instance of the class. /// ParticleBuffer(); /// /// Finalizes an instance of the class. /// ~ParticleBuffer(); /// /// Initializes the particle buffer for the specified emitter. /// /// The emitter. /// True if failed, otherwise false. bool Init(ParticleEmitter* emitter); /// /// Allocates the particles sorting indices buffer. /// /// True if failed, otherwise false. bool AllocateSortBuffer(); /// /// Clears the particles from the buffer (prepares for the simulation). /// void Clear(); /// /// Gets the pointer to the particle data. /// /// Index of the particle. /// The particle data start address. FORCE_INLINE byte* GetParticleCPU(int32 particleIndex) { return CPU.Buffer.Get() + particleIndex * Stride; } /// /// Gets the pointer to the particle data. /// /// Index of the particle. /// The particle data start address. FORCE_INLINE const byte* GetParticleCPU(int32 particleIndex) const { return CPU.Buffer.Get() + particleIndex * Stride; } }; struct ParticleBufferCPUDataAccessorBase { protected: ParticleBuffer* _buffer; int32 _offset; public: ParticleBufferCPUDataAccessorBase() : _buffer(nullptr) , _offset(-1) { } ParticleBufferCPUDataAccessorBase(ParticleBuffer* buffer, int32 offset) : _buffer(buffer) , _offset(offset) { } public: FORCE_INLINE bool IsValid() const { return _buffer != nullptr && _offset != -1; } }; template struct ParticleBufferCPUDataAccessor : ParticleBufferCPUDataAccessorBase { public: ParticleBufferCPUDataAccessor() { } ParticleBufferCPUDataAccessor(ParticleBuffer* buffer, int32 offset) : ParticleBufferCPUDataAccessorBase(buffer, offset) { } public: FORCE_INLINE T operator[](int32 index) const { return Get(index); } FORCE_INLINE T Get(int32 index) const { ASSERT(IsValid() && index >= 0 && index < _buffer->CPU.Count); return *(T*)(_buffer->CPU.Buffer.Get() + _offset + index * _buffer->Stride); } FORCE_INLINE T Get(int32 index, const T& defaultValue) const { if (IsValid()) return Get(index); return defaultValue; } };