merge: occlusion culling for hidden shm surfaces
3 files · +379 -230
M internal/adapters/vulkan/compose_test.go +63 -0
| @@ -138,6 +138,69 @@ func TestRendererSHMCopiedOncePerContent(t *testing.T) { | |||
| 138 | 138 | } | |
| 139 | 139 | } | |
| 140 | 140 | ||
| 141 | + | // A surface fully hidden by an opaque subsurface above it is not copied: | |
| 142 | + | // only the subsurface's bytes are, and the frame shows it. | |
| 143 | + | func TestRendererSkipsOccludedSurface(t *testing.T) { | |
| 144 | + | r, err := New(32, 32) | |
| 145 | + | if err != nil { | |
| 146 | + | t.Skipf("Vulkan unavailable: %v", err) | |
| 147 | + | } | |
| 148 | + | defer r.Close() | |
| 149 | + | green := color.RGBA{0, 255, 0, 255} | |
| 150 | + | root := solidContent(t, 16, 16, color.RGBA{255, 0, 0, 255}) | |
| 151 | + | root.ID, root.Seq = 1, 1 | |
| 152 | + | game := solidContent(t, 16, 16, green) | |
| 153 | + | game.Opaque = true | |
| 154 | + | root.Children = []ports.Subsurface{{SurfaceContent: game}} | |
| 155 | + | scene := ports.Scene{Background: "#000000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 16, H: 16}}}} | |
| 156 | + | if err := render(r, scene, map[ports.WindowID]ports.SurfaceContent{1: root}); err != nil { | |
| 157 | + | t.Fatal(err) | |
| 158 | + | } | |
| 159 | + | if r.copied != 16*16*4 { | |
| 160 | + | t.Fatalf("copied %d bytes, want only the subsurface's %d", r.copied, 16*16*4) | |
| 161 | + | } | |
| 162 | + | if got := r.Pixels().At(8, 8); got != green { | |
| 163 | + | t.Fatalf("At(8,8)=%v want %v", got, green) | |
| 164 | + | } | |
| 165 | + | // A smaller opaque child leaves part of the root visible: it is copied. | |
| 166 | + | small := solidContent(t, 8, 8, green) | |
| 167 | + | small.Opaque = true | |
| 168 | + | root.Seq = 2 | |
| 169 | + | root.Children = []ports.Subsurface{{SurfaceContent: small}} | |
| 170 | + | r.copied = 0 | |
| 171 | + | if err := render(r, scene, map[ports.WindowID]ports.SurfaceContent{1: root}); err != nil { | |
| 172 | + | t.Fatal(err) | |
| 173 | + | } | |
| 174 | + | if r.copied != 16*16*4+8*8*4 { | |
| 175 | + | t.Fatalf("partly hidden root: copied %d", r.copied) | |
| 176 | + | } | |
| 177 | + | } | |
| 178 | + | ||
| 179 | + | // A covering child whose buffer will not draw does not hide the root: | |
| 180 | + | // an unimportable dmabuf still shows the surface below it, not a hole. | |
| 181 | + | func TestRendererKeepsSurfaceBelowFailedCover(t *testing.T) { | |
| 182 | + | r, err := New(32, 32) | |
| 183 | + | if err != nil { | |
| 184 | + | t.Skipf("Vulkan unavailable: %v", err) | |
| 185 | + | } | |
| 186 | + | defer r.Close() | |
| 187 | + | red := color.RGBA{255, 0, 0, 255} | |
| 188 | + | root := solidContent(t, 16, 16, red) | |
| 189 | + | root.ID, root.Seq = 1, 1 | |
| 190 | + | bad := &ports.DMABuf{ID: 9, Width: 16, Height: 16, Format: 0xdeadbeef} | |
| 191 | + | root.Children = []ports.Subsurface{{SurfaceContent: ports.SurfaceContent{Width: 16, Height: 16, Opaque: true, DMABuf: bad}}} | |
| 192 | + | scene := ports.Scene{Background: "#000000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 16, H: 16}}}} | |
| 193 | + | if err := render(r, scene, map[ports.WindowID]ports.SurfaceContent{1: root}); err != nil { | |
| 194 | + | t.Fatal(err) | |
| 195 | + | } | |
| 196 | + | if r.copied != 16*16*4 { | |
| 197 | + | t.Fatalf("copied %d bytes, want the root's %d", r.copied, 16*16*4) | |
| 198 | + | } | |
| 199 | + | if got := r.Pixels().At(8, 8); got != red { | |
| 200 | + | t.Fatalf("At(8,8)=%v want %v", got, red) | |
| 201 | + | } | |
| 202 | + | } | |
| 203 | + | ||
| 141 | 204 | // render draws a frame and closes its fence. | |
| 142 | 205 | func render(r *Renderer, s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent) error { | |
| 143 | 206 | done, err := r.Render(s, contents) | |
| @@ -138,6 +138,69 @@ func TestRendererSHMCopiedOncePerContent(t *testing.T) { | |||||
| 138 | } | 138 | } | ||
| 139 | } | 139 | } | ||
| 140 | 140 | ||||
| 141 | + | // A surface fully hidden by an opaque subsurface above it is not copied: | |||
| 142 | + | // only the subsurface's bytes are, and the frame shows it. | |||
| 143 | + | func TestRendererSkipsOccludedSurface(t *testing.T) { | |||
| 144 | + | r, err := New(32, 32) | |||
| 145 | + | if err != nil { | |||
| 146 | + | t.Skipf("Vulkan unavailable: %v", err) | |||
| 147 | + | } | |||
| 148 | + | defer r.Close() | |||
| 149 | + | green := color.RGBA{0, 255, 0, 255} | |||
| 150 | + | root := solidContent(t, 16, 16, color.RGBA{255, 0, 0, 255}) | |||
| 151 | + | root.ID, root.Seq = 1, 1 | |||
| 152 | + | game := solidContent(t, 16, 16, green) | |||
| 153 | + | game.Opaque = true | |||
| 154 | + | root.Children = []ports.Subsurface{{SurfaceContent: game}} | |||
| 155 | + | scene := ports.Scene{Background: "#000000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 16, H: 16}}}} | |||
| 156 | + | if err := render(r, scene, map[ports.WindowID]ports.SurfaceContent{1: root}); err != nil { | |||
| 157 | + | t.Fatal(err) | |||
| 158 | + | } | |||
| 159 | + | if r.copied != 16*16*4 { | |||
| 160 | + | t.Fatalf("copied %d bytes, want only the subsurface's %d", r.copied, 16*16*4) | |||
| 161 | + | } | |||
| 162 | + | if got := r.Pixels().At(8, 8); got != green { | |||
| 163 | + | t.Fatalf("At(8,8)=%v want %v", got, green) | |||
| 164 | + | } | |||
| 165 | + | // A smaller opaque child leaves part of the root visible: it is copied. | |||
| 166 | + | small := solidContent(t, 8, 8, green) | |||
| 167 | + | small.Opaque = true | |||
| 168 | + | root.Seq = 2 | |||
| 169 | + | root.Children = []ports.Subsurface{{SurfaceContent: small}} | |||
| 170 | + | r.copied = 0 | |||
| 171 | + | if err := render(r, scene, map[ports.WindowID]ports.SurfaceContent{1: root}); err != nil { | |||
| 172 | + | t.Fatal(err) | |||
| 173 | + | } | |||
| 174 | + | if r.copied != 16*16*4+8*8*4 { | |||
| 175 | + | t.Fatalf("partly hidden root: copied %d", r.copied) | |||
| 176 | + | } | |||
| 177 | + | } | |||
| 178 | + | ||||
| 179 | + | // A covering child whose buffer will not draw does not hide the root: | |||
| 180 | + | // an unimportable dmabuf still shows the surface below it, not a hole. | |||
| 181 | + | func TestRendererKeepsSurfaceBelowFailedCover(t *testing.T) { | |||
| 182 | + | r, err := New(32, 32) | |||
| 183 | + | if err != nil { | |||
| 184 | + | t.Skipf("Vulkan unavailable: %v", err) | |||
| 185 | + | } | |||
| 186 | + | defer r.Close() | |||
| 187 | + | red := color.RGBA{255, 0, 0, 255} | |||
| 188 | + | root := solidContent(t, 16, 16, red) | |||
| 189 | + | root.ID, root.Seq = 1, 1 | |||
| 190 | + | bad := &ports.DMABuf{ID: 9, Width: 16, Height: 16, Format: 0xdeadbeef} | |||
| 191 | + | root.Children = []ports.Subsurface{{SurfaceContent: ports.SurfaceContent{Width: 16, Height: 16, Opaque: true, DMABuf: bad}}} | |||
| 192 | + | scene := ports.Scene{Background: "#000000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 16, H: 16}}}} | |||
| 193 | + | if err := render(r, scene, map[ports.WindowID]ports.SurfaceContent{1: root}); err != nil { | |||
| 194 | + | t.Fatal(err) | |||
| 195 | + | } | |||
| 196 | + | if r.copied != 16*16*4 { | |||
| 197 | + | t.Fatalf("copied %d bytes, want the root's %d", r.copied, 16*16*4) | |||
| 198 | + | } | |||
| 199 | + | if got := r.Pixels().At(8, 8); got != red { | |||
| 200 | + | t.Fatalf("At(8,8)=%v want %v", got, red) | |||
| 201 | + | } | |||
| 202 | + | } | |||
| 203 | + | ||||
| 141 | // render draws a frame and closes its fence. | 204 | // render draws a frame and closes its fence. | ||
| 142 | func render(r *Renderer, s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent) error { | 205 | func render(r *Renderer, s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent) error { | ||
| 143 | done, err := r.Render(s, contents) | 206 | done, err := r.Render(s, contents) | ||
M internal/adapters/vulkan/renderer.go +0 -230
| @@ -261,236 +261,6 @@ func (r *Renderer) findMemoryType(bits uint32, props vk.MemoryPropertyFlags) (ui | |||
| 261 | 261 | return 0, fmt.Errorf("no Vulkan memory type for bits %#x and properties %#x", bits, props) | |
| 262 | 262 | } | |
| 263 | 263 | ||
| 264 | - | func parseColor(s string) [3]uint8 { | |
| 265 | - | var c [3]uint8 | |
| 266 | - | if len(s) != 7 || s[0] != '#' { | |
| 267 | - | return c | |
| 268 | - | } | |
| 269 | - | for i := range c { | |
| 270 | - | v, e := strconv.ParseUint(s[1+i*2:3+i*2], 16, 8) | |
| 271 | - | if e != nil { | |
| 272 | - | return [3]uint8{} | |
| 273 | - | } | |
| 274 | - | c[i] = uint8(v) | |
| 275 | - | } | |
| 276 | - | return c | |
| 277 | - | } | |
| 278 | - | ||
| 279 | - | func windowColor(id ports.WindowID) [3]uint8 { | |
| 280 | - | h := math.Mod(float64(id)*0.618034, 1) * 6 | |
| 281 | - | sector := int(h) | |
| 282 | - | f := h - float64(sector) | |
| 283 | - | p, q, t := 0.4, 0.8*(1-0.5*f), 0.8*(1-0.5*(1-f)) | |
| 284 | - | var a, b, c float64 | |
| 285 | - | switch sector { | |
| 286 | - | case 0: | |
| 287 | - | a, b, c = 0.8, t, p | |
| 288 | - | case 1: | |
| 289 | - | a, b, c = q, 0.8, p | |
| 290 | - | case 2: | |
| 291 | - | a, b, c = p, 0.8, t | |
| 292 | - | case 3: | |
| 293 | - | a, b, c = p, q, 0.8 | |
| 294 | - | case 4: | |
| 295 | - | a, b, c = t, p, 0.8 | |
| 296 | - | default: | |
| 297 | - | a, b, c = 0.8, p, q | |
| 298 | - | } | |
| 299 | - | return [3]uint8{uint8(a * 255), uint8(b * 255), uint8(c * 255)} | |
| 300 | - | } | |
| 301 | - | ||
| 302 | - | // draws walks the scene into quads in paint order, copying new wl_shm | |
| 303 | - | // content into GPU buffers on the way. | |
| 304 | - | func (r *Renderer) draws(s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent, dmg *damageRegion) []draw { | |
| 305 | - | // Scene rects are logical; everything below works in physical pixels. | |
| 306 | - | scale := s.Scale | |
| 307 | - | if scale <= 0 { | |
| 308 | - | scale = 1 | |
| 309 | - | } | |
| 310 | - | phys := func(v int) int { return int(math.Round(float64(v) * scale)) } | |
| 311 | - | physRect := func(x, y, w, h int) image.Rectangle { return image.Rect(phys(x), phys(y), phys(x+w), phys(y+h)) } | |
| 312 | - | var draws []draw | |
| 313 | - | bounds := image.Rect(0, 0, r.width, r.height) | |
| 314 | - | add := func(rect image.Rectangle, c [3]uint8) { | |
| 315 | - | rect = rect.Intersect(bounds) | |
| 316 | - | if rect.Empty() { | |
| 317 | - | return | |
| 318 | - | } | |
| 319 | - | draws = append(draws, r.fillDraw(rect, c)) | |
| 320 | - | } | |
| 321 | - | // addContent draws the part of a buffer mapped onto full (physical | |
| 322 | - | // pixels) that dst shows. key names the surface, seq its content. | |
| 323 | - | addContent := func(dst, full image.Rectangle, content *ports.SurfaceContent, key shmKey, seq uint64) { | |
| 324 | - | rect := dst.Intersect(bounds) | |
| 325 | - | if rect.Empty() { | |
| 326 | - | return | |
| 327 | - | } | |
| 328 | - | if content.DMABuf != nil { | |
| 329 | - | im, err := r.importDMABuf(content.DMABuf) | |
| 330 | - | if err != nil { | |
| 331 | - | // The window shows its background until a buffer imports. | |
| 332 | - | return | |
| 333 | - | } | |
| 334 | - | dr := r.contentDraw(rect, full, content.Width, content.Height, modeImage, content.Opaque) | |
| 335 | - | dr.set, dr.im = im.set, im | |
| 336 | - | dr.acquire = content.Acquire | |
| 337 | - | draws = append(draws, dr) | |
| 338 | - | return | |
| 339 | - | } | |
| 340 | - | b := content.SHM | |
| 341 | - | pixels, err := r.shmPixels(b, b.Offset+(content.Height-1)*b.Stride+content.Width*4) | |
| 342 | - | if err != nil { | |
| 343 | - | return | |
| 344 | - | } | |
| 345 | - | st := shmState{w: content.Width, h: content.Height, seq: seq} | |
| 346 | - | // Damage history is the root surface's: children copy in full. | |
| 347 | - | var damage func(uint64) ([]ports.Rect, bool) | |
| 348 | - | if key.index == 0 { | |
| 349 | - | damage = content.DamageSince | |
| 350 | - | } | |
| 351 | - | c, err := r.shmCopyFor(key, st, pixels, b.Offset, b.Stride, damage) | |
| 352 | - | if err != nil { | |
| 353 | - | return | |
| 354 | - | } | |
| 355 | - | dr := r.contentDraw(rect, full, content.Width, content.Height, modeBuffer, content.Opaque) | |
| 356 | - | dr.set = c.set | |
| 357 | - | dr.pc.buf = [4]uint32{0, uint32(content.Width), uint32(content.Height), 0} | |
| 358 | - | draws = append(draws, dr) | |
| 359 | - | } | |
| 360 | - | // drawSurface draws one surface buffer with its origin at (x, y) | |
| 361 | - | // logical, clipped to clip (logical). | |
| 362 | - | drawSurface := func(content *ports.SurfaceContent, x, y int, clip image.Rectangle, key shmKey, seq uint64) { | |
| 363 | - | lw, lh := content.LogicalW, content.LogicalH | |
| 364 | - | if lw <= 0 || lh <= 0 { | |
| 365 | - | lw, lh = content.Width, content.Height | |
| 366 | - | } | |
| 367 | - | if content.SHM == nil && content.DMABuf == nil || lw <= 0 || lh <= 0 || content.Width <= 0 || content.Height <= 0 { | |
| 368 | - | return | |
| 369 | - | } | |
| 370 | - | if content.DMABuf == nil && (content.SHM.Stride < content.Width*4 || content.SHM.Offset < 0) { | |
| 371 | - | return | |
| 372 | - | } | |
| 373 | - | full := physRect(x, y, lw, lh) | |
| 374 | - | // A buffer drawn at the physical size (fractional-scale clients round | |
| 375 | - | // w*scale, we round per edge) is copied 1:1, never resampled for 1px. | |
| 376 | - | near := func(a, b int) bool { return a-b <= 1 && b-a <= 1 } | |
| 377 | - | if near(full.Dx(), content.Width) && near(full.Dy(), content.Height) { | |
| 378 | - | full.Max = full.Min.Add(image.Pt(content.Width, content.Height)) | |
| 379 | - | } | |
| 380 | - | dst := full.Intersect(physRect(clip.Min.X, clip.Min.Y, clip.Dx(), clip.Dy())) | |
| 381 | - | if dst.Empty() { | |
| 382 | - | return | |
| 383 | - | } | |
| 384 | - | if key.index == 0 { | |
| 385 | - | dmg.content(key.win, content, full, dst) | |
| 386 | - | } | |
| 387 | - | addContent(dst, full, content, key, seq) | |
| 388 | - | } | |
| 389 | - | // place draws a surface tree with its window geometry at (x, y) | |
| 390 | - | // logical, clipped to w×h logical: client shadows fall outside. | |
| 391 | - | // Every surface of the tree is keyed by its window and place in it; | |
| 392 | - | // the tree's Seq changes with any of them. | |
| 393 | - | place := func(id ports.WindowID, content *ports.SurfaceContent, x, y, w, h int) { | |
| 394 | - | clip := image.Rect(x, y, x+w, y+h) | |
| 395 | - | ox, oy := x-content.Geometry.X, y-content.Geometry.Y | |
| 396 | - | for i := range content.Children { | |
| 397 | - | if ch := &content.Children[i]; ch.Below { | |
| 398 | - | drawSurface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip, shmKey{id, i + 1}, content.Seq) | |
| 399 | - | } | |
| 400 | - | } | |
| 401 | - | drawSurface(content, ox, oy, clip, shmKey{id, 0}, content.Seq) | |
| 402 | - | for i := range content.Children { | |
| 403 | - | if ch := &content.Children[i]; !ch.Below { | |
| 404 | - | drawSurface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip, shmKey{id, i + 1}, content.Seq) | |
| 405 | - | } | |
| 406 | - | } | |
| 407 | - | } | |
| 408 | - | fullscreen := false | |
| 409 | - | for _, w := range s.Windows { | |
| 410 | - | fullscreen = fullscreen || (!w.Hidden && w.Fullscreen) | |
| 411 | - | } | |
| 412 | - | addLayers := func(afterWindows bool) { | |
| 413 | - | for _, layer := range s.Layers { | |
| 414 | - | if (layer.Layer >= ports.LayerTop) != afterWindows || | |
| 415 | - | (fullscreen && (layer.Layer == ports.LayerBottom || layer.Layer == ports.LayerTop)) || | |
| 416 | - | layer.Rect.W <= 0 || layer.Rect.H <= 0 { | |
| 417 | - | continue | |
| 418 | - | } | |
| 419 | - | content := contents[layer.ID] | |
| 420 | - | dmg.window(layer.ID, content, physRect(layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H)) | |
| 421 | - | place(layer.ID, &content, layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H) | |
| 422 | - | } | |
| 423 | - | } | |
| 424 | - | addLayers(false) | |
| 425 | - | // Separators are drawn in order, active lines over inactive ones: | |
| 426 | - | // tile lines (Window 0) over the tiles, a float's border with it. | |
| 427 | - | addSeparators := func(id ports.WindowID) { | |
| 428 | - | for _, sep := range s.Separators { | |
| 429 | - | if sep.Window != id { | |
| 430 | - | continue | |
| 431 | - | } | |
| 432 | - | col := s.Border.Inactive | |
| 433 | - | if sep.Active { | |
| 434 | - | col = s.Border.Active | |
| 435 | - | } | |
| 436 | - | if col != "" { | |
| 437 | - | add(physRect(sep.Rect.X, sep.Rect.Y, sep.Rect.W, sep.Rect.H), parseColor(col)) | |
| 438 | - | } | |
| 439 | - | } | |
| 440 | - | } | |
| 441 | - | tileLines := false | |
| 442 | - | for _, w := range s.Windows { | |
| 443 | - | if w.Floating && !tileLines { | |
| 444 | - | addSeparators(0) | |
| 445 | - | tileLines = true | |
| 446 | - | } | |
| 447 | - | if w.Hidden || w.Rect.W <= 0 || w.Rect.H <= 0 { | |
| 448 | - | continue | |
| 449 | - | } | |
| 450 | - | x, y := w.Rect.X, w.Rect.Y | |
| 451 | - | if w.Popup { | |
| 452 | - | continue | |
| 453 | - | } | |
| 454 | - | // Content sits inside the border; core sized the client to match. | |
| 455 | - | b, inset := 0, ports.Sides(0) | |
| 456 | - | if !w.Fullscreen { | |
| 457 | - | b, inset = s.Border.Width, w.Inset | |
| 458 | - | } | |
| 459 | - | c := w.Rect.Inset(inset, b) | |
| 460 | - | cx, cy, cw, ch := c.X, c.Y, c.W, c.H | |
| 461 | - | body := physRect(cx, cy, cw, ch) | |
| 462 | - | content := contents[w.ID] | |
| 463 | - | dmg.window(w.ID, content, physRect(x, y, w.Rect.W, w.Rect.H)) | |
| 464 | - | if content.Empty() { | |
| 465 | - | add(body, windowColor(w.ID)) | |
| 466 | - | } else { | |
| 467 | - | add(body, parseColor(s.Background)) | |
| 468 | - | place(w.ID, &content, cx, cy, cw, ch) | |
| 469 | - | } | |
| 470 | - | if w.Floating { | |
| 471 | - | addSeparators(w.ID) | |
| 472 | - | } | |
| 473 | - | } | |
| 474 | - | if !tileLines { | |
| 475 | - | addSeparators(0) | |
| 476 | - | } | |
| 477 | - | // Popups draw only what the client drew, shadows clipped. Window popups | |
| 478 | - | // stay with the windows; a layer's go over every layer. | |
| 479 | - | addPopups := func(overLayers bool) { | |
| 480 | - | for _, w := range s.Windows { | |
| 481 | - | if w.Popup && w.OverLayers == overLayers && !w.Hidden && w.Rect.W > 0 && w.Rect.H > 0 { | |
| 482 | - | content := contents[w.ID] | |
| 483 | - | dmg.window(w.ID, content, physRect(w.Rect.X, w.Rect.Y, w.Rect.W, w.Rect.H)) | |
| 484 | - | place(w.ID, &content, w.Rect.X, w.Rect.Y, w.Rect.W, w.Rect.H) | |
| 485 | - | } | |
| 486 | - | } | |
| 487 | - | } | |
| 488 | - | addPopups(false) | |
| 489 | - | addLayers(true) | |
| 490 | - | addPopups(true) | |
| 491 | - | return draws | |
| 492 | - | } | |
| 493 | - | ||
| 494 | 264 | func (r *Renderer) Pixels() *image.RGBA { | |
| 495 | 265 | out := image.NewRGBA(image.Rect(0, 0, r.width, r.height)) | |
| 496 | 266 | if r.mapped == nil || r.readback() != nil { | |
| @@ -261,236 +261,6 @@ func (r *Renderer) findMemoryType(bits uint32, props vk.MemoryPropertyFlags) (ui | |||||
| 261 | return 0, fmt.Errorf("no Vulkan memory type for bits %#x and properties %#x", bits, props) | 261 | return 0, fmt.Errorf("no Vulkan memory type for bits %#x and properties %#x", bits, props) | ||
| 262 | } | 262 | } | ||
| 263 | 263 | ||||
| 264 | - | func parseColor(s string) [3]uint8 { | |||
| 265 | - | var c [3]uint8 | |||
| 266 | - | if len(s) != 7 || s[0] != '#' { | |||
| 267 | - | return c | |||
| 268 | - | } | |||
| 269 | - | for i := range c { | |||
| 270 | - | v, e := strconv.ParseUint(s[1+i*2:3+i*2], 16, 8) | |||
| 271 | - | if e != nil { | |||
| 272 | - | return [3]uint8{} | |||
| 273 | - | } | |||
| 274 | - | c[i] = uint8(v) | |||
| 275 | - | } | |||
| 276 | - | return c | |||
| 277 | - | } | |||
| 278 | - | ||||
| 279 | - | func windowColor(id ports.WindowID) [3]uint8 { | |||
| 280 | - | h := math.Mod(float64(id)*0.618034, 1) * 6 | |||
| 281 | - | sector := int(h) | |||
| 282 | - | f := h - float64(sector) | |||
| 283 | - | p, q, t := 0.4, 0.8*(1-0.5*f), 0.8*(1-0.5*(1-f)) | |||
| 284 | - | var a, b, c float64 | |||
| 285 | - | switch sector { | |||
| 286 | - | case 0: | |||
| 287 | - | a, b, c = 0.8, t, p | |||
| 288 | - | case 1: | |||
| 289 | - | a, b, c = q, 0.8, p | |||
| 290 | - | case 2: | |||
| 291 | - | a, b, c = p, 0.8, t | |||
| 292 | - | case 3: | |||
| 293 | - | a, b, c = p, q, 0.8 | |||
| 294 | - | case 4: | |||
| 295 | - | a, b, c = t, p, 0.8 | |||
| 296 | - | default: | |||
| 297 | - | a, b, c = 0.8, p, q | |||
| 298 | - | } | |||
| 299 | - | return [3]uint8{uint8(a * 255), uint8(b * 255), uint8(c * 255)} | |||
| 300 | - | } | |||
| 301 | - | ||||
| 302 | - | // draws walks the scene into quads in paint order, copying new wl_shm | |||
| 303 | - | // content into GPU buffers on the way. | |||
| 304 | - | func (r *Renderer) draws(s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent, dmg *damageRegion) []draw { | |||
| 305 | - | // Scene rects are logical; everything below works in physical pixels. | |||
| 306 | - | scale := s.Scale | |||
| 307 | - | if scale <= 0 { | |||
| 308 | - | scale = 1 | |||
| 309 | - | } | |||
| 310 | - | phys := func(v int) int { return int(math.Round(float64(v) * scale)) } | |||
| 311 | - | physRect := func(x, y, w, h int) image.Rectangle { return image.Rect(phys(x), phys(y), phys(x+w), phys(y+h)) } | |||
| 312 | - | var draws []draw | |||
| 313 | - | bounds := image.Rect(0, 0, r.width, r.height) | |||
| 314 | - | add := func(rect image.Rectangle, c [3]uint8) { | |||
| 315 | - | rect = rect.Intersect(bounds) | |||
| 316 | - | if rect.Empty() { | |||
| 317 | - | return | |||
| 318 | - | } | |||
| 319 | - | draws = append(draws, r.fillDraw(rect, c)) | |||
| 320 | - | } | |||
| 321 | - | // addContent draws the part of a buffer mapped onto full (physical | |||
| 322 | - | // pixels) that dst shows. key names the surface, seq its content. | |||
| 323 | - | addContent := func(dst, full image.Rectangle, content *ports.SurfaceContent, key shmKey, seq uint64) { | |||
| 324 | - | rect := dst.Intersect(bounds) | |||
| 325 | - | if rect.Empty() { | |||
| 326 | - | return | |||
| 327 | - | } | |||
| 328 | - | if content.DMABuf != nil { | |||
| 329 | - | im, err := r.importDMABuf(content.DMABuf) | |||
| 330 | - | if err != nil { | |||
| 331 | - | // The window shows its background until a buffer imports. | |||
| 332 | - | return | |||
| 333 | - | } | |||
| 334 | - | dr := r.contentDraw(rect, full, content.Width, content.Height, modeImage, content.Opaque) | |||
| 335 | - | dr.set, dr.im = im.set, im | |||
| 336 | - | dr.acquire = content.Acquire | |||
| 337 | - | draws = append(draws, dr) | |||
| 338 | - | return | |||
| 339 | - | } | |||
| 340 | - | b := content.SHM | |||
| 341 | - | pixels, err := r.shmPixels(b, b.Offset+(content.Height-1)*b.Stride+content.Width*4) | |||
| 342 | - | if err != nil { | |||
| 343 | - | return | |||
| 344 | - | } | |||
| 345 | - | st := shmState{w: content.Width, h: content.Height, seq: seq} | |||
| 346 | - | // Damage history is the root surface's: children copy in full. | |||
| 347 | - | var damage func(uint64) ([]ports.Rect, bool) | |||
| 348 | - | if key.index == 0 { | |||
| 349 | - | damage = content.DamageSince | |||
| 350 | - | } | |||
| 351 | - | c, err := r.shmCopyFor(key, st, pixels, b.Offset, b.Stride, damage) | |||
| 352 | - | if err != nil { | |||
| 353 | - | return | |||
| 354 | - | } | |||
| 355 | - | dr := r.contentDraw(rect, full, content.Width, content.Height, modeBuffer, content.Opaque) | |||
| 356 | - | dr.set = c.set | |||
| 357 | - | dr.pc.buf = [4]uint32{0, uint32(content.Width), uint32(content.Height), 0} | |||
| 358 | - | draws = append(draws, dr) | |||
| 359 | - | } | |||
| 360 | - | // drawSurface draws one surface buffer with its origin at (x, y) | |||
| 361 | - | // logical, clipped to clip (logical). | |||
| 362 | - | drawSurface := func(content *ports.SurfaceContent, x, y int, clip image.Rectangle, key shmKey, seq uint64) { | |||
| 363 | - | lw, lh := content.LogicalW, content.LogicalH | |||
| 364 | - | if lw <= 0 || lh <= 0 { | |||
| 365 | - | lw, lh = content.Width, content.Height | |||
| 366 | - | } | |||
| 367 | - | if content.SHM == nil && content.DMABuf == nil || lw <= 0 || lh <= 0 || content.Width <= 0 || content.Height <= 0 { | |||
| 368 | - | return | |||
| 369 | - | } | |||
| 370 | - | if content.DMABuf == nil && (content.SHM.Stride < content.Width*4 || content.SHM.Offset < 0) { | |||
| 371 | - | return | |||
| 372 | - | } | |||
| 373 | - | full := physRect(x, y, lw, lh) | |||
| 374 | - | // A buffer drawn at the physical size (fractional-scale clients round | |||
| 375 | - | // w*scale, we round per edge) is copied 1:1, never resampled for 1px. | |||
| 376 | - | near := func(a, b int) bool { return a-b <= 1 && b-a <= 1 } | |||
| 377 | - | if near(full.Dx(), content.Width) && near(full.Dy(), content.Height) { | |||
| 378 | - | full.Max = full.Min.Add(image.Pt(content.Width, content.Height)) | |||
| 379 | - | } | |||
| 380 | - | dst := full.Intersect(physRect(clip.Min.X, clip.Min.Y, clip.Dx(), clip.Dy())) | |||
| 381 | - | if dst.Empty() { | |||
| 382 | - | return | |||
| 383 | - | } | |||
| 384 | - | if key.index == 0 { | |||
| 385 | - | dmg.content(key.win, content, full, dst) | |||
| 386 | - | } | |||
| 387 | - | addContent(dst, full, content, key, seq) | |||
| 388 | - | } | |||
| 389 | - | // place draws a surface tree with its window geometry at (x, y) | |||
| 390 | - | // logical, clipped to w×h logical: client shadows fall outside. | |||
| 391 | - | // Every surface of the tree is keyed by its window and place in it; | |||
| 392 | - | // the tree's Seq changes with any of them. | |||
| 393 | - | place := func(id ports.WindowID, content *ports.SurfaceContent, x, y, w, h int) { | |||
| 394 | - | clip := image.Rect(x, y, x+w, y+h) | |||
| 395 | - | ox, oy := x-content.Geometry.X, y-content.Geometry.Y | |||
| 396 | - | for i := range content.Children { | |||
| 397 | - | if ch := &content.Children[i]; ch.Below { | |||
| 398 | - | drawSurface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip, shmKey{id, i + 1}, content.Seq) | |||
| 399 | - | } | |||
| 400 | - | } | |||
| 401 | - | drawSurface(content, ox, oy, clip, shmKey{id, 0}, content.Seq) | |||
| 402 | - | for i := range content.Children { | |||
| 403 | - | if ch := &content.Children[i]; !ch.Below { | |||
| 404 | - | drawSurface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip, shmKey{id, i + 1}, content.Seq) | |||
| 405 | - | } | |||
| 406 | - | } | |||
| 407 | - | } | |||
| 408 | - | fullscreen := false | |||
| 409 | - | for _, w := range s.Windows { | |||
| 410 | - | fullscreen = fullscreen || (!w.Hidden && w.Fullscreen) | |||
| 411 | - | } | |||
| 412 | - | addLayers := func(afterWindows bool) { | |||
| 413 | - | for _, layer := range s.Layers { | |||
| 414 | - | if (layer.Layer >= ports.LayerTop) != afterWindows || | |||
| 415 | - | (fullscreen && (layer.Layer == ports.LayerBottom || layer.Layer == ports.LayerTop)) || | |||
| 416 | - | layer.Rect.W <= 0 || layer.Rect.H <= 0 { | |||
| 417 | - | continue | |||
| 418 | - | } | |||
| 419 | - | content := contents[layer.ID] | |||
| 420 | - | dmg.window(layer.ID, content, physRect(layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H)) | |||
| 421 | - | place(layer.ID, &content, layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H) | |||
| 422 | - | } | |||
| 423 | - | } | |||
| 424 | - | addLayers(false) | |||
| 425 | - | // Separators are drawn in order, active lines over inactive ones: | |||
| 426 | - | // tile lines (Window 0) over the tiles, a float's border with it. | |||
| 427 | - | addSeparators := func(id ports.WindowID) { | |||
| 428 | - | for _, sep := range s.Separators { | |||
| 429 | - | if sep.Window != id { | |||
| 430 | - | continue | |||
| 431 | - | } | |||
| 432 | - | col := s.Border.Inactive | |||
| 433 | - | if sep.Active { | |||
| 434 | - | col = s.Border.Active | |||
| 435 | - | } | |||
| 436 | - | if col != "" { | |||
| 437 | - | add(physRect(sep.Rect.X, sep.Rect.Y, sep.Rect.W, sep.Rect.H), parseColor(col)) | |||
| 438 | - | } | |||
| 439 | - | } | |||
| 440 | - | } | |||
| 441 | - | tileLines := false | |||
| 442 | - | for _, w := range s.Windows { | |||
| 443 | - | if w.Floating && !tileLines { | |||
| 444 | - | addSeparators(0) | |||
| 445 | - | tileLines = true | |||
| 446 | - | } | |||
| 447 | - | if w.Hidden || w.Rect.W <= 0 || w.Rect.H <= 0 { | |||
| 448 | - | continue | |||
| 449 | - | } | |||
| 450 | - | x, y := w.Rect.X, w.Rect.Y | |||
| 451 | - | if w.Popup { | |||
| 452 | - | continue | |||
| 453 | - | } | |||
| 454 | - | // Content sits inside the border; core sized the client to match. | |||
| 455 | - | b, inset := 0, ports.Sides(0) | |||
| 456 | - | if !w.Fullscreen { | |||
| 457 | - | b, inset = s.Border.Width, w.Inset | |||
| 458 | - | } | |||
| 459 | - | c := w.Rect.Inset(inset, b) | |||
| 460 | - | cx, cy, cw, ch := c.X, c.Y, c.W, c.H | |||
| 461 | - | body := physRect(cx, cy, cw, ch) | |||
| 462 | - | content := contents[w.ID] | |||
| 463 | - | dmg.window(w.ID, content, physRect(x, y, w.Rect.W, w.Rect.H)) | |||
| 464 | - | if content.Empty() { | |||
| 465 | - | add(body, windowColor(w.ID)) | |||
| 466 | - | } else { | |||
| 467 | - | add(body, parseColor(s.Background)) | |||
| 468 | - | place(w.ID, &content, cx, cy, cw, ch) | |||
| 469 | - | } | |||
| 470 | - | if w.Floating { | |||
| 471 | - | addSeparators(w.ID) | |||
| 472 | - | } | |||
| 473 | - | } | |||
| 474 | - | if !tileLines { | |||
| 475 | - | addSeparators(0) | |||
| 476 | - | } | |||
| 477 | - | // Popups draw only what the client drew, shadows clipped. Window popups | |||
| 478 | - | // stay with the windows; a layer's go over every layer. | |||
| 479 | - | addPopups := func(overLayers bool) { | |||
| 480 | - | for _, w := range s.Windows { | |||
| 481 | - | if w.Popup && w.OverLayers == overLayers && !w.Hidden && w.Rect.W > 0 && w.Rect.H > 0 { | |||
| 482 | - | content := contents[w.ID] | |||
| 483 | - | dmg.window(w.ID, content, physRect(w.Rect.X, w.Rect.Y, w.Rect.W, w.Rect.H)) | |||
| 484 | - | place(w.ID, &content, w.Rect.X, w.Rect.Y, w.Rect.W, w.Rect.H) | |||
| 485 | - | } | |||
| 486 | - | } | |||
| 487 | - | } | |||
| 488 | - | addPopups(false) | |||
| 489 | - | addLayers(true) | |||
| 490 | - | addPopups(true) | |||
| 491 | - | return draws | |||
| 492 | - | } | |||
| 493 | - | ||||
| 494 | func (r *Renderer) Pixels() *image.RGBA { | 264 | func (r *Renderer) Pixels() *image.RGBA { | ||
| 495 | out := image.NewRGBA(image.Rect(0, 0, r.width, r.height)) | 265 | out := image.NewRGBA(image.Rect(0, 0, r.width, r.height)) | ||
| 496 | if r.mapped == nil || r.readback() != nil { | 266 | if r.mapped == nil || r.readback() != nil { | ||
A internal/adapters/vulkan/scene.go +316 -0
| @@ -0,0 +1,316 @@ | |||
| 1 | + | package vulkan | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "image" | |
| 5 | + | "math" | |
| 6 | + | "strconv" | |
| 7 | + | ||
| 8 | + | "github.com/bnema/nefertty/internal/ports" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | // sceneWalk turns one scene into quads in paint order, copying new wl_shm | |
| 12 | + | // content into GPU buffers on the way. It lives for one frame. | |
| 13 | + | type sceneWalk struct { | |
| 14 | + | r *Renderer | |
| 15 | + | s ports.Scene | |
| 16 | + | contents map[ports.WindowID]ports.SurfaceContent | |
| 17 | + | dmg *damageRegion | |
| 18 | + | // scale maps the scene's logical pixels to the target's physical ones. | |
| 19 | + | scale float64 | |
| 20 | + | bounds image.Rectangle | |
| 21 | + | fullscreen bool | |
| 22 | + | draws []draw | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | // draws walks the scene into quads in paint order. | |
| 26 | + | func (r *Renderer) draws(s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent, dmg *damageRegion) []draw { | |
| 27 | + | w := &sceneWalk{r: r, s: s, contents: contents, dmg: dmg, scale: s.Scale, bounds: image.Rect(0, 0, r.width, r.height)} | |
| 28 | + | if w.scale <= 0 { | |
| 29 | + | w.scale = 1 | |
| 30 | + | } | |
| 31 | + | for _, win := range s.Windows { | |
| 32 | + | w.fullscreen = w.fullscreen || (!win.Hidden && win.Fullscreen) | |
| 33 | + | } | |
| 34 | + | w.layers(false) | |
| 35 | + | w.windows() | |
| 36 | + | w.popups(false) | |
| 37 | + | w.layers(true) | |
| 38 | + | w.popups(true) | |
| 39 | + | return w.draws | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | func (w *sceneWalk) phys(v int) int { return int(math.Round(float64(v) * w.scale)) } | |
| 43 | + | ||
| 44 | + | func (w *sceneWalk) physRect(x, y, width, height int) image.Rectangle { | |
| 45 | + | return image.Rect(w.phys(x), w.phys(y), w.phys(x+width), w.phys(y+height)) | |
| 46 | + | } | |
| 47 | + | ||
| 48 | + | // fill draws a solid rect (physical pixels). | |
| 49 | + | func (w *sceneWalk) fill(rect image.Rectangle, c [3]uint8) { | |
| 50 | + | rect = rect.Intersect(w.bounds) | |
| 51 | + | if rect.Empty() { | |
| 52 | + | return | |
| 53 | + | } | |
| 54 | + | w.draws = append(w.draws, w.r.fillDraw(rect, c)) | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | // layers draws the layer surfaces below the windows, or above them. | |
| 58 | + | // A fullscreen window hides the bottom and top layers. | |
| 59 | + | func (w *sceneWalk) layers(afterWindows bool) { | |
| 60 | + | for _, layer := range w.s.Layers { | |
| 61 | + | if (layer.Layer >= ports.LayerTop) != afterWindows || | |
| 62 | + | (w.fullscreen && (layer.Layer == ports.LayerBottom || layer.Layer == ports.LayerTop)) || | |
| 63 | + | layer.Rect.W <= 0 || layer.Rect.H <= 0 { | |
| 64 | + | continue | |
| 65 | + | } | |
| 66 | + | content := w.contents[layer.ID] | |
| 67 | + | w.dmg.window(layer.ID, content, w.physRect(layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H)) | |
| 68 | + | w.place(layer.ID, &content, layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H) | |
| 69 | + | } | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | // windows draws the tiled then floating windows with their borders. | |
| 73 | + | // Tile lines (Window 0) go over the tiles, a float's border with it. | |
| 74 | + | func (w *sceneWalk) windows() { | |
| 75 | + | tileLines := false | |
| 76 | + | for _, win := range w.s.Windows { | |
| 77 | + | if win.Floating && !tileLines { | |
| 78 | + | w.separators(0) | |
| 79 | + | tileLines = true | |
| 80 | + | } | |
| 81 | + | if win.Hidden || win.Popup || win.Rect.W <= 0 || win.Rect.H <= 0 { | |
| 82 | + | continue | |
| 83 | + | } | |
| 84 | + | // Content sits inside the border; core sized the client to match. | |
| 85 | + | b, inset := 0, ports.Sides(0) | |
| 86 | + | if !win.Fullscreen { | |
| 87 | + | b, inset = w.s.Border.Width, win.Inset | |
| 88 | + | } | |
| 89 | + | c := win.Rect.Inset(inset, b) | |
| 90 | + | body := w.physRect(c.X, c.Y, c.W, c.H) | |
| 91 | + | content := w.contents[win.ID] | |
| 92 | + | w.dmg.window(win.ID, content, w.physRect(win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H)) | |
| 93 | + | if content.Empty() { | |
| 94 | + | w.fill(body, windowColor(win.ID)) | |
| 95 | + | } else { | |
| 96 | + | w.fill(body, parseColor(w.s.Background)) | |
| 97 | + | w.place(win.ID, &content, c.X, c.Y, c.W, c.H) | |
| 98 | + | } | |
| 99 | + | if win.Floating { | |
| 100 | + | w.separators(win.ID) | |
| 101 | + | } | |
| 102 | + | } | |
| 103 | + | if !tileLines { | |
| 104 | + | w.separators(0) | |
| 105 | + | } | |
| 106 | + | } | |
| 107 | + | ||
| 108 | + | // separators draws a window's border lines in order, active lines over | |
| 109 | + | // inactive ones. | |
| 110 | + | func (w *sceneWalk) separators(id ports.WindowID) { | |
| 111 | + | for _, sep := range w.s.Separators { | |
| 112 | + | if sep.Window != id { | |
| 113 | + | continue | |
| 114 | + | } | |
| 115 | + | col := w.s.Border.Inactive | |
| 116 | + | if sep.Active { | |
| 117 | + | col = w.s.Border.Active | |
| 118 | + | } | |
| 119 | + | if col != "" { | |
| 120 | + | w.fill(w.physRect(sep.Rect.X, sep.Rect.Y, sep.Rect.W, sep.Rect.H), parseColor(col)) | |
| 121 | + | } | |
| 122 | + | } | |
| 123 | + | } | |
| 124 | + | ||
| 125 | + | // popups draws only what the client drew, shadows clipped. Window popups | |
| 126 | + | // stay with the windows; a layer's go over every layer. | |
| 127 | + | func (w *sceneWalk) popups(overLayers bool) { | |
| 128 | + | for _, win := range w.s.Windows { | |
| 129 | + | if win.Popup && win.OverLayers == overLayers && !win.Hidden && win.Rect.W > 0 && win.Rect.H > 0 { | |
| 130 | + | content := w.contents[win.ID] | |
| 131 | + | w.dmg.window(win.ID, content, w.physRect(win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H)) | |
| 132 | + | w.place(win.ID, &content, win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H) | |
| 133 | + | } | |
| 134 | + | } | |
| 135 | + | } | |
| 136 | + | ||
| 137 | + | // place draws a surface tree with its window geometry at (x, y) logical, | |
| 138 | + | // clipped to width×height logical: client shadows fall outside. Every | |
| 139 | + | // surface of the tree is keyed by its window and place in it; the tree's | |
| 140 | + | // Seq changes with any of them. | |
| 141 | + | func (w *sceneWalk) place(id ports.WindowID, content *ports.SurfaceContent, x, y, width, height int) { | |
| 142 | + | clip := image.Rect(x, y, x+width, y+height) | |
| 143 | + | ox, oy := x-content.Geometry.X, y-content.Geometry.Y | |
| 144 | + | covers := w.opaqueChildren(content, ox, oy, clip) | |
| 145 | + | for i := range content.Children { | |
| 146 | + | if ch := &content.Children[i]; ch.Below { | |
| 147 | + | w.surface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip, shmKey{id, i + 1}, content.Seq, covers) | |
| 148 | + | } | |
| 149 | + | } | |
| 150 | + | w.surface(content, ox, oy, clip, shmKey{id, 0}, content.Seq, covers) | |
| 151 | + | for i := range content.Children { | |
| 152 | + | if ch := &content.Children[i]; !ch.Below { | |
| 153 | + | w.surface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip, shmKey{id, i + 1}, content.Seq, nil) | |
| 154 | + | } | |
| 155 | + | } | |
| 156 | + | } | |
| 157 | + | ||
| 158 | + | // opaqueChildren gives the physical rects shown by the opaque subsurfaces | |
| 159 | + | // above the root (e.g. a game's GPU surface over its window's shm buffer): | |
| 160 | + | // they hide the surfaces below them. A child whose buffer will not draw is | |
| 161 | + | // left out, so the surface below it still shows: a dmabuf that fails to | |
| 162 | + | // import (opaqueChildren pre-imports it) or an unreadable shm buffer. | |
| 163 | + | func (w *sceneWalk) opaqueChildren(content *ports.SurfaceContent, ox, oy int, clip image.Rectangle) []image.Rectangle { | |
| 164 | + | var covers []image.Rectangle | |
| 165 | + | for i := range content.Children { | |
| 166 | + | ch := &content.Children[i] | |
| 167 | + | if ch.Below || !ch.Opaque { | |
| 168 | + | continue | |
| 169 | + | } | |
| 170 | + | _, dst, ok := w.surfaceRects(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip) | |
| 171 | + | if !ok || !w.drawable(&ch.SurfaceContent) { | |
| 172 | + | continue | |
| 173 | + | } | |
| 174 | + | covers = append(covers, dst) | |
| 175 | + | } | |
| 176 | + | return covers | |
| 177 | + | } | |
| 178 | + | ||
| 179 | + | // drawable reports whether the child's buffer will draw. GPU buffer | |
| 180 | + | // imports are cached, so pre-importing here costs nothing. | |
| 181 | + | func (w *sceneWalk) drawable(c *ports.SurfaceContent) bool { | |
| 182 | + | if c.DMABuf != nil { | |
| 183 | + | _, err := w.r.importDMABuf(c.DMABuf) | |
| 184 | + | return err == nil | |
| 185 | + | } | |
| 186 | + | b := c.SHM | |
| 187 | + | if b == nil { | |
| 188 | + | return false | |
| 189 | + | } | |
| 190 | + | _, err := w.r.shmPixels(b, b.Offset+(c.Height-1)*b.Stride+c.Width*4) | |
| 191 | + | return err == nil | |
| 192 | + | } | |
| 193 | + | ||
| 194 | + | // surfaceRects gives the physical rect of a surface buffer with its origin | |
| 195 | + | // at (x, y) logical, and the part of it clip (logical) shows. ok is false | |
| 196 | + | // when there is nothing to draw. | |
| 197 | + | func (w *sceneWalk) surfaceRects(content *ports.SurfaceContent, x, y int, clip image.Rectangle) (full, dst image.Rectangle, ok bool) { | |
| 198 | + | lw, lh := content.LogicalW, content.LogicalH | |
| 199 | + | if lw <= 0 || lh <= 0 { | |
| 200 | + | lw, lh = content.Width, content.Height | |
| 201 | + | } | |
| 202 | + | if content.SHM == nil && content.DMABuf == nil || lw <= 0 || lh <= 0 || content.Width <= 0 || content.Height <= 0 { | |
| 203 | + | return full, dst, false | |
| 204 | + | } | |
| 205 | + | if content.DMABuf == nil && (content.SHM.Stride < content.Width*4 || content.SHM.Offset < 0) { | |
| 206 | + | return full, dst, false | |
| 207 | + | } | |
| 208 | + | full = w.physRect(x, y, lw, lh) | |
| 209 | + | // A buffer drawn at the physical size (fractional-scale clients round | |
| 210 | + | // w*scale, we round per edge) is copied 1:1, never resampled for 1px. | |
| 211 | + | near := func(a, b int) bool { return a-b <= 1 && b-a <= 1 } | |
| 212 | + | if near(full.Dx(), content.Width) && near(full.Dy(), content.Height) { | |
| 213 | + | full.Max = full.Min.Add(image.Pt(content.Width, content.Height)) | |
| 214 | + | } | |
| 215 | + | dst = full.Intersect(w.physRect(clip.Min.X, clip.Min.Y, clip.Dx(), clip.Dy())) | |
| 216 | + | return full, dst, !dst.Empty() | |
| 217 | + | } | |
| 218 | + | ||
| 219 | + | // surface draws one surface buffer with its origin at (x, y) logical, | |
| 220 | + | // clipped to clip (logical), unless an opaque surface above it (covers, | |
| 221 | + | // physical) hides all of it: its buffer is then neither copied nor drawn. | |
| 222 | + | func (w *sceneWalk) surface(content *ports.SurfaceContent, x, y int, clip image.Rectangle, key shmKey, seq uint64, covers []image.Rectangle) { | |
| 223 | + | full, dst, ok := w.surfaceRects(content, x, y, clip) | |
| 224 | + | if !ok { | |
| 225 | + | return | |
| 226 | + | } | |
| 227 | + | for _, c := range covers { | |
| 228 | + | if dst.In(c) { | |
| 229 | + | return | |
| 230 | + | } | |
| 231 | + | } | |
| 232 | + | if key.index == 0 { | |
| 233 | + | w.dmg.content(key.win, content, full, dst) | |
| 234 | + | } | |
| 235 | + | w.content(dst, full, content, key, seq) | |
| 236 | + | } | |
| 237 | + | ||
| 238 | + | // content draws the part of a buffer mapped onto full (physical pixels) | |
| 239 | + | // that dst shows. key names the surface, seq its content. | |
| 240 | + | func (w *sceneWalk) content(dst, full image.Rectangle, content *ports.SurfaceContent, key shmKey, seq uint64) { | |
| 241 | + | r := w.r | |
| 242 | + | rect := dst.Intersect(w.bounds) | |
| 243 | + | if rect.Empty() { | |
| 244 | + | return | |
| 245 | + | } | |
| 246 | + | if content.DMABuf != nil { | |
| 247 | + | im, err := r.importDMABuf(content.DMABuf) | |
| 248 | + | if err != nil { | |
| 249 | + | // The window shows its background until a buffer imports. | |
| 250 | + | return | |
| 251 | + | } | |
| 252 | + | dr := r.contentDraw(rect, full, content.Width, content.Height, modeImage, content.Opaque) | |
| 253 | + | dr.set, dr.im = im.set, im | |
| 254 | + | dr.acquire = content.Acquire | |
| 255 | + | w.draws = append(w.draws, dr) | |
| 256 | + | return | |
| 257 | + | } | |
| 258 | + | b := content.SHM | |
| 259 | + | pixels, err := r.shmPixels(b, b.Offset+(content.Height-1)*b.Stride+content.Width*4) | |
| 260 | + | if err != nil { | |
| 261 | + | return | |
| 262 | + | } | |
| 263 | + | st := shmState{w: content.Width, h: content.Height, seq: seq} | |
| 264 | + | // Damage history is the root surface's: children copy in full. | |
| 265 | + | var damage func(uint64) ([]ports.Rect, bool) | |
| 266 | + | if key.index == 0 { | |
| 267 | + | damage = content.DamageSince | |
| 268 | + | } | |
| 269 | + | c, err := r.shmCopyFor(key, st, pixels, b.Offset, b.Stride, damage) | |
| 270 | + | if err != nil { | |
| 271 | + | return | |
| 272 | + | } | |
| 273 | + | dr := r.contentDraw(rect, full, content.Width, content.Height, modeBuffer, content.Opaque) | |
| 274 | + | dr.set = c.set | |
| 275 | + | dr.pc.buf = [4]uint32{0, uint32(content.Width), uint32(content.Height), 0} | |
| 276 | + | w.draws = append(w.draws, dr) | |
| 277 | + | } | |
| 278 | + | ||
| 279 | + | func parseColor(s string) [3]uint8 { | |
| 280 | + | var c [3]uint8 | |
| 281 | + | if len(s) != 7 || s[0] != '#' { | |
| 282 | + | return c | |
| 283 | + | } | |
| 284 | + | for i := range c { | |
| 285 | + | v, e := strconv.ParseUint(s[1+i*2:3+i*2], 16, 8) | |
| 286 | + | if e != nil { | |
| 287 | + | return [3]uint8{} | |
| 288 | + | } | |
| 289 | + | c[i] = uint8(v) | |
| 290 | + | } | |
| 291 | + | return c | |
| 292 | + | } | |
| 293 | + | ||
| 294 | + | // windowColor is the placeholder color of a window with no content yet. | |
| 295 | + | func windowColor(id ports.WindowID) [3]uint8 { | |
| 296 | + | h := math.Mod(float64(id)*0.618034, 1) * 6 | |
| 297 | + | sector := int(h) | |
| 298 | + | f := h - float64(sector) | |
| 299 | + | p, q, t := 0.4, 0.8*(1-0.5*f), 0.8*(1-0.5*(1-f)) | |
| 300 | + | var a, b, c float64 | |
| 301 | + | switch sector { | |
| 302 | + | case 0: | |
| 303 | + | a, b, c = 0.8, t, p | |
| 304 | + | case 1: | |
| 305 | + | a, b, c = q, 0.8, p | |
| 306 | + | case 2: | |
| 307 | + | a, b, c = p, 0.8, t | |
| 308 | + | case 3: | |
| 309 | + | a, b, c = p, q, 0.8 | |
| 310 | + | case 4: | |
| 311 | + | a, b, c = t, p, 0.8 | |
| 312 | + | default: | |
| 313 | + | a, b, c = 0.8, p, q | |
| 314 | + | } | |
| 315 | + | return [3]uint8{uint8(a * 255), uint8(b * 255), uint8(c * 255)} | |
| 316 | + | } | |
| @@ -0,0 +1,316 @@ | |||||
| 1 | + | package vulkan | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "image" | |||
| 5 | + | "math" | |||
| 6 | + | "strconv" | |||
| 7 | + | ||||
| 8 | + | "github.com/bnema/nefertty/internal/ports" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | // sceneWalk turns one scene into quads in paint order, copying new wl_shm | |||
| 12 | + | // content into GPU buffers on the way. It lives for one frame. | |||
| 13 | + | type sceneWalk struct { | |||
| 14 | + | r *Renderer | |||
| 15 | + | s ports.Scene | |||
| 16 | + | contents map[ports.WindowID]ports.SurfaceContent | |||
| 17 | + | dmg *damageRegion | |||
| 18 | + | // scale maps the scene's logical pixels to the target's physical ones. | |||
| 19 | + | scale float64 | |||
| 20 | + | bounds image.Rectangle | |||
| 21 | + | fullscreen bool | |||
| 22 | + | draws []draw | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | // draws walks the scene into quads in paint order. | |||
| 26 | + | func (r *Renderer) draws(s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent, dmg *damageRegion) []draw { | |||
| 27 | + | w := &sceneWalk{r: r, s: s, contents: contents, dmg: dmg, scale: s.Scale, bounds: image.Rect(0, 0, r.width, r.height)} | |||
| 28 | + | if w.scale <= 0 { | |||
| 29 | + | w.scale = 1 | |||
| 30 | + | } | |||
| 31 | + | for _, win := range s.Windows { | |||
| 32 | + | w.fullscreen = w.fullscreen || (!win.Hidden && win.Fullscreen) | |||
| 33 | + | } | |||
| 34 | + | w.layers(false) | |||
| 35 | + | w.windows() | |||
| 36 | + | w.popups(false) | |||
| 37 | + | w.layers(true) | |||
| 38 | + | w.popups(true) | |||
| 39 | + | return w.draws | |||
| 40 | + | } | |||
| 41 | + | ||||
| 42 | + | func (w *sceneWalk) phys(v int) int { return int(math.Round(float64(v) * w.scale)) } | |||
| 43 | + | ||||
| 44 | + | func (w *sceneWalk) physRect(x, y, width, height int) image.Rectangle { | |||
| 45 | + | return image.Rect(w.phys(x), w.phys(y), w.phys(x+width), w.phys(y+height)) | |||
| 46 | + | } | |||
| 47 | + | ||||
| 48 | + | // fill draws a solid rect (physical pixels). | |||
| 49 | + | func (w *sceneWalk) fill(rect image.Rectangle, c [3]uint8) { | |||
| 50 | + | rect = rect.Intersect(w.bounds) | |||
| 51 | + | if rect.Empty() { | |||
| 52 | + | return | |||
| 53 | + | } | |||
| 54 | + | w.draws = append(w.draws, w.r.fillDraw(rect, c)) | |||
| 55 | + | } | |||
| 56 | + | ||||
| 57 | + | // layers draws the layer surfaces below the windows, or above them. | |||
| 58 | + | // A fullscreen window hides the bottom and top layers. | |||
| 59 | + | func (w *sceneWalk) layers(afterWindows bool) { | |||
| 60 | + | for _, layer := range w.s.Layers { | |||
| 61 | + | if (layer.Layer >= ports.LayerTop) != afterWindows || | |||
| 62 | + | (w.fullscreen && (layer.Layer == ports.LayerBottom || layer.Layer == ports.LayerTop)) || | |||
| 63 | + | layer.Rect.W <= 0 || layer.Rect.H <= 0 { | |||
| 64 | + | continue | |||
| 65 | + | } | |||
| 66 | + | content := w.contents[layer.ID] | |||
| 67 | + | w.dmg.window(layer.ID, content, w.physRect(layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H)) | |||
| 68 | + | w.place(layer.ID, &content, layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H) | |||
| 69 | + | } | |||
| 70 | + | } | |||
| 71 | + | ||||
| 72 | + | // windows draws the tiled then floating windows with their borders. | |||
| 73 | + | // Tile lines (Window 0) go over the tiles, a float's border with it. | |||
| 74 | + | func (w *sceneWalk) windows() { | |||
| 75 | + | tileLines := false | |||
| 76 | + | for _, win := range w.s.Windows { | |||
| 77 | + | if win.Floating && !tileLines { | |||
| 78 | + | w.separators(0) | |||
| 79 | + | tileLines = true | |||
| 80 | + | } | |||
| 81 | + | if win.Hidden || win.Popup || win.Rect.W <= 0 || win.Rect.H <= 0 { | |||
| 82 | + | continue | |||
| 83 | + | } | |||
| 84 | + | // Content sits inside the border; core sized the client to match. | |||
| 85 | + | b, inset := 0, ports.Sides(0) | |||
| 86 | + | if !win.Fullscreen { | |||
| 87 | + | b, inset = w.s.Border.Width, win.Inset | |||
| 88 | + | } | |||
| 89 | + | c := win.Rect.Inset(inset, b) | |||
| 90 | + | body := w.physRect(c.X, c.Y, c.W, c.H) | |||
| 91 | + | content := w.contents[win.ID] | |||
| 92 | + | w.dmg.window(win.ID, content, w.physRect(win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H)) | |||
| 93 | + | if content.Empty() { | |||
| 94 | + | w.fill(body, windowColor(win.ID)) | |||
| 95 | + | } else { | |||
| 96 | + | w.fill(body, parseColor(w.s.Background)) | |||
| 97 | + | w.place(win.ID, &content, c.X, c.Y, c.W, c.H) | |||
| 98 | + | } | |||
| 99 | + | if win.Floating { | |||
| 100 | + | w.separators(win.ID) | |||
| 101 | + | } | |||
| 102 | + | } | |||
| 103 | + | if !tileLines { | |||
| 104 | + | w.separators(0) | |||
| 105 | + | } | |||
| 106 | + | } | |||
| 107 | + | ||||
| 108 | + | // separators draws a window's border lines in order, active lines over | |||
| 109 | + | // inactive ones. | |||
| 110 | + | func (w *sceneWalk) separators(id ports.WindowID) { | |||
| 111 | + | for _, sep := range w.s.Separators { | |||
| 112 | + | if sep.Window != id { | |||
| 113 | + | continue | |||
| 114 | + | } | |||
| 115 | + | col := w.s.Border.Inactive | |||
| 116 | + | if sep.Active { | |||
| 117 | + | col = w.s.Border.Active | |||
| 118 | + | } | |||
| 119 | + | if col != "" { | |||
| 120 | + | w.fill(w.physRect(sep.Rect.X, sep.Rect.Y, sep.Rect.W, sep.Rect.H), parseColor(col)) | |||
| 121 | + | } | |||
| 122 | + | } | |||
| 123 | + | } | |||
| 124 | + | ||||
| 125 | + | // popups draws only what the client drew, shadows clipped. Window popups | |||
| 126 | + | // stay with the windows; a layer's go over every layer. | |||
| 127 | + | func (w *sceneWalk) popups(overLayers bool) { | |||
| 128 | + | for _, win := range w.s.Windows { | |||
| 129 | + | if win.Popup && win.OverLayers == overLayers && !win.Hidden && win.Rect.W > 0 && win.Rect.H > 0 { | |||
| 130 | + | content := w.contents[win.ID] | |||
| 131 | + | w.dmg.window(win.ID, content, w.physRect(win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H)) | |||
| 132 | + | w.place(win.ID, &content, win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H) | |||
| 133 | + | } | |||
| 134 | + | } | |||
| 135 | + | } | |||
| 136 | + | ||||
| 137 | + | // place draws a surface tree with its window geometry at (x, y) logical, | |||
| 138 | + | // clipped to width×height logical: client shadows fall outside. Every | |||
| 139 | + | // surface of the tree is keyed by its window and place in it; the tree's | |||
| 140 | + | // Seq changes with any of them. | |||
| 141 | + | func (w *sceneWalk) place(id ports.WindowID, content *ports.SurfaceContent, x, y, width, height int) { | |||
| 142 | + | clip := image.Rect(x, y, x+width, y+height) | |||
| 143 | + | ox, oy := x-content.Geometry.X, y-content.Geometry.Y | |||
| 144 | + | covers := w.opaqueChildren(content, ox, oy, clip) | |||
| 145 | + | for i := range content.Children { | |||
| 146 | + | if ch := &content.Children[i]; ch.Below { | |||
| 147 | + | w.surface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip, shmKey{id, i + 1}, content.Seq, covers) | |||
| 148 | + | } | |||
| 149 | + | } | |||
| 150 | + | w.surface(content, ox, oy, clip, shmKey{id, 0}, content.Seq, covers) | |||
| 151 | + | for i := range content.Children { | |||
| 152 | + | if ch := &content.Children[i]; !ch.Below { | |||
| 153 | + | w.surface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip, shmKey{id, i + 1}, content.Seq, nil) | |||
| 154 | + | } | |||
| 155 | + | } | |||
| 156 | + | } | |||
| 157 | + | ||||
| 158 | + | // opaqueChildren gives the physical rects shown by the opaque subsurfaces | |||
| 159 | + | // above the root (e.g. a game's GPU surface over its window's shm buffer): | |||
| 160 | + | // they hide the surfaces below them. A child whose buffer will not draw is | |||
| 161 | + | // left out, so the surface below it still shows: a dmabuf that fails to | |||
| 162 | + | // import (opaqueChildren pre-imports it) or an unreadable shm buffer. | |||
| 163 | + | func (w *sceneWalk) opaqueChildren(content *ports.SurfaceContent, ox, oy int, clip image.Rectangle) []image.Rectangle { | |||
| 164 | + | var covers []image.Rectangle | |||
| 165 | + | for i := range content.Children { | |||
| 166 | + | ch := &content.Children[i] | |||
| 167 | + | if ch.Below || !ch.Opaque { | |||
| 168 | + | continue | |||
| 169 | + | } | |||
| 170 | + | _, dst, ok := w.surfaceRects(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip) | |||
| 171 | + | if !ok || !w.drawable(&ch.SurfaceContent) { | |||
| 172 | + | continue | |||
| 173 | + | } | |||
| 174 | + | covers = append(covers, dst) | |||
| 175 | + | } | |||
| 176 | + | return covers | |||
| 177 | + | } | |||
| 178 | + | ||||
| 179 | + | // drawable reports whether the child's buffer will draw. GPU buffer | |||
| 180 | + | // imports are cached, so pre-importing here costs nothing. | |||
| 181 | + | func (w *sceneWalk) drawable(c *ports.SurfaceContent) bool { | |||
| 182 | + | if c.DMABuf != nil { | |||
| 183 | + | _, err := w.r.importDMABuf(c.DMABuf) | |||
| 184 | + | return err == nil | |||
| 185 | + | } | |||
| 186 | + | b := c.SHM | |||
| 187 | + | if b == nil { | |||
| 188 | + | return false | |||
| 189 | + | } | |||
| 190 | + | _, err := w.r.shmPixels(b, b.Offset+(c.Height-1)*b.Stride+c.Width*4) | |||
| 191 | + | return err == nil | |||
| 192 | + | } | |||
| 193 | + | ||||
| 194 | + | // surfaceRects gives the physical rect of a surface buffer with its origin | |||
| 195 | + | // at (x, y) logical, and the part of it clip (logical) shows. ok is false | |||
| 196 | + | // when there is nothing to draw. | |||
| 197 | + | func (w *sceneWalk) surfaceRects(content *ports.SurfaceContent, x, y int, clip image.Rectangle) (full, dst image.Rectangle, ok bool) { | |||
| 198 | + | lw, lh := content.LogicalW, content.LogicalH | |||
| 199 | + | if lw <= 0 || lh <= 0 { | |||
| 200 | + | lw, lh = content.Width, content.Height | |||
| 201 | + | } | |||
| 202 | + | if content.SHM == nil && content.DMABuf == nil || lw <= 0 || lh <= 0 || content.Width <= 0 || content.Height <= 0 { | |||
| 203 | + | return full, dst, false | |||
| 204 | + | } | |||
| 205 | + | if content.DMABuf == nil && (content.SHM.Stride < content.Width*4 || content.SHM.Offset < 0) { | |||
| 206 | + | return full, dst, false | |||
| 207 | + | } | |||
| 208 | + | full = w.physRect(x, y, lw, lh) | |||
| 209 | + | // A buffer drawn at the physical size (fractional-scale clients round | |||
| 210 | + | // w*scale, we round per edge) is copied 1:1, never resampled for 1px. | |||
| 211 | + | near := func(a, b int) bool { return a-b <= 1 && b-a <= 1 } | |||
| 212 | + | if near(full.Dx(), content.Width) && near(full.Dy(), content.Height) { | |||
| 213 | + | full.Max = full.Min.Add(image.Pt(content.Width, content.Height)) | |||
| 214 | + | } | |||
| 215 | + | dst = full.Intersect(w.physRect(clip.Min.X, clip.Min.Y, clip.Dx(), clip.Dy())) | |||
| 216 | + | return full, dst, !dst.Empty() | |||
| 217 | + | } | |||
| 218 | + | ||||
| 219 | + | // surface draws one surface buffer with its origin at (x, y) logical, | |||
| 220 | + | // clipped to clip (logical), unless an opaque surface above it (covers, | |||
| 221 | + | // physical) hides all of it: its buffer is then neither copied nor drawn. | |||
| 222 | + | func (w *sceneWalk) surface(content *ports.SurfaceContent, x, y int, clip image.Rectangle, key shmKey, seq uint64, covers []image.Rectangle) { | |||
| 223 | + | full, dst, ok := w.surfaceRects(content, x, y, clip) | |||
| 224 | + | if !ok { | |||
| 225 | + | return | |||
| 226 | + | } | |||
| 227 | + | for _, c := range covers { | |||
| 228 | + | if dst.In(c) { | |||
| 229 | + | return | |||
| 230 | + | } | |||
| 231 | + | } | |||
| 232 | + | if key.index == 0 { | |||
| 233 | + | w.dmg.content(key.win, content, full, dst) | |||
| 234 | + | } | |||
| 235 | + | w.content(dst, full, content, key, seq) | |||
| 236 | + | } | |||
| 237 | + | ||||
| 238 | + | // content draws the part of a buffer mapped onto full (physical pixels) | |||
| 239 | + | // that dst shows. key names the surface, seq its content. | |||
| 240 | + | func (w *sceneWalk) content(dst, full image.Rectangle, content *ports.SurfaceContent, key shmKey, seq uint64) { | |||
| 241 | + | r := w.r | |||
| 242 | + | rect := dst.Intersect(w.bounds) | |||
| 243 | + | if rect.Empty() { | |||
| 244 | + | return | |||
| 245 | + | } | |||
| 246 | + | if content.DMABuf != nil { | |||
| 247 | + | im, err := r.importDMABuf(content.DMABuf) | |||
| 248 | + | if err != nil { | |||
| 249 | + | // The window shows its background until a buffer imports. | |||
| 250 | + | return | |||
| 251 | + | } | |||
| 252 | + | dr := r.contentDraw(rect, full, content.Width, content.Height, modeImage, content.Opaque) | |||
| 253 | + | dr.set, dr.im = im.set, im | |||
| 254 | + | dr.acquire = content.Acquire | |||
| 255 | + | w.draws = append(w.draws, dr) | |||
| 256 | + | return | |||
| 257 | + | } | |||
| 258 | + | b := content.SHM | |||
| 259 | + | pixels, err := r.shmPixels(b, b.Offset+(content.Height-1)*b.Stride+content.Width*4) | |||
| 260 | + | if err != nil { | |||
| 261 | + | return | |||
| 262 | + | } | |||
| 263 | + | st := shmState{w: content.Width, h: content.Height, seq: seq} | |||
| 264 | + | // Damage history is the root surface's: children copy in full. | |||
| 265 | + | var damage func(uint64) ([]ports.Rect, bool) | |||
| 266 | + | if key.index == 0 { | |||
| 267 | + | damage = content.DamageSince | |||
| 268 | + | } | |||
| 269 | + | c, err := r.shmCopyFor(key, st, pixels, b.Offset, b.Stride, damage) | |||
| 270 | + | if err != nil { | |||
| 271 | + | return | |||
| 272 | + | } | |||
| 273 | + | dr := r.contentDraw(rect, full, content.Width, content.Height, modeBuffer, content.Opaque) | |||
| 274 | + | dr.set = c.set | |||
| 275 | + | dr.pc.buf = [4]uint32{0, uint32(content.Width), uint32(content.Height), 0} | |||
| 276 | + | w.draws = append(w.draws, dr) | |||
| 277 | + | } | |||
| 278 | + | ||||
| 279 | + | func parseColor(s string) [3]uint8 { | |||
| 280 | + | var c [3]uint8 | |||
| 281 | + | if len(s) != 7 || s[0] != '#' { | |||
| 282 | + | return c | |||
| 283 | + | } | |||
| 284 | + | for i := range c { | |||
| 285 | + | v, e := strconv.ParseUint(s[1+i*2:3+i*2], 16, 8) | |||
| 286 | + | if e != nil { | |||
| 287 | + | return [3]uint8{} | |||
| 288 | + | } | |||
| 289 | + | c[i] = uint8(v) | |||
| 290 | + | } | |||
| 291 | + | return c | |||
| 292 | + | } | |||
| 293 | + | ||||
| 294 | + | // windowColor is the placeholder color of a window with no content yet. | |||
| 295 | + | func windowColor(id ports.WindowID) [3]uint8 { | |||
| 296 | + | h := math.Mod(float64(id)*0.618034, 1) * 6 | |||
| 297 | + | sector := int(h) | |||
| 298 | + | f := h - float64(sector) | |||
| 299 | + | p, q, t := 0.4, 0.8*(1-0.5*f), 0.8*(1-0.5*(1-f)) | |||
| 300 | + | var a, b, c float64 | |||
| 301 | + | switch sector { | |||
| 302 | + | case 0: | |||
| 303 | + | a, b, c = 0.8, t, p | |||
| 304 | + | case 1: | |||
| 305 | + | a, b, c = q, 0.8, p | |||
| 306 | + | case 2: | |||
| 307 | + | a, b, c = p, 0.8, t | |||
| 308 | + | case 3: | |||
| 309 | + | a, b, c = p, q, 0.8 | |||
| 310 | + | case 4: | |||
| 311 | + | a, b, c = t, p, 0.8 | |||
| 312 | + | default: | |||
| 313 | + | a, b, c = 0.8, p, q | |||
| 314 | + | } | |||
| 315 | + | return [3]uint8{uint8(a * 255), uint8(b * 255), uint8(c * 255)} | |||
| 316 | + | } | |||