The main change is configuration: return an Emerge.Config struct instead of a
renderer keyword list. UI construction, element events and viewport state work
as before. Raw input callbacks are now opt-in and use clearer event names.
1. Update the dependency
In mix.exs:
{:emerge, "~> 0.5.0"}Then run:
mix deps.update emerge
Emerge fetches the matching precompiled renderer for supported targets. There is no new source-build step for a normal upgrade. Existing compile-time backend selection stays unchanged.
2. Return a config from mount
0.4.2:
@impl Viewport
def mount(opts) do
{:ok, %{count: 0}, Keyword.merge([title: "Counter", width: 800, height: 600], opts)}
end0.5.0:
@impl Viewport
def mount(opts) do
output = %Emerge.Config.Window{
title: Keyword.get(opts, :title, "Counter"),
size: Keyword.get(opts, :size, {800, 600})
}
{:ok, %{count: 0}, %Emerge.Config{output: output}}
endThe state and render/1 do not change. A stateless viewport returns
{:ok, %Emerge.Config{}} and still uses render/0.
Arguments to start_link/1 are still keywords, but your mount callback decides
which ones to use. In this example, pass size: {1024, 768} instead of separate
width and height arguments. Process options such as name: stay unchanged.
If you start EmergeSkia directly
Replace keyword startup with a config, and move otp_app into assets:
# 0.4.2
EmergeSkia.start(otp_app: :my_app, title: "Preview", rendering_api: :raster)# 0.5.0
EmergeSkia.start(%Emerge.Config{
output: %Emerge.Config.Window{title: "Preview"},
renderer: %Emerge.Config.Renderer{api: :raster},
assets: %Emerge.Config.Assets{otp_app: :my_app}
})Viewports infer their asset application when possible. Standalone callers must supply it. Old positional startup calls and keyword renderer configs are removed.
3. Move settings into their groups
The config has six groups. You only need to supply the ones you customize;
all others use defaults. Remove the old emerge_skia: and viewport: wrappers.
Window and renderer
This is a typical replacement for a window's flat options:
%Emerge.Config{
output: %Emerge.Config.Window{title: "My App", size: {1024, 768}},
renderer: %Emerge.Config.Renderer{api: :opengl}
}backend: :waylandor:macosbecomesWindow.backend; usually leave it automatic.titleandalways_on_topmove intoWindow.widthandheightbecome thesizetuple.rendering_api(or the oldbackend_rendereralias) becomesRenderer.api. Use:opengl, not the old:glalias.- Raster's nested
presentsetting becomesRenderer.raster_present. vulkan_drm_nodebecomesRenderer.device.renderer_cachebecomesRenderer.cache; its field names are unchanged.headless.target_fpsbecomesRenderer.target_fps.
See Emerge.Config.Window and Emerge.Config.Renderer for field descriptions.
DRM display
Replace backend: :drm, drm_card, drm_output and drm_mode with a DRM record:
%Emerge.Config{
output: %Emerge.Config.DRM{card: "/dev/dri/card0", connector: "DSI-1"},
renderer: %Emerge.Config.Renderer{api: :opengl}
}drm_mode becomes mode. Old width/height preferences become
preferred_size: {width, height}; the selected display mode determines the actual
size. Display discovery still uses EmergeSkia.drm_outputs(drm_card: path).
See Emerge.Config.DRM for display-off and brightness settings.
Headless output
Replace the backend flag and nested headless options with a headless record:
%Emerge.Config{
output: %Emerge.Config.Headless{
target: self(),
size: {400, 300},
frames: %Emerge.Config.BinaryFrames{pixel_format: :bw1, dither: true}
},
renderer: %Emerge.Config.Renderer{api: :raster},
assets: %Emerge.Config.Assets{otp_app: :my_app}
}target and frame_message keep their names. Binary pixel_format, bw1_polarity
and dither move into BinaryFrames. For PRIME output, use PrimeFrames instead;
prime.drm_node becomes PrimeFrames.device, while max_in_flight and
on_backpressure keep their names. The explicit frame struct replaces the old
:binary/:prime mode flag. Frame messages still contain VideoInterop.Frame.
Assets
Move the root otp_app into assets. Other asset field names stay the same:
%Emerge.Config{
assets: %Emerge.Config.Assets{
otp_app: :my_app,
fonts: [%Emerge.Config.Font{family: "Inter", source: "fonts/Inter-Regular.ttf"}]
}
}Use RuntimePaths for assets.runtime_paths and AssetCache for assets.cache.
See Use assets for the full examples.
Input and cursor
viewport.input_maskis replaced byInput.events; see the next step.scroll_line_pixelsmoves intoInput.hw_cursorbecomesInput.cursor.hardware.- Cursor image overrides move from
drm_cursorintoInput.cursor, usingCursorIconrecords.sourceandhotspotkeep their names.
Diagnostics
Instead of individual logging booleans, choose categories in Diagnostics.logs:
input_log: true→:inputrender_log: true→:renderclose_signal_log: true→:closerenderer_stats_log: true→:statsrenderer_animation_log: true→:animation
For example, to keep automatic performance summaries:
%Emerge.Config{
diagnostics: %Emerge.Config.Diagnostics{logs: [:stats]}
}stats: true collects data for EmergeSkia.stats/2 without printing summaries.
Use logs: [:stats] when you want printed summaries; specifying both is unnecessary.
macOS supports printed summaries, not the stats API. The old stats field moves
into Diagnostics.stats; drm_force_gpu_finish becomes Diagnostics.force_gpu_finish.
Runtime
Most applications can leave this group at its defaults. If you customized it:
drm_startup_retries→Runtime.startup_retriesdrm_retry_interval_ms→Runtime.retry_interval_msviewport.renderer_check_interval_ms→Runtime.health_check_interval_msviewport.renderer_module→Runtime.adapterviewport.renderer_opts→Runtime.adapter_opts
Custom adapter authors need to update to the new config contract. Ordinary applications use the default adapter and need no adapter changes.
4. Update raw input callbacks, if you use them
Buttons, text inputs, scrolling, element key bindings and window close callbacks
continue to work without raw subscriptions. If you use handle_input/2, explicitly
choose its events; the new default is none:
%Emerge.Config{
input: %Emerge.Config.Input{events: [:key, :pointer_button, :resize]}
}Use Emerge.Config.Input.all_events/0 for all categories. Numeric masks and the
:all/:none shortcuts are no longer accepted.
Update callback patterns:
- Key/button actions
1and0become:pressand:release. :cursor_posbecomes:pointer_move;:cursor_buttonbecomes:pointer_button.:cursor_scrollbecomes:pointer_scroll;:cursor_enteredbecomes:pointer_enter.:resizedbecomes:resize;:focusedbecomes:focus.- Subscribe to
:textfor committed text and IME updates.
For example, {:key, {:escape, 1, modifiers}} becomes
{:key, {:escape, :press, modifiers}}. See the
input guide for complete callback values.
These changes apply to viewport callbacks, not standalone native event messages.
DRM touchscreen observers now subscribe to :touch and receive
{:touch, {contact_id, :down | :move | :up | :cancel, {x, y}}} instead of emulated
mouse-button callbacks. Each contact has an independent lifetime. Virtual keys
activate on that contact's release; cancellation never activates a key. This does
not change mouse observations or require raw subscriptions for ordinary UI input.
Optional: change settings while running
Existing mount-only configuration needs no additional callback. To make a supported
setting follow application state, add config(previous, state) (or config(previous)
for a stateless viewport). Implement only one arity:
@impl Viewport
def config(previous, state) do
output = %{previous.output | title: state.title}
%{previous | output: output}
endCall Viewport.rerender(state) as usual after changing state. The callback receives
the previous config with all nested records expanded into structs. Update those
records to keep other settings; supplying a new record uses its defaults, not
values from the previous one. Lists also replace rather than append.
Settings you can change while running
- Raw input subscriptions and the renderer health-check interval.
- Window titles on Wayland and macOS.
- Frame-rate limits on headless and Wayland outputs.
- DRM display on/off and brightness with explicit OpenGL, Vulkan or raster (CPU or GPU-upload presentation).
Settings that need a new viewport
Output selection, size, renderer API/device, caches, cursor appearance, assets, diagnostics and adapter settings cannot be changed by the callback.
Backend limitations
Frame-rate limits are not available on DRM or macOS. DRM power/brightness controls require an explicit OpenGL, Vulkan or raster renderer; automatic API selection remains unsupported. Raster GPU upload requires an OpenGL-enabled DRM build. See configuration records and each config module for the settings relevant to your output.