From 5a8b1d413e98abd10b8ca6b1eb5eb91987f39ebf Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 29 Jul 2011 00:38:18 -0600 Subject: Support rendering image subregions. This is the first step to supporting distributed renders. --- dimension/dimension.in | 92 ++++++++++++++++++++++++++------------- libdimension-python/dimension.pxd | 7 ++- libdimension-python/dimension.pyx | 47 +++++++++++++++++--- libdimension/dimension/scene.h | 6 +++ libdimension/raytrace.c | 4 +- libdimension/scene.c | 11 +++++ 6 files changed, 127 insertions(+), 40 deletions(-) diff --git a/dimension/dimension.in b/dimension/dimension.in index f8cb114..b81f44b 100644 --- a/dimension/dimension.in +++ b/dimension/dimension.in @@ -19,36 +19,19 @@ # along with this program. If not, see . # ######################################################################### -import argparse -import os.path -import sys - -# Display a progress bar -def progress_bar(str, progress): - if not _args.quiet: - print(str, end = ' ') - sys.stdout.flush() - - term_width = terminal_width() - width = term_width - (len(str) + 1)%term_width - for i in range(width): - progress.wait(i/width) - print('.', end = '') - sys.stdout.flush() - - print() - sys.stdout.flush() - - progress.finish() +import argparse as _argparse +import re as _re +import os.path as _path +import sys as _sys # Specialized parser to print --version output to stdout rather than stderr, # to pass distcheck -class _DimensionArgumentParser(argparse.ArgumentParser): +class _DimensionArgumentParser(_argparse.ArgumentParser): def exit(self, status = 0, message = None): if message: - file = sys.stdout if status == 0 else sys.stderr + file = _sys.stdout if status == 0 else _sys.stderr file.write(message) - sys.exit(status) + _sys.exit(status) # Parse the command line _parser = _DimensionArgumentParser( @@ -56,7 +39,7 @@ _parser = _DimensionArgumentParser( "@PACKAGE_URL@\n" "Copyright (C) 2009-2011 Tavian Barnes <@PACKAGE_BUGREPORT@>\n" "Licensed under the GNU General Public License", - formatter_class = argparse.RawDescriptionHelpFormatter, + formatter_class = _argparse.RawDescriptionHelpFormatter, conflict_handler = "resolve", # For -h as height instead of help ) @@ -67,6 +50,9 @@ _parser.add_argument("-w", "--width", action = "store", type = int, default = 768, help = "image width") _parser.add_argument("-h", "--height", action = "store", type = int, default = 480, help = "image height") +_parser.add_argument("--region", action = "store", type = str, + default = None, + help = "subregion to render, as \"((x1, y1), (x2, y2))\"") _parser.add_argument("-v", "--verbose", action = "store_true", help = "print more information") @@ -85,23 +71,63 @@ _parser.add_argument("input", action = "store", type = str, # Debugging/testing options _parser.add_argument("--strict", action = "store_true", - help = argparse.SUPPRESS) + help = _argparse.SUPPRESS) _args = _parser.parse_args() +# Calculate subregion +if _args.region is None: + _args.region_x = 0 + _args.region_y = 0 + _args.region_width = _args.width + _args.region_height = _args.height +else: + _pattern = r"^\s*\(\s*\(\s*(\d*)\s*,\s*(\d*)\s*\)\s*,\s*\(\s*(\d*)\s*,\s*(\d*)\s*\)\s*\)\s*$" + _match = _re.match(_pattern, _args.region) + if _match is None: + raise RuntimeError("range specified in invalid format.") + _args.region_x = int(_match.group(1)) + _args.region_y = int(_match.group(2)) + _args.region_width = int(_match.group(3)) - _args.region_x + _args.region_height = int(_match.group(4)) - _args.region_y + if _args.region_x + _args.region_width >= _args.width: + raise RuntimeError("region exceeds width of image.") + if _args.region_y + _args.region_height >= _args.height: + raise RuntimeError("region exceeds height of image.") + # Default output is basename(input).png if _args.output is None: - _noext = os.path.splitext(os.path.basename(_args.input))[0] + _noext = _path.splitext(_path.basename(_args.input))[0] _args.output = _noext + ".png" # Imports available to scripts from math import * from dimension import * +# Display a progress bar +def _progress_bar(str, progress): + if not _args.quiet: + print(str, end = ' ') + _sys.stdout.flush() + + term_width = terminal_width() + width = term_width - (len(str) + 1)%term_width + for i in range(width): + progress.wait(i/width) + print('.', end = '') + _sys.stdout.flush() + + print() + _sys.stdout.flush() + + progress.finish() + # --strict option die_on_warnings(_args.strict) -# Defaults +# Defaults/available variables +image_width = _args.width +image_height = _args.height objects = [] lights = [] camera = PerspectiveCamera() @@ -121,7 +147,7 @@ with open(_args.input) as _fh: parse_timer.complete() # Make the canvas -canvas = Canvas(width = _args.width, height = _args.height) +canvas = Canvas(width = _args.region_width, height = _args.region_height) canvas.optimize_PNG() # Make the scene object @@ -129,6 +155,10 @@ scene = Scene(canvas = canvas, objects = objects, lights = lights, camera = camera) +scene.region_x = _args.region_x +scene.region_y = _args.region_y +scene.outer_width = _args.width +scene.outer_height = _args.height scene.default_texture = default_texture scene.background = background if sky_sphere is not None: @@ -145,11 +175,11 @@ if scene.nthreads == 1: render_message = "Rendering scene" else: render_message = "Rendering scene (using %d threads)" % scene.nthreads -progress_bar(render_message, scene.raytrace_async()) +_progress_bar(render_message, scene.raytrace_async()) # Write the output file export_timer = Timer() -progress_bar("Writing %s" % _args.output, canvas.write_PNG_async(_args.output)) +_progress_bar("Writing %s" % _args.output, canvas.write_PNG_async(_args.output)) export_timer.complete() # Print execution times diff --git a/libdimension-python/dimension.pxd b/libdimension-python/dimension.pxd index 9bfe5a9..411be3f 100644 --- a/libdimension-python/dimension.pxd +++ b/libdimension-python/dimension.pxd @@ -372,10 +372,15 @@ cdef extern from "../libdimension/dimension.h": dmnsn_texture *default_texture dmnsn_interior *default_interior + dmnsn_canvas *canvas + size_t region_x + size_t region_y + size_t outer_width + size_t outer_height + dmnsn_array *objects dmnsn_array *lights dmnsn_camera *camera - dmnsn_canvas *canvas dmnsn_quality quality unsigned int reclimit diff --git a/libdimension-python/dimension.pyx b/libdimension-python/dimension.pyx index 2197edd..baafcd6 100644 --- a/libdimension-python/dimension.pyx +++ b/libdimension-python/dimension.pyx @@ -1298,6 +1298,8 @@ cdef class Scene: self._scene.canvas = canvas._canvas DMNSN_INCREF(self._scene.canvas) + self.outer_width = self._scene.canvas.width + self.outer_height = self._scene.canvas.height cdef dmnsn_object *o for obj in objects: @@ -1311,14 +1313,35 @@ cdef class Scene: DMNSN_INCREF(l) dmnsn_array_push(self._scene.lights, &l) - # Account for image dimensions in the camera - camera._camera.trans = dmnsn_matrix_mul( - camera._camera.trans, - dmnsn_scale_matrix(dmnsn_new_vector(canvas.width/canvas.height, 1.0, 1.0)) - ) self._scene.camera = camera._camera DMNSN_INCREF(self._scene.camera) + # Subregion render support + property region_x: + """The x-coordinate of the subregion in the broader image.""" + def __get__(self): + return self._scene.region_x + def __set__(self, x): + self._scene.region_x = x + property region_y: + """The y-coordinate of the subregion in the broader image.""" + def __get__(self): + return self._scene.region_y + def __set__(self, y): + self._scene.region_y = y + property outer_width: + """The width of the broader image.""" + def __get__(self): + return self._scene.outer_width + def __set__(self, width): + self._scene.outer_width = width + property outer_height: + """The height of the broader image.""" + def __get__(self): + return self._scene.outer_height + def __set__(self, height): + self._scene.outer_height = height + property default_texture: """The default Texture for objects.""" def __get__(self): @@ -1400,8 +1423,20 @@ cdef class Scene: self.raytrace_async().finish() def raytrace_async(self): """Render the scene, in the background.""" + # Account for image dimensions in the camera + # Do this here so subregion renders can tell us the broader image size + self._scene.camera.trans = dmnsn_matrix_mul( + self._scene.camera.trans, + dmnsn_scale_matrix( + dmnsn_new_vector( + self.outer_width/self.outer_height, + 1.0, + 1.0 + ) + ) + ) # Ensure the default texture is complete - cdef Texture default = Texture(Black) + cdef Texture default = Texture(pigment = Black) dmnsn_texture_cascade(default._texture, &self._scene.default_texture) return _Progress(dmnsn_raytrace_scene_async(self._scene)) diff --git a/libdimension/dimension/scene.h b/libdimension/dimension/scene.h index 969fa45..41e2360 100644 --- a/libdimension/dimension/scene.h +++ b/libdimension/dimension/scene.h @@ -48,6 +48,12 @@ typedef struct dmnsn_scene { /** Canvas. */ dmnsn_canvas *canvas; + /* Support for rendering image subregions. */ + size_t region_x; /**< The x position of the canvas in the broader image. */ + size_t region_y; /**< The y position of the canvas in the broader image. */ + size_t outer_width; /**< Width of the broader image. */ + size_t outer_height; /**< Height of the broader image. */ + /** Objects. */ dmnsn_array *objects; diff --git a/libdimension/raytrace.c b/libdimension/raytrace.c index 06878fd..cb5c8b6 100644 --- a/libdimension/raytrace.c +++ b/libdimension/raytrace.c @@ -184,8 +184,8 @@ dmnsn_raytrace_scene_concurrent(void *ptr, unsigned int thread, /* Get the ray corresponding to the (x,y)'th pixel */ dmnsn_line ray = dmnsn_camera_ray( scene->camera, - ((double)x)/(scene->canvas->width - 1), - ((double)y)/(scene->canvas->height - 1) + ((double)(x + scene->region_x))/(scene->outer_width - 1), + ((double)(y + scene->region_y))/(scene->outer_height - 1) ); /* Shoot a ray */ diff --git a/libdimension/scene.c b/libdimension/scene.c index ef597ee..f0ff451 100644 --- a/libdimension/scene.c +++ b/libdimension/scene.c @@ -37,6 +37,10 @@ dmnsn_new_scene(void) scene->default_texture = dmnsn_new_texture(); scene->default_interior = dmnsn_new_interior(); scene->canvas = NULL; + scene->region_x = 0; + scene->region_y = 0; + scene->outer_width = 0; + scene->outer_height = 0; scene->objects = dmnsn_new_array(sizeof(dmnsn_object *)); scene->lights = dmnsn_new_array(sizeof(dmnsn_light *)); scene->camera = NULL; @@ -83,6 +87,13 @@ dmnsn_initialize_scene(dmnsn_scene *scene) dmnsn_assert(!scene->initialized, "Texture double-initialized."); scene->initialized = true; + if (scene->outer_width == 0) { + scene->outer_width = scene->canvas->width; + } + if (scene->outer_height == 0) { + scene->outer_height = scene->canvas->height; + } + dmnsn_initialize_texture(scene->default_texture); if (scene->sky_sphere) { -- cgit v1.2.3