Metadata-Version: 2.4
Name: vcti-shader-scenarios
Version: 2.0.0
Summary: Run a shader pipeline on inputs you supply, and check that what it produced is what it should produce
Author: Visual Collaboration Technologies Inc.
License-Expression: LicenseRef-Proprietary
Project-URL: Repository, https://github.com/vcollab/vcti-python-shader-scenarios
Project-URL: Changelog, https://github.com/vcollab/vcti-python-shader-scenarios/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
Requires-Dist: vcti-shader-foundry>=4.0.0
Requires-Dist: vcti-shader-compiler>=4.2.0
Requires-Dist: vcti-shader-fringe>=2.1.0
Requires-Dist: vcti-shader-derive>=3.0.0
Requires-Dist: vcti-shader-mask>=1.1.0
Requires-Dist: vcti-shader-transform>=2.0.0
Requires-Dist: vcti-shader-clip>=1.0.0
Requires-Dist: vcti-shader-material>=1.2.0
Requires-Dist: numpy>=2.3
Provides-Extra: gl
Requires-Dist: vcti-shader-compiler[gl]>=4.2.0; extra == "gl"
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-scenarios

Run a shader pipeline on inputs you supply, and check that what it produced is what it should produce.

## Overview

`vcti-shader-foundry` composes and emits the shader artifact set, and proves
each pipeline compiles, links and rasterizes. What it cannot say is whether
*this* pipeline, given *these* attributes, *these* tables and *these* uniforms,
still produces the image it produced yesterday. This package answers that.

A **scenario** declares a run — a pipeline id, the attribute arrays, the tables
and textures, the uniform values — and validates itself against the pipeline's
own contract, so a missing attribute or a mis-shaped buffer is a message rather
than a garbled picture. Every pipeline carries mask, transform and clip with
their off state as data; the **baseline** functions produce that data from the
contract, so a scenario is written as the baseline plus what it is about. A
**runner** owns one GL context and compiles each pipeline once through the
builder's own compile step, so a matrix of a hundred scenarios costs one context
and twenty-seven compiles, and the stage hashes it records are the hashes a
published artifact set carries. The result is a **float32 framebuffer** plus a
record of what produced it: the shaders, the toolchain, this package's own
version, and the GL driver.

Two ways to use it. Declare scenarios in tests and compare the framebuffer with
numpy — that is the regression matrix. Or run one from a terminal and look at
the output — that is the debugging path, where the expectation is the thing in
doubt.

## Installation

```bash
pip install vcti-shader-scenarios         # declare and validate scenarios
pip install "vcti-shader-scenarios[gl]"   # and run them
```

GL is an extra rather than a dependency: building and checking a scenario needs
no context. The shader compilers (`slangc`, `spirv-cross`, `glslang`) are
external and pinned — discovered via `SLANG_DIR` / `SPIRV_CROSS_DIR` /
`GLSLANG_DIR` or `PATH`.

## Quick Start

Declare a scenario, run it, and assert on the result:

```python
from vcti.shader.scenarios import (
    Runner, Scenario, assert_close, baseline_attributes, baseline_textures,
    baseline_uniforms, cartesian_grid, demo_style, diagonal_ramp, pipeline_contract,
)

contract = pipeline_contract("scalar")
positions, indices = cartesian_grid(32)
scenario = Scenario(
    pipeline="scalar",
    attributes={
        **baseline_attributes(contract, len(positions)),   # ids, normals: the off state
        "a_position": positions,
        "a_result0": diagonal_ramp(positions),
    },
    uniforms={**baseline_uniforms(contract), **demo_style().uniforms(contract)},  # the whole block
    indices=indices,
    size=(128, 128),
    textures=baseline_textures(contract),                  # mask, transform, material rows
)

with Runner() as runner:
    result = runner.run(scenario)

# The demo colormap runs blue -> red along the diagonal, so the corners are the
# ends of the ramp. This pins the colour lookup and the image orientation.
assert_close(result.framebuffer[-1, 0, 2], 1.0, atol=1e-3)   # blue end
assert_close(result.framebuffer[0, -1, 0], 1.0, atol=1e-3)   # red end
```

One runner serves a whole matrix — in pytest, make it a session-scoped fixture:

```python
import numpy as np
import pytest

@pytest.fixture(scope="session")
def runner():
    with Runner() as instance:
        yield instance

@pytest.mark.parametrize("pipeline", pipeline_ids())
def test_every_pipeline_draws(runner, pipeline):
    result = runner.run(scenario_for(pipeline))
    assert len(np.unique(result.framebuffer.reshape(-1, 4), axis=0)) > 1
```

From a terminal, to look at one rather than assert on it:

```console
$ shader-scenarios pipelines
$ shader-scenarios run tests.scenarios:SCALAR_RAMP -o out.png
$ shader-scenarios run --pipeline scalar \
      --attributes vertices.npz --indices tris.npy --textures tables.npz \
      --uniforms style.toml --size 256x256 -o out.npz
```

The extension decides the format: `.png` to look at, `.npy` for the float
framebuffer, `.npz` for the framebuffer and its provenance together.

## Key API

| | |
|---|---|
| `Scenario` | a pipeline, its attributes, uniforms, textures, indices and size — each complete, validated on construction |
| `baseline_attributes`, `baseline_uniforms`, `baseline_textures` | the off state every pipeline binds, from its contract |
| `Runner` | one GL context and one compile cache; `run(scenario) -> RunResult` |
| `RunResult` | `.framebuffer` (float32, or uint32 for the pick shape; read-only) and `.provenance` |
| `assert_close` | comparison with a tolerance, and a failure message worth reading |
| `cartesian_grid`, `diagonal_ramp` | test geometry and a demo field |
| `demo_style`, `FringeStyle`, `CategoryStyle` | a colormap and the colour selector, packed into a pipeline's uniforms |
| `quantise`, `to_png` | float framebuffer to 8-bit, and to PNG bytes |
| `pipeline_ids`, `pipeline_contract` | what can be run, and what it needs |

A scenario may pin what it was built against — `expected_contract_sha256` for
compatibility, `expected_stage_sha256` for the exact emitted GLSL — and the
runner refuses rather than quietly exercising different shader code. The stage
hashes are the ones a published artifact set records for the same build.

## Dependencies

`vcti-shader-foundry` (the pipeline matrix, the generated Slang and the compile
step), `vcti-shader-compiler`, `vcti-shader-fringe` (the band model a style is
built from), the features whose off state the baseline supplies
(`vcti-shader-derive`, `-mask`, `-transform`, `-clip`, `-material`), and
`numpy`. `moderngl` comes with the `gl` extra. The shader toolchain executables
are external, not pip dependencies.

## Documentation

| If you want to… | Read |
|---|---|
| Get started | Quick Start above |
| Write scenarios for real work | [docs/patterns.md](docs/patterns.md) |
| Understand the design and its boundaries | [docs/design.md](docs/design.md) |
| Navigate and modify the source | [docs/source-guide.md](docs/source-guide.md) |
| Add a pipeline type, a style, or a scenario | [docs/extending.md](docs/extending.md) |
