Merge pull request #992 from emersion/screencontent

Implement wlr_export_dmabuf_unstable_v1 protocol
This commit is contained in:
Drew DeVault 2018-06-22 05:37:07 -07:00 committed by GitHub
commit e459fe0ec7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1396 additions and 29 deletions

View file

@ -1,15 +1,16 @@
image: archlinux image: archlinux
packages: packages:
- meson - clang
- wayland - ffmpeg
- wayland-protocols - libcap
- mesa
- libinput - libinput
- libxkbcommon - libxkbcommon
- xcb-util-image - mesa
- libcap - meson
- pixman - pixman
- clang - wayland
- wayland-protocols
- xcb-util-image
sources: sources:
- https://github.com/swaywm/wlroots - https://github.com/swaywm/wlroots
tasks: tasks:

View file

@ -254,6 +254,25 @@ static uint32_t drm_connector_get_gamma_size(struct wlr_output *output) {
return 0; 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) { static void drm_connector_start_renderer(struct wlr_drm_connector *conn) {
if (conn->state != WLR_DRM_CONN_CONNECTED) { if (conn->state != WLR_DRM_CONN_CONNECTED) {
return; return;
@ -742,6 +761,7 @@ static const struct wlr_output_impl output_impl = {
.swap_buffers = drm_connector_swap_buffers, .swap_buffers = drm_connector_swap_buffers,
.set_gamma = drm_connector_set_gamma, .set_gamma = drm_connector_set_gamma,
.get_gamma_size = drm_connector_get_gamma_size, .get_gamma_size = drm_connector_get_gamma_size,
.export_dmabuf = drm_connector_export_dmabuf,
}; };
bool wlr_output_is_drm(struct wlr_output *output) { bool wlr_output_is_drm(struct wlr_output *output) {

View file

@ -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 tex {
struct wlr_egl *egl; struct wlr_egl *egl;
EGLImageKHR img; EGLImageKHR img;
@ -186,16 +214,11 @@ static struct wlr_texture *get_tex_for_bo(struct wlr_drm_renderer *renderer,
return NULL; return NULL;
} }
struct wlr_dmabuf_attributes attribs = { struct wlr_dmabuf_attributes attribs;
.n_planes = 1, if (!export_drm_bo(bo, &attribs)) {
.width = gbm_bo_get_width(bo), free(tex);
.height = gbm_bo_get_height(bo), return NULL;
.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);
tex->tex = wlr_texture_from_dmabuf(renderer->wlr_rend, &attribs); tex->tex = wlr_texture_from_dmabuf(renderer->wlr_rend, &attribs);
if (tex->tex == NULL) { if (tex->tex == NULL) {

743
examples/dmabuf-capture.c Normal file
View file

@ -0,0 +1,743 @@
#define _XOPEN_SOURCE 700
#define _POSIX_C_SOURCE 199309L
#include <libavformat/avformat.h>
#include <libavutil/display.h>
#include <libavutil/hwcontext_drm.h>
#include <libavutil/pixdesc.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libdrm/drm_fourcc.h>
#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, &registry_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 <source id> <hardware device type> <device> "
"<encoder name> <pixel format> <bitrate in Mbps> <file path>\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);
}

View file

@ -1,6 +1,10 @@
threads = dependency('threads') threads = dependency('threads')
wayland_cursor = dependency('wayland-cursor') 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('simple', 'simple.c', dependencies: wlroots)
executable('pointer', 'pointer.c', dependencies: wlroots) executable('pointer', 'pointer.c', dependencies: wlroots)
executable('touch', 'touch.c', 'cat.c', dependencies: wlroots) executable('touch', 'touch.c', 'cat.c', dependencies: wlroots)
@ -38,3 +42,11 @@ executable(
'input-inhibitor.c', 'input-inhibitor.c',
dependencies: [wayland_cursor, wayland_client, wlr_protos, wlroots] 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

View file

@ -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); void post_drm_surface(struct wlr_drm_surface *surf);
struct gbm_bo *copy_drm_surface_mgpu(struct wlr_drm_surface *dest, struct gbm_bo *copy_drm_surface_mgpu(struct wlr_drm_surface *dest,
struct gbm_bo *src); struct gbm_bo *src);
bool export_drm_bo(struct gbm_bo *bo, struct wlr_dmabuf_attributes *attribs);
#endif #endif

View file

@ -45,6 +45,7 @@ struct roots_desktop {
struct wlr_xdg_shell *xdg_shell; struct wlr_xdg_shell *xdg_shell;
struct wlr_gamma_control_manager *gamma_control_manager; struct wlr_gamma_control_manager *gamma_control_manager;
struct wlr_screenshooter *screenshooter; struct wlr_screenshooter *screenshooter;
struct wlr_export_dmabuf_manager_v1 *export_dmabuf_manager_v1;
struct wlr_server_decoration_manager *server_decoration_manager; struct wlr_server_decoration_manager *server_decoration_manager;
struct wlr_primary_selection_device_manager *primary_selection_device_manager; struct wlr_primary_selection_device_manager *primary_selection_device_manager;
struct wlr_idle *idle; struct wlr_idle *idle;

View file

@ -23,6 +23,8 @@ struct wlr_output_impl {
void (*set_gamma)(struct wlr_output *output, void (*set_gamma)(struct wlr_output *output,
uint32_t size, uint16_t *r, uint16_t *g, uint16_t *b); uint32_t size, uint16_t *r, uint16_t *g, uint16_t *b);
uint32_t (*get_gamma_size)(struct wlr_output *output); 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, void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend,

View file

@ -1,6 +1,8 @@
#ifndef WLR_RENDER_DMABUF_H #ifndef WLR_RENDER_DMABUF_H
#define WLR_RENDER_DMABUF_H #define WLR_RENDER_DMABUF_H
#include <stdint.h>
// So we don't have to pull in linux specific drm headers // So we don't have to pull in linux specific drm headers
#ifndef DRM_FORMAT_MOD_INVALID #ifndef DRM_FORMAT_MOD_INVALID
#define DRM_FORMAT_MOD_INVALID ((1ULL<<56) - 1) #define DRM_FORMAT_MOD_INVALID ((1ULL<<56) - 1)
@ -26,4 +28,9 @@ struct wlr_dmabuf_attributes {
int fd[WLR_DMABUF_MAX_PLANES]; 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 #endif

View file

@ -18,9 +18,10 @@ struct wlr_egl {
struct { struct {
bool bind_wayland_display_wl; bool bind_wayland_display_wl;
bool buffer_age_ext; bool buffer_age_ext;
bool image_dmabuf_import_modifiers_ext;
bool image_dmabuf_import_ext;
bool image_base_khr; 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_ext;
bool swap_buffers_with_damage_khr; bool swap_buffers_with_damage_khr;
} exts; } 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, int wlr_egl_get_dmabuf_modifiers(struct wlr_egl *egl, int format,
uint64_t **modifiers); 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. * Destroys an EGL image created with the given wlr_egl.
*/ */

View file

@ -60,6 +60,8 @@ struct wlr_texture_impl {
enum wl_shm_format wl_fmt, uint32_t stride, uint32_t width, 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 height, uint32_t src_x, uint32_t src_y, uint32_t dst_x,
uint32_t dst_y, const void *data); 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); void (*destroy)(struct wlr_texture *texture);
}; };

View file

@ -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, uint32_t src_x, uint32_t src_y, uint32_t dst_x, uint32_t dst_y,
const void *data); const void *data);
bool wlr_texture_to_dmabuf(struct wlr_texture *texture,
struct wlr_dmabuf_attributes *attribs);
/** /**
* Destroys this wlr_texture. * Destroys this wlr_texture.
*/ */

View file

@ -0,0 +1,37 @@
#ifndef WLR_TYPES_WLR_EXPORT_DMABUF_V1_H
#define WLR_TYPES_WLR_EXPORT_DMABUF_V1_H
#include <wayland-server.h>
#include <wlr/render/dmabuf.h>
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

View file

@ -6,6 +6,7 @@
#include <time.h> #include <time.h>
#include <wayland-server.h> #include <wayland-server.h>
#include <wayland-util.h> #include <wayland-util.h>
#include <wlr/types/wlr_linux_dmabuf.h>
struct wlr_output_mode { struct wlr_output_mode {
uint32_t flags; // enum wl_output_mode uint32_t flags; // enum wl_output_mode
@ -81,7 +82,7 @@ struct wlr_output {
struct { struct {
struct wl_signal frame; struct wl_signal frame;
struct wl_signal needs_swap; 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 enable;
struct wl_signal mode; struct wl_signal mode;
struct wl_signal scale; struct wl_signal scale;
@ -107,6 +108,12 @@ struct wlr_output {
void *data; void *data;
}; };
struct wlr_output_event_swap_buffers {
struct wlr_output *output;
struct timespec *when;
pixman_region32_t *damage;
};
struct wlr_surface; struct wlr_surface;
void wlr_output_enable(struct wlr_output *output, bool enable); 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, void wlr_output_set_gamma(struct wlr_output *output,
uint32_t size, uint16_t *r, uint16_t *g, uint16_t *b); uint32_t size, uint16_t *r, uint16_t *g, uint16_t *b);
uint32_t wlr_output_get_gamma_size(struct wlr_output *output); 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, void wlr_output_set_fullscreen_surface(struct wlr_output *output,
struct wlr_surface *surface); struct wlr_surface *surface);
struct wlr_output *wlr_output_from_resource(struct wl_resource *resource); struct wlr_output *wlr_output_from_resource(struct wl_resource *resource);

View file

@ -39,8 +39,9 @@ protocols = [
'screenshooter.xml', 'screenshooter.xml',
'server-decoration.xml', 'server-decoration.xml',
'virtual-keyboard-unstable-v1.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-input-inhibitor-unstable-v1.xml',
'wlr-layer-shell-unstable-v1.xml',
] ]
client_protocols = [ client_protocols = [
@ -49,6 +50,7 @@ client_protocols = [
[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
'idle.xml', 'idle.xml',
'screenshooter.xml', 'screenshooter.xml',
'wlr-export-dmabuf-unstable-v1.xml',
'wlr-layer-shell-unstable-v1.xml', 'wlr-layer-shell-unstable-v1.xml',
'wlr-input-inhibitor-unstable-v1.xml', 'wlr-input-inhibitor-unstable-v1.xml',
] ]

View file

@ -0,0 +1,203 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_export_dmabuf_unstable_v1">
<copyright>
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.
</copyright>
<description summary="a protocol for low overhead screen content capturing">
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.
</description>
<interface name="zwlr_export_dmabuf_manager_v1" version="1">
<description summary="manager to inform clients and begin capturing">
This object is a manager with which to start capturing from sources.
</description>
<request name="capture_output">
<description summary="capture a frame from an output">
Capture the next frame of a an entire output.
</description>
<arg name="frame" type="new_id" interface="zwlr_export_dmabuf_frame_v1"/>
<arg name="overlay_cursor" type="int"
summary="include custom client hardware cursor on top of the frame"/>
<arg name="output" type="object" interface="wl_output"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the manager">
All objects created by the manager will still remain valid, until their
appropriate destroy request has been called.
</description>
</request>
</interface>
<interface name="zwlr_export_dmabuf_frame_v1" version="1">
<description summary="a DMA-BUF frame">
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.
</description>
<enum name="flags">
<description summary="frame flags">
Special flags that should be respected by the client.
</description>
<entry name="transient" value="0x1"
summary="clients should copy frame before processing"/>
</enum>
<event name="frame">
<description summary="a frame description">
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.
</description>
<arg name="width" type="uint"
summary="frame width in pixels"/>
<arg name="height" type="uint"
summary="frame height in pixels"/>
<arg name="offset_x" type="uint"
summary="crop offset for the x axis"/>
<arg name="offset_y" type="uint"
summary="crop offset for the y axis"/>
<arg name="buffer_flags" type="uint"
summary="flags which indicate properties (invert, interlacing),
has the same values as zwp_linux_buffer_params_v1:flags"/>
<arg name="flags" type="uint" enum="flags"
summary="indicates special frame features"/>
<arg name="format" type="uint"
summary="format of the frame (DRM_FORMAT_*)"/>
<arg name="mod_high" type="uint"
summary="drm format modifier, high"/>
<arg name="mod_low" type="uint"
summary="drm format modifier, low"/>
<arg name="num_objects" type="uint"
summary="indicates how many objects (FDs) the frame has (max 4)"/>
</event>
<event name="object">
<description summary="an object description">
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.
</description>
<arg name="index" type="uint"
summary="index of the current object"/>
<arg name="fd" type="fd"
summary="fd of the current object"/>
<arg name="size" type="uint"
summary="size in bytes for the current object"/>
<arg name="offset" type="uint"
summary="starting point for the data in the object's fd"/>
<arg name="stride" type="uint"
summary="line size in bytes"/>
<arg name="plane_index" type="uint"
summary="index of the the plane the data in the object applies to"/>
</event>
<event name="ready">
<description summary="indicates frame 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.
After receiving this event, the client should destroy this object.
</description>
<arg name="tv_sec_hi" type="uint"
summary="high 32 bits of the seconds part of the timestamp"/>
<arg name="tv_sec_lo" type="uint"
summary="low 32 bits of the seconds part of the timestamp"/>
<arg name="tv_nsec" type="uint"
summary="nanoseconds part of the timestamp"/>
</event>
<enum name="cancel_reason">
<description summary="cancel reason">
Indicates reason for cancelling the frame.
</description>
<entry name="temporary" value="0"
summary="temporary error, source will produce more frames"/>
<entry name="permanent" value="1"
summary="fatal error, source will not produce frames"/>
<entry name="resizing" value="2"
summary="temporary error, source will produce more frames"/>
</enum>
<event name="cancel">
<description summary="indicates the frame is no longer valid">
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.
</description>
<arg name="reason" type="uint" enum="cancel_reason"
summary="indicates a reason for cancelling this frame capture"/>
</event>
<request name="destroy" type="destructor">
<description summary="delete this object, used or not">
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.
</description>
</request>
</interface>
</protocol>

10
render/dmabuf.c Normal file
View file

@ -0,0 +1,10 @@
#include <unistd.h>
#include <wlr/render/dmabuf.h>
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;
}

View file

@ -164,6 +164,11 @@ bool wlr_egl_init(struct wlr_egl *egl, EGLenum platform, void *remote_display,
egl->exts.image_dmabuf_import_modifiers_ext = egl->exts.image_dmabuf_import_modifiers_ext =
check_egl_ext(egl->exts_str, "EGL_EXT_image_dma_buf_import_modifiers") check_egl_ext(egl->exts_str, "EGL_EXT_image_dma_buf_import_modifiers")
&& eglQueryDmaBufFormatsEXT && eglQueryDmaBufModifiersEXT; && 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); print_dmabuf_formats(egl);
egl->exts.bind_wayland_display_wl = egl->exts.bind_wayland_display_wl =
@ -511,6 +516,37 @@ int wlr_egl_get_dmabuf_modifiers(struct wlr_egl *egl,
return num; 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) { bool wlr_egl_destroy_surface(struct wlr_egl *egl, EGLSurface surface) {
if (!surface) { if (!surface) {
return true; return true;

View file

@ -10,6 +10,8 @@ eglCreatePlatformWindowSurfaceEXT
-eglSwapBuffersWithDamageKHR -eglSwapBuffersWithDamageKHR
-eglQueryDmaBufFormatsEXT -eglQueryDmaBufFormatsEXT
-eglQueryDmaBufModifiersEXT -eglQueryDmaBufModifiersEXT
-eglExportDMABUFImageQueryMESA
-eglExportDMABUFImageMESA
-eglDebugMessageControlKHR -eglDebugMessageControlKHR
-glDebugMessageCallbackKHR -glDebugMessageCallbackKHR
-glDebugMessageControlKHR -glDebugMessageControlKHR

View file

@ -74,6 +74,34 @@ static bool gles2_texture_write_pixels(struct wlr_texture *wlr_texture,
return true; 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) { static void gles2_texture_destroy(struct wlr_texture *wlr_texture) {
if (wlr_texture == NULL) { if (wlr_texture == NULL) {
return; return;
@ -102,6 +130,7 @@ static void gles2_texture_destroy(struct wlr_texture *wlr_texture) {
static const struct wlr_texture_impl texture_impl = { static const struct wlr_texture_impl texture_impl = {
.get_size = gles2_texture_get_size, .get_size = gles2_texture_get_size,
.write_pixels = gles2_texture_write_pixels, .write_pixels = gles2_texture_write_pixels,
.to_dmabuf = gles2_texture_to_dmabuf,
.destroy = gles2_texture_destroy, .destroy = gles2_texture_destroy,
}; };

View file

@ -9,6 +9,7 @@ glapi = custom_target('glapi',
lib_wlr_render = static_library( lib_wlr_render = static_library(
'wlr_render', 'wlr_render',
files( files(
'dmabuf.c',
'egl.c', 'egl.c',
'gles2/pixel_format.c', 'gles2/pixel_format.c',
'gles2/renderer.c', 'gles2/renderer.c',

View file

@ -54,3 +54,11 @@ bool wlr_texture_write_pixels(struct wlr_texture *texture,
return texture->impl->write_pixels(texture, wl_fmt, stride, width, height, return texture->impl->write_pixels(texture, wl_fmt, stride, width, height,
src_x, src_y, dst_x, dst_y, data); 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);
}

View file

@ -7,9 +7,10 @@
#include <wlr/types/wlr_box.h> #include <wlr/types/wlr_box.h>
#include <wlr/types/wlr_compositor.h> #include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_cursor.h> #include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_export_dmabuf_v1.h>
#include <wlr/types/wlr_gamma_control.h> #include <wlr/types/wlr_gamma_control.h>
#include <wlr/types/wlr_idle.h>
#include <wlr/types/wlr_idle_inhibit_v1.h> #include <wlr/types/wlr_idle_inhibit_v1.h>
#include <wlr/types/wlr_idle.h>
#include <wlr/types/wlr_input_inhibitor.h> #include <wlr/types/wlr_input_inhibitor.h>
#include <wlr/types/wlr_layer_shell.h> #include <wlr/types/wlr_layer_shell.h>
#include <wlr/types/wlr_linux_dmabuf.h> #include <wlr/types/wlr_linux_dmabuf.h>
@ -18,9 +19,9 @@
#include <wlr/types/wlr_server_decoration.h> #include <wlr/types/wlr_server_decoration.h>
#include <wlr/types/wlr_wl_shell.h> #include <wlr/types/wlr_wl_shell.h>
#include <wlr/types/wlr_xcursor_manager.h> #include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/types/wlr_xdg_output.h>
#include <wlr/types/wlr_xdg_shell_v6.h> #include <wlr/types/wlr_xdg_shell_v6.h>
#include <wlr/types/wlr_xdg_shell.h> #include <wlr/types/wlr_xdg_shell.h>
#include <wlr/types/wlr_xdg_output.h>
#include <wlr/util/log.h> #include <wlr/util/log.h>
#include "rootston/layers.h" #include "rootston/layers.h"
#include "rootston/seat.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( desktop->gamma_control_manager = wlr_gamma_control_manager_create(
server->wl_display); server->wl_display);
desktop->screenshooter = wlr_screenshooter_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 = desktop->server_decoration_manager =
wlr_server_decoration_manager_create(server->wl_display); wlr_server_decoration_manager_create(server->wl_display);
wlr_server_decoration_manager_set_default_mode( wlr_server_decoration_manager_set_default_mode(

View file

@ -23,6 +23,7 @@ lib_wlr_types = static_library(
'wlr_buffer.c', 'wlr_buffer.c',
'wlr_compositor.c', 'wlr_compositor.c',
'wlr_cursor.c', 'wlr_cursor.c',
'wlr_export_dmabuf_v1.c',
'wlr_gamma_control.c', 'wlr_gamma_control.c',
'wlr_idle_inhibit_v1.c', 'wlr_idle_inhibit_v1.c',
'wlr_idle.c', 'wlr_idle.c',

View file

@ -0,0 +1,197 @@
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <wlr/interfaces/wlr_output.h>
#include <wlr/types/wlr_export_dmabuf_v1.h>
#include <wlr/types/wlr_linux_dmabuf.h>
#include <wlr/types/wlr_output.h>
#include "wlr-export-dmabuf-unstable-v1-protocol.h"
#include <wlr/util/log.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);
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);
}

View file

@ -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) { static void linux_dmabuf_buffer_destroy(struct wlr_dmabuf_buffer *buffer) {
for (int i = 0; i < buffer->attributes.n_planes; i++) { wlr_dmabuf_attributes_finish(&buffer->attributes);
close(buffer->attributes.fd[i]);
buffer->attributes.fd[i] = -1;
}
buffer->attributes.n_planes = 0;
free(buffer); free(buffer);
} }

View file

@ -467,7 +467,12 @@ bool wlr_output_swap_buffers(struct wlr_output *output, struct timespec *when,
output->idle_frame = NULL; 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; int width, height;
wlr_output_transformed_resolution(output, &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); 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) { void wlr_output_update_needs_swap(struct wlr_output *output) {
output->needs_swap = true; output->needs_swap = true;
wlr_signal_emit_safe(&output->events.needs_swap, output); wlr_signal_emit_safe(&output->events.needs_swap, output);