GPU Programming · Reference
Glossary
The vocabulary every lesson in this workspace uses, and uses consistently.
Terms are added here only once a lesson has actually used them. Where GPU vendors disagree on a word, the Khronos/GLSL term is the one used throughout, with the CUDA equivalent noted — most performance writing on the internet is in CUDA dialect.
01 The execution model
| Term | Meaning |
|---|---|
| Invocation | |
| CUDA: thread | One run of main() in a compute shader. Thousands run at once. Each one knows only its ids and whatever it reads from memory. |
| Workgroup | |
| CUDA: block | A fixed-size block of invocations, declared in the shader with layout(local_size_x = N) in;. The only scope in which invocations can share memory or synchronise with each other. |
| Local size | Invocations per workgroup, up to three dimensions. Chosen by the shader author. Implementations must allow at least 128 invocations per group; 64 is the safe default for 1D work. |
| Dispatch | The CPU-side “go”. You give it a number of workgroups, never a number of invocations. Total invocations = groups × local size. |
| Global invocation id | gl_GlobalInvocationID = gl_WorkGroupID * gl_WorkGroupSize + gl_LocalInvocationID. Your unique index over the whole dispatch, and therefore usually your array index. |
| Bounds guard | if (id >= count) return; at the top of main(). Necessary because dispatches round up to whole workgroups, so surplus invocations are the norm. |
| Warp (NVIDIA) / wavefront (AMD) | A hardware bundle of invocations executing in lockstep: 32 wide on NVIDIA, 64 on AMD. Invisible in GLSL, but it is why local sizes should be multiples of 64. |
| SIMT | Single Instruction, Multiple Threads. Like SIMD, except individual lanes can be masked off — which is how a GPU runs an if at all. |
| Divergence | When invocations in the same warp take different branches. The warp runs both sides, masking lanes, so the cost is the sum, not the max. The reason branchy shader code is slow. |
| Occupancy | How many warps an execution unit has in flight relative to its maximum. High occupancy is how the GPU hides memory latency: while one warp waits on memory, another computes. |
02 The Godot / Vulkan plumbing
| Term | Meaning |
|---|---|
| RenderingDevice | Godot’s thin wrapper over Vulkan. Available only on the Forward+ and Mobile renderers. A local rendering device (create_local_rendering_device()) is your own private one, isolated from the one drawing the game. |
| Global RenderingDevice | The renderer’s own device, returned by RenderingServer.get_rendering_device(). Its resources can participate in both compute and drawing. Access its internals on the render thread; Godot submits its work, so submit()/sync() are unavailable. |
| Render thread | The thread on which Godot’s rendering internals may run. Queue global RenderingDevice work with RenderingServer.call_on_render_thread() rather than assuming the main or physics thread owns the renderer. |
| RID | An opaque handle to a GPU-side resource (buffer, shader, pipeline, uniform set). Not reference-counted: you free it yourself with rd.free_rid(). |
| SPIR-V | The compiled bytecode Vulkan actually consumes. Godot compiles your .glsl file to it at import time; RDShaderFile.get_spirv() hands it over. |
| Storage buffer (SSBO) | A block of GPU memory the shader can read and write, and whose trailing array can be sized at runtime. The workhorse for simulation state. |
| Physical capacity / logical count | Capacity is how many records the allocated buffer can hold; logical count is the length of the record range this dispatch may inspect. A separate flag can still exclude dead records inside that range. A persistent buffer can have spare capacity, so pass the logical count explicitly. |
| std430 | The memory layout rule set for storage buffers: how fields are packed and padded. Mismatched padding between GDScript and GLSL is the classic cause of “the numbers are garbage”. |
| Set and binding | The two-level address of a resource: layout(set = 0, binding = 0) in GLSL must match the set index and RDUniform.binding on the Godot side, or the shader reads nothing. |
| Uniform set | A bundle of resources bound together at one set index. Vulkan calls it a descriptor set. |
| Push constant | A small block of per-dispatch values recorded directly with GPU commands. Use it for counts, time steps, and tuning values; in these lessons it is a 16-byte block whose first 32-bit word is element_count. |
| Compute pipeline | The compiled shader plus fixed state, in the form the GPU can execute. Create once, reuse every frame. |
| Compute list | The recorded sequence of bind/dispatch commands, between compute_list_begin() and compute_list_end(). Recording is not executing. |
| Submit / sync | submit() hands recorded work to the GPU; sync() blocks the CPU until it is done. Calling them back to back throws away the parallelism. |
| Readback | Copying GPU memory back to the CPU (buffer_get_data()). Expensive and synchronising. Good GPU design avoids it in the frame loop. |
| Persistent resource | A buffer, shader, pipeline, or uniform-set RID created during setup, reused across ticks, and explicitly freed during shutdown. A GPU-resident simulation mutates persistent buffer contents rather than recreating the buffer every frame. |
| Barrier | An ordering instruction: “everything before this must finish being written before anything after it reads”. Needed whenever one dispatch consumes another’s output. |
| GLSL vs. Godot shading language | Two different languages. Compute shaders are real GLSL 450 in a .glsl file starting with #[compute]. .gdshader files are Godot’s own, GLSL-like but not GLSL, and cannot express compute. |
03 GLSL as a language
| Term | Meaning |
|---|---|
| Vector type | vec2/vec3/vec4 (floats), and the same shapes for int (ivec), uint (uvec), bool (bvec). A position or velocity is one value of this type, not several scalars kept in sync by hand. |
| Componentwise | How vector operators work by default: a + b on two vec2s adds each matching pair of components independently. No loop, no per-axis code. |
| Swizzle | Naming a vector’s components, in any order, with repeats allowed, via .xyzw/.rgba/.stpq — three spellings for the same slots. v.zyx reverses a vec3; v.xxxx is legal. Writable too: pos.xy = vec2(0) updates exactly those components. |
| No pointers | The GLSL specification states it outright: “There are no pointer types.” Nothing is passed by reference except a whole bound buffer block; there is no address-of, no linked structure built from them. |
| No printf | GLSL has no console output. The only way to inspect what one invocation computed is to write it into a buffer and read that buffer back on the CPU — the readback step from lesson 1 doubles as the debugger. |
| Implicit int→float conversion | Mixing an int/uint and a float in one expression compiles: GLSL picks the floating-point result and converts the integer operand automatically. |
04 GPU-resident body data
| Term | Meaning |
|---|---|
| GPU-resident | State whose authoritative copy remains in GPU memory across simulation and rendering. The CPU may send additions or parameters and receive summaries, but does not copy every body’s live state back each frame. |
| Schema | The byte contract shared by CPU and shader: field order, machine type, byte offset, and record stride. Names are documentation only. |
| Alignment | The byte boundary on which a value is allowed to begin. Under std430, a scalar aligns to 4 bytes, a vec2 to 8, and a vec3/vec4 to 16. |
| Padding | Bytes inserted between or after meaningful fields to satisfy alignment and stride. They carry no gameplay meaning, but they are part of the schema. |
| Stride | The byte distance from the start of array element i to element i+1. Record address = buffer start + index × stride. |
| Flag | One behavior bit packed into an integer. Test with flags & BIT, add with `flags |
| Array of structures (AoS) | Each body record stores all of that body’s fields together: [pos, vel, health][pos, vel, health]…. Straightforward to serialize and ideal for initialization records. |
| Structure of arrays (SoA) | Each field or related field group has its own stream: all physics records, all simulation records, all temporary records. A pass can read only the streams it needs. |
05 GPU-owned lifecycle
| Term | Meaning |
|---|---|
| Atomic operation | An indivisible read-modify-write on one integer memory location. atomicAdd(counter, 1) returns the old value, so concurrent invocations can reserve unique slots without a lock. |
| Append queue | A fixed-capacity sequence of records waiting to join a resident buffer. Each record reserves a destination slot atomically; requests beyond capacity increment overflow instead of writing. |
| Compaction | Copying selected live records into a dense destination range with no holes. Atomic compaction is unstable: it preserves the live set, not its order. |
| Overflow counter | Telemetry recording how many queue records could not fit. It turns a fixed-capacity failure into an explicit, measurable event instead of memory corruption. |
| Ping-pong buffers | Two persistent allocations that alternate source and destination roles. A compaction pass reads A and writes B, then the next pass reads B and writes A. |
| Unstable order | A valid parallel result whose record ordering is unspecified. An index in an unstable compacted buffer is a location, not lasting body identity. |