Search by

jovian / ogx

projectsaturnstudios

OpenGL, EGL and CGL bindings for The PHP OpenGL Extension

0.8.x-dev 2026-09-17 17:34 UTC

This package is auto-updated.

Last update: 2026-09-17 17:34:52 UTC


README

OpenGL, typed in PHP. ext-opengl 0.8.0 projected 1:1 into typed classes, plus the enums the extension deliberately withholds.

use Jovian\Bindings\OpenGL\Enums\GL\PrimitiveType;
use Jovian\Bindings\OpenGL\Enums\GL\StringName;
use Jovian\Bindings\OpenGL\GL\GL10;
use Jovian\Bindings\OpenGL\GL\GL11;

echo GL10::glGetString(StringName::VERSION);   // "4.1 Metal - 89.4"
GL11::glDrawArrays(PrimitiveType::TRIANGLES, 0, 3);

Before this package existed you wrote that as

const GL_VERSION = 0x1F02;     // glcorearb.h:232
const GL_TRIANGLES = 0x0004;   // glcorearb.h:81

echo OpenGL\GL\GL10\GL10::glGetString(GL_VERSION);
OpenGL\GL\GL11\GL11::glDrawArrays(GL_TRIANGLES, 0, 3);

— which is exactly what ext-opengl's own proof still does, with a header:line citation on all 41 of its literals.

What it is

Surface 581 methods — 573 generated + 8 hand-written Bridge calls, the extension's own count
Classes GL10 .. GL41, EGL, CGL (16), namespace root Jovian\Bindings\OpenGL\
Enums 98, int-backed, 1474 cases, 15 documented aliases
Typed 310 enum parameters, 39 enum returns; bitfields stay int
Platforms macOS and Linux, same package, same enums
Requires PHP ^8.4, ext-opengl ^0.8.0

What it is not

It adds types and enums, and nothing else. One PHP method is exactly one extension call, with the same arguments in the same order:

public static function glDrawArrays(PrimitiveType|int $mode, int $first, int $count): void
{
    ExtGL11::glDrawArrays($mode instanceof \BackedEnum ? $mode->value : $mode, $first, $count);
}

There is no convenience layer here. No createShader($type, $source) that compiles and links; no glGetIntegerv that allocates a buffer, reads it and returns an array; no object that owns a pointer's lifetime. All of that is venusian-ogx, one layer up.

Install

composer require jovian/ogx

You need ext-opengl built and loaded. On a Mac with Herd, non-interactive shells need the scan dir:

export HERD_PHP_84_INI_SCAN_DIR=$(zsh -ic 'echo $HERD_PHP_84_INI_SCAN_DIR')

Three things to know before you write any

1. An enum parameter accepts the enum or an int. Every enum-typed slot is SomeEnum|int, so a raw constant still works and a migration can be gradual.

2. A bitfield stays int. PHP cannot OR two enum cases, so a mask is built from ->value:

GL10::glClear(ClearBufferMask::COLOR_BUFFER_BIT->value | ClearBufferMask::DEPTH_BUFFER_BIT->value);

3. An enum-typed return is documentation and hands back an int. Converting would be behaviour, and this layer adds none:

$error = GL10::glGetError();       // int, declared ErrorCode|int
ErrorCode::tryFrom($error);        // ← the idiom
$error === ErrorCode::NO_ERROR;    // ← always false

Pointers are still pointers

ext-opengl crosses every non-string pointer as raw pointer bits in an int, and this package does not change that. Runtime\Bridge gives you the memory; pack() and unpack() give you the typing:

use Jovian\Bindings\OpenGL\Runtime\Bridge;
use Jovian\Bindings\OpenGL\Enums\GL\BufferTargetARB;
use Jovian\Bindings\OpenGL\GL\GL15;

$out = Bridge::alloc(4);
GL15::glGenBuffers(1, $out);
$vbo = unpack('l', (string) Bridge::read($out, 0, 4))[1];
GL15::glBindBuffer(BufferTargetARB::ARRAY_BUFFER, $vbo);
Bridge::free($out);

No count argument is ever invented and none is ever dropped: the PHP signature is the C prototype.

Where the enums come from

glcorearb.h is a flat list of #define GL_* with no type information, so nothing in it can say that GL_TRIANGLES is a primitive mode. Khronos's gl.xml registry — the file the header is generated from — can, and it is vendored at scripts/khronos/gl.xml.

  • Grouping comes from gl.xml (<enums group>, <param group>, <proto group>) for GL, from egl.h's own version sections for EGL, and from the SDK's real typedef enums for CGL.
  • Values always come from the headers ext-opengl itself vendors, so the two packages cannot drift.
  • Nothing was grouped by hand. The complete rule set is .okf/enum-mapping.md.

Both context APIs, on both boxes

EGL and CGL are declared everywhere, because every entry point is resolved at runtime. A call the current context cannot make raises an E_WARNING and returns 0/void — no exception, no error side channel. So CGL::CGLCreateContext() on Linux warns and returns 0, and EGL::eglGetError() on a Mac does the same.

Choosing is the caller's job. This package has no PHP_OS_FAMILY anywhere in src/.

Proof

php examples/proof_headless_typed.php     # PROOF_HEADLESS_TYPED_OK

Creates a headless core-profile context (CGL on macOS, EGL surfaceless on Linux), renders a triangle into a 64×64 RGBA8 texture through an FBO with a GLSL shader pair, reads the pixels back and byte-checks them. It is ext-opengl's own proof_headless.php with the literals replaced by enums and nothing else changed — and scripts/gates/verify-smoke.mjs runs both proofs on the same box and fails on a single differing pixel.

centre corner
Mac, CGL, GL 4.1, Apple M1 Pro 255,128,64,255 0,0,0,255
Pi 5, EGL surfaceless, Mesa GL 3.1, V3D 7.1.7.0 255,128,64,255 0,0,0,255

Layering

ext-opengl (1:1 binding + unavoidable glue) → jovian/ogx (enums + typed projection) → venusian-ogx (composition) → Surface (cross-platform abstraction).

Licence

MIT. scripts/khronos/gl.xml is © 2013-2026 The Khronos Group Inc., Apache-2.0.