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:
- Read texture bits.
- Transform those bits through GLSL operations.
- Write result bits into color buffers.
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
discardand 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
- Low-end mobile:
8 texture units * 4096 texels wide * 4096 texels tall * 4 RGBA channels * 8 bits channel = 4,294,967,296 bits = 536,870,912 bytes = 512 MB. - Typical mobile:
8 texture units * 4096 texels wide * 4096 texels tall * 4 RGBA channels * 32 bits channel (float) = 17,179,869,184 bits = 2,147,483,648 bytes = 2 GB. - A simple laptop:
32 texture units * 4096 texels wide * 4096 texels tall * 4 RGBA channels * 32 bits channel (float) = 68,719,476,736 bits = 8,589,934,592 bytes = 8 GB.
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.
- Mobile color buffer
A single mobile color buffer exposes 64 MB across one full 4096x4096 pass. Visible mobile framebuffers may use fewer than 8 bits per channel, but offscreen GPGPU attachments determine this computation path.1 color buffer * 4096 texels wide * 4096 texels tall * 4 RGBA channels * 8 bits channel = 536,870,912 bits = 67,108,864 bytes = 64 MB. - Laptop color buffer in GPGPU mode
8 color buffers * 4096 texels wide * 4096 texels tall * 4 RGBA channels * 32 bits channel (float) = 17,179,869,184 bits = 2,147,483,648 bytes = 2 GB. - In graphics mode, one 16384x16384 RGB color buffer already carries a 768 MB image, even when the laptop supports 8 attachments.
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:
- 8 fetches from 8 desktop output color buffers;
- or 8 coordinates in one mobile texture.
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.
- 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:
- IN: external state.
- IN: current hidden state.
- IN: current visible state.
- OUT: next hidden state.
- OUT: next visible state.
Textures and color buffers then carry ten explicit I/O types:
- IN_EXT_REQ: external state synchronized through two flip-flops.
- IN_EXT_DATA: external state coordinated by IN_REQ signals.
- IN_CONST: constant hidden data.
- IN_CODE_ROM: fixed code.
- IN_CODE_RAM: current code.
- IN_DATA: current hidden state.
- IN_VISIBLE: current visible state, available for direct updates.
- OUT_CODE_RAM: potentially self-modifying code.
- OUT_DATA: next hidden data.
- 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.