| 1 | //************************************ bs::framework - Copyright 2018 Marko Pintera **************************************// |
| 2 | //*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********// |
| 3 | #pragma once |
| 4 | |
| 5 | #include "BsCorePrerequisites.h" |
| 6 | #include "Image/BsColor.h" |
| 7 | #include "Math/BsVector3.h" |
| 8 | #include "Math/BsVector2.h" |
| 9 | #include "Utility/BsBitwise.h" |
| 10 | #include "Allocators/BsGroupAlloc.h" |
| 11 | |
| 12 | namespace bs |
| 13 | { |
| 14 | /** @addtogroup Particles-Internal |
| 15 | * @{ |
| 16 | */ |
| 17 | |
| 18 | /** Handles buffers containing particle data and their allocation/deallocation. */ |
| 19 | struct ParticleSetData |
| 20 | { |
| 21 | /** Creates a new set and allocates enough space for @p capacity particles. */ |
| 22 | ParticleSetData(UINT32 capacity) |
| 23 | :capacity(capacity) |
| 24 | { |
| 25 | allocate(); |
| 26 | } |
| 27 | |
| 28 | /** |
| 29 | * Creates a new set, allocates enough space for @p capacity particles and initializes the particles by copying |
| 30 | * them from the @p other set. |
| 31 | */ |
| 32 | ParticleSetData(UINT32 capacity, const ParticleSetData& other) |
| 33 | :capacity(capacity) |
| 34 | { |
| 35 | allocate(); |
| 36 | copy(other); |
| 37 | } |
| 38 | |
| 39 | /** Moves data from @p other to this set. */ |
| 40 | ParticleSetData(ParticleSetData&& other) noexcept |
| 41 | { |
| 42 | move(other); |
| 43 | } |
| 44 | |
| 45 | /** Moves data from @p other to this set. */ |
| 46 | ParticleSetData& operator=(ParticleSetData&& other) noexcept |
| 47 | { |
| 48 | if(this != &other) |
| 49 | { |
| 50 | free(); |
| 51 | move(other); |
| 52 | } |
| 53 | |
| 54 | return *this; |
| 55 | } |
| 56 | |
| 57 | ~ParticleSetData() |
| 58 | { |
| 59 | free(); |
| 60 | } |
| 61 | |
| 62 | UINT32 capacity = 0; |
| 63 | |
| 64 | Vector3* prevPosition = nullptr; |
| 65 | Vector3* position = nullptr; |
| 66 | Vector3* velocity = nullptr; |
| 67 | Vector3* size = nullptr; |
| 68 | Vector3* rotation = nullptr; |
| 69 | float* initialLifetime = nullptr; |
| 70 | float* lifetime = nullptr; |
| 71 | RGBA* color = nullptr; |
| 72 | UINT32* seed = nullptr; |
| 73 | float* frame = nullptr; |
| 74 | UINT32* indices = nullptr; |
| 75 | |
| 76 | private: |
| 77 | /** |
| 78 | * Allocates a new set of buffers with enough space to store number of particles equal to the current capacity. * |
| 79 | * Called must ensure any previously allocated buffer is freed by calling free(). |
| 80 | */ |
| 81 | void allocate() |
| 82 | { |
| 83 | alloc. |
| 84 | reserve<Vector3>(capacity). |
| 85 | reserve<Vector3>(capacity). |
| 86 | reserve<Vector3>(capacity). |
| 87 | reserve<Vector3>(capacity). |
| 88 | reserve<Vector3>(capacity). |
| 89 | reserve<float>(capacity). |
| 90 | reserve<float>(capacity). |
| 91 | reserve<RGBA>(capacity). |
| 92 | reserve<UINT32>(capacity). |
| 93 | reserve<float>(capacity). |
| 94 | reserve<UINT32>(capacity). |
| 95 | init(); |
| 96 | |
| 97 | prevPosition = alloc.alloc<Vector3>(capacity); |
| 98 | position = alloc.alloc<Vector3>(capacity); |
| 99 | velocity = alloc.alloc<Vector3>(capacity); |
| 100 | size = alloc.alloc<Vector3>(capacity); |
| 101 | rotation = alloc.alloc<Vector3>(capacity); |
| 102 | lifetime = alloc.alloc<float>(capacity); |
| 103 | initialLifetime = alloc.alloc<float>(capacity); |
| 104 | color = alloc.alloc<RGBA>(capacity); |
| 105 | seed = alloc.alloc<UINT32>(capacity); |
| 106 | frame = alloc.alloc<float>(capacity); |
| 107 | indices = alloc.alloc<UINT32>(capacity); |
| 108 | } |
| 109 | |
| 110 | /** Frees the internal buffers. */ |
| 111 | void free() |
| 112 | { |
| 113 | if(prevPosition) alloc.free(prevPosition); |
| 114 | if(position) alloc.free(position); |
| 115 | if(velocity) alloc.free(velocity); |
| 116 | if(size) alloc.free(size); |
| 117 | if(rotation) alloc.free(rotation); |
| 118 | if(lifetime) alloc.free(lifetime); |
| 119 | if(initialLifetime) alloc.free(initialLifetime); |
| 120 | if(color) alloc.free(color); |
| 121 | if(seed) alloc.free(seed); |
| 122 | if(frame) alloc.free(frame); |
| 123 | if(indices) alloc.free(indices); |
| 124 | |
| 125 | alloc.clear(); |
| 126 | } |
| 127 | |
| 128 | /** Transfers ownership of @p other internal buffers to this object. */ |
| 129 | void move(ParticleSetData& other) |
| 130 | { |
| 131 | prevPosition = std::exchange(other.prevPosition, nullptr); |
| 132 | position = std::exchange(other.position, nullptr); |
| 133 | velocity = std::exchange(other.velocity, nullptr); |
| 134 | size = std::exchange(other.size, nullptr); |
| 135 | rotation = std::exchange(other.rotation, nullptr); |
| 136 | lifetime = std::exchange(other.lifetime, nullptr); |
| 137 | initialLifetime = std::exchange(other.initialLifetime, nullptr); |
| 138 | color = std::exchange(other.color, nullptr); |
| 139 | seed = std::exchange(other.seed, nullptr); |
| 140 | frame = std::exchange(other.frame, nullptr); |
| 141 | indices = std::exchange(other.indices, nullptr); |
| 142 | capacity = std::exchange(other.capacity, 0); |
| 143 | |
| 144 | alloc = std::move(other.alloc); |
| 145 | } |
| 146 | |
| 147 | /** Copies data from @p other buffers to this object. */ |
| 148 | void copy(const ParticleSetData& other) |
| 149 | { |
| 150 | assert(capacity >= other.capacity); |
| 151 | |
| 152 | bs_copy(prevPosition, other.prevPosition, other.capacity); |
| 153 | bs_copy(position, other.position, other.capacity); |
| 154 | bs_copy(velocity, other.velocity, other.capacity); |
| 155 | bs_copy(size, other.size, other.capacity); |
| 156 | bs_copy(rotation, other.rotation, other.capacity); |
| 157 | bs_copy(lifetime, other.lifetime, other.capacity); |
| 158 | bs_copy(initialLifetime, other.initialLifetime, other.capacity); |
| 159 | bs_copy(color, other.color, other.capacity); |
| 160 | bs_copy(seed, other.seed, other.capacity); |
| 161 | bs_copy(frame, other.frame, other.capacity); |
| 162 | bs_copy(indices, other.indices, other.capacity); |
| 163 | } |
| 164 | |
| 165 | GroupAlloc alloc; |
| 166 | }; |
| 167 | |
| 168 | /** Provides a simple and fast way to allocate and deallocate particles. */ |
| 169 | class ParticleSet : public INonCopyable |
| 170 | { |
| 171 | /** Determines how much to increase capacity once the cap is reached, in percent. */ |
| 172 | static constexpr float CAPACITY_SCALE = 1.2f; // 20% |
| 173 | |
| 174 | public: |
| 175 | /** |
| 176 | * Constructs a new particle set with enough space to hold @p capacity particles. The set will automatically |
| 177 | * grow to larger capacity if the limit is reached. |
| 178 | */ |
| 179 | ParticleSet(UINT32 capacity) |
| 180 | :mParticles(capacity) |
| 181 | { } |
| 182 | |
| 183 | /** |
| 184 | * Allocates a number of new particles and returns the index to the particle. Note that the returned index is not |
| 185 | * persistent and can become invalid after a call to freeParticle(). Returns the index to the first allocated |
| 186 | * particle. |
| 187 | */ |
| 188 | UINT32 allocParticles(UINT32 count) |
| 189 | { |
| 190 | const UINT32 particleIdx = mCount; |
| 191 | mCount += count; |
| 192 | |
| 193 | if(mCount > mParticles.capacity) |
| 194 | { |
| 195 | const auto newCapacity = (UINT32)(mCount * CAPACITY_SCALE); |
| 196 | ParticleSetData newData(newCapacity, mParticles); |
| 197 | mParticles = std::move(newData); |
| 198 | } |
| 199 | |
| 200 | const UINT32 particleEnd = particleIdx + count; |
| 201 | if(particleEnd > mMaxIndex) |
| 202 | { |
| 203 | for (; mMaxIndex < particleEnd; mMaxIndex++) |
| 204 | mParticles.indices[mMaxIndex] = mMaxIndex; |
| 205 | } |
| 206 | |
| 207 | return particleIdx; |
| 208 | } |
| 209 | |
| 210 | /** Deallocates a particle. Can invalidate particle indices. */ |
| 211 | void freeParticle(UINT32 idx) |
| 212 | { |
| 213 | // Note: We always keep the active particles sequential. This makes it faster to iterate over all particles, but |
| 214 | // increases the cost when removing particles. Considering iteration should happen many times per-particle, |
| 215 | // while removal will happen only once, this should be the more performant approach, but will likely be worth |
| 216 | // profiling in the future. An alternative approach is to flag dead particles without moving them. |
| 217 | |
| 218 | assert(idx < mCount); |
| 219 | |
| 220 | const UINT32 lastIdx = mCount - 1; |
| 221 | if(idx != lastIdx) |
| 222 | { |
| 223 | std::swap(mParticles.prevPosition[idx], mParticles.prevPosition[lastIdx]); |
| 224 | std::swap(mParticles.position[idx], mParticles.position[lastIdx]); |
| 225 | std::swap(mParticles.velocity[idx], mParticles.velocity[lastIdx]); |
| 226 | std::swap(mParticles.size[idx], mParticles.size[lastIdx]); |
| 227 | std::swap(mParticles.rotation[idx], mParticles.rotation[lastIdx]); |
| 228 | std::swap(mParticles.lifetime[idx], mParticles.lifetime[lastIdx]); |
| 229 | std::swap(mParticles.initialLifetime[idx], mParticles.initialLifetime[lastIdx]); |
| 230 | std::swap(mParticles.color[idx], mParticles.color[lastIdx]); |
| 231 | std::swap(mParticles.seed[idx], mParticles.seed[lastIdx]); |
| 232 | std::swap(mParticles.frame[idx], mParticles.frame[lastIdx]); |
| 233 | std::swap(mParticles.indices[idx], mParticles.indices[lastIdx]); |
| 234 | } |
| 235 | |
| 236 | mCount--; |
| 237 | } |
| 238 | |
| 239 | /** Frees all active partices past the provided particle count (0 to clear all particles). */ |
| 240 | void clear(UINT32 numPartices = 0) |
| 241 | { |
| 242 | if(mCount > numPartices) |
| 243 | mCount = numPartices; |
| 244 | } |
| 245 | |
| 246 | /** Returns all data about the particles. Active particles are always sequential at the start of the buffer. */ |
| 247 | ParticleSetData& getParticles() { return mParticles; } |
| 248 | |
| 249 | /** Returns all data about the particles. Active particles are always sequential at the start of the buffer. */ |
| 250 | const ParticleSetData& getParticles() const { return mParticles; } |
| 251 | |
| 252 | /** Returns the number of particles that are currently active. */ |
| 253 | UINT32 getParticleCount() const { return mCount; } |
| 254 | |
| 255 | /** |
| 256 | * Calculates the size of a texture required for storing the data of this particle set. The texture is assumed |
| 257 | * to be square. |
| 258 | */ |
| 259 | UINT32 determineTextureSize() const |
| 260 | { |
| 261 | const UINT32 count = std::max(2U, getParticleCount()); |
| 262 | |
| 263 | UINT32 width = Bitwise::nextPow2(count); |
| 264 | UINT32 height = 1; |
| 265 | |
| 266 | while (width > height) |
| 267 | { |
| 268 | width /= 2; |
| 269 | height *= 2; |
| 270 | } |
| 271 | |
| 272 | // Make it square |
| 273 | return height; |
| 274 | } |
| 275 | |
| 276 | private: |
| 277 | ParticleSetData mParticles; |
| 278 | UINT32 mCount = 0; |
| 279 | UINT32 mMaxIndex = 0; |
| 280 | }; |
| 281 | |
| 282 | /** @} */ |
| 283 | } |