64static const float cube[] = {
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,
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,
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,
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,
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,
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,
84 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
87 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
90 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
93 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
96 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
99 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
104 qDebug() <<
"Using RHI backend" << name;
107 QFile ktxOutputFile(outPath);
108 if (!ktxOutputFile.open(QIODevice::WriteOnly)) {
109 return QStringLiteral(
"Could not open file: %1").arg(outPath);
112 QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(),
nullptr));
114 return QStringLiteral(
"Failed to initialize QRhi");
116 qDebug() << rhi->driverInfo();
118 QRhiCommandBuffer *cb;
119 rhi->beginOffscreenFrame(&cb);
121 const auto rhiContext = std::make_unique<QSSGRhiContext>(rhi.get());
122 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiContext.get());
123 rhiCtxD->setCommandBuffer(cb);
125 QScopedPointer<QSSGLoadedTexture> inImage(QSSGLoadedTexture::loadHdrImage(QSSGInputUtil::getStreamForFile(inPath), FORMAT));
127 return QStringLiteral(
"Failed to load hdr file");
129 auto shaderCache = std::make_unique<QSSGShaderCache>(*rhiContext);
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;
163 QRhiTexture *envCubeMap = rhi->newTexture(QRhiTexture::RGBA16F,
166 QRhiTexture::RenderTarget | QRhiTexture::CubeMap | QRhiTexture::MipMapped
167 | QRhiTexture::UsedWithGenerateMips);
168 if (!envCubeMap->create()) {
169 return QStringLiteral(
"Failed to create Environment Cube Map");
171 envCubeMap->deleteLater();
174 QRhiRenderBuffer *envMapRenderBuffer = rhi->newRenderBuffer(QRhiRenderBuffer::Color, environmentMapSize);
175 if (!envMapRenderBuffer->create()) {
176 return QStringLiteral(
"Failed to create Environment Map Render Buffer");
178 envMapRenderBuffer->deleteLater();
181 QVarLengthArray<QRhiTextureRenderTarget *, 6> renderTargets;
182 QRhiRenderPassDescriptor *renderPassDesc =
nullptr;
183 for (
int face = 0; face < 6; ++face) {
184 QRhiColorAttachment att(envCubeMap);
186 QRhiTextureRenderTargetDescription rtDesc;
187 rtDesc.setColorAttachments({ att });
188 auto renderTarget = rhi->newTextureRenderTarget(rtDesc);
189 renderTarget->setDescription(rtDesc);
191 renderPassDesc = renderTarget->newCompatibleRenderPassDescriptor();
192 renderTarget->setRenderPassDescriptor(renderPassDesc);
193 if (!renderTarget->create()) {
194 return QStringLiteral(
"Failed to build env map render target");
196 renderTarget->deleteLater();
197 renderTargets << renderTarget;
199 renderPassDesc->deleteLater();
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");
207 sourceTexture->deleteLater();
210 QRhiTextureUploadDescription desc;
211 if (inImage->textureFileData.isValid()) {
214 { inImage->textureFileData.data().constData() + inImage->textureFileData.dataOffset(0),
215 quint32(inImage->textureFileData.dataLength(0)) } } };
217 desc = { { 0, 0, { inImage->data, inImage->dataSizeInBytes } } };
219 auto *rub = rhi->nextResourceUpdateBatch();
220 rub->uploadTexture(sourceTexture, desc);
222 const QSSGRhiSamplerDescription samplerDesc {
223 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat
225 QRhiSampler *sampler = rhiContext->sampler(samplerDesc);
228 const auto &envMapShaderStages = shaderCache->getBuiltInRhiShaders().getRhiEnvironmentmapShader();
231 QRhiBuffer *vertexBuffer = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer,
sizeof(cube));
232 vertexBuffer->create();
233 vertexBuffer->deleteLater();
234 rub->uploadStaticBuffer(vertexBuffer,
cube);
237 int ubufElementSize = rhi->ubufAligned(128);
238 QRhiBuffer *uBuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufElementSize * 6);
242 int ubufEnvMapElementSize = rhi->ubufAligned(4);
243 QRhiBuffer *uBufEnvMap = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufEnvMapElementSize * 6);
244 uBufEnvMap->create();
245 uBufEnvMap->deleteLater();
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) });
254 envMapSrb->deleteLater();
257 QRhiGraphicsPipeline *envMapPipeline = rhi->newGraphicsPipeline();
258 envMapPipeline->setCullMode(QRhiGraphicsPipeline::Front);
259 envMapPipeline->setFrontFace(QRhiGraphicsPipeline::CCW);
260 envMapPipeline->setShaderStages({ *envMapShaderStages->vertexStage(), *envMapShaderStages->fragmentStage() });
262 QRhiVertexInputLayout inputLayout;
263 inputLayout.setBindings({ { 3 *
sizeof(
float) } });
264 inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float3, 0 } });
266 envMapPipeline->setVertexInputLayout(inputLayout);
267 envMapPipeline->setShaderResourceBindings(envMapSrb);
268 envMapPipeline->setRenderPassDescriptor(renderPassDesc);
269 if (!envMapPipeline->create()) {
270 return QStringLiteral(
"Failed to create source env map pipeline state");
272 envMapPipeline->deleteLater();
275 cb->debugMarkBegin(
"Environment Cubemap Generation");
276 const QRhiCommandBuffer::VertexInput vbufBinding(vertexBuffer, 0);
279 QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix();
280 mvp.perspective(90.0f, 1.0f, 0.1f, 10.0f);
282 auto lookAt = [](
const QVector3D &eye,
const QVector3D ¢er,
const QVector3D &up) {
283 QMatrix4x4 viewMatrix;
284 viewMatrix.lookAt(eye, center, up);
287 QVarLengthArray<QMatrix4x4, 6> views;
288 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(1.0, 0.0, 0.0), QVector3D(0.0f, -1.0f, 0.0f)));
289 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(-1.0, 0.0, 0.0), QVector3D(0.0f, -1.0f, 0.0f)));
290 if (rhi->isYUpInFramebuffer()) {
291 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)));
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 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, 0.0, 1.0), QVector3D(0.0f, -1.0f, 0.0f)));
298 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 0.0, -1.0), QVector3D(0.0f, -1.0f, 0.0f)));
299 for (
int face = 0; face < 6; ++face) {
300 rub->updateDynamicBuffer(uBuf, face * ubufElementSize, 64, mvp.constData());
301 rub->updateDynamicBuffer(uBuf, face * ubufElementSize + 64, 64, views[face].constData());
302 rub->updateDynamicBuffer(uBufEnvMap, face * ubufEnvMapElementSize, 4, &colorSpace);
304 cb->resourceUpdate(rub);
306 for (
int face = 0; face < 6; ++face) {
307 cb->beginPass(renderTargets[face], QColor(0, 0, 0, 1), { 1.0f, 0 },
nullptr, rhiContext->commonPassFlags());
310 cb->setGraphicsPipeline(envMapPipeline);
311 cb->setVertexInput(0, 1, &vbufBinding);
312 cb->setViewport(QRhiViewport(0, 0, environmentMapSize.width(), environmentMapSize.height()));
313 QVector<QPair<
int, quint32>> dynamicOffset = {
314 { 0, quint32(ubufElementSize * face) },
315 { 2, quint32(ubufEnvMapElementSize * face )}
317 cb->setShaderResources(envMapSrb, 2, dynamicOffset.constData());
326 rub = rhi->nextResourceUpdateBatch();
327 rub->generateMips(envCubeMap);
328 cb->resourceUpdate(rub);
332 cb->debugMarkBegin(
"Pre-filtered Environment Cubemap Generation");
333 QRhiTexture *preFilteredEnvCubeMap = rhi->newTexture(QRhiTexture::RGBA16F,
336 QRhiTexture::RenderTarget | QRhiTexture::CubeMap | QRhiTexture::MipMapped);
337 if (!preFilteredEnvCubeMap->create())
338 qWarning(
"Failed to create Pre-filtered Environment Cube Map");
339 int mipmapCount = rhi->mipLevelsForSize(environmentMapSize);
340 mipmapCount = qMin(mipmapCount, 6);
341 QMap<
int, QSize> mipLevelSizes;
342 QMap<
int, QVarLengthArray<QRhiTextureRenderTarget *, 6>> renderTargetsMap;
343 QRhiRenderPassDescriptor *renderPassDescriptorPhase2 =
nullptr;
346 for (
int mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) {
347 const QSize levelSize = QSize(environmentMapSize.width() * std::pow(0.5, mipLevel),
348 environmentMapSize.height() * std::pow(0.5, mipLevel));
349 mipLevelSizes.insert(mipLevel, levelSize);
351 QVarLengthArray<QRhiTextureRenderTarget *, 6> renderTargets;
352 for (
int face = 0; face < 6; ++face) {
353 QRhiColorAttachment att(preFilteredEnvCubeMap);
355 att.setLevel(mipLevel);
356 QRhiTextureRenderTargetDescription rtDesc;
357 rtDesc.setColorAttachments({ att });
358 auto renderTarget = rhi->newTextureRenderTarget(rtDesc);
359 renderTarget->setDescription(rtDesc);
360 if (!renderPassDescriptorPhase2)
361 renderPassDescriptorPhase2 = renderTarget->newCompatibleRenderPassDescriptor();
362 renderTarget->setRenderPassDescriptor(renderPassDescriptorPhase2);
363 if (!renderTarget->create())
364 qWarning(
"Failed to build prefilter env map render target");
365 renderTarget->deleteLater();
366 renderTargets << renderTarget;
368 renderTargetsMap.insert(mipLevel, renderTargets);
369 renderPassDescriptorPhase2->deleteLater();
373 const auto &prefilterShaderStages = shaderCache->getBuiltInRhiShaders().getRhienvironmentmapPreFilterShader(isRGBE);
376 const QSSGRhiSamplerDescription samplerMipMapDesc {
377 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat
380 QRhiSampler *envMapCubeSampler =
nullptr;
383 envMapCubeSampler = rhiContext->sampler(samplerMipMapDesc);
385 envMapCubeSampler = sampler;
397 int ubufPrefilterElementSize = rhi->ubufAligned(20);
398 QRhiBuffer *uBufPrefilter = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufPrefilterElementSize * mipmapCount);
399 uBufPrefilter->create();
400 uBufPrefilter->deleteLater();
403 QRhiShaderResourceBindings *preFilterSrb = rhi->newShaderResourceBindings();
404 preFilterSrb->setBindings({
405 QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, QRhiShaderResourceBinding::VertexStage, uBuf, 128),
406 QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(2, QRhiShaderResourceBinding::FragmentStage, uBufPrefilter, 20),
407 QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, envCubeMap, envMapCubeSampler)
409 preFilterSrb->create();
410 preFilterSrb->deleteLater();
413 QRhiGraphicsPipeline *prefilterPipeline = rhi->newGraphicsPipeline();
414 prefilterPipeline->setCullMode(QRhiGraphicsPipeline::Front);
415 prefilterPipeline->setFrontFace(QRhiGraphicsPipeline::CCW);
416 prefilterPipeline->setDepthOp(QRhiGraphicsPipeline::LessOrEqual);
417 prefilterPipeline->setShaderStages({
418 *prefilterShaderStages->vertexStage(),
419 *prefilterShaderStages->fragmentStage()
422 prefilterPipeline->setVertexInputLayout(inputLayout);
423 prefilterPipeline->setShaderResourceBindings(preFilterSrb);
424 prefilterPipeline->setRenderPassDescriptor(renderPassDescriptorPhase2);
425 if (!prefilterPipeline->create())
426 return QStringLiteral(
"Failed to create pre-filter env map pipeline state");
427 prefilterPipeline->deleteLater();
431 rub = rhi->nextResourceUpdateBatch();
432 const float resolution = environmentMapSize.width();
433 const float lodBias = 0.0f;
434 const int sampleCount = 1024;
435 for (
int mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) {
436 Q_ASSERT(mipmapCount - 2);
437 const float roughness =
float(mipLevel) /
float(mipmapCount - 2);
438 const int distribution = mipLevel == (mipmapCount - 1) ? 0 : 1;
439 rub->updateDynamicBuffer(uBufPrefilter, mipLevel * ubufPrefilterElementSize, 4, &roughness);
440 rub->updateDynamicBuffer(uBufPrefilter, mipLevel * ubufPrefilterElementSize + 4, 4, &resolution);
441 rub->updateDynamicBuffer(uBufPrefilter, mipLevel * ubufPrefilterElementSize + 4 + 4, 4, &lodBias);
442 rub->updateDynamicBuffer(uBufPrefilter, mipLevel * ubufPrefilterElementSize + 4 + 4 + 4, 4, &sampleCount);
443 rub->updateDynamicBuffer(uBufPrefilter, mipLevel * ubufPrefilterElementSize + 4 + 4 + 4 + 4, 4, &distribution);
446 cb->resourceUpdate(rub);
449 for (
int mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) {
450 for (
int face = 0; face < 6; ++face) {
451 cb->beginPass(renderTargetsMap[mipLevel][face], QColor(0, 0, 0, 1), { 1.0f, 0 },
nullptr, rhiContext->commonPassFlags());
452 cb->setGraphicsPipeline(prefilterPipeline);
453 cb->setVertexInput(0, 1, &vbufBinding);
454 cb->setViewport(QRhiViewport(0, 0, mipLevelSizes[mipLevel].width(), mipLevelSizes[mipLevel].height()));
455 QVector<QPair<
int, quint32>> dynamicOffsets = {
456 { 0, quint32(ubufElementSize * face) },
457 { 2, quint32(ubufPrefilterElementSize * mipLevel) }
459 cb->setShaderResources(preFilterSrb, 2, dynamicOffsets.constData());
468 const quint32 numberOfMipmapLevels = renderTargetsMap.size();
469 const quint32 numberOfFaces = 6;
471 constexpr size_t KTX_IDENTIFIER_LENGTH = 12;
472 constexpr char ktxIdentifier[KTX_IDENTIFIER_LENGTH] = {
'\xAB',
'K',
'T',
'X',
' ',
'1',
473 '1',
'\xBB',
'\r',
'\n',
'\x1A',
'\n' };
474 constexpr quint32 platformEndianIdentifier = 0x04030201;
475 QVector<
char> keyValueData;
480 static const char key[] =
"QT_IBL_BAKER_VERSION";
481 static const char value[] =
"1";
483 constexpr size_t keyAndValueByteSize =
sizeof(key) +
sizeof(value);
484 appendBinaryVector(keyValueData, keyAndValueByteSize);
485 appendBinaryVector(keyValueData, key);
486 appendBinaryVector(keyValueData, value);
489 const size_t padding = 3 - ((keyAndValueByteSize + 3) % 4);
490 keyValueData.resize(keyValueData.size() + padding);
496 ktxOutputFile.write(ktxIdentifier, KTX_IDENTIFIER_LENGTH);
499 writeUInt32(ktxOutputFile, quint32(platformEndianIdentifier));
505 writeUInt32(ktxOutputFile, quint32(FORMAT.getSizeofFormat()) / quint32(FORMAT.getNumberOfComponent()));
508 writeUInt32(ktxOutputFile, quint32(
GL_RGBA));
511 writeUInt32(ktxOutputFile, quint32(
GL_RGBA16F));
514 writeUInt32(ktxOutputFile, quint32(
GL_RGBA));
517 writeUInt32(ktxOutputFile, quint32(environmentMapSize.width()));
520 writeUInt32(ktxOutputFile, quint32(environmentMapSize.height()));
523 writeUInt32(ktxOutputFile, quint32(0));
526 writeUInt32(ktxOutputFile, quint32(0));
529 writeUInt32(ktxOutputFile, quint32(numberOfFaces));
532 writeUInt32(ktxOutputFile, quint32(numberOfMipmapLevels));
535 writeUInt32(ktxOutputFile, quint32(keyValueData.size()));
538 ktxOutputFile.write(keyValueData.data(), keyValueData.size());
541 for (quint32 mipmap_level = 0; mipmap_level < numberOfMipmapLevels; mipmap_level++) {
542 quint32 imageSize = 0;
543 for (size_t face = 0; face < numberOfFaces; face++) {
544 QRhiTextureRenderTarget *renderTarget = renderTargetsMap[mipmap_level][face];
547 Q_ASSERT(rhi->isRecordingFrame());
549 const auto descr = renderTarget->description();
550 const auto texture = descr.cbeginColorAttachments()->texture();
552 QRhiReadbackResult result;
553 QRhiReadbackDescription readbackDesc(texture);
554 readbackDesc.setLayer(
int(face));
555 readbackDesc.setLevel(mipmap_level);
557 QRhiResourceUpdateBatch *resourceUpdates = rhi->nextResourceUpdateBatch();
558 resourceUpdates->readBackTexture(readbackDesc, &result);
560 cb->resourceUpdate(resourceUpdates);
564 if (imageSize == 0) {
565 imageSize = result.data.size();
566 writeUInt32(ktxOutputFile, quint32(imageSize));
569 ktxOutputFile.write(result.data);
573 ktxOutputFile.close();
575 preFilteredEnvCubeMap->deleteLater();
577 rhi->endOffscreenFrame();