1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3d.h"
5
6#if QT_CONFIG(opengl)
7# include <QtGui/qopenglcontext.h>
8#endif
9#include <QtQuick/qquickwindow.h>
10
11#include <QtCore/qloggingcategory.h>
12
13Q_LOGGING_CATEGORY(lcQuick3D, "qt.quick3d.general")
14
15/*!
16 \class QQuick3D
17 \inmodule QtQuick3D
18 \since 5.15
19 \brief Helper class for selecting correct surface format.
20
21 When using Qt Quick 3D with OpenGL it is necessary to take extra steps to
22 define what kind of \l {QSurfaceFormat}{surface format} is used when
23 initializing Qt Quick. This is because by the time Qt Quick is aware that
24 3D content is being used, the OpenGL context and window surface has already
25 been initialized. So this helper class is provided to request the ideal
26 surface format from Qt Quick 3D so that it can be set as the default surface
27 for Qt Quick before initialization.
28
29 If this helper is run when using any other rendering backends than OpenGL
30 then it just returns a copy of the current default QSurfaceFormat with the
31 requested samples.
32
33 If this helper is run when using the OpenGL rendering backend, then it will
34 test for sufficiently modern versions of OpenGL and support for
35 multisampling if requested. Normally Qt Quick will request an OpenGL 2.0 or
36 OpenGL ES 2.0 context, which would limit the features available when using
37 Qt Quick 3D, so an extra step is needed to request a more capable context.
38
39 The correct usage pattern is to call \l QSurfaceFormat::setDefaultFormat
40 to set the \l QSurfaceFormat returned by \l QQuick3D::idealSurfaceFormat.
41 It is important that this method is called after \l QGuiApplication is
42 constructed, but before the Qt Quick application content is loaded. This
43 code snippet shows the correct usage pattern:
44 \code
45 #include <QGuiApplication>
46 #include <QQmlApplicationEngine>
47
48 #include <QtGui>
49 #include <QtQuick3D/qquick3d.h>
50
51 int main(int argc, char *argv[])
52 {
53 QGuiApplication app(argc, argv);
54
55 QSurfaceFormat::setDefaultFormat(QQuick3D::idealSurfaceFormat(4));
56
57 QQmlApplicationEngine engine;
58 engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
59 if (engine.rootObjects().isEmpty())
60 return -1;
61
62 return app.exec();
63 }
64 \endcode
65*/
66
67QT_BEGIN_NAMESPACE
68#if QT_CONFIG(opengl)
69static QSurfaceFormat findIdealGLVersion(int samples)
70{
71 QSurfaceFormat fmt;
72 int defaultSamples = fmt.samples();
73 const bool multisampling = samples > 1;
74 fmt.setProfile(QSurfaceFormat::CoreProfile);
75
76 // Proper case: Try 4.3 core (so we get compute shaders for instance)
77 fmt.setVersion(major: 4, minor: 3);
78 fmt.setSamples(multisampling ? samples : defaultSamples);
79 QOpenGLContext ctx;
80 ctx.setFormat(fmt);
81 if (ctx.create() && ctx.format().version() >= qMakePair(value1: 4, value2: 3)) {
82 qCDebug(lcQuick3D, "Requesting OpenGL 4.3 core context succeeded");
83 return ctx.format();
84 }
85 if (multisampling) {
86 // try without multisampling
87 fmt.setSamples(defaultSamples);
88 ctx.setFormat(fmt);
89 if (ctx.create() && ctx.format().version() >= qMakePair(value1: 4, value2: 3)) {
90 qCDebug(lcQuick3D, "Requesting OpenGL 4.3 core context succeeded without multisampling");
91 return ctx.format();
92 }
93 }
94
95 // Fallback, but still good, case: Stick with 3.3 (the only thing we lose is compute for HDR mipmaps)
96 fmt.setVersion(major: 3, minor: 3);
97 fmt.setSamples(multisampling ? samples : defaultSamples);
98 ctx.setFormat(fmt);
99 if (ctx.create() && ctx.format().version() >= qMakePair(value1: 3, value2: 3)) {
100 qCDebug(lcQuick3D, "Requesting OpenGL 3.3 core context succeeded");
101 return ctx.format();
102 }
103 if (multisampling) {
104 // try without multisampling
105 fmt.setSamples(defaultSamples);
106 ctx.setFormat(fmt);
107 if (ctx.create() && ctx.format().version() >= qMakePair(value1: 3, value2: 3)) {
108 qCDebug(lcQuick3D, "Requesting OpenGL 3.3 core context succeeded without multisampling");
109 return ctx.format();
110 }
111 }
112
113 // If all else fails, try 3.0. This may have some issues but most things should work.
114 fmt.setVersion(major: 3, minor: 0);
115 // the modern core-compat. concept as we know it is only there since 3.2
116 fmt.setProfile(QSurfaceFormat::NoProfile);
117 fmt.setSamples(multisampling ? samples : defaultSamples);
118 ctx.setFormat(fmt);
119 if (ctx.create() && ctx.format().version() >= qMakePair(value1: 3, value2: 0)) {
120 qCDebug(lcQuick3D, "Requesting OpenGL 3.0 context succeeded");
121 return ctx.format();
122 }
123 if (multisampling) {
124 fmt.setSamples(defaultSamples);
125 ctx.setFormat(fmt);
126 if (ctx.create() && ctx.format().version() >= qMakePair(value1: 3, value2: 0)) {
127 qCDebug(lcQuick3D, "Requesting OpenGL 3.0 context succeeded without multisampling");
128 return ctx.format();
129 }
130 }
131
132 qCWarning(lcQuick3D, "Unable to find ideal GL version.");
133 return fmt;
134}
135
136static QSurfaceFormat findIdealGLESVersion(int samples)
137{
138 QSurfaceFormat fmt;
139 int defaultSamples = fmt.samples();
140 const bool multisampling = samples > 1;
141
142 // Proper case: Try 3.1 (so we get compute shaders)
143 fmt.setVersion(major: 3, minor: 1);
144 fmt.setRenderableType(QSurfaceFormat::OpenGLES);
145 fmt.setSamples(multisampling ? samples : defaultSamples);
146 QOpenGLContext ctx;
147 ctx.setFormat(fmt);
148
149 // Now, it's important to check the format with the actual version (parsed
150 // back from GL_VERSION) since some implementations are broken and succeed
151 // the 3.1 context request even though they only support and return a 3.0
152 // context. This is against the spec since 3.0 is obviously not backwards
153 // compatible with 3.1, but hey...
154 qCDebug(lcQuick3D, "Testing OpenGL ES 3.1");
155 if (ctx.create() && ctx.format().version() >= qMakePair(value1: 3, value2: 1)) {
156 qCDebug(lcQuick3D, "Requesting OpenGL ES 3.1 context succeeded");
157 return ctx.format();
158 }
159 if (multisampling) {
160 fmt.setSamples(defaultSamples);
161 ctx.setFormat(fmt);
162 if (ctx.create() && ctx.format().version() >= qMakePair(value1: 3, value2: 1)) {
163 qCDebug(lcQuick3D, "Requesting OpenGL ES 3.1 context succeeded without multisampling");
164 return ctx.format();
165 }
166 }
167
168 // Fallback, but still good, case: OpenGL ES 3.0
169 fmt.setVersion(major: 3, minor: 0);
170 fmt.setSamples(multisampling ? samples : defaultSamples);
171 ctx.setFormat(fmt);
172 qCDebug(lcQuick3D, "Testing OpenGL ES 3.0");
173 if (ctx.create() && ctx.format().version() >= qMakePair(value1: 3, value2: 0)) {
174 qCDebug(lcQuick3D, "Requesting OpenGL ES 3.0 context succeeded");
175 return ctx.format();
176 }
177 if (multisampling) {
178 fmt.setSamples(defaultSamples);
179 ctx.setFormat(fmt);
180 if (ctx.create() && ctx.format().version() >= qMakePair(value1: 3, value2: 0)) {
181 qCDebug(lcQuick3D, "Requesting OpenGL ES 3.0 context succeeded without multisampling");
182 return ctx.format();
183 }
184 }
185
186 // If all else fails, try 2.0 but that's going to lose a bunch of features.
187 fmt.setVersion(major: 2, minor: 0);
188 fmt.setSamples(multisampling ? samples : defaultSamples);
189 ctx.setFormat(fmt);
190 qCDebug(lcQuick3D, "Testing OpenGL ES 2.0");
191 if (ctx.create()) {
192 qCDebug(lcQuick3D, "Requesting OpenGL ES 2.0 context succeeded");
193 return fmt;
194 }
195 if (multisampling) {
196 fmt.setSamples(defaultSamples);
197 ctx.setFormat(fmt);
198 if (ctx.create()) {
199 qCDebug(lcQuick3D, "Requesting OpenGL ES 2.0 context succeeded without multisampling");
200 return fmt;
201 }
202 }
203
204 qCWarning(lcQuick3D, "Unable to find ideal GLES version.");
205 return fmt;
206}
207#endif // #if QT_CONFIG(opengl)
208/*!
209 Returns an ideal surface format for the platform. Optionally, \a samples can be specified
210 to select the number of multisamples for antialiasing.
211*/
212QSurfaceFormat QQuick3D::idealSurfaceFormat(int samples)
213{
214 if (QQuickWindow::graphicsApi() != QSGRendererInterface::OpenGLRhi) {
215 QSurfaceFormat fmt = QSurfaceFormat::defaultFormat();
216 fmt.setSamples(samples);
217 return fmt;
218 }
219#if QT_CONFIG(opengl)
220 static const QSurfaceFormat f = [samples] {
221 QSurfaceFormat fmt;
222 if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) { // works in dynamic gl builds too because there's a qguiapp already
223 fmt = findIdealGLVersion(samples);
224 } else {
225 fmt = findIdealGLESVersion(samples);
226 }
227 fmt.setDepthBufferSize(24);
228 fmt.setStencilBufferSize(8);
229 // Ignore MSAA here as that is a per-layer setting.
230 return fmt;
231 }();
232#else
233 // It really shouldn't be possible to get but if we do
234 // but at least return something if we do.
235 QSurfaceFormat f = QSurfaceFormat::defaultFormat();
236#endif //#if QT_CONFIG(opengl)
237 return f;
238}
239
240QT_END_NAMESPACE
241

source code of qtquick3d/src/quick3d/qquick3d.cpp