From 855d75319c607feb33bd3cdc66aec18f301c805e Mon Sep 17 00:00:00 2001 From: Stefan Karschti Date: Mon, 29 Apr 2024 22:08:15 +0300 Subject: [PATCH] Synchronous rendering on Metal when annotation views are visible (#2334) --- platform/ios/src/MLNMapView+Impl.h | 4 ++++ platform/ios/src/MLNMapView+Metal.h | 3 +++ platform/ios/src/MLNMapView+Metal.mm | 10 ++++++++-- platform/ios/src/MLNMapView.mm | 12 ++++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/platform/ios/src/MLNMapView+Impl.h b/platform/ios/src/MLNMapView+Impl.h index 04a991977..717818bbf 100644 --- a/platform/ios/src/MLNMapView+Impl.h +++ b/platform/ios/src/MLNMapView+Impl.h @@ -57,6 +57,10 @@ public: void render(); virtual MLNBackendResource getObject() = 0; + + virtual void setSynchronous(bool value) {}; + virtual bool getSynchronous() const { return false; }; + // mbgl::MapObserver implementation void onCameraWillChange(mbgl::MapObserver::CameraChangeMode) override; diff --git a/platform/ios/src/MLNMapView+Metal.h b/platform/ios/src/MLNMapView+Metal.h index 17b26146c..5c43fe853 100644 --- a/platform/ios/src/MLNMapView+Metal.h +++ b/platform/ios/src/MLNMapView+Metal.h @@ -50,6 +50,9 @@ public: MLNBackendResource getObject() override; // End implementation of MLNMapViewImpl + void setSynchronous(bool value) override { synchronousFrame = value; }; + bool getSynchronous() const override { return synchronousFrame; }; private: bool presentsWithTransaction = false; + bool synchronousFrame = false; }; diff --git a/platform/ios/src/MLNMapView+Metal.mm b/platform/ios/src/MLNMapView+Metal.mm index 45498f92d..99deb858a 100644 --- a/platform/ios/src/MLNMapView+Metal.mm +++ b/platform/ios/src/MLNMapView+Metal.mm @@ -84,9 +84,15 @@ public: } [commandBuffer commit]; - // Un-comment for synchronous, which can help troubleshoot rendering problems, + // Synchronous rendering can help troubleshoot rendering problems, // particularly those related to resource tracking and multiple queued buffers. - //[commandBuffer waitUntilCompleted]; + // The conditional logic for synchronous rendering is commanded from MLNMapView.updateAnnotationViews: + // `_mbglView->setSynchronous(haveVisibleAnnotationViews);` + // and fixes the desynchronization of UIView annotation when the map is rendered on the Metal backend + // Issue: https://github.com/maplibre/maplibre-native/issues/2053 + if (backend.getSynchronous()) { + [commandBuffer waitUntilCompleted]; + } commandBuffer = nil; commandBufferPtr.reset(); diff --git a/platform/ios/src/MLNMapView.mm b/platform/ios/src/MLNMapView.mm index 6e4152c93..01f41e79d 100644 --- a/platform/ios/src/MLNMapView.mm +++ b/platform/ios/src/MLNMapView.mm @@ -6917,6 +6917,9 @@ static void *windowScreenContext = &windowScreenContext; NSMutableArray *offscreenAnnotations = [self.annotations mutableCopy]; [offscreenAnnotations removeObjectsInArray:visibleAnnotations]; + // Dynamic flag used to drive the backend's synchronous frame rendering (see comment below) + bool haveVisibleAnnotationViews = false; + // Update the center of visible annotation views for (id annotation in visibleAnnotations) { @@ -6953,6 +6956,7 @@ static void *windowScreenContext = &windowScreenContext; if (annotationView) { annotationView.center = MLNPointRounded([self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self]); + haveVisibleAnnotationViews = true; } } @@ -7000,6 +7004,14 @@ static void *windowScreenContext = &windowScreenContext; } } } + + // Switch synchronous frame rendering on if we have visible annotation views. + // Only implemented on Metal, just a stub on OpenGL. + // This logic is needed to fix the desynchronization of UIView annotations when the map is rendered on the Metal backend with asynchronous frames. + // Root cause: the Map transform is updated immediately and the annotations are positioned on the screen using it, + // while the map rendered surface catches up when the frame is complete. + // Issue: https://github.com/maplibre/maplibre-native/issues/2053 + _mbglView->setSynchronous(haveVisibleAnnotationViews); } - (BOOL)hasAnAnchoredAnnotationCalloutView