diff options
author | Konstantin Käfer <mail@kkaefer.com> | 2019-05-22 12:12:01 +0200 |
---|---|---|
committer | Konstantin Käfer <mail@kkaefer.com> | 2019-05-29 18:27:42 +0200 |
commit | 9eb43a587b264524bf9d43d8a578859b63ef2593 (patch) | |
tree | 3e47753d30765cc0bd1b3eb146c7af06efc18e15 /platform/ios/src/MGLMapView.mm | |
parent | 05e194614fff1527f812c73aa0f28d4205908013 (diff) | |
download | qtlocation-mapboxgl-9eb43a587b264524bf9d43d8a578859b63ef2593.tar.gz |
[ios,macos] refactor MGLMapViewImpl
Diffstat (limited to 'platform/ios/src/MGLMapView.mm')
-rw-r--r-- | platform/ios/src/MGLMapView.mm | 413 |
1 files changed, 61 insertions, 352 deletions
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index e6323d8bc1..a304bfddfc 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -1,7 +1,5 @@ #import "MGLMapView_Private.h" - -#import <GLKit/GLKit.h> -#import <OpenGLES/EAGL.h> +#import "MGLMapView+Impl.h" #include <mbgl/map/map.hpp> #include <mbgl/map/map_options.hpp> @@ -17,8 +15,6 @@ #include <mbgl/style/transition_options.hpp> #include <mbgl/style/layers/custom_layer.hpp> #include <mbgl/renderer/renderer.hpp> -#import <mbgl/gl/renderer_backend.hpp> -#import <mbgl/gl/renderable_resource.hpp> #include <mbgl/math/wrap.hpp> #include <mbgl/util/exception.hpp> #include <mbgl/util/geo.hpp> @@ -78,7 +74,6 @@ #include <map> #include <unordered_set> -class MBGLView; class MGLAnnotationContext; const MGLMapViewDecelerationRate MGLMapViewDecelerationRateNormal = UIScrollViewDecelerationRateNormal; @@ -192,15 +187,12 @@ public: #pragma mark - Private - @interface MGLMapView () <UIGestureRecognizerDelegate, - GLKViewDelegate, MGLLocationManagerDelegate, MGLSMCalloutViewDelegate, MGLCalloutViewDelegate, MGLMultiPointDelegate, MGLAnnotationImageDelegate> -@property (nonatomic, readwrite) EAGLContext *context; -@property (nonatomic) GLKView *glView; @property (nonatomic) UIImageView *glSnapshotView; @property (nonatomic) NSMutableArray<NSLayoutConstraint *> *scaleBarConstraints; @@ -275,7 +267,7 @@ public: @implementation MGLMapView { mbgl::Map *_mbglMap; - MBGLView *_mbglView; + std::unique_ptr<MGLMapViewImpl> _mbglView; std::unique_ptr<MGLRenderFrontend> _rendererFrontend; BOOL _opaque; @@ -328,9 +320,6 @@ public: CFTimeInterval _frameCounterStartTime; NSInteger _frameCount; CFTimeInterval _frameDurations; - - BOOL _atLeastiOS_12_2_0; - } #pragma mark - Setup & Teardown - @@ -441,13 +430,6 @@ public: - (void)commonInit { _opaque = NO; - _atLeastiOS_12_2_0 = [NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){12,2,0}]; - - BOOL background = [UIApplication sharedApplication].applicationState == UIApplicationStateBackground; - if (!background) - { - [self createGLView]; - } // setup accessibility // @@ -463,8 +445,13 @@ public: self.preferredFramesPerSecond = MGLMapViewPreferredFramesPerSecondDefault; // setup mbgl view - _mbglView = new MBGLView(self); - + _mbglView = MGLMapViewImpl::Create(self); + + BOOL background = [UIApplication sharedApplication].applicationState == UIApplicationStateBackground; + if (!background) + { + _mbglView->createView(); + } // Delete the pre-offline ambient cache at ~/Library/Caches/cache.db. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSString *fileCachePath = [paths.firstObject stringByAppendingPathComponent:@"cache.db"]; @@ -473,9 +460,9 @@ public: // setup mbgl map MGLRendererConfiguration *config = [MGLRendererConfiguration currentConfiguration]; - auto renderer = std::make_unique<mbgl::Renderer>(*_mbglView, config.scaleFactor, config.cacheDir, config.localFontFamilyName); + auto renderer = std::make_unique<mbgl::Renderer>(_mbglView->getRendererBackend(), config.scaleFactor, config.cacheDir, config.localFontFamilyName); BOOL enableCrossSourceCollisions = !config.perSourceCollisions; - _rendererFrontend = std::make_unique<MGLRenderFrontend>(std::move(renderer), self, *_mbglView); + _rendererFrontend = std::make_unique<MGLRenderFrontend>(std::move(renderer), self, _mbglView->getRendererBackend()); mbgl::MapOptions mapOptions; mapOptions.withMapMode(mbgl::MapMode::Continuous) @@ -636,7 +623,7 @@ public: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; - // As of 3.7.5, we intentionally do not listen for `UIApplicationWillResignActiveNotification` or call `sleepGL:` in response to it, as doing + // As of 3.7.5, we intentionally do not listen for `UIApplicationWillResignActiveNotification` or call `pauseRendering:` in response to it, as doing // so causes a loop when asking for location permission. See: https://github.com/mapbox/mapbox-gl-native/issues/11225 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; @@ -677,47 +664,6 @@ public: static_cast<uint32_t>(size.height) }; } -- (mbgl::Size)framebufferSize -{ - return { static_cast<uint32_t>(self.glView.drawableWidth), - static_cast<uint32_t>(self.glView.drawableHeight) }; -} - -+ (GLKView *)GLKViewWithFrame:(CGRect)frame context:(EAGLContext *)context opaque:(BOOL)opaque -{ - GLKView *glView = [[GLKView alloc] initWithFrame:frame context:context]; - glView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - glView.enableSetNeedsDisplay = NO; - glView.drawableStencilFormat = GLKViewDrawableStencilFormat8; - glView.drawableDepthFormat = GLKViewDrawableDepthFormat16; - glView.contentScaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)] ? [[UIScreen mainScreen] nativeScale] : [[UIScreen mainScreen] scale]; - glView.layer.opaque = opaque; - glView.contentMode = UIViewContentModeCenter; - - return glView; -} - -- (void)createGLView -{ - if (_context) return; - - // create context - // - _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; - MGLAssert(_context, @"Failed to create OpenGL ES context."); - - // create GL view - // - _glView = [MGLMapView GLKViewWithFrame:self.bounds context:_context opaque:_opaque]; - _glView.delegate = self; - - CAEAGLLayer *eaglLayer = MGL_OBJC_DYNAMIC_CAST(_glView.layer, CAEAGLLayer); - eaglLayer.presentsWithTransaction = NO; - - [_glView bindDrawable]; - [self insertSubview:_glView atIndex:0]; -} - - (UIImage *)compassImage { UIImage *scaleImage = [MGLMapView resourceImageNamed:@"Compass"]; @@ -763,8 +709,7 @@ public: delete _mbglMap; _mbglMap = nullptr; - delete _mbglView; - _mbglView = nullptr; + _mbglView.reset(); _rendererFrontend.reset(); } @@ -789,11 +734,6 @@ public: [self destroyCoreObjects]; - if ([[EAGLContext currentContext] isEqual:_context]) - { - [EAGLContext setCurrentContext:nil]; - } - [self.compassViewConstraints removeAllObjects]; self.compassViewConstraints = nil; @@ -977,11 +917,13 @@ public: - (void)setOpaque:(BOOL)opaque { - _glView.layer.opaque = _opaque = opaque; + _opaque = opaque; + if (_mbglView) { + _mbglView->setOpaque(opaque); + } } -// This is the delegate of the GLKView object's display call. -- (void)glkView:(__unused GLKView *)view drawInRect:(__unused CGRect)rect +- (void)renderSync { if ( ! self.dormant && _rendererFrontend) { @@ -1138,27 +1080,7 @@ public: [self updateAnnotationViews]; [self updateCalloutView]; -#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 (self.enablePresentsWithTransaction && _atLeastiOS_12_2_0) - { - CFTimeInterval before = CACurrentMediaTime(); - [self.glView display]; - CFTimeInterval after = CACurrentMediaTime(); - - if (after-before >= 1.0) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self emergencyRecreateGL]; - }); - } - } - else -#endif - { - [self.glView display]; - } + _mbglView->display(); } if (self.experimental_enableFrameRateMeasurement) @@ -1183,7 +1105,7 @@ public: } } -- (void)setNeedsGLDisplay +- (void)setNeedsRerender { MGLAssertIsMainThread(); @@ -1198,7 +1120,7 @@ public: { [self validateDisplayLink]; self.dormant = YES; - [self.glView deleteDrawable]; + _mbglView->deleteView(); } [self destroyCoreObjects]; @@ -1284,53 +1206,10 @@ public: // If the map is visible, change the layer property too if (self.window) { - CAEAGLLayer *eaglLayer = MGL_OBJC_DYNAMIC_CAST(_glView.layer, CAEAGLLayer); - eaglLayer.presentsWithTransaction = _enablePresentsWithTransaction; + _mbglView->setPresentsWithTransaction(_enablePresentsWithTransaction); } } -#ifdef MGL_RECREATE_GL_IN_AN_EMERGENCY -// See https://github.com/mapbox/mapbox-gl-native/issues/14232 -- (void)emergencyRecreateGL { - MGLLogError(@"Rendering took too long - creating GL views"); - - CAEAGLLayer *eaglLayer = MGL_OBJC_DYNAMIC_CAST(_glView.layer, CAEAGLLayer); - eaglLayer.presentsWithTransaction = NO; - - [self sleepGL:nil]; - - // Just performing a sleepGL/wakeGL pair isn't sufficient - in this case - // we can still get errors when calling bindDrawable. Here we completely - // recreate the GLKView - - [self.userLocationAnnotationView removeFromSuperview]; - [_glView removeFromSuperview]; - - _glView = [MGLMapView GLKViewWithFrame:self.bounds context:_context opaque:_opaque]; - _glView.delegate = self; - - [self insertSubview:_glView atIndex:0]; - - if (self.annotationContainerView) - { - [_glView insertSubview:self.annotationContainerView atIndex:0]; - } - - [self updateUserLocationAnnotationView]; - - // Do not bind...yet - - if (self.window) { - [self wakeGL:nil]; - CAEAGLLayer *eaglLayer = MGL_OBJC_DYNAMIC_CAST(_glView.layer, CAEAGLLayer); - eaglLayer.presentsWithTransaction = self.enablePresentsWithTransaction; - } - else { - MGLLogDebug(@"No window - skipping wakeGL"); - } -} -#endif - - (void)willMoveToWindow:(UIWindow *)newWindow { [super willMoveToWindow:newWindow]; [self refreshSupportedInterfaceOrientationsWithWindow:newWindow]; @@ -1341,9 +1220,8 @@ public: // In iOS 12.2, CAEAGLLayer.presentsWithTransaction can cause dramatic // slow down. The exact cause of this is unknown, but this work around // appears to lessen the effects. - CAEAGLLayer *eaglLayer = MGL_OBJC_DYNAMIC_CAST(_glView.layer, CAEAGLLayer); - eaglLayer.presentsWithTransaction = NO; - + _mbglView->setPresentsWithTransaction(NO); + // Moved from didMoveToWindow [self validateDisplayLink]; } @@ -1356,9 +1234,8 @@ public: if (self.window) { // See above comment - CAEAGLLayer *eaglLayer = MGL_OBJC_DYNAMIC_CAST(_glView.layer, CAEAGLLayer); - eaglLayer.presentsWithTransaction = self.enablePresentsWithTransaction; - + _mbglView->setPresentsWithTransaction(self.enablePresentsWithTransaction); + [self validateDisplayLink]; } } @@ -1475,7 +1352,7 @@ public: return; } - self.lastSnapshotImage = self.glView.snapshot; + self.lastSnapshotImage = _mbglView->snapshot(); // For OpenGL this calls glFinish as recommended in // https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/ImplementingaMultitasking-awareOpenGLESApplication/ImplementingaMultitasking-awareOpenGLESApplication.html#//apple_ref/doc/uid/TP40008793-CH5-SW1 @@ -1488,22 +1365,26 @@ public: - (void)didEnterBackground:(NSNotification *)notification { - [self sleepGL:notification]; + [self pauseRendering:notification]; } - (void)willEnterForeground:(NSNotification *)notification { - // Do nothing, currently if wakeGL is called here it's a no-op. + // Do nothing, currently if resumeRendering is called here it's a no-op. } - (void)didBecomeActive:(NSNotification *)notification { - [self wakeGL:notification]; + [self resumeRendering:notification]; self.lastSnapshotImage = nil; } #pragma mark - GL / display link wake/sleep +- (EAGLContext *)context { + return _mbglView->getEAGLContext(); +} + - (BOOL)supportsBackgroundRendering { // If this view targets an external display, such as AirPlay or CarPlay, we @@ -1514,9 +1395,7 @@ public: return (self.window.screen != [UIScreen mainScreen]); } - - -- (void)sleepGL:(__unused NSNotification *)notification +- (void)pauseRendering:(__unused NSNotification *)notification { // If this view targets an external display, such as AirPlay or CarPlay, we // can safely continue to render OpenGL content without tripping @@ -1553,10 +1432,10 @@ public: if ( ! self.glSnapshotView) { - self.glSnapshotView = [[UIImageView alloc] initWithFrame:self.glView.frame]; - self.glSnapshotView.autoresizingMask = self.glView.autoresizingMask; + self.glSnapshotView = [[UIImageView alloc] initWithFrame: _mbglView->getView().frame]; + self.glSnapshotView.autoresizingMask = _mbglView->getView().autoresizingMask; self.glSnapshotView.contentMode = UIViewContentModeCenter; - [self insertSubview:self.glSnapshotView aboveSubview:self.glView]; + [self insertSubview:self.glSnapshotView aboveSubview:_mbglView->getView()]; } self.glSnapshotView.image = self.lastSnapshotImage; @@ -1570,11 +1449,11 @@ public: [self.glSnapshotView addSubview:snapshotTint]; } - [self.glView deleteDrawable]; + _mbglView->deleteView(); } } -- (void)wakeGL:(__unused NSNotification *)notification +- (void)resumeRendering:(__unused NSNotification *)notification { MGLLogInfo(@"Entering foreground."); MGLAssertIsMainThread(); @@ -1583,14 +1462,12 @@ public: { self.dormant = NO; - [self createGLView]; + _mbglView->createView(); self.glSnapshotView.hidden = YES; [self.glSnapshotView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; - [self.glView bindDrawable]; - _displayLink.paused = NO; [self validateLocationServices]; @@ -4286,7 +4163,7 @@ public: newAnnotationContainerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; newAnnotationContainerView.contentMode = UIViewContentModeCenter; [newAnnotationContainerView addSubviews:annotationViews]; - [_glView insertSubview:newAnnotationContainerView atIndex:0]; + [_mbglView->getView() insertSubview:newAnnotationContainerView atIndex:0]; self.annotationContainerView = newAnnotationContainerView; [self updatePresentsWithTransaction]; @@ -4992,7 +4869,7 @@ public: // Remember, calloutView can be nil here. [calloutView presentCalloutFromRect:calloutPositioningRect - inView:self.glView + inView:_mbglView->getView() constrainedToRect:constrainedRect animated:animateSelection]; @@ -6269,7 +6146,7 @@ public: } } -- (void)didFinishLoadingStyle { +- (void)mapViewDidFinishLoadingStyle { if (!_mbglMap) { return; @@ -6282,6 +6159,22 @@ public: } } +- (void)sourceDidChange:(MGLSource *)source { + // no-op: we only show attribution after tapping the info button, so there's no + // interactive update needed. +} + +- (void)didFailToLoadImage:(NSString *)imageName { + + if ([self.delegate respondsToSelector:@selector(mapView:didFailToLoadImage:)]) { + MGLImage *imageToLoad = [self.delegate mapView:self didFailToLoadImage:imageName]; + if (imageToLoad) { + auto image = [imageToLoad mgl_styleImageWithIdentifier:imageName]; + _mbglMap->getStyle().addImage(std::move(image)); + } + } +} + - (void)updateUserLocationAnnotationView { [self updateUserLocationAnnotationViewAnimatedWithDuration:0]; @@ -6497,7 +6390,7 @@ public: if ( ! annotationView.superview) { - [self.glView addSubview:annotationView]; + [_mbglView->getView() addSubview:annotationView]; // Prevents the view from sliding in from the origin. annotationView.center = userPoint; } @@ -6765,190 +6658,6 @@ public: return _annotationViewReuseQueueByIdentifier[identifier]; } -class MBGLView; - -class MBGLMapViewRenderable final : public mbgl::gl::RenderableResource { -public: - MBGLMapViewRenderable(MBGLView& backend_) : backend(backend_) { - } - - void bind() override; - -private: - MBGLView& backend; -}; - -class MBGLView : public mbgl::gl::RendererBackend, - public mbgl::gfx::Renderable, - public mbgl::MapObserver { -public: - MBGLView(MGLMapView* nativeView_) - : mbgl::gl::RendererBackend(mbgl::gfx::ContextMode::Unique), - mbgl::gfx::Renderable(nativeView_.framebufferSize, - std::make_unique<MBGLMapViewRenderable>(*this)), - nativeView(nativeView_) { - } - - /// 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 updateAssumedState() override { - assumeFramebufferBinding(ImplicitFramebufferBinding); - assumeViewport(0, 0, nativeView.framebufferSize); - } - - void restoreFramebufferBinding() { - 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. - [nativeView.glView bindDrawable]; - updateAssumedState(); - } else { - // Our framebuffer is still bound, but the viewport might have changed. - setViewport(0, 0, nativeView.framebufferSize); - } - } - - void onCameraWillChange(mbgl::MapObserver::CameraChangeMode mode) override { - bool animated = mode == mbgl::MapObserver::CameraChangeMode::Animated; - [nativeView cameraWillChangeAnimated:animated]; - } - - void onCameraIsChanging() override { - [nativeView cameraIsChanging]; - } - - void onCameraDidChange(mbgl::MapObserver::CameraChangeMode mode) override { - bool animated = mode == mbgl::MapObserver::CameraChangeMode::Animated; - [nativeView cameraDidChangeAnimated:animated]; - } - - void onWillStartLoadingMap() override { - [nativeView mapViewWillStartLoadingMap]; - } - - void onDidFinishLoadingMap() override { - [nativeView mapViewDidFinishLoadingMap]; - } - - void onDidFailLoadingMap(mbgl::MapLoadError mapError, const std::string& what) override { - NSString *description; - MGLErrorCode code; - switch (mapError) { - case mbgl::MapLoadError::StyleParseError: - code = MGLErrorCodeParseStyleFailed; - description = NSLocalizedStringWithDefaultValue(@"PARSE_STYLE_FAILED_DESC", nil, nil, @"The map failed to load because the style is corrupted.", @"User-friendly error description"); - break; - case mbgl::MapLoadError::StyleLoadError: - code = MGLErrorCodeLoadStyleFailed; - description = NSLocalizedStringWithDefaultValue(@"LOAD_STYLE_FAILED_DESC", nil, nil, @"The map failed to load because the style can't be loaded.", @"User-friendly error description"); - break; - case mbgl::MapLoadError::NotFoundError: - code = MGLErrorCodeNotFound; - description = NSLocalizedStringWithDefaultValue(@"STYLE_NOT_FOUND_DESC", nil, nil, @"The map failed to load because the style can’t be found or is incompatible.", @"User-friendly error description"); - break; - default: - code = MGLErrorCodeUnknown; - description = NSLocalizedStringWithDefaultValue(@"LOAD_MAP_FAILED_DESC", nil, nil, @"The map failed to load because an unknown error occurred.", @"User-friendly error description"); - } - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: description, - NSLocalizedFailureReasonErrorKey: @(what.c_str()), - }; - NSError *error = [NSError errorWithDomain:MGLErrorDomain code:code userInfo:userInfo]; - [nativeView mapViewDidFailLoadingMapWithError:error]; - } - - void onWillStartRenderingFrame() override { - [nativeView mapViewWillStartRenderingFrame]; - } - - void onDidFinishRenderingFrame(mbgl::MapObserver::RenderMode mode) override { - bool fullyRendered = mode == mbgl::MapObserver::RenderMode::Full; - [nativeView mapViewDidFinishRenderingFrameFullyRendered:fullyRendered]; - } - - void onWillStartRenderingMap() override { - [nativeView mapViewWillStartRenderingMap]; - } - - void onDidFinishRenderingMap(mbgl::MapObserver::RenderMode mode) override { - bool fullyRendered = mode == mbgl::MapObserver::RenderMode::Full; - [nativeView mapViewDidFinishRenderingMapFullyRendered:fullyRendered]; - } - - void onDidBecomeIdle() override { - [nativeView mapViewDidBecomeIdle]; - } - - void onDidFinishLoadingStyle() override { - [nativeView didFinishLoadingStyle]; - } - - void onStyleImageMissing(const std::string& imageIdentifier) override { - NSString *imageName = [NSString stringWithUTF8String:imageIdentifier.c_str()]; - - if ([nativeView.delegate respondsToSelector:@selector(mapView:didFailToLoadImage:)]) { - UIImage *imageToLoad = [nativeView.delegate mapView:nativeView didFailToLoadImage:imageName]; - - if (imageToLoad) { - auto image = [imageToLoad mgl_styleImageWithIdentifier:imageName]; - nativeView.mbglMap.getStyle().addImage(std::move(image)); - } - - } - } - - mbgl::gl::ProcAddress getExtensionFunctionPointer(const char* name) override { - static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengles")); - if (!framework) { - throw std::runtime_error("Failed to load OpenGL framework."); - } - - CFStringRef str = - CFStringCreateWithCString(kCFAllocatorDefault, name, kCFStringEncodingASCII); - void* symbol = CFBundleGetFunctionPointerForName(framework, str); - CFRelease(str); - - return reinterpret_cast<mbgl::gl::ProcAddress>(symbol); - } - - mbgl::gfx::Renderable& getDefaultRenderable() override { - return *this; - } - - void activate() override - { - if (activationCount++) - { - return; - } - - [EAGLContext setCurrentContext:nativeView.context]; - } - - void deactivate() override - { - if (--activationCount) - { - return; - } - - [EAGLContext setCurrentContext:nil]; - } - -private: - __weak MGLMapView* nativeView = nullptr; - - NSUInteger activationCount = 0; -}; - -void MBGLMapViewRenderable::bind() { - backend.restoreFramebufferBinding(); -} - @end #pragma mark - IBAdditions methods |