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

RenderTarget

A framebuffer you draw into instead of the window, and then sample. Render-to-texture, which is the door to most of what comes after simple forward rendering: a post-process pass, a shadow map, a reflection, a mirror, a minimap. ## The viewport goes with the target Bind sets the viewport to the target's size and Unbind restores the window's, because getting that wrong is the classic render-to-texture bug: a 512x512 target still rendering through a 1280x720 viewport draws a quarter of the scene into one corner, with no GL error anywhere. Unbind needs the window in order to know what to restore to -- which is why it takes one. ## Colour is a texture, depth is not Colour goes to a texture because sampling the result is the entire point. Depth goes to a renderbuffer, which is the cheaper option for a buffer that is written and never read. A shadow map wants the reverse, and will need its own constructor for that.

Implements: Freeable

Example

target := RenderTarget->New(512, 512);

target->Bind();                      # draw into the texture
GL->Clear(ClearBit->GL_COLOR_BUFFER_BIT or ClearBit->GL_DEPTH_BUFFER_BIT);
scene->Draw(view_projection);

target->Unbind(window);              # back to the window
post->Use();
post->SetInt("tex", 0);
target->GetTexture()->Bind(0);
quad->Draw();

Operations

Bind #

Draw into this target from here on. Sets the viewport to match.

method : public : Bind() ~ Nil

BindAsCube #

Bind a cube target's texture for sampling. Separate from Texture2D->Bind because the target enum differs: a cube map bound as GL_TEXTURE_2D leaves the sampler reading nothing at all, with no error.

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

Parameters

NameTypeDescription
unitIntthe texture unit

BindFace #

Draw into ONE face of a cube target, and set the viewport to match.

method : public : BindFace(face:Int) ~ Nil

Parameters

NameTypeDescription
faceInt0..5, in GL's order: +X, -X, +Y, -Y, +Z, -Z

Depth # function

A DEPTH-ONLY target: no colour buffer at all, and the depth is a texture that can be sampled. The reverse of the usual arrangement, and what a shadow map needs -- depth read, colour never. ShadowMap wraps this along with everything else the technique requires; reach for it directly only for something ShadowMap does not cover.

function : Depth(size:Int) ~ RenderTarget

Parameters

NameTypeDescription
sizeIntthe map's width and height; powers of two are conventional

Return

TypeDescription
RenderTargetthe target; check IsOk()

DepthCube # function

A CUBE depth target: six square depth faces under one texture name. What a point light needs. A point light shines in every direction, so no single flat map can hold its shadows -- the scene is rendered six times, once down each axis, and sampled by DIRECTION rather than by a projected coordinate. PointShadow wraps all of that; reach for this directly only for something PointShadow does not cover.

function : DepthCube(size:Int) ~ RenderTarget

Parameters

NameTypeDescription
sizeIntthe width and height of each face

Return

TypeDescription
RenderTargetthe target; check IsOk()

Free #

Release the framebuffer, its colour texture and its depth buffer. Call it while the GL context is still current -- see the shutdown order on the GL class.

method : public : Free() ~ Nil

GetAspect #

method : public : GetAspect() ~ Float

Return

TypeDescription
Floatwidth over height, for a projection matched to this target

GetError #

Why creation failed -- an incomplete framebuffer, a size the driver refuses, or the entry points not being loaded.

method : public : GetError() ~ String

Return

TypeDescription
Stringthe reason, or an empty string

GetTexture #

What was drawn, as a texture to bind and sample. The same object every call, and it does NOT own the underlying texture -- this target does. Freeing the returned Texture2D would delete a texture the framebuffer is still attached to, so do not; free the target instead.

method : public : GetTexture() ~ Texture2D

Return

TypeDescription
Texture2Dthe colour texture, or Nil when this target is not ok

IsCube #

method : public : IsCube() ~ Bool

Return

TypeDescription
Booltrue when this target is a cube map rather than a flat one

IsOk #

method : public : IsOk() ~ Bool

Return

TypeDescription
Booltrue when the framebuffer was created AND reported complete

New # constructor

A target of the given size, smoothly sampled.

New(width:Int, height:Int)

Parameters

NameTypeDescription
widthIntin pixels
heightIntin pixels

New # constructor

A target with an explicit sampling filter. Use GL_NEAREST when the result is read exactly rather than scaled -- a deliberately low-resolution pass, or data rather than a picture.

New(width:Int, height:Int, filter:Int)

Parameters

NameTypeDescription
widthIntin pixels
heightIntin pixels
filterInta TextureFilter

Unbind #

Draw into the window again, restoring its viewport.

method : public : Unbind(window:GLWindow) ~ Nil

Parameters

NameTypeDescription
windowGLWindowthe window to go back to