diff options
| author | UjinT34 <41110182+UjinT34@users.noreply.github.com> | 2025-03-14 02:15:18 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-03-14 00:15:18 +0100 |
| commit | 8c97cb7858e5d6c35d1a055930904346fb4248db (patch) | |
| tree | 87d7e30393f9177d035183f273be54f03b9451d5 /src/render | |
| parent | e86d3a14e46d19d8a47f8ceb6410546715d45f10 (diff) | |
renderer: add simple color management (#9506)
Adds proper color management and transformations for CM surfaces.
Diffstat (limited to 'src/render')
| -rw-r--r-- | src/render/OpenGL.cpp | 98 | ||||
| -rw-r--r-- | src/render/OpenGL.hpp | 66 | ||||
| -rw-r--r-- | src/render/Renderer.cpp | 95 | ||||
| -rw-r--r-- | src/render/Shader.hpp | 11 | ||||
| -rw-r--r-- | src/render/pass/SurfacePassElement.cpp | 2 | ||||
| -rw-r--r-- | src/render/shaders/CM.frag | 439 | ||||
| -rw-r--r-- | src/render/shaders/Textures.hpp | 4 |
7 files changed, 614 insertions, 101 deletions
diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 17224523..a8a38bac 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -9,6 +9,7 @@ #include "../desktop/LayerSurface.hpp" #include "../protocols/LayerShell.hpp" #include "../protocols/core/Compositor.hpp" +#include "../protocols/ColorManagement.hpp" #include "../managers/HookSystemManager.hpp" #include "../managers/input/InputManager.hpp" #include "pass/TexPassElement.hpp" @@ -20,6 +21,7 @@ #include <gbm.h> #include <filesystem> using namespace Hyprutils::OS; +using namespace NColorManagement; const std::vector<const char*> ASSET_PATHS = { #ifdef DATAROOTDIR @@ -866,6 +868,39 @@ void CHyprOpenGLImpl::initShaders() { m_RenderData.pCurrentMonData->m_shQUAD.radius = glGetUniformLocation(prog, "radius"); m_RenderData.pCurrentMonData->m_shQUAD.roundingPower = glGetUniformLocation(prog, "roundingPower"); +#ifndef GLES2 + prog = createProgram(TEXVERTSRC320, TEXFRAGSRCCM); + m_RenderData.pCurrentMonData->m_shCM.program = prog; + m_RenderData.pCurrentMonData->m_shCM.proj = glGetUniformLocation(prog, "proj"); + m_RenderData.pCurrentMonData->m_shCM.tex = glGetUniformLocation(prog, "tex"); + m_RenderData.pCurrentMonData->m_shCM.texType = glGetUniformLocation(prog, "texType"); + m_RenderData.pCurrentMonData->m_shCM.skipCM = glGetUniformLocation(prog, "skipCM"); + m_RenderData.pCurrentMonData->m_shCM.sourceTF = glGetUniformLocation(prog, "sourceTF"); + m_RenderData.pCurrentMonData->m_shCM.targetTF = glGetUniformLocation(prog, "targetTF"); + m_RenderData.pCurrentMonData->m_shCM.sourcePrimaries = glGetUniformLocation(prog, "sourcePrimaries"); + m_RenderData.pCurrentMonData->m_shCM.targetPrimaries = glGetUniformLocation(prog, "targetPrimaries"); + m_RenderData.pCurrentMonData->m_shCM.maxLuminance = glGetUniformLocation(prog, "maxLuminance"); + m_RenderData.pCurrentMonData->m_shCM.dstMaxLuminance = glGetUniformLocation(prog, "dstMaxLuminance"); + m_RenderData.pCurrentMonData->m_shCM.dstRefLuminance = glGetUniformLocation(prog, "dstRefLuminance"); + m_RenderData.pCurrentMonData->m_shCM.sdrSaturation = glGetUniformLocation(prog, "sdrSaturation"); + m_RenderData.pCurrentMonData->m_shCM.sdrBrightness = glGetUniformLocation(prog, "sdrBrightnessMultiplier"); + m_RenderData.pCurrentMonData->m_shCM.alphaMatte = glGetUniformLocation(prog, "texMatte"); + m_RenderData.pCurrentMonData->m_shCM.alpha = glGetUniformLocation(prog, "alpha"); + m_RenderData.pCurrentMonData->m_shCM.texAttrib = glGetAttribLocation(prog, "texcoord"); + m_RenderData.pCurrentMonData->m_shCM.matteTexAttrib = glGetAttribLocation(prog, "texcoordMatte"); + m_RenderData.pCurrentMonData->m_shCM.posAttrib = glGetAttribLocation(prog, "pos"); + m_RenderData.pCurrentMonData->m_shCM.discardOpaque = glGetUniformLocation(prog, "discardOpaque"); + m_RenderData.pCurrentMonData->m_shCM.discardAlpha = glGetUniformLocation(prog, "discardAlpha"); + m_RenderData.pCurrentMonData->m_shCM.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue"); + m_RenderData.pCurrentMonData->m_shCM.topLeft = glGetUniformLocation(prog, "topLeft"); + m_RenderData.pCurrentMonData->m_shCM.fullSize = glGetUniformLocation(prog, "fullSize"); + m_RenderData.pCurrentMonData->m_shCM.radius = glGetUniformLocation(prog, "radius"); + m_RenderData.pCurrentMonData->m_shCM.roundingPower = glGetUniformLocation(prog, "roundingPower"); + m_RenderData.pCurrentMonData->m_shCM.applyTint = glGetUniformLocation(prog, "applyTint"); + m_RenderData.pCurrentMonData->m_shCM.tint = glGetUniformLocation(prog, "tint"); + m_RenderData.pCurrentMonData->m_shCM.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte"); +#endif + prog = createProgram(TEXVERTSRC, TEXFRAGSRCRGBA); m_RenderData.pCurrentMonData->m_shRGBA.program = prog; m_RenderData.pCurrentMonData->m_shRGBA.proj = glGetUniformLocation(prog, "proj"); @@ -1280,7 +1315,8 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP<CTexture> tex, const CB CBox newBox = box; m_RenderData.renderModif.applyToBox(newBox); - static auto PDT = CConfigValue<Hyprlang::INT>("debug:damage_tracking"); + static auto PDT = CConfigValue<Hyprlang::INT>("debug:damage_tracking"); + static auto PPASS = CConfigValue<Hyprlang::INT>("render:cm_fs_passthrough"); // get the needed transform for this texture const bool TRANSFORMS_MATCH = wlTransformToHyprutils(m_RenderData.pMonitor->transform) == tex->m_eTransform; // FIXME: combine them properly!!! @@ -1304,6 +1340,8 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP<CTexture> tex, const CB const bool CRASHING = m_bApplyFinalShader && g_pHyprRenderer->m_bCrashingInProgress; + auto texType = tex->m_iType; + if (CRASHING) { shader = &m_RenderData.pCurrentMonData->m_shGLITCH; usingFinalShader = true; @@ -1316,16 +1354,27 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP<CTexture> tex, const CB usingFinalShader = true; } else { switch (tex->m_iType) { +#ifdef GLES2 case TEXTURE_RGBA: shader = &m_RenderData.pCurrentMonData->m_shRGBA; break; case TEXTURE_RGBX: shader = &m_RenderData.pCurrentMonData->m_shRGBX; break; - case TEXTURE_EXTERNAL: shader = &m_RenderData.pCurrentMonData->m_shEXT; break; +#else + case TEXTURE_RGBA: + case TEXTURE_RGBX: shader = &m_RenderData.pCurrentMonData->m_shCM; break; +#endif + case TEXTURE_EXTERNAL: shader = &m_RenderData.pCurrentMonData->m_shEXT; break; // might be unused default: RASSERT(false, "tex->m_iTarget unsupported!"); } } } - if (m_RenderData.currentWindow && m_RenderData.currentWindow->m_sWindowData.RGBX.valueOrDefault()) + if (m_RenderData.currentWindow && m_RenderData.currentWindow->m_sWindowData.RGBX.valueOrDefault()) { +#ifdef GLES2 shader = &m_RenderData.pCurrentMonData->m_shRGBX; +#else + shader = &m_RenderData.pCurrentMonData->m_shCM; +#endif + texType = TEXTURE_RGBX; + } glActiveTexture(GL_TEXTURE0); glBindTexture(tex->m_iTarget, tex->m_iTexID); @@ -1350,6 +1399,49 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP<CTexture> tex, const CB glUniformMatrix3fv(shader->proj, 1, GL_FALSE, glMatrix.getMatrix().data()); #endif glUniform1i(shader->tex, 0); +#ifndef GLES2 + if (!usingFinalShader && (texType == TEXTURE_RGBA || texType == TEXTURE_RGBX)) { + const bool skipCM = *PPASS && m_RenderData.pMonitor->activeWorkspace && m_RenderData.pMonitor->activeWorkspace->m_bHasFullscreenWindow && + m_RenderData.pMonitor->activeWorkspace->m_efFullscreenMode == FSMODE_FULLSCREEN; + glUniform1i(shader->texType, texType); + glUniform1i(shader->skipCM, skipCM); + if (!skipCM) { + const auto imageDescription = + m_RenderData.surface.valid() && m_RenderData.surface->colorManagement.valid() ? m_RenderData.surface->colorManagement->imageDescription() : SImageDescription{}; + glUniform1i(shader->sourceTF, imageDescription.transferFunction); + glUniform1i(shader->targetTF, m_RenderData.pMonitor->imageDescription.transferFunction); + const auto sourcePrimaries = + imageDescription.primariesNameSet || imageDescription.primaries == SPCPRimaries{} ? getPrimaries(imageDescription.primariesNamed) : imageDescription.primaries; + const auto targetPrimaries = m_RenderData.pMonitor->imageDescription.primariesNameSet || m_RenderData.pMonitor->imageDescription.primaries == SPCPRimaries{} ? + getPrimaries(m_RenderData.pMonitor->imageDescription.primariesNamed) : + m_RenderData.pMonitor->imageDescription.primaries; + + const GLfloat glSourcePrimaries[8] = { + sourcePrimaries.red.x, sourcePrimaries.red.y, sourcePrimaries.green.x, sourcePrimaries.green.y, + sourcePrimaries.blue.x, sourcePrimaries.blue.y, sourcePrimaries.white.x, sourcePrimaries.white.y, + }; + const GLfloat glTargetPrimaries[8] = { + targetPrimaries.red.x, targetPrimaries.red.y, targetPrimaries.green.x, targetPrimaries.green.y, + targetPrimaries.blue.x, targetPrimaries.blue.y, targetPrimaries.white.x, targetPrimaries.white.y, + }; + glUniformMatrix4x2fv(shader->sourcePrimaries, 1, false, glSourcePrimaries); + glUniformMatrix4x2fv(shader->targetPrimaries, 1, false, glTargetPrimaries); + + const float maxLuminance = imageDescription.luminances.max > 0 ? imageDescription.luminances.max : imageDescription.luminances.reference; + glUniform1f(shader->maxLuminance, maxLuminance * m_RenderData.pMonitor->imageDescription.luminances.reference / imageDescription.luminances.reference); + glUniform1f(shader->dstMaxLuminance, m_RenderData.pMonitor->imageDescription.luminances.max > 0 ? m_RenderData.pMonitor->imageDescription.luminances.max : 10000); + glUniform1f(shader->dstRefLuminance, m_RenderData.pMonitor->imageDescription.luminances.reference); + glUniform1f(shader->sdrSaturation, + m_RenderData.pMonitor->sdrSaturation > 0 && m_RenderData.pMonitor->imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? + m_RenderData.pMonitor->sdrSaturation : + 1.0f); + glUniform1f(shader->sdrBrightness, + m_RenderData.pMonitor->sdrBrightness > 0 && m_RenderData.pMonitor->imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? + m_RenderData.pMonitor->sdrBrightness : + 1.0f); + } + } +#endif if ((usingFinalShader && *PDT == 0) || CRASHING) { glUniform1f(shader->time, m_tGlobalTimer.getSeconds() - shader->initialTime); diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp index cf1d2549..11079591 100644 --- a/src/render/OpenGL.hpp +++ b/src/render/OpenGL.hpp @@ -27,6 +27,7 @@ #include <hyprutils/os/FileDescriptor.hpp> #include "../debug/TracyDefines.hpp" +#include "../protocols/core/Compositor.hpp" struct gbm_device; class CHyprRenderer; @@ -105,41 +106,42 @@ struct SMonitorRenderData { CShader m_shSHADOW; CShader m_shBORDER1; CShader m_shGLITCH; - // + CShader m_shCM; }; struct SCurrentRenderData { - PHLMONITORREF pMonitor; - Mat3x3 projection; - Mat3x3 savedProjection; - Mat3x3 monitorProjection; - - SMonitorRenderData* pCurrentMonData = nullptr; - CFramebuffer* currentFB = nullptr; // current rendering to - CFramebuffer* mainFB = nullptr; // main to render to - CFramebuffer* outFB = nullptr; // out to render to (if offloaded, etc) - - CRegion damage; - CRegion finalDamage; // damage used for funal off -> main - - SRenderModifData renderModif; - float mouseZoomFactor = 1.f; - bool mouseZoomUseMouse = true; // true by default - bool useNearestNeighbor = false; - bool blockScreenShader = false; - bool simplePass = false; - - Vector2D primarySurfaceUVTopLeft = Vector2D(-1, -1); - Vector2D primarySurfaceUVBottomRight = Vector2D(-1, -1); - - CBox clipBox = {}; // scaled coordinates - CRegion clipRegion; - - uint32_t discardMode = DISCARD_OPAQUE; - float discardOpacity = 0.f; - - PHLLSREF currentLS; - PHLWINDOWREF currentWindow; + PHLMONITORREF pMonitor; + Mat3x3 projection; + Mat3x3 savedProjection; + Mat3x3 monitorProjection; + + SMonitorRenderData* pCurrentMonData = nullptr; + CFramebuffer* currentFB = nullptr; // current rendering to + CFramebuffer* mainFB = nullptr; // main to render to + CFramebuffer* outFB = nullptr; // out to render to (if offloaded, etc) + + CRegion damage; + CRegion finalDamage; // damage used for funal off -> main + + SRenderModifData renderModif; + float mouseZoomFactor = 1.f; + bool mouseZoomUseMouse = true; // true by default + bool useNearestNeighbor = false; + bool blockScreenShader = false; + bool simplePass = false; + + Vector2D primarySurfaceUVTopLeft = Vector2D(-1, -1); + Vector2D primarySurfaceUVBottomRight = Vector2D(-1, -1); + + CBox clipBox = {}; // scaled coordinates + CRegion clipRegion; + + uint32_t discardMode = DISCARD_OPAQUE; + float discardOpacity = 0.f; + + PHLLSREF currentLS; + PHLWINDOWREF currentWindow; + WP<CWLSurfaceResource> surface; }; class CEGLSync { diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 3175b915..88de1ada 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -32,9 +32,9 @@ #include "pass/RendererHintsPassElement.hpp" #include "pass/SurfacePassElement.hpp" #include "debug/Log.hpp" -#include "protocols/ColorManagement.hpp" +#include "../protocols/ColorManagement.hpp" #if AQUAMARINE_VERSION_NUMBER > 702 // >0.7.2 -#include "protocols/types/ContentType.hpp" +#include "../protocols/types/ContentType.hpp" #endif #include <hyprutils/utils/ScopeGuard.hpp> @@ -1409,78 +1409,40 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor) { } } -static const auto BT709 = Aquamarine::IOutput::SChromaticityCoords{ - .red = Aquamarine::IOutput::xy{.x = 0.64, .y = 0.33}, - .green = Aquamarine::IOutput::xy{.x = 0.30, .y = 0.60}, - .blue = Aquamarine::IOutput::xy{.x = 0.15, .y = 0.06}, - .white = Aquamarine::IOutput::xy{.x = 0.3127, .y = 0.3290}, -}; +static const hdr_output_metadata NO_HDR_METADATA = {.hdmi_metadata_type1 = hdr_metadata_infoframe{.eotf = 0}}; -static hdr_output_metadata createHDRMetadata(uint8_t eotf, Aquamarine::IOutput::SParsedEDID edid) { - if (eotf == 0) - return hdr_output_metadata{.hdmi_metadata_type1 = hdr_metadata_infoframe{.eotf = 0}}; // empty metadata for SDR - - const auto toNits = [](float value) { return uint16_t(std::round(value)); }; - const auto to16Bit = [](float value) { return uint16_t(std::round(value * 50000)); }; - const auto colorimetry = edid.chromaticityCoords.value_or(BT709); - - Debug::log(TRACE, "ColorManagement primaries {},{} {},{} {},{} {},{}", colorimetry.red.x, colorimetry.red.y, colorimetry.green.x, colorimetry.green.y, colorimetry.blue.x, - colorimetry.blue.y, colorimetry.white.x, colorimetry.white.y); - Debug::log(TRACE, "ColorManagement max avg {}, min {}, max {}", edid.hdrMetadata->desiredMaxFrameAverageLuminance, edid.hdrMetadata->desiredContentMinLuminance, - edid.hdrMetadata->desiredContentMaxLuminance); - return hdr_output_metadata{ - .metadata_type = 0, - .hdmi_metadata_type1 = - hdr_metadata_infoframe{ - .eotf = eotf, - .metadata_type = 0, - .display_primaries = - { - {.x = to16Bit(colorimetry.red.x), .y = to16Bit(colorimetry.red.y)}, - {.x = to16Bit(colorimetry.green.x), .y = to16Bit(colorimetry.green.y)}, - {.x = to16Bit(colorimetry.blue.x), .y = to16Bit(colorimetry.blue.y)}, - }, - .white_point = {.x = to16Bit(colorimetry.white.x), .y = to16Bit(colorimetry.white.y)}, - .max_display_mastering_luminance = toNits(edid.hdrMetadata->desiredMaxFrameAverageLuminance), - .min_display_mastering_luminance = toNits(edid.hdrMetadata->desiredContentMinLuminance * 10000), - .max_cll = toNits(edid.hdrMetadata->desiredMaxFrameAverageLuminance), - .max_fall = toNits(edid.hdrMetadata->desiredMaxFrameAverageLuminance), - }, - }; -} - -static hdr_output_metadata createHDRMetadata(SImageDescription settings, Aquamarine::IOutput::SParsedEDID edid) { +static hdr_output_metadata createHDRMetadata(SImageDescription settings, Aquamarine::IOutput::SParsedEDID edid) { if (settings.transferFunction != CM_TRANSFER_FUNCTION_ST2084_PQ) - return hdr_output_metadata{.hdmi_metadata_type1 = hdr_metadata_infoframe{.eotf = 0}}; // empty metadata for SDR + return NO_HDR_METADATA; // empty metadata for SDR const auto toNits = [](uint32_t value) { return uint16_t(std::round(value)); }; const auto to16Bit = [](uint32_t value) { return uint16_t(std::round(value * 50000)); }; - auto colorimetry = settings.primaries; + auto colorimetry = settings.primariesNameSet || settings.primaries == SPCPRimaries{} ? getPrimaries(settings.primariesNamed) : settings.primaries; auto luminances = settings.masteringLuminances.max > 0 ? - settings.masteringLuminances : - SImageDescription::SPCMasteringLuminances{.min = edid.hdrMetadata->desiredContentMinLuminance, .max = edid.hdrMetadata->desiredContentMaxLuminance}; + settings.masteringLuminances : + SImageDescription::SPCMasteringLuminances{.min = edid.hdrMetadata->desiredContentMinLuminance, .max = edid.hdrMetadata->desiredContentMaxLuminance}; Debug::log(TRACE, "ColorManagement primaries {},{} {},{} {},{} {},{}", colorimetry.red.x, colorimetry.red.y, colorimetry.green.x, colorimetry.green.y, colorimetry.blue.x, - colorimetry.blue.y, colorimetry.white.x, colorimetry.white.y); + colorimetry.blue.y, colorimetry.white.x, colorimetry.white.y); Debug::log(TRACE, "ColorManagement min {}, max {}, cll {}, fall {}", luminances.min, luminances.max, settings.maxCLL, settings.maxFALL); return hdr_output_metadata{ - .metadata_type = 0, - .hdmi_metadata_type1 = + .metadata_type = 0, + .hdmi_metadata_type1 = hdr_metadata_infoframe{ - .eotf = 2, - .metadata_type = 0, - .display_primaries = - { + .eotf = 2, + .metadata_type = 0, + .display_primaries = + { {.x = to16Bit(colorimetry.red.x), .y = to16Bit(colorimetry.red.y)}, {.x = to16Bit(colorimetry.green.x), .y = to16Bit(colorimetry.green.y)}, {.x = to16Bit(colorimetry.blue.x), .y = to16Bit(colorimetry.blue.y)}, }, - .white_point = {.x = to16Bit(colorimetry.white.x), .y = to16Bit(colorimetry.white.y)}, - .max_display_mastering_luminance = toNits(luminances.max), - .min_display_mastering_luminance = toNits(luminances.min * 10000), - .max_cll = toNits(settings.maxCLL), - .max_fall = toNits(settings.maxFALL), + .white_point = {.x = to16Bit(colorimetry.white.x), .y = to16Bit(colorimetry.white.y)}, + .max_display_mastering_luminance = toNits(luminances.max), + .min_display_mastering_luminance = toNits(luminances.min * 10000), + .max_cll = toNits(settings.maxCLL), + .max_fall = toNits(settings.maxFALL), }, }; } @@ -1495,12 +1457,14 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { if (inFD.isValid()) pMonitor->output->state->setExplicitInFence(inFD.get()); - static auto PHDR = CConfigValue<Hyprlang::INT>("experimental:hdr"); + static auto PPASS = CConfigValue<Hyprlang::INT>("render:cm_fs_passthrough"); + const bool PHDR = pMonitor->imageDescription.transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ; const bool SUPPORTSPQ = pMonitor->output->parsedEDID.hdrMetadata.has_value() ? pMonitor->output->parsedEDID.hdrMetadata->supportsPQ : false; Debug::log(TRACE, "ColorManagement supportsBT2020 {}, supportsPQ {}", pMonitor->output->parsedEDID.supportsBT2020, SUPPORTSPQ); + if (pMonitor->output->parsedEDID.supportsBT2020 && SUPPORTSPQ) { - if (pMonitor->activeWorkspace && pMonitor->activeWorkspace->m_bHasFullscreenWindow && pMonitor->activeWorkspace->m_efFullscreenMode == FSMODE_FULLSCREEN) { + if (*PPASS && pMonitor->activeWorkspace && pMonitor->activeWorkspace->m_bHasFullscreenWindow && pMonitor->activeWorkspace->m_efFullscreenMode == FSMODE_FULLSCREEN) { const auto WINDOW = pMonitor->activeWorkspace->getFullscreenWindow(); const auto ROOT_SURF = WINDOW->m_pWLSurface->resource(); const auto SURF = @@ -1512,18 +1476,17 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { SURF->colorManagement->setHDRMetadata(createHDRMetadata(SURF->colorManagement->imageDescription(), pMonitor->output->parsedEDID)); if (needsHdrMetadataUpdate) pMonitor->output->state->setHDRMetadata(SURF->colorManagement->hdrMetadata()); - } else if ((pMonitor->output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2) != *PHDR) - pMonitor->output->state->setHDRMetadata(*PHDR ? createHDRMetadata(2, pMonitor->output->parsedEDID) : createHDRMetadata(0, pMonitor->output->parsedEDID)); + } else if ((pMonitor->output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2) != PHDR) + pMonitor->output->state->setHDRMetadata(PHDR ? createHDRMetadata(pMonitor->imageDescription, pMonitor->output->parsedEDID) : NO_HDR_METADATA); pMonitor->m_previousFSWindow = WINDOW; } else { - if ((pMonitor->output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2) != *PHDR) - pMonitor->output->state->setHDRMetadata(*PHDR ? createHDRMetadata(2, pMonitor->output->parsedEDID) : createHDRMetadata(0, pMonitor->output->parsedEDID)); + if ((pMonitor->output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2) != PHDR) + pMonitor->output->state->setHDRMetadata(PHDR ? createHDRMetadata(pMonitor->imageDescription, pMonitor->output->parsedEDID) : NO_HDR_METADATA); pMonitor->m_previousFSWindow.reset(); } } - static auto PWIDE = CConfigValue<Hyprlang::INT>("experimental:wide_color_gamut"); - const bool needsWCG = *PWIDE || pMonitor->output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2; + const bool needsWCG = pMonitor->output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2 || pMonitor->imageDescription.primariesNamed == CM_PRIMARIES_BT2020; if (pMonitor->output->state->state().wideColorGamut != needsWCG) { Debug::log(TRACE, "Setting wide color gamut {}", needsWCG ? "on" : "off"); pMonitor->output->state->setWideColorGamut(needsWCG); diff --git a/src/render/Shader.hpp b/src/render/Shader.hpp index 666ee9d4..bcc25268 100644 --- a/src/render/Shader.hpp +++ b/src/render/Shader.hpp @@ -11,6 +11,17 @@ class CShader { GLint proj = -1; GLint color = -1; GLint alphaMatte = -1; + GLint texType = -1; + GLint skipCM = -1; + GLint sourceTF = -1; + GLint targetTF = -1; + GLint sourcePrimaries = -1; + GLint targetPrimaries = -1; + GLint maxLuminance = -1; + GLint dstMaxLuminance = -1; + GLint dstRefLuminance = -1; + GLint sdrSaturation = -1; // sdr -> hdr saturation + GLint sdrBrightness = -1; // sdr -> hdr brightness multiplier GLint tex = -1; GLint alpha = -1; GLint posAttrib = -1; diff --git a/src/render/pass/SurfacePassElement.cpp b/src/render/pass/SurfacePassElement.cpp index a5d3d7c0..46c6d221 100644 --- a/src/render/pass/SurfacePassElement.cpp +++ b/src/render/pass/SurfacePassElement.cpp @@ -18,6 +18,7 @@ CSurfacePassElement::CSurfacePassElement(const CSurfacePassElement::SRenderData& void CSurfacePassElement::draw(const CRegion& damage) { g_pHyprOpenGL->m_RenderData.currentWindow = data.pWindow; + g_pHyprOpenGL->m_RenderData.surface = data.surface; g_pHyprOpenGL->m_RenderData.currentLS = data.pLS; g_pHyprOpenGL->m_RenderData.clipBox = data.clipBox; g_pHyprOpenGL->m_RenderData.discardMode = data.discardMode; @@ -36,6 +37,7 @@ void CSurfacePassElement::draw(const CRegion& damage) { g_pHyprOpenGL->m_RenderData.useNearestNeighbor = false; g_pHyprOpenGL->m_bEndFrame = false; g_pHyprOpenGL->m_RenderData.currentWindow.reset(); + g_pHyprOpenGL->m_RenderData.surface.reset(); g_pHyprOpenGL->m_RenderData.currentLS.reset(); }}; diff --git a/src/render/shaders/CM.frag b/src/render/shaders/CM.frag new file mode 100644 index 00000000..dd695d44 --- /dev/null +++ b/src/render/shaders/CM.frag @@ -0,0 +1,439 @@ +R"#( +#version 320 es +//#extension GL_OES_EGL_image_external : require + +precision highp float; +in vec2 v_texcoord; +uniform sampler2D tex; +//uniform samplerExternalOES texture0; + +uniform int texType; // eTextureType: 0 - rgba, 1 - rgbx, 2 - ext +uniform int skipCM; +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction +uniform mat4x2 sourcePrimaries; +uniform mat4x2 targetPrimaries; +uniform float maxLuminance; +uniform float dstMaxLuminance; +uniform float dstRefLuminance; +uniform float sdrSaturation; +uniform float sdrBrightnessMultiplier; + +uniform float alpha; + +uniform vec2 topLeft; +uniform vec2 fullSize; +uniform float radius; +uniform float roundingPower; + +uniform int discardOpaque; +uniform int discardAlpha; +uniform float discardAlphaValue; + +uniform int applyTint; +uniform vec3 tint; + +//enum eTransferFunction +#define CM_TRANSFER_FUNCTION_BT1886 1 +#define CM_TRANSFER_FUNCTION_GAMMA22 2 +#define CM_TRANSFER_FUNCTION_GAMMA28 3 +#define CM_TRANSFER_FUNCTION_ST240 4 +#define CM_TRANSFER_FUNCTION_EXT_LINEAR 5 +#define CM_TRANSFER_FUNCTION_LOG_100 6 +#define CM_TRANSFER_FUNCTION_LOG_316 7 +#define CM_TRANSFER_FUNCTION_XVYCC 8 +#define CM_TRANSFER_FUNCTION_SRGB 9 +#define CM_TRANSFER_FUNCTION_EXT_SRGB 10 +#define CM_TRANSFER_FUNCTION_ST2084_PQ 11 +#define CM_TRANSFER_FUNCTION_ST428 12 +#define CM_TRANSFER_FUNCTION_HLG 13 + +// sRGB constants +#define SRGB_POW 2.4 +#define SRGB_INV_POW (1.0 / SRGB_POW) +#define SRGB_D_CUT 0.04045 +#define SRGB_E_CUT 0.0031308 +#define SRGB_LO 12.92 +#define SRGB_HI 1.055 +#define SRGB_HI_ADD 0.055 + +// 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 (sqrt(3.0) * pow(HLG_D_CUT, 0.5)) +#define HLG_A 0.17883277 +#define HLG_B 0.28466892 +#define HLG_C 0.55991073 + +#define SDR_MIN_LUMINANCE 0.2 +#define SDR_MAX_LUMINANCE 80.0 +#define HDR_MIN_LUMINANCE 0.005 +#define HDR_MAX_LUMINANCE 10000.0 +#define HLG_MAX_LUMINANCE 1000.0 + +// smoothing constant for the edge: more = blurrier, but smoother +#define M_PI 3.1415926535897932384626433832795 +#define M_E 2.718281828459045 +#define SMOOTHING_CONSTANT (M_PI / 5.34665792551) + +vec4 rounding(vec4 color) { + highp vec2 pixCoord = vec2(gl_FragCoord); + pixCoord -= topLeft + fullSize * 0.5; + pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; + pixCoord -= fullSize * 0.5 - radius; + pixCoord += vec2(1.0, 1.0) / fullSize; // center the pix dont make it top-left + + if (pixCoord.x + pixCoord.y > radius) { + float dist = pow(pow(pixCoord.x, roundingPower) + pow(pixCoord.y, roundingPower), 1.0/roundingPower); + + if (dist > radius + SMOOTHING_CONSTANT) + discard; + + float normalized = 1.0 - smoothstep(0.0, 1.0, (dist - radius + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); + + color *= normalized; + } + + return color; +} + +vec3 xy2xyz(vec2 xy) { + if (xy.y == 0.0) + return vec3(0.0, 0.0, 0.0); + + return vec3(xy.x / xy.y, 1.0, (1.0 - xy.x - xy.y) / xy.y); +} + +vec4 saturate(vec4 color, mat3 primaries, float saturation) { + if (saturation == 1.0) + return color; + vec3 brightness = vec3(primaries[1][0], primaries[1][1], primaries[1][2]); + float Y = dot(color.rgb, brightness); + return vec4(mix(vec3(Y), color.rgb, saturation), color[3]); +} + +vec3 toLinearRGB(vec3 color, int tf) { + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + return color; + + bvec3 isLow; + vec3 lo; + vec3 hi; + switch (tf) { + case CM_TRANSFER_FUNCTION_ST2084_PQ: + vec3 E = pow(clamp(color.rgb, vec3(0.0), vec3(1.0)), vec3(PQ_INV_M2)); + return pow( + (max(E - PQ_C1, vec3(0.0))) / (PQ_C2 - PQ_C3 * E), + vec3(PQ_INV_M1) + ); + case CM_TRANSFER_FUNCTION_GAMMA22: + return pow(max(color.rgb, vec3(0.0)), vec3(2.2)); + case CM_TRANSFER_FUNCTION_GAMMA28: + return pow(max(color.rgb, vec3(0.0)), vec3(2.8)); + case CM_TRANSFER_FUNCTION_HLG: + isLow = lessThanEqual(color.rgb, vec3(HLG_D_CUT)); + lo = sqrt(3.0) * pow(color.rgb, vec3(0.5)); + hi = HLG_A * log(12.0 * color.rgb - HLG_B) + HLG_C; + return mix(hi, lo, isLow); + case CM_TRANSFER_FUNCTION_BT1886: + case CM_TRANSFER_FUNCTION_ST240: + case CM_TRANSFER_FUNCTION_LOG_100: + case CM_TRANSFER_FUNCTION_LOG_316: + case CM_TRANSFER_FUNCTION_XVYCC: + case CM_TRANSFER_FUNCTION_EXT_SRGB: + case CM_TRANSFER_FUNCTION_ST428: + + case CM_TRANSFER_FUNCTION_SRGB: + default: + isLow = lessThanEqual(color.rgb, vec3(SRGB_D_CUT)); + lo = color.rgb / SRGB_LO; + hi = pow((color.rgb + SRGB_HI_ADD) / SRGB_HI, vec3(SRGB_POW)); + return mix(hi, lo, isLow); + } +} + +vec4 toLinear(vec4 color, int tf) { + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + return color; + + color.rgb /= max(color.a, 0.001); + color.rgb = toLinearRGB(color.rgb, tf); + color.rgb *= color.a; + return color; +} + +vec4 toNit(vec4 color, int tf) { + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + color.rgb = color.rgb * SDR_MAX_LUMINANCE; + else { + + switch (tf) { + case CM_TRANSFER_FUNCTION_ST2084_PQ: + color.rgb = color.rgb * (HDR_MAX_LUMINANCE - HDR_MIN_LUMINANCE) + HDR_MIN_LUMINANCE; break; + case CM_TRANSFER_FUNCTION_HLG: + color.rgb = color.rgb * (HLG_MAX_LUMINANCE - HDR_MIN_LUMINANCE) + HDR_MIN_LUMINANCE; break; + case CM_TRANSFER_FUNCTION_GAMMA22: + case CM_TRANSFER_FUNCTION_GAMMA28: + case CM_TRANSFER_FUNCTION_BT1886: + case CM_TRANSFER_FUNCTION_ST240: + case CM_TRANSFER_FUNCTION_LOG_100: + case CM_TRANSFER_FUNCTION_LOG_316: + case CM_TRANSFER_FUNCTION_XVYCC: + case CM_TRANSFER_FUNCTION_EXT_SRGB: + case CM_TRANSFER_FUNCTION_ST428: + case CM_TRANSFER_FUNCTION_SRGB: + default: + color.rgb = color.rgb * (SDR_MAX_LUMINANCE - SDR_MIN_LUMINANCE) + SDR_MIN_LUMINANCE; break; + } + } + return color; +} + +vec3 fromLinearRGB(vec3 color, int tf) { + bvec3 isLow; + vec3 lo; + vec3 hi; + + switch (tf) { + case CM_TRANSFER_FUNCTION_EXT_LINEAR: + return color; + case CM_TRANSFER_FUNCTION_ST2084_PQ: + vec3 E = pow(clamp(color.rgb, vec3(0.0), vec3(1.0)), vec3(PQ_M1)); + return pow( + (vec3(PQ_C1) + PQ_C2 * E) / (vec3(1.0) + PQ_C3 * E), + vec3(PQ_M2) + ); + break; + case CM_TRANSFER_FUNCTION_GAMMA22: + return pow(max(color.rgb, vec3(0.0)), vec3(1.0 / 2.2)); + case CM_TRANSFER_FUNCTION_GAMMA28: + return pow(max(color.rgb, vec3(0.0)), vec3(1.0 / 2.8)); + case CM_TRANSFER_FUNCTION_HLG: + isLow = lessThanEqual(color.rgb, vec3(HLG_E_CUT)); + lo = pow(color.rgb / sqrt(3.0), vec3(2.0)); + hi = (pow(vec3(M_E), (color.rgb - HLG_C) / HLG_A) + HLG_B) / 12.0; + return mix(hi, lo, isLow); + case CM_TRANSFER_FUNCTION_BT1886: + case CM_TRANSFER_FUNCTION_ST240: + case CM_TRANSFER_FUNCTION_LOG_100: + case CM_TRANSFER_FUNCTION_LOG_316: + case CM_TRANSFER_FUNCTION_XVYCC: + case CM_TRANSFER_FUNCTION_EXT_SRGB: + case CM_TRANSFER_FUNCTION_ST428: + + case CM_TRANSFER_FUNCTION_SRGB: + default: + isLow = lessThanEqual(color.rgb, vec3(SRGB_E_CUT)); + lo = color.rgb * SRGB_LO; + hi = pow(color.rgb, vec3(SRGB_INV_POW)) * SRGB_HI - SRGB_HI_ADD; + return mix(hi, lo, isLow); + } +} + +vec4 fromLinear(vec4 color, int tf) { + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + return color; + + color.rgb /= max(color.a, 0.001); + color.rgb = fromLinearRGB(color.rgb, tf); + color.rgb *= color.a; + return color; +} + +vec4 fromLinearNit(vec4 color, int tf) { + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + color.rgb = color.rgb / SDR_MAX_LUMINANCE; + else { + color.rgb /= max(color.a, 0.001); + + switch (tf) { + case CM_TRANSFER_FUNCTION_ST2084_PQ: + color.rgb = (color.rgb - HDR_MIN_LUMINANCE) / (HDR_MAX_LUMINANCE - HDR_MIN_LUMINANCE); break; + case CM_TRANSFER_FUNCTION_HLG: + color.rgb = (color.rgb - HDR_MIN_LUMINANCE) / (HLG_MAX_LUMINANCE - HDR_MIN_LUMINANCE); break; + case CM_TRANSFER_FUNCTION_GAMMA22: + case CM_TRANSFER_FUNCTION_GAMMA28: + case CM_TRANSFER_FUNCTION_BT1886: + case CM_TRANSFER_FUNCTION_ST240: + case CM_TRANSFER_FUNCTION_LOG_100: + case CM_TRANSFER_FUNCTION_LOG_316: + case CM_TRANSFER_FUNCTION_XVYCC: + case CM_TRANSFER_FUNCTION_EXT_SRGB: + case CM_TRANSFER_FUNCTION_ST428: + case CM_TRANSFER_FUNCTION_SRGB: + default: + color.rgb = (color.rgb - SDR_MIN_LUMINANCE) / (SDR_MAX_LUMINANCE - SDR_MIN_LUMINANCE); break; + } + + color.rgb = fromLinearRGB(color.rgb, tf); + + color.rgb *= color.a; + } + return color; +} + +mat3 primaries2xyz(mat4x2 primaries) { + vec3 r = xy2xyz(primaries[0]); + vec3 g = xy2xyz(primaries[1]); + vec3 b = xy2xyz(primaries[2]); + vec3 w = xy2xyz(primaries[3]); + + mat3 invMat = inverse( + mat3( + r.x, r.y, r.z, + g.x, g.y, g.z, + b.x, b.y, b.z + ) + ); + + vec3 s = invMat * w; + + return mat3( + s.r * r.x, s.r * r.y, s.r * r.z, + s.g * g.x, s.g * g.y, s.g * g.z, + s.b * b.x, s.b * b.y, s.b * b.z + ); +} + +// const vec2 D65 = vec2(0.3127, 0.3290); +const mat3 Bradford = mat3( + 0.8951, 0.2664, -0.1614, + -0.7502, 1.7135, 0.0367, + 0.0389, -0.0685, 1.0296 +); +const mat3 BradfordInv = inverse(Bradford); + +mat3 adaptWhite(vec2 src, vec2 dst) { + if (src == dst) + return mat3( + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0 + ); + + vec3 srcXYZ = xy2xyz(src); + vec3 dstXYZ = xy2xyz(dst); + vec3 factors = (Bradford * dstXYZ) / (Bradford * srcXYZ); + + return BradfordInv * mat3( + factors.x, 0.0, 0.0, + 0.0, factors.y, 0.0, + 0.0, 0.0, factors.z + ) * Bradford; +} + +vec4 convertPrimaries(vec4 color, mat3 src, vec2 srcWhite, mat3 dst, vec2 dstWhite) { + mat3 convMat = inverse(dst) * adaptWhite(srcWhite, dstWhite) * src; + return vec4(convMat * color.rgb, color[3]); +} + +const mat3 BT2020toLMS = mat3( + 0.3592, 0.6976, -0.0358, + -0.1922, 1.1004, 0.0755, + 0.0070, 0.0749, 0.8434 +); +const mat3 LMStoBT2020 = inverse(BT2020toLMS); + +const mat3 ICtCpPQ = transpose(mat3( + 2048.0, 2048.0, 0.0, + 6610.0, -13613.0, 7003.0, + 17933.0, -17390.0, -543.0 +) / 4096.0); +const mat3 ICtCpPQInv = inverse(ICtCpPQ); + +const mat3 ICtCpHLG = transpose(mat3( + 2048.0, 2048.0, 0.0, + 3625.0, -7465.0, 3840.0, + 9500.0, -9212.0, -288.0 +) / 4096.0); +const mat3 ICtCpHLGInv = inverse(ICtCpHLG); + +vec4 tonemap(vec4 color, mat3 dstXYZ) { + if (maxLuminance < dstMaxLuminance * 1.01) + return vec4(clamp(color.rgb, vec3(0.0), vec3(dstMaxLuminance)), color[3]); + + mat3 toLMS = BT2020toLMS * dstXYZ; + mat3 fromLMS = inverse(dstXYZ) * LMStoBT2020; + + vec3 lms = fromLinear(vec4((toLMS * color.rgb) / HDR_MAX_LUMINANCE, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb; + vec3 ICtCp = ICtCpPQ * lms; + + 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 srcScale = maxLuminance / dstRefLuminance; + float dstScale = dstMaxLuminance / dstRefLuminance; + + float minScale = min(srcScale, 1.5); + float dimming = 1.0 / clamp(minScale / dstScale, 1.0, minScale); + float refLuminance = dstRefLuminance * dimming; + + float low = min(luminance * dimming, refLuminance); + float highlight = clamp((luminance / dstRefLuminance - 1.0) / (srcScale - 1.0), 0.0, 1.0); + float high = log(highlight * (M_E - 1.0) + 1.0) * (dstMaxLuminance - refLuminance); + luminance = low + high; + + E = pow(clamp(ICtCp[0], 0.0, 1.0), PQ_M1); + ICtCp[0] = pow( + (PQ_C1 + PQ_C2 * E) / (1.0 + PQ_C3 * E), + PQ_M2 + ) / HDR_MAX_LUMINANCE; + return vec4(fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb * HDR_MAX_LUMINANCE, color[3]); +} + +layout(location = 0) out vec4 fragColor; +void main() { + vec4 pixColor; + if (texType == 1) + pixColor = vec4(texture(tex, v_texcoord).rgb, 1.0); +// else if (texType == 2) +// pixColor = texture(texture0, v_texcoord); + else // assume rgba + pixColor = texture(tex, v_texcoord); + + if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) + discard; + + if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue) + discard; + + if (skipCM == 0) { + pixColor.rgb /= max(pixColor.a, 0.001); + pixColor.rgb = toLinearRGB(pixColor.rgb, sourceTF); + mat3 srcxyz = primaries2xyz(sourcePrimaries); + mat3 dstxyz; + if (sourcePrimaries == targetPrimaries) + dstxyz = srcxyz; + else { + dstxyz = primaries2xyz(targetPrimaries); + pixColor = convertPrimaries(pixColor, srcxyz, sourcePrimaries[3], dstxyz, targetPrimaries[3]); + } + pixColor = toNit(pixColor, sourceTF); + pixColor.rgb *= pixColor.a; + pixColor = tonemap(pixColor, dstxyz); + if (sourceTF == CM_TRANSFER_FUNCTION_SRGB && targetTF == CM_TRANSFER_FUNCTION_ST2084_PQ) + pixColor = saturate(pixColor, srcxyz, sdrSaturation); + pixColor *= sdrBrightnessMultiplier; + pixColor = fromLinearNit(pixColor, targetTF); + } + + if (applyTint == 1) + pixColor = vec4(pixColor.rgb * tint.rgb, pixColor[3]); + + if (radius > 0.0) + pixColor = rounding(pixColor); + + fragColor = pixColor * alpha; +} +)#" diff --git a/src/render/shaders/Textures.hpp b/src/render/shaders/Textures.hpp index 78845322..002c3459 100644 --- a/src/render/shaders/Textures.hpp +++ b/src/render/shaders/Textures.hpp @@ -94,6 +94,10 @@ void main() { v_texcoord = texcoord; })#"; +inline const std::string TEXFRAGSRCCM = +#include "CM.frag" + ; + inline const std::string TEXFRAGSRCRGBA = R"#( precision highp float; varying vec2 v_texcoord; // is in 0-1 |
