diff options
Diffstat (limited to 'src/capture')
-rw-r--r-- | src/capture/capture.c | 70 | ||||
-rw-r--r-- | src/capture/kms.c | 778 | ||||
-rw-r--r-- | src/capture/kms_cuda.c | 615 | ||||
-rw-r--r-- | src/capture/kms_vaapi.c | 661 | ||||
-rw-r--r-- | src/capture/nvfbc.c | 465 | ||||
-rw-r--r-- | src/capture/portal.c | 430 | ||||
-rw-r--r-- | src/capture/xcomposite.c | 338 | ||||
-rw-r--r-- | src/capture/xcomposite_cuda.c | 512 | ||||
-rw-r--r-- | src/capture/xcomposite_vaapi.c | 517 | ||||
-rw-r--r-- | src/capture/ximage.c | 247 |
10 files changed, 2036 insertions, 2597 deletions
diff --git a/src/capture/capture.c b/src/capture/capture.c index eea0d1d..bc95300 100644 --- a/src/capture/capture.c +++ b/src/capture/capture.c @@ -1,59 +1,53 @@ #include "../../include/capture/capture.h" -#include <stdio.h> +#include <assert.h> -int gsr_capture_start(gsr_capture *cap, AVCodecContext *video_codec_context) { - if(cap->started) - return -1; - - int res = cap->start(cap, video_codec_context); +int gsr_capture_start(gsr_capture *cap, gsr_capture_metadata *capture_metadata) { + assert(!cap->started); + int res = cap->start(cap, capture_metadata); if(res == 0) cap->started = true; return res; } -void gsr_capture_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) { - if(!cap->started) { - fprintf(stderr, "gsr error: gsp_capture_tick failed: the gsr capture has not been started\n"); - return; - } - +void gsr_capture_tick(gsr_capture *cap) { + assert(cap->started); if(cap->tick) - cap->tick(cap, video_codec_context, frame); + cap->tick(cap); } -bool gsr_capture_should_stop(gsr_capture *cap, bool *err) { - if(!cap->started) { - fprintf(stderr, "gsr error: gsr_capture_should_stop failed: the gsr capture has not been started\n"); - return false; - } +void gsr_capture_on_event(gsr_capture *cap, gsr_egl *egl) { + if(cap->on_event) + cap->on_event(cap, egl); +} - if(!cap->should_stop) +bool gsr_capture_should_stop(gsr_capture *cap, bool *err) { + assert(cap->started); + if(cap->should_stop) + return cap->should_stop(cap, err); + else return false; - - return cap->should_stop(cap, err); } -int gsr_capture_capture(gsr_capture *cap, AVFrame *frame) { - if(!cap->started) { - fprintf(stderr, "gsr error: gsr_capture_capture failed: the gsr capture has not been started\n"); - return -1; - } - return cap->capture(cap, frame); +int gsr_capture_capture(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion) { + assert(cap->started); + return cap->capture(cap, capture_metadata, color_conversion); } -void gsr_capture_end(gsr_capture *cap, AVFrame *frame) { - if(!cap->started) { - fprintf(stderr, "gsr error: gsr_capture_end failed: the gsr capture has not been started\n"); - return; - } - - if(!cap->capture_end) - return; +bool gsr_capture_uses_external_image(gsr_capture *cap) { + if(cap->uses_external_image) + return cap->uses_external_image(cap); + else + return false; +} - cap->capture_end(cap, frame); +bool gsr_capture_set_hdr_metadata(gsr_capture *cap, AVMasteringDisplayMetadata *mastering_display_metadata, AVContentLightMetadata *light_metadata) { + if(cap->set_hdr_metadata) + return cap->set_hdr_metadata(cap, mastering_display_metadata, light_metadata); + else + return false; } -void gsr_capture_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) { - cap->destroy(cap, video_codec_context); +void gsr_capture_destroy(gsr_capture *cap) { + cap->destroy(cap); } diff --git a/src/capture/kms.c b/src/capture/kms.c new file mode 100644 index 0000000..18858f2 --- /dev/null +++ b/src/capture/kms.c @@ -0,0 +1,778 @@ +#include "../../include/capture/kms.h" +#include "../../include/utils.h" +#include "../../include/color_conversion.h" +#include "../../include/cursor.h" +#include "../../include/window/window.h" +#include "../../kms/client/kms_client.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> + +#include <xf86drm.h> +#include <libdrm/drm_fourcc.h> + +#include <libavutil/mastering_display_metadata.h> + +#define FIND_CRTC_BY_NAME_TIMEOUT_SECONDS 2.0 + +#define HDMI_STATIC_METADATA_TYPE1 0 +#define HDMI_EOTF_SMPTE_ST2084 2 + +#define MAX_CONNECTOR_IDS 32 + +typedef struct { + uint32_t connector_ids[MAX_CONNECTOR_IDS]; + int num_connector_ids; +} MonitorId; + +typedef struct { + gsr_capture_kms_params params; + + gsr_kms_client kms_client; + gsr_kms_response kms_response; + + vec2i capture_pos; + vec2i capture_size; + MonitorId monitor_id; + + gsr_monitor_rotation monitor_rotation; + + unsigned int input_texture_id; + unsigned int external_input_texture_id; + unsigned int cursor_texture_id; + + bool no_modifiers_fallback; + bool external_texture_fallback; + + struct hdr_output_metadata hdr_metadata; + bool hdr_metadata_set; + + bool is_x11; + gsr_cursor x11_cursor; + + //int drm_fd; + //uint64_t prev_sequence; + //bool damaged; + + vec2i prev_target_pos; + vec2i prev_plane_size; + + double last_time_monitor_check; +} gsr_capture_kms; + +static void gsr_capture_kms_cleanup_kms_fds(gsr_capture_kms *self) { + for(int i = 0; i < self->kms_response.num_items; ++i) { + for(int j = 0; j < self->kms_response.items[i].num_dma_bufs; ++j) { + gsr_kms_response_dma_buf *dma_buf = &self->kms_response.items[i].dma_buf[j]; + if(dma_buf->fd > 0) { + close(dma_buf->fd); + dma_buf->fd = -1; + } + } + self->kms_response.items[i].num_dma_bufs = 0; + } + self->kms_response.num_items = 0; +} + +static void gsr_capture_kms_stop(gsr_capture_kms *self) { + if(self->input_texture_id) { + self->params.egl->glDeleteTextures(1, &self->input_texture_id); + self->input_texture_id = 0; + } + + if(self->external_input_texture_id) { + self->params.egl->glDeleteTextures(1, &self->external_input_texture_id); + self->external_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->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); +} + +static int max_int(int a, int b) { + return a > b ? a : b; +} + +static void gsr_capture_kms_create_input_texture_ids(gsr_capture_kms *self) { + const float border_color[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + 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_BORDER); + self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + self->params.egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color); + 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->external_input_texture_id); + self->params.egl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, self->external_input_texture_id); + self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + self->params.egl->glTexParameterfv(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_BORDER_COLOR, border_color); + 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); + + 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_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_BORDER); + self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + self->params.egl->glTexParameterfv(cursor_texture_id_target, GL_TEXTURE_BORDER_COLOR, border_color); + 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. */ + +typedef struct { + MonitorId *monitor_id; + const char *monitor_to_capture; + int monitor_to_capture_len; + int num_monitors; +} MonitorCallbackUserdata; + +static void monitor_callback(const gsr_monitor *monitor, void *userdata) { + MonitorCallbackUserdata *monitor_callback_userdata = userdata; + ++monitor_callback_userdata->num_monitors; + + if(monitor_callback_userdata->monitor_to_capture_len != monitor->name_len || memcmp(monitor_callback_userdata->monitor_to_capture, monitor->name, monitor->name_len) != 0) + return; + + if(monitor_callback_userdata->monitor_id->num_connector_ids < MAX_CONNECTOR_IDS) { + monitor_callback_userdata->monitor_id->connector_ids[monitor_callback_userdata->monitor_id->num_connector_ids] = monitor->connector_id; + ++monitor_callback_userdata->monitor_id->num_connector_ids; + } + + if(monitor_callback_userdata->monitor_id->num_connector_ids == MAX_CONNECTOR_IDS) + 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, gsr_capture_metadata *capture_metadata) { + gsr_capture_kms *self = cap->priv; + + gsr_capture_kms_create_input_texture_ids(self); + + gsr_monitor monitor; + self->monitor_id.num_connector_ids = 0; + + int kms_init_res = gsr_kms_client_init(&self->kms_client, self->params.egl->card_path); + if(kms_init_res != 0) + return kms_init_res; + + self->is_x11 = gsr_window_get_display_server(self->params.egl->window) == GSR_DISPLAY_SERVER_X11; + const gsr_connection_type connection_type = self->is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM; + if(self->is_x11) { + Display *display = gsr_window_get_display(self->params.egl->window); + gsr_cursor_init(&self->x11_cursor, self->params.egl, display); + } + + MonitorCallbackUserdata monitor_callback_userdata = { + &self->monitor_id, + self->params.display_to_capture, strlen(self->params.display_to_capture), + 0, + }; + for_each_active_monitor_output(self->params.egl->window, self->params.egl->card_path, connection_type, monitor_callback, &monitor_callback_userdata); + + if(!get_monitor_by_name(self->params.egl, connection_type, self->params.display_to_capture, &monitor)) { + fprintf(stderr, "gsr error: gsr_capture_kms_start: failed to find monitor by name \"%s\"\n", self->params.display_to_capture); + gsr_capture_kms_stop(self); + return -1; + } + + monitor.name = self->params.display_to_capture; + vec2i monitor_position = {0, 0}; + drm_monitor_get_display_server_data(self->params.egl->window, &monitor, &self->monitor_rotation, &monitor_position); + + 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->capture_size = monitor.size; + else + self->capture_size = rotate_capture_size_if_rotated(self, monitor.size); + + if(self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0) { + self->params.output_resolution = scale_keep_aspect_ratio(self->capture_size, self->params.output_resolution); + capture_metadata->width = self->params.output_resolution.x; + capture_metadata->height = self->params.output_resolution.y; + } else if(self->params.region_size.x > 0 && self->params.region_size.y > 0) { + capture_metadata->width = self->params.region_size.x; + capture_metadata->height = self->params.region_size.y; + } else { + capture_metadata->width = self->capture_size.x; + capture_metadata->height = self->capture_size.y; + } + + self->last_time_monitor_check = clock_get_monotonic_seconds(); + return 0; +} + +static void gsr_capture_kms_on_event(gsr_capture *cap, gsr_egl *egl) { + gsr_capture_kms *self = cap->priv; + if(!self->is_x11) + return; + + XEvent *xev = gsr_window_get_event_data(egl->window); + 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 gsr_kms_response_item* find_drm_by_connector_id(gsr_kms_response *kms_response, uint32_t connector_id) { + for(int i = 0; i < kms_response->num_items; ++i) { + if(kms_response->items[i].connector_id == connector_id && !kms_response->items[i].is_cursor) + return &kms_response->items[i]; + } + return NULL; +} + +static gsr_kms_response_item* find_largest_drm(gsr_kms_response *kms_response) { + if(kms_response->num_items == 0) + return NULL; + + int64_t largest_size = 0; + gsr_kms_response_item *largest_drm = &kms_response->items[0]; + for(int i = 0; i < kms_response->num_items; ++i) { + const int64_t size = (int64_t)kms_response->items[i].width * (int64_t)kms_response->items[i].height; + if(size > largest_size && !kms_response->items[i].is_cursor) { + largest_size = size; + largest_drm = &kms_response->items[i]; + } + } + return largest_drm; +} + +static gsr_kms_response_item* find_cursor_drm(gsr_kms_response *kms_response, uint32_t connector_id) { + gsr_kms_response_item *cursor_drm = NULL; + for(int i = 0; i < kms_response->num_items; ++i) { + if(kms_response->items[i].is_cursor) { + cursor_drm = &kms_response->items[i]; + if(kms_response->items[i].connector_id == connector_id) + break; + } + } + return cursor_drm; +} + +static bool hdr_metadata_is_supported_format(const struct hdr_output_metadata *hdr_metadata) { + return hdr_metadata->metadata_type == HDMI_STATIC_METADATA_TYPE1 && + hdr_metadata->hdmi_metadata_type1.metadata_type == HDMI_STATIC_METADATA_TYPE1 && + hdr_metadata->hdmi_metadata_type1.eotf == HDMI_EOTF_SMPTE_ST2084; +} + +// TODO: Check if this hdr data can be changed after the call to av_packet_side_data_add +static void gsr_kms_set_hdr_metadata(gsr_capture_kms *self, const gsr_kms_response_item *drm_fd) { + if(self->hdr_metadata_set) + return; + + self->hdr_metadata_set = true; + self->hdr_metadata = drm_fd->hdr_metadata; +} + +static vec2i swap_vec2i(vec2i value) { + int tmp = value.x; + value.x = value.y; + value.y = tmp; + return value; +} + +static EGLImage gsr_capture_kms_create_egl_image(gsr_capture_kms *self, const gsr_kms_response_item *drm_fd, const int *fds, const uint32_t *offsets, const uint32_t *pitches, const uint64_t *modifiers, bool use_modifiers) { + intptr_t img_attr[44]; + setup_dma_buf_attrs(img_attr, drm_fd->pixel_format, drm_fd->width, drm_fd->height, fds, offsets, pitches, modifiers, drm_fd->num_dma_bufs, use_modifiers); + while(self->params.egl->eglGetError() != EGL_SUCCESS){} + EGLImage image = self->params.egl->eglCreateImage(self->params.egl->egl_display, 0, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr); + if(!image || self->params.egl->eglGetError() != EGL_SUCCESS) { + if(image) + self->params.egl->eglDestroyImage(self->params.egl->egl_display, image); + return NULL; + } + return image; +} + +static EGLImage gsr_capture_kms_create_egl_image_with_fallback(gsr_capture_kms *self, const gsr_kms_response_item *drm_fd) { + // TODO: This causes a crash sometimes on steam deck, why? is it a driver bug? a vaapi pure version doesn't cause a crash. + // Even ffmpeg kmsgrab causes this crash. The error is: + // amdgpu: Failed to allocate a buffer: + // amdgpu: size : 28508160 bytes + // amdgpu: alignment : 2097152 bytes + // amdgpu: domains : 4 + // amdgpu: flags : 4 + // amdgpu: Failed to allocate a buffer: + // amdgpu: size : 28508160 bytes + // amdgpu: alignment : 2097152 bytes + // amdgpu: domains : 4 + // amdgpu: flags : 4 + // EE ../jupiter-mesa/src/gallium/drivers/radeonsi/radeon_vcn_enc.c:516 radeon_create_encoder UVD - Can't create CPB buffer. + // [hevc_vaapi @ 0x55ea72b09840] Failed to upload encode parameters: 2 (resource allocation failed). + // [hevc_vaapi @ 0x55ea72b09840] Encode failed: -5. + // Error: avcodec_send_frame failed, error: Input/output error + // 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 + + int fds[GSR_KMS_MAX_DMA_BUFS]; + uint32_t offsets[GSR_KMS_MAX_DMA_BUFS]; + uint32_t pitches[GSR_KMS_MAX_DMA_BUFS]; + uint64_t modifiers[GSR_KMS_MAX_DMA_BUFS]; + + 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; + } + + EGLImage image = NULL; + if(self->no_modifiers_fallback) { + image = gsr_capture_kms_create_egl_image(self, drm_fd, fds, offsets, pitches, modifiers, false); + } else { + image = gsr_capture_kms_create_egl_image(self, drm_fd, fds, offsets, pitches, modifiers, true); + if(!image) { + fprintf(stderr, "gsr error: gsr_capture_kms_create_egl_image_with_fallback: failed to create egl image with modifiers, trying without modifiers\n"); + self->no_modifiers_fallback = true; + image = gsr_capture_kms_create_egl_image(self, drm_fd, fds, offsets, pitches, modifiers, false); + } + } + return image; +} + +static bool gsr_capture_kms_bind_image_to_texture(gsr_capture_kms *self, EGLImage image, unsigned int texture_id, bool external_texture) { + const int texture_target = external_texture ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; + while(self->params.egl->glGetError() != 0){} + self->params.egl->glBindTexture(texture_target, texture_id); + self->params.egl->glEGLImageTargetTexture2DOES(texture_target, image); + const bool success = self->params.egl->glGetError() == 0; + self->params.egl->glBindTexture(texture_target, 0); + return success; +} + +static void gsr_capture_kms_bind_image_to_input_texture_with_fallback(gsr_capture_kms *self, EGLImage image) { + if(self->external_texture_fallback) { + gsr_capture_kms_bind_image_to_texture(self, image, self->external_input_texture_id, true); + } else { + if(!gsr_capture_kms_bind_image_to_texture(self, image, self->input_texture_id, false)) { + fprintf(stderr, "gsr error: gsr_capture_kms_capture: failed to bind image to texture, trying with external texture\n"); + self->external_texture_fallback = true; + gsr_capture_kms_bind_image_to_texture(self, image, self->external_input_texture_id, true); + } + } +} + +static gsr_kms_response_item* find_monitor_drm(gsr_capture_kms *self, bool *capture_is_combined_plane) { + *capture_is_combined_plane = false; + gsr_kms_response_item *drm_fd = NULL; + + for(int i = 0; i < self->monitor_id.num_connector_ids; ++i) { + drm_fd = find_drm_by_connector_id(&self->kms_response, self->monitor_id.connector_ids[i]); + if(drm_fd) + break; + } + + // Will never happen on wayland unless the target monitor has been disconnected + if(!drm_fd && self->is_x11) { + drm_fd = find_largest_drm(&self->kms_response); + *capture_is_combined_plane = true; + } + + return drm_fd; +} + +static gsr_kms_response_item* find_cursor_drm_if_on_monitor(gsr_capture_kms *self, uint32_t monitor_connector_id, bool capture_is_combined_plane) { + gsr_kms_response_item *cursor_drm_fd = find_cursor_drm(&self->kms_response, monitor_connector_id); + if(!capture_is_combined_plane && cursor_drm_fd && cursor_drm_fd->connector_id != monitor_connector_id) + cursor_drm_fd = NULL; + 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, vec2i target_pos, vec2i output_size, vec2i framebuffer_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}; + + vec2i cursor_pos = {cursor_drm_fd->x, cursor_drm_fd->y}; + switch(self->monitor_rotation) { + case GSR_MONITOR_ROT_0: + break; + case GSR_MONITOR_ROT_90: + cursor_pos = swap_vec2i(cursor_pos); + cursor_pos.x = framebuffer_size.x - cursor_pos.x; + // TODO: Remove this horrible hack + cursor_pos.x -= cursor_size.x; + break; + case GSR_MONITOR_ROT_180: + cursor_pos.x = framebuffer_size.x - cursor_pos.x; + cursor_pos.y = framebuffer_size.y - cursor_pos.y; + // TODO: Remove this horrible hack + cursor_pos.x -= cursor_size.x; + cursor_pos.y -= cursor_size.y; + break; + case GSR_MONITOR_ROT_270: + cursor_pos = swap_vec2i(cursor_pos); + cursor_pos.y = framebuffer_size.y - cursor_pos.y; + // TODO: Remove this horrible hack + cursor_pos.y -= cursor_size.y; + break; + } + + cursor_pos.x -= self->params.region_position.x; + cursor_pos.y -= self->params.region_position.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]; + uint32_t pitches[GSR_KMS_MAX_DMA_BUFS]; + uint64_t modifiers[GSR_KMS_MAX_DMA_BUFS]; + + for(int i = 0; i < cursor_drm_fd->num_dma_bufs; ++i) { + fds[i] = cursor_drm_fd->dma_buf[i].fd; + offsets[i] = cursor_drm_fd->dma_buf[i].offset; + pitches[i] = cursor_drm_fd->dma_buf[i].pitch; + modifiers[i] = cursor_drm_fd->modifier; + } + + intptr_t img_attr_cursor[44]; + setup_dma_buf_attrs(img_attr_cursor, cursor_drm_fd->pixel_format, cursor_drm_fd->width, cursor_drm_fd->height, + fds, offsets, pitches, modifiers, cursor_drm_fd->num_dma_bufs, true); + + 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_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->glBindTexture(target, 0); + + if(cursor_image) + self->params.egl->eglDestroyImage(self->params.egl->egl_display, cursor_image); + + 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->cursor_texture_id, + cursor_pos, (vec2i){cursor_size.x * scale.x, cursor_size.y * scale.y}, + (vec2i){0, 0}, cursor_size, cursor_size, + gsr_monitor_rotation_to_rotation(self->monitor_rotation), GSR_SOURCE_COLOR_RGB, cursor_texture_id_is_external, true); + + self->params.egl->glDisable(GL_SCISSOR_TEST); +} + +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 + }; + + Display *display = gsr_window_get_display(self->params.egl->window); + gsr_cursor_tick(&self->x11_cursor, DefaultRootWindow(display)); + + const vec2i cursor_pos = { + 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_pos.x, target_pos.y, output_size.x, output_size.y); + + gsr_color_conversion_draw(color_conversion, self->x11_cursor.texture_id, + cursor_pos, (vec2i){self->x11_cursor.size.x * scale.x, self->x11_cursor.size.y * scale.y}, + (vec2i){0, 0}, self->x11_cursor.size, self->x11_cursor.size, + GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, true); + + 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 void gsr_capture_kms_update_connector_ids(gsr_capture_kms *self) { + const double now = clock_get_monotonic_seconds(); + if(now - self->last_time_monitor_check < FIND_CRTC_BY_NAME_TIMEOUT_SECONDS) + return; + + self->last_time_monitor_check = now; + /* TODO: Assume for now that there is only 1 framebuffer for all monitors and it doesn't change */ + if(self->is_x11) + return; + + self->monitor_id.num_connector_ids = 0; + const gsr_connection_type connection_type = self->is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM; + // MonitorCallbackUserdata monitor_callback_userdata = { + // &self->monitor_id, + // self->params.display_to_capture, strlen(self->params.display_to_capture), + // 0, + // }; + // for_each_active_monitor_output(self->params.egl->window, self->params.egl->card_path, connection_type, monitor_callback, &monitor_callback_userdata); + + gsr_monitor monitor; + if(!get_monitor_by_name(self->params.egl, connection_type, self->params.display_to_capture, &monitor)) { + fprintf(stderr, "gsr error: gsr_capture_kms_update_connector_ids: failed to find monitor by name \"%s\"\n", self->params.display_to_capture); + return; + } + + self->monitor_id.num_connector_ids = 1; + self->monitor_id.connector_ids[0] = monitor.connector_id; + + monitor.name = self->params.display_to_capture; + vec2i monitor_position = {0, 0}; + // TODO: This is cached. We need it updated. + drm_monitor_get_display_server_data(self->params.egl->window, &monitor, &self->monitor_rotation, &monitor_position); + + 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->capture_size = monitor.size; + else + self->capture_size = rotate_capture_size_if_rotated(self, monitor.size); +} + +static int gsr_capture_kms_capture(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion) { + gsr_capture_kms *self = cap->priv; + + gsr_capture_kms_cleanup_kms_fds(self); + + if(gsr_kms_client_get_kms(&self->kms_client, &self->kms_response) != 0) { + fprintf(stderr, "gsr error: gsr_capture_kms_capture: failed to get kms, error: %d (%s)\n", self->kms_response.result, self->kms_response.err_msg); + return -1; + } + + if(self->kms_response.num_items == 0) { + static bool error_shown = false; + if(!error_shown) { + error_shown = true; + fprintf(stderr, "gsr error: no drm found, capture will fail\n"); + } + return -1; + } + + gsr_capture_kms_update_connector_ids(self); + + bool capture_is_combined_plane = false; + const gsr_kms_response_item *drm_fd = find_monitor_drm(self, &capture_is_combined_plane); + 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); + + self->capture_size = rotate_capture_size_if_rotated(self, (vec2i){ drm_fd->src_w, drm_fd->src_h }); + const vec2i original_frame_size = self->capture_size; + if(self->params.region_size.x > 0 && self->params.region_size.y > 0) + self->capture_size = self->params.region_size; + + 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, capture_metadata->width / 2 - output_size.x / 2), max_int(0, capture_metadata->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) + capture_pos = (vec2i){drm_fd->x, drm_fd->y}; + + capture_pos.x += self->params.region_position.x; + capture_pos.y += self->params.region_position.y; + + //self->params.egl->glFlush(); + //self->params.egl->glFinish(); + + 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, original_frame_size, + gsr_monitor_rotation_to_rotation(self->monitor_rotation), GSR_SOURCE_COLOR_RGB, self->external_texture_fallback, false); + + 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. + // 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) { + vec2i cursor_monitor_offset = self->capture_pos; + cursor_monitor_offset.x += self->params.region_position.x; + cursor_monitor_offset.y += self->params.region_position.y; + render_x11_cursor(self, color_conversion, cursor_monitor_offset, target_pos, output_size); + } else if(cursor_drm_fd) { + const vec2i framebuffer_size = rotate_capture_size_if_rotated(self, (vec2i){ drm_fd->src_w, drm_fd->src_h }); + render_drm_cursor(self, color_conversion, cursor_drm_fd, target_pos, output_size, framebuffer_size); + } + } + + //self->params.egl->glFlush(); + //self->params.egl->glFinish(); + + gsr_capture_kms_cleanup_kms_fds(self); + + return 0; +} + +static bool gsr_capture_kms_should_stop(gsr_capture *cap, bool *err) { + (void)cap; + if(err) + *err = false; + return false; +} + +static bool gsr_capture_kms_uses_external_image(gsr_capture *cap) { + (void)cap; + return true; +} + +static bool gsr_capture_kms_set_hdr_metadata(gsr_capture *cap, AVMasteringDisplayMetadata *mastering_display_metadata, AVContentLightMetadata *light_metadata) { + gsr_capture_kms *self = cap->priv; + + if(!self->hdr_metadata_set) + return false; + + light_metadata->MaxCLL = self->hdr_metadata.hdmi_metadata_type1.max_cll; + light_metadata->MaxFALL = self->hdr_metadata.hdmi_metadata_type1.max_fall; + + for(int i = 0; i < 3; ++i) { + mastering_display_metadata->display_primaries[i][0] = av_make_q(self->hdr_metadata.hdmi_metadata_type1.display_primaries[i].x, 50000); + mastering_display_metadata->display_primaries[i][1] = av_make_q(self->hdr_metadata.hdmi_metadata_type1.display_primaries[i].y, 50000); + } + + mastering_display_metadata->white_point[0] = av_make_q(self->hdr_metadata.hdmi_metadata_type1.white_point.x, 50000); + mastering_display_metadata->white_point[1] = av_make_q(self->hdr_metadata.hdmi_metadata_type1.white_point.y, 50000); + + mastering_display_metadata->min_luminance = av_make_q(self->hdr_metadata.hdmi_metadata_type1.min_display_mastering_luminance, 10000); + mastering_display_metadata->max_luminance = av_make_q(self->hdr_metadata.hdmi_metadata_type1.max_display_mastering_luminance, 1); + + mastering_display_metadata->has_primaries = true; + mastering_display_metadata->has_luminance = true; + + 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) { + gsr_capture_kms *self = cap->priv; + if(cap->priv) { + gsr_capture_kms_stop(self); + free((void*)self->params.display_to_capture); + self->params.display_to_capture = NULL; + free(cap->priv); + cap->priv = NULL; + } + free(cap); +} + +gsr_capture* gsr_capture_kms_create(const gsr_capture_kms_params *params) { + if(!params) { + fprintf(stderr, "gsr error: gsr_capture_kms_create params is NULL\n"); + return NULL; + } + + gsr_capture *cap = calloc(1, sizeof(gsr_capture)); + if(!cap) + return NULL; + + gsr_capture_kms *cap_kms = calloc(1, sizeof(gsr_capture_kms)); + if(!cap_kms) { + free(cap); + return NULL; + } + + const char *display_to_capture = strdup(params->display_to_capture); + if(!display_to_capture) { + free(cap); + free(cap_kms); + return NULL; + } + + cap_kms->params = *params; + cap_kms->params.display_to_capture = display_to_capture; + + *cap = (gsr_capture) { + .start = gsr_capture_kms_start, + .on_event = gsr_capture_kms_on_event, + //.tick = gsr_capture_kms_tick, + .should_stop = gsr_capture_kms_should_stop, + .capture = gsr_capture_kms_capture, + .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 + }; + + return cap; +} diff --git a/src/capture/kms_cuda.c b/src/capture/kms_cuda.c deleted file mode 100644 index d93d603..0000000 --- a/src/capture/kms_cuda.c +++ /dev/null @@ -1,615 +0,0 @@ -#include "../../include/capture/kms_cuda.h" -#include "../../kms/client/kms_client.h" -#include "../../include/utils.h" -#include "../../include/color_conversion.h" -#include "../../include/cuda.h" -#include <stdlib.h> -#include <stdio.h> -#include <unistd.h> -#include <assert.h> -#include <libavutil/hwcontext.h> -#include <libavutil/hwcontext_cuda.h> -#include <libavutil/frame.h> -#include <libavcodec/avcodec.h> - -/* - TODO: Use dummy pool for cuda buffer so we can create our own cuda buffers from pixel buffer objects - and copy the input textures to the pixel buffer objects. Use sw_format NV12 as well. Then this is - similar to kms_vaapi. This allows us to remove one extra texture and texture copy. -*/ -/* TODO: Support cursor plane capture when nvidia supports cursor plane */ - -#define MAX_CONNECTOR_IDS 32 - -typedef struct { - uint32_t connector_ids[MAX_CONNECTOR_IDS]; - int num_connector_ids; -} MonitorId; - -typedef struct { - gsr_capture_kms_cuda_params params; - XEvent xev; - - bool should_stop; - bool stop_is_error; - bool created_hw_frame; - - gsr_cuda cuda; - - gsr_kms_client kms_client; - gsr_kms_response kms_response; - gsr_kms_response_fd wayland_kms_data; - bool using_wayland_capture; - - vec2i capture_pos; - vec2i capture_size; - MonitorId monitor_id; - - CUgraphicsResource cuda_graphics_resource; - CUarray mapped_array; - - unsigned int input_texture; - unsigned int target_texture; - gsr_color_conversion color_conversion; -} gsr_capture_kms_cuda; - -static int max_int(int a, int b) { - return a > b ? a : b; -} - -static void gsr_capture_kms_cuda_stop(gsr_capture *cap, AVCodecContext *video_codec_context); - -static bool cuda_create_codec_context(gsr_capture_kms_cuda *cap_kms, AVCodecContext *video_codec_context) { - CUcontext old_ctx; - cap_kms->cuda.cuCtxPushCurrent_v2(cap_kms->cuda.cu_ctx); - - AVBufferRef *device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA); - if(!device_ctx) { - fprintf(stderr, "Error: Failed to create hardware device context\n"); - cap_kms->cuda.cuCtxPopCurrent_v2(&old_ctx); - return false; - } - - AVHWDeviceContext *hw_device_context = (AVHWDeviceContext*)device_ctx->data; - AVCUDADeviceContext *cuda_device_context = (AVCUDADeviceContext*)hw_device_context->hwctx; - cuda_device_context->cuda_ctx = cap_kms->cuda.cu_ctx; - if(av_hwdevice_ctx_init(device_ctx) < 0) { - fprintf(stderr, "Error: Failed to create hardware device context\n"); - av_buffer_unref(&device_ctx); - cap_kms->cuda.cuCtxPopCurrent_v2(&old_ctx); - return false; - } - - AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx); - if(!frame_context) { - fprintf(stderr, "Error: Failed to create hwframe context\n"); - av_buffer_unref(&device_ctx); - cap_kms->cuda.cuCtxPopCurrent_v2(&old_ctx); - return false; - } - - AVHWFramesContext *hw_frame_context = - (AVHWFramesContext *)frame_context->data; - hw_frame_context->width = video_codec_context->width; - hw_frame_context->height = video_codec_context->height; - hw_frame_context->sw_format = AV_PIX_FMT_BGR0; - hw_frame_context->format = video_codec_context->pix_fmt; - hw_frame_context->device_ref = device_ctx; - hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data; - - hw_frame_context->initial_pool_size = 1; - - if (av_hwframe_ctx_init(frame_context) < 0) { - fprintf(stderr, "Error: Failed to initialize hardware frame context " - "(note: ffmpeg version needs to be > 4.0)\n"); - av_buffer_unref(&device_ctx); - //av_buffer_unref(&frame_context); - cap_kms->cuda.cuCtxPopCurrent_v2(&old_ctx); - return false; - } - - video_codec_context->hw_device_ctx = av_buffer_ref(device_ctx); - video_codec_context->hw_frames_ctx = av_buffer_ref(frame_context); - return true; -} - -// TODO: On monitor reconfiguration, find monitor x, y, width and height again. Do the same for nvfbc. - -typedef struct { - gsr_capture_kms_cuda *cap_kms; - const char *monitor_to_capture; - int monitor_to_capture_len; - int num_monitors; -} MonitorCallbackUserdata; - -static void monitor_callback(const gsr_monitor *monitor, void *userdata) { - MonitorCallbackUserdata *monitor_callback_userdata = userdata; - ++monitor_callback_userdata->num_monitors; - - if(monitor_callback_userdata->monitor_to_capture_len != monitor->name_len || memcmp(monitor_callback_userdata->monitor_to_capture, monitor->name, monitor->name_len) != 0) - return; - - if(monitor_callback_userdata->cap_kms->monitor_id.num_connector_ids < MAX_CONNECTOR_IDS) { - monitor_callback_userdata->cap_kms->monitor_id.connector_ids[monitor_callback_userdata->cap_kms->monitor_id.num_connector_ids] = monitor->connector_id; - ++monitor_callback_userdata->cap_kms->monitor_id.num_connector_ids; - } - - if(monitor_callback_userdata->cap_kms->monitor_id.num_connector_ids == MAX_CONNECTOR_IDS) - fprintf(stderr, "gsr warning: reached max connector ids\n"); -} - -static int gsr_capture_kms_cuda_start(gsr_capture *cap, AVCodecContext *video_codec_context) { - gsr_capture_kms_cuda *cap_kms = cap->priv; - - gsr_monitor monitor; - cap_kms->monitor_id.num_connector_ids = 0; - if(gsr_egl_start_capture(cap_kms->params.egl, cap_kms->params.display_to_capture)) { - if(!get_monitor_by_name(cap_kms->params.egl, GSR_CONNECTION_WAYLAND, cap_kms->params.display_to_capture, &monitor)) { - fprintf(stderr, "gsr error: gsr_capture_kms_cuda_start: failed to find monitor by name \"%s\"\n", cap_kms->params.display_to_capture); - gsr_capture_kms_cuda_stop(cap, video_codec_context); - return -1; - } - cap_kms->using_wayland_capture = true; - } else { - int kms_init_res = gsr_kms_client_init(&cap_kms->kms_client, cap_kms->params.card_path); - if(kms_init_res != 0) { - gsr_capture_kms_cuda_stop(cap, video_codec_context); - return kms_init_res; - } - - MonitorCallbackUserdata monitor_callback_userdata = { - cap_kms, - cap_kms->params.display_to_capture, strlen(cap_kms->params.display_to_capture), - 0 - }; - for_each_active_monitor_output((void*)cap_kms->params.card_path, GSR_CONNECTION_DRM, monitor_callback, &monitor_callback_userdata); - - if(!get_monitor_by_name((void*)cap_kms->params.card_path, GSR_CONNECTION_DRM, cap_kms->params.display_to_capture, &monitor)) { - fprintf(stderr, "gsr error: gsr_capture_kms_cuda_start: failed to find monitor by name \"%s\"\n", cap_kms->params.display_to_capture); - gsr_capture_kms_cuda_stop(cap, video_codec_context); - return -1; - } - } - - cap_kms->capture_pos = monitor.pos; - cap_kms->capture_size = monitor.size; - - video_codec_context->width = max_int(2, cap_kms->capture_size.x & ~1); - video_codec_context->height = max_int(2, cap_kms->capture_size.y & ~1); - - /* Disable vsync */ - cap_kms->params.egl->eglSwapInterval(cap_kms->params.egl->egl_display, 0); - - // TODO: overclocking is not supported on wayland... - if(!gsr_cuda_load(&cap_kms->cuda, NULL, false)) { - fprintf(stderr, "gsr error: gsr_capture_kms_cuda_start: failed to load cuda\n"); - gsr_capture_kms_cuda_stop(cap, video_codec_context); - return -1; - } - - if(!cuda_create_codec_context(cap_kms, video_codec_context)) { - gsr_capture_kms_cuda_stop(cap, video_codec_context); - return -1; - } - - return 0; -} - -static unsigned int gl_create_texture(gsr_capture_kms_cuda *cap_kms, int width, int height) { - unsigned int texture_id = 0; - cap_kms->params.egl->glGenTextures(1, &texture_id); - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, texture_id); - cap_kms->params.egl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); - - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - return texture_id; -} - -static bool cuda_register_opengl_texture(gsr_capture_kms_cuda *cap_kms) { - CUresult res; - CUcontext old_ctx; - res = cap_kms->cuda.cuCtxPushCurrent_v2(cap_kms->cuda.cu_ctx); - // TODO: Use cuGraphicsEGLRegisterImage instead with the window egl image (dont use window_texture). - // That removes the need for an extra texture and texture copy - res = cap_kms->cuda.cuGraphicsGLRegisterImage( - &cap_kms->cuda_graphics_resource, cap_kms->target_texture, GL_TEXTURE_2D, - CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY); - if (res != CUDA_SUCCESS) { - const char *err_str = "unknown"; - cap_kms->cuda.cuGetErrorString(res, &err_str); - fprintf(stderr, "gsr error: cuda_register_opengl_texture: cuGraphicsGLRegisterImage failed, error: %s, texture " "id: %u\n", err_str, cap_kms->target_texture); - res = cap_kms->cuda.cuCtxPopCurrent_v2(&old_ctx); - return false; - } - - res = cap_kms->cuda.cuGraphicsResourceSetMapFlags(cap_kms->cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY); - res = cap_kms->cuda.cuGraphicsMapResources(1, &cap_kms->cuda_graphics_resource, 0); - - res = cap_kms->cuda.cuGraphicsSubResourceGetMappedArray(&cap_kms->mapped_array, cap_kms->cuda_graphics_resource, 0, 0); - res = cap_kms->cuda.cuCtxPopCurrent_v2(&old_ctx); - return true; -} - -static void gsr_capture_kms_cuda_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) { - gsr_capture_kms_cuda *cap_kms = cap->priv; - - // TODO: - cap_kms->params.egl->glClear(GL_COLOR_BUFFER_BIT); - - if(!cap_kms->created_hw_frame) { - cap_kms->created_hw_frame = true; - - av_frame_free(frame); - *frame = av_frame_alloc(); - if(!frame) { - fprintf(stderr, "gsr error: gsr_capture_kms_cuda_tick: failed to allocate frame\n"); - cap_kms->should_stop = true; - cap_kms->stop_is_error = true; - return; - } - (*frame)->format = video_codec_context->pix_fmt; - (*frame)->width = video_codec_context->width; - (*frame)->height = video_codec_context->height; - (*frame)->color_range = video_codec_context->color_range; - (*frame)->color_primaries = video_codec_context->color_primaries; - (*frame)->color_trc = video_codec_context->color_trc; - (*frame)->colorspace = video_codec_context->colorspace; - (*frame)->chroma_location = video_codec_context->chroma_sample_location; - - if(av_hwframe_get_buffer(video_codec_context->hw_frames_ctx, *frame, 0) < 0) { - fprintf(stderr, "gsr error: gsr_capture_kms_cuda_tick: av_hwframe_get_buffer failed\n"); - cap_kms->should_stop = true; - cap_kms->stop_is_error = true; - return; - } - - cap_kms->params.egl->glGenTextures(1, &cap_kms->input_texture); - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, cap_kms->input_texture); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - - cap_kms->target_texture = gl_create_texture(cap_kms, video_codec_context->width, video_codec_context->height); - if(cap_kms->target_texture == 0) { - fprintf(stderr, "gsr error: gsr_capture_kms_cuda_tick: failed to create opengl texture\n"); - cap_kms->should_stop = true; - cap_kms->stop_is_error = true; - return; - } - - if(!cuda_register_opengl_texture(cap_kms)) { - cap_kms->should_stop = true; - cap_kms->stop_is_error = true; - return; - } - - gsr_color_conversion_params color_conversion_params = {0}; - color_conversion_params.egl = cap_kms->params.egl; - color_conversion_params.source_color = GSR_SOURCE_COLOR_RGB; - color_conversion_params.destination_color = GSR_DESTINATION_COLOR_BGR; - - color_conversion_params.destination_textures[0] = cap_kms->target_texture; - color_conversion_params.num_destination_textures = 1; - - if(gsr_color_conversion_init(&cap_kms->color_conversion, &color_conversion_params) != 0) { - fprintf(stderr, "gsr error: gsr_capture_kms_cuda_tick: failed to create color conversion\n"); - cap_kms->should_stop = true; - cap_kms->stop_is_error = true; - return; - } - } -} - -static bool gsr_capture_kms_cuda_should_stop(gsr_capture *cap, bool *err) { - gsr_capture_kms_cuda *cap_kms = cap->priv; - if(cap_kms->should_stop) { - if(err) - *err = cap_kms->stop_is_error; - return true; - } - - if(err) - *err = false; - return false; -} - -/* Prefer non combined planes */ -static gsr_kms_response_fd* find_drm_by_connector_id(gsr_kms_response *kms_response, uint32_t connector_id) { - int index_combined = -1; - for(int i = 0; i < kms_response->num_fds; ++i) { - if(kms_response->fds[i].connector_id == connector_id && !kms_response->fds[i].is_cursor) { - if(kms_response->fds[i].is_combined_plane) - index_combined = i; - else - return &kms_response->fds[i]; - } - } - - if(index_combined != -1) - return &kms_response->fds[index_combined]; - else - return NULL; -} - -static gsr_kms_response_fd* find_first_combined_drm(gsr_kms_response *kms_response) { - for(int i = 0; i < kms_response->num_fds; ++i) { - if(kms_response->fds[i].is_combined_plane && !kms_response->fds[i].is_cursor) - return &kms_response->fds[i]; - } - return NULL; -} - -static gsr_kms_response_fd* find_largest_drm(gsr_kms_response *kms_response) { - if(kms_response->num_fds == 0) - return NULL; - - int64_t largest_size = 0; - gsr_kms_response_fd *largest_drm = &kms_response->fds[0]; - for(int i = 0; i < kms_response->num_fds; ++i) { - const int64_t size = (int64_t)kms_response->fds[i].width * (int64_t)kms_response->fds[i].height; - if(size > largest_size && !kms_response->fds[i].is_cursor) { - largest_size = size; - largest_drm = &kms_response->fds[i]; - } - } - return largest_drm; -} - -static void gsr_capture_kms_unload_cuda_graphics(gsr_capture_kms_cuda *cap_kms) { - if(cap_kms->cuda.cu_ctx) { - CUcontext old_ctx; - cap_kms->cuda.cuCtxPushCurrent_v2(cap_kms->cuda.cu_ctx); - - if(cap_kms->cuda_graphics_resource) { - cap_kms->cuda.cuGraphicsUnmapResources(1, &cap_kms->cuda_graphics_resource, 0); - cap_kms->cuda.cuGraphicsUnregisterResource(cap_kms->cuda_graphics_resource); - cap_kms->cuda_graphics_resource = 0; - } - - cap_kms->cuda.cuCtxPopCurrent_v2(&old_ctx); - } -} - -static gsr_kms_response_fd* find_cursor_drm(gsr_kms_response *kms_response) { - for(int i = 0; i < kms_response->num_fds; ++i) { - if(kms_response->fds[i].is_cursor) - return &kms_response->fds[i]; - } - return NULL; -} - -static int gsr_capture_kms_cuda_capture(gsr_capture *cap, AVFrame *frame) { - (void)frame; - gsr_capture_kms_cuda *cap_kms = cap->priv; - - for(int i = 0; i < cap_kms->kms_response.num_fds; ++i) { - if(cap_kms->kms_response.fds[i].fd > 0) - close(cap_kms->kms_response.fds[i].fd); - cap_kms->kms_response.fds[i].fd = 0; - } - cap_kms->kms_response.num_fds = 0; - - gsr_kms_response_fd *drm_fd = NULL; - gsr_kms_response_fd *cursor_drm_fd = NULL; - bool capture_is_combined_plane = false; - if(cap_kms->using_wayland_capture) { - gsr_egl_update(cap_kms->params.egl); - cap_kms->wayland_kms_data.fd = cap_kms->params.egl->fd; - cap_kms->wayland_kms_data.width = cap_kms->params.egl->width; - cap_kms->wayland_kms_data.height = cap_kms->params.egl->height; - cap_kms->wayland_kms_data.pitch = cap_kms->params.egl->pitch; - cap_kms->wayland_kms_data.offset = cap_kms->params.egl->offset; - cap_kms->wayland_kms_data.pixel_format = cap_kms->params.egl->pixel_format; - cap_kms->wayland_kms_data.modifier = cap_kms->params.egl->modifier; - cap_kms->wayland_kms_data.connector_id = 0; - cap_kms->wayland_kms_data.is_combined_plane = false; - cap_kms->wayland_kms_data.is_cursor = false; - cap_kms->wayland_kms_data.x = cap_kms->wayland_kms_data.x; // TODO: Use these - cap_kms->wayland_kms_data.y = cap_kms->wayland_kms_data.y; - cap_kms->wayland_kms_data.src_w = cap_kms->wayland_kms_data.width; - cap_kms->wayland_kms_data.src_h = cap_kms->wayland_kms_data.height; - - cap_kms->capture_pos.x = cap_kms->wayland_kms_data.x; - cap_kms->capture_pos.y = cap_kms->wayland_kms_data.y; - - if(cap_kms->wayland_kms_data.fd <= 0) - return -1; - - drm_fd = &cap_kms->wayland_kms_data; - } else { - if(gsr_kms_client_get_kms(&cap_kms->kms_client, &cap_kms->kms_response) != 0) { - fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_capture: failed to get kms, error: %d (%s)\n", cap_kms->kms_response.result, cap_kms->kms_response.err_msg); - return -1; - } - - if(cap_kms->kms_response.num_fds == 0) { - static bool error_shown = false; - if(!error_shown) { - error_shown = true; - fprintf(stderr, "gsr error: no drm found, capture will fail\n"); - } - return -1; - } - - for(int i = 0; i < cap_kms->monitor_id.num_connector_ids; ++i) { - drm_fd = find_drm_by_connector_id(&cap_kms->kms_response, cap_kms->monitor_id.connector_ids[i]); - if(drm_fd) - break; - } - - // Will never happen on wayland unless the target monitor has been disconnected - if(!drm_fd) { - drm_fd = find_first_combined_drm(&cap_kms->kms_response); - if(!drm_fd) - drm_fd = find_largest_drm(&cap_kms->kms_response); - capture_is_combined_plane = true; - } - - cursor_drm_fd = find_cursor_drm(&cap_kms->kms_response); - } - - if(!drm_fd) - return -1; - - if(!capture_is_combined_plane && cursor_drm_fd && cursor_drm_fd->connector_id != drm_fd->connector_id) - cursor_drm_fd = NULL; - - const intptr_t img_attr[] = { - //EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, - EGL_LINUX_DRM_FOURCC_EXT, drm_fd->pixel_format,//cap_kms->params.egl->pixel_format, ARGB8888 - EGL_WIDTH, drm_fd->width,//cap_kms->params.egl->width, - EGL_HEIGHT, drm_fd->height,//cap_kms->params.egl->height, - EGL_DMA_BUF_PLANE0_FD_EXT, drm_fd->fd,//cap_kms->params.egl->fd, - EGL_DMA_BUF_PLANE0_OFFSET_EXT, drm_fd->offset,//cap_kms->params.egl->offset, - EGL_DMA_BUF_PLANE0_PITCH_EXT, drm_fd->pitch,//cap_kms->params.egl->pitch, - EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, drm_fd->modifier & 0xFFFFFFFFULL,//cap_kms->params.egl->modifier & 0xFFFFFFFFULL, - EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, drm_fd->modifier >> 32ULL,//cap_kms->params.egl->modifier >> 32ULL, - EGL_NONE - }; - - EGLImage image = cap_kms->params.egl->eglCreateImage(cap_kms->params.egl->egl_display, 0, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr); - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, cap_kms->input_texture); - cap_kms->params.egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); - cap_kms->params.egl->eglDestroyImage(cap_kms->params.egl->egl_display, image); - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - - vec2i capture_pos = cap_kms->capture_pos; - if(!capture_is_combined_plane) - capture_pos = (vec2i){drm_fd->x, drm_fd->y}; - - gsr_color_conversion_draw(&cap_kms->color_conversion, cap_kms->input_texture, - (vec2i){0, 0}, cap_kms->capture_size, - capture_pos, cap_kms->capture_size, - 0.0f); - - cap_kms->params.egl->eglSwapBuffers(cap_kms->params.egl->egl_display, cap_kms->params.egl->egl_surface); - - frame->linesize[0] = frame->width * 4; - - CUDA_MEMCPY2D memcpy_struct; - memcpy_struct.srcXInBytes = 0; - memcpy_struct.srcY = 0; - memcpy_struct.srcMemoryType = CU_MEMORYTYPE_ARRAY; - - memcpy_struct.dstXInBytes = 0; - memcpy_struct.dstY = 0; - memcpy_struct.dstMemoryType = CU_MEMORYTYPE_DEVICE; - - memcpy_struct.srcArray = cap_kms->mapped_array; - memcpy_struct.srcPitch = frame->linesize[0]; - memcpy_struct.dstDevice = (CUdeviceptr)frame->data[0]; - memcpy_struct.dstPitch = frame->linesize[0]; - memcpy_struct.WidthInBytes = frame->width * 4; - memcpy_struct.Height = frame->height; - cap_kms->cuda.cuMemcpy2D_v2(&memcpy_struct); - - return 0; -} - -static void gsr_capture_kms_cuda_capture_end(gsr_capture *cap, AVFrame *frame) { - (void)frame; - gsr_capture_kms_cuda *cap_kms = cap->priv; - - gsr_egl_cleanup_frame(cap_kms->params.egl); - - for(int i = 0; i < cap_kms->kms_response.num_fds; ++i) { - if(cap_kms->kms_response.fds[i].fd > 0) - close(cap_kms->kms_response.fds[i].fd); - cap_kms->kms_response.fds[i].fd = 0; - } - cap_kms->kms_response.num_fds = 0; -} - -static void gsr_capture_kms_cuda_stop(gsr_capture *cap, AVCodecContext *video_codec_context) { - gsr_capture_kms_cuda *cap_kms = cap->priv; - - gsr_color_conversion_deinit(&cap_kms->color_conversion); - - gsr_capture_kms_unload_cuda_graphics(cap_kms); - - if(cap_kms->params.egl->egl_context) { - if(cap_kms->input_texture) { - cap_kms->params.egl->glDeleteTextures(1, &cap_kms->input_texture); - cap_kms->input_texture = 0; - } - - if(cap_kms->target_texture) { - cap_kms->params.egl->glDeleteTextures(1, &cap_kms->target_texture); - cap_kms->target_texture = 0; - } - } - - for(int i = 0; i < cap_kms->kms_response.num_fds; ++i) { - if(cap_kms->kms_response.fds[i].fd > 0) - close(cap_kms->kms_response.fds[i].fd); - cap_kms->kms_response.fds[i].fd = 0; - } - cap_kms->kms_response.num_fds = 0; - - if(video_codec_context->hw_device_ctx) - av_buffer_unref(&video_codec_context->hw_device_ctx); - if(video_codec_context->hw_frames_ctx) - av_buffer_unref(&video_codec_context->hw_frames_ctx); - - gsr_cuda_unload(&cap_kms->cuda); - gsr_kms_client_deinit(&cap_kms->kms_client); -} - -static void gsr_capture_kms_cuda_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) { - (void)video_codec_context; - gsr_capture_kms_cuda *cap_kms = cap->priv; - if(cap->priv) { - gsr_capture_kms_cuda_stop(cap, video_codec_context); - free((void*)cap_kms->params.display_to_capture); - cap_kms->params.display_to_capture = NULL; - free(cap->priv); - cap->priv = NULL; - } - free(cap); -} - -gsr_capture* gsr_capture_kms_cuda_create(const gsr_capture_kms_cuda_params *params) { - if(!params) { - fprintf(stderr, "gsr error: gsr_capture_kms_cuda_create params is NULL\n"); - return NULL; - } - - gsr_capture *cap = calloc(1, sizeof(gsr_capture)); - if(!cap) - return NULL; - - gsr_capture_kms_cuda *cap_kms = calloc(1, sizeof(gsr_capture_kms_cuda)); - if(!cap_kms) { - free(cap); - return NULL; - } - - const char *display_to_capture = strdup(params->display_to_capture); - if(!display_to_capture) { - free(cap); - free(cap_kms); - return NULL; - } - - cap_kms->params = *params; - cap_kms->params.display_to_capture = display_to_capture; - - *cap = (gsr_capture) { - .start = gsr_capture_kms_cuda_start, - .tick = gsr_capture_kms_cuda_tick, - .should_stop = gsr_capture_kms_cuda_should_stop, - .capture = gsr_capture_kms_cuda_capture, - .capture_end = gsr_capture_kms_cuda_capture_end, - .destroy = gsr_capture_kms_cuda_destroy, - .priv = cap_kms - }; - - return cap; -} diff --git a/src/capture/kms_vaapi.c b/src/capture/kms_vaapi.c deleted file mode 100644 index 681f345..0000000 --- a/src/capture/kms_vaapi.c +++ /dev/null @@ -1,661 +0,0 @@ -#include "../../include/capture/kms_vaapi.h" -#include "../../kms/client/kms_client.h" -#include "../../include/utils.h" -#include "../../include/color_conversion.h" -#include <stdlib.h> -#include <stdio.h> -#include <unistd.h> -#include <assert.h> -#include <libavutil/hwcontext.h> -#include <libavutil/hwcontext_vaapi.h> -#include <libavutil/frame.h> -#include <libavcodec/avcodec.h> -#include <va/va.h> -#include <va/va_drmcommon.h> - -#define MAX_CONNECTOR_IDS 32 - -typedef struct { - uint32_t connector_ids[MAX_CONNECTOR_IDS]; - int num_connector_ids; -} MonitorId; - -typedef struct { - gsr_capture_kms_vaapi_params params; - XEvent xev; - - bool should_stop; - bool stop_is_error; - bool created_hw_frame; - - gsr_kms_client kms_client; - gsr_kms_response kms_response; - gsr_kms_response_fd wayland_kms_data; - bool using_wayland_capture; - - vec2i capture_pos; - vec2i capture_size; - MonitorId monitor_id; - - VADisplay va_dpy; - VADRMPRIMESurfaceDescriptor prime; - - unsigned int input_texture; - unsigned int target_textures[2]; - unsigned int cursor_texture; - - gsr_color_conversion color_conversion; -} gsr_capture_kms_vaapi; - -static int max_int(int a, int b) { - return a > b ? a : b; -} - -static void gsr_capture_kms_vaapi_stop(gsr_capture *cap, AVCodecContext *video_codec_context); - -static bool drm_create_codec_context(gsr_capture_kms_vaapi *cap_kms, AVCodecContext *video_codec_context) { - char render_path[128]; - if(!gsr_card_path_get_render_path(cap_kms->params.card_path, render_path)) { - fprintf(stderr, "gsr error: failed to get /dev/dri/renderDXXX file from %s\n", cap_kms->params.card_path); - return false; - } - - AVBufferRef *device_ctx; - if(av_hwdevice_ctx_create(&device_ctx, AV_HWDEVICE_TYPE_VAAPI, render_path, NULL, 0) < 0) { - fprintf(stderr, "Error: Failed to create hardware device context\n"); - return false; - } - - AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx); - if(!frame_context) { - fprintf(stderr, "Error: Failed to create hwframe context\n"); - av_buffer_unref(&device_ctx); - return false; - } - - AVHWFramesContext *hw_frame_context = - (AVHWFramesContext *)frame_context->data; - hw_frame_context->width = video_codec_context->width; - hw_frame_context->height = video_codec_context->height; - hw_frame_context->sw_format = AV_PIX_FMT_NV12;//AV_PIX_FMT_0RGB32;//AV_PIX_FMT_YUV420P;//AV_PIX_FMT_0RGB32;//AV_PIX_FMT_NV12; - hw_frame_context->format = video_codec_context->pix_fmt; - hw_frame_context->device_ref = device_ctx; - hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data; - - hw_frame_context->initial_pool_size = 1; // TODO: (and in other places) - - AVVAAPIDeviceContext *vactx =((AVHWDeviceContext*)device_ctx->data)->hwctx; - cap_kms->va_dpy = vactx->display; - - if (av_hwframe_ctx_init(frame_context) < 0) { - fprintf(stderr, "Error: Failed to initialize hardware frame context " - "(note: ffmpeg version needs to be > 4.0)\n"); - av_buffer_unref(&device_ctx); - //av_buffer_unref(&frame_context); - return false; - } - - video_codec_context->hw_device_ctx = av_buffer_ref(device_ctx); - video_codec_context->hw_frames_ctx = av_buffer_ref(frame_context); - return true; -} - -#define DRM_FORMAT_MOD_INVALID 72057594037927935 - -// TODO: On monitor reconfiguration, find monitor x, y, width and height again. Do the same for nvfbc. - -typedef struct { - gsr_capture_kms_vaapi *cap_kms; - const char *monitor_to_capture; - int monitor_to_capture_len; - int num_monitors; -} MonitorCallbackUserdata; - -static void monitor_callback(const gsr_monitor *monitor, void *userdata) { - (void)monitor; - MonitorCallbackUserdata *monitor_callback_userdata = userdata; - ++monitor_callback_userdata->num_monitors; - - if(monitor_callback_userdata->monitor_to_capture_len != monitor->name_len || memcmp(monitor_callback_userdata->monitor_to_capture, monitor->name, monitor->name_len) != 0) - return; - - if(monitor_callback_userdata->cap_kms->monitor_id.num_connector_ids < MAX_CONNECTOR_IDS) { - monitor_callback_userdata->cap_kms->monitor_id.connector_ids[monitor_callback_userdata->cap_kms->monitor_id.num_connector_ids] = monitor->connector_id; - ++monitor_callback_userdata->cap_kms->monitor_id.num_connector_ids; - } - - if(monitor_callback_userdata->cap_kms->monitor_id.num_connector_ids == MAX_CONNECTOR_IDS) - fprintf(stderr, "gsr warning: reached max connector ids\n"); -} - -static int gsr_capture_kms_vaapi_start(gsr_capture *cap, AVCodecContext *video_codec_context) { - gsr_capture_kms_vaapi *cap_kms = cap->priv; - - gsr_monitor monitor; - cap_kms->monitor_id.num_connector_ids = 0; - if(gsr_egl_start_capture(cap_kms->params.egl, cap_kms->params.display_to_capture)) { - if(!get_monitor_by_name(cap_kms->params.egl, GSR_CONNECTION_WAYLAND, cap_kms->params.display_to_capture, &monitor)) { - fprintf(stderr, "gsr error: gsr_capture_kms_cuda_start: failed to find monitor by name \"%s\"\n", cap_kms->params.display_to_capture); - gsr_capture_kms_vaapi_stop(cap, video_codec_context); - return -1; - } - cap_kms->using_wayland_capture = true; - } else { - int kms_init_res = gsr_kms_client_init(&cap_kms->kms_client, cap_kms->params.card_path); - if(kms_init_res != 0) { - gsr_capture_kms_vaapi_stop(cap, video_codec_context); - return kms_init_res; - } - - MonitorCallbackUserdata monitor_callback_userdata = { - cap_kms, - cap_kms->params.display_to_capture, strlen(cap_kms->params.display_to_capture), - 0, - }; - for_each_active_monitor_output((void*)cap_kms->params.card_path, GSR_CONNECTION_DRM, monitor_callback, &monitor_callback_userdata); - - if(!get_monitor_by_name((void*)cap_kms->params.card_path, GSR_CONNECTION_DRM, cap_kms->params.display_to_capture, &monitor)) { - fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_start: failed to find monitor by name \"%s\"\n", cap_kms->params.display_to_capture); - gsr_capture_kms_vaapi_stop(cap, video_codec_context); - return -1; - } - } - - cap_kms->capture_pos = monitor.pos; - cap_kms->capture_size = monitor.size; - - /* Disable vsync */ - cap_kms->params.egl->eglSwapInterval(cap_kms->params.egl->egl_display, 0); - - video_codec_context->width = max_int(2, even_number_ceil(cap_kms->capture_size.x)); - video_codec_context->height = max_int(2, even_number_ceil(cap_kms->capture_size.y)); - - if(!drm_create_codec_context(cap_kms, video_codec_context)) { - gsr_capture_kms_vaapi_stop(cap, video_codec_context); - return -1; - } - - return 0; -} - -static uint32_t fourcc(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { - return (d << 24) | (c << 16) | (b << 8) | a; -} - -#define FOURCC_NV12 842094158 - -static void gsr_capture_kms_vaapi_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) { - gsr_capture_kms_vaapi *cap_kms = cap->priv; - - // TODO: - cap_kms->params.egl->glClear(GL_COLOR_BUFFER_BIT); - - if(!cap_kms->created_hw_frame) { - cap_kms->created_hw_frame = true; - - av_frame_free(frame); - *frame = av_frame_alloc(); - if(!frame) { - fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: failed to allocate frame\n"); - cap_kms->should_stop = true; - cap_kms->stop_is_error = true; - return; - } - (*frame)->format = video_codec_context->pix_fmt; - (*frame)->width = video_codec_context->width; - (*frame)->height = video_codec_context->height; - (*frame)->color_range = video_codec_context->color_range; - (*frame)->color_primaries = video_codec_context->color_primaries; - (*frame)->color_trc = video_codec_context->color_trc; - (*frame)->colorspace = video_codec_context->colorspace; - (*frame)->chroma_location = video_codec_context->chroma_sample_location; - - int res = av_hwframe_get_buffer(video_codec_context->hw_frames_ctx, *frame, 0); - if(res < 0) { - fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: av_hwframe_get_buffer failed: %d\n", res); - cap_kms->should_stop = true; - cap_kms->stop_is_error = true; - return; - } - - VASurfaceID target_surface_id = (uintptr_t)(*frame)->data[3]; - - VAStatus va_status = vaExportSurfaceHandle(cap_kms->va_dpy, target_surface_id, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, VA_EXPORT_SURFACE_READ_WRITE | VA_EXPORT_SURFACE_SEPARATE_LAYERS, &cap_kms->prime); - if(va_status != VA_STATUS_SUCCESS) { - fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: vaExportSurfaceHandle failed, error: %d\n", va_status); - cap_kms->should_stop = true; - cap_kms->stop_is_error = true; - return; - } - vaSyncSurface(cap_kms->va_dpy, target_surface_id); - - cap_kms->params.egl->glGenTextures(1, &cap_kms->input_texture); - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, cap_kms->input_texture); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - - cap_kms->params.egl->glGenTextures(1, &cap_kms->cursor_texture); - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, cap_kms->cursor_texture); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - - if(cap_kms->prime.fourcc == FOURCC_NV12) { - cap_kms->params.egl->glGenTextures(2, cap_kms->target_textures); - for(int i = 0; i < 2; ++i) { - const uint32_t formats[2] = { fourcc('R', '8', ' ', ' '), fourcc('G', 'R', '8', '8') }; - const int layer = i; - const int plane = 0; - - const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size - //const uint64_t modifier = cap_kms->prime.objects[cap_kms->prime.layers[layer].object_index[plane]].drm_format_modifier; - - const intptr_t img_attr[] = { - EGL_LINUX_DRM_FOURCC_EXT, formats[i], - EGL_WIDTH, cap_kms->prime.width / div[i], - EGL_HEIGHT, cap_kms->prime.height / div[i], - EGL_DMA_BUF_PLANE0_FD_EXT, cap_kms->prime.objects[cap_kms->prime.layers[layer].object_index[plane]].fd, - EGL_DMA_BUF_PLANE0_OFFSET_EXT, cap_kms->prime.layers[layer].offset[plane], - EGL_DMA_BUF_PLANE0_PITCH_EXT, cap_kms->prime.layers[layer].pitch[plane], - // TODO: - //EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, modifier & 0xFFFFFFFFULL, - //EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, modifier >> 32ULL, - EGL_NONE - }; - - while(cap_kms->params.egl->eglGetError() != EGL_SUCCESS){} - EGLImage image = cap_kms->params.egl->eglCreateImage(cap_kms->params.egl->egl_display, 0, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr); - if(!image) { - fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: failed to create egl image from drm fd for output drm fd, error: %d\n", cap_kms->params.egl->eglGetError()); - cap_kms->should_stop = true; - cap_kms->stop_is_error = true; - return; - } - - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, cap_kms->target_textures[i]); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - while(cap_kms->params.egl->glGetError()) {} - while(cap_kms->params.egl->eglGetError() != EGL_SUCCESS){} - cap_kms->params.egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); - if(cap_kms->params.egl->glGetError() != 0 || cap_kms->params.egl->eglGetError() != EGL_SUCCESS) { - // TODO: Get the error properly - fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: failed to bind egl image to gl texture, error: %d\n", cap_kms->params.egl->eglGetError()); - cap_kms->should_stop = true; - cap_kms->stop_is_error = true; - cap_kms->params.egl->eglDestroyImage(cap_kms->params.egl->egl_display, image); - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - return; - } - - cap_kms->params.egl->eglDestroyImage(cap_kms->params.egl->egl_display, image); - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - } - - gsr_color_conversion_params color_conversion_params = {0}; - color_conversion_params.egl = cap_kms->params.egl; - color_conversion_params.source_color = GSR_SOURCE_COLOR_RGB; - color_conversion_params.destination_color = GSR_DESTINATION_COLOR_NV12; - - color_conversion_params.destination_textures[0] = cap_kms->target_textures[0]; - color_conversion_params.destination_textures[1] = cap_kms->target_textures[1]; - color_conversion_params.num_destination_textures = 2; - - if(gsr_color_conversion_init(&cap_kms->color_conversion, &color_conversion_params) != 0) { - fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: failed to create color conversion\n"); - cap_kms->should_stop = true; - cap_kms->stop_is_error = true; - return; - } - } else { - fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: unexpected fourcc %u for output drm fd, expected nv12\n", cap_kms->prime.fourcc); - cap_kms->should_stop = true; - cap_kms->stop_is_error = true; - return; - } - } -} - -static bool gsr_capture_kms_vaapi_should_stop(gsr_capture *cap, bool *err) { - gsr_capture_kms_vaapi *cap_kms = cap->priv; - if(cap_kms->should_stop) { - if(err) - *err = cap_kms->stop_is_error; - return true; - } - - if(err) - *err = false; - return false; -} - -/* Prefer non combined planes */ -static gsr_kms_response_fd* find_drm_by_connector_id(gsr_kms_response *kms_response, uint32_t connector_id) { - int index_combined = -1; - for(int i = 0; i < kms_response->num_fds; ++i) { - if(kms_response->fds[i].connector_id == connector_id && !kms_response->fds[i].is_cursor) { - if(kms_response->fds[i].is_combined_plane) - index_combined = i; - else - return &kms_response->fds[i]; - } - } - - if(index_combined != -1) - return &kms_response->fds[index_combined]; - else - return NULL; -} - -static gsr_kms_response_fd* find_first_combined_drm(gsr_kms_response *kms_response) { - for(int i = 0; i < kms_response->num_fds; ++i) { - if(kms_response->fds[i].is_combined_plane && !kms_response->fds[i].is_cursor) - return &kms_response->fds[i]; - } - return NULL; -} - -static gsr_kms_response_fd* find_largest_drm(gsr_kms_response *kms_response) { - if(kms_response->num_fds == 0) - return NULL; - - int64_t largest_size = 0; - gsr_kms_response_fd *largest_drm = &kms_response->fds[0]; - for(int i = 0; i < kms_response->num_fds; ++i) { - const int64_t size = (int64_t)kms_response->fds[i].width * (int64_t)kms_response->fds[i].height; - if(size > largest_size && !kms_response->fds[i].is_cursor) { - largest_size = size; - largest_drm = &kms_response->fds[i]; - } - } - return largest_drm; -} - -static gsr_kms_response_fd* find_cursor_drm(gsr_kms_response *kms_response) { - for(int i = 0; i < kms_response->num_fds; ++i) { - if(kms_response->fds[i].is_cursor) - return &kms_response->fds[i]; - } - return NULL; -} - -static int gsr_capture_kms_vaapi_capture(gsr_capture *cap, AVFrame *frame) { - (void)frame; - gsr_capture_kms_vaapi *cap_kms = cap->priv; - - for(int i = 0; i < cap_kms->kms_response.num_fds; ++i) { - if(cap_kms->kms_response.fds[i].fd > 0) - close(cap_kms->kms_response.fds[i].fd); - cap_kms->kms_response.fds[i].fd = 0; - } - cap_kms->kms_response.num_fds = 0; - - gsr_kms_response_fd *drm_fd = NULL; - gsr_kms_response_fd *cursor_drm_fd = NULL; - bool capture_is_combined_plane = false; - if(cap_kms->using_wayland_capture) { - gsr_egl_update(cap_kms->params.egl); - cap_kms->wayland_kms_data.fd = cap_kms->params.egl->fd; - cap_kms->wayland_kms_data.width = cap_kms->params.egl->width; - cap_kms->wayland_kms_data.height = cap_kms->params.egl->height; - cap_kms->wayland_kms_data.pitch = cap_kms->params.egl->pitch; - cap_kms->wayland_kms_data.offset = cap_kms->params.egl->offset; - cap_kms->wayland_kms_data.pixel_format = cap_kms->params.egl->pixel_format; - cap_kms->wayland_kms_data.modifier = cap_kms->params.egl->modifier; - cap_kms->wayland_kms_data.connector_id = 0; - cap_kms->wayland_kms_data.is_combined_plane = false; - cap_kms->wayland_kms_data.is_cursor = false; - cap_kms->wayland_kms_data.x = cap_kms->wayland_kms_data.x; // TODO: Use these - cap_kms->wayland_kms_data.y = cap_kms->wayland_kms_data.y; - cap_kms->wayland_kms_data.src_w = cap_kms->wayland_kms_data.width; - cap_kms->wayland_kms_data.src_h = cap_kms->wayland_kms_data.height; - - cap_kms->capture_pos.x = cap_kms->wayland_kms_data.x; - cap_kms->capture_pos.y = cap_kms->wayland_kms_data.y; - - if(cap_kms->wayland_kms_data.fd <= 0) - return -1; - - drm_fd = &cap_kms->wayland_kms_data; - } else { - if(gsr_kms_client_get_kms(&cap_kms->kms_client, &cap_kms->kms_response) != 0) { - fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_capture: failed to get kms, error: %d (%s)\n", cap_kms->kms_response.result, cap_kms->kms_response.err_msg); - return -1; - } - - if(cap_kms->kms_response.num_fds == 0) { - static bool error_shown = false; - if(!error_shown) { - error_shown = true; - fprintf(stderr, "gsr error: no drm found, capture will fail\n"); - } - return -1; - } - - for(int i = 0; i < cap_kms->monitor_id.num_connector_ids; ++i) { - drm_fd = find_drm_by_connector_id(&cap_kms->kms_response, cap_kms->monitor_id.connector_ids[i]); - if(drm_fd) - break; - } - - // Will never happen on wayland unless the target monitor has been disconnected - if(!drm_fd) { - drm_fd = find_first_combined_drm(&cap_kms->kms_response); - if(!drm_fd) - drm_fd = find_largest_drm(&cap_kms->kms_response); - capture_is_combined_plane = true; - } - - cursor_drm_fd = find_cursor_drm(&cap_kms->kms_response); - } - - if(!drm_fd) - return -1; - - if(!capture_is_combined_plane && cursor_drm_fd && cursor_drm_fd->connector_id != drm_fd->connector_id) - cursor_drm_fd = NULL; - - // TODO: This causes a crash sometimes on steam deck, why? is it a driver bug? a vaapi pure version doesn't cause a crash. - // Even ffmpeg kmsgrab causes this crash. The error is: - // amdgpu: Failed to allocate a buffer: - // amdgpu: size : 28508160 bytes - // amdgpu: alignment : 2097152 bytes - // amdgpu: domains : 4 - // amdgpu: flags : 4 - // amdgpu: Failed to allocate a buffer: - // amdgpu: size : 28508160 bytes - // amdgpu: alignment : 2097152 bytes - // amdgpu: domains : 4 - // amdgpu: flags : 4 - // EE ../jupiter-mesa/src/gallium/drivers/radeonsi/radeon_vcn_enc.c:516 radeon_create_encoder UVD - Can't create CPB buffer. - // [hevc_vaapi @ 0x55ea72b09840] Failed to upload encode parameters: 2 (resource allocation failed). - // [hevc_vaapi @ 0x55ea72b09840] Encode failed: -5. - // Error: avcodec_send_frame failed, error: Input/output error - // 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 - const intptr_t img_attr[] = { - 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, - // TODO: - //EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, drm_fd->modifier & 0xFFFFFFFFULL, - //EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, drm_fd->modifier >> 32ULL, - EGL_NONE - }; - - EGLImage image = cap_kms->params.egl->eglCreateImage(cap_kms->params.egl->egl_display, 0, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr); - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, cap_kms->input_texture); - cap_kms->params.egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); - cap_kms->params.egl->eglDestroyImage(cap_kms->params.egl->egl_display, image); - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - - vec2i capture_pos = cap_kms->capture_pos; - - if(cap_kms->using_wayland_capture) { - gsr_color_conversion_draw(&cap_kms->color_conversion, cap_kms->input_texture, - (vec2i){0, 0}, cap_kms->capture_size, - (vec2i){0, 0}, cap_kms->capture_size, - 0.0f); - } else { - if(!capture_is_combined_plane) - capture_pos = (vec2i){drm_fd->x, drm_fd->y}; - - float texture_rotation = 0.0f; - - gsr_color_conversion_draw(&cap_kms->color_conversion, cap_kms->input_texture, - (vec2i){0, 0}, cap_kms->capture_size, - capture_pos, cap_kms->capture_size, - texture_rotation); - - if(cursor_drm_fd) { - 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_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 = cap_kms->params.egl->eglCreateImage(cap_kms->params.egl->egl_display, 0, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr_cursor); - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, cap_kms->cursor_texture); - cap_kms->params.egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, cursor_image); - cap_kms->params.egl->eglDestroyImage(cap_kms->params.egl->egl_display, cursor_image); - cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - - vec2i cursor_size = {cursor_drm_fd->width, cursor_drm_fd->height}; - gsr_color_conversion_draw(&cap_kms->color_conversion, cap_kms->cursor_texture, - (vec2i){cursor_drm_fd->x, cursor_drm_fd->y}, cursor_size, - (vec2i){0, 0}, cursor_size, - 0.0f); - } - } - - cap_kms->params.egl->eglSwapBuffers(cap_kms->params.egl->egl_display, cap_kms->params.egl->egl_surface); - - return 0; -} - -static void gsr_capture_kms_vaapi_capture_end(gsr_capture *cap, AVFrame *frame) { - (void)frame; - gsr_capture_kms_vaapi *cap_kms = cap->priv; - - gsr_egl_cleanup_frame(cap_kms->params.egl); - - for(int i = 0; i < cap_kms->kms_response.num_fds; ++i) { - if(cap_kms->kms_response.fds[i].fd > 0) - close(cap_kms->kms_response.fds[i].fd); - cap_kms->kms_response.fds[i].fd = 0; - } - cap_kms->kms_response.num_fds = 0; -} - -static void gsr_capture_kms_vaapi_stop(gsr_capture *cap, AVCodecContext *video_codec_context) { - gsr_capture_kms_vaapi *cap_kms = cap->priv; - - gsr_color_conversion_deinit(&cap_kms->color_conversion); - - for(uint32_t i = 0; i < cap_kms->prime.num_objects; ++i) { - if(cap_kms->prime.objects[i].fd > 0) { - close(cap_kms->prime.objects[i].fd); - cap_kms->prime.objects[i].fd = 0; - } - } - - if(cap_kms->params.egl->egl_context) { - if(cap_kms->input_texture) { - cap_kms->params.egl->glDeleteTextures(1, &cap_kms->input_texture); - cap_kms->input_texture = 0; - } - - if(cap_kms->cursor_texture) { - cap_kms->params.egl->glDeleteTextures(1, &cap_kms->cursor_texture); - cap_kms->cursor_texture = 0; - } - - cap_kms->params.egl->glDeleteTextures(2, cap_kms->target_textures); - cap_kms->target_textures[0] = 0; - cap_kms->target_textures[1] = 0; - } - - for(int i = 0; i < cap_kms->kms_response.num_fds; ++i) { - if(cap_kms->kms_response.fds[i].fd > 0) - close(cap_kms->kms_response.fds[i].fd); - cap_kms->kms_response.fds[i].fd = 0; - } - cap_kms->kms_response.num_fds = 0; - - if(video_codec_context->hw_device_ctx) - av_buffer_unref(&video_codec_context->hw_device_ctx); - if(video_codec_context->hw_frames_ctx) - av_buffer_unref(&video_codec_context->hw_frames_ctx); - - gsr_kms_client_deinit(&cap_kms->kms_client); -} - -static void gsr_capture_kms_vaapi_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) { - (void)video_codec_context; - gsr_capture_kms_vaapi *cap_kms = cap->priv; - if(cap->priv) { - gsr_capture_kms_vaapi_stop(cap, video_codec_context); - free((void*)cap_kms->params.display_to_capture); - cap_kms->params.display_to_capture = NULL; - free(cap->priv); - cap->priv = NULL; - } - free(cap); -} - -gsr_capture* gsr_capture_kms_vaapi_create(const gsr_capture_kms_vaapi_params *params) { - if(!params) { - fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_create params is NULL\n"); - return NULL; - } - - gsr_capture *cap = calloc(1, sizeof(gsr_capture)); - if(!cap) - return NULL; - - gsr_capture_kms_vaapi *cap_kms = calloc(1, sizeof(gsr_capture_kms_vaapi)); - if(!cap_kms) { - free(cap); - return NULL; - } - - const char *display_to_capture = strdup(params->display_to_capture); - if(!display_to_capture) { - /* TODO XCloseDisplay */ - free(cap); - free(cap_kms); - return NULL; - } - - cap_kms->params = *params; - cap_kms->params.display_to_capture = display_to_capture; - - *cap = (gsr_capture) { - .start = gsr_capture_kms_vaapi_start, - .tick = gsr_capture_kms_vaapi_tick, - .should_stop = gsr_capture_kms_vaapi_should_stop, - .capture = gsr_capture_kms_vaapi_capture, - .capture_end = gsr_capture_kms_vaapi_capture_end, - .destroy = gsr_capture_kms_vaapi_destroy, - .priv = cap_kms - }; - - return cap; -} diff --git a/src/capture/nvfbc.c b/src/capture/nvfbc.c index 5b62310..b92bd41 100644 --- a/src/capture/nvfbc.c +++ b/src/capture/nvfbc.c @@ -1,16 +1,18 @@ #include "../../include/capture/nvfbc.h" #include "../../external/NvFBC.h" -#include "../../include/cuda.h" +#include "../../include/egl.h" +#include "../../include/utils.h" +#include "../../include/color_conversion.h" +#include "../../include/window/window.h" + #include <dlfcn.h> #include <stdlib.h> #include <string.h> #include <stdio.h> +#include <math.h> +#include <assert.h> + #include <X11/Xlib.h> -#include <libavutil/hwcontext.h> -#include <libavutil/hwcontext_cuda.h> -#include <libavutil/frame.h> -#include <libavutil/version.h> -#include <libavcodec/avcodec.h> typedef struct { gsr_capture_nvfbc_params params; @@ -22,16 +24,16 @@ typedef struct { bool fbc_handle_created; bool capture_session_created; - gsr_cuda cuda; - bool frame_initialized; -} gsr_capture_nvfbc; + NVFBC_TOGL_SETUP_PARAMS setup_params; -#if defined(_WIN64) || defined(__LP64__) -typedef unsigned long long CUdeviceptr_v2; -#else -typedef unsigned int CUdeviceptr_v2; -#endif -typedef CUdeviceptr_v2 CUdeviceptr; + bool supports_direct_cursor; + uint32_t width, height; + NVFBC_TRACKING_TYPE tracking_type; + uint32_t output_id; + uint32_t tracking_width, tracking_height; + bool nvfbc_needs_recreate; + double nvfbc_dead_start; +} gsr_capture_nvfbc; static int max_int(int a, int b) { return a > b ? a : b; @@ -100,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); @@ -109,157 +111,84 @@ 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; } -#if LIBAVUTIL_VERSION_MAJOR < 57 -static AVBufferRef* dummy_hw_frame_init(int size) { - return av_buffer_alloc(size); -} -#else -static AVBufferRef* dummy_hw_frame_init(size_t size) { - return av_buffer_alloc(size); -} -#endif - -static bool ffmpeg_create_cuda_contexts(gsr_capture_nvfbc *cap_nvfbc, AVCodecContext *video_codec_context) { - AVBufferRef *device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA); - if(!device_ctx) { - fprintf(stderr, "gsr error: cuda_create_codec_context failed: failed to create hardware device context\n"); - return false; - } - - AVHWDeviceContext *hw_device_context = (AVHWDeviceContext*)device_ctx->data; - AVCUDADeviceContext *cuda_device_context = (AVCUDADeviceContext*)hw_device_context->hwctx; - cuda_device_context->cuda_ctx = cap_nvfbc->cuda.cu_ctx; - if(av_hwdevice_ctx_init(device_ctx) < 0) { - fprintf(stderr, "gsr error: cuda_create_codec_context failed: failed to create hardware device context\n"); - av_buffer_unref(&device_ctx); - return false; - } - - AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx); - if(!frame_context) { - fprintf(stderr, "gsr error: cuda_create_codec_context failed: failed to create hwframe context\n"); - av_buffer_unref(&device_ctx); - return false; - } - - AVHWFramesContext *hw_frame_context = (AVHWFramesContext*)frame_context->data; - hw_frame_context->width = video_codec_context->width; - hw_frame_context->height = video_codec_context->height; - hw_frame_context->sw_format = AV_PIX_FMT_BGR0; - hw_frame_context->format = video_codec_context->pix_fmt; - hw_frame_context->device_ref = device_ctx; - hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data; - - hw_frame_context->pool = av_buffer_pool_init(1, dummy_hw_frame_init); - hw_frame_context->initial_pool_size = 1; - - if (av_hwframe_ctx_init(frame_context) < 0) { - fprintf(stderr, "gsr error: cuda_create_codec_context failed: failed to initialize hardware frame context " - "(note: ffmpeg version needs to be > 4.0)\n"); - av_buffer_unref(&device_ctx); - //av_buffer_unref(&frame_context); - return false; +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; + self->nv_fbc_function_list.nvFBCDestroyCaptureSession(self->nv_fbc_handle, &destroy_capture_params); + self->capture_session_created = false; } - - video_codec_context->hw_device_ctx = av_buffer_ref(device_ctx); - video_codec_context->hw_frames_ctx = av_buffer_ref(frame_context); - return true; } -static int gsr_capture_nvfbc_start(gsr_capture *cap, AVCodecContext *video_codec_context) { - gsr_capture_nvfbc *cap_nvfbc = cap->priv; - if(!gsr_cuda_load(&cap_nvfbc->cuda, cap_nvfbc->params.dpy, cap_nvfbc->params.overclock)) - return -1; - - if(!gsr_capture_nvfbc_load_library(cap)) { - gsr_cuda_unload(&cap_nvfbc->cuda); - return -1; +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; + self->nv_fbc_function_list.nvFBCDestroyHandle(self->nv_fbc_handle, &destroy_params); + self->fbc_handle_created = false; + self->nv_fbc_handle = 0; } +} - const uint32_t x = max_int(cap_nvfbc->params.pos.x, 0); - const uint32_t y = max_int(cap_nvfbc->params.pos.y, 0); - const uint32_t width = max_int(cap_nvfbc->params.size.x, 0); - const uint32_t height = max_int(cap_nvfbc->params.size.y, 0); - - const bool capture_region = (x > 0 || y > 0 || width > 0 || height > 0); - - bool supports_direct_cursor = false; - bool direct_capture = cap_nvfbc->params.direct_capture; - int driver_major_version = 0; - int driver_minor_version = 0; - if(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)) { - 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"); - } - - // TODO: - // Cursor capture disabled because moving the cursor doesn't update capture rate to monitor hz and instead captures at 10-30 hz - /* - if(direct_capture) { - if(version_at_least(driver_major_version, driver_minor_version, 515, 57)) - 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"); - } - */ - } +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 *self) { NVFBCSTATUS status; - NVFBC_TRACKING_TYPE tracking_type; - uint32_t output_id = 0; - cap_nvfbc->fbc_handle_created = false; - cap_nvfbc->capture_session_created = false; 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 = 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; } @@ -268,10 +197,13 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap, AVCodecContext *video_codec goto error_cleanup; } - uint32_t tracking_width = XWidthOfScreen(DefaultScreenOfDisplay(cap_nvfbc->params.dpy)); - uint32_t tracking_height = XHeightOfScreen(DefaultScreenOfDisplay(cap_nvfbc->params.dpy)); - tracking_type = strcmp(cap_nvfbc->params.display_to_capture, "screen") == 0 ? NVFBC_TRACKING_SCREEN : NVFBC_TRACKING_OUTPUT; - if(tracking_type == NVFBC_TRACKING_OUTPUT) { + assert(gsr_window_get_display_server(self->params.egl->window) == GSR_DISPLAY_SERVER_X11); + Display *display = gsr_window_get_display(self->params.egl->window); + + self->tracking_width = XWidthOfScreen(DefaultScreenOfDisplay(display)); + self->tracking_height = XHeightOfScreen(DefaultScreenOfDisplay(display)); + 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; @@ -282,176 +214,201 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap, AVCodecContext *video_codec goto error_cleanup; } - output_id = get_output_id_from_display_name(status_params.outputs, status_params.dwOutputNum, cap_nvfbc->params.display_to_capture, &tracking_width, &tracking_height); - if(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; } } + self->width = self->tracking_width; + self->height = self->tracking_height; + return 0; + + error_cleanup: + gsr_capture_nvfbc_destroy_session_and_handle(self); + return -1; +} + +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_SHARED_CUDA; - create_capture_params.bWithCursor = (!direct_capture || supports_direct_cursor) ? NVFBC_TRUE : NVFBC_FALSE; - if(capture_region) - create_capture_params.captureBox = (NVFBC_BOX){ x, y, width, height }; - create_capture_params.eTrackingType = tracking_type; - create_capture_params.dwSamplingRateMs = 1000u / ((uint32_t)cap_nvfbc->params.fps + 1); - create_capture_params.bAllowDirectCapture = direct_capture ? NVFBC_TRUE : NVFBC_FALSE; - create_capture_params.bPushModel = direct_capture ? NVFBC_TRUE : NVFBC_FALSE; - //create_capture_params.bDisableAutoModesetRecovery = true; // TODO: - if(tracking_type == NVFBC_TRACKING_OUTPUT) - create_capture_params.dwOutputId = output_id; - - status = cap_nvfbc->nv_fbc_function_list.nvFBCCreateCaptureSession(cap_nvfbc->nv_fbc_handle, &create_capture_params); + create_capture_params.eCaptureType = NVFBC_CAPTURE_TO_GL; + 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; + 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(self->tracking_type == NVFBC_TRACKING_OUTPUT) + create_capture_params.dwOutputId = self->output_id; + + 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)); - goto error_cleanup; + 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; - NVFBC_TOCUDA_SETUP_PARAMS setup_params; - memset(&setup_params, 0, sizeof(setup_params)); - setup_params.dwVersion = NVFBC_TOCUDA_SETUP_PARAMS_VER; - 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.nvFBCToCudaSetUp(cap_nvfbc->nv_fbc_handle, &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)); - goto error_cleanup; + 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; } - if(capture_region) { - video_codec_context->width = width & ~1; - video_codec_context->height = height & ~1; - } else { - video_codec_context->width = tracking_width & ~1; - video_codec_context->height = tracking_height & ~1; + return 0; +} + +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(self->params.display_to_capture) { + free((void*)self->params.display_to_capture); + self->params.display_to_capture = NULL; } +} - if(!ffmpeg_create_cuda_contexts(cap_nvfbc, video_codec_context)) - goto error_cleanup; +static int gsr_capture_nvfbc_start(gsr_capture *cap, gsr_capture_metadata *capture_metadata) { + gsr_capture_nvfbc *self = cap->priv; - return 0; + if(!gsr_capture_nvfbc_load_library(cap)) + return -1; - error_cleanup: - if(cap_nvfbc->fbc_handle_created) { - if(cap_nvfbc->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->supports_direct_cursor = false; + int driver_major_version = 0; + int driver_minor_version = 0; + 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)) { + 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"); } - 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; + // TODO: + // Cursor capture disabled because moving the cursor doesn't update capture rate to monitor hz and instead captures at 10-30 hz + /* + if(direct_capture) { + if(version_at_least(driver_major_version, driver_minor_version, 515, 57)) + 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(video_codec_context->hw_device_ctx) - av_buffer_unref(&video_codec_context->hw_device_ctx); - if(video_codec_context->hw_frames_ctx) - av_buffer_unref(&video_codec_context->hw_frames_ctx); - - gsr_cuda_unload(&cap_nvfbc->cuda); - return -1; -} + if(gsr_capture_nvfbc_setup_handle(self) != 0) { + goto error_cleanup; + } -static void gsr_capture_nvfbc_destroy_session(gsr_capture *cap) { - gsr_capture_nvfbc *cap_nvfbc = cap->priv; + if(gsr_capture_nvfbc_setup_session(self) != 0) { + goto error_cleanup; + } - if(cap_nvfbc->fbc_handle_created) { - if(cap_nvfbc->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; - } + capture_metadata->width = self->tracking_width; + capture_metadata->height = self->tracking_height; - 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; + if(self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0) { + self->params.output_resolution = scale_keep_aspect_ratio((vec2i){capture_metadata->width, capture_metadata->height}, self->params.output_resolution); + capture_metadata->width = self->params.output_resolution.x; + capture_metadata->height = self->params.output_resolution.y; + } else if(self->params.region_size.x > 0 && self->params.region_size.y > 0) { + capture_metadata->width = self->params.region_size.x; + capture_metadata->height = self->params.region_size.y; } - cap_nvfbc->nv_fbc_handle = 0; + return 0; + + error_cleanup: + gsr_capture_nvfbc_stop(self); + return -1; } -static void gsr_capture_nvfbc_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) { - gsr_capture_nvfbc *cap_nvfbc = cap->priv; - if(!cap_nvfbc->frame_initialized && video_codec_context->hw_frames_ctx) { - cap_nvfbc->frame_initialized = true; - (*frame)->hw_frames_ctx = video_codec_context->hw_frames_ctx; - (*frame)->buf[0] = av_buffer_pool_get(((AVHWFramesContext*)video_codec_context->hw_frames_ctx->data)->pool); - (*frame)->extended_data = (*frame)->data; - (*frame)->color_range = video_codec_context->color_range; - (*frame)->color_primaries = video_codec_context->color_primaries; - (*frame)->color_trc = video_codec_context->color_trc; - (*frame)->colorspace = video_codec_context->colorspace; - (*frame)->chroma_location = video_codec_context->chroma_sample_location; +static int gsr_capture_nvfbc_capture(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion) { + gsr_capture_nvfbc *self = cap->priv; + + const double nvfbc_recreate_retry_time_seconds = 1.0; + if(self->nvfbc_needs_recreate) { + const double now = clock_get_monotonic_seconds(); + 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(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(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; + } + + self->nvfbc_needs_recreate = false; + } else { + return 0; + } } -} -static int gsr_capture_nvfbc_capture(gsr_capture *cap, AVFrame *frame) { - gsr_capture_nvfbc *cap_nvfbc = cap->priv; + vec2i frame_size = (vec2i){self->width, self->height}; + const vec2i original_frame_size = frame_size; + if(self->params.region_size.x > 0 && self->params.region_size.y > 0) + frame_size = self->params.region_size; + + 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); - CUdeviceptr cu_device_ptr = 0; + 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) }; NVFBC_FRAME_GRAB_INFO frame_info; memset(&frame_info, 0, sizeof(frame_info)); - NVFBC_TOCUDA_GRAB_FRAME_PARAMS grab_params; + NVFBC_TOGL_GRAB_FRAME_PARAMS grab_params; memset(&grab_params, 0, sizeof(grab_params)); - grab_params.dwVersion = NVFBC_TOCUDA_GRAB_FRAME_PARAMS_VER; - grab_params.dwFlags = NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT;/* | NVFBC_TOCUDA_GRAB_FLAGS_FORCE_REFRESH;*/ + grab_params.dwVersion = NVFBC_TOGL_GRAB_FRAME_PARAMS_VER; + grab_params.dwFlags = NVFBC_TOGL_GRAB_FLAGS_NOWAIT | NVFBC_TOGL_GRAB_FLAGS_FORCE_REFRESH; // TODO: Remove NVFBC_TOGL_GRAB_FLAGS_FORCE_REFRESH grab_params.pFrameGrabInfo = &frame_info; - grab_params.pCUDADeviceBuffer = &cu_device_ptr; grab_params.dwTimeoutMs = 0; - NVFBCSTATUS status = cap_nvfbc->nv_fbc_function_list.nvFBCToCudaGrabFrame(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\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle)); - return -1; + 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; } - /* - *byte_size = frame_info.dwByteSize; + //self->params.egl->glFlush(); + //self->params.egl->glFinish(); - TODO: Check bIsNewFrame - TODO: Check dwWidth and dwHeight and update size in video output in ffmpeg. This can happen when xrandr is used to change monitor resolution - */ + gsr_color_conversion_draw(color_conversion, self->setup_params.dwTextures[grab_params.dwTextureIndex], + target_pos, (vec2i){output_size.x, output_size.y}, + self->params.region_position, frame_size, original_frame_size, + GSR_ROT_0, GSR_SOURCE_COLOR_BGR, false, false); + + //self->params.egl->glFlush(); + //self->params.egl->glFinish(); - frame->data[0] = (uint8_t*)cu_device_ptr; - //frame->data[1] = (uint8_t*)cu_device_ptr; - //frame->data[2] = (uint8_t*)cu_device_ptr; - frame->linesize[0] = frame->width * 4; - // TODO: Use these when outputting yuv444 by changing nvfbc color to YUV444P and sw_format to YUV444P - //frame->linesize[1] = frame->width * 1; - //frame->linesize[2] = frame->width * 1; return 0; } -static void gsr_capture_nvfbc_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) { - gsr_capture_nvfbc *cap_nvfbc = cap->priv; - gsr_capture_nvfbc_destroy_session(cap); - if(video_codec_context->hw_device_ctx) - av_buffer_unref(&video_codec_context->hw_device_ctx); - if(video_codec_context->hw_frames_ctx) - av_buffer_unref(&video_codec_context->hw_frames_ctx); - if(cap_nvfbc) { - gsr_cuda_unload(&cap_nvfbc->cuda); - dlclose(cap_nvfbc->library); - free((void*)cap_nvfbc->params.display_to_capture); - cap_nvfbc->params.display_to_capture = NULL; - free(cap->priv); - cap->priv = NULL; - } +static void gsr_capture_nvfbc_destroy(gsr_capture *cap) { + gsr_capture_nvfbc *self = cap->priv; + gsr_capture_nvfbc_stop(self); + free(cap->priv); free(cap); } @@ -486,13 +443,13 @@ 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 = gsr_capture_nvfbc_tick, + .tick = NULL, .should_stop = NULL, .capture = gsr_capture_nvfbc_capture, - .capture_end = NULL, + .uses_external_image = NULL, .destroy = gsr_capture_nvfbc_destroy, .priv = cap_nvfbc }; diff --git a/src/capture/portal.c b/src/capture/portal.c new file mode 100644 index 0000000..a5e62af --- /dev/null +++ b/src/capture/portal.c @@ -0,0 +1,430 @@ +#include "../../include/capture/portal.h" +#include "../../include/color_conversion.h" +#include "../../include/egl.h" +#include "../../include/utils.h" +#include "../../dbus/client/dbus_client.h" +#include "../../include/pipewire_video.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <limits.h> +#include <assert.h> + +typedef struct { + gsr_capture_portal_params params; + + gsr_texture_map texture_map; + + gsr_dbus_client dbus_client; + char session_handle[128]; + + gsr_pipewire_video pipewire; + vec2i capture_size; + gsr_pipewire_video_dmabuf_data dmabuf_data[GSR_PIPEWIRE_VIDEO_DMABUF_MAX_PLANES]; + int num_dmabuf_data; +} gsr_capture_portal; + +static void gsr_capture_portal_cleanup_plane_fds(gsr_capture_portal *self) { + 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_dmabuf_data = 0; +} + +static void gsr_capture_portal_stop(gsr_capture_portal *self) { + if(self->texture_map.texture_id) { + self->params.egl->glDeleteTextures(1, &self->texture_map.texture_id); + self->texture_map.texture_id = 0; + } + + if(self->texture_map.external_texture_id) { + self->params.egl->glDeleteTextures(1, &self->texture_map.external_texture_id); + self->texture_map.external_texture_id = 0; + } + + if(self->texture_map.cursor_texture_id) { + self->params.egl->glDeleteTextures(1, &self->texture_map.cursor_texture_id); + self->texture_map.cursor_texture_id = 0; + } + + gsr_capture_portal_cleanup_plane_fds(self); + gsr_pipewire_video_deinit(&self->pipewire); + gsr_dbus_client_deinit(&self->dbus_client); +} + +static void gsr_capture_portal_create_input_textures(gsr_capture_portal *self) { + const float border_color[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + 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_BORDER); + self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + self->params.egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color); + 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_BORDER); + self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + self->params.egl->glTexParameterfv(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_BORDER_COLOR, border_color); + 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_BORDER); + self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + self->params.egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color); + 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_default_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 bool create_directory_to_file(const char *filepath) { + char dir[PATH_MAX]; + dir[0] = '\0'; + + const char *split = strrchr(filepath, '/'); + if(!split) /* Assuming it's the current directory (for example if filepath is "restore_token"), which doesn't need to be created */ + return true; + + snprintf(dir, sizeof(dir), "%.*s", (int)(split - filepath), filepath); + if(create_directory_recursive(dir) != 0) { + fprintf(stderr, "gsr warning: gsr_capture_portal_save_restore_token: failed to create directory (%s) for restore token\n", dir); + return false; + } + return true; +} + +static void gsr_capture_portal_save_restore_token(const char *restore_token, const char *portal_session_token_filepath) { + char restore_token_path[PATH_MAX]; + restore_token_path[0] = '\0'; + if(portal_session_token_filepath) + snprintf(restore_token_path, sizeof(restore_token_path), "%s", portal_session_token_filepath); + else + get_default_gpu_screen_recorder_restore_token_path(restore_token_path, sizeof(restore_token_path)); + + if(!create_directory_to_file(restore_token_path)) + return; + + 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, const char *portal_session_token_filepath) { + assert(buffer_size > 0); + buffer[0] = '\0'; + + char restore_token_path[PATH_MAX]; + restore_token_path[0] = '\0'; + if(portal_session_token_filepath) + snprintf(restore_token_path, sizeof(restore_token_path), "%s", portal_session_token_filepath); + else + get_default_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); + + 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 int gsr_capture_portal_setup_dbus(gsr_capture_portal *self, int *pipewire_fd, uint32_t *pipewire_node) { + *pipewire_fd = 0; + *pipewire_node = 0; + int response_status = 0; + + 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), self->params.portal_session_token_filepath); + + if(!gsr_dbus_client_init(&self->dbus_client, restore_token)) + return -1; + + fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: CreateSession\n"); + response_status = gsr_dbus_client_screencast_create_session(&self->dbus_client, self->session_handle, sizeof(self->session_handle)); + if(response_status != 0) { + fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: CreateSession failed\n"); + return response_status; + } + + fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: SelectSources\n"); + response_status = gsr_dbus_client_screencast_select_sources(&self->dbus_client, self->session_handle, GSR_PORTAL_CAPTURE_TYPE_ALL, self->params.record_cursor ? GSR_PORTAL_CURSOR_MODE_EMBEDDED : GSR_PORTAL_CURSOR_MODE_HIDDEN); + if(response_status != 0) { + fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: SelectSources failed\n"); + return response_status; + } + + fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: Start\n"); + response_status = gsr_dbus_client_screencast_start(&self->dbus_client, self->session_handle, pipewire_node); + if(response_status != 0) { + fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: Start failed\n"); + return response_status; + } + + const char *screencast_restore_token = gsr_dbus_client_screencast_get_restore_token(&self->dbus_client); + if(screencast_restore_token) + gsr_capture_portal_save_restore_token(screencast_restore_token, self->params.portal_session_token_filepath); + + fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: OpenPipeWireRemote\n"); + if(!gsr_dbus_client_screencast_open_pipewire_remote(&self->dbus_client, self->session_handle, pipewire_fd)) { + fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: OpenPipeWireRemote failed\n"); + return -1; + } + + fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: desktop portal setup finished\n"); + return 0; +} + +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; + 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, gsr_capture_metadata *capture_metadata) { + gsr_capture_portal *self = cap->priv; + + 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"); + 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; + } else { + return -1; + } + } + + 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_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); + return -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; + } + + if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) { + 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 = self->params.output_resolution.x; + capture_metadata->height = self->params.output_resolution.y; + } + + return 0; +} + +static int max_int(int a, int b) { + return a > b ? a : b; +} + +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); + } + } 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, 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(); + + // TODO: Handle region crop + + 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, self->capture_size, + GSR_ROT_0, GSR_SOURCE_COLOR_RGB, using_external_image, false); + + 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 + }; + + const vec2i cursor_pos = { + 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_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}, (vec2i){cursor_region.width, 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(); + + gsr_capture_portal_cleanup_plane_fds(self); + + return 0; +} + +static bool gsr_capture_portal_uses_external_image(gsr_capture *cap) { + (void)cap; + return true; +} + +static bool gsr_capture_portal_is_damaged(gsr_capture *cap) { + gsr_capture_portal *self = cap->priv; + 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_video_clear_damage(&self->pipewire); +} + +static void gsr_capture_portal_destroy(gsr_capture *cap) { + gsr_capture_portal *self = cap->priv; + if(cap->priv) { + gsr_capture_portal_stop(self); + 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 = NULL, + .capture = gsr_capture_portal_capture, + .uses_external_image = gsr_capture_portal_uses_external_image, + .is_damaged = gsr_capture_portal_is_damaged, + .clear_damage = gsr_capture_portal_clear_damage, + .destroy = gsr_capture_portal_destroy, + .priv = cap_portal + }; + + return cap; +} diff --git a/src/capture/xcomposite.c b/src/capture/xcomposite.c new file mode 100644 index 0000000..db41f63 --- /dev/null +++ b/src/capture/xcomposite.c @@ -0,0 +1,338 @@ +#include "../../include/capture/xcomposite.h" +#include "../../include/window_texture.h" +#include "../../include/utils.h" +#include "../../include/cursor.h" +#include "../../include/color_conversion.h" +#include "../../include/window/window.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include <X11/Xlib.h> + +typedef struct { + gsr_capture_xcomposite_params params; + Display *display; + + bool should_stop; + bool stop_is_error; + bool window_resized; + bool follow_focused_initialized; + bool init_new_window; + + Window window; + vec2i window_size; + vec2i texture_size; + double window_resize_timer; + + WindowTexture window_texture; + + Atom net_active_window_atom; + + gsr_cursor cursor; + + bool clear_background; +} gsr_capture_xcomposite; + +static void gsr_capture_xcomposite_stop(gsr_capture_xcomposite *self) { + window_texture_deinit(&self->window_texture); + gsr_cursor_deinit(&self->cursor); +} + +static int max_int(int a, int b) { + return a > b ? a : b; +} + +static Window get_focused_window(Display *display, Atom net_active_window_atom) { + Atom type; + int format = 0; + unsigned long num_items = 0; + unsigned long bytes_after = 0; + unsigned char *properties = NULL; + if(XGetWindowProperty(display, DefaultRootWindow(display), net_active_window_atom, 0, 1024, False, AnyPropertyType, &type, &format, &num_items, &bytes_after, &properties) == Success && properties) { + Window focused_window = *(unsigned long*)properties; + XFree(properties); + return focused_window; + } + return None; +} + +static int gsr_capture_xcomposite_start(gsr_capture *cap, gsr_capture_metadata *capture_metadata) { + gsr_capture_xcomposite *self = cap->priv; + + if(self->params.follow_focused) { + self->net_active_window_atom = XInternAtom(self->display, "_NET_ACTIVE_WINDOW", False); + if(!self->net_active_window_atom) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_start failed: failed to get _NET_ACTIVE_WINDOW atom\n"); + return -1; + } + self->window = get_focused_window(self->display, self->net_active_window_atom); + } else { + self->window = self->params.window; + } + + /* TODO: Do these in tick, and allow error if follow_focused */ + + XWindowAttributes attr; + if(!XGetWindowAttributes(self->display, self->window, &attr) && !self->params.follow_focused) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_start failed: invalid window id: %lu\n", self->window); + return -1; + } + + self->window_size.x = max_int(attr.width, 0); + self->window_size.y = max_int(attr.height, 0); + + if(self->params.follow_focused) + XSelectInput(self->display, DefaultRootWindow(self->display), PropertyChangeMask); + + // TODO: Get select and add these on top of it and then restore at the end. Also do the same in other xcomposite + XSelectInput(self->display, self->window, StructureNotifyMask | ExposureMask); + + if(window_texture_init(&self->window_texture, self->display, self->window, self->params.egl) != 0 && !self->params.follow_focused) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_start: failed to get window texture for window %ld\n", (long)self->window); + return -1; + } + + if(gsr_cursor_init(&self->cursor, self->params.egl, self->display) != 0) { + gsr_capture_xcomposite_stop(self); + return -1; + } + + self->texture_size.x = 0; + self->texture_size.y = 0; + + self->params.egl->glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&self->window_texture)); + self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &self->texture_size.x); + self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &self->texture_size.y); + self->params.egl->glBindTexture(GL_TEXTURE_2D, 0); + + if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) { + capture_metadata->width = self->texture_size.x; + capture_metadata->height = self->texture_size.y; + } else { + capture_metadata->width = self->params.output_resolution.x; + capture_metadata->height = self->params.output_resolution.y; + } + + self->window_resize_timer = clock_get_monotonic_seconds(); + return 0; +} + +static void gsr_capture_xcomposite_tick(gsr_capture *cap) { + gsr_capture_xcomposite *self = cap->priv; + + if(self->params.follow_focused && !self->follow_focused_initialized) { + self->init_new_window = true; + } + + if(self->init_new_window) { + self->init_new_window = false; + Window focused_window = get_focused_window(self->display, self->net_active_window_atom); + if(focused_window != self->window || !self->follow_focused_initialized) { + self->follow_focused_initialized = true; + XSelectInput(self->display, self->window, 0); + self->window = focused_window; + XSelectInput(self->display, self->window, StructureNotifyMask | ExposureMask); + + XWindowAttributes attr; + attr.width = 0; + attr.height = 0; + if(!XGetWindowAttributes(self->display, self->window, &attr)) + fprintf(stderr, "gsr error: gsr_capture_xcomposite_tick failed: invalid window id: %lu\n", self->window); + + self->window_size.x = max_int(attr.width, 0); + self->window_size.y = max_int(attr.height, 0); + + window_texture_deinit(&self->window_texture); + window_texture_init(&self->window_texture, self->display, self->window, self->params.egl); // TODO: Do not do the below window_texture_on_resize after this + + self->texture_size.x = 0; + self->texture_size.y = 0; + + self->params.egl->glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&self->window_texture)); + self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &self->texture_size.x); + self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &self->texture_size.y); + self->params.egl->glBindTexture(GL_TEXTURE_2D, 0); + + self->window_resized = false; + self->clear_background = true; + } + } + + const double window_resize_timeout = 1.0; // 1 second + if(self->window_resized && clock_get_monotonic_seconds() - self->window_resize_timer >= window_resize_timeout) { + self->window_resized = false; + + if(window_texture_on_resize(&self->window_texture) != 0) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_tick: window_texture_on_resize failed\n"); + //self->should_stop = true; + //self->stop_is_error = true; + return; + } + + self->texture_size.x = 0; + self->texture_size.y = 0; + + self->params.egl->glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&self->window_texture)); + self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &self->texture_size.x); + self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &self->texture_size.y); + self->params.egl->glBindTexture(GL_TEXTURE_2D, 0); + + self->clear_background = true; + } +} + +static void gsr_capture_xcomposite_on_event(gsr_capture *cap, gsr_egl *egl) { + gsr_capture_xcomposite *self = cap->priv; + XEvent *xev = gsr_window_get_event_data(egl->window); + switch(xev->type) { + case DestroyNotify: { + /* Window died (when not following focused window), so we stop recording */ + if(!self->params.follow_focused && xev->xdestroywindow.window == self->window) { + self->should_stop = true; + self->stop_is_error = false; + } + break; + } + case Expose: { + /* Requires window texture recreate */ + if(xev->xexpose.count == 0 && xev->xexpose.window == self->window) { + self->window_resize_timer = clock_get_monotonic_seconds(); + self->window_resized = true; + } + break; + } + case ConfigureNotify: { + /* Window resized */ + if(xev->xconfigure.window == self->window && (xev->xconfigure.width != self->window_size.x || xev->xconfigure.height != self->window_size.y)) { + self->window_size.x = max_int(xev->xconfigure.width, 0); + self->window_size.y = max_int(xev->xconfigure.height, 0); + self->window_resize_timer = clock_get_monotonic_seconds(); + self->window_resized = true; + } + break; + } + case PropertyNotify: { + /* Focused window changed */ + if(self->params.follow_focused && xev->xproperty.atom == self->net_active_window_atom) { + self->init_new_window = true; + } + break; + } + } + + gsr_cursor_on_event(&self->cursor, xev); +} + +static bool gsr_capture_xcomposite_should_stop(gsr_capture *cap, bool *err) { + gsr_capture_xcomposite *self = cap->priv; + if(self->should_stop) { + if(err) + *err = self->stop_is_error; + return true; + } + + if(err) + *err = false; + return false; +} + +static int gsr_capture_xcomposite_capture(gsr_capture *cap, gsr_capture_metadata *capture_metdata, gsr_color_conversion *color_conversion) { + gsr_capture_xcomposite *self = cap->priv; + + if(self->clear_background) { + self->clear_background = false; + gsr_color_conversion_clear(color_conversion); + } + + 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, capture_metdata->width / 2 - output_size.x / 2), max_int(0, capture_metdata->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), + target_pos, output_size, + (vec2i){0, 0}, self->texture_size, self->texture_size, + GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, false); + + 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_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 + }; + + if(cursor_pos.x < target_pos.x || cursor_pos.x + self->cursor.size.x > target_pos.x + output_size.x || cursor_pos.y < target_pos.y || cursor_pos.y + self->cursor.size.y > target_pos.y + output_size.y) + self->clear_background = true; + + gsr_color_conversion_draw(color_conversion, self->cursor.texture_id, + cursor_pos, (vec2i){self->cursor.size.x * scale.x, self->cursor.size.y * scale.y}, + (vec2i){0, 0}, self->cursor.size, self->cursor.size, + GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, true); + } + + //self->params.egl->glFlush(); + //self->params.egl->glFinish(); + + return 0; +} + +static uint64_t gsr_capture_xcomposite_get_window_id(gsr_capture *cap) { + gsr_capture_xcomposite *self = cap->priv; + return self->window; +} + +static void gsr_capture_xcomposite_destroy(gsr_capture *cap) { + if(cap->priv) { + gsr_capture_xcomposite_stop(cap->priv); + free(cap->priv); + cap->priv = NULL; + } + free(cap); +} + +gsr_capture* gsr_capture_xcomposite_create(const gsr_capture_xcomposite_params *params) { + if(!params) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_create params is NULL\n"); + return NULL; + } + + gsr_capture *cap = calloc(1, sizeof(gsr_capture)); + if(!cap) + return NULL; + + gsr_capture_xcomposite *cap_xcomp = calloc(1, sizeof(gsr_capture_xcomposite)); + if(!cap_xcomp) { + free(cap); + return NULL; + } + + cap_xcomp->params = *params; + cap_xcomp->display = gsr_window_get_display(params->egl->window); + + *cap = (gsr_capture) { + .start = gsr_capture_xcomposite_start, + .on_event = gsr_capture_xcomposite_on_event, + .tick = gsr_capture_xcomposite_tick, + .should_stop = gsr_capture_xcomposite_should_stop, + .capture = gsr_capture_xcomposite_capture, + .uses_external_image = NULL, + .get_window_id = gsr_capture_xcomposite_get_window_id, + .destroy = gsr_capture_xcomposite_destroy, + .priv = cap_xcomp + }; + + return cap; +} diff --git a/src/capture/xcomposite_cuda.c b/src/capture/xcomposite_cuda.c deleted file mode 100644 index 7e65efa..0000000 --- a/src/capture/xcomposite_cuda.c +++ /dev/null @@ -1,512 +0,0 @@ -#include "../../include/capture/xcomposite_cuda.h" -#include "../../include/cuda.h" -#include "../../include/window_texture.h" -#include "../../include/utils.h" -#include <libavutil/hwcontext.h> -#include <libavutil/hwcontext_cuda.h> -#include <libavutil/frame.h> -#include <libavcodec/avcodec.h> - -typedef struct { - gsr_capture_xcomposite_cuda_params params; - XEvent xev; - - bool should_stop; - bool stop_is_error; - bool window_resized; - bool created_hw_frame; - bool follow_focused_initialized; - double window_resize_timer; - - vec2i window_size; - - unsigned int target_texture_id; - vec2i texture_size; - Window window; - WindowTexture window_texture; - Atom net_active_window_atom; - - CUgraphicsResource cuda_graphics_resource; - CUarray mapped_array; - - gsr_cuda cuda; -} gsr_capture_xcomposite_cuda; - -static int max_int(int a, int b) { - return a > b ? a : b; -} - -static int min_int(int a, int b) { - return a < b ? a : b; -} - -static Window get_focused_window(Display *display, Atom net_active_window_atom) { - Atom type; - int format = 0; - unsigned long num_items = 0; - unsigned long bytes_after = 0; - unsigned char *properties = NULL; - if(XGetWindowProperty(display, DefaultRootWindow(display), net_active_window_atom, 0, 1024, False, AnyPropertyType, &type, &format, &num_items, &bytes_after, &properties) == Success && properties) { - Window focused_window = *(unsigned long*)properties; - XFree(properties); - return focused_window; - } - return None; -} - -static void gsr_capture_xcomposite_cuda_stop(gsr_capture *cap, AVCodecContext *video_codec_context); - -static bool cuda_register_opengl_texture(gsr_capture_xcomposite_cuda *cap_xcomp) { - CUresult res; - CUcontext old_ctx; - res = cap_xcomp->cuda.cuCtxPushCurrent_v2(cap_xcomp->cuda.cu_ctx); - // TODO: Use cuGraphicsEGLRegisterImage instead with the window egl image (dont use window_texture). - // That removes the need for an extra texture and texture copy - res = cap_xcomp->cuda.cuGraphicsGLRegisterImage( - &cap_xcomp->cuda_graphics_resource, cap_xcomp->target_texture_id, GL_TEXTURE_2D, - CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY); - if (res != CUDA_SUCCESS) { - const char *err_str = "unknown"; - cap_xcomp->cuda.cuGetErrorString(res, &err_str); - fprintf(stderr, "gsr error: cuda_register_opengl_texture: cuGraphicsGLRegisterImage failed, error: %s, texture " "id: %u\n", err_str, cap_xcomp->target_texture_id); - res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - return false; - } - - res = cap_xcomp->cuda.cuGraphicsResourceSetMapFlags(cap_xcomp->cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY); - res = cap_xcomp->cuda.cuGraphicsMapResources(1, &cap_xcomp->cuda_graphics_resource, 0); - - res = cap_xcomp->cuda.cuGraphicsSubResourceGetMappedArray(&cap_xcomp->mapped_array, cap_xcomp->cuda_graphics_resource, 0, 0); - res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - return true; -} - -static bool cuda_create_codec_context(gsr_capture_xcomposite_cuda *cap_xcomp, AVCodecContext *video_codec_context) { - CUcontext old_ctx; - cap_xcomp->cuda.cuCtxPushCurrent_v2(cap_xcomp->cuda.cu_ctx); - - AVBufferRef *device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA); - if(!device_ctx) { - fprintf(stderr, "Error: Failed to create hardware device context\n"); - cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - return false; - } - - AVHWDeviceContext *hw_device_context = (AVHWDeviceContext*)device_ctx->data; - AVCUDADeviceContext *cuda_device_context = (AVCUDADeviceContext*)hw_device_context->hwctx; - cuda_device_context->cuda_ctx = cap_xcomp->cuda.cu_ctx; - if(av_hwdevice_ctx_init(device_ctx) < 0) { - fprintf(stderr, "Error: Failed to create hardware device context\n"); - av_buffer_unref(&device_ctx); - cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - return false; - } - - AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx); - if(!frame_context) { - fprintf(stderr, "Error: Failed to create hwframe context\n"); - av_buffer_unref(&device_ctx); - cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - return false; - } - - AVHWFramesContext *hw_frame_context = - (AVHWFramesContext *)frame_context->data; - hw_frame_context->width = video_codec_context->width; - hw_frame_context->height = video_codec_context->height; - hw_frame_context->sw_format = AV_PIX_FMT_BGR0; - hw_frame_context->format = video_codec_context->pix_fmt; - hw_frame_context->device_ref = device_ctx; - hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data; - - hw_frame_context->initial_pool_size = 1; - - if (av_hwframe_ctx_init(frame_context) < 0) { - fprintf(stderr, "Error: Failed to initialize hardware frame context " - "(note: ffmpeg version needs to be > 4.0)\n"); - av_buffer_unref(&device_ctx); - //av_buffer_unref(&frame_context); - cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - return false; - } - - video_codec_context->hw_device_ctx = av_buffer_ref(device_ctx); - video_codec_context->hw_frames_ctx = av_buffer_ref(frame_context); - return true; -} - -static unsigned int gl_create_texture(gsr_capture_xcomposite_cuda *cap_xcomp, int width, int height) { - unsigned int texture_id = 0; - cap_xcomp->params.egl->glGenTextures(1, &texture_id); - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, texture_id); - cap_xcomp->params.egl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); - - cap_xcomp->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - cap_xcomp->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - cap_xcomp->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - cap_xcomp->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - return texture_id; -} - -static int gsr_capture_xcomposite_cuda_start(gsr_capture *cap, AVCodecContext *video_codec_context) { - gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv; - - if(cap_xcomp->params.follow_focused) { - cap_xcomp->net_active_window_atom = XInternAtom(cap_xcomp->params.dpy, "_NET_ACTIVE_WINDOW", False); - if(!cap_xcomp->net_active_window_atom) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start failed: failed to get _NET_ACTIVE_WINDOW atom\n"); - return -1; - } - cap_xcomp->window = get_focused_window(cap_xcomp->params.dpy, cap_xcomp->net_active_window_atom); - } else { - cap_xcomp->window = cap_xcomp->params.window; - } - - /* TODO: Do these in tick, and allow error if follow_focused */ - - XWindowAttributes attr; - attr.width = 0; - attr.height = 0; - if(!XGetWindowAttributes(cap_xcomp->params.dpy, cap_xcomp->window, &attr) && !cap_xcomp->params.follow_focused) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start failed: invalid window id: %lu\n", cap_xcomp->window); - return -1; - } - - cap_xcomp->window_size.x = max_int(attr.width, 0); - cap_xcomp->window_size.y = max_int(attr.height, 0); - - if(cap_xcomp->params.follow_focused) - XSelectInput(cap_xcomp->params.dpy, DefaultRootWindow(cap_xcomp->params.dpy), PropertyChangeMask); - - XSelectInput(cap_xcomp->params.dpy, cap_xcomp->window, StructureNotifyMask | ExposureMask); - - cap_xcomp->params.egl->eglSwapInterval(cap_xcomp->params.egl->egl_display, 0); - if(window_texture_init(&cap_xcomp->window_texture, cap_xcomp->params.dpy, cap_xcomp->window, cap_xcomp->params.egl) != 0 && !cap_xcomp->params.follow_focused) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start: failed to get window texture for window %ld\n", cap_xcomp->window); - return -1; - } - - cap_xcomp->texture_size.x = 0; - cap_xcomp->texture_size.y = 0; - - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture)); - cap_xcomp->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x); - cap_xcomp->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y); - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - - cap_xcomp->texture_size.x = max_int(2, cap_xcomp->texture_size.x & ~1); - cap_xcomp->texture_size.y = max_int(2, cap_xcomp->texture_size.y & ~1); - - video_codec_context->width = cap_xcomp->texture_size.x; - video_codec_context->height = cap_xcomp->texture_size.y; - - if(cap_xcomp->params.region_size.x > 0 && cap_xcomp->params.region_size.y > 0) { - video_codec_context->width = max_int(2, cap_xcomp->params.region_size.x & ~1); - video_codec_context->height = max_int(2, cap_xcomp->params.region_size.y & ~1); - } - - cap_xcomp->target_texture_id = gl_create_texture(cap_xcomp, video_codec_context->width, video_codec_context->height); - if(cap_xcomp->target_texture_id == 0) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start: failed to create opengl texture\n"); - gsr_capture_xcomposite_cuda_stop(cap, video_codec_context); - return -1; - } - - if(!gsr_cuda_load(&cap_xcomp->cuda, cap_xcomp->params.dpy, cap_xcomp->params.overclock)) { - gsr_capture_xcomposite_cuda_stop(cap, video_codec_context); - return -1; - } - - if(!cuda_create_codec_context(cap_xcomp, video_codec_context)) { - gsr_capture_xcomposite_cuda_stop(cap, video_codec_context); - return -1; - } - - if(!cuda_register_opengl_texture(cap_xcomp)) { - gsr_capture_xcomposite_cuda_stop(cap, video_codec_context); - return -1; - } - - cap_xcomp->window_resize_timer = clock_get_monotonic_seconds(); - return 0; -} - -static void gsr_capture_xcomposite_cuda_stop(gsr_capture *cap, AVCodecContext *video_codec_context) { - gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv; - - if(cap_xcomp->cuda.cu_ctx) { - CUcontext old_ctx; - cap_xcomp->cuda.cuCtxPushCurrent_v2(cap_xcomp->cuda.cu_ctx); - - if(cap_xcomp->cuda_graphics_resource) { - cap_xcomp->cuda.cuGraphicsUnmapResources(1, &cap_xcomp->cuda_graphics_resource, 0); - cap_xcomp->cuda.cuGraphicsUnregisterResource(cap_xcomp->cuda_graphics_resource); - } - - cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - } - - window_texture_deinit(&cap_xcomp->window_texture); - - if(cap_xcomp->target_texture_id) { - cap_xcomp->params.egl->glDeleteTextures(1, &cap_xcomp->target_texture_id); - cap_xcomp->target_texture_id = 0; - } - - if(video_codec_context->hw_device_ctx) - av_buffer_unref(&video_codec_context->hw_device_ctx); - if(video_codec_context->hw_frames_ctx) - av_buffer_unref(&video_codec_context->hw_frames_ctx); - - gsr_cuda_unload(&cap_xcomp->cuda); - - if(cap_xcomp->params.dpy) { - // TODO: This causes a crash, why? maybe some other library dlclose xlib and that also happened to unload this??? - //XCloseDisplay(cap_xcomp->dpy); - cap_xcomp->params.dpy = NULL; - } -} - -static void gsr_capture_xcomposite_cuda_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) { - gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv; - - cap_xcomp->params.egl->glClear(GL_COLOR_BUFFER_BIT); - - bool init_new_window = false; - while(XPending(cap_xcomp->params.dpy)) { - XNextEvent(cap_xcomp->params.dpy, &cap_xcomp->xev); - - switch(cap_xcomp->xev.type) { - case DestroyNotify: { - /* Window died (when not following focused window), so we stop recording */ - if(!cap_xcomp->params.follow_focused && cap_xcomp->xev.xdestroywindow.window == cap_xcomp->window) { - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = false; - } - break; - } - case Expose: { - /* Requires window texture recreate */ - if(cap_xcomp->xev.xexpose.count == 0 && cap_xcomp->xev.xexpose.window == cap_xcomp->window) { - cap_xcomp->window_resize_timer = clock_get_monotonic_seconds(); - cap_xcomp->window_resized = true; - } - break; - } - case ConfigureNotify: { - /* Window resized */ - if(cap_xcomp->xev.xconfigure.window == cap_xcomp->window && (cap_xcomp->xev.xconfigure.width != cap_xcomp->window_size.x || cap_xcomp->xev.xconfigure.height != cap_xcomp->window_size.y)) { - cap_xcomp->window_size.x = max_int(cap_xcomp->xev.xconfigure.width, 0); - cap_xcomp->window_size.y = max_int(cap_xcomp->xev.xconfigure.height, 0); - cap_xcomp->window_resize_timer = clock_get_monotonic_seconds(); - cap_xcomp->window_resized = true; - } - break; - } - case PropertyNotify: { - /* Focused window changed */ - if(cap_xcomp->params.follow_focused && cap_xcomp->xev.xproperty.atom == cap_xcomp->net_active_window_atom) { - init_new_window = true; - } - break; - } - } - } - - if(cap_xcomp->params.follow_focused && !cap_xcomp->follow_focused_initialized) { - init_new_window = true; - } - - if(init_new_window) { - Window focused_window = get_focused_window(cap_xcomp->params.dpy, cap_xcomp->net_active_window_atom); - if(focused_window != cap_xcomp->window || !cap_xcomp->follow_focused_initialized) { - cap_xcomp->follow_focused_initialized = true; - XSelectInput(cap_xcomp->params.dpy, cap_xcomp->window, 0); - cap_xcomp->window = focused_window; - XSelectInput(cap_xcomp->params.dpy, cap_xcomp->window, StructureNotifyMask | ExposureMask); - - XWindowAttributes attr; - attr.width = 0; - attr.height = 0; - if(!XGetWindowAttributes(cap_xcomp->params.dpy, cap_xcomp->window, &attr)) - fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick failed: invalid window id: %lu\n", cap_xcomp->window); - - cap_xcomp->window_size.x = max_int(attr.width, 0); - cap_xcomp->window_size.y = max_int(attr.height, 0); - cap_xcomp->window_resized = true; - - window_texture_deinit(&cap_xcomp->window_texture); - window_texture_init(&cap_xcomp->window_texture, cap_xcomp->params.dpy, cap_xcomp->window, cap_xcomp->params.egl); // TODO: Do not do the below window_texture_on_resize after this - - cap_xcomp->texture_size.x = 0; - cap_xcomp->texture_size.y = 0; - - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture)); - cap_xcomp->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x); - cap_xcomp->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y); - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - - cap_xcomp->texture_size.x = min_int(video_codec_context->width, max_int(2, cap_xcomp->texture_size.x & ~1)); - cap_xcomp->texture_size.y = min_int(video_codec_context->height, max_int(2, cap_xcomp->texture_size.y & ~1)); - } - } - - const double window_resize_timeout = 1.0; // 1 second - if(!cap_xcomp->created_hw_frame || (cap_xcomp->window_resized && clock_get_monotonic_seconds() - cap_xcomp->window_resize_timer >= window_resize_timeout)) { - cap_xcomp->window_resized = false; - if(window_texture_on_resize(&cap_xcomp->window_texture) != 0) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick: window_texture_on_resize failed\n"); - //cap_xcomp->should_stop = true; - //cap_xcomp->stop_is_error = true; - return; - } - - cap_xcomp->texture_size.x = 0; - cap_xcomp->texture_size.y = 0; - - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture)); - cap_xcomp->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x); - cap_xcomp->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y); - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - - cap_xcomp->texture_size.x = min_int(video_codec_context->width, max_int(2, cap_xcomp->texture_size.x & ~1)); - cap_xcomp->texture_size.y = min_int(video_codec_context->height, max_int(2, cap_xcomp->texture_size.y & ~1)); - - if(!cap_xcomp->created_hw_frame) { - cap_xcomp->created_hw_frame = true; - av_frame_free(frame); - *frame = av_frame_alloc(); - if(!frame) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick: failed to allocate frame\n"); - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = true; - return; - } - (*frame)->format = video_codec_context->pix_fmt; - (*frame)->width = video_codec_context->width; - (*frame)->height = video_codec_context->height; - (*frame)->color_range = video_codec_context->color_range; - (*frame)->color_primaries = video_codec_context->color_primaries; - (*frame)->color_trc = video_codec_context->color_trc; - (*frame)->colorspace = video_codec_context->colorspace; - (*frame)->chroma_location = video_codec_context->chroma_sample_location; - - if(av_hwframe_get_buffer(video_codec_context->hw_frames_ctx, *frame, 0) < 0) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick: av_hwframe_get_buffer failed\n"); - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = true; - return; - } - } - - // Clear texture with black background because the source texture (window_texture_get_opengl_texture_id(&cap_xcomp->window_texture)) - // might be smaller than cap_xcomp->target_texture_id - cap_xcomp->params.egl->glClearTexImage(cap_xcomp->target_texture_id, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); - } -} - -static bool gsr_capture_xcomposite_cuda_should_stop(gsr_capture *cap, bool *err) { - gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv; - if(cap_xcomp->should_stop) { - if(err) - *err = cap_xcomp->stop_is_error; - return true; - } - - if(err) - *err = false; - return false; -} - -static int gsr_capture_xcomposite_cuda_capture(gsr_capture *cap, AVFrame *frame) { - gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv; - - vec2i source_pos = { 0, 0 }; - vec2i source_size = cap_xcomp->texture_size; - - if(cap_xcomp->window_texture.texture_id != 0) { - while(cap_xcomp->params.egl->glGetError()) {} - /* TODO: Remove this copy, which is only possible by using nvenc directly and encoding window_pixmap.target_texture_id */ - cap_xcomp->params.egl->glCopyImageSubData( - window_texture_get_opengl_texture_id(&cap_xcomp->window_texture), GL_TEXTURE_2D, 0, source_pos.x, source_pos.y, 0, - cap_xcomp->target_texture_id, GL_TEXTURE_2D, 0, 0, 0, 0, - source_size.x, source_size.y, 1); - unsigned int err = cap_xcomp->params.egl->glGetError(); - if(err != 0) { - static bool error_shown = false; - if(!error_shown) { - error_shown = true; - fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_capture: glCopyImageSubData failed, gl error: %d\n", err); - } - } - } - cap_xcomp->params.egl->eglSwapBuffers(cap_xcomp->params.egl->egl_display, cap_xcomp->params.egl->egl_surface); - - frame->linesize[0] = frame->width * 4; - //frame->linesize[0] = frame->width * 1; - //frame->linesize[1] = frame->width * 1; - //frame->linesize[2] = frame->width * 1; - - CUDA_MEMCPY2D memcpy_struct; - memcpy_struct.srcXInBytes = 0; - memcpy_struct.srcY = 0; - memcpy_struct.srcMemoryType = CU_MEMORYTYPE_ARRAY; - - memcpy_struct.dstXInBytes = 0; - memcpy_struct.dstY = 0; - memcpy_struct.dstMemoryType = CU_MEMORYTYPE_DEVICE; - - memcpy_struct.srcArray = cap_xcomp->mapped_array; - memcpy_struct.dstDevice = (CUdeviceptr)frame->data[0]; - memcpy_struct.dstPitch = frame->linesize[0]; - memcpy_struct.WidthInBytes = frame->width * 4;//frame->width * 1; - memcpy_struct.Height = frame->height; - cap_xcomp->cuda.cuMemcpy2D_v2(&memcpy_struct); - - //frame->data[1] = frame->data[0]; - //frame->data[2] = frame->data[0]; - - return 0; -} - -static void gsr_capture_xcomposite_cuda_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) { - if(cap->priv) { - gsr_capture_xcomposite_cuda_stop(cap, video_codec_context); - free(cap->priv); - cap->priv = NULL; - } - free(cap); -} - -gsr_capture* gsr_capture_xcomposite_cuda_create(const gsr_capture_xcomposite_cuda_params *params) { - if(!params) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_create params is NULL\n"); - return NULL; - } - - gsr_capture *cap = calloc(1, sizeof(gsr_capture)); - if(!cap) - return NULL; - - gsr_capture_xcomposite_cuda *cap_xcomp = calloc(1, sizeof(gsr_capture_xcomposite_cuda)); - if(!cap_xcomp) { - free(cap); - return NULL; - } - - cap_xcomp->params = *params; - - *cap = (gsr_capture) { - .start = gsr_capture_xcomposite_cuda_start, - .tick = gsr_capture_xcomposite_cuda_tick, - .should_stop = gsr_capture_xcomposite_cuda_should_stop, - .capture = gsr_capture_xcomposite_cuda_capture, - .capture_end = NULL, - .destroy = gsr_capture_xcomposite_cuda_destroy, - .priv = cap_xcomp - }; - - return cap; -} diff --git a/src/capture/xcomposite_vaapi.c b/src/capture/xcomposite_vaapi.c deleted file mode 100644 index 2bc21a7..0000000 --- a/src/capture/xcomposite_vaapi.c +++ /dev/null @@ -1,517 +0,0 @@ -#include "../../include/capture/xcomposite_vaapi.h" -#include "../../include/window_texture.h" -#include "../../include/utils.h" -#include "../../include/color_conversion.h" -#include <stdlib.h> -#include <stdio.h> -#include <unistd.h> -#include <assert.h> -#include <X11/Xlib.h> -#include <libavutil/hwcontext.h> -#include <libavutil/hwcontext_vaapi.h> -#include <libavutil/frame.h> -#include <libavcodec/avcodec.h> -#include <va/va.h> -#include <va/va_drmcommon.h> - -typedef struct { - gsr_capture_xcomposite_vaapi_params params; - XEvent xev; - - bool should_stop; - bool stop_is_error; - bool window_resized; - bool created_hw_frame; - bool follow_focused_initialized; - - Window window; - vec2i window_size; - vec2i texture_size; - double window_resize_timer; - - WindowTexture window_texture; - - VADisplay va_dpy; - VADRMPRIMESurfaceDescriptor prime; - - unsigned int target_textures[2]; - - gsr_color_conversion color_conversion; - - Atom net_active_window_atom; -} gsr_capture_xcomposite_vaapi; - -static int max_int(int a, int b) { - return a > b ? a : b; -} - -static int min_int(int a, int b) { - return a < b ? a : b; -} - -static void gsr_capture_xcomposite_vaapi_stop(gsr_capture *cap, AVCodecContext *video_codec_context); - -static Window get_focused_window(Display *display, Atom net_active_window_atom) { - Atom type; - int format = 0; - unsigned long num_items = 0; - unsigned long bytes_after = 0; - unsigned char *properties = NULL; - if(XGetWindowProperty(display, DefaultRootWindow(display), net_active_window_atom, 0, 1024, False, AnyPropertyType, &type, &format, &num_items, &bytes_after, &properties) == Success && properties) { - Window focused_window = *(unsigned long*)properties; - XFree(properties); - return focused_window; - } - return None; -} - -static bool drm_create_codec_context(gsr_capture_xcomposite_vaapi *cap_xcomp, AVCodecContext *video_codec_context) { - char render_path[128]; - if(!gsr_card_path_get_render_path(cap_xcomp->params.card_path, render_path)) { - fprintf(stderr, "gsr error: failed to get /dev/dri/renderDXXX file from %s\n", cap_xcomp->params.card_path); - return false; - } - - AVBufferRef *device_ctx; - if(av_hwdevice_ctx_create(&device_ctx, AV_HWDEVICE_TYPE_VAAPI, render_path, NULL, 0) < 0) { - fprintf(stderr, "Error: Failed to create hardware device context\n"); - return false; - } - - AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx); - if(!frame_context) { - fprintf(stderr, "Error: Failed to create hwframe context\n"); - av_buffer_unref(&device_ctx); - return false; - } - - AVHWFramesContext *hw_frame_context = - (AVHWFramesContext *)frame_context->data; - hw_frame_context->width = video_codec_context->width; - hw_frame_context->height = video_codec_context->height; - hw_frame_context->sw_format = AV_PIX_FMT_NV12;//AV_PIX_FMT_0RGB32;//AV_PIX_FMT_YUV420P;//AV_PIX_FMT_0RGB32;//AV_PIX_FMT_NV12; - hw_frame_context->format = video_codec_context->pix_fmt; - hw_frame_context->device_ref = device_ctx; - hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data; - - hw_frame_context->initial_pool_size = 1; - - AVVAAPIDeviceContext *vactx =((AVHWDeviceContext*)device_ctx->data)->hwctx; - cap_xcomp->va_dpy = vactx->display; - - if (av_hwframe_ctx_init(frame_context) < 0) { - fprintf(stderr, "Error: Failed to initialize hardware frame context " - "(note: ffmpeg version needs to be > 4.0)\n"); - av_buffer_unref(&device_ctx); - //av_buffer_unref(&frame_context); - return false; - } - - video_codec_context->hw_device_ctx = av_buffer_ref(device_ctx); - video_codec_context->hw_frames_ctx = av_buffer_ref(frame_context); - return true; -} - -#define DRM_FORMAT_MOD_INVALID 72057594037927935 - -static int gsr_capture_xcomposite_vaapi_start(gsr_capture *cap, AVCodecContext *video_codec_context) { - gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv; - - if(cap_xcomp->params.follow_focused) { - cap_xcomp->net_active_window_atom = XInternAtom(cap_xcomp->params.dpy, "_NET_ACTIVE_WINDOW", False); - if(!cap_xcomp->net_active_window_atom) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_start failed: failed to get _NET_ACTIVE_WINDOW atom\n"); - return -1; - } - cap_xcomp->window = get_focused_window(cap_xcomp->params.dpy, cap_xcomp->net_active_window_atom); - } else { - cap_xcomp->window = cap_xcomp->params.window; - } - - /* TODO: Do these in tick, and allow error if follow_focused */ - - XWindowAttributes attr; - if(!XGetWindowAttributes(cap_xcomp->params.dpy, cap_xcomp->params.window, &attr) && !cap_xcomp->params.follow_focused) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_start failed: invalid window id: %lu\n", cap_xcomp->params.window); - return -1; - } - - cap_xcomp->window_size.x = max_int(attr.width, 0); - cap_xcomp->window_size.y = max_int(attr.height, 0); - - if(cap_xcomp->params.follow_focused) - XSelectInput(cap_xcomp->params.dpy, DefaultRootWindow(cap_xcomp->params.dpy), PropertyChangeMask); - - // TODO: Get select and add these on top of it and then restore at the end. Also do the same in other xcomposite - XSelectInput(cap_xcomp->params.dpy, cap_xcomp->params.window, StructureNotifyMask | ExposureMask); - - if(!cap_xcomp->params.egl->eglExportDMABUFImageQueryMESA) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_start: could not find eglExportDMABUFImageQueryMESA\n"); - return -1; - } - - if(!cap_xcomp->params.egl->eglExportDMABUFImageMESA) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_start: could not find eglExportDMABUFImageMESA\n"); - return -1; - } - - /* Disable vsync */ - cap_xcomp->params.egl->eglSwapInterval(cap_xcomp->params.egl->egl_display, 0); - if(window_texture_init(&cap_xcomp->window_texture, cap_xcomp->params.dpy, cap_xcomp->params.window, cap_xcomp->params.egl) != 0 && !cap_xcomp->params.follow_focused) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_start: failed to get window texture for window %ld\n", cap_xcomp->params.window); - return -1; - } - - cap_xcomp->texture_size.x = 0; - cap_xcomp->texture_size.y = 0; - - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture)); - cap_xcomp->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x); - cap_xcomp->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y); - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - - cap_xcomp->texture_size.x = max_int(2, even_number_ceil(cap_xcomp->texture_size.x)); - cap_xcomp->texture_size.y = max_int(2, even_number_ceil(cap_xcomp->texture_size.y)); - - video_codec_context->width = cap_xcomp->texture_size.x; - video_codec_context->height = cap_xcomp->texture_size.y; - - if(cap_xcomp->params.region_size.x > 0 && cap_xcomp->params.region_size.y > 0) { - video_codec_context->width = max_int(2, even_number_ceil(cap_xcomp->params.region_size.x)); - video_codec_context->height = max_int(2, even_number_ceil(cap_xcomp->params.region_size.y)); - } - - if(!drm_create_codec_context(cap_xcomp, video_codec_context)) { - gsr_capture_xcomposite_vaapi_stop(cap, video_codec_context); - return -1; - } - - cap_xcomp->window_resize_timer = clock_get_monotonic_seconds(); - return 0; -} - -static uint32_t fourcc(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { - return (d << 24) | (c << 16) | (b << 8) | a; -} - -#define FOURCC_NV12 842094158 - -static void gsr_capture_xcomposite_vaapi_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) { - gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv; - - // TODO: - cap_xcomp->params.egl->glClear(GL_COLOR_BUFFER_BIT); - - bool init_new_window = false; - while(XPending(cap_xcomp->params.dpy)) { - XNextEvent(cap_xcomp->params.dpy, &cap_xcomp->xev); - - switch(cap_xcomp->xev.type) { - case DestroyNotify: { - /* Window died (when not following focused window), so we stop recording */ - if(!cap_xcomp->params.follow_focused && cap_xcomp->xev.xdestroywindow.window == cap_xcomp->window) { - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = false; - } - break; - } - case Expose: { - /* Requires window texture recreate */ - if(cap_xcomp->xev.xexpose.count == 0 && cap_xcomp->xev.xexpose.window == cap_xcomp->window) { - cap_xcomp->window_resize_timer = clock_get_monotonic_seconds(); - cap_xcomp->window_resized = true; - } - break; - } - case ConfigureNotify: { - /* Window resized */ - if(cap_xcomp->xev.xconfigure.window == cap_xcomp->window && (cap_xcomp->xev.xconfigure.width != cap_xcomp->window_size.x || cap_xcomp->xev.xconfigure.height != cap_xcomp->window_size.y)) { - cap_xcomp->window_size.x = max_int(cap_xcomp->xev.xconfigure.width, 0); - cap_xcomp->window_size.y = max_int(cap_xcomp->xev.xconfigure.height, 0); - cap_xcomp->window_resize_timer = clock_get_monotonic_seconds(); - cap_xcomp->window_resized = true; - } - break; - } - case PropertyNotify: { - /* Focused window changed */ - if(cap_xcomp->params.follow_focused && cap_xcomp->xev.xproperty.atom == cap_xcomp->net_active_window_atom) { - init_new_window = true; - } - break; - } - } - } - - if(cap_xcomp->params.follow_focused && !cap_xcomp->follow_focused_initialized) { - init_new_window = true; - } - - if(init_new_window) { - Window focused_window = get_focused_window(cap_xcomp->params.dpy, cap_xcomp->net_active_window_atom); - if(focused_window != cap_xcomp->window || !cap_xcomp->follow_focused_initialized) { - cap_xcomp->follow_focused_initialized = true; - XSelectInput(cap_xcomp->params.dpy, cap_xcomp->window, 0); - cap_xcomp->window = focused_window; - XSelectInput(cap_xcomp->params.dpy, cap_xcomp->window, StructureNotifyMask | ExposureMask); - - XWindowAttributes attr; - attr.width = 0; - attr.height = 0; - if(!XGetWindowAttributes(cap_xcomp->params.dpy, cap_xcomp->window, &attr)) - fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick failed: invalid window id: %lu\n", cap_xcomp->window); - - cap_xcomp->window_size.x = max_int(attr.width, 0); - cap_xcomp->window_size.y = max_int(attr.height, 0); - cap_xcomp->window_resized = true; - - window_texture_deinit(&cap_xcomp->window_texture); - window_texture_init(&cap_xcomp->window_texture, cap_xcomp->params.dpy, cap_xcomp->window, cap_xcomp->params.egl); // TODO: Do not do the below window_texture_on_resize after this - - cap_xcomp->texture_size.x = 0; - cap_xcomp->texture_size.y = 0; - - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture)); - cap_xcomp->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x); - cap_xcomp->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y); - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - - cap_xcomp->texture_size.x = min_int(video_codec_context->width, max_int(2, even_number_ceil(cap_xcomp->texture_size.x))); - cap_xcomp->texture_size.y = min_int(video_codec_context->height, max_int(2, even_number_ceil(cap_xcomp->texture_size.y))); - } - } - - const double window_resize_timeout = 1.0; // 1 second - if(!cap_xcomp->created_hw_frame || (cap_xcomp->window_resized && clock_get_monotonic_seconds() - cap_xcomp->window_resize_timer >= window_resize_timeout)) { - cap_xcomp->window_resized = false; - - if(window_texture_on_resize(&cap_xcomp->window_texture) != 0) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: window_texture_on_resize failed\n"); - //cap_xcomp->should_stop = true; - //cap_xcomp->stop_is_error = true; - return; - } - - cap_xcomp->texture_size.x = 0; - cap_xcomp->texture_size.y = 0; - - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture)); - cap_xcomp->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x); - cap_xcomp->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y); - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - - cap_xcomp->texture_size.x = min_int(video_codec_context->width, max_int(2, even_number_ceil(cap_xcomp->texture_size.x))); - cap_xcomp->texture_size.y = min_int(video_codec_context->height, max_int(2, even_number_ceil(cap_xcomp->texture_size.y))); - - if(!cap_xcomp->created_hw_frame) { - cap_xcomp->created_hw_frame = true; - av_frame_free(frame); - *frame = av_frame_alloc(); - if(!frame) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: failed to allocate frame\n"); - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = true; - return; - } - (*frame)->format = video_codec_context->pix_fmt; - (*frame)->width = video_codec_context->width; - (*frame)->height = video_codec_context->height; - (*frame)->color_range = video_codec_context->color_range; - (*frame)->color_primaries = video_codec_context->color_primaries; - (*frame)->color_trc = video_codec_context->color_trc; - (*frame)->colorspace = video_codec_context->colorspace; - (*frame)->chroma_location = video_codec_context->chroma_sample_location; - - int res = av_hwframe_get_buffer(video_codec_context->hw_frames_ctx, *frame, 0); - if(res < 0) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: av_hwframe_get_buffer failed: %d\n", res); - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = true; - return; - } - - VASurfaceID target_surface_id = (uintptr_t)(*frame)->data[3]; - - VAStatus va_status = vaExportSurfaceHandle(cap_xcomp->va_dpy, target_surface_id, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, VA_EXPORT_SURFACE_READ_WRITE | VA_EXPORT_SURFACE_SEPARATE_LAYERS, &cap_xcomp->prime); - if(va_status != VA_STATUS_SUCCESS) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: vaExportSurfaceHandle failed, error: %d\n", va_status); - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = true; - return; - } - vaSyncSurface(cap_xcomp->va_dpy, target_surface_id); - - if(cap_xcomp->prime.fourcc == FOURCC_NV12) { - cap_xcomp->params.egl->glGenTextures(2, cap_xcomp->target_textures); - for(int i = 0; i < 2; ++i) { - const uint32_t formats[2] = { fourcc('R', '8', ' ', ' '), fourcc('G', 'R', '8', '8') }; - const int layer = i; - const int plane = 0; - - const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size - //const uint64_t modifier = cap_kms->prime.objects[cap_kms->prime.layers[layer].object_index[plane]].drm_format_modifier; - - const intptr_t img_attr[] = { - EGL_LINUX_DRM_FOURCC_EXT, formats[i], - EGL_WIDTH, cap_xcomp->prime.width / div[i], - EGL_HEIGHT, cap_xcomp->prime.height / div[i], - EGL_DMA_BUF_PLANE0_FD_EXT, cap_xcomp->prime.objects[cap_xcomp->prime.layers[layer].object_index[plane]].fd, - EGL_DMA_BUF_PLANE0_OFFSET_EXT, cap_xcomp->prime.layers[layer].offset[plane], - EGL_DMA_BUF_PLANE0_PITCH_EXT, cap_xcomp->prime.layers[layer].pitch[plane], - // TODO: - //EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, modifier & 0xFFFFFFFFULL, - //EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, modifier >> 32ULL, - EGL_NONE - }; - - while(cap_xcomp->params.egl->eglGetError() != EGL_SUCCESS){} - EGLImage image = cap_xcomp->params.egl->eglCreateImage(cap_xcomp->params.egl->egl_display, 0, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr); - if(!image) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: failed to create egl image from drm fd for output drm fd, error: %d\n", cap_xcomp->params.egl->eglGetError()); - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = true; - return; - } - - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, cap_xcomp->target_textures[i]); - cap_xcomp->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - cap_xcomp->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - cap_xcomp->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - cap_xcomp->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - while(cap_xcomp->params.egl->glGetError()) {} - while(cap_xcomp->params.egl->eglGetError() != EGL_SUCCESS){} - cap_xcomp->params.egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); - if(cap_xcomp->params.egl->glGetError() != 0 || cap_xcomp->params.egl->eglGetError() != EGL_SUCCESS) { - // TODO: Get the error properly - fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: failed to bind egl image to gl texture, error: %d\n", cap_xcomp->params.egl->eglGetError()); - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = true; - cap_xcomp->params.egl->eglDestroyImage(cap_xcomp->params.egl->egl_display, image); - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - return; - } - - cap_xcomp->params.egl->eglDestroyImage(cap_xcomp->params.egl->egl_display, image); - cap_xcomp->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - } - - gsr_color_conversion_params color_conversion_params = {0}; - color_conversion_params.egl = cap_xcomp->params.egl; - color_conversion_params.source_color = GSR_SOURCE_COLOR_RGB; - color_conversion_params.destination_color = GSR_DESTINATION_COLOR_NV12; - - color_conversion_params.destination_textures[0] = cap_xcomp->target_textures[0]; - color_conversion_params.destination_textures[1] = cap_xcomp->target_textures[1]; - color_conversion_params.num_destination_textures = 2; - - if(gsr_color_conversion_init(&cap_xcomp->color_conversion, &color_conversion_params) != 0) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: failed to create color conversion\n"); - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = true; - return; - } - } else { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: unexpected fourcc %u for output drm fd, expected nv12\n", cap_xcomp->prime.fourcc); - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = true; - return; - } - } - } -} - -static bool gsr_capture_xcomposite_vaapi_should_stop(gsr_capture *cap, bool *err) { - gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv; - if(cap_xcomp->should_stop) { - if(err) - *err = cap_xcomp->stop_is_error; - return true; - } - - if(err) - *err = false; - return false; -} - -static int gsr_capture_xcomposite_vaapi_capture(gsr_capture *cap, AVFrame *frame) { - (void)frame; - gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv; - - float texture_rotation = 0.0f; - gsr_color_conversion_draw(&cap_xcomp->color_conversion, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture), - (vec2i){0, 0}, cap_xcomp->texture_size, - (vec2i){0, 0}, cap_xcomp->texture_size, - texture_rotation); - - cap_xcomp->params.egl->eglSwapBuffers(cap_xcomp->params.egl->egl_display, cap_xcomp->params.egl->egl_surface); - - return 0; -} - -static void gsr_capture_xcomposite_vaapi_stop(gsr_capture *cap, AVCodecContext *video_codec_context) { - gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv; - - gsr_color_conversion_deinit(&cap_xcomp->color_conversion); - - for(uint32_t i = 0; i < cap_xcomp->prime.num_objects; ++i) { - if(cap_xcomp->prime.objects[i].fd > 0) { - close(cap_xcomp->prime.objects[i].fd); - cap_xcomp->prime.objects[i].fd = 0; - } - } - - if(cap_xcomp->params.egl->egl_context) { - cap_xcomp->params.egl->glDeleteTextures(2, cap_xcomp->target_textures); - cap_xcomp->target_textures[0] = 0; - cap_xcomp->target_textures[1] = 0; - } - - window_texture_deinit(&cap_xcomp->window_texture); - - if(video_codec_context->hw_device_ctx) - av_buffer_unref(&video_codec_context->hw_device_ctx); - if(video_codec_context->hw_frames_ctx) - av_buffer_unref(&video_codec_context->hw_frames_ctx); -} - -static void gsr_capture_xcomposite_vaapi_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) { - (void)video_codec_context; - if(cap->priv) { - gsr_capture_xcomposite_vaapi_stop(cap, video_codec_context); - free(cap->priv); - cap->priv = NULL; - } - free(cap); -} - -gsr_capture* gsr_capture_xcomposite_vaapi_create(const gsr_capture_xcomposite_vaapi_params *params) { - if(!params) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_create params is NULL\n"); - return NULL; - } - - gsr_capture *cap = calloc(1, sizeof(gsr_capture)); - if(!cap) - return NULL; - - gsr_capture_xcomposite_vaapi *cap_xcomp = calloc(1, sizeof(gsr_capture_xcomposite_vaapi)); - if(!cap_xcomp) { - free(cap); - return NULL; - } - - cap_xcomp->params = *params; - - *cap = (gsr_capture) { - .start = gsr_capture_xcomposite_vaapi_start, - .tick = gsr_capture_xcomposite_vaapi_tick, - .should_stop = gsr_capture_xcomposite_vaapi_should_stop, - .capture = gsr_capture_xcomposite_vaapi_capture, - .capture_end = NULL, - .destroy = gsr_capture_xcomposite_vaapi_destroy, - .priv = cap_xcomp - }; - - return cap; -} diff --git a/src/capture/ximage.c b/src/capture/ximage.c new file mode 100644 index 0000000..9b02907 --- /dev/null +++ b/src/capture/ximage.c @@ -0,0 +1,247 @@ +#include "../../include/capture/ximage.h" +#include "../../include/utils.h" +#include "../../include/cursor.h" +#include "../../include/color_conversion.h" +#include "../../include/window/window.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include <X11/Xlib.h> + +/* TODO: update when monitors are reconfigured */ + +typedef struct { + gsr_capture_ximage_params params; + Display *display; + gsr_cursor cursor; + gsr_monitor monitor; + vec2i capture_pos; + vec2i capture_size; + unsigned int texture_id; + Window root_window; +} gsr_capture_ximage; + +static void gsr_capture_ximage_stop(gsr_capture_ximage *self) { + gsr_cursor_deinit(&self->cursor); + if(self->texture_id) { + self->params.egl->glDeleteTextures(1, &self->texture_id); + self->texture_id = 0; + } +} + +static int max_int(int a, int b) { + return a > b ? a : b; +} + +static int gsr_capture_ximage_start(gsr_capture *cap, gsr_capture_metadata *capture_metadata) { + gsr_capture_ximage *self = cap->priv; + self->root_window = DefaultRootWindow(self->display); + + if(gsr_cursor_init(&self->cursor, self->params.egl, self->display) != 0) { + gsr_capture_ximage_stop(self); + return -1; + } + + if(!get_monitor_by_name(self->params.egl, GSR_CONNECTION_X11, self->params.display_to_capture, &self->monitor)) { + fprintf(stderr, "gsr error: gsr_capture_ximage_start: failed to find monitor by name \"%s\"\n", self->params.display_to_capture); + gsr_capture_ximage_stop(self); + return -1; + } + + self->capture_pos = self->monitor.pos; + self->capture_size = self->monitor.size; + + if(self->params.region_size.x > 0 && self->params.region_size.y > 0) + self->capture_size = self->params.region_size; + + if(self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0) { + self->params.output_resolution = scale_keep_aspect_ratio(self->capture_size, self->params.output_resolution); + capture_metadata->width = self->params.output_resolution.x; + capture_metadata->height = self->params.output_resolution.y; + } else if(self->params.region_size.x > 0 && self->params.region_size.y > 0) { + capture_metadata->width = self->params.region_size.x; + capture_metadata->height = self->params.region_size.y; + } else { + capture_metadata->width = self->capture_size.x; + capture_metadata->height = self->capture_size.y; + } + + self->texture_id = gl_create_texture(self->params.egl, self->capture_size.x, self->capture_size.y, GL_RGB8, GL_RGB, GL_LINEAR); + if(self->texture_id == 0) { + fprintf(stderr, "gsr error: gsr_capture_ximage_start: failed to create texture\n"); + gsr_capture_ximage_stop(self); + return -1; + } + + return 0; +} + +static void gsr_capture_ximage_on_event(gsr_capture *cap, gsr_egl *egl) { + gsr_capture_ximage *self = cap->priv; + XEvent *xev = gsr_window_get_event_data(egl->window); + gsr_cursor_on_event(&self->cursor, xev); +} + +static bool gsr_capture_ximage_upload_to_texture(gsr_capture_ximage *self, int x, int y, int width, int height) { + const int max_width = XWidthOfScreen(DefaultScreenOfDisplay(self->display)); + const int max_height = XHeightOfScreen(DefaultScreenOfDisplay(self->display)); + + if(x < 0) + x = 0; + else if(x >= max_width) + x = max_width - 1; + + if(y < 0) + y = 0; + else if(y >= max_height) + y = max_height - 1; + + if(width < 0) + width = 0; + else if(x + width >= max_width) + width = max_width - x; + + if(height < 0) + height = 0; + else if(y + height >= max_height) + height = max_height - y; + + XImage *image = XGetImage(self->display, self->root_window, x, y, width, height, AllPlanes, ZPixmap); + if(!image) { + fprintf(stderr, "gsr error: gsr_capture_ximage_upload_to_texture: XGetImage failed\n"); + return false; + } + + bool success = false; + uint8_t *image_data = malloc(image->width * image->height * 3); + if(!image_data) { + fprintf(stderr, "gsr error: gsr_capture_ximage_upload_to_texture: failed to allocate image data\n"); + goto done; + } + + for(int y = 0; y < image->height; ++y) { + for(int x = 0; x < image->width; ++x) { + unsigned long pixel = XGetPixel(image, x, y); + unsigned char red = (pixel & image->red_mask) >> 16; + unsigned char green = (pixel & image->green_mask) >> 8; + unsigned char blue = pixel & image->blue_mask; + + const size_t texture_data_index = (x + y * image->width) * 3; + image_data[texture_data_index + 0] = red; + image_data[texture_data_index + 1] = green; + image_data[texture_data_index + 2] = blue; + } + } + + self->params.egl->glBindTexture(GL_TEXTURE_2D, self->texture_id); + self->params.egl->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image->width, image->height, GL_RGB, GL_UNSIGNED_BYTE, image_data); + self->params.egl->glBindTexture(GL_TEXTURE_2D, 0); + success = true; + + done: + free(image_data); + XDestroyImage(image); + return success; +} + +static int gsr_capture_ximage_capture(gsr_capture *cap, gsr_capture_metadata *capture_metdata, gsr_color_conversion *color_conversion) { + gsr_capture_ximage *self = cap->priv; + + 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, capture_metdata->width / 2 - output_size.x / 2), max_int(0, capture_metdata->height / 2 - output_size.y / 2) }; + gsr_capture_ximage_upload_to_texture(self, self->capture_pos.x + self->params.region_position.x, self->capture_pos.y + self->params.region_position.y, self->capture_size.x, self->capture_size.y); + + gsr_color_conversion_draw(color_conversion, self->texture_id, + target_pos, output_size, + (vec2i){0, 0}, self->capture_size, self->capture_size, + GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, false); + + if(self->params.record_cursor && self->cursor.visible) { + 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->cursor, self->root_window); + + const vec2i cursor_pos = { + target_pos.x + (self->cursor.position.x - self->cursor.hotspot.x) * scale.x - self->capture_pos.x - self->params.region_position.x, + target_pos.y + (self->cursor.position.y - self->cursor.hotspot.y) * scale.y - self->capture_pos.y - self->params.region_position.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->cursor.texture_id, + cursor_pos, (vec2i){self->cursor.size.x * scale.x, self->cursor.size.y * scale.y}, + (vec2i){0, 0}, self->cursor.size, self->cursor.size, + GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, true); + + self->params.egl->glDisable(GL_SCISSOR_TEST); + } + + self->params.egl->glFlush(); + self->params.egl->glFinish(); + + return 0; +} + +static void gsr_capture_ximage_destroy(gsr_capture *cap) { + gsr_capture_ximage *self = cap->priv; + if(cap->priv) { + gsr_capture_ximage_stop(self); + free((void*)self->params.display_to_capture); + self->params.display_to_capture = NULL; + free(self); + cap->priv = NULL; + } + free(cap); +} + +gsr_capture* gsr_capture_ximage_create(const gsr_capture_ximage_params *params) { + if(!params) { + fprintf(stderr, "gsr error: gsr_capture_ximage_create params is NULL\n"); + return NULL; + } + + gsr_capture *cap = calloc(1, sizeof(gsr_capture)); + if(!cap) + return NULL; + + gsr_capture_ximage *cap_ximage = calloc(1, sizeof(gsr_capture_ximage)); + if(!cap_ximage) { + free(cap); + return NULL; + } + + const char *display_to_capture = strdup(params->display_to_capture); + if(!display_to_capture) { + free(cap); + free(cap_ximage); + return NULL; + } + + cap_ximage->params = *params; + cap_ximage->display = gsr_window_get_display(params->egl->window); + cap_ximage->params.display_to_capture = display_to_capture; + + *cap = (gsr_capture) { + .start = gsr_capture_ximage_start, + .on_event = gsr_capture_ximage_on_event, + .tick = NULL, + .should_stop = NULL, + .capture = gsr_capture_ximage_capture, + .uses_external_image = NULL, + .get_window_id = NULL, + .destroy = gsr_capture_ximage_destroy, + .priv = cap_ximage + }; + + return cap; +} |