{"id":6004,"date":"2023-11-24T08:12:27","date_gmt":"2023-11-24T07:12:27","guid":{"rendered":"https:\/\/www.basyskom.de\/?p=6004"},"modified":"2024-07-17T17:57:52","modified_gmt":"2024-07-17T15:57:52","slug":"hello-rhi-how-to-get-started-with-qt-rhi","status":"publish","type":"post","link":"https:\/\/www.basyskom.de\/en\/hello-rhi-how-to-get-started-with-qt-rhi\/","title":{"rendered":"Hello, RHI &#8211; How to get started with Qt RHI"},"content":{"rendered":"<div data-elementor-type=\"wp-post\" data-elementor-id=\"6004\" class=\"elementor elementor-6004\" data-elementor-post-type=\"post\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2a79224 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-column-slider-no wpr-equal-height-no e-con e-parent\" data-id=\"2a79224\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-a512db4 elementor-widget elementor-widget-heading\" data-id=\"a512db4\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">A bit of history<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-267a98f elementor-widget elementor-widget-text-editor\" data-id=\"267a98f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>More than a decade ago, Qt solely focused on OpenGL to deliver hardware accelerated graphics. That was the time of <a href=\"https:\/\/het.as.utexas.edu\/HET\/Software\/html\/qglwidget.html\" target=\"_blank\" rel=\"noopener\">QGLWidget <\/a><\/span><span class=\"inline-comment-marker valid active\" data-ref=\"ba85b79f-c07b-4654-afca-f16786e4fabe\">to use OpenGL in QWidget applications. The second generation of QtQuick has been built on an OpenGL scenegraph to render scenes written in QML.<\/p>\n\n<p>But the times have changed: OpenGL could never truly succeed on Windows; instead Microsoft has further developed and pushed Direct3D. Apple introduced the API Metal as the canoncial way to render graphic scenes on its operating systems while deprecating OpenGL at the same time. The Khronos consortium &#8211; the same consortium that is responsible for OpenGL &#8211; unveiled Vulkan in the year 2016. All of the new graphics APIs have in common that theyoptimize GPU utilization more effectively by significantly reducing overhead.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-1e877e9 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-column-slider-no wpr-equal-height-no e-con e-parent\" data-id=\"1e877e9\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8279649 elementor-widget elementor-widget-heading\" data-id=\"8279649\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Meet RHI<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1e9aaea elementor-widget elementor-widget-text-editor\" data-id=\"1e9aaea\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tQt&#8217;s answer to the fragmented landscape is RHI (Rendering Hardware Interface): RHI delivers a low-level API that makes it possible to write code once and target all the different graphic backends:\n<ul>\n \t<li>OpenGL ES<\/li>\n \t<li>Metal<\/li>\n \t<li>Direct3D 11<\/li>\n \t<li>Direct3D 12<\/li>\n \t<li>Vulkan<\/li>\n<\/ul>\n<br>\n<p>The API has been available since the late Qt5 days, and it has become publicly accessible starting from Qt 6.6. As RHI is very low level, even drawing something as simple as a single triangle requires some amount of code. If you just want to add a shader to your QtQuick programm or light and display a 3D object, you may consider other solutions (like a ShaderEffect in QtQuick or Qt 3D for a 3D scene). But if you need full control to implement your ideas, RHI may be a helpful API in your toolbox.<\/p> <p>For example, we used RHI to implement the <a href=\"https:\/\/www.basyskom.de\/en\/2023\/introducing-the-riveqtquickplugin-powerful-animations-for-your-qtquick-applications\/\">RiveQtQuickPlugin<\/a>.<\/p><p><strong>This blog post<\/strong> will guide you step by step through a basic demo application: We will create a QWindow and initialize a rendering pipeline to render some basic geometry to it.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-49d551e elementor-widget elementor-widget-heading\" data-id=\"49d551e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">The building blocks<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-954b6eb elementor-widget elementor-widget-text-editor\" data-id=\"954b6eb\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>In its most basic form, an application needs the following building blocks to render something into a window:<\/p>\n\n<p>At initialization time:<\/p>\n<ul>\n \t<li>An instance of a <a href=\"https:\/\/alpqr.github.io\/qtrhi\/qrhi.html#\" target=\"_blank\" rel=\"noopener\">QRhi<\/a> object: This is the basic class that helps to setup everything:<\/li>\n \t<li>A QRhiSwapChain: A swap chain represents the connection between the API and the window surface.<\/li>\n \t<li>Some buffers to store the geometries and inputs to the shaders. When using RHI, you use the class QRhiBuffer to store this kind of data. A QRhiResourceUpdateBatch is used to move the data to memory accessible by the GPU<\/li>\n \t<li>A rendering pipeline that defines what to do with the aforementioned buffers. A pipeline consists of shader stages and information about how the vertex data is structured. You also need to define what to do with the rendering result. In detail, the following must be considered\n<ul>\n \t<li>To define how the geometry is laid out, use QRhiVertexInputLayout: Does the geometry buffer just store vertex positions? Does it also include texture coordinates and per vertex colors? Is the data distributed over multiple buffers or stored in one buffer? This defines how the geometry buffer is used as the input for the vertex shader (see below).<\/li>\n \t<li>You need at least two shader stages: A vertex shader and a fragment shader. The vertex shader takes the vertices and transforms them to so-called clip space positions. The fragment shader determines how the primitive is filled.<\/li>\n \t<li>You need to inform the pipeline about uniforms and textures. Uniforms are inputs that remain valid throughout all invocations of the shader (for example screen resolutions, time steps, etc.). This is done with QRhiShaderResourceBindings<\/li>\n \t<li>Last but not least the pipeline needs to know something about where to render into. In our example, the triangle is rendered via the swap chain to the window. But you can also render to an offscreen texture. Besides one or multiple color buffers (the image that is rendered), also a depth and\/or stencil buffer may be attached to the target. This is where QRhiRenderPassDescriptor comes into place. In RHI the descriptor is created from the swap chain or the render target.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-881662b elementor-widget elementor-widget-text-editor\" data-id=\"881662b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>During rendering:<\/p>\n<ul>\n \t<li>Rendering of a frame is done between beginFrame() and endFrame()<\/li>\n \t<li>QRhiCommandBuffer: A command buffer needs to be created during rendering: Modern architectures collect rendering commands to move them to the GPU in one batch. For architectures that don&#8217;t support command buffers, RHI emulates the behavior<\/li>\n \t<li>Rendering of a single pass is done between beginPass() and endPass(). Hereby, rendering to a frame may consist of multiple passes. For example you may decide to render different parts of your scene to different offscreen textures and blend them together in a last pass.<\/li>\n \t<li>Set a graphics pipeline you configured during initialization<\/li>\n \t<li>Set the viewport size<\/li>\n \t<li>Set your input geometry<\/li>\n \t<li>Call draw() to render the geometry<\/li>\n<\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-a883553 elementor-widget elementor-widget-text-editor\" data-id=\"a883553\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>So much for the theory. The source code in the next section demonstrates how all of this fits together by drawing a triangle to the screen:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3b97c0e elementor-widget elementor-widget-heading\" data-id=\"3b97c0e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Walkthrough<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4d6de81 elementor-widget elementor-widget-text-editor\" data-id=\"4d6de81\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>We begin by implementing the main.cpp:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f3315da elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"f3315da\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre data-no-translation=\"\"><code class='language-clike' data-no-translation=\"\">#include &lt;QGuiApplication&gt;\n#include &lt;QLoggingCategory&gt;\n \n#include &quot;hellotriangle.h&quot;\n \n \nint main(int argc, char **argv)\n{\n    QGuiApplication app(argc, argv);\n    QLoggingCategory::setFilterRules(QLatin1String(&quot;qt.rhi.*=true&quot;));\n     \n    HelloTriangle window;\n    window.resize(600, 600);\n    window.setTitle(QCoreApplication::applicationName());\n    window.show();\n \n    int retVal = app.exec();\n \n    if (window.handle()) {\n        window.releaseSwapChain();\n    }\n \n    return retVal;\n} <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-4abe965 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-column-slider-no wpr-equal-height-no e-con e-parent\" data-id=\"4abe965\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2655cd4 elementor-widget elementor-widget-text-editor\" data-id=\"2655cd4\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Here, we just create a QGuiApplication and the HelloTriangle window. When the logging category ist set to the qt.rhi domain, Qt prints some debug messages about the graphics driver on the console. The HelloTriangle class is implemented below:<\/p><p><br \/>First let&#8217;s have a look into the header file:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-87e9961 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"87e9961\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre data-no-translation=\"\"><code class='language-clike' data-no-translation=\"\">#pragma once\n \n#include &lt;QWindow&gt;\n#include &lt;QVector&gt;\n \n#include &lt;private\/qrhi_p.h&gt;\n \nclass QExposeEvent;\nclass QEvent;\n \nclass HelloTriangle : public QWindow\n{\n \n  Q_OBJECT\n \npublic:\n  HelloTriangle(QWindow *parent = nullptr);\n  ~HelloTriangle();\n \n  void releaseSwapChain();\n \npublic slots:\n  void render();\n \nprotected:\n  void exposeEvent(QExposeEvent *exposeEvent) override;\n  bool event(QEvent *event) override;\n \n \nprivate:\n  void maybeInitialize();\n  QShader loadShader(const QString &amp;filename);\n \n \n  bool m_isInitialized { false };\n  bool m_maybeResizeSwapChain { false };\n \n  QRhi *m_rhi { nullptr };\n  QRhiSwapChain *m_swapChain { nullptr };\n \n  QRhiRenderPassDescriptor *m_renderPassDescriptor { nullptr };\n  QRhiResourceUpdateBatch *m_initialUpdates { nullptr };\n \n  QRhiBuffer *m_vertexBuffer { nullptr };\n  QRhiShaderResourceBindings *m_shaderResourceBindings { nullptr };\n  QRhiGraphicsPipeline *m_renderingPipeline { nullptr };\n \n \n  QVector&lt;QRhiResource *&gt; m_releasePool;\n \n}; <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c0a61aa elementor-widget elementor-widget-text-editor\" data-id=\"c0a61aa\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tAnd below is the implementation, where the interesting parts are happening. We added number markers as comments to guide you step by step through the code:\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2ed1e04 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"2ed1e04\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre data-no-translation=\"\"><code class='language-clike' data-no-translation=\"\">#include &quot;hellotriangle.h&quot;\n \n#include &lt;QCoreApplication&gt;\n#include &lt;QExposeEvent&gt;\n#include &lt;QPlatformSurfaceEvent&gt;\n#include &lt;QEvent&gt;\n#include &lt;QFile&gt;\n#include &lt;QOffscreenSurface&gt;\n \n#if QT_VERSION &gt;= QT_VERSION_CHECK(6, 6, 0)\n  #include &lt;QRhi&gt;\n#else\n  #include &lt;private\/qrhigles2_p.h&gt;\n#endif\n \nstatic float vertexData[] = {\n    -0.5f, -0.5f, 0.0f,\n     0.5f, -0.5f, 0.0f,\n     0.0f,  0.5f, 0.0f\n};\n \nHelloTriangle::HelloTriangle(QWindow *parent)\n    : QWindow(parent)\n{\n    setSurfaceType(OpenGLSurface);                                                 \/\/ 1\n}\n \nHelloTriangle::~HelloTriangle()\n{\n    qDeleteAll(m_releasePool);                                                     \/\/ 2\n    m_releasePool.clear();\n \n    delete m_rhi;\n    m_rhi = nullptr; \n}\n \nQShader HelloTriangle::loadShader(const QString &amp;filename)                         \/\/ 3\n{\n    QFile shaderFile(filename);\n    bool success = shaderFile.open(QIODevice::ReadOnly);\n    if (success) {\n        return QShader::fromSerialized(shaderFile.readAll());\n    }\n \n    qWarning() &lt;&lt; &quot;Cannot open shader file:&quot; &lt;&lt; filename;\n    return QShader();\n}\n \n \nvoid HelloTriangle::exposeEvent(QExposeEvent *exposeEvent)                        \/\/ 4\n{\n    if (isExposed()) {\n        m_maybeResizeSwapChain = true;\n        maybeInitialize();\n        render();\n    }\n \n    return QWindow::exposeEvent(exposeEvent);\n}\n \nbool HelloTriangle::event(QEvent *event)                                          \/\/ 5\n{\n     \n    switch (event-&gt;type()) {\n    case QEvent::UpdateRequest:\n        render();\n        break;\n    case QEvent::PlatformSurface:\n        if (static_cast&lt;QPlatformSurfaceEvent *&gt;(event)-&gt;surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {\n            releaseSwapChain();\n        }\n        break;\n    }\n    return QWindow::event(event);\n}\n \nvoid  HelloTriangle::maybeInitialize()                                           \/\/ 6\n{\n    if (m_isInitialized) {\n        return;\n    }\n \n    const int sampleCount = 1;\n     \n    \/\/ init RHI and swap chain to render into a window\n    QRhiGles2InitParams params;                                                 \/\/ 7\n    params.window = this;\n    QOffscreenSurface *fallbackSurface = QRhiGles2InitParams::newFallbackSurface();\n    params.fallbackSurface = fallbackSurface;\n \n    m_rhi = QRhi::create(QRhi::OpenGLES2, &amp;params);\n     \n    m_swapChain = m_rhi-&gt;newSwapChain();                                       \/\/ 8\n    m_releasePool &lt;&lt; m_swapChain;                                              \/\/ 9\n \n    m_swapChain-&gt;setWindow(this);                                              \/\/ 10\n    m_swapChain-&gt;setSampleCount(sampleCount);\n     \n    m_renderPassDescriptor = m_swapChain-&gt;newCompatibleRenderPassDescriptor(); \/\/ 11\n    m_releasePool &lt;&lt; m_renderPassDescriptor;\n     \n    m_swapChain-&gt;setRenderPassDescriptor(m_renderPassDescriptor);\n    m_swapChain-&gt;createOrResize();\n \n    \/\/ init vertex data\n    m_initialUpdates = m_rhi-&gt;nextResourceUpdateBatch();                       \/\/ 12\n \n    m_vertexBuffer = m_rhi-&gt;newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData));     \/\/ 13\n    m_vertexBuffer-&gt;create();\n    m_releasePool &lt;&lt; m_vertexBuffer;\n    m_initialUpdates-&gt;uploadStaticBuffer(m_vertexBuffer, 0, sizeof(vertexData), vertexData);\n \n    \/\/ define vertex layout                                                   \/\/ 14\n    QRhiVertexInputLayout vertexInputLayout;\n    vertexInputLayout.setBindings({ { 3 * sizeof(float) } });\n    vertexInputLayout.setAttributes({\n        { 0, 0, QRhiVertexInputAttribute::Float3, 0 },\n    });\n \n    \/\/ there must be a QRhiShaderResourceBindings\n    \/\/ the hello triangle example does not have any uniforms and textures as shader inputs, so we leave it empty\n    m_shaderResourceBindings = m_rhi-&gt;newShaderResourceBindings();           \/\/ 15\n    m_releasePool &lt;&lt; m_shaderResourceBindings;\n    m_shaderResourceBindings-&gt;create();\n \n    \/\/ init rendering pipeline\n    m_renderingPipeline = m_rhi-&gt;newGraphicsPipeline();                      \/\/ 16\n    m_releasePool &lt;&lt; m_renderingPipeline;\n    m_renderingPipeline-&gt;setShaderStages({\n        { QRhiShaderStage::Vertex, loadShader(QLatin1String(&quot;:\/shaders\/hellotriangle.vert.qsb&quot;)) },\n        { QRhiShaderStage::Fragment, loadShader(QLatin1String(&quot;:\/shaders\/hellotriangle.frag.qsb&quot;)) }\n    });\n    m_renderingPipeline-&gt;setTopology(QRhiGraphicsPipeline::Triangles);\n    m_renderingPipeline-&gt;setVertexInputLayout(vertexInputLayout);\n    m_renderingPipeline-&gt;setShaderResourceBindings(m_shaderResourceBindings);\n    m_renderingPipeline-&gt;setRenderPassDescriptor(m_renderPassDescriptor);\n    m_renderingPipeline-&gt;create();\n \n    \/\/ request next frame\n    m_isInitialized = true;\n \n}\n \nvoid  HelloTriangle::render()\n{\n    if (!isExposed() || !m_isInitialized || !m_swapChain) {\n        return;\n    }\n \n    if (m_swapChain-&gt;currentPixelSize() != m_swapChain-&gt;surfacePixelSize() || m_maybeResizeSwapChain) {         \/\/ 17\n        bool resizeDidWork = m_swapChain-&gt;createOrResize();\n        if (!resizeDidWork) {\n            return;\n        }\n        m_maybeResizeSwapChain = false;\n    }\n \n    const QSize outputPixelSize = m_swapChain-&gt;currentPixelSize();\n \n    m_rhi-&gt;beginFrame(m_swapChain);                                                                             \/\/ 18\n    QRhiCommandBuffer *commandBuffer = m_swapChain-&gt;currentFrameCommandBuffer();                                \/\/ 19\n    QRhiResourceUpdateBatch *updateBatch = m_rhi-&gt;nextResourceUpdateBatch();                                    \/\/ 20\n \n    if (m_initialUpdates) {\n        updateBatch-&gt;merge(m_initialUpdates);\n        m_initialUpdates-&gt;release();\n        m_initialUpdates = nullptr;\n    }\n \n    commandBuffer-&gt;beginPass(m_swapChain-&gt;currentFrameRenderTarget(), Qt::black, { 1.0, 0}, updateBatch);      \/\/ 21\n    commandBuffer-&gt;setGraphicsPipeline(m_renderingPipeline);                                                   \/\/ 22\n    commandBuffer-&gt;setViewport({ 0, 0, float(outputPixelSize.width()), float(outputPixelSize.height()) });\n    commandBuffer-&gt;setShaderResources();\n    const QRhiCommandBuffer::VertexInput vertexInputBinding(m_vertexBuffer, 0);                                \/\/ 23\n    commandBuffer-&gt;setVertexInput(0, 1, &amp;vertexInputBinding);\n    commandBuffer-&gt;draw(3);                                                                                    \/\/ 24\n    commandBuffer-&gt;endPass();\n     \n    m_rhi-&gt;endFrame(m_swapChain);                                                                              \/\/ 25\n \n    requestUpdate();                                                                                           \/\/ 26\n \n}\n \nvoid HelloTriangle::releaseSwapChain()                                                                         \/\/ 27\n{\n    if (m_swapChain) {\n        m_swapChain-&gt;destroy();\n    }\n} <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b6f2481 elementor-widget elementor-widget-text-editor\" data-id=\"b6f2481\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>To break down the source code piece by piece, here is what happens:<\/p>\n<p>1) For didactic purposes, we simplify the code by hardcoding the window&#8217;s surface type to OpenGLSurface. In a real world application, you most probably want to be more flexible and determine the <a href=\"https:\/\/doc.qt.io\/qt-6\/qsurface.html#SurfaceType-enum\" target=\"_blank\" rel=\"noopener\">surface type<\/a> from the platform on which the application has been compiled<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-f2ee311 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-column-slider-no wpr-equal-height-no e-con e-parent\" data-id=\"f2ee311\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2f995bb elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"2f995bb\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre data-no-translation=\"\"><code class='language-clike' data-no-translation=\"\">#if defined(Q_OS_WIN)\n    setSurfaceType(Direct3DSurface); ;\n#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)\n    setSurfaceType(MetalSurface);\n#elif defined(Q_VULKAN)\n    setSurfaceType(VulkanSurface);\n#else\n    setSurfaceType(OpenGLSurface);\n#endif <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b35b5c6 elementor-widget elementor-widget-text-editor\" data-id=\"b35b5c6\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>2) The destructor: It is a common practice to store all RHI resources in a list for convenient batch deletion. QRhi does not inherit from QRhiResources, so we handle its deletion separately.<\/p>\n<p>3) A helper function to load shaders. A shader must be distributed in the <a href=\"https:\/\/doc.qt.io\/qt-6\/qtshadertools-qsb.html\" target=\"_blank\" rel=\"noopener\">QSB format<\/a> &#8211; a container format to store (compiled) shader code for different graphics APIs. See also the next chapter.<\/p>\n<p>4) Overrides the QWindow::exposeEvent method. When the window is first exposed, we need to initialize the rendering pipeline. This event is also triggered, when the user resizes the window. In this case, we may have to resize the swap chain later in the rendering code, which is indicated by the m_maybeResizeSwapChain flag.<\/p>\n<p>5) Overrides QWindow::event: We listen to update and platform surface events. Whenever there is an update request, a render step is triggered; when the window is closed, we get notified, that the surface is about to get destroyed, and we are going to destroy the swap chain.<\/p>\n<p>6) The initialization code; everything that needs to get done before the first call to render<\/p>\n<p>7) Create the central QRhi object; depending on the platform, the initialization process is different. The RHI backend must match the surface type of the window. For the different initialization parameters refer to the <a href=\"https:\/\/doc.qt.io\/qt-6\/qrhiinitparams.html\" target=\"_blank\" rel=\"noopener\">documentation<\/a>.<\/p>\n<p>8) The swap chain connect RHI and the windows<\/p>\n<p>9) To avoid deleting all ressources separately, we just append them to a release pool list<\/p>\n<p >10) Initialize the swap chain: A sample count value above 1 enables Multi-Sample Antialiasing (MSAA). Don&#8217;t forget to set the window, otherwise the application will crash at startup.<\/p>\n<p>11) The renderpass descriptor is essential for the render pipeline that we are going to create in a few more steps. When the pipeline renders the triangle, it must know what to do with the results, i.e. which buffers to use for storing color, depth and stencil values, and their respective types. In RHI, you create compatible descriptors from the swap chain or texture targets. After creating a descriptor, it must be set for the swap chain or the texture target, as RHI doesn&#8217;t handle this automatically. In this example, the pipeline will only render into a color buffer; there is no attached depth or stencil target.<\/p>\n<p >12) RHI batches upload operations; here we create an initial upload batch that we are going to commit when rendering the first frame<\/p>\n<p >13) here, we create a buffer to store the vertices for the triangle. The buffer is static, i.e. we don&#8217;t update the vertex data between draw calls.<\/p>\n<p >14) From the outside, the vertex buffer contains just numbers. The pipeline needs to know about the structure of the buffer. In other words, we need a mapping between the data format and the attributes we declare in the vertex shader. As a reminder, this ist the triangle&#8217;s geometry:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c0ab66d elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"c0ab66d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre data-no-translation=\"\"><code class='language-clike' data-no-translation=\"\">static float vertexData[] = {\n-0.5f, -0.5f, 0.0f,\n0.5f, -0.5f, 0.0f,\n0.0f, 0.5f, 0.0f\n};  <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5a58a04 elementor-widget elementor-widget-text-editor\" data-id=\"5a58a04\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tThere a three points with an x-, y- and z-coordinate. For each point the vertex shader is invoked once. The input to the vertex shader is declared in the following fashion:\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-01188e5 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"01188e5\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre data-no-translation=\"\"><code class='language-clike' data-no-translation=\"\">layout(location = 0) in vec3 position; <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-89376b6 elementor-widget elementor-widget-text-editor\" data-id=\"89376b6\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tNow the declaration of the QRhiVertexInputLayout should make more sense:\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2ee6f48 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"2ee6f48\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre data-no-translation=\"\"><code class='language-clike' data-no-translation=\"\">\/\/ define vertex layout\nQRhiVertexInputLayout vertexInputLayout;\nvertexInputLayout.setBindings({ { 3 * sizeof(float) } });\nvertexInputLayout.setAttributes({\n    { 0, 0, QRhiVertexInputAttribute::Float3, 0 },\n}); <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-531f7c4 elementor-widget elementor-widget-text-editor\" data-id=\"531f7c4\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tWe have one attribute. The first 0 in the attribute definition is the binding point for the vertex buffer. The second 0 is the location (the same location we declared in the vertex shader) of the attribute. The attribute expects a vector consisting of three floats (Float3) with an offset of 0 in the vertex buffer.\nSo, what happens if we need also a texture coordinate next to the vertex position?\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e724bbc elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"e724bbc\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre data-no-translation=\"\"><code class='language-clike' data-no-translation=\"\">\/\/ geometry with position and texture coordinate (x, y, z, u, v)\nstatic float vertexData[] = {\n-0.5f, -0.5f, 0.0f, 0.0f, 0.0f,\n0.5f, -0.5f, 0.0f,  1.0f, 0.0f,\n0.0f, 0.5f, 0.0f,   0.5f, 1.0f,\n};\n \n\/\/ vertex shader input\nlayout(location = 0) in vec3 position;\nlayout(location = 1) in vec3 texCoord;\n \n\/\/ define vertex layout in C++\n    QRhiVertexInputLayout vertexInputLayout;\n    vertexInputLayout.setBindings({ { 5 * sizeof(float) } });\n    vertexInputLayout.setAttributes({\n        { 0, 0, QRhiVertexInputAttribute::Float3, 0 },\n        { 0, 1, QRhiVertexInputAttribute::Float2, quint32(3 * sizeof(float))},\n   }); <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-301c090 elementor-widget elementor-widget-text-editor\" data-id=\"301c090\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Now, each vertex consists of five numbers: x, y, z positions and the u, v texture coordinates.\u00a0 The vertex shader gets a second attribute with the location 1. Each invocation fetches five numbers from the buffer where the first three numbers form the input of the first attribute, and the next two numbers form the input of the second attribute.The binding point for both attributes is 0, as they are fed from the same buffer at binding point 0.<\/p>\n<p>15)\u00a0In the basic triangle example, the shaders don&#8217;t get any uniforms or textures as input. Nevertheless, the pipeline expects a shader resource binding object. Here, we just create one without setting any bindings on it.<\/p>\n<p>16) Finally, we are prepared to create the pipeline. A pipeline must have at least one vertex shader and one fragment shader. The pipeline needs to know how the input is laid out (vertex attributes and shader resources like uniforms and textures), and where to render the results (render pass descriptor).<\/p>\n<p>17) After initialization and setting up at least one rendering pipeline, we are ready to render the triangle. First, we check whether the swap chain needs to be resized (for example, when the user resizes the window and triggers an expose event)<\/p>\n<p>18) Rendering a frame happens always between a call to beginFrame() and endFrame()<\/p>\n<p>19) Modern rendering APIs collect all rendering commands in a rendering buffer, that is commited to the GPU in one batch<\/p>\n<p>20) Here, we request a new update batch from RHI that can be used to update any dynamic buffers like uniform buffers. Before rendering the first frame, we merge the initial updates with the new update batch. We pass the update batch as a parameter to beginPass(), so that RHI can commit the batch.<\/p>\n<p>21) A render pass is executed. A render pass renders to a specific target like a texture or the swap chain. In this case, we render to the window via the swap chain. We set the clear color to black and initialization values for the depth and stencil buffers to 1.0 and 0 respectively (note, that depth and stencil buffers do not exist in the example, but it is not possible to omit the values).<\/p>\n<p>22) During one render pass, we may use multiple rendering pipelines to render to the same render target. For this example, we set the rendering pipeline responsible for the triangle.<\/p>\n<p>23) Set the vertex input, and bind the vertex buffer to binding point 0<\/p>\n<p>24) Draw three points<\/p>\n<p>25) Request a new frame. Since the triangle is not animated, the line can be commented out. Qt ensures that update events triggered with requestUpdate() are in sync with the display vsync signal of your monitor, if possible.<\/p>\n<p>Et voil\u00e0, after executing these 25 steps, the application renders a triangle:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-29a885e elementor-widget elementor-widget-image\" data-id=\"29a885e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<img fetchpriority=\"high\" decoding=\"async\" width=\"601\" height=\"630\" src=\"https:\/\/www.basyskom.de\/wp-content\/uploads\/2023\/11\/hellotriangle_RHI_Blogpost.png\" class=\"attachment-large size-large wp-image-6005\" alt=\"basysKom, HMI Dienstleistung, Qt, Cloud, Azure\" srcset=\"https:\/\/www.basyskom.de\/wp-content\/uploads\/2023\/11\/hellotriangle_RHI_Blogpost.png 601w, https:\/\/www.basyskom.de\/wp-content\/uploads\/2023\/11\/hellotriangle_RHI_Blogpost-286x300.png 286w, https:\/\/www.basyskom.de\/wp-content\/uploads\/2023\/11\/hellotriangle_RHI_Blogpost-560x587.png 560w\" sizes=\"(max-width: 601px) 100vw, 601px\">\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7282136 elementor-widget elementor-widget-heading\" data-id=\"7282136\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">The shader code<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-60e6b0d elementor-widget elementor-widget-text-editor\" data-id=\"60e6b0d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>The graphics pipeline must have at least two programmable shader stages: a vertex shader stage to process vertex geometry and a fragment shader stage to colorize the geometry. For the triangle, we are using a simple passthrough vertex shader that doesn&#8217;t do any transformations. In most applications, you most probably want to multiply the vertex with a model view matrix and a projection matrix.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b571d12 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"b571d12\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre data-no-translation=\"\"><code class='language-clike' data-no-translation=\"\">#version 440\n \nlayout(location = 0) in vec3 position;\n \nout gl_PerVertex { vec4 gl_Position; };\n \nvoid main()\n{\n    gl_Position = vec4(position, 1.0);\n} <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e1c5370 elementor-widget elementor-widget-text-editor\" data-id=\"e1c5370\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tThe fragment shader outputs a red color for each pixel inside the triangle:\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-be88e8a elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"be88e8a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre data-no-translation=\"\"><code class='language-clike' data-no-translation=\"\">#version 440\n \nlayout(location = 0) out vec4 fragColor;\n \nvoid main()\n{\n    fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n} <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5659e73 elementor-widget elementor-widget-text-editor\" data-id=\"5659e73\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tHow does it work to distribute the shader code to different backend APIs? Each API comes with its own shader language: Direct3D uses <a href=\"https:\/\/de.wikipedia.org\/wiki\/High_Level_Shading_Language\" target=\"_blank\" rel=\"noopener\">HLSL<\/a>, Metal uses <a href=\"https:\/\/developer.apple.com\/metal\/Metal-Shading-Language-Specification.pdf\" target=\"_blank\" rel=\"noopener\">MSL<\/a>, and when using OpenGL, you would write your shaders in <a href=\"https:\/\/de.wikipedia.org\/wiki\/OpenGL_Shading_Language\" target=\"_blank\" rel=\"noopener\">GLSL<\/a>. The answer to the question is to write the shader code in modern GLSL, compile it to an intermediate format called <a href=\"https:\/\/www.khronos.org\/spir\/\" target=\"_blank\" rel=\"noopener\">SPIR-V<\/a> and transpile it to an end result suitable for the different backends. Fortunately, Qt comes with a tool called qsb (Qt Shader Baker) to do the work for us and to embed the results for all the different API backends in a single file. The tool can easily be included in your build system when you are using <a href=\"https:\/\/doc.qt.io\/qt-6\/qtshadertools-build.html\" target=\"_blank\" rel=\"noopener\">cmake<\/a>.\n\nTo manually transpile the shader given in the above example, use the command:\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e172579 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"e172579\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre data-no-translation=\"\"><code class='language-bash' data-no-translation=\"\">qsb --glsl &quot;100 es,120,150&quot; --hlsl 50 --msl 12 -o hellotriangle.vert.qsb hellotriangle.vert\nqsb --glsl &quot;100 es,120,150&quot; --hlsl 50 --msl 12 -o hellotriangle.frag.qsb hellotriangle.frag <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0659cd0 elementor-widget elementor-widget-text-editor\" data-id=\"0659cd0\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tThis makes it comfortable to write your shader code once, and use it on different target platforms.\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-693c840 elementor-widget elementor-widget-heading\" data-id=\"693c840\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Conclusion<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-eb4d88b elementor-widget elementor-widget-text-editor\" data-id=\"eb4d88b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>We have illustrated the process of setting up a graphics pipeline with RHI to render hardware-accelerated content on a variety of platform-specific APIs, including OpenGL, DirectX 11 &amp; 12, Metal, and Vulkan. After reading the blog post, you should be able to upload and render geometry onto a window.<\/p>\n\n<p>Of course, in real world applications you might want to render onto a QWidget or within a QtQuick application. Many shaders receive uniform buffers and textures as input. <\/p><strong>But this is not all: <\/strong>RHI enables more complex tasks, such as utilizing tesselation, geometry or even compute shader stages, configuring stencil buffers and scissor rectangles, or rendering to offscreen buffers. In upcoming blog posts, we&#8217;ll delve deeper into these technologies.<\/p>\n<p>\nPlease let us know in the comments, if you have already worked with RHI and in which use cases you are interested.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>","protected":false},"excerpt":{"rendered":"<p>For some time now, Qt has been internally utilizing RHI (Rendering Hardware Interface), a new cross-platform technology for graphic rendering. Since Qt 6.6, this API has been semi-public, meaning that the API is mature for practical use but may still be subject to potential changes between major Qt versions. <\/p>\n<p>In this blog post, we demonstrate how to to get started with RHI.<\/p>","protected":false},"author":16,"featured_media":4011,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1,2,7,720,8],"tags":[262,15,264],"class_list":["post-6004","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-allgemein","category-blog","category-general","category-newsletter","category-qt","tag-how-to","tag-qt","tag-rhi"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/6004","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/users\/16"}],"replies":[{"embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/comments?post=6004"}],"version-history":[{"count":17,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/6004\/revisions"}],"predecessor-version":[{"id":10737,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/6004\/revisions\/10737"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/media\/4011"}],"wp:attachment":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/media?parent=6004"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/categories?post=6004"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/tags?post=6004"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}