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
112 lines
3.1 KiB
Go
112 lines
3.1 KiB
Go
package sprite
|
|
|
|
import (
|
|
"unsafe"
|
|
|
|
"aego/gpu"
|
|
"aego/render/shader"
|
|
)
|
|
|
|
const (
|
|
AuxStride = shader.AuxStride
|
|
AuxSlot = shader.AuxSlot
|
|
AffineLocation = shader.AffineLocation
|
|
NormalUVLocation = shader.NormalUVLocation
|
|
NormalLocalLocation = shader.NormalLocalLocation
|
|
EmissionUVLocation = shader.EmissionUVLocation
|
|
EmissionLocalLocation = shader.EmissionLocalLocation
|
|
)
|
|
|
|
// Aux is an optional per-instance stream independent of material overrides; Affine stores (m00-1,m10,m01,m11) so disabled (0,0,0,1) decodes to identity.
|
|
// NormalUV/NormalLocal map the independently packed normal rectangle to its source canvas.
|
|
type Aux struct {
|
|
Affine [4]float32
|
|
NormalUV [4]float32
|
|
NormalLocal [4]float32
|
|
EmissionUV [4]float32
|
|
EmissionLocal [4]float32
|
|
}
|
|
|
|
const _ = uint(AuxStride - unsafe.Sizeof(Aux{}))
|
|
const _ = uint(unsafe.Sizeof(Aux{}) - AuxStride)
|
|
|
|
func identityAux() Aux {
|
|
return Aux{Affine: [4]float32{0, 0, 0, 1}, NormalLocal: [4]float32{0, 0, 1, 1}, EmissionLocal: [4]float32{0, 0, 1, 1}}
|
|
}
|
|
|
|
func makeAux(d *Draw) Aux {
|
|
a := identityAux()
|
|
a.Affine = d.Basis()
|
|
a.Affine[0]--
|
|
if d.Lighting && !d.Normal.IsZero() {
|
|
r := d.Normal
|
|
a.NormalUV = [4]float32{r.UVMin.X, r.UVMin.Y, r.UVMax.X, r.UVMax.Y}
|
|
a.NormalLocal = r.LocalRect()
|
|
}
|
|
if d.Lighting && d.Emission.Valid() {
|
|
r := d.Emission.Region
|
|
a.EmissionUV = [4]float32{r.UVMin.X, r.UVMin.Y, r.UVMax.X, r.UVMax.Y}
|
|
a.EmissionLocal = r.LocalRect()
|
|
}
|
|
return a
|
|
}
|
|
|
|
func (b *Batcher) collectAux(from, to int) int {
|
|
if len(b.aux) == 0 {
|
|
return -1
|
|
}
|
|
found := false
|
|
for i := from; i < to; i++ {
|
|
if b.sortedAux[i] >= 0 {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return -1
|
|
}
|
|
first := len(b.auxOut)
|
|
for i := from; i < to; i++ {
|
|
a := identityAux()
|
|
if j := b.sortedAux[i]; j >= 0 {
|
|
a = b.aux[j]
|
|
}
|
|
b.auxOut = append(b.auxOut, a)
|
|
}
|
|
return first
|
|
}
|
|
|
|
// UploadAux leaves the ordinary sprite path untouched until a draw needs an
|
|
// affine basis or lighting. Capacity growth is reused by subsequent frames.
|
|
func (b *Batcher) UploadAux() bool {
|
|
n := len(b.auxOut)
|
|
if n == 0 {
|
|
return true
|
|
}
|
|
if n > b.auxCap {
|
|
c := max(n, 256, b.auxCap*2)
|
|
h, err := b.dev.CreateBuffer(gpu.BufferDesc{Usage: gpu.BufferVertex, Size: c * AuxStride, Dynamic: true, Label: "sprite.aux"})
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if !b.auxVB.IsZero() {
|
|
b.dev.DestroyBuffer(b.auxVB)
|
|
}
|
|
b.auxVB, b.auxCap = h, c
|
|
}
|
|
raw := unsafe.Slice((*byte)(unsafe.Pointer(&b.auxOut[0])), n*AuxStride)
|
|
b.dev.UpdateBuffer(b.auxVB, 0, raw)
|
|
return true
|
|
}
|
|
|
|
func (b *Batcher) AuxBuffer() gpu.BufferHandle { return b.auxVB }
|
|
func (b *Batcher) AuxBytes() int { return len(b.auxOut) * AuxStride }
|
|
|
|
// AuxLayout adds the plain sprite shader's affine input; material.Cache adds feature-specific channel inputs, and a disabled stream uses the backend's generic float defaults.
|
|
func AuxLayout(l gpu.VertexLayout) gpu.VertexLayout {
|
|
l.Attrs = append(l.Attrs, gpu.VertexAttr{Location: AffineLocation, Slot: AuxSlot, Format: gpu.VFFloat4, Offset: 0})
|
|
l.Strides[AuxSlot] = AuxStride
|
|
l.PerInstance[AuxSlot] = true
|
|
return l
|
|
}
|