Files
FlaxEngine/Source/Engine/Particles/ParticlesData.h
2022-01-14 13:31:12 +01:00

450 lines
13 KiB
C++

// Copyright (c) 2012-2022 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;
/// <summary>
/// The particle attribute that defines a single particle layout component.
/// </summary>
struct ParticleAttribute
{
enum class ValueTypes
{
Float,
Vector2,
Vector3,
Vector4,
Int,
Uint,
};
/// <summary>
/// The attribute value container type.
/// </summary>
ValueTypes ValueType;
/// <summary>
/// The attribute offset from particle data start (in bytes).
/// </summary>
int32 Offset;
/// <summary>
/// The attribute name.
/// </summary>
String Name;
/// <summary>
/// Gets the size of the attribute (in bytes).
/// </summary>
/// <returns>The size (in bytes).</returns>
int32 GetSize() const
{
switch (ValueType)
{
case ValueTypes::Vector2:
return 8;
case ValueTypes::Vector3:
return 12;
case ValueTypes::Vector4:
return 16;
case ValueTypes::Float:
case ValueTypes::Int:
case ValueTypes::Uint:
return 4;
default:
return 0;
}
}
};
/// <summary>
/// The particle attributes layout descriptor.
/// </summary>
class ParticleLayout
{
public:
/// <summary>
/// The total particle data stride size (in bytes). Defines the required memory amount for a single particle.
/// </summary>
int32 Size;
/// <summary>
/// The attributes set.
/// </summary>
Array<ParticleAttribute, FixedAllocation<PARTICLE_ATTRIBUTES_MAX_COUNT>> Attributes;
public:
/// <summary>
/// Clears the layout data.
/// </summary>
void Clear()
{
Size = 0;
Attributes.Clear();
}
/// <summary>
/// Updates the attributes layout (calculates offset) and updates the total size of the layout.
/// </summary>
void UpdateLayout()
{
Size = 0;
for (int32 i = 0; i < Attributes.Count(); i++)
{
Attributes[i].Offset = Size;
Size += Attributes[i].GetSize();
}
}
/// <summary>
/// Finds the attribute by the name.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The attribute index or -1 if cannot find it.</returns>
int32 FindAttribute(const StringView& name) const
{
for (int32 i = 0; i < Attributes.Count(); i++)
{
if (name == Attributes[i].Name)
{
return i;
}
}
return -1;
}
/// <summary>
/// Finds the attribute by the name and type.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="valueType">The type.</param>
/// <returns>The attribute index or -1 if cannot find it.</returns>
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;
}
/// <summary>
/// Finds the attribute offset by the name.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="fallbackValue">The fallback value to return if attribute is missing.</param>
/// <returns>The attribute offset or fallback value if cannot find it.</returns>
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;
}
/// <summary>
/// Finds the attribute offset by the name.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="valueType">The type.</param>
/// <param name="fallbackValue">The fallback value to return if attribute is missing.</param>
/// <returns>The attribute offset or fallback value if cannot find it.</returns>
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;
}
/// <summary>
/// Gets the attribute offset by the attribute index.
/// </summary>
/// <param name="index">The attribute index.</param>
/// <param name="fallbackValue">The fallback value to return if attribute is missing.</param>
/// <returns>The attribute offset or fallback value if cannot find it.</returns>
FORCE_INLINE int32 GetAttributeOffset(int32 index, int32 fallbackValue = -1) const
{
return index != -1 ? Attributes[index].Offset : fallbackValue;
}
/// <summary>
/// Adds the attribute with the given name and value type.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="valueType">The value type.</param>
/// <returns>The attribute index or -1 if cannot find it.</returns>
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;
}
};
/// <summary>
/// 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.
/// </summary>
class FLAXENGINE_API ParticleBuffer
{
public:
/// <summary>
/// The emitter graph version (cached on Init). Used to discard pooled buffers that has been created for older emitter graph.
/// </summary>
uint32 Version;
/// <summary>
/// The buffer capacity (maximum amount of particles it can hold).
/// </summary>
int32 Capacity;
/// <summary>
/// The stride (size of the particle structure data).
/// </summary>
int32 Stride;
/// <summary>
/// The simulation mode.
/// </summary>
ParticlesSimulationMode Mode;
/// <summary>
/// The emitter.
/// </summary>
ParticleEmitter* Emitter = nullptr;
/// <summary>
/// The layout descriptor.
/// </summary>
ParticleLayout* Layout = nullptr;
struct
{
/// <summary>
/// The active particles count.
/// </summary>
int32 Count;
/// <summary>
/// The particles data buffer (CPU side).
/// </summary>
Array<byte> Buffer;
/// <summary>
/// The sorted ribbon particles indices (CPU side). Cached after system update and reused during rendering (batched for all ribbon modules).
/// </summary>
Array<int32> RibbonOrder;
} CPU;
struct
{
/// <summary>
/// The particles data buffer (GPU side).
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
GPUBuffer* Buffer = nullptr;
/// <summary>
/// The particles secondary data buffer (GPU side). Used as a write destination for GPU particles and swapped with Buffer after simulation.
/// </summary>
GPUBuffer* BufferSecondary = nullptr;
/// <summary>
/// The indirect draw command arguments buffer used by the GPU particles to invoke drawing on a GPU based on the particles amount (instances count).
/// </summary>
GPUBuffer* IndirectDrawArgsBuffer = nullptr;
/// <summary>
/// The GPU particles sorting buffer. Contains structure of particle index and the sorting key for every particle. Used to sort particles.
/// </summary>
GPUBuffer* SortingKeysBuffer = nullptr;
/// <summary>
/// The particles indices buffer (GPU side).
/// </summary>
/// <remarks>
/// Contains sorted particles indices for GPU rendering. Each sorting module from the emitter uses a dedicated range of this buffer.
/// </remarks>
GPUBuffer* SortedIndices = nullptr;
/// <summary>
/// The ribbon particles rendering index buffer (dynamic GPU access).
/// </summary>
DynamicIndexBuffer* RibbonIndexBufferDynamic = nullptr;
/// <summary>
/// The ribbon particles rendering segment distances buffer. Stored one per ribbon module.
/// </summary>
GPUBuffer* RibbonSegmentDistances[PARTICLE_EMITTER_MAX_RIBBONS] = {};
/// <summary>
/// The flag used to indicate that GPU buffers data should be cleared before next simulation.
/// </summary>
bool PendingClear;
/// <summary>
/// The flag used to indicate that GPU buffers data contains a valid particles count.
/// </summary>
bool HasValidCount;
/// <summary>
/// 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.
/// </summary>
uint32 ParticleCounterOffset;
/// <summary>
/// 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.
/// </summary>
int32 ParticlesCountMax;
} GPU;
public:
/// <summary>
/// Initializes a new instance of the <see cref="ParticleBuffer"/> class.
/// </summary>
ParticleBuffer();
/// <summary>
/// Finalizes an instance of the <see cref="ParticleBuffer"/> class.
/// </summary>
~ParticleBuffer();
/// <summary>
/// Initializes the particle buffer for the specified emitter.
/// </summary>
/// <param name="emitter">The emitter.</param>
/// <returns>True if failed, otherwise false.</returns>
bool Init(ParticleEmitter* emitter);
/// <summary>
/// Allocates the particles sorting indices buffer.
/// </summary>
/// <returns>True if failed, otherwise false.</returns>
bool AllocateSortBuffer();
/// <summary>
/// Clears the particles from the buffer (prepares for the simulation).
/// </summary>
void Clear();
/// <summary>
/// Gets the pointer to the particle data.
/// </summary>
/// <param name="particleIndex">Index of the particle.</param>
/// <returns>The particle data start address.</returns>
FORCE_INLINE byte* GetParticleCPU(int32 particleIndex)
{
return CPU.Buffer.Get() + particleIndex * Stride;
}
/// <summary>
/// Gets the pointer to the particle data.
/// </summary>
/// <param name="particleIndex">Index of the particle.</param>
/// <returns>The particle data start address.</returns>
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<typename T>
struct ParticleBufferCPUDataAccessor : ParticleBufferCPUDataAccessorBase
{
public:
ParticleBufferCPUDataAccessor<T>()
{
}
ParticleBufferCPUDataAccessor(ParticleBuffer* buffer, int32 offset)
: ParticleBufferCPUDataAccessorBase(buffer, offset)
{
}
public:
FORCE_INLINE T operator[](int32 index) const
{
return Get(index);
}
T Get(int32 index) const
{
ASSERT(IsValid());
ASSERT(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;
}
};