Sparkle 0.0.1
Loading...
Searching...
No Matches
Compiled artifact format

Concept

The idea behind Lumina is to abstract the GLSL from the user, using the langage as a sort of "script" The compiler will than take the script as input, and compute a file that will contain all the information available about this shader

Content

The compiler now produces a single JSON document that contains every piece of information Sparkle needs to bind a shader. This keeps the artifact human readable, machine parsable, and easy to extend.

{
"shader": {
"sources": {
"vertex": "<glsl code>",
"fragment": "<glsl code>"
}
},
"layouts": [
{ "location": 0, "type": "vec3", "name": "inPosition" }
],
"framebuffers": [
{ "location": 0, "type": "vec4", "name": "outColor" }
],
"textures": [
{ "location": 0, "luminaName": "albedo", "type": "sampler2D", "scope": "constant" }
],
"constants": [
{
"name": "PerFrame",
"type": "UBO",
"size": 160,
"members": [
{ "name": "viewProj", "type": "Element", "offset": 0, "size": 64 },
{
"name": "lights",
"type": "Element",
"offset": 64,
"size": 96,
"members": [
{ "name": "count", "type": "Element", "offset": 0, "size": 4 },
{ "name": "pad", "type": "Element", "offset": 4, "size": 12 },
{
"name": "items",
"type": "Array",
"offset": 16,
"size": 64,
"elementSize": 32,
"nbElements": 2,
"members": [
{ "name": "pos", "type": "Element", "offset": 0, "size": 16 },
{ "name": "color", "type": "Element", "offset": 16, "size": 16 }
]
}
]
}
]
}
],
"attributes": [
{
"name": "VisibleLights",
"type": "SSBO",
"size": 128,
"members": [
{ "name": "header", "type": "Element", "offset": 0, "size": 32 }
],
"dynamicArrayLayout": {
"name": "lights",
"offset": 128,
"elementStride": 32,
"elementPadding": 0,
"members": [
{ "name": "pos", "type": "Element", "offset": 0, "size": 16 },
{
"name": "extra",
"type": "Element",
"offset": 16,
"size": 16,
"members": [
{ "name": "intensity", "type": "Element", "offset": 0, "size": 4 },
{ "name": "range", "type": "Element", "offset": 8, "size": 4 }
]
}
]
}
}
]
}

Layout definition

layouts is an ordered array of objects that describe every vertex attribute consumed by the shader.
Each entry contains:

  • location: numeric layout location (must be unique and sorted).
  • type: GLSL type (vec3, mat4, etc.).
  • name: the identifier generated by the compiler.

Frame buffer definition

framebuffers mirrors the layout structure but lists every color/depth attachment written by the fragment shader. Entries contain the layout location, output type, and generated name.

Textures

The textures array keeps the mapping between the identifier used in Lumina and the layout binding that GLSL uses. The compiler assigns a layout location (binding) to each declared texture in source order starting at 0; the GLSL uses this binding and the artifact mirrors it for the runtime. Each entry also carries the scope selected in the source file (constant or attribute), mirroring the as qualifier used during declaration.

{
"location": 0,
"luminaName": "albedo",
"type": "sampler2D",
"scope": "constant"
}

See docs/lumina/compiled-artifact-example.json for a full sample artifact that exercises every field of the schema.

Constants (UBO / SSBO)

constants is a list of DataBlock declarations that were left at the default constant scope (explicit as constant works too). Bindings are not stored in the artifact anymore; only the total block size and the memory layout are emitted. The compiler evaluates the block body to decide the type:

  • UBO when every field has a fixed size.
  • SSBO when the block contains exactly one unsized array ([]). Declaring more than one unsized array is illegal and aborts compilation.

Each members entry describes a field:

  • name: user-visible identifier.
  • type: "Element" for single values, "Array" for fixed-size arrays.
  • offset: byte offset within the parent scope.
  • size: size of the field.
  • elementSize (optional): size of a single element when the field is a fixed-size array.
  • nbElements (optional): number of elements in the fixed-size array when it can be resolved at compile time.
  • members: optional array for nested structures.

All offsets and sizes are emitted using OpenGL's standard layout rules: constant DataBlocks use std140 (UBO) while SSBO-backed blocks use std430. This means the values already include the implicit padding that GLSL expects, so you can forward them directly to your buffer builders without reapplying the layout rules.

This recursion lets the artifact represent arbitrarily deep structures without duplicating intermediate JSON blocks.

If the block needs a runtime-sized array, the emitted object also carries a dynamicArrayLayout entry that captures how the SSBO tail should be populated:

  • name: identifier of the unsized array inside the block.
  • offset: byte offset where the array begins.
  • elementStride: size of one array element including padding.
  • elementPadding: explicit padding (if any) between elements.
  • members: layout of a single element, following the same recursive format used elsewhere.

Attributes (Attribute DataBlocks -> UBO / SSBO)

attributes follows the same structure as constants, but for DataBlocks declared with as attribute. Just like constant blocks, attribute DataBlocks default to UBO until they declare an unsized array, at which point the compiler switches the type to SSBO and emits the dynamicArrayLayout metadata described above. Attribute-scope DataBlocks are also limited to a single unsized array.

Shader sources

The compiled GLSL for each stage is embedded directly in shader.sources. Consumers can load vertex and fragment strings (and any future stages) without scanning the file for manual markers.