[BY TIMOTHY BOGDALA]

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.

Android Studio with NDK OpenGL ES Sample

Published: December 07, 2014

Android Studio 1.0 RC4 was just recently pushed, so I decided now was the time to start refreshing my projects and ideas for Android development.

To get started, I wanted to see how difficult it would be to compile NDK samples with the new Android Studio IDE. I always found the process to be super clunky with Eclipse and had always resorted to doing everything by hand via the command line. It was obviously not ideal and was not a fun way to develop a game.

This article assumes:

The hello-gl2 Sample

If you download the recent Android NDK (version r10d as of this writing), you should be able to find the hello-gl2 sample in the android-ndk-r10d\samples directory. This sample still uses Java for the activities but it implements the OpenGL ES functionality in C++ and exposes the C++ code through a class called GL2JNILib.

In order to test this with Android Studio, I created a new project, copied the code over, changed the AndroidManifest.xml and updated the Gradle Scripts. Both this page and this page were helpful in figuring out the right steps.

Step 1: Create a New Project

When you start Android Studio, select the New Project option from the wizard. I created a project called NDKSample and left all of the settings at the default in the wizard except for selecting no activity instead of Blank Activity.

Step 2: Copy the Code

I put the hello-gl2 code into the new project by doing the following:

The empty.cpp file is literally an empty code file and is only there because there currently is a bug when compiling a NDK project with only one file. Android Studio will automatically make the equivalent of the Android.mk file, so all that is is needed is the C++ code.

The Java code copied in the com directory contains the Java activity and view classes as well as the class the wraps the JNI code.

Step 3: Modify the AndroidManifest.xml File

You can edit this file by selecting it in the Projects side panel in Android Studio under app > manifests > AndroidMainifest.xml. Replace the content of this file with the following:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.gl2jni">
    <application
    android:allowBackup="true"
    android:label="@string/app_name"
    android:icon="@drawable/ic_launcher"
    android:theme="@style/AppTheme">
    <activity android:name="GL2JNIActivity"
          android:launchMode="singleTask"
          android:configChanges="orientation|keyboardHidden">
          <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
        </activity>
    </application>
</manifest>

What this does is change the package name to com.android.gl2jni so that none of the sample code needs to be edited and it changes the activity name so that it knows what activity to launch at startup.

Step 4: Update the Gradle Scripts

The last step is to change the Gradle build scripts. You need to define your path to the NDK by editing local.properties and adding the following line to the file:

ndk.dir=C\:\\Projects\\android-ndk-r10d

Then you need to define the NDK module to build by editing the build.gradle (Module: app) script, and add the following script code to the defaultConfig block in the file

ndk {
    moduleName "libgl2jni"
    ldLibs "GLESv2","log"
}

This controls the file name of the library and should match what is found in the Java file com.android.gl2jni.GL2JNILib.

Once you make these changes you can sync the project settings by selecting the sync option in the tooltip that should be at the top of the editor, or you can manually invoke it using the menu Tools > Android > Sync Project with Gradle Files

Step 5: Build and Run!

Now you should be able to build the project and run it in the emulator or on your device!

<- OLDER ENTRIES .|. NEWER ENTRIES ->