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 | |
13 | namespace 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 | |
357 | namespace bs |
358 | { |
359 | IMPLEMENT_GLOBAL_POOL(ct::GpuParticleSystem, 32) |
360 | } |