Text + MXL logo pattern

This commit is contained in:
Dmitry Sergeev
2026-09-13 16:33:50 +03:00
parent 94ff41a124
commit 97dec92b8b
15 changed files with 999 additions and 23 deletions
+169
View File
@@ -0,0 +1,169 @@
package generator
import (
"encoding/binary"
"image"
"image/color"
"math"
"path/filepath"
"testing"
)
const logoW, logoH = 8, 4
func TestWGPUDVDLogo(t *testing.T) {
const width, height = 1920, 1080
// Synthetic 8x4 logo: white left half, near-75% red right half, one
// transparent pixel at (2,1) to exercise the alpha fallback.
img := image.NewRGBA(image.Rect(0, 0, logoW, logoH))
for yy := 0; yy < logoH; yy++ {
for xx := 0; xx < logoW; xx++ {
if xx < 4 {
img.Set(xx, yy, color.RGBA{255, 255, 255, 255})
} else {
img.Set(xx, yy, color.RGBA{191, 0, 0, 255})
}
}
}
img.Set(2, 1, color.RGBA{0, 0, 0, 0})
plane, err := BuildLogoPlane(img)
if err != nil {
t.Fatalf("plane: %v", err)
}
g, err := NewWGPUGenerator(width, height,
filepath.Join("..", "..", "kernels", "v210_dvd_logo.wgsl"), WithLogo(plane))
if err != nil {
t.Fatalf("init: %v", err)
}
defer g.Close()
buf := make([]byte, width*height*8/3)
sample := func(x, y int) (yc, cb, cr uint32) {
p := y*width + x
off := (p / 6) * 16
w0 := binary.LittleEndian.Uint32(buf[off:])
w1 := binary.LittleEndian.Uint32(buf[off+4:])
w2 := binary.LittleEndian.Uint32(buf[off+8:])
w3 := binary.LittleEndian.Uint32(buf[off+12:])
switch p % 6 {
case 0:
return (w0 >> 10) & 0x3FF, w0 & 0x3FF, (w0 >> 20) & 0x3FF
case 1:
return w1 & 0x3FF, w0 & 0x3FF, (w0 >> 20) & 0x3FF
case 2:
return (w1 >> 20) & 0x3FF, (w1 >> 10) & 0x3FF, w2 & 0x3FF
case 3:
return (w2 >> 10) & 0x3FF, (w1 >> 10) & 0x3FF, w2 & 0x3FF
case 4:
return w3 & 0x3FF, (w2 >> 20) & 0x3FF, (w3 >> 10) & 0x3FF
default:
return (w3 >> 20) & 0x3FF, (w2 >> 20) & 0x3FF, (w3 >> 10) & 0x3FF
}
}
// Float32 mirrors of the shader position math and bilinear tap blend
// (kernels/v210_dvd_logo.wgsl), so expectations track the shader exactly.
tri01 := func(x float32) float32 {
f := x - float32(math.Floor(float64(x)))
if f > 0.5 {
return 2 - 2*f
}
return 2 * f
}
type logoPos struct {
bx, by int
fx, fy float32
}
logoOrigin := func(frame uint32) logoPos {
tick := float32(frame) / 25.0
lx := tri01(tick*0.061) * (float32(width) - logoW)
ly := tri01(tick*0.088) * (float32(height) - logoH)
return logoPos{int(lx), int(ly), lx - float32(int(lx)), ly - float32(int(ly))}
}
tap := func(dx, dy int) [3]float32 {
if dx < 0 || dy < 0 || dx >= logoW || dy >= logoH {
return [3]float32{}
}
s := plane[1+dy*logoW+dx]
if s>>30 == 0 {
return [3]float32{}
}
return [3]float32{float32(s&0x3FF) - 64, float32((s>>10)&0x3FF) - 512, float32((s>>20)&0x3FF) - 512}
}
mix3 := func(a, b [3]float32, k float32) [3]float32 {
return [3]float32{a[0] + k*(b[0]-a[0]), a[1] + k*(b[1]-a[1]), a[2] + k*(b[2]-a[2])}
}
expect := func(p logoPos, px, py int) (yc, cb, cr uint32) {
m, n := px-p.bx, py-p.by
top := mix3(tap(m, n-1), tap(m-1, n-1), p.fx)
bot := mix3(tap(m, n), tap(m-1, n), p.fx)
d := mix3(bot, top, p.fy)
return uint32(64 + d[0] + 0.5), uint32(512 + d[1] + 0.5), uint32(512 + d[2] + 0.5)
}
// tol allows +-1 code for blended pixels (FMA contraction on GPU may
// differ from Go's separately-rounded float32 ops by 1 ulp).
check := func(p logoPos, px, py int, tol int, desc string) {
t.Helper()
yc, cb, cr := sample(px, py)
wy, wcb, wcr := expect(p, px, py)
dy, dcb, dcr := int(yc)-int(wy), int(cb)-int(wcb), int(cr)-int(wcr)
if abs(dy) > tol || abs(dcb) > tol || abs(dcr) > tol {
t.Fatalf("%s: (%d,%d) got %d/%d/%d, want %d/%d/%d (diff %d/%d/%d, tol %d)",
desc, px, py, yc, cb, cr, wy, wcb, wcr, dy, dcb, dcr, tol)
}
}
// tick 0: fx=fy=0 -> pure nearest lookup, exact values known upfront
if err := g.GenerateFrame(buf, 0); err != nil {
t.Fatalf("tick 0: %v", err)
}
p0 := logoOrigin(0)
if p0.bx != 0 || p0.by != 0 || p0.fx != 0 || p0.fy != 0 {
t.Fatalf("tick 0 origin %+v, want (0,0,0,0)", p0)
}
check(p0, 1, 1, 0, "tick 0 white pixel")
if y, cb, cr := sample(1, 1); y != 940 || cb != 512 || cr != 512 {
t.Fatalf("tick 0 white pixel: got %d/%d/%d, want 940/512/512", y, cb, cr)
}
check(p0, 2, 1, 0, "tick 0 transparent pixel")
if y, _, _ := sample(2, 1); y != 64 {
t.Fatalf("tick 0 transparent pixel: Y=%d, want background 64", y)
}
check(p0, 5, 2, 0, "tick 0 red pixel")
if y, cb, cr := sample(5, 2); y != 203 || cb != 435 || cr != 848 {
t.Fatalf("tick 0 red pixel: got %d/%d/%d, want 203/435/848", y, cb, cr)
}
check(p0, 10, 1, 0, "tick 0 right of logo")
check(p0, 1, 5, 0, "tick 0 below logo")
// tick 100: origin (933,757), fx~0.056 fy~0.504 -> bilinear blending.
// (934,758) lands on solid white (all 4 taps white) and must reproduce
// the texel exactly; (935,759) straddles the transparent pixel (2,1)
// and must match the mirrored blend within +-1.
if err := g.GenerateFrame(buf, 100); err != nil {
t.Fatalf("tick 100: %v", err)
}
p100 := logoOrigin(100)
if p100.bx != 933 || p100.by != 757 {
t.Fatalf("tick 100 origin (%d,%d), want (933,757)", p100.bx, p100.by)
}
check(p100, 934, 758, 0, "tick 100 solid pixel exact")
if y, _, _ := sample(934, 758); y != 940 {
t.Fatalf("tick 100 solid pixel: Y=%d, want exact texel 940", y)
}
check(p100, 935, 759, 1, "tick 100 blended over transparent pixel")
check(p100, 960, 540, 0, "tick 100 frame center")
if y, _, _ := sample(960, 540); y != 64 {
t.Fatalf("tick 100 frame center: Y=%d, want background 64", y)
}
check(p100, 2, 1, 0, "tick 100 old logo position")
}
func abs(v int) int {
if v < 0 {
return -v
}
return v
}