1// Copyright (C) 2016 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 "qsgdefaultrendercontext_p.h"
5
6#include <QtGui/QGuiApplication>
7
8#include <QtQuick/private/qsgbatchrenderer_p.h>
9#include <QtQuick/private/qsgrenderer_p.h>
10#include <QtQuick/private/qsgrhiatlastexture_p.h>
11#include <QtQuick/private/qsgrhidistancefieldglyphcache_p.h>
12#include <QtQuick/private/qsgmaterialshader_p.h>
13
14#include <QtQuick/private/qsgcompressedtexture_p.h>
15
16#include <QtQuick/qsgrendererinterface.h>
17#include <QtQuick/qquickgraphicsconfiguration.h>
18
19QT_BEGIN_NAMESPACE
20
21QSGDefaultRenderContext::QSGDefaultRenderContext(QSGContext *context)
22 : QSGRenderContext(context)
23 , m_rhi(nullptr)
24 , m_maxTextureSize(0)
25 , m_rhiAtlasManager(nullptr)
26 , m_currentFrameCommandBuffer(nullptr)
27 , m_currentFrameRenderPass(nullptr)
28 , m_useDepthBufferFor2D(true)
29 , m_glyphCacheResourceUpdates(nullptr)
30{
31}
32
33/*!
34 Initializes the scene graph render context with the GL context \a context. This also
35 emits the ready() signal so that the QML graph can start building scene graph nodes.
36 */
37void QSGDefaultRenderContext::initialize(const QSGRenderContext::InitParams *params)
38{
39 if (!m_sg)
40 return;
41
42 const InitParams *initParams = static_cast<const InitParams *>(params);
43 if (initParams->sType != INIT_PARAMS_MAGIC)
44 qFatal(msg: "QSGDefaultRenderContext: Invalid parameters passed to initialize()");
45
46 m_initParams = *initParams;
47
48 m_rhi = m_initParams.rhi;
49 m_maxTextureSize = m_rhi->resourceLimit(limit: QRhi::TextureSizeMax);
50 if (!m_rhiAtlasManager)
51 m_rhiAtlasManager = new QSGRhiAtlasTexture::Manager(this, m_initParams.initialSurfacePixelSize, m_initParams.maybeSurface);
52
53 m_glyphCacheResourceUpdates = nullptr;
54
55 m_sg->renderContextInitialized(renderContext: this);
56
57 emit initialized();
58}
59
60void QSGDefaultRenderContext::invalidateGlyphCaches()
61{
62 {
63 auto it = m_glyphCaches.begin();
64 while (it != m_glyphCaches.end()) {
65 if (!(*it)->isActive()) {
66 delete *it;
67 it = m_glyphCaches.erase(it);
68 } else {
69 ++it;
70 }
71 }
72 }
73
74 {
75 auto it = m_fontEnginesToClean.begin();
76 while (it != m_fontEnginesToClean.end()) {
77 if (it.value() == 0) {
78 it.key()->clearGlyphCache(key: this);
79 if (!it.key()->ref.deref())
80 delete it.key();
81 it = m_fontEnginesToClean.erase(it);
82 } else {
83 ++it;
84 }
85 }
86 }
87}
88
89void QSGDefaultRenderContext::invalidate()
90{
91 if (!m_rhi)
92 return;
93
94 qDeleteAll(c: m_texturesToDelete);
95 m_texturesToDelete.clear();
96
97 qDeleteAll(c: m_textures);
98 m_textures.clear();
99
100 /* The cleanup of the atlas textures is a bit intriguing.
101 As part of the cleanup in the threaded render loop, we
102 do:
103 1. call this function
104 2. call QCoreApp::sendPostedEvents() to immediately process
105 any pending deferred deletes.
106 3. delete the GL context.
107
108 As textures need the atlas manager while cleaning up, the
109 manager needs to be cleaned up after the textures, so
110 we post a deleteLater here at the very bottom so it gets
111 deferred deleted last.
112
113 Another alternative would be to use a QPointer in
114 QSGOpenGLAtlasTexture::Texture, but this seemed simpler.
115 */
116 if (m_rhiAtlasManager) {
117 m_rhiAtlasManager->invalidate();
118 m_rhiAtlasManager->deleteLater();
119 m_rhiAtlasManager = nullptr;
120 }
121
122 // The following piece of code will read/write to the font engine's caches,
123 // potentially from different threads. However, this is safe because this
124 // code is only called from QQuickWindow's shutdown which is called
125 // only when the GUI is blocked, and multiple threads will call it in
126 // sequence. (see qsgdefaultglyphnode_p.cpp's init())
127 for (auto it = m_fontEnginesToClean.constBegin(); it != m_fontEnginesToClean.constEnd(); ++it) {
128 it.key()->clearGlyphCache(key: this);
129 if (!it.key()->ref.deref())
130 delete it.key();
131 }
132 m_fontEnginesToClean.clear();
133
134 qDeleteAll(c: m_glyphCaches);
135 m_glyphCaches.clear();
136
137 resetGlyphCacheResources();
138
139 m_rhi = nullptr;
140
141 if (m_sg)
142 m_sg->renderContextInvalidated(renderContext: this);
143
144 emit invalidated();
145}
146
147void QSGDefaultRenderContext::prepareSync(qreal devicePixelRatio,
148 QRhiCommandBuffer *cb,
149 const QQuickGraphicsConfiguration &config)
150{
151 m_currentDevicePixelRatio = devicePixelRatio;
152 m_useDepthBufferFor2D = config.isDepthBufferEnabledFor2D();
153
154 // we store the command buffer already here, in case there is something in
155 // an updatePaintNode() implementation that leads to needing it (for
156 // example, an updateTexture() call on a QSGRhiLayer)
157 m_currentFrameCommandBuffer = cb;
158}
159
160void QSGDefaultRenderContext::beginNextFrame(QSGRenderer *renderer, const QSGRenderTarget &renderTarget,
161 RenderPassCallback mainPassRecordingStart,
162 RenderPassCallback mainPassRecordingEnd,
163 void *callbackUserData)
164{
165 renderer->setRenderTarget(renderTarget);
166 renderer->setRenderPassRecordingCallbacks(start: mainPassRecordingStart, end: mainPassRecordingEnd, userData: callbackUserData);
167
168 m_currentFrameCommandBuffer = renderTarget.cb; // usually the same as what was passed to prepareSync() but cannot count on that having been called
169 m_currentFrameRenderPass = renderTarget.rpDesc;
170}
171
172void QSGDefaultRenderContext::renderNextFrame(QSGRenderer *renderer)
173{
174 renderer->renderScene();
175}
176
177void QSGDefaultRenderContext::endNextFrame(QSGRenderer *renderer)
178{
179 Q_UNUSED(renderer);
180 m_currentFrameCommandBuffer = nullptr;
181 m_currentFrameRenderPass = nullptr;
182}
183
184QSGTexture *QSGDefaultRenderContext::createTexture(const QImage &image, uint flags) const
185{
186 bool atlas = flags & CreateTexture_Atlas;
187 bool mipmap = flags & CreateTexture_Mipmap;
188 bool alpha = flags & CreateTexture_Alpha;
189
190 // The atlas implementation is only supported from the render thread and
191 // does not support mipmaps.
192 if (m_rhi) {
193 if (!mipmap && atlas && QThread::currentThread() == m_rhi->thread()) {
194 QSGTexture *t = m_rhiAtlasManager->create(image, hasAlphaChannel: alpha);
195 if (t)
196 return t;
197 }
198 }
199
200 QSGPlainTexture *texture = new QSGPlainTexture;
201 texture->setImage(image);
202 if (texture->hasAlphaChannel() && !alpha)
203 texture->setHasAlphaChannel(false);
204
205 return texture;
206}
207
208QSGRenderer *QSGDefaultRenderContext::createRenderer(QSGRendererInterface::RenderMode renderMode)
209{
210 return new QSGBatchRenderer::Renderer(this, renderMode);
211}
212
213QSGTexture *QSGDefaultRenderContext::compressedTextureForFactory(const QSGCompressedTextureFactory *factory) const
214{
215 // This is only used for atlasing compressed textures. Returning null implies no atlas.
216
217 if (m_rhi && QThread::currentThread() == m_rhi->thread())
218 return m_rhiAtlasManager->create(factory);
219
220 return nullptr;
221}
222
223QString QSGDefaultRenderContext::fontKey(const QRawFont &font, int renderTypeQuality)
224{
225 QFontEngine *fe = QRawFontPrivate::get(font)->fontEngine;
226 if (!fe->faceId().filename.isEmpty()) {
227 QByteArray keyName =
228 fe->faceId().filename + ' ' + QByteArray::number(fe->faceId().index)
229 + (font.style() != QFont::StyleNormal ? QByteArray(" I") : QByteArray())
230 + (font.weight() != QFont::Normal ? ' ' + QByteArray::number(font.weight()) : QByteArray())
231 + ' ' + QByteArray::number(renderTypeQuality)
232 + QByteArray(" DF");
233 return QString::fromUtf8(ba: keyName);
234 } else {
235 return QString::fromLatin1(ba: "%1_%2_%3_%4_%5")
236 .arg(a: font.familyName())
237 .arg(a: font.styleName())
238 .arg(a: font.weight())
239 .arg(a: font.style())
240 .arg(a: renderTypeQuality);
241 }
242}
243
244void QSGDefaultRenderContext::initializeRhiShader(QSGMaterialShader *shader, QShader::Variant shaderVariant)
245{
246 QSGMaterialShaderPrivate::get(s: shader)->prepare(vertexShaderVariant: shaderVariant);
247}
248
249void QSGDefaultRenderContext::preprocess()
250{
251 for (auto it = m_glyphCaches.begin(); it != m_glyphCaches.end(); ++it) {
252 it.value()->processPendingGlyphs();
253 it.value()->update();
254 }
255}
256
257QSGDistanceFieldGlyphCache *QSGDefaultRenderContext::distanceFieldGlyphCache(const QRawFont &font, int renderTypeQuality)
258{
259 QString key = fontKey(font, renderTypeQuality);
260 QSGDistanceFieldGlyphCache *cache = m_glyphCaches.value(key, defaultValue: 0);
261 if (!cache) {
262 cache = new QSGRhiDistanceFieldGlyphCache(this, font, renderTypeQuality);
263 m_glyphCaches.insert(key, value: cache);
264 }
265
266 return cache;
267}
268
269QRhiResourceUpdateBatch *QSGDefaultRenderContext::maybeGlyphCacheResourceUpdates()
270{
271 return m_glyphCacheResourceUpdates;
272}
273
274QRhiResourceUpdateBatch *QSGDefaultRenderContext::glyphCacheResourceUpdates()
275{
276 if (!m_glyphCacheResourceUpdates)
277 m_glyphCacheResourceUpdates = m_rhi->nextResourceUpdateBatch();
278
279 return m_glyphCacheResourceUpdates;
280}
281
282void QSGDefaultRenderContext::deferredReleaseGlyphCacheTexture(QRhiTexture *texture)
283{
284 if (texture)
285 m_pendingGlyphCacheTextures.insert(value: texture);
286}
287
288void QSGDefaultRenderContext::resetGlyphCacheResources()
289{
290 if (m_glyphCacheResourceUpdates) {
291 m_glyphCacheResourceUpdates->release();
292 m_glyphCacheResourceUpdates = nullptr;
293 }
294
295 for (QRhiTexture *t : std::as_const(t&: m_pendingGlyphCacheTextures))
296 t->deleteLater(); // the QRhiTexture object stays valid for the current frame
297
298 m_pendingGlyphCacheTextures.clear();
299}
300
301QT_END_NAMESPACE
302
303#include "moc_qsgdefaultrendercontext_p.cpp"
304

source code of qtdeclarative/src/quick/scenegraph/qsgdefaultrendercontext.cpp