Go + OpenGL 2.1 + Shaders + VertexAttribPointer
Wow, that took a while.
I decided to port my little JavaScript + WebGL 3d game engine to Go. Everything was going along quite well until I decided to put together a little test renderer to see how it was all shaping up.
It’s fun to watch unit tests pass and all, but I wanted to see some 3d on the screen.
Go has bindings for OpenGL via cgo, and the library has a few examples of how to use OpenGL with Go. Unfortunately for me, both my Macs (and most Macs) only support OpenGL 2.1, and the example for OpenGL 2.1 is using a different pattern than I want.
The way I’ve built my engine is: I pass an array of mixed data to the shader. So for example, instead of passing an array for position and another array for color, I am passing one array with position and color data mixed together.
[x,y,z][r,g,b][x2,y2,z2][r2,g2,b2]...
vs.
[x,y,z,r,g,b,x2,y2,z2,r2,g2,b2...]
From what I have read, that is the better way to go about it.
What this means in practical terms is I need to set pointer offsets in the VertexAttribPointer calls, but doing so seeming involves passing something like: ((void*)(i))
- which turn out is kind of crazy in Go. Actually, it turns out, anything involving pointers between Go and C is mind bending and highly discouraged. So much so that the phrase “cgo is not go” is apparently canon (I think Dave Cheney initially said that)
Anyway, it took several days to sort this out, but if you’re in the same boat, this is how you do it - at least get it to work in a basic sense. I haven’t gone over this fully so it may have memory leaks or other problems.
Here is an example project that you should be able to compile and play with: Example Project
The first mistake I made, was thinking that I could just enable the VertexAttribPointer in order and the order would correspond with the order defined in the shader. It works that way in WebGL, so I just assumed. Bad assumption. You need to grab them by variable name. So given the shader:
#version 120
attribute vec3 Pos;
attribute vec3 Color;
uniform vec4 scaleMove;
varying vec3 fragmentColor;
void main()
{
...
After you compile the program, you need to lookup the locations
posLoc := gl.Uint(gl.GetAttribLocation(program, gl.GLString("Pos")))
colorLoc := gl.Uint(gl.GetAttribLocation(program, gl.GLString("Color")))
Once you have the locations you can then use those location in the VertexAttribPointer.
Which can be use like this:
sizeOfFloat := 4
bpe := gl.Sizei(<size_of_vertex_array> * sizeOfFloat)
gl.VertexAttribPointer(posLoc, 3, gl.FLOAT, gl.FALSE, bpe, gl.Offset(nil, uintptr(0)))
gl.VertexAttribPointer(colorLoc, 3, gl.FLOAT, gl.FALSE, bpe, gl.Offset(nil, uintptr(3*sizeOfFloat)))
The gl.Offset(nil, uintptr(0))
likely being the part you’re missing. From there you can add more as you wish. I have no idea how this effects garbage collection yet, but at least I know it’s a potential problem.
And I finally have a box.
Learning new stuff is funny. I went down so many rabbit holes, and learned so much more about Go than I was trying to. Funny that the fix is just 4 lines :-/