Skip to content

Commit e61a5d6

Browse files
committed
Add Broadcom V3D (Raspberry Pi 4/5) support
V3D has several hardware limitations that require workarounds: 1. Split render/display architecture: Pi5 has separate V3D GPU (card0) and VC4 display controller (card1). Added detection and fallback to find KMS-capable display device when render device lacks KMS. 2. Swapchain format: V3D only supports B8G8R8A8 formats. Added V3D detection and format preference in SDL/DRM backends. 3. Channel swap: Shaders declare rgba8 but V3D swapchain is BGR. Added c_swapChannels specialization constant to swap R/B in output. 4. Tiled/LINEAR mismatch: V3D compute shaders can only write to tiled (UIF) images, but VC4 display needs LINEAR for scanout. Created separate tiled storage images and linear scanout images with copy. 5. LINEAR sampling: V3D cannot sample LINEAR textures. Added tiled shadow textures that get copied from LINEAR dmabufs before sampling. 6. Optional extensions: Made VK_KHR_present_id/wait optional as V3D only has present_id2/wait2 variants. 7. Null descriptors: V3D lacks VK_EXT_robustness2. Added dummy image views for null descriptor slots. Tested on Raspberry Pi 5 with DRM and SDL backends.
1 parent 9dd0427 commit e61a5d6

8 files changed

Lines changed: 856 additions & 107 deletions

File tree

src/Backends/DRMBackend.cpp

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ namespace gamescope
101101

102102
struct 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
{

src/Backends/SDLBackend.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,17 @@ namespace gamescope
427427

428428
void CSDLBackend::GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const
429429
{
430-
*pPrimaryPlaneFormat = VulkanFormatToDRM( VK_FORMAT_A2B10G10R10_UNORM_PACK32 );
431-
*pOverlayPlaneFormat = VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM );
430+
// V3D swapchain only supports B8G8R8A8 formats
431+
if ( g_device.isV3D() )
432+
{
433+
*pPrimaryPlaneFormat = DRM_FORMAT_ARGB8888;
434+
*pOverlayPlaneFormat = DRM_FORMAT_ARGB8888;
435+
}
436+
else
437+
{
438+
*pPrimaryPlaneFormat = VulkanFormatToDRM( VK_FORMAT_A2B10G10R10_UNORM_PACK32 );
439+
*pOverlayPlaneFormat = VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM );
440+
}
432441
}
433442

434443
bool CSDLBackend::ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const

0 commit comments

Comments
 (0)