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 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | static const QRhiShaderResourceBinding::StageFlags VISIBILITY_ALL = |
18 | QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage; |
19 | |
20 | struct 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 | |
37 | void 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 | |
155 | void 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 | |
170 | static 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 | |
196 | static 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 | |
324 | static 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 | |
335 | void 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 | |
548 | void 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 | |
597 | void 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 | |
642 | QT_END_NAMESPACE |
643 | |