From 18d9ad98b1f383b0e5a0c8a25f289a9085bcecc1 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Mon, 22 Nov 2021 18:16:10 -0300 Subject: [PATCH] clients: add client to test the dma-buf feedback implementation Simple client to test the dma-buf feedback implementation. This does not replace the need to implement a dma-buf feedback test that can be run in the CI. But as we still don't know exactly how to do this, this client can be helpful to run tests manually. Signed-off-by: Leandro Ribeiro Reviewed-by: Daniel Stone --- clients/meson.build | 20 + clients/simple-dmabuf-feedback.c | 1387 ++++++++++++++++++++++++++++++ meson_options.txt | 2 +- 3 files changed, 1408 insertions(+), 1 deletion(-) create mode 100644 clients/simple-dmabuf-feedback.c diff --git a/clients/meson.build b/clients/meson.build index aabd6767..50caf7b3 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -50,6 +50,26 @@ simple_clients = [ ], 'dep_objs': [ dep_wayland_client, dep_libshared ] }, + { + 'name': 'dmabuf-feedback', + 'sources': [ + 'simple-dmabuf-feedback.c', + '../libweston/pixel-formats.c', + linux_dmabuf_unstable_v1_client_protocol_h, + linux_dmabuf_unstable_v1_protocol_c, + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, + ], + 'dep_objs': [ + dep_wayland_client, + dep_libshared, + dep_pixman, + dep_libdrm, + dependency('libudev', version: '>= 136'), + ], + 'deps': [ 'egl', 'glesv2', 'gbm' ], + 'options': [ 'renderer-gl' ] + }, { 'name': 'dmabuf-egl', 'sources': [ diff --git a/clients/simple-dmabuf-feedback.c b/clients/simple-dmabuf-feedback.c new file mode 100644 index 00000000..06f5415b --- /dev/null +++ b/clients/simple-dmabuf-feedback.c @@ -0,0 +1,1387 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "shared/helpers.h" +#include "shared/platform.h" +#include "shared/weston-drm-fourcc.h" +#include +#include +#include "xdg-shell-client-protocol.h" +#include "linux-dmabuf-unstable-v1-client-protocol.h" + +#include +#include +#include +#include +#include +#include + +#define NUM_BUFFERS 3 + +/* We have to hack the DRM-backend to pretend that planes of the underlying + * hardware don't support this format. If you change the value of this constant, + * do not forget to change in the DRM-backend as well. See main() description + * for more details. */ +#define INITIAL_BUFFER_FORMAT DRM_FORMAT_XRGB8888 + +static const char *vert_shader_text = + "attribute vec4 pos;\n" + "attribute vec4 color;\n" + "varying vec4 v_color;\n" + "void main() {\n" + " gl_Position = pos;\n" + " v_color = color;\n" + "}\n"; + +static const char *frag_shader_text = + "precision mediump float;\n" + "varying vec4 v_color;\n" + "void main() {\n" + " gl_FragColor = v_color;\n" + "}\n"; + +struct drm_format { + uint32_t format; + struct wl_array modifiers; +}; + +struct drm_format_array { + struct wl_array arr; +}; + +struct dmabuf_feedback_format_table { + unsigned int size; + struct { + uint32_t format; + uint32_t padding; /* unused */ + uint64_t modifier; + } *data; +}; + +struct dmabuf_feedback_tranche { + dev_t target_device; + bool is_scanout_tranche; + struct drm_format_array formats; +}; + +struct dmabuf_feedback { + dev_t main_device; + struct dmabuf_feedback_format_table format_table; + struct wl_array tranches; + struct dmabuf_feedback_tranche pending_tranche; +}; + +struct output { + struct wl_output *wl_output; + int x, y; + int width, height; + int scale; + bool initialized; +}; + +struct egl { + EGLDisplay display; + EGLContext context; + EGLConfig conf; + PFNEGLQUERYDMABUFMODIFIERSEXTPROC query_dmabuf_modifiers; + PFNEGLCREATEIMAGEKHRPROC create_image; + PFNEGLDESTROYIMAGEKHRPROC destroy_image; + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d; +}; + +struct gl { + GLuint program; + GLuint pos; + GLuint color; +}; + +struct display { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct output output; + struct xdg_wm_base *wm_base; + struct zwp_linux_dmabuf_v1 *dmabuf; + struct gbm_device *gbm_device; + struct egl egl; +}; + +struct buffer { + struct window *window; + struct wl_buffer *buffer; + bool busy; + bool recreate; + int dmabuf_fds[4]; + struct gbm_bo *bo; + EGLImageKHR egl_image; + GLuint gl_texture; + GLuint gl_fbo; + int num_planes; + uint32_t width, height, strides[4], offsets[4]; + uint32_t format; + uint64_t modifier; +}; + +struct window { + struct display *display; + struct gl gl; + struct wl_surface *surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + struct wl_callback *callback; + bool wait_for_configure; + uint32_t n_redraws; + struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback_obj; + struct dmabuf_feedback dmabuf_feedback, pending_dmabuf_feedback; + int card_fd; + struct drm_format format; + struct buffer buffers[NUM_BUFFERS]; +}; + +static void +drm_format_array_init(struct drm_format_array *formats) +{ + wl_array_init(&formats->arr); +} + +static void +drm_format_array_fini(struct drm_format_array *formats) +{ + struct drm_format *fmt; + + wl_array_for_each(fmt, &formats->arr) + wl_array_release(&fmt->modifiers); + + wl_array_release(&formats->arr); +} + +static struct drm_format * +drm_format_array_add_format(struct drm_format_array *formats, uint32_t format) +{ + struct drm_format *fmt; + + wl_array_for_each(fmt, &formats->arr) + if (fmt->format == format) + return fmt; + + fmt = wl_array_add(&formats->arr, sizeof(*fmt)); + assert(fmt && "error: could not allocate memory for format"); + + fmt->format = format; + wl_array_init(&fmt->modifiers); + + return fmt; +} + +static void +drm_format_add_modifier(struct drm_format *format, uint64_t modifier) +{ + uint64_t *mod; + + wl_array_for_each(mod, &format->modifiers) + if (*mod == modifier) + return; + + mod = wl_array_add(&format->modifiers, sizeof(uint64_t)); + assert(mod && "error: could not allocate memory for modifier"); + + *mod = modifier; +} + +static void +dmabuf_feedback_format_table_fini(struct dmabuf_feedback_format_table *format_table) +{ + if (format_table->data && format_table->data != MAP_FAILED) + munmap(format_table->data, format_table->size); +} + +static void +dmabuf_feedback_format_table_init(struct dmabuf_feedback_format_table *format_table) +{ + memset(format_table, 0, sizeof(*format_table)); +} + +static void +dmabuf_feedback_tranche_fini(struct dmabuf_feedback_tranche *tranche) +{ + drm_format_array_fini(&tranche->formats); +} + +static void +dmabuf_feedback_tranche_init(struct dmabuf_feedback_tranche *tranche) +{ + memset(tranche, 0, sizeof(*tranche)); + + drm_format_array_init(&tranche->formats); +} + +static void +dmabuf_feedback_fini(struct dmabuf_feedback *feedback) +{ + struct dmabuf_feedback_tranche *tranche; + + dmabuf_feedback_tranche_fini(&feedback->pending_tranche); + + wl_array_for_each(tranche, &feedback->tranches) + dmabuf_feedback_tranche_fini(tranche); + + dmabuf_feedback_format_table_fini(&feedback->format_table); +} + +static void +dmabuf_feedback_init(struct dmabuf_feedback *feedback) +{ + memset(feedback, 0, sizeof(*feedback)); + + dmabuf_feedback_tranche_init(&feedback->pending_tranche); + + wl_array_init(&feedback->tranches); + + dmabuf_feedback_format_table_init(&feedback->format_table); +} + +static GLuint +create_shader(const char *source, GLenum shader_type) +{ + GLuint shader; + GLint status; + + shader = glCreateShader(shader_type); + assert(shader != 0); + + glShaderSource(shader, 1, (const char **) &source, NULL); + glCompileShader(shader); + + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (!status) { + char log[1000]; + GLsizei len; + glGetShaderInfoLog(shader, 1000, &len, log); + fprintf(stderr, "error: compiling %s: %.*s\n", + shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment", + len, log); + return 0; + } + + return shader; +} + +static GLuint +create_and_link_program(GLuint vert, GLuint frag) +{ + GLint status; + GLuint program = glCreateProgram(); + + glAttachShader(program, vert); + glAttachShader(program, frag); + glLinkProgram(program); + + glGetProgramiv(program, GL_LINK_STATUS, &status); + if (!status) { + char log[1000]; + GLsizei len; + glGetProgramInfoLog(program, 1000, &len, log); + fprintf(stderr, "error: linking:\n%.*s\n", len, log); + return 0; + } + + return program; +} + +static void +create_fbo_for_buffer(struct buffer *buffer) +{ + struct display *display = buffer->window->display; + static const int general_attribs = 3; + static const int plane_attribs = 5; + static const int entries_per_attrib = 2; + EGLint attribs[(general_attribs + (plane_attribs * 4)) * entries_per_attrib + 1]; + unsigned int atti = 0; + + attribs[atti++] = EGL_WIDTH; + attribs[atti++] = buffer->width; + attribs[atti++] = EGL_HEIGHT; + attribs[atti++] = buffer->height; + attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; + attribs[atti++] = buffer->format; + + attribs[atti++] = EGL_DMA_BUF_PLANE0_FD_EXT; + attribs[atti++] = buffer->dmabuf_fds[0]; + attribs[atti++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT; + attribs[atti++] = (int) buffer->offsets[0]; + attribs[atti++] = EGL_DMA_BUF_PLANE0_PITCH_EXT; + attribs[atti++] = (int) buffer->strides[0]; + attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT; + attribs[atti++] = buffer->modifier & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT; + attribs[atti++] = buffer->modifier >> 32; + + if (buffer->num_planes > 1) { + attribs[atti++] = EGL_DMA_BUF_PLANE1_FD_EXT; + attribs[atti++] = buffer->dmabuf_fds[1]; + attribs[atti++] = EGL_DMA_BUF_PLANE1_OFFSET_EXT; + attribs[atti++] = (int) buffer->offsets[1]; + attribs[atti++] = EGL_DMA_BUF_PLANE1_PITCH_EXT; + attribs[atti++] = (int) buffer->strides[1]; + attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT; + attribs[atti++] = buffer->modifier & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT; + attribs[atti++] = buffer->modifier >> 32; + } + + if (buffer->num_planes > 2) { + attribs[atti++] = EGL_DMA_BUF_PLANE2_FD_EXT; + attribs[atti++] = buffer->dmabuf_fds[2]; + attribs[atti++] = EGL_DMA_BUF_PLANE2_OFFSET_EXT; + attribs[atti++] = (int) buffer->offsets[2]; + attribs[atti++] = EGL_DMA_BUF_PLANE2_PITCH_EXT; + attribs[atti++] = (int) buffer->strides[2]; + attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT; + attribs[atti++] = buffer->modifier & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT; + attribs[atti++] = buffer->modifier >> 32; + } + + if (buffer->num_planes > 3) { + attribs[atti++] = EGL_DMA_BUF_PLANE3_FD_EXT; + attribs[atti++] = buffer->dmabuf_fds[3]; + attribs[atti++] = EGL_DMA_BUF_PLANE3_OFFSET_EXT; + attribs[atti++] = (int) buffer->offsets[3]; + attribs[atti++] = EGL_DMA_BUF_PLANE3_PITCH_EXT; + attribs[atti++] = (int) buffer->strides[3]; + attribs[atti++] = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT; + attribs[atti++] = buffer->modifier & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT; + attribs[atti++] = buffer->modifier >> 32; + } + + attribs[atti] = EGL_NONE; + + assert(atti < ARRAY_LENGTH(attribs)); + + buffer->egl_image = display->egl.create_image(display->egl.display, + EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, + NULL, attribs); + assert(buffer->egl_image != EGL_NO_IMAGE_KHR && + "error: EGLImageKHR creation failed"); + + if (eglMakeCurrent(display->egl.display, EGL_NO_SURFACE, + EGL_NO_SURFACE, display->egl.context) != EGL_TRUE) + assert(0 && "error: failed to make context current"); + + glGenTextures(1, &buffer->gl_texture); + glBindTexture(GL_TEXTURE_2D, buffer->gl_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + display->egl.image_target_texture_2d(GL_TEXTURE_2D, buffer->egl_image); + + glGenFramebuffers(1, &buffer->gl_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, buffer->gl_texture, 0); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + assert(0 && "error: FBO creation failed"); +} + +static void +buffer_free(struct buffer *buf) +{ + struct egl *egl = &buf->window->display->egl; + int i; + + if (buf->buffer) + wl_buffer_destroy(buf->buffer); + + if (buf->gl_fbo) + glDeleteFramebuffers(1, &buf->gl_fbo); + + if (buf->gl_texture) + glDeleteTextures(1, &buf->gl_texture); + + if (buf->egl_image) + egl->destroy_image(egl->display, buf->egl_image); + + if (buf->bo) + gbm_bo_destroy(buf->bo); + + for (i = 0; i < buf->num_planes; i++) + close(buf->dmabuf_fds[i]); +} + +static void +create_dmabuf_buffer(struct window *window, struct buffer *buf, uint32_t width, + uint32_t height, uint32_t format, unsigned int count_modifiers, + uint64_t *modifiers); + +static void +buffer_recreate(struct buffer *buf) +{ + struct window *window = buf->window; + uint32_t width = buf->width; + uint32_t height = buf->height; + + buffer_free(buf); + create_dmabuf_buffer(window, buf, width, height, + window->format.format, + window->format.modifiers.size / sizeof(uint64_t), + window->format.modifiers.data); + buf->recreate = false; +} + +static void +buffer_release(void *data, struct wl_buffer *buffer) +{ + struct buffer *buf = data; + + buf->busy = false; + + if (buf->recreate) + buffer_recreate(buf); +} + +static const struct wl_buffer_listener buffer_listener = { + buffer_release +}; + +static void +create_succeeded(void *data, struct zwp_linux_buffer_params_v1 *params, + struct wl_buffer *new_buffer) +{ + struct buffer *buf = data; + + buf->buffer = new_buffer; + wl_buffer_add_listener(buf->buffer, &buffer_listener, buf); + zwp_linux_buffer_params_v1_destroy(params); +} + +static void +create_failed(void *data, struct zwp_linux_buffer_params_v1 *params) +{ + struct buffer *buf = data; + + buf->buffer = NULL; + zwp_linux_buffer_params_v1_destroy(params); + + assert(0 && "error: zwp_linux_buffer_params.create failed"); +} + +static const struct zwp_linux_buffer_params_v1_listener params_listener = { + create_succeeded, + create_failed +}; + +static void +create_dmabuf_buffer(struct window *window, struct buffer *buf, uint32_t width, + uint32_t height, uint32_t format, unsigned int count_modifiers, + uint64_t *modifiers) +{ + struct display *display = window->display; + static uint32_t flags = 0; + struct zwp_linux_buffer_params_v1 *params; + int i; + + buf->window = window; + buf->width = width; + buf->height = height; + buf->format = format; + +#ifdef HAVE_GBM_MODIFIERS + if (count_modifiers > 0) { + buf->bo = gbm_bo_create_with_modifiers(display->gbm_device, + buf->width, buf->height, + format, modifiers, + count_modifiers); + if (buf->bo) + buf->modifier = gbm_bo_get_modifier(buf->bo); + } +#endif + + if (!buf->bo) { + buf->bo = gbm_bo_create(display->gbm_device, buf->width, + buf->height, buf->format, + GBM_BO_USE_RENDERING); + buf->modifier = DRM_FORMAT_MOD_INVALID; + } + + assert(buf->bo && "error: could not create GBM bo for buffer"); + + buf->num_planes = gbm_bo_get_plane_count(buf->bo); + + params = zwp_linux_dmabuf_v1_create_params(window->display->dmabuf); + zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, buf); + + for (i = 0; i < buf->num_planes; i++) { + buf->dmabuf_fds[i] = gbm_bo_get_fd_for_plane(buf->bo, i); + buf->strides[i] = gbm_bo_get_stride_for_plane(buf->bo, i); + buf->offsets[i] = gbm_bo_get_offset(buf->bo, i); + assert(buf->dmabuf_fds[i] >= 0 && + "error: could not get fd for GBM bo"); + assert(buf->strides[i] > 0 && + "error: could not get stride for GBM bo"); + + zwp_linux_buffer_params_v1_add(params, buf->dmabuf_fds[i], i, + buf->offsets[i], buf->strides[i], + buf->modifier >> 32, + buf->modifier & 0xffffffff); + } + + zwp_linux_buffer_params_v1_create(params, buf->width, buf->height, + buf->format, flags); + + create_fbo_for_buffer(buf); +} + +static struct buffer * +window_next_buffer(struct window *window) +{ + unsigned int i; + + for (i = 0; i < NUM_BUFFERS; i++) + if (!window->buffers[i].busy) + return &window->buffers[i]; + return NULL; +} + +static void +render(struct buffer *buffer) +{ + struct window *window = buffer->window; + + static const GLfloat verts[4][2] = { + { -0.5, -0.5 }, + { -0.5, 0.5 }, + { 0.5, -0.5 }, + { 0.5, 0.5 } + }; + static const GLfloat colors[4][3] = { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 }, + { 1, 1, 0 } + }; + + glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); + + glViewport(0, 0, buffer->width, buffer->height); + + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); + glVertexAttribPointer(window->gl.color, 3, GL_FLOAT, GL_FALSE, 0, colors); + glEnableVertexAttribArray(window->gl.pos); + glEnableVertexAttribArray(window->gl.color); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(window->gl.pos); + glDisableVertexAttribArray(window->gl.color); + + glFinish(); +} + +static const struct wl_callback_listener frame_listener; + +static void +redraw(void *data, struct wl_callback *callback, uint32_t time) +{ + struct window *window = data; + struct buffer *buf; + struct wl_region *region; + + buf = window_next_buffer(window); + assert(buf && "error: all buffers are busy"); + + render(buf); + + wl_surface_attach(window->surface, buf->buffer, 0, 0); + wl_surface_damage(window->surface, 0, 0, buf->width, buf->height); + + if (callback) + wl_callback_destroy(callback); + + window->callback = wl_surface_frame(window->surface); + wl_callback_add_listener(window->callback, &frame_listener, window); + wl_surface_commit(window->surface); + buf->busy = true; + + region = wl_compositor_create_region(window->display->compositor); + wl_region_add(region, 0, 0, window->display->output.width, + window->display->output.height); + wl_surface_set_opaque_region(window->surface, region); + wl_region_destroy(region); + + window->n_redraws++; +} + +static const struct wl_callback_listener frame_listener = { + redraw +}; + +static void +xdg_surface_handle_configure(void *data, struct xdg_surface *surface, + uint32_t serial) +{ + struct window *window = data; + + xdg_surface_ack_configure(surface, serial); + window->wait_for_configure = false; +} + +static const struct xdg_surface_listener xdg_surface_listener = { + xdg_surface_handle_configure, +}; + +static void +xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, + int32_t width, int32_t height, + struct wl_array *states) +{ +} + +static void +xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) +{ + assert(0 && "error: window closed, this should not happen"); +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + xdg_toplevel_handle_configure, + xdg_toplevel_handle_close, +}; + +static void +gbm_setup(struct window *window) +{ + struct display *display = window->display; + + display->gbm_device = gbm_create_device(window->card_fd); + assert(display->gbm_device && "error: could not create GBM device"); +} + +static void +egl_setup(struct window *window) +{ + struct display *display = window->display; + struct egl *egl = &display->egl; + const char *egl_extensions = NULL; + const char *gl_extensions = NULL; + EGLint major, minor; + EGLint ret; + + static const EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE + }; + + egl->display = weston_platform_get_egl_display(EGL_PLATFORM_GBM_KHR, + display->gbm_device, NULL); + assert(egl->display && "error: could not create EGL display"); + + ret = eglInitialize(egl->display, &major, &minor); + assert(ret != EGL_FALSE && "error: failed to intialized EGL display"); + + ret = eglBindAPI(EGL_OPENGL_ES_API); + assert(ret != EGL_FALSE && "error: failed to set EGL API"); + + egl_extensions = eglQueryString(egl->display, EGL_EXTENSIONS); + assert(egl_extensions && + "error: could not retrieve supported EGL extensions"); + + assert(weston_check_egl_extension(egl_extensions, + "EGL_EXT_image_dma_buf_import")); + assert(weston_check_egl_extension(egl_extensions, + "EGL_KHR_surfaceless_context")); + assert(weston_check_egl_extension(egl_extensions, + "EGL_EXT_image_dma_buf_import_modifiers")); + assert(weston_check_egl_extension(egl_extensions, + "EGL_KHR_no_config_context")); + + egl->context = eglCreateContext(egl->display, EGL_NO_CONFIG_KHR, + EGL_NO_CONTEXT, context_attribs); + assert(egl->context != EGL_NO_CONTEXT && + "error: failed to create EGLContext"); + + ret = eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, + egl->context); + assert(ret == EGL_TRUE && "error: failed to make context current"); + + gl_extensions = (const char *) glGetString(GL_EXTENSIONS); + assert(gl_extensions && + "error: could not retrieve supported GL extensions"); + + assert(weston_check_egl_extension(gl_extensions, + "GL_OES_EGL_image")); + + egl->query_dmabuf_modifiers = + (void *) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); + egl->create_image = + (void *) eglGetProcAddress("eglCreateImageKHR"); + egl->destroy_image = + (void *) eglGetProcAddress("eglDestroyImageKHR"); + egl->image_target_texture_2d = + (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES"); +} + +static void +gl_setup(struct window *window) +{ + struct gl *gl = &window->gl; + GLuint vert; + GLuint frag; + + vert = create_shader(vert_shader_text, GL_VERTEX_SHADER); + assert(vert != 0 && "error: failed to compile vertex shader"); + frag = create_shader(frag_shader_text, GL_FRAGMENT_SHADER); + assert(frag != 0 && "error: failed to compile fragment shader"); + + gl->program = create_and_link_program(vert ,frag); + assert(gl->program != 0 && + "error: failed to attach shaders and create a program"); + + glDeleteShader(vert); + glDeleteShader(frag); + + gl->pos = glGetAttribLocation(window->gl.program, "pos"); + gl->color = glGetAttribLocation(window->gl.program, "color"); + + glUseProgram(gl->program); +} + +static void +destroy_window(struct window *window) +{ + unsigned int i; + + if (window->callback) + wl_callback_destroy(window->callback); + + for (i = 0; i < NUM_BUFFERS; i++) + if (window->buffers[i].buffer) + buffer_free(&window->buffers[i]); + + if (window->xdg_toplevel) + xdg_toplevel_destroy(window->xdg_toplevel); + if (window->xdg_surface) + xdg_surface_destroy(window->xdg_surface); + + wl_surface_destroy(window->surface); + + close(window->card_fd); + + wl_array_release(&window->format.modifiers); + + dmabuf_feedback_fini(&window->dmabuf_feedback); + dmabuf_feedback_fini(&window->pending_dmabuf_feedback); + + free(window); +} + +static const struct zwp_linux_dmabuf_feedback_v1_listener dmabuf_feedback_listener; + +static struct window * +create_window(struct display *display) +{ + struct window *window; + uint32_t width = display->output.width; + uint32_t height = display->output.height; + unsigned int i; + + window = zalloc(sizeof *window); + assert(window && "error: failed to allocate memory for window"); + + window->display = display; + window->surface = wl_compositor_create_surface(display->compositor); + + dmabuf_feedback_init(&window->dmabuf_feedback); + dmabuf_feedback_init(&window->pending_dmabuf_feedback); + + wl_array_init(&window->format.modifiers); + + window->dmabuf_feedback_obj = + zwp_linux_dmabuf_v1_get_surface_feedback(display->dmabuf, + window->surface); + + zwp_linux_dmabuf_feedback_v1_add_listener(window->dmabuf_feedback_obj, + &dmabuf_feedback_listener, + window); + wl_display_roundtrip(display->display); + + assert(window->format.format == INITIAL_BUFFER_FORMAT && + "error: could not setup window->format based on dma-buf feedback"); + + gbm_setup(window); + egl_setup(window); + gl_setup(window); + + for (i = 0; i < NUM_BUFFERS; i++) + create_dmabuf_buffer(window, &window->buffers[i], width, height, + window->format.format, + window->format.modifiers.size / sizeof(uint64_t), + window->format.modifiers.data); + + + window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, + window->surface); + assert(window->xdg_surface && "error: could not get XDG surface"); + xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, + window); + + window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); + assert(window->xdg_toplevel && "error: could not get XDG toplevel"); + xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, + window); + + window->wait_for_configure = true; + wl_surface_commit(window->surface); + + wl_display_roundtrip(display->display); + + xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); + + assert(!window->wait_for_configure && + "error: could not configure XDG surface"); + + return window; +} + +static char * +get_most_appropriate_node(const char *drm_node, bool is_scanout_device) +{ + drmDevice **devices; + drmDevice *match = NULL; + char *appropriate_node = NULL; + int num_devices; + int i, j; + + num_devices = drmGetDevices2(0, NULL, 0); + assert(num_devices > 0 && "error: no drm devices available"); + + devices = zalloc(num_devices * sizeof(*devices)); + assert(devices && "error: failed to allocate memory for drm devices"); + + num_devices = drmGetDevices2(0, devices, num_devices); + assert(num_devices > 0 && "error: no drm devices available"); + + for (i = 0; i < num_devices && match == NULL; i++) { + for (j = 0; j < DRM_NODE_MAX && match == NULL; j++) { + if (!(devices[i]->available_nodes & (1 << j))) + continue; + if (strcmp(devices[i]->nodes[j], drm_node) == 0) + match = devices[i]; + } + } + assert(match != NULL && "error: could not find device on the list"); + assert(match->available_nodes & (1 << DRM_NODE_PRIMARY)); + + if (is_scanout_device) { + appropriate_node = strdup(match->nodes[DRM_NODE_PRIMARY]); + } else { + if (match->available_nodes & (1 << DRM_NODE_RENDER)) + appropriate_node = strdup(match->nodes[DRM_NODE_RENDER]); + else + appropriate_node = strdup(match->nodes[DRM_NODE_PRIMARY]); + } + assert(appropriate_node && "error: could not get drm node"); + + for (i = 0; i < num_devices; i++) + drmFreeDevice(&devices[i]); + free(devices); + + return appropriate_node; +} + +static char * +get_drm_node(dev_t device, bool is_scanout_device) +{ + struct udev *udev; + struct udev_device *udev_dev; + const char *drm_node; + + udev = udev_new(); + assert(udev && "error: failed to create udev context object"); + + udev_dev = udev_device_new_from_devnum(udev, 'c', device); + assert(udev_dev && "error: failed to create udev device"); + + drm_node = udev_device_get_devnode(udev_dev); + assert(drm_node && "error: failed to retrieve drm node"); + + udev_unref(udev); + + return get_most_appropriate_node(drm_node, is_scanout_device); +} + +static void +dmabuf_feedback_format_table(void *data, + struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, + int32_t fd, uint32_t size) +{ + struct window *window = data; + struct dmabuf_feedback *feedback = &window->pending_dmabuf_feedback; + + feedback->format_table.size = size; + feedback->format_table.data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); +} + +static void +dmabuf_feedback_main_device(void *data, + struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback, + struct wl_array *dev) +{ + struct window *window = data; + struct dmabuf_feedback *feedback = &window->pending_dmabuf_feedback; + char *drm_node; + + assert(dev->size == sizeof(feedback->main_device) && + "error: compositor didn't send a dev_t, size is wrong"); + memcpy(&feedback->main_device, dev->data, sizeof(dev)); + + drm_node = get_drm_node(feedback->main_device, false); + assert(drm_node && "error: failed to retrieve drm node"); + + fprintf(stderr, "compositor sent main_device event for dma-buf feedback - %s\n", + drm_node); + + if (!window->card_fd) { + window->card_fd = open(drm_node, O_RDWR | O_CLOEXEC); + assert(window->card_fd > 0 && "error: could not open card node"); + } + + free(drm_node); +} + +static void +dmabuf_feedback_tranche_target_device(void *data, + struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback, + struct wl_array *dev) +{ + struct window *window = data; + struct dmabuf_feedback *feedback = &window->pending_dmabuf_feedback; + + assert(dev->size == sizeof(feedback->pending_tranche.target_device) && + "error: compositor didn't send a dev_t, size is wrong"); + + memcpy(&feedback->pending_tranche.target_device, dev->data, sizeof(dev)); +} + +static void +dmabuf_feedback_tranche_flags(void *data, + struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback, + uint32_t flags) +{ + struct window *window = data; + struct dmabuf_feedback *feedback = &window->pending_dmabuf_feedback; + + if (flags & ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT) + feedback->pending_tranche.is_scanout_tranche = true; +} + +static void +dmabuf_feedback_tranche_formats(void *data, + struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback, + struct wl_array *indices) +{ + struct window *window = data; + struct dmabuf_feedback *feedback = &window->pending_dmabuf_feedback; + struct drm_format *fmt; + uint64_t modifier; + uint32_t format; + uint16_t *index; + + /* Compositor may advertise or not a format table. If it does, we use + * it. Otherwise, we steal the most recent advertised format table */ + if (feedback->format_table.data == NULL) { + feedback->format_table = window->dmabuf_feedback.format_table; + dmabuf_feedback_format_table_init(&window->dmabuf_feedback.format_table); + } + assert(feedback->format_table.data != NULL && + "error: compositor should advertise format table"); + assert(feedback->format_table.data != MAP_FAILED && + "error: we could not map format table advertised by compositor"); + + wl_array_for_each(index, indices) { + format = feedback->format_table.data[*index].format; + modifier = feedback->format_table.data[*index].modifier; + + fmt = drm_format_array_add_format(&feedback->pending_tranche.formats, + format); + drm_format_add_modifier(fmt, modifier); + } +} + +static void +print_dmabuf_feedback_tranche(struct dmabuf_feedback_tranche *tranche) +{ + char *drm_node; + struct drm_format *fmt; + uint64_t *mod; + + drm_node = get_drm_node(tranche->target_device, tranche->is_scanout_tranche); + assert(drm_node && "error: could not retrieve drm node"); + + fprintf(stderr, "├──────target_device for tranche - %s\n", drm_node); + fprintf(stderr, "│ └scanout tranche? %s\n", tranche->is_scanout_tranche ? "yes" : "no"); + + /* TODO: pretty print formats/modifiers when the following series lands: + * https://gitlab.freedesktop.org/mesa/drm/-/merge_requests/108 + */ + wl_array_for_each(fmt, &tranche->formats.arr) + wl_array_for_each(mod, &fmt->modifiers) + fprintf(stderr, "│ ├────────tranche format/modifier pair - " \ + "format %u, modifier %lu\n", fmt->format, *mod); + + fprintf(stderr, "│ └end of tranche\n"); +} + +static void +dmabuf_feedback_tranche_done(void *data, + struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback) +{ + struct window *window = data; + struct dmabuf_feedback *feedback = &window->pending_dmabuf_feedback; + struct dmabuf_feedback_tranche *tranche; + + print_dmabuf_feedback_tranche(&feedback->pending_tranche); + + tranche = wl_array_add(&feedback->tranches, sizeof(*tranche)); + assert(tranche && "error: could not allocate memory for tranche"); + + memcpy(tranche, &feedback->pending_tranche, sizeof(*tranche)); + + dmabuf_feedback_tranche_init(&feedback->pending_tranche); +} + +static void +pick_initial_format_from_renderer_tranche(struct window *window, + struct dmabuf_feedback_tranche *tranche) +{ + struct drm_format *fmt; + + wl_array_for_each(fmt, &tranche->formats.arr) { + /* Skip formats that are not the one we want to start with. */ + if (fmt->format != INITIAL_BUFFER_FORMAT) + continue; + + window->format.format = fmt->format; + wl_array_copy(&window->format.modifiers, &fmt->modifiers); + + return; + } + + assert(0 && "error: INITIAL_BUFFER_FORMAT not supported by the hardware"); +} + +static void +pick_format_from_scanout_tranche(struct window *window, + struct dmabuf_feedback_tranche *tranche) +{ + struct drm_format *fmt; + const struct pixel_format_info *format_info; + + wl_array_for_each(fmt, &tranche->formats.arr) { + + /* Ignore format that we're already using. */ + if (fmt->format == window->format.format) + continue; + + /* Format should be supported by the compositor. */ + format_info = pixel_format_get_info(fmt->format); + if (!format_info) + continue; + + wl_array_release(&window->format.modifiers); + wl_array_init(&window->format.modifiers); + + window->format.format = fmt->format; + wl_array_copy(&window->format.modifiers, &fmt->modifiers); + + return; + } + + assert(0 && "error: no valid pair of format/modifier in the scanout tranche"); +} + +static void +dmabuf_feedback_done(void *data, struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback) +{ + struct window *window = data; + struct dmabuf_feedback_tranche *tranche; + unsigned int i; + + fprintf(stderr, "└end of dma-buf feedback\n\n"); + + /* The first time that we receive dma-buf feedback for a surface it + * contains only the renderer tranche. We pick the INITIAL_BUFFER_FORMAT + * from there. Then the compositor should detect that the format is + * unsupported by the underlying hardware (not actually, but you should + * have faked this in the DRM-backend) and send the scanout tranche. We + * use the formats/modifiers of the scanout tranche to reallocate our + * buffers. */ + wl_array_for_each(tranche, &window->pending_dmabuf_feedback.tranches) { + if (tranche->is_scanout_tranche) { + pick_format_from_scanout_tranche(window, tranche); + for (i = 0; i < NUM_BUFFERS; i++) + window->buffers[i].recreate = true; + break; + } + pick_initial_format_from_renderer_tranche(window, tranche); + } + + dmabuf_feedback_fini(&window->dmabuf_feedback); + window->dmabuf_feedback = window->pending_dmabuf_feedback; + dmabuf_feedback_init(&window->pending_dmabuf_feedback); +} + +static const struct zwp_linux_dmabuf_feedback_v1_listener dmabuf_feedback_listener = { + .format_table = dmabuf_feedback_format_table, + .main_device = dmabuf_feedback_main_device, + .tranche_target_device = dmabuf_feedback_tranche_target_device, + .tranche_formats = dmabuf_feedback_tranche_formats, + .tranche_flags = dmabuf_feedback_tranche_flags, + .tranche_done = dmabuf_feedback_tranche_done, + .done = dmabuf_feedback_done, +}; + +static void +output_handle_geometry(void *data, struct wl_output *wl_output, int x, int y, + int physical_width, int physical_height, int subpixel, + const char *make, const char *model, int32_t transform) +{ + struct output *output = data; + + output->x = x; + output->y = y; +} + +static void +output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, + int width, int height, int refresh) +{ + struct output *output = data; + + if (flags & WL_OUTPUT_MODE_CURRENT) { + output->width = width; + output->height = height; + } +} + +static void +output_handle_scale(void *data, struct wl_output *wl_output, int scale) +{ + struct output *output = data; + + output->scale = scale; +} + +static void +output_handle_done(void *data, struct wl_output *wl_output) +{ + struct output *output = data; + + output->initialized = true; +} + +static const struct wl_output_listener output_listener = { + output_handle_geometry, + output_handle_mode, + output_handle_done, + output_handle_scale, +}; + +static void +xdg_wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial) +{ + xdg_wm_base_pong(wm_base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + xdg_wm_base_ping, +}; + +static void +registry_handle_global(void *data, struct wl_registry *registry, + uint32_t id, const char *interface, uint32_t version) +{ + struct display *d = data; + + if (strcmp(interface, "wl_compositor") == 0) { + d->compositor = wl_registry_bind(registry, id, + &wl_compositor_interface, + 1); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + d->wm_base = wl_registry_bind(registry, id, + &xdg_wm_base_interface, + 1); + xdg_wm_base_add_listener(d->wm_base, &xdg_wm_base_listener, d); + } else if (strcmp(interface, "wl_output") == 0) { + d->output.wl_output = wl_registry_bind(registry, id, + &wl_output_interface, + version); + wl_output_add_listener(d->output.wl_output, + &output_listener, &d->output); + } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { + if (version < ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION) + return; + d->dmabuf = wl_registry_bind(registry, id, + &zwp_linux_dmabuf_v1_interface, + version); + } +} + +static void +registry_handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static void +destroy_display(struct display *display) +{ + gbm_device_destroy(display->gbm_device); + + if (display->egl.context != EGL_NO_CONTEXT) + eglDestroyContext(display->egl.display, display->egl.context); + if (display->egl.display != EGL_NO_DISPLAY) + eglTerminate(display->egl.display); + + zwp_linux_dmabuf_v1_destroy(display->dmabuf); + xdg_wm_base_destroy(display->wm_base); + + wl_compositor_destroy(display->compositor); + wl_registry_destroy(display->registry); + + wl_display_flush(display->display); + wl_display_disconnect(display->display); + + free(display); +} + +static struct display * +create_display() +{ + struct display *display = NULL; + + display = zalloc(sizeof *display); + assert(display && "error: failed to allocate memory for display"); + + display->display = wl_display_connect(NULL); + assert(display->display && "error: could not connect to compositor"); + + display->registry = wl_display_get_registry(display->display); + assert(display->registry && "error: could not get registry"); + wl_registry_add_listener(display->registry, ®istry_listener, display); + + wl_display_roundtrip(display->display); + assert(display->compositor && "error: could not create compositor interface"); + assert(display->dmabuf && "error: dma-buf feedback is not supported by compositor"); + + wl_display_roundtrip(display->display); + assert(display->wm_base && "error: xdg shell is not supported by compositor"); + assert(display->output.initialized && "error: output not initialized"); + + return display; +} + +/* Simple client to test the dma-buf feedback implementation. This does not + * replace the need to implement a dma-buf feedback test that can be run in + * the CI. But as we still don't know exactly how to do this, this client + * can be helpful to run tests manually. + * + * In order to use this, we have to hack the DRM-backend to pretend that + * INITIAL_BUFFER_FORMAT is not supported by the planes of the underlying + * hardware. In Weston, we have to do this in + * drm_output_prepare_plane_view(), more specifically in the part where + * we call drm_output_plane_view_has_valid_format(). So we'd have something + * like this: + * + * // in this example, INITIAL_BUFFER_FORMAT == DRM_FORMAT_XRGB8888 + * + * bool fake_unsupported_format = false; + * if (fb && fb->format->format == DRM_FORMAT_XRGB8888) + * fake_unsupported_format = true; + * + * if (!drm_output_plane_view_has_valid_format(plane, state, ev, fb) || + * fake_unsupported_format) + * ... + * + * It creates a surface and buffers for it with the same resolution of the + * output mode in use. Also, it sets the surface to fullscreen. So we have + * everything set to allow the surface to be placed in a plane. But as + * these buffers are created with INITIAL_BUFFER_FORMAT, they are placed in + * the renderer. + * + * When the compositor creates the client surface, it adds only the + * renderer tranche to its dma-buf feedback object and send the feedback to + * the client. But as the repaint cycles start and Weston detects that the + * only reason why the surface has not been placed in a plane was the + * incompatibility between the framebuffer format and the ones supported by + * the planes of the underlying hardware, Weston adds a scanout tranche to + * the surface dma-buf feedback and resend them. In this tranche the client + * can find pairs of formats and modifiers supported by the planes, and so + * it can recreate its buffers using one of these pairs in order to + * increase the chances of its surface end up in a plane. */ +int +main(int argc, char **argv) +{ + struct display *display; + struct window *window; + int ret = 0; + + fprintf(stderr, "This client was written with the purpose of manually test " \ + "Weston's dma-buf feedback implementation. See main() " \ + "description for more details on how to test this.\n\n"); + + display = create_display(); + window = create_window(display); + + redraw(window, NULL, 0); + while (ret != -1 && window->n_redraws < 200) + ret = wl_display_dispatch(display->display); + + destroy_window(window); + destroy_display(display); + + return 0; +} diff --git a/meson_options.txt b/meson_options.txt index d404aef7..0aa86d1c 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -200,7 +200,7 @@ option( option( 'simple-clients', type: 'array', - choices: [ 'all', 'damage', 'im', 'egl', 'shm', 'touch', 'dmabuf-v4l', 'dmabuf-egl' ], + choices: [ 'all', 'damage', 'im', 'egl', 'shm', 'touch', 'dmabuf-feedback', 'dmabuf-v4l', 'dmabuf-egl' ], value: [ 'all' ], description: 'Sample clients: simple test programs' )