Builtin Library
README.md
material_schema
A shader's declared property vocabulary, and the routing of an authored key onto it.
A material's properties belong to its shader. The uniform buffer is packed by reflected field name, so a key the shader does not declare is never read by anything: it sits in the material's property table, reads back as though it applied, and contributes nothing to the render. This module is the single place that answers "is this key real, is it a texture slot, or is it neither" — so every path that accepts material properties gives the same answer.
Consumed by material.assetType (the .material asset path) and by
renderer.material.create (the runtime GPU path).
API
local schema = require("modules.material_schema")
local s = schema.forShader("pbr") -- nil when the shader can't resolve
schema.route(s, "base_color", {1,0,0,1}) -- "property", "base_color"
schema.route(s, "color", {1,0,0,1}) -- "property", "base_color" (alias)
schema.route(s, "base_color_texture", tex) -- "texture", "base_color_texture"
schema.route(s, "baseColor", {1,0,0,1}) -- "unknown", "baseColor"
schema.declaredNamesList(s) -- "alpha_cutoff, anisotropy, …"
schema.undeclaredMessage(name, key, "pbr", s)
schema.acceptsSlot(s, "base_color_texture") -- true: the shader declares the slot
schema.acceptsSlot(s, "base_color") -- false: a property, not a texture slot
schema.declaredSlotsList(s) -- "base_color_texture, emissive_texture, …"
schema.undeclaredSlotMessage(name, slot, "pbr", s)
schema.isTextureSlotType("texture") -- true
schema.isTextureSlotType("texture_array") -- true
schema.isTextureSlotType("color") -- false
A properties.yaml entry of type texture or texture_array declares a texture
slot; every other type except buffer declares a property.
A texture reaches the bind group only through a slot the shader declares, since
the material interface is generated from those slots. renderer.material.create,
renderer.material.setTexture and a material asset's setTexture /
writeTexture refuse any other slot through acceptsSlot and report it with
undeclaredSlotMessage. A schema that holds no names at all (a shader whose
vocabulary has not been read yet) accepts every slot.
Routing order
route applies the alias table first, then decides by surface:
- Alias —
color→base_color, but only when the shader declares the canonical name and not the alias, so a shader that genuinely exposescolorkeeps its own meaning. - Texture — the schema declares the name as a texture slot, or the value
is itself a texture (handle /
AssetRef<texture>). A texture stored as a uniform property reads back viagetPropertybut never reaches the bind group, so the value's own shape decides regardless of the key. - Property — the schema declares the name as a uniform property.
- Unknown — neither. Nothing reads it; the caller warns with
undeclaredMessageand drops it.
When the shader can't be resolved
forShader returns nil for a shader that is not registered yet (a runtime
shader still compiling, a name that resolves later). Callers stay permissive in
that case rather than reject every key against an empty vocabulary — the check
applies where the vocabulary is actually known.