acron.gpu

Pixel buffers <-> Acron textures, and the way back out of an offscreen scene.

A procedural game is baked fields: height fields, masks, drawn imagery. Acron wants Texture objects with RAM images in its own layout, so this is the one place that conversion lives. Every upload takes any buffer — bytes, bytearray, array.array, a memoryview, a PTA_float, the array('f') acron.noise.field() returns (and, through the same buffer protocol, a numpy array, without this module importing numpy) — or a nested list of rows. A flat buffer says its shape with size=(width, height); a multi-dimensional one (memoryview.cast(fmt, (h, w))) carries it.

Two things here are easy to get wrong and silent when you do:

  • Row order. Acron’s RAM images start at the BOTTOM row (OpenGL convention); a field written top-row-first arrives mirrored in v. A computed FIELD whose row j increases the same way v does uploads as-is (float_texture() and friends); a DRAWN image — painted top row first — must be flipped on the way in (image_texture()). Flip the wrong one and the result looks plausible until physics or lettering disagrees with it.

  • Channel order. Acron’s RGB(A) RAM images are BGR(A); the RGB(A) uploads here take RGB(A) and let Texture.set_ram_image_as swizzle.

  • Mip levels must be uploaded explicitly. Acron will happily generate its own from the base level, but a consumer that samples a specific pre-filtered chain (a clipmap’s coarse rings, say) needs the chain the CPU knows about — an auto-generated one is not it.

And the way back out, for a scene built to be released whole (found releasing REGOLITH’s basin per chapter, 2026-08-27):

  • An offscreen pass is a buffer, a camera and the engine’s window list (drop_buffer()): the camera comes off ShowBase’s camList and the graph, the buffer is deactivated, its render textures cleared and the window removed from the engine — in that order, or a frame can still draw into it on the way out.

  • The engine’s state caches pin textures (purge_state_caches()): a composed RenderState holds its textures, and the caches keep composed states, so a released scene’s textures stay prepared on the GPU until the caches are cleared (they refill on the next cull); then the collector takes the cycles among the scene’s collaborators now rather than when it gets around to it — the point of releasing is the memory.

Global Functions

byte_texture(name: str, field, *, size=None, wrap: bool = False) → Texture

An 8-bit single-channel texture (masks, trails).

drop_buffer(base, buffer, camera=None) → None

Retire an offscreen buffer and the camera that drew into it.

float_texture(name: str, field, *, size=None, wrap: bool = False, mips: list | None = None, fmt=35) → Texture

A single-channel float32 texture from a field (row 0 = v 0).

mips is the whole chain, level 0 first (level 0 is field and is skipped); each level is a buffer or rows like field, half the size of the one before.

half_texture(name: str, field, *, size=None, wrap: bool = False) → Texture

A single-channel HALF-float texture — for live fields re-uploaded often.

Half is the right width for a field that only spans a few metres of range (sub-millimetre precision) and goes back to the GPU every frame it changes: Acron re-uploads a whole texture whenever any part of it changes — the GL backend has no partial-upload path — so the width of a texel is the width of that upload. field holds floats (any width); they are rounded to half here.

image_texture(name: str, drawing, *, size=None, wrap: bool = False, srgb: bool = True) → Texture

A DRAWN RGB image as a texture — top row first, the way it was painted.

The difference from rgb_texture() is one flip, and it is the trap this module exists to hold in one place: Acron’s RAM images start at the bottom row, which is what a computed field wants (its rows run the same way v does) and the opposite of what a drawing wants. A texture uploaded the wrong way up is not obviously wrong — it is lettering that reads upside down on a surface you have to walk round to see.

purge_state_caches() → None

Clear the engine’s composed-state caches and collect, so a released scene’s textures and the cycles among its objects go now.

rgb_texture(name: str, field, *, size=None, wrap: bool = True, srgb: bool = False) → Texture

An 8-bit RGB texture from RGB rows (row 0 = v 0).

srgb asks the hardware to decode on sample. Everything downstream of a lit shader is linear radiance, so an image authored as sRGB has to be decoded somewhere, and the sampler does it for free and at full precision. A mask carries no colour and stays linear.

rgba_texture(name: str, field, *, size=None, wrap: bool = False, flip: bool = False, srgb: bool = False) → Texture

An 8-bit RGBA texture from RGBA rows — sprites with alpha. flip for a drawing painted top row first (see image_texture()).

update_half_rows(tex: Texture, field, z0: int, z1: int) → None

Push rows z0..z1 (inclusive) of a float field into a half texture.

Whole ROWS are the honest unit of work here: the RAM image is row-major, so a sub-row write would be a scatter, and Acron re-sends the whole texture either way. (This function once took an x-range too, and silently ignored it — the signature now says what it does.) field is the whole field (rows of floats, or a buffer of the texture’s width x height floats); rows outside the texture are clamped away.

vertex_view(vdata: GeomVertexData, floats_per_vertex: int) → memoryview

A writable float32 memoryview of a single-array GeomVertexData, shaped (rows, floats_per_vertex).

Index it view[row, column]; for column runs take the flat form, view.cast("B").cast("f"), whose strided slices assign in one go (flat[k::stride] = array("f", ...)).

Taking it through modify_array is also what tells Acron the rows changed, so this is called once per frame by anything that rewrites its geometry — a particle pool, a trail of quads.

The stride is checked rather than assumed. Acron packs float columns end to end today, but a padded array viewed as if it were not would scramble every vertex silently, and a wrong picture is much harder to read than an exception.