Files
aego/asset/loader/normal_reload.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

173 lines
4.9 KiB
Go

package loader
import (
"fmt"
"aego/asset"
"aego/core/ids"
)
type normalShape struct {
w, h int
normal ids.AssetID
emission ids.AssetID
semantic asset.TextureSemantic
}
// Albedo, normal and emission share a generation: incompatible partial batches or failed candidates leave all live channel GPU resources intact,
// even when a same-sized pixel update accompanies a rejected channel.
func (l *Loader) validateNormalSwap(entries []asset.Entry, prepared []staged) error {
if len(l.normals) == 0 && len(l.emissions) == 0 {
return nil
}
next := make(map[ids.AssetID]*asset.Entry, len(entries))
pages := make(map[ids.AssetID]pageEntry)
for i := range entries {
next[entries[i].ID] = &entries[i]
}
for _, s := range prepared {
if s.isTex {
pages[s.id] = s.page
}
}
entry := func(id ids.AssetID) (*asset.Entry, bool) {
if e := next[id]; e != nil {
return e, true
}
return l.entry(id)
}
pageInfo := func(id ids.AssetID) (pageEntry, bool, error) {
if p, ok := pages[id]; ok {
return p, true, nil
}
if p, ok := l.pages[id]; ok {
return p, true, nil
}
e, ok := entry(id)
if !ok {
return pageEntry{}, false, nil
}
a, ok := e.Artifact(e.Kind)
if !ok || (e.Kind != asset.ArtTexture && e.Kind != asset.ArtAtlasPage) {
return pageEntry{}, false, nil
}
data, err := l.read(a)
if err != nil {
return pageEntry{}, false, err
}
t, err := asset.DecodeTexture(data)
if err != nil {
return pageEntry{}, false, err
}
return pageEntry{width: t.Width, height: t.Height, semantic: t.Semantic}, true, nil
}
shape := func(id ids.AssetID) (normalShape, bool, error) {
e, exists := entry(id)
if !exists {
return normalShape{}, false, nil
}
if next[id] == nil {
if cached, ok := l.region[id]; ok {
p, ok, err := pageInfo(cached.page)
if err != nil || !ok {
return normalShape{}, false, err
}
if old, had := l.pages[cached.page]; had && (old.width != p.width || old.height != p.height) {
return normalShape{}, false, fmt.Errorf("normal-related atlas page %s resized without a new region mapping for %s", cached.page, id)
}
size := cached.region.Size()
return normalShape{w: int(size.X), h: int(size.Y), normal: l.normals[id], emission: l.emissions[id], semantic: p.semantic}, true, nil
}
}
if a, ok := e.Artifact(asset.ArtSpriteRegion); ok {
data, err := l.read(a)
if err != nil {
return normalShape{}, false, err
}
r, err := asset.DecodeRegion(data)
if err != nil {
return normalShape{}, false, err
}
p, ok, err := pageInfo(r.Page)
if err != nil || !ok {
return normalShape{}, false, err
}
if p.width != r.PageW || p.height != r.PageH {
return normalShape{}, false, fmt.Errorf("normal-related region %s and page %s have different dimensions", id, r.Page)
}
w, h := r.W, r.H
if r.SourceW != 0 {
w, h = r.SourceW, r.SourceH
}
return normalShape{w: w, h: h, normal: r.Normal, emission: r.Emission, semantic: p.semantic}, true, nil
}
p, ok, err := pageInfo(id)
if err != nil || !ok {
return normalShape{}, false, err
}
return normalShape{w: p.width, h: p.height, semantic: p.semantic}, true, nil
}
active := make(map[ids.AssetID]bool, len(l.normals)+len(l.emissions))
for id := range l.normals {
active[id] = true
}
for id := range l.emissions {
active[id] = true
}
rejected := func(id ids.AssetID) bool {
if l.rejected[id] && next[id] == nil {
return true
}
if e, ok := entry(id); ok {
for _, dep := range e.Deps {
if l.rejected[dep] && next[dep] == nil {
return true
}
}
}
return false
}
for albedo := range active {
aRegion := l.region[albedo]
nID, eID := l.normals[albedo], l.emissions[albedo]
nRegion, eRegion := l.region[nID], l.region[eID]
if next[albedo] == nil && next[aRegion.page] == nil && next[nID] == nil && next[nRegion.page] == nil && next[eID] == nil && next[eRegion.page] == nil {
continue
}
a, ok, err := shape(albedo)
if err != nil {
return err
}
if !ok {
continue
}
if rejected(albedo) || rejected(a.normal) || rejected(a.emission) {
return fmt.Errorf("surface %s has a failed current channel generation; retain the entire last-good surface until its sources are repaired", albedo)
}
for _, channel := range []struct {
name string
id ids.AssetID
}{{"normal", a.normal}, {"emission", a.emission}} {
if channel.id.IsZero() {
continue
}
target, ok, err := shape(channel.id)
if err != nil {
return err
}
if !ok {
continue
}
compatible := target.semantic == asset.TextureNormal
if channel.name == "emission" {
compatible = target.semantic == asset.TextureEmissionRGB || target.semantic == asset.TextureEmissionMask
}
if !compatible || a.w != target.w || a.h != target.h {
return fmt.Errorf("%s %s is incompatible with albedo %s; publish matching role and logical canvases together", channel.name, channel.id, albedo)
}
}
}
return nil
}