Chapter 4 (Third Edition) shader validation error

Hi @mhorga and @Caroline :slight_smile:

I’m following along the Third Edition of Metal by Tutorials and have run into an issue when Shader Validation is enabled. This happens when we are constructing the vertex shader using a buffer of bytes.

Even after making the change to use a packed_float3 to have the shader’s data type use the same amount of bytes for a vertex as our Swift Float array would, the following is shown:

**Invalid device load executing vertex function "vertex_main" encoder: "0", draw: 0, at offset 72**

buffer: <unnamed>, length:72, resident:Read Write

This happens on the latest version of macOS Monterrey on an M1 MacBook Pro, but only for the Vertex (macOS) scheme that was included with the starter project.

The same code, running on the iOS Simulator, does not result in the same shader validation message.

I tried changing the vertex shader to use just a pointer to an array of float, and then traversing through things one pixel at a time to construct a float4 for the vertex, but without any successful results.

I also tried using a simd_float1 in Swift, for the vertices array, and then a simd_float3 in the vertex shader alas with the same results.

Note that the quad renders ok, but that validation error is throwing me off :slight_smile:

Would you be kind enough to provide some insight into why this is happening? I tend to be very curious and like to understand why and how things work (or don’t, as in this case).

Many thanks in advance!

An update on this from assistance I received from other graphics programmer enthusiasts on Discord:

Essentially it appears to come down to the discrepancy between using a [Float] array containing all 18 vertices and then using them as a packed_float3 pointer in the shader.

By simply declaring a struct to match packed_float3 on the Swift side we can fix the problem:

`
struct PackedFloat3 {
var x: Float
var y: Float
var z: Float
}

struct Quad {
let vertexBuffer: MTLBuffer

var vertices: [PackedFloat3] = [
    PackedFloat3(x: -1, y:  1, z: 0),
    PackedFloat3(x:  1, y: -1, z: 0),
    PackedFloat3(x: -1, y: -1, z: 0),

    PackedFloat3(x: -1, y:  1, z: 0),
    PackedFloat3(x:  1, y:  1, z: 0),
    PackedFloat3(x:  1, y: -1, z: 0)
]

init(device: MTLDevice, scale: Float = 1) {
    vertices = vertices.map {
        PackedFloat3(x: $0.x * scale,
                     y: $0.y * scale,
                     z: $0.z * scale)
    }

    guard let vertexBuffer = device.makeBuffer(bytes: &vertices,
                                               length: MemoryLayout<PackedFloat3>.stride * vertices.count,
                                               options: [])
    else {
        fatalError("Unable to create quad vertex buffer.")
    }

    self.vertexBuffer = vertexBuffer
}

}
`

Alternatively, the draw call could pass in the correct amount of triangle primitives to draw:

renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: quad.vertices.count / 3)

We’re essentially making the draw call for a triangle primitive type, but the count of primitives we’re passing it is 18 (quad.vertices.count). Metal calls the vertex shader 18 times thinking it has to draw 18 triangles, when we should be calling the vertex shader 6 times, and using the packed_float3 pointer to index into 3 vertices at a time.

Either that, or using the PackedFloat3 struct to correctly group together 3 Float to make up a vertex, and then the draw call will correctly be passing in 6 primitives for 6 vertex shader calls.

this has been an interesting, albeit hair-pulling exercise as I learn Metal :slight_smile:

Hopefully this can be addressed in the errata post (for now), and properly in a minor revision to the book. :smiley:

3 Likes

Thank you so much for pointing this out @ifeli, and for providing the solution when you want to use packed.

Another alternative is to use a built in type and not have packed in the shader:

struct Quad {
  var vertices: [SIMD3<Float>] = [
    [-1,  1,  0],    // triangle 1
    [ 1, -1,  0],
    [-1, -1,  0],
    [-1,  1,  0],    // triangle 2
    [ 1,  1,  0],
    [ 1, -1,  0]
  ]
  let vertexBuffer: MTLBuffer

  init(device: MTLDevice, scale: Float = 1) {
    vertices = vertices.map {
      $0 * scale
    }
    guard let vertexBuffer = device.makeBuffer(
      bytes: &vertices,
      length: MemoryLayout<SIMD3<Float>>.stride * vertices.count,
      options: []) else {
      fatalError("Unable to create quad vertex buffer")
    }
    self.vertexBuffer = vertexBuffer
  }
}
vertex float4 vertex_main(
  constant float3 *vertices [[buffer(0)]],
  uint vertexID [[vertex_id]])
{
  float4 position = float4(vertices[vertexID], 1);
  return position;
}