175 lines
3.4 KiB
Go
175 lines
3.4 KiB
Go
|
|
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]
|
||
|
|
}
|