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

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