Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qssgiblbaker.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qssgiblbaker_p.h"
5#include <QFile>
6#include <QFileInfo>
7#include <QScopeGuard>
8
9#include <QtQuick3DRuntimeRender/private/qssgrhicontext_p.h>
10#include <QtQuick3DRuntimeRender/private/qssgrenderloadedtexture_p.h>
11#include <QtQuick3DRuntimeRender/private/qssgrendershadercache_p.h>
12
13#if QT_CONFIG(opengl)
14#include <QOffscreenSurface>
15#include <QOpenGLContext>
16#endif
17
19
20#define GL_FLOAT 0x1406
21#define GL_HALF_FLOAT 0x140B
22#define GL_UNSIGNED_BYTE 0x1401
23#define GL_RGBA 0x1908
24#define GL_RGBA8 0x8058
25#define GL_RGBA16F 0x881A
26#define GL_RGBA32F 0x8814
27
29
31{
32 return { QStringLiteral("hdr"), QStringLiteral("exr")};
33}
34
36{
37 return QStringLiteral(".ktx");
38}
39
40namespace {
41void writeUInt32(QIODevice &device, quint32 value)
42{
43 device.write(reinterpret_cast<char *>(&value), sizeof(qint32));
44}
45
46void appendBinaryVector(QVector<char> &dest, const quint32 src)
47{
48 qsizetype oldsize = dest.size();
49 dest.resize(dest.size() + sizeof(src));
50 memcpy(dest.data() + oldsize, &src, sizeof(src));
51}
52
53void appendBinaryVector(QVector<char> &dest, const std::string &src)
54{
55 qsizetype oldsize = dest.size();
56 dest.resize(dest.size() + src.size() + 1);
57 memcpy(dest.data() + oldsize, src.c_str(), src.size() + 1);
58}
59}
60
61// Vertex data for rendering environment cube map
62static const float cube[] = {
63 -1.0f, -1.0f, -1.0f, // -X side
64 -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f,
65
66 -1.0f, -1.0f, -1.0f, // -Z side
67 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f,
68
69 -1.0f, -1.0f, -1.0f, // -Y side
70 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f,
71
72 -1.0f, 1.0f, -1.0f, // +Y side
73 -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f,
74
75 1.0f, 1.0f, -1.0f, // +X side
76 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f,
77
78 -1.0f, 1.0f, 1.0f, // +Z side
79 -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
80
81 0.0f, 1.0f, // -X side
82 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
83
84 1.0f, 1.0f, // -Z side
85 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
86
87 1.0f, 0.0f, // -Y side
88 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
89
90 1.0f, 0.0f, // +Y side
91 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
92
93 1.0f, 0.0f, // +X side
94 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
95
96 0.0f, 0.0f, // +Z side
97 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
98};
99
100QString renderToKTXFileInternal(const char *name, const QString &inPath, const QString &outPath, QRhi::Implementation impl, QRhiInitParams *initParams)
101{
102 qDebug() << "Using RHI backend" << name;
103
104 // Open output file
105 QFile ktxOutputFile(outPath);
106 if (!ktxOutputFile.open(QIODevice::WriteOnly)) {
107 return QStringLiteral("Could not open file: %1").arg(outPath);
108 }
109
110 QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
111 if (!rhi)
112 return QStringLiteral("Failed to initialize QRhi");
113
114 qDebug() << rhi->driverInfo();
115
117 rhi->beginOffscreenFrame(&cb);
118
119 const auto rhiContext = std::make_unique<QSSGRhiContext>(rhi.get());
120 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiContext.get());
121 rhiCtxD->setCommandBuffer(cb);
122
123 QScopedPointer<QSSGLoadedTexture> inImage(QSSGLoadedTexture::loadHdrImage(QSSGInputUtil::getStreamForFile(inPath), FORMAT));
124 if (!inImage)
125 return QStringLiteral("Failed to load hdr file");
126
127 auto shaderCache = std::make_unique<QSSGShaderCache>(*rhiContext);
128
129 // The objective of this method is to take the equirectangular texture
130 // provided by inImage and create a cubeMap that contains both pre-filtered
131 // specular environment maps, as well as a irradiance map for diffuse
132 // operations.
133 // To achieve this though we first convert convert the Equirectangular texture
134 // to a cubeMap with genereated mip map levels (no filtering) to make the
135 // process of creating the prefiltered and irradiance maps eaiser. This
136 // intermediate texture as well as the original equirectangular texture are
137 // destroyed after this frame completes, and all further associations with
138 // the source lightProbe texture are instead associated with the final
139 // generated environment map.
140 // The intermediate environment cubemap is used to generate the final
141 // cubemap. This cubemap will generate 6 mip levels for each face
142 // (the remaining faces are unused). This is what the contents of each
143 // face mip level looks like:
144 // 0: Pre-filtered with roughness 0 (basically unfiltered)
145 // 1: Pre-filtered with roughness 0.25
146 // 2: Pre-filtered with roughness 0.5
147 // 3: Pre-filtered with roughness 0.75
148 // 4: Pre-filtered with rougnness 1.0
149 // 5: Irradiance map (ideally at least 16x16)
150 // It would be better if we could use a separate cubemap for irradiance, but
151 // right now there is a 1:1 association between texture sources on the front-
152 // end and backend.
153
154 // Right now minimum face size needs to be 512x512 to be able to have 6 reasonably sized mips
155 const int suggestedSize = qMax(512.f, inImage->height * 0.5f);
156 const QSize environmentMapSize(suggestedSize, suggestedSize);
157 const bool isRGBE = inImage->format.format == QSSGRenderTextureFormat::Format::RGBE8;
158 const int colorSpace = inImage->isSRGB ? 1 : 0; // 0 Linear | 1 sRGB
159
160 // Phase 1: Convert the Equirectangular texture to a Cubemap
161 QRhiTexture *envCubeMap = rhi->newTexture(QRhiTexture::RGBA16F,
162 environmentMapSize,
163 1,
166 if (!envCubeMap->create()) {
167 return QStringLiteral("Failed to create Environment Cube Map");
168 }
169 envCubeMap->deleteLater();
170
171 // Create a renderbuffer the size of a the cubeMap face
172 QRhiRenderBuffer *envMapRenderBuffer = rhi->newRenderBuffer(QRhiRenderBuffer::Color, environmentMapSize);
173 if (!envMapRenderBuffer->create()) {
174 return QStringLiteral("Failed to create Environment Map Render Buffer");
175 }
176 envMapRenderBuffer->deleteLater();
177
178 // Setup the 6 render targets for each cube face
179 QVarLengthArray<QRhiTextureRenderTarget *, 6> renderTargets;
180 QRhiRenderPassDescriptor *renderPassDesc = nullptr;
181 for (int face = 0; face < 6; ++face) {
182 QRhiColorAttachment att(envCubeMap);
183 att.setLayer(face);
185 rtDesc.setColorAttachments({ att });
186 auto renderTarget = rhi->newTextureRenderTarget(rtDesc);
187 renderTarget->setDescription(rtDesc);
188 if (!renderPassDesc)
189 renderPassDesc = renderTarget->newCompatibleRenderPassDescriptor();
190 renderTarget->setRenderPassDescriptor(renderPassDesc);
191 if (!renderTarget->create()) {
192 return QStringLiteral("Failed to build env map render target");
193 }
194 renderTarget->deleteLater();
195 renderTargets << renderTarget;
196 }
197 renderPassDesc->deleteLater();
198
199 // Setup the sampler for reading the equirectangular loaded texture
200 QSize size(inImage->width, inImage->height);
201 auto *sourceTexture = rhi->newTexture(QRhiTexture::RGBA16F, size, 1);
202 if (!sourceTexture->create()) {
203 return QStringLiteral("Failed to create source env map texture");
204 }
205 sourceTexture->deleteLater();
206
207 // Upload the equirectangular texture
209 if (inImage->textureFileData.isValid()) {
210 desc = { { 0,
211 0,
212 { inImage->textureFileData.data().constData() + inImage->textureFileData.dataOffset(0),
213 quint32(inImage->textureFileData.dataLength(0)) } } };
214 } else {
215 desc = { { 0, 0, { inImage->data, inImage->dataSizeInBytes } } };
216 }
217 auto *rub = rhi->nextResourceUpdateBatch();
218 rub->uploadTexture(sourceTexture, desc);
219
220 const QSSGRhiSamplerDescription samplerDesc {
222 };
223 QRhiSampler *sampler = rhiContext->sampler(samplerDesc);
224
225 // Load shader and setup render pipeline
226 const auto &envMapShaderStages = shaderCache->getBuiltInRhiShaders().getRhiEnvironmentmapShader();
227
228 // Vertex Buffer - Just a single cube that will be viewed from inside
229 QRhiBuffer *vertexBuffer = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube));
230 vertexBuffer->create();
231 vertexBuffer->deleteLater();
232 rub->uploadStaticBuffer(vertexBuffer, cube);
233
234 // Uniform Buffer - 2x mat4
235 int ubufElementSize = rhi->ubufAligned(128);
236 QRhiBuffer *uBuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufElementSize * 6);
237 uBuf->create();
238 uBuf->deleteLater();
239
240 int ubufEnvMapElementSize = rhi->ubufAligned(4);
241 QRhiBuffer *uBufEnvMap = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufEnvMapElementSize * 6);
242 uBufEnvMap->create();
243 uBufEnvMap->deleteLater();
244
245 // Shader Resource Bindings
246 QRhiShaderResourceBindings *envMapSrb = rhi->newShaderResourceBindings();
247 envMapSrb->setBindings(
251 envMapSrb->create();
252 envMapSrb->deleteLater();
253
254 // Pipeline
255 QRhiGraphicsPipeline *envMapPipeline = rhi->newGraphicsPipeline();
256 envMapPipeline->setCullMode(QRhiGraphicsPipeline::Front);
257 envMapPipeline->setFrontFace(QRhiGraphicsPipeline::CCW);
258 envMapPipeline->setShaderStages({ *envMapShaderStages->vertexStage(), *envMapShaderStages->fragmentStage() });
259
260 QRhiVertexInputLayout inputLayout;
261 inputLayout.setBindings({ { 3 * sizeof(float) } });
262 inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float3, 0 } });
263
264 envMapPipeline->setVertexInputLayout(inputLayout);
265 envMapPipeline->setShaderResourceBindings(envMapSrb);
266 envMapPipeline->setRenderPassDescriptor(renderPassDesc);
267 if (!envMapPipeline->create()) {
268 return QStringLiteral("Failed to create source env map pipeline state");
269 }
270 envMapPipeline->deleteLater();
271
272 // Do the actual render passes
273 cb->debugMarkBegin("Environment Cubemap Generation");
274 const QRhiCommandBuffer::VertexInput vbufBinding(vertexBuffer, 0);
275
276 // Set the Uniform Data
277 QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix();
278 mvp.perspective(90.0f, 1.0f, 0.1f, 10.0f);
279
280 auto lookAt = [](const QVector3D &eye, const QVector3D &center, const QVector3D &up) {
281 QMatrix4x4 viewMatrix;
282 viewMatrix.lookAt(eye, center, up);
283 return viewMatrix;
284 };
285 QVarLengthArray<QMatrix4x4, 6> views;
286 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(1.0, 0.0, 0.0), QVector3D(0.0f, -1.0f, 0.0f)));
287 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(-1.0, 0.0, 0.0), QVector3D(0.0f, -1.0f, 0.0f)));
288 if (rhi->isYUpInFramebuffer()) {
289 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 1.0, 0.0), QVector3D(0.0f, 0.0f, 1.0f)));
290 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, -1.0, 0.0), QVector3D(0.0f, 0.0f, -1.0f)));
291 } else {
292 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, -1.0, 0.0), QVector3D(0.0f, 0.0f, -1.0f)));
293 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 1.0, 0.0), QVector3D(0.0f, 0.0f, 1.0f)));
294 }
295 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 0.0, 1.0), QVector3D(0.0f, -1.0f, 0.0f)));
296 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 0.0, -1.0), QVector3D(0.0f, -1.0f, 0.0f)));
297 for (int face = 0; face < 6; ++face) {
298 rub->updateDynamicBuffer(uBuf, face * ubufElementSize, 64, mvp.constData());
299 rub->updateDynamicBuffer(uBuf, face * ubufElementSize + 64, 64, views[face].constData());
300 rub->updateDynamicBuffer(uBufEnvMap, face * ubufEnvMapElementSize, 4, &colorSpace);
301 }
302 cb->resourceUpdate(rub);
303
304 for (int face = 0; face < 6; ++face) {
305 cb->beginPass(renderTargets[face], QColor(0, 0, 0, 1), { 1.0f, 0 }, nullptr, rhiContext->commonPassFlags());
306
307 // Execute render pass
308 cb->setGraphicsPipeline(envMapPipeline);
309 cb->setVertexInput(0, 1, &vbufBinding);
310 cb->setViewport(QRhiViewport(0, 0, environmentMapSize.width(), environmentMapSize.height()));
311 QVector<QPair<int, quint32>> dynamicOffset = {
312 { 0, quint32(ubufElementSize * face) },
313 { 2, quint32(ubufEnvMapElementSize * face )}
314 };
315 cb->setShaderResources(envMapSrb, 2, dynamicOffset.constData());
316
317 cb->draw(36);
318 cb->endPass();
319 }
320 cb->debugMarkEnd();
321
322 if (!isRGBE) {
323 // Generate mipmaps for envMap
324 rub = rhi->nextResourceUpdateBatch();
325 rub->generateMips(envCubeMap);
326 cb->resourceUpdate(rub);
327 }
328
329 // Phase 2: Generate the pre-filtered environment cubemap
330 cb->debugMarkBegin("Pre-filtered Environment Cubemap Generation");
331 QRhiTexture *preFilteredEnvCubeMap = rhi->newTexture(QRhiTexture::RGBA16F,
332 environmentMapSize,
333 1,
335 if (!preFilteredEnvCubeMap->create())
336 qWarning("Failed to create Pre-filtered Environment Cube Map");
337 int mipmapCount = rhi->mipLevelsForSize(environmentMapSize);
338 mipmapCount = qMin(mipmapCount, 6); // don't create more than 6 mip levels
339 QMap<int, QSize> mipLevelSizes;
340 QMap<int, QVarLengthArray<QRhiTextureRenderTarget *, 6>> renderTargetsMap;
341 QRhiRenderPassDescriptor *renderPassDescriptorPhase2 = nullptr;
342
343 // Create a renderbuffer for each mip level
344 for (int mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) {
345 const QSize levelSize = QSize(environmentMapSize.width() * std::pow(0.5, mipLevel),
346 environmentMapSize.height() * std::pow(0.5, mipLevel));
347 mipLevelSizes.insert(mipLevel, levelSize);
348 // Setup Render targets (6 * mipmapCount)
349 QVarLengthArray<QRhiTextureRenderTarget *, 6> renderTargets;
350 for (int face = 0; face < 6; ++face) {
351 QRhiColorAttachment att(preFilteredEnvCubeMap);
352 att.setLayer(face);
353 att.setLevel(mipLevel);
355 rtDesc.setColorAttachments({ att });
356 auto renderTarget = rhi->newTextureRenderTarget(rtDesc);
357 renderTarget->setDescription(rtDesc);
358 if (!renderPassDescriptorPhase2)
359 renderPassDescriptorPhase2 = renderTarget->newCompatibleRenderPassDescriptor();
360 renderTarget->setRenderPassDescriptor(renderPassDescriptorPhase2);
361 if (!renderTarget->create())
362 qWarning("Failed to build prefilter env map render target");
363 renderTarget->deleteLater();
364 renderTargets << renderTarget;
365 }
366 renderTargetsMap.insert(mipLevel, renderTargets);
367 renderPassDescriptorPhase2->deleteLater();
368 }
369
370 // Load the prefilter shader stages
371 const auto &prefilterShaderStages = shaderCache->getBuiltInRhiShaders().getRhienvironmentmapPreFilterShader(isRGBE);
372
373 // Create a new Sampler
374 const QSSGRhiSamplerDescription samplerMipMapDesc {
376 };
377
378 QRhiSampler *envMapCubeSampler = nullptr;
379 // Only use mipmap interpoliation if not using RGBE
380 if (!isRGBE)
381 envMapCubeSampler = rhiContext->sampler(samplerMipMapDesc);
382 else
383 envMapCubeSampler = sampler;
384
385 // Reuse Vertex Buffer from phase 1
386 // Reuse UniformBuffer from phase 1 (for vertex shader)
387
388 // UniformBuffer
389 // float roughness;
390 // float resolution;
391 // float lodBias;
392 // int sampleCount;
393 // int distribution;
394
395 int ubufPrefilterElementSize = rhi->ubufAligned(20);
396 QRhiBuffer *uBufPrefilter = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufPrefilterElementSize * mipmapCount);
397 uBufPrefilter->create();
398 uBufPrefilter->deleteLater();
399
400 // Shader Resource Bindings
401 QRhiShaderResourceBindings *preFilterSrb = rhi->newShaderResourceBindings();
402 preFilterSrb->setBindings({
406 });
407 preFilterSrb->create();
408 preFilterSrb->deleteLater();
409
410 // Pipeline
411 QRhiGraphicsPipeline *prefilterPipeline = rhi->newGraphicsPipeline();
412 prefilterPipeline->setCullMode(QRhiGraphicsPipeline::Front);
413 prefilterPipeline->setFrontFace(QRhiGraphicsPipeline::CCW);
414 prefilterPipeline->setDepthOp(QRhiGraphicsPipeline::LessOrEqual);
415 prefilterPipeline->setShaderStages({
416 *prefilterShaderStages->vertexStage(),
417 *prefilterShaderStages->fragmentStage()
418 });
419 // same as phase 1
420 prefilterPipeline->setVertexInputLayout(inputLayout);
421 prefilterPipeline->setShaderResourceBindings(preFilterSrb);
422 prefilterPipeline->setRenderPassDescriptor(renderPassDescriptorPhase2);
423 if (!prefilterPipeline->create())
424 return QStringLiteral("Failed to create pre-filter env map pipeline state");
425 prefilterPipeline->deleteLater();
426
427 // Uniform Data
428 // set the roughness uniform buffer data
429 rub = rhi->nextResourceUpdateBatch();
430 const float resolution = environmentMapSize.width();
431 const float lodBias = 0.0f;
432 const int sampleCount = 1024;
433 for (int mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) {
434 Q_ASSERT(mipmapCount - 2);
435 const float roughness = float(mipLevel) / float(mipmapCount - 2);
436 const int distribution = mipLevel == (mipmapCount - 1) ? 0 : 1; // last mip level is for irradiance
437 rub->updateDynamicBuffer(uBufPrefilter, mipLevel * ubufPrefilterElementSize, 4, &roughness);
438 rub->updateDynamicBuffer(uBufPrefilter, mipLevel * ubufPrefilterElementSize + 4, 4, &resolution);
439 rub->updateDynamicBuffer(uBufPrefilter, mipLevel * ubufPrefilterElementSize + 4 + 4, 4, &lodBias);
440 rub->updateDynamicBuffer(uBufPrefilter, mipLevel * ubufPrefilterElementSize + 4 + 4 + 4, 4, &sampleCount);
441 rub->updateDynamicBuffer(uBufPrefilter, mipLevel * ubufPrefilterElementSize + 4 + 4 + 4 + 4, 4, &distribution);
442 }
443
444 cb->resourceUpdate(rub);
445
446 // Render
447 for (int mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) {
448 for (int face = 0; face < 6; ++face) {
449 cb->beginPass(renderTargetsMap[mipLevel][face], QColor(0, 0, 0, 1), { 1.0f, 0 }, nullptr, rhiContext->commonPassFlags());
450 cb->setGraphicsPipeline(prefilterPipeline);
451 cb->setVertexInput(0, 1, &vbufBinding);
452 cb->setViewport(QRhiViewport(0, 0, mipLevelSizes[mipLevel].width(), mipLevelSizes[mipLevel].height()));
453 QVector<QPair<int, quint32>> dynamicOffsets = {
454 { 0, quint32(ubufElementSize * face) },
455 { 2, quint32(ubufPrefilterElementSize * mipLevel) }
456 };
457 cb->setShaderResources(preFilterSrb, 2, dynamicOffsets.constData());
458 cb->draw(36);
459 cb->endPass();
460 }
461 }
462 cb->debugMarkEnd();
463
464 // Write ktx
465
466 const quint32 numberOfMipmapLevels = renderTargetsMap.size();
467 const quint32 numberOfFaces = 6;
468
469 constexpr size_t KTX_IDENTIFIER_LENGTH = 12;
470 constexpr char ktxIdentifier[KTX_IDENTIFIER_LENGTH] = { '\xAB', 'K', 'T', 'X', ' ', '1',
471 '1', '\xBB', '\r', '\n', '\x1A', '\n' };
472 constexpr quint32 platformEndianIdentifier = 0x04030201;
473 QVector<char> keyValueData;
474
475 // Prepare Key/Value array
476 {
477 // Add a key to the metadata to know it was created by our IBL baker
478 static const char key[] = "QT_IBL_BAKER_VERSION";
479 static const char value[] = "1";
480
481 constexpr size_t keyAndValueByteSize = sizeof(key) + sizeof(value); // NB: 2x null terminator
482 appendBinaryVector(keyValueData, keyAndValueByteSize);
483 appendBinaryVector(keyValueData, key);
484 appendBinaryVector(keyValueData, value);
485
486 // Pad until next multiple of 4
487 const size_t padding = 3 - ((keyAndValueByteSize + 3) % 4); // Pad until next multiple of 4
488 keyValueData.resize(keyValueData.size() + padding);
489 }
490
491 // Header
492
493 // identifier
494 ktxOutputFile.write(ktxIdentifier, KTX_IDENTIFIER_LENGTH);
495
496 // endianness
497 writeUInt32(ktxOutputFile, quint32(platformEndianIdentifier));
498
499 // glType
500 writeUInt32(ktxOutputFile, quint32(GL_HALF_FLOAT));
501
502 // glTypeSize (in bytes per component)
503 writeUInt32(ktxOutputFile, quint32(FORMAT.getSizeofFormat()) / quint32(FORMAT.getNumberOfComponent()));
504
505 // glFormat
506 writeUInt32(ktxOutputFile, quint32(GL_RGBA));
507
508 // glInternalFormat
509 writeUInt32(ktxOutputFile, quint32(GL_RGBA16F));
510
511 // glBaseInternalFormat
512 writeUInt32(ktxOutputFile, quint32(GL_RGBA));
513
514 // pixelWidth
515 writeUInt32(ktxOutputFile, quint32(environmentMapSize.width()));
516
517 // pixelHeight
518 writeUInt32(ktxOutputFile, quint32(environmentMapSize.height()));
519
520 // pixelDepth
521 writeUInt32(ktxOutputFile, quint32(0));
522
523 // numberOfArrayElements
524 writeUInt32(ktxOutputFile, quint32(0));
525
526 // numberOfFaces
527 writeUInt32(ktxOutputFile, quint32(numberOfFaces));
528
529 // numberOfMipLevels
530 writeUInt32(ktxOutputFile, quint32(numberOfMipmapLevels));
531
532 // bytesOfKeyValueData
533 writeUInt32(ktxOutputFile, quint32(keyValueData.size()));
534
535 // Key/Value
536 ktxOutputFile.write(keyValueData.data(), keyValueData.size());
537
538 // Images
539 for (quint32 mipmap_level = 0; mipmap_level < numberOfMipmapLevels; mipmap_level++) {
540 quint32 imageSize = 0;
541 for (size_t face = 0; face < numberOfFaces; face++) {
542 QRhiTextureRenderTarget *renderTarget = renderTargetsMap[mipmap_level][face];
543
544 // Read back texture
545 Q_ASSERT(rhi->isRecordingFrame());
546
547 const auto texture = renderTarget->description().cbeginColorAttachments()->texture();
548
550 QRhiReadbackDescription readbackDesc(texture); // null src == read from swapchain backbuffer
551 readbackDesc.setLayer(int(face));
552 readbackDesc.setLevel(mipmap_level);
553
554 QRhiResourceUpdateBatch *resourceUpdates = rhi->nextResourceUpdateBatch();
555 resourceUpdates->readBackTexture(readbackDesc, &result);
556
557 cb->resourceUpdate(resourceUpdates);
558 rhi->finish(); // make sure the readback has finished, stall the pipeline if needed
559
560 // Write imageSize once size is known
561 if (imageSize == 0) {
562 imageSize = result.data.size();
563 writeUInt32(ktxOutputFile, quint32(imageSize));
564 }
565
566 ktxOutputFile.write(result.data);
567 }
568 }
569
570 ktxOutputFile.close();
571
572 preFilteredEnvCubeMap->deleteLater();
573
574 rhi->endOffscreenFrame();
575 rhi->finish();
576
577 return {};
578}
579
581{
582#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
583 // A macOS VM may not have Metal support at all. We have to decide at this
584 // point, it will be too late afterwards, and the only way is to see if
585 // MTLCreateSystemDefaultDevice succeeds.
586 if (impl == QRhi::Metal) {
587 QRhiMetalInitParams rhiParams;
588 QRhi *tempRhi = QRhi::create(impl, &rhiParams, {});
589 if (!tempRhi) {
590 impl = QRhi::OpenGLES2;
591 qDebug("Metal does not seem to be supported. Falling back to OpenGL.");
592 } else {
593 delete tempRhi;
594 }
595 }
596#else
597 Q_UNUSED(impl);
598#endif
599}
600
602{
604
605 // check env.vars., fall back to platform-specific defaults when backend is not set
606 const QByteArray rhiBackend = qgetenv("QSG_RHI_BACKEND");
607 if (rhiBackend == QByteArrayLiteral("gl") || rhiBackend == QByteArrayLiteral("gles2")
608 || rhiBackend == QByteArrayLiteral("opengl")) {
609 implementation = QRhi::OpenGLES2;
610 } else if (rhiBackend == QByteArrayLiteral("d3d11") || rhiBackend == QByteArrayLiteral("d3d")) {
611 implementation = QRhi::D3D11;
612 } else if (rhiBackend == QByteArrayLiteral("d3d12")) {
613 implementation = QRhi::D3D12;
614 } else if (rhiBackend == QByteArrayLiteral("vulkan")) {
615 implementation = QRhi::Vulkan;
616 } else if (rhiBackend == QByteArrayLiteral("metal")) {
617 implementation = QRhi::Metal;
618 } else if (rhiBackend == QByteArrayLiteral("null")) {
619 implementation = QRhi::Null;
620 } else {
621 if (!rhiBackend.isEmpty()) {
622 qWarning("Unknown key \"%s\" for QSG_RHI_BACKEND, falling back to default backend.", rhiBackend.constData());
623 }
624#if defined(Q_OS_WIN)
625 implementation = QRhi::D3D11;
626#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)
627 implementation = QRhi::Metal;
628#elif QT_CONFIG(opengl)
629 implementation = QRhi::OpenGLES2;
630#else
631 implementation = QRhi::Vulkan;
632#endif
633 }
634
635 adjustToPlatformQuirks(implementation);
636
637 return implementation;
638}
639
640QString renderToKTXFile(const QString &inPath, const QString &outPath)
641{
642 const auto rhiImplementation = getRhiImplementation();
643
644#if QT_CONFIG(opengl)
645 if (rhiImplementation == QRhi::OpenGLES2) {
648 // OpenGL 3.2 or higher
649 params.format.setProfile(QSurfaceFormat::CoreProfile);
650 params.format.setVersion(3, 2);
651 } else {
652 // OpenGL ES 3.0 or higher
653 params.format.setVersion(3, 0);
654 }
655 params.fallbackSurface = QRhiGles2InitParams::newFallbackSurface();
656 const QString result = renderToKTXFileInternal("OpenGL", inPath, outPath, QRhi::OpenGLES2, &params);
657 delete params.fallbackSurface;
658 return result;
659 }
660#endif
661
662#if QT_CONFIG(vulkan)
663 if (rhiImplementation == QRhi::Vulkan) {
664 QVulkanInstance vulkanInstance;
665 vulkanInstance.create();
667 params.inst = &vulkanInstance;
668 return renderToKTXFileInternal("Vulkan", inPath, outPath, QRhi::Vulkan, &params);
669 }
670#endif
671
672#ifdef Q_OS_WIN
673 if (rhiImplementation == QRhi::D3D11) {
675 return renderToKTXFileInternal("Direct3D 11", inPath, outPath, QRhi::D3D11, &params);
676 } else if (rhiImplementation == QRhi::D3D12) {
677 QRhiD3D12InitParams params;
678 return renderToKTXFileInternal("Direct3D 12", inPath, outPath, QRhi::D3D12, &params);
679 }
680#endif
681
682#if QT_CONFIG(metal)
683 if (rhiImplementation == QRhi::Metal) {
685 return renderToKTXFileInternal("Metal", inPath, outPath, QRhi::Metal, &params);
686 }
687#endif
688
689 return QStringLiteral("No RHI backend");
690}
691
692const QString QSSGIblBaker::import(const QString &sourceFile, const QDir &savePath, QStringList *generatedFiles)
693{
694 qDebug() << "IBL lightprobe baker" << sourceFile;
695
696 QString outFileName = savePath.absoluteFilePath(QFileInfo(sourceFile).baseName() + QStringLiteral(".ktx"));
697
698 QString error = renderToKTXFile(sourceFile, outFileName);
699 if (!error.isEmpty())
700 return error;
701
702 m_generatedFiles.append(outFileName);
703
704 if (generatedFiles)
705 *generatedFiles = m_generatedFiles;
706
707 return QString();
708}
709
IOBluetoothDevice * device
\inmodule QtCore
Definition qbytearray.h:57
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:107
The QColor class provides colors based on RGB, HSV or CMYK values.
Definition qcolor.h:31
\inmodule QtCore
Definition qdir.h:20
\inmodule QtCore
Definition qfile.h:93
\inmodule QtCore \reentrant
Definition qiodevice.h:34
The QMatrix4x4 class represents a 4x4 transformation matrix in 3D space.
Definition qmatrix4x4.h:25
void lookAt(const QVector3D &eye, const QVector3D &center, const QVector3D &up)
Multiplies this matrix by a viewing matrix derived from an eye point.
static OpenGLModuleType openGLModuleType()
Returns the underlying OpenGL implementation type.
\inmodule QtGui
Definition qrhi.h:846
@ Immutable
Definition qrhi.h:849
@ Dynamic
Definition qrhi.h:851
@ VertexBuffer
Definition qrhi.h:855
@ UniformBuffer
Definition qrhi.h:857
virtual bool create()=0
Creates the corresponding native graphics resources.
\inmodule QtGui
Definition qrhi.h:576
QRhiTexture * texture() const
Definition qrhi.h:582
void setLevel(int level)
Sets the mip level.
Definition qrhi.h:592
void setLayer(int layer)
Sets the layer index.
Definition qrhi.h:589
\inmodule QtGui
Definition qrhi.h:1651
QPair< QRhiBuffer *, quint32 > VertexInput
Synonym for QPair<QRhiBuffer *, quint32>.
Definition qrhi.h:1680
\inmodule QtGui
\inmodule QtGui
\inmodule QtGui
Definition qrhi.h:1270
\inmodule QtRhi
\inmodule QtGui
Definition qrhi.h:777
\inmodule QtGui
Definition qrhi.h:1094
\inmodule QtGui
Definition qrhi.h:1142
\inmodule QtGui
Definition qrhi.h:1731
void deleteLater()
When called without a frame being recorded, this function is equivalent to deleting the object.
Definition qrhi.cpp:3545
\inmodule QtGui
Definition qrhi.h:1030
@ ClampToEdge
Definition qrhi.h:1040
static QRhiShaderResourceBinding sampledTexture(int binding, StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler)
Definition qrhi.cpp:5640
static QRhiShaderResourceBinding uniformBufferWithDynamicOffset(int binding, StageFlags stage, QRhiBuffer *buf, quint32 size)
Definition qrhi.cpp:5600
\inmodule QtGui
Definition qrhi.h:1214
const QRhiColorAttachment * cbeginColorAttachments() const
Definition qrhi.h:634
void setColorAttachments(std::initializer_list< QRhiColorAttachment > list)
Sets the list of color attachments.
Definition qrhi.h:627
\inmodule QtGui
Definition qrhi.h:1184
QRhiTextureRenderTargetDescription description() const
Definition qrhi.h:1195
\inmodule QtGui
Definition qrhi.h:716
\inmodule QtGui
Definition qrhi.h:895
@ UsedWithGenerateMips
Definition qrhi.h:903
@ MipMapped
Definition qrhi.h:900
@ RenderTarget
Definition qrhi.h:898
@ CubeMap
Definition qrhi.h:899
@ RGBA16F
Definition qrhi.h:925
\inmodule QtGui
Definition qrhi.h:321
void setBindings(std::initializer_list< QRhiVertexInputBinding > list)
Sets the bindings from the specified list.
Definition qrhi.h:325
void setAttributes(std::initializer_list< QRhiVertexInputAttribute > list)
Sets the attributes from the specified list.
Definition qrhi.h:337
\inmodule QtGui
Definition qrhi.h:85
\inmodule QtGui
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1804
Implementation
Describes which graphics API-specific backend gets used by a QRhi instance.
Definition qrhi.h:1806
@ Metal
Definition qrhi.h:1811
@ Vulkan
Definition qrhi.h:1808
@ Null
Definition qrhi.h:1807
@ D3D11
Definition qrhi.h:1810
@ D3D12
Definition qrhi.h:1812
@ OpenGLES2
Definition qrhi.h:1809
static QRhi * create(Implementation impl, QRhiInitParams *params, Flags flags={}, QRhiNativeHandles *importDevice=nullptr)
Definition qrhi.cpp:8491
const QString import(const QString &sourceFile, const QDir &savePath, QStringList *generatedFiles)
const QString outputExtension() const
const QStringList inputExtensions() const
static QSSGRhiContextPrivate * get(QSSGRhiContext *q)
\inmodule QtCore
Definition qsize.h:25
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:171
The QVulkanInstance class represents a native Vulkan instance, enabling Vulkan rendering onto a QSurf...
Combined button and popup list for selecting options.
#define QByteArrayLiteral(str)
Definition qbytearray.h:52
DBusConnection const char DBusError * error
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
static const quint32 platformEndianIdentifier
#define KTX_IDENTIFIER_LENGTH
static const char ktxIdentifier[KTX_IDENTIFIER_LENGTH]
#define qDebug
[1]
Definition qlogging.h:164
#define qWarning
Definition qlogging.h:166
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLuint64 key
GLint GLsizei GLsizei height
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint sampler
GLenum face
GLenum src
GLint GLsizei width
GLenum GLuint texture
GLuint sourceTexture
GLuint name
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei imageSize
void ** params
GLuint64EXT * result
[6]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
void adjustToPlatformQuirks(QRhi::Implementation &impl)
QRhi::Implementation getRhiImplementation()
#define GL_RGBA16F
QString renderToKTXFile(const QString &inPath, const QString &outPath)
QString renderToKTXFileInternal(const char *name, const QString &inPath, const QString &outPath, QRhi::Implementation impl, QRhiInitParams *initParams)
static constexpr QSSGRenderTextureFormat FORMAT(QSSGRenderTextureFormat::RGBA16F)
static const float cube[]
#define GL_RGBA
#define GL_HALF_FLOAT
SSL_CTX int(* cb)(SSL *ssl, unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg)
#define QStringLiteral(str)
Q_CORE_EXPORT QByteArray qgetenv(const char *varName)
#define Q_UNUSED(x)
unsigned int quint32
Definition qtypes.h:50
int qint32
Definition qtypes.h:49
ptrdiff_t qsizetype
Definition qtypes.h:165
\inmodule QtGui
Definition qrhi.h:1800
\inmodule QtGui
Definition qrhi.h:1723
static QSharedPointer< QIODevice > getStreamForFile(const QString &inPath, bool inQuiet=false, QString *outPath=nullptr)
static QSSGLoadedTexture * loadHdrImage(const QSharedPointer< QIODevice > &source, const QSSGRenderTextureFormat &inFormat)
\variable QSSGRhiGraphicsPipelineState::depthFunc