Files
aego/tests/gl/pixelsnap_test.go
sanspie a6822156cd
ci-platforms / windows amd64 smoke (push) Skipped
ci / release (push) Successful in 8m58s
ci-platforms / darwin amd64 smoke (push) Skipped
ci-platforms / darwin arm64 smoke (push) Skipped
ci / checks (push) Successful in 4m46s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 13m28s
Finish Stage 12 editor fixes and refreshed platformer
2026-11-05 17:36:42 +03:00

127 lines
4.1 KiB
Go

//go:build aego_gltest
package gl
import (
"fmt"
"math"
"testing"
"aego/asset"
"aego/core/log"
"aego/core/mathx"
"aego/gpu"
"aego/render"
"aego/render/material"
"aego/render/sprite"
"aego/render/view"
)
// Compare pixels to a CPU rectangle in top-left physical coordinates; an odd viewport origin/height and four-pixel grid expose the window/offscreen Y-orientation bug that grid=1 masks.
func TestPixelSnapPhysicalGridAcrossDPRViewportAndTargets(t *testing.T) {
e := newGLEngine(t)
defer e.Close()
dev := e.GPU()
r, err := render.New(dev, e.Caps(), e.VFS(), log.Nop(), render.Options{MaxSprites: 8})
if err != nil {
t.Fatal(err)
}
defer r.Close()
r.Materials().OnError = func(sh *material.Shader, variant int, err error) {
t.Errorf("shader %s variant %d: %v", sh.Name, variant, err)
}
data, _ := asset.BuiltinMaterial(asset.BuiltinMaterialPixelSnap)
doc, err := asset.DecodeMaterial(data, "PixelSnap")
if err != nil {
t.Fatal(err)
}
data, _ = asset.BuiltinShader(doc.Shader)
artifact, err := asset.DecodeShader(data)
if err != nil {
t.Fatal(err)
}
m, ps := doc.Build("PixelSnap", artifact.Runtime(doc.Shader, "PixelSnap"), nil)
if m == nil {
t.Fatal(ps)
}
if !m.Set("GridPixels", [4]float32{4}) {
t.Fatal("PixelSnap has no GridPixels parameter")
}
const size = 64
target, err := dev.CreateTexture(gpu.TextureDesc{Width: size, Height: size, Format: gpu.TexRGBA8, Label: "pixelsnap-offscreen"})
if err != nil {
t.Fatal(err)
}
defer dev.DestroyTexture(target)
backbuffer, ok := dev.(interface{ ReadBackbuffer(int, int, []byte) error })
if !ok {
t.Fatal("native backend does not expose backbuffer readback")
}
viewport := mathx.Rect{X: 3, Y: 5, W: 45, H: 39}
position, extent := mathx.Vec2{X: 5.2, Y: 6.3}, mathx.Vec2{X: 8.4, Y: 7.1}
pixels := make([]byte, size*size*4)
for _, offscreen := range []bool{false, true} {
for _, dpr := range []float32{1, 2} {
for _, strength := range []float32{0, 1} {
name := fmt.Sprintf("offscreen=%v DPR=%g strength=%g", offscreen, dpr, strength)
if !m.Set("Strength", [4]float32{strength}) {
t.Fatal("PixelSnap has no Strength parameter")
}
frame := dev.BeginFrame(size, size)
if err := r.BeginFrame(frame, size, size, dpr); err != nil {
t.Fatal(err)
}
v := view.ScreenView(size, size, dpr)
v.Viewport = viewport
v.Camera.Position = mathx.Vec2{X: viewport.W / (2 * dpr), Y: viewport.H / (2 * dpr)}
v.DoClear = true
v.ClearWholeTarget = true // The pixel oracle checks the full attachment, not only the camera viewport.
if offscreen {
v.Target = target
}
vf := r.View(v)
vf.Sprite(sprite.Draw{Region: r.White(), Position: position, Size: extent, Tint: mathx.White, Material: m})
vf.End()
r.EndFrame()
dev.EndFrame()
if offscreen {
err = dev.ReadPixels(target, 0, 0, size, size, pixels)
} else {
err = backbuffer.ReadBackbuffer(size, size, pixels)
}
if err != nil {
t.Fatalf("%s readback: %v", name, err)
}
snap := func(p float32) float32 {
if strength == 0 {
return p
}
return float32(math.Floor(float64(p)/4+0.5) * 4)
}
left, top := snap(viewport.X+position.X*dpr), snap(viewport.Y+position.Y*dpr)
right, bottom := snap(viewport.X+(position.X+extent.X)*dpr), snap(viewport.Y+(position.Y+extent.Y)*dpr)
for y := 0; y < size; y++ {
row := y
if !offscreen {
row = size - 1 - y // Native window readback is bottom-up.
}
for x := 0; x < size; x++ {
visible := float32(x)+0.5 >= left && float32(x)+0.5 < right && float32(y)+0.5 >= top && float32(y)+0.5 < bottom
want := byte(0)
if visible {
want = 255
}
at := (row*size + x) * 4
if pixels[at] != want || pixels[at+1] != want || pixels[at+2] != want {
t.Fatalf("%s pixel (%d,%d)=%v, expected RGB %d in rectangle [%g,%g]→[%g,%g]", name, x, y, pixels[at:at+4], want, left, top, right, bottom)
}
if offscreen && pixels[at+3] != want {
t.Fatalf("%s alpha (%d,%d)=%d, expected %d", name, x, y, pixels[at+3], want)
}
}
}
}
}
}
}