git.lucas.co / cce-browser
web browser (Servo)
git clone https://git.lucas.co/cce-browser.git

spike/wpe-spike.c (7.7K)

  1 /*
  2  * WPE embedding spike — reference, NOT part of the build.
  3  *
  4  * Proves the whole embedding contract WPE-PORT.md plans against: own the
  5  * display, own the view, own the toplevel, and get rendered pixels out as a
  6  * CPU-mappable buffer. Verified 2026-08-27 against wpewebkit 2.52.6-1 —
  7  * renders example.com pixel-correct at 1200x800.
  8  *
  9  *   gcc -Wno-deprecated-declarations spike/wpe-spike.c -o /tmp/wpe-spike \
 10  *       $(pkg-config --cflags --libs wpe-webkit-2.0 wpe-platform-2.0)
 11  *   /tmp/wpe-spike https://example.com     # writes /tmp/wpe-spike.ppm
 12  *
 13  * It is C because the point was to settle the object graph fast, before
 14  * paying for bindgen and GObject subclassing from Rust. Keep it as the
 15  * thing to read while writing that; the two traps below cost hours and
 16  * neither produces an error message.
 17  *
 18  * TRAP 1 — the toplevel owns format negotiation. WebKit asks
 19  * WPEToplevelClass.get_preferred_buffer_formats, NOT the display's. Leave
 20  * WPEDisplayClass.create_toplevel NULL and render_buffer never fires, with
 21  * no warning and a perfectly healthy web process.
 22  *
 23  * TRAP 2 — the buffer handshake has two halves. wpe_view_buffer_rendered
 24  * means "displayed"; wpe_view_buffer_released means "the memory is yours
 25  * again". Call only the first and you get exactly one frame, then a
 26  * permanent stall. This is also the backpressure that makes the unbounded
 27  * upload queue of the Servo path impossible here.
 28  */
 29 #include <wpe/webkit.h>
 30 #include <wpe/wpe-platform.h>
 31 #include <stdio.h>
 32 
 33 static GMainLoop *loop;
 34 static int frames = 0;
 35 
 36 /* ---- our WPEView: receives rendered buffers ---- */
 37 #define SPIKE_TYPE_VIEW (spike_view_get_type())
 38 G_DECLARE_FINAL_TYPE(SpikeView, spike_view, SPIKE, VIEW, WPEView)
 39 struct _SpikeView { WPEView parent; };
 40 G_DEFINE_TYPE(SpikeView, spike_view, WPE_TYPE_VIEW)
 41 
 42 static gboolean spike_view_render_buffer(WPEView *view, WPEBuffer *buffer,
 43                                          const WPERectangle *damage, guint n_damage,
 44                                          GError **error) {
 45     int w = wpe_buffer_get_width(buffer), h = wpe_buffer_get_height(buffer);
 46     const char *kind = WPE_IS_BUFFER_SHM(buffer) ? "SHM"
 47                      : (WPE_IS_BUFFER_DMA_BUF(buffer) ? "DMABuf" : "other");
 48     printf("render_buffer #%d: %dx%d  type=%s  damage_rects=%u\n",
 49            ++frames, w, h, kind, n_damage);
 50 
 51     if (WPE_IS_BUFFER_SHM(buffer)) {
 52         WPEBufferSHM *shm = WPE_BUFFER_SHM(buffer);
 53         GBytes *bytes = wpe_buffer_shm_get_data(shm);
 54         gsize len = 0; const guchar *px = g_bytes_get_data(bytes, &len);
 55         guint stride = wpe_buffer_shm_get_stride(shm);
 56         printf("  bytes=%zu stride=%u format=%d\n", len, stride,
 57                (int)wpe_buffer_shm_get_format(shm));
 58         if (frames == 2) {                 // let the page settle a little
 59             FILE *f = fopen("/tmp/wpe-spike.ppm", "wb");
 60             fprintf(f, "P6\n%d %d\n255\n", w, h);
 61             for (int y = 0; y < h; y++)
 62                 for (int x = 0; x < w; x++) {
 63                     const guchar *p = px + y * stride + x * 4;   // BGRA
 64                     fputc(p[2], f); fputc(p[1], f); fputc(p[0], f);
 65                 }
 66             fclose(f);
 67             printf("  WROTE /tmp/wpe-spike.ppm\n");
 68             g_main_loop_quit(loop);
 69         }
 70     }
 71     // Both halves of the handshake: displayed, then memory returned.
 72     wpe_view_buffer_rendered(view, buffer);
 73     wpe_view_buffer_released(view, buffer);
 74     return TRUE;
 75 }
 76 static void spike_view_init(SpikeView *v) {}
 77 static void spike_view_class_init(SpikeViewClass *k) {
 78     WPE_VIEW_CLASS(k)->render_buffer = spike_view_render_buffer;
 79 }
 80 
 81 /* ---- our WPEToplevel: WebKit asks IT for buffer formats ---- */
 82 #define SPIKE_TYPE_TOPLEVEL (spike_toplevel_get_type())
 83 G_DECLARE_FINAL_TYPE(SpikeToplevel, spike_toplevel, SPIKE, TOPLEVEL, WPEToplevel)
 84 struct _SpikeToplevel { WPEToplevel parent; };
 85 G_DEFINE_TYPE(SpikeToplevel, spike_toplevel, WPE_TYPE_TOPLEVEL)
 86 
 87 #define FOURCC(a,b,c,d) ((guint32)(a)|((guint32)(b)<<8)|((guint32)(c)<<16)|((guint32)(d)<<24))
 88 static WPEBufferFormats *spike_toplevel_formats(WPEToplevel *t) {
 89     WPEBufferFormatsBuilder *b = wpe_buffer_formats_builder_new(NULL);
 90     wpe_buffer_formats_builder_append_group(b, NULL, WPE_BUFFER_FORMAT_USAGE_MAPPING);
 91     wpe_buffer_formats_builder_append_format(b, FOURCC('A','R','2','4'), 0);
 92     wpe_buffer_formats_builder_append_format(b, FOURCC('X','R','2','4'), 0);
 93     return wpe_buffer_formats_builder_end(b);
 94 }
 95 static gboolean spike_toplevel_resize(WPEToplevel *t, int w, int h) {
 96     printf("toplevel resize -> %dx%d\n", w, h);
 97     wpe_toplevel_resized(t, w, h);
 98     return TRUE;
 99 }
100 static void spike_toplevel_init(SpikeToplevel *t) {}
101 static void spike_toplevel_class_init(SpikeToplevelClass *k) {
102     WPEToplevelClass *tc = WPE_TOPLEVEL_CLASS(k);
103     tc->get_preferred_buffer_formats = spike_toplevel_formats;
104     tc->resize = spike_toplevel_resize;
105 }
106 
107 /* ---- our WPEDisplay: vends the view above ---- */
108 #define SPIKE_TYPE_DISPLAY (spike_display_get_type())
109 G_DECLARE_FINAL_TYPE(SpikeDisplay, spike_display, SPIKE, DISPLAY, WPEDisplay)
110 struct _SpikeDisplay { WPEDisplay parent; };
111 G_DEFINE_TYPE(SpikeDisplay, spike_display, WPE_TYPE_DISPLAY)
112 
113 static gboolean spike_display_connect(WPEDisplay *d, GError **e) { return TRUE; }
114 
115 #define FOURCC(a,b,c,d) ((guint32)(a)|((guint32)(b)<<8)|((guint32)(c)<<16)|((guint32)(d)<<24))
116 /* No EGL display and no DRM device -> WebKit should fall back to mappable SHM.
117    Advertise ARGB/XRGB with LINEAR so it has something to pick. */
118 static WPEBufferFormats *spike_display_formats(WPEDisplay *d) {
119     WPEBufferFormatsBuilder *b = wpe_buffer_formats_builder_new(NULL);
120     wpe_buffer_formats_builder_append_group(b, NULL, WPE_BUFFER_FORMAT_USAGE_RENDERING);
121     wpe_buffer_formats_builder_append_format(b, FOURCC('A','B','2','4'), 0 /*LINEAR*/);
122     wpe_buffer_formats_builder_append_format(b, FOURCC('X','B','2','4'), 0);
123     return wpe_buffer_formats_builder_end(b);
124 }
125 static WPEView *spike_display_create_view(WPEDisplay *d) {
126     return g_object_new(SPIKE_TYPE_VIEW, "display", d, NULL);
127 }
128 static WPEToplevel *spike_display_create_toplevel(WPEDisplay *d, guint max_views) {
129     printf("create_toplevel(max_views=%u)\n", max_views);
130     return g_object_new(SPIKE_TYPE_TOPLEVEL, "display", d, "max-views", max_views, NULL);
131 }
132 static void spike_display_init(SpikeDisplay *d) {}
133 static void spike_display_class_init(SpikeDisplayClass *k) {
134     WPEDisplayClass *dc = WPE_DISPLAY_CLASS(k);
135     dc->connect = spike_display_connect;
136     dc->create_view = spike_display_create_view;
137     dc->get_preferred_buffer_formats = spike_display_formats;
138     dc->create_toplevel = spike_display_create_toplevel;
139 }
140 
141 int main(int argc, char **argv) {
142     const char *url = argc > 1 ? argv[1] : "https://example.com";
143     WPEDisplay *display = g_object_new(SPIKE_TYPE_DISPLAY, NULL);
144     GError *err = NULL;
145     if (!wpe_display_connect(display, &err)) {
146         printf("connect failed: %s\n", err ? err->message : "?"); return 1;
147     }
148     WebKitWebView *wv = g_object_new(WEBKIT_TYPE_WEB_VIEW, "display", display, NULL);
149     WPEView *v = webkit_web_view_get_wpe_view(wv);
150     printf("web view=%p  its wpe_view=%p\n", (void*)wv, (void*)v);
151     WPEToplevel *top = wpe_display_create_toplevel(display, 1);
152     printf("toplevel=%p\n", (void*)top);
153     if (top) { wpe_toplevel_resized(top, 1200, 800); wpe_view_set_toplevel(v, top); }
154     wpe_view_resized(v, 1200, 800);
155     wpe_view_set_visible(v, TRUE);
156     wpe_view_map(v);
157     printf("view now %dx%d visible=%d mapped=%d\n", wpe_view_get_width(v),
158            wpe_view_get_height(v), wpe_view_get_visible(v), wpe_view_get_mapped(v));
159     webkit_web_view_load_uri(wv, url);
160     loop = g_main_loop_new(NULL, FALSE);
161     g_timeout_add_seconds(25, (GSourceFunc)g_main_loop_quit, loop);
162     g_main_loop_run(loop);
163     printf("done, %d frames\n", frames);
164     return frames ? 0 : 2;
165 }