Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Using OpenGL in the Google's Go

4.57/5 (9 votes)
10 Nov 2016CPOL5 min read 17.9K   299  
This article will show you how you can use the OpenGL graphics library in Google's Go language.

Introduction

When I see a new programming language, a question usually tries to enter my head; especially when I see a general purpose programming language. “Does this language support any hardware accelerated graphics library... such as OpenGL or DirectX? So I can make a game or a 3D application using it!” I don’t know about others, but I actually love graphics programming. So when I saw the Google’s Go, the same I thought, looked for an OpenGL package first and found a perfect one on the GitHub website. Then I made a simple project using the OpenGL in Go which shows a cube with basic lighting. Like my previous article on Go which showed a simple window creation with pure Win32 API, I’ve also used the pure Win32 API for the window creation in this project. It actually helps to understand the basic concept, as far as I know. So I thought why not share it with people? Let’s do it!

Downloading Necessary Go Packages

The OpenGL package can be found on GitHub website - https://github.com/go-gl/.

The package provides various version of the OpenGL. There also have several other Go bindings such as osmesa, GLFW 3, OpenCL, etc.

We also need the w32 package to use the Windows APIs. Download it from the following link, if you don’t have it already:

Using the Code

In this article, I’m not going to describe the window creation codes much since I already did it in my previous article on Go titled as ‘Creating A Window In Go Language’. So if you haven’t read it yet, I recommend reading it first before you go further. Here is the link:

First, we import all the necessary packages needed for our program. We will use the 2.1 version of the OpenGL API. We also need to import the unsafe package in order to obtain any structure’s size. Importing begins with import keyword:

C#
import (
        "runtime"
        "syscall"
        "unsafe"
        "go-libs/w32"
        "go-libs/gl/v2.1/gl"
)

Inside the main function which is the entry point of any Go program, we start creating the main window and then initialize the OpenGL rendering context. Function begins with func keyword in Go:

C#
func main() {

The following is a bool/boolean variable, used to determine whether the OpenGL is initialized or not. So at the very beginning of our program execution, we assign false as its initial value:

C#
GlInitialized = false

The following codes are to create the main window. This window is actually our drawing surface where we draw our 3D scene using the OpenGL rendering context. Go language lets us easily create and assign variable through using := 

C#
hInstance := w32.GetModuleHandle("")

We require calling the StringToUTF16Ptr function of the syscall package in order to convert a Go’s string literal to a UTF-16 string. Basically here, this string literal is our window’s class name:

C#
lpszClassName := syscall.StringToUTF16Ptr("GoOpenGL!--Class")

Let us create and fill a WNDCLASSEX structure with appropriate values:

C#
var wcex w32.WNDCLASSEX

wcex.Size             = uint32(unsafe.Sizeof(wcex))
wcex.Style  = w32.CS_HREDRAW | w32.CS_VREDRAW
wcex.WndProc      = syscall.NewCallback(WndProc)
wcex.ClsExtra   = 0
wcex.WndExtra   = 0
wcex.Instance   = hInstance
wcex.Icon       = w32.LoadIcon(hInstance, MakeIntResource(w32.IDI_APPLICATION))
wcex.Cursor     = w32.LoadCursor(0, MakeIntResource(w32.IDC_ARROW))
wcex.Background  = w32.COLOR_WINDOW + 11
wcex.MenuName  = nil
wcex.ClassName   = lpszClassName
wcex.IconSm         = w32.LoadIcon(hInstance, MakeIntResource(w32.IDI_APPLICATION))

w32.RegisterClassEx(&wcex)

hWnd := w32.CreateWindowEx(
0, lpszClassName, syscall.StringToUTF16Ptr("Go OpenGL!"), 
w32.WS_OVERLAPPEDWINDOW | w32.WS_VISIBLE, 
w32.CW_USEDEFAULT, w32.CW_USEDEFAULT, 400, 400, 0, 0, hInstance, nil)

In order to setup the pixel format of the drawing surface and create the GL context, we have to obtain our main window’s device context first:

C#
hDC := w32.GetDC(hWnd)

Now, we initialize a pixel format descriptor structure with appropriate values which lets us describe the drawing surface:

C#
var pfd w32.PIXELFORMATDESCRIPTOR

pfd.Size        = uint16(unsafe.Sizeof(pfd))
pfd.Version     = 1
pfd.DwFlags                = w32.PFD_DRAW_TO_WINDOW | w32.PFD_SUPPORT_OPENGL | w32.PFD_DOUBLEBUFFER
pfd.IPixelType   = w32.PFD_TYPE_RGBA

We will use 32-bit color for our drawing surface:

C#
pfd.ColorBits     = 32

The following ChoosePixelFormat function will attempt to match an appropriate pixel format supported by the window’s device context to our given pixel format description. It returns the appropriate pixel format index if success otherwise, returns zero on fail:

C#
pf := w32.ChoosePixelFormat(hDC, &pfd)

Now set the window’s device context’s pixel format to the pixel format returned by ChoosePixelFormat:

C#
w32.SetPixelFormat (hDC, pf, &pfd)

Now, we create the OpenGL rendering context through calling the WglCreateContext function of the w32 package:

C#
hRC := w32.WglCreateContext(hDC)
w32.WglMakeCurrent(hDC, hRC)

Don’t forget to initialize the gl package, otherwise none of any OpenGL function will work and could crash the program:

C#
if err := gl.Init(); err != nil {
    panic(err)
}

Assign the GlInitialized  with true value:

C#
GlInitialized = true

We also enable some lighting for a nice and better visual. Without any lighting, our 3D scene will look flat.

OpenGL has Enable and Disable function, used to enable/disable any specific feature or functionality. So we use the Enable function of the gl package in order to enable OpenGL lighting:

C#
gl.Enable(gl.LIGHTING)

Also set light’s ambient, diffuse and position attributes. You may want to play with these attributes or parameters for the experiment purpose.

In fixed pipeline, the OpenGL supports up to 8 lights and they are indexed as LIGHT0, LIGHT1, and LIGHT2 so on. But we only need one light for our 3D scene. So use the first one which is LIGHT0.

Let’s create our parameter variables and assign appropriate values to them:

C#
ambient := []float32{0.5, 0.5, 0.5, 1}
diffuse := []float32{1, 1, 1, 1}
lightPosition := []float32{-5, 5, 10, 0}

Now tell OpenGL that those parameters are for the LIGHT0:

C#
gl.Lightfv(gl.LIGHT0, gl.AMBIENT, &ambient[0])
gl.Lightfv(gl.LIGHT0, gl.DIFFUSE, &diffuse[0])
gl.Lightfv(gl.LIGHT0, gl.POSITION, &lightPosition[0])

We also have to enable a light manually in order to use it. So let’s enable the LIGHT0:

C#
gl.Enable(gl.LIGHT0)

So now, all the initializations are done. It is the time to draw…

Inside our application’s main loop, we draw our scene and then swap the front and back buffers to show the rendered image into the window:

C#
for {
    // Render our scene scene
    drawScene()

This SwapBuffers function swaps the front and back buffers if the current pixel format for the window referenced by the specified device context includes a back buffer. So, if we don’t exchange the front and back buffers, nothing will appear in the window:

C#
    w32.SwapBuffers(hDC)
}

When the main loop ends, we cleanup the memory:

C#
w32.WglMakeCurrent(w32.HDC(0), w32.HGLRC(0))
w32.ReleaseDC(hWnd, hDC)
w32.WglDeleteContext(hRC)

Also delete the main window we created:

C#
    w32.DestroyWindow(hWnd)
}

This drawScene function clears the window with gray color, setups projection and model-view matrix and then draws a simple cube:

C#
func drawScene() {
    // Clear the drawing surface with gray color
    gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
    gl.ClearColor(0.5, 0.5, 0.5, 0.0)
    gl.ClearDepth(1)
   

    // Setup projection matrix
    gl.MatrixMode(gl.PROJECTION)
          gl.LoadIdentity()
          gl.Frustum(-1, 1, -1, 1, 1.0, 10.0)   

    // Setup model-view matrix
          gl.MatrixMode(gl.MODELVIEW)
          gl.LoadIdentity()

Let’s slightly rotate the view for a 3D look and feel:

C#
gl.Translatef(0, 0, -3.0)
gl.Rotatef(40.0, 1, 0, 0)
gl.Rotatef(40.0, 0, 1, 0)

Now we draw a simple cube:

C#
    drawCube()
}

We also have to handle the WM_SIZE message to resize the OpenGL viewport according to our main window size:

C#
func WndProc(hWnd w32.HWND, msg uint32, wParam, lParam uintptr) (uintptr) {
    switch msg {
    case w32.WM_SIZE:

Make sure we have initialized the OpenGL before we use it (since the WM_SIZE message will immediately appear here after CreateWindowEx function creates the window):

C#
if GlInitialized == true {
    rc := w32.GetClientRect(hWnd)
    gl.Viewport(0, 0, rc.Right, rc.Bottom)
}
break

Building the Program

Finally, we compile our program using the following commands. These commands can/should be putted in a batch (.bat) file for shortcut build:

go build -ldflags "-H windowsgui" main.go

Now, run the ‘main.exe’ file to execute the program and see your ‘Go OpenGL!’

Request to Readers

If you have any ideas about a new project on Go language that would help people to learn the language more easily and effectively, you can inform me through commenting. Also feel free to tell me if you have any suggestion on improving this article.

Conclusion

Go is a very nice general purpose programming language. There is a lot to learn about it and we should use it wisely to get proper benefits from it. And at the end, I hope my article has helped beginner programmers.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)