diff options
Diffstat (limited to 'src/quick/items/qquickrendercontrol.cpp')
-rw-r--r-- | src/quick/items/qquickrendercontrol.cpp | 316 |
1 files changed, 283 insertions, 33 deletions
diff --git a/src/quick/items/qquickrendercontrol.cpp b/src/quick/items/qquickrendercontrol.cpp index 190e0942ab..b14a3f35f7 100644 --- a/src/quick/items/qquickrendercontrol.cpp +++ b/src/quick/items/qquickrendercontrol.cpp @@ -43,29 +43,36 @@ #include <QtCore/QCoreApplication> #include <QtCore/QTime> #include <QtQuick/private/qquickanimatorcontroller_p.h> +#include <QtQuick/private/qsgdefaultrendercontext_p.h> +#include <QtQuick/private/qsgrhisupport_p.h> #if QT_CONFIG(opengl) # include <QOpenGLContext> -# include <QtQuick/private/qsgdefaultrendercontext_p.h> #if QT_CONFIG(quick_shadereffect) # include <QtQuick/private/qquickopenglshadereffectnode_p.h> #endif #endif #include <QtGui/private/qguiapplication_p.h> #include <qpa/qplatformintegration.h> +#include <QtGui/qoffscreensurface.h> #include <QtQml/private/qqmlglobal_p.h> #include <QtQuick/QQuickWindow> +#include <QtQuick/QQuickRenderTarget> #include <QtQuick/private/qquickwindow_p.h> #include <QtQuick/private/qquickitem_p.h> #include <QtQuick/private/qsgsoftwarerenderer_p.h> #include <QtCore/private/qobject_p.h> +#include <QtQuick/private/qquickwindow_p.h> +#include <QtGui/private/qrhi_p.h> + QT_BEGIN_NAMESPACE #if QT_CONFIG(opengl) extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha); #endif + /*! \class QQuickRenderControl @@ -136,9 +143,14 @@ extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_ QSGContext *QQuickRenderControlPrivate::sg = nullptr; -QQuickRenderControlPrivate::QQuickRenderControlPrivate() - : initialized(0), - window(nullptr) +QQuickRenderControlPrivate::QQuickRenderControlPrivate(QQuickRenderControl *renderControl) + : q(renderControl), + initialized(false), + window(nullptr), + rhi(nullptr), + cb(nullptr), + offscreenSurface(nullptr), + sampleCount(1) { if (!sg) { qAddPostRoutine(cleanup); @@ -158,7 +170,7 @@ void QQuickRenderControlPrivate::cleanup() object \a parent. */ QQuickRenderControl::QQuickRenderControl(QObject *parent) - : QObject(*(new QQuickRenderControlPrivate), parent) + : QObject(*(new QQuickRenderControlPrivate(this)), parent) { } @@ -182,11 +194,16 @@ QQuickRenderControl::~QQuickRenderControl() d->windowDestroyed(); delete d->rc; + + d->resetRhi(); } void QQuickRenderControlPrivate::windowDestroyed() { if (window) { + QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window); + cd->cleanupNodesOnShutdown(); + rc->invalidate(); QQuickWindowPrivate::get(window)->animationController.reset(); @@ -215,6 +232,90 @@ void QQuickRenderControl::prepareThread(QThread *targetThread) } /*! + Sets the number of samples to use for multisampling. When \a sampleCount is + 0 or 1, multisampling is disabled. + + \note This function is always used in combination with a multisample render + target, which means \a sampleCount must match the sample count passed to + QQuickRenderTarget::fromNativeTexture(), which in turn must match the + sample count of the native texture. + + \since 6.0 + + \sa initialize(), QQuickRenderTarget + */ +void QQuickRenderControl::setSamples(int sampleCount) +{ + Q_D(QQuickRenderControl); + d->sampleCount = qMax(1, sampleCount); +} + +/*! + \return the current sample count. 1 or 0 means no multisampling. + + \since 6.0 + */ +int QQuickRenderControl::samples() const +{ + Q_D(const QQuickRenderControl); + return d->sampleCount; +} + +/*! + Initializes the scene graph resources. When using a graphics API, such as + Vulkan, Metal, OpenGL, or Direct3D, for Qt Quick rendering, + QQuickRenderControl will set up an appropriate rendering engine when this + function is called. This rendering infrastructure exists as long as the + QQuickRenderControl exists. + + To control what graphics API Qt Quick uses, call + QQuickWindow::setSceneGraphBackend() with one of the + QSGRendererInterface:GraphicsApi constants. That must be done before + calling this function. + + To prevent the scenegraph from creating its own device and context objects, + specify an appropriate QQuickGraphicsDevice, wrapping existing graphics + objects, by calling QQuickWindow::setGraphicsDevice(). + + \note This function does not need to be, and must not be, called when using + the \c software adaptation of Qt Quick. + + \since 6.0 + + \sa QQuickRenderTarget, QQuickGraphicsDevice + */ +bool QQuickRenderControl::initialize() +{ + Q_D(QQuickRenderControl); + + if (!d->window) { + qWarning("QQuickRenderControl::initialize called with no associated window"); + return false; + } + + if (!d->initRhi()) + return false; + + QQuickWindowPrivate *wd = QQuickWindowPrivate::get(d->window); + wd->rhi = d->rhi; + + QSGDefaultRenderContext *renderContext = qobject_cast<QSGDefaultRenderContext *>(d->rc); + if (renderContext) { + QSGDefaultRenderContext::InitParams params; + params.rhi = d->rhi; + params.sampleCount = d->sampleCount; + params.initialSurfacePixelSize = d->window->size() * d->window->effectiveDevicePixelRatio(); + params.maybeSurface = d->window; + renderContext->initialize(¶ms); + } else { + qWarning("QRhi is only compatible with default adaptation"); + return false; + } + + return true; +} + +/*! Initializes the scene graph resources. The context \a gl has to be the current OpenGL context or null if it is not relevant because a Qt Quick backend other than OpenGL is in use. @@ -295,24 +396,33 @@ bool QQuickRenderControl::sync() return false; QQuickWindowPrivate *cd = QQuickWindowPrivate::get(d->window); + // we may not have a d->rhi (software backend) hence the check is important + if (d->rhi) { + if (!d->rhi->isRecordingFrame()) { + qWarning("QQuickRenderControl can only sync when beginFrame() has been called"); + return false; + } + if (!d->cb) { + qWarning("QQuickRenderControl cannot be used with QRhi when no QRhiCommandBuffer is provided"); + return false; + } + cd->setCustomCommandBuffer(d->cb); + } + cd->syncSceneGraph(); d->rc->endSync(); - // TODO: find out if the sync actually caused a scenegraph update. return true; } /*! - Stop rendering and release resources. Requires a current context. + Stop rendering and release resources. This is the equivalent of the cleanup operations that happen with a real QQuickWindow when the window becomes hidden. This function is called from the destructor. Therefore there will - typically be no need to call it directly. Pay attention however to - the fact that this requires the context, that was passed to - initialize(), to be the current one at the time of destroying the - QQuickRenderControl instance. + typically be no need to call it directly. Once invalidate() has been called, it is possible to reuse the QQuickRenderControl instance by calling initialize() again. @@ -353,6 +463,19 @@ void QQuickRenderControl::render() return; QQuickWindowPrivate *cd = QQuickWindowPrivate::get(d->window); + // we may not have a d->rhi (software backend) hence the check is important + if (d->rhi) { + if (!d->rhi->isRecordingFrame()) { + qWarning("QQuickRenderControl can only render when beginFrame() has been called"); + return; + } + if (!d->cb) { + qWarning("QQuickRenderControl cannot be used with QRhi when no QRhiCommandBuffer is provided"); + return; + } + cd->setCustomCommandBuffer(d->cb); + } + cd->renderSceneGraph(d->window->size()); } @@ -378,48 +501,50 @@ void QQuickRenderControl::render() for example. This will lead to better performance. */ -/*! - Grabs the contents of the scene and returns it as an image. - - \note Requires the context to be current. - */ -QImage QQuickRenderControl::grab() +QImage QQuickRenderControlPrivate::grab() { - Q_D(QQuickRenderControl); - if (!d->window) + if (!window) return QImage(); QImage grabContent; - if (d->window->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL) { + if (rhi) { + + // As documented by QQuickWindow::grabWindow(): Nothing to do here, we + // do not support "grabbing" with an application-provided render target + // in Qt 6. (with the exception of the software backend because that + // does not support custom render targets, so the grab implementation + // here is still valuable) + + } else if (window->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL) { #if QT_CONFIG(opengl) - QQuickWindowPrivate *cd = QQuickWindowPrivate::get(d->window); + QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window); cd->polishItems(); cd->syncSceneGraph(); - d->rc->endSync(); - render(); - const bool alpha = d->window->format().alphaBufferSize() > 0 && d->window->color().alpha() < 255; - grabContent = qt_gl_read_framebuffer(d->window->size() * d->window->effectiveDevicePixelRatio(), alpha, alpha); - if (QQuickRenderControl::renderWindowFor(d->window)) { - grabContent.setDevicePixelRatio(d->window->effectiveDevicePixelRatio()); + rc->endSync(); + q->render(); + const bool alpha = window->format().alphaBufferSize() > 0 && window->color().alpha() < 255; + grabContent = qt_gl_read_framebuffer(window->size() * window->effectiveDevicePixelRatio(), alpha, alpha); + if (QQuickRenderControl::renderWindowFor(window)) { + grabContent.setDevicePixelRatio(window->effectiveDevicePixelRatio()); } #endif #if QT_CONFIG(thread) - } else if (d->window->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) { - QQuickWindowPrivate *cd = QQuickWindowPrivate::get(d->window); + } else if (window->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) { + QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window); cd->polishItems(); cd->syncSceneGraph(); QSGSoftwareRenderer *softwareRenderer = static_cast<QSGSoftwareRenderer *>(cd->renderer); if (softwareRenderer) { - const qreal dpr = d->window->effectiveDevicePixelRatio(); - const QSize imageSize = d->window->size() * dpr; + const qreal dpr = window->effectiveDevicePixelRatio(); + const QSize imageSize = window->size() * dpr; grabContent = QImage(imageSize, QImage::Format_ARGB32_Premultiplied); grabContent.setDevicePixelRatio(dpr); QPaintDevice *prevDev = softwareRenderer->currentPaintDevice(); softwareRenderer->setCurrentPaintDevice(&grabContent); softwareRenderer->markDirty(); - d->rc->endSync(); - render(); + rc->endSync(); + q->render(); softwareRenderer->setCurrentPaintDevice(prevDev); } #endif @@ -474,6 +599,131 @@ QWindow *QQuickRenderControl::renderWindowFor(QQuickWindow *win, QPoint *offset) return nullptr; } +/*! + \return the QQuickWindow this QQuickRenderControl is associated with. + + \note A QQuickRenderControl gets associated with a QQuickWindow when + constructing the QQuickWindow. The return value from this function is null + before that point. + + \since 6.0 + */ +QQuickWindow *QQuickRenderControl::window() const +{ + Q_D(const QQuickRenderControl); + return d->window; +} + +/*! + Specifies the start of a graphics frame. Calls to sync() or render() must + be enclosed by calls to beginFrame() and endFrame(). + + Unlike the earlier OpenGL-only world of Qt 5, rendering with other graphics + APIs requires more well-defined points of starting and ending a frame. When + manually driving the rendering loop via QQuickRenderControl, it now falls + to the user of QQuickRenderControl to specify these points. + + A typical update step, including initialization of rendering into an + existing texture, could like like the following. The example snippet + assumes Direct3D 11 but the same concepts apply other graphics APIs as + well. + + \badcode + if (!m_quickInitialized) { + m_quickWindow->setGraphicsDevice(QQuickGraphicsDevice::fromDeviceAndContext(m_engine->device(), m_engine->context())); + + if (!m_renderControl->initialize()) + qWarning("Failed to initialize redirected Qt Quick rendering"); + + m_quickWindow->setRenderTarget(QQuickRenderTarget::fromNativeTexture({ &m_res.texture, 0 }, + QSize(QML_WIDTH, QML_HEIGHT), + SAMPLE_COUNT)); + + m_quickInitialized = true; + } + + m_renderControl->polishItems(); + + m_renderControl->beginFrame(); + m_renderControl->sync(); + m_renderControl->render(); + m_renderControl->endFrame(); // Qt Quick's rendering commands are submitted to the device context here + \endcode + + \since 6.0 + + \sa endFrame(), initialize(), sync(), render(), QQuickGraphicsDevice, QQuickRenderTarget + */ +void QQuickRenderControl::beginFrame() +{ + Q_D(QQuickRenderControl); + if (!d->rhi || d->rhi->isRecordingFrame()) + return; + + d->rhi->beginOffscreenFrame(&d->cb, QRhi::ExternalContentsInPass); // must specify ExternalContents since we do not know in advance +} + +/*! + Specifies the end of a graphics frame. Calls to sync() or render() must be + enclosed by calls to beginFrame() and endFrame(). + + When this function is called, any graphics commands enqueued by the + scenegraph are submitted to the context or command queue, whichever is + applicable. + + \since 6.0 + + \sa beginFrame(), initialize(), sync(), render(), QQuickGraphicsDevice, QQuickRenderTarget + */ +void QQuickRenderControl::endFrame() +{ + Q_D(QQuickRenderControl); + if (!d->rhi || !d->rhi->isRecordingFrame()) + return; + + d->rhi->endOffscreenFrame(); + d->cb = nullptr; +} + +bool QQuickRenderControlPrivate::initRhi() +{ + // initialize() - invalidate() - initialize() uses the QRhi the first + // initialize() created, so if already exists, we are done + if (rhi) + return true; + + QSGRhiSupport *rhiSupport = QSGRhiSupport::instance(); + + // sanity check for Vulkan +#if QT_CONFIG(vulkan) + if (rhiSupport->rhiBackend() == QRhi::Vulkan && !window->vulkanInstance()) { + qWarning("QQuickRenderControl: No QVulkanInstance set for QQuickWindow, cannot initialize"); + return false; + } +#endif + + // for OpenGL + if (!offscreenSurface) + offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window); + + rhi = rhiSupport->createRhi(window, offscreenSurface); + if (!rhi) { + qWarning("QQuickRenderControl: Failed to initialize QRhi"); + return false; + } + + return true; +} + +void QQuickRenderControlPrivate::resetRhi() +{ + delete rhi; + rhi = nullptr; + + delete offscreenSurface; + offscreenSurface = nullptr; +} + QT_END_NAMESPACE #include "moc_qquickrendercontrol.cpp" |