mirror of
https://github.com/hyprwm/wlroots-hyprland.git
synced 2024-12-23 18:39:48 +01:00
Merge pull request #992 from emersion/screencontent
Implement wlr_export_dmabuf_unstable_v1 protocol
This commit is contained in:
commit
e459fe0ec7
27 changed files with 1396 additions and 29 deletions
15
.build.yml
15
.build.yml
|
@ -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:
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
743
examples/dmabuf-capture.c
Normal 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, ®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 <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);
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
37
include/wlr/types/wlr_export_dmabuf_v1.h
Normal file
37
include/wlr/types/wlr_export_dmabuf_v1.h
Normal 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
|
|
@ -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);
|
||||||
|
|
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
203
protocol/wlr-export-dmabuf-unstable-v1.xml
Normal file
203
protocol/wlr-export-dmabuf-unstable-v1.xml
Normal 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
10
render/dmabuf.c
Normal 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;
|
||||||
|
}
|
36
render/egl.c
36
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 =
|
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;
|
||||||
|
|
|
@ -10,6 +10,8 @@ eglCreatePlatformWindowSurfaceEXT
|
||||||
-eglSwapBuffersWithDamageKHR
|
-eglSwapBuffersWithDamageKHR
|
||||||
-eglQueryDmaBufFormatsEXT
|
-eglQueryDmaBufFormatsEXT
|
||||||
-eglQueryDmaBufModifiersEXT
|
-eglQueryDmaBufModifiersEXT
|
||||||
|
-eglExportDMABUFImageQueryMESA
|
||||||
|
-eglExportDMABUFImageMESA
|
||||||
-eglDebugMessageControlKHR
|
-eglDebugMessageControlKHR
|
||||||
-glDebugMessageCallbackKHR
|
-glDebugMessageCallbackKHR
|
||||||
-glDebugMessageControlKHR
|
-glDebugMessageControlKHR
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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',
|
||||||
|
|
197
types/wlr_export_dmabuf_v1.c
Normal file
197
types/wlr_export_dmabuf_v1.c
Normal 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);
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue