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%.
This commit is contained in:
dec05eba 2023-04-14 09:36:24 +02:00 committed by dec05eba
parent 5c714ea714
commit f6107a0c5d
16 changed files with 863 additions and 259 deletions

View File

@ -7,10 +7,10 @@ where only the last few seconds are saved.
## Note ## Note
This software works only on X11 (Wayland with Xwayland is NOT supported).\ 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 ### TEMPORARY ISSUES
1) screen-direct capture has been temporary disabled as it causes issues with stuttering. This might be a nvfbc bug. 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 ### 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). 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". 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`.\ 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 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! 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 # 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`).\ 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`).\

11
TODO
View File

@ -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? 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 configure vaapi. The file size is too large.
Better colors for vaapi. It looks a bit off when recording vscode for example. Better colors for vaapi. It looks a bit off when recording vscode for example.
Clear vaapi surface (for focused window). 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. 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. 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.

View File

@ -25,11 +25,13 @@ build_gsr() {
gcc -c src/xnvctrl.c $opts $includes gcc -c src/xnvctrl.c $opts $includes
gcc -c src/overclock.c $opts $includes gcc -c src/overclock.c $opts $includes
gcc -c src/window_texture.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/utils.c $opts $includes
gcc -c src/library_loader.c $opts $includes gcc -c src/library_loader.c $opts $includes
g++ -c src/sound.cpp $opts $includes g++ -c src/sound.cpp $opts $includes
g++ -c src/main.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 build_gsr_kms_server

View File

@ -1,8 +1,9 @@
#ifndef GSR_CAPTURE_KMS_VAAPI_H #ifndef GSR_CAPTURE_KMS_VAAPI_H
#define GSR_CAPTURE_KMS_VAAPI_H #define GSR_CAPTURE_KMS_VAAPI_H
#include "capture.h"
#include "../vec2.h" #include "../vec2.h"
#include "../utils.h"
#include "capture.h"
#include <X11/X.h> #include <X11/X.h>
typedef struct _XDisplay Display; typedef struct _XDisplay Display;
@ -10,6 +11,7 @@ typedef struct _XDisplay Display;
typedef struct { typedef struct {
Display *dpy; 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 */ 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_kms_vaapi_params;
gsr_capture* gsr_capture_kms_vaapi_create(const gsr_capture_kms_vaapi_params *params); gsr_capture* gsr_capture_kms_vaapi_create(const gsr_capture_kms_vaapi_params *params);

View File

@ -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 */

View File

@ -44,7 +44,18 @@ typedef void (*__eglMustCastToProperFunctionPointerType)(void);
#define EGL_TRUE 1 #define EGL_TRUE 1
#define EGL_IMAGE_PRESERVED_KHR 0x30D2 #define EGL_IMAGE_PRESERVED_KHR 0x30D2
#define EGL_NATIVE_PIXMAP_KHR 0x30B0 #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_TEXTURE_2D 0x0DE1
#define GL_RGB 0x1907 #define GL_RGB 0x1907
#define GL_UNSIGNED_BYTE 0x1401 #define GL_UNSIGNED_BYTE 0x1401
@ -67,6 +78,13 @@ typedef void (*__eglMustCastToProperFunctionPointerType)(void);
#define GL_VENDOR 0x1F00 #define GL_VENDOR 0x1F00
#define GL_RENDERER 0x1F01 #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 { typedef struct {
void *egl_library; void *egl_library;
void *gl_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 (*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 (*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 (*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; } gsr_egl;
bool gsr_egl_load(gsr_egl *self, Display *dpy); bool gsr_egl_load(gsr_egl *self, Display *dpy);

19
include/shader.h Normal file
View File

@ -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 */

View File

@ -5,6 +5,17 @@
#include <stdbool.h> #include <stdbool.h>
#include <X11/extensions/Xrandr.h> #include <X11/extensions/Xrandr.h>
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 { typedef struct {
vec2i pos; vec2i pos;
vec2i size; 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); 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 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 */ #endif /* GSR_UTILS_H */

View File

@ -5,4 +5,8 @@ typedef struct {
int x, y; int x, y;
} vec2i; } vec2i;
#endif /* VEC2_H */ typedef struct {
float x, y;
} vec2f;
#endif /* VEC2_H */

View File

@ -2,6 +2,7 @@
#include "../../kms/client/kms_client.h" #include "../../kms/client/kms_client.h"
#include "../../include/egl.h" #include "../../include/egl.h"
#include "../../include/utils.h" #include "../../include/utils.h"
#include "../../include/color_conversion.h"
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
@ -14,6 +15,13 @@
#include <va/va.h> #include <va/va.h>
#include <va/va_drmcommon.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 { typedef struct {
gsr_capture_kms_vaapi_params params; gsr_capture_kms_vaapi_params params;
bool should_stop; bool should_stop;
@ -36,12 +44,17 @@ typedef struct {
bool screen_capture; bool screen_capture;
VADisplay va_dpy; VADisplay va_dpy;
VAConfigID config_id;
VAContextID context_id; bool requires_rotation;
VASurfaceID input_surface; X11Rotation x11_rot;
VABufferID buffer_id;
VARectangle input_region; VADRMPRIMESurfaceDescriptor prime;
bool context_created;
unsigned int input_texture;
unsigned int target_textures[2];
gsr_color_conversion color_conversion;
unsigned int vao;
} gsr_capture_kms_vaapi; } gsr_capture_kms_vaapi;
static int max_int(int a, int b) { 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. // 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) { static int gsr_capture_kms_vaapi_start(gsr_capture *cap, AVCodecContext *video_codec_context) {
gsr_capture_kms_vaapi *cap_kms = cap->priv; 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; 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; gsr_monitor monitor;
if(strcmp(cap_kms->params.display_to_capture, "screen") == 0) { if(strcmp(cap_kms->params.display_to_capture, "screen") == 0) {
monitor.pos.x = 0; monitor.pos.x = 0;
@ -117,12 +143,24 @@ static int gsr_capture_kms_vaapi_start(gsr_capture *cap, AVCodecContext *video_c
return -1; 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_pos = monitor.pos;
cap_kms->capture_size = monitor.size; cap_kms->capture_size = monitor.size;
if(!gsr_egl_load(&cap_kms->egl, cap_kms->params.dpy)) { 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"); 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; return -1;
} }
@ -137,17 +175,21 @@ static int gsr_capture_kms_vaapi_start(gsr_capture *cap, AVCodecContext *video_c
return -1; return -1;
} }
//cap_kms->window_resize_timer = clock_get_monotonic_seconds(); // TODO:
return 0; 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) { static void gsr_capture_kms_vaapi_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) {
gsr_capture_kms_vaapi *cap_kms = cap->priv; gsr_capture_kms_vaapi *cap_kms = cap->priv;
// TODO: // 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) { if(!cap_kms->created_hw_frame) {
cap_kms->created_hw_frame = true; 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; cap_kms->stop_is_error = true;
return; 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) { static int gsr_capture_kms_vaapi_capture(gsr_capture *cap, AVFrame *frame) {
gsr_capture_kms_vaapi *cap_kms = cap->priv; gsr_capture_kms_vaapi *cap_kms = cap->priv;
VASurfaceID target_surface_id = (uintptr_t)frame->data[3];
if(cap_kms->dmabuf_fd > 0) { if(cap_kms->dmabuf_fd > 0) {
close(cap_kms->dmabuf_fd); close(cap_kms->dmabuf_fd);
cap_kms->dmabuf_fd = 0; 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.x = kms_response.data.fd.width;
cap_kms->kms_size.y = kms_response.data.fd.height; cap_kms->kms_size.y = kms_response.data.fd.height;
if(!cap_kms->context_created) { // TODO: This causes a crash sometimes on steam deck, why? is it a driver bug? a vaapi pure version doesn't cause a crash.
cap_kms->context_created = true; // Even ffmpeg kmsgrab causes this crash. The error is:
// amdgpu: Failed to allocate a buffer:
VAStatus va_status = vaCreateConfig(cap_kms->va_dpy, VAProfileNone, VAEntrypointVideoProc, NULL, 0, &cap_kms->config_id); // amdgpu: size : 28508160 bytes
if(va_status != VA_STATUS_SUCCESS) { // amdgpu: alignment : 2097152 bytes
fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_capture: vaCreateConfig failed: %d\n", va_status); // amdgpu: domains : 4
cap_kms->should_stop = true; // amdgpu: flags : 4
cap_kms->stop_is_error = true; // amdgpu: Failed to allocate a buffer:
return -1; // amdgpu: size : 28508160 bytes
} // amdgpu: alignment : 2097152 bytes
// amdgpu: domains : 4
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); // amdgpu: flags : 4
if(va_status != VA_STATUS_SUCCESS) { // EE ../jupiter-mesa/src/gallium/drivers/radeonsi/radeon_vcn_enc.c:516 radeon_create_encoder UVD - Can't create CPB buffer.
fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_capture: vaCreateContext failed: %d\n", va_status); // [hevc_vaapi @ 0x55ea72b09840] Failed to upload encode parameters: 2 (resource allocation failed).
cap_kms->should_stop = true; // [hevc_vaapi @ 0x55ea72b09840] Encode failed: -5.
cap_kms->stop_is_error = true; // Error: avcodec_send_frame failed, error: Input/output error
return -1; // 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,
if(cap_kms->buffer_id) { EGL_WIDTH, cap_kms->kms_size.x,
vaDestroyBuffer(cap_kms->va_dpy, cap_kms->buffer_id); EGL_HEIGHT, cap_kms->kms_size.y,
cap_kms->buffer_id = 0; 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,
if(cap_kms->input_surface) { EGL_NONE
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
}; };
// Copying a surface to another surface will automatically perform the color conversion. Thanks vaapi! EGLImage image = cap_kms->egl.eglCreateImage(cap_kms->egl.egl_display, 0, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr);
VAProcPipelineParameterBuffer params = {0}; cap_kms->egl.glBindTexture(GL_TEXTURE_2D, cap_kms->input_texture);
params.surface = cap_kms->input_surface; cap_kms->egl.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
if(cap_kms->screen_capture) cap_kms->egl.eglDestroyImage(cap_kms->egl.egl_display, image);
params.surface_region = NULL; cap_kms->egl.glBindTexture(GL_TEXTURE_2D, 0);
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)) gsr_color_conversion_update(&cap_kms->color_conversion, frame->width, frame->height);
// 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); cap_kms->egl.eglSwapBuffers(cap_kms->egl.egl_display, cap_kms->egl.egl_surface);
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, &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;
}
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;
}
if(cap_kms->dmabuf_fd > 0) { if(cap_kms->dmabuf_fd > 0) {
close(cap_kms->dmabuf_fd); close(cap_kms->dmabuf_fd);
@ -366,7 +421,7 @@ static int gsr_capture_kms_vaapi_capture(gsr_capture *cap, AVFrame *frame) {
} }
// TODO: Remove // 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; 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) { static void gsr_capture_kms_vaapi_stop(gsr_capture *cap, AVCodecContext *video_codec_context) {
gsr_capture_kms_vaapi *cap_kms = cap->priv; gsr_capture_kms_vaapi *cap_kms = cap->priv;
if(cap_kms->buffer_id) { gsr_color_conversion_deinit(&cap_kms->color_conversion);
vaDestroyBuffer(cap_kms->va_dpy, cap_kms->buffer_id);
cap_kms->buffer_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->context_id) { if(cap_kms->input_texture) {
vaDestroyContext(cap_kms->va_dpy, cap_kms->context_id); cap_kms->egl.glDeleteTextures(1, &cap_kms->input_texture);
cap_kms->context_id = 0; cap_kms->input_texture = 0;
} }
if(cap_kms->config_id) { cap_kms->egl.glDeleteTextures(2, cap_kms->target_textures);
vaDestroyConfig(cap_kms->va_dpy, cap_kms->config_id); cap_kms->target_textures[0] = 0;
cap_kms->config_id = 0; cap_kms->target_textures[1] = 0;
}
if(cap_kms->input_surface) {
vaDestroySurfaces(cap_kms->va_dpy, &cap_kms->input_surface, 1);
cap_kms->input_surface = 0;
}
if(cap_kms->dmabuf_fd > 0) { if(cap_kms->dmabuf_fd > 0) {
close(cap_kms->dmabuf_fd); close(cap_kms->dmabuf_fd);

View File

@ -452,10 +452,21 @@ static void gsr_capture_xcomposite_vaapi_tick(gsr_capture *cap, AVCodecContext *
params.output_region = NULL; params.output_region = NULL;
params.output_background_color = 0; params.output_background_color = 0;
params.filter_flags = VA_FRAME_PICTURE; 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.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.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); 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) { if(va_status != VA_STATUS_SUCCESS) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: vaCreateBuffer failed: %d\n", va_status); fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: vaCreateBuffer failed: %d\n", va_status);

235
src/color_conversion.c Normal file
View File

@ -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;
}

View File

@ -138,6 +138,37 @@ static bool gsr_egl_load_gl(gsr_egl *self, void *library) {
{ (void**)&self->glTexImage2D, "glTexImage2D" }, { (void**)&self->glTexImage2D, "glTexImage2D" },
{ (void**)&self->glCopyImageSubData, "glCopyImageSubData" }, { (void**)&self->glCopyImageSubData, "glCopyImageSubData" },
{ (void**)&self->glClearTexImage, "glClearTexImage" }, { (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 } { NULL, NULL }
}; };

View File

@ -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. // 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 // 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; 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, static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
VideoQuality video_quality, 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); 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, "b_ref_mode", 0, 0);
//av_opt_set_int(codec_context->priv_data, "cbr", true, 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 // TODO: More options, better options
//codec_context->bit_rate = codec_context->width * codec_context->height; //codec_context->bit_rate = codec_context->width * codec_context->height;
av_opt_set(codec_context->priv_data, "rc_mode", "CQP", 0); 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; 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 // 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) if(!codec_context)
return false; return false;
codec_context->width = 512; codec_context->width = 512;
codec_context->height = 512; codec_context->height = 512;
if(vendor != GPU_VENDOR_NVIDIA) { if(vendor != GSR_GPU_VENDOR_NVIDIA) {
if(!vaapi_create_codec_context(codec_context)) { if(!vaapi_create_codec_context(codec_context)) {
avcodec_free_context(&codec_context); avcodec_free_context(&codec_context);
return false; return false;
@ -442,10 +436,10 @@ static bool check_if_codec_valid_for_hardware(const AVCodec *codec, gpu_vendor v
return success; return success;
} }
static const AVCodec* find_h264_encoder(gpu_vendor vendor) { static const AVCodec* find_h264_encoder(gsr_gpu_vendor vendor) {
const AVCodec *codec = avcodec_find_encoder_by_name(vendor == GPU_VENDOR_NVIDIA ? "h264_nvenc" : "h264_vaapi"); const AVCodec *codec = avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "h264_nvenc" : "h264_vaapi");
if(!codec) 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) if(!codec)
return nullptr; return nullptr;
@ -460,10 +454,10 @@ static const AVCodec* find_h264_encoder(gpu_vendor vendor) {
return checked_success ? codec : nullptr; return checked_success ? codec : nullptr;
} }
static const AVCodec* find_h265_encoder(gpu_vendor vendor) { static const AVCodec* find_h265_encoder(gsr_gpu_vendor vendor) {
const AVCodec *codec = avcodec_find_encoder_by_name(vendor == GPU_VENDOR_NVIDIA ? "hevc_nvenc" : "hevc_vaapi"); const AVCodec *codec = avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "hevc_nvenc" : "hevc_vaapi");
if(!codec) 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) if(!codec)
return nullptr; return nullptr;
@ -514,9 +508,9 @@ static AVFrame* open_audio(AVCodecContext *audio_codec_context) {
return frame; 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; AVDictionary *options = nullptr;
if(vendor == GPU_VENDOR_NVIDIA) { if(vendor == GSR_GPU_VENDOR_NVIDIA) {
bool supports_p4 = false; bool supports_p4 = false;
bool supports_p6 = false; bool supports_p6 = false;
@ -643,11 +637,12 @@ static void usage() {
fprintf(stderr, "\n"); fprintf(stderr, "\n");
fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, "OPTIONS:\n");
fprintf(stderr, " -w Window to record, a display, \"screen\", \"screen-direct\", \"screen-direct-force\" or \"focused\".\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, " 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, " \"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, " 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, " \"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, "\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, " -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"); 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; 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 // 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) 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; return 2;
} }
gpu_info gpu_inf; gsr_gpu_info gpu_inf;
bool very_old_gpu = false; bool very_old_gpu = false;
if(!gl_get_gpu_info(dpy, &gpu_inf)) if(!gl_get_gpu_info(dpy, &gpu_inf))
return 2; 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"); fprintf(stderr, "Info: your gpu appears to be very old (older than maxwell architecture). Switching to lower preset\n");
very_old_gpu = true; 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"); 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 // 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. // 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 *screen_region = args["-s"].value();
const char *window_str = args["-w"].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; const char *capture_target = window_str;
bool direct_capture = strcmp(window_str, "screen-direct") == 0; bool direct_capture = strcmp(window_str, "screen-direct") == 0;
if(direct_capture) { if(direct_capture) {
@ -1389,12 +1338,6 @@ int main(int argc, char **argv) {
if(!capture) if(!capture)
return 1; return 1;
} else { } 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; const char *capture_target = window_str;
if(strcmp(window_str, "screen-direct") == 0 || strcmp(window_str, "screen-direct-force") == 0) { if(strcmp(window_str, "screen-direct") == 0 || strcmp(window_str, "screen-direct-force") == 0) {
capture_target = "screen"; capture_target = "screen";
@ -1403,6 +1346,7 @@ int main(int argc, char **argv) {
gsr_capture_kms_vaapi_params kms_params; gsr_capture_kms_vaapi_params kms_params;
kms_params.dpy = dpy; kms_params.dpy = dpy;
kms_params.display_to_capture = capture_target; kms_params.display_to_capture = capture_target;
kms_params.gpu_inf = gpu_inf;
capture = gsr_capture_kms_vaapi_create(&kms_params); capture = gsr_capture_kms_vaapi_create(&kms_params);
if(!capture) if(!capture)
return 1; return 1;
@ -1418,7 +1362,7 @@ int main(int argc, char **argv) {
if(!capture) { if(!capture) {
switch(gpu_inf.vendor) { switch(gpu_inf.vendor) {
case GPU_VENDOR_AMD: { case GSR_GPU_VENDOR_AMD: {
gsr_capture_xcomposite_vaapi_params xcomposite_params; gsr_capture_xcomposite_vaapi_params xcomposite_params;
xcomposite_params.window = src_window_id; xcomposite_params.window = src_window_id;
xcomposite_params.follow_focused = follow_focused; xcomposite_params.follow_focused = follow_focused;
@ -1428,7 +1372,7 @@ int main(int argc, char **argv) {
return 1; return 1;
break; break;
} }
case GPU_VENDOR_INTEL: { case GSR_GPU_VENDOR_INTEL: {
gsr_capture_xcomposite_vaapi_params xcomposite_params; gsr_capture_xcomposite_vaapi_params xcomposite_params;
xcomposite_params.window = src_window_id; xcomposite_params.window = src_window_id;
xcomposite_params.follow_focused = follow_focused; xcomposite_params.follow_focused = follow_focused;
@ -1438,7 +1382,7 @@ int main(int argc, char **argv) {
return 1; return 1;
break; break;
} }
case GPU_VENDOR_NVIDIA: { case GSR_GPU_VENDOR_NVIDIA: {
gsr_capture_xcomposite_cuda_params xcomposite_params; gsr_capture_xcomposite_cuda_params xcomposite_params;
xcomposite_params.window = src_window_id; xcomposite_params.window = src_window_id;
xcomposite_params.follow_focused = follow_focused; xcomposite_params.follow_focused = follow_focused;
@ -1522,7 +1466,7 @@ int main(int argc, char **argv) {
const double target_fps = 1.0 / (double)fps; const double target_fps = 1.0 / (double)fps;
if(strcmp(video_codec_to_use, "auto") == 0) { 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); const AVCodec *h264_codec = find_h264_encoder(gpu_inf.vendor);
if(!h264_codec) { if(!h264_codec) {
fprintf(stderr, "Info: using h265 encoder because a codec was not specified and your gpu does not support h264\n"); 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; AVStream *video_stream = nullptr;
std::vector<AudioTrack> audio_tracks; 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) if(replay_buffer_size_secs == -1)
video_stream = create_stream(av_format_context, video_codec_context); video_stream = create_stream(av_format_context, video_codec_context);

137
src/shader.c Normal file
View File

@ -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);
}

View File

@ -1,6 +1,8 @@
#include "../include/utils.h" #include "../include/utils.h"
#include "../include/egl.h"
#include <time.h> #include <time.h>
#include <string.h> #include <string.h>
#include <stdio.h>
double clock_get_monotonic_seconds(void) { double clock_get_monotonic_seconds(void) {
struct timespec ts; 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); for_each_active_monitor_output(display, get_monitor_by_name_callback, &userdata);
return userdata.found_monitor; 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;
}