| 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 | |