From 9e26808c280cb32f32835231a76b5b105011fd1e Mon Sep 17 00:00:00 2001 From: emersion Date: Mon, 21 May 2018 18:50:51 +0100 Subject: [PATCH 01/22] output, backend/drm: add wlr_output_export_dmabuf --- backend/drm/drm.c | 21 +++++++++++++++++++++ backend/drm/renderer.c | 25 +++++++++++++++---------- include/backend/drm/renderer.h | 2 ++ include/wlr/interfaces/wlr_output.h | 2 ++ include/wlr/types/wlr_output.h | 3 +++ types/wlr_output.c | 8 ++++++++ 6 files changed, 51 insertions(+), 10 deletions(-) diff --git a/backend/drm/drm.c b/backend/drm/drm.c index ef8efb9a..5b6054d3 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -254,6 +254,26 @@ static uint32_t drm_connector_get_gamma_size(struct wlr_output *output) { return 0; } +static bool drm_connector_export_dmabuf(struct wlr_output *output, + struct wlr_dmabuf_buffer_attribs *attribs) { + struct wlr_drm_connector *conn = (struct wlr_drm_connector *)output; + struct wlr_drm_backend *drm = (struct wlr_drm_backend *)output->backend; + + if (!drm->session->active) { + return false; + } + + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return false; + } + struct wlr_drm_plane *plane = crtc->primary; + struct wlr_drm_surface *surf = &plane->surf; + + export_drm_bo(surf->back, attribs); + return true; +} + static void drm_connector_start_renderer(struct wlr_drm_connector *conn) { if (conn->state != WLR_DRM_CONN_CONNECTED) { return; @@ -742,6 +762,7 @@ static const struct wlr_output_impl output_impl = { .swap_buffers = drm_connector_swap_buffers, .set_gamma = drm_connector_set_gamma, .get_gamma_size = drm_connector_get_gamma_size, + .export_dmabuf = drm_connector_export_dmabuf, }; bool wlr_output_is_drm(struct wlr_output *output) { diff --git a/backend/drm/renderer.c b/backend/drm/renderer.c index d5bcef2b..2cb47a12 100644 --- a/backend/drm/renderer.c +++ b/backend/drm/renderer.c @@ -160,6 +160,19 @@ void post_drm_surface(struct wlr_drm_surface *surf) { } } +void export_drm_bo(struct gbm_bo *bo, + struct wlr_dmabuf_buffer_attribs *attribs) { + memset(attribs, 0, sizeof(struct wlr_dmabuf_buffer_attribs)); + attribs->n_planes = 1; + attribs->width = gbm_bo_get_width(bo); + attribs->height = gbm_bo_get_height(bo); + attribs->format = gbm_bo_get_format(bo); + attribs->offset[0] = 0; + attribs->stride[0] = gbm_bo_get_stride_for_plane(bo, 0); + attribs->modifier[0] = DRM_FORMAT_MOD_LINEAR; + attribs->fd[0] = gbm_bo_get_fd(bo); +} + struct tex { struct wlr_egl *egl; EGLImageKHR img; @@ -186,16 +199,8 @@ static struct wlr_texture *get_tex_for_bo(struct wlr_drm_renderer *renderer, return NULL; } - struct wlr_dmabuf_buffer_attribs attribs = { - .n_planes = 1, - .width = gbm_bo_get_width(bo), - .height = gbm_bo_get_height(bo), - .format = gbm_bo_get_format(bo), - }; - attribs.offset[0] = 0; - attribs.stride[0] = gbm_bo_get_stride_for_plane(bo, 0); - attribs.modifier[0] = DRM_FORMAT_MOD_LINEAR; - attribs.fd[0] = gbm_bo_get_fd(bo); + struct wlr_dmabuf_buffer_attribs attribs; + export_drm_bo(bo, &attribs); tex->tex = wlr_texture_from_dmabuf(renderer->wlr_rend, &attribs); if (tex->tex == NULL) { diff --git a/include/backend/drm/renderer.h b/include/backend/drm/renderer.h index 510abe43..f26ca3d6 100644 --- a/include/backend/drm/renderer.h +++ b/include/backend/drm/renderer.h @@ -52,5 +52,7 @@ struct gbm_bo *get_drm_surface_front(struct wlr_drm_surface *surf); void post_drm_surface(struct wlr_drm_surface *surf); struct gbm_bo *copy_drm_surface_mgpu(struct wlr_drm_surface *dest, struct gbm_bo *src); +void export_drm_bo(struct gbm_bo *bo, + struct wlr_dmabuf_buffer_attribs *attribs); #endif diff --git a/include/wlr/interfaces/wlr_output.h b/include/wlr/interfaces/wlr_output.h index 7ecc7551..d39e4edc 100644 --- a/include/wlr/interfaces/wlr_output.h +++ b/include/wlr/interfaces/wlr_output.h @@ -23,6 +23,8 @@ struct wlr_output_impl { void (*set_gamma)(struct wlr_output *output, uint32_t size, uint16_t *r, uint16_t *g, uint16_t *b); uint32_t (*get_gamma_size)(struct wlr_output *output); + bool (*export_dmabuf)(struct wlr_output *output, + struct wlr_dmabuf_buffer_attribs *attribs); }; void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index cef3fc5d..669b96ed 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -6,6 +6,7 @@ #include #include #include +#include struct wlr_output_mode { uint32_t flags; // enum wl_output_mode @@ -162,6 +163,8 @@ void wlr_output_schedule_frame(struct wlr_output *output); void wlr_output_set_gamma(struct wlr_output *output, uint32_t size, uint16_t *r, uint16_t *g, uint16_t *b); uint32_t wlr_output_get_gamma_size(struct wlr_output *output); +bool wlr_output_export_dmabuf(struct wlr_output *output, + struct wlr_dmabuf_buffer_attribs *attribs); void wlr_output_set_fullscreen_surface(struct wlr_output *output, struct wlr_surface *surface); struct wlr_output *wlr_output_from_resource(struct wl_resource *resource); diff --git a/types/wlr_output.c b/types/wlr_output.c index a5a6d0eb..f7001b16 100644 --- a/types/wlr_output.c +++ b/types/wlr_output.c @@ -560,6 +560,14 @@ uint32_t wlr_output_get_gamma_size(struct wlr_output *output) { return output->impl->get_gamma_size(output); } +bool wlr_output_export_dmabuf(struct wlr_output *output, + struct wlr_dmabuf_buffer_attribs *attribs) { + if (!output->impl->export_dmabuf) { + return false; + } + return output->impl->export_dmabuf(output, attribs); +} + void wlr_output_update_needs_swap(struct wlr_output *output) { output->needs_swap = true; wlr_signal_emit_safe(&output->events.needs_swap, output); From 36bd4795d4fe2282dfcc59f26863bac2896a4a3f Mon Sep 17 00:00:00 2001 From: emersion Date: Tue, 22 May 2018 17:38:05 +0100 Subject: [PATCH 02/22] export-dmabuf: add basic and incomplete implementation --- include/wlr/types/wlr_export_dmabuf_v1.h | 33 +++ protocol/meson.build | 3 +- protocol/wlr-export-dmabuf-unstable-v1.xml | 230 +++++++++++++++++++++ types/meson.build | 1 + types/wlr_export_dmabuf_v1.c | 185 +++++++++++++++++ 5 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 include/wlr/types/wlr_export_dmabuf_v1.h create mode 100644 protocol/wlr-export-dmabuf-unstable-v1.xml create mode 100644 types/wlr_export_dmabuf_v1.c diff --git a/include/wlr/types/wlr_export_dmabuf_v1.h b/include/wlr/types/wlr_export_dmabuf_v1.h new file mode 100644 index 00000000..78db1e61 --- /dev/null +++ b/include/wlr/types/wlr_export_dmabuf_v1.h @@ -0,0 +1,33 @@ +#ifndef WLR_TYPES_WLR_EXPORT_DMABUF_V1_H +#define WLR_TYPES_WLR_EXPORT_DMABUF_V1_H + +#include + +struct wlr_export_dmabuf_manager_v1; + +struct wlr_export_dmabuf_frame_v1 { + struct wl_resource *resource; + struct wlr_export_dmabuf_manager_v1 *manager; + struct wl_list link; + + struct wlr_output *output; +}; + +struct wlr_export_dmabuf_manager_v1 { + struct wl_global *global; + struct wl_list resources; + struct wl_list frames; + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; +}; + +struct wlr_export_dmabuf_manager_v1 *wlr_export_dmabuf_manager_v1_create( + struct wl_display *display); +void wlr_export_dmabuf_manager_v1_destroy( + struct wlr_export_dmabuf_manager_v1 *manager); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index 8fa64ca9..a14e9723 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -39,8 +39,9 @@ protocols = [ 'screenshooter.xml', 'server-decoration.xml', 'virtual-keyboard-unstable-v1.xml', - 'wlr-layer-shell-unstable-v1.xml', + 'wlr-export-dmabuf-unstable-v1.xml', 'wlr-input-inhibitor-unstable-v1.xml', + 'wlr-layer-shell-unstable-v1.xml', ] client_protocols = [ diff --git a/protocol/wlr-export-dmabuf-unstable-v1.xml b/protocol/wlr-export-dmabuf-unstable-v1.xml new file mode 100644 index 00000000..6332b146 --- /dev/null +++ b/protocol/wlr-export-dmabuf-unstable-v1.xml @@ -0,0 +1,230 @@ + + + + + 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. + Overall usage: + + 1.) client registers with zwlr_screencontent_manager_v1 + 2.) server sends client info about surfaces via "receive_surface_info" + 3.) client subscribes to capture a surface via the "capture" requests + 4.) server sends client events via the "zwlr_screencontent_frame" interface + 5.) client finishes and informs server via the "frame_destroy" event + 6.) client optionally resubscribes via repeating steps 3.) through 5.) + + + + + This object represents a frame which is ready to have its resources + fetched and used. + + The receive callback shall be called first, followed by either the + "dma_object" callback once per object or the "dma_layer" callback, + once per layer. The "dma_plane" callback shall only be called after + the "dma_layer" callback corresponding to the layer the plane belongs + to has been called. Finally, the "ready" event is called to indicate that + all the data has been made available for readout, as well as the time + at which presentation happened at. + The ownership of the frame is passed to the client, who's responsible for + destroying it via the "destroy" event once finished. + The data the API describes has been based off of what + VASurfaceAttribExternalBuffers contains. + All frames are read-only and may not be written into or altered. + + + + + Special flags that must be respected by the client. + Transient frames indicate short lifetime frames (such as swapchain + images from external clients). Clients are advised to copy them and do + all processing outside of the "ready" event. + + + + + + + Main callback supplying the client with information about the frame, + as well as an object to serve as context for destruction. Always called + first before any other events. + + The "transform" argument describes the orientation needed to be applied + to correctly orient the buffer. For example, a buffer rotated by 90 + degrees will have a value of "3" here, corresponding to the need to + apply a 270 degree transpose to correctly present the buffer. + + + + + + + + + + + + + + + Callback which serves to supply the client with the file descriptors + containing the data for each object. + + + + + + + + Callback which serves to supply the client with information on what's + contained in each file descriptor and how its laid out. + Will be called after the main receive event, once per layer. + + + + + + + + Callback which supplies the client with plane information for each + layer. + + + + + + + + + + Called as soon as the frame is presented, indicating it is available + for reading. + 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. + + + + + + + + If the frame is no longer valid after the "frame" event has been called, + this callback will be used to inform the client to scrap the frame. + Source is still valid for as long as the subscription function does not + return NULL. + This may get called if for instance the surface is in the process of + resizing. + + + + + + Unreferences the frame, allowing it to be reused. Must be called as soon + as its no longer used. + + + + + + + This object is a manager which informs clients about capturable windows + and is able to create callbacks from which to begin to receive content + from. The "title" argument in the "surface_info" event shall be used + to provide a user-readable identifier such as a window title or + program name. + + + + + This will be called whenever a surface that's able to be captured + appears. + + + + + + + Called if a surface becomes unavailable to capture, for example if has + been closed. + + + + + + + Request to start capturing from a surface with a given id. + If an ID becomes unavailable, a NULL will be returned. + + + + + + + + Request to start capturing from an entire wl_output. + If an output becomes unavailable, a NULL will be returned. + + + + + + diff --git a/types/meson.build b/types/meson.build index f9f5b469..0842f98c 100644 --- a/types/meson.build +++ b/types/meson.build @@ -22,6 +22,7 @@ lib_wlr_types = static_library( 'wlr_box.c', 'wlr_compositor.c', 'wlr_cursor.c', + 'wlr_export_dmabuf_v1.c', 'wlr_gamma_control.c', 'wlr_idle_inhibit_v1.c', 'wlr_idle.c', diff --git a/types/wlr_export_dmabuf_v1.c b/types/wlr_export_dmabuf_v1.c new file mode 100644 index 00000000..fa732397 --- /dev/null +++ b/types/wlr_export_dmabuf_v1.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include +#include +#include "wlr-export-dmabuf-unstable-v1-protocol.h" + +#define EXPORT_DMABUF_MANAGER_VERSION 1 + + +static const struct zwlr_export_dmabuf_frame_v1_interface frame_impl; + +static struct wlr_export_dmabuf_frame_v1 *frame_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_export_dmabuf_frame_v1_interface, &frame_impl)); + return wl_resource_get_user_data(resource); +} + +static void frame_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwlr_export_dmabuf_frame_v1_interface frame_impl = { + .destroy = frame_handle_destroy, +}; + +static void frame_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_export_dmabuf_frame_v1 *frame = frame_from_resource(resource); + wl_list_remove(&frame->link); + free(frame); +} + + +static const struct zwlr_export_dmabuf_manager_v1_interface manager_impl; + +static struct wlr_export_dmabuf_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_export_dmabuf_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void manager_handle_capture_client(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + uint32_t client_id, int32_t overlay_cursor) { + // TODO +} + +static void manager_handle_capture_output(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *output_resource) { + struct wlr_export_dmabuf_manager_v1 *manager = + manager_from_resource(manager_resource); + struct wlr_output *output = wlr_output_from_resource(output_resource); + + struct wlr_export_dmabuf_frame_v1 *frame = + calloc(1, sizeof(struct wlr_export_dmabuf_frame_v1)); + if (frame == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + frame->manager = manager; + frame->output = output; + + uint32_t version = wl_resource_get_version(manager_resource); + frame->resource = wl_resource_create(client, + &zwlr_export_dmabuf_frame_v1_interface, version, id); + if (frame->resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(frame->resource, &frame_impl, frame, + frame_handle_resource_destroy); + + wl_list_insert(&manager->frames, &frame->link); + + struct wlr_dmabuf_buffer_attribs attribs; + if (!wlr_output_export_dmabuf(output, &attribs)) { + zwlr_export_dmabuf_frame_v1_send_abort(frame->resource); + return; + } + + // TODO: multiple layers support + + uint32_t frame_flags = 0; + uint32_t mod_high = attribs.modifier[0] >> 32; + uint32_t mod_low = attribs.modifier[0]; + + zwlr_export_dmabuf_frame_v1_send_frame(frame->resource, + output->width, output->height, output->scale, output->transform, + attribs.flags, frame_flags, mod_high, mod_low, attribs.n_planes, 1); + + zwlr_export_dmabuf_frame_v1_send_dma_layer(frame->resource, 0, + attribs.format, 1); + + for (int i = 0; i < attribs.n_planes; ++i) { + // TODO: what to do if the kernel doesn't support seek on buffer + off_t size = lseek(attribs.fd[i], 0, SEEK_END); + + zwlr_export_dmabuf_frame_v1_send_dma_object(frame->resource, i, + attribs.fd[i], size); + zwlr_export_dmabuf_frame_v1_send_dma_plane(frame->resource, i, 0, i, + attribs.offset[i], attribs.stride[i]); + } + + // TODO: wait for the frame to be ready + // TODO: timestamps + zwlr_export_dmabuf_frame_v1_send_ready(frame->resource, 0, 0, 0); +} + +static const struct zwlr_export_dmabuf_manager_v1_interface manager_impl = { + .capture_client = manager_handle_capture_client, + .capture_output = manager_handle_capture_output, +}; + +static void manager_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void manager_bind(struct wl_client *client, void *data, uint32_t version, + uint32_t id) { + struct wlr_export_dmabuf_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwlr_export_dmabuf_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, + manager_handle_resource_destroy); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_export_dmabuf_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wlr_export_dmabuf_manager_v1_destroy(manager); +} + +struct wlr_export_dmabuf_manager_v1 *wlr_export_dmabuf_manager_v1_create( + struct wl_display *display) { + struct wlr_export_dmabuf_manager_v1 *manager = + calloc(1, sizeof(struct wlr_export_dmabuf_manager_v1)); + if (manager == NULL) { + return NULL; + } + wl_list_init(&manager->resources); + wl_list_init(&manager->frames); + + manager->global = wl_global_create(display, + &zwlr_export_dmabuf_manager_v1_interface, EXPORT_DMABUF_MANAGER_VERSION, + manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +void wlr_export_dmabuf_manager_v1_destroy( + struct wlr_export_dmabuf_manager_v1 *manager) { + if (manager == NULL) { + return; + } + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, &manager->resources) { + wl_resource_destroy(resource); + } + struct wlr_export_dmabuf_frame_v1 *frame, *frame_tmp; + wl_list_for_each_safe(frame, frame_tmp, &manager->frames, link) { + wl_resource_destroy(frame->resource); + } + free(manager); +} From bd430b8620f5dec485e49269d6add8dc3c20a8ab Mon Sep 17 00:00:00 2001 From: emersion Date: Tue, 22 May 2018 23:07:15 +0100 Subject: [PATCH 03/22] backend/drm: support multi-planar DMA-BUFs when exporting --- backend/drm/drm.c | 3 +-- backend/drm/renderer.c | 33 ++++++++++++++++++++++++++------- include/backend/drm/renderer.h | 2 +- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 5b6054d3..d54c93f8 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -270,8 +270,7 @@ static bool drm_connector_export_dmabuf(struct wlr_output *output, struct wlr_drm_plane *plane = crtc->primary; struct wlr_drm_surface *surf = &plane->surf; - export_drm_bo(surf->back, attribs); - return true; + return export_drm_bo(surf->back, attribs); } static void drm_connector_start_renderer(struct wlr_drm_connector *conn) { diff --git a/backend/drm/renderer.c b/backend/drm/renderer.c index 2cb47a12..15cf8cce 100644 --- a/backend/drm/renderer.c +++ b/backend/drm/renderer.c @@ -160,17 +160,33 @@ void post_drm_surface(struct wlr_drm_surface *surf) { } } -void export_drm_bo(struct gbm_bo *bo, +bool export_drm_bo(struct gbm_bo *bo, struct wlr_dmabuf_buffer_attribs *attribs) { memset(attribs, 0, sizeof(struct wlr_dmabuf_buffer_attribs)); - attribs->n_planes = 1; + + attribs->n_planes = gbm_bo_get_plane_count(bo); + if (attribs->n_planes > WLR_LINUX_DMABUF_MAX_PLANES) { + return false; + } + attribs->width = gbm_bo_get_width(bo); attribs->height = gbm_bo_get_height(bo); attribs->format = gbm_bo_get_format(bo); - attribs->offset[0] = 0; - attribs->stride[0] = gbm_bo_get_stride_for_plane(bo, 0); - attribs->modifier[0] = DRM_FORMAT_MOD_LINEAR; - attribs->fd[0] = gbm_bo_get_fd(bo); + + for (int i = 0; i < attribs->n_planes; ++i) { + attribs->offset[i] = gbm_bo_get_offset(bo, i); + attribs->stride[i] = gbm_bo_get_stride_for_plane(bo, i); + attribs->modifier[i] = gbm_bo_get_modifier(bo); + attribs->fd[i] = gbm_bo_get_fd(bo); + if (attribs->fd[i] < 0) { + for (int j = 0; j < i; ++j) { + close(attribs->fd[j]); + } + return false; + } + } + + return true; } struct tex { @@ -200,7 +216,10 @@ static struct wlr_texture *get_tex_for_bo(struct wlr_drm_renderer *renderer, } struct wlr_dmabuf_buffer_attribs attribs; - export_drm_bo(bo, &attribs); + if (!export_drm_bo(bo, &attribs)) { + free(tex); + return NULL; + } tex->tex = wlr_texture_from_dmabuf(renderer->wlr_rend, &attribs); if (tex->tex == NULL) { diff --git a/include/backend/drm/renderer.h b/include/backend/drm/renderer.h index f26ca3d6..00e26976 100644 --- a/include/backend/drm/renderer.h +++ b/include/backend/drm/renderer.h @@ -52,7 +52,7 @@ struct gbm_bo *get_drm_surface_front(struct wlr_drm_surface *surf); void post_drm_surface(struct wlr_drm_surface *surf); struct gbm_bo *copy_drm_surface_mgpu(struct wlr_drm_surface *dest, struct gbm_bo *src); -void export_drm_bo(struct gbm_bo *bo, +bool export_drm_bo(struct gbm_bo *bo, struct wlr_dmabuf_buffer_attribs *attribs); #endif From 5ba1a9af5652aa2407738ec6290e7a9a59a86ea6 Mon Sep 17 00:00:00 2001 From: emersion Date: Wed, 23 May 2018 00:03:26 +0100 Subject: [PATCH 04/22] render: add wlr_texture_to_dmabuf --- include/wlr/render/egl.h | 5 ++++ include/wlr/render/interface.h | 2 ++ include/wlr/render/wlr_texture.h | 3 +++ render/egl.c | 46 +++++++++++++++++++++++++++++--- render/glapi.txt | 2 ++ render/gles2/texture.c | 29 ++++++++++++++++++++ render/wlr_texture.c | 8 ++++++ 7 files changed, 92 insertions(+), 3 deletions(-) diff --git a/include/wlr/render/egl.h b/include/wlr/render/egl.h index 4d837138..17fef7ed 100644 --- a/include/wlr/render/egl.h +++ b/include/wlr/render/egl.h @@ -20,6 +20,7 @@ struct wlr_egl { bool swap_buffers_with_damage; bool dmabuf_import; bool dmabuf_import_modifiers; + bool dmabuf_export; bool bind_wayland_display; } egl_exts; @@ -85,6 +86,10 @@ int wlr_egl_get_dmabuf_formats(struct wlr_egl *egl, int **formats); int wlr_egl_get_dmabuf_modifiers(struct wlr_egl *egl, int format, uint64_t **modifiers); +bool wlr_egl_export_image_to_dmabuf(struct wlr_egl *egl, EGLImageKHR image, + int32_t width, int32_t height, uint32_t flags, + struct wlr_dmabuf_buffer_attribs *attribs); + /** * 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 2267d376..1f075e81 100644 --- a/include/wlr/render/interface.h +++ b/include/wlr/render/interface.h @@ -62,6 +62,8 @@ struct wlr_texture_impl { enum wl_shm_format wl_fmt, uint32_t stride, uint32_t width, uint32_t height, uint32_t src_x, uint32_t src_y, uint32_t dst_x, uint32_t dst_y, const void *data); + bool (*to_dmabuf)(struct wlr_texture *texture, + struct wlr_dmabuf_buffer_attribs *attribs); void (*destroy)(struct wlr_texture *texture); }; diff --git a/include/wlr/render/wlr_texture.h b/include/wlr/render/wlr_texture.h index 239fc51b..481b2a37 100644 --- a/include/wlr/render/wlr_texture.h +++ b/include/wlr/render/wlr_texture.h @@ -48,6 +48,9 @@ bool wlr_texture_write_pixels(struct wlr_texture *texture, uint32_t src_x, uint32_t src_y, uint32_t dst_x, uint32_t dst_y, const void *data); +bool wlr_texture_to_dmabuf(struct wlr_texture *texture, + struct wlr_dmabuf_buffer_attribs *attribs); + /** * Destroys this wlr_texture. */ diff --git a/render/egl.c b/render/egl.c index 579bb5fe..1182a72f 100644 --- a/render/egl.c +++ b/render/egl.c @@ -178,8 +178,12 @@ bool wlr_egl_init(struct wlr_egl *egl, EGLenum platform, void *remote_display, check_egl_ext(egl->exts_str, "EGL_EXT_image_dma_buf_import_modifiers") && eglQueryDmaBufFormatsEXT && eglQueryDmaBufModifiersEXT; + egl->egl_exts.dmabuf_export = + check_egl_ext(egl->exts_str, "EGL_MESA_image_dma_buf_export"); + egl->egl_exts.bind_wayland_display = check_egl_ext(egl->exts_str, "EGL_WL_bind_wayland_display"); + print_dmabuf_formats(egl); return true; @@ -209,7 +213,7 @@ void wlr_egl_finish(struct wlr_egl *egl) { } bool wlr_egl_bind_display(struct wlr_egl *egl, struct wl_display *local_display) { - if (!egl->egl_exts.bind_wayland_display) { + if (!egl->egl_exts.bind_wayland_display || !eglBindWaylandDisplayWL) { return false; } @@ -414,7 +418,7 @@ EGLImageKHR wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl, } #ifndef DRM_FORMAT_BIG_ENDIAN -# define DRM_FORMAT_BIG_ENDIAN 0x80000000 +#define DRM_FORMAT_BIG_ENDIAN 0x80000000 #endif bool wlr_egl_check_import_dmabuf(struct wlr_egl *egl, struct wlr_dmabuf_buffer *dmabuf) { @@ -446,7 +450,7 @@ bool wlr_egl_check_import_dmabuf(struct wlr_egl *egl, int wlr_egl_get_dmabuf_formats(struct wlr_egl *egl, int **formats) { if (!egl->egl_exts.dmabuf_import || - !egl->egl_exts.dmabuf_import_modifiers) { + !egl->egl_exts.dmabuf_import_modifiers) { wlr_log(L_DEBUG, "dmabuf extension not present"); return -1; } @@ -501,6 +505,42 @@ int wlr_egl_get_dmabuf_modifiers(struct wlr_egl *egl, return num; } +bool wlr_egl_export_image_to_dmabuf(struct wlr_egl *egl, EGLImageKHR image, + int32_t width, int32_t height, uint32_t flags, + struct wlr_dmabuf_buffer_attribs *attribs) { + memset(attribs, 0, sizeof(struct wlr_dmabuf_buffer_attribs)); + + if (!egl->egl_exts.dmabuf_export || !eglExportDMABUFImageQueryMESA || + !eglExportDMABUFImageMESA) { + return false; + } + + // Only one set of modifiers is returned for all planes + EGLuint64KHR modifiers; + if (!eglExportDMABUFImageQueryMESA(egl->display, image, + (int *)&attribs->format, &attribs->n_planes, &modifiers)) { + return false; + } + if (attribs->n_planes > WLR_LINUX_DMABUF_MAX_PLANES) { + wlr_log(L_ERROR, "EGL returned %d planes, but only %d are supported", + attribs->n_planes, WLR_LINUX_DMABUF_MAX_PLANES); + return false; + } + for (int i = 0; i < attribs->n_planes; ++i) { + attribs->modifier[i] = modifiers; + } + + if (!eglExportDMABUFImageMESA(egl->display, image, attribs->fd, + (EGLint *)attribs->stride, (EGLint *)attribs->offset)) { + return false; + } + + attribs->width = width; + attribs->height = height; + attribs->flags = flags; + return true; +} + bool wlr_egl_destroy_surface(struct wlr_egl *egl, EGLSurface surface) { if (!surface) { return true; diff --git a/render/glapi.txt b/render/glapi.txt index a8e4aaba..b1166f27 100644 --- a/render/glapi.txt +++ b/render/glapi.txt @@ -10,6 +10,8 @@ eglCreatePlatformWindowSurfaceEXT -eglSwapBuffersWithDamageKHR -eglQueryDmaBufFormatsEXT -eglQueryDmaBufModifiersEXT +-eglExportDMABUFImageQueryMESA +-eglExportDMABUFImageMESA -eglDebugMessageControlKHR -glDebugMessageCallbackKHR -glDebugMessageControlKHR diff --git a/render/gles2/texture.c b/render/gles2/texture.c index 37424802..742f9da0 100644 --- a/render/gles2/texture.c +++ b/render/gles2/texture.c @@ -74,6 +74,34 @@ static bool gles2_texture_write_pixels(struct wlr_texture *wlr_texture, return true; } +static bool gles2_texture_to_dmabuf(struct wlr_texture *wlr_texture, + struct wlr_dmabuf_buffer_attribs *attribs) { + struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture); + + if (!texture->image) { + assert(texture->type == WLR_GLES2_TEXTURE_GLTEX); + + if (!eglCreateImageKHR) { + return false; + } + + texture->image = eglCreateImageKHR(texture->egl->display, + texture->egl->context, EGL_GL_TEXTURE_2D_KHR, + (EGLClientBuffer)(uintptr_t)texture->gl_tex, NULL); + if (texture->image == EGL_NO_IMAGE_KHR) { + return false; + } + } + + uint32_t flags = 0; + if (texture->inverted_y) { + flags |= WLR_DMABUF_BUFFER_ATTRIBS_FLAGS_Y_INVERT; + } + + return wlr_egl_export_image_to_dmabuf(texture->egl, texture->image, + texture->width, texture->height, flags, attribs); +} + static void gles2_texture_destroy(struct wlr_texture *wlr_texture) { if (wlr_texture == NULL) { return; @@ -102,6 +130,7 @@ static void gles2_texture_destroy(struct wlr_texture *wlr_texture) { static const struct wlr_texture_impl texture_impl = { .get_size = gles2_texture_get_size, .write_pixels = gles2_texture_write_pixels, + .to_dmabuf = gles2_texture_to_dmabuf, .destroy = gles2_texture_destroy, }; diff --git a/render/wlr_texture.c b/render/wlr_texture.c index 9aecfd98..f7ce0b44 100644 --- a/render/wlr_texture.c +++ b/render/wlr_texture.c @@ -54,3 +54,11 @@ bool wlr_texture_write_pixels(struct wlr_texture *texture, return texture->impl->write_pixels(texture, wl_fmt, stride, width, height, src_x, src_y, dst_x, dst_y, data); } + +bool wlr_texture_to_dmabuf(struct wlr_texture *texture, + struct wlr_dmabuf_buffer_attribs *attribs) { + if (!texture->impl->to_dmabuf) { + return false; + } + return texture->impl->to_dmabuf(texture, attribs); +} From e26f4dff9843c51762852642719aa0eee1449aca Mon Sep 17 00:00:00 2001 From: emersion Date: Wed, 23 May 2018 09:02:19 +0100 Subject: [PATCH 05/22] export-dmabuf: wait for the frame to be ready, send timestamp --- include/wlr/types/wlr_export_dmabuf_v1.h | 2 ++ include/wlr/types/wlr_output.h | 8 +++++++- types/wlr_export_dmabuf_v1.c | 23 +++++++++++++++++++---- types/wlr_output.c | 7 ++++++- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/include/wlr/types/wlr_export_dmabuf_v1.h b/include/wlr/types/wlr_export_dmabuf_v1.h index 78db1e61..4cb3393f 100644 --- a/include/wlr/types/wlr_export_dmabuf_v1.h +++ b/include/wlr/types/wlr_export_dmabuf_v1.h @@ -11,6 +11,8 @@ struct wlr_export_dmabuf_frame_v1 { struct wl_list link; struct wlr_output *output; + + struct wl_listener output_swap_buffers; }; struct wlr_export_dmabuf_manager_v1 { diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 669b96ed..96c98dc3 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -82,7 +82,7 @@ struct wlr_output { struct { struct wl_signal frame; struct wl_signal needs_swap; - struct wl_signal swap_buffers; + struct wl_signal swap_buffers; // wlr_output_event_swap_buffers struct wl_signal enable; struct wl_signal mode; struct wl_signal scale; @@ -108,6 +108,12 @@ struct wlr_output { void *data; }; +struct wlr_output_event_swap_buffers { + struct wlr_output *output; + struct timespec *when; + pixman_region32_t *damage; +}; + struct wlr_surface; void wlr_output_enable(struct wlr_output *output, bool enable); diff --git a/types/wlr_export_dmabuf_v1.c b/types/wlr_export_dmabuf_v1.c index fa732397..2f1c88f2 100644 --- a/types/wlr_export_dmabuf_v1.c +++ b/types/wlr_export_dmabuf_v1.c @@ -30,9 +30,25 @@ static const struct zwlr_export_dmabuf_frame_v1_interface frame_impl = { static void frame_handle_resource_destroy(struct wl_resource *resource) { struct wlr_export_dmabuf_frame_v1 *frame = frame_from_resource(resource); wl_list_remove(&frame->link); + wl_list_remove(&frame->output_swap_buffers.link); free(frame); } +static void frame_output_handle_swap_buffers(struct wl_listener *listener, + void *data) { + struct wlr_export_dmabuf_frame_v1 *frame = + wl_container_of(listener, frame, output_swap_buffers); + struct wlr_output_event_swap_buffers *event = data; + + wl_list_remove(&frame->output_swap_buffers.link); + wl_list_init(&frame->output_swap_buffers.link); + + uint32_t tv_sec_hi = event->when->tv_sec << 32; + uint32_t tv_sec_lo = event->when->tv_sec & 0xFFFFFFFF; + zwlr_export_dmabuf_frame_v1_send_ready(frame->resource, + tv_sec_hi, tv_sec_lo, event->when->tv_nsec); +} + static const struct zwlr_export_dmabuf_manager_v1_interface manager_impl; @@ -87,7 +103,7 @@ static void manager_handle_capture_output(struct wl_client *client, uint32_t frame_flags = 0; uint32_t mod_high = attribs.modifier[0] >> 32; - uint32_t mod_low = attribs.modifier[0]; + uint32_t mod_low = attribs.modifier[0] & 0xFFFFFFFF; zwlr_export_dmabuf_frame_v1_send_frame(frame->resource, output->width, output->height, output->scale, output->transform, @@ -106,9 +122,8 @@ static void manager_handle_capture_output(struct wl_client *client, attribs.offset[i], attribs.stride[i]); } - // TODO: wait for the frame to be ready - // TODO: timestamps - zwlr_export_dmabuf_frame_v1_send_ready(frame->resource, 0, 0, 0); + frame->output_swap_buffers.notify = frame_output_handle_swap_buffers; + wl_signal_add(&output->events.swap_buffers, &frame->output_swap_buffers); } static const struct zwlr_export_dmabuf_manager_v1_interface manager_impl = { diff --git a/types/wlr_output.c b/types/wlr_output.c index f7001b16..b0321d7f 100644 --- a/types/wlr_output.c +++ b/types/wlr_output.c @@ -467,7 +467,12 @@ bool wlr_output_swap_buffers(struct wlr_output *output, struct timespec *when, output->idle_frame = NULL; } - wlr_signal_emit_safe(&output->events.swap_buffers, damage); + struct wlr_output_event_swap_buffers event = { + .output = output, + .when = when, + .damage = damage, + }; + wlr_signal_emit_safe(&output->events.swap_buffers, &event); int width, height; wlr_output_transformed_resolution(output, &width, &height); From 7901740f940dbaf5f674d533808898157790863a Mon Sep 17 00:00:00 2001 From: emersion Date: Wed, 23 May 2018 09:07:40 +0100 Subject: [PATCH 06/22] rootston: enable export-dmabuf --- include/rootston/desktop.h | 1 + rootston/desktop.c | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/include/rootston/desktop.h b/include/rootston/desktop.h index bb7d2084..dfe070ca 100644 --- a/include/rootston/desktop.h +++ b/include/rootston/desktop.h @@ -45,6 +45,7 @@ struct roots_desktop { struct wlr_xdg_shell *xdg_shell; struct wlr_gamma_control_manager *gamma_control_manager; struct wlr_screenshooter *screenshooter; + struct wlr_export_dmabuf_manager_v1 *export_dmabuf_manager_v1; struct wlr_server_decoration_manager *server_decoration_manager; struct wlr_primary_selection_device_manager *primary_selection_device_manager; struct wlr_idle *idle; diff --git a/rootston/desktop.c b/rootston/desktop.c index a6f9e9a0..2bba06e2 100644 --- a/rootston/desktop.c +++ b/rootston/desktop.c @@ -7,9 +7,10 @@ #include #include #include +#include #include -#include #include +#include #include #include #include @@ -18,9 +19,9 @@ #include #include #include +#include #include #include -#include #include #include "rootston/layers.h" #include "rootston/seat.h" @@ -845,6 +846,8 @@ struct roots_desktop *desktop_create(struct roots_server *server, desktop->gamma_control_manager = wlr_gamma_control_manager_create( server->wl_display); desktop->screenshooter = wlr_screenshooter_create(server->wl_display); + desktop->export_dmabuf_manager_v1 = + wlr_export_dmabuf_manager_v1_create(server->wl_display); desktop->server_decoration_manager = wlr_server_decoration_manager_create(server->wl_display); wlr_server_decoration_manager_set_default_mode( From 1377e551ef583976142bfb98a1ba5b61f1cc1196 Mon Sep 17 00:00:00 2001 From: emersion Date: Wed, 23 May 2018 22:11:45 +0100 Subject: [PATCH 07/22] Update wlr-export-dmabuf protocol --- protocol/wlr-export-dmabuf-unstable-v1.xml | 66 ++++++++++++++-------- types/wlr_export_dmabuf_v1.c | 14 +++-- 2 files changed, 52 insertions(+), 28 deletions(-) diff --git a/protocol/wlr-export-dmabuf-unstable-v1.xml b/protocol/wlr-export-dmabuf-unstable-v1.xml index 6332b146..ab9694a6 100644 --- a/protocol/wlr-export-dmabuf-unstable-v1.xml +++ b/protocol/wlr-export-dmabuf-unstable-v1.xml @@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE. - + An interface to capture surfaces in an efficient way. Overall usage: @@ -41,28 +41,27 @@ This object represents a frame which is ready to have its resources fetched and used. - The receive callback shall be called first, followed by either the - "dma_object" callback once per object or the "dma_layer" callback, - once per layer. The "dma_plane" callback shall only be called after - the "dma_layer" callback corresponding to the layer the plane belongs - to has been called. Finally, the "ready" event is called to indicate that - all the data has been made available for readout, as well as the time - at which presentation happened at. - The ownership of the frame is passed to the client, who's responsible for - destroying it via the "destroy" event once finished. + The receive callback shall be called first, followed by the "object" + callback once per dmabuf object or the "layer" callback, once per dmabuf + layer. The "plane" callback shall only be called after the "layer" + callback corresponding to the layer the plane belongs to has been called + Finally, the "ready" event is called to indicate that all the data has + been made available for readout, as well as the time at which presentation + happened at. The ownership of the frame is passed to the client, who's + responsible for destroying it via the "destroy" event once finished. The data the API describes has been based off of what VASurfaceAttribExternalBuffers contains. All frames are read-only and may not be written into or altered. - + Special flags that must be respected by the client. Transient frames indicate short lifetime frames (such as swapchain images from external clients). Clients are advised to copy them and do all processing outside of the "ready" event. - + @@ -85,10 +84,10 @@ - - @@ -99,7 +98,7 @@ - + Callback which serves to supply the client with the file descriptors containing the data for each object. @@ -111,7 +110,7 @@ - + Callback which serves to supply the client with information on what's contained in each file descriptor and how its laid out. @@ -124,7 +123,7 @@ - + Callback which supplies the client with plane information for each layer. @@ -158,6 +157,21 @@ + + + + Indicates reason for aborting the frame. + + + + + + If the frame is no longer valid after the "frame" event has been called, @@ -167,6 +181,8 @@ This may get called if for instance the surface is in the process of resizing. + @@ -208,23 +224,29 @@ Request to start capturing from a surface with a given id. - If an ID becomes unavailable, a NULL will be returned. + - Request to start capturing from an entire wl_output. - If an output becomes unavailable, a NULL will be returned. - + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + diff --git a/types/wlr_export_dmabuf_v1.c b/types/wlr_export_dmabuf_v1.c index 2f1c88f2..4a7382a3 100644 --- a/types/wlr_export_dmabuf_v1.c +++ b/types/wlr_export_dmabuf_v1.c @@ -61,13 +61,13 @@ static struct wlr_export_dmabuf_manager_v1 *manager_from_resource( static void manager_handle_capture_client(struct wl_client *client, struct wl_resource *manager_resource, uint32_t id, - uint32_t client_id, int32_t overlay_cursor) { + int32_t overlay_cursor, uint32_t client_id) { // TODO } static void manager_handle_capture_output(struct wl_client *client, struct wl_resource *manager_resource, uint32_t id, - struct wl_resource *output_resource) { + int32_t overlay_cursor, struct wl_resource *output_resource) { struct wlr_export_dmabuf_manager_v1 *manager = manager_from_resource(manager_resource); struct wlr_output *output = wlr_output_from_resource(output_resource); @@ -95,7 +95,9 @@ static void manager_handle_capture_output(struct wl_client *client, struct wlr_dmabuf_buffer_attribs attribs; if (!wlr_output_export_dmabuf(output, &attribs)) { - zwlr_export_dmabuf_frame_v1_send_abort(frame->resource); + wl_list_init(&frame->output_swap_buffers.link); + // TODO: abort reason + zwlr_export_dmabuf_frame_v1_send_abort(frame->resource, 0); return; } @@ -109,16 +111,16 @@ static void manager_handle_capture_output(struct wl_client *client, output->width, output->height, output->scale, output->transform, attribs.flags, frame_flags, mod_high, mod_low, attribs.n_planes, 1); - zwlr_export_dmabuf_frame_v1_send_dma_layer(frame->resource, 0, + zwlr_export_dmabuf_frame_v1_send_layer(frame->resource, 0, attribs.format, 1); for (int i = 0; i < attribs.n_planes; ++i) { // TODO: what to do if the kernel doesn't support seek on buffer off_t size = lseek(attribs.fd[i], 0, SEEK_END); - zwlr_export_dmabuf_frame_v1_send_dma_object(frame->resource, i, + zwlr_export_dmabuf_frame_v1_send_object(frame->resource, i, attribs.fd[i], size); - zwlr_export_dmabuf_frame_v1_send_dma_plane(frame->resource, i, 0, i, + zwlr_export_dmabuf_frame_v1_send_plane(frame->resource, i, 0, i, attribs.offset[i], attribs.stride[i]); } From 00e108f2fcd0c41d361a45149ad6a310a0a4b85d Mon Sep 17 00:00:00 2001 From: emersion Date: Wed, 23 May 2018 23:27:15 +0100 Subject: [PATCH 08/22] export-dmabuf: correctly finish wlr_linux_dmabuf_attribs --- include/wlr/types/wlr_export_dmabuf_v1.h | 2 ++ include/wlr/types/wlr_linux_dmabuf.h | 6 ++++++ types/wlr_export_dmabuf_v1.c | 27 +++++++++++++----------- types/wlr_linux_dmabuf.c | 15 ++++++++----- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/include/wlr/types/wlr_export_dmabuf_v1.h b/include/wlr/types/wlr_export_dmabuf_v1.h index 4cb3393f..218b1635 100644 --- a/include/wlr/types/wlr_export_dmabuf_v1.h +++ b/include/wlr/types/wlr_export_dmabuf_v1.h @@ -2,6 +2,7 @@ #define WLR_TYPES_WLR_EXPORT_DMABUF_V1_H #include +#include struct wlr_export_dmabuf_manager_v1; @@ -10,6 +11,7 @@ struct wlr_export_dmabuf_frame_v1 { struct wlr_export_dmabuf_manager_v1 *manager; struct wl_list link; + struct wlr_dmabuf_buffer_attribs attribs; struct wlr_output *output; struct wl_listener output_swap_buffers; diff --git a/include/wlr/types/wlr_linux_dmabuf.h b/include/wlr/types/wlr_linux_dmabuf.h index 531e68ab..1677398b 100644 --- a/include/wlr/types/wlr_linux_dmabuf.h +++ b/include/wlr/types/wlr_linux_dmabuf.h @@ -37,6 +37,12 @@ struct wlr_dmabuf_buffer { struct wlr_dmabuf_buffer_attribs attributes; }; +/** + * Closes all file descriptors in the DMA-BUF attributes. + */ +void wlr_dmabuf_buffer_attribs_finish( + struct wlr_dmabuf_buffer_attribs *attribs); + /** * Returns true if the given resource was created via the linux-dmabuf * buffer protocol, false otherwise diff --git a/types/wlr_export_dmabuf_v1.c b/types/wlr_export_dmabuf_v1.c index 4a7382a3..1b284c44 100644 --- a/types/wlr_export_dmabuf_v1.c +++ b/types/wlr_export_dmabuf_v1.c @@ -5,6 +5,7 @@ #include #include #include "wlr-export-dmabuf-unstable-v1-protocol.h" +#include #define EXPORT_DMABUF_MANAGER_VERSION 1 @@ -31,6 +32,7 @@ static void frame_handle_resource_destroy(struct wl_resource *resource) { struct wlr_export_dmabuf_frame_v1 *frame = frame_from_resource(resource); wl_list_remove(&frame->link); wl_list_remove(&frame->output_swap_buffers.link); + wlr_dmabuf_buffer_attribs_finish(&frame->attribs); free(frame); } @@ -80,6 +82,7 @@ static void manager_handle_capture_output(struct wl_client *client, } frame->manager = manager; frame->output = output; + wl_list_init(&frame->output_swap_buffers.link); uint32_t version = wl_resource_get_version(manager_resource); frame->resource = wl_resource_create(client, @@ -93,9 +96,8 @@ static void manager_handle_capture_output(struct wl_client *client, wl_list_insert(&manager->frames, &frame->link); - struct wlr_dmabuf_buffer_attribs attribs; - if (!wlr_output_export_dmabuf(output, &attribs)) { - wl_list_init(&frame->output_swap_buffers.link); + struct wlr_dmabuf_buffer_attribs *attribs = &frame->attribs; + if (!wlr_output_export_dmabuf(output, attribs)) { // TODO: abort reason zwlr_export_dmabuf_frame_v1_send_abort(frame->resource, 0); return; @@ -104,28 +106,29 @@ static void manager_handle_capture_output(struct wl_client *client, // TODO: multiple layers support uint32_t frame_flags = 0; - uint32_t mod_high = attribs.modifier[0] >> 32; - uint32_t mod_low = attribs.modifier[0] & 0xFFFFFFFF; + uint32_t mod_high = attribs->modifier[0] >> 32; + uint32_t mod_low = attribs->modifier[0] & 0xFFFFFFFF; zwlr_export_dmabuf_frame_v1_send_frame(frame->resource, output->width, output->height, output->scale, output->transform, - attribs.flags, frame_flags, mod_high, mod_low, attribs.n_planes, 1); + attribs->flags, frame_flags, mod_high, mod_low, attribs->n_planes, 1); zwlr_export_dmabuf_frame_v1_send_layer(frame->resource, 0, - attribs.format, 1); + attribs->format, 1); - for (int i = 0; i < attribs.n_planes; ++i) { + for (int i = 0; i < attribs->n_planes; ++i) { // TODO: what to do if the kernel doesn't support seek on buffer - off_t size = lseek(attribs.fd[i], 0, SEEK_END); + off_t size = lseek(attribs->fd[i], 0, SEEK_END); zwlr_export_dmabuf_frame_v1_send_object(frame->resource, i, - attribs.fd[i], size); + attribs->fd[i], size); zwlr_export_dmabuf_frame_v1_send_plane(frame->resource, i, 0, i, - attribs.offset[i], attribs.stride[i]); + attribs->offset[i], attribs->stride[i]); } - frame->output_swap_buffers.notify = frame_output_handle_swap_buffers; + wl_list_remove(&frame->output_swap_buffers.link); wl_signal_add(&output->events.swap_buffers, &frame->output_swap_buffers); + frame->output_swap_buffers.notify = frame_output_handle_swap_buffers; } static const struct zwlr_export_dmabuf_manager_v1_interface manager_impl = { diff --git a/types/wlr_linux_dmabuf.c b/types/wlr_linux_dmabuf.c index 9bcd473f..8f46539f 100644 --- a/types/wlr_linux_dmabuf.c +++ b/types/wlr_linux_dmabuf.c @@ -51,12 +51,17 @@ struct wlr_dmabuf_buffer *wlr_dmabuf_buffer_from_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; +void wlr_dmabuf_buffer_attribs_finish( + struct wlr_dmabuf_buffer_attribs *attribs) { + for (int i = 0; i < attribs->n_planes; ++i) { + close(attribs->fd[i]); + attribs->fd[i] = -1; } - buffer->attributes.n_planes = 0; + attribs->n_planes = 0; +} + +static void linux_dmabuf_buffer_destroy(struct wlr_dmabuf_buffer *buffer) { + wlr_dmabuf_buffer_attribs_finish(&buffer->attributes); free(buffer); } From 3f9796112ee2474bade668dede226b5e6f6144db Mon Sep 17 00:00:00 2001 From: emersion Date: Wed, 23 May 2018 23:56:05 +0100 Subject: [PATCH 09/22] export-dmabuf: fix tv_sec_hi --- types/wlr_export_dmabuf_v1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/wlr_export_dmabuf_v1.c b/types/wlr_export_dmabuf_v1.c index 1b284c44..cbd54749 100644 --- a/types/wlr_export_dmabuf_v1.c +++ b/types/wlr_export_dmabuf_v1.c @@ -45,7 +45,7 @@ static void frame_output_handle_swap_buffers(struct wl_listener *listener, wl_list_remove(&frame->output_swap_buffers.link); wl_list_init(&frame->output_swap_buffers.link); - uint32_t tv_sec_hi = event->when->tv_sec << 32; + uint32_t tv_sec_hi = event->when->tv_sec >> 32; uint32_t tv_sec_lo = event->when->tv_sec & 0xFFFFFFFF; zwlr_export_dmabuf_frame_v1_send_ready(frame->resource, tv_sec_hi, tv_sec_lo, event->when->tv_nsec); From 2432c41a49277351f9159d2c7b0520b83f6dbbf0 Mon Sep 17 00:00:00 2001 From: emersion Date: Fri, 25 May 2018 11:11:29 +0100 Subject: [PATCH 10/22] export-dmabuf: correctly send the number of planes per layer --- types/wlr_export_dmabuf_v1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/wlr_export_dmabuf_v1.c b/types/wlr_export_dmabuf_v1.c index cbd54749..590d5ffb 100644 --- a/types/wlr_export_dmabuf_v1.c +++ b/types/wlr_export_dmabuf_v1.c @@ -114,7 +114,7 @@ static void manager_handle_capture_output(struct wl_client *client, attribs->flags, frame_flags, mod_high, mod_low, attribs->n_planes, 1); zwlr_export_dmabuf_frame_v1_send_layer(frame->resource, 0, - attribs->format, 1); + attribs->format, attribs->n_planes); for (int i = 0; i < attribs->n_planes; ++i) { // TODO: what to do if the kernel doesn't support seek on buffer From 65198b0aab2d77b8ea91696d94b045b440abefea Mon Sep 17 00:00:00 2001 From: emersion Date: Fri, 25 May 2018 13:48:46 +0100 Subject: [PATCH 11/22] export-dmabuf: assert number of planes --- types/wlr_export_dmabuf_v1.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/types/wlr_export_dmabuf_v1.c b/types/wlr_export_dmabuf_v1.c index 590d5ffb..572da262 100644 --- a/types/wlr_export_dmabuf_v1.c +++ b/types/wlr_export_dmabuf_v1.c @@ -102,8 +102,7 @@ static void manager_handle_capture_output(struct wl_client *client, zwlr_export_dmabuf_frame_v1_send_abort(frame->resource, 0); return; } - - // TODO: multiple layers support + assert(attribs->n_planes > 0); uint32_t frame_flags = 0; uint32_t mod_high = attribs->modifier[0] >> 32; From a16ad4327a07157ef2477e036456e8533c47a16e Mon Sep 17 00:00:00 2001 From: emersion Date: Sat, 26 May 2018 08:15:49 +0100 Subject: [PATCH 12/22] Update protocol --- protocol/wlr-export-dmabuf-unstable-v1.xml | 117 ++++++--------------- types/wlr_export_dmabuf_v1.c | 24 ++--- 2 files changed, 40 insertions(+), 101 deletions(-) diff --git a/protocol/wlr-export-dmabuf-unstable-v1.xml b/protocol/wlr-export-dmabuf-unstable-v1.xml index ab9694a6..760345a7 100644 --- a/protocol/wlr-export-dmabuf-unstable-v1.xml +++ b/protocol/wlr-export-dmabuf-unstable-v1.xml @@ -42,26 +42,21 @@ fetched and used. The receive callback shall be called first, followed by the "object" - callback once per dmabuf object or the "layer" callback, once per dmabuf - layer. The "plane" callback shall only be called after the "layer" - callback corresponding to the layer the plane belongs to has been called - Finally, the "ready" event is called to indicate that all the data has + callback once per dmabuf object or the "plane" callback, once per dmabuf + plane. The "ready" event is called last to indicate that all the data has been made available for readout, as well as the time at which presentation happened at. The ownership of the frame is passed to the client, who's - responsible for destroying it via the "destroy" event once finished. - The data the API describes has been based off of what - VASurfaceAttribExternalBuffers contains. + responsible for destroying it via the "destroy" event once finished and + by calling close() on the file descriptors received. All frames are read-only and may not be written into or altered. - Special flags that must be respected by the client. - Transient frames indicate short lifetime frames (such as swapchain - images from external clients). Clients are advised to copy them and do - all processing outside of the "ready" event. + Special flags that should be respected by the client. - + @@ -79,24 +74,25 @@ summary="frame width, scaling factor included"/> - - + + + - + @@ -110,28 +106,13 @@ - - - Callback which serves to supply the client with information on what's - contained in each file descriptor and how its laid out. - Will be called after the main receive event, once per layer. - - - - - Callback which supplies the client with plane information for each - layer. + plane. - - - - Indicates reason for aborting the frame. + + + Indicates reason for cancelling the frame. - - - + + + - + If the frame is no longer valid after the "frame" event has been called, this callback will be used to inform the client to scrap the frame. @@ -181,57 +160,27 @@ This may get called if for instance the surface is in the process of resizing. - + Unreferences the frame, allowing it to be reused. Must be called as soon as its no longer used. + Can be called at any time by the client after the "frame" event, after + which the compositor will not call any other events unless the client + resubscribes to capture more. The client will still have to close any + FDs it has been given. - This object is a manager which informs clients about capturable windows - and is able to create callbacks from which to begin to receive content - from. The "title" argument in the "surface_info" event shall be used - to provide a user-readable identifier such as a window title or - program name. + This object is a manager with which to start capturing from sources. - - - This will be called whenever a surface that's able to be captured - appears. - - - - - - - Called if a surface becomes unavailable to capture, for example if has - been closed. - - - - - - - Request to start capturing from a surface with a given id. - - - - - Request to start capturing from an entire wl_output. diff --git a/types/wlr_export_dmabuf_v1.c b/types/wlr_export_dmabuf_v1.c index 572da262..a2faf2ff 100644 --- a/types/wlr_export_dmabuf_v1.c +++ b/types/wlr_export_dmabuf_v1.c @@ -61,12 +61,6 @@ static struct wlr_export_dmabuf_manager_v1 *manager_from_resource( return wl_resource_get_user_data(resource); } -static void manager_handle_capture_client(struct wl_client *client, - struct wl_resource *manager_resource, uint32_t id, - int32_t overlay_cursor, uint32_t client_id) { - // TODO -} - static void manager_handle_capture_output(struct wl_client *client, struct wl_resource *manager_resource, uint32_t id, int32_t overlay_cursor, struct wl_resource *output_resource) { @@ -98,30 +92,27 @@ static void manager_handle_capture_output(struct wl_client *client, struct wlr_dmabuf_buffer_attribs *attribs = &frame->attribs; if (!wlr_output_export_dmabuf(output, attribs)) { - // TODO: abort reason - zwlr_export_dmabuf_frame_v1_send_abort(frame->resource, 0); + zwlr_export_dmabuf_frame_v1_send_cancel(frame->resource, + ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_TEMPORARY); return; } assert(attribs->n_planes > 0); - uint32_t frame_flags = 0; + uint32_t frame_flags = ZWLR_EXPORT_DMABUF_FRAME_V1_FLAGS_TRANSIENT; uint32_t mod_high = attribs->modifier[0] >> 32; uint32_t mod_low = attribs->modifier[0] & 0xFFFFFFFF; zwlr_export_dmabuf_frame_v1_send_frame(frame->resource, - output->width, output->height, output->scale, output->transform, - attribs->flags, frame_flags, mod_high, mod_low, attribs->n_planes, 1); - - zwlr_export_dmabuf_frame_v1_send_layer(frame->resource, 0, - attribs->format, attribs->n_planes); + output->width, output->height, 0, 0, attribs->flags, frame_flags, + attribs->format, mod_high, mod_low, attribs->n_planes, + attribs->n_planes); for (int i = 0; i < attribs->n_planes; ++i) { - // TODO: what to do if the kernel doesn't support seek on buffer off_t size = lseek(attribs->fd[i], 0, SEEK_END); zwlr_export_dmabuf_frame_v1_send_object(frame->resource, i, attribs->fd[i], size); - zwlr_export_dmabuf_frame_v1_send_plane(frame->resource, i, 0, i, + zwlr_export_dmabuf_frame_v1_send_plane(frame->resource, i, i, attribs->offset[i], attribs->stride[i]); } @@ -131,7 +122,6 @@ static void manager_handle_capture_output(struct wl_client *client, } static const struct zwlr_export_dmabuf_manager_v1_interface manager_impl = { - .capture_client = manager_handle_capture_client, .capture_output = manager_handle_capture_output, }; From b9b397ef8094b221bc1042aedf0dbbbb5d9a5f1e Mon Sep 17 00:00:00 2001 From: Rostislav Pehlivanov Date: Sun, 27 May 2018 04:03:29 +0100 Subject: [PATCH 13/22] Add a demo client for dmabuf export --- examples/dmabuf-capture.c | 767 ++++++++++++++++++++++++++++++++++++++ examples/meson.build | 10 + protocol/meson.build | 1 + 3 files changed, 778 insertions(+) create mode 100644 examples/dmabuf-capture.c diff --git a/examples/dmabuf-capture.c b/examples/dmabuf-capture.c new file mode 100644 index 00000000..f249d437 --- /dev/null +++ b/examples/dmabuf-capture.c @@ -0,0 +1,767 @@ +#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 199309L +#include +#include +#include +#include +#include +#include + +#include "wlr-export-dmabuf-unstable-v1-client-protocol.h" + +#include +#include +#include +#include + +struct wayland_output { + struct wl_list link; + uint32_t id; + struct wl_output *output; + char *make; + char *model; + int width; + int height; + AVRational framerate; +}; + +struct capture_context { + AVClass *class; /* For pretty logging */ + struct wl_display *display; + struct wl_registry *registry; + struct zwlr_export_dmabuf_manager_v1 *export_manager; + + struct wl_list output_list; + + /* Target */ + struct wl_output *target_output; + uint32_t target_client; + + /* Main frame callback */ + struct zwlr_export_dmabuf_frame_v1 *frame_callback; + + /* If something happens during capture */ + int err; + int quit; + + /* FFmpeg specific parts */ + AVFrame *current_frame; + AVBufferRef *drm_device_ref; + AVBufferRef *drm_frames_ref; + + AVBufferRef *mapped_device_ref; + AVBufferRef *mapped_frames_ref; + + AVFormatContext *avf; + AVCodecContext *avctx; + + int64_t start_pts; + + /* Config */ + enum AVPixelFormat software_format; + enum AVHWDeviceType hw_device_type; + AVDictionary *encoder_opts; + int is_software_encoder; + char *hardware_device; + char *out_filename; + char *encoder_name; + float out_bitrate; +}; + +static void output_handle_geometry(void *data, struct wl_output *wl_output, + int32_t x, int32_t y, int32_t phys_width, int32_t phys_height, + int32_t subpixel, const char *make, const char *model, + int32_t transform) { + struct wayland_output *output = data; + output->make = av_strdup(make); + output->model = av_strdup(model); +} + +static void output_handle_mode(void *data, struct wl_output *wl_output, + uint32_t flags, int32_t width, int32_t height, int32_t refresh) { + if (flags & WL_OUTPUT_MODE_CURRENT) { + struct wayland_output *output = data; + output->width = width; + output->height = height; + output->framerate = (AVRational){ refresh, 1000 }; + } +} + +static void output_handle_done(void* data, struct wl_output *wl_output) { + /* Nothing to do */ +} + +static void output_handle_scale(void* data, struct wl_output *wl_output, + int32_t factor) { + /* Nothing to do */ +} + +static const struct wl_output_listener output_listener = { + output_handle_geometry, + output_handle_mode, + output_handle_done, + output_handle_scale, +}; + +static void registry_handle_add(void *data, struct wl_registry *reg, + uint32_t id, const char *interface, uint32_t ver) { + struct capture_context *ctx = data; + + if (!strcmp(interface, wl_output_interface.name)) { + struct wayland_output *output = av_mallocz(sizeof(*output)); + + output->id = id; + output->output = wl_registry_bind(reg, id, &wl_output_interface, 1); + + wl_output_add_listener(output->output, &output_listener, output); + wl_list_insert(&ctx->output_list, &output->link); + } + + if (!strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name)) { + ctx->export_manager = wl_registry_bind(reg, id, + &zwlr_export_dmabuf_manager_v1_interface, 1); + } +} + +static void remove_output(struct wayland_output *out) { + wl_list_remove(&out->link); + av_free(out->make); + av_free(out->model); + av_free(out); + return; +} + +static struct wayland_output *find_output(struct capture_context *ctx, + struct wl_output *out, uint32_t id) { + struct wayland_output *output, *tmp; + wl_list_for_each_safe(output, tmp, &ctx->output_list, link) + if ((output->output == out) || (output->id == id)) + return output; + return NULL; +} + +static void registry_handle_remove(void *data, struct wl_registry *reg, + uint32_t id) { + remove_output(find_output((struct capture_context *)data, NULL, id)); +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_add, + registry_handle_remove, +}; + +static void frame_free(void *opaque, uint8_t *data) { + AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)data; + + for (int i = 0; i < desc->nb_objects; ++i) { + close(desc->objects[i].fd); + } + + zwlr_export_dmabuf_frame_v1_destroy(opaque); + + av_free(data); +} + +static void frame_start(void *data, struct zwlr_export_dmabuf_frame_v1 *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, + uint32_t num_planes) { + struct capture_context *ctx = data; + int err = 0; + + /* Allocate DRM specific struct */ + AVDRMFrameDescriptor *desc = av_mallocz(sizeof(*desc)); + if (!desc) { + err = AVERROR(ENOMEM); + goto fail; + } + + desc->nb_objects = num_objects; + desc->objects[0].format_modifier = ((uint64_t)mod_high << 32) | mod_low; + + desc->nb_layers = 1; + desc->layers[0].format = format; + desc->layers[0].nb_planes = num_planes; + + /* Allocate a frame */ + AVFrame *f = av_frame_alloc(); + if (!f) { + err = AVERROR(ENOMEM); + goto fail; + } + + /* Set base frame properties */ + ctx->current_frame = f; + f->width = width; + f->height = height; + f->format = AV_PIX_FMT_DRM_PRIME; + + /* Set the frame data to the DRM specific struct */ + f->buf[0] = av_buffer_create((uint8_t*)desc, sizeof(*desc), + &frame_free, frame, 0); + if (!f->buf[0]) { + err = AVERROR(ENOMEM); + goto fail; + } + + f->data[0] = (uint8_t*)desc; + + return; + +fail: + ctx->err = err; + frame_free(frame, (uint8_t *)desc); +} + +static void frame_object(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, + uint32_t index, int32_t fd, uint32_t size) { + struct capture_context *ctx = data; + AVFrame *f = ctx->current_frame; + AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)f->data[0]; + + desc->objects[index].fd = fd; + desc->objects[index].size = size; +} + +static void frame_plane(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, + uint32_t index, uint32_t object_index, + uint32_t offset, uint32_t stride) { + struct capture_context *ctx = data; + AVFrame *f = ctx->current_frame; + AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)f->data[0]; + + desc->layers[0].planes[index].object_index = object_index; + desc->layers[0].planes[index].offset = offset; + desc->layers[0].planes[index].pitch = stride; +} + +static const uint32_t pixfmt_to_drm_map[] = { + [AV_PIX_FMT_NV12] = WL_SHM_FORMAT_NV12, + [AV_PIX_FMT_BGRA] = WL_SHM_FORMAT_ARGB8888, + [AV_PIX_FMT_BGR0] = WL_SHM_FORMAT_XRGB8888, + [AV_PIX_FMT_RGBA] = WL_SHM_FORMAT_ABGR8888, + [AV_PIX_FMT_RGB0] = WL_SHM_FORMAT_XBGR8888, + [AV_PIX_FMT_ABGR] = WL_SHM_FORMAT_RGBA8888, + [AV_PIX_FMT_0BGR] = WL_SHM_FORMAT_RGBX8888, + [AV_PIX_FMT_ARGB] = WL_SHM_FORMAT_BGRA8888, + [AV_PIX_FMT_0RGB] = WL_SHM_FORMAT_BGRX8888, +}; + +static enum AVPixelFormat drm_fmt_to_pixfmt(uint32_t fmt) { + for (enum AVPixelFormat i = 0; i < AV_PIX_FMT_NB; i++) { + if (pixfmt_to_drm_map[i] == fmt) { + return i; + } + } + return AV_PIX_FMT_NONE; +} + +static int attach_drm_frames_ref(struct capture_context *ctx, AVFrame *f, + enum AVPixelFormat sw_format) { + int err = 0; + AVHWFramesContext *hwfc; + + if (ctx->drm_frames_ref) { + hwfc = (AVHWFramesContext*)ctx->drm_frames_ref->data; + if (hwfc->width == f->width && hwfc->height == f->height && + hwfc->sw_format == sw_format) { + goto attach; + } + av_buffer_unref(&ctx->drm_frames_ref); + } + + ctx->drm_frames_ref = av_hwframe_ctx_alloc(ctx->drm_device_ref); + if (!ctx->drm_frames_ref) { + err = AVERROR(ENOMEM); + goto fail; + } + + hwfc = (AVHWFramesContext*)ctx->drm_frames_ref->data; + + hwfc->format = f->format; + hwfc->sw_format = sw_format; + hwfc->width = f->width; + hwfc->height = f->height; + + err = av_hwframe_ctx_init(ctx->drm_frames_ref); + if (err) { + av_log(ctx, AV_LOG_ERROR, "AVHWFramesContext init failed: %s!\n", + av_err2str(err)); + goto fail; + } + +attach: + /* Set frame hardware context referencce */ + f->hw_frames_ctx = av_buffer_ref(ctx->drm_frames_ref); + if (!f->hw_frames_ctx) { + err = AVERROR(ENOMEM); + goto fail; + } + + return 0; + +fail: + av_buffer_unref(&ctx->drm_frames_ref); + return err; +} + +static void register_cb(struct capture_context *ctx); + +static void frame_ready(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { + struct capture_context *ctx = data; + AVFrame *f = ctx->current_frame; + AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)f->data[0]; + int err = 0; + + /* Attach the hardware frame context to the frame */ + err = attach_drm_frames_ref(ctx, f, drm_fmt_to_pixfmt(desc->layers[0].format)); + if (err) { + goto end; + } + + AVFrame *mapped_frame = av_frame_alloc(); + if (!mapped_frame) { + err = AVERROR(ENOMEM); + goto end; + } + + AVHWFramesContext *mapped_hwfc; + mapped_hwfc = (AVHWFramesContext *)ctx->mapped_frames_ref->data; + mapped_frame->format = mapped_hwfc->format; + + /* Set frame hardware context referencce */ + mapped_frame->hw_frames_ctx = av_buffer_ref(ctx->mapped_frames_ref); + if (!mapped_frame->hw_frames_ctx) { + err = AVERROR(ENOMEM); + goto end; + } + + err = av_hwframe_map(mapped_frame, f, 0); + if (err) { + av_log(ctx, AV_LOG_ERROR, "Error mapping: %s!\n", av_err2str(err)); + goto end; + } + + AVFrame *enc_input = mapped_frame; + + if (ctx->is_software_encoder) { + AVFrame *soft_frame = av_frame_alloc(); + av_hwframe_transfer_data(soft_frame, mapped_frame, 0); + av_frame_free(&mapped_frame); + enc_input = soft_frame; + } + + /* Nanoseconds */ + enc_input->pts = (((uint64_t)tv_sec_hi) << 32) | tv_sec_lo; + enc_input->pts *= 1000000000; + enc_input->pts += tv_nsec; + + if (!ctx->start_pts) { + ctx->start_pts = enc_input->pts; + } + + enc_input->pts -= ctx->start_pts; + + enc_input->pts = av_rescale_q(enc_input->pts, (AVRational){ 1, 1000000000 }, + ctx->avctx->time_base); + + do { + err = avcodec_send_frame(ctx->avctx, enc_input); + + av_frame_free(&enc_input); + + if (err) { + av_log(ctx, AV_LOG_ERROR, "Error encoding: %s!\n", av_err2str(err)); + goto end; + } + + while (1) { + AVPacket pkt; + av_init_packet(&pkt); + + int ret = avcodec_receive_packet(ctx->avctx, &pkt); + if (ret == AVERROR(EAGAIN)) { + break; + } else if (ret == AVERROR_EOF) { + av_log(ctx, AV_LOG_INFO, "Encoder flushed!\n"); + ctx->quit = 2; + goto end; + } else if (ret) { + av_log(ctx, AV_LOG_ERROR, "Error encoding: %s!\n", + av_err2str(ret)); + err = ret; + goto end; + } + + pkt.stream_index = 0; + err = av_interleaved_write_frame(ctx->avf, &pkt); + + av_packet_unref(&pkt); + + if (err) { + av_log(ctx, AV_LOG_ERROR, "Writing packet fail: %s!\n", + av_err2str(err)); + goto end; + } + }; + } while (ctx->quit); + + av_log(NULL, AV_LOG_INFO, "Encoded frame %i!\n", ctx->avctx->frame_number); + + register_cb(ctx); + +end: + ctx->err = err; + av_frame_free(&ctx->current_frame); +} + +static void frame_cancel(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, + uint32_t reason) { + struct capture_context *ctx = data; + av_log(ctx, AV_LOG_WARNING, "Frame cancelled!\n"); + av_frame_free(&ctx->current_frame); + if (reason != ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERNAMENT) + register_cb(ctx); +} + +static const struct zwlr_export_dmabuf_frame_v1_listener frame_listener = { + frame_start, + frame_object, + frame_plane, + frame_ready, + frame_cancel, +}; + +static void register_cb(struct capture_context *ctx) +{ + ctx->frame_callback = + zwlr_export_dmabuf_manager_v1_capture_output(ctx->export_manager, 0, + ctx->target_output); + + zwlr_export_dmabuf_frame_v1_add_listener(ctx->frame_callback, + &frame_listener, ctx); +} + +static int init_lavu_hwcontext(struct capture_context *ctx) { + + /* DRM hwcontext */ + ctx->drm_device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_DRM); + if (!ctx->drm_device_ref) + return AVERROR(ENOMEM); + + AVHWDeviceContext *ref_data = (AVHWDeviceContext*)ctx->drm_device_ref->data; + AVDRMDeviceContext *hwctx = ref_data->hwctx; + + /* We don't need a device (we don't even know it and can't open it) */ + hwctx->fd = -1; + + av_hwdevice_ctx_init(ctx->drm_device_ref); + + /* Mapped hwcontext */ + int err = av_hwdevice_ctx_create(&ctx->mapped_device_ref, + ctx->hw_device_type, ctx->hardware_device, NULL, 0); + if (err < 0) { + av_log(ctx, AV_LOG_ERROR, "Failed to create a hardware device: %s\n", + av_err2str(err)); + return err; + } + + return 0; +} + +static int set_hwframe_ctx(struct capture_context *ctx, + AVBufferRef *hw_device_ctx) +{ + AVHWFramesContext *frames_ctx = NULL; + int err = 0; + + if (!(ctx->mapped_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx))) { + return AVERROR(ENOMEM); + } + + AVHWFramesConstraints *cst = + av_hwdevice_get_hwframe_constraints(ctx->mapped_device_ref, NULL); + if (!cst) { + av_log(ctx, AV_LOG_ERROR, "Failed to get hw device constraints!\n"); + av_buffer_unref(&ctx->mapped_frames_ref); + return AVERROR(ENOMEM); + } + + frames_ctx = (AVHWFramesContext *)(ctx->mapped_frames_ref->data); + frames_ctx->format = cst->valid_hw_formats[0]; + frames_ctx->sw_format = ctx->avctx->pix_fmt; + frames_ctx->width = ctx->avctx->width; + frames_ctx->height = ctx->avctx->height; + frames_ctx->initial_pool_size = 16; + + av_hwframe_constraints_free(&cst); + + if ((err = av_hwframe_ctx_init(ctx->mapped_frames_ref)) < 0) { + av_log(ctx, AV_LOG_ERROR, "Failed to initialize hw frame context: %s!\n", + av_err2str(err)); + av_buffer_unref(&ctx->mapped_frames_ref); + return err; + } + + if (!ctx->is_software_encoder) { + ctx->avctx->pix_fmt = frames_ctx->format; + ctx->avctx->hw_frames_ctx = av_buffer_ref(ctx->mapped_frames_ref); + if (!ctx->avctx->hw_frames_ctx) { + av_buffer_unref(&ctx->mapped_frames_ref); + err = AVERROR(ENOMEM); + } + } + + return err; +} + +static int init_encoding(struct capture_context *ctx) { + int err; + + /* lavf init */ + err = avformat_alloc_output_context2(&ctx->avf, NULL, + NULL, ctx->out_filename); + if (err) { + av_log(ctx, AV_LOG_ERROR, "Unable to init lavf context!\n"); + return err; + } + + AVStream *st = avformat_new_stream(ctx->avf, NULL); + if (!st) { + av_log(ctx, AV_LOG_ERROR, "Unable to alloc stream!\n"); + return 1; + } + + /* Find encoder */ + AVCodec *out_codec = avcodec_find_encoder_by_name(ctx->encoder_name); + if (!out_codec) { + av_log(ctx, AV_LOG_ERROR, "Codec not found (not compiled in lavc?)!\n"); + return AVERROR(EINVAL); + } + ctx->avf->oformat->video_codec = out_codec->id; + ctx->is_software_encoder = !(out_codec->capabilities & AV_CODEC_CAP_HARDWARE); + + ctx->avctx = avcodec_alloc_context3(out_codec); + if (!ctx->avctx) + return 1; + + ctx->avctx->opaque = ctx; + ctx->avctx->bit_rate = (int)ctx->out_bitrate*1000000.0f; + ctx->avctx->pix_fmt = ctx->software_format; + ctx->avctx->time_base = (AVRational){ 1, 1000 }; + ctx->avctx->compression_level = 7; + ctx->avctx->width = find_output(ctx, ctx->target_output, 0)->width; + ctx->avctx->height = find_output(ctx, ctx->target_output, 0)->height; + + if (ctx->avf->oformat->flags & AVFMT_GLOBALHEADER) + ctx->avctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + + st->id = 0; + st->time_base = ctx->avctx->time_base; + st->avg_frame_rate = find_output(ctx, ctx->target_output, 0)->framerate; + + /* Init hw frames context */ + err = set_hwframe_ctx(ctx, ctx->mapped_device_ref); + if (err) + return err; + + err = avcodec_open2(ctx->avctx, out_codec, &ctx->encoder_opts); + if (err) { + av_log(ctx, AV_LOG_ERROR, "Cannot open encoder: %s!\n", + av_err2str(err)); + return err; + } + + if (avcodec_parameters_from_context(st->codecpar, ctx->avctx) < 0) { + av_log(ctx, AV_LOG_ERROR, "Couldn't copy codec params: %s!\n", + av_err2str(err)); + return err; + } + + /* Debug print */ + av_dump_format(ctx->avf, 0, ctx->out_filename, 1); + + /* Open for writing */ + err = avio_open(&ctx->avf->pb, ctx->out_filename, AVIO_FLAG_WRITE); + if (err) { + av_log(ctx, AV_LOG_ERROR, "Couldn't open %s: %s!\n", ctx->out_filename, + av_err2str(err)); + return err; + } + + err = avformat_write_header(ctx->avf, NULL); + if (err) { + av_log(ctx, AV_LOG_ERROR, "Couldn't write header: %s!\n", av_err2str(err)); + return err; + } + + return err; +} + +struct capture_context *q_ctx = NULL; + +void on_quit_signal(int signo) { + printf("\r"); + q_ctx->quit = 1; +} + +static int main_loop(struct capture_context *ctx) { + int err; + + q_ctx = ctx; + + if (signal(SIGINT, on_quit_signal) == SIG_ERR) { + av_log(ctx, AV_LOG_ERROR, "Unable to install signal handler!\n"); + return AVERROR(EINVAL); + } + + err = init_lavu_hwcontext(ctx); + if (err) + return err; + + err = init_encoding(ctx); + if (err) + return err; + + /* Start the frame callback */ + register_cb(ctx); + + while (!ctx->err && ctx->quit < 2) { + while (wl_display_prepare_read(ctx->display) != 0) { + wl_display_dispatch_pending(ctx->display); + } + + wl_display_flush(ctx->display); + + struct pollfd fds[1] = { + { .fd = wl_display_get_fd(ctx->display), .events = POLLIN }, + }; + + poll(fds, 1, -1); + + if (!(fds[0].revents & POLLIN)) { + wl_display_cancel_read(ctx->display); + } + + if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) { + av_log(ctx, AV_LOG_ERROR, "Error occurred on the display fd!\n"); + break; + } + + if (fds[0].revents & POLLIN) { + if (wl_display_read_events(ctx->display) < 0) { + av_log(ctx, AV_LOG_ERROR, "Failed to read Wayland events!\n"); + break; + } + wl_display_dispatch_pending(ctx->display); + } + } + + err = av_write_trailer(ctx->avf); + if (err) { + av_log(ctx, AV_LOG_ERROR, "Error writing trailer: %s!\n", + av_err2str(err)); + return err; + } + + av_log(ctx, AV_LOG_INFO, "Wrote trailer!\n"); + + return ctx->err; +} + +static int init(struct capture_context *ctx) { + ctx->display = wl_display_connect(NULL); + if (!ctx->display) { + av_log(ctx, AV_LOG_ERROR, "Failed to connect to display!\n"); + return AVERROR(EINVAL); + } + + wl_list_init(&ctx->output_list); + + ctx->registry = wl_display_get_registry(ctx->display); + wl_registry_add_listener(ctx->registry, ®istry_listener, ctx); + + wl_display_roundtrip(ctx->display); + wl_display_dispatch(ctx->display); + + if (!ctx->export_manager) { + av_log(ctx, AV_LOG_ERROR, "Compositor doesn't support %s!\n", + zwlr_export_dmabuf_manager_v1_interface.name); + return -1; + } + + return 0; +} + +static void print_capturable_surfaces(struct capture_context *ctx) { + + struct wayland_output *o, *tmp_o; + wl_list_for_each_reverse_safe(o, tmp_o, &ctx->output_list, link) { + ctx->target_output = o->output; /* Default is first, whatever */ + av_log(ctx, AV_LOG_INFO, "Capturable output: %s Model: %s:\n", + o->make, o->model); + } + + av_log(ctx, AV_LOG_INFO, "Capturing from output: %s!\n", + find_output(ctx, ctx->target_output, 0)->model); +} + +static void uninit(struct capture_context *ctx); + +int main(int argc, char *argv[]) { + int err; + struct capture_context ctx = { 0 }; + ctx.class = &((AVClass) { + .class_name = "dmabuf-capture", + .item_name = av_default_item_name, + .version = LIBAVUTIL_VERSION_INT, + }); + + err = init(&ctx); + if (err) + goto end; + + print_capturable_surfaces(&ctx); + + ctx.hw_device_type = av_hwdevice_find_type_by_name("vaapi"); + ctx.hardware_device = "/dev/dri/renderD128"; + + ctx.encoder_name = "libx264"; + ctx.software_format = av_get_pix_fmt("nv12"); + av_dict_set(&ctx.encoder_opts, "preset", "veryfast", 0); + + ctx.out_filename = "dmabuf_recording_01.mkv"; + ctx.out_bitrate = 29.2f; /* Mbps */ + + err = main_loop(&ctx); + if (err) + goto end; + +end: + uninit(&ctx); + return err; +} + +static void uninit(struct capture_context *ctx) { + struct wayland_output *output, *tmp_o; + wl_list_for_each_safe(output, tmp_o, &ctx->output_list, link) + remove_output(output); + + if (ctx->export_manager) + zwlr_export_dmabuf_manager_v1_destroy(ctx->export_manager); + + av_buffer_unref(&ctx->drm_frames_ref); + av_buffer_unref(&ctx->drm_device_ref); + av_buffer_unref(&ctx->mapped_frames_ref); + av_buffer_unref(&ctx->mapped_device_ref); + + av_dict_free(&ctx->encoder_opts); + + avcodec_close(ctx->avctx); + if (ctx->avf) { + avio_closep(&ctx->avf->pb); + } + avformat_free_context(ctx->avf); +} diff --git a/examples/meson.build b/examples/meson.build index 4725b989..6a0bc46c 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -1,6 +1,10 @@ threads = dependency('threads') wayland_cursor = dependency('wayland-cursor') +libavutil = dependency('libavutil') +libavcodec = dependency('libavcodec') +libavformat = dependency('libavformat') + executable('simple', 'simple.c', dependencies: wlroots) executable('pointer', 'pointer.c', dependencies: wlroots) executable('touch', 'touch.c', 'cat.c', dependencies: wlroots) @@ -38,3 +42,9 @@ executable( 'input-inhibitor.c', dependencies: [wayland_cursor, wayland_client, wlr_protos, wlroots] ) + +executable( + 'dmabuf-capture', + 'dmabuf-capture.c', + dependencies: [wayland_client, wlr_protos, libavutil, libavcodec, libavformat] +) diff --git a/protocol/meson.build b/protocol/meson.build index a14e9723..ca0d82b5 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -50,6 +50,7 @@ client_protocols = [ [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], 'idle.xml', 'screenshooter.xml', + 'wlr-export-dmabuf-unstable-v1.xml', 'wlr-layer-shell-unstable-v1.xml', 'wlr-input-inhibitor-unstable-v1.xml', ] From f204a9127c69cd896d3667a38e3bfb34377715c9 Mon Sep 17 00:00:00 2001 From: Rostislav Pehlivanov Date: Mon, 28 May 2018 00:55:13 +0100 Subject: [PATCH 14/22] Command line parsing --- examples/dmabuf-capture.c | 62 +++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/examples/dmabuf-capture.c b/examples/dmabuf-capture.c index f249d437..a1d7d965 100644 --- a/examples/dmabuf-capture.c +++ b/examples/dmabuf-capture.c @@ -35,7 +35,6 @@ struct capture_context { /* Target */ struct wl_output *target_output; - uint32_t target_client; /* Main frame callback */ struct zwlr_export_dmabuf_frame_v1 *frame_callback; @@ -80,10 +79,10 @@ static void output_handle_geometry(void *data, struct wl_output *wl_output, static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { if (flags & WL_OUTPUT_MODE_CURRENT) { - struct wayland_output *output = data; - output->width = width; - output->height = height; - output->framerate = (AVRational){ refresh, 1000 }; + struct wayland_output *output = data; + output->width = width; + output->height = height; + output->framerate = (AVRational){ refresh, 1000 }; } } @@ -494,11 +493,10 @@ static int set_hwframe_ctx(struct capture_context *ctx, frames_ctx->sw_format = ctx->avctx->pix_fmt; frames_ctx->width = ctx->avctx->width; frames_ctx->height = ctx->avctx->height; - frames_ctx->initial_pool_size = 16; av_hwframe_constraints_free(&cst); - if ((err = av_hwframe_ctx_init(ctx->mapped_frames_ref)) < 0) { + if ((err = av_hwframe_ctx_init(ctx->mapped_frames_ref))) { av_log(ctx, AV_LOG_ERROR, "Failed to initialize hw frame context: %s!\n", av_err2str(err)); av_buffer_unref(&ctx->mapped_frames_ref); @@ -695,19 +693,6 @@ static int init(struct capture_context *ctx) { return 0; } -static void print_capturable_surfaces(struct capture_context *ctx) { - - struct wayland_output *o, *tmp_o; - wl_list_for_each_reverse_safe(o, tmp_o, &ctx->output_list, link) { - ctx->target_output = o->output; /* Default is first, whatever */ - av_log(ctx, AV_LOG_INFO, "Capturable output: %s Model: %s:\n", - o->make, o->model); - } - - av_log(ctx, AV_LOG_INFO, "Capturing from output: %s!\n", - find_output(ctx, ctx->target_output, 0)->model); -} - static void uninit(struct capture_context *ctx); int main(int argc, char *argv[]) { @@ -723,18 +708,39 @@ int main(int argc, char *argv[]) { if (err) goto end; - print_capturable_surfaces(&ctx); + struct wayland_output *o, *tmp_o; + wl_list_for_each_reverse_safe(o, tmp_o, &ctx.output_list, link) { + printf("Capturable output: %s Model: %s: ID: %i\n", + o->make, o->model, o->id); + } - ctx.hw_device_type = av_hwdevice_find_type_by_name("vaapi"); - ctx.hardware_device = "/dev/dri/renderD128"; + if (argc != 8) { + printf("Invalid number of arguments! Usage and example:\n" + "./dmabuf-capture " + " \n" + "./dmabuf-capture 0 vaapi /dev/dri/renderD129 libx264 nv12 12 " + "dmabuf_recording_01.mkv\n"); + return 1; + } + + const int o_id = strtol(argv[1], NULL, 10); + o = find_output(&ctx, NULL, o_id); + if (!o) { + printf("Unable to find output with ID %i!\n", o_id); + return 1; + } + + ctx.target_output = o->output; + ctx.hw_device_type = av_hwdevice_find_type_by_name(argv[2]); + ctx.hardware_device = argv[3]; + + ctx.encoder_name = argv[4]; + ctx.software_format = av_get_pix_fmt(argv[5]); + ctx.out_bitrate = strtof(argv[6], NULL); + ctx.out_filename = argv[7]; - ctx.encoder_name = "libx264"; - ctx.software_format = av_get_pix_fmt("nv12"); av_dict_set(&ctx.encoder_opts, "preset", "veryfast", 0); - ctx.out_filename = "dmabuf_recording_01.mkv"; - ctx.out_bitrate = 29.2f; /* Mbps */ - err = main_loop(&ctx); if (err) goto end; From 2681352e0422221c047b79dfa5c19425cf59d403 Mon Sep 17 00:00:00 2001 From: emersion Date: Mon, 28 May 2018 08:20:01 +0100 Subject: [PATCH 15/22] export-dmabuf: permanently fail if backend doesn't implement export_dmabuf --- types/wlr_export_dmabuf_v1.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/types/wlr_export_dmabuf_v1.c b/types/wlr_export_dmabuf_v1.c index a2faf2ff..a86fe78c 100644 --- a/types/wlr_export_dmabuf_v1.c +++ b/types/wlr_export_dmabuf_v1.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,12 @@ static void manager_handle_capture_output(struct wl_client *client, wl_list_insert(&manager->frames, &frame->link); + if (!output->impl->export_dmabuf) { + zwlr_export_dmabuf_frame_v1_send_cancel(frame->resource, + ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERNAMENT); + return; + } + struct wlr_dmabuf_buffer_attribs *attribs = &frame->attribs; if (!wlr_output_export_dmabuf(output, attribs)) { zwlr_export_dmabuf_frame_v1_send_cancel(frame->resource, From 2198fd5eeda18fee7aecae5b3aaf8e6a207d372e Mon Sep 17 00:00:00 2001 From: emersion Date: Mon, 28 May 2018 08:20:22 +0100 Subject: [PATCH 16/22] examples/dmabuf-capture: fix indentation --- examples/dmabuf-capture.c | 172 +++++++++++++++++++------------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/examples/dmabuf-capture.c b/examples/dmabuf-capture.c index a1d7d965..6f7f9d70 100644 --- a/examples/dmabuf-capture.c +++ b/examples/dmabuf-capture.c @@ -48,13 +48,13 @@ struct capture_context { AVBufferRef *drm_device_ref; AVBufferRef *drm_frames_ref; - AVBufferRef *mapped_device_ref; - AVBufferRef *mapped_frames_ref; + AVBufferRef *mapped_device_ref; + AVBufferRef *mapped_frames_ref; - AVFormatContext *avf; - AVCodecContext *avctx; + AVFormatContext *avf; + AVCodecContext *avctx; - int64_t start_pts; + int64_t start_pts; /* Config */ enum AVPixelFormat software_format; @@ -136,7 +136,7 @@ static struct wayland_output *find_output(struct capture_context *ctx, wl_list_for_each_safe(output, tmp, &ctx->output_list, link) if ((output->output == out) || (output->id == id)) return output; - return NULL; + return NULL; } static void registry_handle_remove(void *data, struct wl_registry *reg, @@ -328,7 +328,7 @@ static void frame_ready(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, AVHWFramesContext *mapped_hwfc; mapped_hwfc = (AVHWFramesContext *)ctx->mapped_frames_ref->data; - mapped_frame->format = mapped_hwfc->format; + mapped_frame->format = mapped_hwfc->format; /* Set frame hardware context referencce */ mapped_frame->hw_frames_ctx = av_buffer_ref(ctx->mapped_frames_ref); @@ -339,8 +339,8 @@ static void frame_ready(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, err = av_hwframe_map(mapped_frame, f, 0); if (err) { - av_log(ctx, AV_LOG_ERROR, "Error mapping: %s!\n", av_err2str(err)); - goto end; + av_log(ctx, AV_LOG_ERROR, "Error mapping: %s!\n", av_err2str(err)); + goto end; } AVFrame *enc_input = mapped_frame; @@ -369,7 +369,7 @@ static void frame_ready(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, do { err = avcodec_send_frame(ctx->avctx, enc_input); - av_frame_free(&enc_input); + av_frame_free(&enc_input); if (err) { av_log(ctx, AV_LOG_ERROR, "Error encoding: %s!\n", av_err2str(err)); @@ -473,12 +473,12 @@ static int init_lavu_hwcontext(struct capture_context *ctx) { static int set_hwframe_ctx(struct capture_context *ctx, AVBufferRef *hw_device_ctx) { - AVHWFramesContext *frames_ctx = NULL; - int err = 0; + AVHWFramesContext *frames_ctx = NULL; + int err = 0; - if (!(ctx->mapped_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx))) { - return AVERROR(ENOMEM); - } + if (!(ctx->mapped_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx))) { + return AVERROR(ENOMEM); + } AVHWFramesConstraints *cst = av_hwdevice_get_hwframe_constraints(ctx->mapped_device_ref, NULL); @@ -512,7 +512,7 @@ static int set_hwframe_ctx(struct capture_context *ctx, } } - return err; + return err; } static int init_encoding(struct capture_context *ctx) { @@ -521,79 +521,79 @@ static int init_encoding(struct capture_context *ctx) { /* lavf init */ err = avformat_alloc_output_context2(&ctx->avf, NULL, NULL, ctx->out_filename); - if (err) { - av_log(ctx, AV_LOG_ERROR, "Unable to init lavf context!\n"); - return err; - } + if (err) { + av_log(ctx, AV_LOG_ERROR, "Unable to init lavf context!\n"); + return err; + } - AVStream *st = avformat_new_stream(ctx->avf, NULL); - if (!st) { - av_log(ctx, AV_LOG_ERROR, "Unable to alloc stream!\n"); - return 1; - } + AVStream *st = avformat_new_stream(ctx->avf, NULL); + if (!st) { + av_log(ctx, AV_LOG_ERROR, "Unable to alloc stream!\n"); + return 1; + } /* Find encoder */ - AVCodec *out_codec = avcodec_find_encoder_by_name(ctx->encoder_name); - if (!out_codec) { - av_log(ctx, AV_LOG_ERROR, "Codec not found (not compiled in lavc?)!\n"); - return AVERROR(EINVAL); - } - ctx->avf->oformat->video_codec = out_codec->id; - ctx->is_software_encoder = !(out_codec->capabilities & AV_CODEC_CAP_HARDWARE); + AVCodec *out_codec = avcodec_find_encoder_by_name(ctx->encoder_name); + if (!out_codec) { + av_log(ctx, AV_LOG_ERROR, "Codec not found (not compiled in lavc?)!\n"); + return AVERROR(EINVAL); + } + ctx->avf->oformat->video_codec = out_codec->id; + ctx->is_software_encoder = !(out_codec->capabilities & AV_CODEC_CAP_HARDWARE); ctx->avctx = avcodec_alloc_context3(out_codec); - if (!ctx->avctx) - return 1; + if (!ctx->avctx) + return 1; - ctx->avctx->opaque = ctx; - ctx->avctx->bit_rate = (int)ctx->out_bitrate*1000000.0f; - ctx->avctx->pix_fmt = ctx->software_format; - ctx->avctx->time_base = (AVRational){ 1, 1000 }; - ctx->avctx->compression_level = 7; - ctx->avctx->width = find_output(ctx, ctx->target_output, 0)->width; - ctx->avctx->height = find_output(ctx, ctx->target_output, 0)->height; + ctx->avctx->opaque = ctx; + ctx->avctx->bit_rate = (int)ctx->out_bitrate*1000000.0f; + ctx->avctx->pix_fmt = ctx->software_format; + ctx->avctx->time_base = (AVRational){ 1, 1000 }; + ctx->avctx->compression_level = 7; + ctx->avctx->width = find_output(ctx, ctx->target_output, 0)->width; + ctx->avctx->height = find_output(ctx, ctx->target_output, 0)->height; if (ctx->avf->oformat->flags & AVFMT_GLOBALHEADER) ctx->avctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; st->id = 0; - st->time_base = ctx->avctx->time_base; - st->avg_frame_rate = find_output(ctx, ctx->target_output, 0)->framerate; + st->time_base = ctx->avctx->time_base; + st->avg_frame_rate = find_output(ctx, ctx->target_output, 0)->framerate; - /* Init hw frames context */ - err = set_hwframe_ctx(ctx, ctx->mapped_device_ref); - if (err) - return err; + /* Init hw frames context */ + err = set_hwframe_ctx(ctx, ctx->mapped_device_ref); + if (err) + return err; err = avcodec_open2(ctx->avctx, out_codec, &ctx->encoder_opts); if (err) { - av_log(ctx, AV_LOG_ERROR, "Cannot open encoder: %s!\n", - av_err2str(err)); - return err; - } + av_log(ctx, AV_LOG_ERROR, "Cannot open encoder: %s!\n", + av_err2str(err)); + return err; + } if (avcodec_parameters_from_context(st->codecpar, ctx->avctx) < 0) { - av_log(ctx, AV_LOG_ERROR, "Couldn't copy codec params: %s!\n", - av_err2str(err)); - return err; - } + av_log(ctx, AV_LOG_ERROR, "Couldn't copy codec params: %s!\n", + av_err2str(err)); + return err; + } - /* Debug print */ - av_dump_format(ctx->avf, 0, ctx->out_filename, 1); + /* Debug print */ + av_dump_format(ctx->avf, 0, ctx->out_filename, 1); - /* Open for writing */ - err = avio_open(&ctx->avf->pb, ctx->out_filename, AVIO_FLAG_WRITE); - if (err) { - av_log(ctx, AV_LOG_ERROR, "Couldn't open %s: %s!\n", ctx->out_filename, - av_err2str(err)); - return err; - } + /* Open for writing */ + err = avio_open(&ctx->avf->pb, ctx->out_filename, AVIO_FLAG_WRITE); + if (err) { + av_log(ctx, AV_LOG_ERROR, "Couldn't open %s: %s!\n", ctx->out_filename, + av_err2str(err)); + return err; + } err = avformat_write_header(ctx->avf, NULL); - if (err) { - av_log(ctx, AV_LOG_ERROR, "Couldn't write header: %s!\n", av_err2str(err)); - return err; - } + if (err) { + av_log(ctx, AV_LOG_ERROR, "Couldn't write header: %s!\n", av_err2str(err)); + return err; + } return err; } @@ -608,12 +608,12 @@ void on_quit_signal(int signo) { static int main_loop(struct capture_context *ctx) { int err; - q_ctx = ctx; + q_ctx = ctx; - if (signal(SIGINT, on_quit_signal) == SIG_ERR) { + if (signal(SIGINT, on_quit_signal) == SIG_ERR) { av_log(ctx, AV_LOG_ERROR, "Unable to install signal handler!\n"); - return AVERROR(EINVAL); - } + return AVERROR(EINVAL); + } err = init_lavu_hwcontext(ctx); if (err) @@ -627,11 +627,11 @@ static int main_loop(struct capture_context *ctx) { register_cb(ctx); while (!ctx->err && ctx->quit < 2) { - while (wl_display_prepare_read(ctx->display) != 0) { + while (wl_display_prepare_read(ctx->display) != 0) { wl_display_dispatch_pending(ctx->display); } - wl_display_flush(ctx->display); + wl_display_flush(ctx->display); struct pollfd fds[1] = { { .fd = wl_display_get_fd(ctx->display), .events = POLLIN }, @@ -649,7 +649,7 @@ static int main_loop(struct capture_context *ctx) { } if (fds[0].revents & POLLIN) { - if (wl_display_read_events(ctx->display) < 0) { + if (wl_display_read_events(ctx->display) < 0) { av_log(ctx, AV_LOG_ERROR, "Failed to read Wayland events!\n"); break; } @@ -658,13 +658,13 @@ static int main_loop(struct capture_context *ctx) { } err = av_write_trailer(ctx->avf); - if (err) { - av_log(ctx, AV_LOG_ERROR, "Error writing trailer: %s!\n", - av_err2str(err)); - return err; - } + if (err) { + av_log(ctx, AV_LOG_ERROR, "Error writing trailer: %s!\n", + av_err2str(err)); + return err; + } - av_log(ctx, AV_LOG_INFO, "Wrote trailer!\n"); + av_log(ctx, AV_LOG_INFO, "Wrote trailer!\n"); return ctx->err; } @@ -679,7 +679,7 @@ static int init(struct capture_context *ctx) { wl_list_init(&ctx->output_list); ctx->registry = wl_display_get_registry(ctx->display); - wl_registry_add_listener(ctx->registry, ®istry_listener, ctx); + wl_registry_add_listener(ctx->registry, ®istry_listener, ctx); wl_display_roundtrip(ctx->display); wl_display_dispatch(ctx->display); @@ -721,7 +721,7 @@ int main(int argc, char *argv[]) { "./dmabuf-capture 0 vaapi /dev/dri/renderD129 libx264 nv12 12 " "dmabuf_recording_01.mkv\n"); return 1; - } + } const int o_id = strtol(argv[1], NULL, 10); o = find_output(&ctx, NULL, o_id); @@ -759,9 +759,9 @@ static void uninit(struct capture_context *ctx) { zwlr_export_dmabuf_manager_v1_destroy(ctx->export_manager); av_buffer_unref(&ctx->drm_frames_ref); - av_buffer_unref(&ctx->drm_device_ref); - av_buffer_unref(&ctx->mapped_frames_ref); - av_buffer_unref(&ctx->mapped_device_ref); + av_buffer_unref(&ctx->drm_device_ref); + av_buffer_unref(&ctx->mapped_frames_ref); + av_buffer_unref(&ctx->mapped_device_ref); av_dict_free(&ctx->encoder_opts); From 85b6b4b0c888deba26b2311a9a509d60160e6644 Mon Sep 17 00:00:00 2001 From: emersion Date: Mon, 28 May 2018 08:24:25 +0100 Subject: [PATCH 17/22] examples/dmabuf-capture: make building this example optional --- examples/meson.build | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/examples/meson.build b/examples/meson.build index 6a0bc46c..d82bd256 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -1,9 +1,9 @@ threads = dependency('threads') wayland_cursor = dependency('wayland-cursor') -libavutil = dependency('libavutil') -libavcodec = dependency('libavcodec') -libavformat = dependency('libavformat') +libavutil = dependency('libavutil', required: false) +libavcodec = dependency('libavcodec', required: false) +libavformat = dependency('libavformat', required: false) executable('simple', 'simple.c', dependencies: wlroots) executable('pointer', 'pointer.c', dependencies: wlroots) @@ -43,8 +43,10 @@ executable( dependencies: [wayland_cursor, wayland_client, wlr_protos, wlroots] ) -executable( - 'dmabuf-capture', - 'dmabuf-capture.c', - dependencies: [wayland_client, wlr_protos, libavutil, libavcodec, libavformat] -) +if libavutil.found() and libavcodec.found() and libavformat.found() + executable( + 'dmabuf-capture', + 'dmabuf-capture.c', + dependencies: [wayland_client, wlr_protos, libavutil, libavcodec, libavformat] + ) +endif From 70d324a0f9b94952b7e4fbbbcce76f8c570ced03 Mon Sep 17 00:00:00 2001 From: emersion Date: Mon, 28 May 2018 08:37:18 +0100 Subject: [PATCH 18/22] examples/dmabuf-capture: simplify event loop and fix style --- examples/dmabuf-capture.c | 163 +++++++++++++++++--------------------- 1 file changed, 72 insertions(+), 91 deletions(-) diff --git a/examples/dmabuf-capture.c b/examples/dmabuf-capture.c index 6f7f9d70..cd8a9267 100644 --- a/examples/dmabuf-capture.c +++ b/examples/dmabuf-capture.c @@ -1,19 +1,17 @@ #define _XOPEN_SOURCE 700 #define _POSIX_C_SOURCE 199309L +#include +#include +#include +#include #include +#include #include #include #include #include -#include - #include "wlr-export-dmabuf-unstable-v1-client-protocol.h" -#include -#include -#include -#include - struct wayland_output { struct wl_list link; uint32_t id; @@ -72,7 +70,7 @@ static void output_handle_geometry(void *data, struct wl_output *wl_output, int32_t subpixel, const char *make, const char *model, int32_t transform) { struct wayland_output *output = data; - output->make = av_strdup(make); + output->make = av_strdup(make); output->model = av_strdup(model); } @@ -80,8 +78,8 @@ static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { if (flags & WL_OUTPUT_MODE_CURRENT) { struct wayland_output *output = data; - output->width = width; - output->height = height; + output->width = width; + output->height = height; output->framerate = (AVRational){ refresh, 1000 }; } } @@ -96,10 +94,10 @@ static void output_handle_scale(void* data, struct wl_output *wl_output, } static const struct wl_output_listener output_listener = { - output_handle_geometry, - output_handle_mode, - output_handle_done, - output_handle_scale, + .geometry = output_handle_geometry, + .mode = output_handle_mode, + .done = output_handle_done, + .scale = output_handle_scale, }; static void registry_handle_add(void *data, struct wl_registry *reg, @@ -109,7 +107,7 @@ static void registry_handle_add(void *data, struct wl_registry *reg, if (!strcmp(interface, wl_output_interface.name)) { struct wayland_output *output = av_mallocz(sizeof(*output)); - output->id = id; + output->id = id; output->output = wl_registry_bind(reg, id, &wl_output_interface, 1); wl_output_add_listener(output->output, &output_listener, output); @@ -117,8 +115,8 @@ static void registry_handle_add(void *data, struct wl_registry *reg, } if (!strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name)) { - ctx->export_manager = wl_registry_bind(reg, id, - &zwlr_export_dmabuf_manager_v1_interface, 1); + ctx->export_manager = wl_registry_bind(reg, id, + &zwlr_export_dmabuf_manager_v1_interface, 1); } } @@ -127,15 +125,16 @@ static void remove_output(struct wayland_output *out) { av_free(out->make); av_free(out->model); av_free(out); - return; } static struct wayland_output *find_output(struct capture_context *ctx, struct wl_output *out, uint32_t id) { struct wayland_output *output, *tmp; - wl_list_for_each_safe(output, tmp, &ctx->output_list, link) - if ((output->output == out) || (output->id == id)) + wl_list_for_each_safe(output, tmp, &ctx->output_list, link) { + if ((output->output == out) || (output->id == id)) { return output; + } + } return NULL; } @@ -145,8 +144,8 @@ static void registry_handle_remove(void *data, struct wl_registry *reg, } static const struct wl_registry_listener registry_listener = { - registry_handle_add, - registry_handle_remove, + .global = registry_handle_add, + .global_remove = registry_handle_remove, }; static void frame_free(void *opaque, uint8_t *data) { @@ -192,7 +191,7 @@ static void frame_start(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, /* Set base frame properties */ ctx->current_frame = f; - f->width = width; + f->width = width; f->height = height; f->format = AV_PIX_FMT_DRM_PRIME; @@ -219,7 +218,7 @@ static void frame_object(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, AVFrame *f = ctx->current_frame; AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)f->data[0]; - desc->objects[index].fd = fd; + desc->objects[index].fd = fd; desc->objects[index].size = size; } @@ -231,8 +230,8 @@ static void frame_plane(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)f->data[0]; desc->layers[0].planes[index].object_index = object_index; - desc->layers[0].planes[index].offset = offset; - desc->layers[0].planes[index].pitch = stride; + desc->layers[0].planes[index].offset = offset; + desc->layers[0].planes[index].pitch = stride; } static const uint32_t pixfmt_to_drm_map[] = { @@ -278,10 +277,10 @@ static int attach_drm_frames_ref(struct capture_context *ctx, AVFrame *f, hwfc = (AVHWFramesContext*)ctx->drm_frames_ref->data; - hwfc->format = f->format; + hwfc->format = f->format; hwfc->sw_format = sw_format; - hwfc->width = f->width; - hwfc->height = f->height; + hwfc->width = f->width; + hwfc->height = f->height; err = av_hwframe_ctx_init(ctx->drm_frames_ref); if (err) { @@ -421,30 +420,31 @@ static void frame_cancel(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, struct capture_context *ctx = data; av_log(ctx, AV_LOG_WARNING, "Frame cancelled!\n"); av_frame_free(&ctx->current_frame); - if (reason != ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERNAMENT) + if (reason == ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERNAMENT) { + av_log(ctx, AV_LOG_ERROR, "Permanent failure, exiting\n"); + ctx->err = 1; + } else { register_cb(ctx); + } } static const struct zwlr_export_dmabuf_frame_v1_listener frame_listener = { - frame_start, - frame_object, - frame_plane, - frame_ready, - frame_cancel, + .frame = frame_start, + .object = frame_object, + .plane = frame_plane, + .ready = frame_ready, + .cancel = frame_cancel, }; -static void register_cb(struct capture_context *ctx) -{ - ctx->frame_callback = - zwlr_export_dmabuf_manager_v1_capture_output(ctx->export_manager, 0, - ctx->target_output); +static void register_cb(struct capture_context *ctx) { + ctx->frame_callback = zwlr_export_dmabuf_manager_v1_capture_output( + ctx->export_manager, 0, ctx->target_output); zwlr_export_dmabuf_frame_v1_add_listener(ctx->frame_callback, &frame_listener, ctx); } static int init_lavu_hwcontext(struct capture_context *ctx) { - /* DRM hwcontext */ ctx->drm_device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_DRM); if (!ctx->drm_device_ref) @@ -471,8 +471,7 @@ static int init_lavu_hwcontext(struct capture_context *ctx) { } static int set_hwframe_ctx(struct capture_context *ctx, - AVBufferRef *hw_device_ctx) -{ + AVBufferRef *hw_device_ctx) { AVHWFramesContext *frames_ctx = NULL; int err = 0; @@ -489,10 +488,10 @@ static int set_hwframe_ctx(struct capture_context *ctx, } frames_ctx = (AVHWFramesContext *)(ctx->mapped_frames_ref->data); - frames_ctx->format = cst->valid_hw_formats[0]; + frames_ctx->format = cst->valid_hw_formats[0]; frames_ctx->sw_format = ctx->avctx->pix_fmt; - frames_ctx->width = ctx->avctx->width; - frames_ctx->height = ctx->avctx->height; + frames_ctx->width = ctx->avctx->width; + frames_ctx->height = ctx->avctx->height; av_hwframe_constraints_free(&cst); @@ -545,25 +544,27 @@ static int init_encoding(struct capture_context *ctx) { if (!ctx->avctx) return 1; - ctx->avctx->opaque = ctx; - ctx->avctx->bit_rate = (int)ctx->out_bitrate*1000000.0f; - ctx->avctx->pix_fmt = ctx->software_format; - ctx->avctx->time_base = (AVRational){ 1, 1000 }; + ctx->avctx->opaque = ctx; + ctx->avctx->bit_rate = (int)ctx->out_bitrate*1000000.0f; + ctx->avctx->pix_fmt = ctx->software_format; + ctx->avctx->time_base = (AVRational){ 1, 1000 }; ctx->avctx->compression_level = 7; - ctx->avctx->width = find_output(ctx, ctx->target_output, 0)->width; - ctx->avctx->height = find_output(ctx, ctx->target_output, 0)->height; + ctx->avctx->width = find_output(ctx, ctx->target_output, 0)->width; + ctx->avctx->height = find_output(ctx, ctx->target_output, 0)->height; - if (ctx->avf->oformat->flags & AVFMT_GLOBALHEADER) + if (ctx->avf->oformat->flags & AVFMT_GLOBALHEADER) { ctx->avctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } - st->id = 0; - st->time_base = ctx->avctx->time_base; + st->id = 0; + st->time_base = ctx->avctx->time_base; st->avg_frame_rate = find_output(ctx, ctx->target_output, 0)->framerate; /* Init hw frames context */ err = set_hwframe_ctx(ctx, ctx->mapped_device_ref); - if (err) + if (err) { return err; + } err = avcodec_open2(ctx->avctx, out_codec, &ctx->encoder_opts); if (err) { @@ -616,45 +617,21 @@ static int main_loop(struct capture_context *ctx) { } err = init_lavu_hwcontext(ctx); - if (err) + if (err) { return err; + } err = init_encoding(ctx); - if (err) + if (err) { return err; + } /* Start the frame callback */ register_cb(ctx); - while (!ctx->err && ctx->quit < 2) { - while (wl_display_prepare_read(ctx->display) != 0) { - wl_display_dispatch_pending(ctx->display); - } - - wl_display_flush(ctx->display); - - struct pollfd fds[1] = { - { .fd = wl_display_get_fd(ctx->display), .events = POLLIN }, - }; - - poll(fds, 1, -1); - - if (!(fds[0].revents & POLLIN)) { - wl_display_cancel_read(ctx->display); - } - - if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) { - av_log(ctx, AV_LOG_ERROR, "Error occurred on the display fd!\n"); - break; - } - - if (fds[0].revents & POLLIN) { - if (wl_display_read_events(ctx->display) < 0) { - av_log(ctx, AV_LOG_ERROR, "Failed to read Wayland events!\n"); - break; - } - wl_display_dispatch_pending(ctx->display); - } + while (wl_display_dispatch(ctx->display) != -1 && !ctx->err && + ctx->quit < 2) { + // This space intentionally left blank } err = av_write_trailer(ctx->avf); @@ -705,8 +682,9 @@ int main(int argc, char *argv[]) { }); err = init(&ctx); - if (err) + if (err) { goto end; + } struct wayland_output *o, *tmp_o; wl_list_for_each_reverse_safe(o, tmp_o, &ctx.output_list, link) { @@ -742,8 +720,9 @@ int main(int argc, char *argv[]) { av_dict_set(&ctx.encoder_opts, "preset", "veryfast", 0); err = main_loop(&ctx); - if (err) + if (err) { goto end; + } end: uninit(&ctx); @@ -752,11 +731,13 @@ end: static void uninit(struct capture_context *ctx) { struct wayland_output *output, *tmp_o; - wl_list_for_each_safe(output, tmp_o, &ctx->output_list, link) + wl_list_for_each_safe(output, tmp_o, &ctx->output_list, link) { remove_output(output); + } - if (ctx->export_manager) + if (ctx->export_manager) { zwlr_export_dmabuf_manager_v1_destroy(ctx->export_manager); + } av_buffer_unref(&ctx->drm_frames_ref); av_buffer_unref(&ctx->drm_device_ref); From c844eaa1b32894417263ce2b5030033bdd7c7851 Mon Sep 17 00:00:00 2001 From: emersion Date: Mon, 28 May 2018 08:39:38 +0100 Subject: [PATCH 19/22] build: add dependencies for examples/dmabuf-capture --- .build.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.build.yml b/.build.yml index 9fd4f116..8b2f9c1f 100644 --- a/.build.yml +++ b/.build.yml @@ -1,15 +1,16 @@ image: archlinux packages: - - meson - - wayland - - wayland-protocols - - mesa + - clang + - ffmpeg + - libcap - libinput - libxkbcommon - - xcb-util-image - - libcap + - mesa + - meson - pixman - - clang + - wayland + - wayland-protocols + - xcb-util-image sources: - https://github.com/swaywm/wlroots tasks: From 9eddcbc376ff92a3a03002b910c31bf96bdba2da Mon Sep 17 00:00:00 2001 From: Rostislav Pehlivanov Date: Sun, 17 Jun 2018 14:06:52 +0100 Subject: [PATCH 20/22] Update example and protocol --- examples/dmabuf-capture.c | 28 ++-- protocol/wlr-export-dmabuf-unstable-v1.xml | 176 +++++++++++---------- 2 files changed, 100 insertions(+), 104 deletions(-) diff --git a/examples/dmabuf-capture.c b/examples/dmabuf-capture.c index cd8a9267..2f7db2f1 100644 --- a/examples/dmabuf-capture.c +++ b/examples/dmabuf-capture.c @@ -163,8 +163,7 @@ static void frame_free(void *opaque, uint8_t *data) { static void frame_start(void *data, struct zwlr_export_dmabuf_frame_v1 *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, - uint32_t num_planes) { + uint32_t mod_high, uint32_t mod_low, uint32_t num_objects) { struct capture_context *ctx = data; int err = 0; @@ -180,7 +179,6 @@ static void frame_start(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, desc->nb_layers = 1; desc->layers[0].format = format; - desc->layers[0].nb_planes = num_planes; /* Allocate a frame */ AVFrame *f = av_frame_alloc(); @@ -213,25 +211,18 @@ fail: } static void frame_object(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, - uint32_t index, int32_t fd, uint32_t size) { + uint32_t index, int32_t fd, uint32_t size, uint32_t offset, + uint32_t stride, uint32_t plane_index) { struct capture_context *ctx = data; AVFrame *f = ctx->current_frame; AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)f->data[0]; desc->objects[index].fd = fd; desc->objects[index].size = size; -} -static void frame_plane(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, - uint32_t index, uint32_t object_index, - uint32_t offset, uint32_t stride) { - struct capture_context *ctx = data; - AVFrame *f = ctx->current_frame; - AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)f->data[0]; - - desc->layers[0].planes[index].object_index = object_index; - desc->layers[0].planes[index].offset = offset; - desc->layers[0].planes[index].pitch = stride; + desc->layers[0].planes[plane_index].object_index = index; + desc->layers[0].planes[plane_index].offset = offset; + desc->layers[0].planes[plane_index].pitch = stride; } static const uint32_t pixfmt_to_drm_map[] = { @@ -311,14 +302,18 @@ static void frame_ready(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, struct capture_context *ctx = data; AVFrame *f = ctx->current_frame; AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)f->data[0]; + enum AVPixelFormat pix_fmt = drm_fmt_to_pixfmt(desc->layers[0].format); int err = 0; /* Attach the hardware frame context to the frame */ - err = attach_drm_frames_ref(ctx, f, drm_fmt_to_pixfmt(desc->layers[0].format)); + err = attach_drm_frames_ref(ctx, f, pix_fmt); if (err) { goto end; } + /* TODO: support multiplane stuff */ + desc->layers[0].nb_planes = av_pix_fmt_count_planes(pix_fmt); + AVFrame *mapped_frame = av_frame_alloc(); if (!mapped_frame) { err = AVERROR(ENOMEM); @@ -431,7 +426,6 @@ static void frame_cancel(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, static const struct zwlr_export_dmabuf_frame_v1_listener frame_listener = { .frame = frame_start, .object = frame_object, - .plane = frame_plane, .ready = frame_ready, .cancel = frame_cancel, }; diff --git a/protocol/wlr-export-dmabuf-unstable-v1.xml b/protocol/wlr-export-dmabuf-unstable-v1.xml index 760345a7..751f7efb 100644 --- a/protocol/wlr-export-dmabuf-unstable-v1.xml +++ b/protocol/wlr-export-dmabuf-unstable-v1.xml @@ -1,6 +1,5 @@ - Copyright © 2018 Rostislav Pehlivanov @@ -25,29 +24,56 @@ - An interface to capture surfaces in an efficient way. - Overall usage: + An interface to capture surfaces in an efficient way by exporting DMA-BUFs. - 1.) client registers with zwlr_screencontent_manager_v1 - 2.) server sends client info about surfaces via "receive_surface_info" - 3.) client subscribes to capture a surface via the "capture" requests - 4.) server sends client events via the "zwlr_screencontent_frame" interface - 5.) client finishes and informs server via the "frame_destroy" event - 6.) client optionally resubscribes via repeating steps 3.) through 5.) + 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 represents a frame which is ready to have its resources - fetched and used. + + + 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. - The receive callback shall be called first, followed by the "object" - callback once per dmabuf object or the "plane" callback, once per dmabuf - plane. The "ready" event is called last to indicate that all the data has - been made available for readout, as well as the time at which presentation - happened at. The ownership of the frame is passed to the client, who's - responsible for destroying it via the "destroy" event once finished and - by calling close() on the file descriptors received. All frames are read-only and may not be written into or altered. @@ -55,25 +81,23 @@ Special flags that should be respected by the client. - - - Main callback supplying the client with information about the frame, - as well as an object to serve as context for destruction. Always called - first before any other events. + + 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. - The "transform" argument describes the orientation needed to be applied - to correctly orient the buffer. For example, a buffer rotated by 90 - degrees will have a value of "3" here, corresponding to the need to - apply a 270 degree transpose to correctly present the buffer. + This event is followed by a number of "object" as specified by the + "num_objects" argument. + summary="frame width in pixels"/> + summary="frame height in pixels"/> - + - - Callback which serves to supply the client with the file descriptors + + 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. @@ -105,31 +131,28 @@ summary="fd of the current object"/> - - - - Callback which supplies the client with plane information for each - plane. - - - + + - Called as soon as the frame is presented, indicating it is available - for reading. + 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. + 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. @@ -143,22 +166,25 @@ Indicates reason for cancelling the frame. - - - + - If the frame is no longer valid after the "frame" event has been called, - this callback will be used to inform the client to scrap the frame. - Source is still valid for as long as the subscription function does not - return NULL. - This may get called if for instance the surface is in the process of - resizing. + 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. @@ -166,35 +192,11 @@ - Unreferences the frame, allowing it to be reused. Must be called as soon - as its no longer used. - Can be called at any time by the client after the "frame" event, after - which the compositor will not call any other events unless the client - resubscribes to capture more. The client will still have to close any - FDs it has been given. - - - + Unreferences the frame. This request must be called as soon as its no + longer used. - - - This object is a manager with which to start capturing from sources. - - - - - Request to start capturing from an entire wl_output. - - - - - - - - All objects created by the manager will still remain valid, until their - appropriate destroy request has been called. + It can be called at any time by the client. The client will still have + to close any FDs it has been given. From bd0c1b794992bf94560bd429f4057c9d09989f06 Mon Sep 17 00:00:00 2001 From: emersion Date: Sun, 17 Jun 2018 14:19:45 +0100 Subject: [PATCH 21/22] export-dmabuf: update protocol --- examples/dmabuf-capture.c | 2 +- types/wlr_export_dmabuf_v1.c | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/examples/dmabuf-capture.c b/examples/dmabuf-capture.c index 2f7db2f1..1aeaf9c5 100644 --- a/examples/dmabuf-capture.c +++ b/examples/dmabuf-capture.c @@ -415,7 +415,7 @@ static void frame_cancel(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, struct capture_context *ctx = data; av_log(ctx, AV_LOG_WARNING, "Frame cancelled!\n"); av_frame_free(&ctx->current_frame); - if (reason == ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERNAMENT) { + if (reason == ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT) { av_log(ctx, AV_LOG_ERROR, "Permanent failure, exiting\n"); ctx->err = 1; } else { diff --git a/types/wlr_export_dmabuf_v1.c b/types/wlr_export_dmabuf_v1.c index 40a0e289..68adda02 100644 --- a/types/wlr_export_dmabuf_v1.c +++ b/types/wlr_export_dmabuf_v1.c @@ -93,7 +93,7 @@ static void manager_handle_capture_output(struct wl_client *client, if (!output->impl->export_dmabuf) { zwlr_export_dmabuf_frame_v1_send_cancel(frame->resource, - ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERNAMENT); + ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT); return; } @@ -110,16 +110,13 @@ static void manager_handle_capture_output(struct wl_client *client, zwlr_export_dmabuf_frame_v1_send_frame(frame->resource, output->width, output->height, 0, 0, attribs->flags, frame_flags, - attribs->format, mod_high, mod_low, attribs->n_planes, - attribs->n_planes); + attribs->format, mod_high, mod_low, attribs->n_planes); for (int i = 0; i < attribs->n_planes; ++i) { off_t size = lseek(attribs->fd[i], 0, SEEK_END); zwlr_export_dmabuf_frame_v1_send_object(frame->resource, i, - attribs->fd[i], size); - zwlr_export_dmabuf_frame_v1_send_plane(frame->resource, i, i, - attribs->offset[i], attribs->stride[i]); + attribs->fd[i], size, attribs->offset[i], attribs->stride[i], i); } wl_list_remove(&frame->output_swap_buffers.link); From ed7d5b0f53167b6191d408e5c7f20a3672fac3c2 Mon Sep 17 00:00:00 2001 From: Rostislav Pehlivanov Date: Sun, 17 Jun 2018 15:19:17 +0100 Subject: [PATCH 22/22] Fix example --- examples/dmabuf-capture.c | 31 +++++++++++++------------------ examples/meson.build | 2 +- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/examples/dmabuf-capture.c b/examples/dmabuf-capture.c index 1aeaf9c5..0d4aa3ee 100644 --- a/examples/dmabuf-capture.c +++ b/examples/dmabuf-capture.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "wlr-export-dmabuf-unstable-v1-client-protocol.h" struct wayland_output { @@ -225,25 +226,19 @@ static void frame_object(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, desc->layers[0].planes[plane_index].pitch = stride; } -static const uint32_t pixfmt_to_drm_map[] = { - [AV_PIX_FMT_NV12] = WL_SHM_FORMAT_NV12, - [AV_PIX_FMT_BGRA] = WL_SHM_FORMAT_ARGB8888, - [AV_PIX_FMT_BGR0] = WL_SHM_FORMAT_XRGB8888, - [AV_PIX_FMT_RGBA] = WL_SHM_FORMAT_ABGR8888, - [AV_PIX_FMT_RGB0] = WL_SHM_FORMAT_XBGR8888, - [AV_PIX_FMT_ABGR] = WL_SHM_FORMAT_RGBA8888, - [AV_PIX_FMT_0BGR] = WL_SHM_FORMAT_RGBX8888, - [AV_PIX_FMT_ARGB] = WL_SHM_FORMAT_BGRA8888, - [AV_PIX_FMT_0RGB] = WL_SHM_FORMAT_BGRX8888, -}; - static enum AVPixelFormat drm_fmt_to_pixfmt(uint32_t fmt) { - for (enum AVPixelFormat i = 0; i < AV_PIX_FMT_NB; i++) { - if (pixfmt_to_drm_map[i] == fmt) { - return i; - } - } - return AV_PIX_FMT_NONE; + switch (fmt) { + case DRM_FORMAT_NV12: return AV_PIX_FMT_NV12; + case DRM_FORMAT_ARGB8888: return AV_PIX_FMT_BGRA; + case DRM_FORMAT_XRGB8888: return AV_PIX_FMT_BGR0; + case DRM_FORMAT_ABGR8888: return AV_PIX_FMT_RGBA; + case DRM_FORMAT_XBGR8888: return AV_PIX_FMT_RGB0; + case DRM_FORMAT_RGBA8888: return AV_PIX_FMT_ABGR; + case DRM_FORMAT_RGBX8888: return AV_PIX_FMT_0BGR; + case DRM_FORMAT_BGRA8888: return AV_PIX_FMT_ARGB; + case DRM_FORMAT_BGRX8888: return AV_PIX_FMT_0RGB; + default: return AV_PIX_FMT_NONE; + }; } static int attach_drm_frames_ref(struct capture_context *ctx, AVFrame *f, diff --git a/examples/meson.build b/examples/meson.build index d82bd256..939a4890 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -47,6 +47,6 @@ if libavutil.found() and libavcodec.found() and libavformat.found() executable( 'dmabuf-capture', 'dmabuf-capture.c', - dependencies: [wayland_client, wlr_protos, libavutil, libavcodec, libavformat] + dependencies: [wayland_client, wlr_protos, libavutil, libavcodec, libavformat, wlroots] ) endif