aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/capture/kms.c16
-rw-r--r--src/capture/xcomposite.c127
-rw-r--r--src/capture/xcomposite_cuda.c12
-rw-r--r--src/capture/xcomposite_vaapi.c12
-rw-r--r--src/cursor.c81
-rw-r--r--src/egl.c8
-rw-r--r--src/main.cpp144
7 files changed, 287 insertions, 113 deletions
diff --git a/src/capture/kms.c b/src/capture/kms.c
index e479019..ec83cab 100644
--- a/src/capture/kms.c
+++ b/src/capture/kms.c
@@ -77,10 +77,21 @@ int gsr_capture_kms_start(gsr_capture_kms *self, const char *display_to_capture,
/* Disable vsync */
egl->eglSwapInterval(egl->egl_display, 0);
+ // TODO: Move this and xcomposite equivalent to a common section unrelated to capture method
if(egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && video_codec_context->codec_id == AV_CODEC_ID_HEVC) {
// TODO: dont do this if using ffmpeg reports that this is not needed (AMD driver bug that was fixed recently)
self->base.video_codec_context->width = FFALIGN(self->capture_size.x, 64);
self->base.video_codec_context->height = FFALIGN(self->capture_size.y, 16);
+ } else if(egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && video_codec_context->codec_id == AV_CODEC_ID_AV1) {
+ // TODO: Dont do this for VCN 5 and forward which should fix this hardware bug
+ self->base.video_codec_context->width = FFALIGN(self->capture_size.x, 64);
+ // AMD driver has special case handling for 1080 height to set it to 1082 instead of 1088 (1080 aligned to 16).
+ // TODO: Set height to 1082 in this case, but it wont work because it will be aligned to 1088.
+ if(self->capture_size.y == 1080) {
+ self->base.video_codec_context->height = 1080;
+ } else {
+ self->base.video_codec_context->height = FFALIGN(self->capture_size.y, 16);
+ }
} else {
self->base.video_codec_context->width = FFALIGN(self->capture_size.x, 2);
self->base.video_codec_context->height = FFALIGN(self->capture_size.y, 2);
@@ -358,10 +369,15 @@ bool gsr_capture_kms_capture(gsr_capture_kms *self, AVFrame *frame, bool hdr, bo
self->base.egl->eglDestroyImage(self->base.egl->egl_display, cursor_image);
self->base.egl->glBindTexture(target, 0);
+ self->base.egl->glEnable(GL_SCISSOR_TEST);
+ self->base.egl->glScissor(target_x, target_y, self->capture_size.x, self->capture_size.y);
+
gsr_color_conversion_draw(&self->base.color_conversion, self->base.cursor_texture,
cursor_pos, cursor_size,
(vec2i){0, 0}, cursor_size,
texture_rotation, cursor_texture_is_external);
+
+ self->base.egl->glDisable(GL_SCISSOR_TEST);
}
self->base.egl->eglSwapBuffers(self->base.egl->egl_display, self->base.egl->egl_surface);
diff --git a/src/capture/xcomposite.c b/src/capture/xcomposite.c
index 47daa99..3240ed8 100644
--- a/src/capture/xcomposite.c
+++ b/src/capture/xcomposite.c
@@ -6,6 +6,7 @@
#include <unistd.h>
#include <assert.h>
#include <X11/Xlib.h>
+#include <X11/extensions/Xdamage.h>
#include <libavutil/hwcontext.h>
#include <libavutil/hwcontext.h>
#include <libavutil/frame.h>
@@ -36,6 +37,23 @@ static Window get_focused_window(Display *display, Atom net_active_window_atom)
return None;
}
+static void gsr_capture_xcomposite_setup_damage(gsr_capture_xcomposite *self, Window window) {
+ if(self->damage_event == 0)
+ return;
+
+ if(self->damage) {
+ XDamageDestroy(self->params.egl->x11.dpy, self->damage);
+ self->damage = None;
+ }
+
+ self->damage = XDamageCreate(self->params.egl->x11.dpy, window, XDamageReportNonEmpty);
+ if(self->damage) {
+ XDamageSubtract(self->params.egl->x11.dpy, self->damage, None, None);
+ } else {
+ fprintf(stderr, "gsr warning: gsr_capture_xcomposite_setup_damage: XDamageCreate failed\n");
+ }
+}
+
int gsr_capture_xcomposite_start(gsr_capture_xcomposite *self, AVCodecContext *video_codec_context, AVFrame *frame) {
self->base.video_codec_context = video_codec_context;
self->base.egl = self->params.egl;
@@ -51,6 +69,20 @@ int gsr_capture_xcomposite_start(gsr_capture_xcomposite *self, AVCodecContext *v
self->window = self->params.window;
}
+ if(self->params.track_damage) {
+ if(!XDamageQueryExtension(self->params.egl->x11.dpy, &self->damage_event, &self->damage_error)) {
+ fprintf(stderr, "gsr warning: gsr_capture_xcomposite_start: XDamage is not supported by your X11 server\n");
+ self->damage_event = 0;
+ self->damage_error = 0;
+ }
+ } else {
+ self->damage_event = 0;
+ self->damage_error = 0;
+ }
+
+ self->damaged = true;
+ gsr_capture_xcomposite_setup_damage(self, self->window);
+
/* TODO: Do these in tick, and allow error if follow_focused */
XWindowAttributes attr;
@@ -107,6 +139,16 @@ int gsr_capture_xcomposite_start(gsr_capture_xcomposite *self, AVCodecContext *v
// TODO: dont do this if using ffmpeg reports that this is not needed (AMD driver bug that was fixed recently)
video_codec_context->width = FFALIGN(video_size.x, 64);
video_codec_context->height = FFALIGN(video_size.y, 16);
+ } else if(self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && video_codec_context->codec_id == AV_CODEC_ID_AV1) {
+ // TODO: Dont do this for VCN 5 and forward which should fix this hardware bug
+ video_codec_context->width = FFALIGN(video_size.x, 64);
+ // AMD driver has special case handling for 1080 height to set it to 1082 instead of 1088 (1080 aligned to 16).
+ // TODO: Set height to 1082 in this case, but it wont work because it will be aligned to 1088.
+ if(video_size.y == 1080) {
+ video_codec_context->height = 1080;
+ } else {
+ video_codec_context->height = FFALIGN(video_size.y, 16);
+ }
} else {
video_codec_context->width = FFALIGN(video_size.x, 2);
video_codec_context->height = FFALIGN(video_size.y, 2);
@@ -116,11 +158,15 @@ int gsr_capture_xcomposite_start(gsr_capture_xcomposite *self, AVCodecContext *v
frame->height = video_codec_context->height;
self->window_resize_timer = clock_get_monotonic_seconds();
- self->clear_next_frame = true;
return 0;
}
void gsr_capture_xcomposite_stop(gsr_capture_xcomposite *self) {
+ if(self->damage) {
+ XDamageDestroy(self->params.egl->x11.dpy, self->damage);
+ self->damage = None;
+ }
+
window_texture_deinit(&self->window_texture);
gsr_cursor_deinit(&self->cursor);
gsr_capture_base_stop(&self->base);
@@ -171,7 +217,20 @@ void gsr_capture_xcomposite_tick(gsr_capture_xcomposite *self, AVCodecContext *v
}
}
- gsr_cursor_update(&self->cursor, &self->xev);
+ if(self->damage_event && self->xev.type == self->damage_event + XDamageNotify) {
+ XDamageNotifyEvent *de = (XDamageNotifyEvent*)&self->xev;
+ XserverRegion region = XFixesCreateRegion(self->params.egl->x11.dpy, NULL, 0);
+ // Subtract all the damage, repairing the window
+ XDamageSubtract(self->params.egl->x11.dpy, de->damage, None, region);
+ XFixesDestroyRegion(self->params.egl->x11.dpy, region);
+ self->damaged = true;
+ }
+
+ if(gsr_cursor_update(&self->cursor, &self->xev)) {
+ if(self->params.record_cursor && self->cursor.visible) {
+ self->damaged = true;
+ }
+ }
}
if(self->params.follow_focused && !self->follow_focused_initialized) {
@@ -198,6 +257,7 @@ void gsr_capture_xcomposite_tick(gsr_capture_xcomposite *self, AVCodecContext *v
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
+ gsr_capture_xcomposite_setup_damage(self, self->window);
}
}
@@ -221,9 +281,18 @@ void gsr_capture_xcomposite_tick(gsr_capture_xcomposite *self, AVCodecContext *v
self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
gsr_color_conversion_clear(&self->base.color_conversion);
+ gsr_capture_xcomposite_setup_damage(self, self->window);
}
}
+bool gsr_capture_xcomposite_is_damaged(gsr_capture_xcomposite *self) {
+ return self->damage_event ? self->damaged : true;
+}
+
+void gsr_capture_xcomposite_clear_damage(gsr_capture_xcomposite *self) {
+ self->damaged = false;
+}
+
bool gsr_capture_xcomposite_should_stop(gsr_capture_xcomposite *self, bool *err) {
if(self->should_stop) {
if(err)
@@ -242,50 +311,36 @@ int gsr_capture_xcomposite_capture(gsr_capture_xcomposite *self, AVFrame *frame)
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);
- // TODO: Can we do this a better way than to call it every capture?
- if(self->params.record_cursor)
- 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
};
- const bool cursor_completely_inside_window =
- cursor_pos.x >= target_x &&
- cursor_pos.x + self->cursor.size.x <= target_x + self->texture_size.x &&
- cursor_pos.y >= target_y &&
- cursor_pos.y + self->cursor.size.y <= target_y + self->texture_size.y;
-
- const bool cursor_inside_window =
- cursor_pos.x + self->cursor.size.x >= target_x &&
- cursor_pos.x <= target_x + self->texture_size.x &&
- cursor_pos.y + self->cursor.size.y >= target_y &&
- cursor_pos.y <= target_y + self->texture_size.y;
-
- if(self->clear_next_frame) {
- self->clear_next_frame = false;
- gsr_color_conversion_clear(&self->base.color_conversion);
- }
-
- /*
- We dont draw the cursor if it's outside the window but if it's partially inside the window then the cursor area that is outside the window
- will not get overdrawn the next frame causing a cursor trail to be visible since we dont clear the background.
- To fix this we detect if the cursor is partially inside the window and clear the background only in that case.
- */
- if(!cursor_completely_inside_window && cursor_inside_window && self->params.record_cursor)
- self->clear_next_frame = true;
-
gsr_color_conversion_draw(&self->base.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);
- if(cursor_inside_window && self->params.record_cursor) {
- gsr_color_conversion_draw(&self->base.color_conversion, self->cursor.texture_id,
- cursor_pos, self->cursor.size,
- (vec2i){0, 0}, self->cursor.size,
- 0.0f, false);
+ if(self->params.record_cursor && self->cursor.visible) {
+ gsr_cursor_tick(&self->cursor, self->window);
+
+ const bool cursor_inside_window =
+ cursor_pos.x + self->cursor.size.x >= target_x &&
+ cursor_pos.x <= target_x + self->texture_size.x &&
+ cursor_pos.y + self->cursor.size.y >= target_y &&
+ cursor_pos.y <= target_y + self->texture_size.y;
+
+ if(cursor_inside_window) {
+ self->base.egl->glEnable(GL_SCISSOR_TEST);
+ self->base.egl->glScissor(target_x, target_y, self->texture_size.x, self->texture_size.y);
+
+ gsr_color_conversion_draw(&self->base.color_conversion, self->cursor.texture_id,
+ cursor_pos, self->cursor.size,
+ (vec2i){0, 0}, self->cursor.size,
+ 0.0f, false);
+
+ self->base.egl->glDisable(GL_SCISSOR_TEST);
+ }
}
self->params.egl->eglSwapBuffers(self->params.egl->egl_display, self->params.egl->egl_surface);
diff --git a/src/capture/xcomposite_cuda.c b/src/capture/xcomposite_cuda.c
index 6e13d2a..c436221 100644
--- a/src/capture/xcomposite_cuda.c
+++ b/src/capture/xcomposite_cuda.c
@@ -76,6 +76,16 @@ static void gsr_capture_xcomposite_cuda_tick(gsr_capture *cap, AVCodecContext *v
gsr_capture_xcomposite_tick(&cap_xcomp->xcomposite, video_codec_context);
}
+static bool gsr_capture_xcomposite_cuda_is_damaged(gsr_capture *cap) {
+ gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv;
+ return gsr_capture_xcomposite_is_damaged(&cap_xcomp->xcomposite);
+}
+
+static void gsr_capture_xcomposite_cuda_clear_damage(gsr_capture *cap) {
+ gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv;
+ gsr_capture_xcomposite_clear_damage(&cap_xcomp->xcomposite);
+}
+
static bool gsr_capture_xcomposite_cuda_should_stop(gsr_capture *cap, bool *err) {
gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv;
return gsr_capture_xcomposite_should_stop(&cap_xcomp->xcomposite, err);
@@ -144,6 +154,8 @@ gsr_capture* gsr_capture_xcomposite_cuda_create(const gsr_capture_xcomposite_cud
*cap = (gsr_capture) {
.start = gsr_capture_xcomposite_cuda_start,
.tick = gsr_capture_xcomposite_cuda_tick,
+ .is_damaged = gsr_capture_xcomposite_cuda_is_damaged,
+ .clear_damage = gsr_capture_xcomposite_cuda_clear_damage,
.should_stop = gsr_capture_xcomposite_cuda_should_stop,
.capture = gsr_capture_xcomposite_cuda_capture,
.capture_end = NULL,
diff --git a/src/capture/xcomposite_vaapi.c b/src/capture/xcomposite_vaapi.c
index 8c9c56c..3f27014 100644
--- a/src/capture/xcomposite_vaapi.c
+++ b/src/capture/xcomposite_vaapi.c
@@ -43,6 +43,16 @@ static void gsr_capture_xcomposite_vaapi_tick(gsr_capture *cap, AVCodecContext *
gsr_capture_xcomposite_tick(&cap_xcomp->xcomposite, video_codec_context);
}
+static bool gsr_capture_xcomposite_vaapi_is_damaged(gsr_capture *cap) {
+ gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv;
+ return gsr_capture_xcomposite_is_damaged(&cap_xcomp->xcomposite);
+}
+
+static void gsr_capture_xcomposite_vaapi_clear_damage(gsr_capture *cap) {
+ gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv;
+ gsr_capture_xcomposite_clear_damage(&cap_xcomp->xcomposite);
+}
+
static bool gsr_capture_xcomposite_vaapi_should_stop(gsr_capture *cap, bool *err) {
gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv;
return gsr_capture_xcomposite_should_stop(&cap_xcomp->xcomposite, err);
@@ -98,6 +108,8 @@ gsr_capture* gsr_capture_xcomposite_vaapi_create(const gsr_capture_xcomposite_va
*cap = (gsr_capture) {
.start = gsr_capture_xcomposite_vaapi_start,
.tick = gsr_capture_xcomposite_vaapi_tick,
+ .is_damaged = gsr_capture_xcomposite_vaapi_is_damaged,
+ .clear_damage = gsr_capture_xcomposite_vaapi_clear_damage,
.should_stop = gsr_capture_xcomposite_vaapi_should_stop,
.capture = gsr_capture_xcomposite_vaapi_capture,
.capture_end = NULL,
diff --git a/src/cursor.c b/src/cursor.c
index 737c33b..9825ad2 100644
--- a/src/cursor.c
+++ b/src/cursor.c
@@ -6,10 +6,15 @@
#include <assert.h>
#include <X11/extensions/Xfixes.h>
+#include <X11/extensions/XI2.h>
+#include <X11/extensions/XInput2.h>
-static bool gsr_cursor_set_from_x11_cursor_image(gsr_cursor *self, XFixesCursorImage *x11_cursor_image) {
+// TODO: Test cursor visibility with XFixesHideCursor
+
+static bool gsr_cursor_set_from_x11_cursor_image(gsr_cursor *self, XFixesCursorImage *x11_cursor_image, bool *visible) {
uint8_t *cursor_data = NULL;
uint8_t *out = NULL;
+ *visible = false;
if(!x11_cursor_image)
goto err;
@@ -34,8 +39,11 @@ static bool gsr_cursor_set_from_x11_cursor_image(gsr_cursor *self, XFixesCursorI
uint32_t pixel = *pixels++;
uint8_t *in = (uint8_t*)&pixel;
uint8_t alpha = in[3];
- if(alpha == 0)
+ if(alpha == 0) {
alpha = 1;
+ } else {
+ *visible = true;
+ }
*out++ = (unsigned)*in++ * 255/alpha;
*out++ = (unsigned)*in++ * 255/alpha;
@@ -63,6 +71,26 @@ static bool gsr_cursor_set_from_x11_cursor_image(gsr_cursor *self, XFixesCursorI
return false;
}
+static bool xinput_is_supported(Display *dpy, int *xi_opcode) {
+ *xi_opcode = 0;
+ int query_event = 0;
+ int query_error = 0;
+ if(!XQueryExtension(dpy, "XInputExtension", xi_opcode, &query_event, &query_error)) {
+ fprintf(stderr, "gsr error: gsr_cursor_init: X Input extension not available\n");
+ return false;
+ }
+
+ int major = 2;
+ int minor = 1;
+ int retval = XIQueryVersion(dpy, &major, &minor);
+ if (retval != Success) {
+ fprintf(stderr, "gsr error: gsr_cursor_init: XInput 2.1 is not supported\n");
+ return false;
+ }
+
+ return true;
+}
+
int gsr_cursor_init(gsr_cursor *self, gsr_egl *egl, Display *display) {
int x_fixes_error_base = 0;
@@ -79,11 +107,31 @@ int gsr_cursor_init(gsr_cursor *self, gsr_egl *egl, Display *display) {
return -1;
}
+ if(!xinput_is_supported(self->display, &self->xi_opcode)) {
+ gsr_cursor_deinit(self);
+ return -1;
+ }
+
+ unsigned char mask[XIMaskLen(XI_LASTEVENT)];
+ memset(mask, 0, sizeof(mask));
+ XISetMask(mask, XI_RawMotion);
+
+ XIEventMask xi_masks;
+ xi_masks.deviceid = XIAllMasterDevices;
+ xi_masks.mask_len = sizeof(mask);
+ xi_masks.mask = mask;
+ if(XISelectEvents(self->display, DefaultRootWindow(self->display), &xi_masks, 1) != Success) {
+ fprintf(stderr, "gsr error: gsr_cursor_init: XISelectEvents failed\n");
+ gsr_cursor_deinit(self);
+ return -1;
+ }
+
self->egl->glGenTextures(1, &self->texture_id);
XFixesSelectCursorInput(self->display, DefaultRootWindow(self->display), XFixesDisplayCursorNotifyMask);
- gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display));
+ gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display), &self->visible);
self->cursor_image_set = true;
+ self->cursor_moved = true;
return 0;
}
@@ -97,29 +145,46 @@ void gsr_cursor_deinit(gsr_cursor *self) {
self->texture_id = 0;
}
+ XISelectEvents(self->display, DefaultRootWindow(self->display), NULL, 0);
XFixesSelectCursorInput(self->display, DefaultRootWindow(self->display), 0);
self->display = NULL;
self->egl = NULL;
}
-void gsr_cursor_update(gsr_cursor *self, XEvent *xev) {
+bool gsr_cursor_update(gsr_cursor *self, XEvent *xev) {
+ bool updated = false;
+ XGenericEventCookie *cookie = (XGenericEventCookie*)&xev->xcookie;
+ const Bool got_event_data = XGetEventData(self->display, cookie);
+ if(got_event_data && cookie->type == GenericEvent && cookie->extension == self->xi_opcode && cookie->evtype == XI_RawMotion) {
+ updated = true;
+ self->cursor_moved = true;
+ }
+ if(got_event_data)
+ XFreeEventData(self->display, cookie);
+
if(xev->type == self->x_fixes_event_base + XFixesCursorNotify) {
XFixesCursorNotifyEvent *cursor_notify_event = (XFixesCursorNotifyEvent*)xev;
if(cursor_notify_event->subtype == XFixesDisplayCursorNotify && cursor_notify_event->window == DefaultRootWindow(self->display)) {
- self->cursor_image_set = true;
- gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display));
+ self->cursor_image_set = false;
}
}
if(!self->cursor_image_set) {
self->cursor_image_set = true;
- gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display));
+ gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display), &self->visible);
+ updated = true;
}
+
+ return updated;
}
void gsr_cursor_tick(gsr_cursor *self, Window relative_to) {
- /* TODO: Use XInput2 instead. However that doesn't work when the pointer is grabbed. Maybe check for focused window change and XSelectInput PointerMask */
+ if(!self->cursor_moved)
+ return;
+
+ self->cursor_moved = false;
+
Window dummy_window;
int dummy_i;
unsigned int dummy_u;
diff --git a/src/egl.c b/src/egl.c
index 2c139e6..552d5f4 100644
--- a/src/egl.c
+++ b/src/egl.c
@@ -328,7 +328,7 @@ static bool gsr_egl_switch_to_glx_context(gsr_egl *self) {
}
static bool gsr_egl_load_egl(gsr_egl *self, void *library) {
- dlsym_assign required_dlsym[] = {
+ const dlsym_assign required_dlsym[] = {
{ (void**)&self->eglGetError, "eglGetError" },
{ (void**)&self->eglGetDisplay, "eglGetDisplay" },
{ (void**)&self->eglInitialize, "eglInitialize" },
@@ -373,7 +373,7 @@ static bool gsr_egl_proc_load_egl(gsr_egl *self) {
}
static bool gsr_egl_load_glx(gsr_egl *self, void *library) {
- dlsym_assign required_dlsym[] = {
+ const dlsym_assign required_dlsym[] = {
{ (void**)&self->glXGetProcAddress, "glXGetProcAddress" },
{ (void**)&self->glXChooseFBConfig, "glXChooseFBConfig" },
{ (void**)&self->glXMakeContextCurrent, "glXMakeContextCurrent" },
@@ -403,7 +403,7 @@ static bool gsr_egl_load_glx(gsr_egl *self, void *library) {
}
static bool gsr_egl_load_gl(gsr_egl *self, void *library) {
- dlsym_assign required_dlsym[] = {
+ const dlsym_assign required_dlsym[] = {
{ (void**)&self->glGetError, "glGetError" },
{ (void**)&self->glGetString, "glGetString" },
{ (void**)&self->glFlush, "glFlush" },
@@ -452,11 +452,13 @@ static bool gsr_egl_load_gl(gsr_egl *self, void *library) {
{ (void**)&self->glEnableVertexAttribArray, "glEnableVertexAttribArray" },
{ (void**)&self->glDrawArrays, "glDrawArrays" },
{ (void**)&self->glEnable, "glEnable" },
+ { (void**)&self->glDisable, "glDisable" },
{ (void**)&self->glBlendFunc, "glBlendFunc" },
{ (void**)&self->glGetUniformLocation, "glGetUniformLocation" },
{ (void**)&self->glUniform1f, "glUniform1f" },
{ (void**)&self->glUniform2f, "glUniform2f" },
{ (void**)&self->glDebugMessageCallback, "glDebugMessageCallback" },
+ { (void**)&self->glScissor, "glScissor" },
{ NULL, NULL }
};
diff --git a/src/main.cpp b/src/main.cpp
index ce56373..1d479fe 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -101,7 +101,8 @@ enum class PixelFormat {
enum class FramerateMode {
CONSTANT,
- VARIABLE
+ VARIABLE,
+ CONTENT
};
static int x11_error_handler(Display*, XErrorEvent*) {
@@ -327,7 +328,7 @@ static AVCodecContext* create_audio_codec_context(int fps, AudioCodec audio_code
static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
VideoQuality video_quality,
- int fps, const AVCodec *codec, bool is_livestream, gsr_gpu_vendor vendor, FramerateMode framerate_mode,
+ int fps, const AVCodec *codec, bool low_latency, gsr_gpu_vendor vendor, FramerateMode framerate_mode,
bool hdr, gsr_color_range color_range, float keyint) {
AVCodecContext *codec_context = avcodec_alloc_context3(codec);
@@ -346,14 +347,14 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
codec_context->framerate.den = 1;
codec_context->sample_aspect_ratio.num = 0;
codec_context->sample_aspect_ratio.den = 0;
- // High values reduce file size but increases time it takes to seek
- if(is_livestream) {
+ if(low_latency) {
codec_context->flags |= (AV_CODEC_FLAG_CLOSED_GOP | AV_CODEC_FLAG_LOW_DELAY);
codec_context->flags2 |= AV_CODEC_FLAG2_FAST;
//codec_context->gop_size = std::numeric_limits<int>::max();
//codec_context->keyint_min = std::numeric_limits<int>::max();
codec_context->gop_size = fps * keyint;
} else {
+ // High values reduce file size but increases time it takes to seek
codec_context->gop_size = fps * keyint;
}
codec_context->max_b_frames = 0;
@@ -792,6 +793,7 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality
if(codec_context->codec_id == AV_CODEC_ID_H264) {
av_dict_set(&options, "profile", "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?
@@ -822,7 +824,7 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality
static void usage_header() {
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
const char *program_name = inside_flatpak ? "flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder" : "gpu-screen-recorder";
- fprintf(stderr, "usage: %s -w <window_id|monitor|focused> [-c <container_format>] [-s WxH] -f <fps> [-a <audio_input>] [-q <quality>] [-r <replay_buffer_size_sec>] [-k h264|hevc|hevc_hdr|av1|av1_hdr] [-ac aac|opus|flac] [-ab <bitrate>] [-oc yes|no] [-fm cfr|vfr] [-cr limited|full] [-v yes|no] [-h|--help] [-o <output_file>] [-mf yes|no] [-sc <script_path>] [-cursor yes|no] [-keyint <value>]\n", program_name);
+ fprintf(stderr, "usage: %s -w <window_id|monitor|focused> [-c <container_format>] [-s WxH] -f <fps> [-a <audio_input>] [-q <quality>] [-r <replay_buffer_size_sec>] [-k h264|hevc|hevc_hdr|av1|av1_hdr] [-ac aac|opus|flac] [-ab <bitrate>] [-oc yes|no] [-fm cfr|vfr|content] [-cr limited|full] [-mf yes|no] [-sc <script_path>] [-cursor yes|no] [-keyint <value>] [-o <output_file>] [-v yes|no] [-h|--help]\n", program_name);
}
static void usage_full() {
@@ -843,7 +845,10 @@ static void usage_full() {
fprintf(stderr, "\n");
fprintf(stderr, " -s The size (area) to record at in the format WxH, for example 1920x1080. This option is only supported (and required) when -w is \"focused\".\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -f Framerate to record at.\n");
+ fprintf(stderr, " -f Frame rate to record at. Recording will only capture frames at this target frame rate.\n");
+ fprintf(stderr, " For constant frame rate mode this option is the frame rate every frame will be captured at and if the capture frame rate is below this target frame rate then the frames will be duplicated.\n");
+ fprintf(stderr, " For variable frame rate mode this option is the max frame rate and if the capture frame rate is below this target frame rate then frames will not be duplicated.\n");
+ fprintf(stderr, " Content frame rate is similar to variable frame rate mode, except the frame rate will match the frame rate of the captured content when possible, but not capturing above the frame rate set in this -f option.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -a Audio device to record from (pulse audio device). Can be specified multiple times. Each time this is specified a new audio track is added for the specified audio device.\n");
fprintf(stderr, " A name can be given to the audio input device by prefixing the audio input with <name>/, for example \"dummy/alsa_output.pci-0000_00_1b.0.analog-stereo.monitor\".\n");
@@ -858,9 +863,8 @@ static void usage_full() {
fprintf(stderr, " and the video will only be saved when the gpu-screen-recorder is closed. This feature is similar to Nvidia's instant replay feature.\n");
fprintf(stderr, " This option has be between 5 and 1200. Note that the replay buffer size will not always be precise, because of keyframes. Optional, disabled by default.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -k Video codec to use. Should be either 'auto', 'h264', 'hevc', 'av1', 'hevc_hdr' or 'av1_hdr'. Defaults to 'auto' which defaults to 'hevc' on AMD/Nvidia and 'h264' on intel.\n");
+ fprintf(stderr, " -k Video codec to use. Should be either 'auto', 'h264', 'hevc', 'av1', 'hevc_hdr' or 'av1_hdr'. Defaults to 'auto' which defaults to 'h264'.\n");
fprintf(stderr, " Forcefully set to 'h264' if the file container type is 'flv'.\n");
- fprintf(stderr, " Forcefully set to 'hevc' on AMD/intel if video codec is 'h264' and if the file container type is 'mkv'.\n");
fprintf(stderr, " 'hevc_hdr' and 'av1_hdr' option is not available on X11.\n");
fprintf(stderr, " Note: hdr metadata is not included in the video when recording with 'hevc_hdr'/'av1_hdr' because of bugs in AMD, Intel and NVIDIA drivers (amazin', they are all bugged).\n");
fprintf(stderr, "\n");
@@ -875,17 +879,14 @@ static void usage_full() {
fprintf(stderr, " is dropped when you record a game. Only needed if you are recording a game that is bottlenecked by GPU. The same issue exists on Wayland but overclocking is not possible on Wayland.\n");
fprintf(stderr, " Works only if your have \"Coolbits\" set to \"12\" in NVIDIA X settings, see README for more information. Note! use at your own risk! Optional, disabled by default.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -fm Framerate mode. Should be either 'cfr' or 'vfr'. Defaults to 'vfr'.\n");
+ fprintf(stderr, " -fm Framerate mode. Should be either 'cfr' (constant frame rate), 'vfr' (variable frame rate) or 'content'. Defaults to 'vfr'.\n");
+ fprintf(stderr, " 'vfr' is recommended for recording for less issue with very high system load but some applications such as video editors may not support it properly.\n");
+ fprintf(stderr, " 'content' is currently only supported when recording a single window, on X11. The 'content' option matches the recording frame rate to the captured content.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -cr Color range. Should be either 'limited' (aka mpeg) or 'full' (aka jpeg). Defaults to 'limited'.\n");
fprintf(stderr, " Limited color range means that colors are in range 16-235 (4112-60395 for hdr) while full color range means that colors are in range 0-255 (0-65535 for hdr).\n");
fprintf(stderr, " Note that some buggy video players (such as vlc) are unable to correctly display videos in full color range.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -v Prints per second, fps updates. Optional, set to 'yes' by default.\n");
- fprintf(stderr, "\n");
- fprintf(stderr, " -h, --help\n");
- fprintf(stderr, " Show this help.\n");
- fprintf(stderr, "\n");
fprintf(stderr, " -mf Organise replays in folders based on the current date.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -sc Run a script on the saved video file (non-blocking). The first argument to the script is the filepath to the saved video file and the second argument is the recording type (either \"regular\" or \"replay\").\n");
@@ -907,6 +908,11 @@ static void usage_full() {
fprintf(stderr, " In replay mode this has to be a directory instead of a file.\n");
fprintf(stderr, " The directory to the file is created (recursively) if it doesn't already exist.\n");
fprintf(stderr, "\n");
+ fprintf(stderr, " -v Prints per second, fps updates. Optional, set to 'yes' by default.\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, " -h, --help\n");
+ fprintf(stderr, " Show this help.\n");
+ fprintf(stderr, "\n");
fprintf(stderr, "NOTES:\n");
fprintf(stderr, " Send signal SIGINT to gpu-screen-recorder (Ctrl+C, or killall -SIGINT gpu-screen-recorder) to stop and save the recording. When in replay mode this stops recording without saving.\n");
fprintf(stderr, " Send signal SIGUSR1 to gpu-screen-recorder (killall -SIGUSR1 gpu-screen-recorder) to save a replay (when in replay mode).\n");
@@ -1292,6 +1298,14 @@ static bool is_livestream_path(const char *str) {
return true;
else if((len >= 7 && memcmp(str, "rtmp://", 7) == 0) || (len >= 8 && memcmp(str, "rtmps://", 8) == 0))
return true;
+ else if((len >= 7 && memcmp(str, "rtsp://", 7) == 0))
+ return true;
+ else if((len >= 6 && memcmp(str, "srt://", 6) == 0))
+ return true;
+ else if((len >= 6 && memcmp(str, "tcp://", 6) == 0))
+ return true;
+ else if((len >= 6 && memcmp(str, "udp://", 6) == 0))
+ return true;
else
return false;
}
@@ -1465,7 +1479,7 @@ static void list_supported_video_codecs() {
XCloseDisplay(dpy);
}
-static gsr_capture* create_capture_impl(const char *window_str, const char *screen_region, bool wayland, gsr_egl &egl, int fps, bool overclock, VideoCodec video_codec, gsr_color_range color_range, bool record_cursor) {
+static gsr_capture* create_capture_impl(const char *window_str, const char *screen_region, bool wayland, gsr_egl &egl, int fps, bool overclock, VideoCodec video_codec, gsr_color_range color_range, bool record_cursor, bool track_damage) {
vec2i region_size = { 0, 0 };
Window src_window_id = None;
bool follow_focused = false;
@@ -1504,6 +1518,7 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre
window_str = first_output.output_name;
} else {
fprintf(stderr, "Error: no available output found\n");
+ _exit(1);
}
}
@@ -1607,6 +1622,7 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre
xcomposite_params.base.region_size = region_size;
xcomposite_params.base.color_range = color_range;
xcomposite_params.base.record_cursor = record_cursor;
+ xcomposite_params.base.track_damage = track_damage;
capture = gsr_capture_xcomposite_vaapi_create(&xcomposite_params);
if(!capture)
_exit(1);
@@ -1620,6 +1636,7 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre
xcomposite_params.base.region_size = region_size;
xcomposite_params.base.color_range = color_range;
xcomposite_params.base.record_cursor = record_cursor;
+ xcomposite_params.base.track_damage = track_damage;
xcomposite_params.overclock = overclock;
capture = gsr_capture_xcomposite_cuda_create(&xcomposite_params);
if(!capture)
@@ -1727,7 +1744,7 @@ int main(int argc, char **argv) {
}
}
- VideoCodec video_codec = VideoCodec::HEVC;
+ VideoCodec video_codec = VideoCodec::H264;
const char *video_codec_to_use = args["-k"].value();
if(!video_codec_to_use)
video_codec_to_use = "auto";
@@ -1941,11 +1958,11 @@ int main(int argc, char **argv) {
if(fps < 1)
fps = 1;
+ VideoQuality quality = VideoQuality::VERY_HIGH;
const char *quality_str = args["-q"].value();
if(!quality_str)
quality_str = "very_high";
- VideoQuality quality;
if(strcmp(quality_str, "medium") == 0) {
quality = VideoQuality::MEDIUM;
} else if(strcmp(quality_str, "high") == 0) {
@@ -2024,7 +2041,7 @@ int main(int argc, char **argv) {
// 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.
- FramerateMode framerate_mode;
+ FramerateMode framerate_mode = FramerateMode::VARIABLE;
const char *framerate_mode_str = args["-fm"].value();
if(!framerate_mode_str)
framerate_mode_str = "vfr";
@@ -2033,12 +2050,19 @@ int main(int argc, char **argv) {
framerate_mode = FramerateMode::CONSTANT;
} else if(strcmp(framerate_mode_str, "vfr") == 0) {
framerate_mode = FramerateMode::VARIABLE;
+ } else if(strcmp(framerate_mode_str, "content") == 0) {
+ framerate_mode = FramerateMode::CONTENT;
} else {
- fprintf(stderr, "Error: -fm should either be either 'cfr' or 'vfr', got: '%s'\n", framerate_mode_str);
+ fprintf(stderr, "Error: -fm should either be either 'cfr', 'vfr' or 'content', got: '%s'\n", framerate_mode_str);
+ usage();
+ }
+
+ if(framerate_mode == FramerateMode::CONTENT && (wayland || is_monitor_capture)) {
+ fprintf(stderr, "Error: -fm 'content' is currently only supported on X11 and when capturing a single window.\n");
usage();
}
- gsr_color_range color_range;
+ gsr_color_range color_range = GSR_COLOR_RANGE_LIMITED;
const char *color_range_str = args["-cr"].value();
if(!color_range_str)
color_range_str = "limited";
@@ -2106,14 +2130,18 @@ int main(int argc, char **argv) {
}
}
+ const bool is_output_piped = strcmp(filename, "/dev/stdout") == 0;
+
AVFormatContext *av_format_context;
// The output format is automatically guessed by the file extension
avformat_alloc_output_context2(&av_format_context, nullptr, container_format, filename);
if (!av_format_context) {
- if(container_format)
+ if(container_format) {
fprintf(stderr, "Error: Container format '%s' (argument -c) is not valid\n", container_format);
- else
- fprintf(stderr, "Error: Failed to deduce container format from file extension\n");
+ } else {
+ fprintf(stderr, "Error: Failed to deduce container format from file extension. Use the '-c' option to specify container format\n");
+ usage();
+ }
_exit(1);
}
@@ -2126,13 +2154,7 @@ int main(int argc, char **argv) {
file_extension = file_extension.substr(0, comma_index);
}
- const bool force_no_audio_offset = is_livestream || (file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm");
-
- if(egl.gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA && file_extension == "mkv" && strcmp(video_codec_to_use, "h264") == 0) {
- video_codec_to_use = "hevc";
- video_codec = VideoCodec::HEVC;
- fprintf(stderr, "Warning: video codec was forcefully set to hevc because mkv container is used and mesa (AMD and Intel driver) does not support h264 in mkv files\n");
- }
+ const bool force_no_audio_offset = is_livestream || is_output_piped || (file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm");
switch(audio_codec) {
case AudioCodec::AAC: {
@@ -2176,38 +2198,15 @@ int main(int argc, char **argv) {
const bool video_codec_auto = strcmp(video_codec_to_use, "auto") == 0;
if(video_codec_auto) {
- if(egl.gpu_info.vendor == GSR_GPU_VENDOR_INTEL) {
- const AVCodec *h264_codec = find_h264_encoder(egl.gpu_info.vendor, egl.card_path);
- if(!h264_codec) {
- fprintf(stderr, "Info: using hevc encoder because a codec was not specified and your gpu does not support h264\n");
- video_codec_to_use = "hevc";
- video_codec = VideoCodec::HEVC;
- } else {
- fprintf(stderr, "Info: using h264 encoder because a codec was not specified\n");
- video_codec_to_use = "h264";
- video_codec = VideoCodec::H264;
- }
+ const AVCodec *h264_codec = find_h264_encoder(egl.gpu_info.vendor, egl.card_path);
+ if(!h264_codec) {
+ fprintf(stderr, "Info: using hevc encoder because a codec was not specified and your gpu does not support h264\n");
+ video_codec_to_use = "hevc";
+ video_codec = VideoCodec::HEVC;
} else {
- const AVCodec *hevc_codec = find_hevc_encoder(egl.gpu_info.vendor, egl.card_path);
-
- if(hevc_codec && fps > 60) {
- fprintf(stderr, "Warning: recording at higher fps than 60 with hevc might result in recording at a very low fps. If this happens, switch to h264 or av1\n");
- }
-
- // TODO: Default to h264 if resolution is around 1366x768 on AMD
-
- // hevc generally allows recording at a higher resolution than h264 on nvidia cards. On a gtx 1080 4k is the max resolution for h264 but for hevc it's 8k.
- // Another important info is that when recording at a higher fps than.. 60? hevc has very bad performance. For example when recording at 144 fps the fps drops to 1
- // while with h264 the fps doesn't drop.
- if(!hevc_codec) {
- fprintf(stderr, "Info: using h264 encoder because a codec was not specified and your gpu does not support hevc\n");
- video_codec_to_use = "h264";
- video_codec = VideoCodec::H264;
- } else {
- fprintf(stderr, "Info: using hevc encoder because a codec was not specified\n");
- video_codec_to_use = "hevc";
- video_codec = VideoCodec::HEVC;
- }
+ fprintf(stderr, "Info: using h264 encoder because a codec was not specified\n");
+ video_codec_to_use = "h264";
+ video_codec = VideoCodec::H264;
}
}
@@ -2316,7 +2315,7 @@ int main(int argc, char **argv) {
_exit(2);
}
- gsr_capture *capture = create_capture_impl(window_str, screen_region, wayland, egl, fps, overclock, video_codec, color_range, record_cursor);
+ gsr_capture *capture = create_capture_impl(window_str, screen_region, wayland, egl, fps, overclock, video_codec, color_range, record_cursor, framerate_mode == FramerateMode::CONTENT);
// (Some?) livestreaming services require at least one audio track to work.
// If not audio is provided then create one silent audio track.
@@ -2335,8 +2334,9 @@ int main(int argc, char **argv) {
AVStream *video_stream = nullptr;
std::vector<AudioTrack> audio_tracks;
const bool hdr = video_codec_is_hdr(video_codec);
+ const bool low_latency_recording = is_livestream || is_output_piped;
- AVCodecContext *video_codec_context = create_video_codec_context(egl.gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_CUDA : AV_PIX_FMT_VAAPI, quality, fps, video_codec_f, is_livestream, egl.gpu_info.vendor, framerate_mode, hdr, color_range, keyint);
+ AVCodecContext *video_codec_context = create_video_codec_context(egl.gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_CUDA : AV_PIX_FMT_VAAPI, quality, fps, video_codec_f, low_latency_recording, egl.gpu_info.vendor, framerate_mode, hdr, color_range, keyint);
if(replay_buffer_size_secs == -1)
video_stream = create_stream(av_format_context, video_codec_context);
@@ -2473,6 +2473,7 @@ int main(int argc, char **argv) {
double fps_start_time = clock_get_monotonic_seconds();
double frame_timer_start = fps_start_time - target_fps; // We want to capture the first frame immediately
int fps_counter = 0;
+ int damage_fps_counter = 0;
bool paused = false;
double paused_time_offset = 0.0;
@@ -2509,6 +2510,9 @@ int main(int argc, char **argv) {
#if LIBAVUTIL_VERSION_MAJOR <= 56
av_opt_set_channel_layout(swr, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_channel_layout(swr, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
+ #elif LIBAVUTIL_VERSION_MAJOR >= 59
+ av_opt_set_chlayout(swr, "in_chlayout", &audio_track.codec_context->ch_layout, 0);
+ av_opt_set_chlayout(swr, "out_chlayout", &audio_track.codec_context->ch_layout, 0);
#else
av_opt_set_chlayout(swr, "in_channel_layout", &audio_track.codec_context->ch_layout, 0);
av_opt_set_chlayout(swr, "out_channel_layout", &audio_track.codec_context->ch_layout, 0);
@@ -2664,7 +2668,6 @@ int main(int argc, char **argv) {
running = 0;
break;
}
- ++fps_counter;
// TODO: Move to another thread, since this shouldn't be locked to video encoding fps
{
@@ -2689,19 +2692,28 @@ int main(int argc, char **argv) {
}
}
+ const bool damaged = !capture->is_damaged || capture->is_damaged(capture);
+ if(damaged) {
+ ++damage_fps_counter;
+ }
+
+ ++fps_counter;
double time_now = clock_get_monotonic_seconds();
double frame_timer_elapsed = time_now - frame_timer_start;
double elapsed = time_now - fps_start_time;
if (elapsed >= 1.0) {
if(verbose) {
- fprintf(stderr, "update fps: %d\n", fps_counter);
+ fprintf(stderr, "update fps: %d, damage fps: %d\n", fps_counter, damage_fps_counter);
}
fps_start_time = time_now;
fps_counter = 0;
+ damage_fps_counter = 0;
}
double frame_time_overflow = frame_timer_elapsed - target_fps;
- if (frame_time_overflow >= 0.0) {
+ if (frame_time_overflow >= 0.0 && damaged) {
+ if(capture->clear_damage)
+ capture->clear_damage(capture);
frame_time_overflow = std::min(frame_time_overflow, target_fps);
frame_timer_start = time_now - frame_time_overflow;