diff --git a/include/rootston/desktop.h b/include/rootston/desktop.h index 467de8ab..db8a088e 100644 --- a/include/rootston/desktop.h +++ b/include/rootston/desktop.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,7 @@ struct roots_desktop { struct wlr_primary_selection_device_manager *primary_selection_device_manager; struct wlr_idle *idle; struct wlr_idle_inhibit_manager_v1 *idle_inhibit; + struct wlr_linux_dmabuf *linux_dmabuf; struct wl_listener new_output; struct wl_listener layout_change; diff --git a/include/wlr/render.h b/include/wlr/render.h index 747603da..9080175f 100644 --- a/include/wlr/render.h +++ b/include/wlr/render.h @@ -85,6 +85,7 @@ struct wlr_texture { bool valid; uint32_t format; int width, height; + bool inverted_y; struct wl_signal destroy_signal; struct wl_resource *resource; }; @@ -122,6 +123,8 @@ bool wlr_texture_upload_drm(struct wlr_texture *tex, bool wlr_texture_upload_eglimage(struct wlr_texture *tex, EGLImageKHR image, uint32_t width, uint32_t height); +bool wlr_texture_upload_dmabuf(struct wlr_texture *tex, + struct wl_resource *dmabuf_resource); /** * Copies a rectangle of pixels from a wl_shm_buffer onto the texture. The * buffer is not accessed after this function returns. Under some circumstances, diff --git a/include/wlr/render/egl.h b/include/wlr/render/egl.h index 97a28016..f05a9837 100644 --- a/include/wlr/render/egl.h +++ b/include/wlr/render/egl.h @@ -6,6 +6,7 @@ #include #include #include +#include struct wlr_egl { EGLDisplay display; @@ -18,6 +19,8 @@ struct wlr_egl { struct { bool buffer_age; bool swap_buffers_with_damage; + bool dmabuf_import; + bool dmabuf_import_modifiers; } egl_exts; struct wl_display *wl_display; @@ -61,6 +64,31 @@ EGLSurface wlr_egl_create_surface(struct wlr_egl *egl, void *window); EGLImageKHR wlr_egl_create_image(struct wlr_egl *egl, EGLenum target, EGLClientBuffer buffer, const EGLint *attribs); +/** + * Creates an egl image from the given dmabuf attributes. Check usability + * of the dmabuf with wlr_egl_check_import_dmabuf once first. + */ +EGLImageKHR wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl, + struct wlr_dmabuf_buffer_attribs *attributes); + +/** + * Try to import the given dmabuf. On success return true false otherwise. + * If this succeeds the dmabuf can be used for rendering on a texture + */ +bool wlr_egl_check_import_dmabuf(struct wlr_egl *egl, + struct wlr_dmabuf_buffer *dmabuf); + +/** + * Get the available dmabuf formats + */ +int wlr_egl_get_dmabuf_formats(struct wlr_egl *egl, int **formats); + +/** + * Get the available dmabuf modifiers for a given format + */ +int wlr_egl_get_dmabuf_modifiers(struct wlr_egl *egl, int format, + uint64_t **modifiers); + /** * Destroys an egl image created with the given wlr_egl. */ diff --git a/include/wlr/render/interface.h b/include/wlr/render/interface.h index eda5af1c..2ba584d9 100644 --- a/include/wlr/render/interface.h +++ b/include/wlr/render/interface.h @@ -8,6 +8,7 @@ #include #include #include +#include struct wlr_renderer_impl; @@ -58,6 +59,8 @@ struct wlr_texture_impl { struct wl_resource *drm_buf); bool (*upload_eglimage)(struct wlr_texture *texture, EGLImageKHR image, uint32_t width, uint32_t height); + bool (*upload_dmabuf)(struct wlr_texture *texture, + struct wl_resource *dmabuf_resource); void (*get_matrix)(struct wlr_texture *state, float (*matrix)[16], const float (*projection)[16], int x, int y); void (*get_buffer_size)(struct wlr_texture *texture, diff --git a/include/wlr/types/wlr_linux_dmabuf.h b/include/wlr/types/wlr_linux_dmabuf.h new file mode 100644 index 00000000..9d71e598 --- /dev/null +++ b/include/wlr/types/wlr_linux_dmabuf.h @@ -0,0 +1,84 @@ +#ifndef WLR_TYPES_WLR_LINUX_DMABUF_H +#define WLR_TYPES_WLR_LINUX_DMABUF_H + +#define WLR_LINUX_DMABUF_MAX_PLANES 4 + +#include +#include + +/* So we don't have to pull in linux specific drm headers */ +#ifndef DRM_FORMAT_MOD_INVALID +#define DRM_FORMAT_MOD_INVALID ((1ULL<<56) - 1) +#endif + +struct wlr_dmabuf_buffer_attribs { + /* set via params_add */ + int n_planes; + uint32_t offset[WLR_LINUX_DMABUF_MAX_PLANES]; + uint32_t stride[WLR_LINUX_DMABUF_MAX_PLANES]; + uint64_t modifier[WLR_LINUX_DMABUF_MAX_PLANES]; + int fd[WLR_LINUX_DMABUF_MAX_PLANES]; + /* set via params_create */ + int32_t width; + int32_t height; + uint32_t format; + uint32_t flags; /* enum zlinux_buffer_params_flags */ +}; + +struct wlr_dmabuf_buffer { + struct wlr_egl *egl; + struct wl_resource *buffer_resource; + struct wl_resource *params_resource; + struct wlr_dmabuf_buffer_attribs attributes; +}; + +/** + * Returns true if the given resource was created via the linux-dmabuf + * buffer protocol, false otherwise + */ +bool wlr_dmabuf_resource_is_buffer(struct wl_resource *buffer_resource); + +/** + * Returns the wlr_dmabuf_buffer if the given resource was created + * via the linux-dmabuf buffer protocol + */ +struct wlr_dmabuf_buffer *wlr_dmabuf_buffer_from_buffer_resource( + struct wl_resource *buffer_resource); + +/** + * Returns the wlr_dmabuf_buffer if the given resource was created + * via the linux-dmabuf params protocol + */ +struct wlr_dmabuf_buffer *wlr_dmabuf_buffer_from_params_resource( + struct wl_resource *params_resource); + +/** + * Returns true if the given dmabuf has y-axis inverted, false otherwise + */ +bool wlr_dmabuf_buffer_has_inverted_y(struct wlr_dmabuf_buffer *dmabuf); + +/* the protocol interface */ +struct wlr_linux_dmabuf { + struct wl_global *wl_global; + struct wl_listener display_destroy; + struct wlr_egl *egl; +}; + +/** + * Create linux-dmabuf interface + */ +struct wlr_linux_dmabuf *wlr_linux_dmabuf_create(struct wl_display *display, + struct wlr_egl *egl); +/** + * Destroy the linux-dmabuf interface + */ +void wlr_linux_dmabuf_destroy(struct wlr_linux_dmabuf *linux_dmabuf); + +/** + * Returns the wlr_linux_dmabuf if the given resource was created + * via the linux_dmabuf protocol + */ +struct wlr_linux_dmabuf *wlr_linux_dmabuf_from_resource( + struct wl_resource *resource); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index 6c87a887..638b0c46 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -21,9 +21,10 @@ wayland_scanner_client = generator( ) protocols = [ - [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'], [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], + [wl_protocol_dir, 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml'], + [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'], 'gamma-control.xml', 'gtk-primary-selection.xml', 'idle.xml', diff --git a/render/egl.c b/render/egl.c index 0a68d6e5..55809983 100644 --- a/render/egl.c +++ b/render/egl.c @@ -167,6 +167,12 @@ bool wlr_egl_init(struct wlr_egl *egl, EGLenum platform, void *remote_display, check_egl_ext(egl->egl_exts_str, "EGL_EXT_swap_buffers_with_damage") || check_egl_ext(egl->egl_exts_str, "EGL_KHR_swap_buffers_with_damage"); + egl->egl_exts.dmabuf_import = + check_egl_ext(egl->egl_exts_str, "EGL_EXT_image_dma_buf_import"); + egl->egl_exts.dmabuf_import_modifiers = + check_egl_ext(egl->egl_exts_str, "EGL_EXT_image_dma_buf_import_modifiers") + && eglQueryDmaBufFormatsEXT && eglQueryDmaBufModifiersEXT; + return true; error: @@ -299,3 +305,132 @@ bool wlr_egl_swap_buffers(struct wlr_egl *egl, EGLSurface surface, } return true; } + +EGLImage wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl, + struct wlr_dmabuf_buffer_attribs *attributes) { + int atti = 0; + EGLint attribs[20]; + attribs[atti++] = EGL_WIDTH; + attribs[atti++] = attributes->width; + attribs[atti++] = EGL_HEIGHT; + attribs[atti++] = attributes->height; + attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; + attribs[atti++] = attributes->format; + + bool has_modifier = false; + if (attributes->modifier[0] != DRM_FORMAT_MOD_INVALID) { + if (!egl->egl_exts.dmabuf_import_modifiers) { + return NULL; + } + has_modifier = true; + } + + /* TODO: YUV planes have up four planes but we only support a + single EGLImage for now */ + if (attributes->n_planes > 1) { + return NULL; + } + + attribs[atti++] = EGL_DMA_BUF_PLANE0_FD_EXT; + attribs[atti++] = attributes->fd[0]; + attribs[atti++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT; + attribs[atti++] = attributes->offset[0]; + attribs[atti++] = EGL_DMA_BUF_PLANE0_PITCH_EXT; + attribs[atti++] = attributes->stride[0]; + if (has_modifier) { + attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT; + attribs[atti++] = attributes->modifier[0] & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT; + attribs[atti++] = attributes->modifier[0] >> 32; + } + attribs[atti++] = EGL_NONE; + return eglCreateImageKHR(egl->display, EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, NULL, attribs); +} + +#ifndef DRM_FORMAT_BIG_ENDIAN +# define DRM_FORMAT_BIG_ENDIAN 0x80000000 +#endif +bool wlr_egl_check_import_dmabuf(struct wlr_egl *egl, + struct wlr_dmabuf_buffer *dmabuf) { + switch (dmabuf->attributes.format & ~DRM_FORMAT_BIG_ENDIAN) { + /* YUV based formats not yet supported */ + case WL_SHM_FORMAT_YUYV: + case WL_SHM_FORMAT_YVYU: + case WL_SHM_FORMAT_UYVY: + case WL_SHM_FORMAT_VYUY: + case WL_SHM_FORMAT_AYUV: + return false; + default: + break; + } + + EGLImage egl_image = wlr_egl_create_image_from_dmabuf(egl, + &dmabuf->attributes); + if (egl_image) { + /* We can import the image, good. No need to keep it + since wlr_texture_upload_dmabuf will import it again */ + wlr_egl_destroy_image(egl, egl_image); + return true; + } + /* TODO: import yuv dmabufs */ + return false; +} + +int wlr_egl_get_dmabuf_formats(struct wlr_egl *egl, + int **formats) { + if (!egl->egl_exts.dmabuf_import || + !egl->egl_exts.dmabuf_import_modifiers) { + wlr_log(L_ERROR, "dmabuf extension not present"); + return -1; + } + + EGLint num; + if (!eglQueryDmaBufFormatsEXT(egl->display, 0, NULL, &num)) { + wlr_log(L_ERROR, "failed to query number of dmabuf formats"); + return -1; + } + + *formats = calloc(num, sizeof(int)); + if (*formats == NULL) { + wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno)); + return -1; + } + + if (!eglQueryDmaBufFormatsEXT(egl->display, num, *formats, &num)) { + wlr_log(L_ERROR, "failed to query dmabuf format"); + free(*formats); + return -1; + } + return num; +} + +int wlr_egl_get_dmabuf_modifiers(struct wlr_egl *egl, + int format, uint64_t **modifiers) { + if (!egl->egl_exts.dmabuf_import || + !egl->egl_exts.dmabuf_import_modifiers) { + wlr_log(L_ERROR, "dmabuf extension not present"); + return -1; + } + + EGLint num; + if (!eglQueryDmaBufModifiersEXT(egl->display, format, 0, + NULL, NULL, &num)) { + wlr_log(L_ERROR, "failed to query dmabuf number of modifiers"); + return -1; + } + + *modifiers = calloc(num, sizeof(uint64_t)); + if (*modifiers == NULL) { + wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno)); + return -1; + } + + if (!eglQueryDmaBufModifiersEXT(egl->display, format, num, + *modifiers, NULL, &num)) { + wlr_log(L_ERROR, "failed to query dmabuf modifiers"); + free(*modifiers); + return -1; + } + return num; +} diff --git a/render/glapi.txt b/render/glapi.txt index 0b0b452c..02ac7dd8 100644 --- a/render/glapi.txt +++ b/render/glapi.txt @@ -8,3 +8,5 @@ eglCreatePlatformWindowSurfaceEXT -glEGLImageTargetTexture2DOES -eglSwapBuffersWithDamageEXT -eglSwapBuffersWithDamageKHR +-eglQueryDmaBufFormatsEXT +-eglQueryDmaBufModifiersEXT diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c index ad739cf8..ba03f599 100644 --- a/render/gles2/renderer.c +++ b/render/gles2/renderer.c @@ -179,7 +179,8 @@ static bool wlr_gles2_render_texture(struct wlr_renderer *wlr_renderer, wlr_texture_bind(texture); GL_CALL(glUniformMatrix4fv(0, 1, GL_FALSE, *matrix)); - GL_CALL(glUniform1f(2, alpha)); + GL_CALL(glUniform1i(1, texture->inverted_y)); + GL_CALL(glUniform1f(3, alpha)); draw_quad(); return true; } diff --git a/render/gles2/shaders.c b/render/gles2/shaders.c index 46a10248..c8ba2ae6 100644 --- a/render/gles2/shaders.c +++ b/render/gles2/shaders.c @@ -10,17 +10,17 @@ const GLchar quad_vertex_src[] = "varying vec4 v_color;" "varying vec2 v_texcoord;" "mat4 transpose(in mat4 inMatrix) {" -" vec4 i0 = inMatrix[0];" -" vec4 i1 = inMatrix[1];" -" vec4 i2 = inMatrix[2];" -" vec4 i3 = inMatrix[3];" -" mat4 outMatrix = mat4(" -" vec4(i0.x, i1.x, i2.x, i3.x)," -" vec4(i0.y, i1.y, i2.y, i3.y)," -" vec4(i0.z, i1.z, i2.z, i3.z)," -" vec4(i0.w, i1.w, i2.w, i3.w)" -" );" -" return outMatrix;" +" vec4 i0 = inMatrix[0];" +" vec4 i1 = inMatrix[1];" +" vec4 i2 = inMatrix[2];" +" vec4 i3 = inMatrix[3];" +" mat4 outMatrix = mat4(" +" vec4(i0.x, i1.x, i2.x, i3.x)," +" vec4(i0.y, i1.y, i2.y, i3.y)," +" vec4(i0.z, i1.z, i2.z, i3.z)," +" vec4(i0.w, i1.w, i2.w, i3.w)" +" );" +" return outMatrix;" "}" "void main() {" " gl_Position = transpose(proj) * vec4(pos, 0.0, 1.0);" @@ -50,26 +50,30 @@ const GLchar ellipse_fragment_src[] = // Textured quads const GLchar vertex_src[] = "uniform mat4 proj;" +"uniform bool invert_y;" "attribute vec2 pos;" "attribute vec2 texcoord;" "varying vec2 v_texcoord;" "mat4 transpose(in mat4 inMatrix) {" -" vec4 i0 = inMatrix[0];" -" vec4 i1 = inMatrix[1];" -" vec4 i2 = inMatrix[2];" -" vec4 i3 = inMatrix[3];" -" mat4 outMatrix = mat4(" -" vec4(i0.x, i1.x, i2.x, i3.x)," -" vec4(i0.y, i1.y, i2.y, i3.y)," -" vec4(i0.z, i1.z, i2.z, i3.z)," -" vec4(i0.w, i1.w, i2.w, i3.w)" -" );" -"" -" return outMatrix;" +" vec4 i0 = inMatrix[0];" +" vec4 i1 = inMatrix[1];" +" vec4 i2 = inMatrix[2];" +" vec4 i3 = inMatrix[3];" +" mat4 outMatrix = mat4(" +" vec4(i0.x, i1.x, i2.x, i3.x)," +" vec4(i0.y, i1.y, i2.y, i3.y)," +" vec4(i0.z, i1.z, i2.z, i3.z)," +" vec4(i0.w, i1.w, i2.w, i3.w)" +" );" +" return outMatrix;" "}" "void main() {" -" gl_Position = transpose(proj) * vec4(pos, 0.0, 1.0);" -" v_texcoord = texcoord;" +" gl_Position = transpose(proj) * vec4(pos, 0.0, 1.0);" +" if (invert_y) {" +" v_texcoord = vec2(texcoord.s, 1.0 - texcoord.t);" +" } else {" +" v_texcoord = texcoord;" +" }" "}"; const GLchar fragment_src_rgba[] = @@ -78,7 +82,7 @@ const GLchar fragment_src_rgba[] = "uniform sampler2D tex;" "uniform float alpha;" "void main() {" -" gl_FragColor = alpha * texture2D(tex, v_texcoord);" +" gl_FragColor = alpha * texture2D(tex, v_texcoord);" "}"; const GLchar fragment_src_rgbx[] = @@ -87,8 +91,8 @@ const GLchar fragment_src_rgbx[] = "uniform sampler2D tex;" "uniform float alpha;" "void main() {" -" gl_FragColor.rgb = alpha * texture2D(tex, v_texcoord).rgb;" -" gl_FragColor.a = alpha;" +" gl_FragColor.rgb = alpha * texture2D(tex, v_texcoord).rgb;" +" gl_FragColor.a = alpha;" "}"; const GLchar fragment_src_external[] = diff --git a/render/gles2/texture.c b/render/gles2/texture.c index 241b94a8..5e84890f 100644 --- a/render/gles2/texture.c +++ b/render/gles2/texture.c @@ -164,6 +164,7 @@ static bool gles2_texture_upload_drm(struct wlr_texture *_tex, EGLint inverted_y; wlr_egl_query_buffer(tex->egl, buf, EGL_WAYLAND_Y_INVERTED_WL, &inverted_y); + tex->wlr_texture.inverted_y = !!inverted_y; GLenum target; const struct pixel_format *pf; @@ -226,6 +227,42 @@ static bool gles2_texture_upload_eglimage(struct wlr_texture *wlr_tex, return true; } + +static bool gles2_texture_upload_dmabuf(struct wlr_texture *_tex, + struct wl_resource *dmabuf_resource) { + struct wlr_gles2_texture *tex = (struct wlr_gles2_texture *)_tex; + struct wlr_dmabuf_buffer *dmabuf = wlr_dmabuf_buffer_from_buffer_resource( + dmabuf_resource); + + if (!tex->egl->egl_exts.dmabuf_import) { + wlr_log(L_ERROR, "Want dmabuf but extension not present"); + return false; + } + + tex->wlr_texture.width = dmabuf->attributes.width; + tex->wlr_texture.height = dmabuf->attributes.height; + + if (tex->image) { + wlr_egl_destroy_image(tex->egl, tex->image); + } + + if (wlr_dmabuf_buffer_has_inverted_y(dmabuf)) { + _tex->inverted_y = true; + } + + GLenum target = GL_TEXTURE_2D; + const struct pixel_format *pf = + gl_format_for_wl_format(WL_SHM_FORMAT_ARGB8888); + gles2_texture_ensure_texture(tex); + GL_CALL(glBindTexture(target, tex->tex_id)); + tex->image = wlr_egl_create_image_from_dmabuf(tex->egl, &dmabuf->attributes); + GL_CALL(glActiveTexture(GL_TEXTURE0)); + GL_CALL(glEGLImageTargetTexture2DOES(target, tex->image)); + tex->pixel_format = pf; + tex->wlr_texture.valid = true; + return true; +} + static void gles2_texture_get_matrix(struct wlr_texture *_texture, float (*matrix)[16], const float (*projection)[16], int x, int y) { struct wlr_gles2_texture *texture = (struct wlr_gles2_texture *)_texture; @@ -239,6 +276,21 @@ static void gles2_texture_get_matrix(struct wlr_texture *_texture, wlr_matrix_mul(projection, matrix, matrix); } + +static bool gles2_texture_get_dmabuf_size(struct wlr_texture *texture, struct + wl_resource *resource, int *width, int *height) { + if (!wlr_dmabuf_resource_is_buffer(resource)) { + return false; + } + + struct wlr_dmabuf_buffer *dmabuf = wlr_dmabuf_buffer_from_buffer_resource( + resource); + *width = dmabuf->attributes.width; + *height = dmabuf->attributes.height; + return true; +} + + static void gles2_texture_get_buffer_size(struct wlr_texture *texture, struct wl_resource *resource, int *width, int *height) { struct wl_shm_buffer *buffer = wl_shm_buffer_get(resource); @@ -249,10 +301,12 @@ static void gles2_texture_get_buffer_size(struct wlr_texture *texture, struct } if (!wlr_egl_query_buffer(tex->egl, resource, EGL_WIDTH, (EGLint*)width)) { - wlr_log(L_ERROR, "could not get size of the buffer " - "(no buffer found)"); - return; - }; + if (!gles2_texture_get_dmabuf_size(texture, resource, + width, height)) { + wlr_log(L_ERROR, "could not get size of the buffer"); + return; + } + } wlr_egl_query_buffer(tex->egl, resource, EGL_HEIGHT, (EGLint*)height); @@ -291,6 +345,7 @@ static struct wlr_texture_impl wlr_texture_impl = { .upload_shm = gles2_texture_upload_shm, .update_shm = gles2_texture_update_shm, .upload_drm = gles2_texture_upload_drm, + .upload_dmabuf = gles2_texture_upload_dmabuf, .upload_eglimage = gles2_texture_upload_eglimage, .get_matrix = gles2_texture_get_matrix, .get_buffer_size = gles2_texture_get_buffer_size, diff --git a/render/wlr_texture.c b/render/wlr_texture.c index a82a16b2..e4e6b9ff 100644 --- a/render/wlr_texture.c +++ b/render/wlr_texture.c @@ -53,6 +53,11 @@ bool wlr_texture_upload_eglimage(struct wlr_texture *texture, return texture->impl->upload_eglimage(texture, image, width, height); } +bool wlr_texture_upload_dmabuf(struct wlr_texture *texture, + struct wl_resource *dmabuf_resource) { + return texture->impl->upload_dmabuf(texture, dmabuf_resource); +} + void wlr_texture_get_matrix(struct wlr_texture *texture, float (*matrix)[16], const float (*projection)[16], int x, int y) { texture->impl->get_matrix(texture, matrix, projection, x, y); diff --git a/rootston/desktop.c b/rootston/desktop.c index 3628b051..a130fe38 100644 --- a/rootston/desktop.c +++ b/rootston/desktop.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -729,6 +730,8 @@ struct roots_desktop *desktop_create(struct roots_server *server, desktop->idle = wlr_idle_create(server->wl_display); desktop->idle_inhibit = wlr_idle_inhibit_v1_create(server->wl_display); + struct wlr_egl *egl = wlr_backend_get_egl(server->backend); + desktop->linux_dmabuf = wlr_linux_dmabuf_create(server->wl_display, egl); return desktop; } diff --git a/types/meson.build b/types/meson.build index 703b06ca..11aa5e20 100644 --- a/types/meson.build +++ b/types/meson.build @@ -9,6 +9,7 @@ lib_wlr_types = static_library( 'wlr_idle.c', 'wlr_input_device.c', 'wlr_keyboard.c', + 'wlr_linux_dmabuf.c', 'wlr_list.c', 'wlr_output_damage.c', 'wlr_output_layout.c', diff --git a/types/wlr_linux_dmabuf.c b/types/wlr_linux_dmabuf.c new file mode 100644 index 00000000..3b86166e --- /dev/null +++ b/types/wlr_linux_dmabuf.c @@ -0,0 +1,464 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include "linux-dmabuf-unstable-v1-protocol.h" + +static void wl_buffer_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_buffer_interface wl_buffer_impl = { + wl_buffer_destroy, +}; + +bool wlr_dmabuf_buffer_has_inverted_y(struct wlr_dmabuf_buffer *dmabuf) { + return dmabuf->attributes.flags + & ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; +} + +bool wlr_dmabuf_resource_is_buffer(struct wl_resource *buffer_resource) { + if (!wl_resource_instance_of(buffer_resource, &wl_buffer_interface, + &wl_buffer_impl)) { + return false; + } + + struct wlr_dmabuf_buffer *buffer = wl_resource_get_user_data(buffer_resource); + if (buffer && buffer->buffer_resource && !buffer->params_resource && + buffer->buffer_resource == buffer_resource) { + return true; + } + + return false; +} + +struct wlr_dmabuf_buffer *wlr_dmabuf_buffer_from_buffer_resource( + struct wl_resource *buffer_resource) { + assert(wl_resource_instance_of(buffer_resource, &wl_buffer_interface, + &wl_buffer_impl)); + + struct wlr_dmabuf_buffer *buffer = wl_resource_get_user_data(buffer_resource); + assert(buffer); + assert(buffer->buffer_resource); + assert(!buffer->params_resource); + assert(buffer->buffer_resource == buffer_resource); + + return buffer; +} + +static void linux_dmabuf_buffer_destroy(struct wlr_dmabuf_buffer *buffer) { + for (int i = 0; i < buffer->attributes.n_planes; i++) { + close(buffer->attributes.fd[i]); + buffer->attributes.fd[i] = -1; + } + buffer->attributes.n_planes = 0; + free(buffer); +} + +static void params_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void params_add(struct wl_client *client, + struct wl_resource *params_resource, int32_t name_fd, + uint32_t plane_idx, uint32_t offset, uint32_t stride, + uint32_t modifier_hi, uint32_t modifier_lo) { + struct wlr_dmabuf_buffer *buffer = wlr_dmabuf_buffer_from_params_resource( + params_resource); + + if (!buffer) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, + "params was already used to create a wl_buffer"); + close(name_fd); + return; + } + + if (plane_idx >= WLR_LINUX_DMABUF_MAX_PLANES) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX, + "plane index %u > %u", plane_idx, WLR_LINUX_DMABUF_MAX_PLANES); + close(name_fd); + return; + } + + if (buffer->attributes.fd[plane_idx] != -1) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET, + "a dmabuf with id %d has already been added for plane %u", + buffer->attributes.fd[plane_idx], + plane_idx); + close(name_fd); + return; + } + + buffer->attributes.fd[plane_idx] = name_fd; + buffer->attributes.offset[plane_idx] = offset; + buffer->attributes.stride[plane_idx] = stride; + buffer->attributes.modifier[plane_idx] = ((uint64_t)modifier_hi << 32) | + modifier_lo; + buffer->attributes.n_planes++; +} + +static void handle_buffer_destroy(struct wl_resource *buffer_resource) +{ + struct wlr_dmabuf_buffer *buffer = wlr_dmabuf_buffer_from_buffer_resource( + buffer_resource); + + linux_dmabuf_buffer_destroy(buffer); +} + +static void params_create_common(struct wl_client *client, + struct wl_resource *params_resource, uint32_t buffer_id, int32_t width, + int32_t height, uint32_t format, uint32_t flags) { + if (!wl_resource_get_user_data(params_resource)) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, + "params was already used to create a wl_buffer"); + return; + } + struct wlr_dmabuf_buffer *buffer = wlr_dmabuf_buffer_from_params_resource( + params_resource); + + /* Switch the linux_dmabuf_buffer object from params resource to + * eventually wl_buffer resource. */ + wl_resource_set_user_data(buffer->params_resource, NULL); + buffer->params_resource = NULL; + + if (!buffer->attributes.n_planes) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, + "no dmabuf has been added to the params"); + goto err_out; + } + + /* TODO: support more planes */ + if (buffer->attributes.n_planes != 1) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, + "only single plane buffers supported not %d", + buffer->attributes.n_planes); + goto err_out; + } + + if (buffer->attributes.fd[0] == -1) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, + "no dmabuf has been added for plane"); + goto err_out; + } + + buffer->attributes.width = width; + buffer->attributes.height = height; + buffer->attributes.format = format; + buffer->attributes.flags = flags; + + if (width < 1 || height < 1) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS, + "invalid width %d or height %d", width, height); + goto err_out; + } + + if ((uint64_t)buffer->attributes.offset[0] + buffer->attributes.stride[0] > UINT32_MAX) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "size overflow for plane"); + goto err_out; + } + + if ((uint64_t)buffer->attributes.offset[0] + + (uint64_t)buffer->attributes.stride[0] * height > UINT32_MAX) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "size overflow for plane"); + goto err_out; + } + + off_t size = lseek(buffer->attributes.fd[0], 0, SEEK_END); + if (size != -1) { /* Skip checks if kernel does no support seek on buffer */ + if (buffer->attributes.offset[0] >= size) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "invalid offset %i for plane", + buffer->attributes.offset[0]); + goto err_out; + } + + if (buffer->attributes.offset[0] + buffer->attributes.stride[0] > size) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "invalid stride %i for plane", + buffer->attributes.stride[0]); + goto err_out; + } + + if (buffer->attributes.offset[0] + buffer->attributes.stride[0] * height > size) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "invalid buffer stride or height for plane"); + goto err_out; + } + } + + /* reject unknown flags */ + if (buffer->attributes.flags & ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT, + "Unknown dmabuf flags %"PRIu32, buffer->attributes.flags); + goto err_out; + } + + /* Check if dmabuf is usable */ + if (!wlr_egl_check_import_dmabuf(buffer->egl, buffer)) { + goto err_failed; + } + + buffer->buffer_resource = wl_resource_create(client, &wl_buffer_interface, + 1, buffer_id); + if (!buffer->buffer_resource) { + wl_resource_post_no_memory(params_resource); + goto err_failed; + } + + wl_resource_set_implementation(buffer->buffer_resource, + &wl_buffer_impl, buffer, handle_buffer_destroy); + + /* send 'created' event when the request is not for an immediate + * import, that is buffer_id is zero */ + if (buffer_id == 0) { + zwp_linux_buffer_params_v1_send_created(params_resource, + buffer->buffer_resource); + } + return; + +err_failed: + if (buffer_id == 0) { + zwp_linux_buffer_params_v1_send_failed(params_resource); + } else { + /* since the behavior is left implementation defined by the + * protocol in case of create_immed failure due to an unknown cause, + * we choose to treat it as a fatal error and immediately kill the + * client instead of creating an invalid handle and waiting for it + * to be used. + */ + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER, + "importing the supplied dmabufs failed"); + } +err_out: + linux_dmabuf_buffer_destroy(buffer); + return; +} + +static void params_create(struct wl_client *client, + struct wl_resource *params_resource, + int32_t width, int32_t height,uint32_t format, uint32_t flags) { + params_create_common(client, params_resource, 0, width, height, format, flags); +} + +static void params_create_immed(struct wl_client *client, + struct wl_resource *params_resource, uint32_t buffer_id, + int32_t width, int32_t height,uint32_t format, uint32_t flags) { + params_create_common(client, params_resource, buffer_id, width, height, format, flags); +} + +static const struct zwp_linux_buffer_params_v1_interface linux_buffer_params_impl = { + params_destroy, + params_add, + params_create, + params_create_immed, +}; + +struct wlr_dmabuf_buffer *wlr_dmabuf_buffer_from_params_resource( + struct wl_resource *params_resource) { + assert(wl_resource_instance_of(params_resource, + &zwp_linux_buffer_params_v1_interface, + &linux_buffer_params_impl)); + + struct wlr_dmabuf_buffer *buffer = wl_resource_get_user_data(params_resource); + assert(buffer); + assert(buffer->params_resource); + assert(!buffer->buffer_resource); + assert(buffer->params_resource == params_resource); + + return buffer; +} + +static void handle_params_destroy(struct wl_resource *params_resource) { + /* Check for NULL since wlr_dmabuf_buffer_from_params_resource will choke */ + if (!wl_resource_get_user_data(params_resource)) { + return; + } + + struct wlr_dmabuf_buffer *buffer = + wlr_dmabuf_buffer_from_params_resource(params_resource); + linux_dmabuf_buffer_destroy(buffer); +} + +static void linux_dmabuf_create_params(struct wl_client *client, + struct wl_resource *linux_dmabuf_resource, + uint32_t params_id) { + struct wlr_linux_dmabuf *linux_dmabuf = wlr_linux_dmabuf_from_resource( + linux_dmabuf_resource); + + uint32_t version = wl_resource_get_version(linux_dmabuf_resource); + struct wlr_dmabuf_buffer *buffer = calloc(1, sizeof *buffer); + if (!buffer) { + goto err; + } + + for (int i = 0; i < WLR_LINUX_DMABUF_MAX_PLANES; i++) { + buffer->attributes.fd[i] = -1; + } + + buffer->egl = linux_dmabuf->egl; + buffer->params_resource = wl_resource_create(client, + &zwp_linux_buffer_params_v1_interface, + version, params_id); + if (!buffer->params_resource) { + goto err_free; + } + + wl_resource_set_implementation(buffer->params_resource, + &linux_buffer_params_impl,buffer, handle_params_destroy); + return; + +err_free: + free(buffer); +err: + wl_resource_post_no_memory(linux_dmabuf_resource); +} + +static void linux_dmabuf_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_linux_dmabuf_v1_interface linux_dmabuf_impl = { + linux_dmabuf_destroy, + linux_dmabuf_create_params +}; + +struct wlr_linux_dmabuf *wlr_linux_dmabuf_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_linux_dmabuf_v1_interface, + &linux_dmabuf_impl)); + + struct wlr_linux_dmabuf *dmabuf = wl_resource_get_user_data(resource); + assert(dmabuf); + return dmabuf; +} + +static void linux_dmabuf_send_modifiers(struct wlr_linux_dmabuf *linux_dmabuf, + struct wl_resource *resource) { + struct wlr_egl *egl = linux_dmabuf->egl; + /* + * Use EGL_EXT_image_dma_buf_import_modifiers to query and advertise + * format/modifier codes. + */ + uint64_t modifier_invalid = DRM_FORMAT_MOD_INVALID; + int *formats = NULL; + int num_formats = wlr_egl_get_dmabuf_formats(egl, &formats); + + if (num_formats < 0) { + return; + } + + for (int i = 0; i < num_formats; i++) { + int num_modifiers; + uint64_t *modifiers = NULL; + + num_modifiers = wlr_egl_get_dmabuf_modifiers(egl, formats[i], + &modifiers); + if (num_modifiers < 0) { + return; + } + /* send DRM_FORMAT_MOD_INVALID token when no modifiers are supported + * for this format */ + if (num_modifiers == 0) { + num_modifiers = 1; + modifiers = &modifier_invalid; + } + for (int j = 0; j < num_modifiers; j++) { + uint32_t modifier_lo = modifiers[j] & 0xFFFFFFFF; + uint32_t modifier_hi = modifiers[j] >> 32; + zwp_linux_dmabuf_v1_send_modifier(resource, formats[i], + modifier_hi, + modifier_lo); + } + if (modifiers != &modifier_invalid) { + free(modifiers); + } + } + free(formats); +} + +static void linux_dmabuf_bind(struct wl_client *client, + void *data, uint32_t version, uint32_t id) { + struct wlr_linux_dmabuf *linux_dmabuf = data; + struct wl_resource *resource = wl_resource_create(client, + &zwp_linux_dmabuf_v1_interface, + version, id); + + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &linux_dmabuf_impl, + linux_dmabuf, NULL); + if (version < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) { + return; + } + + linux_dmabuf_send_modifiers(linux_dmabuf, resource); +} + +void wlr_linux_dmabuf_destroy(struct wlr_linux_dmabuf *linux_dmabuf) { + if (!linux_dmabuf) { + return; + } + wl_list_remove(&linux_dmabuf->display_destroy.link); + + wl_global_destroy(linux_dmabuf->wl_global); + free(linux_dmabuf); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_linux_dmabuf *linux_dmabuf = wl_container_of(listener, linux_dmabuf, display_destroy); + wlr_linux_dmabuf_destroy(linux_dmabuf); +} + +struct wlr_linux_dmabuf *wlr_linux_dmabuf_create(struct wl_display *display, + struct wlr_egl *egl) { + struct wlr_linux_dmabuf *linux_dmabuf = + calloc(1, sizeof(struct wlr_linux_dmabuf)); + if (linux_dmabuf == NULL) { + wlr_log(L_ERROR, "could not create simple dmabuf manager"); + return NULL; + } + + linux_dmabuf->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &linux_dmabuf->display_destroy); + + linux_dmabuf->wl_global = + wl_global_create(display, &zwp_linux_dmabuf_v1_interface, + 3, linux_dmabuf, linux_dmabuf_bind); + + linux_dmabuf->egl = egl; + if (!linux_dmabuf->wl_global) { + wlr_log(L_ERROR, "could not create linux dmabuf v1 wl global"); + free(linux_dmabuf); + return NULL; + } + + return linux_dmabuf; +} diff --git a/types/wlr_surface.c b/types/wlr_surface.c index 23966cd1..51bd47bc 100644 --- a/types/wlr_surface.c +++ b/types/wlr_surface.c @@ -325,6 +325,10 @@ static void wlr_surface_apply_damage(struct wlr_surface *surface, surface->current->buffer)) { wlr_texture_upload_drm(surface->texture, surface->current->buffer); goto release; + } else if (wlr_dmabuf_resource_is_buffer( + surface->current->buffer)) { + wlr_texture_upload_dmabuf(surface->texture, surface->current->buffer); + goto release; } else { wlr_log(L_INFO, "Unknown buffer handle attached"); return;