diff --git a/meta/hl.meta.lua b/meta/hl.meta.lua index 6ea875aee..d615c330c 100644 --- a/meta/hl.meta.lua +++ b/meta/hl.meta.lua @@ -349,6 +349,7 @@ ---| "render.ctm_animation" ---| "render.direct_scanout" ---| "render.expand_undersized_textures" +---| "render.fp16_sdr_tf" ---| "render.icc_vcgt_enabled" ---| "render.keep_unmodified_copy" ---| "render.new_render_scheduling" @@ -1185,6 +1186,7 @@ hl = {} ---@field ['render.ctm_animation'] integer|boolean ---@field ['render.direct_scanout'] integer|boolean ---@field ['render.expand_undersized_textures'] boolean +---@field ['render.fp16_sdr_tf'] integer|boolean ---@field ['render.icc_vcgt_enabled'] boolean ---@field ['render.keep_unmodified_copy'] integer|boolean ---@field ['render.new_render_scheduling'] boolean diff --git a/src/config/values/ConfigValues.cpp b/src/config/values/ConfigValues.cpp index ff23ab5d3..11f6e574f 100644 --- a/src/config/values/ConfigValues.cpp +++ b/src/config/values/ConfigValues.cpp @@ -540,6 +540,8 @@ std::vector> Values::getConfigValues() { {.min = 0, .max = 2, .map = OptionMap{{"disable", 0}, {"enable", 1}, {"auto", 2}}}), MS("render:non_shader_cm_interop", "non_shader_cm interaction with ctm proto (hyprsunset and similar).", 2, {.min = 0, .max = 2, .map = OptionMap{{"disable", 0}, {"enable", 1}, {"auto", 2}}}), + MS("render:fp16_sdr_tf", "Internal workbuffer transfer function for fp16 in SDR mode", 0, + {.min = 0, .max = 2, .map = OptionMap{{"default", 0}, {"linear", 1}, {"monitor", 2}}}), /* * cursor: @@ -604,7 +606,7 @@ std::vector> Values::getConfigValues() { MS("debug:fifo_pending_workaround", "Fifo workaround for empty pending list", false), MS("debug:render_solitary_wo_damage", "Render solitary window with empty damage", false), MS("debug:vfr", "controls the VFR status of Hyprland. Do not turn off unless debugging", true), - MS("debug:invalidate_fp16", "allow fp16 buffer invalidation.", 2, {.min = 0, .max = 2, .map = OptionMap{{"disable", 0}, {"enable", 1}, {"auto", 2}}}), + MS("debug:invalidate_fp16", "allow fp16 buffer invalidation.", 1, {.min = 0, .max = 2, .map = OptionMap{{"disable", 0}, {"enable", 1}, {"auto", 2}}}), /* * layout: diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index c7d4168ae..42a7cdb68 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -617,6 +617,7 @@ void CMonitor::applyCMType(NCMType::eCMType cmType, NTransferFunction::eTF cmSdr if (oldImageDescription != m_imageDescription) { if (PROTO::colorManagement) PROTO::colorManagement->onMonitorImageDescriptionChanged(m_self); + m_blurFBDirty = true; } } @@ -2063,6 +2064,20 @@ bool CMonitor::attemptDirectScanout() { return true; } +void CMonitor::handleDSleave() { + Log::logger->log(Log::DEBUG, "Left a direct scanout."); + m_lastScanout.reset(); + m_previousFSWindow.reset(); // recalc fs settings + m_directScanoutIsActive = false; + + // reset DRM format, but only if needed since it might modeset + if (m_output->state->state().drmFormat != m_prevDrmFormat) + m_output->state->setFormat(m_prevDrmFormat); + + m_drmFormat = m_prevDrmFormat; + m_blurFBDirty = true; +} + bool CMonitor::canAttemptDirectScanoutFast() const { return !m_solitaryClient.expired() || !m_lastScanout.expired() || m_directScanoutIsActive; } @@ -2549,9 +2564,42 @@ bool CMonitor::useFP16() { return shouldUse; } +PImageDescription CMonitor::workBufferImageDescription() { + static const auto PFP16TF = CConfigValue("render:fp16_sdr_tf"); + + if (!useFP16() && !m_imageDescription->value().icc.present) + return m_imageDescription; + + const auto& value = m_imageDescription->value(); + + const bool isHDRLikeTF = + value.transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ || value.transferFunction == CM_TRANSFER_FUNCTION_HLG || value.transferFunction == CM_TRANSFER_FUNCTION_EXT_LINEAR; + + const auto& cached = m_cachedInternalDescription->value(); + + // HDR + if (isHDRLikeTF || value.windowsScRGB || *PFP16TF != 2) { + if (cached.transferFunction != LINEAR_IMAGE_DESCRIPTION->value().transferFunction || cached.luminances != value.luminances) + m_cachedInternalDescription = LINEAR_IMAGE_DESCRIPTION->with(value.luminances); + return m_cachedInternalDescription; + } + + // SDR + if (cached.transferFunction != chooseTF(m_sdrEotf)) + m_cachedInternalDescription = CImageDescription::from(SImageDescription{ + .transferFunction = chooseTF(m_sdrEotf), + .primariesNameSet = true, + // render:keep_unmodified_copy and other conditions that trigger MRT for screen sharing expect a work buffer with sRGB primaries + .primariesNamed = NColorManagement::CM_PRIMARIES_SRGB, + .primaries = NColorPrimaries::BT709, + }); + + return m_cachedInternalDescription; +} + WP CMonitor::resources() { const auto DRM_FORMAT = useFP16() ? DRM_FORMAT_ABGR16161616F : m_output->state->state().drmFormat; - const auto DESC = useFP16() ? LINEAR_IMAGE_DESCRIPTION : m_imageDescription; + const auto DESC = workBufferImageDescription(); if (!m_resources || m_resources->m_drmFormat != DRM_FORMAT || m_resources->m_size != m_pixelSize) m_resources = makeUnique(m_self, DRM_FORMAT, m_pixelSize, DESC); diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 59a64237e..4758481f4 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -326,6 +326,7 @@ class CMonitor { bool updateTearing(); uint16_t isDSBlocked(bool full = false); bool attemptDirectScanout(); + void handleDSleave(); bool canAttemptDirectScanoutFast() const; bool isMultiGPU(); void setCTM(const Mat3x3& ctm); @@ -393,10 +394,11 @@ class CMonitor { return m_position == rhs.m_position && m_size == rhs.m_size && m_name == rhs.m_name; } - bool needsACopyFB(); - bool needsUnmodifiedCopy(); - bool useFP16(); - WP resources(); + bool needsACopyFB(); + bool needsUnmodifiedCopy(); + bool useFP16(); + NColorManagement::PImageDescription workBufferImageDescription(); + WP resources(); private: void updateMatrix(); @@ -414,6 +416,9 @@ class CMonitor { // Resources UP m_resources; + // cached should contain one of predefined descriptions for FP16: sRGB primaries with either linear TF by default and in HDR mode or monitor's TF in SDR with render:fp16_sdr_tf = 2 + // avoids lookup for an id when ::from is used + NColorManagement::PImageDescription m_cachedInternalDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{}); struct { CHyprSignalListener frame; diff --git a/src/helpers/MonitorResources.cpp b/src/helpers/MonitorResources.cpp index 8e7723c23..f54aa9dc4 100644 --- a/src/helpers/MonitorResources.cpp +++ b/src/helpers/MonitorResources.cpp @@ -32,7 +32,7 @@ void CMonitorResources::setImageDescription(NColorManagement::PImageDescription for (const auto& res : m_workBuffers) res.buffer->setImageDescription(imageDescription); if (m_monitorMirrorFB) - m_monitorMirrorFB->setImageDescription(NColorManagement::getDefaultImageDescription()); + m_monitorMirrorFB->setImageDescription(getMirrorTexImageDescription()); if (m_mirrorTex) m_mirrorTex->m_imageDescription = getMirrorTexImageDescription(); } @@ -78,8 +78,8 @@ SP CMonitorResources::mirrorFB() { m_monitorMirrorFB = g_pHyprRenderer->createFB(std::format("Monitor {} mirror FB", m_monitor->m_name)); if (!m_monitorMirrorFB->isAllocated()) { - m_monitorMirrorFB->alloc(m_size.x, m_size.y, DRM_FORMAT_XRGB8888); - m_monitorMirrorFB->setImageDescription(NColorManagement::getDefaultImageDescription()); + m_monitorMirrorFB->alloc(m_size.x, m_size.y, m_monitor->m_activeMonitorRule.m_enable10bit ? DRM_FORMAT_XRGB2101010 : DRM_FORMAT_XRGB8888); + m_monitorMirrorFB->setImageDescription(getMirrorTexImageDescription()); } return m_monitorMirrorFB; @@ -90,20 +90,16 @@ SP CMonitorResources::getMirrorTexture() { } NColorManagement::PImageDescription CMonitorResources::getMirrorTexImageDescription() { - return CImageDescription::from(SImageDescription{ - .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_SRGB, - .primariesNameSet = m_imageDescription->value().primariesNameSet, - .primariesNamed = m_imageDescription->value().primariesNamed, - .primaries = m_imageDescription->value().primaries, - .luminances = {.min = SDR_MIN_LUMINANCE, .max = 80, .reference = 80}, - }); + if (!m_monitor) + return DEFAULT_SRGB_IMAGE_DESCRIPTION; + return m_monitor->workBufferImageDescription(); } void CMonitorResources::enableMirror() { if (m_mirrorTex) return; m_mirrorTex = g_pHyprRenderer->createTexture(); - m_mirrorTex->allocate({m_size.x, m_size.y}, DRM_FORMAT_XRGB8888); + m_mirrorTex->allocate({m_size.x, m_size.y}, m_monitor->m_activeMonitorRule.m_enable10bit ? DRM_FORMAT_XRGB2101010 : DRM_FORMAT_XRGB8888); m_mirrorTex->m_imageDescription = getMirrorTexImageDescription(); m_monitor->m_blurFBDirty = true; } diff --git a/src/helpers/cm/ColorManagement.cpp b/src/helpers/cm/ColorManagement.cpp index 5e4725d40..8428f29ef 100644 --- a/src/helpers/cm/ColorManagement.cpp +++ b/src/helpers/cm/ColorManagement.cpp @@ -1,9 +1,13 @@ #include "ColorManagement.hpp" #include "../../macros.hpp" -#include "helpers/TransferFunction.hpp" +#include "../Color.hpp" +#include "../TransferFunction.hpp" +#include "../../render/Renderer.hpp" +#include "render/types.hpp" #include #include #include +#include using namespace NColorManagement; using namespace NTransferFunction; @@ -208,6 +212,318 @@ Mat3x3 NColorManagement::adaptBradford(Hyprgraphics::CColor::xy srcW, Hyprgraphi return result; } +// sRGB constants +#define SRGB_POW 2.4 +#define SRGB_CUT 0.0031308 +#define SRGB_SCALE 12.92 +#define SRGB_ALPHA 1.055 + +#define BT1886_POW (1.0 / 0.45) +#define BT1886_CUT 0.018053968510807 +#define BT1886_SCALE 4.5 +#define BT1886_ALPHA (1.0 + 5.5 * BT1886_CUT) + +// See http://car.france3.mars.free.fr/HD/INA-%2026%20jan%2006/SMPTE%20normes%20et%20confs/s240m.pdf +#define ST240_POW (1.0 / 0.45) +#define ST240_CUT 0.0228 +#define ST240_SCALE 4.0 +#define ST240_ALPHA 1.1115 + +#define ST428_POW 2.6 +#define ST428_SCALE (52.37 / 48.0) + +// PQ constants +#define PQ_M1 0.1593017578125 +#define PQ_M2 78.84375 +#define PQ_INV_M1 (1.0 / PQ_M1) +#define PQ_INV_M2 (1.0 / PQ_M2) +#define PQ_C1 0.8359375 +#define PQ_C2 18.8515625 +#define PQ_C3 18.6875 + +// HLG constants +#define HLG_D_CUT (1.0 / 12.0) +#define HLG_E_CUT 0.5 +#define HLG_A 0.17883277 +#define HLG_B 0.28466892 +#define HLG_C 0.55991073 + +static inline int sign(double value) { + return value < 0 ? -1 : 1; +} + +// The primary source for these transfer functions is https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.1361-0-199802-W!!PDF-E.pdf +static RGBAColor tfInvPQ(RGBAColor color) { + for (uint i = 0; i <= 2; i++) { + const double E = pow(std::clamp(color.v[i], 0.0, 1.0), PQ_INV_M2); + color.v[i] = pow((std::max(E - PQ_C1, 0.0)) / (PQ_C2 - PQ_C3 * E), PQ_INV_M1); + } + return color; +} + +static RGBAColor tfInvHLG(RGBAColor color) { + for (uint i = 0; i <= 2; i++) { + const bool isLow = color.v[i] <= HLG_E_CUT; + color.v[i] = isLow ? color.v[i] * color.v[i] / 3.0 : (exp((color.v[i] - HLG_C) / HLG_A) + HLG_B) / 12.0; + } + return color; +} + +// Many transfer functions (including sRGB) follow the same pattern: a linear +// segment for small values and a power function for larger values. The +// following function implements this pattern from which sRGB, BT.1886, and +// others can be derived by plugging in the right constants. +static RGBAColor tfInvLinPow(RGBAColor color, float gamma, float thres, float scale, float alpha) { + for (uint i = 0; i <= 2; i++) { + const bool isLow = color.v[i] <= thres * scale; + color.v[i] = isLow ? color.v[i] / scale : pow((color.v[i] + alpha - 1.0) / alpha, gamma); + } + return color; +} + +static RGBAColor tfInvSRGB(RGBAColor color) { + return tfInvLinPow(color, SRGB_POW, SRGB_CUT, SRGB_SCALE, SRGB_ALPHA); +} + +static RGBAColor tfInvExtSRGB(RGBAColor color) { + // EXT sRGB is the sRGB transfer function mirrored around 0. + const auto absColor = tfInvSRGB({{.r = abs(color.c.r), .g = abs(color.c.g), .b = abs(color.c.b), .a = color.c.a}}); + return {{ + .r = absColor.c.r * sign(color.c.r), + .g = absColor.c.g * sign(color.c.g), + .b = absColor.c.b * sign(color.c.b), + .a = absColor.c.a, + }}; +} + +static RGBAColor tfInvBT1886(RGBAColor color) { + return tfInvLinPow(color, BT1886_POW, BT1886_CUT, BT1886_SCALE, BT1886_ALPHA); +} + +static RGBAColor tfInvXVYCC(RGBAColor color) { + // The inverse transfer function for XVYCC is the BT1886 transfer function mirrored around 0, + // same as what EXT sRGB is to sRGB. + const auto absColor = tfInvBT1886({{.r = abs(color.c.r), .g = abs(color.c.g), .b = abs(color.c.b), .a = color.c.a}}); + return {{ + .r = absColor.c.r * sign(color.c.r), + .g = absColor.c.g * sign(color.c.g), + .b = absColor.c.b * sign(color.c.b), + .a = absColor.c.a, + }}; +} + +static RGBAColor tfInvST240(RGBAColor color) { + return tfInvLinPow(color, ST240_POW, ST240_CUT, ST240_SCALE, ST240_ALPHA); +} + +// Forward transfer functions corresponding to the inverse functions above. +static RGBAColor tfPQ(RGBAColor color) { + for (uint i = 0; i <= 2; i++) { + const double E = pow(std::clamp(color.v[i], 0.0, 1.0), PQ_M1); + color.v[i] = pow((PQ_C1 + PQ_C2 * E) / (1.0 + PQ_C3 * E), PQ_M2); + } + return color; +} + +static RGBAColor tfHLG(RGBAColor color) { + for (uint i = 0; i <= 2; i++) { + const bool isLow = color.v[i] <= HLG_D_CUT; + color.v[i] = isLow ? sqrt(std::max(color.v[i], 0.0) * 3.0) : HLG_A * log(std::max(12.0 * color.v[i] - HLG_B, 0.0001)) + HLG_C; + } + return color; +} + +static RGBAColor tfLinPow(RGBAColor color, float gamma, float thres, float scale, float alpha) { + for (uint i = 0; i <= 2; i++) { + const bool isLow = color.v[i] <= thres; + color.v[i] = isLow ? color.v[i] * scale : pow(color.v[i], 1.0 / gamma) * alpha - (alpha - 1.0); + } + return color; +} + +static RGBAColor tfSRGB(RGBAColor color) { + return tfLinPow(color, SRGB_POW, SRGB_CUT, SRGB_SCALE, SRGB_ALPHA); +} + +static RGBAColor tfExtSRGB(RGBAColor color) { + // EXT sRGB is the sRGB transfer function mirrored around 0. + const auto absColor = tfSRGB({{.r = abs(color.c.r), .g = abs(color.c.g), .b = abs(color.c.b), .a = color.c.a}}); + return {{ + .r = absColor.c.r * sign(color.c.r), + .g = absColor.c.g * sign(color.c.g), + .b = absColor.c.b * sign(color.c.b), + .a = absColor.c.a, + }}; +} + +static RGBAColor tfBT1886(RGBAColor color) { + return tfLinPow(color, BT1886_POW, BT1886_CUT, BT1886_SCALE, BT1886_ALPHA); +} + +static RGBAColor tfXVYCC(RGBAColor color) { + // The transfer function for XVYCC is the BT1886 transfer function mirrored around 0, + // same as what EXT sRGB is to sRGB. + const auto absColor = tfBT1886({{.r = abs(color.c.r), .g = abs(color.c.g), .b = abs(color.c.b), .a = color.c.a}}); + return {{ + .r = absColor.c.r * sign(color.c.r), + .g = absColor.c.g * sign(color.c.g), + .b = absColor.c.b * sign(color.c.b), + .a = absColor.c.a, + }}; +} + +static RGBAColor tfST240(RGBAColor color) { + return tfLinPow(color, ST240_POW, ST240_CUT, ST240_SCALE, ST240_ALPHA); +} + +static RGBAColor tfST428(RGBAColor color) { + for (uint i = 0; i <= 2; i++) { + color.v[i] = pow(std::max(color.v[i], 0.0), ST428_POW) * ST428_SCALE; + } + return color; +} + +static RGBAColor tfInvST428(RGBAColor color) { + for (uint i = 0; i <= 2; i++) { + color.v[i] = pow(std::max(color.v[i], 0.0) / ST428_SCALE, 1.0 / ST428_POW); + } + return color; +} + +static RGBAColor tfGamma(RGBAColor color, float gamma) { + for (uint i = 0; i <= 2; i++) { + color.v[i] = pow(std::max(color.v[i], 0.0), gamma); + } + return color; +} + +static RGBAColor tfLog(RGBAColor color, float mult) { + for (uint i = 0; i <= 2; i++) { + color.v[i] = color.v[i] <= 0 ? 0.0 : exp((color.v[i] - 1.0) * mult * std::numbers::ln10); + } + return color; +} + +static RGBAColor tfInvLog(RGBAColor color, float mult, float min) { + for (uint i = 0; i <= 2; i++) { + color.v[i] = color.v[i] <= min ? 0.0 : 1.0 + log(color.v[i]) / std::numbers::ln10 / mult; + } + return color; +} + +static RGBAColor toLinearRGB(RGBAColor color, eTransferFunction tf) { + switch (tf) { + case CM_TRANSFER_FUNCTION_LINEAR: return color; + case CM_TRANSFER_FUNCTION_EXT_LINEAR: return color; + case CM_TRANSFER_FUNCTION_ST2084_PQ: return tfInvPQ(color); + case CM_TRANSFER_FUNCTION_GAMMA22: return tfGamma(color, 2.2); + case CM_TRANSFER_FUNCTION_GAMMA28: return tfGamma(color, 2.8); + case CM_TRANSFER_FUNCTION_HLG: return tfInvHLG(color); + case CM_TRANSFER_FUNCTION_EXT_SRGB: return tfInvExtSRGB(color); + case CM_TRANSFER_FUNCTION_BT1886: return tfInvBT1886(color); + case CM_TRANSFER_FUNCTION_ST240: return tfInvST240(color); + case CM_TRANSFER_FUNCTION_LOG_100: return tfLog(color, 2.0); + case CM_TRANSFER_FUNCTION_LOG_316: return tfLog(color, 2.5); + case CM_TRANSFER_FUNCTION_XVYCC: return tfInvXVYCC(color); + case CM_TRANSFER_FUNCTION_ST428: return tfST428(color); + case CM_TRANSFER_FUNCTION_SRGB: + default: return tfInvSRGB(color); + } +} + +static RGBAColor fromLinearRGB(RGBAColor color, eTransferFunction tf) { + switch (tf) { + case CM_TRANSFER_FUNCTION_EXT_LINEAR: return color; + case CM_TRANSFER_FUNCTION_ST2084_PQ: return tfPQ(color); + case CM_TRANSFER_FUNCTION_GAMMA22: return tfGamma(color, 1.0 / 2.2); + case CM_TRANSFER_FUNCTION_GAMMA28: return tfGamma(color, 1.0 / 2.8); + case CM_TRANSFER_FUNCTION_HLG: return tfHLG(color); + case CM_TRANSFER_FUNCTION_EXT_SRGB: return tfExtSRGB(color); + case CM_TRANSFER_FUNCTION_BT1886: return tfBT1886(color); + case CM_TRANSFER_FUNCTION_ST240: return tfST240(color); + case CM_TRANSFER_FUNCTION_LOG_100: return tfInvLog(color, 2.0, 0.01); + case CM_TRANSFER_FUNCTION_LOG_316: return tfInvLog(color, 2.5, sqrt(10.0) / 1000.0); + case CM_TRANSFER_FUNCTION_XVYCC: return tfXVYCC(color); + case CM_TRANSFER_FUNCTION_ST428: return tfInvST428(color); + case CM_TRANSFER_FUNCTION_SRGB: + default: return tfSRGB(color); + } +} + +static RGBAColor toNit(RGBAColor color, Render::STFRange range) { + for (uint i = 0; i <= 2; i++) { + color.v[i] = color.v[i] * (range.max - range.min) + range.min; + } + return color; +} + +static RGBAColor fromLinearNit(RGBAColor color, eTransferFunction tf, Render::STFRange range) { + if (tf == CM_TRANSFER_FUNCTION_LINEAR) + return color; + + for (uint i = 0; i <= 2; i++) { + color.v[i] = (color.v[i] - range.min * color.c.a) / (range.max - range.min); + } + color /= std::max(color.c.a, 0.001); + color = fromLinearRGB(color, tf); + color *= color.c.a; + return color; +} + +static RGBAColor saturate(RGBAColor color, std::array, 3> primaries, float saturation) { + if (saturation == 1.0) + return color; + + std::array colorArr = {color.v[0], color.v[1], color.v[2]}; + std::array brightness = {primaries[1][0], primaries[1][1], primaries[1][2]}; + const auto Y = std::inner_product(colorArr.begin(), colorArr.end(), brightness.begin(), 0.0); + for (uint i = 0; i <= 2; i++) { + color.v[i] = (color.v[i] * saturation) + (Y * (1.0 - saturation)); + } + return color; +} + +static RGBAColor tonemap(RGBAColor color, std::array, 3> dstXYZ, float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance) { + // TODO source color is expected to be in sRGB colorspace and tonamepping shouldn't be needed + return color; +} + +RGBAColor NColorManagement::convertColor(RGBAColor color, PImageDescription srcDesc, PImageDescription dstDesc) { + const auto settings = + g_pHyprRenderer->getCMSettings(srcDesc, dstDesc, nullptr, true, g_pHyprRenderer->m_renderData.pMonitor ? g_pHyprRenderer->m_renderData.pMonitor->m_sdrMinLuminance : -1, + g_pHyprRenderer->m_renderData.pMonitor ? g_pHyprRenderer->m_renderData.pMonitor->m_sdrMaxLuminance : -1); + + color /= std::max(color.c.a, 0.001); + color = toLinearRGB(color, srcDesc->value().transferFunction); + if (dstDesc->value().icc.present) { + // color.rgb = applyIcc3DLut(color.rgb, iccLut3D, iccLutSize); + color *= color.c.a; + } else { + const auto convertMatrix = srcDesc->getPrimaries()->convertMatrix(dstDesc->getPrimaries()); + const auto converted = convertMatrix * Hyprgraphics::CColor::XYZ{.x = color.c.r, .y = color.c.g, .z = color.c.b}; + color.c.r = converted.x; + color.c.g = converted.y; + color.c.b = converted.z; + if (srcDesc->value().transferFunction != CM_TRANSFER_FUNCTION_LINEAR) + color = toNit(color, settings.srcTFRange); + color *= color.c.a; + if (settings.needsTonemap) + color = tonemap(color, settings.dstPrimaries2XYZ, settings.maxLuminance, settings.dstMaxLuminance, settings.dstRefLuminance, settings.srcRefLuminance); + + color = fromLinearNit(color, dstDesc->value().transferFunction, settings.dstTFRange); + if (settings.needsSDRmod) { + color = saturate(color, settings.dstPrimaries2XYZ, settings.sdrSaturation); + color *= settings.sdrBrightnessMultiplier; + } + } + return color; +} + +CHyprColor NColorManagement::convertColor(const CHyprColor& color, PImageDescription srcDesc, PImageDescription dstDesc) { + const auto& converted = convertColor(RGBAColor{{.r = color.r, .g = color.g, .b = color.b, .a = color.a}}, srcDesc, dstDesc); + return CHyprColor(converted.c.r, converted.c.g, converted.c.b, converted.c.a); +} + PImageDescription NColorManagement::getDefaultImageDescription() { const auto TF = fromConfig(); switch (TF) { diff --git a/src/helpers/cm/ColorManagement.hpp b/src/helpers/cm/ColorManagement.hpp index 183a8544d..c8d1cfcba 100644 --- a/src/helpers/cm/ColorManagement.hpp +++ b/src/helpers/cm/ColorManagement.hpp @@ -3,8 +3,10 @@ #include "color-management-v1.hpp" #include #include -#include "../../helpers/memory/Memory.hpp" -#include "../../helpers/math/Math.hpp" +#include "../memory/Memory.hpp" +#include "../math/Math.hpp" +#include "../Color.hpp" +#include "../../debug/log/Logger.hpp" #include #include @@ -45,6 +47,7 @@ namespace NColorManagement { }; enum eTransferFunction : uint8_t { + CM_TRANSFER_FUNCTION_LINEAR = 0, CM_TRANSFER_FUNCTION_BT1886 = 1, CM_TRANSFER_FUNCTION_GAMMA22 = 2, CM_TRANSFER_FUNCTION_GAMMA28 = 3, @@ -68,9 +71,14 @@ namespace NColorManagement { inline ePrimaries convertPrimaries(wpColorManagerV1Primaries primaries) { return sc(primaries); } - inline wpColorManagerV1TransferFunction convertTransferFunction(eTransferFunction tf) { + inline wpColorManagerV1TransferFunction convertTransferFunction(eTransferFunction tf, bool useV1SRGB = true) { switch (tf) { - case CM_TRANSFER_FUNCTION_SRGB: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_COMPOUND_POWER_2_4; + case CM_TRANSFER_FUNCTION_LINEAR: + Log::logger->log(Log::TRACE, + "CM_TRANSFER_FUNCTION_LINEAR is internal and buffers with this TF shouldn't go outside. Returning " + "WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR for preferred description instead"); + return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR; + case CM_TRANSFER_FUNCTION_SRGB: return useV1SRGB ? WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB : WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_COMPOUND_POWER_2_4; default: return sc(tf); } } @@ -82,6 +90,7 @@ namespace NColorManagement { } inline std::string tfToString(eTransferFunction tf) { switch (tf) { + case CM_TRANSFER_FUNCTION_LINEAR: return "TF:INTERNAL LINEAR NOT NORMALISED"; case CM_TRANSFER_FUNCTION_BT1886: return "TF:BT1886"; case CM_TRANSFER_FUNCTION_GAMMA22: return "TF:GAMMA22"; case CM_TRANSFER_FUNCTION_GAMMA28: return "TF:GAMMA28"; @@ -349,8 +358,32 @@ namespace NColorManagement { SImageDescription m_imageDescription; }; + union RGBAColor { + struct { + double r = 0, g = 0, b = 0, a = 0; + } c; + double v[4]; + + RGBAColor& operator*=(double value) { + c.r *= value; + c.g *= value; + c.b *= value; + return *this; + } + + RGBAColor& operator/=(double value) { + c.r /= value; + c.g /= value; + c.b /= value; + return *this; + } + }; + using PImageDescription = WP; + RGBAColor convertColor(RGBAColor color, PImageDescription srcDesc, PImageDescription dstDesc); + CHyprColor convertColor(const CHyprColor& color, PImageDescription srcDesc, PImageDescription dstDesc); + PImageDescription getDefaultImageDescription(); static const auto DEFAULT_GAMMA22_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ @@ -393,6 +426,17 @@ namespace NColorManagement { .primaries = NColorPrimaries::BT709, .luminances = {.min = 0, .max = 10000, .reference = 80}, }); + + // For internal use only + // not normalised to 0.0 - 1.0 + // luminance values should be set to default SDR settings in SDR mode and to output settings in HDR mode + // keep srgb primaries to avoid conversions for image exports + static const auto LINEAR_NN_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ + .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_LINEAR, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_SRGB, + .primaries = NColorPrimaries::BT709, + }); } template diff --git a/src/managers/screenshare/ScreenshareFrame.cpp b/src/managers/screenshare/ScreenshareFrame.cpp index 7a596d0c6..4cc51335e 100644 --- a/src/managers/screenshare/ScreenshareFrame.cpp +++ b/src/managers/screenshare/ScreenshareFrame.cpp @@ -196,11 +196,10 @@ void CScreenshareFrame::renderMonitor() { g_pHyprRenderer->startRenderPass(); g_pHyprRenderer->draw( CTexPassElement::SRenderData{ - .tex = TEXTURE, - .box = monbox, - .flipEndFrame = true, - .cmBackToSRGB = !IS_CM_AWARE, - .cmBackToSRGBSource = !IS_CM_AWARE ? PMONITOR : nullptr, + .tex = TEXTURE, + .box = monbox, + .flipEndFrame = true, + .cmBackToSRGB = !IS_CM_AWARE, }, {0, 0, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y}); g_pHyprRenderer->m_renderData.renderModif.enabled = OLD; diff --git a/src/protocols/ColorManagement.cpp b/src/protocols/ColorManagement.cpp index fce91691b..a704d4d7a 100644 --- a/src/protocols/ColorManagement.cpp +++ b/src/protocols/ColorManagement.cpp @@ -807,7 +807,7 @@ CColorManagementImageDescriptionInfo::CColorManagementImageDescriptionInfo(SPsendPrimariesNamed(m_settings.primariesNamed); - m_resource->sendTfNamed(m_settings.transferFunction); + m_resource->sendTfNamed(convertTransferFunction(m_settings.transferFunction, m_resource->version() == 1)); if (m_settings.transferFunctionPower != 1.0f) m_resource->sendTfPower(std::round(m_settings.transferFunctionPower * 10000)); diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 6bb04cfae..0a53442ef 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -34,6 +34,7 @@ #include "../managers/screenshare/ScreenshareManager.hpp" #include "../notification/NotificationOverlay.hpp" #include "errorOverlay/Overlay.hpp" +#include "helpers/Color.hpp" #include "macros.hpp" #include "pass/TexPassElement.hpp" #include "pass/RectPassElement.hpp" @@ -758,6 +759,7 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, SPbindFB(g_pHyprRenderer->m_renderData.pMonitor->resources()->getUnusedWorkBuffer()); m_offloadedFramebuffer = true; + GLFB(g_pHyprRenderer->m_renderData.currentFB)->clearAfterInvalidation(); g_pHyprRenderer->m_renderData.mainFB = g_pHyprRenderer->m_renderData.currentFB; g_pHyprRenderer->m_renderData.outFB = fb ? fb : dc(g_pHyprRenderer.get())->m_currentRenderbuffer->getFB(); @@ -1062,6 +1064,19 @@ void CHyprOpenGLImpl::renderRectWithBlurInternal(const CBox& box, const CHyprCol renderRectWithDamageInternal(box, col, data); } +using ColorConversionKey = std::tuple; +static std::map colorConversionCache; + +static CHyprColor getConvertedColor(const CHyprColor& color) { + const auto targetId = g_pHyprRenderer->workBufferImageDescription()->id(); + const ColorConversionKey key = {color.r, color.g, color.b, color.a, targetId}; + if (colorConversionCache.contains(key)) + return colorConversionCache[key]; + const auto converted = convertColor(color, DEFAULT_SRGB_IMAGE_DESCRIPTION, g_pHyprRenderer->workBufferImageDescription()); + colorConversionCache[key] = converted; + return converted; +} + void CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprColor& col, const SRectRenderData& data) { auto& m_renderData = g_pHyprRenderer->m_renderData; RASSERT((box.width > 0 && box.height > 0), "Tried to render rect with width/height < 0!"); @@ -1078,7 +1093,10 @@ void CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprC shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); // premultiply the color as well as we don't work with straight alpha - shader->setUniformFloat4(SHADER_COLOR, col.r * col.a, col.g * col.a, col.b * col.a, col.a); + const auto premultiplied = CHyprColor(col.r * col.a, col.g * col.a, col.b * col.a, col.a); + const auto converted = getConvertedColor(premultiplied); + shader->setUniformFloat4(SHADER_COLOR, converted.r, converted.g, converted.b, converted.a); + shader->setUniformFloat4(SHADER_COLOR_SRGB, premultiplied.r, premultiplied.g, premultiplied.b, premultiplied.a); CBox transformedBox = box; transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, @@ -1298,7 +1316,7 @@ WP CHyprOpenGLImpl::renderToFBInternal(SP tex, const STexture shaderFeatures &= ~SH_FEAT_RGBA; const auto surface = g_pHyprRenderer->m_renderData.surface; - const auto WORK_BUFFER_IMAGE_DESCRIPTION = g_pHyprRenderer->workBufferImageDescription(); + const auto WORK_BUFFER_IMAGE_DESCRIPTION = g_pHyprRenderer->m_renderData.pMonitor->workBufferImageDescription(); // chosenSdrEotf contains the valid eotf for this display @@ -1311,7 +1329,7 @@ WP CHyprOpenGLImpl::renderToFBInternal(SP tex, const STexture return CImageDescription::from(surface->m_colorManagement->imageDescription()); if (data.cmBackToSRGB) - return g_pHyprRenderer->m_renderData.pMonitor->m_imageDescription; + return tex->m_imageDescription ? tex->m_imageDescription : WORK_BUFFER_IMAGE_DESCRIPTION; // otherwise, if we are CM'ing back into source, use chosen, because that's what our work buffer is in // the same applies to the final monitor CM @@ -1660,6 +1678,7 @@ SP CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* or static auto PBLEND = CConfigValue("render:use_shader_blur_blend"); PMIRRORSWAPFB->bind(); + GLFB(PMIRRORSWAPFB)->clearAfterInvalidation(); glActiveTexture(GL_TEXTURE0); @@ -1754,6 +1773,7 @@ SP CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* or // draw the things. // first draw is swap -> mirr PMIRRORFB->bind(); + GLFB(PMIRRORFB)->clearAfterInvalidation(); PMIRRORSWAPFB->getTexture()->bind(); // damage region will be scaled, make a temp @@ -2240,14 +2260,12 @@ void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roun blend(true); - const bool IS_ICC = g_pHyprRenderer->workBufferImageDescription()->value().icc.present; - const bool skipCM = !m_cmSupported || !g_pHyprRenderer->workBufferImageDescription()->needsCM(getDefaultImageDescription()); - auto shader = useShader(getShaderVariant(SH_FRAG_SHADOW, skipCM ? 0 : SH_FEAT_CM | (IS_ICC ? SH_FEAT_ICC : SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD) | globalFeatures())); - if (!skipCM) - passCMUniforms(shader, getDefaultImageDescription()); + auto shader = useShader(getShaderVariant(SH_FRAG_SHADOW, globalFeatures())); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - shader->setUniformFloat4(SHADER_COLOR, col.r, col.g, col.b, col.a * a); + const auto converted = getConvertedColor(col); + shader->setUniformFloat4(SHADER_COLOR, converted.r, converted.g, converted.b, converted.a * a); + shader->setUniformFloat4(SHADER_COLOR_SRGB, col.r, col.g, col.b, col.a * a); const auto TOPLEFT = Vector2D(range + round, range + round); const auto BOTTOMRIGHT = Vector2D(newBox.width - (range + round), newBox.height - (range + round)); @@ -2334,14 +2352,12 @@ void CHyprOpenGLImpl::renderInnerGlow(const CBox& box, int round, float rounding blend(true); - const bool IS_ICC = g_pHyprRenderer->workBufferImageDescription()->value().icc.present; - const bool skipCM = !m_cmSupported || !g_pHyprRenderer->workBufferImageDescription()->needsCM(getDefaultImageDescription()); - auto shader = useShader(getShaderVariant(SH_FRAG_INNER_GLOW, skipCM ? 0 : SH_FEAT_CM | (IS_ICC ? SH_FEAT_ICC : SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD))); - if (!skipCM) - passCMUniforms(shader, getDefaultImageDescription()); + auto shader = useShader(getShaderVariant(SH_FRAG_INNER_GLOW, globalFeatures())); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - shader->setUniformFloat4(SHADER_COLOR, col.r, col.g, col.b, col.a * a); + const auto converted = getConvertedColor(col); + shader->setUniformFloat4(SHADER_COLOR, converted.r, converted.g, converted.b, converted.a * a); + shader->setUniformFloat4(SHADER_COLOR_SRGB, col.r, col.g, col.b, col.a * a); const auto TOPLEFT = Vector2D(round, round); const auto BOTTOMRIGHT = Vector2D(newBox.width - round, newBox.height - round); @@ -2385,7 +2401,8 @@ void CHyprOpenGLImpl::saveBufferForMirror(const CBox& box) { Log::logger->log(Log::ERR, "Invalid source texture for mirror"); return; } - auto guard = g_pHyprRenderer->bindTempFB(g_pHyprRenderer->m_renderData.pMonitor->resources()->mirrorFB()); + auto fb = g_pHyprRenderer->m_renderData.pMonitor->resources()->mirrorFB(); + auto guard = g_pHyprRenderer->bindTempFB(fb); Log::logger->log(Log::TRACE, "CM: saveBufferForMirror {} -> {}", TEX->m_imageDescription->value(), g_pHyprRenderer->m_renderData.currentFB->imageDescription()->value()); @@ -2398,7 +2415,6 @@ void CHyprOpenGLImpl::saveBufferForMirror(const CBox& box) { .round = 0, .discardActive = false, .allowCustomUV = false, - .cmBackToSRGB = true, }); blend(true); diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp index 82b8f0548..2434c0edc 100644 --- a/src/render/OpenGL.hpp +++ b/src/render/OpenGL.hpp @@ -174,7 +174,6 @@ namespace Render::GL { GLenum wrapX = GL_CLAMP_TO_EDGE, wrapY = GL_CLAMP_TO_EDGE; bool cmBackToSRGB = false; bool finalMonitorCM = false; - SP cmBackToSRGBSource; uint32_t discardMode = DISCARD_OPAQUE; float discardOpacity = 0.f; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 635bef556..0cb713f1b 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -2020,18 +2020,8 @@ void IHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { } handleFullscreenSettings(pMonitor); return; - } else if (!pMonitor->m_lastScanout.expired() || pMonitor->m_directScanoutIsActive) { - Log::logger->log(Log::DEBUG, "Left a direct scanout."); - pMonitor->m_lastScanout.reset(); - pMonitor->m_previousFSWindow.reset(); // recalc fs settings - pMonitor->m_directScanoutIsActive = false; - - // reset DRM format, but only if needed since it might modeset - if (pMonitor->m_output->state->state().drmFormat != pMonitor->m_prevDrmFormat) - pMonitor->m_output->state->setFormat(pMonitor->m_prevDrmFormat); - - pMonitor->m_drmFormat = pMonitor->m_prevDrmFormat; - } + } else if (!pMonitor->m_lastScanout.expired() || pMonitor->m_directScanoutIsActive) + pMonitor->handleDSleave(); } Event::bus()->m_events.render.pre.emit(pMonitor); @@ -3049,6 +3039,7 @@ void IHyprRenderer::makeSnapshot(PHLWINDOW pWindow) { const auto PFRAMEBUFFER = ref->m_snapshotFB; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); + PFRAMEBUFFER->setImageDescription(workBufferImageDescription()); beginFullFakeRender(PMONITOR, fakeDamage, PFRAMEBUFFER); @@ -3090,6 +3081,7 @@ void IHyprRenderer::makeSnapshot(PHLLS pLayer) { const auto PFRAMEBUFFER = pLayer->m_snapshotFB; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); + PFRAMEBUFFER->setImageDescription(workBufferImageDescription()); beginFullFakeRender(PMONITOR, fakeDamage, PFRAMEBUFFER); @@ -3132,6 +3124,7 @@ void IHyprRenderer::makeSnapshot(WP popup) { const auto PFRAMEBUFFER = popup->m_snapshotFB; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); + PFRAMEBUFFER->setImageDescription(workBufferImageDescription()); beginFullFakeRender(PMONITOR, fakeDamage, PFRAMEBUFFER); @@ -3314,14 +3307,10 @@ void IHyprRenderer::renderSnapshot(WP popup) { } NColorManagement::PImageDescription IHyprRenderer::workBufferImageDescription() { - // TODO - // const bool IS_MONITOR_ICC = m_renderData.pMonitor->m_imageDescription.valid() && m_renderData.pMonitor->m_imageDescription->value().icc.present; - // const auto sdrEOTF = NTransferFunction::fromConfig(IS_MONITOR_ICC); - // const auto CHOSEN_SDR_EOTF = sdrEOTF != NTransferFunction::TF_SRGB ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB; + if (!m_renderData.pMonitor) + return LINEAR_IMAGE_DESCRIPTION; - return m_renderData.pMonitor->useFP16() ? - LINEAR_IMAGE_DESCRIPTION : - m_renderData.pMonitor->m_imageDescription; //CImageDescription::from(NColorManagement::SImageDescription{.transferFunction = CHOSEN_SDR_EOTF}); + return m_renderData.pMonitor->workBufferImageDescription(); } bool IHyprRenderer::shouldBlur(PHLLS ls) { diff --git a/src/render/Shader.cpp b/src/render/Shader.cpp index 3359de660..8a7b8ba9d 100644 --- a/src/render/Shader.cpp +++ b/src/render/Shader.cpp @@ -126,6 +126,7 @@ void CShader::getUniformLocations() { m_uniformLocations[SHADER_PROJ] = getUniform("proj"); m_uniformLocations[SHADER_COLOR] = getUniform("color"); + m_uniformLocations[SHADER_COLOR_SRGB] = getUniform("colorSRGB"); m_uniformLocations[SHADER_ALPHA_MATTE] = getUniform("texMatte"); m_uniformLocations[SHADER_TEX_TYPE] = getUniform("texType"); diff --git a/src/render/Shader.hpp b/src/render/Shader.hpp index 84a4014cd..cf15c8059 100644 --- a/src/render/Shader.hpp +++ b/src/render/Shader.hpp @@ -7,6 +7,7 @@ enum eShaderUniform : uint8_t { SHADER_PROJ = 0, SHADER_COLOR, + SHADER_COLOR_SRGB, SHADER_ALPHA_MATTE, SHADER_TEX_TYPE, SHADER_SOURCE_TF, diff --git a/src/render/gl/GLElementRenderer.cpp b/src/render/gl/GLElementRenderer.cpp index dbf62cb8f..c20fffc08 100644 --- a/src/render/gl/GLElementRenderer.cpp +++ b/src/render/gl/GLElementRenderer.cpp @@ -114,19 +114,18 @@ void CGLElementRenderer::draw(WP element, const CRegion& damage .blurredBG = m_data.blurredBG, // common settings - .damage = m_data.damage.empty() ? &damage : &m_data.damage, - .surface = m_data.surface, - .a = m_data.a, - .round = m_data.round, - .roundingPower = m_data.roundingPower, - .discardActive = m_data.discardActive, - .allowCustomUV = m_data.allowCustomUV, - .cmBackToSRGB = m_data.cmBackToSRGB, - .cmBackToSRGBSource = m_data.cmBackToSRGBSource, - .discardMode = m_data.ignoreAlpha.has_value() ? sc(DISCARD_ALPHA) : m_data.discardMode, - .discardOpacity = m_data.ignoreAlpha.has_value() ? *m_data.ignoreAlpha : m_data.discardOpacity, - .clipRegion = m_data.clipRegion, - .currentLS = m_data.currentLS, + .damage = m_data.damage.empty() ? &damage : &m_data.damage, + .surface = m_data.surface, + .a = m_data.a, + .round = m_data.round, + .roundingPower = m_data.roundingPower, + .discardActive = m_data.discardActive, + .allowCustomUV = m_data.allowCustomUV, + .cmBackToSRGB = m_data.cmBackToSRGB, + .discardMode = m_data.ignoreAlpha.has_value() ? sc(DISCARD_ALPHA) : m_data.discardMode, + .discardOpacity = m_data.ignoreAlpha.has_value() ? *m_data.ignoreAlpha : m_data.discardOpacity, + .clipRegion = m_data.clipRegion, + .currentLS = m_data.currentLS, .primarySurfaceUVTopLeft = g_pHyprRenderer->m_renderData.primarySurfaceUVTopLeft, .primarySurfaceUVBottomRight = g_pHyprRenderer->m_renderData.primarySurfaceUVBottomRight, diff --git a/src/render/gl/GLFramebuffer.cpp b/src/render/gl/GLFramebuffer.cpp index d8b198c3e..196f33275 100644 --- a/src/render/gl/GLFramebuffer.cpp +++ b/src/render/gl/GLFramebuffer.cpp @@ -171,4 +171,15 @@ void CGLFramebuffer::invalidate(const std::vector& attachments) { return; glInvalidateFramebuffer(GL_FRAMEBUFFER, attachments.size(), attachments.data()); + m_cleared = false; +} + +void CGLFramebuffer::clearAfterInvalidation() { + if (m_cleared) + return; + + m_cleared = true; + glClearColor(0, 0, 0, 0); + g_pHyprOpenGL->scissor(nullptr); + glClear(GL_COLOR_BUFFER_BIT); } diff --git a/src/render/gl/GLFramebuffer.hpp b/src/render/gl/GLFramebuffer.hpp index 1be5d0e02..34051656f 100644 --- a/src/render/gl/GLFramebuffer.hpp +++ b/src/render/gl/GLFramebuffer.hpp @@ -21,11 +21,15 @@ namespace Render::GL { GLuint getFBID(); void invalidate(const std::vector& attachments); + // clear at most once per invalidate() + void clearAfterInvalidation(); + protected: bool internalAlloc(int w, int h, DRMFormat format = DRM_FORMAT_ARGB8888) override; private: - GLuint m_fb = -1; + GLuint m_fb = -1; + bool m_cleared = false; friend class CGLRenderbuffer; }; diff --git a/src/render/pass/TexPassElement.hpp b/src/render/pass/TexPassElement.hpp index 6c0cfab11..413f98753 100644 --- a/src/render/pass/TexPassElement.hpp +++ b/src/render/pass/TexPassElement.hpp @@ -31,7 +31,6 @@ class CTexPassElement : public IPassElement { std::optional ignoreAlpha; std::optional blockBlurOptimization; bool cmBackToSRGB = false; - SP cmBackToSRGBSource; bool discardActive = false; bool allowCustomUV = false; diff --git a/src/render/shaders/glsl/blur1.glsl b/src/render/shaders/glsl/blur1.glsl index 86a37d88a..8d9e39aec 100644 --- a/src/render/shaders/glsl/blur1.glsl +++ b/src/render/shaders/glsl/blur1.glsl @@ -104,10 +104,10 @@ vec4 blur1(vec2 v_texcoord, sampler2D tex, float radius, vec2 halfpixel, int pas // That garbage maps to 0.0-1.0 range with UINT8 buffer and doesn't have any significant impact on the end result. // FP16 garbage maps to -65,504 - 65,504 and defines the end result. Clamp it here to 0.0 - 1.0 to get the same quality outcome as with UINT8. // Rerendering an undamaged area to get some insignificant color accuracy increase on blur edges isn't worth it. - sum += clamp(texture(tex, uv - halfpixel.xy * radius), 0.0, 1.0); - sum += clamp(texture(tex, uv + halfpixel.xy * radius), 0.0, 1.0); - sum += clamp(texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius), 0.0, 1.0); - sum += clamp(texture(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius), 0.0, 1.0); + sum += texture(tex, uv - halfpixel.xy * radius); + sum += texture(tex, uv + halfpixel.xy * radius); + sum += texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius); + sum += texture(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius); vec4 color = sum / 8.0; diff --git a/src/render/shaders/glsl/cm_helpers.glsl b/src/render/shaders/glsl/cm_helpers.glsl index 0d8c68e93..ae4b63ae8 100644 --- a/src/render/shaders/glsl/cm_helpers.glsl +++ b/src/render/shaders/glsl/cm_helpers.glsl @@ -127,6 +127,7 @@ vec3 tfST240(vec3 color) { vec3 toLinearRGB(vec3 color, int tf) { switch (tf) { + case CM_TRANSFER_FUNCTION_LINEAR: return color; case CM_TRANSFER_FUNCTION_EXT_LINEAR: return color; case CM_TRANSFER_FUNCTION_ST2084_PQ: return tfInvPQ(color); case CM_TRANSFER_FUNCTION_GAMMA22: return pow(max(color, vec3(0.0)), vec3(2.2)); @@ -179,7 +180,7 @@ vec3 fromLinearRGB(vec3 color, int tf) { } vec4 fromLinear(vec4 color, int tf) { - if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR || tf == CM_TRANSFER_FUNCTION_LINEAR) return color; color.rgb /= max(color.a, 0.001); @@ -189,6 +190,9 @@ vec4 fromLinear(vec4 color, int tf) { } vec4 fromLinearNit(vec4 color, int tf, vec2 range) { + if (tf == CM_TRANSFER_FUNCTION_LINEAR) + return color; + color.rgb = (color.rgb - range[0] * color.a) / (range[1] - range[0]); // @gulafaran color.rgb /= max(color.a, 0.001); color.rgb = fromLinearRGB(color.rgb, tf); @@ -231,7 +235,8 @@ vec4 pixColor.rgb *= pixColor.a; #else pixColor.rgb = convertMatrix * pixColor.rgb; - pixColor = toNit(pixColor, srcTFRange); + if (srcTF != CM_TRANSFER_FUNCTION_LINEAR) + pixColor = toNit(pixColor, srcTFRange); pixColor.rgb *= pixColor.a; #if USE_TONEMAP pixColor = tonemap(pixColor, dstxyz, maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance); @@ -258,4 +263,4 @@ vec4 #endif } -#endif \ No newline at end of file +#endif diff --git a/src/render/shaders/glsl/constants.h b/src/render/shaders/glsl/constants.h index bbab5284b..db8b2cc1d 100644 --- a/src/render/shaders/glsl/constants.h +++ b/src/render/shaders/glsl/constants.h @@ -1,6 +1,7 @@ #ifndef CONSTANTS_H #define CONSTANTS_H //enum eTransferFunction +#define CM_TRANSFER_FUNCTION_LINEAR 0 // not normalised #define CM_TRANSFER_FUNCTION_BT1886 1 #define CM_TRANSFER_FUNCTION_GAMMA22 2 #define CM_TRANSFER_FUNCTION_GAMMA28 3 diff --git a/src/render/shaders/glsl/inner_glow.frag b/src/render/shaders/glsl/inner_glow.frag index 2fc405c06..63369e0ac 100644 --- a/src/render/shaders/glsl/inner_glow.frag +++ b/src/render/shaders/glsl/inner_glow.frag @@ -8,10 +8,7 @@ precision highp float; in vec4 v_color; in vec2 v_texcoord; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction -uniform mat3 targetPrimariesXYZ; - +uniform vec4 colorSRGB; uniform vec2 topLeft; uniform vec2 bottomRight; uniform vec2 fullSize; @@ -20,38 +17,22 @@ uniform float roundingPower; uniform float range; uniform float shadowPower; -#if USE_CM -#include "cm_helpers.glsl" -#include "CM.glsl" -#endif - #include "inner_glow.glsl" layout(location = 0) out vec4 fragColor; +#if USE_MIRROR +layout(location = 1) out vec4 mirrorColor; +#endif void main() { vec4 pixColor = v_color; - - fragColor = getInnerGlow(pixColor, v_texcoord, radius, roundingPower, topLeft, fullSize, range, shadowPower, bottomRight -#if USE_CM - , - sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange -#if USE_ICC - , - iccLut3D, iccLutSize +#if USE_MIRROR + vec4[2] pixColors = #else -#if USE_TONEMAP || USE_SDR_MOD - , - targetPrimariesXYZ + fragColor = #endif -#if USE_TONEMAP - , - maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance + getInnerGlow(pixColor, colorSRGB, v_texcoord, radius, roundingPower, topLeft, fullSize, range, shadowPower, bottomRight); +#if USE_MIRROR + fragColor = pixColors[0]; + mirrorColor = pixColors[1]; #endif -#if USE_SDR_MOD - , - sdrSaturation, sdrBrightnessMultiplier -#endif -#endif -#endif - ); } diff --git a/src/render/shaders/glsl/inner_glow.glsl b/src/render/shaders/glsl/inner_glow.glsl index b0d55b194..307c3648a 100644 --- a/src/render/shaders/glsl/inner_glow.glsl +++ b/src/render/shaders/glsl/inner_glow.glsl @@ -5,8 +5,6 @@ #ifndef INNER_GLOW_GLSL #define INNER_GLOW_GLSL -#include "cm_helpers.glsl" - float innerGlowAlpha(float distFromEdge, float range, float glowPower) { if (distFromEdge >= range) return 0.0; @@ -26,29 +24,7 @@ float innerGlowSmin(float a, float b, float k) { return min(a, b) - h * h * h * k * (1.0 / 6.0); } -vec4 getInnerGlow(vec4 pixColor, vec2 v_texcoord, float radius, float roundingPower, vec2 topLeft, vec2 fullSize, float range, float glowPower, vec2 bottomRight -#if USE_CM - , - int sourceTF, int targetTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange -#if USE_ICC - , - highp sampler3D iccLut3D, float iccLutSize -#else -#if USE_TONEMAP || USE_SDR_MOD - , - mat3 targetPrimariesXYZ -#endif -#if USE_TONEMAP - , - float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance -#endif -#if USE_SDR_MOD - , - float sdrSaturation, float sdrBrightnessMultiplier -#endif -#endif -#endif -) { +vec4 getInnerGlow(vec4 pixColor, vec4 colorSRGB, vec2 v_texcoord, float radius, float roundingPower, vec2 topLeft, vec2 fullSize, float range, float glowPower, vec2 bottomRight) { vec2 pixCoord = fullSize * v_texcoord; // clip to the rounded rectangle shape using actual SDF @@ -79,28 +55,15 @@ vec4 getInnerGlow(vec4 pixColor, vec2 v_texcoord, float radius, float roundingPo // premultiply pixColor.rgb *= pixColor[3]; -#if USE_CM - pixColor = doColorManagement(pixColor, sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange -#if USE_ICC - , - iccLut3D, iccLutSize +#if USE_MIRROR + vec4[2] pixColors; + pixColors[0] = pixColor; + pixColors[1] = colorSRGB; + pixColors[1].a = pixColor.a; + pixColors[1].rgb *= pixColors[1].a; + return pixColors; #else -#if USE_TONEMAP || USE_SDR_MOD - , - targetPrimariesXYZ -#endif -#if USE_TONEMAP - , - maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance -#endif -#if USE_SDR_MOD - , - sdrSaturation, sdrBrightnessMultiplier -#endif -#endif - ); -#endif - return pixColor; +#endif } #endif diff --git a/src/render/shaders/glsl/quad.frag b/src/render/shaders/glsl/quad.frag index dc1945afa..46d42a8c7 100644 --- a/src/render/shaders/glsl/quad.frag +++ b/src/render/shaders/glsl/quad.frag @@ -7,6 +7,7 @@ precision highp float; in vec4 v_color; +uniform vec4 colorSRGB; #if USE_ROUNDING uniform float radius; uniform float roundingPower; @@ -28,6 +29,10 @@ void main() { fragColor = pixColor; #if USE_MIRROR - mirrorColor = fragColor; +#if USE_ROUNDING + mirrorColor = rounding(colorSRGB, radius, roundingPower, topLeft, fullSize); +#else + mirrorColor = colorSRGB; +#endif #endif } diff --git a/src/render/shaders/glsl/shadow.frag b/src/render/shaders/glsl/shadow.frag index e91e9a140..eb6f31749 100644 --- a/src/render/shaders/glsl/shadow.frag +++ b/src/render/shaders/glsl/shadow.frag @@ -8,10 +8,7 @@ precision highp float; in vec4 v_color; in vec2 v_texcoord; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction -uniform mat3 targetPrimariesXYZ; - +uniform vec4 colorSRGB; uniform vec2 topLeft; uniform vec2 bottomRight; uniform vec2 windowTopLeft; @@ -23,11 +20,6 @@ uniform float range; uniform float shadowPower; uniform float thick; -#if USE_CM -#include "cm_helpers.glsl" -#include "CM.glsl" -#endif - #include "shadow.glsl" layout(location = 0) out vec4 fragColor; @@ -41,29 +33,7 @@ void main() { #else fragColor = #endif - getShadow(pixColor, v_texcoord, radius, roundingPower, topLeft, fullSize, range, shadowPower, bottomRight, windowTopLeft, windowBottomRight, thick -#if USE_CM - , - sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange -#if USE_ICC - , - iccLut3D, iccLutSize -#else -#if USE_TONEMAP || USE_SDR_MOD - , - targetPrimariesXYZ -#endif -#if USE_TONEMAP - , - maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance -#endif -#if USE_SDR_MOD - , - sdrSaturation, sdrBrightnessMultiplier -#endif -#endif -#endif - ); + getShadow(pixColor, colorSRGB, v_texcoord, radius, roundingPower, topLeft, fullSize, range, shadowPower, bottomRight, windowTopLeft, windowBottomRight, thick); #if USE_MIRROR fragColor = pixColors[0]; mirrorColor = pixColors[1]; diff --git a/src/render/shaders/glsl/shadow.glsl b/src/render/shaders/glsl/shadow.glsl index fdfa697c6..ba68bc49f 100644 --- a/src/render/shaders/glsl/shadow.glsl +++ b/src/render/shaders/glsl/shadow.glsl @@ -5,7 +5,6 @@ #ifndef SHADOW_GLSL #define SHADOW_GLSL -#include "cm_helpers.glsl" #include "rounding.glsl" float pixAlphaRoundedDistance(float distanceToCorner, float radius, float range, float shadowPower) { @@ -54,30 +53,8 @@ vec4[2] #else vec4 #endif - getShadow(vec4 pixColor, vec2 v_texcoord, float borderRadius, float roundingPower, vec2 topLeft, vec2 fullSize, float range, float shadowPower, vec2 bottomRight, - vec2 windowTopLeft, vec2 windowBottomRight, float windowRadius -#if USE_CM - , - int sourceTF, int targetTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange -#if USE_ICC - , - highp sampler3D iccLut3D, float iccLutSize -#else -#if USE_TONEMAP || USE_SDR_MOD - , - mat3 targetPrimariesXYZ -#endif -#if USE_TONEMAP - , - float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance -#endif -#if USE_SDR_MOD - , - float sdrSaturation, float sdrBrightnessMultiplier -#endif -#endif -#endif - ) { + getShadow(vec4 pixColor, vec4 colorSRGB, vec2 v_texcoord, float borderRadius, float roundingPower, vec2 topLeft, vec2 fullSize, float range, float shadowPower, vec2 bottomRight, + vec2 windowTopLeft, vec2 windowBottomRight, float windowRadius) { float radius = range + borderRadius; float originalAlpha = pixColor[3]; @@ -144,33 +121,12 @@ vec4 // premultiply pixColor.rgb *= pixColor[3]; -#if USE_CM -#if USE_MIRROR - vec4[2] pixColors = -#else - pixColor = -#endif - doColorManagement(pixColor, sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange -#if USE_ICC - , - iccLut3D, iccLutSize -#else -#if USE_TONEMAP || USE_SDR_MOD - , - targetPrimariesXYZ -#endif -#if USE_TONEMAP - , - maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance -#endif -#if USE_SDR_MOD - , - sdrSaturation, sdrBrightnessMultiplier -#endif -#endif - ); -#endif #if USE_MIRROR + vec4[2] pixColors; + pixColors[0] = pixColor; + pixColors[1] = colorSRGB; + pixColors[1].a = pixColor.a; + pixColors[1].rgb *= pixColors[1].a; return pixColors; #else return pixColor; diff --git a/src/render/shaders/glsl/tonemap.glsl b/src/render/shaders/glsl/tonemap.glsl index dcce45a67..a2db7e6e1 100644 --- a/src/render/shaders/glsl/tonemap.glsl +++ b/src/render/shaders/glsl/tonemap.glsl @@ -50,21 +50,16 @@ vec4 tonemap(vec4 color, mat3 dstXYZ, float maxLuminance, float dstMaxLuminance, float E = pow(clamp(ICtCp[0], 0.0, 1.0), PQ_INV_M2); float luminance = pow((max(E - PQ_C1, 0.0)) / (PQ_C2 - PQ_C3 * E), PQ_INV_M1) * HDR_MAX_LUMINANCE; - float linearPart = min(luminance, dstRefLuminance); - float luminanceAboveRef = max(luminance - dstRefLuminance, 0.0); - float maxExcessLuminance = max(maxLuminance - dstRefLuminance, 1.0); - float shoulder = log((luminanceAboveRef / maxExcessLuminance + 1.0) * (M_E - 1.0)); - float mappedHigh = shoulder * (dstMaxLuminance - dstRefLuminance); - float newLum = clamp(linearPart + mappedHigh, 0.0, dstMaxLuminance); + float luminanceRatio = max(luminance / dstRefLuminance, 0.0); + float srcScale = maxLuminance / dstRefLuminance; + float dstScale = dstMaxLuminance / dstRefLuminance; + float v = (dstScale * (1.0 + srcScale) - srcScale) / pow(srcScale, 2.0); + float newLuminance = (luminanceRatio * (1.0 + luminanceRatio * v) / (1.0 + luminanceRatio)) * dstRefLuminance; - // scale src to dst reference - float refScale = dstRefLuminance / srcRefLuminance; + E = pow(clamp(newLuminance / HDR_MAX_LUMINANCE, 0.0, 1.0), PQ_M1); + ICtCp[0] = pow((PQ_C1 + PQ_C2 * E) / (1.0 + PQ_C3 * E), PQ_M2); - // kind of works but doesn't use newLum at all - return vec4(fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb * HDR_MAX_LUMINANCE * refScale, color[3]); - // breaks with overriden monitor luminances. might be caused by incorrect imput values - // @gulafaran - // vec3 outRGB = fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb; - // outRGB *= (newLum / max(luminance, 0.0001)); // actually apply the tone mapping - // return vec4(clamp(outRGB * HDR_MAX_LUMINANCE * refScale, 0.0, dstMaxLuminance), color[3]); + color = vec4(fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb * HDR_MAX_LUMINANCE, color[3]); + + return clamp(color, 0.0, dstMaxLuminance); }