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).

Texture2D

A 2D texture. Built from an SDL Surface, so image decoding stays SDL's job -- Game.SDL2's Image->Load already reads PNG and JPEG, and this adds no new dependency.

Implements: Freeable

Example

surface := Image->Load("crate.png");
texture := Texture2D->New(surface);
surface->Free();          # the pixels are on the GPU now

Operations

Bind #

Bind to a texture unit, to be read by a sampler uniform set to the same unit number.

method : public : Bind(unit:Int) ~ Nil

Parameters

NameTypeDescription
unitIntthe texture unit, 0 for the first

Checker # function

A checkerboard, generated rather than loaded, so a program needs no asset file to have something recognisable on a surface.

function : Checker(size:Int, squares:Int, light:Int, dark:Int) ~ Texture2D

Parameters

NameTypeDescription
sizeIntthe texture's width and height in pixels
squaresIntsquares per side
lightIntpacked 0xAARRGGBB for one colour
darkIntpacked 0xAARRGGBB for the other

Return

TypeDescription
Texture2Dthe texture; check IsOk()

Example

floor := Texture2D->Checker(64, 8, 0xFFE8E8F0, 0xFF465A82);

Checker # function

A checkerboard with an explicit wrap mode -- pass TextureWrap->GL_REPEAT to tile it across a Mesh->Plane.

function : Checker(size:Int, squares:Int, light:Int, dark:Int, wrap:Int) ~ Texture2D

Parameters

NameTypeDescription
sizeIntthe texture's width and height in pixels
squaresIntsquares per side
lightIntpacked 0xAARRGGBB for one colour
darkIntpacked 0xAARRGGBB for the other
wrapInta TextureWrap

Return

TypeDescription
Texture2Dthe texture; check IsOk()

Free #

Release the texture. Call it while the GL context is still current -- see the shutdown order on the GL class. A texture obtained from RenderTarget->GetTexture or ShadowMap->GetTexture is a BORROWED view of that target's attachment, not a texture of its own, and this does nothing for one. Freeing it used to delete the attachment out from under the target that was still using it.

method : public : Free() ~ Nil

FromFile # function

Load an image file and upload it, freeing the intermediate surface. Decoding is SDL's job -- Game.SDL2's Image->Load already reads PNG and JPEG, so this adds no dependency. Rows are flipped on upload, so the texture is the same way up as the file.

function : FromFile(file:String) ~ Texture2D

Parameters

NameTypeDescription
fileStringthe image path

Return

TypeDescription
Texture2Dthe texture; IsOk() is false when the file could not be read

FromFile # function

Load an image file with explicit sampling.

function : FromFile(file:String, filter:Int, wrap:Int, mipmap:Bool) ~ Texture2D

Parameters

NameTypeDescription
fileStringthe image path
filterInta TextureFilter
wrapInta TextureWrap
mipmapBooltrue to build a mipmap chain

Return

TypeDescription
Texture2Dthe texture; IsOk() is false when the file could not be read

IsOk #

method : public : IsOk() ~ Bool

Return

TypeDescription
Booltrue when the texture uploaded

New # constructor

Upload a surface's pixels as a texture, smoothed and clamped. The surface is converted to 32-bit RGBA internally, so any format Image->Load produces works. The PIXELS ARE COPIED, so the surface can be freed immediately afterwards -- and should be. Every example in this repo used to hold its surfaces alive until shutdown for no reason.

New(surface:Surface)

Parameters

NameTypeDescription
surfaceSurfacethe source surface; still owned by the caller

New # constructor

Upload with explicit sampling.

New(surface:Surface, filter:Int, wrap:Int, mipmap:Bool)

Parameters

NameTypeDescription
surfaceSurfacethe source surface; still owned by the caller
filterIntTextureFilter->GL_LINEAR or GL_NEAREST
wrapInta TextureWrap; GL_REPEAT to tile
mipmapBooltrue to build a mipmap chain, which is what stops a tiled floor from shimmering into noise in the distance

New # constructor

Upload with anisotropic filtering. The fix for a tiled floor that turns to mush in the distance. Anisotropy chooses better mip SAMPLES for a surface seen at a steep angle, which is exactly the case ordinary mipmapping handles worst -- so it needs `mipmap` true to do anything at all, and does nothing visible on a HUD quad seen face-on. Extension-gated, and asked for rather than assumed: a driver without GL_EXT_texture_filter_anisotropic ignores it and a level above what the hardware offers is clamped to the maximum. Check GL->GetMaxAnisotropy() first if you care which happened. 4 or 8 is the usual choice; 16 is the common ceiling.

New(surface:Surface, filter:Int, wrap:Int, mipmap:Bool, anisotropy:Float)

Parameters

NameTypeDescription
surfaceSurfacethe source surface; still owned by the caller
filterIntTextureFilter->GL_LINEAR or GL_NEAREST
wrapInta TextureWrap
mipmapBoolmust be true for anisotropy to have any effect
anisotropyFloat1.0 for none, or 2, 4, 8, 16

NewSurface # function

A blank 32-bit surface with the ARGB channel layout FillRect's packed Int assumes: 0xAARRGGBB. The four masks are the thing worth having in one place. They appeared verbatim in three separate files, each next to its own hand-written bit-shifting helper that had to agree with them.

function : NewSurface(width:Int, height:Int) ~ Surface

Parameters

NameTypeDescription
widthIntin pixels
heightIntin pixels

Return

TypeDescription
Surfacethe surface, or Nil when SDL could not make one

Rgb # function

Pack 8-bit components into the 0xAARRGGBB layout NewSurface declares.

function : Rgb(r:Int, g:Int, b:Int) ~ Int

Parameters

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

Return

TypeDescription
Intthe packed colour, fully opaque

Solid # function

A single-colour texture, one pixel. For an untextured surface that still goes through a textured shader, which is cheaper than maintaining a second shader that does not sample.

function : Solid(argb:Int) ~ Texture2D

Parameters

NameTypeDescription
argbIntpacked 0xAARRGGBB

Return

TypeDescription
Texture2Dthe texture; check IsOk()

Wrapping # function

Wrap a GL texture name this object does not own. For a texture created elsewhere -- a RenderTarget's colour attachment -- so it can be bound and sampled through the usual Texture2D API. Free() on the result would delete a texture something else is still using, so whatever created it is responsible for releasing it.

function : Wrapping(handle:Int) ~ Texture2D

Parameters

NameTypeDescription
handleInta GL texture name

Return

TypeDescription
Texture2Da Texture2D over it