| 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 | } |