diff --git a/CMakeLists.txt b/CMakeLists.txt
index a776d95ac..adfbadd6c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1239,6 +1239,7 @@ if(MLN_WITH_VULKAN)
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/uniform_buffer.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/upload_pass.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/vertex_attribute.hpp
+ ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/vertex_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/shader_group.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/shader_program.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/background.hpp
@@ -1282,6 +1283,7 @@ if(MLN_WITH_VULKAN)
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/uniform_buffer.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/upload_pass.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/vertex_attribute.cpp
+ ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/vertex_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/shader_program.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/background.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/circle.cpp
diff --git a/include/mbgl/gfx/rendering_stats.hpp b/include/mbgl/gfx/rendering_stats.hpp
index 22918e28a..8ee33894e 100644
--- a/include/mbgl/gfx/rendering_stats.hpp
+++ b/include/mbgl/gfx/rendering_stats.hpp
@@ -2,14 +2,27 @@
#include
#include
+#include
+#include
namespace mbgl {
+
+namespace style {
+class Style;
+class SymbolLayer;
+} // namespace style
+
namespace gfx {
struct RenderingStats {
RenderingStats() = default;
bool isZero() const;
+ /// Frame CPU encoding time (milliseconds)
+ double encodingTime = 0.0;
+ /// Frame CPU rendering time (milliseconds)
+ double renderingTime = 0.0;
+
/// Number of frames rendered
int numFrames = 0;
/// Number of draw calls (`glDrawElements`, `drawIndexedPrimitives`, etc.) executed during the most recent frame
@@ -41,25 +54,40 @@ struct RenderingStats {
/// Number of active buffers
int numBuffers = 0;
+ /// Number of active offscreen frame buffers
int numFrameBuffers = 0;
+ /// Number of active index buffers
int numIndexBuffers = 0;
+ /// Sum of index buffers update sizes
std::size_t indexUpdateBytes = 0;
+ /// Number of active vertex buffers
int numVertexBuffers = 0;
+ /// Sum of vertex buffers update sizes
std::size_t vertexUpdateBytes = 0;
+ /// Number of active uniform buffers
int numUniformBuffers = 0;
+ /// Number of times a uniform buffer is updated
int numUniformUpdates = 0;
+ /// Sum of uniform buffers update sizes
std::size_t uniformUpdateBytes = 0;
+ /// Total texture memory
int memTextures = 0;
+ /// Total buffer memory
int memBuffers = 0;
+ /// Total index buffer memory
int memIndexBuffers = 0;
+ /// Total vertex buffer memory
int memVertexBuffers = 0;
+ /// Total uniform buffer memory
int memUniformBuffers = 0;
+ /// Number of stencil buffer clears
int stencilClears = 0;
+ /// Number of stencil buffer updates
int stencilUpdates = 0;
RenderingStats& operator+=(const RenderingStats&);
@@ -69,5 +97,38 @@ struct RenderingStats {
#endif
};
+class RenderingStatsView final {
+public:
+ struct Options {
+ float updateInterval = 0.25f;
+ bool verbose = false;
+ Color textColor = Color::red();
+ float textSize = 4.0f;
+ };
+
+ RenderingStatsView() = default;
+ RenderingStatsView(const Options& options_)
+ : options(options_) {}
+ ~RenderingStatsView() = default;
+
+ void create(style::Style& style);
+ void destroy(style::Style& style);
+
+ mbgl::style::SymbolLayer* getLayer(style::Style& style);
+
+ void update(style::Style& style, const gfx::RenderingStats& stats);
+
+protected:
+ const std::string layerID = "rendering-stats";
+ const std::string sourceID = layerID + "-source";
+
+ Options options;
+
+ double lastUpdate = 0.0;
+ uint32_t frameCount = 0;
+ double encodingTime = 0.0;
+ double renderingTime = 0.0;
+};
+
} // namespace gfx
} // namespace mbgl
diff --git a/include/mbgl/gl/uniform_buffer_gl.hpp b/include/mbgl/gl/uniform_buffer_gl.hpp
index 3a205cfbe..475007f40 100644
--- a/include/mbgl/gl/uniform_buffer_gl.hpp
+++ b/include/mbgl/gl/uniform_buffer_gl.hpp
@@ -11,7 +11,7 @@ class UniformBufferGL final : public gfx::UniformBuffer {
UniformBufferGL(const UniformBufferGL&);
public:
- UniformBufferGL(const void* data, std::size_t size_, IBufferAllocator& allocator);
+ UniformBufferGL(Context& context, const void* data, std::size_t size_, IBufferAllocator& allocator);
~UniformBufferGL() override;
UniformBufferGL(UniformBufferGL&& rhs) noexcept;
@@ -27,6 +27,8 @@ public:
void update(const void* data, std::size_t dataSize) override;
private:
+ Context& context;
+
// unique id used for debugging and profiling purposes
// localID should not be used as unique id because a const buffer pool is managed using IBufferAllocator
// Currently unique IDs for constant buffers are only used when Tracy profiling is enabled
diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp
index e68853e04..22972e27d 100644
--- a/include/mbgl/map/map.hpp
+++ b/include/mbgl/map/map.hpp
@@ -145,6 +145,9 @@ public:
void setDebug(MapDebugOptions);
MapDebugOptions getDebug() const;
+ bool isRenderingStatsViewEnabled() const;
+ void enableRenderingStatsView(bool value);
+
bool isFullyLoaded() const;
void dumpDebugLogs() const;
diff --git a/include/mbgl/map/map_observer.hpp b/include/mbgl/map/map_observer.hpp
index 4c9170fc1..f6055571d 100644
--- a/include/mbgl/map/map_observer.hpp
+++ b/include/mbgl/map/map_observer.hpp
@@ -7,6 +7,7 @@
#include
#include
#include
+#include
#include
#include
@@ -47,8 +48,7 @@ public:
RenderMode mode;
bool needsRepaint; // In continous mode, shows that there are ongoig transitions.
bool placementChanged;
- double frameEncodingTime;
- double frameRenderingTime;
+ gfx::RenderingStats renderingStats;
};
virtual void onCameraWillChange(CameraChangeMode) {}
@@ -58,7 +58,7 @@ public:
virtual void onDidFinishLoadingMap() {}
virtual void onDidFailLoadingMap(MapLoadError, const std::string&) {}
virtual void onWillStartRenderingFrame() {}
- virtual void onDidFinishRenderingFrame(RenderFrameStatus) {}
+ virtual void onDidFinishRenderingFrame(const RenderFrameStatus&) {}
virtual void onWillStartRenderingMap() {}
virtual void onDidFinishRenderingMap(RenderMode) {}
virtual void onDidFinishLoadingStyle() {}
diff --git a/include/mbgl/renderer/renderer_observer.hpp b/include/mbgl/renderer/renderer_observer.hpp
index 5df817afa..5fac1a2a1 100644
--- a/include/mbgl/renderer/renderer_observer.hpp
+++ b/include/mbgl/renderer/renderer_observer.hpp
@@ -2,6 +2,7 @@
#include
#include
+#include
#include
#include
#include
@@ -51,6 +52,13 @@ public:
onDidFinishRenderingFrame(mode, repaint, placementChanged);
}
+ virtual void onDidFinishRenderingFrame(RenderMode mode,
+ bool repaint,
+ bool placementChanged,
+ const gfx::RenderingStats& stats) {
+ onDidFinishRenderingFrame(mode, repaint, placementChanged, stats.encodingTime, stats.renderingTime);
+ }
+
/// Final frame
virtual void onDidFinishRenderingMap() {}
diff --git a/include/mbgl/style/layers/symbol_layer.hpp b/include/mbgl/style/layers/symbol_layer.hpp
index 4d565cb9b..27485c806 100644
--- a/include/mbgl/style/layers/symbol_layer.hpp
+++ b/include/mbgl/style/layers/symbol_layer.hpp
@@ -89,6 +89,10 @@ public:
const PropertyValue& getSymbolPlacement() const;
void setSymbolPlacement(const PropertyValue&);
+ static PropertyValue getDefaultSymbolScreenSpace();
+ const PropertyValue& getSymbolScreenSpace() const;
+ void setSymbolScreenSpace(const PropertyValue&);
+
static PropertyValue getDefaultSymbolSortKey();
const PropertyValue& getSymbolSortKey() const;
void setSymbolSortKey(const PropertyValue&);
diff --git a/include/mbgl/vulkan/context.hpp b/include/mbgl/vulkan/context.hpp
index 9a75e5e96..8a3d06091 100644
--- a/include/mbgl/vulkan/context.hpp
+++ b/include/mbgl/vulkan/context.hpp
@@ -70,7 +70,7 @@ public:
const mbgl::unordered_map& additionalDefines);
/// Called at the end of a frame.
- void performCleanup() override {}
+ void performCleanup() override;
void reduceMemoryUsage() override {}
gfx::UniqueDrawableBuilder createDrawableBuilder(std::string name) override;
diff --git a/include/mbgl/vulkan/vertex_buffer_resource.hpp b/include/mbgl/vulkan/vertex_buffer_resource.hpp
index 5b2b85910..90380220e 100644
--- a/include/mbgl/vulkan/vertex_buffer_resource.hpp
+++ b/include/mbgl/vulkan/vertex_buffer_resource.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include
#include
#include
@@ -9,12 +10,10 @@ namespace vulkan {
class VertexBufferResource : public gfx::VertexBufferResource {
public:
- VertexBufferResource() noexcept = delete;
- VertexBufferResource(BufferResource&& buffer_) noexcept
- : buffer(std::move(buffer_)) {}
+ VertexBufferResource(BufferResource&& buffer_) noexcept;
VertexBufferResource(VertexBufferResource&& other) noexcept
: buffer(std::move(other.buffer)) {}
- ~VertexBufferResource() noexcept override = default;
+ ~VertexBufferResource() noexcept override;
std::size_t getSizeInBytes() const noexcept { return buffer.getSizeInBytes(); }
const void* contents() const noexcept { return buffer.contents(); }
diff --git a/platform/android/MapLibreAndroid/src/cpp/CMakeLists.txt b/platform/android/MapLibreAndroid/src/cpp/CMakeLists.txt
index a1db3493a..c156aa756 100644
--- a/platform/android/MapLibreAndroid/src/cpp/CMakeLists.txt
+++ b/platform/android/MapLibreAndroid/src/cpp/CMakeLists.txt
@@ -122,6 +122,8 @@ add_library(maplibre SHARED
${PROJECT_SOURCE_DIR}/offline/offline_region_error.hpp
${PROJECT_SOURCE_DIR}/offline/offline_region_status.cpp
${PROJECT_SOURCE_DIR}/offline/offline_region_status.hpp
+ ${PROJECT_SOURCE_DIR}/rendering_stats.cpp
+ ${PROJECT_SOURCE_DIR}/rendering_stats.hpp
${PROJECT_SOURCE_DIR}/snapshotter/map_snapshot.cpp
${PROJECT_SOURCE_DIR}/snapshotter/map_snapshot.hpp
${PROJECT_SOURCE_DIR}/snapshotter/map_snapshotter.cpp
diff --git a/platform/android/MapLibreAndroid/src/cpp/android_renderer_frontend.cpp b/platform/android/MapLibreAndroid/src/cpp/android_renderer_frontend.cpp
index 0f5ddb662..988dff420 100644
--- a/platform/android/MapLibreAndroid/src/cpp/android_renderer_frontend.cpp
+++ b/platform/android/MapLibreAndroid/src/cpp/android_renderer_frontend.cpp
@@ -36,11 +36,10 @@ public:
void onDidFinishRenderingFrame(RenderMode mode,
bool repaintNeeded,
bool placementChanged,
- double frameEncodingTime,
- double frameRenderingTime) override {
+ const gfx::RenderingStats& stats) override {
void (RendererObserver::*f)(
- RenderMode, bool, bool, double, double) = &RendererObserver::onDidFinishRenderingFrame;
- delegate.invoke(f, mode, repaintNeeded, placementChanged, frameEncodingTime, frameRenderingTime);
+ RenderMode, bool, bool, const gfx::RenderingStats&) = &RendererObserver::onDidFinishRenderingFrame;
+ delegate.invoke(f, mode, repaintNeeded, placementChanged, stats);
}
void onDidFinishRenderingMap() override { delegate.invoke(&RendererObserver::onDidFinishRenderingMap); }
diff --git a/platform/android/MapLibreAndroid/src/cpp/jni_native.cpp b/platform/android/MapLibreAndroid/src/cpp/jni_native.cpp
index b84fd671c..b0a29b677 100644
--- a/platform/android/MapLibreAndroid/src/cpp/jni_native.cpp
+++ b/platform/android/MapLibreAndroid/src/cpp/jni_native.cpp
@@ -35,6 +35,7 @@
#include "maplibre.hpp"
#include "native_map_view.hpp"
#include "native_map_options.hpp"
+#include "rendering_stats.hpp"
#include "util/tile_server_options.hpp"
#ifndef MBGL_MODULE_OFFLINE_DISABLE
#include "offline/offline_manager.hpp"
@@ -111,6 +112,7 @@ void registerNatives(JavaVM* vm) {
MapRendererRunnable::registerNative(env);
NativeMapView::registerNative(env);
NativeMapOptions::registerNative(env);
+ RenderingStats::registerNative(env);
// Http
RegisterNativeHTTPRequest(env);
diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp
index 232c2652f..9bfb3037d 100644
--- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp
+++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp
@@ -40,6 +40,7 @@
#include "conversion/collection.hpp"
#include "style/conversion/filter.hpp"
#include "geojson/feature.hpp"
+#include "rendering_stats.hpp"
#include "android_renderer_frontend.hpp"
#include "attach_env.hpp"
@@ -187,20 +188,25 @@ void NativeMapView::onWillStartRenderingFrame() {
}
}
-void NativeMapView::onDidFinishRenderingFrame(MapObserver::RenderFrameStatus status) {
+void NativeMapView::onDidFinishRenderingFrame(const MapObserver::RenderFrameStatus& status) {
assert(vm != nullptr);
android::UniqueEnv _env = android::AttachEnv();
static auto& javaClass = jni::Class::Singleton(*_env);
- static auto onDidFinishRenderingFrame = javaClass.GetMethod(
+ static auto onDidFinishRenderingFrame = javaClass.GetMethod)>(
*_env, "onDidFinishRenderingFrame");
auto weakReference = javaPeer.get(*_env);
if (weakReference) {
+ if (!renderingStats) {
+ renderingStats = jni::NewGlobal(*_env, RenderingStats::Create(*_env));
+ }
+
+ RenderingStats::Update(*_env, renderingStats, status.renderingStats);
+
weakReference.Call(*_env,
onDidFinishRenderingFrame,
(jboolean)(status.mode != MapObserver::RenderMode::Partial),
- (jdouble)status.frameEncodingTime,
- (jdouble)status.frameRenderingTime);
+ renderingStats);
}
}
@@ -1272,6 +1278,16 @@ void NativeMapView::triggerRepaint(JNIEnv&) {
map->triggerRepaint();
}
+jni::jboolean NativeMapView::isRenderingStatsViewEnabled(JNIEnv&) {
+ assert(map);
+ return jni::jboolean(map->isRenderingStatsViewEnabled());
+}
+
+void NativeMapView::enableRenderingStatsView(JNIEnv&, jni::jboolean value) {
+ assert(map);
+ map->enableRenderingStatsView(value);
+}
+
// Static methods //
void NativeMapView::registerNative(jni::JNIEnv& env) {
@@ -1384,7 +1400,9 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
METHOD(&NativeMapView::getPrefetchZoomDelta, "nativeGetPrefetchZoomDelta"),
METHOD(&NativeMapView::setTileCacheEnabled, "nativeSetTileCacheEnabled"),
METHOD(&NativeMapView::getTileCacheEnabled, "nativeGetTileCacheEnabled"),
- METHOD(&NativeMapView::triggerRepaint, "nativeTriggerRepaint"));
+ METHOD(&NativeMapView::triggerRepaint, "nativeTriggerRepaint"),
+ METHOD(&NativeMapView::isRenderingStatsViewEnabled, "nativeIsRenderingStatsViewEnabled"),
+ METHOD(&NativeMapView::enableRenderingStatsView, "nativeEnableRenderingStatsView"));
}
void NativeMapView::onRegisterShaders(gfx::ShaderRegistry&) {};
diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp b/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp
index 6fff2e49c..bdfb9143c 100644
--- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp
+++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp
@@ -39,6 +39,7 @@ namespace android {
class AndroidRendererFrontend;
class FileSource;
class MapRenderer;
+class RenderingStats;
class NativeMapView : public MapObserver {
public:
@@ -62,7 +63,7 @@ public:
void onDidFinishLoadingMap() override;
void onDidFailLoadingMap(MapLoadError, const std::string&) override;
void onWillStartRenderingFrame() override;
- void onDidFinishRenderingFrame(MapObserver::RenderFrameStatus) override;
+ void onDidFinishRenderingFrame(const MapObserver::RenderFrameStatus&) override;
void onWillStartRenderingMap() override;
void onDidFinishRenderingMap(MapObserver::RenderMode) override;
void onDidBecomeIdle() override;
@@ -305,6 +306,9 @@ public:
void triggerRepaint(JNIEnv&);
+ jni::jboolean isRenderingStatsViewEnabled(JNIEnv&);
+ void enableRenderingStatsView(JNIEnv&, jni::jboolean);
+
// Shader compilation
void onRegisterShaders(mbgl::gfx::ShaderRegistry&) override;
void onPreCompileShader(mbgl::shaders::BuiltIn, mbgl::gfx::Backend::Type, const std::string&) override;
@@ -336,6 +340,8 @@ private:
float pixelRatio;
+ jni::Global> renderingStats;
+
// Minimum texture size according to OpenGL ES 2.0 specification.
int width = 64;
int height = 64;
diff --git a/platform/android/MapLibreAndroid/src/cpp/rendering_stats.cpp b/platform/android/MapLibreAndroid/src/cpp/rendering_stats.cpp
new file mode 100644
index 000000000..2cca9d4f1
--- /dev/null
+++ b/platform/android/MapLibreAndroid/src/cpp/rendering_stats.cpp
@@ -0,0 +1,61 @@
+#include "rendering_stats.hpp"
+
+namespace mbgl {
+namespace android {
+
+void RenderingStats::registerNative(jni::JNIEnv& env) {
+ jni::Class::Singleton(env);
+}
+
+jni::Local> RenderingStats::Create(jni::JNIEnv& env) {
+ auto& javaClass = jni::Class::Singleton(env);
+ auto constructor = javaClass.GetConstructor(env);
+ return javaClass.New(env, constructor);
+}
+
+void RenderingStats::Update(jni::JNIEnv& env,
+ jni::Object& javaObject,
+ const gfx::RenderingStats& stats) {
+ static auto& javaClass = jni::Class::Singleton(env);
+
+#define SetField(name, type) \
+ static auto name##Field = javaClass.GetField(env, #name); \
+ javaObject.Set(env, name##Field, static_cast(stats.name));
+
+ SetField(encodingTime, jni::jdouble);
+ SetField(renderingTime, jni::jdouble);
+ SetField(numFrames, jni::jint);
+ SetField(numDrawCalls, jni::jint);
+ SetField(totalDrawCalls, jni::jint);
+ SetField(numCreatedTextures, jni::jint);
+ SetField(numActiveTextures, jni::jint);
+ SetField(numTextureBindings, jni::jint);
+ SetField(numTextureUpdates, jni::jint);
+ SetField(textureUpdateBytes, jni::jlong);
+ SetField(totalBuffers, jni::jlong);
+ SetField(totalBufferObjs, jni::jlong);
+ SetField(bufferUpdates, jni::jlong);
+ SetField(bufferObjUpdates, jni::jlong);
+ SetField(bufferUpdateBytes, jni::jlong);
+ SetField(numBuffers, jni::jint);
+ SetField(numFrameBuffers, jni::jint);
+ SetField(numIndexBuffers, jni::jint);
+ SetField(indexUpdateBytes, jni::jlong);
+ SetField(numVertexBuffers, jni::jint);
+ SetField(vertexUpdateBytes, jni::jlong);
+ SetField(numUniformBuffers, jni::jint);
+ SetField(numUniformUpdates, jni::jint);
+ SetField(uniformUpdateBytes, jni::jlong);
+ SetField(memTextures, jni::jint);
+ SetField(memBuffers, jni::jint);
+ SetField(memIndexBuffers, jni::jint);
+ SetField(memVertexBuffers, jni::jint);
+ SetField(memUniformBuffers, jni::jint);
+ SetField(stencilClears, jni::jint);
+ SetField(stencilUpdates, jni::jint);
+
+#undef SetField
+}
+
+} // namespace android
+} // namespace mbgl
diff --git a/platform/android/MapLibreAndroid/src/cpp/rendering_stats.hpp b/platform/android/MapLibreAndroid/src/cpp/rendering_stats.hpp
new file mode 100644
index 000000000..6c452fa26
--- /dev/null
+++ b/platform/android/MapLibreAndroid/src/cpp/rendering_stats.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include
+
+#include
+#include
+#include
+
+namespace mbgl {
+namespace android {
+
+class RenderingStats {
+public:
+ static constexpr auto Name() { return "org/maplibre/android/maps/RenderingStats"; };
+ static void registerNative(jni::JNIEnv& env);
+
+ static jni::Local> Create(jni::JNIEnv&);
+ static void Update(jni::JNIEnv&, jni::Object&, const gfx::RenderingStats&);
+};
+
+} // namespace android
+} // namespace mbgl
diff --git a/platform/android/MapLibreAndroid/src/cpp/style/layers/symbol_layer.cpp b/platform/android/MapLibreAndroid/src/cpp/style/layers/symbol_layer.cpp
index 7038fc3ec..104016817 100644
--- a/platform/android/MapLibreAndroid/src/cpp/style/layers/symbol_layer.cpp
+++ b/platform/android/MapLibreAndroid/src/cpp/style/layers/symbol_layer.cpp
@@ -249,6 +249,11 @@ jni::Local> SymbolLayer::getTextOptional(jni::JNIEnv& env) {
return std::move(*convert>>(env, toSymbolLayer(layer).getTextOptional()));
}
+jni::Local> SymbolLayer::getSymbolScreenSpace(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ return std::move(*convert>>(env, toSymbolLayer(layer).getSymbolScreenSpace()));
+}
+
jni::Local> SymbolLayer::getIconOpacity(jni::JNIEnv& env) {
using namespace mbgl::android::conversion;
return std::move(*convert>>(env, toSymbolLayer(layer).getIconOpacity()));
@@ -557,6 +562,7 @@ void SymbolJavaLayerPeerFactory::registerNative(jni::JNIEnv& env) {
METHOD(&SymbolLayer::getTextAllowOverlap, "nativeGetTextAllowOverlap"),
METHOD(&SymbolLayer::getTextIgnorePlacement, "nativeGetTextIgnorePlacement"),
METHOD(&SymbolLayer::getTextOptional, "nativeGetTextOptional"),
+ METHOD(&SymbolLayer::getSymbolScreenSpace, "nativeGetSymbolScreenSpace"),
METHOD(&SymbolLayer::getIconOpacityTransition, "nativeGetIconOpacityTransition"),
METHOD(&SymbolLayer::setIconOpacityTransition, "nativeSetIconOpacityTransition"),
METHOD(&SymbolLayer::getIconOpacity, "nativeGetIconOpacity"),
diff --git a/platform/android/MapLibreAndroid/src/cpp/style/layers/symbol_layer.hpp b/platform/android/MapLibreAndroid/src/cpp/style/layers/symbol_layer.hpp
index d7333784c..3175f1389 100644
--- a/platform/android/MapLibreAndroid/src/cpp/style/layers/symbol_layer.hpp
+++ b/platform/android/MapLibreAndroid/src/cpp/style/layers/symbol_layer.hpp
@@ -110,6 +110,8 @@ public:
jni::Local> getTextOptional(jni::JNIEnv&);
+ jni::Local> getSymbolScreenSpace(jni::JNIEnv&);
+
jni::Local> getIconOpacity(jni::JNIEnv&);
void setIconOpacityTransition(jni::JNIEnv&, jlong duration, jlong delay);
jni::Local> getIconOpacityTransition(jni::JNIEnv&);
diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapChangeReceiver.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapChangeReceiver.java
index d8216c857..c92d50520 100644
--- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapChangeReceiver.java
+++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapChangeReceiver.java
@@ -23,6 +23,8 @@ class MapChangeReceiver implements NativeMapView.StateCallback {
= new CopyOnWriteArrayList<>();
private final List onDidFinishRenderingFrameList
= new CopyOnWriteArrayList<>();
+ private final List onDidFinishRenderingFrameWithStatsList
+ = new CopyOnWriteArrayList<>();
private final List onWillStartRenderingMapListenerList
= new CopyOnWriteArrayList<>();
private final List onDidFinishRenderingMapListenerList
@@ -156,11 +158,17 @@ class MapChangeReceiver implements NativeMapView.StateCallback {
}
@Override
- public void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime) {
+ public void onDidFinishRenderingFrame(boolean fully, RenderingStats stats) {
try {
if (!onDidFinishRenderingFrameList.isEmpty()) {
for (MapView.OnDidFinishRenderingFrameListener listener : onDidFinishRenderingFrameList) {
- listener.onDidFinishRenderingFrame(fully, frameEncodingTime, frameRenderingTime);
+ listener.onDidFinishRenderingFrame(fully, stats.encodingTime, stats.renderingTime);
+ }
+ }
+
+ if (!onDidFinishRenderingFrameWithStatsList.isEmpty()) {
+ for (MapView.OnDidFinishRenderingFrameWithStatsListener listener : onDidFinishRenderingFrameWithStatsList) {
+ listener.onDidFinishRenderingFrame(fully, stats);
}
}
} catch (Throwable err) {
@@ -480,6 +488,14 @@ class MapChangeReceiver implements NativeMapView.StateCallback {
onDidFinishRenderingFrameList.remove(listener);
}
+ void addOnDidFinishRenderingFrameListener(MapView.OnDidFinishRenderingFrameWithStatsListener listener) {
+ onDidFinishRenderingFrameWithStatsList.add(listener);
+ }
+
+ void removeOnDidFinishRenderingFrameListener(MapView.OnDidFinishRenderingFrameWithStatsListener listener) {
+ onDidFinishRenderingFrameWithStatsList.remove(listener);
+ }
+
void addOnWillStartRenderingMapListener(MapView.OnWillStartRenderingMapListener listener) {
onWillStartRenderingMapListenerList.add(listener);
}
diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java
index cc282134a..ab758b79a 100644
--- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java
+++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java
@@ -103,6 +103,20 @@ public final class MapLibreMap {
nativeMapView.triggerRepaint();
}
+ /**
+ * Query rendering statistics overlay status.
+ */
+ public boolean isRenderingStatsViewEnabled() {
+ return nativeMapView.isRenderingStatsViewEnabled();
+ }
+
+ /**
+ * Enable rendering statistics overlay with {@link RenderingStats} values.
+ */
+ public void enableRenderingStatsView(boolean value) {
+ nativeMapView.enableRenderingStatsView(value);
+ }
+
public void setSwapBehaviorFlush(boolean flush) {
nativeMapView.setSwapBehaviorFlush(flush);
}
diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java
index 39721f55e..d0828b142 100644
--- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java
+++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java
@@ -763,6 +763,24 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback {
mapChangeReceiver.removeOnDidFinishRenderingFrameListener(listener);
}
+ /**
+ * Set a callback that's invoked when the map has finished rendering a frame.
+ *
+ * @param listener The callback that's invoked when the map has finished rendering a frame
+ */
+ public void addOnDidFinishRenderingFrameListener(@NonNull OnDidFinishRenderingFrameWithStatsListener listener) {
+ mapChangeReceiver.addOnDidFinishRenderingFrameListener(listener);
+ }
+
+ /**
+ * Set a callback that's invoked when the map has finished rendering a frame.
+ *
+ * @param listener The callback that's invoked when the map has finished rendering a frame
+ */
+ public void removeOnDidFinishRenderingFrameListener(@NonNull OnDidFinishRenderingFrameWithStatsListener listener) {
+ mapChangeReceiver.removeOnDidFinishRenderingFrameListener(listener);
+ }
+
/**
* Set a callback that's invoked when the map will start rendering.
*
@@ -1182,14 +1200,34 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback {
*
*/
public interface OnDidFinishRenderingFrameListener {
+
/**
* Called when the map has finished rendering a frame
*
* @param fully true if all frames have been rendered, false if partially rendered
+ * @param frameEncodingTime CPU encoding time
+ * @param frameRenderingTime CPU rendering time
*/
void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime);
}
+ /**
+ * Interface definition for a callback to be invoked when the map finished rendering a frame.
+ *
+ * {@link MapView#addOnDidFinishRenderingFrameListener(OnDidFinishRenderingFrameListener)}
+ *
+ */
+ public interface OnDidFinishRenderingFrameWithStatsListener {
+
+ /**
+ * Called when the map has finished rendering a frame
+ *
+ * @param fully true if all frames have been rendered, false if partially rendered
+ * @param stats rendering statistics
+ */
+ void onDidFinishRenderingFrame(boolean fully, RenderingStats stats);
+ }
+
/**
* Interface definition for a callback to be invoked when the map will start rendering the map.
*
@@ -1528,7 +1566,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback {
* The initial render callback waits for rendering to happen before making the map visible for end-users.
* We wait for the second DID_FINISH_RENDERING_FRAME map change event as the first will still show a black surface.
*/
- private class InitialRenderCallback implements OnDidFinishRenderingFrameListener {
+ private class InitialRenderCallback implements OnDidFinishRenderingFrameWithStatsListener {
private int renderCount;
@@ -1537,7 +1575,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback {
}
@Override
- public void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime) {
+ public void onDidFinishRenderingFrame(boolean fully, RenderingStats stats) {
if (maplibreMap != null && maplibreMap.getStyle() != null && maplibreMap.getStyle().isFullyLoaded()) {
renderCount++;
if (renderCount == 3) {
@@ -1643,7 +1681,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback {
}
private class MapCallback implements OnDidFinishLoadingStyleListener,
- OnDidFinishRenderingFrameListener, OnDidFinishLoadingMapListener,
+ OnDidFinishRenderingFrameWithStatsListener, OnDidFinishLoadingMapListener,
OnCameraIsChangingListener, OnCameraDidChangeListener, OnDidFailLoadingMapListener {
private final List onMapReadyCallbackList = new ArrayList<>();
@@ -1709,7 +1747,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback {
}
@Override
- public void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime) {
+ public void onDidFinishRenderingFrame(boolean fully, RenderingStats stats) {
if (maplibreMap != null) {
maplibreMap.onUpdateFullyRendered();
}
diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java
index 4192b6d77..0b3265156 100644
--- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java
+++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java
@@ -250,6 +250,10 @@ interface NativeMap {
void triggerRepaint();
+ boolean isRenderingStatsViewEnabled();
+
+ void enableRenderingStatsView(boolean value);
+
void setSwapBehaviorFlush(boolean flush);
//
diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java
index b714896e3..a0ed4dfcc 100755
--- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java
+++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java
@@ -1075,6 +1075,16 @@ final class NativeMapView implements NativeMap {
nativeTriggerRepaint();
}
+ @Override
+ public boolean isRenderingStatsViewEnabled() {
+ return nativeIsRenderingStatsViewEnabled();
+ }
+
+ @Override
+ public void enableRenderingStatsView(boolean value) {
+ nativeEnableRenderingStatsView(value);
+ }
+
@Override
public void setSwapBehaviorFlush(boolean flush) {
mapRenderer.setSwapBehaviorFlush(flush);
@@ -1145,9 +1155,9 @@ final class NativeMapView implements NativeMap {
}
@Keep
- private void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime) {
+ private void onDidFinishRenderingFrame(boolean fully, RenderingStats stats) {
if (stateCallback != null) {
- stateCallback.onDidFinishRenderingFrame(fully, frameEncodingTime, frameRenderingTime);
+ stateCallback.onDidFinishRenderingFrame(fully, stats);
}
}
@@ -1642,6 +1652,12 @@ final class NativeMapView implements NativeMap {
@Keep
private native void nativeTriggerRepaint();
+ @Keep
+ private native boolean nativeIsRenderingStatsViewEnabled();
+
+ @Keep
+ private native void nativeEnableRenderingStatsView(boolean enabled);
+
//
// Snapshot
//
@@ -1719,7 +1735,7 @@ final class NativeMapView implements NativeMap {
void onWillStartRenderingFrame();
- void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime);
+ void onDidFinishRenderingFrame(boolean fully, RenderingStats stats);
void onWillStartRenderingMap();
diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/RenderingStats.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/RenderingStats.java
new file mode 100644
index 000000000..4e250db53
--- /dev/null
+++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/RenderingStats.java
@@ -0,0 +1,75 @@
+package org.maplibre.android.maps;
+
+public class RenderingStats {
+ /// Frame CPU encoding time (milliseconds)
+ public double encodingTime = 0.0;
+ /// Frame CPU rendering time (milliseconds)
+ public double renderingTime = 0.0;
+
+ /// Number of frames rendered
+ public int numFrames = 0;
+ /// Number of draw calls (`glDrawElements`, `drawIndexedPrimitives`, etc.) executed during the most recent frame
+ public int numDrawCalls = 0;
+ /// Total number of draw calls executed during all the frames
+ public int totalDrawCalls = 0;
+
+ /// Total number of textures created
+ public int numCreatedTextures = 0;
+ /// Net textures
+ public int numActiveTextures = 0;
+ /// Net texture bindings
+ public int numTextureBindings = 0;
+ /// Number of times a texture was updated
+ public int numTextureUpdates = 0;
+ /// Number of bytes used in texture updates
+ public long textureUpdateBytes = 0;
+
+ /// Number of buffers created
+ public long totalBuffers = 0;
+ /// Number of SDK-specific buffers created
+ public long totalBufferObjs = 0;
+ /// Number of times a buffer is updated
+ public long bufferUpdates = 0;
+ /// Number of times an SDK-specific buffer is updated
+ public long bufferObjUpdates = 0;
+ /// Sum of update sizes
+ public long bufferUpdateBytes = 0;
+
+ /// Number of active buffers
+ public int numBuffers = 0;
+ /// Number of active offscreen frame buffers
+ public int numFrameBuffers = 0;
+
+ /// Number of active index buffers
+ public int numIndexBuffers = 0;
+ /// Sum of index buffers update sizes
+ public long indexUpdateBytes = 0;
+
+ /// Number of active vertex buffers
+ public int numVertexBuffers = 0;
+ /// Sum of vertex buffers update sizes
+ public long vertexUpdateBytes = 0;
+
+ /// Number of active uniform buffers
+ public int numUniformBuffers = 0;
+ /// Number of times a uniform buffer is updated
+ public int numUniformUpdates = 0;
+ /// Sum of uniform buffers update sizes
+ public long uniformUpdateBytes = 0;
+
+ /// Total texture memory
+ public int memTextures = 0;
+ /// Total buffer memory
+ public int memBuffers = 0;
+ /// Total index buffer memory
+ public int memIndexBuffers = 0;
+ /// Total vertex buffer memory
+ public int memVertexBuffers = 0;
+ /// Total uniform buffer memory
+ public int memUniformBuffers = 0;
+
+ /// Number of stencil buffer clears
+ public int stencilClears = 0;
+ /// Number of stencil buffer updates
+ public int stencilUpdates = 0;
+}
diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/PropertyFactory.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/PropertyFactory.java
index e845ca276..2613a2dc8 100644
--- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/PropertyFactory.java
+++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/PropertyFactory.java
@@ -2651,6 +2651,26 @@ The unit is in density-independent pixels only for SDF sprites that were created
return new LayoutPropertyValue<>("text-optional", value);
}
+ /**
+ * Internal use only
+ *
+ * @param value a Boolean value
+ * @return property wrapper around Boolean
+ */
+ public static PropertyValue symbolScreenSpace(Boolean value) {
+ return new LayoutPropertyValue<>("symbol-screen-space", value);
+ }
+
+ /**
+ * Internal use only
+ *
+ * @param value a Boolean value
+ * @return property wrapper around Boolean
+ */
+ public static PropertyValue symbolScreenSpace(Expression value) {
+ return new LayoutPropertyValue<>("symbol-screen-space", value);
+ }
+
/**
* Sorts features in ascending order based on this value. Features with a higher sort key will appear above features with a lower sort key.
*
diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/SymbolLayer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/SymbolLayer.java
index 8d5212776..1d8847670 100644
--- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/SymbolLayer.java
+++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/SymbolLayer.java
@@ -649,6 +649,18 @@ public class SymbolLayer extends Layer {
return (PropertyValue) new PropertyValue("text-optional", nativeGetTextOptional());
}
+ /**
+ * Get the SymbolScreenSpace property
+ *
+ * @return property wrapper value around Boolean
+ */
+ @NonNull
+ @SuppressWarnings("unchecked")
+ public PropertyValue getSymbolScreenSpace() {
+ checkThread();
+ return (PropertyValue) new PropertyValue("symbol-screen-space", nativeGetSymbolScreenSpace());
+ }
+
/**
* Get the IconOpacity property
*
@@ -1305,6 +1317,10 @@ public class SymbolLayer extends Layer {
@Keep
private native Object nativeGetTextOptional();
+ @NonNull
+ @Keep
+ private native Object nativeGetSymbolScreenSpace();
+
@NonNull
@Keep
private native Object nativeGetIconOpacity();
diff --git a/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/maps/MapChangeReceiverTest.kt b/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/maps/MapChangeReceiverTest.kt
index 37d1d285a..8731b9f23 100644
--- a/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/maps/MapChangeReceiverTest.kt
+++ b/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/maps/MapChangeReceiverTest.kt
@@ -42,6 +42,9 @@ class MapChangeReceiverTest {
@Mock
private val onDidFinishRenderingFrameListener: OnDidFinishRenderingFrameListener? = null
+ @Mock
+ private val onDidFinishRenderingFrameWithStatsListener: OnDidFinishRenderingFrameWithStatsListener? = null
+
@Mock
private val onWillStartRenderingMapListener: OnWillStartRenderingMapListener? = null
@@ -401,13 +404,13 @@ class MapChangeReceiverTest {
mapChangeEventManager!!.addOnDidFinishRenderingFrameListener(
onDidFinishRenderingFrameListener
)
- mapChangeEventManager!!?.onDidFinishRenderingFrame(true, .0, .0)
+ mapChangeEventManager!!?.onDidFinishRenderingFrame(true, TEST_RENDERING_STATS)
Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(true, .0, .0)
mapChangeEventManager!!.removeOnDidFinishRenderingFrameListener(
onDidFinishRenderingFrameListener
)
- mapChangeEventManager!!?.onDidFinishRenderingFrame(true, .0, .0)
- Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(true,.0, .0)
+ mapChangeEventManager!!?.onDidFinishRenderingFrame(true, TEST_RENDERING_STATS)
+ Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(true, .0, .0)
mapChangeEventManager!!.addOnDidFinishRenderingFrameListener(
onDidFinishRenderingFrameListener
)
@@ -416,7 +419,7 @@ class MapChangeReceiverTest {
Mockito.doThrow(exc).`when`(onDidFinishRenderingFrameListener)
?.onDidFinishRenderingFrame(true, .0, .0)
try {
- mapChangeEventManager!!?.onDidFinishRenderingFrame(true, .0, .0)
+ mapChangeEventManager!!?.onDidFinishRenderingFrame(true, TEST_RENDERING_STATS)
Assert.fail("The exception should've been re-thrown.")
} catch (throwable: RuntimeException) {
Mockito.verify(loggerDefinition)?.e(
@@ -429,7 +432,51 @@ class MapChangeReceiverTest {
Mockito.doThrow(err).`when`(onDidFinishRenderingFrameListener)
?.onDidFinishRenderingFrame(true, .0, .0)
try {
- mapChangeEventManager!!?.onDidFinishRenderingFrame(true, .0, .0)
+ mapChangeEventManager!!?.onDidFinishRenderingFrame(true, TEST_RENDERING_STATS)
+ Assert.fail("The exception should've been re-thrown.")
+ } catch (throwable: ExecutionError) {
+ Mockito.verify(loggerDefinition)?.e(
+ ArgumentMatchers.anyString(),
+ ArgumentMatchers.anyString(),
+ ArgumentMatchers.eq(err)
+ )
+ }
+ }
+
+ @Test
+ fun testOnDidFinishRenderingFrameWithStatsListener() {
+ mapChangeEventManager!!.addOnDidFinishRenderingFrameListener(
+ onDidFinishRenderingFrameWithStatsListener
+ )
+ mapChangeEventManager!!?.onDidFinishRenderingFrame(true, TEST_RENDERING_STATS)
+ Mockito.verify(onDidFinishRenderingFrameWithStatsListener)?.onDidFinishRenderingFrame(true, TEST_RENDERING_STATS)
+ mapChangeEventManager!!.removeOnDidFinishRenderingFrameListener(
+ onDidFinishRenderingFrameWithStatsListener
+ )
+ mapChangeEventManager!!?.onDidFinishRenderingFrame(true, TEST_RENDERING_STATS)
+ Mockito.verify(onDidFinishRenderingFrameWithStatsListener)?.onDidFinishRenderingFrame(true, TEST_RENDERING_STATS)
+ mapChangeEventManager!!.addOnDidFinishRenderingFrameListener(
+ onDidFinishRenderingFrameWithStatsListener
+ )
+ Logger.setLoggerDefinition(loggerDefinition)
+ val exc: Exception = RuntimeException()
+ Mockito.doThrow(exc).`when`(onDidFinishRenderingFrameWithStatsListener)
+ ?.onDidFinishRenderingFrame(true, TEST_RENDERING_STATS)
+ try {
+ mapChangeEventManager!!?.onDidFinishRenderingFrame(true, TEST_RENDERING_STATS)
+ Assert.fail("The exception should've been re-thrown.")
+ } catch (throwable: RuntimeException) {
+ Mockito.verify(loggerDefinition)?.e(
+ ArgumentMatchers.anyString(),
+ ArgumentMatchers.anyString(),
+ ArgumentMatchers.eq(exc)
+ )
+ }
+ val err: Error = ExecutionError("", Error())
+ Mockito.doThrow(err).`when`(onDidFinishRenderingFrameWithStatsListener)
+ ?.onDidFinishRenderingFrame(true, TEST_RENDERING_STATS)
+ try {
+ mapChangeEventManager!!?.onDidFinishRenderingFrame(true, TEST_RENDERING_STATS)
Assert.fail("The exception should've been re-thrown.")
} catch (throwable: ExecutionError) {
Mockito.verify(loggerDefinition)?.e(
@@ -445,12 +492,12 @@ class MapChangeReceiverTest {
mapChangeEventManager!!.addOnDidFinishRenderingFrameListener(
onDidFinishRenderingFrameListener
)
- mapChangeEventManager!!?.onDidFinishRenderingFrame(false, .0, .0)
+ mapChangeEventManager!!?.onDidFinishRenderingFrame(false, TEST_RENDERING_STATS)
Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(false, .0, .0)
mapChangeEventManager!!.removeOnDidFinishRenderingFrameListener(
onDidFinishRenderingFrameListener
)
- mapChangeEventManager!!?.onDidFinishRenderingFrame(false, .0, .0)
+ mapChangeEventManager!!?.onDidFinishRenderingFrame(false, TEST_RENDERING_STATS)
Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(false, .0, .0)
mapChangeEventManager!!.addOnDidFinishRenderingFrameListener(
onDidFinishRenderingFrameListener
@@ -460,7 +507,7 @@ class MapChangeReceiverTest {
Mockito.doThrow(exc).`when`(onDidFinishRenderingFrameListener)
?.onDidFinishRenderingFrame(false, .0, .0)
try {
- mapChangeEventManager!!?.onDidFinishRenderingFrame(false, .0, .0)
+ mapChangeEventManager!!?.onDidFinishRenderingFrame(false, TEST_RENDERING_STATS)
Assert.fail("The exception should've been re-thrown.")
} catch (throwable: RuntimeException) {
Mockito.verify(loggerDefinition)?.e(
@@ -473,7 +520,7 @@ class MapChangeReceiverTest {
Mockito.doThrow(err).`when`(onDidFinishRenderingFrameListener)
?.onDidFinishRenderingFrame(false, .0, .0)
try {
- mapChangeEventManager!!?.onDidFinishRenderingFrame(false, .0, .0)
+ mapChangeEventManager!!?.onDidFinishRenderingFrame(false, TEST_RENDERING_STATS)
Assert.fail("The exception should've been re-thrown.")
} catch (throwable: ExecutionError) {
Mockito.verify(loggerDefinition)?.e(
@@ -710,5 +757,6 @@ class MapChangeReceiverTest {
companion object {
private const val TEST_STRING = "mapChangeRandom"
+ private val TEST_RENDERING_STATS = RenderingStats();
}
}
diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/maps/RemoveUnusedImagesTest.kt b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/maps/RemoveUnusedImagesTest.kt
index 40842838b..79a121961 100644
--- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/maps/RemoveUnusedImagesTest.kt
+++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/maps/RemoveUnusedImagesTest.kt
@@ -60,7 +60,7 @@ class RemoveUnusedImagesTest : AppCenter() {
mapView.addOnCanRemoveUnusedStyleImageListener {
callbackLatch.countDown()
maplibreMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(0.0, 120.0), 8.0))
- mapView.addOnDidFinishRenderingFrameListener{ _, _, _ ->
+ mapView.addOnDidFinishRenderingFrameListener{ _, _ ->
assertNotNull(maplibreMap.style!!.getImage("small"))
assertNotNull(maplibreMap.style!!.getImage("large"))
latch.countDown()
@@ -89,7 +89,7 @@ class RemoveUnusedImagesTest : AppCenter() {
maplibreMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(0.0, 120.0), 8.0))
// Wait for the next frame and check that images were removed from the style.
- mapView.addOnDidFinishRenderingFrameListener { _, _, _ ->
+ mapView.addOnDidFinishRenderingFrameListener { _, _ ->
if (maplibreMap.style!!.getImage("small") == null && maplibreMap.style!!.getImage("large") == null) {
latch.countDown()
}
diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/SymbolLayerTest.java b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/SymbolLayerTest.java
index cd1ed59fe..62a9752d9 100644
--- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/SymbolLayerTest.java
+++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/SymbolLayerTest.java
@@ -917,6 +917,19 @@ public class SymbolLayerTest extends BaseLayerTest {
assertEquals(layer.getTextOptional().getValue(), propertyValue);
}
+ @Test
+ @UiThreadTest
+ public void testSymbolScreenSpaceAsConstant() {
+ Timber.i("symbol-screen-space");
+ assertNotNull(layer);
+ assertNull(layer.getSymbolScreenSpace().getValue());
+
+ // Set and Get
+ Boolean propertyValue = true;
+ layer.setProperties(symbolScreenSpace(propertyValue));
+ assertEquals(layer.getSymbolScreenSpace().getValue(), propertyValue);
+ }
+
@Test
@UiThreadTest
public void testIconOpacityTransition() {
diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/benchmark/BenchmarkActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/benchmark/BenchmarkActivity.kt
index 330a995f4..c7f4f1ca2 100644
--- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/benchmark/BenchmarkActivity.kt
+++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/benchmark/BenchmarkActivity.kt
@@ -27,6 +27,7 @@ import org.maplibre.android.log.Logger.INFO
import org.maplibre.android.maps.MapLibreMap
import org.maplibre.android.maps.MapLibreMap.CancelableCallback
import org.maplibre.android.maps.MapView
+import org.maplibre.android.maps.RenderingStats
import org.maplibre.android.testapp.R
import org.maplibre.android.testapp.styles.TestStyles
import org.maplibre.android.testapp.utils.BenchmarkInputData
@@ -250,9 +251,9 @@ class BenchmarkActivity : AppCompatActivity() {
maplibreMap.setSwapBehaviorFlush(benchmarkRun.syncRendering)
- val listener = MapView.OnDidFinishRenderingFrameListener { _: Boolean, frameEncodingTime: Double, frameRenderingTime: Double ->
- encodingTimeStore.add(frameEncodingTime * 1e3)
- renderingTimeStore.add(frameRenderingTime * 1e3)
+ val listener = MapView.OnDidFinishRenderingFrameWithStatsListener { _: Boolean, stats: RenderingStats ->
+ encodingTimeStore.add(stats.encodingTime * 1e3)
+ renderingTimeStore.add(stats.renderingTime * 1e3)
numFrames++;
}
mapView.addOnDidFinishRenderingFrameListener(listener)
diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/events/ObserverActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/events/ObserverActivity.kt
index 9ac827fea..214493e1b 100644
--- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/events/ObserverActivity.kt
+++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/events/ObserverActivity.kt
@@ -19,10 +19,10 @@ import kotlin.time.TimeSource
import kotlin.time.TimeSource.Monotonic
import org.maplibre.android.log.Logger
import org.maplibre.android.log.Logger.INFO
+import org.maplibre.android.maps.RenderingStats
import org.maplibre.android.maps.MapLibreMap
-
// # --8<-- [start:ObserverActivity]
/**
* Test activity showcasing logging observer actions from the core
@@ -34,7 +34,8 @@ class ObserverActivity : AppCompatActivity(),
MapView.OnGlyphsLoadedListener,
MapView.OnGlyphsRequestedListener,
MapView.OnSpriteLoadedListener,
- MapView.OnSpriteRequestedListener {
+ MapView.OnSpriteRequestedListener,
+ MapView.OnDidFinishRenderingFrameWithStatsListener {
// # --8<-- [end:ObserverActivity]
private lateinit var mapView: MapView
@@ -57,12 +58,14 @@ class ObserverActivity : AppCompatActivity(),
mapView.addOnGlyphsRequestedListener(this)
mapView.addOnSpriteLoadedListener(this)
mapView.addOnSpriteRequestedListener(this)
+ mapView.addOnDidFinishRenderingFrameListener(this)
// # --8<-- [end:addListeners]
mapView.getMapAsync {
it.setStyle(
Style.Builder().fromUri(TestStyles.DEMOTILES)
)
+ it.enableRenderingStatsView(true)
}
// print and clear action journal
@@ -131,6 +134,10 @@ class ObserverActivity : AppCompatActivity(),
TileOperation.NullOp -> Logger.e(TAG, "An unknown tile operation was emitted for tile ${tile}")
}
}
+
+ override fun onDidFinishRenderingFrame(fully: Boolean, stats: RenderingStats) {
+
+ }
// # --8<-- [end:mapEvents]
override fun onStart() {
diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/imagegenerator/SnapshotActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/imagegenerator/SnapshotActivity.kt
index b512f5bf2..98c55421e 100644
--- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/imagegenerator/SnapshotActivity.kt
+++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/imagegenerator/SnapshotActivity.kt
@@ -6,6 +6,7 @@ import org.maplibre.android.log.Logger
import org.maplibre.android.maps.MapView
import org.maplibre.android.maps.MapLibreMap
import org.maplibre.android.maps.OnMapReadyCallback
+import org.maplibre.android.maps.RenderingStats
import org.maplibre.android.maps.Style
import org.maplibre.android.testapp.databinding.ActivitySnapshotBinding
import org.maplibre.android.testapp.styles.TestStyles
diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/MapChangeActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/MapChangeActivity.kt
index 7950ffe99..a77057540 100644
--- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/MapChangeActivity.kt
+++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/MapChangeActivity.kt
@@ -59,7 +59,7 @@ class MapChangeActivity : AppCompatActivity() {
mapView.addOnDidFinishLoadingMapListener(OnDidFinishLoadingMapListener { Timber.v("OnDidFinishLoadingMap") })
mapView.addOnDidFinishLoadingStyleListener(OnDidFinishLoadingStyleListener { Timber.v("OnDidFinishLoadingStyle") })
mapView.addOnDidFinishRenderingFrameListener(
- OnDidFinishRenderingFrameListener { fully: Boolean, frameEncodingTime: Double, frameRenderingTime: Double ->
+ OnDidFinishRenderingFrameListener { fully: Boolean, _: Double, _: Double ->
Timber.v(
"OnDidFinishRenderingFrame: fully: %s",
fully
diff --git a/platform/darwin/bazel/files.bzl b/platform/darwin/bazel/files.bzl
index 4f732095f..4db2748fb 100644
--- a/platform/darwin/bazel/files.bzl
+++ b/platform/darwin/bazel/files.bzl
@@ -86,6 +86,7 @@ MLN_DARWIN_OBJC_HEADERS = [
"src/MLNPolyline.h",
"src/MLNRasterDEMSource.h",
"src/MLNRasterTileSource.h",
+ "src/MLNRenderingStats.h",
"src/MLNSettings.h",
"src/MLNShape.h",
"src/MLNShapeCollection.h",
@@ -148,6 +149,7 @@ MLN_DARWIN_PRIVATE_HEADERS = [
"src/MLNPolygon_Private.h",
"src/MLNPolyline_Private.h",
"src/MLNRasterTileSource_Private.h",
+ "src/MLNRenderingStats_Private.h",
"src/MLNSettings_Private.h",
"src/MLNShapeOfflineRegion_Private.h",
"src/MLNShapeSource_Private.h",
@@ -189,6 +191,7 @@ MLN_DARWIN_PUBLIC_OBJCPP_SOURCE = [
"src/MLNPolyline.mm",
"src/MLNRasterDEMSource.mm",
"src/MLNRasterTileSource.mm",
+ "src/MLNRenderingStats.mm",
"src/MLNSettings.mm",
"src/MLNShape.mm",
"src/MLNShapeCollection.mm",
diff --git a/platform/darwin/src/MLNRenderingStats.h b/platform/darwin/src/MLNRenderingStats.h
new file mode 100644
index 000000000..0c5e208fd
--- /dev/null
+++ b/platform/darwin/src/MLNRenderingStats.h
@@ -0,0 +1,83 @@
+#import
+#import "MLNFoundation.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+MLN_EXPORT
+@interface MLNRenderingStats : NSObject
+
+/// Frame CPU encoding time (milliseconds)
+@property (readonly) double encodingTime;
+/// Frame CPU rendering time (milliseconds)
+@property (readonly) double renderingTime;
+
+/// Number of frames rendered
+@property (readonly) int numFrames;
+/// Number of draw calls (`glDrawElements`, `drawIndexedPrimitives`, etc.) executed during the most
+/// recent frame
+@property (readonly) int numDrawCalls;
+/// Total number of draw calls executed during all the frames
+@property (readonly) int totalDrawCalls;
+
+/// Total number of textures created
+@property (readonly) int numCreatedTextures;
+/// Net textures
+@property (readonly) int numActiveTextures;
+/// Net texture bindings
+@property (readonly) int numTextureBindings;
+/// Number of times a texture was updated
+@property (readonly) int numTextureUpdates;
+/// Number of bytes used in texture updates
+@property (readonly) unsigned long textureUpdateBytes;
+
+/// Number of buffers created
+@property (readonly) unsigned long totalBuffers;
+/// Number of SDK-specific buffers created
+@property (readonly) unsigned long totalBufferObjs;
+/// Number of times a buffer is updated
+@property (readonly) unsigned long bufferUpdates;
+/// Number of times an SDK-specific buffer is updated
+@property (readonly) unsigned long bufferObjUpdates;
+/// Sum of update sizes
+@property (readonly) unsigned long bufferUpdateBytes;
+
+/// Number of active buffers
+@property (readonly) int numBuffers;
+/// Number of active offscreen frame buffers
+@property (readonly) int numFrameBuffers;
+
+/// Number of active index buffers
+@property (readonly) int numIndexBuffers;
+/// Sum of index buffers update sizes
+@property (readonly) unsigned long indexUpdateBytes;
+
+/// Number of active vertex buffers
+@property (readonly) int numVertexBuffers;
+/// Sum of vertex buffers update sizes
+@property (readonly) unsigned long vertexUpdateBytes;
+
+/// Number of active uniform buffers
+@property (readonly) int numUniformBuffers;
+/// Number of times a uniform buffer is updated
+@property (readonly) int numUniformUpdates;
+/// Sum of uniform buffers update sizes
+@property (readonly) unsigned long uniformUpdateBytes;
+
+/// Total texture memory
+@property (readonly) int memTextures;
+/// Total buffer memory
+@property (readonly) int memBuffers;
+/// Total index buffer memory
+@property (readonly) int memIndexBuffers;
+/// Total vertex buffer memory
+@property (readonly) int memVertexBuffers;
+/// Total uniform buffer memory
+@property (readonly) int memUniformBuffers;
+
+/// Number of stencil buffer clears
+@property (readonly) int stencilClears;
+/// Number of stencil buffer updates
+@property (readonly) int stencilUpdates;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MLNRenderingStats.mm b/platform/darwin/src/MLNRenderingStats.mm
new file mode 100644
index 000000000..63231993c
--- /dev/null
+++ b/platform/darwin/src/MLNRenderingStats.mm
@@ -0,0 +1,38 @@
+#import "MLNRenderingStats_Private.h"
+
+@implementation MLNRenderingStats
+
+- (void)setCoreData:(const mbgl::gfx::RenderingStats&)stats {
+ _encodingTime = stats.encodingTime;
+ _renderingTime = stats.renderingTime;
+ _numFrames = stats.numFrames;
+ _numDrawCalls = stats.numDrawCalls;
+ _totalDrawCalls = stats.totalDrawCalls;
+ _numCreatedTextures = stats.numCreatedTextures;
+ _numActiveTextures = stats.numActiveTextures;
+ _numTextureBindings = stats.numTextureBindings;
+ _numTextureUpdates = stats.numTextureUpdates;
+ _textureUpdateBytes = stats.textureUpdateBytes;
+ _totalBuffers = stats.totalBuffers;
+ _totalBufferObjs = stats.totalBufferObjs;
+ _bufferUpdates = stats.bufferUpdates;
+ _bufferObjUpdates = stats.bufferObjUpdates;
+ _bufferUpdateBytes = stats.bufferUpdateBytes;
+ _numIndexBuffers = stats.numIndexBuffers;
+ _indexUpdateBytes = stats.indexUpdateBytes;
+ _numVertexBuffers = stats.numVertexBuffers;
+ _vertexUpdateBytes = stats.vertexUpdateBytes;
+ _numUniformBuffers = stats.numUniformBuffers;
+ _numUniformUpdates = stats.numUniformUpdates;
+ _uniformUpdateBytes = stats.uniformUpdateBytes;
+ _memTextures = stats.memTextures;
+ _memBuffers = stats.memBuffers;
+ _memIndexBuffers = stats.memIndexBuffers;
+ _memVertexBuffers = stats.memVertexBuffers;
+ _memUniformBuffers = stats.memUniformBuffers;
+ _stencilClears = stats.stencilClears;
+ _stencilUpdates = stats.stencilUpdates;
+}
+
+
+@end
diff --git a/platform/darwin/src/MLNRenderingStats_Private.h b/platform/darwin/src/MLNRenderingStats_Private.h
new file mode 100644
index 000000000..24265f40d
--- /dev/null
+++ b/platform/darwin/src/MLNRenderingStats_Private.h
@@ -0,0 +1,12 @@
+#import
+#import "MLNRenderingStats.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MLNRenderingStats (Private)
+
+- (void)setCoreData:(const mbgl::gfx::RenderingStats&)stats;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/default/src/mbgl/map/map_snapshotter.cpp b/platform/default/src/mbgl/map/map_snapshotter.cpp
index e42fe0719..fa74b076a 100644
--- a/platform/default/src/mbgl/map/map_snapshotter.cpp
+++ b/platform/default/src/mbgl/map/map_snapshotter.cpp
@@ -39,11 +39,10 @@ public:
void onDidFinishRenderingFrame(RenderMode mode,
bool repaintNeeded,
bool placementChanged,
- double frameEncodingTime,
- double frameRenderingTime) override {
+ const gfx::RenderingStats& stats) override {
void (RendererObserver::*f)(
- RenderMode, bool, bool, double, double) = &RendererObserver::onDidFinishRenderingFrame;
- delegate.invoke(f, mode, repaintNeeded, placementChanged, frameEncodingTime, frameRenderingTime);
+ RenderMode, bool, bool, const gfx::RenderingStats&) = &RendererObserver::onDidFinishRenderingFrame;
+ delegate.invoke(f, mode, repaintNeeded, placementChanged, stats);
}
void onStyleImageMissing(const std::string& image, const StyleImageMissingCallback& cb) override {
@@ -95,6 +94,16 @@ public:
mode, repaintNeeded, placementChanged, frameEncodingTime, frameRenderingTime);
}
+ void onDidFinishRenderingFrame(RenderMode mode,
+ bool repaint,
+ bool placementChanged,
+ const gfx::RenderingStats& stats) override {
+ if (mode == RenderMode::Full && hasPendingStillImageRequest) {
+ stillImage = frontend.readStillImage();
+ }
+ rendererObserver->onDidFinishRenderingFrame(mode, repaint, placementChanged, stats);
+ }
+
void onStyleImageMissing(const std::string& id, const StyleImageMissingCallback& done) override {
rendererObserver->onStyleImageMissing(id, done);
}
diff --git a/platform/glfw/glfw_view.cpp b/platform/glfw/glfw_view.cpp
index 200fade64..9ba9a1f76 100644
--- a/platform/glfw/glfw_view.cpp
+++ b/platform/glfw/glfw_view.cpp
@@ -278,6 +278,8 @@ GLFWView::GLFWView(bool fullscreen_,
printf("- Press `K` to add a random custom runtime imagery annotation\n");
printf("- Press `L` to add a random line annotation\n");
printf("- Press `W` to pop the last-added annotation off\n");
+ printf("- Press `V` to toggle custom drawable layer\n");
+ printf("- Press `B` to toggle rendering stats\n");
printf("- Press `P` to pause tile requests\n");
printf("\n");
printf("- Hold `Control` + mouse drag to rotate\n");
@@ -373,6 +375,9 @@ void GLFWView::onKey(GLFWwindow *window, int key, int /*scancode*/, int action,
case GLFW_KEY_V:
view->toggleCustomDrawableStyle();
break;
+ case GLFW_KEY_B:
+ view->map->enableRenderingStatsView(!view->map->isRenderingStatsViewEnabled());
+ break;
case GLFW_KEY_I:
view->resetDatabaseCallback();
break;
diff --git a/platform/ios/MapLibre.docc/ActionJournalExample.md b/platform/ios/MapLibre.docc/ActionJournalExample.md
index 4842f0abe..8d33785b3 100644
--- a/platform/ios/MapLibre.docc/ActionJournalExample.md
+++ b/platform/ios/MapLibre.docc/ActionJournalExample.md
@@ -63,7 +63,10 @@ Enabling the action journal.
```swift
-MLNSettings.actionJournalOptions.enabled = true
+let options = MLNMapOptions()
+ options.actionJournalOptions.enabled = true
+ options.styleURL = AMERICANA_STYLE
+ mapView = MLNMapView(frame: view.bounds, options: options)
```
diff --git a/platform/ios/MapLibre.docc/MapLibre.md b/platform/ios/MapLibre.docc/MapLibre.md
index e459c0eb6..8004db5db 100644
--- a/platform/ios/MapLibre.docc/MapLibre.md
+++ b/platform/ios/MapLibre.docc/MapLibre.md
@@ -57,6 +57,7 @@ Powerful, free and open-source mapping toolkit with full control over data sourc
### Advanced
-
+-
### Other Articles
diff --git a/platform/ios/MapLibre.docc/ObserverExample.md b/platform/ios/MapLibre.docc/ObserverExample.md
index 0df88adf3..f5fdc148c 100644
--- a/platform/ios/MapLibre.docc/ObserverExample.md
+++ b/platform/ios/MapLibre.docc/ObserverExample.md
@@ -6,6 +6,20 @@ Learn about the ``MLNMapViewDelegate`` methods for observing map events.
You can observe certain low-level events as they happen. Use these methods to collect metrics or investigate issues during map rendering. This feature is intended primarily for power users. We are always interested in improving observability, so if you have a special use case, feel free to [open an issue or pull request](https://github.com/maplibre/maplibre-native) to extend the types of observability methods.
+## Frame Events
+
+Observe frame rendering statistics with ``MLNMapViewDelegate/mapViewDidFinishRenderingFrame:fullyRendered:renderingStats:``.
+
+
+
+```swift
+func mapViewDidFinishRenderingFrame(_: MLNMapView, fullyRendered: Bool, renderingStats: MLNRenderingStats) {
+
+ }
+```
+
+See also: ``MLNMapViewDelegate/mapViewDidFinishRenderingFrame:fullyRendered:`` and ``MLNMapViewDelegate/mapViewDidFinishRenderingFrame:fullyRendered:frameEncodingTime:frameRenderingTime:``
+
## Shader Events
Observe shader compilation with ``MLNMapViewDelegate/mapView:shaderWillCompile:backend:defines:`` and ``MLNMapViewDelegate/mapView:shaderDidCompile:backend:defines:``.
diff --git a/platform/ios/MapLibre.docc/RenderingStatisticsHud.md b/platform/ios/MapLibre.docc/RenderingStatisticsHud.md
new file mode 100644
index 000000000..e03173876
--- /dev/null
+++ b/platform/ios/MapLibre.docc/RenderingStatisticsHud.md
@@ -0,0 +1,13 @@
+# Rendering Statistics HUD
+
+Show rendering statistics on the map
+
+Enable the rendering HUD with:
+
+
+
+```swift
+mapView.enableRenderingStatsView(true)
+```
+
+
diff --git a/platform/ios/app-swift/Sources/MapLibreNavigationView.swift b/platform/ios/app-swift/Sources/MapLibreNavigationView.swift
index a815f6a2c..e4dd3f371 100644
--- a/platform/ios/app-swift/Sources/MapLibreNavigationView.swift
+++ b/platform/ios/app-swift/Sources/MapLibreNavigationView.swift
@@ -35,7 +35,7 @@ struct MapLibreNavigationView: View {
AddMarkerSymbolExampleUIViewControllerRepresentable()
}
NavigationLink("ObserverExample") {
- ObserverExampleViewExampleUIViewControllerRepresentable()
+ ObserverExampleViewUIViewControllerRepresentable()
}
Group {
NavigationLink("AnimatedLineExample") {
diff --git a/platform/ios/app-swift/Sources/ObserverExample.swift b/platform/ios/app-swift/Sources/ObserverExample.swift
index 167c1bfe1..7fcea92ae 100644
--- a/platform/ios/app-swift/Sources/ObserverExample.swift
+++ b/platform/ios/app-swift/Sources/ObserverExample.swift
@@ -18,11 +18,6 @@ class ObserverExampleView: UIViewController, MLNMapViewDelegate {
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
- mapView.setCenter(
- CLLocationCoordinate2D(latitude: 45.5076, longitude: -122.6736),
- zoomLevel: 11,
- animated: false
- )
view.addSubview(mapView)
mapView.delegate = self
@@ -45,6 +40,17 @@ class ObserverExampleView: UIViewController, MLNMapViewDelegate {
// #-end-example-code
+ func mapViewDidFinishLoadingMap(_: MLNMapView) {
+ // #-example-code(enableRenderingStatsView)
+ mapView.enableRenderingStatsView(true)
+ // #-end-example-code
+ }
+
+ // #-example-code(ObserverExampleRenderingStats)
+ func mapViewDidFinishRenderingFrame(_: MLNMapView, fullyRendered _: Bool, renderingStats _: MLNRenderingStats) {}
+
+ // #-end-example-code
+
// #-example-code(ObserverExampleShaders)
func mapView(_: MLNMapView, shaderWillCompile id: Int, backend: Int, defines: String) {
print("A new shader is being compiled - shaderID:\(id), backend type:\(backend), program configuration:\(defines)")
@@ -125,7 +131,7 @@ class ObserverExampleView: UIViewController, MLNMapViewDelegate {
// #-end-example-code
}
-struct ObserverExampleViewExampleUIViewControllerRepresentable: UIViewControllerRepresentable {
+struct ObserverExampleViewUIViewControllerRepresentable: UIViewControllerRepresentable {
typealias UIViewControllerType = ObserverExampleView
func makeUIViewController(context _: Context) -> ObserverExampleView {
diff --git a/platform/ios/app/MBXViewController.mm b/platform/ios/app/MBXViewController.mm
index 21aad9900..c8bad1e55 100644
--- a/platform/ios/app/MBXViewController.mm
+++ b/platform/ios/app/MBXViewController.mm
@@ -54,7 +54,8 @@ typedef NS_ENUM(NSInteger, MBXSettingsDebugToolsRows) {
MBXSettingsDebugToolsOverdrawVisualization,
MBXSettingsDebugToolsShowZoomLevel,
MBXSettingsDebugToolsShowFrameTimeGraph,
- MBXSettingsDebugToolsShowReuseQueueStats
+ MBXSettingsDebugToolsShowReuseQueueStats,
+ MBXSettingsDebugToolsShowRenderingStats
};
typedef NS_ENUM(NSInteger, MBXSettingsAnnotationsRows) {
@@ -220,6 +221,7 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
@property (nonatomic, getter=isLocalizingLabels) BOOL localizingLabels;
@property (nonatomic) BOOL reuseQueueStatsEnabled;
@property (nonatomic) BOOL frameTimeGraphEnabled;
+@property (nonatomic) BOOL renderingStatsEnabled;
@property (nonatomic) BOOL shouldLimitCameraChanges;
@property (nonatomic) BOOL randomWalk;
@property (nonatomic) BOOL zoomLevelOrnamentEnabled;
@@ -399,7 +401,8 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
(debugMask & MLNMapDebugOverdrawVisualizationMask ? @"Hide" :@"Show")],
[NSString stringWithFormat:@"%@ zoom level ornament", (self.zoomLevelOrnamentEnabled ? @"Hide" :@"Show")],
[NSString stringWithFormat:@"%@ frame time graph", (self.frameTimeGraphEnabled ? @"Hide" :@"Show")],
- [NSString stringWithFormat:@"%@ reuse queue stats", (self.reuseQueueStatsEnabled ? @"Hide" :@"Show")]
+ [NSString stringWithFormat:@"%@ reuse queue stats", (self.reuseQueueStatsEnabled ? @"Hide" :@"Show")],
+ [NSString stringWithFormat:@"%@ rendering stats", (self.renderingStatsEnabled ? @"Hide" :@"Show")]
]];
break;
case MBXSettingsAnnotations:
@@ -533,6 +536,12 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
[self updateHUD];
break;
}
+ case MBXSettingsDebugToolsShowRenderingStats:
+ {
+ self.renderingStatsEnabled = !self.renderingStatsEnabled;
+ [self.mapView enableRenderingStatsView:self.renderingStatsEnabled];
+ break;
+ }
default:
NSAssert(NO, @"All debug tools setting rows should be implemented");
break;
@@ -2691,9 +2700,11 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
return features;
}
-- (void)mapViewDidFinishRenderingFrame:(MLNMapView *)mapView fullyRendered:(BOOL)fullyRendered frameEncodingTime:(double)frameEncodingTime frameRenderingTime:(double)frameRenderingTime {
+- (void)mapViewDidFinishRenderingFrame:(MLNMapView *)mapView
+ fullyRendered:(BOOL)fullyRendered
+ renderingStats:(nonnull MLNRenderingStats *)renderingStats {
if (self.frameTimeGraphEnabled) {
- [self.frameTimeGraphView updatePathWithFrameDuration:frameEncodingTime];
+ [self.frameTimeGraphView updatePathWithFrameDuration:renderingStats.encodingTime];
}
}
diff --git a/platform/ios/src/MLNMapView+Impl.h b/platform/ios/src/MLNMapView+Impl.h
index ab4c3459f..39dc58e34 100644
--- a/platform/ios/src/MLNMapView+Impl.h
+++ b/platform/ios/src/MLNMapView+Impl.h
@@ -64,7 +64,7 @@ class MLNMapViewImpl : public mbgl::MapObserver {
void onDidFinishLoadingMap() override;
void onDidFailLoadingMap(mbgl::MapLoadError mapError, const std::string& what) override;
void onWillStartRenderingFrame() override;
- void onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus) override;
+ void onDidFinishRenderingFrame(const mbgl::MapObserver::RenderFrameStatus&) override;
void onWillStartRenderingMap() override;
void onDidFinishRenderingMap(mbgl::MapObserver::RenderMode) override;
void onDidFinishLoadingStyle() override;
diff --git a/platform/ios/src/MLNMapView+Impl.mm b/platform/ios/src/MLNMapView+Impl.mm
index 35ad18139..a01f67add 100644
--- a/platform/ios/src/MLNMapView+Impl.mm
+++ b/platform/ios/src/MLNMapView+Impl.mm
@@ -79,9 +79,9 @@ void MLNMapViewImpl::onWillStartRenderingFrame() {
[mapView mapViewWillStartRenderingFrame];
}
-void MLNMapViewImpl::onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus status) {
- bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full;
- [mapView mapViewDidFinishRenderingFrameFullyRendered:fullyRendered frameEncodingTime:status.frameEncodingTime frameRenderingTime:status.frameRenderingTime];
+void MLNMapViewImpl::onDidFinishRenderingFrame(const mbgl::MapObserver::RenderFrameStatus& status) {
+ const bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full;
+ [mapView mapViewDidFinishRenderingFrameFullyRendered:fullyRendered renderingStats:status.renderingStats];
}
void MLNMapViewImpl::onWillStartRenderingMap() {
diff --git a/platform/ios/src/MLNMapView.h b/platform/ios/src/MLNMapView.h
index d56c07f36..9308ded6e 100644
--- a/platform/ios/src/MLNMapView.h
+++ b/platform/ios/src/MLNMapView.h
@@ -2157,6 +2157,16 @@ vertically on the map.
*/
@property (nonatomic) MLNMapDebugMaskOptions debugMask;
+/**
+ Returns the status of the rendering statistics overlay.
+ */
+- (BOOL)isRenderingStatsViewEnabled;
+
+/**
+ Enable a rendering statistics overlay with ``MLNRenderingStats`` values.
+ */
+- (void)enableRenderingStatsView:(BOOL)value;
+
/**
Get the list of action journal log files from oldest to newest.
diff --git a/platform/ios/src/MLNMapView.mm b/platform/ios/src/MLNMapView.mm
index 7eeacf3d1..fcb081113 100644
--- a/platform/ios/src/MLNMapView.mm
+++ b/platform/ios/src/MLNMapView.mm
@@ -68,6 +68,7 @@
#import "MLNLoggingConfiguration_Private.h"
#import "MLNNetworkConfiguration_Private.h"
#import "MLNReachability.h"
+#import "MLNRenderingStats_Private.h"
#import "MLNSettings_Private.h"
#import "MLNActionJournalOptions_Private.h"
#import "MLNMapProjection.h"
@@ -495,6 +496,8 @@ public:
CFTimeInterval _frameCounterStartTime;
NSInteger _frameCount;
CFTimeInterval _frameDurations;
+
+ MLNRenderingStats* _renderingStats;
}
// MARK: - Setup & Teardown -
@@ -6913,8 +6916,7 @@ static void *windowScreenContext = &windowScreenContext;
}
- (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered
- frameEncodingTime:(double)frameEncodingTime
- frameRenderingTime:(double)frameRenderingTime {
+ renderingStats:(const mbgl::gfx::RenderingStats &)stats {
if (!_mbglMap)
{
return;
@@ -6926,9 +6928,21 @@ static void *windowScreenContext = &windowScreenContext;
[self.style didChangeValueForKey:@"layers"];
}
- if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:frameEncodingTime:frameRenderingTime:)])
+ if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:renderingStats:)])
{
- [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered frameEncodingTime:frameEncodingTime frameRenderingTime:frameRenderingTime];
+ if (!_renderingStats) {
+ _renderingStats = [[MLNRenderingStats alloc] init];
+ }
+
+ [_renderingStats setCoreData:stats];
+ [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered renderingStats:_renderingStats];
+ }
+ else if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:frameEncodingTime:frameRenderingTime:)])
+ {
+ [self.delegate mapViewDidFinishRenderingFrame:self
+ fullyRendered:fullyRendered
+ frameEncodingTime:stats.encodingTime
+ frameRenderingTime:stats.renderingTime];
}
else if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:)])
{
@@ -7557,6 +7571,14 @@ static void *windowScreenContext = &windowScreenContext;
return _annotationViewReuseQueueByIdentifier[identifier];
}
+- (BOOL)isRenderingStatsViewEnabled {
+ return _mbglMap->isRenderingStatsViewEnabled();
+}
+
+- (void)enableRenderingStatsView:(BOOL)value {
+ _mbglMap->enableRenderingStatsView(value);
+}
+
- (void)triggerRepaint
{
_mbglMap->triggerRepaint();
diff --git a/platform/ios/src/MLNMapViewDelegate.h b/platform/ios/src/MLNMapViewDelegate.h
index 1fa78827a..ec9dd6e54 100644
--- a/platform/ios/src/MLNMapViewDelegate.h
+++ b/platform/ios/src/MLNMapViewDelegate.h
@@ -1,6 +1,7 @@
#import
#import "MLNCameraChangeReason.h"
+#import "MLNRenderingStats.h"
#import "MLNTileOperation.h"
#import "Mapbox.h"
@@ -267,6 +268,23 @@ NS_ASSUME_NONNULL_BEGIN
fullyRendered:(BOOL)fullyRendered
frameEncodingTime:(double)frameEncodingTime
frameRenderingTime:(double)frameRenderingTime;
+/**
+ Tells the delegate that the map view has just redrawn.
+
+ This method is called any time the map view needs to redraw due to a change in
+ the viewpoint or style property transition. This method may be called very
+ frequently, even moreso than `-mapViewRegionIsChanging:`. Therefore, your
+ implementation of this method should be as lightweight as possible to avoid
+ affecting performance.
+
+ @param mapView The map view that has just redrawn.
+ @param fullyRendered A Boolean value indicating whether the map is fully rendered or not.
+ @param renderingStats A collection of rendering statistics
+ */
+- (void)mapViewDidFinishRenderingFrame:(MLNMapView *)mapView
+ fullyRendered:(BOOL)fullyRendered
+ renderingStats:(MLNRenderingStats *)renderingStats;
+
/**
Tells the delegate that the map view is entering an idle state, and no more
drawing will be necessary until new data is loaded or there is some interaction
diff --git a/platform/ios/src/MLNMapView_Private.h b/platform/ios/src/MLNMapView_Private.h
index 632fe4938..db918f1aa 100644
--- a/platform/ios/src/MLNMapView_Private.h
+++ b/platform/ios/src/MLNMapView_Private.h
@@ -3,11 +3,14 @@
#import "MLNTileOperation.h"
#import "MLNUserLocationAnnotationView.h"
-#include
-
namespace mbgl {
class Map;
class Renderer;
+
+namespace gfx {
+struct RenderingStats;
+}
+
} // namespace mbgl
class MLNMapViewImpl;
@@ -39,8 +42,7 @@ FOUNDATION_EXTERN MLN_EXPORT MLNExceptionName const _Nonnull MLNUnderlyingMapUna
- (void)mapViewDidFailLoadingMapWithError:(nonnull NSError *)error;
- (void)mapViewWillStartRenderingFrame;
- (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered
- frameEncodingTime:(double)frameEncodingTime
- frameRenderingTime:(double)frameRenderingTime;
+ renderingStats:(const mbgl::gfx::RenderingStats &)stats;
- (void)mapViewWillStartRenderingMap;
- (void)mapViewDidFinishRenderingMapFullyRendered:(BOOL)fullyRendered;
- (void)mapViewDidBecomeIdle;
diff --git a/platform/ios/test/MLNMapViewDelegateIntegrationTests.swift b/platform/ios/test/MLNMapViewDelegateIntegrationTests.swift
index 3509830e1..6f7e69b9d 100644
--- a/platform/ios/test/MLNMapViewDelegateIntegrationTests.swift
+++ b/platform/ios/test/MLNMapViewDelegateIntegrationTests.swift
@@ -62,6 +62,8 @@ extension MLNMapViewDelegateIntegrationTests: MLNMapViewDelegate {
func mapViewDidFinishRenderingFrame(_: MLNMapView, fullyRendered _: Bool, frameEncodingTime _: Double, frameRenderingTime _: Double) {}
+ func mapViewDidFinishRenderingFrame(_: MLNMapView, fullyRendered _: Bool, renderingStats _: MLNRenderingStats) {}
+
func mapView(_: MLNMapView, shapeAnnotationIsEnabled _: MLNShape) -> Bool { false }
func mapView(_: MLNMapView, didAdd _: [MLNAnnotationView]) {}
diff --git a/platform/macos/src/MLNMapView+Impl.h b/platform/macos/src/MLNMapView+Impl.h
index 16a61fca4..07fd08cc2 100644
--- a/platform/macos/src/MLNMapView+Impl.h
+++ b/platform/macos/src/MLNMapView+Impl.h
@@ -31,7 +31,7 @@ class MLNMapViewImpl : public mbgl::MapObserver {
void onDidFinishLoadingMap() override;
void onDidFailLoadingMap(mbgl::MapLoadError mapError, const std::string& what) override;
void onWillStartRenderingFrame() override;
- void onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus) override;
+ void onDidFinishRenderingFrame(const mbgl::MapObserver::RenderFrameStatus&) override;
void onWillStartRenderingMap() override;
void onDidFinishRenderingMap(mbgl::MapObserver::RenderMode) override;
void onDidFinishLoadingStyle() override;
diff --git a/platform/macos/src/MLNMapView+Impl.mm b/platform/macos/src/MLNMapView+Impl.mm
index 90ba411ad..f555ffdba 100644
--- a/platform/macos/src/MLNMapView+Impl.mm
+++ b/platform/macos/src/MLNMapView+Impl.mm
@@ -80,9 +80,9 @@ void MLNMapViewImpl::onWillStartRenderingFrame() {
[mapView mapViewWillStartRenderingFrame];
}
-void MLNMapViewImpl::onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus status) {
+void MLNMapViewImpl::onDidFinishRenderingFrame(const mbgl::MapObserver::RenderFrameStatus& status) {
bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full;
- [mapView mapViewDidFinishRenderingFrameFullyRendered:fullyRendered];
+ [mapView mapViewDidFinishRenderingFrameFullyRendered:fullyRendered renderingStats:status.renderingStats];
}
void MLNMapViewImpl::onWillStartRenderingMap() {
diff --git a/platform/macos/src/MLNMapView.mm b/platform/macos/src/MLNMapView.mm
index cdaf39dd2..83cd35ed9 100644
--- a/platform/macos/src/MLNMapView.mm
+++ b/platform/macos/src/MLNMapView.mm
@@ -61,6 +61,7 @@
#import "MLNLoggingConfiguration_Private.h"
#import "MLNReachability.h"
#import "MLNActionJournalOptions_Private.h"
+#import "MLNRenderingStats_Private.h"
#import "MLNSettings_Private.h"
#import
@@ -207,6 +208,8 @@ public:
/// reachability instance
MLNReachability *_reachability;
+
+ MLNRenderingStats* _renderingStats;
}
// MARK: Lifecycle
@@ -954,7 +957,8 @@ public:
}
}
-- (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered {
+- (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered
+ renderingStats:(const mbgl::gfx::RenderingStats &)stats {
if (!_mbglMap) {
return;
}
@@ -963,7 +967,25 @@ public:
_isChangingAnnotationLayers = NO;
[self.style didChangeValueForKey:@"layers"];
}
- if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:)]) {
+
+ if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:renderingStats:)])
+ {
+ if (!_renderingStats) {
+ _renderingStats = [[MLNRenderingStats alloc] init];
+ }
+
+ [_renderingStats setCoreData:stats];
+ [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered renderingStats:_renderingStats];
+ }
+ else if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:frameEncodingTime:frameRenderingTime:)])
+ {
+ [self.delegate mapViewDidFinishRenderingFrame:self
+ fullyRendered:fullyRendered
+ frameEncodingTime:stats.encodingTime
+ frameRenderingTime:stats.renderingTime];
+ }
+ else if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:)])
+ {
[self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered];
}
}
diff --git a/platform/macos/src/MLNMapViewDelegate.h b/platform/macos/src/MLNMapViewDelegate.h
index 9dcf94141..43e631b3f 100644
--- a/platform/macos/src/MLNMapViewDelegate.h
+++ b/platform/macos/src/MLNMapViewDelegate.h
@@ -1,5 +1,6 @@
#import
#import
+#import "MLNRenderingStats.h"
#import "MLNTileOperation.h"
NS_ASSUME_NONNULL_BEGIN
@@ -169,11 +170,31 @@ NS_ASSUME_NONNULL_BEGIN
affecting performance.
@param mapView The map view that has just redrawn.
- @param frameTimeNanos The time taken to render the frame, in nanoseconds
+ @param fullyRendered A Boolean value indicating whether the map is fully rendered or not.
+ @param frameEncodingTime The time taken to encode the frame, in milliseconds.
+ @param frameRenderingTime The time taken to render the frame, in milliseconds.
*/
- (void)mapViewDidFinishRenderingFrame:(MLNMapView *)mapView
fullyRendered:(BOOL)fullyRendered
- frameTime:(double)frameTime;
+ frameEncodingTime:(double)frameEncodingTime
+ frameRenderingTime:(double)frameRenderingTime;
+
+/**
+ Tells the delegate that the map view has just redrawn.
+
+ This method is called any time the map view needs to redraw due to a change in
+ the viewpoint or style property transition. This method may be called very
+ frequently, even moreso than `-mapViewRegionIsChanging:`. Therefore, your
+ implementation of this method should be as lightweight as possible to avoid
+ affecting performance.
+
+ @param mapView The map view that has just redrawn.
+ @param fullyRendered A Boolean value indicating whether the map is fully rendered or not.
+ @param renderingStats A collection of rendering statistics
+ */
+- (void)mapViewDidFinishRenderingFrame:(MLNMapView *)mapView
+ fullyRendered:(BOOL)fullyRendered
+ renderingStats:(MLNRenderingStats *)renderingStats;
/**
Tells the delegate that the map view is entering an idle state, and no more
diff --git a/platform/macos/src/MLNMapView_Private.h b/platform/macos/src/MLNMapView_Private.h
index 12c04213d..d5573544c 100644
--- a/platform/macos/src/MLNMapView_Private.h
+++ b/platform/macos/src/MLNMapView_Private.h
@@ -6,6 +6,11 @@
namespace mbgl {
class Map;
class Renderer;
+
+namespace gfx {
+struct RenderingStats;
+}
+
} // namespace mbgl
@class MLNSource;
@@ -40,7 +45,8 @@ class Renderer;
- (void)mapViewDidFinishLoadingMap;
- (void)mapViewDidFailLoadingMapWithError:(nonnull NSError *)error;
- (void)mapViewWillStartRenderingFrame;
-- (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered;
+- (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered
+ renderingStats:(const mbgl::gfx::RenderingStats &)stats;
- (void)mapViewWillStartRenderingMap;
- (void)mapViewDidFinishRenderingMapFullyRendered:(BOOL)fullyRendered;
- (void)mapViewDidBecomeIdle;
diff --git a/scripts/style-spec.mjs b/scripts/style-spec.mjs
index aec25a030..cb383cd91 100644
--- a/scripts/style-spec.mjs
+++ b/scripts/style-spec.mjs
@@ -190,4 +190,12 @@ modifiedReferenceSpec["paint_location-indicator"] = {
}
};
+// internal use
+modifiedReferenceSpec["layout_symbol"]["symbol-screen-space"] = {
+ "type": "boolean",
+ "default": false,
+ "property-type": "data-constant",
+ "doc": "Internal use only"
+};
+
export default modifiedReferenceSpec;
diff --git a/src/mbgl/gfx/rendering_stats.cpp b/src/mbgl/gfx/rendering_stats.cpp
index 20b20c21d..84aa6c590 100644
--- a/src/mbgl/gfx/rendering_stats.cpp
+++ b/src/mbgl/gfx/rendering_stats.cpp
@@ -1,8 +1,14 @@
#include
+#include
+#include
+#include
+#include
+#include
#include
#include
#include
+#include
namespace mbgl {
namespace gfx {
@@ -24,6 +30,8 @@ bool RenderingStats::isZero() const {
}
RenderingStats& RenderingStats::operator+=(const RenderingStats& r) {
+ encodingTime += r.encodingTime;
+ renderingTime += r.renderingTime;
numFrames += r.numFrames;
numDrawCalls += r.numDrawCalls;
totalDrawCalls += r.totalDrawCalls;
@@ -66,6 +74,10 @@ std::ostream& optionalStatLine(std::ostream& stream, T value, std::string_view l
}
std::string RenderingStats::toString(std::string_view sep) const {
std::stringstream ss;
+ ss.precision(2);
+
+ optionalStatLine(ss, encodingTime, "encodingTime", sep);
+ optionalStatLine(ss, renderingTime, "renderingTime", sep);
optionalStatLine(ss, numFrames, "numFrames", sep);
optionalStatLine(ss, numDrawCalls, "numDrawCalls", sep);
optionalStatLine(ss, totalDrawCalls, "totalDrawCalls", sep);
@@ -99,5 +111,182 @@ std::string RenderingStats::toString(std::string_view sep) const {
}
#endif
+void RenderingStatsView::create(style::Style& style) {
+ if (!style.getSource(sourceID)) {
+ style::CustomGeometrySource::Options sourceOptions;
+
+ sourceOptions.zoomRange = {0, 0};
+
+ sourceOptions.fetchTileFunction = [&](const CanonicalTileID& tileID) {
+ auto source = static_cast(style.getSource(sourceID));
+
+ if (!source) {
+ return;
+ }
+
+ mbgl::FeatureCollection features;
+
+ mapbox::geojson::feature feature;
+ feature.geometry = mapbox::geometry::geometry(Point{0, 0});
+
+ features.emplace_back(feature);
+ source->setTileData(tileID, features);
+ };
+
+ style.addSource(std::make_unique(sourceID, sourceOptions));
+ }
+
+ if (!style.getLayer(layerID)) {
+ auto infoLayer = std::make_unique(layerID, sourceID);
+
+ // required
+ infoLayer->setSymbolScreenSpace(true);
+ infoLayer->setTextAllowOverlap(true);
+ infoLayer->setIconAllowOverlap(true);
+ infoLayer->setTextIgnorePlacement(true);
+
+ // customizable
+ infoLayer->setTextColor(options.textColor);
+ infoLayer->setTextSize(options.textSize);
+ infoLayer->setTextMaxWidth(300.0f);
+ infoLayer->setTextJustify(mbgl::style::TextJustifyType::Left);
+ infoLayer->setTextAnchor(mbgl::style::SymbolAnchorType::TopRight);
+
+ const float translation = mbgl::util::EXTENT / 2.0f - 100.0f;
+ infoLayer->setTextTranslateAnchor(mbgl::style::TranslateAnchorType::Viewport);
+ infoLayer->setTextTranslate(std::array{translation, translation});
+
+ style.addLayer(std::move(infoLayer));
+ }
+}
+
+void RenderingStatsView::destroy(style::Style& style) {
+ style.removeLayer(layerID);
+ style.removeSource(sourceID);
+}
+
+mbgl::style::SymbolLayer* RenderingStatsView::getLayer(style::Style& style) {
+ return static_cast(style.getLayer(layerID));
+}
+
+namespace {
+template
+void printNumber(std::stringstream& ss, const std::string_view label, const T& value, bool print) {
+ if (!print) {
+ return;
+ }
+
+ ss << label << ": ";
+
+ if (value >= 1e9) {
+ ss << value / 1e9 << "B";
+ } else if (value >= 1e6) {
+ ss << value / 1e6 << "M";
+ } else if (value >= 1e3) {
+ ss << value / 1e3 << "K";
+ } else {
+ ss << value;
+ }
+
+ ss << "\n";
+};
+
+template
+void printMemory(std::stringstream& ss, const std::string_view label, const T& value, bool print) {
+ if (!print) {
+ return;
+ }
+
+ constexpr auto base = 1024;
+ constexpr auto kb = base;
+ constexpr auto mb = kb * base;
+ constexpr auto gb = mb * base;
+
+ ss << label << ": ";
+
+ if (value >= gb) {
+ ss << value / gb << "GB";
+ } else if (value >= mb) {
+ ss << value / mb << "MB";
+ } else if (value >= kb) {
+ ss << value / kb << "KB";
+ } else {
+ ss << value << "B";
+ }
+
+ ss << "\n";
+};
+
+} // namespace
+
+void RenderingStatsView::update(style::Style& style, const gfx::RenderingStats& stats) {
+ auto layer = getLayer(style);
+
+ // style reloaded? layer got removed?
+ if (!layer) {
+ create(style);
+ layer = getLayer(style);
+ }
+
+ if (!layer) {
+ return;
+ }
+
+ ++frameCount;
+ encodingTime += stats.encodingTime;
+ renderingTime += stats.renderingTime;
+
+ const auto currentTime = util::MonotonicTimer::now().count();
+ if (currentTime - lastUpdate < options.updateInterval) {
+ return;
+ }
+
+ std::stringstream ss;
+ ss << std::setprecision(3) << std::fixed;
+
+ ss << "Encoding time (ms): " << std::setw(7) << encodingTime / frameCount * 1000 << "\n";
+ ss << "Rendering time (ms): " << std::setw(7) << renderingTime / frameCount * 1000 << "\n";
+
+ printNumber(ss, "Frame count", stats.numFrames, true);
+ printNumber(ss, "Draw calls", stats.numDrawCalls, true);
+ printNumber(ss, "Total draw calls", stats.totalDrawCalls, options.verbose);
+
+ printNumber(ss, "Textures", stats.numActiveTextures, true);
+ printNumber(ss, "Total textures", stats.numCreatedTextures, options.verbose);
+ printNumber(ss, "Texture updates", stats.numTextureUpdates, options.verbose);
+ printMemory(ss, "Texture updates", stats.textureUpdateBytes, options.verbose);
+
+ printNumber(ss, "Buffers", stats.numBuffers, true);
+ printNumber(ss, "Total buffers", stats.totalBuffers, options.verbose);
+ printNumber(ss, "Total buffer updates", stats.bufferUpdates, options.verbose);
+ printMemory(ss, "Total buffer updates", stats.bufferUpdateBytes, options.verbose);
+
+ printNumber(ss, "Index buffers", stats.numIndexBuffers, true);
+ printMemory(ss, "Index buffers updates", stats.indexUpdateBytes, options.verbose);
+
+ printNumber(ss, "Vertex buffers", stats.numVertexBuffers, true);
+ printMemory(ss, "Vertex buffers updates", stats.vertexUpdateBytes, options.verbose);
+
+ printNumber(ss, "Uniform buffers", stats.numUniformBuffers, true);
+ printNumber(ss, "Uniform buffer updates", stats.numUniformUpdates, options.verbose);
+ printMemory(ss, "Uniform buffer updates", stats.uniformUpdateBytes, options.verbose);
+
+ printMemory(ss, "Texture memory", stats.memTextures, true);
+ printMemory(ss, "Buffer memory", stats.memBuffers, true);
+ printMemory(ss, "Index buffer memory", stats.memIndexBuffers, true);
+ printMemory(ss, "Vertex buffer memory", stats.memVertexBuffers, true);
+ printMemory(ss, "Uniform buffer memory", stats.memUniformBuffers, true);
+
+ printNumber(ss, "Stencil buffer clears", stats.stencilClears, true);
+ printNumber(ss, "Stencil buffer updates", stats.stencilUpdates, options.verbose);
+
+ layer->setTextField(mbgl::style::expression::Formatted(ss.str().c_str()));
+
+ frameCount = 0;
+ encodingTime = 0.0;
+ renderingTime = 0.0;
+ lastUpdate = currentTime;
+}
+
} // namespace gfx
} // namespace mbgl
diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp
index 916797937..4659190c3 100644
--- a/src/mbgl/gl/context.cpp
+++ b/src/mbgl/gl/context.cpp
@@ -100,6 +100,10 @@ Context::~Context() noexcept {
if (cleanupOnDestruction) {
backend.getThreadPool().runRenderJobs(true /* closeQueue */);
+ for (size_t i = 0; i < globalUniformBuffers.allocatedSize(); i++) {
+ globalUniformBuffers.set(i, nullptr);
+ }
+
reset();
// Delete all pooled resources while the context is still valid
@@ -536,7 +540,7 @@ gfx::UniformBufferPtr Context::createUniformBuffer(const void* data,
bool /*ssbo*/) {
MLN_TRACE_FUNC();
- return std::make_shared(data, size, *uboAllocator);
+ return std::make_shared(*this, data, size, *uboAllocator);
}
gfx::UniqueUniformBufferArray Context::createLayerUniformBufferArray() {
@@ -622,6 +626,7 @@ void Context::clear(std::optional color, std::optional depth
MBGL_CHECK_ERROR(glClear(mask));
stats.numDrawCalls = 0;
+ stats.numFrames++;
}
void Context::setCullFaceMode(const gfx::CullFaceMode& mode) {
@@ -728,6 +733,7 @@ void Context::draw(const gfx::DrawMode& drawMode, std::size_t indexOffset, std::
reinterpret_cast(sizeof(uint16_t) * indexOffset)));
stats.numDrawCalls++;
+ stats.totalDrawCalls++;
}
void Context::performCleanup() {
diff --git a/src/mbgl/gl/index_buffer_resource.cpp b/src/mbgl/gl/index_buffer_resource.cpp
index e9a5cc1f2..0212527f1 100644
--- a/src/mbgl/gl/index_buffer_resource.cpp
+++ b/src/mbgl/gl/index_buffer_resource.cpp
@@ -9,13 +9,29 @@ IndexBufferResource::IndexBufferResource(UniqueBuffer&& buffer_, int byteSize_)
: buffer(std::move(buffer_)),
byteSize(byteSize_) {
MLN_TRACE_ALLOC_INDEX_BUFFER(buffer.get(), byteSize);
+
+ if (buffer) {
+ auto& stats = buffer.get_deleter().context.renderingStats();
+ stats.numIndexBuffers++;
+ stats.memIndexBuffers += byteSize;
+
+ stats.numBuffers++;
+ stats.totalBuffers++;
+ stats.memBuffers += byteSize;
+ }
}
IndexBufferResource::~IndexBufferResource() noexcept {
MLN_TRACE_FREE_INDEX_BUFFER(buffer.get());
- auto& stats = buffer.get_deleter().context.renderingStats();
- stats.memIndexBuffers -= byteSize;
- assert(stats.memIndexBuffers >= 0);
+
+ if (buffer) {
+ auto& stats = buffer.get_deleter().context.renderingStats();
+ stats.numIndexBuffers--;
+ stats.memIndexBuffers -= byteSize;
+ stats.memBuffers -= byteSize;
+
+ assert(stats.memIndexBuffers >= 0);
+ }
}
} // namespace gl
diff --git a/src/mbgl/gl/texture2d.cpp b/src/mbgl/gl/texture2d.cpp
index 332e515a7..a9551dfa8 100644
--- a/src/mbgl/gl/texture2d.cpp
+++ b/src/mbgl/gl/texture2d.cpp
@@ -187,6 +187,9 @@ void Texture2D::uploadSubRegion(const void* pixelData, const Size& size_, uint16
Enum::to(pixelFormat),
Enum::to(channelType),
pixelData));
+
+ context.renderingStats().numTextureUpdates++;
+ context.renderingStats().textureUpdateBytes += static_cast(getPixelStride() * size_.width * size_.height);
}
void Texture2D::upload() noexcept {
diff --git a/src/mbgl/gl/uniform_buffer_gl.cpp b/src/mbgl/gl/uniform_buffer_gl.cpp
index 7bc7d470d..200a945c6 100644
--- a/src/mbgl/gl/uniform_buffer_gl.cpp
+++ b/src/mbgl/gl/uniform_buffer_gl.cpp
@@ -26,12 +26,21 @@ int64_t generateDebugId() noexcept {
} // namespace
-UniformBufferGL::UniformBufferGL(const void* data_, std::size_t size_, IBufferAllocator& allocator_)
+UniformBufferGL::UniformBufferGL(Context& context_, const void* data_, std::size_t size_, IBufferAllocator& allocator_)
: UniformBuffer(size_),
+ context(context_),
#ifdef MLN_TRACY_ENABLE
uniqueDebugId(generateDebugId()),
#endif
managedBuffer(allocator_, this) {
+
+ context.renderingStats().numUniformBuffers++;
+ context.renderingStats().memUniformBuffers += size;
+
+ context.renderingStats().totalBuffers++;
+ context.renderingStats().numBuffers++;
+ context.renderingStats().memBuffers += size;
+
MLN_TRACE_ALLOC_CONST_BUFFER(uniqueDebugId, size_);
if (size_ > managedBuffer.allocator.pageSize()) {
// Buffer is very large, won't fit in the provided allocator
@@ -48,6 +57,7 @@ UniformBufferGL::UniformBufferGL(const void* data_, std::size_t size_, IBufferAl
UniformBufferGL::UniformBufferGL(UniformBufferGL&& rhs) noexcept
: UniformBuffer(rhs.size),
+ context(rhs.context),
#ifdef MLN_TRACY_ENABLE
uniqueDebugId(rhs.uniqueDebugId),
#endif
@@ -62,6 +72,7 @@ UniformBufferGL::UniformBufferGL(UniformBufferGL&& rhs) noexcept
UniformBufferGL::UniformBufferGL(const UniformBufferGL& other)
: UniformBuffer(other),
+ context(other.context),
#ifdef MLN_TRACY_ENABLE
uniqueDebugId(generateDebugId()),
#endif
@@ -83,6 +94,13 @@ UniformBufferGL::~UniformBufferGL() {
#ifdef MLN_TRACY_ENABLE
assert(uniqueDebugId > 0);
#endif
+
+ context.renderingStats().numUniformBuffers--;
+ context.renderingStats().memUniformBuffers -= size;
+
+ context.renderingStats().numBuffers--;
+ context.renderingStats().memBuffers -= size;
+
MLN_TRACE_FREE_CONST_BUFFER(uniqueDebugId);
if (isManagedAllocation) {
return;
@@ -123,6 +141,12 @@ void UniformBufferGL::update(const void* data, std::size_t dataSize) {
MBGL_CHECK_ERROR(glBufferSubData(GL_UNIFORM_BUFFER, 0, dataSize, data));
MBGL_CHECK_ERROR(glBindBuffer(GL_UNIFORM_BUFFER, 0));
}
+
+ context.renderingStats().numUniformUpdates++;
+ context.renderingStats().bufferUpdates++;
+ context.renderingStats().bufferObjUpdates++;
+ context.renderingStats().uniformUpdateBytes += dataSize;
+ context.renderingStats().bufferUpdateBytes += dataSize;
}
void UniformBufferArrayGL::bind() const {
diff --git a/src/mbgl/gl/upload_pass.cpp b/src/mbgl/gl/upload_pass.cpp
index b58358b3c..1a1cec168 100644
--- a/src/mbgl/gl/upload_pass.cpp
+++ b/src/mbgl/gl/upload_pass.cpp
@@ -31,8 +31,7 @@ std::unique_ptr UploadPass::createVertexBufferResourc
bool /*persistent*/) {
BufferID id = 0;
MBGL_CHECK_ERROR(glGenBuffers(1, &id));
- commandEncoder.context.renderingStats().numBuffers++;
- commandEncoder.context.renderingStats().memVertexBuffers += static_cast(size);
+
// NOLINTNEXTLINE(performance-move-const-arg)
UniqueBuffer result{std::move(id), {commandEncoder.context}};
commandEncoder.context.vertexBuffer = result;
@@ -43,6 +42,11 @@ std::unique_ptr UploadPass::createVertexBufferResourc
void UploadPass::updateVertexBufferResource(gfx::VertexBufferResource& resource, const void* data, std::size_t size) {
commandEncoder.context.vertexBuffer = static_cast(resource).getBuffer();
MBGL_CHECK_ERROR(glBufferSubData(GL_ARRAY_BUFFER, 0, size, data));
+
+ commandEncoder.context.renderingStats().vertexUpdateBytes += size;
+ commandEncoder.context.renderingStats().bufferUpdateBytes += size;
+ commandEncoder.context.renderingStats().bufferUpdates++;
+ commandEncoder.context.renderingStats().bufferObjUpdates++;
}
std::unique_ptr UploadPass::createIndexBufferResource(const void* data,
@@ -51,8 +55,7 @@ std::unique_ptr UploadPass::createIndexBufferResource(
bool /*persistent*/) {
BufferID id = 0;
MBGL_CHECK_ERROR(glGenBuffers(1, &id));
- commandEncoder.context.renderingStats().numBuffers++;
- commandEncoder.context.renderingStats().memIndexBuffers += static_cast(size);
+
// NOLINTNEXTLINE(performance-move-const-arg)
UniqueBuffer result{std::move(id), {commandEncoder.context}};
commandEncoder.context.bindVertexArray = 0;
@@ -67,6 +70,11 @@ void UploadPass::updateIndexBufferResource(gfx::IndexBufferResource& resource, c
commandEncoder.context.bindVertexArray = 0;
commandEncoder.context.globalVertexArrayState.indexBuffer = static_cast(resource).buffer;
MBGL_CHECK_ERROR(glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, size, data));
+
+ commandEncoder.context.renderingStats().indexUpdateBytes += size;
+ commandEncoder.context.renderingStats().bufferUpdateBytes += size;
+ commandEncoder.context.renderingStats().bufferUpdates++;
+ commandEncoder.context.renderingStats().bufferObjUpdates++;
}
struct VertexBufferGL : public gfx::VertexBufferBase {
diff --git a/src/mbgl/gl/vertex_buffer_resource.cpp b/src/mbgl/gl/vertex_buffer_resource.cpp
index fbd7e0c88..e66ad9766 100644
--- a/src/mbgl/gl/vertex_buffer_resource.cpp
+++ b/src/mbgl/gl/vertex_buffer_resource.cpp
@@ -9,13 +9,29 @@ VertexBufferResource::VertexBufferResource(UniqueBuffer&& buffer_, int byteSize_
: buffer(std::move(buffer_)),
byteSize(byteSize_) {
MLN_TRACE_ALLOC_VERTEX_BUFFER(buffer.get(), byteSize);
+
+ if (buffer) {
+ auto& stats = buffer.get_deleter().context.renderingStats();
+ stats.numVertexBuffers++;
+ stats.memVertexBuffers += byteSize;
+
+ stats.numBuffers++;
+ stats.totalBuffers++;
+ stats.memBuffers += byteSize;
+ }
}
VertexBufferResource::~VertexBufferResource() noexcept {
MLN_TRACE_FREE_VERTEX_BUFFER(buffer.get());
- auto& stats = buffer.get_deleter().context.renderingStats();
- stats.memVertexBuffers -= byteSize;
- assert(stats.memVertexBuffers >= 0);
+
+ if (buffer) {
+ auto& stats = buffer.get_deleter().context.renderingStats();
+ stats.numVertexBuffers--;
+ stats.memVertexBuffers -= byteSize;
+ stats.memBuffers -= byteSize;
+
+ assert(stats.memVertexBuffers >= 0);
+ }
}
} // namespace gl
diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp
index cff580adb..c298c1242 100644
--- a/src/mbgl/map/map.cpp
+++ b/src/mbgl/map/map.cpp
@@ -507,6 +507,14 @@ MapDebugOptions Map::getDebug() const {
return impl->debugOptions;
}
+bool Map::isRenderingStatsViewEnabled() const {
+ return impl->isRenderingStatsViewEnabled();
+}
+
+void Map::enableRenderingStatsView(bool value) {
+ impl->enableRenderingStatsView(value);
+}
+
void Map::setPrefetchZoomDelta(uint8_t delta) {
impl->prefetchZoomDelta = delta;
}
diff --git a/src/mbgl/map/map_impl.cpp b/src/mbgl/map/map_impl.cpp
index bca50cd62..7ef9d8c50 100644
--- a/src/mbgl/map/map_impl.cpp
+++ b/src/mbgl/map/map_impl.cpp
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
namespace mbgl {
@@ -240,14 +241,16 @@ void Map::Impl::onWillStartRenderingFrame() {
void Map::Impl::onDidFinishRenderingFrame(RenderMode renderMode,
bool needsRepaint,
bool placemenChanged,
- double frameEncodingTime,
- double frameRenderingTime) {
+ const gfx::RenderingStats& stats) {
rendererFullyLoaded = renderMode == RenderMode::Full;
+ if (renderingStatsView && style) {
+ renderingStatsView->update(*style, stats);
+ }
+
if (mode == MapMode::Continuous) {
const MapObserver::RenderFrameStatus frameStatus{
- MapObserver::RenderMode(renderMode), needsRepaint, placemenChanged, frameEncodingTime, frameRenderingTime};
-
+ MapObserver::RenderMode(renderMode), needsRepaint, placemenChanged, stats};
observer.onDidFinishRenderingFrame(frameStatus);
if (actionJournal) {
@@ -304,6 +307,28 @@ void Map::Impl::jumpTo(const CameraOptions& camera) {
onUpdate();
}
+bool Map::Impl::isRenderingStatsViewEnabled() const {
+ return !!renderingStatsView;
+}
+
+void Map::Impl::enableRenderingStatsView(bool value) {
+ if (value) {
+ if (!renderingStatsView) {
+ renderingStatsView = std::make_unique();
+ if (style) {
+ renderingStatsView->create(*style);
+ }
+ }
+ } else {
+ if (renderingStatsView) {
+ if (style) {
+ renderingStatsView->destroy(*style);
+ }
+ renderingStatsView = nullptr;
+ }
+ }
+}
+
void Map::Impl::onStyleImageMissing(const std::string& id, const std::function& done) {
if (!style->getImage(id)) {
observer.onStyleImageMissing(id);
diff --git a/src/mbgl/map/map_impl.hpp b/src/mbgl/map/map_impl.hpp
index 1a8f6c4e1..f280bd1da 100644
--- a/src/mbgl/map/map_impl.hpp
+++ b/src/mbgl/map/map_impl.hpp
@@ -58,7 +58,7 @@ public:
void onInvalidate() final;
void onResourceError(std::exception_ptr) final;
void onWillStartRenderingFrame() final;
- void onDidFinishRenderingFrame(RenderMode, bool, bool, double, double) final;
+ void onDidFinishRenderingFrame(RenderMode, bool, bool, const gfx::RenderingStats&) final;
void onWillStartRenderingMap() final;
void onDidFinishRenderingMap() final;
void onStyleImageMissing(const std::string&, const std::function&) final;
@@ -76,6 +76,9 @@ public:
// Map
void jumpTo(const CameraOptions&);
+ bool isRenderingStatsViewEnabled() const;
+ void enableRenderingStatsView(bool value);
+
MapObserver& observer;
RendererFrontend& rendererFrontend;
std::unique_ptr actionJournal;
@@ -87,6 +90,7 @@ public:
const bool crossSourceCollisions;
MapDebugOptions debugOptions{MapDebugOptions::NoDebug};
+ std::unique_ptr renderingStatsView;
std::shared_ptr fileSource;
diff --git a/src/mbgl/mtl/buffer_resource.cpp b/src/mbgl/mtl/buffer_resource.cpp
index f60528445..a2d2db61e 100644
--- a/src/mbgl/mtl/buffer_resource.cpp
+++ b/src/mbgl/mtl/buffer_resource.cpp
@@ -81,6 +81,7 @@ BufferResource& BufferResource::operator=(BufferResource&& other) noexcept {
context.renderingStats().numBuffers--;
context.renderingStats().memBuffers -= size;
}
+
buffer = std::move(other.buffer);
raw = std::move(other.raw);
size = other.size;
diff --git a/src/mbgl/mtl/command_encoder.cpp b/src/mbgl/mtl/command_encoder.cpp
index b7f7602e0..96a27ad83 100644
--- a/src/mbgl/mtl/command_encoder.cpp
+++ b/src/mbgl/mtl/command_encoder.cpp
@@ -15,9 +15,7 @@ namespace mtl {
CommandEncoder::CommandEncoder(Context& context_)
: context(context_) {}
-CommandEncoder::~CommandEncoder() {
- context.performCleanup();
-}
+CommandEncoder::~CommandEncoder() {}
std::unique_ptr CommandEncoder::createUploadPass(const char* name, gfx::Renderable& renderable) {
return std::make_unique(renderable, *this, name);
diff --git a/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp b/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp
index 864881718..8e08a7679 100644
--- a/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp
+++ b/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp
@@ -58,7 +58,8 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete
auto& context = parameters.context;
const auto& state = parameters.state;
- const auto& evaluated = static_cast(*evaluatedProperties).evaluated;
+ const auto& symbolLayerProperties = static_cast(*evaluatedProperties);
+ const auto& evaluated = symbolLayerProperties.evaluated;
#if !defined(NDEBUG)
const auto label = layerGroup.getName() + "-update-uniforms";
@@ -94,6 +95,10 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete
#endif
const auto camDist = state.getCameraToCenterDistance();
+ const auto screenSpaceProp = symbolLayerProperties.layerImpl().layout.get();
+ const auto isScreenSpace = screenSpaceProp.isConstant() ? screenSpaceProp.asConstant()
+ : SymbolScreenSpace::defaultValue();
+
visitLayerGroupDrawables(layerGroup, [&](gfx::Drawable& drawable) {
if (!drawable.getTileID() || !drawable.getData()) {
return;
@@ -117,12 +122,20 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete
// from RenderTile::translatedMatrix
const auto translate = isText ? evaluated.get() : evaluated.get();
- const auto anchor = isText ? evaluated.get()
- : evaluated.get();
- constexpr bool nearClipped = false;
- constexpr bool inViewportPixelUnits = false;
- const auto matrix = getTileMatrix(
- tileID, parameters, translate, anchor, nearClipped, inViewportPixelUnits, drawable);
+
+ mat4 matrix;
+
+ if (isScreenSpace) {
+ matrix::ortho(matrix, 0, util::EXTENT, -util::EXTENT, 0, 0, 1);
+ matrix::translate(matrix, matrix, 0, -util::EXTENT, 0);
+ matrix::translate(matrix, matrix, translate[0], translate[1], 0);
+ } else {
+ constexpr bool nearClipped = false;
+ constexpr bool inViewportPixelUnits = false;
+ const auto anchor = isText ? evaluated.get()
+ : evaluated.get();
+ matrix = getTileMatrix(tileID, parameters, translate, anchor, nearClipped, inViewportPixelUnits, drawable);
+ }
// from symbol_program, makeValues
const auto currentZoom = static_cast(parameters.state.getZoom());
diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp
index b44d69b1a..3e5a64208 100644
--- a/src/mbgl/renderer/renderer_impl.cpp
+++ b/src/mbgl/renderer/renderer_impl.cpp
@@ -417,7 +417,7 @@ void Renderer::Impl::render(const RenderTree& renderTree,
const auto startRendering = util::MonotonicTimer::now().count();
// present submits render commands
parameters.encoder->present(parameters.backend.getDefaultRenderable());
- const auto renderingTime = util::MonotonicTimer::now().count() - startRendering;
+ context.renderingStats().renderingTime = util::MonotonicTimer::now().count() - startRendering;
parameters.encoder.reset();
context.endFrame();
@@ -435,14 +435,13 @@ void Renderer::Impl::render(const RenderTree& renderTree,
}
#endif // MLN_RENDER_BACKEND_METAL
- const auto encodingTime = renderTree.getElapsedTime() - renderingTime;
+ context.renderingStats().encodingTime = renderTree.getElapsedTime() - context.renderingStats().renderingTime;
observer->onDidFinishRenderingFrame(
renderTreeParameters.loaded ? RendererObserver::RenderMode::Full : RendererObserver::RenderMode::Partial,
renderTreeParameters.needsRepaint,
renderTreeParameters.placementChanged,
- encodingTime,
- renderingTime);
+ context.renderingStats());
if (!renderTreeParameters.loaded) {
renderState = RenderState::Partial;
diff --git a/src/mbgl/style/layers/symbol_layer.cpp b/src/mbgl/style/layers/symbol_layer.cpp
index 6f7b736c1..a55daf5dd 100644
--- a/src/mbgl/style/layers/symbol_layer.cpp
+++ b/src/mbgl/style/layers/symbol_layer.cpp
@@ -302,6 +302,21 @@ void SymbolLayer::setSymbolPlacement(const PropertyValue& v
baseImpl = std::move(impl_);
observer->onLayerChanged(*this);
}
+PropertyValue SymbolLayer::getDefaultSymbolScreenSpace() {
+ return SymbolScreenSpace::defaultValue();
+}
+
+const PropertyValue& SymbolLayer::getSymbolScreenSpace() const {
+ return impl().layout.get();
+}
+
+void SymbolLayer::setSymbolScreenSpace(const PropertyValue& value) {
+ if (value == getSymbolScreenSpace()) return;
+ auto impl_ = mutableImpl();
+ impl_->layout.get() = value;
+ baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
+}
PropertyValue SymbolLayer::getDefaultSymbolSortKey() {
return SymbolSortKey::defaultValue();
}
@@ -1124,6 +1139,7 @@ enum class Property : uint8_t {
IconTextFitPadding,
SymbolAvoidEdges,
SymbolPlacement,
+ SymbolScreenSpace,
SymbolSortKey,
SymbolSpacing,
SymbolZOrder,
@@ -1202,6 +1218,7 @@ constexpr const auto layerProperties = mapbox::eternal::hash_map SymbolLayer::setPropertyInternal(const std::string& name, c
}
if (property == Property::IconAllowOverlap || property == Property::IconIgnorePlacement ||
property == Property::IconKeepUpright || property == Property::IconOptional ||
- property == Property::SymbolAvoidEdges || property == Property::TextAllowOverlap ||
- property == Property::TextIgnorePlacement || property == Property::TextKeepUpright ||
- property == Property::TextOptional) {
+ property == Property::SymbolAvoidEdges || property == Property::SymbolScreenSpace ||
+ property == Property::TextAllowOverlap || property == Property::TextIgnorePlacement ||
+ property == Property::TextKeepUpright || property == Property::TextOptional) {
Error error;
const auto& typedValue = convert>(value, error, false, false);
if (!typedValue) {
@@ -1582,6 +1601,11 @@ std::optional SymbolLayer::setPropertyInternal(const std::string& name, c
return std::nullopt;
}
+ if (property == Property::SymbolScreenSpace) {
+ setSymbolScreenSpace(*typedValue);
+ return std::nullopt;
+ }
+
if (property == Property::TextAllowOverlap) {
setTextAllowOverlap(*typedValue);
return std::nullopt;
diff --git a/src/mbgl/style/layers/symbol_layer_properties.hpp b/src/mbgl/style/layers/symbol_layer_properties.hpp
index bb418eea1..4fc77ff15 100644
--- a/src/mbgl/style/layers/symbol_layer_properties.hpp
+++ b/src/mbgl/style/layers/symbol_layer_properties.hpp
@@ -96,6 +96,11 @@ struct SymbolPlacement : LayoutProperty {
static SymbolPlacementType defaultValue() { return SymbolPlacementType::Point; }
};
+struct SymbolScreenSpace : LayoutProperty {
+ static constexpr const char *name() { return "symbol-screen-space"; }
+ static bool defaultValue() { return false; }
+};
+
struct SymbolSortKey : DataDrivenLayoutProperty {
static constexpr const char *name() { return "symbol-sort-key"; }
static float defaultValue() { return 0.f; }
@@ -302,6 +307,7 @@ class SymbolLayoutProperties : public Properties<
IconTextFitPadding,
SymbolAvoidEdges,
SymbolPlacement,
+ SymbolScreenSpace,
SymbolSortKey,
SymbolSpacing,
SymbolZOrder,
diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp
index b8a222c51..fe774da2e 100644
--- a/src/mbgl/text/placement.cpp
+++ b/src/mbgl/text/placement.cpp
@@ -974,6 +974,7 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket,
const bool rotateWithMap = bucket.layout->get() == style::AlignmentType::Map;
const bool pitchWithMap = bucket.layout->get() == style::AlignmentType::Map;
const bool hasIconTextFit = bucket.layout->get() != style::IconTextFitType::None;
+ const bool screenSpace = bucket.layout->get();
// If allow-overlap is true, we can show symbols before placement runs on them
// But we have to wait for placement if we potentially depend on a paired icon/text
@@ -981,10 +982,10 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket,
// See https://github.com/mapbox/mapbox-gl-native/issues/12483
// Prevent a flickering issue when showing a symbol allowing overlap.
const JointOpacityState defaultOpacityState(
- bucket.justReloaded && textAllowOverlap &&
+ (screenSpace || bucket.justReloaded) && textAllowOverlap &&
(iconAllowOverlap || !(bucket.hasIconData() || bucket.hasSdfIconData()) ||
bucket.layout->get()),
- bucket.justReloaded && iconAllowOverlap &&
+ (screenSpace || bucket.justReloaded) && iconAllowOverlap &&
(textAllowOverlap || !bucket.hasTextData() || bucket.layout->get()),
true);
diff --git a/src/mbgl/vulkan/buffer_resource.cpp b/src/mbgl/vulkan/buffer_resource.cpp
index b920627a5..4e3c32665 100644
--- a/src/mbgl/vulkan/buffer_resource.cpp
+++ b/src/mbgl/vulkan/buffer_resource.cpp
@@ -129,7 +129,12 @@ BufferResource::BufferResource(BufferResource&& other) noexcept
BufferResource::~BufferResource() noexcept {
if (isValid()) {
context.renderingStats().numBuffers--;
- context.renderingStats().memBuffers -= size;
+
+ if (bufferWindowSize > 0) {
+ context.renderingStats().memBuffers -= bufferWindowSize * context.getBackend().getMaxFrames();
+ } else {
+ context.renderingStats().memBuffers -= size;
+ }
}
if (!bufferAllocation) return;
@@ -146,7 +151,7 @@ BufferResource& BufferResource::operator=(BufferResource&& other) noexcept {
if (isValid()) {
context.renderingStats().numBuffers--;
context.renderingStats().memBuffers -= size;
- };
+ }
size = other.size;
usage = other.usage;
@@ -171,6 +176,7 @@ void BufferResource::update(const void* newData, std::size_t updateSize, std::si
auto& stats = context.renderingStats();
stats.bufferUpdateBytes += updateSize;
stats.bufferUpdates++;
+ stats.bufferObjUpdates++;
version++;
if (bufferWindowSize) {
diff --git a/src/mbgl/vulkan/context.cpp b/src/mbgl/vulkan/context.cpp
index f5a8ba331..508426276 100644
--- a/src/mbgl/vulkan/context.cpp
+++ b/src/mbgl/vulkan/context.cpp
@@ -67,6 +67,12 @@ Context::~Context() noexcept {
if (--glslangRefCount == 0) {
glslang::FinalizeProcess();
}
+
+#if !defined(NDEBUG)
+ Log::Debug(Event::General, "Rendering Stats:\n" + stats.toString("\n"));
+#endif
+
+ assert(stats.isZero());
}
void Context::initFrameResources() {
@@ -134,10 +140,20 @@ void Context::destroyResources() {
frame.runDeletionQueue(*this);
}
- globalUniformBuffers.freeDescriptorSets();
-
// all resources have unique handles
frameResources.clear();
+
+ globalUniformBuffers.freeDescriptorSets();
+
+ for (size_t i = 0; i < globalUniformBuffers.allocatedSize(); i++) {
+ globalUniformBuffers.set(i, nullptr);
+ }
+
+ dummyBuffer.reset();
+ dummyTexture2D.reset();
+
+ clipping.indexBuffer.reset();
+ clipping.vertexBuffer.reset();
}
void Context::enqueueDeletion(std::function&& function) {
@@ -355,6 +371,11 @@ UniqueShaderProgram Context::createProgram(shaders::BuiltIn shaderID,
return program;
}
+void Context::performCleanup() {
+ stats.numDrawCalls = 0;
+ ++stats.numFrames;
+}
+
gfx::UniqueDrawableBuilder Context::createDrawableBuilder(std::string name) {
return std::make_unique(std::move(name));
}
diff --git a/src/mbgl/vulkan/texture2d.cpp b/src/mbgl/vulkan/texture2d.cpp
index 8f2da1495..235848d77 100644
--- a/src/mbgl/vulkan/texture2d.cpp
+++ b/src/mbgl/vulkan/texture2d.cpp
@@ -44,9 +44,6 @@ Texture2D::Texture2D(Context& context_)
Texture2D::~Texture2D() {
destroyTexture();
destroySampler();
-
- context.renderingStats().numActiveTextures--;
- context.renderingStats().memTextures -= Texture2D::getDataSize();
}
gfx::Texture2D& Texture2D::setSamplerConfiguration(const SamplerState& samplerState_) noexcept {
@@ -209,6 +206,7 @@ void Texture2D::uploadSubRegion(const void* pixelData,
context.enqueueDeletion([buffAlloc = std::move(bufferAllocation)](auto&) mutable { buffAlloc.reset(); });
context.renderingStats().numTextureUpdates++;
+ context.renderingStats().textureUpdateBytes += bufferInfo.size;
}
vk::Format Texture2D::vulkanFormat(const gfx::TexturePixelType pixel, gfx::TextureChannelDataType channel) {
@@ -402,6 +400,9 @@ void Texture2D::destroyTexture() {
context.enqueueDeletion([allocation = std::move(imageAllocation)](auto&) mutable { allocation.reset(); });
imageLayout = vk::ImageLayout::eUndefined;
+
+ context.renderingStats().numActiveTextures--;
+ context.renderingStats().memTextures -= Texture2D::getDataSize();
}
}
diff --git a/test/src/mbgl/test/stub_map_observer.hpp b/test/src/mbgl/test/stub_map_observer.hpp
index 76972a62d..4b81ac699 100644
--- a/test/src/mbgl/test/stub_map_observer.hpp
+++ b/test/src/mbgl/test/stub_map_observer.hpp
@@ -32,7 +32,7 @@ public:
}
}
- void onDidFinishRenderingFrame(RenderFrameStatus status) final {
+ void onDidFinishRenderingFrame(const RenderFrameStatus& status) final {
if (didFinishRenderingFrameCallback) {
didFinishRenderingFrameCallback(status);
}
diff --git a/test/util/action_journal.test.cpp b/test/util/action_journal.test.cpp
index 5a131099d..d97a8206a 100644
--- a/test/util/action_journal.test.cpp
+++ b/test/util/action_journal.test.cpp
@@ -416,7 +416,7 @@ TEST(ActionJournal, ValidateEvents) {
"");
const auto onDidFinishRenderingFrame =
- static_cast(
+ static_cast(
&RendererObserver::onDidFinishRenderingFrame);
validateEvent(test,
@@ -426,6 +426,5 @@ TEST(ActionJournal, ValidateEvents) {
RendererObserver::RenderMode::Full,
false,
false,
- 0.0,
- 0.0);
+ gfx::RenderingStats());
}