Compare commits

...

10 Commits

11 changed files with 63 additions and 53 deletions

View File

@@ -78,7 +78,6 @@ GPU Screen Recorder uses meson build system so you need to install `meson` to bu
## Build dependencies
These are the dependencies needed to build GPU Screen Recorder:
* libglvnd (which provides libgl, libglx and libegl)
* vulkan-headers
* ffmpeg (libavcodec, libavformat, libavutil, libswresample, libavfilter)
* x11 (libx11, libxcomposite, libxrandr, libxfixes, libxdamage)
@@ -89,6 +88,8 @@ These are the dependencies needed to build GPU Screen Recorder:
* wayland (wayland-client, wayland-egl, wayland-scanner)
## Runtime dependencies
* libglvnd (which provides libgl, libglx and libegl) is needed. Your system needs to support at least OpenGL ES 3.0 (released in 2012)
There are also additional dependencies needed at runtime depending on your GPU vendor:
### AMD
@@ -97,7 +98,7 @@ There are also additional dependencies needed at runtime depending on your GPU v
### Intel
* mesa
* vaapi (intel-media-driver/libva-intel-driver/linux-firmware, depending on which intel iGPU you have)
* vaapi (intel-media-driver/libva-intel-driver/linux-firmware-intel, depending on which intel iGPU you have)
### NVIDIA
* cuda runtime (libcuda.so.1) (libnvidia-compute)

4
TODO
View File

@@ -302,3 +302,7 @@ Allow medium, high, very_high and ultra quality for -bm cbr. If that is used the
Maybe do this in the ui instead (or both?), to show estimated file size.
Maybe remove shader compute code. It doesn't seem necessary anymore now that glSwapBuffer/glFinish isn't used. dbus server isn't needed anymore either, the code can be moved back to the gpu screen recorder process.
Add proper check if opengl functions are supported. dlsym for the symbol will return a no-op function if it's not supported, so it silently fails if used.
Colors are offset to bottom left by 1 pixel or so on steam deck in landscape mode.

View File

@@ -48,6 +48,8 @@ typedef void(*__GLXextFuncPtr)(void);
#define EGL_OPENGL_ES_API 0x30A0
#define EGL_OPENGL_BIT 0x0008
#define EGL_OPENGL_ES_BIT 0x0001
#define EGL_OPENGL_ES2_BIT 0x0004
#define EGL_OPENGL_ES3_BIT 0x00000040
#define EGL_NONE 0x3038
#define EGL_CONTEXT_CLIENT_VERSION 0x3098
#define EGL_BACK_BUFFER 0x3084

View File

@@ -9,7 +9,7 @@
#include <spa/param/video/format.h>
#define GSR_PIPEWIRE_VIDEO_MAX_MODIFIERS 1024
#define GSR_PIPEWIRE_VIDEO_MAX_VIDEO_FORMATS 12
#define GSR_PIPEWIRE_VIDEO_MAX_VIDEO_FORMATS 6
#define GSR_PIPEWIRE_VIDEO_DMABUF_MAX_PLANES 4
typedef struct gsr_egl gsr_egl;

View File

@@ -1,4 +1,4 @@
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.5.8', default_options : ['warning_level=2'])
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.5.10', default_options : ['warning_level=2'])
add_project_arguments('-Wshadow', language : ['c', 'cpp'])
if get_option('buildtype') == 'debug'

View File

@@ -1,7 +1,7 @@
[package]
name = "gpu-screen-recorder"
type = "executable"
version = "5.5.8"
version = "5.5.10"
platforms = ["posix"]
[config]

6
scripts/toggle-recording.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/sh -e
killall -SIGINT gpu-screen-recorder && sleep 0.5 && notify-send -t 1500 -u low 'GPU Screen Recorder' 'Stopped recording' && exit 0;
video="$HOME/Videos/$(date +"Video_%Y-%m-%d_%H-%M-%S.mp4")"
notify-send -t 1500 -u low 'GPU Screen Recorder' "Started recording video to $video"
gpu-screen-recorder -w screen -f 60 -a "default_output" -o "$video"

View File

@@ -12,7 +12,7 @@
#include <fcntl.h>
#include <xf86drm.h>
#include <libdrm/drm_fourcc.h>
#include <drm_fourcc.h>
#include <libavutil/mastering_display_metadata.h>

View File

@@ -23,6 +23,12 @@ typedef struct {
vec2i capture_size;
gsr_pipewire_video_dmabuf_data dmabuf_data[GSR_PIPEWIRE_VIDEO_DMABUF_MAX_PLANES];
int num_dmabuf_data;
gsr_pipewire_video_region region;
gsr_pipewire_video_region cursor_region;
uint32_t pipewire_fourcc;
uint64_t pipewire_modifiers;
bool using_external_image;
} gsr_capture_portal;
static void gsr_capture_portal_cleanup_plane_fds(gsr_capture_portal *self) {
@@ -216,19 +222,13 @@ static int gsr_capture_portal_setup_dbus(gsr_capture_portal *self, int *pipewire
}
static bool gsr_capture_portal_get_frame_dimensions(gsr_capture_portal *self) {
gsr_pipewire_video_region region = {0, 0, 0, 0};
gsr_pipewire_video_region cursor_region = {0, 0, 0, 0};
fprintf(stderr, "gsr info: gsr_capture_portal_start: waiting for pipewire negotiation\n");
const double start_time = clock_get_monotonic_seconds();
while(clock_get_monotonic_seconds() - start_time < 5.0) {
bool uses_external_image = false;
uint32_t fourcc = 0;
uint64_t modifiers = 0;
if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, &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;
if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, &self->region, &self->cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &self->pipewire_fourcc, &self->pipewire_modifiers, &self->using_external_image)) {
self->capture_size.x = self->region.width;
self->capture_size.y = self->region.height;
fprintf(stderr, "gsr info: gsr_capture_portal_start: pipewire negotiation finished\n");
return true;
}
@@ -300,20 +300,17 @@ static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *ca
(void)color_conversion;
gsr_capture_portal *self = cap->priv;
/* TODO: Handle formats other than RGB(a) */
gsr_pipewire_video_region region = {0, 0, 0, 0};
gsr_pipewire_video_region cursor_region = {0, 0, 0, 0};
uint32_t pipewire_fourcc = 0;
uint64_t pipewire_modifiers = 0;
bool using_external_image = false;
if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, &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) {
self->capture_size.x = region.width;
self->capture_size.y = region.height;
gsr_color_conversion_clear(color_conversion);
/* TODO: Handle formats other than RGB(A) */
if(self->num_dmabuf_data == 0) {
if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, &self->region, &self->cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &self->pipewire_fourcc, &self->pipewire_modifiers, &self->using_external_image)) {
if(self->region.width != self->capture_size.x || self->region.height != self->capture_size.y) {
self->capture_size.x = self->region.width;
self->capture_size.y = self->region.height;
gsr_color_conversion_clear(color_conversion);
}
} else {
return -1;
}
} else {
return -1;
}
const bool is_scaled = self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0;
@@ -327,27 +324,27 @@ static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *ca
// TODO: Handle region crop
gsr_color_conversion_draw(color_conversion, using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id,
gsr_color_conversion_draw(color_conversion, self->using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id,
target_pos, output_size,
(vec2i){region.x, region.y}, self->capture_size, self->capture_size,
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, using_external_image, false);
(vec2i){self->region.x, self->region.y}, self->capture_size, self->capture_size,
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, self->using_external_image, false);
if(self->params.record_cursor && self->texture_map.cursor_texture_id > 0 && cursor_region.width > 0) {
if(self->params.record_cursor && self->texture_map.cursor_texture_id > 0 && self->cursor_region.width > 0) {
const vec2d scale = {
self->capture_size.x == 0 ? 0 : (double)output_size.x / (double)self->capture_size.x,
self->capture_size.y == 0 ? 0 : (double)output_size.y / (double)self->capture_size.y
};
const vec2i cursor_pos = {
target_pos.x + (cursor_region.x * scale.x),
target_pos.y + (cursor_region.y * scale.y)
target_pos.x + (self->cursor_region.x * scale.x),
target_pos.y + (self->cursor_region.y * scale.y)
};
self->params.egl->glEnable(GL_SCISSOR_TEST);
self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y);
gsr_color_conversion_draw(color_conversion, self->texture_map.cursor_texture_id,
(vec2i){cursor_pos.x, cursor_pos.y}, (vec2i){cursor_region.width * scale.x, cursor_region.height * scale.y},
(vec2i){0, 0}, (vec2i){cursor_region.width, cursor_region.height}, (vec2i){cursor_region.width, cursor_region.height},
(vec2i){cursor_pos.x, cursor_pos.y}, (vec2i){self->cursor_region.width * scale.x, self->cursor_region.height * scale.y},
(vec2i){0, 0}, (vec2i){self->cursor_region.width, self->cursor_region.height}, (vec2i){self->cursor_region.width, self->cursor_region.height},
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, true);
self->params.egl->glDisable(GL_SCISSOR_TEST);
}

View File

@@ -35,7 +35,7 @@ static bool gsr_egl_create_window(gsr_egl *self) {
const int32_t attr[] = {
EGL_BUFFER_SIZE, 24,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
EGL_NONE, EGL_NONE
};
@@ -459,9 +459,9 @@ bool gsr_egl_load(gsr_egl *self, gsr_window *window, bool is_monitor_capture, bo
/* This fixes nvenc codecs unable to load on openSUSE tumbleweed because of a cuda error. Don't ask me why */
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
if(inside_flatpak)
system("flatpak-spawn --host -- nvidia-smi -f /dev/null");
system("flatpak-spawn --host -- sh -c 'grep -q openSUSE /etc/os-release && nvidia-smi -f /dev/null'");
else
system("nvidia-smi -f /dev/null");
system("sh -c 'grep -q openSUSE /etc/os-release && nvidia-smi -f /dev/null'");
}
return true;

View File

@@ -6,7 +6,7 @@
#include <spa/param/video/format-utils.h>
#include <spa/debug/types.h>
#include <libdrm/drm_fourcc.h>
#include <drm_fourcc.h>
#include <fcntl.h>
#include <unistd.h>
@@ -346,17 +346,17 @@ static int64_t spa_video_format_to_drm_format(const enum spa_video_format format
switch(format) {
case SPA_VIDEO_FORMAT_RGBx: return DRM_FORMAT_XBGR8888;
case SPA_VIDEO_FORMAT_BGRx: return DRM_FORMAT_XRGB8888;
case SPA_VIDEO_FORMAT_RGBA: return DRM_FORMAT_ABGR8888;
case SPA_VIDEO_FORMAT_BGRA: return DRM_FORMAT_ARGB8888;
// case SPA_VIDEO_FORMAT_RGBA: return DRM_FORMAT_ABGR8888;
//case SPA_VIDEO_FORMAT_BGRA: return DRM_FORMAT_ARGB8888;
case SPA_VIDEO_FORMAT_RGB: return DRM_FORMAT_XBGR8888;
case SPA_VIDEO_FORMAT_BGR: return DRM_FORMAT_XRGB8888;
case SPA_VIDEO_FORMAT_ARGB: return DRM_FORMAT_XRGB8888;
case SPA_VIDEO_FORMAT_ABGR: return DRM_FORMAT_XRGB8888;
//case SPA_VIDEO_FORMAT_ARGB: return DRM_FORMAT_XRGB8888;
//case SPA_VIDEO_FORMAT_ABGR: return DRM_FORMAT_XRGB8888;
#if PW_CHECK_VERSION(0, 3, 41)
case SPA_VIDEO_FORMAT_xRGB_210LE: return DRM_FORMAT_XRGB2101010;
case SPA_VIDEO_FORMAT_xBGR_210LE: return DRM_FORMAT_XBGR2101010;
case SPA_VIDEO_FORMAT_ARGB_210LE: return DRM_FORMAT_ARGB2101010;
case SPA_VIDEO_FORMAT_ABGR_210LE: return DRM_FORMAT_ABGR2101010;
// case SPA_VIDEO_FORMAT_ARGB_210LE: return DRM_FORMAT_ARGB2101010;
// case SPA_VIDEO_FORMAT_ABGR_210LE: return DRM_FORMAT_ABGR2101010;
#endif
default: break;
}
@@ -366,23 +366,23 @@ static int64_t spa_video_format_to_drm_format(const enum spa_video_format format
#if PW_CHECK_VERSION(0, 3, 41)
#define GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS GSR_PIPEWIRE_VIDEO_MAX_VIDEO_FORMATS
#else
#define GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS 8
#define GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS 4
#endif
static const enum spa_video_format video_formats[GSR_PIPEWIRE_VIDEO_MAX_VIDEO_FORMATS] = {
SPA_VIDEO_FORMAT_BGRA,
// SPA_VIDEO_FORMAT_BGRA,
SPA_VIDEO_FORMAT_BGRx,
SPA_VIDEO_FORMAT_BGR,
SPA_VIDEO_FORMAT_RGBx,
SPA_VIDEO_FORMAT_RGBA,
// SPA_VIDEO_FORMAT_RGBA,
SPA_VIDEO_FORMAT_RGB,
SPA_VIDEO_FORMAT_ARGB,
SPA_VIDEO_FORMAT_ABGR,
// SPA_VIDEO_FORMAT_ARGB,
// SPA_VIDEO_FORMAT_ABGR,
#if PW_CHECK_VERSION(0, 3, 41)
SPA_VIDEO_FORMAT_xRGB_210LE,
SPA_VIDEO_FORMAT_xBGR_210LE,
SPA_VIDEO_FORMAT_ARGB_210LE,
SPA_VIDEO_FORMAT_ABGR_210LE
// SPA_VIDEO_FORMAT_ARGB_210LE,
// SPA_VIDEO_FORMAT_ABGR_210LE
#endif
};