API Reference · Materials and textures
TextureProgram
public sealed class TextureProgramA 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
| Member | Description |
|---|---|
| Compiles
|
Properties
| Member | Description |
|---|---|
| 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. |
| 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. |
| How many times this program has been evaluated since it was compiled. |
| What the last evaluation cost. Zero until there has been one. |
| How large the computed image is. 256 square by default; each side is clamped to 1..4096. |
| The source as it was handed over, without the prelude. |
| The computed texture. Let a |
| How often the program is evaluated, in hertz. Thirty by default; zero evaluates on every frame. |
Methods
| Member | Description |
|---|---|
| Moves the clock on and evaluates if one is due. Called for you by
|
| 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. |
| Sets a |
| Sets a |
| Sets a |
| Sets a |
| Sets a |
| Sets a |
| Sets a |
| Sets a |
| Sets a |
| Sets a |
| Sets a |
| Gives the program a texture to sample, under the name it declares as |
| Forgets a constant: the uniform reads zero until it is set again. False, and nothing done, for a name that was never set. |