Master the fundamental concepts of modern graphics apis (low level) through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksShaders need data constant for all vertices or fragments in a draw call: MVP matrices, light positions, material properties, elapsed time. Early OpenGL passed these one at a time via glUniform. Modern OpenGL and Vulkan use Uniform Buffer Objects (UBOs) for one bind instead of dozens of API calls.
A UBO is a GPU memory block bound to a shader binding point. The shader declares a uniform block and reads fields by name.
UBOs follow std140 packing rules for predictable alignment:
cLoading…
For example, a vec3 followed by a float in the same struct may waste padding because vec3 already occupies 16 bytes in std140. Misaligned CPU structs cause shaders to read garbage silently. Engines like Unreal auto-generate reflection metadata to avoid this.
glUniform callsYou will define a UBO struct in C with correct std140 padding and print each field's byte offset. This task asks you to compute alignment for matrices, vec3/float pairs, and arrays. Getting struct layout wrong is a classic bug where the GPU reads garbage because CPU-side packing does not match the shader's expected layout.
Shaders read uniform and storage buffers with a fixed memory layout, and the CPU side must write bytes at exactly those offsets. Write a std140/std430 layout calculator. For every member of a block or struct, print its offset, size and alignment, the array and matrix-column strides, and the padding that the rules insert. Then print the total size and how much of it is padding. The vec3 that aligns like a vec4 is the classic bug this catches.
// starts a comment. ;, { and } are ignored, except that a } on its own acts as end.
cLoading…
Types: float int uint bool, vec2 vec3 vec4 (and the ivec/uvec forms), mat2 mat3 mat4 (column-major), and previously declared structs.
| type | std140 | std430 |
|---|---|---|
| scalar | size 4, align 4 | same |
| vec2 / vec3 / vec4 | size 8/12/16, align 8/16/16 | same |
| matC (C columns of vecC) | column stride 16, align 16 | column stride = vecC size rounded to its alignment (mat2: 8, mat3/mat4: 16), align = that of vecC |
| array of T | stride = size(T) rounded up to 16, align ≥ 16 | stride = size(T) rounded up to align(T), align(T) |
| struct | align = max member align rounded up to 16 | align = max member align |
columns × stride, and an array's size is count × stride.vec3 = 12, mat3 = 36), times the array counts, summed through structs. Padding = total - data.cLoading…
%4d: TYPE NAME[N] size S, align A[, array stride X][, column stride Y]. (N bytes of padding) on its own line, and a struct's rounding prints (N bytes of tail padding)., align A.line N: unknown type Tline N: bad array size in NAME[..]line N: expected TYPE NAME[N]line N: struct S cannot contain itselfline N: struct|block X has no members (it is dropped)line N: layout std140|std430line N: expected layout, struct, block or showline N: too many declarations / too many membersshow: no declaration XNAME: missing end (at the end of input)Input:
cLoading…
Output:
cLoading…
Hidden tests cover arrays of scalars and vec3s under both layouts, mat2 in std140 versus std430, a struct array with tail padding, show after switching layouts, and declaration errors.