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

Matrix4

4x4 matrices for the usual transform stack, as flat Float[16] in COLUMN-MAJOR order -- the layout GL expects, so no transpose happens on upload. Static functions returning new arrays, following Vector2's style in Game.Framework. Multiply is 'apply rhs, then lhs', so a model-view-projection is Multiply(projection, Multiply(view, model)).

Operations

Identity # function

function : Identity() ~ Float[]

Return

TypeDescription
Floata new identity matrix

IdentityInto # function

Identity, written into a buffer the caller owns. ## Why every builder here has an Into twin A matrix is a Float[16], and the returning forms allocate one per call. At one or two matrices per object per frame that is hundreds of arrays a second, which is enough to trigger a collection mid-frame -- the classic stutter. Objeck's 2D SDL renderer had to be rewritten once for exactly this. Four of these builders were worse than one allocation: Translation, RotationX, RotationY and RotationZ each called Identity() and then overwrote it, so they allocated twice. Now the returning form is the thin one, wrapping the Into form. Reuse one buffer per thing you draw, filled fresh each frame.

function : IdentityInto(out:Float[]) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 16 elements

Inverse # function

Inverse of a 4x4 matrix, by cofactor expansion. Needed for lighting -- the normal matrix is the transposed inverse of the model matrix, and anything but a uniform scale makes the difference visible -- and for turning a view matrix back into a camera position. Returns the identity when the matrix is singular, which is the same choice LookAt makes for a degenerate basis: a wrong-but-usable matrix rather than a crash or a buffer full of infinities.

function : Inverse(m:Float[]) ~ Float[]

Parameters

NameTypeDescription
mFloatthe matrix

Return

TypeDescription
Floatthe inverse, or the identity when there is none

InverseInto # function

Inverse into a caller's buffer.

function : InverseInto(out:Float[], m:Float[]) ~ Bool

Parameters

NameTypeDescription
outFloatdestination, at least 16 elements; must not be m
mFloatthe matrix to invert

Return

TypeDescription
Booltrue when m was invertible; out is the identity when false

LookAt # function

A right-handed view matrix for a camera anywhere, looking anywhere. This is what lets a camera orbit or track a subject rather than sit on the +Z axis. Degenerate input -- eye equal to target, or an up vector parallel to the view direction -- yields identity rather than NaNs.

function : LookAt(eye:Vector3, target:Vector3, up:Vector3) ~ Float[]

Parameters

NameTypeDescription
eyeVector3where the camera is
targetVector3what it looks at
upVector3which way is up, usually (0, 1, 0)

Return

TypeDescription
Floatthe view matrix

LookAtInto # function

LookAt into a caller's buffer, for a camera that is not a Camera -- a light looking at a scene, say. Camera->GetViewMatrixInto is the cheaper route when there is a Camera.

function : LookAtInto(out:Float[], eye:Vector3, target:Vector3, up:Vector3) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 16 elements
eyeVector3where the viewer is
targetVector3what it looks at
upVector3which way is up

LookAtOrigin # function

A view matrix that steps back along +Z and looks at the origin. The smallest useful camera; use LookAt for one that can move.

function : LookAtOrigin(distance:Float) ~ Float[]

Parameters

NameTypeDescription
distanceFloathow far back to stand

Return

TypeDescription
Floatthe matrix

Multiply # function

Matrix product.

function : Multiply(lhs:Float[], rhs:Float[]) ~ Float[]

Parameters

NameTypeDescription
lhsFloatapplied second
rhsFloatapplied first

Return

TypeDescription
Floatlhs * rhs

MultiplyInto # function

Matrix product written into a buffer the caller owns. Exists so a draw loop can avoid allocating. Multiply returns a fresh Float[16] every call, which at one or two matrices per object per frame is hundreds of arrays a second -- enough to trigger a collection mid-frame, the classic stutter. Scene uses this with a scratch buffer it keeps.

function : MultiplyInto(out:Float[], lhs:Float[], rhs:Float[]) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 16 elements; may not alias lhs or rhs
lhsFloatapplied second
rhsFloatapplied first

Orthographic # function

Orthographic projection: no perspective, so distance does not shrink anything. Two uses. A 2D overlay -- a HUD, a minimap -- drawn in pixel coordinates. And, less obviously, a directional light's view of a scene: a light infinitely far away casts parallel rays, so the projection that matches it is parallel too. A shadow map rendered through a perspective projection is wrong in a way that looks like the shadows are being cast by a nearby lamp.

function : Orthographic(left:Float, right:Float, bottom:Float, top:Float, near:Float, far:Float) ~ Float[]

Parameters

NameTypeDescription
leftFloatthe left clip plane
rightFloatthe right clip plane
bottomFloatthe bottom clip plane
topFloatthe top clip plane
nearFloatthe near clip plane; may be negative, unlike Perspective
farFloatthe far clip plane

Return

TypeDescription
Floatthe matrix

OrthographicBox # function

A square orthographic box centred on the origin -- the usual shape for a directional light's view of a scene.

function : OrthographicBox(extent:Float, near:Float, far:Float) ~ Float[]

Parameters

NameTypeDescription
extentFloathalf the width and height covered
nearFloatthe near clip plane
farFloatthe far clip plane

Return

TypeDescription
Floatthe matrix

OrthographicInto # function

Orthographic projection into a caller's buffer.

function : OrthographicInto(out:Float[], left:Float, right:Float, bottom:Float, top:Float, near:Float, far:Float) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 16 elements
leftFloatthe left clip plane
rightFloatthe right clip plane
bottomFloatthe bottom clip plane
topFloatthe top clip plane
nearFloatthe near clip plane
farFloatthe far clip plane

Perspective # function

Right-handed perspective projection, mapping to GL's -1..1 depth range.

function : Perspective(fov_radians:Float, aspect:Float, near:Float, far:Float) ~ Float[]

Parameters

NameTypeDescription
fov_radiansFloatvertical field of view
aspectFloatwidth divided by height
nearFloatnear clip distance; must be greater than 0
farFloatfar clip distance

Return

TypeDescription
Floatthe matrix

PerspectiveDegrees # function

Perspective with the field of view in DEGREES, and sensible clip planes. Both call sites spelled the fov as a bare expression -- Float->Pi() / 4.0 and Float->Pi() / 3.0 -- and repeated the same 0.1 and 100.0 clip distances verbatim. Degrees are how field of view is actually discussed; 60 is a normal first-person view, 45 a slightly telephoto one.

function : PerspectiveDegrees(fov_degrees:Float, aspect:Float) ~ Float[]

Parameters

NameTypeDescription
fov_degreesFloatvertical field of view in degrees
aspectFloatwidth divided by height, as GLWindow->GetAspect reports

Return

TypeDescription
Floatthe matrix

PerspectiveDegrees # function

Perspective in degrees with explicit clip planes.

function : PerspectiveDegrees(fov_degrees:Float, aspect:Float, near:Float, far:Float) ~ Float[]

Parameters

NameTypeDescription
fov_degreesFloatvertical field of view in degrees
aspectFloatwidth divided by height
nearFloatnear clip distance; must be greater than 0
farFloatfar clip distance

Return

TypeDescription
Floatthe matrix

PerspectiveInto # function

Perspective into a caller's buffer. See IdentityInto. Note this zeroes the destination first, so unlike the other Into forms it must NOT be handed a buffer that already holds something wanted -- a projection matrix is mostly zeroes, and leaving a previous matrix's values in the untouched slots would produce nonsense rather than a projection.

function : PerspectiveInto(out:Float[], fov_radians:Float, aspect:Float, near:Float, far:Float) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 16 elements
fov_radiansFloatvertical field of view
aspectFloatwidth divided by height
nearFloatnear clip distance; must be greater than 0
farFloatfar clip distance

RotationX # function

Rotation about the X axis.

function : RotationX(radians:Float) ~ Float[]

Parameters

NameTypeDescription
radiansFloatangle in radians

Return

TypeDescription
Floatthe matrix

RotationXInto # function

Rotation about X, into a caller's buffer. See IdentityInto.

function : RotationXInto(out:Float[], radians:Float) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 16 elements
radiansFloatangle in radians

RotationY # function

Rotation about the Y axis.

function : RotationY(radians:Float) ~ Float[]

Parameters

NameTypeDescription
radiansFloatangle in radians

Return

TypeDescription
Floatthe matrix

RotationYInto # function

Rotation about Y, into a caller's buffer. See IdentityInto.

function : RotationYInto(out:Float[], radians:Float) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 16 elements
radiansFloatangle in radians

RotationYXZ # function

Yaw, pitch and roll, allocating.

function : RotationYXZ(yaw:Float, pitch:Float, roll:Float) ~ Float[]

Parameters

NameTypeDescription
yawFloatrotation about Y, applied first
pitchFloatrotation about X
rollFloatrotation about Z, applied last

Return

TypeDescription
Floatthe matrix

RotationYXZInto # function

Yaw, then pitch, then roll -- the same result as RotationY * RotationX * RotationZ, computed directly. Spelling that composition out costs six allocations (three builders, each of which allocated twice) plus two matrix products, per object per frame. This is nine multiplies and no allocation. The derivation is verified against the composition in the regression suite, because a transposed or sign-flipped term here produces a rotation that looks plausible until something is mirrored.

function : RotationYXZInto(out:Float[], yaw:Float, pitch:Float, roll:Float) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 16 elements
yawFloatrotation about Y, applied first
pitchFloatrotation about X
rollFloatrotation about Z, applied last

RotationZ # function

Rotation about the Z axis.

function : RotationZ(radians:Float) ~ Float[]

Parameters

NameTypeDescription
radiansFloatangle in radians

Return

TypeDescription
Floatthe matrix

RotationZInto # function

Rotation about Z, into a caller's buffer. See IdentityInto.

function : RotationZInto(out:Float[], radians:Float) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 16 elements
radiansFloatangle in radians

Scale # function

Uniform scale matrix.

function : Scale(s:Float) ~ Float[]

Parameters

NameTypeDescription
sFloatscale factor

Return

TypeDescription
Floatthe matrix

ScaleXYZ # function

Non-uniform scale. This is what lets one unit-cube mesh become every box in a scene -- walls, floors and obstacles -- instead of uploading a separate mesh per shape.

function : ScaleXYZ(x:Float, y:Float, z:Float) ~ Float[]

Parameters

NameTypeDescription
xFloatscale along x
yFloatscale along y
zFloatscale along z

Return

TypeDescription
Floatthe matrix

ScaleXYZInto # function

Non-uniform scale, into a caller's buffer. See IdentityInto.

function : ScaleXYZInto(out:Float[], x:Float, y:Float, z:Float) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 16 elements
xFloatscale along x
yFloatscale along y
zFloatscale along z

Translation # function

Translation matrix.

function : Translation(x:Float, y:Float, z:Float) ~ Float[]

Parameters

NameTypeDescription
xFloatx offset
yFloaty offset
zFloatz offset

Return

TypeDescription
Floatthe matrix

TranslationInto # function

Translation, into a caller's buffer. See IdentityInto.

function : TranslationInto(out:Float[], x:Float, y:Float, z:Float) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 16 elements
xFloatx offset
yFloaty offset
zFloatz offset

TranslationScale # function

Translate and scale, allocating.

function : TranslationScale(x:Float, y:Float, z:Float, sx:Float, sy:Float, sz:Float) ~ Float[]

Parameters

NameTypeDescription
xFloatx offset
yFloaty offset
zFloatz offset
sxFloatscale along x
syFloatscale along y
szFloatscale along z

Return

TypeDescription
Floatthe matrix

TranslationScaleInto # function

Translate and scale in one step, with no multiply at all. This is the whole of a Box's transform -- position plus half-extents -- and it is the single most common model matrix there is. Composing it as Multiply(Translation(...), ScaleXYZ(...)) costs two allocations and sixty-four multiply-adds to produce a matrix whose scale is its diagonal and whose translation is its last column. So write those directly.

function : TranslationScaleInto(out:Float[], x:Float, y:Float, z:Float, sx:Float, sy:Float, sz:Float) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 16 elements
xFloatx offset
yFloaty offset
zFloatz offset
sxFloatscale along x
syFloatscale along y
szFloatscale along z

Transpose # function

Transpose: rows become columns.

function : Transpose(m:Float[]) ~ Float[]

Parameters

NameTypeDescription
mFloatthe matrix

Return

TypeDescription
Floatthe transpose

TransposeInto # function

Transpose into a caller's buffer.

function : TransposeInto(out:Float[], m:Float[]) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 16 elements; must not be m
mFloatthe matrix to transpose