Metal - 我不能使用单个缓冲区绘制超过 2048 个点

pau*_*ndo 3 macos metal

这是我在这里的第一篇文章,所以请原谅任何无意中违反协议或礼节的行为,谢谢!

基本问题:MetalKt 似乎没有画出我想要显示的所有线条。

详细信息:我正在学习 Metal Frameworks(主要是通过 OS X 上的 MetalKit)的第 3 周。到目前为止,我已经设法将一个 MetalView 组合在一起,显示来自磁盘文件的音频波,并在播放音频时在屏幕上移动一个滑动条。

音频波只是一组代表声级的点,每对点由一条线连接,最终看起来像在 GarageBand 或 Logic 等中看到的东西。

我遇到的问题是 Metal 没有绘制我认为我要求它绘制的所有点。通过反复试验,我发现它在绘制 2048 个点(计算机编号!)后停止。我可以验证我是否正确地输入了数据——也就是说,我收集了足够的点来完全绘制波浪,使用正确的坐标绘制整个波浪,但介于创建缓冲区和要求 Metal 绘制它之间,它被剪裁为 2048。其余的音频只是不显示。

所以我想知道我的创作或 Metal 本身是否有一些缓冲区数据限制,这会导致这种情况。我已经通过使用多个缓冲区解决了这个问题,但这感觉就像是创可贴一样,而且我不明白原因让我很烦恼。

设置相当准系统,没有纹理或缩放(我知道......就像我说的我刚刚开始)

这是我的课程:

// Shaders.metal
#include <metal_stdlib>
using namespace metal;

struct Vertex {
    float4 position [[position]];
    float4 color;
};

struct Uniforms {
    float4x4 modelMatrix;
};

vertex Vertex vertex_func(constant  Vertex *vertices    [[buffer(0)]],
                          constant  Uniforms &uniforms  [[buffer(1)]],
                          uint      vid                 [[vertex_id]]) {
    float4x4 matrix = uniforms.modelMatrix;
    Vertex in       = vertices[vid];
    Vertex out;
    out.position    = matrix * float4(in.position);
    out.color       = in.color;
    return out;
}

fragment float4 fragment_func(Vertex vert [[stage_in]]) {
    return vert.color;
}
Run Code Online (Sandbox Code Playgroud)

这是一些矩阵实用程序(主要是为了将来使用,目前正在返回统一) - 改编自 Marius Horga 的在线 Metal 教程:

//  MathUtils.swift
//  chapter07
//
//  Created by Marius on 3/1/16.
//  Copyright © 2016 Marius Horga. All rights reserved.

// adapted for personal use

import simd

struct Vertex {
    var position    : vector_float4
    var color       : vector_float4
    init(pos: vector_float4, col: vector_float4) {
        position    = pos
        color       = col
    }
}

struct Matrix {
    var m: [Float]

    init() {
        m = [1, 0, 0, 0,
             0, 1, 0, 0,
             0, 0, 1, 0,
             0, 0, 0, 1
        ]
    }

    func modelMatrix(var matrix: Matrix) -> Matrix {
        return matrix   // for now, just unity
     }
}
Run Code Online (Sandbox Code Playgroud)

这是视图:

// MetalView
import MetalKit

public class TesterMetalView: MTKView {

    var vert_audio_buffer   : MTLBuffer!
    var uniform_buffer      : MTLBuffer!

    var rps : MTLRenderPipelineState! = nil

    required public init(coder: NSCoder) {
        super.init(coder: coder)

        createBuffers()
        registerShaders()
    }
    override public init(frame frameRect: CGRect, device: MTLDevice?) {
        super.init(frame: frameRect, device: device)
        createBuffers()
        registerShaders()
    }

    override public func drawRect(dirtyRect: NSRect) {
        super.drawRect(dirtyRect)
        if let rpd = currentRenderPassDescriptor,
            drawable = currentDrawable {

            rpd.colorAttachments[0].clearColor = MTLClearColorMake(0.5, 0.5, 0.5, 1.0)
            let command_buffer = device!.newCommandQueue().commandBuffer()
            let command_encoder = command_buffer.renderCommandEncoderWithDescriptor(rpd)
            command_encoder.setRenderPipelineState(rps)

            command_encoder.setVertexBuffer(vert_audio_buffer,  offset: 0, atIndex: 0)
            command_encoder.setVertexBuffer(uniform_buffer,     offset: 0, atIndex: 1)

            let numVerts = (vert_audio_buffer.length / sizeof(Vertex))
            command_encoder.drawPrimitives(.Line, vertexStart: 0, vertexCount: numVerts)

            command_encoder.endEncoding()
            command_buffer.presentDrawable(drawable)
            command_buffer.commit()
        }
    }

    func createBuffers() {
        if device == nil { self.device = MTLCreateSystemDefaultDevice() }

        // rotation + scaling
        uniform_buffer      = device!.newBufferWithLength(sizeof(Float) * 16, options: [])
        let bufferPointer   = uniform_buffer.contents()
        memcpy(bufferPointer, Matrix().modelMatrix(Matrix()).m, sizeof(Float) * 16)
    }

    func registerShaders() {
        if device == nil { self.device = MTLCreateSystemDefaultDevice() }
        if let library  = device!.newDefaultLibrary() {
            let vertex_func = library.newFunctionWithName("vertex_func")
            let frag_func   = library.newFunctionWithName("fragment_func")

            let rpld = MTLRenderPipelineDescriptor()
            rpld.vertexFunction     = vertex_func
            rpld.fragmentFunction   = frag_func
            rpld.colorAttachments[0].pixelFormat = .BGRA8Unorm

            do {
                try rps = device!.newRenderPipelineStateWithDescriptor(rpld)
            } catch {
                Swift.print("***ERROR: newRenderPipelineStateWithDescriptor failed ...\r\t\(error)")
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是金属缓冲区的创建方式:

// within Metal View Controller
var verts_audio = [Vertex]()

// ... create Vertex's from audio data
// I can confirm verts_audio contains valid data
// however, Metal draws only the first 2048 of them

let bufferLength = sizeof(Vertex) * verts_audio.count

// metalV() gets a typed reference to the view
metalV().vert_audio_buffer = metalV().device!
    .newBufferWithBytes(verts_audio,
                        length  : bufferLength,
                        options : [])
Run Code Online (Sandbox Code Playgroud)

因此,如果我尝试绘制一个包含 2838 个点的顶点的波浪,并使用上面的代码将它们全部放在一个缓冲区中,我会得到:

屏幕截图 - 剪切图

如果我将保存顶点分布在多个缓冲区中,每个缓冲区包含 2048 个顶点(代码未显示),我会得到全波(较浅的线条显示额外的缓冲区):

屏幕截图 - 全波

我可能正在做一些愚蠢或显而易见的事情。我肯定会感谢比我更聪明的人对此有所了解。谢谢!

小智 5

我想我今天也一直在与这个斗争。在寻找答案时,我发现“ newBufferWithBytes() 有大小限制吗? ”。

在其中,答案提到了 Apple 文档:Metal Feature Set Tables

在那里它说“常量地址空间中着色器或计算函数变量的最大内存分配”对于iOS设备是无限的,但对于OS X只有64KB。

所以在顶点着色器中,我相信,在 OS X 上,“constant Vertex *vertices”应该是“device Vertex *vertices”以使用设备地址空间。