金属 - 我无法使用单个缓冲区绘制超过2048个点

4
这是我在这里的第一篇文章,如有任何不慎违反协议或礼节之处,请谅解,谢谢!
基本问题:MetalKt似乎没有绘制我尝试显示的所有线条。
详情:我已经学习了约3周的Metal框架(主要通过OS X上的MetalKit)。到目前为止,我已经成功地组合了一个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;
}

这里是一些矩阵工具(主要用于将来使用,目前返回单位矩阵)- 改编自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
     }
}

这是视图:
// 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)")
            }
        }
    }
}

这是创建金属缓冲区的方法:

// 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 : [])

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

屏幕截图-裁剪绘图

如果我将保存的顶点分散到多个缓冲区中,每个缓冲区包含2048个顶点(代码未显示),我就可以得到完整的波浪(较浅的线显示额外的缓冲区):

屏幕截图-完整波浪

我可能在做一些愚蠢或显而易见的事情。我非常感谢比我聪明的人能够给予一些指导。谢谢!


出于好奇,你使用的是什么硬件配置和OS X版本?这可能发生在nVidia GPU上吗? - warrenm
1
@warrenm:到目前为止,这只在一台2013年底的iMac 27英寸、El Capitan 10.11.5上运行。是的,根据“关于本机”:NVIDIA GeForce GTX 775M 2048 MB.. 视频卡可能是答案的一部分,这很有道理,我从来没有想过 - 谢谢!我正在慢慢地推进应用程序,并且一路上学到了很多东西。 - paulcrescendo
1个回答

7
我认为我今天也在解决这个问题。在寻找答案时,我发现了这篇文章:“newBufferWithBytes() 有大小限制吗?”。
其中,答案提到了苹果文档:Metal 特性表
文档中提到,“着色器或计算函数变量在常量地址空间的最大内存分配”在 iOS 设备上是无限的,但在 OS X 上只有 64KB。
因此,在顶点着色器中,我相信“constant Vertex *vertices”应该在 OS X 上改为“device Vertex *vertices”,以使用设备地址空间。

这对我来说是正确的。理想情况下,被vertex_id索引的缓冲区应该位于device地址空间中,因为在着色器调用之间很少访问缓冲区中的相同位置。 - warrenm
@cjm99999:嗨,抱歉回复晚了,这周有陪审团职责(好时光)。无论如何,这似乎是正确的。以我对所有这些的外行理解为基础,如果我的每个顶点使用32字节(strideof),那么32 * 2048 / 1024 = 64KB,这是Metal功能集表所指示的限制。非常感谢您指引我朝着这个方向。 - paulcrescendo
@cjm99999:我已经为你的答案点赞了,但显然在我获得更高的StackOverflow声望之前它不会显示出来。但我已经将其标记为答案。 - paulcrescendo
@warrenm:我将会花一段时间仔细阅读你的答案!我已经多次阅读了有关不同地址空间的内容 - 但并没有真正理解它会如何影响我。所以感谢你帮我理清这个概念。我从未想过macOS会有iOS没有的限制......或者这实际上不是一个限制,而只是一种处理数据的不同方式。无论如何,现在是回到文档的时候了。 - paulcrescendo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接