1package wayland
2
3import (
4 "image"
5 "math"
6 "os"
7 "slices"
8 "time"
9
10 "github.com/bnema/go-wayland-bindings/server/presentationtime"
11
12 "github.com/bnema/go-wayland-bindings/server/wayland"
13 "github.com/bnema/go-wayland-bindings/server/wlrlayershell"
14 "github.com/bnema/go-wayland-bindings/server/xdgshell"
15 "github.com/bnema/neferwl/internal/ports"
16)
17
18type roleKind uint8
19
20const (
21 roleNone roleKind = iota
22 roleXDG
23 roleSubsurface
24 roleLayer
25 roleCursor
26 roleInputPopup
27 roleSessionLock
28)
29
30type surface struct {
31 wl *wayland.Surface
32 viewport *viewport
33 committedViewport viewportState // value snapshot; no allocation on viewport commits
34 // next is the state requested since the last commit (see pendingCommit).
35 next pendingCommit
36 // syncScratch holds next.sync between takeSyncPoints and queueUpdate,
37 // which moves it into the update.
38 syncScratch commitSync
39 // bufferScale, transform and opaque are committed state. formatOpaque is
40 // the current buffer's own opacity (x formats).
41 bufferScale int
42 transform ports.BufferTransform
43 opaque []ports.Rect
44 formatOpaque bool
45 kind roleKind
46 xdg *xdgSurface
47 layer *layerSurface
48 lock *lockSurface
49 // bufferCommitted is permanent, including commits later discarded by FIFO.
50 bufferCommitted bool
51 server *Server
52 current *wayland.Buffer
53 lastW, lastH int // last logged buffer size
54 role func(bool)
55 destroyed bool
56 // hotX, hotY is the click point of a cursor surface (logical).
57 hotX, hotY int
58 // on is the output the surface entered; scale is the last scale sent.
59 on *output
60 scale float64
61 // prefTransform is the last preferred buffer transform sent (wl_surface
62 // v6+); zero (normal) is the protocol default.
63 prefTransform ports.BufferTransform
64 // content is the last content built from this surface's own buffer;
65 // has is false while no buffer is attached.
66 inputAll bool
67 inputRects []ports.Rect
68 sentInput, lastInputAll bool
69 lastInputRects []ports.Rect
70 content ports.SurfaceContent
71 has bool
72 identity uint64
73 version uint64
74 // cachedTree is never modified once returned to emitContent.
75 cachedTree []ports.Subsurface
76 treeDirty bool
77 commitFresh, commitSkipped bool
78 queuedScale int
79 queuedBuffer uint32
80 sub subState
81 // tearing is the surface's wp_tearing_control_v1; async the committed
82 // hint.
83 tearing *tearingHandler
84 async bool
85 colorControl *colorSurface
86 color SurfaceColor
87 representation surfaceRepresentation
88 representationControl *representationSurface
89 // Presentation constraints (fifo.go): the barrier, and the commits
90 // waiting to apply.
91 fifo *fifoHandler
92 timer *timerHandler
93 barrier bool
94 barrierAt time.Time
95 queue []*update
96 // contentType is the wp_content_type_v1; contentKind the committed type.
97 contentType *contentTypeHandler
98 // alpha is the surface's wp_alpha_modifier_surface_v1.
99 alpha *alphaHandler
100 contentKind uint32
101 // committed is what the last commit changed, in buffer pixels (full:
102 // everything), read by the window's damage history.
103 committed damage
104 // drewChildren is whether the root's last published tree drew a
105 // subsurface: one that stops drawing leaves its area to redraw.
106 drewChildren bool
107 // Explicit sync (syncobj.go): the surface's syncobj object and the
108 // current buffer's hold.
109 sync *syncState
110 hold syncHold
111}
112
113// pendingCommit is the double-buffered wl_surface state requested since the
114// last commit. A commit captures it whole into an update (takePending):
115// one-shot parts are cleared, sticky parts (scale, transform, hints, color)
116// carry over to the next commit.
117type pendingCommit struct {
118 // buffer is attached (attached reports an attach request, nil detaches).
119 buffer *wayland.Buffer
120 attached bool
121 // scale is set by set_buffer_scale, transform by set_buffer_transform.
122 scale int
123 transform ports.BufferTransform
124 // opaque and the input region are set by their requests (the Set flags).
125 opaque []ports.Rect
126 opaqueSet bool
127 inputAll, inputSet bool
128 inputRects []ports.Rect
129 callbacks []*wayland.Callback
130 async bool
131 color SurfaceColor
132 representation surfaceRepresentation
133 kind uint32
134 // fade is 1 - the wp_alpha_modifier_v1 multiplier (sticky).
135 fade float32
136 // barrier and wait are fifo requests; at the commit timestamp.
137 barrier, wait bool
138 at time.Time
139 // damage is in surface (logical) pixels, bufDamage in buffer pixels.
140 damage, bufDamage []ports.Rect
141 // feedback are wp_presentation feedbacks for this commit.
142 feedback []*presentationtime.WpPresentationFeedback
143 // sync is the commit's explicit-sync points, with the wait for its
144 // acquire point.
145 sync *commitSync
146}
147
148// commitSync is a commit's explicit-sync points and acquire wait.
149type commitSync struct {
150 acquire, release syncPoint
151 wait *syncWait
152 fence *os.File // exported before application; owned until apply/drop
153}
154
155// damage is what one commit changed in a surface's buffer.
156type damage struct {
157 full bool
158 rects []ports.Rect
159}
160
161// maxDamageRects bounds one commit's rects: past it the commit is full.
162const maxDamageRects = 32
163
164// subState is the subsurface tree: parent is set on subsurfaces, children
165// are ordered bottom to top, below marks children under their parent.
166type subState struct {
167 // role is the live wl_subsurface, nil when the surface has none.
168 role *wayland.Subsurface
169 parent *surface
170 children []*surface // structural children
171 pendingLayout, layout []childLayout
172 synced bool // explicit synchronization mode
173}
174
175// root is the top of the surface's subsurface tree.
176func (s *surface) root() *surface {
177 for s.sub.parent != nil {
178 s = s.sub.parent
179 }
180 return s
181}
182
183// windowID is the mapped window or layer the surface draws, or 0.
184func (s *surface) windowID() ports.WindowID {
185 if s.xdg != nil && s.xdg.window != nil && s.xdg.window.mapped {
186 return s.xdg.window.id
187 }
188 if s.layer != nil && s.layer.mapped {
189 return s.layer.id
190 }
191 if s.lock != nil && s.lock.mapped {
192 return s.lock.id
193 }
194 return 0
195}
196
197// outputColor converts the committed protocol description without allocating.
198func (s *surface) outputColor() ports.SurfaceColor {
199 return ports.SurfaceColor{TF: uint8(s.color.TF), Primaries: uint8(s.color.Primaries), MaxCLL: uint16(s.color.MaxCLL), MaxFALL: uint16(s.color.MaxFALL), Coefficients: s.representation.coefficients, Range: s.representation.rangeValue, Chroma: s.representation.chroma}
200}
201
202func (s *surface) contentWithColor() ports.SurfaceContent {
203 c := s.content
204 c.Surface, c.Version = s.identity, s.version
205 c.Color = s.outputColor()
206 // A subsurface asks for tearing itself: the output may scan it out.
207 c.Async = s.async
208 return c
209}
210
211// tree is the root's content with its subsurfaces flattened.
212func (s *surface) tree(id ports.WindowID) ports.SurfaceContent {
213 c := s.contentWithColor()
214 c.ID = id
215 c.ContentType = ports.ContentType(s.contentKind)
216 if s.treeDirty || s.cachedTree == nil {
217 count := s.treeCount()
218 children := make([]ports.Subsurface, 0, count)
219 for _, item := range s.sub.layout {
220 item.child.appendTree(&children, item.x, item.y, item.below)
221 }
222 s.cachedTree, s.treeDirty = children, false
223 }
224 c.Children = s.cachedTree
225 if s.xdg != nil {
226 c.Geometry = s.xdg.geometry
227 }
228 return c
229}
230
231func (s *surface) treeCount() int {
232 n := 0
233 for _, item := range s.sub.layout {
234 if item.child.has {
235 n++
236 }
237 n += item.child.treeCount()
238 }
239 return n
240}
241
242// appendTree flattens a subsurface at (x, y) from the root: its children
243// below it, itself, then its children above.
244func (s *surface) appendTree(out *[]ports.Subsurface, x, y int, below bool) {
245 for _, item := range s.sub.layout {
246 if item.below {
247 item.child.appendTree(out, x+item.x, y+item.y, below)
248 }
249 }
250 if s.has {
251 *out = append(*out, ports.Subsurface{X: x, Y: y, Below: below, SurfaceContent: s.contentWithColor()})
252 }
253 for _, item := range s.sub.layout {
254 if !item.below {
255 item.child.appendTree(out, x+item.x, y+item.y, below)
256 }
257 }
258}
259
260// redraw sends the tree of a mapped root to the outputs. Only a commit
261// of the root's own buffer, with no subsurface drawn before or now, keeps
262// partial damage: subsurfaces without a buffer (clients often keep one
263// for later) are not drawn, so they do not make it full.
264func (s *surface) redraw() {
265 r := s.root()
266 if id := r.windowID(); id != 0 && s.server.channels.Contents != nil {
267 c := r.tree(id)
268 d := damage{full: true}
269 if s == r && len(c.Children) == 0 && !r.drewChildren {
270 d = r.committed
271 }
272 r.drewChildren = len(c.Children) > 0
273 s.server.emitContent(c, d)
274 }
275}
276
277// detach removes a subsurface from its parent and redraws the parent.
278func (s *surface) detach() {
279 p := s.sub.parent
280 if p == nil {
281 return
282 }
283 for i, ch := range p.sub.children {
284 if ch == s {
285 p.sub.children = append(p.sub.children[:i:i], p.sub.children[i+1:]...)
286 break
287 }
288 }
289 p.sub.pendingLayout = removeLayout(p.sub.pendingLayout, s)
290 p.sub.layout = removeLayout(p.sub.layout, s)
291 p.root().treeDirty = true
292 for _, u := range p.queue {
293 u.layout = removeLayout(u.layout, s)
294 }
295 s.sub.parent = nil
296 // Parent updates cannot make a detached child's bound commits visible.
297 s.dropQueue()
298 s.flushDesync()
299 p.redraw()
300 p.emitInput()
301 s.sendScale()
302}
303
304func (s *surface) Destroy(*wayland.Surface) {
305 s.destroyed = true
306 for _, fb := range s.next.feedback {
307 discard(fb)
308 }
309 s.next.feedback = nil
310 s.tearing = nil // the control becomes inert
311 s.colorControl = nil
312 s.representationControl = nil
313 s.dropQueue()
314 if s.server.cursorSurface == s {
315 // The pointer keeps no cursor until the client sets another.
316 s.server.cursorSurface = nil
317 s.server.setCursor(ports.CursorChange{Hidden: true})
318 }
319 s.detach()
320 for _, ch := range s.sub.children {
321 ch.sub.parent = nil
322 ch.dropQueue()
323 ch.flushDesync()
324 }
325 s.sub.children = nil
326 s.sub.layout, s.sub.pendingLayout = nil, nil
327 for _, cb := range s.next.callbacks {
328 cb.Destroy()
329 }
330 s.next.callbacks = nil
331 // The client may reuse the buffer on another surface.
332 s.dropSync(s.next.sync)
333 s.next.sync = nil
334 if s.current != nil {
335 s.server.releaseBuffer(s, s.current, s.hold)
336 s.hold = syncHold{}
337 }
338 s.current, s.next.buffer = nil, nil
339 if s.role != nil {
340 s.role(false)
341 }
342 if c := s.server.constraints[s]; c != nil {
343 s.server.dropConstraint(c)
344 }
345}
346func (s *surface) Attach(_ *wayland.Surface, b *wayland.Buffer, _, _ int32) {
347 s.next.buffer = b
348 s.next.attached = true
349}
350func (s *surface) Frame(r *wayland.Surface, id uint32) {
351 cb, err := wayland.NewCallback(r.Client(), 1, id, struct{}{})
352 if err == nil {
353 s.next.callbacks = append(s.next.callbacks, cb)
354 }
355}
356func (s *surface) Commit(*wayland.Surface) {
357 if s.next.attached && s.next.buffer != nil {
358 s.bufferCommitted = true
359 }
360 if s.lock != nil && !s.lock.checkCommit() {
361 return
362 }
363 if s.layer != nil && s.next.attached && s.next.buffer != nil && !s.layer.acked {
364 s.layer.resource.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSurfaceState), "buffer before configure ack")
365 return
366 }
367 if s.xdg != nil && s.next.attached && s.next.buffer != nil && !s.xdg.acked {
368 s.xdg.resource.PostError(uint32(xdgshell.SurfaceErrorUnconfiguredBuffer), "buffer before initial configure ack")
369 return
370 }
371 if !s.checkSyncCommit() || !s.checkRepresentationCommit() {
372 return
373 }
374 s.takeSyncPoints()
375 // Each request creates an ordered content update. A synchronized update
376 // can only be reached through a parent's committed dependency graph.
377 s.queueAndTick()
378}
379
380// queueAndTick queues the commit and applies what is ready. The pacer is
381// woken at most once, after the work, and by a tick on this goroutine
382// rather than by a wake that makes the pacer call Do back.
383func (s *surface) queueAndTick() {
384 srv := s.server
385 srv.paceDepth++
386 s.queueUpdate()
387 srv.tickFifo(time.Now(), nil)
388 if n := len(s.queue); n > 0 && !s.queue[n-1].synced {
389 // The commit waits for a barrier, a fence or a refresh: the pacer
390 // must time it. A commit applied at once needs no tick of its own;
391 // its callbacks, barrier and held buffers wake the pacer where
392 // they are queued. A synchronized commit waits for its parent's
393 // commit, which ticks for it.
394 srv.wakePacer()
395 }
396 srv.paceDepth--
397 if srv.paceDepth == 0 && srv.paceDirty {
398 srv.paceDirty = false
399 srv.paceNow()
400 }
401}
402
403// applyCommit makes only the captured update current. Live pending requests
404// remain untouched, even when this update waited behind another commit.
405func (s *surface) applyCommit(u *update) {
406 oldW, oldH, oldSource, oldTransform := s.content.LogicalW, s.content.LogicalH, s.content.Source, s.content.Transform
407 oldColor, oldRepresentation := s.color, s.representation
408 fresh := u.attached && u.buffer != nil
409 s.commitFresh = fresh
410 if !s.commitSkipped {
411 s.queuedScale = u.scale
412 s.queuedBuffer = 0
413 if u.buffer != nil {
414 s.queuedBuffer = u.buffer.ID()
415 }
416 }
417 if u.barrier {
418 s.setBarrier(time.Now())
419 }
420 if s.contentKind != u.kind {
421 s.contentKind = u.kind
422 s.server.log.Info().Uint64("id", uint64(s.root().windowID())).Uint32("content_type", s.contentKind).Msg("content type")
423 }
424 if u.scale > 0 {
425 s.bufferScale = u.scale
426 }
427 s.transform = u.transform
428 oldOpaque, oldOpaqueRect := s.content.Opaque, s.content.OpaqueRect
429 if u.opaqueSet {
430 s.opaque = u.opaque
431 }
432 oldFade := s.content.Fade
433 hinted := s.async != u.async || s.color != u.color || s.representation != u.representation
434 if s.async != u.async {
435 // Contents carry the hint of every surface in the tree.
436 s.root().treeDirty = true
437 }
438 s.async = u.async
439 s.color = u.color
440 s.representation = u.representation
441 if s.lock != nil && s.commitSkipped {
442 // A discarded replacement cannot change retained lock geometry, even
443 // if its viewport was destroyed before capture or before application.
444 } else if u.vp != nil && (s.lock != nil || u.vp.resource != nil && u.vp.resource.Resource.Alive()) {
445 s.committedViewport.destW, s.committedViewport.destH, s.committedViewport.dest = u.vpW, u.vpH, u.vpSet
446 s.committedViewport.src, s.committedViewport.crop = u.vpSrc, u.vpCrop
447 } else {
448 s.committedViewport.dest, s.committedViewport.crop = false, false
449 }
450 cs := u.sync
451 if u.attached {
452 if s.current != nil && (u.buffer == nil || s.current.Resource != u.buffer.Resource) {
453 s.server.releaseBuffer(s, s.current, s.hold)
454 } else if cs != nil || u.buffer == nil {
455 // Same buffer again, or detached: the old points are done
456 // with once the new ones take over.
457 s.server.releaseBufferSync(s, s.hold)
458 }
459 s.hold = syncHold{}
460 s.current = u.buffer
461 }
462 s.applySync(cs)
463 if u.inputSet {
464 s.inputAll, s.inputRects = u.inputAll, u.inputRects
465 }
466 if len(u.callbacks) > 0 {
467 s.server.queueFrames(s, u.callbacks)
468 }
469 if s.lock != nil && !s.commitSkipped && !s.lock.checkCaptured(u) {
470 return
471 }
472 if s.role != nil {
473 if u.layer != nil && s.layer == u.layer {
474 u.layer.commitState(u.layerNext, s.current != nil)
475 } else {
476 s.role(s.current != nil)
477 }
478 }
479 if fresh {
480 if state, ok := s.server.buffers[s.current.Resource]; ok {
481 if c, ok := state.content(0); ok {
482 if !s.validateViewport(c.Width, c.Height) {
483 return
484 }
485 c.Transform = s.transform
486 c.Source, _ = s.source(c.Width, c.Height)
487 c.LogicalW, c.LogicalH = s.logicalSize(c.Width, c.Height)
488 s.formatOpaque = c.Opaque
489 c.Opaque = (c.Opaque || s.opaqueCovers(c.LogicalW, c.LogicalH)) && u.fade == 0
490 c.OpaqueRect = s.opaqueRect(c.Opaque, c.LogicalW, c.LogicalH, u.fade)
491 c.Fade = u.fade
492 resized := !s.has || c.Width != s.content.Width || c.Height != s.content.Height || c.LogicalW != s.content.LogicalW || c.LogicalH != s.content.LogicalH
493 s.commitDamage(u, true, resized, c.Width, c.Height)
494 if s.sub.parent == nil && (c.Width != s.lastW || c.Height != s.lastH) {
495 s.lastW, s.lastH = c.Width, c.Height
496 s.server.log.Info().Uint64("id", uint64(s.windowID())).Int("w", c.Width).Int("h", c.Height).Msg("buffer size")
497 }
498 c.Acquire = s.hold.acquire
499 s.content, s.has = c, true
500 } else if shm, ok := state.(*buffer); ok {
501 shm.pool.shm.PostError(uint32(wayland.ShmErrorInvalidFd), "SHM backing file truncated")
502 }
503 // Buffers are read in place: released when the next one
504 // replaces them.
505 }
506 }
507 damaged := !fresh && s.current != nil && (len(u.damage) > 0 || len(u.bufDamage) > 0)
508 if !fresh {
509 s.commitDamage(u, false, false, 0, 0)
510 // A NULL attach is exempt from out_of_buffer: its content is
511 // cleared below, so the retained buffer is not validated.
512 if s.has && s.current != nil {
513 if !s.validateViewport(s.content.Width, s.content.Height) {
514 return
515 }
516 s.content.Transform = s.transform
517 s.content.Source, _ = s.source(s.content.Width, s.content.Height)
518 s.content.LogicalW, s.content.LogicalH = s.logicalSize(s.content.Width, s.content.Height)
519 s.content.Opaque = (s.formatOpaque || s.opaqueCovers(s.content.LogicalW, s.content.LogicalH)) && u.fade == 0
520 s.content.OpaqueRect = s.opaqueRect(s.content.Opaque, s.content.LogicalW, s.content.LogicalH, u.fade)
521 s.content.Fade = u.fade
522 }
523 }
524 if s.current == nil {
525 s.content, s.has = ports.SurfaceContent{}, false
526 }
527 if fresh || damaged || oldW != s.content.LogicalW || oldH != s.content.LogicalH || oldSource != s.content.Source || oldColor != s.color || oldRepresentation != s.representation || oldTransform != s.content.Transform || oldOpaque != s.content.Opaque || oldOpaqueRect != s.content.OpaqueRect || oldFade != s.content.Fade {
528 s.version++
529 s.root().treeDirty = true
530 }
531 // The layout belongs to this update, not to the latest requests.
532 moved := !sameLayout(s.sub.layout, u.layout)
533 if moved {
534 s.root().treeDirty = true
535 }
536 // Queued updates can still reference the previous layout. Never mutate it.
537 if moved {
538 s.sub.layout = u.layout
539 }
540 geometry := false
541 if s.xdg != nil && s.xdg == u.xdg && u.geometry != s.xdg.geometry {
542 s.xdg.geometry, geometry = u.geometry, true
543 }
544 s.commitConstraint(u.cons, u.region, geometry)
545 s.emitInput()
546 if s.xdg != nil && s.xdg.window != nil {
547 s.xdg.window.afterCommit()
548 }
549 if s.lock != nil {
550 // Role callbacks precede content construction; publish lock placement
551 // only once validated current dimensions are available.
552 s.server.lockSurfaceChanged()
553 }
554 reshaped := s.has && (s.content.LogicalW != oldW || s.content.LogicalH != oldH || s.content.Source != oldSource || s.content.Transform != oldTransform)
555 // Opacity changes how every pixel blends, so it repaints the whole surface.
556 opacity := s.has && (s.content.Opaque != oldOpaque || s.content.OpaqueRect != oldOpaqueRect || s.content.Fade != oldFade)
557 drawn := fresh || damaged || moved || geometry || hinted || reshaped || opacity || s.sub.parent != nil
558 if drawn {
559 if moved || geometry || reshaped || opacity {
560 s.committed = damage{full: true}
561 }
562 if s.server.applyingGraph {
563 s.server.graphDrawn = true
564 } else {
565 s.redraw()
566 }
567 }
568 fb := u.feedback
569 if s.server.applyingGraph {
570 s.server.graphFeedback = append(s.server.graphFeedback, graphFeedback{s, fb, fresh})
571 } else {
572 s.commitFeedback(fb, fresh)
573 }
574}
575func (s *surface) Damage(_ *wayland.Surface, x, y, w, h int32) {
576 if w > 0 && h > 0 && len(s.next.damage) <= maxDamageRects {
577 s.next.damage = append(s.next.damage, ports.Rect{X: int(x), Y: int(y), W: int(w), H: int(h)})
578 }
579}
580func (s *surface) DamageBuffer(_ *wayland.Surface, x, y, w, h int32) {
581 if w > 0 && h > 0 && len(s.next.bufDamage) <= maxDamageRects {
582 s.next.bufDamage = append(s.next.bufDamage, ports.Rect{X: int(x), Y: int(y), W: int(w), H: int(h)})
583 }
584}
585
586// commitDamage turns the requested damage into buffer pixels of a bw×bh
587// buffer. Surface damage scales by the buffer/logical ratio, rounded out.
588// A new size, too many rects or none at all with a new buffer (clients
589// that do not report damage) is a full change.
590func (s *surface) commitDamage(u *update, fresh, resized bool, bw, bh int) {
591 surf, buf := u.damage, u.bufDamage
592 if !fresh {
593 s.committed = damage{}
594 return
595 }
596 if resized || len(surf)+len(buf) == 0 || len(surf)+len(buf) > maxDamageRects {
597 s.committed = damage{full: true}
598 return
599 }
600 lw, lh := s.logicalSize(bw, bh)
601 d := damage{rects: make([]ports.Rect, 0, len(surf)+len(buf))}
602 bounds := image.Rect(0, 0, bw, bh)
603 add := func(r image.Rectangle) {
604 if r = r.Intersect(bounds); !r.Empty() {
605 d.rects = append(d.rects, ports.Rect{X: r.Min.X, Y: r.Min.Y, W: r.Dx(), H: r.Dy()})
606 }
607 }
608 for _, r := range buf {
609 add(image.Rect(r.X, r.Y, r.X+r.W, r.Y+r.H))
610 }
611 // Surface damage maps through the crop, in the transformed buffer's
612 // axes, then back through the buffer transform.
613 tw, th := s.transformedSize(bw, bh)
614 sx, sy, sw, sh := 0.0, 0.0, float64(tw), float64(th)
615 if s.committedViewport.crop {
616 v := s.committedViewport.src
617 scale := float64(max(s.bufferScale, 1)) / 256
618 sx, sy, sw, sh = float64(v[0])*scale, float64(v[1])*scale, float64(v[2])*scale, float64(v[3])*scale
619 }
620 for _, r := range surf {
621 if lw <= 0 || lh <= 0 {
622 s.committed = damage{full: true}
623 return
624 }
625 // Clients may damage INT32_MAX-sized rects: clamp before scaling.
626 x0, y0 := max(r.X, 0), max(r.Y, 0)
627 x1, y1 := min(r.X+min(r.W, lw), lw), min(r.Y+min(r.H, lh), lh)
628 ax, ay := s.transform.ToBuffer(sx+float64(x0)*sw/float64(lw), sy+float64(y0)*sh/float64(lh), float64(tw), float64(th))
629 bx, by := s.transform.ToBuffer(sx+float64(x1)*sw/float64(lw), sy+float64(y1)*sh/float64(lh), float64(tw), float64(th))
630 add(image.Rect(int(math.Floor(min(ax, bx))), int(math.Floor(min(ay, by))), int(math.Ceil(max(ax, bx))), int(math.Ceil(max(ay, by)))))
631 }
632 s.committed = d
633}
634
635// SetOpaqueRegion copies the region: a nil region means nothing is opaque.
636func (s *surface) SetOpaqueRegion(_ *wayland.Surface, reg *wayland.Region) {
637 s.next.opaqueSet = true
638 s.next.opaque = nil
639 if reg != nil && reg.Resource != nil {
640 if g := s.server.regions[reg.Resource]; g != nil {
641 s.next.opaque = slices.Clone(g.rects)
642 }
643 }
644}
645
646func (s *surface) SetBufferTransform(r *wayland.Surface, v int32) {
647 if v < 0 || v > 7 {
648 r.PostError(uint32(wayland.SurfaceErrorInvalidTransform), "invalid buffer transform")
649 return
650 }
651 s.next.transform = ports.BufferTransform(v)
652}
653
654// opaqueCovers reports whether the committed opaque region covers the whole
655// w×h surface. A partial region is ignored: it is only an optimization hint.
656func (s *surface) opaqueCovers(w, h int) bool {
657 return w > 0 && h > 0 && covers(s.opaque, ports.Rect{W: w, H: h})
658}
659
660// opaqueRect is the largest rect of the committed opaque region inside the
661// w×h surface, for a surface that is not wholly opaque (opaque) and not
662// faded: clients often leave an edge out of a region that covers nearly
663// all of the surface (a 983×1230 region on a 984×1231 surface). The zero
664// Rect means none.
665func (s *surface) opaqueRect(opaque bool, w, h int, fade float32) ports.Rect {
666 var best ports.Rect
667 if opaque || fade != 0 || w <= 0 || h <= 0 {
668 return best
669 }
670 for _, r := range s.opaque {
671 x0, y0, x1, y1 := max(r.X, 0), max(r.Y, 0), min(r.X+r.W, w), min(r.Y+r.H, h)
672 if x1 > x0 && y1 > y0 && (x1-x0)*(y1-y0) > best.W*best.H {
673 best = ports.Rect{X: x0, Y: y0, W: x1 - x0, H: y1 - y0}
674 }
675 }
676 return best
677}
678
679// maxCoverRects bounds the region covers checks: beyond it, not covered.
680const maxCoverRects = 16
681
682// covers reports whether the union of rects contains all of r. It tests
683// one point per cell of the grid the rect edges draw.
684func covers(rects []ports.Rect, r ports.Rect) bool {
685 if len(rects) == 0 || len(rects) > maxCoverRects {
686 return false
687 }
688 var xsBuf, ysBuf [2*maxCoverRects + 2]int
689 xs, ys := append(xsBuf[:0], r.X, r.X+r.W), append(ysBuf[:0], r.Y, r.Y+r.H)
690 for _, c := range rects {
691 if c.X > r.X && c.X < r.X+r.W {
692 xs = append(xs, c.X)
693 }
694 if e := c.X + c.W; e > r.X && e < r.X+r.W {
695 xs = append(xs, e)
696 }
697 if c.Y > r.Y && c.Y < r.Y+r.H {
698 ys = append(ys, c.Y)
699 }
700 if e := c.Y + c.H; e > r.Y && e < r.Y+r.H {
701 ys = append(ys, e)
702 }
703 }
704 slices.Sort(xs)
705 slices.Sort(ys)
706 for i := 1; i < len(xs); i++ {
707 for j := 1; j < len(ys); j++ {
708 if xs[i] == xs[i-1] || ys[j] == ys[j-1] {
709 continue
710 }
711 x, y := xs[i-1], ys[j-1]
712 if !slices.ContainsFunc(rects, func(c ports.Rect) bool {
713 return x >= c.X && x < c.X+c.W && y >= c.Y && y < c.Y+c.H
714 }) {
715 return false
716 }
717 }
718 }
719 return true
720}
721func (s *surface) SetBufferScale(r *wayland.Surface, v int32) {
722 if v < 1 {
723 r.PostError(uint32(wayland.SurfaceErrorInvalidScale), "buffer scale must be positive")
724 return
725 }
726 s.next.scale = int(v)
727}
728func (*surface) Offset(*wayland.Surface, int32, int32) {}
729func (*surface) GetRelease(*wayland.Surface, uint32) {}