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