Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qioscontext.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qioscontext.h"
5
6#include "qiosintegration.h"
7#include "qioswindow.h"
8
9#include <dlfcn.h>
10
11#include <QtGui/QGuiApplication>
12#include <QtGui/QOpenGLContext>
13
14#import <OpenGLES/EAGL.h>
15#import <OpenGLES/ES2/glext.h>
16#import <QuartzCore/CAEAGLLayer.h>
17
19
20Q_LOGGING_CATEGORY(lcQpaGLContext, "qt.qpa.glcontext");
21
24 , m_sharedContext(static_cast<QIOSContext *>(context->shareHandle()))
25 , m_eaglContext(0)
26 , m_format(context->format())
27{
29
30 EAGLSharegroup *shareGroup = m_sharedContext ? [m_sharedContext->m_eaglContext sharegroup] : nil;
31 const int preferredVersion = m_format.majorVersion() == 1 ? kEAGLRenderingAPIOpenGLES1 : kEAGLRenderingAPIOpenGLES3;
32 for (int version = preferredVersion; !m_eaglContext && version >= m_format.majorVersion(); --version)
33 m_eaglContext = [[EAGLContext alloc] initWithAPI:EAGLRenderingAPI(version) sharegroup:shareGroup];
34
35 if (m_eaglContext != nil) {
36 EAGLContext *originalContext = [EAGLContext currentContext];
37 [EAGLContext setCurrentContext:m_eaglContext];
38 const GLubyte *s = glGetString(GL_VERSION);
39 if (s) {
40 QByteArray version = QByteArray(reinterpret_cast<const char *>(s));
41 int major, minor;
42 if (QPlatformOpenGLContext::parseOpenGLVersion(version, major, minor)) {
43 m_format.setMajorVersion(major);
44 m_format.setMinorVersion(minor);
45 }
46 }
47 [EAGLContext setCurrentContext:originalContext];
48 }
49
50 // iOS internally double-buffers its rendering using copy instead of flipping,
51 // so technically we could report that we are single-buffered so that clients
52 // could take advantage of the unchanged buffer, but this means clients (and Qt)
53 // will also assume that swapBufferes() is not needed, which is _not_ the case.
55
56 qCDebug(lcQpaGLContext) << "created context with format" << m_format << "shared with" << m_sharedContext;
57}
58
60{
61 [EAGLContext setCurrentContext:m_eaglContext];
62
63 foreach (const FramebufferObject &framebufferObject, m_framebufferObjects)
64 deleteBuffers(framebufferObject);
65
66 [EAGLContext setCurrentContext:nil];
67 [m_eaglContext release];
68}
69
70void QIOSContext::deleteBuffers(const FramebufferObject &framebufferObject)
71{
72 if (framebufferObject.handle)
73 glDeleteFramebuffers(1, &framebufferObject.handle);
74 if (framebufferObject.colorRenderbuffer)
75 glDeleteRenderbuffers(1, &framebufferObject.colorRenderbuffer);
76 if (framebufferObject.depthRenderbuffer)
77 glDeleteRenderbuffers(1, &framebufferObject.depthRenderbuffer);
78}
79
81{
82 return m_format;
83}
84
85#define QT_IOS_GL_STATUS_CASE(val) case val: return QLatin1StringView(#val)
86
88{
89 switch (status) {
91 QT_IOS_GL_STATUS_CASE(GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS);
94 default:
95 return QString(QStringLiteral("unknown status: %x")).arg(status);
96 }
97}
98
99#define Q_ASSERT_IS_GL_SURFACE(surface) \
100 Q_ASSERT(surface && (surface->surface()->surfaceType() & (QSurface::OpenGLSurface | QSurface::RasterGLSurface)))
101
103{
104 Q_ASSERT_IS_GL_SURFACE(surface);
105
106 if (!verifyGraphicsHardwareAvailability())
107 return false;
108
109 [EAGLContext setCurrentContext:m_eaglContext];
110
111 // For offscreen surfaces we don't prepare a default FBO
112 if (surface->surface()->surfaceClass() == QSurface::Offscreen)
113 return true;
114
116 FramebufferObject &framebufferObject = backingFramebufferObjectFor(surface);
117
118 if (!framebufferObject.handle) {
119 // Set up an FBO for the window if it hasn't been created yet
120 glGenFramebuffers(1, &framebufferObject.handle);
121 glBindFramebuffer(GL_FRAMEBUFFER, framebufferObject.handle);
122
123 glGenRenderbuffers(1, &framebufferObject.colorRenderbuffer);
124 glBindRenderbuffer(GL_RENDERBUFFER, framebufferObject.colorRenderbuffer);
125 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
126 framebufferObject.colorRenderbuffer);
127
128 if (m_format.depthBufferSize() > 0 || m_format.stencilBufferSize() > 0) {
129 glGenRenderbuffers(1, &framebufferObject.depthRenderbuffer);
130 glBindRenderbuffer(GL_RENDERBUFFER, framebufferObject.depthRenderbuffer);
131 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
132 framebufferObject.depthRenderbuffer);
133
134 if (m_format.stencilBufferSize() > 0)
135 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
136 framebufferObject.depthRenderbuffer);
137 }
138 } else {
139 glBindFramebuffer(GL_FRAMEBUFFER, framebufferObject.handle);
140 }
141
142 if (needsRenderbufferResize(surface)) {
143 // Ensure that the FBO's buffers match the size of the layer
144 CAEAGLLayer *layer = static_cast<QIOSWindow *>(surface)->eaglLayer();
145 qCDebug(lcQpaGLContext, "Reallocating renderbuffer storage - current: %dx%d, layer: %gx%g",
146 framebufferObject.renderbufferWidth, framebufferObject.renderbufferHeight,
147 layer.frame.size.width * layer.contentsScale, layer.frame.size.height * layer.contentsScale);
148
149 glBindRenderbuffer(GL_RENDERBUFFER, framebufferObject.colorRenderbuffer);
150 [m_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];
151
152 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &framebufferObject.renderbufferWidth);
153 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &framebufferObject.renderbufferHeight);
154
155 if (framebufferObject.depthRenderbuffer) {
156 glBindRenderbuffer(GL_RENDERBUFFER, framebufferObject.depthRenderbuffer);
157
158 // FIXME: Support more fine grained control over depth/stencil buffer sizes
159 if (m_format.stencilBufferSize() > 0)
160 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES,
161 framebufferObject.renderbufferWidth, framebufferObject.renderbufferHeight);
162 else
163 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16,
164 framebufferObject.renderbufferWidth, framebufferObject.renderbufferHeight);
165 }
166
167 framebufferObject.isComplete = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
168
169 if (!framebufferObject.isComplete) {
170 qCWarning(lcQpaGLContext, "QIOSContext failed to make complete framebuffer object (%s)",
171 qPrintable(fboStatusString(glCheckFramebufferStatus(GL_FRAMEBUFFER))));
172 }
173 }
174
175 return framebufferObject.isComplete;
176}
177
179{
180 [EAGLContext setCurrentContext:nil];
181}
182
184{
185 Q_ASSERT_IS_GL_SURFACE(surface);
186
187 if (!verifyGraphicsHardwareAvailability())
188 return;
189
190 if (surface->surface()->surfaceClass() == QSurface::Offscreen)
191 return; // Nothing to do
192
193 FramebufferObject &framebufferObject = backingFramebufferObjectFor(surface);
194 Q_ASSERT_X(framebufferObject.isComplete, "QIOSContext", "swapBuffers on incomplete FBO");
195
196 if (needsRenderbufferResize(surface)) {
197 qCWarning(lcQpaGLContext, "CAEAGLLayer was resized between makeCurrent and swapBuffers, skipping flush");
198 return;
199 }
200
201 [EAGLContext setCurrentContext:m_eaglContext];
202 glBindRenderbuffer(GL_RENDERBUFFER, framebufferObject.colorRenderbuffer);
203 [m_eaglContext presentRenderbuffer:GL_RENDERBUFFER];
204}
205
206QIOSContext::FramebufferObject &QIOSContext::backingFramebufferObjectFor(QPlatformSurface *surface) const
207{
208 // We keep track of default-FBOs in the root context of a share-group. This assumes
209 // that the contexts form a tree, where leaf nodes are always destroyed before their
210 // parents. If that assumption (based on the current implementation) doesn't hold we
211 // should probably use QOpenGLMultiGroupSharedResource to track the shared default-FBOs.
212 if (m_sharedContext)
213 return m_sharedContext->backingFramebufferObjectFor(surface);
214
215 if (!m_framebufferObjects.contains(surface)) {
216 // We're about to create a new FBO, make sure it's cleaned up as well
217 connect(static_cast<QIOSWindow *>(surface), SIGNAL(destroyed(QObject*)), this, SLOT(windowDestroyed(QObject*)));
218 }
219
220 return m_framebufferObjects[surface];
221}
222
224{
225 if (surface->surface()->surfaceClass() == QSurface::Offscreen) {
226 // Binding and rendering to the zero-FBO on iOS seems to be
227 // no-ops, so we can safely return 0 here, even if it's not
228 // really a valid FBO on iOS.
229 return 0;
230 }
231
232 FramebufferObject &framebufferObject = backingFramebufferObjectFor(surface);
233 Q_ASSERT_X(framebufferObject.handle, "QIOSContext", "can't resolve default FBO before makeCurrent");
234
235 return framebufferObject.handle;
236}
237
238bool QIOSContext::needsRenderbufferResize(QPlatformSurface *surface) const
239{
241
242 FramebufferObject &framebufferObject = backingFramebufferObjectFor(surface);
243 CAEAGLLayer *layer = static_cast<QIOSWindow *>(surface)->eaglLayer();
244
245 if (framebufferObject.renderbufferWidth != (layer.frame.size.width * layer.contentsScale))
246 return true;
247
248 if (framebufferObject.renderbufferHeight != (layer.frame.size.height * layer.contentsScale))
249 return true;
250
251 return false;
252}
253
254bool QIOSContext::verifyGraphicsHardwareAvailability()
255{
256 // Per the iOS OpenGL ES Programming Guide, background apps may not execute commands on the
257 // graphics hardware. Specifically: "In your app delegate’s applicationDidEnterBackground:
258 // method, your app may want to delete some of its OpenGL ES objects to make memory and
259 // resources available to the foreground app. Call the glFinish function to ensure that
260 // the resources are removed immediately. After your app exits its applicationDidEnterBackground:
261 // method, it must not make any new OpenGL ES calls. If it makes an OpenGL ES call, it is
262 // terminated by iOS.".
263 static bool applicationBackgrounded = QGuiApplication::applicationState() == Qt::ApplicationSuspended;
264
265 static dispatch_once_t onceToken = 0;
266 dispatch_once(&onceToken, ^{
270 Q_UNUSED(oldState);
271 if (applicationBackgrounded && newState != Qt::ApplicationSuspended) {
272 qCDebug(lcQpaGLContext) << "app no longer backgrounded, rendering enabled";
273 applicationBackgrounded = false;
274 }
275 }
276 );
279 Q_UNUSED(oldState);
281 return;
282
283 qCDebug(lcQpaGLContext) << "app backgrounded, rendering disabled";
284 applicationBackgrounded = true;
285
286 // By the time we receive this signal the application has moved into
287 // Qt::ApplactionStateSuspended, and all windows have been obscured,
288 // which should stop all rendering. If there's still an active GL context,
289 // we follow Apple's advice and call glFinish before making it inactive.
290 if (QOpenGLContext *currentContext = QOpenGLContext::currentContext()) {
291 qCWarning(lcQpaGLContext) << "explicitly glFinishing and deactivating" << currentContext;
292 glFinish();
293 currentContext->doneCurrent();
294 }
295 }
296 );
297 });
298
299 if (applicationBackgrounded)
300 qCWarning(lcQpaGLContext, "OpenGL ES calls are not allowed while an application is backgrounded");
301
302 return !applicationBackgrounded;
303}
304
305void QIOSContext::windowDestroyed(QObject *object)
306{
307 QIOSWindow *window = static_cast<QIOSWindow *>(object);
308 if (!m_framebufferObjects.contains(window))
309 return;
310
311 qCDebug(lcQpaGLContext) << object << "destroyed, deleting corresponding FBO";
312
313 EAGLContext *originalContext = [EAGLContext currentContext];
314 [EAGLContext setCurrentContext:m_eaglContext];
315 deleteBuffers(m_framebufferObjects[window]);
316 m_framebufferObjects.remove(window);
317 [EAGLContext setCurrentContext:originalContext];
318}
319
320QFunctionPointer QIOSContext::getProcAddress(const char *functionName)
321{
322 return QFunctionPointer(dlsym(RTLD_DEFAULT, functionName));
323}
324
326{
327 return m_eaglContext;
328}
329
331{
332 return m_sharedContext;
333}
334
336
337#include "moc_qioscontext.cpp"
\inmodule QtCore
Definition qbytearray.h:57
static Qt::ApplicationState applicationState()
bool remove(const Key &key)
Removes the item that has the key from the hash.
Definition qhash.h:956
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
Definition qhash.h:991
void applicationStateWillChange(Qt::ApplicationState oldState, Qt::ApplicationState newState)
void applicationStateDidChange(Qt::ApplicationState oldState, Qt::ApplicationState newState)
QSurfaceFormat format() const override
void doneCurrent() override
bool isSharing() const override
void swapBuffers(QPlatformSurface *surface) override
Reimplement in subclass to native swap buffers calls.
GLuint defaultFramebufferObject(QPlatformSurface *) const override
Reimplement in subclass if your platform uses framebuffer objects for surfaces.
bool isValid() const override
QFunctionPointer getProcAddress(const char *procName) override
Reimplement in subclass to allow dynamic querying of OpenGL symbols.
bool makeCurrent(QPlatformSurface *surface) override
QIOSContext(QOpenGLContext *context)
QIOSApplicationState applicationState
static QIOSIntegration * instance()
\inmodule QtCore
Definition qobject.h:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
void destroyed(QObject *=nullptr)
This signal is emitted immediately before the object obj is destroyed, after any instances of QPointe...
\inmodule QtGui
static QOpenGLContext * currentContext()
Returns the last context which called makeCurrent in the current thread, or \nullptr,...
The QPlatformOpenGLContext class provides an abstraction for native GL contexts.
static bool parseOpenGLVersion(const QByteArray &versionString, int &major, int &minor)
The QPlatformSurface class provides an abstraction for a surface.
QSurface * surface() const
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
Definition qstring.cpp:8606
The QSurfaceFormat class represents the format of a QSurface. \inmodule QtGui.
void setMinorVersion(int minorVersion)
Sets the desired minor OpenGL version.
void setSwapBehavior(SwapBehavior behavior)
Set the swap behavior of the surface.
void setRenderableType(RenderableType type)
Sets the desired renderable type.
int stencilBufferSize() const
Returns the stencil buffer size in bits.
int depthBufferSize() const
Returns the depth buffer size.
int majorVersion() const
Returns the major OpenGL version.
void setMajorVersion(int majorVersion)
Sets the desired major OpenGL version.
SurfaceClass surfaceClass() const
Returns the surface class of this surface.
Definition qsurface.cpp:121
@ Offscreen
Definition qsurface.h:26
void newState(QList< State > &states, const char *token, const char *lexem, bool pre)
Combined button and popup list for selecting options.
ApplicationState
Definition qnamespace.h:261
@ ApplicationSuspended
Definition qnamespace.h:262
static void * context
EGLOutputLayerEXT layer
#define Q_ASSERT_IS_GL_SURFACE(surface)
#define QT_IOS_GL_STATUS_CASE(val)
static QString fboStatusString(GLenum status)
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
#define SLOT(a)
Definition qobjectdefs.h:51
#define SIGNAL(a)
Definition qobjectdefs.h:52
GLuint object
[3]
typedef GLenum(GL_APIENTRYP PFNGLGETGRAPHICSRESETSTATUSKHRPROC)(void)
#define GL_DEPTH24_STENCIL8_OES
GLint GLsizei GLsizei GLenum format
#define GL_DEPTH_COMPONENT16
Definition qopenglext.h:328
#define GL_RENDERBUFFER_WIDTH
#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
#define GL_COLOR_ATTACHMENT0
#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT
#define GL_RENDERBUFFER_HEIGHT
#define GL_FRAMEBUFFER_COMPLETE
#define GL_RENDERBUFFER
#define GL_FRAMEBUFFER_UNSUPPORTED
#define GL_FRAMEBUFFER
GLdouble s
[6]
Definition qopenglext.h:235
#define GL_DEPTH_ATTACHMENT
#define GL_STENCIL_ATTACHMENT
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
#define GLuint
#define qPrintable(string)
Definition qstring.h:1391
#define QStringLiteral(str)
#define Q_UNUSED(x)
sem release()
aWidget window() -> setWindowTitle("New Window Title")
[2]