diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c238e433015f9b218cba0f2108fb7177b67e370c..db89c7becbe5822e4f6042c1f2cf6fd083efd142 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,7 +43,7 @@ variables: FDO_UPSTREAM_REPO: wayland/weston FDO_REPO_SUFFIX: "$BUILD_OS/$BUILD_ARCH" - FDO_DISTRIBUTION_TAG: '2021-11-25.2-bullseye-sphinx' + FDO_DISTRIBUTION_TAG: '2021-11-25.0-dmabuf-feedback' include: diff --git a/.gitlab-ci/build-deps.sh b/.gitlab-ci/build-deps.sh index 816a65859ec7a634b2f2b5fede24b68089c011ef..bce36e920da644b262fc490c99c416e628354ead 100755 --- a/.gitlab-ci/build-deps.sh +++ b/.gitlab-ci/build-deps.sh @@ -106,14 +106,12 @@ rm -rf wayland # Keep this version in sync with our dependency in meson.build. If you wish to # raise a MR against custom protocol, please change this reference to clone # your relevant tree, and make sure you bump $FDO_DISTRIBUTION_TAG. -git clone --branch 1.19 https://gitlab.freedesktop.org/wayland/wayland-protocols +git clone --branch 1.24 --depth=1 https://gitlab.freedesktop.org/wayland/wayland-protocols cd wayland-protocols git show -s HEAD -mkdir build -cd build -../autogen.sh -make install -cd ../../ +meson build +ninja ${NINJAFLAGS} -C build install +cd .. rm -rf wayland-protocols # Build and install our own version of Mesa. Debian provides a perfectly usable @@ -123,9 +121,8 @@ rm -rf wayland-protocols # features from Mesa then bump this version and $FDO_DISTRIBUTION_TAG, however # please be prepared for some of the tests to change output, which will need to # be manually inspected for correctness. -git clone --single-branch --branch 20.3 --shallow-since='2020-12-15' https://gitlab.freedesktop.org/mesa/mesa.git mesa +git clone --branch 21.3 --depth=1 https://gitlab.freedesktop.org/mesa/mesa.git cd mesa -git checkout -b snapshot mesa-20.3.1 meson build -Dauto_features=disabled \ -Dgallium-drivers=swrast -Dvulkan-drivers= -Ddri-drivers= ninja ${NINJAFLAGS} -C build install diff --git a/clients/meson.build b/clients/meson.build index aabd6767f30f1736de99e8daa86322ff5fd0d1d3..50caf7b3cfa48d3a33ed731624e2b0e4c3627126 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 0000000000000000000000000000000000000000..06f5415b59c651ae88c6ad637504ef011ac02313 --- /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/include/libweston/libweston.h b/include/libweston/libweston.h index 58da3022ac956e7ebf16228c17c053b494e679ad..abc2e736688aa8930cfd58932bee34db412e2037 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -1054,6 +1054,8 @@ struct weston_desktop_xwayland; struct weston_desktop_xwayland_interface; struct weston_debug_compositor; struct weston_color_manager; +struct weston_dmabuf_feedback; +struct weston_dmabuf_feedback_format_table; /** Main object, container-like structure which aggregates all other objects. * @@ -1129,6 +1131,9 @@ struct weston_compositor { struct weston_backend *backend; struct weston_launcher *launcher; + struct weston_dmabuf_feedback *default_dmabuf_feedback; + struct weston_dmabuf_feedback_format_table *dmabuf_feedback_format_table; + struct wl_list plugin_api_list; /* struct weston_plugin_api::link */ uint32_t output_id_pool; @@ -1538,6 +1543,8 @@ struct weston_surface { int acquire_fence_fd; struct weston_buffer_release_reference buffer_release_ref; + struct weston_dmabuf_feedback *dmabuf_feedback; + enum weston_hdcp_protection desired_protection; enum weston_hdcp_protection current_protection; enum weston_surface_protection_mode protection_mode; diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index 86c25105e7313cc2da9d6350cd91e5cb374612fe..1d3170c3deddf2c48f21b5f14fe75245535953d3 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -227,6 +227,27 @@ enum wdrm_crtc_property { WDRM_CRTC__COUNT }; +/** + * Reasons why placing a view on a plane failed. Needed by the dma-buf feedback. + */ +enum try_view_on_plane_failure_reasons { + FAILURE_REASONS_NONE = 0, + FAILURE_REASONS_FORCE_RENDERER = (1 << 0), + FAILURE_REASONS_FB_FORMAT_INCOMPATIBLE = (1 << 1), + FAILURE_REASONS_DMABUF_MODIFIER_INVALID = (1 << 2), + FAILURE_REASONS_ADD_FB_FAILED = (1 << 3), +}; + +/** + * We use this to keep track of actions we need to do with the dma-buf feedback + * in order to keep it up-to-date with the info we get from the DRM-backend. + */ +enum actions_needed_dmabuf_feedback { + ACTION_NEEDED_NONE = 0, + ACTION_NEEDED_ADD_SCANOUT_TRANCHE = (1 << 0), + ACTION_NEEDED_REMOVE_SCANOUT_TRANCHE = (1 << 1), +}; + struct drm_backend { struct weston_backend base; struct weston_compositor *compositor; @@ -694,13 +715,15 @@ drm_output_set_cursor_view(struct drm_output *output, struct weston_view *ev); #ifdef BUILD_DRM_GBM extern struct drm_fb * -drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev); +drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev, + uint32_t *try_view_on_plane_failure_reasons); extern bool drm_can_scanout_dmabuf(struct weston_compositor *ec, struct linux_dmabuf_buffer *dmabuf); #else static inline struct drm_fb * -drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev) +drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev, + uint32_t *try_view_on_plane_failure_reasons) { return NULL; } diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 480b13c2658392a532632387f5c647ca17013f43..5f6d6552fd7482fa9ce06ea9b588ddb70f342cf3 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -1669,6 +1669,65 @@ drm_output_deinit_planes(struct drm_output *output) output->scanout_plane = NULL; } +static struct weston_drm_format_array * +get_scanout_formats(struct drm_backend *b) +{ + struct weston_compositor *ec = b->compositor; + const struct weston_drm_format_array *renderer_formats; + struct weston_drm_format_array *scanout_formats, union_planes_formats; + struct drm_plane *plane; + int ret; + + /* If we got here it means that dma-buf feedback is supported and that + * the renderer has formats/modifiers to expose. */ + assert(ec->renderer->get_supported_formats != NULL); + renderer_formats = ec->renderer->get_supported_formats(ec); + + scanout_formats = zalloc(sizeof(*scanout_formats)); + if (!scanout_formats) { + weston_log("%s: out of memory\n", __func__); + return NULL; + } + + weston_drm_format_array_init(&union_planes_formats); + weston_drm_format_array_init(scanout_formats); + + /* Compute the union of the format/modifiers of the KMS planes */ + wl_list_for_each(plane, &b->plane_list, link) { + /* The scanout formats are used by the dma-buf feedback. But for + * now cursor planes do not support dma-buf buffers, only wl_shm + * buffers. So we skip cursor planes here. */ + if (plane->type == WDRM_PLANE_TYPE_CURSOR) + continue; + + ret = weston_drm_format_array_join(&union_planes_formats, + &plane->formats); + if (ret < 0) + goto err; + } + + /* Compute the intersection between the union of format/modifiers of KMS + * planes and the formats supported by the renderer */ + ret = weston_drm_format_array_replace(scanout_formats, + renderer_formats); + if (ret < 0) + goto err; + + ret = weston_drm_format_array_intersect(scanout_formats, + &union_planes_formats); + if (ret < 0) + goto err; + + weston_drm_format_array_fini(&union_planes_formats); + + return scanout_formats; + +err: + weston_drm_format_array_fini(&union_planes_formats); + weston_drm_format_array_fini(scanout_formats); + return NULL; +} + /** Pick a CRTC and reserve it for the output. * * On failure, the output remains without a CRTC. @@ -2917,6 +2976,7 @@ drm_backend_create(struct weston_compositor *compositor, struct wl_event_loop *loop; const char *seat_id = default_seat; const char *session_seat; + struct weston_drm_format_array *scanout_formats; drmModeRes *res; int ret; @@ -3082,6 +3142,21 @@ drm_backend_create(struct weston_compositor *compositor, if (linux_dmabuf_setup(compositor) < 0) weston_log("Error: initializing dmabuf " "support failed.\n"); + if (compositor->default_dmabuf_feedback) { + /* We were able to create the compositor's default + * dma-buf feedback in the renderer, that means that the + * table was already created and populated with + * renderer's format/modifier pairs. So now we must + * compute the scanout formats indices in the table */ + scanout_formats = get_scanout_formats(b); + if (!scanout_formats) + goto err_udev_monitor; + ret = weston_dmabuf_feedback_format_table_set_scanout_indices(compositor->dmabuf_feedback_format_table, + scanout_formats); + weston_drm_format_array_fini(scanout_formats); + if (ret < 0) + goto err_udev_monitor; + } if (weston_direct_display_setup(compositor) < 0) weston_log("Error: initializing direct-display " "support failed.\n"); diff --git a/libweston/backend-drm/fb.c b/libweston/backend-drm/fb.c index 1d35b4934b4b5e7f127fdf5f98775fafd5e31644..017422f20c6fa563df110e73e21d0e2a5a84da9d 100644 --- a/libweston/backend-drm/fb.c +++ b/libweston/backend-drm/fb.c @@ -220,7 +220,8 @@ drm_fb_destroy_dmabuf(struct drm_fb *fb) static struct drm_fb * drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, - struct drm_backend *backend, bool is_opaque) + struct drm_backend *backend, bool is_opaque, + uint32_t *try_view_on_plane_failure_reasons) { #ifndef HAVE_GBM_FD_IMPORT /* Importing a buffer to KMS requires explicit modifiers, so @@ -245,8 +246,12 @@ drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, * KMS driver can't know. So giving the buffer to KMS is not safe, as * not knowing its layout can result in garbage being displayed. In * short, importing a buffer to KMS requires explicit modifiers. */ - if (dmabuf->attributes.modifier[0] == DRM_FORMAT_MOD_INVALID) + if (dmabuf->attributes.modifier[0] == DRM_FORMAT_MOD_INVALID) { + if (try_view_on_plane_failure_reasons) + *try_view_on_plane_failure_reasons |= + FAILURE_REASONS_DMABUF_MODIFIER_INVALID; return NULL; + } /* XXX: TODO: * @@ -313,8 +318,12 @@ drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, fb->handles[i] = handle.u32; } - if (drm_fb_addfb(backend, fb) != 0) + if (drm_fb_addfb(backend, fb) != 0) { + if (try_view_on_plane_failure_reasons) + *try_view_on_plane_failure_reasons |= + FAILURE_REASONS_ADD_FB_FAILED; goto err_free; + } return fb; @@ -455,7 +464,7 @@ drm_can_scanout_dmabuf(struct weston_compositor *ec, struct drm_backend *b = to_drm_backend(ec); bool ret = false; - fb = drm_fb_get_from_dmabuf(dmabuf, b, true); + fb = drm_fb_get_from_dmabuf(dmabuf, b, true, NULL); if (fb) ret = true; @@ -466,7 +475,8 @@ drm_can_scanout_dmabuf(struct weston_compositor *ec, } struct drm_fb * -drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev) +drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev, + uint32_t *try_view_on_plane_failure_reasons) { struct drm_output *output = state->output; struct drm_backend *b = to_drm_backend(output->base.compositor); @@ -497,7 +507,8 @@ drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev) dmabuf = linux_dmabuf_buffer_get(buffer->resource); if (dmabuf) { - fb = drm_fb_get_from_dmabuf(dmabuf, b, is_opaque); + fb = drm_fb_get_from_dmabuf(dmabuf, b, is_opaque, + try_view_on_plane_failure_reasons); if (!fb) return NULL; } else { diff --git a/libweston/backend-drm/state-propose.c b/libweston/backend-drm/state-propose.c index ce168d1f6fdb2541b12452b1fef246d80112f756..247762f4b43f8f523346fdd9fd33d9dd1b2dd75b 100644 --- a/libweston/backend-drm/state-propose.c +++ b/libweston/backend-drm/state-propose.c @@ -649,12 +649,108 @@ drm_output_check_zpos_plane_states(struct drm_output_state *state) } } +static bool +dmabuf_feedback_maybe_update(struct drm_backend *b, struct weston_view *ev, + uint32_t try_view_on_plane_failure_reasons) +{ + struct weston_dmabuf_feedback *dmabuf_feedback = ev->surface->dmabuf_feedback; + struct weston_dmabuf_feedback_tranche *scanout_tranche; + dev_t scanout_dev = b->drm.devnum; + uint32_t scanout_flags = ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT; + uint32_t action_needed = ACTION_NEEDED_NONE; + struct timespec current_time, delta_time; + const time_t MAX_TIME_SECONDS = 2; + + /* Find out what we need to do with the dma-buf feedback */ + if (try_view_on_plane_failure_reasons & FAILURE_REASONS_FORCE_RENDERER) + action_needed |= ACTION_NEEDED_REMOVE_SCANOUT_TRANCHE; + if (try_view_on_plane_failure_reasons & + (FAILURE_REASONS_ADD_FB_FAILED | + FAILURE_REASONS_FB_FORMAT_INCOMPATIBLE | + FAILURE_REASONS_DMABUF_MODIFIER_INVALID)) + action_needed |= ACTION_NEEDED_ADD_SCANOUT_TRANCHE; + + assert(action_needed != (ACTION_NEEDED_REMOVE_SCANOUT_TRANCHE | + ACTION_NEEDED_ADD_SCANOUT_TRANCHE)); + + /* Look for scanout tranche. If not found, add it but in disabled mode + * (we still don't know if we'll have to send it to clients). This + * simplifies the code. */ + scanout_tranche = + weston_dmabuf_feedback_find_tranche(dmabuf_feedback, scanout_dev, + scanout_flags, SCANOUT_PREF); + if (!scanout_tranche) { + scanout_tranche = + weston_dmabuf_feedback_tranche_create(dmabuf_feedback, + b->compositor->dmabuf_feedback_format_table, + scanout_dev, scanout_flags, + SCANOUT_PREF); + scanout_tranche->active = false; + } + + /* No actions needed, so disarm timer and return */ + if (action_needed == ACTION_NEEDED_NONE || + (action_needed == ACTION_NEEDED_ADD_SCANOUT_TRANCHE && + scanout_tranche->active) || + (action_needed == ACTION_NEEDED_REMOVE_SCANOUT_TRANCHE && + !scanout_tranche->active)) { + dmabuf_feedback->action_needed = ACTION_NEEDED_NONE; + return false; + } + + /* We hit this if: + * + * 1. timer is still off, or + * 2. the action needed when it was set to on does not match the most + * recent needed action we've detected. + * + * So we reset the timestamp, set the timer to on it with the most + * recent needed action, return and leave the timer running. */ + if (dmabuf_feedback->action_needed == ACTION_NEEDED_NONE || + dmabuf_feedback->action_needed != action_needed) { + clock_gettime(CLOCK_MONOTONIC, &dmabuf_feedback->timer); + dmabuf_feedback->action_needed = action_needed; + return false; + /* Timer is already on and the action needed when it was set to on does + * not conflict with the most recent needed action we've detected. If + * more than MAX_TIME_SECONDS has passed, we need to resend the dma-buf + * feedback. Otherwise, return and leave the timer running. */ + } else { + clock_gettime(CLOCK_MONOTONIC, ¤t_time); + delta_time.tv_sec = current_time.tv_sec - + dmabuf_feedback->timer.tv_sec; + if (delta_time.tv_sec < MAX_TIME_SECONDS) + return false; + } + + /* If we got here it means that the timer has triggered, so we have + * pending actions with the dma-buf feedback. So we update and resend + * them. */ + if (action_needed == ACTION_NEEDED_ADD_SCANOUT_TRANCHE) + scanout_tranche->active = true; + else if (action_needed == ACTION_NEEDED_REMOVE_SCANOUT_TRANCHE) + scanout_tranche->active = false; + else + assert(0); + + drm_debug(b, "\t[repaint] Need to update and resend the " + "dma-buf feedback for surface of view %p\n", ev); + weston_dmabuf_feedback_send_all(dmabuf_feedback, + b->compositor->dmabuf_feedback_format_table); + + /* Set the timer to off */ + dmabuf_feedback->action_needed = ACTION_NEEDED_NONE; + + return true; +} + static struct drm_plane_state * drm_output_prepare_plane_view(struct drm_output_state *state, struct weston_view *ev, enum drm_output_propose_state_mode mode, struct drm_plane_state *scanout_state, - uint64_t current_lowest_zpos) + uint64_t current_lowest_zpos, + uint32_t *try_view_on_plane_failure_reasons) { struct drm_output *output = state->output; struct drm_backend *b = to_drm_backend(output->base.compositor); @@ -676,7 +772,7 @@ drm_output_prepare_plane_view(struct drm_output_state *state, buffer = ev->surface->buffer_ref.buffer; shmbuf = wl_shm_buffer_get(buffer->resource); - fb = drm_fb_get_from_view(state, ev); + fb = drm_fb_get_from_view(state, ev, try_view_on_plane_failure_reasons); /* assemble a list with possible candidates */ wl_list_for_each(plane, &b->plane_list, link) { @@ -730,6 +826,8 @@ drm_output_prepare_plane_view(struct drm_output_state *state, } if (!drm_output_plane_view_has_valid_format(plane, state, ev, fb)) { + *try_view_on_plane_failure_reasons |= + FAILURE_REASONS_FB_FORMAT_INCOMPATIBLE; drm_debug(b, "\t\t\t\t[plane] not adding plane %d to " "candidate list: invalid pixel format\n", plane->plane_id); @@ -960,7 +1058,18 @@ drm_output_propose_state(struct weston_output *output_base, current_lowest_zpos); ps = drm_output_prepare_plane_view(state, ev, mode, scanout_state, - current_lowest_zpos); + current_lowest_zpos, + &pnode->try_view_on_plane_failure_reasons); + /* If we were able to place the view in a plane, set + * failure reasons to none. */ + if (ps) + pnode->try_view_on_plane_failure_reasons = + FAILURE_REASONS_NONE; + } else { + /* We are forced to place the view in the renderer, set + * the failure reason accordingly. */ + pnode->try_view_on_plane_failure_reasons = + FAILURE_REASONS_FORCE_RENDERER; } if (ps) { @@ -1093,6 +1202,12 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) if (!(ev->output_mask & (1u << output->base.id))) continue; + /* Update dmabuf-feedback if needed */ + if (ev->surface->dmabuf_feedback) + dmabuf_feedback_maybe_update(b, ev, + pnode->try_view_on_plane_failure_reasons); + pnode->try_view_on_plane_failure_reasons = FAILURE_REASONS_NONE; + /* Test whether this buffer can ever go into a plane: * non-shm, or small enough to be a cursor. * diff --git a/libweston/compositor.c b/libweston/compositor.c index 42984f981ebf7e3b9cabc4d12b414f58085ee0e7..bcdcb0981d41d3f4634508a35973888bf5638394 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2313,6 +2313,9 @@ weston_surface_destroy(struct weston_surface *surface) assert(wl_list_empty(&surface->subsurface_list_pending)); assert(wl_list_empty(&surface->subsurface_list)); + if (surface->dmabuf_feedback) + weston_dmabuf_feedback_destroy(surface->dmabuf_feedback); + wl_list_for_each_safe(ev, nv, &surface->views, surface_link) weston_view_destroy(ev); @@ -4038,31 +4041,65 @@ static const struct wl_surface_interface surface_interface = { surface_damage_buffer }; +static int +create_surface_dmabuf_feedback(struct weston_compositor *ec, + struct weston_surface *surface) +{ + struct weston_dmabuf_feedback_tranche *tranche; + dev_t main_device = ec->default_dmabuf_feedback->main_device; + uint32_t flags = 0; + + surface->dmabuf_feedback = weston_dmabuf_feedback_create(main_device); + if (!surface->dmabuf_feedback) + return -1; + + tranche = weston_dmabuf_feedback_tranche_create(surface->dmabuf_feedback, + ec->dmabuf_feedback_format_table, + main_device, flags, + RENDERER_PREF); + if (!tranche) { + weston_dmabuf_feedback_destroy(surface->dmabuf_feedback); + surface->dmabuf_feedback = NULL; + return -1; + } + + return 0; +} + static void compositor_create_surface(struct wl_client *client, struct wl_resource *resource, uint32_t id) { struct weston_compositor *ec = wl_resource_get_user_data(resource); struct weston_surface *surface; + int ret; surface = weston_surface_create(ec); - if (surface == NULL) { - wl_resource_post_no_memory(resource); - return; + if (surface == NULL) + goto err; + + if (ec->default_dmabuf_feedback) { + ret = create_surface_dmabuf_feedback(ec, surface); + if (ret < 0) + goto err_dmabuf_feedback; } surface->resource = wl_resource_create(client, &wl_surface_interface, wl_resource_get_version(resource), id); - if (surface->resource == NULL) { - weston_surface_destroy(surface); - wl_resource_post_no_memory(resource); - return; - } + if (surface->resource == NULL) + goto err_dmabuf_feedback; wl_resource_set_implementation(surface->resource, &surface_interface, surface, destroy_surface); wl_signal_emit(&ec->create_surface_signal, surface); + + return; + +err_dmabuf_feedback: + weston_surface_destroy(surface); +err: + wl_resource_post_no_memory(resource); } static void @@ -8191,6 +8228,11 @@ weston_compositor_destroy(struct weston_compositor *compositor) weston_log_scope_destroy(compositor->timeline); compositor->timeline = NULL; + if (compositor->default_dmabuf_feedback) { + weston_dmabuf_feedback_destroy(compositor->default_dmabuf_feedback); + weston_dmabuf_feedback_format_table_destroy(compositor->dmabuf_feedback_format_table); + } + free(compositor); } diff --git a/libweston/drm-formats.c b/libweston/drm-formats.c index 491ecd9fdbfe49dd4c7f8484802fe11b4c9b57de..6bc08b5da06d9caa7c247c7893651b9876fc4d93 100644 --- a/libweston/drm-formats.c +++ b/libweston/drm-formats.c @@ -184,6 +184,24 @@ weston_drm_format_array_find_format(const struct weston_drm_format_array *format return NULL; } +/** + * Counts the number of format/modifier pairs in a weston_drm_format_array + * + * @param formats The weston_drm_format_array + * @return The number of format/modifier pairs in the array + */ +WL_EXPORT unsigned int +weston_drm_format_array_count_pairs(const struct weston_drm_format_array *formats) +{ + struct weston_drm_format *fmt; + unsigned int num_pairs = 0; + + wl_array_for_each(fmt, &formats->arr) + num_pairs += fmt->modifiers.size / sizeof(uint64_t); + + return num_pairs; +} + /** * Compare the content of two weston_drm_format_array * diff --git a/libweston/libweston-internal.h b/libweston/libweston-internal.h index ec6a59528bd66d3c67a91a5ad082f1e54f47d79f..7c30706f420569841c28f9d377f75ccd85f277fe 100644 --- a/libweston/libweston-internal.h +++ b/libweston/libweston-internal.h @@ -358,6 +358,9 @@ struct weston_drm_format * weston_drm_format_array_find_format(const struct weston_drm_format_array *formats, uint32_t format); +unsigned int +weston_drm_format_array_count_pairs(const struct weston_drm_format_array *formats); + bool weston_drm_format_array_equal(const struct weston_drm_format_array *formats_A, const struct weston_drm_format_array *formats_B); @@ -413,6 +416,8 @@ struct weston_paint_node { struct weston_surface_color_transform surf_xform; bool surf_xform_valid; + + uint32_t try_view_on_plane_failure_reasons; }; struct weston_paint_node * diff --git a/libweston/linux-dmabuf.c b/libweston/linux-dmabuf.c index 60373d39a6f20a7b7ffbcd04f33f3d7303487dd1..66702a4cba21ae363052db8c5bba413c54f012e3 100644 --- a/libweston/linux-dmabuf.c +++ b/libweston/linux-dmabuf.c @@ -26,13 +26,16 @@ #include "config.h" #include +#include #include #include +#include +#include #include #include #include "linux-dmabuf.h" -#include "linux-dmabuf-unstable-v1-server-protocol.h" +#include "shared/os-compatibility.h" #include "libweston-internal.h" #include "shared/weston-drm-fourcc.h" @@ -395,6 +398,489 @@ err_out: wl_resource_post_no_memory(linux_dmabuf_resource); } +/** Creates dma-buf feedback tranche + * + * The tranche is added to dma-buf feedback's tranche list + * + * @param dmabuf_feedback The dma-buf feedback object to which the tranche is added + * @param format_table The dma-buf feedback formats table + * @param target_device The target device of the new tranche + * @param flags The flags of the new tranche + * @param preference The preference of the new tranche + * @return The tranche created, or NULL on failure + */ +WL_EXPORT struct weston_dmabuf_feedback_tranche * +weston_dmabuf_feedback_tranche_create(struct weston_dmabuf_feedback *dmabuf_feedback, + struct weston_dmabuf_feedback_format_table *format_table, + dev_t target_device, uint32_t flags, + enum weston_dmabuf_feedback_tranche_preference preference) +{ + struct weston_dmabuf_feedback_tranche *tranche, *ptr; + struct wl_list *pos; + + tranche = zalloc(sizeof(*tranche)); + if (!tranche) { + weston_log("%s: out of memory\n", __func__); + return NULL; + } + + tranche->active = true; + tranche->target_device = target_device; + tranche->flags = flags; + tranche->preference = preference; + + /* Get the formats indices array */ + if (flags == 0) { + if (wl_array_copy(&tranche->formats_indices, + &format_table->renderer_formats_indices) < 0) { + weston_log("%s: out of memory\n", __func__); + goto err; + } + } else if (flags & ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT) { + if (wl_array_copy(&tranche->formats_indices, + &format_table->scanout_formats_indices) < 0) { + weston_log("%s: out of memory\n", __func__); + goto err; + } + } else { + weston_log("error: for now we just have renderer and scanout " + "tranches, can't create other type of tranche\n"); + goto err; + } + + /* The list of tranches is ordered by preference. + * Highest preference comes first. */ + pos = &dmabuf_feedback->tranche_list; + wl_list_for_each(ptr, &dmabuf_feedback->tranche_list, link) { + pos = &ptr->link; + if (ptr->preference <= tranche->preference) + break; + } + wl_list_insert(pos->prev, &tranche->link); + + return tranche; + +err: + free(tranche); + return NULL; +} + +static void +weston_dmabuf_feedback_tranche_destroy(struct weston_dmabuf_feedback_tranche *tranche) +{ + wl_array_release(&tranche->formats_indices); + wl_list_remove(&tranche->link); + free(tranche); +} + +static int +format_table_add_renderer_formats(struct weston_dmabuf_feedback_format_table *format_table, + const struct weston_drm_format_array *renderer_formats) +{ + struct weston_drm_format *fmt; + unsigned int num_modifiers; + const uint64_t *modifiers; + uint16_t index, *index_ptr; + unsigned int size; + unsigned int i; + + size = sizeof(index) * + weston_drm_format_array_count_pairs(renderer_formats); + + if (!wl_array_add(&format_table->renderer_formats_indices, size)) { + weston_log("%s: out of memory\n", __func__); + return -1; + } + + index = 0; + wl_array_for_each(fmt, &renderer_formats->arr) { + modifiers = weston_drm_format_get_modifiers(fmt, &num_modifiers); + for (i = 0; i < num_modifiers; i++) { + format_table->data[index].format = fmt->format; + format_table->data[index].modifier = modifiers[i]; + index++; + } + } + + index = 0; + wl_array_for_each(index_ptr, &format_table->renderer_formats_indices) + *index_ptr = index++; + + return 0; +} + +/** Creates dma-buf feedback format table + * + * @param renderer_formats The formats that compose the table + * @return The dma-buf feedback format table, or NULL on failure + */ +WL_EXPORT struct weston_dmabuf_feedback_format_table * +weston_dmabuf_feedback_format_table_create(const struct weston_drm_format_array *renderer_formats) +{ + struct weston_dmabuf_feedback_format_table *format_table; + int ret; + + format_table = zalloc(sizeof(*format_table)); + if (!format_table) { + weston_log("%s: out of memory\n", __func__); + return NULL; + } + wl_array_init(&format_table->renderer_formats_indices); + wl_array_init(&format_table->scanout_formats_indices); + + /* Creates formats file table and mmap it */ + format_table->size = weston_drm_format_array_count_pairs(renderer_formats) * + sizeof(*format_table->data); + format_table->fd = os_create_anonymous_file(format_table->size); + if (format_table->fd < 0) { + weston_log("error: failed to create format table file: %s\n", + strerror(errno)); + goto err_fd; + } + format_table->data = mmap(NULL, format_table->size, PROT_READ | PROT_WRITE, + MAP_SHARED, format_table->fd, 0); + if (format_table->data == MAP_FAILED) { + weston_log("error: mmap for format table failed: %s\n", + strerror(errno)); + goto err_mmap; + } + + /* Add renderer formats to file table */ + ret = format_table_add_renderer_formats(format_table, renderer_formats); + if (ret < 0) + goto err_formats; + + return format_table; + +err_formats: + munmap(format_table->data, format_table->size); +err_mmap: + close(format_table->fd); +err_fd: + wl_array_release(&format_table->renderer_formats_indices); + free(format_table); + return NULL; +} + +/** Destroys dma-buf feedback formats table + * + * @param format_table The dma-buf feedback format table to destroy + */ +WL_EXPORT void +weston_dmabuf_feedback_format_table_destroy(struct weston_dmabuf_feedback_format_table *format_table) +{ + wl_array_release(&format_table->renderer_formats_indices); + wl_array_release(&format_table->scanout_formats_indices); + + munmap(format_table->data, format_table->size); + close(format_table->fd); + + free(format_table); +} + +static int +format_table_get_format_index(struct weston_dmabuf_feedback_format_table *format_table, + uint32_t format, uint64_t modifier, uint16_t *index_out) +{ + uint16_t index; + unsigned int num_elements = format_table->size / sizeof(index); + + for (index = 0; index < num_elements; index++) { + if (format_table->data[index].format == format && + format_table->data[index].modifier == modifier) { + *index_out = index; + return 0; + } + } + + return -1; +} + +/** Set scanout formats indices in the dma-buf feedback format table + * + * The table consists of the formats supported by the renderer. A dma-buf + * feedback scanout tranche consists of the union of the KMS plane's formats + * intersected with the renderer formats. With this function we compute the + * indices of these plane's formats in the table and save them in the + * table->scanout_formats_indices, allowing us to create scanout tranches. + * + * @param format_table The dma-buf feedback format table + * @param scanout_formats The scanout formats + * @return 0 on success, -1 on failure + */ +WL_EXPORT int +weston_dmabuf_feedback_format_table_set_scanout_indices(struct weston_dmabuf_feedback_format_table *format_table, + const struct weston_drm_format_array *scanout_formats) +{ + struct weston_drm_format *fmt; + unsigned int num_modifiers; + const uint64_t *modifiers; + uint16_t index, *index_ptr; + unsigned int i; + int ret; + + wl_array_for_each(fmt, &scanout_formats->arr) { + modifiers = weston_drm_format_get_modifiers(fmt, &num_modifiers); + for (i = 0; i < num_modifiers; i++) { + index_ptr = + wl_array_add(&format_table->scanout_formats_indices, + sizeof(index)); + if (!index_ptr) + goto err; + + ret = format_table_get_format_index(format_table, fmt->format, + modifiers[i], &index); + if (ret < 0) + goto err; + + *index_ptr = index; + } + } + + return 0; + +err: + wl_array_release(&format_table->scanout_formats_indices); + wl_array_init(&format_table->scanout_formats_indices); + return -1; +} + +/** Creates dma-buf feedback object + * + * @param main_device The main device of the dma-buf feedback + * @return The dma-buf feedback object created, or NULL on failure + */ +WL_EXPORT struct weston_dmabuf_feedback * +weston_dmabuf_feedback_create(dev_t main_device) +{ + struct weston_dmabuf_feedback *dmabuf_feedback; + + dmabuf_feedback = zalloc(sizeof(*dmabuf_feedback)); + if (!dmabuf_feedback) { + weston_log("%s: out of memory\n", __func__); + return NULL; + } + + dmabuf_feedback->main_device = main_device; + wl_list_init(&dmabuf_feedback->tranche_list); + wl_list_init(&dmabuf_feedback->resource_list); + + return dmabuf_feedback; +} + +/** Destroy dma-buf feedback object + * + * @param dmabuf_feedback The dma-buf feedback object to destroy + */ +WL_EXPORT void +weston_dmabuf_feedback_destroy(struct weston_dmabuf_feedback *dmabuf_feedback) +{ + struct weston_dmabuf_feedback_tranche *tranche, *tranche_tmp; + struct wl_resource *res, *res_tmp; + + wl_list_for_each_safe(tranche, tranche_tmp, &dmabuf_feedback->tranche_list, link) + weston_dmabuf_feedback_tranche_destroy(tranche); + + wl_resource_for_each_safe(res, res_tmp, &dmabuf_feedback->resource_list) { + wl_list_remove(wl_resource_get_link(res)); + wl_list_init(wl_resource_get_link(res)); + } + + free(dmabuf_feedback); +} + +/** Find tranche in a dma-buf feedback object + * + * @param dmabuf_feedback The dma-buf feedback object where to look for + * @param target_device The target device of the tranche + * @param flags The flags of the tranche + * @param preference The preference of the tranche + * @return The tranche, or NULL if it was not found + */ +WL_EXPORT struct weston_dmabuf_feedback_tranche * +weston_dmabuf_feedback_find_tranche(struct weston_dmabuf_feedback *dmabuf_feedback, + dev_t target_device, uint32_t flags, + enum weston_dmabuf_feedback_tranche_preference preference) +{ + struct weston_dmabuf_feedback_tranche *tranche; + + wl_list_for_each(tranche, &dmabuf_feedback->tranche_list, link) + if (tranche->target_device == target_device && + tranche->flags == flags && tranche->preference == preference) + return tranche; + + return NULL; +} + +static void +weston_dmabuf_feedback_send(struct weston_dmabuf_feedback *dmabuf_feedback, + struct weston_dmabuf_feedback_format_table *format_table, + struct wl_resource *res, bool advertise_format_table) +{ + struct weston_dmabuf_feedback_tranche *tranche; + struct wl_array device; + dev_t *dev; + + /* main_device and target_device events need a dev_t as parameter, + * but we can't use this directly to communicate with the Wayland + * client. The solution is to use a wl_array, which is supported by + * Wayland, and add the dev_t as an element of the array. */ + wl_array_init(&device); + dev = wl_array_add(&device, sizeof(*dev)); + if (!dev) { + wl_resource_post_no_memory(res); + return; + } + + /* format_table event - In Weston, we never modify the dma-buf feedback + * format table. So we have this flag in order to advertise the format + * table only if the client has just subscribed to receive the events + * for this feedback object. When we need to re-send the feedback events + * for this client, the table event won't be sent. */ + if (advertise_format_table) + zwp_linux_dmabuf_feedback_v1_send_format_table(res, format_table->fd, + format_table->size); + + /* main_device event */ + *dev = dmabuf_feedback->main_device; + zwp_linux_dmabuf_feedback_v1_send_main_device(res, &device); + + /* send events for each tranche */ + wl_list_for_each(tranche, &dmabuf_feedback->tranche_list, link) { + if (!tranche->active) + continue; + + /* tranche_target_device event */ + *dev = tranche->target_device; + zwp_linux_dmabuf_feedback_v1_send_tranche_target_device(res, &device); + + /* tranche_flags event */ + zwp_linux_dmabuf_feedback_v1_send_tranche_flags(res, tranche->flags); + + /* tranche_formats event */ + zwp_linux_dmabuf_feedback_v1_send_tranche_formats(res, &tranche->formats_indices); + + /* tranche_done_event */ + zwp_linux_dmabuf_feedback_v1_send_tranche_done(res); + } + + /* compositor_done_event */ + zwp_linux_dmabuf_feedback_v1_send_done(res); + + wl_array_release(&device); +} + +/** Sends the feedback events for a dma-buf feedback object + * + * Given a dma-buf feedback object, this will send events to clients that are + * subscribed to it. This is useful for the per-surface dma-buf feedback, which + * is dynamic and can change throughout compositor's life. These changes results + * in the need to resend the feedback events to clients. + * + * @param dmabuf_feedback The weston_dmabuf_feedback object + * @param format_table The dma-buf feedback formats table + */ +WL_EXPORT void +weston_dmabuf_feedback_send_all(struct weston_dmabuf_feedback *dmabuf_feedback, + struct weston_dmabuf_feedback_format_table *format_table) +{ + struct wl_resource *res; + + wl_resource_for_each(res, &dmabuf_feedback->resource_list) + weston_dmabuf_feedback_send(dmabuf_feedback, + format_table, res, false); +} + +static void +dmabuf_feedback_resource_destroy(struct wl_resource *resource) +{ + wl_list_remove(wl_resource_get_link(resource)); +} + +static void +dmabuf_feedback_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct zwp_linux_dmabuf_feedback_v1_interface +zwp_linux_dmabuf_feedback_implementation = { + dmabuf_feedback_destroy +}; + +static struct wl_resource * +dmabuf_feedback_resource_create(struct wl_resource *dmabuf_resource, + struct wl_client *client, uint32_t dmabuf_feedback_id) +{ + struct wl_resource *dmabuf_feedback_res; + uint32_t version; + + version = wl_resource_get_version(dmabuf_resource); + + dmabuf_feedback_res = + wl_resource_create(client, &zwp_linux_dmabuf_feedback_v1_interface, + version, dmabuf_feedback_id); + if (!dmabuf_feedback_res) + return NULL; + + wl_list_init(wl_resource_get_link(dmabuf_feedback_res)); + wl_resource_set_implementation(dmabuf_feedback_res, + &zwp_linux_dmabuf_feedback_implementation, + NULL, dmabuf_feedback_resource_destroy); + + return dmabuf_feedback_res; +} + +static void +linux_dmabuf_get_default_feedback(struct wl_client *client, + struct wl_resource *dmabuf_resource, + uint32_t dmabuf_feedback_id) +{ + struct weston_compositor *compositor = + wl_resource_get_user_data(dmabuf_resource); + struct wl_resource *dmabuf_feedback_resource; + + dmabuf_feedback_resource = + dmabuf_feedback_resource_create(dmabuf_resource, + client, dmabuf_feedback_id); + if (!dmabuf_feedback_resource) { + wl_resource_post_no_memory(dmabuf_resource); + return; + } + + weston_dmabuf_feedback_send(compositor->default_dmabuf_feedback, + compositor->dmabuf_feedback_format_table, + dmabuf_feedback_resource, true); +} + +static void +linux_dmabuf_get_per_surface_feedback(struct wl_client *client, + struct wl_resource *dmabuf_resource, + uint32_t dmabuf_feedback_id, + struct wl_resource *surface_resource) +{ + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + struct wl_resource *dmabuf_feedback_resource; + + dmabuf_feedback_resource = + dmabuf_feedback_resource_create(dmabuf_resource, + client, dmabuf_feedback_id); + if (!dmabuf_feedback_resource) { + wl_resource_post_no_memory(dmabuf_resource); + return; + } + + /* Surface dma-buf feedback is dynamic and may need to be resent to + * clients when they change. So we need to keep the resources list */ + wl_list_insert(&surface->dmabuf_feedback->resource_list, + wl_resource_get_link(dmabuf_feedback_resource)); + + weston_dmabuf_feedback_send(surface->dmabuf_feedback, + surface->compositor->dmabuf_feedback_format_table, + dmabuf_feedback_resource, true); +} + /** Get the linux_dmabuf_buffer from a wl_buffer resource * * If the given wl_buffer resource was created through the linux_dmabuf @@ -469,7 +955,9 @@ linux_dmabuf_buffer_get_user_data(struct linux_dmabuf_buffer *buffer) static const struct zwp_linux_dmabuf_v1_interface linux_dmabuf_implementation = { linux_dmabuf_destroy, - linux_dmabuf_create_params + linux_dmabuf_create_params, + linux_dmabuf_get_default_feedback, + linux_dmabuf_get_per_surface_feedback }; static void @@ -494,8 +982,17 @@ bind_linux_dmabuf(struct wl_client *client, wl_resource_set_implementation(resource, &linux_dmabuf_implementation, compositor, NULL); - /* Advertise the formats/modifiers */ + /* Advertise formats/modifiers. From version 4 onwards, we should not send + * zwp_linux_dmabuf_v1_send_modifier and zwp_linux_dmabuf_v1_send_format + * events, instead we must send the dma-buf feedback events. */ + if (version >= 4) + return; + + /* If we got here, it means that the renderer is able to import dma-buf + * buffers, and so it must have get_supported_formats() set. */ + assert(compositor->renderer->get_supported_formats != NULL); supported_formats = compositor->renderer->get_supported_formats(compositor); + wl_array_for_each(fmt, &supported_formats->arr) { modifiers = weston_drm_format_get_modifiers(fmt, &num_modifiers); for (i = 0; i < num_modifiers; i++) { @@ -529,9 +1026,16 @@ bind_linux_dmabuf(struct wl_client *client, WL_EXPORT int linux_dmabuf_setup(struct weston_compositor *compositor) { + int max_version; + + /* If we were able to create the default dma-buf feedback for the + * compositor, that means that we are able to advertise dma-buf feedback + * events. In such case we support the version 4 of the protocol. */ + max_version = compositor->default_dmabuf_feedback ? 4 : 3; + if (!wl_global_create(compositor->wl_display, - &zwp_linux_dmabuf_v1_interface, 3, - compositor, bind_linux_dmabuf)) + &zwp_linux_dmabuf_v1_interface, + max_version, compositor, bind_linux_dmabuf)) return -1; return 0; diff --git a/libweston/linux-dmabuf.h b/libweston/linux-dmabuf.h index b89f03f16a22dbaa3b3584337e216ee627ad315b..7cae93c58ce5003bfa3fcef3a2b4f7d105d3fcc8 100644 --- a/libweston/linux-dmabuf.h +++ b/libweston/linux-dmabuf.h @@ -27,6 +27,7 @@ #define WESTON_LINUX_DMABUF_H #include +#include "linux-dmabuf-unstable-v1-server-protocol.h" #define MAX_DMABUF_PLANES 4 @@ -74,6 +75,76 @@ struct linux_dmabuf_buffer { bool direct_display; }; +enum weston_dmabuf_feedback_tranche_preference { + RENDERER_PREF = 0, + SCANOUT_PREF = 1 +}; + +struct weston_dmabuf_feedback_format_table { + int fd; + unsigned int size; + + /* This is a pointer to the region of memory where we mapped the file + * that clients receive. We fill it with the format/modifier pairs + * supported by the renderer. We don't formats not supported by the + * renderer in the table, as we must always be able to fallback to the + * renderer if direct scanout fails. */ + struct { + uint32_t format; + uint32_t pad; /* unused */ + uint64_t modifier; + } *data; + + /* Indices of the renderer formats in the table. As the table consists + * of formats supported by the renderer, this goes from 0 to the number + * of pairs in the table. */ + struct wl_array renderer_formats_indices; + /* Indices of the scanout formats (union of KMS plane's supported + * formats intersected with the renderer formats). */ + struct wl_array scanout_formats_indices; +}; + +struct weston_dmabuf_feedback { + /* We can have multiple clients subscribing to the same surface dma-buf + * feedback. As they are dynamic and we need to re-send them multiple + * times during Weston's lifetime, we need to keep track of the + * resources of each client. In the case of the default feedback this is + * not necessary, as we only advertise them when clients subscribe. IOW, + * default feedback events are never re-sent. */ + struct wl_list resource_list; + + dev_t main_device; + + /* weston_dmabuf_feedback_tranche::link */ + struct wl_list tranche_list; + + /* We use this timer to know if the scene has stabilized and that would + * be useful to resend dma-buf feedback events to clients. Consider the + * timer off when action_needed == ACTION_NEEDED_NONE. See enum + * actions_needed_dmabuf_feedback. */ + struct timespec timer; + uint32_t action_needed; +}; + +struct weston_dmabuf_feedback_tranche { + /* weston_dmabuf_feedback::tranche_list */ + struct wl_list link; + + /* Instead of destroying tranches and reconstructing them when necessary + * (it can be expensive), we have this flag to know if the tranche + * should be advertised or not. This is particularly useful for the + * scanout tranche, as based on the DRM-backend feedback and the current + * scene (which changes a lot during compositor lifetime) we can decide + * to send it or not. */ + bool active; + + dev_t target_device; + uint32_t flags; + enum weston_dmabuf_feedback_tranche_preference preference; + + struct wl_array formats_indices; +}; + int linux_dmabuf_setup(struct weston_compositor *compositor); @@ -94,4 +165,35 @@ void linux_dmabuf_buffer_send_server_error(struct linux_dmabuf_buffer *buffer, const char *msg); +struct weston_dmabuf_feedback * +weston_dmabuf_feedback_create(dev_t main_device); + +void +weston_dmabuf_feedback_destroy(struct weston_dmabuf_feedback *dmabuf_feedback); + +void +weston_dmabuf_feedback_send_all(struct weston_dmabuf_feedback *dmabuf_feedback, + struct weston_dmabuf_feedback_format_table *format_table); + +struct weston_dmabuf_feedback_tranche * +weston_dmabuf_feedback_find_tranche(struct weston_dmabuf_feedback *dmabuf_feedback, + dev_t target_device, uint32_t flags, + enum weston_dmabuf_feedback_tranche_preference preference); + +struct weston_dmabuf_feedback_format_table * +weston_dmabuf_feedback_format_table_create(const struct weston_drm_format_array *renderer_formats); + +void +weston_dmabuf_feedback_format_table_destroy(struct weston_dmabuf_feedback_format_table *format_table); + +int +weston_dmabuf_feedback_format_table_set_scanout_indices(struct weston_dmabuf_feedback_format_table *format_table, + const struct weston_drm_format_array *scanout_formats); + +struct weston_dmabuf_feedback_tranche * +weston_dmabuf_feedback_tranche_create(struct weston_dmabuf_feedback *dmabuf_feedback, + struct weston_dmabuf_feedback_format_table *format_table, + dev_t target_device, uint32_t flags, + enum weston_dmabuf_feedback_tranche_preference preference); + #endif /* WESTON_LINUX_DMABUF_H */ diff --git a/libweston/renderer-gl/egl-glue.c b/libweston/renderer-gl/egl-glue.c index 28be4ffe47ea33f0bace064432ae7f4c06609605..013172afd45a79e887ca98de7a4cc4051f017179 100644 --- a/libweston/renderer-gl/egl-glue.c +++ b/libweston/renderer-gl/egl-glue.c @@ -455,6 +455,45 @@ gl_renderer_get_egl_config(struct gl_renderer *gr, return egl_config; } +static void +gl_renderer_set_egl_device(struct gl_renderer *gr) +{ + EGLAttrib attrib; + const char *extensions; + + assert(gr->has_device_query); + + if (!gr->query_display_attrib(gr->egl_display, EGL_DEVICE_EXT, &attrib)) { + weston_log("failed to get EGL device\n"); + gl_renderer_print_egl_error_state(); + return; + } + + gr->egl_device = (EGLDeviceEXT) attrib; + + extensions = gr->query_device_string(gr->egl_device, EGL_EXTENSIONS); + if (!extensions) { + weston_log("failed to get EGL extensions\n"); + return; + } + + gl_renderer_log_extensions("EGL device extensions", extensions); + + /* Try to query the render node using EGL_DRM_RENDER_NODE_FILE_EXT */ + if (weston_check_egl_extension(extensions, "EGL_EXT_device_drm_render_node")) + gr->drm_device = gr->query_device_string(gr->egl_device, + EGL_DRM_RENDER_NODE_FILE_EXT); + + /* The extension is not supported by the Mesa version of the system or + * the query failed. Fallback to EGL_DRM_DEVICE_FILE_EXT */ + if (!gr->drm_device && weston_check_egl_extension(extensions, "EGL_EXT_device_drm")) + gr->drm_device = gr->query_device_string(gr->egl_device, + EGL_DRM_DEVICE_FILE_EXT); + + if (!gr->drm_device) + weston_log("failed to query DRM device from EGL\n"); +} + int gl_renderer_setup_egl_display(struct gl_renderer *gr, void *native_display) @@ -484,6 +523,9 @@ gl_renderer_setup_egl_display(struct gl_renderer *gr, goto fail; } + if (gr->has_device_query) + gl_renderer_set_egl_device(gr); + return 0; fail: @@ -534,6 +576,14 @@ gl_renderer_setup_egl_client_extensions(struct gl_renderer *gr) gl_renderer_log_extensions("EGL client extensions", extensions); + if (weston_check_egl_extension(extensions, "EGL_EXT_device_query")) { + gr->query_display_attrib = + (void *) eglGetProcAddress("eglQueryDisplayAttribEXT"); + gr->query_device_string = + (void *) eglGetProcAddress("eglQueryDeviceStringEXT"); + gr->has_device_query = true; + } + if (weston_check_egl_extension(extensions, "EGL_EXT_platform_base")) { gr->get_platform_display = (void *) eglGetProcAddress("eglGetPlatformDisplayEXT"); diff --git a/libweston/renderer-gl/gl-renderer-internal.h b/libweston/renderer-gl/gl-renderer-internal.h index 8dfdc9bf02ad3e4857f85ec16f3ea2471a2c845c..72101b472afb1f12410355480cf647a5a3220a0d 100644 --- a/libweston/renderer-gl/gl-renderer-internal.h +++ b/libweston/renderer-gl/gl-renderer-internal.h @@ -119,6 +119,9 @@ struct gl_renderer { struct wl_array vertices; struct wl_array vtxcnt; + EGLDeviceEXT egl_device; + const char *drm_device; + struct weston_drm_format_array supported_formats; PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d; @@ -163,6 +166,10 @@ struct gl_renderer { PFNEGLQUERYDMABUFFORMATSEXTPROC query_dmabuf_formats; PFNEGLQUERYDMABUFMODIFIERSEXTPROC query_dmabuf_modifiers; + bool has_device_query; + PFNEGLQUERYDISPLAYATTRIBEXTPROC query_display_attrib; + PFNEGLQUERYDEVICESTRINGEXTPROC query_device_string; + bool has_native_fence_sync; PFNEGLCREATESYNCKHRPROC create_sync; PFNEGLDESTROYSYNCKHRPROC destroy_sync; diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index b178f56b99f3b793f80f9c714dd03492cd3b0701..a5f5eae44f83fe4488380f35b4d13fec22cffa16 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -3611,6 +3612,38 @@ gl_renderer_create_pbuffer_surface(struct gl_renderer *gr) { return 0; } +static int +create_default_dmabuf_feedback(struct weston_compositor *ec, + struct gl_renderer *gr) +{ + struct stat dev_stat; + struct weston_dmabuf_feedback_tranche *tranche; + uint32_t flags = 0; + + if (stat(gr->drm_device, &dev_stat) != 0) { + weston_log("%s: device disappeared, so we can't recover\n", __func__); + abort(); + } + + ec->default_dmabuf_feedback = + weston_dmabuf_feedback_create(dev_stat.st_rdev); + if (!ec->default_dmabuf_feedback) + return -1; + + tranche = + weston_dmabuf_feedback_tranche_create(ec->default_dmabuf_feedback, + ec->dmabuf_feedback_format_table, + dev_stat.st_rdev, flags, + RENDERER_PREF); + if (!tranche) { + weston_dmabuf_feedback_destroy(ec->default_dmabuf_feedback); + ec->default_dmabuf_feedback = NULL; + return -1; + } + + return 0; +} + static int gl_renderer_display_create(struct weston_compositor *ec, const struct gl_renderer_display_options *options) @@ -3685,6 +3718,17 @@ gl_renderer_display_create(struct weston_compositor *ec, ret = populate_supported_formats(ec, &gr->supported_formats); if (ret < 0) goto fail_terminate; + if (gr->drm_device) { + /* We support dma-buf feedback only when the renderer + * exposes a DRM-device */ + ec->dmabuf_feedback_format_table = + weston_dmabuf_feedback_format_table_create(&gr->supported_formats); + if (!ec->dmabuf_feedback_format_table) + goto fail_terminate; + ret = create_default_dmabuf_feedback(ec, gr); + if (ret < 0) + goto fail_feedback; + } } wl_list_init(&gr->dmabuf_formats); @@ -3731,6 +3775,15 @@ gl_renderer_display_create(struct weston_compositor *ec, fail_with_error: gl_renderer_print_egl_error_state(); + if (gr->drm_device) { + weston_dmabuf_feedback_destroy(ec->default_dmabuf_feedback); + ec->default_dmabuf_feedback = NULL; + } +fail_feedback: + if (gr->drm_device) { + weston_dmabuf_feedback_format_table_destroy(ec->dmabuf_feedback_format_table); + ec->dmabuf_feedback_format_table = NULL; + } fail_terminate: weston_drm_format_array_fini(&gr->supported_formats); eglTerminate(gr->egl_display); diff --git a/meson_options.txt b/meson_options.txt index d404aef7239c1d521b0c772cb797e0b7b9a5e3bc..0aa86d1c1742c360750964c0c5dd8aa2b324ed09 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' ) diff --git a/protocol/meson.build b/protocol/meson.build index e0b5b7ac08d548fdf6bc36c952ddffa199cdbfa5..7d869dadf2994eaf3b717346606e21d8acae24b7 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -1,7 +1,7 @@ dep_scanner = dependency('wayland-scanner', native: true) prog_scanner = find_program(dep_scanner.get_pkgconfig_variable('wayland_scanner')) -dep_wp = dependency('wayland-protocols', version: '>= 1.19') +dep_wp = dependency('wayland-protocols', version: '>= 1.24') dir_wp_base = dep_wp.get_pkgconfig_variable('pkgdatadir') install_data( diff --git a/shared/weston-egl-ext.h b/shared/weston-egl-ext.h index ac308ba43f6cd8f8ad1b4f915c03e609334fd366..822810f4f0cf3c9cbba51e02a8932ac7104200c8 100644 --- a/shared/weston-egl-ext.h +++ b/shared/weston-egl-ext.h @@ -143,6 +143,38 @@ typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFFORMATSEXTPROC) (EGLDisplay dp typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFMODIFIERSEXTPROC) (EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR *modifiers, EGLBoolean *external_only, EGLint *num_modifiers); #endif +/* Define tokens from EGL_EXT_device_base */ +#ifndef EGL_EXT_device_base +#define EGL_EXT_device_base 1 +typedef void *EGLDeviceEXT; +#define EGL_NO_DEVICE_EXT EGL_CAST(EGLDeviceEXT,0) +#define EGL_BAD_DEVICE_EXT 0x322B +#define EGL_DEVICE_EXT 0x322C +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDEVICEATTRIBEXTPROC) (EGLDeviceEXT device, EGLint attribute, EGLAttrib *value); +typedef const char *(EGLAPIENTRYP PFNEGLQUERYDEVICESTRINGEXTPROC) (EGLDeviceEXT device, EGLint name); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDEVICESEXTPROC) (EGLint max_devices, EGLDeviceEXT *devices, EGLint *num_devices); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDISPLAYATTRIBEXTPROC) (EGLDisplay dpy, EGLint attribute, EGLAttrib *value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDeviceAttribEXT (EGLDeviceEXT device, EGLint attribute, EGLAttrib *value); +EGLAPI const char *EGLAPIENTRY eglQueryDeviceStringEXT (EGLDeviceEXT device, EGLint name); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDevicesEXT (EGLint max_devices, EGLDeviceEXT *devices, EGLint *num_devices); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDisplayAttribEXT (EGLDisplay dpy, EGLint attribute, EGLAttrib *value); +#endif +#endif /* EGL_EXT_device_base */ + +/* Define tokens from EGL_EXT_device_drm */ +#ifndef EGL_EXT_device_drm +#define EGL_EXT_device_drm 1 +#define EGL_DRM_DEVICE_FILE_EXT 0x3233 +#define EGL_DRM_MASTER_FD_EXT 0x333C +#endif /* EGL_EXT_device_drm */ + +/* Define tokens from EGL_EXT_device_drm_render_node */ +#ifndef EGL_EXT_device_drm_render_node +#define EGL_EXT_device_drm_render_node 1 +#define EGL_DRM_RENDER_NODE_FILE_EXT 0x3377 +#endif /* EGL_EXT_device_drm_render_node */ + #ifndef EGL_EXT_swap_buffers_with_damage #define EGL_EXT_swap_buffers_with_damage 1 typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC) (EGLDisplay dpy, EGLSurface surface, EGLint *rects, EGLint n_rects); diff --git a/tests/drm-formats-test.c b/tests/drm-formats-test.c index cfbf3cd8e69778f52cb07ff56b7caecb8e205c42..b4e4a17aa7564d24825213b369208c8f2a0522ae 100644 --- a/tests/drm-formats-test.c +++ b/tests/drm-formats-test.c @@ -81,6 +81,8 @@ TEST(basic_operations) weston_drm_format_array_init(&format_array); + assert(weston_drm_format_array_count_pairs(&format_array) == 0); + ADD_FORMATS_AND_MODS(&format_array, formats, modifiers); for (i = 0; i < ARRAY_LENGTH(formats); i++) { @@ -90,6 +92,9 @@ TEST(basic_operations) assert(weston_drm_format_has_modifier(fmt, modifiers[j])); } + assert(weston_drm_format_array_count_pairs(&format_array) == + ARRAY_LENGTH(formats) * ARRAY_LENGTH(modifiers)); + weston_drm_format_array_fini(&format_array); }