1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qssgrhiparticles_p.h"
5#include "qssgrhicontext_p.h"
6
7#include <qfloat16.h>
8
9#include <QtQuick3DUtils/private/qssgutils_p.h>
10
11#include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
12#include <QtQuick3DRuntimeRender/private/qssgrendercamera_p.h>
13#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
14
15QT_BEGIN_NAMESPACE
16
17static const QRhiShaderResourceBinding::StageFlags VISIBILITY_ALL =
18 QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage;
19
20struct ParticleLightData
21{
22 QVector4D pointLightPos[4];
23 float pointLightConstantAtt[4] = {1.0f, 1.0f, 1.0f, 1.0f};
24 float pointLightLinearAtt[4] = {0.0f};
25 float pointLightQuadAtt[4] = {0.0f};
26 QVector4D pointLightColor[4];
27 QVector4D spotLightPos[4];
28 float spotLightConstantAtt[4] = {1.0f, 1.0f, 1.0f, 1.0f};
29 float spotLightLinearAtt[4] = {0.0f};
30 float spotLightQuadAtt[4] = {0.0f};
31 QVector4D spotLightColor[4];
32 QVector4D spotLightDir[4];
33 float spotLightConeAngle[4] = {0.0f};
34 float spotLightInnerConeAngle[4] = {0.0f};
35};
36
37void QSSGParticleRenderer::updateUniformsForParticles(QSSGRhiShaderPipeline &shaders,
38 QSSGRhiContext *rhiCtx,
39 char *ubufData,
40 QSSGParticlesRenderable &renderable,
41 QSSGRenderCamera &inCamera)
42{
43 const QMatrix4x4 clipSpaceCorrMatrix = rhiCtx->rhi()->clipSpaceCorrMatrix();
44
45 QSSGRhiShaderPipeline::CommonUniformIndices &cui = shaders.commonUniformIndices;
46
47 const QMatrix4x4 projection = clipSpaceCorrMatrix * inCamera.projection;
48 shaders.setUniform(ubufData, name: "qt_projectionMatrix", data: projection.constData(), size: 16 * sizeof(float), storeIndex: &cui.projectionMatrixIdx);
49
50 const QMatrix4x4 viewMatrix = inCamera.globalTransform.inverted();
51 shaders.setUniform(ubufData, name: "qt_viewMatrix", data: viewMatrix.constData(), size: 16 * sizeof(float), storeIndex: &cui.viewMatrixIdx);
52
53 const QMatrix4x4 &modelMatrix = renderable.globalTransform;
54 shaders.setUniform(ubufData, name: "qt_modelMatrix", data: modelMatrix.constData(), size: 16 * sizeof(float), storeIndex: &cui.modelMatrixIdx);
55
56 QVector2D oneOverSize = QVector2D(1.0f, 1.0f);
57 auto &particleBuffer = renderable.particles.m_particleBuffer;
58 const quint32 particlesPerSlice = particleBuffer.particlesPerSlice();
59 oneOverSize = QVector2D(1.0f / particleBuffer.size().width(), 1.0f / particleBuffer.size().height());
60 shaders.setUniform(ubufData, name: "qt_oneOverParticleImageSize", data: &oneOverSize, size: 2 * sizeof(float));
61 shaders.setUniform(ubufData, name: "qt_countPerSlice", data: &particlesPerSlice, size: 1 * sizeof(quint32));
62
63 // Global opacity of the particles node
64 shaders.setUniform(ubufData, name: "qt_opacity", data: &renderable.opacity, size: 1 * sizeof(float));
65
66 float blendImages = renderable.particles.m_blendImages ? 1.0f : 0.0f;
67 float imageCount = float(renderable.particles.m_spriteImageCount);
68 float ooImageCount = 1.0f / imageCount;
69
70 QVector4D spriteConfig(imageCount, ooImageCount, 0.0f, blendImages);
71 shaders.setUniform(ubufData, name: "qt_spriteConfig", data: &spriteConfig, size: 4 * sizeof(float));
72
73 const float billboard = renderable.particles.m_billboard ? 1.0f : 0.0f;
74 shaders.setUniform(ubufData, name: "qt_billboard", data: &billboard, size: 1 * sizeof(float));
75
76 // Lights
77 QVector3D theLightAmbientTotal;
78 bool hasLights = !renderable.particles.m_lights.isEmpty();
79 int pointLight = 0;
80 int spotLight = 0;
81 if (hasLights) {
82 ParticleLightData lightData;
83 auto &lights = renderable.lights;
84 for (quint32 lightIdx = 0, lightEnd = lights.size();
85 lightIdx < lightEnd && lightIdx < QSSG_MAX_NUM_LIGHTS; ++lightIdx) {
86 QSSGRenderLight *theLight(lights[lightIdx].light);
87 // Ignore lights which are not specified for the particle
88 if (!renderable.particles.m_lights.contains(t: theLight))
89 continue;
90 if (theLight->type == QSSGRenderLight::Type::DirectionalLight) {
91 theLightAmbientTotal += theLight->m_diffuseColor * theLight->m_brightness;
92 } else if (theLight->type == QSSGRenderLight::Type::PointLight && pointLight < 4) {
93 lightData.pointLightColor[pointLight] = QVector4D(theLight->m_diffuseColor * theLight->m_brightness, 1.0f);
94 lightData.pointLightPos[pointLight] = QVector4D(theLight->getGlobalPos(), 1.0f);
95 lightData.pointLightConstantAtt[pointLight] = QSSGUtils::aux::translateConstantAttenuation(attenuation: theLight->m_constantFade);
96 lightData.pointLightLinearAtt[pointLight] = QSSGUtils::aux::translateLinearAttenuation(attenuation: theLight->m_linearFade);
97 lightData.pointLightQuadAtt[pointLight] = QSSGUtils::aux::translateQuadraticAttenuation(attenuation: theLight->m_quadraticFade);
98 pointLight++;
99 } else if (theLight->type == QSSGRenderLight::Type::SpotLight && spotLight < 4) {
100 lightData.spotLightColor[spotLight] = QVector4D(theLight->m_diffuseColor * theLight->m_brightness, 1.0f);
101 lightData.spotLightPos[spotLight] = QVector4D(theLight->getGlobalPos(), 1.0f);
102 lightData.spotLightDir[spotLight] = QVector4D(lights[lightIdx].direction, 0.0f);
103 lightData.spotLightConstantAtt[spotLight] = QSSGUtils::aux::translateConstantAttenuation(attenuation: theLight->m_constantFade);
104 lightData.spotLightLinearAtt[spotLight] = QSSGUtils::aux::translateLinearAttenuation(attenuation: theLight->m_linearFade);
105 lightData.spotLightQuadAtt[spotLight] = QSSGUtils::aux::translateQuadraticAttenuation(attenuation: theLight->m_quadraticFade);
106 float coneAngle = theLight->m_coneAngle;
107 // Inner cone angle must always be < cone angle, to not have possible undefined behavior for shader smoothstep
108 float innerConeAngle = std::min(a: theLight->m_innerConeAngle, b: coneAngle - 0.01f);
109 lightData.spotLightConeAngle[spotLight] = qDegreesToRadians(degrees: coneAngle);
110 lightData.spotLightInnerConeAngle[spotLight] = qDegreesToRadians(degrees: innerConeAngle);
111 spotLight++;
112 }
113 theLightAmbientTotal += theLight->m_ambientColor;
114 }
115 // Copy light data
116 int lightOffset = shaders.offsetOfUniform(name: "qt_pointLightPosition");
117 if (lightOffset >= 0)
118 memcpy(dest: ubufData + lightOffset, src: &lightData, n: sizeof(ParticleLightData));
119 }
120 shaders.setUniform(ubufData, name: "qt_light_ambient_total", data: &theLightAmbientTotal, size: 3 * sizeof(float), storeIndex: &cui.light_ambient_totalIdx);
121 int enablePointLights = pointLight > 0 ? 1 : 0;
122 int enableSpotLights = spotLight > 0 ? 1 : 0;
123 shaders.setUniform(ubufData, name: "qt_pointLights", data: &enablePointLights, size: sizeof(int));
124 shaders.setUniform(ubufData, name: "qt_spotLights", data: &enableSpotLights, size: sizeof(int));
125
126 // Line particle uniform
127 int segmentCount = particleBuffer.segments();
128 if (segmentCount) {
129 shaders.setUniform(ubufData, name: "qt_lineSegmentCount", data: &segmentCount, size: sizeof(int));
130 float alphaFade = renderable.particles.m_alphaFade;
131 float sizeModifier = renderable.particles.m_sizeModifier;
132 float texcoordScale = renderable.particles.m_texcoordScale;
133 auto image = renderable.firstImage;
134 if (image && image->m_texture.m_texture) {
135 const auto size = image->m_texture.m_texture->pixelSize();
136 texcoordScale *= float(size.height()) / float(size.width());
137 }
138 shaders.setUniform(ubufData, name: "qt_alphaFade", data: &alphaFade, size: sizeof(float));
139 shaders.setUniform(ubufData, name: "qt_sizeModifier", data: &sizeModifier, size: sizeof(float));
140 shaders.setUniform(ubufData, name: "qt_texcoordScale", data: &texcoordScale, size: sizeof(float));
141 }
142}
143
144void QSSGParticleRenderer::updateUniformsForParticleModel(QSSGRhiShaderPipeline &shaderPipeline,
145 char *ubufData,
146 const QSSGRenderModel *model,
147 quint32 offset)
148{
149 auto &particleBuffer = *model->particleBuffer;
150 const quint32 particlesPerSlice = particleBuffer.particlesPerSlice();
151 const QVector2D oneOverSize = QVector2D(1.0f / particleBuffer.size().width(), 1.0f / particleBuffer.size().height());
152 shaderPipeline.setUniform(ubufData, name: "qt_oneOverParticleImageSize", data: &oneOverSize, size: 2 * sizeof(float));
153 shaderPipeline.setUniform(ubufData, name: "qt_countPerSlice", data: &particlesPerSlice, size: sizeof(quint32));
154 const QMatrix4x4 &particleMatrix = model->particleMatrix;
155 shaderPipeline.setUniform(ubufData, name: "qt_particleMatrix", data: &particleMatrix, size: 16 * sizeof(float));
156 shaderPipeline.setUniform(ubufData, name: "qt_particleIndexOffset", data: &offset, size: sizeof(quint32));
157}
158
159static void fillTargetBlend(QRhiGraphicsPipeline::TargetBlend &targetBlend, QSSGRenderParticles::BlendMode mode)
160{
161 switch (mode) {
162 case QSSGRenderParticles::BlendMode::Screen:
163 targetBlend.srcColor = QRhiGraphicsPipeline::SrcAlpha;
164 targetBlend.dstColor = QRhiGraphicsPipeline::One;
165 targetBlend.srcAlpha = QRhiGraphicsPipeline::One;
166 targetBlend.dstAlpha = QRhiGraphicsPipeline::One;
167 break;
168 case QSSGRenderParticles::BlendMode::Multiply:
169 targetBlend.srcColor = QRhiGraphicsPipeline::DstColor;
170 targetBlend.dstColor = QRhiGraphicsPipeline::Zero;
171 targetBlend.srcAlpha = QRhiGraphicsPipeline::One;
172 targetBlend.dstAlpha = QRhiGraphicsPipeline::One;
173 break;
174 default:
175 // Source over as default
176 targetBlend.srcColor = QRhiGraphicsPipeline::SrcAlpha;
177 targetBlend.dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
178 targetBlend.srcAlpha = QRhiGraphicsPipeline::One;
179 targetBlend.dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
180 break;
181
182 }
183}
184
185static void sortParticles(QByteArray &result, QList<QSSGRhiSortData> &sortData,
186 const QSSGParticleBuffer &buffer, const QSSGRenderParticles &particles,
187 const QVector3D &cameraDirection, bool animatedParticles)
188{
189 const QMatrix4x4 &invModelMatrix = particles.globalTransform.inverted();
190 QVector3D dir = invModelMatrix.map(point: cameraDirection);
191 QVector3D n = dir.normalized();
192 const auto segments = buffer.segments();
193 auto particleCount = buffer.particleCount();
194 const bool lineParticles = segments > 0;
195 if (lineParticles)
196 particleCount /= segments;
197 sortData.resize(size: particleCount);
198 sortData.fill(t: {});
199
200 const auto srcParticlePointer = [](int line, int segment, int sc, int ss, int pps, const char *source) -> const QSSGLineParticle * {
201 int pi = (line * sc + segment) / pps;
202 int i = (line * sc + segment) % pps;
203 const QSSGLineParticle *sp = reinterpret_cast<const QSSGLineParticle *>(source + pi * ss);
204 return sp + i;
205 };
206
207 // create sort data
208 {
209 const auto slices = buffer.sliceCount();
210 const auto ss = buffer.sliceStride();
211 const auto pps = buffer.particlesPerSlice();
212
213 QSSGRhiSortData *dst = sortData.data();
214 const char *source = buffer.pointer();
215 const char *begin = source;
216 int i = 0;
217 if (lineParticles) {
218 for (i = 0; i < particleCount; i++) {
219 QSSGRhiSortData lineData;
220 const QSSGLineParticle *lineBegin = srcParticlePointer(i, 0, segments, ss, pps, source);
221 lineData.indexOrOffset = i;
222 lineData.d = QVector3D::dotProduct(v1: lineBegin->position, v2: n);
223 for (int j = 1; j < buffer.segments(); j++) {
224 const QSSGLineParticle *p = srcParticlePointer(i, j, segments, ss, pps, source);
225 lineData.d = qMin(a: lineData.d, b: QVector3D::dotProduct(v1: p->position, v2: n));
226 }
227 *dst++ = lineData;
228 }
229 } else if (animatedParticles) {
230 for (int s = 0; s < slices; s++) {
231 const QSSGParticleAnimated *sp = reinterpret_cast<const QSSGParticleAnimated *>(source);
232 for (int p = 0; p < pps && i < particleCount; p++) {
233 *dst = { .d: QVector3D::dotProduct(v1: sp->position, v2: n), .indexOrOffset: int(reinterpret_cast<const char *>(sp) - begin)};
234 sp++;
235 dst++;
236 i++;
237 }
238 source += ss;
239 }
240 } else {
241 for (int s = 0; s < slices; s++) {
242 const QSSGParticleSimple *sp = reinterpret_cast<const QSSGParticleSimple *>(source);
243 for (int p = 0; p < pps && i < particleCount; p++) {
244 *dst = { .d: QVector3D::dotProduct(v1: sp->position, v2: n), .indexOrOffset: int(reinterpret_cast<const char *>(sp) - begin)};
245 sp++;
246 dst++;
247 i++;
248 }
249 source += ss;
250 }
251 }
252 }
253
254 // sort
255 result.resize(size: buffer.bufferSize());
256 std::sort(first: sortData.begin(), last: sortData.end(), comp: [](const QSSGRhiSortData &a, const QSSGRhiSortData &b){
257 return a.d > b.d;
258 });
259
260 auto copyParticles = [&](QByteArray &dst, const QList<QSSGRhiSortData> &data, const QSSGParticleBuffer &buffer) {
261 const auto slices = buffer.sliceCount();
262 const auto ss = buffer.sliceStride();
263 const auto pps = buffer.particlesPerSlice();
264 const QSSGRhiSortData *sdata = data.data();
265 char *dest = dst.data();
266 const char *source = buffer.pointer();
267 int i = 0;
268 if (lineParticles) {
269 int seg = 0;
270 for (int s = 0; s < slices; s++) {
271 QSSGLineParticle *dp = reinterpret_cast<QSSGLineParticle *>(dest);
272 for (int p = 0; p < pps && i < particleCount; p++) {
273 *dp = *srcParticlePointer(sdata->indexOrOffset, seg, segments, ss, pps, source);
274 dp++;
275 seg++;
276 if (seg == segments) {
277 sdata++;
278 i++;
279 seg = 0;
280 }
281 }
282 dest += ss;
283 }
284 } else if (animatedParticles) {
285 for (int s = 0; s < slices; s++) {
286 QSSGParticleAnimated *dp = reinterpret_cast<QSSGParticleAnimated *>(dest);
287 for (int p = 0; p < pps && i < particleCount; p++) {
288 *dp = *reinterpret_cast<const QSSGParticleAnimated *>(source + sdata->indexOrOffset);
289 dp++;
290 sdata++;
291 i++;
292 }
293 dest += ss;
294 }
295 } else {
296 for (int s = 0; s < slices; s++) {
297 QSSGParticleSimple *dp = reinterpret_cast<QSSGParticleSimple *>(dest);
298 for (int p = 0; p < pps && i < particleCount; p++) {
299 *dp = *reinterpret_cast<const QSSGParticleSimple *>(source + sdata->indexOrOffset);
300 dp++;
301 sdata++;
302 i++;
303 }
304 dest += ss;
305 }
306 }
307 };
308
309 // write result
310 copyParticles(result, sortData, buffer);
311}
312
313static QByteArray convertParticleData(QByteArray &dest, const QByteArray &data, bool convert)
314{
315 if (!convert)
316 return data;
317 int count = data.size() / 4;
318 if (dest.size() != count * 2)
319 dest.resize(size: 2 * count);
320 qFloatToFloat16(reinterpret_cast<qfloat16 *>(dest.data()), reinterpret_cast<const float *>(data.constData()), length: count);
321 return dest;
322}
323
324void QSSGParticleRenderer::rhiPrepareRenderable(QSSGRhiShaderPipeline &shaderPipeline,
325 QSSGPassKey passKey,
326 QSSGRhiContext *rhiCtx,
327 QSSGRhiGraphicsPipelineState *ps,
328 QSSGParticlesRenderable &renderable,
329 const QSSGLayerRenderData &inData,
330 QRhiRenderPassDescriptor *renderPassDescriptor,
331 int samples,
332 QSSGRenderCamera *camera,
333 QSSGRenderTextureCubeFace cubeFace,
334 QSSGReflectionMapEntry *entry)
335{
336 const void *node = &renderable.particles;
337 const bool needsConversion = !rhiCtx->rhi()->isTextureFormatSupported(format: QRhiTexture::RGBA32F);
338
339 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face: cubeFace);
340 QSSGRhiDrawCallData &dcd = rhiCtx->drawCallData(key: { .cid: passKey, .model: node, .entry: entry, .entryIdx: cubeFaceIdx });
341 shaderPipeline.ensureUniformBuffer(ubuf: &dcd.ubuf);
342
343 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
344 if (!camera)
345 updateUniformsForParticles(shaders&: shaderPipeline, rhiCtx, ubufData, renderable, inCamera&: *inData.camera);
346 else
347 updateUniformsForParticles(shaders&: shaderPipeline, rhiCtx, ubufData, renderable, inCamera&: *camera);
348 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
349
350 QSSGRhiParticleData &particleData = rhiCtx->particleData(particlesOrModel: &renderable.particles);
351 const QSSGParticleBuffer &particleBuffer = renderable.particles.m_particleBuffer;
352 int particleCount = particleBuffer.particleCount();
353 if (particleData.texture == nullptr || particleData.particleCount != particleCount) {
354 QSize size(particleBuffer.size());
355 if (!particleData.texture) {
356 particleData.texture = rhiCtx->rhi()->newTexture(format: needsConversion ? QRhiTexture::RGBA16F : QRhiTexture::RGBA32F, pixelSize: size);
357 particleData.texture->create();
358 } else {
359 particleData.texture->setPixelSize(size);
360 particleData.texture->create();
361 }
362 particleData.particleCount = particleCount;
363 }
364
365 bool sortingChanged = particleData.sorting != renderable.particles.m_depthSorting;
366 if (sortingChanged && !renderable.particles.m_depthSorting) {
367 particleData.sortData.clear();
368 particleData.sortedData.clear();
369 }
370 particleData.sorting = renderable.particles.m_depthSorting;
371
372 QByteArray uploadData;
373
374 if (renderable.particles.m_depthSorting) {
375 bool animatedParticles = renderable.particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::Animated;
376 if (!camera)
377 sortParticles(result&: particleData.sortedData, sortData&: particleData.sortData, buffer: particleBuffer, particles: renderable.particles, cameraDirection: inData.cameraData->direction, animatedParticles);
378 else
379 sortParticles(result&: particleData.sortedData, sortData&: particleData.sortData, buffer: particleBuffer, particles: renderable.particles, cameraDirection: camera->getScalingCorrectDirection(), animatedParticles);
380 uploadData = convertParticleData(dest&: particleData.convertData, data: particleData.sortedData, convert: needsConversion);
381 } else {
382 uploadData = convertParticleData(dest&: particleData.convertData, data: particleBuffer.data(), convert: needsConversion);
383 }
384
385 QRhiResourceUpdateBatch *rub = rhiCtx->rhi()->nextResourceUpdateBatch();
386 QRhiTextureSubresourceUploadDescription upload;
387 upload.setData(uploadData);
388 QRhiTextureUploadDescription uploadDesc(QRhiTextureUploadEntry(0, 0, upload));
389 rub->uploadTexture(tex: particleData.texture, desc: uploadDesc);
390 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates: rub);
391
392 ps->ia.topology = QRhiGraphicsPipeline::TriangleStrip;
393 ps->ia.inputLayout = QRhiVertexInputLayout();
394 ps->ia.inputs.clear();
395
396 ps->samples = samples;
397 ps->cullMode = QRhiGraphicsPipeline::None;
398 if (renderable.renderableFlags.hasTransparency())
399 fillTargetBlend(targetBlend&: ps->targetBlend, mode: renderable.particles.m_blendMode);
400 else
401 ps->targetBlend = QRhiGraphicsPipeline::TargetBlend();
402
403 QSSGRhiShaderResourceBindingList bindings;
404 bindings.addUniformBuffer(binding: 0, stage: VISIBILITY_ALL, buf: dcd.ubuf, offset: 0, size: shaderPipeline.ub0Size());
405
406 // Texture maps
407 // we only have one image
408 QSSGRenderableImage *renderableImage = renderable.firstImage;
409
410 int samplerBinding = shaderPipeline.bindingForTexture(name: "qt_sprite");
411 if (samplerBinding >= 0) {
412 QRhiTexture *texture = renderableImage ? renderableImage->m_texture.m_texture : nullptr;
413 if (samplerBinding >= 0 && texture) {
414 const bool mipmapped = texture->flags().testFlag(flag: QRhiTexture::MipMapped);
415 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: toRhi(op: renderableImage->m_imageNode.m_minFilterType),
416 .magFilter: toRhi(op: renderableImage->m_imageNode.m_magFilterType),
417 .mipmap: mipmapped ? toRhi(op: renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
418 .hTiling: toRhi(tiling: renderableImage->m_imageNode.m_horizontalTilingMode),
419 .vTiling: toRhi(tiling: renderableImage->m_imageNode.m_verticalTilingMode),
420 .zTiling: QRhiSampler::Repeat
421 });
422 bindings.addTexture(binding: samplerBinding, stage: QRhiShaderResourceBinding::FragmentStage, tex: texture, sampler);
423 } else {
424 QRhiResourceUpdateBatch *rub = rhiCtx->rhi()->nextResourceUpdateBatch();
425 QRhiTexture *texture = rhiCtx->dummyTexture(flags: {}, rub, size: QSize(4, 4), fillColor: Qt::white);
426 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates: rub);
427 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
428 .magFilter: QRhiSampler::Nearest,
429 .mipmap: QRhiSampler::None,
430 .hTiling: QRhiSampler::ClampToEdge,
431 .vTiling: QRhiSampler::ClampToEdge,
432 .zTiling: QRhiSampler::Repeat
433 });
434 bindings.addTexture(binding: samplerBinding, stage: QRhiShaderResourceBinding::FragmentStage, tex: texture, sampler);
435 }
436 }
437
438 samplerBinding = shaderPipeline.bindingForTexture(name: "qt_particleTexture");
439 if (samplerBinding >= 0) {
440 QRhiTexture *texture = particleData.texture;
441 if (samplerBinding >= 0 && texture) {
442 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
443 .magFilter: QRhiSampler::Nearest,
444 .mipmap: QRhiSampler::None,
445 .hTiling: QRhiSampler::ClampToEdge,
446 .vTiling: QRhiSampler::ClampToEdge,
447 .zTiling: QRhiSampler::Repeat
448 });
449 bindings.addTexture(binding: samplerBinding, stage: QRhiShaderResourceBinding::VertexStage, tex: texture, sampler);
450 }
451 }
452
453 samplerBinding = shaderPipeline.bindingForTexture(name: "qt_colorTable");
454 if (samplerBinding >= 0) {
455 bool hasTexture = false;
456 if (renderable.colorTable) {
457 QRhiTexture *texture = renderable.colorTable->m_texture.m_texture;
458 if (texture) {
459 hasTexture = true;
460 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
461 .magFilter: QRhiSampler::Nearest,
462 .mipmap: QRhiSampler::None,
463 .hTiling: QRhiSampler::ClampToEdge,
464 .vTiling: QRhiSampler::ClampToEdge,
465 .zTiling: QRhiSampler::Repeat
466 });
467 bindings.addTexture(binding: samplerBinding, stage: QRhiShaderResourceBinding::FragmentStage, tex: texture, sampler);
468 }
469 }
470
471 if (!hasTexture) {
472 QRhiResourceUpdateBatch *rub = rhiCtx->rhi()->nextResourceUpdateBatch();
473 QRhiTexture *texture = rhiCtx->dummyTexture(flags: {}, rub, size: QSize(4, 4), fillColor: Qt::white);
474 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates: rub);
475 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
476 .magFilter: QRhiSampler::Nearest,
477 .mipmap: QRhiSampler::None,
478 .hTiling: QRhiSampler::ClampToEdge,
479 .vTiling: QRhiSampler::ClampToEdge,
480 .zTiling: QRhiSampler::Repeat
481 });
482 bindings.addTexture(binding: samplerBinding, stage: QRhiShaderResourceBinding::FragmentStage, tex: texture, sampler);
483 }
484 }
485
486 // TODO: This is identical to other renderables. Make into a function?
487 QRhiShaderResourceBindings *&srb = dcd.srb;
488 bool srbChanged = false;
489 if (!srb || bindings != dcd.bindings) {
490 srb = rhiCtx->srb(bindings);
491 dcd.bindings = bindings;
492 srbChanged = true;
493 }
494
495 if (cubeFace == QSSGRenderTextureCubeFaceNone)
496 renderable.rhiRenderData.mainPass.srb = srb;
497 else
498 renderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx] = srb;
499
500 const QSSGGraphicsPipelineStateKey pipelineKey = QSSGGraphicsPipelineStateKey::create(state: *ps, rpDesc: renderPassDescriptor, srb);
501 if (dcd.pipeline
502 && !srbChanged
503 && dcd.renderTargetDescriptionHash == pipelineKey.extra.renderTargetDescriptionHash
504 && dcd.renderTargetDescription == pipelineKey.renderTargetDescription
505 && dcd.ps == *ps)
506 {
507 if (cubeFace == QSSGRenderTextureCubeFaceNone)
508 renderable.rhiRenderData.mainPass.pipeline = dcd.pipeline;
509 else
510 renderable.rhiRenderData.reflectionPass.pipeline = dcd.pipeline;
511 } else {
512 if (cubeFace == QSSGRenderTextureCubeFaceNone) {
513 renderable.rhiRenderData.mainPass.pipeline = rhiCtx->pipeline(key: pipelineKey,
514 rpDesc: renderPassDescriptor,
515 srb);
516 dcd.pipeline = renderable.rhiRenderData.mainPass.pipeline;
517 } else {
518 renderable.rhiRenderData.reflectionPass.pipeline = rhiCtx->pipeline(key: pipelineKey,
519 rpDesc: renderPassDescriptor,
520 srb);
521 dcd.pipeline = renderable.rhiRenderData.reflectionPass.pipeline;
522 }
523 dcd.renderTargetDescriptionHash = pipelineKey.extra.renderTargetDescriptionHash;
524 dcd.renderTargetDescription = pipelineKey.renderTargetDescription;
525 dcd.ps = *ps;
526 }
527}
528
529void QSSGParticleRenderer::prepareParticlesForModel(QSSGRhiShaderPipeline &shaderPipeline,
530 QSSGRhiContext *rhiCtx,
531 QSSGRhiShaderResourceBindingList &bindings,
532 const QSSGRenderModel *model)
533{
534 QSSGRhiParticleData &particleData = rhiCtx->particleData(particlesOrModel: model);
535 const QSSGParticleBuffer &particleBuffer = *model->particleBuffer;
536 int particleCount = particleBuffer.particleCount();
537 bool update = particleBuffer.serial() != particleData.serial;
538 const bool needsConversion = !rhiCtx->rhi()->isTextureFormatSupported(format: QRhiTexture::RGBA32F);
539 if (particleData.texture == nullptr || particleData.particleCount != particleCount) {
540 QSize size(particleBuffer.size());
541 if (!particleData.texture) {
542 particleData.texture = rhiCtx->rhi()->newTexture(format: needsConversion ? QRhiTexture::RGBA16F : QRhiTexture::RGBA32F, pixelSize: size);
543 particleData.texture->create();
544 } else {
545 particleData.texture->setPixelSize(size);
546 particleData.texture->create();
547 }
548 particleData.particleCount = particleCount;
549 update = true;
550 }
551
552 if (update) {
553 QRhiResourceUpdateBatch *rub = rhiCtx->rhi()->nextResourceUpdateBatch();
554 QRhiTextureSubresourceUploadDescription upload;
555 upload.setData(convertParticleData(dest&: particleData.convertData, data: particleBuffer.data(), convert: needsConversion));
556 QRhiTextureUploadDescription uploadDesc(QRhiTextureUploadEntry(0, 0, upload));
557 rub->uploadTexture(tex: particleData.texture, desc: uploadDesc);
558 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates: rub);
559 }
560 particleData.serial = particleBuffer.serial();
561 int samplerBinding = shaderPipeline.bindingForTexture(name: "qt_particleTexture");
562 if (samplerBinding >= 0) {
563 QRhiTexture *texture = particleData.texture;
564 if (samplerBinding >= 0 && texture) {
565 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
566 .magFilter: QRhiSampler::Nearest,
567 .mipmap: QRhiSampler::None,
568 .hTiling: QRhiSampler::ClampToEdge,
569 .vTiling: QRhiSampler::ClampToEdge,
570 .zTiling: QRhiSampler::Repeat
571 });
572 bindings.addTexture(binding: samplerBinding, stage: QRhiShaderResourceBinding::VertexStage, tex: texture, sampler);
573 }
574 }
575}
576
577void QSSGParticleRenderer::rhiRenderRenderable(QSSGRhiContext *rhiCtx,
578 QSSGParticlesRenderable &renderable,
579 bool *needsSetViewport,
580 QSSGRenderTextureCubeFace cubeFace,
581 const QSSGRhiGraphicsPipelineState &state)
582{
583 QRhiGraphicsPipeline *ps = renderable.rhiRenderData.mainPass.pipeline;
584 QRhiShaderResourceBindings *srb = renderable.rhiRenderData.mainPass.srb;
585
586 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
587 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face: cubeFace);
588 ps = renderable.rhiRenderData.reflectionPass.pipeline;
589 srb = renderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx];
590 }
591
592 if (!ps || !srb)
593 return;
594
595 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
596
597 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
598 // QRhi optimizes out unnecessary binding of the same pipline
599 cb->setGraphicsPipeline(ps);
600 cb->setVertexInput(startBinding: 0, bindingCount: 0, bindings: nullptr);
601 cb->setShaderResources(srb);
602
603 if (needsSetViewport && *needsSetViewport) {
604 cb->setViewport(state.viewport);
605 *needsSetViewport = false;
606 }
607 if (renderable.particles.m_featureLevel >= QSSGRenderParticles::FeatureLevel::Line) {
608 // draw triangle strip with 2 * segmentCount vertices N times
609 int S = renderable.particles.m_particleBuffer.segments();
610 int N = renderable.particles.m_particleBuffer.particleCount() / S;
611 cb->draw(vertexCount: 2 * S, instanceCount: N);
612 QSSGRHICTX_STAT(rhiCtx, draw(2 * S, N));
613 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DRenderCall, (2 * S | quint64(N) << 32), renderable.particles.profilingId);
614 } else {
615 // draw triangle strip with 2 triangles N times
616 cb->draw(vertexCount: 4, instanceCount: renderable.particles.m_particleBuffer.particleCount());
617 QSSGRHICTX_STAT(rhiCtx, draw(4, renderable.particles.m_particleBuffer.particleCount()));
618 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DRenderCall, (4 | quint64(renderable.particles.m_particleBuffer.particleCount()) << 32), renderable.particles.profilingId);
619 }
620}
621
622QT_END_NAMESPACE
623

source code of qtquick3d/src/runtimerender/qssgrhiparticles.cpp