diff options
Diffstat (limited to 'src/capture/portal.c')
-rw-r--r-- | src/capture/portal.c | 202 |
1 files changed, 93 insertions, 109 deletions
diff --git a/src/capture/portal.c b/src/capture/portal.c index cfbfbcd..d2217d1 100644 --- a/src/capture/portal.c +++ b/src/capture/portal.c @@ -8,9 +8,17 @@ #include <stdlib.h> #include <stdio.h> #include <unistd.h> +#include <limits.h> #include <assert.h> -#include <libavcodec/avcodec.h> +#define PORTAL_CAPTURE_CANCELED_BY_USER_EXIT_CODE 60 + +typedef enum { + PORTAL_CAPTURE_SETUP_IDLE, + PORTAL_CAPTURE_SETUP_IN_PROGRESS, + PORTAL_CAPTURE_SETUP_FINISHED, + PORTAL_CAPTURE_SETUP_FAILED +} gsr_portal_capture_setup_state; typedef struct { gsr_capture_portal_params params; @@ -25,8 +33,14 @@ typedef struct { gsr_pipewire_video_dmabuf_data dmabuf_data[GSR_PIPEWIRE_VIDEO_DMABUF_MAX_PLANES]; int num_dmabuf_data; - bool fast_path_failed; - bool mesa_supports_compute_only_vaapi_copy; + gsr_pipewire_video_region region; + gsr_pipewire_video_region cursor_region; + uint32_t pipewire_fourcc; + uint64_t pipewire_modifiers; + bool using_external_image; + + bool should_stop; + bool stop_is_error; } gsr_capture_portal; static void gsr_capture_portal_cleanup_plane_fds(gsr_capture_portal *self) { @@ -56,38 +70,25 @@ static void gsr_capture_portal_stop(gsr_capture_portal *self) { } gsr_capture_portal_cleanup_plane_fds(self); - gsr_pipewire_video_deinit(&self->pipewire); - - if(self->session_handle) { - free(self->session_handle); - self->session_handle = NULL; - } - gsr_dbus_deinit(&self->dbus); } static void gsr_capture_portal_create_input_textures(gsr_capture_portal *self) { self->params.egl->glGenTextures(1, &self->texture_map.texture_id); self->params.egl->glBindTexture(GL_TEXTURE_2D, self->texture_map.texture_id); - self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); self->params.egl->glBindTexture(GL_TEXTURE_2D, 0); self->params.egl->glGenTextures(1, &self->texture_map.external_texture_id); self->params.egl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, self->texture_map.external_texture_id); - self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); self->params.egl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0); self->params.egl->glGenTextures(1, &self->texture_map.cursor_texture_id); self->params.egl->glBindTexture(GL_TEXTURE_2D, self->texture_map.cursor_texture_id); - self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); self->params.egl->glBindTexture(GL_TEXTURE_2D, 0); @@ -233,19 +234,13 @@ static int gsr_capture_portal_setup_dbus(gsr_capture_portal *self, int *pipewire } static bool gsr_capture_portal_get_frame_dimensions(gsr_capture_portal *self) { - gsr_pipewire_video_region region = {0, 0, 0, 0}; - gsr_pipewire_video_region cursor_region = {0, 0, 0, 0}; fprintf(stderr, "gsr info: gsr_capture_portal_start: waiting for pipewire negotiation\n"); const double start_time = clock_get_monotonic_seconds(); while(clock_get_monotonic_seconds() - start_time < 5.0) { - bool uses_external_image = false; - uint32_t fourcc = 0; - uint64_t modifiers = 0; - if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, ®ion, &cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &fourcc, &modifiers, &uses_external_image)) { - gsr_capture_portal_cleanup_plane_fds(self); - self->capture_size.x = region.width; - self->capture_size.y = region.height; + if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, &self->region, &self->cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &self->pipewire_fourcc, &self->pipewire_modifiers, &self->using_external_image)) { + self->capture_size.x = self->region.width; + self->capture_size.y = self->region.height; fprintf(stderr, "gsr info: gsr_capture_portal_start: pipewire negotiation finished\n"); return true; } @@ -256,63 +251,62 @@ static bool gsr_capture_portal_get_frame_dimensions(gsr_capture_portal *self) { return false; } -static int gsr_capture_portal_start(gsr_capture *cap, gsr_capture_metadata *capture_metadata) { - gsr_capture_portal *self = cap->priv; - +static int gsr_capture_portal_setup(gsr_capture_portal *self, int fps) { gsr_capture_portal_create_input_textures(self); int pipewire_fd = 0; uint32_t pipewire_node = 0; const int response_status = gsr_capture_portal_setup_dbus(self, &pipewire_fd, &pipewire_node); if(response_status != 0) { - gsr_capture_portal_stop(self); // Response status values: // 0: Success, the request is carried out // 1: The user cancelled the interaction // 2: The user interaction was ended in some other way // Response status value 2 happens usually if there was some kind of error in the desktop portal on the system if(response_status == 2) { - fprintf(stderr, "gsr error: gsr_capture_portal_start: desktop portal capture failed. Either you Wayland compositor doesn't support desktop portal capture or it's incorrectly setup on your system\n"); + fprintf(stderr, "gsr error: gsr_capture_portal_setup: desktop portal capture failed. Either you Wayland compositor doesn't support desktop portal capture or it's incorrectly setup on your system\n"); return 50; } else if(response_status == 1) { - fprintf(stderr, "gsr error: gsr_capture_portal_start: desktop portal capture failed. It seems like desktop portal capture was canceled by the user.\n"); - return 60; + fprintf(stderr, "gsr error: gsr_capture_portal_setup: desktop portal capture failed. It seems like desktop portal capture was canceled by the user.\n"); + return PORTAL_CAPTURE_CANCELED_BY_USER_EXIT_CODE; } else { return -1; } } - fprintf(stderr, "gsr info: gsr_capture_portal_start: setting up pipewire\n"); + fprintf(stderr, "gsr info: gsr_capture_portal_setup: setting up pipewire\n"); /* TODO: support hdr when pipewire supports it */ /* gsr_pipewire closes the pipewire fd, even on failure */ - if(!gsr_pipewire_video_init(&self->pipewire, pipewire_fd, pipewire_node, capture_metadata->fps, self->params.record_cursor, self->params.egl)) { - fprintf(stderr, "gsr error: gsr_capture_portal_start: failed to setup pipewire with fd: %d, node: %" PRIu32 "\n", pipewire_fd, pipewire_node); - gsr_capture_portal_stop(self); + if(!gsr_pipewire_video_init(&self->pipewire, pipewire_fd, pipewire_node, fps, self->params.record_cursor, self->params.egl)) { + fprintf(stderr, "gsr error: gsr_capture_portal_setup: failed to setup pipewire with fd: %d, node: %" PRIu32 "\n", pipewire_fd, pipewire_node); return -1; } - fprintf(stderr, "gsr info: gsr_capture_portal_start: pipewire setup finished\n"); + fprintf(stderr, "gsr info: gsr_capture_portal_setup: pipewire setup finished\n"); - if(!gsr_capture_portal_get_frame_dimensions(self)) { - gsr_capture_portal_stop(self); + if(!gsr_capture_portal_get_frame_dimensions(self)) return -1; + + return 0; +} + +static int gsr_capture_portal_start(gsr_capture *cap, gsr_capture_metadata *capture_metadata) { + gsr_capture_portal *self = cap->priv; + + const int result = gsr_capture_portal_setup(self, capture_metadata->fps); + if(result != 0) { + gsr_capture_portal_stop(self); + return result; } if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) { - self->params.output_resolution = self->capture_size; - capture_metadata->width = FFALIGN(self->capture_size.x, 2); - capture_metadata->height = FFALIGN(self->capture_size.y, 2); + capture_metadata->width = self->capture_size.x; + capture_metadata->height = self->capture_size.y; } else { self->params.output_resolution = scale_keep_aspect_ratio(self->capture_size, self->params.output_resolution); - capture_metadata->width = FFALIGN(self->params.output_resolution.x, 2); - capture_metadata->height = FFALIGN(self->params.output_resolution.y, 2); + capture_metadata->width = self->params.output_resolution.x; + capture_metadata->height = self->params.output_resolution.y; } - self->fast_path_failed = self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && !gl_driver_version_greater_than(&self->params.egl->gpu_info, 24, 0, 9); - if(self->fast_path_failed) - fprintf(stderr, "gsr warning: gsr_capture_kms_start: your amd driver (mesa) version is known to be buggy (<= version 24.0.9), falling back to opengl copy\n"); - - self->mesa_supports_compute_only_vaapi_copy = self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && gl_driver_version_greater_than(&self->params.egl->gpu_info, 24, 3, 6); - return 0; } @@ -320,37 +314,41 @@ static int max_int(int a, int b) { return a > b ? a : b; } -static void gsr_capture_portal_fail_fast_path_if_not_fast(gsr_capture_portal *self, uint32_t pixel_format) { - const uint8_t pixel_format_color_depth_1 = (pixel_format >> 16) & 0xFF; - if(!self->fast_path_failed && self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && !self->mesa_supports_compute_only_vaapi_copy && (pixel_format_color_depth_1 == '3' || pixel_format_color_depth_1 == '4')) { - self->fast_path_failed = true; - fprintf(stderr, "gsr warning: gsr_capture_kms_capture: the monitor you are recording is in 10/12-bit color format and your mesa version is <= 24.3.6, composition will be used." - " If you experience performance problems in the video then record on a single window on X11 instead or disable 10/12-bit color option in your desktop environment settings," - " or try to record the monitor on X11 instead (if you aren't already doing that) or update your mesa version.\n"); - } +static bool gsr_capture_portal_capture_has_synchronous_task(gsr_capture *cap) { + gsr_capture_portal *self = cap->priv; + return gsr_pipewire_video_should_restart(&self->pipewire); } static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion) { (void)color_conversion; gsr_capture_portal *self = cap->priv; - /* TODO: Handle formats other than RGB(a) */ - gsr_pipewire_video_region region = {0, 0, 0, 0}; - gsr_pipewire_video_region cursor_region = {0, 0, 0, 0}; - uint32_t pipewire_fourcc = 0; - uint64_t pipewire_modifiers = 0; - bool using_external_image = false; - if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, ®ion, &cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &pipewire_fourcc, &pipewire_modifiers, &using_external_image)) { - if(region.width != self->capture_size.x || region.height != self->capture_size.y) { - self->capture_size.x = region.width; - self->capture_size.y = region.height; - gsr_color_conversion_clear(color_conversion); + if(self->should_stop) + return -1; + + if(gsr_pipewire_video_should_restart(&self->pipewire)) { + fprintf(stderr, "gsr info: gsr_capture_portal_capture: pipewire capture was paused, trying to start capture again\n"); + gsr_capture_portal_stop(self); + const int result = gsr_capture_portal_setup(self, capture_metadata->fps); + if(result != 0) { + self->stop_is_error = result != PORTAL_CAPTURE_CANCELED_BY_USER_EXIT_CODE; + self->should_stop = true; } - } else { - return 0; + return -1; } - gsr_capture_portal_fail_fast_path_if_not_fast(self, pipewire_fourcc); + /* TODO: Handle formats other than RGB(A) */ + if(self->num_dmabuf_data == 0) { + if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, &self->region, &self->cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &self->pipewire_fourcc, &self->pipewire_modifiers, &self->using_external_image)) { + if(self->region.width != self->capture_size.x || self->region.height != self->capture_size.y) { + self->capture_size.x = self->region.width; + self->capture_size.y = self->region.height; + gsr_color_conversion_clear(color_conversion); + } + } else { + return -1; + } + } const bool is_scaled = self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0; vec2i output_size = is_scaled ? self->params.output_resolution : self->capture_size; @@ -358,60 +356,38 @@ static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *ca const vec2i target_pos = { max_int(0, capture_metadata->width / 2 - output_size.x / 2), max_int(0, capture_metadata->height / 2 - output_size.y / 2) }; - self->params.egl->glFlush(); - self->params.egl->glFinish(); + //self->params.egl->glFlush(); + //self->params.egl->glFinish(); // TODO: Handle region crop - /* Fast opengl free path */ - if(!self->fast_path_failed && video_codec_context_is_vaapi(capture_metadata->video_codec_context) && self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD) { - int fds[4]; - uint32_t offsets[4]; - uint32_t pitches[4]; - uint64_t modifiers[4]; - for(int i = 0; i < self->num_dmabuf_data; ++i) { - fds[i] = self->dmabuf_data[i].fd; - offsets[i] = self->dmabuf_data[i].offset; - pitches[i] = self->dmabuf_data[i].stride; - modifiers[i] = pipewire_modifiers; - } - if(!vaapi_copy_drm_planes_to_video_surface(capture_metadata->video_codec_context, capture_metadata->frame, (vec2i){region.x, region.y}, self->capture_size, target_pos, output_size, pipewire_fourcc, self->capture_size, fds, offsets, pitches, modifiers, self->num_dmabuf_data)) { - fprintf(stderr, "gsr error: gsr_capture_portal_capture: vaapi_copy_drm_planes_to_video_surface failed, falling back to opengl copy. Please report this as an issue at https://github.com/dec05eba/gpu-screen-recorder-issues\n"); - self->fast_path_failed = true; - } - } else { - self->fast_path_failed = true; - } - - if(self->fast_path_failed) { - gsr_color_conversion_draw(color_conversion, using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id, - target_pos, output_size, - (vec2i){region.x, region.y}, self->capture_size, - 0.0f, using_external_image, GSR_SOURCE_COLOR_RGB); - } + gsr_color_conversion_draw(color_conversion, self->using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id, + target_pos, output_size, + (vec2i){self->region.x, self->region.y}, self->capture_size, self->capture_size, + GSR_ROT_0, GSR_SOURCE_COLOR_RGB, self->using_external_image, false); - if(self->params.record_cursor && self->texture_map.cursor_texture_id > 0 && cursor_region.width > 0) { + if(self->params.record_cursor && self->texture_map.cursor_texture_id > 0 && self->cursor_region.width > 0) { const vec2d scale = { self->capture_size.x == 0 ? 0 : (double)output_size.x / (double)self->capture_size.x, self->capture_size.y == 0 ? 0 : (double)output_size.y / (double)self->capture_size.y }; const vec2i cursor_pos = { - target_pos.x + (cursor_region.x * scale.x), - target_pos.y + (cursor_region.y * scale.y) + target_pos.x + (self->cursor_region.x * scale.x), + target_pos.y + (self->cursor_region.y * scale.y) }; self->params.egl->glEnable(GL_SCISSOR_TEST); self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y); gsr_color_conversion_draw(color_conversion, self->texture_map.cursor_texture_id, - (vec2i){cursor_pos.x, cursor_pos.y}, (vec2i){cursor_region.width * scale.x, cursor_region.height * scale.y}, - (vec2i){0, 0}, (vec2i){cursor_region.width, cursor_region.height}, - 0.0f, false, GSR_SOURCE_COLOR_RGB); + (vec2i){cursor_pos.x, cursor_pos.y}, (vec2i){self->cursor_region.width * scale.x, self->cursor_region.height * scale.y}, + (vec2i){0, 0}, (vec2i){self->cursor_region.width, self->cursor_region.height}, (vec2i){self->cursor_region.width, self->cursor_region.height}, + GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, true); self->params.egl->glDisable(GL_SCISSOR_TEST); } - self->params.egl->glFlush(); - self->params.egl->glFinish(); + //self->params.egl->glFlush(); + //self->params.egl->glFinish(); gsr_capture_portal_cleanup_plane_fds(self); @@ -423,6 +399,13 @@ static bool gsr_capture_portal_uses_external_image(gsr_capture *cap) { return true; } +static bool gsr_capture_portal_should_stop(gsr_capture *cap, bool *err) { + gsr_capture_portal *self = cap->priv; + if(err) + *err = self->stop_is_error; + return self->should_stop; +} + static bool gsr_capture_portal_is_damaged(gsr_capture *cap) { gsr_capture_portal *self = cap->priv; return gsr_pipewire_video_is_damaged(&self->pipewire); @@ -464,7 +447,8 @@ gsr_capture* gsr_capture_portal_create(const gsr_capture_portal_params *params) *cap = (gsr_capture) { .start = gsr_capture_portal_start, .tick = NULL, - .should_stop = NULL, + .should_stop = gsr_capture_portal_should_stop, + .capture_has_synchronous_task = gsr_capture_portal_capture_has_synchronous_task, .capture = gsr_capture_portal_capture, .uses_external_image = gsr_capture_portal_uses_external_image, .is_damaged = gsr_capture_portal_is_damaged, |