Skip to main content

Core Engine

The GLRE core engine provides GPU abstraction and automatic resource management.

Engine Initialization

Basic Creation

import { createGL } from 'glre'

// Minimal setup
const gl = createGL()

// With options
const gl = createGL({
width: 800,
height: 600,
isWebGL: true,
})

Configuration Options

OptionTypeDefaultDescription
widthnumberwindow.innerWidthCanvas width
heightnumberwindow.innerHeightCanvas height
isWebGLbooleantrueUse WebGL2 instead of WebGPU
isLoopbooleantrueEnable animation loop
countnumber6Number of vertices to draw

Platform Detection

The engine automatically selects the best available graphics API:

const createOptimalGL = () => {
// Automatic platform detection
if (navigator.gpu && !forceLegacy) {
return createGL({ isWebGL: false }) // WebGPU
}
return createGL({ isWebGL: true }) // WebGL2
}

Platform Capabilities

PlatformFeaturesCompatibility
WebGPUCompute shaders, advanced pipelinesChrome 113+, Edge 113+
WebGL2Fragment/vertex shaders, wide supportAll modern browsers

Resource Management

Automatic Binding

GLRE automatically manages GPU resources:

const gl = createGL({
fragment: () => {
// Automatic uniform binding
const time = uniform('iTime')
const resolution = uniform('iResolution')

// Automatic type inference
const color = sin(time).mul(0.5).add(0.5)
return vec4(color, 0.5, 1.0, 1.0)
},
})

// Engine handles binding automatically
gl.uniform('iTime', performance.now() / 1000)
gl.uniform('iResolution', [800, 600])

Memory Management

The engine uses closure-based patterns for resource management:

const createRenderer = (config) => {
let context = null
let program = null
let buffers = {}

const initialize = () => {
context = initializeContext(config)
program = createShaderProgram(context, config.shaders)
buffers = createBuffers(context)
}

const render = () => {
if (!context || !program) return

context.useProgram(program)
bindBuffers(context, buffers)
context.drawArrays(context.TRIANGLES, 0, config.count)
}

const cleanup = () => {
if (program) context.deleteProgram(program)
Object.values(buffers).forEach((buffer) => {
if (buffer) context.deleteBuffer(buffer)
})
context = null
program = null
buffers = {}
}

initialize()

return { render, cleanup }
}

Shader Compilation

Automatic Generation

GLRE converts TypeScript to shader code:

// TypeScript Node System
const nodeShader = () => {
const pos = builtin('position')
const uv = pos.xy.mul(0.5).add(0.5)
const pattern = sin(uv.x.mul(10)).mul(sin(uv.y.mul(10)))
return vec4(pattern, pattern, pattern, 1.0)
}

// Compiles to GLSL/WGSL automatically
const gl = createGL({
fragment: nodeShader,
})

Manual Shader Code

You can also provide raw shader strings:

const gl = createGL({
vertex: `
#version 300 es
in vec3 position;
void main() {
gl_Position = vec4(position, 1.0);
}
`,
fragment: `
#version 300 es
precision mediump float;
out vec4 fragColor;
void main() {
fragColor = vec4(1.0, 0.5, 0.2, 1.0);
}
`,
})

Context Management

WebGL2 Context

const createWebGLContext = (canvas, options) => {
const contextOptions = {
antialias: options.antialias ?? true,
alpha: options.alpha ?? false,
depth: options.depth ?? true,
stencil: options.stencil ?? false,
premultipliedAlpha: false,
preserveDrawingBuffer: false,
}

const gl = canvas.getContext('webgl2', contextOptions)
if (!gl) {
throw new Error('WebGL2 not supported')
}

return gl
}

WebGPU Context

const createWebGPUContext = async (canvas, options) => {
if (!navigator.gpu) {
throw new Error('WebGPU not supported')
}

const adapter = await navigator.gpu.requestAdapter({
powerPreference: options.powerPreference ?? 'default',
})

if (!adapter) {
throw new Error('No WebGPU adapter found')
}

const device = await adapter.requestDevice()
const context = canvas.getContext('webgpu')

context.configure({
device,
format: 'bgra8unorm',
alphaMode: 'premultiplied',
})

return { device, context, adapter }
}

Uniform System

Type-Safe Uniforms

const setupUniforms = (gl) => {
const uniformCache = new Map()

const setUniform = (name, value) => {
// Automatic type detection
if (typeof value === 'number') {
gl.uniform1f(name, value)
} else if (Array.isArray(value)) {
switch (value.length) {
case 2:
gl.uniform2f(name, ...value)
break
case 3:
gl.uniform3f(name, ...value)
break
case 4:
gl.uniform4f(name, ...value)
break
case 16:
gl.uniformMatrix4fv(name, false, value)
break
}
}

uniformCache.set(name, value)
}

const getUniform = (name) => uniformCache.get(name)

return { setUniform, getUniform }
}

Attribute Management

Vertex Data Handling

const createAttributeManager = (gl) => {
const attributes = new Map()

const setAttribute = (name, data, options = {}) => {
const buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW)

const location = gl.getAttribLocation(program, name)
if (location !== -1) {
gl.enableVertexAttribArray(location)
gl.vertexAttribPointer(
location,
options.size ?? 3,
gl.FLOAT,
false,
options.stride ?? 0,
options.offset ?? 0
)

if (options.divisor !== undefined) {
gl.vertexAttribDivisor(location, options.divisor)
}
}

attributes.set(name, { buffer, location, data })
}

const cleanup = () => {
attributes.forEach(({ buffer }) => {
gl.deleteBuffer(buffer)
})
attributes.clear()
}

return { setAttribute, cleanup }
}

Texture Loading

Automatic Texture Management

const createTextureManager = (gl) => {
const textures = new Map()

const loadTexture = async (name, source) => {
const texture = gl.createTexture()
gl.bindTexture(gl.TEXTURE_2D, texture)

// Placeholder 1x1 pixel
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
1,
1,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
new Uint8Array([255, 0, 255, 255])
)

if (typeof source === 'string') {
const image = new Image()

return new Promise((resolve, reject) => {
image.onload = () => {
gl.bindTexture(gl.TEXTURE_2D, texture)
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)
gl.generateMipmap(gl.TEXTURE_2D)

textures.set(name, texture)
resolve(texture)
}
image.onerror = reject
image.src = source
})
}

textures.set(name, texture)
return texture
}

const getTexture = (name) => textures.get(name)

const cleanup = () => {
textures.forEach((texture) => gl.deleteTexture(texture))
textures.clear()
}

return { loadTexture, getTexture, cleanup }
}

Error Handling

Graceful Degradation

const createGLWithFallback = (config) => {
try {
// Try WebGPU first
if (!config.forceWebGL && navigator.gpu) {
return createGL({ ...config, isWebGL: false })
}
} catch (error) {
console.warn('WebGPU failed, falling back to WebGL:', error)
}

try {
// Fallback to WebGL2
return createGL({ ...config, isWebGL: true })
} catch (error) {
console.error('Both WebGPU and WebGL2 failed:', error)
throw new Error('No compatible graphics API found')
}
}

Context Loss Handling

const handleContextEvents = (canvas, gl, onRestore) => {
canvas.addEventListener('webglcontextlost', (event) => {
event.preventDefault()
console.warn('WebGL context lost')
})

canvas.addEventListener('webglcontextrestored', () => {
console.log('WebGL context restored')
onRestore()
})
}

Render Loop

Animation Management

const createRenderLoop = (gl) => {
let isRunning = false
let frameId = null

const start = () => {
if (isRunning) return
isRunning = true

const frame = () => {
if (!isRunning) return

gl.render()
frameId = requestAnimationFrame(frame)
}

frame()
}

const stop = () => {
isRunning = false
if (frameId) {
cancelAnimationFrame(frameId)
frameId = null
}
}

return { start, stop }
}

The GLRE core engine handles all low-level GPU operations automatically, letting you focus on creative coding rather than graphics API complexity.