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