diff options
author | GhostNaN <59353890+GhostNaN@users.noreply.github.com> | 2022-09-16 01:42:45 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-16 01:42:45 +0000 |
commit | 11d42ca12b1f8c649f45482f0ba4a597ffc7c6ee (patch) | |
tree | 3d105495857ecd84eec6a4ba0c4f62d91ca0e544 | |
parent | f65700a3ecc9ecd8ca501e18a807ee18845f9441 (diff) | |
parent | 781320f5c0b19ed37a0d274720a045a1cd60e32f (diff) |
Merge pull request #26 from mstoeckl/multi-output
Allow rendering on multiple outputs with <output> = *
-rw-r--r-- | mpvpaper.man | 5 | ||||
-rw-r--r-- | src/holder.c | 6 | ||||
-rw-r--r-- | src/main.c | 190 |
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); } } @@ -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; |