1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQuick module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qsgdefaultrendercontext_p.h" |
41 | |
42 | #include <QtGui/QGuiApplication> |
43 | #include <QtGui/QOpenGLFramebufferObject> |
44 | |
45 | #include <QtQuick/private/qsgbatchrenderer_p.h> |
46 | #include <QtQuick/private/qsgrenderer_p.h> |
47 | #include <QtQuick/private/qsgrhiatlastexture_p.h> |
48 | #include <QtQuick/private/qsgrhidistancefieldglyphcache_p.h> |
49 | #include <QtQuick/private/qsgmaterialrhishader_p.h> |
50 | |
51 | #include <QtQuick/private/qsgopenglatlastexture_p.h> |
52 | #include <QtQuick/private/qsgcompressedtexture_p.h> |
53 | #include <QtQuick/private/qsgopengldistancefieldglyphcache_p.h> |
54 | |
55 | QT_BEGIN_NAMESPACE |
56 | |
57 | #define QSG_RENDERCONTEXT_PROPERTY "_q_sgrendercontext" |
58 | |
59 | QSGDefaultRenderContext::QSGDefaultRenderContext(QSGContext *context) |
60 | : QSGRenderContext(context) |
61 | , m_rhi(nullptr) |
62 | , m_gl(nullptr) |
63 | , m_depthStencilManager(nullptr) |
64 | , m_maxTextureSize(0) |
65 | , m_brokenIBOs(false) |
66 | , m_serializedRender(false) |
67 | , m_attachToGLContext(true) |
68 | , m_glAtlasManager(nullptr) |
69 | , m_rhiAtlasManager(nullptr) |
70 | , m_currentFrameCommandBuffer(nullptr) |
71 | , m_currentFrameRenderPass(nullptr) |
72 | { |
73 | } |
74 | |
75 | /*! |
76 | Initializes the scene graph render context with the GL context \a context. This also |
77 | emits the ready() signal so that the QML graph can start building scene graph nodes. |
78 | */ |
79 | void QSGDefaultRenderContext::initialize(const QSGRenderContext::InitParams *params) |
80 | { |
81 | if (!m_sg) |
82 | return; |
83 | |
84 | const InitParams *initParams = static_cast<const InitParams *>(params); |
85 | if (initParams->sType != INIT_PARAMS_MAGIC) |
86 | qFatal(msg: "QSGDefaultRenderContext: Invalid parameters passed to initialize()" ); |
87 | |
88 | m_initParams = *initParams; |
89 | |
90 | m_rhi = m_initParams.rhi; |
91 | if (m_rhi) { |
92 | m_maxTextureSize = m_rhi->resourceLimit(limit: QRhi::TextureSizeMax); |
93 | if (!m_rhiAtlasManager) |
94 | m_rhiAtlasManager = new QSGRhiAtlasTexture::Manager(this, m_initParams.initialSurfacePixelSize, m_initParams.maybeSurface); |
95 | } else { |
96 | QOpenGLFunctions *funcs = m_rhi ? nullptr : QOpenGLContext::currentContext()->functions(); |
97 | funcs->glGetIntegerv(GL_MAX_TEXTURE_SIZE, params: &m_maxTextureSize); |
98 | |
99 | // Sanity check the surface format, in case it was overridden by the application |
100 | QSurfaceFormat requested = m_sg->defaultSurfaceFormat(); |
101 | QSurfaceFormat actual = m_initParams.openGLContext->format(); |
102 | if (requested.depthBufferSize() > 0 && actual.depthBufferSize() <= 0) |
103 | qWarning(msg: "QSGContext::initialize: depth buffer support missing, expect rendering errors" ); |
104 | if (requested.stencilBufferSize() > 0 && actual.stencilBufferSize() <= 0) |
105 | qWarning(msg: "QSGContext::initialize: stencil buffer support missing, expect rendering errors" ); |
106 | |
107 | #ifdef Q_OS_LINUX |
108 | const char *vendor = (const char *) funcs->glGetString(GL_VENDOR); |
109 | if (vendor && strstr(haystack: vendor, needle: "nouveau" )) |
110 | m_brokenIBOs = true; |
111 | const char *renderer = (const char *) funcs->glGetString(GL_RENDERER); |
112 | if (renderer && strstr(haystack: renderer, needle: "llvmpipe" )) |
113 | m_serializedRender = true; |
114 | if (vendor && renderer && strstr(haystack: vendor, needle: "Hisilicon Technologies" ) && strstr(haystack: renderer, needle: "Immersion.16" )) |
115 | m_brokenIBOs = true; |
116 | #endif |
117 | |
118 | Q_ASSERT_X(!m_gl, "QSGRenderContext::initialize" , "already initialized!" ); |
119 | m_gl = m_initParams.openGLContext; |
120 | if (m_attachToGLContext) { |
121 | Q_ASSERT(!m_gl->property(QSG_RENDERCONTEXT_PROPERTY).isValid()); |
122 | m_gl->setProperty(QSG_RENDERCONTEXT_PROPERTY, value: QVariant::fromValue(value: this)); |
123 | } |
124 | |
125 | if (!m_glAtlasManager) |
126 | m_glAtlasManager = new QSGOpenGLAtlasTexture::Manager(m_initParams.initialSurfacePixelSize); |
127 | } |
128 | |
129 | m_sg->renderContextInitialized(renderContext: this); |
130 | |
131 | emit initialized(); |
132 | } |
133 | |
134 | void QSGDefaultRenderContext::invalidate() |
135 | { |
136 | if (!m_gl && !m_rhi) |
137 | return; |
138 | |
139 | qDeleteAll(c: m_texturesToDelete); |
140 | m_texturesToDelete.clear(); |
141 | |
142 | qDeleteAll(c: m_textures); |
143 | m_textures.clear(); |
144 | |
145 | /* The cleanup of the atlas textures is a bit intriguing. |
146 | As part of the cleanup in the threaded render loop, we |
147 | do: |
148 | 1. call this function |
149 | 2. call QCoreApp::sendPostedEvents() to immediately process |
150 | any pending deferred deletes. |
151 | 3. delete the GL context. |
152 | |
153 | As textures need the atlas manager while cleaning up, the |
154 | manager needs to be cleaned up after the textures, so |
155 | we post a deleteLater here at the very bottom so it gets |
156 | deferred deleted last. |
157 | |
158 | Another alternative would be to use a QPointer in |
159 | QSGOpenGLAtlasTexture::Texture, but this seemed simpler. |
160 | */ |
161 | if (m_glAtlasManager) { |
162 | m_glAtlasManager->invalidate(); |
163 | m_glAtlasManager->deleteLater(); |
164 | m_glAtlasManager = nullptr; |
165 | } |
166 | if (m_rhiAtlasManager) { |
167 | m_rhiAtlasManager->invalidate(); |
168 | m_rhiAtlasManager->deleteLater(); |
169 | m_rhiAtlasManager = nullptr; |
170 | } |
171 | |
172 | // The following piece of code will read/write to the font engine's caches, |
173 | // potentially from different threads. However, this is safe because this |
174 | // code is only called from QQuickWindow's shutdown which is called |
175 | // only when the GUI is blocked, and multiple threads will call it in |
176 | // sequence. (see qsgdefaultglyphnode_p.cpp's init()) |
177 | for (QSet<QFontEngine *>::const_iterator it = m_fontEnginesToClean.constBegin(), |
178 | end = m_fontEnginesToClean.constEnd(); it != end; ++it) { |
179 | (*it)->clearGlyphCache(key: m_gl ? (void *) m_gl : (void *) m_rhi); |
180 | if (!(*it)->ref.deref()) |
181 | delete *it; |
182 | } |
183 | m_fontEnginesToClean.clear(); |
184 | |
185 | delete m_depthStencilManager; |
186 | m_depthStencilManager = nullptr; |
187 | |
188 | qDeleteAll(c: m_glyphCaches); |
189 | m_glyphCaches.clear(); |
190 | |
191 | if (m_gl && m_gl->property(QSG_RENDERCONTEXT_PROPERTY) == QVariant::fromValue(value: this)) |
192 | m_gl->setProperty(QSG_RENDERCONTEXT_PROPERTY, value: QVariant()); |
193 | |
194 | m_gl = nullptr; |
195 | m_rhi = nullptr; |
196 | |
197 | if (m_sg) |
198 | m_sg->renderContextInvalidated(renderContext: this); |
199 | |
200 | emit invalidated(); |
201 | } |
202 | |
203 | void QSGDefaultRenderContext::prepareSync(qreal devicePixelRatio, QRhiCommandBuffer *cb) |
204 | { |
205 | m_currentDevicePixelRatio = devicePixelRatio; |
206 | |
207 | // we store the command buffer already here, in case there is something in |
208 | // an updatePaintNode() implementation that leads to needing it (for |
209 | // example, an updateTexture() call on a QSGRhiLayer) |
210 | m_currentFrameCommandBuffer = cb; |
211 | } |
212 | |
213 | static QBasicMutex qsg_framerender_mutex; |
214 | |
215 | void QSGDefaultRenderContext::beginNextFrame(QSGRenderer *renderer, |
216 | RenderPassCallback mainPassRecordingStart, |
217 | RenderPassCallback mainPassRecordingEnd, |
218 | void *callbackUserData) |
219 | { |
220 | renderer->setRenderPassRecordingCallbacks(start: mainPassRecordingStart, end: mainPassRecordingEnd, userData: callbackUserData); |
221 | } |
222 | |
223 | void QSGDefaultRenderContext::renderNextFrame(QSGRenderer *renderer, uint fboId) |
224 | { |
225 | if (m_serializedRender) |
226 | qsg_framerender_mutex.lock(); |
227 | |
228 | renderer->renderScene(fboId); |
229 | |
230 | if (m_serializedRender) |
231 | qsg_framerender_mutex.unlock(); |
232 | } |
233 | |
234 | void QSGDefaultRenderContext::endNextFrame(QSGRenderer *renderer) |
235 | { |
236 | Q_UNUSED(renderer); |
237 | } |
238 | |
239 | void QSGDefaultRenderContext::beginNextRhiFrame(QSGRenderer *renderer, QRhiRenderTarget *rt, QRhiRenderPassDescriptor *rp, |
240 | QRhiCommandBuffer *cb, |
241 | RenderPassCallback mainPassRecordingStart, |
242 | RenderPassCallback mainPassRecordingEnd, |
243 | void *callbackUserData) |
244 | { |
245 | renderer->setRenderTarget(rt); |
246 | renderer->setRenderPassDescriptor(rp); |
247 | renderer->setCommandBuffer(cb); |
248 | renderer->setRenderPassRecordingCallbacks(start: mainPassRecordingStart, end: mainPassRecordingEnd, userData: callbackUserData); |
249 | |
250 | m_currentFrameCommandBuffer = cb; // usually the same as what was passed to prepareSync() but cannot count on that having been called |
251 | m_currentFrameRenderPass = rp; |
252 | } |
253 | |
254 | void QSGDefaultRenderContext::renderNextRhiFrame(QSGRenderer *renderer) |
255 | { |
256 | renderer->renderScene(); |
257 | } |
258 | |
259 | void QSGDefaultRenderContext::endNextRhiFrame(QSGRenderer *renderer) |
260 | { |
261 | Q_UNUSED(renderer); |
262 | m_currentFrameCommandBuffer = nullptr; |
263 | m_currentFrameRenderPass = nullptr; |
264 | } |
265 | |
266 | /*! |
267 | Returns a shared pointer to a depth stencil buffer that can be used with \a fbo. |
268 | */ |
269 | QSharedPointer<QSGDepthStencilBuffer> QSGDefaultRenderContext::depthStencilBufferForFbo(QOpenGLFramebufferObject *fbo) |
270 | { |
271 | if (!m_gl) |
272 | return QSharedPointer<QSGDepthStencilBuffer>(); |
273 | QSGDepthStencilBufferManager *manager = depthStencilBufferManager(); |
274 | QSGDepthStencilBuffer::Format format; |
275 | format.size = fbo->size(); |
276 | format.samples = fbo->format().samples(); |
277 | format.attachments = QSGDepthStencilBuffer::DepthAttachment | QSGDepthStencilBuffer::StencilAttachment; |
278 | QSharedPointer<QSGDepthStencilBuffer> buffer = manager->bufferForFormat(fmt: format); |
279 | if (buffer.isNull()) { |
280 | buffer = QSharedPointer<QSGDepthStencilBuffer>(new QSGDefaultDepthStencilBuffer(m_gl, format)); |
281 | manager->insertBuffer(buffer); |
282 | } |
283 | return buffer; |
284 | } |
285 | |
286 | /*! |
287 | Returns a pointer to the context's depth/stencil buffer manager. This is useful for custom |
288 | implementations of \l depthStencilBufferForFbo(). |
289 | */ |
290 | QSGDepthStencilBufferManager *QSGDefaultRenderContext::depthStencilBufferManager() |
291 | { |
292 | if (!m_gl) |
293 | return nullptr; |
294 | if (!m_depthStencilManager) |
295 | m_depthStencilManager = new QSGDepthStencilBufferManager(m_gl); |
296 | return m_depthStencilManager; |
297 | } |
298 | |
299 | QSGTexture *QSGDefaultRenderContext::createTexture(const QImage &image, uint flags) const |
300 | { |
301 | bool atlas = flags & CreateTexture_Atlas; |
302 | bool mipmap = flags & CreateTexture_Mipmap; |
303 | bool alpha = flags & CreateTexture_Alpha; |
304 | |
305 | // The atlas implementation is only supported from the render thread and |
306 | // does not support mipmaps. |
307 | if (m_rhi) { |
308 | if (!mipmap && atlas && QThread::currentThread() == m_rhi->thread()) { |
309 | QSGTexture *t = m_rhiAtlasManager->create(image, hasAlphaChannel: alpha); |
310 | if (t) |
311 | return t; |
312 | } |
313 | } else { |
314 | if (!mipmap && atlas && openglContext() && QThread::currentThread() == openglContext()->thread()) { |
315 | QSGTexture *t = m_glAtlasManager->create(image, hasAlphaChannel: alpha); |
316 | if (t) |
317 | return t; |
318 | } |
319 | } |
320 | |
321 | QSGPlainTexture *texture = new QSGPlainTexture; |
322 | texture->setImage(image); |
323 | if (texture->hasAlphaChannel() && !alpha) |
324 | texture->setHasAlphaChannel(false); |
325 | |
326 | return texture; |
327 | } |
328 | |
329 | QSGRenderer *QSGDefaultRenderContext::createRenderer() |
330 | { |
331 | return new QSGBatchRenderer::Renderer(this); |
332 | } |
333 | |
334 | QSGTexture *QSGDefaultRenderContext::compressedTextureForFactory(const QSGCompressedTextureFactory *factory) const |
335 | { |
336 | // This is only used for atlasing compressed textures. Returning null implies no atlas. |
337 | |
338 | if (m_rhi) { |
339 | // ### |
340 | } else if (openglContext() && QThread::currentThread() == openglContext()->thread()) { |
341 | // The atlas implementation is only supported from the render thread |
342 | return m_glAtlasManager->create(factory); |
343 | } |
344 | |
345 | return nullptr; |
346 | } |
347 | |
348 | /*! |
349 | Compile \a shader, optionally using \a vertexCode and \a fragmentCode as |
350 | replacement for the source code supplied by \a shader. |
351 | |
352 | If \a vertexCode or \a fragmentCode is supplied, the caller is responsible |
353 | for setting up attribute bindings. |
354 | |
355 | \a material is supplied in case the implementation needs to take the |
356 | material flags into account. |
357 | */ |
358 | void QSGDefaultRenderContext::compileShader(QSGMaterialShader *shader, QSGMaterial *material, const char *vertexCode, const char *fragmentCode) |
359 | { |
360 | Q_UNUSED(material); |
361 | if (vertexCode || fragmentCode) { |
362 | Q_ASSERT_X((material->flags() & QSGMaterial::CustomCompileStep) == 0, |
363 | "QSGRenderContext::compile()" , |
364 | "materials with custom compile step cannot have modified vertex or fragment code" ); |
365 | QOpenGLShaderProgram *p = shader->program(); |
366 | p->addCacheableShaderFromSourceCode(type: QOpenGLShader::Vertex, source: vertexCode ? vertexCode : shader->vertexShader()); |
367 | p->addCacheableShaderFromSourceCode(type: QOpenGLShader::Fragment, source: fragmentCode ? fragmentCode : shader->fragmentShader()); |
368 | p->link(); |
369 | if (!p->isLinked()) |
370 | qWarning() << "shader compilation failed:" << Qt::endl << p->log(); |
371 | } else { |
372 | shader->compile(); |
373 | } |
374 | } |
375 | |
376 | QString QSGDefaultRenderContext::fontKey(const QRawFont &font) |
377 | { |
378 | QFontEngine *fe = QRawFontPrivate::get(font)->fontEngine; |
379 | if (!fe->faceId().filename.isEmpty()) { |
380 | QByteArray keyName = fe->faceId().filename + ' ' + QByteArray::number(fe->faceId().index); |
381 | if (font.style() != QFont::StyleNormal) |
382 | keyName += QByteArray(" I" ); |
383 | if (font.weight() != QFont::Normal) |
384 | keyName += ' ' + QByteArray::number(font.weight()); |
385 | keyName += QByteArray(" DF" ); |
386 | return QString::fromUtf8(str: keyName); |
387 | } else { |
388 | return QString::fromLatin1(str: "%1_%2_%3_%4" ) |
389 | .arg(a: font.familyName()) |
390 | .arg(a: font.styleName()) |
391 | .arg(a: font.weight()) |
392 | .arg(a: font.style()); |
393 | } |
394 | } |
395 | |
396 | void QSGDefaultRenderContext::initializeShader(QSGMaterialShader *shader) |
397 | { |
398 | shader->program()->bind(); |
399 | shader->initialize(); |
400 | } |
401 | |
402 | void QSGDefaultRenderContext::initializeRhiShader(QSGMaterialRhiShader *shader, QShader::Variant shaderVariant) |
403 | { |
404 | QSGMaterialRhiShaderPrivate::get(s: shader)->prepare(vertexShaderVariant: shaderVariant); |
405 | } |
406 | |
407 | void QSGDefaultRenderContext::setAttachToGraphicsContext(bool attach) |
408 | { |
409 | Q_ASSERT(!isValid()); |
410 | m_attachToGLContext = attach; |
411 | } |
412 | |
413 | QSGDefaultRenderContext *QSGDefaultRenderContext::from(QOpenGLContext *context) |
414 | { |
415 | return qobject_cast<QSGDefaultRenderContext *>(object: context->property(QSG_RENDERCONTEXT_PROPERTY).value<QObject *>()); |
416 | } |
417 | |
418 | bool QSGDefaultRenderContext::separateIndexBuffer() const |
419 | { |
420 | if (m_rhi) |
421 | return true; |
422 | |
423 | // WebGL: A given WebGLBuffer object may only be bound to one of |
424 | // the ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target in its |
425 | // lifetime. An attempt to bind a buffer object to the other |
426 | // target will generate an INVALID_OPERATION error, and the |
427 | // current binding will remain untouched. |
428 | static const bool isWebGL = (qGuiApp->platformName().compare(other: QLatin1String("webgl" )) == 0 |
429 | || qGuiApp->platformName().compare(other: QLatin1String("wasm" )) == 0); |
430 | return isWebGL; |
431 | } |
432 | |
433 | void QSGDefaultRenderContext::preprocess() |
434 | { |
435 | for (auto it = m_glyphCaches.begin(); it != m_glyphCaches.end(); ++it) { |
436 | it.value()->processPendingGlyphs(); |
437 | it.value()->update(); |
438 | } |
439 | } |
440 | |
441 | QSGDistanceFieldGlyphCache *QSGDefaultRenderContext::distanceFieldGlyphCache(const QRawFont &font) |
442 | { |
443 | QString key = fontKey(font); |
444 | QSGDistanceFieldGlyphCache *cache = m_glyphCaches.value(key, defaultValue: 0); |
445 | if (!cache) { |
446 | if (m_rhi) |
447 | cache = new QSGRhiDistanceFieldGlyphCache(m_rhi, font); |
448 | else |
449 | cache = new QSGOpenGLDistanceFieldGlyphCache(openglContext(), font); |
450 | m_glyphCaches.insert(key, value: cache); |
451 | } |
452 | |
453 | return cache; |
454 | } |
455 | |
456 | QT_END_NAMESPACE |
457 | |
458 | #include "moc_qsgdefaultrendercontext_p.cpp" |
459 | |