Files
aego/render/viewframe.go
2026-09-26 16:32:40 +03:00

157 lines
3.1 KiB
Go

package render
import (
"aego/core/mathx"
"aego/gpu"
"aego/render/culling"
"aego/render/debug"
"aego/render/sprite"
"aego/render/text"
"aego/render/view"
)
const maxClipDepth = 16
type ViewFrame struct {
r *Renderer
view view.View
pass gpu.Pass
bounds mathx.AABB
clips [maxClipDepth]mathx.Rect
clipOn [maxClipDepth]bool
depth int
submission uint32
ended bool
}
func (f *ViewFrame) reset(v view.View) {
r := f.r
w, h := r.framebuffer()
v.Normalize(w, h, r.fbScale)
f.view = v
f.bounds = v.VisibleBounds()
f.depth = 0
f.submission = 0
f.ended = false
f.pass = nil
r.batcher.Reset()
r.batcher.SetView(v.ZMin, v.ZMax, v.PixelsPerUnit)
r.dbg.Reset()
}
func (f *ViewFrame) View() *view.View {
return &f.view
}
func (f *ViewFrame) Debug() *debug.Draw {
return f.r.dbg
}
func (f *ViewFrame) PushClip(r mathx.Rect) {
if f.depth >= maxClipDepth {
return
}
scale := f.view.PixelScale
px := mathx.Rect{X: r.X * scale, Y: r.Y * scale, W: r.W * scale, H: r.H * scale}
if f.depth > 0 && f.clipOn[f.depth-1] {
px = intersect(px, f.clips[f.depth-1])
}
f.clips[f.depth] = px
f.clipOn[f.depth] = true
f.depth++
}
func (f *ViewFrame) PopClip() {
if f.depth > 0 {
f.depth--
}
}
func intersect(a, b mathx.Rect) mathx.Rect {
x0 := max(a.X, b.X)
y0 := max(a.Y, b.Y)
x1 := min(a.X+a.W, b.X+b.W)
y1 := min(a.Y+a.H, b.Y+b.H)
return mathx.Rect{X: x0, Y: y0, W: max(0, x1-x0), H: max(0, y1-y0)}
}
func (f *ViewFrame) clip() (mathx.Rect, bool) {
if f.depth == 0 {
return mathx.Rect{}, false
}
return f.clips[f.depth-1], f.clipOn[f.depth-1]
}
func (f *ViewFrame) Sprite(d sprite.Draw) {
f.submit(&d)
}
func (f *ViewFrame) Sprites(ds []sprite.Draw) {
for i := range ds {
f.submit(&ds[i])
}
}
func (f *ViewFrame) submit(d *sprite.Draw) {
r := f.r
r.stats.Submitted++
if d.Region.IsZero() {
return
}
if !culling.Visible(f.bounds, d, f.view.PixelsPerUnit) {
r.stats.Culled++
return
}
clip, on := f.clip()
if !r.batcher.Push(d, r.defSampler, clip, on) {
r.stats.Dropped++
if !r.warned {
r.warned = true
r.log.Warn("sprite budget exceeded, extra sprites are dropped")
}
return
}
r.stats.Visible++
f.submission++
}
func (f *ViewFrame) Text(font *text.BitmapFont, s string, pos mathx.Vec2, scale float32, c mathx.Color, layer, order int16) {
if font == nil || s == "" {
return
}
r := f.r
r.textBuf = font.Layout(s, pos, scale, c, layer, order, r.textBuf[:0])
f.Sprites(r.textBuf)
}
func (f *ViewFrame) End() {
if f.ended {
return
}
f.ended = true
r := f.r
r.batcher.Sort()
r.stats.ZClamped += r.batcher.Clamped()
if f.pass == nil {
return
}
r.passCtx = PassContext{Renderer: r, View: &f.view, Pass: f.pass, Frame: f}
for i := range r.order {
r.order[i].Pass.Execute(&r.passCtx)
}
if r.stats.Views == 1 {
r.prof.End(f.pass)
}
f.pass.End()
f.pass = nil
}
func (f *ViewFrame) TextFont(font *text.Font, s string, pos mathx.Vec2, scale float32, c mathx.Color, layer, order int16) {
if font == nil || s == "" {
return
}
r := f.r
r.textBuf = font.Layout(s, pos, scale, c, layer, order, r.textBuf[:0])
f.Sprites(r.textBuf)
}