* massive structural rewrite. Split off code into several new modules (render, render_png, render_local) and updated new modules to use them.
authorTero Marttila <terom@fixme.fi>
Fri, 06 Jun 2008 18:35:46 +0300
changeset 11 082bfaf38cf0
parent 10 9daa832ab9c4
child 12 43297144f196
* massive structural rewrite. Split off code into several new modules (render, render_png, render_local) and updated new modules to use them.
* the beginnings of render_multi, really not done yet

committer: Tero Marttila <terom@fixme.fi>
Makefile
common.c
common.h
mandelbrot.c
mandelbrot.h
render.c
render.h
render_file.c
render_internal.h
render_local.c
render_local.h
render_multi.c
render_multi.h
render_node.c
render_png.c
render_png.h
render_remote.c
render_remote.h
--- a/Makefile	Fri Jun 06 16:05:26 2008 +0300
+++ b/Makefile	Fri Jun 06 18:35:46 2008 +0300
@@ -11,13 +11,15 @@
 remote_pool.o: remote_pool.c remote_pool.h
 render.o: render.c render.h
 render_remote.o: render_remote.c render_remote.h
+render_png.o: render_png.c render_png.h
+render_local.o: render_local.c render_local.h
 
 render_file.o: render_file.c
 render_node.o: render_node.c
 web_main.o: web_main.c
 
-render_file: render_file.o common.o render.o mandelbrot.o
-render_node: render_node.o common.o render.o mandelbrot.o
+render_file: render_file.o common.o render.o render_png.o render_local.o mandelbrot.o
+render_node: render_node.o common.o render.o render_png.o render_local.o mandelbrot.o
 web_main: web_main.o common.o http.o render.o remote_node.o remote_pool.o render_remote.o
 
 clean :
--- a/common.c	Fri Jun 06 16:05:26 2008 +0300
+++ b/common.c	Fri Jun 06 18:35:46 2008 +0300
@@ -54,6 +54,30 @@
     exit(EXIT_FAILURE);
 }
 
+void err_func (const char *func, const char *fmt, ...) {
+    va_list va;
+    
+    fprintf(stderr, "%s: ", func);
+
+    va_start(va, fmt);
+    vfprintf(stderr, fmt, va);
+    va_end(va);
+
+    fprintf(stderr, "\n");
+}
+
+void perr_func (const char *func, const char *fmt, ...) {
+    va_list va;
+    
+    fprintf(stderr, "%s: ", func);
+
+    va_start(va, fmt);
+    vfprintf(stderr, fmt, va);
+    va_end(va);
+
+    fprintf(stderr, ": %s\n", strerror(errno));
+}
+
 int parse_hostport (char *hostport, char **host, char **port) {
     char *c;
 
--- a/common.h	Fri Jun 06 16:05:26 2008 +0300
+++ b/common.h	Fri Jun 06 18:35:46 2008 +0300
@@ -18,6 +18,18 @@
 // fprintf + newline + exit
 void err_exit (const char *fmt, ...);
 
+// fprintf (__func__ + msg, ...)
+void err_func (const char *func, const char *fmt, ...);
+
+// fprintf (__func__ + msg + strerror, ...)
+void perr_func (const char *func, const char *fmt, ...);
+
+// error(func + colon + msg, ...) + goto error
+#define ERROR(msg) do { err_func(__func__, "%s", msg); goto error; } while (0)
+#define ERROR_FMT(fmt, ...) do { err_func(__func__, fmt, __VA_ARGS__); goto error; } while (0)
+#define PERROR(msg) do { perr_func(__func__, "%s", msg); goto error; } while (0)
+#define PERROR_FMT(fmt, ...) do { perr_func(__func__, fmt, __VA_ARGS__); goto error; } while (0)
+
 /*
  * Parse a host:port string.
  *
--- a/mandelbrot.c	Fri Jun 06 16:05:26 2008 +0300
+++ b/mandelbrot.c	Fri Jun 06 18:35:46 2008 +0300
@@ -1,9 +1,7 @@
 #include <stdlib.h>
 #include <time.h>
 
-#include <png.h>
-
-#include "render.h"
+#include "render_internal.h"
 #include "mandelbrot.h"
 #include "common.h"
 
@@ -11,97 +9,17 @@
 
 #define absdelta(a, b) (a>b ? a-b : b-a)
 
-void user_write_data(png_structp png_ptr, png_bytep data, png_size_t length) {
-    struct render_ctx *ctx = (struct render_ctx *) png_get_io_ptr(png_ptr);
-
-    if (ctx->io_write_fn)
-        switch (ctx->io_write_fn(data, length, ctx->io_cb_arg)) {
-            case RENDER_CB_ERR :
-                ctx->io_error = 1;
-                break;
-
-            case RENDER_CB_OK :
-                // great!
-                break;
-        }
-}
-
-void user_flush_data(png_structp png_ptr) {
-    struct render_ctx *ctx = (struct render_ctx *) png_get_io_ptr(png_ptr);
-
-    if (ctx->io_flush_fn)
-        switch (ctx->io_flush_fn(ctx->io_cb_arg)) {
-            case RENDER_CB_ERR :
-                ctx->io_error = 1;
-                break;
-
-            case RENDER_CB_OK :
-                // great!
-                break;
-        }
-}
-
-int mandelbrot_render (render_t *ctx) {
-    // libpng handles
-    png_structp png_ptr = NULL;
-    png_infop info_ptr = NULL;
-
+int render_mandelbrot (struct render *ctx) {
     // render algorithm vars
     u_int32_t img_x, img_y;
     double x0, y0, x, y, _x, _y, w_scale, h_scale;
     u_int8_t iter;
     u_int8_t *row;
 
-    // clear out any potential error in ctx
-    ctx->io_error = 0;
-    
     // calcluate the scale factors
     w_scale = ctx->img_w/absdelta(ctx->x1, ctx->x2);
     h_scale = ctx->img_h/absdelta(ctx->y1, ctx->y2);
     
-    // malloc the memory used to render each row
-    row = (u_int8_t *) malloc(ctx->img_w);
-
-    if (!row)
-        goto error;
-    
-    // PNG or not?
-    if (ctx->mode == RENDER_PNG) {
-        // libpng initialization
-        png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
-
-        if (!png_ptr)
-            goto error;
-
-        info_ptr = png_create_info_struct(png_ptr);
-
-        if (!info_ptr)
-            goto error;
-
-        // libpng error handling
-        if (setjmp(png_jmpbuf(png_ptr))) {
-            goto error;
-        }
-        
-        if (ctx->io_stream) {
-            // use normal libpng I/O
-            png_init_io(png_ptr, ctx->io_stream);
-        } else {
-            // setup our custom I/O callbacks
-            png_set_write_fn(png_ptr, ctx, &user_write_data, &user_flush_data);
-        }
-
-        // some PNG metadata
-        png_set_IHDR(png_ptr, info_ptr, ctx->img_w, ctx->img_h, 8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
-
-        // write out the PNG header
-        png_write_info(png_ptr, info_ptr);
-
-        // possible error return
-        if (ctx->io_error)
-            goto error;
-    }
-
     // start rendering!
     for (img_y=0; img_y < ctx->img_h; img_y++) {
         // render the current row
@@ -122,56 +40,24 @@
                 iter--;
             }
             
-            row[img_x] = iter;
+            (*ctx->local_rowbuf_addr)[img_x] = iter;
         }
-        
-        if (ctx->mode == RENDER_PNG) {
-            // write the raw pixels to libpng
-            png_write_row(png_ptr, row);
-            
-            // check for user errors return
-            if (ctx->io_error)
-                goto error;
-        } else {
-            // pass on the pixels to the io callback
-            if (ctx->io_write_fn(row, ctx->img_w, ctx->io_cb_arg) == RENDER_CB_ERR) {
-                ctx->io_error = 1;
-                goto error;
-            }
-        }
+
+        // row cb
+        if (ctx->local_row_fn(ctx->cb_arg, (*ctx->local_rowbuf_addr)))
+            ERROR("local_row_fn");
     }
    
-    if (ctx->mode == RENDER_PNG) {
-        // finished writing
-        png_write_end(png_ptr, info_ptr);
-    }
-    
-    // clean up
-    png_destroy_write_struct(&png_ptr, &info_ptr);
-    free(row); row = NULL;
-
-    // check for user errors return
-    if (ctx->io_error)
-        goto error;
-    
     // return succesfully
-    return MANDELBROT_OK;
+    return 0;
 
 error:
-    if (png_ptr || info_ptr)
-        png_destroy_write_struct(&png_ptr, &info_ptr);
-
-    if (row) 
-        free(row);
-    
-    row = NULL;
-
-    return MANDELBROT_ERR;
+    return -1;
 }
 
-int mandelbrot_render_timed (render_t *ctx, double *duration) {
+int render_mandelbrot_timed (struct render *ctx, double *duration) {
     clock_t t1 = clock();
-    int ret = mandelbrot_render(ctx);
+    int ret = render_mandelbrot(ctx);
     clock_t t2 = clock();
 
     *duration = ((double)(t2 - t1))/CLOCKS_PER_SEC;
--- a/mandelbrot.h	Fri Jun 06 16:05:26 2008 +0300
+++ b/mandelbrot.h	Fri Jun 06 18:35:46 2008 +0300
@@ -13,6 +13,6 @@
 /*
  * Render the image specified by the given render context
  */
-int mandelbrot_render (render_t *ctx);
-int mandelbrot_render_timed (render_t *ctx, double *duration);
+int render_mandelbrot (struct render *ctx);
+int render_mandelbrot_timed (struct render *ctx, double *duration);
 
--- a/render.c	Fri Jun 06 16:05:26 2008 +0300
+++ b/render.c	Fri Jun 06 18:35:46 2008 +0300
@@ -1,66 +1,91 @@
+#include <stdlib.h>
 #include <string.h>
-#include <arpa/inet.h>
 
+#include "render_internal.h"
 #include "render.h"
 
-int render_init (render_t *ctx, int mode) {
+int render_init (struct render *ctx, int mode) {
     memset(ctx, 0, sizeof(*ctx));
     
     return render_set_mode(ctx, mode);
 }
 
-int render_set_mode (render_t *ctx, int mode) {
+struct render *render_alloc () {
+    struct render *ctx;
+
+    if (!(ctx = calloc(1, sizeof(struct render))))
+        return NULL;
+
+    return ctx;
+}
+
+int render_set_mode (struct render *ctx, int mode) {
     if (mode != RENDER_RAW && mode != RENDER_PNG)
-        return RENDER_ERR;
+        return -1;
 
     ctx->mode = mode;
 
-    return RENDER_OK;
+    return 0;
 }
 
-int render_set_size (render_t *ctx, u_int32_t img_w, u_int32_t img_h) {
+int render_set_size (struct render *ctx, u_int32_t img_w, u_int32_t img_h) {
     if (img_w == 0 || img_h == 0)
-        return RENDER_ERR;
+        return -1;
 
     ctx->img_w = img_w;
     ctx->img_h = img_h;
 
-    return RENDER_OK;
+    return 0;
 }
 
-int render_region_full (render_t *ctx) {
+int render_get_size (struct render *ctx, u_int32_t *img_w, u_int32_t *img_h) {
+    *img_w = ctx->img_w;
+    *img_h = ctx->img_h;
+
+    return 0;
+}
+
+int render_region_full (struct render *ctx) {
     return render_region_raw(ctx, REGION_X1, REGION_Y1, REGION_X2, REGION_Y2);
 }
 
-int render_region_raw (render_t *ctx, double x1, double y1, double x2, double y2) {
+int render_region_raw (struct render *ctx, double x1, double y1, double x2, double y2) {
     if ((x1 >= x2) || (y1 >= y2))
-        return RENDER_ERR;
+        return -1;
 
     ctx->x1 = x1;
     ctx->y1 = y1;
     ctx->x2 = x2;
     ctx->y2 = y2;
 
-    return RENDER_OK;
+    return 0;
 }
 
-int render_io_custom (render_t *ctx, render_ctx_write_cb write_fn, render_ctx_flush_cb flush_fn, void *arg) {
+int render_io_custom (struct render *ctx, render_ctx_write_cb write_fn, render_ctx_flush_cb flush_fn, void *arg) {
     if (!write_fn)
-        return RENDER_ERR;
+        return -1;
 
     ctx->io_write_fn = write_fn;
     ctx->io_flush_fn = flush_fn;
-    ctx->io_cb_arg = arg;
+    ctx->cb_arg = arg;
 
-    return RENDER_OK;
+    return 0;
 }
 
-int render_io_stream (render_t *ctx, FILE *fh) {
+int render_io_stream (struct render *ctx, FILE *fh) {
     if (!fh)
-        return RENDER_ERR;
+        return -1;
 
     ctx->io_stream = fh;
 
-    return RENDER_OK;
+    return 0;
 }
 
+int render_local_mem (struct render *ctx, unsigned char **rowbuf_addr, render_ctx_row_cb row_fn, void *arg) {
+    ctx->local_rowbuf_addr = rowbuf_addr;
+    ctx->local_row_fn = row_fn;
+    ctx->cb_arg = arg;
+
+    return 0;
+}
+
--- a/render.h	Fri Jun 06 16:05:26 2008 +0300
+++ b/render.h	Fri Jun 06 18:35:46 2008 +0300
@@ -8,16 +8,18 @@
  * This module provides various ways to render mandelbrot images in various formats
  */
 
-// custom I/O callbacks function signatures
+/*
+ * Custom callbacks
+ *
+ * These should return non-zero on error, whereupon the rendering will be aborted, or zero on success
+ */
 typedef int (*render_ctx_write_cb)(const unsigned char *data, size_t length, void *arg);
 typedef int (*render_ctx_flush_cb)(void *arg);
+typedef int (*render_ctx_row_cb)(void *arg, unsigned char *rowbuf);
+typedef int (*render_ctx_done_cb)(void *arg);
 
-// callback return codes
-#define RENDER_CB_OK 0
-#define RENDER_CB_ERR 1
-
-#define RENDER_OK 0
-#define RENDER_ERR 1
+// include render_internal.h first if you need it
+struct render;
 
 // output types
 enum {
@@ -25,51 +27,21 @@
     RENDER_PNG,     // a png image
 };
 
-typedef struct render_ctx {
-/* render mode */
-    int mode;
-
-/* image size */
-    u_int32_t img_w;
-    u_int32_t img_h;
-
-/* mandelbrot region */
-    double      x1;
-    double      y1;
-    double      x2;
-    double      y2;
-
-/* I/O parameters */
-    // if this is non-NULL, use libpng's normal IO on this stream
-    FILE *io_stream;
-
-    // called to handle the output data
-    render_ctx_write_cb io_write_fn;
-
-    // called when the output data should be flushed - can be safely ignored if not needed
-    render_ctx_flush_cb io_flush_fn;
-
-    // the callback argument
-    void *io_cb_arg;
-
-    // error status
-    int io_error;
-} render_t;
-
 /*
- * Clear out the render context and set the mode
+ * Alloc a new render context
  */
-int render_init (render_t *ctx, int mode);
+struct render *render_alloc ();
 
 /*
  * What kind of image to render, PNG or RAW?
  */
-int render_set_mode (render_t *ctx, int mode);
+int render_set_mode (struct render *ctx, int mode);
 
 /*
  * What size of image to render
  */
-int render_set_size (render_t *ctx, u_int32_t img_w, u_int32_t img_h);
+int render_set_size (struct render *ctx, u_int32_t img_w, u_int32_t img_h);
+int render_get_size (struct render *ctx, u_int32_t *img_w, u_int32_t *img_h);
 
 /*
  * Select what region to render
@@ -81,13 +53,25 @@
 #define REGION_X2 1.0
 #define REGION_Y2 1.5
 
-int render_region_full (render_t *ctx);
-int render_region_raw (render_t *ctx, double x1, double y1, double x2, double y2);
+int render_region_full (struct render *ctx);
+int render_region_raw (struct render *ctx, double x1, double y1, double x2, double y2);
+
+/*
+ * Render raw pixel data directly to local memory.
+ *
+ * rowbuf_addr should point to a memory address that the mandelbrot will be
+ * rendered into. This memory address should have room for <img_w> bytes.
+ *
+ * row_fn will be called after each row has been written, passing in arg and
+ * *rowbuf_addr as arguments.
+ * 
+ */
+int render_local_mem (struct render *ctx, unsigned char **rowbuf_addr, render_ctx_row_cb row_fn, void *arg);
 
 /*
  * How to handle the I/O
  */
-int render_io_custom (render_t *ctx, render_ctx_write_cb write_fn, render_ctx_flush_cb flush_fn, void *arg);
-int render_io_stream (render_t *ctx, FILE *fh);
+int render_io_custom (struct render *ctx, render_ctx_write_cb write_fn, render_ctx_flush_cb flush_fn, void *arg);
+int render_io_stream (struct render *ctx, FILE *fh);
 
 #endif /* RENDER_H */
--- a/render_file.c	Fri Jun 06 16:05:26 2008 +0300
+++ b/render_file.c	Fri Jun 06 18:35:46 2008 +0300
@@ -6,27 +6,110 @@
 #include <sys/socket.h>
 #include <arpa/inet.h>
 
+#include "common.h"
 #include "render.h"
-#include "mandelbrot.h"
-#include "common.h"
+#include "render_local.h"
 
 static int verbose;
 
-void render_local (int img_w, int img_h, FILE *output) {
-    render_t ctx;
-
-    render_init(&ctx, RENDER_PNG);
-    render_set_size(&ctx, img_w, img_h);
-    render_region_full(&ctx);
-    render_io_stream(&ctx, output);
-
+int render_file_local (struct render *ctx, FILE *fh) {
     double duration;
 
-    if (mandelbrot_render_timed(&ctx, &duration) != MANDELBROT_OK)
-        err_exit("mandelbrot_render_region failed");
+    if (render_io_stream(ctx, fh))
+        ERROR("render_io_stream");
+
+    if (render_local(ctx, &duration))
+        ERROR("render_local");
+
+    if (verbose) {
+        u_int32_t img_w, img_h;
+
+        if (render_get_size(ctx, &img_w, &img_h))
+            ERROR("render_get_size");
+
+        fprintf(stdout, "rendered %dx%d mandelbrot in %f seconds\n", img_w, img_h, duration);
+    }
     
-    if (verbose) 
-        fprintf(stdout, "rendered %dx%d mandelbrot in %f seconds\n", img_w, img_h, duration);
+    return 0;
+    
+error:
+    return -1;
+}
+
+int main (int argc, char **argv) {
+    int error = 1;
+
+    struct render *ctx = NULL;
+
+    // parse arguments
+    int opt;
+    FILE *output = NULL;
+    int img_w = 256, img_h = 256;
+
+    while ((opt = getopt(argc, argv, "w:h:o:v")) != -1) {
+        switch(opt) {
+            case 'w' :
+                img_w = atoi(optarg);
+                break;
+
+            case 'h' :
+                img_h = atoi(optarg);
+                break;
+
+            case 'o' :
+                if (output)
+                    err_exit("Only use -o once");
+
+                output = fopen(optarg, "w");
+
+                if (!output)
+                    die(optarg);
+
+                break;
+
+            case 'v' :
+                verbose = 1;
+                break;
+
+
+            default :
+                err_exit("Usage: %s [-w img_w] [-h img_h] [-o output_file] [-v]", argv[0]);
+        }
+    }
+
+    if (!output)
+        output = stdout;
+    
+    // setup the struct render
+    if (!(ctx = render_alloc()))
+        ERROR("render_alloc");
+
+    if (render_set_mode(ctx, RENDER_PNG))
+        ERROR("render_set_mode");
+
+    if (render_set_size(ctx, img_w, img_h))
+        ERROR("render_set_size");
+
+    if (render_region_full(ctx))
+        ERROR("render_region_full");
+    
+    // do the render!
+    if (verbose)
+        fprintf(stderr, "Render [%dx%d] mandelbrot locally...\n", img_w, img_h);
+    
+    if (render_file_local(ctx, output))
+        ERROR("render_local");
+    
+    // success
+    error = 0;
+
+error:
+    if (output)
+        fclose(output);
+
+    free(ctx);
+
+    return error;
 }
 
 #if 0
@@ -105,74 +188,4 @@
 }
 #endif
 
-int main (int argc, char **argv) {
-    int opt;
-    
-    FILE *output = NULL /*, *remote = NULL */ ;
-    int img_w = 256, img_h = 256;
-    
-
-    while ((opt = getopt(argc, argv, "w:h:o:vr:")) != -1) {
-        switch(opt) {
-            case 'w' :
-                img_w = atoi(optarg);
-                break;
-
-            case 'h' :
-                img_h = atoi(optarg);
-                break;
-
-            case 'o' :
-                if (output)
-                    err_exit("Only use -o once");
-
-                output = fopen(optarg, "w");
-
-                if (!output)
-                    die(optarg);
-
-                break;
-
-            case 'v' :
-                verbose = 1;
-                break;
 
-#if 0
-            case 'r' :
-                if (remote)
-                    err_exit("Only use -r once");
-
-                remote = open_remote(optarg);
-
-                break;
-#endif
-
-            default :
-                err_exit("Usage: %s [-w img_w] [-h img_h] [-o output_file] [-v] [-r host[:port]]", argv[0]);
-        }
-    }
-
-    if (!output)
-        output = stdout;
-
-#if 0
-    if (remote) {
-        if (verbose)
-            fprintf(stderr, "Render [%dx%d] mandelbrot remotely\n", img_w, img_h);
-
-        render_remote(img_w, img_h, output, remote);
-
-        fclose(remote);
-    } else
-#endif    
-    {
-        if (verbose)
-            fprintf(stderr, "Render [%dx%d] mandelbrot locally\n", img_w, img_h);
-
-        render_local(img_w, img_h, output);
-    }
-
-    fclose(output);
-
-    return 0;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render_internal.h	Fri Jun 06 18:35:46 2008 +0300
@@ -0,0 +1,43 @@
+#ifdef RENDER_H
+#error render_internal.h must be included before render.h
+#endif
+
+#ifndef RENDER_INTERNAL_H
+#define RENDER_INTERNAL_H
+
+#include "render.h"
+
+struct render {
+/* render mode */
+    int mode;
+
+/* image size */
+    u_int32_t img_w;
+    u_int32_t img_h;
+
+/* mandelbrot region */
+    double      x1;
+    double      y1;
+    double      x2;
+    double      y2;
+
+/* local rendering */
+    unsigned char **local_rowbuf_addr;
+    render_ctx_row_cb local_row_fn;
+
+/* local I/O parameters */
+    // if this is non-NULL, the image data is simply fwrite():en to this stream
+    FILE *io_stream;
+
+    // called to handle the output data
+    render_ctx_write_cb io_write_fn;
+
+    // called when the output data should be flushed - can be safely ignored if not needed
+    render_ctx_flush_cb io_flush_fn;
+
+    // the callback argument for all of the above *_fn
+    void *cb_arg;
+};
+
+#endif /* RENDER_INTERNAL_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render_local.c	Fri Jun 06 18:35:46 2008 +0300
@@ -0,0 +1,43 @@
+#include <stdlib.h>
+#include <assert.h>
+
+#include "common.h"
+#include "render_internal.h"
+#include "render_local.h"
+#include "render_png.h"
+#include "mandelbrot.h"
+
+int render_local (struct render *render, double *duration) {
+    assert(render->mode == RENDER_PNG);
+
+    unsigned char *rowbuf = NULL;
+    struct render_png *png_ctx = NULL;
+
+    *duration = -1;
+
+    // alloc the memory buffer
+    if (!(rowbuf = malloc(render->img_w)))
+        ERROR("malloc");
+ 
+    // the render_png stuff
+    if (!(png_ctx = render_png_init(render)))
+        ERROR("render_png_init");
+    
+    //  set render_* to use it
+    if (render_local_mem(render, &rowbuf, (int(*)(void *arg, unsigned char *)) &render_png_row, png_ctx))
+        ERROR("render_local_mem");
+   
+    // then we can actually render
+    if (duration ? render_mandelbrot_timed(render, duration) : render_mandelbrot(render))
+        ERROR("render_mandelbrot");
+
+    // success
+    return 0;
+
+error:
+    free(rowbuf);
+    free(png_ctx);
+
+    return -1;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render_local.h	Fri Jun 06 18:35:46 2008 +0300
@@ -0,0 +1,11 @@
+#ifndef RENDER_LOCAL_H
+#define RENDER_LOCAL_H
+
+#include "render.h"
+
+/*
+ * Renders the given struct render locally in one operation.
+ */
+int render_local (struct render *render, double *duration);
+
+#endif /* RENDER_LOCAL_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render_multi.c	Fri Jun 06 18:35:46 2008 +0300
@@ -0,0 +1,143 @@
+
+
+#include "render_multi.h"
+#include "remote_node.h"
+
+
+struct render_multi {
+    // these are used as arguments to render_remote
+    struct render_sub_ctx {
+        // our offset in the list
+        int index;
+
+        // the remote_render_ctx
+        struct remote_render_ctx *remote_render;
+        
+        // a pointer to ourself
+        struct render_multi *self;
+
+        // _render_sent called for this?
+        int render_sent;
+    } remote_renders[RENDER_MULTI_NODES_MAX];
+    
+    // how many remote_renders we have
+    int remote_render_count;
+
+    // our own callbacks that we call
+    void (*cb_sent)(void *arg);
+    void (*cb_data)(struct evbuffer *buf, void *arg);
+    void (*cb_done)(void *arg);
+    void (*cb_fail)(void *arg);
+
+    void *cb_arg;
+
+};
+
+// our render_remote callbacks
+void _render_sent (void *arg) {
+    struct render_sub_ctx *ctx, *ctx2 = arg;
+
+    ctx->render_sent = 1;
+
+    // have all render_sub_ctxs been sent?
+    int i;
+    for (i = 0; i < ctx->self->remote_render_count; i++) {
+        if (!ctx->self->remote_renders[i].render_sent)
+            break;
+    }
+    
+    if (i == ctx->self->remote_render_count) {
+        // call cb_sent
+        ctx->self->cb_sent(ctx->self->cb_arg);
+    }
+}
+
+void _render_data (struct evbuffer *buf, void *arg) {
+
+}
+
+void _render_done (void *arg) {
+
+}
+
+void _render_fail (void *arg) {
+
+}
+
+#define ROUND_DOWN(dividend, divisor) ((dividend) / (divisor))
+#define ROUND_UP(dividend, divisor) (((dividend) / (divisor)) + ((dividend) % (divisor)))
+
+#define HALF(a, b) (( a + b) / 2)
+
+struct render_multi *render_multi (
+        render_t *render_ctx,               // what to render
+        struct remote_pool *render_pool,    // what render pool to use
+        void (*cb_sent)(void *arg),
+        void (*cb_data)(struct evbuffer *buf, void *arg),
+        void (*cb_done)(void *arg),
+        void (*cb_fail)(void *arg),
+        void *cb_arg
+) {
+    struct render_multi *ctx = NULL;
+    render_t r_left, r_right;
+
+    // for now, just split it in half into two render_ts
+    assert(RENDER_MULTI_NODES_MAX >= 2);
+
+    if (
+            render_init(&r_left, RENDER_RAW)
+         || render_init(&r_right, RENDER_RAW)
+         || render_set_size(&r_left, render_ctx->img_w / 2, render_ctx->img_h)
+         || render_set_size(&r_right, render_ctx->img_w / 2 +  render_ctx->img_w % 2, render_ctx->img_h)
+         || render_set_region_raw(&r_left, render_ctx->x1, render_ctx->y1, HALF(render_ctx->x1, render_ctx->x2), render_ctx->y2)
+         || render_set_region_raw(&r_right, HALF(render_ctx->x1, render_ctx->x2), render_ctx->y1, render_ctx->x2, render_ctx->y2)
+    )
+        ERROR("render_{init,set_size,set_region_raw}");
+    
+    // alloc the render_multi
+    ctx = calloc(1, sizeof(struct render_multi));
+
+    if (!ctx)
+        ERROR("calloc");
+        
+    // store the provided callback functions
+    ctx->cb_sent = cb_sent;
+    ctx->cb_data = cb_data;
+    ctx->cb_done = cb_done;
+    ctx->cb_fail = cb_fail;
+    ctx->cb_arg = cb_arg;
+
+    // pull two nodes from the pool
+    struct remote_node *node_left, *node_right;
+
+    if (
+            !(node_left = remote_pool_get(pool_info))
+         || !(node_right = remote_pool_get(pool_info))
+    )
+        ERROR("remote_pool_get");
+    
+    // init the remote_render
+    ctx->remote_renders[0].index = 0;
+    ctx->remote_renders[0].self = &ctx
+    ctx->remote_renders[1].index = 1;
+    ctx->remote_renders[1].self = &ctx
+    ctx->remote_render_count = 2;
+
+    // the two render_remote calls
+    if (
+            !(ctx->remote_renders[0].remote_render = render_remote(&r_left, node_left, 
+                &_render_sent, &_render_data, &_render_done, &_render_fail, &ctx->remote_renders[0]))
+         || !(ctx->remote_renders[1].remote_render = render_remote(&r_right, node_right,
+                &_render_sent, &_render_data, &_render_done, &_render_fail, &ctx->remote_renders[1]))
+    )
+        ERROR("render_remote");
+    
+    // I guess that's a succesfull start now
+    return 0;
+
+error:
+    free(ctx);
+
+    return -1;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render_multi.h	Fri Jun 06 18:35:46 2008 +0300
@@ -0,0 +1,30 @@
+#ifndef RENDER_MULTI_H
+#define RENDER_MULTI_H
+
+#include "remote_pool.h"
+
+/*
+ * Execute a given render_t operate across multiple remote render nodes, and aggregate the results into a single PNG stream locally
+ */
+
+struct render_multi;
+
+// how many nodes can be involved in a render operation at most
+#define RENDER_MULTI_NODES_MAX 2
+
+/*
+ * Execute the given render operation on multiple nodes from the given remote pool, streaming back the result in PNG format via the given callbacks
+ *
+ * The behaviour of the callbacks is mostly the same as for render_remote.h
+ */
+struct render_multi *render_multi (
+        render_t *render_ctx,               // what to render
+        struct remote_pool *render_pool,    // what render pool to use
+        void (*cb_sent)(void *arg),
+        void (*cb_data)(struct evbuffer *buf, void *arg),
+        void (*cb_done)(void *arg),
+        void (*cb_fail)(void *arg),
+        void *cb_arg
+);
+
+#endif /* RENDER_MULTI_H */
--- a/render_node.c	Fri Jun 06 16:05:26 2008 +0300
+++ b/render_node.c	Fri Jun 06 18:35:46 2008 +0300
@@ -7,10 +7,11 @@
 #include <string.h>
 #include <unistd.h>
 
+#include "common.h"
 #include "render.h"
 #include "render_remote.h"
+#include "render_local.h"
 #include "mandelbrot.h"
-#include "common.h"
 
 void sigpipe_handler (int signal) {
     /* ignore */
@@ -63,48 +64,57 @@
 }
 
 void handle_client (int sock) {
-    // open it as a FILE*
-    FILE *fh = fdopen(sock, "r+");
-    
-    // read the parameters
+    double duration;
+    struct render *ctx;
+    FILE *fh;
     u_int8_t mode;
     u_int32_t img_w, img_h;
     double x1, y1, x2, y2;
 
+    
+    // open it as a FILE*
+    if (!(fh = fdopen(sock, "r+")))
+        ERROR("fdopen");
+    
+    // read the parameters
     if (
-        !read_byte(fh, &mode) ||
-        !read_int(fh, &img_w) ||
-        !read_int(fh, &img_h) ||
-        !read_double(fh, &x1) ||
-        !read_double(fh, &y1) ||
-        !read_double(fh, &x2) ||
-        !read_double(fh, &y2)
-    ) {
-        error("ERR: invalid arguments");
-        fclose(fh);
-        return;
-    }
+            !read_byte(fh, &mode)
+         || !read_int(fh, &img_w)
+         || !read_int(fh, &img_h)
+         || !read_double(fh, &x1)
+         || !read_double(fh, &y1)
+         || !read_double(fh, &x2)
+         || !read_double(fh, &y2)
+    )
+        ERROR("read_{byte,int,double}");
 
-    printf("RENDER: [%ux%u] (%f, %f) -> (%f, %f): ...", img_w, img_h, x1, y1, x2, y2);
+    printf("RENDER: [%ux%u] (%f, %f) -> (%f, %f): ... ", img_w, img_h, x1, y1, x2, y2);
     fflush(stdout);
 
-    double duration;
-
     // set up the render_ctx
-    struct render_ctx ctx;
-    render_init(&ctx, RENDER_PNG);
-    render_set_size(&ctx, img_w, img_h);
-    render_region_raw(&ctx, x1, y1, x2, y2);
-    render_io_stream(&ctx, fh);
-    
-    sigpipe_ignore();
+    if (!(ctx = render_alloc()))
+        ERROR("render_alloc");
+
+    if (render_set_mode(ctx, mode))
+        ERROR("render_set_mode");
+
+    if (render_set_size(ctx, img_w, img_h))
+        ERROR("render_set_size");
+
+    if (render_region_raw(ctx, x1, y1, x2, y2))
+        ERROR("render_region_raw");
+
+    if (render_io_stream(ctx, fh))
+        ERROR("render_io_stream");
+  
     
     // render!
-    if (mandelbrot_render_timed(&ctx, &duration))
-        printf("error\n");  // XXX: notify our client?
-    else
-        printf("time=%fs\n", duration);
-    
+    if (render_local(ctx, &duration))
+        ERROR("render_local");
+
+    printf("time=%fs\n", duration);
+
+error:
     // close the FILE* and socket
     fclose(fh);
     
@@ -120,12 +130,13 @@
     // parse arguments
     int opt;
     const char *port_name = NULL;
+    unsigned short port;
 
     while ((opt = getopt(argc, argv, "l:")) != -1) {
         switch (opt) {
             case 'l':
                 if (port_name)
-                    err_exit("only specify -l once");
+                    ERROR("only specify -l once");
 
                 port_name = optarg;
                 break;
@@ -134,43 +145,48 @@
                 err_exit("Usage: %s [-l port]", argv[0]);
         }
     }
-
+    
+    // post-process arguments
     if (!port_name)
         port_name = RENDER_PORT_NAME;
 
-    unsigned short port = atoi(port_name);
-
-    if (!port)
-        err_exit("invalid port: %s", port_name);
+    if (!(port = atoi(port_name)))
+        ERROR_FMT("invalid port: %s", port_name);
 
     // create the socket
     if ((ssock = socket(PF_INET, SOCK_STREAM, 0)) == -1)
-        perr_exit("socket");
+        PERROR("socket");
 
     addr.sin_family = AF_INET;
     addr.sin_port = htons(port);
     addr.sin_addr.s_addr = INADDR_ANY;
 
     if (bind(ssock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == -1)
-        perr_exit("bind");
+        PERROR("bind");
     
     if (listen(ssock, 1) == -1)
-        perr_exit("listen");
+        PERROR("listen");
     
+    // ignore sigpipe
+    sigpipe_ignore();
+    
+    // main accept loop
     printf("RUN: %s:%hu\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
 
-    // main accept loop
     while (1) {
         addr_len = sizeof(struct sockaddr_in);
 
         // accept a new client
         if ((sock = accept(ssock, (struct sockaddr *) &addr, &addr_len)) == -1)
-            perr_exit("accept");
+            PERROR("accept");
         
         printf("ACCEPT: %s:%hu\n", inet_ntoa(addr.sin_addr), addr.sin_port);
         
         // handle their resquest
         handle_client(sock);
     }
+
+error:
+    return 1;
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render_png.c	Fri Jun 06 18:35:46 2008 +0300
@@ -0,0 +1,136 @@
+#include <stdlib.h>
+
+#include <png.h>
+
+#include "common.h"
+#include "render_internal.h"
+#include "render_png.h"
+
+struct render_png {
+    // libpng handles
+    png_structp png_ptr;
+    png_infop info_ptr;
+
+    // our render op, containing the I/O callbacks
+    struct render *render;
+};
+
+void _render_png_write(png_structp png_ptr, png_bytep data, png_size_t length) {
+    struct render_png *ctx = png_get_io_ptr(png_ptr);
+
+    if (ctx->render->io_write_fn)
+        if (ctx->render->io_write_fn(data, length, ctx->render->cb_arg)) {
+            // error, doesn't return
+            png_error(png_ptr, "_render_png_write: io_write_fn");
+        }
+}
+
+void _render_png_flush(png_structp png_ptr) {
+    struct render_png *ctx = png_get_io_ptr(png_ptr);
+    
+    if (ctx->render->io_flush_fn)
+        if (ctx->render->io_flush_fn(ctx->render->cb_arg)) {
+            // error, doesn't return
+            png_error(png_ptr, "_render_png_flush: io_flush_fn");
+        }
+}
+
+void _render_png_free (struct render_png *ctx) {
+    if (ctx)
+        png_destroy_write_struct(&ctx->png_ptr, &ctx->info_ptr);
+
+    free(ctx);
+}
+
+
+struct render_png *render_png_init (struct render *render) {
+
+    struct render_png *ctx = NULL;
+
+    // calloc the render_png
+    if (!(ctx = calloc(1, sizeof(struct render_png))))
+        ERROR("calloc");
+
+    // store the struct render
+    ctx->render = render;
+
+    // libpng initialization
+    if (!(ctx->png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)))
+        ERROR("png_create_write_struct");
+
+    if (!(ctx->info_ptr = png_create_info_struct(ctx->png_ptr)))
+        ERROR("png_create_info_struct");
+
+    // libpng error handling
+    if (setjmp(png_jmpbuf(ctx->png_ptr)))
+        ERROR("libpng");
+    
+    if (render->io_stream) {
+        // use normal libpng I/O
+        // XXX: who fcloses this?
+        png_init_io(ctx->png_ptr, render->io_stream);
+    } else {
+        // setup our custom I/O callbacks
+        png_set_write_fn(ctx->png_ptr, ctx, &_render_png_write, &_render_png_flush);
+    }
+
+    // some PNG metadata
+    png_set_IHDR(ctx->png_ptr, ctx->info_ptr, render->img_w, render->img_h, 8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+    // write out the PNG header
+    png_write_info(ctx->png_ptr, ctx->info_ptr);
+
+    // success
+    return ctx;
+
+error:
+    _render_png_free(ctx);
+    return NULL;
+}
+
+int render_png_row (struct render_png *ctx, unsigned char *rowbuf) {
+    // libpng error handling
+    if (setjmp(png_jmpbuf(ctx->png_ptr)))
+        ERROR("libpng");
+
+    // write it in
+    png_write_row(ctx->png_ptr, rowbuf);
+
+    // success
+    return 0;
+
+error:
+    _render_png_free(ctx);
+    return -1;
+}
+
+int render_png_done (struct render_png *ctx) {
+    // libpng error handling
+    if (setjmp(png_jmpbuf(ctx->png_ptr)))
+        ERROR("libpng");
+
+    // write end
+    png_write_end(ctx->png_ptr, ctx->info_ptr);
+
+    // success
+    return 0;
+
+error:
+    _render_png_free(ctx);
+    return -1;
+}
+
+int render_png_abort (struct render_png *ctx) {
+    // libpng error handling
+    if (setjmp(png_jmpbuf(ctx->png_ptr)))
+        ERROR("libpng");
+
+    _render_png_free(ctx);
+
+    // success
+    return 0;
+
+error:
+    return -1;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render_png.h	Fri Jun 06 18:35:46 2008 +0300
@@ -0,0 +1,37 @@
+#ifndef RENDER_PNG_H
+#define RENDER_PNG_H
+
+#include "render.h"
+
+/*
+ * For rendering PNG images
+ */
+struct render_png;
+
+/*
+ * Build and return a render_png that can be used to render a PNG of the given struct render.
+ *
+ * This inspects the size and io properties of the struct render.
+ *
+ * returns NULL on failure.
+ */
+struct render_png *render_png_init (struct render *render);
+
+/*
+ * Feed a full row of raw pixel data into the PNG image.
+ *
+ * buf must be struct render.img_w bytes wide.
+ */
+int render_png_row (struct render_png *ctx, unsigned char *rowbuf);
+
+/*
+ * Mark the render as complete and free the render_png, flushing out any remaining data.
+ */
+int render_png_done (struct render_png *ctx);
+
+/*
+ * Abort the render, free the render_png, and discard any remaining data.
+ */
+int render_png_abort (struct render_png *ctx);
+
+#endif /* RENDER_PNG_H */
--- a/render_remote.c	Fri Jun 06 16:05:26 2008 +0300
+++ b/render_remote.c	Fri Jun 06 18:35:46 2008 +0300
@@ -7,6 +7,7 @@
 #include <event2/event.h>
 #include <event2/bufferevent.h>
 
+#include "render_internal.h"    // for render_cmd_build
 #include "render_remote.h"
 #include "common.h"
 
@@ -14,8 +15,6 @@
     struct event *ev_conn;
     struct bufferevent *data_bev;
 
-    struct remote_node *remote_node;
-
     #pragma pack(push)
     #pragma pack(1)
 
@@ -41,33 +40,27 @@
     void *cb_arg;
 };
 
-void _remote_render_ctx_free (struct remote_render_ctx *ctx) {
-    // close the socket (ctx->ev_conn remains valid even after we're done with it...)
-    close(event_get_fd(ctx->ev_conn));
-    
-    // free the connect event
-    event_free(ctx->ev_conn);
-
+void _remote_render_ctx_free (struct remote_render_ctx **ctx) {
     // free the data_bev
-    if (ctx->data_bev) {
-        bufferevent_free(ctx->data_bev);
-        ctx->data_bev = NULL;
+    if ((*ctx)->data_bev) {
+        bufferevent_free((*ctx)->data_bev);
+        (*ctx)->data_bev = NULL;
     }
 
-    // update remote_node load
-    ctx->remote_node->current_load--;
+    // and the event
+    event_free((*ctx)->ev_conn);
     
     // free the context structure
-    free(ctx);
+    free(*ctx);
     
-    ctx = NULL;
+    *ctx = NULL;
 }
 
 #define RENDER_FAILED(ctx, desc) \
     do {                                        \
         perror(desc);                           \
         ctx->cb_fail(ctx->cb_arg);              \
-        _remote_render_ctx_free(ctx);          \
+        _remote_render_ctx_free(&ctx);          \
         return;                                 \
     } while (0)
 
@@ -125,7 +118,7 @@
     }
 
     // free resources
-    _remote_render_ctx_free(ctx);
+    _remote_render_ctx_free(&ctx);
 }
 
 void _remote_connected (int fd, short event, void *arg) {
@@ -144,19 +137,19 @@
         RENDER_FAILED(ctx, "render_remote: bufferevent_enable");
 }
 
-void render_cmd_build (render_t *rctx, struct remote_render_ctx *rrctx) {
+void render_cmd_build (struct render *render, struct remote_render_ctx *ctx) {
     // just copy over the render params to the render_cmd
-    rrctx->render_cmd.mode = rctx->mode;
-    rrctx->render_cmd.img_w = htonl(rctx->img_w);
-    rrctx->render_cmd.img_h = htonl(rctx->img_h);
-    rrctx->render_cmd.x1 = rctx->x1;
-    rrctx->render_cmd.y1 = rctx->y1;
-    rrctx->render_cmd.x2 = rctx->x2;
-    rrctx->render_cmd.y2 = rctx->y2;
+    ctx->render_cmd.mode = render->mode;
+    ctx->render_cmd.img_w = htonl(render->img_w);
+    ctx->render_cmd.img_h = htonl(render->img_h);
+    ctx->render_cmd.x1 = render->x1;
+    ctx->render_cmd.y1 = render->y1;
+    ctx->render_cmd.x2 = render->x2;
+    ctx->render_cmd.y2 = render->y2;
 }
 
 struct remote_render_ctx *render_remote (
-        render_t *render_ctx,
+        struct render *render,
         struct remote_node *remote_node,
         void (*cb_sent)(void *arg),
         void (*cb_data)(struct evbuffer *buf, void *arg),
@@ -164,15 +157,14 @@
         void (*cb_fail)(void *arg),
         void *cb_arg
 ) {    
-    printf("render_remote: remote render load: %d/%d\n", remote_node->current_load, remote_node->parallel_renders);
+    struct remote_render_ctx *ctx;
+    int sock;
+
+    printf("remote_node render load: %d/%d\n", remote_node->current_load, remote_node->parallel_renders);
 
     // alloc the remote render ctx
-    struct remote_render_ctx *ctx = malloc(sizeof(struct remote_render_ctx));
-
-    if (!ctx) {
-        error("render_remote: malloc");
-        return NULL;
-    }
+    if (!(ctx = calloc(1, sizeof(struct remote_render_ctx))))
+        ERROR("calloc");
     
     // store the provided callback functions
     ctx->cb_sent = cb_sent;
@@ -180,50 +172,30 @@
     ctx->cb_done = cb_done;
     ctx->cb_fail = cb_fail;
     ctx->cb_arg = cb_arg;
-
-    // keep a reference to remote_node so we can decr it's load
-    ctx->remote_node = remote_node;
     
     // copy the relevant stuff from the render_ctx
-    render_cmd_build(render_ctx, ctx);
+    render_cmd_build(render, ctx);
     
     // create the socket
-    int sock = socket(remote_node->addr.ss_family, SOCK_STREAM, 0);
-
-    if (sock < 0) {
-        perror("render_remote: socket");
-        goto error;
-    }
+    if ((sock = socket(remote_node->addr.ss_family, SOCK_STREAM, 0)) < 0)
+        PERROR("socket");
 
     // mark it as nonblocking
-    if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) {
-        perror("render_remote: fcntl");
-        goto error;
-    }
+    if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1)
+        PERROR("fcntl");
     
     // initiate the connect
     int err = connect(sock, (struct sockaddr *) &remote_node->addr, sizeof(remote_node->addr));
 
-    if (err != -1 || errno != EINPROGRESS) {
-        perror("render_remote: connect");
-        goto error;
-    }
+    if (err != -1 || errno != EINPROGRESS)
+        PERROR("connect");
 
     // do the libevent dance
-    ctx->ev_conn = event_new(NULL, sock, EV_WRITE, &_remote_connected, ctx);
-
-    if (!ctx->ev_conn) {
-        error("render_remote: event_new");
-        goto error;
-    }
+    if (!(ctx->ev_conn = event_new(NULL, sock, EV_WRITE, &_remote_connected, ctx)))
+        ERROR("event_new");
 
-    if (event_add(ctx->ev_conn, NULL)) {
-        error("render_remote: event_add");
-        goto error;
-    }
-
-    // update remote_node load
-    remote_node->current_load++;
+    if (event_add(ctx->ev_conn, NULL))
+        ERROR("event_add");
     
     // success
     return ctx;
@@ -259,9 +231,13 @@
     // if it's still just connecting, cancel that
     if (event_pending(ctx->ev_conn, EV_WRITE, NULL)) {
         event_del(ctx->ev_conn);
+
     }
     
+    // close the socket (ctx->ev_conn remains valid even after we're done with it...)
+    close(event_get_fd(ctx->ev_conn));
+    
     // this takes care of the rest
-    _remote_render_ctx_free (ctx);
+    _remote_render_ctx_free (&ctx);
 }
 
--- a/render_remote.h	Fri Jun 06 16:05:26 2008 +0300
+++ b/render_remote.h	Fri Jun 06 18:35:46 2008 +0300
@@ -25,9 +25,11 @@
  * cb_data is called whenever new data has been received. See also, render_remote_set_chunk_size
  * cb_done is called after our last call to cb_data (note: see render_remote_shake)
  * cb_fail is called when an error is encountered. This can (and will) happen at any time!
+ *
+ * Returns NULL on error, or a handle that can be used for cancel/etc on success
  */
 struct remote_render_ctx *render_remote (
-        render_t *render_ctx,               // what to render
+        struct render *render,              // what to render
         struct remote_node *remote_node,    // what render node to use
         void (*cb_sent)(void *arg),
         void (*cb_data)(struct evbuffer *buf, void *arg),