v2026.9.4
All Bundles
Bundle OpenGL 3.3 core rendering on top of SDL2. Builds on sdl2.obl for the window and context; this bundle is the GL side. Compile with -lib sdl2. Targets the **3.3 core profile**, forward-compatible. That is the highest common denominator across the platforms Objeck ships: Windows, Linux and macOS desktop all reach it, and macOS caps at 4.1 core so nothing above 4.1 is portable there. GLES-only hardware (Raspberry Pi and similar) is out of scope by construction. Layers, from thinnest to friendliest -- all public, so nothing is capped: * GLWindow -- a window and a 3.3 core context, opened and paced for you. * GL -- static state operations. Thin, but never per-vertex. * Shader -- a linked program; Flat/Textured/TexturedFog/Normals built in, so the common cases need no GLSL at all. * Mesh -- geometry uploaded once into buffer objects, drawn many times, with Cube/Plane/Quad/Sphere built in, OBJ loading, and instancing. * Texture2D -- a texture, from a file or generated; Solid/Checker built in. * Light -- one directional light plus ambient, written into a shader. * RenderTarget -- draw into a texture instead of the window, then sample it. * Material -- a surface: its texture, tint and shininess. * Frustum -- the six planes of a view, for skipping what is off screen. * ShadowMap -- shadows from a directional or spot light, depth pass and all. * PointShadow -- shadows from a point light, in every direction, via a cube map. * Overlay -- text and rectangles over the scene, positioned in pixels. * Transform -- position, rotation and scale, with a cached matrix. * Box, Scene -- a world of boxes: draws itself, and answers collision. ## Why the API is shaped this way The native call boundary is expensive: the VM resolves each native symbol by string on EVERY call (GetProcAddress/dlsym) and boxes every argument into a fresh holder. A 1:1 mapping of OpenGL onto that would be thousands of lookups and allocations per frame. So each call here does real work -- 'compile a program from two sources' is one call, not the five GL calls it decomposes into -- and bulk data crosses as whole arrays, never element by element. This happens to be exactly what GL 3.3 core wants anyway: upload geometry once, then draw with few calls. ## Adding a call One `void fn(VMContext&)` in the OpenGL section of core/lib/sdl/sdl.cpp, and one method here that names it. See that file's header for the two rules that matter (positional slot indices, and keeping each call coarse).

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.

Implements: Freeable

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

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() ~ Nil

ClearCache #

Drop every cached string texture.

method : public : ClearCache() ~ Nil

End #

Finish drawing overlay content and restore the 3D state.

method : public : End() ~ Nil

Free #

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() ~ Nil

GetError #

method : public : GetError() ~ String

Return

TypeDescription
Stringwhy 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() ~ Float

Return

TypeDescription
Float1.0 on an ordinary screen, 2.0 on a typical Retina one

HasFont #

method : public : HasFont() ~ Bool

Return

TypeDescription
Booltrue 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) ~ Nil

Parameters

NameTypeDescription
textureTexture2Dwhat to draw; ignored when Nil or not ok
xIntleft edge in pixels, measured from the left
yInttop edge in pixels, measured from the TOP
widthInthow wide to draw it
heightInthow 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.

IsOk #

method : public : IsOk() ~ Bool

Return

TypeDescription
Booltrue when the overlay can draw

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) ~ Nil

Parameters

NameTypeDescription
x1Intstart x
y1Intstart y
x2Intend x
y2Intend y
thicknessIntin 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) ~ Int

Parameters

NameTypeDescription
textStringthe string to measure

Return

TypeDescription
Intits width in pixels, or 0 when there is no font

New # constructor

An overlay sized to a window's drawable.

New(window:GLWindow)

Parameters

NameTypeDescription
windowGLWindowthe 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) ~ Nil

Parameters

NameTypeDescription
fontFontthe font this overlay should close

Rasterize #

The string's texture, rasterising it only if it is not already cached.

method : private : Rasterize() ~ Texture2D

Parameters

NameTypeDescription

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) ~ Nil

Parameters

NameTypeDescription
xIntpixels from the left
yIntpixels from the top
widthIntin pixels
heightIntin 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) ~ Nil

Parameters

NameTypeDescription
xIntleft edge in pixels
yInttop edge in pixels
widthIntwidth in pixels
heightIntheight in pixels
rIntred, 0..255
gIntgreen, 0..255
bIntblue, 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() ~ Nil

SetColor #

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) ~ Nil

Parameters

NameTypeDescription
rIntred, 0-255
gIntgreen, 0-255
bIntblue, 0-255

SetFont #

The font used by Text. Not owned -- the caller loads it and frees it.

method : public : SetFont(font:Font) ~ Nil

Parameters

NameTypeDescription
fontFonta 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) ~ Nil

Parameters

NameTypeDescription
rFloatred, 0..1
gFloatgreen, 0..1
bFloatblue, 0..1
aFloatalpha, 0..1

SetTintRgb #

Opaque tint from 0-255 components, matching SetColor's units.

method : public : SetTintRgb(r:Int, g:Int, b:Int) ~ Nil

Parameters

NameTypeDescription
rIntred, 0..255
gIntgreen, 0..255
bIntblue, 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) ~ Int

Parameters

NameTypeDescription
textStringwhat to draw
xIntpixels from the left
yIntpixels from the top

Return

TypeDescription
Intthe 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) ~ Overlay

Parameters

NameTypeDescription
windowGLWindowthe window to draw over
sizeIntpoint size

Return

TypeDescription
Overlaythe overlay; check IsOk() for the GL side, HasFont() for the text