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
55QT_BEGIN_NAMESPACE
56
57#define QSG_RENDERCONTEXT_PROPERTY "_q_sgrendercontext"
58
59QSGDefaultRenderContext::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{
72}
73
74/*!
75 Initializes the scene graph render context with the GL context \a context. This also
76 emits the ready() signal so that the QML graph can start building scene graph nodes.
77 */
78void QSGDefaultRenderContext::initialize(const QSGRenderContext::InitParams *params)
79{
80 if (!m_sg)
81 return;
82
83 const InitParams *initParams = static_cast<const InitParams *>(params);
84 if (initParams->sType != INIT_PARAMS_MAGIC)
85 qFatal("QSGDefaultRenderContext: Invalid parameters passed to initialize()");
86
87 m_initParams = *initParams;
88
89 m_rhi = m_initParams.rhi;
90 if (m_rhi) {
91 m_maxTextureSize = m_rhi->resourceLimit(QRhi::TextureSizeMax);
92 if (!m_rhiAtlasManager)
93 m_rhiAtlasManager = new QSGRhiAtlasTexture::Manager(this, m_initParams.initialSurfacePixelSize, m_initParams.maybeSurface);
94 } else {
95 QOpenGLFunctions *funcs = m_rhi ? nullptr : QOpenGLContext::currentContext()->functions();
96 funcs->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_maxTextureSize);
97
98 // Sanity check the surface format, in case it was overridden by the application
99 QSurfaceFormat requested = m_sg->defaultSurfaceFormat();
100 QSurfaceFormat actual = m_initParams.openGLContext->format();
101 if (requested.depthBufferSize() > 0 && actual.depthBufferSize() <= 0)
102 qWarning("QSGContext::initialize: depth buffer support missing, expect rendering errors");
103 if (requested.stencilBufferSize() > 0 && actual.stencilBufferSize() <= 0)
104 qWarning("QSGContext::initialize: stencil buffer support missing, expect rendering errors");
105
106#ifdef Q_OS_LINUX
107 const char *vendor = (const char *) funcs->glGetString(GL_VENDOR);
108 if (vendor && strstr(vendor, "nouveau"))
109 m_brokenIBOs = true;
110 const char *renderer = (const char *) funcs->glGetString(GL_RENDERER);
111 if (renderer && strstr(renderer, "llvmpipe"))
112 m_serializedRender = true;
113 if (vendor && renderer && strstr(vendor, "Hisilicon Technologies") && strstr(renderer, "Immersion.16"))
114 m_brokenIBOs = true;
115#endif
116
117 Q_ASSERT_X(!m_gl, "QSGRenderContext::initialize", "already initialized!");
118 m_gl = m_initParams.openGLContext;
119 if (m_attachToGLContext) {
120 Q_ASSERT(!m_gl->property(QSG_RENDERCONTEXT_PROPERTY).isValid());
121 m_gl->setProperty(QSG_RENDERCONTEXT_PROPERTY, QVariant::fromValue(this));
122 }
123
124 if (!m_glAtlasManager)
125 m_glAtlasManager = new QSGOpenGLAtlasTexture::Manager(m_initParams.initialSurfacePixelSize);
126 }
127
128 m_sg->renderContextInitialized(this);
129
130 emit initialized();
131}
132
133void QSGDefaultRenderContext::invalidate()
134{
135 if (!m_gl && !m_rhi)
136 return;
137
138 qDeleteAll(m_texturesToDelete);
139 m_texturesToDelete.clear();
140
141 qDeleteAll(m_textures);
142 m_textures.clear();
143
144 /* The cleanup of the atlas textures is a bit intriguing.
145 As part of the cleanup in the threaded render loop, we
146 do:
147 1. call this function
148 2. call QCoreApp::sendPostedEvents() to immediately process
149 any pending deferred deletes.
150 3. delete the GL context.
151
152 As textures need the atlas manager while cleaning up, the
153 manager needs to be cleaned up after the textures, so
154 we post a deleteLater here at the very bottom so it gets
155 deferred deleted last.
156
157 Another alternative would be to use a QPointer in
158 QSGOpenGLAtlasTexture::Texture, but this seemed simpler.
159 */
160 if (m_glAtlasManager) {
161 m_glAtlasManager->invalidate();
162 m_glAtlasManager->deleteLater();
163 m_glAtlasManager = nullptr;
164 }
165 if (m_rhiAtlasManager) {
166 m_rhiAtlasManager->invalidate();
167 m_rhiAtlasManager->deleteLater();
168 m_rhiAtlasManager = nullptr;
169 }
170
171 // The following piece of code will read/write to the font engine's caches,
172 // potentially from different threads. However, this is safe because this
173 // code is only called from QQuickWindow's shutdown which is called
174 // only when the GUI is blocked, and multiple threads will call it in
175 // sequence. (see qsgdefaultglyphnode_p.cpp's init())
176 for (QSet<QFontEngine *>::const_iterator it = m_fontEnginesToClean.constBegin(),
177 end = m_fontEnginesToClean.constEnd(); it != end; ++it) {
178 (*it)->clearGlyphCache(m_gl ? (void *) m_gl : (void *) m_rhi);
179 if (!(*it)->ref.deref())
180 delete *it;
181 }
182 m_fontEnginesToClean.clear();
183
184 delete m_depthStencilManager;
185 m_depthStencilManager = nullptr;
186
187 qDeleteAll(m_glyphCaches);
188 m_glyphCaches.clear();
189
190 if (m_gl && m_gl->property(QSG_RENDERCONTEXT_PROPERTY) == QVariant::fromValue(this))
191 m_gl->setProperty(QSG_RENDERCONTEXT_PROPERTY, QVariant());
192
193 m_gl = nullptr;
194 m_rhi = nullptr;
195
196 if (m_sg)
197 m_sg->renderContextInvalidated(this);
198
199 emit invalidated();
200}
201
202static QBasicMutex qsg_framerender_mutex;
203
204void QSGDefaultRenderContext::beginNextFrame(QSGRenderer *renderer,
205 RenderPassCallback mainPassRecordingStart,
206 RenderPassCallback mainPassRecordingEnd,
207 void *callbackUserData)
208{
209 renderer->setRenderPassRecordingCallbacks(mainPassRecordingStart, mainPassRecordingEnd, callbackUserData);
210}
211
212void QSGDefaultRenderContext::renderNextFrame(QSGRenderer *renderer, uint fboId)
213{
214 if (m_serializedRender)
215 qsg_framerender_mutex.lock();
216
217 renderer->renderScene(fboId);
218
219 if (m_serializedRender)
220 qsg_framerender_mutex.unlock();
221}
222
223void QSGDefaultRenderContext::endNextFrame(QSGRenderer *renderer)
224{
225 Q_UNUSED(renderer);
226}
227
228void QSGDefaultRenderContext::beginNextRhiFrame(QSGRenderer *renderer, QRhiRenderTarget *rt, QRhiRenderPassDescriptor *rp,
229 QRhiCommandBuffer *cb,
230 RenderPassCallback mainPassRecordingStart,
231 RenderPassCallback mainPassRecordingEnd,
232 void *callbackUserData)
233{
234 Q_ASSERT(!m_currentFrameCommandBuffer);
235
236 renderer->setRenderTarget(rt);
237 renderer->setRenderPassDescriptor(rp);
238 renderer->setCommandBuffer(cb);
239 renderer->setRenderPassRecordingCallbacks(mainPassRecordingStart, mainPassRecordingEnd, callbackUserData);
240
241 m_currentFrameCommandBuffer = cb;
242}
243
244void QSGDefaultRenderContext::renderNextRhiFrame(QSGRenderer *renderer)
245{
246 renderer->renderScene();
247}
248
249void QSGDefaultRenderContext::endNextRhiFrame(QSGRenderer *renderer)
250{
251 Q_UNUSED(renderer);
252 m_currentFrameCommandBuffer = nullptr;
253}
254
255/*!
256 Returns a shared pointer to a depth stencil buffer that can be used with \a fbo.
257*/
258QSharedPointer<QSGDepthStencilBuffer> QSGDefaultRenderContext::depthStencilBufferForFbo(QOpenGLFramebufferObject *fbo)
259{
260 if (!m_gl)
261 return QSharedPointer<QSGDepthStencilBuffer>();
262 QSGDepthStencilBufferManager *manager = depthStencilBufferManager();
263 QSGDepthStencilBuffer::Format format;
264 format.size = fbo->size();
265 format.samples = fbo->format().samples();
266 format.attachments = QSGDepthStencilBuffer::DepthAttachment | QSGDepthStencilBuffer::StencilAttachment;
267 QSharedPointer<QSGDepthStencilBuffer> buffer = manager->bufferForFormat(format);
268 if (buffer.isNull()) {
269 buffer = QSharedPointer<QSGDepthStencilBuffer>(new QSGDefaultDepthStencilBuffer(m_gl, format));
270 manager->insertBuffer(buffer);
271 }
272 return buffer;
273}
274
275/*!
276 Returns a pointer to the context's depth/stencil buffer manager. This is useful for custom
277 implementations of \l depthStencilBufferForFbo().
278*/
279QSGDepthStencilBufferManager *QSGDefaultRenderContext::depthStencilBufferManager()
280{
281 if (!m_gl)
282 return nullptr;
283 if (!m_depthStencilManager)
284 m_depthStencilManager = new QSGDepthStencilBufferManager(m_gl);
285 return m_depthStencilManager;
286}
287
288QSGTexture *QSGDefaultRenderContext::createTexture(const QImage &image, uint flags) const
289{
290 bool atlas = flags & CreateTexture_Atlas;
291 bool mipmap = flags & CreateTexture_Mipmap;
292 bool alpha = flags & CreateTexture_Alpha;
293
294 // The atlas implementation is only supported from the render thread and
295 // does not support mipmaps.
296 if (m_rhi) {
297 if (!mipmap && atlas && QThread::currentThread() == m_rhi->thread()) {
298 QSGTexture *t = m_rhiAtlasManager->create(image, alpha);
299 if (t)
300 return t;
301 }
302 } else {
303 if (!mipmap && atlas && openglContext() && QThread::currentThread() == openglContext()->thread()) {
304 QSGTexture *t = m_glAtlasManager->create(image, alpha);
305 if (t)
306 return t;
307 }
308 }
309
310 QSGPlainTexture *texture = new QSGPlainTexture;
311 texture->setImage(image);
312 if (texture->hasAlphaChannel() && !alpha)
313 texture->setHasAlphaChannel(false);
314
315 return texture;
316}
317
318QSGRenderer *QSGDefaultRenderContext::createRenderer()
319{
320 return new QSGBatchRenderer::Renderer(this);
321}
322
323QSGTexture *QSGDefaultRenderContext::compressedTextureForFactory(const QSGCompressedTextureFactory *factory) const
324{
325 // This is only used for atlasing compressed textures. Returning null implies no atlas.
326
327 if (m_rhi) {
328 // ###
329 } else if (openglContext() && QThread::currentThread() == openglContext()->thread()) {
330 // The atlas implementation is only supported from the render thread
331 return m_glAtlasManager->create(factory);
332 }
333
334 return nullptr;
335}
336
337/*!
338 Compile \a shader, optionally using \a vertexCode and \a fragmentCode as
339 replacement for the source code supplied by \a shader.
340
341 If \a vertexCode or \a fragmentCode is supplied, the caller is responsible
342 for setting up attribute bindings.
343
344 \a material is supplied in case the implementation needs to take the
345 material flags into account.
346 */
347void QSGDefaultRenderContext::compileShader(QSGMaterialShader *shader, QSGMaterial *material, const char *vertexCode, const char *fragmentCode)
348{
349 Q_UNUSED(material);
350 if (vertexCode || fragmentCode) {
351 Q_ASSERT_X((material->flags() & QSGMaterial::CustomCompileStep) == 0,
352 "QSGRenderContext::compile()",
353 "materials with custom compile step cannot have modified vertex or fragment code");
354 QOpenGLShaderProgram *p = shader->program();
355 p->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexCode ? vertexCode : shader->vertexShader());
356 p->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentCode ? fragmentCode : shader->fragmentShader());
357 p->link();
358 if (!p->isLinked())
359 qWarning() << "shader compilation failed:" << endl << p->log();
360 } else {
361 shader->compile();
362 }
363}
364
365QString QSGDefaultRenderContext::fontKey(const QRawFont &font)
366{
367 QFontEngine *fe = QRawFontPrivate::get(font)->fontEngine;
368 if (!fe->faceId().filename.isEmpty()) {
369 QByteArray keyName = fe->faceId().filename;
370 if (font.style() != QFont::StyleNormal)
371 keyName += QByteArray(" I");
372 if (font.weight() != QFont::Normal)
373 keyName += ' ' + QByteArray::number(font.weight());
374 keyName += QByteArray(" DF");
375 return QString::fromUtf8(keyName);
376 } else {
377 return QString::fromLatin1("%1_%2_%3_%4")
378 .arg(font.familyName())
379 .arg(font.styleName())
380 .arg(font.weight())
381 .arg(font.style());
382 }
383}
384
385void QSGDefaultRenderContext::initializeShader(QSGMaterialShader *shader)
386{
387 shader->program()->bind();
388 shader->initialize();
389}
390
391void QSGDefaultRenderContext::initializeRhiShader(QSGMaterialRhiShader *shader, QShader::Variant shaderVariant)
392{
393 QSGMaterialRhiShaderPrivate::get(shader)->prepare(shaderVariant);
394}
395
396void QSGDefaultRenderContext::setAttachToGraphicsContext(bool attach)
397{
398 Q_ASSERT(!isValid());
399 m_attachToGLContext = attach;
400}
401
402QSGDefaultRenderContext *QSGDefaultRenderContext::from(QOpenGLContext *context)
403{
404 return qobject_cast<QSGDefaultRenderContext *>(context->property(QSG_RENDERCONTEXT_PROPERTY).value<QObject *>());
405}
406
407bool QSGDefaultRenderContext::separateIndexBuffer() const
408{
409 if (m_rhi)
410 return true;
411
412 // WebGL: A given WebGLBuffer object may only be bound to one of
413 // the ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target in its
414 // lifetime. An attempt to bind a buffer object to the other
415 // target will generate an INVALID_OPERATION error, and the
416 // current binding will remain untouched.
417 static const bool isWebGL = (qGuiApp->platformName().compare(QLatin1String("webgl")) == 0
418 || qGuiApp->platformName().compare(QLatin1String("wasm")) == 0);
419 return isWebGL;
420}
421
422QSGDistanceFieldGlyphCache *QSGDefaultRenderContext::distanceFieldGlyphCache(const QRawFont &font)
423{
424 QString key = fontKey(font);
425 QSGDistanceFieldGlyphCache *cache = m_glyphCaches.value(key, 0);
426 if (!cache) {
427 if (m_rhi)
428 cache = new QSGRhiDistanceFieldGlyphCache(m_rhi, font);
429 else
430 cache = new QSGOpenGLDistanceFieldGlyphCache(openglContext(), font);
431 m_glyphCaches.insert(key, cache);
432 }
433
434 return cache;
435}
436
437QT_END_NAMESPACE
438
439#include "moc_qsgdefaultrendercontext_p.cpp"
440