diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/quick/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/quick/doc/snippets/qquickrhiitem/qquickrhiitem_intro.cpp | 147 | ||||
| -rw-r--r-- | src/quick/items/qquickgraphicsconfiguration.cpp | 6 | ||||
| -rw-r--r-- | src/quick/items/qquickrhiitem.cpp | 1098 | ||||
| -rw-r--r-- | src/quick/items/qquickrhiitem.h | 117 | ||||
| -rw-r--r-- | src/quick/items/qquickrhiitem_p.h | 101 |
6 files changed, 1468 insertions, 2 deletions
diff --git a/src/quick/CMakeLists.txt b/src/quick/CMakeLists.txt index fbfab68160..ca0947cdb9 100644 --- a/src/quick/CMakeLists.txt +++ b/src/quick/CMakeLists.txt @@ -75,6 +75,7 @@ qt_internal_add_qml_module(Quick items/qquickrectangle_p_p.h items/qquickrendercontrol.cpp items/qquickrendercontrol.h items/qquickrendercontrol_p.h items/qquickrendertarget.cpp items/qquickrendertarget.h items/qquickrendertarget_p.h + items/qquickrhiitem.cpp items/qquickrhiitem.h items/qquickrhiitem_p.h items/qquickscalegrid.cpp items/qquickscalegrid_p_p.h items/qquickscreen.cpp items/qquickscreen_p.h diff --git a/src/quick/doc/snippets/qquickrhiitem/qquickrhiitem_intro.cpp b/src/quick/doc/snippets/qquickrhiitem/qquickrhiitem_intro.cpp new file mode 100644 index 0000000000..3a3d1585bf --- /dev/null +++ b/src/quick/doc/snippets/qquickrhiitem/qquickrhiitem_intro.cpp @@ -0,0 +1,147 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include <QtQuick/QQuickRhiItem> +#include <rhi/qrhi.h> + +//![0] +class ExampleRhiItemRenderer : public QQuickRhiItemRenderer +{ +public: + void initialize(QRhiCommandBuffer *cb) override; + void synchronize(QQuickRhiItem *item) override; + void render(QRhiCommandBuffer *cb) override; + +private: + QRhi *m_rhi = nullptr; + std::unique_ptr<QRhiBuffer> m_vbuf; + std::unique_ptr<QRhiBuffer> m_ubuf; + std::unique_ptr<QRhiShaderResourceBindings> m_srb; + std::unique_ptr<QRhiGraphicsPipeline> m_pipeline; + QMatrix4x4 m_viewProjection; + float m_angle = 0.0f; +}; + +class ExampleRhiItem : public QQuickRhiItem +{ + Q_OBJECT + QML_NAMED_ELEMENT(ExampleRhiItem) + Q_PROPERTY(float angle READ angle WRITE setAngle NOTIFY angleChanged) + +public: + QQuickRhiItemRenderer *createRenderer() override; + + float angle() const { return m_angle; } + void setAngle(float a); + +signals: + void angleChanged(); + +private: + float m_angle = 0.0f; +}; + +QQuickRhiItemRenderer *ExampleRhiItem::createRenderer() +{ + return new ExampleRhiItemRenderer; +} + +void ExampleRhiItem::setAngle(float a) +{ + if (m_angle == a) + return; + + m_angle = a; + emit angleChanged(); + update(); +} + +void ExampleRhiItemRenderer::synchronize(QQuickRhiItem *rhiItem) +{ + ExampleRhiItem *item = static_cast<ExampleRhiItem *>(rhiItem); + if (item->angle() != m_angle) + m_angle = item->angle(); +} + +static QShader getShader(const QString &name) +{ + QFile f(name); + return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader(); +} + +static float vertexData[] = { + 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, +}; + +void ExampleRhiItemRenderer::initialize(QRhiCommandBuffer *cb) +{ + if (m_rhi != rhi()) { + m_pipeline.reset(); + m_rhi = rhi(); + } + + if (!m_pipeline) { + m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData))); + m_vbuf->create(); + + m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64)); + m_ubuf->create(); + + m_srb.reset(m_rhi->newShaderResourceBindings()); + m_srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, m_ubuf.get()), + }); + m_srb->create(); + + m_pipeline.reset(m_rhi->newGraphicsPipeline()); + m_pipeline->setShaderStages({ + { QRhiShaderStage::Vertex, getShader(QLatin1String(":/shaders/color.vert.qsb")) }, + { QRhiShaderStage::Fragment, getShader(QLatin1String(":/shaders/color.frag.qsb")) } + }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 5 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) } + }); + m_pipeline->setVertexInputLayout(inputLayout); + m_pipeline->setShaderResourceBindings(m_srb.get()); + m_pipeline->setRenderPassDescriptor(renderTarget()->renderPassDescriptor()); + m_pipeline->create(); + + QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch(); + resourceUpdates->uploadStaticBuffer(m_vbuf.get(), vertexData); + cb->resourceUpdate(resourceUpdates); + } + + const QSize outputSize = renderTarget()->pixelSize(); + m_viewProjection = m_rhi->clipSpaceCorrMatrix(); + m_viewProjection.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f); + m_viewProjection.translate(0, 0, -4); +} + +void ExampleRhiItemRenderer::render(QRhiCommandBuffer *cb) +{ + QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch(); + QMatrix4x4 modelViewProjection = m_viewProjection; + modelViewProjection.rotate(m_angle, 0, 1, 0); + resourceUpdates->updateDynamicBuffer(m_ubuf.get(), 0, 64, modelViewProjection.constData()); + + const QColor clearColor = QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f); + cb->beginPass(renderTarget(), clearColor, { 1.0f, 0 }, resourceUpdates); + + cb->setGraphicsPipeline(m_pipeline.get()); + const QSize outputSize = renderTarget()->pixelSize(); + cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height())); + cb->setShaderResources(); + const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0); + cb->setVertexInput(0, 1, &vbufBinding); + cb->draw(3); + + cb->endPass(); +} +//![0] diff --git a/src/quick/items/qquickgraphicsconfiguration.cpp b/src/quick/items/qquickgraphicsconfiguration.cpp index 8c9bd1b2cb..520c3b5284 100644 --- a/src/quick/items/qquickgraphicsconfiguration.cpp +++ b/src/quick/items/qquickgraphicsconfiguration.cpp @@ -54,8 +54,10 @@ QT_BEGIN_NAMESPACE Vulkan, or graphics APIs where the concept is applicable. Where some concepts are not applicable, the related settings are simply ignored. - Examples of functions in this category are preferredInstanceExtensions() - and setDeviceExtensions(). + Examples of functions in this category are setDeviceExtensions() and + preferredInstanceExtensions(). The latter is useful when the application + manages its own \l QVulkanInstance which is then associated with the + QQuickWindow via \l QWindow::setVulkanInstance(). \section1 Qt Quick Scene Graph Renderer Configuration diff --git a/src/quick/items/qquickrhiitem.cpp b/src/quick/items/qquickrhiitem.cpp new file mode 100644 index 0000000000..211bec6d51 --- /dev/null +++ b/src/quick/items/qquickrhiitem.cpp @@ -0,0 +1,1098 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qquickrhiitem_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QQuickRhiItem + \inmodule QtQuick + \since 6.7 + + \brief The QQuickRhiItem class is a portable alternative to + QQuickFramebufferObject that is not tied to OpenGL, but rather allows + integrating rendering with the QRhi APIs with Qt Quick. + + \preliminary + + \note QQuickRhiItem is in tech preview in Qt 6.7. \b {The API is under + development and subject to change.} + + QQuickRhiItem is effectively the counterpart of \l QRhiWidget in the world of + Qt Quick. Both of these are meant to be subclassed, and they both enable + recording QRhi-based rendering that targets an offscreen color buffer. The + resulting 2D image is then composited with the rest of the Qt Quick scene. + + \note While QQuickRhiItem is a public Qt API, the QRhi family of classes in + the Qt Gui module, including QShader and QShaderDescription, offer limited + compatibility guarantees. There are no source or binary compatibility + guarantees for these classes, meaning the API is only guaranteed to work + with the Qt version the application was developed against. Source + incompatible changes are however aimed to be kept at a minimum and will + only be made in minor releases (6.7, 6.8, and so on). \c{qquickrhiitem.h} + does not directly include any QRhi-related headers. To use those classes + when implementing a QQuickRhiItem subclass, link to + \c{Qt::GuiPrivate} (if using CMake), and include the appropriate headers + with the \c rhi prefix, for example \c{#include <rhi/qrhi.h>}. + + QQuickRhiItem is a replacement for the legacy \l QQuickFramebufferObject + class. The latter is inherently tied to OpenGL / OpenGL ES, whereas + QQuickRhiItem works with the QRhi classes, allowing to run the same + rendering code with Vulkan, Metal, Direct 3D 11/12, and OpenGL / OpenGL ES. + Conceptually and functionally they are very close, and migrating from + QQuickFramebufferObject to QQuickRhiItem is straightforward. + QQuickFramebufferObject continues to be available to ensure compatibility + for existing application code that works directly with the OpenGL API. + + \note QQuickRhiItem will not be functional when using the \c software + adaptation of the Qt Quick scene graph. + + On most platforms, the scene graph rendering, and thus the rendering + performed by the QQuickRhiItem will occur on a \l {Scene Graph and + Rendering}{dedicated thread}. For this reason, the QQuickRhiItem class + enforces a strict separation between the item implementation (the + QQuickItem subclass) and the actual rendering logic. All item logic, such + as properties and UI-related helper functions exposed to QML must be + located in the QQuickRhiItem subclass. Everything that relates to rendering + must be located in the QQuickRhiItemRenderer class. To avoid race + conditions and read/write issues from two threads it is important that the + renderer and the item never read or write shared variables. Communication + between the item and the renderer should primarily happen via the + QQuickRhiItem::synchronize() function. This function will be called on the + render thread while the GUI thread is blocked. Using queued connections or + events for communication between item and renderer is also possible. + + Applications must subclass both QQuickRhiItem and QQuickRhiItemRenderer. + The pure virtual createRenderer() function must be reimplemented to return + a new instance of the QQuickRhiItemRenderer subclass. + + As with QRhiWidget, QQuickRhiItem automatically managed the color buffer, + which is a 2D texture (QRhiTexture) normally, or a QRhiRenderBuffer when + multisampling is in use. (some 3D APIs differentiate between textures and + renderbuffers, while with some others the underlying native resource is the + same; renderbuffers are used mainly to allow multisampling with OpenGL ES + 3.0) + + The size of the texture will by default adapt to the size of the item (with + the \l{QQuickWindow::effectiveDevicePixelRatio()}{device pixel ratio} taken + into account). If the item size changes, the texture is recreated with the + correct size. If a fixed size is preferred, set \l explicitTextureWidth and + \l explicitTextureHeight to non-zero values. + + QQuickRhiItem is a \l{QSGTextureProvider}{texture provider} and can be used + directly in \l {ShaderEffect}{ShaderEffects} and other classes that consume + texture providers. + + While not a primary use case, QQuickRhiItem also allows incorporating + rendering code that directly uses a 3D graphics API such as Vulkan, Metal, + Direct 3D, or OpenGL. See \l QRhiCommandBuffer::beginExternal() for details + on recording native commands within a QRhi render pass, as well as + \l QRhiTexture::createFrom() for a way to wrap an existing native texture and + then use it with QRhi in a subsequent render pass. See also + \l QQuickGraphicsConfiguration regarding configuring the native 3D API + environment (e.g. device extensions) and note that the \l QQuickWindow can be + associated with a custom \l QVulkanInstance by calling + \l QWindow::setVulkanInstance() early enough. + + \note QQuickRhiItem always uses the same QRhi instance the QQuickWindow + uses (and by extension, the same OpenGL context, Vulkan device, etc.). To + choose which underlying 3D graphics API is used, call + \l{QQuickWindow::setGraphicsApi()}{setGraphicsApi()} on the QQuickWindow + early enough. Changing it is not possible once the scene graph has + initialized, and all QQuickRhiItem instances in the scene will render using + the same 3D API. + + \section2 A simple example + + Take the following subclass of QQuickRhiItem. It is shown here in complete + form. It renders a single triangle with a perspective projection, where the + triangle is rotated based on the \c angle property of the custom item. + (meaning it can be driven for example with animations such as + \l NumberAnimation from QML) + + \snippet qquickrhiitem/qquickrhiitem_intro.cpp 0 + + It is notable that this simple class is almost exactly the same as the code + shown in the \l QRhiWidget introduction. The vertex and fragment shaders + are the same as shown there. + + Once exposed to QML (note the \c QML_NAMED_ELEMENT), our custom item can be + instantiated in any scene. (after importing the appropriate \c URI specified + for \l{qt6_add_qml_module}{qt_add_qml_module} in the CMake project) + + \code + ExampleRhiItem { + anchors.fill: parent + anchors.margins: 10 + NumberAnimation on angle { from: 0; to: 360: duration: 5000; loops: Animation.Infinite } + } + \endcode + + \sa QQuickRhiItemRenderer, QRhi, {Scene Graph and Rendering} + */ + +/*! + \class QQuickRhiItemRenderer + \inmodule QtQuick + \since 6.7 + + \brief A QQuickRhiItemRenderer implements the rendering logic of a + QQuickRhiItem. + + \preliminary + + \note QQuickRhiItem and QQuickRhiItemRenderer are in tech preview in Qt + 6.7. \b {The API is under development and subject to change.} + + \sa QQuickRhiItem, QRhi + */ + +QQuickRhiItemNode::QQuickRhiItemNode(QQuickRhiItem *item) + : m_item(item) +{ + m_window = m_item->window(); + connect(m_window, &QQuickWindow::beforeRendering, this, &QQuickRhiItemNode::render, + Qt::DirectConnection); + connect(m_window, &QQuickWindow::screenChanged, this, [this]() { + if (m_window->effectiveDevicePixelRatio() != m_dpr) + m_item->update(); + }, Qt::DirectConnection); +} + +QSGTexture *QQuickRhiItemNode::texture() const +{ + return m_sgTexture.get(); +} + +void QQuickRhiItemNode::resetColorBufferObjects() +{ + // owns either m_colorTexture or m_resolveTexture + m_sgTexture.reset(); + + m_colorTexture = nullptr; + m_resolveTexture = nullptr; + + m_msaaColorBuffer.reset(); +} + +void QQuickRhiItemNode::resetRenderTargetObjects() +{ + m_renderTarget.reset(); + m_renderPassDescriptor.reset(); + m_depthStencilBuffer.reset(); +} + +void QQuickRhiItemNode::sync() +{ + if (!m_rhi) { + m_rhi = m_window->rhi(); + if (!m_rhi) { + qWarning("No QRhi found for window %p, QQuickRhiItem will not be functional", m_window); + return; + } + } + + m_dpr = m_window->effectiveDevicePixelRatio(); + const int minTexSize = m_rhi->resourceLimit(QRhi::TextureSizeMin); + const int maxTexSize = m_rhi->resourceLimit(QRhi::TextureSizeMax); + + QQuickRhiItemPrivate *itemD = m_item->d_func(); + QSize newSize = QSize(itemD->explicitTextureWidth, itemD->explicitTextureHeight); + if (newSize.isEmpty()) + newSize = QSize(int(m_item->width()), int(m_item->height())) * m_dpr; + + newSize.setWidth(qMin(maxTexSize, qMax(minTexSize, newSize.width()))); + newSize.setHeight(qMin(maxTexSize, qMax(minTexSize, newSize.height()))); + + if (m_colorTexture) { + if (m_colorTexture->format() != itemD->rhiTextureFormat + || m_colorTexture->sampleCount() != itemD->samples) + { + resetColorBufferObjects(); + resetRenderTargetObjects(); + } + } + + if (m_msaaColorBuffer) { + if (m_msaaColorBuffer->backingFormat() != itemD->rhiTextureFormat + || m_msaaColorBuffer->sampleCount() != itemD->samples) + { + resetColorBufferObjects(); + resetRenderTargetObjects(); + } + } + + if (m_sgTexture && m_sgTexture->hasAlphaChannel() != itemD->blend) { + resetColorBufferObjects(); + resetRenderTargetObjects(); + } + + if (!m_colorTexture && itemD->samples <= 1) { + if (!m_rhi->isTextureFormatSupported(itemD->rhiTextureFormat)) { + qWarning("QQuickRhiItem: The requested texture format (%d) is not supported by the " + "underlying 3D graphics API implementation", int(itemD->rhiTextureFormat)); + } + m_colorTexture = m_rhi->newTexture(itemD->rhiTextureFormat, newSize, itemD->samples, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource); + if (!m_colorTexture->create()) { + qWarning("Failed to create backing texture for QQuickRhiItem"); + delete m_colorTexture; + m_colorTexture = nullptr; + return; + } + } + + if (itemD->samples > 1) { + if (!m_msaaColorBuffer) { + if (!m_rhi->isFeatureSupported(QRhi::MultisampleRenderBuffer)) { + qWarning("QQuickRhiItem: Multisample renderbuffers are reported as unsupported; " + "sample count %d will not work as expected", itemD->samples); + } + if (!m_rhi->isTextureFormatSupported(itemD->rhiTextureFormat)) { + qWarning("QQuickRhiItem: The requested texture format (%d) is not supported by the " + "underlying 3D graphics API implementation", int(itemD->rhiTextureFormat)); + } + m_msaaColorBuffer.reset(m_rhi->newRenderBuffer(QRhiRenderBuffer::Color, newSize, itemD->samples, + {}, itemD->rhiTextureFormat)); + if (!m_msaaColorBuffer->create()) { + qWarning("Failed to create multisample color buffer for QQuickRhiItem"); + m_msaaColorBuffer.reset(); + return; + } + } + if (!m_resolveTexture) { + m_resolveTexture = m_rhi->newTexture(itemD->rhiTextureFormat, newSize, 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource); + if (!m_resolveTexture->create()) { + qWarning("Failed to create resolve texture for QQuickRhiItem"); + delete m_resolveTexture; + m_resolveTexture = nullptr; + return; + } + } + } else if (m_resolveTexture) { + m_resolveTexture->deleteLater(); + m_resolveTexture = nullptr; + } + + if (m_colorTexture && m_colorTexture->pixelSize() != newSize) { + m_colorTexture->setPixelSize(newSize); + if (!m_colorTexture->create()) + qWarning("Failed to rebuild texture for QQuickRhiItem after resizing"); + } + + if (m_msaaColorBuffer && m_msaaColorBuffer->pixelSize() != newSize) { + m_msaaColorBuffer->setPixelSize(newSize); + if (!m_msaaColorBuffer->create()) + qWarning("Failed to rebuild multisample color buffer for QQuickRhiitem after resizing"); + } + + if (m_resolveTexture && m_resolveTexture->pixelSize() != newSize) { + m_resolveTexture->setPixelSize(newSize); + if (!m_resolveTexture->create()) + qWarning("Failed to rebuild resolve texture for QQuickRhiItem after resizing"); + } + + if (!m_sgTexture) { + QQuickWindow::CreateTextureOptions options; + if (itemD->blend) + options |= QQuickWindow::TextureHasAlphaChannel; + // the QSGTexture takes ownership of the QRhiTexture + m_sgTexture.reset(m_window->createTextureFromRhiTexture(m_colorTexture ? m_colorTexture : m_resolveTexture, + options)); + setTexture(m_sgTexture.get()); + } + + if (itemD->autoRenderTarget) { + const QSize pixelSize = m_colorTexture ? m_colorTexture->pixelSize() + : m_msaaColorBuffer->pixelSize(); + if (!m_depthStencilBuffer) { + m_depthStencilBuffer.reset(m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, itemD->samples)); + if (!m_depthStencilBuffer->create()) { + qWarning("Failed to create depth-stencil buffer for QQuickRhiItem"); + resetRenderTargetObjects(); + return; + } + } else if (m_depthStencilBuffer->pixelSize() != pixelSize) { + m_depthStencilBuffer->setPixelSize(pixelSize); + if (!m_depthStencilBuffer->create()) { + qWarning("Failed to rebuild depth-stencil buffer for QQuickRhiItem with new size"); + return; + } + } + if (!m_renderTarget) { + QRhiColorAttachment color0; + if (m_colorTexture) + color0.setTexture(m_colorTexture); + else + color0.setRenderBuffer(m_msaaColorBuffer.get()); + if (itemD->samples > 1) + color0.setResolveTexture(m_resolveTexture); + QRhiTextureRenderTargetDescription rtDesc(color0, m_depthStencilBuffer.get()); + m_renderTarget.reset(m_rhi->newTextureRenderTarget(rtDesc)); + m_renderPassDescriptor.reset(m_renderTarget->newCompatibleRenderPassDescriptor()); + m_renderTarget->setRenderPassDescriptor(m_renderPassDescriptor.get()); + if (!m_renderTarget->create()) { + qWarning("Failed to create render target for QQuickRhiitem"); + resetRenderTargetObjects(); + return; + } + } + } else { + resetRenderTargetObjects(); + } + + if (newSize != itemD->effectiveTextureSize) { + itemD->effectiveTextureSize = newSize; + emit m_item->effectiveTextureSizeChanged(); + } + + QRhiCommandBuffer *cb = queryCommandBuffer(); + if (cb) + m_renderer->initialize(cb); + + m_renderer->synchronize(m_item); +} + +QRhiCommandBuffer *QQuickRhiItemNode::queryCommandBuffer() +{ + QRhiSwapChain *swapchain = m_window->swapChain(); + QSGRendererInterface *rif = m_window->rendererInterface(); + + // Handle both cases: on-screen QQuickWindow vs. off-screen QQuickWindow + // e.g. by using QQuickRenderControl to redirect into a texture. + QRhiCommandBuffer *cb = swapchain ? swapchain->currentFrameCommandBuffer() + : static_cast<QRhiCommandBuffer *>( + rif->getResource(m_window, QSGRendererInterface::RhiRedirectCommandBuffer)); + + if (!cb) { + qWarning("QQuickRhiItem: Neither swapchain nor redirected command buffer are available."); + return nullptr; + } + + return cb; +} + +void QQuickRhiItemNode::render() +{ + // called before Qt Quick starts recording its main render pass + + if (!isValid() || !m_renderPending) + return; + + QRhiCommandBuffer *cb = queryCommandBuffer(); + if (!cb) + return; + + m_renderPending = false; + m_renderer->render(cb); + + markDirty(QSGNode::DirtyMaterial); + emit textureChanged(); +} + +/*! + Constructs a new QQuickRhiItem with the given \a parent. + */ +QQuickRhiItem::QQuickRhiItem(QQuickItem *parent) + : QQuickItem(*new QQuickRhiItemPrivate, parent) +{ + setFlag(ItemHasContents); +} + +/*! + \internal + */ +QSGNode *QQuickRhiItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) +{ + // Changing to an empty size should not involve destroying and then later + // recreating the node, because we do not know how expensive the user's + // renderer setup is. Rather, keep the node if it already exist, and clamp + // all accesses to width and height. Hence the unusual !oldNode condition here. + if (!oldNode && (width() <= 0 || height() <= 0)) + return nullptr; + + Q_D(QQuickRhiItem); + QQuickRhiItemNode *n = static_cast<QQuickRhiItemNode *>(oldNode); + if (!n) { + if (!d->node) + d->node = new QQuickRhiItemNode(this); + if (!d->node->hasRenderer()) { + QQuickRhiItemRenderer *r = createRenderer(); + if (r) { + r->data = d->node; + d->node->setRenderer(r); + } else { + qWarning("No QQuickRhiItemRenderer was created; the item will not render"); + delete d->node; + d->node = nullptr; + return nullptr; + } + } + n = d->node; + } + + n->sync(); + + if (!n->isValid()) { + delete n; + d->node = nullptr; + return nullptr; + } + + if (window()->rhi()->isYUpInFramebuffer()) { + n->setTextureCoordinatesTransform(d->mirrorVertically + ? QSGSimpleTextureNode::NoTransform + : QSGSimpleTextureNode::MirrorVertically); + } else { + n->setTextureCoordinatesTransform(d->mirrorVertically + ? QSGSimpleTextureNode::MirrorVertically + : QSGSimpleTextureNode::NoTransform); + } + n->setFiltering(d->smooth ? QSGTexture::Linear : QSGTexture::Nearest); + n->setRect(0, 0, qMax<int>(0, width()), qMax<int>(0, height())); + + n->scheduleUpdate(); + + return n; +} + +/*! + \reimp + */ +void QQuickRhiItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + QQuickItem::geometryChange(newGeometry, oldGeometry); + if (newGeometry.size() != oldGeometry.size()) + update(); +} + +/*! + \reimp + */ +void QQuickRhiItem::releaseResources() +{ + // called on the gui thread if the item is removed from scene + + Q_D(QQuickRhiItem); + d->node = nullptr; +} + +void QQuickRhiItem::invalidateSceneGraph() +{ + // called on the render thread when the scenegraph is invalidated + + Q_D(QQuickRhiItem); + d->node = nullptr; +} + +/*! + \reimp + */ +bool QQuickRhiItem::isTextureProvider() const +{ + return true; +} + +/*! + \reimp + */ +QSGTextureProvider *QQuickRhiItem::textureProvider() const +{ + if (QQuickItem::isTextureProvider()) // e.g. if Item::layer::enabled == true + return QQuickItem::textureProvider(); + + Q_D(const QQuickRhiItem); + if (!d->node) // create a node to have a provider, the texture will be null but that's ok + d->node = new QQuickRhiItemNode(const_cast<QQuickRhiItem *>(this)); + + return d->node; +} + +/*! + \property QQuickRhiItem::sampleCount + + This property controls for sample count for multisample antialiasing. + By default the value is \c 1 which means MSAA is disabled. + + Valid values are 1, 4, 8, and sometimes 16 and 32. + \l QRhi::supportedSampleCounts() can be used to query the supported sample + counts at run time, but typically applications should request 1 (no MSAA), + 4x (normal MSAA) or 8x (high MSAA). + + \note Setting a new value implies that all QRhiGraphicsPipeline objects + created by the renderer must use the same sample count from then on. + Existing QRhiGraphicsPipeline objects created with a different sample count + must not be used anymore. When the value changes, all color and + depth-stencil buffers are destroyed and recreated automatically, and + initialize() is invoked again. However, when + \l autoRenderTarget is \c false, it will be up to the application to + manage this with regards to the depth-stencil buffer or additional color + buffers. + + Changing the sample count from the default 1 to a higher value implies that + colorTexture() becomes \nullptr and msaaColorBuffer() starts returning a + valid object. Switching back to 1 (or 0), implies the opposite: in the next + call to initialize() msaaColorBuffer() is going to return \nullptr, whereas + colorTexture() becomes once again valid. In addition, resolveTexture() + returns a valid (non-multisample) QRhiTexture whenever the sample count is + greater than 1 (i.e., MSAA is in use). + + \sa msaaColorBuffer(), resolveTexture() + */ + +int QQuickRhiItem::sampleCount() const +{ + Q_D(const QQuickRhiItem); + return d->samples; +} + +void QQuickRhiItem::setSampleCount(int samples) +{ + Q_D(QQuickRhiItem); + if (d->samples == samples) + return; + + d->samples = samples; + emit sampleCountChanged(); + update(); +} + +/*! + \property QQuickRhiItem::textureFormat + + This property controls the texture format for the texture used as the color + buffer. The default value is TextureFormat::RGBA8. QQuickRhiItem supports + rendering to a subset of the formats supported by \l QRhiTexture. Only + formats that are reported as supported from + \l QRhi::isTextureFormatSupported() should be specified, rendering will not be + functional otherwise. + + \note Setting a new format when the item and its renderer are already + initialized and have rendered implies that all QRhiGraphicsPipeline objects + created by the renderer may become unusable, if the associated + QRhiRenderPassDescriptor is now incompatible due to the different texture + format. Similarly to changing + \l sampleCount dynamically, this means that initialize() or render() + implementations must then take care of releasing the existing pipelines and + creating new ones. + */ + +QQuickRhiItem::TextureFormat QQuickRhiItem::textureFormat() const +{ + Q_D(const QQuickRhiItem); + return d->itemTextureFormat; +} + +void QQuickRhiItem::setTextureFormat(TextureFormat format) +{ + Q_D(QQuickRhiItem); + if (d->itemTextureFormat == format) + return; + + d->itemTextureFormat = format; + switch (format) { + case TextureFormat::RGBA8: + d->rhiTextureFormat = QRhiTexture::RGBA8; + break; + case TextureFormat::RGBA16F: + d->rhiTextureFormat = QRhiTexture::RGBA16F; + break; + case TextureFormat::RGBA32F: + d->rhiTextureFormat = QRhiTexture::RGBA32F; + break; + case TextureFormat::RGB10A2: + d->rhiTextureFormat = QRhiTexture::RGB10A2; + break; + } + emit textureFormatChanged(); + update(); +} + +/*! + \property QQuickRhiItem::autoRenderTarget + + This property controls if a depth-stencil QRhiRenderBuffer and a + QRhiTextureRenderTarget is created and maintained automatically by the + item. The default value is \c true. + + In automatic mode, the size and sample count of the depth-stencil buffer + follows the color buffer texture's settings. In non-automatic mode, + renderTarget() and depthStencilBuffer() always return \nullptr and it is + then up to the application's implementation of initialize() to take care of + setting up and managing these objects. + */ + +bool QQuickRhiItem::isAutoRenderTargetEnabled() const +{ + Q_D(const QQuickRhiItem); + return d->autoRenderTarget; +} + +void QQuickRhiItem::setAutoRenderTarget(bool enabled) +{ + Q_D(QQuickRhiItem); + if (d->autoRenderTarget == enabled) + return; + + d->autoRenderTarget = enabled; + emit autoRenderTargetChanged(); + update(); +} + +/*! + \property QQuickRhiItem::mirrorVertically + + This property controls if texture UVs are flipped when drawing the textured + quad. It has no effect on the contents of the offscreen color buffer and + the rendering implemented by the QQuickRhiItemRenderer. + + The default value is \c false. + */ + +bool QQuickRhiItem::isMirrorVerticallyEnabled() const +{ + Q_D(const QQuickRhiItem); + return d->mirrorVertically; +} + +void QQuickRhiItem::setMirrorVertically(bool enable) +{ + Q_D(QQuickRhiItem); + if (d->mirrorVertically == enable) + return; + + d->mirrorVertically = enable; + emit mirrorVerticallyChanged(); + update(); +} + +/*! + \property QQuickRhiItem::explicitTextureWidth + + The fixed width, in pixels, of the item's associated texture. Relevant when + a fixed texture size is desired that does not depend on the item's size. + This size has no effect on the geometry of the item (its size and placement + within the scene), which means the texture's content will appear stretched + (scaled up) or scaled down onto the item's area. + + For example, setting a size that is exactly twice the item's (pixel) size + effectively performs 2x supersampling (rendering at twice the resolution + and then implicitly scaling down when texturing the quad corresponding to + the item in the scene). + + By default the value is \c 0. A value of 0 means that texture's size + follows the item's size. (\c{texture size} = \c{item size} * \c{device + pixel ratio}). + */ +int QQuickRhiItem::explicitTextureWidth() const +{ + Q_D(const QQuickRhiItem); + return d->explicitTextureWidth; +} + +void QQuickRhiItem::setExplicitTextureWidth(int width) +{ + Q_D(QQuickRhiItem); + if (d->explicitTextureWidth == width) + return; + + d->explicitTextureWidth = width; + emit explicitTextureWidthChanged(); + update(); +} + +/*! + \property QQuickRhiItem::explicitTextureHeight + + The fixed height, in pixels, of the item's associated texture. Relevant when + a fixed texture size is desired that does not depend on the item's size. + This size has no effect on the geometry of the item (its size and placement + within the scene), which means the texture's content will appear stretched + (scaled up) or scaled down onto the item's area. + + For example, setting a size that is exactly twice the item's (pixel) size + effectively performs 2x supersampling (rendering at twice the resolution + and then implicitly scaling down when texturing the quad corresponding to + the item in the scene). + + By default the value is \c 0. A value of 0 means that texture's size + follows the item's size. (\c{texture size} = \c{item size} * \c{device + pixel ratio}). + */ + +int QQuickRhiItem::explicitTextureHeight() const +{ + Q_D(const QQuickRhiItem); + return d->explicitTextureHeight; +} + +void QQuickRhiItem::setExplicitTextureHeight(int height) +{ + Q_D(QQuickRhiItem); + if (d->explicitTextureHeight == height) + return; + + d->explicitTextureHeight = height; + emit explicitTextureHeightChanged(); + update(); +} + +/*! + \property QQuickRhiItem::effectiveTextureSize + + This property exposes the size, in pixels, of the underlying color buffer + (the QRhiTexture or QRhiRenderBuffer). It is provided for use on the GUI + thread thread, for example from QML to perform calculations based on it, if + needed. QQuickRhiItemRenderer implementations should not use this property. + Rather, query the size from the + \l{QQuickRhiItemRenderer::renderTarget()}{render target}. + + This is a read-only property. + */ + +QSize QQuickRhiItem::effectiveTextureSize() const +{ + Q_D(const QQuickRhiItem); + return d->effectiveTextureSize; +} + +/*! + \property QQuickRhiItem::alphaBlending + + Controls if blending is always enabled when drawing the quad textured with + the content generated by the QQuickRhiItem and its renderer. + + The default value is \c false. This is for performance reasons: if + semi-transparency is not involved, because the QQuickRhiItemRenderer clears + to an opaque color and never renders fragments with alpha smaller than 1, + then there is no point in enabling blending. + + If the QQuickRhiItemRenderer subclass renders with semi-transparency involved, + set this property to true. + + \note Under certain conditions blending is still going to happen regardless + of the value of this property. For example, if the item's + \l{QQuickItem::opacity}{opacity} (more precisely, the combined opacity + inherited from the parent chain) is smaller than 1, blending will be + automatically enabled even when this property is set to false. + + \note The Qt Quick scene graph relies on and expect pre-multiplied alpha. + For example, if the intention is to clear the background in the renderer to + an alpha value of 0.5, then make sure to multiply the red, green, and blue + clear color values with 0.5 as well. Otherwise the blending results will be + incorrect. + */ + +bool QQuickRhiItem::alphaBlending() const +{ + Q_D(const QQuickRhiItem); + return d->blend; +} + +void QQuickRhiItem::setAlphaBlending(bool enable) +{ + Q_D(QQuickRhiItem); + if (d->blend == enable) + return; + + d->blend = enable; + emit alphaBlendingChanged(); + update(); +} + +/*! + Constructs a new renderer. + + This function is called on the rendering thread during the scene graph sync + phase when the GUI thread is blocked. + + \sa QQuickRhiItem::createRenderer() + */ +QQuickRhiItemRenderer::QQuickRhiItemRenderer() +{ +} + +/*! + The Renderer is automatically deleted when the scene graph resources for + the QQuickRhiItem item are cleaned up. + + This function is called on the rendering thread. + + Under certain conditions it is normal and expected that the renderer object + is destroyed and then recreated. This is because the renderer's lifetime + effectively follows the underlying scene graph node. For example, when + changing the parent of a QQuickRhiItem object so that it then belongs to a + different \l QQuickWindow, the scene graph nodes are all dropped and + recreated due to the window change. This will also involve dropping and + creating a new QQuickRhiItemRenderer. + + Unlike \l QRhiWidget, QQuickRhiItemRenderer has no need to implement + additional code paths for releasing (or early-relasing) graphics resources + created via QRhi. It is sufficient to release everything in the destructor, + or rely on smart pointers. + */ +QQuickRhiItemRenderer::~QQuickRhiItemRenderer() +{ +} + +/*! + Call this function when the content of the offscreen color buffer should be + updated. (i.e. to request that render() is called again; the call will + happen at a later point, and note that updates are typically throttled to + the presentation rate) + + This function can be called from render() to schedule an update. + + \note This function should be used from inside the renderer. To update + the item on the GUI thread, use QQuickRhiItem::update(). + */ +void QQuickRhiItemRenderer::update() +{ + if (data) + static_cast<QQuickRhiItemNode *>(data)->scheduleUpdate(); +} + +/*! + \return the current QRhi object. + + Must only be called from initialize() and render(). + */ +QRhi *QQuickRhiItemRenderer::rhi() const +{ + return data ? static_cast<QQuickRhiItemNode *>(data)->m_rhi : nullptr; +} + +/*! + \return the texture serving as the color buffer for the item. + + Must only be called from initialize() and render(). + + Unlike the depth-stencil buffer and the QRhiRenderTarget, this texture is + always available and is managed by the QQuickRhiItem, independent of the value + of \l autoRenderTarget. + + \note When \l sampleCount is larger than 1, and so multisample antialiasing + is enabled, the return value is \nullptr. Instead, query the + \l QRhiRenderBuffer by calling msaaColorBuffer(). + + \note The backing texture size and sample count can also be queried via the + QRhiRenderTarget returned from renderTarget(). This can be more convenient + and compact than querying from the QRhiTexture or QRhiRenderBuffer, because + it works regardless of multisampling is in use or not. + + \sa msaaColorBuffer(), depthStencilBuffer(), renderTarget(), resolveTexture() + */ +QRhiTexture *QQuickRhiItemRenderer::colorTexture() const +{ + return data ? static_cast<QQuickRhiItemNode *>(data)->m_colorTexture : nullptr; +} + +/*! + \return the renderbuffer serving as the multisample color buffer for the item. + + Must only be called from initialize() and render(). + + When \l sampleCount is larger than 1, and so multisample antialising is + enabled, the returned QRhiRenderBuffer has a matching sample count and + serves as the color buffer. Graphics pipelines used to render into this + buffer must be created with the same sample count, and the depth-stencil + buffer's sample count must match as well. The multisample content is + expected to be resolved into the texture returned from resolveTexture(). + When \l autoRenderTarget is + \c true, renderTarget() is set up automatically to do this, by setting up + msaaColorBuffer() as the \l{QRhiColorAttachment::renderBuffer()}{renderbuffer} of + color attachment 0 and resolveTexture() as its + \l{QRhiColorAttachment::resolveTexture()}{resolveTexture}. + + When MSAA is not in use, the return value is \nullptr. Use colorTexture() + instead then. + + Depending on the underlying 3D graphics API, there may be no practical + difference between multisample textures and color renderbuffers with a + sample count larger than 1 (QRhi may just map both to the same native + resource type). Some older APIs however may differentiate between textures + and renderbuffers. In order to support OpenGL ES 3.0, where multisample + renderbuffers are available, but multisample textures are not, QQuickRhiItem + always performs MSAA by using a multisample QRhiRenderBuffer as the color + attachment (and never a multisample QRhiTexture). + + \note The backing texture size and sample count can also be queried via the + QRhiRenderTarget returned from renderTarget(). This can be more convenient + and compact than querying from the QRhiTexture or QRhiRenderBuffer, because + it works regardless of multisampling is in use or not. + + \sa colorTexture(), depthStencilBuffer(), renderTarget(), resolveTexture() + */ +QRhiRenderBuffer *QQuickRhiItemRenderer::msaaColorBuffer() const +{ + return data ? static_cast<QQuickRhiItemNode *>(data)->m_msaaColorBuffer.get() : nullptr; +} + +/*! + \return the non-multisample texture to which the multisample content is resolved. + + The result is \nullptr when multisample antialiasing is not enabled. + + Must only be called from initialize() and render(). + + With MSAA enabled, this is the texture that gets used by the item's + underlying scene graph node when texturing a quad in the main render pass + of Qt Quick. However, the QQuickRhiItemRenderer's rendering must target the + (multisample) QRhiRenderBuffer returned from msaaColorBuffer(). When + \l autoRenderTarget is \c true, this is taken care of by the + QRhiRenderTarget returned from renderTarget(). Otherwise, it is up to the + subclass code to correctly configure a render target object with both the + color buffer and resolve textures. + + \sa colorTexture() + */ +QRhiTexture *QQuickRhiItemRenderer::resolveTexture() const +{ + return data ? static_cast<QQuickRhiItemNode *>(data)->m_resolveTexture : nullptr; +} + +/*! + \return the depth-stencil buffer used by the item's rendering. + + Must only be called from initialize() and render(). + + Available only when \l autoRenderTarget is \c true. Otherwise the + returned value is \nullptr and it is up the reimplementation of + initialize() to create and manage a depth-stencil buffer and a + QRhiTextureRenderTarget. + + \sa colorTexture(), renderTarget() + */ +QRhiRenderBuffer *QQuickRhiItemRenderer::depthStencilBuffer() const +{ + return data ? static_cast<QQuickRhiItemNode *>(data)->m_depthStencilBuffer.get() : nullptr; +} + +/*! + \return the render target object that must be used with + \l QRhiCommandBuffer::beginPass() in reimplementations of render(). + + Must only be called from initialize() and render(). + + Available only when \l autoRenderTarget is \c true. Otherwise the + returned value is \nullptr and it is up the reimplementation of + initialize() to create and manage a depth-stencil buffer and a + QRhiTextureRenderTarget. + + When creating \l{QRhiGraphicsPipeline}{graphics pipelines}, a + QRhiRenderPassDescriptor is needed. This can be queried from the returned + QRhiTextureRenderTarget by calling + \l{QRhiTextureRenderTarget::renderPassDescriptor()}{renderPassDescriptor()}. + + \sa colorTexture(), depthStencilBuffer() + */ +QRhiTextureRenderTarget *QQuickRhiItemRenderer::renderTarget() const +{ + return data ? static_cast<QQuickRhiItemNode *>(data)->m_renderTarget.get() : nullptr; +} + +/*! + \fn QQuickRhiItemRenderer *QQuickRhiItem::createRenderer() + + Reimplement this function to create and return a new instance of a + QQuickRhiItemRenderer subclass. + + This function will be called on the rendering thread while the GUI thread + is blocked. + */ + +/*! + \fn void QQuickRhiItemRenderer::initialize(QRhiCommandBuffer *cb) + + Called when the item is initialized for the first time, when the + associated texture's size, format, or sample count changes, or when the + QRhi or texture change for any reason. The function is expected to + maintain (create if not yet created, adjust and rebuild if the size has + changed) the graphics resources used by the rendering code in render(). + + To query the QRhi, QRhiTexture, and other related objects, call rhi(), + colorTexture(), depthStencilBuffer(), and renderTarget(). + + When the item size changes, the QRhi object, the color buffer texture, + and the depth stencil buffer objects are all the same instances (so the + getters return the same pointers) as before, but the color and + depth/stencil buffers will likely have been rebuilt, meaning the + \l{QRhiTexture::pixelSize()}{size} and the underlying native texture + resource may be different than in the last invocation. + + Reimplementations should also be prepared that the QRhi object and the + color buffer texture may change between invocations of this function. For + example, when the item is reparented so that it belongs to a new + QQuickWindow, the the QRhi and all related resources managed by the + QQuickRhiItem will be different instances than before in the subsequent + call to this function. Is is then important that all existing QRhi + resources previously created by the subclass are destroyed because they + belong to the previous QRhi that should not be used anymore. + + When \l autoRenderTarget is \c true, which is the default, a + depth-stencil QRhiRenderBuffer and a QRhiTextureRenderTarget associated + with the colorTexture() (or msaaColorBuffer()) and the depth-stencil buffer + are created and managed automatically. Reimplementations of initialize() + and render() can query those objects via depthStencilBuffer() and + renderTarget(). When \l autoRenderTarget is set to \c false, these + objects are no longer created and managed automatically. Rather, it will be + up the the initialize() implementation to create buffers and set up the + render target as it sees fit. When manually managing additional color or + depth-stencil attachments for the render target, their size and sample + count must always follow the size and sample count of colorTexture() (or + msaaColorBuffer()), otherwise rendering or 3D API validation errors may + occur. + + The subclass-created graphics resources are expected to be released in the + destructor implementation of the subclass. + + \a cb is the QRhiCommandBuffer for the current frame. The function is + called with a frame being recorded, but without an active render pass. The + command buffer is provided primarily to allow enqueuing + \l{QRhiCommandBuffer::resourceUpdate()}{resource updates} without deferring + to render(). + + This function is called on the render thread, if there is one. + + \sa render() + */ + +/*! + \fn void QQuickRhiItemRenderer::synchronize(QQuickRhiItem *item) + + This function is called on the render thread, if there is one, while the + main/GUI thread is blocked. It is called from + \l{QQuickItem::updatePaintNode()}{the QQuickRhiItem's synchronize step}, + and allows reading and writing data belonging to the main and render + threads. Typically property values stored in the QQuickRhiItem are copied + into the QQuickRhiItemRenderer, so that they can be safely read afterwards + in render() when the render and main threads continue to work in parallel. + + \sa initialize(), render() + */ + +/*! + \fn void QQuickRhiItemRenderer::render(QRhiCommandBuffer *cb) + + Called when the backing color buffer's contents needs updating. + + There is always at least one call to initialize() before this function is + called. + + To request updates, call \l QQuickItem::update() when calling from QML or + from C++ code on the main/GUI thread (e.g. when in a property setter), or + \l update() when calling from within a QQuickRhiItemRenderer callback. + Calling QQuickRhiItemRenderer's update() from within + render() will lead to triggering updates continuously. + + \a cb is the QRhiCommandBuffer for the current frame. The function is + called with a frame being recorded, but without an active render pass. + + This function is called on the render thread, if there is one. + + \sa initialize(), synchronize() + */ + +QT_END_NAMESPACE diff --git a/src/quick/items/qquickrhiitem.h b/src/quick/items/qquickrhiitem.h new file mode 100644 index 0000000000..ffa28d5c37 --- /dev/null +++ b/src/quick/items/qquickrhiitem.h @@ -0,0 +1,117 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQUICKRHIITEM_H +#define QQUICKRHIITEM_H + +#include <QtQuick/QQuickItem> + +QT_BEGIN_NAMESPACE + +class QQuickRhiItem; +class QQuickRhiItemPrivate; +class QRhi; +class QRhiCommandBuffer; +class QRhiTexture; +class QRhiRenderBuffer; +class QRhiTextureRenderTarget; + +class Q_QUICK_EXPORT QQuickRhiItemRenderer : public QObject +{ +public: + QQuickRhiItemRenderer(); + virtual ~QQuickRhiItemRenderer(); + + virtual void initialize(QRhiCommandBuffer *cb) = 0; + virtual void synchronize(QQuickRhiItem *item) = 0; + virtual void render(QRhiCommandBuffer *cb) = 0; + + void update(); + + QRhi *rhi() const; + QRhiTexture *colorTexture() const; + QRhiRenderBuffer *msaaColorBuffer() const; + QRhiTexture *resolveTexture() const; + QRhiRenderBuffer *depthStencilBuffer() const; + QRhiTextureRenderTarget *renderTarget() const; + +private: + void *data; + friend class QQuickRhiItem; +}; + +class Q_QUICK_EXPORT QQuickRhiItem : public QQuickItem +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQuickRhiItem) + + Q_PROPERTY(int sampleCount READ sampleCount WRITE setSampleCount NOTIFY sampleCountChanged FINAL) + Q_PROPERTY(TextureFormat textureFormat READ textureFormat WRITE setTextureFormat NOTIFY textureFormatChanged FINAL) + Q_PROPERTY(bool autoRenderTarget READ isAutoRenderTargetEnabled WRITE setAutoRenderTarget NOTIFY autoRenderTargetChanged FINAL) + Q_PROPERTY(bool mirrorVertically READ isMirrorVerticallyEnabled WRITE setMirrorVertically NOTIFY mirrorVerticallyChanged FINAL) + Q_PROPERTY(bool alphaBlending READ alphaBlending WRITE setAlphaBlending NOTIFY alphaBlendingChanged) + Q_PROPERTY(int explicitTextureWidth READ explicitTextureWidth WRITE setExplicitTextureWidth NOTIFY explicitTextureWidthChanged) + Q_PROPERTY(int explicitTextureHeight READ explicitTextureHeight WRITE setExplicitTextureHeight NOTIFY explicitTextureHeightChanged) + Q_PROPERTY(QSize effectiveTextureSize READ effectiveTextureSize NOTIFY effectiveTextureSizeChanged) + +public: + enum class TextureFormat { + RGBA8, + RGBA16F, + RGBA32F, + RGB10A2 + }; + Q_ENUM(TextureFormat) + + QQuickRhiItem(QQuickItem *parent = nullptr); + + int sampleCount() const; + void setSampleCount(int samples); + + TextureFormat textureFormat() const; + void setTextureFormat(TextureFormat format); + + bool isAutoRenderTargetEnabled() const; + void setAutoRenderTarget(bool enabled); + + bool isMirrorVerticallyEnabled() const; + void setMirrorVertically(bool enable); + + bool alphaBlending() const; + void setAlphaBlending(bool enable); + + int explicitTextureWidth() const; + void setExplicitTextureWidth(int width); + int explicitTextureHeight() const; + void setExplicitTextureHeight(int height); + + QSize effectiveTextureSize() const; + + virtual QQuickRhiItemRenderer *createRenderer() = 0; + +Q_SIGNALS: + void sampleCountChanged(); + void textureFormatChanged(); + void autoRenderTargetChanged(); + void mirrorVerticallyChanged(); + void alphaBlendingChanged(); + void explicitTextureWidthChanged(); + void explicitTextureHeightChanged(); + void effectiveTextureSizeChanged(); + +protected: + QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override; + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + void releaseResources() override; + bool isTextureProvider() const override; + QSGTextureProvider *textureProvider() const override; + +private Q_SLOTS: + void invalidateSceneGraph(); + + friend class QQuickRhiItemNode; +}; + +QT_END_NAMESPACE + +#endif // QQUICKRHIITEM_H diff --git a/src/quick/items/qquickrhiitem_p.h b/src/quick/items/qquickrhiitem_p.h new file mode 100644 index 0000000000..2e540ee3a5 --- /dev/null +++ b/src/quick/items/qquickrhiitem_p.h @@ -0,0 +1,101 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQUICKRHIITEM_P_H +#define QQUICKRHIITEM_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickrhiitem.h" +#include <QtQuick/QSGTextureProvider> +#include <QtQuick/QSGSimpleTextureNode> +#include <QtQuick/private/qquickitem_p.h> +#include <rhi/qrhi.h> + +QT_BEGIN_NAMESPACE + +class QQuickRhiItemNode : public QSGTextureProvider, public QSGSimpleTextureNode +{ + Q_OBJECT + +public: + QQuickRhiItemNode(QQuickRhiItem *item); + + QSGTexture *texture() const override; + + void sync(); + QRhiCommandBuffer *queryCommandBuffer(); + void resetColorBufferObjects(); + void resetRenderTargetObjects(); + + bool isValid() const + { + return m_rhi && m_sgTexture; + } + + void scheduleUpdate() + { + m_renderPending = true; + m_window->update(); // ensure getting to beforeRendering() at some point + } + + bool hasRenderer() const + { + return m_renderer != nullptr; + } + + void setRenderer(QQuickRhiItemRenderer *r) + { + m_renderer.reset(r); + } + + QQuickRhiItem *m_item; + QQuickWindow *m_window; + QSize m_pixelSize; + qreal m_dpr = 0.0f; + QRhi *m_rhi = nullptr; + bool m_renderPending = true; + std::unique_ptr<QSGTexture> m_sgTexture; + std::unique_ptr<QQuickRhiItemRenderer> m_renderer; + QRhiTexture *m_colorTexture = nullptr; + QRhiTexture *m_resolveTexture = nullptr; + std::unique_ptr<QRhiRenderBuffer> m_msaaColorBuffer; + std::unique_ptr<QRhiRenderBuffer> m_depthStencilBuffer; + std::unique_ptr<QRhiTextureRenderTarget> m_renderTarget; + std::unique_ptr<QRhiRenderPassDescriptor> m_renderPassDescriptor; + +public slots: + void render(); +}; + +class QQuickRhiItemPrivate : public QQuickItemPrivate +{ + Q_DECLARE_PUBLIC(QQuickRhiItem) + +public: + static QQuickRhiItemPrivate *get(QQuickRhiItem *item) { return item->d_func(); } + + mutable QQuickRhiItemNode *node = nullptr; + QQuickRhiItem::TextureFormat itemTextureFormat = QQuickRhiItem::TextureFormat::RGBA8; + QRhiTexture::Format rhiTextureFormat = QRhiTexture::RGBA8; + int samples = 1; + bool autoRenderTarget = true; + bool mirrorVertically = false; + bool blend = false; + int explicitTextureWidth = 0; + int explicitTextureHeight = 0; + QSize effectiveTextureSize; +}; + +QT_END_NAMESPACE + +#endif |
