Refactor xcomposite into abstract capture api

Refactor c++ files into c files, more usable
This commit is contained in:
dec05eba 2022-10-16 02:08:40 +02:00
parent 93d46b9767
commit a7e0dbd833
21 changed files with 1342 additions and 1184 deletions

View File

@ -1,2 +0,0 @@
BasedOnStyle: LLVM
IndentWidth: 4

View File

@ -38,7 +38,7 @@ Send signal SIGUSR1 (`killall -SIGUSR1 gpu-screen-recorder`) to gpu-screen-recor
You can find the default output audio device (headset, speakers (in other words, desktop audio)) with the command `pactl get-default-sink`. Add `monitor` to the end of that to use that as an audio input in gpu-screen-recorder.\
You can find the default input audio device (microphone) with the command `pactl get-default-source`. This input should not have `monitor` added to the end when used in gpu-screen-recorder.\
Example of recording both desktop audio and microphone: `gpu-screen-recorder -w $(xdotool selectwindow) -c mp4 -f 60 -a "$(pactl get-default-sink).monitor" -a "$(pactl get-default-source)" -o test_video.mp4`.\
Note that if you use multiple audio inputs then they are each recorded into separate audio tracks in the video file. There is currently no option to merge audio tracks, but it's a planned feature.
Note that if you use multiple audio inputs then they are each recorded into separate audio tracks in the video file. There is currently no option to merge audio tracks, but it's a planned feature. For now I recommend using gpwgraph if you are using pipewire. Gpwgraph allows you to merge multiple audio inputs into one with a simple gui. If you use pulseaudio then you need to create a virtual sink, which is a bit more complex.
There is also a gui for the gpu-screen-recorder called [gpu-screen-recorder-gtk](https://git.dec05eba.com/gpu-screen-recorder-gtk/).

1
TODO
View File

@ -14,3 +14,4 @@ Resizing the target window to be smaller than the initial size is buggy. The win
Handle frames (especially for applications with rounded client-side decorations, such as gnome applications. They are huge).
Use nvenc directly, which allows removing the use of cuda.
Fallback to nvfbc and window tracking if window capture fails.
Handle xrandr monitor change in nvfbc.

View File

@ -5,7 +5,12 @@ includes="$(pkg-config --cflags $dependencies)"
libs="$(pkg-config --libs $dependencies) -ldl -pthread -lm"
gcc -c src/capture/capture.c -O2 -g0 -DNDEBUG $includes
gcc -c src/capture/nvfbc.c -O2 -g0 -DNDEBUG $includes
gcc -c src/capture/xcomposite.c -O2 -g0 -DNDEBUG $includes
gcc -c src/gl.c -O2 -g0 -DNDEBUG $includes
gcc -c src/cuda.c -O2 -g0 -DNDEBUG $includes
gcc -c src/window_texture.c -O2 -g0 -DNDEBUG $includes
gcc -c src/time.c -O2 -g0 -DNDEBUG $includes
g++ -c src/sound.cpp -O2 -g0 -DNDEBUG $includes
g++ -c src/main.cpp -O2 -g0 -DNDEBUG $includes
g++ -o gpu-screen-recorder -O2 capture.o nvfbc.o sound.o main.o -s $libs
g++ -o gpu-screen-recorder -O2 capture.o nvfbc.o gl.o cuda.o window_texture.o time.o xcomposite.o sound.o main.o -s $libs
echo "Successfully built gpu-screen-recorder"

View File

@ -3,23 +3,28 @@
#include <stdbool.h>
typedef struct AVCodecContext AVCodecContext;
typedef struct AVFrame AVFrame;
typedef struct gsr_capture gsr_capture;
struct gsr_capture {
int (*start)(gsr_capture *cap);
void (*stop)(gsr_capture *cap);
/* These methods should not be called manually. Call gsr_capture_* instead */
int (*start)(gsr_capture *cap, AVCodecContext *video_codec_context);
void (*tick)(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame); /* can be NULL */
bool (*should_stop)(gsr_capture *cap, bool *err); /* can be NULL */
int (*capture)(gsr_capture *cap, AVFrame *frame);
void (*destroy)(gsr_capture *cap);
void (*destroy)(gsr_capture *cap, AVCodecContext *video_codec_context);
void *priv;
void *priv; /* can be NULL */
bool started;
};
int gsr_capture_start(gsr_capture *cap);
void gsr_capture_stop(gsr_capture *cap);
int gsr_capture_start(gsr_capture *cap, AVCodecContext *video_codec_context);
void gsr_capture_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame);
bool gsr_capture_should_stop(gsr_capture *cap, bool *err);
int gsr_capture_capture(gsr_capture *cap, AVFrame *frame);
/* Calls |gsr_capture_stop| as well */
void gsr_capture_destroy(gsr_capture *cap);
void gsr_capture_destroy(gsr_capture *cap, AVCodecContext *video_codec_context);
#endif /* GSR_CAPTURE_CAPTURE_H */

View File

@ -4,8 +4,11 @@
#include "capture.h"
#include "../vec2.h"
typedef struct _XDisplay Display;
typedef struct {
const char *display_to_capture; /* if this is "screen", then the entire x11 screen is captured (all displays) */
Display *dpy;
const char *display_to_capture; /* if this is "screen", then the entire x11 screen is captured (all displays). A copy is made of this */
int fps;
vec2i pos;
vec2i size;

View File

@ -0,0 +1,16 @@
#ifndef GSR_CAPTURE_XCOMPOSITE_H
#define GSR_CAPTURE_XCOMPOSITE_H
#include "capture.h"
#include "../vec2.h"
#include <X11/X.h>
typedef struct _XDisplay Display;
typedef struct {
Window window;
} gsr_capture_xcomposite_params;
gsr_capture* gsr_capture_xcomposite_create(const gsr_capture_xcomposite_params *params);
#endif /* GSR_CAPTURE_XCOMPOSITE_H */

View File

@ -1,9 +1,8 @@
#pragma once
#ifndef GSR_CUDA_H
#define GSR_CUDA_H
#include "LibraryLoader.hpp"
#include <dlfcn.h>
#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
// To prevent hwcontext_cuda.h from including cuda.h
#define CUDA_VERSION 11070
@ -22,7 +21,7 @@ typedef struct CUctx_st *CUcontext;
typedef struct CUstream_st *CUstream;
typedef struct CUarray_st *CUarray;
static const int CUDA_SUCCESS = 0;
#define CUDA_SUCCESS 0
typedef enum CUgraphicsMapResourceFlags_enum {
CU_GRAPHICS_MAP_RESOURCE_FLAGS_NONE = 0x00,
@ -69,15 +68,19 @@ typedef struct CUDA_MEMCPY2D_st {
} CUDA_MEMCPY2D_v2;
typedef CUDA_MEMCPY2D_v2 CUDA_MEMCPY2D;
static const int CU_CTX_SCHED_AUTO = 0;
#define CU_CTX_SCHED_AUTO 0
typedef struct CUgraphicsResource_st *CUgraphicsResource;
struct Cuda {
typedef struct {
void *library;
CUcontext cu_ctx;
CUresult (*cuInit)(unsigned int Flags);
CUresult (*cuDeviceGetCount)(int *count);
CUresult (*cuDeviceGet)(CUdevice *device, int ordinal);
CUresult (*cuCtxCreate_v2)(CUcontext *pctx, unsigned int flags, CUdevice dev);
CUresult (*cuCtxDestroy_v2)(CUcontext ctx);
CUresult (*cuCtxPushCurrent_v2)(CUcontext ctx);
CUresult (*cuCtxPopCurrent_v2)(CUcontext *pctx);
CUresult (*cuGetErrorString)(CUresult error, const char **pStr);
@ -87,57 +90,12 @@ struct Cuda {
CUresult (*cuGraphicsGLRegisterImage)(CUgraphicsResource *pCudaResource, unsigned int image, unsigned int target, unsigned int Flags);
CUresult (*cuGraphicsResourceSetMapFlags)(CUgraphicsResource resource, unsigned int flags);
CUresult (*cuGraphicsMapResources)(unsigned int count, CUgraphicsResource *resources, CUstream hStream);
CUresult (*cuGraphicsUnmapResources)(unsigned int count, CUgraphicsResource *resources, CUstream hStream);
CUresult (*cuGraphicsUnregisterResource)(CUgraphicsResource resource);
CUresult (*cuGraphicsSubResourceGetMappedArray)(CUarray *pArray, CUgraphicsResource resource, unsigned int arrayIndex, unsigned int mipLevel);
} gsr_cuda;
~Cuda() {
if(library)
dlclose(library);
}
bool gsr_cuda_load(gsr_cuda *self);
void gsr_cuda_unload(gsr_cuda *self);
bool load() {
if(library)
return true;
dlerror(); // clear
void *lib = dlopen("libcuda.so.1", RTLD_LAZY);
if(!lib) {
lib = dlopen("libcuda.so", RTLD_LAZY);
if(!lib) {
fprintf(stderr, "Error: failed to load libcuda.so/libcuda.so.1, error: %s\n", dlerror());
return false;
}
}
dlsym_assign required_dlsym[] = {
{ (void**)&cuInit, "cuInit" },
{ (void**)&cuDeviceGetCount, "cuDeviceGetCount" },
{ (void**)&cuDeviceGet, "cuDeviceGet" },
{ (void**)&cuCtxCreate_v2, "cuCtxCreate_v2" },
{ (void**)&cuCtxPushCurrent_v2, "cuCtxPushCurrent_v2" },
{ (void**)&cuCtxPopCurrent_v2, "cuCtxPopCurrent_v2" },
{ (void**)&cuGetErrorString, "cuGetErrorString" },
{ (void**)&cuMemsetD8_v2, "cuMemsetD8_v2" },
{ (void**)&cuMemcpy2D_v2, "cuMemcpy2D_v2" },
{ (void**)&cuGraphicsGLRegisterImage, "cuGraphicsGLRegisterImage" },
{ (void**)&cuGraphicsResourceSetMapFlags, "cuGraphicsResourceSetMapFlags" },
{ (void**)&cuGraphicsMapResources, "cuGraphicsMapResources" },
{ (void**)&cuGraphicsUnregisterResource, "cuGraphicsUnregisterResource" },
{ (void**)&cuGraphicsSubResourceGetMappedArray, "cuGraphicsSubResourceGetMappedArray" },
{ NULL, NULL }
};
if(dlsym_load_list(lib, required_dlsym)) {
library = lib;
return true;
} else {
fprintf(stderr, "Error: missing required symbols in libcuda.so\n");
dlclose(lib);
return false;
}
}
private:
void *library = nullptr;
};
#endif /* GSR_CUDA_H */

View File

@ -1,11 +1,11 @@
#pragma once
#ifndef GSR_GL_H
#define GSR_GL_H
#include "LibraryLoader.hpp"
/* OpenGL library with a hidden window context (to allow using the opengl functions) */
#include <X11/X.h>
#include <X11/Xutil.h>
#include <dlfcn.h>
#include <stdio.h>
#include <stdbool.h>
typedef XID GLXPixmap;
typedef XID GLXDrawable;
@ -25,6 +25,8 @@ typedef struct __GLXFBConfigRec *GLXFBConfig;
#define GL_TEXTURE_WIDTH 0x1000
#define GL_TEXTURE_HEIGHT 0x1001
#define GL_NEAREST 0x2600
#define GL_CLAMP_TO_EDGE 0x812F
#define GL_LINEAR 0x2601
#define GL_RENDERER 0x1F01
@ -54,7 +56,16 @@ typedef struct __GLXFBConfigRec *GLXFBConfig;
#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
#define GLX_CONTEXT_FLAGS_ARB 0x2094
struct GlLibrary {
typedef struct {
void *library;
Display *dpy;
GLXFBConfig *fbconfigs;
XVisualInfo *visual_info;
GLXFBConfig fbconfig;
Colormap colormap;
GLXContext gl_context;
Window window;
GLXPixmap (*glXCreatePixmap)(Display *dpy, GLXFBConfig config, Pixmap pixmap, const int *attribList);
void (*glXDestroyPixmap)(Display *dpy, GLXPixmap pixmap);
void (*glXBindTexImageEXT)(Display *dpy, GLXDrawable drawable, int buffer, const int *attrib_list);
@ -82,75 +93,10 @@ struct GlLibrary {
void (*glGetTexLevelParameteriv)(unsigned int target, int level, unsigned int pname, int *params);
void (*glTexImage2D)(unsigned int target, int level, int internalFormat, int width, int height, int border, unsigned int format, unsigned int type, const void *pixels);
void (*glCopyImageSubData)(unsigned int srcName, unsigned int srcTarget, int srcLevel, int srcX, int srcY, int srcZ, unsigned int dstName, unsigned int dstTarget, int dstLevel, int dstX, int dstY, int dstZ, int srcWidth, int srcHeight, int srcDepth);
} gsr_gl;
~GlLibrary() {
unload();
}
bool gsr_gl_load(gsr_gl *self, Display *dpy);
bool gsr_gl_make_context_current(gsr_gl *self);
void gsr_gl_unload(gsr_gl *self);
bool load() {
if(library)
return true;
dlerror(); // clear
void *lib = dlopen("libGL.so.1", RTLD_LAZY);
if(!lib) {
fprintf(stderr, "Error: failed to load libGL.so.1, error: %s\n", dlerror());
return false;
}
dlsym_assign optional_dlsym[] = {
{ (void**)&glClearTexImage, "glClearTexImage" },
{ (void**)&glXSwapIntervalEXT, "glXSwapIntervalEXT" },
{ (void**)&glXSwapIntervalMESA, "glXSwapIntervalMESA" },
{ (void**)&glXSwapIntervalSGI, "glXSwapIntervalSGI" },
{ NULL, NULL }
};
dlsym_load_list_optional(lib, optional_dlsym);
dlsym_assign required_dlsym[] = {
{ (void**)&glXCreatePixmap, "glXCreatePixmap" },
{ (void**)&glXDestroyPixmap, "glXDestroyPixmap" },
{ (void**)&glXBindTexImageEXT, "glXBindTexImageEXT" },
{ (void**)&glXReleaseTexImageEXT, "glXReleaseTexImageEXT" },
{ (void**)&glXChooseFBConfig, "glXChooseFBConfig" },
{ (void**)&glXGetVisualFromFBConfig, "glXGetVisualFromFBConfig" },
{ (void**)&glXCreateContextAttribsARB, "glXCreateContextAttribsARB" },
{ (void**)&glXMakeContextCurrent, "glXMakeContextCurrent" },
{ (void**)&glXDestroyContext, "glXDestroyContext" },
{ (void**)&glXSwapBuffers, "glXSwapBuffers" },
{ (void**)&glGetError, "glGetError" },
{ (void**)&glGetString, "glGetString" },
{ (void**)&glClear, "glClear" },
{ (void**)&glGenTextures, "glGenTextures" },
{ (void**)&glDeleteTextures, "glDeleteTextures" },
{ (void**)&glBindTexture, "glBindTexture" },
{ (void**)&glTexParameteri, "glTexParameteri" },
{ (void**)&glGetTexLevelParameteriv, "glGetTexLevelParameteriv" },
{ (void**)&glTexImage2D, "glTexImage2D" },
{ (void**)&glCopyImageSubData, "glCopyImageSubData" },
{ NULL, NULL }
};
if(dlsym_load_list(lib, required_dlsym)) {
library = lib;
return true;
} else {
fprintf(stderr, "Error: missing required symbols in libGL.so.1\n");
dlclose(lib);
return false;
}
}
void unload() {
if(library) {
dlclose(library);
library = nullptr;
}
}
private:
void *library = nullptr;
};
#endif /* GSR_GL_H */

View File

@ -1,4 +1,5 @@
#pragma once
#ifndef GSR_LIBRARY_LOADER_H
#define GSR_LIBRARY_LOADER_H
#include <dlfcn.h>
#include <stdio.h>
@ -36,3 +37,5 @@ static void dlsym_load_list_optional(void *handle, const dlsym_assign *dlsyms) {
*dlsyms[i].func = dlsym_print_fail(handle, dlsyms[i].name, false);
}
}
#endif /* GSR_LIBRARY_LOADER_H */

6
include/time.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef GSR_TIME_H
#define GSR_TIME_H
double clock_get_monotonic_seconds();
#endif /* GSR_TIME_H */

28
include/window_texture.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef WINDOW_TEXTURE_H
#define WINDOW_TEXTURE_H
#include "gl.h"
typedef struct {
Display *display;
Window window;
Pixmap pixmap;
GLXPixmap glx_pixmap;
unsigned int texture_id;
int redirected;
gsr_gl *gl;
} WindowTexture;
/* Returns 0 on success */
int window_texture_init(WindowTexture *window_texture, Display *display, Window window, gsr_gl *gl);
void window_texture_deinit(WindowTexture *self);
/*
This should ONLY be called when the target window is resized.
Returns 0 on success.
*/
int window_texture_on_resize(WindowTexture *self);
unsigned int window_texture_get_opengl_texture_id(WindowTexture *self);
#endif /* WINDOW_TEXTURE_H */

View File

@ -1,17 +1,47 @@
#include "../../include/capture/capture.h"
#include <stdio.h>
int gsr_capture_start(gsr_capture *cap) {
return cap->start(cap);
int gsr_capture_start(gsr_capture *cap, AVCodecContext *video_codec_context) {
if(cap->started)
return -1;
int res = cap->start(cap, video_codec_context);
if(res == 0)
cap->started = true;
return res;
}
void gsr_capture_stop(gsr_capture *cap) {
cap->stop(cap);
void gsr_capture_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) {
if(!cap->started) {
fprintf(stderr, "gsr error: gsp_capture_tick failed: the gsr capture has not been started\n");
return;
}
if(cap->tick)
cap->tick(cap, video_codec_context, frame);
}
bool gsr_capture_should_stop(gsr_capture *cap, bool *err) {
if(!cap->started) {
fprintf(stderr, "gsr error: gsr_capture_should_stop failed: the gsr capture has not been started\n");
return false;
}
if(!cap->should_stop)
return false;
return cap->should_stop(cap, err);
}
int gsr_capture_capture(gsr_capture *cap, AVFrame *frame) {
if(!cap->started) {
fprintf(stderr, "gsr error: gsr_capture_capture failed: the gsr capture has not been started\n");
return -1;
}
return cap->capture(cap, frame);
}
void gsr_capture_destroy(gsr_capture *cap) {
cap->destroy(cap);
void gsr_capture_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
cap->destroy(cap, video_codec_context);
}

View File

@ -1,10 +1,16 @@
#include "../../include/capture/nvfbc.h"
#include "../../external/NvFBC.h"
#include "../../include/cuda.h"
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <libavutil/hwcontext.h>
#include <libavutil/hwcontext_cuda.h>
#include <libavutil/frame.h>
#include <libavutil/version.h>
#include <libavcodec/avcodec.h>
typedef struct {
gsr_capture_nvfbc_params params;
@ -14,6 +20,8 @@ typedef struct {
PNVFBCCREATEINSTANCE nv_fbc_create_instance;
NVFBC_API_FUNCTION_LIST nv_fbc_function_list;
bool fbc_handle_created;
gsr_cuda cuda;
} gsr_capture_nvfbc;
#if defined(_WIN64) || defined(__LP64__)
@ -28,13 +36,16 @@ static int max_int(int a, int b) {
}
/* Returns 0 on failure */
static uint32_t get_output_id_from_display_name(NVFBC_RANDR_OUTPUT_INFO *outputs, uint32_t num_outputs, const char *display_name) {
static uint32_t get_output_id_from_display_name(NVFBC_RANDR_OUTPUT_INFO *outputs, uint32_t num_outputs, const char *display_name, uint32_t *width, uint32_t *height) {
if(!outputs)
return 0;
for(uint32_t i = 0; i < num_outputs; ++i) {
if(strcmp(outputs[i].name, display_name) == 0)
if(strcmp(outputs[i].name, display_name) == 0) {
*width = outputs[i].trackedBox.w;
*height = outputs[i].trackedBox.h;
return outputs[i].dwId;
}
}
return 0;
@ -95,16 +106,78 @@ static bool gsr_capture_nvfbc_load_library(gsr_capture *cap) {
return true;
}
static int gsr_capture_nvfbc_start(gsr_capture *cap) {
#if LIBAVUTIL_VERSION_MAJOR < 57
static AVBufferRef* dummy_hw_frame_init(int size) {
return av_buffer_alloc(size);
}
#else
static AVBufferRef* dummy_hw_frame_init(size_t size) {
return av_buffer_alloc(size);
}
#endif
static bool ffmpeg_create_cuda_contexts(gsr_capture_nvfbc *cap_nvfbc, AVCodecContext *video_codec_context) {
AVBufferRef *device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA);
if(!device_ctx) {
fprintf(stderr, "gsr error: cuda_create_codec_context failed: 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_nvfbc->cuda.cu_ctx;
if(av_hwdevice_ctx_init(device_ctx) < 0) {
fprintf(stderr, "gsr error: cuda_create_codec_context failed: failed to create hardware device context\n");
av_buffer_unref(&device_ctx);
return false;
}
AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx);
if(!frame_context) {
fprintf(stderr, "gsr error: cuda_create_codec_context failed: 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_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;
hw_frame_context->pool = av_buffer_pool_init(1, dummy_hw_frame_init);
hw_frame_context->initial_pool_size = 1;
if (av_hwframe_ctx_init(frame_context) < 0) {
fprintf(stderr, "gsr error: cuda_create_codec_context failed: 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;
video_codec_context->hw_frames_ctx = frame_context;
return true;
}
static int gsr_capture_nvfbc_start(gsr_capture *cap, AVCodecContext *video_codec_context) {
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
if(!gsr_cuda_load(&cap_nvfbc->cuda))
return -1;
if(!gsr_capture_nvfbc_load_library(cap)) {
gsr_cuda_unload(&cap_nvfbc->cuda);
return -1;
}
const uint32_t x = max_int(cap_nvfbc->params.pos.x, 0);
const uint32_t y = max_int(cap_nvfbc->params.pos.y, 0);
const uint32_t width = max_int(cap_nvfbc->params.size.x, 0);
const uint32_t height = max_int(cap_nvfbc->params.size.y, 0);
if(!cap_nvfbc->library || !cap_nvfbc->params.display_to_capture || cap_nvfbc->fbc_handle_created)
return -1;
const bool capture_region = (x > 0 || y > 0 || width > 0 || height > 0);
NVFBCSTATUS status;
@ -127,7 +200,7 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap) {
status = cap_nvfbc->nv_fbc_function_list.nvFBCCreateHandle(&cap_nvfbc->nv_fbc_handle, &create_params);
if(status != NVFBC_SUCCESS) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle));
return -1;
goto error_cleanup;
}
}
cap_nvfbc->fbc_handle_created = true;
@ -147,6 +220,8 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap) {
goto error_cleanup;
}
uint32_t tracking_width = XWidthOfScreen(DefaultScreenOfDisplay(cap_nvfbc->params.dpy));
uint32_t tracking_height = XHeightOfScreen(DefaultScreenOfDisplay(cap_nvfbc->params.dpy));
tracking_type = strcmp(cap_nvfbc->params.display_to_capture, "screen") == 0 ? NVFBC_TRACKING_SCREEN : NVFBC_TRACKING_OUTPUT;
if(tracking_type == NVFBC_TRACKING_OUTPUT) {
if(!status_params.bXRandRAvailable) {
@ -159,7 +234,7 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap) {
goto error_cleanup;
}
output_id = get_output_id_from_display_name(status_params.outputs, status_params.dwOutputNum, cap_nvfbc->params.display_to_capture);
output_id = get_output_id_from_display_name(status_params.outputs, status_params.dwOutputNum, cap_nvfbc->params.display_to_capture, &tracking_width, &tracking_height);
if(output_id == 0) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: display '%s' not found\n", cap_nvfbc->params.display_to_capture);
goto error_cleanup;
@ -198,6 +273,17 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap) {
goto error_cleanup;
}
if(capture_region) {
video_codec_context->width = width & ~1;
video_codec_context->height = height & ~1;
} else {
video_codec_context->width = tracking_width & ~1;
video_codec_context->height = tracking_height & ~1;
}
if(!ffmpeg_create_cuda_contexts(cap_nvfbc, video_codec_context))
goto error_cleanup;
return 0;
error_cleanup:
@ -215,17 +301,16 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap) {
cap_nvfbc->nv_fbc_function_list.nvFBCDestroyHandle(cap_nvfbc->nv_fbc_handle, &destroy_params);
cap_nvfbc->fbc_handle_created = false;
}
output_id = 0;
av_buffer_unref(&video_codec_context->hw_device_ctx);
av_buffer_unref(&video_codec_context->hw_frames_ctx);
gsr_cuda_unload(&cap_nvfbc->cuda);
return -1;
}
static void gsr_capture_nvfbc_stop(gsr_capture *cap) {
static void gsr_capture_nvfbc_destroy_session(gsr_capture *cap) {
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
/* Intentionally ignore failure on destroy */
if(!cap_nvfbc->nv_fbc_handle)
return;
NVFBC_DESTROY_CAPTURE_SESSION_PARAMS destroy_capture_params;
memset(&destroy_capture_params, 0, sizeof(destroy_capture_params));
destroy_capture_params.dwVersion = NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER;
@ -241,8 +326,6 @@ static void gsr_capture_nvfbc_stop(gsr_capture *cap) {
static int gsr_capture_nvfbc_capture(gsr_capture *cap, AVFrame *frame) {
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
if(!cap_nvfbc->library || !cap_nvfbc->fbc_handle_created)
return -1;
CUdeviceptr cu_device_ptr = 0;
@ -274,18 +357,19 @@ static int gsr_capture_nvfbc_capture(gsr_capture *cap, AVFrame *frame) {
return 0;
}
static void gsr_capture_nvfbc_destroy(gsr_capture *cap) {
if(cap) {
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
gsr_capture_nvfbc_stop(cap);
if(cap_nvfbc) {
dlclose(cap_nvfbc->library);
free((void*)cap_nvfbc->params.display_to_capture);
free(cap->priv);
cap->priv = NULL;
}
free(cap);
static void gsr_capture_nvfbc_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
gsr_capture_nvfbc_destroy_session(cap);
av_buffer_unref(&video_codec_context->hw_device_ctx);
av_buffer_unref(&video_codec_context->hw_frames_ctx);
if(cap_nvfbc) {
gsr_cuda_unload(&cap_nvfbc->cuda);
dlclose(cap_nvfbc->library);
free((void*)cap_nvfbc->params.display_to_capture);
free(cap->priv);
cap->priv = NULL;
}
free(cap);
}
gsr_capture* gsr_capture_nvfbc_create(const gsr_capture_nvfbc_params *params) {
@ -294,6 +378,11 @@ gsr_capture* gsr_capture_nvfbc_create(const gsr_capture_nvfbc_params *params) {
return NULL;
}
if(!params->display_to_capture) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_create params.display_to_capture is NULL\n");
return NULL;
}
gsr_capture *cap = calloc(1, sizeof(gsr_capture));
if(!cap)
return NULL;
@ -317,16 +406,12 @@ gsr_capture* gsr_capture_nvfbc_create(const gsr_capture_nvfbc_params *params) {
*cap = (gsr_capture) {
.start = gsr_capture_nvfbc_start,
.stop = gsr_capture_nvfbc_stop,
.tick = NULL,
.should_stop = NULL,
.capture = gsr_capture_nvfbc_capture,
.destroy = gsr_capture_nvfbc_destroy,
.priv = cap_nvfbc
};
if(!gsr_capture_nvfbc_load_library(cap)) {
gsr_capture_nvfbc_destroy(cap);
return NULL;
}
return cap;
}

517
src/capture/xcomposite.c Normal file
View File

@ -0,0 +1,517 @@
#include "../../include/capture/xcomposite.h"
#include "../../include/gl.h"
#include "../../include/cuda.h"
#include "../../include/window_texture.h"
#include "../../include/time.h"
#include <X11/extensions/Xcomposite.h>
#include <libavutil/hwcontext.h>
#include <libavutil/hwcontext_cuda.h>
#include <libavutil/frame.h>
#include <libavcodec/avcodec.h>
/* 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;
}

100
src/cuda.c Normal file
View File

@ -0,0 +1,100 @@
#include "../include/cuda.h"
#include "../include/library_loader.h"
#include <string.h>
bool gsr_cuda_load(gsr_cuda *self) {
memset(self, 0, sizeof(gsr_cuda));
dlerror(); /* clear */
void *lib = dlopen("libcuda.so.1", RTLD_LAZY);
if(!lib) {
lib = dlopen("libcuda.so", RTLD_LAZY);
if(!lib) {
fprintf(stderr, "gsr error: gsr_cuda_load failed: failed to load libcuda.so/libcuda.so.1, error: %s\n", dlerror());
return false;
}
}
dlsym_assign required_dlsym[] = {
{ (void**)&self->cuInit, "cuInit" },
{ (void**)&self->cuDeviceGetCount, "cuDeviceGetCount" },
{ (void**)&self->cuDeviceGet, "cuDeviceGet" },
{ (void**)&self->cuCtxCreate_v2, "cuCtxCreate_v2" },
{ (void**)&self->cuCtxDestroy_v2, "cuCtxDestroy_v2" },
{ (void**)&self->cuCtxPushCurrent_v2, "cuCtxPushCurrent_v2" },
{ (void**)&self->cuCtxPopCurrent_v2, "cuCtxPopCurrent_v2" },
{ (void**)&self->cuGetErrorString, "cuGetErrorString" },
{ (void**)&self->cuMemsetD8_v2, "cuMemsetD8_v2" },
{ (void**)&self->cuMemcpy2D_v2, "cuMemcpy2D_v2" },
{ (void**)&self->cuGraphicsGLRegisterImage, "cuGraphicsGLRegisterImage" },
{ (void**)&self->cuGraphicsResourceSetMapFlags, "cuGraphicsResourceSetMapFlags" },
{ (void**)&self->cuGraphicsMapResources, "cuGraphicsMapResources" },
{ (void**)&self->cuGraphicsUnmapResources, "cuGraphicsUnmapResources" },
{ (void**)&self->cuGraphicsUnregisterResource, "cuGraphicsUnregisterResource" },
{ (void**)&self->cuGraphicsSubResourceGetMappedArray, "cuGraphicsSubResourceGetMappedArray" },
{ NULL, NULL }
};
if(!dlsym_load_list(lib, required_dlsym)) {
fprintf(stderr, "gsr error: gsr_cuda_load failed: missing required symbols in libcuda.so/libcuda.so.1\n");
dlclose(lib);
memset(self, 0, sizeof(gsr_cuda));
return false;
}
CUresult res;
res = self->cuInit(0);
if(res != CUDA_SUCCESS) {
const char *err_str = "unknown";
self->cuGetErrorString(res, &err_str);
fprintf(stderr, "gsr error: gsr_cuda_load failed: cuInit failed, error: %s (result: %d)\n", err_str, res);
goto fail;
}
int nGpu = 0;
self->cuDeviceGetCount(&nGpu);
if(nGpu <= 0) {
fprintf(stderr, "gsr error: gsr_cuda_load failed: no cuda supported devices found\n");
goto fail;
}
CUdevice cu_dev;
res = self->cuDeviceGet(&cu_dev, 0);
if(res != CUDA_SUCCESS) {
const char *err_str = "unknown";
self->cuGetErrorString(res, &err_str);
fprintf(stderr, "gsr error: gsr_cuda_load failed: unable to get CUDA device, error: %s (result: %d)\n", err_str, res);
goto fail;
}
res = self->cuCtxCreate_v2(&self->cu_ctx, CU_CTX_SCHED_AUTO, cu_dev);
if(res != CUDA_SUCCESS) {
const char *err_str = "unknown";
self->cuGetErrorString(res, &err_str);
fprintf(stderr, "gsr error: gsr_cuda_load failed: unable to create CUDA context, error: %s (result: %d)\n", err_str, res);
goto fail;
}
self->library = lib;
return true;
fail:
dlclose(lib);
memset(self, 0, sizeof(gsr_cuda));
return false;
}
void gsr_cuda_unload(gsr_cuda *self) {
if(self->library) {
if(self->cu_ctx) {
self->cuCtxDestroy_v2(self->cu_ctx);
self->cu_ctx = 0;
}
dlclose(self->library);
memset(self, 0, sizeof(gsr_cuda));
}
}

198
src/gl.c Normal file
View File

@ -0,0 +1,198 @@
#include "../include/gl.h"
#include "../include/library_loader.h"
#include <string.h>
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));
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,7 @@
/*
Copyright (C) 2020 dec05eba
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "../include/sound.hpp"
extern "C" {
#include "../include/time.h"
}
#include <stdlib.h>
#include <stdio.h>
@ -43,14 +29,6 @@
} \
} while(false);
static double clock_get_monotonic_seconds() {
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 0;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (double)ts.tv_sec + (double)ts.tv_nsec * 0.000000001;
}
struct pa_handle {
pa_context *context;
pa_stream *stream;

10
src/time.c Normal file
View File

@ -0,0 +1,10 @@
#include "../include/time.h"
#include <time.h>
double clock_get_monotonic_seconds() {
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 0;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (double)ts.tv_sec + (double)ts.tv_nsec * 0.000000001;
}

176
src/window_texture.c Normal file
View File

@ -0,0 +1,176 @@
#include "../include/window_texture.h"
#include <X11/extensions/Xcomposite.h>
#include <stdio.h>
static int x11_supports_composite_named_window_pixmap(Display *display) {
int extension_major;
int extension_minor;
if(!XCompositeQueryExtension(display, &extension_major, &extension_minor))
return 0;
int major_version;
int minor_version;
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) {
window_texture->display = display;
window_texture->window = window;
window_texture->pixmap = None;
window_texture->glx_pixmap = None;
window_texture->texture_id = 0;
window_texture->redirected = 0;
window_texture->gl = gl;
if(!x11_supports_composite_named_window_pixmap(display))
return 1;
XCompositeRedirectWindow(display, window, CompositeRedirectAutomatic);
window_texture->redirected = 1;
return window_texture_on_resize(window_texture);
}
static void window_texture_cleanup(WindowTexture *self, int delete_texture) {
if(delete_texture && self->texture_id) {
self->gl->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(self->pixmap) {
XFreePixmap(self->display, self->pixmap);
self->pixmap = None;
}
}
void window_texture_deinit(WindowTexture *self) {
if(self->redirected) {
XCompositeUnredirectWindow(self->display, self->window, CompositeRedirectAutomatic);
self->redirected = 0;
}
window_texture_cleanup(self, 1);
}
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;
}
if(!found) {
fprintf(stderr, "No matching fb config found\n");
result = 1;
goto cleanup;
}
pixmap = XCompositeNameWindowPixmap(self->display, self->window);
if(!pixmap) {
result = 2;
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);
if(texture_id == 0) {
result = 4;
goto cleanup;
}
self->gl->glBindTexture(GL_TEXTURE_2D, texture_id);
} else {
self->gl->glBindTexture(GL_TEXTURE_2D, self->texture_id);
}
self->gl->glXBindTexImageEXT(self->display, glx_pixmap, GLX_FRONT_EXT, NULL);
glx_pixmap_bound = 1;
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->gl->glBindTexture(GL_TEXTURE_2D, 0);
XFree(configs);
self->pixmap = pixmap;
self->glx_pixmap = glx_pixmap;
if(texture_id != 0)
self->texture_id = texture_id;
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(pixmap) XFreePixmap(self->display, pixmap);
if(configs) XFree(configs);
return result;
}
unsigned int window_texture_get_opengl_texture_id(WindowTexture *self) {
return self->texture_id;
}