[BY TIMOTHY BOGDALA]

calling c code from go

Published: April 11, 2015

This article covers how to compile Assimp, the Open Asset Import Library, for MinGW on Windows and then how to use that library from within code written in Go.

There’s a project on github by andrebq that wraps some basic Assimp functionality. But if you want more things, like animations, then you’ll still need to do some work.

I decided to take this opportunity to write a custom importer, which will pull information from Assimp, put it into a Go structure, and then write it out using gob (a binary encoding). The following snippet below is an abbreviated minimal-working-version.

Requirements

Assimp might not compile without the DirectX SDK installed. If there’s an error of S1023 at the end it’s because of a newer VC10 runtime dll. For this tutorial, that error can be ignored.

If there were other dependencies, I missed them. I already have a functioning OpenGL development platform working on Windows with Go.

Building Zlib

Zlib is a dependency for Assimp, so you can compile your own version of it using these steps:

  1. Download the zlib-1.2.8.zip source file from the website.
  2. Unzip into a temporary directory.
  3. Open a MinGW shell and cd into that directory.
  4. Compile and install!
make -f win32/Makefile.gcc BINARY_PATH=/c/MinGW/msys/1.0/bin INCLUDE_PATH=/c/MinGW/msys/1.0/include LIBRARY_PATH=/c/MinGW/msys/1.0/lib install

Building Assimp

These steps build Assimp as a DLL and not build tools and tests. I ran into problems when trying to build a static version, plus linking the static version really slowed down the builds.

  1. Download the assimp-3.1.zip file from the website.
  2. Unzip into a temporary directory.
  3. Open a MinGW shell and cd to that directory.
  4. Create a cmake build directory and cd to that.
mkdir build; cd build
  1. Build it!
cmake -G "MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=/c/MinGW/msys/1.0 -ASSIMP_BUILD_STATIC_LIB=FALSE -DBUILD_SHARED_LIBS=TRUE -DASSIMP_BUILD_ASSIMP_TOOLS=FALSE -DASSIMP_BUILD_SAMPLES=FALSE -DASSIMP_BUILD_TESTS=FALSE -DCMAKE_BUILD_TYPE=RELEASE ..
make
make install

Building the Assimp Test in Go

Now, we’re ready for the fun part. The Go source code Here’s the source file in Go to do some basic querying of a model from Assimp:

package main

/*
#cgo CPPFLAGS: -I/MinGW/msys/1.0/include -std=c99
#cgo LDFLAGS: -L/MinGW/msys/1.0/lib -lassimp -lz -lstdc++

#include <stdio.h>
#include <stdlib.h>

#include <assimp/cimport.h>
#include <assimp/scene.h>
#include <assimp/mesh.h>
#include <assimp/cimport.h>
#include <assimp/matrix4x4.h>
#include <assimp/postprocess.h>

struct aiMesh* mesh_at(struct aiScene* s, unsigned int index)
{
    return s->mMeshes[index];
}

*/
import "C"
import (
    "flag"
    "fmt"
    "unsafe"
)

func main() {
    ///////////////////////////////////////////////////////////
    // process command line flags
    modelFilePtr := flag.String("mf", "model.obj", "model file")
    flag.Parse()
    modelFile := *modelFilePtr

    ///////////////////////////////////////////////////////////
    // attempt to load the file
    fmt.Printf("\n\nLoading: %s\n", modelFile)
    cModelFile := C.CString(modelFile)

    cScene := C.aiImportFile(cModelFile,
        C.aiProcess_JoinIdenticalVertices|
            C.aiProcess_Triangulate|
            C.aiProcess_GenSmoothNormals|
            C.aiProcess_CalcTangentSpace|
            C.aiProcess_FindInvalidData|
            C.aiProcess_LimitBoneWeights|
            C.aiProcess_ImproveCacheLocality|
            C.aiProcess_FixInfacingNormals|
            C.aiProcess_OptimizeMeshes|
            C.aiProcess_ValidateDataStructure)

    // make sure that we got a scene back
    if uintptr(unsafe.Pointer(cScene)) == 0 {
        fmt.Printf("Unable to load %s.\n", modelFile)
        return
    }

    fmt.Printf("Model file is loaded.\n")

    ///////////////////////////////////////////////////////////
    // write out some information about the model file
    fmt.Printf("\tMesh count: %d\n", cScene.mNumMeshes)
    fmt.Printf("\tTexture count: %d\n", cScene.mNumTextures)
    fmt.Printf("\tAnimations count: %d\n", cScene.mNumAnimations)

    ///////////////////////////////////////////////////////////
    // loop through each mesh
    for i:=uint(0); i<uint(cScene.mNumMeshes); i++ {
        cMesh := C.mesh_at(cScene, C.uint(i))

        ///////////////////////////////////////////////////////////
        // write out some information about the mesh
        fmt.Printf("Mesh index: %d\n", i)
        fmt.Printf("\tFace count: %d\n", cMesh.mNumFaces)
        fmt.Printf("\tBone count: %d\n", cMesh.mNumBones)
        fmt.Printf("\tUV component count: %d\n", cMesh.mNumUVComponents[0])
        fmt.Printf("\tMaterial index: %d\n", cMesh.mMaterialIndex)
        if cMesh.mTangents != nil {
          fmt.Printf("\tHas tangents: true\n")
        } else {
          fmt.Printf("\tHas tangents: false\n")
        }
    }
}

For an introduction into cgo and how Go interoperates with C, check out the following:

Basically, the magic happens in the comment above the import "C" line.

Each line with #cgo specifies compiler flags. In this case, I needed to explicitly set where to search for includes and libraries. I also control what libraries to link with.

The C code that follows that essentially gets embedded into the Go program and can be accessed from the C package (which is special). An example of calling a C function in Go is the mesh_at() function that is defined in C and called in Go via C.mesh_at(). The function itself was needed because I couldn’t figure out how to index aiMesh** types in Go.

The sample also shows that all of the code that was #included can be accessed from the C package (e.g. calling C.aiImportFile()).

Running this will copy the c++ library to where the program can find it (which I’m still not sure why I need to do) and then run the code. This snippit assumes you saved the above code in a file called assimp_importer.go.

cp /c/MinGW/bin/libstdc++-6.dll .
go run assimp_importer.go -mf test.obj

You should see some output like:

Loading: c:/Users/timothy/Desktop/test.obj
Model file is loaded.
        Mesh count: 1
        Texture count: 0
        Animations count: 0
Mesh index: 0
        Face count: 246
        Bone count: 0
        UV component count: 2
        Material index: 0
        Has tangents: true

go and opengl with windows and mingw

Published: December 20, 2014

As a follow-up to my previous article about using OpenGL in Go, this article describes how to get the sample running in Windows.

Requirements

64-bit gcc isn’t implemented in MinGW, so the 32-bit version of Go must be used.

MinGW, GLEW and GLFW will be installed in the next section.

Compiling Dependencies

The first thing is to make MinGW compatible versions of GLEW and GLFW.

Installing MinGW

  1. download the mingw-get-setup.exe installer from the MinGW website (tested with the 2013-10-04 version).
  2. run mingw-get-setup.exe … when selecting package, make sure to flag the bases, g++ and developer tools packages and then Apply Changes from the Installation menu.
  3. run the shell by executing C:\mingw\msys\1.0\msys.bat
  4. run in the shell: export PATH=/c/MinGW/bin:$PATH

This gives you the MinGW shell with the PATH environment variable updated to include the bin directory for it so that it can locate the compilers.

Also note that this article assumes you’ve installed it to C:\MinGW which is the recommended default.

Installing GLEW

Installing GLEW with MinGW is straight forward and doesn’t require much effort.

  1. download glew-1.11.0 from the GLEW website.
  2. unzip the source archive and cd to that directory in the same MinGW shell
  3. run: make all
  4. run: make install

This will put the libraries into /usr/lib/ which means basically it goes to C:\MinGW\msys\1.0\lib.

Installing GLFW3

The process to install GLFW with MinGW is a little more complicated involving some extra settings to the CMake file.

  1. download glfw 3.0.4 from the GLFW website
  2. unzip the source archive and cd to that directory in the same MinGW shell
  3. run: mkdir build; cd build to create directory to hold the build output
  4. run: cmake -G "MSYS Makefiles" -DCMAKE_INSTALL_PREFIX:PATH=/c/MinGW/msys/1.0 -DBUILD_SHARED_LIBS=TRUE ..
  5. run: make
  6. run: make install

At this point everything should be compiled and installed, with GLFW built as a DLL. Unfortunately we need these libraries to have the lib prefix in their name. I couldn’t find an option in CMake for this, so my solution was to just create file links using the MinGW shell:

cd /c/MinGW/msys/1.0/lib
ln -s glfw3.dll libglfw3.dll
ln -s glfw3dll.a libglfw3dll.a

Now we have our dependencies compiled, we should be able to import the Go libraries we need, which will end up using MinGW’s compilers to interface with the C libraries.

Compiling the Go Code

In the MinGW shell used, with the PATH variable set to include MinGW/bin, set some more variables by running the following:

export GOPATH=/c/go
export CGO_CFLAGS=-I/c/MinGW/msys/1.0/include
export CGO_LDFLAGS=-L/c/MinGW/msys/1.0/lib

This allows the go tool to find out where it’s installed and also tells the cgo tool where to find the include files and libraries for the dependencies.

Install the required Go libraries by running these commands:

go get github.com/go-gl/gl
go get github.com/go-gl/glfw3

Those should complete with no errors though you will probably see some warnings when getting the go-gl/gl package.

At this point, you can take the sample code from my previous article or download it directly: goTest.go. Save the file somewhere and built it by cd’ing to that directory in the shell and running:

go build glTest.go
cp /c/MinGW/msys/1.0/lib/glfw3.dll .
./glTest.exe

If you have any problems with this, tweet me @tbogdala.

Edit 141230: Added CMake as a dependency.

getting started with go and opengl

Published: December 18, 2014

This article goes over the basic steps to create a very basic modern OpenGL program. This means that we’ll be setting up an OpenGL 3.3 core context and loading shaders to draw stuff.

Who wouldn’t want to draw cool, multi-color triangles?

A multicolor triangle.

Requirements

Install Some Dependencies

This article uses wrappers for OpenGL libraries that are already made available to the community. You can install them with the following commands in your shell:

go get github.com/go-gl/gl
go get github.com/go-gl/glfw3

If you want to get the examples for using these libraries, you can get them by running:

go get github.com/go-gl/examples

The examples might fail to build if you don’t have all of the libraries for all of the examples. For example, I don’t have earlier versions of GLFW installed, so an error message was thrown. That’s fine though, because I just wanted a local copy of the source code.

Simple Window Creation

The first step is getting some boilerplate out of the way by creating the window that will contain the OpenGL context. The following listing is a modified version of a basic example that exists in go-gl/glfw3/README.md, combined with some of the triangle.go sample, but with more comments from me to explain what is going on.

package main

import (
  "fmt"
  glfw "github.com/go-gl/glfw3"
  )

  // handle GLFW errors by printing them out
  func errorCallback(err glfw.ErrorCode, desc string) {
    fmt.Printf("%v: %v\n", err, desc)
  }

  // key events are a way to get input from GLFW.
  // here we check for the escape key being pressed. if it is pressed,
  // request that the window be closed
  func keyCallback(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
    if key == glfw.KeyEscape && action == glfw.Press {
      w.SetShouldClose(true)
    }
  }

  func main() {
    // make sure that we display any errors that are encountered
    glfw.SetErrorCallback(errorCallback)

    // the GLFW library has to be initialized before any of the methods
    // can be invoked
    if !glfw.Init() {
      panic("Can't init glfw!")
    }

    // to be tidy, make sure glfw.Terminate() is called at the end of
    // the program to clean things up by using `defer`
    defer glfw.Terminate()

    // hints are the way you configure the features requested for the
    // window and are required to be set before calling glfw.CreateWindow().

    // desired number of samples to use for mulitsampling
    glfw.WindowHint(glfw.Samples, 4)

    // request a OpenGL 3.3 core context
    glfw.WindowHint(glfw.ContextVersionMajor, 3)
    glfw.WindowHint(glfw.ContextVersionMinor, 3)
    glfw.WindowHint(glfw.OpenglForwardCompatible, glfw.True)
    glfw.WindowHint(glfw.OpenglProfile, glfw.OpenglCoreProfile)

    // do the actual window creation
    window, err := glfw.CreateWindow(640, 480, "Testing", nil, nil)
    if err != nil {
      // we legitimately cannot recover from a failure to create
      // the window in this sample, so just bail out
      panic(err)
    }

    // set the callback function to get all of the key input from the user
    window.SetKeyCallback(keyCallback)

    // GLFW3 can work with more than one window, so make sure we set our
    // new window as the current context to operate on
    window.MakeContextCurrent()

    // disable v-sync for max FPS if the driver allows it
    glfw.SwapInterval(0)

    // while there's no request to close the window
    for !window.ShouldClose() {
      // ...
      // do OpenGL stuff
      // ...

      // swapping OpenGL buffers and polling events has been decoupled
      // in GLFW3, so make sure to invoke both here
      window.SwapBuffers()
      glfw.PollEvents()
    }
  }

Save this code to a file called go_test1.go. This test can be built using this command in the same directory that contains go_test1.go:

go build go_test1.go

For those that are curious, here’s some file info for you (from my arch linux system):

[timothy@Blitz tests]$ ls -lh
total 2.2M
-rwxr-xr-x 1 timothy timothy 2.2M Dec 18 15:54 go_test1
-rw-r--r-- 1 timothy timothy 1.3K Dec 18 15:54 go_test1.go

[timothy@Blitz tests]$ strip go_test1

[timothy@Blitz tests]$ ls -lh
total 1.5M
-rwxr-xr-x 1 timothy timothy 1.5M Dec 18 15:54 go_test1
-rw-r--r-- 1 timothy timothy 1.3K Dec 18 15:54 go_test1.go

[timothy@Blitz tests]$ ldd go_test1
linux-vdso.so.1 (0x00007fffb5901000)
libglfw.so.3 => /usr/lib/libglfw.so.3 (0x00007f76b6d9d000)
libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007f76b6b81000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007f76b67de000)
libX11.so.6 => /usr/lib/libX11.so.6 (0x00007f76b649c000)
libXrandr.so.2 => /usr/lib/libXrandr.so.2 (0x00007f76b6292000)
libXi.so.6 => /usr/lib/libXi.so.6 (0x00007f76b6081000)
libXxf86vm.so.1 => /usr/lib/libXxf86vm.so.1 (0x00007f76b5e7b000)
librt.so.1 => /usr/lib/librt.so.1 (0x00007f76b5c73000)
libm.so.6 => /usr/lib/libm.so.6 (0x00007f76b596e000)
libGL.so.1 => /usr/lib/libGL.so.1 (0x00007f76b5620000)
/lib64/ld-linux-x86-64.so.2 (0x00007f76b6fb0000)
libxcb.so.1 => /usr/lib/libxcb.so.1 (0x00007f76b53fe000)
libdl.so.2 => /usr/lib/libdl.so.2 (0x00007f76b51fa000)
libXext.so.6 => /usr/lib/libXext.so.6 (0x00007f76b4fe8000)
libXrender.so.1 => /usr/lib/libXrender.so.1 (0x00007f76b4dde000)
libnvidia-tls.so.343.36 => /usr/lib/libnvidia-tls.so.343.36 (0x00007f76b4bdb000)
libnvidia-glcore.so.343.36 => /usr/lib/libnvidia-glcore.so.343.36 (0x00007f76b1f73000)
libXau.so.6 => /usr/lib/libXau.so.6 (0x00007f76b1d6f000)
libXdmcp.so.6 => /usr/lib/libXdmcp.so.6 (0x00007f76b1b69000)

Running this test in the shell using ./go_test1 will cause a window to open, though it won’t actually display anything. This is a step in the right direction.

An empty window.

Hit the esc key to close the window.

Drawing the Triangle

The next step is to do all of the rest of the boilerplate to get the triangle to draw. For this article, I’m making the conscious decision to use as little help as possible and stick just to the gl and glfw libraries. There is a glh library that would otherwise shorten this code but, but I wanted to use the long form for those that are already familiar with OpenGL.

For this part, I will cover some of the new additions separately and then I’ll show the whole code file at the end.

Shaders

Modern OpenGL is largely driven by shaders and you’ll need one to show anything in a version 3.3 core context. We start off by declaring variables at the module level for the shader code and attribute locations:

var (
  // vertex shader
  triangleVertShader = `#version 330
  in vec3 position;
  out vec3 out_pos;
  void main()
  {
      out_pos = position;
      gl_Position = vec4(position, 1.0);
  }`

  // fragment shader
  triangleFragShader = `#version 330
  in vec3 out_pos;
  out vec4 colourOut;
  void main()
  {
    float red = out_pos.x + 0.5;
    float green = (-0.5 + out_pos.x) * -1.0;
    float blue = out_pos.y;
    colourOut = vec4(red, green, blue, 1.0);
  }`

  // shader bind location for the position attribute
  shaderAttrLoc_Position = gl.AttribLocation(1)
)

If you have any experience with GLSL, you’ll see that these are very simple shaders. The vertex shader simply copies the vertex location and shares it with the fragment shader. The fragment shader determines the color per-pixel based off of the fragment location. We also explicity bind the location for the input position vector for the vertex shader.

A separate function was created to load the shader code into a program in OpenGL. If you use the glh library, this function could be made a lot shorter, but I wanted to do this by hand for illustration.

// loads shader objects and then attaches them to a program
func loadShaderProgram() (gl.Program, error) {
    // create the program
    prog := gl.CreateProgram()

    // create the vertex shader
    vs := gl.CreateShader(gl.VERTEX_SHADER)
    vs.Source(triangleVertShader)
    vs.Compile()
    vsCompiled := vs.Get(gl.COMPILE_STATUS)
    if vsCompiled != gl.TRUE {
        error := fmt.Sprintf("Failed to compile the vertex shader!\n%s", vs.GetInfoLog())
        fmt.Println(error)
        return prog, errors.New(error)
    }
    fmt.Println("Compiled the vertex shader ...")

    // create the fragment shader
    fs := gl.CreateShader(gl.FRAGMENT_SHADER)
    fs.Source(triangleFragShader)
    fs.Compile()
    fsCompiled := fs.Get(gl.COMPILE_STATUS)
    if fsCompiled != gl.TRUE {
        error := fmt.Sprintf("Failed to compile the fragment shader!\n%s", fs.GetInfoLog())
        fmt.Println(error)
        return prog, errors.New(error)
    }
    fmt.Println("Compiled the fragment shader ...")

    // attach the shaders to the program and link
    prog.AttachShader(vs)
    prog.AttachShader(fs)
    prog.BindAttribLocation(shaderAttrLoc_Position, "position")
    prog.Link()
    progLinked := prog.Get(gl.LINK_STATUS)
    if progLinked != gl.TRUE {
        error := fmt.Sprintf("Failed to link the program!\n%s", prog.GetInfoLog())
        fmt.Println(error)
        return prog, errors.New(error)
    }
    fmt.Println("Shader program linked ...")

    // at this point the shaders can be deleted
    vs.Delete()
    fs.Delete()

    return prog, nil
}

Again, if you’re familiar with OpenGL, then you’ve seen this pattern before. The shader code in string form are attached to shader objects and compiled. It is also very helpful to check the compile status because things will break – if not from a syntax error it will be due to different driver compatibilities amongst NVidia, AMD and Intel.

Once the shaders are compiled, they’re attached to a program object. The input attribute position is then explicitly bound to a location. This is not strictly required for this sample but it is a useful technique to know. The program is then linked and the link status is checked. The shader objects are then deleted as you only need the program at this point.

Now, back in the main() function, we add this fragment after the window was created and the final options set.

// make sure that GLEW initializes all of the GL functions
if gl.Init() != 0 {
  panic("Failed to initialize GL and GLEW!")
}

// compile our shader
prog, err := loadShaderProgram()
if err != nil {
  panic("Failed to compile and link the shader program!")
}
prog.Use()

This initializes the GL library which works with GLEW to correctly find OpenGL functions to call. Failure to call this function will crash your program when it attempts to call OpenGL functions. Suspect that you forgot to call Init if you see random crashes with things like gl.CreateProgram().

After the library is initialized we can use it to invoke the OpenGL commands to load our shader by calling the function we wrote above. After we get our shader program, we call Use() to make it the current shader to use while drawing. We’ll only be drawing one object, so doing this once here is sufficient.

Creating the Triangle

Next we need to load the vertex data for the triangle before we can draw it.

// create and bind the required VAO object
vao := gl.GenVertexArray()
vao.Bind()

// the raw vertex floats for the triangle
verts := [...]float32{
  -0.5, 0.0, 0.0, // bottom left
  0.5, 0.0, 0.0, // bottom right
  0.0, 1.0, 0.0} // top

// calculate the memory size of floats used to calculate total memory size of float arrays
floatSize := int(unsafe.Sizeof(gl.GLfloat(1.0)))

// create a VBO to hold the vertex data
vboVerts := gl.GenBuffer()
vboVerts.Bind(gl.ARRAY_BUFFER)

// load our data up and bind it to the 'position' shader attribute
gl.BufferData(gl.ARRAY_BUFFER, floatSize*len(verts), &verts, gl.STATIC_DRAW)
shaderAttrLoc_Position.EnableArray()
shaderAttrLoc_Position.AttribPointer(3, gl.FLOAT, false, 0, nil)

Our OpenGL 3.3 context will require a VAO to be used, so we create and bind one. We don’t do any rebinding since we’re only drawing one object.

Next we have an array of floats representing three vertices in 3d space. To calculate how many bytes we must buffer, we calculate the size of a gl.GLfloat by calling some unsafe code.

We create a VBO for the vertex data and pump the data into it. Finally, we point the position attribute of the shader to this VBO.

Drawing the Triangle

At last, we can modify the render loop to draw the triangle. The final loop looks like this:

for !window.ShouldClose() {
  // get the size of the window because it may have changed since creation
  width, height := window.GetFramebufferSize()

  // clear it all out
  gl.Viewport(0, 0, width, height)
  gl.ClearColor(0.0, 0.0, 0.0, 1.0)
  gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

  // draw the triangle
  gl.DrawArrays(gl.TRIANGLES, 0, 3)

  // swapping OpenGL buffers and polling events has been decoupled
  // in GLFW3, so make sure to invoke both here
  window.SwapBuffers()
  glfw.PollEvents()
}

We obtain the height and width of the window each time in case the user changes the dimensions of the window at run time. After that, we clear it with black and draw the triangle. Since we’re only drawing one object, the shader program, VAOs and VBOs have already been set up for use.

The Final Program

Here’s the final product in one shot. You should be able to save this to a file and compile it with go build. Note the new imports that were used.

If you prefer to download it in the file, you can access it here: glTest.go

package main

import (
    "errors"
    "fmt"
    gl "github.com/go-gl/gl"
    glfw "github.com/go-gl/glfw3"
    "unsafe"
)

var (
    // vertex shader
    triangleVertShader = `#version 330
        in vec3 position;
        out vec3 out_pos;
        void main()
        {
            out_pos = position;
            gl_Position = vec4(position, 1.0);
        }`

    // fragment shader
    triangleFragShader = `#version 330
        in vec3 out_pos;
        out vec4 colourOut;
        void main()
        {
            float red = out_pos.x + 0.5;
            float green = (-0.5 + out_pos.x) * -1.0;
            float blue = out_pos.y;
            colourOut = vec4(red, green, blue, 1.0);
        }`

    // shader bind location for the position attribute
    shaderAttrLoc_Position = gl.AttribLocation(1)
)

// handle GLFW errors by printing them out
func errorCallback(err glfw.ErrorCode, desc string) {
    fmt.Printf("%v: %v\n", err, desc)
}

// key events are a way to get input from GLFW.
// here we check for the escape key being pressed. if it is pressed,
// request that the window be closed
func keyCallback(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
    if key == glfw.KeyEscape && action == glfw.Press {
        w.SetShouldClose(true)
    }
}

// loads shader objects and then attaches them to a program
func loadShaderProgram() (gl.Program, error) {
    // create the program
    prog := gl.CreateProgram()

    // create the vertex shader
    vs := gl.CreateShader(gl.VERTEX_SHADER)
    vs.Source(triangleVertShader)
    vs.Compile()
    vsCompiled := vs.Get(gl.COMPILE_STATUS)
    if vsCompiled != gl.TRUE {
        error := fmt.Sprintf("Failed to compile the vertex shader!\n%s", vs.GetInfoLog())
        fmt.Println(error)
        return prog, errors.New(error)
    }
    fmt.Println("Compiled the vertex shader ...")

    // create the fragment shader
    fs := gl.CreateShader(gl.FRAGMENT_SHADER)
    fs.Source(triangleFragShader)
    fs.Compile()
    fsCompiled := fs.Get(gl.COMPILE_STATUS)
    if fsCompiled != gl.TRUE {
        error := fmt.Sprintf("Failed to compile the fragment shader!\n%s", fs.GetInfoLog())
        fmt.Println(error)
        return prog, errors.New(error)
    }
    fmt.Println("Compiled the fragment shader ...")

    // attach the shaders to the program and link
    prog.AttachShader(vs)
    prog.AttachShader(fs)
    prog.BindAttribLocation(shaderAttrLoc_Position, "position")
    prog.Link()
    progLinked := prog.Get(gl.LINK_STATUS)
    if progLinked != gl.TRUE {
        error := fmt.Sprintf("Failed to link the program!\n%s", prog.GetInfoLog())
        fmt.Println(error)
        return prog, errors.New(error)
    }
    fmt.Println("Shader program linked ...")

    // at this point the shaders can be deleted
    vs.Delete()
    fs.Delete()

    return prog, nil
}

func main() {
    // make sure that we display any errors that are encountered
    glfw.SetErrorCallback(errorCallback)

    // the GLFW library has to be initialized before any of the methods
    // can be invoked
    if !glfw.Init() {
        panic("Can't init glfw!")
    }

    // to be tidy, make sure glfw.Terminate() is called at the end of
    // the program to clean things up by using `defer`
    defer glfw.Terminate()

    // hints are the way you configure the features requested for the
    // window and are required to be set before calling glfw.CreateWindow().

    // desired number of samples to use for mulitsampling
    glfw.WindowHint(glfw.Samples, 4)

    // request a OpenGL 3.3 core context
    glfw.WindowHint(glfw.ContextVersionMajor, 3)
    glfw.WindowHint(glfw.ContextVersionMinor, 3)
    glfw.WindowHint(glfw.OpenglForwardCompatible, glfw.True)
    glfw.WindowHint(glfw.OpenglProfile, glfw.OpenglCoreProfile)

    // do the actual window creation
    window, err := glfw.CreateWindow(640, 480, "Testing", nil, nil)
    if err != nil {
        // we legitimately cannot recover from a failure to create
        // the window in this sample, so just bail out
        panic(err)
    }

    // set the callback function to get all of the key input from the user
    window.SetKeyCallback(keyCallback)

    // GLFW3 can work with more than one window, so make sure we set our
    // new window as the current context to operate on
    window.MakeContextCurrent()

    // disable v-sync for max FPS if the driver allows it
    glfw.SwapInterval(0)

    // make sure that GLEW initializes all of the GL functions
    if gl.Init() != 0 {
        panic("Failed to initialize GL and GLEW!")
    }

    // compile our shader
    prog, err := loadShaderProgram()
    if err != nil {
        panic("Failed to compile and link the shader program!")
    }
    prog.Use()

    // create and bind the required VAO object
    vao := gl.GenVertexArray()
    vao.Bind()

    // the raw vertex floats for the triangle
    verts := [...]float32{
        -0.5, 0.0, 0.0, // bottom left
        0.5, 0.0, 0.0, // bottom right
        0.0, 1.0, 0.0} // top

    // calculate the memory size of floats used to calculate total memory size of float arrays
    floatSize := int(unsafe.Sizeof(gl.GLfloat(1.0)))

    // create a VBO to hold the vertex data
    vboVerts := gl.GenBuffer()
    vboVerts.Bind(gl.ARRAY_BUFFER)

    // load our data up and bind it to the 'position' shader attribute
    gl.BufferData(gl.ARRAY_BUFFER, floatSize*len(verts), &verts, gl.STATIC_DRAW)
    shaderAttrLoc_Position.EnableArray()
    shaderAttrLoc_Position.AttribPointer(3, gl.FLOAT, false, 0, nil)

    // while there's no request to close the window
    for !window.ShouldClose() {
        // get the size of the window because it may have changed since creation
        width, height := window.GetFramebufferSize()

        // clear it all out
        gl.Viewport(0, 0, width, height)
        gl.ClearColor(0.0, 0.0, 0.0, 1.0)
        gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

        // draw the triangle
        gl.DrawArrays(gl.TRIANGLES, 0, 3)

        // swapping OpenGL buffers and polling events has been decoupled
        // in GLFW3, so make sure to invoke both here
        window.SwapBuffers()
        glfw.PollEvents()
    }
}

When you build and run it, you should see a triangle like this:

A multicolor triangle.

Not too bad!

For reference, you can compare it to my earlier article, Basic OpenGL 2.0 Tutorial, where this material is covered in C but using GLFW 2.x and OpenGL 2.x.

One Final Caveat

All of this was done on my linux system.

I have not confirmed that it will work on Mac OS X. Traditionally there’s always some platform hangup I miss and I suspect that GLFW might need some extra massaging.

I also have not attempted to get this running in Windows yet. That is the very next thing on my TODO list and I’ll either update or make a new post once I got things running.

For getting this set up on Windows, see my article: Go and OpenGL with Windows and MinGW.

<- OLDER ENTRIES .|. NEWER ENTRIES ->