diff options
Diffstat (limited to 'src/capture')
-rw-r--r-- | src/capture/capture.c | 14 | ||||
-rw-r--r-- | src/capture/kms.c | 215 | ||||
-rw-r--r-- | src/capture/nvfbc.c | 242 | ||||
-rw-r--r-- | src/capture/portal.c | 139 | ||||
-rw-r--r-- | src/capture/xcomposite.c | 70 |
5 files changed, 429 insertions, 251 deletions
diff --git a/src/capture/capture.c b/src/capture/capture.c index 5fc96d0..2a4a689 100644 --- a/src/capture/capture.c +++ b/src/capture/capture.c @@ -10,10 +10,10 @@ int gsr_capture_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVF return res; } -void gsr_capture_tick(gsr_capture *cap, AVCodecContext *video_codec_context) { +void gsr_capture_tick(gsr_capture *cap) { assert(cap->started); if(cap->tick) - cap->tick(cap, video_codec_context); + cap->tick(cap); } void gsr_capture_on_event(gsr_capture *cap, gsr_egl *egl) { @@ -34,16 +34,6 @@ int gsr_capture_capture(gsr_capture *cap, AVFrame *frame, gsr_color_conversion * return cap->capture(cap, frame, color_conversion); } -void gsr_capture_capture_end(gsr_capture *cap, AVFrame *frame) { - assert(cap->started); - if(cap->capture_end) - cap->capture_end(cap, frame); -} - -gsr_source_color gsr_capture_get_source_color(gsr_capture *cap) { - return cap->get_source_color(cap); -} - bool gsr_capture_uses_external_image(gsr_capture *cap) { if(cap->uses_external_image) return cap->uses_external_image(cap); diff --git a/src/capture/kms.c b/src/capture/kms.c index 9287a8b..c85811e 100644 --- a/src/capture/kms.c +++ b/src/capture/kms.c @@ -8,7 +8,9 @@ #include <string.h> #include <stdio.h> #include <unistd.h> +#include <fcntl.h> +#include <xf86drm.h> #include <libdrm/drm_fourcc.h> #include <libavcodec/avcodec.h> @@ -49,6 +51,17 @@ typedef struct { bool is_x11; gsr_cursor x11_cursor; + + AVCodecContext *video_codec_context; + bool performance_error_shown; + bool fast_path_failed; + + //int drm_fd; + //uint64_t prev_sequence; + //bool damaged; + + vec2i prev_target_pos; + vec2i prev_plane_size; } gsr_capture_kms; static void gsr_capture_kms_cleanup_kms_fds(gsr_capture_kms *self) { @@ -81,6 +94,11 @@ static void gsr_capture_kms_stop(gsr_capture_kms *self) { self->cursor_texture_id = 0; } + // if(self->drm_fd > 0) { + // close(self->drm_fd); + // self->drm_fd = -1; + // } + gsr_capture_kms_cleanup_kms_fds(self); gsr_kms_client_deinit(&self->kms_client); gsr_cursor_deinit(&self->x11_cursor); @@ -144,6 +162,15 @@ static void monitor_callback(const gsr_monitor *monitor, void *userdata) { fprintf(stderr, "gsr warning: reached max connector ids\n"); } +static vec2i rotate_capture_size_if_rotated(gsr_capture_kms *self, vec2i capture_size) { + if(self->monitor_rotation == GSR_MONITOR_ROT_90 || self->monitor_rotation == GSR_MONITOR_ROT_270) { + int tmp_x = capture_size.x; + capture_size.x = capture_size.y; + capture_size.y = tmp_x; + } + return capture_size; +} + static int gsr_capture_kms_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) { gsr_capture_kms *self = cap->priv; @@ -179,21 +206,28 @@ static int gsr_capture_kms_start(gsr_capture *cap, AVCodecContext *video_codec_c self->capture_pos = monitor.pos; /* Monitor size is already rotated on x11 when the monitor is rotated, no need to apply it ourselves */ - if(!self->is_x11 && (self->monitor_rotation == GSR_MONITOR_ROT_90 || self->monitor_rotation == GSR_MONITOR_ROT_270)) { - self->capture_size.x = monitor.size.y; - self->capture_size.y = monitor.size.x; - } else { + if(self->is_x11) self->capture_size = monitor.size; - } + else + self->capture_size = rotate_capture_size_if_rotated(self, monitor.size); /* Disable vsync */ self->params.egl->eglSwapInterval(self->params.egl->egl_display, 0); - video_codec_context->width = FFALIGN(self->capture_size.x, 2); - video_codec_context->height = FFALIGN(self->capture_size.y, 2); + if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) { + self->params.output_resolution = self->capture_size; + video_codec_context->width = FFALIGN(self->capture_size.x, 2); + video_codec_context->height = FFALIGN(self->capture_size.y, 2); + } else { + self->params.output_resolution = scale_keep_aspect_ratio(self->capture_size, self->params.output_resolution); + video_codec_context->width = FFALIGN(self->params.output_resolution.x, 2); + video_codec_context->height = FFALIGN(self->params.output_resolution.y, 2); + } frame->width = video_codec_context->width; frame->height = video_codec_context->height; + + self->video_codec_context = video_codec_context; return 0; } @@ -206,6 +240,27 @@ static void gsr_capture_kms_on_event(gsr_capture *cap, gsr_egl *egl) { gsr_cursor_on_event(&self->x11_cursor, xev); } +// TODO: This is disabled for now because we want to be able to record at a framerate higher than the monitor framerate +// static void gsr_capture_kms_tick(gsr_capture *cap) { +// gsr_capture_kms *self = cap->priv; + +// if(self->drm_fd <= 0) +// self->drm_fd = open(self->params.egl->card_path, O_RDONLY); + +// if(self->drm_fd <= 0) +// return; + +// uint64_t sequence = 0; +// uint64_t ns = 0; +// if(drmCrtcGetSequence(self->drm_fd, 79, &sequence, &ns) != 0) +// return; + +// if(sequence != self->prev_sequence) { +// self->prev_sequence = sequence; +// self->damaged = true; +// } +// } + static float monitor_rotation_to_radians(gsr_monitor_rotation rot) { switch(rot) { case GSR_MONITOR_ROT_0: return 0.0f; @@ -381,7 +436,12 @@ static gsr_kms_response_item* find_cursor_drm_if_on_monitor(gsr_capture_kms *sel return cursor_drm_fd; } -static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color_conversion, const gsr_kms_response_item *cursor_drm_fd, int target_x, int target_y, float texture_rotation) { +static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color_conversion, const gsr_kms_response_item *cursor_drm_fd, vec2i target_pos, float texture_rotation, vec2i output_size) { + 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 bool cursor_texture_id_is_external = self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA; const vec2i cursor_size = {cursor_drm_fd->width, cursor_drm_fd->height}; @@ -410,8 +470,11 @@ static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color break; } - cursor_pos.x += target_x; - cursor_pos.y += target_y; + cursor_pos.x *= scale.x; + cursor_pos.y *= scale.y; + + cursor_pos.x += target_pos.x; + cursor_pos.y += target_pos.y; int fds[GSR_KMS_MAX_DMA_BUFS]; uint32_t offsets[GSR_KMS_MAX_DMA_BUFS]; @@ -439,35 +502,51 @@ static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color self->params.egl->eglDestroyImage(self->params.egl->egl_display, cursor_image); self->params.egl->glEnable(GL_SCISSOR_TEST); - self->params.egl->glScissor(target_x, target_y, self->capture_size.x, self->capture_size.y); + self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y); gsr_color_conversion_draw(color_conversion, self->cursor_texture_id, - cursor_pos, cursor_size, + cursor_pos, (vec2i){cursor_size.x * scale.x, cursor_size.y * scale.y}, (vec2i){0, 0}, cursor_size, - texture_rotation, cursor_texture_id_is_external); + texture_rotation, cursor_texture_id_is_external, GSR_SOURCE_COLOR_RGB); self->params.egl->glDisable(GL_SCISSOR_TEST); } -static void render_x11_cursor(gsr_capture_kms *self, gsr_color_conversion *color_conversion, vec2i capture_pos, int target_x, int target_y) { +static void render_x11_cursor(gsr_capture_kms *self, gsr_color_conversion *color_conversion, vec2i capture_pos, vec2i target_pos, vec2i output_size) { + if(!self->x11_cursor.visible) + return; + + 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 + }; + gsr_cursor_tick(&self->x11_cursor, DefaultRootWindow(self->params.egl->x11.dpy)); const vec2i cursor_pos = { - target_x + self->x11_cursor.position.x - self->x11_cursor.hotspot.x - capture_pos.x, - target_y + self->x11_cursor.position.y - self->x11_cursor.hotspot.y - capture_pos.y + target_pos.x + (self->x11_cursor.position.x - self->x11_cursor.hotspot.x - capture_pos.x) * scale.x, + target_pos.y + (self->x11_cursor.position.y - self->x11_cursor.hotspot.y - capture_pos.y) * scale.y }; self->params.egl->glEnable(GL_SCISSOR_TEST); - self->params.egl->glScissor(target_x, target_y, self->capture_size.x, self->capture_size.y); + self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y); gsr_color_conversion_draw(color_conversion, self->x11_cursor.texture_id, - cursor_pos, self->x11_cursor.size, + cursor_pos, (vec2i){self->x11_cursor.size.x * scale.x, self->x11_cursor.size.y * scale.y}, (vec2i){0, 0}, self->x11_cursor.size, - 0.0f, false); + 0.0f, false, GSR_SOURCE_COLOR_RGB); self->params.egl->glDisable(GL_SCISSOR_TEST); } +static void gsr_capture_kms_update_capture_size_change(gsr_capture_kms *self, gsr_color_conversion *color_conversion, vec2i target_pos, const gsr_kms_response_item *drm_fd) { + if(target_pos.x != self->prev_target_pos.x || target_pos.y != self->prev_target_pos.y || drm_fd->src_w != self->prev_plane_size.x || drm_fd->src_h != self->prev_plane_size.y) { + self->prev_target_pos = target_pos; + self->prev_plane_size = self->capture_size; + gsr_color_conversion_clear(color_conversion); + } +} + static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_conversion *color_conversion) { gsr_capture_kms *self = cap->priv; @@ -489,21 +568,29 @@ static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_c bool capture_is_combined_plane = false; const gsr_kms_response_item *drm_fd = find_monitor_drm(self, &capture_is_combined_plane); - if(!drm_fd) + if(!drm_fd) { + gsr_capture_kms_cleanup_kms_fds(self); return -1; + } if(drm_fd->has_hdr_metadata && self->params.hdr && hdr_metadata_is_supported_format(&drm_fd->hdr_metadata)) gsr_kms_set_hdr_metadata(self, drm_fd); - EGLImage image = gsr_capture_kms_create_egl_image_with_fallback(self, drm_fd); - if(image) { - gsr_capture_kms_bind_image_to_input_texture_with_fallback(self, image); - self->params.egl->eglDestroyImage(self->params.egl->egl_display, image); + if(!self->performance_error_shown && self->monitor_rotation != GSR_MONITOR_ROT_0 && video_codec_context_is_vaapi(self->video_codec_context) && self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD) { + self->performance_error_shown = true; + fprintf(stderr,"gsr warning: gsr_capture_kms_capture: the monitor you are recording is rotated, composition will have to be used." + " If you are experience performance problems in the video then record a single window on X11 or use portal capture option instead\n"); } + self->capture_size = rotate_capture_size_if_rotated(self, (vec2i){ drm_fd->src_w, drm_fd->src_h }); + + 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; + output_size = scale_keep_aspect_ratio(self->capture_size, output_size); + const float texture_rotation = monitor_rotation_to_radians(self->monitor_rotation); - const int target_x = max_int(0, frame->width / 2 - self->capture_size.x / 2); - const int target_y = max_int(0, frame->height / 2 - self->capture_size.y / 2); + const vec2i target_pos = { max_int(0, frame->width / 2 - output_size.x / 2), max_int(0, frame->height / 2 - output_size.y / 2) }; + gsr_capture_kms_update_capture_size_change(self, color_conversion, target_pos, drm_fd); vec2i capture_pos = self->capture_pos; if(!capture_is_combined_plane) @@ -512,24 +599,58 @@ static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_c self->params.egl->glFlush(); self->params.egl->glFinish(); - gsr_color_conversion_draw(color_conversion, self->external_texture_fallback ? self->external_input_texture_id : self->input_texture_id, - (vec2i){target_x, target_y}, self->capture_size, - capture_pos, self->capture_size, - texture_rotation, self->external_texture_fallback); + /* Fast opengl free path */ + if(!self->fast_path_failed && self->monitor_rotation == GSR_MONITOR_ROT_0 && video_codec_context_is_vaapi(self->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 < drm_fd->num_dma_bufs; ++i) { + fds[i] = drm_fd->dma_buf[i].fd; + offsets[i] = drm_fd->dma_buf[i].offset; + pitches[i] = drm_fd->dma_buf[i].pitch; + modifiers[i] = drm_fd->modifier; + } + if(!vaapi_copy_drm_planes_to_video_surface(self->video_codec_context, frame, (vec2i){capture_pos.x, capture_pos.y}, self->capture_size, target_pos, output_size, drm_fd->pixel_format, (vec2i){drm_fd->width, drm_fd->height}, fds, offsets, pitches, modifiers, drm_fd->num_dma_bufs)) { + fprintf(stderr, "gsr error: gsr_capture_kms_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) { + EGLImage image = gsr_capture_kms_create_egl_image_with_fallback(self, drm_fd); + if(image) { + gsr_capture_kms_bind_image_to_input_texture_with_fallback(self, image); + self->params.egl->eglDestroyImage(self->params.egl->egl_display, image); + } + + gsr_color_conversion_draw(color_conversion, self->external_texture_fallback ? self->external_input_texture_id : self->input_texture_id, + target_pos, output_size, + capture_pos, self->capture_size, + texture_rotation, self->external_texture_fallback, GSR_SOURCE_COLOR_RGB); + } if(self->params.record_cursor) { gsr_kms_response_item *cursor_drm_fd = find_cursor_drm_if_on_monitor(self, drm_fd->connector_id, capture_is_combined_plane); // The cursor is handled by x11 on x11 instead of using the cursor drm plane because on prime systems with a dedicated nvidia gpu // the cursor plane is not available when the cursor is on the monitor controlled by the nvidia device. - if(self->is_x11) - render_x11_cursor(self, color_conversion, capture_pos, target_x, target_y); - else if(cursor_drm_fd) - render_drm_cursor(self, color_conversion, cursor_drm_fd, target_x, target_y, texture_rotation); + // TODO: This doesn't work properly with software cursor on x11 since it will draw the x11 cursor on top of the cursor already in the framebuffer. + // Detect if software cursor is used on x11 somehow. + if(self->is_x11) { + const vec2i cursor_monitor_offset = self->capture_pos; + render_x11_cursor(self, color_conversion, cursor_monitor_offset, target_pos, output_size); + } else if(cursor_drm_fd) { + render_drm_cursor(self, color_conversion, cursor_drm_fd, target_pos, texture_rotation, output_size); + } } self->params.egl->glFlush(); self->params.egl->glFinish(); + gsr_capture_kms_cleanup_kms_fds(self); + return 0; } @@ -540,16 +661,6 @@ static bool gsr_capture_kms_should_stop(gsr_capture *cap, bool *err) { return false; } -static void gsr_capture_kms_capture_end(gsr_capture *cap, AVFrame *frame) { - (void)frame; - gsr_capture_kms_cleanup_kms_fds(cap->priv); -} - -static gsr_source_color gsr_capture_kms_get_source_color(gsr_capture *cap) { - (void)cap; - return GSR_SOURCE_COLOR_RGB; -} - static bool gsr_capture_kms_uses_external_image(gsr_capture *cap) { (void)cap; return true; @@ -581,6 +692,16 @@ static bool gsr_capture_kms_set_hdr_metadata(gsr_capture *cap, AVMasteringDispla return true; } +// static bool gsr_capture_kms_is_damaged(gsr_capture *cap) { +// gsr_capture_kms *self = cap->priv; +// return self->damaged; +// } + +// static void gsr_capture_kms_clear_damage(gsr_capture *cap) { +// gsr_capture_kms *self = cap->priv; +// self->damaged = false; +// } + static void gsr_capture_kms_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) { (void)video_codec_context; gsr_capture_kms *self = cap->priv; @@ -623,13 +744,13 @@ gsr_capture* gsr_capture_kms_create(const gsr_capture_kms_params *params) { *cap = (gsr_capture) { .start = gsr_capture_kms_start, .on_event = gsr_capture_kms_on_event, - .tick = NULL, + //.tick = gsr_capture_kms_tick, .should_stop = gsr_capture_kms_should_stop, .capture = gsr_capture_kms_capture, - .capture_end = gsr_capture_kms_capture_end, - .get_source_color = gsr_capture_kms_get_source_color, .uses_external_image = gsr_capture_kms_uses_external_image, .set_hdr_metadata = gsr_capture_kms_set_hdr_metadata, + //.is_damaged = gsr_capture_kms_is_damaged, + //.clear_damage = gsr_capture_kms_clear_damage, .destroy = gsr_capture_kms_destroy, .priv = cap_kms }; diff --git a/src/capture/nvfbc.c b/src/capture/nvfbc.c index b7e6bb5..d5a270e 100644 --- a/src/capture/nvfbc.c +++ b/src/capture/nvfbc.c @@ -102,7 +102,7 @@ static void set_func_ptr(void **dst, void *src) { } static bool gsr_capture_nvfbc_load_library(gsr_capture *cap) { - gsr_capture_nvfbc *cap_nvfbc = cap->priv; + gsr_capture_nvfbc *self = cap->priv; dlerror(); /* clear */ void *lib = dlopen("libnvidia-fbc.so.1", RTLD_LAZY); @@ -111,23 +111,23 @@ static bool gsr_capture_nvfbc_load_library(gsr_capture *cap) { return false; } - set_func_ptr((void**)&cap_nvfbc->nv_fbc_create_instance, dlsym(lib, "NvFBCCreateInstance")); - if(!cap_nvfbc->nv_fbc_create_instance) { + set_func_ptr((void**)&self->nv_fbc_create_instance, dlsym(lib, "NvFBCCreateInstance")); + if(!self->nv_fbc_create_instance) { fprintf(stderr, "gsr error: unable to resolve symbol 'NvFBCCreateInstance'\n"); dlclose(lib); return false; } - memset(&cap_nvfbc->nv_fbc_function_list, 0, sizeof(cap_nvfbc->nv_fbc_function_list)); - cap_nvfbc->nv_fbc_function_list.dwVersion = NVFBC_VERSION; - NVFBCSTATUS status = cap_nvfbc->nv_fbc_create_instance(&cap_nvfbc->nv_fbc_function_list); + memset(&self->nv_fbc_function_list, 0, sizeof(self->nv_fbc_function_list)); + self->nv_fbc_function_list.dwVersion = NVFBC_VERSION; + NVFBCSTATUS status = self->nv_fbc_create_instance(&self->nv_fbc_function_list); if(status != NVFBC_SUCCESS) { fprintf(stderr, "gsr error: failed to create NvFBC instance (status: %d)\n", status); dlclose(lib); return false; } - cap_nvfbc->library = lib; + self->library = lib; return true; } @@ -153,64 +153,64 @@ static void set_vertical_sync_enabled(gsr_egl *egl, int enabled) { fprintf(stderr, "gsr warning: setting vertical sync failed\n"); } -static void gsr_capture_nvfbc_destroy_session(gsr_capture_nvfbc *cap_nvfbc) { - if(cap_nvfbc->fbc_handle_created && cap_nvfbc->capture_session_created) { +static void gsr_capture_nvfbc_destroy_session(gsr_capture_nvfbc *self) { + if(self->fbc_handle_created && self->capture_session_created) { NVFBC_DESTROY_CAPTURE_SESSION_PARAMS destroy_capture_params; memset(&destroy_capture_params, 0, sizeof(destroy_capture_params)); destroy_capture_params.dwVersion = NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER; - cap_nvfbc->nv_fbc_function_list.nvFBCDestroyCaptureSession(cap_nvfbc->nv_fbc_handle, &destroy_capture_params); - cap_nvfbc->capture_session_created = false; + self->nv_fbc_function_list.nvFBCDestroyCaptureSession(self->nv_fbc_handle, &destroy_capture_params); + self->capture_session_created = false; } } -static void gsr_capture_nvfbc_destroy_handle(gsr_capture_nvfbc *cap_nvfbc) { - if(cap_nvfbc->fbc_handle_created) { +static void gsr_capture_nvfbc_destroy_handle(gsr_capture_nvfbc *self) { + if(self->fbc_handle_created) { NVFBC_DESTROY_HANDLE_PARAMS destroy_params; memset(&destroy_params, 0, sizeof(destroy_params)); destroy_params.dwVersion = NVFBC_DESTROY_HANDLE_PARAMS_VER; - cap_nvfbc->nv_fbc_function_list.nvFBCDestroyHandle(cap_nvfbc->nv_fbc_handle, &destroy_params); - cap_nvfbc->fbc_handle_created = false; - cap_nvfbc->nv_fbc_handle = 0; + self->nv_fbc_function_list.nvFBCDestroyHandle(self->nv_fbc_handle, &destroy_params); + self->fbc_handle_created = false; + self->nv_fbc_handle = 0; } } -static void gsr_capture_nvfbc_destroy_session_and_handle(gsr_capture_nvfbc *cap_nvfbc) { - gsr_capture_nvfbc_destroy_session(cap_nvfbc); - gsr_capture_nvfbc_destroy_handle(cap_nvfbc); +static void gsr_capture_nvfbc_destroy_session_and_handle(gsr_capture_nvfbc *self) { + gsr_capture_nvfbc_destroy_session(self); + gsr_capture_nvfbc_destroy_handle(self); } -static int gsr_capture_nvfbc_setup_handle(gsr_capture_nvfbc *cap_nvfbc) { +static int gsr_capture_nvfbc_setup_handle(gsr_capture_nvfbc *self) { NVFBCSTATUS status; NVFBC_CREATE_HANDLE_PARAMS create_params; memset(&create_params, 0, sizeof(create_params)); create_params.dwVersion = NVFBC_CREATE_HANDLE_PARAMS_VER; create_params.bExternallyManagedContext = NVFBC_TRUE; - create_params.glxCtx = cap_nvfbc->params.egl->glx_context; - create_params.glxFBConfig = cap_nvfbc->params.egl->glx_fb_config; + create_params.glxCtx = self->params.egl->glx_context; + create_params.glxFBConfig = self->params.egl->glx_fb_config; - status = cap_nvfbc->nv_fbc_function_list.nvFBCCreateHandle(&cap_nvfbc->nv_fbc_handle, &create_params); + status = self->nv_fbc_function_list.nvFBCCreateHandle(&self->nv_fbc_handle, &create_params); if(status != NVFBC_SUCCESS) { // Reverse engineering for interoperability const uint8_t enable_key[] = { 0xac, 0x10, 0xc9, 0x2e, 0xa5, 0xe6, 0x87, 0x4f, 0x8f, 0x4b, 0xf4, 0x61, 0xf8, 0x56, 0x27, 0xe9 }; create_params.privateData = enable_key; create_params.privateDataSize = 16; - status = cap_nvfbc->nv_fbc_function_list.nvFBCCreateHandle(&cap_nvfbc->nv_fbc_handle, &create_params); + status = self->nv_fbc_function_list.nvFBCCreateHandle(&self->nv_fbc_handle, &create_params); if(status != NVFBC_SUCCESS) { - fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle)); + fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", self->nv_fbc_function_list.nvFBCGetLastErrorStr(self->nv_fbc_handle)); goto error_cleanup; } } - cap_nvfbc->fbc_handle_created = true; + self->fbc_handle_created = true; NVFBC_GET_STATUS_PARAMS status_params; memset(&status_params, 0, sizeof(status_params)); status_params.dwVersion = NVFBC_GET_STATUS_PARAMS_VER; - status = cap_nvfbc->nv_fbc_function_list.nvFBCGetStatus(cap_nvfbc->nv_fbc_handle, &status_params); + status = self->nv_fbc_function_list.nvFBCGetStatus(self->nv_fbc_handle, &status_params); if(status != NVFBC_SUCCESS) { - fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle)); + fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", self->nv_fbc_function_list.nvFBCGetLastErrorStr(self->nv_fbc_handle)); goto error_cleanup; } @@ -219,10 +219,10 @@ static int gsr_capture_nvfbc_setup_handle(gsr_capture_nvfbc *cap_nvfbc) { goto error_cleanup; } - cap_nvfbc->tracking_width = XWidthOfScreen(DefaultScreenOfDisplay(cap_nvfbc->params.egl->x11.dpy)); - cap_nvfbc->tracking_height = XHeightOfScreen(DefaultScreenOfDisplay(cap_nvfbc->params.egl->x11.dpy)); - cap_nvfbc->tracking_type = strcmp(cap_nvfbc->params.display_to_capture, "screen") == 0 ? NVFBC_TRACKING_SCREEN : NVFBC_TRACKING_OUTPUT; - if(cap_nvfbc->tracking_type == NVFBC_TRACKING_OUTPUT) { + self->tracking_width = XWidthOfScreen(DefaultScreenOfDisplay(self->params.egl->x11.dpy)); + self->tracking_height = XHeightOfScreen(DefaultScreenOfDisplay(self->params.egl->x11.dpy)); + self->tracking_type = strcmp(self->params.display_to_capture, "screen") == 0 ? NVFBC_TRACKING_SCREEN : NVFBC_TRACKING_OUTPUT; + if(self->tracking_type == NVFBC_TRACKING_OUTPUT) { if(!status_params.bXRandRAvailable) { fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: the xrandr extension is not available\n"); goto error_cleanup; @@ -233,93 +233,98 @@ static int gsr_capture_nvfbc_setup_handle(gsr_capture_nvfbc *cap_nvfbc) { goto error_cleanup; } - cap_nvfbc->output_id = get_output_id_from_display_name(status_params.outputs, status_params.dwOutputNum, cap_nvfbc->params.display_to_capture, &cap_nvfbc->tracking_width, &cap_nvfbc->tracking_height); - if(cap_nvfbc->output_id == 0) { - fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: display '%s' not found\n", cap_nvfbc->params.display_to_capture); + self->output_id = get_output_id_from_display_name(status_params.outputs, status_params.dwOutputNum, self->params.display_to_capture, &self->tracking_width, &self->tracking_height); + if(self->output_id == 0) { + fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: display '%s' not found\n", self->params.display_to_capture); goto error_cleanup; } } + if(!self->capture_region) { + self->width = self->tracking_width; + self->height = self->tracking_height; + } + return 0; error_cleanup: - gsr_capture_nvfbc_destroy_session_and_handle(cap_nvfbc); + gsr_capture_nvfbc_destroy_session_and_handle(self); return -1; } -static int gsr_capture_nvfbc_setup_session(gsr_capture_nvfbc *cap_nvfbc) { +static int gsr_capture_nvfbc_setup_session(gsr_capture_nvfbc *self) { NVFBC_CREATE_CAPTURE_SESSION_PARAMS create_capture_params; memset(&create_capture_params, 0, sizeof(create_capture_params)); create_capture_params.dwVersion = NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER; create_capture_params.eCaptureType = NVFBC_CAPTURE_TO_GL; - create_capture_params.bWithCursor = (!cap_nvfbc->params.direct_capture || cap_nvfbc->supports_direct_cursor) ? NVFBC_TRUE : NVFBC_FALSE; - if(!cap_nvfbc->params.record_cursor) + create_capture_params.bWithCursor = (!self->params.direct_capture || self->supports_direct_cursor) ? NVFBC_TRUE : NVFBC_FALSE; + if(!self->params.record_cursor) create_capture_params.bWithCursor = false; - if(cap_nvfbc->capture_region) - create_capture_params.captureBox = (NVFBC_BOX){ cap_nvfbc->x, cap_nvfbc->y, cap_nvfbc->width, cap_nvfbc->height }; - create_capture_params.eTrackingType = cap_nvfbc->tracking_type; - create_capture_params.dwSamplingRateMs = (uint32_t)ceilf(1000.0f / (float)cap_nvfbc->params.fps); - create_capture_params.bAllowDirectCapture = cap_nvfbc->params.direct_capture ? NVFBC_TRUE : NVFBC_FALSE; - create_capture_params.bPushModel = cap_nvfbc->params.direct_capture ? NVFBC_TRUE : NVFBC_FALSE; + if(self->capture_region) + create_capture_params.captureBox = (NVFBC_BOX){ self->x, self->y, self->width, self->height }; + create_capture_params.eTrackingType = self->tracking_type; + create_capture_params.dwSamplingRateMs = (uint32_t)ceilf(1000.0f / (float)self->params.fps); + create_capture_params.bAllowDirectCapture = self->params.direct_capture ? NVFBC_TRUE : NVFBC_FALSE; + create_capture_params.bPushModel = self->params.direct_capture ? NVFBC_TRUE : NVFBC_FALSE; create_capture_params.bDisableAutoModesetRecovery = true; - if(cap_nvfbc->tracking_type == NVFBC_TRACKING_OUTPUT) - create_capture_params.dwOutputId = cap_nvfbc->output_id; + if(self->tracking_type == NVFBC_TRACKING_OUTPUT) + create_capture_params.dwOutputId = self->output_id; - NVFBCSTATUS status = cap_nvfbc->nv_fbc_function_list.nvFBCCreateCaptureSession(cap_nvfbc->nv_fbc_handle, &create_capture_params); + NVFBCSTATUS status = self->nv_fbc_function_list.nvFBCCreateCaptureSession(self->nv_fbc_handle, &create_capture_params); if(status != NVFBC_SUCCESS) { - fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle)); + fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", self->nv_fbc_function_list.nvFBCGetLastErrorStr(self->nv_fbc_handle)); return -1; } - cap_nvfbc->capture_session_created = true; + self->capture_session_created = true; - memset(&cap_nvfbc->setup_params, 0, sizeof(cap_nvfbc->setup_params)); - cap_nvfbc->setup_params.dwVersion = NVFBC_TOGL_SETUP_PARAMS_VER; - cap_nvfbc->setup_params.eBufferFormat = NVFBC_BUFFER_FORMAT_BGRA; + memset(&self->setup_params, 0, sizeof(self->setup_params)); + self->setup_params.dwVersion = NVFBC_TOGL_SETUP_PARAMS_VER; + self->setup_params.eBufferFormat = NVFBC_BUFFER_FORMAT_BGRA; - status = cap_nvfbc->nv_fbc_function_list.nvFBCToGLSetUp(cap_nvfbc->nv_fbc_handle, &cap_nvfbc->setup_params); + status = self->nv_fbc_function_list.nvFBCToGLSetUp(self->nv_fbc_handle, &self->setup_params); if(status != NVFBC_SUCCESS) { - fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle)); - gsr_capture_nvfbc_destroy_session(cap_nvfbc); + fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", self->nv_fbc_function_list.nvFBCGetLastErrorStr(self->nv_fbc_handle)); + gsr_capture_nvfbc_destroy_session(self); return -1; } return 0; } -static void gsr_capture_nvfbc_stop(gsr_capture_nvfbc *cap_nvfbc) { - gsr_capture_nvfbc_destroy_session_and_handle(cap_nvfbc); - if(cap_nvfbc->library) { - dlclose(cap_nvfbc->library); - cap_nvfbc->library = NULL; +static void gsr_capture_nvfbc_stop(gsr_capture_nvfbc *self) { + gsr_capture_nvfbc_destroy_session_and_handle(self); + if(self->library) { + dlclose(self->library); + self->library = NULL; } - if(cap_nvfbc->params.display_to_capture) { - free((void*)cap_nvfbc->params.display_to_capture); - cap_nvfbc->params.display_to_capture = NULL; + if(self->params.display_to_capture) { + free((void*)self->params.display_to_capture); + self->params.display_to_capture = NULL; } } static int gsr_capture_nvfbc_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) { - gsr_capture_nvfbc *cap_nvfbc = cap->priv; + gsr_capture_nvfbc *self = cap->priv; if(!gsr_capture_nvfbc_load_library(cap)) return -1; - cap_nvfbc->x = max_int(cap_nvfbc->params.pos.x, 0); - cap_nvfbc->y = max_int(cap_nvfbc->params.pos.y, 0); - cap_nvfbc->width = max_int(cap_nvfbc->params.size.x, 0); - cap_nvfbc->height = max_int(cap_nvfbc->params.size.y, 0); + self->x = max_int(self->params.pos.x, 0); + self->y = max_int(self->params.pos.y, 0); + self->width = max_int(self->params.size.x, 0); + self->height = max_int(self->params.size.y, 0); - cap_nvfbc->capture_region = (cap_nvfbc->x > 0 || cap_nvfbc->y > 0 || cap_nvfbc->width > 0 || cap_nvfbc->height > 0); + self->capture_region = (self->x > 0 || self->y > 0 || self->width > 0 || self->height > 0); - cap_nvfbc->supports_direct_cursor = false; + self->supports_direct_cursor = false; int driver_major_version = 0; int driver_minor_version = 0; - if(cap_nvfbc->params.direct_capture && get_driver_version(&driver_major_version, &driver_minor_version)) { + if(self->params.direct_capture && get_driver_version(&driver_major_version, &driver_minor_version)) { fprintf(stderr, "Info: detected nvidia version: %d.%d\n", driver_major_version, driver_minor_version); // TODO: if(version_at_least(driver_major_version, driver_minor_version, 515, 57) && version_less_than(driver_major_version, driver_minor_version, 520, 56)) { - cap_nvfbc->params.direct_capture = false; + self->params.direct_capture = false; fprintf(stderr, "Warning: \"screen-direct\" has temporary been disabled as it causes stuttering with driver versions >= 515.57 and < 520.56. Please update your driver if possible. Capturing \"screen\" instead.\n"); } @@ -328,68 +333,83 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap, AVCodecContext *video_codec /* if(direct_capture) { if(version_at_least(driver_major_version, driver_minor_version, 515, 57)) - cap_nvfbc->supports_direct_cursor = true; + self->supports_direct_cursor = true; else fprintf(stderr, "Info: capturing \"screen-direct\" but driver version appears to be less than 515.57. Disabling capture of cursor. Please update your driver if you want to capture your cursor or record \"screen\" instead.\n"); } */ } - if(gsr_capture_nvfbc_setup_handle(cap_nvfbc) != 0) { + if(gsr_capture_nvfbc_setup_handle(self) != 0) { goto error_cleanup; } - if(gsr_capture_nvfbc_setup_session(cap_nvfbc) != 0) { + if(gsr_capture_nvfbc_setup_session(self) != 0) { goto error_cleanup; } - if(cap_nvfbc->capture_region) { - video_codec_context->width = FFALIGN(cap_nvfbc->width, 2); - video_codec_context->height = FFALIGN(cap_nvfbc->height, 2); + if(self->capture_region) { + video_codec_context->width = FFALIGN(self->width, 2); + video_codec_context->height = FFALIGN(self->height, 2); + } else { + video_codec_context->width = FFALIGN(self->tracking_width, 2); + video_codec_context->height = FFALIGN(self->tracking_height, 2); + } + + if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) { + self->params.output_resolution = (vec2i){video_codec_context->width, video_codec_context->height}; } else { - video_codec_context->width = FFALIGN(cap_nvfbc->tracking_width, 2); - video_codec_context->height = FFALIGN(cap_nvfbc->tracking_height, 2); + self->params.output_resolution = scale_keep_aspect_ratio((vec2i){video_codec_context->width, video_codec_context->height}, self->params.output_resolution); + video_codec_context->width = FFALIGN(self->params.output_resolution.x, 2); + video_codec_context->height = FFALIGN(self->params.output_resolution.y, 2); } frame->width = video_codec_context->width; frame->height = video_codec_context->height; /* Disable vsync */ - set_vertical_sync_enabled(cap_nvfbc->params.egl, 0); + set_vertical_sync_enabled(self->params.egl, 0); return 0; error_cleanup: - gsr_capture_nvfbc_stop(cap_nvfbc); + gsr_capture_nvfbc_stop(self); return -1; } static int gsr_capture_nvfbc_capture(gsr_capture *cap, AVFrame *frame, gsr_color_conversion *color_conversion) { - gsr_capture_nvfbc *cap_nvfbc = cap->priv; + gsr_capture_nvfbc *self = cap->priv; const double nvfbc_recreate_retry_time_seconds = 1.0; - if(cap_nvfbc->nvfbc_needs_recreate) { + if(self->nvfbc_needs_recreate) { const double now = clock_get_monotonic_seconds(); - if(now - cap_nvfbc->nvfbc_dead_start >= nvfbc_recreate_retry_time_seconds) { - cap_nvfbc->nvfbc_dead_start = now; - gsr_capture_nvfbc_destroy_session_and_handle(cap_nvfbc); + if(now - self->nvfbc_dead_start >= nvfbc_recreate_retry_time_seconds) { + self->nvfbc_dead_start = now; + gsr_capture_nvfbc_destroy_session_and_handle(self); - if(gsr_capture_nvfbc_setup_handle(cap_nvfbc) != 0) { + if(gsr_capture_nvfbc_setup_handle(self) != 0) { fprintf(stderr, "gsr error: gsr_capture_nvfbc_capture failed to recreate nvfbc handle, trying again in %f second(s)\n", nvfbc_recreate_retry_time_seconds); return -1; } - - if(gsr_capture_nvfbc_setup_session(cap_nvfbc) != 0) { + + if(gsr_capture_nvfbc_setup_session(self) != 0) { fprintf(stderr, "gsr error: gsr_capture_nvfbc_capture failed to recreate nvfbc session, trying again in %f second(s)\n", nvfbc_recreate_retry_time_seconds); return -1; } - cap_nvfbc->nvfbc_needs_recreate = false; + self->nvfbc_needs_recreate = false; } else { return 0; } } + const vec2i frame_size = (vec2i){self->width, self->height}; + 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 : frame_size; + output_size = scale_keep_aspect_ratio(frame_size, output_size); + + const vec2i target_pos = { max_int(0, frame->width / 2 - output_size.x / 2), max_int(0, frame->height / 2 - output_size.y / 2) }; + NVFBC_FRAME_GRAB_INFO frame_info; memset(&frame_info, 0, sizeof(frame_info)); @@ -400,37 +420,33 @@ static int gsr_capture_nvfbc_capture(gsr_capture *cap, AVFrame *frame, gsr_color grab_params.pFrameGrabInfo = &frame_info; grab_params.dwTimeoutMs = 0; - NVFBCSTATUS status = cap_nvfbc->nv_fbc_function_list.nvFBCToGLGrabFrame(cap_nvfbc->nv_fbc_handle, &grab_params); + NVFBCSTATUS status = self->nv_fbc_function_list.nvFBCToGLGrabFrame(self->nv_fbc_handle, &grab_params); if(status != NVFBC_SUCCESS) { - fprintf(stderr, "gsr error: gsr_capture_nvfbc_capture failed: %s (%d), recreating session after %f second(s)\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle), status, nvfbc_recreate_retry_time_seconds); - cap_nvfbc->nvfbc_needs_recreate = true; - cap_nvfbc->nvfbc_dead_start = clock_get_monotonic_seconds(); + fprintf(stderr, "gsr error: gsr_capture_nvfbc_capture failed: %s (%d), recreating session after %f second(s)\n", self->nv_fbc_function_list.nvFBCGetLastErrorStr(self->nv_fbc_handle), status, nvfbc_recreate_retry_time_seconds); + self->nvfbc_needs_recreate = true; + self->nvfbc_dead_start = clock_get_monotonic_seconds(); return 0; } - cap_nvfbc->params.egl->glFlush(); - cap_nvfbc->params.egl->glFinish(); + self->params.egl->glFlush(); + self->params.egl->glFinish(); - gsr_color_conversion_draw(color_conversion, cap_nvfbc->setup_params.dwTextures[grab_params.dwTextureIndex], - (vec2i){0, 0}, (vec2i){frame->width, frame->height}, - (vec2i){0, 0}, (vec2i){frame->width, frame->height}, - 0.0f, false); + gsr_color_conversion_draw(color_conversion, self->setup_params.dwTextures[grab_params.dwTextureIndex], + target_pos, (vec2i){output_size.x, output_size.y}, + (vec2i){0, 0}, frame_size, + 0.0f, false, GSR_SOURCE_COLOR_BGR); - cap_nvfbc->params.egl->glFlush(); - cap_nvfbc->params.egl->glFinish(); + self->params.egl->glFlush(); + self->params.egl->glFinish(); return 0; } -static gsr_source_color gsr_capture_nvfbc_get_source_color(gsr_capture *cap) { - (void)cap; - return GSR_SOURCE_COLOR_BGR; -} - static void gsr_capture_nvfbc_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) { (void)video_codec_context; - gsr_capture_nvfbc *cap_nvfbc = cap->priv; - gsr_capture_nvfbc_stop(cap_nvfbc); + gsr_capture_nvfbc *self = cap->priv; + gsr_capture_nvfbc_stop(self); + free(cap->priv); free(cap); } @@ -465,14 +481,12 @@ gsr_capture* gsr_capture_nvfbc_create(const gsr_capture_nvfbc_params *params) { cap_nvfbc->params = *params; cap_nvfbc->params.display_to_capture = display_to_capture; cap_nvfbc->params.fps = max_int(cap_nvfbc->params.fps, 1); - + *cap = (gsr_capture) { .start = gsr_capture_nvfbc_start, .tick = NULL, .should_stop = NULL, .capture = gsr_capture_nvfbc_capture, - .capture_end = NULL, - .get_source_color = gsr_capture_nvfbc_get_source_color, .uses_external_image = NULL, .destroy = gsr_capture_nvfbc_destroy, .priv = cap_nvfbc diff --git a/src/capture/portal.c b/src/capture/portal.c index 95470c1..d68e86f 100644 --- a/src/capture/portal.c +++ b/src/capture/portal.c @@ -3,7 +3,7 @@ #include "../../include/egl.h" #include "../../include/utils.h" #include "../../include/dbus.h" -#include "../../include/pipewire.h" +#include "../../include/pipewire_video.h" #include <stdlib.h> #include <stdio.h> @@ -20,20 +20,23 @@ typedef struct { gsr_dbus dbus; char *session_handle; - gsr_pipewire pipewire; + gsr_pipewire_video pipewire; vec2i capture_size; - int plane_fds[GSR_PIPEWIRE_DMABUF_MAX_PLANES]; - int num_plane_fds; + gsr_pipewire_video_dmabuf_data dmabuf_data[GSR_PIPEWIRE_VIDEO_DMABUF_MAX_PLANES]; + int num_dmabuf_data; + + AVCodecContext *video_codec_context; + bool fast_path_failed; } gsr_capture_portal; static void gsr_capture_portal_cleanup_plane_fds(gsr_capture_portal *self) { - for(int i = 0; i < self->num_plane_fds; ++i) { - if(self->plane_fds[i] > 0) { - close(self->plane_fds[i]); - self->plane_fds[i] = 0; + for(int i = 0; i < self->num_dmabuf_data; ++i) { + if(self->dmabuf_data[i].fd > 0) { + close(self->dmabuf_data[i].fd); + self->dmabuf_data[i].fd = 0; } } - self->num_plane_fds = 0; + self->num_dmabuf_data = 0; } static void gsr_capture_portal_stop(gsr_capture_portal *self) { @@ -54,7 +57,7 @@ static void gsr_capture_portal_stop(gsr_capture_portal *self) { gsr_capture_portal_cleanup_plane_fds(self); - gsr_pipewire_deinit(&self->pipewire); + gsr_pipewire_video_deinit(&self->pipewire); if(self->session_handle) { free(self->session_handle); @@ -230,14 +233,16 @@ 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_region region = {0, 0, 0, 0}; - gsr_pipewire_region cursor_region = {0, 0, 0, 0}; + 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; - if(gsr_pipewire_map_texture(&self->pipewire, self->texture_map, ®ion, &cursor_region, self->plane_fds, &self->num_plane_fds, &uses_external_image)) { + 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; @@ -280,7 +285,7 @@ static int gsr_capture_portal_start(gsr_capture *cap, AVCodecContext *video_code fprintf(stderr, "gsr info: gsr_capture_portal_start: setting up pipewire\n"); /* TODO: support hdr when pipewire supports it */ /* gsr_pipewire closes the pipewire fd, even on failure */ - if(!gsr_pipewire_init(&self->pipewire, pipewire_fd, pipewire_node, video_codec_context->framerate.num, self->params.record_cursor, self->params.egl)) { + if(!gsr_pipewire_video_init(&self->pipewire, pipewire_fd, pipewire_node, video_codec_context->framerate.num, 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); return -1; @@ -295,11 +300,20 @@ static int gsr_capture_portal_start(gsr_capture *cap, AVCodecContext *video_code /* Disable vsync */ self->params.egl->eglSwapInterval(self->params.egl->egl_display, 0); - video_codec_context->width = FFALIGN(self->capture_size.x, 2); - video_codec_context->height = FFALIGN(self->capture_size.y, 2); + if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) { + self->params.output_resolution = self->capture_size; + video_codec_context->width = FFALIGN(self->capture_size.x, 2); + video_codec_context->height = FFALIGN(self->capture_size.y, 2); + } else { + self->params.output_resolution = scale_keep_aspect_ratio(self->capture_size, self->params.output_resolution); + video_codec_context->width = FFALIGN(self->params.output_resolution.x, 2); + video_codec_context->height = FFALIGN(self->params.output_resolution.y, 2); + } frame->width = video_codec_context->width; frame->height = video_codec_context->height; + + self->video_codec_context = video_codec_context; return 0; } @@ -312,61 +326,86 @@ static int gsr_capture_portal_capture(gsr_capture *cap, AVFrame *frame, gsr_colo (void)color_conversion; gsr_capture_portal *self = cap->priv; - gsr_capture_portal_cleanup_plane_fds(self); - /* TODO: Handle formats other than RGB(a) */ - gsr_pipewire_region region = {0, 0, 0, 0}; - gsr_pipewire_region cursor_region = {0, 0, 0, 0}; + 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_map_texture(&self->pipewire, self->texture_map, ®ion, &cursor_region, self->plane_fds, &self->num_plane_fds, &using_external_image)) { + 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) { - gsr_color_conversion_clear(color_conversion); self->capture_size.x = region.width; self->capture_size.y = region.height; + gsr_color_conversion_clear(color_conversion); } + } else { + return 0; } + 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; + output_size = scale_keep_aspect_ratio(self->capture_size, output_size); + + const vec2i target_pos = { max_int(0, frame->width / 2 - output_size.x / 2), max_int(0, frame->height / 2 - output_size.y / 2) }; + self->params.egl->glFlush(); self->params.egl->glFinish(); - - const int target_x = max_int(0, frame->width / 2 - self->capture_size.x / 2); - const int target_y = max_int(0, frame->height / 2 - self->capture_size.y / 2); - gsr_color_conversion_draw(color_conversion, using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id, - (vec2i){target_x, target_y}, self->capture_size, - (vec2i){region.x, region.y}, self->capture_size, - 0.0f, using_external_image); + // TODO: Handle region crop + + /* Fast opengl free path */ + if(!self->fast_path_failed && video_codec_context_is_vaapi(self->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(self->video_codec_context, 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); + } + + if(self->params.record_cursor && self->texture_map.cursor_texture_id > 0 && 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 + }; - if(self->params.record_cursor) { const vec2i cursor_pos = { - target_x + cursor_region.x, - target_y + cursor_region.y + target_pos.x + (cursor_region.x * scale.x), + target_pos.y + (cursor_region.y * scale.y) }; self->params.egl->glEnable(GL_SCISSOR_TEST); - self->params.egl->glScissor(target_x, target_y, self->capture_size.x, self->capture_size.y); + 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, cursor_region.height}, + (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); + 0.0f, false, GSR_SOURCE_COLOR_RGB); self->params.egl->glDisable(GL_SCISSOR_TEST); } self->params.egl->glFlush(); self->params.egl->glFinish(); - return 0; -} - -static void gsr_capture_portal_capture_end(gsr_capture *cap, AVFrame *frame) { - (void)frame; - gsr_capture_portal *self = cap->priv; gsr_capture_portal_cleanup_plane_fds(self); -} -static gsr_source_color gsr_capture_portal_get_source_color(gsr_capture *cap) { - (void)cap; - return GSR_SOURCE_COLOR_RGB; + return 0; } static bool gsr_capture_portal_uses_external_image(gsr_capture *cap) { @@ -376,19 +415,19 @@ static bool gsr_capture_portal_uses_external_image(gsr_capture *cap) { static bool gsr_capture_portal_is_damaged(gsr_capture *cap) { gsr_capture_portal *self = cap->priv; - return gsr_pipewire_is_damaged(&self->pipewire); + return gsr_pipewire_video_is_damaged(&self->pipewire); } static void gsr_capture_portal_clear_damage(gsr_capture *cap) { gsr_capture_portal *self = cap->priv; - gsr_pipewire_clear_damage(&self->pipewire); + gsr_pipewire_video_clear_damage(&self->pipewire); } static void gsr_capture_portal_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) { (void)video_codec_context; - gsr_capture_portal *cap_portal = cap->priv; + gsr_capture_portal *self = cap->priv; if(cap->priv) { - gsr_capture_portal_stop(cap_portal); + gsr_capture_portal_stop(self); free(cap->priv); cap->priv = NULL; } @@ -418,8 +457,6 @@ gsr_capture* gsr_capture_portal_create(const gsr_capture_portal_params *params) .tick = NULL, .should_stop = NULL, .capture = gsr_capture_portal_capture, - .capture_end = gsr_capture_portal_capture_end, - .get_source_color = gsr_capture_portal_get_source_color, .uses_external_image = gsr_capture_portal_uses_external_image, .is_damaged = gsr_capture_portal_is_damaged, .clear_damage = gsr_capture_portal_clear_damage, diff --git a/src/capture/xcomposite.c b/src/capture/xcomposite.c index 1b6021b..2867b45 100644 --- a/src/capture/xcomposite.c +++ b/src/capture/xcomposite.c @@ -29,12 +29,14 @@ typedef struct { double window_resize_timer; WindowTexture window_texture; + AVCodecContext *video_codec_context; Atom net_active_window_atom; gsr_cursor cursor; bool clear_background; + bool fast_path_failed; } gsr_capture_xcomposite; static void gsr_capture_xcomposite_stop(gsr_capture_xcomposite *self) { @@ -111,23 +113,24 @@ static int gsr_capture_xcomposite_start(gsr_capture *cap, AVCodecContext *video_ self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &self->texture_size.y); self->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - vec2i video_size = self->texture_size; - - if(self->params.region_size.x > 0 && self->params.region_size.y > 0) - video_size = self->params.region_size; - - video_codec_context->width = FFALIGN(video_size.x, 2); - video_codec_context->height = FFALIGN(video_size.y, 2); + if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) { + self->params.output_resolution = self->texture_size; + video_codec_context->width = FFALIGN(self->texture_size.x, 2); + video_codec_context->height = FFALIGN(self->texture_size.y, 2); + } else { + video_codec_context->width = FFALIGN(self->params.output_resolution.x, 2); + video_codec_context->height = FFALIGN(self->params.output_resolution.y, 2); + } frame->width = video_codec_context->width; frame->height = video_codec_context->height; + self->video_codec_context = video_codec_context; self->window_resize_timer = clock_get_monotonic_seconds(); return 0; } -static void gsr_capture_xcomposite_tick(gsr_capture *cap, AVCodecContext *video_codec_context) { - (void)video_codec_context; +static void gsr_capture_xcomposite_tick(gsr_capture *cap) { gsr_capture_xcomposite *self = cap->priv; if(self->params.follow_focused && !self->follow_focused_initialized) { @@ -255,32 +258,52 @@ static int gsr_capture_xcomposite_capture(gsr_capture *cap, AVFrame *frame, gsr_ gsr_color_conversion_clear(color_conversion); } - const int target_x = max_int(0, frame->width / 2 - self->texture_size.x / 2); - const int target_y = max_int(0, frame->height / 2 - self->texture_size.y / 2); + 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->texture_size; + output_size = scale_keep_aspect_ratio(self->texture_size, output_size); + + const vec2i target_pos = { max_int(0, frame->width / 2 - output_size.x / 2), max_int(0, frame->height / 2 - output_size.y / 2) }; self->params.egl->glFlush(); self->params.egl->glFinish(); - gsr_color_conversion_draw(color_conversion, window_texture_get_opengl_texture_id(&self->window_texture), - (vec2i){target_x, target_y}, self->texture_size, - (vec2i){0, 0}, self->texture_size, - 0.0f, false); + /* Fast opengl free path */ + if(!self->fast_path_failed && video_codec_context_is_vaapi(self->video_codec_context) && self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD) { + if(!vaapi_copy_egl_image_to_video_surface(self->params.egl, self->window_texture.image, (vec2i){0, 0}, self->texture_size, target_pos, output_size, self->video_codec_context, frame)) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_capture: vaapi_copy_egl_image_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, window_texture_get_opengl_texture_id(&self->window_texture), + target_pos, output_size, + (vec2i){0, 0}, self->texture_size, + 0.0f, false, GSR_SOURCE_COLOR_RGB); + } if(self->params.record_cursor && self->cursor.visible) { + const vec2d scale = { + self->texture_size.x == 0 ? 0 : (double)output_size.x / (double)self->texture_size.x, + self->texture_size.y == 0 ? 0 : (double)output_size.y / (double)self->texture_size.y + }; + gsr_cursor_tick(&self->cursor, self->window); const vec2i cursor_pos = { - target_x + self->cursor.position.x - self->cursor.hotspot.x, - target_y + self->cursor.position.y - self->cursor.hotspot.y + target_pos.x + (self->cursor.position.x - self->cursor.hotspot.x) * scale.x, + target_pos.y + (self->cursor.position.y - self->cursor.hotspot.y) * scale.y }; self->params.egl->glEnable(GL_SCISSOR_TEST); - self->params.egl->glScissor(target_x, target_y, self->texture_size.x, self->texture_size.y); + self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y); gsr_color_conversion_draw(color_conversion, self->cursor.texture_id, - cursor_pos, self->cursor.size, + cursor_pos, (vec2i){self->cursor.size.x * scale.x, self->cursor.size.y * scale.y}, (vec2i){0, 0}, self->cursor.size, - 0.0f, false); + 0.0f, false, GSR_SOURCE_COLOR_RGB); self->params.egl->glDisable(GL_SCISSOR_TEST); } @@ -291,11 +314,6 @@ static int gsr_capture_xcomposite_capture(gsr_capture *cap, AVFrame *frame, gsr_ return 0; } -static gsr_source_color gsr_capture_xcomposite_get_source_color(gsr_capture *cap) { - (void)cap; - return GSR_SOURCE_COLOR_RGB; -} - static uint64_t gsr_capture_xcomposite_get_window_id(gsr_capture *cap) { gsr_capture_xcomposite *self = cap->priv; return self->window; @@ -335,8 +353,6 @@ gsr_capture* gsr_capture_xcomposite_create(const gsr_capture_xcomposite_params * .tick = gsr_capture_xcomposite_tick, .should_stop = gsr_capture_xcomposite_should_stop, .capture = gsr_capture_xcomposite_capture, - .capture_end = NULL, - .get_source_color = gsr_capture_xcomposite_get_source_color, .uses_external_image = NULL, .get_window_id = gsr_capture_xcomposite_get_window_id, .destroy = gsr_capture_xcomposite_destroy, |