[BY TIMOTHY BOGDALA]

mingw-w64 and Go

Published: July 23, 2015

This article covers how to setup mingw-w64 on Windows – specifically Windows 8 64-bit – and get the 64-bit version of Go to use a 64-bit version of gcc.

I’ve been working on writing a game using the Go programming language. While I’ve been working mostly in Linux these days, any game worth its salt should be able to run on Windows. The problem I’ve had with Go, OpenGL and Windows is that the cgo tool requires a matching gcc compiler. That means you need a 64-bit gcc if you’re using Go 64-bit. Up until now, I’ve been using mingw (see my articles on calling c code and using OpenGL with Go). Mingw does not ship a 64-bit version of gcc, which means I could only use Go 386.

It also means that I’m on my own when compiling dependencies and I may mess something up. As it turns out, I must have done something bad with assimp, because loading a ‘.fbx’ file with a mesh and animation was crashing on Windows using my Go wrapper for assimp but was operating fine in Linux. When I researched mingw-w64, besides the expected 64-bit gcc, I was super excited to see that it uses pacman for dependency management and installation. This means that I ended up just running pacman to install pre-built binaries for mingw-w64 that resulted in my assimp wrapper and game no longer crashing. And I got to use Go 64-bit to boot, which meant I could do more largely memory dependent experiments.

And the last little bit of joy I got from switching to mingw-w64 came in the form of a terminal window that was fully resizable! It sounds so silly that I was happy about this in 2015, but on Windows it just doesn’t seem common to have a decent terminal window.

Uninstalling Old Versions

Uninstall the following projects if you already have them installed:

Check your environment variables to see if C:\MinGW\bin is listed in either the user $PATH or the system $PATH variables. If it is there, then remove it while being careful with the ; characters so that you don’t corrupt your PATH.

Requirements

When you go to the mingw-w64 download page, you can see some pre-built tool chains. For this article, we will be using Msys2 which includes all the 64-bit goodness we need.

Installation of Go amd64

After downloading go1.4.2.windows-amd64.msi, run the installer. Accept the license agreement, keep the default folder of C:\Go and install. According to the install docs, the MSI file should automatically update your environment variables for you, it just requires you to restart any open terminals.

Installation of Msys2 and mingw-w64

After downloading msys2-x86_64-20150512.exe, run the installer. The instructions on the main Msys2 page have some nice images for those that are more visually oriented, but they are a little out of date. Follow the defaults of the installer program, which installs to the c:\msys64 folder and has you running MSYS2 after install.

Now that you have the msys2 environment installed with mingw-w64, in the MSYS2 terminal run the following command to update the installation:

pacman --needed -Sy bash pacman pacman-mirrors msys2-runtime

After that completes, restart the MSYS2 terminal (Found at C:\msys64\mingw64_shell.bat) and run:

pacman -Su

Due to whatever reasons the MSYS2 people have, it’s necessary to update with pacman in that order. If you get any errors while downloading during that first line where you update the msys2-runtime, keep retrying until everything is successful. Sourceforge has been super flaky as of late and it will throw off the install process if the first line doesn’t complete successfully.

Lastly, you need to get the actuall 64-bit gcc compilers with the following command:

pacman -S mingw-w64-x86_64-gcc

After those commands, you now have a completely up-to-date system with 64-bit versions of gcc.

Installing Dependency C/C++ Libraries

Lets say you want to want to use Go and OpenGL together. You’ll want to install dependeny packages like GLFW. To see if they exist in the new MSYS2/MinGW-w64 environment, query pacman with the following command:

pacman -Ss glfw

This will produce output like:

timothy@Blitz MINGW64 ~
$ pacman -Ss glfw
mingw32/mingw-w64-i686-glfw 3.1-1
    A free, open source, portable framework for OpenGL application development.
    (mingw-w64)
mingw64/mingw-w64-x86_64-glfw 3.1-1
    A free, open source, portable framework for OpenGL application development.
    (mingw-w64)

You can actually install 32-bit or 64-bit versions of most (all?) libraries in the pacman package system. For now, install the 64-bit version of GLFW as well as the git and mercurial version control systems by using the following command:

pacman -S mingw-w64-x86_64-glfw git mercurial

You should now be able to install the Go wrapper library for GLFW by running the following command which pulls the current library from github.com:

export GOPATH=/c/Gocode
go get -u github.com/go-gl/glfw/v3.1/glfw

You can customize the GOPATH variable to be whatever you want, but using the above line lets go create C:\Gocode and then install the GLFW wrapper library to C:\Gocode\src\github.com\go-gl\glfw\v3.1\glfw.

You can now take the sample code from the wrapper’s README.md (replicated below), save it to C:\Gocode\src\glfwtest.go and run it using go run /c/Gocode/src/glfwtest.go

package main

import (
    "runtime"
    "github.com/go-gl/glfw/v3.1/glfw"
)

func init() {
    // This is needed to arrange that main() runs on main thread.
    // See documentation for functions that are only allowed to be called from the main thread.
    runtime.LockOSThread()
}

func main() {
    err := glfw.Init()
    if err != nil {
        panic(err)
    }
    defer glfw.Terminate()

    window, err := glfw.CreateWindow(640, 480, "Testing", nil, nil)
    if err != nil {
        panic(err)
    }

    window.MakeContextCurrent()

    for !window.ShouldClose() {
        // Do OpenGL stuff.
        window.SwapBuffers()
        glfw.PollEvents()
    }
}

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.

<- OLDER ENTRIES .|. NEWER ENTRIES ->