Support defining a custom style layer from iOS Swift (#3154)
This commit is contained in:
@@ -72,7 +72,7 @@
|
||||
}
|
||||
|
||||
- (void)didMoveToMapView:(MLNMapView *)mapView {
|
||||
MLNBackendResource resource = [mapView backendResource];
|
||||
MLNBackendResource* resource = [mapView backendResource];
|
||||
|
||||
NSString *shaderSource = @
|
||||
" #include <metal_stdlib>\n"
|
||||
@@ -140,7 +140,7 @@
|
||||
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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
platform/darwin/src/MLNBackendResource.mm
Normal file
36
platform/darwin/src/MLNBackendResource.mm
Normal file
@@ -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
platform/ios/app-swift/Sources/CustomStyleLayerExample.swift
Normal file
186
platform/ios/app-swift/Sources/CustomStyleLayerExample.swift
Normal file
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -227,13 +227,12 @@ void MLNMapViewMetalImpl::layoutChanged() {
|
||||
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];
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -253,6 +253,6 @@ EAGLContext* MLNMapViewOpenGLImpl::getEAGLContext() {
|
||||
return resource.context;
|
||||
}
|
||||
|
||||
MLNBackendResource MLNMapViewOpenGLImpl::getObject() {
|
||||
return MLNBackendResource();
|
||||
MLNBackendResource* MLNMapViewOpenGLImpl::getObject() {
|
||||
return [[MLNBackendResource alloc] init];
|
||||
}
|
||||
|
||||
@@ -2120,7 +2120,7 @@ vertically on the map.
|
||||
*/
|
||||
@property (nonatomic) MLNMapDebugMaskOptions debugMask;
|
||||
|
||||
- (MLNBackendResource)backendResource;
|
||||
- (MLNBackendResource *)backendResource;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -7361,7 +7361,7 @@ static void *windowScreenContext = &windowScreenContext;
|
||||
return _annotationViewReuseQueueByIdentifier[identifier];
|
||||
}
|
||||
|
||||
- (MLNBackendResource)backendResource {
|
||||
- (MLNBackendResource*)backendResource {
|
||||
return _mbglView->getObject();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user