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 | 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 | |
144 | void 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 | |
159 | static 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 | |
185 | static 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 | |
313 | static 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 | |
324 | void 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 | |
529 | void 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 | |
577 | void 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 | |
622 | QT_END_NAMESPACE |
623 | |