func renderer( width, height int, renderedImages chan *hdrimage.Image, scene *scene.Scene, seed int64, totalSamples int, threads int, quiet bool, ) { randomGen := random.New(seed) sampleCounter := rpc.NewSampleCounter(totalSamples) var bar *progress.ProgressBar if !quiet { bar = progress.StartProgressBar(totalSamples, "rendering samples") } wg := sync.WaitGroup{} wg.Add(threads) defer func() { wg.Wait() if !quiet { bar.Done() } }() for i := 0; i < threads; i++ { go func(seed int64) { defer wg.Done() raytracer := raytracer.Raytracer{ Scene: scene, Random: random.New(seed), } image := hdrimage.New(width, height) image.Divisor = 0 for { if sampleCounter.Dec(1) == 0 { renderedImages <- image return } raytracer.Sample(image) if !quiet { bar.Add(1) } } }(randomGen.NewSeed()) } }
// JoinSamples combines samples into a single image func JoinSamples( renderedImages <-chan *hdrimage.Image, width int, height int, ) *hdrimage.Image { averageImage := hdrimage.New(width, height) averageImage.Divisor = 0 for image := range renderedImages { averageImage.Add(image) averageImage.Divisor += image.Divisor } return averageImage }
// Sample works like StoreSample(), but instead of storing the image internally, // returns a new image. func (cr *ConcurrentRaytracer) Sample(settings *SampleSettings) (*hdrimage.Image, error) { unit := <-cr.units if unit.raytracer.Scene == nil { return nil, fmt.Errorf("N/A scene") } image := hdrimage.New(settings.Width, settings.Height) image.Divisor = 0 for i := 0; i < settings.SamplesAtOnce; i++ { unit.raytracer.Sample(image) } cr.units <- unit return image, nil }
// StoreSample renders the scene into an internal image. You can (and should) // call it multiple times, in parallel, and when you're finished you can get // the merged samples with GetImage(). StoreSample will block if the parallel // calls exceed the parallelSamples value, and wait for other samples to finish. func (cr *ConcurrentRaytracer) StoreSample(settings *SampleSettings) error { unit := <-cr.units if unit.raytracer.Scene == nil { return fmt.Errorf("N/A scene") } if unit.image == nil { unit.image = hdrimage.New(settings.Width, settings.Height) unit.image.Divisor = 0 } for i := 0; i < settings.SamplesAtOnce; i++ { unit.raytracer.Sample(unit.image) } cr.units <- unit return nil }
// GetImage collects all samples up to this moment (and waits for those that // are currently being rendered to finish), and merges them. // StoreSample() can be called during calling this function, and will block until // it finishes. Next samples will start from zero (e.g. the base image is reset) func (cr *ConcurrentRaytracer) GetImage() *hdrimage.Image { var mergedSamples *hdrimage.Image units := cr.getAllUnits() for _, unit := range units { if mergedSamples == nil { mergedSamples = unit.image } else if unit.image != nil { mergedSamples.Add(unit.image) mergedSamples.Divisor += unit.image.Divisor } unit.image = nil } cr.pushAllUnits(units) if mergedSamples == nil { return hdrimage.New(0, 0) } return mergedSamples }
func runRender(c *cli.Context) error { scenePath, image := getArguments(c) quiet := c.GlobalBool("quiet") if !quiet { log.Printf( "will render %d samples of %s to %s of size %dx%d with %d threads", c.Int("total-samples"), scenePath, image, c.Int("width"), c.Int("height"), c.Int("max-jobs"), ) } width, height := c.Int("width"), c.Int("height") totalSamples := c.Int("total-samples") threads := c.Int("max-jobs") renderedImages := make(chan *hdrimage.Image) scene, err := scene.LoadFromFile(scenePath) if err != nil { return fmt.Errorf("can't open scene: %s", err) } scene.Init() go func() { renderer(width, height, renderedImages, scene, 42, totalSamples, threads, quiet) close(renderedImages) }() averageImage := hdrimage.New(width, height) averageImage.Divisor = 0 for currentImage := range renderedImages { averageImage.Add(currentImage) averageImage.Divisor += currentImage.Divisor } return savePng(averageImage, image) }