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: + - clang + - ffmpeg + - libcap + - libinput + - libxkbcommon + - mesa - meson + - pixman - wayland - wayland-protocols - - mesa - - libinput - - libxkbcommon - xcb-util-image - - libcap - - pixman - - clang sources: - https://github.com/swaywm/wlroots tasks: diff --git a/backend/drm/drm.c b/backend/drm/drm.c index ef8efb9a..c5db480e 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -254,6 +254,25 @@ 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_attributes *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; + + return export_drm_bo(surf->back, attribs); +} + static void drm_connector_start_renderer(struct wlr_drm_connector *conn) { if (conn->state != WLR_DRM_CONN_CONNECTED) { return; @@ -742,6 +761,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 72a0254b..0ab63c86 100644 --- a/backend/drm/renderer.c +++ b/backend/drm/renderer.c @@ -160,6 +160,34 @@ void post_drm_surface(struct wlr_drm_surface *surf) { } } +bool export_drm_bo(struct gbm_bo *bo, struct wlr_dmabuf_attributes *attribs) { + memset(attribs, 0, sizeof(struct wlr_dmabuf_attributes)); + + attribs->n_planes = gbm_bo_get_plane_count(bo); + if (attribs->n_planes > WLR_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->modifier = gbm_bo_get_modifier(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->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 { struct wlr_egl *egl; EGLImageKHR img; @@ -186,16 +214,11 @@ static struct wlr_texture *get_tex_for_bo(struct wlr_drm_renderer *renderer, return NULL; } - struct wlr_dmabuf_attributes attribs = { - .n_planes = 1, - .width = gbm_bo_get_width(bo), - .height = gbm_bo_get_height(bo), - .format = gbm_bo_get_format(bo), - .modifier = DRM_FORMAT_MOD_LINEAR, - }; - attribs.offset[0] = 0; - attribs.stride[0] = gbm_bo_get_stride_for_plane(bo, 0); - attribs.fd[0] = gbm_bo_get_fd(bo); + struct wlr_dmabuf_attributes 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/backend/libinput/backend.c b/backend/libinput/backend.c index f4d54c97..1dde5854 100644 --- a/backend/libinput/backend.c +++ b/backend/libinput/backend.c @@ -55,8 +55,8 @@ static bool backend_start(struct wlr_backend *_backend) { return false; } - // TODO: Let user customize seat used - if (libinput_udev_assign_seat(backend->libinput_context, "seat0") != 0) { + if (libinput_udev_assign_seat(backend->libinput_context, + backend->session->seat) != 0) { wlr_log(L_ERROR, "Failed to assign libinput seat"); return false; } diff --git a/backend/session/direct-ipc.c b/backend/session/direct-ipc.c index 6c69b70a..f8ba07f7 100644 --- a/backend/session/direct-ipc.c +++ b/backend/session/direct-ipc.c @@ -130,7 +130,7 @@ static void communicate(int sock) { int drm_fd = -1; bool running = true; - while (running && recv_msg(sock, &drm_fd, &msg, sizeof(msg)) >= 0) { + while (running && recv_msg(sock, &drm_fd, &msg, sizeof(msg)) > 0) { switch (msg.type) { case MSG_OPEN: errno = 0; diff --git a/backend/session/direct.c b/backend/session/direct.c index 13a26d99..7fa7d05b 100644 --- a/backend/session/direct.c +++ b/backend/session/direct.c @@ -1,5 +1,6 @@ #define _POSIX_C_SOURCE 200809L #include +#include #include #include #include @@ -76,30 +77,39 @@ static void direct_session_close(struct wlr_session *base, int fd) { static bool direct_change_vt(struct wlr_session *base, unsigned vt) { struct direct_session *session = wl_container_of(base, session, base); + + // Only seat0 has VTs associated with it + if (strcmp(session->base.seat, "seat0") != 0) { + return true; + } + return ioctl(session->tty_fd, VT_ACTIVATE, (int)vt) == 0; } static void direct_session_destroy(struct wlr_session *base) { struct direct_session *session = wl_container_of(base, session, base); - struct vt_mode mode = { - .mode = VT_AUTO, - }; - errno = 0; + if (strcmp(session->base.seat, "seat0") == 0) { + struct vt_mode mode = { + .mode = VT_AUTO, + }; + errno = 0; + + ioctl(session->tty_fd, KDSKBMODE, session->old_kbmode); + ioctl(session->tty_fd, KDSETMODE, KD_TEXT); + ioctl(session->tty_fd, VT_SETMODE, &mode); - ioctl(session->tty_fd, KDSKBMODE, session->old_kbmode); - ioctl(session->tty_fd, KDSETMODE, KD_TEXT); - ioctl(session->tty_fd, VT_SETMODE, &mode); + if (errno) { + wlr_log(L_ERROR, "Failed to restore tty"); + } - if (errno) { - wlr_log(L_ERROR, "Failed to restore tty"); + wl_event_source_remove(session->vt_source); + close(session->tty_fd); } direct_ipc_finish(session->sock, session->child); close(session->sock); - wl_event_source_remove(session->vt_source); - close(session->tty_fd); free(session); } @@ -138,19 +148,19 @@ static int vt_handler(int signo, void *data) { } static bool setup_tty(struct direct_session *session, struct wl_display *display) { - int fd = dup(STDIN_FILENO); + int fd = open("/dev/tty", O_RDWR); if (fd == -1) { - wlr_log_errno(L_ERROR, "Cannot open tty"); + wlr_log_errno(L_ERROR, "Cannot open /dev/tty"); return false; } - struct stat st; - if (fstat(fd, &st) == -1 || major(st.st_rdev) != TTY_MAJOR || minor(st.st_rdev) == 0) { - wlr_log(L_ERROR, "Not running from a virtual terminal"); + struct vt_stat vt_stat; + if (ioctl(fd, VT_GETSTATE, &vt_stat)) { + wlr_log_errno(L_ERROR, "Could not get current tty number"); goto error; } - int tty = minor(st.st_rdev); + int tty = vt_stat.v_active; int ret, kd_mode, old_kbmode; ret = ioctl(fd, KDGETMODE, &kd_mode); @@ -224,20 +234,24 @@ static struct wlr_session *direct_session_create(struct wl_display *disp) { goto error_session; } - if (!setup_tty(session, disp)) { - goto error_ipc; - } - - // XXX: Is it okay to trust the environment like this? const char *seat = getenv("XDG_SEAT"); if (!seat) { seat = "seat0"; } - wlr_log(L_INFO, "Successfully loaded direct session"); + if (strcmp(seat, "seat0") == 0) { + if (!setup_tty(session, disp)) { + goto error_ipc; + } + } else { + session->base.vtnr = 0; + session->tty_fd = -1; + } snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat); session->base.impl = &session_direct; + + wlr_log(L_INFO, "Successfully loaded direct session"); return &session->base; error_ipc: diff --git a/backend/session/logind.c b/backend/session/logind.c index f0ac1e93..3a3cc57d 100644 --- a/backend/session/logind.c +++ b/backend/session/logind.c @@ -109,6 +109,11 @@ static void logind_release_device(struct wlr_session *base, int fd) { static bool logind_change_vt(struct wlr_session *base, unsigned vt) { struct logind_session *session = wl_container_of(base, session, base); + // Only seat0 has VTs associated with it + if (strcmp(session->base.seat, "seat0") != 0) { + return true; + } + int ret; sd_bus_message *msg = NULL; sd_bus_error error = SD_BUS_ERROR_NULL; diff --git a/backend/session/session.c b/backend/session/session.c index 2d5d9776..026040fd 100644 --- a/backend/session/session.c +++ b/backend/session/session.c @@ -19,9 +19,7 @@ extern const struct session_impl session_logind; extern const struct session_impl session_direct; static const struct session_impl *impls[] = { -#ifdef WLR_HAS_SYSTEMD - &session_logind, -#elif defined(WLR_HAS_ELOGIND) +#if defined(WLR_HAS_SYSTEMD) || defined(WLR_HAS_ELOGIND) &session_logind, #endif &session_direct, diff --git a/examples/dmabuf-capture.c b/examples/dmabuf-capture.c new file mode 100644 index 00000000..0d4aa3ee --- /dev/null +++ b/examples/dmabuf-capture.c @@ -0,0 +1,743 @@ +#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" + +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; + + /* 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 = { + .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, + 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); +} + +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 = { + .global = registry_handle_add, + .global_remove = 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) { + 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; + + /* 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, 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; + + 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 enum AVPixelFormat drm_fmt_to_pixfmt(uint32_t fmt) { + 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, + 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]; + 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, 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); + 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_PERMANENT) { + 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 = frame_start, + .object = frame_object, + .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); + + 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; + + av_hwframe_constraints_free(&cst); + + 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); + 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 (wl_display_dispatch(ctx->display) != -1 && !ctx->err && + ctx->quit < 2) { + // This space intentionally left blank + } + + 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 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; + } + + 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); + } + + 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]; + + av_dict_set(&ctx.encoder_opts, "preset", "veryfast", 0); + + 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..939a4890 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -1,6 +1,10 @@ threads = dependency('threads') wayland_cursor = dependency('wayland-cursor') +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) executable('touch', 'touch.c', 'cat.c', dependencies: wlroots) @@ -38,3 +42,11 @@ executable( 'input-inhibitor.c', dependencies: [wayland_cursor, wayland_client, wlr_protos, wlroots] ) + +if libavutil.found() and libavcodec.found() and libavformat.found() + executable( + 'dmabuf-capture', + 'dmabuf-capture.c', + dependencies: [wayland_client, wlr_protos, libavutil, libavcodec, libavformat, wlroots] + ) +endif diff --git a/include/backend/drm/renderer.h b/include/backend/drm/renderer.h index 510abe43..575758de 100644 --- a/include/backend/drm/renderer.h +++ b/include/backend/drm/renderer.h @@ -52,5 +52,6 @@ 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); +bool export_drm_bo(struct gbm_bo *bo, struct wlr_dmabuf_attributes *attribs); #endif diff --git a/include/render/gles2.h b/include/render/gles2.h index 99beff29..67d4e9f5 100644 --- a/include/render/gles2.h +++ b/include/render/gles2.h @@ -25,6 +25,14 @@ struct wlr_gles2_pixel_format { bool has_alpha; }; +struct wlr_gles2_tex_shader { + GLuint program; + GLint proj; + GLint invert_y; + GLint tex; + GLint alpha; +}; + struct wlr_gles2_renderer { struct wlr_renderer wlr_renderer; @@ -32,11 +40,19 @@ struct wlr_gles2_renderer { const char *exts_str; struct { - GLuint quad; - GLuint ellipse; - GLuint tex_rgba; - GLuint tex_rgbx; - GLuint tex_ext; + struct { + GLuint program; + GLint proj; + GLint color; + } quad; + struct { + GLuint program; + GLint proj; + GLint color; + } ellipse; + struct wlr_gles2_tex_shader tex_rgba; + struct wlr_gles2_tex_shader tex_rgbx; + struct wlr_gles2_tex_shader tex_ext; } shaders; uint32_t viewport_width, viewport_height; 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/include/wlr/backend/session.h b/include/wlr/backend/session.h index 4d04b363..1cf41939 100644 --- a/include/wlr/backend/session.h +++ b/include/wlr/backend/session.h @@ -25,8 +25,12 @@ struct wlr_session { struct wl_signal session_signal; bool active; + /* + * 0 if virtual terminals are not supported + * i.e. seat != "seat0" + */ unsigned vtnr; - char seat[8]; + char seat[256]; struct udev *udev; struct udev_monitor *mon; diff --git a/include/wlr/interfaces/wlr_output.h b/include/wlr/interfaces/wlr_output.h index 7ecc7551..8f87408a 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_attributes *attribs); }; void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, diff --git a/include/wlr/render/dmabuf.h b/include/wlr/render/dmabuf.h index 2e13fe75..78f8c2eb 100644 --- a/include/wlr/render/dmabuf.h +++ b/include/wlr/render/dmabuf.h @@ -1,6 +1,8 @@ #ifndef WLR_RENDER_DMABUF_H #define WLR_RENDER_DMABUF_H +#include + // So we don't have to pull in linux specific drm headers #ifndef DRM_FORMAT_MOD_INVALID #define DRM_FORMAT_MOD_INVALID ((1ULL<<56) - 1) @@ -26,4 +28,9 @@ struct wlr_dmabuf_attributes { int fd[WLR_DMABUF_MAX_PLANES]; }; +/** + * Closes all file descriptors in the DMA-BUF attributes. + */ +void wlr_dmabuf_attributes_finish(struct wlr_dmabuf_attributes *attribs); + #endif diff --git a/include/wlr/render/egl.h b/include/wlr/render/egl.h index 0f591d60..6b887f4f 100644 --- a/include/wlr/render/egl.h +++ b/include/wlr/render/egl.h @@ -18,9 +18,10 @@ struct wlr_egl { struct { bool bind_wayland_display_wl; bool buffer_age_ext; - bool image_dmabuf_import_modifiers_ext; - bool image_dmabuf_import_ext; bool image_base_khr; + bool image_dma_buf_export_mesa; + bool image_dmabuf_import_ext; + bool image_dmabuf_import_modifiers_ext; bool swap_buffers_with_damage_ext; bool swap_buffers_with_damage_khr; } exts; @@ -80,6 +81,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_attributes *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 80788858..fb427c89 100644 --- a/include/wlr/render/interface.h +++ b/include/wlr/render/interface.h @@ -60,6 +60,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_attributes *attribs); void (*destroy)(struct wlr_texture *texture); }; diff --git a/include/wlr/render/wlr_texture.h b/include/wlr/render/wlr_texture.h index c1633820..785f4fc6 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_attributes *attribs); + /** * Destroys this wlr_texture. */ diff --git a/include/wlr/types/wlr_buffer.h b/include/wlr/types/wlr_buffer.h new file mode 100644 index 00000000..eabc8b51 --- /dev/null +++ b/include/wlr/types/wlr_buffer.h @@ -0,0 +1,63 @@ +#ifndef WLR_TYPES_WLR_BUFFER_H +#define WLR_TYPES_WLR_BUFFER_H + +#include +#include + +/** + * A client buffer. + */ +struct wlr_buffer { + /** + * The buffer resource, if any. Will be NULL if the client destroys it. + */ + struct wl_resource *resource; + /** + * The buffer's texture, if any. A buffer will not have a texture if the + * client destroys the buffer before it has been released. + */ + struct wlr_texture *texture; + bool released; + size_t n_refs; + + struct wl_listener resource_destroy; +}; + +struct wlr_renderer; + +/** + * Check if a resource is a wl_buffer resource. + */ +bool wlr_resource_is_buffer(struct wl_resource *resource); +/** + * Get the size of a wl_buffer resource. + */ +bool wlr_buffer_get_resource_size(struct wl_resource *resource, + struct wlr_renderer *renderer, int *width, int *height); + +/** + * Upload a buffer to the GPU and reference it. + */ +struct wlr_buffer *wlr_buffer_create(struct wlr_renderer *renderer, + struct wl_resource *resource); +/** + * Reference the buffer. + */ +struct wlr_buffer *wlr_buffer_ref(struct wlr_buffer *buffer); +/** + * Unreference the buffer. After this call, `buffer` may not be accessed + * anymore. + */ +void wlr_buffer_unref(struct wlr_buffer *buffer); +/** + * Try to update the buffer's content. On success, returns the updated buffer + * and destroys the provided `buffer`. On error, `buffer` is intact and NULL is + * returned. + * + * Fails if there's more than one reference to the buffer or if the texture + * isn't mutable. + */ +struct wlr_buffer *wlr_buffer_apply_damage(struct wlr_buffer *buffer, + struct wl_resource *resource, pixman_region32_t *damage); + +#endif 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..a094b3de --- /dev/null +++ b/include/wlr/types/wlr_export_dmabuf_v1.h @@ -0,0 +1,37 @@ +#ifndef WLR_TYPES_WLR_EXPORT_DMABUF_V1_H +#define WLR_TYPES_WLR_EXPORT_DMABUF_V1_H + +#include +#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_dmabuf_attributes attribs; + struct wlr_output *output; + + struct wl_listener output_swap_buffers; +}; + +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/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index cef3fc5d..158d174e 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 @@ -81,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; @@ -107,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); @@ -162,6 +169,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_attributes *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/include/wlr/types/wlr_surface.h b/include/wlr/types/wlr_surface.h index 526e4e2c..46588f0a 100644 --- a/include/wlr/types/wlr_surface.h +++ b/include/wlr/types/wlr_surface.h @@ -8,11 +8,6 @@ #include #include -struct wlr_frame_callback { - struct wl_resource *resource; - struct wl_list link; -}; - #define WLR_SURFACE_INVALID_BUFFER 1 #define WLR_SURFACE_INVALID_SURFACE_DAMAGE 2 #define WLR_SURFACE_INVALID_BUFFER_DAMAGE 4 @@ -69,13 +64,16 @@ struct wlr_subsurface { struct wlr_surface { struct wl_resource *resource; struct wlr_renderer *renderer; - struct wlr_texture *texture; + /** + * The surface's buffer, if any. A surface has an attached buffer when it + * commits with a non-null buffer in its pending state. A surface will not + * have a buffer if it has never committed one, has committed a null buffer, + * or something went wrong with uploading the buffer. + */ + struct wlr_buffer *buffer; struct wlr_surface_state *current, *pending; const char *role; // the lifetime-bound role or null - float buffer_to_surface_matrix[9]; - float surface_to_buffer_matrix[9]; - struct { struct wl_signal commit; struct wl_signal new_subsurface; @@ -124,6 +122,13 @@ int wlr_surface_set_role(struct wlr_surface *surface, const char *role, */ bool wlr_surface_has_buffer(struct wlr_surface *surface); +/** + * Get the texture of the buffer currently attached to this surface. Returns + * NULL if no buffer is currently attached or if something went wrong with + * uploading the buffer. + */ +struct wlr_texture *wlr_surface_get_texture(struct wlr_surface *surface); + /** * Create a new subsurface resource with the provided new ID. If `resource_list` * is non-NULL, adds the subsurface's resource to the list. diff --git a/protocol/meson.build b/protocol/meson.build index 8fa64ca9..ca0d82b5 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 = [ @@ -49,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', ] diff --git a/protocol/wlr-export-dmabuf-unstable-v1.xml b/protocol/wlr-export-dmabuf-unstable-v1.xml new file mode 100644 index 00000000..751f7efb --- /dev/null +++ b/protocol/wlr-export-dmabuf-unstable-v1.xml @@ -0,0 +1,203 @@ + + + + Copyright © 2018 Rostislav Pehlivanov + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + An interface to capture surfaces in an efficient way by exporting DMA-BUFs. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This object is a manager with which to start capturing from sources. + + + + + Capture the next frame of a an entire output. + + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object represents a single DMA-BUF frame. + + If the capture is successful, the compositor will first send a "frame" + event, followed by one or several "object". When the frame is available + for readout, the "ready" event is sent. + + If the capture failed, the "cancel" event is sent. This can happen anytime + before the "ready" event. + + Once either a "ready" or a "cancel" event is received, the client should + destroy the frame. Once an "object" event is received, the client is + responsible for closing the associated file descriptor. + + All frames are read-only and may not be written into or altered. + + + + + Special flags that should be respected by the client. + + + + + + + Main event supplying the client with information about the frame. If the + capture didn't fail, this event is always emitted first before any other + events. + + This event is followed by a number of "object" as specified by the + "num_objects" argument. + + + + + + + + + + + + + + + + Event which serves to supply the client with the file descriptors + containing the data for each object. + + After receiving this event, the client must always close the file + descriptor as soon as they're done with it and even if the frame fails. + + + + + + + + + + + + This event is sent as soon as the frame is presented, indicating it is + available for reading. This event includes the time at which + presentation happened at. + + The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, + each component being an unsigned 32-bit value. Whole seconds are in + tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, + and the additional fractional part in tv_nsec as nanoseconds. Hence, + for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part + may have an arbitrary offset at start. + + After receiving this event, the client should destroy this object. + + + + + + + + + Indicates reason for cancelling the frame. + + + + + + + + + If the capture failed or if the frame is no longer valid after the + "frame" event has been emitted, this event will be used to inform the + client to scrap the frame. + + If the failure is temporary, the client may capture again the same + source. If the failure is permanent, any further attempts to capture the + same source will fail again. + + After receiving this event, the client should destroy this object. + + + + + + + Unreferences the frame. This request must be called as soon as its no + longer used. + + It can be called at any time by the client. The client will still have + to close any FDs it has been given. + + + + diff --git a/render/dmabuf.c b/render/dmabuf.c new file mode 100644 index 00000000..6b500748 --- /dev/null +++ b/render/dmabuf.c @@ -0,0 +1,10 @@ +#include +#include + +void wlr_dmabuf_attributes_finish( struct wlr_dmabuf_attributes *attribs) { + for (int i = 0; i < attribs->n_planes; ++i) { + close(attribs->fd[i]); + attribs->fd[i] = -1; + } + attribs->n_planes = 0; +} diff --git a/render/egl.c b/render/egl.c index 450562db..7f910e86 100644 --- a/render/egl.c +++ b/render/egl.c @@ -164,6 +164,11 @@ bool wlr_egl_init(struct wlr_egl *egl, EGLenum platform, void *remote_display, egl->exts.image_dmabuf_import_modifiers_ext = check_egl_ext(egl->exts_str, "EGL_EXT_image_dma_buf_import_modifiers") && eglQueryDmaBufFormatsEXT && eglQueryDmaBufModifiersEXT; + + egl->exts.image_dma_buf_export_mesa = + check_egl_ext(egl->exts_str, "EGL_MESA_image_dma_buf_export") && + eglExportDMABUFImageQueryMESA && eglExportDMABUFImageMESA; + print_dmabuf_formats(egl); egl->exts.bind_wayland_display_wl = @@ -511,6 +516,37 @@ 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_attributes *attribs) { + memset(attribs, 0, sizeof(struct wlr_dmabuf_attributes)); + + if (!egl->exts.image_dma_buf_export_mesa) { + return false; + } + + // Only one set of modifiers is returned for all planes + if (!eglExportDMABUFImageQueryMESA(egl->display, image, + (int *)&attribs->format, &attribs->n_planes, &attribs->modifier)) { + return false; + } + if (attribs->n_planes > WLR_DMABUF_MAX_PLANES) { + wlr_log(L_ERROR, "EGL returned %d planes, but only %d are supported", + attribs->n_planes, WLR_DMABUF_MAX_PLANES); + return false; + } + + 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/renderer.c b/render/gles2/renderer.c index 1eeb915e..05426fa9 100644 --- a/render/gles2/renderer.c +++ b/render/gles2/renderer.c @@ -118,18 +118,22 @@ static bool gles2_render_texture_with_matrix(struct wlr_renderer *wlr_renderer, struct wlr_gles2_texture *texture = get_gles2_texture_in_context(wlr_texture); - GLuint prog = 0; + struct wlr_gles2_tex_shader *shader = NULL; GLenum target = 0; + switch (texture->type) { case WLR_GLES2_TEXTURE_GLTEX: case WLR_GLES2_TEXTURE_WL_DRM_GL: - prog = texture->has_alpha ? renderer->shaders.tex_rgba : - renderer->shaders.tex_rgbx; + if (texture->has_alpha) { + shader = &renderer->shaders.tex_rgba; + } else { + shader = &renderer->shaders.tex_rgbx; + } target = GL_TEXTURE_2D; break; case WLR_GLES2_TEXTURE_WL_DRM_EXT: case WLR_GLES2_TEXTURE_DMABUF: - prog = renderer->shaders.tex_ext; + shader = &renderer->shaders.tex_ext; target = GL_TEXTURE_EXTERNAL_OES; break; } @@ -149,11 +153,12 @@ static bool gles2_render_texture_with_matrix(struct wlr_renderer *wlr_renderer, glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glUseProgram(prog); + glUseProgram(shader->program); - glUniformMatrix3fv(0, 1, GL_FALSE, transposition); - glUniform1i(1, texture->inverted_y); - glUniform1f(3, alpha); + glUniformMatrix3fv(shader->proj, 1, GL_FALSE, transposition); + glUniform1i(shader->invert_y, texture->inverted_y); + glUniform1i(shader->tex, 0); + glUniform1f(shader->alpha, alpha); draw_quad(); @@ -173,9 +178,10 @@ static void gles2_render_quad_with_matrix(struct wlr_renderer *wlr_renderer, wlr_matrix_transpose(transposition, matrix); PUSH_GLES2_DEBUG; - glUseProgram(renderer->shaders.quad); - glUniformMatrix3fv(0, 1, GL_FALSE, transposition); - glUniform4f(1, color[0], color[1], color[2], color[3]); + glUseProgram(renderer->shaders.quad.program); + + glUniformMatrix3fv(renderer->shaders.quad.proj, 1, GL_FALSE, transposition); + glUniform4f(renderer->shaders.quad.color, color[0], color[1], color[2], color[3]); draw_quad(); POP_GLES2_DEBUG; } @@ -191,9 +197,10 @@ static void gles2_render_ellipse_with_matrix(struct wlr_renderer *wlr_renderer, wlr_matrix_transpose(transposition, matrix); PUSH_GLES2_DEBUG; - glUseProgram(renderer->shaders.ellipse); - glUniformMatrix3fv(0, 1, GL_FALSE, transposition); - glUniform4f(1, color[0], color[1], color[2], color[3]); + glUseProgram(renderer->shaders.ellipse.program); + + glUniformMatrix3fv(renderer->shaders.ellipse.proj, 1, GL_FALSE, transposition); + glUniform4f(renderer->shaders.ellipse.color, color[0], color[1], color[2], color[3]); draw_quad(); POP_GLES2_DEBUG; } @@ -313,11 +320,11 @@ static void gles2_destroy(struct wlr_renderer *wlr_renderer) { wlr_egl_make_current(renderer->egl, EGL_NO_SURFACE, NULL); PUSH_GLES2_DEBUG; - glDeleteProgram(renderer->shaders.quad); - glDeleteProgram(renderer->shaders.ellipse); - glDeleteProgram(renderer->shaders.tex_rgba); - glDeleteProgram(renderer->shaders.tex_rgbx); - glDeleteProgram(renderer->shaders.tex_ext); + glDeleteProgram(renderer->shaders.quad.program); + glDeleteProgram(renderer->shaders.ellipse.program); + glDeleteProgram(renderer->shaders.tex_rgba.program); + glDeleteProgram(renderer->shaders.tex_rgbx.program); + glDeleteProgram(renderer->shaders.tex_ext.program); POP_GLES2_DEBUG; if (glDebugMessageCallbackKHR) { @@ -486,31 +493,53 @@ struct wlr_renderer *wlr_gles2_renderer_create(struct wlr_egl *egl) { PUSH_GLES2_DEBUG; - renderer->shaders.quad = link_program(quad_vertex_src, quad_fragment_src); - if (!renderer->shaders.quad) { + GLuint prog; + renderer->shaders.quad.program = prog = + link_program(quad_vertex_src, quad_fragment_src); + if (!renderer->shaders.quad.program) { goto error; } - renderer->shaders.ellipse = + renderer->shaders.quad.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.quad.color = glGetUniformLocation(prog, "color"); + + renderer->shaders.ellipse.program = prog = link_program(quad_vertex_src, ellipse_fragment_src); - if (!renderer->shaders.ellipse) { + if (!renderer->shaders.ellipse.program) { goto error; } - renderer->shaders.tex_rgba = + renderer->shaders.ellipse.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.ellipse.color = glGetUniformLocation(prog, "color"); + + renderer->shaders.tex_rgba.program = prog = link_program(tex_vertex_src, tex_fragment_src_rgba); - if (!renderer->shaders.tex_rgba) { + if (!renderer->shaders.tex_rgba.program) { goto error; } - renderer->shaders.tex_rgbx = + renderer->shaders.tex_rgba.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.tex_rgba.invert_y = glGetUniformLocation(prog, "invert_y"); + renderer->shaders.tex_rgba.tex = glGetUniformLocation(prog, "tex"); + renderer->shaders.tex_rgba.alpha = glGetUniformLocation(prog, "alpha"); + + renderer->shaders.tex_rgbx.program = prog = link_program(tex_vertex_src, tex_fragment_src_rgbx); - if (!renderer->shaders.tex_rgbx) { + if (!renderer->shaders.tex_rgbx.program) { goto error; } + renderer->shaders.tex_rgbx.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.tex_rgbx.invert_y = glGetUniformLocation(prog, "invert_y"); + renderer->shaders.tex_rgbx.tex = glGetUniformLocation(prog, "tex"); + renderer->shaders.tex_rgbx.alpha = glGetUniformLocation(prog, "alpha"); + if (glEGLImageTargetTexture2DOES) { - renderer->shaders.tex_ext = + renderer->shaders.tex_ext.program = prog = link_program(tex_vertex_src, tex_fragment_src_external); - if (!renderer->shaders.tex_ext) { + if (!renderer->shaders.tex_ext.program) { goto error; } + renderer->shaders.tex_ext.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.tex_ext.invert_y = glGetUniformLocation(prog, "invert_y"); + renderer->shaders.tex_ext.tex = glGetUniformLocation(prog, "tex"); + renderer->shaders.tex_ext.alpha = glGetUniformLocation(prog, "alpha"); } POP_GLES2_DEBUG; @@ -518,11 +547,11 @@ struct wlr_renderer *wlr_gles2_renderer_create(struct wlr_egl *egl) { return &renderer->wlr_renderer; error: - glDeleteProgram(renderer->shaders.quad); - glDeleteProgram(renderer->shaders.ellipse); - glDeleteProgram(renderer->shaders.tex_rgba); - glDeleteProgram(renderer->shaders.tex_rgbx); - glDeleteProgram(renderer->shaders.tex_ext); + glDeleteProgram(renderer->shaders.quad.program); + glDeleteProgram(renderer->shaders.ellipse.program); + glDeleteProgram(renderer->shaders.tex_rgba.program); + glDeleteProgram(renderer->shaders.tex_rgbx.program); + glDeleteProgram(renderer->shaders.tex_ext.program); POP_GLES2_DEBUG; diff --git a/render/gles2/texture.c b/render/gles2/texture.c index da98a523..0ef9d759 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_attributes *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_ATTRIBUTES_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/meson.build b/render/meson.build index 4fe9ea67..4b90c229 100644 --- a/render/meson.build +++ b/render/meson.build @@ -9,6 +9,7 @@ glapi = custom_target('glapi', lib_wlr_render = static_library( 'wlr_render', files( + 'dmabuf.c', 'egl.c', 'gles2/pixel_format.c', 'gles2/renderer.c', diff --git a/render/wlr_texture.c b/render/wlr_texture.c index 38c75d28..76986920 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_attributes *attribs) { + if (!texture->impl->to_dmabuf) { + return false; + } + return texture->impl->to_dmabuf(texture, attribs); +} 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( diff --git a/rootston/output.c b/rootston/output.c index faa808d1..353d431f 100644 --- a/rootston/output.c +++ b/rootston/output.c @@ -189,7 +189,8 @@ static void render_surface(struct wlr_surface *surface, int sx, int sy, struct roots_output *output = data->output; float rotation = data->layout.rotation; - if (!wlr_surface_has_buffer(surface)) { + struct wlr_texture *texture = wlr_surface_get_texture(surface); + if (texture == NULL) { return; } @@ -230,8 +231,7 @@ static void render_surface(struct wlr_surface *surface, int sx, int sy, pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); for (int i = 0; i < nrects; ++i) { scissor_output(output, &rects[i]); - wlr_render_texture_with_matrix(renderer, surface->texture, matrix, - data->alpha); + wlr_render_texture_with_matrix(renderer, texture, matrix, data->alpha); } damage_finish: diff --git a/types/meson.build b/types/meson.build index f9f5b469..6d6e0fa3 100644 --- a/types/meson.build +++ b/types/meson.build @@ -20,8 +20,10 @@ lib_wlr_types = static_library( 'xdg_shell_v6/wlr_xdg_surface_v6.c', 'xdg_shell_v6/wlr_xdg_toplevel_v6.c', 'wlr_box.c', + 'wlr_buffer.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_buffer.c b/types/wlr_buffer.c new file mode 100644 index 00000000..673d7088 --- /dev/null +++ b/types/wlr_buffer.c @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include +#include + +bool wlr_resource_is_buffer(struct wl_resource *resource) { + return strcmp(wl_resource_get_class(resource), wl_buffer_interface.name) == 0; +} + +bool wlr_buffer_get_resource_size(struct wl_resource *resource, + struct wlr_renderer *renderer, int *width, int *height) { + assert(wlr_resource_is_buffer(resource)); + + struct wl_shm_buffer *shm_buf = wl_shm_buffer_get(resource); + if (shm_buf != NULL) { + *width = wl_shm_buffer_get_width(shm_buf); + *height = wl_shm_buffer_get_height(shm_buf); + } else if (wlr_renderer_resource_is_wl_drm_buffer(renderer, + resource)) { + wlr_renderer_wl_drm_buffer_get_size(renderer, resource, + width, height); + } else if (wlr_dmabuf_resource_is_buffer(resource)) { + struct wlr_dmabuf_buffer *dmabuf = + wlr_dmabuf_buffer_from_buffer_resource(resource); + *width = dmabuf->attributes.width; + *height = dmabuf->attributes.height; + } else { + *width = *height = 0; + return false; + } + + return true; +} + + +static void buffer_resource_handle_destroy(struct wl_listener *listener, + void *data) { + struct wlr_buffer *buffer = + wl_container_of(listener, buffer, resource_destroy); + wl_list_remove(&buffer->resource_destroy.link); + wl_list_init(&buffer->resource_destroy.link); + buffer->resource = NULL; + + // At this point, if the wl_buffer comes from linux-dmabuf or wl_drm, we + // still haven't released it (ie. we'll read it in the future) but the + // client destroyed it. Reading the texture itself should be fine because + // we still hold a reference to the DMA-BUF via the texture. However the + // client could decide to re-use the same DMA-BUF for something else, in + // which case we'll read garbage. We decide to accept this risk. +} + +struct wlr_buffer *wlr_buffer_create(struct wlr_renderer *renderer, + struct wl_resource *resource) { + assert(wlr_resource_is_buffer(resource)); + + struct wlr_texture *texture = NULL; + bool released = false; + + struct wl_shm_buffer *shm_buf = wl_shm_buffer_get(resource); + if (shm_buf != NULL) { + enum wl_shm_format fmt = wl_shm_buffer_get_format(shm_buf); + int32_t stride = wl_shm_buffer_get_stride(shm_buf); + int32_t width = wl_shm_buffer_get_width(shm_buf); + int32_t height = wl_shm_buffer_get_height(shm_buf); + + wl_shm_buffer_begin_access(shm_buf); + void *data = wl_shm_buffer_get_data(shm_buf); + texture = wlr_texture_from_pixels(renderer, fmt, stride, + width, height, data); + wl_shm_buffer_end_access(shm_buf); + + // We have uploaded the data, we don't need to access the wl_buffer + // anymore + wl_buffer_send_release(resource); + released = true; + } else if (wlr_renderer_resource_is_wl_drm_buffer(renderer, resource)) { + texture = wlr_texture_from_wl_drm(renderer, resource); + } else if (wlr_dmabuf_resource_is_buffer(resource)) { + struct wlr_dmabuf_buffer *dmabuf = + wlr_dmabuf_buffer_from_buffer_resource(resource); + texture = wlr_texture_from_dmabuf(renderer, &dmabuf->attributes); + + // We have imported the DMA-BUF, but we need to prevent the client from + // re-using the same DMA-BUF for the next frames, so we don't release + // the buffer yet. + } else { + wlr_log(L_ERROR, "Cannot upload texture: unknown buffer type"); + return NULL; + } + + if (texture == NULL) { + wlr_log(L_ERROR, "Failed to upload texture"); + return NULL; + } + + struct wlr_buffer *buffer = calloc(1, sizeof(struct wlr_buffer)); + if (buffer == NULL) { + wlr_texture_destroy(texture); + return NULL; + } + buffer->resource = resource; + buffer->texture = texture; + buffer->released = released; + buffer->n_refs = 1; + + wl_resource_add_destroy_listener(resource, &buffer->resource_destroy); + buffer->resource_destroy.notify = buffer_resource_handle_destroy; + + return buffer; +} + +struct wlr_buffer *wlr_buffer_ref(struct wlr_buffer *buffer) { + buffer->n_refs++; + return buffer; +} + +void wlr_buffer_unref(struct wlr_buffer *buffer) { + if (buffer == NULL) { + return; + } + + assert(buffer->n_refs > 0); + buffer->n_refs--; + if (buffer->n_refs > 0) { + return; + } + + if (!buffer->released && buffer->resource != NULL) { + wl_buffer_send_release(buffer->resource); + } + + wl_list_remove(&buffer->resource_destroy.link); + wlr_texture_destroy(buffer->texture); + free(buffer); +} + +struct wlr_buffer *wlr_buffer_apply_damage(struct wlr_buffer *buffer, + struct wl_resource *resource, pixman_region32_t *damage) { + assert(wlr_resource_is_buffer(resource)); + + if (buffer->n_refs > 1) { + // Someone else still has a reference to the buffer + return NULL; + } + + struct wl_shm_buffer *shm_buf = wl_shm_buffer_get(resource); + if (shm_buf == NULL) { + // Uploading only damaged regions only works for wl_shm buffers + return NULL; + } + + enum wl_shm_format fmt = wl_shm_buffer_get_format(shm_buf); + int32_t stride = wl_shm_buffer_get_stride(shm_buf); + int32_t width = wl_shm_buffer_get_width(shm_buf); + int32_t height = wl_shm_buffer_get_height(shm_buf); + + int32_t texture_width, texture_height; + wlr_texture_get_size(buffer->texture, &texture_width, &texture_height); + if (width != texture_width || height != texture_height) { + return NULL; + } + + wl_shm_buffer_begin_access(shm_buf); + void *data = wl_shm_buffer_get_data(shm_buf); + + int n; + pixman_box32_t *rects = pixman_region32_rectangles(damage, &n); + for (int i = 0; i < n; ++i) { + pixman_box32_t *r = &rects[i]; + if (!wlr_texture_write_pixels(buffer->texture, fmt, stride, + r->x2 - r->x1, r->y2 - r->y1, r->x1, r->y1, + r->x1, r->y1, data)) { + wl_shm_buffer_end_access(shm_buf); + return NULL; + } + } + + wl_shm_buffer_end_access(shm_buf); + + // We have uploaded the data, we don't need to access the wl_buffer + // anymore + wl_buffer_send_release(resource); + + wl_list_remove(&buffer->resource_destroy.link); + wl_resource_add_destroy_listener(resource, &buffer->resource_destroy); + buffer->resource_destroy.notify = buffer_resource_handle_destroy; + + buffer->resource = resource; + buffer->released = true; + return buffer; +} diff --git a/types/wlr_export_dmabuf_v1.c b/types/wlr_export_dmabuf_v1.c new file mode 100644 index 00000000..68adda02 --- /dev/null +++ b/types/wlr_export_dmabuf_v1.c @@ -0,0 +1,197 @@ +#include +#include +#include +#include +#include +#include +#include +#include "wlr-export-dmabuf-unstable-v1-protocol.h" +#include + +#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); + wl_list_remove(&frame->output_swap_buffers.link); + wlr_dmabuf_attributes_finish(&frame->attribs); + 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; + +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_output(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + 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); + + 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; + wl_list_init(&frame->output_swap_buffers.link); + + 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); + + if (!output->impl->export_dmabuf) { + zwlr_export_dmabuf_frame_v1_send_cancel(frame->resource, + ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT); + return; + } + + struct wlr_dmabuf_attributes *attribs = &frame->attribs; + if (!wlr_output_export_dmabuf(output, attribs)) { + zwlr_export_dmabuf_frame_v1_send_cancel(frame->resource, + ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_TEMPORARY); + return; + } + + uint32_t frame_flags = ZWLR_EXPORT_DMABUF_FRAME_V1_FLAGS_TRANSIENT; + uint32_t mod_high = attribs->modifier >> 32; + uint32_t mod_low = attribs->modifier & 0xFFFFFFFF; + + 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); + + 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, attribs->offset[i], attribs->stride[i], i); + } + + 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 = { + .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); +} diff --git a/types/wlr_layer_shell.c b/types/wlr_layer_shell.c index e64d7937..76d6d721 100644 --- a/types/wlr_layer_shell.c +++ b/types/wlr_layer_shell.c @@ -176,7 +176,9 @@ static void layer_surface_unmap(struct wlr_layer_surface *surface) { } static void layer_surface_destroy(struct wlr_layer_surface *surface) { - layer_surface_unmap(surface); + if (surface->configured && surface->mapped) { + layer_surface_unmap(surface); + } wlr_signal_emit_safe(&surface->events.destroy, surface); wl_resource_set_user_data(surface->resource, NULL); wl_list_remove(&surface->surface_destroy_listener.link); diff --git a/types/wlr_linux_dmabuf.c b/types/wlr_linux_dmabuf.c index 3b4fc78b..08f44243 100644 --- a/types/wlr_linux_dmabuf.c +++ b/types/wlr_linux_dmabuf.c @@ -52,11 +52,7 @@ struct wlr_dmabuf_buffer *wlr_dmabuf_buffer_from_buffer_resource( } static void linux_dmabuf_buffer_destroy(struct wlr_dmabuf_buffer *buffer) { - for (int i = 0; i < buffer->attributes.n_planes; i++) { - close(buffer->attributes.fd[i]); - buffer->attributes.fd[i] = -1; - } - buffer->attributes.n_planes = 0; + wlr_dmabuf_attributes_finish(&buffer->attributes); free(buffer); } diff --git a/types/wlr_output.c b/types/wlr_output.c index a5a6d0eb..40332efd 100644 --- a/types/wlr_output.c +++ b/types/wlr_output.c @@ -367,7 +367,8 @@ static void output_fullscreen_surface_render(struct wlr_output *output, struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); assert(renderer); - if (!wlr_surface_has_buffer(surface)) { + struct wlr_texture *texture = wlr_surface_get_texture(surface); + if (texture == NULL) { wlr_renderer_clear(renderer, (float[]){0, 0, 0, 1}); return; } @@ -386,8 +387,7 @@ static void output_fullscreen_surface_render(struct wlr_output *output, for (int i = 0; i < nrects; ++i) { output_scissor(output, &rects[i]); wlr_renderer_clear(renderer, (float[]){0, 0, 0, 1}); - wlr_render_texture_with_matrix(surface->renderer, surface->texture, - matrix, 1.0f); + wlr_render_texture_with_matrix(surface->renderer, texture, matrix, 1.0f); } wlr_renderer_scissor(renderer, NULL); @@ -418,7 +418,7 @@ static void output_cursor_render(struct wlr_output_cursor *cursor, struct wlr_texture *texture = cursor->texture; if (cursor->surface != NULL) { - texture = cursor->surface->texture; + texture = wlr_surface_get_texture(cursor->surface); } if (texture == NULL) { return; @@ -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); @@ -560,6 +565,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_attributes *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); @@ -700,7 +713,7 @@ static bool output_cursor_attempt_hardware(struct wlr_output_cursor *cursor) { enum wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; struct wlr_texture *texture = cursor->texture; if (cursor->surface != NULL) { - texture = cursor->surface->texture; + texture = wlr_surface_get_texture(cursor->surface); scale = cursor->surface->current->scale; transform = cursor->surface->current->transform; } diff --git a/types/wlr_surface.c b/types/wlr_surface.c index fca4e847..59a6fc2c 100644 --- a/types/wlr_surface.c +++ b/types/wlr_surface.c @@ -1,10 +1,9 @@ #include #include #include -#include #include +#include #include -#include #include #include #include @@ -46,13 +45,6 @@ static void surface_handle_buffer_destroy(struct wl_listener *listener, surface_state_reset_buffer(state); } -static void surface_state_release_buffer(struct wlr_surface_state *state) { - if (state->buffer) { - wl_buffer_send_release(state->buffer); - surface_state_reset_buffer(state); - } -} - static void surface_state_set_buffer(struct wlr_surface_state *state, struct wl_resource *buffer) { surface_state_reset_buffer(state); @@ -94,40 +86,25 @@ static void surface_damage(struct wl_client *client, x, y, width, height); } -static struct wlr_frame_callback *frame_callback_from_resource( - struct wl_resource *resource) { - assert(wl_resource_instance_of(resource, &wl_callback_interface, NULL)); - return wl_resource_get_user_data(resource); -} - static void callback_handle_resource_destroy(struct wl_resource *resource) { - struct wlr_frame_callback *cb = frame_callback_from_resource(resource); - wl_list_remove(&cb->link); - free(cb); + wl_list_remove(wl_resource_get_link(resource)); } static void surface_frame(struct wl_client *client, struct wl_resource *resource, uint32_t callback) { struct wlr_surface *surface = wlr_surface_from_resource(resource); - struct wlr_frame_callback *cb = - calloc(1, sizeof(struct wlr_frame_callback)); - if (cb == NULL) { + struct wl_resource *callback_resource = wl_resource_create(client, + &wl_callback_interface, CALLBACK_VERSION, callback); + if (callback_resource == NULL) { wl_resource_post_no_memory(resource); return; } - - cb->resource = wl_resource_create(client, &wl_callback_interface, - CALLBACK_VERSION, callback); - if (cb->resource == NULL) { - free(cb); - wl_resource_post_no_memory(resource); - return; - } - wl_resource_set_implementation(cb->resource, NULL, cb, + wl_resource_set_implementation(callback_resource, NULL, NULL, callback_handle_resource_destroy); - wl_list_insert(surface->pending->frame_callback_list.prev, &cb->link); + wl_list_insert(surface->pending->frame_callback_list.prev, + wl_resource_get_link(callback_resource)); surface->pending->invalid |= WLR_SURFACE_INVALID_FRAME_CALLBACK_LIST; } @@ -175,24 +152,8 @@ static bool surface_update_size(struct wlr_surface *surface, int scale = state->scale; enum wl_output_transform transform = state->transform; - struct wl_shm_buffer *buf = wl_shm_buffer_get(state->buffer); - if (buf != NULL) { - state->buffer_width = wl_shm_buffer_get_width(buf); - state->buffer_height = wl_shm_buffer_get_height(buf); - } else if (wlr_renderer_resource_is_wl_drm_buffer(surface->renderer, - state->buffer)) { - wlr_renderer_wl_drm_buffer_get_size(surface->renderer, state->buffer, - &state->buffer_width, &state->buffer_height); - } else if (wlr_dmabuf_resource_is_buffer(state->buffer)) { - struct wlr_dmabuf_buffer *dmabuf = - wlr_dmabuf_buffer_from_buffer_resource(state->buffer); - state->buffer_width = dmabuf->attributes.width; - state->buffer_height = dmabuf->attributes.height; - } else { - wlr_log(L_ERROR, "Unknown buffer handle attached"); - state->buffer_width = 0; - state->buffer_height = 0; - } + wlr_buffer_get_resource_size(state->buffer, surface->renderer, + &state->buffer_width, &state->buffer_height); int width = state->buffer_width / scale; int height = state->buffer_height / scale; @@ -243,7 +204,6 @@ static void surface_move_state(struct wlr_surface *surface, update_size = true; } if ((next->invalid & WLR_SURFACE_INVALID_BUFFER)) { - surface_state_release_buffer(state); surface_state_set_buffer(state, next->buffer); surface_state_reset_buffer(next); state->sx = next->sx; @@ -350,93 +310,54 @@ static void surface_damage_subsurfaces(struct wlr_subsurface *subsurface) { } } -static void surface_apply_damage(struct wlr_surface *surface, - bool invalid_buffer, bool reupload_buffer) { +static void surface_apply_damage(struct wlr_surface *surface) { struct wl_resource *resource = surface->current->buffer; if (resource == NULL) { + // NULL commit + wlr_buffer_unref(surface->buffer); + surface->buffer = NULL; return; } - struct wl_shm_buffer *buf = wl_shm_buffer_get(resource); - if (buf != NULL) { - wl_shm_buffer_begin_access(buf); + if (surface->buffer != NULL && surface->buffer->released) { + pixman_region32_t damage; + pixman_region32_init(&damage); + pixman_region32_copy(&damage, &surface->current->buffer_damage); + pixman_region32_intersect_rect(&damage, &damage, 0, 0, + surface->current->buffer_width, surface->current->buffer_height); - enum wl_shm_format fmt = wl_shm_buffer_get_format(buf); - int32_t stride = wl_shm_buffer_get_stride(buf); - int32_t width = wl_shm_buffer_get_width(buf); - int32_t height = wl_shm_buffer_get_height(buf); - void *data = wl_shm_buffer_get_data(buf); + struct wlr_buffer *updated_buffer = + wlr_buffer_apply_damage(surface->buffer, resource, &damage); - if (surface->texture == NULL || reupload_buffer) { - wlr_texture_destroy(surface->texture); - surface->texture = wlr_texture_from_pixels(surface->renderer, fmt, - stride, width, height, data); - } else { - pixman_region32_t damage; - pixman_region32_init(&damage); - pixman_region32_copy(&damage, &surface->current->buffer_damage); - pixman_region32_intersect_rect(&damage, &damage, 0, 0, - surface->current->buffer_width, - surface->current->buffer_height); - - int n; - pixman_box32_t *rects = pixman_region32_rectangles(&damage, &n); - for (int i = 0; i < n; ++i) { - pixman_box32_t *r = &rects[i]; - if (!wlr_texture_write_pixels(surface->texture, fmt, stride, - r->x2 - r->x1, r->y2 - r->y1, r->x1, r->y1, - r->x1, r->y1, data)) { - break; - } - } - - pixman_region32_fini(&damage); - } + pixman_region32_fini(&damage); - wl_shm_buffer_end_access(buf); - - // We've uploaded the wl_shm_buffer data to the GPU, we won't access the - // wl_buffer anymore - surface_state_release_buffer(surface->current); - } else if (invalid_buffer || reupload_buffer) { - wlr_texture_destroy(surface->texture); - - if (wlr_renderer_resource_is_wl_drm_buffer(surface->renderer, resource)) { - surface->texture = - wlr_texture_from_wl_drm(surface->renderer, resource); - } else if (wlr_dmabuf_resource_is_buffer(resource)) { - struct wlr_dmabuf_buffer *dmabuf = - wlr_dmabuf_buffer_from_buffer_resource(resource); - surface->texture = - wlr_texture_from_dmabuf(surface->renderer, &dmabuf->attributes); - } else { - surface->texture = NULL; - wlr_log(L_ERROR, "Unknown buffer handle attached"); + if (updated_buffer != NULL) { + surface->buffer = updated_buffer; + return; } + } - // Don't release the wl_buffer yet: since the texture is shared with the - // client, we'll access the wl_buffer when rendering + wlr_buffer_unref(surface->buffer); + surface->buffer = NULL; + + struct wlr_buffer *buffer = wlr_buffer_create(surface->renderer, resource); + if (buffer == NULL) { + wlr_log(L_ERROR, "Failed to upload buffer"); + return; } + + surface->buffer = buffer; } static void surface_commit_pending(struct wlr_surface *surface) { - int32_t oldw = surface->current->buffer_width; - int32_t oldh = surface->current->buffer_height; - bool invalid_buffer = surface->pending->invalid & WLR_SURFACE_INVALID_BUFFER; - bool null_buffer_commit = invalid_buffer && surface->pending->buffer == NULL; surface_move_state(surface, surface->pending, surface->current); - if (null_buffer_commit) { - wlr_texture_destroy(surface->texture); - surface->texture = NULL; + if (invalid_buffer) { + surface_apply_damage(surface); } - bool reupload_buffer = oldw != surface->current->buffer_width || - oldh != surface->current->buffer_height; - surface_apply_damage(surface, invalid_buffer, reupload_buffer); - // commit subsurface order struct wlr_subsurface *subsurface; wl_list_for_each_reverse(subsurface, &surface->subsurface_pending_list, @@ -613,9 +534,9 @@ static struct wlr_surface_state *surface_state_create(void) { static void surface_state_destroy(struct wlr_surface_state *state) { surface_state_reset_buffer(state); - struct wlr_frame_callback *cb, *tmp; - wl_list_for_each_safe(cb, tmp, &state->frame_callback_list, link) { - wl_resource_destroy(cb->resource); + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &state->frame_callback_list) { + wl_resource_destroy(resource); } pixman_region32_fini(&state->surface_damage); @@ -657,10 +578,9 @@ static void surface_handle_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(surface->resource)); wl_list_remove(&surface->renderer_destroy.link); - wlr_texture_destroy(surface->texture); surface_state_destroy(surface->pending); surface_state_destroy(surface->current); - + wlr_buffer_unref(surface->buffer); free(surface); } @@ -717,8 +637,15 @@ struct wlr_surface *wlr_surface_create(struct wl_client *client, return surface; } +struct wlr_texture *wlr_surface_get_texture(struct wlr_surface *surface) { + if (surface->buffer == NULL) { + return NULL; + } + return surface->buffer->texture; +} + bool wlr_surface_has_buffer(struct wlr_surface *surface) { - return surface->texture != NULL; + return wlr_surface_get_texture(surface) != NULL; } int wlr_surface_set_role(struct wlr_surface *surface, const char *role, @@ -1023,11 +950,11 @@ static inline int64_t timespec_to_msec(const struct timespec *a) { void wlr_surface_send_frame_done(struct wlr_surface *surface, const struct timespec *when) { - struct wlr_frame_callback *cb, *cnext; - wl_list_for_each_safe(cb, cnext, &surface->current->frame_callback_list, - link) { - wl_callback_send_done(cb->resource, timespec_to_msec(when)); - wl_resource_destroy(cb->resource); + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, + &surface->current->frame_callback_list) { + wl_callback_send_done(resource, timespec_to_msec(when)); + wl_resource_destroy(resource); } } diff --git a/types/xdg_shell/wlr_xdg_toplevel.c b/types/xdg_shell/wlr_xdg_toplevel.c index fa0ec929..c1350931 100644 --- a/types/xdg_shell/wlr_xdg_toplevel.c +++ b/types/xdg_shell/wlr_xdg_toplevel.c @@ -40,7 +40,7 @@ bool compare_xdg_surface_toplevel_state(struct wlr_xdg_toplevel *state) { // last configure is actually the current state, just use it configured.state = state->current; configured.width = state->base->surface->current->width; - configured.height = state->base->surface->current->width; + configured.height = state->base->surface->current->height; } else { struct wlr_xdg_surface_configure *configure = wl_container_of(state->base->configure_list.prev, configure, link); diff --git a/types/xdg_shell_v6/wlr_xdg_toplevel_v6.c b/types/xdg_shell_v6/wlr_xdg_toplevel_v6.c index 0f9a26d3..e2e1e480 100644 --- a/types/xdg_shell_v6/wlr_xdg_toplevel_v6.c +++ b/types/xdg_shell_v6/wlr_xdg_toplevel_v6.c @@ -288,7 +288,7 @@ bool compare_xdg_surface_v6_toplevel_state(struct wlr_xdg_toplevel_v6 *state) { // last configure is actually the current state, just use it configured.state = state->current; configured.width = state->base->surface->current->width; - configured.height = state->base->surface->current->width; + configured.height = state->base->surface->current->height; } else { struct wlr_xdg_surface_v6_configure *configure = wl_container_of(state->base->configure_list.prev, configure, link);