Go SDL2 Lesson 4

Working with fonts

While I was working on lesson 4 I noticed that the SDL2 bindings had been updated since the last time I checked, and the update added a new example on font rendering. Fortunately for me, the example doesn’t deal with hardware acceleration and only handles 1 of the 3 types of fonts so I’m not trashing this lesson.

Go SDL2 Lesson 4

It is worth remembering that the default for “go get” when pointed at a package is to add any files the local repository is missing and leave everything else alone. Use “go get -u” to also update local files. Lesson 4 source code (and font) is available on Github. The code is a bit different from the previous lessons, stuffing everything into a main() was getting to be a mess.

  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
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package main

import (
	"fmt"
	"os"
	"time"

	"github.com/veandco/go-sdl2/sdl"
	"github.com/veandco/go-sdl2/ttf"
)

const (
	screenWidth  = 640
	screenHeight = 480
)

// Globals - terrible for serious programs, great for short examples
var (
	window         *sdl.Window
	renderer       *sdl.Renderer
	event          sdl.Event
	quit           bool
	err            error
	solidTexture   *sdl.Texture
	blendedTexture *sdl.Texture
	shadedTexture  *sdl.Texture
)

func Setup() (successful bool) {
	err = sdl.Init(sdl.INIT_VIDEO)
	if err != nil {
		fmt.Printf("Failed to initialize sdl: %s\n", err)
		return false
	}
	// Using the SDL_ttf library so need to initialize it before using it
	if err = ttf.Init(); err != nil {
		fmt.Printf("Failed to initialize TTF: %s\n", err)
		return false
	}

	window, err = sdl.CreateWindow("Go + SDL2 Lesson 4", sdl.WINDOWPOS_UNDEFINED,
		sdl.WINDOWPOS_UNDEFINED, screenWidth, screenHeight, sdl.WINDOW_SHOWN)
	if err != nil {
		fmt.Printf("Failed to create renderer: %s\n", err)
		return false
	}

	renderer, err = sdl.CreateRenderer(window, -1, sdl.RENDERER_ACCELERATED)
	if err != nil {
		fmt.Printf("Failed to create renderer: %s\n", err)
		return false
	}

	sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1")

	return true
}

func Shutdown() {
	solidTexture.Destroy()
	shadedTexture.Destroy()
	blendedTexture.Destroy()
	ttf.Quit()
	renderer.Destroy()
	window.Destroy()
	sdl.Quit()
}

func HandleEvents() {
	for event = sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
		switch t := event.(type) {
		case *sdl.QuitEvent:
			quit = true
		case *sdl.KeyboardEvent:
			if t.Keysym.Sym == sdl.K_ESCAPE {
				quit = true
			}
		}
	}
}

func Draw() {
	renderer.SetDrawColor(255, 255, 55, 255)
	renderer.Clear()

	// Draw the 3 text textures
	renderer.Copy(solidTexture, nil, &sdl.Rect{(screenWidth / 2) - 89, 60, 190, 53})
	renderer.Copy(shadedTexture, nil, &sdl.Rect{(screenWidth / 2) - 112, 130, 244, 53})
	renderer.Copy(blendedTexture, nil, &sdl.Rect{(screenWidth / 2) - 117, 200, 244, 53})

	renderer.Present()
}

func CreateFonts() (successful bool) {
	var font *ttf.Font

	if font, err = ttf.OpenFont("Roboto-Regular.ttf", 40); err != nil {
		fmt.Printf("Failed to open font: %s\n", err)
		return false
	}

	var solidSurface *sdl.Surface
	if solidSurface, err = font.RenderUTF8Solid("Solid Text", sdl.Color{255, 0, 0, 255}); err != nil {
		fmt.Printf("Failed to render text: %s\n", err)
		return false
	}

	if solidTexture, err = renderer.CreateTextureFromSurface(solidSurface); err != nil {
		fmt.Printf("Failed to create texture: %s\n", err)
		return false
	}

	solidSurface.Free()

	var shadedSurface *sdl.Surface
	if shadedSurface, err = font.RenderUTF8Shaded("Shaded Text",
		sdl.Color{0, 255, 0, 255}, sdl.Color{255, 0, 255, 255}); err != nil {
		fmt.Printf("Failed to render text: %s\n", err)
		return false
	}

	if shadedTexture, err = renderer.CreateTextureFromSurface(shadedSurface); err != nil {
		fmt.Printf("Failed to create texture: %s\n", err)
		return false
	}

	shadedSurface.Free()

	var blendedSurface *sdl.Surface
	if blendedSurface, err = font.RenderUTF8Blended("Blended Text", sdl.Color{0, 0, 255, 255}); err != nil {
		fmt.Printf("Failed to render text: %s\n", err)
		return false
	}

	if blendedTexture, err = renderer.CreateTextureFromSurface(blendedSurface); err != nil {
		fmt.Printf("Failed to create texture: %s\n", err)
		return false
	}

	blendedSurface.Free()
	font.Close()

	return true
}

func main() {
	// initialize sdl and create the window & renderer
	// if there's an error terminate the program
	if !Setup() {
		os.Exit(1)
	}

	// isolate most of the new code into a seperate func for reading
	//terminate program is there is an error
	if !CreateFonts() {
		os.Exit(2)
	}

	// No more calling time.Sleep! Create a ticker that will trigger up to 30 times a second
	// for a more consistent frame rate and animation speed. Obviously more useful in demos that
	// actually have movement. Still not as good as calculating delta time for consistent framerate
	// independent animation - but this is WAY more convenient!
	ticker := time.NewTicker(time.Second / 30)

	// New main loop. Broke events and drawing into seperate functions - it was getting unwieldy
	for !quit {
		HandleEvents()
		Draw()
		<-ticker.C // wait up to 1/30th of a second

	}
	// Self explainatory
	Shutdown()
}

This lesson uses the SDL_tff library so a new import has been added.

import “github.com/veandco/go-sdl2/ttf”

Much like SDL2’s sdl.Init, to use SDL_tff you must first initialize it.

ttf.Init()

Opens the true type font file and sets the size of the font type.

ttf.OpenFont(“Roboto-Regular.ttf”, 40)

These 3 functions all render text to a surface. When programming in C/C++ SDL provides numerous different ways to input text and turn it into a surface. With Go there is only the UTF-8 functions. Go is all about the UTF-8 - hardly surprising since the co-creators of UTF-8 (Rob Pike & Ken Thompson) make up two of the three original creators of Go.

font.RenderUTF8Solid

font.RenderUTF8Shaded

font.RenderUTF8Blended

Once the newly created surface with text is created the program turns it into a hardware accelerated texture.

renderer.CreateTextureFromSurface(solidSurface)

Once the text has been turned into a texture it is just like lesson 3 and dealing with images. All that’s left is the clean up.

solidSurface.Free()

Release the surface memory since it is no longer needed. It would be nice to be able to skip the surface creation and go straight to a texture but that is not an option with the SDL_tff library.

font.Close()

When we no longer need the font *ttf.Font it is released from memory in the program.

solidTexture.Destroy()

Like any texture, once we are done using the texture with the text it needs to be properly released.

 ttf.Quit()

Time to properly shut down the SDL_tff library.

The glorious multicolored text program

So about those 3 types of text renderings (solid/shaded/blended)…. Typically it goes:

Sold: The best performance but lowest quality, best to use on text that frequently changes such as fps counters

Shaded: Gives nicer anti-aliased text but is slower than Solid and has a box around it

Blended: Best quality, slowest performance.

July 31, 2018 Update: Updated code to deal with breaking changes introduced to Go-SDL2 by version 0.3.