From a81338cc025825d62c6100b9459072ff0c434afd Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 19 May 2024 12:30:36 +0200 Subject: [PATCH 01/15] drm: fetch plane color pipelines --- src/Backends/DRMBackend.cpp | 91 ++++++++++++++++++++++++++++++++++++- src/drm_include.h | 4 ++ 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index 1d34eba030..4d7efde946 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -258,6 +258,7 @@ namespace gamescope static std::optional Instantiate( const char *pszName, CDRMAtomicObject *pObject, const DRMObjectRawProperties& rawProperties ); + uint32_t GetPropertyId() const { return m_uPropertyId; } uint64_t GetPendingValue() const { return m_ulPendingValue; } uint64_t GetCurrentValue() const { return m_ulCurrentValue; } uint64_t GetInitialValue() const { return m_ulInitialValue; } @@ -317,6 +318,7 @@ namespace gamescope std::optional AMD_PLANE_LUT3D; std::optional AMD_PLANE_BLEND_TF; std::optional AMD_PLANE_BLEND_LUT; + std::optional COLOR_PIPELINE; std::optional DUMMY_END; }; PlaneProperties &GetProperties() { return m_Props; } @@ -528,6 +530,28 @@ namespace gamescope ConnectorProperties m_Props; }; + class CDRMColorOp final : public CDRMAtomicTypedObject + { + public: + CDRMColorOp( uint32_t uColorOpId ); + + void RefreshState(); + + struct ColorOpProperties + { + std::optional TYPE; // Immutable + std::optional NEXT; // Immutable + std::optional BYPASS; // Immutable + + std::optional DATA; + std::optional MULTIPLIER; + }; + ColorOpProperties &GetProperties() { return m_Props; } + const ColorOpProperties &GetProperties() const { return m_Props; } + private: + ColorOpProperties m_Props; + }; + class CDRMFb final : public CBaseBackendFb { public: @@ -535,7 +559,7 @@ namespace gamescope ~CDRMFb(); uint32_t GetFbId() const { return m_uFbId; } - + private: uint32_t m_uFbId = 0; }; @@ -887,6 +911,43 @@ static bool refresh_state( drm_t *drm ) return true; } +static bool get_color_pipeline( struct drm_t *drm, uint32_t uHeadId ) +{ + std::vector> pipeline; + + uint32_t uColorOpId = uHeadId; + while ( uColorOpId != 0 ) + { + auto pColorOp = std::make_unique( uHeadId ); + pColorOp->RefreshState(); + uColorOpId = pColorOp->GetProperties().NEXT.value().GetInitialValue(); + pipeline.emplace_back( std::move(pColorOp) ); + } + + return true; +} + +static bool get_plane_color_pipelines( struct drm_t *drm, std::unique_ptr< gamescope::CDRMPlane > &pPlane ) +{ + auto pColorPipelineProp = pPlane->GetProperties().COLOR_PIPELINE; + if ( !pColorPipelineProp ) + return true; + + drmModePropertyRes *pProperty = drmModeGetProperty( g_DRM.fd, pColorPipelineProp->GetPropertyId() ); + if ( !pProperty ) + return false; + defer( drmModeFreeProperty( pProperty ) ); + + for ( int i = 0; i < pProperty->count_enums; i++ ) + { + auto entry = pProperty->enums[ i ]; + if ( !get_color_pipeline( drm, entry.value ) ) + return false; + } + + return true; +} + static bool get_resources(struct drm_t *drm) { { @@ -923,6 +984,12 @@ static bool get_resources(struct drm_t *drm) } } + for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) + { + if ( !get_plane_color_pipelines( drm, pPlane ) ) + return false; + } + return refresh_state( drm ); } @@ -2065,6 +2132,7 @@ namespace gamescope m_Props.AMD_PLANE_LUT3D = CDRMAtomicProperty::Instantiate( "AMD_PLANE_LUT3D", this, *rawProperties ); m_Props.AMD_PLANE_BLEND_TF = CDRMAtomicProperty::Instantiate( "AMD_PLANE_BLEND_TF", this, *rawProperties ); m_Props.AMD_PLANE_BLEND_LUT = CDRMAtomicProperty::Instantiate( "AMD_PLANE_BLEND_LUT", this, *rawProperties ); + m_Props.COLOR_PIPELINE = CDRMAtomicProperty::Instantiate( "COLOR PIPELINE", this, *rawProperties ); } } @@ -2546,6 +2614,27 @@ namespace gamescope } } + ///////////////////////// + // CDRMConnector + ///////////////////////// + CDRMColorOp::CDRMColorOp( uint32_t uColorOpId ) + : CDRMAtomicTypedObject( uColorOpId ) + { + } + + void CDRMColorOp::RefreshState() + { + auto rawProperties = GetRawProperties(); + if ( rawProperties ) + { + m_Props.TYPE = CDRMAtomicProperty::Instantiate( "TYPE", this, *rawProperties ); + m_Props.NEXT = CDRMAtomicProperty::Instantiate( "NEXT", this, *rawProperties ); + m_Props.BYPASS = CDRMAtomicProperty::Instantiate( "BYPASS", this, *rawProperties ); + m_Props.DATA = CDRMAtomicProperty::Instantiate( "DATA", this, *rawProperties ); + m_Props.MULTIPLIER = CDRMAtomicProperty::Instantiate( "MULTIPLIER", this, *rawProperties ); + } + } + ///////////////////////// // CDRMFb ///////////////////////// diff --git a/src/drm_include.h b/src/drm_include.h index d0ed3ce9e2..3924692748 100644 --- a/src/drm_include.h +++ b/src/drm_include.h @@ -86,3 +86,7 @@ enum drm_colorspace { #define DRM_MODE_CONTENT_TYPE_PHOTO 2 #define DRM_MODE_CONTENT_TYPE_CINEMA 3 #define DRM_MODE_CONTENT_TYPE_GAME 4 + +#define DRM_MODE_OBJECT_COLOROP 0xfafafafa + +#define DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE 7 From feb06a47c54e471a9783a114703b23ef51955cdf Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Thu, 3 Jul 2025 18:20:02 -0300 Subject: [PATCH 02/15] drm: fixes to fetch color pipelines Signed-off-by: Melissa Wen --- src/Backends/DRMBackend.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index 4d7efde946..6b7bac0541 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -918,7 +918,7 @@ static bool get_color_pipeline( struct drm_t *drm, uint32_t uHeadId ) uint32_t uColorOpId = uHeadId; while ( uColorOpId != 0 ) { - auto pColorOp = std::make_unique( uHeadId ); + auto pColorOp = std::make_unique( uColorOpId ); pColorOp->RefreshState(); uColorOpId = pColorOp->GetProperties().NEXT.value().GetInitialValue(); pipeline.emplace_back( std::move(pColorOp) ); @@ -2132,7 +2132,7 @@ namespace gamescope m_Props.AMD_PLANE_LUT3D = CDRMAtomicProperty::Instantiate( "AMD_PLANE_LUT3D", this, *rawProperties ); m_Props.AMD_PLANE_BLEND_TF = CDRMAtomicProperty::Instantiate( "AMD_PLANE_BLEND_TF", this, *rawProperties ); m_Props.AMD_PLANE_BLEND_LUT = CDRMAtomicProperty::Instantiate( "AMD_PLANE_BLEND_LUT", this, *rawProperties ); - m_Props.COLOR_PIPELINE = CDRMAtomicProperty::Instantiate( "COLOR PIPELINE", this, *rawProperties ); + m_Props.COLOR_PIPELINE = CDRMAtomicProperty::Instantiate( "COLOR_PIPELINE", this, *rawProperties ); } } From 4f79dc2149a88a6a882ccce268bb77e22a551cc1 Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Fri, 4 Jul 2025 09:51:18 -0300 Subject: [PATCH 03/15] drm: check if color pipeline api is supported Signed-off-by: Melissa Wen --- src/Backends/DRMBackend.cpp | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index 6b7bac0541..90dd4d81b4 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -598,6 +598,7 @@ struct drm_color_ctm2 { bool g_bSupportsAsyncFlips = false; bool g_bSupportsSyncObjs = false; +bool g_bSupportsColorPipeline = false; extern gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration; extern GamescopePanelOrientation g_DesiredInternalOrientation; @@ -984,10 +985,13 @@ static bool get_resources(struct drm_t *drm) } } - for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) + if ( g_bSupportsColorPipeline ) { - if ( !get_plane_color_pipelines( drm, pPlane ) ) - return false; + for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) + { + if ( !get_plane_color_pipelines( drm, pPlane ) ) + return false; + } } return refresh_state( drm ); @@ -1364,6 +1368,8 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh) drm_log.errorf("Immediate flips disabled from environment"); } + g_bSupportsColorPipeline = drmSetClientCap(drm->fd, DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE, 1) == 0; + if (!get_resources(drm)) { return false; } @@ -3464,12 +3470,29 @@ bool drm_supports_color_mgmt(struct drm_t *drm) if ( g_bForceDisableColorMgmt ) return false; + if ( g_bSupportsColorPipeline ) + return false; + if ( !drm->pPrimaryPlane ) return false; return drm->pPrimaryPlane->GetProperties().AMD_PLANE_CTM.has_value() && drm->pPrimaryPlane->GetProperties().AMD_PLANE_BLEND_TF.has_value(); } +bool drm_supports_color_pipeline(struct drm_t *drm) +{ + if ( g_bForceDisableColorMgmt ) + return false; + + if ( !g_bSupportsColorPipeline ) + return false; + + if ( !drm->pPrimaryPlane ) + return false; + + return drm->pPrimaryPlane->GetProperties().COLOR_PIPELINE.has_value() ; +} + std::span drm_get_valid_refresh_rates( struct drm_t *drm ) { if ( drm && drm->pConnector ) From f3f37672e345fd1a17c79e49d3e3c46ac69a32c7 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 15 May 2024 16:55:53 +0200 Subject: [PATCH 04/15] drm: check pipeline shape --- src/Backends/DRMBackend.cpp | 43 +++++++++++++++++++++++++++++++++---- src/drm_include.h | 18 ++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index 90dd4d81b4..630b227281 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -552,6 +552,16 @@ namespace gamescope ColorOpProperties m_Props; }; + struct CDRMColorPipeline + { + std::unique_ptr degamma; + std::unique_ptr CTM; + std::unique_ptr HDRMult; + std::unique_ptr shaper; + std::unique_ptr lut3D; + std::unique_ptr blend; + }; + class CDRMFb final : public CBaseBackendFb { public: @@ -912,7 +922,7 @@ static bool refresh_state( drm_t *drm ) return true; } -static bool get_color_pipeline( struct drm_t *drm, uint32_t uHeadId ) +static std::optional get_color_pipeline( struct drm_t *drm, uint32_t uHeadId ) { std::vector> pipeline; @@ -925,7 +935,31 @@ static bool get_color_pipeline( struct drm_t *drm, uint32_t uHeadId ) pipeline.emplace_back( std::move(pColorOp) ); } - return true; + // Check if the pipeline has what we need + if ( pipeline.size() != 6 ) + return {}; + if ( pipeline[0]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_1D_CURVE ) + return {}; + if ( pipeline[1]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_CTM_3X4 ) + return {}; + if ( pipeline[2]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_MULTIPLIER ) + return {}; + if ( pipeline[3]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_1D_CURVE ) + return {}; + if ( pipeline[4]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_3D_LUT ) + return {}; + if ( pipeline[5]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_1D_CURVE ) + return {}; + + gamescope::CDRMColorPipeline p { + .degamma = std::move(pipeline[0]), + .CTM = std::move(pipeline[1]), + .HDRMult = std::move(pipeline[2]), + .shaper = std::move(pipeline[3]), + .lut3D = std::move(pipeline[4]), + .blend = std::move(pipeline[5]), + }; + return p; } static bool get_plane_color_pipelines( struct drm_t *drm, std::unique_ptr< gamescope::CDRMPlane > &pPlane ) @@ -942,8 +976,7 @@ static bool get_plane_color_pipelines( struct drm_t *drm, std::unique_ptr< games for ( int i = 0; i < pProperty->count_enums; i++ ) { auto entry = pProperty->enums[ i ]; - if ( !get_color_pipeline( drm, entry.value ) ) - return false; + get_color_pipeline( drm, entry.value ); } return true; @@ -1328,6 +1361,8 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh) return false; } + drmSetClientCap(drm->fd, DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE, 1); + if (drmGetCap(drm->fd, DRM_CAP_CURSOR_WIDTH, &drm->cursor_width) != 0) { drm->cursor_width = 64; } diff --git a/src/drm_include.h b/src/drm_include.h index 3924692748..86d9a5ab08 100644 --- a/src/drm_include.h +++ b/src/drm_include.h @@ -90,3 +90,21 @@ enum drm_colorspace { #define DRM_MODE_OBJECT_COLOROP 0xfafafafa #define DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE 7 + +enum drm_colorop_type { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_3D_LUT, /* TODO: check value w/ kernel */ +}; + +enum drm_colorop_curve_1d_type { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF, + DRM_COLOROP_1D_CURVE_BT2020_INV_OETF, + DRM_COLOROP_1D_CURVE_BT2020_OETF, + DRM_COLOROP_1D_CURVE_PQ_125_EOTF, + DRM_COLOROP_1D_CURVE_PQ_125_INV_EOTF, + DRM_COLOROP_1D_CURVE_COUNT +}; From 87f8de19c3706c32f28ed1b456e7199cf376654f Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Thu, 3 Jul 2025 18:33:30 -0300 Subject: [PATCH 05/15] drm: update plane color pipeline Signed-off-by: Melissa Wen --- src/Backends/DRMBackend.cpp | 44 +++++++++++++++++++++++-------------- src/drm_include.h | 22 +++++-------------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index 630b227281..0ccbe69f97 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -555,11 +555,13 @@ namespace gamescope struct CDRMColorPipeline { std::unique_ptr degamma; - std::unique_ptr CTM; std::unique_ptr HDRMult; + std::unique_ptr CTM; std::unique_ptr shaper; + std::unique_ptr shaperLut; std::unique_ptr lut3D; std::unique_ptr blend; + std::unique_ptr blendLut; }; class CDRMFb final : public CBaseBackendFb @@ -936,50 +938,59 @@ static std::optional get_color_pipeline( struct dr } // Check if the pipeline has what we need - if ( pipeline.size() != 6 ) + if ( pipeline.size() != 8 ) return {}; if ( pipeline[0]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_1D_CURVE ) return {}; - if ( pipeline[1]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_CTM_3X4 ) + if ( pipeline[1]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_MULTIPLIER ) return {}; - if ( pipeline[2]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_MULTIPLIER ) + if ( pipeline[2]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_CTM_3X4 ) return {}; if ( pipeline[3]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_1D_CURVE ) return {}; - if ( pipeline[4]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_3D_LUT ) + if ( pipeline[4]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_1D_LUT ) + return {}; + if ( pipeline[5]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_3D_LUT ) return {}; - if ( pipeline[5]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_1D_CURVE ) + if ( pipeline[6]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_1D_CURVE ) + return {}; + if ( pipeline[7]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_1D_LUT ) return {}; gamescope::CDRMColorPipeline p { .degamma = std::move(pipeline[0]), - .CTM = std::move(pipeline[1]), - .HDRMult = std::move(pipeline[2]), + .HDRMult = std::move(pipeline[1]), + .CTM = std::move(pipeline[2]), .shaper = std::move(pipeline[3]), - .lut3D = std::move(pipeline[4]), - .blend = std::move(pipeline[5]), + .shaperLut = std::move(pipeline[4]), + .lut3D = std::move(pipeline[5]), + .blend = std::move(pipeline[6]), + .blendLut = std::move(pipeline[7]), }; return p; } -static bool get_plane_color_pipelines( struct drm_t *drm, std::unique_ptr< gamescope::CDRMPlane > &pPlane ) +static std::optional get_plane_color_pipelines( struct drm_t *drm, std::unique_ptr< gamescope::CDRMPlane > &pPlane ) { auto pColorPipelineProp = pPlane->GetProperties().COLOR_PIPELINE; if ( !pColorPipelineProp ) - return true; + return {}; drmModePropertyRes *pProperty = drmModeGetProperty( g_DRM.fd, pColorPipelineProp->GetPropertyId() ); if ( !pProperty ) - return false; + return {}; + defer( drmModeFreeProperty( pProperty ) ); for ( int i = 0; i < pProperty->count_enums; i++ ) { auto entry = pProperty->enums[ i ]; - get_color_pipeline( drm, entry.value ); + std::optional p = get_color_pipeline( drm, entry.value ); + if ( p.has_value() ) + return p; } - return true; + return {}; } static bool get_resources(struct drm_t *drm) @@ -1022,7 +1033,8 @@ static bool get_resources(struct drm_t *drm) { for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) { - if ( !get_plane_color_pipelines( drm, pPlane ) ) + // AMD Cursor plane doesn't support color management + if ( pPlane->GetProperties().type->GetCurrentValue() != DRM_PLANE_TYPE_CURSOR && !get_plane_color_pipelines( drm, pPlane ) ) return false; } } diff --git a/src/drm_include.h b/src/drm_include.h index 86d9a5ab08..6b5fcc8cfd 100644 --- a/src/drm_include.h +++ b/src/drm_include.h @@ -87,24 +87,14 @@ enum drm_colorspace { #define DRM_MODE_CONTENT_TYPE_CINEMA 3 #define DRM_MODE_CONTENT_TYPE_GAME 4 -#define DRM_MODE_OBJECT_COLOROP 0xfafafafa - -#define DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE 7 - -enum drm_colorop_type { - DRM_COLOROP_1D_CURVE, - DRM_COLOROP_1D_LUT, - DRM_COLOROP_CTM_3X4, - DRM_COLOROP_MULTIPLIER, - DRM_COLOROP_3D_LUT, /* TODO: check value w/ kernel */ -}; - enum drm_colorop_curve_1d_type { - DRM_COLOROP_1D_CURVE_SRGB_EOTF, - DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF, + DRM_COLOROP_1D_CURVE_PQ_125_EOTF, + DRM_COLOROP_1D_CURVE_PQ_125_INV_EOTF, DRM_COLOROP_1D_CURVE_BT2020_INV_OETF, DRM_COLOROP_1D_CURVE_BT2020_OETF, - DRM_COLOROP_1D_CURVE_PQ_125_EOTF, - DRM_COLOROP_1D_CURVE_PQ_125_INV_EOTF, + DRM_COLOROP_1D_CURVE_GAMMA22, + DRM_COLOROP_1D_CURVE_GAMMA22_INV, DRM_COLOROP_1D_CURVE_COUNT }; From 050a8a4f7b08275589577fa891b9c5e4df51d3a5 Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Thu, 5 Mar 2026 14:12:39 -0300 Subject: [PATCH 06/15] drm: store Color Pipeline id Store the value of the color pipeline enum for a given plane that fits color pipeline needs. So that we can set plane COLOR_PIPELINE property with this value. Signed-off-by: Melissa Wen --- src/Backends/DRMBackend.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index 0ccbe69f97..191de58dbc 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -554,6 +554,7 @@ namespace gamescope struct CDRMColorPipeline { + uint32_t id; std::unique_ptr degamma; std::unique_ptr HDRMult; std::unique_ptr CTM; @@ -958,6 +959,7 @@ static std::optional get_color_pipeline( struct dr return {}; gamescope::CDRMColorPipeline p { + .id = uHeadId, .degamma = std::move(pipeline[0]), .HDRMult = std::move(pipeline[1]), .CTM = std::move(pipeline[2]), From 4c949df8d1f11fd95facee26b836b78883dac1af Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Thu, 5 Mar 2026 14:15:52 -0300 Subject: [PATCH 07/15] drm: set color pipeline that fits plane color mgmt needs Check if there is any color pipeline with the properties needed for plane color mgmt. Signed-off-by: Melissa Wen --- src/Backends/DRMBackend.cpp | 40 ++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index 191de58dbc..e6dd8c34ce 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -599,6 +599,7 @@ extern std::string g_reshade_effect; bool drm_update_color_mgmt(struct drm_t *drm); bool drm_supports_color_mgmt(struct drm_t *drm); +bool drm_supports_color_pipeline(struct drm_t *drm); bool drm_set_connector( struct drm_t *drm, gamescope::CDRMConnector *conn ); struct drm_color_ctm2 { @@ -2928,6 +2929,39 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo } } + if ( ret == 0 && drm_supports_color_pipeline( drm ) ) + { + auto entry = FrameInfoToLiftoffStateCacheEntry( drm, frameInfo ); + for ( int i = 0; i < frameInfo->layerCount; i++ ) + { + if ( !frameInfo->layers[i].applyColorMgmt ) + continue; + + struct liftoff_plane *plane = liftoff_layer_get_plane( drm->lo_layers[ i ] ); + uint32_t plane_id = plane ? liftoff_plane_get_id( plane ) : 0; + + if ( plane_id == 0 ) + continue; + + for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) + { + if ( pPlane->GetObjectId() != plane_id ) + continue; + + bool bYCbCr = entry.layerState[i].ycbcr; + std::optional p = get_plane_color_pipelines( drm, pPlane ); + if ( !p ) { + drm_log.debugf( "drm_prepare_liftoff: No color pipeline fits color mgmt needs of plane_id %d", plane_id ); + break; + } + + pPlane->GetProperties().COLOR_PIPELINE->SetPendingValue( drm->req, p->id, true ); ++ + break; + } + } + } + if ( ret == 0 ) { // We don't support partial composition yet @@ -3040,7 +3074,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI bool bSinglePlane = frameInfo->layerCount < 2 && cv_drm_single_plane_optimizations; - if ( drm_supports_color_mgmt( &g_DRM ) && frameInfo->applyOutputColorMgmt ) + if ( ( drm_supports_color_mgmt( &g_DRM ) || drm_supports_color_pipeline( &g_DRM ) ) && frameInfo->applyOutputColorMgmt ) { if ( !cv_drm_debug_disable_output_tf && !bSinglePlane ) { @@ -3317,7 +3351,7 @@ gamescope::GamescopeScreenType drm_get_screen_type(struct drm_t *drm) bool drm_update_color_mgmt(struct drm_t *drm) { - if ( !drm_supports_color_mgmt( drm ) ) + if ( !drm_supports_color_mgmt( drm ) && !drm_supports_color_pipeline ( &g_DRM ) ) return true; if ( g_ColorMgmt.serial == drm->current.color_mgmt_serial ) @@ -4063,7 +4097,7 @@ namespace gamescope bool SupportsColorManagement() const { - return drm_supports_color_mgmt( &g_DRM ); + return drm_supports_color_mgmt( &g_DRM ) || drm_supports_color_pipeline( &g_DRM ); } int Commit( const FrameInfo_t *pFrameInfo ) From c92ebddea08b4dc5dcd1bbd422593eb0b73b6cf5 Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Fri, 13 Mar 2026 21:16:26 -0300 Subject: [PATCH 08/15] drm: add 1D degamma curve colorop Add CURVE_1D_TYPE property to colorop, helper for degamma curve using DRM_COLOROP_* predefined curve instead of AMD_TRANSFER_FUNCTIONs, add set degamma 1D curve accordingly Signed-off-by: Melissa Wen --- src/Backends/DRMBackend.cpp | 40 ++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index e6dd8c34ce..df44889140 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -541,8 +541,8 @@ namespace gamescope { std::optional TYPE; // Immutable std::optional NEXT; // Immutable - std::optional BYPASS; // Immutable - + std::optional BYPASS; + std::optional CURVE_1D_TYPE; std::optional DATA; std::optional MULTIPLIER; }; @@ -1923,6 +1923,21 @@ static inline amdgpu_transfer_function colorspace_to_plane_degamma_tf(GamescopeA } } +static inline std::optional colorspace_to_drm_plane_degamma_curve(GamescopeAppTextureColorspace colorspace) +{ + switch ( colorspace ) + { + default: // Linear in this sense is SRGB. Linear = sRGB image view doing automatic sRGB -> Linear which doesn't happen on DRM side. + case GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB: + return DRM_COLOROP_1D_CURVE_SRGB_EOTF; + case GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU: + case GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB: + return std::nullopt; // DEFAULT doesn't exist anymore + case GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ: + return DRM_COLOROP_1D_CURVE_PQ_125_EOTF; + } +} + static inline amdgpu_transfer_function colorspace_to_plane_shaper_tf(GamescopeAppTextureColorspace colorspace) { switch ( colorspace ) @@ -2687,6 +2702,7 @@ namespace gamescope m_Props.NEXT = CDRMAtomicProperty::Instantiate( "NEXT", this, *rawProperties ); m_Props.BYPASS = CDRMAtomicProperty::Instantiate( "BYPASS", this, *rawProperties ); m_Props.DATA = CDRMAtomicProperty::Instantiate( "DATA", this, *rawProperties ); + m_Props.CURVE_1D_TYPE = CDRMAtomicProperty::Instantiate( "CURVE_1D_TYPE", this, *rawProperties ); m_Props.MULTIPLIER = CDRMAtomicProperty::Instantiate( "MULTIPLIER", this, *rawProperties ); } } @@ -2956,7 +2972,25 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo } pPlane->GetProperties().COLOR_PIPELINE->SetPendingValue( drm->req, p->id, true ); -+ + + GamescopeAppTextureColorspace colorspace = entry.layerState[i].colorspace; + std::optional degamma_tf = colorspace_to_drm_plane_degamma_curve( colorspace ); + + if ( bYCbCr ) + { + degamma_tf = DRM_COLOROP_1D_CURVE_BT2020_INV_OETF; + } + + bool bUseDegamma = !cv_drm_debug_disable_degamma_tf; + if ( bUseDegamma && degamma_tf.has_value() ) + { + p->degamma->GetProperties().BYPASS->SetPendingValue( drm->req, 0, true ); + p->degamma->GetProperties().CURVE_1D_TYPE->SetPendingValue( drm->req, *degamma_tf, true ); + } + else + { + p->degamma->GetProperties().BYPASS->SetPendingValue( drm->req, 1, true ); + } break; } } From 781bb0e3c36240885d0a5123f19ccc9fc27de2ec Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Fri, 13 Mar 2026 21:22:03 -0300 Subject: [PATCH 09/15] drm: add shaper 1D curve Create helper to use DRM_COLOROP curves instead of AMD_TRANSFER_FUNCTIONs and set properties accordingly Signed-off-by: Melissa Wen --- src/Backends/DRMBackend.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index df44889140..ae150a1ae9 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -1953,6 +1953,22 @@ static inline amdgpu_transfer_function colorspace_to_plane_shaper_tf(GamescopeAp } } +static inline std::optional colorspace_to_drm_plane_shaper_curve(GamescopeAppTextureColorspace colorspace) +{ + switch ( colorspace ) + { + default: // Linear in this sense is SRGB. Linear = sRGB image view doing automatic sRGB -> Linear which doesn't happen on DRM side. + case GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB: + return DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF; + case GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ: + case GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB: + return DRM_COLOROP_1D_CURVE_PQ_125_INV_EOTF; + case GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU: + return std::nullopt; // DEFAULT doesn't exist anymore + } +} + + static inline amdgpu_transfer_function inverse_tf(amdgpu_transfer_function tf) { switch ( tf ) @@ -2975,10 +2991,12 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo GamescopeAppTextureColorspace colorspace = entry.layerState[i].colorspace; std::optional degamma_tf = colorspace_to_drm_plane_degamma_curve( colorspace ); + std::optional shaper_tf = colorspace_to_drm_plane_shaper_curve( colorspace ); if ( bYCbCr ) { degamma_tf = DRM_COLOROP_1D_CURVE_BT2020_INV_OETF; + shaper_tf = DRM_COLOROP_1D_CURVE_BT2020_OETF; } bool bUseDegamma = !cv_drm_debug_disable_degamma_tf; @@ -2991,6 +3009,18 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo { p->degamma->GetProperties().BYPASS->SetPendingValue( drm->req, 1, true ); } + + bool bUseShaperAnd3DLUT = !cv_drm_debug_disable_shaper_and_3dlut; + if ( bUseShaperAnd3DLUT && shaper_tf.has_value() ) + { + p->shaper->GetProperties().BYPASS->SetPendingValue( drm->req, 0, true ); + p->shaper->GetProperties().CURVE_1D_TYPE->SetPendingValue( drm->req, *shaper_tf, true ); + } + else + { + p->shaper->GetProperties().BYPASS->SetPendingValue( drm->req, 1, true ); + } + break; } } From 4c9ebe2c54aaea2aa2ef08ad86ea04a42c2b62cb Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Fri, 13 Mar 2026 21:25:17 -0300 Subject: [PATCH 10/15] drm: add blend 1D curve and LUT Create a helper to translate output tf of AMD_TRANSFER_FUNCTION enum type to DRM_COLOROP type Signed-off-by: Melissa Wen --- src/Backends/DRMBackend.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index ae150a1ae9..cc0a05163f 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -2005,6 +2005,27 @@ static inline amdgpu_transfer_function inverse_tf(amdgpu_transfer_function tf) } } +static inline std::optional amd_tf_to_drm_curve ( amdgpu_transfer_function tf ) +{ + switch ( tf ) + { + case AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF: + return DRM_COLOROP_1D_CURVE_SRGB_EOTF; + case AMDGPU_TRANSFER_FUNCTION_PQ_EOTF: + return DRM_COLOROP_1D_CURVE_PQ_125_EOTF; + case AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF: + return DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF; + case AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF: + return DRM_COLOROP_1D_CURVE_PQ_125_INV_EOTF; + case AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF: + return DRM_COLOROP_1D_CURVE_GAMMA22; + case AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF: + return DRM_COLOROP_1D_CURVE_GAMMA22_INV; + default: + return std::nullopt; + } +} + static inline uint32_t ColorSpaceToEOTFIndex( GamescopeAppTextureColorspace colorspace ) { switch ( colorspace ) @@ -3021,6 +3042,19 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo p->shaper->GetProperties().BYPASS->SetPendingValue( drm->req, 1, true ); } + std::optional blend_tf = amd_tf_to_drm_curve(drm->pending.output_tf); + if (!cv_drm_debug_disable_blend_tf && !bSinglePlane && blend_tf.has_value() ) + { + p->blend->GetProperties().BYPASS->SetPendingValue( drm->req, 0, true ); + p->blend->GetProperties().CURVE_1D_TYPE->SetPendingValue( drm->req, *blend_tf, true ); + } + else + { + p->blend->GetProperties().BYPASS->SetPendingValue( drm->req, 1, true ); + } + + p->blendLut->GetProperties().BYPASS->SetPendingValue( drm->req, 1, true ); + break; } } From e1873c7e76760b9e69784eacde09216d168ffde5 Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Fri, 13 Mar 2026 21:28:22 -0300 Subject: [PATCH 11/15] drm: add CTM colorop Gamescope already use 3x4 matrix, so doesn't needed additional helper. Signed-off-by: Melissa Wen --- src/Backends/DRMBackend.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index cc0a05163f..f3b9cb1753 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -3055,6 +3055,16 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo p->blendLut->GetProperties().BYPASS->SetPendingValue( drm->req, 1, true ); + if ( frameInfo->layers[i].ctm != nullptr ) + { + p->CTM->GetProperties().BYPASS->SetPendingValue( drm->req, 0, true ); + p->CTM->GetProperties().DATA->SetPendingValue( drm->req, frameInfo->layers[i].ctm->GetBlobValue(), true ); + } + else + { + p->CTM->GetProperties().BYPASS->SetPendingValue( drm->req, 1, true ); + } + break; } } From e72dadf8531a6d74adb0297ff3b70c4d1024eeaa Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Fri, 13 Mar 2026 21:32:20 -0300 Subject: [PATCH 12/15] drm: add HDR multiplier colorop Signed-off-by: Melissa Wen --- src/Backends/DRMBackend.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index f3b9cb1753..ebd0cb46c3 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -3065,6 +3065,9 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo p->CTM->GetProperties().BYPASS->SetPendingValue( drm->req, 1, true ); } + p->HDRMult->GetProperties().BYPASS->SetPendingValue( drm->req, 0, true ); + p->HDRMult->GetProperties().MULTIPLIER->SetPendingValue( drm->req, 0x100000000ULL, true ); + break; } } From 813b0e375acc0038379c3dadb824a4274666b208 Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Fri, 13 Mar 2026 21:33:24 -0300 Subject: [PATCH 13/15] drm: add shaper LUT and 3D LUT colorops Instead of struct drm_color_lut, shaper and 3D LUTs follow struct drm_color_lut32, which means 32-bit for each channel, not 16-bit anymore. Create a helper to make possible reuse LUTs already created for AMD driver-specific color properties. Assisted-by: Claude Code Signed-off-by: Melissa Wen --- src/Backends/DRMBackend.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index ebd0cb46c3..8b755f3793 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -134,6 +134,8 @@ struct drm_t { uint32_t color_mgmt_serial; std::shared_ptr lut3d_id[ EOTF_Count ]; std::shared_ptr shaperlut_id[ EOTF_Count ]; + std::shared_ptr lut3d_colorop_id[ EOTF_Count ]; + std::shared_ptr shaperlut_colorop_id[ EOTF_Count ]; amdgpu_transfer_function output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; } current, pending; @@ -3042,6 +3044,20 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo p->shaper->GetProperties().BYPASS->SetPendingValue( drm->req, 1, true ); } + if ( bUseShaperAnd3DLUT ) + { + p->shaperLut->GetProperties().BYPASS->SetPendingValue( drm->req, 0, true ); + p->shaperLut->GetProperties().DATA->SetPendingValue( drm->req, drm->pending.shaperlut_colorop_id[ ColorSpaceToEOTFIndex( colorspace ) ]->GetBlobValue(), true ); + + p->lut3D->GetProperties().BYPASS->SetPendingValue( drm->req, 0, true ); + p->lut3D->GetProperties().DATA->SetPendingValue( drm->req, drm->pending.lut3d_colorop_id[ ColorSpaceToEOTFIndex( colorspace ) ]->GetBlobValue(), true ); + } + else + { + p->shaperLut->GetProperties().BYPASS->SetPendingValue( drm->req, 1, true ); + p->lut3D->GetProperties().BYPASS->SetPendingValue( drm->req, 1, true ); + } + std::optional blend_tf = amd_tf_to_drm_curve(drm->pending.output_tf); if (!cv_drm_debug_disable_blend_tf && !bSinglePlane && blend_tf.has_value() ) { @@ -3460,6 +3476,15 @@ gamescope::GamescopeScreenType drm_get_screen_type(struct drm_t *drm) return drm->pConnector->GetScreenType(); } +template +static std::array lut_convert_16bit_to_32bit( const uint16_t (&lut)[N] ) +{ + std::array out; + for ( size_t i = 0; i < N; i++ ) + out[i] = (uint32_t)lut[i] * 0x10001u; + return out; +} + bool drm_update_color_mgmt(struct drm_t *drm) { if ( !drm_supports_color_mgmt( drm ) && !drm_supports_color_pipeline ( &g_DRM ) ) @@ -3474,6 +3499,8 @@ bool drm_update_color_mgmt(struct drm_t *drm) { drm->pending.shaperlut_id[ i ] = 0; drm->pending.lut3d_id[ i ] = 0; + drm->pending.shaperlut_colorop_id[ i ] = 0; + drm->pending.lut3d_colorop_id[ i ] = 0; } for ( uint32_t i = 0; i < EOTF_Count; i++ ) @@ -3483,6 +3510,9 @@ bool drm_update_color_mgmt(struct drm_t *drm) drm->pending.shaperlut_id[ i ] = GetBackend()->CreateBackendBlob( g_ColorMgmtLuts[i].lut1d ); drm->pending.lut3d_id[ i ] = GetBackend()->CreateBackendBlob( g_ColorMgmtLuts[i].lut3d ); + + drm->pending.shaperlut_colorop_id[ i ] = GetBackend()->CreateBackendBlob( lut_convert_16bit_to_32bit( g_ColorMgmtLuts[i].lut1d ) ); + drm->pending.lut3d_colorop_id[ i ] = GetBackend()->CreateBackendBlob( lut_convert_16bit_to_32bit( g_ColorMgmtLuts[i].lut3d ) ); } return true; From 1a563288ce10a7dd458bdfcd68ed2f37c3f3e0bd Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Fri, 13 Mar 2026 22:32:10 -0300 Subject: [PATCH 14/15] drm: bypass COLOR PIPELINE prop when finishing drm Signed-off-by: Melissa Wen --- src/Backends/DRMBackend.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index 8b755f3793..5c00928752 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -1662,6 +1662,10 @@ void finish_drm(struct drm_t *drm) if ( pPlane->GetProperties().AMD_PLANE_BLEND_LUT ) pPlane->GetProperties().AMD_PLANE_BLEND_LUT->SetPendingValue( req, 0, true ); + + if ( pPlane->GetProperties().COLOR_PIPELINE ) + pPlane->GetProperties().COLOR_PIPELINE->SetPendingValue( req, 0, true ); + } // We can't do a non-blocking commit here or else risk EBUSY in case the From 63d54e7bb029295a4f3e55b2c9d7e5974bfa4589 Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Thu, 19 Mar 2026 15:05:52 -0700 Subject: [PATCH 15/15] drm: reset COLOR_PIPELINE to bypass for non-color-managed layers Match the existing behavior of the legacy AMD plane properties, which reset layers to DEFAULT without color management applied to them. Otherwise, stale color pipeline state from the previous scanout frame persists during composition, resulting in double color management and an overly-saturated output image. Signed-off-by: Matthew Schwartz --- src/Backends/DRMBackend.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index 5c00928752..ca15d37122 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -2993,9 +2993,6 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo auto entry = FrameInfoToLiftoffStateCacheEntry( drm, frameInfo ); for ( int i = 0; i < frameInfo->layerCount; i++ ) { - if ( !frameInfo->layers[i].applyColorMgmt ) - continue; - struct liftoff_plane *plane = liftoff_layer_get_plane( drm->lo_layers[ i ] ); uint32_t plane_id = plane ? liftoff_plane_get_id( plane ) : 0; @@ -3007,6 +3004,12 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo if ( pPlane->GetObjectId() != plane_id ) continue; + if ( !frameInfo->layers[i].applyColorMgmt ) + { + pPlane->GetProperties().COLOR_PIPELINE->SetPendingValue( drm->req, 0, true ); + break; + } + bool bYCbCr = entry.layerState[i].ycbcr; std::optional p = get_plane_color_pipelines( drm, pPlane ); if ( !p ) {