merge: render-fills (solid fills staged as a strip, 30% -> 2% CPU)
2 files · +77 -25
M internal/adapters/vulkan/renderer.go +46 -24
| @@ -42,8 +42,14 @@ type Renderer struct { | |||
| 42 | 42 | dmabuf ports.DMABufSupport | |
| 43 | 43 | imports map[uint64]*imported | |
| 44 | 44 | frame uint64 | |
| 45 | + | // fillRegions is reused by Render for the bands of solid fills. | |
| 46 | + | fillRegions []vk.BufferImageCopy | |
| 45 | 47 | } | |
| 46 | 48 | ||
| 49 | + | // fillRows is the height of the strip staged for a solid fill: the CPU | |
| 50 | + | // writes it once, the GPU copies it down the rect. | |
| 51 | + | const fillRows = 64 | |
| 52 | + | ||
| 47 | 53 | func checked(name string, result vk.Result) error { | |
| 48 | 54 | if err := vk.Check(result); err != nil { | |
| 49 | 55 | return fmt.Errorf("%s: %w", name, err) | |
| @@ -360,6 +366,8 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||
| 360 | 366 | opaque bool | |
| 361 | 367 | // dma is a client GPU buffer, blitted instead of copied from staging. | |
| 362 | 368 | dma *imported | |
| 369 | + | // fill is a solid color: staging holds one strip of rows. | |
| 370 | + | fill bool | |
| 363 | 371 | } | |
| 364 | 372 | var uploads []upload | |
| 365 | 373 | used := 0 | |
| @@ -369,8 +377,10 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||
| 369 | 377 | if rect.Empty() { | |
| 370 | 378 | return | |
| 371 | 379 | } | |
| 372 | - | uploads = append(uploads, upload{rect: rect, offset: used, color: c}) | |
| 373 | - | used += rect.Dx() * rect.Dy() * 4 | |
| 380 | + | // A fill stages a strip of up to fillRows rows; the GPU copies the | |
| 381 | + | // strip down the rect. | |
| 382 | + | uploads = append(uploads, upload{rect: rect, offset: used, color: c, fill: true}) | |
| 383 | + | used += rect.Dx() * min(rect.Dy(), fillRows) * 4 | |
| 374 | 384 | } | |
| 375 | 385 | // addContent maps src (buffer pixels) onto dst (physical pixels). | |
| 376 | 386 | addContent := func(dst, src image.Rectangle, content *ports.SurfaceContent) { | |
| @@ -498,31 +508,32 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||
| 498 | 508 | if u.dma != nil { | |
| 499 | 509 | continue | |
| 500 | 510 | } | |
| 511 | + | if u.fill { | |
| 512 | + | line := data[u.offset : u.offset+u.rect.Dx()*min(u.rect.Dy(), fillRows)*4] | |
| 513 | + | for i := 0; i < len(line); i += 4 { | |
| 514 | + | line[i], line[i+1], line[i+2], line[i+3] = u.color[2], u.color[1], u.color[0], 255 | |
| 515 | + | } | |
| 516 | + | continue | |
| 517 | + | } | |
| 501 | 518 | dst := data[u.offset : u.offset+u.rect.Dx()*u.rect.Dy()*4] | |
| 502 | 519 | for row := 0; row < u.rect.Dy(); row++ { | |
| 503 | 520 | line := dst[row*u.rect.Dx()*4 : (row+1)*u.rect.Dx()*4] | |
| 504 | - | if u.pixels != nil { | |
| 505 | - | y := u.rect.Min.Y + row | |
| 506 | - | if u.dst.Size() == u.src.Size() { | |
| 507 | - | start := (u.src.Min.Y+y-u.dst.Min.Y)*u.stride + (u.src.Min.X+u.rect.Min.X-u.dst.Min.X)*4 | |
| 508 | - | copy(line, u.pixels[start:start+len(line)]) | |
| 509 | - | } else { | |
| 510 | - | // Nearest-neighbour resample, sampling pixel centres. | |
| 511 | - | sy := u.src.Min.Y + ((y-u.dst.Min.Y)*2+1)*u.src.Dy()/(u.dst.Dy()*2) | |
| 512 | - | src := u.pixels[sy*u.stride:] | |
| 513 | - | for i := 0; i < u.rect.Dx(); i++ { | |
| 514 | - | sx := u.src.Min.X + ((u.rect.Min.X+i-u.dst.Min.X)*2+1)*u.src.Dx()/(u.dst.Dx()*2) | |
| 515 | - | copy(line[i*4:i*4+4], src[sx*4:sx*4+4]) | |
| 516 | - | } | |
| 517 | - | } | |
| 518 | - | if u.opaque { | |
| 519 | - | for i := 3; i < len(line); i += 4 { | |
| 520 | - | line[i] = 255 | |
| 521 | - | } | |
| 522 | - | } | |
| 521 | + | y := u.rect.Min.Y + row | |
| 522 | + | if u.dst.Size() == u.src.Size() { | |
| 523 | + | start := (u.src.Min.Y+y-u.dst.Min.Y)*u.stride + (u.src.Min.X+u.rect.Min.X-u.dst.Min.X)*4 | |
| 524 | + | copy(line, u.pixels[start:start+len(line)]) | |
| 523 | 525 | } else { | |
| 524 | - | for i := 0; i < len(line); i += 4 { | |
| 525 | - | line[i], line[i+1], line[i+2], line[i+3] = u.color[2], u.color[1], u.color[0], 255 | |
| 526 | + | // Nearest-neighbour resample, sampling pixel centres. | |
| 527 | + | sy := u.src.Min.Y + ((y-u.dst.Min.Y)*2+1)*u.src.Dy()/(u.dst.Dy()*2) | |
| 528 | + | src := u.pixels[sy*u.stride:] | |
| 529 | + | for i := 0; i < u.rect.Dx(); i++ { | |
| 530 | + | sx := u.src.Min.X + ((u.rect.Min.X+i-u.dst.Min.X)*2+1)*u.src.Dx()/(u.dst.Dx()*2) | |
| 531 | + | copy(line[i*4:i*4+4], src[sx*4:sx*4+4]) | |
| 532 | + | } | |
| 533 | + | } | |
| 534 | + | if u.opaque { | |
| 535 | + | for i := 3; i < len(line); i += 4 { | |
| 536 | + | line[i] = 255 | |
| 526 | 537 | } | |
| 527 | 538 | } | |
| 528 | 539 | } | |
| @@ -599,7 +610,18 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||
| 599 | 610 | d.CmdBlitImage(r.command, f.dma.image, vk.ImageLayoutTransferSrcOptimal, r.image, vk.ImageLayoutTransferDstOptimal, 1, &blit, vk.FilterNearest) | |
| 600 | 611 | continue | |
| 601 | 612 | } | |
| 602 | - | region := vk.BufferImageCopy{BufferOffset: vk.DeviceSize(f.offset), ImageSubresource: vk.ImageSubresourceLayers{AspectMask: vk.ImageAspectColorBit, LayerCount: 1}, ImageOffset: vk.Offset3D{X: int32(rect.Min.X), Y: int32(rect.Min.Y)}, ImageExtent: vk.Extent3D{Width: uint32(rect.Dx()), Height: uint32(rect.Dy()), Depth: 1}} | |
| 613 | + | layers := vk.ImageSubresourceLayers{AspectMask: vk.ImageAspectColorBit, LayerCount: 1} | |
| 614 | + | if f.fill { | |
| 615 | + | // Every band reads the same staged strip. | |
| 616 | + | r.fillRegions = r.fillRegions[:0] | |
| 617 | + | for y := rect.Min.Y; y < rect.Max.Y; y += fillRows { | |
| 618 | + | h := min(fillRows, rect.Max.Y-y) | |
| 619 | + | r.fillRegions = append(r.fillRegions, vk.BufferImageCopy{BufferOffset: vk.DeviceSize(f.offset), ImageSubresource: layers, ImageOffset: vk.Offset3D{X: int32(rect.Min.X), Y: int32(y)}, ImageExtent: vk.Extent3D{Width: uint32(rect.Dx()), Height: uint32(h), Depth: 1}}) | |
| 620 | + | } | |
| 621 | + | d.CmdCopyBufferToImage(r.command, r.staging, r.image, vk.ImageLayoutTransferDstOptimal, uint32(len(r.fillRegions)), &r.fillRegions[0]) | |
| 622 | + | continue | |
| 623 | + | } | |
| 624 | + | region := vk.BufferImageCopy{BufferOffset: vk.DeviceSize(f.offset), ImageSubresource: layers, ImageOffset: vk.Offset3D{X: int32(rect.Min.X), Y: int32(rect.Min.Y)}, ImageExtent: vk.Extent3D{Width: uint32(rect.Dx()), Height: uint32(rect.Dy()), Depth: 1}} | |
| 603 | 625 | d.CmdCopyBufferToImage(r.command, r.staging, r.image, vk.ImageLayoutTransferDstOptimal, 1, ®ion) | |
| 604 | 626 | } | |
| 605 | 627 | ownership(false) | |
| @@ -42,8 +42,14 @@ type Renderer struct { | |||||
| 42 | dmabuf ports.DMABufSupport | 42 | dmabuf ports.DMABufSupport | ||
| 43 | imports map[uint64]*imported | 43 | imports map[uint64]*imported | ||
| 44 | frame uint64 | 44 | frame uint64 | ||
| 45 | + | // fillRegions is reused by Render for the bands of solid fills. | |||
| 46 | + | fillRegions []vk.BufferImageCopy | |||
| 45 | } | 47 | } | ||
| 46 | 48 | ||||
| 49 | + | // fillRows is the height of the strip staged for a solid fill: the CPU | |||
| 50 | + | // writes it once, the GPU copies it down the rect. | |||
| 51 | + | const fillRows = 64 | |||
| 52 | + | ||||
| 47 | func checked(name string, result vk.Result) error { | 53 | func checked(name string, result vk.Result) error { | ||
| 48 | if err := vk.Check(result); err != nil { | 54 | if err := vk.Check(result); err != nil { | ||
| 49 | return fmt.Errorf("%s: %w", name, err) | 55 | return fmt.Errorf("%s: %w", name, err) | ||
| @@ -360,6 +366,8 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||||
| 360 | opaque bool | 366 | opaque bool | ||
| 361 | // dma is a client GPU buffer, blitted instead of copied from staging. | 367 | // dma is a client GPU buffer, blitted instead of copied from staging. | ||
| 362 | dma *imported | 368 | dma *imported | ||
| 369 | + | // fill is a solid color: staging holds one strip of rows. | |||
| 370 | + | fill bool | |||
| 363 | } | 371 | } | ||
| 364 | var uploads []upload | 372 | var uploads []upload | ||
| 365 | used := 0 | 373 | used := 0 | ||
| @@ -369,8 +377,10 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||||
| 369 | if rect.Empty() { | 377 | if rect.Empty() { | ||
| 370 | return | 378 | return | ||
| 371 | } | 379 | } | ||
| 372 | - | uploads = append(uploads, upload{rect: rect, offset: used, color: c}) | 380 | + | // A fill stages a strip of up to fillRows rows; the GPU copies the |
| 373 | - | used += rect.Dx() * rect.Dy() * 4 | 381 | + | // strip down the rect. |
| 382 | + | uploads = append(uploads, upload{rect: rect, offset: used, color: c, fill: true}) | |||
| 383 | + | used += rect.Dx() * min(rect.Dy(), fillRows) * 4 | |||
| 374 | } | 384 | } | ||
| 375 | // addContent maps src (buffer pixels) onto dst (physical pixels). | 385 | // addContent maps src (buffer pixels) onto dst (physical pixels). | ||
| 376 | addContent := func(dst, src image.Rectangle, content *ports.SurfaceContent) { | 386 | addContent := func(dst, src image.Rectangle, content *ports.SurfaceContent) { | ||
| @@ -498,31 +508,32 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||||
| 498 | if u.dma != nil { | 508 | if u.dma != nil { | ||
| 499 | continue | 509 | continue | ||
| 500 | } | 510 | } | ||
| 511 | + | if u.fill { | |||
| 512 | + | line := data[u.offset : u.offset+u.rect.Dx()*min(u.rect.Dy(), fillRows)*4] | |||
| 513 | + | for i := 0; i < len(line); i += 4 { | |||
| 514 | + | line[i], line[i+1], line[i+2], line[i+3] = u.color[2], u.color[1], u.color[0], 255 | |||
| 515 | + | } | |||
| 516 | + | continue | |||
| 517 | + | } | |||
| 501 | dst := data[u.offset : u.offset+u.rect.Dx()*u.rect.Dy()*4] | 518 | dst := data[u.offset : u.offset+u.rect.Dx()*u.rect.Dy()*4] | ||
| 502 | for row := 0; row < u.rect.Dy(); row++ { | 519 | for row := 0; row < u.rect.Dy(); row++ { | ||
| 503 | line := dst[row*u.rect.Dx()*4 : (row+1)*u.rect.Dx()*4] | 520 | line := dst[row*u.rect.Dx()*4 : (row+1)*u.rect.Dx()*4] | ||
| 504 | - | if u.pixels != nil { | 521 | + | y := u.rect.Min.Y + row |
| 505 | - | y := u.rect.Min.Y + row | 522 | + | if u.dst.Size() == u.src.Size() { |
| 506 | - | if u.dst.Size() == u.src.Size() { | 523 | + | start := (u.src.Min.Y+y-u.dst.Min.Y)*u.stride + (u.src.Min.X+u.rect.Min.X-u.dst.Min.X)*4 |
| 507 | - | start := (u.src.Min.Y+y-u.dst.Min.Y)*u.stride + (u.src.Min.X+u.rect.Min.X-u.dst.Min.X)*4 | 524 | + | copy(line, u.pixels[start:start+len(line)]) |
| 508 | - | copy(line, u.pixels[start:start+len(line)]) | |||
| 509 | - | } else { | |||
| 510 | - | // Nearest-neighbour resample, sampling pixel centres. | |||
| 511 | - | sy := u.src.Min.Y + ((y-u.dst.Min.Y)*2+1)*u.src.Dy()/(u.dst.Dy()*2) | |||
| 512 | - | src := u.pixels[sy*u.stride:] | |||
| 513 | - | for i := 0; i < u.rect.Dx(); i++ { | |||
| 514 | - | sx := u.src.Min.X + ((u.rect.Min.X+i-u.dst.Min.X)*2+1)*u.src.Dx()/(u.dst.Dx()*2) | |||
| 515 | - | copy(line[i*4:i*4+4], src[sx*4:sx*4+4]) | |||
| 516 | - | } | |||
| 517 | - | } | |||
| 518 | - | if u.opaque { | |||
| 519 | - | for i := 3; i < len(line); i += 4 { | |||
| 520 | - | line[i] = 255 | |||
| 521 | - | } | |||
| 522 | - | } | |||
| 523 | } else { | 525 | } else { | ||
| 524 | - | for i := 0; i < len(line); i += 4 { | 526 | + | // Nearest-neighbour resample, sampling pixel centres. |
| 525 | - | line[i], line[i+1], line[i+2], line[i+3] = u.color[2], u.color[1], u.color[0], 255 | 527 | + | sy := u.src.Min.Y + ((y-u.dst.Min.Y)*2+1)*u.src.Dy()/(u.dst.Dy()*2) |
| 528 | + | src := u.pixels[sy*u.stride:] | |||
| 529 | + | for i := 0; i < u.rect.Dx(); i++ { | |||
| 530 | + | sx := u.src.Min.X + ((u.rect.Min.X+i-u.dst.Min.X)*2+1)*u.src.Dx()/(u.dst.Dx()*2) | |||
| 531 | + | copy(line[i*4:i*4+4], src[sx*4:sx*4+4]) | |||
| 532 | + | } | |||
| 533 | + | } | |||
| 534 | + | if u.opaque { | |||
| 535 | + | for i := 3; i < len(line); i += 4 { | |||
| 536 | + | line[i] = 255 | |||
| 526 | } | 537 | } | ||
| 527 | } | 538 | } | ||
| 528 | } | 539 | } | ||
| @@ -599,7 +610,18 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||||
| 599 | d.CmdBlitImage(r.command, f.dma.image, vk.ImageLayoutTransferSrcOptimal, r.image, vk.ImageLayoutTransferDstOptimal, 1, &blit, vk.FilterNearest) | 610 | d.CmdBlitImage(r.command, f.dma.image, vk.ImageLayoutTransferSrcOptimal, r.image, vk.ImageLayoutTransferDstOptimal, 1, &blit, vk.FilterNearest) | ||
| 600 | continue | 611 | continue | ||
| 601 | } | 612 | } | ||
| 602 | - | region := vk.BufferImageCopy{BufferOffset: vk.DeviceSize(f.offset), ImageSubresource: vk.ImageSubresourceLayers{AspectMask: vk.ImageAspectColorBit, LayerCount: 1}, ImageOffset: vk.Offset3D{X: int32(rect.Min.X), Y: int32(rect.Min.Y)}, ImageExtent: vk.Extent3D{Width: uint32(rect.Dx()), Height: uint32(rect.Dy()), Depth: 1}} | 613 | + | layers := vk.ImageSubresourceLayers{AspectMask: vk.ImageAspectColorBit, LayerCount: 1} |
| 614 | + | if f.fill { | |||
| 615 | + | // Every band reads the same staged strip. | |||
| 616 | + | r.fillRegions = r.fillRegions[:0] | |||
| 617 | + | for y := rect.Min.Y; y < rect.Max.Y; y += fillRows { | |||
| 618 | + | h := min(fillRows, rect.Max.Y-y) | |||
| 619 | + | r.fillRegions = append(r.fillRegions, vk.BufferImageCopy{BufferOffset: vk.DeviceSize(f.offset), ImageSubresource: layers, ImageOffset: vk.Offset3D{X: int32(rect.Min.X), Y: int32(y)}, ImageExtent: vk.Extent3D{Width: uint32(rect.Dx()), Height: uint32(h), Depth: 1}}) | |||
| 620 | + | } | |||
| 621 | + | d.CmdCopyBufferToImage(r.command, r.staging, r.image, vk.ImageLayoutTransferDstOptimal, uint32(len(r.fillRegions)), &r.fillRegions[0]) | |||
| 622 | + | continue | |||
| 623 | + | } | |||
| 624 | + | region := vk.BufferImageCopy{BufferOffset: vk.DeviceSize(f.offset), ImageSubresource: layers, ImageOffset: vk.Offset3D{X: int32(rect.Min.X), Y: int32(rect.Min.Y)}, ImageExtent: vk.Extent3D{Width: uint32(rect.Dx()), Height: uint32(rect.Dy()), Depth: 1}} | |||
| 603 | d.CmdCopyBufferToImage(r.command, r.staging, r.image, vk.ImageLayoutTransferDstOptimal, 1, ®ion) | 625 | d.CmdCopyBufferToImage(r.command, r.staging, r.image, vk.ImageLayoutTransferDstOptimal, 1, ®ion) | ||
| 604 | } | 626 | } | ||
| 605 | ownership(false) | 627 | ownership(false) | ||
M internal/adapters/vulkan/renderer_test.go +31 -1
| @@ -58,7 +58,7 @@ func TestRendererRender(t *testing.T) { | |||
| 58 | 58 | c := windowColor(1) | |
| 59 | 59 | wc := color.RGBA{c[0], c[1], c[2], 255} | |
| 60 | 60 | s := ports.Scene{Background: "#102030", Border: ports.Border{Width: 2, Active: "#b4befe", Inactive: "#313244"}, Windows: []ports.SceneWindow{win}} | |
| 61 | - | check(s, map[image.Point]color.RGBA{{0, 0}: bg, {9, 9}: {0x31, 0x32, 0x44, 255}, {10, 10}: wc, {16, 16}: wc}) | |
| 61 | + | check(s, map[image.Point]color.RGBA{{0, 0}: bg, {9, 9}: {0x31, 0x32, 0x44, 255}, {10, 10}: wc, {16, 16}: wc, {21, 21}: wc, {22, 21}: {0x31, 0x32, 0x44, 255}, {21, 22}: {0x31, 0x32, 0x44, 255}, {23, 23}: {0x31, 0x32, 0x44, 255}, {24, 24}: bg}) | |
| 62 | 62 | s.Windows[0].Focused = true | |
| 63 | 63 | check(s, map[image.Point]color.RGBA{{9, 9}: {0xb4, 0xbe, 0xfe, 255}, {10, 10}: wc, {16, 16}: wc}) | |
| 64 | 64 | s.Windows[0].Fullscreen = true | |
| @@ -303,3 +303,33 @@ func TestRendererGeometryAndSubsurfaces(t *testing.T) { | |||
| 303 | 303 | t.Errorf("child only: %v", got) | |
| 304 | 304 | } | |
| 305 | 305 | } | |
| 306 | + | ||
| 307 | + | // A fill taller than one staged strip is copied down in bands: every row | |
| 308 | + | // through the last one gets the color. | |
| 309 | + | func TestRendererTallFill(t *testing.T) { | |
| 310 | + | h := fillRows*2 + 7 | |
| 311 | + | r, err := New(8, h) | |
| 312 | + | if err != nil { | |
| 313 | + | t.Skipf("Vulkan unavailable: %v", err) | |
| 314 | + | } | |
| 315 | + | defer r.Close() | |
| 316 | + | scene := ports.Scene{Background: "#102030", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{X: 1, Y: 0, W: 6, H: h}}}} | |
| 317 | + | if err := r.Render(scene, nil); err != nil { | |
| 318 | + | t.Fatal(err) | |
| 319 | + | } | |
| 320 | + | c := windowColor(1) | |
| 321 | + | wc := color.RGBA{c[0], c[1], c[2], 255} | |
| 322 | + | bg := color.RGBA{16, 32, 48, 255} | |
| 323 | + | px := r.Pixels() | |
| 324 | + | for y := 0; y < h; y++ { | |
| 325 | + | if got := px.At(1, y); got != wc { | |
| 326 | + | t.Fatalf("At(1,%d)=%v want %v", y, got, wc) | |
| 327 | + | } | |
| 328 | + | if got := px.At(6, y); got != wc { | |
| 329 | + | t.Fatalf("At(6,%d)=%v want %v", y, got, wc) | |
| 330 | + | } | |
| 331 | + | if got := px.At(7, y); got != bg { | |
| 332 | + | t.Fatalf("At(7,%d)=%v want background", y, got) | |
| 333 | + | } | |
| 334 | + | } | |
| 335 | + | } | |
| @@ -58,7 +58,7 @@ func TestRendererRender(t *testing.T) { | |||||
| 58 | c := windowColor(1) | 58 | c := windowColor(1) | ||
| 59 | wc := color.RGBA{c[0], c[1], c[2], 255} | 59 | wc := color.RGBA{c[0], c[1], c[2], 255} | ||
| 60 | s := ports.Scene{Background: "#102030", Border: ports.Border{Width: 2, Active: "#b4befe", Inactive: "#313244"}, Windows: []ports.SceneWindow{win}} | 60 | s := ports.Scene{Background: "#102030", Border: ports.Border{Width: 2, Active: "#b4befe", Inactive: "#313244"}, Windows: []ports.SceneWindow{win}} | ||
| 61 | - | check(s, map[image.Point]color.RGBA{{0, 0}: bg, {9, 9}: {0x31, 0x32, 0x44, 255}, {10, 10}: wc, {16, 16}: wc}) | 61 | + | check(s, map[image.Point]color.RGBA{{0, 0}: bg, {9, 9}: {0x31, 0x32, 0x44, 255}, {10, 10}: wc, {16, 16}: wc, {21, 21}: wc, {22, 21}: {0x31, 0x32, 0x44, 255}, {21, 22}: {0x31, 0x32, 0x44, 255}, {23, 23}: {0x31, 0x32, 0x44, 255}, {24, 24}: bg}) |
| 62 | s.Windows[0].Focused = true | 62 | s.Windows[0].Focused = true | ||
| 63 | check(s, map[image.Point]color.RGBA{{9, 9}: {0xb4, 0xbe, 0xfe, 255}, {10, 10}: wc, {16, 16}: wc}) | 63 | check(s, map[image.Point]color.RGBA{{9, 9}: {0xb4, 0xbe, 0xfe, 255}, {10, 10}: wc, {16, 16}: wc}) | ||
| 64 | s.Windows[0].Fullscreen = true | 64 | s.Windows[0].Fullscreen = true | ||
| @@ -303,3 +303,33 @@ func TestRendererGeometryAndSubsurfaces(t *testing.T) { | |||||
| 303 | t.Errorf("child only: %v", got) | 303 | t.Errorf("child only: %v", got) | ||
| 304 | } | 304 | } | ||
| 305 | } | 305 | } | ||
| 306 | + | ||||
| 307 | + | // A fill taller than one staged strip is copied down in bands: every row | |||
| 308 | + | // through the last one gets the color. | |||
| 309 | + | func TestRendererTallFill(t *testing.T) { | |||
| 310 | + | h := fillRows*2 + 7 | |||
| 311 | + | r, err := New(8, h) | |||
| 312 | + | if err != nil { | |||
| 313 | + | t.Skipf("Vulkan unavailable: %v", err) | |||
| 314 | + | } | |||
| 315 | + | defer r.Close() | |||
| 316 | + | scene := ports.Scene{Background: "#102030", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{X: 1, Y: 0, W: 6, H: h}}}} | |||
| 317 | + | if err := r.Render(scene, nil); err != nil { | |||
| 318 | + | t.Fatal(err) | |||
| 319 | + | } | |||
| 320 | + | c := windowColor(1) | |||
| 321 | + | wc := color.RGBA{c[0], c[1], c[2], 255} | |||
| 322 | + | bg := color.RGBA{16, 32, 48, 255} | |||
| 323 | + | px := r.Pixels() | |||
| 324 | + | for y := 0; y < h; y++ { | |||
| 325 | + | if got := px.At(1, y); got != wc { | |||
| 326 | + | t.Fatalf("At(1,%d)=%v want %v", y, got, wc) | |||
| 327 | + | } | |||
| 328 | + | if got := px.At(6, y); got != wc { | |||
| 329 | + | t.Fatalf("At(6,%d)=%v want %v", y, got, wc) | |||
| 330 | + | } | |||
| 331 | + | if got := px.At(7, y); got != bg { | |||
| 332 | + | t.Fatalf("At(7,%d)=%v want background", y, got) | |||
| 333 | + | } | |||
| 334 | + | } | |||
| 335 | + | } | |||