Add run-time plug-in layer support to iOS (#3430)

This commit is contained in:
AtlasProgramming
2025-06-26 00:27:32 -04:00
committed by GitHub
parent 779b893468
commit 5171f6b966
45 changed files with 4259 additions and 13 deletions

View File

@@ -4,6 +4,8 @@ load("@npm//:defs.bzl", "npm_link_all_packages")
load("@rules_cc//cc:defs.bzl", "cc_library")
load(
"//bazel:core.bzl",
"MLN_LAYER_PLUGIN_HEADERS",
"MLN_LAYER_PLUGIN_SOURCE",
"MLN_CORE_HEADERS",
"MLN_CORE_SOURCE",
"MLN_DRAWABLES_GL_HEADERS",
@@ -76,14 +78,15 @@ cc_library(
# the prefix from the path.
cc_library(
name = "mbgl-core",
srcs = MLN_CORE_SOURCE +
srcs = MLN_LAYER_PLUGIN_SOURCE +
MLN_CORE_SOURCE +
MLN_GENERATED_SHADER_SOURCE +
MLN_GENERATED_STYLE_SOURCE + select({
":drawable_renderer": MLN_OPENGL_SOURCE + MLN_DRAWABLES_SOURCE + MLN_DRAWABLES_GL_SOURCE,
":metal_renderer": MLN_DRAWABLES_SOURCE + MLN_DRAWABLES_MTL_SOURCE,
"//conditions:default": [],
}),
hdrs = MLN_CORE_HEADERS + select({
hdrs = MLN_LAYER_PLUGIN_HEADERS + MLN_CORE_HEADERS + select({
":drawable_renderer": MLN_OPENGL_HEADERS + MLN_DRAWABLES_HEADERS + MLN_DRAWABLES_GL_HEADERS,
":metal_renderer": MLN_DRAWABLES_HEADERS + MLN_DRAWABLES_MTL_HEADERS,
"//conditions:default": [],

View File

@@ -966,6 +966,19 @@ list(APPEND SRC_FILES
${PROJECT_SOURCE_DIR}/src/mbgl/util/version.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/util/work_request.cpp
)
list(APPEND SRC_FILES
${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_render.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_properties.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_impl.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_factory.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_render.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_properties.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_impl.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_factory.cpp
)
if(MLN_WITH_OPENGL)
message(STATUS "Configuring GL-Native with OpenGL renderer backend")

View File

@@ -1,3 +1,19 @@
MLN_LAYER_PLUGIN_HEADERS = [
"src/mbgl/plugin/plugin_layer.hpp",
"src/mbgl/plugin/plugin_layer_factory.hpp",
"src/mbgl/plugin/plugin_layer_impl.hpp",
"src/mbgl/plugin/plugin_layer_render.hpp",
"src/mbgl/plugin/plugin_layer_properties.hpp",
]
MLN_LAYER_PLUGIN_SOURCE = [
"src/mbgl/plugin/plugin_layer.cpp",
"src/mbgl/plugin/plugin_layer_factory.cpp",
"src/mbgl/plugin/plugin_layer_impl.cpp",
"src/mbgl/plugin/plugin_layer_render.cpp",
"src/mbgl/plugin/plugin_layer_properties.cpp",
]
MLN_PUBLIC_GENERATED_STYLE_HEADERS = [
"include/mbgl/style/layers/circle_layer.hpp",
"include/mbgl/style/layers/fill_layer.hpp",

View File

@@ -67,6 +67,15 @@ public:
*/
static const bool annotationsEnabled;
/**
* Enables a layer type for JSON style only.
*
* We might not want to expose runtime API for some layer types
* in order to save binary size (the corresponding SDK layer wrappers
* should be excluded from the project build).
*/
virtual void addLayerTypeCoreOnly(std::unique_ptr<mbgl::LayerFactory>);
protected:
virtual ~LayerManager() = default;
virtual LayerFactory* getFactory(const std::string& type) noexcept = 0;

View File

@@ -90,6 +90,10 @@ struct LayerTypeInfo {
} tileKind;
};
// Added this to support plugins and that their LayerTypeInfo isn't the same point
// across the board
bool layerTypeInfoEquals(const mbgl::style::LayerTypeInfo* one, const mbgl::style::LayerTypeInfo* other);
/**
* The runtime representation of a [layer](https://maplibre.org/maplibre-style-spec/#layers)
* from the MapLibre Style Spec.

View File

@@ -266,6 +266,10 @@ objc_library(
objc_library(
name = "iosapp",
srcs = [
"//platform/darwin:app/PluginLayerExample.h",
"//platform/darwin:app/PluginLayerExample.mm",
"//platform/darwin:app/PluginLayerExampleMetalRendering.h",
"//platform/darwin:app/PluginLayerExampleMetalRendering.mm",
"//platform/darwin:app/CustomStyleLayerExample.h",
"//platform/darwin:app/CustomStyleLayerExample.m",
"//platform/ios:ios_app_srcs",

View File

@@ -174,6 +174,7 @@ swift_library(
data = [
"test/amsterdam.geojson",
"test/one-liner.json",
"app/PluginLayerTestStyle.json",
],
visibility = [
"//platform/ios/test:__pkg__",
@@ -297,6 +298,11 @@ objc_library(
exports_files(
[
"app/PluginLayerTestStyle.json",
"app/PluginLayerExample.h",
"app/PluginLayerExample.mm",
"app/PluginLayerExampleMetalRendering.h",
"app/PluginLayerExampleMetalRendering.mm",
"test/amsterdam.geojson",
"test/MLNSDKTestHelpers.swift",
"app/CustomStyleLayerExample.h",

View File

@@ -0,0 +1,9 @@
#import <Foundation/Foundation.h>
#import "MLNPluginLayer.h"
NS_ASSUME_NONNULL_BEGIN
@interface PluginLayerExample : MLNPluginLayer
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,30 @@
#import "PluginLayerExample.h"
@implementation PluginLayerExample
// This is the layer type in the style that is used
+(MLNPluginLayerCapabilities *)layerCapabilities {
MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init];
tempResult.layerID = @"plugin-layer-test";
tempResult.requiresPass3D = YES;
return tempResult;
}
// The overrides
-(void)onRenderLayer {
NSLog(@"PluginLayerExample: On Render Layer");
}
-(void)onUpdateLayer {
}
-(void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
// NSLog(@"Layer Properties: %@", layerProperties);
}
@end

View File

@@ -0,0 +1,17 @@
/*
PluginLayerExampleMetalRendering
This is a port lf the CustomStyleLayerExample into the plug-in architecture. It assumes
that the rendering is being done in metal since it's added to the core via a darwin method
*/
#import "MLNPluginLayer.h"
NS_ASSUME_NONNULL_BEGIN
@interface PluginLayerExampleMetalRendering : MLNPluginLayer
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,233 @@
/*
PluginLayerExampleMetalRendering
This is a port lf the CustomStyleLayerExample into the plug-in architecture. It assumes
that the rendering is being done in metal since it's added to the core via a darwin method
*/
#import "PluginLayerExampleMetalRendering.h"
#import <MetalKit/MetalKit.h>
#import "Mapbox.h"
typedef struct
{
vector_float2 position;
vector_float4 color;
} Vertex;
@interface PluginLayerExampleMetalRendering () {
// The render pipeline state
id<MTLRenderPipelineState> _pipelineState;
id<MTLDepthStencilState> _depthStencilStateWithoutStencil;
// Properties
float _offsetX;
float _offsetY;
float _scale;
float _r, _g, _b, _a;
}
@end
@implementation PluginLayerExampleMetalRendering
// This is a static class method that is used by the MLNMapView to create the
// required marshalling classes to interface with the MapLibre core.
+(MLNPluginLayerCapabilities *)layerCapabilities {
MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init];
tempResult.layerID = @"plugin-layer-metal-rendering";
tempResult.requiresPass3D = YES;
// Define the paint properties that this layer implements and
// what types they are
tempResult.layerProperties = @[
// The scale property
[MLNPluginLayerProperty propertyWithName:@"scale"
propertyType:MLNPluginLayerPropertyTypeSingleFloat
defaultValue:@(1.0)],
// The fill color property
[MLNPluginLayerProperty propertyWithName:@"fill-color"
propertyType:MLNPluginLayerPropertyTypeColor
defaultValue:[UIColor blueColor]]
];
return tempResult;
}
- (void)createShaders:(MLNMapView *)mapView {
MLNBackendResource* resource = [mapView backendResource];
NSString *shaderSource = @
" #include <metal_stdlib>\n"
" using namespace metal;\n"
" typedef struct\n"
" {\n"
" vector_float2 position;\n"
" vector_float4 color;\n"
" } Vertex;\n"
" struct RasterizerData\n"
" {\n"
" float4 position [[position]];\n"
" float4 color;\n"
" };\n"
" vertex RasterizerData\n"
" vertexShader(uint vertexID [[vertex_id]],\n"
" constant Vertex *vertices [[buffer(0)]],\n"
" constant vector_uint2 *viewportSizePointer [[buffer(1)]])\n"
" {\n"
" RasterizerData out;\n"
" float2 pixelSpacePosition = vertices[vertexID].position.xy;\n"
" vector_float2 viewportSize = vector_float2(*viewportSizePointer);\n"
" out.position = vector_float4(0.0, 0.0, 0.0, 1.0);\n"
" out.position.xy = pixelSpacePosition / (viewportSize / 2.0);\n"
" out.color = vertices[vertexID].color;\n"
" return out;\n"
" }\n"
" fragment float4 fragmentShader(RasterizerData in [[stage_in]])\n"
" {\n"
" return in.color;\n"
" }\n";
NSError *error = nil;
id<MTLDevice> _device = resource.device;
id<MTLLibrary> library = [_device newLibraryWithSource:shaderSource options:nil error:&error];
NSAssert(library, @"Error compiling shaders: %@", error);
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"vertexShader"];
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragmentShader"];
// Configure a pipeline descriptor that is used to create a pipeline state.
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"Simple Pipeline";
pipelineStateDescriptor.vertexFunction = vertexFunction;
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = resource.mtkView.colorPixelFormat;
pipelineStateDescriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
pipelineStateDescriptor.stencilAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
error:&error];
NSAssert(_pipelineState, @"Failed to create pipeline state: %@", error);
// Notice that we don't configure the stencilTest property, leaving stencil testing disabled
MTLDepthStencilDescriptor *depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init];
depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; // Or another value as needed
depthStencilDescriptor.depthWriteEnabled = NO;
_depthStencilStateWithoutStencil = [_device newDepthStencilStateWithDescriptor:depthStencilDescriptor];
}
// Setup the rendering environment
-(void)setupRendering:(MLNMapView *)mapView {
[self createShaders:mapView];
if (_scale == 0) {
_scale = 1;
}
// Set the initial
_a = 1;
}
// The overrides
-(void)onRenderLayer:(MLNMapView *)mapView
renderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder {
MLNBackendResource* resource = [mapView backendResource];
if (_pipelineState == nil) {
[self setupRendering:mapView];
}
if (renderEncoder == nil) {
return;
}
vector_uint2 _viewportSize;
_viewportSize.x = resource.mtkView.drawableSize.width;
_viewportSize.y = resource.mtkView.drawableSize.height;
Vertex triangleVerticesWithColor[] = {
// 2D positions, RGBA colors
{ { (250 + _offsetX) * _scale, (-250 + _offsetY) * _scale }, { 1, 1, 1, _a } },
{ { (-250 + _offsetX) * _scale, (-250 + _offsetY) * _scale }, { 1, 1, 1, _a} },
{ { (0 + _offsetX) * _scale, (250 + _offsetY) * _scale }, { _r, _g, _b, _a } },
};
[renderEncoder setRenderPipelineState:_pipelineState];
[renderEncoder setDepthStencilState:_depthStencilStateWithoutStencil];
// Pass in the parameter data.
[renderEncoder setVertexBytes:triangleVerticesWithColor
length:sizeof(triangleVerticesWithColor)
atIndex:0];
[renderEncoder setVertexBytes:&_viewportSize
length:sizeof(_viewportSize)
atIndex:1];
// Draw the triangle.
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:3];
}
-(void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext {
// This is called before the render call and is a place where
// any animations/etc can be updated
}
-(void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
//NSLog(@"Metal Layer Rendering Properties: %@", layerProperties);
// These two properties are in the "properties" section of the style sheet and
// are passed in at layer creation. These are not expression based properties
NSNumber *offsetX = [layerProperties objectForKey:@"offset-x"];
if (offsetX) {
_offsetX = [[layerProperties objectForKey:@"offset-x"] floatValue];
}
NSNumber *offsetY = [layerProperties objectForKey:@"offset-y"];
if (offsetY) {
_offsetY = [[layerProperties objectForKey:@"offset-y"] floatValue];
}
// These are the paint properties and can be updated each frame or zoom level
// change/etc depending on if they are static or expression based/etc.
NSNumber *scale = [layerProperties objectForKey:@"scale"];
if (scale) {
if ([scale isKindOfClass:[NSNumber class]]) {
_scale = [scale floatValue];
}
}
NSString *fillColor = [layerProperties objectForKey:@"fill-color"];
if (fillColor) {
//NSLog(@"Fill Color: %@", fillColor);
fillColor = [fillColor stringByReplacingOccurrencesOfString:@"rgba(" withString:@""];
fillColor = [fillColor stringByReplacingOccurrencesOfString:@")" withString:@""];
NSArray *components = [fillColor componentsSeparatedByString:@","];
if ([components count] == 4) {
// Convert to float since they come in as 0..255
_r = [[components objectAtIndex:0] floatValue] / 255.0;
_g = [[components objectAtIndex:1] floatValue] / 255.0;
_b = [[components objectAtIndex:2] floatValue] / 255.0;
_a = [[components objectAtIndex:3] floatValue];
}
}
}
@end

File diff suppressed because it is too large Load Diff

View File

@@ -106,6 +106,8 @@ MLN_DARWIN_OBJC_HEADERS = [
"src/NSExpression+MLNAdditions.h",
"src/NSPredicate+MLNAdditions.h",
"src/NSValue+MLNAdditions.h",
"src/MLNPluginLayer.h",
"src/MLNPluginStyleLayer.h",
]
MLN_DARWIN_OBJCPP_HEADERS = [
@@ -163,6 +165,8 @@ MLN_DARWIN_PRIVATE_HEADERS = [
"src/MLNVectorTileSource_Private.h",
"src/NSExpression+MLNPrivateAdditions.h",
"src/NSPredicate+MLNPrivateAdditions.h",
"src/MLNPluginStyleLayer_Private.h"
]
MLN_DARWIN_PUBLIC_OBJCPP_SOURCE = [
@@ -217,6 +221,9 @@ MLN_DARWIN_PUBLIC_OBJCPP_SOURCE = [
"src/NSExpression+MLNAdditions.mm",
"src/NSPredicate+MLNAdditions.mm",
"src/NSValue+MLNStyleAttributeAdditions.mm",
"src/MLNPluginLayer.mm",
"src/MLNPluginStyleLayer.mm"
]
MLN_DARWIN_PUBLIC_OBJCPP_CUSTOM_DRAWABLE_SOURCE = [
"src/MLNCustomDrawableStyleLayer_Private.h",

View File

@@ -162,6 +162,8 @@ add_library(
EXCLUDE_FROM_ALL
"${CMAKE_CURRENT_LIST_DIR}/app/ExampleCustomDrawableStyleLayer.mm"
"${CMAKE_CURRENT_LIST_DIR}/app/CustomStyleLayerExample.m"
"${CMAKE_CURRENT_LIST_DIR}/app/PluginLayerExample.mm"
"${CMAKE_CURRENT_LIST_DIR}/app/PluginLayerExampleMetalRendering.mm"
)
target_link_libraries(

View File

@@ -32,6 +32,11 @@ MLN_EXPORT
*/
@property (nonatomic, nonnull) MLNActionJournalOptions *actionJournalOptions;
/**
List of plugin classes
*/
@property NSArray *pluginLayers;
@end
NS_ASSUME_NONNULL_END

View File

@@ -13,6 +13,7 @@
{
_styleURL = nil;
_styleJSON = nil;
_pluginLayers = nil;
_actionJournalOptions = [[MLNActionJournalOptions alloc] init];
}

View File

@@ -0,0 +1,109 @@
#import <Foundation/Foundation.h>
#import <Metal/Metal.h>
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#import <AppKit/AppKit.h>
#endif
#import "MLNFoundation.h"
#import "MLNGeometry.h"
NS_ASSUME_NONNULL_BEGIN
typedef enum {
MLNPluginLayerPropertyTypeUnknown,
MLNPluginLayerPropertyTypeSingleFloat,
MLNPluginLayerPropertyTypeColor
} MLNPluginLayerPropertyType;
MLN_EXPORT
@interface MLNPluginLayerProperty : NSObject
+ (MLNPluginLayerProperty *)propertyWithName:(NSString *)propertyName
propertyType:(MLNPluginLayerPropertyType)propertyType
defaultValue:(id)defaultValue;
// The name of the property
@property (copy) NSString *propertyName;
// The type of property
@property MLNPluginLayerPropertyType propertyType;
// Single float default value
@property float singleFloatDefaultValue;
// Color default value
#if TARGET_OS_IPHONE
@property (copy) UIColor *colorDefaultValue;
#else
@property NSColor *colorDefaultValue;
#endif
@end
typedef enum {
MLNPluginLayerTileKindGeometry,
MLNPluginLayerTileKindRaster,
MLNPluginLayerTileKindRasterDEM,
MLNPluginLayerTileKindNotRequired
} MLNPluginLayerTileKind;
MLN_EXPORT
@interface MLNPluginLayerCapabilities : NSObject
@property (copy) NSString *layerID;
@property BOOL requiresPass3D;
//! This is a list of layer properties that this layer supports.
@property (copy) NSArray<MLNPluginLayerProperty *> *layerProperties;
@end
@class MLNMapView;
// Copied initially from MLNStyleLayerDrawingContext. Decided to copy instead of use since we
// might add additional properties here
/// A structure containing context needed to draw a frame in an ``MLNCustomStyleLayer``.
typedef struct MLNPluginLayerDrawingContext {
/// The size of the drawable area, in points.
CGSize size;
/// The center coordinate of the map view.
CLLocationCoordinate2D centerCoordinate;
/// The current zoom level of the map view.
double zoomLevel;
/// The heading (direction) in degrees clockwise from true north.
CLLocationDirection direction;
/// The current pitch of the map view in degrees, measured from the map plane.
CGFloat pitch;
/// The vertical field of view, in degrees, for the map’s perspective.
CGFloat fieldOfView;
/// A 4×4 matrix representing the map view’s current projection state.
MLNMatrix4 projectionMatrix;
} MLNPluginLayerDrawingContext;
MLN_EXPORT
@interface MLNPluginLayer : NSObject
/// Returns the layer capabilities of the plugin layer.
/// This must be overridden by the plug-in layer and return a set of capabilities
+ (MLNPluginLayerCapabilities *)layerCapabilities;
// These are public methods that can be overridden by the plugin layer
/// Called when the layer is rendered
- (void)onRenderLayer:(MLNMapView *)mapView
renderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder;
/// Called when the layer is updated in the render loop. This would update animations/etc
- (void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext;
/// Called when the layer properties are updated. Can be on initial load from the JSON or when
/// dynamic properties are updated
- (void)onUpdateLayerProperties:(NSDictionary *)layerProperties;
/// Added to a map view
- (void)didMoveToMapView:(MLNMapView *)mapView;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,80 @@
#import "MLNPluginLayer.h"
@implementation MLNPluginLayerProperty
+(MLNPluginLayerProperty *)propertyWithName:(NSString *)propertyName
propertyType:(MLNPluginLayerPropertyType)propertyType
defaultValue:(id)defaultValue {
MLNPluginLayerProperty *tempResult = [[MLNPluginLayerProperty alloc] init];
tempResult.propertyName = propertyName;
tempResult.propertyType = propertyType;
if (propertyType == MLNPluginLayerPropertyTypeSingleFloat) {
if ([defaultValue isKindOfClass:[NSNumber class]]) {
tempResult.singleFloatDefaultValue = [defaultValue floatValue];
}
} else if (propertyType == MLNPluginLayerPropertyTypeColor) {
#if TARGET_OS_IPHONE
if ([defaultValue isKindOfClass:[UIColor class]]) {
#else
if ([defaultValue isKindOfClass:[NSColor class]]) {
#endif
tempResult.colorDefaultValue = defaultValue;
}
}
return tempResult;
}
-(id)init {
// Base class implemenation
if (self = [super init]) {
// Default setup
self.propertyType = MLNPluginLayerPropertyTypeUnknown;
self.propertyName = @"unknown";
// Default values for the various types
self.singleFloatDefaultValue = 1.0;
}
return self;
}
@end
@implementation MLNPluginLayer
// This is the layer type in the style that is used
+(MLNPluginLayerCapabilities *)layerCapabilities {
// Base class returns the class name just to return something
// TODO: Add an assert/etc or something to notify the developer that this needs to be overridden
return nil;
}
- (void)onRenderLayer:(MLNMapView *)mapView
renderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder {
// Base class does nothing
}
- (void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext {
// Base class does nothing
}
-(void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
// Base class does nothing
}
/// Added to a map view
- (void)didMoveToMapView:(MLNMapView *)mapView {
// Base class does nothing
}
@end
@implementation MLNPluginLayerCapabilities
@end

View File

@@ -0,0 +1,13 @@
#import "MLNStyleLayer.h"
NS_ASSUME_NONNULL_BEGIN
@class MLNPluginLayer;
@interface MLNPluginStyleLayer : MLNStyleLayer
- (MLNPluginLayer *)pluginLayer;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,35 @@
#import "MLNPluginStyleLayer.h"
#import "MLNPluginStyleLayer_Private.h"
#import <mbgl/plugin/plugin_layer.hpp>
#import <mbgl/plugin/plugin_layer_impl.hpp>
#import "MLNPluginLayer.h"
@implementation MLNPluginStyleLayer
-(void)getStats {
mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer;
auto pl = l->impl();
}
-(MLNPluginLayer *)pluginLayer {
mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer;
if (l->_platformReference) {
MLNPluginLayer *pl = (__bridge MLNPluginLayer *)l->_platformReference;
return pl;
}
return nil;
}
@end
MLNStyleLayer* mbgl::PluginLayerPeerFactory::createPeer(style::Layer *rawLayer) {
return [[MLNPluginStyleLayer alloc] initWithRawLayer:rawLayer];
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "MLNStyleLayer_Private.h"
#include <mbgl/plugin/plugin_layer_factory.hpp>
namespace mbgl {
class PluginLayerPeerFactory : public LayerPeerFactory, public mbgl::PluginLayerFactory {
public:
PluginLayerPeerFactory(std::string& layerType,
mbgl::style::LayerTypeInfo::Source source,
mbgl::style::LayerTypeInfo::Pass3D pass3D,
mbgl::style::LayerTypeInfo::Layout layout,
mbgl::style::LayerTypeInfo::FadingTiles fadingTiles,
mbgl::style::LayerTypeInfo::CrossTileIndex crossTileIndex,
mbgl::style::LayerTypeInfo::TileKind tileKind)
: mbgl::PluginLayerFactory(layerType, source, pass3D, layout, fadingTiles, crossTileIndex, tileKind) {}
// LayerPeerFactory overrides.
LayerFactory* getCoreLayerFactory() final { return this; }
virtual MLNStyleLayer* createPeer(style::Layer*) final;
};
} // namespace mbgl

View File

@@ -18,12 +18,7 @@ public:
MLNStyleLayer* createPeer(style::Layer*);
private:
LayerManagerDarwin();
/**
* Enables a layer type for both JSON style and runtime API.
*/
void addLayerType(std::unique_ptr<LayerPeerFactory>);
public:
/**
* Enables a layer type for JSON style only.
*
@@ -31,7 +26,15 @@ private:
* in order to save binary size (the corresponding SDK layer wrappers
* should be excluded from the project build).
*/
void addLayerTypeCoreOnly(std::unique_ptr<mbgl::LayerFactory>);
void addLayerTypeCoreOnly(std::unique_ptr<mbgl::LayerFactory>) override;
/**
* Enables a layer type for both JSON style and runtime API.
*/
void addLayerType(std::unique_ptr<LayerPeerFactory>);
private:
LayerManagerDarwin();
void registerCoreFactory(LayerFactory*);
LayerPeerFactory* getPeerFactory(const style::LayerTypeInfo* typeInfo);

View File

@@ -109,7 +109,7 @@ void LayerManagerDarwin::registerCoreFactory(LayerFactory* factory) {
LayerPeerFactory* LayerManagerDarwin::getPeerFactory(const mbgl::style::LayerTypeInfo* typeInfo) {
for (const auto& factory: peerFactories) {
if (factory->getCoreLayerFactory()->getTypeInfo() == typeInfo) {
if (layerTypeInfoEquals(factory->getCoreLayerFactory()->getTypeInfo(), typeInfo)) {
return factory.get();
}
}
@@ -127,7 +127,8 @@ LayerFactory* LayerManagerDarwin::getFactory(const mbgl::style::LayerTypeInfo* i
}
for (const auto& factory: coreFactories) {
if (factory->getTypeInfo() == info) {
if (layerTypeInfoEquals(factory->getTypeInfo(), info)) {
// if (factory->getTypeInfo() == info) {
return factory.get();
}
}

View File

@@ -24,6 +24,15 @@ class LayerManagerDefault final : public LayerManager {
public:
LayerManagerDefault();
/**
* Enables a layer type for JSON style only.
*
* We might not want to expose runtime API for some layer types
* in order to save binary size (the corresponding SDK layer wrappers
* should be excluded from the project build).
*/
void addLayerTypeCoreOnly(std::unique_ptr<mbgl::LayerFactory>) override;
private:
void addLayerType(std::unique_ptr<LayerFactory>);
// LayerManager overrides.
@@ -34,6 +43,10 @@ private:
std::map<std::string, LayerFactory*> typeToFactory;
};
void LayerManagerDefault::addLayerTypeCoreOnly(std::unique_ptr<mbgl::LayerFactory> layerFactory) {
addLayerType(std::move(layerFactory));
}
LayerManagerDefault::LayerManagerDefault() {
#if !defined(MBGL_LAYER_FILL_DISABLE_ALL)
addLayerType(std::make_unique<FillLayerFactory>());
@@ -90,7 +103,7 @@ void LayerManagerDefault::addLayerType(std::unique_ptr<LayerFactory> factory) {
LayerFactory* LayerManagerDefault::getFactory(const mbgl::style::LayerTypeInfo* typeInfo) noexcept {
assert(typeInfo);
for (const auto& factory : factories) {
if (factory->getTypeInfo() == typeInfo) {
if (layerTypeInfoEquals(factory->getTypeInfo(), typeInfo)) {
return factory.get();
}
}

View File

@@ -189,6 +189,7 @@ _IOS_APPLICATION_RESOURCES = [
"app/LaunchScreen.storyboard",
"app/simple_route.json",
"app/fill_filter_style.json",
"//platform/darwin:app/PluginLayerTestStyle.json",
"//platform/darwin:test/amsterdam.geojson",
] + glob([
"app/*.lproj/**",

View File

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

View File

@@ -0,0 +1,148 @@
# Plugin Layers
Plugin Layers are a way to add layer types that render themselves into the style parsing engine at runtime. It is a way to dynamically link new layer types to the MapLibre core -- and styling language -- without having to compile them into the library itself.
Because the layers are bound at runtime, one or more different types of specialized layers can be added and different versions of layer types can be added.
Currently Plugin Layers are only available on iOS/Darwin using the Metal Rendering pipeline.
## Creating a Plugin Layer
Plugin Layers can be created by creating a descendant from MLNPluginLayer and add the class type to an instance of MLNMapView. The layer is self describing (e.g. the layer type, what properties are available, etc) and that information will be registered with the MapLibre core. When the plugin layer type is found in the style, the core will automatically instantiate it, pass along initial properties and manage the rendering of the layer.
### Defining a layer's capabilities
The newly created layer class should override the layerCapabilites class method. Please note that this is a class method and not an instance method, so make sure it's prefaced with a + and not a -.
The object that is returned from this method defines the layer type and the propeties that the layer expects.
The triangle example (platform/darwin/app/PluginLayerExampleMetalRendering.mm) defines it's layer type "plugin-layer-metal-rendering" and two paint properties (scale and fill-color). These properties can be expression based. It's important to define the type of property (single float or color) and a default value. The properties returned by layerCapabilities will correspond to any properties in the "paint": part of the style.
Initialization properties can also be added to the "properties": section of the layer style.
```objc
+(MLNPluginLayerCapabilities *)layerCapabilities {
MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init];
tempResult.layerID = @"plugin-layer-metal-rendering";
tempResult.requiresPass3D = YES;
// Define the paint properties that this layer implements and
// what types they are
tempResult.layerProperties = @[
// The scale property
[MLNPluginLayerProperty propertyWithName:@"scale"
propertyType:MLNPluginLayerPropertyTypeSingleFloat
defaultValue:@(1.0)],
// The fill color property
[MLNPluginLayerProperty propertyWithName:@"fill-color"
propertyType:MLNPluginLayerPropertyTypeColor
defaultValue:[UIColor blueColor]]
];
return tempResult;
}
```
For the triangle example, there are two initialization properties (offset-x and offset-y) for positioning and two paint properties, scale and fill-color. Here are three different instances of the plugin layer being added to the style. One that has a static scale (and uses the default color), one that has an expression based scale (and uses the default scale) and one that has an expression based scale and fill color.
```json
{
"id": "metal-rendering-layer-1",
"type": "plugin-layer-metal-rendering",
"properties": {
"offset-x": -300,
"offset-y": -300
},
"paint": {
"scale": 2.5
}
},
{
"id": "metal-rendering-layer-2",
"type": "plugin-layer-metal-rendering",
"properties": {
"offset-x": -300
},
"paint": {
"scale": [
"interpolate",
[
"linear"
],
[
"zoom"
],
5,
0.5,
15,
3
]
}
},
{
"id": "metal-rendering-layer-3",
"type": "plugin-layer-metal-rendering",
"properties": {
"color1": "#DDAAFF",
"offset-x": 300
},
"paint": {
"scale": [
"interpolate",
[
"linear"
],
[
"zoom"
],
5,
3,
15,
0.5
],
"fill-color": [
"interpolate",
[
"linear"
],
[
"zoom"
],
1,
"#ff0000",
22,
"#00ff00"
]
}
}
```
![](PluginLayer.png)
## Example 2: GLTF Plugin Layer
In [this repository](https://github.com/AtlasProgramming/maplibre-gltf-models-plugin) you can find an example of a plugin layer that allows adding GLTF models. The following JSON is added to the style.
```
{
"id": "model-layer",
"type": "hudhud::gltf-model-layer",
"properties": {
"model-source-resource":"models.json"
},
"paint": {
"scale": 1.0
}
}
```
@Video(
source: "gltfplugin.mp4",
poster: "gltfplugin.png",
alt: "GLTF Plugin in action.") {
GLTF Plugin showing 3D models of the Arc de Triomphe and the Eifel Tower.
}

View File

@@ -23,6 +23,11 @@
#import "MLNMapView_Experimental.h"
#import <objc/runtime.h>
// Plug In Examples
#import "PluginLayerExample.h"
#import "PluginLayerExampleMetalRendering.h"
#import "MLNPluginStyleLayer.h"
static const CLLocationCoordinate2D WorldTourDestinations[] = {
{ .latitude = 38.8999418, .longitude = -77.033996 },
{ .latitude = 37.7884307, .longitude = -122.3998631 },
@@ -270,10 +275,21 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
return self;
}
// This will add the plug-in layers. This is a demo of how
// extensible layers for the style can be added to the map view
-(void)addPluginLayers {
[self.mapView addPluginLayerType:[PluginLayerExample class]];
[self.mapView addPluginLayerType:[PluginLayerExampleMetalRendering class]];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self addPluginLayers];
// Keep track of current map state and debug preferences,
// saving and restoring when the application's state changes.
self.currentState = [MBXStateManager sharedManager].currentState;
@@ -2314,10 +2330,19 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
self.styleNames = [NSMutableArray array];
self.styleURLs = [NSMutableArray array];
/// Style that does not require an `apiKey` nor any further configuration
[self.styleNames addObject:@"MapLibre Basic"];
[self.styleURLs addObject:[NSURL URLWithString:@"https://demotiles.maplibre.org/style.json"]];
/// This is hte same style as above but copied locally and the three instances of the metal plug-in layer added to the style
/// Look for "type": "plugin-layer-metal-rendering" in the PluginLayerTestStyle.json for an example of how the layer is defined
[self.styleNames addObject:@"MapLibre Basic - Local With Plugin"];
NSURL *url = [[NSBundle mainBundle] URLForResource:@"PluginLayerTestStyle.json" withExtension:nil];
[self.styleURLs addObject:url];
/// Add MapLibre Styles if an `apiKey` exists
NSString* apiKey = [MLNSettings apiKey];
if (apiKey.length)

View File

@@ -11,7 +11,7 @@ MLN_IOS_SDK_HEADERS = [
"src/MLNUserLocation.h",
"src/MLNUserLocationAnnotationView.h",
"src/MLNUserLocationAnnotationViewStyle.h",
"src/MLNScaleBar.h",
"src/MLNScaleBar.h"
]
MLN_IOS_PUBLIC_HEADERS = [

View File

@@ -19,6 +19,7 @@ NS_ASSUME_NONNULL_BEGIN
@class MLNPolygon;
@class MLNScaleBar;
@class MLNShape;
@class MLNPluginLayer;
@protocol MLNMapViewDelegate;
@protocol MLNAnnotation;
@@ -2252,6 +2253,11 @@ vertically on the map.
*/
- (void)triggerRepaint;
/**
Adds a plug-in layer that is external to this library
*/
- (void)addPluginLayerType:(Class)pluginLayerClass;
@end
NS_ASSUME_NONNULL_END

View File

@@ -26,6 +26,13 @@
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/projection.hpp>
#include <mbgl/layermanager/layer_manager.hpp>
#include <mbgl/plugin/plugin_layer_factory.hpp>
#include <mbgl/plugin/plugin_layer.hpp>
#include <mbgl/plugin/plugin_layer_impl.hpp>
#include <mbgl/renderer/paint_parameters.hpp>
#include <mbgl/mtl/mtl_fwd.hpp>
#include <mbgl/mtl/render_pass.hpp>
#import "Mapbox.h"
#import "MLNShape_Private.h"
@@ -72,6 +79,9 @@
#import "MLNSettings_Private.h"
#import "MLNActionJournalOptions_Private.h"
#import "MLNMapProjection.h"
#import "MLNPluginLayer.h"
#import "MLNStyleLayerManager.h"
#include "MLNPluginStyleLayer_Private.h"
#include <algorithm>
#include <cstdlib>
@@ -442,6 +452,9 @@ public:
@property (nonatomic) CADisplayLink *displayLink;
@property (nonatomic, assign) BOOL needsDisplayRefresh;
// Plugin Layers
@property NSMutableArray *pluginLayers;
@end
@implementation MLNMapView
@@ -552,6 +565,12 @@ public:
if (options)
{
if (options.pluginLayers) {
for (Class c in options.pluginLayers) {
[self addPluginLayerType:c];
}
}
if (options.styleURL) {
self.styleURL = options.styleURL;
} else if (options.styleJSON) {
@@ -7624,6 +7643,146 @@ static void *windowScreenContext = &windowScreenContext;
_mbglMap->triggerRepaint();
}
/**
Adds a plug-in layer that is external to this library
*/
-(void)addPluginLayerType:(Class)pluginLayerClass {
auto layerManager = mbgl::LayerManager::get();
auto darwinLayerManager = (mbgl::LayerManagerDarwin *)layerManager;
MLNPluginLayerCapabilities *capabilities = [pluginLayerClass layerCapabilities];
std::string layerType = [capabilities.layerID UTF8String];
// Default values
mbgl::style::LayerTypeInfo::Source source = mbgl::style::LayerTypeInfo::Source::NotRequired;
mbgl::style::LayerTypeInfo::TileKind tileKind = mbgl::style::LayerTypeInfo::TileKind::NotRequired;
mbgl::style::LayerTypeInfo::FadingTiles fadingTiles = mbgl::style::LayerTypeInfo::FadingTiles::NotRequired;
mbgl::style::LayerTypeInfo::Layout layout = mbgl::style::LayerTypeInfo::Layout::NotRequired;
mbgl::style::LayerTypeInfo::CrossTileIndex crossTileIndex = mbgl::style::LayerTypeInfo::CrossTileIndex::NotRequired;
mbgl::style::LayerTypeInfo::Pass3D pass3D = mbgl::style::LayerTypeInfo::Pass3D::NotRequired;
if (capabilities.requiresPass3D) {
pass3D = mbgl::style::LayerTypeInfo::Pass3D::Required;
}
auto factory = std::make_unique<mbgl::PluginLayerPeerFactory>(layerType,
source,
pass3D,
layout,
fadingTiles,
crossTileIndex,
tileKind);
__weak MLNMapView *weakMapView = self;
Class layerClass = pluginLayerClass;
factory->setOnLayerCreatedEvent([layerClass, weakMapView, pluginLayerClass](mbgl::style::PluginLayer *pluginLayer) {
//NSLog(@"Creating Plugin Layer: %@", layerClass);
MLNPluginLayer *layer = [[layerClass alloc] init];
if (!weakMapView.pluginLayers) {
weakMapView.pluginLayers = [NSMutableArray array];
}
[weakMapView.pluginLayers addObject:layer];
// Use weak here so there isn't a retain cycle
MLNPluginLayer *weakPlugInLayer = layer;
pluginLayer->_platformReference = (__bridge void *)layer;
MLNPluginLayerCapabilities *capabilities = [pluginLayerClass layerCapabilities];
auto pluginLayerImpl = (mbgl::style::PluginLayer::Impl *)pluginLayer->baseImpl.get();
auto & pm = pluginLayerImpl->_propertyManager;
for (MLNPluginLayerProperty *property in capabilities.layerProperties) {
mbgl::style::PluginLayerProperty *p = new mbgl::style::PluginLayerProperty();
switch (property.propertyType) {
case MLNPluginLayerPropertyTypeSingleFloat:
p->_propertyType = mbgl::style::PluginLayerProperty::PropertyType::SingleFloat;
p->_defaultSingleFloatValue = property.singleFloatDefaultValue;
break;
case MLNPluginLayerPropertyTypeColor:
{
p->_propertyType = mbgl::style::PluginLayerProperty::PropertyType::Color;
if (property.colorDefaultValue) {
CGFloat r, g, b, a;
[property.colorDefaultValue getRed:&r green:&g blue:&b alpha:&a];
p->_defaultColorValue = mbgl::Color(r, g, b, a);
}
}
break;
default:
p->_propertyType = mbgl::style::PluginLayerProperty::PropertyType::Unknown;
break;
}
p->_propertyName = [property.propertyName UTF8String];
pm.addProperty(p);
}
// Set the render function
auto renderFunction = [weakPlugInLayer, weakMapView](mbgl::PaintParameters& paintParameters){
const mbgl::mtl::RenderPass& renderPass = static_cast<mbgl::mtl::RenderPass&>(*paintParameters.renderPass);
id<MTLRenderCommandEncoder> encoder = (__bridge id<MTLRenderCommandEncoder>)renderPass.getMetalEncoder().get();
MLNMapView *strongMapView = weakMapView;
const mbgl::TransformState& state = paintParameters.state;
MLNPluginLayerDrawingContext drawingContext;
drawingContext.size = CGSizeMake(state.getSize().width,
state.getSize().height);
drawingContext.centerCoordinate = CLLocationCoordinate2DMake(state.getLatLng().latitude(),
state.getLatLng().longitude());
drawingContext.zoomLevel = state.getZoom();
drawingContext.direction = mbgl::util::rad2deg(-state.getBearing());
drawingContext.pitch = state.getPitch();
drawingContext.fieldOfView = state.getFieldOfView();
mbgl::mat4 projMatrix;
state.getProjMatrix(projMatrix);
drawingContext.projectionMatrix = MLNMatrix4Make(projMatrix);
//NSLog(@"Rendering");
// Call update with the scene state variables
[weakPlugInLayer onUpdateLayer:drawingContext];
// Call render
[weakPlugInLayer onRenderLayer:strongMapView
renderEncoder:encoder];
};
// Set the lambdas
//auto pluginLayerImpl = (mbgl::style::PluginLayer::Impl *)pluginLayer->baseImpl.get();
pluginLayerImpl->setRenderFunction(renderFunction);
// Set the update properties function
pluginLayerImpl->setUpdatePropertiesFunction([weakPlugInLayer](const std::string & jsonProperties) {
// Use autorelease pools in lambdas
@autoreleasepool {
// Just wrap the string with NSData so it can be run through properties
NSData *d = [NSData dataWithBytesNoCopy:(void *)jsonProperties.data() length:jsonProperties.length() freeWhenDone:NO];
NSError *error = nil;
NSDictionary *properties = [NSJSONSerialization JSONObjectWithData:d
options:0
error:&error];
if (error) {
// TODO: What should we do here?
}
[weakPlugInLayer onUpdateLayerProperties:properties];
}
});
});
// TODO: Same question as above. Do we ever want to have a core only layer type?
// This could actually be something that we could set in the layer capabilities class
darwinLayerManager->addLayerType(std::move(factory));
//darwinLayerManager->addLayerTypeCoreOnly(std::move(factory));
}
- (NSArray<NSString*>*)getActionJournalLogFiles
{
const auto& actionJournal = _mbglMap->getActionJournal();

View File

@@ -11,6 +11,8 @@
namespace mbgl {
void LayerManager::addLayerTypeCoreOnly(std::unique_ptr<mbgl::LayerFactory>) {}
std::unique_ptr<style::Layer> LayerManager::createLayer(const std::string& type,
const std::string& id,
const style::conversion::Convertible& value,

View File

@@ -0,0 +1,84 @@
#include <mbgl/plugin/plugin_layer.hpp>
#include <mbgl/plugin/plugin_layer_impl.hpp>
#include <iostream>
namespace mbgl {
namespace style {
PluginLayer::PluginLayer(const std::string& layerID,
const std::string& sourceID,
const style::LayerTypeInfo layerTypeInfo,
const std::string& layerProperties
//,const style::conversion::Convertible& layerProperties
)
: Layer(makeMutable<Impl>(layerID, sourceID, layerTypeInfo, layerProperties)) {}
PluginLayer::PluginLayer(Immutable<Impl> impl_)
: Layer(std::move(impl_)) {}
PluginLayer::~PluginLayer() = default;
const PluginLayer::Impl& PluginLayer::impl() const {
return static_cast<const Impl&>(*baseImpl);
}
Mutable<PluginLayer::Impl> PluginLayer::mutableImpl() const {
return makeMutable<Impl>(impl());
}
std::unique_ptr<Layer> PluginLayer::cloneRef(const std::string& id_) const {
auto impl_ = mutableImpl();
impl_->id = id_;
return std::make_unique<PluginLayer>(std::move(impl_));
}
void PluginLayer::Impl::stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const {}
Value PluginLayer::serialize() const {
auto result = Layer::serialize();
return result;
}
std::optional<conversion::Error> PluginLayer::setPropertyInternal(const std::string& name,
const conversion::Convertible& value) {
auto i = static_cast<const mbgl::style::PluginLayer::Impl*>(baseImpl.get());
auto pm = i->_propertyManager;
// The properties should be defined when the plugin layer is created
PluginLayerProperty* property = pm.getProperty(name); // i->_propertyManager.getProperty(name);
if (property == nullptr) {
return Error{"layer doesn't support this property"};
}
Error error;
if (property->_propertyType == PluginLayerProperty::PropertyType::SingleFloat) {
const auto& tempValue = convert<PropertyValue<float>>(value, error, false, false);
if (!tempValue) {
return error;
}
property->setSingleFloat(tempValue.value());
} else if (property->_propertyType == PluginLayerProperty::PropertyType::Color) {
const auto& tempValue = convert<PropertyValue<mbgl::Color>>(value, error, false, false);
if (!tempValue) {
return error;
}
property->setColor(tempValue.value());
}
return std::nullopt;
}
StyleProperty PluginLayer::getProperty([[maybe_unused]] const std::string& name) const {
// TODO: Implement this via callback
// return getLayerProperty(*this, name);
return {};
}
Mutable<Layer::Impl> PluginLayer::mutableBaseImpl() const {
return staticMutableCast<Layer::Impl>(mutableImpl());
}
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,45 @@
#pragma once
#include <mbgl/style/layer.hpp>
#include <mbgl/renderer/paint_parameters.hpp>
#include <mbgl/renderer/render_layer.hpp>
namespace mbgl {
namespace style {
class PluginLayer final : public Layer {
public:
PluginLayer(const std::string& layerID,
const std::string& sourceID,
const style::LayerTypeInfo layerTypeInfo,
const std::string& layerProperties);
~PluginLayer() override;
// Private implementation
class Impl;
const Impl& impl() const;
Mutable<Impl> mutableImpl() const;
PluginLayer(Immutable<Impl>);
std::unique_ptr<Layer> cloneRef(const std::string& id) const final;
public:
using OnRenderLayer = std::function<void(PaintParameters&)>;
using OnUpdateLayer = std::function<void(const LayerPrepareParameters&)>;
using OnUpdateLayerProperties = std::function<void(const std::string& properties)>;
void* _platformReference = nullptr;
protected:
std::optional<conversion::Error> setPropertyInternal(const std::string& name,
const conversion::Convertible& value) final;
StyleProperty getProperty(const std::string& name) const final;
Value serialize() const final;
Mutable<Layer::Impl> mutableBaseImpl() const final;
};
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,188 @@
#include <mbgl/plugin/plugin_layer_factory.hpp>
#include <mbgl/plugin/plugin_layer.hpp>
#include <mbgl/plugin/plugin_layer_impl.hpp>
#include <mbgl/plugin/plugin_layer_render.hpp>
#include <mbgl/style/conversion_impl.hpp>
#include <mbgl/renderer/bucket.hpp>
#include <iostream>
#include <string>
namespace mbgl {
namespace plugins {
// This is really hacky, but wanted to do it here to discuss if the
// const can be removed from teh regular LayerTypeInfo
struct NonConstLayerTypeInfo {
const char* type;
enum class Source {
Required,
NotRequired
} source;
enum class Pass3D {
Required,
NotRequired
} pass3d;
enum class Layout {
Required,
NotRequired
} layout;
enum class FadingTiles {
Required,
NotRequired
} fadingTiles;
enum class CrossTileIndex {
Required,
NotRequired
} crossTileIndex;
enum class TileKind : uint8_t {
Geometry,
Raster,
RasterDEM,
NotRequired
} tileKind;
};
} // namespace plugins
style::LayerTypeInfo getDefaultInfo() {
style::LayerTypeInfo tempResult = {.type = "unknown",
.source = style::LayerTypeInfo::Source::Required,
.pass3d = style::LayerTypeInfo::Pass3D::Required,
.layout = style::LayerTypeInfo::Layout::NotRequired,
.fadingTiles = style::LayerTypeInfo::FadingTiles::NotRequired,
.crossTileIndex = style::LayerTypeInfo::CrossTileIndex::NotRequired,
.tileKind = style::LayerTypeInfo::TileKind::Geometry};
return tempResult;
}
PluginLayerFactory::PluginLayerFactory(std::string& layerType,
mbgl::style::LayerTypeInfo::Source source,
mbgl::style::LayerTypeInfo::Pass3D pass3D,
mbgl::style::LayerTypeInfo::Layout layout,
mbgl::style::LayerTypeInfo::FadingTiles fadingTiles,
mbgl::style::LayerTypeInfo::CrossTileIndex crossTileIndex,
mbgl::style::LayerTypeInfo::TileKind tileKind)
: _layerTypeInfo(getDefaultInfo()),
_layerType(layerType) {
_layerTypeInfo.type = layerType.c_str();
plugins::NonConstLayerTypeInfo* lti = (plugins::NonConstLayerTypeInfo*)&_layerTypeInfo;
lti->source = (plugins::NonConstLayerTypeInfo::Source)((int)source);
lti->pass3d = (plugins::NonConstLayerTypeInfo::Pass3D)((int)pass3D);
lti->layout = (plugins::NonConstLayerTypeInfo::Layout)((int)layout);
lti->fadingTiles = (plugins::NonConstLayerTypeInfo::FadingTiles)((int)fadingTiles);
lti->crossTileIndex = (plugins::NonConstLayerTypeInfo::CrossTileIndex)((int)crossTileIndex);
lti->tileKind = (plugins::NonConstLayerTypeInfo::TileKind)((int)tileKind);
}
const style::LayerTypeInfo* PluginLayerFactory::getTypeInfo() const noexcept {
return &_layerTypeInfo;
}
void jsonStringFromConvertible(const style::conversion::Convertible& value, std::string& output) {
if (isObject(value)) {
output.append("{");
bool firstItem = true;
eachMember(value,
[&output, &firstItem](const std::string& name, const style::conversion::Convertible& paramValue)
-> std::optional<style::conversion::Error> {
if (!firstItem) {
output.append(",");
}
firstItem = false;
output.append("\"");
output.append(name);
output.append("\":");
jsonStringFromConvertible(paramValue, output);
return std::nullopt;
});
output.append("}");
} else if (isArray(value)) {
output.append("[");
bool firstItem = true;
for (size_t i = 0; i < arrayLength(value); i++) {
if (!firstItem) {
output.append(",");
}
firstItem = false;
auto itemValue = arrayMember(value, i);
jsonStringFromConvertible(itemValue, output);
}
output.append("]");
} else {
auto v = toValue(value);
if (auto iVal = v.value().getInt()) {
std::string tempResult = std::to_string(*iVal);
output.append(tempResult);
} else if (auto uIVal = v.value().getUint()) {
std::string tempResult = std::to_string(*uIVal);
output.append(tempResult);
} else if (auto s = v.value().getString()) {
output.append("\"");
output.append(s->c_str());
output.append("\"");
} else if (auto d = v.value().getDouble()) {
output.append(std::to_string(*d));
}
}
}
std::unique_ptr<style::Layer> PluginLayerFactory::createLayer(const std::string& id,
const style::conversion::Convertible& value) noexcept {
std::string layerProperties;
if (auto memberValue = objectMember(value, "properties")) {
jsonStringFromConvertible(*memberValue, layerProperties);
if (isObject(*memberValue)) {
eachMember(*memberValue,
[]([[maybe_unused]] const std::string& name,
[[maybe_unused]] const style::conversion::Convertible& paramValue)
-> std::optional<style::conversion::Error> { return std::nullopt; });
}
}
std::string source = "source";
auto tempResult = std::unique_ptr<style::Layer>(new (std::nothrow)
style::PluginLayer(id, source, _layerTypeInfo, layerProperties
//,*customProperties
));
if (_onLayerCreated != nullptr) {
auto layerRaw = tempResult.get();
auto pluginLayer = static_cast<mbgl::style::PluginLayer*>(layerRaw);
_onLayerCreated(pluginLayer);
auto pluginLayerImpl = (mbgl::style::PluginLayer::Impl*)pluginLayer->baseImpl.get();
if (pluginLayerImpl->_updateLayerPropertiesFunction != nullptr) {
pluginLayerImpl->_updateLayerPropertiesFunction(layerProperties);
}
}
return tempResult;
}
std::unique_ptr<Bucket> PluginLayerFactory::createBucket(
[[maybe_unused]] const BucketParameters& parameters,
[[maybe_unused]] const std::vector<Immutable<style::LayerProperties>>& layers) noexcept {
// Returning null for now. Not using buckets in plug-ins yet.
return nullptr;
}
std::unique_ptr<RenderLayer> PluginLayerFactory::createRenderLayer(Immutable<style::Layer::Impl> impl) noexcept {
auto localImpl = staticImmutableCast<style::PluginLayer::Impl>(impl);
auto tempResult = std::make_unique<RenderPluginLayer>(staticImmutableCast<style::PluginLayer::Impl>(impl));
tempResult->setRenderFunction(localImpl->_renderFunction);
tempResult->setUpdateFunction(localImpl->_updateFunction);
tempResult->setUpdatePropertiesFunction(localImpl->_updateLayerPropertiesFunction);
return tempResult;
}
} // namespace mbgl

View File

@@ -0,0 +1,45 @@
#pragma once
#include <mbgl/layermanager/layer_factory.hpp>
#include <string>
#include <functional>
namespace mbgl {
namespace style {
// Forward
class PluginLayer;
} // namespace style
class PluginLayerFactory : public LayerFactory {
public:
PluginLayerFactory(std::string& layerType,
mbgl::style::LayerTypeInfo::Source source,
mbgl::style::LayerTypeInfo::Pass3D pass3D,
mbgl::style::LayerTypeInfo::Layout layout,
mbgl::style::LayerTypeInfo::FadingTiles fadingTiles,
mbgl::style::LayerTypeInfo::CrossTileIndex crossTileIndex,
mbgl::style::LayerTypeInfo::TileKind tileKind);
using OnLayerCreatedEvent = std::function<void(mbgl::style::PluginLayer* pluginLayer)>;
void setOnLayerCreatedEvent(OnLayerCreatedEvent onLayerCreated) { _onLayerCreated = onLayerCreated; }
protected:
const style::LayerTypeInfo* getTypeInfo() const noexcept final;
std::unique_ptr<style::Layer> createLayer(const std::string& id,
const style::conversion::Convertible& value) noexcept final;
std::unique_ptr<Bucket> createBucket(const BucketParameters&,
const std::vector<Immutable<style::LayerProperties>>&) noexcept final;
std::unique_ptr<RenderLayer> createRenderLayer(Immutable<style::Layer::Impl>) noexcept final;
private:
// These is the layer type info that is setup during factory creation and returned in the getTypeInfo method
style::LayerTypeInfo _layerTypeInfo;
std::string _layerType;
OnLayerCreatedEvent _onLayerCreated;
};
} // namespace mbgl

View File

@@ -0,0 +1,99 @@
#include <mbgl/plugin/plugin_layer_impl.hpp>
#include <iostream>
namespace mbgl {
namespace style {
PluginLayer::Impl::Impl(std::string layerID,
std::string sourceID,
LayerTypeInfo layerTypeInfo,
const std::string& layerProperties)
: Layer::Impl(layerID, sourceID),
_layerTypeInfo(layerTypeInfo),
_layerProperties(layerProperties) {}
bool PluginLayer::Impl::hasLayoutDifference([[maybe_unused]] const Layer::Impl& other) const {
return false;
}
// Return this property as json
std::string PluginLayerProperty::asJSON() {
std::string tempResult;
if (_propertyType == PropertyType::SingleFloat) {
tempResult = "\"" + _propertyName + "\":" + std::to_string(_singleFloatValue);
} else if (_propertyType == PropertyType::Color) {
// In RGBA format
tempResult = "\"" + _propertyName + "\":\"" + _dataDrivenColorValue.stringify() + "\"";
}
return tempResult;
}
void PluginLayerProperty::setPropertyValue([[maybe_unused]] const conversion::Convertible& value) {}
const PropertyValue<float>& PluginLayerProperty::getSingleFloat() const {
return _singleFloatProperty;
}
void PluginLayerProperty::setSingleFloat(const PropertyValue<float>& value) {
_singleFloatProperty = std::move(value);
}
void PluginLayerProperty::setCurrentSingleFloatValue(float value) {
_singleFloatValue = value;
}
PluginLayerProperty* PluginLayerPropertyManager::getProperty(const std::string& propertyName) {
if (_properties.count(propertyName) > 0) {
return _properties[propertyName];
}
return nullptr;
}
void PluginLayerPropertyManager::addProperty(PluginLayerProperty* property) {
_properties[property->_propertyName] = property;
}
std::string PluginLayerPropertyManager::propertiesAsJSON() {
std::string tempResult = "{";
bool firstItem = true;
for (auto d : _properties) {
if (!firstItem) {
tempResult.append(", ");
}
firstItem = false;
tempResult.append(d.second->asJSON());
}
tempResult.append("}");
return tempResult;
}
std::vector<PluginLayerProperty*> PluginLayerPropertyManager::getProperties() {
std::vector<PluginLayerProperty*> tempResult;
tempResult.reserve(_properties.size());
for (auto it = _properties.begin(); it != _properties.end(); ++it) {
tempResult.push_back(it->second);
}
return tempResult;
}
const PropertyValue<mbgl::Color>& PluginLayerProperty::getColor() const {
return _dataDrivenColorProperty;
}
void PluginLayerProperty::setColor(const PropertyValue<mbgl::Color>& value) {
_dataDrivenColorProperty = std::move(value);
}
void PluginLayerProperty::setCurrentColorValue(mbgl::Color value) {
_dataDrivenColorValue = value;
}
namespace conversion {} // namespace conversion
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,122 @@
#pragma once
#include <mbgl/style/layer_impl.hpp>
#include <mbgl/plugin/plugin_layer.hpp>
#include <mbgl/style/conversion_impl.hpp>
#include <mbgl/style/types.hpp>
#include <mbgl/style/layer_properties.hpp>
#include <mbgl/style/layout_property.hpp>
#include <mbgl/style/paint_property.hpp>
#include <mbgl/style/properties.hpp>
#include <mbgl/shaders/attributes.hpp>
#include <mbgl/shaders/uniforms.hpp>
#include <mbgl/style/property_value.hpp>
#include <mbgl/style/conversion/property_value.hpp>
#include <mbgl/util/color.hpp>
#include <map>
#include <vector>
namespace mbgl {
namespace style {
using namespace conversion;
struct DataDrivenSingleFloatProperty : DataDrivenPaintProperty<float, attributes::width, uniforms::width> {
static float defaultValue() { return 1.f; }
};
struct DataDrivenColorProperty : DataDrivenPaintProperty<mbgl::Color, attributes::color, uniforms::color> {
static mbgl::Color defaultValue() { return mbgl::Color::black(); }
static constexpr auto expressionType() { return expression::type::ColorType{}; };
using EvaluatorType = DataDrivenPropertyEvaluator<Color, false>;
};
class PluginLayerProperty {
public:
enum class PropertyType {
Unknown,
SingleFloat,
Color
};
public:
PropertyType _propertyType = PropertyType::Unknown;
std::string _propertyName;
void setPropertyValue(const conversion::Convertible& value);
public:
const PropertyValue<float>& getSingleFloat() const;
void setSingleFloat(const PropertyValue<float>& value);
float _defaultSingleFloatValue = 1.0;
float _singleFloatValue = 0;
PropertyValue<float> _singleFloatProperty;
void setCurrentSingleFloatValue(float value);
// Color
const PropertyValue<mbgl::Color>& getColor() const;
void setColor(const PropertyValue<mbgl::Color>& value);
mbgl::Color _defaultColorValue = mbgl::Color::black();
mbgl::Color _dataDrivenColorValue = mbgl::Color::black();
PropertyValue<mbgl::Color> _dataDrivenColorProperty;
void setCurrentColorValue(mbgl::Color value);
// Return this property as json
std::string asJSON();
private:
};
class PluginLayerPropertyManager {
public:
PluginLayerProperty* getProperty(const std::string& propertyName);
void addProperty(PluginLayerProperty* property);
std::string propertiesAsJSON();
std::vector<PluginLayerProperty*> getProperties();
private:
std::map<std::string, PluginLayerProperty*> _properties;
};
class PluginLayer::Impl : public Layer::Impl {
public:
Impl(std::string layerID, std::string sourceID, LayerTypeInfo layerTypeInfo, const std::string& layerProperties);
using Layer::Impl::Impl;
bool hasLayoutDifference(const Layer::Impl&) const override;
void stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const override;
const LayerTypeInfo* getTypeInfo() const noexcept final { return &_layerTypeInfo; }
void setRenderFunction(OnRenderLayer renderFunction) { _renderFunction = renderFunction; }
void setUpdateFunction(OnUpdateLayer updateFunction) { _updateFunction = updateFunction; }
void setUpdatePropertiesFunction(OnUpdateLayerProperties updateLayerPropertiesFunction) {
_updateLayerPropertiesFunction = updateLayerPropertiesFunction;
}
//! The property manager handles all of the custom properties for this layer type / instance
PluginLayerPropertyManager _propertyManager;
//! Optional: Called when the layer is expected to render itself.
OnRenderLayer _renderFunction;
//! Optional: Called when the layer is expected to update it's animations/etc.
// TODO: Does this need to be here or can it be done via the render function. Potentially, we could
// have this method called on a background thread/etc or use another way to parallalize work
OnUpdateLayer _updateFunction;
//! Optional: Called when the layer properties change. The properties are passed as JSON for now
OnUpdateLayerProperties _updateLayerPropertiesFunction;
private:
LayerTypeInfo _layerTypeInfo;
std::string _layerProperties;
};
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,22 @@
#include <mbgl/plugin/plugin_layer_properties.hpp>
#include <mbgl/plugin/plugin_layer_impl.hpp>
namespace mbgl {
namespace style {
PluginLayerProperties::PluginLayerProperties(Immutable<PluginLayer::Impl> impl_)
: LayerProperties(std::move(impl_)) {}
PluginLayerProperties::~PluginLayerProperties() = default;
unsigned long PluginLayerProperties::constantsMask() const {
return 0;
}
expression::Dependency PluginLayerProperties::getDependencies() const noexcept {
return expression::Dependency::None;
}
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,21 @@
#pragma once
#include <mbgl/style/types.hpp>
#include <mbgl/style/layer_properties.hpp>
#include <mbgl/plugin/plugin_layer.hpp>
namespace mbgl {
namespace style {
class PluginLayerProperties final : public LayerProperties {
public:
PluginLayerProperties(Immutable<PluginLayer::Impl>);
~PluginLayerProperties() override;
unsigned long constantsMask() const override;
expression::Dependency getDependencies() const noexcept override;
};
} // namespace style
} // namespace mbgl

View File

@@ -0,0 +1,199 @@
#include <mbgl/plugin/plugin_layer_render.hpp>
#include <mbgl/gfx/context.hpp>
#include <mbgl/gfx/drawable_tweaker.hpp>
#include <mbgl/gfx/drawable_builder.hpp>
#include <mbgl/renderer/paint_parameters.hpp>
#if MLN_RENDER_BACKEND_METAL
#include <mbgl/style/layers/mtl/custom_layer_render_parameters.hpp>
#include <mbgl/mtl/render_pass.hpp>
#else
#include <mbgl/style/layers/custom_layer_render_parameters.hpp>
#endif
#include <mbgl/gfx/drawable.hpp>
#include <mbgl/gfx/context.hpp>
#include <mbgl/gfx/renderer_backend.hpp>
#include <mbgl/style/properties.hpp>
#include <iostream>
using namespace mbgl;
namespace mbgl {
namespace gfx {
class Drawable;
}
} // namespace mbgl
class RenderPluginLayerTweaker : public mbgl::gfx::DrawableTweaker {
public:
RenderPluginLayerTweaker(RenderPluginLayer* plugInRenderer)
: _plugInRenderer(plugInRenderer) {}
~RenderPluginLayerTweaker() override = default;
void init(mbgl::gfx::Drawable&) override;
void execute(mbgl::gfx::Drawable&, mbgl::PaintParameters&) override;
protected:
RenderPluginLayer* _plugInRenderer = nullptr;
};
void RenderPluginLayerTweaker::init([[maybe_unused]] mbgl::gfx::Drawable& drawablee) {
};
void RenderPluginLayerTweaker::execute([[maybe_unused]] mbgl::gfx::Drawable& drawable,
[[maybe_unused]] mbgl::PaintParameters& paintParameters) {
// custom drawing
auto& context = paintParameters.context;
context.resetState(paintParameters.depthModeForSublayer(0, mbgl::gfx::DepthMaskType::ReadOnly),
paintParameters.colorModeForRenderPass());
#if MLN_RENDER_BACKEND_METAL
const auto& mtlRenderPass = static_cast<mbgl::mtl::RenderPass*>(paintParameters.renderPass.get());
mtlRenderPass->resetState();
style::mtl::CustomLayerRenderParameters parameters(paintParameters);
#else
style::CustomLayerRenderParameters parameters(paintParameters);
#endif
_plugInRenderer->render(paintParameters);
// Reset the view back to our original one, just in case the CustomLayer
// changed the viewport or Framebuffer.
paintParameters.backend.getDefaultRenderable().getResource<gfx::RenderableResource>().bind();
context.setDirtyState();
context.bindGlobalUniformBuffers(*paintParameters.renderPass);
}
RenderPluginLayer::RenderPluginLayer(Immutable<style::PluginLayer::Impl> _impl)
: RenderLayer(makeMutable<style::PluginLayerProperties>(std::move(_impl))) {}
RenderPluginLayer::~RenderPluginLayer() = default;
void RenderPluginLayer::markLayerRenderable([[maybe_unused]] bool willRender,
[[maybe_unused]] UniqueChangeRequestVec& changes) {
isRenderable = true;
}
/// Generate any changes needed by the layer
void RenderPluginLayer::update([[maybe_unused]] gfx::ShaderRegistry& shaderRegistery,
[[maybe_unused]] gfx::Context& context,
[[maybe_unused]] const TransformState& transformState,
[[maybe_unused]] const std::shared_ptr<UpdateParameters>& updateParameters,
[[maybe_unused]] const RenderTree& renderTree,
[[maybe_unused]] UniqueChangeRequestVec& changes) {
// create layer group
if (!layerGroup) {
if (auto layerGroup_ = context.createLayerGroup(layerIndex, /*initialCapacity=*/1, getID())) {
setLayerGroup(std::move(layerGroup_), changes);
}
}
auto* localLayerGroup = static_cast<LayerGroup*>(layerGroup.get());
// TODO: Implement this
bool hostChanged = false;
// create drawable
if (localLayerGroup->getDrawableCount() == 0 || hostChanged) {
localLayerGroup->clearDrawables();
// create tweaker
auto tweaker = std::make_shared<RenderPluginLayerTweaker>(this);
// create empty drawable using a builder
std::unique_ptr<gfx::DrawableBuilder> builder = context.createDrawableBuilder(getID());
auto& drawable = builder->getCurrentDrawable(true);
drawable->setIsCustom(true);
drawable->setRenderPass(RenderPass::Translucent);
// assign tweaker to drawable
drawable->addTweaker(tweaker);
// add drawable to layer group
localLayerGroup->addDrawable(std::move(drawable));
++stats.drawablesAdded;
}
}
void RenderPluginLayer::upload([[maybe_unused]] gfx::UploadPass& uploadPass) {}
void RenderPluginLayer::render(PaintParameters& paintParameters) {
if (_renderFunction) {
_renderFunction(paintParameters);
}
}
void RenderPluginLayer::prepare(const LayerPrepareParameters& layerParameters) {
if (_updateFunction) {
_updateFunction(layerParameters);
}
}
// --- Private methods
void RenderPluginLayer::transition([[maybe_unused]] const TransitionParameters& parameters) {
// Called when switching between styles
}
void RenderPluginLayer::evaluate(const PropertyEvaluationParameters& parameters) {
auto i = static_cast<const style::PluginLayer::Impl*>(baseImpl.get());
auto pm = i->_propertyManager;
for (auto property : pm.getProperties()) {
if (property->_propertyType == style::PluginLayerProperty::PropertyType::SingleFloat) {
auto& f = property->getSingleFloat();
using Evaluator = typename style::DataDrivenSingleFloatProperty::EvaluatorType;
auto df = property->_defaultSingleFloatValue;
auto newF = f.evaluate(Evaluator(parameters, df), parameters.now);
auto v = newF.constant().value();
property->setCurrentSingleFloatValue(v);
} else if (property->_propertyType == style::PluginLayerProperty::PropertyType::Color) {
auto& f = property->getColor();
using Evaluator = typename style::DataDrivenColorProperty::EvaluatorType;
auto df = property->_defaultColorValue;
auto newF = f.evaluate(Evaluator(parameters, df), parameters.now);
auto v = newF.constant().value();
property->setCurrentColorValue(v);
}
}
std::string jsonProperties = pm.propertiesAsJSON();
i->_updateLayerPropertiesFunction(jsonProperties);
}
bool RenderPluginLayer::hasTransition() const {
return true;
}
bool RenderPluginLayer::hasCrossfade() const {
return false;
}
bool RenderPluginLayer::queryIntersectsFeature([[maybe_unused]] const GeometryCoordinates&,
[[maybe_unused]] const GeometryTileFeature&,
[[maybe_unused]] float,
[[maybe_unused]] const TransformState&,
[[maybe_unused]] float,
[[maybe_unused]] const mat4&,
[[maybe_unused]] const FeatureState&) const {
return false;
}
void RenderPluginLayer::layerChanged([[maybe_unused]] const TransitionParameters& parameters,
[[maybe_unused]] const Immutable<style::Layer::Impl>& impl,
[[maybe_unused]] UniqueChangeRequestVec& changes) {}
/// Remove all drawables for the tile from the layer group
/// @return The number of drawables actually removed.
std::size_t RenderPluginLayer::removeTile([[maybe_unused]] RenderPass, [[maybe_unused]] const OverscaledTileID&) {
return 0;
}
/// Remove all the drawables for tiles
/// @return The number of drawables actually removed.
std::size_t RenderPluginLayer::removeAllDrawables() {
return 0;
}

View File

@@ -0,0 +1,72 @@
#pragma once
#include <mbgl/gfx/offscreen_texture.hpp>
#include <mbgl/renderer/render_layer.hpp>
#include <mbgl/plugin/plugin_layer_impl.hpp>
#include <mbgl/plugin/plugin_layer_properties.hpp>
#include <optional>
namespace mbgl {
class RenderPluginLayer final : public RenderLayer {
public:
explicit RenderPluginLayer(Immutable<style::PluginLayer::Impl>);
~RenderPluginLayer() override;
void markLayerRenderable(bool willRender, UniqueChangeRequestVec& changes) override;
/// Generate any changes needed by the layer
void update(gfx::ShaderRegistry&,
gfx::Context&,
const TransformState&,
const std::shared_ptr<UpdateParameters>&,
const RenderTree&,
UniqueChangeRequestVec&) override;
void prepare(const LayerPrepareParameters&) override;
void upload(gfx::UploadPass&) override;
void render(PaintParameters&) override;
void setRenderFunction(style::PluginLayer::OnRenderLayer renderFunction) { _renderFunction = renderFunction; }
void setUpdateFunction(style::PluginLayer::OnUpdateLayer updateFunction) { _updateFunction = updateFunction; }
void setUpdatePropertiesFunction(style::PluginLayer::OnUpdateLayerProperties updateLayerPropertiesFunction) {
_updateLayerPropertiesFunction = updateLayerPropertiesFunction;
}
private:
void transition(const TransitionParameters&) override;
void evaluate(const PropertyEvaluationParameters&) override;
bool hasTransition() const override;
bool hasCrossfade() const override;
bool queryIntersectsFeature(const GeometryCoordinates&,
const GeometryTileFeature&,
float,
const TransformState&,
float,
const mat4&,
const FeatureState&) const override;
void updateColorRamp();
void layerChanged(const TransitionParameters& parameters,
const Immutable<style::Layer::Impl>& impl,
UniqueChangeRequestVec& changes) override;
/// Remove all drawables for the tile from the layer group
/// @return The number of drawables actually removed.
std::size_t removeTile(RenderPass, const OverscaledTileID&) override;
/// Remove all the drawables for tiles
/// @return The number of drawables actually removed.
std::size_t removeAllDrawables() override;
// The render methods
style::PluginLayer::OnRenderLayer _renderFunction = nullptr;
style::PluginLayer::OnUpdateLayer _updateFunction = nullptr;
style::PluginLayer::OnUpdateLayerProperties _updateLayerPropertiesFunction = nullptr;
};
} // namespace mbgl

View File

@@ -12,6 +12,14 @@
namespace mbgl {
namespace style {
// Added this to support plugins and that their LayerTypeInfo isn't the same point
// across the board
bool layerTypeInfoEquals(const mbgl::style::LayerTypeInfo* one, const mbgl::style::LayerTypeInfo* other) {
return ((strcmp(one->type, other->type) == 0) && (one->source == other->source) && (one->pass3d == other->pass3d) &&
(one->layout == other->layout) && (one->fadingTiles == other->fadingTiles) &&
(one->crossTileIndex == other->crossTileIndex) && (one->tileKind == other->tileKind));
};
static_assert(mbgl::underlying_type(Tile::Kind::Geometry) == mbgl::underlying_type(LayerTypeInfo::TileKind::Geometry),
"tile kind error");
static_assert(mbgl::underlying_type(Tile::Kind::Raster) == mbgl::underlying_type(LayerTypeInfo::TileKind::Raster),

View File

@@ -18,6 +18,7 @@ add_library(
${PROJECT_SOURCE_DIR}/test/math/minmax.test.cpp
${PROJECT_SOURCE_DIR}/test/math/wrap.test.cpp
${PROJECT_SOURCE_DIR}/test/platform/settings.test.cpp
${PROJECT_SOURCE_DIR}/test/plugin/plugin.test.cpp
${PROJECT_SOURCE_DIR}/test/renderer/image_manager.test.cpp
${PROJECT_SOURCE_DIR}/test/renderer/pattern_atlas.test.cpp
${PROJECT_SOURCE_DIR}/test/renderer/shader_registry.test.cpp

236
test/plugin/plugin.test.cpp Normal file
View File

@@ -0,0 +1,236 @@
#include <gmock/gmock.h>
#include <mbgl/test/util.hpp>
#include <mbgl/test/stub_file_source.hpp>
#include <mbgl/test/stub_map_observer.hpp>
#include <mbgl/test/fake_file_source.hpp>
#include <mbgl/test/fixture_log_observer.hpp>
#include <mbgl/test/map_adapter.hpp>
#include <mbgl/gfx/backend_scope.hpp>
#include <mbgl/gfx/headless_frontend.hpp>
#include <mbgl/gfx/shader_registry.hpp>
#include <mbgl/map/map_options.hpp>
#include <mbgl/math/log2.hpp>
#include <mbgl/renderer/renderer.hpp>
#include <mbgl/renderer/update_parameters.hpp>
#include <mbgl/storage/file_source_manager.hpp>
#include <mbgl/storage/main_resource_loader.hpp>
#include <mbgl/storage/network_status.hpp>
#include <mbgl/storage/online_file_source.hpp>
#include <mbgl/storage/resource_options.hpp>
#include <mbgl/style/expression/dsl.hpp>
#include <mbgl/style/image_impl.hpp>
#include <mbgl/style/image.hpp>
#include <mbgl/style/layers/background_layer.hpp>
#include <mbgl/style/layers/background_layer.hpp>
#include <mbgl/style/layers/fill_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
#include <mbgl/style/layers/symbol_layer.hpp>
#include <mbgl/style/sources/custom_geometry_source.hpp>
#include <mbgl/style/sources/geojson_source.hpp>
#include <mbgl/style/sources/image_source.hpp>
#include <mbgl/style/sources/vector_source.hpp>
#include <mbgl/style/style_impl.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/util/async_task.hpp>
#include <mbgl/util/client_options.hpp>
#include <mbgl/util/color.hpp>
#include <mbgl/util/image.hpp>
#include <mbgl/util/io.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mbgl/layermanager/layer_manager.hpp>
#include <mbgl/plugin/plugin_layer_factory.hpp>
#include <mbgl/plugin/plugin_layer.hpp>
#include <mbgl/plugin/plugin_layer_impl.hpp>
#include <atomic>
using namespace mbgl;
using namespace mbgl::style;
using namespace std::literals::string_literals;
template <class FileSource = StubFileSource, class Frontend = HeadlessFrontend>
class MapTest {
public:
util::RunLoop runLoop;
std::shared_ptr<FileSource> fileSource;
StubMapObserver observer;
Frontend frontend;
MapAdapter map;
MapTest(float pixelRatio = 1, MapMode mode = MapMode::Static)
: fileSource(std::make_shared<FileSource>(ResourceOptions::Default(), ClientOptions())),
frontend(pixelRatio),
map(frontend,
observer,
fileSource,
MapOptions().withMapMode(mode).withSize(frontend.getSize()).withPixelRatio(pixelRatio)) {}
explicit MapTest(MapOptions options)
: fileSource(std::make_shared<FileSource>()),
frontend(options.pixelRatio()),
map(frontend, observer, fileSource, options.withSize(frontend.getSize())) {}
template <typename T = FileSource>
MapTest(const std::string& cachePath,
const std::string& assetPath,
float pixelRatio = 1,
MapMode mode = MapMode::Static,
typename std::enable_if_t<std::is_same_v<T, MainResourceLoader>>* = nullptr)
: fileSource(std::make_shared<T>(ResourceOptions().withCachePath(cachePath).withAssetPath(assetPath),
ClientOptions())),
frontend(pixelRatio),
map(frontend,
observer,
fileSource,
MapOptions().withMapMode(mode).withSize(frontend.getSize()).withPixelRatio(pixelRatio)) {}
template <typename T = FileSource>
MapTest(const ResourceOptions& resourceOptions,
const ClientOptions& clientOptions = ClientOptions(),
float pixelRatio = 1,
MapMode mode = MapMode::Static)
: fileSource(std::make_shared<T>(resourceOptions, clientOptions)),
frontend(pixelRatio),
map(frontend,
observer,
fileSource,
MapOptions().withMapMode(mode).withSize(frontend.getSize()).withPixelRatio(pixelRatio)) {}
};
TEST(Plugin, PluginLayerProperty) {
bool _singleFloatValid = false;
bool _colorValid = false;
std::cout << "Test: Plugin.PluginLayerProperty\n";
{
PluginLayerProperty p;
p._propertyType = PluginLayerProperty::PropertyType::SingleFloat;
p._singleFloatValue = 1.0;
auto json = p.asJSON();
std::string testValue = R"("":1.000000)";
std::cout << " _singleFloatValid: " << json << ";|" << testValue << "|\n";
_singleFloatValid = json == testValue;
EXPECT_STREQ(testValue.c_str(), json.c_str());
}
{
PluginLayerProperty p;
p._propertyType = PluginLayerProperty::PropertyType::Color;
p._dataDrivenColorValue = Color(1, 1, 1, 1);
auto json = p.asJSON();
std::string testValue = R"JSON("":"rgba(255,255,255,1)")JSON";
std::cout << " _colorValid: |" << json << "|;|" << testValue << "|\n";
_colorValid = json == testValue;
EXPECT_STREQ(testValue.c_str(), json.c_str());
}
ASSERT_TRUE(_colorValid);
ASSERT_TRUE(_singleFloatValid);
}
TEST(Plugin, PluginLayerPropertyManager) {
PluginLayerPropertyManager pm;
PluginLayerProperty* p1 = new PluginLayerProperty();
p1->_propertyType = PluginLayerProperty::PropertyType::SingleFloat;
p1->_propertyName = "float";
p1->_singleFloatValue = 1.0;
pm.addProperty(p1);
PluginLayerProperty* p2 = new PluginLayerProperty();
p2->_propertyType = PluginLayerProperty::PropertyType::Color;
p2->_propertyName = "color";
p2->_dataDrivenColorValue = Color(1, 1, 1, 1);
pm.addProperty(p2);
auto json = pm.propertiesAsJSON();
std::cout << "Test: Plugin.PluginLayerPropertyManager\n";
std::string testValue = R"JSON({"color":"rgba(255,255,255,1)", "float":1.000000})JSON";
std::cout << " JSON: |" << json << "|;|" << testValue << "|\n";
EXPECT_STREQ(testValue.c_str(), json.c_str());
bool _jsonValid = (json == testValue);
ASSERT_TRUE(_jsonValid);
}
TEST(Plugin, PluginLayer) {
bool _layerCreated = false;
bool _initialPropertiesFound = false;
bool _layerRendered = false;
bool _paintPropertiesFound = false;
// Create the plug-in layer type
std::string layerType = "plugin-layer-test";
auto pluginLayerFactory = std::make_unique<PluginLayerFactory>(
layerType,
mbgl::style::LayerTypeInfo::Source::NotRequired,
mbgl::style::LayerTypeInfo::Pass3D::NotRequired,
mbgl::style::LayerTypeInfo::Layout::NotRequired,
mbgl::style::LayerTypeInfo::FadingTiles::NotRequired,
mbgl::style::LayerTypeInfo::CrossTileIndex::NotRequired,
mbgl::style::LayerTypeInfo::TileKind::NotRequired);
pluginLayerFactory->setOnLayerCreatedEvent(
[&_layerRendered, &_layerCreated, &_initialPropertiesFound, &_paintPropertiesFound](
mbgl::style::PluginLayer* pluginLayer) {
// std::cout << "On Layer Created\n";
_layerCreated = true;
auto pluginLayerImpl = (mbgl::style::PluginLayer::Impl*)pluginLayer->baseImpl.get();
auto& pm = pluginLayerImpl->_propertyManager;
PluginLayerProperty* p = new PluginLayerProperty();
p->_propertyName = "scale";
p->_propertyType = PluginLayerProperty::PropertyType::SingleFloat;
p->_defaultSingleFloatValue = 1.0;
pm.addProperty(p);
pluginLayerImpl->setRenderFunction([&_layerRendered]([[maybe_unused]] PaintParameters& paintParameters) {
// std::cout << "On Layer Rendered\n";
// std::cout << " Paint Properties.currentLayer: " << paintParameters.currentLayer <<
// "\n";
_layerRendered = true;
});
pluginLayerImpl->setUpdatePropertiesFunction(
[&_initialPropertiesFound, &_paintPropertiesFound](const std::string& properties) {
// std::cout << "On Layer Update Properties\n";
// std::cout << " -> " << properties << "\n";
auto propertiesCheck = R"({"custom-property1":"custom-property-value1"})";
if (properties == propertiesCheck) {
_initialPropertiesFound = true;
}
if (properties.find("scale") > 0) {
_paintPropertiesFound = true;
}
});
});
auto lm = LayerManager::get();
lm->addLayerTypeCoreOnly(std::move(pluginLayerFactory));
MapTest<> test{1, MapMode::Continuous};
test.map.getStyle().loadJSON(R"STYLE({
"version": 8,
"layers": [{
"id": "plugin-layer-1",
"type": "plugin-layer-test",
"properties": {
"custom-property1":"custom-property-value1"
},
"paint": {"scale": 1.0 }
}]
})STYLE");
for (int i = 0; i < 10; i++) {
test.runLoop.runOnce();
}
ASSERT_TRUE(_layerCreated);
ASSERT_TRUE(_initialPropertiesFound);
ASSERT_TRUE(_layerRendered);
ASSERT_TRUE(_paintPropertiesFound);
}