Version: 1.2.1-build3
Last updated: 2026-09-01
Language: | English
This page documents the API that switches the render path
without restarting
the application, together with Handoff Meta,
which carries state across a
switch.
The switch itself (blink_web_view_switch_render_path()
and the
render-path-changed signal) is described in
signals-api-en.md. This page focuses
on the handoff
metadata and how the two are used together.
Render paths differ in what they are good at. CPU compositing is
broadly
compatible; handing dmabuf directly to the GPU is better for bandwidth
and CPU
time. Which one is preferable depends on the reader's machine and the
moment.
Changing paths used to mean starting the process again. To a reader
that is a
pause of several seconds. BlinkShift makes it a switch inside
the running
process.
Handoff Meta expresses what is carried across a switch, in a form
that does not
depend on any particular path.
Geometry is carried as logical pixels plus an explicit scale.
physical px = logical x device_scale_factor
Physical pixels are not passed on their own, so that the receiving
side never
has to guess the scale. When the two sides disagree about scale, it
shows up as
content drawn at half size.
#define BLINK_HANDOFF_META_SCHEMA_VERSION 1
typedef struct _BlinkHandoffMeta {
int schema_version; /* == BLINK_HANDOFF_META_SCHEMA_VERSION */
int logical_width; /* geometry: logical px */
int logical_height;
int device_scale_factor; /* physical px = logical x this */
char* url; /* content: current URL (owned) */
int scroll_x; /* logical px */
int scroll_y;
char* path_id; /* output path: "software" / "egl" ... (owned) */
} BlinkHandoffMeta;schema_version exists for forward compatibility. A
reader of the structure
should check it against the version it knows before
using the contents.
| Function | Purpose |
|---|---|
blink_web_view_export_handoff_meta() |
Write the current state into a meta. The caller frees the result |
blink_web_view_import_handoff_meta() |
Restore state from a meta |
blink_handoff_meta_serialize() |
Turn a meta into text. The caller frees the result |
blink_handoff_meta_deserialize() |
Build a meta from text. The caller frees the result |
blink_handoff_meta_free() |
Free a meta |
BlinkHandoffMeta* blink_web_view_export_handoff_meta(BlinkWebView* web_view);
void blink_web_view_import_handoff_meta(BlinkWebView* web_view,
const BlinkHandoffMeta* meta);
char* blink_handoff_meta_serialize(const BlinkHandoffMeta* meta);
BlinkHandoffMeta* blink_handoff_meta_deserialize(const char* text);
void blink_handoff_meta_free(BlinkHandoffMeta* meta);export returns NULL on failure. Free the
result of serialize with
free(), and the results of deserialize and
export with
blink_handoff_meta_free().
The serialized form is INI-compatible text and can be read the same
way as the
session file.
Only path-independent semantic state is carried.
| Carried | Not carried |
|---|---|
| Geometry (logical px + scale) | EGL context |
| URL | Wayland surface |
| Scroll position (logical px) | dmabuf file descriptors |
| Output path id | Surface identifiers |
Everything in the right column is recreated on each switch. Because
no raw
resource is carried across, a failed switch can return to the previous
path.
The following state is not something the engine can judge the
correctness
of, so it is not carried:
Attaching a type to a value the engine cannot verify produces a field
that
looks safe but whose contents nothing guarantees. Save and
restore these in
the application.
Page geometry such as column pitch is available from the vertical
writing
metrics API. Keeping a copy in the meta would mean two places holding
the same
value, where one can go stale unnoticed.
Take a meta before and after the switch and compare them. The switch
is
asynchronous, so completion arrives as a signal.
#include <blink_gtk/blink_gtk.h>
#include <gtk/gtk.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int g_seq = 0;
static char* g_before = NULL;
static void on_render_path_changed(BlinkWebView* view,
const char* to,
const char* result,
int gate_ms,
int seq,
gpointer user_data) {
(void)user_data;
if (seq != g_seq) {
return; /* a different switch; ignore when the key does not match */
}
if (strcmp(result, "ok") != 0) {
printf("switch did not take (%s); the previous path is still in use\n", result);
return;
}
BlinkHandoffMeta* after = blink_web_view_export_handoff_meta(view);
if (after != NULL) {
char* text = blink_handoff_meta_serialize(after);
printf("path=%s %d ms\n", to, gate_ms);
printf(" before: %s", g_before != NULL ? g_before : "(unavailable)\n");
printf(" after: %s", text != NULL ? text : "(unavailable)\n");
free(text);
blink_handoff_meta_free(after);
}
}
static void request_switch(BlinkWebView* view, const char* path_id) {
BlinkHandoffMeta* before = blink_web_view_export_handoff_meta(view);
if (before != NULL) {
free(g_before);
g_before = blink_handoff_meta_serialize(before);
blink_handoff_meta_free(before);
}
g_seq = blink_web_view_switch_render_path(view, path_id);
if (g_seq == 0) {
printf("the switch request was not accepted\n");
}
}
int main(int argc, char** argv) {
blink_gtk_init(&argc, &argv);
GtkWidget* window = gtk_window_new();
GtkWidget* widget = blink_web_view_new();
BlinkWebView* view = BLINK_WEB_VIEW(widget);
g_signal_connect(view, "render-path-changed",
G_CALLBACK(on_render_path_changed), NULL);
gtk_window_set_child(GTK_WINDOW(window), widget);
gtk_window_set_default_size(GTK_WINDOW(window), 1024, 768);
blink_web_view_load_uri(view, "https://example.com/");
gtk_widget_set_visible(window, TRUE);
request_switch(view, "P2");
blink_gtk_run_main_loop();
free(g_before);
blink_gtk_shutdown();
return 0;
}blink_web_view_switch_render_path() returns the
sequence number of the
accepted switch (1 or greater; 0 means the request was not accepted).
The
completion signal carries the same number in its seq
argument.
When switches follow one another, matching before and after
by how close the
timestamps are will pair records from different switches.
Worse, the pairing
looks like agreement, so the mistake goes unnoticed. Use the number.
result |
Meaning |
|---|---|
ok |
The new path is drawing stably |
rollback |
The new path failed its check and the previous path was restored. The display is intact |
fail |
The previous path could not be restored. Treat as an error |
rollback is a failure, but the reader's screen is not
broken by it.
| id | Meaning |
|---|---|
P1 |
CPU compositing, direct delivery (default) |
P2 |
CPU compositing, shared-memory delivery |
P3 |
GPU dmabuf delivery. Only the output layer is exchanged, so a failure can return to CPU compositing |
blink_web_view_get_render_path() returns the current
path id. The value
changes only when a switch completes with ok.
render-path-changed signal