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 "BsRenderBeastPrerequisites.h"
6#include "Utility/BsBitfield.h"
7#include "Utility/BsModule.h"
8#include "Particles/BsParticleManager.h"
9#include "Allocators/BsPoolAlloc.h"
10#include "Utility/BsTextureRowAllocator.h"
11#include "Utility/BsGpuSort.h"
12
13namespace bs { namespace ct
14{
15 struct RendererParticles;
16 class GpuParticleSimulateMat;
17 struct GBufferTextures;
18 struct SceneInfo;
19 class GpuParticleResources;
20
21 /** @addtogroup RenderBeast
22 * @{
23 */
24
25 /** Contains information about a single tile allocated in the particle texture used for GPU simulation. */
26 struct GpuParticleTile
27 {
28 UINT32 id = (UINT32)-1;
29 UINT32 numFreeParticles = 0;
30 float lifetime = 0.0f;
31 };
32
33 /** Contains functionality specific to a single particle system simulated on the GPU. */
34 class GpuParticleSystem
35 {
36 public:
37 GpuParticleSystem(ParticleSystem* parent);
38 ~GpuParticleSystem();
39
40 /** Returns the non-renderer particle system object that owns this object. */
41 ParticleSystem* getParent() const { return mParent; }
42
43 /**
44 * Attempts to allocate room for a set of particles. Particles will attempt to be inserted into an existing tile if
45 * there's room, or new tiles will be allocated otherwise. If the particle texture is full the allocation will
46 * silently fail.
47 *
48 * @param[in] resources Object holding the global particle textures.
49 * @param[in,out] newParticles List of new particles for which space needs to be allocated. The particles will
50 * get updated in-place with the UV coordinates at which their data is located.
51 * @param[in] newTiles Indices of the tiles that were newly allocated, if any.
52 * @return True if any new tiles were allocated, false otherwise.
53 */
54 bool allocateTiles(GpuParticleResources& resources, Vector<GpuParticle>& newParticles,
55 Vector<UINT32>& newTiles);
56
57 /**
58 * Detects which tiles had all of their particle's expire and marks the inactive so they can be re-used on the
59 * next call to allocateTiles().
60 */
61 void detectInactiveTiles();
62
63 /** Releases any tiles that were marked as inactive so they may be re-used by some other particle system. */
64 bool freeInactiveTiles(GpuParticleResources& resources);
65
66 /** Returns a buffer containing UV coordinates to which each of the allocate tiles map to. */
67 SPtr<GpuBuffer> getTileUVs() const { return mTileUVs; }
68
69 /** Returns a buffer containing per-particle indices used for locating particle data in the particle textures. */
70 SPtr<GpuBuffer> getParticleIndices() const { return mParticleIndices; }
71
72 /**
73 * Returns the total number of tiles used by this particle system. This may include inactive tiles unless you have
74 * freed them using freeInactiveTiles earlier.
75 */
76 UINT32 getNumTiles() const { return (UINT32)mTiles.size(); }
77
78 /** Rebuilds ths internal buffers that contain tile UVs and per-particle UVs. */
79 void updateGpuBuffers();
80
81 /** Increments the internal time counter. */
82 void advanceTime(float dt);
83
84 /** Returns the time since the system was created. */
85 float getTime() const { return mTime; }
86
87 /**
88 * Returns the bounds of the particle system. These will be user-provided bounds, or infinite bounds if no
89 * user-provided ones exist.
90 */
91 AABox getBounds() const;
92
93 /** Returns the object that can be used for retrieving random numbers when evaluating this particle system. */
94 const Random& getRandom() const { return mRandom; }
95
96 /**
97 * Sets information about the results of particle system sorting.
98 *
99 * @param[in] sorted True if the system has information in the sorted index buffer.
100 * @param[in] offset Offset into the sorted index buffer. Only relevant if @p sorted is true.
101 */
102 void setSortInfo(bool sorted, UINT32 offset)
103 {
104 mSorted = sorted;
105
106 if(sorted)
107 mSortOffset = offset;
108 }
109
110 /** Returns true if the particle system has its indices stored in the sorted index buffer. */
111 bool hasSortInfo() const { return mSorted; }
112
113 /**
114 * Returns offset into the sorted index buffer at which indices of the particle system start. Only available if
115 * hasSortInfo() returns true.
116 */
117 UINT32 getSortOffset() const { return mSortOffset; }
118
119 private:
120 ParticleSystem* mParent = nullptr;
121 Vector<GpuParticleTile> mTiles;
122 Bitfield mActiveTiles;
123 UINT32 mNumActiveTiles = 0;
124 UINT32 mLastAllocatedTile = (UINT32)-1;
125 float mTime = 0.0f;
126 bool mSorted = false;
127 UINT32 mSortOffset = 0;
128 Random mRandom;
129
130 SPtr<GpuBuffer> mTileUVs;
131 SPtr<GpuBuffer> mParticleIndices;
132 };
133
134 /** Performs simulation for all particle systems that have GPU simulation enabled. */
135 class GpuParticleSimulation : public Module<GpuParticleSimulation>
136 {
137 struct Pimpl;
138 public:
139 GpuParticleSimulation();
140 ~GpuParticleSimulation();
141
142 /**
143 * Registers a new GPU particle system to simulate. The system will be simulated until removed by a call to
144 * removeSystem().
145 */
146 void addSystem(GpuParticleSystem* system);
147
148 /** Unregisters a previously registered particle system. */
149 void removeSystem(GpuParticleSystem* system);
150
151 /**
152 * Performs GPU particle simulation on all registered particle systems.
153 *
154 * @param[in] sceneInfo Information about the scene currently being rendered.
155 * @param[in] simData Particle simulation data output on the simulation thread.
156 * @param[in] viewParams Buffer containing properties of the view that's currently being rendered.
157 * @param[in] gbuffer Populated GBuffer with depths and normals.
158 * @param[in] dt Time step to advance the simulation by.
159 */
160 void simulate(const SceneInfo& sceneInfo, const ParticlePerFrameData* simData,
161 const SPtr<GpuParamBlockBuffer>& viewParams, const GBufferTextures& gbuffer, float dt);
162
163 /**
164 * Sorts the particle systems for the provided view. Only sorts systems using distance based sorting and only
165 * works on systems supporting compute. Sort results are written to a global buffer accessible through
166 * getResources(), with offsets into the buffer written into particle system objects in @p sceneInfo.
167 */
168 void sort(const RendererView& view);
169
170 /** Returns textures used for storing particle data. */
171 GpuParticleResources& getResources() const;
172 private:
173 /** Prepares buffer necessary for simulating the provided particle system. */
174 void prepareBuffers(const GpuParticleSystem* system, const RendererParticles& rendererInfo);
175
176 /** Clears out all the areas in particle textures as marked by the provided tiles to their default values. */
177 void clearTiles(const Vector<UINT32>& tiles);
178
179 /** Inserts the provided set of particles into the particle textures. */
180 void injectParticles(const Vector<GpuParticle>& particles);
181
182 Pimpl* m;
183 };
184
185 /** Contains textures that get updated with every run of the GPU particle simulation. */
186 struct GpuParticleStateTextures
187 {
188 SPtr<Texture> positionAndTimeTex;
189 SPtr<Texture> velocityTex;
190 };
191
192 /** Contains textures that contain data static throughout the particle's lifetime. */
193 struct GpuParticleStaticTextures
194 {
195 SPtr<Texture> sizeAndRotationTex;
196 };
197
198 /** Contains a texture containing quantized versions of all curves used for the GPU particle system. */
199 class GpuParticleCurves
200 {
201 static constexpr UINT32 TEX_SIZE = 1024;
202 static constexpr UINT32 SCRATCH_NUM_VERTICES = 16384;
203 public:
204 GpuParticleCurves();
205 ~GpuParticleCurves();
206
207 /**
208 * Adds the provided set of pixels to the curve texture. Note you must call apply() to actually inject the
209 * pixels into the texture.
210 *
211 * @param[in] pixels Pixels to inject into the curve.
212 * @param[in] count Number of pixels in the @p pixels array.
213 * @return Allocation information about in which part of the texture the pixels were places.
214 */
215 TextureRowAllocation alloc(Color* pixels, uint32_t count);
216
217 /** Frees a previously allocated region. */
218 void free(const TextureRowAllocation& alloc);
219
220 /**
221 * Injects all the newly added pixels into the curve texture (since the last call to this method). Should be
222 * called after alloc() has been called for all new entries, but before the texture is used for reading.
223 */
224 void applyChanges();
225
226 /** Returns the internal texture the curve data is written to. */
227 const SPtr<Texture>& getTexture() const { return mCurveTexture; }
228
229 /** Returns the UV coordinates at which the provided allocation starts. */
230 static Vector2 getUVOffset(const TextureRowAllocation& alloc);
231
232 /**
233 * Returns a value which scales a value in range [0, 1] to a range of pixels of the provided allocation, where 0
234 * represents the left-most pixel, and 1 the right-most pixel.
235 */
236 static float getUVScale(const TextureRowAllocation& alloc);
237
238 private:
239 /** Information about an allocation not yet injected into the curve texture. */
240 struct PendingAllocation
241 {
242 Color* pixels;
243 TextureRowAllocation allocation;
244 };
245
246 FrameAlloc mPendingAllocator;
247 Vector<PendingAllocation> mPendingAllocations;
248
249 SPtr<Texture> mCurveTexture;
250 SPtr<RenderTexture> mRT;
251
252 TextureRowAllocator<TEX_SIZE, TEX_SIZE> mRowAllocator;
253
254 SPtr<VertexBuffer> mInjectUV;
255 SPtr<IndexBuffer> mInjectIndices;
256 SPtr<VertexDeclaration> mInjectVertexDecl;
257 SPtr<VertexBuffer> mInjectScratch;
258 };
259
260 /**
261 * Contains textures and buffers used for GPU particle simulation and handles allocation of tiles within the particle
262 * textures. State textures are double-buffered so one can be used for reading and other for writing during simulation.
263 */
264 class GpuParticleResources
265 {
266 public:
267 static constexpr UINT32 TEX_SIZE = 1024;
268 static constexpr UINT32 TILE_SIZE = 4;
269 static constexpr UINT32 PARTICLES_PER_TILE = TILE_SIZE * TILE_SIZE;
270 static constexpr UINT32 TILE_COUNT_1D = TEX_SIZE / TILE_SIZE;
271 static constexpr UINT32 TILE_COUNT = TILE_COUNT_1D * TILE_COUNT_1D;
272
273 static_assert((TEX_SIZE & (TEX_SIZE - 1)) == 0, "Particle texture size not a power of two");
274 static_assert((TILE_SIZE & (TILE_SIZE - 1)) == 0, "Particle tile size not a power of two");
275
276 GpuParticleResources();
277
278 /** Swap the read and write state textures. */
279 void swap() { mWriteBufferIdx ^= 0x1; }
280
281 /** Returns textures that contain the results from the previous simulation step. */
282 GpuParticleStateTextures& getPreviousState() { return mStateTextures[mWriteBufferIdx ^ 0x1]; }
283
284 /** Returns textures that contain the results from the last available simulation step. */
285 GpuParticleStateTextures& getCurrentState() { return mStateTextures[mWriteBufferIdx]; }
286
287 /** @copydoc getCurrentState() */
288 const GpuParticleStateTextures& getCurrentState() const { return mStateTextures[mWriteBufferIdx]; }
289
290 /** Returns a set of textures containing particle state that is static throughout the particle's lifetime. */
291 const GpuParticleStaticTextures& getStaticTextures() const { return mStaticTextures; }
292
293 /** Returns an object containing quantized curves for all particle systems. */
294 GpuParticleCurves& getCurveTexture() { return mCurveTexture; }
295
296 /** @copydoc getCurveTexture() */
297 const GpuParticleCurves& getCurveTexture() const { return mCurveTexture; }
298
299 /** Returns the render target which can be used for injecting new particle data in the state textures. */
300 const SPtr<RenderTexture>& getInjectTarget() const { return mInjectRT[mWriteBufferIdx ^ 0x1]; }
301
302 /** Returns the render target which can be used for writing the results of the particle system simulation. */
303 const SPtr<RenderTexture>& getSimulationTarget() const { return mSimulateRT[mWriteBufferIdx]; }
304
305 /** Returns a global buffer containing particle indices for sorted particle systems. */
306 const SPtr<GpuBuffer>& getSortedIndices() const;
307
308 /**
309 * Attempts to allocate a new tile in particle textures. Returns index of the tile if successful or -1 if no more
310 * room.
311 */
312 UINT32 allocTile();
313
314 /** Frees a tile previously allocated with allocTile(). */
315 void freeTile(UINT32 tile);
316
317 /** Returns offset (in pixels) at which the tile with the specified index starts at. */
318 static Vector2I getTileOffset(UINT32 tileId);
319
320 /** Returns the UV coordinates at which the tile with the specified index starts at. */
321 static Vector2 getTileCoords(UINT32 tileId);
322
323 /**
324 * Returns the particle offset (in pixels) relative to the tile. @p subTileIdx represents he index of the particle
325 * in a tile.
326 */
327 static Vector2I getParticleOffset(UINT32 subTileId);
328
329 /**
330 * Returns the particle coordinates relative to the tile. @p subTileIdx represents the index of the particle in
331 * a tile.
332 */
333 static Vector2 getParticleCoords(UINT32 subTileIdx);
334
335 private:
336 friend class GpuParticleSimulation;
337
338 GpuParticleStateTextures mStateTextures[2];
339 GpuParticleStaticTextures mStaticTextures;
340 GpuParticleCurves mCurveTexture;
341 GpuSortBuffers mSortBuffers;
342 SPtr<GpuBuffer> mSortedIndices[2];
343 UINT32 mSortedIndicesBufferIdx = 0;
344
345 SPtr<RenderTexture> mSimulateRT[2];
346 SPtr<RenderTexture> mInjectRT[2];
347
348 UINT32 mWriteBufferIdx = 0;
349
350 UINT32 mFreeTiles[TILE_COUNT];
351 UINT32 mNumFreeTiles = TILE_COUNT;
352 };
353
354 /** @} */
355}}
356
357namespace bs
358{
359 IMPLEMENT_GLOBAL_POOL(ct::GpuParticleSystem, 32)
360}