API Reference · Materials and textures

TextureProgram

Namespace: Ava3D

public sealed class TextureProgram

A texture whose pixels are computed rather than stored: a small program, evaluated over the surface, with the scene's clock as one of its inputs.

What it is for. Anything animated that is currently a texture atlas — a screen, a terminal, a scrolling sign, a CRT, a readout, a noise field, a dissolve mask. Baking those costs a page per frame per variant, which is memory that grows with how smoothly the thing moves; a program costs one texture however long it runs, and moves continuously instead of stepping through however many frames were baked.

The language is SkSL, Skia's shading language — the same one Flutter and Android's RuntimeShader use, documented at skia.org, and already in this library's dependencies. It is GLSL-shaped, so an author is at home immediately, and deliberately restricted to what every backend can run, so a program that compiles is a program that works. It is compiled once, when the source is handed over: a program that will not compile draws nothing and says why in TextureProgram.Error, rather than reaching a driver.

Every program can call ava3dHash(float), ava3dHash2(float2) and ava3dNoise(float2), which return values in 0..1 and give the same picture on every backend — unlike the common fract(sin(x) * 43758.5453) hash, which does not.

var nav = new TextureProgram("""
    uniform float2 uSize;
    uniform float  uTime;
    uniform float4 uConst;
    half4 main(float2 frag) {
        float2 p = frag / uSize * 2.0 - 1.0;
        float sweep = uTime * 1.6 + uConst.x;
        float lag = mod(sweep - atan(p.y, p.x), 6.2831853);
        float g = smoothstep(1.2, 0.0, lag) * smoothstep(1.0, 0.94, length(p));
        return half4(half3(0.35, 1.0, 0.45) * half(g), 1.0);
    }
    """) { Size = new PixelSize(256, 256) };

nav.Set("uConst", new Vector4(phase, 0, 0, 0));
screen.EmissiveProgram = nav;
One program, many surfaces. Give each screen its own instance of the same source and set a different constant on each; the compile is shared and what differs is a handful of numbers.

What it is not. It computes a texture, so it sees texture coordinates, the clock, its constants and whatever maps it was given — not the surface's normal, the view direction or the scene behind it. Water that ripples its albedo is this; water that bends what is behind it is not. Nothing here reaches the lighting.

What it costs, and where. Evaluation is per pixel on the CPU, on the UI thread, inside the frame callback — about 20 ms for a 512² image on a quiet machine and 1.3 ms at 128². So TextureProgram.Size and TextureProgram.UpdatesPerSecond are the budget rather than conveniences, and TextureProgram.LastEvaluation says what one is actually costing. Programs created together are given different phases of their update period so that twenty of them do not all come due on the same frame.

Which thread, exactly.TextureProgram.Advance writes into TextureProgram.Texture's own pixel array on the UI thread and then bumps its Texture.Revision; a renderer reads that array on the render thread when it next uploads. Those are not synchronised, and deliberately not: the array is overwritten in place at a fixed size, so the worst case is one frame showing part of the previous picture and part of the next — a tear on a screen that is already changing thirty times a second, and gone on the frame after. It is never a malformed texture, because the size never changes underneath a reader: assigning TextureProgram.Size builds a whole new TextureProgram.Texture, and a snapshot already in flight keeps the old one with its own array. A lock on the pixel array would put the render thread behind a 20 ms CPU evaluation, which is a worse trade than a torn frame nobody can see.

Constructors

MemberDescription
TextureProgram(string source)

Compiles source. Check TextureProgram.Error before expecting a picture.

source

SkSL. The entry point is half4 main(float2 fragCoord), where fragCoord is the pixel, not a 0..1 coordinate — divide by uSize for that.

The returned colour is premultiplied, as it is for every Skia runtime shader. At full opacity there is nothing to notice; below it, a half-covered red is half4(half3(1, 0, 0) * 0.5, 0.5), and a colour written unscaled beside a fractional alpha asks for a brighter colour than exists. TextureProgram.Texture itself holds straight pixels — the conversion is done here.

Properties

MemberDescription
IReadOnlyList<string> Diagnostics { get; }

What was set on this program and could not be used, one line each, in the order it happened. Today that is one thing: a constant whose name the compiled program does not have.

string Error { get; }

Why the program will not run, or null. Set at construction when the source does not compile, and later if an evaluation fails; either way the failure stops here rather than on the UI thread. TextureProgram.Texture keeps what it held — transparent for a program that never compiled, the last picture for one that failed while running — and nothing more is evaluated.

long Evaluations { get; }

How many times this program has been evaluated since it was compiled.

TimeSpan LastEvaluation { get; }

What the last evaluation cost. Zero until there has been one.

PixelSize Size { get; set; }

How large the computed image is. 256 square by default; each side is clamped to 1..4096.

string Source { get; }

The source as it was handed over, without the prelude.

Texture Texture { get; }

The computed texture. Let a Material's program property assign it, or assign it yourself to any material map or to a SpriteNode; a view showing it advances the program either way.

double UpdatesPerSecond { get; set; }

How often the program is evaluated, in hertz. Thirty by default; zero evaluates on every frame.

Methods

MemberDescription
void Advance(double seconds)

Moves the clock on and evaluates if one is due. Called for you by Ava3DView.

seconds

The scene's clock, in seconds — an absolute reading, not a delta. A reading earlier than the last is taken as a different clock, and the program draws on it straight away rather than waiting for the old clock's next due time to come round.

void Dispose()

Releases the compiled program and the Skia objects behind it.

A compiled effect is a native object held for the life of this instance, and a game that builds a screen when a room is entered and drops it when the room is left would otherwise leave one behind each time, released only when a finaliser eventually ran. Disposing a program that is still on a material leaves the material's map as it was — the last picture the program drew — and stops it changing.

void Set(string name, SkiaSharp.SKColorF value)

Sets a uniform float4 — or a float3, which takes the colour without its alpha.

void Set(string name, bool value)

Sets a uniform int to one or zero. SkSL has no bool uniform, so a switch is declared as an int in the program and set from here.

void Set(string name, int value)

Sets a uniform int.

void Set(string name, Matrix3x2 value)

Sets a uniform float3x3 from an affine 2D matrix, laid out so that m * float3(p, 1) in the program is Vector2.Transform here.

void Set(string name, Matrix4x4 value)

Sets a uniform float4x4, laid out so that m * float4(p, 1) in the program is Vector4.Transform here.

void Set(string name, Vector2 value)

Sets a uniform float2.

void Set(string name, Vector3 value)

Sets a uniform float3.

void Set(string name, Vector4 value)

Sets a uniform float4.

void Set(string name, Vector4[] value)

Sets a uniform float4 name[N]. The array must hold exactly N vectors.

void Set(string name, float value)

Sets a uniform float. Write 2f: a bare 2 is an int.

void Set(string name, float[] value)

Sets a uniform float name[N]. The array must hold exactly N values; it is copied.

void SetTexture(string name, Texture texture)

Gives the program a texture to sample, under the name it declares as uniform shader and reads with .eval(p).

bool Unset(string name)

Forgets a constant: the uniform reads zero until it is set again. False, and nothing done, for a name that was never set.

See also