62static const float cube[] = {
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,
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,
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,
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,
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,
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,
82 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
85 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
88 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
91 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
94 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
97 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
102 qDebug() <<
"Using RHI backend" << name;
105 QFile ktxOutputFile(outPath);
106 if (!ktxOutputFile.open(QIODevice::WriteOnly)) {
107 return QStringLiteral(
"Could not open file: %1").arg(outPath);
110 QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(),
nullptr));
112 return QStringLiteral(
"Failed to initialize QRhi");
114 qDebug() << rhi->driverInfo();
116 QRhiCommandBuffer *cb;
117 rhi->beginOffscreenFrame(&cb);
119 const auto rhiContext = std::make_unique<QSSGRhiContext>(rhi.get());
120 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiContext.get());
121 rhiCtxD->setCommandBuffer(cb);
123 QScopedPointer<QSSGLoadedTexture> inImage(QSSGLoadedTexture::loadHdrImage(QSSGInputUtil::getStreamForFile(inPath), FORMAT));
125 return QStringLiteral(
"Failed to load hdr file");
127 auto shaderCache = std::make_unique<QSSGShaderCache>(*rhiContext);
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;
161 QRhiTexture *envCubeMap = rhi->newTexture(QRhiTexture::RGBA16F,
164 QRhiTexture::RenderTarget | QRhiTexture::CubeMap | QRhiTexture::MipMapped
165 | QRhiTexture::UsedWithGenerateMips);
166 if (!envCubeMap->create()) {
167 return QStringLiteral(
"Failed to create Environment Cube Map");
169 envCubeMap->deleteLater();
172 QRhiRenderBuffer *envMapRenderBuffer = rhi->newRenderBuffer(QRhiRenderBuffer::Color, environmentMapSize);
173 if (!envMapRenderBuffer->create()) {
174 return QStringLiteral(
"Failed to create Environment Map Render Buffer");
176 envMapRenderBuffer->deleteLater();
179 QVarLengthArray<QRhiTextureRenderTarget *, 6> renderTargets;
180 QRhiRenderPassDescriptor *renderPassDesc =
nullptr;
181 for (
int face = 0; face < 6; ++face) {
182 QRhiColorAttachment att(envCubeMap);
184 QRhiTextureRenderTargetDescription rtDesc;
185 rtDesc.setColorAttachments({ att });
186 auto renderTarget = rhi->newTextureRenderTarget(rtDesc);
187 renderTarget->setDescription(rtDesc);
189 renderPassDesc = renderTarget->newCompatibleRenderPassDescriptor();
190 renderTarget->setRenderPassDescriptor(renderPassDesc);
191 if (!renderTarget->create()) {
192 return QStringLiteral(
"Failed to build env map render target");
194 renderTarget->deleteLater();
195 renderTargets << renderTarget;
197 renderPassDesc->deleteLater();
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");
205 sourceTexture->deleteLater();
208 QRhiTextureUploadDescription desc;
209 if (inImage->textureFileData.isValid()) {
212 { inImage->textureFileData.data().constData() + inImage->textureFileData.dataOffset(0),
213 quint32(inImage->textureFileData.dataLength(0)) } } };
215 desc = { { 0, 0, { inImage->data, inImage->dataSizeInBytes } } };
217 auto *rub = rhi->nextResourceUpdateBatch();
218 rub->uploadTexture(sourceTexture, desc);
220 const QSSGRhiSamplerDescription samplerDesc {
221 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat
223 QRhiSampler *sampler = rhiContext->sampler(samplerDesc);
226 const auto &envMapShaderStages = shaderCache->getBuiltInRhiShaders().getRhiEnvironmentmapShader();
229 QRhiBuffer *vertexBuffer = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer,
sizeof(cube));
230 vertexBuffer->create();
231 vertexBuffer->deleteLater();
232 rub->uploadStaticBuffer(vertexBuffer,
cube);
235 int ubufElementSize = rhi->ubufAligned(128);
236 QRhiBuffer *uBuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufElementSize * 6);
240 int ubufEnvMapElementSize = rhi->ubufAligned(4);
241 QRhiBuffer *uBufEnvMap = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufEnvMapElementSize * 6);
242 uBufEnvMap->create();
243 uBufEnvMap->deleteLater();
246 QRhiShaderResourceBindings *envMapSrb = rhi->newShaderResourceBindings();
247 envMapSrb->setBindings(
248 { QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, QRhiShaderResourceBinding::VertexStage, uBuf, 128),
249 QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(2, QRhiShaderResourceBinding::FragmentStage, uBufEnvMap, ubufEnvMapElementSize),
250 QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, sourceTexture, sampler) });
252 envMapSrb->deleteLater();
255 QRhiGraphicsPipeline *envMapPipeline = rhi->newGraphicsPipeline();
256 envMapPipeline->setCullMode(QRhiGraphicsPipeline::Front);
257 envMapPipeline->setFrontFace(QRhiGraphicsPipeline::CCW);
258 envMapPipeline->setShaderStages({ *envMapShaderStages->vertexStage(), *envMapShaderStages->fragmentStage() });
260 QRhiVertexInputLayout inputLayout;
261 inputLayout.setBindings({ { 3 *
sizeof(
float) } });
262 inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float3, 0 } });
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");
270 envMapPipeline->deleteLater();
273 cb->debugMarkBegin(
"Environment Cubemap Generation");
274 const QRhiCommandBuffer::VertexInput vbufBinding(vertexBuffer, 0);
277 QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix();
278 mvp.perspective(90.0f, 1.0f, 0.1f, 10.0f);
280 auto lookAt = [](
const QVector3D &eye,
const QVector3D ¢er,
const QVector3D &up) {
281 QMatrix4x4 viewMatrix;
282 viewMatrix.lookAt(eye, center, up);
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)));
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)));
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);
302 cb->resourceUpdate(rub);
304 for (
int face = 0; face < 6; ++face) {
305 cb->beginPass(renderTargets[face], QColor(0, 0, 0, 1), { 1.0f, 0 },
nullptr, rhiContext->commonPassFlags());
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 )}
315 cb->setShaderResources(envMapSrb, 2, dynamicOffset.constData());
324 rub = rhi->nextResourceUpdateBatch();
325 rub->generateMips(envCubeMap);
326 cb->resourceUpdate(rub);
330 cb->debugMarkBegin(
"Pre-filtered Environment Cubemap Generation");
331 QRhiTexture *preFilteredEnvCubeMap = rhi->newTexture(QRhiTexture::RGBA16F,
334 QRhiTexture::RenderTarget | QRhiTexture::CubeMap | QRhiTexture::MipMapped);
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);
339 QMap<
int, QSize> mipLevelSizes;
340 QMap<
int, QVarLengthArray<QRhiTextureRenderTarget *, 6>> renderTargetsMap;
341 QRhiRenderPassDescriptor *renderPassDescriptorPhase2 =
nullptr;
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);
349 QVarLengthArray<QRhiTextureRenderTarget *, 6> renderTargets;
350 for (
int face = 0; face < 6; ++face) {
351 QRhiColorAttachment att(preFilteredEnvCubeMap);
353 att.setLevel(mipLevel);
354 QRhiTextureRenderTargetDescription rtDesc;
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;
366 renderTargetsMap.insert(mipLevel, renderTargets);
367 renderPassDescriptorPhase2->deleteLater();
371 const auto &prefilterShaderStages = shaderCache->getBuiltInRhiShaders().getRhienvironmentmapPreFilterShader(isRGBE);
374 const QSSGRhiSamplerDescription samplerMipMapDesc {
375 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat
378 QRhiSampler *envMapCubeSampler =
nullptr;
381 envMapCubeSampler = rhiContext->sampler(samplerMipMapDesc);
383 envMapCubeSampler = sampler;
395 int ubufPrefilterElementSize = rhi->ubufAligned(20);
396 QRhiBuffer *uBufPrefilter = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufPrefilterElementSize * mipmapCount);
397 uBufPrefilter->create();
398 uBufPrefilter->deleteLater();
401 QRhiShaderResourceBindings *preFilterSrb = rhi->newShaderResourceBindings();
402 preFilterSrb->setBindings({
403 QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, QRhiShaderResourceBinding::VertexStage, uBuf, 128),
404 QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(2, QRhiShaderResourceBinding::FragmentStage, uBufPrefilter, 20),
405 QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, envCubeMap, envMapCubeSampler)
407 preFilterSrb->create();
408 preFilterSrb->deleteLater();
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()
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();
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;
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);
444 cb->resourceUpdate(rub);
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) }
457 cb->setShaderResources(preFilterSrb, 2, dynamicOffsets.constData());
466 const quint32 numberOfMipmapLevels = renderTargetsMap.size();
467 const quint32 numberOfFaces = 6;
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;
478 static const char key[] =
"QT_IBL_BAKER_VERSION";
479 static const char value[] =
"1";
481 constexpr size_t keyAndValueByteSize =
sizeof(key) +
sizeof(value);
482 appendBinaryVector(keyValueData, keyAndValueByteSize);
483 appendBinaryVector(keyValueData, key);
484 appendBinaryVector(keyValueData, value);
487 const size_t padding = 3 - ((keyAndValueByteSize + 3) % 4);
488 keyValueData.resize(keyValueData.size() + padding);
494 ktxOutputFile.write(ktxIdentifier, KTX_IDENTIFIER_LENGTH);
497 writeUInt32(ktxOutputFile, quint32(platformEndianIdentifier));
503 writeUInt32(ktxOutputFile, quint32(FORMAT.getSizeofFormat()) / quint32(FORMAT.getNumberOfComponent()));
506 writeUInt32(ktxOutputFile, quint32(
GL_RGBA));
509 writeUInt32(ktxOutputFile, quint32(
GL_RGBA16F));
512 writeUInt32(ktxOutputFile, quint32(
GL_RGBA));
515 writeUInt32(ktxOutputFile, quint32(environmentMapSize.width()));
518 writeUInt32(ktxOutputFile, quint32(environmentMapSize.height()));
521 writeUInt32(ktxOutputFile, quint32(0));
524 writeUInt32(ktxOutputFile, quint32(0));
527 writeUInt32(ktxOutputFile, quint32(numberOfFaces));
530 writeUInt32(ktxOutputFile, quint32(numberOfMipmapLevels));
533 writeUInt32(ktxOutputFile, quint32(keyValueData.size()));
536 ktxOutputFile.write(keyValueData.data(), keyValueData.size());
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];
545 Q_ASSERT(rhi->isRecordingFrame());
547 const auto texture = renderTarget->description().cbeginColorAttachments()->texture();
549 QRhiReadbackResult result;
550 QRhiReadbackDescription readbackDesc(texture);
551 readbackDesc.setLayer(
int(face));
552 readbackDesc.setLevel(mipmap_level);
554 QRhiResourceUpdateBatch *resourceUpdates = rhi->nextResourceUpdateBatch();
555 resourceUpdates->readBackTexture(readbackDesc, &result);
557 cb->resourceUpdate(resourceUpdates);
561 if (imageSize == 0) {
562 imageSize = result.data.size();
563 writeUInt32(ktxOutputFile, quint32(imageSize));
566 ktxOutputFile.write(result.data);
570 ktxOutputFile.close();
572 preFilteredEnvCubeMap->deleteLater();
574 rhi->endOffscreenFrame();