5#include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
6#include <QtQuick3DRuntimeRender/private/qssgrhiquadrenderer_p.h>
7#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
9#include <QtQuick3DUtils/private/qssgutils_p.h>
11#ifdef QT_QUICK3D_HAS_LIGHTMAPPER
12#include <QtCore/qfuture.h>
13#include <QtCore/qfileinfo.h>
14#include <QtConcurrent/qtconcurrentrun.h>
15#include <QRandomGenerator>
17#include <embree3/rtcore.h>
36#ifdef QT_QUICK3D_HAS_LIGHTMAPPER
38struct QSSGLightmapperPrivate
43 QVector<QSSGBakedLightingModel> bakedLightingModels;
50 unsigned int geomId = RTC_INVALID_GEOMETRY_ID;
59 float normalStrength = 0.0f;
62 using SubMeshInfoList = QVector<SubMeshInfo>;
63 QVector<SubMeshInfoList> subMeshInfos;
71 quint32 positionOffset = UINT_MAX;
73 quint32 normalOffset = UINT_MAX;
77 quint32 lightmapUVOffset = UINT_MAX;
79 quint32 tangentOffset = UINT_MAX;
81 quint32 binormalOffset = UINT_MAX;
85 QVector<DrawInfo> drawInfos;
98 float cosInnerConeAngle;
99 float constantAttenuation;
100 float linearAttenuation;
101 float quadraticAttenuation;
103 QVector<Light> lights;
105 RTCDevice rdev =
nullptr;
106 RTCScene rscene =
nullptr;
108 struct LightmapEntry {
114 bool isValid()
const {
return !worldPos.
isNull() && !normal.
isNull(); }
119 Lightmap(
const QSize &pixelSize) : pixelSize(pixelSize) {
120 entries.resize(pixelSize.
width() * pixelSize.
height());
123 QVector<LightmapEntry> entries;
125 bool hasBaseColorTransparency =
false;
127 QVector<Lightmap> lightmaps;
128 QVector<int> geomLightmapMap;
129 QVector<float> subMeshOpacityMap;
131 inline const LightmapEntry &texelForLightmapUV(
unsigned int geomId,
float u,
float v)
const
134 const Lightmap &hitLightmap(lightmaps[geomLightmapMap[geomId]]);
135 u =
qBound(0.0f, u, 1.0f);
139 const int w = hitLightmap.pixelSize.width();
140 const int h = hitLightmap.pixelSize.height();
141 const int x =
qBound(0,
int(
w * u),
w - 1);
144 return hitLightmap.entries[
x +
y *
w];
147 bool commitGeometry();
148 bool prepareLightmaps();
149 void computeDirectLight();
150 void computeIndirectLight();
152 bool storeLightmaps();
156static const int LM_SEAM_BLEND_ITER_COUNT = 4;
159 :
d(new QSSGLightmapperPrivate)
165 _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
166 _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
176 _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_OFF);
177 _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_OFF);
183 d->bakedLightingModels.clear();
184 d->subMeshInfos.clear();
185 d->drawInfos.clear();
187 d->lightmaps.clear();
188 d->geomLightmapMap.clear();
189 d->subMeshOpacityMap.clear();
192 rtcReleaseScene(
d->rscene);
196 rtcReleaseDevice(
d->rdev);
200 d->bakingControl.cancelled =
false;
205 d->options = options;
210 d->outputCallback = callback;
215 d->bakedLightingModels.append(
model);
216 return d->bakedLightingModels.size() - 1;
219static void embreeErrFunc(
void *, RTCError
error,
const char *
str)
224static const unsigned int NORMAL_SLOT = 0;
225static const unsigned int LIGHTMAP_UV_SLOT = 1;
227static void embreeFilterFunc(
const RTCFilterFunctionNArguments *
args)
229 RTCHit *hit =
reinterpret_cast<RTCHit *
>(
args->hit);
230 QSSGLightmapperPrivate *
d =
static_cast<QSSGLightmapperPrivate *
>(
args->geometryUserPtr);
231 RTCGeometry geom = rtcGetGeometry(
d->rscene, hit->geomID);
234 rtcInterpolate0(geom, hit->primID, hit->u, hit->v, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, LIGHTMAP_UV_SLOT, &hit->u, 2);
236 const float opacity =
d->subMeshOpacityMap[hit->geomID];
237 if (opacity < 1.0f || d->lightmaps[
d->geomLightmapMap[hit->geomID]].hasBaseColorTransparency) {
238 const QSSGLightmapperPrivate::LightmapEntry &texel(
d->texelForLightmapUV(hit->geomID, hit->u, hit->v));
243 const float alpha = opacity * texel.baseColor.w();
249 if (alpha < d->options.opacityThreshold)
254bool QSSGLightmapperPrivate::commitGeometry()
256 if (bakedLightingModels.isEmpty()) {
263 geomPrepTimer.
start();
265 const auto &bufferManager(
renderer->contextInterface()->bufferManager());
267 const int bakedLightingModelCount = bakedLightingModels.size();
268 subMeshInfos.resize(bakedLightingModelCount);
269 drawInfos.resize(bakedLightingModelCount);
271 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
273 if (lm.renderables.isEmpty()) {
275 arg(lm.model->lightmapKey));
278 if (lm.model->skin || lm.model->skeleton) {
280 arg(lm.model->lightmapKey));
284 subMeshInfos[lmIdx].reserve(lm.renderables.size());
286 Q_ASSERT(
handle.obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset
287 ||
handle.obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset);
290 info.offset = renderableObj->subset.offset;
291 info.count = renderableObj->subset.count;
292 info.opacity = renderableObj->opacity;
293 if (
handle.obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
295 info.baseColor = defMat->color;
296 info.emissiveFactor = defMat->emissiveColor;
297 if (defMat->colorMap) {
298 info.baseColorNode = defMat->colorMap;
302 if (defMat->emissiveMap) {
303 info.emissiveNode = defMat->emissiveMap;
307 if (defMat->normalMap) {
308 info.normalMapNode = defMat->normalMap;
311 info.normalStrength = defMat->bumpAmount;
317 subMeshInfos[lmIdx].append(
info);
323 worldTransform = renderableObj->globalTransform;
324 normalMatrix = renderableObj->modelContext.normalMatrix;
326 DrawInfo &drawInfo(drawInfos[lmIdx]);
329 if (lm.model->geometry)
330 mesh = bufferManager->loadMeshData(lm.model->geometry);
332 mesh = bufferManager->loadMeshData(lm.model->meshPath);
336 arg(lm.model->lightmapKey));
345 arg(lm.model->lightmapKey));
349 arg(lm.model->lightmapKey).
350 arg(unwrapTimer.elapsed()));
352 if (lm.model->hasLightmap())
353 drawInfo.meshWithLightmapUV = mesh;
358 drawInfo.lightmapSize = mesh.
subsets().first().lightmapSizeHint;
359 if (drawInfo.lightmapSize.isEmpty()) {
361 arg(lm.model->lightmapKey));
362 drawInfo.lightmapSize =
QSize(1024, 1024);
369 if (drawInfo.vertexData.isEmpty()) {
373 if (drawInfo.indexData.isEmpty()) {
379 case QSSGMesh::Mesh::ComponentType::UnsignedInt16:
382 case QSSGMesh::Mesh::ComponentType::UnsignedInt32:
388 arg(lm.model->lightmapKey));
394 drawInfo.positionOffset = vbe.offset;
397 drawInfo.normalOffset = vbe.offset;
400 drawInfo.uvOffset = vbe.offset;
403 drawInfo.lightmapUVOffset = vbe.offset;
406 drawInfo.tangentOffset = vbe.offset;
409 drawInfo.binormalOffset = vbe.offset;
414 if (!(drawInfo.positionOffset != UINT_MAX && drawInfo.normalOffset != UINT_MAX)) {
416 arg(lm.model->lightmapKey));
425 arg(lm.model->lightmapKey));
429 if (drawInfo.lightmapUVOffset == UINT_MAX) {
431 arg(lm.model->lightmapKey));
437 arg(lm.model->lightmapKey));
442 if (drawInfo.uvOffset != UINT_MAX) {
445 arg(lm.model->lightmapKey));
450 if (drawInfo.tangentOffset != UINT_MAX) {
453 arg(lm.model->lightmapKey));
457 if (drawInfo.binormalOffset != UINT_MAX) {
460 arg(lm.model->lightmapKey));
468 const quint16 *
s =
reinterpret_cast<const quint16 *
>(drawInfo.indexData.constData());
469 size_t sz = drawInfo.indexData.size() / 2;
473 drawInfo.indexData = newIndexData;
478 char *vertexBase = drawInfo.vertexData.data();
479 const qsizetype sz = drawInfo.vertexData.size();
481 char *posPtr = vertexBase +
offset + drawInfo.positionOffset;
482 float *fPosPtr =
reinterpret_cast<float *
>(posPtr);
484 char *normalPtr = vertexBase +
offset + drawInfo.normalOffset;
485 float *fNormalPtr =
reinterpret_cast<float *
>(normalPtr);
486 QVector3D normal(fNormalPtr[0], fNormalPtr[1], fNormalPtr[2]);
489 *fPosPtr++ =
pos.
x();
490 *fPosPtr++ =
pos.y();
491 *fPosPtr++ =
pos.z();
492 *fNormalPtr++ = normal.
x();
493 *fNormalPtr++ = normal.
y();
494 *fNormalPtr++ = normal.
z();
504 if (!sl.light->m_bakingEnabled)
508 light.indirectOnly = !sl.light->m_fullyBaked;
509 light.direction = sl.direction;
511 const float brightness = sl.light->m_brightness;
512 light.color =
QVector3D(sl.light->m_diffuseColor.x() * brightness,
513 sl.light->m_diffuseColor.y() * brightness,
514 sl.light->m_diffuseColor.z() * brightness);
516 if (sl.light->type == QSSGRenderLight::Type::PointLight
517 || sl.light->type == QSSGRenderLight::Type::SpotLight)
519 light.worldPos = sl.light->getGlobalPos();
520 if (sl.light->type == QSSGRenderLight::Type::SpotLight) {
521 light.type = Light::Spot;
524 qMin(sl.light->m_innerConeAngle, sl.light->m_coneAngle)));
526 light.type = Light::Point;
532 light.type = Light::Directional;
535 lights.append(light);
540 rdev = rtcNewDevice(
nullptr);
546 rtcSetDeviceErrorFunction(rdev, embreeErrFunc,
nullptr);
548 rscene = rtcNewScene(rdev);
550 unsigned int geomId = 1;
552 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
559 if (!lm.model->castsShadows)
562 const DrawInfo &drawInfo(drawInfos[lmIdx]);
563 const char *vbase = drawInfo.vertexData.constData();
564 const quint32 *ibase =
reinterpret_cast<const quint32 *
>(drawInfo.indexData.constData());
566 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx]) {
567 RTCGeometry geom = rtcNewGeometry(rdev, RTC_GEOMETRY_TYPE_TRIANGLE);
568 rtcSetGeometryVertexAttributeCount(geom, 2);
569 quint32 *ip =
static_cast<quint32 *
>(rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, 3 *
sizeof(uint32_t), subMeshInfo.count / 3));
570 for (
quint32 i = 0;
i < subMeshInfo.count; ++
i)
572 float *vp =
static_cast<float *
>(rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, 3 *
sizeof(
float), subMeshInfo.count));
573 for (
quint32 i = 0;
i < subMeshInfo.count; ++
i) {
574 const quint32 idx = *(ibase + subMeshInfo.offset +
i);
575 const float *
src =
reinterpret_cast<const float *
>(vbase + idx * drawInfo.vertexStride + drawInfo.positionOffset);
580 vp =
static_cast<float *
>(rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, NORMAL_SLOT, RTC_FORMAT_FLOAT3, 3 *
sizeof(
float), subMeshInfo.count));
581 for (
quint32 i = 0;
i < subMeshInfo.count; ++
i) {
582 const quint32 idx = *(ibase + subMeshInfo.offset +
i);
583 const float *
src =
reinterpret_cast<const float *
>(vbase + idx * drawInfo.vertexStride + drawInfo.normalOffset);
588 vp =
static_cast<float *
>(rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, LIGHTMAP_UV_SLOT, RTC_FORMAT_FLOAT2, 2 *
sizeof(
float), subMeshInfo.count));
589 for (
quint32 i = 0;
i < subMeshInfo.count; ++
i) {
590 const quint32 idx = *(ibase + subMeshInfo.offset +
i);
591 const float *
src =
reinterpret_cast<const float *
>(vbase + idx * drawInfo.vertexStride + drawInfo.lightmapUVOffset);
595 rtcCommitGeometry(geom);
596 rtcSetGeometryIntersectFilterFunction(geom, embreeFilterFunc);
597 rtcSetGeometryUserData(geom,
this);
598 rtcAttachGeometryByID(rscene, geom, geomId);
599 subMeshInfo.geomId = geomId++;
600 rtcReleaseGeometry(geom);
604 rtcCommitScene(rscene);
607 rtcGetSceneBounds(rscene, &bounds);
608 QVector3D lowerBound(bounds.lower_x, bounds.lower_y, bounds.lower_z);
609 QVector3D upperBound(bounds.upper_x, bounds.upper_y, bounds.upper_z);
610 qDebug() <<
"[lm] Bounds in world space for raytracing scene:" << lowerBound << upperBound;
612 const unsigned int geomIdBasedMapSize = geomId;
615 geomLightmapMap.fill(-1, geomIdBasedMapSize);
616 subMeshOpacityMap.fill(0.0f, geomIdBasedMapSize);
618 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
620 if (!lm.model->castsShadows)
622 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx])
623 subMeshOpacityMap[subMeshInfo.geomId] = subMeshInfo.opacity;
630bool QSSGLightmapperPrivate::prepareLightmaps()
648 const int bakedLightingModelCount = bakedLightingModels.size();
649 Q_ASSERT(drawInfos.size() == bakedLightingModelCount);
650 Q_ASSERT(subMeshInfos.size() == bakedLightingModelCount);
652 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
654 rasterizeTimer.
start();
658 const DrawInfo &bakeModelDrawInfo(drawInfos[lmIdx]);
659 const bool hasUV0 = bakeModelDrawInfo.uvOffset != UINT_MAX;
660 const bool hasTangentAndBinormal = bakeModelDrawInfo.tangentOffset != UINT_MAX
661 && bakeModelDrawInfo.binormalOffset != UINT_MAX;
662 const QSize outputSize = bakeModelDrawInfo.lightmapSize;
668 if (!vbuf->create()) {
673 if (!ibuf->create()) {
679 resUpd->uploadStaticBuffer(ibuf.get(), bakeModelDrawInfo.indexData.constData());
681 cb->resourceUpdate(resUpd);
685 if (!positionData->create()) {
691 if (!normalData->create()) {
697 if (!baseColorData->create()) {
703 if (!emissionData->create()) {
720 rtDesc.setDepthStencilBuffer(ds.get());
723 std::unique_ptr<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
724 rt->setRenderPassDescriptor(rpDesc.get());
731 const int subMeshCount = subMeshInfos[lmIdx].size();
733 const int totalUbufSize = alignedUbufSize * subMeshCount;
735 if (!ubuf->create()) {
747 char *ubufData = ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
748 for (
int subMeshIdx = 0; subMeshIdx != subMeshCount; ++subMeshIdx) {
749 const SubMeshInfo &subMeshInfo(subMeshInfos[lmIdx][subMeshIdx]);
750 qint32 hasBaseColorMap = subMeshInfo.baseColorMap ? 1 : 0;
751 qint32 hasEmissiveMap = subMeshInfo.emissiveMap ? 1 : 0;
752 qint32 hasNormalMap = subMeshInfo.normalMap ? 1 : 0;
753 char *
p = ubufData + subMeshIdx * alignedUbufSize;
754 memcpy(
p, &subMeshInfo.baseColor, 4 *
sizeof(
float));
755 memcpy(
p + 16, &subMeshInfo.emissiveFactor, 3 *
sizeof(
float));
756 memcpy(
p + 28, &flipY,
sizeof(
qint32));
757 memcpy(
p + 32, &hasBaseColorMap,
sizeof(
qint32));
758 memcpy(
p + 36, &hasEmissiveMap,
sizeof(
qint32));
759 memcpy(
p + 40, &hasNormalMap,
sizeof(
qint32));
760 memcpy(
p + 44, &subMeshInfo.normalStrength,
sizeof(
float));
762 ubuf->endFullDynamicBufferUpdateForCurrentFrame();
773 ps->
setShaderStages(shaderPipeline->cbeginStages(), shaderPipeline->cendStages());
782 QVector<QRhiGraphicsPipeline *> ps;
785 QVector<QRhiGraphicsPipeline *> psLine;
787 for (
int subMeshIdx = 0; subMeshIdx != subMeshCount; ++subMeshIdx) {
788 const SubMeshInfo &subMeshInfo(subMeshInfos[lmIdx][subMeshIdx]);
789 QVarLengthArray<QRhiVertexInputAttribute, 6> vertexAttrs;
800 if (hasTangentAndBinormal)
804 const auto &shaderCache =
renderer->contextInterface()->shaderCache();
805 const auto &lmUvRastShaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiLightmapUVRasterizationShader(shaderVariant);
806 if (!lmUvRastShaderPipeline) {
813 if (hasTangentAndBinormal) {
819 inputLayout.
setAttributes(vertexAttrs.cbegin(), vertexAttrs.cend());
823 subMeshIdx * alignedUbufSize,
UBUF_SIZE);
826 if (subMeshInfo.baseColorMap) {
839 if (subMeshInfo.emissiveMap) {
852 if (subMeshInfo.normalMap) {
868 if (!pipeline->
create()) {
877 pipeline = setupPipeline(lmUvRastShaderPipeline.get(), srb, inputLayout);
879 if (!pipeline->
create()) {
887 psLine.append(pipeline);
892 bool hadViewport =
false;
895 for (
int subMeshIdx = 0; subMeshIdx != subMeshCount; ++subMeshIdx) {
896 const SubMeshInfo &subMeshInfo(subMeshInfos[lmIdx][subMeshIdx]);
897 cb->setGraphicsPipeline(ps[subMeshIdx]);
902 cb->setShaderResources();
904 cb->drawIndexed(subMeshInfo.count, 1, subMeshInfo.offset);
905 cb->setGraphicsPipeline(psLine[subMeshIdx]);
906 cb->setShaderResources();
907 cb->drawIndexed(subMeshInfo.count, 1, subMeshInfo.offset);
915 resUpd->readBackTexture({ positionData.get() }, &posReadResult);
916 resUpd->readBackTexture({ normalData.get() }, &normalReadResult);
917 resUpd->readBackTexture({ baseColorData.get() }, &baseColorReadResult);
918 resUpd->readBackTexture({ emissionData.get() }, &emissionReadResult);
927 Lightmap lightmap(outputSize);
931 if (posReadResult.data.size() < lightmap.entries.size() * 16) {
935 if (normalReadResult.data.size() < lightmap.entries.size() * 16) {
939 if (baseColorReadResult.data.size() < lightmap.entries.size() * 16) {
943 if (emissionReadResult.data.size() < lightmap.entries.size() * 16) {
947 const float *lmPosPtr =
reinterpret_cast<const float *
>(posReadResult.data.constData());
948 const float *lmNormPtr =
reinterpret_cast<const float *
>(normalReadResult.data.constData());
949 const float *lmBaseColorPtr =
reinterpret_cast<const float *
>(baseColorReadResult.data.constData());
950 const float *lmEmissionPtr =
reinterpret_cast<const float *
>(emissionReadResult.data.constData());
951 int unusedEntries = 0;
952 for (
qsizetype i = 0, ie = lightmap.entries.size();
i != ie; ++
i) {
953 LightmapEntry &lmPix(lightmap.entries[
i]);
955 float x = *lmPosPtr++;
956 float y = *lmPosPtr++;
957 float z = *lmPosPtr++;
967 float r = *lmBaseColorPtr++;
968 float g = *lmBaseColorPtr++;
969 float b = *lmBaseColorPtr++;
970 float a = *lmBaseColorPtr++;
973 lightmap.hasBaseColorTransparency =
true;
975 r = *lmEmissionPtr++;
976 g = *lmEmissionPtr++;
977 b = *lmEmissionPtr++;
981 if (!lmPix.isValid())
986 arg(lightmap.entries.size() - unusedEntries).
987 arg(lightmap.entries.size()).
988 arg(lm.model->lightmapKey).
990 arg(rasterizeTimer.elapsed()));
991 lightmaps.append(lightmap);
993 for (
const SubMeshInfo &subMeshInfo :
std::as_const(subMeshInfos[lmIdx])) {
994 if (!lm.model->castsShadows)
996 geomLightmapMap[subMeshInfo.geomId] = lightmaps.size() - 1;
1006 RayHit(
const QVector3D &
org,
const QVector3D &
dir,
float tnear = 0.0f,
float tfar = std::numeric_limits<float>::infinity()) {
1007 rayhit.ray.org_x =
org.x();
1008 rayhit.ray.org_y =
org.y();
1009 rayhit.ray.org_z =
org.z();
1010 rayhit.ray.dir_x =
dir.x();
1011 rayhit.ray.dir_y =
dir.y();
1012 rayhit.ray.dir_z =
dir.z();
1013 rayhit.ray.tnear = tnear;
1014 rayhit.ray.tfar = tfar;
1015 rayhit.hit.u = 0.0f;
1016 rayhit.hit.v = 0.0f;
1017 rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID;
1022 bool intersect(RTCScene
scene)
1024 RTCIntersectContext
ctx;
1025 rtcInitIntersectContext(&
ctx);
1026 rtcIntersect1(
scene, &
ctx, &rayhit);
1027 return rayhit.hit.geomID != RTC_INVALID_GEOMETRY_ID;
1033 return QVector3D(
v.x() < 1.0f ? -1.0f : 1.0f,
1034 v.
y() < 1.0f ? -1.0f : 1.0f,
1035 v.
z() < 1.0f ? -1.0f : 1.0f);
1045void QSSGLightmapperPrivate::computeDirectLight()
1049 fullDirectLightTimer.
start();
1051 const int bakedLightingModelCount = bakedLightingModels.size();
1052 Q_ASSERT(lightmaps.size() == bakedLightingModelCount);
1054 QVector<QFuture<void>> futures;
1056 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
1058 Lightmap &lightmap(lightmaps[lmIdx]);
1063 directLightTimer.
start();
1065 const int lightCount = lights.size();
1066 for (LightmapEntry &lmPix : lightmap.entries) {
1067 if (!lmPix.isValid())
1071 if (options.useAdaptiveBias)
1072 worldPos += vectorSign(lmPix.normal) * vectorAbs(worldPos * 0.0000002f);
1075 for (
int i = 0;
i < lightCount; ++
i) {
1076 const Light &light(lights[
i]);
1079 float dist = std::numeric_limits<float>::infinity();
1080 float attenuation = 1.0f;
1081 if (light.type == Light::Directional) {
1082 lightWorldPos = worldPos - light.direction;
1084 lightWorldPos = light.worldPos;
1086 attenuation = 1.0f / (light.constantAttenuation
1087 + light.linearAttenuation *
dist
1088 + light.quadraticAttenuation *
dist *
dist);
1089 if (light.type == Light::Spot) {
1091 light.direction.normalized());
1092 if (spotAngle > light.cosConeAngle) {
1094 const float edge0 = light.cosConeAngle;
1095 const float edge1 = light.cosInnerConeAngle;
1096 const float x = spotAngle;
1097 const float t =
qBound(0.0f, (
x - edge0) / (edge1 - edge0), 1.0f);
1098 const float spotFactor =
t *
t * (3.0f - 2.0f *
t);
1099 attenuation *= spotFactor;
1113 RayHit ray(worldPos, L, options.bias,
dist);
1114 const bool lightReachable = !ray.intersect(rscene);
1115 if (lightReachable) {
1117 lmPix.directLight += light.color * energy;
1119 if (!light.indirectOnly)
1120 lmPix.allLight += light.color * energy;
1126 arg(lm.model->lightmapKey).
1127 arg(directLightTimer.elapsed()));
1131 for (QFuture<void> &
future : futures)
1135 arg(fullDirectLightTimer.elapsed()));
1139static inline float uniformRand()
1145 return float(
state) / float(UINT32_MAX);
1148static inline QVector3D cosWeightedHemisphereSample()
1150 const float r1 = uniformRand();
1151 const float r2 = uniformRand() * 2.0f * float(
M_PI);
1152 const float sqr1 = std::sqrt(
r1);
1153 const float sqr1m = std::sqrt(1.0f -
r1);
1154 return QVector3D(sqr1 * std::cos(
r2), sqr1 * std::sin(
r2), sqr1m);
1157void QSSGLightmapperPrivate::computeIndirectLight()
1161 fullIndirectLightTimer.
start();
1163 const int bakedLightingModelCount = bakedLightingModels.size();
1165 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
1167 if (!bakedLightingModels[lmIdx].
model->hasLightmap())
1171 Lightmap &lightmap(lightmaps[lmIdx]);
1174 arg(lm.model->lightmapKey).
1175 arg(lightmap.entries.size()));
1177 indirectLightTimer.
start();
1184 int wgSizePerGroup =
qMax(1, options.indirectLightWorkgroupSize);
1185 int wgCount = options.indirectLightSamples / wgSizePerGroup;
1186 if (options.indirectLightSamples % wgSizePerGroup)
1189 QVector<QFuture<QVector3D>> wg(wgCount);
1192 arg(lm.model->lightmapKey));
1194 arg(options.indirectLightSamples).
1195 arg(wgSizePerGroup).
1196 arg(options.indirectLightBounces).
1197 arg(options.indirectLightFactor));
1198 for (LightmapEntry &lmPix : lightmap.entries) {
1199 if (!lmPix.isValid())
1202 for (
int wgIdx = 0; wgIdx < wgCount; ++wgIdx) {
1203 const int beginIdx = wgIdx * wgSizePerGroup;
1204 const int endIdx =
qMin(beginIdx + wgSizePerGroup, options.indirectLightSamples);
1208 for (
int sampleIdx = beginIdx; sampleIdx < endIdx; ++sampleIdx) {
1214 for (
int bounce = 0; bounce < options.indirectLightBounces; ++bounce) {
1215 if (options.useAdaptiveBias)
1219 const QVector3D sample = cosWeightedHemisphereSample();
1228 tangent.
x() * sample.
x() + bitangent.x() * sample.
y() + normal.
x() * sample.
z(),
1229 tangent.
y() * sample.
x() + bitangent.y() * sample.
y() + normal.
y() * sample.
z(),
1230 tangent.
z() * sample.
x() + bitangent.z() * sample.
y() + normal.
z() * sample.
z());
1235 const float pdf = NdotL / float(
M_PI);
1241 if (!ray.intersect(rscene))
1245 const LightmapEntry &hitEntry = texelForLightmapUV(ray.rayhit.hit.geomID,
1255 const QVector3D brdf = hitEntry.baseColor.toVector3D() / float(
M_PI);
1258 sampleResult += throughput * hitEntry.emission;
1259 throughput *= brdf * NdotL / pdf;
1260 sampleResult += throughput * hitEntry.directLight;
1264 const float p =
qMax(
qMax(throughput.x(), throughput.y()), throughput.z());
1265 if (
p < uniformRand())
1273 normal = hitEntry.
normal;
1276 wgResult += sampleResult;
1283 for (
const auto &
future : wg)
1286 lmPix.allLight += totalIndirect * options.indirectLightFactor / options.indirectLightSamples;
1289 if (texelsDone % 10000 == 0)
1291 arg(lightmap.entries.size() - texelsDone));
1293 if (bakingControl.cancelled)
1297 arg(lm.model->lightmapKey).
1298 arg(indirectLightTimer.elapsed()));
1302 arg(fullIndirectLightTimer.elapsed()));
1306 std::array<QVector3D, 2>
pos;
1307 std::array<QVector3D, 2> normal;
1325 std::array<QVector2D, 2> uv;
1330 std::array<std::array<QVector2D, 2>, 2> uv;
1335 if (
a.x() ==
b.x()) {
1337 return a.z() <
b.z();
1339 return a.y() <
b.y();
1341 return a.x() <
b.x();
1344static inline float floatSign(
float f)
1346 return f > 0.0f ? 1.0f : (
f < 0.0f ? -1.0f : 0.0f);
1351 return QVector2D(std::floor(
v.x()), std::floor(
v.y()));
1358 const float lengthSquared =
n.lengthSquared();
1360 const float d = (
n.x() *
p.x() +
n.y() *
p.y()) / lengthSquared;
1369 const QSize &lightmapPixelSize)
1371 const QVector2D size(lightmapPixelSize.width(), lightmapPixelSize.height());
1374 const float lineLength =
line[0].distanceToPoint(
line[1]);
1382 const QVector2D tStep(1.0f / std::abs(
dir.x()), 1.0f / std::abs(
dir.y()));
1386 if (pixelStep.x() == 1.0f)
1387 nextT.setX(1.0f - nextT.x());
1388 if (pixelStep.y() == 1.0f)
1389 nextT.setY(1.0f - nextT.y());
1392 nextT.setX(nextT.x() / std::abs(
dir.x()));
1394 nextT.setX(std::numeric_limits<float>::max());
1397 nextT.setY(nextT.y() / std::abs(
dir.y()));
1399 nextT.setY(std::numeric_limits<float>::max());
1401 float *fpW =
reinterpret_cast<float *
>(writeBuf.data());
1402 const float *fpR =
reinterpret_cast<const float *
>(readBuf.constData());
1406 while (startPixel.distanceToPoint(pixel) < lineLength + 1.0f) {
1408 const float t =
line[0].distanceToPoint(point) / lineLength;
1409 const QVector2D uvInterp = uvFrom * (1.0 -
t) + uvTo *
t;
1412 const int sampOfs = (int(sampledPixel.x()) + int(sampledPixel.y()) * lightmapPixelSize.width()) * 4;
1413 const QVector3D sampledColor(fpR[sampOfs], fpR[sampOfs + 1], fpR[sampOfs + 2]);
1414 const int pixOfs = (int(pixel.
x()) + int(pixel.
y()) * lightmapPixelSize.width()) * 4;
1415 QVector3D currentColor(fpW[pixOfs], fpW[pixOfs + 1], fpW[pixOfs + 2]);
1416 currentColor = currentColor * 0.6f + sampledColor * 0.4f;
1417 fpW[pixOfs] = currentColor.x();
1418 fpW[pixOfs + 1] = currentColor.y();
1419 fpW[pixOfs + 2] = currentColor.z();
1421 if (pixel != endPixel) {
1422 if (nextT.x() < nextT.y()) {
1423 pixel.
setX(pixel.
x() + pixelStep.x());
1424 nextT.setX(nextT.x() + tStep.x());
1426 pixel.
setY(pixel.
y() + pixelStep.y());
1427 nextT.setY(nextT.y() + tStep.y());
1435bool QSSGLightmapperPrivate::postProcess()
1440 const int bakedLightingModelCount = bakedLightingModels.size();
1443 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
1445 postProcessTimer.
start();
1449 if (!lm.model->hasLightmap())
1452 Lightmap &lightmap(lightmaps[lmIdx]);
1456 float *lightmapFloatPtr =
reinterpret_cast<float *
>(lightmapFP32.data());
1457 for (
const LightmapEntry &lmPix :
std::as_const(lightmap.entries)) {
1458 *lightmapFloatPtr++ = lmPix.allLight.x();
1459 *lightmapFloatPtr++ = lmPix.allLight.y();
1460 *lightmapFloatPtr++ = lmPix.allLight.z();
1461 *lightmapFloatPtr++ = lmPix.isValid() ? 1.0f : 0.0f;
1465 const QRhiViewport viewport(0, 0,
float(lightmap.pixelSize.width()),
float(lightmap.pixelSize.height()));
1468 if (!lightmapTex->create()) {
1474 if (!dilatedLightmapTex->create()) {
1480 std::unique_ptr<QRhiRenderPassDescriptor> rpDescDilate(rtDilate->newCompatibleRenderPassDescriptor());
1481 rtDilate->setRenderPassDescriptor(rpDescDilate.get());
1482 if (!rtDilate->create()) {
1493 renderer->rhiQuadRenderer()->prepareQuad(rhiCtx, resUpd);
1494 const auto &shaderCache =
renderer->contextInterface()->shaderCache();
1495 const auto &lmDilatePipeline = shaderCache->getBuiltInRhiShaders().getRhiLightmapDilateShader();
1496 if (!lmDilatePipeline) {
1506 resUpd->readBackTexture({ dilatedLightmapTex.get() }, &dilateReadResult);
1507 cb->resourceUpdate(resUpd);
1512 lightmap.imageFP32 = dilateReadResult.data;
1517 const DrawInfo &drawInfo(drawInfos[lmIdx]);
1518 const char *vbase = drawInfo.vertexData.constData();
1519 const quint32 *ibase =
reinterpret_cast<const quint32 *
>(drawInfo.indexData.constData());
1524 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx])
1525 assembledVertexCount += subMeshInfo.
count;
1526 QVector<QVector3D> smPos(assembledVertexCount);
1527 QVector<QVector3D> smNormal(assembledVertexCount);
1528 QVector<QVector2D> smCoord(assembledVertexCount);
1530 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx]) {
1531 for (
quint32 i = 0;
i < subMeshInfo.count; ++
i) {
1532 const quint32 idx = *(ibase + subMeshInfo.offset +
i);
1533 const float *
src =
reinterpret_cast<const float *
>(vbase + idx * drawInfo.vertexStride + drawInfo.positionOffset);
1538 src =
reinterpret_cast<const float *
>(vbase + idx * drawInfo.vertexStride + drawInfo.normalOffset);
1543 src =
reinterpret_cast<const float *
>(vbase + idx * drawInfo.vertexStride + drawInfo.lightmapUVOffset);
1551 QHash<Edge, EdgeUV> edgeUVMap;
1552 QVector<SeamUV> seams;
1553 for (vertexIdx = 0; vertexIdx < assembledVertexCount; vertexIdx += 3) {
1554 QVector3D triVert[3] = { smPos[vertexIdx], smPos[vertexIdx + 1], smPos[vertexIdx + 2] };
1555 QVector3D triNorm[3] = { smNormal[vertexIdx], smNormal[vertexIdx + 1], smNormal[vertexIdx + 2] };
1556 QVector2D triUV[3] = { smCoord[vertexIdx], smCoord[vertexIdx + 1], smCoord[vertexIdx + 2] };
1558 for (
int i = 0;
i < 3; ++
i) {
1560 int i1 = (
i + 1) % 3;
1561 if (vectorLessThan(triVert[i1], triVert[i0]))
1565 { triVert[i0], triVert[i1] },
1566 { triNorm[i0], triNorm[i1] }
1568 const EdgeUV edgeUV = { { triUV[i0], triUV[i1] } };
1569 auto it = edgeUVMap.
find(e);
1570 if (
it == edgeUVMap.
end()) {
1571 edgeUVMap.insert(e, edgeUV);
1574 seams.append(SeamUV({ { edgeUV.uv,
it->uv } }));
1580 qDebug() <<
"lm:" << seams.size() <<
"UV seams in" << lm.model;
1583 for (
int blendIter = 0; blendIter < LM_SEAM_BLEND_ITER_COUNT; ++blendIter) {
1584 memcpy(workBuf.data(), lightmap.imageFP32.constData(), lightmap.imageFP32.size());
1585 for (
int seamIdx = 0,
end = seams.size(); seamIdx !=
end; ++seamIdx) {
1586 const SeamUV &seam(seams[seamIdx]);
1587 blendLine(seam.uv[0][0], seam.uv[0][1],
1588 seam.uv[1][0], seam.uv[1][1],
1589 workBuf, lightmap.imageFP32, lightmap.pixelSize);
1590 blendLine(seam.uv[1][0], seam.uv[1][1],
1591 seam.uv[0][0], seam.uv[0][1],
1592 workBuf, lightmap.imageFP32, lightmap.pixelSize);
1597 arg(lm.model->lightmapKey).
1598 arg(postProcessTimer.elapsed()));
1604bool QSSGLightmapperPrivate::storeLightmaps()
1606 const int bakedLightingModelCount = bakedLightingModels.size();
1609 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
1612 if (!lm.model->hasLightmap())
1620 if (!lm.model->lightmapLoadPath.startsWith(
QStringLiteral(
":/")))
1621 outputFolder = lm.model->lightmapLoadPath;
1627 listContents +=
'\n';
1629 const Lightmap &lightmap(lightmaps[lmIdx]);
1631 if (SaveEXR(
reinterpret_cast<const float *
>(lightmap.imageFP32.constData()),
1632 lightmap.pixelSize.width(), lightmap.pixelSize.height(),
1633 4,
false, fns.constData(),
nullptr) < 0)
1640 arg(lm.model->lightmapKey).
1642 arg(writeTimer.elapsed()));
1643 const DrawInfo &bakeModelDrawInfo(drawInfos[lmIdx]);
1644 if (bakeModelDrawInfo.meshWithLightmapUV.isValid()) {
1648 bakeModelDrawInfo.meshWithLightmapUV.save(&
f);
1655 arg(lm.model->lightmapKey).
1657 arg(writeTimer.elapsed()));
1664 arg(listFile.fileName()));
1667 listFile.write(listContents);
1697 if (msg.has_value())
1706 outputCallback(
type, msg, &bakingControl);
1717 if (
d->bakedLightingModels.isEmpty()) {
1722 if (!
d->commitGeometry()) {
1727 if (!
d->prepareLightmaps()) {
1732 if (
d->bakingControl.cancelled) {
1737 d->computeDirectLight();
1739 if (
d->bakingControl.cancelled) {
1744 if (
d->options.indirectLightEnabled)
1745 d->computeIndirectLight();
1747 if (
d->bakingControl.cancelled) {
1752 if (!
d->postProcess()) {
1757 if (
d->bakingControl.cancelled) {
1762 if (!
d->storeLightmaps()) {
1801 qWarning(
"Qt Quick 3D was built without the lightmapper; cannot bake lightmaps");
1810 if (!
model.lightmapLoadPath.isEmpty()) {
1816 case LightmapAsset::LightmapImage:
1819 case LightmapAsset::MeshWithLightmapUV:
1835 case LightmapAsset::LightmapImage:
1838 case LightmapAsset::MeshWithLightmapUV:
1842 result += lightmapAssetPathForSave(asset, outputFolder);
1855 case LightmapAsset::LightmapImageList:
void start() noexcept
\typealias QElapsedTimer::Duration Synonym for std::chrono::nanoseconds.
QString absoluteFilePath() const
The QMatrix4x4 class represents a 4x4 transformation matrix in 3D space.
QPoint map(const QPoint &point) const
Maps point by multiplying this matrix by point.
static Q_DECL_CONST_FUNCTION QRandomGenerator * global()
\threadsafe
QPair< QRhiBuffer *, quint32 > VertexInput
Synonym for QPair<QRhiBuffer *, quint32>.
IndexFormat
Specifies the index data type.
void setTargetBlends(std::initializer_list< TargetBlend > list)
Sets the list of render target blend settings.
void setDepthWrite(bool enable)
Controls the writing out of depth data into the depth buffer based on enable.
void setShaderResourceBindings(QRhiShaderResourceBindings *srb)
Associates with srb describing the resource binding layout and the resources (QRhiBuffer,...
void setDepthOp(CompareOp op)
Sets the depth comparison function op.
void setVertexInputLayout(const QRhiVertexInputLayout &layout)
Specifies the vertex input layout.
void setShaderStages(std::initializer_list< QRhiShaderStage > list)
Sets the list of shader stages.
void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc)
Associates with the specified QRhiRenderPassDescriptor desc.
void setTopology(Topology t)
Sets the primitive topology t.
virtual bool create()=0
Creates the corresponding native graphics resources.
void setDepthTest(bool enable)
Enables or disables depth testing based on enable.
void setPolygonMode(PolygonMode mode)
Sets the polygon mode.
void uploadStaticBuffer(QRhiBuffer *buf, quint32 offset, quint32 size, const void *data)
Enqueues updating a region of a QRhiBuffer buf created with the type QRhiBuffer::Immutable or QRhiBuf...
void setColorAttachments(std::initializer_list< QRhiColorAttachment > list)
Sets the list of color attachments.
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
QRhiBuffer * newBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size)
bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags={}) const
int ubufAligned(int v) const
int resourceLimit(ResourceLimit limit) const
bool isYUpInFramebuffer() const
bool isFeatureSupported(QRhi::Feature feature) const
QRhiRenderBuffer * newRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, int sampleCount=1, QRhiRenderBuffer::Flags flags={}, QRhiTexture::Format backingFormatHint=QRhiTexture::UnknownFormat)
QRhi::FrameOpResult finish()
Waits for any work on the graphics queue (where applicable) to complete, then executes all deferred o...
QRhiTextureRenderTarget * newTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags={})
QRhiGraphicsPipeline * newGraphicsPipeline()
QRhiTexture * newTexture(QRhiTexture::Format format, const QSize &pixelSize, int sampleCount=1, QRhiTexture::Flags flags={})
QRhiResourceUpdateBatch * nextResourceUpdateBatch()
LightmapUVRasterizationShaderMode
static QString lightmapAssetPathForSave(const QSSGRenderModel &model, LightmapAsset asset, const QString &outputFolder={})
qsizetype add(const QSSGBakedLightingModel &model)
std::function< void(BakingStatus, std::optional< QString >, BakingControl *) Callback)
void setOptions(const QSSGLightmapperOptions &options)
QSSGLightmapper(QSSGRhiContext *rhiCtx, QSSGRenderer *renderer)
static QString lightmapAssetPathForLoad(const QSSGRenderModel &model, LightmapAsset asset)
void setOutputCallback(Callback callback)
bool createLightmapUVChannel(uint lightmapBaseResolution)
bool hasLightmapUVChannel() const
VertexBuffer vertexBuffer() const
IndexBuffer indexBuffer() const
QVector< Subset > subsets() const
static QSSGRhiContextPrivate * get(QSSGRhiContext *q)
QRhiCommandBuffer * commandBuffer() const
QRhiTexture * dummyTexture(QRhiTexture::Flags flags, QRhiResourceUpdateBatch *rub, const QSize &size=QSize(64, 64), const QColor &fillColor=Qt::black, int arraySize=0)
QRhiSampler * sampler(const QSSGRhiSamplerDescription &samplerDescription)
void addUniformBuffer(int binding, QRhiShaderResourceBinding::StageFlags stage, QRhiBuffer *buf, int offset=0, int size=0)
void addTexture(int binding, QRhiShaderResourceBinding::StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler)
iterator find(const T &value)
constexpr int height() const noexcept
Returns the height.
constexpr int width() const noexcept
Returns the width.
\macro QT_RESTRICTED_CAST_FROM_ASCII
QByteArray toUtf8() const &
The QVector2D class represents a vector or vertex in 2D space.
constexpr float y() const noexcept
Returns the y coordinate of this point.
constexpr float x() const noexcept
Returns the x coordinate of this point.
constexpr void setY(float y) noexcept
Sets the y coordinate of this point to the given finite y coordinate.
constexpr void setX(float x) noexcept
Sets the x coordinate of this point to the given finite x coordinate.
The QVector3D class represents a vector or vertex in 3D space.
constexpr bool isNull() const noexcept
Returns true if the x, y, and z coordinates are set to 0.0, otherwise returns false.
static QVector3D normal(QVector3D v1, QVector3D v2) noexcept
Returns the unit normal vector of a plane spanned by vectors v1 and v2, which must not be parallel to...
QVector3D normalized() const noexcept
Returns the normalized unit vector form of this vector.
constexpr float y() const noexcept
Returns the y coordinate of this point.
constexpr float x() const noexcept
Returns the x coordinate of this point.
static constexpr float dotProduct(QVector3D v1, QVector3D v2) noexcept
Returns the dot product of v1 and v2.
static constexpr QVector3D crossProduct(QVector3D v1, QVector3D v2) noexcept
Returns the cross-product of vectors v1 and v2, which is normal to the plane spanned by v1 and v2.
constexpr float z() const noexcept
Returns the z coordinate of this point.
The QVector4D class represents a vector or vertex in 4D space.
qDeleteAll(list.begin(), list.end())
QSet< QString >::iterator it
QRhiVertexInputAttribute::Format toVertexInputFormat(QSSGRenderComponentType compType, quint32 numComps)
QRhiSampler::Filter toRhi(QSSGRenderTextureFilterOp op)
Q_DECL_CONSTEXPR float translateLinearAttenuation(float attenuation)
Q_DECL_CONSTEXPR float translateConstantAttenuation(float attenuation)
Q_DECL_CONSTEXPR float translateQuadraticAttenuation(float attenuation)
QVector3D Q_QUICK3DUTILS_EXPORT transform(const QMatrix3x3 &m, const QVector3D &v)
Combined button and popup list for selecting options.
QTCONCURRENT_RUN_NODISCARD auto run(QThreadPool *pool, Function &&f, Args &&...args)
constexpr Initialization Uninitialized
static const int UBUF_SIZE
DBusConnection const char DBusError * error
size_t qHash(const QFileSystemWatcherPathKey &key, size_t seed=0)
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
bool qFuzzyIsNull(qfloat16 f) noexcept
constexpr float qDegreesToRadians(float degrees)
constexpr const T & qMin(const T &a, const T &b)
constexpr const T & qBound(const T &min, const T &val, const T &max)
constexpr const T & qMax(const T &a, const T &b)
static QList< QNetworkInterfacePrivate * > postProcess(QList< QNetworkInterfacePrivate * > list)
constexpr T qAbs(const T &t)
GLboolean GLboolean GLboolean b
GLsizei const GLfloat * v
[13]
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat z
GLuint64 GLenum void * handle
GLint GLint GLint GLint GLint x
[0]
GLfloat GLfloat GLfloat w
[0]
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLuint GLenum GLsizei length
GLenum GLenum GLsizei count
GLenum GLuint GLintptr offset
GLfloat GLfloat GLfloat GLfloat h
GLint GLenum GLboolean normalized
GLfloat GLfloat GLfloat alpha
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
static Q_CONSTINIT QBasicAtomicInteger< unsigned > seed
bool operator==(const QRandomGenerator &rng1, const QRandomGenerator &rng2)
SSL_CTX int(* cb)(SSL *ssl, unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg)
#define QStringLiteral(str)
QSqlQueryModel * model
[16]
QFuture< void > future
[5]
std::uniform_real_distribution dist(1, 2.5)
[2]
view viewport() -> scroll(dx, dy, deviceRect)
QSvgRenderer * renderer
[0]
\inmodule QtCore \reentrant
static const char * getLightmapUVAttrName()
static const char * getNormalAttrName()
static const char * getTexBinormalAttrName()
static const char * getPositionAttrName()
static const char * getTexTanAttrName()
static const char * getUV0AttrName()
ComponentType componentType
static void setShaderPipeline(QSSGRhiGraphicsPipelineState &ps, const QSSGRhiShaderPipeline *pipeline)