Sprites Shader Media Storage
A shader image lives in the data store as one record with just two fields: its id and a data object. None of the metadata, the name, media, platform, times, thumbnail or info summary, is here; that is the meta record, kept beside it under the same id. The data object holds the whole picture as code, not pixels: its width and height, a block of shared global GLSL, a set of image wide parameters passed to every layer as uniforms, and a stack of layers, bottom to top, each layer a piece of GLSL that renders into a texture over the layer below. Nothing here is rasterised: the stored form is the source and its inputs, and the render driver turns it into an image. The order the layer's full GLSL is assembled in is set out under Composition.
{
"id": "5b8e2c1a7f9042d3a6e0b4c9d1f83027",
"data": {
"media": "shader",
"w": 512,
"h": 512,
"globals": "float Circle(vec2 p, float r) { /* ... */ }",
"params": [
{ "name": "uTime", "type": "float", "value": { "optionalFloat": 0 } }
],
"layers": [
{
"name": "Layer 1",
"visible": true,
"code": "vec4 LayerMain() { return vec4(Unit, 0.0, 1.0); }",
"params": []
}
]
}
}
id and the data object. The sections below walk the object down through its parameters and its layers.Contents
The record in the data store is just the id and the data object. Everything a listing shows, the name and the size and layer-count summary, is the meta record, so this store carries only the picture.
| Field | Type | Description |
|---|---|---|
id | String | The item id, 32 hex characters. The key, shared with the item's meta record. |
data | Object | The picture as code: its size, shared globals, image parameters and layers. Detailed in Data below. |
Data
The data object carries the size the image renders at, the shared global code, the image wide parameters and the layer stack. A shader image holds no stored pixels: the width and height are the size the layers render into, not a buffer.
| Field | Type | Description |
|---|---|---|
media | String | Always the string "shader", naming the media this record holds. |
w | Number | The width in pixels, a whole number from 1 to 4096. The size the layers render into. |
h | Number | The height in pixels, a whole number from 1 to 4096. |
globals | String | The shared global GLSL, added to every layer's code ahead of the layer's own code, so helpers defined here are in scope in each layer. Detailed in Globals below. |
params | Array | The image wide parameters, declared as uniforms in every layer. Zero or more. Detailed in Parameters below. |
layers | Array | The layers, bottom to top. One or more. Detailed in Layers below. |
Globals
globals is a plain string of GLSL, the shared code every layer gets. When a layer's full source is assembled it is placed ahead of the layer's own code, so a function or constant defined in the globals can be called from any layer's LayerMain. It declares no main of its own; it holds helpers only. See Composition for where it lands in the assembled source.
Parameters
A parameter becomes a GLSL uniform passed into the shader. The image wide params are passed to every layer; a layer's own params are passed to that layer only. A parameter is { name, type, value }. The value is a tagged union: it carries one of optionalFloat, optionalVec2, optionalVec3 or optionalVec4, and which one is present is decided by the type. Exactly one field is set at a time; the rest are absent.
| Field | Type | Description |
|---|---|---|
name | String | The uniform name declared in the GLSL, for example uTime. It must be a valid GLSL identifier and unique among the parameters in scope for a layer. |
type | String | One of "float", "vec2", "vec3", "vec4" or "colour". It fixes which value field is present and the GLSL type the uniform declares as. A colour declares as a vec4; it differs from a plain vec4 only in the editor control, a colour picker rather than four number fields. |
value | Object | The tagged union holding the parameter's current value. Detailed in Value below. |
Value
The value object holds exactly one of the following, chosen by the type. The vector fields are plain objects of named components, each a number. A colour stores its red, green, blue and alpha in optionalVec4, each 0 to 1.
| Field | Present when type is | Shape |
|---|---|---|
optionalFloat | float | A Number. |
optionalVec2 | vec2 | { x, y }, each a Number. |
optionalVec3 | vec3 | { x, y, z }, each a Number. |
optionalVec4 | vec4 or colour | { x, y, z, w }, each a Number. For a colour these are red, green, blue and alpha, each 0 to 1. |
{ "name": "uCentre", "type": "vec2", "value": { "optionalVec2": { "x": 0, "y": 0 } } }
{ "name": "uTint", "type": "colour", "value": { "optionalVec4": { "x": 1, "y": 0.4, "z": 0.2, "w": 1 } } }
vec4 in the GLSL; only the editor control differs.Layers
layers is an array of layer objects, bottom to top. Each layer renders into its own texture, taking the texture the layer below rendered as its input, so the layers compose like a chain of effects. The bottom layer's input is a blank, transparent texture. Each layer holds its name, a visible flag, its GLSL code and its own parameters.
| Field | Type | Description |
|---|---|---|
name | String | The layer's name, shown in the Layers list and the Layer window. Free text. |
visible | Boolean | Whether the layer is rendered. A hidden layer, false, stays in the data but is skipped, so the chain passes the layer below straight through. |
code | String | The layer's own GLSL. It must define vec4 LayerMain(), the function the assembled main calls to produce the fragment. It may read the built-in inputs and any parameter uniform in scope, and call helpers from the shared globals. |
params | Array | The layer's own parameters, declared as uniforms in that layer only, alongside the image wide ones. Same shape as the image parameters. Zero or more. |
Composition
A layer is never stored as complete GLSL; the stored code is just the layer's own part. The full source a layer compiles from is assembled in a fixed order, so the same stored fields always produce the same shader:
- The built-in prelude: the GLSL version and precision, the built-in uniforms
uInput, the texture the layer below rendered, anduResolution, the image size in pixels, the fragment output, and the built-in per-fragment values the code may read. - The image parameter uniform declarations, one
uniform <type> <name>;per image parameter. - The layer parameter uniform declarations, the same for the layer's own parameters.
- The shared globals, the
globalsstring. - The layer code, the layer's
codestring, which definesLayerMain. - The built-in main, which fills the built-in per-fragment values, then composites
LayerMain()over the layer below with a straight-alpha source-over and writes that to the fragment output. So aLayerMainthat returns a lower alpha lets the layer below show through, and an opaque one replaces it; a layer need not sampleuInputitself to stack over the one below, though it may for an effect.
The built-in per-fragment values the main fills, and the layer code may read, all take the top left as their origin, so +x runs right and +y runs down. They are: Resolution, the image size in pixels; Pixel, the fragment in pixels; Uv, the same as 0 to 1, for sampling uInput; and three centred coordinates sharing the image centre as origin, Unit, which runs -1 to 1 across the minor (shorter) axis so it stays square with the longer axis extending past 1, UnitMajor, which runs -1 to 1 across the major (longer) axis so the whole image fits within the unit square, and UnitScaled, which runs -1 to 1 on both axes independently, stretched to the aspect. This assembly is pure text and is performed by Sprites.Media.Shader.Source, so it follows the parameters, the shared code and the layer code reactively; the render driver recompiles when the assembled text changes.
Persistence
The record is structured-clone persisted in IndexedDB, the data store, keyed by id. The meta record, the name, the size and layer-count summary and the thumbnail, lives beside it under the same id in the meta store. Every field here is a plain string, number, boolean, array or object, so the record round-trips through structured clone and through a JSON export unchanged. See Sprites.Media.Shader for the editor and the model that read and write this format.
Full example
A 512×512 shader with one image parameter and two layers. The bottom layer lays down a gradient; the top layer reads the layer below through uInput and tints it, using an image parameter and a helper from the shared globals.
{
"media": "shader",
"w": 512,
"h": 512,
"globals": "float Circle(vec2 p, float r) { return 1.0 - smoothstep(r - 0.01, r, length(p)); }",
"params": [
{ "name": "uTint", "type": "colour", "value": { "optionalVec4": { "x": 1, "y": 0.5, "z": 0.2, "w": 1 } } }
],
"layers": [
{
"name": "Gradient",
"visible": true,
"code": "vec4 LayerMain() { return vec4(0.5 + 0.5 * Unit, 0.5, 1.0); }",
"params": []
},
{
"name": "Tint",
"visible": true,
"code": "vec4 LayerMain() { vec4 below = texture(uInput, Uv); float m = Circle(Unit, 0.6); return mix(below, below * uTint, m); }",
"params": []
}
]
}
data object of a two-layer shader image. It sits in the data store beside the id. Each layer's code is its own part only; the built-in prelude, the uniform declarations, the shared globals and the built-in main are assembled around it at compile time, in the order under Composition.