From 72a453c6a8b02c08c5c09842f468459d6a51c387 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Tue, 13 Jun 2023 13:10:40 +0200 Subject: Add QRhiWidget Task-number: QTBUG-113331 Change-Id: I8baa697b4997b05f52acdee0e08d3c368fde5bc2 Reviewed-by: Andy Nichols --- src/widgets/kernel/qrhiwidget.cpp | 1283 +++++++++++++++++++++++++++++++++++++ src/widgets/kernel/qrhiwidget.h | 102 +++ src/widgets/kernel/qrhiwidget_p.h | 63 ++ src/widgets/kernel/qwidget.cpp | 3 +- 4 files changed, 1450 insertions(+), 1 deletion(-) create mode 100644 src/widgets/kernel/qrhiwidget.cpp create mode 100644 src/widgets/kernel/qrhiwidget.h create mode 100644 src/widgets/kernel/qrhiwidget_p.h (limited to 'src/widgets/kernel') diff --git a/src/widgets/kernel/qrhiwidget.cpp b/src/widgets/kernel/qrhiwidget.cpp new file mode 100644 index 0000000000..0b49e7465d --- /dev/null +++ b/src/widgets/kernel/qrhiwidget.cpp @@ -0,0 +1,1283 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "qrhiwidget_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QRhiWidget + \inmodule QtWidgets + \since 6.7 + + \brief The QRhiWidget class is a widget for rendering 3D graphics via an + accelerated grapics API, such as Vulkan, Metal, or Direct 3D. + + \preliminary + + \note QRhiWidget is in tech preview in Qt 6.7. \b {The API is under + development and subject to change.} + + QRhiWidget provides functionality for displaying 3D content rendered + through the \l QRhi APIs within a QWidget-based application. In many ways + it is the portable equivalent of \l QOpenGLWidget that is not tied to a + single 3D graphics API, but rather can function with all the APIs QRhi + supports (such as, Direct 3D 11/12, Vulkan, Metal, and OpenGL). + + QRhiWidget is expected to be subclassed. To render into the 2D texture that + is implicitly created and managed by the QRhiWidget, subclasses should + reimplement the virtual functions initialize() and render(). + + The size of the texture will by default adapt to the size of the item. If a + fixed size is preferred, set an explicit size specified in pixels by + calling setExplicitSize(). + + In addition to the texture serving as the color buffer, a depth/stencil + buffer and a render target binding these together is maintained implicitly + as well. + + The QRhi for the widget's top-level window is configured to use a platform + specific backend and graphics API by default: Metal on macOS and iOS, + Direct 3D 11 on Windows, OpenGL otherwise. Call setApi() to override this. + + \note A single widget window can only use one QRhi backend, and so graphics + API. If two QRhiWidget or QQuickWidget widgets in the window's widget + hierarchy request different APIs, only one of them will function correctly. + + \note While QRhiWidget 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{qrhiwidget.h} does + not directly include any QRhi-related headers. To use those classes when + implementing a QRhiWidget subclass, link to + \c{Qt::GuiPrivate} (if using CMake), and include the appropriate headers + with the \c rhi prefix, for example \c{#include }. + + An example of a simple QRhiWidget subclass rendering a triangle is the + following: + + \snippet qrhiwidget/rhiwidgetintro.cpp 0 + + This is a widget that continuously requests updates, throttled by the + presentation rate (vsync, depending on the screen refresh rate). If + continuously rendering is not desired, the update() call in render() should + be removed and rather issued when updating the rendered content is + necessary. For example, if the rotation should be tied to the value of a + QSlider, then connecting the slider's value change signal to a slot or + lambda that forwards the new value and calls update() is sufficient. + + The vertex and fragment shaders are provided as Vulkan-style GLSL and must + be processed first by the Qt shader infrastructure first. This is achieved + either by running the \c qsb command-line tool manually, or by using the + qt_add_shaders() function in CMake. The QRhiWidget implementation loads + these pre-processed \c{.qsb} files that are shipped with the application. + + The source code for these shaders could be the following: + + \c{color.vert} + + \snippet qrhiwidget/rhiwidgetintro.vert 0 + + \c{color.frag} + + \snippet qrhiwidget/rhiwidgetintro.frag 0 + + The result is a widget that shows the following: + + \image qrhiwidget-intro.jpg + + For a complete, minimal, introductory example check out the \l{Simple RHI + Widget Example}. + + For an example with more functionality and demonstration of further + concepts, check the \l{Cube RHI Widget Example}. + + QRhiWidget always involves rendering into a backing texture, not + directly to the window (the surface or layer provided by the windowing + system for the native window). This allows properly compositing the content + with the rest of the widget-based UI, and offering a simple and compact + API, making it easy to get started. All this comes at the expense of + additional resources and a potential effect on performance. This is often + perfectly acceptable in practice, but advanced users should keep in mind + the pros and cons of the different approaches. Refer to the \l{RHI Window + Example} and compare it with the \l{Simple RHI Widget Example} for details + about the two approaches. + + Reparenting a QRhiWidget into a widget hierarchy that belongs to a + different window (top-level widget), or making the QRhiWidget itself a + top-level (by setting the parent to \nullptr), involves changing the + associated QRhi (and potentially destroying the old one) while the + QRhiWidget continues to stay alive and well. To support this, robust + QRhiWidget implementations are expected to reimplement the + releaseResources() virtual function as well, and drop their QRhi resources + just as they do in the destructor. The \l{Cube RHI Widget Example} + demonstrates this in practice. + + While not a primary use case, QRhiWidget 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. Note however that the + configurability of the underlying graphics API (its device or context + features, layers, extensions, etc.) is going to be limited since + QRhiWidget's primary goal is to provide an environment suitable for + QRhi-based rendering code, not to enable arbitrary, potentially complex, + foreign rendering engines. + + \since 6.7 + + \sa QRhi, QShader, QOpenGLWidget, {Simple RHI Widget Example}, {Cube RHI Widget Example} + */ + +/*! + \enum QRhiWidget::Api + Specifies the 3D API and QRhi backend to use + + \value OpenGL + \value Metal + \value Vulkan + \value D3D11 + \value D3D12 + \value Null + + \sa QRhi + */ + +/*! + \enum QRhiWidget::TextureFormat + Specifies the format of the texture to which the QRhiWidget renders. + + \value RGBA8 See QRhiTexture::RGBA8. + \value RGBA16F See QRhiTexture::RGBA16F. + \value RGBA32F See QRhiTexture::RGBA32F. + \value RGB10A2 See QRhiTexture::RGB10A2. + + \sa QRhiTexture + */ + +/*! + Constructs a widget which is a child of \a parent, with widget flags set to \a f. + */ +QRhiWidget::QRhiWidget(QWidget *parent, Qt::WindowFlags f) + : QWidget(*(new QRhiWidgetPrivate), parent, f) +{ + Q_D(QRhiWidget); + if (Q_UNLIKELY(!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RhiBasedRendering))) + qWarning("QRhiWidget: QRhi is not supported on this platform."); + else + d->setRenderToTexture(); + + d->config.setEnabled(true); +#if defined(Q_OS_DARWIN) + d->config.setApi(QPlatformBackingStoreRhiConfig::Metal); +#elif defined(Q_OS_WIN) + d->config.setApi(QPlatformBackingStoreRhiConfig::D3D11); +#else + d->config.setApi(QPlatformBackingStoreRhiConfig::OpenGL); +#endif +} + +/*! + Destructor. + */ +QRhiWidget::~QRhiWidget() +{ + Q_D(QRhiWidget); + + if (d->rhi) { + d->rhi->removeCleanupCallback(this); + // rhi resources must be destroyed here, due to how QWidget teardown works; + // it should not be left to the private object's destruction. + d->resetRenderTargetObjects(); + d->resetColorBufferObjects(); + qDeleteAll(d->pendingDeletes); + } + + d->offscreenRenderer.reset(); +} + +/*! + Handles resize events that are passed in the \a e event parameter. Calls + the virtual function initialize(). + + \note Avoid overriding this function in derived classes. If that is not + feasible, make sure that QRhiWidget's implementation is invoked too. + Otherwise the underlying texture object and related resources will not get + resized properly and will lead to incorrect rendering. + */ +void QRhiWidget::resizeEvent(QResizeEvent *e) +{ + Q_D(QRhiWidget); + + if (e->size().isEmpty()) { + d->noSize = true; + return; + } + d->noSize = false; + + d->sendPaintEvent(QRect(QPoint(0, 0), size())); +} + +/*! + Handles paint events. + + Calling QWidget::update() will lead to sending a paint event \a e, and thus + invoking this function. The sending of the event is asynchronous and will + happen at some point after returning from update(). This function will + then, after some preparation, call the virtual render() to update the + contents of the QRhiWidget's associated texture. The widget's top-level + window will then composite the texture with the rest of the window. + */ +void QRhiWidget::paintEvent(QPaintEvent *) +{ + Q_D(QRhiWidget); + if (!updatesEnabled() || d->noSize) + return; + + d->ensureRhi(); + if (!d->rhi) { + qWarning("QRhiWidget: No QRhi"); + emit renderFailed(); + return; + } + + QRhiCommandBuffer *cb = nullptr; + if (d->rhi->beginOffscreenFrame(&cb) != QRhi::FrameOpSuccess) + return; + + bool needsInit = false; + d->ensureTexture(&needsInit); + if (d->colorTexture || d->msaaColorBuffer) { + bool canRender = true; + if (needsInit) + canRender = d->invokeInitialize(cb); + if (canRender) + render(cb); + } + + d->rhi->endOffscreenFrame(); +} + +/*! + \reimp +*/ +bool QRhiWidget::event(QEvent *e) +{ + Q_D(QRhiWidget); + switch (e->type()) { + case QEvent::WindowChangeInternal: + // The QRhi will almost certainly change, prevent texture() from + // returning the existing QRhiTexture in the meantime. + d->textureInvalid = true; + + if (d->rhi && d->rhi != d->offscreenRenderer.rhi()) { + // Drop the cleanup callback registered to the toplevel's rhi and + // do the early-release, there may not be another chance to do + // this, and the QRhi we have currently set may be destroyed by the + // time we get to ensureRhi() again. + d->rhi->removeCleanupCallback(this); + releaseResources(); // notify the user code about the early-release + d->releaseResources(); + // must _not_ null out d->rhi here, for proper interaction with ensureRhi() + } + + break; + + case QEvent::Show: + if (isVisible()) + d->sendPaintEvent(QRect(QPoint(0, 0), size())); + break; + default: + break; + } + return QWidget::event(e); +} + +QWidgetPrivate::TextureData QRhiWidgetPrivate::texture() const +{ + // This is the only safe place to clear pendingDeletes, due to the + // possibility of the texture returned in the previous invocation of this + // function having been added to pendingDeletes, meaning the object then + // needs to be valid until the next (this) invocation of this function. + // (the exact object lifetime requirements depend on the + // QWidget/RepaintManager internal implementation; for now avoid relying on + // such details by clearing pendingDeletes only here, not in endCompose()) + qDeleteAll(pendingDeletes); + pendingDeletes.clear(); + + TextureData td; + if (!textureInvalid) + td.textureLeft = resolveTexture ? resolveTexture : colorTexture; + return td; +} + +QPlatformTextureList::Flags QRhiWidgetPrivate::textureListFlags() +{ + QPlatformTextureList::Flags flags = QWidgetPrivate::textureListFlags(); + if (mirrorVertically) + flags |= QPlatformTextureList::MirrorVertically; + return flags; +} + +QPlatformBackingStoreRhiConfig QRhiWidgetPrivate::rhiConfig() const +{ + return config; +} + +void QRhiWidgetPrivate::endCompose() +{ + // This function is called by QWidgetRepaintManager right after the + // backingstore's QRhi-based flush returns. In practice that means after + // the begin-endFrame() on the top-level window's swapchain. + + if (rhi) { + Q_Q(QRhiWidget); + emit q->frameSubmitted(); + } +} + +void QRhiWidgetPrivate::resetColorBufferObjects() +{ + if (colorTexture) { + pendingDeletes.append(colorTexture); + colorTexture = nullptr; + } + if (msaaColorBuffer) { + pendingDeletes.append(msaaColorBuffer); + msaaColorBuffer = nullptr; + } + if (resolveTexture) { + pendingDeletes.append(resolveTexture); + resolveTexture = nullptr; + } +} + +void QRhiWidgetPrivate::resetRenderTargetObjects() +{ + if (renderTarget) { + renderTarget->deleteLater(); + renderTarget = nullptr; + } + if (renderPassDescriptor) { + renderPassDescriptor->deleteLater(); + renderPassDescriptor = nullptr; + } + if (depthStencilBuffer) { + depthStencilBuffer->deleteLater(); + depthStencilBuffer = nullptr; + } +} + +void QRhiWidgetPrivate::releaseResources() +{ + resetRenderTargetObjects(); + resetColorBufferObjects(); + qDeleteAll(pendingDeletes); + pendingDeletes.clear(); +} + +void QRhiWidgetPrivate::ensureRhi() +{ + Q_Q(QRhiWidget); + // the QRhi and infrastructure belongs to the top-level widget, not to this widget + QWidget *tlw = q->window(); + QWidgetPrivate *wd = get(tlw); + + QRhi *currentRhi = nullptr; + if (QWidgetRepaintManager *repaintManager = wd->maybeRepaintManager()) + currentRhi = repaintManager->rhi(); + + if (currentRhi && currentRhi->backend() != QBackingStoreRhiSupport::apiToRhiBackend(config.api())) { + qWarning("The top-level window is already using another graphics API for composition, " + "'%s' is not compatible with this widget", + currentRhi->backendName()); + return; + } + + // NB the rhi member may be an invalid object, the pointer can be used, but no deref + if (currentRhi && rhi && rhi != currentRhi) { + // if previously we created our own but now get a QRhi from the + // top-level, then drop what we have and start using the top-level's + if (rhi == offscreenRenderer.rhi()) { + q->releaseResources(); // notify the user code about the early-release + releaseResources(); + offscreenRenderer.reset(); + } else { + // rhi resources created by us all belong to the old rhi, drop them; + // due to nulling out colorTexture this is also what ensures that + // initialize() is going to be called again eventually + resetRenderTargetObjects(); + resetColorBufferObjects(); + } + + // Normally the widget gets destroyed before the QRhi (which is managed by + // the top-level's backingstore). When reparenting between top-levels is + // involved, that is not always the case. Therefore we use a per-widget rhi + // cleanup callback to get notified when the QRhi is about to be destroyed + // while the QRhiWidget is still around. + currentRhi->addCleanupCallback(q, [q, this](QRhi *regRhi) { + if (!QWidgetPrivate::get(q)->data.in_destructor && this->rhi == regRhi) { + q->releaseResources(); // notify the user code about the early-release + releaseResources(); + // must null out our ref, the QRhi object is going to be invalid + this->rhi = nullptr; + } + }); + } + + rhi = currentRhi; +} + +void QRhiWidgetPrivate::ensureTexture(bool *changed) +{ + Q_Q(QRhiWidget); + + QSize newSize = explicitSize; + if (newSize.isEmpty()) + newSize = q->size() * q->devicePixelRatio(); + + const int minTexSize = rhi->resourceLimit(QRhi::TextureSizeMin); + const int maxTexSize = rhi->resourceLimit(QRhi::TextureSizeMax); + newSize.setWidth(qMin(maxTexSize, qMax(minTexSize, newSize.width()))); + newSize.setHeight(qMin(maxTexSize, qMax(minTexSize, newSize.height()))); + + if (colorTexture) { + if (colorTexture->format() != rhiTextureFormat || colorTexture->sampleCount() != samples) { + resetColorBufferObjects(); + // sample count change needs new depth-stencil, possibly a new + // render target; format change needs new renderpassdescriptor; + // therefore must drop the rest too + resetRenderTargetObjects(); + } + } + + if (msaaColorBuffer) { + if (msaaColorBuffer->backingFormat() != rhiTextureFormat || msaaColorBuffer->sampleCount() != samples) { + resetColorBufferObjects(); + // sample count change needs new depth-stencil, possibly a new + // render target; format change needs new renderpassdescriptor; + // therefore must drop the rest too + resetRenderTargetObjects(); + } + } + + if (!colorTexture && samples <= 1) { + if (changed) + *changed = true; + if (!rhi->isTextureFormatSupported(rhiTextureFormat)) { + qWarning("QRhiWidget: The requested texture format (%d) is not supported by the " + "underlying 3D graphics API implementation", int(rhiTextureFormat)); + } + colorTexture = rhi->newTexture(rhiTextureFormat, newSize, samples, QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource); + if (!colorTexture->create()) { + qWarning("Failed to create backing texture for QRhiWidget"); + delete colorTexture; + colorTexture = nullptr; + return; + } + } + + if (samples > 1) { + if (!msaaColorBuffer) { + if (changed) + *changed = true; + if (!rhi->isFeatureSupported(QRhi::MultisampleRenderBuffer)) { + qWarning("QRhiWidget: Multisample renderbuffers are reported as unsupported; " + "sample count %d will not work as expected", samples); + } + if (!rhi->isTextureFormatSupported(rhiTextureFormat)) { + qWarning("QRhiWidget: The requested texture format (%d) is not supported by the " + "underlying 3D graphics API implementation", int(rhiTextureFormat)); + } + msaaColorBuffer = rhi->newRenderBuffer(QRhiRenderBuffer::Color, newSize, samples, {}, rhiTextureFormat); + if (!msaaColorBuffer->create()) { + qWarning("Failed to create multisample color buffer for QRhiWidget"); + delete msaaColorBuffer; + msaaColorBuffer = nullptr; + return; + } + } + if (!resolveTexture) { + if (changed) + *changed = true; + resolveTexture = rhi->newTexture(rhiTextureFormat, newSize, 1, QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource); + if (!resolveTexture->create()) { + qWarning("Failed to create resolve texture for QRhiWidget"); + delete resolveTexture; + resolveTexture = nullptr; + return; + } + } + } else if (resolveTexture) { + resolveTexture->deleteLater(); + resolveTexture = nullptr; + } + + if (colorTexture && colorTexture->pixelSize() != newSize) { + if (changed) + *changed = true; + colorTexture->setPixelSize(newSize); + if (!colorTexture->create()) + qWarning("Failed to rebuild texture for QRhiWidget after resizing"); + } + + if (msaaColorBuffer && msaaColorBuffer->pixelSize() != newSize) { + if (changed) + *changed = true; + msaaColorBuffer->setPixelSize(newSize); + if (!msaaColorBuffer->create()) + qWarning("Failed to rebuild multisample color buffer for QRhiWidget after resizing"); + } + + if (resolveTexture && resolveTexture->pixelSize() != newSize) { + if (changed) + *changed = true; + resolveTexture->setPixelSize(newSize); + if (!resolveTexture->create()) + qWarning("Failed to rebuild resolve texture for QRhiWidget after resizing"); + } + + textureInvalid = false; +} + +bool QRhiWidgetPrivate::invokeInitialize(QRhiCommandBuffer *cb) +{ + Q_Q(QRhiWidget); + if (!colorTexture && !msaaColorBuffer) + return false; + + if (autoRenderTarget) { + const QSize pixelSize = colorTexture ? colorTexture->pixelSize() : msaaColorBuffer->pixelSize(); + if (!depthStencilBuffer) { + depthStencilBuffer = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, samples); + if (!depthStencilBuffer->create()) { + qWarning("Failed to create depth-stencil buffer for QRhiWidget"); + resetRenderTargetObjects(); + return false; + } + } else if (depthStencilBuffer->pixelSize() != pixelSize) { + depthStencilBuffer->setPixelSize(pixelSize); + if (!depthStencilBuffer->create()) { + qWarning("Failed to rebuild depth-stencil buffer for QRhiWidget with new size"); + return false; + } + } + + if (!renderTarget) { + QRhiColorAttachment color0; + if (colorTexture) + color0.setTexture(colorTexture); + else + color0.setRenderBuffer(msaaColorBuffer); + if (samples > 1) + color0.setResolveTexture(resolveTexture); + QRhiTextureRenderTargetDescription rtDesc(color0, depthStencilBuffer); + renderTarget = rhi->newTextureRenderTarget(rtDesc); + renderPassDescriptor = renderTarget->newCompatibleRenderPassDescriptor(); + renderTarget->setRenderPassDescriptor(renderPassDescriptor); + if (!renderTarget->create()) { + qWarning("Failed to create render target for QRhiWidget"); + resetRenderTargetObjects(); + return false; + } + } + } else { + resetRenderTargetObjects(); + } + + q->initialize(cb); + + return true; +} + +/*! + \return the currently set graphics API (QRhi backend). + + \sa setApi() + */ +QRhiWidget::Api QRhiWidget::api() const +{ + Q_D(const QRhiWidget); + switch (d->config.api()) { + case QPlatformBackingStoreRhiConfig::OpenGL: + return Api::OpenGL; + case QPlatformBackingStoreRhiConfig::Metal: + return Api::Metal; + case QPlatformBackingStoreRhiConfig::Vulkan: + return Api::Vulkan; + case QPlatformBackingStoreRhiConfig::D3D11: + return Api::D3D11; + case QPlatformBackingStoreRhiConfig::D3D12: + return Api::D3D12; + default: + return Api::Null; + } +} + +/*! + Sets the graphics API and QRhi backend to use to \a api. + + \warning This function must be called early enough, before the widget is + added to a widget hierarchy and displayed on screen. For example, aim to + call the function for the subclass constructor. If called too late, the + function will have no effect. + + The default value depends on the platform: Metal on macOS and iOS, Direct + 3D 11 on Windows, OpenGL otherwise. + + The \a api can only be set once for the widget and its top-level window, + once it is done and takes effect, the window can only use that API and QRhi + backend to render. Attempting to set another value, or to add another + QRhiWidget with a different \a api will not function as expected. + + \sa setTextureFormat(), setDebugLayer(), api() + */ +void QRhiWidget::setApi(Api api) +{ + Q_D(QRhiWidget); + switch (api) { + case Api::OpenGL: + d->config.setApi(QPlatformBackingStoreRhiConfig::OpenGL); + break; + case Api::Metal: + d->config.setApi(QPlatformBackingStoreRhiConfig::Metal); + break; + case Api::Vulkan: + d->config.setApi(QPlatformBackingStoreRhiConfig::Vulkan); + break; + case Api::D3D11: + d->config.setApi(QPlatformBackingStoreRhiConfig::D3D11); + break; + case Api::D3D12: + d->config.setApi(QPlatformBackingStoreRhiConfig::D3D12); + break; + case Api::Null: + d->config.setApi(QPlatformBackingStoreRhiConfig::Null); + break; + } +} + +/*! + \return true if a debug or validation layer will be requested if applicable + to the graphics API in use. + + \sa setDebugLayer() + */ +bool QRhiWidget::isDebugLayerEnabled() const +{ + Q_D(const QRhiWidget); + return d->config.isDebugLayerEnabled(); +} + +/*! + Requests the debug or validation layer of the underlying graphics API + when \a enable is true. + + \warning This function must be called early enough, before the widget is added + to a widget hierarchy and displayed on screen. For example, aim to call the + function for the subclass constructor. If called too late, the function + will have no effect. + + Applicable for Vulkan and Direct 3D. + + By default this is disabled. + + \sa setApi(), isDebugLayerEnabled() + */ +void QRhiWidget::setDebugLayer(bool enable) +{ + Q_D(QRhiWidget); + d->config.setDebugLayer(enable); +} + +/*! + \property QRhiWidget::textureFormat + + This property controls the texture format for the texture used as the color + buffer. The default value is TextureFormat::RGBA8. QRhiWidget 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 widget is already initialized and has + 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. + */ + +QRhiWidget::TextureFormat QRhiWidget::textureFormat() const +{ + Q_D(const QRhiWidget); + return d->widgetTextureFormat; +} + +void QRhiWidget::setTextureFormat(TextureFormat format) +{ + Q_D(QRhiWidget); + if (d->widgetTextureFormat != format) { + d->widgetTextureFormat = 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(format); + update(); + } +} + +/*! + \property QRhiWidget::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 QRhiWidget::sampleCount() const +{ + Q_D(const QRhiWidget); + return d->samples; +} + +void QRhiWidget::setSampleCount(int samples) +{ + Q_D(QRhiWidget); + if (d->samples != samples) { + d->samples = samples; + emit sampleCountChanged(samples); + update(); + } +} + +/*! + \property QRhiWidget::explicitSize + + The fixed size, in pixels, of the QRhiWidget's associated texture. Relevant + when a fixed texture size is desired that does not depend on the widget's + size. This size has no effect on the geometry of the widget (its size and + placement within the top-level window), which means the texture's content + will appear stretched (scaled up) or scaled down onto the widget's area. + + For example, setting a size that is exactly twice the widget's (pixel) size + effectively performs 2x supersampling (rendering at twice the resolution + and then implicitly scaling down when texturing the quad corresponding to + the widget in the window). + + By default the value is a null QSize. A null or empty QSize means that the + texture's size follows the QRhiWidget's size. (\c{texture size} = \c{widget + size} * \c{device pixel ratio}). + */ + +QSize QRhiWidget::explicitSize() const +{ + Q_D(const QRhiWidget); + return d->explicitSize; +} + +void QRhiWidget::setExplicitSize(const QSize &pixelSize) +{ + Q_D(QRhiWidget); + if (d->explicitSize != pixelSize) { + d->explicitSize = pixelSize; + emit explicitSizeChanged(pixelSize); + update(); + } +} + +/*! + \property QRhiWidget::mirrorVertically + + When enabled, flips the image around the X axis when compositing the + QRhiWidget's backing texture with the rest of the widget content in the + top-level window. + + The default value is \c false. + */ + +bool QRhiWidget::isMirrorVerticallyEnabled() const +{ + Q_D(const QRhiWidget); + return d->mirrorVertically; +} + +void QRhiWidget::setMirrorVertically(bool enabled) +{ + Q_D(QRhiWidget); + if (d->mirrorVertically != enabled) { + d->mirrorVertically = enabled; + emit mirrorVerticallyChanged(enabled); + update(); + } +} + +/*! + \property QRhiWidget::autoRenderTarget + + This property controls if a depth-stencil QRhiRenderBuffer and a + QRhiTextureRenderTarget is created and maintained automatically by the + widget. 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 QRhiWidget::isAutoRenderTargetEnabled() const +{ + Q_D(const QRhiWidget); + return d->autoRenderTarget; +} + +void QRhiWidget::setAutoRenderTarget(bool enabled) +{ + Q_D(QRhiWidget); + if (d->autoRenderTarget != enabled) { + d->autoRenderTarget = enabled; + emit autoRenderTargetChanged(enabled); + update(); + } +} + +/*! + Renders a new frame, reads the contents of the texture back, and returns it + as a QImage. + + When an error occurs, a null QImage is returned. + + The returned QImage will have a format of QImage::Format_RGBA8888, + QImage::Format_RGBA16FPx4, QImage::Format_RGBA32FPx4, or + QImage::Format_BGR30 depending on textureFormat(). + + QRhiWidget does not know the renderer's approach to blending and + composition, and therefore cannot know if the output has alpha + premultiplied in the RGB color values. Thus \c{_Premultiplied} QImage + formats are never used for the returned QImage, even when it would be + appropriate. It is up to the caller to reinterpret the resulting data as it + sees fit. + + This function can also be called when the QRhiWidget is not added to a + widget hierarchy belonging to an on-screen top-level window. This allows + generating an image from a 3D rendering off-screen. + + \sa setTextureFormat() + */ +QImage QRhiWidget::grab() +{ + Q_D(QRhiWidget); + if (d->noSize) + return QImage(); + + d->ensureRhi(); + if (!d->rhi) { + // The widget (and its parent chain, if any) may not be shown at + // all, yet one may still want to use it for grabs. This is + // ridiculous of course because the rendering infrastructure is + // tied to the top-level widget that initializes upon expose, but + // it has to be supported. + d->offscreenRenderer.setConfig(d->config); + // no window passed in, so no swapchain, but we get a functional QRhi which we own + d->offscreenRenderer.create(); + d->rhi = d->offscreenRenderer.rhi(); + if (!d->rhi) { + qWarning("QRhiWidget: Failed to create dedicated QRhi for grabbing"); + emit renderFailed(); + return QImage(); + } + } + + QRhiCommandBuffer *cb = nullptr; + if (d->rhi->beginOffscreenFrame(&cb) != QRhi::FrameOpSuccess) + return QImage(); + + QRhiReadbackResult readResult; + bool readCompleted = false; + bool needsInit = false; + d->ensureTexture(&needsInit); + + if (d->colorTexture || d->msaaColorBuffer) { + bool canRender = true; + if (needsInit) + canRender = d->invokeInitialize(cb); + if (canRender) + render(cb); + + QRhiResourceUpdateBatch *readbackBatch = d->rhi->nextResourceUpdateBatch(); + readResult.completed = [&readCompleted] { readCompleted = true; }; + readbackBatch->readBackTexture(d->resolveTexture ? d->resolveTexture : d->colorTexture, &readResult); + cb->resourceUpdate(readbackBatch); + } + + d->rhi->endOffscreenFrame(); + + if (readCompleted) { + QImage::Format imageFormat = QImage::Format_RGBA8888; + switch (d->widgetTextureFormat) { + case TextureFormat::RGBA8: + break; + case TextureFormat::RGBA16F: + imageFormat = QImage::Format_RGBA16FPx4; + break; + case TextureFormat::RGBA32F: + imageFormat = QImage::Format_RGBA32FPx4; + break; + case TextureFormat::RGB10A2: + imageFormat = QImage::Format_BGR30; + break; + } + QImage wrapperImage(reinterpret_cast(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + imageFormat); + QImage result; + if (d->rhi->isYUpInFramebuffer()) + result = wrapperImage.mirrored(); + else + result = wrapperImage.copy(); + result.setDevicePixelRatio(devicePixelRatio()); + return result; + } else { + Q_UNREACHABLE(); + } + + return QImage(); +} + +/*! + Called when the widget is initialized for the first time, when the + associated texture's size, format, or sample count changes, or when the + QRhi and 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 widget 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. One + special case where the objects will be different is when performing a + grab() with a widget that is not yet shown, and then making the + widget visible on-screen within a top-level widget. There the grab will + happen with a dedicated QRhi that is then replaced with the top-level + window's associated QRhi in subsequent initialize() and render() + invocations. Another, more common case is when the widget is reparented so + that it belongs to a new top-level window. In this case the QRhi and all + related resources managed by the QRhiWidget 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 + by the widget anymore. + + When \l autoRenderTarget is \c true, which is the default, a + depth-stencil QRhiRenderBuffer and a QRhiTextureRenderTarget associated + with 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() / + 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 of the widget. 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(). + + \sa render() + */ +void QRhiWidget::initialize(QRhiCommandBuffer *cb) +{ + Q_UNUSED(cb); +} + +/*! + Called when the widget contents (i.e. the contents of the texture) need + updating. + + There is always at least one call to initialize() before this function is + called. + + To request updates, call QWidget::update(). Calling update() from within + render() will lead to updating continuously, throttled by vsync. + + \a cb is the QRhiCommandBuffer for the current frame of the widget. The + function is called with a frame being recorded, but without an active + render pass. + + \sa initialize() + */ +void QRhiWidget::render(QRhiCommandBuffer *cb) +{ + Q_UNUSED(cb); +} + +/*! + Called when the need to early-release the graphics resources arises. + + This normally does not happen for a QRhiWidget that is added to a top-level + widget's child hierarchy and it then stays there for the rest of its and + the top-level's lifetime. Thus in many cases there is no need to + reimplement this function, e.g. because the application only ever has a + single top-level widget (native window). However, when reparenting of the + widget (or an ancestor of it) is involved, reimplementing this function + will become necessary in robust, well-written QRhiWidget subclasses. + + When this function is called, the implementation is expected to destroy all + QRhi resources (QRhiBuffer, QRhiTexture, etc. objects), similarly to how it + is expected to do this in the destructor. Nulling out, using a smart + pointer, or setting a \c{resources-invalid} flag is going to be required as + well, because initialize() will eventually get called afterwards. Note + however that deferring the releasing of resources to the subsequent + initialize() is wrong. If this function is called, the resource must be + dropped before returning. Also note that implementing this function does + not replace the class destructor (or smart pointers): the graphics + resources must still be released in both. + + See the \l{Cube RHI Widget Example} for an example of this in action. There + the button that toggles the QRhiWidget between being a child widget (due to + having a parent widget) and being a top-level widget (due to having no + parent widget), will trigger invoking this function since the associated + top-level widget, native window, and QRhi all change during the lifetime of + the QRhiWidget, with the previously used QRhi getting destroyed which + implies an early-release of the associated resources managed by the + still-alive QRhiWidget. + + Another case when this function is called is when grab() is used + with a QRhiWidget that is not added to a visible window, i.e. the rendering + is performed offscreen. If later on this QRhiWidget is made visible, or + added to a visible widget hierarchy, the associated QRhi will change from + the temporary one used for offscreen rendering to the window's dedicated + one, thus triggering this function as well. + + \sa initialize() + */ +void QRhiWidget::releaseResources() +{ +} + +/*! + \return the current QRhi object. + + Must only be called from initialize() and render(). + */ +QRhi *QRhiWidget::rhi() const +{ + Q_D(const QRhiWidget); + return d->rhi; +} + +/*! + \return the texture serving as the color buffer for the widget. + + 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 QRhiWidget, 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 *QRhiWidget::colorTexture() const +{ + Q_D(const QRhiWidget); + return d->colorTexture; +} + +/*! + \return the renderbuffer serving as the multisample color buffer for the widget. + + 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, QRhiWidget + 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 *QRhiWidget::msaaColorBuffer() const +{ + Q_D(const QRhiWidget); + return d->msaaColorBuffer; +} + +/*! + \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 composited with the rest + of the QWidget content on-screen. However, the QRhiWidget'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 *QRhiWidget::resolveTexture() const +{ + Q_D(const QRhiWidget); + return d->resolveTexture; +} + +/*! + \return the depth-stencil buffer used by the widget'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 *QRhiWidget::depthStencilBuffer() const +{ + Q_D(const QRhiWidget); + return d->depthStencilBuffer; +} + +/*! + \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 *QRhiWidget::renderTarget() const +{ + Q_D(const QRhiWidget); + return d->renderTarget; +} + +/*! + \fn void QRhiWidget::framePresented() + + This signal is emitted after the widget's top-level window has finished + composition and has \l{QRhi::endFrame()}{submitted a frame}. +*/ + +/*! + \fn void QRhiWidget::renderFailed() + + This signal is emitted whenever the widget is supposed to render to its + backing texture (either due to a \l{QWidget::update()}{widget update} or + due to a call to grab()), but there is no \l QRhi for the widget to + use, likely due to issues related to graphics configuration. + + This signal may be emitted multiple times when a problem arises. Do not + assume it is emitted only once. Connect with Qt::SingleShotConnection if + the error handling code is to be notified only once. +*/ + +QT_END_NAMESPACE diff --git a/src/widgets/kernel/qrhiwidget.h b/src/widgets/kernel/qrhiwidget.h new file mode 100644 index 0000000000..592c35e737 --- /dev/null +++ b/src/widgets/kernel/qrhiwidget.h @@ -0,0 +1,102 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef QRHIWIDGET_H +#define QRHIWIDGET_H + +#include + +QT_BEGIN_NAMESPACE + +class QRhiWidgetPrivate; +class QRhi; +class QRhiTexture; +class QRhiRenderBuffer; +class QRhiTextureRenderTarget; +class QRhiCommandBuffer; + +class Q_WIDGETS_EXPORT QRhiWidget : public QWidget +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QRhiWidget) + Q_PROPERTY(int sampleCount READ sampleCount WRITE setSampleCount NOTIFY sampleCountChanged) + Q_PROPERTY(TextureFormat textureFormat READ textureFormat WRITE setTextureFormat NOTIFY textureFormatChanged) + Q_PROPERTY(bool autoRenderTarget READ isAutoRenderTargetEnabled WRITE setAutoRenderTarget NOTIFY autoRenderTargetChanged) + Q_PROPERTY(QSize explicitSize READ explicitSize WRITE setExplicitSize NOTIFY explicitSizeChanged) + Q_PROPERTY(bool mirrorVertically READ isMirrorVerticallyEnabled WRITE setMirrorVertically NOTIFY mirrorVerticallyChanged) + +public: + QRhiWidget(QWidget *parent = nullptr, Qt::WindowFlags f = {}); + ~QRhiWidget(); + + enum class Api { + OpenGL, + Metal, + Vulkan, + D3D11, + D3D12, + Null + }; + Q_ENUM(Api) + + enum class TextureFormat { + RGBA8, + RGBA16F, + RGBA32F, + RGB10A2 + }; + Q_ENUM(TextureFormat) + + Api api() const; + void setApi(Api api); + + bool isDebugLayerEnabled() const; + void setDebugLayer(bool enable); + + int sampleCount() const; + void setSampleCount(int samples); + + TextureFormat textureFormat() const; + void setTextureFormat(TextureFormat format); + + QSize explicitSize() const; + void setExplicitSize(const QSize &pixelSize); + void setExplicitSize(int w, int h) { setExplicitSize(QSize(w, h)); } + + bool isAutoRenderTargetEnabled() const; + void setAutoRenderTarget(bool enabled); + + bool isMirrorVerticallyEnabled() const; + void setMirrorVertically(bool enabled); + + QImage grab(); + + virtual void initialize(QRhiCommandBuffer *cb); + virtual void render(QRhiCommandBuffer *cb); + virtual void releaseResources(); + + QRhi *rhi() const; + QRhiTexture *colorTexture() const; + QRhiRenderBuffer *msaaColorBuffer() const; + QRhiTexture *resolveTexture() const; + QRhiRenderBuffer *depthStencilBuffer() const; + QRhiTextureRenderTarget *renderTarget() const; + +Q_SIGNALS: + void frameSubmitted(); + void renderFailed(); + void sampleCountChanged(int samples); + void textureFormatChanged(TextureFormat format); + void autoRenderTargetChanged(bool enabled); + void explicitSizeChanged(const QSize &pixelSize); + void mirrorVerticallyChanged(bool enabled); + +protected: + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + bool event(QEvent *e) override; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/widgets/kernel/qrhiwidget_p.h b/src/widgets/kernel/qrhiwidget_p.h new file mode 100644 index 0000000000..cc3ab26861 --- /dev/null +++ b/src/widgets/kernel/qrhiwidget_p.h @@ -0,0 +1,63 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef QRHIWIDGET_P_H +#define QRHIWIDGET_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 "qrhiwidget.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QRhiWidgetPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QRhiWidget) +public: + TextureData texture() const override; + QPlatformTextureList::Flags textureListFlags() override; + QPlatformBackingStoreRhiConfig rhiConfig() const override; + void endCompose() override; + + void ensureRhi(); + void ensureTexture(bool *changed); + bool invokeInitialize(QRhiCommandBuffer *cb); + void resetColorBufferObjects(); + void resetRenderTargetObjects(); + void releaseResources(); + + QRhi *rhi = nullptr; + bool noSize = false; + QPlatformBackingStoreRhiConfig config; + QRhiWidget::TextureFormat widgetTextureFormat = QRhiWidget::TextureFormat::RGBA8; + QRhiTexture::Format rhiTextureFormat = QRhiTexture::RGBA8; + int samples = 1; + QSize explicitSize; + bool autoRenderTarget = true; + bool mirrorVertically = false; + QBackingStoreRhiSupport offscreenRenderer; + bool textureInvalid = false; + QRhiTexture *colorTexture = nullptr; + QRhiRenderBuffer *msaaColorBuffer = nullptr; + QRhiTexture *resolveTexture = nullptr; + QRhiRenderBuffer *depthStencilBuffer = nullptr; + QRhiTextureRenderTarget *renderTarget = nullptr; + QRhiRenderPassDescriptor *renderPassDescriptor = nullptr; + mutable QVector pendingDeletes; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index f893dcda3d..da7fae2af8 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -10858,9 +10858,10 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f) // do it on newtlw instead, the performance implications of that are // problematic when it comes to large widget trees. if (q_evaluateRhiConfig(this, nullptr, &surfaceType)) { + const bool wasUsingRhiFlush = newtlw->d_func()->usesRhiFlush; newtlw->d_func()->usesRhiFlush = true; if (QWindow *w = newtlw->windowHandle()) { - if (w->surfaceType() != surfaceType) { + if (w->surfaceType() != surfaceType || !wasUsingRhiFlush) { newtlw->destroy(); newtlw->create(); } -- cgit v1.2.3