summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGhostNaN <59353890+GhostNaN@users.noreply.github.com>2022-09-16 01:42:45 +0000
committerGitHub <noreply@github.com>2022-09-16 01:42:45 +0000
commit11d42ca12b1f8c649f45482f0ba4a597ffc7c6ee (patch)
tree3d105495857ecd84eec6a4ba0c4f62d91ca0e544
parentf65700a3ecc9ecd8ca501e18a807ee18845f9441 (diff)
parent781320f5c0b19ed37a0d274720a045a1cd60e32f (diff)
Merge pull request #26 from mstoeckl/multi-output
Allow rendering on multiple outputs with <output> = *
-rw-r--r--mpvpaper.man5
-rw-r--r--src/holder.c6
-rw-r--r--src/main.c190
3 files changed, 140 insertions, 61 deletions
diff --git a/mpvpaper.man b/mpvpaper.man
index 217c360..d6cb1cb 100644
--- a/mpvpaper.man
+++ b/mpvpaper.man
@@ -54,6 +54,11 @@ Simple example:
mpvpaper DP-2 /path/to/video
.RE
+To play the same video on all outputs:
+.RS
+mpvpaper '*' /path/to/video
+.RE
+
Forward mpv options by passing "--mpv-options" or "-o" like so:
.RS
mpvpaper -o "no-audio --loop-playlist shuffle" HDMI-A-1 www.url/to/playlist
diff --git a/src/holder.c b/src/holder.c
index 3fb0f2c..75e8f51 100644
--- a/src/holder.c
+++ b/src/holder.c
@@ -210,10 +210,12 @@ static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output
struct display_output *output = data;
- if (strcmp(output->name, output->state->monitor) == 0 && !output->layer_surface) {
+ bool name_ok = (strcmp(output->name, output->state->monitor) == 0) ||
+ (strcmp(output->state->monitor, "*") == 0);
+ if (name_ok && !output->layer_surface) {
create_layer_surface(output);
}
- else {
+ if (!name_ok) {
destroy_display_output(output);
}
}
diff --git a/src/main.c b/src/main.c
index 52746b7..b6ceacb 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,8 +1,11 @@
+#include <errno.h>
+#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <unistd.h>
+#include <poll.h>
#include <pthread.h>
#include <time.h>
#include <stdbool.h>
@@ -44,20 +47,25 @@ struct display_output {
struct wl_state *state;
struct wl_surface *surface;
struct zwlr_layer_surface_v1 *layer_surface;
+ struct wl_egl_window* egl_window;
+ EGLSurface *egl_surface;
uint32_t width, height;
uint32_t scale;
struct wl_list link;
+
+ struct wl_callback *frame_callback;
+ bool redraw_needed;
};
-static struct wl_egl_window* egl_window;
+static EGLConfig egl_config;
static EGLDisplay *egl_display;
static EGLContext *egl_context;
-static EGLSurface *egl_surface;
static mpv_handle *mpv;
static mpv_render_context *mpv_glcontext;
+static int wakeup_pipe[2];
static char *video_path;
static char *mpv_options;
@@ -96,12 +104,8 @@ static void exit_cleanup() {
if (mpv)
mpv_terminate_destroy(mpv);
- if (egl_surface)
- eglDestroySurface(egl_display, egl_surface);
if (egl_context)
eglDestroyContext(egl_display, egl_context);
- if (egl_window)
- wl_egl_window_destroy(egl_window);
}
static void exit_mpvpaper(int reason) {
@@ -131,21 +135,33 @@ static void render(struct display_output *output) {
}},
// Flip rendering (needed due to flipped GL coordinate system).
{MPV_RENDER_PARAM_FLIP_Y, &(int){1}},
+ // Do not wait for a fresh frame to render
+ {MPV_RENDER_PARAM_BLOCK_FOR_TARGET_TIME, &(int){0}},
+ {MPV_RENDER_PARAM_INVALID, NULL},
};
+
+ if (!eglMakeCurrent(egl_display, output->egl_surface, output->egl_surface, egl_context)) {
+ cflp_error("Failed to make output surface current 0x%X", eglGetError());
+ }
+ glViewport(0, 0, output->width * output->scale, output->height * output->scale);
+
// Render frame
mpv_render_context_render(mpv_glcontext, render_params);
// Callback new frame
- struct wl_callback *callback = wl_surface_frame(output->surface);
- wl_callback_add_listener(callback, &wl_surface_frame_listener, output);
+ output->frame_callback = wl_surface_frame(output->surface);
+ wl_callback_add_listener(output->frame_callback, &wl_surface_frame_listener, output);
+ output->redraw_needed = false;
// Display frame
- if (!eglSwapBuffers(egl_display, egl_surface))
+ if (!eglSwapBuffers(egl_display, output->egl_surface))
cflp_error("Failed to swap egl buffers 0x%X", eglGetError());
}
static void frame_handle_done(void *data, struct wl_callback *callback, uint32_t frame_time) {
(void) frame_time;
+ struct display_output *output = data;
+ output->frame_callback = NULL;
wl_callback_destroy(callback);
// Reset deadman switch timer
@@ -166,9 +182,10 @@ static void frame_handle_done(void *data, struct wl_callback *callback, uint32_t
}
// Render next frame
- if (!halt_info.kill_render_loop)
- render(data);
- else {
+ if (!halt_info.kill_render_loop) {
+ if (output->redraw_needed)
+ render(output);
+ } else {
halt_info.kill_render_loop = 0;
exit_mpvpaper(0);
}
@@ -485,6 +502,12 @@ static void *get_proc_address_mpv(void *ctx, const char *name){
return eglGetProcAddress(name);
}
+static void render_update_callback(void *callback_ctx) {
+ (void)callback_ctx;
+ uint8_t tmp = 0;
+ write(wakeup_pipe[1], &tmp, 1);
+}
+
static void init_mpv(struct display_output *output) {
mpv = mpv_create();
@@ -515,6 +538,7 @@ static void init_mpv(struct display_output *output) {
{MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &(mpv_opengl_init_params){
.get_proc_address = get_proc_address_mpv,
}},
+ {MPV_RENDER_PARAM_INVALID, NULL},
};
if (mpv_render_context_create(&mpv_glcontext, mpv, params) < 0)
cflp_error("Failed to initialize mpv GL context");
@@ -549,16 +573,14 @@ static void init_mpv(struct display_output *output) {
// mpv must never idle
mpv_command(mpv, (const char*[]) {"set", "idle", "no", NULL});
+
+ mpv_render_context_set_update_callback(mpv_glcontext, render_update_callback, NULL);
}
-static void init_egl(struct display_output *output) {
+static void init_egl(struct wl_state *state) {
+ egl_display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, state->display, NULL);
+ eglInitialize(egl_display, NULL, NULL);
- wl_surface_set_buffer_scale(output->surface, output->scale);
- egl_window = wl_egl_window_create(output->surface, output->width * output->scale, output->height * output->scale);
- if (!egl_display) {
- egl_display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, output->state->display, NULL);
- eglInitialize(egl_display, NULL, NULL);
- }
eglBindAPI(EGL_OPENGL_API);
const EGLint win_attrib[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
@@ -569,49 +591,44 @@ static void init_egl(struct display_output *output) {
EGL_NONE
};
- EGLConfig config;
EGLint config_len;
- eglChooseConfig(egl_display, win_attrib, &config, 1, &config_len);
+ eglChooseConfig(egl_display, win_attrib, &egl_config, 1, &config_len);
- if (!egl_context) {
- // Check for OpenGL compatibility for creating egl context
- static const struct { int major, minor; } gl_versions[] = {
- {4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0},
- {3, 3}, {3, 2}, {3, 1}, {3, 0},
- {0, 0}
+ // Check for OpenGL compatibility for creating egl context
+ static const struct { int major, minor; } gl_versions[] = {
+ {4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0},
+ {3, 3}, {3, 2}, {3, 1}, {3, 0},
+ {0, 0}
+ };
+ egl_context = NULL;
+ for (uint i = 0; gl_versions[i].major > 0; i++) {
+ const EGLint ctx_attrib[] = {
+ EGL_CONTEXT_MAJOR_VERSION, gl_versions[i].major,
+ EGL_CONTEXT_MINOR_VERSION, gl_versions[i].major,
+ EGL_NONE
};
- egl_context = NULL;
- for (uint i = 0; gl_versions[i].major > 0; i++) {
- const EGLint ctx_attrib[] = {
- EGL_CONTEXT_MAJOR_VERSION, gl_versions[i].major,
- EGL_CONTEXT_MINOR_VERSION, gl_versions[i].major,
- EGL_NONE
- };
- egl_context = eglCreateContext(egl_display, config, EGL_NO_CONTEXT, ctx_attrib);
- if (egl_context) {
- if (VERBOSE) {
- cflp_info("OpenGL %i.%i EGL context created", gl_versions[i].major, gl_versions[i].minor);
- }
- break;
+ egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, ctx_attrib);
+ if (egl_context) {
+ if (VERBOSE) {
+ cflp_info("OpenGL %i.%i EGL context created", gl_versions[i].major, gl_versions[i].minor);
}
+ break;
}
- if (!egl_context) {
- cflp_error("Failed to create EGL context");
- exit_mpvpaper(1);
- }
+ }
+ if (!egl_context) {
+ cflp_error("Failed to create EGL context");
+ exit_mpvpaper(1);
}
- egl_surface = eglCreatePlatformWindowSurface(egl_display, config, egl_window, NULL);
- eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
- eglSwapInterval(egl_display, 0);
+ if (!eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context)) {
+ cflp_error("Failed to make context current");
+ exit_mpvpaper(1);
+ }
if(!gladLoadGLLoader((GLADloadproc) eglGetProcAddress)) {
cflp_error("Failed to load OpenGL");
exit_mpvpaper(1);
}
-
- glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
- glViewport(0, 0, output->width * output->scale, output->height * output->scale);
}
static void destroy_display_output(struct display_output *output) {
@@ -625,9 +642,11 @@ static void destroy_display_output(struct display_output *output) {
if (output->surface != NULL) {
wl_surface_destroy(output->surface);
}
- if (egl_display && strcmp(output->name,output->state->monitor) == 0) {
- eglDestroySurface(egl_display, egl_surface);
- wl_egl_window_destroy(egl_window);
+ if (output->egl_surface) {
+ eglDestroySurface(egl_display, output->egl_surface);
+ }
+ if (output->egl_window) {
+ wl_egl_window_destroy(output->egl_window);
}
zxdg_output_v1_destroy(output->xdg_output);
wl_output_destroy(output->wl_output);
@@ -643,17 +662,30 @@ static void layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *su
output->width = width;
output->height = height;
zwlr_layer_surface_v1_ack_configure(surface, serial);
+ wl_surface_set_buffer_scale(output->surface, output->scale);
// Setup render loop
- init_egl(output);
+ struct wl_state *state = output->state;
+ if (!egl_display) {
+ init_egl(state);
+ }
if (!mpv) {
init_mpv(output);
init_threads();
}
- if (egl_display && mpv_glcontext) {
+ if (!output->egl_window) {
+ output->egl_window = wl_egl_window_create(output->surface, output->width * output->scale, output->height * output->scale);
+ output->egl_surface = eglCreatePlatformWindowSurface(egl_display, egl_config, output->egl_window, NULL);
+ eglMakeCurrent(egl_display, output->egl_surface, output->egl_surface, egl_context);
+ eglSwapInterval(egl_display, 0);
+
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+
// Start render loop
render(output);
+ } else {
+ wl_egl_window_resize(output->egl_window, output->width * output->scale, output->height * output->scale, 0, 0);
}
}
@@ -728,12 +760,14 @@ static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output
struct display_output *output = data;
- if (strcmp(output->name, output->state->monitor) == 0 && !output->layer_surface) {
+ bool name_ok = (strcmp(output->name, output->state->monitor) == 0) ||
+ (strcmp(output->state->monitor, "*") == 0);
+ if (name_ok && !output->layer_surface) {
if (VERBOSE)
cflp_info("Output %s (%s) selected", output->name, output->identifier);
create_layer_surface(output);
}
- else {
+ if (!name_ok) {
if (VERBOSE)
cflp_warning("Output %s (%s) not selected", output->name, output->identifier);
destroy_display_output(output);
@@ -999,6 +1033,13 @@ int main(int argc, char **argv) {
}
}
+ if (pipe(wakeup_pipe) == -1) {
+ cflp_error("Creating a self-pipe failed.");
+ return EXIT_FAILURE;
+ }
+ fcntl(wakeup_pipe[0], F_SETFD, FD_CLOEXEC);
+ fcntl(wakeup_pipe[1], F_SETFD, FD_CLOEXEC);
+
state.display = wl_display_connect(NULL);
if (!state.display) {
cflp_error("Unable to connect to the compositor. "
@@ -1032,8 +1073,39 @@ int main(int argc, char **argv) {
}
state.run_display = 1;
- while (wl_display_dispatch(state.display) != -1) {
- // NOP
+ while (true) {
+ if (wl_display_flush(state.display) == -1 && errno != EAGAIN)
+ break;
+
+ struct pollfd fds[2];
+ fds[0].fd = wl_display_get_fd(state.display);
+ fds[0].events = POLLIN;
+ fds[1].fd = wakeup_pipe[0];
+ fds[1].events = POLLIN;
+
+ if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) == -1 && errno != EINTR)
+ break;
+
+ if (fds[0].revents & POLLIN) {
+ if (wl_display_dispatch(state.display) == -1)
+ break;
+ }
+
+ if (fds[1].revents & POLLIN) {
+ // Empty the pipe
+ char tmp[64];
+ read(wakeup_pipe[0], tmp, sizeof(tmp));
+
+ mpv_render_context_update(mpv_glcontext);
+
+ wl_list_for_each(output, &state.outputs, link) {
+ // Redraw immediately if not waiting for frame callback
+ if (output->frame_callback == NULL)
+ render(output);
+ else
+ output->redraw_needed = true;
+ }
+ }
}
struct display_output *tmp_output;