@@ -101,6 +101,7 @@ namespace gamescope
101101
102102struct drm_t {
103103 bool bUseLiftoff;
104+ bool bSplitDisplay = false ; // true when render GPU != display GPU (V3D+VC4)
104105
105106 int fd = -1 ;
106107
@@ -1229,9 +1230,63 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh)
12291230
12301231 if ( !drmIsKMS ( drm->fd ) )
12311232 {
1232- drm_log.errorf ( " '%s' is not a KMS device" , drm->device_name );
1233+ drm_log.infof ( " '%s' is not a KMS device, searching for a separate KMS display device (split render/display architecture)... " , drm->device_name );
12331234 wlsession_close_kms ();
1234- return -1 ;
1235+
1236+ bool bFoundKmsDevice = false ;
1237+ drmDevice **drmDevices = nullptr ;
1238+ int nDevices = drmGetDevices2 ( 0 , nullptr , 0 );
1239+ if ( nDevices > 0 )
1240+ {
1241+ drmDevices = (drmDevice **)calloc ( nDevices, sizeof (drmDevice *) );
1242+ nDevices = drmGetDevices2 ( 0 , drmDevices, nDevices );
1243+ }
1244+ for ( int i = 0 ; i < nDevices; i++ )
1245+ {
1246+ drmDevice *pDevice = drmDevices[i];
1247+ if ( !( pDevice->available_nodes & ( 1 << DRM_NODE_PRIMARY ) ) )
1248+ continue ;
1249+
1250+ const char *pPrimaryNode = pDevice->nodes [DRM_NODE_PRIMARY];
1251+
1252+ // Skip the Vulkan device's node that we already tried
1253+ if ( drm->device_name && strcmp ( pPrimaryNode, drm->device_name ) == 0 )
1254+ continue ;
1255+
1256+ int fd = open ( pPrimaryNode, O_RDWR | O_CLOEXEC );
1257+ if ( fd < 0 )
1258+ continue ;
1259+
1260+ if ( drmIsKMS ( fd ) )
1261+ {
1262+ close ( fd );
1263+ free ( drm->device_name );
1264+ drm->device_name = strdup ( pPrimaryNode );
1265+ drm_log.infof ( " Found separate KMS display device: '%s'" , drm->device_name );
1266+ drm->fd = wlsession_open_kms ( drm->device_name );
1267+ if ( drm->fd >= 0 )
1268+ {
1269+ drm->bSplitDisplay = true ;
1270+ bFoundKmsDevice = true ;
1271+ break ;
1272+ }
1273+ }
1274+ else
1275+ {
1276+ close ( fd );
1277+ }
1278+ }
1279+ if ( drmDevices )
1280+ {
1281+ drmFreeDevices ( drmDevices, nDevices );
1282+ free ( drmDevices );
1283+ }
1284+
1285+ if ( !bFoundKmsDevice )
1286+ {
1287+ drm_log.errorf ( " No KMS-capable display device found" );
1288+ return false ;
1289+ }
12351290 }
12361291
12371292 if (drmSetClientCap (drm->fd , DRM_CLIENT_CAP_ATOMIC, 1 ) != 0 ) {
@@ -1338,6 +1393,20 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh)
13381393 // 2. When compositing HDR content as a fallback when we undock, it avoids introducing
13391394 // a bunch of horrible banding when going to G2.2 curve.
13401395 // It ensures that we can dither that.
1396+ if ( drm->bSplitDisplay )
1397+ {
1398+ drm_log.infof ( " Split display mode: preferring 8-bit formats for cross-device compatibility" );
1399+ g_nDRMFormat = pick_plane_format (&drm->primary_formats , DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888);
1400+ if ( g_nDRMFormat == DRM_FORMAT_INVALID ) {
1401+ g_nDRMFormat = pick_plane_format (&drm->primary_formats , DRM_FORMAT_XRGB2101010, DRM_FORMAT_ARGB2101010);
1402+ if ( g_nDRMFormat == DRM_FORMAT_INVALID ) {
1403+ drm_log.errorf (" Primary plane doesn't support any formats >= 8888" );
1404+ return false ;
1405+ }
1406+ }
1407+ }
1408+ else
1409+ {
13411410 g_nDRMFormat = pick_plane_format (&drm->primary_formats , DRM_FORMAT_XRGB2101010, DRM_FORMAT_ARGB2101010);
13421411 if ( g_nDRMFormat == DRM_FORMAT_INVALID ) {
13431412 g_nDRMFormat = pick_plane_format (&drm->primary_formats , DRM_FORMAT_XBGR2101010, DRM_FORMAT_ABGR2101010);
@@ -1349,9 +1418,23 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh)
13491418 }
13501419 }
13511420 }
1421+ }
13521422
13531423 if (have_overlay_planes (drm)) {
13541424 // ARGB8888 is the Xformat and AFormat here in this function as we want transparent overlay
1425+ if ( drm->bSplitDisplay )
1426+ {
1427+ g_nDRMFormatOverlay = pick_plane_format (&drm->formats , DRM_FORMAT_ARGB8888, DRM_FORMAT_ARGB8888);
1428+ if ( g_nDRMFormatOverlay == DRM_FORMAT_INVALID ) {
1429+ g_nDRMFormatOverlay = pick_plane_format (&drm->formats , DRM_FORMAT_ARGB2101010, DRM_FORMAT_ARGB2101010);
1430+ if ( g_nDRMFormatOverlay == DRM_FORMAT_INVALID ) {
1431+ drm_log.errorf (" Overlay plane doesn't support any formats >= 8888" );
1432+ return false ;
1433+ }
1434+ }
1435+ }
1436+ else
1437+ {
13551438 g_nDRMFormatOverlay = pick_plane_format (&drm->formats , DRM_FORMAT_ARGB2101010, DRM_FORMAT_ARGB2101010);
13561439 if ( g_nDRMFormatOverlay == DRM_FORMAT_INVALID ) {
13571440 g_nDRMFormatOverlay = pick_plane_format (&drm->formats , DRM_FORMAT_ABGR2101010, DRM_FORMAT_ABGR2101010);
@@ -1363,6 +1446,7 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh)
13631446 }
13641447 }
13651448 }
1449+ }
13661450 } else {
13671451 switch (g_nDRMFormat) {
13681452 case DRM_FORMAT_XRGB2101010:
@@ -3468,6 +3552,11 @@ namespace gamescope
34683552 bNeedsFullComposite |= pFrameInfo->bFadingOut ;
34693553 bNeedsFullComposite |= !g_reshade_effect.empty ();
34703554
3555+ // V3D (Pi5) split render/display: Always composite because V3D creates UIF-tiled
3556+ // textures that VC4 display controller cannot scanout directly.
3557+ // This avoids wasted drm_prepare attempts that will always fail.
3558+ bNeedsFullComposite |= g_DRM.bSplitDisplay ;
3559+
34713560 if ( g_bOutputHDREnabled )
34723561 {
34733562 bNeedsFullComposite |= g_bHDRItmEnable;
@@ -3586,6 +3675,18 @@ namespace gamescope
35863675
35873676 vulkan_wait ( *oCompositeResult, true );
35883677
3678+ // V3D: Copy from tiled storage to linear scanout image
3679+ // The image index that was just written to is (nOutImage + 2) % 3 after increment
3680+ // vulkan_copy_to_scanout_image uses the same logic as vulkan_get_last_output_image
3681+ {
3682+ uint32_t nRegularImage = ( g_output.nOutImage + 2 ) % 3 ;
3683+ std::optional oCopyResult = vulkan_copy_to_scanout_image ( nRegularImage, !bNeedsFullComposite );
3684+ if ( oCopyResult )
3685+ {
3686+ vulkan_wait ( *oCopyResult, true );
3687+ }
3688+ }
3689+
35893690 FrameInfo_t presentCompFrameInfo = {};
35903691 presentCompFrameInfo.allowVRR = pFrameInfo->allowVRR ;
35913692 presentCompFrameInfo.outputEncodingEOTF = pFrameInfo->outputEncodingEOTF ;
@@ -3909,13 +4010,13 @@ namespace gamescope
39094010 m_uNextPresentCtx = ( m_uNextPresentCtx + 1 ) % 3 ;
39104011 m_PresentCtxs[uCurrentPresentCtx].ulPendingFlipCount = GetCurrentConnector ()->PresentationFeedback ().m_uQueuedPresents ;
39114012
3912- drm_log.debugf (" flip commit %" PRIu64, (uint64_t )GetCurrentConnector ()->PresentationFeedback ().m_uQueuedPresents );
4013+ drm_log.debugf (" flip commit %" PRIu64 " flags=0x%x " , (uint64_t )GetCurrentConnector ()->PresentationFeedback ().m_uQueuedPresents , drm-> flags );
39134014 gpuvis_trace_printf ( " flip commit %" PRIu64, (uint64_t )GetCurrentConnector ()->PresentationFeedback ().m_uQueuedPresents );
39144015
39154016 ret = drmModeAtomicCommit (drm->fd , drm->req , drm->flags , &m_PresentCtxs[uCurrentPresentCtx] );
39164017 if ( ret != 0 )
39174018 {
3918- drm_log.errorf_errno ( " flip error" );
4019+ drm_log.errorf_errno ( " flip error ret=%d " , ret );
39194020
39204021 if ( ret != -EBUSY && ret != -EACCES )
39214022 {
0 commit comments