Files
aego/app/export/postprocess_test.go
sanspie 5eb1832907
ci-platforms / darwin amd64 smoke (push) Skipped
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 15m28s
ci / release (push) Skipped
ci-platforms / windows amd64 smoke (push) Skipped
ci-platforms / darwin arm64 smoke (push) Skipped
ci / checks (push) Successful in 7m47s
Complete Stage 12 for v0.12.0
2026-11-05 11:46:53 +03:00

171 lines
5.9 KiB
Go

package export
import (
"fmt"
"reflect"
"strings"
"testing"
"aego/app/assembly"
"aego/app/assembly/builtin"
"aego/asset"
"aego/asset/importers/scenefile"
"aego/asset/pipeline"
"aego/core/ids"
"aego/core/log"
"aego/engine/module"
"aego/engine/problems"
"aego/modules/render2d"
"aego/project"
"aego/render/postfx"
"aego/runtime/bundle"
"aego/scene"
"aego/scene/sceneio"
"aego/script/bind"
)
func TestPostProcessStrictExport(t *testing.T) {
const file = "scenes/main.aescene"
const node = "d777a4e656b70fdff88f889abc615020"
const active = `{typeID="postfx.vignette" instanceID="edges" enabled=true version=1 parameters=[{name="Intensity" kind=float value=[0.33,0,0,0]}]}`
unknown := fmt.Sprintf(`{typeID="future.effect" instanceID="future" enabled=false version=3 parameters=[{name="FutureMap" kind=texture asset=asset(%q)}]}`, asset.BuiltinWhite.String())
fixture := func(fields string) string {
return fmt.Sprintf(`scene version=1 {
node %q name="Player camera" enabled=true {
component "engine.Camera2D" version=1 { active=true }
component "render2d.PostProcess" version=1 { %s }
}
}`, node, fields)
}
p, err := project.Create(t.TempDir(), "Output", false)
if err != nil {
t.Fatal(err)
}
reg := pipeline.NewImporterRegistry()
if err := reg.Register(scenefile.NewScene()); err != nil {
t.Fatal(err)
}
pl, err := pipeline.Open(p, reg, log.Nop())
if err != nil {
t.Fatal(err)
}
defer pl.Close()
for _, tc := range []struct{ name, fields, code, want string }{
{"disabled unknown", "enabled=true effects=[" + active + "," + unknown + "]", "", ""},
{"required unknown", "enabled=true effects=[" + strings.Replace(unknown, "enabled=false", "enabled=true", 1) + "]", "PFX001", "future.effect"},
{"mismatched version", "enabled=true effects=[" + strings.Replace(active, "version=1", "version=3", 1) + "]", "PFX001", "edges"},
{"wrong parameter kind", "enabled=true effects=[" + strings.Replace(active, "kind=float", "kind=vec3", 1) + "]", "PFX001", "Intensity"},
{"duplicate identity", "enabled=true effects=[" + active + "," + active + "]", "PFX001", "edges"},
{"malformed list", `enabled=true effects="not a list"`, CodeScene, "effects"},
{"malformed checkbox", `enabled="typo" effects=[]`, CodeScene, "enabled"},
} {
t.Run(tc.name, func(t *testing.T) {
writeMaterialExportSource(t, p.Root, file, fixture(tc.fields))
if _, _, err := pl.Scan(); err != nil {
t.Fatal(err)
}
if batch, err := pl.ImportAll(); err != nil || len(batch.Failed) != 0 {
t.Fatalf("fixture import: %v %+v", err, batch.Failed)
}
p.MainScene, _ = pl.Lookup(file)
snap := Take(pl)
ds := Check(p, snap, bundle.Release, false)
if tc.code == "" {
if Blocking(ds) != 0 {
t.Fatal(ds)
}
checkUnknownEffectRoundTrip(t, p, snap)
return
}
for _, d := range ds {
if d.Code == tc.code && d.Severity == problems.Error && d.Subject.File == file && strings.Contains(d.Message, tc.want) {
if tc.code == "PFX001" && d.Subject.Node.String() != node {
t.Fatalf("diagnostic lost camera identity: %+v", d)
}
return
}
}
t.Fatalf("missing %s containing %q: %+v", tc.code, tc.want, ds)
})
}
}
func checkUnknownEffectRoundTrip(t *testing.T, p *project.Project, snap *Snapshot) {
t.Helper()
types, err := assembly.NewTypes()
if err != nil {
t.Fatal(err)
}
if _, err := builtin.Runtime().Load(types, bind.New(), assembly.Options{}); err != nil {
t.Fatal(err)
}
res := &resolver{s: snap}
src, ok := res.blob(p.MainScene, asset.ArtScene)
if !ok {
t.Fatal("missing frozen scene")
}
var id ids.NodeID
var want []postfx.Instance
for i := 0; i < 2; i++ {
sc, rep, err := sceneio.Load(src, "scenes/main.aescene", types, res, sceneio.LoadOptions{KeepIDs: true})
if err != nil {
t.Fatal(err)
}
var got []postfx.Instance
scene.Store[render2d.PostProcess](sc).EachAll(func(h scene.NodeHandle, pp *render2d.PostProcess) {
got = pp.Effects
if i == 0 {
id = sc.ID(h)
} else if sc.ID(h) != id {
t.Fatal("save changed camera identity")
}
})
if len(got) != 2 || got[0].InstanceID != "edges" || got[1].TypeID != "future.effect" || got[1].Enabled || got[1].Version != 3 || len(got[1].Parameters) != 1 || got[1].Parameters[0].Asset.ID != asset.BuiltinWhite {
t.Fatalf("unknown typed effect not preserved: %+v; report=%+v fixture=%s", got, rep, src)
}
if i == 0 {
want, src = got, sceneio.Save(sc)
} else if !reflect.DeepEqual(got, want) {
t.Fatalf("save/load changed stack: %+v vs %+v", got, want)
}
}
}
type opaquePostEffect struct{}
func (opaquePostEffect) Info() module.Info { return module.Info{ID: "thirdparty"} }
func (opaquePostEffect) Register(r *module.Registry) error {
r.Provide(postfx.ProvisionKind, postfx.Descriptor{
ID: "thirdparty.opaque", Label: "Opaque effect", Version: 1, Phase: postfx.DisplayLinear, Alpha: postfx.OpaqueOnly,
Fragment: "vec4 aego_effect(vec4 c, vec2 uv, vec2 size, vec4 p[4]) { return vec4(c.rgb, 1.0); }",
})
return nil
}
func TestPostProcessTargetModuleOutputProfile(t *testing.T) {
types, err := assembly.NewTypes()
if err != nil {
t.Fatal(err)
}
if _, err := builtin.Runtime().Load(types, bind.New(), assembly.Options{}, opaquePostEffect{}); err != nil {
t.Fatal(err)
}
s := scene.New(types, nil, scene.Options{})
h := s.Create(s.Root(), "Camera")
cam := scene.Add[render2d.Camera2D](s, h)
*cam = render2d.DefaultCamera2D()
c := scene.Add[render2d.PostProcess](s, h)
*c = render2d.PostProcess{Enabled: true, Effects: []postfx.Instance{{TypeID: "thirdparty.opaque", InstanceID: "glow", Enabled: true, Version: 1}}}
s.Flush()
if ds := module.ValidateScene(s, nil); Blocking(ds) != 0 {
t.Fatalf("registered module's opaque camera: %+v", ds)
}
cam, _ = scene.Get[render2d.Camera2D](s, h)
cam.ClearColor.A = .5
ds := module.ValidateScene(s, nil)
if !hasExportError(ds, "PFX001", "opaque output") {
t.Fatalf("accepted incompatible transparent output: %+v", ds)
}
}