diff --git a/platform/darwin/app/CustomStyleLayerExample.m b/platform/darwin/app/CustomStyleLayerExample.m index c2f69589d..c2f4b6a5c 100644 --- a/platform/darwin/app/CustomStyleLayerExample.m +++ b/platform/darwin/app/CustomStyleLayerExample.m @@ -72,7 +72,7 @@ } - (void)didMoveToMapView:(MLNMapView *)mapView { - MLNBackendResource resource = [mapView backendResource]; + MLNBackendResource* resource = [mapView backendResource]; NSString *shaderSource = @ " #include \n" @@ -140,7 +140,7 @@ id renderEncoder = self.renderEncoder; if(renderEncoder != nil) { - MLNBackendResource resource = [mapView backendResource]; + MLNBackendResource* resource = [mapView backendResource]; vector_uint2 _viewportSize; _viewportSize.x = resource.mtkView.drawableSize.width; diff --git a/platform/darwin/bazel/files.bzl b/platform/darwin/bazel/files.bzl index fd344cb28..fb76746c4 100644 --- a/platform/darwin/bazel/files.bzl +++ b/platform/darwin/bazel/files.bzl @@ -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", diff --git a/platform/darwin/src/MLNBackendResource.h b/platform/darwin/src/MLNBackendResource.h index e8af9ae63..4830ca05b 100644 --- a/platform/darwin/src/MLNBackendResource.h +++ b/platform/darwin/src/MLNBackendResource.h @@ -2,16 +2,28 @@ #import -typedef struct { - MTKView *mtkView; - id device; - MTLRenderPassDescriptor *renderPassDescriptor; - id commandBuffer; -} MLNBackendResource; +@interface MLNBackendResource : NSObject + +@property (nonatomic, strong) MTKView *mtkView; +@property (nonatomic, strong) id device; +@property (nonatomic, strong) MTLRenderPassDescriptor *renderPassDescriptor; +@property (nonatomic, strong) id commandBuffer; + +- (instancetype)initWithMTKView:(MTKView *)mtkView + device:(id)device + renderPassDescriptor:(MTLRenderPassDescriptor *)renderPassDescriptor + commandBuffer:(id)commandBuffer; + +@end #else -typedef struct { -} MLNBackendResource; +#import +#import "MLNFoundation.h" + +MLN_EXPORT +@interface MLNBackendResource : NSObject + +@end #endif diff --git a/platform/darwin/src/MLNBackendResource.mm b/platform/darwin/src/MLNBackendResource.mm new file mode 100644 index 000000000..7e3e84564 --- /dev/null +++ b/platform/darwin/src/MLNBackendResource.mm @@ -0,0 +1,36 @@ +#if MLN_RENDER_BACKEND_METAL + +#import "MLNBackendResource.h" + +@implementation MLNBackendResource + +- (instancetype)initWithMTKView:(MTKView *)mtkView + device:(id)device + renderPassDescriptor:(MTLRenderPassDescriptor *)renderPassDescriptor + commandBuffer:(id)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 \ No newline at end of file diff --git a/platform/ios/app-swift/Sources/CustomStyleLayerExample.swift b/platform/ios/app-swift/Sources/CustomStyleLayerExample.swift new file mode 100644 index 000000000..0d3c3237a --- /dev/null +++ b/platform/ios/app-swift/Sources/CustomStyleLayerExample.swift @@ -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 + 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.size * triangleVertices.count, index: 0) + renderEncoder.setVertexBytes(&projectionMatrix, length: MemoryLayout.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 diff --git a/platform/ios/app-swift/Sources/MapLibreNavigationView.swift b/platform/ios/app-swift/Sources/MapLibreNavigationView.swift index db2c95a32..e08aa876e 100644 --- a/platform/ios/app-swift/Sources/MapLibreNavigationView.swift +++ b/platform/ios/app-swift/Sources/MapLibreNavigationView.swift @@ -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) } diff --git a/platform/ios/src/MLNMapView+Impl.h b/platform/ios/src/MLNMapView+Impl.h index c78fd4531..ff413367e 100644 --- a/platform/ios/src/MLNMapView+Impl.h +++ b/platform/ios/src/MLNMapView+Impl.h @@ -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; diff --git a/platform/ios/src/MLNMapView+Metal.h b/platform/ios/src/MLNMapView+Metal.h index cd7e80a15..39cdc98a2 100644 --- a/platform/ios/src/MLNMapView+Metal.h +++ b/platform/ios/src/MLNMapView+Metal.h @@ -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 }; diff --git a/platform/ios/src/MLNMapView+Metal.mm b/platform/ios/src/MLNMapView+Metal.mm index 54c530c83..976b54717 100644 --- a/platform/ios/src/MLNMapView+Metal.mm +++ b/platform/ios/src/MLNMapView+Metal.mm @@ -227,13 +227,12 @@ void MLNMapViewMetalImpl::layoutChanged() { static_cast(mapView.bounds.size.height * scaleFactor) }; } -MLNBackendResource MLNMapViewMetalImpl::getObject() { +MLNBackendResource* MLNMapViewMetalImpl::getObject() { auto& resource = getResource(); 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]; } diff --git a/platform/ios/src/MLNMapView+OpenGL.h b/platform/ios/src/MLNMapView+OpenGL.h index bd7d60f0e..263ab3461 100644 --- a/platform/ios/src/MLNMapView+OpenGL.h +++ b/platform/ios/src/MLNMapView+OpenGL.h @@ -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 }; diff --git a/platform/ios/src/MLNMapView+OpenGL.mm b/platform/ios/src/MLNMapView+OpenGL.mm index 3e3cc0006..ab3c6625a 100644 --- a/platform/ios/src/MLNMapView+OpenGL.mm +++ b/platform/ios/src/MLNMapView+OpenGL.mm @@ -253,6 +253,6 @@ EAGLContext* MLNMapViewOpenGLImpl::getEAGLContext() { return resource.context; } -MLNBackendResource MLNMapViewOpenGLImpl::getObject() { - return MLNBackendResource(); +MLNBackendResource* MLNMapViewOpenGLImpl::getObject() { + return [[MLNBackendResource alloc] init]; } diff --git a/platform/ios/src/MLNMapView.h b/platform/ios/src/MLNMapView.h index a3a1ce7f7..7e8423c68 100644 --- a/platform/ios/src/MLNMapView.h +++ b/platform/ios/src/MLNMapView.h @@ -2120,7 +2120,7 @@ vertically on the map. */ @property (nonatomic) MLNMapDebugMaskOptions debugMask; -- (MLNBackendResource)backendResource; +- (MLNBackendResource *)backendResource; @end NS_ASSUME_NONNULL_END diff --git a/platform/ios/src/MLNMapView.mm b/platform/ios/src/MLNMapView.mm index fe9c58a6d..24dd6128b 100644 --- a/platform/ios/src/MLNMapView.mm +++ b/platform/ios/src/MLNMapView.mm @@ -7361,7 +7361,7 @@ static void *windowScreenContext = &windowScreenContext; return _annotationViewReuseQueueByIdentifier[identifier]; } -- (MLNBackendResource)backendResource { +- (MLNBackendResource*)backendResource { return _mbglView->getObject(); }