// Copyright (c) 2012-2024 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;
}
};