feat(present): add Surface.DestroyTimeline
- commit
- 7c372866b041246e4970efd36727716b88a3705c
- author
- brice · · 2026-10-01 19:21 UTC
- parent
- 49445e1
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +44 -0
M present.go +16 -0
| @@ -124,6 +124,22 @@ func (s *Surface) ImportTimeline(id uint64, fd int) error { | |||
| 124 | 124 | return nil | |
| 125 | 125 | } | |
| 126 | 126 | ||
| 127 | + | // DestroyTimeline destroys an imported timeline object. The id is the one given | |
| 128 | + | // to [Surface.ImportTimeline]; it may be imported again afterwards. Do not | |
| 129 | + | // destroy a timeline that a pending commit still references. Surface.Close | |
| 130 | + | // destroys every remaining timeline. | |
| 131 | + | func (s *Surface) DestroyTimeline(id uint64) error { | |
| 132 | + | t, ok := s.timelines[id] | |
| 133 | + | if !ok { | |
| 134 | + | return fmt.Errorf("neferclient: unknown timeline %d", id) | |
| 135 | + | } | |
| 136 | + | delete(s.timelines, id) | |
| 137 | + | if err := t.Destroy(); err != nil { | |
| 138 | + | return fmt.Errorf("neferclient: destroy timeline %d: %w", id, err) | |
| 139 | + | } | |
| 140 | + | return nil | |
| 141 | + | } | |
| 142 | + | ||
| 127 | 143 | // Present commits an imported buffer. It refuses before the first configure, | |
| 128 | 144 | // before the first complete dmabuf feedback and while the previous frame | |
| 129 | 145 | // callback is pending. | |
| @@ -124,6 +124,22 @@ func (s *Surface) ImportTimeline(id uint64, fd int) error { | |||||
| 124 | return nil | 124 | return nil | ||
| 125 | } | 125 | } | ||
| 126 | 126 | ||||
| 127 | + | // DestroyTimeline destroys an imported timeline object. The id is the one given | |||
| 128 | + | // to [Surface.ImportTimeline]; it may be imported again afterwards. Do not | |||
| 129 | + | // destroy a timeline that a pending commit still references. Surface.Close | |||
| 130 | + | // destroys every remaining timeline. | |||
| 131 | + | func (s *Surface) DestroyTimeline(id uint64) error { | |||
| 132 | + | t, ok := s.timelines[id] | |||
| 133 | + | if !ok { | |||
| 134 | + | return fmt.Errorf("neferclient: unknown timeline %d", id) | |||
| 135 | + | } | |||
| 136 | + | delete(s.timelines, id) | |||
| 137 | + | if err := t.Destroy(); err != nil { | |||
| 138 | + | return fmt.Errorf("neferclient: destroy timeline %d: %w", id, err) | |||
| 139 | + | } | |||
| 140 | + | return nil | |||
| 141 | + | } | |||
| 142 | + | ||||
| 127 | // Present commits an imported buffer. It refuses before the first configure, | 143 | // Present commits an imported buffer. It refuses before the first configure, | ||
| 128 | // before the first complete dmabuf feedback and while the previous frame | 144 | // before the first complete dmabuf feedback and while the previous frame | ||
| 129 | // callback is pending. | 145 | // callback is pending. | ||
M surface_wire_test.go +28 -0
| @@ -58,6 +58,34 @@ func TestImportErrorPathsCloseDupsOnce(t *testing.T) { | |||
| 58 | 58 | require.Equal(t, before, openFDs(t)) | |
| 59 | 59 | } | |
| 60 | 60 | ||
| 61 | + | // DestroyTimeline sends wp_linux_drm_syncobj_timeline_v1.destroy (opcode 0) for | |
| 62 | + | // the imported timeline, refuses an unknown id and allows importing it again. | |
| 63 | + | func TestDestroyTimeline(t *testing.T) { | |
| 64 | + | c, srv := connectWire(t, surfaceGlobals, surfaceOutputs) | |
| 65 | + | s, err := c.NewToplevel("tl", 64, 48) | |
| 66 | + | require.NoError(t, err) | |
| 67 | + | memfd, err := unix.MemfdCreate("tl", unix.MFD_CLOEXEC) | |
| 68 | + | require.NoError(t, err) | |
| 69 | + | defer unix.Close(memfd) | |
| 70 | + | require.NoError(t, s.ImportTimeline(7, memfd)) | |
| 71 | + | require.NoError(t, c.Roundtrip()) | |
| 72 | + | ||
| 73 | + | var destroys atomic.Int32 | |
| 74 | + | srv.mu.Lock() | |
| 75 | + | srv.hook = func(_ uint32, op uint16, _ []byte) { | |
| 76 | + | if op == 0 { | |
| 77 | + | destroys.Add(1) | |
| 78 | + | } | |
| 79 | + | } | |
| 80 | + | srv.mu.Unlock() | |
| 81 | + | require.NoError(t, s.DestroyTimeline(7)) | |
| 82 | + | require.NoError(t, c.Roundtrip()) | |
| 83 | + | require.Equal(t, int32(1), destroys.Load(), "one destroy request") | |
| 84 | + | require.ErrorContains(t, s.DestroyTimeline(7), "unknown timeline 7") | |
| 85 | + | require.ErrorContains(t, s.DestroyTimeline(99), "unknown timeline 99") | |
| 86 | + | require.NoError(t, s.ImportTimeline(7, memfd), "the id is free again") | |
| 87 | + | } | |
| 88 | + | ||
| 61 | 89 | // requestLog counts the commits the wire peer sees. | |
| 62 | 90 | type requestLog struct{ commits atomic.Int32 } | |
| 63 | 91 | ||
| @@ -58,6 +58,34 @@ func TestImportErrorPathsCloseDupsOnce(t *testing.T) { | |||||
| 58 | require.Equal(t, before, openFDs(t)) | 58 | require.Equal(t, before, openFDs(t)) | ||
| 59 | } | 59 | } | ||
| 60 | 60 | ||||
| 61 | + | // DestroyTimeline sends wp_linux_drm_syncobj_timeline_v1.destroy (opcode 0) for | |||
| 62 | + | // the imported timeline, refuses an unknown id and allows importing it again. | |||
| 63 | + | func TestDestroyTimeline(t *testing.T) { | |||
| 64 | + | c, srv := connectWire(t, surfaceGlobals, surfaceOutputs) | |||
| 65 | + | s, err := c.NewToplevel("tl", 64, 48) | |||
| 66 | + | require.NoError(t, err) | |||
| 67 | + | memfd, err := unix.MemfdCreate("tl", unix.MFD_CLOEXEC) | |||
| 68 | + | require.NoError(t, err) | |||
| 69 | + | defer unix.Close(memfd) | |||
| 70 | + | require.NoError(t, s.ImportTimeline(7, memfd)) | |||
| 71 | + | require.NoError(t, c.Roundtrip()) | |||
| 72 | + | ||||
| 73 | + | var destroys atomic.Int32 | |||
| 74 | + | srv.mu.Lock() | |||
| 75 | + | srv.hook = func(_ uint32, op uint16, _ []byte) { | |||
| 76 | + | if op == 0 { | |||
| 77 | + | destroys.Add(1) | |||
| 78 | + | } | |||
| 79 | + | } | |||
| 80 | + | srv.mu.Unlock() | |||
| 81 | + | require.NoError(t, s.DestroyTimeline(7)) | |||
| 82 | + | require.NoError(t, c.Roundtrip()) | |||
| 83 | + | require.Equal(t, int32(1), destroys.Load(), "one destroy request") | |||
| 84 | + | require.ErrorContains(t, s.DestroyTimeline(7), "unknown timeline 7") | |||
| 85 | + | require.ErrorContains(t, s.DestroyTimeline(99), "unknown timeline 99") | |||
| 86 | + | require.NoError(t, s.ImportTimeline(7, memfd), "the id is free again") | |||
| 87 | + | } | |||
| 88 | + | ||||
| 61 | // requestLog counts the commits the wire peer sees. | 89 | // requestLog counts the commits the wire peer sees. | ||
| 62 | type requestLog struct{ commits atomic.Int32 } | 90 | type requestLog struct{ commits atomic.Int32 } | ||
| 63 | 91 | ||||