aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba®protonmail.com>2023-04-14 09:36:24 +0200
committerdec05eba <dec05eba@protonmail.com>2023-04-15 19:06:08 +0200
commitf6107a0c5d41aa9fbaa41d64e2f6a5681f9237cc (patch)
tree8fae2bf69dd325d1da0ab1e475a58f32435768cf /src
parent5c714ea7142272b7b95b95019501df1d49691db1 (diff)
Fix AMD single monitor rotated display being rotated in recording
If there is only one monitor connected and it's rotated then the drm buf will also be rotated. This only the case with AMD and only when using one monitor! To fix this, we perform color conversion with an opengl shader which allows us to also rotate the texture. VAAPI supports rotation but it's not implemented by AMD at least. Performance seems to be the same as when using VAAPI, even when GPU usage is 100%.
Diffstat (limited to 'src')
-rw-r--r--src/capture/kms_vaapi.c389
-rw-r--r--src/capture/xcomposite_vaapi.c13
-rw-r--r--src/color_conversion.c235
-rw-r--r--src/egl.c31
-rw-r--r--src/main.cpp108
-rw-r--r--src/shader.c137
-rw-r--r--src/utils.c43
7 files changed, 705 insertions, 251 deletions
diff --git a/src/capture/kms_vaapi.c b/src/capture/kms_vaapi.c
index 2f7232f..8d650ed 100644
--- a/src/capture/kms_vaapi.c
+++ b/src/capture/kms_vaapi.c
@@ -2,6 +2,7 @@
#include "../../kms/client/kms_client.h"
#include "../../include/egl.h"
#include "../../include/utils.h"
+#include "../../include/color_conversion.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
@@ -14,6 +15,13 @@
#include <va/va.h>
#include <va/va_drmcommon.h>
+typedef enum {
+ X11_ROT_0 = 1 << 0,
+ X11_ROT_90 = 1 << 1,
+ X11_ROT_180 = 1 << 2,
+ X11_ROT_270 = 1 << 3
+} X11Rotation;
+
typedef struct {
gsr_capture_kms_vaapi_params params;
bool should_stop;
@@ -36,12 +44,17 @@ typedef struct {
bool screen_capture;
VADisplay va_dpy;
- VAConfigID config_id;
- VAContextID context_id;
- VASurfaceID input_surface;
- VABufferID buffer_id;
- VARectangle input_region;
- bool context_created;
+
+ bool requires_rotation;
+ X11Rotation x11_rot;
+
+ VADRMPRIMESurfaceDescriptor prime;
+
+ unsigned int input_texture;
+ unsigned int target_textures[2];
+
+ gsr_color_conversion color_conversion;
+ unsigned int vao;
} gsr_capture_kms_vaapi;
static int max_int(int a, int b) {
@@ -95,6 +108,17 @@ static bool drm_create_codec_context(gsr_capture_kms_vaapi *cap_kms, AVCodecCont
// TODO: On monitor reconfiguration, find monitor x, y, width and height again. Do the same for nvfbc.
+typedef struct {
+ int num_monitors;
+ int rotation;
+} MonitorCallbackUserdata;
+
+static void monitor_callback(const XRROutputInfo *output_info, const XRRCrtcInfo *crt_info, const XRRModeInfo *mode_info, void *userdata) {
+ MonitorCallbackUserdata *monitor_callback_userdata = userdata;
+ monitor_callback_userdata->rotation = crt_info->rotation;
+ ++monitor_callback_userdata->num_monitors;
+}
+
static int gsr_capture_kms_vaapi_start(gsr_capture *cap, AVCodecContext *video_codec_context) {
gsr_capture_kms_vaapi *cap_kms = cap->priv;
@@ -103,7 +127,9 @@ static int gsr_capture_kms_vaapi_start(gsr_capture *cap, AVCodecContext *video_c
return -1;
}
- // TODO: Update on monitor reconfiguration, make sure to update on window focus change (maybe for kms update each second?), needs to work on focus change to the witcher 3 on full window, fullscreen firefox, etc
+ MonitorCallbackUserdata monitor_callback_userdata = {0};
+ for_each_active_monitor_output(cap_kms->params.dpy, monitor_callback, &monitor_callback_userdata);
+
gsr_monitor monitor;
if(strcmp(cap_kms->params.display_to_capture, "screen") == 0) {
monitor.pos.x = 0;
@@ -117,12 +143,24 @@ static int gsr_capture_kms_vaapi_start(gsr_capture *cap, AVCodecContext *video_c
return -1;
}
+ // TODO: Find a better way to do this. Is this info available somewhere in drm? it should be!
+
+ // TODO: test on intel
+ // Note: workaround AMD issue. If there is one monitor enabled and it's rotated then
+ // the drm buf will also be rotated. This only happens when you only have one monitor enabled.
+ cap_kms->x11_rot = monitor_callback_userdata.rotation;
+ if(monitor_callback_userdata.num_monitors == 1 && cap_kms->x11_rot != X11_ROT_0 && cap_kms->params.gpu_inf.vendor == GSR_GPU_VENDOR_AMD) {
+ cap_kms->requires_rotation = true;
+ } else {
+ cap_kms->requires_rotation = false;
+ }
+
cap_kms->capture_pos = monitor.pos;
cap_kms->capture_size = monitor.size;
-
if(!gsr_egl_load(&cap_kms->egl, cap_kms->params.dpy)) {
fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_start: failed to load opengl\n");
+ gsr_capture_kms_vaapi_stop(cap, video_codec_context);
return -1;
}
@@ -137,17 +175,21 @@ static int gsr_capture_kms_vaapi_start(gsr_capture *cap, AVCodecContext *video_c
return -1;
}
- //cap_kms->window_resize_timer = clock_get_monotonic_seconds(); // TODO:
return 0;
}
+static uint32_t fourcc(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
+ return (d << 24) | (c << 16) | (b << 8) | a;
+}
+
+#define FOURCC_NV12 842094158
+
static void gsr_capture_kms_vaapi_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) {
gsr_capture_kms_vaapi *cap_kms = cap->priv;
// TODO:
- //cap_kms->egl.glClear(GL_COLOR_BUFFER_BIT);
+ cap_kms->egl.glClear(GL_COLOR_BUFFER_BIT);
- //const double window_resize_timeout = 1.0; // 1 second
if(!cap_kms->created_hw_frame) {
cap_kms->created_hw_frame = true;
@@ -175,6 +217,128 @@ static void gsr_capture_kms_vaapi_tick(gsr_capture *cap, AVCodecContext *video_c
cap_kms->stop_is_error = true;
return;
}
+
+ VASurfaceID target_surface_id = (uintptr_t)(*frame)->data[3];
+
+ VAStatus va_status = vaExportSurfaceHandle(cap_kms->va_dpy, target_surface_id, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, VA_EXPORT_SURFACE_READ_WRITE | VA_EXPORT_SURFACE_SEPARATE_LAYERS, &cap_kms->prime);
+ if(va_status != VA_STATUS_SUCCESS) {
+ fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: vaExportSurfaceHandle failed, error: %d\n", va_status);
+ cap_kms->should_stop = true;
+ cap_kms->stop_is_error = true;
+ return;
+ }
+ vaSyncSurface(cap_kms->va_dpy, target_surface_id);
+
+ cap_kms->egl.glGenTextures(1, &cap_kms->input_texture);
+ cap_kms->egl.glBindTexture(GL_TEXTURE_2D, cap_kms->input_texture);
+ cap_kms->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ cap_kms->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ cap_kms->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ cap_kms->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ cap_kms->egl.glBindTexture(GL_TEXTURE_2D, 0);
+
+ if(cap_kms->prime.fourcc == FOURCC_NV12) {
+ cap_kms->egl.glGenTextures(2, cap_kms->target_textures);
+ for(int i = 0; i < 2; ++i) {
+ const uint32_t formats[2] = { fourcc('R', '8', ' ', ' '), fourcc('G', 'R', '8', '8') };
+ const int layer = i;
+ const int plane = 0;
+
+ const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size
+
+ const intptr_t img_attr[] = {
+ EGL_LINUX_DRM_FOURCC_EXT, formats[i],
+ EGL_WIDTH, cap_kms->prime.width / div[i],
+ EGL_HEIGHT, cap_kms->prime.height / div[i],
+ EGL_DMA_BUF_PLANE0_FD_EXT, cap_kms->prime.objects[cap_kms->prime.layers[layer].object_index[plane]].fd,
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT, cap_kms->prime.layers[layer].offset[plane],
+ EGL_DMA_BUF_PLANE0_PITCH_EXT, cap_kms->prime.layers[layer].pitch[plane],
+ EGL_NONE
+ };
+
+ while(cap_kms->egl.eglGetError() != EGL_SUCCESS){}
+ EGLImage image = cap_kms->egl.eglCreateImage(cap_kms->egl.egl_display, 0, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr);
+ if(!image) {
+ fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: failed to create egl image from drm fd for output drm fd, error: %d\n", cap_kms->egl.eglGetError());
+ cap_kms->should_stop = true;
+ cap_kms->stop_is_error = true;
+ return;
+ }
+
+ cap_kms->egl.glBindTexture(GL_TEXTURE_2D, cap_kms->target_textures[i]);
+ cap_kms->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ cap_kms->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ cap_kms->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ cap_kms->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ while(cap_kms->egl.glGetError()) {}
+ while(cap_kms->egl.eglGetError() != EGL_SUCCESS){}
+ cap_kms->egl.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
+ if(cap_kms->egl.glGetError() != 0 || cap_kms->egl.eglGetError() != EGL_SUCCESS) {
+ fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: failed to bind egl image to gl texture, error: %d\n", cap_kms->egl.eglGetError());
+ cap_kms->should_stop = true;
+ cap_kms->stop_is_error = true;
+ cap_kms->egl.eglDestroyImage(cap_kms->egl.egl_display, image);
+ cap_kms->egl.glBindTexture(GL_TEXTURE_2D, 0);
+ return;
+ }
+
+ cap_kms->egl.eglDestroyImage(cap_kms->egl.egl_display, image);
+ cap_kms->egl.glBindTexture(GL_TEXTURE_2D, 0);
+ }
+
+ float texture_rotation = 0.0f;
+ if(cap_kms->requires_rotation) {
+ switch(cap_kms->x11_rot) {
+ case X11_ROT_90:
+ texture_rotation = M_PI*0.5f;
+ break;
+ case X11_ROT_180:
+ texture_rotation = M_PI;
+ break;
+ case X11_ROT_270:
+ texture_rotation = M_PI*1.5f;
+ break;
+ default:
+ texture_rotation = 0.0f;
+ break;
+ }
+ }
+
+ const double combined_monitor_width = (double)XWidthOfScreen(DefaultScreenOfDisplay(cap_kms->params.dpy));
+ const double combined_monitor_height = (double)XHeightOfScreen(DefaultScreenOfDisplay(cap_kms->params.dpy));
+
+ gsr_color_conversion_params color_conversion_params = {0};
+ color_conversion_params.egl = &cap_kms->egl;
+ color_conversion_params.source_color = GSR_SOURCE_COLOR_RGB;
+ color_conversion_params.destination_color = GSR_DESTINATION_COLOR_NV12;
+
+ color_conversion_params.source_textures[0] = cap_kms->input_texture;
+ color_conversion_params.num_source_textures = 1;
+
+ color_conversion_params.destination_textures[0] = cap_kms->target_textures[0];
+ color_conversion_params.destination_textures[1] = cap_kms->target_textures[1];
+ color_conversion_params.num_destination_textures = 2;
+
+ color_conversion_params.rotation = texture_rotation;
+
+ color_conversion_params.position.x = (double)cap_kms->capture_pos.x / combined_monitor_width;
+ color_conversion_params.position.y = (double)cap_kms->capture_pos.y / combined_monitor_height;
+ color_conversion_params.size.x = (double)cap_kms->capture_size.x / combined_monitor_width;
+ color_conversion_params.size.y = (double)cap_kms->capture_size.y / combined_monitor_height;
+
+ if(gsr_color_conversion_init(&cap_kms->color_conversion, &color_conversion_params) != 0) {
+ fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: failed to create color conversion\n");
+ cap_kms->should_stop = true;
+ cap_kms->stop_is_error = true;
+ return;
+ }
+ } else {
+ fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: unexpected fourcc %u for output drm fd, expected nv12\n", cap_kms->prime.fourcc);
+ cap_kms->should_stop = true;
+ cap_kms->stop_is_error = true;
+ return;
+ }
}
}
@@ -194,8 +358,6 @@ static bool gsr_capture_kms_vaapi_should_stop(gsr_capture *cap, bool *err) {
static int gsr_capture_kms_vaapi_capture(gsr_capture *cap, AVFrame *frame) {
gsr_capture_kms_vaapi *cap_kms = cap->priv;
- VASurfaceID target_surface_id = (uintptr_t)frame->data[3];
-
if(cap_kms->dmabuf_fd > 0) {
close(cap_kms->dmabuf_fd);
cap_kms->dmabuf_fd = 0;
@@ -215,150 +377,43 @@ static int gsr_capture_kms_vaapi_capture(gsr_capture *cap, AVFrame *frame) {
cap_kms->kms_size.x = kms_response.data.fd.width;
cap_kms->kms_size.y = kms_response.data.fd.height;
- if(!cap_kms->context_created) {
- cap_kms->context_created = true;
-
- VAStatus va_status = vaCreateConfig(cap_kms->va_dpy, VAProfileNone, VAEntrypointVideoProc, NULL, 0, &cap_kms->config_id);
- if(va_status != VA_STATUS_SUCCESS) {
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_capture: vaCreateConfig failed: %d\n", va_status);
- cap_kms->should_stop = true;
- cap_kms->stop_is_error = true;
- return -1;
- }
-
- va_status = vaCreateContext(cap_kms->va_dpy, cap_kms->config_id, cap_kms->kms_size.x, cap_kms->kms_size.y, VA_PROGRESSIVE, &target_surface_id, 1, &cap_kms->context_id);
- if(va_status != VA_STATUS_SUCCESS) {
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_capture: vaCreateContext failed: %d\n", va_status);
- cap_kms->should_stop = true;
- cap_kms->stop_is_error = true;
- return -1;
- }
- }
-
- if(cap_kms->buffer_id) {
- vaDestroyBuffer(cap_kms->va_dpy, cap_kms->buffer_id);
- cap_kms->buffer_id = 0;
- }
-
- if(cap_kms->input_surface) {
- vaDestroySurfaces(cap_kms->va_dpy, &cap_kms->input_surface, 1);
- cap_kms->input_surface = 0;
- }
-
- uintptr_t dmabuf = cap_kms->dmabuf_fd;
-
- VASurfaceAttribExternalBuffers buf = {0};
- buf.pixel_format = VA_FOURCC_BGRX;
- buf.width = cap_kms->kms_size.x;
- buf.height = cap_kms->kms_size.y;
- buf.data_size = cap_kms->kms_size.y * cap_kms->pitch;
- buf.num_planes = 1;
- buf.pitches[0] = cap_kms->pitch;
- buf.offsets[0] = cap_kms->offset;
- buf.buffers = &dmabuf;
- buf.num_buffers = 1;
- buf.flags = 0;
- buf.private_data = 0;
-
- #define VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME 0x20000000
-
- VASurfaceAttrib attribs[3] = {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;
- attribs[1].type = VASurfaceAttribExternalBufferDescriptor;
- attribs[1].flags = VA_SURFACE_ATTRIB_SETTABLE;
- attribs[1].value.type = VAGenericValueTypePointer;
- attribs[1].value.value.p = &buf;
-
- // TODO: Do we really need to create a new surface every frame?
- VAStatus va_status = vaCreateSurfaces(cap_kms->va_dpy, VA_RT_FORMAT_RGB32, cap_kms->kms_size.x, cap_kms->kms_size.y, &cap_kms->input_surface, 1, attribs, 2);
- if(va_status != VA_STATUS_SUCCESS) {
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_capture: vaCreateSurfaces failed: %d\n", va_status);
- cap_kms->should_stop = true;
- cap_kms->stop_is_error = true;
- return -1;
- }
-
- cap_kms->input_region = (VARectangle) {
- .x = cap_kms->capture_pos.x,
- .y = cap_kms->capture_pos.y,
- .width = cap_kms->capture_size.x,
- .height = cap_kms->capture_size.y
+ // TODO: This causes a crash sometimes on steam deck, why? is it a driver bug? a vaapi pure version doesn't cause a crash.
+ // Even ffmpeg kmsgrab causes this crash. The error is:
+ // amdgpu: Failed to allocate a buffer:
+ // amdgpu: size : 28508160 bytes
+ // amdgpu: alignment : 2097152 bytes
+ // amdgpu: domains : 4
+ // amdgpu: flags : 4
+ // amdgpu: Failed to allocate a buffer:
+ // amdgpu: size : 28508160 bytes
+ // amdgpu: alignment : 2097152 bytes
+ // amdgpu: domains : 4
+ // amdgpu: flags : 4
+ // EE ../jupiter-mesa/src/gallium/drivers/radeonsi/radeon_vcn_enc.c:516 radeon_create_encoder UVD - Can't create CPB buffer.
+ // [hevc_vaapi @ 0x55ea72b09840] Failed to upload encode parameters: 2 (resource allocation failed).
+ // [hevc_vaapi @ 0x55ea72b09840] Encode failed: -5.
+ // Error: avcodec_send_frame failed, error: Input/output error
+ // Assertion pic->display_order == pic->encode_order failed at libavcodec/vaapi_encode_h265.c:765
+ // kms server info: kms client shutdown, shutting down the server
+ const intptr_t img_attr[] = {
+ EGL_LINUX_DRM_FOURCC_EXT, cap_kms->fourcc,
+ EGL_WIDTH, cap_kms->kms_size.x,
+ EGL_HEIGHT, cap_kms->kms_size.y,
+ EGL_DMA_BUF_PLANE0_FD_EXT, cap_kms->dmabuf_fd,
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT, cap_kms->offset,
+ EGL_DMA_BUF_PLANE0_PITCH_EXT, cap_kms->pitch,
+ EGL_NONE
};
- // Copying a surface to another surface will automatically perform the color conversion. Thanks vaapi!
- VAProcPipelineParameterBuffer params = {0};
- params.surface = cap_kms->input_surface;
- if(cap_kms->screen_capture)
- params.surface_region = NULL;
- else
- params.surface_region = &cap_kms->input_region;
- params.output_region = NULL;
- params.output_background_color = 0;
- params.filter_flags = VA_FRAME_PICTURE;
- // TODO: Colors
- params.input_color_properties.color_range = frame->color_range == AVCOL_RANGE_JPEG ? VA_SOURCE_RANGE_FULL : VA_SOURCE_RANGE_REDUCED;
- params.output_color_properties.color_range = frame->color_range == AVCOL_RANGE_JPEG ? VA_SOURCE_RANGE_FULL : VA_SOURCE_RANGE_REDUCED;
-
- // Clear texture with black background because the source texture (window_texture_get_opengl_texture_id(&cap_kms->window_texture))
- // might be smaller than cap_kms->target_texture_id
- // TODO:
- //cap_kms->egl.glClearTexImage(cap_kms->target_texture_id, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
-
- va_status = vaBeginPicture(cap_kms->va_dpy, cap_kms->context_id, target_surface_id);
- if(va_status != VA_STATUS_SUCCESS) {
- static bool error_printed = false;
- if(!error_printed) {
- error_printed = true;
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_capture: vaBeginPicture failed: %d\n", va_status);
- }
- return -1;
- }
+ EGLImage image = cap_kms->egl.eglCreateImage(cap_kms->egl.egl_display, 0, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr);
+ cap_kms->egl.glBindTexture(GL_TEXTURE_2D, cap_kms->input_texture);
+ cap_kms->egl.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
+ cap_kms->egl.eglDestroyImage(cap_kms->egl.egl_display, image);
+ cap_kms->egl.glBindTexture(GL_TEXTURE_2D, 0);
- va_status = vaCreateBuffer(cap_kms->va_dpy, cap_kms->context_id, VAProcPipelineParameterBufferType, sizeof(params), 1, &params, &cap_kms->buffer_id);
- if(va_status != VA_STATUS_SUCCESS) {
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_capture: vaCreateBuffer failed: %d\n", va_status);
- cap_kms->should_stop = true;
- cap_kms->stop_is_error = true;
- return -1;
- }
+ gsr_color_conversion_update(&cap_kms->color_conversion, frame->width, frame->height);
- va_status = vaRenderPicture(cap_kms->va_dpy, cap_kms->context_id, &cap_kms->buffer_id, 1);
- if(va_status != VA_STATUS_SUCCESS) {
- vaEndPicture(cap_kms->va_dpy, cap_kms->context_id);
- static bool error_printed = false;
- if(!error_printed) {
- error_printed = true;
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_capture: vaRenderPicture failed: %d\n", va_status);
- }
- return -1;
- }
-
- va_status = vaEndPicture(cap_kms->va_dpy, cap_kms->context_id);
- if(va_status != VA_STATUS_SUCCESS) {
- static bool error_printed = false;
- if(!error_printed) {
- error_printed = true;
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_capture: vaEndPicture failed: %d\n", va_status);
- }
- return -1;
- }
-
- // TODO: Needed?
- vaSyncSurface(cap_kms->va_dpy, cap_kms->input_surface);
- vaSyncSurface(cap_kms->va_dpy, target_surface_id);
-
- if(cap_kms->buffer_id) {
- vaDestroyBuffer(cap_kms->va_dpy, cap_kms->buffer_id);
- cap_kms->buffer_id = 0;
- }
-
- if(cap_kms->input_surface) {
- vaDestroySurfaces(cap_kms->va_dpy, &cap_kms->input_surface, 1);
- cap_kms->input_surface = 0;
- }
+ cap_kms->egl.eglSwapBuffers(cap_kms->egl.egl_display, cap_kms->egl.egl_surface);
if(cap_kms->dmabuf_fd > 0) {
close(cap_kms->dmabuf_fd);
@@ -366,7 +421,7 @@ static int gsr_capture_kms_vaapi_capture(gsr_capture *cap, AVFrame *frame) {
}
// TODO: Remove
- //cap_kms->egl.eglSwapBuffers(cap_kms->egl.egl_display, cap_kms->egl.egl_surface);
+ cap_kms->egl.eglSwapBuffers(cap_kms->egl.egl_display, cap_kms->egl.egl_surface);
return 0;
}
@@ -374,25 +429,23 @@ static int gsr_capture_kms_vaapi_capture(gsr_capture *cap, AVFrame *frame) {
static void gsr_capture_kms_vaapi_stop(gsr_capture *cap, AVCodecContext *video_codec_context) {
gsr_capture_kms_vaapi *cap_kms = cap->priv;
- if(cap_kms->buffer_id) {
- vaDestroyBuffer(cap_kms->va_dpy, cap_kms->buffer_id);
- cap_kms->buffer_id = 0;
- }
+ gsr_color_conversion_deinit(&cap_kms->color_conversion);
- if(cap_kms->context_id) {
- vaDestroyContext(cap_kms->va_dpy, cap_kms->context_id);
- cap_kms->context_id = 0;
+ for(uint32_t i = 0; i < cap_kms->prime.num_objects; ++i) {
+ if(cap_kms->prime.objects[i].fd > 0) {
+ close(cap_kms->prime.objects[i].fd);
+ cap_kms->prime.objects[i].fd = 0;
+ }
}
- if(cap_kms->config_id) {
- vaDestroyConfig(cap_kms->va_dpy, cap_kms->config_id);
- cap_kms->config_id = 0;
+ if(cap_kms->input_texture) {
+ cap_kms->egl.glDeleteTextures(1, &cap_kms->input_texture);
+ cap_kms->input_texture = 0;
}
- if(cap_kms->input_surface) {
- vaDestroySurfaces(cap_kms->va_dpy, &cap_kms->input_surface, 1);
- cap_kms->input_surface = 0;
- }
+ cap_kms->egl.glDeleteTextures(2, cap_kms->target_textures);
+ cap_kms->target_textures[0] = 0;
+ cap_kms->target_textures[1] = 0;
if(cap_kms->dmabuf_fd > 0) {
close(cap_kms->dmabuf_fd);
diff --git a/src/capture/xcomposite_vaapi.c b/src/capture/xcomposite_vaapi.c
index 532c0b0..3423af0 100644
--- a/src/capture/xcomposite_vaapi.c
+++ b/src/capture/xcomposite_vaapi.c
@@ -452,10 +452,21 @@ static void gsr_capture_xcomposite_vaapi_tick(gsr_capture *cap, AVCodecContext *
params.output_region = NULL;
params.output_background_color = 0;
params.filter_flags = VA_FRAME_PICTURE;
- // TODO: Colors
+
+ 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;
params.input_color_properties.color_range = (*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;
params.output_color_properties.color_range = (*frame)->color_range == AVCOL_RANGE_JPEG ? VA_SOURCE_RANGE_FULL : VA_SOURCE_RANGE_REDUCED;
+ params.processing_mode = VAProcPerformanceMode;
+
va_status = vaCreateBuffer(cap_xcomp->va_dpy, cap_xcomp->context_id, VAProcPipelineParameterBufferType, sizeof(params), 1, &params, &cap_xcomp->buffer_id);
if(va_status != VA_STATUS_SUCCESS) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: vaCreateBuffer failed: %d\n", va_status);
diff --git a/src/color_conversion.c b/src/color_conversion.c
new file mode 100644
index 0000000..fbbe9e6
--- /dev/null
+++ b/src/color_conversion.c
@@ -0,0 +1,235 @@
+#include "../include/color_conversion.h"
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#define MAX_SHADERS 2
+#define MAX_FRAMEBUFFERS 2
+
+#define ROTATE_Z "mat4 rotate_z(in float angle) {\n" \
+ " return mat4(cos(angle), -sin(angle), 0.0, 0.0,\n" \
+ " sin(angle), cos(angle), 0.0, 0.0,\n" \
+ " 0.0, 0.0, 1.0, 0.0,\n" \
+ " 0.0, 0.0, 0.0, 1.0);\n" \
+ "}\n"
+
+#define RGB_TO_YUV "const mat4 RGBtoYUV = mat4(0.257, 0.439, -0.148, 0.0,\n" \
+ " 0.504, -0.368, -0.291, 0.0,\n" \
+ " 0.098, -0.071, 0.439, 0.0,\n" \
+ " 0.0625, 0.500, 0.500, 1.0);"
+
+static int load_shader_y(gsr_shader *shader, gsr_egl *egl, float rotation) {
+ char vertex_shader[2048];
+ snprintf(vertex_shader, sizeof(vertex_shader),
+ "#version 300 es \n"
+ "in vec2 pos; \n"
+ "in vec2 texcoords; \n"
+ "out vec2 texcoords_out; \n"
+ ROTATE_Z
+ "void main() \n"
+ "{ \n"
+ " texcoords_out = texcoords; \n"
+ " gl_Position = vec4(pos.x, pos.y, 0.0, 1.0) * rotate_z(%f); \n"
+ "} \n", rotation);
+
+ char fragment_shader[] =
+ "#version 300 es \n"
+ "precision mediump float; \n"
+ "in vec2 texcoords_out; \n"
+ "uniform sampler2D tex1; \n"
+ "out vec4 FragColor; \n"
+ RGB_TO_YUV
+ "void main() \n"
+ "{ \n"
+ " FragColor.x = (RGBtoYUV * vec4(texture(tex1, texcoords_out).rgb, 1.0)).x; \n"
+ "} \n";
+
+ if(gsr_shader_init(shader, egl, vertex_shader, fragment_shader) != 0)
+ return -1;
+
+ gsr_shader_bind_attribute_location(shader, "pos", 0);
+ gsr_shader_bind_attribute_location(shader, "texcoords", 1);
+ return 0;
+}
+
+static unsigned int load_shader_uv(gsr_shader *shader, gsr_egl *egl, float rotation) {
+ char vertex_shader[2048];
+ snprintf(vertex_shader, sizeof(vertex_shader),
+ "#version 300 es \n"
+ "in vec2 pos; \n"
+ "in vec2 texcoords; \n"
+ "out vec2 texcoords_out; \n"
+ ROTATE_Z
+ "void main() \n"
+ "{ \n"
+ " texcoords_out = texcoords; \n"
+ " gl_Position = vec4(pos.x, pos.y, 0.0, 1.0) * rotate_z(%f) * vec4(0.5, 0.5, 1.0, 1.0) - vec4(0.5, 0.5, 0.0, 0.0); \n"
+ "} \n", rotation);
+
+ char fragment_shader[] =
+ "#version 300 es \n"
+ "precision mediump float; \n"
+ "in vec2 texcoords_out; \n"
+ "uniform sampler2D tex1; \n"
+ "out vec4 FragColor; \n"
+ RGB_TO_YUV
+ "void main() \n"
+ "{ \n"
+ " FragColor.xy = (RGBtoYUV * vec4(texture(tex1, texcoords_out).rgb, 1.0)).zy; \n"
+ "} \n";
+
+ if(gsr_shader_init(shader, egl, vertex_shader, fragment_shader) != 0)
+ return -1;
+
+ gsr_shader_bind_attribute_location(shader, "pos", 0);
+ gsr_shader_bind_attribute_location(shader, "texcoords", 1);
+ return 0;
+}
+
+static int loader_framebuffers(gsr_color_conversion *self) {
+ const unsigned int draw_buffer = GL_COLOR_ATTACHMENT0;
+ self->egl->glGenFramebuffers(MAX_FRAMEBUFFERS, self->framebuffers);
+
+ self->egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[0]);
+ self->egl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self->destination_textures[0], 0);
+ self->egl->glDrawBuffers(1, &draw_buffer);
+ if(self->egl->glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to create framebuffer for Y\n");
+ goto err;
+ }
+
+ self->egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[1]);
+ self->egl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self->destination_textures[1], 0);
+ self->egl->glDrawBuffers(1, &draw_buffer);
+ if(self->egl->glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to create framebuffer for UV\n");
+ goto err;
+ }
+
+ self->egl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ return 0;
+
+ err:
+ self->egl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ return -1;
+}
+
+static int create_vertices(gsr_color_conversion *self, vec2f position, vec2f size) {
+ const float vertices[] = {
+ -1.0f, 1.0f, position.x, position.y + size.y,
+ -1.0f, -1.0f, position.x, position.y,
+ 1.0f, -1.0f, position.x + size.x, position.y,
+
+ -1.0f, 1.0f, position.x, position.y + size.y,
+ 1.0f, -1.0f, position.x + size.x, position.y,
+ 1.0f, 1.0f, position.x + size.x, position.y + size.y
+ };
+
+ self->egl->glGenVertexArrays(1, &self->vertex_array_object_id);
+ self->egl->glGenBuffers(1, &self->vertex_buffer_object_id);
+ self->egl->glBindVertexArray(self->vertex_array_object_id);
+ self->egl->glBindBuffer(GL_ARRAY_BUFFER, self->vertex_buffer_object_id);
+ self->egl->glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW);
+
+ self->egl->glEnableVertexAttribArray(0);
+ self->egl->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
+
+ self->egl->glEnableVertexAttribArray(1);
+ self->egl->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
+
+ self->egl->glBindVertexArray(0);
+ return 0;
+}
+
+int gsr_color_conversion_init(gsr_color_conversion *self, const gsr_color_conversion_params *params) {
+ assert(params);
+ assert(params->egl);
+ memset(self, 0, sizeof(*self));
+ self->egl = params->egl;
+
+ if(params->num_source_textures != 1) {
+ fprintf(stderr, "gsr error: gsr_color_conversion_init: expected 1 source texture for source color RGB, got %d source texture(s)\n", params->num_source_textures);
+ return -1;
+ }
+
+ if(params->num_destination_textures != 2) {
+ fprintf(stderr, "gsr error: gsr_color_conversion_init: expected 2 destination textures for destination color NV12, got %d destination texture(s)\n", params->num_destination_textures);
+ return -1;
+ }
+
+ if(load_shader_y(&self->shaders[0], self->egl, params->rotation) != 0) {
+ fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to loader Y shader\n");
+ goto err;
+ }
+
+ if(load_shader_uv(&self->shaders[1], self->egl, params->rotation) != 0) {
+ fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to loader UV shader\n");
+ goto err;
+ }
+
+ self->source_textures[0] = params->source_textures[0];
+ self->destination_textures[0] = params->destination_textures[0];
+ self->destination_textures[1] = params->destination_textures[1];
+
+ if(loader_framebuffers(self) != 0)
+ goto err;
+
+ if(create_vertices(self, params->position, params->size) != 0)
+ goto err;
+
+ return 0;
+
+ err:
+ self->egl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ gsr_color_conversion_deinit(self);
+ return -1;
+}
+
+void gsr_color_conversion_deinit(gsr_color_conversion *self) {
+ if(self->vertex_buffer_object_id) {
+ self->egl->glDeleteBuffers(1, &self->vertex_buffer_object_id);
+ self->vertex_buffer_object_id = 0;
+ }
+
+ if(self->vertex_array_object_id) {
+ self->egl->glDeleteVertexArrays(1, &self->vertex_array_object_id);
+ self->vertex_array_object_id = 0;
+ }
+
+ self->egl->glDeleteFramebuffers(MAX_FRAMEBUFFERS, self->framebuffers);
+ for(int i = 0; i < MAX_FRAMEBUFFERS; ++i) {
+ self->framebuffers[i] = 0;
+ }
+
+ for(int i = 0; i < MAX_SHADERS; ++i) {
+ gsr_shader_deinit(&self->shaders[i]);
+ }
+}
+
+int gsr_color_conversion_update(gsr_color_conversion *self, int width, int height) {
+ self->egl->glBindVertexArray(self->vertex_array_object_id);
+ self->egl->glViewport(0, 0, width, height);
+ self->egl->glBindTexture(GL_TEXTURE_2D, self->source_textures[0]);
+
+ {
+ self->egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[0]);
+ //cap_xcomp->egl.glClear(GL_COLOR_BUFFER_BIT);
+
+ gsr_shader_use(&self->shaders[0]);
+ self->egl->glDrawArrays(GL_TRIANGLES, 0, 6);
+ }
+
+ {
+ self->egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[1]);
+ //cap_xcomp->egl.glClear(GL_COLOR_BUFFER_BIT);
+
+ gsr_shader_use(&self->shaders[1]);
+ self->egl->glDrawArrays(GL_TRIANGLES, 0, 6);
+ }
+
+ self->egl->glBindVertexArray(0);
+ gsr_shader_use_none(&self->shaders[0]);
+ self->egl->glBindTexture(GL_TEXTURE_2D, 0);
+ self->egl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ return 0;
+}
diff --git a/src/egl.c b/src/egl.c
index eb5bf9a..c5b14b9 100644
--- a/src/egl.c
+++ b/src/egl.c
@@ -138,6 +138,37 @@ static bool gsr_egl_load_gl(gsr_egl *self, void *library) {
{ (void**)&self->glTexImage2D, "glTexImage2D" },
{ (void**)&self->glCopyImageSubData, "glCopyImageSubData" },
{ (void**)&self->glClearTexImage, "glClearTexImage" },
+ { (void**)&self->glGenFramebuffers, "glGenFramebuffers" },
+ { (void**)&self->glBindFramebuffer, "glBindFramebuffer" },
+ { (void**)&self->glDeleteFramebuffers, "glDeleteFramebuffers" },
+ { (void**)&self->glViewport, "glViewport" },
+ { (void**)&self->glFramebufferTexture2D, "glFramebufferTexture2D" },
+ { (void**)&self->glDrawBuffers, "glDrawBuffers" },
+ { (void**)&self->glCheckFramebufferStatus, "glCheckFramebufferStatus" },
+ { (void**)&self->glBindBuffer, "glBindBuffer" },
+ { (void**)&self->glGenBuffers, "glGenBuffers" },
+ { (void**)&self->glBufferData, "glBufferData" },
+ { (void**)&self->glDeleteBuffers, "glDeleteBuffers" },
+ { (void**)&self->glGenVertexArrays, "glGenVertexArrays" },
+ { (void**)&self->glBindVertexArray, "glBindVertexArray" },
+ { (void**)&self->glDeleteVertexArrays, "glDeleteVertexArrays" },
+ { (void**)&self->glCreateProgram, "glCreateProgram" },
+ { (void**)&self->glCreateShader, "glCreateShader" },
+ { (void**)&self->glAttachShader, "glAttachShader" },
+ { (void**)&self->glBindAttribLocation, "glBindAttribLocation" },
+ { (void**)&self->glCompileShader, "glCompileShader" },
+ { (void**)&self->glLinkProgram, "glLinkProgram" },
+ { (void**)&self->glShaderSource, "glShaderSource" },
+ { (void**)&self->glUseProgram, "glUseProgram" },
+ { (void**)&self->glGetProgramInfoLog, "glGetProgramInfoLog" },
+ { (void**)&self->glGetShaderiv, "glGetShaderiv" },
+ { (void**)&self->glGetShaderInfoLog, "glGetShaderInfoLog" },
+ { (void**)&self->glDeleteProgram, "glDeleteProgram" },
+ { (void**)&self->glDeleteShader, "glDeleteShader" },
+ { (void**)&self->glGetProgramiv, "glGetProgramiv" },
+ { (void**)&self->glVertexAttribPointer, "glVertexAttribPointer" },
+ { (void**)&self->glEnableVertexAttribArray, "glEnableVertexAttribArray" },
+ { (void**)&self->glDrawArrays, "glDrawArrays" },
{ NULL, NULL }
};
diff --git a/src/main.cpp b/src/main.cpp
index a1ed3cc..9c624a2 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -39,12 +39,6 @@ extern "C" {
// TODO: If options are not supported then they are returned (allocated) in the options. This should be free'd.
-typedef enum {
- GPU_VENDOR_AMD,
- GPU_VENDOR_INTEL,
- GPU_VENDOR_NVIDIA
-} gpu_vendor;
-
// TODO: Remove LIBAVUTIL_VERSION_MAJOR checks in the future when ubuntu, pop os LTS etc update ffmpeg to >= 5.0
static const int VIDEO_STREAM_INDEX = 0;
@@ -276,7 +270,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, gpu_vendor vendor, FramerateMode framerate_mode) {
+ int fps, const AVCodec *codec, bool is_livestream, gsr_gpu_vendor vendor, FramerateMode framerate_mode) {
AVCodecContext *codec_context = avcodec_alloc_context3(codec);
@@ -361,7 +355,7 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
av_opt_set_int(codec_context->priv_data, "b_ref_mode", 0, 0);
//av_opt_set_int(codec_context->priv_data, "cbr", true, 0);
- if(vendor != GPU_VENDOR_NVIDIA) {
+ if(vendor != GSR_GPU_VENDOR_NVIDIA) {
// TODO: More options, better options
//codec_context->bit_rate = codec_context->width * codec_context->height;
av_opt_set(codec_context->priv_data, "rc_mode", "CQP", 0);
@@ -416,16 +410,16 @@ static bool vaapi_create_codec_context(AVCodecContext *video_codec_context) {
return true;
}
-static bool check_if_codec_valid_for_hardware(const AVCodec *codec, gpu_vendor vendor) {
+static bool check_if_codec_valid_for_hardware(const AVCodec *codec, gsr_gpu_vendor vendor) {
// Do not use AV_PIX_FMT_CUDA because we dont want to do full check with hardware context
- AVCodecContext *codec_context = create_video_codec_context(vendor == GPU_VENDOR_NVIDIA ? AV_PIX_FMT_YUV420P : AV_PIX_FMT_VAAPI, VideoQuality::VERY_HIGH, 60, codec, false, vendor, FramerateMode::CONSTANT);
+ AVCodecContext *codec_context = create_video_codec_context(vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_YUV420P : AV_PIX_FMT_VAAPI, VideoQuality::VERY_HIGH, 60, codec, false, vendor, FramerateMode::CONSTANT);
if(!codec_context)
return false;
codec_context->width = 512;
codec_context->height = 512;
- if(vendor != GPU_VENDOR_NVIDIA) {
+ if(vendor != GSR_GPU_VENDOR_NVIDIA) {
if(!vaapi_create_codec_context(codec_context)) {
avcodec_free_context(&codec_context);
return false;
@@ -442,10 +436,10 @@ static bool check_if_codec_valid_for_hardware(const AVCodec *codec, gpu_vendor v
return success;
}
-static const AVCodec* find_h264_encoder(gpu_vendor vendor) {
- const AVCodec *codec = avcodec_find_encoder_by_name(vendor == GPU_VENDOR_NVIDIA ? "h264_nvenc" : "h264_vaapi");
+static const AVCodec* find_h264_encoder(gsr_gpu_vendor vendor) {
+ const AVCodec *codec = avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "h264_nvenc" : "h264_vaapi");
if(!codec)
- codec = avcodec_find_encoder_by_name(vendor == GPU_VENDOR_NVIDIA ? "nvenc_h264" : "vaapi_h264");
+ codec = avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "nvenc_h264" : "vaapi_h264");
if(!codec)
return nullptr;
@@ -460,10 +454,10 @@ static const AVCodec* find_h264_encoder(gpu_vendor vendor) {
return checked_success ? codec : nullptr;
}
-static const AVCodec* find_h265_encoder(gpu_vendor vendor) {
- const AVCodec *codec = avcodec_find_encoder_by_name(vendor == GPU_VENDOR_NVIDIA ? "hevc_nvenc" : "hevc_vaapi");
+static const AVCodec* find_h265_encoder(gsr_gpu_vendor vendor) {
+ const AVCodec *codec = avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "hevc_nvenc" : "hevc_vaapi");
if(!codec)
- codec = avcodec_find_encoder_by_name(vendor == GPU_VENDOR_NVIDIA ? "nvenc_hevc" : "vaapi_hevc");
+ codec = avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "nvenc_hevc" : "vaapi_hevc");
if(!codec)
return nullptr;
@@ -514,9 +508,9 @@ static AVFrame* open_audio(AVCodecContext *audio_codec_context) {
return frame;
}
-static void open_video(AVCodecContext *codec_context, VideoQuality video_quality, bool very_old_gpu, gpu_vendor vendor, PixelFormat pixel_format) {
+static void open_video(AVCodecContext *codec_context, VideoQuality video_quality, bool very_old_gpu, gsr_gpu_vendor vendor, PixelFormat pixel_format) {
AVDictionary *options = nullptr;
- if(vendor == GPU_VENDOR_NVIDIA) {
+ if(vendor == GSR_GPU_VENDOR_NVIDIA) {
bool supports_p4 = false;
bool supports_p6 = false;
@@ -643,11 +637,12 @@ static void usage() {
fprintf(stderr, "\n");
fprintf(stderr, "OPTIONS:\n");
fprintf(stderr, " -w Window to record, a display, \"screen\", \"screen-direct\", \"screen-direct-force\" or \"focused\".\n");
- fprintf(stderr, " The display is the display (monitor) name in xrandr and if \"screen\" or \"screen-direct\" is selected then all displays are recorded.\n");
+ fprintf(stderr, " The display is the display (monitor) name in xrandr and if \"screen\", \"screen-direct\" or \"screen-direct-force\" is selected then all displays are recorded.\n");
fprintf(stderr, " If this is \"focused\" then the currently focused window is recorded. When recording the focused window then the -s option has to be used as well.\n");
fprintf(stderr, " \"screen-direct\"/\"screen-direct-force\" skips one texture copy for fullscreen applications so it may lead to better performance and it works with VRR monitors\n");
fprintf(stderr, " when recording fullscreen application but may break some applications, such as mpv in fullscreen mode. Direct mode doesn't capture cursor either.\n");
fprintf(stderr, " \"screen-direct-force\" is not recommended unless you use a VRR monitor because there might be driver issues that cause the video to stutter or record a black screen.\n");
+ fprintf(stderr, " On AMD/Intel, capturing a monitor might have better performance than recording a single window.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -c Container format for output file, for example mp4, or flv. Only required if no output file is specified or if recording in replay buffer mode.\n");
fprintf(stderr, " If an output file is specified and -c is not used then the container format is determined from the output filename extension.\n");
@@ -935,52 +930,6 @@ static bool is_livestream_path(const char *str) {
return false;
}
-typedef struct {
- gpu_vendor vendor;
- int gpu_version; /* 0 if unknown */
-} gpu_info;
-
-static bool gl_get_gpu_info(Display *dpy, gpu_info *info) {
- gsr_egl gl;
- if(!gsr_egl_load(&gl, dpy)) {
- fprintf(stderr, "Error: failed to load opengl\n");
- return false;
- }
-
- bool supported = true;
- const unsigned char *gl_vendor = gl.glGetString(GL_VENDOR);
- const unsigned char *gl_renderer = gl.glGetString(GL_RENDERER);
-
- info->gpu_version = 0;
-
- if(!gl_vendor) {
- fprintf(stderr, "Error: failed to get gpu vendor\n");
- supported = false;
- goto end;
- }
-
- if(strstr((const char*)gl_vendor, "AMD"))
- info->vendor = GPU_VENDOR_AMD;
- else if(strstr((const char*)gl_vendor, "Intel"))
- info->vendor = GPU_VENDOR_INTEL;
- else if(strstr((const char*)gl_vendor, "NVIDIA"))
- info->vendor = GPU_VENDOR_NVIDIA;
- else {
- fprintf(stderr, "Error: unknown gpu vendor: %s\n", gl_vendor);
- supported = false;
- goto end;
- }
-
- if(gl_renderer) {
- if(info->vendor == GPU_VENDOR_NVIDIA)
- sscanf((const char*)gl_renderer, "%*s %*s %*s %d", &info->gpu_version);
- }
-
- end:
- gsr_egl_unload(&gl);
- return supported;
-}
-
// TODO: Proper cleanup
static int init_filter_graph(AVCodecContext *audio_codec_context, AVFilterGraph **graph, AVFilterContext **sink, std::vector<AVFilterContext*> &src_filter_ctx, size_t num_sources)
{
@@ -1301,23 +1250,23 @@ int main(int argc, char **argv) {
return 2;
}
- gpu_info gpu_inf;
+ gsr_gpu_info gpu_inf;
bool very_old_gpu = false;
if(!gl_get_gpu_info(dpy, &gpu_inf))
return 2;
- if(gpu_inf.vendor == GPU_VENDOR_NVIDIA && gpu_inf.gpu_version != 0 && gpu_inf.gpu_version < 900) {
+ if(gpu_inf.vendor == GSR_GPU_VENDOR_NVIDIA && gpu_inf.gpu_version != 0 && gpu_inf.gpu_version < 900) {
fprintf(stderr, "Info: your gpu appears to be very old (older than maxwell architecture). Switching to lower preset\n");
very_old_gpu = true;
}
- if(gpu_inf.vendor != GPU_VENDOR_NVIDIA && overclock) {
+ if(gpu_inf.vendor != GSR_GPU_VENDOR_NVIDIA && overclock) {
fprintf(stderr, "Info: overclock option has no effect on amd/intel, ignoring option...\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.
- const FramerateMode framerate_mode = gpu_inf.vendor == GPU_VENDOR_NVIDIA ? FramerateMode::CONSTANT : FramerateMode::VARIABLE;
+ const FramerateMode framerate_mode = gpu_inf.vendor == GSR_GPU_VENDOR_NVIDIA ? FramerateMode::CONSTANT : FramerateMode::VARIABLE;
const char *screen_region = args["-s"].value();
const char *window_str = args["-w"].value();
@@ -1362,7 +1311,7 @@ int main(int argc, char **argv) {
}
}
- if(gpu_inf.vendor == GPU_VENDOR_NVIDIA) {
+ if(gpu_inf.vendor == GSR_GPU_VENDOR_NVIDIA) {
const char *capture_target = window_str;
bool direct_capture = strcmp(window_str, "screen-direct") == 0;
if(direct_capture) {
@@ -1389,12 +1338,6 @@ int main(int argc, char **argv) {
if(!capture)
return 1;
} else {
- bool broken = true;
- if(broken) {
- fprintf(stderr, "Error: recording a monitor on AMD/Intel has been temporary disabled because of issues. Please record a window instead\n");
- return 1;
- }
-
const char *capture_target = window_str;
if(strcmp(window_str, "screen-direct") == 0 || strcmp(window_str, "screen-direct-force") == 0) {
capture_target = "screen";
@@ -1403,6 +1346,7 @@ int main(int argc, char **argv) {
gsr_capture_kms_vaapi_params kms_params;
kms_params.dpy = dpy;
kms_params.display_to_capture = capture_target;
+ kms_params.gpu_inf = gpu_inf;
capture = gsr_capture_kms_vaapi_create(&kms_params);
if(!capture)
return 1;
@@ -1418,7 +1362,7 @@ int main(int argc, char **argv) {
if(!capture) {
switch(gpu_inf.vendor) {
- case GPU_VENDOR_AMD: {
+ case GSR_GPU_VENDOR_AMD: {
gsr_capture_xcomposite_vaapi_params xcomposite_params;
xcomposite_params.window = src_window_id;
xcomposite_params.follow_focused = follow_focused;
@@ -1428,7 +1372,7 @@ int main(int argc, char **argv) {
return 1;
break;
}
- case GPU_VENDOR_INTEL: {
+ case GSR_GPU_VENDOR_INTEL: {
gsr_capture_xcomposite_vaapi_params xcomposite_params;
xcomposite_params.window = src_window_id;
xcomposite_params.follow_focused = follow_focused;
@@ -1438,7 +1382,7 @@ int main(int argc, char **argv) {
return 1;
break;
}
- case GPU_VENDOR_NVIDIA: {
+ case GSR_GPU_VENDOR_NVIDIA: {
gsr_capture_xcomposite_cuda_params xcomposite_params;
xcomposite_params.window = src_window_id;
xcomposite_params.follow_focused = follow_focused;
@@ -1522,7 +1466,7 @@ int main(int argc, char **argv) {
const double target_fps = 1.0 / (double)fps;
if(strcmp(video_codec_to_use, "auto") == 0) {
- if(gpu_inf.vendor == GPU_VENDOR_INTEL) {
+ if(gpu_inf.vendor == GSR_GPU_VENDOR_INTEL) {
const AVCodec *h264_codec = find_h264_encoder(gpu_inf.vendor);
if(!h264_codec) {
fprintf(stderr, "Info: using h265 encoder because a codec was not specified and your gpu does not support h264\n");
@@ -1590,7 +1534,7 @@ int main(int argc, char **argv) {
AVStream *video_stream = nullptr;
std::vector<AudioTrack> audio_tracks;
- AVCodecContext *video_codec_context = create_video_codec_context(gpu_inf.vendor == GPU_VENDOR_NVIDIA ? AV_PIX_FMT_CUDA : AV_PIX_FMT_VAAPI, quality, fps, video_codec_f, is_livestream, gpu_inf.vendor, framerate_mode);
+ AVCodecContext *video_codec_context = create_video_codec_context(gpu_inf.vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_CUDA : AV_PIX_FMT_VAAPI, quality, fps, video_codec_f, is_livestream, gpu_inf.vendor, framerate_mode);
if(replay_buffer_size_secs == -1)
video_stream = create_stream(av_format_context, video_codec_context);
diff --git a/src/shader.c b/src/shader.c
new file mode 100644
index 0000000..e7b3bb2
--- /dev/null
+++ b/src/shader.c
@@ -0,0 +1,137 @@
+#include "../include/shader.h"
+#include <stdio.h>
+#include <assert.h>
+
+static int min_int(int a, int b) {
+ return a < b ? a : b;
+}
+
+static unsigned int loader_shader(gsr_egl *egl, unsigned int type, const char *source) {
+ unsigned int shader_id = egl->glCreateShader(type);
+ if(shader_id == 0) {
+ fprintf(stderr, "gsr error: loader_shader: failed to create shader, error: %d\n", egl->glGetError());
+ return 0;
+ }
+
+ egl->glShaderSource(shader_id, 1, &source, NULL);
+ egl->glCompileShader(shader_id);
+
+ int compiled = 0;
+ egl->glGetShaderiv(shader_id, GL_COMPILE_STATUS, &compiled);
+ if(!compiled) {
+ int info_length = 0;
+ egl->glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &info_length);
+
+ if(info_length > 1) {
+ char info_log[4096];
+ egl->glGetShaderInfoLog(shader_id, min_int(4096, info_length), NULL, info_log);
+ fprintf(stderr, "gsr error: loader shader: failed to compile shader, error:\n%s\n", info_log);
+ }
+
+ egl->glDeleteShader(shader_id);
+ return 0;
+ }
+
+ return shader_id;
+}
+
+static unsigned int load_program(gsr_egl *egl, const char *vertex_shader, const char *fragment_shader) {
+ unsigned int vertex_shader_id = 0;
+ unsigned int fragment_shader_id = 0;
+ unsigned int program_id = 0;
+ int linked = 0;
+
+ if(vertex_shader) {
+ vertex_shader_id = loader_shader(egl, GL_VERTEX_SHADER, vertex_shader);
+ if(vertex_shader_id == 0)
+ goto err;
+ }
+
+ if(fragment_shader) {
+ fragment_shader_id = loader_shader(egl, GL_FRAGMENT_SHADER, fragment_shader);
+ if(fragment_shader_id == 0)
+ goto err;
+ }
+
+ program_id = egl->glCreateProgram();
+ if(program_id == 0) {
+ fprintf(stderr, "gsr error: load_program: failed to create shader program, error: %d\n", egl->glGetError());
+ goto err;
+ }
+
+ if(vertex_shader_id)
+ egl->glAttachShader(program_id, vertex_shader_id);
+
+ if(fragment_shader_id)
+ egl->glAttachShader(program_id, fragment_shader_id);
+
+ egl->glLinkProgram(program_id);
+
+ egl->glGetProgramiv(program_id, GL_LINK_STATUS, &linked);
+ if(!linked) {
+ int info_length = 0;
+ egl->glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &info_length);
+
+ if(info_length > 1) {
+ char info_log[4096];
+ egl->glGetProgramInfoLog(program_id, min_int(4096, info_length), NULL, info_log);
+ fprintf(stderr, "gsr error: load program: linking shader program failed, error:\n%s\n", info_log);
+ }
+
+ goto err;
+ }
+
+ if(fragment_shader_id)
+ egl->glDeleteShader(fragment_shader_id);
+ if(vertex_shader_id)
+ egl->glDeleteShader(vertex_shader_id);
+
+ return program_id;
+
+ err:
+ if(program_id)
+ egl->glDeleteProgram(program_id);
+ if(fragment_shader_id)
+ egl->glDeleteShader(fragment_shader_id);
+ if(vertex_shader_id)
+ egl->glDeleteShader(vertex_shader_id);
+ return 0;
+}
+
+int gsr_shader_init(gsr_shader *self, gsr_egl *egl, const char *vertex_shader, const char *fragment_shader) {
+ assert(egl);
+ self->egl = egl;
+ self->program_id = 0;
+
+ if(!vertex_shader && !fragment_shader) {
+ fprintf(stderr, "gsr error: gsr_shader_init: vertex shader and fragment shader can't be NULL at the same time\n");
+ return -1;
+ }
+
+ self->program_id = load_program(self->egl, vertex_shader, fragment_shader);
+ if(self->program_id == 0)
+ return -1;
+
+ return 0;
+}
+
+void gsr_shader_deinit(gsr_shader *self) {
+ if(self->program_id) {
+ self->egl->glDeleteProgram(self->program_id);
+ self->program_id = 0;
+ }
+}
+
+int gsr_shader_bind_attribute_location(gsr_shader *self, const char *attribute, int location) {
+ while(self->egl->glGetError()) {}
+ self->egl->glBindAttribLocation(self->program_id, location, attribute);
+ return self->egl->glGetError();
+}
+
+void gsr_shader_use(gsr_shader *self) {
+ self->egl->glUseProgram(self->program_id);
+}
+
+void gsr_shader_use_none(gsr_shader *self) {
+ self->egl->glUseProgram(0);
+}
diff --git a/src/utils.c b/src/utils.c
index bf0a0c1..98de5f0 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -1,6 +1,8 @@
#include "../include/utils.h"
+#include "../include/egl.h"
#include <time.h>
#include <string.h>
+#include <stdio.h>
double clock_get_monotonic_seconds(void) {
struct timespec ts;
@@ -60,3 +62,44 @@ bool get_monitor_by_name(Display *display, const char *name, gsr_monitor *monito
for_each_active_monitor_output(display, get_monitor_by_name_callback, &userdata);
return userdata.found_monitor;
}
+
+bool gl_get_gpu_info(Display *dpy, gsr_gpu_info *info) {
+ gsr_egl gl;
+ if(!gsr_egl_load(&gl, dpy)) {
+ fprintf(stderr, "gsr error: failed to load opengl\n");
+ return false;
+ }
+
+ bool supported = true;
+ const unsigned char *gl_vendor = gl.glGetString(GL_VENDOR);
+ const unsigned char *gl_renderer = gl.glGetString(GL_RENDERER);
+
+ info->gpu_version = 0;
+
+ if(!gl_vendor) {
+ fprintf(stderr, "gsr error: failed to get gpu vendor\n");
+ supported = false;
+ goto end;
+ }
+
+ if(strstr((const char*)gl_vendor, "AMD"))
+ info->vendor = GSR_GPU_VENDOR_AMD;
+ else if(strstr((const char*)gl_vendor, "Intel"))
+ info->vendor = GSR_GPU_VENDOR_INTEL;
+ else if(strstr((const char*)gl_vendor, "NVIDIA"))
+ info->vendor = GSR_GPU_VENDOR_NVIDIA;
+ else {
+ fprintf(stderr, "gsr error: unknown gpu vendor: %s\n", gl_vendor);
+ supported = false;
+ goto end;
+ }
+
+ if(gl_renderer) {
+ if(info->vendor == GSR_GPU_VENDOR_NVIDIA)
+ sscanf((const char*)gl_renderer, "%*s %*s %*s %d", &info->gpu_version);
+ }
+
+ end:
+ gsr_egl_unload(&gl);
+ return supported;
+}