From 6a6bb703bce2d844175950ede4bda5d06dc5a8ae Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 17 Oct 2022 02:55:59 +0200 Subject: wip intel --- src/capture/nvfbc.c | 71 +++- src/capture/xcomposite.c | 517 ------------------------- src/capture/xcomposite_cuda.c | 474 +++++++++++++++++++++++ src/capture/xcomposite_drm.c | 855 ++++++++++++++++++++++++++++++++++++++++++ src/egl.c | 275 ++++++++++++++ src/gl.c | 198 ---------- src/main.cpp | 360 ++++++++++++------ src/window_texture.c | 145 +++---- 8 files changed, 1966 insertions(+), 929 deletions(-) delete mode 100644 src/capture/xcomposite.c create mode 100644 src/capture/xcomposite_cuda.c create mode 100644 src/capture/xcomposite_drm.c create mode 100644 src/egl.c delete mode 100644 src/gl.c (limited to 'src') diff --git a/src/capture/nvfbc.c b/src/capture/nvfbc.c index a470879..17aedf8 100644 --- a/src/capture/nvfbc.c +++ b/src/capture/nvfbc.c @@ -22,6 +22,7 @@ typedef struct { bool fbc_handle_created; gsr_cuda cuda; + bool frame_initialized; } gsr_capture_nvfbc; #if defined(_WIN64) || defined(__LP64__) @@ -52,28 +53,45 @@ static uint32_t get_output_id_from_display_name(NVFBC_RANDR_OUTPUT_INFO *outputs } /* TODO: Test with optimus and open kernel modules */ -static bool driver_supports_direct_capture_cursor() { +static bool get_driver_version(int *major, int *minor) { + *major = 0; + *minor = 0; + FILE *f = fopen("/proc/driver/nvidia/version", "rb"); - if(!f) + if(!f) { + fprintf(stderr, "gsr warning: failed to get nvidia driver version (failed to read /proc/driver/nvidia/version)\n"); return false; + } char buffer[2048]; size_t bytes_read = fread(buffer, 1, sizeof(buffer) - 1, f); buffer[bytes_read] = '\0'; - bool supports_cursor = false; + bool success = false; const char *p = strstr(buffer, "Kernel Module"); if(p) { p += 13; int driver_major_version = 0, driver_minor_version = 0; if(sscanf(p, "%d.%d", &driver_major_version, &driver_minor_version) == 2) { - if(driver_major_version > 515 || (driver_major_version == 515 && driver_minor_version >= 57)) - supports_cursor = true; + *major = driver_major_version; + *minor = driver_minor_version; + success = true; } } + if(!success) + fprintf(stderr, "gsr warning: failed to get nvidia driver version\n"); + fclose(f); - return supports_cursor; + return success; +} + +static bool version_at_least(int major, int minor, int expected_major, int expected_minor) { + return major > expected_major || (major == expected_major && minor >= expected_minor); +} + +static bool version_less_than(int major, int minor, int expected_major, int expected_minor) { + return major < expected_major || (major == expected_major && minor < expected_minor); } static bool gsr_capture_nvfbc_load_library(gsr_capture *cap) { @@ -180,6 +198,30 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap, AVCodecContext *video_codec const bool capture_region = (x > 0 || y > 0 || width > 0 || height > 0); + bool supports_direct_cursor = false; + bool direct_capture = cap_nvfbc->params.direct_capture; + int driver_major_version = 0; + int driver_minor_version = 0; + if(direct_capture && get_driver_version(&driver_major_version, &driver_minor_version)) { + fprintf(stderr, "Info: detected nvidia version: %d.%d\n", driver_major_version, driver_minor_version); + + if(version_at_least(driver_major_version, driver_minor_version, 515, 57) && version_less_than(driver_major_version, driver_minor_version, 520, 56)) { + direct_capture = false; + fprintf(stderr, "Warning: \"screen-direct\" has temporary been disabled as it causes stuttering with driver versions >= 515.57 and < 520.56. Please update your driver if possible. Capturing \"screen\" instead.\n"); + } + + // TODO: + // Cursor capture disabled because moving the cursor doesn't update capture rate to monitor hz and instead captures at 10-30 hz + /* + if(direct_capture) { + if(version_at_least(driver_major_version, driver_minor_version, 515, 57)) + supports_direct_cursor = true; + else + fprintf(stderr, "Info: capturing \"screen-direct\" but driver version appears to be less than 515.57. Disabling capture of cursor. Please update your driver if you want to capture your cursor or record \"screen\" instead.\n"); + } + */ + } + NVFBCSTATUS status; NVFBC_TRACKING_TYPE tracking_type; bool capture_session_created = false; @@ -245,11 +287,11 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap, AVCodecContext *video_codec memset(&create_capture_params, 0, sizeof(create_capture_params)); create_capture_params.dwVersion = NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER; create_capture_params.eCaptureType = NVFBC_CAPTURE_SHARED_CUDA; - create_capture_params.bWithCursor = (!cap_nvfbc->params.direct_capture || driver_supports_direct_capture_cursor()) ? NVFBC_TRUE : NVFBC_FALSE; + create_capture_params.bWithCursor = (!direct_capture || supports_direct_cursor) ? NVFBC_TRUE : NVFBC_FALSE; if(capture_region) create_capture_params.captureBox = (NVFBC_BOX){ x, y, width, height }; create_capture_params.eTrackingType = tracking_type; - create_capture_params.dwSamplingRateMs = 1000u / (uint32_t)cap_nvfbc->params.fps; + create_capture_params.dwSamplingRateMs = 1000u / ((uint32_t)cap_nvfbc->params.fps + 1); create_capture_params.bAllowDirectCapture = cap_nvfbc->params.direct_capture ? NVFBC_TRUE : NVFBC_FALSE; create_capture_params.bPushModel = cap_nvfbc->params.direct_capture ? NVFBC_TRUE : NVFBC_FALSE; if(tracking_type == NVFBC_TRACKING_OUTPUT) @@ -324,6 +366,16 @@ static void gsr_capture_nvfbc_destroy_session(gsr_capture *cap) { cap_nvfbc->nv_fbc_handle = 0; } +static void gsr_capture_nvfbc_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) { + gsr_capture_nvfbc *cap_nvfbc = cap->priv; + if(!cap_nvfbc->frame_initialized && video_codec_context->hw_frames_ctx) { + cap_nvfbc->frame_initialized = true; + (*frame)->hw_frames_ctx = video_codec_context->hw_frames_ctx; + (*frame)->buf[0] = av_buffer_pool_get(((AVHWFramesContext*)video_codec_context->hw_frames_ctx->data)->pool); + (*frame)->extended_data = (*frame)->data; + } +} + static int gsr_capture_nvfbc_capture(gsr_capture *cap, AVFrame *frame) { gsr_capture_nvfbc *cap_nvfbc = cap->priv; @@ -338,6 +390,7 @@ static int gsr_capture_nvfbc_capture(gsr_capture *cap, AVFrame *frame) { grab_params.dwFlags = NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT;/* | NVFBC_TOCUDA_GRAB_FLAGS_FORCE_REFRESH;*/ grab_params.pFrameGrabInfo = &frame_info; grab_params.pCUDADeviceBuffer = &cu_device_ptr; + grab_params.dwTimeoutMs = 0; NVFBCSTATUS status = cap_nvfbc->nv_fbc_function_list.nvFBCToCudaGrabFrame(cap_nvfbc->nv_fbc_handle, &grab_params); if(status != NVFBC_SUCCESS) { @@ -406,7 +459,7 @@ gsr_capture* gsr_capture_nvfbc_create(const gsr_capture_nvfbc_params *params) { *cap = (gsr_capture) { .start = gsr_capture_nvfbc_start, - .tick = NULL, + .tick = gsr_capture_nvfbc_tick, .should_stop = NULL, .capture = gsr_capture_nvfbc_capture, .destroy = gsr_capture_nvfbc_destroy, diff --git a/src/capture/xcomposite.c b/src/capture/xcomposite.c deleted file mode 100644 index 755ac92..0000000 --- a/src/capture/xcomposite.c +++ /dev/null @@ -1,517 +0,0 @@ -#include "../../include/capture/xcomposite.h" -#include "../../include/gl.h" -#include "../../include/cuda.h" -#include "../../include/window_texture.h" -#include "../../include/time.h" -#include -#include -#include -#include -#include - -/* TODO: Proper error checks and cleanups */ - -typedef struct { - gsr_capture_xcomposite_params params; - Display *dpy; - XEvent xev; - bool should_stop; - bool stop_is_error; - bool window_resized; - bool created_hw_frame; - double window_resize_timer; - - vec2i window_size; - vec2i window_pos; - - unsigned int target_texture_id; - vec2i texture_size; - Window composite_window; - WindowTexture window_texture; - - CUgraphicsResource cuda_graphics_resource; - CUarray mapped_array; - - gsr_gl gl; - gsr_cuda cuda; -} gsr_capture_xcomposite; - -static int max_int(int a, int b) { - return a > b ? a : b; -} - -static int min_int(int a, int b) { - return a < b ? a : b; -} - -static void gsr_capture_xcomposite_stop(gsr_capture *cap, AVCodecContext *video_codec_context); - -static Window get_compositor_window(Display *display) { - Window overlay_window = XCompositeGetOverlayWindow(display, DefaultRootWindow(display)); - XCompositeReleaseOverlayWindow(display, DefaultRootWindow(display)); - - Window root_window, parent_window; - Window *children = NULL; - unsigned int num_children = 0; - if(XQueryTree(display, overlay_window, &root_window, &parent_window, &children, &num_children) == 0) - return None; - - Window compositor_window = None; - if(num_children == 1) { - compositor_window = children[0]; - const int screen_width = XWidthOfScreen(DefaultScreenOfDisplay(display)); - const int screen_height = XHeightOfScreen(DefaultScreenOfDisplay(display)); - - XWindowAttributes attr; - if(!XGetWindowAttributes(display, compositor_window, &attr) || attr.width != screen_width || attr.height != screen_height) - compositor_window = None; - } - - if(children) - XFree(children); - - return compositor_window; -} - -/* TODO: check for glx swap control extension string (GLX_EXT_swap_control, etc) */ -static void set_vertical_sync_enabled(Display *display, Window window, gsr_gl *gl, bool enabled) { - int result = 0; - - if(gl->glXSwapIntervalEXT) { - gl->glXSwapIntervalEXT(display, window, enabled ? 1 : 0); - } else if(gl->glXSwapIntervalMESA) { - result = gl->glXSwapIntervalMESA(enabled ? 1 : 0); - } else if(gl->glXSwapIntervalSGI) { - result = gl->glXSwapIntervalSGI(enabled ? 1 : 0); - } else { - static int warned = 0; - if (!warned) { - warned = 1; - fprintf(stderr, "Warning: setting vertical sync not supported\n"); - } - } - - if(result != 0) - fprintf(stderr, "Warning: setting vertical sync failed\n"); -} - -static bool cuda_register_opengl_texture(gsr_capture_xcomposite *cap_xcomp) { - CUresult res; - CUcontext old_ctx; - res = cap_xcomp->cuda.cuCtxPushCurrent_v2(cap_xcomp->cuda.cu_ctx); - res = cap_xcomp->cuda.cuGraphicsGLRegisterImage( - &cap_xcomp->cuda_graphics_resource, cap_xcomp->target_texture_id, GL_TEXTURE_2D, - CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY); - if (res != CUDA_SUCCESS) { - const char *err_str = "unknown"; - cap_xcomp->cuda.cuGetErrorString(res, &err_str); - fprintf(stderr, - "Error: cuGraphicsGLRegisterImage failed, error %s, texture " - "id: %u\n", - err_str, cap_xcomp->target_texture_id); - return false; - } - - /* Get texture */ - res = cap_xcomp->cuda.cuGraphicsResourceSetMapFlags(cap_xcomp->cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY); - res = cap_xcomp->cuda.cuGraphicsMapResources(1, &cap_xcomp->cuda_graphics_resource, 0); - - /* Map texture to cuda array */ - res = cap_xcomp->cuda.cuGraphicsSubResourceGetMappedArray(&cap_xcomp->mapped_array, cap_xcomp->cuda_graphics_resource, 0, 0); - res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - return true; -} - -static bool cuda_create_codec_context(gsr_capture_xcomposite *cap_xcomp, AVCodecContext *video_codec_context) { - CUcontext old_ctx; - cap_xcomp->cuda.cuCtxPushCurrent_v2(cap_xcomp->cuda.cu_ctx); - - AVBufferRef *device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA); - if(!device_ctx) { - fprintf(stderr, "Error: Failed to create hardware device context\n"); - return false; - } - - AVHWDeviceContext *hw_device_context = (AVHWDeviceContext*)device_ctx->data; - AVCUDADeviceContext *cuda_device_context = (AVCUDADeviceContext*)hw_device_context->hwctx; - cuda_device_context->cuda_ctx = cap_xcomp->cuda.cu_ctx; - if(av_hwdevice_ctx_init(device_ctx) < 0) { - fprintf(stderr, "Error: Failed to create hardware device context\n"); - av_buffer_unref(&device_ctx); - cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - return false; - } - - AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx); - if(!frame_context) { - fprintf(stderr, "Error: Failed to create hwframe context\n"); - av_buffer_unref(&device_ctx); - cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - return false; - } - - AVHWFramesContext *hw_frame_context = - (AVHWFramesContext *)frame_context->data; - hw_frame_context->width = video_codec_context->width; - hw_frame_context->height = video_codec_context->height; - hw_frame_context->sw_format = AV_PIX_FMT_0RGB32; - hw_frame_context->format = video_codec_context->pix_fmt; - hw_frame_context->device_ref = device_ctx; - hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data; - - if (av_hwframe_ctx_init(frame_context) < 0) { - fprintf(stderr, "Error: Failed to initialize hardware frame context " - "(note: ffmpeg version needs to be > 4.0)\n"); - av_buffer_unref(&device_ctx); - av_buffer_unref(&frame_context); - cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - return false; - } - - video_codec_context->hw_device_ctx = device_ctx; - video_codec_context->hw_frames_ctx = frame_context; - return true; -} - -static unsigned int gl_create_texture(gsr_capture_xcomposite *cap_xcomp, int width, int height) { - // Generating this second texture is needed because - // cuGraphicsGLRegisterImage cant be used with the texture that is mapped - // directly to the pixmap. - // TODO: Investigate if it's somehow possible to use the pixmap texture - // directly, this should improve performance since only less image copy is - // then needed every frame. - // Ignoring failure for now.. TODO: Show proper error - unsigned int texture_id = 0; - cap_xcomp->gl.glGenTextures(1, &texture_id); - cap_xcomp->gl.glBindTexture(GL_TEXTURE_2D, texture_id); - cap_xcomp->gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); - - cap_xcomp->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - cap_xcomp->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - cap_xcomp->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - cap_xcomp->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - - cap_xcomp->gl.glBindTexture(GL_TEXTURE_2D, 0); - return texture_id; -} - -static int gsr_capture_xcomposite_start(gsr_capture *cap, AVCodecContext *video_codec_context) { - gsr_capture_xcomposite *cap_xcomp = cap->priv; - - XWindowAttributes attr; - if(!XGetWindowAttributes(cap_xcomp->dpy, cap_xcomp->params.window, &attr)) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_start failed: invalid window id: %lu\n", cap_xcomp->params.window); - return -1; - } - - cap_xcomp->window_size.x = max_int(attr.width, 0); - cap_xcomp->window_size.y = max_int(attr.height, 0); - Window c; - XTranslateCoordinates(cap_xcomp->dpy, cap_xcomp->params.window, DefaultRootWindow(cap_xcomp->dpy), 0, 0, &cap_xcomp->window_pos.x, &cap_xcomp->window_pos.y, &c); - - XSelectInput(cap_xcomp->dpy, cap_xcomp->params.window, StructureNotifyMask | ExposureMask); - - if(!gsr_gl_load(&cap_xcomp->gl, cap_xcomp->dpy)) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_start: failed to load opengl\n"); - return -1; - } - - set_vertical_sync_enabled(cap_xcomp->dpy, cap_xcomp->gl.window, &cap_xcomp->gl, false); - if(window_texture_init(&cap_xcomp->window_texture, cap_xcomp->dpy, cap_xcomp->params.window, &cap_xcomp->gl) != 0) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_start: failed get window texture for window %ld\n", cap_xcomp->params.window); - gsr_gl_unload(&cap_xcomp->gl); - return -1; - } - - cap_xcomp->gl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture)); - cap_xcomp->texture_size.x = 0; - cap_xcomp->texture_size.y = 0; - cap_xcomp->gl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x); - cap_xcomp->gl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y); - cap_xcomp->gl.glBindTexture(GL_TEXTURE_2D, 0); - - cap_xcomp->texture_size.x = max_int(2, cap_xcomp->texture_size.x & ~1); - cap_xcomp->texture_size.y = max_int(2, cap_xcomp->texture_size.y & ~1); - - cap_xcomp->target_texture_id = gl_create_texture(cap_xcomp, cap_xcomp->texture_size.x, cap_xcomp->texture_size.y); - if(cap_xcomp->target_texture_id == 0) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_start: failed to create opengl texture\n"); - gsr_capture_xcomposite_stop(cap, video_codec_context); - return -1; - } - - video_codec_context->width = cap_xcomp->texture_size.x; - video_codec_context->height = cap_xcomp->texture_size.y; - - if(!gsr_cuda_load(&cap_xcomp->cuda)) { - gsr_capture_xcomposite_stop(cap, video_codec_context); - return -1; - } - - if(!cuda_create_codec_context(cap_xcomp, video_codec_context)) { - gsr_capture_xcomposite_stop(cap, video_codec_context); - return -1; - } - - if(!cuda_register_opengl_texture(cap_xcomp)) { - gsr_capture_xcomposite_stop(cap, video_codec_context); - return -1; - } - - cap_xcomp->window_resize_timer = clock_get_monotonic_seconds(); - return 0; -} - -static void gsr_capture_xcomposite_stop(gsr_capture *cap, AVCodecContext *video_codec_context) { - gsr_capture_xcomposite *cap_xcomp = cap->priv; - - window_texture_deinit(&cap_xcomp->window_texture); - - if(cap_xcomp->target_texture_id) { - cap_xcomp->gl.glDeleteTextures(1, &cap_xcomp->target_texture_id); - cap_xcomp->target_texture_id = 0; - } - - if(cap_xcomp->composite_window) { - XCompositeUnredirectWindow(cap_xcomp->dpy, cap_xcomp->composite_window, CompositeRedirectAutomatic); - cap_xcomp->composite_window = None; - } - - av_buffer_unref(&video_codec_context->hw_device_ctx); - av_buffer_unref(&video_codec_context->hw_frames_ctx); - - cap_xcomp->cuda.cuGraphicsUnmapResources(1, &cap_xcomp->cuda_graphics_resource, 0); - cap_xcomp->cuda.cuGraphicsUnregisterResource(cap_xcomp->cuda_graphics_resource); - gsr_cuda_unload(&cap_xcomp->cuda); - - gsr_gl_unload(&cap_xcomp->gl); - if(cap_xcomp->dpy) { - XCloseDisplay(cap_xcomp->dpy); - cap_xcomp->dpy = NULL; - } -} - -static void gsr_capture_xcomposite_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) { - gsr_capture_xcomposite *cap_xcomp = cap->priv; - - cap_xcomp->gl.glClear(GL_COLOR_BUFFER_BIT); - - if(!cap_xcomp->created_hw_frame) { - CUcontext old_ctx; - cap_xcomp->cuda.cuCtxPushCurrent_v2(cap_xcomp->cuda.cu_ctx); - - if(av_hwframe_get_buffer(video_codec_context->hw_frames_ctx, *frame, 0) < 0) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_tick: av_hwframe_get_buffer failed\n"); - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = true; - cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - return; - } - - cap_xcomp->created_hw_frame = true; - cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - } - - if(XCheckTypedWindowEvent(cap_xcomp->dpy, cap_xcomp->params.window, DestroyNotify, &cap_xcomp->xev)) { - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = false; - } - - if(XCheckTypedWindowEvent(cap_xcomp->dpy, cap_xcomp->params.window, Expose, &cap_xcomp->xev) && cap_xcomp->xev.xexpose.count == 0) { - cap_xcomp->window_resize_timer = clock_get_monotonic_seconds(); - cap_xcomp->window_resized = true; - } - - if(XCheckTypedWindowEvent(cap_xcomp->dpy, cap_xcomp->params.window, ConfigureNotify, &cap_xcomp->xev) && cap_xcomp->xev.xconfigure.window == cap_xcomp->params.window) { - while(XCheckTypedWindowEvent(cap_xcomp->dpy, cap_xcomp->params.window, ConfigureNotify, &cap_xcomp->xev)) {} - Window c; - XTranslateCoordinates(cap_xcomp->dpy, cap_xcomp->params.window, DefaultRootWindow(cap_xcomp->dpy), 0, 0, &cap_xcomp->xev.xconfigure.x, &cap_xcomp->xev.xconfigure.y, &c); - cap_xcomp->window_pos.x = cap_xcomp->xev.xconfigure.x; - cap_xcomp->window_pos.y = cap_xcomp->xev.xconfigure.y; - - /* Window resize */ - if(cap_xcomp->xev.xconfigure.width != cap_xcomp->window_size.x || cap_xcomp->xev.xconfigure.height != cap_xcomp->window_size.y) { - cap_xcomp->window_size.x = max_int(cap_xcomp->xev.xconfigure.width, 0); - cap_xcomp->window_size.y = max_int(cap_xcomp->xev.xconfigure.height, 0); - cap_xcomp->window_resize_timer = clock_get_monotonic_seconds(); - cap_xcomp->window_resized = true; - } - } - - const double window_resize_timeout = 1.0; // 1 second - if(cap_xcomp->window_resized && clock_get_monotonic_seconds() - cap_xcomp->window_resize_timer >= window_resize_timeout) { - cap_xcomp->window_resized = false; - fprintf(stderr, "Resize window!\n"); - if(window_texture_on_resize(&cap_xcomp->window_texture) != 0) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_tick: window_texture_on_resize failed\n"); - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = true; - return; - } - - cap_xcomp->gl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture)); - cap_xcomp->texture_size.x = 0; - cap_xcomp->texture_size.y = 0; - cap_xcomp->gl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x); - cap_xcomp->gl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y); - cap_xcomp->gl.glBindTexture(GL_TEXTURE_2D, 0); - - cap_xcomp->texture_size.x = min_int(video_codec_context->width, max_int(2, cap_xcomp->texture_size.x & ~1)); - cap_xcomp->texture_size.y = min_int(video_codec_context->height, max_int(2, cap_xcomp->texture_size.y & ~1)); - - cap_xcomp->gl.glBindTexture(GL_TEXTURE_2D, cap_xcomp->target_texture_id); - cap_xcomp->gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, cap_xcomp->texture_size.x, cap_xcomp->texture_size.y, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); - cap_xcomp->gl.glBindTexture(GL_TEXTURE_2D, 0); - - CUcontext old_ctx; - CUresult res = cap_xcomp->cuda.cuCtxPushCurrent_v2(cap_xcomp->cuda.cu_ctx); - - cap_xcomp->cuda.cuGraphicsUnmapResources(1, &cap_xcomp->cuda_graphics_resource, 0); - cap_xcomp->cuda.cuGraphicsUnregisterResource(cap_xcomp->cuda_graphics_resource); - res = cap_xcomp->cuda.cuGraphicsGLRegisterImage(&cap_xcomp->cuda_graphics_resource, cap_xcomp->target_texture_id, GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY); - if (res != CUDA_SUCCESS) { - const char *err_str = "unknown"; - cap_xcomp->cuda.cuGetErrorString(res, &err_str); - fprintf(stderr, "gsr error: gsr_capture_xcomposite_tick: cuGraphicsGLRegisterImage failed, error %s, texture id: %u\n", err_str, cap_xcomp->target_texture_id); - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = true; - res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - return; - } - - res = cap_xcomp->cuda.cuGraphicsResourceSetMapFlags(cap_xcomp->cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY); - res = cap_xcomp->cuda.cuGraphicsMapResources(1, &cap_xcomp->cuda_graphics_resource, 0); - res = cap_xcomp->cuda.cuGraphicsSubResourceGetMappedArray(&cap_xcomp->mapped_array, cap_xcomp->cuda_graphics_resource, 0, 0); - - av_frame_free(frame); - *frame = av_frame_alloc(); - if(!frame) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_tick: failed to allocate frame\n"); - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = true; - res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - return; - } - (*frame)->format = video_codec_context->pix_fmt; - (*frame)->width = video_codec_context->width; - (*frame)->height = video_codec_context->height; - - if(av_hwframe_get_buffer(video_codec_context->hw_frames_ctx, *frame, 0) < 0) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_tick: av_hwframe_get_buffer failed\n"); - cap_xcomp->should_stop = true; - cap_xcomp->stop_is_error = true; - res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - return; - } - - // Make it completely black to clear unused parts - // TODO: cuMemsetD32? - res = cap_xcomp->cuda.cuMemsetD8_v2((CUdeviceptr)(*frame)->data[0], 0, (*frame)->width * (*frame)->height * 4); - res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); - } -} - -static bool gsr_capture_xcomposite_should_stop(gsr_capture *cap, bool *err) { - gsr_capture_xcomposite *cap_xcomp = cap->priv; - if(cap_xcomp->should_stop) { - if(err) - *err = cap_xcomp->stop_is_error; - return true; - } - - if(err) - *err = false; - return false; -} - -static int gsr_capture_xcomposite_capture(gsr_capture *cap, AVFrame *frame) { - gsr_capture_xcomposite *cap_xcomp = cap->priv; - - // TODO: Use a framebuffer instead. glCopyImageSubData requires opengl 4.2 - vec2i source_pos = { 0, 0 }; - vec2i source_size = cap_xcomp->texture_size; - - // Requires opengl 4.2... TODO: Replace with earlier opengl if opengl < 4.2. - cap_xcomp->gl.glCopyImageSubData( - window_texture_get_opengl_texture_id(&cap_xcomp->window_texture), GL_TEXTURE_2D, 0, source_pos.x, source_pos.y, 0, - cap_xcomp->target_texture_id, GL_TEXTURE_2D, 0, 0, 0, 0, - source_size.x, source_size.y, 1); - unsigned int err = cap_xcomp->gl.glGetError(); - if(err != 0) { - static bool error_shown = false; - if(!error_shown) { - error_shown = true; - fprintf(stderr, "Error: glCopyImageSubData failed, gl error: %d\n", err); - } - } - cap_xcomp->gl.glXSwapBuffers(cap_xcomp->dpy, cap_xcomp->gl.window); - // TODO: Remove this copy, which is only possible by using nvenc directly and encoding window_pixmap.target_texture_id - - frame->linesize[0] = frame->width * 4; - - CUDA_MEMCPY2D memcpy_struct; - memcpy_struct.srcXInBytes = 0; - memcpy_struct.srcY = 0; - memcpy_struct.srcMemoryType = CU_MEMORYTYPE_ARRAY; - - memcpy_struct.dstXInBytes = 0; - memcpy_struct.dstY = 0; - memcpy_struct.dstMemoryType = CU_MEMORYTYPE_DEVICE; - - memcpy_struct.srcArray = cap_xcomp->mapped_array; - memcpy_struct.dstDevice = (CUdeviceptr)frame->data[0]; - memcpy_struct.dstPitch = frame->linesize[0]; - memcpy_struct.WidthInBytes = frame->width * 4; - memcpy_struct.Height = frame->height; - cap_xcomp->cuda.cuMemcpy2D_v2(&memcpy_struct); - - return 0; -} - -static void gsr_capture_xcomposite_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) { - gsr_capture_xcomposite_stop(cap, video_codec_context); - if(cap->priv) { - free(cap->priv); - cap->priv = NULL; - } - free(cap); -} - -gsr_capture* gsr_capture_xcomposite_create(const gsr_capture_xcomposite_params *params) { - if(!params) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_create params is NULL\n"); - return NULL; - } - - gsr_capture *cap = calloc(1, sizeof(gsr_capture)); - if(!cap) - return NULL; - - gsr_capture_xcomposite *cap_xcomp = calloc(1, sizeof(gsr_capture_xcomposite)); - if(!cap_xcomp) { - free(cap); - return NULL; - } - - Display *display = XOpenDisplay(NULL); - if(!display) { - fprintf(stderr, "gsr error: gsr_capture_xcomposite_create failed: XOpenDisplay failed\n"); - free(cap); - free(cap_xcomp); - return NULL; - } - - cap_xcomp->dpy = display; - cap_xcomp->params = *params; - - *cap = (gsr_capture) { - .start = gsr_capture_xcomposite_start, - .tick = gsr_capture_xcomposite_tick, - .should_stop = gsr_capture_xcomposite_should_stop, - .capture = gsr_capture_xcomposite_capture, - .destroy = gsr_capture_xcomposite_destroy, - .priv = cap_xcomp - }; - - return cap; -} diff --git a/src/capture/xcomposite_cuda.c b/src/capture/xcomposite_cuda.c new file mode 100644 index 0000000..d6c147b --- /dev/null +++ b/src/capture/xcomposite_cuda.c @@ -0,0 +1,474 @@ +#include "../../include/capture/xcomposite_cuda.h" +#include "../../include/egl.h" +#include "../../include/cuda.h" +#include "../../include/window_texture.h" +#include "../../include/time.h" +#include +#include +#include +#include +#include + +typedef struct { + gsr_capture_xcomposite_cuda_params params; + Display *dpy; + XEvent xev; + bool should_stop; + bool stop_is_error; + bool window_resized; + bool created_hw_frame; + double window_resize_timer; + + vec2i window_size; + vec2i window_pos; + + unsigned int target_texture_id; + vec2i texture_size; + Window composite_window; + WindowTexture window_texture; + + CUgraphicsResource cuda_graphics_resource; + CUarray mapped_array; + + gsr_egl egl; + gsr_cuda cuda; +} gsr_capture_xcomposite_cuda; + +static int max_int(int a, int b) { + return a > b ? a : b; +} + +static int min_int(int a, int b) { + return a < b ? a : b; +} + +static void gsr_capture_xcomposite_cuda_stop(gsr_capture *cap, AVCodecContext *video_codec_context); + +static bool cuda_register_opengl_texture(gsr_capture_xcomposite_cuda *cap_xcomp) { + CUresult res; + CUcontext old_ctx; + res = cap_xcomp->cuda.cuCtxPushCurrent_v2(cap_xcomp->cuda.cu_ctx); + res = cap_xcomp->cuda.cuGraphicsGLRegisterImage( + &cap_xcomp->cuda_graphics_resource, cap_xcomp->target_texture_id, GL_TEXTURE_2D, + CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY); + if (res != CUDA_SUCCESS) { + const char *err_str = "unknown"; + cap_xcomp->cuda.cuGetErrorString(res, &err_str); + fprintf(stderr, + "Error: cuGraphicsGLRegisterImage failed, error %s, texture " + "id: %u\n", + err_str, cap_xcomp->target_texture_id); + return false; + } + + res = cap_xcomp->cuda.cuGraphicsResourceSetMapFlags(cap_xcomp->cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY); + res = cap_xcomp->cuda.cuGraphicsMapResources(1, &cap_xcomp->cuda_graphics_resource, 0); + + res = cap_xcomp->cuda.cuGraphicsSubResourceGetMappedArray(&cap_xcomp->mapped_array, cap_xcomp->cuda_graphics_resource, 0, 0); + res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); + return true; +} + +static bool cuda_create_codec_context(gsr_capture_xcomposite_cuda *cap_xcomp, AVCodecContext *video_codec_context) { + CUcontext old_ctx; + cap_xcomp->cuda.cuCtxPushCurrent_v2(cap_xcomp->cuda.cu_ctx); + + AVBufferRef *device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA); + if(!device_ctx) { + fprintf(stderr, "Error: Failed to create hardware device context\n"); + cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); + return false; + } + + AVHWDeviceContext *hw_device_context = (AVHWDeviceContext*)device_ctx->data; + AVCUDADeviceContext *cuda_device_context = (AVCUDADeviceContext*)hw_device_context->hwctx; + cuda_device_context->cuda_ctx = cap_xcomp->cuda.cu_ctx; + if(av_hwdevice_ctx_init(device_ctx) < 0) { + fprintf(stderr, "Error: Failed to create hardware device context\n"); + av_buffer_unref(&device_ctx); + cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); + return false; + } + + AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx); + if(!frame_context) { + fprintf(stderr, "Error: Failed to create hwframe context\n"); + av_buffer_unref(&device_ctx); + cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); + return false; + } + + AVHWFramesContext *hw_frame_context = + (AVHWFramesContext *)frame_context->data; + hw_frame_context->width = video_codec_context->width; + hw_frame_context->height = video_codec_context->height; + hw_frame_context->sw_format = AV_PIX_FMT_0RGB32; + hw_frame_context->format = video_codec_context->pix_fmt; + hw_frame_context->device_ref = device_ctx; + hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data; + + if (av_hwframe_ctx_init(frame_context) < 0) { + fprintf(stderr, "Error: Failed to initialize hardware frame context " + "(note: ffmpeg version needs to be > 4.0)\n"); + av_buffer_unref(&device_ctx); + av_buffer_unref(&frame_context); + cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); + return false; + } + + video_codec_context->hw_device_ctx = device_ctx; + video_codec_context->hw_frames_ctx = frame_context; + return true; +} + +static unsigned int gl_create_texture(gsr_capture_xcomposite_cuda *cap_xcomp, int width, int height) { + // Generating this second texture is needed because + // cuGraphicsGLRegisterImage cant be used with the texture that is mapped + // directly to the pixmap. + // TODO: Investigate if it's somehow possible to use the pixmap texture + // directly, this should improve performance since only less image copy is + // then needed every frame. + // Ignoring failure for now.. TODO: Show proper error + unsigned int texture_id = 0; + cap_xcomp->egl.glGenTextures(1, &texture_id); + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, texture_id); + cap_xcomp->egl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + + cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0); + return texture_id; +} + +static int gsr_capture_xcomposite_cuda_start(gsr_capture *cap, AVCodecContext *video_codec_context) { + gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv; + + XWindowAttributes attr; + if(!XGetWindowAttributes(cap_xcomp->dpy, cap_xcomp->params.window, &attr)) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start failed: invalid window id: %lu\n", cap_xcomp->params.window); + return -1; + } + + cap_xcomp->window_size.x = max_int(attr.width, 0); + cap_xcomp->window_size.y = max_int(attr.height, 0); + Window c; + XTranslateCoordinates(cap_xcomp->dpy, cap_xcomp->params.window, DefaultRootWindow(cap_xcomp->dpy), 0, 0, &cap_xcomp->window_pos.x, &cap_xcomp->window_pos.y, &c); + + XSelectInput(cap_xcomp->dpy, cap_xcomp->params.window, StructureNotifyMask | ExposureMask); + + if(!gsr_egl_load(&cap_xcomp->egl, cap_xcomp->dpy)) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start: failed to load opengl\n"); + return -1; + } + + cap_xcomp->egl.eglSwapInterval(cap_xcomp->egl.egl_display, 0); + // TODO: Fallback to composite window + if(window_texture_init(&cap_xcomp->window_texture, cap_xcomp->dpy, cap_xcomp->params.window, &cap_xcomp->egl) != 0) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start: failed get window texture for window %ld\n", cap_xcomp->params.window); + gsr_egl_unload(&cap_xcomp->egl); + return -1; + } + + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture)); + cap_xcomp->texture_size.x = 0; + cap_xcomp->texture_size.y = 0; + cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x); + cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y); + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0); + + cap_xcomp->texture_size.x = max_int(2, cap_xcomp->texture_size.x & ~1); + cap_xcomp->texture_size.y = max_int(2, cap_xcomp->texture_size.y & ~1); + + cap_xcomp->target_texture_id = gl_create_texture(cap_xcomp, cap_xcomp->texture_size.x, cap_xcomp->texture_size.y); + if(cap_xcomp->target_texture_id == 0) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start: failed to create opengl texture\n"); + gsr_capture_xcomposite_cuda_stop(cap, video_codec_context); + return -1; + } + + video_codec_context->width = cap_xcomp->texture_size.x; + video_codec_context->height = cap_xcomp->texture_size.y; + + if(!gsr_cuda_load(&cap_xcomp->cuda)) { + gsr_capture_xcomposite_cuda_stop(cap, video_codec_context); + return -1; + } + + if(!cuda_create_codec_context(cap_xcomp, video_codec_context)) { + gsr_capture_xcomposite_cuda_stop(cap, video_codec_context); + return -1; + } + + if(!cuda_register_opengl_texture(cap_xcomp)) { + gsr_capture_xcomposite_cuda_stop(cap, video_codec_context); + return -1; + } + + cap_xcomp->window_resize_timer = clock_get_monotonic_seconds(); + return 0; +} + +static void gsr_capture_xcomposite_cuda_stop(gsr_capture *cap, AVCodecContext *video_codec_context) { + gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv; + + window_texture_deinit(&cap_xcomp->window_texture); + + if(cap_xcomp->target_texture_id) { + cap_xcomp->egl.glDeleteTextures(1, &cap_xcomp->target_texture_id); + cap_xcomp->target_texture_id = 0; + } + + if(cap_xcomp->composite_window) { + XCompositeUnredirectWindow(cap_xcomp->dpy, cap_xcomp->composite_window, CompositeRedirectAutomatic); + cap_xcomp->composite_window = None; + } + + av_buffer_unref(&video_codec_context->hw_device_ctx); + av_buffer_unref(&video_codec_context->hw_frames_ctx); + + if(cap_xcomp->cuda.cu_ctx) { + CUcontext old_ctx; + cap_xcomp->cuda.cuCtxPushCurrent_v2(cap_xcomp->cuda.cu_ctx); + + cap_xcomp->cuda.cuGraphicsUnmapResources(1, &cap_xcomp->cuda_graphics_resource, 0); + cap_xcomp->cuda.cuGraphicsUnregisterResource(cap_xcomp->cuda_graphics_resource); + cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); + } + gsr_cuda_unload(&cap_xcomp->cuda); + + gsr_egl_unload(&cap_xcomp->egl); + if(cap_xcomp->dpy) { + // TODO: Why is this crashing? + XCloseDisplay(cap_xcomp->dpy); + cap_xcomp->dpy = NULL; + } +} + +static void gsr_capture_xcomposite_cuda_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) { + gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv; + + cap_xcomp->egl.glClear(GL_COLOR_BUFFER_BIT); + + if(!cap_xcomp->created_hw_frame) { + cap_xcomp->created_hw_frame = true; + CUcontext old_ctx; + cap_xcomp->cuda.cuCtxPushCurrent_v2(cap_xcomp->cuda.cu_ctx); + + if(av_hwframe_get_buffer(video_codec_context->hw_frames_ctx, *frame, 0) < 0) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick: av_hwframe_get_buffer failed\n"); + cap_xcomp->should_stop = true; + cap_xcomp->stop_is_error = true; + cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); + return; + } + + cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); + } + + if(XCheckTypedWindowEvent(cap_xcomp->dpy, cap_xcomp->params.window, DestroyNotify, &cap_xcomp->xev)) { + cap_xcomp->should_stop = true; + cap_xcomp->stop_is_error = false; + } + + if(XCheckTypedWindowEvent(cap_xcomp->dpy, cap_xcomp->params.window, Expose, &cap_xcomp->xev) && cap_xcomp->xev.xexpose.count == 0) { + cap_xcomp->window_resize_timer = clock_get_monotonic_seconds(); + cap_xcomp->window_resized = true; + } + + if(XCheckTypedWindowEvent(cap_xcomp->dpy, cap_xcomp->params.window, ConfigureNotify, &cap_xcomp->xev) && cap_xcomp->xev.xconfigure.window == cap_xcomp->params.window) { + while(XCheckTypedWindowEvent(cap_xcomp->dpy, cap_xcomp->params.window, ConfigureNotify, &cap_xcomp->xev)) {} + Window c; + XTranslateCoordinates(cap_xcomp->dpy, cap_xcomp->params.window, DefaultRootWindow(cap_xcomp->dpy), 0, 0, &cap_xcomp->xev.xconfigure.x, &cap_xcomp->xev.xconfigure.y, &c); + cap_xcomp->window_pos.x = cap_xcomp->xev.xconfigure.x; + cap_xcomp->window_pos.y = cap_xcomp->xev.xconfigure.y; + + /* Window resize */ + if(cap_xcomp->xev.xconfigure.width != cap_xcomp->window_size.x || cap_xcomp->xev.xconfigure.height != cap_xcomp->window_size.y) { + cap_xcomp->window_size.x = max_int(cap_xcomp->xev.xconfigure.width, 0); + cap_xcomp->window_size.y = max_int(cap_xcomp->xev.xconfigure.height, 0); + cap_xcomp->window_resize_timer = clock_get_monotonic_seconds(); + cap_xcomp->window_resized = true; + } + } + + const double window_resize_timeout = 1.0; // 1 second + if(cap_xcomp->window_resized && clock_get_monotonic_seconds() - cap_xcomp->window_resize_timer >= window_resize_timeout) { + cap_xcomp->window_resized = false; + fprintf(stderr, "Resize window!\n"); + if(window_texture_on_resize(&cap_xcomp->window_texture) != 0) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick: window_texture_on_resize failed\n"); + cap_xcomp->should_stop = true; + cap_xcomp->stop_is_error = true; + return; + } + + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture)); + cap_xcomp->texture_size.x = 0; + cap_xcomp->texture_size.y = 0; + cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x); + cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y); + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0); + + cap_xcomp->texture_size.x = min_int(video_codec_context->width, max_int(2, cap_xcomp->texture_size.x & ~1)); + cap_xcomp->texture_size.y = min_int(video_codec_context->height, max_int(2, cap_xcomp->texture_size.y & ~1)); + + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, cap_xcomp->target_texture_id); + cap_xcomp->egl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, cap_xcomp->texture_size.x, cap_xcomp->texture_size.y, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0); + + CUcontext old_ctx; + CUresult res = cap_xcomp->cuda.cuCtxPushCurrent_v2(cap_xcomp->cuda.cu_ctx); + + cap_xcomp->cuda.cuGraphicsUnmapResources(1, &cap_xcomp->cuda_graphics_resource, 0); + cap_xcomp->cuda.cuGraphicsUnregisterResource(cap_xcomp->cuda_graphics_resource); + res = cap_xcomp->cuda.cuGraphicsGLRegisterImage(&cap_xcomp->cuda_graphics_resource, cap_xcomp->target_texture_id, GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY); + if (res != CUDA_SUCCESS) { + const char *err_str = "unknown"; + cap_xcomp->cuda.cuGetErrorString(res, &err_str); + fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick: cuGraphicsGLRegisterImage failed, error %s, texture id: %u\n", err_str, cap_xcomp->target_texture_id); + cap_xcomp->should_stop = true; + cap_xcomp->stop_is_error = true; + res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); + return; + } + + res = cap_xcomp->cuda.cuGraphicsResourceSetMapFlags(cap_xcomp->cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY); + res = cap_xcomp->cuda.cuGraphicsMapResources(1, &cap_xcomp->cuda_graphics_resource, 0); + res = cap_xcomp->cuda.cuGraphicsSubResourceGetMappedArray(&cap_xcomp->mapped_array, cap_xcomp->cuda_graphics_resource, 0, 0); + + av_frame_free(frame); + *frame = av_frame_alloc(); + if(!frame) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick: failed to allocate frame\n"); + cap_xcomp->should_stop = true; + cap_xcomp->stop_is_error = true; + res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); + return; + } + (*frame)->format = video_codec_context->pix_fmt; + (*frame)->width = video_codec_context->width; + (*frame)->height = video_codec_context->height; + (*frame)->color_range = AVCOL_RANGE_JPEG; + + if(av_hwframe_get_buffer(video_codec_context->hw_frames_ctx, *frame, 0) < 0) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick: av_hwframe_get_buffer failed\n"); + cap_xcomp->should_stop = true; + cap_xcomp->stop_is_error = true; + res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); + return; + } + + // Make it completely black to clear unused parts + // TODO: cuMemsetD32? + res = cap_xcomp->cuda.cuMemsetD8_v2((CUdeviceptr)(*frame)->data[0], 0, (*frame)->width * (*frame)->height * 4); + res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx); + } +} + +static bool gsr_capture_xcomposite_cuda_should_stop(gsr_capture *cap, bool *err) { + gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv; + if(cap_xcomp->should_stop) { + if(err) + *err = cap_xcomp->stop_is_error; + return true; + } + + if(err) + *err = false; + return false; +} + +static int gsr_capture_xcomposite_cuda_capture(gsr_capture *cap, AVFrame *frame) { + gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv; + + // TODO: Use a framebuffer instead. glCopyImageSubData requires opengl 4.2 + vec2i source_pos = { 0, 0 }; + vec2i source_size = cap_xcomp->texture_size; + + // Requires opengl 4.2... TODO: Replace with earlier opengl if opengl < 4.2. + cap_xcomp->egl.glCopyImageSubData( + window_texture_get_opengl_texture_id(&cap_xcomp->window_texture), GL_TEXTURE_2D, 0, source_pos.x, source_pos.y, 0, + cap_xcomp->target_texture_id, GL_TEXTURE_2D, 0, 0, 0, 0, + source_size.x, source_size.y, 1); + unsigned int err = cap_xcomp->egl.glGetError(); + if(err != 0) { + static bool error_shown = false; + if(!error_shown) { + error_shown = true; + fprintf(stderr, "Error: glCopyImageSubData failed, gl error: %d\n", err); + } + } + cap_xcomp->egl.eglSwapBuffers(cap_xcomp->egl.egl_display, cap_xcomp->egl.egl_surface); + // TODO: Remove this copy, which is only possible by using nvenc directly and encoding window_pixmap.target_texture_id + + frame->linesize[0] = frame->width * 4; + + CUDA_MEMCPY2D memcpy_struct; + memcpy_struct.srcXInBytes = 0; + memcpy_struct.srcY = 0; + memcpy_struct.srcMemoryType = CU_MEMORYTYPE_ARRAY; + + memcpy_struct.dstXInBytes = 0; + memcpy_struct.dstY = 0; + memcpy_struct.dstMemoryType = CU_MEMORYTYPE_DEVICE; + + memcpy_struct.srcArray = cap_xcomp->mapped_array; + memcpy_struct.dstDevice = (CUdeviceptr)frame->data[0]; + memcpy_struct.dstPitch = frame->linesize[0]; + memcpy_struct.WidthInBytes = frame->width * 4; + memcpy_struct.Height = frame->height; + cap_xcomp->cuda.cuMemcpy2D_v2(&memcpy_struct); + + return 0; +} + +static void gsr_capture_xcomposite_cuda_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) { + gsr_capture_xcomposite_cuda_stop(cap, video_codec_context); + if(cap->priv) { + free(cap->priv); + cap->priv = NULL; + } + free(cap); +} + +gsr_capture* gsr_capture_xcomposite_cuda_create(const gsr_capture_xcomposite_cuda_params *params) { + if(!params) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_create params is NULL\n"); + return NULL; + } + + gsr_capture *cap = calloc(1, sizeof(gsr_capture)); + if(!cap) + return NULL; + + gsr_capture_xcomposite_cuda *cap_xcomp = calloc(1, sizeof(gsr_capture_xcomposite_cuda)); + if(!cap_xcomp) { + free(cap); + return NULL; + } + + Display *display = XOpenDisplay(NULL); + if(!display) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_create failed: XOpenDisplay failed\n"); + free(cap); + free(cap_xcomp); + return NULL; + } + + cap_xcomp->dpy = display; + cap_xcomp->params = *params; + + *cap = (gsr_capture) { + .start = gsr_capture_xcomposite_cuda_start, + .tick = gsr_capture_xcomposite_cuda_tick, + .should_stop = gsr_capture_xcomposite_cuda_should_stop, + .capture = gsr_capture_xcomposite_cuda_capture, + .destroy = gsr_capture_xcomposite_cuda_destroy, + .priv = cap_xcomp + }; + + return cap; +} diff --git a/src/capture/xcomposite_drm.c b/src/capture/xcomposite_drm.c new file mode 100644 index 0000000..65dec1e --- /dev/null +++ b/src/capture/xcomposite_drm.c @@ -0,0 +1,855 @@ +#include "../../include/capture/xcomposite_drm.h" +#include "../../include/egl.h" +#include "../../include/window_texture.h" +#include "../../include/time.h" +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include +/* TODO: Proper error checks and cleanups */ + +typedef struct { + gsr_capture_xcomposite_drm_params params; + Display *dpy; + XEvent xev; + bool created_hw_frame; + + vec2i window_pos; + vec2i window_size; + vec2i texture_size; + double window_resize_timer; + + WindowTexture window_texture; + + gsr_egl egl; + + int fourcc; + int num_planes; + uint64_t modifiers; + int dmabuf_fd; + int32_t stride; + int32_t offset; + + unsigned int target_texture_id; + + unsigned int FramebufferName; + unsigned int quad_VertexArrayID; + unsigned int quad_vertexbuffer; + unsigned int quadVAO; +} gsr_capture_xcomposite_drm; + +static int max_int(int a, int b) { + return a > b ? a : b; +} + +static int min_int(int a, int b) { + return a < b ? a : b; +} + +static bool drm_create_codec_context(gsr_capture_xcomposite_drm *cap_xcomp, AVCodecContext *video_codec_context) { + // TODO: "/dev/dri/card0" + AVBufferRef *device_ctx; + if(av_hwdevice_ctx_create(&device_ctx, AV_HWDEVICE_TYPE_VAAPI, "/dev/dri/card0", NULL, 0) < 0) { + fprintf(stderr, "Error: Failed to create hardware device context\n"); + return false; + } + + AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx); + if(!frame_context) { + fprintf(stderr, "Error: Failed to create hwframe context\n"); + av_buffer_unref(&device_ctx); + return false; + } + + AVHWFramesContext *hw_frame_context = + (AVHWFramesContext *)frame_context->data; + hw_frame_context->width = video_codec_context->width; + hw_frame_context->height = video_codec_context->height; + hw_frame_context->sw_format = AV_PIX_FMT_YUV420P;//AV_PIX_FMT_0RGB32;//AV_PIX_FMT_YUV420P;//AV_PIX_FMT_0RGB32;//AV_PIX_FMT_NV12; + hw_frame_context->format = video_codec_context->pix_fmt; + hw_frame_context->device_ref = device_ctx; + hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data; + + if (av_hwframe_ctx_init(frame_context) < 0) { + fprintf(stderr, "Error: Failed to initialize hardware frame context " + "(note: ffmpeg version needs to be > 4.0)\n"); + av_buffer_unref(&device_ctx); + av_buffer_unref(&frame_context); + return false; + } + + video_codec_context->hw_device_ctx = device_ctx; // TODO: av_buffer_ref? and in more places + video_codec_context->hw_frames_ctx = frame_context; + return true; +} + +#define EGL_SURFACE_TYPE 0x3033 +#define EGL_WINDOW_BIT 0x0004 +#define EGL_PIXMAP_BIT 0x0002 +#define EGL_BIND_TO_TEXTURE_RGB 0x3039 +#define EGL_TRUE 1 +#define EGL_RED_SIZE 0x3024 +#define EGL_GREEN_SIZE 0x3023 +#define EGL_BLUE_SIZE 0x3022 +#define EGL_ALPHA_SIZE 0x3021 +#define EGL_TEXTURE_FORMAT 0x3080 +#define EGL_TEXTURE_RGB 0x305D +#define EGL_TEXTURE_TARGET 0x3081 +#define EGL_TEXTURE_2D 0x305F +#define EGL_GL_TEXTURE_2D 0x30B1 + +#define GL_RGBA 0x1908 + +static unsigned int gl_create_texture(gsr_capture_xcomposite_drm *cap_xcomp, int width, int height) { + // Generating this second texture is needed because + // cuGraphicsGLRegisterImage cant be used with the texture that is mapped + // directly to the pixmap. + // TODO: Investigate if it's somehow possible to use the pixmap texture + // directly, this should improve performance since only less image copy is + // then needed every frame. + // Ignoring failure for now.. TODO: Show proper error + unsigned int texture_id = 0; + cap_xcomp->egl.glGenTextures(1, &texture_id); + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, texture_id); + cap_xcomp->egl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + + cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0); + return texture_id; +} + +#define GL_COMPILE_STATUS 0x8B81 +#define GL_INFO_LOG_LENGTH 0x8B84 + +unsigned int esLoadShader ( gsr_capture_xcomposite_drm *cap_xcomp, unsigned int type, const char *shaderSrc ) { + unsigned int shader; + int compiled; + + // Create the shader object + shader = cap_xcomp->egl.glCreateShader ( type ); + + if ( shader == 0 ) + return 0; + + // Load the shader source + cap_xcomp->egl.glShaderSource ( shader, 1, &shaderSrc, NULL ); + + // Compile the shader + cap_xcomp->egl.glCompileShader ( shader ); + + // Check the compile status + cap_xcomp->egl.glGetShaderiv ( shader, GL_COMPILE_STATUS, &compiled ); + + if ( !compiled ) + { + int infoLen = 0; + + cap_xcomp->egl.glGetShaderiv ( shader, GL_INFO_LOG_LENGTH, &infoLen ); + + if ( infoLen > 1 ) + { + char* infoLog = malloc (sizeof(char) * infoLen ); + + cap_xcomp->egl.glGetShaderInfoLog ( shader, infoLen, NULL, infoLog ); + fprintf (stderr, "Error compiling shader:\n%s\n", infoLog ); + + free ( infoLog ); + } + + cap_xcomp->egl.glDeleteShader ( shader ); + return 0; + } + + return shader; + +} + +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 + + +// +/// +/// \brief Load a vertex and fragment shader, create a program object, link program. +// Errors output to log. +/// \param vertShaderSrc Vertex shader source code +/// \param fragShaderSrc Fragment shader source code +/// \return A new program object linked with the vertex/fragment shader pair, 0 on failure +// +unsigned int esLoadProgram ( gsr_capture_xcomposite_drm *cap_xcomp, const char *vertShaderSrc, const char *fragShaderSrc ) +{ + unsigned int vertexShader; + unsigned int fragmentShader; + unsigned int programObject; + int linked; + + // Load the vertex/fragment shaders + vertexShader = esLoadShader ( cap_xcomp, GL_VERTEX_SHADER, vertShaderSrc ); + if ( vertexShader == 0 ) + return 0; + + fragmentShader = esLoadShader ( cap_xcomp, GL_FRAGMENT_SHADER, fragShaderSrc ); + if ( fragmentShader == 0 ) + { + cap_xcomp->egl.glDeleteShader( vertexShader ); + return 0; + } + + // Create the program object + programObject = cap_xcomp->egl.glCreateProgram ( ); + + if ( programObject == 0 ) + return 0; + + cap_xcomp->egl.glAttachShader ( programObject, vertexShader ); + cap_xcomp->egl.glAttachShader ( programObject, fragmentShader ); + + // Link the program + cap_xcomp->egl.glLinkProgram ( programObject ); + + // Check the link status + cap_xcomp->egl.glGetProgramiv ( programObject, GL_LINK_STATUS, &linked ); + + if ( !linked ) + { + int infoLen = 0; + + cap_xcomp->egl.glGetProgramiv ( programObject, GL_INFO_LOG_LENGTH, &infoLen ); + + if ( infoLen > 1 ) + { + char* infoLog = malloc (sizeof(char) * infoLen ); + + cap_xcomp->egl.glGetProgramInfoLog ( programObject, infoLen, NULL, infoLog ); + fprintf (stderr, "Error linking program:\n%s\n", infoLog ); + + free ( infoLog ); + } + + cap_xcomp->egl.glDeleteProgram ( programObject ); + return 0; + } + + // Free up no longer needed shader resources + cap_xcomp->egl.glDeleteShader ( vertexShader ); + cap_xcomp->egl.glDeleteShader ( fragmentShader ); + + return programObject; +} + +static unsigned int shader_program = 0; +static unsigned int texID = 0; + +static void LoadShaders(gsr_capture_xcomposite_drm *cap_xcomp) { + char vShaderStr[] = + "#version 300 es \n" + "in vec2 pos; \n" + "in vec2 texcoords; \n" + "out vec2 texcoords_out; \n" + "void main() \n" + "{ \n" + " texcoords_out = texcoords; \n" + " gl_Position = vec4(pos.x, pos.y, 0.0, 1.0); \n" + "} \n"; + +#if 0 + char fShaderStr[] = + "#version 300 es \n" + "precision mediump float; \n" + "in vec2 texcoords_out; \n" + "uniform sampler2D tex; \n" + "out vec4 FragColor; \n" + + + "float imageWidth = 1920.0;\n" + "float imageHeight = 1080.0;\n" + + "float getYPixel(vec2 position) {\n" + " position.y = (position.y * 2.0 / 3.0) + (1.0 / 3.0);\n" + " return texture2D(tex, position).x;\n" + "}\n" +"\n" + "vec2 mapCommon(vec2 position, float planarOffset) {\n" + " planarOffset += (imageWidth * floor(position.y / 2.0)) / 2.0 +\n" + " floor((imageWidth - 1.0 - position.x) / 2.0);\n" + " float x = floor(imageWidth - 1.0 - floor(mod(planarOffset, imageWidth)));\n" + " float y = floor(floor(planarOffset / imageWidth));\n" + " return vec2((x + 0.5) / imageWidth, (y + 0.5) / (1.5 * imageHeight));\n" + "}\n" +"\n" + "vec2 mapU(vec2 position) {\n" + " float planarOffset = (imageWidth * imageHeight) / 4.0;\n" + " return mapCommon(position, planarOffset);\n" + "}\n" +"\n" + "vec2 mapV(vec2 position) {\n" + " return mapCommon(position, 0.0);\n" + "}\n" + + "void main() \n" + "{ \n" + + "vec2 pixelPosition = vec2(floor(imageWidth * texcoords_out.x),\n" + " floor(imageHeight * texcoords_out.y));\n" + "pixelPosition -= vec2(0.5, 0.5);\n" +"\n" + "float yChannel = getYPixel(texcoords_out);\n" + "float uChannel = texture2D(tex, mapU(pixelPosition)).x;\n" + "float vChannel = texture2D(tex, mapV(pixelPosition)).x;\n" + "vec4 channels = vec4(yChannel, uChannel, vChannel, 1.0);\n" + "mat4 conversion = mat4(1.0, 0.0, 1.402, -0.701,\n" + " 1.0, -0.344, -0.714, 0.529,\n" + " 1.0, 1.772, 0.0, -0.886,\n" + " 0, 0, 0, 0);\n" + "vec3 rgb = (channels * conversion).xyz;\n" + + " FragColor = vec4(rgb, 1.0); \n" + "} \n"; +#elif 1 + char fShaderStr[] = + "#version 300 es \n" + "precision mediump float; \n" + "in vec2 texcoords_out; \n" + "uniform sampler2D tex; \n" + "out vec4 FragColor; \n" + "void main() \n" + "{ \n" + " vec3 rgb = texture(tex, texcoords_out).rgb; \n" + " FragColor = vec4(rgb, 1.0); \n" + "} \n"; +#else + char fShaderStr[] = + "#version 300 es \n" + "precision mediump float; \n" + "in vec2 texcoords_out; \n" + "uniform sampler2D tex; \n" + "out vec4 FragColor; \n" + + "vec3 rgb2yuv(vec3 rgb){\n" + " float y = 0.299*rgb.r + 0.587*rgb.g + 0.114*rgb.b;\n" + " return vec3(y, 0.493*(rgb.b-y), 0.877*(rgb.r-y));\n" + "}\n" + + "vec3 yuv2rgb(vec3 yuv){\n" + " float y = yuv.x;\n" + " float u = yuv.y;\n" + " float v = yuv.z;\n" + " \n" + " return vec3(\n" + " y + 1.0/0.877*v,\n" + " y - 0.39393*u - 0.58081*v,\n" + " y + 1.0/0.493*u\n" + " );\n" + "}\n" + + "void main() \n" + "{ \n" + " float s = 0.5;\n" + " vec3 lum = texture(tex, texcoords_out).rgb;\n" + " vec3 chr = texture(tex, floor(texcoords_out*s-.5)/s).rgb;\n" + " vec3 rgb = vec3(rgb2yuv(lum).x, rgb2yuv(chr).yz);\n" + " FragColor = vec4(rgb, 1.0); \n" + "} \n"; +#endif + + shader_program = esLoadProgram(cap_xcomp, vShaderStr, fShaderStr); + if (shader_program == 0) { + fprintf(stderr, "failed to create shader!\n"); + return; + } + + cap_xcomp->egl.glBindAttribLocation(shader_program, 0, "pos"); + cap_xcomp->egl.glBindAttribLocation(shader_program, 1, "texcoords"); + return; +} + +#define GL_FLOAT 0x1406 +#define GL_FALSE 0 +#define GL_TRUE 1 +#define GL_TRIANGLES 0x0004 +#define DRM_FORMAT_MOD_INVALID 72057594037927935 + +static int gsr_capture_xcomposite_drm_start(gsr_capture *cap, AVCodecContext *video_codec_context) { + gsr_capture_xcomposite_drm *cap_xcomp = cap->priv; + + XWindowAttributes attr; + if(!XGetWindowAttributes(cap_xcomp->dpy, cap_xcomp->params.window, &attr)) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_start failed: invalid window id: %lu\n", cap_xcomp->params.window); + return -1; + } + + cap_xcomp->window_size.x = max_int(attr.width, 0); + cap_xcomp->window_size.y = max_int(attr.height, 0); + Window c; + XTranslateCoordinates(cap_xcomp->dpy, cap_xcomp->params.window, DefaultRootWindow(cap_xcomp->dpy), 0, 0, &cap_xcomp->window_pos.x, &cap_xcomp->window_pos.y, &c); + + // TODO: Get select and add these on top of it and then restore at the end. Also do the same in other xcomposite + XSelectInput(cap_xcomp->dpy, cap_xcomp->params.window, StructureNotifyMask | ExposureMask); + + if(!gsr_egl_load(&cap_xcomp->egl, cap_xcomp->dpy)) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_start: failed to load opengl\n"); + return -1; + } + + /* Disable vsync */ + cap_xcomp->egl.eglSwapInterval(cap_xcomp->egl.egl_display, 0); +#if 0 + // TODO: Fallback to composite window + if(window_texture_init(&cap_xcomp->window_texture, cap_xcomp->dpy, cap_xcomp->params.window, &cap_xcomp->gl) != 0) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_start: failed get window texture for window %ld\n", cap_xcomp->params.window); + gsr_egl_unload(&cap_xcomp->egl); + return -1; + } + + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture)); + cap_xcomp->texture_size.x = 0; + cap_xcomp->texture_size.y = 0; + cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x); + cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y); + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0); + + cap_xcomp->texture_size.x = max_int(2, cap_xcomp->texture_size.x & ~1); + cap_xcomp->texture_size.y = max_int(2, cap_xcomp->texture_size.y & ~1); + + cap_xcomp->target_texture_id = gl_create_texture(cap_xcomp, cap_xcomp->texture_size.x, cap_xcomp->texture_size.y); + if(cap_xcomp->target_texture_id == 0) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_start: failed to create opengl texture\n"); + gsr_capture_xcomposite_stop(cap, video_codec_context); + return -1; + } + + video_codec_context->width = cap_xcomp->texture_size.x; + video_codec_context->height = cap_xcomp->texture_size.y; + + cap_xcomp->window_resize_timer = clock_get_monotonic_seconds(); + return 0; +#else + // TODO: Fallback to composite window + if(window_texture_init(&cap_xcomp->window_texture, cap_xcomp->dpy, cap_xcomp->params.window, &cap_xcomp->egl) != 0) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_drm_start: failed get window texture for window %ld\n", cap_xcomp->params.window); + gsr_egl_unload(&cap_xcomp->egl); + return -1; + } + + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture)); + cap_xcomp->texture_size.x = 0; + cap_xcomp->texture_size.y = 0; + cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x); + cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y); + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0); + + #if 1 + cap_xcomp->target_texture_id = gl_create_texture(cap_xcomp, cap_xcomp->texture_size.x, cap_xcomp->texture_size.y); + if(cap_xcomp->target_texture_id == 0) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_drm_start: failed to create opengl texture\n"); + return -1; + } + #else + // TODO: + cap_xcomp->target_texture_id = window_texture_get_opengl_texture_id(&cap_xcomp->window_texture); + #endif + + cap_xcomp->texture_size.x = max_int(2, cap_xcomp->texture_size.x & ~1); + cap_xcomp->texture_size.y = max_int(2, cap_xcomp->texture_size.y & ~1); + + video_codec_context->width = cap_xcomp->texture_size.x; + video_codec_context->height = cap_xcomp->texture_size.y; + + { + EGLImage img = cap_xcomp->egl.eglCreateImage(cap_xcomp->egl.egl_display, cap_xcomp->egl.egl_context, EGL_GL_TEXTURE_2D, (EGLClientBuffer)(uint64_t)cap_xcomp->target_texture_id, NULL); + if(!img) { + fprintf(stderr, "eglCreateImage failed\n"); + return -1; + } + + if(!cap_xcomp->egl.eglExportDMABUFImageQueryMESA(cap_xcomp->egl.egl_display, img, &cap_xcomp->fourcc, &cap_xcomp->num_planes, &cap_xcomp->modifiers) || cap_xcomp->modifiers == DRM_FORMAT_MOD_INVALID) { + fprintf(stderr, "eglExportDMABUFImageQueryMESA failed\n"); + return -1; + } + + if(cap_xcomp->num_planes != 1) { + // TODO: FAIL! + fprintf(stderr, "Blablalba\n"); + return -1; + } + + if(!cap_xcomp->egl.eglExportDMABUFImageMESA(cap_xcomp->egl.egl_display, img, &cap_xcomp->dmabuf_fd, &cap_xcomp->stride, &cap_xcomp->offset)) { + fprintf(stderr, "eglExportDMABUFImageMESA failed\n"); + return -1; + } + + fprintf(stderr, "texture: %u, dmabuf: %d, stride: %d, offset: %d\n", cap_xcomp->target_texture_id, cap_xcomp->dmabuf_fd, cap_xcomp->stride, cap_xcomp->offset); + fprintf(stderr, "fourcc: %d, num planes: %d, modifiers: %zu\n", cap_xcomp->fourcc, cap_xcomp->num_planes, cap_xcomp->modifiers); + } + + cap_xcomp->egl.glGenFramebuffers(1, &cap_xcomp->FramebufferName); + cap_xcomp->egl.glBindFramebuffer(GL_FRAMEBUFFER, cap_xcomp->FramebufferName); + + cap_xcomp->egl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, cap_xcomp->target_texture_id, 0); + + // Set the list of draw buffers. + unsigned int DrawBuffers[1] = {GL_COLOR_ATTACHMENT0}; + cap_xcomp->egl.glDrawBuffers(1, DrawBuffers); // "1" is the size of DrawBuffers + + if(cap_xcomp->egl.glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + fprintf(stderr, "Failed to setup framebuffer\n"); + return -1; + } + + cap_xcomp->egl.glBindFramebuffer(GL_FRAMEBUFFER, 0); + + //cap_xcomp->egl.glGenVertexArrays(1, &cap_xcomp->quad_VertexArrayID); + //cap_xcomp->egl.glBindVertexArray(cap_xcomp->quad_VertexArrayID); + + static const float g_quad_vertex_buffer_data[] = { + -1.0f, -1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, + }; + + //cap_xcomp->egl.glGenBuffers(1, &cap_xcomp->quad_vertexbuffer); + //cap_xcomp->egl.glBindBuffer(GL_ARRAY_BUFFER, cap_xcomp->quad_vertexbuffer); + //cap_xcomp->egl.glBufferData(GL_ARRAY_BUFFER, sizeof(g_quad_vertex_buffer_data), g_quad_vertex_buffer_data, GL_STATIC_DRAW); + + // Create and compile our GLSL program from the shaders + LoadShaders(cap_xcomp); + texID = cap_xcomp->egl.glGetUniformLocation(shader_program, "tex"); + fprintf(stderr, "uniform id: %u\n", texID); + + float vVertices[] = { + -1.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + + -1.0f, 1.0f, 0.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f + }; + + unsigned int quadVBO; + cap_xcomp->egl.glGenVertexArrays(1, &cap_xcomp->quadVAO); + cap_xcomp->egl.glGenBuffers(1, &quadVBO); + cap_xcomp->egl.glBindVertexArray(cap_xcomp->quadVAO); + cap_xcomp->egl.glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + cap_xcomp->egl.glBufferData(GL_ARRAY_BUFFER, sizeof(vVertices), &vVertices, GL_STATIC_DRAW); + + cap_xcomp->egl.glEnableVertexAttribArray(0); + cap_xcomp->egl.glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0); + + cap_xcomp->egl.glEnableVertexAttribArray(1); + cap_xcomp->egl.glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float))); + + cap_xcomp->egl.glBindVertexArray(0); + + //cap_xcomp->egl.glUniform1i(texID, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture)); + + //cap_xcomp->egl.glViewport(0, 0, 1920, 1080); + + //cap_xcomp->egl.glBindBuffer(GL_ARRAY_BUFFER, 0); + //cap_xcomp->egl.glBindVertexArray(0); + + if(!drm_create_codec_context(cap_xcomp, video_codec_context)) { + fprintf(stderr, "failed to create hw codec context\n"); + gsr_egl_unload(&cap_xcomp->egl); + return -1; + } + + fprintf(stderr, "sneed: %u\n", cap_xcomp->FramebufferName); + return 0; +#endif +} + +// TODO: +static void free_desc(void *opaque, uint8_t *data) { + AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor*)data; + int i; + + //for (i = 0; i < desc->nb_objects; i++) + // close(desc->objects[i].fd); + + av_free(desc); +} + + +static void gsr_capture_xcomposite_drm_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) { + gsr_capture_xcomposite_drm *cap_xcomp = cap->priv; + + if(!cap_xcomp->created_hw_frame) { + cap_xcomp->created_hw_frame = true; + + /*if(av_hwframe_get_buffer(video_codec_context->hw_frames_ctx, *frame, 0) < 0) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_drm_tick: av_hwframe_get_buffer failed\n"); + return; + }*/ + + AVDRMFrameDescriptor *desc = av_malloc(sizeof(AVDRMFrameDescriptor)); + if(!desc) { + fprintf(stderr, "poop\n"); + return; + } + + fprintf(stderr, "tick fd: %d\n", cap_xcomp->dmabuf_fd); + + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, cap_xcomp->target_texture_id); + int xx = 0; + int yy = 0; + cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &xx); + cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &yy); + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0); + + *desc = (AVDRMFrameDescriptor) { + .nb_objects = 1, + .objects[0] = { + .fd = cap_xcomp->dmabuf_fd, + .size = yy * cap_xcomp->stride, + .format_modifier = cap_xcomp->modifiers, + }, + .nb_layers = 1, + .layers[0] = { + .format = cap_xcomp->fourcc, // DRM_FORMAT_NV12 + .nb_planes = 1, //cap_xcomp->num_planes, // TODO: Ensure this is 1, otherwise ffmpeg cant handle it in av_hwframe_map + .planes[0] = { + .object_index = 0, + .offset = cap_xcomp->offset, + .pitch = cap_xcomp->stride, + }, + }, + }; + + #if 0 + AVBufferRef *device_ctx; + if(av_hwdevice_ctx_create(&device_ctx, AV_HWDEVICE_TYPE_DRM, "/dev/dri/card0", NULL, 0) < 0) { + fprintf(stderr, "Error: Failed to create hardware device context\n"); + return; + } + + AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx); + if(!frame_context) { + fprintf(stderr, "Error: Failed to create hwframe context\n"); + av_buffer_unref(&device_ctx); + return; + } + + AVHWFramesContext *hw_frame_context = + (AVHWFramesContext *)frame_context->data; + hw_frame_context->width = video_codec_context->width; + hw_frame_context->height = video_codec_context->height; + hw_frame_context->sw_format = AV_PIX_FMT_0RGB32; + hw_frame_context->format = AV_PIX_FMT_DRM_PRIME; + hw_frame_context->device_ref = device_ctx; + hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data; + + if (av_hwframe_ctx_init(frame_context) < 0) { + fprintf(stderr, "Error: Failed to initialize hardware frame context " + "(note: ffmpeg version needs to be > 4.0)\n"); + av_buffer_unref(&device_ctx); + av_buffer_unref(&frame_context); + return; + } + #endif + + av_frame_free(frame); + *frame = av_frame_alloc(); + if(!frame) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_tick: failed to allocate frame\n"); + return; + } + (*frame)->format = video_codec_context->pix_fmt; + (*frame)->width = video_codec_context->width; + (*frame)->height = video_codec_context->height; + (*frame)->color_range = AVCOL_RANGE_JPEG; + + int res = av_hwframe_get_buffer(video_codec_context->hw_frames_ctx, *frame, 0); + if(res < 0) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_tick: av_hwframe_get_buffer failed 1: %d\n", res); + return; + } + + AVFrame *src_frame = av_frame_alloc(); + assert(src_frame); + src_frame->format = AV_PIX_FMT_DRM_PRIME; + src_frame->width = video_codec_context->width; + src_frame->height = video_codec_context->height; + src_frame->color_range = AVCOL_RANGE_JPEG; + + src_frame->buf[0] = av_buffer_create((uint8_t*)desc, sizeof(*desc), + &free_desc, video_codec_context, 0); + if (!src_frame->buf[0]) { + fprintf(stderr, "failed to create buffer!\n"); + return; + } + + src_frame->data[0] = (uint8_t*)desc; + src_frame->extended_data = src_frame->data; + src_frame->format = AV_PIX_FMT_DRM_PRIME; + + res = av_hwframe_map(*frame, src_frame, AV_HWFRAME_MAP_DIRECT); + if(res < 0) { + fprintf(stderr, "av_hwframe_map failed: %d\n", res); + } + } + + cap_xcomp->egl.glClear(GL_COLOR_BUFFER_BIT); +} + +static bool gsr_capture_xcomposite_drm_should_stop(gsr_capture *cap, bool *err) { + return false; +} + +#define GL_FLOAT 0x1406 +#define GL_FALSE 0 +#define GL_TRUE 1 +#define GL_TRIANGLES 0x0004 + +void FBO_2_PPM_file(gsr_capture_xcomposite_drm *cap_xcomp, int output_width, int output_height) +{ + FILE *output_image; + + /// READ THE PIXELS VALUES from FBO AND SAVE TO A .PPM FILE + int i, j, k; + unsigned char *pixels = (unsigned char*)malloc(output_width*output_height*3); + + unsigned int err = cap_xcomp->egl.glGetError(); + fprintf(stderr, "opengl err 1: %u\n", err); + + /// READ THE CONTENT FROM THE FBO + cap_xcomp->egl.glReadBuffer(GL_COLOR_ATTACHMENT0); + + err = cap_xcomp->egl.glGetError(); + fprintf(stderr, "opengl err 2: %u\n", err); + + cap_xcomp->egl.glReadPixels(0, 0, output_width, output_height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + err = cap_xcomp->egl.glGetError(); + fprintf(stderr, "opengl err 3: %u\n", err); + + output_image = fopen("output.ppm", "wb"); + fprintf(output_image,"P3\n"); + fprintf(output_image,"# Created by Ricao\n"); + fprintf(output_image,"%d %d\n",output_width,output_height); + fprintf(output_image,"255\n"); + + k = 0; + for(i=0; ipriv; + vec2i source_size = cap_xcomp->texture_size; + + #if 1 + // Requires opengl 4.2... TODO: Replace with earlier opengl if opengl < 4.2. + cap_xcomp->egl.glCopyImageSubData( + window_texture_get_opengl_texture_id(&cap_xcomp->window_texture), GL_TEXTURE_2D, 0, 0, 0, 0, + cap_xcomp->target_texture_id, GL_TEXTURE_2D, 0, 0, 0, 0, + source_size.x, source_size.y, 1); + unsigned int err = cap_xcomp->egl.glGetError(); + if(err != 0) { + static bool error_shown = false; + if(!error_shown) { + error_shown = true; + fprintf(stderr, "Error: glCopyImageSubData failed, gl error: %d\n", err); + } + } + #elif 0 + cap_xcomp->egl.glBindFramebuffer(GL_FRAMEBUFFER, cap_xcomp->FramebufferName); + cap_xcomp->egl.glViewport(0, 0, 1920, 1080); + //cap_xcomp->egl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + cap_xcomp->egl.glClear(GL_COLOR_BUFFER_BIT); + + cap_xcomp->egl.glUseProgram(shader_program); + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture)); + cap_xcomp->egl.glBindVertexArray(cap_xcomp->quadVAO); + cap_xcomp->egl.glDrawArrays(GL_TRIANGLES, 0, 6); + cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0); + + static int counter = 0; + ++counter; + static bool image_saved = false; + if(!image_saved && counter == 5) { + image_saved = true; + FBO_2_PPM_file(cap_xcomp, 1920, 1080); + fprintf(stderr, "saved image!\n"); + } + + cap_xcomp->egl.glBindVertexArray(0); + cap_xcomp->egl.glUseProgram(0); + cap_xcomp->egl.glBindFramebuffer(GL_FRAMEBUFFER, 0); + #endif + cap_xcomp->egl.eglSwapBuffers(cap_xcomp->egl.egl_display, cap_xcomp->egl.egl_surface); + + return 0; +} + +static void gsr_capture_xcomposite_drm_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) { + if(cap->priv) { + free(cap->priv); + cap->priv = NULL; + } + free(cap); +} + +gsr_capture* gsr_capture_xcomposite_drm_create(const gsr_capture_xcomposite_drm_params *params) { + if(!params) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_drm_create params is NULL\n"); + return NULL; + } + + gsr_capture *cap = calloc(1, sizeof(gsr_capture)); + if(!cap) + return NULL; + + gsr_capture_xcomposite_drm *cap_xcomp = calloc(1, sizeof(gsr_capture_xcomposite_drm)); + if(!cap_xcomp) { + free(cap); + return NULL; + } + + Display *display = XOpenDisplay(NULL); + if(!display) { + fprintf(stderr, "gsr error: gsr_capture_xcomposite_drm_create failed: XOpenDisplay failed\n"); + free(cap); + free(cap_xcomp); + return NULL; + } + + cap_xcomp->dpy = display; + cap_xcomp->params = *params; + + *cap = (gsr_capture) { + .start = gsr_capture_xcomposite_drm_start, + .tick = gsr_capture_xcomposite_drm_tick, + .should_stop = gsr_capture_xcomposite_drm_should_stop, + .capture = gsr_capture_xcomposite_drm_capture, + .destroy = gsr_capture_xcomposite_drm_destroy, + .priv = cap_xcomp + }; + + return cap; +} diff --git a/src/egl.c b/src/egl.c new file mode 100644 index 0000000..05819c2 --- /dev/null +++ b/src/egl.c @@ -0,0 +1,275 @@ +#include "../include/egl.h" +#include "../include/library_loader.h" +#include + +static bool gsr_egl_create_window(gsr_egl *self) { + EGLDisplay egl_display = NULL; + EGLConfig ecfg; + int32_t num_config; + EGLSurface egl_surface; + EGLContext egl_context; + + int32_t attr[] = { + EGL_BUFFER_SIZE, 24, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + int32_t ctxattr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + // TODO: Is there a way to remove the need to create a window? + Window window = XCreateWindow(self->dpy, DefaultRootWindow(self->dpy), 0, 0, 1, 1, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL); + + if(!window) { + fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to create gl window\n"); + goto fail; + } + + egl_display = self->eglGetDisplay(self->dpy); + if(!egl_display) { + fprintf(stderr, "gsr error: gsr_egl_create_window failed: eglGetDisplay failed\n"); + return false; + } + + if(!self->eglInitialize(egl_display, NULL, NULL)) { + fprintf(stderr, "gsr error: gsr_egl_create_window failed: eglInitialize failed\n"); + return false; + } + + // TODO: Cleanup ecfg? + if (!self->eglChooseConfig( egl_display, attr, &ecfg, 1, &num_config ) ) { + //cerr << "Failed to choose config (eglError: " << eglGetError() << ")" << endl; + return false; + } + + if ( num_config != 1 ) { + //cerr << "Didn't get exactly one config, but " << num_config << endl; + return false; + } + + egl_surface = self->eglCreateWindowSurface ( egl_display, ecfg, (EGLNativeWindowType)window, NULL ); + if ( !egl_surface ) { + //cerr << "Unable to create EGL surface (eglError: " << eglGetError() << ")" << endl; + return false; + } + + //// egl-contexts collect all state descriptions needed required for operation + egl_context = self->eglCreateContext ( egl_display, ecfg, NULL, ctxattr ); + if ( !egl_context ) { + //cerr << "Unable to create EGL context (eglError: " << eglGetError() << ")" << endl; + return false; + } + + //// associate the egl-context with the egl-surface + self->eglMakeCurrent( egl_display, egl_surface, egl_surface, egl_context ); + + self->egl_display = egl_display; + self->egl_surface = egl_surface; + self->egl_context = egl_context; + self->window = window; + return true; + + fail: + // TODO: + /* + if(window) + XDestroyWindow(self->dpy, window); + if(colormap) + XFreeColormap(self->dpy, colormap); + if(gl_context) + self->glXDestroyContext(self->dpy, gl_context); + if(visual_info) + XFree(visual_info); + XFree(fbconfigs); + */ + return False; +} + +static bool gsr_egl_load_egl(gsr_egl *self, void *library) { + dlsym_assign required_dlsym[] = { + { (void**)&self->eglGetDisplay, "eglGetDisplay" }, + { (void**)&self->eglInitialize, "eglInitialize" }, + { (void**)&self->eglChooseConfig, "eglChooseConfig" }, + { (void**)&self->eglCreateWindowSurface, "eglCreateWindowSurface" }, + { (void**)&self->eglCreateContext, "eglCreateContext" }, + { (void**)&self->eglMakeCurrent, "eglMakeCurrent" }, + { (void**)&self->eglCreatePixmapSurface, "eglCreatePixmapSurface" }, + { (void**)&self->eglCreateImage, "eglCreateImage" }, // TODO: eglCreateImageKHR + { (void**)&self->eglBindTexImage, "eglBindTexImage" }, + { (void**)&self->eglSwapInterval, "eglSwapInterval" }, + { (void**)&self->eglSwapBuffers, "eglSwapBuffers" }, + { (void**)&self->eglGetProcAddress, "eglGetProcAddress" }, + + { NULL, NULL } + }; + + if(!dlsym_load_list(library, required_dlsym)) { + fprintf(stderr, "gsr error: gsr_egl_load failed: missing required symbols in libEGL.so.1\n"); + return false; + } + + return true; +} + +static bool gsr_egl_mesa_load_egl(gsr_egl *self) { + self->eglExportDMABUFImageQueryMESA = self->eglGetProcAddress("eglExportDMABUFImageQueryMESA"); + self->eglExportDMABUFImageMESA = self->eglGetProcAddress("eglExportDMABUFImageMESA"); + self->glEGLImageTargetTexture2DOES = self->eglGetProcAddress("glEGLImageTargetTexture2DOES"); + + if(!self->eglExportDMABUFImageQueryMESA) { + fprintf(stderr, "could not find eglExportDMABUFImageQueryMESA\n"); + return false; + } + + if(!self->eglExportDMABUFImageMESA) { + fprintf(stderr, "could not find eglExportDMABUFImageMESA\n"); + return false; + } + + if(!self->glEGLImageTargetTexture2DOES) { + fprintf(stderr, "could not find glEGLImageTargetTexture2DOES\n"); + return false; + } + + return true; +} + +static bool gsr_egl_load_gl(gsr_egl *self, void *library) { + dlsym_assign required_dlsym[] = { + { (void**)&self->glGetError, "glGetError" }, + { (void**)&self->glGetString, "glGetString" }, + { (void**)&self->glClear, "glClear" }, + { (void**)&self->glClearColor, "glClearColor" }, + { (void**)&self->glGenTextures, "glGenTextures" }, + { (void**)&self->glDeleteTextures, "glDeleteTextures" }, + { (void**)&self->glBindTexture, "glBindTexture" }, + { (void**)&self->glTexParameteri, "glTexParameteri" }, + { (void**)&self->glGetTexLevelParameteriv, "glGetTexLevelParameteriv" }, + { (void**)&self->glTexImage2D, "glTexImage2D" }, + { (void**)&self->glCopyImageSubData, "glCopyImageSubData" }, + { (void**)&self->glGenFramebuffers, "glGenFramebuffers" }, + { (void**)&self->glBindFramebuffer, "glBindFramebuffer" }, + { (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->glGetUniformLocation, "glGetUniformLocation" }, + { (void**)&self->glGenVertexArrays, "glGenVertexArrays" }, + { (void**)&self->glBindVertexArray, "glBindVertexArray" }, + { (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->glGetShaderSource, "glGetShaderSource" }, + { (void**)&self->glDeleteProgram, "glDeleteProgram" }, + { (void**)&self->glDeleteShader, "glDeleteShader" }, + { (void**)&self->glGetProgramiv, "glGetProgramiv" }, + { (void**)&self->glVertexAttribPointer, "glVertexAttribPointer" }, + { (void**)&self->glEnableVertexAttribArray, "glEnableVertexAttribArray" }, + { (void**)&self->glDrawArrays, "glDrawArrays" }, + { (void**)&self->glReadBuffer, "glReadBuffer" }, + { (void**)&self->glReadPixels, "glReadPixels" }, + + { NULL, NULL } + }; + + if(!dlsym_load_list(library, required_dlsym)) { + fprintf(stderr, "gsr error: gsr_egl_load failed: missing required symbols in libGL.so.1\n"); + return false; + } + + return true; +} + +bool gsr_egl_load(gsr_egl *self, Display *dpy) { + memset(self, 0, sizeof(gsr_egl)); + self->dpy = dpy; + + dlerror(); /* clear */ + void *egl_lib = dlopen("libEGL.so.1", RTLD_LAZY); + if(!egl_lib) { + fprintf(stderr, "gsr error: gsr_egl_load: failed to load libEGL.so.1, error: %s\n", dlerror()); + return false; + } + + void *gl_lib = dlopen("libGL.so.1", RTLD_LAZY); + if(!egl_lib) { + fprintf(stderr, "gsr error: gsr_egl_load: failed to load libGL.so.1, error: %s\n", dlerror()); + dlclose(egl_lib); + memset(self, 0, sizeof(gsr_egl)); + return false; + } + + if(!gsr_egl_load_egl(self, egl_lib)) { + dlclose(egl_lib); + dlclose(gl_lib); + memset(self, 0, sizeof(gsr_egl)); + return false; + } + + if(!gsr_egl_load_gl(self, gl_lib)) { + dlclose(egl_lib); + dlclose(gl_lib); + memset(self, 0, sizeof(gsr_egl)); + return false; + } + + if(!gsr_egl_mesa_load_egl(self)) { + dlclose(egl_lib); + dlclose(gl_lib); + memset(self, 0, sizeof(gsr_egl)); + return false; + } + + if(!gsr_egl_create_window(self)) { + dlclose(egl_lib); + dlclose(gl_lib); + memset(self, 0, sizeof(gsr_egl)); + return false; + } + + self->egl_library = egl_lib; + self->gl_library = gl_lib; + return true; +} + +bool gsr_egl_make_context_current(gsr_egl *self) { + // TODO: + return true; + //return self->glXMakeContextCurrent(self->dpy, self->window, self->window, self->gl_context); +} + +void gsr_egl_unload(gsr_egl *self) { + // TODO: Cleanup of egl resources + + if(self->window) { + XDestroyWindow(self->dpy, self->window); + self->window = None; + } + + if(self->egl_library) { + dlclose(self->egl_library); + self->egl_library = NULL; + } + + if(self->gl_library) { + dlclose(self->gl_library); + self->gl_library = NULL; + } + + memset(self, 0, sizeof(gsr_egl)); +} diff --git a/src/gl.c b/src/gl.c deleted file mode 100644 index 2f471aa..0000000 --- a/src/gl.c +++ /dev/null @@ -1,198 +0,0 @@ -#include "../include/gl.h" -#include "../include/library_loader.h" -#include - -static bool gsr_gl_create_window(gsr_gl *self) { - const int attr[] = { - GLX_RENDER_TYPE, GLX_RGBA_BIT, - GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, - GLX_DOUBLEBUFFER, True, - GLX_RED_SIZE, 8, - GLX_GREEN_SIZE, 8, - GLX_BLUE_SIZE, 8, - GLX_ALPHA_SIZE, 8, - GLX_DEPTH_SIZE, 0, - None - }; - - GLXFBConfig *fbconfigs = NULL; - XVisualInfo *visual_info = NULL; - GLXFBConfig fbconfig = NULL; - Colormap colormap = None; - GLXContext gl_context = NULL; - Window window = None; - - int numfbconfigs = 0; - fbconfigs = self->glXChooseFBConfig(self->dpy, DefaultScreen(self->dpy), attr, &numfbconfigs); - for(int i = 0; i < numfbconfigs; i++) { - visual_info = self->glXGetVisualFromFBConfig(self->dpy, fbconfigs[i]); - if(!visual_info) - continue; - - fbconfig = fbconfigs[i]; - break; - } - - if(!visual_info) { - fprintf(stderr, "gsr error: gsr_gl_create_window failed: no appropriate visual found\n"); - XFree(fbconfigs); - return false; - } - - /* TODO: Core profile? GLX_CONTEXT_CORE_PROFILE_BIT_ARB. */ - /* TODO: Remove need for 4.2 when copy texture function has been removed. */ - int context_attribs[] = { - GLX_CONTEXT_MAJOR_VERSION_ARB, 4, - GLX_CONTEXT_MINOR_VERSION_ARB, 2, - GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, - None - }; - - gl_context = self->glXCreateContextAttribsARB(self->dpy, fbconfig, NULL, True, context_attribs); - if(!gl_context) { - fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to create gl context\n"); - goto fail; - } - - colormap = XCreateColormap(self->dpy, DefaultRootWindow(self->dpy), visual_info->visual, AllocNone); - if(!colormap) { - fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to create x11 colormap\n"); - goto fail; - } - - XSetWindowAttributes window_attr; - window_attr.colormap = colormap; - - // TODO: Is there a way to remove the need to create a window? - window = XCreateWindow(self->dpy, DefaultRootWindow(self->dpy), 0, 0, 1, 1, 0, visual_info->depth, InputOutput, visual_info->visual, CWColormap, &window_attr); - - if(!window) { - fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to create gl window\n"); - goto fail; - } - - if(!self->glXMakeContextCurrent(self->dpy, window, window, gl_context)) { - fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to make gl context current\n"); - goto fail; - } - - self->fbconfigs = fbconfigs; - self->visual_info = visual_info; - self->colormap = colormap; - self->gl_context = gl_context; - self->window = window; - return true; - - fail: - if(window) - XDestroyWindow(self->dpy, window); - if(colormap) - XFreeColormap(self->dpy, colormap); - if(gl_context) - self->glXDestroyContext(self->dpy, gl_context); - if(visual_info) - XFree(visual_info); - XFree(fbconfigs); - return False; -} - -bool gsr_gl_load(gsr_gl *self, Display *dpy) { - memset(self, 0, sizeof(gsr_gl)); - self->dpy = dpy; - - dlerror(); /* clear */ - void *lib = dlopen("libGL.so.1", RTLD_LAZY); - if(!lib) { - fprintf(stderr, "gsr error: gsr_gl_load: failed to load libGL.so.1, error: %s\n", dlerror()); - return false; - } - - dlsym_assign optional_dlsym[] = { - { (void**)&self->glClearTexImage, "glClearTexImage" }, - { (void**)&self->glXSwapIntervalEXT, "glXSwapIntervalEXT" }, - { (void**)&self->glXSwapIntervalMESA, "glXSwapIntervalMESA" }, - { (void**)&self->glXSwapIntervalSGI, "glXSwapIntervalSGI" }, - - { NULL, NULL } - }; - - dlsym_load_list_optional(lib, optional_dlsym); - - dlsym_assign required_dlsym[] = { - { (void**)&self->glXCreatePixmap, "glXCreatePixmap" }, - { (void**)&self->glXDestroyPixmap, "glXDestroyPixmap" }, - { (void**)&self->glXBindTexImageEXT, "glXBindTexImageEXT" }, - { (void**)&self->glXReleaseTexImageEXT, "glXReleaseTexImageEXT" }, - { (void**)&self->glXChooseFBConfig, "glXChooseFBConfig" }, - { (void**)&self->glXGetVisualFromFBConfig, "glXGetVisualFromFBConfig" }, - { (void**)&self->glXCreateContextAttribsARB, "glXCreateContextAttribsARB" }, - { (void**)&self->glXMakeContextCurrent, "glXMakeContextCurrent" }, - { (void**)&self->glXDestroyContext, "glXDestroyContext" }, - { (void**)&self->glXSwapBuffers, "glXSwapBuffers" }, - - { (void**)&self->glGetError, "glGetError" }, - { (void**)&self->glGetString, "glGetString" }, - { (void**)&self->glClear, "glClear" }, - { (void**)&self->glGenTextures, "glGenTextures" }, - { (void**)&self->glDeleteTextures, "glDeleteTextures" }, - { (void**)&self->glBindTexture, "glBindTexture" }, - { (void**)&self->glTexParameteri, "glTexParameteri" }, - { (void**)&self->glGetTexLevelParameteriv, "glGetTexLevelParameteriv" }, - { (void**)&self->glTexImage2D, "glTexImage2D" }, - { (void**)&self->glCopyImageSubData, "glCopyImageSubData" }, - - { NULL, NULL } - }; - - if(!dlsym_load_list(lib, required_dlsym)) { - fprintf(stderr, "gsr error: gsr_gl_load failed: missing required symbols in libGL.so.1\n"); - dlclose(lib); - memset(self, 0, sizeof(gsr_gl)); - return false; - } - - if(!gsr_gl_create_window(self)) { - dlclose(lib); - memset(self, 0, sizeof(gsr_gl)); - return false; - } - - self->library = lib; - return true; -} - -bool gsr_gl_make_context_current(gsr_gl *self) { - return self->glXMakeContextCurrent(self->dpy, self->window, self->window, self->gl_context); -} - -void gsr_gl_unload(gsr_gl *self) { - if(self->window) { - XDestroyWindow(self->dpy, self->window); - self->window = None; - } - - if(self->colormap) { - XFreeColormap(self->dpy, self->colormap); - self->colormap = None; - } - - if(self->gl_context) { - self->glXDestroyContext(self->dpy, self->gl_context); - self->gl_context = NULL; - } - - if(self->visual_info) { - XFree(self->visual_info); - self->visual_info = NULL; - } - - if(self->fbconfigs) { - XFree(self->fbconfigs); - self->fbconfigs = NULL; - } - - if(self->library) { - dlclose(self->library); - memset(self, 0, sizeof(gsr_gl)); - } -} diff --git a/src/main.cpp b/src/main.cpp index a227f12..bffa93b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,8 @@ extern "C" { #include "../include/capture/nvfbc.h" -#include "../include/capture/xcomposite.h" -#include "../include/gl.h" +#include "../include/capture/xcomposite_cuda.h" +#include "../include/capture/xcomposite_drm.h" +#include "../include/egl.h" #include "../include/time.h" } @@ -157,7 +158,10 @@ static void receive_frames(AVCodecContext *av_codec_context, int stream_index, A av_packet.stream_index = stream_index; av_packet.pts = av_packet.dts = frame->pts; - std::lock_guard lock(write_output_mutex); + if(frame->flags & AV_FRAME_FLAG_DISCARD) + av_packet.flags |= AV_PKT_FLAG_DISCARD; + + std::lock_guard lock(write_output_mutex); if(replay_buffer_size_secs != -1) { double time_now = clock_get_monotonic_seconds(); double replay_time_elapsed = time_now - replay_start_time; @@ -170,15 +174,16 @@ static void receive_frames(AVCodecContext *av_codec_context, int stream_index, A frame_data_queue.pop_front(); frames_erased = true; } + av_packet_unref(&av_packet); } else { av_packet_rescale_ts(&av_packet, av_codec_context->time_base, stream->time_base); av_packet.stream_index = stream->index; - int ret = av_write_frame(av_format_context, &av_packet); + // TODO: Is av_interleaved_write_frame needed? + int ret = av_interleaved_write_frame(av_format_context, &av_packet); if(ret < 0) { fprintf(stderr, "Error: Failed to write frame index %d to muxer, reason: %s (%d)\n", av_packet.stream_index, av_error_to_string(ret), ret); } } - av_packet_unref(&av_packet); } else if (res == AVERROR(EAGAIN)) { // we have no packet // fprintf(stderr, "No packet!\n"); av_packet_unref(&av_packet); @@ -195,7 +200,7 @@ static void receive_frames(AVCodecContext *av_codec_context, int stream_index, A } } -static AVCodecContext* create_audio_codec_context(AVFormatContext *av_format_context, int fps) { +static AVCodecContext* create_audio_codec_context(int fps) { const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_AAC); if (!codec) { fprintf( @@ -223,28 +228,12 @@ static AVCodecContext* create_audio_codec_context(AVFormatContext *av_format_con codec_context->time_base.den = codec_context->sample_rate; codec_context->framerate.num = fps; codec_context->framerate.den = 1; - - av_format_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; return codec_context; } -static const AVCodec* find_h264_encoder() { - const AVCodec *codec = avcodec_find_encoder_by_name("h264_nvenc"); - if(!codec) - codec = avcodec_find_encoder_by_name("nvenc_h264"); - return codec; -} - -static const AVCodec* find_h265_encoder() { - const AVCodec *codec = avcodec_find_encoder_by_name("hevc_nvenc"); - if(!codec) - codec = avcodec_find_encoder_by_name("nvenc_hevc"); - return codec; -} - -static AVCodecContext *create_video_codec_context(AVFormatContext *av_format_context, +static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, VideoQuality video_quality, int fps, const AVCodec *codec, bool is_livestream) { @@ -275,7 +264,7 @@ static AVCodecContext *create_video_codec_context(AVFormatContext *av_format_con codec_context->gop_size = fps * 2; } codec_context->max_b_frames = 0; - codec_context->pix_fmt = AV_PIX_FMT_CUDA; + codec_context->pix_fmt = pix_fmt; codec_context->color_range = AVCOL_RANGE_JPEG; if(codec->id == AV_CODEC_ID_HEVC) codec_context->codec_tag = MKTAG('h', 'v', 'c', '1'); @@ -305,9 +294,6 @@ static AVCodecContext *create_video_codec_context(AVFormatContext *av_format_con if (codec_context->codec_id == AV_CODEC_ID_MPEG1VIDEO) codec_context->mb_decision = 2; - if(codec_context->codec_id == AV_CODEC_ID_H264) - codec_context->profile = FF_PROFILE_H264_HIGH; - // stream->time_base = codec_context->time_base; // codec_context->ticks_per_frame = 30; //av_opt_set(codec_context->priv_data, "tune", "hq", 0); @@ -331,12 +317,76 @@ static AVCodecContext *create_video_codec_context(AVFormatContext *av_format_con //codec_context->rc_min_rate = codec_context->bit_rate; //codec_context->rc_buffer_size = codec_context->bit_rate / 10; - av_format_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; return codec_context; } +#if 0 +static const AVCodec* find_h264_encoder() { + const AVCodec *codec = avcodec_find_encoder_by_name("h264_vaapi"); + if(!codec) + codec = avcodec_find_encoder_by_name("vaapi_h264"); + return codec; +} + +static const AVCodec* find_h265_encoder() { + const AVCodec *codec = avcodec_find_encoder_by_name("hevc_vaapi"); + if(!codec) + codec = avcodec_find_encoder_by_name("vaapi_hevc"); + return codec; +} +#else +static const AVCodec* find_h264_encoder() { + const AVCodec *codec = avcodec_find_encoder_by_name("h264_nvenc"); + if(!codec) + codec = avcodec_find_encoder_by_name("nvenc_h264"); + + static bool checked = false; + if(!checked) { + checked = true; + // 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(AV_PIX_FMT_YUV420P, VideoQuality::VERY_HIGH, 60, codec, false); + codec_context->width = 1920; + codec_context->height = 1080; + if(codec_context) { + if (avcodec_open2(codec_context, codec_context->codec, NULL) < 0) { + avcodec_free_context(&codec_context); + return nullptr; + } + avcodec_free_context(&codec_context); + } + } + return codec; +} + +static const AVCodec* find_h265_encoder() { + const AVCodec *codec = avcodec_find_encoder_by_name("hevc_nvenc"); + if(!codec) + codec = avcodec_find_encoder_by_name("nvenc_hevc"); + + if(!codec) + return nullptr; + + static bool checked = false; + if(!checked) { + checked = true; + // 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(AV_PIX_FMT_YUV420P, VideoQuality::VERY_HIGH, 60, codec, false); + codec_context->width = 1920; + codec_context->height = 1080; + if(codec_context) { + if (avcodec_open2(codec_context, codec_context->codec, NULL) < 0) { + avcodec_free_context(&codec_context); + return nullptr; + } + avcodec_free_context(&codec_context); + } + } + return codec; +} +#endif + static AVFrame* open_audio(AVCodecContext *audio_codec_context) { int ret; ret = avcodec_open2(audio_codec_context, audio_codec_context->codec, nullptr); @@ -371,15 +421,15 @@ static AVFrame* open_audio(AVCodecContext *audio_codec_context) { static void open_video(AVCodecContext *codec_context, VideoQuality video_quality, bool very_old_gpu) { bool supports_p4 = false; - bool supports_p7 = false; + bool supports_p6 = false; const AVOption *opt = nullptr; while((opt = av_opt_next(codec_context->priv_data, opt))) { if(opt->type == AV_OPT_TYPE_CONST) { if(strcmp(opt->name, "p4") == 0) supports_p4 = true; - else if(strcmp(opt->name, "p7") == 0) - supports_p7 = true; + else if(strcmp(opt->name, "p6") == 0) + supports_p6 = true; } } @@ -416,7 +466,7 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality } } - if(!supports_p4 && !supports_p7) + if(!supports_p4 && !supports_p6) fprintf(stderr, "Info: your ffmpeg version is outdated. It's recommended that you use the flatpak version of gpu-screen-recorder version instead, which you can find at https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder\n"); //if(is_livestream) { @@ -429,10 +479,11 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality // with pretty good performance but you now have to choose p1-p7, which are gpu agnostic and on // older gpus p5-p7 slow the gpu down to a crawl... // "hq" is now just an alias for p7 in ffmpeg :( + // TODO: Temporary disable because of stuttering? if(very_old_gpu) av_dict_set(&options, "preset", supports_p4 ? "p4" : "medium", 0); else - av_dict_set(&options, "preset", supports_p7 ? "p7" : "slow", 0); + av_dict_set(&options, "preset", supports_p6 ? "p6" : "slow", 0); av_dict_set(&options, "tune", "hq", 0); av_dict_set(&options, "rc", "constqp", 0); @@ -448,11 +499,11 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality } static void usage() { - fprintf(stderr, "usage: gpu-screen-recorder -w -c -f [-a ...] [-q ] [-r ] [-o ]\n"); + fprintf(stderr, "usage: gpu-screen-recorder -w [-c ] -f [-a ...] [-q ] [-r ] [-o ]\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -w Window to record or a display, \"screen\" or \"screen-direct\". The display is the display name in xrandr and if \"screen\" or \"screen-direct\" is selected then all displays are recorded and they are recorded in h265 (aka hevc)." "\"screen-direct\" skips one texture copy for fullscreen applications so it may lead to better performance and it works with VRR monitors when recording fullscreen application but may break some applications, such as mpv in fullscreen mode. Recording a display requires a gpu with NvFBC support.\n"); - fprintf(stderr, " -c Container format for output file, for example mp4, or flv.\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. 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, " -f Framerate to record at.\n"); fprintf(stderr, " -a Audio device to record from (pulse audio device). Can be specified multiple times. Each time this is specified a new audio track is added for the specified audio device. A name can be given to the audio input device by prefixing the audio input with /, for example \"dummy/alsa_output.pci-0000_00_1b.0.analog-stereo.monitor\". Optional, no audio track is added by default.\n"); fprintf(stderr, " -q Video quality. Should be either 'medium', 'high', 'very_high' or 'ultra'. 'high' is the recommended option when live streaming or when you have a slower harddrive. Optional, set to 'very_high' be default.\n"); @@ -547,7 +598,7 @@ static std::future save_replay_thread; static std::vector save_replay_packets; static std::string save_replay_output_filepath; -static void save_replay_async(AVCodecContext *video_codec_context, int video_stream_index, std::vector &audio_tracks, const std::deque &frame_data_queue, bool frames_erased, std::string output_dir, std::string container_format, std::mutex &write_output_mutex) { +static void save_replay_async(AVCodecContext *video_codec_context, int video_stream_index, std::vector &audio_tracks, const std::deque &frame_data_queue, bool frames_erased, std::string output_dir, const char *container_format, const std::string &file_extension, std::mutex &write_output_mutex) { if(save_replay_thread.valid()) return; @@ -590,11 +641,10 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str } } - save_replay_output_filepath = output_dir + "/Replay_" + get_date_str() + "." + container_format; + save_replay_output_filepath = output_dir + "/Replay_" + get_date_str() + "." + file_extension; save_replay_thread = std::async(std::launch::async, [video_stream_index, container_format, start_index, video_pts_offset, audio_pts_offset, video_codec_context, &audio_tracks]() mutable { AVFormatContext *av_format_context; - // The output format is automatically guessed from the file extension - avformat_alloc_output_context2(&av_format_context, nullptr, container_format.c_str(), nullptr); + avformat_alloc_output_context2(&av_format_context, nullptr, container_format, nullptr); av_format_context->flags |= AVFMT_FLAG_GENPTS; av_format_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; @@ -643,7 +693,7 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str av_packet.stream_index = stream->index; av_packet_rescale_ts(&av_packet, codec_context->time_base, stream->time_base); - int ret = av_write_frame(av_format_context, &av_packet); + int ret = av_interleaved_write_frame(av_format_context, &av_packet); if(ret < 0) fprintf(stderr, "Error: Failed to write frame index %d to muxer, reason: %s (%d)\n", stream->index, av_error_to_string(ret), ret); } @@ -682,14 +732,68 @@ static bool is_livestream_path(const char *str) { return false; } +typedef enum { + GPU_VENDOR_AMD, + GPU_VENDOR_INTEL, + GPU_VENDOR_NVIDIA +} gpu_vendor; + +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; +} + int main(int argc, char **argv) { signal(SIGINT, int_handler); signal(SIGUSR1, save_replay_handler); + //av_log_set_level(AV_LOG_TRACE); + std::map args = { { "-w", Arg { {}, false, false } }, //{ "-s", Arg { nullptr, true } }, - { "-c", Arg { {}, false, false } }, + { "-c", Arg { {}, true, false } }, { "-f", Arg { {}, false, false } }, //{ "-s", Arg { {}, true, false } }, { "-a", Arg { {}, true, true } }, @@ -807,35 +911,31 @@ int main(int argc, char **argv) { Display *dpy = XOpenDisplay(nullptr); if (!dpy) { fprintf(stderr, "Error: Failed to open display\n"); - return 1; + return 2; } XSetErrorHandler(x11_error_handler); XSetIOErrorHandler(x11_io_error_handler); - gsr_gl gl; - if(!gsr_gl_load(&gl, dpy)) { - fprintf(stderr, "Error: failed to load opengl\n"); - return 1; - } - + gpu_info gpu_inf; bool very_old_gpu = false; - const unsigned char *gl_renderer = gl.glGetString(GL_RENDERER); - if(gl_renderer) { - int gpu_num = 1000; - sscanf((const char*)gl_renderer, "%*s %*s %*s %d", &gpu_num); - if(gpu_num < 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(!gl_get_gpu_info(dpy, &gpu_inf)) + return 2; - gsr_gl_unload(&gl); + if(gpu_inf.vendor == 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; + } const char *window_str = args["-w"].value(); gsr_capture *capture = nullptr; if(contains_non_hex_number(window_str)) { + if(gpu_inf.vendor != GPU_VENDOR_NVIDIA) { + fprintf(stderr, "Error: recording a monitor is only supported on NVIDIA right now\n"); + return 2; + } + const char *capture_target = window_str; bool direct_capture = strcmp(window_str, "screen-direct") == 0; if(direct_capture) { @@ -874,19 +974,45 @@ int main(int argc, char **argv) { usage(); } - gsr_capture_xcomposite_params xcomposite_params; - xcomposite_params.window = src_window_id; - capture = gsr_capture_xcomposite_create(&xcomposite_params); - if(!capture) - return 1; + switch(gpu_inf.vendor) { + case GPU_VENDOR_AMD: { + gsr_capture_xcomposite_drm_params xcomposite_params; + xcomposite_params.window = src_window_id; + capture = gsr_capture_xcomposite_drm_create(&xcomposite_params); + if(!capture) + return 1; + break; + } + case GPU_VENDOR_INTEL: { + gsr_capture_xcomposite_drm_params xcomposite_params; + xcomposite_params.window = src_window_id; + capture = gsr_capture_xcomposite_drm_create(&xcomposite_params); + if(!capture) + return 1; + break; + } + case GPU_VENDOR_NVIDIA: { + gsr_capture_xcomposite_cuda_params xcomposite_params; + xcomposite_params.window = src_window_id; + capture = gsr_capture_xcomposite_cuda_create(&xcomposite_params); + if(!capture) + return 1; + break; + } + } } const char *filename = args["-o"].value(); if(filename) { if(replay_buffer_size_secs != -1) { + if(!container_format) { + fprintf(stderr, "Error: option -c is required when using option -r\n"); + usage(); + } + struct stat buf; if(stat(filename, &buf) == -1 || !S_ISDIR(buf.st_mode)) { - fprintf(stderr, "%s does not exist or is not a directory\n", filename); + fprintf(stderr, "Error: directory \"%s\" does not exist or is not a directory\n", filename); usage(); } } @@ -894,11 +1020,35 @@ int main(int argc, char **argv) { if(replay_buffer_size_secs == -1) { filename = "/dev/stdout"; } else { - fprintf(stderr, "Option -o is required when using option -r\n"); + fprintf(stderr, "Error: Option -o is required when using option -r\n"); + usage(); + } + + if(!container_format) { + fprintf(stderr, "Error: option -c is required when not using option -o\n"); usage(); } } + AVFormatContext *av_format_context; + // The output format is automatically guessed by the file extension + avformat_alloc_output_context2(&av_format_context, nullptr, container_format, filename); + if (!av_format_context) { + fprintf(stderr, "Error: Failed to deduce container format from file extension\n"); + return 1; + } + + av_format_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + av_format_context->flags |= AVFMT_FLAG_GENPTS; + const AVOutputFormat *output_format = av_format_context->oformat; + + std::string file_extension = output_format->extensions; + { + size_t comma_index = file_extension.find(','); + if(comma_index != std::string::npos) + file_extension = file_extension.substr(0, comma_index); + } + const double target_fps = 1.0 / (double)fps; if(strcmp(codec_to_use, "auto") == 0) { @@ -909,6 +1059,8 @@ int main(int argc, char **argv) { // while with h264 the fps doesn't drop. if(!h265_codec) { fprintf(stderr, "Info: using h264 encoder because a codec was not specified and your gpu does not support h265\n"); + codec_to_use = "h264"; + video_codec = VideoCodec::H264; } else if(fps > 60) { fprintf(stderr, "Info: using h264 encoder because a codec was not specified and fps is more than 60\n"); codec_to_use = "h264"; @@ -921,7 +1073,7 @@ int main(int argc, char **argv) { } //bool use_hevc = strcmp(window_str, "screen") == 0 || strcmp(window_str, "screen-direct") == 0; - if(video_codec != VideoCodec::H264 && strcmp(container_format, "flv") == 0) { + if(video_codec != VideoCodec::H264 && strcmp(file_extension.c_str(), "flv") == 0) { video_codec = VideoCodec::H264; fprintf(stderr, "Warning: h265 is not compatible with flv, falling back to h264 instead.\n"); } @@ -941,21 +1093,6 @@ int main(int argc, char **argv) { exit(2); } - // Video start - AVFormatContext *av_format_context; - // The output format is automatically guessed by the file extension - avformat_alloc_output_context2(&av_format_context, nullptr, container_format, - nullptr); - if (!av_format_context) { - fprintf( - stderr, - "Error: Failed to deduce output format from file extension\n"); - return 1; - } - - av_format_context->flags |= AVFMT_FLAG_GENPTS; - const AVOutputFormat *output_format = av_format_context->oformat; - const bool is_livestream = is_livestream_path(filename); // (Some?) livestreaming services require at least one audio track to work. // If not audio is provided then create one silent audio track. @@ -967,7 +1104,7 @@ int main(int argc, char **argv) { AVStream *video_stream = nullptr; std::vector audio_tracks; - AVCodecContext *video_codec_context = create_video_codec_context(av_format_context, quality, fps, video_codec_f, is_livestream); + AVCodecContext *video_codec_context = create_video_codec_context(AV_PIX_FMT_CUDA, quality, fps, video_codec_f, is_livestream); if(replay_buffer_size_secs == -1) video_stream = create_stream(av_format_context, video_codec_context); @@ -982,7 +1119,7 @@ int main(int argc, char **argv) { int audio_stream_index = VIDEO_STREAM_INDEX + 1; for(const AudioInput &audio_input : requested_audio_inputs) { - AVCodecContext *audio_codec_context = create_audio_codec_context(av_format_context, fps); + AVCodecContext *audio_codec_context = create_audio_codec_context(fps); AVStream *audio_stream = nullptr; if(replay_buffer_size_secs == -1) @@ -1028,13 +1165,7 @@ int main(int argc, char **argv) { frame->format = video_codec_context->pix_fmt; frame->width = video_codec_context->width; frame->height = video_codec_context->height; - - if(video_codec_context->hw_frames_ctx) { - // TODO: Unref at the end? - frame->hw_frames_ctx = av_buffer_ref(video_codec_context->hw_frames_ctx); - frame->buf[0] = av_buffer_pool_get(((AVHWFramesContext*)video_codec_context->hw_frames_ctx->data)->pool); - frame->extended_data = frame->data; - } + frame->color_range = AVCOL_RANGE_JPEG; std::mutex write_output_mutex; @@ -1103,15 +1234,21 @@ int main(int argc, char **argv) { break; } - const int64_t num_missing_frames = std::round((this_audio_frame_time - received_audio_time) / target_audio_hz / (int64_t)audio_track.frame->nb_samples); + int64_t num_missing_frames = std::round((this_audio_frame_time - received_audio_time) / target_audio_hz / (int64_t)audio_track.frame->nb_samples); + if(got_audio_data) + num_missing_frames = std::max((int64_t)0, num_missing_frames - 1); + + if(!audio_track.sound_device.handle) + num_missing_frames = std::max((int64_t)1, num_missing_frames); + // Jesus is there a better way to do this? I JUST WANT TO KEEP VIDEO AND AUDIO SYNCED HOLY FUCK I WANT TO KILL MYSELF NOW. // THIS PIECE OF SHIT WANTS EMPTY FRAMES OTHERWISE VIDEO PLAYS TOO FAST TO KEEP UP WITH AUDIO OR THE AUDIO PLAYS TOO EARLY. // BUT WE CANT USE DELAYS TO GIVE DUMMY DATA BECAUSE PULSEAUDIO MIGHT GIVE AUDIO A BIG DELAYED!!! - if(num_missing_frames >= 5 || (num_missing_frames > 0 && got_audio_data)) { + if(num_missing_frames >= 5 || !audio_track.sound_device.handle) { // TODO: //audio_track.frame->data[0] = empty_audio; received_audio_time = this_audio_frame_time; - swr_convert(swr, &audio_track.frame->data[0], audio_track.frame->nb_samples, (const uint8_t**)&empty_audio, audio_track.sound_device.frames); + swr_convert(swr, &audio_track.frame->data[0], audio_track.frame->nb_samples, (const uint8_t**)&empty_audio, audio_track.codec_context->frame_size); // TODO: Check if duplicate frame can be saved just by writing it with a different pts instead of sending it again for(int i = 0; i < num_missing_frames; ++i) { audio_track.frame->pts = pts; @@ -1125,26 +1262,12 @@ int main(int argc, char **argv) { } } - if(!audio_track.sound_device.handle) { - // TODO: - //audio_track.frame->data[0] = empty_audio; - received_audio_time = this_audio_frame_time; - swr_convert(swr, &audio_track.frame->data[0], audio_track.frame->nb_samples, (const uint8_t**)&empty_audio, audio_track.codec_context->frame_size); - audio_track.frame->pts = pts; - pts += audio_track.frame->nb_samples; - ret = avcodec_send_frame(audio_track.codec_context, audio_track.frame); - if(ret >= 0){ - receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, audio_track.frame, av_format_context, record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, *write_output_mutex); - } else { - fprintf(stderr, "Failed to encode audio!\n"); - } - + if(!audio_track.sound_device.handle) usleep(timeout_ms * 1000); - } if(got_audio_data) { // TODO: Instead of converting audio, get float audio from alsa. Or does alsa do conversion internally to get this format? - swr_convert(swr, &audio_track.frame->data[0], audio_track.frame->nb_samples, (const uint8_t**)&sound_buffer, audio_track.sound_device.frames); + swr_convert(swr, &audio_track.frame->data[0], audio_track.frame->nb_samples, (const uint8_t**)&sound_buffer, audio_track.codec_context->frame_size); audio_track.frame->pts = pts; pts += audio_track.frame->nb_samples; @@ -1197,14 +1320,20 @@ int main(int argc, char **argv) { const int64_t expected_frames = std::round((this_video_frame_time - start_time_pts) / target_fps); const int num_frames = std::max(0L, expected_frames - video_pts_counter); + + frame->flags &= ~AV_FRAME_FLAG_DISCARD; // TODO: Check if duplicate frame can be saved just by writing it with a different pts instead of sending it again for(int i = 0; i < num_frames; ++i) { + if(i > 0) + frame->flags |= AV_FRAME_FLAG_DISCARD; + frame->pts = video_pts_counter + i; - if (avcodec_send_frame(video_codec_context, frame) >= 0) { + int ret = avcodec_send_frame(video_codec_context, frame); + if (ret >= 0) { receive_frames(video_codec_context, VIDEO_STREAM_INDEX, video_stream, frame, av_format_context, record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex); } else { - fprintf(stderr, "Error: avcodec_send_frame failed\n"); + fprintf(stderr, "Error: avcodec_send_frame failed, error: %s\n", av_error_to_string(ret)); } } video_pts_counter += num_frames; @@ -1213,15 +1342,12 @@ int main(int argc, char **argv) { if(save_replay_thread.valid() && save_replay_thread.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { save_replay_thread.get(); puts(save_replay_output_filepath.c_str()); - for(size_t i = 0; i < save_replay_packets.size(); ++i) { - av_packet_unref(&save_replay_packets[i]); - } save_replay_packets.clear(); } if(save_replay == 1 && !save_replay_thread.valid() && replay_buffer_size_secs != -1) { save_replay = 0; - save_replay_async(video_codec_context, VIDEO_STREAM_INDEX, audio_tracks, frame_data_queue, frames_erased, filename, container_format, write_output_mutex); + save_replay_async(video_codec_context, VIDEO_STREAM_INDEX, audio_tracks, frame_data_queue, frames_erased, filename, container_format, file_extension, write_output_mutex); } // av_frame_free(&frame); @@ -1234,8 +1360,10 @@ int main(int argc, char **argv) { running = 0; - if(save_replay_thread.valid()) + if(save_replay_thread.valid()) { save_replay_thread.get(); + puts(save_replay_output_filepath.c_str()); + } for(AudioTrack &audio_track : audio_tracks) { audio_track.thread.join(); diff --git a/src/window_texture.c b/src/window_texture.c index 1c152ae..cea77d7 100644 --- a/src/window_texture.c +++ b/src/window_texture.c @@ -13,14 +13,16 @@ static int x11_supports_composite_named_window_pixmap(Display *display) { return XCompositeQueryVersion(display, &major_version, &minor_version) && (major_version > 0 || minor_version >= 2); } -int window_texture_init(WindowTexture *window_texture, Display *display, Window window, gsr_gl *gl) { +int window_texture_init(WindowTexture *window_texture, Display *display, Window window, gsr_egl *egl) { window_texture->display = display; window_texture->window = window; window_texture->pixmap = None; - window_texture->glx_pixmap = None; window_texture->texture_id = 0; + window_texture->target_texture_id = 0; + window_texture->texture_width = 0; + window_texture->texture_height = 0; window_texture->redirected = 0; - window_texture->gl = gl; + window_texture->egl = egl; if(!x11_supports_composite_named_window_pixmap(display)) return 1; @@ -32,14 +34,13 @@ int window_texture_init(WindowTexture *window_texture, Display *display, Window static void window_texture_cleanup(WindowTexture *self, int delete_texture) { if(delete_texture && self->texture_id) { - self->gl->glDeleteTextures(1, &self->texture_id); + self->egl->glDeleteTextures(1, &self->texture_id); self->texture_id = 0; } - if(self->glx_pixmap) { - self->gl->glXDestroyPixmap(self->display, self->glx_pixmap); - self->gl->glXReleaseTexImageEXT(self->display, self->glx_pixmap, GLX_FRONT_EXT); - self->glx_pixmap = None; + if(delete_texture && self->target_texture_id) { + self->egl->glDeleteTextures(1, &self->target_texture_id); + self->target_texture_id = 0; } if(self->pixmap) { @@ -56,71 +57,23 @@ void window_texture_deinit(WindowTexture *self) { window_texture_cleanup(self, 1); } + +#define EGL_TRUE 1 +#define EGL_IMAGE_PRESERVED_KHR 0x30D2 +#define EGL_NATIVE_PIXMAP_KHR 0x30B0 + int window_texture_on_resize(WindowTexture *self) { window_texture_cleanup(self, 0); int result = 0; - GLXFBConfig *configs = NULL; Pixmap pixmap = None; - GLXPixmap glx_pixmap = None; unsigned int texture_id = 0; - int glx_pixmap_bound = 0; - - const int pixmap_config[] = { - GLX_BIND_TO_TEXTURE_RGB_EXT, True, - GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT | GLX_WINDOW_BIT, - GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT, - /*GLX_BIND_TO_MIPMAP_TEXTURE_EXT, True,*/ - GLX_BUFFER_SIZE, 24, - GLX_RED_SIZE, 8, - GLX_GREEN_SIZE, 8, - GLX_BLUE_SIZE, 8, - GLX_ALPHA_SIZE, 0, - None - }; - - const int pixmap_attribs[] = { - GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT, - GLX_TEXTURE_FORMAT_EXT, GLX_TEXTURE_FORMAT_RGB_EXT, - /*GLX_MIPMAP_TEXTURE_EXT, True,*/ - None - }; - - XWindowAttributes attr; - if (!XGetWindowAttributes(self->display, self->window, &attr)) { - fprintf(stderr, "Failed to get window attributes\n"); - return 1; - } - - GLXFBConfig config; - int c; - configs = self->gl->glXChooseFBConfig(self->display, 0, pixmap_config, &c); - if(!configs) { - fprintf(stderr, "Failed to choose fb config\n"); - return 1; - } - - int found = 0; - for (int i = 0; i < c; i++) { - config = configs[i]; - XVisualInfo *visual = self->gl->glXGetVisualFromFBConfig(self->display, config); - if (!visual) - continue; - - if (attr.depth != visual->depth) { - XFree(visual); - continue; - } - XFree(visual); - found = 1; - break; - } + EGLImage image = NULL; - if(!found) { - fprintf(stderr, "No matching fb config found\n"); - result = 1; - goto cleanup; - } + const intptr_t pixmap_attrs[] = { + EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, + EGL_NONE, + }; pixmap = XCompositeNameWindowPixmap(self->display, self->window); if(!pixmap) { @@ -128,46 +81,60 @@ int window_texture_on_resize(WindowTexture *self) { goto cleanup; } - glx_pixmap = self->gl->glXCreatePixmap(self->display, config, pixmap, pixmap_attribs); - if(!glx_pixmap) { - result = 3; - goto cleanup; - } - if(self->texture_id == 0) { - self->gl->glGenTextures(1, &texture_id); + self->egl->glGenTextures(1, &texture_id); if(texture_id == 0) { result = 4; goto cleanup; } - self->gl->glBindTexture(GL_TEXTURE_2D, texture_id); + self->egl->glBindTexture(GL_TEXTURE_2D, texture_id); } else { - self->gl->glBindTexture(GL_TEXTURE_2D, self->texture_id); + self->egl->glBindTexture(GL_TEXTURE_2D, self->texture_id); + texture_id = self->texture_id; + } + + image = self->egl->eglCreateImage(self->egl->egl_display, NULL, EGL_NATIVE_PIXMAP_KHR, (EGLClientBuffer)pixmap, pixmap_attrs); + if(!image) { + fprintf(stderr, "eglCreateImage failed\n"); + return -1; } + fprintf(stderr, "gl error: %d\n", self->egl->glGetError()); - self->gl->glXBindTexImageEXT(self->display, glx_pixmap, GLX_FRONT_EXT, NULL); - glx_pixmap_bound = 1; + fprintf(stderr, "image: %p\n", image); + self->egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); + if(self->egl->glGetError() != 0) { + fprintf(stderr, "glEGLImageTargetTexture2DOES failed\n"); + } - self->gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - self->gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - self->gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - self->gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - self->gl->glBindTexture(GL_TEXTURE_2D, 0); + self->egl->glBindTexture(GL_TEXTURE_2D, 0); - XFree(configs); self->pixmap = pixmap; - self->glx_pixmap = glx_pixmap; - if(texture_id != 0) + if(texture_id != 0) { self->texture_id = texture_id; + + self->egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &self->texture_width); + self->egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &self->texture_height); + + fprintf(stderr, "texture width: %d, height: %d\n", self->texture_width, self->texture_height); + + self->egl->glGenTextures(1, &self->target_texture_id); + self->egl->glBindTexture(GL_TEXTURE_2D, self->target_texture_id); + self->egl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, self->texture_width, self->texture_height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + fprintf(stderr, "gl error: %d\n", self->egl->glGetError()); + self->egl->glBindTexture(GL_TEXTURE_2D, 0); + } + + // TODO: destroyImage(image) return 0; cleanup: - if(texture_id != 0) self->gl->glDeleteTextures(1, &texture_id); - if(glx_pixmap) self->gl->glXDestroyPixmap(self->display, glx_pixmap); - if(glx_pixmap_bound) self->gl->glXReleaseTexImageEXT(self->display, glx_pixmap, GLX_FRONT_EXT); + if(texture_id != 0) self->egl->glDeleteTextures(1, &texture_id); if(pixmap) XFreePixmap(self->display, pixmap); - if(configs) XFree(configs); return result; } -- cgit v1.2.3