@@ -450,6 +450,138 @@ unsafe extern "C" {
450450 fn libc_close_fd ( fd : RawFd ) -> i32 ;
451451}
452452
453+ // ---- DRI3 PixmapFromBuffers + Present: flip a GPU frame with no readback -----
454+ //
455+ // The last hop of the zero-copy path: wrap a rendered dma-buf as an X Pixmap via
456+ // DRI3 `PixmapFromBuffers` (the plane fds sent over this socket with SCM_RIGHTS,
457+ // echoing the format modifier so tiled buffers import correctly), then flip that
458+ // Pixmap to the window with the Present extension's `PresentPixmap`. The
459+ // `PixmapFromBuffers` step needs a real GPU (the server imports the dma-buf), so
460+ // it's hardware-gated; the Present extension itself needs no GPU (it works under
461+ // Xvfb), so its negotiation is CI-verifiable. The wire encoders below are
462+ // unit-tested regardless.
463+
464+ const DRI3_PIXMAP_FROM_BUFFERS : u8 = 7 ;
465+ const PRESENT_QUERY_VERSION : u8 = 0 ;
466+ const PRESENT_PIXMAP : u8 = 1 ;
467+
468+ /// A rendered dma-buf to wrap as an X Pixmap via DRI3 `PixmapFromBuffers`. Holds
469+ /// the geometry + format the server needs to interpret the (1..=4) planes; the
470+ /// plane fds are passed separately as SCM_RIGHTS ancillary data.
471+ #[ derive( Clone , Debug , PartialEq , Eq ) ]
472+ pub struct DmabufImage {
473+ pub width : u16 ,
474+ pub height : u16 ,
475+ /// Pixmap depth (e.g. 24 for XRGB8888, 32 for ARGB8888).
476+ pub depth : u8 ,
477+ /// Bits per pixel (e.g. 32).
478+ pub bpp : u8 ,
479+ /// DRM format modifier — must echo the export's modifier (e.g. from
480+ /// `EGL_DMA_BUF_PLANE0_MODIFIER_*`) or a tiled buffer imports as garbage.
481+ pub modifier : u64 ,
482+ /// Per-plane `(stride, offset)` in bytes; 1..=4 planes, parallel to the fds.
483+ pub planes : Vec < ( u32 , u32 ) > ,
484+ }
485+
486+ /// Build a DRI3 `PixmapFromBuffers` request (minor 7): wrap the dma-buf planes
487+ /// of `img` (whose fds are sent alongside as ancillary data via
488+ /// [`crate::scm::send_with_fds`]) as the X resource `pixmap`, in `window`'s
489+ /// screen. Absent planes (of the 4 wire slots) are zero-filled.
490+ pub fn pixmap_from_buffers_request (
491+ major : u8 ,
492+ pixmap : u32 ,
493+ window : u32 ,
494+ img : & DmabufImage ,
495+ ) -> Vec < u8 > {
496+ let mut req = vec ! [ major, DRI3_PIXMAP_FROM_BUFFERS , 0 , 0 ] ;
497+ req. extend_from_slice ( & pixmap. to_le_bytes ( ) ) ;
498+ req. extend_from_slice ( & window. to_le_bytes ( ) ) ;
499+ req. push ( img. planes . len ( ) as u8 ) ;
500+ req. extend_from_slice ( & [ 0u8 ; 3 ] ) ; // pad
501+ req. extend_from_slice ( & img. width . to_le_bytes ( ) ) ;
502+ req. extend_from_slice ( & img. height . to_le_bytes ( ) ) ;
503+ // stride0,offset0 .. stride3,offset3 (zero-filled for absent planes).
504+ for i in 0 ..4 {
505+ let ( stride, offset) = img. planes . get ( i) . copied ( ) . unwrap_or ( ( 0 , 0 ) ) ;
506+ req. extend_from_slice ( & stride. to_le_bytes ( ) ) ;
507+ req. extend_from_slice ( & offset. to_le_bytes ( ) ) ;
508+ }
509+ req. push ( img. depth ) ;
510+ req. push ( img. bpp ) ;
511+ req. extend_from_slice ( & [ 0u8 ; 2 ] ) ; // pad
512+ req. extend_from_slice ( & img. modifier . to_le_bytes ( ) ) ;
513+ finish ( req)
514+ }
515+
516+ /// Build a Present `PresentPixmap` request (minor 1): present `pixmap` to
517+ /// `window`. Regions, target CRTC, and sync fences are left as None (0) and the
518+ /// target MSC/divisor/remainder as 0 — a plain immediate present; `serial` and
519+ /// `options` pass through. No notify list.
520+ pub fn present_pixmap_request (
521+ major : u8 ,
522+ window : u32 ,
523+ pixmap : u32 ,
524+ serial : u32 ,
525+ options : u32 ,
526+ ) -> Vec < u8 > {
527+ let mut req = vec ! [ major, PRESENT_PIXMAP , 0 , 0 ] ;
528+ req. extend_from_slice ( & window. to_le_bytes ( ) ) ;
529+ req. extend_from_slice ( & pixmap. to_le_bytes ( ) ) ;
530+ req. extend_from_slice ( & serial. to_le_bytes ( ) ) ;
531+ req. extend_from_slice ( & 0u32 . to_le_bytes ( ) ) ; // valid region (None)
532+ req. extend_from_slice ( & 0u32 . to_le_bytes ( ) ) ; // update region (None)
533+ req. extend_from_slice ( & 0i16 . to_le_bytes ( ) ) ; // x-off
534+ req. extend_from_slice ( & 0i16 . to_le_bytes ( ) ) ; // y-off
535+ req. extend_from_slice ( & 0u32 . to_le_bytes ( ) ) ; // target CRTC (None)
536+ req. extend_from_slice ( & 0u32 . to_le_bytes ( ) ) ; // wait fence (None)
537+ req. extend_from_slice ( & 0u32 . to_le_bytes ( ) ) ; // idle fence (None)
538+ req. extend_from_slice ( & options. to_le_bytes ( ) ) ;
539+ req. extend_from_slice ( & 0u32 . to_le_bytes ( ) ) ; // unused
540+ req. extend_from_slice ( & 0u64 . to_le_bytes ( ) ) ; // target MSC
541+ req. extend_from_slice ( & 0u64 . to_le_bytes ( ) ) ; // divisor
542+ req. extend_from_slice ( & 0u64 . to_le_bytes ( ) ) ; // remainder
543+ finish ( req)
544+ }
545+
546+ /// Negotiate the Present extension, returning the server's `(major, minor)`
547+ /// version. `None` if the server lacks Present. Works without a GPU.
548+ fn present_query_version ( conn : & mut Conn ) -> io:: Result < Option < ( u32 , u32 ) > > {
549+ let Some ( major) = query_extension ( conn, b"Present" ) ? else {
550+ return Ok ( None ) ;
551+ } ;
552+ let mut req = vec ! [ major, PRESENT_QUERY_VERSION , 0 , 0 ] ;
553+ req. extend_from_slice ( & 1u32 . to_le_bytes ( ) ) ; // client major
554+ req. extend_from_slice ( & 2u32 . to_le_bytes ( ) ) ; // client minor
555+ conn. send ( & finish ( req) ) ?;
556+ let mut reply = [ 0u8 ; 32 ] ;
557+ conn. stream . read_exact ( & mut reply) ?;
558+ if reply[ 0 ] != 1 {
559+ return Ok ( None ) ;
560+ }
561+ let smajor = u32:: from_le_bytes ( [ reply[ 8 ] , reply[ 9 ] , reply[ 10 ] , reply[ 11 ] ] ) ;
562+ let sminor = u32:: from_le_bytes ( [ reply[ 12 ] , reply[ 13 ] , reply[ 14 ] , reply[ 15 ] ] ) ;
563+ Ok ( Some ( ( smajor, sminor) ) )
564+ }
565+
566+ // The runtime composition that flips a GPU frame to a window with no readback —
567+ // query DRI3 + Present, `conn.new_id()` for the pixmap,
568+ // `scm::send_with_fds(pixmap_from_buffers_request(..), plane_fds)`, then
569+ // `present_pixmap_request(..)` — lives in the windowed compositor once
570+ // `forma-gpu` exports a scene's dma-buf descriptor (fds + per-plane
571+ // stride/offset + modifier). The wire encoders above are that path's tested
572+ // building blocks; both DRI3 import and Present flip need real GPU hardware.
573+
574+ /// Connect and negotiate the Present extension, returning a one-line status.
575+ /// Present needs no GPU (Xvfb supports it), so this is CI-verifiable — it proves
576+ /// the half of the zero-copy present path that doesn't require DRM hardware.
577+ pub fn present_probe ( ) -> Result < String , PlatformError > {
578+ let mut conn = Conn :: connect ( ) ?;
579+ match present_query_version ( & mut conn) . map_err ( os) ? {
580+ Some ( ( maj, min) ) => Ok ( format ! ( "Present {maj}.{min} available" ) ) ,
581+ None => Ok ( "Present unavailable on this server" . to_string ( ) ) ,
582+ }
583+ }
584+
453585/// Set up MIT-SHM: create a shared segment of `capacity` bytes, attach it to the
454586/// server, and confirm the attach succeeded. Returns `None` (with everything
455587/// cleaned up) if the extension is absent or any step fails — the caller then
@@ -1383,6 +1515,61 @@ mod tests {
13831515 assert_eq ! ( req. len( ) , 12 ) ;
13841516 }
13851517
1518+ #[ test]
1519+ fn pixmap_from_buffers_request_is_well_formed ( ) {
1520+ // A single-plane ARGB8888 4096×2160 buffer with a tiled modifier.
1521+ let img = DmabufImage {
1522+ width : 4096 ,
1523+ height : 2160 ,
1524+ depth : 24 ,
1525+ bpp : 32 ,
1526+ modifier : 0x0100_0000_0000_0001 , // an arbitrary 64-bit DRM modifier
1527+ planes : vec ! [ ( 16384 , 0 ) ] ,
1528+ } ;
1529+ let req = pixmap_from_buffers_request ( 0x90 , 0x0040_0000 , 0x0020_0001 , & img) ;
1530+ assert_eq ! ( req[ 0 ] , 0x90 ) ;
1531+ assert_eq ! ( req[ 1 ] , DRI3_PIXMAP_FROM_BUFFERS ) ;
1532+ // Fixed wire size: header(4) + pixmap(4) + window(4) + num/pad/w/h(8)
1533+ // + 8 plane words(32) + depth/bpp/pad(4) + modifier(8) = 64 bytes / 16 words.
1534+ assert_eq ! ( u16 :: from_le_bytes( [ req[ 2 ] , req[ 3 ] ] ) , 16 ) ;
1535+ assert_eq ! ( req. len( ) , 64 ) ;
1536+ assert_eq ! ( rd_u32( & req, 4 ) . unwrap( ) , 0x0040_0000 ) ; // pixmap
1537+ assert_eq ! ( rd_u32( & req, 8 ) . unwrap( ) , 0x0020_0001 ) ; // window
1538+ assert_eq ! ( req[ 12 ] , 1 ) ; // num_buffers
1539+ assert_eq ! ( u16 :: from_le_bytes( [ req[ 16 ] , req[ 17 ] ] ) , 4096 ) ; // width
1540+ assert_eq ! ( u16 :: from_le_bytes( [ req[ 18 ] , req[ 19 ] ] ) , 2160 ) ; // height
1541+ assert_eq ! ( rd_u32( & req, 20 ) . unwrap( ) , 16384 ) ; // stride0
1542+ assert_eq ! ( rd_u32( & req, 24 ) . unwrap( ) , 0 ) ; // offset0
1543+ // Planes 1..3 are zero-filled (stride1@28 .. offset3@48).
1544+ assert_eq ! ( rd_u32( & req, 28 ) . unwrap( ) , 0 ) ;
1545+ assert_eq ! ( rd_u32( & req, 48 ) . unwrap( ) , 0 ) ;
1546+ assert_eq ! ( req[ 52 ] , 24 ) ; // depth
1547+ assert_eq ! ( req[ 53 ] , 32 ) ; // bpp
1548+ // Modifier is the last 8 bytes (little-endian u64).
1549+ let modifier = u64:: from_le_bytes ( req[ 56 ..64 ] . try_into ( ) . unwrap ( ) ) ;
1550+ assert_eq ! ( modifier, 0x0100_0000_0000_0001 ) ;
1551+ }
1552+
1553+ #[ test]
1554+ fn present_pixmap_request_is_well_formed ( ) {
1555+ let req = present_pixmap_request ( 0x91 , 0x0020_0001 , 0x0040_0000 , 7 , 0 ) ;
1556+ assert_eq ! ( req[ 0 ] , 0x91 ) ;
1557+ assert_eq ! ( req[ 1 ] , PRESENT_PIXMAP ) ;
1558+ // 4-byte header + window+pixmap+serial+valid+update (5 words) + off word
1559+ // + crtc+wait+idle+options+unused (5 words) + msc/divisor/remainder
1560+ // (6 words) = 18 words.
1561+ assert_eq ! ( u16 :: from_le_bytes( [ req[ 2 ] , req[ 3 ] ] ) , 18 ) ;
1562+ assert_eq ! ( req. len( ) , 72 ) ;
1563+ assert_eq ! ( rd_u32( & req, 4 ) . unwrap( ) , 0x0020_0001 ) ; // window
1564+ assert_eq ! ( rd_u32( & req, 8 ) . unwrap( ) , 0x0040_0000 ) ; // pixmap
1565+ assert_eq ! ( rd_u32( & req, 12 ) . unwrap( ) , 7 ) ; // serial
1566+ assert_eq ! ( rd_u32( & req, 16 ) . unwrap( ) , 0 ) ; // valid region (None)
1567+ assert_eq ! ( rd_u32( & req, 20 ) . unwrap( ) , 0 ) ; // update region (None)
1568+ // target-msc/divisor/remainder are the final three zero u64s.
1569+ assert_eq ! ( u64 :: from_le_bytes( req[ 48 ..56 ] . try_into( ) . unwrap( ) ) , 0 ) ;
1570+ assert_eq ! ( u64 :: from_le_bytes( req[ 64 ..72 ] . try_into( ) . unwrap( ) ) , 0 ) ;
1571+ }
1572+
13861573 #[ test]
13871574 fn selection_notify_send_event_is_well_formed ( ) {
13881575 // A SendEvent carrying a SelectionNotify is 44 bytes (11 words): 12-byte
0 commit comments