Low-level renderer API used by Emerge viewports.
Most applications should define a viewport with use Emerge. Use
EmergeSkia directly when integrating the renderer without the viewport
process or when using renderer-specific APIs such as capture, statistics, or
video targets.
Example
{:ok, renderer} =
EmergeSkia.start(%Emerge.Config{
output: %Emerge.Config.Window{title: "My App"},
assets: [otp_app: :my_app]
})
{_state, _assigned_tree} = EmergeSkia.upload_tree(renderer, tree)
:ok = EmergeSkia.stop(renderer)start/1 accepts only Emerge.Config; see the
viewport configuration documentation and
0.5 migration guide. renderer_info/1 reports the
backend and rendering API actually selected. stats/2 and the capture
functions provide on-demand diagnostics for a running renderer.
For UI colors, prefer Emerge.UI.Color. rgb/3 and rgba/4 return packed
unsigned 0xRRGGBBAA values for low-level interfaces.
When an input target is registered with set_input_target/2, Wayland close
requests arrive as {:emerge_skia_close, :window_close_requested} independently
of config.input.events. Native payload names/actions differ from the typed
Emerge.Input values passed to a viewport's handle_input/2.
Summary
Types
Native live-control progress, distinct from rendering or presentation.
Session-scoped native live-control submission token.
Renderer handle returned by start/1.
Functions
Reports native live-control revisions for this renderer session.
Lists DRM connectors and their advertised modes without changing display configuration.
Load a font from a file path.
Measure text dimensions for layout purposes.
Apply patches for a new tree, run layout, and render.
Return pixels from the renderer's latest already-presented frame.
Return an encoded PNG from the renderer's latest already-presented frame.
Fetch normalized renderer/backend information for a running renderer.
Returns native session health and cleanup status.
Convert RGB values to a color integer.
Convert RGBA values to a color integer.
Check if the renderer window is still open.
Set the target process to receive renderer events.
Set the target process to receive native renderer log messages.
Starts a renderer using %Emerge.Config{}.
Fetch renderer counters and timings as a structured snapshot.
Stop the renderer and close its window or output session.
Requests native shutdown and waits at most timeout milliseconds (default 5,000).
Submits one VideoInterop frame to a renderer-local video target.
Upload a full EMRG tree, run layout, and render.
Types
@type color() :: non_neg_integer()
@type config_control_status() :: %{ session_id: non_neg_integer(), desired_revision: non_neg_integer(), pending_revision: pos_integer() | nil, applied_revision: pos_integer() | nil, failed_revision: pos_integer() | nil, error: String.t() | nil }
Native live-control progress, distinct from rendering or presentation.
@type config_token() :: {non_neg_integer(), pos_integer()}
Session-scoped native live-control submission token.
@type drm_mode() :: %{ id: String.t(), name: String.t(), width: pos_integer(), height: pos_integer(), refresh_hz: float(), preferred: boolean(), interlaced: boolean() }
@type drm_output() :: %{ name: String.t(), connector_id: non_neg_integer(), connected: boolean(), modes: [drm_mode()] }
Renderer handle returned by start/1.
Functions
@spec config_status(renderer()) :: {:ok, config_control_status()} | {:error, :unsupported}
Reports native live-control revisions for this renderer session.
desired_revision is the latest accepted submission, pending_revision awaits
owner application, and applied_revision is the last confirmed live-control
revision. Failures include failed_revision and error; a failed update may
have partial effects, so the last applied revision is not a rollback guarantee.
Revision zero means no live submission (startup settings are not counted). This
reports native owner processing, not independent verification of physical display
state. macOS host revision reporting is not implemented.
Native configure/3 submissions send {:emerge_skia_config, {session_id, revision}, :ok | {:error, reason}} to the submitting process. Session shutdown can end
delivery; use renderer status/recovery as the terminal ownership authority.
@spec drm_outputs(keyword()) :: {:ok, [drm_output()]} | {:error, String.t()}
Lists DRM connectors and their advertised modes without changing display configuration.
Accepts drm_card: "/dev/dri/card0" (the default), including stable
/dev/dri/by-path/...-card paths. Returns disconnected outputs too, with
connected: false. Requires Linux, compiled DRM support and read access to the card.
Pass an output's name as Config.DRM.connector and a mode map (or its
exact id) as Config.DRM.mode. IDs describe exact timings,
not indices or rounded refresh rates. Discovery is a snapshot; startup checks
availability again. Explicit selections never fall back to another output/mode.
{:ok, outputs} = EmergeSkia.drm_outputs(drm_card: "/dev/dri/card0")
output = Enum.find(outputs, &(&1.connected and &1.name == "HDMI-A-1"))
mode = Enum.find(output.modes, & &1.preferred)
%Emerge.Config{output: %Emerge.Config.DRM{connector: output.name, mode: mode}}
@spec load_font_file(renderer(), String.t(), non_neg_integer(), boolean(), Path.t()) :: :ok | {:error, term()}
Load a font from a file path.
The font is registered by name and can be used with Font.family/1 in elements.
Load different variants (bold, italic) with separate calls using appropriate weight/italic params.
The registration belongs only to renderer and does not change any other
renderer or offscreen render.
Parameters
renderer- Renderer returned bystart/1name- Font family name to register (e.g., "my-font")weight- Font weight (100-900, 400=normal, 700=bold)italic- Whether this is an italic variantpath- Path to the TTF font file
Example
# Load font variants into one renderer
:ok = EmergeSkia.load_font_file(renderer, "my-font", 400, false, "priv/fonts/MyFont-Regular.ttf")
:ok = EmergeSkia.load_font_file(renderer, "my-font", 700, false, "priv/fonts/MyFont-Bold.ttf")
:ok = EmergeSkia.load_font_file(renderer, "my-font", 400, true, "priv/fonts/MyFont-Italic.ttf")
# Use in elements
el([Font.family("my-font"), Font.size(16)], text("Hello"))
el([Font.family("my-font"), Font.bold()], text("Bold text"))
Measure text dimensions for layout purposes.
Returns {width, line_height, ascent, descent} where:
width- Horizontal extent of the textline_height- Total line height (ascent + descent)ascent- Distance from baseline to top (positive)descent- Distance from baseline to bottom (positive)
@spec patch_tree(renderer(), Emerge.Engine.diff_state(), Emerge.tree()) :: {Emerge.Engine.diff_state(), Emerge.tree()}
Apply patches for a new tree, run layout, and render.
Window dimensions come from the initial start config and are updated automatically when the window is resized (handled on the Rust side).
Return pixels from the renderer's latest already-presented frame.
This API captures retained renderer state. It no longer accepts an Emerge tree;
pass a renderer handle returned by start/1.
{:ok, pixels} =
EmergeSkia.render_to_pixels(renderer,
region: {0, 0, 320, 240},
pixel_format: :rgba8888,
timeout: 5_000
)Supported options are region: {x, y, width, height},
pixel_format: :rgba8888 | :rgb888, timeout, scale: 1.0, and
background: :transparent. macOS and headless PRIME do not support capture.
Return an encoded PNG from the renderer's latest already-presented frame.
This API captures retained renderer state. It no longer accepts an Emerge tree;
pass a renderer handle returned by start/1.
{:ok, png} = EmergeSkia.render_to_png(renderer, png: [compression: :default])
File.write!("capture.png", png)PNG capture accepts the same region, timeout, scale, and background options
as render_to_pixels/2, plus png: [compression: :default]. macOS and
headless PRIME do not support capture.
@spec renderer_info(renderer()) :: {:ok, EmergeSkia.Native.renderer_info()} | {:error, term()}
Fetch normalized renderer/backend information for a running renderer.
{:ok, info} = EmergeSkia.renderer_info(renderer)
info.rendering_api.selected
info.capabilities.screenshotExplicit Vulkan renderers include the physical device that won startup
selection. When selection used an exact DRM node, vulkan_device.drm_node
contains its path, match field, and major/minor identity. The KMS card is not
reported as the Vulkan node on split-device systems. Non-Vulkan renderers
return vulkan_device: nil.
Use capabilities to check capture, raster presentation, and PRIME video
support before starting an optional operation.
Returns native session health and cleanup status.
cleanup_complete is true only after safe teardown. running?/1 == false
alone does not mean a DRM output is available for a replacement renderer.
@spec rgb(0..255, 0..255, 0..255) :: color()
Convert RGB values to a color integer.
Examples
iex> EmergeSkia.rgb(255, 0, 0)
0xFF0000FF
iex> EmergeSkia.rgb(0, 255, 0)
0x00FF00FF
@spec rgba(0..255, 0..255, 0..255, 0..255) :: color()
Convert RGBA values to a color integer.
Examples
iex> EmergeSkia.rgba(255, 0, 0, 128)
0xFF000080
iex> EmergeSkia.rgba(0, 0, 0, 255)
0x000000FF
Check if the renderer window is still open.
Set the target process to receive renderer events.
Events are sent directly to the target process as
{:emerge_skia_event, event} messages. Subscribe with config.input.events at
startup; the default empty list forwards no raw input. Element events and close
notifications remain independent.
This is the low-level native payload contract, not the typed first argument
to Emerge.handle_input/2. A viewport translates names/actions through
Emerge.Input; do not match its callbacks against this table.
Raw input event payloads include:
{:cursor_pos, {x, y}}{:cursor_button, {button, action, mods, {x, y}}}{:cursor_scroll, {{dx, dy}, {x, y}}}{:key, {key, action, mods}}{:text_commit, {text, mods}}{:text_preedit, {text, cursor}}:text_preedit_clear{:delete_surrounding, {before_bytes, after_bytes}}(where the backend supports it){:cursor_entered, entered}{:resized, {width, height, scale}}{:focused, focused}
On Wayland, close notifications are sent separately as:
{:emerge_skia_close, :window_close_requested}
This lifecycle message bypasses the input mask so close requests are still delivered when other raw input categories are disabled.
On DRM, raw {:cursor_pos, {x, y}} delivery is latest-wins under load so
pointer motion does not stall rendering. Button, scroll, key, and text events
remain ordered.
Element event payloads include:
{id_bin, event_type}{id_bin, event_type, payload}
where id_bin is an opaque element id and event_type is an atom such as
:press, :click, :swipe_up, :swipe_down, :swipe_left,
:swipe_right, :change, :key_down, :key_up, or :key_press.
Text-input :change payloads are binaries. Slider :change payloads are
floats.
Routed :key_down, :key_up, and :key_press payloads currently carry an
opaque binding route id used by higher-level runtimes.
Higher-level runtimes should route element events with
Emerge.Engine.lookup_event/3 or Emerge.Engine.dispatch_event/3/4.
Where:
buttonis an atom like:left,:right,:middleactionis 0 for release, 1 for pressmodsis a list of modifier atoms like[:shift, :ctrl]keyis a canonical atom like:escape,:enter,:a,:digit_1,:arrow_left, or:plus
Raw key events stay layout-independent. Text-producing input is delivered separately
through text commit/preedit events. For example, Shift+= reports raw key :equal
with [:shift] and still commits the text "+".
Example
EmergeSkia.set_input_target(renderer, self())
receive do
{:emerge_skia_event, {:cursor_button, {button, 1, _mods, {x, y}}}} ->
IO.puts("Clicked #{button} at #{x}, #{y}")
{:emerge_skia_event, {:key, {key, 1, _mods}}} ->
IO.puts("Key pressed: #{key}")
end
Set the target process to receive native renderer log messages.
Native logs are sent directly to the target process as
{:emerge_skia_log, level, source, message} messages.
@spec start(Emerge.Config.t()) :: {:ok, renderer()} | {:error, term()}
Starts a renderer using %Emerge.Config{}.
Outside a viewport, assets.otp_app is required to resolve logical assets.
See Emerge.Config and its nested records for defaults and types. Legacy
keyword configuration and positional startup arities have been removed.
output = %Emerge.Config.Headless{target: self(), size: {640, 480}}
config = %Emerge.Config{output: output, renderer: [api: :raster], assets: [otp_app: :my_app]}
{:ok, renderer} = EmergeSkia.start(config)Native logs go to the starting process as
{:emerge_skia_log, level, source, message}. Use set_log_target/2 to change
the destination. Unsupported controls raise before native startup.
@spec stats(renderer(), EmergeSkia.Native.stats_command()) :: {:ok, EmergeSkia.Native.stats_snapshot()} | {:error, term()}
Fetch renderer counters and timings as a structured snapshot.
On native Wayland, DRM and headless renderers, collection is disabled by default.
Set diagnostics: [stats: true] in Emerge.Config to collect data without
periodic stats output, or diagnostics: [logs: [:stats]] to enable both
collection and periodic summaries. See Emerge.Config.Diagnostics for their
shared measurement-window behavior and log destinations.
{:ok, snapshot} = EmergeSkia.stats(renderer, :peek)
{:ok, closed_window} = EmergeSkia.stats(renderer, :take)Commands are:
:peekreads the current window without resetting it;:takereturns and resets the current window;:resetdiscards the current window.
When collection is disabled, the snapshot reports enabled: false and empty
counters. Renderer collection is startup-only; {:configure, %{enabled: boolean}}
is not supported for renderer handles.
:take and :reset may return a draining error while asynchronous GPU samples
finish; retry the same command to complete the closed window. The stats schema
version is 25. The macOS host returns {:error, :not_supported} for this API;
use diagnostics: [logs: [:stats]] for its periodic summaries instead.
Stop the renderer and close its window or output session.
Most sessions return :ok. Native sessions, including headless PRIME, can
return {:error, reason} when ownership-safe shutdown cannot finish. Do not
treat an error as successful cleanup. Use renderer_status/1 to check native
cleanup completion; a timeout does not make a DRM output reusable.
Quarantined sessions require a BEAM process restart.
Requests native shutdown and waits at most timeout milliseconds (default 5,000).
A timeout does not release ownership or make an output reusable. Native cleanup
continues; renderer_status/1 distinguishes pending cleanup from completion.
Quarantined sessions require a VM restart. macOS currently supports only stop/1.
@spec submit_video_frame(renderer(), atom(), VideoInterop.Frame.t()) :: :ok | {:error, {:caller_owned | :transferred, term()}}
Submits one VideoInterop frame to a renderer-local video target.
The target is the atom passed to Emerge.UI.video/2 in the renderer's tree.
Hidden targets consume and drop frames immediately. Visible targets retain
only the latest submitted frame.
This low-level function exposes ownership on errors:
:okmeans the frame was consumed.{:error, {:caller_owned, reason}}means submission did not consume the frame; the caller must release borrowed storage.{:error, {:transferred, reason}}means ownership transferred before the failure and the caller must not release the frame.
Applications submitting through a viewport should normally use
Emerge.submit_video_frame/3, which consumes the frame on every normal
return and hides these ownership receipts.
@spec upload_tree(renderer(), Emerge.tree()) :: {Emerge.Engine.diff_state(), Emerge.tree()}
Upload a full EMRG tree, run layout, and render.
Window dimensions come from the initial start config and are updated automatically when the window is resized (handled on the Rust side).