Overlay
Text and flat rectangles drawn on top of a 3D scene, positioned in pixels. The gap this fills is not subtle: there was no way to draw text at all. A frame-rate counter, a label on an object, a score, a "press escape to quit" -- none of it was reachable, in a 3D framework, from a language whose SDL2 bindings have had TrueType support all along. ## Pixels, from the top-left Positions are in DRAWABLE pixels with the origin at the TOP-LEFT, because that is where everyone puts a HUD and it is how every 2D API in the world reads. GL has its origin at the bottom-left, so the flip happens here rather than in every caller's arithmetic. On a high-DPI display the drawable is bigger than the window, so a HUD placed at a fixed pixel offset sits closer to the corner than on a 1x screen. Use GetScale to size text and margins if that matters. ## Why the text cache exists Turning a string into a texture means rasterising glyphs with TTF, allocating a surface, and uploading it to the GPU. Doing that per frame for a counter that says "60 fps" is enough work to be visible in a profile, and the string only changes when the number does. So a texture is kept per string and reused; a frame that draws the same text as the last one uploads nothing. The cache is bounded, because a caller drawing a timestamp every frame would otherwise grow it without limit. When it fills, it is cleared rather than evicted one entry at a time -- overlay text is a handful of strings in practice, and a clear is one line rather than an LRU.
Example
overlay := Overlay->New(window);
font := Font->New("lazy.ttf", 18);
overlay->SetFont(font);
# per frame, after the scene
overlay->Begin();
overlay->Text("Hello", 12, 12);
overlay->End();Operations
- New
- Begin
- ClearCache
- End
- Free
- GetError
- GetScale
- HasFont
- Image
- IsOk
- Line
- MeasureWidth
- OwnFont
- Rasterize
- Rect
- Resize
- SetColor
- SetFont
- SetTint
- SetTintRgb
- Text
- WithDefaultFont
Begin #
Start drawing overlay content. Turns depth testing off and alpha blending on, because a HUD belongs on top of everything regardless of where it is in space, and glyph edges are antialiased into their alpha channel. End puts both back.
method : public : Begin() ~ NilFree #
Release the overlay's own GL objects, and a font it loaded ITSELF through WithDefaultFont. A font handed in by SetFont is not freed -- that one belongs to the caller and may be shared with another overlay. Call it while the GL context is still current -- see the shutdown order on the GL class.
method : public : Free() ~ NilGetError #
method : public : GetError() ~ StringReturn
| Type | Description |
|---|---|
| String | why it cannot draw, or an empty string |
GetScale #
How much bigger the drawable is than the window, for sizing a HUD on a high-DPI display.
method : public : GetScale() ~ FloatReturn
| Type | Description |
|---|---|
| Float | 1.0 on an ordinary screen, 2.0 on a typical Retina one |
HasFont #
method : public : HasFont() ~ BoolReturn
| Type | Description |
|---|---|
| Bool | true when a font is set and text will actually draw |
Image #
Draw a texture as a rectangle, in pixels. The missing verb. This overlay could draw text, filled rectangles and lines, and the one thing an actual HUD is mostly made of -- an icon, a portrait, a minimap, a health bar with a texture rather than a colour -- had no route, even though the private DrawTextured that Text and Rect both call has been here since the overlay was written. The current tint MULTIPLIES the texture, exactly as it does for text, so SetTint(1.0, 1.0, 1.0, 1.0) draws it untouched and a lower alpha fades it.
method : public : Image(texture:Texture2D, x:Int, y:Int, width:Int, height:Int) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| texture | Texture2D | what to draw; ignored when Nil or not ok |
| x | Int | left edge in pixels, measured from the left |
| y | Int | top edge in pixels, measured from the TOP |
| width | Int | how wide to draw it |
| height | Int | how tall to draw it There is deliberately no form that omits the size. Texture2D holds a GL handle and an ownership flag and does NOT know its own dimensions -- and for one made by Wrapping or Adopt, around a handle something else created, it cannot. Giving it a size means every construction path recording one and the borrowed case admitting it has none, which is a change of its own. |
Line #
A straight line, one pixel of thickness per unit. Horizontal and vertical only -- it is a rectangle underneath. An arbitrary diagonal needs a rotation this class does not carry, and a HUD rule, a divider and a bar chart are all axis-aligned.
method : public : Line(x1:Int, y1:Int, x2:Int, y2:Int, thickness:Int) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| x1 | Int | start x |
| y1 | Int | start y |
| x2 | Int | end x |
| y2 | Int | end y |
| thickness | Int | in pixels, at least 1 |
MeasureWidth #
How wide and tall a string would be, without drawing it. For centring or right-aligning.
method : public : MeasureWidth(text:String) ~ IntParameters
| Name | Type | Description |
|---|---|---|
| text | String | the string to measure |
Return
| Type | Description |
|---|---|
| Int | its width in pixels, or 0 when there is no font |
New # constructor
An overlay sized to a window's drawable.
New(window:GLWindow)Parameters
| Name | Type | Description |
|---|---|---|
| window | GLWindow | the window it draws over |
OwnFont #
Take ownership of a font, so Free closes it. SetFont deliberately does NOT own what it is given -- a program may share one font between overlays. WithDefaultFont loaded its own, so it says so.
method : public : OwnFont(font:Font) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| font | Font | the font this overlay should close |
Rasterize #
The string's texture, rasterising it only if it is not already cached.
method : private : Rasterize() ~ Texture2DParameters
| Name | Type | Description |
|---|---|---|
Rect #
A filled rectangle -- a panel behind text, a health bar, a crosshair. Uses the overlay's own one-pixel white texture, tinted by the shader, so it costs no extra state change between this and a Text call.
method : public : Rect(x:Int, y:Int, width:Int, height:Int) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| x | Int | pixels from the left |
| y | Int | pixels from the top |
| width | Int | in pixels |
| height | Int | in pixels |
Rect #
A filled rectangle in the current tint.
method : public : Rect(x:Int, y:Int, width:Int, height:Int, r:Int, g:Int, b:Int) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| x | Int | left edge in pixels |
| y | Int | top edge in pixels |
| width | Int | width in pixels |
| height | Int | height in pixels |
| r | Int | red, 0..255 |
| g | Int | green, 0..255 |
| b | Int | blue, 0..255 |
Resize #
Recompute the pixel projection. Call after the window's drawable changes size; Begin does it anyway, so this is rarely needed directly.
method : public : Resize() ~ NilSetColor #
The colour text is drawn in. Changing it invalidates the cache, since the colour is baked into each rasterised texture.
method : public : SetColor(r:Int, g:Int, b:Int) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| r | Int | red, 0-255 |
| g | Int | green, 0-255 |
| b | Int | blue, 0-255 |
SetFont #
The font used by Text. Not owned -- the caller loads it and frees it.
method : public : SetFont(font:Font) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| font | Font | a loaded Font |
SetTint #
Colour for the next Rect, Line and Text, as 0..1 components. This does NOT re-rasterise anything: it multiplies in the shader, so a program can change colour per call at no cost. SetColor is the other one and is not interchangeable -- that bakes a colour into the glyph textures and throws the cache away, so two SetColor calls a frame re-rasterise every string on screen twice a frame. Rule of thumb: SetColor once at startup for the text's base colour, SetTint freely for anything that changes.
method : public : SetTint(r:Float, g:Float, b:Float, a:Float) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| r | Float | red, 0..1 |
| g | Float | green, 0..1 |
| b | Float | blue, 0..1 |
| a | Float | alpha, 0..1 |
SetTintRgb #
Opaque tint from 0-255 components, matching SetColor's units.
method : public : SetTintRgb(r:Int, g:Int, b:Int) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| r | Int | red, 0..255 |
| g | Int | green, 0..255 |
| b | Int | blue, 0..255 |
Text #
Draw a string with its top-left corner at (x, y) in pixels.
method : public : Text(text:String, x:Int, y:Int) ~ IntParameters
| Name | Type | Description |
|---|---|---|
| text | String | what to draw |
| x | Int | pixels from the left |
| y | Int | pixels from the top |
Return
| Type | Description |
|---|---|
| Int | the width the text occupied, so the next one can follow it |
WithDefaultFont # function
An overlay using the font that ships with Objeck. Three examples had ten identical lines of this -- Font->Init, a hardcoded "../lib/sdl/fonts/lazy.ttf", an IsOk-and-not-null check, SetFont, SetColor, and a Nil on failure that every draw site then had to test for. That path is relative, and it only resolves when the program is started from the deploy tree's bin directory. Run the same program from the examples directory it ships in and the font is silently missing. This tries the places it actually lives, so a program works from either. The overlay is returned whether or not a font was found -- Text is a no-op without one, so a missing font costs the HUD rather than the program.
function : WithDefaultFont(window:GLWindow, size:Int) ~ OverlayParameters
| Name | Type | Description |
|---|---|---|
| window | GLWindow | the window to draw over |
| size | Int | point size |
Return
| Type | Description |
|---|---|
| Overlay | the overlay; check IsOk() for the GL side, HasFont() for the text |