API Reference · Materials and textures

Texture

Namespace: Ava3D

public sealed class Texture

An image: either encoded bytes (PNG or JPEG) or raw RGBA pixels.

Decoding is deferred to the renderer, and deliberately so: a scene like Sponza carries twenty-five of these, and decoding them all up front allocates hundreds of megabytes at once — which is survivable on desktop and is not in a 32-bit WebAssembly heap. Raw pixels are deferred the same way: they stay in managed memory until a renderer uploads them, and the same streaming budget applies.

Use Texture.FromPixels for anything generated at runtime. Encoding a procedural texture to PNG only for the renderer to decode it again is pure round-trip waste, and it lands on the thread that can least afford it.

Textures are shared by identity, like Mesh; the same instance on many materials is uploaded once.

Properties

MemberDescription
TextureColorSpace ColorSpace { get; set; }

How this texture's bytes are encoded — sRGB, linear, or whatever the slot expects.

TextureColorSpace.Auto unless said otherwise, which reads a base-colour, emissive or matcap map as sRGB and every other map as linear data. Set TextureColorSpace.Linear on a colour map whose values were computed in linear light — a gradient, a noise field, a ramp written straight from a float — and it shows as those values rather than as their sRGB decode, which is darker. Set TextureColorSpace.Srgb on a data map that is really a photograph — a picture used as an occlusion map — and it is decoded before it is read.

Honoured the same way on every renderer, at upload or decode rather than in the shaders: the bytes are re-encoded once into what the slot expects. An eight-bit linear image through a colour slot keeps eight bits of sRGB, which is coarser in the darks than the linear original; for values that have to survive exactly, keep the map linear and put it in a data slot. It has no effect on an environment image or on a sprite's texture.

byte[] Encoded { get; set; }

PNG or JPEG bytes, exactly as they came from the source file. Null when this texture carries Texture.Pixels instead.

Exactly one of the two has to be set, and prefer Texture.FromEncoded or Texture.FromPixels to setting either by hand — this was required until raw pixels gave it an alternative, and C# cannot express "one of these two", so the compiler no longer insists. Setting neither leaves a surface rendering as flat base colour with the reason available only through RenderInfo.

TextureFilter Filter { get; set; }

How the image is sampled between its texels. TextureFilter.Linear unless the grid is the content — see TextureFilter.Nearest.

TextureFormat Format { get; set; }

What Texture.Pixels holds, when it is not RGBA8.

TextureFormat.Rgba8 for everything built by Texture.FromPixels and everything decoded from PNG or JPEG, which is what the whole library did before compressed textures existed.

int Height { get; set; }

Height in pixels. Only meaningful with Texture.Pixels.

bool IsCompressed { get; }

Whether this texture's bytes are block-compressed and must go to the card as they are.

bool IsRaw { get; }

Whether this texture carries raw pixels rather than encoded bytes.

int[] MipOffsets { get; set; }

Where each mip level begins in Texture.Pixels, largest first, or null for a single level.

Compressed textures carry their own chain: a block format cannot be filtered down by glGenerateMipmap on every context, and re-compressing on the device is not something to do in a frame. So the chain comes in the file or there is none.

string Name { get; set; }

Optional name, carried through from the source image for diagnostics.

byte[] Pixels { get; set; }

Tightly packed RGBA8, row-major, top-left origin: Width × Height × 4 bytes with no padding between rows. Null when this texture carries Texture.Encoded instead.

int Revision { get; }

How many times Texture.Refresh has been called. Renderers upload again when it moves.

int Width { get; set; }

Width in pixels. Only meaningful with Texture.Pixels.

TextureWrap WrapU { get; set; }

How the horizontal texture coordinate behaves outside 0..1.

TextureWrap WrapV { get; set; }

How the vertical texture coordinate behaves outside 0..1.

Methods

MemberDescription
static Texture Compressed(TextureFormat format, byte[] data, int width, int height, int[] mipOffsets = null, string name = null)

A block-compressed image, in bytes the card can take without decoding.

format

Which compression. See TextureFormat.

data

The blocks, largest mip level first.

width

The largest level's width in texels.

height

The largest level's height in texels.

mipOffsets

Where each level starts in data, largest first, or null when there is one level.

name

Optional name, for diagnostics.

static int CompressedSize(TextureFormat format, int width, int height)

How many bytes one level of a compressed image takes.

Block formats round up to whole blocks, which is why a 5×5 BC1 image is the same size as an 8×8 one. Getting that rounding wrong is how a payload comes out short by exactly one row of blocks, which the driver reads past the end of.

static Texture DetailField(int size = 512, int octaves = 4, float persistence = 0.5f, int seed = 0, string name = "detail")

A tileable detail field: a value, its own gradient, and a finer octave, packed into one texture.

size

Width and height in pixels. A power of two tiles most cleanly; 1024 is the useful size.

octaves

How many doublings of frequency are summed into the value.

persistence

How much each octave contributes relative to the one before it.

seed

Changes the field without changing its character.

name

Optional name, for diagnostics.

static Texture FromEncoded(byte[] encoded, string name = null, TextureWrap wrap = TextureWrap.Repeat, TextureFilter filter = TextureFilter.Linear)

A texture from PNG or JPEG bytes, decoded by whichever renderer first needs it.

encoded

The file's bytes, exactly as they came off disk or the wire.

name

Optional name, for diagnostics.

wrap

How coordinates outside 0..1 behave, on both axes.

filter

How to sample between texels. See TextureFilter.

static Texture FromPixels(byte[] rgba, int width, int height, string name = null, TextureWrap wrap = TextureWrap.Repeat, TextureFilter filter = TextureFilter.Linear)

A texture from raw pixels, for anything generated at runtime.

rgba

Tightly packed RGBA8, row-major, top-left origin.

width

Width in pixels.

height

Height in pixels.

name

Optional name, for diagnostics.

wrap

How coordinates outside 0..1 behave, on both axes. TextureWrap.MirroredRepeat is worth knowing about for anything generated: a noise field tiled across a ground plane shows its seams at every tile boundary, and mirroring makes those edges match exactly without the generator having to be written tileable.

filter

How to sample between texels. TextureFilter.Nearest for a small image meant to be seen large with its pixels intact — the case this factory exists for as often as not.

static Texture FromText(string text, TextOptions options = null)

A string, drawn into a texture, sized to what it actually measured.

The measuring is the reason this is here rather than in the application. Drawing text into an image is a few lines of Skia and any host can do it; knowing how wide the result came out is what a quad in a scene needs and what a host cannot hand over with the pixels. Miss it and every label is stretched by however far the guess was out — which on a face that fell back to a substitute is a different amount for every string, on the machines least likely to be the ones it was authored on. LabelNode reads Texture.Width and Texture.Height back off the result and shapes itself to them.

The image is tight to the ink plus whatever TextOptions.Outline needs, transparent everywhere else, and clamped rather than repeated — a label that tiles is never what was meant.

text

The string. Empty gives a 1×1 transparent texture rather than an exception.

options

The face, the size, the ink. Null takes every default.

static Texture Glow(int size = 128, float falloff = 2.0f, string name = "glow")

A radial glow: opaque and white at the centre, fading to nothing at the rim.

size

Width and height in pixels. 128 is enough for a sprite seen at any size.

falloff

How sharply the alpha falls away. One is a linear cone; higher is a tighter core with a longer tail, which is what reads as a light rather than as a disc. Two is a good default and four is a hard little spark.

name

Optional name, for diagnostics.

static Texture Noise(int size = 128, int octaves = 4, float persistence = 0.5f, int seed = 0, string name = "noise")

Tileable fractal value noise, grey in RGB and opaque.

size

Width and height in pixels. A power of two tiles most cleanly.

octaves

How many doublings of frequency are summed. One is a soft blur; four is the usual cloudy field; past six the extra octaves are finer than a texel and cost time for nothing.

persistence

How much each octave contributes relative to the one before it.

seed

Changes the field without changing its character.

name

Optional name, for diagnostics.

static Texture NormalFromHeight(Texture height, float strength = 1.0f, bool wrap = true, string name = "normal")

A normal map from a height map.

height

The heights, read from the red channel. Any texture with raw pixels will do; an encoded one is refused, because this has to read the numbers rather than hand them to a decoder.

strength

How steep the result is. One is the literal slope of the heights as given; larger exaggerates.

wrap

Whether the gradient wraps at the edges. True for a tiling map, which is what makes the seam disappear; false for a decal or a one-off, where wrapping would fold the far edge into the near one.

name

Optional name, for diagnostics.

void Refresh()

Says that Texture.Pixels has been written to and the renderers should look again.

A texture is cached by identity, so a renderer that has uploaded one never reads its bytes a second time — which is right for the overwhelming majority of textures and wrong for the few that change: a video frame, a minimap, a procedural map being animated, a game drawn into a buffer. The obvious workaround is to build a new Texture each time, and it has a visible cost that is not obvious at all. Uploads are streamed a few per frame to keep a model with twenty-five maps in it from stalling the first frame, so a texture nobody has seen before draws as flat white until its turn comes — and a texture replaced every tenth of a second is therefore white for one frame in every six, which reads as a flicker and is very hard to attribute to anything.

So: keep the instance, write into the array you gave it, and call this. The handle already uploaded keeps being drawn — showing the previous picture, which is at worst one frame stale — right up to the moment the new pixels replace it. Nothing is ever white and nothing is allocated.

See also