diff --git a/platform/ios/src/MLNMapView+Impl.h b/platform/ios/src/MLNMapView+Impl.h index 04a9919773f..717818bbf55 100644 --- a/platform/ios/src/MLNMapView+Impl.h +++ b/platform/ios/src/MLNMapView+Impl.h @@ -57,6 +57,10 @@ class MLNMapViewImpl : public mbgl::MapObserver { 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 17b26146ca3..5c43fe853bc 100644 --- a/platform/ios/src/MLNMapView+Metal.h +++ b/platform/ios/src/MLNMapView+Metal.h @@ -50,6 +50,9 @@ class MLNMapViewMetalImpl final : public MLNMapViewImpl, 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 45498f92d62..99deb858af5 100644 --- a/platform/ios/src/MLNMapView+Metal.mm +++ b/platform/ios/src/MLNMapView+Metal.mm @@ -84,9 +84,15 @@ void swap() override { } [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 6e4152c934d..01f41e79d5b 100644 --- a/platform/ios/src/MLNMapView.mm +++ b/platform/ios/src/MLNMapView.mm @@ -6917,6 +6917,9 @@ - (void)updateAnnotationViews 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 @@ - (void)updateAnnotationViews if (annotationView) { annotationView.center = MLNPointRounded([self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self]); + haveVisibleAnnotationViews = true; } } @@ -7000,6 +7004,14 @@ - (void)updateAnnotationViews } } } + + // 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