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#include "BsRendererParticles.h"
4#include "Particles/BsParticleManager.h"
5#include "Renderer/BsRendererUtility.h"
6#include "RenderAPI/BsGpuBuffer.h"
7#include "RenderAPI/BsVertexBuffer.h"
8#include "Mesh/BsMeshData.h"
9#include "Mesh/BsMesh.h"
10#include "RenderAPI/BsVertexDataDesc.h"
11#include "Shading/BsGpuParticleSimulation.h"
12#include "Material/BsGpuParamsSet.h"
13#include "BsRendererView.h"
14#include "Mesh/BsMeshUtility.h"
15
16namespace bs { namespace ct
17{
18 template<bool LOCK_Y, bool GPU, bool IS_3D, ParticleForwardLightingType FWD>
19 const ShaderVariation& _getParticleShaderVariation(ParticleOrientation orient)
20 {
21 switch (orient)
22 {
23 default:
24 case ParticleOrientation::ViewPlane:
25 return getParticleShaderVariation<ParticleOrientation::ViewPlane, LOCK_Y, GPU, IS_3D, FWD>();
26 case ParticleOrientation::ViewPosition:
27 return getParticleShaderVariation<ParticleOrientation::ViewPosition, LOCK_Y, GPU, IS_3D, FWD>();
28 case ParticleOrientation::Plane:
29 return getParticleShaderVariation<ParticleOrientation::Plane, LOCK_Y, GPU, IS_3D, FWD>();
30 }
31 }
32
33 template<bool GPU, bool IS_3D, ParticleForwardLightingType FWD>
34 const ShaderVariation& _getParticleShaderVariation(ParticleOrientation orient, bool lockY)
35 {
36 if (lockY)
37 return _getParticleShaderVariation<true, GPU, IS_3D, FWD>(orient);
38
39 return _getParticleShaderVariation<false, GPU, IS_3D, FWD>(orient);
40 }
41
42 template<bool IS_3D, ParticleForwardLightingType FWD>
43 const ShaderVariation& _getParticleShaderVariation(ParticleOrientation orient, bool lockY, bool gpu)
44 {
45 if(gpu)
46 return _getParticleShaderVariation<true, IS_3D, FWD>(orient, lockY);
47
48 return _getParticleShaderVariation<false, IS_3D, FWD>(orient, lockY);
49 }
50
51 template<ParticleForwardLightingType FWD>
52 const ShaderVariation& _getParticleShaderVariation(ParticleOrientation orient, bool lockY, bool gpu, bool is3D)
53 {
54 if(is3D)
55 return _getParticleShaderVariation<true, FWD>(orient, lockY, gpu);
56
57 return _getParticleShaderVariation<false, FWD>(orient, lockY, gpu);
58 }
59
60 const ShaderVariation& getParticleShaderVariation(ParticleOrientation orient, bool lockY, bool gpu,
61 bool is3D, ParticleForwardLightingType forwardLighting)
62 {
63 switch(forwardLighting)
64 {
65 default:
66 case ParticleForwardLightingType::None:
67 return _getParticleShaderVariation<ParticleForwardLightingType::None>(orient, lockY, gpu, is3D);
68 case ParticleForwardLightingType::Clustered:
69 return _getParticleShaderVariation<ParticleForwardLightingType::Clustered>(orient, lockY, gpu, is3D);
70 case ParticleForwardLightingType::Standard:
71 return _getParticleShaderVariation<ParticleForwardLightingType::Standard>(orient, lockY, gpu, is3D);
72 }
73 }
74
75 ParticlesParamDef gParticlesParamDef;
76 GpuParticlesParamDef gGpuParticlesParamDef;
77
78 void writeIndices(GpuBuffer* buffer, const Vector<UINT32>& input, UINT32 texSize)
79 {
80 const auto numParticles = (UINT32)input.size();
81 if (numParticles == 0)
82 return;
83
84 auto* const indices = (UINT32*)buffer->lock(GBL_WRITE_ONLY_DISCARD);
85
86 UINT32 idx = 0;
87 for(auto& entry : input)
88 {
89 const UINT32 x = entry % texSize;
90 const UINT32 y = entry / texSize;
91
92 indices[idx++] = (x & 0xFFFF) | (y << 16);
93 }
94
95 buffer->unlock();
96 }
97
98 void ParticlesRenderElement::draw() const
99 {
100 if (numParticles > 0)
101 {
102 if (is3D)
103 gRendererUtility().draw(mesh, numParticles);
104 else
105 ParticleRenderer::instance().drawBillboards(numParticles);
106 }
107 }
108
109 void RendererParticles::bindCPUSimulatedInputs(const ParticleRenderData* renderData, const RendererView& view) const
110 {
111 ParticleTexturePool& particlesTexPool = ParticleRenderer::instance().getTexturePool();
112
113 const ParticleSystemSettings& settings = particleSystem->getSettings();
114 UINT32 texSize;
115 switch (settings.renderMode)
116 {
117 default:
118 case ParticleRenderMode::Billboard:
119 {
120 const auto billboardRenderData = static_cast<const ParticleBillboardRenderData*>(renderData);
121 const ParticleBillboardTextures* textures = particlesTexPool.alloc(*billboardRenderData);
122
123 renderElement.paramsCPUBillboard.positionAndRotTexture.set(textures->positionAndRotation);
124 renderElement.paramsCPUBillboard.colorTexture.set(textures->color);
125 renderElement.paramsCPUBillboard.sizeAndFrameIdxTexture.set(textures->sizeAndFrameIdx);
126
127 renderElement.indicesBuffer.set(textures->indices);
128 texSize = textures->positionAndRotation->getProperties().getWidth();
129 }
130 break;
131 case ParticleRenderMode::Mesh:
132 {
133 const auto meshRenderData = static_cast<const ParticleMeshRenderData*>(renderData);
134 const ParticleMeshTextures* textures = particlesTexPool.alloc(*meshRenderData);
135
136 renderElement.paramsCPUMesh.positionTexture.set(textures->position);
137 renderElement.paramsCPUMesh.colorTexture.set(textures->color);
138 renderElement.paramsCPUMesh.rotationTexture.set(textures->rotation);
139 renderElement.paramsCPUMesh.sizeTexture.set(textures->size);
140
141 renderElement.indicesBuffer.set(textures->indices);
142 texSize = textures->position->getProperties().getWidth();
143 }
144 break;
145 }
146
147 renderElement.numParticles = renderData->numParticles;
148
149 gParticlesParamDef.gTexSize.set(particlesParamBuffer, texSize);
150 gParticlesParamDef.gBufferOffset.set(particlesParamBuffer, 0);
151
152 SPtr<GpuParams> gpuParams = renderElement.params->getGpuParams();
153 for (UINT32 j = 0; j < GPT_COUNT; j++)
154 {
155 const GpuParamBinding& binding = renderElement.perCameraBindings[j];
156 if (binding.slot != (UINT32)-1)
157 gpuParams->setParamBlockBuffer(binding.set, binding.slot, view.getPerViewBuffer());
158 }
159 }
160
161 void RendererParticles::bindGPUSimulatedInputs(const GpuParticleResources& gpuSimResources, const RendererView& view) const
162 {
163 const GpuParticleStateTextures& gpuSimStateTextures = gpuSimResources.getCurrentState();
164 const GpuParticleStaticTextures& gpuSimStaticTextures = gpuSimResources.getStaticTextures();
165 const GpuParticleCurves& gpuCurves = gpuSimResources.getCurveTexture();
166 const SPtr<GpuBuffer>& sortedIndices = gpuSimResources.getSortedIndices();
167
168 renderElement.paramsGPU.positionTimeTexture.set(gpuSimStateTextures.positionAndTimeTex);
169 renderElement.paramsGPU.sizeRotationTexture.set(gpuSimStaticTextures.sizeAndRotationTex);
170 renderElement.paramsGPU.curvesTexture.set(gpuCurves.getTexture());
171 renderElement.numParticles = gpuParticleSystem->getNumTiles() * GpuParticleResources::PARTICLES_PER_TILE;
172
173 if (gpuParticleSystem->hasSortInfo())
174 {
175 renderElement.indicesBuffer.set(sortedIndices);
176 gParticlesParamDef.gBufferOffset.set(particlesParamBuffer,
177 gpuParticleSystem->getSortOffset());
178 }
179 else
180 {
181 renderElement.indicesBuffer.set(gpuParticleSystem->getParticleIndices());
182 gParticlesParamDef.gBufferOffset.set(particlesParamBuffer, 0);
183 }
184
185 const UINT32 texSize = GpuParticleResources::TEX_SIZE;
186 gParticlesParamDef.gTexSize.set(particlesParamBuffer, texSize);
187
188 SPtr<GpuParams> gpuParams = renderElement.params->getGpuParams();
189 for (UINT32 j = 0; j < GPT_COUNT; j++)
190 {
191 const GpuParamBinding& binding = renderElement.perCameraBindings[j];
192 if (binding.slot != (UINT32)-1)
193 gpuParams->setParamBlockBuffer(binding.set, binding.slot, view.getPerViewBuffer());
194 }
195 }
196
197 ParticleTexturePool::~ParticleTexturePool()
198 {
199 for (auto& sizeEntry : mBillboardBufferList)
200 {
201 for (auto& entry : sizeEntry.second.buffers)
202 mBillboardAlloc.destruct(entry);
203 }
204
205 for (auto& sizeEntry : mMeshBufferList)
206 {
207 for (auto& entry : sizeEntry.second.buffers)
208 mMeshAlloc.destruct(entry);
209 }
210 }
211
212 const ParticleBillboardTextures* ParticleTexturePool::alloc(const ParticleBillboardRenderData& simulationData)
213 {
214 const UINT32 size = simulationData.color.getWidth();
215
216 const ParticleBillboardTextures* output = nullptr;
217 BillboardBuffersPerSize& buffers = mBillboardBufferList[size];
218 if (buffers.nextFreeIdx < (UINT32)buffers.buffers.size())
219 {
220 output = buffers.buffers[buffers.nextFreeIdx];
221 buffers.nextFreeIdx++;
222 }
223
224 if (!output)
225 {
226 output = createNewBillboardTextures(size);
227 buffers.nextFreeIdx++;
228 }
229
230 // Populate texture contents
231 // Note: Perhaps instead of using write-discard here, we should track which frame has finished rendering and then
232 // just use no-overwrite? write-discard will very likely allocate memory under the hood.
233 output->positionAndRotation->writeData(simulationData.positionAndRotation, 0, 0, true);
234 output->color->writeData(simulationData.color, 0, 0, true);
235 output->sizeAndFrameIdx->writeData(simulationData.sizeAndFrameIdx, 0, 0, true);
236
237 writeIndices(output->indices.get(), simulationData.indices, size);
238 return output;
239 }
240
241 const ParticleMeshTextures* ParticleTexturePool::alloc(const ParticleMeshRenderData& simulationData)
242 {
243 const UINT32 size = simulationData.color.getWidth();
244
245 const ParticleMeshTextures* output = nullptr;
246 MeshBuffersPerSize& buffers = mMeshBufferList[size];
247 if (buffers.nextFreeIdx < (UINT32)buffers.buffers.size())
248 {
249 output = buffers.buffers[buffers.nextFreeIdx];
250 buffers.nextFreeIdx++;
251 }
252
253 if (!output)
254 {
255 output = createNewMeshTextures(size);
256 buffers.nextFreeIdx++;
257 }
258
259 // Populate texture contents
260 // Note: Perhaps instead of using write-discard here, we should track which frame has finished rendering and then
261 // just use no-overwrite? write-discard will very likely allocate memory under the hood.
262 output->position->writeData(simulationData.position, 0, 0, true);
263 output->color->writeData(simulationData.color, 0, 0, true);
264 output->size->writeData(simulationData.size, 0, 0, true);
265 output->rotation->writeData(simulationData.rotation, 0, 0, true);
266
267 writeIndices(output->indices.get(), simulationData.indices, size);
268 return output;
269 }
270
271 void ParticleTexturePool::clear()
272 {
273 for(auto& buffers : mBillboardBufferList)
274 buffers.second.nextFreeIdx = 0;
275
276 for(auto& buffers : mMeshBufferList)
277 buffers.second.nextFreeIdx = 0;
278 }
279
280 ParticleBillboardTextures* ParticleTexturePool::createNewBillboardTextures(UINT32 size)
281 {
282 ParticleBillboardTextures* output = mBillboardAlloc.construct<ParticleBillboardTextures>();
283
284 TEXTURE_DESC texDesc;
285 texDesc.type = TEX_TYPE_2D;
286 texDesc.width = size;
287 texDesc.height = size;
288 texDesc.usage = TU_DYNAMIC;
289
290 texDesc.format = PF_RGBA32F;
291 output->positionAndRotation = Texture::create(texDesc);
292
293 texDesc.format = PF_RGBA8;
294 output->color = Texture::create(texDesc);
295
296 texDesc.format = PF_RGBA16F;
297 output->sizeAndFrameIdx = Texture::create(texDesc);
298
299 GPU_BUFFER_DESC bufferDesc;
300 bufferDesc.type = GBT_STANDARD;
301 bufferDesc.elementCount = size * size;
302 bufferDesc.format = BF_16X2U;
303
304 output->indices = GpuBuffer::create(bufferDesc);
305
306 mBillboardBufferList[size].buffers.push_back(output);
307 return output;
308 }
309
310 ParticleMeshTextures* ParticleTexturePool::createNewMeshTextures(UINT32 size)
311 {
312 ParticleMeshTextures* output = mMeshAlloc.construct<ParticleMeshTextures>();
313
314 TEXTURE_DESC texDesc;
315 texDesc.type = TEX_TYPE_2D;
316 texDesc.width = size;
317 texDesc.height = size;
318 texDesc.usage = TU_DYNAMIC;
319
320 texDesc.format = PF_RGBA32F;
321 output->position = Texture::create(texDesc);
322
323 texDesc.format = PF_RGBA8;
324 output->color = Texture::create(texDesc);
325
326 texDesc.format = PF_RGBA16F;
327 output->size = Texture::create(texDesc);
328
329 texDesc.format = PF_RGBA16F;
330 output->rotation = Texture::create(texDesc);
331
332 GPU_BUFFER_DESC bufferDesc;
333 bufferDesc.type = GBT_STANDARD;
334 bufferDesc.elementCount = size * size;
335 bufferDesc.format = BF_16X2U;
336
337 output->indices = GpuBuffer::create(bufferDesc);
338
339 mMeshBufferList[size].buffers.push_back(output);
340 return output;
341 }
342 struct ParticleRenderer::Members
343 {
344 SPtr<VertexBuffer> billboardVB;
345 SPtr<VertexDeclaration> billboardVD;
346 };
347
348 ParticleRenderer::ParticleRenderer()
349 :m(bs_new<Members>())
350 {
351 SPtr<VertexDataDesc> vertexDesc = bs_shared_ptr_new<VertexDataDesc>();
352 vertexDesc->addVertElem(VET_FLOAT3, VES_POSITION);
353 vertexDesc->addVertElem(VET_FLOAT2, VES_TEXCOORD);
354 vertexDesc->addVertElem(VET_UBYTE4_NORM, VES_NORMAL);
355 vertexDesc->addVertElem(VET_UBYTE4_NORM, VES_TANGENT);
356
357 m->billboardVD = VertexDeclaration::create(vertexDesc);
358
359 VERTEX_BUFFER_DESC vbDesc;
360 vbDesc.numVerts = 4;
361 vbDesc.vertexSize = m->billboardVD->getProperties().getVertexSize(0);
362 m->billboardVB = VertexBuffer::create(vbDesc);
363
364 MeshData meshData(4, 0, vertexDesc);
365 auto vecIter = meshData.getVec3DataIter(VES_POSITION);
366 vecIter.addValue(Vector3(-0.5f, -0.5f, 0.0f));
367 vecIter.addValue(Vector3(-0.5f, 0.5f, 0.0f));
368 vecIter.addValue(Vector3(0.5f, -0.5f, 0.0f));
369 vecIter.addValue(Vector3(0.5f, 0.5f, 0.0f));
370
371 auto uvIter = meshData.getVec2DataIter(VES_TEXCOORD);
372 uvIter.addValue(Vector2(0.0f, 1.0f));
373 uvIter.addValue(Vector2(0.0f, 0.0f));
374 uvIter.addValue(Vector2(1.0f, 1.0f));
375 uvIter.addValue(Vector2(1.0f, 0.0f));
376
377 UINT32 stride = meshData.getVertexDesc()->getVertexStride(0);
378
379 Vector3 normal = Vector3::UNIT_Y;
380 Vector4 tangent(1.0f, 0.0f, 0.0f, 1.0f);
381
382 UINT8* normalDst = meshData.getElementData(VES_NORMAL);
383 for(UINT32 i = 0; i < 4; i++)
384 {
385 MeshUtility::packNormals(&normal, normalDst, 1, sizeof(Vector3), stride);
386 normalDst += stride;
387 }
388
389 UINT8* tangentDst = meshData.getElementData(VES_TANGENT);
390 for(UINT32 i = 0; i < 4; i++)
391 {
392 MeshUtility::packNormals(&tangent, tangentDst, 1, sizeof(Vector4), stride);
393 tangentDst += stride;
394 }
395
396 m->billboardVB->writeData(0, meshData.getStreamSize(0), meshData.getStreamData(0), BWT_DISCARD);
397 }
398
399 ParticleRenderer::~ParticleRenderer()
400 {
401 bs_delete(m);
402 }
403
404 void ParticleRenderer::drawBillboards(UINT32 count)
405 {
406 SPtr<VertexBuffer> vertexBuffers[] = { m->billboardVB };
407
408 RenderAPI& rapi = RenderAPI::instance();
409 rapi.setVertexDeclaration(m->billboardVD);
410 rapi.setVertexBuffers(0, vertexBuffers, 1);
411 rapi.setDrawOperation(DOT_TRIANGLE_STRIP);
412 rapi.draw(0, 4, count);
413 }
414
415 void ParticleRenderer::sortByDistance(const Vector3& refPoint, const PixelData& positions, UINT32 numParticles,
416 UINT32 stride, Vector<UINT32>& indices)
417 {
418 struct ParticleSortData
419 {
420 ParticleSortData(float key, UINT32 idx)
421 :key(key), idx(idx)
422 { }
423
424 float key;
425 UINT32 idx;
426 };
427
428 const UINT32 size = positions.getWidth();
429 UINT8* positionPtr = positions.getData();
430
431 bs_frame_mark();
432 {
433 FrameVector<ParticleSortData> sortData;
434 sortData.reserve(numParticles);
435
436 UINT32 x = 0;
437 for (UINT32 i = 0; i < numParticles; i++)
438 {
439 const Vector3& position = *(Vector3*)positionPtr;
440
441 float distance = refPoint.squaredDistance(position);
442 sortData.emplace_back(distance, i);
443
444 positionPtr += sizeof(float) * stride;
445 x++;
446
447 if (x >= size)
448 {
449 x = 0;
450 positionPtr += positions.getRowSkip();
451 }
452 }
453
454 std::sort(sortData.begin(), sortData.end(),
455 [](const ParticleSortData& lhs, const ParticleSortData& rhs)
456 {
457 return rhs.key < lhs.key;
458 });
459
460 for (UINT32 i = 0; i < numParticles; i++)
461 indices[i] = sortData[i].idx;
462 }
463 bs_frame_clear();
464 }
465
466}}
467