WebGL GPGPU Scaffolding

A texture can hold state, a fragment shader can transform it, and a framebuffer can capture the next state.

This page builds that machine directly from WebGL 1.0, OpenGL ES 2.0 shaders, and ES6. In 2020, browsers already supplied these parts across GPU-capable laptops and phones. Removing framework layers keeps every allocation, compilation step, bit surface, and state transition visible.

Turn a Switch Into a Parallel State Machine

A switch lets one condition control another path. A shader performs millions of such selections across a two-dimensional address space, then writes their results into new state.

The scaffold connects those levels directly: texture bits enter, GLSL intertwines them, and color-buffer bits leave. Repeating that transition turns regulation into a simulator.

Control Browser Computation in Space and Time

A Shader Reads, Transforms, and Writes Bits

Every shader run follows three operations:

  1. Read texture bits.
  2. Transform those bits through GLSL operations.
  3. Write result bits into color buffers.
Textures define the accessible input space. Rasterized fragments define where computation runs. Color attachments receive the output.

Five terms keep the data path precise:

Pixel
A visible picture element on the screen.
Texel
A texture element that a shader reads.
Fragment
One candidate output from a shader invocation. Several fragments can contribute to a pixel across multiple runs, while stencil and depth tests can influence the final value. The shader can also call discard and suppress the write.
Texture image unit
An array of texels available to one shader run. All GPUs support 8 simultaneous texture image units; some systems expose 16 or 32.
Color buffer
An array that receives shader output. Most mobile platforms expose one color buffer with 32 bits per pixel.

Texture-unit count, texture dimensions, and texel width together determine the shader's addressable input range.

This scaffold uses a maximum texture size of 4096 by 4096 texels.

Mobile platforms commonly provide 32-bit texels through four 8-bit R, G, B, and A channels in the range 0..255. Laptops can provide 128-bit texels through four 32-bit floating-point channels. The OES_texture_float extension unlocks that floating-point input path on most platforms above the lowest tier.

Texture image units supply the primary high-bandwidth shader input.

Color buffers supply the main output. Laptops can expose 8 color buffers.

Phones commonly support color buffers up to 4096x4096, while laptops can reach 16384x16384. Texture flipping removes the GPGPU advantage of those larger dimensions, so resolutions above 4096 mainly serve visible rendering and high-resolution export.

The GPGPU path therefore uses 4096 by 4096 as its working color-buffer ceiling and reserves larger buffers for real-time graphics or high-resolution export.

Count the Addressable Shader Input

High-end AMD and Nvidia GPUs extend the addressable texture memory beyond these examples.

Count the Addressable Shader Output

A shader may sample only part of its texture input, yet a pass often writes every targeted fragment. Triangles and rectangles restrict execution to the output region that actually needs recomputation.

Memory bandwidth usually limits how many bits the system can transform each second. DDR4-2400 in a laptop provides 18.75 GB/s; the iPhone XS Max LPDDR4X provides 8.33 GB/s.

Measure One Fragment's Output

A mobile fragment writes 32 bits. A desktop fragment can write 32 times more: 1024 bits across 8 floating-point vec4 outputs.

At the smallest workload, one desktop fragment carries the same output width as an 8x4 block of smartphone fragments.

Eight vec4 reads can match that output in two useful arrangements:

That comparison turns texture choice into a measurable performance question.

Low-end mobile GPUs without floating-point textures read only 8-bit-per-channel RGBA textures.

Give the GLSL API Explicit Bit Surfaces

A cohesive GLSL API should use GPU resources efficiently, retain broad compatibility, and map cleanly onto FPGA and ASIC architectures. Binary input, output, and internal-state surfaces provide that common interface.

Practical computing systems expose three fundamental classes of bit surfaces:

INPUTS from external world
The external world can change these bits asynchronously to internal computation. An FPGA may buffer them or synchronize an external REQ signal. Two categories keep that relationship explicit:
  • Synchronized REQ input signals pass through two flip-flops under the current computation-wave clock; a system may contain many local clocks.
  • Asynchronous input data remains volatile and follows a coordinating REQ signal. FPGA- and ASIC-compatible code reads these bits only when REQ grants access because the surface can change during a shader read.
The interface does not buffer either category automatically. The design must copy data or manage read and write pointers explicitly. Daniel Chapiro's PhD thesis work develops GALS: globally asynchronous, locally synchronous systems.
OUTPUTS visible to external world
These bits carry computation into the external world. A memory flip-flop holds each value so an external consumer can read it at any moment. The circuit must electrically drive every visible 0 or 1. This interface therefore omits the Mealy-style output path.
HIDDEN STATE under complete internal control and invisible to the external world
Internal computation divides this memory into several useful regions:
  • Constant surfaces hold values that never change, such as π.
  • Fixed source-code surfaces hold ROM-style procedures.
  • Dynamic source-code surfaces support load-dependent deployment, IDE changes, and optimized JIT output while keeping code upgradeable.
  • Data caches hold state-machine state, intermediate values, object fields, momentum, velocity, pressure, and other evolving quantities.

An Interrupt Vector Table trigger belongs to both input and output behavior. A process can poll inputs on its own schedule, while a reactive system can wake processing when selected bits change. IVT hardware marks those bits as modified, and that event launches the next reactive wave.

A simulator can map that IVT concept onto a restricted trigger area and rerun a shader only when those selected bits change. This wake/sleep contract bridges the classic CPU IVT model, reactive FPGA logic, and GPGPU execution.

An IVT acts like a subscription that internal code controls. The vector maps selected external changes onto modified-bit destinations, then wakes emulation or physical processing.

During a dynamic transition, the three surface classes expand into five roles:

  1. IN: external state.
  2. IN: current hidden state.
  3. IN: current visible state.
  4. OUT: next hidden state.
  5. OUT: next visible state.

Textures and color buffers then carry ten explicit I/O types:

  1. IN_EXT_REQ: external state synchronized through two flip-flops.
  2. IN_EXT_DATA: external state coordinated by IN_REQ signals.
  3. IN_CONST: constant hidden data.
  4. IN_CODE_ROM: fixed code.
  5. IN_CODE_RAM: current code.
  6. IN_DATA: current hidden state.
  7. IN_VISIBLE: current visible state, available for direct updates.
  8. OUT_CODE_RAM: potentially self-modifying code.
  9. OUT_DATA: next hidden data.
  10. OUT_VISIBLE: next visible state.

This division prevents unnecessary writes. External inputs, constants, and fixed algorithms remain input textures while each pass targets only the code, hidden data, and visible state that can change.

The following GLSL interface turns those roles into named surfaces. Each shader run computes a selected region of the OUT_ state.

Give WebGL an Explicit Computation Path

A GPU runs many switching operations at once. The first code block creates a WebGL context and chooses initialization options that favor direct computation.

WebGL inherits a verbose C-style shader pipeline. The next function creates vertex and fragment shaders, loads their source, compiles them, links a program, and reports compiler or linker failures through one reusable JavaScript call.

The compile_shader() call receives a WebGL context plus vertex and fragment source strings. The OpenGL ES 2 driver compiles both GLSL programs for the GPU's instruction set.

The first capability query asks whether a shader can write floating-point values instead of four RGBA bytes in the range 0 to 255.

If the query returns OK, this platform offers only the four-byte output path and no floating-point rendering.

The next query checks whether one pass can target multiple textures.

Typical mobile platforms support neither floating-point render targets nor simultaneous writes to multiple textures.

The input path may still fetch four-component floating-point vectors, so the next query checks OES_texture_float.

A wrapper can bridge the wide output path in a laptop GPU and the battery-efficient path in a smartphone GPU.

Global shader parameters expose the current output coordinate and let one source adjust to either execution mode.

The broadly compatible configuration fetches 8 floating-point vec4 values and writes four bytes, or 32 result bits, per shader thread. Fetching a vec4 from four bytes adds no memory-transfer penalty, so two buffers can trade current and next state roles after each pass.

WebGL launches fragment work by rasterizing triangles. Their coverage selects exactly which output coordinates run the shader.

An offscreen framebuffer directs those fragments into a texture instead of the visible canvas.

The triangles define the recomputation region. Reactive workloads can target only changed areas, while globally coupled computations still need a full pass because any input may affect any output.

A 1x1 floating-point texture makes the first state transition easy to inspect:

The scaffold now owns the complete loop: create textures, attach a framebuffer, bind inputs, draw the output region, read the result, and swap state roles for the next transition.