Switch from direct DRM/KMS rendering to Wayland/Weston rendering
So far we have rendered graphics directly through the Linux graphics stack using DRM/KMS, with framebuffers or GPU buffers created via GBM and rendered through EGL. In this setup our application talks directly to the graphics subsystem and is responsible for producing the final image that is scanned out to the display.
However, as soon as we want to run multiple graphical programs at the same time, things become more complicated. We need a way to coordinate which application draws where on the screen and which program should receive user input such as mouse or keyboard events.
This is where Wayland comes in. Wayland defines a protocol that standardizes the communication between graphical applications and a display server. Instead of rendering directly to the screen, applications render their graphics into their own buffers (for example GBM/EGL GPU buffers or shared-memory buffers) and submit these buffers to the display server.
In the Wayland architecture this display server is called the compositor. The compositor collects the buffers from all running applications, composes them into the final image that is displayed on the screen, and routes input events to the appropriate client.
Weston is the reference implementation of such a Wayland compositor and is often used as a lightweight compositor on embedded systems.
In other words, when moving from a pure DRM/KMS setup to a Wayland system, we introduce an additional layer: applications become Wayland clients, and the compositor (for example Weston) takes over the responsibility of composing the final display image and managing input.
Installing Wayland and Weston on Debian Trixie
On Debian Trixie the necessary components can be installed directly from the package repository.
sudo apt install weston wayland-utils wayland-protocols
The packages provide:
| Package |
Purpose |
| weston |
reference Wayland compositor |
| wayland-utils |
diagnostic tools (wayland-info) |
| wayland-protocols |
XML protocol definitions such as xdg-shell |
Now Weston can be started directly on the DRM/KMS backend:
weston --backend=drm --renderer=gl
often even the following is sufficient
weston
If everything works, Weston will open a simple desktop environment.
From a DRM/KMS Application to a Wayland Client
Our previous example rendered directly to the screen using:
- DRM/KMS
- GBM
- EGL
- OpenGL ES
The application created scanout buffers and called drmModeSetCrtc() to present them.
In a Wayland system this is no longer possible. Only the compositor (Weston) talks to DRM/KMS. Applications become Wayland clients that render into buffers which are then submitted to the compositor. Conceptually the rendering pipeline now looks like this:
Application
β OpenGL ES / EGL
βΌ
Wayland buffer
βΌ
Weston compositor
βΌ
DRM / KMS
βΌ
Display
The OpenGL rendering itself stays almost identical. What changes is the window creation and buffer presentation. Instead of:
GBM surface β DRM framebuffer β drmModeSetCrtc
we now use:
wl_surface β wl_egl_window β eglSwapBuffers β compositor
The following example is the Wayland version of the previous DRM/KMS line-rendering benchmark. The rendering code is mostly unchanged. The additional code is mainly needed for:
- connecting to the Wayland server
- discovering global interfaces
- creating a surface
- attaching the xdg-shell window role
- handling configure events from the compositor
The core differences compared to the DRM/KMS version are:
1. Connect to the Wayland server
wl_display_connect(NULL);
2. Discover compositor interfaces
wl_registry_add_listener(...)
3. Create a surface and window role
wl_surface
xdg_surface
xdg_toplevel
4. Create an EGL window
wl_egl_window
eglCreateWindowSurface
5. Render and swap buffers
eglSwapBuffers()
After eglSwapBuffers() the compositor takes care of presenting the frame. Unlike the DRM version, the client does not control scanout anymore.
// Build preparation:
//
// xdg-shell
// wayland-scanner client-header \
// /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml \
// xdg-shell-client-protocol.h
//
// wayland-scanner private-code \
// /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml \
// xdg-shell-protocol.c
//
// presentation-time
// On many systems:
// wayland-scanner client-header \
// /usr/share/wayland-protocols/staging/presentation-time/presentation-time.xml \
// presentation-time-client-protocol.h
//
// wayland-scanner private-code \
// /usr/share/wayland-protocols/staging/presentation-time/presentation-time.xml \
// presentation-time-protocol.c
//
// On some older systems the XML may instead be here:
// /usr/share/wayland-protocols/unstable/presentation-time/presentation-time.xml
//
// Build:
// gcc ogl-min-line-perf-wayland-present.c \
// xdg-shell-protocol.c presentation-time-protocol.c \
// -o ogl-min-line-perf-wayland-present \
// $(pkg-config --cflags --libs wayland-client wayland-egl egl glesv2)
#include <wayland-client.h>
#include <wayland-egl.h>
#include "xdg-shell-client-protocol.h"
#include "presentation-time-client-protocol.h"
#include <EGL/egl.h>
#include <GLES3/gl3.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <inttypes.h>
typedef struct {
struct wl_display *display;
struct wl_registry *registry;
struct wl_compositor *compositor;
struct wl_surface *surface;
struct xdg_wm_base *wm_base;
struct xdg_surface *xdg_surface;
struct xdg_toplevel *xdg_toplevel;
struct wp_presentation *presentation;
struct wl_egl_window *egl_window;
EGLDisplay egl_display;
EGLConfig egl_config;
EGLContext egl_context;
EGLSurface egl_surface;
GLuint shader_program;
GLuint vertex_array_object;
GLuint vertex_buffer_object;
int width;
int height;
int configured;
int running;
int presentation_clock_id;
int have_presentation_clock;
struct wl_callback *frame_callback;
struct wp_presentation_feedback *presentation_feedback;
uint64_t t_frame_begin_ns;
uint64_t t_vertices_done_ns;
uint64_t t_draw_begin_ns;
uint64_t t_gpu_done_ns;
uint64_t t_swap_return_ns;
uint64_t t_frame_done_ns;
uint64_t t_present_ns;
int frame_done_received;
int presentation_received;
int presentation_discarded;
} GraphicsContext;
static void fatal(const char *msg)
{
fprintf(stderr, "%s\n", msg);
exit(1);
}
static void check_egl_bool(const char *what, EGLBoolean ok)
{
if (!ok) {
EGLint err = eglGetError();
fprintf(stderr, "%s failed, EGL error = 0x%04x\n", what, err);
exit(1);
}
}
static uint64_t get_time_ns_from_clock(clockid_t clk)
{
struct timespec ts;
if (clock_gettime(clk, &ts) != 0) {
perror("clock_gettime");
exit(1);
}
return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
}
static uint64_t get_now_ns(const GraphicsContext *gfx)
{
clockid_t clk = CLOCK_MONOTONIC;
if (gfx->have_presentation_clock) {
clk = (clockid_t)gfx->presentation_clock_id;
}
return get_time_ns_from_clock(clk);
}
static double ns_to_ms(uint64_t ns)
{
return (double)ns / 1e6;
}
static GLuint create_shader(GLenum type, const char *source)
{
GLuint shader = glCreateShader(type);
if (!shader) {
fatal("glCreateShader failed");
}
glShaderSource(shader, 1, &source, NULL);
glCompileShader(shader);
GLint ok = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
if (!ok) {
char log[2048];
glGetShaderInfoLog(shader, sizeof(log), NULL, log);
fprintf(stderr, "Shader compile error: %s\n", log);
exit(1);
}
return shader;
}
static GLuint create_program(const char *vs, const char *fs)
{
GLuint v = create_shader(GL_VERTEX_SHADER, vs);
GLuint f = create_shader(GL_FRAGMENT_SHADER, fs);
GLuint program = glCreateProgram();
if (!program) {
fatal("glCreateProgram failed");
}
glAttachShader(program, v);
glAttachShader(program, f);
glLinkProgram(program);
GLint ok = 0;
glGetProgramiv(program, GL_LINK_STATUS, &ok);
if (!ok) {
char log[2048];
glGetProgramInfoLog(program, sizeof(log), NULL, log);
fprintf(stderr, "Program link error: %s\n", log);
exit(1);
}
glDeleteShader(v);
glDeleteShader(f);
return program;
}
/* ---------------- xdg_wm_base ---------------- */
static void handle_wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial)
{
(void)data;
xdg_wm_base_pong(wm_base, serial);
}
static const struct xdg_wm_base_listener wm_base_listener = {
.ping = handle_wm_base_ping
};
/* ---------------- xdg_surface ---------------- */
static void handle_xdg_surface_configure(void *data, struct xdg_surface *surface, uint32_t serial)
{
GraphicsContext *gfx = (GraphicsContext *)data;
xdg_surface_ack_configure(surface, serial);
gfx->configured = 1;
}
static const struct xdg_surface_listener xdg_surface_listener = {
.configure = handle_xdg_surface_configure
};
/* ---------------- xdg_toplevel ---------------- */
static void handle_toplevel_configure(void *data,
struct xdg_toplevel *toplevel,
int32_t width,
int32_t height,
struct wl_array *states)
{
(void)toplevel;
(void)states;
GraphicsContext *gfx = (GraphicsContext *)data;
if (width > 0) {
gfx->width = width;
}
if (height > 0) {
gfx->height = height;
}
if (gfx->egl_window) {
wl_egl_window_resize(gfx->egl_window, gfx->width, gfx->height, 0, 0);
glViewport(0, 0, gfx->width, gfx->height);
}
}
static void handle_toplevel_close(void *data, struct xdg_toplevel *toplevel)
{
(void)toplevel;
GraphicsContext *gfx = (GraphicsContext *)data;
gfx->running = 0;
}
static const struct xdg_toplevel_listener toplevel_listener = {
.configure = handle_toplevel_configure,
.close = handle_toplevel_close
};
/* ---------------- wl_surface.frame ---------------- */
static void handle_frame_done(void *data, struct wl_callback *cb, uint32_t callback_data)
{
(void)callback_data;
GraphicsContext *gfx = (GraphicsContext *)data;
gfx->t_frame_done_ns = get_now_ns(gfx);
gfx->frame_done_received = 1;
if (cb) {
wl_callback_destroy(cb);
}
if (gfx->frame_callback == cb) {
gfx->frame_callback = NULL;
}
}
static const struct wl_callback_listener frame_listener = {
.done = handle_frame_done
};
/* ---------------- wp_presentation ---------------- */
static void handle_presentation_clock_id(void *data,
struct wp_presentation *presentation,
uint32_t clk_id)
{
(void)presentation;
GraphicsContext *gfx = (GraphicsContext *)data;
gfx->presentation_clock_id = (int)clk_id;
gfx->have_presentation_clock = 1;
fprintf(stderr, "presentation clock_id = %u\n", clk_id);
}
static const struct wp_presentation_listener presentation_listener = {
.clock_id = handle_presentation_clock_id
};
static void handle_feedback_sync_output(void *data,
struct wp_presentation_feedback *feedback,
struct wl_output *output)
{
(void)data;
(void)feedback;
(void)output;
}
static void handle_feedback_presented(void *data,
struct wp_presentation_feedback *feedback,
uint32_t tv_sec_hi,
uint32_t tv_sec_lo,
uint32_t tv_nsec,
uint32_t refresh,
uint32_t seq_hi,
uint32_t seq_lo,
uint32_t flags)
{
(void)refresh;
(void)seq_hi;
(void)seq_lo;
(void)flags;
GraphicsContext *gfx = (GraphicsContext *)data;
uint64_t sec = ((uint64_t)tv_sec_hi << 32) | (uint64_t)tv_sec_lo;
gfx->t_present_ns = sec * 1000000000ull + (uint64_t)tv_nsec;
gfx->presentation_received = 1;
if (feedback) {
wp_presentation_feedback_destroy(feedback);
}
if (gfx->presentation_feedback == feedback) {
gfx->presentation_feedback = NULL;
}
}
static void handle_feedback_discarded(void *data,
struct wp_presentation_feedback *feedback)
{
GraphicsContext *gfx = (GraphicsContext *)data;
gfx->presentation_discarded = 1;
if (feedback) {
wp_presentation_feedback_destroy(feedback);
}
if (gfx->presentation_feedback == feedback) {
gfx->presentation_feedback = NULL;
}
}
static const struct wp_presentation_feedback_listener presentation_feedback_listener = {
.sync_output = handle_feedback_sync_output,
.presented = handle_feedback_presented,
.discarded = handle_feedback_discarded
};
/* ---------------- registry ---------------- */
static void registry_global(void *data,
struct wl_registry *registry,
uint32_t name,
const char *interface,
uint32_t version)
{
GraphicsContext *gfx = (GraphicsContext *)data;
if (strcmp(interface, wl_compositor_interface.name) == 0) {
uint32_t bind_version = version < 4 ? version : 4;
gfx->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, bind_version);
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
gfx->wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
} else if (strcmp(interface, wp_presentation_interface.name) == 0) {
uint32_t bind_version = version < 1 ? version : 1;
gfx->presentation = wl_registry_bind(registry, name, &wp_presentation_interface, bind_version);
}
}
static void registry_global_remove(void *data, struct wl_registry *registry, uint32_t name)
{
(void)data;
(void)registry;
(void)name;
}
static const struct wl_registry_listener registry_listener = {
.global = registry_global,
.global_remove = registry_global_remove
};
static void process_wayland_once(GraphicsContext *gfx)
{
wl_display_dispatch_pending(gfx->display);
wl_display_flush(gfx->display);
}
static void reset_frame_measurements(GraphicsContext *gfx)
{
gfx->frame_done_received = 0;
gfx->presentation_received = 0;
gfx->presentation_discarded = 0;
gfx->t_frame_done_ns = 0;
gfx->t_present_ns = 0;
}
static void destroy_pending_callbacks(GraphicsContext *gfx)
{
if (gfx->frame_callback) {
wl_callback_destroy(gfx->frame_callback);
gfx->frame_callback = NULL;
}
if (gfx->presentation_feedback) {
wp_presentation_feedback_destroy(gfx->presentation_feedback);
gfx->presentation_feedback = NULL;
}
}
static void wait_for_presentation_feedback(GraphicsContext *gfx)
{
while (gfx->running &&
!gfx->presentation_received &&
!gfx->presentation_discarded) {
if (wl_display_dispatch(gfx->display) < 0) {
fprintf(stderr, "wl_display_dispatch failed while waiting for presentation feedback\n");
gfx->running = 0;
break;
}
}
}
static void graphics_init(GraphicsContext *gfx)
{
memset(gfx, 0, sizeof(*gfx));
gfx->width = 1920;
gfx->height = 1080;
gfx->running = 1;
gfx->egl_display = EGL_NO_DISPLAY;
gfx->egl_context = EGL_NO_CONTEXT;
gfx->egl_surface = EGL_NO_SURFACE;
gfx->display = wl_display_connect(NULL);
if (!gfx->display) {
fatal("Cannot connect to Wayland display");
}
gfx->registry = wl_display_get_registry(gfx->display);
if (!gfx->registry) {
fatal("wl_display_get_registry failed");
}
wl_registry_add_listener(gfx->registry, ®istry_listener, gfx);
wl_display_roundtrip(gfx->display);
wl_display_roundtrip(gfx->display);
if (!gfx->compositor || !gfx->wm_base) {
fatal("Missing required Wayland globals");
}
if (gfx->presentation) {
wp_presentation_add_listener(gfx->presentation, &presentation_listener, gfx);
wl_display_roundtrip(gfx->display);
} else {
fprintf(stderr, "Warning: compositor does not advertise wp_presentation\n");
fprintf(stderr, "Actual display presentation timestamps will not be available\n");
}
xdg_wm_base_add_listener(gfx->wm_base, &wm_base_listener, gfx);
gfx->surface = wl_compositor_create_surface(gfx->compositor);
if (!gfx->surface) {
fatal("wl_compositor_create_surface failed");
}
gfx->xdg_surface = xdg_wm_base_get_xdg_surface(gfx->wm_base, gfx->surface);
if (!gfx->xdg_surface) {
fatal("xdg_wm_base_get_xdg_surface failed");
}
xdg_surface_add_listener(gfx->xdg_surface, &xdg_surface_listener, gfx);
gfx->xdg_toplevel = xdg_surface_get_toplevel(gfx->xdg_surface);
if (!gfx->xdg_toplevel) {
fatal("xdg_surface_get_toplevel failed");
}
xdg_toplevel_set_title(gfx->xdg_toplevel, "ogl-min-line-perf-wayland-present");
xdg_toplevel_set_app_id(gfx->xdg_toplevel, "ogl-min-line-perf-wayland-present");
xdg_toplevel_add_listener(gfx->xdg_toplevel, &toplevel_listener, gfx);
wl_surface_commit(gfx->surface);
while (!gfx->configured) {
if (wl_display_dispatch(gfx->display) < 0) {
fatal("wl_display_dispatch failed during initial configure");
}
}
gfx->egl_display = eglGetDisplay((EGLNativeDisplayType)gfx->display);
if (gfx->egl_display == EGL_NO_DISPLAY) {
fatal("eglGetDisplay failed");
}
check_egl_bool("eglInitialize", eglInitialize(gfx->egl_display, NULL, NULL));
check_egl_bool("eglBindAPI", eglBindAPI(EGL_OPENGL_ES_API));
EGLint config_attributes[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_NONE
};
EGLint num_configs = 0;
check_egl_bool("eglChooseConfig",
eglChooseConfig(gfx->egl_display,
config_attributes,
&gfx->egl_config,
1,
&num_configs));
if (num_configs < 1) {
fatal("No suitable EGL config found");
}
gfx->egl_context = eglCreateContext(
gfx->egl_display,
gfx->egl_config,
EGL_NO_CONTEXT,
(EGLint[]){ EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }
);
if (gfx->egl_context == EGL_NO_CONTEXT) {
fprintf(stderr, "eglCreateContext failed, EGL error = 0x%04x\n", eglGetError());
exit(1);
}
gfx->egl_window = wl_egl_window_create(gfx->surface, gfx->width, gfx->height);
if (!gfx->egl_window) {
fatal("wl_egl_window_create failed");
}
gfx->egl_surface = eglCreateWindowSurface(
gfx->egl_display,
gfx->egl_config,
(EGLNativeWindowType)gfx->egl_window,
NULL
);
if (gfx->egl_surface == EGL_NO_SURFACE) {
fprintf(stderr, "eglCreateWindowSurface failed, EGL error = 0x%04x\n", eglGetError());
exit(1);
}
check_egl_bool("eglMakeCurrent",
eglMakeCurrent(gfx->egl_display,
gfx->egl_surface,
gfx->egl_surface,
gfx->egl_context));
{
const char *vertex_shader_source =
"#version 300 es\n"
"layout(location=0) in vec2 position;\n"
"layout(location=1) in vec4 color;\n"
"out vec4 vColor;\n"
"void main(){\n"
" vColor = color;\n"
" gl_Position = vec4(position, 0.0, 1.0);\n"
"}\n";
const char *fragment_shader_source =
"#version 300 es\n"
"precision mediump float;\n"
"in vec4 vColor;\n"
"out vec4 fragColor;\n"
"void main(){\n"
" fragColor = vColor;\n"
"}\n";
gfx->shader_program = create_program(vertex_shader_source, fragment_shader_source);
}
glUseProgram(gfx->shader_program);
glGenVertexArrays(1, &gfx->vertex_array_object);
glBindVertexArray(gfx->vertex_array_object);
glGenBuffers(1, &gfx->vertex_buffer_object);
glBindBuffer(GL_ARRAY_BUFFER, gfx->vertex_buffer_object);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 6 * (GLsizei)sizeof(float), (const void *)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 6 * (GLsizei)sizeof(float),
(const void *)(2 * sizeof(float)));
glEnableVertexAttribArray(1);
glViewport(0, 0, gfx->width, gfx->height);
if (!eglSwapInterval(gfx->egl_display, 1)) {
fprintf(stderr, "Warning: eglSwapInterval(1) failed, EGL error = 0x%04x\n", eglGetError());
}
}
static void cleanup(GraphicsContext *gfx)
{
destroy_pending_callbacks(gfx);
if (gfx->shader_program) {
glDeleteProgram(gfx->shader_program);
gfx->shader_program = 0;
}
if (gfx->vertex_buffer_object) {
glDeleteBuffers(1, &gfx->vertex_buffer_object);
gfx->vertex_buffer_object = 0;
}
if (gfx->vertex_array_object) {
glDeleteVertexArrays(1, &gfx->vertex_array_object);
gfx->vertex_array_object = 0;
}
if (gfx->egl_display != EGL_NO_DISPLAY) {
eglMakeCurrent(gfx->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (gfx->egl_surface != EGL_NO_SURFACE) {
eglDestroySurface(gfx->egl_display, gfx->egl_surface);
gfx->egl_surface = EGL_NO_SURFACE;
}
if (gfx->egl_context != EGL_NO_CONTEXT) {
eglDestroyContext(gfx->egl_display, gfx->egl_context);
gfx->egl_context = EGL_NO_CONTEXT;
}
eglTerminate(gfx->egl_display);
gfx->egl_display = EGL_NO_DISPLAY;
}
if (gfx->egl_window) {
wl_egl_window_destroy(gfx->egl_window);
gfx->egl_window = NULL;
}
if (gfx->xdg_toplevel) {
xdg_toplevel_destroy(gfx->xdg_toplevel);
gfx->xdg_toplevel = NULL;
}
if (gfx->xdg_surface) {
xdg_surface_destroy(gfx->xdg_surface);
gfx->xdg_surface = NULL;
}
if (gfx->surface) {
wl_surface_destroy(gfx->surface);
gfx->surface = NULL;
}
if (gfx->presentation) {
wp_presentation_destroy(gfx->presentation);
gfx->presentation = NULL;
}
if (gfx->wm_base) {
xdg_wm_base_destroy(gfx->wm_base);
gfx->wm_base = NULL;
}
if (gfx->compositor) {
wl_compositor_destroy(gfx->compositor);
gfx->compositor = NULL;
}
if (gfx->registry) {
wl_registry_destroy(gfx->registry);
gfx->registry = NULL;
}
if (gfx->display) {
wl_display_disconnect(gfx->display);
gfx->display = NULL;
}
}
int main(void)
{
GraphicsContext gfx;
graphics_init(&gfx);
const int line_count = 100000;
const int vertices_per_line = 2;
const int total_vertices = line_count * vertices_per_line;
const int floats_per_vertex = 6;
size_t vertex_buffer_size =
(size_t)total_vertices * (size_t)floats_per_vertex * sizeof(float);
float *vertex_data = (float *)malloc(vertex_buffer_size);
if (!vertex_data) {
fprintf(stderr, "malloc failed\n");
cleanup(&gfx);
return 1;
}
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)vertex_buffer_size, NULL, GL_STREAM_DRAW);
srandom((unsigned int)time(NULL));
while (gfx.running) {
process_wayland_once(&gfx);
reset_frame_measurements(&gfx);
gfx.t_frame_begin_ns = get_now_ns(&gfx);
for (int i = 0; i < line_count; i++) {
int x0 = (int)(random() % gfx.width);
int y0 = (int)(random() % gfx.height);
int x1 = (int)(random() % gfx.width);
int y1 = (int)(random() % gfx.height);
float fx0 = 2.0f * (float)x0 / (float)(gfx.width - 1) - 1.0f;
float fy0 = 1.0f - 2.0f * (float)y0 / (float)(gfx.height - 1);
float fx1 = 2.0f * (float)x1 / (float)(gfx.width - 1) - 1.0f;
float fy1 = 1.0f - 2.0f * (float)y1 / (float)(gfx.height - 1);
float r = (float)(random() % 256) / 255.0f;
float g = (float)(random() % 256) / 255.0f;
float b = (float)(random() % 256) / 255.0f;
int base = i * vertices_per_line * floats_per_vertex;
vertex_data[base + 0] = fx0;
vertex_data[base + 1] = fy0;
vertex_data[base + 2] = r;
vertex_data[base + 3] = g;
vertex_data[base + 4] = b;
vertex_data[base + 5] = 1.0f;
vertex_data[base + 6] = fx1;
vertex_data[base + 7] = fy1;
vertex_data[base + 8] = r;
vertex_data[base + 9] = g;
vertex_data[base + 10] = b;
vertex_data[base + 11] = 1.0f;
}
gfx.t_vertices_done_ns = get_now_ns(&gfx);
gfx.t_draw_begin_ns = get_now_ns(&gfx);
glClear(GL_COLOR_BUFFER_BIT);
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)vertex_buffer_size, NULL, GL_STREAM_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, (GLsizeiptr)vertex_buffer_size, vertex_data);
glDrawArrays(GL_LINES, 0, total_vertices);
{
GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
if (!fence) {
fprintf(stderr, "glFenceSync failed\n");
break;
}
glFlush();
for (;;) {
GLenum wait_result = glClientWaitSync(
fence,
GL_SYNC_FLUSH_COMMANDS_BIT,
1000000000ull
);
if (wait_result == GL_ALREADY_SIGNALED ||
wait_result == GL_CONDITION_SATISFIED) {
break;
}
if (wait_result == GL_WAIT_FAILED) {
fprintf(stderr, "glClientWaitSync failed\n");
glDeleteSync(fence);
free(vertex_data);
cleanup(&gfx);
return 1;
}
}
gfx.t_gpu_done_ns = get_now_ns(&gfx);
glDeleteSync(fence);
}
destroy_pending_callbacks(&gfx);
gfx.frame_callback = wl_surface_frame(gfx.surface);
if (!gfx.frame_callback) {
fprintf(stderr, "wl_surface_frame failed\n");
break;
}
wl_callback_add_listener(gfx.frame_callback, &frame_listener, &gfx);
if (gfx.presentation) {
gfx.presentation_feedback = wp_presentation_feedback(gfx.presentation, gfx.surface);
if (!gfx.presentation_feedback) {
fprintf(stderr, "wp_presentation_feedback failed\n");
break;
}
wp_presentation_feedback_add_listener(
gfx.presentation_feedback,
&presentation_feedback_listener,
&gfx
);
}
if (!eglSwapBuffers(gfx.egl_display, gfx.egl_surface)) {
fprintf(stderr, "eglSwapBuffers failed, EGL error = 0x%04x\n", eglGetError());
break;
}
gfx.t_swap_return_ns = get_now_ns(&gfx);
if (gfx.presentation) {
wait_for_presentation_feedback(&gfx);
} else {
for (int i = 0; i < 10 && gfx.running; i++) {
process_wayland_once(&gfx);
usleep(1000);
}
}
printf("Create Vert CPU : %8.3f ms\n",
ns_to_ms(gfx.t_vertices_done_ns - gfx.t_frame_begin_ns));
printf("Draw+Upload submit: %8.3f ms\n",
ns_to_ms(gfx.t_swap_return_ns - gfx.t_draw_begin_ns));
printf("GPU done : %8.3f ms\n",
ns_to_ms(gfx.t_gpu_done_ns - gfx.t_frame_begin_ns));
printf("Swap return : %8.3f ms\n",
ns_to_ms(gfx.t_swap_return_ns - gfx.t_frame_begin_ns));
if (gfx.frame_done_received) {
printf("frame callback : %8.3f ms\n",
ns_to_ms(gfx.t_frame_done_ns - gfx.t_frame_begin_ns));
} else {
printf("frame callback : (not received yet)\n");
}
if (gfx.presentation_received) {
printf("PRESENTED : %8.3f ms\n",
ns_to_ms(gfx.t_present_ns - gfx.t_frame_begin_ns));
} else if (gfx.presentation_discarded) {
printf("PRESENTED : discarded by compositor\n");
} else {
printf("PRESENTED : unavailable\n");
}
printf("\n");
sleep(1);
}
free(vertex_data);
cleanup(&gfx);
return 0;
}
The Wayland program is much larger than the DRM/KMS version.
Most of these additional lines are not rendering code but protocol handling:
- Wayland registry discovery
- xdg-shell window setup
- configure event handling
- Wayland event dispatching
- Wayland presentation-time protocol to measure rendering time
In the DRM/KMS version our application owned the display completely. In the Wayland version it becomes one client among potentially many and must cooperate with the compositor.
The picture shows the line performance test running in fullscreen mode. The terminal window displaying the measurement results is overlaid by the compositor - a really nice feature of the compositor.
Despite the additional composition step in the rendering pipeline, the total rendering time increases only slightly. This is because the compositor does not need to re-render the entire scene. Instead, each application renders into its own buffer, and the compositor combines these buffers during the final display composition.
DRM/KMS rendering : 0.234 s
Weston Wayland rendering : 0.269 s
Ressources:
Repository of Wayland / Weston: