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

LightRig

Several lights at once, plus the ambient they all sit on top of. One directional light is enough to see a scene; anything with a lamp, a fire, a torch or a window needs more, and this is how a forward renderer carries them -- as arrays with a count, written once per frame. ## The limit is fixed, and low on purpose GLSL needs a compile-time array size, so the maximum is a constant rather than whatever you add. Eight, because a forward renderer evaluates EVERY light for EVERY fragment -- the cost is lights times pixels, and this is the point where a deferred renderer would be the answer instead. Lights past the limit are ignored rather than silently replacing others. ## The eye position Specular highlights depend on where the viewer is, so SetViewPosition has to be told each frame if the camera moves. Forget it and the highlights sit still while the camera orbits, which looks like the material is wrong rather than like a missing call.

Example

rig := LightRig->New();
rig->Add(Light->Directional(Vector3->New(-0.3, 1.0, 0.4)));
rig->Add(Light->Point(Vector3->New(2.0, 1.5, 0.0), 8.0));
rig->SetAmbient(0.18, 0.19, 0.24);

# once a frame, before drawing
rig->SetViewPosition(camera->GetPosition());
rig->ApplyTo(shader);

Operations

Add #

Add a light. Beyond GetMaxLights it is ignored.

method : public : Add(light:Light) ~ Bool

Parameters

NameTypeDescription
lightLightthe light

Return

TypeDescription
Booltrue when it was taken

ApplyTo #

Write the whole rig into a shader. Once a frame, not once per object: none of it varies between objects. Uniforms the shader does not declare are skipped, so an unlit program can be handed this without filing a diagnostic.

method : public : ApplyTo(shader:Shader) ~ Nil

Parameters

NameTypeDescription
shaderShaderthe program about to draw

Clear #

Remove every light. Ambient and the material settings are kept.

method : public : Clear() ~ Nil

Get #

method : public : Get(index:Int) ~ Light

Parameters

NameTypeDescription
indexIntwhich light

Return

TypeDescription
Lightthe light, or Nil when out of range

GetCount #

method : public : GetCount() ~ Int

Return

TypeDescription
Inthow many lights are in the rig

GetMaxLights # function

How many lights a shader built by this bundle can take.

function : GetMaxLights() ~ Int

Return

TypeDescription
Intthe maximum

GetShininess #

method : public : GetShininess() ~ Float

Return

TypeDescription
Floatthe scene-wide shininess, so a Material that overrode it can be put back rather than guessed at

GetSpecular #

method : public : GetSpecular() ~ Float

Return

TypeDescription
Floatthe scene-wide specular strength

SetAmbient #

The light every surface gets regardless of which way it faces.

method : public : SetAmbient(r:Float, g:Float, b:Float) ~ Nil

Parameters

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

SetViewPosition #

Where the viewer is, which specular highlights depend on. Set it each frame if the camera moves.

method : public : SetViewPosition(position:Vector3) ~ Nil

Parameters

NameTypeDescription
positionVector3the eye position in world space