# `CauldronGPU`
[🔗](https://github.com/jaman/cauldron/blob/v0.3.3/cauldron_gpu/lib/cauldron_gpu.ex#L1)

A window drawn by the graphics card from a scene described in Elixir.

Elixir keeps the world and sends changes; a native render thread draws at the display's
rate from its own copy of the scene, interpolating poses between ticks.

    {:ok, gpu} = CauldronGPU.open(title: "Demo", width: 1280, height: 720)
    cube = CauldronGPU.mesh(gpu, CauldronGPU.Mesh.cube(1.0))
    red = CauldronGPU.material(gpu, %CauldronGPU.Material{base_color: {0.8, 0.1, 0.1, 1.0}})

    CauldronGPU.submit(gpu, [
      {:instance, 1, %CauldronGPU.Instance{mesh: cube, materials: [red]}},
      {:light, 1, %CauldronGPU.Light{kind: :directional, direction: {-0.4, -1.0, -0.3}, shadows: true}},
      %CauldronGPU.Camera{position: {0.0, 1.0, 4.0}}
    ])

Window events arrive at the `:events` process as `{:cauldron_gpu, window_id, event}`:
`{:key, :down | :up, key, repeat?}` (key an atom such as `:w`, `:space`, `:left_shift`),
`{:mouse_motion, x, y, dx, dy}`, `{:mouse_button, :down | :up, button, x, y}`,
`{:mouse_wheel, dx, dy}`, `{:resized, width, height}`, `{:focus, boolean}`,
`:close_requested`, and `{:error, message}` when the renderer rejects a change.

On macOS the window runs on the process's main thread, which the first `open/1` takes
for the rest of the VM's life; `wx` cannot open windows in the same VM.

# `info`

```elixir
@type info() :: %{
  backend: :metal | :vulkan,
  device: String.t(),
  ray_tracing: boolean(),
  mesh_shaders: boolean(),
  unified_memory: boolean(),
  upscaling: boolean(),
  frame_generation: boolean()
}
```

# `t`

```elixir
@type t() :: %CauldronGPU{
  id: non_neg_integer(),
  ids: :atomics.atomics_ref(),
  info: info(),
  window: reference()
}
```

# `backends`

```elixir
@spec backends() :: [:metal | :vulkan]
```

The backends this build has, preferred first.

# `canvas`

```elixir
@spec canvas(t(), CauldronGPU.Canvas.t()) :: CauldronGPU.Handle.t()
```

Makes a canvas (`CauldronGPU.Canvas`) and returns its handle, a texture's.

# `capture`

```elixir
@spec capture(t(), timeout(), keyword()) ::
  {:ok,
   %{width: pos_integer(), height: pos_integer(), shared_memory: String.t()}}
  | {:ok, %{width: pos_integer(), height: pos_integer(), pixels: binary()}}
  | {:error, term()}
```

The next drawn frame as `{:ok, %{width: w, height: h, pixels: rgba}}`: tone-mapped,
sRGB-encoded RGBA bytes, rows from the top. Waits up to `timeout` milliseconds.
`generated: true` returns the next frame made by frame generation instead.

`shared_memory: name` writes the pixels into the POSIX shared-memory object `name`
(at most 31 bytes on macOS, starting with `/`; an existing object of that name is
removed first) instead of returning them, and answers
`{:ok, %{width: w, height: h, shared_memory: name}}`. The reader normally removes it;
`remove_shared_memory/1` removes one that was never read. Not available on Windows.

# `close`

```elixir
@spec close(t()) :: :ok
```

Closes the window. Later calls with `gpu` return `{:error, "closed"}`.

# `frames`

```elixir
@spec frames(t()) :: non_neg_integer()
```

How many frames the renderer has drawn into the window.

# `material`

```elixir
@spec material(t(), CauldronGPU.Material.t()) :: CauldronGPU.Handle.t()
```

Defines a material and returns its handle, which keeps its textures' handles.

# `mesh`

```elixir
@spec mesh(t(), CauldronGPU.Mesh.t()) :: CauldronGPU.Handle.t()
```

Uploads a mesh and returns its handle.

# `open`

```elixir
@spec open(keyword()) :: {:ok, t()} | {:error, String.t()}
```

Opens a window. Options: `:title`, `:width` and `:height` in points (default 1280×720),
`:backend` (`:metal` or `:vulkan`, default the first of `backends/0`), `:events`
(the pid that receives window events, default the caller), `:window` (default `true`).

With `window: false` nothing appears on screen: `width` and `height` are the picture's
size in pixels, a frame is drawn for each `submit/2` and `capture/2`, and frames are
read with `capture/2`.

# `relative_mouse`

```elixir
@spec relative_mouse(t(), boolean()) :: :ok | {:error, String.t()}
```

Hides the pointer and reports only relative motion while `enabled`.

# `remove_shared_memory`

```elixir
@spec remove_shared_memory(String.t()) :: :ok
```

Removes the POSIX shared-memory object `name`, if there is one.

# `resize`

```elixir
@spec resize(t(), pos_integer(), pos_integer()) :: :ok | {:error, String.t()}
```

Changes an offscreen device's picture size (`window: false`). Windows follow their window.

# `stats`

```elixir
@spec stats(t()) :: %{
  frames: non_neg_integer(),
  gpu_ms: float(),
  cpu_ms: float(),
  refresh_rate: float(),
  generating: boolean()
}
```

Timing averaged over recent frames: `frames` drawn, `gpu_ms` and `cpu_ms` per frame, the
display's `refresh_rate` (60 when offscreen), and whether frame generation is
`generating` (it runs only when requested and frames take longer than the display's
refresh).

# `submit`

```elixir
@spec submit(t(), [CauldronGPU.Stream.change()]) :: :ok | {:error, String.t()}
```

Sends one tick of changes (see `CauldronGPU.Stream`); a `:tick` is put before them.

# `terminal_size`

```elixir
@spec terminal_size() :: {:ok, map()} | {:error, String.t()}
```

The size of the terminal the VM runs in: `{:ok, %{columns:, rows:, width:, height:}}`,
cells and pixels (pixels are 0 where the terminal does not report them), or
`{:error, reason}` when there is no terminal.

# `texture`

```elixir
@spec texture(t(), CauldronGPU.Texture.t()) :: CauldronGPU.Handle.t()
```

Uploads a texture and returns its handle.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
