Compare commits

...

45 Commits
5.3.2 ... 5.3.8

Author SHA1 Message Date
dec05eba
db04b2e55e 5.3.8 2025-04-06 22:22:43 +02:00
dec05eba
5029906c34 Fallback to graphics shader instead of compute shader if the gpu doesn't support compute shader (either glsl 420 or opengl es glsl 310) 2025-04-06 21:52:15 +02:00
dec05eba
9de04e74ea Use opengl 4.20 for non-external texture (supports wider range of hardware) 2025-04-06 19:14:26 +02:00
dec05eba
54e5ec6193 Improve color quality, use texture instead of texelFetch 2025-04-06 18:31:23 +02:00
dec05eba
01040796ee 5.3.7 2025-04-04 23:56:16 +02:00
dec05eba
152d3d7536 m 2025-04-04 20:48:17 +02:00
dec05eba
ff01f69006 Cleanup 2025-04-04 20:36:51 +02:00
dec05eba
6910009e07 Fix region capture on hyprland with multiple monitors 2025-04-04 20:29:32 +02:00
dec05eba
4e614a18bc Remove unused code 2025-04-04 13:53:13 +02:00
dec05eba
a4845db319 5.3.6 2025-04-04 13:05:02 +02:00
dec05eba
8065ede251 rc-lookahead 0 2025-04-04 12:57:02 +02:00
dec05eba
baf0434854 Remove -preset and -multipass, replace with -tune performance|quality 2025-04-04 12:48:34 +02:00
dec05eba
0deb41afe1 Correct error message for preset 2025-04-04 01:47:19 +02:00
dec05eba
752e773fb7 Add -preset and -multipass options for higher quality video encoding on nvidia 2025-04-04 01:41:01 +02:00
dec05eba
12dd2cd3e1 Optimize compute shaders 2025-04-04 01:15:07 +02:00
dec05eba
33467cb7f4 m 2025-04-01 01:05:18 +02:00
dec05eba
1a61c64e3f Usage text 2025-04-01 01:04:24 +02:00
dec05eba
7ed9977068 5.3.5 2025-03-30 23:09:36 +02:00
dec05eba
8feb94f518 Fix incorrect region when monitor is rotated 2025-03-30 23:00:54 +02:00
dec05eba
6acd65a9c2 Remove -high-performance-encoding, always force enable it 2025-03-30 22:29:49 +02:00
dec05eba
56e2a82474 Fix cursor rotated background in compute shader on wayland 2025-03-30 22:11:33 +02:00
dec05eba
3e3d8a179f Fix incorrect region for region capture after compute shader change 2025-03-30 18:11:25 +02:00
dec05eba
9599834d9c 5.3.4 2025-03-30 16:42:26 +02:00
dec05eba
d37688e4c2 Compute shader cleanup 2025-03-30 16:06:01 +02:00
dec05eba
3b617ddc53 Cleanup 2025-03-30 15:17:37 +02:00
dec05eba
ec0411c248 TODO: scissor conversion to compute shader 2025-03-30 15:09:52 +02:00
dec05eba
ad26bc77db Fix incorrect compute size when scaling image 2025-03-30 13:46:06 +02:00
dec05eba
74865fad78 Highp rgb shader 2025-03-30 12:57:47 +02:00
dec05eba
82ec8e9630 Change quality parameters for 'very_high' (default) quality 2025-03-30 05:52:42 +02:00
dec05eba
a4713da02c Refine options text 2025-03-30 05:43:01 +02:00
dec05eba
7502f1ebbc GL_READ_WRITE -> GL_WRITE_ONLY 2025-03-30 05:13:44 +02:00
dec05eba
9e61479c9c Add -high-performance-encoding option to improve encoding performance (on amd) 2025-03-30 05:11:31 +02:00
dec05eba
bb58870a94 Compute shader cleanup 2025-03-30 05:02:32 +02:00
dec05eba
eb9761af1a Compute shader: nvidia: render full image (incorrect dispatch size) 2025-03-30 04:54:36 +02:00
dec05eba
96ca048856 Compute shader: add support for external texture, color component swapping (bgr to rgb) 2025-03-30 04:47:30 +02:00
dec05eba
c679b2fdb6 Use sampler2D for alpha blending compute shader instead of output image2D 2025-03-30 03:36:39 +02:00
dec05eba
b8a521a785 Reapply "WIP: use compute shader instead of graphics shader for better performance (especially on amd)"
This reverts commit f85a7ab205.
2025-03-29 15:38:05 +01:00
dec05eba
f85a7ab205 Revert "WIP: use compute shader instead of graphics shader for better performance (especially on amd)"
This reverts commit a41a32cb90.
2025-03-29 15:37:53 +01:00
dec05eba
a41a32cb90 WIP: use compute shader instead of graphics shader for better performance (especially on amd) 2025-03-29 13:29:10 +01:00
dec05eba
634a563bc0 Use highp instead of mediump 2025-03-19 10:51:19 +01:00
dec05eba
8e0b20df62 m 2025-03-18 23:03:22 +01:00
dec05eba
d2ab24121d Test broadcom (raspberry pi) support 2025-03-18 21:54:12 +01:00
dec05eba
e7a95f830a 5.3.3 2025-03-16 01:22:35 +01:00
dec05eba
1e0e24c818 todo 2025-03-16 01:16:18 +01:00
dec05eba
22c76fb8c3 Ffmpeg changes the api YET AGAIN, breaking recording on nvidia 2025-03-16 00:48:38 +01:00
33 changed files with 1256 additions and 747 deletions

4
.gitignore vendored
View File

@@ -4,8 +4,8 @@ compile_commands.json
tests/sibs-build/
tests/compile_commands.json
external/wlr-export-dmabuf-unstable-v1-client-protocol.h
external/wlr-export-dmabuf-unstable-v1-protocol.c
**/xdg-output-unstable-v1-client-protocol.h
**/xdg-output-unstable-v1-protocol.c
.clangd/
.cache/

View File

@@ -42,7 +42,7 @@ When recording GTA V at 4k on highest settings, fps drops from 60 to 23 when usi
GPU Screen Recorder also produces much smoother videos than OBS when GPU utilization is close to 100%, see comparison here: [https://www.youtube.com/watch?v=zfj4sNVLLLg](https://www.youtube.com/watch?v=zfj4sNVLLLg).\
GPU Screen Recorder has much better performance than OBS Studio even with version 30.2 that does "zero-copy" recording and encoding, see: [https://www.youtube.com/watch?v=jdroRjibsDw](https://www.youtube.com/watch?v=jdroRjibsDw).\
It is recommended to save the video to a SSD because of the large file size, which a slow HDD might not be fast enough to handle. Using variable framerate mode (-fm vfr) which is the default is also recommended as this reduces encoding load. Ultra quality is also overkill most of the time, very high (the default) or lower quality is usually enough.\
Note that recording on AMD can have some performance issues on Wayland in the recording itself when recording without desktop portal unless your mesa version is 25.0.0 or greater.
Note that for best performance you should close other screen recorders such as OBS Studio when using GPU Screen Recorder even if they are not recording, since they can affect performance even when idle. This is the case with OBS Studio.
## Note about optimal performance on NVIDIA
NVIDIA driver has a "feature" (read: bug) where it will downclock memory transfer rate when a program uses cuda (or nvenc, which uses cuda), such as GPU Screen Recorder. To work around this bug, GPU Screen Recorder can overclock your GPU memory transfer rate to it's normal optimal level.\
To enable overclocking for optimal performance use the `-oc` option when running GPU Screen Recorder. You also need to have "Coolbits" NVIDIA X setting set to "12" to enable overclocking. You can automatically add this option if you run `sudo nvidia-xconfig --cool-bits=12` and then reboot your computer.\
@@ -87,6 +87,7 @@ These are the dependencies needed to build GPU Screen Recorder:
* libcap
* wayland-client
* wayland-egl
* wayland-scanner
## Runtime dependencies
There are also additional dependencies needed at runtime depending on your GPU vendor:
@@ -185,3 +186,5 @@ To fix this you can either record the video in .mkv format or constant frame rat
## Colors look incorrect when recording HDR (with hevc_hdr/av1_hdr) or using an ICC profile
KDE Plasma version 6.2 broke HDR and ICC profiles for screen recorders. This was changed in KDE plasma version 6.3 and recording HDR works now, as long as you set HDR brightness to 100% (which means setting "Maximum SDR Brightness" in KDE plasma display settings to 203) and set color accuracy to "Prefer color accuracy". If you want to convert HDR to SDR then record with desktop portal option (`-w portal`) instead.
I don't know how well recording HDR works in wayland compositors other than KDE plasma.
## GPU Screen Recorder starts lagging after 30-40 minutes when launching GPU Screen Recorder from steam command launcher
This is a [steam issue](https://github.com/ValveSoftware/steam-for-linux/issues/11446). Prepend the gpu-screen-recorder command with `LD_PREFIX=""`, for example `LD_PREFIX="" gpu-screen-recorder -w screen -o video.mp4`.

15
TODO
View File

@@ -4,7 +4,6 @@ See https://trac.ffmpeg.org/wiki/EncodingForStreamingSites for optimizing stream
Look at VK_EXT_external_memory_dma_buf.
Use mov+faststart.
Allow recording all monitors/selected monitor without nvfbc by recording the compositor proxy window and only recording the part that matches the monitor(s).
Allow recording a region by recording the compositor proxy window / nvfbc window and copying part of it.
Support amf and qsv.
Disable flipping on nvidia? this might fix some stuttering issues on some setups. See NvCtrlGetAttribute/NvCtrlSetAttributeAndGetStatus NV_CTRL_SYNC_TO_VBLANK https://github.com/NVIDIA/nvidia-settings/blob/d5f022976368cbceb2f20b838ddb0bf992f0cfb9/src/gtk%2B-2.x/ctkopengl.c.
Replays seem to have some issues with audio/video. Why?
@@ -250,3 +249,17 @@ Support high quality scaling with -s by using lanczos.
Support spanning multiple monitors with region capture. This would also allow the user to record multiple monitors at the same time, the same way screen-direct works on nvidia x11.
When webcam support is added also support v4l2loopback? this is done by using avdevice_register_all(); and -c v4l2 -o /dev/video0; but it needs to output raw data as well instead of h264 and possibly yuv420p. Maybe add a -k yuv420p option to do that.
Do proper exit, to call gsr_capture_destroy which will properly stop gsr-kms-server. Otherwise there can be zombie gsr-kms-server on error.
Replace all scissors with clearing textures if the cursor hits the outside of the frame image.
Cursor position might be slightly wrong on rotated monitor.
External texture doesn't work on nvidia x11, probably because of glx context (requires gles es). External texture is not used on nvidia x11 right now so it's not an issue.
Add option to save replay buffer on disk instead of ram.
nvfbc capture cursor with cursor.h instead and composite that on top. This allows us to also always get a cursor in direct capture mode. This could possible give better performance as well.
Maybe remove external shader code and make a simple external to internal texture converter (compute shader), to reduce texture sampling. Maybe this is faster?

View File

@@ -2,9 +2,14 @@
#define GSR_COLOR_CONVERSION_H
#include "shader.h"
#include "defs.h"
#include "vec2.h"
#include <stdbool.h>
#define GSR_COLOR_CONVERSION_MAX_COMPUTE_SHADERS 12
#define GSR_COLOR_CONVERSION_MAX_GRAPHICS_SHADERS 6
#define GSR_COLOR_CONVERSION_MAX_FRAMEBUFFERS 2
typedef enum {
GSR_COLOR_RANGE_LIMITED,
GSR_COLOR_RANGE_FULL
@@ -26,10 +31,24 @@ typedef enum {
GSR_DESTINATION_COLOR_RGB8
} gsr_destination_color;
typedef enum {
GSR_ROT_0,
GSR_ROT_90,
GSR_ROT_180,
GSR_ROT_270
} gsr_rotation;
typedef struct {
int rotation_matrix;
int offset;
int rotation;
} gsr_color_uniforms;
} gsr_color_graphics_uniforms;
typedef struct {
int rotation_matrix;
int source_position;
int target_position;
int scale;
} gsr_color_compute_uniforms;
typedef struct {
gsr_egl *egl;
@@ -45,19 +64,30 @@ typedef struct {
typedef struct {
gsr_color_conversion_params params;
gsr_color_uniforms uniforms[4];
gsr_shader shaders[4];
gsr_color_compute_uniforms compute_uniforms[GSR_COLOR_CONVERSION_MAX_COMPUTE_SHADERS];
gsr_shader compute_shaders[GSR_COLOR_CONVERSION_MAX_COMPUTE_SHADERS];
unsigned int framebuffers[2];
/* These are only loader if compute shaders (of the same type) fail to load */
gsr_color_graphics_uniforms graphics_uniforms[GSR_COLOR_CONVERSION_MAX_GRAPHICS_SHADERS];
gsr_shader graphics_shaders[GSR_COLOR_CONVERSION_MAX_GRAPHICS_SHADERS];
bool compute_shaders_failed_to_load;
bool external_compute_shaders_failed_to_load;
unsigned int framebuffers[GSR_COLOR_CONVERSION_MAX_FRAMEBUFFERS];
unsigned int vertex_array_object_id;
unsigned int vertex_buffer_object_id;
int max_local_size_dim;
} gsr_color_conversion;
int gsr_color_conversion_init(gsr_color_conversion *self, const gsr_color_conversion_params *params);
void gsr_color_conversion_deinit(gsr_color_conversion *self);
void gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_id, vec2i source_pos, vec2i source_size, vec2i texture_pos, vec2i texture_size, float rotation, bool external_texture, gsr_source_color source_color);
void gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_id, vec2i destination_pos, vec2i destination_size, vec2i source_pos, vec2i source_size, vec2i texture_size, gsr_rotation rotation, gsr_source_color source_color, bool external_texture, bool alpha_blending);
void gsr_color_conversion_clear(gsr_color_conversion *self);
gsr_rotation gsr_monitor_rotation_to_rotation(gsr_monitor_rotation monitor_rotation);
#endif /* GSR_COLOR_CONVERSION_H */

View File

@@ -6,7 +6,8 @@
typedef enum {
GSR_GPU_VENDOR_AMD,
GSR_GPU_VENDOR_INTEL,
GSR_GPU_VENDOR_NVIDIA
GSR_GPU_VENDOR_NVIDIA,
GSR_GPU_VENDOR_BROADCOM
} gsr_gpu_vendor;
typedef struct {

View File

@@ -98,7 +98,7 @@ typedef void(*__GLXextFuncPtr)(void);
#define GL_TEXTURE_EXTERNAL_OES 0x8D65
#define GL_RED 0x1903
#define GL_GREEN 0x1904
#define GL_BLUE 0x1905
#define GL_BLUE 0x1905
#define GL_ALPHA 0x1906
#define GL_TEXTURE_SWIZZLE_RGBA 0x8E46
#define GL_RG 0x8227
@@ -111,6 +111,7 @@ typedef void(*__GLXextFuncPtr)(void);
#define GL_R16 0x822A
#define GL_RG16 0x822C
#define GL_RGB16 0x8054
#define GL_RGBA32F 0x8814
#define GL_UNSIGNED_BYTE 0x1401
#define GL_COLOR_BUFFER_BIT 0x00004000
#define GL_TEXTURE_WRAP_S 0x2802
@@ -134,6 +135,16 @@ typedef void(*__GLXextFuncPtr)(void);
#define GL_SCISSOR_TEST 0x0C11
#define GL_PACK_ALIGNMENT 0x0D05
#define GL_UNPACK_ALIGNMENT 0x0CF5
#define GL_READ_ONLY 0x88B8
#define GL_WRITE_ONLY 0x88B9
#define GL_READ_WRITE 0x88BA
#define GL_MAX_COMPUTE_FIXED_GROUP_INVOCATIONS 0x90EB
#define GL_TEXTURE0 0x84C0
#define GL_TEXTURE1 0x84C1
#define GL_CLAMP_TO_BORDER 0x812D
#define GL_TEXTURE_BORDER_COLOR 0x1004
#define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020
#define GL_ALL_BARRIER_BITS 0xFFFFFFFF
#define GL_VENDOR 0x1F00
#define GL_RENDERER 0x1F01
@@ -143,6 +154,7 @@ typedef void(*__GLXextFuncPtr)(void);
#define GL_INFO_LOG_LENGTH 0x8B84
#define GL_FRAGMENT_SHADER 0x8B30
#define GL_VERTEX_SHADER 0x8B31
#define GL_COMPUTE_SHADER 0x91B9
#define GL_COMPILE_STATUS 0x8B81
#define GL_LINK_STATUS 0x8B82
@@ -230,9 +242,12 @@ struct gsr_egl {
void (*glClearColor)(float red, float green, float blue, float alpha);
void (*glGenTextures)(int n, unsigned int *textures);
void (*glDeleteTextures)(int n, const unsigned int *texture);
void (*glActiveTexture)(unsigned int texture);
void (*glBindTexture)(unsigned int target, unsigned int texture);
void (*glBindImageTexture)(unsigned int unit, unsigned int texture, int level, unsigned char layered, int layer, unsigned int access, unsigned int format);
void (*glTexParameteri)(unsigned int target, unsigned int pname, int param);
void (*glTexParameteriv)(unsigned int target, unsigned int pname, const int *params);
void (*glTexParameterfv)(unsigned int target, unsigned int pname, const float *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);
@@ -240,6 +255,8 @@ struct gsr_egl {
void (*glGenFramebuffers)(int n, unsigned int *framebuffers);
void (*glBindFramebuffer)(unsigned int target, unsigned int framebuffer);
void (*glDeleteFramebuffers)(int n, const unsigned int *framebuffers);
void (*glDispatchCompute)(unsigned int num_groups_x, unsigned int num_groups_y, unsigned int num_groups_z);
void (*glMemoryBarrier)(unsigned int barriers);
void (*glViewport)(int x, int y, int width, int height);
void (*glFramebufferTexture2D)(unsigned int target, unsigned int attachment, unsigned int textarget, unsigned int texture, int level);
void (*glDrawBuffers)(int n, const unsigned int *bufs);
@@ -276,11 +293,15 @@ struct gsr_egl {
int (*glGetUniformLocation)(unsigned int program, const char *name);
void (*glUniform1f)(int location, float v0);
void (*glUniform2f)(int location, float v0, float v1);
void (*glUniform1i)(int location, int v0);
void (*glUniform2i)(int location, int v0, int v1);
void (*glUniformMatrix2fv)(int location, int count, unsigned char transpose, const float *value);
void (*glDebugMessageCallback)(GLDEBUGPROC callback, const void *userParam);
void (*glScissor)(int x, int y, int width, int height);
void (*glReadPixels)(int x, int y, int width, int height, unsigned int format, unsigned int type, void *pixels);
void* (*glMapBuffer)(unsigned int target, unsigned int access);
unsigned char (*glUnmapBuffer)(unsigned int target);
void (*glGetIntegerv)(unsigned int pname, int *params);
};
bool gsr_egl_load(gsr_egl *self, gsr_window *window, bool is_monitor_capture, bool enable_debug);

View File

@@ -25,7 +25,7 @@ typedef struct {
} gsr_image_writer;
bool gsr_image_writer_init_opengl(gsr_image_writer *self, gsr_egl *egl, int width, int height);
/* |memory| is taken as a reference */
/* |memory| is taken as a reference. The data is expected to be in rgba8 format (8 bit rgba) */
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);

View File

@@ -1,6 +1,8 @@
#ifndef GSR_SHADER_H
#define GSR_SHADER_H
#include <stdbool.h>
typedef struct gsr_egl gsr_egl;
typedef struct {
@@ -9,11 +11,13 @@ typedef struct {
} gsr_shader;
/* |vertex_shader| or |fragment_shader| may be NULL */
int gsr_shader_init(gsr_shader *self, gsr_egl *egl, const char *vertex_shader, const char *fragment_shader);
int gsr_shader_init(gsr_shader *self, gsr_egl *egl, const char *vertex_shader, const char *fragment_shader, const char *compute_shader);
void gsr_shader_deinit(gsr_shader *self);
int gsr_shader_bind_attribute_location(gsr_shader *self, const char *attribute, int location);
void gsr_shader_use(gsr_shader *self);
void gsr_shader_use_none(gsr_shader *self);
void gsr_shader_enable_debug_output(bool enable);
#endif /* GSR_SHADER_H */

View File

@@ -50,8 +50,6 @@ drm_connector_type_count* drm_connector_types_get_index(drm_connector_type_count
uint32_t monitor_identifier_from_type_and_count(int monitor_type_index, int monitor_type_count);
bool gl_get_gpu_info(gsr_egl *egl, gsr_gpu_info *info);
bool version_greater_than(int major, int minor, int patch, int other_major, int other_minor, int other_patch);
bool gl_driver_version_greater_than(const gsr_gpu_info *gpu_info, int major, int minor, int patch);
bool try_card_has_valid_plane(const char *card_path);
/* |output| should be at least 128 bytes in size */
@@ -64,8 +62,6 @@ int create_directory_recursive(char *path);
/* |img_attr| needs to be at least 44 in size */
void setup_dma_buf_attrs(intptr_t *img_attr, uint32_t format, uint32_t width, uint32_t height, const int *fds, const uint32_t *offsets, const uint32_t *pitches, const uint64_t *modifiers, int num_planes, bool use_modifier);
bool video_codec_context_is_vaapi(AVCodecContext *video_codec_context);
bool vaapi_copy_drm_planes_to_video_surface(AVCodecContext *video_codec_context, AVFrame *video_frame, vec2i source_pos, vec2i source_size, vec2i dest_pos, vec2i dest_size, uint32_t format, vec2i size, const int *fds, const uint32_t *offsets, const uint32_t *pitches, const uint64_t *modifiers, int num_planes);
bool vaapi_copy_egl_image_to_video_surface(gsr_egl *egl, EGLImage image, vec2i source_pos, vec2i source_size, vec2i dest_pos, vec2i dest_size, AVCodecContext *video_codec_context, AVFrame *video_frame);
vec2i scale_keep_aspect_ratio(vec2i from, vec2i to);

View File

@@ -207,7 +207,7 @@ static uint32_t plane_get_properties(int drmfd, uint32_t plane_id, int *x, int *
return property_mask;
}
/* Returns 0 if not found */
/* Returns NULL if not found */
static const connector_crtc_pair* get_connector_pair_by_crtc_id(const connector_to_crtc_map *c2crtc_map, uint32_t crtc_id) {
for(int i = 0; i < c2crtc_map->num_maps; ++i) {
if(c2crtc_map->maps[i].crtc_id == crtc_id)
@@ -433,81 +433,6 @@ static double clock_get_monotonic_seconds(void) {
return (double)ts.tv_sec + (double)ts.tv_nsec * 0.000000001;
}
// static bool readlink_realpath(const char *filepath, char *buffer) {
// char symlinked_path[PATH_MAX];
// ssize_t bytes_written = readlink(filepath, symlinked_path, sizeof(symlinked_path) - 1);
// if(bytes_written == -1 && errno == EINVAL) {
// /* Not a symlink */
// snprintf(symlinked_path, sizeof(symlinked_path), "%s", filepath);
// } else if(bytes_written == -1) {
// return false;
// } else {
// symlinked_path[bytes_written] = '\0';
// }
// if(!realpath(symlinked_path, buffer))
// return false;
// return true;
// }
// static void file_get_directory(char *filepath) {
// char *end = strrchr(filepath, '/');
// if(end == NULL)
// filepath[0] = '\0';
// else
// *end = '\0';
// }
// static bool string_ends_with(const char *str, const char *ends_with) {
// const int len = strlen(str);
// const int ends_with_len = strlen(ends_with);
// return len >= ends_with_len && memcmp(str + len - ends_with_len, ends_with, ends_with_len) == 0;
// }
// This is not foolproof, but the assumption is that gsr-kms-server and gpu-screen-recorder are installed in the same directory
// in a location that only the root user can write to (usually /usr/bin or /usr/local/bin) and if the client runs from that location
// and is called gpu-screen-recorder then gsr-kms-server can only be used by a malicious program if the malicious program
// had root access, to modify that program install directory.
// static bool is_remote_peer_program_gpu_screen_recorder(int socket_fd) {
// // TODO: Use SO_PEERPIDFD on kernel >= 6.5 to avoid a race condition in the /proc/<pid> check
// struct ucred cred;
// socklen_t ucred_len = sizeof(cred);
// if(getsockopt(socket_fd, SOL_SOCKET, SO_PEERCRED, &cred, &ucred_len) == -1) {
// fprintf(stderr, "kms server error: failed to get peer credentials, error: %s\n", strerror(errno));
// return false;
// }
// char self_directory[PATH_MAX];
// if(!readlink_realpath("/proc/self/exe", self_directory)) {
// fprintf(stderr, "kms server error: failed to resolve /proc/self/exe\n");
// return false;
// }
// file_get_directory(self_directory);
// char peer_directory[PATH_MAX];
// char peer_exe_path[PATH_MAX];
// snprintf(peer_exe_path, sizeof(peer_exe_path), "/proc/%d/exe", (int)cred.pid);
// if(!readlink_realpath(peer_exe_path, peer_directory)) {
// fprintf(stderr, "kms server error: failed to resolve /proc/self/exe\n");
// return false;
// }
// if(!string_ends_with(peer_directory, "/gpu-screen-recorder")) {
// fprintf(stderr, "kms server error: only gpu-screen-recorder can use gsr-kms-server. client program location is %s\n", peer_directory);
// return false;
// }
// file_get_directory(peer_directory);
// if(strcmp(self_directory, peer_directory) != 0) {
// fprintf(stderr, "kms server error: the client program is in directory %s but only programs in %s can run gsr-kms-server\n", peer_directory, self_directory);
// return false;
// }
// return true;
// }
int main(int argc, char **argv) {
int res = 0;
int socket_fd = 0;
@@ -579,11 +504,6 @@ int main(int argc, char **argv) {
goto done;
}
// if(!is_remote_peer_program_gpu_screen_recorder(socket_fd)) {
// res = 3;
// goto done;
// }
for(;;) {
gsr_kms_request request;
request.version = 0;

View File

@@ -1,4 +1,4 @@
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.3.2', default_options : ['warning_level=2'])
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.3.8', default_options : ['warning_level=2'])
add_project_arguments('-Wshadow', language : ['c', 'cpp'])
if get_option('buildtype') == 'debug'
@@ -41,6 +41,9 @@ src = [
'src/main.cpp',
]
subdir('protocol')
src += protocol_src
dep = [
dependency('threads'),
dependency('libavcodec'),

View File

@@ -1,5 +1,5 @@
option('systemd', type : 'boolean', value : true, description : 'Install systemd service file')
option('capabilities', type : 'boolean', value : true, description : 'Set binary admin capability on gsr-kms-server binary to remove password prompt when recording monitor (without desktop portal option) on amd/intel or nvidia wayland')
option('nvidia_suspend_fix', type : 'boolean', value : true, description : 'Install nvidia modprobe config file to tell nvidia driver to preserve video memory on suspend. This is a workaround for an nvidia driver bug that breaks cuda (and gpu screen recorder) on suspend')
option('portal', type : 'boolean', value : true, description : 'Build with support for xdg desktop portal ScreenCast capture (wayland only) (-w portal option)')
option('app_audio', type : 'boolean', value : true, description : 'Build with support for recording a single audio source (-aa option). Requires pipewire')
option('portal', type : 'boolean', value : true, description : 'Build with support for xdg desktop portal ScreenCast capture (wayland only) (-w portal option). Requires pipewire')
option('app_audio', type : 'boolean', value : true, description : 'Build with support for recording a single audio source (-a app: option). Requires pipewire')

View File

@@ -1,7 +1,7 @@
[package]
name = "gpu-screen-recorder"
type = "executable"
version = "5.3.2"
version = "5.3.8"
platforms = ["posix"]
[config]

25
protocol/meson.build Normal file
View File

@@ -0,0 +1,25 @@
wayland_scanner = dependency('wayland-scanner', native: true)
wayland_scanner_path = wayland_scanner.get_variable(pkgconfig: 'wayland_scanner')
wayland_scanner_prog = find_program(wayland_scanner_path, native: true)
wayland_scanner_code = generator(
wayland_scanner_prog,
output: '@BASENAME@-protocol.c',
arguments: ['private-code', '@INPUT@', '@OUTPUT@'],
)
wayland_scanner_client = generator(
wayland_scanner_prog,
output: '@BASENAME@-client-protocol.h',
arguments: ['client-header', '@INPUT@', '@OUTPUT@'],
)
protocols = [
'xdg-output-unstable-v1.xml',
]
protocol_src = []
foreach xml : protocols
protocol_src += wayland_scanner_code.process(xml)
protocol_src += wayland_scanner_client.process(xml)
endforeach

View File

@@ -0,0 +1,222 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="xdg_output_unstable_v1">
<copyright>
Copyright © 2017 Red Hat Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<description summary="Protocol to describe output regions">
This protocol aims at describing outputs in a way which is more in line
with the concept of an output on desktop oriented systems.
Some information are more specific to the concept of an output for
a desktop oriented system and may not make sense in other applications,
such as IVI systems for example.
Typically, the global compositor space on a desktop system is made of
a contiguous or overlapping set of rectangular regions.
The logical_position and logical_size events defined in this protocol
might provide information identical to their counterparts already
available from wl_output, in which case the information provided by this
protocol should be preferred to their equivalent in wl_output. The goal is
to move the desktop specific concepts (such as output location within the
global compositor space, etc.) out of the core wl_output protocol.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible
changes may be added together with the corresponding interface
version bump.
Backward incompatible changes are done by bumping the version
number in the protocol and interface names and resetting the
interface version. Once the protocol is to be declared stable,
the 'z' prefix and the version number in the protocol and
interface names are removed and the interface version number is
reset.
</description>
<interface name="zxdg_output_manager_v1" version="3">
<description summary="manage xdg_output objects">
A global factory interface for xdg_output objects.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the xdg_output_manager object">
Using this request a client can tell the server that it is not
going to use the xdg_output_manager object anymore.
Any objects already created through this instance are not affected.
</description>
</request>
<request name="get_xdg_output">
<description summary="create an xdg output from a wl_output">
This creates a new xdg_output object for the given wl_output.
</description>
<arg name="id" type="new_id" interface="zxdg_output_v1"/>
<arg name="output" type="object" interface="wl_output"/>
</request>
</interface>
<interface name="zxdg_output_v1" version="3">
<description summary="compositor logical output region">
An xdg_output describes part of the compositor geometry.
This typically corresponds to a monitor that displays part of the
compositor space.
For objects version 3 onwards, after all xdg_output properties have been
sent (when the object is created and when properties are updated), a
wl_output.done event is sent. This allows changes to the output
properties to be seen as atomic, even if they happen via multiple events.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the xdg_output object">
Using this request a client can tell the server that it is not
going to use the xdg_output object anymore.
</description>
</request>
<event name="logical_position">
<description summary="position of the output within the global compositor space">
The position event describes the location of the wl_output within
the global compositor space.
The logical_position event is sent after creating an xdg_output
(see xdg_output_manager.get_xdg_output) and whenever the location
of the output changes within the global compositor space.
</description>
<arg name="x" type="int"
summary="x position within the global compositor space"/>
<arg name="y" type="int"
summary="y position within the global compositor space"/>
</event>
<event name="logical_size">
<description summary="size of the output in the global compositor space">
The logical_size event describes the size of the output in the
global compositor space.
Most regular Wayland clients should not pay attention to the
logical size and would rather rely on xdg_shell interfaces.
Some clients such as Xwayland, however, need this to configure
their surfaces in the global compositor space as the compositor
may apply a different scale from what is advertised by the output
scaling property (to achieve fractional scaling, for example).
For example, for a wl_output mode 3840×2160 and a scale factor 2:
- A compositor not scaling the monitor viewport in its compositing space
will advertise a logical size of 3840×2160,
- A compositor scaling the monitor viewport with scale factor 2 will
advertise a logical size of 1920×1080,
- A compositor scaling the monitor viewport using a fractional scale of
1.5 will advertise a logical size of 2560×1440.
For example, for a wl_output mode 1920×1080 and a 90 degree rotation,
the compositor will advertise a logical size of 1080x1920.
The logical_size event is sent after creating an xdg_output
(see xdg_output_manager.get_xdg_output) and whenever the logical
size of the output changes, either as a result of a change in the
applied scale or because of a change in the corresponding output
mode(see wl_output.mode) or transform (see wl_output.transform).
</description>
<arg name="width" type="int"
summary="width in global compositor space"/>
<arg name="height" type="int"
summary="height in global compositor space"/>
</event>
<event name="done" deprecated-since="3">
<description summary="all information about the output have been sent">
This event is sent after all other properties of an xdg_output
have been sent.
This allows changes to the xdg_output properties to be seen as
atomic, even if they happen via multiple events.
For objects version 3 onwards, this event is deprecated. Compositors
are not required to send it anymore and must send wl_output.done
instead.
</description>
</event>
<!-- Version 2 additions -->
<event name="name" since="2">
<description summary="name of this output">
Many compositors will assign names to their outputs, show them to the
user, allow them to be configured by name, etc. The client may wish to
know this name as well to offer the user similar behaviors.
The naming convention is compositor defined, but limited to
alphanumeric characters and dashes (-). Each name is unique among all
wl_output globals, but if a wl_output global is destroyed the same name
may be reused later. The names will also remain consistent across
sessions with the same hardware and software configuration.
Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do
not assume that the name is a reflection of an underlying DRM
connector, X11 connection, etc.
The name event is sent after creating an xdg_output (see
xdg_output_manager.get_xdg_output). This event is only sent once per
xdg_output, and the name does not change over the lifetime of the
wl_output global.
This event is deprecated, instead clients should use wl_output.name.
Compositors must still support this event.
</description>
<arg name="name" type="string" summary="output name"/>
</event>
<event name="description" since="2">
<description summary="human-readable description of this output">
Many compositors can produce human-readable descriptions of their
outputs. The client may wish to know this description as well, to
communicate the user for various purposes.
The description is a UTF-8 string with no convention defined for its
contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11
output via :1'.
The description event is sent after creating an xdg_output (see
xdg_output_manager.get_xdg_output) and whenever the description
changes. The description is optional, and may not be sent at all.
For objects of version 2 and lower, this event is only sent once per
xdg_output, and the description does not change over the lifetime of
the wl_output global.
This event is deprecated, instead clients should use
wl_output.description. Compositors must still support this event.
</description>
<arg name="description" type="string" summary="output description"/>
</event>
</interface>
</protocol>

View File

@@ -53,10 +53,6 @@ typedef struct {
bool is_x11;
gsr_cursor x11_cursor;
bool performance_error_shown;
bool fast_path_failed;
bool mesa_supports_compute_only_vaapi_copy;
//int drm_fd;
//uint64_t prev_sequence;
//bool damaged;
@@ -112,18 +108,22 @@ static int max_int(int a, int b) {
}
static void gsr_capture_kms_create_input_texture_ids(gsr_capture_kms *self) {
const float border_color[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
self->params.egl->glGenTextures(1, &self->input_texture_id);
self->params.egl->glBindTexture(GL_TEXTURE_2D, self->input_texture_id);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
self->params.egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
self->params.egl->glGenTextures(1, &self->external_input_texture_id);
self->params.egl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, self->external_input_texture_id);
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
self->params.egl->glTexParameterfv(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_BORDER_COLOR, border_color);
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->params.egl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
@@ -133,8 +133,9 @@ static void gsr_capture_kms_create_input_texture_ids(gsr_capture_kms *self) {
self->params.egl->glGenTextures(1, &self->cursor_texture_id);
self->params.egl->glBindTexture(cursor_texture_id_target, self->cursor_texture_id);
self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
self->params.egl->glTexParameterfv(cursor_texture_id_target, GL_TEXTURE_BORDER_COLOR, border_color);
self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->params.egl->glBindTexture(cursor_texture_id_target, 0);
@@ -229,17 +230,6 @@ static int gsr_capture_kms_start(gsr_capture *cap, gsr_capture_metadata *capture
capture_metadata->height = self->capture_size.y;
}
self->fast_path_failed = self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && !gl_driver_version_greater_than(&self->params.egl->gpu_info, 24, 0, 9);
if(self->fast_path_failed)
fprintf(stderr, "gsr warning: gsr_capture_kms_start: your amd driver (mesa) version is known to be buggy (<= version 24.0.9), falling back to opengl copy\n");
//if(self->params.hdr) {
// self->fast_path_failed = true;
// fprintf(stderr, "gsr warning: gsr_capture_kms_start: recording with hdr requires shader color conversion which might be slow. If this is an issue record with -w portal instead (which converts HDR to SDR)\n");
//}
self->mesa_supports_compute_only_vaapi_copy = self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && gl_driver_version_greater_than(&self->params.egl->gpu_info, 24, 3, 6);
self->last_time_monitor_check = clock_get_monotonic_seconds();
return 0;
}
@@ -274,16 +264,6 @@ static void gsr_capture_kms_on_event(gsr_capture *cap, gsr_egl *egl) {
// }
// }
static float monitor_rotation_to_radians(gsr_monitor_rotation rot) {
switch(rot) {
case GSR_MONITOR_ROT_0: return 0.0f;
case GSR_MONITOR_ROT_90: return M_PI_2;
case GSR_MONITOR_ROT_180: return M_PI;
case GSR_MONITOR_ROT_270: return M_PI + M_PI_2;
}
return 0.0f;
}
static gsr_kms_response_item* find_drm_by_connector_id(gsr_kms_response *kms_response, uint32_t connector_id) {
for(int i = 0; i < kms_response->num_items; ++i) {
if(kms_response->items[i].connector_id == connector_id && !kms_response->items[i].is_cursor)
@@ -449,7 +429,7 @@ static gsr_kms_response_item* find_cursor_drm_if_on_monitor(gsr_capture_kms *sel
return cursor_drm_fd;
}
static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color_conversion, const gsr_kms_response_item *cursor_drm_fd, vec2i target_pos, float texture_rotation, vec2i output_size, vec2i framebuffer_size) {
static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color_conversion, const gsr_kms_response_item *cursor_drm_fd, vec2i target_pos, vec2i output_size, vec2i framebuffer_size) {
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
@@ -522,8 +502,8 @@ static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color
gsr_color_conversion_draw(color_conversion, self->cursor_texture_id,
cursor_pos, (vec2i){cursor_size.x * scale.x, cursor_size.y * scale.y},
(vec2i){0, 0}, cursor_size,
texture_rotation, cursor_texture_id_is_external, GSR_SOURCE_COLOR_RGB);
(vec2i){0, 0}, cursor_size, cursor_size,
gsr_monitor_rotation_to_rotation(self->monitor_rotation), GSR_SOURCE_COLOR_RGB, cursor_texture_id_is_external, true);
self->params.egl->glDisable(GL_SCISSOR_TEST);
}
@@ -550,8 +530,8 @@ static void render_x11_cursor(gsr_capture_kms *self, gsr_color_conversion *color
gsr_color_conversion_draw(color_conversion, self->x11_cursor.texture_id,
cursor_pos, (vec2i){self->x11_cursor.size.x * scale.x, self->x11_cursor.size.y * scale.y},
(vec2i){0, 0}, self->x11_cursor.size,
0.0f, false, GSR_SOURCE_COLOR_RGB);
(vec2i){0, 0}, self->x11_cursor.size, self->x11_cursor.size,
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, true);
self->params.egl->glDisable(GL_SCISSOR_TEST);
}
@@ -594,6 +574,7 @@ static void gsr_capture_kms_update_connector_ids(gsr_capture_kms *self) {
monitor.name = self->params.display_to_capture;
vec2i monitor_position = {0, 0};
// TODO: This is cached. We need it updated.
drm_monitor_get_display_server_data(self->params.egl->window, &monitor, &self->monitor_rotation, &monitor_position);
self->capture_pos = monitor.pos;
@@ -604,16 +585,6 @@ static void gsr_capture_kms_update_connector_ids(gsr_capture_kms *self) {
self->capture_size = rotate_capture_size_if_rotated(self, monitor.size);
}
static void gsr_capture_kms_fail_fast_path_if_not_fast(gsr_capture_kms *self, uint32_t pixel_format) {
const uint8_t pixel_format_color_depth_1 = (pixel_format >> 16) & 0xFF;
if(!self->fast_path_failed && self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && !self->mesa_supports_compute_only_vaapi_copy && (pixel_format_color_depth_1 == '3' || pixel_format_color_depth_1 == '4')) {
self->fast_path_failed = true;
fprintf(stderr, "gsr warning: gsr_capture_kms_capture: the monitor you are recording is in 10/12-bit color format and your mesa version is <= 24.3.6, composition will be used."
" If you experience performance problems in the video then record on a single window on X11 or use portal capture option instead or disable 10/12-bit color option in your desktop environment settings,"
" or try to record the monitor on X11 instead (if you aren't already doing that) or update your mesa version.\n");
}
}
static int gsr_capture_kms_capture(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion) {
gsr_capture_kms *self = cap->priv;
@@ -645,16 +616,8 @@ static int gsr_capture_kms_capture(gsr_capture *cap, gsr_capture_metadata *captu
if(drm_fd->has_hdr_metadata && self->params.hdr && hdr_metadata_is_supported_format(&drm_fd->hdr_metadata))
gsr_kms_set_hdr_metadata(self, drm_fd);
if(!self->performance_error_shown && self->monitor_rotation != GSR_MONITOR_ROT_0 && video_codec_context_is_vaapi(capture_metadata->video_codec_context) && self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD) {
self->performance_error_shown = true;
self->fast_path_failed = true;
fprintf(stderr, "gsr warning: gsr_capture_kms_capture: the monitor you are recording is rotated, composition will have to be used."
" If you experience performance problems in the video then record a single window on X11 or use portal capture option instead\n");
}
gsr_capture_kms_fail_fast_path_if_not_fast(self, drm_fd->pixel_format);
self->capture_size = rotate_capture_size_if_rotated(self, (vec2i){ drm_fd->src_w, drm_fd->src_h });
const vec2i original_frame_size = self->capture_size;
if(self->params.region_size.x > 0 && self->params.region_size.y > 0)
self->capture_size = self->params.region_size;
@@ -662,7 +625,6 @@ static int gsr_capture_kms_capture(gsr_capture *cap, gsr_capture_metadata *captu
vec2i output_size = is_scaled ? self->params.output_resolution : self->capture_size;
output_size = scale_keep_aspect_ratio(self->capture_size, output_size);
const float texture_rotation = monitor_rotation_to_radians(self->monitor_rotation);
const vec2i target_pos = { max_int(0, capture_metadata->width / 2 - output_size.x / 2), max_int(0, capture_metadata->height / 2 - output_size.y / 2) };
gsr_capture_kms_update_capture_size_change(self, color_conversion, target_pos, drm_fd);
@@ -673,41 +635,19 @@ static int gsr_capture_kms_capture(gsr_capture *cap, gsr_capture_metadata *captu
capture_pos.x += self->params.region_position.x;
capture_pos.y += self->params.region_position.y;
self->params.egl->glFlush();
self->params.egl->glFinish();
//self->params.egl->glFlush();
//self->params.egl->glFinish();
/* Fast opengl free path */
if(!self->fast_path_failed && self->monitor_rotation == GSR_MONITOR_ROT_0 && video_codec_context_is_vaapi(capture_metadata->video_codec_context) && self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD) {
int fds[4];
uint32_t offsets[4];
uint32_t pitches[4];
uint64_t modifiers[4];
for(int i = 0; i < drm_fd->num_dma_bufs; ++i) {
fds[i] = drm_fd->dma_buf[i].fd;
offsets[i] = drm_fd->dma_buf[i].offset;
pitches[i] = drm_fd->dma_buf[i].pitch;
modifiers[i] = drm_fd->modifier;
}
if(!vaapi_copy_drm_planes_to_video_surface(capture_metadata->video_codec_context, capture_metadata->frame, (vec2i){capture_pos.x, capture_pos.y}, self->capture_size, target_pos, output_size, drm_fd->pixel_format, (vec2i){drm_fd->width, drm_fd->height}, fds, offsets, pitches, modifiers, drm_fd->num_dma_bufs)) {
fprintf(stderr, "gsr error: gsr_capture_kms_capture: vaapi_copy_drm_planes_to_video_surface failed, falling back to opengl copy. Please report this as an issue at https://github.com/dec05eba/gpu-screen-recorder-issues\n");
self->fast_path_failed = true;
}
} else {
self->fast_path_failed = true;
EGLImage image = gsr_capture_kms_create_egl_image_with_fallback(self, drm_fd);
if(image) {
gsr_capture_kms_bind_image_to_input_texture_with_fallback(self, image);
self->params.egl->eglDestroyImage(self->params.egl->egl_display, image);
}
if(self->fast_path_failed) {
EGLImage image = gsr_capture_kms_create_egl_image_with_fallback(self, drm_fd);
if(image) {
gsr_capture_kms_bind_image_to_input_texture_with_fallback(self, image);
self->params.egl->eglDestroyImage(self->params.egl->egl_display, image);
}
gsr_color_conversion_draw(color_conversion, self->external_texture_fallback ? self->external_input_texture_id : self->input_texture_id,
target_pos, output_size,
capture_pos, self->capture_size,
texture_rotation, self->external_texture_fallback, GSR_SOURCE_COLOR_RGB);
}
gsr_color_conversion_draw(color_conversion, self->external_texture_fallback ? self->external_input_texture_id : self->input_texture_id,
target_pos, output_size,
capture_pos, self->capture_size, original_frame_size,
gsr_monitor_rotation_to_rotation(self->monitor_rotation), GSR_SOURCE_COLOR_RGB, self->external_texture_fallback, false);
if(self->params.record_cursor) {
gsr_kms_response_item *cursor_drm_fd = find_cursor_drm_if_on_monitor(self, drm_fd->connector_id, capture_is_combined_plane);
@@ -722,12 +662,12 @@ static int gsr_capture_kms_capture(gsr_capture *cap, gsr_capture_metadata *captu
render_x11_cursor(self, color_conversion, cursor_monitor_offset, target_pos, output_size);
} else if(cursor_drm_fd) {
const vec2i framebuffer_size = rotate_capture_size_if_rotated(self, (vec2i){ drm_fd->src_w, drm_fd->src_h });
render_drm_cursor(self, color_conversion, cursor_drm_fd, target_pos, texture_rotation, output_size, framebuffer_size);
render_drm_cursor(self, color_conversion, cursor_drm_fd, target_pos, output_size, framebuffer_size);
}
}
self->params.egl->glFlush();
self->params.egl->glFinish();
//self->params.egl->glFlush();
//self->params.egl->glFinish();
gsr_capture_kms_cleanup_kms_fds(self);

View File

@@ -363,6 +363,7 @@ static int gsr_capture_nvfbc_capture(gsr_capture *cap, gsr_capture_metadata *cap
}
vec2i frame_size = (vec2i){self->width, self->height};
const vec2i original_frame_size = frame_size;
if(self->params.region_size.x > 0 && self->params.region_size.y > 0)
frame_size = self->params.region_size;
@@ -390,16 +391,16 @@ static int gsr_capture_nvfbc_capture(gsr_capture *cap, gsr_capture_metadata *cap
return 0;
}
self->params.egl->glFlush();
self->params.egl->glFinish();
//self->params.egl->glFlush();
//self->params.egl->glFinish();
gsr_color_conversion_draw(color_conversion, self->setup_params.dwTextures[grab_params.dwTextureIndex],
target_pos, (vec2i){output_size.x, output_size.y},
self->params.region_position, frame_size,
0.0f, false, GSR_SOURCE_COLOR_BGR);
self->params.region_position, frame_size, original_frame_size,
GSR_ROT_0, GSR_SOURCE_COLOR_BGR, false, false);
self->params.egl->glFlush();
self->params.egl->glFinish();
//self->params.egl->glFlush();
//self->params.egl->glFinish();
return 0;
}

View File

@@ -23,9 +23,6 @@ typedef struct {
vec2i capture_size;
gsr_pipewire_video_dmabuf_data dmabuf_data[GSR_PIPEWIRE_VIDEO_DMABUF_MAX_PLANES];
int num_dmabuf_data;
bool fast_path_failed;
bool mesa_supports_compute_only_vaapi_copy;
} gsr_capture_portal;
static void gsr_capture_portal_cleanup_plane_fds(gsr_capture_portal *self) {
@@ -67,26 +64,31 @@ static void gsr_capture_portal_stop(gsr_capture_portal *self) {
}
static void gsr_capture_portal_create_input_textures(gsr_capture_portal *self) {
const float border_color[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
self->params.egl->glGenTextures(1, &self->texture_map.texture_id);
self->params.egl->glBindTexture(GL_TEXTURE_2D, self->texture_map.texture_id);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
self->params.egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
self->params.egl->glGenTextures(1, &self->texture_map.external_texture_id);
self->params.egl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, self->texture_map.external_texture_id);
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
self->params.egl->glTexParameterfv(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_BORDER_COLOR, border_color);
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->params.egl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
self->params.egl->glGenTextures(1, &self->texture_map.cursor_texture_id);
self->params.egl->glBindTexture(GL_TEXTURE_2D, self->texture_map.cursor_texture_id);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
self->params.egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
@@ -305,12 +307,6 @@ static int gsr_capture_portal_start(gsr_capture *cap, gsr_capture_metadata *capt
capture_metadata->height = self->params.output_resolution.y;
}
self->fast_path_failed = self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && !gl_driver_version_greater_than(&self->params.egl->gpu_info, 24, 0, 9);
if(self->fast_path_failed)
fprintf(stderr, "gsr warning: gsr_capture_kms_start: your amd driver (mesa) version is known to be buggy (<= version 24.0.9), falling back to opengl copy\n");
self->mesa_supports_compute_only_vaapi_copy = self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && gl_driver_version_greater_than(&self->params.egl->gpu_info, 24, 3, 6);
return 0;
}
@@ -318,16 +314,6 @@ static int max_int(int a, int b) {
return a > b ? a : b;
}
static void gsr_capture_portal_fail_fast_path_if_not_fast(gsr_capture_portal *self, uint32_t pixel_format) {
const uint8_t pixel_format_color_depth_1 = (pixel_format >> 16) & 0xFF;
if(!self->fast_path_failed && self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && !self->mesa_supports_compute_only_vaapi_copy && (pixel_format_color_depth_1 == '3' || pixel_format_color_depth_1 == '4')) {
self->fast_path_failed = true;
fprintf(stderr, "gsr warning: gsr_capture_kms_capture: the monitor you are recording is in 10/12-bit color format and your mesa version is <= 24.3.6, composition will be used."
" If you experience performance problems in the video then record on a single window on X11 instead or disable 10/12-bit color option in your desktop environment settings,"
" or try to record the monitor on X11 instead (if you aren't already doing that) or update your mesa version.\n");
}
}
static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion) {
(void)color_conversion;
gsr_capture_portal *self = cap->priv;
@@ -348,45 +334,21 @@ static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *ca
return 0;
}
gsr_capture_portal_fail_fast_path_if_not_fast(self, pipewire_fourcc);
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_metadata->width / 2 - output_size.x / 2), max_int(0, capture_metadata->height / 2 - output_size.y / 2) };
self->params.egl->glFlush();
self->params.egl->glFinish();
//self->params.egl->glFlush();
//self->params.egl->glFinish();
// TODO: Handle region crop
/* Fast opengl free path */
if(!self->fast_path_failed && video_codec_context_is_vaapi(capture_metadata->video_codec_context) && self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD) {
int fds[4];
uint32_t offsets[4];
uint32_t pitches[4];
uint64_t modifiers[4];
for(int i = 0; i < self->num_dmabuf_data; ++i) {
fds[i] = self->dmabuf_data[i].fd;
offsets[i] = self->dmabuf_data[i].offset;
pitches[i] = self->dmabuf_data[i].stride;
modifiers[i] = pipewire_modifiers;
}
if(!vaapi_copy_drm_planes_to_video_surface(capture_metadata->video_codec_context, capture_metadata->frame, (vec2i){region.x, region.y}, self->capture_size, target_pos, output_size, pipewire_fourcc, self->capture_size, fds, offsets, pitches, modifiers, self->num_dmabuf_data)) {
fprintf(stderr, "gsr error: gsr_capture_portal_capture: vaapi_copy_drm_planes_to_video_surface failed, falling back to opengl copy. Please report this as an issue at https://github.com/dec05eba/gpu-screen-recorder-issues\n");
self->fast_path_failed = true;
}
} else {
self->fast_path_failed = true;
}
if(self->fast_path_failed) {
gsr_color_conversion_draw(color_conversion, using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id,
target_pos, output_size,
(vec2i){region.x, region.y}, self->capture_size,
0.0f, using_external_image, GSR_SOURCE_COLOR_RGB);
}
gsr_color_conversion_draw(color_conversion, using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id,
target_pos, output_size,
(vec2i){region.x, region.y}, self->capture_size, self->capture_size,
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, using_external_image, false);
if(self->params.record_cursor && self->texture_map.cursor_texture_id > 0 && cursor_region.width > 0) {
const vec2d scale = {
@@ -403,13 +365,13 @@ static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *ca
self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y);
gsr_color_conversion_draw(color_conversion, self->texture_map.cursor_texture_id,
(vec2i){cursor_pos.x, cursor_pos.y}, (vec2i){cursor_region.width * scale.x, cursor_region.height * scale.y},
(vec2i){0, 0}, (vec2i){cursor_region.width, cursor_region.height},
0.0f, false, GSR_SOURCE_COLOR_RGB);
(vec2i){0, 0}, (vec2i){cursor_region.width, cursor_region.height}, (vec2i){cursor_region.width, cursor_region.height},
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, true);
self->params.egl->glDisable(GL_SCISSOR_TEST);
}
self->params.egl->glFlush();
self->params.egl->glFinish();
//self->params.egl->glFlush();
//self->params.egl->glFinish();
gsr_capture_portal_cleanup_plane_fds(self);

View File

@@ -34,7 +34,6 @@ typedef struct {
gsr_cursor cursor;
bool clear_background;
bool fast_path_failed;
} gsr_capture_xcomposite;
static void gsr_capture_xcomposite_stop(gsr_capture_xcomposite *self) {
@@ -117,10 +116,6 @@ static int gsr_capture_xcomposite_start(gsr_capture *cap, gsr_capture_metadata *
capture_metadata->height = self->params.output_resolution.y;
}
self->fast_path_failed = self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && !gl_driver_version_greater_than(&self->params.egl->gpu_info, 24, 0, 9);
if(self->fast_path_failed)
fprintf(stderr, "gsr warning: gsr_capture_kms_start: your amd driver (mesa) version is known to be buggy (<= version 24.0.9), falling back to opengl copy\n");
self->window_resize_timer = clock_get_monotonic_seconds();
return 0;
}
@@ -258,25 +253,13 @@ static int gsr_capture_xcomposite_capture(gsr_capture *cap, gsr_capture_metadata
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) };
self->params.egl->glFlush();
self->params.egl->glFinish();
//self->params.egl->glFlush();
//self->params.egl->glFinish();
/* Fast opengl free path */
if(!self->fast_path_failed && video_codec_context_is_vaapi(capture_metdata->video_codec_context) && self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD) {
if(!vaapi_copy_egl_image_to_video_surface(self->params.egl, self->window_texture.image, (vec2i){0, 0}, self->texture_size, target_pos, output_size, capture_metdata->video_codec_context, capture_metdata->frame)) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_capture: vaapi_copy_egl_image_to_video_surface failed, falling back to opengl copy. Please report this as an issue at https://github.com/dec05eba/gpu-screen-recorder-issues\n");
self->fast_path_failed = true;
}
} else {
self->fast_path_failed = true;
}
if(self->fast_path_failed) {
gsr_color_conversion_draw(color_conversion, window_texture_get_opengl_texture_id(&self->window_texture),
target_pos, output_size,
(vec2i){0, 0}, self->texture_size,
0.0f, false, GSR_SOURCE_COLOR_RGB);
}
gsr_color_conversion_draw(color_conversion, window_texture_get_opengl_texture_id(&self->window_texture),
target_pos, output_size,
(vec2i){0, 0}, self->texture_size, self->texture_size,
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, false);
if(self->params.record_cursor && self->cursor.visible) {
const vec2d scale = {
@@ -291,19 +274,17 @@ static int gsr_capture_xcomposite_capture(gsr_capture *cap, gsr_capture_metadata
target_pos.y + (self->cursor.position.y - self->cursor.hotspot.y) * scale.y
};
self->params.egl->glEnable(GL_SCISSOR_TEST);
self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y);
if(cursor_pos.x < target_pos.x || cursor_pos.x + self->cursor.size.x > target_pos.x + output_size.x || cursor_pos.y < target_pos.y || cursor_pos.y + self->cursor.size.y > target_pos.y + output_size.y)
self->clear_background = true;
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);
(vec2i){0, 0}, self->cursor.size, self->cursor.size,
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, true);
}
self->params.egl->glFlush();
self->params.egl->glFinish();
//self->params.egl->glFlush();
//self->params.egl->glFinish();
return 0;
}

View File

@@ -159,8 +159,8 @@ static int gsr_capture_ximage_capture(gsr_capture *cap, gsr_capture_metadata *ca
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);
(vec2i){0, 0}, self->capture_size, self->capture_size,
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, false);
if(self->params.record_cursor && self->cursor.visible) {
const vec2d scale = {
@@ -180,8 +180,8 @@ static int gsr_capture_ximage_capture(gsr_capture *cap, gsr_capture_metadata *ca
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);
(vec2i){0, 0}, self->cursor.size, self->cursor.size,
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, true);
self->params.egl->glDisable(GL_SCISSOR_TEST);
}

View File

@@ -1,26 +1,29 @@
#include "../include/color_conversion.h"
#include "../include/egl.h"
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <string.h>
#include <assert.h>
/* TODO: highp instead of mediump? */
#define COMPUTE_SHADER_INDEX_Y 0
#define COMPUTE_SHADER_INDEX_UV 1
#define COMPUTE_SHADER_INDEX_Y_EXTERNAL 2
#define COMPUTE_SHADER_INDEX_UV_EXTERNAL 3
#define COMPUTE_SHADER_INDEX_RGB 4
#define COMPUTE_SHADER_INDEX_RGB_EXTERNAL 5
#define COMPUTE_SHADER_INDEX_Y_BLEND 6
#define COMPUTE_SHADER_INDEX_UV_BLEND 7
#define COMPUTE_SHADER_INDEX_Y_EXTERNAL_BLEND 8
#define COMPUTE_SHADER_INDEX_UV_EXTERNAL_BLEND 9
#define COMPUTE_SHADER_INDEX_RGB_BLEND 10
#define COMPUTE_SHADER_INDEX_RGB_EXTERNAL_BLEND 11
#define MAX_SHADERS 4
#define MAX_FRAMEBUFFERS 2
#define EXTERNAL_TEXTURE_SHADER_OFFSET 2
static float abs_f(float v) {
return v >= 0.0f ? v : -v;
}
#define ROTATE_Z "mat4 rotate_z(in float angle) {\n" \
" return mat4(cos(angle), -sin(angle), 0.0, 0.0,\n" \
" sin(angle), cos(angle), 0.0, 0.0,\n" \
" 0.0, 0.0, 1.0, 0.0,\n" \
" 0.0, 0.0, 0.0, 1.0);\n" \
"}\n"
#define GRAPHICS_SHADER_INDEX_Y 0
#define GRAPHICS_SHADER_INDEX_UV 1
#define GRAPHICS_SHADER_INDEX_Y_EXTERNAL 2
#define GRAPHICS_SHADER_INDEX_UV_EXTERNAL 3
#define GRAPHICS_SHADER_INDEX_RGB 4
#define GRAPHICS_SHADER_INDEX_RGB_EXTERNAL 5
/* https://en.wikipedia.org/wiki/YCbCr, see study/color_space_transform_matrix.png */
@@ -50,6 +53,10 @@ static float abs_f(float v) {
" 0.060118, 0.429412, -0.038049, 0.000000,\n" \
" 0.062745, 0.500000, 0.500000, 1.000000);\n"
static int max_int(int a, int b) {
return a > b ? a : b;
}
static const char* color_format_range_get_transform_matrix(gsr_destination_color color_format, gsr_color_range color_range) {
switch(color_format) {
case GSR_DESTINATION_COLOR_NV12: {
@@ -78,7 +85,146 @@ static const char* color_format_range_get_transform_matrix(gsr_destination_color
return NULL;
}
static int load_shader_y(gsr_shader *shader, gsr_egl *egl, gsr_color_uniforms *uniforms, gsr_destination_color color_format, gsr_color_range color_range, bool external_texture) {
static void get_compute_shader_header(char *header, size_t header_size, bool external_texture) {
if(external_texture) {
snprintf(header, header_size,
"#version 310 es\n"
"#extension GL_ARB_compute_shader: enable\n"
"#extension GL_OES_EGL_image_external : enable\n"
"#extension GL_OES_EGL_image_external_essl3 : require\n"
"layout(binding = 0) uniform highp samplerExternalOES img_input;\n"
"layout(binding = 1) uniform highp sampler2D img_background;\n");
} else {
snprintf(header, header_size,
"#version 420\n"
"#extension GL_ARB_compute_shader: enable\n"
"layout(binding = 0) uniform highp sampler2D img_input;\n"
"layout(binding = 1) uniform highp sampler2D img_background;\n");
}
}
static int load_compute_shader_y(gsr_shader *shader, gsr_egl *egl, gsr_color_compute_uniforms *uniforms, int max_local_size_dim, gsr_destination_color color_format, gsr_color_range color_range, bool external_texture, bool alpha_blending) {
const char *color_transform_matrix = color_format_range_get_transform_matrix(color_format, color_range);
char header[512];
get_compute_shader_header(header, sizeof(header), external_texture);
char compute_shader[2048];
snprintf(compute_shader, sizeof(compute_shader),
"%s"
"layout (local_size_x = %d, local_size_y = %d, local_size_z = 1) in;\n"
"precision highp float;\n"
"uniform ivec2 source_position;\n"
"uniform ivec2 target_position;\n"
"uniform vec2 scale;\n"
"uniform mat2 rotation_matrix;\n"
"layout(rgba8, binding = 0) writeonly uniform highp image2D img_output;\n"
"%s"
"void main() {\n"
" ivec2 texel_coord = ivec2(gl_GlobalInvocationID.xy);\n"
" ivec2 size = ivec2(vec2(textureSize(img_input, 0)) * scale + 0.5);\n"
" ivec2 size_shift = size >> 1;\n" // size/2
" ivec2 output_size = textureSize(img_background, 0);\n"
" vec2 rotated_texel_coord = vec2(texel_coord - source_position - size_shift) * rotation_matrix + vec2(size_shift) + 0.5;\n"
" vec2 output_texel_coord = vec2(texel_coord - source_position + target_position) + 0.5;\n"
" vec4 source_color = texture(img_input, rotated_texel_coord/vec2(size));\n"
" vec4 source_color_yuv = RGBtoYUV * vec4(source_color.rgb, 1.0);\n"
" vec4 output_color_yuv = %s;\n"
" float y_color = mix(output_color_yuv.r, source_color_yuv.r, source_color.a);\n"
" imageStore(img_output, texel_coord + target_position, vec4(y_color, 1.0, 1.0, 1.0));\n"
"}\n", header, max_local_size_dim, max_local_size_dim, color_transform_matrix,
alpha_blending ? "texture(img_background, output_texel_coord/vec2(output_size))" : "source_color_yuv");
if(gsr_shader_init(shader, egl, NULL, NULL, compute_shader) != 0)
return -1;
uniforms->source_position = egl->glGetUniformLocation(shader->program_id, "source_position");
uniforms->target_position = egl->glGetUniformLocation(shader->program_id, "target_position");
uniforms->rotation_matrix = egl->glGetUniformLocation(shader->program_id, "rotation_matrix");
uniforms->scale = egl->glGetUniformLocation(shader->program_id, "scale");
return 0;
}
static int load_compute_shader_uv(gsr_shader *shader, gsr_egl *egl, gsr_color_compute_uniforms *uniforms, int max_local_size_dim, gsr_destination_color color_format, gsr_color_range color_range, bool external_texture, bool alpha_blending) {
const char *color_transform_matrix = color_format_range_get_transform_matrix(color_format, color_range);
char header[512];
get_compute_shader_header(header, sizeof(header), external_texture);
char compute_shader[2048];
snprintf(compute_shader, sizeof(compute_shader),
"%s"
"layout (local_size_x = %d, local_size_y = %d, local_size_z = 1) in;\n"
"precision highp float;\n"
"uniform ivec2 source_position;\n"
"uniform ivec2 target_position;\n"
"uniform vec2 scale;\n"
"uniform mat2 rotation_matrix;\n"
"layout(rgba8, binding = 0) writeonly uniform highp image2D img_output;\n"
"%s"
"void main() {\n"
" ivec2 texel_coord = ivec2(gl_GlobalInvocationID.xy);\n"
" ivec2 size = ivec2(vec2(textureSize(img_input, 0)) * scale + 0.5);\n"
" ivec2 size_shift = size >> 2;\n" // size/4
" ivec2 output_size = textureSize(img_background, 0);\n"
" vec2 rotated_texel_coord = vec2(texel_coord - source_position - size_shift) * rotation_matrix + vec2(size_shift) + 0.5;\n"
" vec2 output_texel_coord = vec2(texel_coord - source_position + target_position) + 0.5;\n"
" vec4 source_color = texture(img_input, rotated_texel_coord/vec2(size>>1));\n" // size/2
" vec4 source_color_yuv = RGBtoYUV * vec4(source_color.rgb, 1.0);\n"
" vec4 output_color_yuv = %s;\n"
" vec2 uv_color = mix(output_color_yuv.rg, source_color_yuv.gb, source_color.a);\n"
" imageStore(img_output, texel_coord + target_position, vec4(uv_color, 1.0, 1.0));\n"
"}\n", header, max_local_size_dim, max_local_size_dim, color_transform_matrix,
alpha_blending ? "texture(img_background, output_texel_coord/vec2(output_size))" : "source_color_yuv");
if(gsr_shader_init(shader, egl, NULL, NULL, compute_shader) != 0)
return -1;
uniforms->source_position = egl->glGetUniformLocation(shader->program_id, "source_position");
uniforms->target_position = egl->glGetUniformLocation(shader->program_id, "target_position");
uniforms->rotation_matrix = egl->glGetUniformLocation(shader->program_id, "rotation_matrix");
uniforms->scale = egl->glGetUniformLocation(shader->program_id, "scale");
return 0;
}
static int load_compute_shader_rgb(gsr_shader *shader, gsr_egl *egl, gsr_color_compute_uniforms *uniforms, int max_local_size_dim, bool external_texture, bool alpha_blending) {
char header[512];
get_compute_shader_header(header, sizeof(header), external_texture);
char compute_shader[2048];
snprintf(compute_shader, sizeof(compute_shader),
"%s"
"layout (local_size_x = %d, local_size_y = %d, local_size_z = 1) in;\n"
"uniform ivec2 source_position;\n"
"uniform ivec2 target_position;\n"
"uniform vec2 scale;\n"
"uniform mat2 rotation_matrix;\n"
"layout(rgba8, binding = 0) writeonly uniform highp image2D img_output;\n"
"void main() {\n"
" ivec2 texel_coord = ivec2(gl_GlobalInvocationID.xy);\n"
" ivec2 size = ivec2(vec2(textureSize(img_input, 0)) * scale + 0.5);\n"
" ivec2 size_shift = size >> 1;\n" // size/2
" ivec2 output_size = textureSize(img_background, 0);\n"
" vec2 rotated_texel_coord = vec2(texel_coord - source_position - size_shift) * rotation_matrix + vec2(size_shift) + 0.5;\n"
" vec2 output_texel_coord = vec2(texel_coord - source_position + target_position) + 0.5;\n"
" vec4 source_color = texture(img_input, rotated_texel_coord/vec2(size));\n"
" vec4 output_color = %s;\n"
" vec3 color = mix(output_color.rgb, source_color.rgb, source_color.a);\n"
" imageStore(img_output, texel_coord + target_position, vec4(color, 1.0));\n"
"}\n", header, max_local_size_dim, max_local_size_dim,
alpha_blending ? "texture(img_background, output_texel_coord/vec2(output_size))" : "source_color");
if(gsr_shader_init(shader, egl, NULL, NULL, compute_shader) != 0)
return -1;
uniforms->source_position = egl->glGetUniformLocation(shader->program_id, "source_position");
uniforms->target_position = egl->glGetUniformLocation(shader->program_id, "target_position");
uniforms->rotation_matrix = egl->glGetUniformLocation(shader->program_id, "rotation_matrix");
uniforms->scale = egl->glGetUniformLocation(shader->program_id, "scale");
return 0;
}
static int load_graphics_shader_y(gsr_shader *shader, gsr_egl *egl, gsr_color_graphics_uniforms *uniforms, gsr_destination_color color_format, gsr_color_range color_range, bool external_texture) {
const char *color_transform_matrix = color_format_range_get_transform_matrix(color_format, color_range);
char vertex_shader[2048];
@@ -89,10 +235,10 @@ static int load_shader_y(gsr_shader *shader, gsr_egl *egl, gsr_color_uniforms *u
"out vec2 texcoords_out; \n"
"uniform vec2 offset; \n"
"uniform float rotation; \n"
ROTATE_Z
"uniform mat2 rotation_matrix; \n"
"void main() \n"
"{ \n"
" texcoords_out = (vec4(texcoords.x - 0.5, texcoords.y - 0.5, 0.0, 0.0) * rotate_z(rotation)).xy + vec2(0.5, 0.5); \n"
" texcoords_out = vec2(texcoords.x - 0.5, texcoords.y - 0.5) * rotation_matrix + vec2(0.5, 0.5); \n"
" gl_Position = vec4(offset.x, offset.y, 0.0, 0.0) + vec4(pos.x, pos.y, 0.0, 1.0); \n"
"} \n");
@@ -108,7 +254,7 @@ static int load_shader_y(gsr_shader *shader, gsr_egl *egl, gsr_color_uniforms *u
"#version 300 es \n"
"#extension GL_OES_EGL_image_external : enable \n"
"#extension GL_OES_EGL_image_external_essl3 : require \n"
"precision mediump float; \n"
"precision highp float; \n"
"in vec2 texcoords_out; \n"
"uniform samplerExternalOES tex1; \n"
"out vec4 FragColor; \n"
@@ -120,7 +266,7 @@ static int load_shader_y(gsr_shader *shader, gsr_egl *egl, gsr_color_uniforms *u
} else {
snprintf(fragment_shader, sizeof(fragment_shader),
"#version 300 es \n"
"precision mediump float; \n"
"precision highp float; \n"
"in vec2 texcoords_out; \n"
"uniform sampler2D tex1; \n"
"out vec4 FragColor; \n"
@@ -131,17 +277,17 @@ static int load_shader_y(gsr_shader *shader, gsr_egl *egl, gsr_color_uniforms *u
"} \n", color_transform_matrix, main_code);
}
if(gsr_shader_init(shader, egl, vertex_shader, fragment_shader) != 0)
if(gsr_shader_init(shader, egl, vertex_shader, fragment_shader, NULL) != 0)
return -1;
gsr_shader_bind_attribute_location(shader, "pos", 0);
gsr_shader_bind_attribute_location(shader, "texcoords", 1);
uniforms->offset = egl->glGetUniformLocation(shader->program_id, "offset");
uniforms->rotation = egl->glGetUniformLocation(shader->program_id, "rotation");
uniforms->rotation_matrix = egl->glGetUniformLocation(shader->program_id, "rotation_matrix");
return 0;
}
static unsigned int load_shader_uv(gsr_shader *shader, gsr_egl *egl, gsr_color_uniforms *uniforms, gsr_destination_color color_format, gsr_color_range color_range, bool external_texture) {
static unsigned int load_graphics_shader_uv(gsr_shader *shader, gsr_egl *egl, gsr_color_graphics_uniforms *uniforms, gsr_destination_color color_format, gsr_color_range color_range, bool external_texture) {
const char *color_transform_matrix = color_format_range_get_transform_matrix(color_format, color_range);
char vertex_shader[2048];
@@ -152,10 +298,10 @@ static unsigned int load_shader_uv(gsr_shader *shader, gsr_egl *egl, gsr_color_u
"out vec2 texcoords_out; \n"
"uniform vec2 offset; \n"
"uniform float rotation; \n"
ROTATE_Z
"uniform mat2 rotation_matrix; \n"
"void main() \n"
"{ \n"
" texcoords_out = (vec4(texcoords.x - 0.5, texcoords.y - 0.5, 0.0, 0.0) * rotate_z(rotation)).xy + vec2(0.5, 0.5); \n"
" texcoords_out = vec2(texcoords.x - 0.5, texcoords.y - 0.5) * rotation_matrix + vec2(0.5, 0.5); \n"
" gl_Position = (vec4(offset.x, offset.y, 0.0, 0.0) + vec4(pos.x, pos.y, 0.0, 1.0)) * vec4(0.5, 0.5, 1.0, 1.0) - vec4(0.5, 0.5, 0.0, 0.0); \n"
"} \n");
@@ -171,7 +317,7 @@ static unsigned int load_shader_uv(gsr_shader *shader, gsr_egl *egl, gsr_color_u
"#version 300 es \n"
"#extension GL_OES_EGL_image_external : enable \n"
"#extension GL_OES_EGL_image_external_essl3 : require \n"
"precision mediump float; \n"
"precision highp float; \n"
"in vec2 texcoords_out; \n"
"uniform samplerExternalOES tex1; \n"
"out vec4 FragColor; \n"
@@ -183,7 +329,7 @@ static unsigned int load_shader_uv(gsr_shader *shader, gsr_egl *egl, gsr_color_u
} else {
snprintf(fragment_shader, sizeof(fragment_shader),
"#version 300 es \n"
"precision mediump float; \n"
"precision highp float; \n"
"in vec2 texcoords_out; \n"
"uniform sampler2D tex1; \n"
"out vec4 FragColor; \n"
@@ -194,17 +340,17 @@ static unsigned int load_shader_uv(gsr_shader *shader, gsr_egl *egl, gsr_color_u
"} \n", color_transform_matrix, main_code);
}
if(gsr_shader_init(shader, egl, vertex_shader, fragment_shader) != 0)
if(gsr_shader_init(shader, egl, vertex_shader, fragment_shader, NULL) != 0)
return -1;
gsr_shader_bind_attribute_location(shader, "pos", 0);
gsr_shader_bind_attribute_location(shader, "texcoords", 1);
uniforms->offset = egl->glGetUniformLocation(shader->program_id, "offset");
uniforms->rotation = egl->glGetUniformLocation(shader->program_id, "rotation");
uniforms->rotation_matrix = egl->glGetUniformLocation(shader->program_id, "rotation_matrix");
return 0;
}
static unsigned int load_shader_rgb(gsr_shader *shader, gsr_egl *egl, gsr_color_uniforms *uniforms, bool external_texture) {
static unsigned int load_graphics_shader_rgb(gsr_shader *shader, gsr_egl *egl, gsr_color_graphics_uniforms *uniforms, bool external_texture) {
char vertex_shader[2048];
snprintf(vertex_shader, sizeof(vertex_shader),
"#version 300 es \n"
@@ -213,10 +359,10 @@ static unsigned int load_shader_rgb(gsr_shader *shader, gsr_egl *egl, gsr_color_
"out vec2 texcoords_out; \n"
"uniform vec2 offset; \n"
"uniform float rotation; \n"
ROTATE_Z
"uniform mat2 rotation_matrix; \n"
"void main() \n"
"{ \n"
" texcoords_out = (vec4(texcoords.x - 0.5, texcoords.y - 0.5, 0.0, 0.0) * rotate_z(rotation)).xy + vec2(0.5, 0.5); \n"
" texcoords_out = vec2(texcoords.x - 0.5, texcoords.y - 0.5) * rotation_matrix + vec2(0.5, 0.5); \n"
" gl_Position = vec4(offset.x, offset.y, 0.0, 0.0) + vec4(pos.x, pos.y, 0.0, 1.0); \n"
"} \n");
@@ -231,7 +377,7 @@ static unsigned int load_shader_rgb(gsr_shader *shader, gsr_egl *egl, gsr_color_
"#version 300 es \n"
"#extension GL_OES_EGL_image_external : enable \n"
"#extension GL_OES_EGL_image_external_essl3 : require \n"
"precision mediump float; \n"
"precision highp float; \n"
"in vec2 texcoords_out; \n"
"uniform samplerExternalOES tex1; \n"
"out vec4 FragColor; \n"
@@ -242,7 +388,7 @@ static unsigned int load_shader_rgb(gsr_shader *shader, gsr_egl *egl, gsr_color_
} else {
snprintf(fragment_shader, sizeof(fragment_shader),
"#version 300 es \n"
"precision mediump float; \n"
"precision highp float; \n"
"in vec2 texcoords_out; \n"
"uniform sampler2D tex1; \n"
"out vec4 FragColor; \n"
@@ -252,20 +398,20 @@ static unsigned int load_shader_rgb(gsr_shader *shader, gsr_egl *egl, gsr_color_
"} \n", main_code);
}
if(gsr_shader_init(shader, egl, vertex_shader, fragment_shader) != 0)
if(gsr_shader_init(shader, egl, vertex_shader, fragment_shader, NULL) != 0)
return -1;
gsr_shader_bind_attribute_location(shader, "pos", 0);
gsr_shader_bind_attribute_location(shader, "texcoords", 1);
uniforms->offset = egl->glGetUniformLocation(shader->program_id, "offset");
uniforms->rotation = egl->glGetUniformLocation(shader->program_id, "rotation");
uniforms->rotation_matrix = egl->glGetUniformLocation(shader->program_id, "rotation_matrix");
return 0;
}
static int load_framebuffers(gsr_color_conversion *self) {
/* TODO: Only generate the necessary amount of framebuffers (self->params.num_destination_textures) */
const unsigned int draw_buffer = GL_COLOR_ATTACHMENT0;
self->params.egl->glGenFramebuffers(MAX_FRAMEBUFFERS, self->framebuffers);
self->params.egl->glGenFramebuffers(GSR_COLOR_CONVERSION_MAX_FRAMEBUFFERS, self->framebuffers);
self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[0]);
self->params.egl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self->params.destination_textures[0], 0);
@@ -311,65 +457,185 @@ static int create_vertices(gsr_color_conversion *self) {
return 0;
}
static bool gsr_color_conversion_load_compute_shaders(gsr_color_conversion *self) {
switch(self->params.destination_color) {
case GSR_DESTINATION_COLOR_NV12:
case GSR_DESTINATION_COLOR_P010: {
if(load_compute_shader_y(&self->compute_shaders[COMPUTE_SHADER_INDEX_Y], self->params.egl, &self->compute_uniforms[COMPUTE_SHADER_INDEX_Y], self->max_local_size_dim, self->params.destination_color, self->params.color_range, false, false) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y compute shader\n");
return false;
}
if(load_compute_shader_uv(&self->compute_shaders[COMPUTE_SHADER_INDEX_UV], self->params.egl, &self->compute_uniforms[COMPUTE_SHADER_INDEX_UV], self->max_local_size_dim, self->params.destination_color, self->params.color_range, false, false) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load UV compute shader\n");
return false;
}
if(load_compute_shader_y(&self->compute_shaders[COMPUTE_SHADER_INDEX_Y_BLEND], self->params.egl, &self->compute_uniforms[COMPUTE_SHADER_INDEX_Y_BLEND], self->max_local_size_dim, self->params.destination_color, self->params.color_range, false, true) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y compute shader\n");
return false;
}
if(load_compute_shader_uv(&self->compute_shaders[COMPUTE_SHADER_INDEX_UV_BLEND], self->params.egl, &self->compute_uniforms[COMPUTE_SHADER_INDEX_UV_BLEND], self->max_local_size_dim, self->params.destination_color, self->params.color_range, false, true) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load UV compute shader\n");
return false;
}
break;
}
case GSR_DESTINATION_COLOR_RGB8: {
if(load_compute_shader_rgb(&self->compute_shaders[COMPUTE_SHADER_INDEX_RGB], self->params.egl, &self->compute_uniforms[COMPUTE_SHADER_INDEX_RGB], self->max_local_size_dim, false, false) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y compute shader\n");
return false;
}
if(load_compute_shader_rgb(&self->compute_shaders[COMPUTE_SHADER_INDEX_RGB_BLEND], self->params.egl, &self->compute_uniforms[COMPUTE_SHADER_INDEX_RGB_BLEND], self->max_local_size_dim, false, true) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y compute shader\n");
return false;
}
break;
}
}
return true;
}
static bool gsr_color_conversion_load_external_compute_shaders(gsr_color_conversion *self) {
switch(self->params.destination_color) {
case GSR_DESTINATION_COLOR_NV12:
case GSR_DESTINATION_COLOR_P010: {
if(load_compute_shader_y(&self->compute_shaders[COMPUTE_SHADER_INDEX_Y_EXTERNAL], self->params.egl, &self->compute_uniforms[COMPUTE_SHADER_INDEX_Y_EXTERNAL], self->max_local_size_dim, self->params.destination_color, self->params.color_range, true, false) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y compute shader\n");
return false;
}
if(load_compute_shader_uv(&self->compute_shaders[COMPUTE_SHADER_INDEX_UV_EXTERNAL], self->params.egl, &self->compute_uniforms[COMPUTE_SHADER_INDEX_UV_EXTERNAL], self->max_local_size_dim, self->params.destination_color, self->params.color_range, true, false) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load UV compute shader\n");
return false;
}
if(load_compute_shader_y(&self->compute_shaders[COMPUTE_SHADER_INDEX_Y_EXTERNAL_BLEND], self->params.egl, &self->compute_uniforms[COMPUTE_SHADER_INDEX_Y_EXTERNAL_BLEND], self->max_local_size_dim, self->params.destination_color, self->params.color_range, true, true) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y compute shader\n");
return false;
}
if(load_compute_shader_uv(&self->compute_shaders[COMPUTE_SHADER_INDEX_UV_EXTERNAL_BLEND], self->params.egl, &self->compute_uniforms[COMPUTE_SHADER_INDEX_UV_EXTERNAL_BLEND], self->max_local_size_dim, self->params.destination_color, self->params.color_range, true, true) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load UV compute shader\n");
return false;
}
break;
}
case GSR_DESTINATION_COLOR_RGB8: {
if(load_compute_shader_rgb(&self->compute_shaders[COMPUTE_SHADER_INDEX_RGB_EXTERNAL], self->params.egl, &self->compute_uniforms[COMPUTE_SHADER_INDEX_RGB_EXTERNAL], self->max_local_size_dim, true, false) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y compute shader\n");
return false;
}
if(load_compute_shader_rgb(&self->compute_shaders[COMPUTE_SHADER_INDEX_RGB_EXTERNAL_BLEND], self->params.egl, &self->compute_uniforms[COMPUTE_SHADER_INDEX_RGB_EXTERNAL_BLEND], self->max_local_size_dim, true, true) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y compute shader\n");
return false;
}
break;
}
}
return true;
}
static bool gsr_color_conversion_load_graphics_shaders(gsr_color_conversion *self) {
switch(self->params.destination_color) {
case GSR_DESTINATION_COLOR_NV12:
case GSR_DESTINATION_COLOR_P010: {
if(load_graphics_shader_y(&self->graphics_shaders[GRAPHICS_SHADER_INDEX_Y], self->params.egl, &self->graphics_uniforms[GRAPHICS_SHADER_INDEX_Y], self->params.destination_color, self->params.color_range, false) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y graphics shader\n");
return false;
}
if(load_graphics_shader_uv(&self->graphics_shaders[GRAPHICS_SHADER_INDEX_UV], self->params.egl, &self->graphics_uniforms[GRAPHICS_SHADER_INDEX_UV], self->params.destination_color, self->params.color_range, false) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load UV graphics shader\n");
return false;
}
break;
}
case GSR_DESTINATION_COLOR_RGB8: {
if(load_graphics_shader_rgb(&self->graphics_shaders[GRAPHICS_SHADER_INDEX_RGB], self->params.egl, &self->graphics_uniforms[GRAPHICS_SHADER_INDEX_RGB], false) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y graphics shader\n");
return false;
}
break;
}
}
return true;
}
static bool gsr_color_conversion_load_external_graphics_shaders(gsr_color_conversion *self) {
switch(self->params.destination_color) {
case GSR_DESTINATION_COLOR_NV12:
case GSR_DESTINATION_COLOR_P010: {
if(load_graphics_shader_y(&self->graphics_shaders[GRAPHICS_SHADER_INDEX_Y_EXTERNAL], self->params.egl, &self->graphics_uniforms[GRAPHICS_SHADER_INDEX_Y_EXTERNAL], self->params.destination_color, self->params.color_range, true) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y graphics shader\n");
return false;
}
if(load_graphics_shader_uv(&self->graphics_shaders[GRAPHICS_SHADER_INDEX_UV_EXTERNAL], self->params.egl, &self->graphics_uniforms[GRAPHICS_SHADER_INDEX_UV_EXTERNAL], self->params.destination_color, self->params.color_range, true) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load UV graphics shader\n");
return false;
}
break;
}
case GSR_DESTINATION_COLOR_RGB8: {
if(load_graphics_shader_rgb(&self->graphics_shaders[GRAPHICS_SHADER_INDEX_RGB_EXTERNAL], self->params.egl, &self->graphics_uniforms[GRAPHICS_SHADER_INDEX_RGB_EXTERNAL], true) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y graphics shader\n");
return false;
}
break;
}
}
return true;
}
int gsr_color_conversion_init(gsr_color_conversion *self, const gsr_color_conversion_params *params) {
assert(params);
assert(params->egl);
memset(self, 0, sizeof(*self));
self->params.egl = params->egl;
self->params = *params;
int max_compute_work_group_invocations = 256;
self->params.egl->glGetIntegerv(GL_MAX_COMPUTE_FIXED_GROUP_INVOCATIONS, &max_compute_work_group_invocations);
self->max_local_size_dim = sqrt(max_compute_work_group_invocations);
switch(params->destination_color) {
switch(self->params.destination_color) {
case GSR_DESTINATION_COLOR_NV12:
case GSR_DESTINATION_COLOR_P010: {
if(self->params.num_destination_textures != 2) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: expected 2 destination textures for destination color NV12/P010, got %d destination texture(s)\n", self->params.num_destination_textures);
return -1;
}
if(load_shader_y(&self->shaders[0], self->params.egl, &self->uniforms[0], params->destination_color, params->color_range, false) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y shader\n");
goto err;
}
if(load_shader_uv(&self->shaders[1], self->params.egl, &self->uniforms[1], params->destination_color, params->color_range, false) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load UV shader\n");
goto err;
}
if(self->params.load_external_image_shader) {
if(load_shader_y(&self->shaders[EXTERNAL_TEXTURE_SHADER_OFFSET], self->params.egl, &self->uniforms[EXTERNAL_TEXTURE_SHADER_OFFSET], params->destination_color, params->color_range, true) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y shader\n");
goto err;
}
if(load_shader_uv(&self->shaders[EXTERNAL_TEXTURE_SHADER_OFFSET + 1], self->params.egl, &self->uniforms[EXTERNAL_TEXTURE_SHADER_OFFSET + 1], params->destination_color, params->color_range, true) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load UV shader\n");
goto err;
}
}
break;
}
case GSR_DESTINATION_COLOR_RGB8: {
if(self->params.num_destination_textures != 1) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: expected 1 destination textures for destination color RGB8, got %d destination texture(s)\n", self->params.num_destination_textures);
return -1;
}
if(load_shader_rgb(&self->shaders[0], self->params.egl, &self->uniforms[0], false) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y shader\n");
goto err;
}
if(self->params.load_external_image_shader) {
if(load_shader_rgb(&self->shaders[EXTERNAL_TEXTURE_SHADER_OFFSET], self->params.egl, &self->uniforms[EXTERNAL_TEXTURE_SHADER_OFFSET], true) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y shader\n");
goto err;
}
}
break;
}
}
if(!gsr_color_conversion_load_compute_shaders(self)) {
self->compute_shaders_failed_to_load = true;
fprintf(stderr, "gsr info: failed to load one or more compute shaders, run gpu-screen-recorder with the '-gl-debug yes' option to see why. Falling back to slower graphics shader instead\n");
if(!gsr_color_conversion_load_graphics_shaders(self))
goto err;
}
if(self->params.load_external_image_shader) {
if(!gsr_color_conversion_load_external_compute_shaders(self)) {
self->external_compute_shaders_failed_to_load = true;
fprintf(stderr, "gsr info: failed to load one or more external compute shaders, run gpu-screen-recorder with the '-gl-debug yes' option to see why. Falling back to slower graphics shader instead\n");
if(!gsr_color_conversion_load_external_graphics_shaders(self))
goto err;
}
}
if(load_framebuffers(self) != 0)
goto err;
@@ -397,18 +663,59 @@ void gsr_color_conversion_deinit(gsr_color_conversion *self) {
self->vertex_array_object_id = 0;
}
self->params.egl->glDeleteFramebuffers(MAX_FRAMEBUFFERS, self->framebuffers);
for(int i = 0; i < MAX_FRAMEBUFFERS; ++i) {
self->params.egl->glDeleteFramebuffers(GSR_COLOR_CONVERSION_MAX_FRAMEBUFFERS, self->framebuffers);
for(int i = 0; i < GSR_COLOR_CONVERSION_MAX_FRAMEBUFFERS; ++i) {
self->framebuffers[i] = 0;
}
for(int i = 0; i < MAX_SHADERS; ++i) {
gsr_shader_deinit(&self->shaders[i]);
for(int i = 0; i < GSR_COLOR_CONVERSION_MAX_COMPUTE_SHADERS; ++i) {
gsr_shader_deinit(&self->compute_shaders[i]);
}
for(int i = 0; i < GSR_COLOR_CONVERSION_MAX_GRAPHICS_SHADERS; ++i) {
gsr_shader_deinit(&self->graphics_shaders[i]);
}
self->params.egl = NULL;
}
static void gsr_color_conversion_apply_rotation(gsr_rotation rotation, float rotation_matrix[2][2]) {
/*
rotation_matrix[0][0] = cos(angle);
rotation_matrix[0][1] = -sin(angle);
rotation_matrix[1][0] = sin(angle);
rotation_matrix[1][1] = cos(angle);
The manual matrix code below is the same as this code above, but without floating-point errors.
This is done to remove any blurring caused by these floating-point errors.
*/
switch(rotation) {
case GSR_ROT_0:
rotation_matrix[0][0] = 1.0f;
rotation_matrix[0][1] = 0.0f;
rotation_matrix[1][0] = 0.0f;
rotation_matrix[1][1] = 1.0f;
break;
case GSR_ROT_90:
rotation_matrix[0][0] = 0.0f;
rotation_matrix[0][1] = -1.0f;
rotation_matrix[1][0] = 1.0f;
rotation_matrix[1][1] = 0.0f;
break;
case GSR_ROT_180:
rotation_matrix[0][0] = -1.0f;
rotation_matrix[0][1] = 0.0f;
rotation_matrix[1][0] = 0.0f;
rotation_matrix[1][1] = -1.0f;
break;
case GSR_ROT_270:
rotation_matrix[0][0] = 0.0f;
rotation_matrix[0][1] = 1.0f;
rotation_matrix[1][0] = -1.0f;
rotation_matrix[1][1] = 0.0f;
break;
}
}
static void gsr_color_conversion_swizzle_texture_source(gsr_color_conversion *self, gsr_source_color source_color) {
if(source_color == GSR_SOURCE_COLOR_BGR) {
const int swizzle_mask[] = { GL_BLUE, GL_GREEN, GL_RED, 1 };
@@ -423,11 +730,79 @@ static void gsr_color_conversion_swizzle_reset(gsr_color_conversion *self, gsr_s
}
}
/* |source_pos| is in pixel coordinates and |source_size| */
void gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_id, vec2i source_pos, vec2i source_size, vec2i texture_pos, vec2i texture_size, float rotation, bool external_texture, gsr_source_color source_color) {
// TODO: Remove this crap
rotation = M_PI*2.0f - rotation;
typedef enum {
GSR_COLOR_COMP_Y,
GSR_COLOR_COMP_UV,
GSR_COLOR_COMP_RGB
} gsr_color_component;
static int color_component_get_destination_texture_index(gsr_color_component color_component) {
switch(color_component) {
case GSR_COLOR_COMP_Y: return 0;
case GSR_COLOR_COMP_UV: return 1;
case GSR_COLOR_COMP_RGB: return 0;
}
assert(false);
return 0;
}
static unsigned int color_component_get_color_format(gsr_color_component color_component, bool use_16bit_colors) {
switch(color_component) {
case GSR_COLOR_COMP_Y: return use_16bit_colors ? GL_R16 : GL_R8;
case GSR_COLOR_COMP_UV: return use_16bit_colors ? GL_RG16 : GL_RG8;
case GSR_COLOR_COMP_RGB: return GL_RGBA8; // TODO: 16-bit color support
}
assert(false);
return GL_RGBA8;
}
static int color_component_get_COMPUTE_SHADER_INDEX(gsr_color_component color_component, bool external_texture, bool alpha_blending) {
switch(color_component) {
case GSR_COLOR_COMP_Y: {
if(external_texture)
return alpha_blending ? COMPUTE_SHADER_INDEX_Y_EXTERNAL_BLEND : COMPUTE_SHADER_INDEX_Y_EXTERNAL;
else
return alpha_blending ? COMPUTE_SHADER_INDEX_Y_BLEND : COMPUTE_SHADER_INDEX_Y;
}
case GSR_COLOR_COMP_UV: {
if(external_texture)
return alpha_blending ? COMPUTE_SHADER_INDEX_UV_EXTERNAL_BLEND : COMPUTE_SHADER_INDEX_UV_EXTERNAL;
else
return alpha_blending ? COMPUTE_SHADER_INDEX_UV_BLEND : COMPUTE_SHADER_INDEX_UV;
}
case GSR_COLOR_COMP_RGB: {
if(external_texture)
return alpha_blending ? COMPUTE_SHADER_INDEX_RGB_EXTERNAL_BLEND : COMPUTE_SHADER_INDEX_RGB_EXTERNAL;
else
return alpha_blending ? COMPUTE_SHADER_INDEX_RGB_BLEND : COMPUTE_SHADER_INDEX_RGB;
}
}
assert(false);
return COMPUTE_SHADER_INDEX_RGB;
}
static void gsr_color_conversion_dispatch_compute_shader(gsr_color_conversion *self, bool external_texture, bool alpha_blending, float rotation_matrix[2][2], vec2i source_position, vec2i destination_pos, vec2i destination_size, vec2f scale, bool use_16bit_colors, gsr_color_component color_component) {
const int compute_shader_index = color_component_get_COMPUTE_SHADER_INDEX(color_component, external_texture, alpha_blending);
const int destination_texture_index = color_component_get_destination_texture_index(color_component);
const unsigned int color_format = color_component_get_color_format(color_component, use_16bit_colors);
self->params.egl->glActiveTexture(GL_TEXTURE1);
self->params.egl->glBindTexture(GL_TEXTURE_2D, self->params.destination_textures[destination_texture_index]);
self->params.egl->glActiveTexture(GL_TEXTURE0);
gsr_color_compute_uniforms *uniform = &self->compute_uniforms[compute_shader_index];
gsr_shader_use(&self->compute_shaders[compute_shader_index]);
self->params.egl->glUniformMatrix2fv(uniform->rotation_matrix, 1, GL_TRUE, (const float*)rotation_matrix);
self->params.egl->glUniform2i(uniform->source_position, source_position.x, source_position.y);
self->params.egl->glUniform2i(uniform->target_position, destination_pos.x, destination_pos.y);
self->params.egl->glUniform2f(uniform->scale, scale.x, scale.y);
self->params.egl->glBindImageTexture(0, self->params.destination_textures[destination_texture_index], 0, GL_FALSE, 0, GL_WRITE_ONLY, color_format);
const double num_groups_x = ceil((double)destination_size.x/(double)self->max_local_size_dim);
const double num_groups_y = ceil((double)destination_size.y/(double)self->max_local_size_dim);
self->params.egl->glDispatchCompute(max_int(1, num_groups_x), max_int(1, num_groups_y), 1);
}
static void gsr_color_conversion_draw_graphics(gsr_color_conversion *self, unsigned int texture_id, bool external_texture, float rotation_matrix[2][2], vec2i source_position, vec2i source_size, vec2i destination_pos, vec2i texture_size, vec2f scale, gsr_source_color source_color) {
/* TODO: Do not call this every frame? */
vec2i dest_texture_size = {0, 0};
self->params.egl->glBindTexture(GL_TEXTURE_2D, self->params.destination_textures[0]);
@@ -438,42 +813,26 @@ void gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_
const int texture_target = external_texture ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D;
self->params.egl->glBindTexture(texture_target, texture_id);
vec2i source_texture_size = {0, 0};
if(external_texture) {
assert(self->params.load_external_image_shader);
source_texture_size = source_size;
} else {
/* TODO: Do not call this every frame? */
self->params.egl->glGetTexLevelParameteriv(texture_target, 0, GL_TEXTURE_WIDTH, &source_texture_size.x);
self->params.egl->glGetTexLevelParameteriv(texture_target, 0, GL_TEXTURE_HEIGHT, &source_texture_size.y);
}
// TODO: Remove this crap
if(abs_f(M_PI * 0.5f - rotation) <= 0.001f || abs_f(M_PI * 1.5f - rotation) <= 0.001f) {
float tmp = source_texture_size.x;
source_texture_size.x = source_texture_size.y;
source_texture_size.y = tmp;
}
gsr_color_conversion_swizzle_texture_source(self, source_color);
const vec2f pos_norm = {
((float)source_pos.x / (dest_texture_size.x == 0 ? 1.0f : (float)dest_texture_size.x)) * 2.0f,
((float)source_pos.y / (dest_texture_size.y == 0 ? 1.0f : (float)dest_texture_size.y)) * 2.0f,
((float)destination_pos.x / (dest_texture_size.x == 0 ? 1.0f : (float)dest_texture_size.x)) * 2.0f,
((float)destination_pos.y / (dest_texture_size.y == 0 ? 1.0f : (float)dest_texture_size.y)) * 2.0f,
};
const vec2f size_norm = {
((float)source_size.x / (dest_texture_size.x == 0 ? 1.0f : (float)dest_texture_size.x)) * 2.0f,
((float)source_size.y / (dest_texture_size.y == 0 ? 1.0f : (float)dest_texture_size.y)) * 2.0f,
((float)source_size.x / (dest_texture_size.x == 0 ? 1.0f : (float)dest_texture_size.x)) * 2.0f * scale.x,
((float)source_size.y / (dest_texture_size.y == 0 ? 1.0f : (float)dest_texture_size.y)) * 2.0f * scale.y,
};
const vec2f texture_pos_norm = {
(float)texture_pos.x / (source_texture_size.x == 0 ? 1.0f : (float)source_texture_size.x),
(float)texture_pos.y / (source_texture_size.y == 0 ? 1.0f : (float)source_texture_size.y),
(float)source_position.x / (texture_size.x == 0 ? 1.0f : (float)texture_size.x),
(float)source_position.y / (texture_size.y == 0 ? 1.0f : (float)texture_size.y),
};
const vec2f texture_size_norm = {
(float)texture_size.x / (source_texture_size.x == 0 ? 1.0f : (float)source_texture_size.x),
(float)texture_size.y / (source_texture_size.y == 0 ? 1.0f : (float)source_texture_size.y),
(float)source_size.x / (texture_size.x == 0 ? 1.0f : (float)texture_size.x),
(float)source_size.y / (texture_size.y == 0 ? 1.0f : (float)texture_size.y),
};
const float vertices[] = {
@@ -486,8 +845,6 @@ void gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_
-1.0f + 0.0f + size_norm.x, -1.0f + 0.0f + size_norm.y, texture_pos_norm.x + texture_size_norm.x, texture_pos_norm.y + texture_size_norm.y
};
gsr_color_conversion_swizzle_texture_source(self, source_color);
self->params.egl->glBindVertexArray(self->vertex_array_object_id);
self->params.egl->glViewport(0, 0, dest_texture_size.x, dest_texture_size.y);
@@ -495,34 +852,115 @@ void gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_
//self->params.egl->glBindBuffer(GL_ARRAY_BUFFER, self->vertex_buffer_object_id);
self->params.egl->glBufferSubData(GL_ARRAY_BUFFER, 0, 24 * sizeof(float), vertices);
{
self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[0]);
//cap_xcomp->params.egl->glClear(GL_COLOR_BUFFER_BIT); // TODO: Do this in a separate clear_ function. We want to do that when using multiple drm to create the final image (multiple monitors for example)
switch(self->params.destination_color) {
case GSR_DESTINATION_COLOR_NV12:
case GSR_DESTINATION_COLOR_P010: {
self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[0]);
//cap_xcomp->params.egl->glClear(GL_COLOR_BUFFER_BIT); // TODO: Do this in a separate clear_ function. We want to do that when using multiple drm to create the final image (multiple monitors for example)
const int shader_index = external_texture ? EXTERNAL_TEXTURE_SHADER_OFFSET : 0;
gsr_shader_use(&self->shaders[shader_index]);
self->params.egl->glUniform1f(self->uniforms[shader_index].rotation, rotation);
self->params.egl->glUniform2f(self->uniforms[shader_index].offset, pos_norm.x, pos_norm.y);
self->params.egl->glDrawArrays(GL_TRIANGLES, 0, 6);
}
int shader_index = external_texture ? GRAPHICS_SHADER_INDEX_Y_EXTERNAL : GRAPHICS_SHADER_INDEX_Y;
gsr_shader_use(&self->graphics_shaders[shader_index]);
self->params.egl->glUniformMatrix2fv(self->graphics_uniforms[shader_index].rotation_matrix, 1, GL_TRUE, (const float*)rotation_matrix);
self->params.egl->glUniform2f(self->graphics_uniforms[shader_index].offset, pos_norm.x, pos_norm.y);
self->params.egl->glDrawArrays(GL_TRIANGLES, 0, 6);
if(self->params.num_destination_textures > 1) {
self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[1]);
//cap_xcomp->params.egl->glClear(GL_COLOR_BUFFER_BIT);
if(self->params.num_destination_textures > 1) {
self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[1]);
//cap_xcomp->params.egl->glClear(GL_COLOR_BUFFER_BIT);
const int shader_index = external_texture ? EXTERNAL_TEXTURE_SHADER_OFFSET + 1 : 1;
gsr_shader_use(&self->shaders[shader_index]);
self->params.egl->glUniform1f(self->uniforms[shader_index].rotation, rotation);
self->params.egl->glUniform2f(self->uniforms[shader_index].offset, pos_norm.x, pos_norm.y);
self->params.egl->glDrawArrays(GL_TRIANGLES, 0, 6);
shader_index = external_texture ? GRAPHICS_SHADER_INDEX_UV_EXTERNAL : GRAPHICS_SHADER_INDEX_UV;
gsr_shader_use(&self->graphics_shaders[shader_index]);
self->params.egl->glUniformMatrix2fv(self->graphics_uniforms[shader_index].rotation_matrix, 1, GL_TRUE, (const float*)rotation_matrix);
self->params.egl->glUniform2f(self->graphics_uniforms[shader_index].offset, pos_norm.x, pos_norm.y);
self->params.egl->glDrawArrays(GL_TRIANGLES, 0, 6);
}
break;
}
case GSR_DESTINATION_COLOR_RGB8: {
self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[0]);
//cap_xcomp->params.egl->glClear(GL_COLOR_BUFFER_BIT); // TODO: Do this in a separate clear_ function. We want to do that when using multiple drm to create the final image (multiple monitors for example)
const int shader_index = external_texture ? GRAPHICS_SHADER_INDEX_RGB_EXTERNAL : GRAPHICS_SHADER_INDEX_RGB;
gsr_shader_use(&self->graphics_shaders[shader_index]);
self->params.egl->glUniformMatrix2fv(self->graphics_uniforms[shader_index].rotation_matrix, 1, GL_TRUE, (const float*)rotation_matrix);
self->params.egl->glUniform2f(self->graphics_uniforms[shader_index].offset, pos_norm.x, pos_norm.y);
self->params.egl->glDrawArrays(GL_TRIANGLES, 0, 6);
break;
}
}
self->params.egl->glBindVertexArray(0);
gsr_shader_use_none(&self->shaders[0]);
self->params.egl->glUseProgram(0);
gsr_color_conversion_swizzle_reset(self, source_color);
self->params.egl->glBindTexture(texture_target, 0);
self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_id, vec2i destination_pos, vec2i destination_size, vec2i source_pos, vec2i source_size, vec2i texture_size, gsr_rotation rotation, gsr_source_color source_color, bool external_texture, bool alpha_blending) {
assert(!external_texture || self->params.load_external_image_shader);
if(external_texture && !self->params.load_external_image_shader) {
fprintf(stderr, "gsr error: gsr_color_conversion_draw: external texture not loaded\n");
return;
}
vec2f scale = {0.0f, 0.0f};
if(source_size.x > 0 && source_size.y > 0)
scale = (vec2f){ (double)destination_size.x/(double)source_size.x, (double)destination_size.y/(double)source_size.y };
vec2i source_position = {0, 0};
float rotation_matrix[2][2] = {{0, 0}, {0, 0}};
gsr_color_conversion_apply_rotation(rotation, rotation_matrix);
const int texture_target = external_texture ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D;
self->params.egl->glBindTexture(texture_target, texture_id);
gsr_color_conversion_swizzle_texture_source(self, source_color);
const bool use_graphics_shader = external_texture ? self->external_compute_shaders_failed_to_load : self->compute_shaders_failed_to_load;
if(use_graphics_shader) {
source_position.x += source_pos.x;
source_position.y += source_pos.y;
gsr_color_conversion_draw_graphics(self, texture_id, external_texture, rotation_matrix, source_position, source_size, destination_pos, texture_size, scale, source_color);
// TODO: Is glFlush and glFinish needed here for graphics garbage?
} else {
switch(rotation) {
case GSR_ROT_0:
break;
case GSR_ROT_90:
source_position.x += (((double)texture_size.x*0.5 - (double)texture_size.y*0.5) * scale.x);
source_position.y += (((double)texture_size.y*0.5 - (double)texture_size.x*0.5) * scale.y);
break;
case GSR_ROT_180:
break;
case GSR_ROT_270:
source_position.x += (((double)texture_size.x*0.5 - (double)texture_size.y*0.5) * scale.x);
source_position.y += (((double)texture_size.y*0.5 - (double)texture_size.x*0.5) * scale.y);
break;
}
source_position.x -= (source_pos.x * scale.x + 0.5);
source_position.y -= (source_pos.y * scale.y + 0.5);
switch(self->params.destination_color) {
case GSR_DESTINATION_COLOR_NV12:
case GSR_DESTINATION_COLOR_P010: {
const bool use_16bit_colors = self->params.destination_color == GSR_DESTINATION_COLOR_P010;
gsr_color_conversion_dispatch_compute_shader(self, external_texture, alpha_blending, rotation_matrix, source_position, destination_pos, destination_size, scale, use_16bit_colors, GSR_COLOR_COMP_Y);
gsr_color_conversion_dispatch_compute_shader(self, external_texture, alpha_blending, rotation_matrix, (vec2i){source_position.x/2, source_position.y/2},
(vec2i){destination_pos.x/2, destination_pos.y/2}, (vec2i){destination_size.x/2, destination_size.y/2}, scale, use_16bit_colors, GSR_COLOR_COMP_UV);
break;
}
case GSR_DESTINATION_COLOR_RGB8: {
gsr_color_conversion_dispatch_compute_shader(self, external_texture, alpha_blending, rotation_matrix, source_position, destination_pos, destination_size, scale, false, GSR_COLOR_COMP_RGB);
break;
}
}
}
// TODO: Use the minimal barrier required
self->params.egl->glMemoryBarrier(GL_ALL_BARRIER_BITS); // GL_SHADER_IMAGE_ACCESS_BARRIER_BIT
self->params.egl->glUseProgram(0);
gsr_color_conversion_swizzle_reset(self, source_color);
self->params.egl->glBindTexture(texture_target, 0);
}
void gsr_color_conversion_clear(gsr_color_conversion *self) {
@@ -559,3 +997,7 @@ void gsr_color_conversion_clear(gsr_color_conversion *self) {
self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
gsr_rotation gsr_monitor_rotation_to_rotation(gsr_monitor_rotation monitor_rotation) {
return (gsr_rotation)monitor_rotation;
}

View File

@@ -56,8 +56,10 @@ static bool gsr_cursor_set_from_x11_cursor_image(gsr_cursor *self, XFixesCursorI
self->egl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, self->size.x, self->size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, cursor_data);
free(cursor_data);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
const float border_color[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
self->egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

View File

@@ -283,9 +283,12 @@ static bool gsr_egl_load_gl(gsr_egl *self, void *library) {
{ (void**)&self->glClearColor, "glClearColor" },
{ (void**)&self->glGenTextures, "glGenTextures" },
{ (void**)&self->glDeleteTextures, "glDeleteTextures" },
{ (void**)&self->glActiveTexture, "glActiveTexture" },
{ (void**)&self->glBindTexture, "glBindTexture" },
{ (void**)&self->glBindImageTexture, "glBindImageTexture" },
{ (void**)&self->glTexParameteri, "glTexParameteri" },
{ (void**)&self->glTexParameteriv, "glTexParameteriv" },
{ (void**)&self->glTexParameterfv, "glTexParameterfv" },
{ (void**)&self->glGetTexLevelParameteriv, "glGetTexLevelParameteriv" },
{ (void**)&self->glTexImage2D, "glTexImage2D" },
{ (void**)&self->glTexSubImage2D, "glTexSubImage2D" },
@@ -293,6 +296,8 @@ static bool gsr_egl_load_gl(gsr_egl *self, void *library) {
{ (void**)&self->glGenFramebuffers, "glGenFramebuffers" },
{ (void**)&self->glBindFramebuffer, "glBindFramebuffer" },
{ (void**)&self->glDeleteFramebuffers, "glDeleteFramebuffers" },
{ (void**)&self->glDispatchCompute, "glDispatchCompute" },
{ (void**)&self->glMemoryBarrier, "glMemoryBarrier" },
{ (void**)&self->glViewport, "glViewport" },
{ (void**)&self->glFramebufferTexture2D, "glFramebufferTexture2D" },
{ (void**)&self->glDrawBuffers, "glDrawBuffers" },
@@ -329,11 +334,15 @@ static bool gsr_egl_load_gl(gsr_egl *self, void *library) {
{ (void**)&self->glGetUniformLocation, "glGetUniformLocation" },
{ (void**)&self->glUniform1f, "glUniform1f" },
{ (void**)&self->glUniform2f, "glUniform2f" },
{ (void**)&self->glUniform1i, "glUniform1i" },
{ (void**)&self->glUniform2i, "glUniform2i" },
{ (void**)&self->glUniformMatrix2fv, "glUniformMatrix2fv" },
{ (void**)&self->glDebugMessageCallback, "glDebugMessageCallback" },
{ (void**)&self->glScissor, "glScissor" },
{ (void**)&self->glReadPixels, "glReadPixels" },
{ (void**)&self->glMapBuffer, "glMapBuffer" },
{ (void**)&self->glUnmapBuffer, "glUnmapBuffer" },
{ (void**)&self->glGetIntegerv, "glGetIntegerv" },
{ NULL, NULL }
};

View File

@@ -83,8 +83,8 @@ static void gsr_video_encoder_software_copy_textures_to_frame(gsr_video_encoder
self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
// cap_kms->kms.base.egl->eglSwapBuffers(cap_kms->kms.base.egl->egl_display, cap_kms->kms.base.egl->egl_surface);
self->params.egl->glFlush();
self->params.egl->glFinish();
//self->params.egl->glFlush();
//self->params.egl->glFinish();
}
static void gsr_video_encoder_software_get_textures(gsr_video_encoder *encoder, unsigned int *textures, int *num_textures, gsr_destination_color *destination_color) {

View File

@@ -92,6 +92,10 @@ static bool gsr_video_encoder_vaapi_setup_textures(gsr_video_encoder_vaapi *self
if(self->prime.fourcc == VA_FOURCC_NV12 || self->prime.fourcc == VA_FOURCC_P010) {
const uint32_t *formats = self->prime.fourcc == VA_FOURCC_NV12 ? formats_nv12 : formats_p010;
const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size
const float border_colors[2][4] = {
{0.0f, 0.0f, 0.0f, 1.0f},
{0.5f, 0.5f, 0.0f, 1.0f}
};
self->params.egl->glGenTextures(2, self->target_textures);
for(int i = 0; i < 2; ++i) {
@@ -121,10 +125,11 @@ static bool gsr_video_encoder_vaapi_setup_textures(gsr_video_encoder_vaapi *self
}
self->params.egl->glBindTexture(GL_TEXTURE_2D, self->target_textures[i]);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
self->params.egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_colors[i]);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
while(self->params.egl->glGetError()) {}
while(self->params.egl->eglGetError() != EGL_SUCCESS){}

View File

@@ -94,7 +94,7 @@ static bool gsr_video_encoder_vulkan_setup_textures(gsr_video_encoder_vulkan *se
//AVVkFrame *target_surface_id = (AVVkFrame*)frame->data[0];
self->vv = video_codec_context_get_vulkan_data(video_codec_context);
const unsigned int internal_formats_nv12[2] = { GL_RGBA8, GL_RGBA8 };
const unsigned int internal_formats_nv12[2] = { GL_RGBA8, GL_RGBA8 }; // TODO: GL_R8, GL_R16
const unsigned int internal_formats_p010[2] = { GL_R16, GL_RG16 };
const unsigned int formats[2] = { GL_RED, GL_RG };
const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size

View File

@@ -17,7 +17,7 @@ bool gsr_image_writer_init_opengl(gsr_image_writer *self, gsr_egl *egl, int widt
self->egl = egl;
self->width = width;
self->height = height;
self->texture = gl_create_texture(self->egl, self->width, self->height, GL_RGB8, GL_RGB, GL_NEAREST); /* TODO: use GL_RGB16 instead of GL_RGB8 for hdr/10-bit */
self->texture = gl_create_texture(self->egl, self->width, self->height, GL_RGBA8, GL_RGBA, GL_NEAREST); /* 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;
@@ -50,10 +50,10 @@ static bool gsr_image_writer_write_memory_to_file(gsr_image_writer *self, const
bool success = false;
switch(image_format) {
case GSR_IMAGE_FORMAT_JPEG:
success = stbi_write_jpg(filepath, self->width, self->height, 3, data, quality);
success = stbi_write_jpg(filepath, self->width, self->height, 4, data, quality);
break;
case GSR_IMAGE_FORMAT_PNG:
success = stbi_write_png(filepath, self->width, self->height, 3, data, 0);
success = stbi_write_png(filepath, self->width, self->height, 4, data, 0);
break;
}
@@ -65,7 +65,7 @@ static bool gsr_image_writer_write_memory_to_file(gsr_image_writer *self, const
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);
uint8_t *frame_data = malloc(self->width * self->height * 4);
if(!frame_data) {
fprintf(stderr, "gsr error: gsr_image_writer_write_to_file: failed to allocate memory for image frame\n");
return false;
@@ -74,7 +74,7 @@ static bool gsr_image_writer_write_opengl_texture_to_file(gsr_image_writer *self
// TODO: hdr support
self->egl->glBindTexture(GL_TEXTURE_2D, self->texture);
// We could use glGetTexSubImage, but it's only available starting from opengl 4.5
self->egl->glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, frame_data);
self->egl->glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame_data);
self->egl->glBindTexture(GL_TEXTURE_2D, 0);
self->egl->glFlush();

View File

@@ -82,11 +82,14 @@ typedef struct {
static void monitor_output_callback_print(const gsr_monitor *monitor, void *userdata) {
const MonitorOutputCallbackUserdata *options = (MonitorOutputCallbackUserdata*)userdata;
vec2i monitor_position = monitor->pos;
vec2i monitor_size = monitor->size;
if(gsr_window_get_display_server(options->window) == GSR_DISPLAY_SERVER_WAYLAND) {
gsr_monitor_rotation monitor_rotation = GSR_MONITOR_ROT_0;
drm_monitor_get_display_server_data(options->window, monitor, &monitor_rotation, &monitor_position);
if(monitor_rotation == GSR_MONITOR_ROT_90 || monitor_rotation == GSR_MONITOR_ROT_270)
std::swap(monitor_size.x, monitor_size.y);
}
fprintf(stderr, " \"%.*s\" (%dx%d+%d+%d)\n", monitor->name_len, monitor->name, monitor->size.x, monitor->size.y, monitor_position.x, monitor_position.y);
fprintf(stderr, " \"%.*s\" (%dx%d+%d+%d)\n", monitor->name_len, monitor->name, monitor_size.x, monitor_size.y, monitor_position.x, monitor_position.y);
}
typedef struct {
@@ -110,9 +113,14 @@ typedef struct {
static void get_monitor_by_position_callback(const gsr_monitor *monitor, void *userdata) {
MonitorByPositionCallback *data = (MonitorByPositionCallback*)userdata;
gsr_monitor_rotation monitor_rotation = GSR_MONITOR_ROT_0;
vec2i monitor_position = monitor->pos;
drm_monitor_get_display_server_data(data->window, monitor, &monitor_rotation, &monitor_position);
vec2i monitor_size = monitor->size;
if(gsr_window_get_display_server(data->window) == GSR_DISPLAY_SERVER_WAYLAND) {
gsr_monitor_rotation monitor_rotation = GSR_MONITOR_ROT_0;
drm_monitor_get_display_server_data(data->window, monitor, &monitor_rotation, &monitor_position);
if(monitor_rotation == GSR_MONITOR_ROT_90 || monitor_rotation == GSR_MONITOR_ROT_270)
std::swap(monitor_size.x, monitor_size.y);
}
if(!data->output_name && data->position.x >= monitor_position.x && data->position.x <= monitor_position.x + monitor->size.x
&& data->position.y >= monitor_position.y && data->position.y <= monitor_position.y + monitor->size.y)
@@ -173,6 +181,11 @@ enum class BitrateMode {
CBR
};
enum class Tune {
PERFORMANCE,
QUALITY
};
static int x11_error_handler(Display*, XErrorEvent*) {
return 0;
}
@@ -777,7 +790,7 @@ static void dict_set_profile(AVCodecContext *codec_context, gsr_gpu_vendor vendo
//if(color_depth == GSR_COLOR_DEPTH_10_BITS)
// av_dict_set_int(options, "profile", AV_PROFILE_H264_HIGH_10, 0);
//else
av_dict_set_int(options, "profile", AV_PROFILE_H264_HIGH, 0);
av_dict_set_int(options, "profile", vendor == GSR_GPU_VENDOR_NVIDIA ? 2 : AV_PROFILE_H264_HIGH, 0);
} else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
if(vendor == GSR_GPU_VENDOR_NVIDIA) {
if(color_depth == GSR_COLOR_DEPTH_10_BITS)
@@ -787,9 +800,9 @@ static void dict_set_profile(AVCodecContext *codec_context, gsr_gpu_vendor vendo
}
} else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
if(color_depth == GSR_COLOR_DEPTH_10_BITS)
av_dict_set_int(options, "profile", AV_PROFILE_HEVC_MAIN_10, 0);
av_dict_set_int(options, "profile", vendor == GSR_GPU_VENDOR_NVIDIA ? 1 : AV_PROFILE_HEVC_MAIN_10, 0);
else
av_dict_set_int(options, "profile", AV_PROFILE_HEVC_MAIN, 0);
av_dict_set_int(options, "profile", vendor == GSR_GPU_VENDOR_NVIDIA ? 0 : AV_PROFILE_HEVC_MAIN, 0);
}
#endif
}
@@ -915,7 +928,7 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi
av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
av_dict_set_int(options, "qp", 27 * qp_multiply, 0);
av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
@@ -930,7 +943,7 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi
av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
av_dict_set_int(options, "qp", 27 * qp_multiply, 0);
av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
@@ -945,7 +958,7 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi
av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
av_dict_set_int(options, "qp", 27 * qp_multiply, 0);
av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
@@ -960,7 +973,7 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi
av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
av_dict_set_int(options, "qp", 27 * qp_multiply, 0);
av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
@@ -979,7 +992,7 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi
av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
av_dict_set_int(options, "qp", 27 * qp_multiply, 0);
av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
@@ -994,7 +1007,7 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi
av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
av_dict_set_int(options, "qp", 27 * qp_multiply, 0);
av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
@@ -1009,7 +1022,7 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi
av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
av_dict_set_int(options, "qp", 27 * qp_multiply, 0);
av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
@@ -1019,7 +1032,7 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi
}
}
static void open_video_hardware(AVCodecContext *codec_context, VideoQuality video_quality, bool very_old_gpu, gsr_gpu_vendor vendor, PixelFormat pixel_format, bool hdr, gsr_color_depth color_depth, BitrateMode bitrate_mode, VideoCodec video_codec, bool low_power) {
static void open_video_hardware(AVCodecContext *codec_context, VideoQuality video_quality, bool very_old_gpu, gsr_gpu_vendor vendor, PixelFormat pixel_format, bool hdr, gsr_color_depth color_depth, BitrateMode bitrate_mode, VideoCodec video_codec, bool low_power, Tune tune) {
(void)very_old_gpu;
AVDictionary *options = nullptr;
@@ -1043,6 +1056,17 @@ static void open_video_hardware(AVCodecContext *codec_context, VideoQuality vide
// }
av_dict_set(&options, "tune", "hq", 0);
switch(tune) {
case Tune::PERFORMANCE:
//av_dict_set(&options, "multipass", "qres", 0);
break;
case Tune::QUALITY:
av_dict_set(&options, "multipass", "fullres", 0);
av_dict_set(&options, "preset", "p6", 0);
av_dict_set_int(&options, "rc-lookahead", 0, 0);
break;
}
dict_set_profile(codec_context, vendor, color_depth, &options);
if(codec_context->codec_id == AV_CODEC_ID_H264) {
@@ -1072,8 +1096,9 @@ static void open_video_hardware(AVCodecContext *codec_context, VideoQuality vide
// TODO: More quality options
if(low_power)
av_dict_set_int(&options, "low_power", 1, 0);
// Improves performance but increases vram
//av_dict_set_int(&options, "async_depth", 8, 0);
// Improves performance but increases vram.
// TODO: Might need a different async_depth for optimal performance on different amd/intel gpus
av_dict_set_int(&options, "async_depth", 3, 0);
if(codec_context->codec_id == AV_CODEC_ID_H264) {
// Removed because it causes stutter in games for some people
@@ -1104,7 +1129,7 @@ static void open_video_hardware(AVCodecContext *codec_context, VideoQuality vide
static void usage_header() {
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
const char *program_name = inside_flatpak ? "flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder" : "gpu-screen-recorder";
printf("usage: %s -w <window_id|monitor|focused|portal|region> [-c <container_format>] [-s WxH] [-region WxH+X+Y] [-f <fps>] [-a <audio_input>] [-q <quality>] [-r <replay_buffer_size_sec>] [-restart-replay-on-save yes|no] [-k h264|hevc|av1|vp8|vp9|hevc_hdr|av1_hdr|hevc_10bit|av1_10bit] [-ac aac|opus|flac] [-ab <bitrate>] [-oc yes|no] [-fm cfr|vfr|content] [-bm auto|qp|vbr|cbr] [-cr limited|full] [-df yes|no] [-sc <script_path>] [-cursor yes|no] [-keyint <value>] [-restore-portal-session yes|no] [-portal-session-token-filepath filepath] [-encoder gpu|cpu] [-o <output_file>] [--list-capture-options [card_path] [vendor]] [--list-audio-devices] [--list-application-audio] [-v yes|no] [-gl-debug yes|no] [--version] [-h|--help]\n", program_name);
printf("usage: %s -w <window_id|monitor|focused|portal|region> [-c <container_format>] [-s WxH] [-region WxH+X+Y] [-f <fps>] [-a <audio_input>] [-q <quality>] [-r <replay_buffer_size_sec>] [-restart-replay-on-save yes|no] [-k h264|hevc|av1|vp8|vp9|hevc_hdr|av1_hdr|hevc_10bit|av1_10bit] [-ac aac|opus|flac] [-ab <bitrate>] [-oc yes|no] [-fm cfr|vfr|content] [-bm auto|qp|vbr|cbr] [-cr limited|full] [-tune performance|quality] [-df yes|no] [-sc <script_path>] [-cursor yes|no] [-keyint <value>] [-restore-portal-session yes|no] [-portal-session-token-filepath filepath] [-encoder gpu|cpu] [-o <output_file>] [--list-capture-options [card_path] [vendor]] [--list-audio-devices] [--list-application-audio] [-v yes|no] [-gl-debug yes|no] [--version] [-h|--help]\n", program_name);
fflush(stdout);
}
@@ -1209,6 +1234,10 @@ static void usage_full() {
printf(" Note that some buggy video players (such as vlc) are unable to correctly display videos in full color range and when upload the video to websites the website\n");
printf(" might re-encoder the video to make the video limited color range.\n");
printf("\n");
printf(" -tune\n");
printf(" Tune for performance or quality. Should be either 'performance' or 'quality'. At the moment this option only has an effect on Nvidia where setting this to quality\n");
printf(" sets options such as preset, multipass and b frames. Optional, set to 'performance' by default.\n");
printf("\n");
printf(" -df Organise replays in folders based on the current date.\n");
printf("\n");
printf(" -sc Run a script on the saved video file (asynchronously). The first argument to the script is the filepath to the saved video file and the second argument is the recording type (either \"regular\" or \"replay\").\n");
@@ -1252,7 +1281,7 @@ static void usage_full() {
printf(" window\n");
printf(" DP-1|1920x1080\n");
printf(" The <option> and <monitor_name> is the name that can be passed to GPU Screen Recorder with the -w option.\n");
printf(" --list-capture-options optionally accepts a card path (\"/dev/dri/cardN\") and vendor (\"amd\", \"intel\" or \"nvidia\") which can improve the performance of running this command.\n");
printf(" --list-capture-options optionally accepts a card path (\"/dev/dri/cardN\") and vendor (\"amd\", \"intel\", \"nvidia\" or \"broadcom\") which can improve the performance of running this command.\n");
printf("\n");
printf(" --list-audio-devices\n");
printf(" List audio devices. Lists audio devices in the following format (prints them to stdout and exits):\n");
@@ -1287,7 +1316,7 @@ static void usage_full() {
printf("NOTES:\n");
printf(" Send signal SIGINT to gpu-screen-recorder (Ctrl+C, or killall -SIGINT gpu-screen-recorder) to stop and save the recording. When in replay mode this stops recording without saving.\n");
printf(" Send signal SIGUSR1 to gpu-screen-recorder (killall -SIGUSR1 gpu-screen-recorder) to save a replay (when in replay mode).\n");
printf(" Send signal SIGUSR2 to gpu-screen-recorder (killall -SIGUSR2 gpu-screen-recorder) to pause/unpause recording. Only applicable and useful when recording (not streaming nor replay).\n");
printf(" Send signal SIGUSR2 to gpu-screen-recorder (killall -SIGUSR2 gpu-screen-recorder) to pause/unpause recording. Only applicable when recording (not streaming nor replay).\n");
printf("\n");
printf("EXAMPLES:\n");
printf(" %s -w screen -f 60 -a default_output -o video.mp4\n", program_name);
@@ -1299,7 +1328,7 @@ static void usage_full() {
printf(" %s -w screen -f 60 -a default_output -bm cbr -q 15000 -o video.mp4\n", program_name);
printf(" %s -w screen -f 60 -a \"app:firefox|app:csgo\" -o video.mp4\n", program_name);
printf(" %s -w screen -f 60 -a \"app-inverse:firefox|app-inverse:csgo\" -o video.mp4\n", program_name);
printf(" %s -w screen -f 60 -a \"default-input|app-inverse:Brave\" -o video.mp4\n", program_name);
printf(" %s -w screen -f 60 -a \"default_input|app-inverse:Brave\" -o video.mp4\n", program_name);
printf(" %s -w screen -o image.jpg\n", program_name);
printf(" %s -w screen -q medium -o image.jpg\n", program_name);
printf(" %s -w region -region 640x480+100+100 -o video.mp4\n", program_name);
@@ -1922,7 +1951,8 @@ static gsr_video_encoder* create_video_encoder(gsr_egl *egl, bool overclock, gsr
switch(egl->gpu_info.vendor) {
case GSR_GPU_VENDOR_AMD:
case GSR_GPU_VENDOR_INTEL: {
case GSR_GPU_VENDOR_INTEL:
case GSR_GPU_VENDOR_BROADCOM: {
gsr_video_encoder_vaapi_params params;
params.egl = egl;
params.color_depth = color_depth;
@@ -1956,6 +1986,7 @@ static bool get_supported_video_codecs(gsr_egl *egl, VideoCodec video_codec, boo
switch(egl->gpu_info.vendor) {
case GSR_GPU_VENDOR_AMD:
case GSR_GPU_VENDOR_INTEL:
case GSR_GPU_VENDOR_BROADCOM:
return gsr_get_supported_video_codecs_vaapi(video_codecs, egl->card_path, cleanup);
case GSR_GPU_VENDOR_NVIDIA:
return gsr_get_supported_video_codecs_nvenc(video_codecs, cleanup);
@@ -2029,6 +2060,9 @@ static void list_gpu_info(gsr_egl *egl) {
case GSR_GPU_VENDOR_NVIDIA:
printf("vendor|nvidia\n");
break;
case GSR_GPU_VENDOR_BROADCOM:
printf("vendor|broadcom\n");
break;
}
printf("card_path|%s\n", egl->card_path);
}
@@ -2369,6 +2403,9 @@ static bool gpu_vendor_from_string(const char *vendor_str, gsr_gpu_vendor *vendo
} else if(strcmp(vendor_str, "nvidia") == 0) {
*vendor = GSR_GPU_VENDOR_NVIDIA;
return true;
} else if(strcmp(vendor_str, "broadcom") == 0) {
*vendor = GSR_GPU_VENDOR_BROADCOM;
return true;
} else {
return false;
}
@@ -3313,7 +3350,7 @@ int main(int argc, char **argv) {
const char *vendor_str = argv[3];
gsr_gpu_vendor vendor;
if(!gpu_vendor_from_string(vendor_str, &vendor)) {
fprintf(stderr, "Error: \"%s\" is not a valid vendor, expected \"amd\", \"intel\" or \"nvidia\"\n", vendor_str);
fprintf(stderr, "Error: \"%s\" is not a valid vendor, expected \"amd\", \"intel\", \"nvidia\" or \"broadcom\"\n", vendor_str);
_exit(1);
}
@@ -3357,10 +3394,11 @@ int main(int argc, char **argv) {
{ "-df", Arg { {}, is_optional, !is_list, ArgType::BOOLEAN, {false} } },
{ "-sc", Arg { {}, is_optional, !is_list, ArgType::STRING, {false} } },
{ "-cr", Arg { {}, is_optional, !is_list, ArgType::STRING, {false} } },
{ "-tune", Arg { {}, is_optional, !is_list, ArgType::STRING, {false} } },
{ "-cursor", Arg { {}, is_optional, !is_list, ArgType::BOOLEAN, {false} } },
{ "-keyint", Arg { {}, is_optional, !is_list, ArgType::STRING, {false} } },
{ "-restore-portal-session", Arg { {}, is_optional, !is_list, ArgType::BOOLEAN, {false} } },
{ "-portal-session-token-filepath", Arg { {}, is_optional, !is_list, ArgType::BOOLEAN, {false} } },
{ "-portal-session-token-filepath", Arg { {}, is_optional, !is_list, ArgType::STRING, {false} } },
{ "-encoder", Arg { {}, is_optional, !is_list, ArgType::STRING, {false} } },
};
@@ -3432,7 +3470,7 @@ int main(int argc, char **argv) {
//} else if(strcmp(video_codec_to_use, "hevc_vulkan") == 0) {
// video_codec = VideoCodec::HEVC_VULKAN;
} else if(strcmp(video_codec_to_use, "auto") != 0) {
fprintf(stderr, "Error: -k should either be either 'auto', 'h264', 'hevc', 'av1', 'vp8', 'vp9', 'hevc_hdr', 'av1_hdr', 'hevc_10bit' or 'av1_10bit', got: '%s'\n", video_codec_to_use);
fprintf(stderr, "Error: -k should either be 'auto', 'h264', 'hevc', 'av1', 'vp8', 'vp9', 'hevc_hdr', 'av1_hdr', 'hevc_10bit' or 'av1_10bit', got: '%s'\n", video_codec_to_use);
usage();
}
@@ -3448,7 +3486,7 @@ int main(int argc, char **argv) {
} else if(strcmp(audio_codec_to_use, "flac") == 0) {
audio_codec = AudioCodec::FLAC;
} else {
fprintf(stderr, "Error: -ac should either be either 'aac', 'opus' or 'flac', got: '%s'\n", audio_codec_to_use);
fprintf(stderr, "Error: -ac should either be 'aac', 'opus' or 'flac', got: '%s'\n", audio_codec_to_use);
usage();
}
@@ -3547,7 +3585,7 @@ int main(int argc, char **argv) {
} else if(strcmp(pixfmt, "yuv444") == 0) {
pixel_format = PixelFormat::YUV444;
} else {
fprintf(stderr, "Error: -pixfmt should either be either 'yuv420', or 'yuv444', got: '%s'\n", pixfmt);
fprintf(stderr, "Error: -pixfmt should either be 'yuv420', or 'yuv444', got: '%s'\n", pixfmt);
usage();
}
@@ -3668,6 +3706,11 @@ int main(int argc, char **argv) {
_exit(1);
}
gsr_shader_enable_debug_output(gl_debug);
#ifndef NDEBUG
gsr_shader_enable_debug_output(true);
#endif
if(egl.gpu_info.is_steam_deck) {
fprintf(stderr, "gsr warning: steam deck has multiple driver issues. One of them has been reported here: https://github.com/ValveSoftware/SteamOS/issues/1609\n"
"If you have issues with GPU Screen Recorder on steam deck that you don't have on a desktop computer then report the issue to Valve and/or AMD.\n");
@@ -3719,7 +3762,7 @@ int main(int argc, char **argv) {
} else if(strcmp(framerate_mode_str, "content") == 0) {
framerate_mode = FramerateMode::CONTENT;
} else {
fprintf(stderr, "Error: -fm should either be either 'cfr', 'vfr' or 'content', got: '%s'\n", framerate_mode_str);
fprintf(stderr, "Error: -fm should either be 'cfr', 'vfr' or 'content', got: '%s'\n", framerate_mode_str);
usage();
}
@@ -3740,7 +3783,7 @@ int main(int argc, char **argv) {
} else if(strcmp(bitrate_mode_str, "cbr") == 0) {
bitrate_mode = BitrateMode::CBR;
} else if(strcmp(bitrate_mode_str, "auto") != 0) {
fprintf(stderr, "Error: -bm should either be either 'auto', 'qp', 'vbr' or 'cbr', got: '%s'\n", bitrate_mode_str);
fprintf(stderr, "Error: -bm should either be 'auto', 'qp', 'vbr' or 'cbr', got: '%s'\n", bitrate_mode_str);
usage();
}
@@ -3793,7 +3836,7 @@ int main(int argc, char **argv) {
} else if(strcmp(quality_str, "ultra") == 0) {
quality = VideoQuality::ULTRA;
} else {
fprintf(stderr, "Error: -q should either be either 'medium', 'high', 'very_high' or 'ultra', got: '%s'\n", quality_str);
fprintf(stderr, "Error: -q should either be 'medium', 'high', 'very_high' or 'ultra', got: '%s'\n", quality_str);
usage();
}
}
@@ -3808,7 +3851,21 @@ int main(int argc, char **argv) {
} else if(strcmp(color_range_str, "full") == 0) {
color_range = GSR_COLOR_RANGE_FULL;
} else {
fprintf(stderr, "Error: -cr should either be either 'limited' or 'full', got: '%s'\n", color_range_str);
fprintf(stderr, "Error: -cr should either be 'limited' or 'full', got: '%s'\n", color_range_str);
usage();
}
Tune tune = Tune::PERFORMANCE;
const char *tune_str = args["-tune"].value();
if(!tune_str)
tune_str = "performance";
if(strcmp(tune_str, "performance") == 0) {
tune = Tune::PERFORMANCE;
} else if(strcmp(tune_str, "quality") == 0) {
tune = Tune::QUALITY;
} else {
fprintf(stderr, "Error: -tune should either be 'performance' or 'quality', got: '%s'\n", tune_str);
usage();
}
@@ -3826,7 +3883,7 @@ int main(int argc, char **argv) {
}
if(output_resolution.x < 0 || output_resolution.y < 0) {
fprintf(stderr, "Error: invalud value for option -s '%s', expected width and height to be greater or equal to 0\n", output_resolution_str);
fprintf(stderr, "Error: invalid value for option -s '%s', expected width and height to be greater or equal to 0\n", output_resolution_str);
usage();
}
}
@@ -3846,7 +3903,7 @@ int main(int argc, char **argv) {
}
if(region_size.x < 0 || region_size.y < 0 || region_position.x < 0 || region_position.y < 0) {
fprintf(stderr, "Error: invalud value for option -region '%s', expected width, height, x and y to be greater or equal to 0\n", region_str);
fprintf(stderr, "Error: invalid value for option -region '%s', expected width, height, x and y to be greater or equal to 0\n", region_str);
usage();
}
} else {
@@ -3973,6 +4030,9 @@ int main(int argc, char **argv) {
if(replay_buffer_size_secs == -1)
video_stream = create_stream(av_format_context, video_codec_context);
if(tune == Tune::QUALITY)
video_codec_context->max_b_frames = 2;
AVFrame *video_frame = av_frame_alloc();
if(!video_frame) {
fprintf(stderr, "Error: Failed to allocate video frame\n");
@@ -4037,7 +4097,7 @@ int main(int argc, char **argv) {
if(use_software_video_encoder) {
open_video_software(video_codec_context, quality, pixel_format, hdr, color_depth, bitrate_mode);
} else {
open_video_hardware(video_codec_context, quality, very_old_gpu, egl.gpu_info.vendor, pixel_format, hdr, color_depth, bitrate_mode, video_codec, low_power);
open_video_hardware(video_codec_context, quality, very_old_gpu, egl.gpu_info.vendor, pixel_format, hdr, color_depth, bitrate_mode, video_codec, low_power, tune);
}
if(video_stream)
avcodec_parameters_from_context(video_stream->codecpar, video_codec_context);

View File

@@ -736,11 +736,13 @@ static void gsr_pipewire_video_update_cursor_texture(gsr_pipewire_video *self, g
if(!self->cursor.data)
return;
const float border_color[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
self->egl->glBindTexture(GL_TEXTURE_2D, texture_map.cursor_texture_id);
// TODO: glTextureSubImage2D if same size
self->egl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, self->cursor.width, self->cursor.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, self->cursor.data);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
self->egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
self->egl->glBindTexture(GL_TEXTURE_2D, 0);

View File

@@ -3,14 +3,16 @@
#include <stdio.h>
#include <assert.h>
static bool print_compile_errors = false;
static int min_int(int a, int b) {
return a < b ? a : b;
}
static unsigned int loader_shader(gsr_egl *egl, unsigned int type, const char *source) {
static unsigned int load_shader(gsr_egl *egl, unsigned int type, const char *source) {
unsigned int shader_id = egl->glCreateShader(type);
if(shader_id == 0) {
fprintf(stderr, "gsr error: loader_shader: failed to create shader, error: %d\n", egl->glGetError());
fprintf(stderr, "gsr error: load_shader: failed to create shader, error: %d\n", egl->glGetError());
return 0;
}
@@ -23,7 +25,7 @@ static unsigned int loader_shader(gsr_egl *egl, unsigned int type, const char *s
int info_length = 0;
egl->glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &info_length);
if(info_length > 1) {
if(info_length > 1 && print_compile_errors) {
char info_log[4096];
egl->glGetShaderInfoLog(shader_id, min_int(4096, info_length), NULL, info_log);
fprintf(stderr, "gsr error: loader shader: failed to compile shader, error:\n%s\nshader source:\n%s\n", info_log, source);
@@ -36,28 +38,36 @@ static unsigned int loader_shader(gsr_egl *egl, unsigned int type, const char *s
return shader_id;
}
static unsigned int load_program(gsr_egl *egl, const char *vertex_shader, const char *fragment_shader) {
static unsigned int load_program(gsr_egl *egl, const char *vertex_shader, const char *fragment_shader, const char *compute_shader) {
unsigned int vertex_shader_id = 0;
unsigned int fragment_shader_id = 0;
unsigned int compute_shader_id = 0;
unsigned int program_id = 0;
int linked = 0;
bool success = false;
if(vertex_shader) {
vertex_shader_id = loader_shader(egl, GL_VERTEX_SHADER, vertex_shader);
vertex_shader_id = load_shader(egl, GL_VERTEX_SHADER, vertex_shader);
if(vertex_shader_id == 0)
goto err;
goto done;
}
if(fragment_shader) {
fragment_shader_id = loader_shader(egl, GL_FRAGMENT_SHADER, fragment_shader);
fragment_shader_id = load_shader(egl, GL_FRAGMENT_SHADER, fragment_shader);
if(fragment_shader_id == 0)
goto err;
goto done;
}
if(compute_shader) {
compute_shader_id = load_shader(egl, GL_COMPUTE_SHADER, compute_shader);
if(compute_shader_id == 0)
goto done;
}
program_id = egl->glCreateProgram();
if(program_id == 0) {
fprintf(stderr, "gsr error: load_program: failed to create shader program, error: %d\n", egl->glGetError());
goto err;
goto done;
}
if(vertex_shader_id)
@@ -66,6 +76,9 @@ static unsigned int load_program(gsr_egl *egl, const char *vertex_shader, const
if(fragment_shader_id)
egl->glAttachShader(program_id, fragment_shader_id);
if(compute_shader_id)
egl->glAttachShader(program_id, compute_shader_id);
egl->glLinkProgram(program_id);
egl->glGetProgramiv(program_id, GL_LINK_STATUS, &linked);
@@ -79,37 +92,36 @@ static unsigned int load_program(gsr_egl *egl, const char *vertex_shader, const
fprintf(stderr, "gsr error: load program: linking shader program failed, error:\n%s\n", info_log);
}
goto err;
goto done;
}
success = true;
done:
if(!success) {
if(program_id)
egl->glDeleteProgram(program_id);
}
if(compute_shader_id)
egl->glDeleteShader(compute_shader_id);
if(fragment_shader_id)
egl->glDeleteShader(fragment_shader_id);
if(vertex_shader_id)
egl->glDeleteShader(vertex_shader_id);
return program_id;
err:
if(program_id)
egl->glDeleteProgram(program_id);
if(fragment_shader_id)
egl->glDeleteShader(fragment_shader_id);
if(vertex_shader_id)
egl->glDeleteShader(vertex_shader_id);
return 0;
}
int gsr_shader_init(gsr_shader *self, gsr_egl *egl, const char *vertex_shader, const char *fragment_shader) {
int gsr_shader_init(gsr_shader *self, gsr_egl *egl, const char *vertex_shader, const char *fragment_shader, const char *compute_shader) {
assert(egl);
self->egl = egl;
self->program_id = 0;
if(!vertex_shader && !fragment_shader) {
fprintf(stderr, "gsr error: gsr_shader_init: vertex shader and fragment shader can't be NULL at the same time\n");
if(!vertex_shader && !fragment_shader && !compute_shader) {
fprintf(stderr, "gsr error: gsr_shader_init: vertex, fragment shader and compute shaders can't be NULL at the same time\n");
return -1;
}
self->program_id = load_program(self->egl, vertex_shader, fragment_shader);
self->program_id = load_program(self->egl, vertex_shader, fragment_shader, compute_shader);
if(self->program_id == 0)
return -1;
@@ -141,3 +153,7 @@ void gsr_shader_use(gsr_shader *self) {
void gsr_shader_use_none(gsr_shader *self) {
self->egl->glUseProgram(0);
}
void gsr_shader_enable_debug_output(bool enable) {
print_compile_errors = enable;
}

View File

@@ -14,10 +14,8 @@
#include <xf86drmMode.h>
#include <xf86drm.h>
#include <libdrm/drm_fourcc.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xrandr.h>
#include <va/va_drmcommon.h>
#include <libavcodec/avcodec.h>
#include <libavutil/hwcontext_vaapi.h>
@@ -396,6 +394,8 @@ bool gl_get_gpu_info(gsr_egl *egl, gsr_gpu_info *info) {
info->vendor = GSR_GPU_VENDOR_INTEL;
else if(strstr((const char*)gl_vendor, "NVIDIA"))
info->vendor = GSR_GPU_VENDOR_NVIDIA;
else if(strstr((const char*)gl_vendor, "Broadcom"))
info->vendor = GSR_GPU_VENDOR_BROADCOM;
else {
fprintf(stderr, "gsr error: unknown gpu vendor: %s\n", gl_vendor);
supported = false;
@@ -427,14 +427,6 @@ bool gl_get_gpu_info(gsr_egl *egl, gsr_gpu_info *info) {
return supported;
}
bool version_greater_than(int major, int minor, int patch, int other_major, int other_minor, int other_patch) {
return (major > other_major) || (major == other_major && minor > other_minor) || (major == other_major && minor == other_minor && patch > other_patch);
}
bool gl_driver_version_greater_than(const gsr_gpu_info *gpu_info, int major, int minor, int patch) {
return version_greater_than(gpu_info->driver_major, gpu_info->driver_minor, gpu_info->driver_patch, major, minor, patch);
}
bool try_card_has_valid_plane(const char *card_path) {
drmVersion *ver = NULL;
drmModePlaneResPtr planes = NULL;
@@ -661,241 +653,6 @@ bool video_codec_context_is_vaapi(AVCodecContext *video_codec_context) {
return device_context->type == AV_HWDEVICE_TYPE_VAAPI;
}
static uint32_t drm_fourcc_to_va_fourcc(uint32_t drm_fourcc) {
switch(drm_fourcc) {
case DRM_FORMAT_XRGB8888: return VA_FOURCC_BGRX;
case DRM_FORMAT_XBGR8888: return VA_FOURCC_RGBX;
case DRM_FORMAT_RGBX8888: return VA_FOURCC_XBGR;
case DRM_FORMAT_BGRX8888: return VA_FOURCC_XRGB;
case DRM_FORMAT_ARGB8888: return VA_FOURCC_BGRA;
case DRM_FORMAT_ABGR8888: return VA_FOURCC_RGBA;
case DRM_FORMAT_RGBA8888: return VA_FOURCC_ABGR;
case DRM_FORMAT_BGRA8888: return VA_FOURCC_ARGB;
default: return drm_fourcc;
}
}
bool vaapi_copy_drm_planes_to_video_surface(AVCodecContext *video_codec_context, AVFrame *video_frame, vec2i source_pos, vec2i source_size, vec2i dest_pos, vec2i dest_size, uint32_t format, vec2i size, const int *fds, const uint32_t *offsets, const uint32_t *pitches, const uint64_t *modifiers, int num_planes) {
VAConfigID config_id = 0;
VAContextID context_id = 0;
VASurfaceID input_surface_id = 0;
VABufferID buffer_id = 0;
bool success = true;
VADisplay va_dpy = video_codec_context_get_vaapi_display(video_codec_context);
if(!va_dpy) {
success = false;
goto done;
}
VAStatus va_status = vaCreateConfig(va_dpy, VAProfileNone, VAEntrypointVideoProc, NULL, 0, &config_id);
if(va_status != VA_STATUS_SUCCESS) {
fprintf(stderr, "gsr error: vaapi_copy_drm_planes_to_video_surface: vaCreateConfig failed, error: %s\n", vaErrorStr(va_status));
success = false;
goto done;
}
VASurfaceID output_surface_id = (uintptr_t)video_frame->data[3];
va_status = vaCreateContext(va_dpy, config_id, size.x, size.y, VA_PROGRESSIVE, &output_surface_id, 1, &context_id);
if(va_status != VA_STATUS_SUCCESS) {
fprintf(stderr, "gsr error: vaapi_copy_drm_planes_to_video_surface: vaCreateContext failed, error: %s\n", vaErrorStr(va_status));
success = false;
goto done;
}
VADRMPRIMESurfaceDescriptor buf = {0};
buf.fourcc = drm_fourcc_to_va_fourcc(format);//VA_FOURCC_BGRX; // TODO: VA_FOURCC_BGRA, VA_FOURCC_X2R10G10B10
buf.width = size.x;
buf.height = size.y;
buf.num_objects = num_planes;
buf.num_layers = 1;
buf.layers[0].drm_format = format;
buf.layers[0].num_planes = buf.num_objects;
for(int i = 0; i < num_planes; ++i) {
buf.objects[i].fd = fds[i];
buf.objects[i].size = size.y * pitches[i]; // TODO:
buf.objects[i].drm_format_modifier = modifiers[i];
buf.layers[0].object_index[i] = i;
buf.layers[0].offset[i] = offsets[i];
buf.layers[0].pitch[i] = pitches[i];
}
VASurfaceAttrib attribs[2] = {0};
attribs[0].type = VASurfaceAttribMemoryType;
attribs[0].flags = VA_SURFACE_ATTRIB_SETTABLE;
attribs[0].value.type = VAGenericValueTypeInteger;
attribs[0].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2;
attribs[1].type = VASurfaceAttribExternalBufferDescriptor;
attribs[1].flags = VA_SURFACE_ATTRIB_SETTABLE;
attribs[1].value.type = VAGenericValueTypePointer;
attribs[1].value.value.p = &buf;
// TODO: RT_FORMAT with 10 bit/hdr, VA_RT_FORMAT_RGB32_10
// TODO: Max size same as source_size
va_status = vaCreateSurfaces(va_dpy, VA_RT_FORMAT_RGB32, size.x, size.y, &input_surface_id, 1, attribs, 2);
if(va_status != VA_STATUS_SUCCESS) {
fprintf(stderr, "gsr error: vaapi_copy_drm_planes_to_video_surface: vaCreateSurfaces failed, error: %s\n", vaErrorStr(va_status));
success = false;
goto done;
}
const VARectangle source_region = {
.x = source_pos.x,
.y = source_pos.y,
.width = source_size.x,
.height = source_size.y
};
const VARectangle output_region = {
.x = dest_pos.x,
.y = dest_pos.y,
.width = dest_size.x,
.height = dest_size.y
};
const bool scaled = dest_size.x != source_size.x || dest_size.y != source_size.y;
// Copying a surface to another surface will automatically perform the color conversion. Thanks vaapi!
VAProcPipelineParameterBuffer params = {0};
params.surface = input_surface_id;
params.surface_region = NULL;
params.surface_region = &source_region;
params.output_region = &output_region;
params.output_background_color = 0;
params.filter_flags = scaled ? (VA_FILTER_SCALING_HQ | VA_FILTER_INTERPOLATION_BILINEAR) : 0;
params.pipeline_flags = VA_PROC_PIPELINE_FAST;
params.input_color_properties.colour_primaries = 1;
params.input_color_properties.transfer_characteristics = 1;
params.input_color_properties.matrix_coefficients = 1;
params.surface_color_standard = VAProcColorStandardBT709; // TODO:
params.input_color_properties.color_range = video_frame->color_range == AVCOL_RANGE_JPEG ? VA_SOURCE_RANGE_FULL : VA_SOURCE_RANGE_REDUCED;
params.output_color_properties.colour_primaries = 1;
params.output_color_properties.transfer_characteristics = 1;
params.output_color_properties.matrix_coefficients = 1;
params.output_color_standard = VAProcColorStandardBT709; // TODO:
params.output_color_properties.color_range = video_frame->color_range == AVCOL_RANGE_JPEG ? VA_SOURCE_RANGE_FULL : VA_SOURCE_RANGE_REDUCED;
params.processing_mode = VAProcPerformanceMode;
// VAProcPipelineCaps pipeline_caps = {0};
// va_status = vaQueryVideoProcPipelineCaps(self->va_dpy,
// self->context_id,
// NULL, 0,
// &pipeline_caps);
// if(va_status == VA_STATUS_SUCCESS) {
// fprintf(stderr, "pipeline_caps: %u, %u\n", (unsigned int)pipeline_caps.rotation_flags, pipeline_caps.blend_flags);
// }
// TODO: params.output_hdr_metadata
// TODO:
// if (first surface to render)
// pipeline_param->output_background_color = 0xff000000; // black
va_status = vaCreateBuffer(va_dpy, context_id, VAProcPipelineParameterBufferType, sizeof(params), 1, &params, &buffer_id);
if(va_status != VA_STATUS_SUCCESS) {
fprintf(stderr, "gsr error: vaapi_copy_drm_planes_to_video_surface: vaCreateBuffer failed, error: %d\n", va_status);
success = false;
goto done;
}
va_status = vaBeginPicture(va_dpy, context_id, output_surface_id);
if(va_status != VA_STATUS_SUCCESS) {
fprintf(stderr, "gsr error: vaapi_copy_drm_planes_to_video_surface: vaBeginPicture failed, error: %d\n", va_status);
success = false;
goto done;
}
va_status = vaRenderPicture(va_dpy, context_id, &buffer_id, 1);
if(va_status != VA_STATUS_SUCCESS) {
vaEndPicture(va_dpy, context_id);
fprintf(stderr, "gsr error: vaapi_copy_drm_planes_to_video_surface: vaRenderPicture failed, error: %d\n", va_status);
success = false;
goto done;
}
va_status = vaEndPicture(va_dpy, context_id);
if(va_status != VA_STATUS_SUCCESS) {
fprintf(stderr, "gsr error: vaapi_copy_drm_planes_to_video_surface: vaEndPicture failed, error: %d\n", va_status);
success = false;
goto done;
}
// vaSyncBuffer(va_dpy, buffer_id, 1000 * 1000 * 1000);
// vaSyncSurface(va_dpy, input_surface_id);
// vaSyncSurface(va_dpy, output_surface_id);
done:
if(buffer_id)
vaDestroyBuffer(va_dpy, buffer_id);
if(input_surface_id)
vaDestroySurfaces(va_dpy, &input_surface_id, 1);
if(context_id)
vaDestroyContext(va_dpy, context_id);
if(config_id)
vaDestroyConfig(va_dpy, config_id);
return success;
}
bool vaapi_copy_egl_image_to_video_surface(gsr_egl *egl, EGLImage image, vec2i source_pos, vec2i source_size, vec2i dest_pos, vec2i dest_size, AVCodecContext *video_codec_context, AVFrame *video_frame) {
if(!image)
return false;
int texture_fourcc = 0;
int texture_num_planes = 0;
uint64_t texture_modifiers = 0;
if(!egl->eglExportDMABUFImageQueryMESA(egl->egl_display, image, &texture_fourcc, &texture_num_planes, &texture_modifiers)) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: eglExportDMABUFImageQueryMESA failed\n");
return false;
}
if(texture_num_planes <= 0 || texture_num_planes > 8) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: expected planes size to be 0<planes<=8 for drm buf, got %d planes\n", texture_num_planes);
return false;
}
int texture_fds[8];
int32_t texture_strides[8];
int32_t texture_offsets[8];
while(egl->eglGetError() != EGL_SUCCESS){}
if(!egl->eglExportDMABUFImageMESA(egl->egl_display, image, texture_fds, texture_strides, texture_offsets)) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: eglExportDMABUFImageMESA failed, error: %d\n", egl->eglGetError());
return false;
}
int fds[8];
uint32_t offsets[8];
uint32_t pitches[8];
uint64_t modifiers[8];
for(int i = 0; i < texture_num_planes; ++i) {
fds[i] = texture_fds[i];
offsets[i] = texture_offsets[i];
pitches[i] = texture_strides[i];
modifiers[i] = texture_modifiers;
if(fds[i] == -1)
texture_num_planes = i;
}
const bool success = texture_num_planes > 0 && vaapi_copy_drm_planes_to_video_surface(video_codec_context, video_frame, source_pos, source_size, dest_pos, dest_size, texture_fourcc, source_size, fds, offsets, pitches, modifiers, texture_num_planes);
for(int i = 0; i < texture_num_planes; ++i) {
if(texture_fds[i] > 0) {
close(texture_fds[i]);
texture_fds[i] = -1;
}
}
return success;
}
vec2i scale_keep_aspect_ratio(vec2i from, vec2i to) {
if(from.x == 0 || from.y == 0)
return (vec2i){0, 0};
@@ -914,13 +671,22 @@ 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) {
float border_color[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
if(format == GL_RG) { // UV
border_color[0] = 0.5f;
border_color[1] = 0.5f;
border_color[2] = 0.0f;
border_color[3] = 1.0f;
}
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_WRAP_S, GL_CLAMP_TO_BORDER);
egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);

View File

@@ -9,19 +9,23 @@
#include <stdint.h>
#include <wayland-client.h>
#include <wayland-egl.h>
#include "xdg-output-unstable-v1-client-protocol.h"
#define GSR_MAX_OUTPUTS 32
typedef struct gsr_window_wayland gsr_window_wayland;
typedef struct {
uint32_t wl_name;
void *output;
struct wl_output *output;
struct zxdg_output_v1 *xdg_output;
vec2i pos;
vec2i size;
int32_t transform;
char *name;
} gsr_wayland_output;
typedef struct {
struct gsr_window_wayland {
void *display;
void *window;
void *registry;
@@ -29,7 +33,8 @@ typedef struct {
void *compositor;
gsr_wayland_output outputs[GSR_MAX_OUTPUTS];
int num_outputs;
} gsr_window_wayland;
struct zxdg_output_manager_v1 *xdg_output_manager;
};
static void output_handle_geometry(void *data, struct wl_output *wl_output,
int32_t x, int32_t y, int32_t phys_width, int32_t phys_height,
@@ -95,7 +100,7 @@ static const struct wl_output_listener output_listener = {
static void registry_add_object(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {
(void)version;
gsr_window_wayland *window_wayland = data;
if (strcmp(interface, "wl_compositor") == 0) {
if(strcmp(interface, "wl_compositor") == 0) {
if(window_wayland->compositor) {
wl_compositor_destroy(window_wayland->compositor);
window_wayland->compositor = NULL;
@@ -103,7 +108,7 @@ static void registry_add_object(void *data, struct wl_registry *registry, uint32
window_wayland->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1);
} else if(strcmp(interface, wl_output_interface.name) == 0) {
if(version < 4) {
fprintf(stderr, "gsr warning: wl output interface version is < 4, expected >= 4 to capture a monitor. Using KMS capture instead\n");
fprintf(stderr, "gsr warning: wl output interface version is < 4, expected >= 4 to capture a monitor\n");
return;
}
@@ -123,6 +128,17 @@ static void registry_add_object(void *data, struct wl_registry *registry, uint32
.name = NULL,
};
wl_output_add_listener(gsr_output->output, &output_listener, gsr_output);
} else if(strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
if(version < 1) {
fprintf(stderr, "gsr warning: xdg output interface version is < 1, expected >= 1 to capture a monitor\n");
return;
}
if(window_wayland->xdg_output_manager) {
zxdg_output_manager_v1_destroy(window_wayland->xdg_output_manager);
window_wayland->xdg_output_manager = NULL;
}
window_wayland->xdg_output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, 1);
}
}
@@ -130,6 +146,7 @@ static void registry_remove_object(void *data, struct wl_registry *registry, uin
(void)data;
(void)registry;
(void)name;
// TODO: Remove output
}
static struct wl_registry_listener registry_listener = {
@@ -137,6 +154,60 @@ static struct wl_registry_listener registry_listener = {
.global_remove = registry_remove_object,
};
static void xdg_output_logical_position(void *data, struct zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y) {
(void)zxdg_output_v1;
gsr_wayland_output *gsr_xdg_output = data;
gsr_xdg_output->pos.x = x;
gsr_xdg_output->pos.y = y;
}
static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) {
(void)data;
(void)xdg_output;
(void)width;
(void)height;
}
static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) {
(void)data;
(void)xdg_output;
}
static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) {
(void)data;
(void)xdg_output;
(void)name;
}
static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) {
(void)data;
(void)xdg_output;
(void)description;
}
static const struct zxdg_output_v1_listener xdg_output_listener = {
.logical_position = xdg_output_logical_position,
.logical_size = xdg_output_handle_logical_size,
.done = xdg_output_handle_done,
.name = xdg_output_handle_name,
.description = xdg_output_handle_description,
};
static void gsr_window_wayland_set_monitor_outputs_from_xdg_output(gsr_window_wayland *self) {
if(!self->xdg_output_manager) {
fprintf(stderr, "gsr warning: zxdg_output_manager not found. registered monitor positions might be incorrect\n");
return;
}
for(int i = 0; i < self->num_outputs; ++i) {
self->outputs[i].xdg_output = zxdg_output_manager_v1_get_xdg_output(self->xdg_output_manager, self->outputs[i].output);
zxdg_output_v1_add_listener(self->outputs[i].xdg_output, &xdg_output_listener, &self->outputs[i]);
}
// Fetch xdg_output
wl_display_roundtrip(self->display);
}
static void gsr_window_wayland_deinit(gsr_window_wayland *self) {
if(self->window) {
wl_egl_window_destroy(self->window);
@@ -158,9 +229,19 @@ static void gsr_window_wayland_deinit(gsr_window_wayland *self) {
free(self->outputs[i].name);
self->outputs[i].name = NULL;
}
if(self->outputs[i].xdg_output) {
zxdg_output_v1_destroy(self->outputs[i].xdg_output);
self->outputs[i].output = NULL;
}
}
self->num_outputs = 0;
if(self->xdg_output_manager) {
zxdg_output_manager_v1_destroy(self->xdg_output_manager);
self->xdg_output_manager = NULL;
}
if(self->compositor) {
wl_compositor_destroy(self->compositor);
self->compositor = NULL;
@@ -193,6 +274,8 @@ static bool gsr_window_wayland_init(gsr_window_wayland *self) {
// Fetch wl_output
wl_display_roundtrip(self->display);
gsr_window_wayland_set_monitor_outputs_from_xdg_output(self);
if(!self->compositor) {
fprintf(stderr, "gsr error: gsr_window_wayland_init failed: failed to find compositor\n");
goto fail;

View File

@@ -85,8 +85,10 @@ int window_texture_on_resize(WindowTexture *self) {
texture_id = self->texture_id;
}
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
const float border_color[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
self->egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);