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