From 048b8d21ecbd1168ff8e033b12cbfd66bba0127c Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 15 Jul 2024 18:57:33 +0200 Subject: Add support for desktop portal capture (-w portal) --- src/capture/kms.c | 83 +++++----- src/capture/portal.c | 384 +++++++++++++++++++++++++++++++++++++++++++++++ src/capture/xcomposite.c | 26 ++-- 3 files changed, 434 insertions(+), 59 deletions(-) create mode 100644 src/capture/portal.c (limited to 'src/capture') diff --git a/src/capture/kms.c b/src/capture/kms.c index a9ce73c..e7b0b59 100644 --- a/src/capture/kms.c +++ b/src/capture/kms.c @@ -3,6 +3,7 @@ #include "../../include/color_conversion.h" #include "../../kms/client/kms_client.h" +#include #include #include #include @@ -38,8 +39,8 @@ typedef struct { gsr_monitor_rotation monitor_rotation; - unsigned int input_texture; - unsigned int cursor_texture; + unsigned int input_texture_id; + unsigned int cursor_texture_id; } gsr_capture_kms; static void gsr_capture_kms_cleanup_kms_fds(gsr_capture_kms *self) { @@ -52,14 +53,14 @@ static void gsr_capture_kms_cleanup_kms_fds(gsr_capture_kms *self) { } static void gsr_capture_kms_stop(gsr_capture_kms *self) { - if(self->input_texture) { - self->params.egl->glDeleteTextures(1, &self->input_texture); - self->input_texture = 0; + if(self->input_texture_id) { + self->params.egl->glDeleteTextures(1, &self->input_texture_id); + self->input_texture_id = 0; } - if(self->cursor_texture) { - self->params.egl->glDeleteTextures(1, &self->cursor_texture); - self->cursor_texture = 0; + if(self->cursor_texture_id) { + self->params.egl->glDeleteTextures(1, &self->cursor_texture_id); + self->cursor_texture_id = 0; } gsr_capture_kms_cleanup_kms_fds(self); @@ -70,25 +71,25 @@ static int max_int(int a, int b) { return a > b ? a : b; } -static void gsr_capture_kms_create_input_textures(gsr_capture_kms *self) { - self->params.egl->glGenTextures(1, &self->input_texture); - self->params.egl->glBindTexture(GL_TEXTURE_2D, self->input_texture); +static void gsr_capture_kms_create_input_texture_ids(gsr_capture_kms *self) { + self->params.egl->glGenTextures(1, &self->input_texture_id); + self->params.egl->glBindTexture(GL_TEXTURE_2D, self->input_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); - const bool cursor_texture_is_external = self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA; - const int cursor_texture_target = cursor_texture_is_external ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; + const bool cursor_texture_id_is_external = self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA; + const int cursor_texture_id_target = cursor_texture_id_is_external ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; - self->params.egl->glGenTextures(1, &self->cursor_texture); - self->params.egl->glBindTexture(cursor_texture_target, self->cursor_texture); - self->params.egl->glTexParameteri(cursor_texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - self->params.egl->glTexParameteri(cursor_texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - self->params.egl->glTexParameteri(cursor_texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - self->params.egl->glTexParameteri(cursor_texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - self->params.egl->glBindTexture(cursor_texture_target, 0); + self->params.egl->glGenTextures(1, &self->cursor_texture_id); + self->params.egl->glBindTexture(cursor_texture_id_target, self->cursor_texture_id); + self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + self->params.egl->glBindTexture(cursor_texture_id_target, 0); } /* TODO: On monitor reconfiguration, find monitor x, y, width and height again. Do the same for nvfbc. */ @@ -119,7 +120,7 @@ static void monitor_callback(const gsr_monitor *monitor, void *userdata) { static int gsr_capture_kms_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) { gsr_capture_kms *self = cap->priv; - gsr_capture_kms_create_input_textures(self); + gsr_capture_kms_create_input_texture_ids(self); gsr_monitor monitor; self->monitor_id.num_connector_ids = 0; @@ -268,7 +269,7 @@ static vec2i swap_vec2i(vec2i value) { static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_conversion *color_conversion) { gsr_capture_kms *self = cap->priv; const bool screen_plane_use_modifiers = self->params.egl->gpu_info.vendor != GSR_GPU_VENDOR_AMD; - const bool cursor_texture_is_external = self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA; + const bool cursor_texture_id_is_external = self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA; //egl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); self->params.egl->glClear(0); @@ -337,12 +338,12 @@ static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_c // Assertion pic->display_order == pic->encode_order failed at libavcodec/vaapi_encode_h265.c:765 // kms server info: kms client shutdown, shutting down the server intptr_t img_attr[18] = { - EGL_LINUX_DRM_FOURCC_EXT, drm_fd->pixel_format, - EGL_WIDTH, drm_fd->width, - EGL_HEIGHT, drm_fd->height, - EGL_DMA_BUF_PLANE0_FD_EXT, drm_fd->fd, - EGL_DMA_BUF_PLANE0_OFFSET_EXT, drm_fd->offset, - EGL_DMA_BUF_PLANE0_PITCH_EXT, drm_fd->pitch, + EGL_LINUX_DRM_FOURCC_EXT, drm_fd->pixel_format, + EGL_WIDTH, drm_fd->width, + EGL_HEIGHT, drm_fd->height, + EGL_DMA_BUF_PLANE0_FD_EXT, drm_fd->fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, drm_fd->offset, + EGL_DMA_BUF_PLANE0_PITCH_EXT, drm_fd->pitch, }; if(screen_plane_use_modifiers) { @@ -360,7 +361,7 @@ static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_c } EGLImage image = self->params.egl->eglCreateImage(self->params.egl->egl_display, 0, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr); - self->params.egl->glBindTexture(GL_TEXTURE_2D, self->input_texture); + self->params.egl->glBindTexture(GL_TEXTURE_2D, self->input_texture_id); self->params.egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); self->params.egl->eglDestroyImage(self->params.egl->egl_display, image); self->params.egl->glBindTexture(GL_TEXTURE_2D, 0); @@ -374,7 +375,7 @@ static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_c 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, self->input_texture, + gsr_color_conversion_draw(color_conversion, self->input_texture_id, (vec2i){target_x, target_y}, self->capture_size, capture_pos, self->capture_size, texture_rotation, false); @@ -410,20 +411,20 @@ static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_c cursor_pos.y += target_y; const intptr_t img_attr_cursor[] = { - EGL_LINUX_DRM_FOURCC_EXT, cursor_drm_fd->pixel_format, - EGL_WIDTH, cursor_drm_fd->width, - EGL_HEIGHT, cursor_drm_fd->height, - EGL_DMA_BUF_PLANE0_FD_EXT, cursor_drm_fd->fd, - EGL_DMA_BUF_PLANE0_OFFSET_EXT, cursor_drm_fd->offset, - EGL_DMA_BUF_PLANE0_PITCH_EXT, cursor_drm_fd->pitch, + EGL_LINUX_DRM_FOURCC_EXT, cursor_drm_fd->pixel_format, + EGL_WIDTH, cursor_drm_fd->width, + EGL_HEIGHT, cursor_drm_fd->height, + EGL_DMA_BUF_PLANE0_FD_EXT, cursor_drm_fd->fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, cursor_drm_fd->offset, + EGL_DMA_BUF_PLANE0_PITCH_EXT, cursor_drm_fd->pitch, EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, cursor_drm_fd->modifier & 0xFFFFFFFFULL, EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, cursor_drm_fd->modifier >> 32ULL, EGL_NONE }; EGLImage cursor_image = self->params.egl->eglCreateImage(self->params.egl->egl_display, 0, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr_cursor); - const int target = cursor_texture_is_external ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; - self->params.egl->glBindTexture(target, self->cursor_texture); + const int target = cursor_texture_id_is_external ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; + self->params.egl->glBindTexture(target, self->cursor_texture_id); self->params.egl->glEGLImageTargetTexture2DOES(target, cursor_image); self->params.egl->eglDestroyImage(self->params.egl->egl_display, cursor_image); self->params.egl->glBindTexture(target, 0); @@ -431,17 +432,15 @@ static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_c self->params.egl->glEnable(GL_SCISSOR_TEST); self->params.egl->glScissor(target_x, target_y, self->capture_size.x, self->capture_size.y); - gsr_color_conversion_draw(color_conversion, self->cursor_texture, + gsr_color_conversion_draw(color_conversion, self->cursor_texture_id, cursor_pos, cursor_size, (vec2i){0, 0}, cursor_size, - texture_rotation, cursor_texture_is_external); + texture_rotation, cursor_texture_id_is_external); self->params.egl->glDisable(GL_SCISSOR_TEST); } self->params.egl->eglSwapBuffers(self->params.egl->egl_display, self->params.egl->egl_surface); - - // TODO: Do software specific video encoder conversion here //self->params.egl->glFlush(); //self->params.egl->glFinish(); diff --git a/src/capture/portal.c b/src/capture/portal.c new file mode 100644 index 0000000..77da206 --- /dev/null +++ b/src/capture/portal.c @@ -0,0 +1,384 @@ +#include "../../include/capture/portal.h" +#include "../../include/color_conversion.h" +#include "../../include/egl.h" +#include "../../include/utils.h" +#include "../../include/dbus.h" +#include "../../include/pipewire.h" + +#include +#include +#include +#include + +#include + +typedef struct { + gsr_capture_portal_params params; + + bool should_stop; + bool stop_is_error; + + unsigned int input_texture_id; + unsigned int cursor_texture_id; + + gsr_dbus dbus; + char *session_handle; + + uint32_t pipewire_node; + int pipewire_fd; + gsr_pipewire pipewire; + vec2i capture_size; +} gsr_capture_portal; + +static void gsr_capture_portal_stop(gsr_capture_portal *self) { + if(self->input_texture_id) { + self->params.egl->glDeleteTextures(1, &self->input_texture_id); + self->input_texture_id = 0; + } + + if(self->cursor_texture_id) { + self->params.egl->glDeleteTextures(1, &self->cursor_texture_id); + self->cursor_texture_id = 0; + } + + if(self->pipewire_fd > 0) { + close(self->pipewire_fd); + self->pipewire_fd = -1; + } + + gsr_pipewire_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->input_texture_id); + self->params.egl->glBindTexture(GL_TEXTURE_2D, self->input_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->cursor_texture_id); + self->params.egl->glBindTexture(GL_TEXTURE_2D, self->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); +} + +static void get_gpu_screen_recorder_config_directory_path(char *buffer, size_t buffer_size) { + const char *xdg_config_home = getenv("XDG_CONFIG_HOME"); + if(xdg_config_home) { + snprintf(buffer, buffer_size, "%s/gpu-screen-recorder", xdg_config_home); + } else { + const char *home = getenv("HOME"); + if(!home) + home = "/tmp"; + snprintf(buffer, buffer_size, "%s/.config/gpu-screen-recorder", home); + } +} + +static void get_gpu_screen_recorder_restore_token_path(char *buffer, size_t buffer_size) { + const char *xdg_config_home = getenv("XDG_CONFIG_HOME"); + if(xdg_config_home) { + snprintf(buffer, buffer_size, "%s/gpu-screen-recorder/restore_token", xdg_config_home); + } else { + const char *home = getenv("HOME"); + if(!home) + home = "/tmp"; + snprintf(buffer, buffer_size, "%s/.config/gpu-screen-recorder/restore_token", home); + } +} + +static void gsr_capture_portal_save_restore_token(const char *restore_token) { + char config_path[PATH_MAX]; + config_path[0] = '\0'; + get_gpu_screen_recorder_config_directory_path(config_path, sizeof(config_path)); + + if(create_directory_recursive(config_path) != 0) { + fprintf(stderr, "gsr warning: gsr_capture_portal_save_restore_token: failed to create directory (%s) for restore token\n", config_path); + return; + } + + char restore_token_path[PATH_MAX]; + restore_token_path[0] = '\0'; + get_gpu_screen_recorder_restore_token_path(restore_token_path, sizeof(restore_token_path)); + + FILE *f = fopen(restore_token_path, "wb"); + if(!f) { + fprintf(stderr, "gsr warning: gsr_capture_portal_save_restore_token: failed to create restore token file (%s)\n", restore_token_path); + return; + } + + const int restore_token_len = strlen(restore_token); + if((long)fwrite(restore_token, 1, restore_token_len, f) != restore_token_len) { + fprintf(stderr, "gsr warning: gsr_capture_portal_save_restore_token: failed to write restore token to file (%s)\n", restore_token_path); + fclose(f); + return; + } + + fprintf(stderr, "gsr info: gsr_capture_portal_save_restore_token: saved restore token to cache (%s)\n", restore_token); + fclose(f); +} + +static void gsr_capture_portal_get_restore_token_from_cache(char *buffer, size_t buffer_size) { + assert(buffer_size > 0); + buffer[0] = '\0'; + + char restore_token_path[PATH_MAX]; + restore_token_path[0] = '\0'; + get_gpu_screen_recorder_restore_token_path(restore_token_path, sizeof(restore_token_path)); + + FILE *f = fopen(restore_token_path, "rb"); + if(!f) { + fprintf(stderr, "gsr info: gsr_capture_portal_get_restore_token_from_cache: no restore token found in cache or failed to load (%s)\n", restore_token_path); + return; + } + + fseek(f, 0, SEEK_END); + long file_size = ftell(f); + fseek(f, 0, SEEK_SET); + + fprintf(stderr, "file size: %ld\n", file_size); + + if(file_size > 0 && file_size < 1024 && file_size < (long)buffer_size && (long)fread(buffer, 1, file_size, f) != file_size) { + buffer[0] = '\0'; + fprintf(stderr, "gsr warning: gsr_capture_portal_get_restore_token_from_cache: failed to read restore token (%s)\n", restore_token_path); + fclose(f); + return; + } + + if(file_size > 0 && file_size < (long)buffer_size) + buffer[file_size] = '\0'; + + fprintf(stderr, "gsr info: gsr_capture_portal_get_restore_token_from_cache: read cached restore token (%s)\n", buffer); + fclose(f); +} + +static bool gsr_capture_portal_setup_dbus(gsr_capture_portal *self) { + char restore_token[1024]; + restore_token[0] = '\0'; + if(self->params.restore_portal_session) + gsr_capture_portal_get_restore_token_from_cache(restore_token, sizeof(restore_token)); + + if(!gsr_dbus_init(&self->dbus, restore_token)) + return false; + + fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: CreateSession\n"); + if(!gsr_dbus_screencast_create_session(&self->dbus, &self->session_handle)) { + fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: CreateSession failed\n"); + return false; + } + + fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: SelectSources\n"); + if(!gsr_dbus_screencast_select_sources(&self->dbus, self->session_handle, GSR_PORTAL_CAPTURE_TYPE_MONITOR | GSR_PORTAL_CAPTURE_TYPE_WINDOW, self->params.record_cursor ? GSR_PORTAL_CURSOR_MODE_EMBEDDED : GSR_PORTAL_CURSOR_MODE_HIDDEN)) { + fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: SelectSources failed\n"); + return false; + } + + fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: Start\n"); + if(!gsr_dbus_screencast_start(&self->dbus, self->session_handle, &self->pipewire_node)) { + fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: Start failed\n"); + return false; + } + + const char *screencast_restore_token = gsr_dbus_screencast_get_restore_token(&self->dbus); + if(screencast_restore_token) + gsr_capture_portal_save_restore_token(screencast_restore_token); + + fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: OpenPipeWireRemote\n"); + if(!gsr_dbus_screencast_open_pipewire_remote(&self->dbus, self->session_handle, &self->pipewire_fd)) { + fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: OpenPipeWireRemote failed\n"); + return false; + } + + fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: desktop portal setup finished\n"); + return true; +} + +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}; + 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) { + if(gsr_pipewire_map_texture(&self->pipewire, self->input_texture_id, self->cursor_texture_id, ®ion, &cursor_region)) { + self->capture_size.x = region.width; + self->capture_size.y = region.height; + fprintf(stderr, "gsr info: gsr_capture_portal_start: pipewire negotiation finished\n"); + return true; + } + usleep(30 * 1000); /* 30 milliseconds */ + } + + fprintf(stderr, "gsr info: gsr_capture_portal_start: timed out waiting for pipewire negotiation (5 seconds)\n"); + return false; +} + +static int gsr_capture_portal_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) { + gsr_capture_portal *self = cap->priv; + + gsr_capture_portal_create_input_textures(self); + + if(!gsr_capture_portal_setup_dbus(self)) { + gsr_capture_portal_stop(self); + return -1; + } + + fprintf(stderr, "gsr info: gsr_capture_portal_start: setting up pipewire\n"); + /* TODO: support hdr when pipewire supports it */ + if(!gsr_pipewire_init(&self->pipewire, self->pipewire_fd, self->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", self->pipewire_fd, self->pipewire_node); + gsr_capture_portal_stop(self); + return -1; + } + self->pipewire_fd = -1; + fprintf(stderr, "gsr info: gsr_capture_portal_start: pipewire setup finished\n"); + + if(!gsr_capture_portal_get_frame_dimensions(self)) { + gsr_capture_portal_stop(self); + return -1; + } + + /* 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); + + frame->width = video_codec_context->width; + frame->height = video_codec_context->height; + return 0; +} + +static int max_int(int a, int b) { + return a > b ? a : b; +} + +static int gsr_capture_portal_capture(gsr_capture *cap, AVFrame *frame, gsr_color_conversion *color_conversion) { + (void)frame; + (void)color_conversion; + gsr_capture_portal *self = cap->priv; + + //egl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + self->params.egl->glClear(0); + + vec2i content_size = self->capture_size; + + /* TODO: Handle formats other than RGB(a) */ + gsr_pipewire_region region = {0, 0, 0, 0}; + gsr_pipewire_region cursor_region = {0, 0, 0, 0}; + if(gsr_pipewire_map_texture(&self->pipewire, self->input_texture_id, self->cursor_texture_id, ®ion, &cursor_region)) { + content_size.x = region.width; + content_size.y = region.height; + } + + const int target_x = max_int(0, frame->width / 2 - content_size.x / 2); + const int target_y = max_int(0, frame->height / 2 - content_size.y / 2); + + gsr_color_conversion_draw(color_conversion, self->input_texture_id, + (vec2i){target_x, target_y}, content_size, + (vec2i){region.x, region.y}, content_size, + 0.0f, false); + + const vec2i cursor_pos = { + target_x + cursor_region.x, + target_y + cursor_region.y + }; + + self->params.egl->glEnable(GL_SCISSOR_TEST); + self->params.egl->glScissor(target_x, target_y, content_size.x, content_size.y); + gsr_color_conversion_draw(color_conversion, self->cursor_texture_id, + (vec2i){cursor_pos.x, cursor_pos.y}, (vec2i){cursor_region.width, cursor_region.height}, + (vec2i){0, 0}, (vec2i){cursor_region.width, cursor_region.height}, + 0.0f, false); + self->params.egl->glDisable(GL_SCISSOR_TEST); + + self->params.egl->eglSwapBuffers(self->params.egl->egl_display, self->params.egl->egl_surface); + + //self->params.egl->glFlush(); + //self->params.egl->glFinish(); + + return 0; +} + +static bool gsr_capture_portal_should_stop(gsr_capture *cap, bool *err) { + gsr_capture_portal *cap_portal = cap->priv; + if(cap_portal->should_stop) { + if(err) + *err = cap_portal->stop_is_error; + return true; + } + + if(err) + *err = false; + return false; +} + +static void gsr_capture_portal_capture_end(gsr_capture *cap, AVFrame *frame) { + (void)cap; + (void)frame; +} + +static gsr_source_color gsr_capture_portal_get_source_color(gsr_capture *cap) { + (void)cap; + return GSR_SOURCE_COLOR_RGB; +} + +// static bool gsr_capture_portal_uses_external_image(gsr_capture *cap) { +// gsr_capture_portal *cap_portal = cap->priv; +// return cap_portal->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA; +// } + +static void gsr_capture_portal_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) { + (void)video_codec_context; + gsr_capture_portal *cap_portal = cap->priv; + if(cap->priv) { + gsr_capture_portal_stop(cap_portal); + free(cap->priv); + cap->priv = NULL; + } + free(cap); +} + +gsr_capture* gsr_capture_portal_create(const gsr_capture_portal_params *params) { + if(!params) { + fprintf(stderr, "gsr error: gsr_capture_portal_create params is NULL\n"); + return NULL; + } + + gsr_capture *cap = calloc(1, sizeof(gsr_capture)); + if(!cap) + return NULL; + + gsr_capture_portal *cap_portal = calloc(1, sizeof(gsr_capture_portal)); + if(!cap_portal) { + free(cap); + return NULL; + } + + cap_portal->params = *params; + + *cap = (gsr_capture) { + .start = gsr_capture_portal_start, + .tick = NULL, + .should_stop = gsr_capture_portal_should_stop, + .capture = gsr_capture_portal_capture, + .capture_end = gsr_capture_portal_capture_end, + .get_source_color = gsr_capture_portal_get_source_color, + .uses_external_image = NULL, + .destroy = gsr_capture_portal_destroy, + .priv = cap_portal + }; + + return cap; +} diff --git a/src/capture/xcomposite.c b/src/capture/xcomposite.c index f5d2b2f..83c4800 100644 --- a/src/capture/xcomposite.c +++ b/src/capture/xcomposite.c @@ -351,23 +351,15 @@ static int gsr_capture_xcomposite_capture(gsr_capture *cap, AVFrame *frame, gsr_ if(self->params.record_cursor && self->cursor.visible) { gsr_cursor_tick(&self->cursor, self->window); - const bool cursor_inside_window = - cursor_pos.x + self->cursor.size.x >= target_x && - cursor_pos.x <= target_x + self->texture_size.x && - cursor_pos.y + self->cursor.size.y >= target_y && - cursor_pos.y <= target_y + self->texture_size.y; - - if(cursor_inside_window) { - self->params.egl->glEnable(GL_SCISSOR_TEST); - self->params.egl->glScissor(target_x, target_y, self->texture_size.x, self->texture_size.y); - - gsr_color_conversion_draw(color_conversion, self->cursor.texture_id, - cursor_pos, self->cursor.size, - (vec2i){0, 0}, self->cursor.size, - 0.0f, false); - - self->params.egl->glDisable(GL_SCISSOR_TEST); - } + self->params.egl->glEnable(GL_SCISSOR_TEST); + self->params.egl->glScissor(target_x, target_y, self->texture_size.x, self->texture_size.y); + + gsr_color_conversion_draw(color_conversion, self->cursor.texture_id, + cursor_pos, self->cursor.size, + (vec2i){0, 0}, self->cursor.size, + 0.0f, false); + + self->params.egl->glDisable(GL_SCISSOR_TEST); } self->params.egl->eglSwapBuffers(self->params.egl->egl_display, self->params.egl->egl_surface); -- cgit v1.2.3