Attempt to fix incorrect hdr colors on kde plasma 6.2

This commit is contained in:
dec05eba
2025-02-09 06:51:22 +01:00
parent 86df5a580e
commit a3b9b89a7f
10 changed files with 89 additions and 29 deletions

View File

@@ -180,6 +180,3 @@ You have to either record in hdr mode (-k `hevc_hdr` or -k `av1_hdr` option) to
You can record with desktop portal option (`-w portal`) instead which ignores night light, if you are ok with recording without HDR.
## Kdenlive says that the video is not usable for editing because it has variable frame rate
To fix this you can either record the video in .mkv format or constant frame rate (-fm cfr).
## Colors look incorrect when recording HDR (with hevc_hdr/av1_hdr) or using an ICC profile
The latest version of KDE Plasma breaks HDR and ICC profiles for screen recorders. Wayland in general doesn't properly support recording HDR yet. Use desktop portal option (`-w portal`) for now to turn HDR recording into SDR and to be able to record with correct colors when using an ICC profile.\
Note that this appears to only be an issue when trying to record SDR content. If you for example record a fullscreen HDR game then it appears to record correctly.

1
TODO
View File

@@ -194,7 +194,6 @@ Add option to record audio from the recorded window only.
Add option to automatically select best video codec available. Add -k best, -k best_10bit and -k best_hdr.
HDR is broken on kde plasma > 6.2 because of change to how HDR metadata works. See https://github.com/dec05eba/gpu-screen-recorder-issues/issues/60.
Use wayland color management protocol when it's available: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/14.
Use different exit codes for different errors. Use one for invalid -w option, another one for invalid -a option for audio devices, etc. This is to make UI error reporting better.

View File

@@ -40,6 +40,8 @@ typedef struct {
gsr_color_range color_range;
bool load_external_image_shader;
bool kde_gamma_correction;
} gsr_color_conversion_params;
typedef struct {

View File

@@ -50,6 +50,7 @@ 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);

View File

@@ -21,6 +21,7 @@ struct gsr_window {
void* (*get_display)(gsr_window *self);
void* (*get_window)(gsr_window *self);
void (*for_each_active_monitor_output_cached)(const gsr_window *self, active_monitor_callback callback, void *userdata);
bool (*is_compositor_kwin)(const gsr_window *self); /* can be NULL. Is currently only defined for Wayland */
void *priv;
};

View File

@@ -236,6 +236,11 @@ static int gsr_capture_kms_start(gsr_capture *cap, AVCodecContext *video_codec_c
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);
frame->width = video_codec_context->width;

View File

@@ -75,7 +75,7 @@ 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 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, bool kde_gamma_correction) {
const char *color_transform_matrix = color_format_range_get_transform_matrix(color_format, color_range);
char vertex_shader[2048];
@@ -93,6 +93,19 @@ static int load_shader_y(gsr_shader *shader, gsr_egl *egl, gsr_color_uniforms *u
" gl_Position = vec4(offset.x, offset.y, 0.0, 0.0) + vec4(pos.x, pos.y, 0.0, 1.0); \n"
"} \n");
const char *main_code = NULL;
if(kde_gamma_correction) {
main_code =
" vec4 pixel = texture(tex1, texcoords_out); \n"
" FragColor.x = pow((RGBtoYUV * vec4(pixel.rgb, 1.0)).x, 0.55)*0.8; \n"
" FragColor.w = pixel.a; \n";
} else {
main_code =
" vec4 pixel = texture(tex1, texcoords_out); \n"
" FragColor.x = (RGBtoYUV * vec4(pixel.rgb, 1.0)).x; \n"
" FragColor.w = pixel.a; \n";
}
char fragment_shader[2048];
if(external_texture) {
snprintf(fragment_shader, sizeof(fragment_shader),
@@ -106,10 +119,8 @@ static int load_shader_y(gsr_shader *shader, gsr_egl *egl, gsr_color_uniforms *u
"%s"
"void main() \n"
"{ \n"
" vec4 pixel = texture(tex1, texcoords_out); \n"
" FragColor.x = (RGBtoYUV * vec4(pixel.rgb, 1.0)).x; \n"
" FragColor.w = pixel.a; \n"
"} \n", color_transform_matrix);
"%s"
"} \n", color_transform_matrix, main_code);
} else {
snprintf(fragment_shader, sizeof(fragment_shader),
"#version 300 es \n"
@@ -120,10 +131,8 @@ static int load_shader_y(gsr_shader *shader, gsr_egl *egl, gsr_color_uniforms *u
"%s"
"void main() \n"
"{ \n"
" vec4 pixel = texture(tex1, texcoords_out); \n"
" FragColor.x = (RGBtoYUV * vec4(pixel.rgb, 1.0)).x; \n"
" FragColor.w = pixel.a; \n"
"} \n", color_transform_matrix);
"%s"
"} \n", color_transform_matrix, main_code);
}
if(gsr_shader_init(shader, egl, vertex_shader, fragment_shader) != 0)
@@ -136,7 +145,7 @@ static int load_shader_y(gsr_shader *shader, gsr_egl *egl, gsr_color_uniforms *u
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_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, bool kde_gamma_correction) {
const char *color_transform_matrix = color_format_range_get_transform_matrix(color_format, color_range);
char vertex_shader[2048];
@@ -154,6 +163,19 @@ static unsigned int load_shader_uv(gsr_shader *shader, gsr_egl *egl, gsr_color_u
" 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");
const char *main_code = NULL;
if(kde_gamma_correction) {
main_code =
" vec4 pixel = texture(tex1, texcoords_out); \n"
" FragColor.xy = (RGBtoYUV * vec4(pow(pixel.rgb, vec3(0.3)), 1.0)).yz; \n"
" FragColor.w = pixel.a; \n";
} else {
main_code =
" vec4 pixel = texture(tex1, texcoords_out); \n"
" FragColor.xy = (RGBtoYUV * vec4(pixel.rgb, 1.0)).yz; \n"
" FragColor.w = pixel.a; \n";
}
char fragment_shader[2048];
if(external_texture) {
snprintf(fragment_shader, sizeof(fragment_shader),
@@ -167,10 +189,8 @@ static unsigned int load_shader_uv(gsr_shader *shader, gsr_egl *egl, gsr_color_u
"%s"
"void main() \n"
"{ \n"
" vec4 pixel = texture(tex1, texcoords_out); \n"
" FragColor.xy = (RGBtoYUV * vec4(pixel.rgb, 1.0)).yz; \n"
" FragColor.w = pixel.a; \n"
"} \n", color_transform_matrix);
"%s"
"} \n", color_transform_matrix, main_code);
} else {
snprintf(fragment_shader, sizeof(fragment_shader),
"#version 300 es \n"
@@ -181,10 +201,8 @@ static unsigned int load_shader_uv(gsr_shader *shader, gsr_egl *egl, gsr_color_u
"%s"
"void main() \n"
"{ \n"
" vec4 pixel = texture(tex1, texcoords_out); \n"
" FragColor.xy = (RGBtoYUV * vec4(pixel.rgb, 1.0)).yz; \n"
" FragColor.w = pixel.a; \n"
"} \n", color_transform_matrix);
"%s"
"} \n", color_transform_matrix, main_code);
}
if(gsr_shader_init(shader, egl, vertex_shader, fragment_shader) != 0)
@@ -261,23 +279,23 @@ int gsr_color_conversion_init(gsr_color_conversion *self, const gsr_color_conver
return -1;
}
if(load_shader_y(&self->shaders[0], self->params.egl, &self->uniforms[0], params->destination_color, params->color_range, false) != 0) {
if(load_shader_y(&self->shaders[0], self->params.egl, &self->uniforms[0], params->destination_color, params->color_range, false, params->kde_gamma_correction) != 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) {
if(load_shader_uv(&self->shaders[1], self->params.egl, &self->uniforms[1], params->destination_color, params->color_range, false, params->kde_gamma_correction) != 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[2], self->params.egl, &self->uniforms[2], params->destination_color, params->color_range, true) != 0) {
if(load_shader_y(&self->shaders[2], self->params.egl, &self->uniforms[2], params->destination_color, params->color_range, true, params->kde_gamma_correction) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y shader\n");
goto err;
}
if(load_shader_uv(&self->shaders[3], self->params.egl, &self->uniforms[3], params->destination_color, params->color_range, true) != 0) {
if(load_shader_uv(&self->shaders[3], self->params.egl, &self->uniforms[3], params->destination_color, params->color_range, true, params->kde_gamma_correction) != 0) {
fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load UV shader\n");
goto err;
}

View File

@@ -3006,6 +3006,33 @@ static AudioDeviceData create_application_audio_audio_input(const MergedAudioInp
}
#endif
static bool is_kde_plasma_version_greater_than_6_1_90() {
FILE *f = popen("plasmashell -v 2> /dev/null", "r");
if(!f)
return false;
char output[512];
const ssize_t bytes_read = fread(output, 1, sizeof(output) - 1, f);
if(bytes_read < 0 || ferror(f)) {
pclose(f);
return false;
}
output[bytes_read] = '\0';
bool is_above = false;
const char *version_start = strstr(output, "plasmashell ");
if(version_start) {
version_start += 12;
int major = 0;
int minor = 0;
int patch = 0;
is_above = sscanf(version_start, "%d.%d.%d", &major, &minor, &patch) == 3 && version_greater_than(major, minor, patch, 6, 1, 90);
}
pclose(f);
return is_above;
}
static bool arg_get_boolean_value(std::map<std::string, Arg> &args, const char *arg_name, bool default_value) {
auto it = args.find(arg_name);
if(it == args.end() || !it->second.value()) {
@@ -3734,6 +3761,7 @@ int main(int argc, char **argv) {
color_conversion_params.color_range = color_range;
color_conversion_params.egl = &egl;
color_conversion_params.load_external_image_shader = gsr_capture_uses_external_image(capture);
color_conversion_params.kde_gamma_correction = hdr && is_monitor_capture && window->is_compositor_kwin && window->is_compositor_kwin(window) && is_kde_plasma_version_greater_than_6_1_90();
gsr_video_encoder_get_textures(video_encoder, color_conversion_params.destination_textures, &color_conversion_params.num_destination_textures, &color_conversion_params.destination_color);
gsr_color_conversion color_conversion;

View File

@@ -413,7 +413,7 @@ bool gl_get_gpu_info(gsr_egl *egl, gsr_gpu_info *info) {
return supported;
}
static bool version_greater_than(int major, int minor, int patch, int other_major, int other_minor, int other_patch) {
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);
}

View File

@@ -29,6 +29,7 @@ typedef struct {
void *compositor;
gsr_wayland_output outputs[GSR_MAX_OUTPUTS];
int num_outputs;
bool is_compositor_kwin;
} gsr_window_wayland;
static void output_handle_geometry(void *data, struct wl_output *wl_output,
@@ -123,6 +124,8 @@ 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, "org_kde_plasma_shell") == 0) {
window_wayland->is_compositor_kwin = true;
}
}
@@ -289,6 +292,11 @@ static void gsr_window_wayland_for_each_active_monitor_output_cached(const gsr_w
}
}
static bool gsr_window_wayland_is_compositor_kwin(const gsr_window *window) {
const gsr_window_wayland *self = window->priv;
return self->is_compositor_kwin;
}
gsr_window* gsr_window_wayland_create(void) {
gsr_window *window = calloc(1, sizeof(gsr_window));
if(!window)
@@ -314,6 +322,7 @@ gsr_window* gsr_window_wayland_create(void) {
.get_display = gsr_window_wayland_get_display,
.get_window = gsr_window_wayland_get_window,
.for_each_active_monitor_output_cached = gsr_window_wayland_for_each_active_monitor_output_cached,
.is_compositor_kwin = gsr_window_wayland_is_compositor_kwin,
.priv = window_wayland
};