diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index 1d34eba030..ca15d37122 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; @@ -258,6 +260,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 +320,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 +532,41 @@ 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; + std::optional CURVE_1D_TYPE; + std::optional DATA; + std::optional MULTIPLIER; + }; + ColorOpProperties &GetProperties() { return m_Props; } + const ColorOpProperties &GetProperties() const { return m_Props; } + private: + ColorOpProperties m_Props; + }; + + struct CDRMColorPipeline + { + uint32_t id; + std::unique_ptr degamma; + 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 { public: @@ -535,7 +574,7 @@ namespace gamescope ~CDRMFb(); uint32_t GetFbId() const { return m_uFbId; } - + private: uint32_t m_uFbId = 0; }; @@ -562,6 +601,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 { @@ -574,6 +614,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; @@ -887,6 +928,76 @@ static bool refresh_state( drm_t *drm ) return true; } +static std::optional 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( uColorOpId ); + pColorOp->RefreshState(); + uColorOpId = pColorOp->GetProperties().NEXT.value().GetInitialValue(); + pipeline.emplace_back( std::move(pColorOp) ); + } + + // Check if the pipeline has what we need + 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_MULTIPLIER ) + return {}; + 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_1D_LUT ) + return {}; + if ( pipeline[5]->GetProperties().TYPE.value().GetInitialValue() != DRM_COLOROP_3D_LUT ) + return {}; + 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 { + .id = uHeadId, + .degamma = std::move(pipeline[0]), + .HDRMult = std::move(pipeline[1]), + .CTM = std::move(pipeline[2]), + .shaper = std::move(pipeline[3]), + .shaperLut = std::move(pipeline[4]), + .lut3D = std::move(pipeline[5]), + .blend = std::move(pipeline[6]), + .blendLut = std::move(pipeline[7]), + }; + return p; +} + +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 {}; + + drmModePropertyRes *pProperty = drmModeGetProperty( g_DRM.fd, pColorPipelineProp->GetPropertyId() ); + if ( !pProperty ) + return {}; + + defer( drmModeFreeProperty( pProperty ) ); + + for ( int i = 0; i < pProperty->count_enums; i++ ) + { + auto entry = pProperty->enums[ i ]; + std::optional p = get_color_pipeline( drm, entry.value ); + if ( p.has_value() ) + return p; + } + + return {}; +} + static bool get_resources(struct drm_t *drm) { { @@ -923,6 +1034,16 @@ static bool get_resources(struct drm_t *drm) } } + if ( g_bSupportsColorPipeline ) + { + for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) + { + // 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; + } + } + return refresh_state( drm ); } @@ -1257,6 +1378,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; } @@ -1297,6 +1420,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; } @@ -1537,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 @@ -1800,6 +1929,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 ) @@ -1815,6 +1959,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 ) @@ -1851,6 +2011,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 ) @@ -2065,6 +2246,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 +2728,28 @@ 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.CURVE_1D_TYPE = CDRMAtomicProperty::Instantiate( "CURVE_1D_TYPE", this, *rawProperties ); + m_Props.MULTIPLIER = CDRMAtomicProperty::Instantiate( "MULTIPLIER", this, *rawProperties ); + } + } + ///////////////////////// // CDRMFb ///////////////////////// @@ -2784,6 +2988,114 @@ 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++ ) + { + 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; + + 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 ) { + 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 ); + + 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; + 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 ); + } + + 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 ); + } + + 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() ) + { + 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 ); + + 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 ); + } + + p->HDRMult->GetProperties().BYPASS->SetPendingValue( drm->req, 0, true ); + p->HDRMult->GetProperties().MULTIPLIER->SetPendingValue( drm->req, 0x100000000ULL, true ); + + break; + } + } + } + if ( ret == 0 ) { // We don't support partial composition yet @@ -2896,7 +3208,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 ) { @@ -3171,9 +3483,18 @@ 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 ) ) + if ( !drm_supports_color_mgmt( drm ) && !drm_supports_color_pipeline ( &g_DRM ) ) return true; if ( g_ColorMgmt.serial == drm->current.color_mgmt_serial ) @@ -3185,6 +3506,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++ ) @@ -3194,6 +3517,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; @@ -3375,12 +3701,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 ) @@ -3902,7 +4245,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 ) diff --git a/src/drm_include.h b/src/drm_include.h index d0ed3ce9e2..6b5fcc8cfd 100644 --- a/src/drm_include.h +++ b/src/drm_include.h @@ -86,3 +86,15 @@ enum drm_colorspace { #define DRM_MODE_CONTENT_TYPE_PHOTO 2 #define DRM_MODE_CONTENT_TYPE_CINEMA 3 #define DRM_MODE_CONTENT_TYPE_GAME 4 + +enum drm_colorop_curve_1d_type { + 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_GAMMA22, + DRM_COLOROP_1D_CURVE_GAMMA22_INV, + DRM_COLOROP_1D_CURVE_COUNT +};