summaryrefslogtreecommitdiff
path: root/platform/ios/src/MGLMapView+OpenGL.mm
diff options
context:
space:
mode:
Diffstat (limited to 'platform/ios/src/MGLMapView+OpenGL.mm')
-rw-r--r--platform/ios/src/MGLMapView+OpenGL.mm253
1 files changed, 253 insertions, 0 deletions
diff --git a/platform/ios/src/MGLMapView+OpenGL.mm b/platform/ios/src/MGLMapView+OpenGL.mm
new file mode 100644
index 0000000000..cd9489d96f
--- /dev/null
+++ b/platform/ios/src/MGLMapView+OpenGL.mm
@@ -0,0 +1,253 @@
+#import "MGLFoundation_Private.h"
+#import "MGLLoggingConfiguration_Private.h"
+#import "MGLMapView+OpenGL.h"
+
+#include <mbgl/gl/renderable_resource.hpp>
+
+#import <GLKit/GLKit.h>
+#import <OpenGLES/EAGL.h>
+#import <QuartzCore/CAEAGLLayer.h>
+
+@interface MGLMapViewImplDelegate : NSObject <GLKViewDelegate>
+@end
+
+@implementation MGLMapViewImplDelegate {
+ MGLMapViewOpenGLImpl* _impl;
+}
+
+- (instancetype)initWithImpl:(MGLMapViewOpenGLImpl*)impl {
+ if (self = [super init]) {
+ _impl = impl;
+ }
+ return self;
+}
+
+- (void)glkView:(nonnull GLKView*)view drawInRect:(CGRect)rect {
+ _impl->render();
+}
+
+@end
+
+class MGLMapViewOpenGLRenderableResource final : public mbgl::gl::RenderableResource {
+public:
+ MGLMapViewOpenGLRenderableResource(MGLMapViewOpenGLImpl& backend_)
+ : backend(backend_),
+ delegate([[MGLMapViewImplDelegate alloc] initWithImpl:&backend]),
+ atLeastiOS_12_2_0([NSProcessInfo.processInfo
+ isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){ 12, 2, 0 }]) {
+ }
+
+ void bind() override {
+ backend.restoreFramebufferBinding();
+ }
+
+ mbgl::Size framebufferSize() {
+ assert(glView);
+ return { static_cast<uint32_t>(glView.drawableWidth),
+ static_cast<uint32_t>(glView.drawableHeight) };
+ }
+
+private:
+ MGLMapViewOpenGLImpl& backend;
+
+public:
+ MGLMapViewImplDelegate* delegate = nil;
+ GLKView *glView = nil;
+ EAGLContext *context = nil;
+ const bool atLeastiOS_12_2_0;
+
+ // We count how often the context was activated/deactivated so that we can truly deactivate it
+ // after the activation count drops to 0.
+ NSUInteger activationCount = 0;
+};
+
+MGLMapViewOpenGLImpl::MGLMapViewOpenGLImpl(MGLMapView* nativeView_)
+ : MGLMapViewImpl(nativeView_),
+ mbgl::gl::RendererBackend(mbgl::gfx::ContextMode::Unique),
+ mbgl::gfx::Renderable({ 0, 0 }, std::make_unique<MGLMapViewOpenGLRenderableResource>(*this)) {
+}
+
+MGLMapViewOpenGLImpl::~MGLMapViewOpenGLImpl() {
+ auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
+ if (resource.context && [[EAGLContext currentContext] isEqual:resource.context]) {
+ [EAGLContext setCurrentContext:nil];
+ }
+}
+
+void MGLMapViewOpenGLImpl::setOpaque(const bool opaque) {
+ auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
+ resource.glView.opaque = opaque;
+ resource.glView.layer.opaque = opaque;
+}
+
+void MGLMapViewOpenGLImpl::setPresentsWithTransaction(const bool value) {
+ auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
+ CAEAGLLayer* eaglLayer = MGL_OBJC_DYNAMIC_CAST(resource.glView.layer, CAEAGLLayer);
+ eaglLayer.presentsWithTransaction = value;
+}
+
+void MGLMapViewOpenGLImpl::display() {
+ auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
+#ifdef MGL_RECREATE_GL_IN_AN_EMERGENCY
+ // See https://github.com/mapbox/mapbox-gl-native/issues/14232
+ // glClear can be blocked for 1 second. This code is an "escape hatch",
+ // an attempt to detect this situation and rebuild the GL views.
+ if (mapView.enablePresentsWithTransaction && resource.atLeastiOS_12_2_0) {
+ CFTimeInterval before = CACurrentMediaTime();
+ [resource.glView display];
+ CFTimeInterval after = CACurrentMediaTime();
+
+ if (after - before >= 1.0) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ emergencyRecreateGL();
+ });
+ }
+ }
+ else
+#endif
+ {
+ [resource.glView display];
+ }
+}
+
+void MGLMapViewOpenGLImpl::createView() {
+ auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
+ if (resource.glView) {
+ return;
+ }
+
+ if (!resource.context) {
+ resource.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+ assert(resource.context);
+ }
+
+ resource.glView = [[GLKView alloc] initWithFrame:mapView.bounds context:resource.context];
+ resource.glView.delegate = resource.delegate;
+ resource.glView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+ resource.glView.contentScaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)]
+ ? [[UIScreen mainScreen] nativeScale]
+ : [[UIScreen mainScreen] scale];
+ resource.glView.contentMode = UIViewContentModeCenter;
+ resource.glView.drawableStencilFormat = GLKViewDrawableStencilFormat8;
+ resource.glView.drawableDepthFormat = GLKViewDrawableDepthFormat16;
+ resource.glView.opaque = mapView.opaque;
+ resource.glView.layer.opaque = mapView.opaque;
+ resource.glView.enableSetNeedsDisplay = NO;
+ CAEAGLLayer* eaglLayer = MGL_OBJC_DYNAMIC_CAST(resource.glView.layer, CAEAGLLayer);
+ eaglLayer.presentsWithTransaction = NO;
+
+ [mapView insertSubview:resource.glView atIndex:0];
+ size = resource.framebufferSize();
+}
+
+UIView* MGLMapViewOpenGLImpl::getView() {
+ auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
+ return resource.glView;
+}
+
+void MGLMapViewOpenGLImpl::deleteView() {
+ auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
+ [resource.glView deleteDrawable];
+}
+
+#ifdef MGL_RECREATE_GL_IN_AN_EMERGENCY
+// See https://github.com/mapbox/mapbox-gl-native/issues/14232
+void MGLMapViewOpenGLImpl::emergencyRecreateGL() {
+ auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
+ MGLLogError(@"Rendering took too long - creating GL views");
+
+ CAEAGLLayer* eaglLayer = MGL_OBJC_DYNAMIC_CAST(resource.glView.layer, CAEAGLLayer);
+ eaglLayer.presentsWithTransaction = NO;
+
+ [mapView pauseRendering:nil];
+
+ // Just performing a pauseRendering:/resumeRendering: pair isn't sufficient - in this case
+ // we can still get errors when calling bindDrawable. Here we completely
+ // recreate the GLKView
+
+ [mapView.userLocationAnnotationView removeFromSuperview];
+ [resource.glView removeFromSuperview];
+
+ // Recreate the view
+ resource.glView = nil;
+ createView();
+
+ if (mapView.annotationContainerView) {
+ [resource.glView insertSubview:mapView.annotationContainerView atIndex:0];
+ }
+
+ [mapView updateUserLocationAnnotationView];
+
+ // Do not bind...yet
+
+ if (mapView.window) {
+ [mapView resumeRendering:nil];
+ eaglLayer = MGL_OBJC_DYNAMIC_CAST(resource.glView.layer, CAEAGLLayer);
+ eaglLayer.presentsWithTransaction = mapView.enablePresentsWithTransaction;
+ } else {
+ MGLLogDebug(@"No window - skipping resumeRendering");
+ }
+}
+#endif
+
+mbgl::gl::ProcAddress MGLMapViewOpenGLImpl::getExtensionFunctionPointer(const char* name) {
+ static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengles"));
+ if (!framework) {
+ throw std::runtime_error("Failed to load OpenGL framework.");
+ }
+
+ return reinterpret_cast<mbgl::gl::ProcAddress>(CFBundleGetFunctionPointerForName(
+ framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name]));
+}
+
+void MGLMapViewOpenGLImpl::activate() {
+ auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
+ if (resource.activationCount++) {
+ return;
+ }
+
+ [EAGLContext setCurrentContext:resource.context];
+}
+
+void MGLMapViewOpenGLImpl::deactivate() {
+ auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
+ if (--resource.activationCount) {
+ return;
+ }
+
+ [EAGLContext setCurrentContext:nil];
+}
+
+/// This function is called before we start rendering, when iOS invokes our rendering method.
+/// iOS already sets the correct framebuffer and viewport for us, so we need to update the
+/// context state with the anticipated values.
+void MGLMapViewOpenGLImpl::updateAssumedState() {
+ auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
+ assumeFramebufferBinding(ImplicitFramebufferBinding);
+ assumeViewport(0, 0, resource.framebufferSize());
+}
+
+void MGLMapViewOpenGLImpl::restoreFramebufferBinding() {
+ auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
+ if (!implicitFramebufferBound()) {
+ // Something modified our state, and we need to bind the original drawable again.
+ // Doing this also sets the viewport to the full framebuffer.
+ // Note that in reality, iOS does not use the Framebuffer 0 (it's typically 1), and we
+ // only use this is a placeholder value.
+ [resource.glView bindDrawable];
+ updateAssumedState();
+ } else {
+ // Our framebuffer is still bound, but the viewport might have changed.
+ setViewport(0, 0, resource.framebufferSize());
+ }
+}
+
+UIImage* MGLMapViewOpenGLImpl::snapshot() {
+ auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
+ return resource.glView.snapshot;
+}
+
+EAGLContext* MGLMapViewOpenGLImpl::getEAGLContext() {
+ auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
+ return resource.context;
+}