diff options
-rw-r--r-- | include/capture/kms.h | 4 | ||||
-rw-r--r-- | include/capture/nvfbc.h | 2 | ||||
-rw-r--r-- | include/capture/portal.h | 2 | ||||
-rw-r--r-- | include/capture/xcomposite.h | 2 | ||||
-rw-r--r-- | include/capture/ximage.h | 18 | ||||
-rw-r--r-- | include/egl.h | 1 | ||||
-rw-r--r-- | include/image_writer.h | 8 | ||||
-rw-r--r-- | include/utils.h | 2 | ||||
-rw-r--r-- | meson.build | 1 | ||||
-rw-r--r-- | src/capture/kms.c | 2 | ||||
-rw-r--r-- | src/capture/nvfbc.c | 1 | ||||
-rw-r--r-- | src/capture/portal.c | 3 | ||||
-rw-r--r-- | src/capture/xcomposite.c | 3 | ||||
-rw-r--r-- | src/capture/ximage.c | 247 | ||||
-rw-r--r-- | src/egl.c | 1 | ||||
-rw-r--r-- | src/encoder/video/nvenc.c | 17 | ||||
-rw-r--r-- | src/encoder/video/software.c | 18 | ||||
-rw-r--r-- | src/encoder/video/vulkan.c | 18 | ||||
-rw-r--r-- | src/image_writer.c | 77 | ||||
-rw-r--r-- | src/main.cpp | 107 | ||||
-rw-r--r-- | src/utils.c | 15 |
21 files changed, 403 insertions, 146 deletions
diff --git a/include/capture/kms.h b/include/capture/kms.h index f359783..ce09817 100644 --- a/include/capture/kms.h +++ b/include/capture/kms.h @@ -5,9 +5,7 @@ typedef struct { gsr_egl *egl; - const char *display_to_capture; /* if this is "screen", then the first monitor is captured. A copy is made of this */ - gsr_color_depth color_depth; - gsr_color_range color_range; + const char *display_to_capture; /* A copy is made of this */ bool hdr; bool record_cursor; int fps; diff --git a/include/capture/nvfbc.h b/include/capture/nvfbc.h index f93fdc0..07cc42f 100644 --- a/include/capture/nvfbc.h +++ b/include/capture/nvfbc.h @@ -11,8 +11,6 @@ typedef struct { vec2i pos; vec2i size; bool direct_capture; - gsr_color_depth color_depth; - gsr_color_range color_range; bool record_cursor; vec2i output_resolution; vec2i region_size; diff --git a/include/capture/portal.h b/include/capture/portal.h index 3989b98..74cdba9 100644 --- a/include/capture/portal.h +++ b/include/capture/portal.h @@ -5,8 +5,6 @@ typedef struct { gsr_egl *egl; - gsr_color_depth color_depth; - gsr_color_range color_range; bool record_cursor; bool restore_portal_session; /* If this is set to NULL then this defaults to $XDG_CONFIG_HOME/gpu-screen-recorder/restore_token ($XDG_CONFIG_HOME defaults to $HOME/.config) */ diff --git a/include/capture/xcomposite.h b/include/capture/xcomposite.h index 45eb481..bf6532e 100644 --- a/include/capture/xcomposite.h +++ b/include/capture/xcomposite.h @@ -8,9 +8,7 @@ typedef struct { gsr_egl *egl; unsigned long window; bool follow_focused; /* If this is set then |window| is ignored */ - gsr_color_range color_range; bool record_cursor; - gsr_color_depth color_depth; vec2i output_resolution; } gsr_capture_xcomposite_params; diff --git a/include/capture/ximage.h b/include/capture/ximage.h new file mode 100644 index 0000000..e6c3607 --- /dev/null +++ b/include/capture/ximage.h @@ -0,0 +1,18 @@ +#ifndef GSR_CAPTURE_XIMAGE_H +#define GSR_CAPTURE_XIMAGE_H + +#include "capture.h" +#include "../vec2.h" + +typedef struct { + gsr_egl *egl; + const char *display_to_capture; /* A copy is made of this */ + bool record_cursor; + vec2i output_resolution; + vec2i region_size; + vec2i region_position; +} gsr_capture_ximage_params; + +gsr_capture* gsr_capture_ximage_create(const gsr_capture_ximage_params *params); + +#endif /* GSR_CAPTURE_XIMAGE_H */ diff --git a/include/egl.h b/include/egl.h index 8caf89a..0d08270 100644 --- a/include/egl.h +++ b/include/egl.h @@ -235,6 +235,7 @@ struct gsr_egl { void (*glTexParameteriv)(unsigned int target, unsigned int pname, const 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 (*glTexSubImage2D)(unsigned int target, int level, int xoffset, int yoffset, int width, int height, unsigned format, unsigned type, const void *pixels); void (*glGetTexImage)(unsigned int target, int level, unsigned int format, unsigned int type, void *pixels); void (*glGenFramebuffers)(int n, unsigned int *framebuffers); void (*glBindFramebuffer)(unsigned int target, unsigned int framebuffer); diff --git a/include/image_writer.h b/include/image_writer.h index 79f549e..400edd0 100644 --- a/include/image_writer.h +++ b/include/image_writer.h @@ -11,7 +11,8 @@ typedef enum { } gsr_image_format; typedef enum { - GSR_IMAGE_WRITER_SOURCE_OPENGL + GSR_IMAGE_WRITER_SOURCE_OPENGL, + GSR_IMAGE_WRITER_SOURCE_MEMORY } gsr_image_writer_source; typedef struct { @@ -20,9 +21,12 @@ typedef struct { int width; int height; unsigned int texture; + const void *memory; /* Reference */ } gsr_image_writer; -bool gsr_image_writer_init(gsr_image_writer *self, gsr_image_writer_source source, gsr_egl *egl, int width, int height); +bool gsr_image_writer_init_opengl(gsr_image_writer *self, gsr_egl *egl, int width, int height); +/* |memory| is taken as a reference */ +bool gsr_image_writer_init_memory(gsr_image_writer *self, const void *memory, int width, int height); void gsr_image_writer_deinit(gsr_image_writer *self); /* Quality is between 1 and 100 where 100 is the max quality. Quality doesn't apply to lossless formats */ diff --git a/include/utils.h b/include/utils.h index 22bc4cf..fd340e8 100644 --- a/include/utils.h +++ b/include/utils.h @@ -69,4 +69,6 @@ bool vaapi_copy_egl_image_to_video_surface(gsr_egl *egl, EGLImage image, vec2i s vec2i scale_keep_aspect_ratio(vec2i from, vec2i to); +unsigned int gl_create_texture(gsr_egl *egl, int width, int height, int internal_format, unsigned int format, int filter); + #endif /* GSR_UTILS_H */ diff --git a/meson.build b/meson.build index 28d3b24..0991728 100644 --- a/meson.build +++ b/meson.build @@ -12,6 +12,7 @@ src = [ 'src/capture/capture.c', 'src/capture/nvfbc.c', 'src/capture/xcomposite.c', + 'src/capture/ximage.c', 'src/capture/kms.c', 'src/encoder/video/video.c', 'src/encoder/video/nvenc.c', diff --git a/src/capture/kms.c b/src/capture/kms.c index 9693c58..578fded 100644 --- a/src/capture/kms.c +++ b/src/capture/kms.c @@ -14,9 +14,7 @@ #include <xf86drm.h> #include <libdrm/drm_fourcc.h> -#include <libavcodec/avcodec.h> #include <libavutil/mastering_display_metadata.h> -#include <libavformat/avformat.h> #define FIND_CRTC_BY_NAME_TIMEOUT_SECONDS 2.0 diff --git a/src/capture/nvfbc.c b/src/capture/nvfbc.c index a0c8d29..188b321 100644 --- a/src/capture/nvfbc.c +++ b/src/capture/nvfbc.c @@ -13,7 +13,6 @@ #include <assert.h> #include <X11/Xlib.h> -#include <libavcodec/avcodec.h> typedef struct { gsr_capture_nvfbc_params params; diff --git a/src/capture/portal.c b/src/capture/portal.c index 893c07a..e065f02 100644 --- a/src/capture/portal.c +++ b/src/capture/portal.c @@ -8,10 +8,9 @@ #include <stdlib.h> #include <stdio.h> #include <unistd.h> +#include <limits.h> #include <assert.h> -#include <libavcodec/avcodec.h> - typedef struct { gsr_capture_portal_params params; diff --git a/src/capture/xcomposite.c b/src/capture/xcomposite.c index 2016a31..d8f4c27 100644 --- a/src/capture/xcomposite.c +++ b/src/capture/xcomposite.c @@ -12,9 +12,6 @@ #include <X11/Xlib.h> -#include <libavutil/frame.h> -#include <libavcodec/avcodec.h> - typedef struct { gsr_capture_xcomposite_params params; Display *display; diff --git a/src/capture/ximage.c b/src/capture/ximage.c new file mode 100644 index 0000000..259761d --- /dev/null +++ b/src/capture/ximage.c @@ -0,0 +1,247 @@ +#include "../../include/capture/ximage.h" +#include "../../include/utils.h" +#include "../../include/cursor.h" +#include "../../include/color_conversion.h" +#include "../../include/window/window.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include <X11/Xlib.h> + +/* TODO: update when monitors are reconfigured */ + +typedef struct { + gsr_capture_ximage_params params; + Display *display; + gsr_cursor cursor; + gsr_monitor monitor; + vec2i capture_pos; + vec2i capture_size; + unsigned int texture_id; + Window root_window; +} gsr_capture_ximage; + +static void gsr_capture_ximage_stop(gsr_capture_ximage *self) { + gsr_cursor_deinit(&self->cursor); + if(self->texture_id) { + self->params.egl->glDeleteTextures(1, &self->texture_id); + self->texture_id = 0; + } +} + +static int max_int(int a, int b) { + return a > b ? a : b; +} + +static int gsr_capture_ximage_start(gsr_capture *cap, gsr_capture_metadata *capture_metadata) { + gsr_capture_ximage *self = cap->priv; + self->root_window = DefaultRootWindow(self->display); + + if(gsr_cursor_init(&self->cursor, self->params.egl, self->display) != 0) { + gsr_capture_ximage_stop(self); + return -1; + } + + if(!get_monitor_by_name(self->params.egl, GSR_CONNECTION_X11, self->params.display_to_capture, &self->monitor)) { + fprintf(stderr, "gsr error: gsr_capture_ximage_start: failed to find monitor by name \"%s\"\n", self->params.display_to_capture); + gsr_capture_ximage_stop(self); + return -1; + } + + self->capture_pos = self->monitor.pos; + self->capture_size = self->monitor.size; + + if(self->params.region_size.x > 0 && self->params.region_size.y > 0) + self->capture_size = self->params.region_size; + + if(self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0) { + self->params.output_resolution = scale_keep_aspect_ratio(self->capture_size, self->params.output_resolution); + capture_metadata->width = self->params.output_resolution.x; + capture_metadata->height = self->params.output_resolution.y; + } else if(self->params.region_size.x > 0 && self->params.region_size.y > 0) { + capture_metadata->width = self->params.region_size.x; + capture_metadata->height = self->params.region_size.y; + } else { + capture_metadata->width = self->capture_size.x; + capture_metadata->height = self->capture_size.y; + } + + self->texture_id = gl_create_texture(self->params.egl, self->capture_size.x, self->capture_size.y, GL_RGB8, GL_RGB, GL_LINEAR); + if(self->texture_id == 0) { + fprintf(stderr, "gsr error: gsr_capture_ximage_start: failed to create texture\n"); + gsr_capture_ximage_stop(self); + return -1; + } + + return 0; +} + +static void gsr_capture_ximage_on_event(gsr_capture *cap, gsr_egl *egl) { + gsr_capture_ximage *self = cap->priv; + XEvent *xev = gsr_window_get_event_data(egl->window); + gsr_cursor_on_event(&self->cursor, xev); +} + +static bool gsr_capture_ximage_upload_to_texture(gsr_capture_ximage *self, int x, int y, int width, int height) { + const int max_width = XWidthOfScreen(DefaultScreenOfDisplay(self->display)); + const int max_height = XHeightOfScreen(DefaultScreenOfDisplay(self->display)); + + if(x < 0) + x = 0; + else if(x >= max_width) + x = max_width - 1; + + if(y < 0) + y = 0; + else if(y >= max_height) + y = max_height - 1; + + if(width < 0) + width = 0; + else if(x + width >= max_width) + width = max_width - x; + + if(height < 0) + height = 0; + else if(y + height >= max_height) + height = max_height - y; + + XImage *image = XGetImage(self->display, self->root_window, x, y, width, height, AllPlanes, ZPixmap); + if(!image) { + fprintf(stderr, "gsr error: gsr_capture_ximage_upload_to_texture: XGetImage failed\n"); + return false; + } + + bool success = false; + uint8_t *image_data = malloc(image->width * image->height * 3); + if(!image_data) { + fprintf(stderr, "gsr error: gsr_capture_ximage_upload_to_texture: failed to allocate image data\n"); + goto done; + } + + for(int y = 0; y < image->height; ++y) { + for(int x = 0; x < image->width; ++x) { + unsigned long pixel = XGetPixel(image, x, y); + unsigned char red = (pixel & image->red_mask) >> 16; + unsigned char green = (pixel & image->green_mask) >> 8; + unsigned char blue = pixel & image->blue_mask; + + const size_t texture_data_index = (x + y * image->width) * 3; + image_data[texture_data_index + 0] = red; + image_data[texture_data_index + 1] = green; + image_data[texture_data_index + 2] = blue; + } + } + + self->params.egl->glBindTexture(GL_TEXTURE_2D, self->texture_id); + self->params.egl->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image->width, image->height, GL_RGB, GL_UNSIGNED_BYTE, image_data); + self->params.egl->glBindTexture(GL_TEXTURE_2D, 0); + success = true; + + done: + free(image_data); + XDestroyImage(image); + return success; +} + +static int gsr_capture_ximage_capture(gsr_capture *cap, gsr_capture_metadata *capture_metdata, gsr_color_conversion *color_conversion) { + gsr_capture_ximage *self = cap->priv; + + const bool is_scaled = self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0; + vec2i output_size = is_scaled ? self->params.output_resolution : self->capture_size; + output_size = scale_keep_aspect_ratio(self->capture_size, output_size); + + const vec2i target_pos = { max_int(0, capture_metdata->width / 2 - output_size.x / 2), max_int(0, capture_metdata->height / 2 - output_size.y / 2) }; + gsr_capture_ximage_upload_to_texture(self, self->capture_pos.x + self->params.region_position.x, self->capture_pos.y + self->params.region_position.y, self->capture_size.x, self->capture_size.y); + + gsr_color_conversion_draw(color_conversion, self->texture_id, + target_pos, output_size, + (vec2i){0, 0}, self->capture_size, + 0.0f, false, GSR_SOURCE_COLOR_RGB); + + if(self->params.record_cursor && self->cursor.visible) { + const vec2d scale = { + self->capture_size.x == 0 ? 0 : (double)output_size.x / (double)self->capture_size.x, + self->capture_size.y == 0 ? 0 : (double)output_size.y / (double)self->capture_size.y + }; + + gsr_cursor_tick(&self->cursor, self->root_window); + + const vec2i cursor_pos = { + target_pos.x + (self->cursor.position.x - self->cursor.hotspot.x) * scale.x - self->capture_pos.x - self->params.region_position.x, + target_pos.y + (self->cursor.position.y - self->cursor.hotspot.y) * scale.y - self->capture_pos.y - self->params.region_position.y + }; + + self->params.egl->glEnable(GL_SCISSOR_TEST); + self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y); + + gsr_color_conversion_draw(color_conversion, self->cursor.texture_id, + cursor_pos, (vec2i){self->cursor.size.x * scale.x, self->cursor.size.y * scale.y}, + (vec2i){0, 0}, self->cursor.size, + 0.0f, false, GSR_SOURCE_COLOR_RGB); + + self->params.egl->glDisable(GL_SCISSOR_TEST); + } + + self->params.egl->glFlush(); + self->params.egl->glFinish(); + + return 0; +} + +static void gsr_capture_ximage_destroy(gsr_capture *cap) { + gsr_capture_ximage *self = cap->priv; + if(cap->priv) { + gsr_capture_ximage_stop(self); + free((void*)self->params.display_to_capture); + self->params.display_to_capture = NULL; + free(self); + cap->priv = NULL; + } + free(cap); +} + +gsr_capture* gsr_capture_ximage_create(const gsr_capture_ximage_params *params) { + if(!params) { + fprintf(stderr, "gsr error: gsr_capture_ximage_create params is NULL\n"); + return NULL; + } + + gsr_capture *cap = calloc(1, sizeof(gsr_capture)); + if(!cap) + return NULL; + + gsr_capture_ximage *cap_ximage = calloc(1, sizeof(gsr_capture_ximage)); + if(!cap_ximage) { + free(cap); + return NULL; + } + + const char *display_to_capture = strdup(params->display_to_capture); + if(!display_to_capture) { + free(cap); + free(cap_ximage); + return NULL; + } + + cap_ximage->params = *params; + cap_ximage->display = gsr_window_get_display(params->egl->window); + cap_ximage->params.display_to_capture = display_to_capture; + + *cap = (gsr_capture) { + .start = gsr_capture_ximage_start, + .on_event = gsr_capture_ximage_on_event, + .tick = NULL, + .should_stop = NULL, + .capture = gsr_capture_ximage_capture, + .uses_external_image = NULL, + .get_window_id = NULL, + .destroy = gsr_capture_ximage_destroy, + .priv = cap_ximage + }; + + return cap; +} @@ -288,6 +288,7 @@ static bool gsr_egl_load_gl(gsr_egl *self, void *library) { { (void**)&self->glTexParameteriv, "glTexParameteriv" }, { (void**)&self->glGetTexLevelParameteriv, "glGetTexLevelParameteriv" }, { (void**)&self->glTexImage2D, "glTexImage2D" }, + { (void**)&self->glTexSubImage2D, "glTexSubImage2D" }, { (void**)&self->glGetTexImage, "glGetTexImage" }, { (void**)&self->glGenFramebuffers, "glGenFramebuffers" }, { (void**)&self->glBindFramebuffer, "glBindFramebuffer" }, diff --git a/src/encoder/video/nvenc.c b/src/encoder/video/nvenc.c index c78e94a..7d8cf5d 100644 --- a/src/encoder/video/nvenc.c +++ b/src/encoder/video/nvenc.c @@ -65,21 +65,6 @@ static bool gsr_video_encoder_nvenc_setup_context(gsr_video_encoder_nvenc *self, return true; } -static unsigned int gl_create_texture(gsr_egl *egl, int width, int height, int internal_format, unsigned int format) { - unsigned int texture_id = 0; - egl->glGenTextures(1, &texture_id); - egl->glBindTexture(GL_TEXTURE_2D, texture_id); - egl->glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL); - - egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - - egl->glBindTexture(GL_TEXTURE_2D, 0); - return texture_id; -} - static bool cuda_register_opengl_texture(gsr_cuda *cuda, CUgraphicsResource *cuda_graphics_resource, CUarray *mapped_array, unsigned int texture_id) { CUresult res; res = cuda->cuGraphicsGLRegisterImage(cuda_graphics_resource, texture_id, GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_NONE); @@ -110,7 +95,7 @@ static bool gsr_video_encoder_nvenc_setup_textures(gsr_video_encoder_nvenc *self const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size for(int i = 0; i < 2; ++i) { - self->target_textures[i] = gl_create_texture(self->params.egl, video_codec_context->width / div[i], video_codec_context->height / div[i], self->params.color_depth == GSR_COLOR_DEPTH_8_BITS ? internal_formats_nv12[i] : internal_formats_p010[i], formats[i]); + self->target_textures[i] = gl_create_texture(self->params.egl, video_codec_context->width / div[i], video_codec_context->height / div[i], self->params.color_depth == GSR_COLOR_DEPTH_8_BITS ? internal_formats_nv12[i] : internal_formats_p010[i], formats[i], GL_LINEAR); if(self->target_textures[i] == 0) { fprintf(stderr, "gsr error: gsr_video_encoder_nvenc_setup_textures: failed to create opengl texture\n"); return false; diff --git a/src/encoder/video/software.c b/src/encoder/video/software.c index be227f2..33b8d9c 100644 --- a/src/encoder/video/software.c +++ b/src/encoder/video/software.c @@ -1,5 +1,6 @@ #include "../../../include/encoder/video/software.h" #include "../../../include/egl.h" +#include "../../../include/utils.h" #include <libavcodec/avcodec.h> #include <libavutil/frame.h> @@ -14,21 +15,6 @@ typedef struct { unsigned int target_textures[2]; } gsr_video_encoder_software; -static unsigned int gl_create_texture(gsr_egl *egl, int width, int height, int internal_format, unsigned int format) { - unsigned int texture_id = 0; - egl->glGenTextures(1, &texture_id); - egl->glBindTexture(GL_TEXTURE_2D, texture_id); - egl->glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL); - - egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - - egl->glBindTexture(GL_TEXTURE_2D, 0); - return texture_id; -} - static bool gsr_video_encoder_software_setup_textures(gsr_video_encoder_software *self, AVCodecContext *video_codec_context, AVFrame *frame) { int res = av_frame_get_buffer(frame, LINESIZE_ALIGNMENT); if(res < 0) { @@ -48,7 +34,7 @@ static bool gsr_video_encoder_software_setup_textures(gsr_video_encoder_software const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size for(int i = 0; i < 2; ++i) { - self->target_textures[i] = gl_create_texture(self->params.egl, video_codec_context->width / div[i], video_codec_context->height / div[i], self->params.color_depth == GSR_COLOR_DEPTH_8_BITS ? internal_formats_nv12[i] : internal_formats_p010[i], formats[i]); + self->target_textures[i] = gl_create_texture(self->params.egl, video_codec_context->width / div[i], video_codec_context->height / div[i], self->params.color_depth == GSR_COLOR_DEPTH_8_BITS ? internal_formats_nv12[i] : internal_formats_p010[i], formats[i], GL_LINEAR); if(self->target_textures[i] == 0) { fprintf(stderr, "gsr error: gsr_capture_kms_setup_cuda_textures: failed to create opengl texture\n"); return false; diff --git a/src/encoder/video/vulkan.c b/src/encoder/video/vulkan.c index d202ddd..24e24d6 100644 --- a/src/encoder/video/vulkan.c +++ b/src/encoder/video/vulkan.c @@ -71,22 +71,6 @@ static bool gsr_video_encoder_vulkan_setup_context(gsr_video_encoder_vulkan *sel return true; } -static unsigned int gl_create_texture(gsr_egl *egl, int width, int height, int internal_format, unsigned int format) { - unsigned int texture_id = 0; - egl->glGenTextures(1, &texture_id); - egl->glBindTexture(GL_TEXTURE_2D, texture_id); - //egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_TILING_EXT, GL_OPTIMAL_TILING_EXT); - egl->glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL); - - egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - - egl->glBindTexture(GL_TEXTURE_2D, 0); - return texture_id; -} - static AVVulkanDeviceContext* video_codec_context_get_vulkan_data(AVCodecContext *video_codec_context) { AVBufferRef *hw_frames_ctx = video_codec_context->hw_frames_ctx; if(!hw_frames_ctx) @@ -116,7 +100,7 @@ static bool gsr_video_encoder_vulkan_setup_textures(gsr_video_encoder_vulkan *se const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size for(int i = 0; i < 2; ++i) { - self->target_textures[i] = gl_create_texture(self->params.egl, video_codec_context->width / div[i], video_codec_context->height / div[i], self->params.color_depth == GSR_COLOR_DEPTH_8_BITS ? internal_formats_nv12[i] : internal_formats_p010[i], formats[i]); + self->target_textures[i] = gl_create_texture(self->params.egl, video_codec_context->width / div[i], video_codec_context->height / div[i], self->params.color_depth == GSR_COLOR_DEPTH_8_BITS ? internal_formats_nv12[i] : internal_formats_p010[i], formats[i], GL_LINEAR); if(self->target_textures[i] == 0) { fprintf(stderr, "gsr error: gsr_video_encoder_cuda_setup_textures: failed to create opengl texture\n"); return false; diff --git a/src/image_writer.c b/src/image_writer.c index d01a66c..3b4b24a 100644 --- a/src/image_writer.c +++ b/src/image_writer.c @@ -1,5 +1,6 @@ #include "../include/image_writer.h" #include "../include/egl.h" +#include "../include/utils.h" #define STB_IMAGE_WRITE_IMPLEMENTATION #include "../external/stb_image_write.h" @@ -9,29 +10,14 @@ #include <stdio.h> #include <assert.h> -static unsigned int gl_create_texture(gsr_egl *egl, int width, int height, int internal_format, unsigned int format) { - unsigned int texture_id = 0; - egl->glGenTextures(1, &texture_id); - egl->glBindTexture(GL_TEXTURE_2D, texture_id); - egl->glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL); - - egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - - egl->glBindTexture(GL_TEXTURE_2D, 0); - return texture_id; -} - /* TODO: Support hdr/10-bit */ -bool gsr_image_writer_init(gsr_image_writer *self, gsr_image_writer_source source, gsr_egl *egl, int width, int height) { - assert(source == GSR_IMAGE_WRITER_SOURCE_OPENGL); - self->source = source; +bool gsr_image_writer_init_opengl(gsr_image_writer *self, gsr_egl *egl, int width, int height) { + memset(self, 0, sizeof(*self)); + self->source = GSR_IMAGE_WRITER_SOURCE_OPENGL; self->egl = egl; self->width = width; self->height = height; - self->texture = gl_create_texture(self->egl, self->width, self->height, GL_RGB8, GL_RGB); /* TODO: use GL_RGB16 instead of GL_RGB8 for hdr/10-bit */ + self->texture = gl_create_texture(self->egl, self->width, self->height, GL_RGB8, GL_RGB, GL_LINEAR); /* TODO: use GL_RGB16 instead of GL_RGB8 for hdr/10-bit */ if(self->texture == 0) { fprintf(stderr, "gsr error: gsr_image_writer_init: failed to create texture\n"); return false; @@ -39,6 +25,15 @@ bool gsr_image_writer_init(gsr_image_writer *self, gsr_image_writer_source sourc return true; } +bool gsr_image_writer_init_memory(gsr_image_writer *self, const void *memory, int width, int height) { + memset(self, 0, sizeof(*self)); + self->source = GSR_IMAGE_WRITER_SOURCE_OPENGL; + self->width = width; + self->height = height; + self->memory = memory; + return true; +} + void gsr_image_writer_deinit(gsr_image_writer *self) { if(self->texture) { self->egl->glDeleteTextures(1, &self->texture); @@ -46,12 +41,30 @@ void gsr_image_writer_deinit(gsr_image_writer *self) { } } -bool gsr_image_writer_write_to_file(gsr_image_writer *self, const char *filepath, gsr_image_format image_format, int quality) { +static bool gsr_image_writer_write_memory_to_file(gsr_image_writer *self, const char *filepath, gsr_image_format image_format, int quality, const void *data) { if(quality < 1) quality = 1; else if(quality > 100) quality = 100; + bool success = false; + switch(image_format) { + case GSR_IMAGE_FORMAT_JPEG: + success = stbi_write_jpg(filepath, self->width, self->height, 3, data, quality); + break; + case GSR_IMAGE_FORMAT_PNG: + success = stbi_write_png(filepath, self->width, self->height, 3, data, 0); + break; + } + + if(!success) + fprintf(stderr, "gsr error: gsr_image_writer_write_to_file: failed to write image data to output file %s\n", filepath); + + return success; +} + +static bool gsr_image_writer_write_opengl_texture_to_file(gsr_image_writer *self, const char *filepath, gsr_image_format image_format, int quality) { + assert(self->source == GSR_IMAGE_WRITER_SOURCE_OPENGL); uint8_t *frame_data = malloc(self->width * self->height * 3); if(!frame_data) { fprintf(stderr, "gsr error: gsr_image_writer_write_to_file: failed to allocate memory for image frame\n"); @@ -67,19 +80,17 @@ bool gsr_image_writer_write_to_file(gsr_image_writer *self, const char *filepath self->egl->glFlush(); self->egl->glFinish(); - bool success = false; - switch(image_format) { - case GSR_IMAGE_FORMAT_JPEG: - success = stbi_write_jpg(filepath, self->width, self->height, 3, frame_data, quality); - break; - case GSR_IMAGE_FORMAT_PNG: - success = stbi_write_png(filepath, self->width, self->height, 3, frame_data, 0); - break; - } - - if(!success) - fprintf(stderr, "gsr error: gsr_image_writer_write_to_file: failed to write image data to output file %s\n", filepath); - + const bool success = gsr_image_writer_write_memory_to_file(self, filepath, image_format, quality, frame_data); free(frame_data); return success; } + +bool gsr_image_writer_write_to_file(gsr_image_writer *self, const char *filepath, gsr_image_format image_format, int quality) { + switch(self->source) { + case GSR_IMAGE_WRITER_SOURCE_OPENGL: + return gsr_image_writer_write_opengl_texture_to_file(self, filepath, image_format, quality); + case GSR_IMAGE_WRITER_SOURCE_MEMORY: + return gsr_image_writer_write_memory_to_file(self, filepath, image_format, quality, self->memory); + } + return false; +} diff --git a/src/main.cpp b/src/main.cpp index 2e08030..34a5808 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,7 @@ extern "C" { #include "../include/capture/nvfbc.h" #include "../include/capture/xcomposite.h" +#include "../include/capture/ximage.h" #include "../include/capture/kms.h" #ifdef GSR_PORTAL #include "../include/capture/portal.h" @@ -2427,7 +2428,7 @@ static std::string get_monitor_by_region_center(const gsr_egl *egl, vec2i region return result; } -static gsr_capture* create_monitor_capture(const std::string &window_str, vec2i output_resolution, vec2i region_size, vec2i region_position, gsr_egl *egl, int fps, bool hdr, gsr_color_range color_range, bool record_cursor, gsr_color_depth color_depth) { +static gsr_capture* create_monitor_capture(const std::string &window_str, vec2i output_resolution, vec2i region_size, vec2i region_position, gsr_egl *egl, int fps, bool hdr, bool record_cursor) { if(!monitor_capture_use_drm(egl->window, egl->gpu_info.vendor)) { const char *capture_target = window_str.c_str(); const bool direct_capture = strcmp(window_str.c_str(), "screen-direct") == 0 || strcmp(window_str.c_str(), "screen-direct-force") == 0; @@ -2443,8 +2444,6 @@ static gsr_capture* create_monitor_capture(const std::string &window_str, vec2i nvfbc_params.pos = { 0, 0 }; nvfbc_params.size = { 0, 0 }; nvfbc_params.direct_capture = direct_capture; - nvfbc_params.color_depth = color_depth; - nvfbc_params.color_range = color_range; nvfbc_params.record_cursor = record_cursor; nvfbc_params.output_resolution = output_resolution; nvfbc_params.region_size = region_size; @@ -2454,8 +2453,6 @@ static gsr_capture* create_monitor_capture(const std::string &window_str, vec2i gsr_capture_kms_params kms_params; kms_params.egl = egl; kms_params.display_to_capture = window_str.c_str(); - kms_params.color_depth = color_depth; - kms_params.color_range = color_range; kms_params.record_cursor = record_cursor; kms_params.hdr = hdr; kms_params.fps = fps; @@ -2466,9 +2463,33 @@ static gsr_capture* create_monitor_capture(const std::string &window_str, vec2i } } -static gsr_capture* create_capture_impl(std::string &window_str, vec2i output_resolution, vec2i region_size, vec2i region_position, bool wayland, gsr_egl *egl, int fps, bool hdr, gsr_color_range color_range, - bool record_cursor, bool restore_portal_session, const char *portal_session_token_filepath, - gsr_color_depth color_depth) +static void region_get_data(std::string &window_str, gsr_egl *egl, vec2i *region_size, vec2i *region_position) { + vec2i monitor_pos = {0, 0}; + vec2i monitor_size = {0, 0}; + window_str = get_monitor_by_region_center(egl, *region_position, *region_size, &monitor_pos, &monitor_size); + if(window_str.empty()) { + const bool is_x11 = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11; + const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM; + fprintf(stderr, "Error: the region %dx%d+%d+%d doesn't match any monitor. Available monitors and their regions:\n", region_size->x, region_size->y, region_position->x, region_position->y); + + MonitorOutputCallbackUserdata userdata; + userdata.window = egl->window; + for_each_active_monitor_output(egl->window, egl->card_path, connection_type, monitor_output_callback_print, &userdata); + _exit(51); + } + + // Capture whole monitor when region size is set to 0x0 + if(region_size->x == 0 && region_size->y == 0) { + region_position->x = 0; + region_position->y = 0; + } else { + region_position->x -= monitor_pos.x; + region_position->y -= monitor_pos.y; + } +} + +static gsr_capture* create_capture_impl(std::string &window_str, vec2i output_resolution, vec2i region_size, vec2i region_position, bool wayland, gsr_egl *egl, int fps, bool hdr, + bool record_cursor, bool restore_portal_session, const char *portal_session_token_filepath) { Window src_window_id = None; bool follow_focused = false; @@ -2496,8 +2517,6 @@ static gsr_capture* create_capture_impl(std::string &window_str, vec2i output_re gsr_capture_portal_params portal_params; portal_params.egl = egl; - portal_params.color_depth = color_depth; - portal_params.color_range = color_range; portal_params.record_cursor = record_cursor; portal_params.restore_portal_session = restore_portal_session; portal_params.portal_session_token_filepath = portal_session_token_filepath; @@ -2510,35 +2529,13 @@ static gsr_capture* create_capture_impl(std::string &window_str, vec2i output_re _exit(2); #endif } else if(strcmp(window_str.c_str(), "region") == 0) { - vec2i monitor_pos = {0, 0}; - vec2i monitor_size = {0, 0}; - window_str = get_monitor_by_region_center(egl, region_position, region_size, &monitor_pos, &monitor_size); - if(window_str.empty()) { - const bool is_x11 = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11; - const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM; - fprintf(stderr, "Error: the region %dx%d+%d+%d doesn't match any monitor. Available monitors and their regions:\n", region_size.x, region_size.y, region_position.x, region_position.y); - - MonitorOutputCallbackUserdata userdata; - userdata.window = egl->window; - for_each_active_monitor_output(egl->window, egl->card_path, connection_type, monitor_output_callback_print, &userdata); - _exit(51); - } - - // Capture whole monitor when region size is set to 0x0 - if(region_size.x == 0 && region_size.y == 0) { - region_position.x = 0; - region_position.y = 0; - } else { - region_position.x -= monitor_pos.x; - region_position.y -= monitor_pos.y; - } - - capture = create_monitor_capture(window_str, output_resolution, region_size, region_position, egl, fps, hdr, color_range, record_cursor, color_depth); + region_get_data(window_str, egl, ®ion_size, ®ion_position); + capture = create_monitor_capture(window_str, output_resolution, region_size, region_position, egl, fps, hdr, record_cursor); if(!capture) _exit(1); } else if(contains_non_hex_number(window_str.c_str())) { validate_monitor_get_valid(egl, window_str); - capture = create_monitor_capture(window_str, output_resolution, region_size, region_position, egl, fps, hdr, color_range, record_cursor, color_depth); + capture = create_monitor_capture(window_str, output_resolution, region_size, region_position, egl, fps, hdr, record_cursor); if(!capture) _exit(1); } else { @@ -2560,9 +2557,7 @@ static gsr_capture* create_capture_impl(std::string &window_str, vec2i output_re xcomposite_params.egl = egl; xcomposite_params.window = src_window_id; xcomposite_params.follow_focused = follow_focused; - xcomposite_params.color_range = color_range; xcomposite_params.record_cursor = record_cursor; - xcomposite_params.color_depth = color_depth; xcomposite_params.output_resolution = output_resolution; capture = gsr_capture_xcomposite_create(&xcomposite_params); if(!capture) @@ -2601,7 +2596,29 @@ static void capture_image_to_file(const char *filepath, std::string &window_str, bool record_cursor, bool restore_portal_session, const char *portal_session_token_filepath, VideoQuality video_quality) { const gsr_color_range color_range = image_format_to_color_range(image_format); const int fps = 60; - gsr_capture *capture = create_capture_impl(window_str, output_resolution, region_size, region_position, wayland, egl, fps, false, color_range, record_cursor, restore_portal_session, portal_session_token_filepath, GSR_COLOR_DEPTH_8_BITS); + gsr_capture *capture = nullptr; + switch(gsr_window_get_display_server(egl->window)) { + case GSR_DISPLAY_SERVER_X11: { + if(window_str == "region") + region_get_data(window_str, egl, ®ion_size, ®ion_position); + else + validate_monitor_get_valid(egl, window_str); + + gsr_capture_ximage_params ximage_params; + ximage_params.egl = egl; + ximage_params.display_to_capture = window_str.c_str(); + ximage_params.record_cursor = record_cursor; + ximage_params.output_resolution = output_resolution; + ximage_params.region_size = region_size; + ximage_params.region_position = region_position; + capture = gsr_capture_ximage_create(&ximage_params); + break; + } + case GSR_DISPLAY_SERVER_WAYLAND: { + capture = create_capture_impl(window_str, output_resolution, region_size, region_position, wayland, egl, fps, false, record_cursor, restore_portal_session, portal_session_token_filepath); + break; + } + } gsr_capture_metadata capture_metadata; capture_metadata.width = 0; @@ -2612,13 +2629,13 @@ static void capture_image_to_file(const char *filepath, std::string &window_str, int capture_result = gsr_capture_start(capture, &capture_metadata); if(capture_result != 0) { - fprintf(stderr, "gsr error: gsr_capture_start failed\n"); + fprintf(stderr, "gsr error: capture_image_to_file_wayland: gsr_capture_start failed\n"); _exit(capture_result); } gsr_image_writer image_writer; - if(!gsr_image_writer_init(&image_writer, GSR_IMAGE_WRITER_SOURCE_OPENGL, egl, capture_metadata.width, capture_metadata.height)) { - fprintf(stderr, "gsr error: gsr_image_write_gl_init failed\n"); + if(!gsr_image_writer_init_opengl(&image_writer, egl, capture_metadata.width, capture_metadata.height)) { + fprintf(stderr, "gsr error: capture_image_to_file_wayland: gsr_image_write_gl_init failed\n"); _exit(1); } @@ -2634,7 +2651,7 @@ static void capture_image_to_file(const char *filepath, std::string &window_str, gsr_color_conversion color_conversion; if(gsr_color_conversion_init(&color_conversion, &color_conversion_params) != 0) { - fprintf(stderr, "gsr error: gsr_capture_kms_setup_vaapi_textures: failed to create color conversion\n"); + fprintf(stderr, "gsr error: capture_image_to_file_wayland: failed to create color conversion\n"); _exit(1); } @@ -2664,7 +2681,7 @@ static void capture_image_to_file(const char *filepath, std::string &window_str, const int image_quality = video_quality_to_image_quality_value(video_quality); if(!gsr_image_writer_write_to_file(&image_writer, filepath, image_format, image_quality)) { - fprintf(stderr, "gsr error: failed to write opengl texture to image output file %s\n", filepath); + fprintf(stderr, "gsr error: capture_image_to_file_wayland: failed to write opengl texture to image output file %s\n", filepath); _exit(1); } @@ -3941,7 +3958,7 @@ int main(int argc, char **argv) { const AVCodec *video_codec_f = select_video_codec_with_fallback(&video_codec, video_codec_to_use, file_extension.c_str(), use_software_video_encoder, &egl, &low_power); const gsr_color_depth color_depth = video_codec_to_bit_depth(video_codec); - gsr_capture *capture = create_capture_impl(window_str, output_resolution, region_size, region_position, wayland, &egl, fps, video_codec_is_hdr(video_codec), color_range, record_cursor, restore_portal_session, portal_session_token_filepath, color_depth); + gsr_capture *capture = create_capture_impl(window_str, output_resolution, region_size, region_position, wayland, &egl, fps, video_codec_is_hdr(video_codec), record_cursor, restore_portal_session, portal_session_token_filepath); // (Some?) livestreaming services require at least one audio track to work. // If not audio is provided then create one silent audio track. @@ -4022,7 +4039,7 @@ int main(int argc, char **argv) { gsr_color_conversion color_conversion; if(gsr_color_conversion_init(&color_conversion, &color_conversion_params) != 0) { - fprintf(stderr, "gsr error: gsr_capture_kms_setup_vaapi_textures: failed to create color conversion\n"); + fprintf(stderr, "gsr error: main: failed to create color conversion\n"); _exit(1); } diff --git a/src/utils.c b/src/utils.c index 473ac96..f053eed 100644 --- a/src/utils.c +++ b/src/utils.c @@ -912,3 +912,18 @@ vec2i scale_keep_aspect_ratio(vec2i from, vec2i to) { return from; } + +unsigned int gl_create_texture(gsr_egl *egl, int width, int height, int internal_format, unsigned int format, int filter) { + unsigned int texture_id = 0; + egl->glGenTextures(1, &texture_id); + egl->glBindTexture(GL_TEXTURE_2D, texture_id); + egl->glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL); + + egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + + egl->glBindTexture(GL_TEXTURE_2D, 0); + return texture_id; +} |