diff options
| -rw-r--r-- | README.md | 6 | ||||
| -rw-r--r-- | include/defs.h | 9 | ||||
| -rw-r--r-- | include/kde_night_light.h | 11 | ||||
| -rw-r--r-- | meson.build | 2 | ||||
| -rw-r--r-- | project.conf | 2 | ||||
| -rw-r--r-- | src/capture/kms.c | 152 | ||||
| -rw-r--r-- | src/kde_night_light.c | 182 | ||||
| -rw-r--r-- | src/main.cpp | 13 | ||||
| -rw-r--r-- | src/utils.c | 33 | ||||
| -rw-r--r-- | src/window/wayland.c | 31 |
10 files changed, 332 insertions, 109 deletions
@@ -14,7 +14,7 @@ This is a cli-only tool, if you want an UI for this check out [GPU Screen Record Supported video codecs: * H264 (default) * HEVC (Optionally with HDR) -* AV1 +* AV1 (Optionally with HDR) * VP8 * VP9 @@ -44,7 +44,7 @@ Here are some known unofficial packages: * Alpine Linux: [gpu-screen-recorder](https://pkgs.alpinelinux.org/package/edge/testing/x86/gpu-screen-recorder) * AppImage [AppImage GitHub releases](https://github.com/pkgforge-dev/gpu-screen-recorder-AppImage/releases) * CentOS: [Copr](https://copr.fedorainfracloud.org/coprs/brycensranch/gpu-screen-recorder-git/) -* Debian: [gpu-screen-recorder-cli](https://tracker.debian.org/pkg/gpu-screen-recorder) or [gpu-screen-recorder on github](https://github.com/Sarisan/gpu-screen-recorder) +* Debian: [gpu-screen-recorder-cli](https://tracker.debian.org/pkg/gpu-screen-recorder) or [gpu-screen-recorder alternative repository](https://sekoohaka.ddns.net/?p=about#debian-repository) * Fedora: [Copr](https://copr.fedorainfracloud.org/coprs/brycensranch/gpu-screen-recorder-git/) * Nix: [NixOS wiki](https://wiki.nixos.org/wiki/Gpu-screen-recorder) * Nobara: [Nobara wiki](https://wiki.nobaraproject.org/en/general-usage/additional-software/GPU-Screen-Recorder) @@ -221,7 +221,7 @@ See [https://git.dec05eba.com/?p=about](https://git.dec05eba.com/?p=about). Some linux distros (such as manjaro and fedora) disable hardware accelerated h264/hevc on AMD/Intel because of "patent license issues". If you are using an arch-based distro then you can install mesa-git instead of mesa and if you are using another distro then you may have to switch to a better distro. On fedora based distros you can follow this: [Hardware Accelerated Codec](https://rpmfusion.org/Howto/Multimedia).\ You can alternatively install the flatpak version of GPU Screen Recorder from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder) which doesn't have this issue on any distro. ## I have an old nvidia GPU that supports nvenc but I get a cuda error when trying to record -Newer ffmpeg versions don't support older nvidia cards. Try installing GPU Screen Recorder flatpak from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder) instead. It comes with an older ffmpeg version which might work for your GPU. +Newer ffmpeg versions don't support older nvidia cards. Try installing GPU Screen Recorder flatpak from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder) instead. It comes with a custom version of ffmpeg to support older nvidia GPUs. ## I get a black screen/glitches while live streaming It seems like ffmpeg earlier than version 6.1 has some type of bug. Install ffmpeg version 6.1 or later and then reinstall GPU Screen Recorder to fix this issue. The flatpak version of GPU Screen Recorder comes with a newer version of ffmpeg so no extra steps are needed. ## I can't play the video in my browser directly or in discord diff --git a/include/defs.h b/include/defs.h index b868148..4bb10d8 100644 --- a/include/defs.h +++ b/include/defs.h @@ -27,8 +27,17 @@ typedef enum { } gsr_monitor_rotation; typedef struct { + float red_x, red_y; + float green_x, green_y; + float blue_x, blue_y; + float white_x, white_y; +} gsr_color_chromaticities; + +typedef struct { float sdr_white_luminance; /* cd/m² (nits) */ float max_peak_luminance; /* cd/m² (nits) */ + gsr_color_chromaticities primaries; /* The color volume that the monitor image is encoded in, valid when |primaries_valid| is true */ + bool primaries_valid; } gsr_monitor_hdr_info; typedef enum { diff --git a/include/kde_night_light.h b/include/kde_night_light.h index ed12f1d..c9b65af 100644 --- a/include/kde_night_light.h +++ b/include/kde_night_light.h @@ -1,13 +1,20 @@ #ifndef GSR_KDE_NIGHT_LIGHT_H #define GSR_KDE_NIGHT_LIGHT_H +#include "defs.h" #include <stdbool.h> typedef struct gsr_kde_night_light gsr_kde_night_light; +typedef enum { + GSR_NIGHT_LIGHT_COMPENSATION_TINT, /* The captured image contains the night light tint, including its luminance dimming */ + GSR_NIGHT_LIGHT_COMPENSATION_TINT_KEEP_LUMINANCE /* The captured image contains the tint and the luminance dimming is normalized away by the compositors reference luminance */ +} gsr_night_light_compensation; + gsr_kde_night_light* gsr_kde_night_light_create(void); void gsr_kde_night_light_destroy(gsr_kde_night_light *self); -/* Returns true when night light is active. |inverse_matrix| is filled with the row major 3x3 matrix that removes the night light tint from linear rgb values */ -bool gsr_kde_night_light_get_inverse_matrix(gsr_kde_night_light *self, float inverse_matrix[9]); +/* Returns true when night light is active. |inverse_matrix| is filled with the row major 3x3 matrix that removes the night light color change from linear rgb values. + |container_primaries| is the color volume that the captured image is encoded in, or NULL for sRGB */ +bool gsr_kde_night_light_get_inverse_matrix(gsr_kde_night_light *self, gsr_night_light_compensation compensation, const gsr_color_chromaticities *container_primaries, float inverse_matrix[9]); #endif /* GSR_KDE_NIGHT_LIGHT_H */ diff --git a/meson.build b/meson.build index 4b24f75..3ec7e82 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('gpu-screen-recorder', ['c', 'cpp'], version : '5.15.1', default_options : ['warning_level=2', 'cpp_std=c++17']) +project('gpu-screen-recorder', ['c', 'cpp'], version : '5.15.2', default_options : ['warning_level=2', 'cpp_std=c++17']) add_project_arguments('-Wshadow', language : ['c', 'cpp']) if get_option('buildtype') == 'debug' diff --git a/project.conf b/project.conf index 26b995e..5ff0b53 100644 --- a/project.conf +++ b/project.conf @@ -1,7 +1,7 @@ [package] name = "gpu-screen-recorder" type = "executable" -version = "5.15.1" +version = "5.15.2" platforms = ["posix"] [lang.cpp] diff --git a/src/capture/kms.c b/src/capture/kms.c index 31d4b17..0f7c37c 100644 --- a/src/capture/kms.c +++ b/src/capture/kms.c @@ -53,10 +53,14 @@ typedef struct { bool tone_mapping_message_shown; int drm_card_fd; - uint32_t gamma_lut_connector_id; - uint32_t gamma_lut_crtc_id; + uint32_t crtc_connector_id; + uint32_t crtc_id; uint32_t gamma_lut_property_id; + uint32_t ctm_property_id; + uint32_t degamma_lut_property_id; uint64_t gamma_lut_blob_id; + uint64_t gamma_lut_uploaded_blob_id; + bool crtc_color_ops_active; bool night_light_message_shown; bool hdr_luminance_message_shown; @@ -292,10 +296,12 @@ static bool drm_plane_is_hdr(const gsr_kms_response_item *drm_fd) { return drm_fd->has_hdr_metadata && hdr_metadata_is_supported_format(&drm_fd->hdr_metadata); } -static void gsr_capture_kms_resolve_gamma_lut_property(gsr_capture_kms *self, uint32_t connector_id) { - self->gamma_lut_connector_id = connector_id; - self->gamma_lut_crtc_id = 0; +static void gsr_capture_kms_resolve_crtc_color_properties(gsr_capture_kms *self, uint32_t connector_id) { + self->crtc_connector_id = connector_id; + self->crtc_id = 0; self->gamma_lut_property_id = 0; + self->ctm_property_id = 0; + self->degamma_lut_property_id = 0; drmModeConnector *connector = drmModeGetConnectorCurrent(self->drm_card_fd, connector_id); if(!connector) @@ -304,16 +310,16 @@ static void gsr_capture_kms_resolve_gamma_lut_property(gsr_capture_kms *self, ui if(connector->encoder_id) { drmModeEncoder *encoder = drmModeGetEncoder(self->drm_card_fd, connector->encoder_id); if(encoder) { - self->gamma_lut_crtc_id = encoder->crtc_id; + self->crtc_id = encoder->crtc_id; drmModeFreeEncoder(encoder); } } drmModeFreeConnector(connector); - if(self->gamma_lut_crtc_id == 0) + if(self->crtc_id == 0) return; - drmModeObjectProperties *properties = drmModeObjectGetProperties(self->drm_card_fd, self->gamma_lut_crtc_id, DRM_MODE_OBJECT_CRTC); + drmModeObjectProperties *properties = drmModeObjectGetProperties(self->drm_card_fd, self->crtc_id, DRM_MODE_OBJECT_CRTC); if(!properties) return; @@ -324,53 +330,62 @@ static void gsr_capture_kms_resolve_gamma_lut_property(gsr_capture_kms *self, ui if(strcmp(property->name, "GAMMA_LUT") == 0) self->gamma_lut_property_id = property->prop_id; + else if(strcmp(property->name, "CTM") == 0) + self->ctm_property_id = property->prop_id; + else if(strcmp(property->name, "DEGAMMA_LUT") == 0) + self->degamma_lut_property_id = property->prop_id; drmModeFreeProperty(property); - if(self->gamma_lut_property_id) - break; } drmModeFreeObjectProperties(properties); } -static uint64_t gsr_capture_kms_get_gamma_lut_blob_id(gsr_capture_kms *self) { - if(self->gamma_lut_crtc_id == 0 || self->gamma_lut_property_id == 0) - return 0; +static void gsr_capture_kms_read_crtc_color_ops(gsr_capture_kms *self) { + self->gamma_lut_blob_id = 0; + self->crtc_color_ops_active = false; + + if(self->crtc_id == 0) + return; - uint64_t blob_id = 0; - drmModeObjectProperties *properties = drmModeObjectGetProperties(self->drm_card_fd, self->gamma_lut_crtc_id, DRM_MODE_OBJECT_CRTC); + drmModeObjectProperties *properties = drmModeObjectGetProperties(self->drm_card_fd, self->crtc_id, DRM_MODE_OBJECT_CRTC); if(!properties) - return 0; + return; for(uint32_t i = 0; i < properties->count_props; ++i) { + if(properties->prop_values[i] == 0) + continue; + if(properties->props[i] == self->gamma_lut_property_id) { - blob_id = properties->prop_values[i]; - break; + self->gamma_lut_blob_id = properties->prop_values[i]; + self->crtc_color_ops_active = true; + } else if(properties->props[i] == self->ctm_property_id || properties->props[i] == self->degamma_lut_property_id) { + self->crtc_color_ops_active = true; } } drmModeFreeObjectProperties(properties); - return blob_id; } -static void gsr_capture_kms_update_gamma_lut(gsr_capture_kms *self, gsr_color_conversion *color_conversion) { +static void gsr_capture_kms_update_crtc_color_ops(gsr_capture_kms *self, gsr_color_conversion *color_conversion) { if(self->drm_card_fd <= 0) return; - if(self->drm_fd->connector_id != self->gamma_lut_connector_id) - gsr_capture_kms_resolve_gamma_lut_property(self, self->drm_fd->connector_id); + if(self->drm_fd->connector_id != self->crtc_connector_id) + gsr_capture_kms_resolve_crtc_color_properties(self, self->drm_fd->connector_id); + + gsr_capture_kms_read_crtc_color_ops(self); - const uint64_t blob_id = gsr_capture_kms_get_gamma_lut_blob_id(self); - if(blob_id == self->gamma_lut_blob_id) + if(self->gamma_lut_blob_id == self->gamma_lut_uploaded_blob_id) return; - self->gamma_lut_blob_id = blob_id; - if(blob_id == 0) { + self->gamma_lut_uploaded_blob_id = self->gamma_lut_blob_id; + if(self->gamma_lut_uploaded_blob_id == 0) { gsr_color_conversion_set_gamma_lut(color_conversion, NULL, 0); return; } - drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(self->drm_card_fd, blob_id); + drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(self->drm_card_fd, self->gamma_lut_uploaded_blob_id); if(!blob) { - self->gamma_lut_blob_id = 0; + self->gamma_lut_uploaded_blob_id = 0; gsr_color_conversion_set_gamma_lut(color_conversion, NULL, 0); return; } @@ -386,7 +401,7 @@ static void gsr_capture_kms_update_gamma_lut(gsr_capture_kms *self, gsr_color_co } gsr_color_conversion_set_gamma_lut(color_conversion, rgb_values, num_entries); } else { - self->gamma_lut_blob_id = 0; + self->gamma_lut_uploaded_blob_id = 0; gsr_color_conversion_set_gamma_lut(color_conversion, NULL, 0); } @@ -394,42 +409,57 @@ static void gsr_capture_kms_update_gamma_lut(gsr_capture_kms *self, gsr_color_co drmModeFreePropertyBlob(blob); } -static void gsr_capture_kms_get_monitor_luminances(gsr_capture_kms *self, const gsr_kms_response_item *drm_fd, float *sdr_white_luminance, float *hdr_peak_luminance) { - *sdr_white_luminance = 0.0f; - *hdr_peak_luminance = hdr_metadata_get_max_luminance(&drm_fd->hdr_metadata); +static void gsr_capture_kms_get_monitor_hdr_info(gsr_capture_kms *self, const gsr_kms_response_item *drm_fd, gsr_monitor_hdr_info *monitor_hdr_info) { + if(!gsr_window_get_monitor_hdr_info(self->params.egl->window, self->params.display_to_capture, monitor_hdr_info)) + memset(monitor_hdr_info, 0, sizeof(*monitor_hdr_info)); - gsr_monitor_hdr_info monitor_hdr_info; - if(gsr_window_get_monitor_hdr_info(self->params.egl->window, self->params.display_to_capture, &monitor_hdr_info)) { - if(monitor_hdr_info.sdr_white_luminance > 0.0f) - *sdr_white_luminance = monitor_hdr_info.sdr_white_luminance; - if(monitor_hdr_info.max_peak_luminance > 0.0f) - *hdr_peak_luminance = monitor_hdr_info.max_peak_luminance; - } + if(monitor_hdr_info->max_peak_luminance <= 0.0f) + monitor_hdr_info->max_peak_luminance = hdr_metadata_get_max_luminance(&drm_fd->hdr_metadata); - if(*hdr_peak_luminance <= 0.0f) - *hdr_peak_luminance = HDR_PEAK_LUMINANCE_FALLBACK; + if(monitor_hdr_info->max_peak_luminance <= 0.0f) + monitor_hdr_info->max_peak_luminance = HDR_PEAK_LUMINANCE_FALLBACK; - if(*sdr_white_luminance <= 0.0f) - *sdr_white_luminance = SDR_WHITE_LUMINANCE_REFERENCE; + if(monitor_hdr_info->sdr_white_luminance <= 0.0f) + monitor_hdr_info->sdr_white_luminance = SDR_WHITE_LUMINANCE_REFERENCE; - if(*sdr_white_luminance > *hdr_peak_luminance) - *sdr_white_luminance = *hdr_peak_luminance; + if(monitor_hdr_info->sdr_white_luminance > monitor_hdr_info->max_peak_luminance) + monitor_hdr_info->sdr_white_luminance = monitor_hdr_info->max_peak_luminance; +} + +static void mat3_multiply(const float a[9], const float b[9], float result[9]) { + for(int row = 0; row < 3; ++row) { + for(int column = 0; column < 3; ++column) { + result[row*3 + column] = + a[row*3 + 0] * b[0*3 + column] + + a[row*3 + 1] * b[1*3 + column] + + a[row*3 + 2] * b[2*3 + column]; + } + } } static void gsr_capture_kms_update_hdr_color_transforms(gsr_capture_kms *self, gsr_color_conversion *color_conversion, const gsr_kms_response_item *drm_fd) { const bool plane_is_hdr = drm_plane_is_hdr(drm_fd); - gsr_color_conversion_enable_gamma_lut(color_conversion, plane_is_hdr && self->gamma_lut_blob_id != 0); + + gsr_monitor_hdr_info monitor_hdr_info; + gsr_capture_kms_get_monitor_hdr_info(self, drm_fd, &monitor_hdr_info); + const float sdr_white_luminance = monitor_hdr_info.sdr_white_luminance; + const float hdr_peak_luminance = monitor_hdr_info.max_peak_luminance; + + /* For hdr the night light luminance dimming is normalized away by the reference luminance from the compositor, which the compositor + dims by the same amount. The hdr image is encoded in the color volume of the monitor reported by the compositor rather than sRGB */ + const gsr_night_light_compensation night_light_compensation = plane_is_hdr ? GSR_NIGHT_LIGHT_COMPENSATION_TINT_KEEP_LUMINANCE : GSR_NIGHT_LIGHT_COMPENSATION_TINT; + const gsr_color_chromaticities *night_light_container_primaries = plane_is_hdr && monitor_hdr_info.primaries_valid ? &monitor_hdr_info.primaries : NULL; float night_light_matrix[9]; - const bool night_light = self->params.kde_night_light && gsr_kde_night_light_get_inverse_matrix(self->params.kde_night_light, night_light_matrix); + const bool night_light = self->params.kde_night_light && gsr_kde_night_light_get_inverse_matrix(self->params.kde_night_light, night_light_compensation, night_light_container_primaries, night_light_matrix); if(night_light && !self->night_light_message_shown) { self->night_light_message_shown = true; fprintf(stderr, "gsr info: gsr_capture_kms_update_hdr_color_transforms: night light is active, removing the night light tint from the capture\n"); } - float sdr_white_luminance = 0.0f; - float hdr_peak_luminance = 0.0f; - gsr_capture_kms_get_monitor_luminances(self, drm_fd, &sdr_white_luminance, &hdr_peak_luminance); + /* The compositor applies the night light tint at scanout with the crtc gamma lut when it offloads its color transforms to the crtc + ("prefer efficiency" color accuracy on kde plasma), so the gamma lut is applied to the captured image to get the same image that the monitor displays */ + gsr_color_conversion_enable_gamma_lut(color_conversion, (plane_is_hdr || night_light) && self->gamma_lut_uploaded_blob_id != 0); /* Hdr passthrough: the compositor composites sdr white at the monitors sdr white luminance while the video should have sdr white at the reference luminance (203 nits), so the luminance of the video is scaled to match that */ @@ -466,10 +496,13 @@ static void gsr_capture_kms_update_hdr_color_transforms(gsr_capture_kms *self, g 0.016391f, 0.088013f, 0.895595f }; float color_matrix[9]; + if(night_light) { + mat3_multiply(BT709_TO_BT2020, night_light_matrix, color_matrix); + } else { + memcpy(color_matrix, BT709_TO_BT2020, sizeof(color_matrix)); + } for(int i = 0; i < 9; ++i) { - color_matrix[i] = BT709_TO_BT2020[i] * (SDR_WHITE_LUMINANCE_REFERENCE / 10000.0f); - if(night_light) - color_matrix[i] *= night_light_matrix[(i % 3) * 4]; + color_matrix[i] *= SDR_WHITE_LUMINANCE_REFERENCE / 10000.0f; } gsr_color_conversion_set_color_matrix(color_conversion, color_matrix, GSR_COLOR_MATRIX_TRANSFER_GAMMA22_TO_PQ); } else if(night_light || luminance_scale != 1.0f) { @@ -519,10 +552,9 @@ static void gsr_kms_set_hdr_metadata(gsr_capture_kms *self, const gsr_kms_respon self->hdr_metadata_set = true; self->hdr_metadata = drm_fd->hdr_metadata; - float sdr_white_luminance = 0.0f; - float hdr_peak_luminance = 0.0f; - gsr_capture_kms_get_monitor_luminances(self, drm_fd, &sdr_white_luminance, &hdr_peak_luminance); - self->hdr_video_max_luminance = hdr_peak_luminance * (SDR_WHITE_LUMINANCE_REFERENCE / sdr_white_luminance); + gsr_monitor_hdr_info monitor_hdr_info; + gsr_capture_kms_get_monitor_hdr_info(self, drm_fd, &monitor_hdr_info); + self->hdr_video_max_luminance = monitor_hdr_info.max_peak_luminance * (SDR_WHITE_LUMINANCE_REFERENCE / monitor_hdr_info.sdr_white_luminance); } static vec2i swap_vec2i(vec2i value) { @@ -779,7 +811,7 @@ static void gsr_capture_kms_update_connector_ids(gsr_capture_kms *self) { if(self->is_x11) return; - self->gamma_lut_connector_id = 0; + self->crtc_connector_id = 0; self->monitor_id.num_connector_ids = 0; const gsr_connection_type connection_type = self->is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM; @@ -831,8 +863,8 @@ static void gsr_capture_kms_pre_capture(gsr_capture *cap, gsr_capture_metadata * if(!self->drm_fd) return; - if(drm_plane_is_hdr(self->drm_fd)) - gsr_capture_kms_update_gamma_lut(self, color_conversion); + if(self->params.kde_night_light || drm_plane_is_hdr(self->drm_fd)) + gsr_capture_kms_update_crtc_color_ops(self, color_conversion); if(self->params.hdr) { if(drm_plane_is_hdr(self->drm_fd)) diff --git a/src/kde_night_light.c b/src/kde_night_light.c index bed43af..d5c9578 100644 --- a/src/kde_night_light.c +++ b/src/kde_night_light.c @@ -19,13 +19,18 @@ typedef struct { double v[3]; } vec3d; +typedef struct { + double m[9]; +} mat3d; + struct gsr_kde_night_light { pthread_t thread; bool thread_created; atomic_bool stop; atomic_int temperature; - int cached_temperature; - float cached_inverse_matrix[9]; + int cached_temperature[2]; + gsr_color_chromaticities cached_container_primaries[2]; + float cached_inverse_matrix[2][9]; }; /* Blackbody whitepoint table for 1000K-6500K at 100K intervals, from https://github.com/jonls/redshift/blob/master/README-colorramp */ @@ -77,13 +82,152 @@ static vec3d night_light_channel_factors(int temperature) { return result; } -/* Kde plasma multiplies the composited image with the night light channel factors in linear space (verified against kde plasma 6.7, both in sdr and hdr mode) */ -static void compute_inverse_night_light_matrix(int temperature, float inverse_matrix[9]) { - const vec3d factors = night_light_channel_factors(temperature); +static const gsr_color_chromaticities SRGB_CHROMATICITIES = { + .red_x = 0.64f, .red_y = 0.33f, + .green_x = 0.30f, .green_y = 0.60f, + .blue_x = 0.15f, .blue_y = 0.06f, + .white_x = 0.3127f, .white_y = 0.3290f +}; + +/* Bradford cone response matrices, the same values that kde plasma uses (kwin colorspace.cpp, chromaticAdaptationMatrix) */ +static const mat3d BRADFORD = { .m = { + 0.8951, 0.2664, -0.1614, + -0.7502, 1.7135, 0.0367, + 0.0389, -0.0685, 1.0296 +}}; + +static const mat3d BRADFORD_INVERSE = { .m = { + 0.9869929, -0.1470543, 0.1599627, + 0.4323053, 0.5183603, 0.0492912, + -0.0085287, 0.0400428, 0.9684867 +}}; + +static vec3d chromaticity_to_xyz(double x, double y) { + return (vec3d){ .v = { x / y, 1.0, (1.0 - x - y) / y } }; +} + +static vec3d mat3d_mul_vec3d(const mat3d *matrix, vec3d vec) { + vec3d result; + for(int i = 0; i < 3; ++i) { + result.v[i] = matrix->m[i*3 + 0] * vec.v[0] + matrix->m[i*3 + 1] * vec.v[1] + matrix->m[i*3 + 2] * vec.v[2]; + } + return result; +} + +static mat3d mat3d_multiply(const mat3d *a, const mat3d *b) { + mat3d result; + for(int row = 0; row < 3; ++row) { + for(int column = 0; column < 3; ++column) { + result.m[row*3 + column] = + a->m[row*3 + 0] * b->m[0*3 + column] + + a->m[row*3 + 1] * b->m[1*3 + column] + + a->m[row*3 + 2] * b->m[2*3 + column]; + } + } + return result; +} + +static bool mat3d_invert(const mat3d *matrix, mat3d *result) { + const double *m = matrix->m; + const double c00 = m[4]*m[8] - m[5]*m[7]; + const double c01 = m[5]*m[6] - m[3]*m[8]; + const double c02 = m[3]*m[7] - m[4]*m[6]; + const double determinant = m[0]*c00 + m[1]*c01 + m[2]*c02; + if(fabs(determinant) < 1e-12) + return false; + + const double inverse_determinant = 1.0 / determinant; + result->m[0] = c00 * inverse_determinant; + result->m[1] = (m[2]*m[7] - m[1]*m[8]) * inverse_determinant; + result->m[2] = (m[1]*m[5] - m[2]*m[4]) * inverse_determinant; + result->m[3] = c01 * inverse_determinant; + result->m[4] = (m[0]*m[8] - m[2]*m[6]) * inverse_determinant; + result->m[5] = (m[2]*m[3] - m[0]*m[5]) * inverse_determinant; + result->m[6] = c02 * inverse_determinant; + result->m[7] = (m[1]*m[6] - m[0]*m[7]) * inverse_determinant; + result->m[8] = (m[0]*m[4] - m[1]*m[3]) * inverse_determinant; + return true; +} + +static bool rgb_to_xyz_matrix(const gsr_color_chromaticities *chromaticities, mat3d *result) { + const vec3d red = chromaticity_to_xyz(chromaticities->red_x, chromaticities->red_y); + const vec3d green = chromaticity_to_xyz(chromaticities->green_x, chromaticities->green_y); + const vec3d blue = chromaticity_to_xyz(chromaticities->blue_x, chromaticities->blue_y); + const vec3d white = chromaticity_to_xyz(chromaticities->white_x, chromaticities->white_y); + + mat3d primaries; + for(int i = 0; i < 3; ++i) { + primaries.m[i*3 + 0] = red.v[i]; + primaries.m[i*3 + 1] = green.v[i]; + primaries.m[i*3 + 2] = blue.v[i]; + } + + mat3d primaries_inverse; + if(!mat3d_invert(&primaries, &primaries_inverse)) + return false; + + const vec3d white_scale = mat3d_mul_vec3d(&primaries_inverse, white); + for(int i = 0; i < 3; ++i) { + result->m[i*3 + 0] = red.v[i] * white_scale.v[0]; + result->m[i*3 + 1] = green.v[i] * white_scale.v[1]; + result->m[i*3 + 2] = blue.v[i] * white_scale.v[2]; + } + return true; +} + +static mat3d bradford_chromatic_adaptation_matrix(vec3d source_whitepoint, vec3d destination_whitepoint) { + const vec3d source_response = mat3d_mul_vec3d(&BRADFORD, source_whitepoint); + const vec3d destination_response = mat3d_mul_vec3d(&BRADFORD, destination_whitepoint); + mat3d scaled_bradford = BRADFORD; + for(int i = 0; i < 3; ++i) { + const double response_scale = destination_response.v[i] / source_response.v[i]; + for(int j = 0; j < 3; ++j) { + scaled_bradford.m[i*3 + j] *= response_scale; + } + } + return mat3d_multiply(&BRADFORD_INVERSE, &scaled_bradford); +} + +/* Kde plasma applies night light by moving the whitepoint of the output to the night light whitepoint with bradford chromatic adaptation + and dimming the output by the luminance of that whitepoint (kwin drm_output.cpp, applyNightLight - verified against kde plasma 6.7). + The chromatic adaptation acts on the image in the color volume that the image is encoded in, so the inverse is conjugated by |container_primaries|, + which is assumed to have a D65 whitepoint before the night light whitepoint change */ +static void compute_inverse_night_light_matrix(int temperature, gsr_night_light_compensation compensation, const gsr_color_chromaticities *container_primaries, float inverse_matrix[9]) { memset(inverse_matrix, 0, sizeof(float) * 9); - inverse_matrix[0] = 1.0 / fmax(factors.v[0], 0.0001); - inverse_matrix[4] = 1.0 / fmax(factors.v[1], 0.0001); - inverse_matrix[8] = 1.0 / fmax(factors.v[2], 0.0001); + inverse_matrix[0] = 1.0f; + inverse_matrix[4] = 1.0f; + inverse_matrix[8] = 1.0f; + + const vec3d factors = night_light_channel_factors(temperature); + + gsr_color_chromaticities container_primaries_d65 = *container_primaries; + container_primaries_d65.white_x = SRGB_CHROMATICITIES.white_x; + container_primaries_d65.white_y = SRGB_CHROMATICITIES.white_y; + + mat3d srgb_to_xyz, container_to_xyz, xyz_to_container; + if(!rgb_to_xyz_matrix(&SRGB_CHROMATICITIES, &srgb_to_xyz)) + return; + if(!rgb_to_xyz_matrix(&container_primaries_d65, &container_to_xyz)) + return; + if(!mat3d_invert(&container_to_xyz, &xyz_to_container)) + return; + + vec3d night_light_whitepoint = mat3d_mul_vec3d(&srgb_to_xyz, factors); + const double luminance = fmax(night_light_whitepoint.v[1], 0.0001); + for(int i = 0; i < 3; ++i) { + night_light_whitepoint.v[i] /= luminance; + } + + const vec3d d65_whitepoint = chromaticity_to_xyz(SRGB_CHROMATICITIES.white_x, SRGB_CHROMATICITIES.white_y); + const mat3d inverse_adaptation = bradford_chromatic_adaptation_matrix(night_light_whitepoint, d65_whitepoint); + + const mat3d adaptation_in_container = mat3d_multiply(&xyz_to_container, &inverse_adaptation); + const mat3d inverse_tint = mat3d_multiply(&adaptation_in_container, &container_to_xyz); + + const double luminance_scale = compensation == GSR_NIGHT_LIGHT_COMPENSATION_TINT ? 1.0 / luminance : 1.0; + for(int i = 0; i < 9; ++i) { + inverse_matrix[i] = inverse_tint.m[i] * luminance_scale; + } } static bool dbus_get_night_light_property(DBusConnection *connection, const char *property_name, int expected_type, DBusBasicValue *value) { @@ -151,7 +295,8 @@ gsr_kde_night_light* gsr_kde_night_light_create(void) { if(!self) return NULL; - self->cached_temperature = -1; + self->cached_temperature[0] = -1; + self->cached_temperature[1] = -1; if(pthread_create(&self->thread, NULL, night_light_poll_thread, self) != 0) { free(self); @@ -173,7 +318,7 @@ void gsr_kde_night_light_destroy(gsr_kde_night_light *self) { free(self); } -bool gsr_kde_night_light_get_inverse_matrix(gsr_kde_night_light *self, float inverse_matrix[9]) { +bool gsr_kde_night_light_get_inverse_matrix(gsr_kde_night_light *self, gsr_night_light_compensation compensation, const gsr_color_chromaticities *container_primaries, float inverse_matrix[9]) { if(!self) return false; @@ -181,12 +326,17 @@ bool gsr_kde_night_light_get_inverse_matrix(gsr_kde_night_light *self, float inv if(temperature <= 0) return false; - if(self->cached_temperature != temperature) { - compute_inverse_night_light_matrix(temperature, self->cached_inverse_matrix); - self->cached_temperature = temperature; + if(!container_primaries) + container_primaries = &SRGB_CHROMATICITIES; + + const int cache_index = compensation; + if(self->cached_temperature[cache_index] != temperature || memcmp(&self->cached_container_primaries[cache_index], container_primaries, sizeof(*container_primaries)) != 0) { + compute_inverse_night_light_matrix(temperature, compensation, container_primaries, self->cached_inverse_matrix[cache_index]); + self->cached_temperature[cache_index] = temperature; + self->cached_container_primaries[cache_index] = *container_primaries; } - memcpy(inverse_matrix, self->cached_inverse_matrix, sizeof(self->cached_inverse_matrix)); + memcpy(inverse_matrix, self->cached_inverse_matrix[cache_index], sizeof(self->cached_inverse_matrix[cache_index])); return true; } @@ -204,8 +354,10 @@ void gsr_kde_night_light_destroy(gsr_kde_night_light *self) { (void)self; } -bool gsr_kde_night_light_get_inverse_matrix(gsr_kde_night_light *self, float inverse_matrix[9]) { +bool gsr_kde_night_light_get_inverse_matrix(gsr_kde_night_light *self, gsr_night_light_compensation compensation, const gsr_color_chromaticities *container_primaries, float inverse_matrix[9]) { (void)self; + (void)compensation; + (void)container_primaries; (void)inverse_matrix; return false; } diff --git a/src/main.cpp b/src/main.cpp index 852a3c4..ec1941a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1040,20 +1040,19 @@ static bool add_hdr_metadata_to_video_stream(gsr_capture *cap, AVStream *video_s static void set_format_context_options(AVFormatContext *av_format_context) { if(LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(62, 6, 101)) { - bool hybrid_fragmented_available = false; av_opt_set(av_format_context->priv_data, "use_editlist", "1", 0); const AVOption *opt = av_opt_find(av_format_context->priv_data, "movflags", NULL, 0, 0); if (opt && opt->unit) { const AVOption *flag = av_opt_find(av_format_context->priv_data, "hybrid_fragmented", opt->unit, 0, 0); - if (flag) { - hybrid_fragmented_available = true; + if (flag) av_opt_set(av_format_context->priv_data, "movflags", "+hybrid_fragmented", 0); - } } - - if(!hybrid_fragmented_available) - fprintf(stderr, "gsr warning: hybrid_fragmented movflags is not available in your FFmpeg version. If you experience stutter in the mp4 file then update your FFmpeg to at least version 8 or record to a mkv file\n"); } else { + const AVOutputFormat *output_format = av_format_context->oformat; + const char *file_extension = output_format->extensions ? output_format->extensions : ""; + if(strcmp(file_extension, "mp4") != 0 && strcmp(file_extension, "mov") != 0) + return; + fprintf(stderr, "gsr warning: your FFmpeg version is known to be buggy (it doesn't have working hybrid_fragmented movflags). If you experience stutter in the mp4 file then update your FFmpeg to at least version 8 or record to a mkv file\n"); } } diff --git a/src/utils.c b/src/utils.c index 9010064..31853e2 100644 --- a/src/utils.c +++ b/src/utils.c @@ -358,53 +358,48 @@ bool drm_monitor_get_display_server_data(const gsr_window *window, const gsr_mon bool gl_get_gpu_info(gsr_egl *egl, gsr_gpu_info *info) { const char *software_renderers[] = { "llvmpipe", "SWR", "softpipe", NULL }; - bool supported = true; - const unsigned char *gl_vendor = egl->glGetString(GL_VENDOR); - const unsigned char *gl_renderer = egl->glGetString(GL_RENDERER); + const char *gl_vendor = (const char*)egl->glGetString(GL_VENDOR); + const char *gl_renderer = (const char*)egl->glGetString(GL_RENDERER); info->gpu_version = 0; info->is_steam_deck = false; if(!gl_vendor) { fprintf(stderr, "gsr error: failed to get gpu vendor\n"); - supported = false; - goto end; + return false; } if(gl_renderer) { for(int i = 0; software_renderers[i]; ++i) { - if(strstr((const char*)gl_renderer, software_renderers[i])) { + if(strstr(gl_renderer, software_renderers[i])) { fprintf(stderr, "gsr error: your opengl environment is not properly setup. It's using %s (software rendering) for opengl instead of your graphics card. Please make sure your graphics driver is properly installed\n", software_renderers[i]); - supported = false; - goto end; + return false; } } } - if(strstr((const char*)gl_vendor, "AMD")) + if(strstr(gl_vendor, "AMD")) info->vendor = GSR_GPU_VENDOR_AMD; - else if(strstr((const char*)gl_vendor, "Mesa") && gl_renderer && strstr((const char*)gl_renderer, "AMD")) + else if(strstr(gl_vendor, "Mesa") && gl_renderer && strstr(gl_renderer, "AMD")) info->vendor = GSR_GPU_VENDOR_AMD; - else if(strstr((const char*)gl_vendor, "Intel")) + else if(strstr(gl_vendor, "Intel")) info->vendor = GSR_GPU_VENDOR_INTEL; - else if(strstr((const char*)gl_vendor, "NVIDIA")) + else if(strstr(gl_vendor, "NVIDIA")) info->vendor = GSR_GPU_VENDOR_NVIDIA; - else if(strstr((const char*)gl_vendor, "Broadcom")) + else if(strstr(gl_vendor, "Broadcom")) info->vendor = GSR_GPU_VENDOR_BROADCOM; else { fprintf(stderr, "gsr error: unknown gpu vendor: %s\n", gl_vendor); - supported = false; - goto end; + return false; } if(gl_renderer) { if(info->vendor == GSR_GPU_VENDOR_NVIDIA) - sscanf((const char*)gl_renderer, "%*s %*s %*s %d", &info->gpu_version); - info->is_steam_deck = strstr((const char*)gl_renderer, "vangogh") != NULL; + sscanf(gl_renderer, "%*s %*s %*s %d", &info->gpu_version); + info->is_steam_deck = strstr(gl_renderer, "vangogh") != NULL; } - end: - return supported; + return true; } bool try_card_has_valid_plane(const char *card_path) { diff --git a/src/window/wayland.c b/src/window/wayland.c index dc5dfc5..6c263c5 100644 --- a/src/window/wayland.c +++ b/src/window/wayland.c @@ -33,6 +33,8 @@ typedef struct { uint32_t reference_luminance; /* cd/m² (nits) */ uint32_t max_luminance; /* cd/m² (nits) */ bool luminance_valid; + int32_t primaries[8]; /* CIE 1931 xy chromaticity coordinates multiplied by 1 million, in the order red, green, blue, white */ + bool primaries_valid; } gsr_wayland_output; struct gsr_window_wayland { @@ -113,6 +115,7 @@ static void image_description_info_handle_done(void *data, struct wp_image_descr wp_image_description_info_v1_destroy(info); output->image_description_info = NULL; output->luminance_valid = output->reference_luminance > 0 || output->max_luminance > 0; + output->primaries_valid = output->primaries[1] > 0 && output->primaries[3] > 0 && output->primaries[5] > 0 && output->primaries[7] > 0; } static void image_description_info_handle_icc_file(void *data, struct wp_image_description_info_v1 *info, int32_t icc, uint32_t icc_size) { @@ -121,6 +124,19 @@ static void image_description_info_handle_icc_file(void *data, struct wp_image_d } static void image_description_info_handle_primaries(void *data, struct wp_image_description_info_v1 *info, int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) { + (void)info; + gsr_wayland_output *output = data; + output->primaries[0] = r_x; + output->primaries[1] = r_y; + output->primaries[2] = g_x; + output->primaries[3] = g_y; + output->primaries[4] = b_x; + output->primaries[5] = b_y; + output->primaries[6] = w_x; + output->primaries[7] = w_y; +} + +static void image_description_info_handle_target_primaries(void *data, struct wp_image_description_info_v1 *info, int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) { (void)data; (void)info; (void)r_x; (void)r_y; (void)g_x; (void)g_y; (void)b_x; (void)b_y; (void)w_x; (void)w_y; } @@ -147,7 +163,7 @@ static const struct wp_image_description_info_v1_listener image_description_info .tf_power = image_description_info_handle_uint_noop, .tf_named = image_description_info_handle_uint_noop, .luminances = image_description_info_handle_luminances, - .target_primaries = image_description_info_handle_primaries, + .target_primaries = image_description_info_handle_target_primaries, .target_luminance = image_description_info_handle_target_luminance, .target_max_cll = image_description_info_handle_uint_noop, .target_max_fall = image_description_info_handle_uint_noop, @@ -580,6 +596,19 @@ static bool gsr_window_wayland_get_monitor_hdr_info(const gsr_window *window, co hdr_info->sdr_white_luminance = output->reference_luminance; hdr_info->max_peak_luminance = output->max_luminance; + hdr_info->primaries_valid = output->primaries_valid; + if(output->primaries_valid) { + hdr_info->primaries.red_x = output->primaries[0] / 1000000.0f; + hdr_info->primaries.red_y = output->primaries[1] / 1000000.0f; + hdr_info->primaries.green_x = output->primaries[2] / 1000000.0f; + hdr_info->primaries.green_y = output->primaries[3] / 1000000.0f; + hdr_info->primaries.blue_x = output->primaries[4] / 1000000.0f; + hdr_info->primaries.blue_y = output->primaries[5] / 1000000.0f; + hdr_info->primaries.white_x = output->primaries[6] / 1000000.0f; + hdr_info->primaries.white_y = output->primaries[7] / 1000000.0f; + } else { + memset(&hdr_info->primaries, 0, sizeof(hdr_info->primaries)); + } return true; } return false; |
