From f6107a0c5d41aa9fbaa41d64e2f6a5681f9237cc Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 14 Apr 2023 09:36:24 +0200 Subject: [PATCH] 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%. --- README.md | 6 +- TODO | 11 +- build.sh | 4 +- include/capture/kms_vaapi.h | 4 +- include/color_conversion.h | 51 +++++ include/egl.h | 50 +++++ include/shader.h | 19 ++ include/utils.h | 13 ++ include/vec2.h | 6 +- src/capture/kms_vaapi.c | 391 +++++++++++++++++++-------------- src/capture/xcomposite_vaapi.c | 13 +- src/color_conversion.c | 235 ++++++++++++++++++++ src/egl.c | 31 +++ src/main.cpp | 108 +++------ src/shader.c | 137 ++++++++++++ src/utils.c | 43 ++++ 16 files changed, 863 insertions(+), 259 deletions(-) create mode 100644 include/color_conversion.h create mode 100644 include/shader.h create mode 100644 src/color_conversion.c create mode 100644 src/shader.c diff --git a/README.md b/README.md index 0ceb87b..007b0ac 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ where only the last few seconds are saved. ## Note This software works only on X11 (Wayland with Xwayland is NOT supported).\ -If you are using a variable refresh rate monitor then choose to record "screen-direct". This will allow variable refresh rate to work when recording fullscreen applications. Note that some applications such as mpv will not work in fullscreen mode. A fix is being developed for this. +If you are using a variable refresh rate monitor then choose to record "screen-direct-force". This will allow variable refresh rate to work when recording fullscreen applications. Note that some applications such as mpv will not work in fullscreen mode. A fix is being developed for this. ### TEMPORARY ISSUES 1) screen-direct capture has been temporary disabled as it causes issues with stuttering. This might be a nvfbc bug. -2) Recording monitor on AMD/Intel has been temporary disables as issues surrounding it are fixed. For now, record a window instead. +2) recording the monitor on steam deck might fail sometimes. This happens even when using ffmpeg directly. This might be a steam deck driver bug. Recording a single window doesn't have this issue. ### AMD/Intel root permission When recording a window under AMD/Intel no special user permission is required, however when recording a monitor the program needs root permission (to access KMS). To make this safer, the part that needs root access has been moved to its own executable (to make it as small as possible) and a GUI sudo prompt is shown to run this executable as root. The executable is called "gsr-kms-server". @@ -27,6 +27,8 @@ NVIDIA driver has a "feature" (read: bug) where it will downclock memory transfe To enable overclocking for optimal performance use the `-oc` option when running GPU Screen Recorder. You also need to have "Coolbits" NVIDIA X setting set to "12" to enable overclocking. You can automatically add this option if you run `sudo install_coolbits.sh` and then reboot your computer. This script is automatically run if you are using NVIDIA and run `install.sh`.\ Note that this only works when Xorg server is running as root, and using this option will only give you a performance boost if the game you are recording is bottlenecked by your GPU.\ Note! use at your own risk! +## Note about optimal performance on AMD/Intel +Performance is the same when recording a single window or the monitor, however in some cases, such as when gpu usage is 100%, the video capture rate might be slower than the games fps when recording a single window instead of a monitor. Recording the monitor instead is recommended in such cases. # Installation If you are running an Arch Linux based distro, then you can find gpu screen recorder on aur under the name gpu-screen-recorder-git (`yay -S gpu-screen-recorder-git`).\ diff --git a/TODO b/TODO index a3a9995..1552d20 100644 --- a/TODO +++ b/TODO @@ -39,12 +39,19 @@ Fix constant framerate not working properly on amd/intel because capture framera JPEG color range on amd seems to produce too bright video with h264 but not hevc, why? +Properly handle monitor reconfiguration (kms vaapi, nvfbc). + Better configure vaapi. The file size is too large. Better colors for vaapi. It looks a bit off when recording vscode for example. Clear vaapi surface (for focused window). Support -h and --help. -h should only show the first line and --help the full help. On error, only show that -h. -Rotated display on amd doesn't work correctly (if you only have 1 monitor connected and it's rotated), even with ffmpeg kmsgrab. The drmfd is rotated as well so it needs to be rotated in the surface copy. +Window capture performance on steam deck isn't good when playing the witcher 3 for example. The capture itself is fine but video encoding puts it to 30fps even if the game runs at 57 fps. -Fix kms monitor capture. Broken right now on amd and performance is bad. \ No newline at end of file +Monitor capture on steam deck is slightly below the game fps, but only when capturing on the steam deck screen. If capturing on another monitor, there is no issue. + Is this related to the dma buf rotation issue? different modifier being slow? does this always happen? + +Make sure rgb to yuv color conversion is 100% correct. +Fallback to vaapi copy in kms if opengl version fails. This can happen on steam deck for some reason (driver bug?). +Test if vaapi copy version uses less memory than opengl version. \ No newline at end of file diff --git a/build.sh b/build.sh index 317340b..22c713b 100755 --- a/build.sh +++ b/build.sh @@ -25,11 +25,13 @@ build_gsr() { gcc -c src/xnvctrl.c $opts $includes gcc -c src/overclock.c $opts $includes gcc -c src/window_texture.c $opts $includes + gcc -c src/shader.c $opts $includes + gcc -c src/color_conversion.c $opts $includes gcc -c src/utils.c $opts $includes gcc -c src/library_loader.c $opts $includes g++ -c src/sound.cpp $opts $includes g++ -c src/main.cpp $opts $includes - g++ -o gpu-screen-recorder -O2 capture.o nvfbc.o kms_client.o egl.o cuda.o xnvctrl.o overclock.o window_texture.o utils.o library_loader.o xcomposite_cuda.o xcomposite_vaapi.o kms_vaapi.o sound.o main.o -s $libs + g++ -o gpu-screen-recorder -O2 capture.o nvfbc.o kms_client.o egl.o cuda.o xnvctrl.o overclock.o window_texture.o shader.o color_conversion.o utils.o library_loader.o xcomposite_cuda.o xcomposite_vaapi.o kms_vaapi.o sound.o main.o -s $libs } build_gsr_kms_server diff --git a/include/capture/kms_vaapi.h b/include/capture/kms_vaapi.h index ba5763b..a4aa3ed 100644 --- a/include/capture/kms_vaapi.h +++ b/include/capture/kms_vaapi.h @@ -1,8 +1,9 @@ #ifndef GSR_CAPTURE_KMS_VAAPI_H #define GSR_CAPTURE_KMS_VAAPI_H -#include "capture.h" #include "../vec2.h" +#include "../utils.h" +#include "capture.h" #include typedef struct _XDisplay Display; @@ -10,6 +11,7 @@ typedef struct _XDisplay Display; typedef struct { Display *dpy; const char *display_to_capture; /* if this is "screen", then the entire x11 screen is captured (all displays). A copy is made of this */ + gsr_gpu_info gpu_inf; } gsr_capture_kms_vaapi_params; gsr_capture* gsr_capture_kms_vaapi_create(const gsr_capture_kms_vaapi_params *params); diff --git a/include/color_conversion.h b/include/color_conversion.h new file mode 100644 index 0000000..de83d1e --- /dev/null +++ b/include/color_conversion.h @@ -0,0 +1,51 @@ +#ifndef GSR_COLOR_CONVERSION_H +#define GSR_COLOR_CONVERSION_H + +#include "shader.h" +#include "vec2.h" + +typedef enum { + GSR_SOURCE_COLOR_RGB +} gsr_source_color; + +typedef enum { + GSR_DESTINATION_COLOR_NV12 +} gsr_destination_color; + +typedef struct { + gsr_egl *egl; + + gsr_source_color source_color; + gsr_destination_color destination_color; + + unsigned int source_textures[2]; + int num_source_textures; + + unsigned int destination_textures[2]; + int num_destination_textures; + + float rotation; + + vec2f position; + vec2f size; +} gsr_color_conversion_params; + +typedef struct { + gsr_egl *egl; + gsr_shader shaders[2]; + + unsigned int source_textures[2]; + unsigned int destination_textures[2]; + + unsigned int framebuffers[2]; + + unsigned int vertex_array_object_id; + unsigned int vertex_buffer_object_id; +} gsr_color_conversion; + +int gsr_color_conversion_init(gsr_color_conversion *self, const gsr_color_conversion_params *params); +void gsr_color_conversion_deinit(gsr_color_conversion *self); + +int gsr_color_conversion_update(gsr_color_conversion *self, int width, int height); + +#endif /* GSR_COLOR_CONVERSION_H */ diff --git a/include/egl.h b/include/egl.h index 1056a5c..891c7a0 100644 --- a/include/egl.h +++ b/include/egl.h @@ -44,7 +44,18 @@ typedef void (*__eglMustCastToProperFunctionPointerType)(void); #define EGL_TRUE 1 #define EGL_IMAGE_PRESERVED_KHR 0x30D2 #define EGL_NATIVE_PIXMAP_KHR 0x30B0 +#define EGL_LINUX_DRM_FOURCC_EXT 0x3271 +#define EGL_WIDTH 0x3057 +#define EGL_HEIGHT 0x3056 +#define EGL_DMA_BUF_PLANE0_FD_EXT 0x3272 +#define EGL_DMA_BUF_PLANE0_OFFSET_EXT 0x3273 +#define EGL_DMA_BUF_PLANE0_PITCH_EXT 0x3274 +#define EGL_LINUX_DMA_BUF_EXT 0x3270 +#define GL_FLOAT 0x1406 +#define GL_FALSE 0 +#define GL_TRUE 1 +#define GL_TRIANGLES 0x0004 #define GL_TEXTURE_2D 0x0DE1 #define GL_RGB 0x1907 #define GL_UNSIGNED_BYTE 0x1401 @@ -67,6 +78,13 @@ typedef void (*__eglMustCastToProperFunctionPointerType)(void); #define GL_VENDOR 0x1F00 #define GL_RENDERER 0x1F01 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 + typedef struct { void *egl_library; void *gl_library; @@ -108,6 +126,38 @@ typedef struct { void (*glTexImage2D)(unsigned int target, int level, int internalFormat, int width, int height, int border, unsigned int format, unsigned int type, const void *pixels); void (*glCopyImageSubData)(unsigned int srcName, unsigned int srcTarget, int srcLevel, int srcX, int srcY, int srcZ, unsigned int dstName, unsigned int dstTarget, int dstLevel, int dstX, int dstY, int dstZ, int srcWidth, int srcHeight, int srcDepth); void (*glClearTexImage)(unsigned int texture, unsigned int level, unsigned int format, unsigned int type, const void *data); + void (*glGenFramebuffers)(int n, unsigned int *framebuffers); + void (*glBindFramebuffer)(unsigned int target, unsigned int framebuffer); + void (*glDeleteFramebuffers)(int n, const unsigned int *framebuffers); + void (*glViewport)(int x, int y, int width, int height); + void (*glFramebufferTexture2D)(unsigned int target, unsigned int attachment, unsigned int textarget, unsigned int texture, int level); + void (*glDrawBuffers)(int n, const unsigned int *bufs); + unsigned int (*glCheckFramebufferStatus)(unsigned int target); + void (*glBindBuffer)(unsigned int target, unsigned int buffer); + void (*glGenBuffers)(int n, unsigned int *buffers); + void (*glBufferData)(unsigned int target, khronos_ssize_t size, const void *data, unsigned int usage); + void (*glDeleteBuffers)(int n, const unsigned int *buffers); + void (*glGenVertexArrays)(int n, unsigned int *arrays); + void (*glBindVertexArray)(unsigned int array); + void (*glDeleteVertexArrays)(int n, const unsigned int *arrays); + + unsigned int (*glCreateProgram)(void); + unsigned int (*glCreateShader)(unsigned int type); + void (*glAttachShader)(unsigned int program, unsigned int shader); + void (*glBindAttribLocation)(unsigned int program, unsigned int index, const char *name); + void (*glCompileShader)(unsigned int shader); + void (*glLinkProgram)(unsigned int program); + void (*glShaderSource)(unsigned int shader, int count, const char *const*string, const int *length); + void (*glUseProgram)(unsigned int program); + void (*glGetProgramInfoLog)(unsigned int program, int bufSize, int *length, char *infoLog); + void (*glGetShaderiv)(unsigned int shader, unsigned int pname, int *params); + void (*glGetShaderInfoLog)(unsigned int shader, int bufSize, int *length, char *infoLog); + void (*glDeleteProgram)(unsigned int program); + void (*glDeleteShader)(unsigned int shader); + void (*glGetProgramiv)(unsigned int program, unsigned int pname, int *params); + void (*glVertexAttribPointer)(unsigned int index, int size, unsigned int type, unsigned char normalized, int stride, const void *pointer); + void (*glEnableVertexAttribArray)(unsigned int index); + void (*glDrawArrays)(unsigned int mode, int first, int count ); } gsr_egl; bool gsr_egl_load(gsr_egl *self, Display *dpy); diff --git a/include/shader.h b/include/shader.h new file mode 100644 index 0000000..37f4c09 --- /dev/null +++ b/include/shader.h @@ -0,0 +1,19 @@ +#ifndef GSR_SHADER_H +#define GSR_SHADER_H + +#include "egl.h" + +typedef struct { + gsr_egl *egl; + unsigned int program_id; +} gsr_shader; + +/* |vertex_shader| or |fragment_shader| may be NULL */ +int gsr_shader_init(gsr_shader *self, gsr_egl *egl, const char *vertex_shader, const char *fragment_shader); +void gsr_shader_deinit(gsr_shader *self); + +int gsr_shader_bind_attribute_location(gsr_shader *self, const char *attribute, int location); +void gsr_shader_use(gsr_shader *self); +void gsr_shader_use_none(gsr_shader *self); + +#endif /* GSR_SHADER_H */ diff --git a/include/utils.h b/include/utils.h index f4925e5..fb1365b 100644 --- a/include/utils.h +++ b/include/utils.h @@ -5,6 +5,17 @@ #include #include +typedef enum { + GSR_GPU_VENDOR_AMD, + GSR_GPU_VENDOR_INTEL, + GSR_GPU_VENDOR_NVIDIA +} gsr_gpu_vendor; + +typedef struct { + gsr_gpu_vendor vendor; + int gpu_version; /* 0 if unknown */ +} gsr_gpu_info; + typedef struct { vec2i pos; vec2i size; @@ -23,4 +34,6 @@ typedef void (*active_monitor_callback)(const XRROutputInfo *output_info, const void for_each_active_monitor_output(Display *display, active_monitor_callback callback, void *userdata); bool get_monitor_by_name(Display *display, const char *name, gsr_monitor *monitor); +bool gl_get_gpu_info(Display *dpy, gsr_gpu_info *info); + #endif /* GSR_UTILS_H */ diff --git a/include/vec2.h b/include/vec2.h index d1957ef..3e33cfb 100644 --- a/include/vec2.h +++ b/include/vec2.h @@ -5,4 +5,8 @@ typedef struct { int x, y; } vec2i; -#endif /* VEC2_H */ \ No newline at end of file +typedef struct { + float x, y; +} vec2f; + +#endif /* VEC2_H */ 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 #include #include @@ -14,6 +15,13 @@ #include #include +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; + 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); - // 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); + gsr_color_conversion_update(&cap_kms->color_conversion, frame->width, frame->height); - 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; - } - - va_status = vaCreateBuffer(cap_kms->va_dpy, cap_kms->context_id, VAProcPipelineParameterBufferType, sizeof(params), 1, ¶ms, &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; - } - - 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); + + 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->context_id) { - vaDestroyContext(cap_kms->va_dpy, cap_kms->context_id); - cap_kms->context_id = 0; + if(cap_kms->input_texture) { + cap_kms->egl.glDeleteTextures(1, &cap_kms->input_texture); + cap_kms->input_texture = 0; } - if(cap_kms->config_id) { - vaDestroyConfig(cap_kms->va_dpy, cap_kms->config_id); - cap_kms->config_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.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, ¶ms, &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 +#include +#include + +#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 &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 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 +#include + +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 #include +#include 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; +}