Getting started with SDL2 and Go on Wayland
Edited: Tuesday 28 January 2025

go 26 January 2025

  • A binding for SDL2 exists already for Go, so there is no need to use the C functions directly
  • A simple program to run a window and color it didn’t work out of the box and SDL_VIDEODRIVER=wayland needs to be set.
  • The functions that takes structs as first parameter are adapted in the binding to be a method to the struct.

Simple colored window

Adapted from SDL2 tutorial. It opens a window, color it light red and wait for 3 seconds before closing.

 1package main
 2
 3import (
 4	"log/slog"
 5	"os"
 6	"time"
 7
 8	"github.com/veandco/go-sdl2/sdl"
 9)
10
11func main() {
12	slog.Info("Initializing...")
13	ErrAndExit("Failed to initialize SDL", sdl.Init(sdl.INIT_EVERYTHING))
14	defer sdl.Quit()
15
16	w, err := sdl.CreateWindow("Example", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, 1280, 720, sdl.WINDOWEVENT_SHOWN)
17	ErrAndExit("Failed to create a window", err)
18	defer w.Destroy()
19
20	s, err := w.GetSurface()
21	ErrAndExit("Failed to get window surface", err)
22
23	ErrAndExit("Failed to fill rect", s.FillRect(nil, sdl.MapRGB(s.Format, 255, 90, 120)))
24	ErrAndExit("Failed to update surface", w.UpdateSurface())
25
26	time.Sleep(time.Second * 3)
27}
28
29func ErrAndExit(msg string, err error) {
30	if err != nil {
31		slog.Error("Failed to update surface", "error", err)
32		os.Exit(1)
33	}
34}

It needs to run using the following command otherwise the window appears black:

1SDL_VIDEODRIVER=wayland  go run main.go

The output looks like the following

2025/01/26 12:40:32 INFO Initializing...

Press Q to exit

Replace time.Sleep with the following, will exit when q is pressed or the program gets a quit signal from the system

 1for {
 2    switch e := sdl.PollEvent().(type) {
 3    case *sdl.QuitEvent:
 4        os.Exit(0)
 5    case *sdl.KeyboardEvent:
 6        if e.Keysym.Sym == sdl.K_q {
 7            os.Exit(0)
 8        }
 9    }
10}

Load an Image

Before updating the surface we can load an image and draw it

1i, err := sdl.LoadBMP("image.bmp")
2ErrAndExit("Failed to load image", err)
3defer i.Free()
4
5ErrAndExit("Failed to draw image", i.Blit(nil, s, &sdl.Rect{X: 0, Y: 0}))

Switch to GPU rendering

We need to change our approach. replace the part from creating the window until we update the surface with the following

 1w, r, err := sdl.CreateWindowAndRenderer(1280, 720, sdl.WINDOWEVENT_SHOWN)
 2ErrAndExit("Failed to create a window", err)
 3defer w.Destroy()
 4defer r.Destroy()
 5
 6r.SetDrawColor(255, 90, 120, 0)
 7ErrAndExit("Failed to fill rect", r.FillRect(nil))
 8
 9i, err := sdl.LoadBMP("image.bmp")
10ErrAndExit("Failed to load image", err)
11defer i.Free()
12
13t, err := r.CreateTextureFromSurface(i)
14ErrAndExit("Failed to create texture", err)
15
16r.Copy(t, nil, nil)
17r.Present()

Switch to Go image stdlib

This function loads an image and convert it to SDL surface. It can be used instead of sdl.LoadBMP so there is no need for SDL image

 1func loadImageAsSurface(filePath string) (*sdl.Surface, error) {
 2	file, err := os.Open(filePath)
 3	ErrAndExit("Failed to open image", err)
 4	defer file.Close()
 5
 6	img, _, err := image.Decode(file)
 7	ErrAndExit("Failed to decode image", err)
 8
 9	bounds := img.Bounds()
10	width, height := bounds.Dx(), bounds.Dy()
11
12	surface, err := sdl.CreateRGBSurfaceWithFormat(0, int32(width), int32(height), 32, sdl.PIXELFORMAT_ABGR8888)
13	ErrAndExit("Failed to create surface", err)
14
15	surface.Lock()
16
17	for y := 0; y < height; y++ {
18		for x := 0; x < width; x++ {
19			surface.Set(x, y, img.At(x, y))
20		}
21	}
22
23	surface.Unlock()
24
25	return surface, nil
26}

Hello world text

Init TTF, Load a font, print hello world to a surface, convert it to texture and draw it

 1ErrAndExit("Failed to initialize TTF", ttf.Init())
 2defer ttf.Quit()
 3
 4f, err := ttf.OpenFont("/usr/share/fonts/inter/InterVariable.ttf", 32)
 5ErrAndExit("Failed to open fond", err)
 6defer f.Close()
 7
 8txt, err := f.RenderUTF8Solid("Hello world", sdl.Color{R: 255, G: 255, B: 255})
 9ErrAndExit("Failed to render text", err)
10defer txt.Free()
11
12txtt, err := r.CreateTextureFromSurface(txt)
13ErrAndExit("Failed to convert text surface to texture", err)
14defer txtt.Destroy()
15
16r.Copy(txtt, nil, &sdl.Rect{X: 10, Y: 10, W: txt.W, H: txt.H})

Vsync

Split creating the window and renderer to be able to pass flags for VSYNC

1w, err := sdl.CreateWindow("Example", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, 1280, 720, sdl.WINDOWEVENT_SHOWN)
2ErrAndExit("Failed to create a window", err)
3defer w.Destroy()
4
5r, err := sdl.CreateRenderer(w, -1, sdl.RENDERER_ACCELERATED|sdl.RENDERER_PRESENTVSYNC)
6ErrAndExit("Failed to create renderer", err)
7defer r.Destroy()

References

See Also