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 | |
13 | Q_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 | |
67 | QT_BEGIN_NAMESPACE |
68 | #if QT_CONFIG(opengl) |
69 | static 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 | |
136 | static 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 | #ifdef Q_OS_WASM |
188 | qCWarning(lcQuick3D, "OpenGL ES 3.0 / WebGL 2 is required on WebAssembly." ); |
189 | #endif |
190 | fmt.setVersion(major: 2, minor: 0); |
191 | fmt.setSamples(multisampling ? samples : defaultSamples); |
192 | ctx.setFormat(fmt); |
193 | qCDebug(lcQuick3D, "Testing OpenGL ES 2.0" ); |
194 | if (ctx.create()) { |
195 | qCDebug(lcQuick3D, "Requesting OpenGL ES 2.0 context succeeded" ); |
196 | return fmt; |
197 | } |
198 | if (multisampling) { |
199 | fmt.setSamples(defaultSamples); |
200 | ctx.setFormat(fmt); |
201 | if (ctx.create()) { |
202 | qCDebug(lcQuick3D, "Requesting OpenGL ES 2.0 context succeeded without multisampling" ); |
203 | return fmt; |
204 | } |
205 | } |
206 | |
207 | qCWarning(lcQuick3D, "Unable to find ideal GLES version." ); |
208 | return fmt; |
209 | } |
210 | #endif // #if QT_CONFIG(opengl) |
211 | /*! |
212 | Returns an ideal surface format for the platform. Optionally, \a samples can be specified |
213 | to select the number of multisamples for antialiasing. |
214 | */ |
215 | QSurfaceFormat QQuick3D::idealSurfaceFormat(int samples) |
216 | { |
217 | if (QQuickWindow::graphicsApi() != QSGRendererInterface::OpenGLRhi) { |
218 | QSurfaceFormat fmt = QSurfaceFormat::defaultFormat(); |
219 | fmt.setSamples(samples); |
220 | return fmt; |
221 | } |
222 | #if QT_CONFIG(opengl) |
223 | static const QSurfaceFormat f = [samples] { |
224 | QSurfaceFormat fmt; |
225 | if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) { // works in dynamic gl builds too because there's a qguiapp already |
226 | fmt = findIdealGLVersion(samples); |
227 | } else { |
228 | fmt = findIdealGLESVersion(samples); |
229 | } |
230 | fmt.setDepthBufferSize(24); |
231 | fmt.setStencilBufferSize(8); |
232 | // Ignore MSAA here as that is a per-layer setting. |
233 | return fmt; |
234 | }(); |
235 | #else |
236 | // It really shouldn't be possible to get but if we do |
237 | // but at least return something if we do. |
238 | QSurfaceFormat f = QSurfaceFormat::defaultFormat(); |
239 | #endif //#if QT_CONFIG(opengl) |
240 | return f; |
241 | } |
242 | |
243 | QT_END_NAMESPACE |
244 | |