你如何使用顶点缓冲区将动态且大小可变的数据传递给Metal?

编程语言 2026-07-09

背景:

我正在学习Metal,以及如何在SwiftUI中使用MTKView。

我在为一个面向对象的绘图程序打基础,该程序将允许用户使用Catmull-Rom样条来绘制羽化笔触。

我一开始通过 setVertexBytes 将我的顶点传给Metal,以便把顶点传给我的顶点着色器。

我已经写了辅助函数来绘制粗线、圆弧、开圆(甜甜圈)、正方形,以及其他一些我从示例代码中抽出来简化的其它类型。

当我尝试绘制一个大圆时,你会看到它像一个多边形而不是圆。我把分段数提高到360,但后来发现你只能通过 setVertexBytes 传输大约4k的数据。

我更新了代码,改为使用顶点缓冲区来传递顶点列表,而不是用 setVertexBytes 传递,这样我就可以传入更大的顶点数组。然而,在我看来,顶点缓冲区像是附着在Metal设备上的一个固定大小的持久性资源。我不确定如何通过顶点缓冲区向着色器传递可变长度的数据。(因为我在做绘图程序,顶点列表会随着用户的绘图而增长。)

问题:

当我使用 setVertexBytes 绘制时,我可以通过对我的形状绘制函数的调用来组成一个绘图,一切都按预期工作。下面的绘图代码,我稍后再详细解释:

        let limit: Float = 0.9
//        drawCircle(center: simd_float2(-0.75, -0.75), color: blue, radius: 30, lineThickness: 6)
//        drawCircle(center: simd_float2(-0.75, -0.75), color: black, radius: 20, lineThickness: 6)
//        drawCircle(center: simd_float2(-0.75, -0.75), color: blue, radius: 10, lineThickness: 6)
//        drawCircle(center: simd_float2(-0.75, -0.75), color: black, radius: 2, lineThickness: 4)

        drawCircle(center: simd_float2(0, 0), color: blue, radius: 280, steps: 120, lineThickness: 6)

//        drawSquare(center: simd_float2(0.7, 0.7), color: red, width: 58, orthoMatrix: orthoMatrix)

        drawThickLine(
            p1: simd_float2(-limit,limit * drawingInfo.wrappedValue.linePlacement),
            p2: simd_float2(limit, -limit * drawingInfo.wrappedValue.linePlacement),
            color: black,
            thickness: 20,
        )

会得到这张图片:

我的示例绘图,使用 setVertexBytes

(我注释掉了部分代码以简化失败场景。 
然而,当我改用 setVertexBytes 传递顶点数据时,事情就变得很糟。形状要么完全缺失,要么缺失部分,而且它们彼此之间会相互渗透。上面绘图代码的结果看起来像这样:


使用 setVertexBuffer 渲染的错误图像

如果我只绘制大圆或粗线,它会正确显示。如果我只尝试绘制一个红色正方形,只有它的一个三角形被绘制出来。如果我只绘制那个大圆和粗线,结果如上所示。如果我取消注释上面的其他绘图代码,也会得到上面的图像。红色正方形和同心圆完全缺失。

以下是我的着色器代码,这段代码不需要根据顶点数据的传递方式而改变:

//
//  Shaders.metal
//  DrawingApp
//
//  Created by Duncan Champney on 5/4/26.
//

#include <metal_stdlib>
#include "MetalStructs.h"
using namespace metal;


struct VertexOut {
    float4 position [[position]];
    float2 texCoord;
};


struct Uniforms {
    float4 color;
    bool drawWithTexture;
    float4x4 orthoMatrix;
};


vertex VertexOut vertex_main(const device float2* position [[buffer(0)]],
                             constant Uniforms& uniforms [[buffer(1)]],
                             uint vid [[vertex_id]]) {
    VertexOut out;
    float2 pos = position[vid];
    out.position = uniforms.orthoMatrix * float4(pos, 0, 1);    
    out.texCoord =  pos * 0.5 + 0.5; // basic mapping

    return out;
}

fragment float4 fragment_main(VertexOut in [[stage_in]],
                              texture2d<float> tex [[texture(0)]],
                              constant Uniforms& uniforms [[buffer(1)]]) {
    constexpr sampler s(address::clamp_to_edge, filter::linear);
    if (uniforms.drawWithTexture) {
        float2 coord = in.texCoord;
        return tex.sample(s, coord);
    } else {
        return uniforms.color;
    }
}

我当前在创建渲染器时只创建一次顶点缓冲区,使其足够容纳我最大的顶点数组。然后在每次调用 drawPrimitives 之前把顶点数据拷贝到其中。

我的渲染器的init方法看起来像这样:

    init(drawingInfo: Binding<DrawingInfo>) {
        self.drawingInfo = drawingInfo
        device = MTLCreateSystemDefaultDevice()
        guard let vertBuffer = device.makeBuffer(
            length:  maxVerticiesSize,
            options: .storageModeShared
        ) else {
            fatalError("Could not create vertex buffer")
        }
        vertexBuffer = vertBuffer

        super.init()


        //MARK: Oversampling
        if device.supportsTextureSampleCount(4) {
            sampleCount = 4
        } else if device.supportsTextureSampleCount(2) {
            sampleCount = 2
        }
        commandQueue = device.makeCommandQueue()
        makePipeline()
    }

而我的渲染器的 draw() 方法看起来像这样:

    func draw(in view: MTKView) {

        enum ArrowHeadDirection {
            case down
            case left
        }

        guard let drawable = view.currentDrawable else {
            print("[ScopeRenderer] currentDrawable is nil")
            return
        }
        guard let descriptor = view.currentRenderPassDescriptor else {
            print("[ScopeRenderer] currentRenderPassDescriptor is nil")
            return
        }
        guard let pipeline = pipeline else {
            print("[ScopeRenderer] pipeline is nil")
            return
        }
#if os(macOS)
        scale = Float(mtkView?.window?.screen?.backingScaleFactor ?? 1.0)
#else
        scale = Float(mtkView?.contentScaleFactor ?? 1)
#endif
        let orthoMatrix = matrix_identity_float4x4


        let commandBuffer = commandQueue.makeCommandBuffer()!

        let colorComponents = drawingInfo.wrappedValue.backgroundColor.components()
        descriptor.colorAttachments[0].clearColor = MTLClearColor(
            red: colorComponents[0],
            green: colorComponents[1],
            blue: colorComponents[2],
            alpha: colorComponents[3])

        descriptor.colorAttachments[0].loadAction =  MTLLoadAction.clear
        let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor)!
        encoder.setRenderPipelineState(pipeline)

        // Drawing code goes here:


        var uniforms = Uniforms(
            color: black,
            drawWithTetxure: false,
            orthoMatrix: orthoMatrix
        )


        let limit: Float = 0.9
//        drawCircle(center: simd_float2(-0.75, -0.75), color: blue, radius: 30, lineThickness: 6)
//        drawCircle(center: simd_float2(-0.75, -0.75), color: black, radius: 20, lineThickness: 6)
//        drawCircle(center: simd_float2(-0.75, -0.75), color: blue, radius: 10, lineThickness: 6)
//        drawCircle(center: simd_float2(-0.75, -0.75), color: black, radius: 2, lineThickness: 4)

        drawCircle(center: simd_float2(0, 0), color: blue, radius: 280, steps: 120, lineThickness: 6)

//        drawSquare(center: simd_float2(0.7, 0.7), color: red, width: 58, orthoMatrix: orthoMatrix)

        drawThickLine(
            p1: simd_float2(-limit,limit * drawingInfo.wrappedValue.linePlacement),
            p2: simd_float2(limit, -limit * drawingInfo.wrappedValue.linePlacement),
            color: black,
            thickness: 20,
        )

        encoder.endEncoding()
        commandBuffer.present(drawable)
        commandBuffer.commit()


        // MARK: - nested drawing functions
        func drawCircle(
            center: simd_float2,
            color: SIMD4<Float>,
            radius: Float,
            steps: Int = 24,
            lineThickness: Float,
        ) {
            drawArc(
                center: center,
                color: color,
                radius: radius,
                steps: steps,
                lineThickness: lineThickness)
        }

        func drawArc(
            center: simd_float2,
            color: SIMD4<Float>,
            radius: Float,
            startAngle: Float = 0,
            endAngle: Float = 360.0,
            steps: Int = 24,
            lineThickness: Float,
            asDiamond: Bool = false) {

                let aspect = drawingInfo.wrappedValue.imageAspectRatio
                let landscape = aspect > 1
                let adjustment: simd_float2 = landscape ?  simd_float2(1, aspect) : simd_float2(1/aspect, 1)

                let widthPerPixel: Float = scale / Float(max(drawableSize.width, drawableSize.height))

                let startAngleRadians = startAngle.degreesToRadians
                let arcDelta = endAngle.degreesToRadians - startAngleRadians
                let notFullCircle = startAngle != 0.0 || endAngle != 360.0

                var verticies = [simd_float2]()
                verticies.reserveCapacity(steps * 2)
                let radius = 2 * radius + lineThickness / 8

                let loopSteps = notFullCircle ? steps - 1 : steps
                for step in 0 ..< loopSteps {
                    let angle: Float = startAngleRadians + Float(step) / Float(steps) * arcDelta
                    let angle2 = startAngleRadians + Float((step+1) % steps) / Float(loopSteps) * arcDelta

                    var deltaX = cos(angle) * widthPerPixel * (radius - lineThickness) * adjustment.x
                    var deltaY = sin(angle) * widthPerPixel * (radius - lineThickness) * adjustment.y

                    let p1Inside = simd_float2(x: center.x + deltaX, y: center.y + deltaY)

                    deltaX = cos(angle) * widthPerPixel  * (radius + lineThickness) * adjustment.x
                    deltaY = sin(angle) * widthPerPixel * (radius + lineThickness) * adjustment.y

                    let p1Outside = simd_float2(x: center.x + deltaX, y: center.y + deltaY)

                    deltaX = cos(angle2) * widthPerPixel * (radius - lineThickness) * adjustment.x
                    deltaY = sin(angle2) * widthPerPixel * (radius - lineThickness) * adjustment.y
                    let p2Inside = simd_float2(x: center.x + deltaX, y: center.y + deltaY)

                    deltaX = cos(angle2) * widthPerPixel  * (radius + lineThickness) * adjustment.x
                    deltaY = sin(angle2) * widthPerPixel * (radius + lineThickness) * adjustment.y
                    let p2Outside = simd_float2(x: center.x + deltaX, y: center.y + deltaY)

                    verticies += [p1Inside, p1Outside, p2Inside, p2Outside]
                }

                uniforms = Uniforms(
                    color: color,
                    drawWithTetxure: false,
                    orthoMatrix: orthoMatrix
                )

                let verticiesSize = MemoryLayout<simd_float2>.stride * verticies.count
                if maxVerticiesSize < verticiesSize {
                    maxVerticiesSize = verticiesSize
                    print("maxVerticiesSize = \(maxVerticiesSize). verticies.count = \(verticies.count)")
                }
                if useVertexBuffers {
                    vertexBuffer.contents().copyMemory(from: verticies, byteCount: verticiesSize)
                    encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
                } else {
                    encoder.setVertexBytes(verticies, length: verticiesSize, index: 0)
                }

                encoder.setVertexBytes(&uniforms, length: MemoryLayout<Uniforms>.stride, index: 1)
                encoder.setFragmentBytes(&uniforms, length: MemoryLayout<Uniforms>.stride, index: 1)
                encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: verticies.count)
            }

        func drawThickLine(
            p1: simd_float2,
            p2: simd_float2,
            color: SIMD4<Float>,
            thickness: Float
        ) {

                let aspect = drawingInfo.wrappedValue.imageAspectRatio
                let landscape = aspect > 1
                let adjustment: simd_float2 = landscape ?  simd_float2(1, 1/aspect) : simd_float2(1*aspect, 1)
                let p1Tweaked = p1  * adjustment
                let p2Tweaked = p2  * adjustment

                let thickness = thickness * scale / Float(max(drawableSize.width, drawableSize.height))
                let dir = normalize(p2Tweaked - p1Tweaked)
                let normal = simd_float2(-dir.y, dir.x) * thickness


                let v0 = (p1Tweaked + normal) / adjustment
                let v1 = (p1Tweaked - normal) / adjustment
                let v2 = (p2Tweaked + normal) / adjustment
                let v3 = (p2Tweaked - normal) / adjustment
                var vertices = [v0, v1, v2, v3]

            if useVertexBuffers {
                let verticiesSize = MemoryLayout<simd_float2>.stride * 4
                vertexBuffer.contents().copyMemory(from: vertices, byteCount: verticiesSize)
                encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
            } else {
                encoder.setVertexBytes(vertices, length: MemoryLayout<simd_float2>.stride * 4, index: 0)
            }

                 uniforms = Uniforms(
                    color: color,
                    drawWithTetxure: false,
                    orthoMatrix: orthoMatrix
                )
                encoder.setVertexBytes(&uniforms, length: MemoryLayout<Uniforms>.stride, index: 1)
                encoder.setFragmentBytes(&uniforms, length: MemoryLayout<Uniforms>.stride, index: 1)
                encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
            }
    }

大量 的代码,我知道。对不起。我不确定还能省略哪些部分,同时仍然能完整描述问题。

问题:

为什么我的代码不起作用?如果我只用 setVertexBuffer 绘制一个单独的圆或单独的粗线,它确实可以工作, 你能否将数据通过组合使用 setVertexBuffer 和setVertexBytes传给着色器?以及如何处理顶点数据会随时间变化并增长的图像的渲染?

你可以在Github上看到整个测试项目,地址是 https://github.com/DuncanMC/DrawingApp.git

解决方案

所以我问了ChatGPT,看看发生了什么,它识别出问题并给了我一个解决方案。

问题在于我在渲染一个帧时多次写入我的顶点缓冲区,然后一次性提交绘制。

Metal是异步运行的,在我用下一条绘制命令修改缓冲区之后,它会从我的顶点缓冲区获取内容。

ChatGPT给出的解决方案是将代码改为使用顶点缓冲区作循环缓冲区,在写入时改为在其中的某个偏移量处写入,而不是总是在起始位置,并在setVertexBuffer调用中传递该偏移量。

这需要一些额外的逻辑来强制让循环缓冲区中的偏移量达到256字节的页对齐,这是Metal要求在缓冲区使用偏移量时的对齐要求。

我会把更新后的代码推送到我的仓库以供参考。

以下是更新后的绘制方法:

    func draw(in view: MTKView) {

        enum ArrowHeadDirection {
            case down
            case left
        }

        guard let drawable = view.currentDrawable else {
            print("[ScopeRenderer] currentDrawable is nil")
            return
        }
        guard let descriptor = view.currentRenderPassDescriptor else {
            print("[ScopeRenderer] currentRenderPassDescriptor is nil")
            return
        }
        guard let pipeline = pipeline else {
            print("[ScopeRenderer] pipeline is nil")
            return
        }
#if os(macOS)
        scale = Float(mtkView?.window?.screen?.backingScaleFactor ?? 1.0)
#else
        scale = Float(mtkView?.contentScaleFactor ?? 1)
#endif
        let orthoMatrix = matrix_identity_float4x4

        // Reset ring write offset at the start of a frame region
        // Simple partitioning by frame without explicit GPU sync. For robust sync, use in-flight semaphores.
        if ringWriteOffset >= ringBufferSize - ringBufferAlignment {
            ringWriteOffset = 0
        }

        let commandBuffer = commandQueue.makeCommandBuffer()!

        let colorComponents = drawingInfo.wrappedValue.backgroundColor.components()
        descriptor.colorAttachments[0].clearColor = MTLClearColor(
            red: colorComponents[0],
            green: colorComponents[1],
            blue: colorComponents[2],
            alpha: colorComponents[3])

        descriptor.colorAttachments[0].loadAction =  MTLLoadAction.clear
        let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor)!
        encoder.setRenderPipelineState(pipeline)

        // Drawing code goes here:

        var uniforms = Uniforms(
            color: black,
            drawWithTetxure: false,
            orthoMatrix: orthoMatrix
        )


        // MARK: Test drawing code.
        let limit: Float = 0.9

        drawCircle(center: simd_float2(0, 0), color: blue, radius: 280, steps: 120, lineThickness: 6)

        drawCircle(center: simd_float2(-0.75, -0.75), color: blue, radius: 30, lineThickness: 6)
        drawCircle(center: simd_float2(-0.75, -0.75), color: black, radius: 20, lineThickness: 6)
        drawCircle(center: simd_float2(-0.75, -0.75), color: blue, radius: 10, lineThickness: 6)
        drawCircle(center: simd_float2(-0.75, -0.75), color: black, radius: 2, lineThickness: 4)


        drawThickLine(
            p1: simd_float2(-limit,limit * drawingInfo.wrappedValue.linePlacement),
            p2: simd_float2(limit, -limit * drawingInfo.wrappedValue.linePlacement),
            color: black,
            thickness: 20,
        )

        drawSquare(center: simd_float2(0.7, 0.7), color: red, width: 58, orthoMatrix: orthoMatrix)

        encoder.endEncoding()
        commandBuffer.present(drawable)
        commandBuffer.commit()


        // MARK: - nested drawing functions
        func drawCircle(
            center: simd_float2,
            color: SIMD4<Float>,
            radius: Float,
            steps: Int = 24,
            lineThickness: Float,
        ) {
            drawArc(
                center: center,
                color: color,
                radius: radius,
                steps: steps,
                lineThickness: lineThickness)
        }

        func drawArc(
            center: simd_float2,
            color: SIMD4<Float>,
            radius: Float,
            startAngle: Float = 0,
            endAngle: Float = 360.0,
            steps: Int = 24,
            lineThickness: Float,
            asDiamond: Bool = false) {

                let aspect = drawingInfo.wrappedValue.imageAspectRatio
                let landscape = aspect > 1
                let adjustment: simd_float2 = landscape ?  simd_float2(1, aspect) : simd_float2(1/aspect, 1)

                let widthPerPixel: Float = scale / Float(max(drawableSize.width, drawableSize.height))

                let startAngleRadians = startAngle.degreesToRadians
                let arcDelta = endAngle.degreesToRadians - startAngleRadians
                let notFullCircle = startAngle != 0.0 || endAngle != 360.0

                var verticies = [simd_float2]()
                verticies.reserveCapacity(steps * 2)
                let radius = 2 * radius + lineThickness / 8

                let loopSteps = notFullCircle ? steps - 1 : steps
                for step in 0 ..< loopSteps {
                    let angle: Float = startAngleRadians + Float(step) / Float(steps) * arcDelta
                    let angle2 = startAngleRadians + Float((step+1) % steps) / Float(loopSteps) * arcDelta

                    var deltaX = cos(angle) * widthPerPixel * (radius - lineThickness) * adjustment.x
                    var deltaY = sin(angle) * widthPerPixel * (radius - lineThickness) * adjustment.y

                    let p1Inside = simd_float2(x: center.x + deltaX, y: center.y + deltaY)

                    deltaX = cos(angle) * widthPerPixel  * (radius + lineThickness) * adjustment.x
                    deltaY = sin(angle) * widthPerPixel * (radius + lineThickness) * adjustment.y

                    let p1Outside = simd_float2(x: center.x + deltaX, y: center.y + deltaY)

                    deltaX = cos(angle2) * widthPerPixel * (radius - lineThickness) * adjustment.x
                    deltaY = sin(angle2) * widthPerPixel * (radius - lineThickness) * adjustment.y
                    let p2Inside = simd_float2(x: center.x + deltaX, y: center.y + deltaY)

                    deltaX = cos(angle2) * widthPerPixel  * (radius + lineThickness) * adjustment.x
                    deltaY = sin(angle2) * widthPerPixel * (radius + lineThickness) * adjustment.y
                    let p2Outside = simd_float2(x: center.x + deltaX, y: center.y + deltaY)

                    verticies += [p1Inside, p1Outside, p2Inside, p2Outside]
                }

                uniforms = Uniforms(
                    color: color,
                    drawWithTetxure: false,
                    orthoMatrix: orthoMatrix
                )

                let verticiesSize = MemoryLayout<simd_float2>.stride * verticies.count
                if maxVerticiesSize < verticiesSize {
                    maxVerticiesSize = verticiesSize
                    print("maxVerticiesSize = \(maxVerticiesSize). verticies.count = \(verticies.count)")
                }
                if useVertexBuffers {
                    let offset = allocateVerticesInRing(byteCount: verticiesSize)
                    let dst = vertexBuffer.contents().advanced(by: offset)
                    dst.copyMemory(from: verticies, byteCount: verticiesSize)
                    encoder.setVertexBuffer(vertexBuffer, offset: offset, index: 0)
                } else {
                    encoder.setVertexBytes(verticies, length: verticiesSize, index: 0)
                }

                encoder.setVertexBytes(&uniforms, length: MemoryLayout<Uniforms>.stride, index: 1)
                encoder.setFragmentBytes(&uniforms, length: MemoryLayout<Uniforms>.stride, index: 1)
                encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: verticies.count)
            }

        func drawSquare(
            center: simd_float2,
            color: SIMD4<Float>,
            width: Float,
            orthoMatrix: float4x4,
            asDiamond: Bool = false) {

                let aspect = drawingInfo.wrappedValue.imageAspectRatio
                let landscape = aspect > 1
                let adjustment: simd_float2 = landscape ?  simd_float2(1, 1/aspect) : simd_float2(1*aspect, 1)

                let width = width
                let center: simd_float2 = simd_float2(x: center.x, y: center.y)
                let widthPerPixel: Float = scale / Float(max(drawableSize.width, drawableSize.height))
                let yOffset = (widthPerPixel * width) / adjustment.y
                let xOffset = widthPerPixel * width  / adjustment.x
                let p1: simd_float2
                let p2: simd_float2
                let p3: simd_float2
                let p4: simd_float2
                if !asDiamond {
                    p1 = simd_float2(x: center.x - xOffset, y: center.y + yOffset)
                    p2 = simd_float2(x: center.x + xOffset, y: center.y + yOffset)
                    p3 = simd_float2(x: center.x + xOffset, y: center.y - yOffset)
                    p4 = simd_float2(x: center.x - xOffset, y: center.y - yOffset)

                } else {
                    p1 = simd_float2(x: center.x, y: center.y + yOffset)
                    p2 = simd_float2(x: center.x + xOffset, y: center.y)
                    p3 = simd_float2(x: center.x, y: center.y - yOffset)
                    p4 = simd_float2(x: center.x - xOffset, y: center.y)
                }

                var verts: [simd_float2] = [p1, p2, p3]

                var uniforms: Uniforms = Uniforms(
                    color: color,
                    drawWithTetxure: false,
                    orthoMatrix: orthoMatrix
                )

                var verticiesSize = MemoryLayout<simd_float2>.stride * verts.count

                if useVertexBuffers {
                    let offset = allocateVerticesInRing(byteCount: verticiesSize)
                    let dst = vertexBuffer.contents().advanced(by: offset)
                    dst.copyMemory(from: verts, byteCount: verticiesSize)
                    encoder.setVertexBuffer(vertexBuffer, offset: offset, index: 0)
                } else {
                    encoder.setVertexBytes(verts, length: MemoryLayout<simd_float2>.stride * 3, index: 0)
                }

                encoder.setVertexBytes(&uniforms, length: MemoryLayout<Uniforms>.stride, index: 1)
                encoder.setFragmentBytes(&uniforms, length: MemoryLayout<Uniforms>.stride, index: 1)
                encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)

                verts = [p1, p3, p4]
                verticiesSize = MemoryLayout<simd_float2>.stride * verts.count

                if useVertexBuffers {
                    let offset2 = allocateVerticesInRing(byteCount: verticiesSize)
                    let dst2 = vertexBuffer.contents().advanced(by: offset2)
                    dst2.copyMemory(from: verts, byteCount: verticiesSize)
                    encoder.setVertexBuffer(vertexBuffer, offset: offset2, index: 0)
                } else {
                    encoder.setVertexBytes(verts, length: MemoryLayout<simd_float2>.stride * 3, index: 0)
                }
                encoder.setVertexBytes(&uniforms, length: MemoryLayout<Uniforms>.stride, index: 1)
                encoder.setFragmentBytes(&uniforms, length: MemoryLayout<Uniforms>.stride, index: 1)
                encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)

            }

        func drawThickLine(
            p1: simd_float2,
            p2: simd_float2,
            color: SIMD4<Float>,
            thickness: Float
        ) {

                let aspect = drawingInfo.wrappedValue.imageAspectRatio
                let landscape = aspect > 1
                let adjustment: simd_float2 = landscape ?  simd_float2(1, 1/aspect) : simd_float2(1*aspect, 1)
                let p1Tweaked = p1  * adjustment
                let p2Tweaked = p2  * adjustment

                let thickness = thickness * scale / Float(max(drawableSize.width, drawableSize.height))
                let dir = normalize(p2Tweaked - p1Tweaked)
                let normal = simd_float2(-dir.y, dir.x) * thickness


                let v0 = (p1Tweaked + normal) / adjustment
                let v1 = (p1Tweaked - normal) / adjustment
                let v2 = (p2Tweaked + normal) / adjustment
                let v3 = (p2Tweaked - normal) / adjustment
                var vertices = [v0, v1, v2, v3]

            if useVertexBuffers {
                let verticiesSize = MemoryLayout<simd_float2>.stride * 4
                let offset = allocateVerticesInRing(byteCount: verticiesSize)
                let dst = vertexBuffer.contents().advanced(by: offset)
                dst.copyMemory(from: vertices, byteCount: verticiesSize)
                encoder.setVertexBuffer(vertexBuffer, offset: offset, index: 0)
            } else {
                encoder.setVertexBytes(vertices, length: MemoryLayout<simd_float2>.stride * 4, index: 0)
            }

                 uniforms = Uniforms(
                    color: color,
                    drawWithTetxure: false,
                    orthoMatrix: orthoMatrix
                )
                encoder.setVertexBytes(&uniforms, length: MemoryLayout<Uniforms>.stride, index: 1)
                encoder.setFragmentBytes(&uniforms, length: MemoryLayout<Uniforms>.stride, index: 1)
                encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
            }

        // MARK: Helper function for managing offsets into the ring buffer

        @inline(__always)
        func allocateVerticesInRing(byteCount: Int) -> Int {
            let alignedSize = ((byteCount + ringBufferAlignment - 1) / ringBufferAlignment) * ringBufferAlignment
            if ringWriteOffset + alignedSize > ringBufferSize {
                // Wrap to start if not enough space
                ringWriteOffset = 0
            }
            let offset = ringWriteOffset
            ringWriteOffset += alignedSize
            return offset
        }
    }
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章