# `Dala.Media.Scene`
[🔗](https://github.com/manhvu/dala/blob/main/lib/dala/media/scene.ex#L1)

Realtime scene graph compositor.

Composites multiple media sources (video, overlays, text, effects) into a
single GPU-rendered output. Frame-clock driven for smooth 60fps.

Scene graph:
    Scene
     ├── VideoNode     — hardware-decoded video texture
     ├── OverlayNode   — image/UI overlay layers
     ├── TextNode      — GPU-rendered text
     ├── EffectNode    — GPU compute filters (blur, LUT, etc.)
     └── AnimationNode — frame-clock driven animations

## Example

    {:ok, scene} = Dala.Media.Scene.new(1920, 1080)

    # Add a video layer
    {:ok, video_node} = Dala.Media.Scene.add_node(scene, :video, %{
      stream: video_stream,
      position: {0, 0},
      size: {1920, 1080},
      z_index: 0
    })

    # Add an overlay
    {:ok, overlay_node} = Dala.Media.Scene.add_node(scene, :overlay, %{
      texture: overlay_texture,
      position: {100, 100},
      size: {200, 50},
      opacity: 0.8,
      z_index: 10
    })

    # Add a blur effect
    {:ok, effect_node} = Dala.Media.Scene.add_node(scene, :effect, %{
      type: :blur,
      radius: 5.0,
      input: video_node,
      z_index: 5
    })

    # Composite and render
    Dala.Media.Scene.render(scene)

# `node_id`

```elixir
@type node_id() :: reference()
```

# `node_type`

```elixir
@type node_type() :: :video | :image | :overlay | :text | :effect | :animation
```

# `opacity`

```elixir
@type opacity() :: float()
```

# `position`

```elixir
@type position() :: {non_neg_integer(), non_neg_integer()}
```

# `scene_ref`

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

# `size`

```elixir
@type size() :: {non_neg_integer(), non_neg_integer()}
```

# `transform`

```elixir
@type transform() :: %{
  position: position(),
  scale: {float(), float()},
  rotation: float(),
  opacity: opacity(),
  z_index: z_index()
}
```

# `z_index`

```elixir
@type z_index() :: integer()
```

# `add_image`

```elixir
@spec add_image(
  scene_ref(),
  keyword()
) :: {:ok, node_id()} | {:error, term()}
```

Add an image node to the scene.

Convenience wrapper around `add_node/3` for image sources.

## Options

  * `:image_id` — The GPU image ID (from `Dala.Gpu.load_image/4`)
  * `:position` — `{x, y}` tuple (default: `{0, 0}`)
  * `:size` — `{w, h}` tuple (default: `{100, 100}`)
  * `:z_index` — Layer order (default: `0`)
  * `:opacity` — `0.0` to `1.0` (default: `1.0`)

## Example

    {:ok, img_node} = Dala.Media.Scene.add_image(scene, image_id: 1, position: {540, 20}, size: {120, 90}, z_index: 100)

# `add_node`

```elixir
@spec add_node(scene_ref(), node_type(), map()) :: {:ok, node_id()} | {:error, term()}
```

Add a node to the scene.

# `add_video`

```elixir
@spec add_video(
  scene_ref(),
  keyword()
) :: {:ok, node_id()} | {:error, term()}
```

Add a video node to the scene with optional PiP (picture-in-picture) transform.

Convenience wrapper around `add_node/3` for video streams.

## Options

  * `:stream` — A `Dala.Media.Video` pid (required)
  * `:position` — `{x, y}` tuple (default: `{0, 0}`)
  * `:size` — `{w, h}` tuple (default: `{1920, 1080}`)
  * `:z_index` — Layer order (default: `0`)
  * `:pip` — If `true`, applies a PiP transform (small overlay in top-right corner)
  * `:pip_position` — Custom PiP position `{x, y}` (default: auto-calculated)
  * `:pip_size` — Custom PiP size `{w, h}` (default: `{200, 150}`)

## Example

    # Full-screen video
    {:ok, vid} = Dala.Media.Scene.add_video(scene, stream: video_stream, size: {1920, 1080})

    # PiP video overlay
    {:ok, pip} = Dala.Media.Scene.add_video(scene, stream: pip_stream, pip: true, z_index: 100)

    # Custom PiP position
    {:ok, pip} = Dala.Media.Scene.add_video(scene, stream: pip_stream, pip: true, pip_position: {50, 50}, pip_size: {300, 200})

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `destroy`

```elixir
@spec destroy(scene_ref()) :: :ok
```

Destroy the scene and release all GPU resources.

# `fps`

```elixir
@spec fps(scene_ref()) :: float()
```

Get current FPS.

# `frame_count`

```elixir
@spec frame_count(scene_ref()) :: non_neg_integer()
```

Get current frame count.

# `new`

```elixir
@spec new(non_neg_integer(), non_neg_integer(), keyword()) ::
  {:ok, scene_ref()} | {:error, term()}
```

Create a new scene with the given dimensions.

# `remove_node`

```elixir
@spec remove_node(scene_ref(), node_id()) :: :ok
```

Remove a node from the scene.

# `render`

```elixir
@spec render(scene_ref()) :: :ok
```

Composite all nodes and render to the GPU surface.

# `set_pip_transform`

```elixir
@spec set_pip_transform(scene_ref(), node_id(), keyword()) :: :ok
```

Update a node's PiP (picture-in-picture) transform.

Convenience function to move/resize a PiP node.

## Example

    Dala.Media.Scene.set_pip_transform(scene, pip_node_id, position: {100, 50}, size: {250, 180})

# `set_target_fps`

```elixir
@spec set_target_fps(scene_ref(), pos_integer()) :: :ok
```

Set target FPS (default 60).

# `set_transform`

```elixir
@spec set_transform(scene_ref(), node_id(), transform()) :: :ok
```

Update a node's transform.

# `update_node`

```elixir
@spec update_node(scene_ref(), node_id(), map()) :: :ok
```

Update a node's properties.

---

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