NIF bindings for the Skia renderer.
Summary
Types
Native stats payload. Current schema version: 25.
Functions
Configure native asset loading policy and source roots.
Check if the renderer is still running.
Load a font from binary data and register it with a name.
Measure text dimensions.
Render a tree to an RGBA pixel buffer (synchronous, no window).
Render a tree to an encoded PNG binary (synchronous, no window).
Apply EMRG patches, run layout, and render immediately. Window dimensions come from the initial start config and resize events.
Upload a full EMRG tree, run layout, and render immediately. Window dimensions come from the initial start config and resize events.
Private observer-mask transport; applications declare config.input.events.
This filters raw forwarding only, not element interactions or window close.
Set the target process to receive input events.
Set the target process to receive native renderer log messages.
Private window NIF binding. Application startup uses
EmergeSkia.start(%Emerge.Config{}), not this positional transport entrypoint.
Returns a renderer resource for internal operations.
Start through the private native map ABI.
Stop the renderer and close the window.
Clear the tree.
Check if the tree is empty.
Compute layout for the tree with given width/height constraints and scale factor.
Create a new empty tree resource.
Get the number of nodes in the tree.
Apply patches to an existing tree.
Apply patches to an existing tree and return the encoded EMRG binary.
Decode an EMRG binary in Rust and re-encode it.
Upload a full tree from EMRG binary format. Replaces any existing tree contents.
Upload a full tree and return the encoded EMRG binary.
Types
@type duration_stats() :: %{ count: non_neg_integer(), avg_ms: float(), min_ms: float(), max_ms: float() }
@type layout_cache_stats() :: %{ intrinsic_measure_hits: non_neg_integer(), intrinsic_measure_misses: non_neg_integer(), intrinsic_measure_stores: non_neg_integer(), subtree_measure_hits: non_neg_integer(), subtree_measure_misses: non_neg_integer(), subtree_measure_stores: non_neg_integer(), resolve_hits: non_neg_integer(), resolve_misses: non_neg_integer(), resolve_stores: non_neg_integer() }
@type renderer_cache_kind_stats() :: %{ candidates: non_neg_integer(), visible_candidates: non_neg_integer(), suppressed_by_parent: non_neg_integer(), admitted: non_neg_integer(), hits: non_neg_integer(), misses: non_neg_integer(), stores: non_neg_integer(), evictions: non_neg_integer(), stale_evictions: non_neg_integer(), rejected: non_neg_integer(), current_entries: non_neg_integer(), current_bytes: non_neg_integer(), current_gpu_payloads: non_neg_integer(), current_cpu_payloads: non_neg_integer(), evicted_bytes: non_neg_integer(), stale_evicted_bytes: non_neg_integer(), gpu_payload_stores: non_neg_integer(), cpu_payload_stores: non_neg_integer(), prepare_successes: non_neg_integer(), prepare_failures: non_neg_integer(), direct_fallbacks_after_admission: non_neg_integer(), rejected_ineligible: non_neg_integer(), rejected_admission: non_neg_integer(), rejected_oversized: non_neg_integer(), rejected_payload_budget: non_neg_integer(), rejected_fractional_placement: non_neg_integer(), rejected_unsupported_transform: non_neg_integer(), prepare: duration_stats(), draw_hit: duration_stats() }
@type renderer_cache_stats() :: %{ enabled: boolean(), disabled_reason: String.t() | nil, paint_layer: renderer_cache_kind_stats() }
@type renderer_info() :: %{ backend: atom() | String.t(), rendering_api: rendering_api_info(), capabilities: %{ gpu: boolean(), renderer_cache: boolean(), screenshot: boolean(), raster_present: [atom() | String.t()], prime_video: boolean(), prime_video_formats: [String.t()] }, vulkan_device: vulkan_device_info() | nil }
@type stats_command() :: :peek | :take | :reset | {:configure, %{enabled: boolean()}}
@type stats_snapshot() :: %{ version: pos_integer(), kind: String.t(), enabled: boolean(), rendering_api: rendering_api_info() | nil, window: %{elapsed_ms: non_neg_integer(), reset_on_read: boolean()}, frames: %{ fps: float(), display_fps: float(), display_frame_ms: float(), frame_count: non_neg_integer() }, timings: %{ render: duration_stats(), render_draw: duration_stats(), render_flush: duration_stats(), render_gpu_flush: duration_stats(), render_submit: duration_stats(), present_submit: duration_stats(), pipeline: duration_stats(), pipeline_submit_to_tree_start: duration_stats(), pipeline_tree: duration_stats(), pipeline_render_queue: duration_stats(), pipeline_submit_to_swap: duration_stats(), pipeline_swap_to_frame_callback: duration_stats(), layout: duration_stats(), refresh: duration_stats(), event_resolve: duration_stats(), patch_tree_process: duration_stats() }, drm: %{ forced_gpu_finish_before_swap: duration_stats(), forced_gpu_finish_after_swap: duration_stats(), gpu_render_elapsed: duration_stats(), gpu_render_elapsed_disjoint_discarded_samples: non_neg_integer(), gpu_render_elapsed_pool_saturated_sample_skips: non_neg_integer(), gpu_render_elapsed_stale_epoch_samples: non_neg_integer(), video_retired_gl_finish_fallbacks: non_neg_integer(), egl_swap_buffers: duration_stats(), gbm_lock_front_buffer: duration_stats(), framebuffer_lookup: duration_stats(), prepared_to_commit: duration_stats(), previous_flip_to_commit: duration_stats(), atomic_commit_ioctl: duration_stats(), commit_to_kernel_page_flip: duration_stats(), kernel_page_flip_interval: duration_stats(), page_flip_dispatch_delay: duration_stats(), commit_to_event_processed: duration_stats(), page_flip_events: non_neg_integer(), page_flip_sequence_steps: non_neg_integer(), missed_vblanks: non_neg_integer() }, counters: %{ pipeline: %{ scenes_constructed: non_neg_integer(), render_queue_overwrites: non_neg_integer(), drm_scenes_selected_for_draw: non_neg_integer(), drm_scenes_presented: non_neg_integer() }, video: %{ submitted: non_neg_integer(), inactive_dropped: non_neg_integer(), pending_replaced: non_neg_integer(), pending_taken: non_neg_integer(), imported: non_neg_integer(), leases_released: non_neg_integer(), retired_fences_created: non_neg_integer(), retired_fences_released: non_neg_integer(), retired_gl_finish_fallbacks: non_neg_integer(), acquire_fences_received: non_neg_integer(), acquire_server_waits_queued: non_neg_integer(), acquire_client_wait_fallbacks: non_neg_integer(), acquire_wait_timeouts: non_neg_integer(), acquire_wait_errors: non_neg_integer(), primary_prepared: non_neg_integer(), video_primary_prepared: non_neg_integer(), stale_prepared: non_neg_integer(), stale_video_prepared: non_neg_integer(), gbm_no_free: non_neg_integer(), primary_commit_attempts: non_neg_integer(), primary_commit_ebusy: non_neg_integer(), primary_committed: non_neg_integer(), primary_presented: non_neg_integer(), video_primary_presented: non_neg_integer(), video_primary_ever_presented: boolean(), last_presented_streams: [ %{ renderer_epoch: non_neg_integer(), target_id: binary(), target_incarnation: non_neg_integer(), stream_id: non_neg_integer() } ], page_flip_events: non_neg_integer(), page_flip_sequence_steps: non_neg_integer(), missed_vblanks: non_neg_integer(), current_pending: non_neg_integer(), current_direct_imports: non_neg_integer(), current_retired_imports: non_neg_integer(), max_retired_imports: non_neg_integer(), current_prepared: non_neg_integer(), current_in_flight: non_neg_integer() }, vulkan_video: %{ acquire_sync_fd_imported: non_neg_integer(), temporary_semaphore_import_failures: non_neg_integer(), ownership_acquires_submitted: non_neg_integer(), acquire_submit_failures: non_neg_integer(), ganesh_waits_rejected: non_neg_integer(), releases_submitted: non_neg_integer(), release_submit_failures: non_neg_integer(), releases_completed: non_neg_integer(), release_fences_created: non_neg_integer(), release_fence_errors: non_neg_integer(), release_fence_completions: non_neg_integer(), retirement_timeouts: non_neg_integer(), import_cap_saturations: non_neg_integer(), quarantined: non_neg_integer(), global_quarantine_terminal: boolean(), device_lost: non_neg_integer() }, layout_cache: layout_cache_stats(), renderer_cache: renderer_cache_stats() } }
Native stats payload. Current schema version: 25.
@type vulkan_drm_node_info() :: %{ path: String.t(), match_field: :primary | :render | String.t(), major: non_neg_integer(), minor: non_neg_integer() }
Functions
Configure native asset loading policy and source roots.
Check if the renderer is still running.
@spec load_font_nif(reference(), String.t(), non_neg_integer(), boolean(), binary()) :: {:ok, boolean()} | {:error, String.t()}
Load a font from binary data and register it with a name.
Parameters
renderer- Renderer that owns the font registrationname- Font family name to register (e.g., "my-font")weight- Font weight (100-900, 400=normal, 700=bold)italic- Whether this is an italic variantdata- Binary font data (TTF file contents)
Example
{:ok, data} = File.read("fonts/MyFont-Bold.ttf")
{:ok, true} = EmergeSkia.Native.load_font_nif(renderer, "my-font", 700, false, data)
Measure text dimensions.
Returns {width, line_height, ascent, descent}.
@spec render_tree_to_pixels_nif( binary(), map() ) :: binary() | {:ok, binary()} | {:error, String.t()}
Render a tree to an RGBA pixel buffer (synchronous, no window).
The tree is an encoded EMRG binary. Offline tree-rendering helpers build the
private map; it is not the public renderer-start config.
It shares asset policy with Emerge.Config.Assets and adds offscreen asset mode.
@spec render_tree_to_png_nif( binary(), map() ) :: binary() | {:ok, binary()} | {:error, String.t()}
Render a tree to an encoded PNG binary (synchronous, no window).
The tree is an encoded EMRG binary. Offline tree-rendering helpers build the
private map; it is not the public renderer-start config.
It shares asset policy with Emerge.Config.Assets and adds offscreen asset mode.
Apply EMRG patches, run layout, and render immediately. Window dimensions come from the initial start config and resize events.
Upload a full EMRG tree, run layout, and render immediately. Window dimensions come from the initial start config and resize events.
@spec set_input_mask(reference(), non_neg_integer()) :: :ok
Private observer-mask transport; applications declare config.input.events.
This filters raw forwarding only, not element interactions or window close.
Mask bits:
- 0x01: Key events
- 0x02: Text commit/preedit/clear/surrounding deletion events
- 0x04: Cursor position events
- 0x08: Cursor button events
- 0x10: Cursor scroll events
- 0x20: Cursor enter/exit events
- 0x40: Resize events
- 0x80: Focus events
- 0xFF: All events
Set the target process to receive input events.
Input events are sent directly to the target process as
{:emerge_skia_event, event} messages.
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(String.t(), non_neg_integer(), non_neg_integer()) :: reference() | {:ok, reference()} | {:error, term()}
Private window NIF binding. Application startup uses
EmergeSkia.start(%Emerge.Config{}), not this positional transport entrypoint.
Returns a renderer resource for internal operations.
@spec start_opts(%{ owner: pid() | nil, backend: String.t(), rendering_api: %{ kind: String.t(), raster_present: String.t(), raster_present_configured: boolean() }, title: String.t(), width: non_neg_integer(), height: non_neg_integer(), always_on_top: boolean(), drm_card: String.t() | nil, drm_output: String.t() | nil, drm_mode: String.t() | nil, vulkan_drm_node: String.t() | nil, drm_hotplug_interval_ms: pos_integer() | nil, drm_startup_retries: non_neg_integer(), drm_retry_interval_ms: non_neg_integer(), drm_force_gpu_finish: boolean(), asset_sources: [String.t()], asset_runtime_enabled: boolean(), asset_allowlist: [String.t()], asset_follow_symlinks: boolean(), asset_max_file_size: pos_integer(), asset_extensions: [String.t()], asset_cache_max_entries: non_neg_integer(), asset_cache_max_bytes: non_neg_integer(), asset_svg_tree_max_entries: non_neg_integer(), asset_svg_tree_max_bytes: non_neg_integer(), asset_decode_at_size: boolean(), drm_cursor: [ %{ icon: String.t(), source: String.t(), hotspot_x: float(), hotspot_y: float() } ], scroll_line_pixels: float(), hw_cursor: boolean(), input_log: boolean(), render_log: boolean(), close_signal_log: boolean(), stats_enabled: boolean(), renderer_stats_log: boolean(), renderer_animation_log: boolean(), renderer_cache: %{ enabled: boolean(), max_new_payloads_per_frame: non_neg_integer(), paint_layer: %{ max_entries: non_neg_integer(), max_bytes: non_neg_integer(), max_entry_bytes: non_neg_integer(), min_visible_before_store: non_neg_integer(), max_stale_frames: non_neg_integer() } }, target_fps: pos_integer() | nil, headless: %{ target: pid() | nil, mode: String.t(), pixel_format: String.t(), bw1_polarity: String.t(), dither: boolean(), frame_message: String.t(), prime: %{max_in_flight: pos_integer(), on_backpressure: String.t()} } }) :: reference() | {:ok, reference()} | {:error, term()}
Start through the private native map ABI.
Internal config/option adapters derive this map from normalized config. These transport names are not accepted public startup keywords.
Stop the renderer and close the window.
@spec tree_clear(reference()) :: :ok
Clear the tree.
Check if the tree is empty.
@spec tree_layout(reference(), float(), float(), float()) :: {:ok, [{binary(), float(), float(), float(), float()}]} | {:error, String.t()}
Compute layout for the tree with given width/height constraints and scale factor.
Returns a list of {id_binary, x, y, width, height} tuples for all elements.
The id_binary is the element id encoded as <<id::unsigned-big-64>>.
Scale is applied to all pixel-based attributes (px sizes, padding, spacing, border radius, border width, font size). Use scale > 1.0 for high-DPI displays.
@spec tree_new() :: reference()
Create a new empty tree resource.
@spec tree_node_count(reference()) :: non_neg_integer()
Get the number of nodes in the tree.
Apply patches to an existing tree.
@spec tree_patch_roundtrip(reference(), binary()) :: binary() | {:ok, binary()} | {:error, String.t()}
Apply patches to an existing tree and return the encoded EMRG binary.
Decode an EMRG binary in Rust and re-encode it.
Upload a full tree from EMRG binary format. Replaces any existing tree contents.
@spec tree_upload_roundtrip(reference(), binary()) :: binary() | {:ok, binary()} | {:error, String.t()}
Upload a full tree and return the encoded EMRG binary.