-
-
Notifications
You must be signed in to change notification settings - Fork 341
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support defining a custom style layer from iOS Swift (#3154)
- Loading branch information
1 parent
10f5c90
commit a69c5dc
Showing
13 changed files
with
263 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
186
platform/ios/app-swift/Sources/CustomStyleLayerExample.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters