aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
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;
+}