diff options
Diffstat (limited to 'platform/ios/src/MGLMapView+OpenGL.mm')
-rw-r--r-- | platform/ios/src/MGLMapView+OpenGL.mm | 253 |
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; +} |