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