diff options
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | TODO | 11 | ||||
-rwxr-xr-x | extra/meson_post_install.sh | 2 | ||||
-rw-r--r-- | include/capture/capture.h | 6 | ||||
-rw-r--r-- | include/damage.h | 3 | ||||
-rw-r--r-- | include/egl.h | 5 | ||||
-rw-r--r-- | include/pipewire.h | 4 | ||||
-rw-r--r-- | include/utils.h | 6 | ||||
-rw-r--r-- | include/window_texture.h | 1 | ||||
-rw-r--r-- | meson.build | 2 | ||||
-rw-r--r-- | meson_options.txt | 2 | ||||
-rw-r--r-- | project.conf | 4 | ||||
-rw-r--r-- | src/capture/capture.c | 10 | ||||
-rw-r--r-- | src/capture/kms.c | 185 | ||||
-rw-r--r-- | src/capture/nvfbc.c | 1 | ||||
-rw-r--r-- | src/capture/portal.c | 77 | ||||
-rw-r--r-- | src/capture/xcomposite.c | 28 | ||||
-rw-r--r-- | src/damage.c | 27 | ||||
-rw-r--r-- | src/egl.c | 12 | ||||
-rw-r--r-- | src/encoder/video/vaapi.c | 5 | ||||
-rw-r--r-- | src/main.cpp | 135 | ||||
-rw-r--r-- | src/pipewire.c | 49 | ||||
-rw-r--r-- | src/utils.c | 262 | ||||
-rw-r--r-- | src/window_texture.c | 12 |
24 files changed, 673 insertions, 180 deletions
@@ -162,6 +162,10 @@ This is mostly an issue on AMD. For av1 it's a hardware issue, see: https://gitl This can happen if your video player is missing the H264/HEVC video codecs. Either install the codecs or use mpv. ## I get stutter in the video Try recording to an SSD and make sure it's not using NTFS file system. Also record in variable framerate format. +## The colors look washed out when recording a monitor with HDR enabled +You have to either record in hdr mode (-k `hevc_hdr` or -k `av1_hdr` option) to record a HDR video or record with desktop portal option (`-w portal`) to turn the HDR recording into SDR. +## GPU Screen Recorder records night light when recording in HDR mode +You can record with desktop portal option (`-w portal`) instead which ignores night light, if you are ok with recording without HDR. # Donations If you want to donate you can donate via bitcoin or monero. @@ -152,8 +152,15 @@ Allow prime-run on x11 if monitor capture and the prime gpu is not nvidia. Enable 2-pass encoding. -Add vbr/cbr option. +Add cbr option. Restart replay/update video resolution if monitor resolution changes. -Support damage tracking on wayland. +Fix pure vaapi copy on intel. + +ffmpeg supports vulkan encoding now (h264!). Doesn't work on amd yet because mesa is missing VK_KHR_video_maintenance1, see https://gitlab.freedesktop.org/mesa/mesa/-/issues/11857. Test on nvidia! + +Test vaapi low latency mode (setenv("AMD_DEBUG", "lowlatencyenc", true);), added in mesa 24.1.4, released on july 17, 2024. Note that this forces gpu power usage to max at all times, even when recording at 2 fps. +Use nvidia low latency options for better encoding times. + +Test ideal async_depth value. Increasing async_depth also increased gpu memory usage a lot (from 100mb to 500mb when moving from async_depth 2 to 16) at 4k resolution. Setting it to 8 increases it by 200mb which might be ok. diff --git a/extra/meson_post_install.sh b/extra/meson_post_install.sh index a93f27b..7bf0d96 100755 --- a/extra/meson_post_install.sh +++ b/extra/meson_post_install.sh @@ -1,6 +1,6 @@ #!/bin/sh -# Needed to remove password prompt when recording a monitor on amd/intel or nvidia wayland +# Needed to remove password prompt when recording a monitor (without desktop portal option) on amd/intel or nvidia wayland /usr/sbin/setcap cap_sys_admin+ep ${MESON_INSTALL_DESTDIR_PREFIX}/bin/gsr-kms-server \ || echo "\n!!! Please re-run install as root\n" diff --git a/include/capture/capture.h b/include/capture/capture.h index e31b3a5..7c8887d 100644 --- a/include/capture/capture.h +++ b/include/capture/capture.h @@ -17,10 +17,9 @@ struct gsr_capture { /* These methods should not be called manually. Call gsr_capture_* instead */ int (*start)(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame); void (*on_event)(gsr_capture *cap, gsr_egl *egl); /* can be NULL */ - void (*tick)(gsr_capture *cap, AVCodecContext *video_codec_context); /* can be NULL. If there is an event then |on_event| is called before this */ + void (*tick)(gsr_capture *cap); /* can be NULL. If there is an event then |on_event| is called before this */ bool (*should_stop)(gsr_capture *cap, bool *err); /* can be NULL. If NULL, return false */ int (*capture)(gsr_capture *cap, AVFrame *frame, gsr_color_conversion *color_conversion); - void (*capture_end)(gsr_capture *cap, AVFrame *frame); /* can be NULL */ gsr_source_color (*get_source_color)(gsr_capture *cap); bool (*uses_external_image)(gsr_capture *cap); /* can be NULL. If NULL, return false */ bool (*set_hdr_metadata)(gsr_capture *cap, AVMasteringDisplayMetadata *mastering_display_metadata, AVContentLightMetadata *light_metadata); /* can be NULL. If NULL, return false */ @@ -35,10 +34,9 @@ struct gsr_capture { int gsr_capture_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame); void gsr_capture_on_event(gsr_capture *cap, gsr_egl *egl); -void gsr_capture_tick(gsr_capture *cap, AVCodecContext *video_codec_context); +void gsr_capture_tick(gsr_capture *cap); bool gsr_capture_should_stop(gsr_capture *cap, bool *err); int gsr_capture_capture(gsr_capture *cap, AVFrame *frame, gsr_color_conversion *color_conversion); -void gsr_capture_capture_end(gsr_capture *cap, AVFrame *frame); gsr_source_color gsr_capture_get_source_color(gsr_capture *cap); bool gsr_capture_uses_external_image(gsr_capture *cap); bool gsr_capture_set_hdr_metadata(gsr_capture *cap, AVMasteringDisplayMetadata *mastering_display_metadata, AVContentLightMetadata *light_metadata); diff --git a/include/damage.h b/include/damage.h index 2bf43d7..7229418 100644 --- a/include/damage.h +++ b/include/damage.h @@ -1,6 +1,7 @@ #ifndef GSR_DAMAGE_H #define GSR_DAMAGE_H +#include "cursor.h" #include "utils.h" #include <stdbool.h> #include <stdint.h> @@ -31,7 +32,7 @@ typedef struct { //vec2i window_pos; vec2i window_size; - vec2i cursor_position; /* Relative to |window| */ + gsr_cursor cursor; /* Relative to |window| */ gsr_monitor monitor; char monitor_name[32]; } gsr_damage; diff --git a/include/egl.h b/include/egl.h index 61890b4..3fdbf48 100644 --- a/include/egl.h +++ b/include/egl.h @@ -31,6 +31,7 @@ typedef void* EGLSurface; typedef void* EGLContext; typedef void* EGLClientBuffer; typedef void* EGLImage; +typedef void* EGLImageKHR; typedef void *GLeglImageOES; typedef void (*__eglMustCastToProperFunctionPointerType)(void); typedef struct __GLXFBConfigRec *GLXFBConfig; @@ -138,6 +139,8 @@ typedef void(*__GLXextFuncPtr)(void); #define GL_COMPILE_STATUS 0x8B81 #define GL_LINK_STATUS 0x8B82 +typedef unsigned int (*FUNC_eglExportDMABUFImageQueryMESA)(EGLDisplay dpy, EGLImageKHR image, int *fourcc, int *num_planes, uint64_t *modifiers); +typedef unsigned int (*FUNC_eglExportDMABUFImageMESA)(EGLDisplay dpy, EGLImageKHR image, int *fds, int32_t *strides, int32_t *offsets); typedef void (*FUNC_glEGLImageTargetTexture2DOES)(unsigned int target, GLeglImageOES image); typedef GLXContext (*FUNC_glXCreateContextAttribsARB)(Display *dpy, GLXFBConfig config, GLXContext share_context, Bool direct, const int *attrib_list); typedef void (*FUNC_glXSwapIntervalEXT)(Display * dpy, GLXDrawable drawable, int interval); @@ -235,6 +238,8 @@ struct gsr_egl { unsigned int (*eglBindAPI)(unsigned int api); __eglMustCastToProperFunctionPointerType (*eglGetProcAddress)(const char *procname); + FUNC_eglExportDMABUFImageQueryMESA eglExportDMABUFImageQueryMESA; + FUNC_eglExportDMABUFImageMESA eglExportDMABUFImageMESA; FUNC_glEGLImageTargetTexture2DOES glEGLImageTargetTexture2DOES; FUNC_eglQueryDisplayAttribEXT eglQueryDisplayAttribEXT; FUNC_eglQueryDeviceStringEXT eglQueryDeviceStringEXT; diff --git a/include/pipewire.h b/include/pipewire.h index 13c4b67..1908e2d 100644 --- a/include/pipewire.h +++ b/include/pipewire.h @@ -104,8 +104,8 @@ typedef struct { bool gsr_pipewire_init(gsr_pipewire *self, int pipewire_fd, uint32_t pipewire_node, int fps, bool capture_cursor, gsr_egl *egl); void gsr_pipewire_deinit(gsr_pipewire *self); -/* |plane_fds| should be at least GSR_PIPEWIRE_DMABUF_MAX_PLANES in size */ -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); +/* |dmabuf_data| should be at least GSR_PIPEWIRE_DMABUF_MAX_PLANES in size */ +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); bool gsr_pipewire_is_damaged(gsr_pipewire *self); void gsr_pipewire_clear_damage(gsr_pipewire *self); diff --git a/include/utils.h b/include/utils.h index 99503e2..92eb851 100644 --- a/include/utils.h +++ b/include/utils.h @@ -7,6 +7,9 @@ #include <stdbool.h> #include <stdint.h> +typedef struct AVCodecContext AVCodecContext; +typedef struct AVFrame AVFrame; + typedef struct { const char *name; int name_len; @@ -44,5 +47,8 @@ int create_directory_recursive(char *path); /* |img_attr| needs to be at least 44 in size */ void setup_dma_buf_attrs(intptr_t *img_attr, uint32_t format, uint32_t width, uint32_t height, const int *fds, const uint32_t *offsets, const uint32_t *pitches, const uint64_t *modifiers, int num_planes, bool use_modifier); +bool video_codec_context_is_vaapi(AVCodecContext *video_codec_context); +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); +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); #endif /* GSR_UTILS_H */ diff --git a/include/window_texture.h b/include/window_texture.h index 75bb2a7..6ee5df4 100644 --- a/include/window_texture.h +++ b/include/window_texture.h @@ -7,6 +7,7 @@ typedef struct { Display *display; Window window; Pixmap pixmap; + EGLImage image; unsigned int texture_id; int redirected; gsr_egl *egl; diff --git a/meson.build b/meson.build index 76fd915..b275812 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('gpu-screen-recorder', ['c', 'cpp'], version : '4.1.7', default_options : ['warning_level=2']) +project('gpu-screen-recorder', ['c', 'cpp'], version : '4.1.8', default_options : ['warning_level=2']) add_project_arguments('-Wshadow', language : ['c', 'cpp']) if get_option('buildtype') == 'debug' diff --git a/meson_options.txt b/meson_options.txt index bfd85ba..61972c6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,4 +1,4 @@ option('systemd', type : 'boolean', value : true, description : 'Install systemd service file') -option('capabilities', type : 'boolean', value : true, description : 'Set binary admin capability to remove password prompt and nice capability to allow gpu screen recorder to run at a higher framerate than the game you are recording') +option('capabilities', type : 'boolean', value : true, description : 'Set binary admin capability to remove password prompt when recording monitor (without desktop portal option) on amd/intel or nvidia wayland') option('nvidia_suspend_fix', type : 'boolean', value : true, description : 'Install nvidia modprobe config file to tell nvidia driver to preserve video memory on suspend. This is a workaround for an nvidia driver bug that breaks cuda (and gpu screen recorder) on suspend') option('portal', type : 'boolean', value : true, description : 'If GPU Screen Recorder should be built with support for xdg desktop portal ScreenCast capture (wayland only)') diff --git a/project.conf b/project.conf index 52c8af9..63b03a0 100644 --- a/project.conf +++ b/project.conf @@ -1,7 +1,7 @@ [package] name = "gpu-screen-recorder" type = "executable" -version = "4.1.7" +version = "4.1.8" platforms = ["posix"] [config] @@ -31,4 +31,4 @@ wayland-egl = ">=15" wayland-client = ">=1" dbus-1 = ">=1" libpipewire-0.3 = ">=1" -libspa-0.2 = ">=0"
\ No newline at end of file +libspa-0.2 = ">=0" 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, ®ion, &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, ®ion, &cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &fourcc, &modifiers, &uses_external_image)) { gsr_capture_portal_cleanup_plane_fds(self); self->capture_size.x = region.width; self->capture_size.y = region.height; @@ -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, ®ion, &cursor_region, self->plane_fds, &self->num_plane_fds, &using_external_image)) { + if(gsr_pipewire_map_texture(&self->pipewire, self->texture_map, ®ion, &cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &pipewire_fourcc, &pipewire_modifiers, &using_external_image)) { if(region.width != self->capture_size.x || region.height != self->capture_size.y) { - 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) { @@ -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, ¶ms, &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) |