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 | 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 | */ |
212 | QSurfaceFormat 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 | |
240 | QT_END_NAMESPACE |
241 | |