aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/capture/capture.c10
-rw-r--r--src/capture/kms.c185
-rw-r--r--src/capture/nvfbc.c1
-rw-r--r--src/capture/portal.c77
-rw-r--r--src/capture/xcomposite.c28
-rw-r--r--src/damage.c27
-rw-r--r--src/egl.c12
-rw-r--r--src/encoder/video/vaapi.c5
-rw-r--r--src/main.cpp135
-rw-r--r--src/pipewire.c49
-rw-r--r--src/utils.c262
-rw-r--r--src/window_texture.c12
12 files changed, 637 insertions, 166 deletions
diff --git a/src/capture/capture.c b/src/capture/capture.c
index 5fc96d0..ec10854 100644
--- a/src/capture/capture.c
+++ b/src/capture/capture.c
@@ -10,10 +10,10 @@ int gsr_capture_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVF
return res;
}
-void gsr_capture_tick(gsr_capture *cap, AVCodecContext *video_codec_context) {
+void gsr_capture_tick(gsr_capture *cap) {
assert(cap->started);
if(cap->tick)
- cap->tick(cap, video_codec_context);
+ cap->tick(cap);
}
void gsr_capture_on_event(gsr_capture *cap, gsr_egl *egl) {
@@ -34,12 +34,6 @@ int gsr_capture_capture(gsr_capture *cap, AVFrame *frame, gsr_color_conversion *
return cap->capture(cap, frame, color_conversion);
}
-void gsr_capture_capture_end(gsr_capture *cap, AVFrame *frame) {
- assert(cap->started);
- if(cap->capture_end)
- cap->capture_end(cap, frame);
-}
-
gsr_source_color gsr_capture_get_source_color(gsr_capture *cap) {
return cap->get_source_color(cap);
}
diff --git a/src/capture/kms.c b/src/capture/kms.c
index 9287a8b..030d609 100644
--- a/src/capture/kms.c
+++ b/src/capture/kms.c
@@ -8,7 +8,9 @@
#include <string.h>
#include <stdio.h>
#include <unistd.h>
+#include <fcntl.h>
+#include <xf86drm.h>
#include <libdrm/drm_fourcc.h>
#include <libavcodec/avcodec.h>
@@ -49,6 +51,16 @@ typedef struct {
bool is_x11;
gsr_cursor x11_cursor;
+
+ AVCodecContext *video_codec_context;
+ bool performance_error_shown;
+
+ //int drm_fd;
+ //uint64_t prev_sequence;
+ //bool damaged;
+
+ vec2i prev_target_pos;
+ vec2i prev_plane_size;
} gsr_capture_kms;
static void gsr_capture_kms_cleanup_kms_fds(gsr_capture_kms *self) {
@@ -81,6 +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);
@@ -144,6 +161,15 @@ static void monitor_callback(const gsr_monitor *monitor, void *userdata) {
fprintf(stderr, "gsr warning: reached max connector ids\n");
}
+static vec2i rotate_capture_size_if_rotated(gsr_capture_kms *self, vec2i capture_size) {
+ if(self->monitor_rotation == GSR_MONITOR_ROT_90 || self->monitor_rotation == GSR_MONITOR_ROT_270) {
+ int tmp_x = capture_size.x;
+ capture_size.x = capture_size.y;
+ capture_size.y = tmp_x;
+ }
+ return capture_size;
+}
+
static int gsr_capture_kms_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) {
gsr_capture_kms *self = cap->priv;
@@ -179,12 +205,10 @@ static int gsr_capture_kms_start(gsr_capture *cap, AVCodecContext *video_codec_c
self->capture_pos = monitor.pos;
/* Monitor size is already rotated on x11 when the monitor is rotated, no need to apply it ourselves */
- if(!self->is_x11 && (self->monitor_rotation == GSR_MONITOR_ROT_90 || self->monitor_rotation == GSR_MONITOR_ROT_270)) {
- self->capture_size.x = monitor.size.y;
- self->capture_size.y = monitor.size.x;
- } else {
+ if(self->is_x11)
self->capture_size = monitor.size;
- }
+ else
+ self->capture_size = rotate_capture_size_if_rotated(self, monitor.size);
/* Disable vsync */
self->params.egl->eglSwapInterval(self->params.egl->egl_display, 0);
@@ -194,6 +218,8 @@ static int gsr_capture_kms_start(gsr_capture *cap, AVCodecContext *video_codec_c
frame->width = video_codec_context->width;
frame->height = video_codec_context->height;
+
+ self->video_codec_context = video_codec_context;
return 0;
}
@@ -206,6 +232,27 @@ static void gsr_capture_kms_on_event(gsr_capture *cap, gsr_egl *egl) {
gsr_cursor_on_event(&self->x11_cursor, xev);
}
+// TODO: This is disabled for now because we want to be able to record at a framerate highe than the monitor framerate
+// static void gsr_capture_kms_tick(gsr_capture *cap) {
+// gsr_capture_kms *self = cap->priv;
+
+// if(self->drm_fd <= 0)
+// self->drm_fd = open(self->params.egl->card_path, O_RDONLY);
+
+// if(self->drm_fd <= 0)
+// return;
+
+// uint64_t sequence = 0;
+// uint64_t ns = 0;
+// if(drmCrtcGetSequence(self->drm_fd, 79, &sequence, &ns) != 0)
+// return;
+
+// if(sequence != self->prev_sequence) {
+// self->prev_sequence = sequence;
+// self->damaged = true;
+// }
+// }
+
static float monitor_rotation_to_radians(gsr_monitor_rotation rot) {
switch(rot) {
case GSR_MONITOR_ROT_0: return 0.0f;
@@ -381,7 +428,7 @@ static gsr_kms_response_item* find_cursor_drm_if_on_monitor(gsr_capture_kms *sel
return cursor_drm_fd;
}
-static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color_conversion, const gsr_kms_response_item *cursor_drm_fd, int target_x, int target_y, float texture_rotation) {
+static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color_conversion, const gsr_kms_response_item *cursor_drm_fd, vec2i target_pos, float texture_rotation) {
const bool cursor_texture_id_is_external = self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA;
const vec2i cursor_size = {cursor_drm_fd->width, cursor_drm_fd->height};
@@ -410,8 +457,8 @@ static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color
break;
}
- cursor_pos.x += target_x;
- cursor_pos.y += target_y;
+ cursor_pos.x += 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,7 +486,7 @@ 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, self->capture_size.x, self->capture_size.y);
gsr_color_conversion_draw(color_conversion, self->cursor_texture_id,
cursor_pos, cursor_size,
@@ -449,16 +496,19 @@ static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color
self->params.egl->glDisable(GL_SCISSOR_TEST);
}
-static void render_x11_cursor(gsr_capture_kms *self, gsr_color_conversion *color_conversion, vec2i capture_pos, int target_x, int target_y) {
+static void render_x11_cursor(gsr_capture_kms *self, gsr_color_conversion *color_conversion, vec2i capture_pos, vec2i target_pos) {
+ if(!self->x11_cursor.visible)
+ return;
+
gsr_cursor_tick(&self->x11_cursor, DefaultRootWindow(self->params.egl->x11.dpy));
const vec2i cursor_pos = {
- target_x + self->x11_cursor.position.x - self->x11_cursor.hotspot.x - capture_pos.x,
- target_y + self->x11_cursor.position.y - self->x11_cursor.hotspot.y - capture_pos.y
+ target_pos.x + self->x11_cursor.position.x - self->x11_cursor.hotspot.x - capture_pos.x,
+ target_pos.y + self->x11_cursor.position.y - self->x11_cursor.hotspot.y - capture_pos.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, self->capture_size.x, self->capture_size.y);
gsr_color_conversion_draw(color_conversion, self->x11_cursor.texture_id,
cursor_pos, self->x11_cursor.size,
@@ -468,6 +518,14 @@ static void render_x11_cursor(gsr_capture_kms *self, gsr_color_conversion *color
self->params.egl->glDisable(GL_SCISSOR_TEST);
}
+static void gsr_capture_kms_update_capture_size_change(gsr_capture_kms *self, gsr_color_conversion *color_conversion, vec2i target_pos, const gsr_kms_response_item *drm_fd) {
+ if(target_pos.x != self->prev_target_pos.x || target_pos.y != self->prev_target_pos.y || drm_fd->src_w != self->prev_plane_size.x || drm_fd->src_h != self->prev_plane_size.y) {
+ self->prev_target_pos = target_pos;
+ self->prev_plane_size = self->capture_size;
+ gsr_color_conversion_clear(color_conversion);
+ }
+}
+
static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_conversion *color_conversion) {
gsr_capture_kms *self = cap->priv;
@@ -489,46 +547,89 @@ static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_c
bool capture_is_combined_plane = false;
const gsr_kms_response_item *drm_fd = find_monitor_drm(self, &capture_is_combined_plane);
- if(!drm_fd)
+ if(!drm_fd) {
+ gsr_capture_kms_cleanup_kms_fds(self);
return -1;
+ }
if(drm_fd->has_hdr_metadata && self->params.hdr && hdr_metadata_is_supported_format(&drm_fd->hdr_metadata))
gsr_kms_set_hdr_metadata(self, drm_fd);
- EGLImage image = gsr_capture_kms_create_egl_image_with_fallback(self, drm_fd);
- if(image) {
- gsr_capture_kms_bind_image_to_input_texture_with_fallback(self, image);
- self->params.egl->eglDestroyImage(self->params.egl->egl_display, image);
+ if(!self->performance_error_shown && self->monitor_rotation != GSR_MONITOR_ROT_0 && video_codec_context_is_vaapi(self->video_codec_context) && self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD) {
+ self->performance_error_shown = true;
+ fprintf(stderr,"gsr warning: gsr_capture_kms_capture: the monitor you are recording is rotated, composition will have to be used."
+ " If you are experience performance problems in the video then record a single window on X11 or use portal capture option instead\n");
}
const float texture_rotation = monitor_rotation_to_radians(self->monitor_rotation);
- const int target_x = max_int(0, frame->width / 2 - self->capture_size.x / 2);
- const int target_y = max_int(0, frame->height / 2 - self->capture_size.y / 2);
+ const vec2i target_pos = { max_int(0, frame->width / 2 - self->capture_size.x / 2), max_int(0, frame->height / 2 - self->capture_size.y / 2) };
+ self->capture_size = rotate_capture_size_if_rotated(self, (vec2i){ drm_fd->src_w, drm_fd->src_h });
+ 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();
+ // TODO: Hack!! cursor flickers without this when using vaapi copy on wayland.
+ // There is probably some sync issue between opengl and vaapi.
+ // Remove this when that has been figured out. Same for the below glFlush && glFinish
+ for(int i = 0; i < 3; ++i) {
+ self->params.egl->glFlush();
+ self->params.egl->glFinish();
+ }
+
+ /* Fast opengl free path */
+ if(self->monitor_rotation == GSR_MONITOR_ROT_0 && video_codec_context_is_vaapi(self->video_codec_context) && self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD) {
+ int fds[4];
+ uint32_t offsets[4];
+ uint32_t pitches[4];
+ uint64_t modifiers[4];
+ for(int i = 0; i < drm_fd->num_dma_bufs; ++i) {
+ fds[i] = drm_fd->dma_buf[i].fd;
+ offsets[i] = drm_fd->dma_buf[i].offset;
+ pitches[i] = drm_fd->dma_buf[i].pitch;
+ modifiers[i] = drm_fd->modifier;
+ }
+ vaapi_copy_drm_planes_to_video_surface(self->video_codec_context, frame, (vec2i){capture_pos.x, capture_pos.y}, self->capture_size, target_pos, self->capture_size, drm_fd->pixel_format, (vec2i){drm_fd->width, drm_fd->height}, fds, offsets, pitches, modifiers, drm_fd->num_dma_bufs);
+ } else {
+ 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->params.egl->glFlush();
+ self->params.egl->glFinish();
+
+ gsr_color_conversion_draw(color_conversion, self->external_texture_fallback ? self->external_input_texture_id : self->input_texture_id,
+ target_pos, self->capture_size,
+ capture_pos, self->capture_size,
+ texture_rotation, self->external_texture_fallback);
+ }
- 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);
+ for(int i = 0; i < 3; ++i) {
+ self->params.egl->glFlush();
+ self->params.egl->glFinish();
+ }
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);
+ if(self->is_x11) {
+ const vec2i cursor_monitor_offset = self->capture_pos;
+ render_x11_cursor(self, color_conversion, cursor_monitor_offset, target_pos);
+ } else if(cursor_drm_fd) {
+ render_drm_cursor(self, color_conversion, cursor_drm_fd, target_pos, texture_rotation);
+ }
}
- self->params.egl->glFlush();
- self->params.egl->glFinish();
+ for(int i = 0; i < 3; ++i) {
+ self->params.egl->glFlush();
+ self->params.egl->glFinish();
+ }
+
+ gsr_capture_kms_cleanup_kms_fds(self);
return 0;
}
@@ -540,11 +641,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;
@@ -581,6 +677,16 @@ static bool gsr_capture_kms_set_hdr_metadata(gsr_capture *cap, AVMasteringDispla
return true;
}
+// static bool gsr_capture_kms_is_damaged(gsr_capture *cap) {
+// gsr_capture_kms *self = cap->priv;
+// return self->damaged;
+// }
+
+// static void gsr_capture_kms_clear_damage(gsr_capture *cap) {
+// gsr_capture_kms *self = cap->priv;
+// self->damaged = false;
+// }
+
static void gsr_capture_kms_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
(void)video_codec_context;
gsr_capture_kms *self = cap->priv;
@@ -623,13 +729,14 @@ 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..a38df63 100644
--- a/src/capture/nvfbc.c
+++ b/src/capture/nvfbc.c
@@ -471,7 +471,6 @@ gsr_capture* gsr_capture_nvfbc_create(const gsr_capture_nvfbc_params *params) {
.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,
diff --git a/src/capture/portal.c b/src/capture/portal.c
index 95470c1..40e4581 100644
--- a/src/capture/portal.c
+++ b/src/capture/portal.c
@@ -22,18 +22,20 @@ typedef struct {
gsr_pipewire pipewire;
vec2i capture_size;
- int plane_fds[GSR_PIPEWIRE_DMABUF_MAX_PLANES];
- int num_plane_fds;
+ gsr_pipewire_dmabuf_data dmabuf_data[GSR_PIPEWIRE_DMABUF_MAX_PLANES];
+ int num_dmabuf_data;
+
+ AVCodecContext *video_codec_context;
} 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) {
@@ -237,7 +239,9 @@ static bool gsr_capture_portal_get_frame_dimensions(gsr_capture_portal *self) {
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)) {
+ uint32_t fourcc = 0;
+ uint64_t modifiers = 0;
+ if(gsr_pipewire_map_texture(&self->pipewire, self->texture_map, &region, &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;
@@ -300,6 +304,8 @@ static int gsr_capture_portal_start(gsr_capture *cap, AVCodecContext *video_code
frame->width = video_codec_context->width;
frame->height = video_codec_context->height;
+
+ self->video_codec_context = video_codec_context;
return 0;
}
@@ -312,39 +318,57 @@ static int gsr_capture_portal_capture(gsr_capture *cap, AVFrame *frame, gsr_colo
(void)color_conversion;
gsr_capture_portal *self = cap->priv;
- gsr_capture_portal_cleanup_plane_fds(self);
-
/* TODO: Handle formats other than RGB(a) */
gsr_pipewire_region region = {0, 0, 0, 0};
gsr_pipewire_region cursor_region = {0, 0, 0, 0};
+ uint32_t pipewire_fourcc = 0;
+ uint64_t pipewire_modifiers = 0;
bool using_external_image = false;
- if(gsr_pipewire_map_texture(&self->pipewire, self->texture_map, &region, &cursor_region, self->plane_fds, &self->num_plane_fds, &using_external_image)) {
+ if(gsr_pipewire_map_texture(&self->pipewire, self->texture_map, &region, &cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &pipewire_fourcc, &pipewire_modifiers, &using_external_image)) {
if(region.width != self->capture_size.x || region.height != self->capture_size.y) {
- gsr_color_conversion_clear(color_conversion);
self->capture_size.x = region.width;
self->capture_size.y = region.height;
+ gsr_color_conversion_clear(color_conversion);
}
+ } else {
+ return 0;
}
self->params.egl->glFlush();
self->params.egl->glFinish();
- const int target_x = max_int(0, frame->width / 2 - self->capture_size.x / 2);
- const int target_y = max_int(0, frame->height / 2 - self->capture_size.y / 2);
-
- gsr_color_conversion_draw(color_conversion, using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id,
- (vec2i){target_x, target_y}, self->capture_size,
- (vec2i){region.x, region.y}, self->capture_size,
- 0.0f, using_external_image);
+ const vec2i target_pos = { max_int(0, frame->width / 2 - self->capture_size.x / 2), max_int(0, frame->height / 2 - self->capture_size.y / 2) };
+
+ // TODO: Handle region crop
+
+ /* Fast opengl free path */
+ if(video_codec_context_is_vaapi(self->video_codec_context) && self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD) {
+ int fds[4];
+ uint32_t offsets[4];
+ uint32_t pitches[4];
+ uint64_t modifiers[4];
+ for(int i = 0; i < self->num_dmabuf_data; ++i) {
+ fds[i] = self->dmabuf_data[i].fd;
+ offsets[i] = self->dmabuf_data[i].offset;
+ pitches[i] = self->dmabuf_data[i].stride;
+ modifiers[i] = pipewire_modifiers;
+ }
+ vaapi_copy_drm_planes_to_video_surface(self->video_codec_context, frame, (vec2i){region.x, region.y}, self->capture_size, target_pos, self->capture_size, pipewire_fourcc, self->capture_size, fds, offsets, pitches, modifiers, self->num_dmabuf_data);
+ } else {
+ gsr_color_conversion_draw(color_conversion, using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id,
+ target_pos, self->capture_size,
+ (vec2i){region.x, region.y}, self->capture_size,
+ 0.0f, using_external_image);
+ }
if(self->params.record_cursor) {
const vec2i cursor_pos = {
- target_x + cursor_region.x,
- target_y + cursor_region.y
+ target_pos.x + cursor_region.x,
+ target_pos.y + cursor_region.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, self->capture_size.x, self->capture_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},
@@ -355,13 +379,9 @@ static int gsr_capture_portal_capture(gsr_capture *cap, AVFrame *frame, gsr_colo
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);
+
+ return 0;
}
static gsr_source_color gsr_capture_portal_get_source_color(gsr_capture *cap) {
@@ -418,7 +438,6 @@ gsr_capture* gsr_capture_portal_create(const gsr_capture_portal_params *params)
.tick = NULL,
.should_stop = NULL,
.capture = gsr_capture_portal_capture,
- .capture_end = gsr_capture_portal_capture_end,
.get_source_color = gsr_capture_portal_get_source_color,
.uses_external_image = gsr_capture_portal_uses_external_image,
.is_damaged = gsr_capture_portal_is_damaged,
diff --git a/src/capture/xcomposite.c b/src/capture/xcomposite.c
index 1b6021b..9d053bb 100644
--- a/src/capture/xcomposite.c
+++ b/src/capture/xcomposite.c
@@ -29,6 +29,7 @@ typedef struct {
double window_resize_timer;
WindowTexture window_texture;
+ AVCodecContext *video_codec_context;
Atom net_active_window_atom;
@@ -122,12 +123,12 @@ static int gsr_capture_xcomposite_start(gsr_capture *cap, AVCodecContext *video_
frame->width = video_codec_context->width;
frame->height = video_codec_context->height;
+ self->video_codec_context = video_codec_context;
self->window_resize_timer = clock_get_monotonic_seconds();
return 0;
}
-static void gsr_capture_xcomposite_tick(gsr_capture *cap, AVCodecContext *video_codec_context) {
- (void)video_codec_context;
+static void gsr_capture_xcomposite_tick(gsr_capture *cap) {
gsr_capture_xcomposite *self = cap->priv;
if(self->params.follow_focused && !self->follow_focused_initialized) {
@@ -255,27 +256,31 @@ static int gsr_capture_xcomposite_capture(gsr_capture *cap, AVFrame *frame, gsr_
gsr_color_conversion_clear(color_conversion);
}
- const int target_x = max_int(0, frame->width / 2 - self->texture_size.x / 2);
- const int target_y = max_int(0, frame->height / 2 - self->texture_size.y / 2);
+ const vec2i target_pos = { max_int(0, frame->width / 2 - self->texture_size.x / 2), max_int(0, frame->height / 2 - self->texture_size.y / 2) };
self->params.egl->glFlush();
self->params.egl->glFinish();
- gsr_color_conversion_draw(color_conversion, window_texture_get_opengl_texture_id(&self->window_texture),
- (vec2i){target_x, target_y}, self->texture_size,
- (vec2i){0, 0}, self->texture_size,
- 0.0f, false);
+ /* Fast opengl free path */
+ if(video_codec_context_is_vaapi(self->video_codec_context) && self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD) {
+ vaapi_copy_egl_image_to_video_surface(self->params.egl, self->window_texture.image, (vec2i){0, 0}, self->texture_size, target_pos, self->texture_size, self->video_codec_context, frame);
+ } else {
+ gsr_color_conversion_draw(color_conversion, window_texture_get_opengl_texture_id(&self->window_texture),
+ target_pos, self->texture_size,
+ (vec2i){0, 0}, self->texture_size,
+ 0.0f, false);
+ }
if(self->params.record_cursor && self->cursor.visible) {
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,
+ target_pos.y + self->cursor.position.y - self->cursor.hotspot.y
};
self->params.egl->glEnable(GL_SCISSOR_TEST);
- self->params.egl->glScissor(target_x, target_y, self->texture_size.x, self->texture_size.y);
+ self->params.egl->glScissor(target_pos.x, target_pos.y, self->texture_size.x, self->texture_size.y);
gsr_color_conversion_draw(color_conversion, self->cursor.texture_id,
cursor_pos, self->cursor.size,
@@ -335,7 +340,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,
diff --git a/src/damage.c b/src/damage.c
index 868a2e6..8e62762 100644
--- a/src/damage.c
+++ b/src/damage.c
@@ -53,6 +53,9 @@ bool gsr_damage_init(gsr_damage *self, gsr_egl *egl, bool track_cursor) {
return false;
}
+ if(self->track_cursor)
+ self->track_cursor = gsr_cursor_init(&self->cursor, self->egl, self->egl->x11.dpy) == 0;
+
XRRSelectInput(self->egl->x11.dpy, DefaultRootWindow(self->egl->x11.dpy), RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask | RROutputChangeNotifyMask);
self->damaged = true;
@@ -65,6 +68,8 @@ void gsr_damage_deinit(gsr_damage *self) {
self->damage = None;
}
+ gsr_cursor_deinit(&self->cursor);
+
self->damage_event = 0;
self->damage_error = 0;
@@ -245,16 +250,11 @@ static void gsr_damage_on_damage_event(gsr_damage *self, XEvent *xev) {
XFlush(self->egl->x11.dpy);
}
-static void gsr_damage_on_event_cursor(gsr_damage *self) {
- Window root_return = None;
- Window child_return = None;
- int dummy_i;
- unsigned int dummy_u;
- vec2i cursor_position = {0, 0};
- XQueryPointer(self->egl->x11.dpy, self->window, &root_return, &child_return, &dummy_i, &dummy_i, &cursor_position.x, &cursor_position.y, &dummy_u);
- if(cursor_position.x != self->cursor_position.x || cursor_position.y != self->cursor_position.y) {
- self->cursor_position = cursor_position;
- const gsr_rectangle cursor_region = { self->cursor_position, {64, 64} }; // TODO: Track cursor size
+static void gsr_damage_on_tick_cursor(gsr_damage *self) {
+ vec2i prev_cursor_pos = self->cursor.position;
+ gsr_cursor_tick(&self->cursor, self->window);
+ if(self->cursor.position.x != prev_cursor_pos.x || self->cursor.position.y != prev_cursor_pos.y) {
+ const gsr_rectangle cursor_region = { self->cursor.position, self->cursor.size };
switch(self->track_type) {
case GSR_DAMAGE_TRACK_NONE: {
self->damaged = true;
@@ -302,14 +302,17 @@ void gsr_damage_on_event(gsr_damage *self, XEvent *xev) {
if(self->damage_event && xev->type == self->damage_event + XDamageNotify)
gsr_damage_on_damage_event(self, xev);
+
+ if(self->track_cursor)
+ gsr_cursor_on_event(&self->cursor, xev);
}
void gsr_damage_tick(gsr_damage *self) {
if(self->damage_event == 0 || self->track_type == GSR_DAMAGE_TRACK_NONE)
return;
- if(self->track_cursor && !self->damaged)
- gsr_damage_on_event_cursor(self);
+ if(self->track_cursor && self->cursor.visible && !self->damaged)
+ gsr_damage_on_tick_cursor(self);
}
bool gsr_damage_is_damaged(gsr_damage *self) {
diff --git a/src/egl.c b/src/egl.c
index b4e3902..05d6680 100644
--- a/src/egl.c
+++ b/src/egl.c
@@ -388,11 +388,23 @@ static bool gsr_egl_load_egl(gsr_egl *self, void *library) {
}
static bool gsr_egl_proc_load_egl(gsr_egl *self) {
+ self->eglExportDMABUFImageQueryMESA = (FUNC_eglExportDMABUFImageQueryMESA)self->eglGetProcAddress("eglExportDMABUFImageQueryMESA");
+ self->eglExportDMABUFImageMESA = (FUNC_eglExportDMABUFImageMESA)self->eglGetProcAddress("eglExportDMABUFImageMESA");
self->glEGLImageTargetTexture2DOES = (FUNC_glEGLImageTargetTexture2DOES)self->eglGetProcAddress("glEGLImageTargetTexture2DOES");
self->eglQueryDisplayAttribEXT = (FUNC_eglQueryDisplayAttribEXT)self->eglGetProcAddress("eglQueryDisplayAttribEXT");
self->eglQueryDeviceStringEXT = (FUNC_eglQueryDeviceStringEXT)self->eglGetProcAddress("eglQueryDeviceStringEXT");
self->eglQueryDmaBufModifiersEXT = (FUNC_eglQueryDmaBufModifiersEXT)self->eglGetProcAddress("eglQueryDmaBufModifiersEXT");
+ if(!self->eglExportDMABUFImageQueryMESA) {
+ fprintf(stderr, "gsr error: gsr_egl_load failed: could not find eglExportDMABUFImageQueryMESA\n");
+ return false;
+ }
+
+ if(!self->eglExportDMABUFImageMESA) {
+ fprintf(stderr, "gsr error: gsr_egl_load failed: could not find eglExportDMABUFImageMESA\n");
+ return false;
+ }
+
if(!self->glEGLImageTargetTexture2DOES) {
fprintf(stderr, "gsr error: gsr_egl_load failed: could not find glEGLImageTargetTexture2DOES\n");
return false;
diff --git a/src/encoder/video/vaapi.c b/src/encoder/video/vaapi.c
index 7a2abfc..03218cb 100644
--- a/src/encoder/video/vaapi.c
+++ b/src/encoder/video/vaapi.c
@@ -41,8 +41,7 @@ static bool gsr_video_encoder_vaapi_setup_context(gsr_video_encoder_vaapi *self,
return false;
}
- AVHWFramesContext *hw_frame_context =
- (AVHWFramesContext *)frame_context->data;
+ 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 = self->params.color_depth == GSR_COLOR_DEPTH_10_BITS ? AV_PIX_FMT_P010LE : AV_PIX_FMT_NV12;
@@ -51,7 +50,7 @@ static bool gsr_video_encoder_vaapi_setup_context(gsr_video_encoder_vaapi *self,
//hw_frame_context->initial_pool_size = 20;
- AVVAAPIDeviceContext *vactx =((AVHWDeviceContext*)self->device_ctx->data)->hwctx;
+ AVVAAPIDeviceContext *vactx = ((AVHWDeviceContext*)self->device_ctx->data)->hwctx;
self->va_dpy = vactx->display;
if (av_hwframe_ctx_init(frame_context) < 0) {
diff --git a/src/main.cpp b/src/main.cpp
index 7687b07..c6b0e8d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -143,6 +143,17 @@ static bool video_codec_is_hdr(VideoCodec video_codec) {
}
}
+static VideoCodec hdr_video_codec_to_sdr_video_codec(VideoCodec video_codec) {
+ switch(video_codec) {
+ case VideoCodec::HEVC_HDR:
+ return VideoCodec::HEVC;
+ case VideoCodec::AV1_HDR:
+ return VideoCodec::AV1;
+ default:
+ return video_codec;
+ }
+}
+
static gsr_color_depth video_codec_to_bit_depth(VideoCodec video_codec) {
switch(video_codec) {
case VideoCodec::HEVC_HDR:
@@ -276,7 +287,8 @@ static AVCodecID audio_codec_get_id(AudioCodec audio_codec) {
return AV_CODEC_ID_AAC;
}
-static AVSampleFormat audio_codec_get_sample_format(AudioCodec audio_codec, const AVCodec *codec, bool mix_audio) {
+static AVSampleFormat audio_codec_get_sample_format(AVCodecContext *audio_codec_context, AudioCodec audio_codec, const AVCodec *codec, bool mix_audio) {
+ (void)audio_codec_context;
switch(audio_codec) {
case AudioCodec::AAC: {
return AV_SAMPLE_FMT_FLTP;
@@ -285,13 +297,32 @@ static AVSampleFormat audio_codec_get_sample_format(AudioCodec audio_codec, cons
bool supports_s16 = false;
bool supports_flt = false;
- for(size_t i = 0; codec->sample_fmts && codec->sample_fmts[i] != -1; ++i) {
+ #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(61, 15, 0)
+ for(size_t i = 0; codec->sample_fmts && codec->sample_fmts[i] != AV_SAMPLE_FMT_NONE; ++i) {
if(codec->sample_fmts[i] == AV_SAMPLE_FMT_S16) {
supports_s16 = true;
} else if(codec->sample_fmts[i] == AV_SAMPLE_FMT_FLT) {
supports_flt = true;
}
}
+ #else
+ const enum AVSampleFormat *sample_fmts = NULL;
+ if(avcodec_get_supported_config(audio_codec_context, codec, AV_CODEC_CONFIG_SAMPLE_FORMAT, 0, (const void**)&sample_fmts, NULL) >= 0) {
+ if(sample_fmts) {
+ for(size_t i = 0; sample_fmts[i] != AV_SAMPLE_FMT_NONE; ++i) {
+ if(sample_fmts[i] == AV_SAMPLE_FMT_S16) {
+ supports_s16 = true;
+ } else if(sample_fmts[i] == AV_SAMPLE_FMT_FLT) {
+ supports_flt = true;
+ }
+ }
+ } else {
+ // What a dumb API. It returns NULL if all formats are supported
+ supports_s16 = true;
+ supports_flt = true;
+ }
+ }
+ #endif
// Amix only works with float audio
if(mix_audio)
@@ -360,7 +391,7 @@ static AVCodecContext* create_audio_codec_context(int fps, AudioCodec audio_code
assert(codec->type == AVMEDIA_TYPE_AUDIO);
codec_context->codec_id = codec->id;
- codec_context->sample_fmt = audio_codec_get_sample_format(audio_codec, codec, mix_audio);
+ codec_context->sample_fmt = audio_codec_get_sample_format(codec_context, audio_codec, codec, mix_audio);
codec_context->bit_rate = audio_bitrate == 0 ? audio_codec_get_get_bitrate(audio_codec) : audio_bitrate;
codec_context->sample_rate = AUDIO_SAMPLE_RATE;
if(audio_codec == AudioCodec::AAC)
@@ -519,13 +550,13 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
if(codec_context->codec_id == AV_CODEC_ID_AV1 || codec_context->codec_id == AV_CODEC_ID_H264 || codec_context->codec_id == AV_CODEC_ID_HEVC) {
switch(video_quality) {
case VideoQuality::MEDIUM:
- codec_context->global_quality = 160 * quality_multiply;
+ codec_context->global_quality = 150 * quality_multiply;
break;
case VideoQuality::HIGH:
- codec_context->global_quality = 130 * quality_multiply;
+ codec_context->global_quality = 120 * quality_multiply;
break;
case VideoQuality::VERY_HIGH:
- codec_context->global_quality = 110 * quality_multiply;
+ codec_context->global_quality = 100 * quality_multiply;
break;
case VideoQuality::ULTRA:
codec_context->global_quality = 90 * quality_multiply;
@@ -683,9 +714,9 @@ static void open_video_software(AVCodecContext *codec_context, VideoQuality vide
av_dict_set(&options, "preset", "medium", 0);
if(color_depth == GSR_COLOR_DEPTH_10_BITS) {
- av_dict_set(&options, "profile", "high10", 0);
+ av_dict_set_int(&options, "profile", AV_PROFILE_H264_HIGH_10, 0);
} else {
- av_dict_set(&options, "profile", "high", 0);
+ av_dict_set_int(&options, "profile", AV_PROFILE_H264_HIGH, 0);
}
// TODO: If streaming or piping output set this to zerolatency
av_dict_set(&options, "tune", "fastdecode", 0);
@@ -876,23 +907,25 @@ static void open_video_hardware(AVCodecContext *codec_context, VideoQuality vide
} else {
// TODO: More quality options
//av_dict_set_int(&options, "low_power", 1, 0);
+ // Improves performance but increases vram
+ av_dict_set_int(&options, "async_depth", 8, 0);
if(codec_context->codec_id == AV_CODEC_ID_H264) {
// TODO:
if(color_depth == GSR_COLOR_DEPTH_10_BITS)
- av_dict_set(&options, "profile", "high10", 0);
+ av_dict_set_int(&options, "profile", AV_PROFILE_H264_HIGH_10, 0);
else
- av_dict_set(&options, "profile", "high", 0);
+ av_dict_set_int(&options, "profile", AV_PROFILE_H264_HIGH, 0);
// Removed because it causes stutter in games for some people
//av_dict_set_int(&options, "quality", 5, 0); // quality preset
} else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
- av_dict_set(&options, "profile", "main", 0); // TODO: use professional instead?
+ av_dict_set_int(&options, "profile", AV_PROFILE_AV1_MAIN, 0); // TODO: use professional instead?
av_dict_set(&options, "tier", "main", 0);
} else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
if(color_depth == GSR_COLOR_DEPTH_10_BITS)
- av_dict_set(&options, "profile", "main10", 0);
+ av_dict_set_int(&options, "profile", AV_PROFILE_HEVC_MAIN_10, 0);
else
- av_dict_set(&options, "profile", "main", 0);
+ av_dict_set_int(&options, "profile", AV_PROFILE_HEVC_MAIN, 0);
if(hdr)
av_dict_set(&options, "sei", "hdr", 0);
@@ -1893,10 +1926,6 @@ static gsr_capture* create_capture_impl(std::string &window_str, const char *scr
_exit(1);
}
- if(video_codec_is_hdr(video_codec)) {
- fprintf(stderr, "Warning: portal capture option doesn't support hdr yet (pipewire doesn't support hdr)\n");
- }
-
gsr_capture_portal_params portal_params;
portal_params.egl = egl;
portal_params.color_depth = color_depth;
@@ -2753,6 +2782,11 @@ int main(int argc, char **argv) {
}
}
+ if(wayland && is_monitor_capture) {
+ fprintf(stderr, "gsr warning: it's not possible to sync video to recorded monitor exactly on wayland when recording a monitor."
+ " If you experience stutter in the video then record with portal capture option instead (-w portal) or use X11 instead\n");
+ }
+
// TODO: Fix constant framerate not working properly on amd/intel because capture framerate gets locked to the same framerate as
// game framerate, which doesn't work well when you need to encode multiple duplicate frames (AMD/Intel is slow at encoding!).
// It also appears to skip audio frames on nvidia wayland? why? that should be fine, but it causes video stuttering because of audio/video sync.
@@ -2896,6 +2930,11 @@ int main(int argc, char **argv) {
const bool force_no_audio_offset = is_livestream || is_output_piped || (file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm");
const double target_fps = 1.0 / (double)fps;
+ if(video_codec_is_hdr(video_codec) && is_portal_capture) {
+ fprintf(stderr, "Warning: portal capture option doesn't support hdr yet (pipewire doesn't support hdr), the video will be tonemapped from hdr to sdr\n");
+ video_codec = hdr_video_codec_to_sdr_video_codec(video_codec);
+ }
+
audio_codec = select_audio_codec_with_fallback(audio_codec, file_extension, uses_amix);
const AVCodec *video_codec_f = select_video_codec_with_fallback(&video_codec, video_codec_to_use, file_extension.c_str(), use_software_video_encoder, &egl);
@@ -3270,23 +3309,25 @@ int main(int argc, char **argv) {
amix_thread = std::thread([&]() {
AVFrame *aframe = av_frame_alloc();
while(running) {
- std::lock_guard<std::mutex> lock(audio_filter_mutex);
- for(AudioTrack &audio_track : audio_tracks) {
- if(!audio_track.sink)
- continue;
-
- int err = 0;
- while ((err = av_buffersink_get_frame(audio_track.sink, aframe)) >= 0) {
- aframe->pts = audio_track.pts;
- err = avcodec_send_frame(audio_track.codec_context, aframe);
- if(err >= 0){
- // TODO: Move to separate thread because this could write to network (for example when livestreaming)
- receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, aframe->pts, av_format_context, record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset);
- } else {
- fprintf(stderr, "Failed to encode audio!\n");
+ {
+ std::lock_guard<std::mutex> lock(audio_filter_mutex);
+ for(AudioTrack &audio_track : audio_tracks) {
+ if(!audio_track.sink)
+ continue;
+
+ int err = 0;
+ while ((err = av_buffersink_get_frame(audio_track.sink, aframe)) >= 0) {
+ aframe->pts = audio_track.pts;
+ err = avcodec_send_frame(audio_track.codec_context, aframe);
+ if(err >= 0){
+ // TODO: Move to separate thread because this could write to network (for example when livestreaming)
+ receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, aframe->pts, av_format_context, record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset);
+ } else {
+ fprintf(stderr, "Failed to encode audio!\n");
+ }
+ av_frame_unref(aframe);
+ audio_track.pts += audio_track.codec_context->frame_size;
}
- av_frame_unref(aframe);
- audio_track.pts += audio_track.codec_context->frame_size;
}
}
av_usleep(5 * 1000); // 5 milliseconds
@@ -3326,7 +3367,7 @@ int main(int argc, char **argv) {
gsr_damage_on_event(&damage, gsr_egl_get_event_data(&egl));
}
gsr_damage_tick(&damage);
- gsr_capture_tick(capture, video_codec_context);
+ gsr_capture_tick(capture);
if(!is_monitor_capture) {
Window damage_target_window = 0;
@@ -3344,10 +3385,12 @@ int main(int argc, char **argv) {
}
bool damaged = false;
- if(capture->is_damaged)
+ if(use_damage_tracking)
+ damaged = gsr_damage_is_damaged(&damage);
+ else if(capture->is_damaged)
damaged = capture->is_damaged(capture);
else
- damaged = !use_damage_tracking || gsr_damage_is_damaged(&damage);
+ damaged = true;
if(damaged)
++damage_fps_counter;
@@ -3366,18 +3409,19 @@ int main(int argc, char **argv) {
const double this_video_frame_time = clock_get_monotonic_seconds() - paused_time_offset;
const int64_t expected_frames = std::round((this_video_frame_time - record_start_time) / target_fps);
- int num_frames = std::max((int64_t)0LL, expected_frames - video_pts_counter);
+ const int num_frames = std::max((int64_t)0LL, expected_frames - video_pts_counter);
const double num_frames_seconds = num_frames * target_fps;
- if((damaged || num_frames_seconds >= damage_timeout_seconds) && !paused/* && fps_counter < fps + 100*/) {
+ if((damaged || (framerate_mode == FramerateMode::CONSTANT && num_frames > 0) || (framerate_mode != FramerateMode::CONSTANT && num_frames_seconds >= damage_timeout_seconds)) && !paused) {
gsr_damage_clear(&damage);
if(capture->clear_damage)
capture->clear_damage(capture);
- egl.glClear(0);
- gsr_capture_capture(capture, video_frame, &color_conversion);
- gsr_egl_swap_buffers(&egl);
-
- gsr_video_encoder_copy_textures_to_frame(video_encoder, video_frame);
+ if(damaged || video_pts_counter == 0) {
+ egl.glClear(0);
+ gsr_capture_capture(capture, video_frame, &color_conversion);
+ gsr_egl_swap_buffers(&egl);
+ gsr_video_encoder_copy_textures_to_frame(video_encoder, video_frame);
+ }
if(hdr && !hdr_metadata_set && replay_buffer_size_secs == -1 && add_hdr_metadata_to_video_stream(capture, video_stream))
hdr_metadata_set = true;
@@ -3405,7 +3449,6 @@ int main(int argc, char **argv) {
}
}
- gsr_capture_capture_end(capture, video_frame);
video_pts_counter += num_frames;
}
@@ -3442,8 +3485,8 @@ int main(int argc, char **argv) {
const double frame_sleep_fps = 1.0 / update_fps;
const double sleep_time = frame_sleep_fps - (frame_end - frame_start);
if(sleep_time > 0.0) {
- if(damaged)
- av_usleep(sleep_time * 1000.0 * 1000.0);
+ if(damaged)
+ av_usleep(sleep_time * 1000.0 * 1000.0);
else
av_usleep(2 * 1000.0); // 2 milliseconds
}
diff --git a/src/pipewire.c b/src/pipewire.c
index 30e2c00..3bf54db 100644
--- a/src/pipewire.c
+++ b/src/pipewire.c
@@ -88,6 +88,7 @@ static const struct pw_core_events core_events = {
static void on_process_cb(void *user_data) {
gsr_pipewire *self = user_data;
struct spa_meta_cursor *cursor = NULL;
+ //struct spa_meta *video_damage = NULL;
/* Find the most recent buffer */
struct pw_buffer *pw_buf = NULL;
@@ -135,6 +136,7 @@ static void on_process_cb(void *user_data) {
// TODO:
}
+ // TODO: Move down to read_metadata
struct spa_meta_region *region = spa_buffer_find_meta_data(buffer, SPA_META_VideoCrop, sizeof(*region));
if(region && spa_meta_region_is_valid(region)) {
// fprintf(stderr, "gsr info: pipewire: crop Region available (%dx%d+%d+%d)\n",
@@ -153,6 +155,17 @@ static void on_process_cb(void *user_data) {
read_metadata:
+ // video_damage = spa_buffer_find_meta(buffer, SPA_META_VideoDamage);
+ // if(video_damage) {
+ // struct spa_meta_region *r = spa_meta_first(video_damage);
+ // if(spa_meta_check(r, video_damage)) {
+ // //fprintf(stderr, "damage: %d,%d %ux%u\n", r->region.position.x, r->region.position.y, r->region.size.width, r->region.size.height);
+ // pthread_mutex_lock(&self->mutex);
+ // self->damaged = true;
+ // pthread_mutex_unlock(&self->mutex);
+ // }
+ // }
+
cursor = spa_buffer_find_meta_data(buffer, SPA_META_Cursor, sizeof(*cursor));
self->cursor.valid = cursor && spa_meta_cursor_is_valid(cursor);
@@ -229,27 +242,35 @@ static void on_param_changed_cb(void *user_data, uint32_t id, const struct spa_p
uint8_t params_buffer[1024];
struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
- const struct spa_pod *params[3];
+ const struct spa_pod *params[4];
params[0] = spa_pod_builder_add_object(
- &pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
- SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop),
- SPA_PARAM_META_size,
- SPA_POD_Int(sizeof(struct spa_meta_region)));
+ &pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop),
+ SPA_PARAM_META_size,
+ SPA_POD_Int(sizeof(struct spa_meta_region)));
params[1] = spa_pod_builder_add_object(
&pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage),
+ SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int(
+ sizeof(struct spa_meta_region) * 16,
+ sizeof(struct spa_meta_region) * 1,
+ sizeof(struct spa_meta_region) * 16));
+
+ params[2] = spa_pod_builder_add_object(
+ &pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor),
SPA_PARAM_META_size,
SPA_POD_CHOICE_RANGE_Int(CURSOR_META_SIZE(64, 64),
CURSOR_META_SIZE(1, 1),
CURSOR_META_SIZE(1024, 1024)));
- params[2] = spa_pod_builder_add_object(
+ params[3] = spa_pod_builder_add_object(
&pod_builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
SPA_PARAM_BUFFERS_dataType, SPA_POD_Int(buffer_types));
- pw_stream_update_params(self->stream, params, 3);
+ pw_stream_update_params(self->stream, params, 4);
self->negotiated = true;
}
@@ -694,12 +715,14 @@ static void gsr_pipewire_update_cursor_texture(gsr_pipewire *self, gsr_texture_m
self->cursor.data = NULL;
}
-bool gsr_pipewire_map_texture(gsr_pipewire *self, gsr_texture_map texture_map, gsr_pipewire_region *region, gsr_pipewire_region *cursor_region, int *plane_fds, int *num_plane_fds, bool *using_external_image) {
+bool gsr_pipewire_map_texture(gsr_pipewire *self, gsr_texture_map texture_map, gsr_pipewire_region *region, gsr_pipewire_region *cursor_region, gsr_pipewire_dmabuf_data *dmabuf_data, int *num_dmabuf_data, uint32_t *fourcc, uint64_t *modifiers, bool *using_external_image) {
for(int i = 0; i < GSR_PIPEWIRE_DMABUF_MAX_PLANES; ++i) {
- plane_fds[i] = -1;
+ memset(&dmabuf_data[i], 0, sizeof(gsr_pipewire_dmabuf_data));
}
- *num_plane_fds = 0;
+ *num_dmabuf_data = 0;
*using_external_image = self->external_texture_fallback;
+ *fourcc = 0;
+ *modifiers = 0;
pthread_mutex_lock(&self->mutex);
if(!self->negotiated || self->dmabuf_data[0].fd <= 0) {
@@ -738,10 +761,12 @@ bool gsr_pipewire_map_texture(gsr_pipewire *self, gsr_texture_map texture_map, g
cursor_region->height = self->cursor.height;
for(size_t i = 0; i < self->dmabuf_num_planes; ++i) {
- plane_fds[i] = self->dmabuf_data[i].fd;
+ dmabuf_data[i] = self->dmabuf_data[i];
self->dmabuf_data[i].fd = -1;
}
- *num_plane_fds = self->dmabuf_num_planes;
+ *num_dmabuf_data = self->dmabuf_num_planes;
+ *fourcc = spa_video_format_to_drm_format(self->format.info.raw.format);
+ *modifiers = self->format.info.raw.modifier;
self->dmabuf_num_planes = 0;
pthread_mutex_unlock(&self->mutex);
diff --git a/src/utils.c b/src/utils.c
index 3e4138a..b4e34fd 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -12,9 +12,12 @@
#include <xf86drmMode.h>
#include <xf86drm.h>
-
+#include <libdrm/drm_fourcc.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xrandr.h>
+#include <va/va_drmcommon.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/hwcontext_vaapi.h>
double clock_get_monotonic_seconds(void) {
struct timespec ts;
@@ -624,3 +627,260 @@ void setup_dma_buf_attrs(intptr_t *img_attr, uint32_t format, uint32_t width, ui
img_attr[img_attr_index++] = EGL_NONE;
assert(img_attr_index <= 44);
}
+
+static VADisplay video_codec_context_get_vaapi_display(AVCodecContext *video_codec_context) {
+ AVBufferRef *hw_frames_ctx = video_codec_context->hw_frames_ctx;
+ if(!hw_frames_ctx)
+ return NULL;
+
+ AVHWFramesContext *hw_frame_context = (AVHWFramesContext*)hw_frames_ctx->data;
+ AVHWDeviceContext *device_context = (AVHWDeviceContext*)hw_frame_context->device_ctx;
+ if(device_context->type != AV_HWDEVICE_TYPE_VAAPI)
+ return NULL;
+
+ AVVAAPIDeviceContext *vactx = device_context->hwctx;
+ return vactx->display;
+}
+
+bool video_codec_context_is_vaapi(AVCodecContext *video_codec_context) {
+ AVBufferRef *hw_frames_ctx = video_codec_context->hw_frames_ctx;
+ if(!hw_frames_ctx)
+ return NULL;
+
+ AVHWFramesContext *hw_frame_context = (AVHWFramesContext*)hw_frames_ctx->data;
+ AVHWDeviceContext *device_context = (AVHWDeviceContext*)hw_frame_context->device_ctx;
+ return device_context->type == AV_HWDEVICE_TYPE_VAAPI;
+}
+
+static uint32_t drm_fourcc_to_va_fourcc(uint32_t drm_fourcc) {
+ switch(drm_fourcc) {
+ case DRM_FORMAT_XRGB8888: return VA_FOURCC_BGRX;
+ case DRM_FORMAT_XBGR8888: return VA_FOURCC_RGBX;
+ case DRM_FORMAT_RGBX8888: return VA_FOURCC_XBGR;
+ case DRM_FORMAT_BGRX8888: return VA_FOURCC_XRGB;
+ case DRM_FORMAT_ARGB8888: return VA_FOURCC_BGRA;
+ case DRM_FORMAT_ABGR8888: return VA_FOURCC_RGBA;
+ case DRM_FORMAT_RGBA8888: return VA_FOURCC_ABGR;
+ case DRM_FORMAT_BGRA8888: return VA_FOURCC_ARGB;
+ default: return drm_fourcc;
+ }
+}
+
+bool vaapi_copy_drm_planes_to_video_surface(AVCodecContext *video_codec_context, AVFrame *video_frame, vec2i source_pos, vec2i source_size, vec2i dest_pos, vec2i dest_size, uint32_t format, vec2i size, const int *fds, const uint32_t *offsets, const uint32_t *pitches, const uint64_t *modifiers, int num_planes) {
+ VAConfigID config_id = 0;
+ VAContextID context_id = 0;
+ VASurfaceID input_surface_id = 0;
+ VABufferID buffer_id = 0;
+ bool success = true;
+
+ VADisplay va_dpy = video_codec_context_get_vaapi_display(video_codec_context);
+ if(!va_dpy) {
+ success = false;
+ goto done;
+ }
+
+ VAStatus va_status = vaCreateConfig(va_dpy, VAProfileNone, VAEntrypointVideoProc, NULL, 0, &config_id);
+ if(va_status != VA_STATUS_SUCCESS) {
+ fprintf(stderr, "gsr error: vaapi_copy_drm_planes_to_video_surface: vaCreateConfig failed, error: %s\n", vaErrorStr(va_status));
+ success = false;
+ goto done;
+ }
+
+ VASurfaceID output_surface_id = (uintptr_t)video_frame->data[3];
+ va_status = vaCreateContext(va_dpy, config_id, size.x, size.y, VA_PROGRESSIVE, &output_surface_id, 1, &context_id);
+ if(va_status != VA_STATUS_SUCCESS) {
+ fprintf(stderr, "gsr error: vaapi_copy_drm_planes_to_video_surface: vaCreateContext failed, error: %s\n", vaErrorStr(va_status));
+ success = false;
+ goto done;
+ }
+
+ VADRMPRIMESurfaceDescriptor buf = {0};
+ buf.fourcc = drm_fourcc_to_va_fourcc(format);//VA_FOURCC_BGRX; // TODO: VA_FOURCC_BGRA, VA_FOURCC_X2R10G10B10
+ buf.width = size.x;
+ buf.height = size.y;
+ buf.num_objects = num_planes;
+ buf.num_layers = 1;
+ buf.layers[0].drm_format = format;
+ buf.layers[0].num_planes = buf.num_objects;
+ for(int i = 0; i < num_planes; ++i) {
+ buf.objects[i].fd = fds[i];
+ buf.objects[i].size = size.y * pitches[i]; // TODO:
+ buf.objects[i].drm_format_modifier = modifiers[i];
+
+ buf.layers[0].object_index[i] = i;
+ buf.layers[0].offset[i] = offsets[i];
+ buf.layers[0].pitch[i] = pitches[i];
+ }
+
+ VASurfaceAttrib attribs[2] = {0};
+ attribs[0].type = VASurfaceAttribMemoryType;
+ attribs[0].flags = VA_SURFACE_ATTRIB_SETTABLE;
+ attribs[0].value.type = VAGenericValueTypeInteger;
+ attribs[0].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2;
+ attribs[1].type = VASurfaceAttribExternalBufferDescriptor;
+ attribs[1].flags = VA_SURFACE_ATTRIB_SETTABLE;
+ attribs[1].value.type = VAGenericValueTypePointer;
+ attribs[1].value.value.p = &buf;
+
+ // TODO: RT_FORMAT with 10 bit/hdr, VA_RT_FORMAT_RGB32_10
+ // TODO: Max size same as source_size
+ va_status = vaCreateSurfaces(va_dpy, VA_RT_FORMAT_RGB32, size.x, size.y, &input_surface_id, 1, attribs, 2);
+ if(va_status != VA_STATUS_SUCCESS) {
+ fprintf(stderr, "gsr error: vaapi_copy_drm_planes_to_video_surface: vaCreateSurfaces failed, error: %s\n", vaErrorStr(va_status));
+ success = false;
+ goto done;
+ }
+
+ const VARectangle source_region = {
+ .x = source_pos.x,
+ .y = source_pos.y,
+ .width = source_size.x,
+ .height = source_size.y
+ };
+
+ const VARectangle output_region = {
+ .x = dest_pos.x,
+ .y = dest_pos.y,
+ .width = dest_size.x,
+ .height = dest_size.y
+ };
+
+ // Copying a surface to another surface will automatically perform the color conversion. Thanks vaapi!
+ VAProcPipelineParameterBuffer params = {0};
+ params.surface = input_surface_id;
+ params.surface_region = NULL;
+ params.surface_region = &source_region;
+ params.output_region = &output_region;
+ params.output_background_color = 0;
+ params.filter_flags = VA_FRAME_PICTURE;
+ params.pipeline_flags = VA_PROC_PIPELINE_FAST;
+
+ params.input_color_properties.colour_primaries = 1;
+ params.input_color_properties.transfer_characteristics = 1;
+ params.input_color_properties.matrix_coefficients = 1;
+ params.surface_color_standard = VAProcColorStandardBT709; // TODO:
+ params.input_color_properties.color_range = video_frame->color_range == AVCOL_RANGE_JPEG ? VA_SOURCE_RANGE_FULL : VA_SOURCE_RANGE_REDUCED;
+
+ params.output_color_properties.colour_primaries = 1;
+ params.output_color_properties.transfer_characteristics = 1;
+ params.output_color_properties.matrix_coefficients = 1;
+ params.output_color_standard = VAProcColorStandardBT709; // TODO:
+ params.output_color_properties.color_range = video_frame->color_range == AVCOL_RANGE_JPEG ? VA_SOURCE_RANGE_FULL : VA_SOURCE_RANGE_REDUCED;
+
+ params.processing_mode = VAProcPerformanceMode;
+
+ // VAProcPipelineCaps pipeline_caps = {0};
+ // va_status = vaQueryVideoProcPipelineCaps(self->va_dpy,
+ // self->context_id,
+ // NULL, 0,
+ // &pipeline_caps);
+ // if(va_status == VA_STATUS_SUCCESS) {
+ // fprintf(stderr, "pipeline_caps: %u, %u\n", (unsigned int)pipeline_caps.rotation_flags, pipeline_caps.blend_flags);
+ // }
+
+ // TODO: params.output_hdr_metadata
+
+ // TODO:
+ // if (first surface to render)
+ // pipeline_param->output_background_color = 0xff000000; // black
+
+ va_status = vaCreateBuffer(va_dpy, context_id, VAProcPipelineParameterBufferType, sizeof(params), 1, &params, &buffer_id);
+ if(va_status != VA_STATUS_SUCCESS) {
+ fprintf(stderr, "gsr error: vaapi_copy_drm_planes_to_video_surface: vaCreateBuffer failed, error: %d\n", va_status);
+ success = false;
+ goto done;
+ }
+
+ va_status = vaBeginPicture(va_dpy, context_id, output_surface_id);
+ if(va_status != VA_STATUS_SUCCESS) {
+ fprintf(stderr, "gsr error: vaapi_copy_drm_planes_to_video_surface: vaBeginPicture failed, error: %d\n", va_status);
+ success = false;
+ goto done;
+ }
+
+ va_status = vaRenderPicture(va_dpy, context_id, &buffer_id, 1);
+ if(va_status != VA_STATUS_SUCCESS) {
+ vaEndPicture(va_dpy, context_id);
+ fprintf(stderr, "gsr error: vaapi_copy_drm_planes_to_video_surface: vaRenderPicture failed, error: %d\n", va_status);
+ success = false;
+ goto done;
+ }
+
+ va_status = vaEndPicture(va_dpy, context_id);
+ if(va_status != VA_STATUS_SUCCESS) {
+ fprintf(stderr, "gsr error: vaapi_copy_drm_planes_to_video_surface: vaEndPicture failed, error: %d\n", va_status);
+ success = false;
+ goto done;
+ }
+
+ // vaSyncBuffer(va_dpy, buffer_id, 1000 * 1000 * 1000);
+ // vaSyncSurface(va_dpy, input_surface_id);
+ // vaSyncSurface(va_dpy, output_surface_id);
+
+ done:
+ if(buffer_id)
+ vaDestroyBuffer(va_dpy, buffer_id);
+
+ if(input_surface_id)
+ vaDestroySurfaces(va_dpy, &input_surface_id, 1);
+
+ if(context_id)
+ vaDestroyContext(va_dpy, context_id);
+
+ if(config_id)
+ vaDestroyConfig(va_dpy, config_id);
+
+ return success;
+}
+
+bool vaapi_copy_egl_image_to_video_surface(gsr_egl *egl, EGLImage image, vec2i source_pos, vec2i source_size, vec2i dest_pos, vec2i dest_size, AVCodecContext *video_codec_context, AVFrame *video_frame) {
+ if(!image)
+ return false;
+
+ int texture_fourcc = 0;
+ int texture_num_planes = 0;
+ uint64_t texture_modifiers = 0;
+ if(!egl->eglExportDMABUFImageQueryMESA(egl->egl_display, image, &texture_fourcc, &texture_num_planes, &texture_modifiers)) {
+ fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: eglExportDMABUFImageQueryMESA failed\n");
+ return false;
+ }
+
+ if(texture_num_planes <= 0 || texture_num_planes > 8) {
+ fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: expected planes size to be 0<planes<8 for drm buf, got %d planes\n", texture_num_planes);
+ return false;
+ }
+
+ int texture_fds[8];
+ int32_t texture_strides[8];
+ int32_t texture_offsets[8];
+
+ while(egl->eglGetError() != EGL_SUCCESS){}
+ if(!egl->eglExportDMABUFImageMESA(egl->egl_display, image, texture_fds, texture_strides, texture_offsets)) {
+ fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: eglExportDMABUFImageMESA failed, error: %d\n", egl->eglGetError());
+ return false;
+ }
+
+ int fds[8];
+ uint32_t offsets[8];
+ uint32_t pitches[8];
+ uint64_t modifiers[8];
+ for(int i = 0; i < texture_num_planes; ++i) {
+ fds[i] = texture_fds[i];
+ offsets[i] = texture_offsets[i];
+ pitches[i] = texture_strides[i];
+ modifiers[i] = texture_modifiers;
+
+ if(fds[i] == -1)
+ texture_num_planes = i;
+ }
+ const bool success = texture_num_planes > 0 && vaapi_copy_drm_planes_to_video_surface(video_codec_context, video_frame, source_pos, source_size, dest_pos, dest_size, texture_fourcc, source_size, fds, offsets, pitches, modifiers, texture_num_planes);
+
+ for(int i = 0; i < texture_num_planes; ++i) {
+ if(texture_fds[i] > 0) {
+ close(texture_fds[i]);
+ texture_fds[i] = -1;
+ }
+ }
+
+ return success;
+}
diff --git a/src/window_texture.c b/src/window_texture.c
index 0f4aa2c..8eef4c9 100644
--- a/src/window_texture.c
+++ b/src/window_texture.c
@@ -16,6 +16,7 @@ int window_texture_init(WindowTexture *window_texture, Display *display, Window
window_texture->display = display;
window_texture->window = window;
window_texture->pixmap = None;
+ window_texture->image = NULL;
window_texture->texture_id = 0;
window_texture->redirected = 0;
window_texture->egl = egl;
@@ -34,6 +35,11 @@ static void window_texture_cleanup(WindowTexture *self, int delete_texture) {
self->texture_id = 0;
}
+ if(self->image) {
+ self->egl->eglDestroyImage(self->egl->egl_display, self->image);
+ self->image = NULL;
+ }
+
if(self->pixmap) {
XFreePixmap(self->display, self->pixmap);
self->pixmap = None;
@@ -101,14 +107,14 @@ int window_texture_on_resize(WindowTexture *self) {
self->pixmap = pixmap;
self->texture_id = texture_id;
+ self->image = image;
cleanup:
self->egl->glBindTexture(GL_TEXTURE_2D, 0);
- if(image)
- self->egl->eglDestroyImage(self->egl->egl_display, image);
-
if(result != 0) {
+ if(image)
+ self->egl->eglDestroyImage(self->egl->egl_display, image);
if(texture_id != 0)
self->egl->glDeleteTextures(1, &texture_id);
if(pixmap)