diff --git a/Cargo.lock b/Cargo.lock index 1df8004..a56882f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,11 +17,24 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "base64" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bitflags" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "byteorder" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "clap" version = "2.24.2" @@ -51,7 +64,7 @@ dependencies = [ [[package]] name = "gcc" -version = "0.3.49" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -100,7 +113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "num_cpus" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", @@ -121,7 +134,7 @@ name = "openexr-sys" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gcc 0.3.49 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -140,6 +153,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "psychopath" version = "0.1.0" dependencies = [ + "base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "float4 0.1.0", @@ -149,7 +163,7 @@ dependencies = [ "math3d 0.1.0", "mem_arena 0.1.0", "nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "openexr 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "png_encode_mini 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", @@ -161,7 +175,7 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -205,7 +219,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -237,21 +251,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" "checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159" +"checksum base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557" "checksum bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1370e9fc2a6ae53aea8b7a5110edbd08836ed87c88736dfabccade1c2b44bff4" +"checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" "checksum clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b8f69e518f967224e628896b54e41ff6acfb4dcfefc5076325c36525dac900f" "checksum crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5ea215664ca264da8a9d9c3be80d2eaf30923c259d03e870388eb927508f97" -"checksum gcc 0.3.49 (registry+https://github.com/rust-lang/crates.io-index)" = "9be730064c122681712957ba1a9abaf082150be8aaf94526a805d900015b65b9" +"checksum gcc 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)" = "5f837c392f2ea61cb1576eac188653df828c861b7137d74ea4a5caa89621f9e6" "checksum half 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "63d68db75012a85555434ee079e7e6337931f87a087ab2988becbadf64673a7f" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf" "checksum libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e7eb6b826bfc1fdea7935d46556250d1799b7fe2d9f7951071f4291710665e3e" "checksum nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce" -"checksum num_cpus 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f6e850c7f35c3de263e6094e819f6b4b9c09190ff4438fc6dec1aef1568547bc" +"checksum num_cpus 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6e416ba127a4bb3ff398cb19546a8d0414f73352efe2857f4060d36f5fe5983a" "checksum openexr 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "849a5af32d6b0716cf44a8b18c1b611b8448aec44a9b1c59b6199bf9d22aba94" "checksum openexr-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d597730c7049d9098ce64bb97c6eb4e6f96fa55d5ced7eba74a01c224fe0bcc4" "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" "checksum png_encode_mini 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "116ae962ea1a679f99915c2c53dc470b45aaf6b42e607580d16fce8c6248f666" -"checksum redox_syscall 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "29dbdfd4b9df8ab31dec47c6087b7b13cbf4a776f335e4de8efba8288dda075b" +"checksum redox_syscall 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "3041aeb6000db123d2c9c751433f526e1f404b23213bd733167ab770c3989b4d" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum scoped_threadpool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3ef399c8893e8cb7aa9696e895427fab3a6bf265977bb96e126f24ddd2cda85a" "checksum simd 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a94d14a2ae1f1f110937de5fb69e494372560181c7e1739a097fcc2cee37ba0" diff --git a/Cargo.toml b/Cargo.toml index 56a4933..80e86cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ debug = true [dependencies] # Crates.io dependencies +base64 = "0.5" clap = "2.23" crossbeam = "0.2" half = "1.0" diff --git a/psychoblend/psy_export.py b/psychoblend/psy_export.py index 0b69187..02f0245 100644 --- a/psychoblend/psy_export.py +++ b/psychoblend/psy_export.py @@ -3,6 +3,12 @@ import bpy from math import degrees, pi, log from mathutils import Vector, Matrix +class ExportCancelled(Exception): + """ Indicates that the render was cancelled in the middle of exporting + the scene file. + """ + pass + def mat2str(m): """ Converts a matrix into a single-line string of values. @@ -87,7 +93,8 @@ class IndentedWriter: class PsychoExporter: - def __init__(self, scene): + def __init__(self, render_engine, scene): + self.render_engine = render_engine self.scene = scene self.mesh_names = {} @@ -112,9 +119,22 @@ class PsychoExporter: else: self.scene.frame_set(frame-1, 1.0+fraction) - def export_psy(self, export_path, render_image_path): - f = open(export_path, 'w') + try: + f = open(export_path, 'w') + self._export_psy(f, export_path, render_image_path) + except ExportCancelled: + # Cleanup + f.close() + self.scene.frame_set(self.fr) + return False + else: + # Cleanup + f.close() + self.scene.frame_set(self.fr) + return True + + def _export_psy(self, f, export_path, render_image_path): self.w = IndentedWriter(f) # Info @@ -168,6 +188,10 @@ class PsychoExporter: matz = Matrix() matz[2][2] = -1 for i in range(self.time_samples): + # Check if render is cancelled + if self.render_engine.test_break(): + raise ExportCancelled() + if res_x >= res_y: self.w.write("Fov [%f]\n" % degrees(cam.data.angle)) else: @@ -222,9 +246,6 @@ class PsychoExporter: self.w.unindent() self.w.write("}\n") - # Cleanup - f.close() - self.scene.frame_set(self.fr) def export_materials(self, materials): @@ -243,6 +264,10 @@ class PsychoExporter: def export_objects(self, objects, visible_layers, group_prefix="", translation_offset=(0,0,0)): for ob in objects: + # Check if render is cancelled + if self.render_engine.test_break(): + raise ExportCancelled() + # Check if the object is visible for rendering vis_layer = False for i in range(len(ob.layers)): @@ -278,6 +303,9 @@ class PsychoExporter: if needs_xform_mb(ob): for i in range(self.time_samples): + # Check if render is cancelled + if self.render_engine.test_break(): + raise ExportCancelled() self.set_frame(self.fr, self.shutter_start + (self.shutter_diff*i)) mat = ob.matrix_world.copy() mat[0][3] += translation_offset[0] @@ -316,6 +344,9 @@ class PsychoExporter: # Collect time samples time_meshes = [] for i in range(self.time_samples): + # Check if render is cancelled + if self.render_engine.test_break(): + raise ExportCancelled() self.set_frame(self.fr, self.shutter_start + (self.shutter_diff*i)) if export_mesh and (deform_mb or i == 0): time_meshes += [ob.to_mesh(self.scene, True, 'RENDER')] @@ -366,6 +397,9 @@ class PsychoExporter: # Collect time samples time_surfaces = [] for i in range(self.time_samples): + # Check if render is cancelled + if self.render_engine.test_break(): + raise ExportCancelled() self.set_frame(self.fr, self.shutter_start + (self.shutter_diff*i)) time_surfaces += [ob.data.copy()] @@ -393,6 +427,9 @@ class PsychoExporter: time_col = [] time_rad = [] for i in range(self.time_samples): + # Check if render is cancelled + if self.render_engine.test_break(): + raise ExportCancelled() self.set_frame(self.fr, self.shutter_start + (self.shutter_diff*i)) time_col += [ob.data.color * ob.data.energy] time_rad += [ob.data.shadow_soft_size] @@ -417,6 +454,9 @@ class PsychoExporter: time_col = [] time_dim = [] for i in range(self.time_samples): + # Check if render is cancelled + if self.render_engine.test_break(): + raise ExportCancelled() self.set_frame(self.fr, self.shutter_start + (self.shutter_diff*i)) time_col += [ob.data.color * ob.data.energy] if ob.data.shape == 'RECTANGLE': @@ -446,6 +486,9 @@ class PsychoExporter: time_col = [] time_rad = [] for i in range(self.time_samples): + # Check if render is cancelled + if self.render_engine.test_break(): + raise ExportCancelled() self.set_frame(self.fr, self.shutter_start + (self.shutter_diff*i)) time_dir += [tuple(ob.matrix_world.to_3x3() * Vector((0, 0, -1)))] time_col += [ob.data.color * ob.data.energy] diff --git a/psychoblend/render.py b/psychoblend/render.py index f70713f..c7f293e 100644 --- a/psychoblend/render.py +++ b/psychoblend/render.py @@ -3,6 +3,8 @@ import time import os import subprocess import tempfile +import base64 +import struct from . import psy_export def get_temp_filename(suffix=""): @@ -39,8 +41,8 @@ class PsychopathRender(bpy.types.RenderEngine): return "" def _export(self, scene, export_path, render_image_path): - exporter = psy_export.PsychoExporter(scene) - exporter.export_psy(export_path, render_image_path) + exporter = psy_export.PsychoExporter(self, scene) + return exporter.export_psy(export_path, render_image_path) def _render(self, scene, psy_filepath): psy_binary = PsychopathRender._locate_binary() @@ -49,7 +51,7 @@ class PsychopathRender(bpy.types.RenderEngine): return False # TODO: figure out command line options - args = ["--spb", str(scene.psychopath.max_samples_per_bucket), "-i", psy_filepath] + args = ["--spb", str(scene.psychopath.max_samples_per_bucket), "--blender_output", "-i", psy_filepath] # Start Rendering! try: @@ -65,27 +67,24 @@ class PsychopathRender(bpy.types.RenderEngine): return True + def _draw_bucket(self, bucket_info, pixels_encoded): + x = bucket_info[0] + y = self.size_y - bucket_info[3] + width = bucket_info[2] - bucket_info[0] + height = bucket_info[3] - bucket_info[1] - def _cleanup(self): - # for f in (self._temp_file_in, self._temp_file_ini, self._temp_file_out): - # for i in range(5): - # try: - # os.unlink(f) - # break - # except OSError: - # # Wait a bit before retrying file might be still in use by Blender, - # # and Windows does not know how to delete a file in use! - # time.sleep(self.DELAY) - # for i in unpacked_images: - # for c in range(5): - # try: - # os.unlink(i) - # break - # except OSError: - # # Wait a bit before retrying file might be still in use by Blender, - # # and Windows does not know how to delete a file in use! - # time.sleep(self.DELAY) - pass + # Decode pixel data + pixels = [p for p in struct.iter_unpack("ffff", base64.b64decode(pixels_encoded))] + pixels_flipped = [] + for i in range(height): + n = height - i - 1 + pixels_flipped += pixels[n*width:(n+1)*width] + + # Write pixel data to render image + result = self.begin_result(x, y, width, height) + lay = result.layers[0].passes["Combined"] + lay.rect = pixels_flipped + self.end_result(result) def render(self, scene): # has to be called to update the frame on exporting animations @@ -103,7 +102,10 @@ class PsychopathRender(bpy.types.RenderEngine): # start export self.update_stats("", "Psychopath: Exporting data from Blender") - self._export(scene, export_path, render_image_path) + if not self._export(scene, export_path, render_image_path): + # Render cancelled in the middle of exporting, + # so just return. + return # Start rendering self.update_stats("", "Psychopath: Rendering from exported file") @@ -113,54 +115,78 @@ class PsychopathRender(bpy.types.RenderEngine): r = scene.render # compute resolution - x = int(r.resolution_x * r.resolution_percentage) - y = int(r.resolution_y * r.resolution_percentage) + self.size_x = int(r.resolution_x * r.resolution_percentage / 100) + self.size_y = int(r.resolution_y * r.resolution_percentage / 100) - result = self.begin_result(0, 0, x, y) - lay = result.layers[0] + # If we can, make the render process's stdout non-blocking. The + # benefit of this is that canceling the render won't block waiting + # for the next piece of input. + try: + import fcntl + fd = self._process.stdout.fileno() + fl = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) + except: + print("NOTE: Can't make Psychopath's stdout non-blocking, so canceling renders may take a moment to respond.") - # TODO: Update viewport with render result while rendering + # Process output from rendering process + reached_first_bucket = False output = b"" while self._process.poll() == None: - # Wait for self.DELAY seconds, but check for render cancels - # and progress updates while waiting. - t = 0.0 - while t < self.DELAY: + # Wait for render process output while checking for render + # cancellation + while True: # Check for render cancel if self.test_break(): self._process.terminate() break - # Update render progress bar - output += self._process.stdout.read1(2**16) - outputs = output.rsplit(b'\r') - progress = 0.0 - if len(outputs) > 0 and outputs[-1][-1] == b"%"[0]: + # Get render output from stdin + tmp = self._process.stdout.read1(2**16) + if len(tmp) == 0: + time.sleep(0.01) + continue + else: + break + output += tmp + outputs = output.split(b'DIV\n') + + # Skip render process output until we hit the first bucket. + # (The stuff before it is just informational printouts.) + if not reached_first_bucket: + if len(outputs) > 1: + reached_first_bucket = True + outputs = outputs[1:] + else: + continue + + # Clear output buffer, since it's all in 'outputs' now. + output = b"" + + # Process buckets + for bucket in outputs: + if len(bucket) == 0: + continue + + if bucket[-11:] == b'BUCKET_END\n': + # Parse bucket text + contents = bucket.split(b'\n') + percentage = contents[0] + bucket_info = [int(i) for i in contents[1].split(b' ')] + pixels = contents[2] + + # Draw the bucket + self._draw_bucket(bucket_info, pixels) + + # Update render progress bar try: - progress = float(outputs[-1][:-1]) + progress = float(percentage[:-1]) except ValueError: pass finally: self.update_progress(progress/100) - - time.sleep(0.05) - t += 0.05 - - # # Update viewport image with latest render output - # if os.path.exists(render_image_path): - # # This assumes the file has been fully written We wait a bit, just in case! - # try: - # lay.load_from_file(render_image_path) - # self.update_result(result) - # except RuntimeError: - # pass - - # Load final image - lay.load_from_file(render_image_path) - self.end_result(result) - - # Delete temporary image file - os.remove(render_image_path) + else: + output += bucket def register(): bpy.utils.register_class(PsychopathRender) diff --git a/src/image.rs b/src/image.rs index d120e1a..d35dbe9 100644 --- a/src/image.rs +++ b/src/image.rs @@ -220,6 +220,33 @@ impl<'a> Bucket<'a> { data[img.res.0 * y as usize + x as usize] = value; } + + /// Returns the bucket's contents encoded in base64. + /// + /// `color_convert` lets you do a colorspace conversion before base64 + /// encoding if desired. + /// + /// The data is laid out as four-floats-per-pixel in scanline order before + /// encoding to base64. The fourth channel is alpha, and is set to 1.0 for + /// all pixels. + pub fn rgba_base64(&mut self, color_convert: F) -> String + where F: Fn((f32, f32, f32)) -> (f32, f32, f32) + { + use base64; + use std::slice; + let mut data = Vec::with_capacity((4 * (self.max.0 - self.min.0) * (self.max.1 - self.min.1)) as usize); + for y in self.min.1..self.max.1 { + for x in self.min.0..self.max.0 { + let color = color_convert(self.get(x, y).to_tuple()); + data.push(color.0); + data.push(color.1); + data.push(color.2); + data.push(1.0); + } + } + let data_u8 = unsafe { slice::from_raw_parts(&data[0] as *const f32 as *const u8, data.len() * 4) }; + base64::encode(data_u8) + } } impl<'a> Drop for Bucket<'a> { diff --git a/src/main.rs b/src/main.rs index 93e39c5..a8c7aeb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ extern crate math3d; extern crate mem_arena; extern crate spectra_xyz; +extern crate base64; extern crate clap; extern crate crossbeam; extern crate half; @@ -141,6 +142,12 @@ fn main() { .long("dev") .help("Show useful dev/debug info.") ) + .arg( + Arg::with_name("blender_output") + .long("blender_output") + .help("Used by PsychoBlend to pass render output through standard in/out.") + .hidden(true) + ) .get_matches(); // Print some misc useful dev info. @@ -207,7 +214,11 @@ fn main() { println!("\tBuilt scene in {:.3}s", t.tick()); println!("Rendering scene with {} threads...", thread_count); - let (mut image, rstats) = r.render(max_samples_per_bucket, thread_count); + let (mut image, rstats) = r.render( + max_samples_per_bucket, + thread_count, + args.is_present("blender_output"), + ); // Print render stats { let rtime = t.tick(); @@ -235,15 +246,17 @@ fn main() { ); } - println!("Writing image to disk..."); - if r.output_file.ends_with(".png") { - let _ = image.write_png(Path::new(&r.output_file)); - } else if r.output_file.ends_with(".exr") { - image.write_exr(Path::new(&r.output_file)); - } else { - panic!("Unknown output file extension."); + if !args.is_present("blender_output") { + println!("Writing image to disk..."); + if r.output_file.ends_with(".png") { + let _ = image.write_png(Path::new(&r.output_file)); + } else if r.output_file.ends_with(".exr") { + image.write_exr(Path::new(&r.output_file)); + } else { + panic!("Unknown output file extension."); + } + println!("\tWrote image in {:.3}s", t.tick()); } - println!("\tWrote image in {:.3}s", t.tick()); // Print memory stats if stats are wanted. if args.is_present("stats") { diff --git a/src/renderer.rs b/src/renderer.rs index b29e8cc..4bdd81e 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -68,7 +68,7 @@ impl RenderStats { } impl<'a> Renderer<'a> { - pub fn render(&self, max_samples_per_bucket: u32, thread_count: u32) -> (Image, RenderStats) { + pub fn render(&self, max_samples_per_bucket: u32, thread_count: u32, do_blender_output: bool) -> (Image, RenderStats) { let mut tpool = Pool::new(thread_count); let image = Image::new(self.resolution.0, self.resolution.1); @@ -94,7 +94,7 @@ impl<'a> Renderer<'a> { let img = ℑ let cstats = &collective_stats; let pixrenref = &pixels_rendered; - scope.execute(move || self.render_job(jq, ajq, img, cstats, pixrenref)); + scope.execute(move || self.render_job(jq, ajq, img, cstats, pixrenref, do_blender_output)); } // Print initial 0.00% progress @@ -164,7 +164,7 @@ impl<'a> Renderer<'a> { } /// Waits for buckets in the job queue to render and renders them when available. - fn render_job(&self, job_queue: &MsQueue, all_jobs_queued: &RwLock, image: &Image, collected_stats: &RwLock, pixels_rendered: &Mutex>) { + fn render_job(&self, job_queue: &MsQueue, all_jobs_queued: &RwLock, image: &Image, collected_stats: &RwLock, pixels_rendered: &Mutex>, do_blender_output: bool) { let mut stats = RenderStats::new(); let mut timer = Timer::new(); let mut total_timer = Timer::new(); @@ -251,8 +251,8 @@ impl<'a> Renderer<'a> { stats.ray_generation_time += timer.tick() as f64; } - // Calculate color based on ray hits and save to image { + // Calculate color based on ray hits and save to image let min = (bucket.x, bucket.y); let max = (bucket.x + bucket.w, bucket.y + bucket.h); let mut img_bucket = image.get_bucket(min, max); @@ -263,10 +263,8 @@ impl<'a> Renderer<'a> { img_bucket.set(path.pixel_co.0, path.pixel_co.1, col); } stats.sample_writing_time += timer.tick() as f64; - } - // Print render progress - { + // Print render progress, and image data if doing blender output let guard = pixels_rendered.lock().unwrap(); let mut pr = (*guard).get(); let percentage_old = pr as f64 / total_pixels as f64 * 100.0; @@ -278,10 +276,20 @@ impl<'a> Renderer<'a> { let old_string = format!("{:.2}%", percentage_old); let new_string = format!("{:.2}%", percentage_new); - if new_string != old_string { - print!("\r{}", new_string); - let _ = io::stdout().flush(); + if do_blender_output { + use color::xyz_to_rec709e; + println!("DIV"); + println!("{}", new_string); + println!("{} {} {} {}", min.0, min.1, max.0, max.1); + println!("{}", img_bucket.rgba_base64(xyz_to_rec709e)); + println!("BUCKET_END"); + println!("DIV"); + } else { + if new_string != old_string { + print!("\r{}", new_string); + } } + let _ = io::stdout().flush(); } }