1
0
mirror of https://github.com/artizirk/wdisplays.git synced 2024-12-22 12:30:15 +02:00

Use wlr-export-dmabuf to capture frames

This commit is contained in:
Jason Francis 2019-10-26 16:02:44 -04:00
parent dd7e1e22ee
commit 3ac3f6cab2
8 changed files with 606 additions and 112 deletions

View File

@ -31,6 +31,7 @@ Build requirements are:
- meson
- GTK+3
- libdrm
- epoxy
- wayland-client

View File

@ -21,9 +21,11 @@ wayland_scanner_client = generator(
client_protocols = [
[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
[wl_protocol_dir, 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml'],
[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
['wlr-output-management-unstable-v1.xml'],
['wlr-screencopy-unstable-v1.xml'],
['wlr-export-dmabuf-unstable-v1.xml'],
['wlr-layer-shell-unstable-v1.xml']
]

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>

View File

@ -3,11 +3,14 @@
#include <gtk/gtk.h>
#include <gdk/gdkwayland.h>
#include <drm_fourcc.h>
#include "wdisplays.h"
#include "glviewport.h"
#include "headform.h"
#include "linux-dmabuf-unstable-v1-client-protocol.h"
__attribute__((noreturn)) void wd_fatal_error(int status, const char *message) {
GtkWindow *parent = gtk_application_get_active_window(GTK_APPLICATION(g_application_get_default()));
GtkWidget *dialog = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", message);
@ -438,7 +441,11 @@ static void canvas_realize(GtkWidget *widget, gpointer data) {
}
struct wd_state *state = data;
state->gl_data = wd_gl_setup();
GdkWindow *window = gtk_widget_get_window(widget);
GdkDisplay *display = gdk_window_get_display(window);
struct wl_display *wl_display = gdk_wayland_display_get_wl_display(display);
state->gl_data = wd_gl_setup(wl_display);
}
static inline bool size_changed(const struct wd_render_head_data *render) {
@ -524,6 +531,7 @@ static void canvas_render(GtkGLArea *area, GdkGLContext *context, gpointer data)
uint64_t tick = gdk_frame_clock_get_frame_time(clock);
wd_capture_frame(state);
state->render.external_images = state->export_manager != NULL;
struct wd_head *head;
wl_list_for_each(head, &state->heads, link) {
@ -534,16 +542,28 @@ static void canvas_render(GtkGLArea *area, GdkGLContext *context, gpointer data)
frame = wl_container_of(output->frames.prev, frame, link);
}
if (render != NULL) {
if (state->capture && frame != NULL && frame->pixels != NULL) {
if (state->capture && frame != NULL && frame->ready) {
if (frame->tick > render->updated_at) {
render->tex_stride = frame->stride;
render->tex_width = frame->width;
render->tex_height = frame->height;
render->pixels = frame->pixels;
if (state->render.external_images) {
render->pixels = NULL;
render->image = wd_gl_create_dmabuf_texture(state->gl_data, frame);
render->has_alpha = TRUE;
render->swap_rgb = TRUE;
} else {
render->pixels = frame->pixels;
render->image = NULL;
render->tex_stride = frame->strides[0];
render->swap_rgb = frame->format == WL_SHM_FORMAT_ABGR8888
|| frame->format == WL_SHM_FORMAT_XBGR8888;
render->has_alpha = frame->format == WL_SHM_FORMAT_ARGB8888
|| frame->format == WL_SHM_FORMAT_ABGR8888;
}
render->y_invert = frame->flags &
ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT;
render->preview = TRUE;
render->updated_at = tick;
render->y_invert = frame->y_invert;
render->swap_rgb = frame->swap_rgb;
}
if (render->preview) {
render->active.rotation = render->queued.rotation;
@ -560,10 +580,12 @@ static void canvas_render(GtkGLArea *area, GdkGLContext *context, gpointer data)
head->surface = draw_head(pango, &state->render, head->name,
render->tex_width, render->tex_height);
render->pixels = cairo_image_surface_get_data(head->surface);
render->image = NULL;
render->tex_stride = cairo_image_surface_get_stride(head->surface);
render->updated_at = tick;
render->active.rotation = 0;
render->active.x_invert = FALSE;
render->has_alpha = FALSE;
render->y_invert = FALSE;
render->swap_rgb = FALSE;
}
@ -1054,7 +1076,7 @@ static void activate(GtkApplication* app, gpointer user_data) {
if (state->xdg_output_manager == NULL) {
wd_fatal_error(1, "Compositor doesn't support xdg-output-unstable-v1");
}
if (state->copy_manager == NULL) {
if (state->copy_manager == NULL && state->export_manager == NULL) {
state->capture = FALSE;
g_simple_action_set_state(capture_action, g_variant_new_boolean(state->capture));
g_simple_action_set_enabled(capture_action, FALSE);

View File

@ -8,6 +8,7 @@ gdk = dependency('gdk-3.0', version: '>= 3.24')
gtk = dependency('gtk+-3.0', version: '>= 3.24')
assert(gdk.get_pkgconfig_variable('targets').split().contains('wayland'), 'Wayland GDK backend not present')
epoxy = dependency('epoxy')
libdrm = dependency('libdrm')
configure_file(input: 'config.h.in', output: 'config.h', configuration: conf)
@ -28,6 +29,7 @@ executable(
wayland_client,
client_protos,
epoxy,
libdrm,
gtk
],
install: true

View File

@ -25,6 +25,8 @@
#include "wlr-output-management-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h"
#include "linux-dmabuf-unstable-v1-client-protocol.h"
#include "wlr-export-dmabuf-unstable-v1-client-protocol.h"
#include "wlr-screencopy-unstable-v1-client-protocol.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
@ -143,15 +145,18 @@ void wd_apply_state(struct wd_state *state, struct wl_list *new_outputs,
static void wd_frame_destroy(struct wd_frame *frame) {
if (frame->pixels != NULL)
munmap(frame->pixels, frame->height * frame->stride);
munmap(frame->pixels, frame->height * frame->strides[0]);
if (frame->buffer != NULL)
wl_buffer_destroy(frame->buffer);
if (frame->pool != NULL)
wl_shm_pool_destroy(frame->pool);
if (frame->capture_fd != -1)
close(frame->capture_fd);
if (frame->wlr_frame != NULL)
zwlr_screencopy_frame_v1_destroy(frame->wlr_frame);
for (int i = 0; i < frame->n_planes; i++)
if (frame->fds[i])
close(frame->fds[i]);
if (frame->copy_frame != NULL)
zwlr_screencopy_frame_v1_destroy(frame->copy_frame);
if (frame->export_frame != NULL)
zwlr_export_dmabuf_frame_v1_destroy(frame->export_frame);
wl_list_remove(&frame->link);
free(frame);
@ -189,6 +194,112 @@ static int create_shm_file(size_t size, const char *fmt, ...) {
return fd;
}
static void make_frame_current(struct wd_frame *frame,
uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
uint64_t tv_sec = (uint64_t) tv_sec_hi << 32 | tv_sec_lo;
frame->tick = (tv_sec * 1000000) + (tv_nsec / 1000);
struct wd_frame *frame_iter, *frame_tmp;
wl_list_for_each_safe(frame_iter, frame_tmp, &frame->output->frames, link) {
if (frame != frame_iter) {
wd_frame_destroy(frame_iter);
}
}
}
#define WD_FRAME_COPY 0x100
static void export_frame(void *data,
struct zwlr_export_dmabuf_frame_v1 *wlr_frame,
uint32_t width, uint32_t height, uint32_t offset_x, uint32_t offset_y,
uint32_t buffer_flags, uint32_t flags, uint32_t format,
uint32_t mod_high, uint32_t mod_low, uint32_t num_objects) {
struct wd_frame *frame = data;
frame->width = width;
frame->height = height;
frame->format = format;
frame->flags = buffer_flags;
/*
if (flags & ZWLR_EXPORT_DMABUF_FRAME_V1_FLAGS_TRANSIENT) {
frame->flags |= WD_FRAME_COPY;
}
*/
frame->modifier = ((uint64_t) mod_high << 32) | mod_low;
frame->n_planes = num_objects;
}
static void export_object(void *data,
struct zwlr_export_dmabuf_frame_v1 *wlr_frame,
uint32_t index, int32_t fd, uint32_t size, uint32_t offset,
uint32_t stride, uint32_t plane_index) {
struct wd_frame *frame = data;
if (frame->flags & WD_FRAME_COPY) {
void *src = NULL;
void *dest = NULL;
int wfd = create_shm_file(size, "/wd-%s", frame->output->name);
if (wfd == -1) {
goto done;
}
src = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, offset);
if (src == MAP_FAILED) {
fprintf(stderr, "mmap: %d: %s\n", fd, strerror(errno));
close(wfd);
wfd = -1;
goto done;
}
dest = mmap(NULL, size - offset, PROT_READ | PROT_WRITE,
MAP_PRIVATE, wfd, 0);
if (dest == MAP_FAILED) {
fprintf(stderr, "mmap: %d: %s\n", wfd, strerror(errno));
close(wfd);
wfd = -1;
goto done;
}
memcpy(dest, src, size - offset);
done:
if (src)
munmap(src, size);
if (dest)
munmap(dest, size - offset);
close(fd);
frame->fds[plane_index] = wfd;
} else {
frame->fds[plane_index] = fd;
frame->offsets[plane_index] = offset;
}
frame->strides[plane_index] = stride;
}
static void export_ready(void *data,
struct zwlr_export_dmabuf_frame_v1 *wlr_frame,
uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
struct wd_frame *frame = data;
make_frame_current(frame, tv_sec_hi, tv_sec_lo, tv_nsec);
if (frame->flags & WD_FRAME_COPY) {
frame->flags ^= WD_FRAME_COPY;
zwlr_export_dmabuf_frame_v1_destroy(frame->export_frame);
frame->export_frame = NULL;
}
frame->ready = true;
}
static void export_cancel(void *data,
struct zwlr_export_dmabuf_frame_v1 *wlr_frame,
uint32_t reason) {
struct wd_frame *frame = data;
wd_frame_destroy(frame);
}
static struct zwlr_export_dmabuf_frame_v1_listener export_listener = {
.frame = export_frame,
.object = export_object,
.ready = export_ready,
.cancel = export_cancel
};
static void capture_buffer(void *data,
struct zwlr_screencopy_frame_v1 *copy_frame,
uint32_t format, uint32_t width, uint32_t height, uint32_t stride) {
@ -199,22 +310,23 @@ static void capture_buffer(void *data,
goto err;
}
frame->n_planes = 1;
size_t size = stride * height;
frame->capture_fd = create_shm_file(size, "/wd-%s", frame->output->name);
if (frame->capture_fd == -1) {
frame->fds[0] = create_shm_file(size, "/wd-%s", frame->output->name);
if (frame->fds[0] == -1) {
goto err;
}
frame->pool = wl_shm_create_pool(frame->output->state->shm,
frame->capture_fd, size);
frame->fds[0], size);
frame->buffer = wl_shm_pool_create_buffer(frame->pool, 0,
width, height, stride, format);
zwlr_screencopy_frame_v1_copy(copy_frame, frame->buffer);
frame->stride = stride;
frame->offsets[0] = 0;
frame->strides[0] = stride;
frame->width = width;
frame->height = height;
frame->swap_rgb = format == WL_SHM_FORMAT_ABGR8888
|| format == WL_SHM_FORMAT_XBGR8888;
frame->format = format;
return;
err:
@ -225,7 +337,9 @@ static void capture_flags(void *data,
struct zwlr_screencopy_frame_v1 *wlr_frame,
uint32_t flags) {
struct wd_frame *frame = data;
frame->y_invert = !!(flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT);
if (flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT) {
frame->flags |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT;
}
}
static void capture_ready(void *data,
@ -233,27 +347,20 @@ static void capture_ready(void *data,
uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
struct wd_frame *frame = data;
frame->pixels = mmap(NULL, frame->stride * frame->height,
PROT_READ, MAP_SHARED, frame->capture_fd, 0);
frame->pixels = mmap(NULL, frame->strides[0] * frame->height,
PROT_READ, MAP_SHARED, frame->fds[0], 0);
if (frame->pixels == MAP_FAILED) {
frame->pixels = NULL;
fprintf(stderr, "mmap: %d: %s\n", frame->capture_fd, strerror(errno));
fprintf(stderr, "mmap: %d: %s\n", frame->fds[0], strerror(errno));
wd_frame_destroy(frame);
return;
} else {
uint64_t tv_sec = (uint64_t) tv_sec_hi << 32 | tv_sec_lo;
frame->tick = (tv_sec * 1000000) + (tv_nsec / 1000);
}
make_frame_current(frame, tv_sec_hi, tv_sec_lo, tv_nsec);
zwlr_screencopy_frame_v1_destroy(frame->wlr_frame);
frame->wlr_frame = NULL;
struct wd_frame *frame_iter, *frame_tmp;
wl_list_for_each_safe(frame_iter, frame_tmp, &frame->output->frames, link) {
if (frame != frame_iter) {
wd_frame_destroy(frame_iter);
}
}
zwlr_screencopy_frame_v1_destroy(frame->copy_frame);
frame->copy_frame = NULL;
frame->ready = true;
}
static void capture_failed(void *data,
@ -274,7 +381,7 @@ static bool has_pending_captures(struct wd_state *state) {
wl_list_for_each(output, &state->outputs, link) {
struct wd_frame *frame;
wl_list_for_each(frame, &output->frames, link) {
if (frame->pixels == NULL) {
if (!frame->ready) {
return true;
}
}
@ -283,8 +390,8 @@ static bool has_pending_captures(struct wd_state *state) {
}
void wd_capture_frame(struct wd_state *state) {
if (state->copy_manager == NULL || has_pending_captures(state)
|| !state->capture) {
if ((state->export_manager == NULL && state->copy_manager == NULL)
|| has_pending_captures(state) || !state->capture) {
return;
}
@ -292,12 +399,21 @@ void wd_capture_frame(struct wd_state *state) {
wl_list_for_each(output, &state->outputs, link) {
struct wd_frame *frame = calloc(1, sizeof(*frame));
frame->output = output;
frame->capture_fd = -1;
frame->wlr_frame =
zwlr_screencopy_manager_v1_capture_output(state->copy_manager, 1,
output->wl_output);
zwlr_screencopy_frame_v1_add_listener(frame->wlr_frame, &capture_listener,
frame);
for (int i = 0; i < WD_MAX_PLANES; i++)
frame->fds[i] = -1;
if (state->export_manager) {
frame->export_frame =
zwlr_export_dmabuf_manager_v1_capture_output(state->export_manager, 1,
output->wl_output);
zwlr_export_dmabuf_frame_v1_add_listener(frame->export_frame,
&export_listener, frame);
} else if (state->copy_manager) {
frame->copy_frame =
zwlr_screencopy_manager_v1_capture_output(state->copy_manager, 1,
output->wl_output);
zwlr_screencopy_frame_v1_add_listener(frame->copy_frame,
&capture_listener, frame);
}
wl_list_insert(&output->frames, &frame->link);
}
}
@ -540,6 +656,9 @@ static void registry_handle_global(void *data, struct wl_registry *registry,
} else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
state->xdg_output_manager = wl_registry_bind(registry, name,
&zxdg_output_manager_v1_interface, version);
} else if(strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name) == 0) {
state->export_manager = wl_registry_bind(registry, name,
&zwlr_export_dmabuf_manager_v1_interface, version);
} else if(strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) {
state->copy_manager = wl_registry_bind(registry, name,
&zwlr_screencopy_manager_v1_interface, version);
@ -689,6 +808,9 @@ void wd_state_destroy(struct wd_state *state) {
if (state->layer_shell != NULL) {
zwlr_layer_shell_v1_destroy(state->layer_shell);
}
if (state->export_manager != NULL) {
zwlr_export_dmabuf_manager_v1_destroy(state->export_manager);
}
if (state->copy_manager != NULL) {
zwlr_screencopy_manager_v1_destroy(state->copy_manager);
}

View File

@ -1,13 +1,24 @@
/* SPDX-FileCopyrightText: 2020 Jason Francis <jason@cycles.network>
* SPDX-License-Identifier: GPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2018 Simon Ser
* SPDX-FileCopyrightText: 2018 Guido Günther
* SPDX-License-Identifier: MIT */
/* Parts of this file are taken from swaywm/wlroots:
* https://github.com/swaywm/wlroots/blob/e97c2c3639119831ced4f6b9f704b096c2075973/render/egl.c
*/
#include "wdisplays.h"
#define WL_EGL_PLATFORM 1
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <epoxy/gl.h>
#include <wayland-util.h>
#include <epoxy/gl.h>
#include <epoxy/egl.h>
#include <drm_fourcc.h>
#define BT_UV_VERT_SIZE (2 + 2)
#define BT_UV_QUAD_SIZE (6 * BT_UV_VERT_SIZE)
@ -29,22 +40,25 @@ enum gl_buffers {
NUM_BUFFERS
};
struct wd_gl_texture_program {
GLuint id;
GLuint position_attribute;
GLuint uv_attribute;
GLuint screen_size_uniform;
GLuint texture_uniform;
GLuint color_transform_uniform;
GLuint color_add_uniform;
};
struct wd_gl_data {
EGLDisplay display;
GLuint color_program;
GLuint color_vertex_shader;
GLuint color_fragment_shader;
GLuint color_position_attribute;
GLuint color_color_attribute;
GLuint color_screen_size_uniform;
GLuint texture_program;
GLuint texture_vertex_shader;
GLuint texture_fragment_shader;
GLuint texture_position_attribute;
GLuint texture_uv_attribute;
GLuint texture_screen_size_uniform;
GLuint texture_texture_uniform;
GLuint texture_color_transform_uniform;
struct wd_gl_texture_program rgb;
GLuint buffers[NUM_BUFFERS];
@ -90,8 +104,9 @@ precision mediump float;\n\
varying vec2 uv_out;\n\
uniform sampler2D texture;\n\
uniform mat4 color_transform;\n\
uniform vec4 color_add;\n\
void main(void) {\n\
gl_FragColor = texture2D(texture, uv_out) * color_transform;\n\
gl_FragColor = texture2D(texture, uv_out) * color_transform + color_add;\n\
}";
static GLuint gl_make_shader(GLenum type, const char *src) {
@ -142,18 +157,62 @@ static void gl_link_and_validate(GLint program) {
}
}
struct wd_gl_data *wd_gl_setup(void) {
static void setup_texture_program(struct wd_gl_texture_program *prog,
const char *fragment_src) {
prog->id = glCreateProgram();
GLuint vertex_shader = gl_make_shader(GL_VERTEX_SHADER,
texture_vertex_shader_src);
glAttachShader(prog->id, vertex_shader);
GLuint fragment_shader = gl_make_shader(GL_FRAGMENT_SHADER, fragment_src);
glAttachShader(prog->id, fragment_shader);
gl_link_and_validate(prog->id);
glDeleteShader(fragment_shader);
glDeleteShader(vertex_shader);
prog->position_attribute = glGetAttribLocation(prog->id, "position");
prog->uv_attribute = glGetAttribLocation(prog->id, "uv");
prog->screen_size_uniform = glGetUniformLocation(prog->id, "screen_size");
prog->texture_uniform = glGetUniformLocation(prog->id, "texture");
prog->color_transform_uniform = glGetUniformLocation(prog->id,
"color_transform");
prog->color_add_uniform = glGetUniformLocation(prog->id, "color_add");
}
#define assert_ext(_name, _expr) do { \
static bool _tested_##_name = false; \
if (!_tested_##_name) { \
_tested_##_name = true; \
if (!(_expr)) \
wd_fatal_error(1, "Extension " #_name " not found\n"); \
} \
} while (0)
#define assert_gl_ext(_name) \
assert_ext(_name, epoxy_has_gl_extension(#_name))
#define assert_egl_ext(_res, _name) \
assert_ext(_name, epoxy_has_egl_extension((_res)->display, #_name))
struct wd_gl_data *wd_gl_setup(struct wl_display *display) {
struct wd_gl_data *res = calloc(1, sizeof(struct wd_gl_data));
res->display = eglGetDisplay(display);
res->color_program = glCreateProgram();
res->color_vertex_shader = gl_make_shader(GL_VERTEX_SHADER,
GLuint vertex_shader = gl_make_shader(GL_VERTEX_SHADER,
color_vertex_shader_src);
glAttachShader(res->color_program, res->color_vertex_shader);
res->color_fragment_shader = gl_make_shader(GL_FRAGMENT_SHADER,
glAttachShader(res->color_program, vertex_shader);
GLuint fragment_shader = gl_make_shader(GL_FRAGMENT_SHADER,
color_fragment_shader_src);
glAttachShader(res->color_program, res->color_fragment_shader);
glAttachShader(res->color_program, fragment_shader);
gl_link_and_validate(res->color_program);
glDeleteShader(fragment_shader);
glDeleteShader(vertex_shader);
setup_texture_program(&res->rgb, texture_fragment_shader_src);
res->color_position_attribute = glGetAttribLocation(res->color_program,
"position");
res->color_color_attribute = glGetAttribLocation(res->color_program,
@ -161,27 +220,6 @@ struct wd_gl_data *wd_gl_setup(void) {
res->color_screen_size_uniform = glGetUniformLocation(res->color_program,
"screen_size");
res->texture_program = glCreateProgram();
res->texture_vertex_shader = gl_make_shader(GL_VERTEX_SHADER,
texture_vertex_shader_src);
glAttachShader(res->texture_program, res->texture_vertex_shader);
res->texture_fragment_shader = gl_make_shader(GL_FRAGMENT_SHADER,
texture_fragment_shader_src);
glAttachShader(res->texture_program, res->texture_fragment_shader);
gl_link_and_validate(res->texture_program);
res->texture_position_attribute = glGetAttribLocation(res->texture_program,
"position");
res->texture_uv_attribute = glGetAttribLocation(res->texture_program,
"uv");
res->texture_screen_size_uniform = glGetUniformLocation(res->texture_program,
"screen_size");
res->texture_texture_uniform = glGetUniformLocation(res->texture_program,
"texture");
res->texture_color_transform_uniform = glGetUniformLocation(
res->texture_program, "color_transform");
glGenBuffers(NUM_BUFFERS, res->buffers);
glBindBuffer(GL_ARRAY_BUFFER, res->buffers[TEXTURE_BUFFER]);
glBufferData(GL_ARRAY_BUFFER, BT_UV_MAX * sizeof(float),
@ -198,6 +236,83 @@ struct wd_gl_data *wd_gl_setup(void) {
return res;
}
EGLImageKHR wd_gl_create_dmabuf_texture(struct wd_gl_data *res,
struct wd_frame *frame) {
assert(!frame->pixels);
assert_egl_ext(res, EGL_KHR_image_base);
assert_egl_ext(res, EGL_EXT_image_dma_buf_import);
bool has_modifier = frame->modifier != DRM_FORMAT_MOD_INVALID
&& frame->modifier != DRM_FORMAT_MOD_LINEAR;
if (has_modifier) {
assert_egl_ext(res, EGL_EXT_image_dma_buf_import_modifiers);
}
unsigned int atti = 0;
EGLint attribs[50];
attribs[atti++] = EGL_WIDTH;
attribs[atti++] = frame->width;
attribs[atti++] = EGL_HEIGHT;
attribs[atti++] = frame->height;
attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT;
attribs[atti++] = frame->format;
struct {
EGLint fd;
EGLint offset;
EGLint pitch;
EGLint mod_lo;
EGLint mod_hi;
} attr_names[WD_MAX_PLANES] = {
{
EGL_DMA_BUF_PLANE0_FD_EXT,
EGL_DMA_BUF_PLANE0_OFFSET_EXT,
EGL_DMA_BUF_PLANE0_PITCH_EXT,
EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT
}, {
EGL_DMA_BUF_PLANE1_FD_EXT,
EGL_DMA_BUF_PLANE1_OFFSET_EXT,
EGL_DMA_BUF_PLANE1_PITCH_EXT,
EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT
}, {
EGL_DMA_BUF_PLANE2_FD_EXT,
EGL_DMA_BUF_PLANE2_OFFSET_EXT,
EGL_DMA_BUF_PLANE2_PITCH_EXT,
EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT,
EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT
}, {
EGL_DMA_BUF_PLANE3_FD_EXT,
EGL_DMA_BUF_PLANE3_OFFSET_EXT,
EGL_DMA_BUF_PLANE3_PITCH_EXT,
EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT,
EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT
}
};
for (int i=0; i < frame->n_planes; i++) {
attribs[atti++] = attr_names[i].fd;
attribs[atti++] = frame->fds[i];
attribs[atti++] = attr_names[i].offset;
attribs[atti++] = frame->offsets[i];
attribs[atti++] = attr_names[i].pitch;
attribs[atti++] = frame->strides[i];
if (has_modifier) {
attribs[atti++] = attr_names[i].mod_lo;
attribs[atti++] = frame->modifier & 0xFFFFFFFF;
attribs[atti++] = attr_names[i].mod_hi;
attribs[atti++] = frame->modifier >> 32;
}
}
attribs[atti++] = EGL_NONE;
assert(atti < sizeof(attribs)/sizeof(attribs[0]));
return eglCreateImageKHR(res->display, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT, NULL, attribs);
}
static const GLfloat TRANSFORM_RGB[16] = {
1, 0, 0, 0,
0, 1, 0, 0,
@ -320,34 +435,42 @@ void wd_gl_render(struct wd_gl_data *res, struct wd_render_data *info,
float screen_size[2] = { info->viewport_width, info->viewport_height };
if (tri_verts > 0) {
glUseProgram(res->texture_program);
glUseProgram(res->rgb.id);
glBindBuffer(GL_ARRAY_BUFFER, res->buffers[TEXTURE_BUFFER]);
glBufferSubData(GL_ARRAY_BUFFER, 0,
tri_verts * BT_UV_VERT_SIZE * sizeof(float), res->verts);
glEnableVertexAttribArray(res->texture_position_attribute);
glEnableVertexAttribArray(res->texture_uv_attribute);
glVertexAttribPointer(res->texture_position_attribute,
glEnableVertexAttribArray(res->rgb.position_attribute);
glEnableVertexAttribArray(res->rgb.uv_attribute);
glVertexAttribPointer(res->rgb.position_attribute,
2, GL_FLOAT, GL_FALSE,
BT_UV_VERT_SIZE * sizeof(float), (void *) (0 * sizeof(float)));
glVertexAttribPointer(res->texture_uv_attribute, 2, GL_FLOAT, GL_FALSE,
glVertexAttribPointer(res->rgb.uv_attribute, 2, GL_FLOAT, GL_FALSE,
BT_UV_VERT_SIZE * sizeof(float), (void *) (2 * sizeof(float)));
glUniform2fv(res->texture_screen_size_uniform, 1, screen_size);
glUniform1i(res->texture_texture_uniform, 0);
glUniform2fv(res->rgb.screen_size_uniform, 1, screen_size);
glUniform1i(res->rgb.texture_uniform, 0);
glActiveTexture(GL_TEXTURE0);
i = 0;
wl_list_for_each_reverse(head, &info->heads, link) {
glBindTexture(GL_TEXTURE_2D, res->textures[i]);
if (head->updated_at == tick) {
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, head->tex_stride / 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
head->tex_width, head->tex_height,
0, GL_RGBA, GL_UNSIGNED_BYTE, head->pixels);
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
if (head->image) {
assert_gl_ext(GL_OES_EGL_image);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, head->image);
} else if (head->pixels) {
assert_gl_ext(GL_EXT_unpack_subimage);
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, head->tex_stride / 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
head->tex_width, head->tex_height,
0, GL_RGBA, GL_UNSIGNED_BYTE, head->pixels);
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
}
glGenerateMipmap(GL_TEXTURE_2D);
}
glUniformMatrix4fv(res->texture_color_transform_uniform, 1, GL_FALSE,
glUniformMatrix4fv(res->rgb.color_transform_uniform, 1, GL_FALSE,
head->swap_rgb ? TRANSFORM_RGB : TRANSFORM_BGR);
float color_add[4] = { 0.f, 0.f, 0.f, head->has_alpha ? 0.f : 1.f };
glUniform4fv(res->rgb.color_add_uniform, 1, color_add);
glDrawArrays(GL_TRIANGLES, i * 6, 6);
i++;
if (i >= HEADS_MAX)
@ -514,13 +637,9 @@ void wd_gl_render(struct wd_gl_data *res, struct wd_render_data *info,
void wd_gl_cleanup(struct wd_gl_data *res) {
glDeleteBuffers(NUM_BUFFERS, res->buffers);
glDeleteShader(res->texture_fragment_shader);
glDeleteShader(res->texture_vertex_shader);
glDeleteProgram(res->texture_program);
glDeleteShader(res->color_fragment_shader);
glDeleteShader(res->color_vertex_shader);
glDeleteProgram(res->color_program);
glDeleteProgram(res->rgb.id);
free(res);
}

View File

@ -27,6 +27,8 @@ struct zxdg_output_manager_v1;
struct zwlr_output_mode_v1;
struct zwlr_output_head_v1;
struct zwlr_output_manager_v1;
struct zwlr_export_dmabuf_manager_v1;
struct zwlr_export_dmabuf_frame_v1;
struct zwlr_screencopy_manager_v1;
struct zwlr_screencopy_frame_v1;
struct zwlr_layer_shell_v1;
@ -41,6 +43,8 @@ typedef struct _GdkCursor GdkCursor;
struct _cairo_surface;
typedef struct _cairo_surface cairo_surface_t;
typedef void *EGLImageKHR;
struct wd_output {
struct wd_state *state;
struct zxdg_output_v1 *xdg_output;
@ -53,21 +57,30 @@ struct wd_output {
struct zwlr_layer_surface_v1 *overlay_layer_surface;
};
#define WD_MAX_PLANES 4
struct wd_frame {
struct wd_output *output;
struct zwlr_screencopy_frame_v1 *wlr_frame;
struct zwlr_export_dmabuf_frame_v1 *export_frame;
struct zwlr_screencopy_frame_v1 *copy_frame;
struct wl_list link;
int capture_fd;
unsigned stride;
unsigned width;
unsigned height;
uint64_t tick;
uint32_t format;
uint32_t flags;
uint64_t modifier;
uint32_t n_planes;
int fds[WD_MAX_PLANES];
uint32_t offsets[WD_MAX_PLANES];
uint32_t strides[WD_MAX_PLANES];
struct wl_shm_pool *pool;
struct wl_buffer *buffer;
uint8_t *pixels;
uint64_t tick;
bool y_invert;
bool swap_rgb;
bool ready;
};
struct wd_head_config {
@ -141,15 +154,17 @@ struct wd_render_head_data {
struct wd_render_head_flags active;
uint8_t *pixels;
EGLImageKHR image;
unsigned tex_stride;
unsigned tex_width;
unsigned tex_height;
bool preview;
bool y_invert;
bool swap_rgb;
bool hovered;
bool clicked;
uint32_t preview : 1,
y_invert : 1,
swap_rgb : 1,
has_alpha : 1,
hovered : 1,
clicked : 1;
};
struct wd_render_data {
@ -166,6 +181,7 @@ struct wd_render_data {
int x_origin;
int y_origin;
uint64_t updated_at;
bool external_images;
struct wl_list heads;
};
@ -178,6 +194,7 @@ struct wd_point {
struct wd_state {
struct zxdg_output_manager_v1 *xdg_output_manager;
struct zwlr_output_manager_v1 *output_manager;
struct zwlr_export_dmabuf_manager_v1 *export_manager;
struct zwlr_screencopy_manager_v1 *copy_manager;
struct zwlr_layer_shell_v1 *layer_shell;
struct wl_shm *shm;
@ -311,8 +328,14 @@ void wd_ui_show_error(struct wd_state *state, const char *message);
/*
* Compiles the GL shaders.
*/
struct wd_gl_data *wd_gl_setup(void);
struct wd_gl_data *wd_gl_setup(struct wl_display *display);
/*
* Creates an EGLImage from a given output frame. The frame must have a dmabuf
* received from a wlr_export_dmabuf_frame.
*/
EGLImageKHR wd_gl_create_dmabuf_texture(struct wd_gl_data *res,
struct wd_frame *frame);
/*
* Renders the GL scene.
*/