zworld/engine/renderapi/cache/sampler_cache.go
2024-01-14 22:56:06 +08:00

159 lines
3.4 KiB
Go

package cache
import (
"zworld/engine/renderapi/color"
"zworld/engine/renderapi/descriptor"
"zworld/engine/renderapi/texture"
)
type samplers struct {
textures TextureCache
desc *descriptor.SamplerArray
reverse map[string]*SamplerHandle
free map[int]bool
descriptors []texture.T
next int
// the max age must be shorter than the max life of the texture cache.
// if using a per-frame sampler cache, then the max life time should be
// at most (texture max life) / (number of swapchain frames)
maxAge int
// blank keeps a reference to a blank (white) texture
blank texture.T
}
type SamplerHandle struct {
ID int
Texture texture.T
age int
}
type SamplerCache interface {
T[texture.Ref, *SamplerHandle]
// Assign a handle to a texture directly
Assign(texture.T) *SamplerHandle
// Writes descriptor updates to the backing Sampler Array.
Flush()
}
func NewSamplerCache(textures TextureCache, desc *descriptor.SamplerArray) SamplerCache {
samplers := &samplers{
textures: textures,
desc: desc,
reverse: make(map[string]*SamplerHandle, 1000),
free: make(map[int]bool, 100),
descriptors: make([]texture.T, desc.Count),
next: 0,
maxAge: textures.MaxAge() / 4,
blank: textures.Fetch(color.White),
}
// ensure id 0 is always blank
samplers.assignHandle(color.White)
return samplers
}
func (s *samplers) MaxAge() int {
return s.maxAge
}
func (s *samplers) nextID() int {
// check free list
for handle := range s.free {
delete(s.free, handle)
return handle
}
// allocate new handle
id := s.next
if id >= s.desc.Count {
panic("out of handles")
}
s.next++
return id
}
type Keyed interface {
Key() string
}
func (s *samplers) assignHandle(ref Keyed) *SamplerHandle {
if handle, exists := s.reverse[ref.Key()]; exists {
// reset the age of the existing handle, if we have one
handle.age = 0
return handle
}
handle := &SamplerHandle{
ID: s.nextID(),
age: 0,
}
s.reverse[ref.Key()] = handle
return handle
}
func (s *samplers) TryFetch(ref texture.Ref) (*SamplerHandle, bool) {
handle := s.assignHandle(ref)
var exists bool
if handle.Texture, exists = s.textures.TryFetch(ref); exists {
return handle, true
}
return nil, false
}
func (s *samplers) Fetch(ref texture.Ref) *SamplerHandle {
handle := s.assignHandle(ref)
handle.Texture = s.textures.Fetch(ref)
return handle
}
func (s *samplers) Assign(tex texture.T) *SamplerHandle {
handle := s.assignHandle(tex)
handle.Texture = tex
return handle
}
func (s *samplers) Flush() {
s.Tick()
for _, handle := range s.reverse {
tex := handle.Texture
if tex == nil {
continue
}
if s.descriptors[handle.ID] == tex {
// texture hasnt changed, nothing to do.
continue
}
// texture has changed! update descriptor
s.descriptors[handle.ID] = tex
s.desc.Set(handle.ID, tex)
}
}
func (s *samplers) Tick() {
for ref, handle := range s.reverse {
// increase the age of the handle and check for eviction
handle.age++
if handle.age > s.maxAge {
delete(s.reverse, ref)
s.free[handle.ID] = true
// overwrite descriptor with blank texture
handle.Texture = s.blank
s.descriptors[handle.ID] = nil
s.desc.Set(handle.ID, s.blank)
}
}
}
func (s *samplers) Destroy() {
// todo: unclear if theres anything to do here
// the backing texture cache holds all resources and will release them if unused
}