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 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | QSGDefaultRenderContext::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 | */ |
37 | void 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 | |
60 | void 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 | |
89 | void 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 | |
147 | void 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 | |
160 | void 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 | |
172 | void QSGDefaultRenderContext::renderNextFrame(QSGRenderer *renderer) |
173 | { |
174 | renderer->renderScene(); |
175 | } |
176 | |
177 | void QSGDefaultRenderContext::endNextFrame(QSGRenderer *renderer) |
178 | { |
179 | Q_UNUSED(renderer); |
180 | m_currentFrameCommandBuffer = nullptr; |
181 | m_currentFrameRenderPass = nullptr; |
182 | } |
183 | |
184 | QSGTexture *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 | |
208 | QSGRenderer *QSGDefaultRenderContext::createRenderer(QSGRendererInterface::RenderMode renderMode) |
209 | { |
210 | return new QSGBatchRenderer::Renderer(this, renderMode); |
211 | } |
212 | |
213 | QSGTexture *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 | |
223 | QString 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 | |
244 | void QSGDefaultRenderContext::initializeRhiShader(QSGMaterialShader *shader, QShader::Variant shaderVariant) |
245 | { |
246 | QSGMaterialShaderPrivate::get(s: shader)->prepare(vertexShaderVariant: shaderVariant); |
247 | } |
248 | |
249 | void 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 | |
257 | QSGDistanceFieldGlyphCache *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 | |
269 | QRhiResourceUpdateBatch *QSGDefaultRenderContext::maybeGlyphCacheResourceUpdates() |
270 | { |
271 | return m_glyphCacheResourceUpdates; |
272 | } |
273 | |
274 | QRhiResourceUpdateBatch *QSGDefaultRenderContext::glyphCacheResourceUpdates() |
275 | { |
276 | if (!m_glyphCacheResourceUpdates) |
277 | m_glyphCacheResourceUpdates = m_rhi->nextResourceUpdateBatch(); |
278 | |
279 | return m_glyphCacheResourceUpdates; |
280 | } |
281 | |
282 | void QSGDefaultRenderContext::deferredReleaseGlyphCacheTexture(QRhiTexture *texture) |
283 | { |
284 | if (texture) |
285 | m_pendingGlyphCacheTextures.insert(value: texture); |
286 | } |
287 | |
288 | void 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 | |
301 | QT_END_NAMESPACE |
302 | |
303 | #include "moc_qsgdefaultrendercontext_p.cpp" |
304 | |