Search code examples
swiftopenglnsopenglview

Swift, OpenGL and glVertexAttribPointer


I'm translating the Objective-C code in this repo into Swift, in order to learn some of the basics of OpenGL. I'm totally new to it. I've got a working project that compiles and produces a working NSOpenGLView with a floating rectangle, but the colors are wrong. I've narrowed the problem down to my use of the glVertexAttribPointer functions that point to the vertex and color data.

Here is how I have my vertex and color data stored:

struct Vertex {
    var position: (x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat)
    var color: (r: GLfloat, g: GLfloat, b: GLfloat, a: GLfloat)
}

struct Vertices {
    var v1: Vertex
    var v2: Vertex
    var v3: Vertex
    var v4: Vertex
}

var vertexData = Vertices(
    v1: Vertex( position:   (x: -0.5, y: -0.5, z:  0.0, w:  1.0),
                color:      (r:  1.0, g:  0.0, b:  0.0, a:  1.0)),
    v2: Vertex( position:   (x: -0.5, y:  0.5, z:  0.0, w:  1.0),
                color:      (r:  0.0, g:  1.0, b:  0.0, a:  1.0)),
    v3: Vertex( position:   (x:  0.5, y:  0.5, z:  0.0, w:  1.0),
                color:      (r:  0.0, g:  0.0, b:  1.0, a:  1.0)),
    v4: Vertex( position:   (x:  0.5, y: -0.5, z:  0.0, w:  1.0),
                color:      (r:  1.0, g:  1.0, b:  1.0, a:  1.0)) )

The Objective-C versions of the glVertexAttribPointer functions that I am trying to translate look like this:

glVertexAttribPointer((GLuint)positionAttribute, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *)offsetof(Vertex, position));
glVertexAttribPointer((GLuint)colourAttribute  , 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *)offsetof(Vertex, colour  ));

The Objective-C versions use the offsetof macro to set the pointer parameter of these functions. Swift doesn't allow macros, and so I'm trying to figure out what to use in its place. I've tried passing nil, like this:

glVertexAttribPointer(GLuint(positionAttribute), 4, UInt32(GL_FLOAT), UInt8(GL_FALSE), GLsizei(sizeof(Vertex)), nil)

glVertexAttribPointer(GLuint(colorAttribute), 4, UInt32(GL_FLOAT), UInt8(GL_FALSE), GLsizei(sizeof(Vertex)), nil)

But if I do that, the color data array is filled with the position data - no offset is taken into account, so it uses the position data for both the position and color attributes.

I found this stackoverflow answer which suggests using withUnsafePointer and tried it out, like this:

withUnsafePointer(&vertexData.v1.position) { ptr in
    glVertexAttribPointer(GLuint(positionAttribute), 4, UInt32(GL_FLOAT), UInt8(GL_FALSE), GLsizei(sizeof(Vertex)), ptr)
}

withUnsafePointer(&vertexData.v1.color) { ptr in
    glVertexAttribPointer(GLuint(colorAttribute), 4, UInt32(GL_FLOAT), UInt8(GL_FALSE), GLsizei(sizeof(Vertex)), ptr)
}

But that crashes the whole display, requiring a forced shutdown and a reboot.

I'm not sure what to try next. The complete code that I'm working on is available here.

EDIT:

I've also tried taking a pointer to the first data point and advancing it by 4 GLfloats, like this:

let ptr = UnsafePointer<GLfloat>([vertexData.v1.position.x])
glVertexAttribPointer(GLuint(positionAttribute), 4, UInt32(GL_FLOAT), UInt8(GL_FALSE), GLsizei(sizeof(Vertex)), ptr)

glVertexAttribPointer(GLuint(colorAttribute), 4, UInt32(GL_FLOAT), UInt8(GL_FALSE), GLsizei(sizeof(Vertex)), ptr.advancedBy(4))

The display doesn't crash, but nothing is drawn to the screen at all.


Solution

  • To replicate the offsetof functionality you just need to know at which byte offset does each field lie in your structure.

    In your case, assuming your struct is tightly packed, the first call should receive 0, and the second 4 * GLfloat because that's the size of the first component. I'm not sure how to extract this data directly from the structure, especially since you're using tuples.

    To illustrate, your structure:

    struct Vertex {
        var position: (x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat)
        var color: (r: GLfloat, g: GLfloat, b: GLfloat, a: GLfloat)
    }
    

    is most probably laid out like that:

    GLfloat // <-- structure start // <-- position
    GLfloat
    GLfloat
    GLfloat
    GLfloat // <-- color
    GLfloat
    GLfloat
    GLfloat
    

    Hence the position lies at offset 0, and color at 4 * GLfloat.


    To create a pointer with a value 16, this answer looks relevant:

    let ptr = UnsafePointer<()> + 16
    

    So I suppose this should work as well:

    let ptr = UnsafePointer<()> + sizeof(GLfloat) * 4