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 Q_ASSERT_X(framebufferObject.isComplete, "QIOSContext", "swapBuffers on incomplete FBO");
198
199 if (needsRenderbufferResize(surface)) {
200 qCWarning(lcQpaGLContext, "CAEAGLLayer was resized between makeCurrent and swapBuffers, skipping flush");
201 return;
202 }
203
204 [EAGLContext setCurrentContext:m_eaglContext];
205 glBindRenderbuffer(GL_RENDERBUFFER, framebufferObject.colorRenderbuffer);
206 [m_eaglContext presentRenderbuffer:GL_RENDERBUFFER];
207}
208
209QIOSContext::FramebufferObject &QIOSContext::backingFramebufferObjectFor(QPlatformSurface *surface) const
210{
211 // We keep track of default-FBOs in the root context of a share-group. This assumes
212 // that the contexts form a tree, where leaf nodes are always destroyed before their
213 // parents. If that assumption (based on the current implementation) doesn't hold we
214 // should probably use QOpenGLMultiGroupSharedResource to track the shared default-FBOs.
215 if (m_sharedContext)
216 return m_sharedContext->backingFramebufferObjectFor(surface);
217
218 if (!m_framebufferObjects.contains(surface)) {
219 // We're about to create a new FBO, make sure it's cleaned up as well
220 connect(static_cast<QIOSWindow *>(surface), SIGNAL(destroyed(QObject*)), this, SLOT(windowDestroyed(QObject*)));
221 }
222
223 return m_framebufferObjects[surface];
224}
225
226GLuint QIOSContext::defaultFramebufferObject(QPlatformSurface *surface) const
227{
228 if (surface->surface()->surfaceClass() == QSurface::Offscreen) {
229 // Binding and rendering to the zero-FBO on iOS seems to be
230 // no-ops, so we can safely return 0 here, even if it's not
231 // really a valid FBO on iOS.
232 return 0;
233 }
234
235 FramebufferObject &framebufferObject = backingFramebufferObjectFor(surface);
236 Q_ASSERT_X(framebufferObject.handle, "QIOSContext", "can't resolve default FBO before makeCurrent");
237
238 return framebufferObject.handle;
239}
240
241bool QIOSContext::needsRenderbufferResize(QPlatformSurface *surface) const
242{
243 Q_ASSERT(surface->surface()->surfaceClass() == QSurface::Window);
244
245 FramebufferObject &framebufferObject = backingFramebufferObjectFor(surface);
246 CAEAGLLayer *layer = static_cast<QIOSWindow *>(surface)->eaglLayer();
247
248 if (framebufferObject.renderbufferWidth != (layer.frame.size.width * layer.contentsScale))
249 return true;
250
251 if (framebufferObject.renderbufferHeight != (layer.frame.size.height * layer.contentsScale))
252 return true;
253
254 return false;
255}
256
257bool QIOSContext::verifyGraphicsHardwareAvailability()
258{
259 // Per the iOS OpenGL ES Programming Guide, background apps may not execute commands on the
260 // graphics hardware. Specifically: "In your app delegate’s applicationDidEnterBackground:
261 // method, your app may want to delete some of its OpenGL ES objects to make memory and
262 // resources available to the foreground app. Call the glFinish function to ensure that
263 // the resources are removed immediately. After your app exits its applicationDidEnterBackground:
264 // method, it must not make any new OpenGL ES calls. If it makes an OpenGL ES call, it is
265 // terminated by iOS.".
266 static bool applicationBackgrounded = QGuiApplication::applicationState() == Qt::ApplicationSuspended;
267
268 static dispatch_once_t onceToken = 0;
269 dispatch_once(&onceToken, ^{
270 QIOSApplicationState *applicationState = &QIOSIntegration::instance()->applicationState;
271 connect(applicationState, &QIOSApplicationState::applicationStateWillChange,
272 [](Qt::ApplicationState oldState, Qt::ApplicationState newState) {
273 Q_UNUSED(oldState);
274 if (applicationBackgrounded && newState != Qt::ApplicationSuspended) {
275 qCDebug(lcQpaGLContext) << "app no longer backgrounded, rendering enabled";
276 applicationBackgrounded = false;
277 }
278 }
279 );
280 connect(applicationState, &QIOSApplicationState::applicationStateDidChange,
281 [](Qt::ApplicationState oldState, Qt::ApplicationState newState) {
282 Q_UNUSED(oldState);
283 if (newState != Qt::ApplicationSuspended)
284 return;
285
286 qCDebug(lcQpaGLContext) << "app backgrounded, rendering disabled";
287 applicationBackgrounded = true;
288
289 // By the time we receive this signal the application has moved into
290 // Qt::ApplactionStateSuspended, and all windows have been obscured,
291 // which should stop all rendering. If there's still an active GL context,
292 // we follow Apple's advice and call glFinish before making it inactive.
293 if (QOpenGLContext *currentContext = QOpenGLContext::currentContext()) {
294 qCWarning(lcQpaGLContext) << "explicitly glFinishing and deactivating" << currentContext;
295 glFinish();
296 currentContext->doneCurrent();
297 }
298 }
299 );
300 });
301
302 if (applicationBackgrounded)
303 qCWarning(lcQpaGLContext, "OpenGL ES calls are not allowed while an application is backgrounded");
304
305 return !applicationBackgrounded;
306}
307
308void QIOSContext::windowDestroyed(QObject *object)
309{
310 QIOSWindow *window = static_cast<QIOSWindow *>(object);
311 if (!m_framebufferObjects.contains(window))
312 return;
313
314 qCDebug(lcQpaGLContext) << object << "destroyed, deleting corresponding FBO";
315
316 EAGLContext *originalContext = [EAGLContext currentContext];
317 [EAGLContext setCurrentContext:m_eaglContext];
318 deleteBuffers(m_framebufferObjects[window]);
319 m_framebufferObjects.remove(window);
320 [EAGLContext setCurrentContext:originalContext];
321}
322
324{
325 return QFunctionPointer(dlsym(RTLD_DEFAULT, functionName));
326}
327
328bool QIOSContext::isValid() const
329{
330 return m_eaglContext;
331}
332
334{
335 return m_sharedContext;
336}
337
338QT_END_NAMESPACE
339
340#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)