3b1b/manim · error · ValueError
An array in a block holds {ARRAY_ELEMENT_SIZE} floats to an
Error message
An array in a block holds {ARRAY_ELEMENT_SIZE} floats to an element, so {name} cannot hold {count} of {size} What it means
uniform_block builds a packed numpy dtype plus a WGSL block from a member list. GLSL/WGSL std140-style rules require each element of an array in a uniform block to be a vec4 (ARRAY_ELEMENT_SIZE = 4 floats, uniform_block.py:34). A member declared with count > 1 but a per-element size other than 4 floats (e.g. three vec3s) cannot be laid out, so it raises ValueError at block-construction time.
Source
Thrown at manimlib/renderer/uniform_block.py:62
The rules reproduced are that a member is aligned to its own size, rounded up to four
floats for anything wider than two, and that the block as a whole is rounded up likewise.
What that alignment skips over is declared as a field rather than left as a gap, numpy not
carrying the contents of a gap over when copying, which would leave whatever the memory
held to be compared against and sent.
"""
names: list[str] = []
formats: list[Any] = []
def add(name: str, shape: tuple[int, ...]) -> None:
names.append(name)
formats.append(np.float32 if shape == (1,) else (np.float32, shape))
size_so_far = 0
for name, size, *rest in members:
count = rest[0] if rest else 1
if count > 1 and size != ARRAY_ELEMENT_SIZE:
raise ValueError(
f"An array in a block holds {ARRAY_ELEMENT_SIZE} floats to an element, so "
f"{name} cannot hold {count} of {size}"
)
if size not in BLOCK_MEMBER_TYPES:
raise ValueError(f"No room in a block for {name}, of {size} floats")
skipped = -size_so_far % (size if size <= 2 else 4)
if skipped:
add(f"_pad{len(names)}", (skipped,))
add(name, (size,) if count == 1 else (count, size))
size_so_far += skipped + count * size
if -size_so_far % 4:
add(f"_pad{len(names)}", (-size_so_far % 4,))
# Left to pack the fields itself, numpy places them back to back, which is where the
# padding above has been chosen to put them
return np.dtype({"names": names, "formats": formats})
def uniform_block_code(dtype: np.dtype) -> str:View on GitHub (pinned to dee01804d4)
Solutions
- Pad each element to 4 floats: declare the member as size 4 and store positions as vec4s (w unused)
- Alternatively split into separate non-array members of size 3, which are padded automatically by the existing _pad logic
- Keep arrays in blocks to vec4 (or mat4) elements only
Example fix
# before
UniformBlock(... members=[('body_positions', 3, 3)])
# after
UniformBlock(... members=[('body_positions', 4, 3)]) # vec4 elements, w unused Defensive patterns
Strategy: validation
Validate before calling
ARRAY_ELEMENT_SIZE = 4
for name, size, *rest in members:
count = rest[0] if rest else 1
if count > 1:
assert size == ARRAY_ELEMENT_SIZE, f"{name}: array elements must be {ARRAY_ELEMENT_SIZE} floats" Type guard
def valid_block_members(members) -> bool:
return all(
(rest[0] if rest else 1) == 1 or size == 4
for _, size, *rest in members
) Prevention
- Use only vec4 (or mat4) elements inside uniform-block arrays
- Pad vec3 data to vec4 in the buffer upload (numpy structured dtype already does this when declared size 4)
When it happens
Trigger: Adding a uniform-block member like ('body_positions', 3, 3) — an array of 3-float elements — in a custom shader's UniformBlock declaration; any ('name', size, count) with count > 1 and size != 4.
Common situations: Writing custom shaders that pass an array of vec3s (positions, colors per instance); porting GLSL uniform blocks to this renderer; upgrading manim versions where the block-building API gained this check.
Related errors
- No room in a block for {name}, of {size} floats
- The stroke shader no longer declares {declaration!r}, so a f
AI-assisted analysis of 3b1b/manim@dee01804d4 (2026-08-14).
Data as JSON: /api/errors/26c6e17456e27475.
Report an issue: GitHub.