Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix/multi buffers #653

Merged
merged 12 commits into from
Nov 12, 2024

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions bridging/android/jni/map/NativeMapCameraInterface.cpp

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions bridging/ios/MCMapCameraInterface+Private.mm

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions bridging/ios/MCMapCameraInterface.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions djinni/map/core.djinni
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ map_camera_interface = interface +c {
screen_pos_from_coord_zoom(coord: coord, zoom: f32): vec_2_f;

map_units_from_pixels(distancePx: f64): f64;
get_scaling_factor(): f64;

# padding in percentage, where 1.0 = rect is half of full width and height
coord_is_visible_on_screen(coord: coord, padding_pc: f32): bool;

Expand Down
75 changes: 46 additions & 29 deletions ios/graphics/Model/BaseGraphicsObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,37 @@ open class BaseGraphicsObject: @unchecked Sendable {
public let lock = OSLock()

public var originOffset: MCVec3D = .init(x: 0, y: 0, z: 0)
public var originOffsetBuffer: MTLBuffer?

public init(device: MTLDevice, sampler: MTLSamplerState, label: String = "") {
public var originOffsetBuffers: MultiBuffer<simd_float4>

public var vpMatrixBuffers: MultiBuffer<simd_float4x4>

public init(device: MTLDevice, sampler: MTLSamplerState, label: String = "")
{
self.device = device
self.sampler = sampler
self.label = label

var originOffset: simd_float4 = simd_float4(0, 0, 0, 0)
originOffsetBuffer = device.makeBuffer(bytes: &originOffset, length: MemoryLayout<simd_float4>.stride, options: [])
self.originOffsetBuffers = .init(device: device)
self.vpMatrixBuffers = .init(device: device)
}

open func render(encoder _: MTLRenderCommandEncoder,
context _: RenderingContext,
renderPass _: MCRenderPassConfig,
vpMatrix _: Int64,
mMatrix _: Int64,
origin: MCVec3D,
isMasked _: Bool,
screenPixelAsRealMeterFactor _: Double) {
open func render(
encoder _: MTLRenderCommandEncoder,
context _: RenderingContext,
renderPass _: MCRenderPassConfig,
vpMatrix _: Int64,
mMatrix _: Int64,
origin: MCVec3D,
isMasked _: Bool,
screenPixelAsRealMeterFactor _: Double
) {
fatalError("has to be overwritten by subclass")
}

open func compute(encoder _: MTLComputeCommandEncoder,
context _: RenderingContext) {
open func compute(
encoder _: MTLComputeCommandEncoder,
context _: RenderingContext
) {
// subclasses may override
}
}
Expand All @@ -80,32 +87,42 @@ extension BaseGraphicsObject: MCGraphicsObjectInterface {
maskInverse = inversed
}

public func render(_ context: MCRenderingContextInterface?, renderPass: MCRenderPassConfig, vpMatrix: Int64, mMatrix: Int64, origin: MCVec3D, isMasked: Bool, screenPixelAsRealMeterFactor: Double) {
public func render(
_ context: MCRenderingContextInterface?, renderPass: MCRenderPassConfig,
vpMatrix: Int64, mMatrix: Int64, origin: MCVec3D, isMasked: Bool,
screenPixelAsRealMeterFactor: Double
) {
guard isReady(),
let context = context as? RenderingContext,
let encoder = context.encoder
let context = context as? RenderingContext,
let encoder = context.encoder
else { return }

render(encoder: encoder,
context: context,
renderPass: renderPass,
vpMatrix: vpMatrix,
mMatrix: mMatrix,
origin: origin,
isMasked: isMasked,
screenPixelAsRealMeterFactor: screenPixelAsRealMeterFactor)
render(
encoder: encoder,
context: context,
renderPass: renderPass,
vpMatrix: vpMatrix,
mMatrix: mMatrix,
origin: origin,
isMasked: isMasked,
screenPixelAsRealMeterFactor: screenPixelAsRealMeterFactor)
}

public func compute(_ context: (any MCRenderingContextInterface)?, renderPass: MCRenderPassConfig) {
public func compute(
_ context: (any MCRenderingContextInterface)?,
renderPass: MCRenderPassConfig
) {
guard let context = context as? RenderingContext,
let encoder = context.computeEncoder
let encoder = context.computeEncoder
else { return }
compute(encoder: encoder, context: context)
}

// MARK: - Stencil

public func maskStencilState(readMask: UInt32 = 0b1111_1111, writeMask: UInt32 = 0b0000_0000) -> MTLDepthStencilState? {
public func maskStencilState(
readMask: UInt32 = 0b1111_1111, writeMask: UInt32 = 0b0000_0000
) -> MTLDepthStencilState? {
let s = MTLStencilDescriptor()
s.stencilCompareFunction = .equal
s.stencilFailureOperation = .zero
Expand Down
10 changes: 8 additions & 2 deletions ios/graphics/Model/Icosahedron/Icosahedron.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,12 @@ final class Icosahedron: BaseGraphicsObject, @unchecked Sendable {
shader.preRender(context)

encoder.setVertexBuffer(verticesBuffer, offset: 0, index: 0)

let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context)
if let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) {
encoder.setVertexBytes(matrixPointer, length: 64, index: 1)
vpMatrixBuffer?.contents().copyMemory(from: matrixPointer, byteCount: 64)
}
encoder.setVertexBuffer(vpMatrixBuffer, offset: 0, index: 1)
Comment on lines +73 to +77
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add safety checks for buffer and pointer operations.

The current implementation has potential safety issues:

  1. No nil check on vpMatrixBuffer before use
  2. Unchecked memory access through UnsafeRawPointer

Consider applying this safer implementation:

 let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context)
-if let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) {
+guard let vpMatrixBuffer = vpMatrixBuffer,
+      let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) else {
+    return
+}
-    vpMatrixBuffer?.contents().copyMemory(from: matrixPointer, byteCount: 64)
-}
+vpMatrixBuffer.contents().copyMemory(from: matrixPointer, byteCount: 64)
 encoder.setVertexBuffer(vpMatrixBuffer, offset: 0, index: 1)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context)
if let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) {
encoder.setVertexBytes(matrixPointer, length: 64, index: 1)
vpMatrixBuffer?.contents().copyMemory(from: matrixPointer, byteCount: 64)
}
encoder.setVertexBuffer(vpMatrixBuffer, offset: 0, index: 1)
let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context)
guard let vpMatrixBuffer = vpMatrixBuffer,
let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) else {
return
}
vpMatrixBuffer.contents().copyMemory(from: matrixPointer, byteCount: 64)
encoder.setVertexBuffer(vpMatrixBuffer, offset: 0, index: 1)

Comment on lines +72 to +77
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add null check before accessing vpMatrixBuffer contents.

The buffer management change from setVertexBytes to pre-allocated buffers is a good improvement for performance. However, there's a potential crash risk if vpMatrixBuffer is nil when accessing its contents.

Consider adding a null check:

 let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context)
-if let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) {
+if let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)),
+   let buffer = vpMatrixBuffer {
-    vpMatrixBuffer?.contents().copyMemory(from: matrixPointer, byteCount: 64)
+    buffer.contents().copyMemory(from: matrixPointer, byteCount: 64)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context)
if let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) {
encoder.setVertexBytes(matrixPointer, length: 64, index: 1)
vpMatrixBuffer?.contents().copyMemory(from: matrixPointer, byteCount: 64)
}
encoder.setVertexBuffer(vpMatrixBuffer, offset: 0, index: 1)
let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context)
if let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)),
let buffer = vpMatrixBuffer {
buffer.contents().copyMemory(from: matrixPointer, byteCount: 64)
}
encoder.setVertexBuffer(vpMatrixBuffer, offset: 0, index: 1)


encoder.drawIndexedPrimitives(type: .triangle,
indexCount: indicesCount,
Expand Down Expand Up @@ -120,9 +123,12 @@ extension Icosahedron: MCMaskingObjectInterface {
shader.preRender(context)

encoder.setVertexBuffer(verticesBuffer, offset: 0, index: 0)

let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context)
if let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) {
encoder.setVertexBytes(matrixPointer, length: 64, index: 1)
vpMatrixBuffer?.contents().copyMemory(from: matrixPointer, byteCount: 64)
}
encoder.setVertexBuffer(vpMatrixBuffer, offset: 0, index: 1)
Comment on lines +127 to +131
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Extract duplicated matrix buffer logic into a helper method.

This code block is identical to the one in the first render method. Consider extracting it to reduce duplication and centralize the safety improvements.

Create a private helper method:

private func setupVPMatrixBuffer(_ vpMatrix: Int64, context: RenderingContext, encoder: MTLRenderCommandEncoder) {
    guard let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context),
          let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) else {
        return
    }
    vpMatrixBuffer.contents().copyMemory(from: matrixPointer, byteCount: 64)
    encoder.setVertexBuffer(vpMatrixBuffer, offset: 0, index: 1)
}

Then use it in both render methods:

-let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context)
-if let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) {
-    vpMatrixBuffer?.contents().copyMemory(from: matrixPointer, byteCount: 64)
-}
-encoder.setVertexBuffer(vpMatrixBuffer, offset: 0, index: 1)
+setupVPMatrixBuffer(vpMatrix, context: context, encoder: encoder)


encoder.drawIndexedPrimitives(type: .triangle,
indexCount: indicesCount,
Expand Down
9 changes: 8 additions & 1 deletion ios/graphics/Model/Line/LineGroup2d.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ final class LineGroup2d: BaseGraphicsObject, @unchecked Sendable {
label: "LineGroup2d")
var originOffset: simd_float4 = simd_float4(0, 0, 0, 0)
tileOriginBuffer = metalContext.device.makeBuffer(bytes: &originOffset, length: MemoryLayout<simd_float4>.stride, options: [])

}

private func setupStencilBufferDescriptor() {
Expand Down Expand Up @@ -115,10 +116,13 @@ final class LineGroup2d: BaseGraphicsObject, @unchecked Sendable {

encoder.setVertexBuffer(lineVerticesBuffer, offset: 0, index: 0)

let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context)
if let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) {
encoder.setVertexBytes(matrixPointer, length: 64, index: 1)
vpMatrixBuffer?.contents().copyMemory(from: matrixPointer, byteCount: 64)
}
encoder.setVertexBuffer(vpMatrixBuffer, offset: 0, index: 1)
Comment on lines +119 to +123
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add null check for vpMatrixBuffer before memory copy.

While the switch to buffer-based matrix handling is good, the code should verify that vpMatrixBuffer is not nil before performing the memory copy operation to prevent potential crashes.

 let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context)
-if let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) {
+guard let vpMatrixBuffer, let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) else {
+    return
+}
-    vpMatrixBuffer?.contents().copyMemory(from: matrixPointer, byteCount: 64)
+vpMatrixBuffer.contents().copyMemory(from: matrixPointer, byteCount: 64)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context)
if let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) {
encoder.setVertexBytes(matrixPointer, length: 64, index: 1)
vpMatrixBuffer?.contents().copyMemory(from: matrixPointer, byteCount: 64)
}
encoder.setVertexBuffer(vpMatrixBuffer, offset: 0, index: 1)
let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context)
guard let vpMatrixBuffer, let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) else {
return
}
vpMatrixBuffer.contents().copyMemory(from: matrixPointer, byteCount: 64)
encoder.setVertexBuffer(vpMatrixBuffer, offset: 0, index: 1)

Comment on lines +119 to +123
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add null check before copying memory to vpMatrixBuffer.

While the buffer pool pattern is a good improvement for thread safety, the code should verify that vpMatrixBuffer is not null before copying memory to avoid potential crashes.

Consider this safer implementation:

 let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context)
-if let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) {
+guard let vpMatrixBuffer = vpMatrixBuffer,
+      let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) else {
+    return
+}
-    vpMatrixBuffer?.contents().copyMemory(from: matrixPointer, byteCount: 64)
+vpMatrixBuffer.contents().copyMemory(from: matrixPointer, byteCount: 64)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context)
if let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) {
encoder.setVertexBytes(matrixPointer, length: 64, index: 1)
vpMatrixBuffer?.contents().copyMemory(from: matrixPointer, byteCount: 64)
}
encoder.setVertexBuffer(vpMatrixBuffer, offset: 0, index: 1)
let vpMatrixBuffer = vpMatrixBuffers.getNextBuffer(context)
guard let vpMatrixBuffer = vpMatrixBuffer,
let matrixPointer = UnsafeRawPointer(bitPattern: Int(vpMatrix)) else {
return
}
vpMatrixBuffer.contents().copyMemory(from: matrixPointer, byteCount: 64)
encoder.setVertexBuffer(vpMatrixBuffer, offset: 0, index: 1)


let originOffsetBuffer = originOffsetBuffers.getNextBuffer(context)
if let bufferPointer = originOffsetBuffer?.contents().assumingMemoryBound(to: simd_float4.self) {
bufferPointer.pointee.x = Float(originOffset.x - origin.x)
bufferPointer.pointee.y = Float(originOffset.y - origin.y)
Expand Down Expand Up @@ -158,6 +162,9 @@ extension LineGroup2d: MCLineGroup2dInterface {
bufferPointer.pointee.y = originOffset.yF
bufferPointer.pointee.z = originOffset.zF
}
else {
fatalError()
}
Comment on lines +165 to +167
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve error handling for buffer access failure.

The current implementation uses fatalError() without a message, which is not helpful for debugging. Additionally, crashing the app might be too aggressive when buffer access fails.

Consider these improvements:

-else {
-    fatalError()
-}
+else {
+    NSLog("Failed to access tileOriginBuffer contents in LineGroup2d.setLines")
+    // Reset state to prevent undefined behavior
+    self.lineVerticesBuffer = nil
+    self.lineIndicesBuffer = nil
+    self.indicesCount = 0
+    return
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else {
fatalError()
}
else {
NSLog("Failed to access tileOriginBuffer contents in LineGroup2d.setLines")
// Reset state to prevent undefined behavior
self.lineVerticesBuffer = nil
self.lineIndicesBuffer = nil
self.indicesCount = 0
return
}

Comment on lines +165 to +167
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace fatal error with graceful error handling.

Using fatalError() for buffer availability is too aggressive for a graphics application. Consider logging the error and handling the failure gracefully to prevent app crashes.

Consider this alternative approach:

-else {
-    fatalError()
-}
+else {
+    NSLog("Failed to access tileOriginBuffer in LineGroup2d")
+    return
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else {
fatalError()
}
else {
NSLog("Failed to access tileOriginBuffer in LineGroup2d")
return
}

self.lineVerticesBuffer.copyOrCreate(from: lines, device: device)
self.lineIndicesBuffer.copyOrCreate(from: indices, device: device)
if self.lineVerticesBuffer != nil, self.lineIndicesBuffer != nil {
Expand Down
70 changes: 70 additions & 0 deletions ios/graphics/Model/MultiBuffer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// MultiBuffer.swift
// MapCore
//
// Created by Nicolas Märki on 04.11.2024.
//

import Metal
import simd

public struct MultiBuffer<DataType> {
// Warning: Seems like suited to be generic
// But makeBuffer &reference does not like generic data
var buffers: [MTLBuffer]

fileprivate init(buffers: [MTLBuffer]) {
self.buffers = buffers
}

public mutating func getNextBuffer(_ context: RenderingContext)
-> MTLBuffer?
{
return buffers[context.currentBufferIndex]
}
}

extension MultiBuffer<simd_float1> {
public init(device: MTLDevice) {
var initialMutable = simd_float1(1.0)
let buffers: [MTLBuffer] = (0..<RenderingContext.bufferCount).compactMap
{ _ in
device
.makeBuffer(
bytes: &initialMutable,
length: MemoryLayout<simd_float1>.stride
)
}
self.init(buffers: buffers)
}
}

extension MultiBuffer<simd_float4> {
public init(device: MTLDevice) {
var initialMutable = simd_float4(0.0, 0.0, 0.0, 0.0)
let buffers: [MTLBuffer] = (0..<RenderingContext.bufferCount).compactMap
{ _ in
device
.makeBuffer(
bytes: &initialMutable,
length: MemoryLayout<simd_float4>.stride
)
}
self.init(buffers: buffers)
}
}

extension MultiBuffer<simd_float4x4> {
public init(device: MTLDevice) {
var initialMutable = simd_float4x4(1.0)
let buffers: [MTLBuffer] = (0..<RenderingContext.bufferCount).compactMap
{ _ in
device
.makeBuffer(
bytes: &initialMutable,
length: MemoryLayout<simd_float4x4>.stride
)
}
self.init(buffers: buffers)
}
}
Loading
Loading