Use a host interface for CustomLayer instead of function pointers (#11553)

Use a host interface for CustomLayer instead of function pointers

Co-authored-by: Julian Rex <julian.rex@mapbox.com>
This commit is contained in:
Asheem Mamoowala
2018-04-02 16:15:25 -07:00
committed by GitHub
parent c3079c6155
commit 099fcc3b3c
21 changed files with 403 additions and 305 deletions

View File

@@ -47,10 +47,10 @@ protected:
// as a matched pair, exclusively through BackendScope, in two situations:
//
// 1. When releasing GL resources during Renderer destruction
// (Including calling CustomLayerDeinitializeFunction during RenderCustomLayer destruction)
// (Including calling CustomLayerHost::deinitialize during RenderCustomLayer destruction)
// 2. When renderering through Renderer::render()
// (Including calling CustomLayerDeinitializeFunction for newly added custom layers and
// CustomLayerDeinitializeFunction on layer removal)
// (Including calling CustomLayerHost::initialize for newly added custom layers and
// CustomLayerHost::deinitialize on layer removal)
virtual void activate() = 0;
virtual void deactivate() = 0;

View File

@@ -6,16 +6,7 @@ namespace mbgl {
namespace style {
/**
* Initialize any GL state needed by the custom layer. This method is called once, from the
* main thread, at a point when the GL context is active but before rendering for the first
* time.
*
* Resources that are acquired in this method must be released in the UninitializeFunction.
*/
using CustomLayerInitializeFunction = void (*)(void* context);
/**
* Parameters that define the current camera position for a CustomLayerRenderFunction.
* Parameters that define the current camera position for a `CustomLayerHost::render()` function.
*/
struct CustomLayerRenderParameters {
double width;
@@ -28,46 +19,49 @@ struct CustomLayerRenderParameters {
double fieldOfView;
};
/**
* Render the layer. This method is called once per frame. The implementation should not make
* any assumptions about the GL state (other than that the correct context is active). It may
* make changes to the state, and is not required to reset values such as the depth mask, stencil
* mask, and corresponding test flags to their original values.
* Make sure that you are drawing your fragments with a z value of 1 to take advantage of the
* opaque fragment culling in case there are opaque layers above your custom layer.
*/
using CustomLayerRenderFunction = void (*)(void* context, const CustomLayerRenderParameters&);
class CustomLayerHost {
public:
virtual ~CustomLayerHost() = default;
/**
* Initialize any GL state needed by the custom layer. This method is called once, from the
* main thread, at a point when the GL context is active but before rendering for the first
* time.
*
* Resources that are acquired in this method must be released in the `deinitialize` function.
*/
virtual void initialize() = 0;
/**
* Called when the system has destroyed the underlying GL context. The
* `CustomLayerDeinitializeFunction` will not be called in this case, however
* `CustomLayerInitializeFunction` will be called instead to prepare for a new render.
*
*/
using CustomLayerContextLostFunction = void (*)(void* context);
/**
* Render the layer. This method is called once per frame. The implementation should not make
* any assumptions about the GL state (other than that the correct context is active). It may
* make changes to the state, and is not required to reset values such as the depth mask, stencil
* mask, and corresponding test flags to their original values.
* Make sure that you are drawing your fragments with a z value of 1 to take advantage of the
* opaque fragment culling in case there are opaque layers above your custom layer.
*/
virtual void render(const CustomLayerRenderParameters&) = 0;
/**
* Destroy any GL state needed by the custom layer, and deallocate context, if necessary. This
* method is called once, from the main thread, at a point when the GL context is active.
*
* Note that it may be called even when the InitializeFunction has not been called.
*/
using CustomLayerDeinitializeFunction = void (*)(void* context);
/**
* Called when the system has destroyed the underlying GL context. The
* `deinitialize` function will not be called in this case, however
* `initialize` will be called instead to prepare for a new render.
*
*/
virtual void contextLost() = 0;
/**
* Destroy any GL state needed by the custom layer, and deallocate context, if necessary. This
* method is called once, from the main thread, at a point when the GL context is active.
*
* Note that it may be called even when the `initialize` function has not been called.
*/
virtual void deinitialize() = 0;
};
class CustomLayer : public Layer {
public:
CustomLayer(const std::string& id,
CustomLayerInitializeFunction,
CustomLayerRenderFunction,
CustomLayerContextLostFunction,
CustomLayerDeinitializeFunction,
void* context);
CustomLayer(const std::string& id,
CustomLayerInitializeFunction,
CustomLayerRenderFunction,
CustomLayerDeinitializeFunction,
void* context);
std::unique_ptr<CustomLayerHost> host);
~CustomLayer() final;

View File

@@ -9,20 +9,8 @@ package com.mapbox.mapboxsdk.style.layers;
public class CustomLayer extends Layer {
public CustomLayer(String id,
long context,
long initializeFunction,
long renderFunction,
long deinitializeFunction) {
this(id, context, initializeFunction, renderFunction, 0L, deinitializeFunction);
}
public CustomLayer(String id,
long context,
long initializeFunction,
long renderFunction,
long contextLostFunction,
long deinitializeFunction) {
initialize(id, initializeFunction, renderFunction, contextLostFunction, deinitializeFunction, context);
long host) {
initialize(id, host);
}
public CustomLayer(long nativePtr) {
@@ -33,9 +21,7 @@ public class CustomLayer extends Layer {
nativeUpdate();
}
protected native void initialize(String id, long initializeFunction, long renderFunction,
long contextLostFunction, long deinitializeFunction,
long context);
protected native void initialize(String id, long host);
protected native void nativeUpdate();

View File

@@ -58,11 +58,7 @@ public class CustomLayerActivity extends AppCompatActivity {
fab.setImageResource(R.drawable.ic_layers);
} else {
customLayer = new CustomLayer("custom",
ExampleCustomLayer.createContext(),
ExampleCustomLayer.InitializeFunction,
ExampleCustomLayer.RenderFunction,
ExampleCustomLayer.ContextLostFunction, // Optional
ExampleCustomLayer.DeinitializeFunction);
ExampleCustomLayer.createContext());
mapboxMap.addLayerBelow(customLayer, "building");
fab.setImageResource(R.drawable.ic_layers_clear);
}

View File

@@ -112,18 +112,9 @@ void checkCompileStatus(GLuint shader) {
static const GLchar * vertexShaderSource = "attribute vec2 a_pos; void main() { gl_Position = vec4(a_pos, 0, 1); }";
static const GLchar * fragmentShaderSource = "uniform highp vec4 fill_color; void main() { gl_FragColor = fill_color; }";
class ExampleCustomLayer {
class ExampleCustomLayer: mbgl::style::CustomLayerHost {
public:
~ExampleCustomLayer() {
__android_log_write(ANDROID_LOG_INFO, LOG_TAG, "~ExampleCustomLayer");
if (program) {
glDeleteBuffers(1, &buffer);
glDetachShader(program, vertexShader);
glDetachShader(program, fragmentShader);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
glDeleteProgram(program);
}
}
void initialize() {
@@ -158,8 +149,15 @@ public:
GL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), background, GL_STATIC_DRAW));
}
void render() {
void render(const mbgl::style::CustomLayerRenderParameters&) {
__android_log_write(ANDROID_LOG_INFO, LOG_TAG, "Render");
glUseProgram(program);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glEnableVertexAttribArray(a_pos);
glVertexAttribPointer(a_pos, 2, GL_FLOAT, GL_FALSE, 0, NULL);
glDisable(GL_STENCIL_TEST);
glDisable(GL_DEPTH_TEST);
glUniform4fv(fill_color, 1, color);
GL_CHECK_ERROR(glUseProgram(program));
GL_CHECK_ERROR(glBindBuffer(GL_ARRAY_BUFFER, buffer));
@@ -172,6 +170,23 @@ public:
}
void contextLost() {
__android_log_write(ANDROID_LOG_INFO, LOG_TAG, "ContextLost");
program = 0;
}
void deinitialize() {
__android_log_write(ANDROID_LOG_INFO, LOG_TAG, "DeInitialize");
if (program) {
glDeleteBuffers(1, &buffer);
glDetachShader(program, vertexShader);
glDetachShader(program, fragmentShader);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
glDeleteProgram(program);
}
}
GLuint program = 0;
GLuint vertexShader = 0;
GLuint fragmentShader = 0;
@@ -186,7 +201,8 @@ GLfloat ExampleCustomLayer::color[] = { 0.0f, 1.0f, 0.0f, 1.0f };
jlong JNICALL nativeCreateContext(JNIEnv*, jobject) {
__android_log_write(ANDROID_LOG_INFO, LOG_TAG, "nativeCreateContext");
return reinterpret_cast<jlong>(new ExampleCustomLayer());
auto exampleCustomLayer = std::make_unique<ExampleCustomLayer>();
return reinterpret_cast<jlong>(exampleCustomLayer.release());
}
void JNICALL nativeSetColor(JNIEnv*, jobject, jfloat red, jfloat green, jfloat blue, jfloat alpha) {
@@ -197,25 +213,6 @@ void JNICALL nativeSetColor(JNIEnv*, jobject, jfloat red, jfloat green, jfloat b
ExampleCustomLayer::color[3] = alpha;
}
void nativeInitialize(void *context) {
__android_log_write(ANDROID_LOG_INFO, LOG_TAG, "nativeInitialize");
reinterpret_cast<ExampleCustomLayer*>(context)->initialize();
}
void nativeRender(void *context, const mbgl::style::CustomLayerRenderParameters& /*parameters*/) {
__android_log_write(ANDROID_LOG_INFO, LOG_TAG, "nativeRender");
reinterpret_cast<ExampleCustomLayer*>(context)->render();
}
void nativeContextLost(void */*context*/) {
__android_log_write(ANDROID_LOG_INFO, LOG_TAG, "nativeContextLost");
}
void nativeDeinitialize(void *context) {
__android_log_write(ANDROID_LOG_INFO, LOG_TAG, "nativeDeinitialize");
delete reinterpret_cast<ExampleCustomLayer*>(context);
}
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
__android_log_write(ANDROID_LOG_INFO, LOG_TAG, "OnLoad");
@@ -234,22 +231,6 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
return JNI_ERR;
}
env->SetStaticLongField(customLayerClass,
env->GetStaticFieldID(customLayerClass, "InitializeFunction", "J"),
reinterpret_cast<jlong>(nativeInitialize));
env->SetStaticLongField(customLayerClass,
env->GetStaticFieldID(customLayerClass, "RenderFunction", "J"),
reinterpret_cast<jlong>(nativeRender));
env->SetStaticLongField(customLayerClass,
env->GetStaticFieldID(customLayerClass, "ContextLostFunction", "J"),
reinterpret_cast<jlong>(nativeContextLost));
env->SetStaticLongField(customLayerClass,
env->GetStaticFieldID(customLayerClass, "DeinitializeFunction", "J"),
reinterpret_cast<jlong>(nativeDeinitialize));
return JNI_VERSION_1_6;
}

View File

@@ -7,14 +7,10 @@
namespace mbgl {
namespace android {
CustomLayer::CustomLayer(jni::JNIEnv& env, jni::String layerId, jni::jlong initializeFunction, jni::jlong renderFunction, jni::jlong contextLostFunction, jni::jlong deinitializeFunction, jni::jlong context)
CustomLayer::CustomLayer(jni::JNIEnv& env, jni::String layerId, jni::jlong host)
: Layer(env, std::make_unique<mbgl::style::CustomLayer>(
jni::Make<std::string>(env, layerId),
reinterpret_cast<mbgl::style::CustomLayerInitializeFunction>(initializeFunction),
reinterpret_cast<mbgl::style::CustomLayerRenderFunction>(renderFunction),
reinterpret_cast<mbgl::style::CustomLayerContextLostFunction>(contextLostFunction),
reinterpret_cast<mbgl::style::CustomLayerDeinitializeFunction>(deinitializeFunction),
reinterpret_cast<void*>(context))
std::unique_ptr<mbgl::style::CustomLayerHost>(reinterpret_cast<mbgl::style::CustomLayerHost*>(host)))
) {
}
@@ -53,7 +49,7 @@ namespace android {
// Register the peer
jni::RegisterNativePeer<CustomLayer>(
env, CustomLayer::javaClass, "nativePtr",
std::make_unique<CustomLayer, JNIEnv&, jni::String, jni::jlong, jni::jlong, jni::jlong, jni::jlong, jni::jlong>,
std::make_unique<CustomLayer, JNIEnv&, jni::String, jni::jlong>,
"initialize",
"finalize",
METHOD(&CustomLayer::update, "nativeUpdate"));

View File

@@ -16,7 +16,7 @@ public:
static void registerNative(jni::JNIEnv&);
CustomLayer(jni::JNIEnv&, jni::String, jni::jlong, jni::jlong, jni::jlong, jni::jlong, jni::jlong);
CustomLayer(jni::JNIEnv&, jni::String, jni::jlong);
CustomLayer(mbgl::Map&, mbgl::style::CustomLayer&);

View File

@@ -7,49 +7,47 @@
#include <mbgl/style/layers/custom_layer.hpp>
#include <mbgl/math/wrap.hpp>
/**
Runs the preparation handler block contained in the given context, which is
implicitly an instance of `MGLOpenGLStyleLayer`.
class MGLOpenGLLayerHost : public mbgl::style::CustomLayerHost {
public:
MGLOpenGLLayerHost(MGLOpenGLStyleLayer *styleLayer) {
layerRef = styleLayer;
layer = nil;
}
@param context An `MGLOpenGLStyleLayer` instance that was provided as context
when creating an OpenGL style layer.
*/
void MGLPrepareCustomStyleLayer(void *context) {
MGLOpenGLStyleLayer *layer = (__bridge MGLOpenGLStyleLayer *)context;
[layer didMoveToMapView:layer.style.mapView];
}
void initialize() {
if (layerRef == nil) return;
else if (layer == nil) layer = layerRef;
/**
Runs the drawing handler block contained in the given context, which is
implicitly an instance of `MGLOpenGLStyleLayer`.
[layer didMoveToMapView:layer.style.mapView];
}
@param context An `MGLOpenGLStyleLayer` instance that was provided as context
when creating an OpenGL style layer.
*/
void MGLDrawCustomStyleLayer(void *context, const mbgl::style::CustomLayerRenderParameters &params) {
MGLOpenGLStyleLayer *layer = (__bridge MGLOpenGLStyleLayer *)context;
MGLStyleLayerDrawingContext drawingContext = {
.size = CGSizeMake(params.width, params.height),
.centerCoordinate = CLLocationCoordinate2DMake(params.latitude, params.longitude),
.zoomLevel = params.zoom,
.direction = mbgl::util::wrap(params.bearing, 0., 360.),
.pitch = static_cast<CGFloat>(params.pitch),
.fieldOfView = static_cast<CGFloat>(params.fieldOfView),
};
[layer drawInMapView:layer.style.mapView withContext:drawingContext];
}
void render(const mbgl::style::CustomLayerRenderParameters &params) {
if(!layer) return;
/**
Runs the completion handler block contained in the given context, which is
implicitly an instance of `MGLOpenGLStyleLayer`.
MGLStyleLayerDrawingContext drawingContext = {
.size = CGSizeMake(params.width, params.height),
.centerCoordinate = CLLocationCoordinate2DMake(params.latitude, params.longitude),
.zoomLevel = params.zoom,
.direction = mbgl::util::wrap(params.bearing, 0., 360.),
.pitch = static_cast<CGFloat>(params.pitch),
.fieldOfView = static_cast<CGFloat>(params.fieldOfView),
};
[layer drawInMapView:layer.style.mapView withContext:drawingContext];
}
@param context An `MGLOpenGLStyleLayer` instance that was provided as context
when creating an OpenGL style layer.
*/
void MGLFinishCustomStyleLayer(void *context) {
MGLOpenGLStyleLayer *layer = (__bridge_transfer MGLOpenGLStyleLayer *)context;
[layer willMoveFromMapView:layer.style.mapView];
}
void contextLost() {}
void deinitialize() {
if (layer == nil) return;
[layer willMoveFromMapView:layer.style.mapView];
layerRef = layer;
layer = nil;
}
private:
__weak MGLOpenGLStyleLayer * layerRef;
MGLOpenGLStyleLayer * layer = nil;
};
/**
An `MGLOpenGLStyleLayer` is a style layer that is rendered by OpenGL code that
@@ -98,10 +96,7 @@ void MGLFinishCustomStyleLayer(void *context) {
*/
- (instancetype)initWithIdentifier:(NSString *)identifier {
auto layer = std::make_unique<mbgl::style::CustomLayer>(identifier.UTF8String,
MGLPrepareCustomStyleLayer,
MGLDrawCustomStyleLayer,
MGLFinishCustomStyleLayer,
(__bridge_retained void *)self);
std::make_unique<MGLOpenGLLayerHost>(self));
return self = [super initWithPendingLayer:std::move(layer)];
}
@@ -110,22 +105,15 @@ void MGLFinishCustomStyleLayer(void *context) {
}
#pragma mark - Adding to and removing from a map view
- (void)setStyle:(MGLStyle *)style {
if (_style && style) {
[NSException raise:@"MGLLayerReuseException"
format:@"%@ cannot be added to more than one MGLStyle at a time.", self];
}
_style = style;
}
- (void)addToStyle:(MGLStyle *)style belowLayer:(MGLStyleLayer *)otherLayer {
self.style = style;
self.style.openGLLayers[self.identifier] = self;
[super addToStyle:style belowLayer:otherLayer];
}
- (void)removeFromStyle:(MGLStyle *)style {
[super removeFromStyle:style];
self.style.openGLLayers[self.identifier] = nil;
self.style = nil;
}

View File

@@ -82,6 +82,7 @@
@property (nonatomic, readonly, weak) MGLMapView *mapView;
@property (nonatomic, readonly) mbgl::style::Style *rawStyle;
@property (readonly, copy, nullable) NSURL *URL;
@property (nonatomic, readwrite, strong) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLOpenGLStyleLayer *) *openGLLayers;
@property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, NS_DICTIONARY_OF(NSObject *, MGLTextLanguage *) *) *localizedLayersByIdentifier;
@end
@@ -124,6 +125,7 @@ static_assert(6 == mbgl::util::default_styles::numOrderedStyles,
if (self = [super init]) {
_mapView = mapView;
_rawStyle = rawStyle;
_openGLLayers = [NSMutableDictionary dictionary];
_localizedLayersByIdentifier = [NSMutableDictionary dictionary];
}
return self;

View File

@@ -25,7 +25,7 @@ namespace mbgl {
@property (nonatomic, readonly) mbgl::style::Style *rawStyle;
- (nullable NS_ARRAY_OF(MGLAttributionInfo *) *)attributionInfosWithFontSize:(CGFloat)fontSize linkColor:(nullable MGLColor *)linkColor;
@property (nonatomic, readonly, strong) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLOpenGLStyleLayer *) *openGLLayers;
- (void)setStyleClasses:(NS_ARRAY_OF(NSString *) *)appliedClasses transitionDuration:(NSTimeInterval)transitionDuration;
@end

View File

@@ -1,6 +1,6 @@
#import <XCTest/XCTest.h>
@import Mapbox;
#import <objc/message.h>
#import <Mapbox/Mapbox.h>
@interface MBGLIntegrationTests : XCTestCase <MGLMapViewDelegate>
@@ -11,8 +11,11 @@
@implementation MBGLIntegrationTests {
XCTestExpectation *_styleLoadingExpectation;
XCTestExpectation *_renderFinishedExpectation;
}
#pragma mark - Setup/Teardown
- (void)setUp {
[super setUp];
@@ -32,6 +35,16 @@
[window makeKeyAndVisible];
}
- (void)tearDown {
_styleLoadingExpectation = nil;
self.mapView = nil;
self.style = nil;
[super tearDown];
}
#pragma mark - MGLMapViewDelegate
- (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style {
XCTAssertNotNil(mapView.style);
XCTAssertEqual(mapView.style, style);
@@ -39,30 +52,51 @@
[_styleLoadingExpectation fulfill];
}
- (void)tearDown {
_styleLoadingExpectation = nil;
self.mapView = nil;
- (void)mapViewDidFinishRenderingFrame:(MGLMapView *)mapView fullyRendered:(__unused BOOL)fullyRendered {
[_renderFinishedExpectation fulfill];
_renderFinishedExpectation = nil;
}
[super tearDown];
#pragma mark - Utilities
- (void)waitForMapViewToBeRendered {
[self.mapView setNeedsDisplay];
_renderFinishedExpectation = [self expectationWithDescription:@"Map view should be rendered"];
[self waitForExpectations:@[_renderFinishedExpectation] timeout:1];
}
- (MGLStyle *)style {
return self.mapView.style;
}
- (void)testAddingRemovingOpenGLLayer {
#pragma mark - Tests
// This test does not strictly need to be in this test file/target. Including here for convenience.
- (void)testOpenGLLayerDoesNotLeakWhenCreatedAndDestroyedWithoutAddingToStyle {
MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"];
__weak id weakLayer = layer;
layer = nil;
XCTAssertNil(weakLayer);
}
- (void)testAddingRemovingOpenGLLayerWithoutRendering {
XCTAssertNotNil(self.style);
void(^addRemoveGLLayer)(void) = ^{
MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer atIndex:0];
layer = nil;
__weak id weakLayer = nil;
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];
@autoreleasepool {
MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer atIndex:0];
weakLayer = layer;
id retrievedLayer = [self.style layerWithIdentifier:@"gl-layer"];
XCTAssertNotNil(retrievedLayer);
[self.style removeLayer:retrievedLayer];
// Nil the layer prior to remove to ensure it's being retained
layer = nil;
[self.style removeLayer:weakLayer];
}
XCTAssertNil(weakLayer);
};
addRemoveGLLayer();
@@ -70,8 +104,159 @@
addRemoveGLLayer();
}
//- (void)testOpenGLLayerDoesNotLeakWhenCreatedAndDestroyedWithoutAddingToStyle {
// XCTFail(@"Not yet implemented");
//}
- (void)testReusingOpenGLLayerIdentifier {
__weak MGLOpenGLStyleLayer *weakLayer2;
@autoreleasepool {
MGLOpenGLStyleLayer *layer1 = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer1 atIndex:0];
[self waitForMapViewToBeRendered];
[self.style removeLayer:layer1];
MGLOpenGLStyleLayer *layer2 = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"];
weakLayer2 = layer2;
XCTAssertNotNil(layer2);
XCTAssert(layer1 != layer2);
[self.style insertLayer:layer2 atIndex:0];
[self waitForMapViewToBeRendered];
[self.style removeLayer:layer2];
XCTAssertNil([layer1 style]);
XCTAssertNil([layer2 style]);
}
// At this point, layer2 (and layer1) should still be around,
// since the render process needs to keep a reference to them.
XCTAssertNotNil(weakLayer2);
// Let render loop run enough to release the layers
[self waitForMapViewToBeRendered];
XCTAssertNil(weakLayer2);
}
- (void)testAddingRemovingOpenGLLayer {
XCTAssertNotNil(self.style);
void(^addRemoveGLLayer)(void) = ^{
__weak id retrievedLayer = nil;
@autoreleasepool {
MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer atIndex:0];
layer = nil;
[self waitForMapViewToBeRendered];
retrievedLayer = [self.style layerWithIdentifier:@"gl-layer"];
XCTAssertNotNil(retrievedLayer);
[self.style removeLayer:retrievedLayer];
[self waitForMapViewToBeRendered];
}
XCTAssertNil(retrievedLayer);
};
addRemoveGLLayer();
addRemoveGLLayer();
addRemoveGLLayer();
}
- (void)testReusingOpenGLLayer {
MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer atIndex:0];
[self waitForMapViewToBeRendered];
[self.style removeLayer:layer];
[self waitForMapViewToBeRendered];
[self.style insertLayer:layer atIndex:0];
[self waitForMapViewToBeRendered];
[self.style removeLayer:layer];
[self waitForMapViewToBeRendered];
}
- (void)testOpenGLLayerDoesNotLeakWhenRemovedFromStyle {
__weak id weakLayer;
@autoreleasepool {
MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"];
weakLayer = layer;
[self.style insertLayer:layer atIndex:0];
layer = nil;
[self waitForMapViewToBeRendered];
[self.style removeLayer:[self.style layerWithIdentifier:@"gl-layer"]];
}
MGLStyleLayer *layer2 = weakLayer;
XCTAssertNotNil(weakLayer);
[self waitForMapViewToBeRendered];
layer2 = nil;
XCTAssertNil(weakLayer);
}
- (void)testOpenGLLayerDoesNotLeakWhenStyleChanged {
__weak MGLOpenGLStyleLayer *weakLayer;
@autoreleasepool {
{
MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"];
weakLayer = layer;
[self.style insertLayer:layer atIndex:0];
layer = nil;
}
}
XCTAssertNotNil(weakLayer);
[self waitForMapViewToBeRendered];
MGLStyleLayer *layer2 = [self.mapView.style layerWithIdentifier:@"gl-layer"];
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"];
_styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."];
[self.mapView setStyleURL:styleURL];
[self waitForExpectations:@[_styleLoadingExpectation] timeout:10];
// At this point the C++ CustomLayer will have been destroyed, and the rawLayer pointer has been NULLed
XCTAssert(weakLayer == layer2);
XCTAssertNotNil(weakLayer);
// Asking the style for the layer should return nil
MGLStyleLayer *layer3 = [self.mapView.style layerWithIdentifier:@"gl-layer"];
XCTAssertNil(layer3);
}
- (void)testOpenGLLayerDoesNotLeakWhenMapViewDeallocs {
__weak id weakLayer;
@autoreleasepool {
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"];
MGLMapView *mapView2 = [[MGLMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL];
mapView2.delegate = self;
XCTAssertNil(mapView2.style);
_styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."];
[self waitForExpectationsWithTimeout:1 handler:nil];
MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"];
weakLayer = layer;
[mapView2.style insertLayer:layer atIndex:0];
layer = nil;
[self waitForMapViewToBeRendered];
}
XCTAssertNil(weakLayer);
}
@end

View File

@@ -112,12 +112,16 @@ struct Q_MAPBOXGL_EXPORT CustomLayerRenderParameters {
double zoom;
double bearing;
double pitch;
double altitude;
double fieldOfView;
};
typedef void (*CustomLayerInitializeFunction)(void* context) ;
typedef void (*CustomLayerRenderFunction)(void* context, const CustomLayerRenderParameters&);
typedef void (*CustomLayerDeinitializeFunction)(void* context);
class Q_MAPBOXGL_EXPORT CustomLayerHostInterface {
public:
virtual ~CustomLayerHostInterface() = default;
virtual void initialize() = 0;
virtual void render(const CustomLayerRenderParameters&) = 0;
virtual void deinitialize() = 0;
};
} // namespace QMapbox

View File

@@ -223,10 +223,7 @@ public:
void removeImage(const QString &name);
void addCustomLayer(const QString &id,
QMapbox::CustomLayerInitializeFunction,
QMapbox::CustomLayerRenderFunction,
QMapbox::CustomLayerDeinitializeFunction,
void* context,
QScopedPointer<QMapbox::CustomLayerHostInterface>& host,
const QString& before = QString());
void addLayer(const QVariantMap &params, const QString& before = QString());
bool layerExists(const QString &id);

View File

@@ -158,27 +158,9 @@ namespace QMapbox {
*/
/*!
\typedef QMapbox::CustomLayerDeinitializeFunction
\class QMapbox::CustomLayerHostInterface
Represents a callback to be called when destroying a custom layer.
\warning This is used for delegating the rendering of a layer to the user of
this API and is not officially supported. Use at your own risk.
*/
/*!
\typedef QMapbox::CustomLayerInitializeFunction
Represents a callback to be called when initializing a custom layer.
\warning This is used for delegating the rendering of a layer to the user of
this API and is not officially supported. Use at your own risk.
*/
/*!
\typedef QMapbox::CustomLayerRenderFunction
Represents a callback to be called on each render pass for a custom layer.
Represents a host interface to be implemented for rendering custom layers.
\warning This is used for delegating the rendering of a layer to the user of
this API and is not officially supported. Use at your own risk.

View File

@@ -1343,20 +1343,43 @@ void QMapboxGL::removeSource(const QString& id)
this API and is not officially supported. Use at your own risk.
*/
void QMapboxGL::addCustomLayer(const QString &id,
QMapbox::CustomLayerInitializeFunction initFn,
QMapbox::CustomLayerRenderFunction renderFn,
QMapbox::CustomLayerDeinitializeFunction deinitFn,
void *context,
QScopedPointer<QMapbox::CustomLayerHostInterface>& host,
const QString& before)
{
class HostWrapper : public mbgl::style::CustomLayerHost {
public:
QScopedPointer<QMapbox::CustomLayerHostInterface> ptr;
HostWrapper(QScopedPointer<QMapbox::CustomLayerHostInterface>& p)
: ptr(p.take()) {
}
void initialize() {
ptr->initialize();
}
void render(const mbgl::style::CustomLayerRenderParameters& params) {
QMapbox::CustomLayerRenderParameters renderParams;
renderParams.width = params.width;
renderParams.height = params.height;
renderParams.latitude = params.latitude;
renderParams.longitude = params.longitude;
renderParams.zoom = params.zoom;
renderParams.bearing = params.bearing;
renderParams.pitch = params.pitch;
renderParams.fieldOfView = params.fieldOfView;
ptr->render(renderParams);
}
void contextLost() { }
void deinitialize() {
ptr->deinitialize();
}
};
d_ptr->mapObj->getStyle().addLayer(std::make_unique<mbgl::style::CustomLayer>(
id.toStdString(),
reinterpret_cast<mbgl::style::CustomLayerInitializeFunction>(initFn),
// This cast is safe as long as both mbgl:: and QMapbox::
// CustomLayerRenderParameters members remains the same.
(mbgl::style::CustomLayerRenderFunction)renderFn,
reinterpret_cast<mbgl::style::CustomLayerDeinitializeFunction>(deinitFn),
context),
std::make_unique<HostWrapper>(host)),
before.isEmpty() ? mbgl::optional<std::string>() : mbgl::optional<std::string>(before.toStdString()));
}

View File

@@ -12,17 +12,17 @@ namespace mbgl {
using namespace style;
RenderCustomLayer::RenderCustomLayer(Immutable<style::CustomLayer::Impl> _impl)
: RenderLayer(LayerType::Custom, _impl) {
: RenderLayer(LayerType::Custom, _impl), host(_impl->host) {
assert(BackendScope::exists());
host->initialize();
}
RenderCustomLayer::~RenderCustomLayer() {
assert(BackendScope::exists());
if (initialized) {
if (contextDestroyed && impl().contextLostFn ) {
impl().contextLostFn(impl().context);
} else if (!contextDestroyed && impl().deinitializeFn) {
impl().deinitializeFn(impl().context);
}
if (contextDestroyed) {
host->contextLost();
} else {
host->deinitialize();
}
}
@@ -44,15 +44,13 @@ std::unique_ptr<Bucket> RenderCustomLayer::createBucket(const BucketParameters&,
}
void RenderCustomLayer::render(PaintParameters& paintParameters, RenderSource*) {
if (context != impl().context || !initialized) {
if (host != impl().host) {
//If the context changed, deinitialize the previous one before initializing the new one.
if (context && !contextDestroyed && impl().deinitializeFn) {
MBGL_CHECK_ERROR(impl().deinitializeFn(context));
if (host && !contextDestroyed) {
MBGL_CHECK_ERROR(host->deinitialize());
}
context = impl().context;
assert(impl().initializeFn);
MBGL_CHECK_ERROR(impl().initializeFn(impl().context));
initialized = true;
host = impl().host;
MBGL_CHECK_ERROR(host->initialize());
}
gl::Context& glContext = paintParameters.context;
@@ -75,8 +73,7 @@ void RenderCustomLayer::render(PaintParameters& paintParameters, RenderSource*)
parameters.pitch = state.getPitch();
parameters.fieldOfView = state.getFieldOfView();
assert(impl().renderFn);
MBGL_CHECK_ERROR(impl().renderFn(context, parameters));
MBGL_CHECK_ERROR(host->render(parameters));
// Reset the view back to our original one, just in case the CustomLayer changed
// the viewport or Framebuffer.

View File

@@ -24,9 +24,8 @@ public:
};
private:
bool initialized = false;
bool contextDestroyed = false;
void * context = nullptr;
std::shared_ptr<style::CustomLayerHost> host;
};
template <>

View File

@@ -6,20 +6,8 @@ namespace mbgl {
namespace style {
CustomLayer::CustomLayer(const std::string& layerID,
CustomLayerInitializeFunction init,
CustomLayerRenderFunction render,
CustomLayerContextLostFunction contextLost,
CustomLayerDeinitializeFunction deinit,
void* context)
: Layer(makeMutable<Impl>(layerID, init, render, contextLost, deinit, context)) {
}
CustomLayer::CustomLayer(const std::string& layerID,
CustomLayerInitializeFunction init,
CustomLayerRenderFunction render,
CustomLayerDeinitializeFunction deinit,
void* context)
: Layer(makeMutable<Impl>(layerID, init, render, nullptr, deinit, context)) {
std::unique_ptr<CustomLayerHost> host)
: Layer(makeMutable<Impl>(layerID, std::move(host))) {
}
CustomLayer::~CustomLayer() = default;

View File

@@ -4,17 +4,9 @@ namespace mbgl {
namespace style {
CustomLayer::Impl::Impl(const std::string& id_,
CustomLayerInitializeFunction initializeFn_,
CustomLayerRenderFunction renderFn_,
CustomLayerContextLostFunction contextLostFn_,
CustomLayerDeinitializeFunction deinitializeFn_,
void* context_)
std::unique_ptr<CustomLayerHost> host_)
: Layer::Impl(LayerType::Custom, id_, std::string()) {
initializeFn = initializeFn_;
renderFn = renderFn_;
deinitializeFn = deinitializeFn_;
contextLostFn = contextLostFn_;
context = context_;
host = std::move(host_);
}
bool CustomLayer::Impl::hasLayoutDifference(const Layer::Impl&) const {

View File

@@ -3,6 +3,8 @@
#include <mbgl/style/layer_impl.hpp>
#include <mbgl/style/layers/custom_layer.hpp>
#include <memory>
namespace mbgl {
class TransformState;
@@ -12,20 +14,12 @@ namespace style {
class CustomLayer::Impl : public Layer::Impl {
public:
Impl(const std::string& id,
CustomLayerInitializeFunction,
CustomLayerRenderFunction,
CustomLayerContextLostFunction,
CustomLayerDeinitializeFunction,
void* context);
std::unique_ptr<CustomLayerHost> host);
bool hasLayoutDifference(const Layer::Impl&) const override;
void stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const override;
CustomLayerInitializeFunction initializeFn = nullptr;
CustomLayerRenderFunction renderFn = nullptr;
CustomLayerContextLostFunction contextLostFn = nullptr;
CustomLayerDeinitializeFunction deinitializeFn = nullptr;
void* context = nullptr;
std::shared_ptr<CustomLayerHost> host;
};
} // namespace style

View File

@@ -34,19 +34,8 @@ void main() {
// layer implementation because it is intended to reflect how someone using custom layers
// might actually write their own implementation.
class TestLayer {
class TestLayer : public mbgl::style::CustomLayerHost {
public:
~TestLayer() {
if (program) {
MBGL_CHECK_ERROR(glDeleteBuffers(1, &buffer));
MBGL_CHECK_ERROR(glDetachShader(program, vertexShader));
MBGL_CHECK_ERROR(glDetachShader(program, fragmentShader));
MBGL_CHECK_ERROR(glDeleteShader(vertexShader));
MBGL_CHECK_ERROR(glDeleteShader(fragmentShader));
MBGL_CHECK_ERROR(glDeleteProgram(program));
}
}
void initialize() {
program = MBGL_CHECK_ERROR(glCreateProgram());
vertexShader = MBGL_CHECK_ERROR(glCreateShader(GL_VERTEX_SHADER));
@@ -67,7 +56,7 @@ public:
MBGL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(GLfloat), triangle, GL_STATIC_DRAW));
}
void render() {
void render(const mbgl::style::CustomLayerRenderParameters&) {
MBGL_CHECK_ERROR(glUseProgram(program));
MBGL_CHECK_ERROR(glBindBuffer(GL_ARRAY_BUFFER, buffer));
MBGL_CHECK_ERROR(glEnableVertexAttribArray(a_pos));
@@ -75,6 +64,19 @@ public:
MBGL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, 3));
}
void contextLost() {}
void deinitialize() {
if (program) {
MBGL_CHECK_ERROR(glDeleteBuffers(1, &buffer));
MBGL_CHECK_ERROR(glDetachShader(program, vertexShader));
MBGL_CHECK_ERROR(glDetachShader(program, fragmentShader));
MBGL_CHECK_ERROR(glDeleteShader(vertexShader));
MBGL_CHECK_ERROR(glDeleteShader(fragmentShader));
MBGL_CHECK_ERROR(glDeleteProgram(program));
}
}
GLuint program = 0;
GLuint vertexShader = 0;
GLuint fragmentShader = 0;
@@ -95,15 +97,7 @@ TEST(CustomLayer, Basic) {
map.setLatLngZoom({ 37.8, -122.5 }, 10);
map.getStyle().addLayer(std::make_unique<CustomLayer>(
"custom",
[] (void* context) {
reinterpret_cast<TestLayer*>(context)->initialize();
},
[] (void* context, const CustomLayerRenderParameters&) {
reinterpret_cast<TestLayer*>(context)->render();
},
[] (void* context) {
delete reinterpret_cast<TestLayer*>(context);
}, new TestLayer()));
std::make_unique<TestLayer>()));
auto layer = std::make_unique<FillLayer>("landcover", "mapbox");
layer->setSourceLayer("landcover");