Metadata-Version: 2.4
Name: vcti-shader-base
Version: 1.0.0
Summary: The vocabulary a shader feature declares itself with: attribute/uniform/output specs and the ShaderDefinition record. Zero dependencies.
Author: Visual Collaboration Technologies Inc.
License-Expression: LicenseRef-Proprietary
Project-URL: Repository, https://github.com/vcollab/vcti-python-shader-base
Project-URL: Changelog, https://github.com/vcollab/vcti-python-shader-base/blob/main/CHANGELOG.md
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: <3.15,>=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Requires-Dist: pytest-cov; extra == "test"
Provides-Extra: lint
Requires-Dist: ruff; extra == "lint"
Provides-Extra: typecheck
Requires-Dist: mypy; extra == "typecheck"
Dynamic: license-file

# vcti-shader-base

The vocabulary a shader feature declares itself with: attribute/uniform/output specs and the ShaderDefinition record. Zero dependencies.

## Overview

Shaders in this system are compiled ahead of time.

A build step turns Slang sources into GLSL ES text. Whatever draws with the
result later — a web viewer, a test harness — did not compile it and cannot
inspect it. So it has to be *told* what the shader expects:

- which buffer belongs in each vertex attribute,
- which uniforms exist and how large they are,
- which integer selects which mode.

Writing that down is what this package is for.

A **shader feature** is one piece of composable shading math. Here are some
examples:

- `deform` moves geometry,
- `fringe` colors it by bands,
- `derive` computes a quantity from a source field — scalar, vector, 6-DOF or
  tensor,
- `atom-lut` culls hidden atoms.

Each ships as its own installable package. Each declares what its own math needs,
and says what it is.

`vcti-shader-base` is the vocabulary for writing exactly that declaration, and
nothing more:

- **Field specs** — `AttributeSpec` (per-vertex inputs), `UniformSpec`
  (draw-constant values), `OutputSpec` (fragment outputs).
- **The definition** — `ShaderDefinition`, with `StageRole`: how a feature names
  the stage it runs in, the Slang modules it ships, and the capability tags it
  introduces.

Declaring a feature needs **nothing else** — no compiler, no build toolchain, no
other package.

## Installation

```bash
pip install vcti-shader-base
```

Requires Python 3.12, 3.13, or 3.14. No runtime dependencies.

### In `requirements.txt`

```
vcti-shader-base>=1.0.0
```

### In `pyproject.toml` dependencies

```toml
dependencies = [
    "vcti-shader-base>=1.0.0",
]
```

## Quick Start

### Declare what the shading math needs

Consider a structural analysis. A CAE solver reports how far each node of a mesh
moves under a load, and we want to draw the deformed shape.

Every vertex carries two values — where it sits, and how far it moved. Both vary
per vertex, so both are **attributes**. We also want to exaggerate the movement,
scaling it independently in x, y and z; that factor is the same for every vertex
in the draw, so it is a **uniform**:

```python
from vcti.shader.base import AttributeSpec, UniformSpec

inputs = (
    AttributeSpec("a_position", "vec3", "coordinates"),
    AttributeSpec("a_deformation", "vec3", "deformation"),
)
uniforms = (UniformSpec("u_deformScale", "vec3"),)
```

`a_position` and `a_deformation` are the names the *shader source* uses. The
third argument is the **semantic**, and it is what makes the declaration useful
to a caller. A caller has buffers of its own — node coordinates, a displacement
field — and must know which one goes where. The name cannot answer that: it is a
shader-source detail and can be renamed. The semantic names the data instead, so
`coordinates` means mesh node positions and `deformation` means the displacement
vector.

Two optional fields are worth knowing. `array_length` stays a **number** so Python
can size a buffer, and `gl_type` joins it onto the type only where the type is
emitted:

```python
UniformSpec("u_bandColors", "vec4", array_length=8).gl_type   # 'vec4[8]'
UniformSpec("u_deformScale", "vec3").gl_type                  # 'vec3'
```

`named_values` turns an integer a caller would otherwise hard-code into something
nameable:

```python
mode = UniformSpec("u_deformMode", "int", named_values={"displacement": 0, "rotation": 1})
mode.named_values["rotation"]      # 1 — the value to write
```

A feature in the fragment stage also declares what it writes:

```python
from vcti.shader.base import OutputSpec

outputs = (OutputSpec("fragColor", "vec4"),)
```

### Describe the feature itself

The specs say what the shading math needs. A `ShaderDefinition` says what the
feature *is*. Each feature constructs exactly one and exports it as `DEFINITION`:

```python
from pathlib import Path
from vcti.shader.base import ShaderDefinition, StageRole

DEFINITION = ShaderDefinition(
    id="deform",
    role=StageRole.VERTEX,
    capabilities=("deform3", "deform6"),
    slang_modules=("deform.slang",),
    slang_dir=Path(__file__).parent / "slang",
    description="deform3 scaled displacement; deform6 Rodrigues rotation.",
)
```

- **`role`** is where the feature's math runs. `VERTEX` moves geometry;
  `FRAGMENT` decides color.
- **`capabilities`** are the tags this feature offers. They are opaque strings and
  each feature owns its own, so adding one needs no release of this package.
- **`slang_modules`** names the Slang modules this feature publishes for a shader
  to import. **`slang_dir`** says where they live — and it is the field with teeth:
  the `.slang` files are installed inside the feature's own package, so only the
  feature can resolve the directory, and the build passes it to the compiler as an
  `import` search path. This package only records the path; it never opens it, so
  confirming the files are really there is the feature's own test's job.

A feature that stops here is complete: it constructs one `ShaderDefinition`,
exports it as `DEFINITION`, and declares the specs its math needs.

## Type Reference

| Type | Fields | Notes |
|---|---|---|
| `AttributeSpec` | `name`, `type`, `semantic` | A per-vertex input, and what its data *is* |
| `UniformSpec` | `name`, `type`, `array_length=None`, `named_values=None` | `gl_type` joins the array suffix; `named_values` only on dispatch uniforms |
| `OutputSpec` | `name`, `type` | A fragment output; no semantic, since there is nothing to bind |
| `ShaderDefinition` | `id`, `role`, `capabilities`, `slang_modules`, `slang_dir`, `description=""` | One feature's self-declaration |
| `StageRole` | `VERTEX`, `FRAGMENT` | Where the feature's math runs |

Every type is immutable and compared by value, and every one is hashable — so
specs can go in a set or a dict key, and duplicates drop out on their own.

## Dependencies

None — the standard library covers it. A feature can declare its specs and its
definition without installing a compiler or a build toolchain behind it.

Development extras: `test` (pytest, pytest-cov), `lint` (ruff), `typecheck`
(mypy).

## Documentation

| If you want to… | Read |
|---|---|
| Get started using the package | Quick Start above |
| Build and ship a complete feature, and avoid the pitfalls | [docs/patterns.md](docs/patterns.md) |
| Understand what these types describe and why | [docs/design.md](docs/design.md) |
| Navigate or modify the source | [docs/source-guide.md](docs/source-guide.md) |
