Rendering statistics view (#3322)

Co-authored-by: Bart Louwers <bart@emeel.net>
This commit is contained in:
Adrian Cojocaru
2025-06-03 18:59:51 +03:00
committed by GitHub
parent 3c07cacc4d
commit 2a7249a3b0
81 changed files with 1271 additions and 128 deletions

View File

@@ -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

View File

@@ -2,14 +2,27 @@
#include <cstddef>
#include <string>
#include <memory>
#include <mbgl/util/color.hpp>
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

View File

@@ -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

View File

@@ -145,6 +145,9 @@ public:
void setDebug(MapDebugOptions);
MapDebugOptions getDebug() const;
bool isRenderingStatsViewEnabled() const;
void enableRenderingStatsView(bool value);
bool isFullyLoaded() const;
void dumpDebugLogs() const;

View File

@@ -7,6 +7,7 @@
#include <mbgl/style/sprite.hpp>
#include <mbgl/text/glyph.hpp>
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/gfx/rendering_stats.hpp>
#include <cstdint>
#include <string>
@@ -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() {}

View File

@@ -2,6 +2,7 @@
#include <mbgl/tile/tile_id.hpp>
#include <mbgl/util/font_stack.hpp>
#include <mbgl/gfx/rendering_stats.hpp>
#include <mbgl/text/glyph_range.hpp>
#include <mbgl/tile/tile_operation.hpp>
#include <mbgl/gfx/backend.hpp>
@@ -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() {}

View File

@@ -89,6 +89,10 @@ public:
const PropertyValue<SymbolPlacementType>& getSymbolPlacement() const;
void setSymbolPlacement(const PropertyValue<SymbolPlacementType>&);
static PropertyValue<bool> getDefaultSymbolScreenSpace();
const PropertyValue<bool>& getSymbolScreenSpace() const;
void setSymbolScreenSpace(const PropertyValue<bool>&);
static PropertyValue<float> getDefaultSymbolSortKey();
const PropertyValue<float>& getSymbolSortKey() const;
void setSymbolSortKey(const PropertyValue<float>&);

View File

@@ -70,7 +70,7 @@ public:
const mbgl::unordered_map<std::string, std::string>& additionalDefines);
/// Called at the end of a frame.
void performCleanup() override {}
void performCleanup() override;
void reduceMemoryUsage() override {}
gfx::UniqueDrawableBuilder createDrawableBuilder(std::string name) override;

View File

@@ -1,5 +1,6 @@
#pragma once
#include <mbgl/gfx/vertex_buffer.hpp>
#include <mbgl/vulkan/buffer_resource.hpp>
#include <memory>
@@ -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(); }

View File

@@ -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

View File

@@ -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); }

View File

@@ -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);

View File

@@ -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<NativeMapView>::Singleton(*_env);
static auto onDidFinishRenderingFrame = javaClass.GetMethod<void(jboolean, jdouble, jdouble)>(
static auto onDidFinishRenderingFrame = javaClass.GetMethod<void(jboolean, jni::Object<RenderingStats>)>(
*_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&) {};

View File

@@ -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<jni::Object<RenderingStats>> renderingStats;
// Minimum texture size according to OpenGL ES 2.0 specification.
int width = 64;
int height = 64;

View File

@@ -0,0 +1,61 @@
#include "rendering_stats.hpp"
namespace mbgl {
namespace android {
void RenderingStats::registerNative(jni::JNIEnv& env) {
jni::Class<RenderingStats>::Singleton(env);
}
jni::Local<jni::Object<RenderingStats>> RenderingStats::Create(jni::JNIEnv& env) {
auto& javaClass = jni::Class<RenderingStats>::Singleton(env);
auto constructor = javaClass.GetConstructor(env);
return javaClass.New(env, constructor);
}
void RenderingStats::Update(jni::JNIEnv& env,
jni::Object<RenderingStats>& javaObject,
const gfx::RenderingStats& stats) {
static auto& javaClass = jni::Class<RenderingStats>::Singleton(env);
#define SetField(name, type) \
static auto name##Field = javaClass.GetField<type>(env, #name); \
javaObject.Set(env, name##Field, static_cast<type>(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

View File

@@ -0,0 +1,22 @@
#pragma once
#include <mbgl/gfx/rendering_stats.hpp>
#include <string>
#include <jni.h>
#include <jni/jni.hpp>
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<jni::Object<RenderingStats>> Create(jni::JNIEnv&);
static void Update(jni::JNIEnv&, jni::Object<RenderingStats>&, const gfx::RenderingStats&);
};
} // namespace android
} // namespace mbgl

View File

@@ -249,6 +249,11 @@ jni::Local<jni::Object<>> SymbolLayer::getTextOptional(jni::JNIEnv& env) {
return std::move(*convert<jni::Local<jni::Object<>>>(env, toSymbolLayer(layer).getTextOptional()));
}
jni::Local<jni::Object<>> SymbolLayer::getSymbolScreenSpace(jni::JNIEnv& env) {
using namespace mbgl::android::conversion;
return std::move(*convert<jni::Local<jni::Object<>>>(env, toSymbolLayer(layer).getSymbolScreenSpace()));
}
jni::Local<jni::Object<>> SymbolLayer::getIconOpacity(jni::JNIEnv& env) {
using namespace mbgl::android::conversion;
return std::move(*convert<jni::Local<jni::Object<>>>(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"),

View File

@@ -110,6 +110,8 @@ public:
jni::Local<jni::Object<jni::ObjectTag>> getTextOptional(jni::JNIEnv&);
jni::Local<jni::Object<jni::ObjectTag>> getSymbolScreenSpace(jni::JNIEnv&);
jni::Local<jni::Object<jni::ObjectTag>> getIconOpacity(jni::JNIEnv&);
void setIconOpacityTransition(jni::JNIEnv&, jlong duration, jlong delay);
jni::Local<jni::Object<TransitionOptions>> getIconOpacityTransition(jni::JNIEnv&);

View File

@@ -23,6 +23,8 @@ class MapChangeReceiver implements NativeMapView.StateCallback {
= new CopyOnWriteArrayList<>();
private final List<MapView.OnDidFinishRenderingFrameListener> onDidFinishRenderingFrameList
= new CopyOnWriteArrayList<>();
private final List<MapView.OnDidFinishRenderingFrameWithStatsListener> onDidFinishRenderingFrameWithStatsList
= new CopyOnWriteArrayList<>();
private final List<MapView.OnWillStartRenderingMapListener> onWillStartRenderingMapListenerList
= new CopyOnWriteArrayList<>();
private final List<MapView.OnDidFinishRenderingMapListener> 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);
}

View File

@@ -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);
}

View File

@@ -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 {
* </p>
*/
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.
* <p>
* {@link MapView#addOnDidFinishRenderingFrameListener(OnDidFinishRenderingFrameListener)}
* </p>
*/
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.
* <p>
@@ -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<OnMapReadyCallback> 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();
}

View File

@@ -250,6 +250,10 @@ interface NativeMap {
void triggerRepaint();
boolean isRenderingStatsViewEnabled();
void enableRenderingStatsView(boolean value);
void setSwapBehaviorFlush(boolean flush);
//

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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<Boolean> 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<Expression> 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.
*

View File

@@ -649,6 +649,18 @@ public class SymbolLayer extends Layer {
return (PropertyValue<Boolean>) new PropertyValue("text-optional", nativeGetTextOptional());
}
/**
* Get the SymbolScreenSpace property
*
* @return property wrapper value around Boolean
*/
@NonNull
@SuppressWarnings("unchecked")
public PropertyValue<Boolean> getSymbolScreenSpace() {
checkThread();
return (PropertyValue<Boolean>) 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();

View File

@@ -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();
}
}

View File

@@ -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()
}

View File

@@ -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() {

View File

@@ -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)

View File

@@ -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() {

View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -0,0 +1,83 @@
#import <Foundation/Foundation.h>
#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

View File

@@ -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

View File

@@ -0,0 +1,12 @@
#import <mbgl/gfx/rendering_stats.hpp>
#import "MLNRenderingStats.h"
NS_ASSUME_NONNULL_BEGIN
@interface MLNRenderingStats (Private)
- (void)setCoreData:(const mbgl::gfx::RenderingStats&)stats;
@end
NS_ASSUME_NONNULL_END

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -63,7 +63,10 @@ Enabling the action journal.
<!-- include-example(actionJournalOptions) -->
```swift
MLNSettings.actionJournalOptions.enabled = true
let options = MLNMapOptions()
options.actionJournalOptions.enabled = true
options.styleURL = AMERICANA_STYLE
mapView = MLNMapView(frame: view.bounds, options: options)
```
<!-- include-example(ObserverExampleActionJournal) -->

View File

@@ -57,6 +57,7 @@ Powerful, free and open-source mapping toolkit with full control over data sourc
### Advanced
- <doc:CustomStyleLayerExample>
- <doc:RenderingStatisticsHud>
### Other Articles

View File

@@ -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:``.
<!-- include-example(ObserverExampleRenderingStats) -->
```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:``.

View File

@@ -0,0 +1,13 @@
# Rendering Statistics HUD
Show rendering statistics on the map
Enable the rendering HUD with:
<!-- include-example(enableRenderingStatsView) -->
```swift
mapView.enableRenderingStatsView(true)
```
![](RenderingStatistics.png)

View File

@@ -35,7 +35,7 @@ struct MapLibreNavigationView: View {
AddMarkerSymbolExampleUIViewControllerRepresentable()
}
NavigationLink("ObserverExample") {
ObserverExampleViewExampleUIViewControllerRepresentable()
ObserverExampleViewUIViewControllerRepresentable()
}
Group {
NavigationLink("AnimatedLineExample") {

View File

@@ -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 {

View File

@@ -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];
}
}

View File

@@ -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;

View File

@@ -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() {

View File

@@ -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.

View File

@@ -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();

View File

@@ -1,6 +1,7 @@
#import <UIKit/UIKit.h>
#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

View File

@@ -3,11 +3,14 @@
#import "MLNTileOperation.h"
#import "MLNUserLocationAnnotationView.h"
#include <mbgl/util/size.hpp>
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;

View File

@@ -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]) {}

View File

@@ -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;

View File

@@ -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() {

View File

@@ -61,6 +61,7 @@
#import "MLNLoggingConfiguration_Private.h"
#import "MLNReachability.h"
#import "MLNActionJournalOptions_Private.h"
#import "MLNRenderingStats_Private.h"
#import "MLNSettings_Private.h"
#import <CoreImage/CIFilter.h>
@@ -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];
}
}

View File

@@ -1,5 +1,6 @@
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#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

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,8 +1,14 @@
#include <mbgl/gfx/rendering_stats.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/style/sources/custom_geometry_source.hpp>
#include <mbgl/style/layers/symbol_layer.hpp>
#include <mbgl/style/layers/symbol_layer_impl.hpp>
#include <mbgl/util/monotonic_timer.hpp>
#include <algorithm>
#include <initializer_list>
#include <sstream>
#include <iomanip>
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::CustomGeometrySource*>(style.getSource(sourceID));
if (!source) {
return;
}
mbgl::FeatureCollection features;
mapbox::geojson::feature feature;
feature.geometry = mapbox::geometry::geometry<double>(Point<double>{0, 0});
features.emplace_back(feature);
source->setTileData(tileID, features);
};
style.addSource(std::make_unique<style::CustomGeometrySource>(sourceID, sourceOptions));
}
if (!style.getLayer(layerID)) {
auto infoLayer = std::make_unique<style::SymbolLayer>(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<float, 2>{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<mbgl::style::SymbolLayer*>(style.getLayer(layerID));
}
namespace {
template <typename T>
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 <typename T>
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

View File

@@ -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<gl::UniformBufferGL>(data, size, *uboAllocator);
return std::make_shared<gl::UniformBufferGL>(*this, data, size, *uboAllocator);
}
gfx::UniqueUniformBufferArray Context::createLayerUniformBufferArray() {
@@ -622,6 +626,7 @@ void Context::clear(std::optional<mbgl::Color> color, std::optional<float> 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<GLvoid*>(sizeof(uint16_t) * indexOffset)));
stats.numDrawCalls++;
stats.totalDrawCalls++;
}
void Context::performCleanup() {

View File

@@ -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

View File

@@ -187,6 +187,9 @@ void Texture2D::uploadSubRegion(const void* pixelData, const Size& size_, uint16
Enum<gfx::TexturePixelType>::to(pixelFormat),
Enum<gfx::TextureChannelDataType>::to(channelType),
pixelData));
context.renderingStats().numTextureUpdates++;
context.renderingStats().textureUpdateBytes += static_cast<size_t>(getPixelStride() * size_.width * size_.height);
}
void Texture2D::upload() noexcept {

View File

@@ -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 {

View File

@@ -31,8 +31,7 @@ std::unique_ptr<gfx::VertexBufferResource> UploadPass::createVertexBufferResourc
bool /*persistent*/) {
BufferID id = 0;
MBGL_CHECK_ERROR(glGenBuffers(1, &id));
commandEncoder.context.renderingStats().numBuffers++;
commandEncoder.context.renderingStats().memVertexBuffers += static_cast<int>(size);
// NOLINTNEXTLINE(performance-move-const-arg)
UniqueBuffer result{std::move(id), {commandEncoder.context}};
commandEncoder.context.vertexBuffer = result;
@@ -43,6 +42,11 @@ std::unique_ptr<gfx::VertexBufferResource> UploadPass::createVertexBufferResourc
void UploadPass::updateVertexBufferResource(gfx::VertexBufferResource& resource, const void* data, std::size_t size) {
commandEncoder.context.vertexBuffer = static_cast<gl::VertexBufferResource&>(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<gfx::IndexBufferResource> UploadPass::createIndexBufferResource(const void* data,
@@ -51,8 +55,7 @@ std::unique_ptr<gfx::IndexBufferResource> UploadPass::createIndexBufferResource(
bool /*persistent*/) {
BufferID id = 0;
MBGL_CHECK_ERROR(glGenBuffers(1, &id));
commandEncoder.context.renderingStats().numBuffers++;
commandEncoder.context.renderingStats().memIndexBuffers += static_cast<int>(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<gl::IndexBufferResource&>(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 {

View File

@@ -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

View File

@@ -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;
}

View File

@@ -8,6 +8,7 @@
#include <mbgl/util/traits.hpp>
#include <mbgl/util/action_journal.hpp>
#include <mbgl/util/action_journal_impl.hpp>
#include <mbgl/gfx/rendering_stats.hpp>
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<gfx::RenderingStatsView>();
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<void()>& done) {
if (!style->getImage(id)) {
observer.onStyleImageMissing(id);

View File

@@ -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<void()>&) 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<util::ActionJournal> actionJournal;
@@ -87,6 +90,7 @@ public:
const bool crossSourceCollisions;
MapDebugOptions debugOptions{MapDebugOptions::NoDebug};
std::unique_ptr<gfx::RenderingStatsView> renderingStatsView;
std::shared_ptr<FileSource> fileSource;

View File

@@ -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;

View File

@@ -15,9 +15,7 @@ namespace mtl {
CommandEncoder::CommandEncoder(Context& context_)
: context(context_) {}
CommandEncoder::~CommandEncoder() {
context.performCleanup();
}
CommandEncoder::~CommandEncoder() {}
std::unique_ptr<gfx::UploadPass> CommandEncoder::createUploadPass(const char* name, gfx::Renderable& renderable) {
return std::make_unique<UploadPass>(renderable, *this, name);

View File

@@ -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<const SymbolLayerProperties&>(*evaluatedProperties).evaluated;
const auto& symbolLayerProperties = static_cast<const SymbolLayerProperties&>(*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<SymbolScreenSpace>();
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<style::TextTranslate>() : evaluated.get<style::IconTranslate>();
const auto anchor = isText ? evaluated.get<style::TextTranslateAnchor>()
: evaluated.get<style::IconTranslateAnchor>();
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<style::TextTranslateAnchor>()
: evaluated.get<style::IconTranslateAnchor>();
matrix = getTileMatrix(tileID, parameters, translate, anchor, nearClipped, inViewportPixelUnits, drawable);
}
// from symbol_program, makeValues
const auto currentZoom = static_cast<float>(parameters.state.getZoom());

View File

@@ -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;

View File

@@ -302,6 +302,21 @@ void SymbolLayer::setSymbolPlacement(const PropertyValue<SymbolPlacementType>& v
baseImpl = std::move(impl_);
observer->onLayerChanged(*this);
}
PropertyValue<bool> SymbolLayer::getDefaultSymbolScreenSpace() {
return SymbolScreenSpace::defaultValue();
}
const PropertyValue<bool>& SymbolLayer::getSymbolScreenSpace() const {
return impl().layout.get<SymbolScreenSpace>();
}
void SymbolLayer::setSymbolScreenSpace(const PropertyValue<bool>& value) {
if (value == getSymbolScreenSpace()) return;
auto impl_ = mutableImpl();
impl_->layout.get<SymbolScreenSpace>() = value;
baseImpl = std::move(impl_);
observer->onLayerChanged(*this);
}
PropertyValue<float> 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<mapbox::eternal
{"icon-text-fit-padding", toUint8(Property::IconTextFitPadding)},
{"symbol-avoid-edges", toUint8(Property::SymbolAvoidEdges)},
{"symbol-placement", toUint8(Property::SymbolPlacement)},
{"symbol-screen-space", toUint8(Property::SymbolScreenSpace)},
{"symbol-sort-key", toUint8(Property::SymbolSortKey)},
{"symbol-spacing", toUint8(Property::SymbolSpacing)},
{"symbol-z-order", toUint8(Property::SymbolZOrder)},
@@ -1319,6 +1336,8 @@ StyleProperty getLayerProperty(const SymbolLayer& layer, Property property) {
return makeStyleProperty(layer.getSymbolAvoidEdges());
case Property::SymbolPlacement:
return makeStyleProperty(layer.getSymbolPlacement());
case Property::SymbolScreenSpace:
return makeStyleProperty(layer.getSymbolScreenSpace());
case Property::SymbolSortKey:
return makeStyleProperty(layer.getSymbolSortKey());
case Property::SymbolSpacing:
@@ -1548,9 +1567,9 @@ std::optional<Error> 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<PropertyValue<bool>>(value, error, false, false);
if (!typedValue) {
@@ -1582,6 +1601,11 @@ std::optional<Error> 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;

View File

@@ -96,6 +96,11 @@ struct SymbolPlacement : LayoutProperty<SymbolPlacementType> {
static SymbolPlacementType defaultValue() { return SymbolPlacementType::Point; }
};
struct SymbolScreenSpace : LayoutProperty<bool> {
static constexpr const char *name() { return "symbol-screen-space"; }
static bool defaultValue() { return false; }
};
struct SymbolSortKey : DataDrivenLayoutProperty<float> {
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,

View File

@@ -974,6 +974,7 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket,
const bool rotateWithMap = bucket.layout->get<style::TextRotationAlignment>() == style::AlignmentType::Map;
const bool pitchWithMap = bucket.layout->get<style::TextPitchAlignment>() == style::AlignmentType::Map;
const bool hasIconTextFit = bucket.layout->get<style::IconTextFit>() != style::IconTextFitType::None;
const bool screenSpace = bucket.layout->get<style::SymbolScreenSpace>();
// 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<style::IconOptional>()),
bucket.justReloaded && iconAllowOverlap &&
(screenSpace || bucket.justReloaded) && iconAllowOverlap &&
(textAllowOverlap || !bucket.hasTextData() || bucket.layout->get<style::TextOptional>()),
true);

View File

@@ -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) {

View File

@@ -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<void(Context&)>&& 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<DrawableBuilder>(std::move(name));
}

View File

@@ -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();
}
}

View File

@@ -32,7 +32,7 @@ public:
}
}
void onDidFinishRenderingFrame(RenderFrameStatus status) final {
void onDidFinishRenderingFrame(const RenderFrameStatus& status) final {
if (didFinishRenderingFrameCallback) {
didFinishRenderingFrameCallback(status);
}

View File

@@ -416,7 +416,7 @@ TEST(ActionJournal, ValidateEvents) {
"");
const auto onDidFinishRenderingFrame =
static_cast<void (RendererObserver::*)(RendererObserver::RenderMode, bool, bool, double, double)>(
static_cast<void (RendererObserver::*)(RendererObserver::RenderMode, bool, bool, const gfx::RenderingStats&)>(
&RendererObserver::onDidFinishRenderingFrame);
validateEvent(test,
@@ -426,6 +426,5 @@ TEST(ActionJournal, ValidateEvents) {
RendererObserver::RenderMode::Full,
false,
false,
0.0,
0.0);
gfx::RenderingStats());
}