Files
aego/render/render3d_test.go
T
sanspie 17cb5e4291 Stage 2-5: assets, scene/ECS, editor, 3D, and Aego Script
Stage 2: render pipeline and UI toolkit over gpu/platform.
Stage 3: schema registry, asset pipeline + runtime loader, scene graph and ECS.
Stage 4: editor — docking, inspector, scene/game views, play mode, asset browser, console, profiler; 4.5 adds the 3D foundation (mixed 2D/3D spaces, meshes, depth).
Stage 5: Aego Script — compiler (lexer/parser/types/resolver/checker/asm/compiler), bytecode with verifier and register VM, instance runtime, stdlib, native bindings, and editor/CLI integration (aego run/edit/check/test/dump).
2026-10-08 13:17:28 +03:00

111 lines
3.1 KiB
Go

package render
import (
"testing"
"aego/core/mathx"
"aego/gpu"
"aego/gpu/null"
"aego/render/mesh"
"aego/render/sprite"
"aego/render/texture"
"aego/render/view"
)
func perspective(w, h int) view.View {
eye := mathx.Mat4TRS(mathx.Vec3{Z: 5}, mathx.QuatIdentity(), mathx.Vec3{X: 1, Y: 1, Z: 1})
v := view.PerspectiveView(eye, mathx.Deg2Rad(60), 0.1, 100, mathx.Rect{W: float32(w), H: float32(h)})
v.TargetW, v.TargetH = w, h
v.DoClear = true
return v
}
func cube(t *testing.T, dev gpu.Device) *mesh.Mesh {
t.Helper()
m, err := mesh.Upload(dev, mesh.Cube(1), "cube")
if err != nil {
t.Fatal(err)
}
return m
}
func TestPassOrderIncludes3D(t *testing.T) {
r, _ := newTestRenderer(t, 64)
if err := r.BeginFrame(nil, 64, 64, 1); err != nil {
t.Fatal(err)
}
r.EndFrame()
want := []string{"mesh_opaque", "mesh_blend", "sprites3d", "sprites", "debug"}
if len(r.order) != len(want) {
t.Fatalf("order = %v", r.order)
}
for i, id := range want {
if r.order[i].ID != id {
t.Fatalf("pass %d = %s, want %s", i, r.order[i].ID, id)
}
}
}
func TestMeshCullingAndDraws(t *testing.T) {
r, dev := newTestRenderer(t, 64)
c := cube(t, dev)
fr := dev.BeginFrame(1280, 720)
r.BeginFrame(fr, 1280, 720, 1)
f := r.View(perspective(1280, 720))
f.Mesh(mesh.Draw{Mesh: c, World: mathx.Mat4Identity(), Blend: sprite.BlendOpaque})
f.Mesh(mesh.Draw{Mesh: c, World: mathx.Mat4Translate(mathx.Vec3{Z: 10}), Blend: sprite.BlendOpaque})
f.Mesh(mesh.Draw{Mesh: c, World: mathx.Mat4Translate(mathx.Vec3{X: 1}), Color: mathx.Color{R: 1, G: 1, B: 1, A: 0.5}})
f.End()
s := r.EndFrame()
dev.EndFrame()
if s.MeshCulled != 1 || s.MeshDraws != 2 {
t.Fatalf("culled=%d draws=%d", s.MeshCulled, s.MeshDraws)
}
}
func TestSprite3DCulling(t *testing.T) {
r, dev := newTestRenderer(t, 64)
tex, _ := r.Textures().Upload(texture.Solid(4, 4, mathx.Color{R: 1, A: 1}), texture.UploadOptions{})
reg := texture.Whole(tex, 4, 4)
fr := dev.BeginFrame(1280, 720)
r.BeginFrame(fr, 1280, 720, 1)
f := r.View(perspective(1280, 720))
f.Sprite3D(sprite.Draw3D{Region: reg, World: mathx.Mat4Identity(), Size: mathx.Vec2{X: 1, Y: 1}})
f.Sprite3D(sprite.Draw3D{Region: reg, World: mathx.Mat4Translate(mathx.Vec3{Z: 20}), Size: mathx.Vec2{X: 1, Y: 1}})
f.End()
s := r.EndFrame()
dev.EndFrame()
if s.Sprites3D != 1 || s.Sprite3DCulled != 1 || s.Instances != 1 {
t.Fatalf("stats = %+v", s)
}
}
func TestMeshFrameDoesNotAllocate(t *testing.T) {
r, dev := newTestRenderer(t, 64)
c := cube(t, dev)
draws := make([]mesh.Draw, 0, 2000)
for i := 0; i < 2000; i++ {
x, z := float32(i%50)-25, -float32(i/50)
draws = append(draws, mesh.Draw{Mesh: c, World: mathx.Mat4Translate(mathx.Vec3{X: x, Z: z}), Blend: sprite.BlendOpaque})
}
v := perspective(1280, 720)
frame := func() {
fr := dev.BeginFrame(1280, 720)
r.BeginFrame(fr, 1280, 720, 1)
f := r.View(v)
f.Meshes(draws)
f.Debug().Line3(mathx.Vec3{}, mathx.Vec3{Y: 1}, mathx.Color{G: 1, A: 1})
f.End()
r.EndFrame()
dev.EndFrame()
}
for i := 0; i < 5; i++ {
frame()
}
if n := testing.AllocsPerRun(10, frame); n != 0 {
t.Fatalf("mesh frame allocates %.1f objects", n)
}
}
var _ = null.New