aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick3d/qquick3d.cpp
blob: c3d1751a1877232923fd33b0c6e8a0e3afafeb5f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include "qquick3d.h"

#if QT_CONFIG(opengl)
# include <QtGui/qopenglcontext.h>
#endif
#include <QtQuick/qquickwindow.h>

#include <QtCore/qloggingcategory.h>

Q_STATIC_LOGGING_CATEGORY(lcQuick3D, "qt.quick3d.general")

/*!
    \class QQuick3D
    \inmodule QtQuick3D
    \since 5.15
    \brief Helper class for selecting correct surface format.

    When using Qt Quick 3D with OpenGL it is necessary to take extra steps to
    define what kind of \l {QSurfaceFormat}{surface format} is used when
    initializing Qt Quick. This is because by the time Qt Quick is aware that
    3D content is being used, the OpenGL context and window surface has already
    been initialized. So this helper class is provided to request the ideal
    surface format from Qt Quick 3D so that it can be set as the default surface
    for Qt Quick before initialization.

    If this helper is run when using any other rendering backends than OpenGL
    then it just returns a copy of the current default QSurfaceFormat with the
    requested samples.

    If this helper is run when using the OpenGL rendering backend, then it will
    test for sufficiently modern versions of OpenGL and support for
    multisampling if requested. Normally Qt Quick will request an OpenGL 2.0 or
    OpenGL ES 2.0 context, which would limit the features available when using
    Qt Quick 3D, so an extra step is needed to request a more capable context.

    The correct usage pattern is to call \l QSurfaceFormat::setDefaultFormat
    to set the \l QSurfaceFormat returned by \l QQuick3D::idealSurfaceFormat.
    It is important that this method is called after \l QGuiApplication is
    constructed, but before the Qt Quick application content is loaded. This
    code snippet shows the correct usage pattern:
    \code
    #include <QGuiApplication>
    #include <QQmlApplicationEngine>

    #include <QtGui>
    #include <QtQuick3D/qquick3d.h>

    int main(int argc, char *argv[])
    {
        QGuiApplication app(argc, argv);

        QSurfaceFormat::setDefaultFormat(QQuick3D::idealSurfaceFormat(4));

        QQmlApplicationEngine engine;
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
        if (engine.rootObjects().isEmpty())
            return -1;

        return app.exec();
    }
    \endcode

    \sa {Qt Quick 3D Graphics Requirements} {OpenGL specifics}
*/

QT_BEGIN_NAMESPACE
#if QT_CONFIG(opengl)
static QSurfaceFormat findIdealGLVersion(int samples)
{
    QSurfaceFormat fmt;
    int defaultSamples = fmt.samples();
    const bool multisampling = samples > 1;
    fmt.setProfile(QSurfaceFormat::CoreProfile);

    // Proper case: Try 4.3 core (so we get compute shaders for instance)
    fmt.setVersion(4, 3);
    fmt.setSamples(multisampling ? samples : defaultSamples);
    QOpenGLContext ctx;
    ctx.setFormat(fmt);
    if (ctx.create() && ctx.format().version() >= qMakePair(4, 3)) {
        qCDebug(lcQuick3D, "Requesting OpenGL 4.3 core context succeeded");
        return ctx.format();
    }
    if (multisampling) {
        // try without multisampling
        fmt.setSamples(defaultSamples);
        ctx.setFormat(fmt);
        if (ctx.create() && ctx.format().version() >= qMakePair(4, 3)) {
            qCDebug(lcQuick3D, "Requesting OpenGL 4.3 core context succeeded without multisampling");
            return ctx.format();
        }
    }

    // Fallback, but still good, case: Stick with 3.3 (the only thing we lose is compute for HDR mipmaps)
    fmt.setVersion(3, 3);
    fmt.setSamples(multisampling ? samples : defaultSamples);
    ctx.setFormat(fmt);
    if (ctx.create() && ctx.format().version() >= qMakePair(3, 3)) {
        qCDebug(lcQuick3D, "Requesting OpenGL 3.3 core context succeeded");
        return ctx.format();
    }
    if (multisampling) {
        // try without multisampling
        fmt.setSamples(defaultSamples);
        ctx.setFormat(fmt);
        if (ctx.create() && ctx.format().version() >= qMakePair(3, 3)) {
            qCDebug(lcQuick3D, "Requesting OpenGL 3.3 core context succeeded without multisampling");
            return ctx.format();
        }
    }

    // If all else fails, try 3.0. This may have some issues but most things should work.
    fmt.setVersion(3, 0);
    // the modern core-compat. concept as we know it is only there since 3.2
    fmt.setProfile(QSurfaceFormat::NoProfile);
    fmt.setSamples(multisampling ? samples : defaultSamples);
    ctx.setFormat(fmt);
    if (ctx.create() && ctx.format().version() >= qMakePair(3, 0)) {
        qCDebug(lcQuick3D, "Requesting OpenGL 3.0 context succeeded");
        return ctx.format();
    }
    if (multisampling) {
        fmt.setSamples(defaultSamples);
        ctx.setFormat(fmt);
        if (ctx.create() && ctx.format().version() >= qMakePair(3, 0)) {
            qCDebug(lcQuick3D, "Requesting OpenGL 3.0 context succeeded without multisampling");
            return ctx.format();
        }
    }

    qCWarning(lcQuick3D, "Unable to find ideal GL version.");
    return fmt;
}

static QSurfaceFormat findIdealGLESVersion(int samples)
{
    QSurfaceFormat fmt;
    int defaultSamples = fmt.samples();
    const bool multisampling = samples > 1;

    // Proper case: Try 3.1 (so we get compute shaders)
    fmt.setVersion(3, 1);
    fmt.setRenderableType(QSurfaceFormat::OpenGLES);
    fmt.setSamples(multisampling ? samples : defaultSamples);
    QOpenGLContext ctx;
    ctx.setFormat(fmt);

    // Now, it's important to check the format with the actual version (parsed
    // back from GL_VERSION) since some implementations are broken and succeed
    // the 3.1 context request even though they only support and return a 3.0
    // context. This is against the spec since 3.0 is obviously not backwards
    // compatible with 3.1, but hey...
    qCDebug(lcQuick3D, "Testing OpenGL ES 3.1");
    if (ctx.create() && ctx.format().version() >= qMakePair(3, 1)) {
        qCDebug(lcQuick3D, "Requesting OpenGL ES 3.1 context succeeded");
        return ctx.format();
    }
    if (multisampling) {
        fmt.setSamples(defaultSamples);
        ctx.setFormat(fmt);
        if (ctx.create() && ctx.format().version() >= qMakePair(3, 1)) {
            qCDebug(lcQuick3D, "Requesting OpenGL ES 3.1 context succeeded without multisampling");
            return ctx.format();
        }
    }

    // Fallback, but still good, case: OpenGL ES 3.0
    fmt.setVersion(3, 0);
    fmt.setSamples(multisampling ? samples : defaultSamples);
    ctx.setFormat(fmt);
    qCDebug(lcQuick3D, "Testing OpenGL ES 3.0");
    if (ctx.create() && ctx.format().version() >= qMakePair(3, 0)) {
        qCDebug(lcQuick3D, "Requesting OpenGL ES 3.0 context succeeded");
        return ctx.format();
    }
    if (multisampling) {
        fmt.setSamples(defaultSamples);
        ctx.setFormat(fmt);
        if (ctx.create() && ctx.format().version() >= qMakePair(3, 0)) {
            qCDebug(lcQuick3D, "Requesting OpenGL ES 3.0 context succeeded without multisampling");
            return ctx.format();
        }
    }

    // If all else fails, try 2.0 but that's going to lose a bunch of features.
#ifdef Q_OS_WASM
    qCWarning(lcQuick3D, "OpenGL ES 3.0 / WebGL 2 is required on WebAssembly.");
#endif
    fmt.setVersion(2, 0);
    fmt.setSamples(multisampling ? samples : defaultSamples);
    ctx.setFormat(fmt);
    qCDebug(lcQuick3D, "Testing OpenGL ES 2.0");
    if (ctx.create()) {
        qCDebug(lcQuick3D, "Requesting OpenGL ES 2.0 context succeeded");
        return fmt;
    }
    if (multisampling) {
        fmt.setSamples(defaultSamples);
        ctx.setFormat(fmt);
        if (ctx.create()) {
            qCDebug(lcQuick3D, "Requesting OpenGL ES 2.0 context succeeded without multisampling");
            return fmt;
        }
    }

    qCWarning(lcQuick3D, "Unable to find ideal GLES version.");
    return fmt;
}
#endif // #if QT_CONFIG(opengl)
/*!
    Returns an ideal surface format for the platform. Optionally, \a samples can be specified
    to select the number of multisamples for antialiasing.
*/
QSurfaceFormat QQuick3D::idealSurfaceFormat(int samples)
{
    if (QQuickWindow::graphicsApi() != QSGRendererInterface::OpenGLRhi) {
        QSurfaceFormat fmt = QSurfaceFormat::defaultFormat();
        fmt.setSamples(samples);
        return fmt;
    }
#if QT_CONFIG(opengl)
    static const QSurfaceFormat f = [samples] {
        QSurfaceFormat fmt;
        if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) { // works in dynamic gl builds too because there's a qguiapp already
            fmt = findIdealGLVersion(samples);
        } else {
            fmt = findIdealGLESVersion(samples);
        }
        fmt.setDepthBufferSize(24);
        fmt.setStencilBufferSize(8);
        // Ignore MSAA here as that is a per-layer setting.
        return fmt;
    }();
#else
    // It really shouldn't be possible to get but if we do
    // but at least return something if we do.
    QSurfaceFormat f = QSurfaceFormat::defaultFormat();
#endif //#if QT_CONFIG(opengl)
    return f;
}

QT_END_NAMESPACE