Merge branch 'master' into 'master'

[RFC] Add GBM surface backend for zero-copy GPU buffer sharing

See merge request cairo/cairo!653
This commit is contained in:
Alberto Ruiz 2026-05-02 20:49:09 +00:00
commit 610b15ecd6
16 changed files with 2523 additions and 1 deletions

View file

@ -0,0 +1,172 @@
/*
* Copyright © 2026 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software
* and its documentation for any purpose is hereby granted without
* fee, provided that the above copyright notice appear in all copies
* and that both that copyright notice and this permission notice
* appear in supporting documentation, and that the name of
* Red Hat, Inc. not be used in advertising or publicity pertaining to
* distribution of the software without specific, written prior
* permission. Red Hat, Inc. makes no representations about the
* suitability of this software for any purpose. It is provided "as
* is" without express or implied warranty.
*
* RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Author: Alberto Ruiz <aruiz@redhat.com>
*/
#include "cairo-boilerplate-private.h"
#include <cairo-gbm.h>
#include <gbm.h>
#include <xf86drm.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
typedef struct _gbm_target_closure {
struct gbm_device *gbm_device;
int drm_fd;
} gbm_target_closure_t;
static struct gbm_device *
_open_gbm_device (int *fd_out)
{
drmDevice *devices[16];
int n, i;
n = drmGetDevices2 (0, devices, 16);
for (i = 0; i < n; i++) {
int fd;
struct gbm_device *dev;
if (!(devices[i]->available_nodes & (1 << DRM_NODE_RENDER)))
continue;
fd = open (devices[i]->nodes[DRM_NODE_RENDER], O_RDWR);
if (fd < 0)
continue;
dev = gbm_create_device (fd);
if (dev) {
*fd_out = fd;
drmFreeDevices (devices, n);
return dev;
}
close (fd);
}
if (n > 0)
drmFreeDevices (devices, n);
return NULL;
}
static cairo_surface_t *
_cairo_boilerplate_gbm_create_surface (const char *name,
cairo_content_t content,
double width,
double height,
double max_width,
double max_height,
cairo_boilerplate_mode_t mode,
void **closure)
{
gbm_target_closure_t *gtc;
cairo_format_t format;
cairo_surface_t *surface;
*closure = gtc = xcalloc (1, sizeof (gbm_target_closure_t));
gtc->gbm_device = _open_gbm_device (&gtc->drm_fd);
if (gtc->gbm_device == NULL) {
free (gtc);
CAIRO_BOILERPLATE_DEBUG (("Failed to open GBM device\n"));
return NULL;
}
width = ceil (width);
if (width < 1)
width = 1;
height = ceil (height);
if (height < 1)
height = 1;
if (content == CAIRO_CONTENT_COLOR)
format = CAIRO_FORMAT_RGB24;
else if (content == CAIRO_CONTENT_ALPHA)
format = CAIRO_FORMAT_A8;
else
format = CAIRO_FORMAT_ARGB32;
surface = cairo_gbm_surface_create (gtc->gbm_device,
format,
(int) width,
(int) height);
if (cairo_surface_status (surface)) {
cairo_surface_destroy (surface);
gbm_device_destroy (gtc->gbm_device);
close (gtc->drm_fd);
free (gtc);
CAIRO_BOILERPLATE_DEBUG (("Failed to create GBM surface\n"));
return NULL;
}
return surface;
}
static void
_cairo_boilerplate_gbm_cleanup (void *closure)
{
gbm_target_closure_t *gtc = closure;
if (gtc->gbm_device)
gbm_device_destroy (gtc->gbm_device);
if (gtc->drm_fd >= 0)
close (gtc->drm_fd);
free (gtc);
}
static const cairo_boilerplate_target_t targets[] = {
{
"gbm", "image", NULL, NULL,
CAIRO_SURFACE_TYPE_GBM, CAIRO_CONTENT_COLOR_ALPHA, 0,
"cairo_gbm_surface_create",
_cairo_boilerplate_gbm_create_surface,
cairo_surface_create_similar,
NULL, NULL,
_cairo_boilerplate_get_image_surface,
cairo_surface_write_to_png,
_cairo_boilerplate_gbm_cleanup,
NULL,
NULL,
TRUE, FALSE, FALSE
},
{
"gbm", "image", NULL, NULL,
CAIRO_SURFACE_TYPE_GBM, CAIRO_CONTENT_COLOR, 0,
"cairo_gbm_surface_create",
_cairo_boilerplate_gbm_create_surface,
cairo_surface_create_similar,
NULL, NULL,
_cairo_boilerplate_get_image_surface,
cairo_surface_write_to_png,
_cairo_boilerplate_gbm_cleanup,
NULL,
NULL,
FALSE, FALSE, FALSE
},
};
CAIRO_BOILERPLATE (gbm, targets)

View file

@ -13,6 +13,7 @@ cairo_boilerplate_feature_sources = {
'cairo-ps': ['cairo-boilerplate-ps.c'],
'cairo-svg': ['cairo-boilerplate-svg.c'],
'cairo-script': ['cairo-boilerplate-script.c'],
'cairo-gbm': ['cairo-boilerplate-gbm.c'],
}
foreach feature: built_features

View file

@ -0,0 +1,915 @@
/* gbm-egl-benchmark.c — Compare image-surface vs GBM-surface display paths
*
* Renders an animated scene with Cairo onto both an image surface and a
* GBM surface, then uploads each to a GL texture:
*
* Image path: glTexSubImage2D (CPU memcpy to GPU)
* GBM path: EGL DMA-BUF import (zero-copy)
*
* Headless mode (default):
* Prints per-frame timing for the upload/import step.
* No window system required runs entirely on a DRM render node.
*
* Windowed mode (--window):
* Opens a Wayland window and renders the scene live using the GBM
* zero-copy path, with a timing overlay comparing both methods.
*
* Build: meson builds this automatically when the dependencies are met.
* Run: ./gbm-egl-benchmark [--window] [--frames N] [--width W] [--height H]
*
* Copyright © 2026 Red Hat, Inc.
* Author: Alberto Ruiz <aruiz@redhat.com>
* SPDX-License-Identifier: MIT
*/
#include <cairo.h>
#include <cairo-gbm.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <drm_fourcc.h>
#include <gbm.h>
#include <xf86drm.h>
#include <fcntl.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#ifdef HAS_WAYLAND
#include <wayland-client.h>
#include <wayland-egl.h>
#include "xdg-shell-client-protocol.h"
#endif
/* ------------------------------------------------------------------ */
/* Timing helpers */
/* ------------------------------------------------------------------ */
static double
now_ms (void)
{
struct timespec ts;
clock_gettime (CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000.0 + ts.tv_nsec / 1e6;
}
/* ------------------------------------------------------------------ */
/* EGL helpers */
/* ------------------------------------------------------------------ */
static PFNEGLCREATEIMAGEKHRPROC create_image;
static PFNEGLDESTROYIMAGEKHRPROC destroy_image;
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture;
static void
load_egl_procs (void)
{
create_image = (PFNEGLCREATEIMAGEKHRPROC)
eglGetProcAddress ("eglCreateImageKHR");
destroy_image = (PFNEGLDESTROYIMAGEKHRPROC)
eglGetProcAddress ("eglDestroyImageKHR");
image_target_texture = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)
eglGetProcAddress ("glEGLImageTargetTexture2DOES");
}
/* ------------------------------------------------------------------ */
/* EGL bootstrap — headless (GBM platform, surfaceless) */
/* ------------------------------------------------------------------ */
static int
init_egl_headless (struct gbm_device *gbm_dev,
EGLDisplay *out_dpy, EGLContext *out_ctx)
{
static const EGLint config_attribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8,
EGL_NONE
};
static const EGLint ctx_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE
};
PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display;
EGLDisplay dpy;
EGLConfig cfg;
EGLContext ctx;
EGLint n;
get_platform_display = (PFNEGLGETPLATFORMDISPLAYEXTPROC)
eglGetProcAddress ("eglGetPlatformDisplayEXT");
if (!get_platform_display)
return -1;
dpy = get_platform_display (EGL_PLATFORM_GBM_KHR, gbm_dev, NULL);
if (dpy == EGL_NO_DISPLAY)
return -1;
if (!eglInitialize (dpy, NULL, NULL))
return -1;
if (!eglBindAPI (EGL_OPENGL_ES_API))
return -1;
if (!eglChooseConfig (dpy, config_attribs, &cfg, 1, &n) || n < 1)
return -1;
ctx = eglCreateContext (dpy, cfg, EGL_NO_CONTEXT, ctx_attribs);
if (ctx == EGL_NO_CONTEXT)
return -1;
if (!eglMakeCurrent (dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx))
return -1;
load_egl_procs ();
if (!create_image || !destroy_image || !image_target_texture)
return -1;
*out_dpy = dpy;
*out_ctx = ctx;
return 0;
}
/* ------------------------------------------------------------------ */
/* GL shader helpers */
/* ------------------------------------------------------------------ */
static GLuint
compile_shader (GLenum type, const char *src)
{
GLuint s = glCreateShader (type);
GLint ok;
glShaderSource (s, 1, &src, NULL);
glCompileShader (s);
glGetShaderiv (s, GL_COMPILE_STATUS, &ok);
if (!ok) {
char log[512];
glGetShaderInfoLog (s, sizeof log, NULL, log);
fprintf (stderr, "shader compile: %s\n", log);
}
return s;
}
static GLuint
build_program (void)
{
static const char *vert_src =
"attribute vec2 pos;\n"
"attribute vec2 uv_in;\n"
"varying vec2 uv;\n"
"void main() {\n"
" gl_Position = vec4(pos, 0.0, 1.0);\n"
" uv = uv_in;\n"
"}\n";
static const char *frag_src =
"precision mediump float;\n"
"varying vec2 uv;\n"
"uniform sampler2D tex;\n"
"void main() {\n"
" vec4 c = texture2D(tex, uv);\n"
" gl_FragColor = vec4(c.b, c.g, c.r, c.a);\n" /* BGRA→RGBA swizzle */
"}\n";
GLuint vs = compile_shader (GL_VERTEX_SHADER, vert_src);
GLuint fs = compile_shader (GL_FRAGMENT_SHADER, frag_src);
GLuint prog = glCreateProgram ();
GLint ok;
glAttachShader (prog, vs);
glAttachShader (prog, fs);
glBindAttribLocation (prog, 0, "pos");
glBindAttribLocation (prog, 1, "uv_in");
glLinkProgram (prog);
glGetProgramiv (prog, GL_LINK_STATUS, &ok);
if (!ok) {
char log[512];
glGetProgramInfoLog (prog, sizeof log, NULL, log);
fprintf (stderr, "program link: %s\n", log);
}
glDeleteShader (vs);
glDeleteShader (fs);
return prog;
}
static void
draw_quad (GLuint prog, GLuint tex,
float x0, float y0, float x1, float y1)
{
float verts[] = {
x0, y0, 0, 1,
x1, y0, 1, 1,
x0, y1, 0, 0,
x1, y1, 1, 0,
};
glUseProgram (prog);
glActiveTexture (GL_TEXTURE0);
glBindTexture (GL_TEXTURE_2D, tex);
glUniform1i (glGetUniformLocation (prog, "tex"), 0);
glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE, 16, verts);
glEnableVertexAttribArray (0);
glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE, 16, verts + 2);
glEnableVertexAttribArray (1);
glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
}
/* ------------------------------------------------------------------ */
/* Animated Cairo scene */
/* ------------------------------------------------------------------ */
static void
draw_frame (cairo_surface_t *surface, int width, int height, int frame)
{
cairo_t *cr = cairo_create (surface);
double cx = width / 2.0;
double cy = height / 2.0;
double r = (width < height ? width : height) * 0.4;
double angle = frame * 0.05;
int i;
cairo_pattern_t *bg = cairo_pattern_create_linear (0, 0, width, height);
cairo_pattern_add_color_stop_rgb (bg, 0.0, 0.05, 0.05, 0.15);
cairo_pattern_add_color_stop_rgb (bg, 1.0, 0.15, 0.05, 0.05);
cairo_set_source (cr, bg);
cairo_paint (cr);
cairo_pattern_destroy (bg);
for (i = 0; i < 12; i++) {
double a = angle + i * M_PI / 6.0;
double hue = fmod (i / 12.0 + frame * 0.002, 1.0);
double red = fabs (hue * 6.0 - 3.0) - 1.0;
double green = 2.0 - fabs (hue * 6.0 - 2.0);
double blue = 2.0 - fabs (hue * 6.0 - 4.0);
if (red < 0) red = 0;
if (red > 1) red = 1;
if (green < 0) green = 0;
if (green > 1) green = 1;
if (blue < 0) blue = 0;
if (blue > 1) blue = 1;
cairo_save (cr);
cairo_translate (cr, cx, cy);
cairo_rotate (cr, a);
cairo_set_source_rgba (cr, red, green, blue, 0.6);
cairo_set_line_width (cr, r * 0.08);
cairo_arc (cr, 0, 0, r * (0.5 + 0.3 * sin (angle * 2 + i)),
-0.3, 0.3);
cairo_stroke (cr);
cairo_restore (cr);
}
double pulse = 0.15 + 0.08 * sin (frame * 0.1);
cairo_pattern_t *radial = cairo_pattern_create_radial (
cx, cy, 0, cx, cy, r * pulse * 3);
cairo_pattern_add_color_stop_rgba (radial, 0.0, 1, 1, 1, 0.9);
cairo_pattern_add_color_stop_rgba (radial, 1.0, 1, 1, 1, 0.0);
cairo_set_source (cr, radial);
cairo_arc (cr, cx, cy, r * pulse * 3, 0, 2 * M_PI);
cairo_fill (cr);
cairo_pattern_destroy (radial);
char buf[64];
snprintf (buf, sizeof buf, "frame %d", frame);
cairo_set_source_rgb (cr, 1, 1, 1);
cairo_select_font_face (cr, "sans-serif",
CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size (cr, height * 0.04);
cairo_move_to (cr, 10, height - 10);
cairo_show_text (cr, buf);
cairo_destroy (cr);
}
/* ------------------------------------------------------------------ */
/* Image-surface path: glTexSubImage2D upload */
/* ------------------------------------------------------------------ */
static double
upload_image_surface (cairo_surface_t *surface, GLuint tex,
int width, int height)
{
double t0, t1;
cairo_surface_flush (surface);
glBindTexture (GL_TEXTURE_2D, tex);
t0 = now_ms ();
glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, width, height,
GL_BGRA_EXT, GL_UNSIGNED_BYTE,
cairo_image_surface_get_data (surface));
glFinish ();
t1 = now_ms ();
return t1 - t0;
}
/* ------------------------------------------------------------------ */
/* GBM-surface path: EGL DMA-BUF import */
/* ------------------------------------------------------------------ */
static double
import_gbm_surface (EGLDisplay dpy, cairo_surface_t *surface, GLuint tex,
int width, int height)
{
double t0, t1;
EGLImageKHR image;
struct gbm_bo *bo;
int stride, fd;
cairo_surface_flush (surface);
bo = cairo_gbm_surface_get_bo (surface);
stride = gbm_bo_get_stride (bo);
fd = cairo_gbm_surface_get_dma_buf_fd (surface);
if (fd < 0)
return -1;
EGLint attribs[] = {
EGL_WIDTH, width,
EGL_HEIGHT, height,
EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_ARGB8888,
EGL_DMA_BUF_PLANE0_FD_EXT, fd,
EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
EGL_DMA_BUF_PLANE0_PITCH_EXT, stride,
EGL_NONE
};
glBindTexture (GL_TEXTURE_2D, tex);
t0 = now_ms ();
image = create_image (dpy, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT, NULL, attribs);
if (image == EGL_NO_IMAGE_KHR) {
close (fd);
return -1;
}
image_target_texture (GL_TEXTURE_2D, image);
glFinish ();
t1 = now_ms ();
destroy_image (dpy, image);
close (fd);
return t1 - t0;
}
/* ------------------------------------------------------------------ */
/* Headless benchmark */
/* ------------------------------------------------------------------ */
static int
run_headless (struct gbm_device *gbm_dev, int width, int height, int frames)
{
EGLDisplay egl_dpy;
EGLContext egl_ctx;
cairo_surface_t *img_surface, *gbm_surface;
GLuint tex_img, tex_gbm;
double sum_img = 0, sum_gbm = 0;
double max_img = 0, max_gbm = 0;
int i;
if (init_egl_headless (gbm_dev, &egl_dpy, &egl_ctx) < 0) {
fprintf (stderr, "Failed to init headless EGL\n");
return 1;
}
printf ("EGL vendor: %s\n", eglQueryString (egl_dpy, EGL_VENDOR));
printf ("GL renderer: %s\n\n", glGetString (GL_RENDERER));
img_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
width, height);
gbm_surface = cairo_gbm_surface_create (gbm_dev, CAIRO_FORMAT_ARGB32,
width, height);
glGenTextures (1, &tex_img);
glBindTexture (GL_TEXTURE_2D, tex_img);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
GL_BGRA_EXT, GL_UNSIGNED_BYTE, NULL);
glGenTextures (1, &tex_gbm);
glBindTexture (GL_TEXTURE_2D, tex_gbm);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
/* Warm up */
draw_frame (img_surface, width, height, 0);
upload_image_surface (img_surface, tex_img, width, height);
draw_frame (gbm_surface, width, height, 0);
import_gbm_surface (egl_dpy, gbm_surface, tex_gbm, width, height);
printf ("%5s %12s %12s %s\n",
"frame", "image (ms)", "gbm (ms)", "speedup");
printf ("%5s %12s %12s %s\n",
"-----", "----------", "--------", "-------");
for (i = 0; i < frames; i++) {
double t_img, t_gbm;
draw_frame (img_surface, width, height, i);
t_img = upload_image_surface (img_surface, tex_img, width, height);
draw_frame (gbm_surface, width, height, i);
t_gbm = import_gbm_surface (egl_dpy, gbm_surface, tex_gbm,
width, height);
if (t_gbm < 0)
break;
sum_img += t_img;
sum_gbm += t_gbm;
if (t_img > max_img) max_img = t_img;
if (t_gbm > max_gbm) max_gbm = t_gbm;
if (i < 10 || (i + 1) % 10 == 0)
printf ("%5d %10.3f %10.3f %.1fx\n",
i, t_img, t_gbm,
t_gbm > 0 ? t_img / t_gbm : 0);
}
printf ("\n=== Summary (%d frames at %dx%d) ===\n", frames, width, height);
printf (" image gbm\n");
printf (" avg upload: %8.3f ms %8.3f ms\n",
sum_img / frames, sum_gbm / frames);
printf (" max upload: %8.3f ms %8.3f ms\n", max_img, max_gbm);
printf (" total: %8.1f ms %8.1f ms\n", sum_img, sum_gbm);
printf (" avg speedup: %.1fx\n",
sum_gbm > 0 ? sum_img / sum_gbm : 0);
cairo_surface_destroy (img_surface);
cairo_surface_destroy (gbm_surface);
glDeleteTextures (1, &tex_img);
glDeleteTextures (1, &tex_gbm);
eglDestroyContext (egl_dpy, egl_ctx);
eglTerminate (egl_dpy);
return 0;
}
/* ------------------------------------------------------------------ */
/* Wayland windowed mode */
/* ------------------------------------------------------------------ */
#ifdef HAS_WAYLAND
static struct wl_display *wl_dpy;
static struct wl_compositor *wl_comp;
static struct wl_surface *wl_surf;
static struct xdg_wm_base *xdg_base;
static struct xdg_surface *xdg_surf;
static struct xdg_toplevel *xdg_top;
static int configured;
static int running = 1;
static void xdg_wm_base_ping (void *data, struct xdg_wm_base *base, uint32_t serial)
{
(void) data;
xdg_wm_base_pong (base, serial);
}
static const struct xdg_wm_base_listener xdg_base_listener = {
.ping = xdg_wm_base_ping,
};
static void xdg_surface_configure (void *data, struct xdg_surface *surf, uint32_t serial)
{
(void) data;
xdg_surface_ack_configure (surf, serial);
configured = 1;
}
static const struct xdg_surface_listener xdg_surf_listener = {
.configure = xdg_surface_configure,
};
static void xdg_toplevel_configure (void *data, struct xdg_toplevel *top,
int32_t w, int32_t h,
struct wl_array *states)
{
(void) data; (void) top; (void) w; (void) h; (void) states;
}
static void xdg_toplevel_close (void *data, struct xdg_toplevel *top)
{
(void) data; (void) top;
running = 0;
}
static void xdg_toplevel_configure_bounds (void *data, struct xdg_toplevel *top,
int32_t w, int32_t h)
{
(void) data; (void) top; (void) w; (void) h;
}
static void xdg_toplevel_wm_capabilities (void *data, struct xdg_toplevel *top,
struct wl_array *caps)
{
(void) data; (void) top; (void) caps;
}
static const struct xdg_toplevel_listener xdg_top_listener = {
.configure = xdg_toplevel_configure,
.close = xdg_toplevel_close,
.configure_bounds = xdg_toplevel_configure_bounds,
.wm_capabilities = xdg_toplevel_wm_capabilities,
};
static void registry_global (void *data, struct wl_registry *reg,
uint32_t name, const char *iface, uint32_t ver)
{
(void) data;
if (!strcmp (iface, wl_compositor_interface.name))
wl_comp = wl_registry_bind (reg, name, &wl_compositor_interface, 1);
else if (!strcmp (iface, xdg_wm_base_interface.name))
xdg_base = wl_registry_bind (reg, name, &xdg_wm_base_interface, 1);
}
static void registry_global_remove (void *data, struct wl_registry *reg, uint32_t name)
{
(void) data; (void) reg; (void) name;
}
static const struct wl_registry_listener registry_listener = {
.global = registry_global,
.global_remove = registry_global_remove,
};
static int
init_egl_wayland (struct gbm_device *gbm_dev,
int width, int height,
EGLDisplay *out_dpy, EGLContext *out_ctx,
EGLSurface *out_surf,
struct wl_egl_window **out_egl_win)
{
static const EGLint config_attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8,
EGL_NONE
};
static const EGLint ctx_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE
};
EGLDisplay dpy;
EGLConfig cfg;
EGLContext ctx;
EGLSurface surf;
EGLint n;
struct wl_egl_window *egl_win;
dpy = eglGetDisplay ((EGLNativeDisplayType) wl_dpy);
if (dpy == EGL_NO_DISPLAY)
return -1;
if (!eglInitialize (dpy, NULL, NULL))
return -1;
if (!eglBindAPI (EGL_OPENGL_ES_API))
return -1;
if (!eglChooseConfig (dpy, config_attribs, &cfg, 1, &n) || n < 1)
return -1;
ctx = eglCreateContext (dpy, cfg, EGL_NO_CONTEXT, ctx_attribs);
if (ctx == EGL_NO_CONTEXT)
return -1;
egl_win = wl_egl_window_create (wl_surf, width, height);
if (!egl_win)
return -1;
surf = eglCreateWindowSurface (dpy, cfg,
(EGLNativeWindowType) egl_win, NULL);
if (surf == EGL_NO_SURFACE) {
wl_egl_window_destroy (egl_win);
return -1;
}
if (!eglMakeCurrent (dpy, surf, surf, ctx)) {
eglDestroySurface (dpy, surf);
wl_egl_window_destroy (egl_win);
return -1;
}
load_egl_procs ();
if (!create_image || !destroy_image || !image_target_texture) {
fprintf (stderr, "EGL image extensions not available\n");
return -1;
}
*out_dpy = dpy;
*out_ctx = ctx;
*out_surf = surf;
*out_egl_win = egl_win;
return 0;
}
static void
draw_overlay (cairo_surface_t *surface, int width, int height,
int frame, double t_img, double t_gbm, double fps)
{
cairo_t *cr = cairo_create (surface);
char buf[128];
cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
cairo_paint (cr);
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
/* Semi-transparent background strip */
cairo_set_source_rgba (cr, 0, 0, 0, 0.7);
cairo_rectangle (cr, 0, 0, width, 80);
cairo_fill (cr);
cairo_select_font_face (cr, "sans-serif",
CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size (cr, 18);
/* Title line */
cairo_set_source_rgb (cr, 1, 1, 1);
snprintf (buf, sizeof buf,
"Cairo GBM Backend Demo | %.0f FPS | frame %d",
fps, frame);
cairo_move_to (cr, 12, 28);
cairo_show_text (cr, buf);
/* Timing comparison */
cairo_set_font_size (cr, 15);
cairo_set_source_rgb (cr, 0.6, 0.8, 1.0);
snprintf (buf, sizeof buf,
"image upload: %.2f ms", t_img);
cairo_move_to (cr, 12, 52);
cairo_show_text (cr, buf);
cairo_set_source_rgb (cr, 0.6, 1.0, 0.6);
snprintf (buf, sizeof buf,
"GBM zero-copy: %.2f ms (%.0fx faster)",
t_gbm, t_gbm > 0 ? t_img / t_gbm : 0);
cairo_move_to (cr, 12, 72);
cairo_show_text (cr, buf);
cairo_destroy (cr);
}
static int
run_windowed (struct gbm_device *gbm_dev, int width, int height, int frames)
{
EGLDisplay egl_dpy;
EGLContext egl_ctx;
EGLSurface egl_surf;
struct wl_egl_window *egl_win;
struct wl_registry *reg;
cairo_surface_t *img_surface, *gbm_surface, *overlay_surface;
GLuint tex_gbm, tex_overlay, prog;
int frame = 0;
double fps = 0;
double last_fps_time;
int fps_count = 0;
double t_img = 0, t_gbm = 0;
wl_dpy = wl_display_connect (NULL);
if (!wl_dpy) {
fprintf (stderr, "Cannot connect to Wayland display.\n"
"Run without --window for headless benchmark.\n");
return 1;
}
reg = wl_display_get_registry (wl_dpy);
wl_registry_add_listener (reg, &registry_listener, NULL);
wl_display_roundtrip (wl_dpy);
if (!wl_comp || !xdg_base) {
fprintf (stderr, "Missing wl_compositor or xdg_wm_base\n");
return 1;
}
xdg_wm_base_add_listener (xdg_base, &xdg_base_listener, NULL);
wl_surf = wl_compositor_create_surface (wl_comp);
xdg_surf = xdg_wm_base_get_xdg_surface (xdg_base, wl_surf);
xdg_surface_add_listener (xdg_surf, &xdg_surf_listener, NULL);
xdg_top = xdg_surface_get_toplevel (xdg_surf);
xdg_toplevel_add_listener (xdg_top, &xdg_top_listener, NULL);
xdg_toplevel_set_title (xdg_top, "Cairo GBM Benchmark");
xdg_toplevel_set_app_id (xdg_top, "cairo-gbm-benchmark");
wl_surface_commit (wl_surf);
/* Wait for the first configure */
while (!configured)
wl_display_roundtrip (wl_dpy);
if (init_egl_wayland (gbm_dev, width, height,
&egl_dpy, &egl_ctx, &egl_surf, &egl_win) < 0) {
fprintf (stderr, "Failed to init Wayland EGL\n");
return 1;
}
printf ("EGL vendor: %s\n", eglQueryString (egl_dpy, EGL_VENDOR));
printf ("GL renderer: %s\n\n", glGetString (GL_RENDERER));
prog = build_program ();
/* Cairo surfaces: GBM for display, image for timing comparison */
gbm_surface = cairo_gbm_surface_create (gbm_dev, CAIRO_FORMAT_ARGB32,
width, height);
img_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
width, height);
overlay_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
width, 80);
/* GL textures */
glGenTextures (1, &tex_gbm);
glBindTexture (GL_TEXTURE_2D, tex_gbm);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GLuint tex_img;
glGenTextures (1, &tex_img);
glBindTexture (GL_TEXTURE_2D, tex_img);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
GL_BGRA_EXT, GL_UNSIGNED_BYTE, NULL);
glGenTextures (1, &tex_overlay);
glBindTexture (GL_TEXTURE_2D, tex_overlay);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, width, 80, 0,
GL_BGRA_EXT, GL_UNSIGNED_BYTE, NULL);
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
last_fps_time = now_ms ();
while (running && (frames == 0 || frame < frames)) {
wl_display_dispatch_pending (wl_dpy);
/* Render the scene to both surfaces and time the uploads */
draw_frame (gbm_surface, width, height, frame);
t_gbm = import_gbm_surface (egl_dpy, gbm_surface, tex_gbm,
width, height);
draw_frame (img_surface, width, height, frame);
t_img = upload_image_surface (img_surface, tex_img, width, height);
/* FPS counter */
fps_count++;
double now = now_ms ();
if (now - last_fps_time >= 1000.0) {
fps = fps_count * 1000.0 / (now - last_fps_time);
fps_count = 0;
last_fps_time = now;
}
/* Update overlay */
draw_overlay (overlay_surface, width, 80,
frame, t_img, t_gbm, fps);
cairo_surface_flush (overlay_surface);
glBindTexture (GL_TEXTURE_2D, tex_overlay);
glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, width, 80,
GL_BGRA_EXT, GL_UNSIGNED_BYTE,
cairo_image_surface_get_data (overlay_surface));
/* Draw scene (GBM zero-copy texture) and overlay */
glViewport (0, 0, width, height);
glClearColor (0, 0, 0, 1);
glClear (GL_COLOR_BUFFER_BIT);
glDisable (GL_BLEND);
draw_quad (prog, tex_gbm, -1, -1, 1, 1);
glEnable (GL_BLEND);
float overlay_top = 1.0;
float overlay_bot = 1.0 - (80.0 / height) * 2.0;
draw_quad (prog, tex_overlay, -1, overlay_bot, 1, overlay_top);
eglSwapBuffers (egl_dpy, egl_surf);
frame++;
}
printf ("\nFinal: %d frames, image upload avg %.2f ms, "
"GBM import avg %.2f ms\n",
frame, t_img, t_gbm);
cairo_surface_destroy (gbm_surface);
cairo_surface_destroy (img_surface);
cairo_surface_destroy (overlay_surface);
glDeleteTextures (1, &tex_gbm);
glDeleteTextures (1, &tex_img);
glDeleteTextures (1, &tex_overlay);
glDeleteProgram (prog);
eglDestroySurface (egl_dpy, egl_surf);
wl_egl_window_destroy (egl_win);
eglDestroyContext (egl_dpy, egl_ctx);
eglTerminate (egl_dpy);
xdg_toplevel_destroy (xdg_top);
xdg_surface_destroy (xdg_surf);
wl_surface_destroy (wl_surf);
xdg_wm_base_destroy (xdg_base);
wl_compositor_destroy (wl_comp);
wl_registry_destroy (reg);
wl_display_disconnect (wl_dpy);
return 0;
}
#endif /* HAS_WAYLAND */
/* ------------------------------------------------------------------ */
/* Main */
/* ------------------------------------------------------------------ */
static void
usage (const char *prog)
{
fprintf (stderr,
"Usage: %s [--window] [--frames N] [--width W] [--height H]\n"
"\n"
" --window Open a Wayland window showing the animated scene\n"
" rendered via GBM zero-copy, with a timing overlay.\n"
" Without this flag, runs a headless benchmark.\n"
" --frames Number of frames (default 100, 0 = unlimited in window mode)\n"
" --width Surface width (default 1920)\n"
" --height Surface height (default 1080)\n",
prog);
}
int
main (int argc, char **argv)
{
int width = 1920;
int height = 1080;
int frames = -1; /* -1 means "not set by user" */
int windowed = 0;
int drm_fd;
struct gbm_device *gbm_dev;
int i, ret;
for (i = 1; i < argc; i++) {
if (!strcmp (argv[i], "--window"))
windowed = 1;
else if (!strcmp (argv[i], "--frames") && i + 1 < argc)
frames = atoi (argv[++i]);
else if (!strcmp (argv[i], "--width") && i + 1 < argc)
width = atoi (argv[++i]);
else if (!strcmp (argv[i], "--height") && i + 1 < argc)
height = atoi (argv[++i]);
else {
usage (argv[0]);
return 1;
}
}
if (frames < 0)
frames = windowed ? 0 : 100;
printf ("GBM/EGL benchmark: %dx%d, %s, %d frames%s\n",
width, height,
windowed ? "windowed" : "headless",
frames,
frames == 0 ? " (unlimited)" : "");
printf ("Buffer size: %.1f MB per frame (ARGB32)\n\n",
width * height * 4.0 / (1024 * 1024));
{
drmDevice *devices[16];
int n = drmGetDevices2 (0, devices, 16);
for (i = 0; i < n; i++) {
if (!(devices[i]->available_nodes & (1 << DRM_NODE_RENDER)))
continue;
drm_fd = open (devices[i]->nodes[DRM_NODE_RENDER], O_RDWR);
if (drm_fd < 0)
continue;
gbm_dev = gbm_create_device (drm_fd);
if (gbm_dev)
break;
close (drm_fd);
drm_fd = -1;
}
if (n > 0)
drmFreeDevices (devices, n);
}
if (!gbm_dev) {
fprintf (stderr, "No usable DRM render node found\n");
return 1;
}
if (windowed) {
#ifdef HAS_WAYLAND
ret = run_windowed (gbm_dev, width, height, frames);
#else
fprintf (stderr, "--window requires Wayland support "
"(not available in this build)\n");
ret = 1;
#endif
} else {
ret = run_headless (gbm_dev, width, height, frames);
}
gbm_device_destroy (gbm_dev);
close (drm_fd);
return ret;
}

46
examples/meson.build Normal file
View file

@ -0,0 +1,46 @@
egl_dep = dependency('egl', required: false)
glesv2_dep = dependency('glesv2', required: false)
if not (feature_conf.get('CAIRO_HAS_GBM_SURFACE', 0) == 1 and egl_dep.found() and glesv2_dep.found())
subdir_done()
endif
benchmark_deps = [libcairo_dep, gbm_dep, libdrm_dep, egl_dep, glesv2_dep]
benchmark_sources = ['gbm-egl-benchmark.c']
benchmark_c_args = []
# Wayland support for --window mode
wl_client_dep = dependency('wayland-client', required: false)
wl_egl_dep = dependency('wayland-egl', required: false)
wl_protocols_dep = dependency('wayland-protocols', required: false)
wl_scanner = find_program('wayland-scanner', required: false)
if wl_client_dep.found() and wl_egl_dep.found() and wl_protocols_dep.found() and wl_scanner.found()
wl_protocols_dir = wl_protocols_dep.get_variable('pkgdatadir')
xdg_shell_xml = wl_protocols_dir / 'stable' / 'xdg-shell' / 'xdg-shell.xml'
xdg_shell_client_header = custom_target('xdg-shell-client-header',
input: xdg_shell_xml,
output: 'xdg-shell-client-protocol.h',
command: [wl_scanner, 'client-header', '@INPUT@', '@OUTPUT@'],
)
xdg_shell_code = custom_target('xdg-shell-code',
input: xdg_shell_xml,
output: 'xdg-shell-protocol.c',
command: [wl_scanner, 'private-code', '@INPUT@', '@OUTPUT@'],
)
benchmark_sources += [xdg_shell_client_header, xdg_shell_code]
benchmark_deps += [wl_client_dep, wl_egl_dep]
benchmark_c_args += ['-DHAS_WAYLAND']
endif
executable('gbm-egl-benchmark',
benchmark_sources,
dependencies: benchmark_deps,
c_args: benchmark_c_args,
link_args: ['-lm'],
install: false,
)

View file

@ -696,6 +696,18 @@ if not get_option('tee').disabled()
}]
endif
gbm_dep = dependency('gbm', required: get_option('gbm'))
libdrm_dep = dependency('libdrm', required: get_option('gbm'))
if gbm_dep.found() and libdrm_dep.found()
feature_conf.set('CAIRO_HAS_GBM_SURFACE', 1)
built_features += [{
'name': 'cairo-gbm',
'description': 'GBM surface backend',
'deps': [gbm_dep, libdrm_dep],
}]
deps += [gbm_dep, libdrm_dep]
endif
incbase = include_directories('.')
foreach check : check_sizeofs
@ -835,6 +847,8 @@ if not get_option('tests').disabled() and feature_conf.get('CAIRO_HAS_PNG_FUNCTI
subdir('perf')
endif
subdir('examples')
if get_option('gtk_doc')
doc_srcdir = include_directories('src')
subdir('doc/public')
@ -863,6 +877,7 @@ summary({
'Recording': true,
'Observer': true,
'Mime': true,
'GBM': feature_conf.get('CAIRO_HAS_GBM_SURFACE', 0) == 1,
'Tee': feature_conf.get('CAIRO_HAS_TEE_SURFACE', 0) == 1,
'Xlib': feature_conf.get('CAIRO_HAS_XLIB_SURFACE', 0) == 1,
'Xlib Xrender': feature_conf.get('CAIRO_HAS_XLIB_XRENDER_SURFACE', 0) == 1,

View file

@ -4,6 +4,7 @@ option('fontconfig', type : 'feature', value : 'auto')
option('freetype', type : 'feature', value : 'auto')
# Cairo surface backends
option('gbm', type : 'feature', value : 'auto')
option('png', type : 'feature', value : 'auto') # png and svg surfaces
option('quartz', type : 'feature', value : 'auto')
option('tee', type : 'feature', value : 'auto')

717
src/cairo-gbm-surface.c Normal file
View file

@ -0,0 +1,717 @@
/* cairo - a vector graphics library with display and print output
*
* Copyright © 2026 Red Hat, Inc.
*
* Author: Alberto Ruiz <aruiz@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it either under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation
* (the "LGPL") or, at your option, under the terms of the Mozilla
* Public License Version 1.1 (the "MPL"). If you do not alter this
* notice, a recipient may use your version of this file under either
* the MPL or the LGPL.
*
* You should have received a copy of the LGPL along with this library
* in the file COPYING-LGPL-2.1; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
* You should have received a copy of the MPL along with this library
* in the file COPYING-MPL-1.1
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
* OF ANY KIND, either express or implied. See the LGPL or the MPL for
* the specific language governing rights and limitations.
*
* The Original Code is the cairo graphics library.
*/
/**
* SECTION:cairo-gbm
* @Title: GBM Surfaces
* @Short_Description: Rendering to GBM buffers for zero-copy GPU handoff
* @See_Also: #cairo_surface_t
*
* A GBM surface allocates its pixel buffer through GBM (Generic Buffer
* Manager), producing memory that is directly importable by a GPU
* compositor via DMA-BUF. Rendering is still done on the CPU via
* pixman, but the result can be handed to a GPU-based compositor
* (e.g. GSK in GTK4, or Mutter) without an intermediate copy.
**/
#include "cairoint.h"
#include "cairo-default-context-private.h"
#include "cairo-error-private.h"
#include "cairo-image-surface-private.h"
#include "cairo-surface-private.h"
#include "cairo-gbm.h"
#include <drm_fourcc.h>
#include <unistd.h>
typedef struct _cairo_gbm_surface {
cairo_surface_t base;
struct gbm_device *gbm_device;
struct gbm_bo *bo;
cairo_bool_t owns_bo;
uint32_t drm_format;
int width;
int height;
int stride;
void *map_data;
unsigned char *mapped_data;
cairo_image_surface_t *image;
} cairo_gbm_surface_t;
static uint32_t
_cairo_format_to_drm_format (cairo_format_t format)
{
switch (format) {
case CAIRO_FORMAT_ARGB32:
return DRM_FORMAT_ARGB8888;
case CAIRO_FORMAT_RGB24:
return DRM_FORMAT_XRGB8888;
case CAIRO_FORMAT_RGB30:
return DRM_FORMAT_XRGB2101010;
case CAIRO_FORMAT_RGB16_565:
return DRM_FORMAT_RGB565;
case CAIRO_FORMAT_A8:
return DRM_FORMAT_R8;
case CAIRO_FORMAT_INVALID:
case CAIRO_FORMAT_A1:
case CAIRO_FORMAT_RGB96F:
case CAIRO_FORMAT_RGBA128F:
return 0;
}
return 0;
}
static cairo_format_t
_drm_format_to_cairo_format (uint32_t drm_format)
{
switch (drm_format) {
case DRM_FORMAT_ARGB8888:
return CAIRO_FORMAT_ARGB32;
case DRM_FORMAT_XRGB8888:
return CAIRO_FORMAT_RGB24;
case DRM_FORMAT_XRGB2101010:
return CAIRO_FORMAT_RGB30;
case DRM_FORMAT_RGB565:
return CAIRO_FORMAT_RGB16_565;
case DRM_FORMAT_R8:
return CAIRO_FORMAT_A8;
default:
return CAIRO_FORMAT_INVALID;
}
}
static cairo_status_t
_cairo_gbm_surface_map (cairo_gbm_surface_t *surface)
{
uint32_t map_stride;
if (surface->mapped_data)
return CAIRO_STATUS_SUCCESS;
surface->mapped_data = gbm_bo_map (surface->bo,
0, 0,
surface->width,
surface->height,
GBM_BO_TRANSFER_READ_WRITE,
&map_stride,
&surface->map_data);
if (surface->mapped_data == NULL)
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
surface->stride = (int) map_stride;
return CAIRO_STATUS_SUCCESS;
}
static void
_cairo_gbm_surface_unmap (cairo_gbm_surface_t *surface)
{
if (surface->mapped_data == NULL)
return;
gbm_bo_unmap (surface->bo, surface->map_data);
surface->map_data = NULL;
surface->mapped_data = NULL;
}
static cairo_image_surface_t *
_cairo_gbm_surface_ensure_image (cairo_gbm_surface_t *surface)
{
cairo_status_t status;
cairo_format_t format;
pixman_format_code_t pixman_format;
if (surface->image)
return surface->image;
status = _cairo_gbm_surface_map (surface);
if (unlikely (status))
return (cairo_image_surface_t *)
_cairo_surface_create_in_error (status);
format = _drm_format_to_cairo_format (surface->drm_format);
pixman_format = _cairo_format_to_pixman_format_code (format);
surface->image = (cairo_image_surface_t *)
_cairo_image_surface_create_with_pixman_format (surface->mapped_data,
pixman_format,
surface->width,
surface->height,
surface->stride);
if (unlikely (surface->image->base.status))
return surface->image;
surface->image->base.is_clear = surface->base.is_clear;
return surface->image;
}
static void
_cairo_gbm_surface_discard_image (cairo_gbm_surface_t *surface)
{
if (surface->image == NULL)
return;
cairo_surface_finish (&surface->image->base);
cairo_surface_destroy (&surface->image->base);
surface->image = NULL;
}
static cairo_status_t
_cairo_gbm_surface_finish (void *abstract_surface)
{
cairo_gbm_surface_t *surface = abstract_surface;
_cairo_gbm_surface_discard_image (surface);
_cairo_gbm_surface_unmap (surface);
if (surface->bo && surface->owns_bo) {
gbm_bo_destroy (surface->bo);
surface->bo = NULL;
}
return CAIRO_STATUS_SUCCESS;
}
static cairo_surface_t *
_cairo_gbm_surface_create_similar (void *abstract_surface,
cairo_content_t content,
int width,
int height)
{
cairo_gbm_surface_t *other = abstract_surface;
cairo_format_t format;
format = _cairo_format_from_content (content);
return cairo_gbm_surface_create (other->gbm_device, format, width, height);
}
static cairo_image_surface_t *
_cairo_gbm_surface_map_to_image (void *abstract_surface,
const cairo_rectangle_int_t *extents)
{
cairo_gbm_surface_t *surface = abstract_surface;
cairo_image_surface_t *image;
cairo_surface_t *sub;
uint8_t *data;
pixman_format_code_t pixman_format;
cairo_format_t format;
image = _cairo_gbm_surface_ensure_image (surface);
if (unlikely (image->base.status))
return image;
if (extents->x == 0 &&
extents->y == 0 &&
extents->width == surface->width &&
extents->height == surface->height)
{
return (cairo_image_surface_t *)
cairo_surface_reference (&image->base);
}
format = _drm_format_to_cairo_format (surface->drm_format);
pixman_format = _cairo_format_to_pixman_format_code (format);
data = surface->mapped_data;
data += extents->y * surface->stride;
data += extents->x * (PIXMAN_FORMAT_BPP (pixman_format) / 8);
sub = _cairo_image_surface_create_with_pixman_format (data,
pixman_format,
extents->width,
extents->height,
surface->stride);
cairo_surface_set_device_offset (sub, -extents->x, -extents->y);
return (cairo_image_surface_t *) sub;
}
static cairo_int_status_t
_cairo_gbm_surface_unmap_image (void *abstract_surface,
cairo_image_surface_t *image)
{
cairo_gbm_surface_t *surface = abstract_surface;
cairo_image_surface_t *own_image;
own_image = _cairo_gbm_surface_ensure_image (surface);
if (&image->base != &own_image->base) {
cairo_surface_finish (&image->base);
cairo_surface_destroy (&image->base);
}
return CAIRO_INT_STATUS_SUCCESS;
}
static cairo_surface_t *
_cairo_gbm_surface_source (void *abstract_surface,
cairo_rectangle_int_t *extents)
{
cairo_gbm_surface_t *surface = abstract_surface;
cairo_image_surface_t *image;
image = _cairo_gbm_surface_ensure_image (surface);
if (unlikely (image->base.status))
return &image->base;
if (extents) {
extents->x = extents->y = 0;
extents->width = surface->width;
extents->height = surface->height;
}
return &image->base;
}
static cairo_status_t
_cairo_gbm_surface_acquire_source_image (void *abstract_surface,
cairo_image_surface_t **image_out,
void **image_extra)
{
cairo_gbm_surface_t *surface = abstract_surface;
cairo_image_surface_t *image;
image = _cairo_gbm_surface_ensure_image (surface);
if (unlikely (image->base.status))
return image->base.status;
*image_out = image;
*image_extra = NULL;
return CAIRO_STATUS_SUCCESS;
}
static void
_cairo_gbm_surface_release_source_image (void *abstract_surface,
cairo_image_surface_t *image,
void *image_extra)
{
}
static cairo_bool_t
_cairo_gbm_surface_get_extents (void *abstract_surface,
cairo_rectangle_int_t *rectangle)
{
cairo_gbm_surface_t *surface = abstract_surface;
rectangle->x = 0;
rectangle->y = 0;
rectangle->width = surface->width;
rectangle->height = surface->height;
return TRUE;
}
static void
_cairo_gbm_surface_get_font_options (void *abstract_surface,
cairo_font_options_t *options)
{
_cairo_font_options_init_default (options);
cairo_font_options_set_hint_metrics (options, CAIRO_HINT_METRICS_ON);
_cairo_font_options_set_round_glyph_positions (options, CAIRO_ROUND_GLYPH_POS_ON);
}
static cairo_status_t
_cairo_gbm_surface_flush (void *abstract_surface, unsigned flags)
{
cairo_gbm_surface_t *surface = abstract_surface;
if (flags)
return CAIRO_STATUS_SUCCESS;
_cairo_gbm_surface_discard_image (surface);
_cairo_gbm_surface_unmap (surface);
return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
_cairo_gbm_surface_mark_dirty (void *abstract_surface,
int x, int y,
int width, int height)
{
return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
_cairo_gbm_surface_paint (void *abstract_surface,
cairo_operator_t op,
const cairo_pattern_t *source,
const cairo_clip_t *clip)
{
cairo_gbm_surface_t *surface = abstract_surface;
cairo_image_surface_t *image;
image = _cairo_gbm_surface_ensure_image (surface);
if (unlikely (image->base.status))
return image->base.status;
return _cairo_image_surface_paint (&image->base, op, source, clip);
}
static cairo_int_status_t
_cairo_gbm_surface_mask (void *abstract_surface,
cairo_operator_t op,
const cairo_pattern_t *source,
const cairo_pattern_t *mask,
const cairo_clip_t *clip)
{
cairo_gbm_surface_t *surface = abstract_surface;
cairo_image_surface_t *image;
image = _cairo_gbm_surface_ensure_image (surface);
if (unlikely (image->base.status))
return image->base.status;
return _cairo_image_surface_mask (&image->base, op, source, mask, clip);
}
static cairo_int_status_t
_cairo_gbm_surface_stroke (void *abstract_surface,
cairo_operator_t op,
const cairo_pattern_t *source,
const cairo_path_fixed_t *path,
const cairo_stroke_style_t *style,
const cairo_matrix_t *ctm,
const cairo_matrix_t *ctm_inverse,
double tolerance,
cairo_antialias_t antialias,
const cairo_clip_t *clip)
{
cairo_gbm_surface_t *surface = abstract_surface;
cairo_image_surface_t *image;
image = _cairo_gbm_surface_ensure_image (surface);
if (unlikely (image->base.status))
return image->base.status;
return _cairo_image_surface_stroke (&image->base, op, source,
path, style,
ctm, ctm_inverse,
tolerance, antialias, clip);
}
static cairo_int_status_t
_cairo_gbm_surface_fill (void *abstract_surface,
cairo_operator_t op,
const cairo_pattern_t *source,
const cairo_path_fixed_t *path,
cairo_fill_rule_t fill_rule,
double tolerance,
cairo_antialias_t antialias,
const cairo_clip_t *clip)
{
cairo_gbm_surface_t *surface = abstract_surface;
cairo_image_surface_t *image;
image = _cairo_gbm_surface_ensure_image (surface);
if (unlikely (image->base.status))
return image->base.status;
return _cairo_image_surface_fill (&image->base, op, source,
path, fill_rule,
tolerance, antialias, clip);
}
static cairo_int_status_t
_cairo_gbm_surface_glyphs (void *abstract_surface,
cairo_operator_t op,
const cairo_pattern_t *source,
cairo_glyph_t *glyphs,
int num_glyphs,
cairo_scaled_font_t *scaled_font,
const cairo_clip_t *clip)
{
cairo_gbm_surface_t *surface = abstract_surface;
cairo_image_surface_t *image;
image = _cairo_gbm_surface_ensure_image (surface);
if (unlikely (image->base.status))
return image->base.status;
return _cairo_image_surface_glyphs (&image->base, op, source,
glyphs, num_glyphs,
scaled_font, clip);
}
static const cairo_surface_backend_t cairo_gbm_surface_backend = {
CAIRO_SURFACE_TYPE_GBM,
_cairo_gbm_surface_finish,
_cairo_default_context_create,
_cairo_gbm_surface_create_similar,
NULL, /* create_similar_image */
_cairo_gbm_surface_map_to_image,
_cairo_gbm_surface_unmap_image,
_cairo_gbm_surface_source,
_cairo_gbm_surface_acquire_source_image,
_cairo_gbm_surface_release_source_image,
NULL, /* snapshot */
NULL, /* copy_page */
NULL, /* show_page */
_cairo_gbm_surface_get_extents,
_cairo_gbm_surface_get_font_options,
_cairo_gbm_surface_flush,
_cairo_gbm_surface_mark_dirty,
_cairo_gbm_surface_paint,
_cairo_gbm_surface_mask,
_cairo_gbm_surface_stroke,
_cairo_gbm_surface_fill,
NULL, /* fill_stroke */
_cairo_gbm_surface_glyphs,
};
/**
* cairo_gbm_surface_create:
* @device: a GBM device obtained from gbm_create_device()
* @format: the format of pixels in the surface
* @width: width of the surface, in pixels
* @height: height of the surface, in pixels
*
* Creates a GBM surface of the specified format and dimensions. The
* buffer is allocated via GBM with a linear modifier, making it
* suitable for CPU rendering while remaining directly importable by a
* GPU compositor through its DMA-BUF file descriptor.
*
* Not all #cairo_format_t values have a DRM equivalent. The supported
* formats are: %CAIRO_FORMAT_ARGB32, %CAIRO_FORMAT_RGB24,
* %CAIRO_FORMAT_RGB30, %CAIRO_FORMAT_RGB16_565, and %CAIRO_FORMAT_A8.
*
* Return value: a pointer to the newly created surface. The caller
* owns the surface and should call cairo_surface_destroy() when done
* with it.
*
* This function always returns a valid pointer, but it will return a
* pointer to a "nil" surface if an error occurs. You can use
* cairo_surface_status() to check for this.
*
* Since: 1.18
**/
cairo_surface_t *
cairo_gbm_surface_create (struct gbm_device *device,
cairo_format_t format,
int width,
int height)
{
cairo_gbm_surface_t *surface;
uint32_t drm_format;
struct gbm_bo *bo;
cairo_content_t content;
if (device == NULL)
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NULL_POINTER));
if (width <= 0 || height <= 0)
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_SIZE));
if (! CAIRO_FORMAT_VALID (format))
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT));
drm_format = _cairo_format_to_drm_format (format);
if (drm_format == 0)
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT));
bo = gbm_bo_create_with_modifiers2 (device,
width, height,
drm_format,
(const uint64_t[]){ DRM_FORMAT_MOD_LINEAR },
1,
GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING);
if (bo == NULL) {
bo = gbm_bo_create (device,
width, height,
drm_format,
GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING);
}
if (bo == NULL)
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
surface = _cairo_calloc (sizeof (cairo_gbm_surface_t));
if (unlikely (surface == NULL)) {
gbm_bo_destroy (bo);
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
}
content = _cairo_content_from_format (format);
_cairo_surface_init (&surface->base,
&cairo_gbm_surface_backend,
NULL, /* device */
content,
FALSE); /* is_vector */
surface->gbm_device = device;
surface->bo = bo;
surface->owns_bo = TRUE;
surface->drm_format = drm_format;
surface->width = width;
surface->height = height;
surface->stride = 0;
surface->map_data = NULL;
surface->mapped_data = NULL;
surface->image = NULL;
return &surface->base;
}
/**
* cairo_gbm_surface_create_for_bo:
* @bo: an existing GBM buffer object
* @format: the #cairo_format_t matching the bo's pixel layout
*
* Wraps an existing GBM buffer object as a cairo surface. The caller
* retains ownership of @bo and must ensure it outlives the returned
* surface. The surface will not destroy the buffer object when finished.
*
* Return value: a pointer to the newly created surface. The caller
* owns the surface and should call cairo_surface_destroy() when done
* with it.
*
* Since: 1.18
**/
cairo_surface_t *
cairo_gbm_surface_create_for_bo (struct gbm_bo *bo,
cairo_format_t format)
{
cairo_gbm_surface_t *surface;
uint32_t drm_format;
cairo_content_t content;
if (bo == NULL)
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NULL_POINTER));
if (! CAIRO_FORMAT_VALID (format))
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT));
drm_format = _cairo_format_to_drm_format (format);
if (drm_format == 0)
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT));
surface = _cairo_calloc (sizeof (cairo_gbm_surface_t));
if (unlikely (surface == NULL))
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
content = _cairo_content_from_format (format);
_cairo_surface_init (&surface->base,
&cairo_gbm_surface_backend,
NULL, /* device */
content,
FALSE); /* is_vector */
surface->gbm_device = gbm_bo_get_device (bo);
surface->bo = bo;
surface->owns_bo = FALSE;
surface->drm_format = drm_format;
surface->width = gbm_bo_get_width (bo);
surface->height = gbm_bo_get_height (bo);
surface->stride = 0;
surface->map_data = NULL;
surface->mapped_data = NULL;
surface->image = NULL;
return &surface->base;
}
/**
* cairo_gbm_surface_get_bo:
* @surface: a GBM surface
*
* Returns the underlying #gbm_bo associated with this surface.
*
* Return value: the GBM buffer object, or %NULL if the surface is not
* a GBM surface or is in an error state.
*
* Since: 1.18
**/
struct gbm_bo *
cairo_gbm_surface_get_bo (cairo_surface_t *surface)
{
cairo_gbm_surface_t *gbm_surface = (cairo_gbm_surface_t *) surface;
if (surface->backend != &cairo_gbm_surface_backend)
return NULL;
if (unlikely (surface->status))
return NULL;
if (unlikely (surface->finished))
return NULL;
return gbm_surface->bo;
}
/**
* cairo_gbm_surface_get_dma_buf_fd:
* @surface: a GBM surface
*
* Obtains a DMA-BUF file descriptor for the surface's backing buffer.
* This fd can be imported by an EGL/Vulkan compositor for zero-copy
* texture access.
*
* The caller takes ownership of the returned fd and must close() it
* when no longer needed.
*
* Return value: a DMA-BUF file descriptor, or -1 on error. The caller
* must close() the fd when done.
*
* Since: 1.18
**/
int
cairo_gbm_surface_get_dma_buf_fd (cairo_surface_t *surface)
{
cairo_gbm_surface_t *gbm_surface = (cairo_gbm_surface_t *) surface;
if (surface->backend != &cairo_gbm_surface_backend)
return -1;
if (unlikely (surface->status))
return -1;
if (unlikely (surface->finished))
return -1;
if (unlikely (gbm_surface->bo == NULL))
return -1;
return gbm_bo_get_fd (gbm_surface->bo);
}

93
src/cairo-gbm.h Normal file
View file

@ -0,0 +1,93 @@
/* cairo - a vector graphics library with display and print output
*
* Copyright © 2026 Red Hat, Inc.
*
* Author: Alberto Ruiz <aruiz@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it either under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation
* (the "LGPL") or, at your option, under the terms of the Mozilla
* Public License Version 1.1 (the "MPL"). If you do not alter this
* notice, a recipient may use your version of this file under either
* the MPL or the LGPL.
*
* You should have received a copy of the LGPL along with this library
* in the file COPYING-LGPL-2.1; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
* You should have received a copy of the MPL along with this library
* in the file COPYING-MPL-1.1
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
* OF ANY KIND, either express or implied. See the LGPL or the MPL for
* the specific language governing rights and limitations.
*
* The Original Code is the cairo graphics library.
*/
#ifndef CAIRO_GBM_H
#define CAIRO_GBM_H
#include "cairo.h"
#if CAIRO_HAS_GBM_SURFACE
#include <gbm.h>
CAIRO_BEGIN_DECLS
/**
* SECTION:cairo-gbm
* @Title: GBM Surfaces
* @Short_Description: Rendering to GBM (Generic Buffer Manager) buffers
* @See_Also: #cairo_surface_t
*
* The GBM surface provides the ability to render to memory buffers
* allocated via GBM (Generic Buffer Manager). These buffers can be
* shared with the GPU via DMA-BUF file descriptors, enabling zero-copy
* handoff to GPU-based compositors such as those used by GTK4 and
* Mutter.
*
* Rendering is performed by pixman on the CPU side, but the backing
* memory is GPU-importable, avoiding the texture upload that would
* otherwise be required when the compositor needs to display the
* content.
*/
/**
* CAIRO_HAS_GBM_SURFACE:
*
* Defined if the GBM surface backend is available.
* This macro can be used to conditionally compile backend-specific code.
*
* Since: 1.18
*/
cairo_public cairo_surface_t *
cairo_gbm_surface_create (struct gbm_device *device,
cairo_format_t format,
int width,
int height);
cairo_public cairo_surface_t *
cairo_gbm_surface_create_for_bo (struct gbm_bo *bo,
cairo_format_t format);
cairo_public struct gbm_bo *
cairo_gbm_surface_get_bo (cairo_surface_t *surface);
cairo_public int
cairo_gbm_surface_get_dma_buf_fd (cairo_surface_t *surface);
CAIRO_END_DECLS
#else /* CAIRO_HAS_GBM_SURFACE */
# error Cairo was not compiled with support for the GBM backend
#endif /* CAIRO_HAS_GBM_SURFACE */
#endif /* CAIRO_GBM_H */

View file

@ -4763,6 +4763,7 @@ _cairo_debug_print_surface_pattern (FILE *file,
case CAIRO_SURFACE_TYPE_SKIA: s = "skia"; break; /* Deprecated */
case CAIRO_SURFACE_TYPE_SUBSURFACE: s = "subsurface"; break;
case CAIRO_SURFACE_TYPE_COGL: s = "cogl"; break;
case CAIRO_SURFACE_TYPE_GBM: s = "gbm"; break;
default: s = "invalid"; ASSERT_NOT_REACHED; break;
}
fprintf (file, " surface type: %s\n", s);

View file

@ -2535,6 +2535,8 @@ cairo_surface_status (cairo_surface_t *surface);
* cairo_surface_create_for_rectangle(), since 1.10
* @CAIRO_SURFACE_TYPE_COGL: This surface is of type Cogl, since 1.12, deprecated 1.18
* (Cogl support have been removed, this surface type will never be set by cairo)
* @CAIRO_SURFACE_TYPE_GBM: The surface is of type GBM (Generic Buffer Manager),
* since 1.18
*
* #cairo_surface_type_t is used to describe the type of a given
* surface. The surface types are also known as "backends" or "surface
@ -2585,7 +2587,8 @@ typedef enum _cairo_surface_type {
CAIRO_SURFACE_TYPE_XML,
CAIRO_SURFACE_TYPE_SKIA,
CAIRO_SURFACE_TYPE_SUBSURFACE,
CAIRO_SURFACE_TYPE_COGL
CAIRO_SURFACE_TYPE_COGL,
CAIRO_SURFACE_TYPE_GBM
} cairo_surface_type_t;
cairo_public cairo_surface_type_t

View file

@ -198,6 +198,9 @@ cairo_feature_sources = {
'cairo-tee': [
'cairo-tee-surface.c',
],
'cairo-gbm': [
'cairo-gbm-surface.c',
],
}
cairo_feature_headers = {
@ -215,6 +218,7 @@ cairo_feature_headers = {
'cairo-gl': ['cairo-gl.h'],
'cairo-script': ['cairo-script.h'],
'cairo-tee': ['cairo-tee.h'],
'cairo-gbm': ['cairo-gbm.h'],
'cairo-vg': ['cairo-vg.h'],
}

View file

@ -80,6 +80,9 @@
#if CAIRO_HAS_TEE_SURFACE
#include <cairo-tee.h>
#endif
#if CAIRO_HAS_GBM_SURFACE
#include <cairo-gbm.h>
#endif
#if CAIRO_HAS_XCB_SURFACE
#include <cairo-xcb.h>
#endif
@ -1402,6 +1405,24 @@ test_cairo_svg_surface_restrict_to_version (cairo_surface_t *surface)
#endif /* CAIRO_HAS_SVG_SURFACE */
#if CAIRO_HAS_GBM_SURFACE
static cairo_test_status_t
test_cairo_gbm_surface_get_bo (cairo_surface_t *surface)
{
cairo_gbm_surface_get_bo (surface);
return CAIRO_TEST_SUCCESS;
}
static cairo_test_status_t
test_cairo_gbm_surface_get_dma_buf_fd (cairo_surface_t *surface)
{
cairo_gbm_surface_get_dma_buf_fd (surface);
return CAIRO_TEST_SUCCESS;
}
#endif /* CAIRO_HAS_GBM_SURFACE */
#if CAIRO_HAS_XCB_SURFACE
static cairo_test_status_t
@ -1659,6 +1680,10 @@ struct {
#if CAIRO_HAS_SVG_SURFACE
TEST (cairo_svg_surface_restrict_to_version, CAIRO_SURFACE_TYPE_SVG, TRUE),
#endif
#if CAIRO_HAS_GBM_SURFACE
TEST (cairo_gbm_surface_get_bo, CAIRO_SURFACE_TYPE_GBM, FALSE),
TEST (cairo_gbm_surface_get_dma_buf_fd, CAIRO_SURFACE_TYPE_GBM, FALSE),
#endif
#if CAIRO_HAS_XCB_SURFACE
TEST (cairo_xcb_surface_set_size, CAIRO_SURFACE_TYPE_XCB, TRUE),
TEST (cairo_xcb_surface_set_drawable, CAIRO_SURFACE_TYPE_XCB, TRUE),

View file

@ -35,6 +35,9 @@
#if CAIRO_HAS_PS_SURFACE
#include <cairo-ps.h>
#endif
#if CAIRO_HAS_GBM_SURFACE
#include <cairo-gbm.h>
#endif
#if CAIRO_HAS_XCB_SURFACE
#include <cairo-xcb.h>
#endif
@ -64,6 +67,11 @@ preamble (cairo_test_context_t *ctx)
cairo_ps_surface_dsc_begin_page_setup (surface);
#endif
#if CAIRO_HAS_GBM_SURFACE
cairo_gbm_surface_get_bo (surface);
cairo_gbm_surface_get_dma_buf_fd (surface);
#endif
#if CAIRO_HAS_XCB_SURFACE
cairo_xcb_surface_set_size (surface, 0, 0);
#endif

74
test/gbm-surface-source.c Normal file
View file

@ -0,0 +1,74 @@
/*
* Copyright © 2026 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software
* and its documentation for any purpose is hereby granted without
* fee, provided that the above copyright notice appear in all copies
* and that both that copyright notice and this permission notice
* appear in supporting documentation, and that the name of
* Red Hat, Inc. not be used in advertising or publicity pertaining to
* distribution of the software without specific, written prior
* permission. Red Hat, Inc. makes no representations about the
* suitability of this software for any purpose. It is provided "as
* is" without express or implied warranty.
*
* RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Author: Alberto Ruiz <aruiz@redhat.com>
*/
#include "cairo-test.h"
#include <cairo-gbm.h>
#include <gbm.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include "surface-source.c"
static cairo_surface_t *
create_source_surface (int size)
{
#if CAIRO_HAS_GBM_SURFACE
int fd;
struct gbm_device *dev;
cairo_surface_t *surface;
fd = open ("/dev/dri/renderD128", O_RDWR);
if (fd < 0)
return NULL;
dev = gbm_create_device (fd);
if (dev == NULL) {
close (fd);
return NULL;
}
surface = cairo_gbm_surface_create (dev, CAIRO_FORMAT_ARGB32, size, size);
if (cairo_surface_status (surface)) {
cairo_surface_destroy (surface);
gbm_device_destroy (dev);
close (fd);
return NULL;
}
return surface;
#else
return NULL;
#endif
}
CAIRO_TEST (gbm_surface_source,
"Test using a GBM surface as the source",
"source, gbm", /* keywords */
NULL, /* requirements */
SIZE, SIZE,
preamble, draw)

438
test/gbm-surface.c Normal file
View file

@ -0,0 +1,438 @@
/*
* Copyright © 2026 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software
* and its documentation for any purpose is hereby granted without
* fee, provided that the above copyright notice appear in all copies
* and that both that copyright notice and this permission notice
* appear in supporting documentation, and that the name of
* Red Hat, Inc. not be used in advertising or publicity pertaining to
* distribution of the software without specific, written prior
* permission. Red Hat, Inc. makes no representations about the
* suitability of this software for any purpose. It is provided "as
* is" without express or implied warranty.
*
* RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Author: Alberto Ruiz <aruiz@redhat.com>
*/
#include "cairo-test.h"
#include <cairo-gbm.h>
#include <gbm.h>
#include <fcntl.h>
#include <unistd.h>
static struct gbm_device *
open_gbm_device (int *fd_out)
{
int fd;
struct gbm_device *dev;
fd = open ("/dev/dri/renderD128", O_RDWR);
if (fd < 0)
return NULL;
dev = gbm_create_device (fd);
if (dev == NULL) {
close (fd);
return NULL;
}
*fd_out = fd;
return dev;
}
static cairo_test_status_t
test_create_valid (struct gbm_device *dev, cairo_test_context_t *ctx)
{
cairo_surface_t *surface;
surface = cairo_gbm_surface_create (dev, CAIRO_FORMAT_ARGB32, 100, 100);
if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) {
cairo_test_log (ctx, "Failed to create ARGB32 GBM surface\n");
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_GBM) {
cairo_test_log (ctx, "Surface type is not CAIRO_SURFACE_TYPE_GBM\n");
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
cairo_surface_destroy (surface);
surface = cairo_gbm_surface_create (dev, CAIRO_FORMAT_RGB24, 100, 100);
if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) {
cairo_test_log (ctx, "Failed to create RGB24 GBM surface\n");
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
cairo_surface_destroy (surface);
return CAIRO_TEST_SUCCESS;
}
static cairo_test_status_t
test_create_error_paths (cairo_test_context_t *ctx)
{
cairo_surface_t *surface;
/* _cairo_surface_create_in_error() has no dedicated nil surface for
* CAIRO_STATUS_NULL_POINTER, so it falls through to NO_MEMORY. */
surface = cairo_gbm_surface_create (NULL, CAIRO_FORMAT_ARGB32, 100, 100);
if (cairo_surface_status (surface) != CAIRO_STATUS_NO_MEMORY) {
cairo_test_log (ctx, "NULL device: expected NO_MEMORY, got %d\n",
cairo_surface_status (surface));
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
cairo_surface_destroy (surface);
surface = cairo_gbm_surface_create (NULL, CAIRO_FORMAT_INVALID, 100, 100);
if (cairo_surface_status (surface) != CAIRO_STATUS_NO_MEMORY) {
cairo_test_log (ctx, "NULL device + invalid format: expected NO_MEMORY, got %d\n",
cairo_surface_status (surface));
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
cairo_surface_destroy (surface);
return CAIRO_TEST_SUCCESS;
}
static cairo_test_status_t
test_create_invalid_format (struct gbm_device *dev, cairo_test_context_t *ctx)
{
cairo_surface_t *surface;
surface = cairo_gbm_surface_create (dev, CAIRO_FORMAT_INVALID, 100, 100);
if (cairo_surface_status (surface) != CAIRO_STATUS_INVALID_FORMAT) {
cairo_test_log (ctx, "INVALID format: expected INVALID_FORMAT, got %d\n",
cairo_surface_status (surface));
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
cairo_surface_destroy (surface);
surface = cairo_gbm_surface_create (dev, CAIRO_FORMAT_A1, 100, 100);
if (cairo_surface_status (surface) != CAIRO_STATUS_INVALID_FORMAT) {
cairo_test_log (ctx, "A1 format (no DRM equivalent): expected INVALID_FORMAT, got %d\n",
cairo_surface_status (surface));
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
cairo_surface_destroy (surface);
surface = cairo_gbm_surface_create (dev, CAIRO_FORMAT_RGB96F, 100, 100);
if (cairo_surface_status (surface) != CAIRO_STATUS_INVALID_FORMAT) {
cairo_test_log (ctx, "RGB96F format (no DRM equivalent): expected INVALID_FORMAT, got %d\n",
cairo_surface_status (surface));
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
cairo_surface_destroy (surface);
return CAIRO_TEST_SUCCESS;
}
static cairo_test_status_t
test_create_invalid_size (struct gbm_device *dev, cairo_test_context_t *ctx)
{
cairo_surface_t *surface;
surface = cairo_gbm_surface_create (dev, CAIRO_FORMAT_ARGB32, 0, 100);
if (cairo_surface_status (surface) != CAIRO_STATUS_INVALID_SIZE) {
cairo_test_log (ctx, "Zero width: expected INVALID_SIZE, got %d\n",
cairo_surface_status (surface));
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
cairo_surface_destroy (surface);
surface = cairo_gbm_surface_create (dev, CAIRO_FORMAT_ARGB32, 100, 0);
if (cairo_surface_status (surface) != CAIRO_STATUS_INVALID_SIZE) {
cairo_test_log (ctx, "Zero height: expected INVALID_SIZE, got %d\n",
cairo_surface_status (surface));
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
cairo_surface_destroy (surface);
surface = cairo_gbm_surface_create (dev, CAIRO_FORMAT_ARGB32, -1, 100);
if (cairo_surface_status (surface) != CAIRO_STATUS_INVALID_SIZE) {
cairo_test_log (ctx, "Negative width: expected INVALID_SIZE, got %d\n",
cairo_surface_status (surface));
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
cairo_surface_destroy (surface);
return CAIRO_TEST_SUCCESS;
}
static cairo_test_status_t
test_create_for_bo (struct gbm_device *dev, cairo_test_context_t *ctx)
{
cairo_surface_t *surface;
struct gbm_bo *bo;
bo = gbm_bo_create (dev, 64, 64, GBM_FORMAT_ARGB8888,
GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING);
if (bo == NULL) {
cairo_test_log (ctx, "Failed to create gbm_bo for test\n");
return CAIRO_TEST_FAILURE;
}
surface = cairo_gbm_surface_create_for_bo (bo, CAIRO_FORMAT_ARGB32);
if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) {
cairo_test_log (ctx, "Failed to create surface for bo\n");
cairo_surface_destroy (surface);
gbm_bo_destroy (bo);
return CAIRO_TEST_FAILURE;
}
if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_GBM) {
cairo_test_log (ctx, "create_for_bo: type is not GBM\n");
cairo_surface_destroy (surface);
gbm_bo_destroy (bo);
return CAIRO_TEST_FAILURE;
}
/* Verify get_bo returns the same bo */
if (cairo_gbm_surface_get_bo (surface) != bo) {
cairo_test_log (ctx, "create_for_bo: get_bo returned wrong pointer\n");
cairo_surface_destroy (surface);
gbm_bo_destroy (bo);
return CAIRO_TEST_FAILURE;
}
/* Destroy surface — bo should survive (caller ownership) */
cairo_surface_destroy (surface);
gbm_bo_destroy (bo);
return CAIRO_TEST_SUCCESS;
}
static cairo_test_status_t
test_create_for_bo_error_paths (struct gbm_device *dev, cairo_test_context_t *ctx)
{
cairo_surface_t *surface;
struct gbm_bo *bo;
surface = cairo_gbm_surface_create_for_bo (NULL, CAIRO_FORMAT_ARGB32);
if (cairo_surface_status (surface) != CAIRO_STATUS_NO_MEMORY) {
cairo_test_log (ctx, "NULL bo: expected NO_MEMORY, got %d\n",
cairo_surface_status (surface));
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
cairo_surface_destroy (surface);
bo = gbm_bo_create (dev, 64, 64, GBM_FORMAT_ARGB8888,
GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING);
if (bo == NULL) {
cairo_test_log (ctx, "Failed to create gbm_bo for error test\n");
return CAIRO_TEST_FAILURE;
}
surface = cairo_gbm_surface_create_for_bo (bo, CAIRO_FORMAT_INVALID);
if (cairo_surface_status (surface) != CAIRO_STATUS_INVALID_FORMAT) {
cairo_test_log (ctx, "Invalid format with valid bo: expected INVALID_FORMAT, got %d\n",
cairo_surface_status (surface));
cairo_surface_destroy (surface);
gbm_bo_destroy (bo);
return CAIRO_TEST_FAILURE;
}
cairo_surface_destroy (surface);
gbm_bo_destroy (bo);
return CAIRO_TEST_SUCCESS;
}
static cairo_test_status_t
test_getters_wrong_type (cairo_test_context_t *ctx)
{
cairo_surface_t *image;
image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 10, 10);
if (cairo_gbm_surface_get_bo (image) != NULL) {
cairo_test_log (ctx, "get_bo on image surface should return NULL\n");
cairo_surface_destroy (image);
return CAIRO_TEST_FAILURE;
}
if (cairo_gbm_surface_get_dma_buf_fd (image) != -1) {
cairo_test_log (ctx, "get_dma_buf_fd on image surface should return -1\n");
cairo_surface_destroy (image);
return CAIRO_TEST_FAILURE;
}
cairo_surface_destroy (image);
return CAIRO_TEST_SUCCESS;
}
static cairo_test_status_t
test_dma_buf_fd (struct gbm_device *dev, cairo_test_context_t *ctx)
{
cairo_surface_t *surface;
int fd;
surface = cairo_gbm_surface_create (dev, CAIRO_FORMAT_ARGB32, 64, 64);
if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) {
cairo_test_log (ctx, "Failed to create surface for fd test\n");
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
fd = cairo_gbm_surface_get_dma_buf_fd (surface);
if (fd < 0) {
cairo_test_log (ctx, "get_dma_buf_fd returned %d\n", fd);
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
close (fd);
cairo_surface_destroy (surface);
return CAIRO_TEST_SUCCESS;
}
static cairo_test_status_t
test_draw_and_flush (struct gbm_device *dev, cairo_test_context_t *ctx)
{
cairo_surface_t *surface;
cairo_t *cr;
int fd;
surface = cairo_gbm_surface_create (dev, CAIRO_FORMAT_ARGB32, 64, 64);
if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) {
cairo_test_log (ctx, "Failed to create surface for draw test\n");
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
cr = cairo_create (surface);
cairo_set_source_rgb (cr, 1, 0, 0);
cairo_rectangle (cr, 0, 0, 32, 32);
cairo_fill (cr);
if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) {
cairo_test_log (ctx, "Drawing failed: %d\n", cairo_status (cr));
cairo_destroy (cr);
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
cairo_destroy (cr);
cairo_surface_flush (surface);
fd = cairo_gbm_surface_get_dma_buf_fd (surface);
if (fd < 0) {
cairo_test_log (ctx, "get_dma_buf_fd after flush returned %d\n", fd);
cairo_surface_destroy (surface);
return CAIRO_TEST_FAILURE;
}
close (fd);
cairo_surface_destroy (surface);
return CAIRO_TEST_SUCCESS;
}
static cairo_test_status_t
test_all_supported_formats (struct gbm_device *dev, cairo_test_context_t *ctx)
{
static const cairo_format_t formats[] = {
CAIRO_FORMAT_ARGB32,
CAIRO_FORMAT_RGB24,
CAIRO_FORMAT_RGB30,
CAIRO_FORMAT_RGB16_565,
CAIRO_FORMAT_A8,
};
unsigned int i;
for (i = 0; i < sizeof (formats) / sizeof (formats[0]); i++) {
cairo_surface_t *surface;
surface = cairo_gbm_surface_create (dev, formats[i], 64, 64);
if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) {
cairo_test_log (ctx,
"Format %d: create failed with status %d "
"(GPU may not support this DRM format, skipping)\n",
formats[i], cairo_surface_status (surface));
cairo_surface_destroy (surface);
continue;
}
cairo_surface_destroy (surface);
}
return CAIRO_TEST_SUCCESS;
}
static cairo_test_status_t
preamble (cairo_test_context_t *ctx)
{
struct gbm_device *dev;
int drm_fd;
cairo_test_status_t status;
status = test_create_error_paths (ctx);
if (status != CAIRO_TEST_SUCCESS)
return status;
status = test_getters_wrong_type (ctx);
if (status != CAIRO_TEST_SUCCESS)
return status;
dev = open_gbm_device (&drm_fd);
if (dev == NULL) {
cairo_test_log (ctx, "No DRM render node available, skipping\n");
return CAIRO_TEST_UNTESTED;
}
status = test_create_valid (dev, ctx);
if (status != CAIRO_TEST_SUCCESS)
goto CLEANUP;
status = test_create_invalid_format (dev, ctx);
if (status != CAIRO_TEST_SUCCESS)
goto CLEANUP;
status = test_create_invalid_size (dev, ctx);
if (status != CAIRO_TEST_SUCCESS)
goto CLEANUP;
status = test_create_for_bo (dev, ctx);
if (status != CAIRO_TEST_SUCCESS)
goto CLEANUP;
status = test_create_for_bo_error_paths (dev, ctx);
if (status != CAIRO_TEST_SUCCESS)
goto CLEANUP;
status = test_dma_buf_fd (dev, ctx);
if (status != CAIRO_TEST_SUCCESS)
goto CLEANUP;
status = test_draw_and_flush (dev, ctx);
if (status != CAIRO_TEST_SUCCESS)
goto CLEANUP;
status = test_all_supported_formats (dev, ctx);
CLEANUP:
gbm_device_destroy (dev);
close (drm_fd);
return status;
}
CAIRO_TEST (gbm_surface,
"Test GBM surface API: creation, error handling, getters, draw, flush, DMA-BUF fd",
"gbm, api", /* keywords */
NULL, /* requirements */
0, 0,
preamble, NULL)

View file

@ -487,6 +487,11 @@ test_xlib_xrender_sources = [
'get-xrender-format.c',
]
test_gbm_sources = [
'gbm-surface.c',
'gbm-surface-source.c',
]
test_multi_page_sources = [
'multi-page.c',
'mime-unique-id.c',
@ -586,6 +591,10 @@ if feature_conf.get('CAIRO_HAS_SVG_SURFACE', 0) == 1
add_fallback_resolution = true
endif
if feature_conf.get('CAIRO_HAS_GBM_SURFACE', 0) == 1
test_sources += test_gbm_sources
endif
if feature_conf.get('CAIRO_HAS_XCB_SURFACE', 0) == 1
test_sources += test_xcb_sources
endif