Skip to content

Commit

Permalink
Support defining a custom style layer from iOS Swift (#3154)
Browse files Browse the repository at this point in the history
  • Loading branch information
christian-boks authored Jan 22, 2025
1 parent 10f5c90 commit a69c5dc
Show file tree
Hide file tree
Showing 13 changed files with 263 additions and 24 deletions.
4 changes: 2 additions & 2 deletions platform/darwin/app/CustomStyleLayerExample.m
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ @implementation CustomStyleLayerExample {
}

- (void)didMoveToMapView:(MLNMapView *)mapView {
MLNBackendResource resource = [mapView backendResource];
MLNBackendResource* resource = [mapView backendResource];

NSString *shaderSource = @
" #include <metal_stdlib>\n"
Expand Down Expand Up @@ -140,7 +140,7 @@ - (void)drawInMapView:(MLNMapView *)mapView withContext:(MLNStyleLayerDrawingCon
id<MTLRenderCommandEncoder> renderEncoder = self.renderEncoder;
if(renderEncoder != nil)
{
MLNBackendResource resource = [mapView backendResource];
MLNBackendResource* resource = [mapView backendResource];

vector_uint2 _viewportSize;
_viewportSize.x = resource.mtkView.drawableSize.width;
Expand Down
1 change: 1 addition & 0 deletions platform/darwin/bazel/files.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ MLN_DARWIN_PRIVATE_HEADERS = [

MLN_DARWIN_PUBLIC_OBJCPP_SOURCE = [
"src/MLNAttributionInfo.mm",
"src/MLNBackendResource.mm",
"src/MLNComputedShapeSource.mm",
"src/MLNCustomStyleLayer.mm",
"src/MLNDefaultStyle.mm",
Expand Down
28 changes: 20 additions & 8 deletions platform/darwin/src/MLNBackendResource.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,28 @@

#import <MetalKit/MetalKit.h>

typedef struct {
MTKView *mtkView;
id<MTLDevice> device;
MTLRenderPassDescriptor *renderPassDescriptor;
id<MTLCommandBuffer> commandBuffer;
} MLNBackendResource;
@interface MLNBackendResource : NSObject

@property (nonatomic, strong) MTKView *mtkView;
@property (nonatomic, strong) id<MTLDevice> device;
@property (nonatomic, strong) MTLRenderPassDescriptor *renderPassDescriptor;
@property (nonatomic, strong) id<MTLCommandBuffer> commandBuffer;

- (instancetype)initWithMTKView:(MTKView *)mtkView
device:(id<MTLDevice>)device
renderPassDescriptor:(MTLRenderPassDescriptor *)renderPassDescriptor
commandBuffer:(id<MTLCommandBuffer>)commandBuffer;

@end

#else

typedef struct {
} MLNBackendResource;
#import <Foundation/Foundation.h>
#import "MLNFoundation.h"

MLN_EXPORT
@interface MLNBackendResource : NSObject

@end

#endif
36 changes: 36 additions & 0 deletions platform/darwin/src/MLNBackendResource.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#if MLN_RENDER_BACKEND_METAL

#import "MLNBackendResource.h"

@implementation MLNBackendResource

- (instancetype)initWithMTKView:(MTKView *)mtkView
device:(id<MTLDevice>)device
renderPassDescriptor:(MTLRenderPassDescriptor *)renderPassDescriptor
commandBuffer:(id<MTLCommandBuffer>)commandBuffer {
self = [super init];
if (self) {
_mtkView = mtkView;
_device = device;
_renderPassDescriptor = renderPassDescriptor;
_commandBuffer = commandBuffer;
}
return self;
}

@end

#else

#import "MLNBackendResource.h"

@implementation MLNBackendResource

- (instancetype)init {
self = [super init];
return self;
}

@end

#endif
186 changes: 186 additions & 0 deletions platform/ios/app-swift/Sources/CustomStyleLayerExample.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import Foundation
import MapLibre
import MetalKit
import SwiftUI
import UIKit

// #-example-code(CustomStyleLayerExample)
struct CustomStyleLayerExample: UIViewRepresentable {
func makeCoordinator() -> CustomStyleLayerExample.Coordinator {
Coordinator(self)
}

final class Coordinator: NSObject, MLNMapViewDelegate {
var control: CustomStyleLayerExample

init(_ control: CustomStyleLayerExample) {
self.control = control
}

func mapViewDidFinishLoadingMap(_ mapView: MLNMapView) {
let mapOverlay = CustomStyleLayer(identifier: "test-overlay")
let style = mapView.style!
style.layers.append(mapOverlay)
}
}

func makeUIView(context: Context) -> MLNMapView {
let mapView = MLNMapView()
mapView.delegate = context.coordinator
return mapView
}

func updateUIView(_: MLNMapView, context _: Context) {}
}

class CustomStyleLayer: MLNCustomStyleLayer {
private var pipelineState: MTLRenderPipelineState?
private var depthStencilStateWithoutStencil: MTLDepthStencilState?

override func didMove(to mapView: MLNMapView) {
#if MLN_RENDER_BACKEND_METAL
let resource = mapView.backendResource()

let shaderSource = """
#include <metal_stdlib>
using namespace metal;
typedef struct
{
vector_float2 position;
vector_float4 color;
} Vertex;
struct RasterizerData
{
float4 position [[position]];
float4 color;
};
struct Uniforms
{
float4x4 matrix;
};
vertex RasterizerData
vertexShader(uint vertexID [[vertex_id]],
constant Vertex *vertices [[buffer(0)]],
constant Uniforms &uniforms [[buffer(1)]])
{
RasterizerData out;
const float4 position = uniforms.matrix * float4(float2(vertices[vertexID].position.xy), 1, 1);
out.position = position;
out.color = vertices[vertexID].color;
return out;
}
fragment float4 fragmentShader(RasterizerData in [[stage_in]])
{
return in.color;
}
"""

var error: NSError?
let device = resource.device
let library = try? device?.makeLibrary(source: shaderSource, options: nil)
assert(library != nil, "Error compiling shaders: \(String(describing: error))")
let vertexFunction = library?.makeFunction(name: "vertexShader")
let fragmentFunction = library?.makeFunction(name: "fragmentShader")

// Configure a pipeline descriptor that is used to create a pipeline state.
let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
pipelineStateDescriptor.label = "Simple Pipeline"
pipelineStateDescriptor.vertexFunction = vertexFunction
pipelineStateDescriptor.fragmentFunction = fragmentFunction
pipelineStateDescriptor.colorAttachments[0].pixelFormat = resource.mtkView.colorPixelFormat
pipelineStateDescriptor.depthAttachmentPixelFormat = .depth32Float_stencil8
pipelineStateDescriptor.stencilAttachmentPixelFormat = .depth32Float_stencil8

do {
pipelineState = try device?.makeRenderPipelineState(descriptor: pipelineStateDescriptor)
} catch {
assertionFailure("Failed to create pipeline state: \(error)")
}

// Notice that we don't configure the stencilTest property, leaving stencil testing disabled
let depthStencilDescriptor = MTLDepthStencilDescriptor()
depthStencilDescriptor.depthCompareFunction = .always // Or another value as needed
depthStencilDescriptor.isDepthWriteEnabled = false

depthStencilStateWithoutStencil = device!.makeDepthStencilState(descriptor: depthStencilDescriptor)
#endif
}

override func willMove(from _: MLNMapView) {}

override func draw(in _: MLNMapView, with context: MLNStyleLayerDrawingContext) {
#if MLN_RENDER_BACKEND_METAL
// Use the supplied render command encoder to encode commands
guard let renderEncoder else {
return
}

let p1 = project(CLLocationCoordinate2D(latitude: 25.0, longitude: 12.5))
let p2 = project(CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0))
let p3 = project(CLLocationCoordinate2D(latitude: 0.0, longitude: 25.0))

struct Vertex {
var position: vector_float2
var color: vector_float4
}

let triangleVertices: [Vertex] = [
Vertex(position: vector_float2(Float(p1.x), Float(p1.y)), color: vector_float4(1, 0, 0, 1)),
Vertex(position: vector_float2(Float(p2.x), Float(p2.y)), color: vector_float4(0, 1, 0, 1)),
Vertex(position: vector_float2(Float(p3.x), Float(p3.y)), color: vector_float4(0, 0, 1, 1)),
]

// Convert the projection matrix to float from double, and scale it to match our projection
var projectionMatrix = convertMatrix(context.projectionMatrix)
let worldSize = 512.0 * pow(2.0, context.zoomLevel)
projectionMatrix.m00 = projectionMatrix.m00 * Float(worldSize)
projectionMatrix.m11 = projectionMatrix.m11 * Float(worldSize)

renderEncoder.setRenderPipelineState(pipelineState!)
renderEncoder.setDepthStencilState(depthStencilStateWithoutStencil)

// Pass in the parameter data.
renderEncoder.setVertexBytes(triangleVertices, length: MemoryLayout<Vertex>.size * triangleVertices.count, index: 0)
renderEncoder.setVertexBytes(&projectionMatrix, length: MemoryLayout<float4x4>.size, index: 1)

// Draw the triangle.
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)
#endif
}

func project(_ coordinate: CLLocationCoordinate2D) -> CGPoint {
// We project the coordinates into the space 0 to 1 and then scale these when drawing based on the current zoom level
let worldSize = 1.0
let x = (180.0 + coordinate.longitude) / 360.0 * worldSize
let yi = log(tan((45.0 + coordinate.latitude / 2.0) * Double.pi / 180.0))
let y = (180.0 - yi * (180.0 / Double.pi)) / 360.0 * worldSize

return CGPoint(x: x, y: y)
}

struct MLNMatrix4f {
var m00, m01, m02, m03: Float
var m10, m11, m12, m13: Float
var m20, m21, m22, m23: Float
var m30, m31, m32, m33: Float
}

func convertMatrix(_ mat: MLNMatrix4) -> MLNMatrix4f {
MLNMatrix4f(
m00: Float(mat.m00), m01: Float(mat.m01), m02: Float(mat.m02), m03: Float(mat.m03),
m10: Float(mat.m10), m11: Float(mat.m11), m12: Float(mat.m12), m13: Float(mat.m13),
m20: Float(mat.m20), m21: Float(mat.m21), m22: Float(mat.m22), m23: Float(mat.m23),
m30: Float(mat.m30), m31: Float(mat.m31), m32: Float(mat.m32), m33: Float(mat.m33)
)
}
}

// #-end-example-code
5 changes: 5 additions & 0 deletions platform/ios/app-swift/Sources/MapLibreNavigationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ struct MapLibreNavigationView: View {
NavigationLink("SimpleMap") {
SimpleMap().edgesIgnoringSafeArea(.all)
}
#if MLN_RENDER_BACKEND_METAL
NavigationLink("CustomStyleLayer (Metal)") {
CustomStyleLayerExample().edgesIgnoringSafeArea(.all)
}
#endif
NavigationLink("LineTapMap") {
LineTapMap().edgesIgnoringSafeArea(.all)
}
Expand Down
2 changes: 1 addition & 1 deletion platform/ios/src/MLNMapView+Impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class MLNMapViewImpl : public mbgl::MapObserver {
// Called by the view delegate when it's time to render.
void render();

virtual MLNBackendResource getObject() = 0;
virtual MLNBackendResource* getObject() = 0;

// mbgl::MapObserver implementation
void onCameraWillChange(mbgl::MapObserver::CameraChangeMode) override;
Expand Down
2 changes: 1 addition & 1 deletion platform/ios/src/MLNMapView+Metal.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@ class MLNMapViewMetalImpl final : public MLNMapViewImpl,
void deleteView() override;
UIImage* snapshot() override;
void layoutChanged() override;
MLNBackendResource getObject() override;
MLNBackendResource* getObject() override;
// End implementation of MLNMapViewImpl
};
13 changes: 6 additions & 7 deletions platform/ios/src/MLNMapView+Metal.mm
Original file line number Diff line number Diff line change
Expand Up @@ -227,13 +227,12 @@ void swap() override {
static_cast<uint32_t>(mapView.bounds.size.height * scaleFactor) };
}

MLNBackendResource MLNMapViewMetalImpl::getObject() {
MLNBackendResource* MLNMapViewMetalImpl::getObject() {
auto& resource = getResource<MLNMapViewMetalRenderableResource>();
auto renderPassDescriptor = resource.getRenderPassDescriptor().get();
return {
resource.mtlView,
resource.mtlView.device,
[MTLRenderPassDescriptor renderPassDescriptor],
resource.commandBuffer
};

return [[MLNBackendResource alloc] initWithMTKView:resource.mtlView
device:resource.mtlView.device
renderPassDescriptor:[MTLRenderPassDescriptor renderPassDescriptor]
commandBuffer:resource.commandBuffer];
}
2 changes: 1 addition & 1 deletion platform/ios/src/MLNMapView+OpenGL.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,6 @@ class MLNMapViewOpenGLImpl final : public MLNMapViewImpl,
void deleteView() override;
UIImage* snapshot() override;
void layoutChanged() override;
MLNBackendResource getObject() override;
MLNBackendResource* getObject() override;
// End implementation of MLNMapViewImpl
};
4 changes: 2 additions & 2 deletions platform/ios/src/MLNMapView+OpenGL.mm
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,6 @@ void bind() override {
return resource.context;
}

MLNBackendResource MLNMapViewOpenGLImpl::getObject() {
return MLNBackendResource();
MLNBackendResource* MLNMapViewOpenGLImpl::getObject() {
return [[MLNBackendResource alloc] init];
}
2 changes: 1 addition & 1 deletion platform/ios/src/MLNMapView.h
Original file line number Diff line number Diff line change
Expand Up @@ -2120,7 +2120,7 @@ vertically on the map.
*/
@property (nonatomic) MLNMapDebugMaskOptions debugMask;

- (MLNBackendResource)backendResource;
- (MLNBackendResource *)backendResource;
@end

NS_ASSUME_NONNULL_END
2 changes: 1 addition & 1 deletion platform/ios/src/MLNMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -7361,7 +7361,7 @@ - (void)prepareForInterfaceBuilder
return _annotationViewReuseQueueByIdentifier[identifier];
}

- (MLNBackendResource)backendResource {
- (MLNBackendResource*)backendResource {
return _mbglView->getObject();
}

Expand Down

0 comments on commit a69c5dc

Please sign in to comment.