| Permalink

Getting started with OpenGL and Go

With the recent popularization of Golang, I’ve decided to show how graphics programming can be done in Go using modern OpenGL techniques. I’ll assume you are familiar with the concept of OpenGL and have a general idea of how it works on a higher level.

OpenGL's logo

Open Graphics Library (OpenGL) is a cross-language, cross-platform application programming interface (API) for rendering 2D and 3D vector graphics.

OpenGL context and platform-agnostic APIs

The first thing we need in order to be able to render with OpenGL is an OpenGL context. Now, depending on the platform we’re developing for, this can be done in a completely different way - if we’re developing for Windows, we’d have to use the win32 API; on Linux, that would be X; and Mac, Cocoa and NSOpenGL. That can be a real nightmare to deal with when you don’t have experience with those platform layers.

Also, we’d like to focus on multi-platform development, since that’s where Go really shines, and so we’re going to use an abstraction library that allows us to create a window with an OpenGL context regardless of the platform layer: GLFW. Now, GLFW was initially created as a C library. However, there are Go bindings available. That essentially means we’ll be calling the C library from the Go code. But hey, don’t worry: we’ll actually be writing pure Go code. The library takes care of the platform-specific details for us.

To start, we need to fetch two packages. First we download GLFW (for installation details or more info, check https://github.com/go-gl/glfw#installation):

1
go get -u github.com/go-gl/glfw/v3.2/glfw

Then we download the actual OpenGL bindings library:

1
go get -u github.com/go-gl/gl/v4.1-core/gl

Here, the versions might differ depending on what version of OpenGL your graphics card (and the graphics cards of those who will be targeted by your code) support. For more reference on the versions, check https://github.com/go-gl/gl#usage.

We’ll use GLFW 3.2 and OpenGL 4.1-core for the rest of this series of articles.

Opening a window

Let’s start from the basics: we need to call the initialization function of GLFW. You’re probably not familiar with this style of code (C-like), where there are no objects and functions are very much stateful, imperative. To do that, you should write something like:

1
2
3
4
runtime.LockOSThread()
if err := glfw.Init(); err != nil {  
    panic(fmt.Errorf("could not initialize glfw: %v", err)) 
}

You shouldn’t see any errors at this point. If you do, don’t panic.

The first line makes sure that OpenGL won’t break. That’s because it requires every function call that interfaces with its C library to be called from the main thread. We need to call that to ensure this in Go.

Next, we can specify window hints, which are essentially hints given to the GLFW library regarding the window and its OpenGL context. Let’s pass a few basic ones:

1
2
3
4
5
glfw.WindowHint(glfw.ContextVersionMajor, 4) 
glfw.WindowHint(glfw.ContextVersionMinor, 1) 
glfw.WindowHint(glfw.Resizable, glfw.True) 
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)

Here we’re saying a few number of things:

To read more about window hints and other configuration, check http://www.glfw.org/docs/latest/window_guide.html.

We’re almost there! Now that we’ve passed the required hints to GLFW, we can finally call the function that actually creates the window, like so:

1
2
3
4
win, err := glfw.CreateWindow(800, 600, "Hello world", nil, nil)
if err != nil {  
    panic(fmt.Errorf("could not create opengl renderer: %v", err))
}

Preparing for OpenGL

After creating the window, we just need a few more steps:

1
win.MakeContextCurrent()

MakeContextCurrent is what actually creates the OpenGL context within the platform window that gets created by GLFW. Now, we’re ready to open the window. Usually, for that, we’ll write a simple loop:

1
2
3
4
for !win.ShouldClose() {
   win.SwapBuffers()
   glfw.PollEvents()
}

Here, we’re looping as long as the window should be opened, and we do basic operations:

The final result should be a beautiful black window:

Window with a Black Screen

Now that we have an OpenGL context ready window, let’s actually initialize OpenGL and call a simple function to clear the screen to a nice blue color (this code should be placed before our window loop):

1
2
3
if err := gl.Init(); err != nil {
	panic(err)
}

And right after we initialize OpenGL, let’s clear the screen:

1
gl.ClearColor(0, 0.5, 1.0, 1.0)

And for that to work properly, we need to adjust our loop, with an extra OpenGL function call:

1
2
3
4
5
for !win.ShouldClose() {
	gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
	win.SwapBuffers()
	glfw.PollEvents()
}

And the result should be a blue screen, just like this one:

Window with a Blue Screen

Here’s the full code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import (
	"fmt"
	"runtime"

	"github.com/go-gl/gl/v4.1-core/gl"
	"github.com/go-gl/glfw/v3.2/glfw"
)

const (
	windowWidth  = 960
	windowHeight = 540
)

func main() {
	runtime.LockOSThread()
	if err := glfw.Init(); err != nil {
		panic(fmt.Errorf("could not initialize glfw: %v", err))
	}

	glfw.WindowHint(glfw.ContextVersionMajor, 4)
	glfw.WindowHint(glfw.ContextVersionMinor, 1)
	glfw.WindowHint(glfw.Resizable, glfw.True)
	glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
	glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)

	win, err := glfw.CreateWindow(windowWidth, windowHeight, "Hello world", nil, nil)
	if err != nil {
		panic(fmt.Errorf("could not create opengl renderer: %v", err))
	}

	win.MakeContextCurrent()
	if err := gl.Init(); err != nil {
		panic(err)
	}

	gl.ClearColor(0, 0.5, 1.0, 1.0)

	for !win.ShouldClose() {
		gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
		win.SwapBuffers()
		glfw.PollEvents()
	}
}

I hope you enjoyed this article, and that it was helpful to you in some way. We’re going to be looking at drawing some basic primitive geometries in the next article. See you then!


Followed Articles

Status update, February 2021

Hi! Once again my focus has been Wayland-related projects this month. A steady stream of improvements made it into wlroots: Xyene…

via emersion February 22, 2021
Generated by openring