test(vkdevice): add the image after cached glyphs in the upload regression test
The first frame already initializes the atlas, so a one-frame test passed even without uploading images in glyph-free frames. Render text first, then add the image when no glyph is new.
- commit
- 71ebe61bffa4175a621e1353d1500d7b7470c1b2
- author
- brice · · 2026-10-09 10:51 UTC
- parent
- 05a3458
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email - refs
- tag: v0.7.2
1 file · +26 -6
M internal/ui/renderer_image_gpu_test.go +26 -6
| @@ -6,11 +6,14 @@ import ( | |||
| 6 | 6 | "image" | |
| 7 | 7 | "image/color" | |
| 8 | 8 | "testing" | |
| 9 | + | "time" | |
| 9 | 10 | ) | |
| 10 | 11 | ||
| 11 | - | // An image in a frame that uploads no glyph is still copied to the GPU: | |
| 12 | - | // before the fix it sampled an uninitialized texture and drew nothing. | |
| 13 | - | func TestRendererGPUImageWithoutText(t *testing.T) { | |
| 12 | + | // An image added in a frame that uploads no glyph is still copied to the GPU: | |
| 13 | + | // before the fix it sampled an uninitialized texture and drew nothing. A first | |
| 14 | + | // text-only frame caches the glyphs and initializes the atlas, so the second | |
| 15 | + | // frame has nothing to upload but the image. | |
| 16 | + | func TestRendererGPUImageWithoutNewGlyphs(t *testing.T) { | |
| 14 | 17 | r, tg := gpuRenderer(t) | |
| 15 | 18 | src := image.NewNRGBA(image.Rect(0, 0, 40, 30)) | |
| 16 | 19 | for y := range 30 { | |
| @@ -18,21 +21,38 @@ func TestRendererGPUImageWithoutText(t *testing.T) { | |||
| 18 | 21 | src.SetNRGBA(x, y, color.NRGBA{R: uint8(x * 6), G: uint8(y * 8), B: 0x80, A: 0xff}) | |
| 19 | 22 | } | |
| 20 | 23 | } | |
| 24 | + | show := false | |
| 21 | 25 | view := func(f *Frame, _ *struct{}) { | |
| 22 | - | f.Root().Stack().Box(Inline("overflow:hidden")).Rect(0, 0, 40, 30).Image(src, Inline("width:100%;height:100%")) | |
| 26 | + | st := f.Root().Stack() | |
| 27 | + | if show { | |
| 28 | + | st.Box(Inline("overflow:hidden")).Rect(0, 0, 40, 30).Image(src, Inline("width:100%;height:100%")) | |
| 29 | + | } | |
| 30 | + | st.Box().Rect(0, 0, 1, 1).Text("x") | |
| 23 | 31 | } | |
| 24 | 32 | r.Resize(40, 30, 1) | |
| 25 | 33 | var out Output | |
| 26 | 34 | var m struct{} | |
| 27 | 35 | ok, err := r.Render(&out, &m, view) | |
| 28 | 36 | if err != nil || !ok { | |
| 29 | - | t.Fatalf("render: ok=%v err=%v", ok, err) | |
| 37 | + | t.Fatalf("text frame: ok=%v err=%v", ok, err) | |
| 38 | + | } | |
| 39 | + | signalRelease(t, tg, &out) | |
| 40 | + | if !readable(t, out.ReleaseFD, time.Second) { | |
| 41 | + | t.Fatal("text frame not released") | |
| 42 | + | } | |
| 43 | + | if err = r.Released(out.Buffer); err != nil { | |
| 44 | + | t.Fatal(err) | |
| 45 | + | } | |
| 46 | + | show = true | |
| 47 | + | r.Invalidate() | |
| 48 | + | if ok, err = r.Render(&out, &m, view); err != nil || !ok { | |
| 49 | + | t.Fatalf("image frame: ok=%v err=%v", ok, err) | |
| 30 | 50 | } | |
| 31 | 51 | img, err := tg.Readback(out.Buffer) | |
| 32 | 52 | if err != nil { | |
| 33 | 53 | t.Fatal(err) | |
| 34 | 54 | } | |
| 35 | - | for _, p := range []image.Point{{0, 0}, {20, 15}, {39, 29}} { | |
| 55 | + | for _, p := range []image.Point{{5, 5}, {20, 15}, {39, 29}} { | |
| 36 | 56 | want, got := src.NRGBAAt(p.X, p.Y), img.NRGBAAt(p.X, p.Y) | |
| 37 | 57 | if got.R != want.R || got.G != want.G || got.B != want.B { | |
| 38 | 58 | t.Fatalf("pixel %v = %+v, want %+v", p, got, want) | |
| @@ -6,11 +6,14 @@ import ( | |||||
| 6 | "image" | 6 | "image" | ||
| 7 | "image/color" | 7 | "image/color" | ||
| 8 | "testing" | 8 | "testing" | ||
| 9 | + | "time" | |||
| 9 | ) | 10 | ) | ||
| 10 | 11 | ||||
| 11 | - | // An image in a frame that uploads no glyph is still copied to the GPU: | 12 | + | // An image added in a frame that uploads no glyph is still copied to the GPU: |
| 12 | - | // before the fix it sampled an uninitialized texture and drew nothing. | 13 | + | // before the fix it sampled an uninitialized texture and drew nothing. A first |
| 13 | - | func TestRendererGPUImageWithoutText(t *testing.T) { | 14 | + | // text-only frame caches the glyphs and initializes the atlas, so the second |
| 15 | + | // frame has nothing to upload but the image. | |||
| 16 | + | func TestRendererGPUImageWithoutNewGlyphs(t *testing.T) { | |||
| 14 | r, tg := gpuRenderer(t) | 17 | r, tg := gpuRenderer(t) | ||
| 15 | src := image.NewNRGBA(image.Rect(0, 0, 40, 30)) | 18 | src := image.NewNRGBA(image.Rect(0, 0, 40, 30)) | ||
| 16 | for y := range 30 { | 19 | for y := range 30 { | ||
| @@ -18,21 +21,38 @@ func TestRendererGPUImageWithoutText(t *testing.T) { | |||||
| 18 | src.SetNRGBA(x, y, color.NRGBA{R: uint8(x * 6), G: uint8(y * 8), B: 0x80, A: 0xff}) | 21 | src.SetNRGBA(x, y, color.NRGBA{R: uint8(x * 6), G: uint8(y * 8), B: 0x80, A: 0xff}) | ||
| 19 | } | 22 | } | ||
| 20 | } | 23 | } | ||
| 24 | + | show := false | |||
| 21 | view := func(f *Frame, _ *struct{}) { | 25 | view := func(f *Frame, _ *struct{}) { | ||
| 22 | - | f.Root().Stack().Box(Inline("overflow:hidden")).Rect(0, 0, 40, 30).Image(src, Inline("width:100%;height:100%")) | 26 | + | st := f.Root().Stack() |
| 27 | + | if show { | |||
| 28 | + | st.Box(Inline("overflow:hidden")).Rect(0, 0, 40, 30).Image(src, Inline("width:100%;height:100%")) | |||
| 29 | + | } | |||
| 30 | + | st.Box().Rect(0, 0, 1, 1).Text("x") | |||
| 23 | } | 31 | } | ||
| 24 | r.Resize(40, 30, 1) | 32 | r.Resize(40, 30, 1) | ||
| 25 | var out Output | 33 | var out Output | ||
| 26 | var m struct{} | 34 | var m struct{} | ||
| 27 | ok, err := r.Render(&out, &m, view) | 35 | ok, err := r.Render(&out, &m, view) | ||
| 28 | if err != nil || !ok { | 36 | if err != nil || !ok { | ||
| 29 | - | t.Fatalf("render: ok=%v err=%v", ok, err) | 37 | + | t.Fatalf("text frame: ok=%v err=%v", ok, err) |
| 38 | + | } | |||
| 39 | + | signalRelease(t, tg, &out) | |||
| 40 | + | if !readable(t, out.ReleaseFD, time.Second) { | |||
| 41 | + | t.Fatal("text frame not released") | |||
| 42 | + | } | |||
| 43 | + | if err = r.Released(out.Buffer); err != nil { | |||
| 44 | + | t.Fatal(err) | |||
| 45 | + | } | |||
| 46 | + | show = true | |||
| 47 | + | r.Invalidate() | |||
| 48 | + | if ok, err = r.Render(&out, &m, view); err != nil || !ok { | |||
| 49 | + | t.Fatalf("image frame: ok=%v err=%v", ok, err) | |||
| 30 | } | 50 | } | ||
| 31 | img, err := tg.Readback(out.Buffer) | 51 | img, err := tg.Readback(out.Buffer) | ||
| 32 | if err != nil { | 52 | if err != nil { | ||
| 33 | t.Fatal(err) | 53 | t.Fatal(err) | ||
| 34 | } | 54 | } | ||
| 35 | - | for _, p := range []image.Point{{0, 0}, {20, 15}, {39, 29}} { | 55 | + | for _, p := range []image.Point{{5, 5}, {20, 15}, {39, 29}} { |
| 36 | want, got := src.NRGBAAt(p.X, p.Y), img.NRGBAAt(p.X, p.Y) | 56 | want, got := src.NRGBAAt(p.X, p.Y), img.NRGBAAt(p.X, p.Y) | ||
| 37 | if got.R != want.R || got.G != want.G || got.B != want.B { | 57 | if got.R != want.R || got.G != want.G || got.B != want.B { | ||
| 38 | t.Fatalf("pixel %v = %+v, want %+v", p, got, want) | 58 | t.Fatalf("pixel %v = %+v, want %+v", p, got, want) | ||