fix(wayland): fifo barrier deadline, dead or role-less queued buffers, clamped timestamps
- commit
- b7541350ca36c23071c1e454561db997c82984e8
- author
- brice · · 2026-09-25 19:15 UTC
- parent
- d5fa86c
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +92 -18
M internal/adapters/wayland/fifo.go +47 -18
| @@ -122,16 +122,31 @@ func (h *timerHandler) SetTimestamp(r *committiming.WpCommitTimerV1, secHi, secL | |||
| 122 | 122 | case !h.surface.pendingTime.IsZero(): | |
| 123 | 123 | r.PostError(uint32(committiming.WpCommitTimerV1ErrorTimestampExists), "timestamp already set") | |
| 124 | 124 | default: | |
| 125 | - | h.surface.pendingTime = monotonicTime(int64(secHi)<<32|int64(secLo), int64(nsec)) | |
| 125 | + | h.surface.pendingTime = monotonicTime(uint64(secHi)<<32|uint64(secLo), int64(nsec)) | |
| 126 | 126 | } | |
| 127 | 127 | } | |
| 128 | 128 | func (*timerHandler) Destroy(*committiming.WpCommitTimerV1) {} | |
| 129 | 129 | ||
| 130 | - | // monotonicTime converts a CLOCK_MONOTONIC time to wall time. | |
| 131 | - | func monotonicTime(sec, nsec int64) time.Time { | |
| 130 | + | // maxTimestampAhead bounds how far a commit timestamp can hold a commit. | |
| 131 | + | const maxTimestampAhead = time.Second | |
| 132 | + | ||
| 133 | + | // monotonicTime converts a CLOCK_MONOTONIC time to wall time, at most | |
| 134 | + | // maxTimestampAhead from now: a far timestamp must not stall the surface. | |
| 135 | + | func monotonicTime(sec uint64, nsec int64) time.Time { | |
| 132 | 136 | var now unix.Timespec | |
| 133 | - | _ = unix.ClockGettime(unix.CLOCK_MONOTONIC, &now) | |
| 134 | - | return time.Now().Add(time.Duration((sec-now.Sec)*1e9 + nsec - now.Nsec)) | |
| 137 | + | if unix.ClockGettime(unix.CLOCK_MONOTONIC, &now) != nil { | |
| 138 | + | return time.Now() | |
| 139 | + | } | |
| 140 | + | ahead := time.Duration(nsec - now.Nsec) | |
| 141 | + | switch n := uint64(now.Sec); { | |
| 142 | + | case sec >= n+2: | |
| 143 | + | ahead = maxTimestampAhead | |
| 144 | + | case sec+2 <= n: | |
| 145 | + | ahead = 0 // in the past: apply now | |
| 146 | + | default: | |
| 147 | + | ahead += time.Duration(int64(sec)-now.Sec) * time.Second | |
| 148 | + | } | |
| 149 | + | return time.Now().Add(min(max(ahead, 0), maxTimestampAhead)) | |
| 135 | 150 | } | |
| 136 | 151 | ||
| 137 | 152 | // content_type_v1 | |
| @@ -279,19 +294,25 @@ func (s *surface) queueUpdate() { | |||
| 279 | 294 | ||
| 280 | 295 | // applyUpdate applies a queued update, keeping the requests made since. | |
| 281 | 296 | func (s *surface) applyUpdate(u update) { | |
| 297 | + | // A buffer destroyed while queued (a swapchain resize) shows nothing. | |
| 298 | + | if u.buffer != nil && !u.buffer.Resource.Alive() { | |
| 299 | + | u.buffer = nil | |
| 300 | + | } | |
| 282 | 301 | later := s.takePending() | |
| 283 | 302 | s.putPending(u) | |
| 284 | 303 | s.applyCommit() | |
| 285 | 304 | s.putPending(later) | |
| 286 | 305 | } | |
| 287 | 306 | ||
| 288 | - | // dropQueue discards the queued updates of a destroyed surface. | |
| 307 | + | // dropQueue discards the queued updates of a destroyed surface or role. | |
| 289 | 308 | func (s *surface) dropQueue() { | |
| 309 | + | released := map[*server.Resource]bool{} | |
| 290 | 310 | for _, u := range s.queue { | |
| 291 | 311 | for _, cb := range u.callbacks { | |
| 292 | 312 | cb.Destroy() | |
| 293 | 313 | } | |
| 294 | - | if u.buffer != nil && u.buffer != s.current && u.buffer.Resource.Alive() { | |
| 314 | + | if u.buffer != nil && !sameBuffer(u.buffer, s.current) && !released[u.buffer.Resource] && u.buffer.Resource.Alive() { | |
| 315 | + | released[u.buffer.Resource] = true | |
| 295 | 316 | u.buffer.SendRelease() | |
| 296 | 317 | } | |
| 297 | 318 | } | |
| @@ -329,23 +350,27 @@ func (s *Server) outputPeriod(name string) time.Duration { | |||
| 329 | 350 | // clear it after one refresh, or 1.5 when they usually flip. It returns | |
| 330 | 351 | // the time to the next deadline and whether any surface still waits. | |
| 331 | 352 | func (s *Server) tickFifo(now time.Time, flipped map[string]bool) (time.Duration, bool) { | |
| 332 | - | for name := range flipped { | |
| 333 | - | if flipped[name] { | |
| 353 | + | for name, f := range flipped { | |
| 354 | + | if f { | |
| 334 | 355 | s.lastFlip[name] = now | |
| 335 | 356 | } | |
| 336 | 357 | } | |
| 358 | + | for name := range s.lastFlip { | |
| 359 | + | if s.outputByNameExact(name) == nil { | |
| 360 | + | delete(s.lastFlip, name) | |
| 361 | + | } | |
| 362 | + | } | |
| 337 | 363 | wait := defaultFramePeriod | |
| 338 | 364 | for surf := range s.fifoSurfaces { | |
| 339 | 365 | name := s.fifoOutput(surf) | |
| 340 | 366 | p := s.outputPeriod(name) | |
| 341 | - | if surf.barrier { | |
| 342 | - | deadline := surf.barrierAt.Add(p) | |
| 343 | - | if name != "" && now.Sub(s.lastFlip[name]) < time.Second { | |
| 344 | - | deadline = deadline.Add(p / 2) | |
| 345 | - | } | |
| 346 | - | if (flipped[name] && name != "" && s.lastFlip[name].After(surf.barrierAt)) || !now.Before(deadline) { | |
| 347 | - | surf.barrier = false | |
| 348 | - | } | |
| 367 | + | // The deadline of outputs that usually flip only catches a missed flip. | |
| 368 | + | deadline := surf.barrierAt.Add(p) | |
| 369 | + | if name != "" && now.Sub(s.lastFlip[name]) < time.Second { | |
| 370 | + | deadline = deadline.Add(p / 2) | |
| 371 | + | } | |
| 372 | + | if surf.barrier && ((flipped[name] && name != "" && s.lastFlip[name].After(surf.barrierAt)) || !now.Before(deadline)) { | |
| 373 | + | surf.barrier = false | |
| 349 | 374 | } | |
| 350 | 375 | for len(surf.queue) > 0 && !surf.destroyed { | |
| 351 | 376 | u := surf.queue[0] | |
| @@ -360,7 +385,6 @@ func (s *Server) tickFifo(now time.Time, flipped map[string]bool) (time.Duration | |||
| 360 | 385 | surf.applyUpdate(u) | |
| 361 | 386 | } | |
| 362 | 387 | if surf.barrier { | |
| 363 | - | deadline := surf.barrierAt.Add(p) | |
| 364 | 388 | wait = min(wait, deadline.Sub(now)) | |
| 365 | 389 | } | |
| 366 | 390 | if !surf.barrier && len(surf.queue) == 0 { | |
| @@ -370,6 +394,11 @@ func (s *Server) tickFifo(now time.Time, flipped map[string]bool) (time.Duration | |||
| 370 | 394 | return max(wait, time.Millisecond), len(s.fifoSurfaces) > 0 | |
| 371 | 395 | } | |
| 372 | 396 | ||
| 397 | + | // sameBuffer reports whether two wrappers are the same wl_buffer. | |
| 398 | + | func sameBuffer(a, b *wayland.Buffer) bool { | |
| 399 | + | return a != nil && b != nil && a.Resource == b.Resource | |
| 400 | + | } | |
| 401 | + | ||
| 373 | 402 | // wakePacer makes the pacer look at its queues now. | |
| 374 | 403 | func (s *Server) wakePacer() { | |
| 375 | 404 | select { | |
| @@ -122,16 +122,31 @@ func (h *timerHandler) SetTimestamp(r *committiming.WpCommitTimerV1, secHi, secL | |||||
| 122 | case !h.surface.pendingTime.IsZero(): | 122 | case !h.surface.pendingTime.IsZero(): | ||
| 123 | r.PostError(uint32(committiming.WpCommitTimerV1ErrorTimestampExists), "timestamp already set") | 123 | r.PostError(uint32(committiming.WpCommitTimerV1ErrorTimestampExists), "timestamp already set") | ||
| 124 | default: | 124 | default: | ||
| 125 | - | h.surface.pendingTime = monotonicTime(int64(secHi)<<32|int64(secLo), int64(nsec)) | 125 | + | h.surface.pendingTime = monotonicTime(uint64(secHi)<<32|uint64(secLo), int64(nsec)) |
| 126 | } | 126 | } | ||
| 127 | } | 127 | } | ||
| 128 | func (*timerHandler) Destroy(*committiming.WpCommitTimerV1) {} | 128 | func (*timerHandler) Destroy(*committiming.WpCommitTimerV1) {} | ||
| 129 | 129 | ||||
| 130 | - | // monotonicTime converts a CLOCK_MONOTONIC time to wall time. | 130 | + | // maxTimestampAhead bounds how far a commit timestamp can hold a commit. |
| 131 | - | func monotonicTime(sec, nsec int64) time.Time { | 131 | + | const maxTimestampAhead = time.Second |
| 132 | + | ||||
| 133 | + | // monotonicTime converts a CLOCK_MONOTONIC time to wall time, at most | |||
| 134 | + | // maxTimestampAhead from now: a far timestamp must not stall the surface. | |||
| 135 | + | func monotonicTime(sec uint64, nsec int64) time.Time { | |||
| 132 | var now unix.Timespec | 136 | var now unix.Timespec | ||
| 133 | - | _ = unix.ClockGettime(unix.CLOCK_MONOTONIC, &now) | 137 | + | if unix.ClockGettime(unix.CLOCK_MONOTONIC, &now) != nil { |
| 134 | - | return time.Now().Add(time.Duration((sec-now.Sec)*1e9 + nsec - now.Nsec)) | 138 | + | return time.Now() |
| 139 | + | } | |||
| 140 | + | ahead := time.Duration(nsec - now.Nsec) | |||
| 141 | + | switch n := uint64(now.Sec); { | |||
| 142 | + | case sec >= n+2: | |||
| 143 | + | ahead = maxTimestampAhead | |||
| 144 | + | case sec+2 <= n: | |||
| 145 | + | ahead = 0 // in the past: apply now | |||
| 146 | + | default: | |||
| 147 | + | ahead += time.Duration(int64(sec)-now.Sec) * time.Second | |||
| 148 | + | } | |||
| 149 | + | return time.Now().Add(min(max(ahead, 0), maxTimestampAhead)) | |||
| 135 | } | 150 | } | ||
| 136 | 151 | ||||
| 137 | // content_type_v1 | 152 | // content_type_v1 | ||
| @@ -279,19 +294,25 @@ func (s *surface) queueUpdate() { | |||||
| 279 | 294 | ||||
| 280 | // applyUpdate applies a queued update, keeping the requests made since. | 295 | // applyUpdate applies a queued update, keeping the requests made since. | ||
| 281 | func (s *surface) applyUpdate(u update) { | 296 | func (s *surface) applyUpdate(u update) { | ||
| 297 | + | // A buffer destroyed while queued (a swapchain resize) shows nothing. | |||
| 298 | + | if u.buffer != nil && !u.buffer.Resource.Alive() { | |||
| 299 | + | u.buffer = nil | |||
| 300 | + | } | |||
| 282 | later := s.takePending() | 301 | later := s.takePending() | ||
| 283 | s.putPending(u) | 302 | s.putPending(u) | ||
| 284 | s.applyCommit() | 303 | s.applyCommit() | ||
| 285 | s.putPending(later) | 304 | s.putPending(later) | ||
| 286 | } | 305 | } | ||
| 287 | 306 | ||||
| 288 | - | // dropQueue discards the queued updates of a destroyed surface. | 307 | + | // dropQueue discards the queued updates of a destroyed surface or role. |
| 289 | func (s *surface) dropQueue() { | 308 | func (s *surface) dropQueue() { | ||
| 309 | + | released := map[*server.Resource]bool{} | |||
| 290 | for _, u := range s.queue { | 310 | for _, u := range s.queue { | ||
| 291 | for _, cb := range u.callbacks { | 311 | for _, cb := range u.callbacks { | ||
| 292 | cb.Destroy() | 312 | cb.Destroy() | ||
| 293 | } | 313 | } | ||
| 294 | - | if u.buffer != nil && u.buffer != s.current && u.buffer.Resource.Alive() { | 314 | + | if u.buffer != nil && !sameBuffer(u.buffer, s.current) && !released[u.buffer.Resource] && u.buffer.Resource.Alive() { |
| 315 | + | released[u.buffer.Resource] = true | |||
| 295 | u.buffer.SendRelease() | 316 | u.buffer.SendRelease() | ||
| 296 | } | 317 | } | ||
| 297 | } | 318 | } | ||
| @@ -329,23 +350,27 @@ func (s *Server) outputPeriod(name string) time.Duration { | |||||
| 329 | // clear it after one refresh, or 1.5 when they usually flip. It returns | 350 | // clear it after one refresh, or 1.5 when they usually flip. It returns | ||
| 330 | // the time to the next deadline and whether any surface still waits. | 351 | // the time to the next deadline and whether any surface still waits. | ||
| 331 | func (s *Server) tickFifo(now time.Time, flipped map[string]bool) (time.Duration, bool) { | 352 | func (s *Server) tickFifo(now time.Time, flipped map[string]bool) (time.Duration, bool) { | ||
| 332 | - | for name := range flipped { | 353 | + | for name, f := range flipped { |
| 333 | - | if flipped[name] { | 354 | + | if f { |
| 334 | s.lastFlip[name] = now | 355 | s.lastFlip[name] = now | ||
| 335 | } | 356 | } | ||
| 336 | } | 357 | } | ||
| 358 | + | for name := range s.lastFlip { | |||
| 359 | + | if s.outputByNameExact(name) == nil { | |||
| 360 | + | delete(s.lastFlip, name) | |||
| 361 | + | } | |||
| 362 | + | } | |||
| 337 | wait := defaultFramePeriod | 363 | wait := defaultFramePeriod | ||
| 338 | for surf := range s.fifoSurfaces { | 364 | for surf := range s.fifoSurfaces { | ||
| 339 | name := s.fifoOutput(surf) | 365 | name := s.fifoOutput(surf) | ||
| 340 | p := s.outputPeriod(name) | 366 | p := s.outputPeriod(name) | ||
| 341 | - | if surf.barrier { | 367 | + | // The deadline of outputs that usually flip only catches a missed flip. |
| 342 | - | deadline := surf.barrierAt.Add(p) | 368 | + | deadline := surf.barrierAt.Add(p) |
| 343 | - | if name != "" && now.Sub(s.lastFlip[name]) < time.Second { | 369 | + | if name != "" && now.Sub(s.lastFlip[name]) < time.Second { |
| 344 | - | deadline = deadline.Add(p / 2) | 370 | + | deadline = deadline.Add(p / 2) |
| 345 | - | } | 371 | + | } |
| 346 | - | if (flipped[name] && name != "" && s.lastFlip[name].After(surf.barrierAt)) || !now.Before(deadline) { | 372 | + | if surf.barrier && ((flipped[name] && name != "" && s.lastFlip[name].After(surf.barrierAt)) || !now.Before(deadline)) { |
| 347 | - | surf.barrier = false | 373 | + | surf.barrier = false |
| 348 | - | } | |||
| 349 | } | 374 | } | ||
| 350 | for len(surf.queue) > 0 && !surf.destroyed { | 375 | for len(surf.queue) > 0 && !surf.destroyed { | ||
| 351 | u := surf.queue[0] | 376 | u := surf.queue[0] | ||
| @@ -360,7 +385,6 @@ func (s *Server) tickFifo(now time.Time, flipped map[string]bool) (time.Duration | |||||
| 360 | surf.applyUpdate(u) | 385 | surf.applyUpdate(u) | ||
| 361 | } | 386 | } | ||
| 362 | if surf.barrier { | 387 | if surf.barrier { | ||
| 363 | - | deadline := surf.barrierAt.Add(p) | |||
| 364 | wait = min(wait, deadline.Sub(now)) | 388 | wait = min(wait, deadline.Sub(now)) | ||
| 365 | } | 389 | } | ||
| 366 | if !surf.barrier && len(surf.queue) == 0 { | 390 | if !surf.barrier && len(surf.queue) == 0 { | ||
| @@ -370,6 +394,11 @@ func (s *Server) tickFifo(now time.Time, flipped map[string]bool) (time.Duration | |||||
| 370 | return max(wait, time.Millisecond), len(s.fifoSurfaces) > 0 | 394 | return max(wait, time.Millisecond), len(s.fifoSurfaces) > 0 | ||
| 371 | } | 395 | } | ||
| 372 | 396 | ||||
| 397 | + | // sameBuffer reports whether two wrappers are the same wl_buffer. | |||
| 398 | + | func sameBuffer(a, b *wayland.Buffer) bool { | |||
| 399 | + | return a != nil && b != nil && a.Resource == b.Resource | |||
| 400 | + | } | |||
| 401 | + | ||||
| 373 | // wakePacer makes the pacer look at its queues now. | 402 | // wakePacer makes the pacer look at its queues now. | ||
| 374 | func (s *Server) wakePacer() { | 403 | func (s *Server) wakePacer() { | ||
| 375 | select { | 404 | select { | ||
M internal/adapters/wayland/fifo_test.go +42 -0
| @@ -128,3 +128,45 @@ func TestCommitTimingDelaysCommit(t *testing.T) { | |||
| 128 | 128 | requestProtocol(t, c, timer, committiming.WpCommitTimerV1RequestSetTimestamp, uint32(0), uint32(1), uint32(0)) | |
| 129 | 129 | expectProtocolError(t, c, timer, uint32(committiming.WpCommitTimerV1ErrorTimestampExists)) | |
| 130 | 130 | } | |
| 131 | + | ||
| 132 | + | // Far or past timestamps never hold a surface longer than a second. | |
| 133 | + | func TestMonotonicTimeClamps(t *testing.T) { | |
| 134 | + | var now unix.Timespec | |
| 135 | + | if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &now); err != nil { | |
| 136 | + | t.Fatal(err) | |
| 137 | + | } | |
| 138 | + | for _, tc := range []struct { | |
| 139 | + | sec uint64 | |
| 140 | + | nsec int64 | |
| 141 | + | lo, hi time.Duration | |
| 142 | + | }{ | |
| 143 | + | {^uint64(0), 0, maxTimestampAhead - 10*time.Millisecond, maxTimestampAhead}, | |
| 144 | + | {0, 0, -10 * time.Millisecond, 10 * time.Millisecond}, | |
| 145 | + | {uint64(now.Sec), now.Nsec, -10 * time.Millisecond, 10 * time.Millisecond}, | |
| 146 | + | } { | |
| 147 | + | got := time.Until(monotonicTime(tc.sec, tc.nsec)) | |
| 148 | + | if got < tc.lo || got > tc.hi { | |
| 149 | + | t.Errorf("sec %d: %v not in [%v, %v]", tc.sec, got, tc.lo, tc.hi) | |
| 150 | + | } | |
| 151 | + | } | |
| 152 | + | } | |
| 153 | + | ||
| 154 | + | // A barrier on an output that flips clears on its next flip. | |
| 155 | + | func TestTickFifoClearsBarrierOnFlip(t *testing.T) { | |
| 156 | + | out := &output{place: ports.OutputPlacement{Info: ports.OutputInfo{Name: "DP-2", RefreshMilli: 60000}}} | |
| 157 | + | s := &Server{outputs: []*output{out}, fifoSurfaces: map[*surface]struct{}{}, lastFlip: map[string]time.Time{}, frameReady: make(chan struct{}, 1)} | |
| 158 | + | surf := &surface{server: s, xdg: &xdgSurface{window: &window{last: ports.ConfigureWindow{Output: "DP-2"}}}} | |
| 159 | + | now := time.Unix(100, 0) | |
| 160 | + | s.lastFlip["DP-2"] = now.Add(-time.Millisecond) | |
| 161 | + | surf.setBarrier(now) | |
| 162 | + | if _, waiting := s.tickFifo(now.Add(time.Millisecond), nil); !waiting || !surf.barrier { | |
| 163 | + | t.Fatal("barrier cleared before the flip") | |
| 164 | + | } | |
| 165 | + | // A flipping output waits up to 1.5 refreshes for a missed flip. | |
| 166 | + | if w, _ := s.tickFifo(now.Add(20*time.Millisecond), nil); !surf.barrier || w < 4*time.Millisecond { | |
| 167 | + | t.Fatalf("barrier %v wait %v", surf.barrier, w) | |
| 168 | + | } | |
| 169 | + | if _, waiting := s.tickFifo(now.Add(21*time.Millisecond), map[string]bool{"DP-2": true}); waiting || surf.barrier { | |
| 170 | + | t.Fatal("barrier kept after the flip") | |
| 171 | + | } | |
| 172 | + | } | |
| @@ -128,3 +128,45 @@ func TestCommitTimingDelaysCommit(t *testing.T) { | |||||
| 128 | requestProtocol(t, c, timer, committiming.WpCommitTimerV1RequestSetTimestamp, uint32(0), uint32(1), uint32(0)) | 128 | requestProtocol(t, c, timer, committiming.WpCommitTimerV1RequestSetTimestamp, uint32(0), uint32(1), uint32(0)) | ||
| 129 | expectProtocolError(t, c, timer, uint32(committiming.WpCommitTimerV1ErrorTimestampExists)) | 129 | expectProtocolError(t, c, timer, uint32(committiming.WpCommitTimerV1ErrorTimestampExists)) | ||
| 130 | } | 130 | } | ||
| 131 | + | ||||
| 132 | + | // Far or past timestamps never hold a surface longer than a second. | |||
| 133 | + | func TestMonotonicTimeClamps(t *testing.T) { | |||
| 134 | + | var now unix.Timespec | |||
| 135 | + | if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &now); err != nil { | |||
| 136 | + | t.Fatal(err) | |||
| 137 | + | } | |||
| 138 | + | for _, tc := range []struct { | |||
| 139 | + | sec uint64 | |||
| 140 | + | nsec int64 | |||
| 141 | + | lo, hi time.Duration | |||
| 142 | + | }{ | |||
| 143 | + | {^uint64(0), 0, maxTimestampAhead - 10*time.Millisecond, maxTimestampAhead}, | |||
| 144 | + | {0, 0, -10 * time.Millisecond, 10 * time.Millisecond}, | |||
| 145 | + | {uint64(now.Sec), now.Nsec, -10 * time.Millisecond, 10 * time.Millisecond}, | |||
| 146 | + | } { | |||
| 147 | + | got := time.Until(monotonicTime(tc.sec, tc.nsec)) | |||
| 148 | + | if got < tc.lo || got > tc.hi { | |||
| 149 | + | t.Errorf("sec %d: %v not in [%v, %v]", tc.sec, got, tc.lo, tc.hi) | |||
| 150 | + | } | |||
| 151 | + | } | |||
| 152 | + | } | |||
| 153 | + | ||||
| 154 | + | // A barrier on an output that flips clears on its next flip. | |||
| 155 | + | func TestTickFifoClearsBarrierOnFlip(t *testing.T) { | |||
| 156 | + | out := &output{place: ports.OutputPlacement{Info: ports.OutputInfo{Name: "DP-2", RefreshMilli: 60000}}} | |||
| 157 | + | s := &Server{outputs: []*output{out}, fifoSurfaces: map[*surface]struct{}{}, lastFlip: map[string]time.Time{}, frameReady: make(chan struct{}, 1)} | |||
| 158 | + | surf := &surface{server: s, xdg: &xdgSurface{window: &window{last: ports.ConfigureWindow{Output: "DP-2"}}}} | |||
| 159 | + | now := time.Unix(100, 0) | |||
| 160 | + | s.lastFlip["DP-2"] = now.Add(-time.Millisecond) | |||
| 161 | + | surf.setBarrier(now) | |||
| 162 | + | if _, waiting := s.tickFifo(now.Add(time.Millisecond), nil); !waiting || !surf.barrier { | |||
| 163 | + | t.Fatal("barrier cleared before the flip") | |||
| 164 | + | } | |||
| 165 | + | // A flipping output waits up to 1.5 refreshes for a missed flip. | |||
| 166 | + | if w, _ := s.tickFifo(now.Add(20*time.Millisecond), nil); !surf.barrier || w < 4*time.Millisecond { | |||
| 167 | + | t.Fatalf("barrier %v wait %v", surf.barrier, w) | |||
| 168 | + | } | |||
| 169 | + | if _, waiting := s.tickFifo(now.Add(21*time.Millisecond), map[string]bool{"DP-2": true}); waiting || surf.barrier { | |||
| 170 | + | t.Fatal("barrier kept after the flip") | |||
| 171 | + | } | |||
| 172 | + | } | |||
M internal/adapters/wayland/layer.go +1 -0
| @@ -76,6 +76,7 @@ func (h layerShell) GetLayerSurface(r *wlrlayershell.ZwlrLayerShellV1, id uint32 | |||
| 76 | 76 | delete(h.server.layers, l.id) | |
| 77 | 77 | state.layer = nil | |
| 78 | 78 | state.role = nil | |
| 79 | + | state.dropQueue() | |
| 79 | 80 | state.current, state.pending = nil, nil | |
| 80 | 81 | state.attached = false | |
| 81 | 82 | } | |
| @@ -76,6 +76,7 @@ func (h layerShell) GetLayerSurface(r *wlrlayershell.ZwlrLayerShellV1, id uint32 | |||||
| 76 | delete(h.server.layers, l.id) | 76 | delete(h.server.layers, l.id) | ||
| 77 | state.layer = nil | 77 | state.layer = nil | ||
| 78 | state.role = nil | 78 | state.role = nil | ||
| 79 | + | state.dropQueue() | |||
| 79 | state.current, state.pending = nil, nil | 80 | state.current, state.pending = nil, nil | ||
| 80 | state.attached = false | 81 | state.attached = false | ||
| 81 | } | 82 | } | ||
M internal/adapters/wayland/popup.go +1 -0
| @@ -75,6 +75,7 @@ func (s *Server) newPopup(x *xdgSurface, r *xdgshell.Surface, id uint32, parentX | |||
| 75 | 75 | } | |
| 76 | 76 | w.unmap() | |
| 77 | 77 | pp.done = true | |
| 78 | + | x.surface.dropQueue() | |
| 78 | 79 | x.surface.current, x.surface.pending = nil, nil | |
| 79 | 80 | x.surface.attached = false | |
| 80 | 81 | delete(s.windows, w.id) | |
| @@ -75,6 +75,7 @@ func (s *Server) newPopup(x *xdgSurface, r *xdgshell.Surface, id uint32, parentX | |||||
| 75 | } | 75 | } | ||
| 76 | w.unmap() | 76 | w.unmap() | ||
| 77 | pp.done = true | 77 | pp.done = true | ||
| 78 | + | x.surface.dropQueue() | |||
| 78 | x.surface.current, x.surface.pending = nil, nil | 79 | x.surface.current, x.surface.pending = nil, nil | ||
| 79 | x.surface.attached = false | 80 | x.surface.attached = false | ||
| 80 | delete(s.windows, w.id) | 81 | delete(s.windows, w.id) | ||
M internal/adapters/wayland/xdg.go +1 -0
| @@ -222,6 +222,7 @@ func (x *xdgSurface) GetToplevel(r *xdgshell.Surface, id uint32) { | |||
| 222 | 222 | } | |
| 223 | 223 | t.OnDestroy = func() { | |
| 224 | 224 | w.unmap() | |
| 225 | + | x.surface.dropQueue() | |
| 225 | 226 | x.surface.current, x.surface.pending = nil, nil | |
| 226 | 227 | x.surface.attached = false | |
| 227 | 228 | delete(x.server.windows, w.id) | |
| @@ -222,6 +222,7 @@ func (x *xdgSurface) GetToplevel(r *xdgshell.Surface, id uint32) { | |||||
| 222 | } | 222 | } | ||
| 223 | t.OnDestroy = func() { | 223 | t.OnDestroy = func() { | ||
| 224 | w.unmap() | 224 | w.unmap() | ||
| 225 | + | x.surface.dropQueue() | |||
| 225 | x.surface.current, x.surface.pending = nil, nil | 226 | x.surface.current, x.surface.pending = nil, nil | ||
| 226 | x.surface.attached = false | 227 | x.surface.attached = false | ||
| 227 | delete(x.server.windows, w.id) | 228 | delete(x.server.windows, w.id) | ||