mirror of
https://gitlab.freedesktop.org/cairo/cairo.git
synced 2026-05-15 17:18:05 +02:00
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:
commit
610b15ecd6
16 changed files with 2523 additions and 1 deletions
172
boilerplate/cairo-boilerplate-gbm.c
Normal file
172
boilerplate/cairo-boilerplate-gbm.c
Normal 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 (>c->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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
915
examples/gbm-egl-benchmark.c
Normal file
915
examples/gbm-egl-benchmark.c
Normal 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, ®istry_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
46
examples/meson.build
Normal 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,
|
||||
)
|
||||
15
meson.build
15
meson.build
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
717
src/cairo-gbm-surface.c
Normal 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
93
src/cairo-gbm.h
Normal 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 */
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
74
test/gbm-surface-source.c
Normal 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
438
test/gbm-surface.c
Normal 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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue