zworld/engine/render/graph/graph.go

175 lines
3.4 KiB
Go
Raw Normal View History

2024-01-14 22:56:06 +08:00
package graph
import (
"fmt"
"github.com/vkngwrapper/core/v2/core1_0"
"image"
"log"
"time"
"zworld/engine/object"
"zworld/engine/renderapi/upload"
"zworld/engine/renderapi/vulkan"
)
type NodeFunc func(T, vulkan.Target) []Resource
// The render graph is responsible for synchronization between
// different render nodes.
type T interface {
Node(pass NodePass) Node
Recreate()
Draw(scene object.Object, time, delta float32)
Destroy()
Screengrab() *image.RGBA
Screenshot()
}
type Resource interface {
Destroy()
}
type graph struct {
app vulkan.App
target vulkan.Target
pre *preNode
post *postNode
nodes []Node
todo map[Node]bool
init NodeFunc
resources []Resource
}
func New(app vulkan.App, output vulkan.Target, init NodeFunc) T {
g := &graph{
app: app,
target: output,
nodes: make([]Node, 0, 16),
todo: make(map[Node]bool, 16),
init: init,
}
g.Recreate()
return g
}
func (g *graph) Recreate() {
g.Destroy()
g.app.Pool().Recreate()
g.resources = g.init(g, g.target)
g.pre = newPreNode(g.app, g.target)
g.post = newPostNode(g.app, g.target)
g.connect()
}
func (g *graph) Node(pass NodePass) Node {
nd := newNode(g.app, pass.Name(), pass)
g.nodes = append(g.nodes, nd)
return nd
}
func (g *graph) connect() {
// use bottom of pipe so that subsequent passes start as soon as possible
for _, node := range g.nodes {
if len(node.Requires()) == 0 {
node.After(g.pre, core1_0.PipelineStageTopOfPipe)
}
}
for _, node := range g.nodes {
if len(node.Dependants()) == 0 {
g.post.After(node, core1_0.PipelineStageTopOfPipe)
}
}
}
func (g *graph) Draw(scene object.Object, time, delta float32) {
// put all nodes in a todo list
// for each node in todo list
// if all Before nodes are not in todo list
// record node
// remove node from todo list
for _, n := range g.nodes {
g.todo[n] = true
}
ready := func(n Node) bool {
for _, req := range n.Requires() {
if g.todo[req] {
return false
}
}
return true
}
// prepare
args, context, err := g.pre.Prepare(scene, time, delta)
if err != nil {
log.Println("Render preparation error:", err)
g.Recreate()
return
}
// select a suitable worker for this frame
worker := g.app.Worker(args.Frame)
for len(g.todo) > 0 {
progress := false
for node := range g.todo {
// check if ready
if ready(node) {
log.Println(":::draw ", node.Name(), scene.Name())
node.Draw(worker, *args, scene)
delete(g.todo, node)
progress = true
break
}
}
if !progress {
// dependency error
panic("unable to make progress in render graph")
}
}
g.post.Present(worker, context)
}
func (g *graph) Screengrab() *image.RGBA {
idx := 0
g.app.Device().WaitIdle()
source := g.target.Surfaces()[idx]
ss, err := upload.DownloadImage(g.app.Device(), g.app.Transferer(), source)
if err != nil {
panic(err)
}
return ss
}
func (g *graph) Screenshot() {
img := g.Screengrab()
filename := fmt.Sprintf("Screenshot-%s.png", time.Now().Format("2006-01-02_15-04-05"))
if err := upload.SavePng(img, filename); err != nil {
panic(err)
}
log.Println("saved screenshot", filename)
}
func (g *graph) Destroy() {
g.app.Flush()
for _, resource := range g.resources {
resource.Destroy()
}
g.resources = nil
if g.pre != nil {
g.pre.Destroy()
g.pre = nil
}
if g.post != nil {
g.post.Destroy()
g.post = nil
}
for _, node := range g.nodes {
node.Destroy()
}
g.nodes = g.nodes[:0]
}