aboutsummaryrefslogtreecommitdiff
path: root/src/capture
diff options
context:
space:
mode:
Diffstat (limited to 'src/capture')
-rw-r--r--src/capture/capture.c26
-rw-r--r--src/capture/kms.c319
-rw-r--r--src/capture/nvfbc.c284
-rw-r--r--src/capture/portal.c230
-rw-r--r--src/capture/xcomposite.c113
-rw-r--r--src/capture/ximage.c247
6 files changed, 791 insertions, 428 deletions
diff --git a/src/capture/capture.c b/src/capture/capture.c
index 5fc96d0..bc95300 100644
--- a/src/capture/capture.c
+++ b/src/capture/capture.c
@@ -1,19 +1,19 @@
#include "../../include/capture/capture.h"
#include <assert.h>
-int gsr_capture_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) {
+int gsr_capture_start(gsr_capture *cap, gsr_capture_metadata *capture_metadata) {
assert(!cap->started);
- int res = cap->start(cap, video_codec_context, frame);
+ 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) {
+void gsr_capture_tick(gsr_capture *cap) {
assert(cap->started);
if(cap->tick)
- cap->tick(cap, video_codec_context);
+ cap->tick(cap);
}
void gsr_capture_on_event(gsr_capture *cap, gsr_egl *egl) {
@@ -29,19 +29,9 @@ bool gsr_capture_should_stop(gsr_capture *cap, bool *err) {
return false;
}
-int gsr_capture_capture(gsr_capture *cap, AVFrame *frame, gsr_color_conversion *color_conversion) {
+int gsr_capture_capture(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion) {
assert(cap->started);
- return cap->capture(cap, frame, color_conversion);
-}
-
-void gsr_capture_capture_end(gsr_capture *cap, AVFrame *frame) {
- assert(cap->started);
- if(cap->capture_end)
- cap->capture_end(cap, frame);
-}
-
-gsr_source_color gsr_capture_get_source_color(gsr_capture *cap) {
- return cap->get_source_color(cap);
+ return cap->capture(cap, capture_metadata, color_conversion);
}
bool gsr_capture_uses_external_image(gsr_capture *cap) {
@@ -58,6 +48,6 @@ bool gsr_capture_set_hdr_metadata(gsr_capture *cap, AVMasteringDisplayMetadata *
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
index 9287a8b..36a5355 100644
--- a/src/capture/kms.c
+++ b/src/capture/kms.c
@@ -2,18 +2,21 @@
#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 <libdrm/drm_fourcc.h>
+#include <xf86drm.h>
+#include <drm_fourcc.h>
-#include <libavcodec/avcodec.h>
#include <libavutil/mastering_display_metadata.h>
-#include <libavformat/avformat.h>
+
+#define FIND_CRTC_BY_NAME_TIMEOUT_SECONDS 2.0
#define HDMI_STATIC_METADATA_TYPE1 0
#define HDMI_EOTF_SMPTE_ST2084 2
@@ -49,6 +52,15 @@ typedef struct {
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) {
@@ -81,6 +93,11 @@ static void gsr_capture_kms_stop(gsr_capture_kms *self) {
self->cursor_texture_id = 0;
}
+ // if(self->drm_fd > 0) {
+ // close(self->drm_fd);
+ // self->drm_fd = -1;
+ // }
+
gsr_capture_kms_cleanup_kms_fds(self);
gsr_kms_client_deinit(&self->kms_client);
gsr_cursor_deinit(&self->x11_cursor);
@@ -93,16 +110,12 @@ static int max_int(int a, int b) {
static void gsr_capture_kms_create_input_texture_ids(gsr_capture_kms *self) {
self->params.egl->glGenTextures(1, &self->input_texture_id);
self->params.egl->glBindTexture(GL_TEXTURE_2D, self->input_texture_id);
- self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
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_EDGE);
- self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->params.egl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
@@ -112,8 +125,6 @@ static void gsr_capture_kms_create_input_texture_ids(gsr_capture_kms *self) {
self->params.egl->glGenTextures(1, &self->cursor_texture_id);
self->params.egl->glBindTexture(cursor_texture_id_target, self->cursor_texture_id);
- self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->params.egl->glBindTexture(cursor_texture_id_target, 0);
@@ -144,7 +155,16 @@ static void monitor_callback(const gsr_monitor *monitor, void *userdata) {
fprintf(stderr, "gsr warning: reached max connector ids\n");
}
-static int gsr_capture_kms_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) {
+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);
@@ -156,17 +176,19 @@ static int gsr_capture_kms_start(gsr_capture *cap, AVCodecContext *video_codec_c
if(kms_init_res != 0)
return kms_init_res;
- self->is_x11 = gsr_egl_get_display_server(self->params.egl) == GSR_DISPLAY_SERVER_X11;
+ 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)
- gsr_cursor_init(&self->x11_cursor, self->params.egl, self->params.egl->x11.dpy);
+ 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, connection_type, monitor_callback, &monitor_callback_userdata);
+ 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);
@@ -175,25 +197,29 @@ static int gsr_capture_kms_start(gsr_capture *cap, AVCodecContext *video_codec_c
}
monitor.name = self->params.display_to_capture;
- self->monitor_rotation = drm_monitor_get_display_server_rotation(self->params.egl, &monitor);
+ 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->monitor_rotation == GSR_MONITOR_ROT_90 || self->monitor_rotation == GSR_MONITOR_ROT_270)) {
- self->capture_size.x = monitor.size.y;
- self->capture_size.y = monitor.size.x;
- } else {
+ if(self->is_x11)
self->capture_size = monitor.size;
+ else
+ self->capture_size = rotate_capture_size_if_rotated(self, monitor.size);
+
+ 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;
}
- /* Disable vsync */
- self->params.egl->eglSwapInterval(self->params.egl->egl_display, 0);
-
- video_codec_context->width = FFALIGN(self->capture_size.x, 2);
- video_codec_context->height = FFALIGN(self->capture_size.y, 2);
-
- frame->width = video_codec_context->width;
- frame->height = video_codec_context->height;
+ self->last_time_monitor_check = clock_get_monotonic_seconds();
return 0;
}
@@ -202,19 +228,30 @@ static void gsr_capture_kms_on_event(gsr_capture *cap, gsr_egl *egl) {
if(!self->is_x11)
return;
- XEvent *xev = gsr_egl_get_event_data(egl);
+ XEvent *xev = gsr_window_get_event_data(egl->window);
gsr_cursor_on_event(&self->x11_cursor, xev);
}
-static float monitor_rotation_to_radians(gsr_monitor_rotation rot) {
- switch(rot) {
- case GSR_MONITOR_ROT_0: return 0.0f;
- case GSR_MONITOR_ROT_90: return M_PI_2;
- case GSR_MONITOR_ROT_180: return M_PI;
- case GSR_MONITOR_ROT_270: return M_PI + M_PI_2;
- }
- return 0.0f;
-}
+// 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) {
@@ -366,7 +403,7 @@ static gsr_kms_response_item* find_monitor_drm(gsr_capture_kms *self, bool *capt
}
// Will never happen on wayland unless the target monitor has been disconnected
- if(!drm_fd) {
+ if(!drm_fd && self->is_x11) {
drm_fd = find_largest_drm(&self->kms_response);
*capture_is_combined_plane = true;
}
@@ -381,7 +418,12 @@ static gsr_kms_response_item* find_cursor_drm_if_on_monitor(gsr_capture_kms *sel
return cursor_drm_fd;
}
-static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color_conversion, const gsr_kms_response_item *cursor_drm_fd, int target_x, int target_y, float texture_rotation) {
+static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color_conversion, const gsr_kms_response_item *cursor_drm_fd, vec2i target_pos, 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};
@@ -391,27 +433,33 @@ static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color
break;
case GSR_MONITOR_ROT_90:
cursor_pos = swap_vec2i(cursor_pos);
- cursor_pos.x = self->capture_size.x - cursor_pos.x;
+ 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 = self->capture_size.x - cursor_pos.x;
- cursor_pos.y = self->capture_size.y - cursor_pos.y;
+ 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 = self->capture_size.y - cursor_pos.y;
+ cursor_pos.y = framebuffer_size.y - cursor_pos.y;
// TODO: Remove this horrible hack
cursor_pos.y -= cursor_size.y;
break;
}
- cursor_pos.x += target_x;
- cursor_pos.y += target_y;
+ 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];
@@ -439,36 +487,94 @@ static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color
self->params.egl->eglDestroyImage(self->params.egl->egl_display, cursor_image);
self->params.egl->glEnable(GL_SCISSOR_TEST);
- self->params.egl->glScissor(target_x, target_y, self->capture_size.x, self->capture_size.y);
+ self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y);
gsr_color_conversion_draw(color_conversion, self->cursor_texture_id,
- cursor_pos, cursor_size,
- (vec2i){0, 0}, cursor_size,
- texture_rotation, cursor_texture_id_is_external);
+ 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, int target_x, int target_y) {
- gsr_cursor_tick(&self->x11_cursor, DefaultRootWindow(self->params.egl->x11.dpy));
+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_x + self->x11_cursor.position.x - self->x11_cursor.hotspot.x - capture_pos.x,
- target_y + self->x11_cursor.position.y - self->x11_cursor.hotspot.y - capture_pos.y
+ target_pos.x + (self->x11_cursor.position.x - self->x11_cursor.hotspot.x - capture_pos.x) * scale.x,
+ target_pos.y + (self->x11_cursor.position.y - self->x11_cursor.hotspot.y - capture_pos.y) * scale.y
};
self->params.egl->glEnable(GL_SCISSOR_TEST);
- self->params.egl->glScissor(target_x, target_y, self->capture_size.x, self->capture_size.y);
+ self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y);
gsr_color_conversion_draw(color_conversion, self->x11_cursor.texture_id,
- cursor_pos, self->x11_cursor.size,
- (vec2i){0, 0}, self->x11_cursor.size,
- 0.0f, false);
+ 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 int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_conversion *color_conversion) {
+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);
@@ -487,48 +593,72 @@ static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_c
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)
+ if(!drm_fd) {
+ gsr_capture_kms_cleanup_kms_fds(self);
return -1;
+ }
if(drm_fd->has_hdr_metadata && self->params.hdr && hdr_metadata_is_supported_format(&drm_fd->hdr_metadata))
gsr_kms_set_hdr_metadata(self, drm_fd);
- EGLImage image = gsr_capture_kms_create_egl_image_with_fallback(self, drm_fd);
- if(image) {
- gsr_capture_kms_bind_image_to_input_texture_with_fallback(self, image);
- self->params.egl->eglDestroyImage(self->params.egl->egl_display, image);
- }
+ 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 float texture_rotation = monitor_rotation_to_radians(self->monitor_rotation);
- const int target_x = max_int(0, frame->width / 2 - self->capture_size.x / 2);
- const int target_y = max_int(0, frame->height / 2 - self->capture_size.y / 2);
+ const vec2i target_pos = { max_int(0, 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};
- self->params.egl->glFlush();
- self->params.egl->glFinish();
+ 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,
- (vec2i){target_x, target_y}, self->capture_size,
- capture_pos, self->capture_size,
- texture_rotation, self->external_texture_fallback);
+ 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.
- if(self->is_x11)
- render_x11_cursor(self, color_conversion, capture_pos, target_x, target_y);
- else if(cursor_drm_fd)
- render_drm_cursor(self, color_conversion, cursor_drm_fd, target_x, target_y, texture_rotation);
+ // TODO: This doesn't work properly with software cursor on x11 since it will draw the x11 cursor on top of the cursor already in the framebuffer.
+ // Detect if software cursor is used on x11 somehow.
+ if(self->is_x11) {
+ 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();
+ //self->params.egl->glFlush();
+ //self->params.egl->glFinish();
+
+ gsr_capture_kms_cleanup_kms_fds(self);
return 0;
}
@@ -540,16 +670,6 @@ static bool gsr_capture_kms_should_stop(gsr_capture *cap, bool *err) {
return false;
}
-static void gsr_capture_kms_capture_end(gsr_capture *cap, AVFrame *frame) {
- (void)frame;
- gsr_capture_kms_cleanup_kms_fds(cap->priv);
-}
-
-static gsr_source_color gsr_capture_kms_get_source_color(gsr_capture *cap) {
- (void)cap;
- return GSR_SOURCE_COLOR_RGB;
-}
-
static bool gsr_capture_kms_uses_external_image(gsr_capture *cap) {
(void)cap;
return true;
@@ -575,14 +695,23 @@ static bool gsr_capture_kms_set_hdr_metadata(gsr_capture *cap, AVMasteringDispla
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 = mastering_display_metadata->display_primaries[0][0].num > 0;
- mastering_display_metadata->has_luminance = mastering_display_metadata->max_luminance.num > 0;
+ mastering_display_metadata->has_primaries = true;
+ mastering_display_metadata->has_luminance = true;
return true;
}
-static void gsr_capture_kms_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
- (void)video_codec_context;
+// 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);
@@ -623,13 +752,13 @@ gsr_capture* gsr_capture_kms_create(const gsr_capture_kms_params *params) {
*cap = (gsr_capture) {
.start = gsr_capture_kms_start,
.on_event = gsr_capture_kms_on_event,
- .tick = NULL,
+ //.tick = gsr_capture_kms_tick,
.should_stop = gsr_capture_kms_should_stop,
.capture = gsr_capture_kms_capture,
- .capture_end = gsr_capture_kms_capture_end,
- .get_source_color = gsr_capture_kms_get_source_color,
.uses_external_image = gsr_capture_kms_uses_external_image,
.set_hdr_metadata = gsr_capture_kms_set_hdr_metadata,
+ //.is_damaged = gsr_capture_kms_is_damaged,
+ //.clear_damage = gsr_capture_kms_clear_damage,
.destroy = gsr_capture_kms_destroy,
.priv = cap_kms
};
diff --git a/src/capture/nvfbc.c b/src/capture/nvfbc.c
index b7e6bb5..13b46c3 100644
--- a/src/capture/nvfbc.c
+++ b/src/capture/nvfbc.c
@@ -3,15 +3,16 @@
#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 <libavcodec/avcodec.h>
typedef struct {
gsr_capture_nvfbc_params params;
@@ -26,8 +27,7 @@ typedef struct {
NVFBC_TOGL_SETUP_PARAMS setup_params;
bool supports_direct_cursor;
- bool capture_region;
- uint32_t x, y, width, height;
+ uint32_t width, height;
NVFBC_TRACKING_TYPE tracking_type;
uint32_t output_id;
uint32_t tracking_width, tracking_height;
@@ -102,7 +102,7 @@ static void set_func_ptr(void **dst, void *src) {
}
static bool gsr_capture_nvfbc_load_library(gsr_capture *cap) {
- gsr_capture_nvfbc *cap_nvfbc = cap->priv;
+ gsr_capture_nvfbc *self = cap->priv;
dlerror(); /* clear */
void *lib = dlopen("libnvidia-fbc.so.1", RTLD_LAZY);
@@ -111,106 +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;
}
-/* TODO: check for glx swap control extension string (GLX_EXT_swap_control, etc) */
-static void set_vertical_sync_enabled(gsr_egl *egl, int enabled) {
- int result = 0;
-
- if(egl->glXSwapIntervalEXT) {
- egl->glXSwapIntervalEXT(egl->x11.dpy, egl->x11.window, enabled ? 1 : 0);
- } else if(egl->glXSwapIntervalMESA) {
- result = egl->glXSwapIntervalMESA(enabled ? 1 : 0);
- } else if(egl->glXSwapIntervalSGI) {
- result = egl->glXSwapIntervalSGI(enabled ? 1 : 0);
- } else {
- static int warned = 0;
- if (!warned) {
- warned = 1;
- fprintf(stderr, "gsr warning: setting vertical sync not supported\n");
- }
- }
-
- if(result != 0)
- fprintf(stderr, "gsr warning: setting vertical sync failed\n");
-}
-
-static void gsr_capture_nvfbc_destroy_session(gsr_capture_nvfbc *cap_nvfbc) {
- if(cap_nvfbc->fbc_handle_created && cap_nvfbc->capture_session_created) {
+static void gsr_capture_nvfbc_destroy_session(gsr_capture_nvfbc *self) {
+ if(self->fbc_handle_created && self->capture_session_created) {
NVFBC_DESTROY_CAPTURE_SESSION_PARAMS destroy_capture_params;
memset(&destroy_capture_params, 0, sizeof(destroy_capture_params));
destroy_capture_params.dwVersion = NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER;
- cap_nvfbc->nv_fbc_function_list.nvFBCDestroyCaptureSession(cap_nvfbc->nv_fbc_handle, &destroy_capture_params);
- cap_nvfbc->capture_session_created = false;
+ self->nv_fbc_function_list.nvFBCDestroyCaptureSession(self->nv_fbc_handle, &destroy_capture_params);
+ self->capture_session_created = false;
}
}
-static void gsr_capture_nvfbc_destroy_handle(gsr_capture_nvfbc *cap_nvfbc) {
- if(cap_nvfbc->fbc_handle_created) {
+static void gsr_capture_nvfbc_destroy_handle(gsr_capture_nvfbc *self) {
+ if(self->fbc_handle_created) {
NVFBC_DESTROY_HANDLE_PARAMS destroy_params;
memset(&destroy_params, 0, sizeof(destroy_params));
destroy_params.dwVersion = NVFBC_DESTROY_HANDLE_PARAMS_VER;
- cap_nvfbc->nv_fbc_function_list.nvFBCDestroyHandle(cap_nvfbc->nv_fbc_handle, &destroy_params);
- cap_nvfbc->fbc_handle_created = false;
- cap_nvfbc->nv_fbc_handle = 0;
+ self->nv_fbc_function_list.nvFBCDestroyHandle(self->nv_fbc_handle, &destroy_params);
+ self->fbc_handle_created = false;
+ self->nv_fbc_handle = 0;
}
}
-static void gsr_capture_nvfbc_destroy_session_and_handle(gsr_capture_nvfbc *cap_nvfbc) {
- gsr_capture_nvfbc_destroy_session(cap_nvfbc);
- gsr_capture_nvfbc_destroy_handle(cap_nvfbc);
+static void gsr_capture_nvfbc_destroy_session_and_handle(gsr_capture_nvfbc *self) {
+ gsr_capture_nvfbc_destroy_session(self);
+ gsr_capture_nvfbc_destroy_handle(self);
}
-static int gsr_capture_nvfbc_setup_handle(gsr_capture_nvfbc *cap_nvfbc) {
+static int gsr_capture_nvfbc_setup_handle(gsr_capture_nvfbc *self) {
NVFBCSTATUS status;
NVFBC_CREATE_HANDLE_PARAMS create_params;
memset(&create_params, 0, sizeof(create_params));
create_params.dwVersion = NVFBC_CREATE_HANDLE_PARAMS_VER;
create_params.bExternallyManagedContext = NVFBC_TRUE;
- create_params.glxCtx = cap_nvfbc->params.egl->glx_context;
- create_params.glxFBConfig = cap_nvfbc->params.egl->glx_fb_config;
+ create_params.glxCtx = self->params.egl->glx_context;
+ create_params.glxFBConfig = self->params.egl->glx_fb_config;
- status = cap_nvfbc->nv_fbc_function_list.nvFBCCreateHandle(&cap_nvfbc->nv_fbc_handle, &create_params);
+ status = self->nv_fbc_function_list.nvFBCCreateHandle(&self->nv_fbc_handle, &create_params);
if(status != NVFBC_SUCCESS) {
// Reverse engineering for interoperability
const uint8_t enable_key[] = { 0xac, 0x10, 0xc9, 0x2e, 0xa5, 0xe6, 0x87, 0x4f, 0x8f, 0x4b, 0xf4, 0x61, 0xf8, 0x56, 0x27, 0xe9 };
create_params.privateData = enable_key;
create_params.privateDataSize = 16;
- status = cap_nvfbc->nv_fbc_function_list.nvFBCCreateHandle(&cap_nvfbc->nv_fbc_handle, &create_params);
+ status = self->nv_fbc_function_list.nvFBCCreateHandle(&self->nv_fbc_handle, &create_params);
if(status != NVFBC_SUCCESS) {
- fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle));
+ fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", self->nv_fbc_function_list.nvFBCGetLastErrorStr(self->nv_fbc_handle));
goto error_cleanup;
}
}
- cap_nvfbc->fbc_handle_created = true;
+ self->fbc_handle_created = true;
NVFBC_GET_STATUS_PARAMS status_params;
memset(&status_params, 0, sizeof(status_params));
status_params.dwVersion = NVFBC_GET_STATUS_PARAMS_VER;
- status = cap_nvfbc->nv_fbc_function_list.nvFBCGetStatus(cap_nvfbc->nv_fbc_handle, &status_params);
+ status = self->nv_fbc_function_list.nvFBCGetStatus(self->nv_fbc_handle, &status_params);
if(status != NVFBC_SUCCESS) {
- fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle));
+ fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", self->nv_fbc_function_list.nvFBCGetLastErrorStr(self->nv_fbc_handle));
goto error_cleanup;
}
@@ -219,10 +197,13 @@ static int gsr_capture_nvfbc_setup_handle(gsr_capture_nvfbc *cap_nvfbc) {
goto error_cleanup;
}
- cap_nvfbc->tracking_width = XWidthOfScreen(DefaultScreenOfDisplay(cap_nvfbc->params.egl->x11.dpy));
- cap_nvfbc->tracking_height = XHeightOfScreen(DefaultScreenOfDisplay(cap_nvfbc->params.egl->x11.dpy));
- cap_nvfbc->tracking_type = strcmp(cap_nvfbc->params.display_to_capture, "screen") == 0 ? NVFBC_TRACKING_SCREEN : NVFBC_TRACKING_OUTPUT;
- if(cap_nvfbc->tracking_type == NVFBC_TRACKING_OUTPUT) {
+ 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;
@@ -233,94 +214,87 @@ static int gsr_capture_nvfbc_setup_handle(gsr_capture_nvfbc *cap_nvfbc) {
goto error_cleanup;
}
- cap_nvfbc->output_id = get_output_id_from_display_name(status_params.outputs, status_params.dwOutputNum, cap_nvfbc->params.display_to_capture, &cap_nvfbc->tracking_width, &cap_nvfbc->tracking_height);
- if(cap_nvfbc->output_id == 0) {
- fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: display '%s' not found\n", cap_nvfbc->params.display_to_capture);
+ self->output_id = get_output_id_from_display_name(status_params.outputs, status_params.dwOutputNum, self->params.display_to_capture, &self->tracking_width, &self->tracking_height);
+ if(self->output_id == 0) {
+ fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: display '%s' not found\n", self->params.display_to_capture);
goto error_cleanup;
}
}
+ self->width = self->tracking_width;
+ self->height = self->tracking_height;
return 0;
error_cleanup:
- gsr_capture_nvfbc_destroy_session_and_handle(cap_nvfbc);
+ gsr_capture_nvfbc_destroy_session_and_handle(self);
return -1;
}
-static int gsr_capture_nvfbc_setup_session(gsr_capture_nvfbc *cap_nvfbc) {
+static int gsr_capture_nvfbc_setup_session(gsr_capture_nvfbc *self) {
NVFBC_CREATE_CAPTURE_SESSION_PARAMS create_capture_params;
memset(&create_capture_params, 0, sizeof(create_capture_params));
create_capture_params.dwVersion = NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER;
create_capture_params.eCaptureType = NVFBC_CAPTURE_TO_GL;
- create_capture_params.bWithCursor = (!cap_nvfbc->params.direct_capture || cap_nvfbc->supports_direct_cursor) ? NVFBC_TRUE : NVFBC_FALSE;
- if(!cap_nvfbc->params.record_cursor)
+ create_capture_params.bWithCursor = (!self->params.direct_capture || self->supports_direct_cursor) ? NVFBC_TRUE : NVFBC_FALSE;
+ if(!self->params.record_cursor)
create_capture_params.bWithCursor = false;
- if(cap_nvfbc->capture_region)
- create_capture_params.captureBox = (NVFBC_BOX){ cap_nvfbc->x, cap_nvfbc->y, cap_nvfbc->width, cap_nvfbc->height };
- create_capture_params.eTrackingType = cap_nvfbc->tracking_type;
- create_capture_params.dwSamplingRateMs = (uint32_t)ceilf(1000.0f / (float)cap_nvfbc->params.fps);
- create_capture_params.bAllowDirectCapture = cap_nvfbc->params.direct_capture ? NVFBC_TRUE : NVFBC_FALSE;
- create_capture_params.bPushModel = cap_nvfbc->params.direct_capture ? NVFBC_TRUE : NVFBC_FALSE;
+ create_capture_params.eTrackingType = self->tracking_type;
+ create_capture_params.dwSamplingRateMs = (uint32_t)ceilf(1000.0f / (float)self->params.fps);
+ create_capture_params.bAllowDirectCapture = self->params.direct_capture ? NVFBC_TRUE : NVFBC_FALSE;
+ create_capture_params.bPushModel = self->params.direct_capture ? NVFBC_TRUE : NVFBC_FALSE;
create_capture_params.bDisableAutoModesetRecovery = true;
- if(cap_nvfbc->tracking_type == NVFBC_TRACKING_OUTPUT)
- create_capture_params.dwOutputId = cap_nvfbc->output_id;
+ if(self->tracking_type == NVFBC_TRACKING_OUTPUT)
+ create_capture_params.dwOutputId = self->output_id;
- NVFBCSTATUS status = cap_nvfbc->nv_fbc_function_list.nvFBCCreateCaptureSession(cap_nvfbc->nv_fbc_handle, &create_capture_params);
+ NVFBCSTATUS status = self->nv_fbc_function_list.nvFBCCreateCaptureSession(self->nv_fbc_handle, &create_capture_params);
if(status != NVFBC_SUCCESS) {
- fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle));
+ fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", self->nv_fbc_function_list.nvFBCGetLastErrorStr(self->nv_fbc_handle));
return -1;
}
- cap_nvfbc->capture_session_created = true;
+ self->capture_session_created = true;
- memset(&cap_nvfbc->setup_params, 0, sizeof(cap_nvfbc->setup_params));
- cap_nvfbc->setup_params.dwVersion = NVFBC_TOGL_SETUP_PARAMS_VER;
- cap_nvfbc->setup_params.eBufferFormat = NVFBC_BUFFER_FORMAT_BGRA;
+ memset(&self->setup_params, 0, sizeof(self->setup_params));
+ self->setup_params.dwVersion = NVFBC_TOGL_SETUP_PARAMS_VER;
+ self->setup_params.eBufferFormat = NVFBC_BUFFER_FORMAT_BGRA;
- status = cap_nvfbc->nv_fbc_function_list.nvFBCToGLSetUp(cap_nvfbc->nv_fbc_handle, &cap_nvfbc->setup_params);
+ status = self->nv_fbc_function_list.nvFBCToGLSetUp(self->nv_fbc_handle, &self->setup_params);
if(status != NVFBC_SUCCESS) {
- fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle));
- gsr_capture_nvfbc_destroy_session(cap_nvfbc);
+ fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", self->nv_fbc_function_list.nvFBCGetLastErrorStr(self->nv_fbc_handle));
+ gsr_capture_nvfbc_destroy_session(self);
return -1;
}
return 0;
}
-static void gsr_capture_nvfbc_stop(gsr_capture_nvfbc *cap_nvfbc) {
- gsr_capture_nvfbc_destroy_session_and_handle(cap_nvfbc);
- if(cap_nvfbc->library) {
- dlclose(cap_nvfbc->library);
- cap_nvfbc->library = NULL;
+static void gsr_capture_nvfbc_stop(gsr_capture_nvfbc *self) {
+ gsr_capture_nvfbc_destroy_session_and_handle(self);
+ if(self->library) {
+ dlclose(self->library);
+ self->library = NULL;
}
- if(cap_nvfbc->params.display_to_capture) {
- free((void*)cap_nvfbc->params.display_to_capture);
- cap_nvfbc->params.display_to_capture = NULL;
+ if(self->params.display_to_capture) {
+ free((void*)self->params.display_to_capture);
+ self->params.display_to_capture = NULL;
}
}
-static int gsr_capture_nvfbc_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) {
- gsr_capture_nvfbc *cap_nvfbc = cap->priv;
+static int gsr_capture_nvfbc_start(gsr_capture *cap, gsr_capture_metadata *capture_metadata) {
+ gsr_capture_nvfbc *self = cap->priv;
if(!gsr_capture_nvfbc_load_library(cap))
return -1;
- cap_nvfbc->x = max_int(cap_nvfbc->params.pos.x, 0);
- cap_nvfbc->y = max_int(cap_nvfbc->params.pos.y, 0);
- cap_nvfbc->width = max_int(cap_nvfbc->params.size.x, 0);
- cap_nvfbc->height = max_int(cap_nvfbc->params.size.y, 0);
-
- cap_nvfbc->capture_region = (cap_nvfbc->x > 0 || cap_nvfbc->y > 0 || cap_nvfbc->width > 0 || cap_nvfbc->height > 0);
-
- cap_nvfbc->supports_direct_cursor = false;
+ self->supports_direct_cursor = false;
int driver_major_version = 0;
int driver_minor_version = 0;
- if(cap_nvfbc->params.direct_capture && get_driver_version(&driver_major_version, &driver_minor_version)) {
- fprintf(stderr, "Info: detected nvidia version: %d.%d\n", driver_major_version, driver_minor_version);
+ if(self->params.direct_capture && get_driver_version(&driver_major_version, &driver_minor_version)) {
+ fprintf(stderr, "gsr info: detected nvidia version: %d.%d\n", driver_major_version, driver_minor_version);
// TODO:
if(version_at_least(driver_major_version, driver_minor_version, 515, 57) && version_less_than(driver_major_version, driver_minor_version, 520, 56)) {
- cap_nvfbc->params.direct_capture = false;
- 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");
+ self->params.direct_capture = false;
+ fprintf(stderr, "gsr 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:
@@ -328,68 +302,77 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap, AVCodecContext *video_codec
/*
if(direct_capture) {
if(version_at_least(driver_major_version, driver_minor_version, 515, 57))
- cap_nvfbc->supports_direct_cursor = true;
+ self->supports_direct_cursor = true;
else
- fprintf(stderr, "Info: capturing \"screen-direct\" but driver version appears to be less than 515.57. Disabling capture of cursor. Please update your driver if you want to capture your cursor or record \"screen\" instead.\n");
+ fprintf(stderr, "gsr info: capturing \"screen-direct\" but driver version appears to be less than 515.57. Disabling capture of cursor. Please update your driver if you want to capture your cursor or record \"screen\" instead.\n");
}
*/
}
- if(gsr_capture_nvfbc_setup_handle(cap_nvfbc) != 0) {
+ if(gsr_capture_nvfbc_setup_handle(self) != 0) {
goto error_cleanup;
}
- if(gsr_capture_nvfbc_setup_session(cap_nvfbc) != 0) {
+ if(gsr_capture_nvfbc_setup_session(self) != 0) {
goto error_cleanup;
}
- if(cap_nvfbc->capture_region) {
- video_codec_context->width = FFALIGN(cap_nvfbc->width, 2);
- video_codec_context->height = FFALIGN(cap_nvfbc->height, 2);
- } else {
- video_codec_context->width = FFALIGN(cap_nvfbc->tracking_width, 2);
- video_codec_context->height = FFALIGN(cap_nvfbc->tracking_height, 2);
- }
-
- frame->width = video_codec_context->width;
- frame->height = video_codec_context->height;
+ capture_metadata->width = self->tracking_width;
+ capture_metadata->height = self->tracking_height;
- /* Disable vsync */
- set_vertical_sync_enabled(cap_nvfbc->params.egl, 0);
+ 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;
+ }
return 0;
error_cleanup:
- gsr_capture_nvfbc_stop(cap_nvfbc);
+ gsr_capture_nvfbc_stop(self);
return -1;
}
-static int gsr_capture_nvfbc_capture(gsr_capture *cap, AVFrame *frame, gsr_color_conversion *color_conversion) {
- gsr_capture_nvfbc *cap_nvfbc = cap->priv;
+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(cap_nvfbc->nvfbc_needs_recreate) {
+ if(self->nvfbc_needs_recreate) {
const double now = clock_get_monotonic_seconds();
- if(now - cap_nvfbc->nvfbc_dead_start >= nvfbc_recreate_retry_time_seconds) {
- cap_nvfbc->nvfbc_dead_start = now;
- gsr_capture_nvfbc_destroy_session_and_handle(cap_nvfbc);
+ if(now - self->nvfbc_dead_start >= nvfbc_recreate_retry_time_seconds) {
+ self->nvfbc_dead_start = now;
+ gsr_capture_nvfbc_destroy_session_and_handle(self);
- if(gsr_capture_nvfbc_setup_handle(cap_nvfbc) != 0) {
+ if(gsr_capture_nvfbc_setup_handle(self) != 0) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_capture failed to recreate nvfbc handle, trying again in %f second(s)\n", nvfbc_recreate_retry_time_seconds);
return -1;
}
-
- if(gsr_capture_nvfbc_setup_session(cap_nvfbc) != 0) {
+
+ if(gsr_capture_nvfbc_setup_session(self) != 0) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_capture failed to recreate nvfbc session, trying again in %f second(s)\n", nvfbc_recreate_retry_time_seconds);
return -1;
}
- cap_nvfbc->nvfbc_needs_recreate = false;
+ self->nvfbc_needs_recreate = false;
} else {
return 0;
}
}
+ 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);
+
+ 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));
@@ -400,37 +383,32 @@ static int gsr_capture_nvfbc_capture(gsr_capture *cap, AVFrame *frame, gsr_color
grab_params.pFrameGrabInfo = &frame_info;
grab_params.dwTimeoutMs = 0;
- NVFBCSTATUS status = cap_nvfbc->nv_fbc_function_list.nvFBCToGLGrabFrame(cap_nvfbc->nv_fbc_handle, &grab_params);
+ NVFBCSTATUS status = self->nv_fbc_function_list.nvFBCToGLGrabFrame(self->nv_fbc_handle, &grab_params);
if(status != NVFBC_SUCCESS) {
- fprintf(stderr, "gsr error: gsr_capture_nvfbc_capture failed: %s (%d), recreating session after %f second(s)\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle), status, nvfbc_recreate_retry_time_seconds);
- cap_nvfbc->nvfbc_needs_recreate = true;
- cap_nvfbc->nvfbc_dead_start = clock_get_monotonic_seconds();
+ fprintf(stderr, "gsr error: gsr_capture_nvfbc_capture failed: %s (%d), recreating session after %f second(s)\n", self->nv_fbc_function_list.nvFBCGetLastErrorStr(self->nv_fbc_handle), status, nvfbc_recreate_retry_time_seconds);
+ self->nvfbc_needs_recreate = true;
+ self->nvfbc_dead_start = clock_get_monotonic_seconds();
return 0;
}
- cap_nvfbc->params.egl->glFlush();
- cap_nvfbc->params.egl->glFinish();
+ //self->params.egl->glFlush();
+ //self->params.egl->glFinish();
- gsr_color_conversion_draw(color_conversion, cap_nvfbc->setup_params.dwTextures[grab_params.dwTextureIndex],
- (vec2i){0, 0}, (vec2i){frame->width, frame->height},
- (vec2i){0, 0}, (vec2i){frame->width, frame->height},
- 0.0f, false);
+ gsr_color_conversion_draw(color_conversion, self->setup_params.dwTextures[grab_params.dwTextureIndex],
+ target_pos, (vec2i){output_size.x, output_size.y},
+ self->params.region_position, frame_size, original_frame_size,
+ GSR_ROT_0, GSR_SOURCE_COLOR_BGR, false, false);
- cap_nvfbc->params.egl->glFlush();
- cap_nvfbc->params.egl->glFinish();
+ //self->params.egl->glFlush();
+ //self->params.egl->glFinish();
return 0;
}
-static gsr_source_color gsr_capture_nvfbc_get_source_color(gsr_capture *cap) {
- (void)cap;
- return GSR_SOURCE_COLOR_BGR;
-}
-
-static void gsr_capture_nvfbc_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
- (void)video_codec_context;
- gsr_capture_nvfbc *cap_nvfbc = cap->priv;
- gsr_capture_nvfbc_stop(cap_nvfbc);
+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);
}
@@ -465,14 +443,12 @@ gsr_capture* gsr_capture_nvfbc_create(const gsr_capture_nvfbc_params *params) {
cap_nvfbc->params = *params;
cap_nvfbc->params.display_to_capture = display_to_capture;
cap_nvfbc->params.fps = max_int(cap_nvfbc->params.fps, 1);
-
+
*cap = (gsr_capture) {
.start = gsr_capture_nvfbc_start,
.tick = NULL,
.should_stop = NULL,
.capture = gsr_capture_nvfbc_capture,
- .capture_end = NULL,
- .get_source_color = gsr_capture_nvfbc_get_source_color,
.uses_external_image = NULL,
.destroy = gsr_capture_nvfbc_destroy,
.priv = cap_nvfbc
diff --git a/src/capture/portal.c b/src/capture/portal.c
index 95470c1..d2217d1 100644
--- a/src/capture/portal.c
+++ b/src/capture/portal.c
@@ -3,14 +3,22 @@
#include "../../include/egl.h"
#include "../../include/utils.h"
#include "../../include/dbus.h"
-#include "../../include/pipewire.h"
+#include "../../include/pipewire_video.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
+#include <limits.h>
#include <assert.h>
-#include <libavcodec/avcodec.h>
+#define PORTAL_CAPTURE_CANCELED_BY_USER_EXIT_CODE 60
+
+typedef enum {
+ PORTAL_CAPTURE_SETUP_IDLE,
+ PORTAL_CAPTURE_SETUP_IN_PROGRESS,
+ PORTAL_CAPTURE_SETUP_FINISHED,
+ PORTAL_CAPTURE_SETUP_FAILED
+} gsr_portal_capture_setup_state;
typedef struct {
gsr_capture_portal_params params;
@@ -20,20 +28,29 @@ typedef struct {
gsr_dbus dbus;
char *session_handle;
- gsr_pipewire pipewire;
+ gsr_pipewire_video pipewire;
vec2i capture_size;
- int plane_fds[GSR_PIPEWIRE_DMABUF_MAX_PLANES];
- int num_plane_fds;
+ gsr_pipewire_video_dmabuf_data dmabuf_data[GSR_PIPEWIRE_VIDEO_DMABUF_MAX_PLANES];
+ int num_dmabuf_data;
+
+ gsr_pipewire_video_region region;
+ gsr_pipewire_video_region cursor_region;
+ uint32_t pipewire_fourcc;
+ uint64_t pipewire_modifiers;
+ bool using_external_image;
+
+ bool should_stop;
+ bool stop_is_error;
} gsr_capture_portal;
static void gsr_capture_portal_cleanup_plane_fds(gsr_capture_portal *self) {
- for(int i = 0; i < self->num_plane_fds; ++i) {
- if(self->plane_fds[i] > 0) {
- close(self->plane_fds[i]);
- self->plane_fds[i] = 0;
+ for(int i = 0; i < self->num_dmabuf_data; ++i) {
+ if(self->dmabuf_data[i].fd > 0) {
+ close(self->dmabuf_data[i].fd);
+ self->dmabuf_data[i].fd = 0;
}
}
- self->num_plane_fds = 0;
+ self->num_dmabuf_data = 0;
}
static void gsr_capture_portal_stop(gsr_capture_portal *self) {
@@ -53,38 +70,25 @@ static void gsr_capture_portal_stop(gsr_capture_portal *self) {
}
gsr_capture_portal_cleanup_plane_fds(self);
-
- gsr_pipewire_deinit(&self->pipewire);
-
- if(self->session_handle) {
- free(self->session_handle);
- self->session_handle = NULL;
- }
-
+ gsr_pipewire_video_deinit(&self->pipewire);
gsr_dbus_deinit(&self->dbus);
}
static void gsr_capture_portal_create_input_textures(gsr_capture_portal *self) {
self->params.egl->glGenTextures(1, &self->texture_map.texture_id);
self->params.egl->glBindTexture(GL_TEXTURE_2D, self->texture_map.texture_id);
- self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
self->params.egl->glGenTextures(1, &self->texture_map.external_texture_id);
self->params.egl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, self->texture_map.external_texture_id);
- self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->params.egl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
self->params.egl->glGenTextures(1, &self->texture_map.cursor_texture_id);
self->params.egl->glBindTexture(GL_TEXTURE_2D, self->texture_map.cursor_texture_id);
- self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
@@ -230,17 +234,13 @@ static int gsr_capture_portal_setup_dbus(gsr_capture_portal *self, int *pipewire
}
static bool gsr_capture_portal_get_frame_dimensions(gsr_capture_portal *self) {
- gsr_pipewire_region region = {0, 0, 0, 0};
- gsr_pipewire_region cursor_region = {0, 0, 0, 0};
fprintf(stderr, "gsr info: gsr_capture_portal_start: waiting for pipewire negotiation\n");
const double start_time = clock_get_monotonic_seconds();
while(clock_get_monotonic_seconds() - start_time < 5.0) {
- bool uses_external_image = false;
- if(gsr_pipewire_map_texture(&self->pipewire, self->texture_map, &region, &cursor_region, self->plane_fds, &self->num_plane_fds, &uses_external_image)) {
- gsr_capture_portal_cleanup_plane_fds(self);
- self->capture_size.x = region.width;
- self->capture_size.y = region.height;
+ if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, &self->region, &self->cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &self->pipewire_fourcc, &self->pipewire_modifiers, &self->using_external_image)) {
+ self->capture_size.x = self->region.width;
+ self->capture_size.y = self->region.height;
fprintf(stderr, "gsr info: gsr_capture_portal_start: pipewire negotiation finished\n");
return true;
}
@@ -251,55 +251,62 @@ static bool gsr_capture_portal_get_frame_dimensions(gsr_capture_portal *self) {
return false;
}
-static int gsr_capture_portal_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) {
- gsr_capture_portal *self = cap->priv;
-
+static int gsr_capture_portal_setup(gsr_capture_portal *self, int fps) {
gsr_capture_portal_create_input_textures(self);
int pipewire_fd = 0;
uint32_t pipewire_node = 0;
const int response_status = gsr_capture_portal_setup_dbus(self, &pipewire_fd, &pipewire_node);
if(response_status != 0) {
- gsr_capture_portal_stop(self);
// Response status values:
// 0: Success, the request is carried out
// 1: The user cancelled the interaction
// 2: The user interaction was ended in some other way
// Response status value 2 happens usually if there was some kind of error in the desktop portal on the system
if(response_status == 2) {
- fprintf(stderr, "gsr error: gsr_capture_portal_start: desktop portal capture failed. Either you Wayland compositor doesn't support desktop portal capture or it's incorrectly setup on your system\n");
+ fprintf(stderr, "gsr error: gsr_capture_portal_setup: desktop portal capture failed. Either you Wayland compositor doesn't support desktop portal capture or it's incorrectly setup on your system\n");
return 50;
} else if(response_status == 1) {
- fprintf(stderr, "gsr error: gsr_capture_portal_start: desktop portal capture failed. It seems like desktop portal capture was canceled by the user.\n");
- return 60;
+ fprintf(stderr, "gsr error: gsr_capture_portal_setup: desktop portal capture failed. It seems like desktop portal capture was canceled by the user.\n");
+ return PORTAL_CAPTURE_CANCELED_BY_USER_EXIT_CODE;
} else {
return -1;
}
}
- fprintf(stderr, "gsr info: gsr_capture_portal_start: setting up pipewire\n");
+ fprintf(stderr, "gsr info: gsr_capture_portal_setup: setting up pipewire\n");
/* TODO: support hdr when pipewire supports it */
/* gsr_pipewire closes the pipewire fd, even on failure */
- if(!gsr_pipewire_init(&self->pipewire, pipewire_fd, pipewire_node, video_codec_context->framerate.num, self->params.record_cursor, self->params.egl)) {
- fprintf(stderr, "gsr error: gsr_capture_portal_start: failed to setup pipewire with fd: %d, node: %" PRIu32 "\n", pipewire_fd, pipewire_node);
- gsr_capture_portal_stop(self);
+ if(!gsr_pipewire_video_init(&self->pipewire, pipewire_fd, pipewire_node, fps, self->params.record_cursor, self->params.egl)) {
+ fprintf(stderr, "gsr error: gsr_capture_portal_setup: failed to setup pipewire with fd: %d, node: %" PRIu32 "\n", pipewire_fd, pipewire_node);
return -1;
}
- fprintf(stderr, "gsr info: gsr_capture_portal_start: pipewire setup finished\n");
+ fprintf(stderr, "gsr info: gsr_capture_portal_setup: pipewire setup finished\n");
- if(!gsr_capture_portal_get_frame_dimensions(self)) {
- gsr_capture_portal_stop(self);
+ if(!gsr_capture_portal_get_frame_dimensions(self))
return -1;
- }
- /* Disable vsync */
- self->params.egl->eglSwapInterval(self->params.egl->egl_display, 0);
+ return 0;
+}
+
+static int gsr_capture_portal_start(gsr_capture *cap, gsr_capture_metadata *capture_metadata) {
+ gsr_capture_portal *self = cap->priv;
+
+ const int result = gsr_capture_portal_setup(self, capture_metadata->fps);
+ if(result != 0) {
+ gsr_capture_portal_stop(self);
+ return result;
+ }
- video_codec_context->width = FFALIGN(self->capture_size.x, 2);
- video_codec_context->height = FFALIGN(self->capture_size.y, 2);
+ if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) {
+ 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;
+ }
- frame->width = video_codec_context->width;
- frame->height = video_codec_context->height;
return 0;
}
@@ -307,66 +314,84 @@ static int max_int(int a, int b) {
return a > b ? a : b;
}
-static int gsr_capture_portal_capture(gsr_capture *cap, AVFrame *frame, gsr_color_conversion *color_conversion) {
- (void)frame;
+static bool gsr_capture_portal_capture_has_synchronous_task(gsr_capture *cap) {
+ gsr_capture_portal *self = cap->priv;
+ return gsr_pipewire_video_should_restart(&self->pipewire);
+}
+
+static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion) {
(void)color_conversion;
gsr_capture_portal *self = cap->priv;
- gsr_capture_portal_cleanup_plane_fds(self);
+ if(self->should_stop)
+ return -1;
- /* TODO: Handle formats other than RGB(a) */
- gsr_pipewire_region region = {0, 0, 0, 0};
- gsr_pipewire_region cursor_region = {0, 0, 0, 0};
- bool using_external_image = false;
- if(gsr_pipewire_map_texture(&self->pipewire, self->texture_map, &region, &cursor_region, self->plane_fds, &self->num_plane_fds, &using_external_image)) {
- if(region.width != self->capture_size.x || region.height != self->capture_size.y) {
- gsr_color_conversion_clear(color_conversion);
- self->capture_size.x = region.width;
- self->capture_size.y = region.height;
+ if(gsr_pipewire_video_should_restart(&self->pipewire)) {
+ fprintf(stderr, "gsr info: gsr_capture_portal_capture: pipewire capture was paused, trying to start capture again\n");
+ gsr_capture_portal_stop(self);
+ const int result = gsr_capture_portal_setup(self, capture_metadata->fps);
+ if(result != 0) {
+ self->stop_is_error = result != PORTAL_CAPTURE_CANCELED_BY_USER_EXIT_CODE;
+ self->should_stop = true;
}
+ return -1;
}
- self->params.egl->glFlush();
- self->params.egl->glFinish();
+ /* TODO: Handle formats other than RGB(A) */
+ if(self->num_dmabuf_data == 0) {
+ if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, &self->region, &self->cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &self->pipewire_fourcc, &self->pipewire_modifiers, &self->using_external_image)) {
+ if(self->region.width != self->capture_size.x || self->region.height != self->capture_size.y) {
+ self->capture_size.x = self->region.width;
+ self->capture_size.y = self->region.height;
+ gsr_color_conversion_clear(color_conversion);
+ }
+ } else {
+ return -1;
+ }
+ }
+
+ const bool is_scaled = self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0;
+ vec2i output_size = is_scaled ? self->params.output_resolution : self->capture_size;
+ output_size = scale_keep_aspect_ratio(self->capture_size, output_size);
- const int target_x = max_int(0, frame->width / 2 - self->capture_size.x / 2);
- const int target_y = max_int(0, frame->height / 2 - self->capture_size.y / 2);
+ const vec2i target_pos = { max_int(0, 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();
- gsr_color_conversion_draw(color_conversion, using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id,
- (vec2i){target_x, target_y}, self->capture_size,
- (vec2i){region.x, region.y}, self->capture_size,
- 0.0f, using_external_image);
+ // TODO: Handle region crop
+
+ gsr_color_conversion_draw(color_conversion, self->using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id,
+ target_pos, output_size,
+ (vec2i){self->region.x, self->region.y}, self->capture_size, self->capture_size,
+ GSR_ROT_0, GSR_SOURCE_COLOR_RGB, self->using_external_image, false);
+
+ if(self->params.record_cursor && self->texture_map.cursor_texture_id > 0 && self->cursor_region.width > 0) {
+ const vec2d scale = {
+ self->capture_size.x == 0 ? 0 : (double)output_size.x / (double)self->capture_size.x,
+ self->capture_size.y == 0 ? 0 : (double)output_size.y / (double)self->capture_size.y
+ };
- if(self->params.record_cursor) {
const vec2i cursor_pos = {
- target_x + cursor_region.x,
- target_y + cursor_region.y
+ target_pos.x + (self->cursor_region.x * scale.x),
+ target_pos.y + (self->cursor_region.y * scale.y)
};
self->params.egl->glEnable(GL_SCISSOR_TEST);
- self->params.egl->glScissor(target_x, target_y, self->capture_size.x, self->capture_size.y);
+ self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y);
gsr_color_conversion_draw(color_conversion, self->texture_map.cursor_texture_id,
- (vec2i){cursor_pos.x, cursor_pos.y}, (vec2i){cursor_region.width, cursor_region.height},
- (vec2i){0, 0}, (vec2i){cursor_region.width, cursor_region.height},
- 0.0f, false);
+ (vec2i){cursor_pos.x, cursor_pos.y}, (vec2i){self->cursor_region.width * scale.x, self->cursor_region.height * scale.y},
+ (vec2i){0, 0}, (vec2i){self->cursor_region.width, self->cursor_region.height}, (vec2i){self->cursor_region.width, self->cursor_region.height},
+ GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, true);
self->params.egl->glDisable(GL_SCISSOR_TEST);
}
- self->params.egl->glFlush();
- self->params.egl->glFinish();
+ //self->params.egl->glFlush();
+ //self->params.egl->glFinish();
- return 0;
-}
-
-static void gsr_capture_portal_capture_end(gsr_capture *cap, AVFrame *frame) {
- (void)frame;
- gsr_capture_portal *self = cap->priv;
gsr_capture_portal_cleanup_plane_fds(self);
-}
-static gsr_source_color gsr_capture_portal_get_source_color(gsr_capture *cap) {
- (void)cap;
- return GSR_SOURCE_COLOR_RGB;
+ return 0;
}
static bool gsr_capture_portal_uses_external_image(gsr_capture *cap) {
@@ -374,21 +399,27 @@ static bool gsr_capture_portal_uses_external_image(gsr_capture *cap) {
return true;
}
+static bool gsr_capture_portal_should_stop(gsr_capture *cap, bool *err) {
+ gsr_capture_portal *self = cap->priv;
+ if(err)
+ *err = self->stop_is_error;
+ return self->should_stop;
+}
+
static bool gsr_capture_portal_is_damaged(gsr_capture *cap) {
gsr_capture_portal *self = cap->priv;
- return gsr_pipewire_is_damaged(&self->pipewire);
+ return gsr_pipewire_video_is_damaged(&self->pipewire);
}
static void gsr_capture_portal_clear_damage(gsr_capture *cap) {
gsr_capture_portal *self = cap->priv;
- gsr_pipewire_clear_damage(&self->pipewire);
+ gsr_pipewire_video_clear_damage(&self->pipewire);
}
-static void gsr_capture_portal_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
- (void)video_codec_context;
- gsr_capture_portal *cap_portal = cap->priv;
+static void gsr_capture_portal_destroy(gsr_capture *cap) {
+ gsr_capture_portal *self = cap->priv;
if(cap->priv) {
- gsr_capture_portal_stop(cap_portal);
+ gsr_capture_portal_stop(self);
free(cap->priv);
cap->priv = NULL;
}
@@ -416,10 +447,9 @@ gsr_capture* gsr_capture_portal_create(const gsr_capture_portal_params *params)
*cap = (gsr_capture) {
.start = gsr_capture_portal_start,
.tick = NULL,
- .should_stop = NULL,
+ .should_stop = gsr_capture_portal_should_stop,
+ .capture_has_synchronous_task = gsr_capture_portal_capture_has_synchronous_task,
.capture = gsr_capture_portal_capture,
- .capture_end = gsr_capture_portal_capture_end,
- .get_source_color = gsr_capture_portal_get_source_color,
.uses_external_image = gsr_capture_portal_uses_external_image,
.is_damaged = gsr_capture_portal_is_damaged,
.clear_damage = gsr_capture_portal_clear_damage,
diff --git a/src/capture/xcomposite.c b/src/capture/xcomposite.c
index 1b6021b..db41f63 100644
--- a/src/capture/xcomposite.c
+++ b/src/capture/xcomposite.c
@@ -3,6 +3,7 @@
#include "../../include/utils.h"
#include "../../include/cursor.h"
#include "../../include/color_conversion.h"
+#include "../../include/window/window.h"
#include <stdlib.h>
#include <stdio.h>
@@ -11,11 +12,9 @@
#include <X11/Xlib.h>
-#include <libavutil/frame.h>
-#include <libavcodec/avcodec.h>
-
typedef struct {
gsr_capture_xcomposite_params params;
+ Display *display;
bool should_stop;
bool stop_is_error;
@@ -60,16 +59,16 @@ static Window get_focused_window(Display *display, Atom net_active_window_atom)
return None;
}
-static int gsr_capture_xcomposite_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) {
+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->params.egl->x11.dpy, "_NET_ACTIVE_WINDOW", False);
+ 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->params.egl->x11.dpy, self->net_active_window_atom);
+ self->window = get_focused_window(self->display, self->net_active_window_atom);
} else {
self->window = self->params.window;
}
@@ -77,7 +76,7 @@ static int gsr_capture_xcomposite_start(gsr_capture *cap, AVCodecContext *video_
/* TODO: Do these in tick, and allow error if follow_focused */
XWindowAttributes attr;
- if(!XGetWindowAttributes(self->params.egl->x11.dpy, self->window, &attr) && !self->params.follow_focused) {
+ 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;
}
@@ -86,19 +85,17 @@ static int gsr_capture_xcomposite_start(gsr_capture *cap, AVCodecContext *video_
self->window_size.y = max_int(attr.height, 0);
if(self->params.follow_focused)
- XSelectInput(self->params.egl->x11.dpy, DefaultRootWindow(self->params.egl->x11.dpy), PropertyChangeMask);
+ 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->params.egl->x11.dpy, self->window, StructureNotifyMask | ExposureMask);
+ XSelectInput(self->display, self->window, StructureNotifyMask | ExposureMask);
- /* Disable vsync */
- self->params.egl->eglSwapInterval(self->params.egl->egl_display, 0);
- if(window_texture_init(&self->window_texture, self->params.egl->x11.dpy, 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", self->window);
+ 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->params.egl->x11.dpy) != 0) {
+ if(gsr_cursor_init(&self->cursor, self->params.egl, self->display) != 0) {
gsr_capture_xcomposite_stop(self);
return -1;
}
@@ -111,23 +108,19 @@ static int gsr_capture_xcomposite_start(gsr_capture *cap, AVCodecContext *video_
self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &self->texture_size.y);
self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
- vec2i video_size = self->texture_size;
-
- if(self->params.region_size.x > 0 && self->params.region_size.y > 0)
- video_size = self->params.region_size;
-
- video_codec_context->width = FFALIGN(video_size.x, 2);
- video_codec_context->height = FFALIGN(video_size.y, 2);
-
- frame->width = video_codec_context->width;
- frame->height = video_codec_context->height;
+ 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, AVCodecContext *video_codec_context) {
- (void)video_codec_context;
+static void gsr_capture_xcomposite_tick(gsr_capture *cap) {
gsr_capture_xcomposite *self = cap->priv;
if(self->params.follow_focused && !self->follow_focused_initialized) {
@@ -136,24 +129,24 @@ static void gsr_capture_xcomposite_tick(gsr_capture *cap, AVCodecContext *video_
if(self->init_new_window) {
self->init_new_window = false;
- Window focused_window = get_focused_window(self->params.egl->x11.dpy, self->net_active_window_atom);
+ 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->params.egl->x11.dpy, self->window, 0);
+ XSelectInput(self->display, self->window, 0);
self->window = focused_window;
- XSelectInput(self->params.egl->x11.dpy, self->window, StructureNotifyMask | ExposureMask);
+ XSelectInput(self->display, self->window, StructureNotifyMask | ExposureMask);
XWindowAttributes attr;
attr.width = 0;
attr.height = 0;
- if(!XGetWindowAttributes(self->params.egl->x11.dpy, self->window, &attr))
+ 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->params.egl->x11.dpy, self->window, self->params.egl); // TODO: Do not do the below window_texture_on_resize after this
+ 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;
@@ -193,7 +186,7 @@ static void gsr_capture_xcomposite_tick(gsr_capture *cap, AVCodecContext *video_
static void gsr_capture_xcomposite_on_event(gsr_capture *cap, gsr_egl *egl) {
gsr_capture_xcomposite *self = cap->priv;
- XEvent *xev = gsr_egl_get_event_data(egl);
+ 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 */
@@ -246,63 +239,62 @@ static bool gsr_capture_xcomposite_should_stop(gsr_capture *cap, bool *err) {
return false;
}
-static int gsr_capture_xcomposite_capture(gsr_capture *cap, AVFrame *frame, gsr_color_conversion *color_conversion) {
+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;
- (void)frame;
if(self->clear_background) {
self->clear_background = false;
gsr_color_conversion_clear(color_conversion);
}
- const int target_x = max_int(0, frame->width / 2 - self->texture_size.x / 2);
- const int target_y = max_int(0, frame->height / 2 - self->texture_size.y / 2);
+ const bool is_scaled = self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0;
+ vec2i output_size = is_scaled ? self->params.output_resolution : self->texture_size;
+ output_size = scale_keep_aspect_ratio(self->texture_size, output_size);
+
+ const vec2i target_pos = { max_int(0, 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();
+ //self->params.egl->glFlush();
+ //self->params.egl->glFinish();
gsr_color_conversion_draw(color_conversion, window_texture_get_opengl_texture_id(&self->window_texture),
- (vec2i){target_x, target_y}, self->texture_size,
- (vec2i){0, 0}, self->texture_size,
- 0.0f, false);
+ 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_x + self->cursor.position.x - self->cursor.hotspot.x,
- target_y + self->cursor.position.y - self->cursor.hotspot.y
+ target_pos.x + (self->cursor.position.x - self->cursor.hotspot.x) * scale.x,
+ target_pos.y + (self->cursor.position.y - self->cursor.hotspot.y) * scale.y
};
- self->params.egl->glEnable(GL_SCISSOR_TEST);
- self->params.egl->glScissor(target_x, target_y, self->texture_size.x, self->texture_size.y);
+ 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, self->cursor.size,
- (vec2i){0, 0}, self->cursor.size,
- 0.0f, false);
-
- self->params.egl->glDisable(GL_SCISSOR_TEST);
+ 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();
+ //self->params.egl->glFlush();
+ //self->params.egl->glFinish();
return 0;
}
-static gsr_source_color gsr_capture_xcomposite_get_source_color(gsr_capture *cap) {
- (void)cap;
- return GSR_SOURCE_COLOR_RGB;
-}
-
static uint64_t gsr_capture_xcomposite_get_window_id(gsr_capture *cap) {
gsr_capture_xcomposite *self = cap->priv;
return self->window;
}
-static void gsr_capture_xcomposite_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
- (void)video_codec_context;
+static void gsr_capture_xcomposite_destroy(gsr_capture *cap) {
if(cap->priv) {
gsr_capture_xcomposite_stop(cap->priv);
free(cap->priv);
@@ -328,6 +320,7 @@ gsr_capture* gsr_capture_xcomposite_create(const gsr_capture_xcomposite_params *
}
cap_xcomp->params = *params;
+ cap_xcomp->display = gsr_window_get_display(params->egl->window);
*cap = (gsr_capture) {
.start = gsr_capture_xcomposite_start,
@@ -335,8 +328,6 @@ gsr_capture* gsr_capture_xcomposite_create(const gsr_capture_xcomposite_params *
.tick = gsr_capture_xcomposite_tick,
.should_stop = gsr_capture_xcomposite_should_stop,
.capture = gsr_capture_xcomposite_capture,
- .capture_end = NULL,
- .get_source_color = gsr_capture_xcomposite_get_source_color,
.uses_external_image = NULL,
.get_window_id = gsr_capture_xcomposite_get_window_id,
.destroy = gsr_capture_xcomposite_destroy,
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;
+}