Revision control
++ // Copyright (C) 2023 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
++
++ //! [0]
++ #include <QGuiApplication>
++ #include <QImage>
++ #include <QFile>
++ #include <rhi/qrhi.h>
++
++ int main(int argc, char **argv)
++ {
++ QGuiApplication app(argc, argv);
++
++ #if QT_CONFIG(vulkan)
++ QVulkanInstance inst;
++ #endif
++ std::unique_ptr<QRhi> rhi;
++ #if defined(Q_OS_WIN)
++ QRhiD3D12InitParams params;
++ rhi.reset(QRhi::create(QRhi::D3D12, ¶ms));
++ #elif QT_CONFIG(metal)
++ QRhiMetalInitParams params;
++ rhi.reset(QRhi::create(QRhi::Metal, ¶ms));
++ #elif QT_CONFIG(vulkan)
++ inst.setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions());
++ if (inst.create()) {
++ QRhiVulkanInitParams params;
++ params.inst = &inst;
++ rhi.reset(QRhi::create(QRhi::Vulkan, ¶ms));
++ } else {
++ qFatal("Failed to create Vulkan instance");
++ }
++ #endif
++ if (rhi)
++ qDebug() << rhi->backendName() << rhi->driverInfo();
++ else
++ qFatal("Failed to initialize RHI");
++
++ float rotation = 0.0f;
++ float opacity = 1.0f;
++ int opacityDir = 1;
++
++ std::unique_ptr<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8,
++ QSize(1280, 720),
++ 1,
++ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
++ tex->create();
++ std::unique_ptr<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ tex.get() }));
++ std::unique_ptr<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor());
++ rt->setRenderPassDescriptor(rp.get());
++ rt->create();
++
++ QMatrix4x4 viewProjection = rhi->clipSpaceCorrMatrix();
++ viewProjection.perspective(45.0f, 1280 / 720.f, 0.01f, 1000.0f);
++ viewProjection.translate(0, 0, -4);
++
++ static float vertexData[] = { // Y up, CCW
++ 0.0f, 0.5f, 1.0f, 0.0f, 0.0f,
++ -0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
++ 0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
++ };
++
++ std::unique_ptr<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable,
++ QRhiBuffer::VertexBuffer,
++ sizeof(vertexData)));
++ vbuf->create();
++
++ std::unique_ptr<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic,
++ QRhiBuffer::UniformBuffer,
++ 64 + 4));
++ ubuf->create();
++
++ std::unique_ptr<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
++ srb->setBindings({
++ QRhiShaderResourceBinding::uniformBuffer(0,
++ QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage,
++ ubuf.get())
++ });
++ srb->create();
++
++ std::unique_ptr<QRhiGraphicsPipeline> ps(rhi->newGraphicsPipeline());
++ QRhiGraphicsPipeline::TargetBlend premulAlphaBlend;
++ premulAlphaBlend.enable = true;
++ ps->setTargetBlends({ premulAlphaBlend });
++ static auto getShader = [](const QString &name) {
++ QFile f(name);
++ return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader();
++ };
++ ps->setShaderStages({
++ { QRhiShaderStage::Vertex, getShader(QLatin1String("color.vert.qsb")) },
++ { QRhiShaderStage::Fragment, getShader(QLatin1String("color.frag.qsb")) }
++ });
++ QRhiVertexInputLayout inputLayout;
++ inputLayout.setBindings({
++ { 5 * sizeof(float) }
++ });
++ inputLayout.setAttributes({
++ { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
++ { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) }
++ });
++ ps->setVertexInputLayout(inputLayout);
++ ps->setShaderResourceBindings(srb.get());
++ ps->setRenderPassDescriptor(rp.get());
++ ps->create();
++
++ QRhiCommandBuffer *cb;
++ for (int frame = 0; frame < 20; ++frame) {
++ rhi->beginOffscreenFrame(&cb);
++
++ QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
++ if (frame == 0)
++ u->uploadStaticBuffer(vbuf.get(), vertexData);
++
++ QMatrix4x4 mvp = viewProjection;
++ mvp.rotate(rotation, 0, 1, 0);
++ u->updateDynamicBuffer(ubuf.get(), 0, 64, mvp.constData());
++ rotation += 5.0f;
++
++ u->updateDynamicBuffer(ubuf.get(), 64, 4, &opacity);
++ opacity += opacityDir * 0.2f;
++ if (opacity < 0.0f || opacity > 1.0f) {
++ opacityDir *= -1;
++ opacity = qBound(0.0f, opacity, 1.0f);
++ }
++
++ cb->beginPass(rt.get(), Qt::green, { 1.0f, 0 }, u);
++ cb->setGraphicsPipeline(ps.get());
++ cb->setViewport({ 0, 0, 1280, 720 });
++ cb->setShaderResources();
++ const QRhiCommandBuffer::VertexInput vbufBinding(vbuf.get(), 0);
++ cb->setVertexInput(0, 1, &vbufBinding);
++ cb->draw(3);
++ QRhiReadbackResult readbackResult;
++ u = rhi->nextResourceUpdateBatch();
++ u->readBackTexture({ tex.get() }, &readbackResult);
++ cb->endPass(u);
++
++ rhi->endOffscreenFrame();
++
++ QImage image(reinterpret_cast<const uchar *>(readbackResult.data.constData()),
++ readbackResult.pixelSize.width(),
++ readbackResult.pixelSize.height(),
++ QImage::Format_RGBA8888_Premultiplied);
++ if (rhi->isYUpInFramebuffer())
++ image.flip();
++ image.save(QString::asprintf("frame%d.png", frame));
++ }
++
++ return 0;
++ }
++ //! [0]