Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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// Qt-Security score:significant reason:default
4
5#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
6
7#include "qioscontext.h"
8
10#include "qioswindow.h"
11
12#include <dlfcn.h>
13
14#include <QtGui/QGuiApplication>
15#include <QtGui/QOpenGLContext>
16
17#import <OpenGLES/EAGL.h>
18#import <OpenGLES/ES2/glext.h>
19#import <QuartzCore/CAEAGLLayer.h>
20
22
23Q_LOGGING_CATEGORY(lcQpaGLContext, "qt.qpa.glcontext");
24
25QIOSContext::QIOSContext(QOpenGLContext *context)
26 : QPlatformOpenGLContext()
27 , m_sharedContext(static_cast<QIOSContext *>(context->shareHandle()))
28 , m_eaglContext(0)
29 , m_format(context->format())
30{
31 m_format.setRenderableType(QSurfaceFormat::OpenGLES);
32
33 EAGLSharegroup *shareGroup = m_sharedContext ? [m_sharedContext->m_eaglContext sharegroup] : nil;
34 const int preferredVersion = m_format.majorVersion() == 1 ? kEAGLRenderingAPIOpenGLES1 : kEAGLRenderingAPIOpenGLES3;
35 for (int version = preferredVersion; !m_eaglContext && version >= m_format.majorVersion(); --version)
36 m_eaglContext = [[EAGLContext alloc] initWithAPI:EAGLRenderingAPI(version) sharegroup:shareGroup];
37
38 if (m_eaglContext != nil) {
39 EAGLContext *originalContext = [EAGLContext currentContext];
40 [EAGLContext setCurrentContext:m_eaglContext];
41 const GLubyte *s = glGetString(GL_VERSION);
42 if (s) {
43 QByteArray version = QByteArray(reinterpret_cast<const char *>(s));
44 int major, minor;
45 if (QPlatformOpenGLContext::parseOpenGLVersion(version, major, minor)) {
46 m_format.setMajorVersion(major);
47 m_format.setMinorVersion(minor);
48 }
49 }
50 [EAGLContext setCurrentContext:originalContext];
51 }
52
53 // iOS internally double-buffers its rendering using copy instead of flipping,
54 // so technically we could report that we are single-buffered so that clients
55 // could take advantage of the unchanged buffer, but this means clients (and Qt)
56 // will also assume that swapBufferes() is not needed, which is _not_ the case.
57 m_format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
58
59 qCDebug(lcQpaGLContext) << "created context with format" << m_format << "shared with" << m_sharedContext;
60}
61
63{
64 [EAGLContext setCurrentContext:m_eaglContext];
65
66 foreach (const FramebufferObject &framebufferObject, m_framebufferObjects)
67 deleteBuffers(framebufferObject);
68
69 [EAGLContext setCurrentContext:nil];
70 [m_eaglContext release];
71}
72
73void QIOSContext::deleteBuffers(const FramebufferObject &framebufferObject)
74{
75 if (framebufferObject.handle)
76 glDeleteFramebuffers(1, &framebufferObject.handle);
77 if (framebufferObject.colorRenderbuffer)
78 glDeleteRenderbuffers(1, &framebufferObject.colorRenderbuffer);
79 if (framebufferObject.depthRenderbuffer)
80 glDeleteRenderbuffers(1, &framebufferObject.depthRenderbuffer);
81}
82
84{
85 return m_format;
86}
87
88#define QT_IOS_GL_STATUS_CASE(val) case val: return QLatin1StringView(#val)
89
90static QString fboStatusString(GLenum status)
91{
92 switch (status) {
93 QT_IOS_GL_STATUS_CASE(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
94 QT_IOS_GL_STATUS_CASE(GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS);
95 QT_IOS_GL_STATUS_CASE(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT);
96 QT_IOS_GL_STATUS_CASE(GL_FRAMEBUFFER_UNSUPPORTED);
97 default:
98 return QString(QStringLiteral("unknown status: %x")).arg(status);
99 }
100}
101
102#define Q_ASSERT_IS_GL_SURFACE(surface)
103 Q_ASSERT(surface && (surface->surface()->surfaceType() == QSurface::OpenGLSurface))
104
105bool QIOSContext::makeCurrent(QPlatformSurface *surface)
106{
107 Q_ASSERT_IS_GL_SURFACE(surface);
108
109 if (!verifyGraphicsHardwareAvailability())
110 return false;
111
112 [EAGLContext setCurrentContext:m_eaglContext];
113
114 // For offscreen surfaces we don't prepare a default FBO
115 if (surface->surface()->surfaceClass() == QSurface::Offscreen)
116 return true;
117
118 Q_ASSERT(surface->surface()->surfaceClass() == QSurface::Window);
119 FramebufferObject &framebufferObject = backingFramebufferObjectFor(surface);
120
121 if (!framebufferObject.handle) {
122 // Set up an FBO for the window if it hasn't been created yet
123 glGenFramebuffers(1, &framebufferObject.handle);
124 glBindFramebuffer(GL_FRAMEBUFFER, framebufferObject.handle);
125
126 glGenRenderbuffers(1, &framebufferObject.colorRenderbuffer);
127 glBindRenderbuffer(GL_RENDERBUFFER, framebufferObject.colorRenderbuffer);
128 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
129 framebufferObject.colorRenderbuffer);
130
131 if (m_format.depthBufferSize() > 0 || m_format.stencilBufferSize() > 0) {
132 glGenRenderbuffers(1, &framebufferObject.depthRenderbuffer);
133 glBindRenderbuffer(GL_RENDERBUFFER, framebufferObject.depthRenderbuffer);
134 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
135 framebufferObject.depthRenderbuffer);
136
137 if (m_format.stencilBufferSize() > 0)
138 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
139 framebufferObject.depthRenderbuffer);
140 }
141 } else {
142 glBindFramebuffer(GL_FRAMEBUFFER, framebufferObject.handle);
143 }
144
145 if (needsRenderbufferResize(surface)) {
146 // Ensure that the FBO's buffers match the size of the layer
147 CAEAGLLayer *layer = static_cast<QIOSWindow *>(surface)->eaglLayer();
148 qCDebug(lcQpaGLContext, "Reallocating renderbuffer storage - current: %dx%d, layer: %gx%g",
149 framebufferObject.renderbufferWidth, framebufferObject.renderbufferHeight,
150 layer.frame.size.width * layer.contentsScale, layer.frame.size.height * layer.contentsScale);
151
152 glBindRenderbuffer(GL_RENDERBUFFER, framebufferObject.colorRenderbuffer);
153 [m_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];
154
155 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &framebufferObject.renderbufferWidth);
156 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &framebufferObject.renderbufferHeight);
157
158 if (framebufferObject.depthRenderbuffer) {
159 glBindRenderbuffer(GL_RENDERBUFFER, framebufferObject.depthRenderbuffer);
160
161 // FIXME: Support more fine grained control over depth/stencil buffer sizes
162 if (m_format.stencilBufferSize() > 0)
163 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES,
164 framebufferObject.renderbufferWidth, framebufferObject.renderbufferHeight);
165 else
166 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16,
167 framebufferObject.renderbufferWidth, framebufferObject.renderbufferHeight);
168 }
169
170 framebufferObject.isComplete = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
171
172 if (!framebufferObject.isComplete) {
173 qCWarning(lcQpaGLContext, "QIOSContext failed to make complete framebuffer object (%s)",
174 qPrintable(fboStatusString(glCheckFramebufferStatus(GL_FRAMEBUFFER))));
175 }
176 }
177
178 return framebufferObject.isComplete;
179}
180
182{
183 [EAGLContext setCurrentContext:nil];
184}
185
186void QIOSContext::swapBuffers(QPlatformSurface *surface)
187{
188 Q_ASSERT_IS_GL_SURFACE(surface);
189
190 if (!verifyGraphicsHardwareAvailability())
191 return;
192
193 if (surface->surface()->surfaceClass() == QSurface::Offscreen)
194 return; // Nothing to do
195
196 FramebufferObject &framebufferObject = backingFramebufferObjectFor(surface);
197 if (!framebufferObject.isComplete) {
198 qCWarning(lcQpaGLContext, "swapBuffers on incomplete framebuffer object (%s). Skipping flush",
199 qPrintable(fboStatusString(glCheckFramebufferStatus(GL_FRAMEBUFFER))));
200 return;
201 }
202
203 if (needsRenderbufferResize(surface)) {
204 qCWarning(lcQpaGLContext, "CAEAGLLayer was resized between makeCurrent and swapBuffers, skipping flush");
205 return;
206 }
207
208 [EAGLContext setCurrentContext:m_eaglContext];
209 glBindRenderbuffer(GL_RENDERBUFFER, framebufferObject.colorRenderbuffer);
210 [m_eaglContext presentRenderbuffer:GL_RENDERBUFFER];
211}
212
213QIOSContext::FramebufferObject &QIOSContext::backingFramebufferObjectFor(QPlatformSurface *surface) const
214{
215 // We keep track of default-FBOs in the root context of a share-group. This assumes
216 // that the contexts form a tree, where leaf nodes are always destroyed before their
217 // parents. If that assumption (based on the current implementation) doesn't hold we
218 // should probably use QOpenGLMultiGroupSharedResource to track the shared default-FBOs.
219 if (m_sharedContext)
220 return m_sharedContext->backingFramebufferObjectFor(surface);
221
222 if (!m_framebufferObjects.contains(surface)) {
223 // We're about to create a new FBO, make sure it's cleaned up as well
224 connect(static_cast<QIOSWindow *>(surface), SIGNAL(destroyed(QObject*)), this, SLOT(windowDestroyed(QObject*)));
225 }
226
227 return m_framebufferObjects[surface];
228}
229
230GLuint QIOSContext::defaultFramebufferObject(QPlatformSurface *surface) const
231{
232 if (surface->surface()->surfaceClass() == QSurface::Offscreen) {
233 // Binding and rendering to the zero-FBO on iOS seems to be
234 // no-ops, so we can safely return 0 here, even if it's not
235 // really a valid FBO on iOS.
236 return 0;
237 }
238
239 FramebufferObject &framebufferObject = backingFramebufferObjectFor(surface);
240 Q_ASSERT_X(framebufferObject.handle, "QIOSContext", "can't resolve default FBO before makeCurrent");
241
242 return framebufferObject.handle;
243}
244
245bool QIOSContext::needsRenderbufferResize(QPlatformSurface *surface) const
246{
247 Q_ASSERT(surface->surface()->surfaceClass() == QSurface::Window);
248
249 FramebufferObject &framebufferObject = backingFramebufferObjectFor(surface);
250 CAEAGLLayer *layer = static_cast<QIOSWindow *>(surface)->eaglLayer();
251
252 if (framebufferObject.renderbufferWidth != (layer.frame.size.width * layer.contentsScale))
253 return true;
254
255 if (framebufferObject.renderbufferHeight != (layer.frame.size.height * layer.contentsScale))
256 return true;
257
258 return false;
259}
260
261bool QIOSContext::verifyGraphicsHardwareAvailability()
262{
263 // Per the iOS OpenGL ES Programming Guide, background apps may not execute commands on the
264 // graphics hardware. Specifically: "In your app delegate’s applicationDidEnterBackground:
265 // method, your app may want to delete some of its OpenGL ES objects to make memory and
266 // resources available to the foreground app. Call the glFinish function to ensure that
267 // the resources are removed immediately. After your app exits its applicationDidEnterBackground:
268 // method, it must not make any new OpenGL ES calls. If it makes an OpenGL ES call, it is
269 // terminated by iOS.".
270 static bool applicationBackgrounded = QGuiApplication::applicationState() == Qt::ApplicationSuspended;
271
272 static dispatch_once_t onceToken = 0;
273 dispatch_once(&onceToken, ^{
274 QIOSApplicationState *applicationState = &QIOSIntegration::instance()->applicationState;
275 connect(applicationState, &QIOSApplicationState::applicationStateWillChange,
276 [](Qt::ApplicationState oldState, Qt::ApplicationState newState) {
277 Q_UNUSED(oldState);
278 if (applicationBackgrounded && newState != Qt::ApplicationSuspended) {
279 qCDebug(lcQpaGLContext) << "app no longer backgrounded, rendering enabled";
280 applicationBackgrounded = false;
281 }
282 }
283 );
284 connect(applicationState, &QIOSApplicationState::applicationStateDidChange,
285 [](Qt::ApplicationState oldState, Qt::ApplicationState newState) {
286 Q_UNUSED(oldState);
287 if (newState != Qt::ApplicationSuspended)
288 return;
289
290 qCDebug(lcQpaGLContext) << "app backgrounded, rendering disabled";
291 applicationBackgrounded = true;
292
293 // By the time we receive this signal the application has moved into
294 // Qt::ApplactionStateSuspended, and all windows have been obscured,
295 // which should stop all rendering. If there's still an active GL context,
296 // we follow Apple's advice and call glFinish before making it inactive.
297 if (QOpenGLContext *currentContext = QOpenGLContext::currentContext()) {
298 qCWarning(lcQpaGLContext) << "explicitly glFinishing and deactivating" << currentContext;
299 glFinish();
300 currentContext->doneCurrent();
301 }
302 }
303 );
304 });
305
306 if (applicationBackgrounded)
307 qCWarning(lcQpaGLContext, "OpenGL ES calls are not allowed while an application is backgrounded");
308
309 return !applicationBackgrounded;
310}
311
312void QIOSContext::windowDestroyed(QObject *object)
313{
314 QIOSWindow *window = static_cast<QIOSWindow *>(object);
315 if (!m_framebufferObjects.contains(window))
316 return;
317
318 qCDebug(lcQpaGLContext) << object << "destroyed, deleting corresponding FBO";
319
320 EAGLContext *originalContext = [EAGLContext currentContext];
321 [EAGLContext setCurrentContext:m_eaglContext];
322 deleteBuffers(m_framebufferObjects[window]);
323 m_framebufferObjects.remove(window);
324 [EAGLContext setCurrentContext:originalContext];
325}
326
328{
329 return QFunctionPointer(dlsym(RTLD_DEFAULT, functionName));
330}
331
332bool QIOSContext::isValid() const
333{
334 return m_eaglContext;
335}
336
338{
339 return m_sharedContext;
340}
341
342QT_END_NAMESPACE
343
344#include "moc_qioscontext.cpp"
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
static QIOSIntegration * instance()
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
#define Q_ASSERT_IS_GL_SURFACE(surface)
#define QT_IOS_GL_STATUS_CASE(val)
static QString fboStatusString(GLenum status)