# cat-wgpu

Categorical host layer for wgpu compute.  Wraps `wgpu` 29+ behind the public
surface of [`comp-cat-rs`](../comp-cat-rs) `Io` / `Resource` combinators.

## House rules

Follows the standard workspace Rust rules: no `unwrap`, no `expect`, no
`unreachable!`, no `unsafe`, no `dyn`, hand-rolled `Error` enum, combinators
over pattern matching on `Option`, exhaustive matches on enums (no `_`
wildcard), `if`/`else` on `bool`, `match () { () if cond => ... }` instead of
long `if`/`else-if` chains, no public struct fields, dual MIT OR Apache-2.0
license.

## Local exception: `let mut` inside `src/runtime/backend.rs`

`src/runtime/backend.rs` is the **sole** place in this crate where `let mut`
bindings are permitted.  This mirrors the `comp-cat-rs` carve-out for `dyn`
and the `kan-hunt-anchor-runner` carve-out for its `solana-program-test`
driver, and is scoped as narrowly as possible.

The module file is named `backend.rs` rather than `wgpu.rs` to avoid any
visual collision with the external `wgpu` crate we depend on.

### Why

`wgpu`'s compute-dispatch recording API is entirely `&mut self`:

- `CommandEncoder::begin_compute_pass(&mut self, ...) -> ComputePass<'_>`
- `ComputePass::set_pipeline(&mut self, ...)`
- `ComputePass::set_bind_group(&mut self, ...)`
- `ComputePass::dispatch_workgroups(&mut self, ...)`

There is no value-returning or builder-by-move form.  The alternatives
considered and rejected were:

1. **Allocating a fresh encoder for every recorded op** — wgpu forbids it
   (the encoder has to be finished as one `CommandBuffer` before submission).
2. **Interior-mutability wrapper (`RefCell<ComputePass>`)** — `ComputePass`
   is neither `Clone` nor `'static`; borrows bleed up through the wrapper.
3. **Async recording** — wgpu's recording API is synchronous; nothing to
   await.  Would just add noise.
4. **Dropping wgpu in favour of a custom driver** — back to the no-safe-path
   problem that pushed us off CUDA in the first place.

### Scope of the exception

- Only `src/runtime/backend.rs` may use `let mut`.
- All other modules (`error`, `device`, `memory`, `slipstream`, `kernel`,
  `launch`, `runtime/mod.rs`, every file under `examples/`, every file under
  `tests/`) follow the standard no-mut rule.
- `unwrap` / `expect` / `unreachable!` / `unsafe` remain prohibited even
  inside `runtime/backend.rs`.  The exception is scoped to `mut` only.
- If a future backend (e.g. a parallel CUDA crate, or an optional SPIR-V
  driver) needs the same exception, add it to the list explicitly by file
  path.  Do not generalise this to "any module touching the GPU".

## Naming

- `SlipStream` = our CUDA-style work queue.  In wgpu terms, wraps the shared
  `Queue` handle.  Rename avoids collision with `comp_cat_rs::effect::Stream`,
  which is the iterative colimit abstraction.
- `Wgsl` = owned WGSL source bytes.
- `ShaderModule` = compiled-on-the-device WGSL module.
- `ComputePipeline` = a bound pipeline with its bind-group layout pinned.
- `DeviceMem<T>` = typed storage buffer.  Drop goes through `Resource<Error,
  DeviceMem<T>>` in `launch.rs`.

## Build

```sh
cargo build
cargo test
RUSTFLAGS="-D warnings" cargo clippy --all-targets
cargo run --example vector_add
```
