QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCOffscreenRenderer.cc
Go to the documentation of this file.
2
3#include <QtCore/QCoreApplication>
4#include <QtCore/QEventLoop>
5#include <QtQml/QQmlComponent>
6#include <QtQml/QQmlEngine>
7#include <QtQml/QQmlError>
8#include <QtQuick/QQuickItem>
9#include <QtQuick/QQuickRenderControl>
10#include <QtQuick/QQuickRenderTarget>
11#include <QtQuick/QQuickWindow>
12#include <rhi/qrhi.h>
13
14#include "QGCLoggingCategory.h"
15
16QGC_LOGGING_CATEGORY(QGCOffscreenRendererLog, "Video.QGCOffscreenRenderer")
17
18QGCOffscreenRenderer::QGCOffscreenRenderer(QObject* parent) : QObject(parent) {}
19
21{
22 if (_renderControl) {
23 _renderControl->invalidate();
24 }
25 releaseRhiResources();
26}
27
28void QGCOffscreenRenderer::releaseRhiResources()
29{
30 _rpDesc.reset();
31 _renderTarget.reset();
32 _depthStencil.reset();
33 _texture.reset();
34}
35
36bool QGCOffscreenRenderer::ensureRhiTarget()
37{
38 _rhi = _renderControl->rhi();
39 if (!_rhi) {
40 qCWarning(QGCOffscreenRendererLog) << "QQuickRenderControl produced no QRhi";
41 return false;
42 }
43
44 _texture.reset(_rhi->newTexture(QRhiTexture::RGBA8, _pixelSize, 1,
45 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
46 if (!_texture->create()) {
47 qCWarning(QGCOffscreenRendererLog) << "Failed to create offscreen texture";
48 return false;
49 }
50
51 _depthStencil.reset(_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, _pixelSize));
52 if (!_depthStencil->create()) {
53 qCWarning(QGCOffscreenRendererLog) << "Failed to create depth-stencil buffer";
54 return false;
55 }
56
57 QRhiTextureRenderTargetDescription rtDesc(QRhiColorAttachment(_texture.get()), _depthStencil.get());
58 _renderTarget.reset(_rhi->newTextureRenderTarget(rtDesc));
59 _rpDesc.reset(_renderTarget->newCompatibleRenderPassDescriptor());
60 _renderTarget->setRenderPassDescriptor(_rpDesc.get());
61 if (!_renderTarget->create()) {
62 qCWarning(QGCOffscreenRendererLog) << "Failed to create texture render target";
63 return false;
64 }
65
66 _quickWindow->setRenderTarget(QQuickRenderTarget::fromRhiRenderTarget(_renderTarget.get()));
67 return true;
68}
69
70bool QGCOffscreenRenderer::load(const QUrl& qmlSource, const QSize& pixelSize)
71{
72 if (pixelSize.isEmpty()) {
73 qCWarning(QGCOffscreenRendererLog) << "Invalid pixel size" << pixelSize;
74 return false;
75 }
76 _pixelSize = pixelSize;
77
78 _renderControl = std::make_unique<QQuickRenderControl>(this);
79 _quickWindow = std::make_unique<QQuickWindow>(_renderControl.get());
80
81 _qmlEngine = std::make_unique<QQmlEngine>();
82 if (!_qmlEngine->incubationController()) {
83 _qmlEngine->setIncubationController(_quickWindow->incubationController());
84 }
85
86 _qmlComponent = std::make_unique<QQmlComponent>(_qmlEngine.get(), qmlSource);
87 if (_qmlComponent->isError()) {
88 for (const QQmlError& error : _qmlComponent->errors()) {
89 qCWarning(QGCOffscreenRendererLog) << error.toString();
90 }
91 return false;
92 }
93
94 std::unique_ptr<QObject> rootObject(_qmlComponent->create());
95 if (_qmlComponent->isError()) {
96 for (const QQmlError& error : _qmlComponent->errors()) {
97 qCWarning(QGCOffscreenRendererLog) << error.toString();
98 }
99 return false;
100 }
101
102 _rootItem = qobject_cast<QQuickItem*>(rootObject.get());
103 if (!_rootItem) {
104 qCWarning(QGCOffscreenRendererLog) << "QML root is not a QQuickItem";
105 return false;
106 }
107 rootObject.release()->setParent(_quickWindow.get());
108
109 _rootItem->setParentItem(_quickWindow->contentItem());
110 _rootItem->setSize(_pixelSize);
111 _quickWindow->setGeometry(0, 0, _pixelSize.width(), _pixelSize.height());
112
113 if (!_renderControl->initialize()) {
114 qCWarning(QGCOffscreenRendererLog) << "QQuickRenderControl::initialize() failed";
115 return false;
116 }
117
118 if (!ensureRhiTarget()) {
119 return false;
120 }
121
122 _initialized = true;
123 return true;
124}
125
127{
128 if (!_initialized) {
129 qCWarning(QGCOffscreenRendererLog) << "renderToImage() before successful load()";
130 return {};
131 }
132
133 _renderControl->polishItems();
134 _renderControl->beginFrame();
135 _renderControl->sync();
136 _renderControl->render();
137
138 QImage result;
139 QRhiReadbackResult readback;
140 bool done = false;
141 readback.completed = [&]() {
142 const QImage img(reinterpret_cast<const uchar*>(readback.data.constData()), readback.pixelSize.width(),
143 readback.pixelSize.height(), QImage::Format_RGBA8888_Premultiplied);
144 result = img.copy();
145 done = true;
146 };
147
148 QRhiResourceUpdateBatch* batch = _rhi->nextResourceUpdateBatch();
149 QRhiReadbackDescription rb(_texture.get());
150 batch->readBackTexture(rb, &readback);
151
152 QRhiCommandBuffer* cb = _renderControl->commandBuffer();
153 if (cb) {
154 cb->resourceUpdate(batch);
155 } else {
156 batch->release();
157 qCWarning(QGCOffscreenRendererLog) << "No command buffer for readback";
158 }
159
160 _renderControl->endFrame();
161
162 if (!done) {
163 qCWarning(QGCOffscreenRendererLog) << "Texture readback did not complete";
164 return {};
165 }
166
167 if (_rhi->isYUpInFramebuffer()) {
168 result.flip(Qt::Vertical);
169 }
170 return result;
171}
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Renders a QML scene into an offscreen RHI texture and reads it back to a QImage.
bool load(const QUrl &qmlSource, const QSize &pixelSize)