From 3ea11fe32cc743e25de07cd9e5ea9a871e00fd7e Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 12 Nov 2023 22:08:20 +0100 Subject: [PATCH] Monomorphize projection call and avoid Num.clamp. Saves another 22% CPU cycles on the UI thread for my test-case. --- lib/src/geo/crs.dart | 11 +++-- .../layer/polygon_layer/polygon_layer.dart | 32 +++------------ lib/src/layer/polyline_layer.dart | 28 ++----------- lib/src/misc/offsets.dart | 40 +++++++++++++++++++ 4 files changed, 56 insertions(+), 55 deletions(-) create mode 100644 lib/src/misc/offsets.dart diff --git a/lib/src/geo/crs.dart b/lib/src/geo/crs.dart index d4877f0f6..94770ca17 100644 --- a/lib/src/geo/crs.dart +++ b/lib/src/geo/crs.dart @@ -322,6 +322,7 @@ abstract class Projection { const Projection(this.bounds); + @nonVirtual Point project(LatLng latlng) { final (x, y) = projectXY(latlng); return Point(x, y); @@ -329,6 +330,7 @@ abstract class Projection { (double, double) projectXY(LatLng latlng); + @nonVirtual LatLng unproject(Point point) => unprojectXY(point.x.toDouble(), point.y.toDouble()); LatLng unprojectXY(double x, double y); @@ -365,7 +367,7 @@ class SphericalMercator extends Projection { const SphericalMercator() : super(_bounds); static double projectLat(double latitude) { - final lat = latitude.clamp(-maxLatitude, maxLatitude); + final lat = _clampSym(latitude, maxLatitude); final sin = math.sin(lat * math.pi / 180); return r / 2 * math.log((1 + sin) / (1 - sin)); @@ -445,5 +447,8 @@ class _Transformation { ); } -double _inclusiveLat(double value) => value.clamp(-90, 90); -double _inclusiveLng(double value) => value.clamp(-180, 180); +// Num.clamp is slow due to virtual function overhead. +double _clampSym(double value, double limit) => + value < -limit ? -limit : (value > limit ? limit : value); +double _inclusiveLat(double value) => _clampSym(value, 90); +double _inclusiveLng(double value) => _clampSym(value, 180); diff --git a/lib/src/layer/polygon_layer/polygon_layer.dart b/lib/src/layer/polygon_layer/polygon_layer.dart index 5f397b36d..414856d7a 100644 --- a/lib/src/layer/polygon_layer/polygon_layer.dart +++ b/lib/src/layer/polygon_layer/polygon_layer.dart @@ -5,6 +5,7 @@ import 'package:flutter_map/src/geo/latlng_bounds.dart'; import 'package:flutter_map/src/layer/general/mobile_layer_transformer.dart'; import 'package:flutter_map/src/layer/polygon_layer/label.dart'; import 'package:flutter_map/src/map/camera/camera.dart'; +import 'package:flutter_map/src/misc/offsets.dart'; import 'package:flutter_map/src/misc/point_extensions.dart'; import 'package:latlong2/latlong.dart' hide Path; // conflict with Path from UI @@ -164,31 +165,8 @@ class PolygonPainter extends CustomPainter { ({Offset min, Offset max}) getBounds(Offset origin, Polygon polygon) { final bbox = polygon.boundingBox; return ( - min: getOffset(origin, bbox.southWest), - max: getOffset(origin, bbox.northEast), - ); - } - - Offset getOffset(Offset origin, LatLng point) { - // Critically create as little garbage as possible. This is called on every frame. - final projected = map.project(point, map.zoom); - return Offset(projected.x - origin.dx, projected.y - origin.dy); - } - - List getOffsets(Offset origin, List points) { - final crs = map.crs; - final zoomScale = crs.scale(map.zoom); - - final ox = -origin.dx; - final oy = -origin.dy; - - return List.generate( - points.length, - (index) { - final (x, y) = crs.latLngToXY(points[index], zoomScale); - return Offset(x + ox, y + oy); - }, - growable: false, + min: getOffset(map, origin, bbox.southWest), + max: getOffset(map, origin, bbox.northEast), ); } @@ -234,7 +212,7 @@ class PolygonPainter extends CustomPainter { if (polygon.points.isEmpty) { continue; } - final offsets = getOffsets(origin, polygon.points); + final offsets = getOffsets(map, origin, polygon.points); // The hash is based on the polygons visual properties. If the hash from // the current and the previous polygon no longer match, we need to flush @@ -264,7 +242,7 @@ class PolygonPainter extends CustomPainter { final holeOffsetsList = List>.generate( holePointsList.length, - (i) => getOffsets(origin, holePointsList[i]), + (i) => getOffsets(map, origin, holePointsList[i]), growable: false, ); diff --git a/lib/src/layer/polyline_layer.dart b/lib/src/layer/polyline_layer.dart index ce8de6029..9e71f60df 100644 --- a/lib/src/layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer.dart @@ -5,6 +5,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_map/src/geo/latlng_bounds.dart'; import 'package:flutter_map/src/layer/general/mobile_layer_transformer.dart'; import 'package:flutter_map/src/map/camera/camera.dart'; +import 'package:flutter_map/src/misc/offsets.dart'; import 'package:flutter_map/src/misc/point_extensions.dart'; import 'package:latlong2/latlong.dart'; @@ -98,29 +99,6 @@ class PolylinePainter extends CustomPainter { int? _hash; - Offset getOffset(Offset origin, LatLng point) { - // Critically create as little garbage as possible. This is called on every frame. - final projected = map.project(point, map.zoom); - return Offset(projected.x - origin.dx, projected.y - origin.dy); - } - - List getOffsets(Offset origin, List points) { - final crs = map.crs; - final zoomScale = crs.scale(map.zoom); - - final ox = -origin.dx; - final oy = -origin.dy; - - return List.generate( - points.length, - (index) { - final (x, y) = crs.latLngToXY(points[index], zoomScale); - return Offset(x + ox, y + oy); - }, - growable: false, - ); - } - @override void paint(Canvas canvas, Size size) { final rect = Offset.zero & size; @@ -163,7 +141,7 @@ class PolylinePainter extends CustomPainter { final origin = map.project(map.center).toOffset() - map.size.toOffset() / 2; for (final polyline in polylines) { - final offsets = getOffsets(origin, polyline.points); + final offsets = getOffsets(map, origin, polyline.points); if (offsets.isEmpty) { continue; } @@ -185,7 +163,7 @@ class PolylinePainter extends CustomPainter { polyline.strokeWidth, 180, ); - final delta = firstOffset - getOffset(origin, r); + final delta = firstOffset - getOffset(map, origin, r); strokeWidth = delta.distance; } else { diff --git a/lib/src/misc/offsets.dart b/lib/src/misc/offsets.dart new file mode 100644 index 000000000..c399a07ae --- /dev/null +++ b/lib/src/misc/offsets.dart @@ -0,0 +1,40 @@ +import 'dart:ui'; + +import 'package:flutter_map/src/geo/crs.dart'; +import 'package:flutter_map/src/map/camera/camera.dart'; +import 'package:latlong2/latlong.dart'; + +Offset getOffset(MapCamera camera, Offset origin, LatLng point) { + final crs = camera.crs; + final zoomScale = crs.scale(camera.zoom); + final (x, y) = crs.latLngToXY(point, zoomScale); + return Offset(x - origin.dx, y - origin.dy); +} + +List getOffsets(MapCamera camera, Offset origin, List points) { + // Critically create as little garbage as possible. This is called on every frame. + final crs = camera.crs; + final zoomScale = crs.scale(camera.zoom); + + final ox = -origin.dx; + final oy = -origin.dy; + final len = points.length; + + // Optimization: monomorphize the Epsg3857-case to save the virtual function overhead. + if (crs is Epsg3857) { + final Epsg3857 epsg3857 = crs; + final v = List.filled(len, Offset.zero); + for (int i = 0; i < len; ++i) { + final (x, y) = epsg3857.latLngToXY(points[i], zoomScale); + v[i] = Offset(x + ox, y + oy); + } + return v; + } + + final v = List.filled(len, Offset.zero); + for (int i = 0; i < len; ++i) { + final (x, y) = crs.latLngToXY(points[i], zoomScale); + v[i] = Offset(x + ox, y + oy); + } + return v; +}