Refactor xcomposite into abstract capture api
Refactor c++ files into c files, more usable
This commit is contained in:
parent
93d46b9767
commit
a7e0dbd833
@ -1,2 +0,0 @@
|
|||||||
BasedOnStyle: LLVM
|
|
||||||
IndentWidth: 4
|
|
@ -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 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.\
|
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`.\
|
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/).
|
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
1
TODO
@ -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).
|
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.
|
Use nvenc directly, which allows removing the use of cuda.
|
||||||
Fallback to nvfbc and window tracking if window capture fails.
|
Fallback to nvfbc and window tracking if window capture fails.
|
||||||
|
Handle xrandr monitor change in nvfbc.
|
7
build.sh
7
build.sh
@ -5,7 +5,12 @@ includes="$(pkg-config --cflags $dependencies)"
|
|||||||
libs="$(pkg-config --libs $dependencies) -ldl -pthread -lm"
|
libs="$(pkg-config --libs $dependencies) -ldl -pthread -lm"
|
||||||
gcc -c src/capture/capture.c -O2 -g0 -DNDEBUG $includes
|
gcc -c src/capture/capture.c -O2 -g0 -DNDEBUG $includes
|
||||||
gcc -c src/capture/nvfbc.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/sound.cpp -O2 -g0 -DNDEBUG $includes
|
||||||
g++ -c src/main.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"
|
echo "Successfully built gpu-screen-recorder"
|
@ -3,23 +3,28 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef struct AVCodecContext AVCodecContext;
|
||||||
typedef struct AVFrame AVFrame;
|
typedef struct AVFrame AVFrame;
|
||||||
|
|
||||||
typedef struct gsr_capture gsr_capture;
|
typedef struct gsr_capture gsr_capture;
|
||||||
|
|
||||||
struct gsr_capture {
|
struct gsr_capture {
|
||||||
int (*start)(gsr_capture *cap);
|
/* These methods should not be called manually. Call gsr_capture_* instead */
|
||||||
void (*stop)(gsr_capture *cap);
|
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);
|
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);
|
int gsr_capture_start(gsr_capture *cap, AVCodecContext *video_codec_context);
|
||||||
void gsr_capture_stop(gsr_capture *cap);
|
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);
|
int gsr_capture_capture(gsr_capture *cap, AVFrame *frame);
|
||||||
/* Calls |gsr_capture_stop| as well */
|
/* 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 */
|
#endif /* GSR_CAPTURE_CAPTURE_H */
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
#include "capture.h"
|
#include "capture.h"
|
||||||
#include "../vec2.h"
|
#include "../vec2.h"
|
||||||
|
|
||||||
|
typedef struct _XDisplay Display;
|
||||||
|
|
||||||
typedef struct {
|
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;
|
int fps;
|
||||||
vec2i pos;
|
vec2i pos;
|
||||||
vec2i size;
|
vec2i size;
|
||||||
|
16
include/capture/xcomposite.h
Normal file
16
include/capture/xcomposite.h
Normal 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 */
|
@ -1,9 +1,8 @@
|
|||||||
#pragma once
|
#ifndef GSR_CUDA_H
|
||||||
|
#define GSR_CUDA_H
|
||||||
|
|
||||||
#include "LibraryLoader.hpp"
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <dlfcn.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
// To prevent hwcontext_cuda.h from including cuda.h
|
// To prevent hwcontext_cuda.h from including cuda.h
|
||||||
#define CUDA_VERSION 11070
|
#define CUDA_VERSION 11070
|
||||||
@ -22,7 +21,7 @@ typedef struct CUctx_st *CUcontext;
|
|||||||
typedef struct CUstream_st *CUstream;
|
typedef struct CUstream_st *CUstream;
|
||||||
typedef struct CUarray_st *CUarray;
|
typedef struct CUarray_st *CUarray;
|
||||||
|
|
||||||
static const int CUDA_SUCCESS = 0;
|
#define CUDA_SUCCESS 0
|
||||||
|
|
||||||
typedef enum CUgraphicsMapResourceFlags_enum {
|
typedef enum CUgraphicsMapResourceFlags_enum {
|
||||||
CU_GRAPHICS_MAP_RESOURCE_FLAGS_NONE = 0x00,
|
CU_GRAPHICS_MAP_RESOURCE_FLAGS_NONE = 0x00,
|
||||||
@ -69,15 +68,19 @@ typedef struct CUDA_MEMCPY2D_st {
|
|||||||
} CUDA_MEMCPY2D_v2;
|
} CUDA_MEMCPY2D_v2;
|
||||||
typedef CUDA_MEMCPY2D_v2 CUDA_MEMCPY2D;
|
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;
|
typedef struct CUgraphicsResource_st *CUgraphicsResource;
|
||||||
|
|
||||||
struct Cuda {
|
typedef struct {
|
||||||
|
void *library;
|
||||||
|
CUcontext cu_ctx;
|
||||||
|
|
||||||
CUresult (*cuInit)(unsigned int Flags);
|
CUresult (*cuInit)(unsigned int Flags);
|
||||||
CUresult (*cuDeviceGetCount)(int *count);
|
CUresult (*cuDeviceGetCount)(int *count);
|
||||||
CUresult (*cuDeviceGet)(CUdevice *device, int ordinal);
|
CUresult (*cuDeviceGet)(CUdevice *device, int ordinal);
|
||||||
CUresult (*cuCtxCreate_v2)(CUcontext *pctx, unsigned int flags, CUdevice dev);
|
CUresult (*cuCtxCreate_v2)(CUcontext *pctx, unsigned int flags, CUdevice dev);
|
||||||
|
CUresult (*cuCtxDestroy_v2)(CUcontext ctx);
|
||||||
CUresult (*cuCtxPushCurrent_v2)(CUcontext ctx);
|
CUresult (*cuCtxPushCurrent_v2)(CUcontext ctx);
|
||||||
CUresult (*cuCtxPopCurrent_v2)(CUcontext *pctx);
|
CUresult (*cuCtxPopCurrent_v2)(CUcontext *pctx);
|
||||||
CUresult (*cuGetErrorString)(CUresult error, const char **pStr);
|
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 (*cuGraphicsGLRegisterImage)(CUgraphicsResource *pCudaResource, unsigned int image, unsigned int target, unsigned int Flags);
|
||||||
CUresult (*cuGraphicsResourceSetMapFlags)(CUgraphicsResource resource, unsigned int flags);
|
CUresult (*cuGraphicsResourceSetMapFlags)(CUgraphicsResource resource, unsigned int flags);
|
||||||
CUresult (*cuGraphicsMapResources)(unsigned int count, CUgraphicsResource *resources, CUstream hStream);
|
CUresult (*cuGraphicsMapResources)(unsigned int count, CUgraphicsResource *resources, CUstream hStream);
|
||||||
|
CUresult (*cuGraphicsUnmapResources)(unsigned int count, CUgraphicsResource *resources, CUstream hStream);
|
||||||
CUresult (*cuGraphicsUnregisterResource)(CUgraphicsResource resource);
|
CUresult (*cuGraphicsUnregisterResource)(CUgraphicsResource resource);
|
||||||
CUresult (*cuGraphicsSubResourceGetMappedArray)(CUarray *pArray, CUgraphicsResource resource, unsigned int arrayIndex, unsigned int mipLevel);
|
CUresult (*cuGraphicsSubResourceGetMappedArray)(CUarray *pArray, CUgraphicsResource resource, unsigned int arrayIndex, unsigned int mipLevel);
|
||||||
|
} gsr_cuda;
|
||||||
|
|
||||||
~Cuda() {
|
bool gsr_cuda_load(gsr_cuda *self);
|
||||||
if(library)
|
void gsr_cuda_unload(gsr_cuda *self);
|
||||||
dlclose(library);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool load() {
|
#endif /* GSR_CUDA_H */
|
||||||
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;
|
|
||||||
};
|
|
@ -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/X.h>
|
||||||
#include <X11/Xutil.h>
|
#include <X11/Xutil.h>
|
||||||
#include <dlfcn.h>
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
typedef XID GLXPixmap;
|
typedef XID GLXPixmap;
|
||||||
typedef XID GLXDrawable;
|
typedef XID GLXDrawable;
|
||||||
@ -25,6 +25,8 @@ typedef struct __GLXFBConfigRec *GLXFBConfig;
|
|||||||
#define GL_TEXTURE_WIDTH 0x1000
|
#define GL_TEXTURE_WIDTH 0x1000
|
||||||
#define GL_TEXTURE_HEIGHT 0x1001
|
#define GL_TEXTURE_HEIGHT 0x1001
|
||||||
#define GL_NEAREST 0x2600
|
#define GL_NEAREST 0x2600
|
||||||
|
#define GL_CLAMP_TO_EDGE 0x812F
|
||||||
|
#define GL_LINEAR 0x2601
|
||||||
|
|
||||||
#define GL_RENDERER 0x1F01
|
#define GL_RENDERER 0x1F01
|
||||||
|
|
||||||
@ -54,7 +56,16 @@ typedef struct __GLXFBConfigRec *GLXFBConfig;
|
|||||||
#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
|
#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
|
||||||
#define GLX_CONTEXT_FLAGS_ARB 0x2094
|
#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);
|
GLXPixmap (*glXCreatePixmap)(Display *dpy, GLXFBConfig config, Pixmap pixmap, const int *attribList);
|
||||||
void (*glXDestroyPixmap)(Display *dpy, GLXPixmap pixmap);
|
void (*glXDestroyPixmap)(Display *dpy, GLXPixmap pixmap);
|
||||||
void (*glXBindTexImageEXT)(Display *dpy, GLXDrawable drawable, int buffer, const int *attrib_list);
|
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 (*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 (*glTexImage2D)(unsigned int target, int level, int internalFormat, int width, int height, int border, unsigned int format, unsigned int type, const void *pixels);
|
||||||
void (*glCopyImageSubData)(unsigned int srcName, unsigned int srcTarget, int srcLevel, int srcX, int srcY, int srcZ, unsigned int dstName, unsigned int dstTarget, int dstLevel, int dstX, int dstY, int dstZ, int srcWidth, int srcHeight, int srcDepth);
|
void (*glCopyImageSubData)(unsigned int srcName, unsigned int srcTarget, int srcLevel, int srcX, int srcY, int srcZ, unsigned int dstName, unsigned int dstTarget, int dstLevel, int dstX, int dstY, int dstZ, int srcWidth, int srcHeight, int srcDepth);
|
||||||
|
} gsr_gl;
|
||||||
|
|
||||||
~GlLibrary() {
|
bool gsr_gl_load(gsr_gl *self, Display *dpy);
|
||||||
unload();
|
bool gsr_gl_make_context_current(gsr_gl *self);
|
||||||
}
|
void gsr_gl_unload(gsr_gl *self);
|
||||||
|
|
||||||
bool load() {
|
#endif /* GSR_GL_H */
|
||||||
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;
|
|
||||||
};
|
|
@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#ifndef GSR_LIBRARY_LOADER_H
|
||||||
|
#define GSR_LIBRARY_LOADER_H
|
||||||
|
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#include <stdio.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);
|
*dlsyms[i].func = dlsym_print_fail(handle, dlsyms[i].name, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif /* GSR_LIBRARY_LOADER_H */
|
6
include/time.h
Normal file
6
include/time.h
Normal 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
28
include/window_texture.h
Normal 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 */
|
@ -1,17 +1,47 @@
|
|||||||
#include "../../include/capture/capture.h"
|
#include "../../include/capture/capture.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
int gsr_capture_start(gsr_capture *cap) {
|
int gsr_capture_start(gsr_capture *cap, AVCodecContext *video_codec_context) {
|
||||||
return cap->start(cap);
|
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) {
|
void gsr_capture_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) {
|
||||||
cap->stop(cap);
|
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) {
|
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);
|
return cap->capture(cap, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
void gsr_capture_destroy(gsr_capture *cap) {
|
void gsr_capture_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
|
||||||
cap->destroy(cap);
|
cap->destroy(cap, video_codec_context);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
#include "../../include/capture/nvfbc.h"
|
#include "../../include/capture/nvfbc.h"
|
||||||
#include "../../external/NvFBC.h"
|
#include "../../external/NvFBC.h"
|
||||||
|
#include "../../include/cuda.h"
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <libavutil/hwcontext.h>
|
||||||
|
#include <libavutil/hwcontext_cuda.h>
|
||||||
#include <libavutil/frame.h>
|
#include <libavutil/frame.h>
|
||||||
|
#include <libavutil/version.h>
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
gsr_capture_nvfbc_params params;
|
gsr_capture_nvfbc_params params;
|
||||||
@ -14,6 +20,8 @@ typedef struct {
|
|||||||
PNVFBCCREATEINSTANCE nv_fbc_create_instance;
|
PNVFBCCREATEINSTANCE nv_fbc_create_instance;
|
||||||
NVFBC_API_FUNCTION_LIST nv_fbc_function_list;
|
NVFBC_API_FUNCTION_LIST nv_fbc_function_list;
|
||||||
bool fbc_handle_created;
|
bool fbc_handle_created;
|
||||||
|
|
||||||
|
gsr_cuda cuda;
|
||||||
} gsr_capture_nvfbc;
|
} gsr_capture_nvfbc;
|
||||||
|
|
||||||
#if defined(_WIN64) || defined(__LP64__)
|
#if defined(_WIN64) || defined(__LP64__)
|
||||||
@ -28,14 +36,17 @@ static int max_int(int a, int b) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Returns 0 on failure */
|
/* 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)
|
if(!outputs)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
for(uint32_t i = 0; i < num_outputs; ++i) {
|
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 outputs[i].dwId;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -95,16 +106,78 @@ static bool gsr_capture_nvfbc_load_library(gsr_capture *cap) {
|
|||||||
return true;
|
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;
|
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 x = max_int(cap_nvfbc->params.pos.x, 0);
|
||||||
const uint32_t y = max_int(cap_nvfbc->params.pos.y, 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 width = max_int(cap_nvfbc->params.size.x, 0);
|
||||||
const uint32_t height = max_int(cap_nvfbc->params.size.y, 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);
|
const bool capture_region = (x > 0 || y > 0 || width > 0 || height > 0);
|
||||||
|
|
||||||
NVFBCSTATUS status;
|
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);
|
status = cap_nvfbc->nv_fbc_function_list.nvFBCCreateHandle(&cap_nvfbc->nv_fbc_handle, &create_params);
|
||||||
if(status != NVFBC_SUCCESS) {
|
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));
|
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;
|
cap_nvfbc->fbc_handle_created = true;
|
||||||
@ -147,6 +220,8 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap) {
|
|||||||
goto error_cleanup;
|
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;
|
tracking_type = strcmp(cap_nvfbc->params.display_to_capture, "screen") == 0 ? NVFBC_TRACKING_SCREEN : NVFBC_TRACKING_OUTPUT;
|
||||||
if(tracking_type == NVFBC_TRACKING_OUTPUT) {
|
if(tracking_type == NVFBC_TRACKING_OUTPUT) {
|
||||||
if(!status_params.bXRandRAvailable) {
|
if(!status_params.bXRandRAvailable) {
|
||||||
@ -159,7 +234,7 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap) {
|
|||||||
goto error_cleanup;
|
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) {
|
if(output_id == 0) {
|
||||||
fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: display '%s' not found\n", cap_nvfbc->params.display_to_capture);
|
fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: display '%s' not found\n", cap_nvfbc->params.display_to_capture);
|
||||||
goto error_cleanup;
|
goto error_cleanup;
|
||||||
@ -198,6 +273,17 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap) {
|
|||||||
goto error_cleanup;
|
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;
|
return 0;
|
||||||
|
|
||||||
error_cleanup:
|
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->nv_fbc_function_list.nvFBCDestroyHandle(cap_nvfbc->nv_fbc_handle, &destroy_params);
|
||||||
cap_nvfbc->fbc_handle_created = false;
|
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;
|
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;
|
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;
|
NVFBC_DESTROY_CAPTURE_SESSION_PARAMS destroy_capture_params;
|
||||||
memset(&destroy_capture_params, 0, sizeof(destroy_capture_params));
|
memset(&destroy_capture_params, 0, sizeof(destroy_capture_params));
|
||||||
destroy_capture_params.dwVersion = NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER;
|
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) {
|
static int gsr_capture_nvfbc_capture(gsr_capture *cap, AVFrame *frame) {
|
||||||
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
|
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
|
||||||
if(!cap_nvfbc->library || !cap_nvfbc->fbc_handle_created)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
CUdeviceptr cu_device_ptr = 0;
|
CUdeviceptr cu_device_ptr = 0;
|
||||||
|
|
||||||
@ -274,11 +357,13 @@ static int gsr_capture_nvfbc_capture(gsr_capture *cap, AVFrame *frame) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gsr_capture_nvfbc_destroy(gsr_capture *cap) {
|
static void gsr_capture_nvfbc_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
|
||||||
if(cap) {
|
|
||||||
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
|
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
|
||||||
gsr_capture_nvfbc_stop(cap);
|
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) {
|
if(cap_nvfbc) {
|
||||||
|
gsr_cuda_unload(&cap_nvfbc->cuda);
|
||||||
dlclose(cap_nvfbc->library);
|
dlclose(cap_nvfbc->library);
|
||||||
free((void*)cap_nvfbc->params.display_to_capture);
|
free((void*)cap_nvfbc->params.display_to_capture);
|
||||||
free(cap->priv);
|
free(cap->priv);
|
||||||
@ -286,7 +371,6 @@ static void gsr_capture_nvfbc_destroy(gsr_capture *cap) {
|
|||||||
}
|
}
|
||||||
free(cap);
|
free(cap);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
gsr_capture* gsr_capture_nvfbc_create(const gsr_capture_nvfbc_params *params) {
|
gsr_capture* gsr_capture_nvfbc_create(const gsr_capture_nvfbc_params *params) {
|
||||||
if(!params) {
|
if(!params) {
|
||||||
@ -294,6 +378,11 @@ gsr_capture* gsr_capture_nvfbc_create(const gsr_capture_nvfbc_params *params) {
|
|||||||
return NULL;
|
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));
|
gsr_capture *cap = calloc(1, sizeof(gsr_capture));
|
||||||
if(!cap)
|
if(!cap)
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -317,16 +406,12 @@ gsr_capture* gsr_capture_nvfbc_create(const gsr_capture_nvfbc_params *params) {
|
|||||||
|
|
||||||
*cap = (gsr_capture) {
|
*cap = (gsr_capture) {
|
||||||
.start = gsr_capture_nvfbc_start,
|
.start = gsr_capture_nvfbc_start,
|
||||||
.stop = gsr_capture_nvfbc_stop,
|
.tick = NULL,
|
||||||
|
.should_stop = NULL,
|
||||||
.capture = gsr_capture_nvfbc_capture,
|
.capture = gsr_capture_nvfbc_capture,
|
||||||
.destroy = gsr_capture_nvfbc_destroy,
|
.destroy = gsr_capture_nvfbc_destroy,
|
||||||
.priv = cap_nvfbc
|
.priv = cap_nvfbc
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!gsr_capture_nvfbc_load_library(cap)) {
|
|
||||||
gsr_capture_nvfbc_destroy(cap);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cap;
|
return cap;
|
||||||
}
|
}
|
||||||
|
517
src/capture/xcomposite.c
Normal file
517
src/capture/xcomposite.c
Normal 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
100
src/cuda.c
Normal 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
198
src/gl.c
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
1039
src/main.cpp
1039
src/main.cpp
File diff suppressed because it is too large
Load Diff
@ -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"
|
#include "../include/sound.hpp"
|
||||||
|
extern "C" {
|
||||||
|
#include "../include/time.h"
|
||||||
|
}
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -43,14 +29,6 @@
|
|||||||
} \
|
} \
|
||||||
} while(false);
|
} 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 {
|
struct pa_handle {
|
||||||
pa_context *context;
|
pa_context *context;
|
||||||
pa_stream *stream;
|
pa_stream *stream;
|
||||||
|
10
src/time.c
Normal file
10
src/time.c
Normal 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
176
src/window_texture.c
Normal 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user