PsychoBlend now updates render in realtime within Blender.
Also, cancelling renders is much more responsive now, especially during long exports.
This commit is contained in:
parent
3beffab507
commit
f84d093f66
34
Cargo.lock
generated
34
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -21,6 +21,7 @@ debug = true
|
|||
|
||||
[dependencies]
|
||||
# Crates.io dependencies
|
||||
base64 = "0.5"
|
||||
clap = "2.23"
|
||||
crossbeam = "0.2"
|
||||
half = "1.0"
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
27
src/image.rs
27
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<F>(&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> {
|
||||
|
|
31
src/main.rs
31
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") {
|
||||
|
|
|
@ -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<BucketJob>, all_jobs_queued: &RwLock<bool>, image: &Image, collected_stats: &RwLock<RenderStats>, pixels_rendered: &Mutex<Cell<usize>>) {
|
||||
fn render_job(&self, job_queue: &MsQueue<BucketJob>, all_jobs_queued: &RwLock<bool>, image: &Image, collected_stats: &RwLock<RenderStats>, pixels_rendered: &Mutex<Cell<usize>>, 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user