# Migrate to Emerge 0.4

> Historical guide: startup/config examples below describe **0.4**, not the current
> 0.5 API. After applying these changes, follow [Migrate to Emerge 0.5](0.5.md)
> for typed config, input subscriptions and callback migration.

Emerge 0.4 unifies renderer selection, changes screenshot capture to retained
renderer state, and moves video submission to viewport-local atom targets.

## Move gradients to `Color`

`Emerge.UI.Background.gradient/2,3` has been removed. Pass a gradient color to
`Background.color/1` instead:

```elixir
# Removed
Background.gradient(from, to, angle)

# Replacement (with use Emerge.UI)
Background.color(gradient([from, to], angle))

# Additional colors are evenly spaced; the angle defaults to 0 degrees
Background.color(gradient([color(:sky, 400), color(:violet, 500), color(:rose, 600)]))
```

The fully qualified constructor is `Emerge.UI.Color.gradient/2`. Gradients need
at least two solid colors and cannot contain nested gradients. The same value
works with `Font.color/1`, `Border.color/1`, shadow colors and `Svg.color/1`:

```elixir
paint = gradient([color(:sky, 400), color(:rose, 600)], 45)
Font.color(paint)
Border.color(paint)
Border.shadow(color: paint)
Svg.color(paint)
```

SVG tint preserves source alpha and multiplies it by gradient alpha. Gradients
span the element's border box for backgrounds/borders/shadows, and content box
for text/SVG. Paragraph fragments share the paragraph content box; glyphs, words,
lines, border edges and SVG tiles do not independently restart the gradient.
Animated gradients keep the same stop count; solid endpoints are lifted to the
gradient endpoint's count and angle before interpolation.

Raw `{:gradient, from, to, angle}` attributes are no longer accepted either.

## Review animation dimensions

Width and height animations now support pixel, content, fill, and weighted-fill
sizes. `min/2` and `max/2` remain valid for static layout, but cannot be animation
endpoints or the starting value of a change transition. Keep those bounds on
static elements, or resolve them to pixel sizes before constructing an animation.

Use `Animation.change/3` with a list of attributes to animate later value changes:

```elixir
Animation.change([width(fill()), height(px(60))], 300, :ease_out)
```

Each attribute uses the shared timing. First mount is immediate; later changes
start from the current displayed value. A plain attribute removes its change
policy. `Animation.change([width(content())], 300, :ease_out)` also animates size
changes caused by text or children. See `Emerge.UI.Animation` for examples.

## Review shadows on transparent elements

`Border.shadow` and `Border.glow` now paint the full box behind the element,
including text and other content. A transparent interior reveals the shadow
instead of cutting it out. Add an opaque background if you want to cover the
interior. Inline paragraph wrappers use the same behavior; inset shadows are unchanged.

## Use `rendering_api`

Replace macOS-specific selection:

```elixir
# 0.3
EmergeSkia.start(otp_app: :my_app, backend: :macos, macos_backend: :metal)

# 0.4
EmergeSkia.start(otp_app: :my_app, backend: :macos, rendering_api: :metal)
```

Replace the compatibility renderer name:

```elixir
# Deprecated in 0.4
backend_renderer: :gl

# Current
rendering_api: :opengl
```

`backend_renderer` and `:gl` still warn and work in 0.4, but should not be used
in new configuration. `macos_backend` and `dispatch_mode` have been removed and
raise during option validation.

See `EmergeSkia.start/1` for supported backend/rendering API combinations and
fallback rules.

## Capture a retained renderer

In 0.3, `render_to_pixels/2` and `render_to_png/2` accepted a tree, created a
one-shot raster surface, and returned a binary:

```elixir
# 0.3
png =
  EmergeSkia.render_to_png(tree,
    otp_app: :my_app,
    width: 800,
    height: 600
  )
```

In 0.4, they capture the latest already-presented frame from a running renderer
and return a result tuple:

```elixir
# 0.4
{:ok, renderer} = EmergeSkia.start(otp_app: :my_app, backend: :wayland)
{_diff_state, _assigned_tree} = EmergeSkia.upload_tree(renderer, tree)

{:ok, png} = EmergeSkia.render_to_png(renderer, timeout: 5_000)
```

Update both the first argument and return-value matching. macOS host and
headless PRIME sessions currently return a not-supported error for capture.

See `EmergeSkia.render_to_pixels/2` and `EmergeSkia.render_to_png/2` for the
current capture options and backend support.

## Submit video to an atom target

Renderer-owned target handles, direct viewport connections, and
`submit_prime/2` were removed. Describe the destination with an atom:

```elixir
video([width(fill()), height(fill()), image_fit(:contain)], :camera)
```

Submit each owned binary or borrowed DMA-BUF frame to the viewport:

```elixir
:ok = Emerge.submit_video_frame(viewport, :camera, frame)
```

Every normal return consumes the frame. Hidden targets drop it immediately;
visible targets retain only the latest frame. Use `VideoInterop.retain/2` first
when another branch also needs the borrowed storage.

macOS now accepts owned, single-plane RGBA8888 binary frames with implicit
synchronization and premultiplied, straight, or opaque alpha. DMA-BUF/PRIME input
and retained-frame capture remain unsupported there.

For Emerge-to-Emerge transport, configure the headless viewport to send
`%VideoInterop.Frame{}` values into `Membrane.VideoInterop.Source`, then route
them to `Membrane.VideoInterop.Sink` with an MFA that calls
`Emerge.submit_video_frame/3`. Do not forward frames through an application
`handle_info/2` bridge.

The native video ownership contract changed in 0.4. Stop producers and
consumers and cold-restart the BEAM operating-system process before loading
upgraded Emerge, VideoInterop, or producer native libraries.

## Handle renderer stop errors

`EmergeSkia.stop/1` can now return `{:error, reason}` when a native session,
including headless PRIME, cannot finish ownership-safe shutdown:

```elixir
case EmergeSkia.stop(renderer) do
  :ok -> :ok
  {:error, reason} -> Logger.error("renderer did not stop cleanly: #{inspect(reason)}")
end
```

Do not treat an error as successful cleanup. Native callers can use
`EmergeSkia.stop(renderer, timeout: 5_000)` to bound the wait, then inspect
`EmergeSkia.renderer_status/1`. A timeout does not make the output reusable;
quarantined sessions require a BEAM process restart. macOS currently supports
only `stop/1`, not the native status/bounded-stop APIs.

## Keep viewport identity, refresh renderer handles

Native viewports can recover an unexpectedly stopped renderer after safe cleanup
while keeping their callback PID, state, and subscriptions. Applications still
supervise the viewport process; callback death is not a renderer-only restart.

Do not cache the result of `Emerge.renderer/1` across recovery. It may be `nil`
while unavailable, and the replacement has a new handle. Frames submitted through
`Emerge.submit_video_frame/3` during that interval return
`{:error, :viewport_not_ready}` and are still consumed; do not resubmit them.
macOS and custom renderer adapters do not implement this native recovery contract.
See `Emerge.Runtime.Viewport` for recovery and supervision details.

## Check stats schema version

The renderer stats payload changed with renderer, video, Vulkan, and cache
diagnostics. The 0.4 schema version is `25`.

```elixir
with {:ok, %{version: 25} = snapshot} <- EmergeSkia.stats(renderer, :peek) do
  inspect_snapshot(snapshot)
end
```

Consumers that persist or transmit snapshots should branch on `version` rather
than assuming the 0.3 map shape.

## Review new memory defaults

Decoded raster assets now default to a 256-entry, 256 MiB cache. Constrained
devices should set smaller limits and enable target-sized decode:

```elixir
assets: [
  decode_at_size: true,
  cache: [max_entries: 8, max_bytes: 2 * 1024 * 1024]
]
```

See [Use assets](../tutorials/use_assets.md) for configuration and memory
examples.

## Scope runtime font registration

Runtime font registration now requires the renderer that owns the font:

```elixir
# 0.3
:ok = EmergeSkia.load_font_file("my-font", 400, false, path)

# 0.4
:ok = EmergeSkia.load_font_file(renderer, "my-font", 400, false, path)
```

The registration and its text-metrics cache are isolated from every other
renderer. Fonts declared through `assets.fonts` are loaded into the renderer
created by that `start/1` call.

## Review Vulkan configuration

Vulkan is supported and opt-in at build time. Existing OpenGL configurations do
not switch to Vulkan automatically.

```elixir
config :emerge,
  compiled_backends: [wayland: [:opengl, :vulkan]]
```

`compiled_backends` is a backend/API matrix. Use `:all` for every GPU API on a
backend or list the exact APIs to include. For example,
`[wayland: [:vulkan]]` contains Vulkan but no OpenGL. Atom entries retain their
0.3 behavior and select OpenGL. 64-bit Linux has precompiled Vulkan-only
Wayland, DRM, and headless variants.

Explicit `rendering_api: :vulkan` does not fall back. DRM Vulkan also requires
an exact `vulkan_drm_node` path separate from `drm_card`.
