Skip to main content

vcti-shader-atom-lut

The atom-lut shader feature: the vertex-stage lookup that reads per-atom state from a client-owned table.

Overview

A viewer needs to hide, show, isolate, select, recolour and drag parts of a mesh around while the user works. Rebuilding geometry for each of those is slow and gets slower as the model grows, so this feature does it differently: the mesh is divided once into atoms — the smallest addressable render units — and the client keeps a row of state per atom, indexed by a dense atom-id that every vertex carries as an attribute.

Each of those interactions is then one texture write. No geometry is rebuilt, no buffer repacked, and the cost does not depend on how the affected atoms are scattered through the mesh.

vcti-shader-atom-lut is the shader half of that arrangement. It ships the Slang that addresses the table and reads its slots, a Python mirror of the same layout so a caller can build and inspect a row, the specs saying what the lookup needs bound, and the ShaderDefinition saying what the feature is.

Everything here is a declaration or fixed shader source. Nothing compiles or runs a shader; a build step does that, using what this package declares.

Installation

pip install vcti-shader-atom-lut

Requires Python 3.12, 3.13, or 3.14, matching vcti-shader-base. Nothing native is built on that path, and nothing native is built by [test] either — only the [gl] extra pulls a GL binding, and only on 3.14 does that compile from source for want of a cp314 wheel.

In requirements.txt

vcti-shader-atom-lut>=1.0.0

In pyproject.toml dependencies

dependencies = [
    "vcti-shader-atom-lut>=1.0.0",
]

Quick Start

Build an atom's row

The client owns the table, so building its rows is the first thing a caller does. A row is the flags, plus whichever payloads the deployment uses:

import math
from vcti.shader.atomlut import Material, Transform, axis_angle, pack_row

row = pack_row(
    visible=True,
    material=Material(diffuse=(200, 40, 40, 255), ambient=(20, 4, 4), shininess=64),
    transform=Transform(
        translation=(1.0, 2.0, 3.0),
        rotation=axis_angle((0.0, 0.0, 1.0), math.pi / 2),
        scale=2.0,
    ),
)
assert len(row) == 12

The transformed flag is derived, never passed — it is true exactly when a transform is supplied that is not the identity, which is what keeps the flag and the slots from disagreeing:

from vcti.shader.atomlut import atom_transformed, atom_visible

assert atom_visible(row[0])
assert atom_transformed(row[0])
assert not atom_transformed(pack_row(transform=Transform())[0])

Read it back

The same layout the shader reads, read in Python — which is what lets a caller answer questions without a GPU, and what the shader tests diff against:

from vcti.shader.atomlut import unpack_material, unpack_transform

assert unpack_material(row).diffuse == (200, 40, 40, 255)
placed = unpack_transform(row).apply((1.0, 0.0, 0.0))
assert [round(component, 6) for component in placed] == [1.0, 4.0, 3.0]

Visibility is a bit test — flags & 1 — and the shader's test is the same one, so there is no threshold the two sides can disagree about.

Upload only what you use

A row is stride consecutive words. The stride a client uploads is decided by the behaviours its pipeline enables, and the shader reads only the slots those need:

from vcti.shader.atomlut import stride_for

assert stride_for(["visibility"]) == 1                      # 4 bytes per atom
assert stride_for(["visibility", "atom-material"]) == 4     # 16 bytes per atom
assert stride_for(["visibility", "atom-transform"]) == 12   # 48 bytes per atom

The table is two-dimensional, because a single row would cap the atom count at whatever MAX_TEXTURE_SIZE a device reports. A caller picks a width, and an address wraps across texture rows:

from vcti.shader.atomlut import address, texel

word = address(1000, 0, 12)        # atom 1000, slot 0, stride 12
assert word == 12_000
assert texel(word, 2048) == (1760, 5)

Upload the words as an R32UI texture of that width with NEAREST filtering, and pass the width and stride as u_atomLutWidth and u_atomLutStride.

What a build step binds

from vcti.shader.atomlut import vertex_attributes, vertex_uniforms

attributes = vertex_attributes()
assert attributes[0].name == "a_atomId"
assert attributes[0].semantic == "atom-id"

assert [u.name for u in vertex_uniforms()] == ["u_atomLutWidth", "u_atomLutStride"]

The table sampler itself is not in there — a texture input cannot yet be expressed in vcti-shader-base, so it is contract rather than spec. See docs/design.md.

Select a pipeline

One tag per behaviour, plus one naming the table encoding. A consumer asks for the behaviours it needs and the format it uploads:

from vcti.shader.atomlut import DEFINITION, Encoding, capability_for_encoding

assert DEFINITION.role.value == "vertex"
assert capability_for_encoding(Encoding.R32UI) == "atom-lut-r32ui"
assert set(DEFINITION.capabilities) == {
    "visibility",
    "atom-material",
    "atom-transform",
    "atom-lut-r32ui",
}

Before emitting a shader, check the encoding can carry the slots those behaviours need. A 16-bit table holds the flags but no payload slot:

from vcti.shader.atomlut import check_encoding, slots_exceeding

check_encoding(Encoding.R32UI, ["visibility", "atom-material"])   # returns None
check_encoding(Encoding.R16UI, ["visibility"])                    # flags fit
assert slots_exceeding(Encoding.R16UI)

The row

Slot Holds Needed by
0 flags — visible, selected, dead, transformed visibility
1 diffuse RGBA atom-material
2 ambient RGB + shininess atom-material
3 specular RGB atom-material
4-6 translation atom-transform
7-10 rotation quaternion, scalar last atom-transform
11 uniform scale atom-transform

Colours are bytes; the transform is float32 bits, recovered in the shader by reinterpretation, which is exact.

API surface

Name What it is
DEFINITION the ShaderDefinition a build step imports to compose this feature
SLANG_DIR, SLANG_MODULES the installed Slang directory, and the modules in it
pack_row, unpack_material, unpack_transform build and read an atom's row
Material, Transform, axis_angle the payloads, and a rotation helper
pack_flags, atom_visible, atom_selected, atom_dead, atom_transformed the state word
SLOTS, FULL_STRIDE, address, texel, rows_needed the layout and its addressing
stride_for the smallest stride a set of behaviours needs
Encoding, IMPLEMENTED_ENCODINGS, word_bits the table encodings and their capacity
check_encoding, slots_exceeding reject an encoding too narrow for a slot
capability_for_encoding, slang_module_for_encoding the tag and module naming one

Dependencies

vcti-shader-base is the only runtime dependency — declaring a feature is pure data. vcti-shader-compiler>=4.0.0 and numpy are test-only, and a separate gl extra adds the GL binding the shader tests need to execute rather than skip.

Documentation

If you want to… Read
Get started using the package Quick Start above
Understand the table contract and the decisions behind it docs/design.md
Navigate or modify the source, including the Slang docs/source-guide.md

The full API reference is generated from the source docstrings and published in the unified VCollab docs.

Metadata

Release files for vcti-shader-atom-lut 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for vcti-shader-atom-lut 1.0.0
File Size Uploaded
vcti_shader_atom_lut-1.0.0.tar.gz 52.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for vcti-shader-atom-lut 1.0.0
File Interpreter ABI Platform
vcti_shader_atom_lut-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 77.4 kB

Release files / vcti_shader_atom_lut-1.0.0.tar.gz

Download URL vcti_shader_atom_lut-1.0.0.tar.gz
Size 52.2 kB
Tags Source
SHA-256 checksum
How to use checksums
fcec1013c230c8c081427983b2ba4861d446130216592def63af4561042115d7
BLAKE2b-256 checksum
How to use checksums
023b7a18d3d53bd25571ba93f1355963679d16841d1da9f9132347f2816563eb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.

Transparency log

Release files / vcti_shader_atom_lut-1.0.0-py3-none-any.whl

Download URL vcti_shader_atom_lut-1.0.0-py3-none-any.whl
Size 25.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4e7d9ee77e8950113e2d2a76e6049f202e85a146f7742a7154fe856486c2067b
BLAKE2b-256 checksum
How to use checksums
5ecd56f78a5d448e167265eaf259143cb02d9af6484db1d3b41cea40f1487f07
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page