diff --git a/cmd/pbr/main.go b/cmd/pbr/main.go index 2cff8d4..4aecafd 100644 --- a/cmd/pbr/main.go +++ b/cmd/pbr/main.go @@ -5,6 +5,7 @@ import ( "os" "strings" "runtime/pprof" + "time" "github.com/hunterloftis/pbr/pkg/camera" "github.com/hunterloftis/pbr/pkg/env" @@ -45,6 +46,20 @@ func stopProfile(f *os.File) { f.Close() } +func startTimer(o *Options, s string) time.Time { + if o.Verbose { + fmt.Print(s) + } + return time.Now() +} + +func stopTimer(o *Options, start time.Time) { + if o.Verbose { + elapsed := time.Since(start) + fmt.Printf("done: %s\n", elapsed.Round(time.Millisecond)) + } +} + func run(o *Options) error { if o.Profile { f, err := createProfile() @@ -54,10 +69,12 @@ func run(o *Options) error { defer stopProfile(f) } + start := startTimer(o, "Loading object file... ") mesh, err := obj.ReadFile(o.Scene, true) if err != nil { return err } + stopTimer(o, start) if o.Scale != nil { mesh.Scale(*o.Scale) @@ -69,7 +86,9 @@ func run(o *Options) error { m := materials[strings.ToLower(o.Material)] mesh.SetMaterial(m) } + start = startTimer(o, "Calculating mesh bounds... ") bounds, surfaces := mesh.Bounds() + stopTimer(o, start) camera := camera.NewSLR() environment := render.Environment(env.NewGradient(rgb.Black, *o.Ambient, 3)) @@ -107,9 +126,12 @@ func run(o *Options) error { surfaces = append(surfaces, sun) } + start = startTimer(o, "Creating surface tree... ") tree := surface.NewTree(surfaces...) + stopTimer(o, start) scene := render.NewScene(camera, tree, environment) fmt.Println("Surfaces:", len(surfaces)) - return render.Iterative(scene, o.Out, o.Width, o.Height, o.Bounce, !o.Indirect) + limits := render.NewLimits(o.DumpPeriod, o.Time, o.Frames) + return render.Iterative(scene, limits, o.Out, o.Width, o.Height, o.Bounce, !o.Indirect) } diff --git a/cmd/pbr/options.go b/cmd/pbr/options.go index d77cebf..b388505 100644 --- a/cmd/pbr/options.go +++ b/cmd/pbr/options.go @@ -1,7 +1,6 @@ package main import ( - "math" "path/filepath" arg "github.com/alexflint/go-arg" @@ -15,8 +14,9 @@ type Options struct { Scene string `arg:"positional,required" help:"input scene .obj"` Verbose bool `arg:"-v" help:"verbose output with scene information"` Info bool `help:"output scene information and exit"` - Frames float64 `arg:"-f" help:"number of frames at which to exit"` - Time float64 `arg:"-t" help:"time to run before exiting (seconds)"` + DumpPeriod int `help:"how frequently to output progress frames"` + Frames int `arg:"-f" help:"number of frames at which to exit"` + Time int `arg:"-t" help:"time to run before exiting (seconds)"` Material string `help:"override material (glass, gold, mirror, plastic)"` Width int `arg:"-w" help:"rendering width in pixels"` @@ -57,8 +57,9 @@ func options() *Options { Rad: 100, Bounce: 6, Indirect: false, - Frames: math.Inf(1), - Time: math.Inf(1), + DumpPeriod: 6, + Frames: 0, + Time: 0, Lens: 50, FStop: 4, Focus: 1, diff --git a/examples/hello/hello.go b/examples/hello/hello.go index 691ae2c..2a4dfa8 100644 --- a/examples/hello/hello.go +++ b/examples/hello/hello.go @@ -24,7 +24,7 @@ func main() { e := env.NewGradient(rgb.Black, rgb.Energy{750, 750, 750}, 7) scene := render.NewScene(c, s, e) - err := render.Iterative(scene, "hello.png", 898, 450, 8, true) + err := render.Iterative(scene, nil, "hello.png", 898, 450, 8, true) if err != nil { fmt.Fprintf(os.Stderr, "\nError: %v\n", err) } diff --git a/examples/redblue/redblue.go b/examples/redblue/redblue.go index 364a3a1..2d2c2c5 100644 --- a/examples/redblue/redblue.go +++ b/examples/redblue/redblue.go @@ -44,5 +44,5 @@ func run() error { tree := surface.NewTree(surfaces...) scene := render.NewScene(camera, tree, environment) - return render.Iterative(scene, "redblue.png", 1280, 720, 6, true) + return render.Iterative(scene, nil, "redblue.png", 1280, 720, 6, true) } diff --git a/examples/shapes/shapes.go b/examples/shapes/shapes.go index cac2bff..885b6c5 100644 --- a/examples/shapes/shapes.go +++ b/examples/shapes/shapes.go @@ -48,5 +48,5 @@ func run() error { ) scene := render.NewScene(cam, surf, sky) - return render.Iterative(scene, "shapes.png", 800, 450, 6, true) + return render.Iterative(scene, nil, "shapes.png", 800, 450, 6, true) } diff --git a/examples/sponza/sponza.go b/examples/sponza/sponza.go index 6527942..0f124a0 100644 --- a/examples/sponza/sponza.go +++ b/examples/sponza/sponza.go @@ -44,5 +44,5 @@ func run() error { tree := surface.NewTree(surfaces...) scene := render.NewScene(camera, tree, environment) - return render.Iterative(scene, "sponza.png", 1280, 720, 8, true) + return render.Iterative(scene, nil, "sponza.png", 1280, 720, 8, true) } diff --git a/pkg/render/iterative.go b/pkg/render/iterative.go index 7dcdd4b..fd5b8aa 100644 --- a/pkg/render/iterative.go +++ b/pkg/render/iterative.go @@ -14,14 +14,46 @@ import ( "golang.org/x/text/message" ) -func Iterative(scene *Scene, file string, width, height, depth int, direct bool) error { +type Limits struct { + DumpPeriod time.Duration + Time time.Duration + Frames int +} + +func NewLimits(d int, t int, f int) *Limits { + if d == 0 { + d = math.MaxInt32 + } + if t == 0 { + t = math.MaxInt32 + } + return &Limits{ + DumpPeriod: time.Duration(d) * time.Second, + Time: time.Duration(t) * time.Second, + Frames: f, + } +} + +func Iterative(scene *Scene, limits *Limits, file string, width, height, depth int, direct bool) error { kill := make(chan os.Signal, 2) signal.Notify(kill, os.Interrupt, syscall.SIGTERM) + if limits == nil { + limits = NewLimits(6, 0, 0) + } + frame := scene.Render(width, height, depth, direct) defer frame.Stop() - ticker := time.NewTicker(6 * time.Second) // 10 .s = 1 minute, 100 .s = 1 hr + ticker := time.NewTicker(limits.DumpPeriod) defer ticker.Stop() + limiter := time.NewTicker(limits.Time) + defer limiter.Stop() + framelim := time.NewTicker(1 * time.Second) + if limits.Frames == 0 { + framelim.Stop() + } else { + defer framelim.Stop() + } start := time.Now().UnixNano() max := 0 @@ -31,6 +63,14 @@ func Iterative(scene *Scene, file string, width, height, depth int, direct bool) select { case <-kill: frame.Stop() + case <-limiter.C: + frame.Stop() + fmt.Printf("\nTime limit reached.\n") + case <-framelim.C: + if _, n := frame.Sample(); n > limits.Frames { + frame.Stop() + fmt.Print("\nFrame limit reached.\n") + } case <-ticker.C: if sample, n := frame.Sample(); n > max { max = n @@ -43,12 +83,16 @@ func Iterative(scene *Scene, file string, width, height, depth int, direct bool) } stop := time.Now().UnixNano() - sample, _ := frame.Sample() + sample, frames := frame.Sample() total := sample.Total() p := message.NewPrinter(language.English) secs := float64(stop-start) / 1e9 sps := math.Round(float64(total) / secs) // TODO: rename to pixels/sec for clarity p.Printf("\n%v samples in %.1f seconds (%.0f samples/sec)\n", total, secs, sps) + p.Printf("\n%v frames (%.1f frames/sec)\n", frames, float64(frames)/secs) + if err := writePng(file, sample.Image()); err != nil { + return err + } return nil }