1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qrhiwidget_p.h"
5#include <private/qguiapplication_p.h>
6#include <qpa/qplatformintegration.h>
7#include <private/qwidgetrepaintmanager_p.h>
8
9QT_BEGIN_NAMESPACE
10
11/*!
12 \class QRhiWidget
13 \inmodule QtWidgets
14 \since 6.7
15
16 \brief The QRhiWidget class is a widget for rendering 3D graphics via an
17 accelerated grapics API, such as Vulkan, Metal, or Direct 3D.
18
19 QRhiWidget provides functionality for displaying 3D content rendered
20 through the \l QRhi APIs within a QWidget-based application. In many ways
21 it is the portable equivalent of \l QOpenGLWidget that is not tied to a
22 single 3D graphics API, but rather can function with all the APIs QRhi
23 supports (such as, Direct 3D 11/12, Vulkan, Metal, and OpenGL).
24
25 QRhiWidget is expected to be subclassed. To render into the 2D texture that
26 is implicitly created and managed by the QRhiWidget, subclasses should
27 reimplement the virtual functions initialize() and render().
28
29 The size of the texture will by default adapt to the size of the widget. If
30 a fixed size is preferred, set a fixed size specified in pixels by calling
31 setFixedColorBufferSize().
32
33 In addition to the texture serving as the color buffer, a depth/stencil
34 buffer and a render target binding these together is maintained implicitly
35 as well.
36
37 The QRhi for the widget's top-level window is configured to use a
38 platform-specific backend and graphics API by default: Metal on macOS and
39 iOS, Direct 3D 11 on Windows, OpenGL otherwise. Call setApi() to override
40 this.
41
42 \note A single widget window can only use one QRhi backend, and so one
43 single 3D graphics API. If two QRhiWidget or QQuickWidget widgets in the
44 window's widget hierarchy request different APIs, only one of them will
45 function correctly.
46
47 \note While QRhiWidget is a public Qt API, the QRhi family of classes in
48 the Qt Gui module, including QRhi, QShader and QShaderDescription, offer
49 limited compatibility guarantees. There are no source or binary
50 compatibility guarantees for these classes, meaning the API is only
51 guaranteed to work with the Qt version the application was developed
52 against. Source incompatible changes are however aimed to be kept at a
53 minimum and will only be made in minor releases (6.7, 6.8, and so on).
54 \c{qrhiwidget.h} does not directly include any QRhi-related headers. To use
55 those classes when implementing a QRhiWidget subclass, link to
56 \c{Qt::GuiPrivate} (if using CMake), and include the appropriate headers
57 with the \c rhi prefix, for example \c{#include <rhi/qrhi.h>}.
58
59 An example of a simple QRhiWidget subclass rendering a triangle is the
60 following:
61
62 \snippet qrhiwidget/rhiwidgetintro.cpp 0
63
64 This is a widget that continuously requests updates, throttled by the
65 presentation rate (vsync, depending on the screen refresh rate). If
66 rendering continuously is not desired, the update() call in render() should
67 be removed, and rather issued only when updating the rendered content is
68 necessary. For example, if the rotation of the cube should be tied to the
69 value of a QSlider, then connecting the slider's value change signal to a
70 slot or lambda that forwards the new value and calls update() is
71 sufficient.
72
73 The vertex and fragment shaders are provided as Vulkan-style GLSL and must
74 be processed first by the Qt shader infrastructure first. This is achieved
75 either by running the \c qsb command-line tool manually, or by using the
76 \l{Qt Shader Tools Build System Integration}{qt_add_shaders()} function in
77 CMake. The QRhiWidget implementation loads these pre-processed \c{.qsb}
78 files that are shipped with the application. See \l{Qt Shader Tools} for
79 more information about Qt's shader translation infrastructure.
80
81 The source code for these shaders could be the following:
82
83 \c{color.vert}
84
85 \snippet qrhiwidget/rhiwidgetintro.vert 0
86
87 \c{color.frag}
88
89 \snippet qrhiwidget/rhiwidgetintro.frag 0
90
91 The result is a widget that shows the following:
92
93 \image qrhiwidget-intro.jpg
94
95 For a complete, minimal, introductory example check out the \l{Simple RHI
96 Widget Example}.
97
98 For an example with more functionality and demonstration of further
99 concepts, see the \l{Cube RHI Widget Example}.
100
101 QRhiWidget always involves rendering into a backing texture, not
102 directly to the window (the surface or layer provided by the windowing
103 system for the native window). This allows properly compositing the content
104 with the rest of the widget-based UI, and offering a simple and compact
105 API, making it easy to get started. All this comes at the expense of
106 additional resources and a potential effect on performance. This is often
107 perfectly acceptable in practice, but advanced users should keep in mind
108 the pros and cons of the different approaches. Refer to the \l{RHI Window
109 Example} and compare it with the \l{Simple RHI Widget Example} for details
110 about the two approaches.
111
112 Reparenting a QRhiWidget into a widget hierarchy that belongs to a
113 different window (top-level widget), or making the QRhiWidget itself a
114 top-level (by setting the parent to \nullptr), involves changing the
115 associated QRhi (and potentially destroying the old one) while the
116 QRhiWidget continues to stay alive and well. To support this, robust
117 QRhiWidget implementations are expected to reimplement the
118 releaseResources() virtual function as well, and drop their QRhi resources
119 just as they do in the destructor. The \l{Cube RHI Widget Example}
120 demonstrates this in practice.
121
122 While not a primary use case, QRhiWidget also allows incorporating
123 rendering code that directly uses a 3D graphics API such as Vulkan, Metal,
124 Direct 3D, or OpenGL. See \l QRhiCommandBuffer::beginExternal() for details
125 on recording native commands within a QRhi render pass, as well as
126 \l QRhiTexture::createFrom() for a way to wrap an existing native texture and
127 then use it with QRhi in a subsequent render pass. Note however that the
128 configurability of the underlying graphics API (its device or context
129 features, layers, extensions, etc.) is going to be limited since
130 QRhiWidget's primary goal is to provide an environment suitable for
131 QRhi-based rendering code, not to enable arbitrary, potentially complex,
132 foreign rendering engines.
133
134 \since 6.7
135
136 \sa QRhi, QShader, QOpenGLWidget, {Simple RHI Widget Example}, {Cube RHI Widget Example}
137 */
138
139/*!
140 \enum QRhiWidget::Api
141 Specifies the 3D API and QRhi backend to use
142
143 \value Null
144 \value OpenGL
145 \value Metal
146 \value Vulkan
147 \value Direct3D11
148 \value Direct3D12
149
150 \sa QRhi
151 */
152
153/*!
154 \enum QRhiWidget::TextureFormat
155 Specifies the format of the texture to which the QRhiWidget renders.
156
157 \value RGBA8 See QRhiTexture::RGBA8.
158 \value RGBA16F See QRhiTexture::RGBA16F.
159 \value RGBA32F See QRhiTexture::RGBA32F.
160 \value RGB10A2 See QRhiTexture::RGB10A2.
161
162 \sa QRhiTexture
163 */
164
165/*!
166 Constructs a widget which is a child of \a parent, with widget flags set to \a f.
167 */
168QRhiWidget::QRhiWidget(QWidget *parent, Qt::WindowFlags f)
169 : QWidget(*(new QRhiWidgetPrivate), parent, f)
170{
171 Q_D(QRhiWidget);
172 d->init();
173}
174
175/*!
176 * \internal
177 */
178QRhiWidget::QRhiWidget(QRhiWidgetPrivate &dd, QWidget *parent, Qt::WindowFlags f)
179 : QWidget(dd, parent, f)
180{
181 Q_D(QRhiWidget);
182 d->init();
183}
184
185/*!
186 * \internal
187 */
188void QRhiWidgetPrivate::init()
189{
190 if (Q_UNLIKELY(!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RhiBasedRendering)))
191 qWarning(msg: "QRhiWidget: QRhi is not supported on this platform.");
192 else
193 setRenderToTexture();
194
195 config.setEnabled(true);
196#if defined(Q_OS_DARWIN)
197 config.setApi(QPlatformBackingStoreRhiConfig::Metal);
198#elif defined(Q_OS_WIN)
199 config.setApi(QPlatformBackingStoreRhiConfig::D3D11);
200#else
201 config.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
202#endif
203}
204
205/*!
206 Destructor.
207 */
208QRhiWidget::~QRhiWidget()
209{
210 Q_D(QRhiWidget);
211
212 if (d->rhi) {
213 d->rhi->removeCleanupCallback(key: this);
214 // rhi resources must be destroyed here, due to how QWidget teardown works;
215 // it should not be left to the private object's destruction.
216 d->resetRenderTargetObjects();
217 d->resetColorBufferObjects();
218 qDeleteAll(c: d->pendingDeletes);
219 }
220
221 d->offscreenRenderer.reset();
222}
223
224/*!
225 Handles resize events that are passed in the \a e event parameter. Calls
226 the virtual function initialize().
227
228 \note Avoid overriding this function in derived classes. If that is not
229 feasible, make sure that QRhiWidget's implementation is invoked too.
230 Otherwise the underlying texture object and related resources will not get
231 resized properly and will lead to incorrect rendering.
232 */
233void QRhiWidget::resizeEvent(QResizeEvent *e)
234{
235 Q_D(QRhiWidget);
236
237 if (e->size().isEmpty()) {
238 d->noSize = true;
239 return;
240 }
241 d->noSize = false;
242
243 d->sendPaintEvent(toBePainted: QRect(QPoint(0, 0), size()));
244}
245
246/*!
247 Handles paint events.
248
249 Calling QWidget::update() will lead to sending a paint event \a e, and thus
250 invoking this function. The sending of the event is asynchronous and will
251 happen at some point after returning from update(). This function will
252 then, after some preparation, call the virtual render() to update the
253 contents of the QRhiWidget's associated texture. The widget's top-level
254 window will then composite the texture with the rest of the window.
255 */
256void QRhiWidget::paintEvent(QPaintEvent *)
257{
258 Q_D(QRhiWidget);
259 if (!updatesEnabled() || d->noSize)
260 return;
261
262 d->ensureRhi();
263 if (!d->rhi) {
264 qWarning(msg: "QRhiWidget: No QRhi");
265 emit renderFailed();
266 return;
267 }
268
269 QRhiCommandBuffer *cb = nullptr;
270 if (d->rhi->beginOffscreenFrame(cb: &cb) != QRhi::FrameOpSuccess)
271 return;
272
273 bool needsInit = false;
274 d->ensureTexture(changed: &needsInit);
275 if (d->colorTexture || d->msaaColorBuffer) {
276 bool canRender = true;
277 if (needsInit)
278 canRender = d->invokeInitialize(cb);
279 if (canRender)
280 render(cb);
281 }
282
283 d->rhi->endOffscreenFrame();
284}
285
286/*!
287 \reimp
288*/
289bool QRhiWidget::event(QEvent *e)
290{
291 Q_D(QRhiWidget);
292 switch (e->type()) {
293 case QEvent::WindowAboutToChangeInternal:
294 // The QRhi will almost certainly change, prevent texture() from
295 // returning the existing QRhiTexture in the meantime.
296 d->textureInvalid = true;
297
298 if (d->rhi && d->rhi != d->offscreenRenderer.rhi()) {
299 // Drop the cleanup callback registered to the toplevel's rhi and
300 // do the early-release, there may not be another chance to do
301 // this, and the QRhi we have currently set may be destroyed by the
302 // time we get to ensureRhi() again.
303 d->rhi->removeCleanupCallback(key: this);
304 releaseResources(); // notify the user code about the early-release
305 d->releaseResources();
306 // must _not_ null out d->rhi here, for proper interaction with ensureRhi()
307 }
308
309 break;
310
311 case QEvent::Show:
312 if (isVisible())
313 d->sendPaintEvent(toBePainted: QRect(QPoint(0, 0), size()));
314 break;
315 default:
316 break;
317 }
318 return QWidget::event(event: e);
319}
320
321QWidgetPrivate::TextureData QRhiWidgetPrivate::texture() const
322{
323 // This is the only safe place to clear pendingDeletes, due to the
324 // possibility of the texture returned in the previous invocation of this
325 // function having been added to pendingDeletes, meaning the object then
326 // needs to be valid until the next (this) invocation of this function.
327 // (the exact object lifetime requirements depend on the
328 // QWidget/RepaintManager internal implementation; for now avoid relying on
329 // such details by clearing pendingDeletes only here, not in endCompose())
330 qDeleteAll(c: pendingDeletes);
331 pendingDeletes.clear();
332
333 TextureData td;
334 if (!textureInvalid)
335 td.textureLeft = resolveTexture ? resolveTexture : colorTexture;
336 return td;
337}
338
339QPlatformTextureList::Flags QRhiWidgetPrivate::textureListFlags()
340{
341 QPlatformTextureList::Flags flags = QWidgetPrivate::textureListFlags();
342 if (mirrorVertically)
343 flags |= QPlatformTextureList::MirrorVertically;
344 return flags;
345}
346
347QPlatformBackingStoreRhiConfig QRhiWidgetPrivate::rhiConfig() const
348{
349 return config;
350}
351
352void QRhiWidgetPrivate::endCompose()
353{
354 // This function is called by QWidgetRepaintManager right after the
355 // backingstore's QRhi-based flush returns. In practice that means after
356 // the begin-endFrame() on the top-level window's swapchain.
357
358 if (rhi) {
359 Q_Q(QRhiWidget);
360 emit q->frameSubmitted();
361 }
362}
363
364// This is reimplemented to enable calling QWidget::grab() on the widget or an
365// ancestor of it. At the same time, QRhiWidget provides its own
366// grabFramebuffer() as well, mirroring QQuickWidget and QOpenGLWidget for
367// consistency. In both types of grabs we end up in here.
368QImage QRhiWidgetPrivate::grabFramebuffer()
369{
370 Q_Q(QRhiWidget);
371 if (noSize)
372 return QImage();
373
374 ensureRhi();
375 if (!rhi) {
376 // The widget (and its parent chain, if any) may not be shown at
377 // all, yet one may still want to use it for grabs. This is
378 // ridiculous of course because the rendering infrastructure is
379 // tied to the top-level widget that initializes upon expose, but
380 // it has to be supported.
381 offscreenRenderer.setConfig(config);
382 // no window passed in, so no swapchain, but we get a functional QRhi which we own
383 offscreenRenderer.create();
384 rhi = offscreenRenderer.rhi();
385 if (!rhi) {
386 qWarning(msg: "QRhiWidget: Failed to create dedicated QRhi for grabbing");
387 emit q->renderFailed();
388 return QImage();
389 }
390 }
391
392 QRhiCommandBuffer *cb = nullptr;
393 if (rhi->beginOffscreenFrame(cb: &cb) != QRhi::FrameOpSuccess)
394 return QImage();
395
396 QRhiReadbackResult readResult;
397 bool readCompleted = false;
398 bool needsInit = false;
399 ensureTexture(changed: &needsInit);
400
401 if (colorTexture || msaaColorBuffer) {
402 bool canRender = true;
403 if (needsInit)
404 canRender = invokeInitialize(cb);
405 if (canRender)
406 q->render(cb);
407
408 QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
409 readResult.completed = [&readCompleted] { readCompleted = true; };
410 readbackBatch->readBackTexture(rb: resolveTexture ? resolveTexture : colorTexture, result: &readResult);
411 cb->resourceUpdate(resourceUpdates: readbackBatch);
412 }
413
414 rhi->endOffscreenFrame();
415
416 if (readCompleted) {
417 QImage::Format imageFormat = QImage::Format_RGBA8888;
418 switch (widgetTextureFormat) {
419 case QRhiWidget::TextureFormat::RGBA8:
420 break;
421 case QRhiWidget::TextureFormat::RGBA16F:
422 imageFormat = QImage::Format_RGBA16FPx4;
423 break;
424 case QRhiWidget::TextureFormat::RGBA32F:
425 imageFormat = QImage::Format_RGBA32FPx4;
426 break;
427 case QRhiWidget::TextureFormat::RGB10A2:
428 imageFormat = QImage::Format_BGR30;
429 break;
430 }
431 QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
432 readResult.pixelSize.width(), readResult.pixelSize.height(),
433 imageFormat);
434 QImage result;
435 if (rhi->isYUpInFramebuffer())
436 result = wrapperImage.flipped();
437 else
438 result = wrapperImage.copy();
439 result.setDevicePixelRatio(q->devicePixelRatio());
440 return result;
441 } else {
442 Q_UNREACHABLE();
443 }
444
445 return QImage();
446}
447
448void QRhiWidgetPrivate::resetColorBufferObjects()
449{
450 if (colorTexture) {
451 pendingDeletes.append(t: colorTexture);
452 colorTexture = nullptr;
453 }
454 if (msaaColorBuffer) {
455 pendingDeletes.append(t: msaaColorBuffer);
456 msaaColorBuffer = nullptr;
457 }
458 if (resolveTexture) {
459 pendingDeletes.append(t: resolveTexture);
460 resolveTexture = nullptr;
461 }
462}
463
464void QRhiWidgetPrivate::resetRenderTargetObjects()
465{
466 if (renderTarget) {
467 renderTarget->deleteLater();
468 renderTarget = nullptr;
469 }
470 if (renderPassDescriptor) {
471 renderPassDescriptor->deleteLater();
472 renderPassDescriptor = nullptr;
473 }
474 if (depthStencilBuffer) {
475 depthStencilBuffer->deleteLater();
476 depthStencilBuffer = nullptr;
477 }
478}
479
480void QRhiWidgetPrivate::releaseResources()
481{
482 resetRenderTargetObjects();
483 resetColorBufferObjects();
484 qDeleteAll(c: pendingDeletes);
485 pendingDeletes.clear();
486}
487
488void QRhiWidgetPrivate::ensureRhi()
489{
490 Q_Q(QRhiWidget);
491 QRhi *currentRhi = QWidgetPrivate::rhi();
492 if (currentRhi && currentRhi->backend() != QBackingStoreRhiSupport::apiToRhiBackend(api: config.api())) {
493 qWarning(msg: "The top-level window is already using another graphics API for composition, "
494 "'%s' is not compatible with this widget",
495 currentRhi->backendName());
496 return;
497 }
498
499 // NB the rhi member may be an invalid object, the pointer can be used, but no deref
500 if (currentRhi && rhi != currentRhi) {
501 if (rhi) {
502 // if previously we created our own but now get a QRhi from the
503 // top-level, then drop what we have and start using the top-level's
504 if (rhi == offscreenRenderer.rhi()) {
505 q->releaseResources(); // notify the user code about the early-release
506 releaseResources();
507 offscreenRenderer.reset();
508 } else {
509 // rhi resources created by us all belong to the old rhi, drop them;
510 // due to nulling out colorTexture this is also what ensures that
511 // initialize() is going to be called again eventually
512 resetRenderTargetObjects();
513 resetColorBufferObjects();
514 }
515 }
516
517 // Normally the widget gets destroyed before the QRhi (which is managed by
518 // the top-level's backingstore). When reparenting between top-levels is
519 // involved, that is not always the case. Therefore we use a per-widget rhi
520 // cleanup callback to get notified when the QRhi is about to be destroyed
521 // while the QRhiWidget is still around.
522 currentRhi->addCleanupCallback(key: q, callback: [q, this](QRhi *regRhi) {
523 if (!QWidgetPrivate::get(w: q)->data.in_destructor && this->rhi == regRhi) {
524 q->releaseResources(); // notify the user code about the early-release
525 releaseResources();
526 // must null out our ref, the QRhi object is going to be invalid
527 this->rhi = nullptr;
528 }
529 });
530 }
531
532 rhi = currentRhi;
533}
534
535void QRhiWidgetPrivate::ensureTexture(bool *changed)
536{
537 Q_Q(QRhiWidget);
538
539 QSize newSize = fixedSize;
540 if (newSize.isEmpty())
541 newSize = q->size() * q->devicePixelRatio();
542
543 const int minTexSize = rhi->resourceLimit(limit: QRhi::TextureSizeMin);
544 const int maxTexSize = rhi->resourceLimit(limit: QRhi::TextureSizeMax);
545 newSize.setWidth(qMin(a: maxTexSize, b: qMax(a: minTexSize, b: newSize.width())));
546 newSize.setHeight(qMin(a: maxTexSize, b: qMax(a: minTexSize, b: newSize.height())));
547
548 if (colorTexture) {
549 if (colorTexture->format() != rhiTextureFormat || colorTexture->sampleCount() != samples) {
550 resetColorBufferObjects();
551 // sample count change needs new depth-stencil, possibly a new
552 // render target; format change needs new renderpassdescriptor;
553 // therefore must drop the rest too
554 resetRenderTargetObjects();
555 }
556 }
557
558 if (msaaColorBuffer) {
559 if (msaaColorBuffer->backingFormat() != rhiTextureFormat || msaaColorBuffer->sampleCount() != samples) {
560 resetColorBufferObjects();
561 // sample count change needs new depth-stencil, possibly a new
562 // render target; format change needs new renderpassdescriptor;
563 // therefore must drop the rest too
564 resetRenderTargetObjects();
565 }
566 }
567
568 if (!colorTexture && samples <= 1) {
569 if (changed)
570 *changed = true;
571 if (!rhi->isTextureFormatSupported(format: rhiTextureFormat)) {
572 qWarning(msg: "QRhiWidget: The requested texture format (%d) is not supported by the "
573 "underlying 3D graphics API implementation", int(rhiTextureFormat));
574 }
575 colorTexture = rhi->newTexture(format: rhiTextureFormat, pixelSize: newSize, sampleCount: samples, flags: QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
576 if (!colorTexture->create()) {
577 qWarning(msg: "Failed to create backing texture for QRhiWidget");
578 delete colorTexture;
579 colorTexture = nullptr;
580 return;
581 }
582 }
583
584 if (samples > 1) {
585 if (!msaaColorBuffer) {
586 if (changed)
587 *changed = true;
588 if (!rhi->isFeatureSupported(feature: QRhi::MultisampleRenderBuffer)) {
589 qWarning(msg: "QRhiWidget: Multisample renderbuffers are reported as unsupported; "
590 "sample count %d will not work as expected", samples);
591 }
592 if (!rhi->isTextureFormatSupported(format: rhiTextureFormat)) {
593 qWarning(msg: "QRhiWidget: The requested texture format (%d) is not supported by the "
594 "underlying 3D graphics API implementation", int(rhiTextureFormat));
595 }
596 msaaColorBuffer = rhi->newRenderBuffer(type: QRhiRenderBuffer::Color, pixelSize: newSize, sampleCount: samples, flags: {}, backingFormatHint: rhiTextureFormat);
597 if (!msaaColorBuffer->create()) {
598 qWarning(msg: "Failed to create multisample color buffer for QRhiWidget");
599 delete msaaColorBuffer;
600 msaaColorBuffer = nullptr;
601 return;
602 }
603 }
604 if (!resolveTexture) {
605 if (changed)
606 *changed = true;
607 resolveTexture = rhi->newTexture(format: rhiTextureFormat, pixelSize: newSize, sampleCount: 1, flags: QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
608 if (!resolveTexture->create()) {
609 qWarning(msg: "Failed to create resolve texture for QRhiWidget");
610 delete resolveTexture;
611 resolveTexture = nullptr;
612 return;
613 }
614 }
615 } else if (resolveTexture) {
616 resolveTexture->deleteLater();
617 resolveTexture = nullptr;
618 }
619
620 if (colorTexture && colorTexture->pixelSize() != newSize) {
621 if (changed)
622 *changed = true;
623 colorTexture->setPixelSize(newSize);
624 if (!colorTexture->create())
625 qWarning(msg: "Failed to rebuild texture for QRhiWidget after resizing");
626 }
627
628 if (msaaColorBuffer && msaaColorBuffer->pixelSize() != newSize) {
629 if (changed)
630 *changed = true;
631 msaaColorBuffer->setPixelSize(newSize);
632 if (!msaaColorBuffer->create())
633 qWarning(msg: "Failed to rebuild multisample color buffer for QRhiWidget after resizing");
634 }
635
636 if (resolveTexture && resolveTexture->pixelSize() != newSize) {
637 if (changed)
638 *changed = true;
639 resolveTexture->setPixelSize(newSize);
640 if (!resolveTexture->create())
641 qWarning(msg: "Failed to rebuild resolve texture for QRhiWidget after resizing");
642 }
643
644 textureInvalid = false;
645}
646
647bool QRhiWidgetPrivate::invokeInitialize(QRhiCommandBuffer *cb)
648{
649 Q_Q(QRhiWidget);
650 if (!colorTexture && !msaaColorBuffer)
651 return false;
652
653 if (autoRenderTarget) {
654 const QSize pixelSize = colorTexture ? colorTexture->pixelSize() : msaaColorBuffer->pixelSize();
655 if (!depthStencilBuffer) {
656 depthStencilBuffer = rhi->newRenderBuffer(type: QRhiRenderBuffer::DepthStencil, pixelSize, sampleCount: samples);
657 if (!depthStencilBuffer->create()) {
658 qWarning(msg: "Failed to create depth-stencil buffer for QRhiWidget");
659 resetRenderTargetObjects();
660 return false;
661 }
662 } else if (depthStencilBuffer->pixelSize() != pixelSize) {
663 depthStencilBuffer->setPixelSize(pixelSize);
664 if (!depthStencilBuffer->create()) {
665 qWarning(msg: "Failed to rebuild depth-stencil buffer for QRhiWidget with new size");
666 return false;
667 }
668 }
669
670 if (!renderTarget) {
671 QRhiColorAttachment color0;
672 if (colorTexture)
673 color0.setTexture(colorTexture);
674 else
675 color0.setRenderBuffer(msaaColorBuffer);
676 if (samples > 1)
677 color0.setResolveTexture(resolveTexture);
678 QRhiTextureRenderTargetDescription rtDesc(color0, depthStencilBuffer);
679 renderTarget = rhi->newTextureRenderTarget(desc: rtDesc);
680 renderPassDescriptor = renderTarget->newCompatibleRenderPassDescriptor();
681 renderTarget->setRenderPassDescriptor(renderPassDescriptor);
682 if (!renderTarget->create()) {
683 qWarning(msg: "Failed to create render target for QRhiWidget");
684 resetRenderTargetObjects();
685 return false;
686 }
687 }
688 } else {
689 resetRenderTargetObjects();
690 }
691
692 q->initialize(cb);
693
694 return true;
695}
696
697/*!
698 \return the currently set graphics API (QRhi backend).
699
700 \sa setApi()
701 */
702QRhiWidget::Api QRhiWidget::api() const
703{
704 Q_D(const QRhiWidget);
705 switch (d->config.api()) {
706 case QPlatformBackingStoreRhiConfig::OpenGL:
707 return Api::OpenGL;
708 case QPlatformBackingStoreRhiConfig::Metal:
709 return Api::Metal;
710 case QPlatformBackingStoreRhiConfig::Vulkan:
711 return Api::Vulkan;
712 case QPlatformBackingStoreRhiConfig::D3D11:
713 return Api::Direct3D11;
714 case QPlatformBackingStoreRhiConfig::D3D12:
715 return Api::Direct3D12;
716 case QPlatformBackingStoreRhiConfig::Null:
717 return Api::Null;
718 }
719 Q_UNREACHABLE_RETURN(Api::Null);
720}
721
722/*!
723 Sets the graphics API and QRhi backend to use to \a api.
724
725 \warning This function must be called early enough, before the widget is
726 added to a widget hierarchy and displayed on screen. For example, aim to
727 call the function for the subclass constructor. If called too late, the
728 function will have no effect.
729
730 The default value depends on the platform: Metal on macOS and iOS, Direct
731 3D 11 on Windows, OpenGL otherwise.
732
733 The \a api can only be set once for the widget and its top-level window,
734 once it is done and takes effect, the window can only use that API and QRhi
735 backend to render. Attempting to set another value, or to add another
736 QRhiWidget with a different \a api will not function as expected.
737
738 \sa setColorBufferFormat(), setDebugLayerEnabled(), api()
739 */
740void QRhiWidget::setApi(Api api)
741{
742 Q_D(QRhiWidget);
743 switch (api) {
744 case Api::OpenGL:
745 d->config.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
746 break;
747 case Api::Metal:
748 d->config.setApi(QPlatformBackingStoreRhiConfig::Metal);
749 break;
750 case Api::Vulkan:
751 d->config.setApi(QPlatformBackingStoreRhiConfig::Vulkan);
752 break;
753 case Api::Direct3D11:
754 d->config.setApi(QPlatformBackingStoreRhiConfig::D3D11);
755 break;
756 case Api::Direct3D12:
757 d->config.setApi(QPlatformBackingStoreRhiConfig::D3D12);
758 break;
759 case Api::Null:
760 d->config.setApi(QPlatformBackingStoreRhiConfig::Null);
761 break;
762 }
763}
764
765/*!
766 \return true if a debug or validation layer will be requested if applicable
767 to the graphics API in use.
768
769 \sa setDebugLayerEnabled()
770 */
771bool QRhiWidget::isDebugLayerEnabled() const
772{
773 Q_D(const QRhiWidget);
774 return d->config.isDebugLayerEnabled();
775}
776
777/*!
778 Requests the debug or validation layer of the underlying graphics API
779 when \a enable is true.
780
781 \warning This function must be called early enough, before the widget is added
782 to a widget hierarchy and displayed on screen. For example, aim to call the
783 function for the subclass constructor. If called too late, the function
784 will have no effect.
785
786 Applicable for Vulkan and Direct 3D.
787
788 By default this is disabled.
789
790 \sa setApi(), isDebugLayerEnabled()
791 */
792void QRhiWidget::setDebugLayerEnabled(bool enable)
793{
794 Q_D(QRhiWidget);
795 d->config.setDebugLayer(enable);
796}
797
798/*!
799 \property QRhiWidget::colorBufferFormat
800
801 This property controls the texture format of the texture (or renderbuffer)
802 used as the color buffer. The default value is TextureFormat::RGBA8.
803 QRhiWidget supports rendering to a subset of the formats supported by \l
804 QRhiTexture. Only formats that are reported as supported from \l
805 QRhi::isTextureFormatSupported() should be specified, rendering will not be
806 functional otherwise.
807
808 \note Setting a new format when the widget is already initialized and has
809 rendered implies that all QRhiGraphicsPipeline objects created by the
810 renderer may become unusable, if the associated QRhiRenderPassDescriptor is
811 now incompatible due to the different texture format. Similarly to changing
812 \l sampleCount dynamically, this means that initialize() or render()
813 implementations must then take care of releasing the existing pipelines and
814 creating new ones.
815 */
816
817QRhiWidget::TextureFormat QRhiWidget::colorBufferFormat() const
818{
819 Q_D(const QRhiWidget);
820 return d->widgetTextureFormat;
821}
822
823void QRhiWidget::setColorBufferFormat(TextureFormat format)
824{
825 Q_D(QRhiWidget);
826 if (d->widgetTextureFormat != format) {
827 d->widgetTextureFormat = format;
828 switch (format) {
829 case TextureFormat::RGBA8:
830 d->rhiTextureFormat = QRhiTexture::RGBA8;
831 break;
832 case TextureFormat::RGBA16F:
833 d->rhiTextureFormat = QRhiTexture::RGBA16F;
834 break;
835 case TextureFormat::RGBA32F:
836 d->rhiTextureFormat = QRhiTexture::RGBA32F;
837 break;
838 case TextureFormat::RGB10A2:
839 d->rhiTextureFormat = QRhiTexture::RGB10A2;
840 break;
841 }
842 emit colorBufferFormatChanged(format);
843 update();
844 }
845}
846
847/*!
848 \property QRhiWidget::sampleCount
849
850 This property controls for sample count for multisample antialiasing.
851 By default the value is \c 1 which means MSAA is disabled.
852
853 Valid values are 1, 4, 8, and sometimes 16 and 32.
854 \l QRhi::supportedSampleCounts() can be used to query the supported sample
855 counts at run time, but typically applications should request 1 (no MSAA),
856 4x (normal MSAA) or 8x (high MSAA).
857
858 \note Setting a new value implies that all QRhiGraphicsPipeline objects
859 created by the renderer must use the same sample count from then on.
860 Existing QRhiGraphicsPipeline objects created with a different sample count
861 must not be used anymore. When the value changes, all color and
862 depth-stencil buffers are destroyed and recreated automatically, and
863 initialize() is invoked again. However, when
864 \l autoRenderTarget is \c false, it will be up to the application to
865 manage this with regards to the depth-stencil buffer or additional color
866 buffers.
867
868 Changing the sample count from the default 1 to a higher value implies that
869 colorTexture() becomes \nullptr and msaaColorBuffer() starts returning a
870 valid object. Switching back to 1 (or 0), implies the opposite: in the next
871 call to initialize() msaaColorBuffer() is going to return \nullptr, whereas
872 colorTexture() becomes once again valid. In addition, resolveTexture()
873 returns a valid (non-multisample) QRhiTexture whenever the sample count is
874 greater than 1 (i.e., MSAA is in use).
875
876 \sa msaaColorBuffer(), resolveTexture()
877 */
878
879int QRhiWidget::sampleCount() const
880{
881 Q_D(const QRhiWidget);
882 return d->samples;
883}
884
885void QRhiWidget::setSampleCount(int samples)
886{
887 Q_D(QRhiWidget);
888 if (d->samples != samples) {
889 d->samples = samples;
890 emit sampleCountChanged(samples);
891 update();
892 }
893}
894
895/*!
896 \property QRhiWidget::fixedColorBufferSize
897
898 The fixed size, in pixels, of the QRhiWidget's associated texture. Relevant
899 when a fixed texture size is desired that does not depend on the widget's
900 size. This size has no effect on the geometry of the widget (its size and
901 placement within the top-level window), which means the texture's content
902 will appear stretched (scaled up) or scaled down onto the widget's area.
903
904 For example, setting a size that is exactly twice the widget's (pixel) size
905 effectively performs 2x supersampling (rendering at twice the resolution
906 and then implicitly scaling down when texturing the quad corresponding to
907 the widget in the window).
908
909 By default the value is a null QSize. A null or empty QSize means that the
910 texture's size follows the QRhiWidget's size. (\c{texture size} = \c{widget
911 size} * \c{device pixel ratio}).
912 */
913
914QSize QRhiWidget::fixedColorBufferSize() const
915{
916 Q_D(const QRhiWidget);
917 return d->fixedSize;
918}
919
920void QRhiWidget::setFixedColorBufferSize(QSize pixelSize)
921{
922 Q_D(QRhiWidget);
923 if (d->fixedSize != pixelSize) {
924 d->fixedSize = pixelSize;
925 emit fixedColorBufferSizeChanged(pixelSize);
926 update();
927 }
928}
929
930/*!
931 \property QRhiWidget::mirrorVertically
932
933 When enabled, flips the image around the X axis when compositing the
934 QRhiWidget's backing texture with the rest of the widget content in the
935 top-level window.
936
937 The default value is \c false.
938 */
939
940bool QRhiWidget::isMirrorVerticallyEnabled() const
941{
942 Q_D(const QRhiWidget);
943 return d->mirrorVertically;
944}
945
946void QRhiWidget::setMirrorVertically(bool enabled)
947{
948 Q_D(QRhiWidget);
949 if (d->mirrorVertically != enabled) {
950 d->mirrorVertically = enabled;
951 emit mirrorVerticallyChanged(enabled);
952 update();
953 }
954}
955
956/*!
957 \property QRhiWidget::autoRenderTarget
958
959 The current setting for automatic depth-stencil buffer and render
960 target maintenance.
961
962 By default the value is \c true.
963 */
964bool QRhiWidget::isAutoRenderTargetEnabled() const
965{
966 Q_D(const QRhiWidget);
967 return d->autoRenderTarget;
968}
969
970/*!
971 Controls if a depth-stencil QRhiRenderBuffer and a QRhiTextureRenderTarget
972 is created and maintained automatically by the widget. The default value is
973 \c true.
974
975 In automatic mode, the size and sample count of the depth-stencil buffer
976 follows the color buffer texture's settings. In non-automatic mode,
977 renderTarget() and depthStencilBuffer() always return \nullptr and it is
978 then up to the application's implementation of initialize() to take care of
979 setting up and managing these objects.
980
981 Call this function with \a enabled set to \c false early on, for example in
982 the derived class' constructor, to disable the automatic mode.
983 */
984void QRhiWidget::setAutoRenderTarget(bool enabled)
985{
986 Q_D(QRhiWidget);
987 if (d->autoRenderTarget != enabled) {
988 d->autoRenderTarget = enabled;
989 update();
990 }
991}
992
993/*!
994 Renders a new frame, reads the contents of the texture back, and returns it
995 as a QImage.
996
997 When an error occurs, a null QImage is returned.
998
999 The returned QImage will have a format of QImage::Format_RGBA8888,
1000 QImage::Format_RGBA16FPx4, QImage::Format_RGBA32FPx4, or
1001 QImage::Format_BGR30, depending on colorBufferFormat().
1002
1003 QRhiWidget does not know the renderer's approach to blending and
1004 composition, and therefore cannot know if the output has alpha
1005 premultiplied in the RGB color values. Thus \c{_Premultiplied} QImage
1006 formats are never used for the returned QImage, even when it would be
1007 appropriate. It is up to the caller to reinterpret the resulting data as it
1008 sees fit.
1009
1010 \note This function can also be called when the QRhiWidget is not added to
1011 a widget hierarchy belonging to an on-screen top-level window. This allows
1012 generating an image from a 3D rendering off-screen.
1013
1014 The function is named grabFramebuffer() for consistency with QOpenGLWidget
1015 and QQuickWidget. It is not the only way to get CPU-side image data out of
1016 the QRhiWidget's content: calling \l QWidget::grab() on a QRhiWidget, or an
1017 ancestor of it, is functional as well (returning a QPixmap). Besides
1018 working directly with QImage, another advantage of grabFramebuffer() is
1019 that it may be slightly more performant, simply because it does not have to
1020 go through the rest of QWidget infrastructure but can right away trigger
1021 rendering a new frame and then do the readback.
1022
1023 \sa setColorBufferFormat()
1024 */
1025QImage QRhiWidget::grabFramebuffer() const
1026{
1027 return const_cast<QRhiWidgetPrivate *>(d_func())->grabFramebuffer();
1028}
1029
1030/*!
1031 Called when the widget is initialized for the first time, when the
1032 associated texture's size, format, or sample count changes, or when the
1033 QRhi and texture change for any reason. The function is expected to
1034 maintain (create if not yet created, adjust and rebuild if the size has
1035 changed) the graphics resources used by the rendering code in render().
1036
1037 To query the QRhi, QRhiTexture, and other related objects, call rhi(),
1038 colorTexture(), depthStencilBuffer(), and renderTarget().
1039
1040 When the widget size changes, the QRhi object, the color buffer texture,
1041 and the depth stencil buffer objects are all the same instances (so the
1042 getters return the same pointers) as before, but the color and
1043 depth/stencil buffers will likely have been rebuilt, meaning the
1044 \l{QRhiTexture::pixelSize()}{size} and the underlying native texture
1045 resource may be different than in the last invocation.
1046
1047 Reimplementations should also be prepared that the QRhi object and the
1048 color buffer texture may change between invocations of this function. One
1049 special case where the objects will be different is when performing a
1050 grabFramebuffer() with a widget that is not yet shown, and then making the
1051 widget visible on-screen within a top-level widget. There the grab will
1052 happen with a dedicated QRhi that is then replaced with the top-level
1053 window's associated QRhi in subsequent initialize() and render()
1054 invocations. Another, more common case is when the widget is reparented so
1055 that it belongs to a new top-level window. In this case the QRhi and all
1056 related resources managed by the QRhiWidget will be different instances
1057 than before in the subsequent call to this function. Is is then important
1058 that all existing QRhi resources previously created by the subclass are
1059 destroyed because they belong to the previous QRhi that should not be used
1060 by the widget anymore.
1061
1062 When \l autoRenderTarget is \c true, which is the default, a
1063 depth-stencil QRhiRenderBuffer and a QRhiTextureRenderTarget associated
1064 with colorTexture() (or msaaColorBuffer()) and the depth-stencil buffer are
1065 created and managed automatically. Reimplementations of initialize() and
1066 render() can query those objects via depthStencilBuffer() and
1067 renderTarget(). When \l autoRenderTarget is set to \c false, these
1068 objects are no longer created and managed automatically. Rather, it will be
1069 up the the initialize() implementation to create buffers and set up the
1070 render target as it sees fit. When manually managing additional color or
1071 depth-stencil attachments for the render target, their size and sample
1072 count must always follow the size and sample count of colorTexture() /
1073 msaaColorBuffer(), otherwise rendering or 3D API validation errors may
1074 occur.
1075
1076 The subclass-created graphics resources are expected to be released in the
1077 destructor implementation of the subclass.
1078
1079 \a cb is the QRhiCommandBuffer for the current frame of the widget. The
1080 function is called with a frame being recorded, but without an active
1081 render pass. The command buffer is provided primarily to allow enqueuing
1082 \l{QRhiCommandBuffer::resourceUpdate()}{resource updates} without deferring
1083 to render().
1084
1085 \sa render()
1086 */
1087void QRhiWidget::initialize(QRhiCommandBuffer *cb)
1088{
1089 Q_UNUSED(cb);
1090}
1091
1092/*!
1093 Called when the widget contents (i.e. the contents of the texture) need
1094 updating.
1095
1096 There is always at least one call to initialize() before this function is
1097 called.
1098
1099 To request updates, call QWidget::update(). Calling update() from within
1100 render() will lead to updating continuously, throttled by vsync.
1101
1102 \a cb is the QRhiCommandBuffer for the current frame of the widget. The
1103 function is called with a frame being recorded, but without an active
1104 render pass.
1105
1106 \sa initialize()
1107 */
1108void QRhiWidget::render(QRhiCommandBuffer *cb)
1109{
1110 Q_UNUSED(cb);
1111}
1112
1113/*!
1114 Called when the need to early-release the graphics resources arises.
1115
1116 This normally does not happen for a QRhiWidget that is added to a top-level
1117 widget's child hierarchy and it then stays there for the rest of its and
1118 the top-level's lifetime. Thus in many cases there is no need to
1119 reimplement this function, e.g. because the application only ever has a
1120 single top-level widget (native window). However, when reparenting of the
1121 widget (or an ancestor of it) is involved, reimplementing this function
1122 will become necessary in robust, well-written QRhiWidget subclasses.
1123
1124 When this function is called, the implementation is expected to destroy all
1125 QRhi resources (QRhiBuffer, QRhiTexture, etc. objects), similarly to how it
1126 is expected to do this in the destructor. Nulling out, using a smart
1127 pointer, or setting a \c{resources-invalid} flag is going to be required as
1128 well, because initialize() will eventually get called afterwards. Note
1129 however that deferring the releasing of resources to the subsequent
1130 initialize() is wrong. If this function is called, the resource must be
1131 dropped before returning. Also note that implementing this function does
1132 not replace the class destructor (or smart pointers): the graphics
1133 resources must still be released in both.
1134
1135 See the \l{Cube RHI Widget Example} for an example of this in action. There
1136 the button that toggles the QRhiWidget between being a child widget (due to
1137 having a parent widget) and being a top-level widget (due to having no
1138 parent widget), will trigger invoking this function since the associated
1139 top-level widget, native window, and QRhi all change during the lifetime of
1140 the QRhiWidget, with the previously used QRhi getting destroyed which
1141 implies an early-release of the associated resources managed by the
1142 still-alive QRhiWidget.
1143
1144 Another case when this function is called is when grabFramebuffer() is used
1145 with a QRhiWidget that is not added to a visible window, i.e. the rendering
1146 is performed offscreen. If later on this QRhiWidget is made visible, or
1147 added to a visible widget hierarchy, the associated QRhi will change from
1148 the temporary one used for offscreen rendering to the window's dedicated
1149 one, thus triggering this function as well.
1150
1151 \sa initialize()
1152 */
1153void QRhiWidget::releaseResources()
1154{
1155}
1156
1157/*!
1158 \return the current QRhi object.
1159
1160 Must only be called from initialize() and render().
1161 */
1162QRhi *QRhiWidget::rhi() const
1163{
1164 Q_D(const QRhiWidget);
1165 return d->rhi;
1166}
1167
1168/*!
1169 \return the texture serving as the color buffer for the widget.
1170
1171 Must only be called from initialize() and render().
1172
1173 Unlike the depth-stencil buffer and the QRhiRenderTarget, this texture is
1174 always available and is managed by the QRhiWidget, independent of the value
1175 of \l autoRenderTarget.
1176
1177 \note When \l sampleCount is larger than 1, and so multisample antialiasing
1178 is enabled, the return value is \nullptr. Instead, query the
1179 \l QRhiRenderBuffer by calling msaaColorBuffer().
1180
1181 \note The backing texture size and sample count can also be queried via the
1182 QRhiRenderTarget returned from renderTarget(). This can be more convenient
1183 and compact than querying from the QRhiTexture or QRhiRenderBuffer, because
1184 it works regardless of multisampling is in use or not.
1185
1186 \sa msaaColorBuffer(), depthStencilBuffer(), renderTarget(), resolveTexture()
1187 */
1188QRhiTexture *QRhiWidget::colorTexture() const
1189{
1190 Q_D(const QRhiWidget);
1191 return d->colorTexture;
1192}
1193
1194/*!
1195 \return the renderbuffer serving as the multisample color buffer for the widget.
1196
1197 Must only be called from initialize() and render().
1198
1199 When \l sampleCount is larger than 1, and so multisample antialising is
1200 enabled, the returned QRhiRenderBuffer has a matching sample count and
1201 serves as the color buffer. Graphics pipelines used to render into this
1202 buffer must be created with the same sample count, and the depth-stencil
1203 buffer's sample count must match as well. The multisample content is
1204 expected to be resolved into the texture returned from resolveTexture().
1205 When \l autoRenderTarget is
1206 \c true, renderTarget() is set up automatically to do this, by setting up
1207 msaaColorBuffer() as the \l{QRhiColorAttachment::renderBuffer()}{renderbuffer} of
1208 color attachment 0 and resolveTexture() as its
1209 \l{QRhiColorAttachment::resolveTexture()}{resolveTexture}.
1210
1211 When MSAA is not in use, the return value is \nullptr. Use colorTexture()
1212 instead then.
1213
1214 Depending on the underlying 3D graphics API, there may be no practical
1215 difference between multisample textures and color renderbuffers with a
1216 sample count larger than 1 (QRhi may just map both to the same native
1217 resource type). Some older APIs however may differentiate between textures
1218 and renderbuffers. In order to support OpenGL ES 3.0, where multisample
1219 renderbuffers are available, but multisample textures are not, QRhiWidget
1220 always performs MSAA by using a multisample QRhiRenderBuffer as the color
1221 attachment (and never a multisample QRhiTexture).
1222
1223 \note The backing texture size and sample count can also be queried via the
1224 QRhiRenderTarget returned from renderTarget(). This can be more convenient
1225 and compact than querying from the QRhiTexture or QRhiRenderBuffer, because
1226 it works regardless of multisampling is in use or not.
1227
1228 \sa colorTexture(), depthStencilBuffer(), renderTarget(), resolveTexture()
1229 */
1230QRhiRenderBuffer *QRhiWidget::msaaColorBuffer() const
1231{
1232 Q_D(const QRhiWidget);
1233 return d->msaaColorBuffer;
1234}
1235
1236/*!
1237 \return the non-multisample texture to which the multisample content is resolved.
1238
1239 The result is \nullptr when multisample antialiasing is not enabled.
1240
1241 Must only be called from initialize() and render().
1242
1243 With MSAA enabled, this is the texture that gets composited with the rest
1244 of the QWidget content on-screen. However, the QRhiWidget's rendering must
1245 target the (multisample) QRhiRenderBuffer returned from
1246 msaaColorBuffer(). When
1247 \l autoRenderTarget is \c true, this is taken care of by the
1248 QRhiRenderTarget returned from renderTarget(). Otherwise, it is up to the
1249 subclass code to correctly configure a render target object with both the
1250 color buffer and resolve textures.
1251
1252 \sa colorTexture()
1253 */
1254QRhiTexture *QRhiWidget::resolveTexture() const
1255{
1256 Q_D(const QRhiWidget);
1257 return d->resolveTexture;
1258}
1259
1260/*!
1261 \return the depth-stencil buffer used by the widget's rendering.
1262
1263 Must only be called from initialize() and render().
1264
1265 Available only when \l autoRenderTarget is \c true. Otherwise the
1266 returned value is \nullptr and it is up the reimplementation of
1267 initialize() to create and manage a depth-stencil buffer and a
1268 QRhiTextureRenderTarget.
1269
1270 \sa colorTexture(), renderTarget()
1271 */
1272QRhiRenderBuffer *QRhiWidget::depthStencilBuffer() const
1273{
1274 Q_D(const QRhiWidget);
1275 return d->depthStencilBuffer;
1276}
1277
1278/*!
1279 \return the render target object that must be used with
1280 \l QRhiCommandBuffer::beginPass() in reimplementations of render().
1281
1282 Must only be called from initialize() and render().
1283
1284 Available only when \l autoRenderTarget is \c true. Otherwise the
1285 returned value is \nullptr and it is up the reimplementation of
1286 initialize() to create and manage a depth-stencil buffer and a
1287 QRhiTextureRenderTarget.
1288
1289 When creating \l{QRhiGraphicsPipeline}{graphics pipelines}, a
1290 QRhiRenderPassDescriptor is needed. This can be queried from the returned
1291 QRhiTextureRenderTarget by calling
1292 \l{QRhiTextureRenderTarget::renderPassDescriptor()}{renderPassDescriptor()}.
1293
1294 \sa colorTexture(), depthStencilBuffer()
1295 */
1296QRhiRenderTarget *QRhiWidget::renderTarget() const
1297{
1298 Q_D(const QRhiWidget);
1299 return d->renderTarget;
1300}
1301
1302/*!
1303 \fn void QRhiWidget::frameSubmitted()
1304
1305 This signal is emitted after the widget's top-level window has finished
1306 composition and has \l{QRhi::endFrame()}{submitted a frame}.
1307*/
1308
1309/*!
1310 \fn void QRhiWidget::renderFailed()
1311
1312 This signal is emitted whenever the widget is supposed to render to its
1313 backing texture (either due to a \l{QWidget::update()}{widget update} or
1314 due to a call to grabFramebuffer()), but there is no \l QRhi for the widget to
1315 use, likely due to issues related to graphics configuration.
1316
1317 This signal may be emitted multiple times when a problem arises. Do not
1318 assume it is emitted only once. Connect with Qt::SingleShotConnection if
1319 the error handling code is to be notified only once.
1320*/
1321
1322QT_END_NAMESPACE
1323

source code of qtbase/src/widgets/kernel/qrhiwidget.cpp