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