This is my first post here, so please forgive any unintentional breach of protocol or etiquette, thanks!
BASIC PROBLEM: MetalKt doesn't seem to be drawing all the lines I'm trying to display.
DETAILS: I'm into about week 3 of learning the Metal Frameworks (mostly thru MetalKit on OS X). So far I've managed to put together a MetalView displaying an audio wave from a file on disk, with a swiper bar that travels across the screen as the audio is playing back.
The audio wave is just a set of points representing sound levels, each pair of which is connected by a line, which eventually looks like something one would see in GarageBand or Logic etc.
The problem I am having is Metal doesn't draw all the points I believe I am asking it to. Thru trial and error I discovered it stops after drawing 2048 points (computer number!). I can verify that I'm feeding the data in correctly - that is, I'm gathering enough points to draw the wave fully, with the correct coordinates that draw the entire wave, but somewhere between creating the buffer and asking Metal to draw it, it gets clipped to 2048. The rest of the audio just doesn’t show up.
So I'm wondering if there is some buffer data limit of my creation, or in Metal itself, that would cause this. I've gotten around it by using multiple buffers, but this feels like a band-aid fix, and it troubles me that I don't understand the cause.
The setup is fairly barebones, no textures or scaling (that I'm aware of ... like I said I'm just starting)
Here are my classes:
// 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;
}
Here's some matrix utils (mostly for future use, currently returning unity) - adapted from online Metal tutorial by Marius Horga:
// 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
}
}
Here's the view:
// 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)")
}
}
}
}
And here's how the metal buffers are created:
// 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 : [])
So, if I try to draw a wave with vertices containing 2838 points, and put them all in a single buffer using the above code, I get this:
If I spread the save vertices amongst multiple buffers, each containing 2048 Vertex's (code not shown), I get the full wave (lighter lines show extra buffers):
I'm probably doing something bone-headed or obvious. I'd sure appreciate someone smarter than me shedding some light on this. Thanks!