From 3ac3f6cab22624db1bf094009bcb627d8ee58cc5 Mon Sep 17 00:00:00 2001 From: Jason Francis Date: Sat, 26 Oct 2019 16:02:44 -0400 Subject: [PATCH] Use wlr-export-dmabuf to capture frames --- README.md | 1 + protocol/meson.build | 2 + protocol/wlr-export-dmabuf-unstable-v1.xml | 203 ++++++++++++++++++ src/main.c | 36 +++- src/meson.build | 2 + src/outputs.c | 194 +++++++++++++---- src/render.c | 233 ++++++++++++++++----- src/wdisplays.h | 47 +++-- 8 files changed, 606 insertions(+), 112 deletions(-) create mode 100644 protocol/wlr-export-dmabuf-unstable-v1.xml diff --git a/README.md b/README.md index 7648997..62a39e3 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Build requirements are: - meson - GTK+3 +- libdrm - epoxy - wayland-client diff --git a/protocol/meson.build b/protocol/meson.build index 5f34a7a..2792f87 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -21,9 +21,11 @@ wayland_scanner_client = generator( client_protocols = [ [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], + [wl_protocol_dir, 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml'], [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], ['wlr-output-management-unstable-v1.xml'], ['wlr-screencopy-unstable-v1.xml'], + ['wlr-export-dmabuf-unstable-v1.xml'], ['wlr-layer-shell-unstable-v1.xml'] ] diff --git a/protocol/wlr-export-dmabuf-unstable-v1.xml b/protocol/wlr-export-dmabuf-unstable-v1.xml new file mode 100644 index 0000000..751f7ef --- /dev/null +++ b/protocol/wlr-export-dmabuf-unstable-v1.xml @@ -0,0 +1,203 @@ + + + + Copyright © 2018 Rostislav Pehlivanov + + 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. + + + + An interface to capture surfaces in an efficient way by exporting DMA-BUFs. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This object is a manager with which to start capturing from sources. + + + + + Capture the next frame of a an entire output. + + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object represents a single DMA-BUF frame. + + If the capture is successful, the compositor will first send a "frame" + event, followed by one or several "object". When the frame is available + for readout, the "ready" event is sent. + + If the capture failed, the "cancel" event is sent. This can happen anytime + before the "ready" event. + + Once either a "ready" or a "cancel" event is received, the client should + destroy the frame. Once an "object" event is received, the client is + responsible for closing the associated file descriptor. + + All frames are read-only and may not be written into or altered. + + + + + Special flags that should be respected by the client. + + + + + + + Main event supplying the client with information about the frame. If the + capture didn't fail, this event is always emitted first before any other + events. + + This event is followed by a number of "object" as specified by the + "num_objects" argument. + + + + + + + + + + + + + + + + Event which serves to supply the client with the file descriptors + containing the data for each object. + + After receiving this event, the client must always close the file + descriptor as soon as they're done with it and even if the frame fails. + + + + + + + + + + + + This event is sent as soon as the frame is presented, indicating it is + available for reading. This event includes the time at which + presentation happened at. + + The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, + each component being an unsigned 32-bit value. Whole seconds are in + tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, + and the additional fractional part in tv_nsec as nanoseconds. Hence, + for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part + may have an arbitrary offset at start. + + After receiving this event, the client should destroy this object. + + + + + + + + + Indicates reason for cancelling the frame. + + + + + + + + + If the capture failed or if the frame is no longer valid after the + "frame" event has been emitted, this event will be used to inform the + client to scrap the frame. + + If the failure is temporary, the client may capture again the same + source. If the failure is permanent, any further attempts to capture the + same source will fail again. + + After receiving this event, the client should destroy this object. + + + + + + + Unreferences the frame. This request must be called as soon as its no + longer used. + + It can be called at any time by the client. The client will still have + to close any FDs it has been given. + + + + diff --git a/src/main.c b/src/main.c index e9690c4..7d91cca 100644 --- a/src/main.c +++ b/src/main.c @@ -3,11 +3,14 @@ #include #include +#include #include "wdisplays.h" #include "glviewport.h" #include "headform.h" +#include "linux-dmabuf-unstable-v1-client-protocol.h" + __attribute__((noreturn)) void wd_fatal_error(int status, const char *message) { GtkWindow *parent = gtk_application_get_active_window(GTK_APPLICATION(g_application_get_default())); GtkWidget *dialog = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", message); @@ -438,7 +441,11 @@ static void canvas_realize(GtkWidget *widget, gpointer data) { } struct wd_state *state = data; - state->gl_data = wd_gl_setup(); + + GdkWindow *window = gtk_widget_get_window(widget); + GdkDisplay *display = gdk_window_get_display(window); + struct wl_display *wl_display = gdk_wayland_display_get_wl_display(display); + state->gl_data = wd_gl_setup(wl_display); } static inline bool size_changed(const struct wd_render_head_data *render) { @@ -524,6 +531,7 @@ static void canvas_render(GtkGLArea *area, GdkGLContext *context, gpointer data) uint64_t tick = gdk_frame_clock_get_frame_time(clock); wd_capture_frame(state); + state->render.external_images = state->export_manager != NULL; struct wd_head *head; wl_list_for_each(head, &state->heads, link) { @@ -534,16 +542,28 @@ static void canvas_render(GtkGLArea *area, GdkGLContext *context, gpointer data) frame = wl_container_of(output->frames.prev, frame, link); } if (render != NULL) { - if (state->capture && frame != NULL && frame->pixels != NULL) { + if (state->capture && frame != NULL && frame->ready) { if (frame->tick > render->updated_at) { - render->tex_stride = frame->stride; render->tex_width = frame->width; render->tex_height = frame->height; - render->pixels = frame->pixels; + if (state->render.external_images) { + render->pixels = NULL; + render->image = wd_gl_create_dmabuf_texture(state->gl_data, frame); + render->has_alpha = TRUE; + render->swap_rgb = TRUE; + } else { + render->pixels = frame->pixels; + render->image = NULL; + render->tex_stride = frame->strides[0]; + render->swap_rgb = frame->format == WL_SHM_FORMAT_ABGR8888 + || frame->format == WL_SHM_FORMAT_XBGR8888; + render->has_alpha = frame->format == WL_SHM_FORMAT_ARGB8888 + || frame->format == WL_SHM_FORMAT_ABGR8888; + } + render->y_invert = frame->flags & + ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; render->preview = TRUE; render->updated_at = tick; - render->y_invert = frame->y_invert; - render->swap_rgb = frame->swap_rgb; } if (render->preview) { render->active.rotation = render->queued.rotation; @@ -560,10 +580,12 @@ static void canvas_render(GtkGLArea *area, GdkGLContext *context, gpointer data) head->surface = draw_head(pango, &state->render, head->name, render->tex_width, render->tex_height); render->pixels = cairo_image_surface_get_data(head->surface); + render->image = NULL; render->tex_stride = cairo_image_surface_get_stride(head->surface); render->updated_at = tick; render->active.rotation = 0; render->active.x_invert = FALSE; + render->has_alpha = FALSE; render->y_invert = FALSE; render->swap_rgb = FALSE; } @@ -1054,7 +1076,7 @@ static void activate(GtkApplication* app, gpointer user_data) { if (state->xdg_output_manager == NULL) { wd_fatal_error(1, "Compositor doesn't support xdg-output-unstable-v1"); } - if (state->copy_manager == NULL) { + if (state->copy_manager == NULL && state->export_manager == NULL) { state->capture = FALSE; g_simple_action_set_state(capture_action, g_variant_new_boolean(state->capture)); g_simple_action_set_enabled(capture_action, FALSE); diff --git a/src/meson.build b/src/meson.build index 08830e9..6f3d3ac 100644 --- a/src/meson.build +++ b/src/meson.build @@ -8,6 +8,7 @@ gdk = dependency('gdk-3.0', version: '>= 3.24') gtk = dependency('gtk+-3.0', version: '>= 3.24') assert(gdk.get_pkgconfig_variable('targets').split().contains('wayland'), 'Wayland GDK backend not present') epoxy = dependency('epoxy') +libdrm = dependency('libdrm') configure_file(input: 'config.h.in', output: 'config.h', configuration: conf) @@ -28,6 +29,7 @@ executable( wayland_client, client_protos, epoxy, + libdrm, gtk ], install: true diff --git a/src/outputs.c b/src/outputs.c index 9ebf7e5..bc7908f 100644 --- a/src/outputs.c +++ b/src/outputs.c @@ -25,6 +25,8 @@ #include "wlr-output-management-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" +#include "linux-dmabuf-unstable-v1-client-protocol.h" +#include "wlr-export-dmabuf-unstable-v1-client-protocol.h" #include "wlr-screencopy-unstable-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" @@ -143,15 +145,18 @@ void wd_apply_state(struct wd_state *state, struct wl_list *new_outputs, static void wd_frame_destroy(struct wd_frame *frame) { if (frame->pixels != NULL) - munmap(frame->pixels, frame->height * frame->stride); + munmap(frame->pixels, frame->height * frame->strides[0]); if (frame->buffer != NULL) wl_buffer_destroy(frame->buffer); if (frame->pool != NULL) wl_shm_pool_destroy(frame->pool); - if (frame->capture_fd != -1) - close(frame->capture_fd); - if (frame->wlr_frame != NULL) - zwlr_screencopy_frame_v1_destroy(frame->wlr_frame); + for (int i = 0; i < frame->n_planes; i++) + if (frame->fds[i]) + close(frame->fds[i]); + if (frame->copy_frame != NULL) + zwlr_screencopy_frame_v1_destroy(frame->copy_frame); + if (frame->export_frame != NULL) + zwlr_export_dmabuf_frame_v1_destroy(frame->export_frame); wl_list_remove(&frame->link); free(frame); @@ -189,6 +194,112 @@ static int create_shm_file(size_t size, const char *fmt, ...) { return fd; } +static void make_frame_current(struct wd_frame *frame, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { + uint64_t tv_sec = (uint64_t) tv_sec_hi << 32 | tv_sec_lo; + frame->tick = (tv_sec * 1000000) + (tv_nsec / 1000); + + struct wd_frame *frame_iter, *frame_tmp; + wl_list_for_each_safe(frame_iter, frame_tmp, &frame->output->frames, link) { + if (frame != frame_iter) { + wd_frame_destroy(frame_iter); + } + } +} + +#define WD_FRAME_COPY 0x100 + +static void export_frame(void *data, + struct zwlr_export_dmabuf_frame_v1 *wlr_frame, + uint32_t width, uint32_t height, uint32_t offset_x, uint32_t offset_y, + uint32_t buffer_flags, uint32_t flags, uint32_t format, + uint32_t mod_high, uint32_t mod_low, uint32_t num_objects) { + struct wd_frame *frame = data; + frame->width = width; + frame->height = height; + frame->format = format; + frame->flags = buffer_flags; +/* + if (flags & ZWLR_EXPORT_DMABUF_FRAME_V1_FLAGS_TRANSIENT) { + frame->flags |= WD_FRAME_COPY; + } +*/ + frame->modifier = ((uint64_t) mod_high << 32) | mod_low; + frame->n_planes = num_objects; +} + +static void export_object(void *data, + struct zwlr_export_dmabuf_frame_v1 *wlr_frame, + uint32_t index, int32_t fd, uint32_t size, uint32_t offset, + uint32_t stride, uint32_t plane_index) { + struct wd_frame *frame = data; + + if (frame->flags & WD_FRAME_COPY) { + void *src = NULL; + void *dest = NULL; + int wfd = create_shm_file(size, "/wd-%s", frame->output->name); + if (wfd == -1) { + goto done; + } + src = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, offset); + if (src == MAP_FAILED) { + fprintf(stderr, "mmap: %d: %s\n", fd, strerror(errno)); + close(wfd); + wfd = -1; + goto done; + } + dest = mmap(NULL, size - offset, PROT_READ | PROT_WRITE, + MAP_PRIVATE, wfd, 0); + if (dest == MAP_FAILED) { + fprintf(stderr, "mmap: %d: %s\n", wfd, strerror(errno)); + close(wfd); + wfd = -1; + goto done; + } + memcpy(dest, src, size - offset); +done: + if (src) + munmap(src, size); + if (dest) + munmap(dest, size - offset); + close(fd); + frame->fds[plane_index] = wfd; + } else { + frame->fds[plane_index] = fd; + frame->offsets[plane_index] = offset; + } + frame->strides[plane_index] = stride; +} + +static void export_ready(void *data, + struct zwlr_export_dmabuf_frame_v1 *wlr_frame, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { + struct wd_frame *frame = data; + + make_frame_current(frame, tv_sec_hi, tv_sec_lo, tv_nsec); + + if (frame->flags & WD_FRAME_COPY) { + frame->flags ^= WD_FRAME_COPY; + zwlr_export_dmabuf_frame_v1_destroy(frame->export_frame); + frame->export_frame = NULL; + } + frame->ready = true; +} + +static void export_cancel(void *data, + struct zwlr_export_dmabuf_frame_v1 *wlr_frame, + uint32_t reason) { + struct wd_frame *frame = data; + wd_frame_destroy(frame); +} + +static struct zwlr_export_dmabuf_frame_v1_listener export_listener = { + .frame = export_frame, + .object = export_object, + .ready = export_ready, + .cancel = export_cancel +}; + static void capture_buffer(void *data, struct zwlr_screencopy_frame_v1 *copy_frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { @@ -199,22 +310,23 @@ static void capture_buffer(void *data, goto err; } + frame->n_planes = 1; size_t size = stride * height; - frame->capture_fd = create_shm_file(size, "/wd-%s", frame->output->name); - if (frame->capture_fd == -1) { + frame->fds[0] = create_shm_file(size, "/wd-%s", frame->output->name); + if (frame->fds[0] == -1) { goto err; } frame->pool = wl_shm_create_pool(frame->output->state->shm, - frame->capture_fd, size); + frame->fds[0], size); frame->buffer = wl_shm_pool_create_buffer(frame->pool, 0, width, height, stride, format); zwlr_screencopy_frame_v1_copy(copy_frame, frame->buffer); - frame->stride = stride; + frame->offsets[0] = 0; + frame->strides[0] = stride; frame->width = width; frame->height = height; - frame->swap_rgb = format == WL_SHM_FORMAT_ABGR8888 - || format == WL_SHM_FORMAT_XBGR8888; + frame->format = format; return; err: @@ -225,7 +337,9 @@ static void capture_flags(void *data, struct zwlr_screencopy_frame_v1 *wlr_frame, uint32_t flags) { struct wd_frame *frame = data; - frame->y_invert = !!(flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT); + if (flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT) { + frame->flags |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; + } } static void capture_ready(void *data, @@ -233,27 +347,20 @@ static void capture_ready(void *data, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { struct wd_frame *frame = data; - frame->pixels = mmap(NULL, frame->stride * frame->height, - PROT_READ, MAP_SHARED, frame->capture_fd, 0); + frame->pixels = mmap(NULL, frame->strides[0] * frame->height, + PROT_READ, MAP_SHARED, frame->fds[0], 0); if (frame->pixels == MAP_FAILED) { frame->pixels = NULL; - fprintf(stderr, "mmap: %d: %s\n", frame->capture_fd, strerror(errno)); + fprintf(stderr, "mmap: %d: %s\n", frame->fds[0], strerror(errno)); wd_frame_destroy(frame); return; - } else { - uint64_t tv_sec = (uint64_t) tv_sec_hi << 32 | tv_sec_lo; - frame->tick = (tv_sec * 1000000) + (tv_nsec / 1000); } + + make_frame_current(frame, tv_sec_hi, tv_sec_lo, tv_nsec); - zwlr_screencopy_frame_v1_destroy(frame->wlr_frame); - frame->wlr_frame = NULL; - - struct wd_frame *frame_iter, *frame_tmp; - wl_list_for_each_safe(frame_iter, frame_tmp, &frame->output->frames, link) { - if (frame != frame_iter) { - wd_frame_destroy(frame_iter); - } - } + zwlr_screencopy_frame_v1_destroy(frame->copy_frame); + frame->copy_frame = NULL; + frame->ready = true; } static void capture_failed(void *data, @@ -274,7 +381,7 @@ static bool has_pending_captures(struct wd_state *state) { wl_list_for_each(output, &state->outputs, link) { struct wd_frame *frame; wl_list_for_each(frame, &output->frames, link) { - if (frame->pixels == NULL) { + if (!frame->ready) { return true; } } @@ -283,8 +390,8 @@ static bool has_pending_captures(struct wd_state *state) { } void wd_capture_frame(struct wd_state *state) { - if (state->copy_manager == NULL || has_pending_captures(state) - || !state->capture) { + if ((state->export_manager == NULL && state->copy_manager == NULL) + || has_pending_captures(state) || !state->capture) { return; } @@ -292,12 +399,21 @@ void wd_capture_frame(struct wd_state *state) { wl_list_for_each(output, &state->outputs, link) { struct wd_frame *frame = calloc(1, sizeof(*frame)); frame->output = output; - frame->capture_fd = -1; - frame->wlr_frame = - zwlr_screencopy_manager_v1_capture_output(state->copy_manager, 1, - output->wl_output); - zwlr_screencopy_frame_v1_add_listener(frame->wlr_frame, &capture_listener, - frame); + for (int i = 0; i < WD_MAX_PLANES; i++) + frame->fds[i] = -1; + if (state->export_manager) { + frame->export_frame = + zwlr_export_dmabuf_manager_v1_capture_output(state->export_manager, 1, + output->wl_output); + zwlr_export_dmabuf_frame_v1_add_listener(frame->export_frame, + &export_listener, frame); + } else if (state->copy_manager) { + frame->copy_frame = + zwlr_screencopy_manager_v1_capture_output(state->copy_manager, 1, + output->wl_output); + zwlr_screencopy_frame_v1_add_listener(frame->copy_frame, + &capture_listener, frame); + } wl_list_insert(&output->frames, &frame->link); } } @@ -540,6 +656,9 @@ static void registry_handle_global(void *data, struct wl_registry *registry, } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { state->xdg_output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, version); + } else if(strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name) == 0) { + state->export_manager = wl_registry_bind(registry, name, + &zwlr_export_dmabuf_manager_v1_interface, version); } else if(strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) { state->copy_manager = wl_registry_bind(registry, name, &zwlr_screencopy_manager_v1_interface, version); @@ -689,6 +808,9 @@ void wd_state_destroy(struct wd_state *state) { if (state->layer_shell != NULL) { zwlr_layer_shell_v1_destroy(state->layer_shell); } + if (state->export_manager != NULL) { + zwlr_export_dmabuf_manager_v1_destroy(state->export_manager); + } if (state->copy_manager != NULL) { zwlr_screencopy_manager_v1_destroy(state->copy_manager); } diff --git a/src/render.c b/src/render.c index 99ff589..a964c49 100644 --- a/src/render.c +++ b/src/render.c @@ -1,13 +1,24 @@ /* SPDX-FileCopyrightText: 2020 Jason Francis * SPDX-License-Identifier: GPL-3.0-or-later */ +/* SPDX-FileCopyrightText: 2018 Simon Ser + * SPDX-FileCopyrightText: 2018 Guido Günther + * SPDX-License-Identifier: MIT */ +/* Parts of this file are taken from swaywm/wlroots: + * https://github.com/swaywm/wlroots/blob/e97c2c3639119831ced4f6b9f704b096c2075973/render/egl.c + */ #include "wdisplays.h" +#define WL_EGL_PLATFORM 1 + +#include #include #include #include -#include #include +#include +#include +#include #define BT_UV_VERT_SIZE (2 + 2) #define BT_UV_QUAD_SIZE (6 * BT_UV_VERT_SIZE) @@ -29,22 +40,25 @@ enum gl_buffers { NUM_BUFFERS }; +struct wd_gl_texture_program { + GLuint id; + GLuint position_attribute; + GLuint uv_attribute; + GLuint screen_size_uniform; + GLuint texture_uniform; + GLuint color_transform_uniform; + GLuint color_add_uniform; +}; + struct wd_gl_data { + EGLDisplay display; + GLuint color_program; - GLuint color_vertex_shader; - GLuint color_fragment_shader; GLuint color_position_attribute; GLuint color_color_attribute; GLuint color_screen_size_uniform; - GLuint texture_program; - GLuint texture_vertex_shader; - GLuint texture_fragment_shader; - GLuint texture_position_attribute; - GLuint texture_uv_attribute; - GLuint texture_screen_size_uniform; - GLuint texture_texture_uniform; - GLuint texture_color_transform_uniform; + struct wd_gl_texture_program rgb; GLuint buffers[NUM_BUFFERS]; @@ -90,8 +104,9 @@ precision mediump float;\n\ varying vec2 uv_out;\n\ uniform sampler2D texture;\n\ uniform mat4 color_transform;\n\ +uniform vec4 color_add;\n\ void main(void) {\n\ - gl_FragColor = texture2D(texture, uv_out) * color_transform;\n\ + gl_FragColor = texture2D(texture, uv_out) * color_transform + color_add;\n\ }"; static GLuint gl_make_shader(GLenum type, const char *src) { @@ -142,18 +157,62 @@ static void gl_link_and_validate(GLint program) { } } -struct wd_gl_data *wd_gl_setup(void) { +static void setup_texture_program(struct wd_gl_texture_program *prog, + const char *fragment_src) { + prog->id = glCreateProgram(); + + GLuint vertex_shader = gl_make_shader(GL_VERTEX_SHADER, + texture_vertex_shader_src); + glAttachShader(prog->id, vertex_shader); + GLuint fragment_shader = gl_make_shader(GL_FRAGMENT_SHADER, fragment_src); + glAttachShader(prog->id, fragment_shader); + gl_link_and_validate(prog->id); + + glDeleteShader(fragment_shader); + glDeleteShader(vertex_shader); + + prog->position_attribute = glGetAttribLocation(prog->id, "position"); + prog->uv_attribute = glGetAttribLocation(prog->id, "uv"); + prog->screen_size_uniform = glGetUniformLocation(prog->id, "screen_size"); + prog->texture_uniform = glGetUniformLocation(prog->id, "texture"); + prog->color_transform_uniform = glGetUniformLocation(prog->id, + "color_transform"); + prog->color_add_uniform = glGetUniformLocation(prog->id, "color_add"); +} + +#define assert_ext(_name, _expr) do { \ + static bool _tested_##_name = false; \ + if (!_tested_##_name) { \ + _tested_##_name = true; \ + if (!(_expr)) \ + wd_fatal_error(1, "Extension " #_name " not found\n"); \ + } \ +} while (0) + +#define assert_gl_ext(_name) \ + assert_ext(_name, epoxy_has_gl_extension(#_name)) + +#define assert_egl_ext(_res, _name) \ + assert_ext(_name, epoxy_has_egl_extension((_res)->display, #_name)) + +struct wd_gl_data *wd_gl_setup(struct wl_display *display) { struct wd_gl_data *res = calloc(1, sizeof(struct wd_gl_data)); + res->display = eglGetDisplay(display); res->color_program = glCreateProgram(); - res->color_vertex_shader = gl_make_shader(GL_VERTEX_SHADER, + GLuint vertex_shader = gl_make_shader(GL_VERTEX_SHADER, color_vertex_shader_src); - glAttachShader(res->color_program, res->color_vertex_shader); - res->color_fragment_shader = gl_make_shader(GL_FRAGMENT_SHADER, + glAttachShader(res->color_program, vertex_shader); + GLuint fragment_shader = gl_make_shader(GL_FRAGMENT_SHADER, color_fragment_shader_src); - glAttachShader(res->color_program, res->color_fragment_shader); + glAttachShader(res->color_program, fragment_shader); gl_link_and_validate(res->color_program); + glDeleteShader(fragment_shader); + glDeleteShader(vertex_shader); + + setup_texture_program(&res->rgb, texture_fragment_shader_src); + res->color_position_attribute = glGetAttribLocation(res->color_program, "position"); res->color_color_attribute = glGetAttribLocation(res->color_program, @@ -161,27 +220,6 @@ struct wd_gl_data *wd_gl_setup(void) { res->color_screen_size_uniform = glGetUniformLocation(res->color_program, "screen_size"); - res->texture_program = glCreateProgram(); - - res->texture_vertex_shader = gl_make_shader(GL_VERTEX_SHADER, - texture_vertex_shader_src); - glAttachShader(res->texture_program, res->texture_vertex_shader); - res->texture_fragment_shader = gl_make_shader(GL_FRAGMENT_SHADER, - texture_fragment_shader_src); - glAttachShader(res->texture_program, res->texture_fragment_shader); - gl_link_and_validate(res->texture_program); - - res->texture_position_attribute = glGetAttribLocation(res->texture_program, - "position"); - res->texture_uv_attribute = glGetAttribLocation(res->texture_program, - "uv"); - res->texture_screen_size_uniform = glGetUniformLocation(res->texture_program, - "screen_size"); - res->texture_texture_uniform = glGetUniformLocation(res->texture_program, - "texture"); - res->texture_color_transform_uniform = glGetUniformLocation( - res->texture_program, "color_transform"); - glGenBuffers(NUM_BUFFERS, res->buffers); glBindBuffer(GL_ARRAY_BUFFER, res->buffers[TEXTURE_BUFFER]); glBufferData(GL_ARRAY_BUFFER, BT_UV_MAX * sizeof(float), @@ -198,6 +236,83 @@ struct wd_gl_data *wd_gl_setup(void) { return res; } +EGLImageKHR wd_gl_create_dmabuf_texture(struct wd_gl_data *res, + struct wd_frame *frame) { + assert(!frame->pixels); + assert_egl_ext(res, EGL_KHR_image_base); + assert_egl_ext(res, EGL_EXT_image_dma_buf_import); + + bool has_modifier = frame->modifier != DRM_FORMAT_MOD_INVALID + && frame->modifier != DRM_FORMAT_MOD_LINEAR; + + if (has_modifier) { + assert_egl_ext(res, EGL_EXT_image_dma_buf_import_modifiers); + } + + unsigned int atti = 0; + EGLint attribs[50]; + attribs[atti++] = EGL_WIDTH; + attribs[atti++] = frame->width; + attribs[atti++] = EGL_HEIGHT; + attribs[atti++] = frame->height; + attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; + attribs[atti++] = frame->format; + + struct { + EGLint fd; + EGLint offset; + EGLint pitch; + EGLint mod_lo; + EGLint mod_hi; + } attr_names[WD_MAX_PLANES] = { + { + EGL_DMA_BUF_PLANE0_FD_EXT, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, + EGL_DMA_BUF_PLANE0_PITCH_EXT, + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT + }, { + EGL_DMA_BUF_PLANE1_FD_EXT, + EGL_DMA_BUF_PLANE1_OFFSET_EXT, + EGL_DMA_BUF_PLANE1_PITCH_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT + }, { + EGL_DMA_BUF_PLANE2_FD_EXT, + EGL_DMA_BUF_PLANE2_OFFSET_EXT, + EGL_DMA_BUF_PLANE2_PITCH_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT + }, { + EGL_DMA_BUF_PLANE3_FD_EXT, + EGL_DMA_BUF_PLANE3_OFFSET_EXT, + EGL_DMA_BUF_PLANE3_PITCH_EXT, + EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT + } + }; + + for (int i=0; i < frame->n_planes; i++) { + attribs[atti++] = attr_names[i].fd; + attribs[atti++] = frame->fds[i]; + attribs[atti++] = attr_names[i].offset; + attribs[atti++] = frame->offsets[i]; + attribs[atti++] = attr_names[i].pitch; + attribs[atti++] = frame->strides[i]; + if (has_modifier) { + attribs[atti++] = attr_names[i].mod_lo; + attribs[atti++] = frame->modifier & 0xFFFFFFFF; + attribs[atti++] = attr_names[i].mod_hi; + attribs[atti++] = frame->modifier >> 32; + } + } + attribs[atti++] = EGL_NONE; + assert(atti < sizeof(attribs)/sizeof(attribs[0])); + + return eglCreateImageKHR(res->display, EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, NULL, attribs); +} + static const GLfloat TRANSFORM_RGB[16] = { 1, 0, 0, 0, 0, 1, 0, 0, @@ -320,34 +435,42 @@ void wd_gl_render(struct wd_gl_data *res, struct wd_render_data *info, float screen_size[2] = { info->viewport_width, info->viewport_height }; if (tri_verts > 0) { - glUseProgram(res->texture_program); + glUseProgram(res->rgb.id); glBindBuffer(GL_ARRAY_BUFFER, res->buffers[TEXTURE_BUFFER]); glBufferSubData(GL_ARRAY_BUFFER, 0, tri_verts * BT_UV_VERT_SIZE * sizeof(float), res->verts); - glEnableVertexAttribArray(res->texture_position_attribute); - glEnableVertexAttribArray(res->texture_uv_attribute); - glVertexAttribPointer(res->texture_position_attribute, + glEnableVertexAttribArray(res->rgb.position_attribute); + glEnableVertexAttribArray(res->rgb.uv_attribute); + glVertexAttribPointer(res->rgb.position_attribute, 2, GL_FLOAT, GL_FALSE, BT_UV_VERT_SIZE * sizeof(float), (void *) (0 * sizeof(float))); - glVertexAttribPointer(res->texture_uv_attribute, 2, GL_FLOAT, GL_FALSE, + glVertexAttribPointer(res->rgb.uv_attribute, 2, GL_FLOAT, GL_FALSE, BT_UV_VERT_SIZE * sizeof(float), (void *) (2 * sizeof(float))); - glUniform2fv(res->texture_screen_size_uniform, 1, screen_size); - glUniform1i(res->texture_texture_uniform, 0); + glUniform2fv(res->rgb.screen_size_uniform, 1, screen_size); + glUniform1i(res->rgb.texture_uniform, 0); glActiveTexture(GL_TEXTURE0); i = 0; wl_list_for_each_reverse(head, &info->heads, link) { glBindTexture(GL_TEXTURE_2D, res->textures[i]); if (head->updated_at == tick) { - glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, head->tex_stride / 4); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, - head->tex_width, head->tex_height, - 0, GL_RGBA, GL_UNSIGNED_BYTE, head->pixels); - glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); + if (head->image) { + assert_gl_ext(GL_OES_EGL_image); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, head->image); + } else if (head->pixels) { + assert_gl_ext(GL_EXT_unpack_subimage); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, head->tex_stride / 4); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + head->tex_width, head->tex_height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, head->pixels); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); + } glGenerateMipmap(GL_TEXTURE_2D); } - glUniformMatrix4fv(res->texture_color_transform_uniform, 1, GL_FALSE, + glUniformMatrix4fv(res->rgb.color_transform_uniform, 1, GL_FALSE, head->swap_rgb ? TRANSFORM_RGB : TRANSFORM_BGR); + float color_add[4] = { 0.f, 0.f, 0.f, head->has_alpha ? 0.f : 1.f }; + glUniform4fv(res->rgb.color_add_uniform, 1, color_add); glDrawArrays(GL_TRIANGLES, i * 6, 6); i++; if (i >= HEADS_MAX) @@ -514,13 +637,9 @@ void wd_gl_render(struct wd_gl_data *res, struct wd_render_data *info, void wd_gl_cleanup(struct wd_gl_data *res) { glDeleteBuffers(NUM_BUFFERS, res->buffers); - glDeleteShader(res->texture_fragment_shader); - glDeleteShader(res->texture_vertex_shader); - glDeleteProgram(res->texture_program); - - glDeleteShader(res->color_fragment_shader); - glDeleteShader(res->color_vertex_shader); glDeleteProgram(res->color_program); + glDeleteProgram(res->rgb.id); + free(res); } diff --git a/src/wdisplays.h b/src/wdisplays.h index 4824017..8f7d91e 100644 --- a/src/wdisplays.h +++ b/src/wdisplays.h @@ -27,6 +27,8 @@ struct zxdg_output_manager_v1; struct zwlr_output_mode_v1; struct zwlr_output_head_v1; struct zwlr_output_manager_v1; +struct zwlr_export_dmabuf_manager_v1; +struct zwlr_export_dmabuf_frame_v1; struct zwlr_screencopy_manager_v1; struct zwlr_screencopy_frame_v1; struct zwlr_layer_shell_v1; @@ -41,6 +43,8 @@ typedef struct _GdkCursor GdkCursor; struct _cairo_surface; typedef struct _cairo_surface cairo_surface_t; +typedef void *EGLImageKHR; + struct wd_output { struct wd_state *state; struct zxdg_output_v1 *xdg_output; @@ -53,21 +57,30 @@ struct wd_output { struct zwlr_layer_surface_v1 *overlay_layer_surface; }; +#define WD_MAX_PLANES 4 + struct wd_frame { struct wd_output *output; - struct zwlr_screencopy_frame_v1 *wlr_frame; + struct zwlr_export_dmabuf_frame_v1 *export_frame; + struct zwlr_screencopy_frame_v1 *copy_frame; struct wl_list link; - int capture_fd; - unsigned stride; + unsigned width; unsigned height; + uint64_t tick; + + uint32_t format; + uint32_t flags; + uint64_t modifier; + uint32_t n_planes; + int fds[WD_MAX_PLANES]; + uint32_t offsets[WD_MAX_PLANES]; + uint32_t strides[WD_MAX_PLANES]; struct wl_shm_pool *pool; struct wl_buffer *buffer; uint8_t *pixels; - uint64_t tick; - bool y_invert; - bool swap_rgb; + bool ready; }; struct wd_head_config { @@ -141,15 +154,17 @@ struct wd_render_head_data { struct wd_render_head_flags active; uint8_t *pixels; + EGLImageKHR image; unsigned tex_stride; unsigned tex_width; unsigned tex_height; - bool preview; - bool y_invert; - bool swap_rgb; - bool hovered; - bool clicked; + uint32_t preview : 1, + y_invert : 1, + swap_rgb : 1, + has_alpha : 1, + hovered : 1, + clicked : 1; }; struct wd_render_data { @@ -166,6 +181,7 @@ struct wd_render_data { int x_origin; int y_origin; uint64_t updated_at; + bool external_images; struct wl_list heads; }; @@ -178,6 +194,7 @@ struct wd_point { struct wd_state { struct zxdg_output_manager_v1 *xdg_output_manager; struct zwlr_output_manager_v1 *output_manager; + struct zwlr_export_dmabuf_manager_v1 *export_manager; struct zwlr_screencopy_manager_v1 *copy_manager; struct zwlr_layer_shell_v1 *layer_shell; struct wl_shm *shm; @@ -311,8 +328,14 @@ void wd_ui_show_error(struct wd_state *state, const char *message); /* * Compiles the GL shaders. */ -struct wd_gl_data *wd_gl_setup(void); +struct wd_gl_data *wd_gl_setup(struct wl_display *display); +/* + * Creates an EGLImage from a given output frame. The frame must have a dmabuf + * received from a wlr_export_dmabuf_frame. + */ +EGLImageKHR wd_gl_create_dmabuf_texture(struct wd_gl_data *res, + struct wd_frame *frame); /* * Renders the GL scene. */