# `Dala.Gpu`
[🔗](https://github.com/manhvu/dala/blob/main/lib/dala/gpu.ex#L1)

GPU texture rendering surface.

Provides a CPU-side framebuffer that is uploaded to the GPU every frame
and rendered as a fullscreen quad. This is useful for:
- Custom canvas rendering
- ML tensor visualization
- Game-like rendering
- Video processing
- Shader effects

The rendering pipeline:
1. Elixir issues render commands (fill_rect, blit, etc.)
2. Rust processes commands on a dedicated render thread
3. CPU framebuffer is uploaded as GPU texture
4. Fullscreen quad is rendered with the texture

## Example

    # Create a 256x256 GPU surface
    {:ok, surface} = Dala.Gpu.create_surface(256, 256)

    # Issue render commands
    Dala.Gpu.clear(surface, :black)
    Dala.Gpu.fill_rect(surface, 10, 10, 100, 100, :red)
    Dala.Gpu.present(surface)

    # Direct pixel access (for advanced use)
    Dala.Gpu.with_pixels(surface, fn pixels ->
      # pixels is a binary of RGBA8888 data
      # modify directly for maximum performance
    end)

# `color`

```elixir
@type color() :: Dala.Gpu.Command.color()
```

# `surface_pid`

```elixir
@type surface_pid() :: pid()
```

# `batch`

```elixir
@spec batch(surface_pid(), [binary()]) :: :ok
```

Execute a batch of pre-encoded command binaries atomically.

# `blit`

```elixir
@spec blit(surface_pid(), non_neg_integer(), integer(), integer()) :: :ok
```

Blit a loaded sprite at the given position.

# `clear`

```elixir
@spec clear(surface_pid(), color()) :: :ok
```

Clear the entire surface with a solid color.

# `compute_buffer`

```elixir
@spec compute_buffer(list(), tuple(), atom()) :: Dala.Gpu.Compute.Buffer.t()
```

Create a GPU compute buffer.

Delegates to `Dala.Gpu.Compute.buffer/3`.

# `compute_free`

```elixir
@spec compute_free(Dala.Gpu.Compute.Buffer.t()) :: :ok
```

Free a GPU compute buffer.

Delegates to `Dala.Gpu.Compute.free/1`.

# `compute_free_many`

```elixir
@spec compute_free_many([Dala.Gpu.Compute.Buffer.t()]) :: :ok
```

Free multiple GPU compute buffers.

Delegates to `Dala.Gpu.Compute.free_many/1`.

# `compute_pipeline`

```elixir
@spec compute_pipeline([map()]) :: :ok | {:error, term()}
```

Run a GPU compute pipeline.

Delegates to `Dala.Gpu.Compute.pipeline/0`, `Dala.Gpu.Compute.pipeline_add/2`,
and `Dala.Gpu.Compute.pipeline_run/1`.

# `compute_run`

```elixir
@spec compute_run(
  atom(),
  [Dala.Gpu.Compute.Buffer.t()],
  Dala.Gpu.Compute.Buffer.t(),
  map()
) ::
  :ok | {:error, term()}
```

Run a GPU compute kernel synchronously.

Delegates to `Dala.Gpu.Compute.run_kernel/4`.

# `create_surface`

```elixir
@spec create_surface(non_neg_integer(), non_neg_integer()) ::
  {:ok, surface_pid()} | {:error, term()}
```

Create a new GPU surface with the given dimensions.

Returns `{:ok, pid}` where pid is the surface GenServer.
The surface is automatically cleaned up when the calling process exits
or when `destroy_surface/1` is called.

# `destroy_surface`

```elixir
@spec destroy_surface(surface_pid()) :: :ok
```

Destroy a GPU surface and free all associated GPU resources.

# `dispatch_compute`

```elixir
@spec dispatch_compute(
  surface_pid(),
  String.t(),
  binary(),
  {non_neg_integer(), non_neg_integer(), non_neg_integer()}
) :: :ok | {:error, term()}
```

Dispatch a GPU compute shader on the surface.

`shader_source` is the shader source code (MSL for Metal, GLSL for OpenGL ES).
`params` is a binary of parameter data passed to the shader.
`workgroup_count` is the number of threadgroups to dispatch {x, y, z}.

For filter presets, see `Dala.Media.Filter`.

# `draw_circle`

```elixir
@spec draw_circle(surface_pid(), integer(), integer(), non_neg_integer(), color()) ::
  :ok
```

Draw a circle outline.

# `draw_image`

```elixir
@spec draw_image(
  surface_pid(),
  non_neg_integer(),
  integer(),
  integer(),
  non_neg_integer(),
  non_neg_integer()
) :: :ok
```

Draw a loaded image onto the framebuffer at the given position and size.

The image is scaled to fit the destination rectangle. For best quality,
load the image at its native resolution and scale via the destination size.

## Example

    # Full-size render
    Dala.Gpu.draw_image(surface, image_id, 0, 0, 640, 480)

    # PiP mode: small overlay in the corner
    Dala.Gpu.draw_image(surface, image_id, 540, 20, 120, 90)

# `draw_line`

```elixir
@spec draw_line(surface_pid(), integer(), integer(), integer(), integer(), color()) ::
  :ok
```

Draw a line between two points with the given color.

# `draw_round_rect`

```elixir
@spec draw_round_rect(
  surface_pid(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  color()
) :: :ok
```

Draw a rounded rectangle outline.

# `draw_triangle`

```elixir
@spec draw_triangle(
  surface_pid(),
  integer(),
  integer(),
  integer(),
  integer(),
  integer(),
  integer(),
  color()
) :: :ok
```

Draw a triangle outline from three points.

# `fill_circle`

```elixir
@spec fill_circle(surface_pid(), integer(), integer(), non_neg_integer(), color()) ::
  :ok
```

Fill a circle.

# `fill_rect`

```elixir
@spec fill_rect(
  surface_pid(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  color()
) :: :ok
```

Fill a rectangle with a solid color.

# `fill_round_rect`

```elixir
@spec fill_round_rect(
  surface_pid(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  color()
) :: :ok
```

Fill a rounded rectangle.

# `fill_triangle`

```elixir
@spec fill_triangle(
  surface_pid(),
  integer(),
  integer(),
  integer(),
  integer(),
  integer(),
  integer(),
  color()
) :: :ok
```

Fill a triangle.

# `get_pixels`

```elixir
@spec get_pixels(surface_pid()) :: binary()
```

Get the current pixel data as an RGBA8888 binary.

The binary size is `width * height * 4` bytes.

# `height`

```elixir
@spec height(surface_pid()) :: non_neg_integer()
```

Get the height of the surface in pixels.

# `load_image`

```elixir
@spec load_image(
  surface_pid(),
  non_neg_integer(),
  binary(),
  non_neg_integer(),
  non_neg_integer()
) :: :ok
```

Load an image into the GPU texture pool.

`id` is a unique non-negative integer identifying the image.
`rgba_binary` is the pixel data in RGBA8888 format.

The image can then be rendered with `draw_image/6`.

## Example

    # Load a PNG-decoded binary
    {:ok, rgba_data} = Image.load("photo.png")
    Dala.Gpu.load_image(surface, 1, rgba_data, 640, 480)

    # Draw it at position (100, 200) scaled to 320x240
    Dala.Gpu.draw_image(surface, 1, 100, 200, 320, 240)

# `load_shader`

```elixir
@spec load_shader(surface_pid(), String.t(), String.t()) :: :ok | {:error, term()}
```

Load or hot-reload a named shader.

# `load_sprite`

```elixir
@spec load_sprite(
  surface_pid(),
  non_neg_integer(),
  binary(),
  non_neg_integer(),
  non_neg_integer()
) :: :ok
```

Load a sprite into the texture atlas for later blitting.

`id` is a unique non-negative integer identifying the sprite.
`rgba_binary` is the pixel data in RGBA8888 format.

# `present`

```elixir
@spec present(surface_pid()) :: :ok
```

Present the surface — flush the command queue and update the GPU texture.

# `remove_image`

```elixir
@spec remove_image(surface_pid(), non_neg_integer()) :: :ok
```

Remove an image from the GPU texture pool.

# `remove_sprite`

```elixir
@spec remove_sprite(surface_pid(), non_neg_integer()) :: :ok
```

Remove a sprite from the texture atlas.

# `reset_clip`

```elixir
@spec reset_clip(surface_pid()) :: :ok
```

Reset the clipping region to the full framebuffer.

# `resize_surface`

```elixir
@spec resize_surface(surface_pid(), non_neg_integer(), non_neg_integer()) :: :ok
```

Resize an existing GPU surface. This may reallocate the GPU texture.

# `set_clip`

```elixir
@spec set_clip(
  surface_pid(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  boolean()
) :: :ok
```

Set the clipping rectangle. Pass `enabled: false` to disable clipping.

# `set_pixels`

```elixir
@spec set_pixels(surface_pid(), binary()) :: :ok
```

Set the pixel data directly from an RGBA8888 binary.

The binary must be exactly `width * height * 4` bytes.

# `set_uniform`

```elixir
@spec set_uniform(surface_pid(), String.t(), binary()) :: :ok | {:error, term()}
```

Set a uniform value on the current shader pipeline.

# `supports_compute`

```elixir
@spec supports_compute(surface_pid()) :: boolean()
```

Check if the GPU backend supports compute shaders.

# `width`

```elixir
@spec width(surface_pid()) :: non_neg_integer()
```

Get the width of the surface in pixels.

# `with_pixels`

```elixir
@spec with_pixels(surface_pid(), (binary() -&gt; binary())) :: :ok
```

Modify pixels via a callback for maximum performance.

The callback receives the current RGBA8888 binary and must return
a new RGBA8888 binary of the same size. This avoids an extra
binary copy compared to `get_pixels`/`set_pixels`.

## Example

    Dala.Gpu.with_pixels(surface, fn pixels ->
      # Set the first pixel to red
      <<255, 0, 0, 255, rest::binary>> = pixels
      rest
    end)

---

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