Index: /nanovis/branches/1.2/BMPWriter.cpp
===================================================================
--- /nanovis/branches/1.2/BMPWriter.cpp	(revision 4937)
+++ /nanovis/branches/1.2/BMPWriter.cpp	(revision 4937)
@@ -0,0 +1,352 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (c) 2004-2013  HUBzero Foundation, LLC
+ *
+ * Authors:
+ *   Wei Qiao <qiaow@purdue.edu>
+ *   Insoo Woo <iwoo@purdue.edu>
+ *   George A. Howlett <gah@purdue.edu>
+ */
+
+#ifndef USE_THREADS
+#include <unistd.h>
+#endif
+#include <cstdlib>
+#include <cstring>
+#include <cstdio>
+
+#include "nanovis.h"
+#include "BMPWriter.h"
+#ifdef USE_THREADS
+#include "ResponseQueue.h"
+#endif
+#include "Trace.h"
+
+using namespace nv;
+
+// used internally to build up the BMP file header
+// Writes an integer value into the header data structure at pos
+static inline void
+bmpHeaderAddInt(unsigned char* header, int& pos, int data)
+{
+#ifdef WORDS_BIGENDIAN
+    header[pos++] = (data >> 24) & 0xFF;
+    header[pos++] = (data >> 16) & 0xFF;
+    header[pos++] = (data >> 8)  & 0xFF;
+    header[pos++] = (data)       & 0xFF;
+#else
+    header[pos++] = data & 0xff;
+    header[pos++] = (data >> 8) & 0xff;
+    header[pos++] = (data >> 16) & 0xff;
+    header[pos++] = (data >> 24) & 0xff;
+#endif
+}
+
+/**
+ * \brief Write image data to a bitmap (BMP) file
+ *
+ * Note that this function assumes the imgData has 4 byte aligned
+ * rows (bottom to top) of RGB pixel data.
+ */
+bool
+nv::writeBMPFile(int frameNumber, const char *directoryName,
+                 unsigned char *imgData, int width, int height)
+{
+    unsigned char header[SIZEOF_BMP_HEADER];
+
+    // BE CAREFUL:  BMP files must have an even multiple of 4 bytes
+    // on each scan line.  If need be, we add padding to each line.
+    int pad = 0;
+    if ((3 * width) % 4 > 0) {
+        pad = 4 - ((3 * width) % 4);
+    }
+
+    size_t headerLength = SIZEOF_BMP_HEADER;
+    size_t pixelBytes = (3 * width + pad) * height;
+    int fsize = headerLength + pixelBytes;
+
+    // Prepare header
+    int pos = 0;
+    header[pos++] = 'B';
+    header[pos++] = 'M';
+
+    // file size in bytes
+    bmpHeaderAddInt(header, pos, fsize);
+
+    // reserved value (must be 0)
+    bmpHeaderAddInt(header, pos, 0);
+
+    // offset in bytes to start of bitmap data
+    bmpHeaderAddInt(header, pos, headerLength);
+
+    // size of the BITMAPINFOHEADER
+    bmpHeaderAddInt(header, pos, 40);
+
+    // width of the image in pixels
+    bmpHeaderAddInt(header, pos, width);
+
+    // height of the image in pixels
+    bmpHeaderAddInt(header, pos, height);
+
+    // 1 plane + (24 bits/pixel << 16)
+    bmpHeaderAddInt(header, pos, 1572865);
+
+    // no compression
+    // size of image for compression
+    bmpHeaderAddInt(header, pos, 0);
+    bmpHeaderAddInt(header, pos, 0);
+
+    // x pixels per meter
+    // y pixels per meter
+    bmpHeaderAddInt(header, pos, 0);
+    bmpHeaderAddInt(header, pos, 0);
+
+    // number of colors used (0 = compute from bits/pixel)
+    // number of important colors (0 = all colors important)
+    bmpHeaderAddInt(header, pos, 0);
+    bmpHeaderAddInt(header, pos, 0);
+
+    // BE CAREFUL: BMP format wants BGR ordering for screen data
+    unsigned char *scr = imgData;
+    for (int row = 0; row < height; row++) {
+        for (int col = 0; col < width; col++) {
+            unsigned char tmp = scr[2];
+            scr[2] = scr[0];  // B
+            scr[0] = tmp;     // R
+            scr += 3;
+        }
+        scr += pad;  // skip over padding already in screen data
+    }
+
+    bool ret = true;
+    FILE *filp;
+    char filename[100];
+    if (frameNumber >= 0) {
+        if (directoryName)
+            sprintf(filename, "%s/image%03d.bmp", directoryName, frameNumber);
+        else
+            sprintf(filename, "/tmp/flow_animation/image%03d.bmp", frameNumber);
+
+        TRACE("Writing %s", filename);
+        filp = fopen(filename, "wb");
+        if (filp == NULL) {
+            ERROR("cannot create file");
+            ret = false;
+        }
+    } else {
+        filp = fopen("/tmp/image.bmp", "wb");
+        if (filp == NULL) {
+            ERROR("cannot create file");
+            ret = false;
+        }
+    }
+    if (fwrite(header, headerLength, 1, filp) != 1) {
+        ERROR("can't write header: short write");
+        ret = false;
+    }
+    if (fwrite(imgData, pixelBytes, 1, filp) != 1) {
+        ERROR("can't write data: short write");
+        ret = false;
+    }
+    fclose(filp);
+    return ret;
+}
+
+#ifdef USE_THREADS
+
+/**
+ * \brief Write image data to a bitmap (BMP) stream
+ *
+ * Note that this function assumes the imgData has 4 byte aligned
+ * rows (bottom to top) of RGB pixel data.
+ */
+void
+nv::queueBMP(ResponseQueue *queue, const char *cmdName,
+             unsigned char *imgData, int width, int height)
+{
+    unsigned char header[SIZEOF_BMP_HEADER];
+
+    // BE CAREFUL:  BMP files must have an even multiple of 4 bytes
+    // on each scan line.  If need be, we add padding to each line.
+    int pad = 0;
+    if ((3 * width) % 4 > 0) {
+        pad = 4 - ((3 * width) % 4);
+    }
+
+    size_t headerLength = SIZEOF_BMP_HEADER;
+    size_t pixelBytes = (3 * width + pad) * height;
+    int fsize = headerLength + pixelBytes;
+
+    // Prepare header
+    int pos = 0;
+    header[pos++] = 'B';
+    header[pos++] = 'M';
+
+    // file size in bytes
+    bmpHeaderAddInt(header, pos, fsize);
+
+    // reserved value (must be 0)
+    bmpHeaderAddInt(header, pos, 0);
+
+    // offset in bytes to start of bitmap data
+    bmpHeaderAddInt(header, pos, headerLength);
+
+    // size of the BITMAPINFOHEADER
+    bmpHeaderAddInt(header, pos, 40);
+
+    // width of the image in pixels
+    bmpHeaderAddInt(header, pos, width);
+
+    // height of the image in pixels
+    bmpHeaderAddInt(header, pos, height);
+    // 1 plane + (24 bits/pixel << 16)
+    bmpHeaderAddInt(header, pos, 1572865);
+
+    // no compression
+    // size of image for compression
+    bmpHeaderAddInt(header, pos, 0);
+    bmpHeaderAddInt(header, pos, 0);
+
+    // x pixels per meter
+    // y pixels per meter
+    bmpHeaderAddInt(header, pos, 0);
+    bmpHeaderAddInt(header, pos, 0);
+
+    // number of colors used (0 = compute from bits/pixel)
+    // number of important colors (0 = all colors important)
+    bmpHeaderAddInt(header, pos, 0);
+    bmpHeaderAddInt(header, pos, 0);
+
+    char command[200];
+    sprintf(command, "%s %lu\n", cmdName,
+            (unsigned long)headerLength + pixelBytes);
+
+    size_t cmdLength;
+    cmdLength = strlen(command);
+
+    size_t length = cmdLength + headerLength + pixelBytes;
+    unsigned char *mesg = (unsigned char *)malloc(length);
+    if (mesg == NULL) {
+        ERROR("can't allocate %ld bytes for the image message", length);
+        return;
+    }
+    memcpy(mesg, command, cmdLength);
+    memcpy(mesg + cmdLength, header, headerLength);
+
+    // BE CAREFUL: BMP format wants BGR ordering for screen data
+    unsigned char *scr = imgData;
+    for (int row = 0; row < height; row++) {
+        for (int col = 0; col < width; col++) {
+            unsigned char tmp = scr[2];
+            scr[2] = scr[0];  // B
+            scr[0] = tmp;     // R
+            scr += 3;
+        }
+        scr += pad;  // skip over padding already in screen data
+    }
+
+    memcpy(mesg + cmdLength + headerLength, imgData, pixelBytes);
+
+    Response *response;
+    if (strncmp(cmdName, "nv>legend", 9) == 0) {
+        response = new Response(Response::LEGEND);
+    } else {
+        response = new Response(Response::IMAGE);
+    }
+    response->setMessage(mesg, length, Response::DYNAMIC);
+    queue->enqueue(response);
+}
+#else
+/**
+ * \brief Write image data to a bitmap (BMP) stream
+ *
+ * Note that this function assumes the imgData has 4 byte aligned
+ * rows (bottom to top) of RGB pixel data.
+ */
+void
+nv::writeBMP(int fd, const char *prefix,
+             unsigned char *imgData, int width, int height)
+{
+    unsigned char header[SIZEOF_BMP_HEADER];
+
+    // BE CAREFUL:  BMP files must have an even multiple of 4 bytes
+    // on each scan line.  If need be, we add padding to each line.
+    int pad = 0;
+    if ((3 * width) % 4 > 0) {
+        pad = 4 - ((3 * width) % 4);
+    }
+
+    size_t headerLength = SIZEOF_BMP_HEADER;
+    size_t pixelBytes = (3 * width + pad) * height;
+    int fsize = headerLength + pixelBytes;
+
+    char command[200];
+    sprintf(command, "%s %d\n", prefix, fsize);
+    if (write(fd, command, strlen(command)) != (ssize_t)strlen(command)) {
+        ERROR("Short write");
+        return;
+    }
+
+    // Prepare header
+    int pos = 0;
+    header[pos++] = 'B';
+    header[pos++] = 'M';
+
+    // file size in bytes
+    bmpHeaderAddInt(header, pos, fsize);
+
+    // reserved value (must be 0)
+    bmpHeaderAddInt(header, pos, 0);
+
+    // offset in bytes to start of bitmap data
+    bmpHeaderAddInt(header, pos, headerLength);
+
+    // size of the BITMAPINFOHEADER
+    bmpHeaderAddInt(header, pos, 40);
+
+    // width of the image in pixels
+    bmpHeaderAddInt(header, pos, width);
+
+    // height of the image in pixels
+    bmpHeaderAddInt(header, pos, height);
+
+    // 1 plane + (24 bits/pixel << 16)
+    bmpHeaderAddInt(header, pos, 1572865);
+
+    // no compression
+    // size of image for compression
+    bmpHeaderAddInt(header, pos, 0);
+    bmpHeaderAddInt(header, pos, 0);
+
+    // x pixels per meter
+    // y pixels per meter
+    bmpHeaderAddInt(header, pos, 0);
+    bmpHeaderAddInt(header, pos, 0);
+
+    // number of colors used (0 = compute from bits/pixel)
+    // number of important colors (0 = all colors important)
+    bmpHeaderAddInt(header, pos, 0);
+    bmpHeaderAddInt(header, pos, 0);
+
+    // BE CAREFUL: BMP format wants BGR ordering for screen data
+    unsigned char *scr = imgData;
+    for (int row = 0; row < height; row++) {
+        for (int col = 0; col < width; col++) {
+            unsigned char tmp = scr[2];
+            scr[2] = scr[0];  // B
+            scr[0] = tmp;     // R
+            scr += 3;
+        }
+        scr += pad;  // skip over padding already in screen data
+    }
+
+    if (write(fd, header, headerLength) != (ssize_t)headerLength) {
+        ERROR("Short write");
+        return;
+    }
+    if (write(fd, imgData, pixelBytes) != (ssize_t)pixelBytes) {
+        ERROR("Short write");
+        return;
+    }
+}
+#endif
Index: /nanovis/branches/1.2/BMPWriter.h
===================================================================
--- /nanovis/branches/1.2/BMPWriter.h	(revision 4937)
+++ /nanovis/branches/1.2/BMPWriter.h	(revision 4937)
@@ -0,0 +1,32 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2012  HUBzero Foundation, LLC
+ *
+ * Author: George A. Howlett <gah@purdue.edu>
+ */
+
+#ifndef NV_BMPWRITER_H
+#define NV_BMPWRITER_H
+
+#ifdef USE_THREADS
+#include "ResponseQueue.h"
+#endif
+
+#define SIZEOF_BMP_HEADER   54
+
+namespace nv {
+extern
+bool writeBMPFile(int frameNumber, const char *directoryName,
+                  unsigned char *imgData, int width, int height);
+
+#ifdef USE_THREADS
+extern
+void queueBMP(ResponseQueue *queue, const char *cmdName, 
+              unsigned char *data, int width, int height);
+#else
+extern
+void writeBMP(int fd, const char *cmdName, 
+              unsigned char *data, int width, int height);
+#endif
+}
+#endif
Index: /nanovis/branches/1.2/Command.cpp
===================================================================
--- /nanovis/branches/1.2/Command.cpp	(revision 4936)
+++ /nanovis/branches/1.2/Command.cpp	(revision 4937)
@@ -50,4 +50,7 @@
 #include "nanovis.h"
 #include "ReadBuffer.h"
+#ifdef USE_THREADS
+#include "ResponseQueue.h"
+#endif
 #include "Command.h"
 #include "CmdProc.h"
@@ -55,4 +58,6 @@
 #include "dxReader.h"
 #include "VtkReader.h"
+#include "BMPWriter.h"
+#include "PPMWriter.h"
 #include "Grid.h"
 #include "HeightMap.h"
@@ -108,4 +113,16 @@
 static int lastCmdStatus;
 
+#ifdef USE_THREADS
+void
+nv::queueResponse(const void *bytes, size_t len,
+                  Response::AllocationType allocType,
+                  Response::ResponseType type)
+{
+    Response *response = new Response(type);
+    response->setMessage((unsigned char *)bytes, len, allocType);
+    g_queue->enqueue(response);
+}
+#else
+
 ssize_t
 nv::SocketWrite(const void *bytes, size_t len)
@@ -123,4 +140,6 @@
     return bytesWritten;
 }
+
+#endif /*USE_THREADS*/
 
 bool
@@ -143,5 +162,4 @@
     TRACE("command %lu: '%s'", g_stats.nCommands+1, cmd.c_str());
 #endif
-
     lastCmdStatus = TCL_OK;
     result = Tcl_EvalEx(interp, Tcl_DStringValue(dsPtr),
@@ -149,5 +167,4 @@
                         TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL);
     Tcl_DStringSetLength(dsPtr, 0);
-
     if (lastCmdStatus == TCL_BREAK) {
         return TCL_BREAK;
@@ -512,5 +529,5 @@
 nv::GetDataStream(Tcl_Interp *interp, Rappture::Buffer &buf, int nBytes)
 {
-#ifdef USE_NEW_EVENT_LOOP
+#if defined(USE_NEW_EVENT_LOOP) || defined(USE_THREADS)
     if (!SocketRead((char *)buf.bytes(), nBytes)) {
         return TCL_ERROR;
@@ -687,6 +704,11 @@
     NanoVis::render();
     NanoVis::readScreen();
-
-    NanoVis::ppmWrite("nv>image -type print -bytes");
+#ifdef USE_THREADS
+    queuePPM(g_queue, "nv>image -type print -bytes",
+             NanoVis::screenBuffer, width, height);
+#else
+    writePPM(g_fdOut, "nv>image -type print -bytes",
+             NanoVis::screenBuffer, width, height);
+#endif
     NanoVis::resizeOffscreenBuffer(origWidth, origHeight);
 
@@ -1188,5 +1210,10 @@
             glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboOrig);
 
-            NanoVis::bmpWriteToFile(frameNum, dirName);
+            /* FIXME: this function requires 4-byte aligned RGB rows,
+             * but screen buffer is now 1 byte aligned for PPM images.
+             */
+            writeBMPFile(frameNum, dirName,
+                         NanoVis::screenBuffer,
+                         NanoVis::winWidth, NanoVis::winHeight);
         }
     }
@@ -1356,8 +1383,12 @@
                     volume->wAxis.min(), volume->wAxis.max(),
                     Volume::valueMin, Volume::valueMax);
+#ifdef USE_THREADS
+        queueResponse(info, cmdLength, Response::VOLATILE);
+#else
         if (SocketWrite(info, (size_t)cmdLength) != (ssize_t)cmdLength) {
             ERROR("Short write");
             return TCL_ERROR;
         }
+#endif
     }
 
@@ -2290,8 +2321,12 @@
             nBytes = ostr.length();
 
+#ifdef USE_THREADS
+            queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR);
+#else
             if (write(fdOut, oss.str().c_str(), nBytes) < 0) {
                 ERROR("write failed: %s", strerror(errno));
                 return -1;
             }
+#endif
         }
     }
@@ -2308,8 +2343,12 @@
         nBytes = ostr.length();
 
+#ifdef USE_THREADS
+        queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR);
+#else
         if (write(fdOut, ostr.c_str(), nBytes) < 0) {
             ERROR("write failed: %s", strerror(errno));
             return -1;
         }
+#endif
         clearUserMessages();
     }
Index: /nanovis/branches/1.2/Command.h
===================================================================
--- /nanovis/branches/1.2/Command.h	(revision 4936)
+++ /nanovis/branches/1.2/Command.h	(revision 4937)
@@ -16,4 +16,6 @@
 #include <tcl.h>
 
+#include "ResponseQueue.h"
+
 namespace Rappture {
 class Buffer;
@@ -25,5 +27,11 @@
 class Volume;
 
+#ifdef USE_THREADS
+extern void queueResponse(const void *bytes, size_t len,
+                          Response::AllocationType allocType,
+                          Response::ResponseType type = Response::DATA);
+#else
 extern ssize_t SocketWrite(const void *bytes, size_t len);
+#endif
 
 extern bool SocketRead(char *bytes, size_t len);
Index: /nanovis/branches/1.2/FlowCmd.cpp
===================================================================
--- /nanovis/branches/1.2/FlowCmd.cpp	(revision 4936)
+++ /nanovis/branches/1.2/FlowCmd.cpp	(revision 4937)
@@ -5,5 +5,4 @@
  */
 #include <assert.h>
-#include <errno.h>
 #define _OPEN_SYS
 #include <fcntl.h>
@@ -32,4 +31,5 @@
 #include "CmdProc.h"
 #include "Command.h"
+#include "PPMWriter.h"
 #include "FlowCmd.h"
 #include "FlowTypes.h"
@@ -232,7 +232,11 @@
             sprintf(info, "nv>data tag %s min %g max %g\n",
                     flow->name(), unirect->magMin(), unirect->magMax());
+#ifdef USE_THREADS
+        queueResponse(info, (size_t)length, Response::VOLATILE);
+#else
         if (SocketWrite(info, (size_t)length) < 0) {
             return TCL_ERROR;
         }
+#endif
     }
     Flow::updatePending = true;
@@ -1002,53 +1006,4 @@
 
 static bool
-ppmWriteToFile(const char *path, const unsigned char *imgData,
-               int width, int height)
-{
-    /* Open the named file for writing. */
-    int fdOut = creat(path, 0600);
-    if (fdOut < 0) {
-        ERROR("can't open temporary image file \"%s\": %s", path, strerror(errno));
-        return false;
-    }
-    // Generate the PPM binary file header
-    char header[200];
-#define PPM_MAXVAL 255
-    sprintf(header, "P6 %d %d %d\n", width, height, 
-        PPM_MAXVAL);
-
-    size_t header_length = strlen(header);
-    size_t wordsPerRow = (width * 24 + 31) / 32;
-    size_t bytesPerRow = wordsPerRow * 4;
-    size_t rowLength = width * 3;
-    size_t numRecords = height + 1;
-
-    struct iovec *iov;
-    iov = (struct iovec *)malloc(sizeof(struct iovec) * numRecords);
-
-    // Add the PPM image header.
-    iov[0].iov_base = header;
-    iov[0].iov_len = header_length;
-
-    // Now add the image data, reversing the order of the rows.
-    int y;
-    unsigned char *srcRowPtr = const_cast<unsigned char *>(imgData);
-    /* Reversing the pointers for the image rows.  PPM is top-to-bottom. */
-    for (y = height; y >= 1; y--) {
-        iov[y].iov_base = srcRowPtr;
-        iov[y].iov_len = rowLength;
-        srcRowPtr += bytesPerRow;
-    }
-    if (writev(fdOut, iov, numRecords) < 0) {
-        ERROR("writing image to \"%s\" failed: %s", path, strerror(errno));
-        free(iov);
-        close(fdOut);
-        return false;
-    }
-    close(fdOut);
-    free(iov);
-    return true;
-}
-
-static bool
 MakeImageFiles(char *tmpFileName,
                int width, int height, int numFrames,
@@ -1081,4 +1036,5 @@
             break;
         }
+
         NanoVis::licRenderer->convolve();
         NanoVis::advectFlows();
@@ -1094,6 +1050,6 @@
 
         sprintf(tmpFileName + length, "/image%d.ppm", i);
-        result = ppmWriteToFile(tmpFileName, NanoVis::screenBuffer,
-                                width, height);
+        result = nv::writePPMFile(tmpFileName, NanoVis::screenBuffer,
+                                  width, height);
         if (!result) {
             break;
@@ -1130,5 +1086,5 @@
     /* The ffmpeg command is
      *   ffmpeg -f image2 -i /var/tmp/xxxxx/image%d.ppm                 \
-     *      -b bitrate -f framerate /var/tmp/xxxxx/movie.mpeg 
+     *      -f outformat -b bitrate -r framerate /var/tmp/xxxxx/movie.mpeg 
      */
 #ifndef FFMPEG 
@@ -1146,10 +1102,10 @@
         return TCL_ERROR;
     }
-    Rappture::Buffer data;
+    char *data = NULL;
     size_t total = 0;
     for (;;) {
         char buffer[BUFSIZ]; 
         
-        ssize_t numRead = fread(buffer, sizeof(unsigned char), BUFSIZ, f);
+        ssize_t numRead = fread(buffer, sizeof(char), BUFSIZ, f);
         total += numRead;
         if (numRead == 0) {             // EOF
@@ -1163,5 +1119,6 @@
             return TCL_ERROR;
         }
-        if (!data.append(buffer, numRead)) {
+        data = (char *)realloc(data, total);
+        if (data == NULL) {
             ERROR("Can't append movie data to buffer %d bytes",
                   numRead);
@@ -1170,12 +1127,14 @@
             return TCL_ERROR;
         }
-    }
-    if (data.size() == 0) {
+        memcpy(data + (total - numRead), buffer, numRead);
+    }
+    if (total == 0) {
         ERROR("ffmpeg returned 0 bytes");
     }
     // Send zero length to client so it can deal with error
     sprintf(cmd,"nv>image -type movie -token \"%s\" -bytes %lu\n", 
-            token, (unsigned long)data.size());
-    nv::sendDataToClient(cmd, data.bytes(), data.size());
+            token, total);
+    // Memory for data payload is freed by this call
+    nv::sendDataToClient(cmd, data, total);
     return TCL_OK;
 }
Index: /nanovis/branches/1.2/Makefile.in
===================================================================
--- /nanovis/branches/1.2/Makefile.in	(revision 4936)
+++ /nanovis/branches/1.2/Makefile.in	(revision 4937)
@@ -1,3 +1,4 @@
 TRACE			= #yes
+USE_THREADS		= yes
 USE_POINTSET_RENDERER	= #yes
 USE_PARTICLE_SYSTEM	= #yes
@@ -62,5 +63,5 @@
 LD_RUN_PATH     = $(libdir):@LD_RPATH@
 
-SVN_VERSION	= $(shell svnversion $(srcdir))
+SVN_VERSION	= $(shell svnversion $(srcdir) | sed 's/Unversioned directory/unknown/')
 STATSDIR	= @STATSDIR@
 
@@ -97,4 +98,7 @@
 DEFINES		+= -DWANT_TRACE
 endif
+ifdef USE_THREADS
+DEFINES		+= -DUSE_THREADS
+endif
 ifdef USE_POINTSET_RENDERER
 DEFINES         += -DUSE_POINTSET_RENDERER
@@ -104,4 +108,5 @@
 OBJS		= \
 		Axis.o \
+		BMPWriter.o \
 		Camera.o \
 		Chain.o \
@@ -124,8 +129,10 @@
 		Plane.o \
 		PlaneRenderer.o \
+		PPMWriter.o \
 		ReadBuffer.o \
 		ReaderCommon.o \
 		RegularVolumeShader.o \
 		RenderVertexArray.o \
+		ResponseQueue.o \
 		Shader.o \
 		StdVertexShader.o \
@@ -271,4 +278,5 @@
 
 Axis.o: Axis.cpp Axis.h Chain.h
+BMPWriter.o: BMPWriter.cpp BMPWriter.h nanovis.h
 BucketSort.o: BucketSort.cpp BucketSort.h $(VRMATH_DIR)/include/vrmath/Vector3f.h $(VRMATH_DIR)/include/vrmath/Vector4f.h $(VRMATH_DIR)/include/vrmath/Matrix4x4d.h PCASplit.h
 Camera.o: Camera.cpp Camera.h config.h $(VRMATH_DIR)/include/vrmath/Matrix4x4d.h
@@ -276,10 +284,10 @@
 CmdProc.o: CmdProc.cpp CmdProc.h
 ColorTableShader.o: ColorTableShader.cpp ColorTableShader.h Shader.h
-Command.o: Command.cpp nanovis.h nanovisServer.h ReadBuffer.h config.h define.h FlowCmd.h CmdProc.h Trace.h PlaneRenderer.h dxReader.h Grid.h HeightMap.h Camera.h ZincBlendeReconstructor.h Unirect.h Volume.h VolumeRenderer.h
+Command.o: Command.cpp nanovis.h nanovisServer.h ReadBuffer.h ResponseQueue.h config.h define.h FlowCmd.h CmdProc.h Trace.h PlaneRenderer.h PPMWriter.h BMPWriter.h dxReader.h Grid.h HeightMap.h Camera.h ZincBlendeReconstructor.h Unirect.h Volume.h VolumeRenderer.h
 ContourLineFilter.o: ContourLineFilter.cpp ContourLineFilter.h
 ConvexPolygon.o: ConvexPolygon.cpp ConvexPolygon.h $(VRMATH_DIR)/include/vrmath/Vector4f.h $(VRMATH_DIR)/include/vrmath/Matrix4x4d.h Plane.h
 Flow.o: Flow.cpp Flow.h FlowCmd.h FlowTypes.h FlowBox.h FlowParticles.h LIC.h VelocityArrowsSlice.h Switch.h Unirect.h Volume.h TransferFunction.h Trace.h
 FlowBox.o: FlowBox.cpp FlowBox.h FlowTypes.h Switch.h Trace.h Volume.h $(VRMATH_DIR)/include/vrmath/Vector3f.h $(VRMATH_DIR)/include/vrmath/Vector4f.h $(VRMATH_DIR)/include/vrmath/Matrix4x4d.h
-<FlowCmd.o: FlowCmd.cpp FlowCmd.h FlowParticles.h FlowBox.h FlowTypes.h Command.h Switch.h Trace.h TransferFunction.h nanovis.h nanovisServer.h CmdProc.h LIC.h Unirect.h Volume.h VelocityArrowsSlice.h $(VRMATH_DIR)/include/vrmath/Vector3f.h
+FlowCmd.o: FlowCmd.cpp FlowCmd.h FlowParticles.h FlowBox.h FlowTypes.h Command.h PPMWriter.h Switch.h Trace.h TransferFunction.h nanovis.h nanovisServer.h CmdProc.h LIC.h Unirect.h Volume.h VelocityArrowsSlice.h $(VRMATH_DIR)/include/vrmath/Vector3f.h
 FlowParticles.o: FlowParticles.cpp FlowParticles.h FlowTypes.h FlowCmd.h Switch.h Trace.h ParticleRenderer.h Volume.h $(VRMATH_DIR)/include/vrmath/Vector3f.h $(VRMATH_DIR)/include/vrmath/Vector4f.h
 GradientFilter.o: GradientFilter.cpp GradientFilter.h
@@ -299,4 +307,5 @@
 PointSetRenderer.o: PointSetRenderer.cpp PointSetRenderer.h
 PointShader.o: PointShader.cpp PointShader.h
+PPMWriter.o: PPMWriter.cpp PPMWriter.h
 ReadBuffer.o: ReadBuffer.cpp ReadBuffer.h Trace.h
 ReaderCommon.o: ReaderCommon.cpp ReaderCommon.h GradientFilter.h $(VRMATH_DIR)/include/vrmath/Vector3f.h
@@ -324,3 +333,3 @@
 md5.o: md5.h
 nanovis.o: nanovis.cpp nanovis.h nanovisServer.h config.h define.h Command.h Flow.h Grid.h HeightMap.h Camera.h LIC.h ZincBlendeReconstructor.h PerfQuery.h PlaneRenderer.h PointSetRenderer.h PointSet.h Switch.h Trace.h Unirect.h VelocityArrowsSlice.h VolumeInterpolator.h VolumeRenderer.h ZincBlendeVolume.h Axis.h Chain.h $(UTIL_DIR)/Fonts.h
-nanovisServer.o: nanovisServer.cpp nanovisServer.h config.h nanovis.h define.h Command.h ReadBuffer.h Shader.h Trace.h
+nanovisServer.o: nanovisServer.cpp nanovisServer.h config.h nanovis.h define.h Command.h PPMWriter.h ReadBuffer.h Shader.h ResponseQueue.h Trace.h
Index: /nanovis/branches/1.2/PPMWriter.cpp
===================================================================
--- /nanovis/branches/1.2/PPMWriter.cpp	(revision 4937)
+++ /nanovis/branches/1.2/PPMWriter.cpp	(revision 4937)
@@ -0,0 +1,215 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2012  HUBzero Foundation, LLC
+ *
+ * Author: George A. Howlett <gah@purdue.edu>
+ */
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cerrno>
+#include <sys/uio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "Trace.h"
+#include "PPMWriter.h"
+#ifdef USE_THREADS
+#include "ResponseQueue.h"
+#endif
+
+using namespace nv;
+
+#define PPM_MAXVAL 255
+
+#ifdef USE_THREADS
+
+/**
+ * \brief Writes image data as PPM binary data to the client.
+ *
+ * The PPM binary format is very simple.
+ *
+ *     P6 w h 255\n
+ *     3-byte RGB pixel data.
+ *
+ * The client (using the TkImg library) will do less work to unpack this 
+ * format, as opposed to BMP or PNG.
+ *
+ * Note that currently the image data has bottom to top scanlines.  This 
+ * routine could be made even simpler (faster) if the image data had top 
+ * to bottom scanlines.
+ *
+ * \param[in] queue Pointer to ResponseQueue to write to
+ * \param[in] cmdName Command name to send (byte length will be appended)
+ * \param[in] data Image data
+ * \param[in] width Width of image in pixels
+ * \param[in] height Height of image in pixels
+ */
+void
+nv::queuePPM(ResponseQueue *queue, const char *cmdName, 
+             const unsigned char *data, int width, int height)
+{
+    char header[200];
+
+    TRACE("Entering (%dx%d)\n", width, height);
+    // Generate the PPM binary file header
+    snprintf(header, sizeof(header), "P6 %d %d %d\n", width, height, 
+             PPM_MAXVAL);
+
+    size_t headerLength = strlen(header);
+    size_t dataLength = width * height * 3;
+
+    char command[200];
+    snprintf(command, sizeof(command), "%s %lu\n", cmdName, 
+             (unsigned long)headerLength + dataLength);
+
+    size_t cmdLength;
+    cmdLength = strlen(command);
+
+    size_t length = cmdLength + headerLength + dataLength;
+    unsigned char *mesg = (unsigned char *)malloc(length);
+    if (mesg == NULL) {
+        ERROR("can't allocate %ld bytes for the image message", length);
+        return;
+    }
+    memcpy(mesg, command, cmdLength);
+    memcpy(mesg + cmdLength, header, headerLength);
+
+    size_t bytesPerRow = width * 3;
+    unsigned char *destRowPtr = mesg + length - bytesPerRow;
+    unsigned char *srcRowPtr = const_cast<unsigned char *>(data);
+    for (int y = 0; y < height; y++) {
+        memcpy(destRowPtr, srcRowPtr, bytesPerRow);
+        srcRowPtr += bytesPerRow;
+        destRowPtr -= bytesPerRow;
+    }
+
+    Response *response;
+    if (strncmp(cmdName, "nv>legend", 9) == 0) {
+        response = new Response(Response::LEGEND);
+    } else {
+        response = new Response(Response::IMAGE);
+    }
+    response->setMessage(mesg, length, Response::DYNAMIC);
+    queue->enqueue(response);
+    TRACE("Leaving (%dx%d)\n", width, height);
+}
+#else
+
+/**
+ * \brief Writes image data as PPM binary data to the client.
+ *
+ * The PPM binary format is very simple.
+ *
+ *     P6 w h 255\n
+ *     3-byte RGB pixel data.
+ *
+ * The client (using the TkImg library) will do less work to unpack this 
+ * format, as opposed to BMP or PNG.
+ *
+ * Note that currently the image data has bottom to top scanlines.  This 
+ * routine could be made even simpler (faster) if the image data had top 
+ * to bottom scanlines.
+ *
+ * \param[in] fd File descriptor that will be written to
+ * \param[in] cmdName Command name to send (byte length will be appended)
+ * \param[in] data Image data
+ * \param[in] width Width of image in pixels
+ * \param[in] height Height of image in pixels
+ */
+void
+nv::writePPM(int fd, const char *cmdName, 
+             const unsigned char *data, int width, int height)
+{
+    TRACE("Entering (%dx%d)\n", width, height);
+
+    // Generate the PPM binary file header
+    char header[200];
+    snprintf(header, sizeof(header), "P6 %d %d %d\n", width, height,
+             PPM_MAXVAL);
+
+    size_t headerLength = strlen(header);
+    size_t dataLength = width * height * 3;
+
+    char command[200];
+    snprintf(command, sizeof(command), "%s %lu\n", cmdName, 
+             (unsigned long)headerLength + dataLength);
+
+    size_t numRecords = height + 2;
+    struct iovec *iov = (struct iovec *)malloc(sizeof(struct iovec) * numRecords);
+
+    // Write the command, then the image header and data.
+    // Command
+    iov[0].iov_base = command;
+    iov[0].iov_len = strlen(command);
+    // Header of image data
+    iov[1].iov_base = header;
+    iov[1].iov_len = headerLength;
+
+    // Image data
+    size_t bytesPerRow = width * 3;
+    size_t rowLength = width * 3;
+    unsigned char *srcRowPtr = const_cast<unsigned char *>(data);
+    for (int y = height + 1; y >= 2; y--) {
+        iov[y].iov_base = srcRowPtr;
+        iov[y].iov_len = rowLength;
+        srcRowPtr += bytesPerRow;
+    }
+    if (writev(fd, iov, numRecords) < 0) {
+        ERROR("write failed: %s\n", strerror(errno));
+    }
+    free(iov);
+
+    TRACE("Leaving (%dx%d)\n", width, height);
+}
+
+#endif /*USE_THREADS*/
+
+bool
+nv::writePPMFile(const char *path,
+                 const unsigned char *data, int width, int height)
+{
+    // Open the named file for writing.
+    int fd = creat(path, 0600);
+    if (fd < 0) {
+        ERROR("Can't open temporary image file \"%s\": %s", path, strerror(errno));
+        return false;
+    }
+
+    // Generate the PPM binary file header
+    char header[200];
+    snprintf(header, sizeof(header), "P6 %d %d %d\n", width, height,
+             PPM_MAXVAL);
+
+    size_t headerLength = strlen(header);
+
+    size_t numRecords = height + 1;
+    struct iovec *iov = (struct iovec *)malloc(sizeof(struct iovec) * numRecords);
+
+    // Add the PPM image header.
+    iov[0].iov_base = header;
+    iov[0].iov_len = headerLength;
+
+    // Now add the image data, reversing the order of the rows.  PPM is top-to-bottom.
+    //size_t wordsPerRow = (width * 24 + 31) / 32;
+    //size_t bytesPerRow = wordsPerRow * 4;
+    size_t bytesPerRow = width * 3;
+    size_t rowLength = width * 3;
+    unsigned char *srcRowPtr = const_cast<unsigned char *>(data);
+    for (int y = height; y >= 1; y--) {
+        iov[y].iov_base = srcRowPtr;
+        iov[y].iov_len = rowLength;
+        srcRowPtr += bytesPerRow;
+    }
+    bool ret = true;
+    if (writev(fd, iov, numRecords) < 0) {
+        ERROR("writing image to \"%s\" failed: %s\n", path, strerror(errno));
+        ret = false;
+    }
+    close(fd);
+    free(iov);
+    return ret;
+}
Index: /nanovis/branches/1.2/PPMWriter.h
===================================================================
--- /nanovis/branches/1.2/PPMWriter.h	(revision 4937)
+++ /nanovis/branches/1.2/PPMWriter.h	(revision 4937)
@@ -0,0 +1,30 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2012  HUBzero Foundation, LLC
+ *
+ * Author: George A. Howlett <gah@purdue.edu>
+ */
+
+#ifndef NV_PPMWRITER_H
+#define NV_PPMWRITER_H
+
+#ifdef USE_THREADS
+#include "ResponseQueue.h"
+#endif
+
+namespace nv {
+extern
+bool writePPMFile(const char *path,
+                  const unsigned char *imgData,
+                  int width, int height);
+#ifdef USE_THREADS
+extern
+void queuePPM(ResponseQueue *queue, const char *cmdName, 
+              const unsigned char *data, int width, int height);
+#else
+extern
+void writePPM(int fd, const char *cmdName, 
+              const unsigned char *data, int width, int height);
+#endif
+}
+#endif
Index: /nanovis/branches/1.2/ResponseQueue.cpp
===================================================================
--- /nanovis/branches/1.2/ResponseQueue.cpp	(revision 4937)
+++ /nanovis/branches/1.2/ResponseQueue.cpp	(revision 4937)
@@ -0,0 +1,109 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2012  HUBzero Foundation, LLC
+ *
+ * Author: George A. Howlett <gah@purdue.edu>
+ */
+
+#include <pthread.h>
+#include <semaphore.h>
+#include <cerrno>
+#include <cstring>
+#include <cstdlib>
+#include <list>
+
+#include "Trace.h"
+#include "ResponseQueue.h"
+
+using namespace nv;
+
+ResponseQueue::ResponseQueue()
+{
+    pthread_mutex_init(&_idle, NULL);
+    if (sem_init(&_ready, 0, 0) < 0) {
+        ERROR("can't initialize semaphore: %s", strerror(errno));
+    }
+}
+
+ResponseQueue::~ResponseQueue() 
+{
+    TRACE("Deleting ResponseQueue");
+    pthread_mutex_destroy(&_idle);
+    if (sem_destroy(&_ready) < 0) {
+        ERROR("can't destroy semaphore: %s", strerror(errno));
+    }
+    for (std::list<Response *>::iterator itr = _list.begin();
+         itr != _list.end(); ++itr) {
+        delete *itr;
+    }
+    _list.clear();
+}
+
+void
+ResponseQueue::enqueue(Response *response)
+{
+    if (pthread_mutex_lock(&_idle) != 0) {
+        ERROR("can't lock mutex: %s", strerror(errno));
+    }	
+    /* Examine the list and remove any queued responses of the same type. */
+    TRACE("Before # of elements is %d", _list.size());
+    for (std::list<Response *>::iterator itr = _list.begin();
+         itr != _list.end();) {
+        /* Remove any duplicate image or legend responses. There should be no 
+         * more than one.  Note that if the client starts using multiple legends 
+         * for different fields, this optimization should be disabled for legends.
+         * We may also need to differentiate between screen images and "hardcopy"
+         * images.
+         */
+        if ((response->type() == Response::IMAGE ||
+             response->type() == Response::LEGEND) &&
+            (*itr)->type() == response->type()) {
+            TRACE("Removing duplicate response of type: %d", (*itr)->type());
+            delete *itr;
+            itr = _list.erase(itr);
+        } else {
+            TRACE("Found queued response of type %d", (*itr)->type());
+            ++itr;
+        }
+    }
+    /* Add the new response to the end of the list. */
+    _list.push_back(response);
+    TRACE("After # of elements is %d", _list.size());
+    if (sem_post(&_ready) < 0) {
+        ERROR("can't post semaphore: %s", strerror(errno));
+    }
+    if (pthread_mutex_unlock(&_idle) != 0) {
+        ERROR("can't unlock mutex: %s", strerror(errno));
+    }	
+}
+
+Response *
+ResponseQueue::dequeue()
+{
+    Response *response = NULL;
+
+    int ret;
+    while ((ret = sem_wait(&_ready)) < 0 && errno == EINTR)
+        continue;
+    if (ret < 0) {
+        ERROR("can't wait on semaphore: %s", strerror(errno));
+    }
+    if (pthread_mutex_lock(&_idle) != 0) {
+        ERROR("can't lock mutex: %s", strerror(errno));
+    }
+    /* List may be empty if semaphore value was incremented beyond list
+     * size in the case of an enqueue operation deleting from the queue
+     * This is not an error, so just unlock and return to wait loop
+     */
+    if (_list.empty()) {
+        TRACE("Empty queue");
+    } else {
+        response = _list.front();
+        _list.pop_front();
+        TRACE("Dequeued response of type %d", response->type());
+    }
+    if (pthread_mutex_unlock(&_idle) != 0) {
+        ERROR("can't unlock mutex: %s", strerror(errno));
+    }	
+    return response;
+}
Index: /nanovis/branches/1.2/ResponseQueue.h
===================================================================
--- /nanovis/branches/1.2/ResponseQueue.h	(revision 4937)
+++ /nanovis/branches/1.2/ResponseQueue.h	(revision 4937)
@@ -0,0 +1,130 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2012  HUBzero Foundation, LLC
+ *
+ * Author: George A. Howlett <gah@purdue.edu>
+ */
+#ifndef NV_RESPONSE_QUEUE_H
+#define NV_RESPONSE_QUEUE_H
+
+#include <pthread.h>
+#include <semaphore.h>
+#include <cstdlib>
+#include <cstring>
+#include <list>
+
+namespace nv {
+
+/**
+ * \brief Holds data for a response to be sent to client
+ */
+class Response
+{
+public:
+    enum AllocationType {
+        STATIC,
+        DYNAMIC,
+        VOLATILE
+    };
+    enum ResponseType {
+        IMAGE,          /**< Image to be displayed. */
+        LEGEND,         /**< Legend to be displayed. */
+        DATA,           /**< Any other type of non-error message. */
+        ERROR,          /**< Error message. */
+        OK              /**< Ack for client when no render required */
+    };
+
+    Response(ResponseType type) :
+        _mesg(NULL),
+        _length(0),
+        _type(type)
+    {
+    }
+
+    virtual ~Response()
+    {
+	if ((_length > 0) && (_mesg != NULL) && (_allocType == DYNAMIC)) {
+            free(_mesg);
+	}
+    }
+
+    /// Get the ResponseType
+    ResponseType type()
+    {
+	return _type;
+    }
+
+    /// Get the Response data
+    unsigned char *message()
+    {
+	return _mesg;
+    }
+
+    /// Get the number of bytes in the Response data
+    size_t length()
+    {
+	return _length;
+    }
+
+    /// Set the message/data making up the Response
+    /**
+     * If the AllocationType is DYNAMIC, the message data will be free()d
+     * by the destructor.  If the AllocationType is VOLATILE, a copy of 
+     * the message data will be made.
+     *
+     * \param[in] mesg The Response data, can be a command and/or binary data
+     * \param[in] length The number of bytes in mesg
+     * \param[in] type Specify how the memory was allocated for mesg
+     */ 
+    void setMessage(unsigned char *mesg, size_t length, AllocationType type)
+    {
+	if (type == VOLATILE) {
+	    _length = length;
+	    _mesg = (unsigned char *)malloc(length);
+	    memcpy(_mesg, mesg, length);
+	    _allocType = DYNAMIC;
+	} else {
+	    _length = length;
+	    _mesg = mesg;
+	    _allocType = type;
+	}
+    }
+
+private:
+    /** 
+     * (Malloc-ed by caller, freed by destructor)
+     * Contains the message/bytes to be sent to the client. */
+    unsigned char *_mesg;
+    size_t _length;       /**< # of bytes in the above message. */
+    ResponseType _type;
+    AllocationType _allocType;
+};
+
+/**
+ * \brief Queue to hold pending Responses to be sent to the client
+ *
+ * A semaphore and mutex are used to control access to the 
+ * queue by a reader and writer thread
+ */
+class ResponseQueue
+{
+public:
+    ResponseQueue();
+
+    virtual ~ResponseQueue();
+
+    /// Add a response to the end of the queue
+    void enqueue(Response *response);
+
+    /// Remove a response from the front of the queue
+    Response *dequeue();
+
+private:
+    pthread_mutex_t _idle;
+    sem_t _ready; /**< Semaphore indicating that a response has been queued. */
+    std::list<Response *> _list;
+};
+
+}
+
+#endif
Index: /nanovis/branches/1.2/configure
===================================================================
--- /nanovis/branches/1.2/configure	(revision 4936)
+++ /nanovis/branches/1.2/configure	(revision 4937)
@@ -1,5 +1,5 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for nanovis 1.1.
+# Generated by GNU Autoconf 2.69 for nanovis 1.2.
 #
 # Report bugs to <rappture@nanohub.org>.
@@ -581,6 +581,6 @@
 PACKAGE_NAME='nanovis'
 PACKAGE_TARNAME='nanovis'
-PACKAGE_VERSION='1.1'
-PACKAGE_STRING='nanovis 1.1'
+PACKAGE_VERSION='1.2'
+PACKAGE_STRING='nanovis 1.2'
 PACKAGE_BUGREPORT='rappture@nanohub.org'
 PACKAGE_URL=''
@@ -1256,5 +1256,5 @@
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures nanovis 1.1 to adapt to many kinds of systems.
+\`configure' configures nanovis 1.2 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1317,5 +1317,5 @@
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of nanovis 1.1:";;
+     short | recursive ) echo "Configuration of nanovis 1.2:";;
    esac
   cat <<\_ACEOF
@@ -1411,5 +1411,5 @@
 if $ac_init_version; then
   cat <<\_ACEOF
-nanovis configure 1.1
+nanovis configure 1.2
 generated by GNU Autoconf 2.69
 
@@ -1855,5 +1855,5 @@
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by nanovis $as_me 1.1, which was
+It was created by nanovis $as_me 1.2, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
@@ -5506,5 +5506,5 @@
 # values after options handling.
 ac_log="
-This file was extended by nanovis $as_me 1.1, which was
+This file was extended by nanovis $as_me 1.2, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
@@ -5568,5 +5568,5 @@
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-nanovis config.status 1.1
+nanovis config.status 1.2
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
Index: /nanovis/branches/1.2/configure.in
===================================================================
--- /nanovis/branches/1.2/configure.in	(revision 4936)
+++ /nanovis/branches/1.2/configure.in	(revision 4937)
@@ -1,4 +1,4 @@
 AC_PREREQ([2.60])
-AC_INIT([nanovis], [1.1], [rappture@nanohub.org])
+AC_INIT([nanovis], [1.2], [rappture@nanohub.org])
 AC_CONFIG_AUX_DIR(cf)
 AC_CONFIG_HEADERS([nvconf.h])
Index: /nanovis/branches/1.2/nanovis.cpp
===================================================================
--- /nanovis/branches/1.2/nanovis.cpp	(revision 4936)
+++ /nanovis/branches/1.2/nanovis.cpp	(revision 4937)
@@ -17,16 +17,6 @@
 
 #include <sys/time.h>
-#include <sys/types.h>
-#include <sys/uio.h> // for writev in ppmWrite
-#include <unistd.h>
-
 #include <cassert>
-#include <cerrno>
-#include <cstdlib>
 #include <cstdio>
-#include <cstring>
-#include <cmath>
-
-#include <string>
 
 #include <GL/glew.h>
@@ -54,4 +44,5 @@
 #include "Shader.h"
 #include "PlaneRenderer.h"
+#include "PPMWriter.h"
 #include "Texture2D.h"
 #include "Trace.h"
@@ -67,6 +58,4 @@
 using namespace nv::util;
 using namespace vrmath;
-
-#define SIZEOF_BMP_HEADER   54
 
 // STATIC MEMBER DATA
@@ -332,5 +321,5 @@
     planeRenderer->render();
 
-    //glPixelStorei(GL_PACK_ALIGNMENT, 1);
+    glPixelStorei(GL_PACK_ALIGNMENT, 1);
     glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, screenBuffer);
     {
@@ -339,5 +328,9 @@
         TRACE("Sending ppm legend image %s min:%g max:%g", volArg, min, max);
         sprintf(prefix, "nv>legend %s %g %g", volArg, min, max);
-        ppmWrite(prefix);
+#ifdef USE_THREADS
+        queuePPM(g_queue, prefix, screenBuffer, width, height);
+#else
+        writePPM(g_fdOut, prefix, screenBuffer, width, height);
+#endif
     }
     planeRenderer->removePlane(index);
@@ -361,9 +354,6 @@
     glGenTextures(1, &_finalColorTex);
     glBindTexture(GL_TEXTURE_2D, _finalColorTex);
-
-    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
-
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 #if defined(HAVE_FLOAT_TEXTURES) && defined(USE_HALF_FLOAT)
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, winWidth, winHeight, 0,
@@ -418,5 +408,5 @@
     }
 
-    screenBuffer = new unsigned char[4*winWidth*winHeight];
+    screenBuffer = new unsigned char[3*winWidth*winHeight];
     assert(screenBuffer != NULL);
 
@@ -435,9 +425,6 @@
     glGenTextures(1, &_finalColorTex);
     glBindTexture(GL_TEXTURE_2D, _finalColorTex);
-
-    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
-
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 #if defined(HAVE_FLOAT_TEXTURES) && defined(USE_HALF_FLOAT)
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, winWidth, winHeight, 0,
@@ -557,5 +544,5 @@
         screenBuffer = NULL;
     }
-    screenBuffer = new unsigned char[4*winWidth*winHeight];
+    screenBuffer = new unsigned char[3*winWidth*winHeight];
     assert(screenBuffer != NULL);
 
@@ -609,260 +596,4 @@
 
     return true;
-}
-
-// used internally to build up the BMP file header
-// Writes an integer value into the header data structure at pos
-static inline void
-bmpHeaderAddInt(unsigned char* header, int& pos, int data)
-{
-#ifdef WORDS_BIGENDIAN
-    header[pos++] = (data >> 24) & 0xFF;
-    header[pos++] = (data >> 16) & 0xFF;
-    header[pos++] = (data >> 8)  & 0xFF;
-    header[pos++] = (data)       & 0xFF;
-#else
-    header[pos++] = data & 0xff;
-    header[pos++] = (data >> 8) & 0xff;
-    header[pos++] = (data >> 16) & 0xff;
-    header[pos++] = (data >> 24) & 0xff;
-#endif
-}
-
-void
-NanoVis::bmpWriteToFile(int frame_number, const char *directory_name)
-{
-    unsigned char header[SIZEOF_BMP_HEADER];
-    int pos = 0;
-    header[pos++] = 'B';
-    header[pos++] = 'M';
-
-    // BE CAREFUL:  BMP files must have an even multiple of 4 bytes
-    // on each scan line.  If need be, we add padding to each line.
-    int pad = 0;
-    if ((3*winWidth) % 4 > 0) {
-        pad = 4 - ((3*winWidth) % 4);
-    }
-
-    // file size in bytes
-    int fsize = (3*winWidth+pad)*winHeight + SIZEOF_BMP_HEADER;
-    bmpHeaderAddInt(header, pos, fsize);
-
-    // reserved value (must be 0)
-    bmpHeaderAddInt(header, pos, 0);
-
-    // offset in bytes to start of bitmap data
-    bmpHeaderAddInt(header, pos, SIZEOF_BMP_HEADER);
-
-    // size of the BITMAPINFOHEADER
-    bmpHeaderAddInt(header, pos, 40);
-
-    // width of the image in pixels
-    bmpHeaderAddInt(header, pos, winWidth);
-
-    // height of the image in pixels
-    bmpHeaderAddInt(header, pos, winHeight);
-
-    // 1 plane + (24 bits/pixel << 16)
-    bmpHeaderAddInt(header, pos, 1572865);
-
-    // no compression
-    // size of image for compression
-    bmpHeaderAddInt(header, pos, 0);
-    bmpHeaderAddInt(header, pos, 0);
-
-    // x pixels per meter
-    // y pixels per meter
-    bmpHeaderAddInt(header, pos, 0);
-    bmpHeaderAddInt(header, pos, 0);
-
-    // number of colors used (0 = compute from bits/pixel)
-    // number of important colors (0 = all colors important)
-    bmpHeaderAddInt(header, pos, 0);
-    bmpHeaderAddInt(header, pos, 0);
-
-    // BE CAREFUL: BMP format wants BGR ordering for screen data
-    unsigned char* scr = screenBuffer;
-    for (int row=0; row < winHeight; row++) {
-        for (int col=0; col < winWidth; col++) {
-            unsigned char tmp = scr[2];
-            scr[2] = scr[0];  // B
-            scr[0] = tmp;     // R
-            scr += 3;
-        }
-        scr += pad;  // skip over padding already in screen data
-    }
-
-    FILE* f;
-    char filename[100];
-    if (frame_number >= 0) {
-        if (directory_name)
-            sprintf(filename, "%s/image%03d.bmp", directory_name, frame_number);
-        else
-            sprintf(filename, "/tmp/flow_animation/image%03d.bmp", frame_number);
-
-        TRACE("Writing %s", filename);
-        f = fopen(filename, "wb");
-        if (f == 0) {
-            ERROR("cannot create file");
-        }
-    } else {
-        f = fopen("/tmp/image.bmp", "wb");
-        if (f == 0) {
-            ERROR("cannot create file");
-        }
-    }
-    if (fwrite(header, SIZEOF_BMP_HEADER, 1, f) != 1) {
-        ERROR("can't write header: short write");
-    }
-    if (fwrite(screenBuffer, (3*winWidth+pad)*winHeight, 1, f) != 1) {
-        ERROR("can't write data: short write");
-    }
-    fclose(f);
-}
-
-void
-NanoVis::bmpWrite(const char *prefix)
-{
-    unsigned char header[SIZEOF_BMP_HEADER];
-    ssize_t nWritten;
-    int pos = 0;
-
-    // BE CAREFUL:  BMP files must have an even multiple of 4 bytes
-    // on each scan line.  If need be, we add padding to each line.
-    int pad = 0;
-    if ((3*winWidth) % 4 > 0) {
-        pad = 4 - ((3*winWidth) % 4);
-    }
-    pad = 0;
-    int fsize = (3*winWidth+pad)*winHeight + sizeof(header);
-
-    char string[200];
-    sprintf(string, "%s %d\n", prefix, fsize);
-    nWritten = write(g_fdOut, string, strlen(string));
-    assert(nWritten == (ssize_t)strlen(string));
-    header[pos++] = 'B';
-    header[pos++] = 'M';
-
-    // file size in bytes
-    bmpHeaderAddInt(header, pos, fsize);
-
-    // reserved value (must be 0)
-    bmpHeaderAddInt(header, pos, 0);
-
-    // offset in bytes to start of bitmap data
-    bmpHeaderAddInt(header, pos, SIZEOF_BMP_HEADER);
-
-    // size of the BITMAPINFOHEADER
-    bmpHeaderAddInt(header, pos, 40);
-
-    // width of the image in pixels
-    bmpHeaderAddInt(header, pos, winWidth);
-
-    // height of the image in pixels
-    bmpHeaderAddInt(header, pos, winHeight);
-
-    // 1 plane + (24 bits/pixel << 16)
-    bmpHeaderAddInt(header, pos, 1572865);
-
-    // no compression
-    // size of image for compression
-    bmpHeaderAddInt(header, pos, 0);
-    bmpHeaderAddInt(header, pos, 0);
-
-    // x pixels per meter
-    // y pixels per meter
-    bmpHeaderAddInt(header, pos, 0);
-    bmpHeaderAddInt(header, pos, 0);
-
-    // number of colors used (0 = compute from bits/pixel)
-    // number of important colors (0 = all colors important)
-    bmpHeaderAddInt(header, pos, 0);
-    bmpHeaderAddInt(header, pos, 0);
-
-    // BE CAREFUL: BMP format wants BGR ordering for screen data
-    unsigned char* scr = screenBuffer;
-    for (int row=0; row < winHeight; row++) {
-        for (int col=0; col < winWidth; col++) {
-            unsigned char tmp = scr[2];
-            scr[2] = scr[0];  // B
-            scr[0] = tmp;     // R
-            scr += 3;
-        }
-        scr += pad;  // skip over padding already in screen data
-    }
-
-    nWritten = write(g_fdOut, header, SIZEOF_BMP_HEADER);
-    assert(nWritten == SIZEOF_BMP_HEADER);
-    nWritten = write(g_fdOut, screenBuffer, (3*winWidth+pad)*winHeight);
-    assert(nWritten == (3*winWidth+pad)*winHeight);
-    g_stats.nFrames++;
-    g_stats.nFrameBytes += (3*winWidth+pad)*winHeight;
-}
-
-/*
- * ppmWrite --
- *
- *  Writes the screen image as PPM binary data to the nanovisviewer
- *  client.  The PPM binary format is very simple.
- *
- *      P6 w h 255\n
- *      3-byte RGB pixel data.
- *
- *  The nanovisviewer client (using the TkImg library) will do less work
- *  to unpack this format, as opposed to BMP or PNG.  (This doesn't
- *  eliminate the need to look into DXT compression performed on the GPU).
- *
- *      Note that currently the image data from the screen is both row-padded
- *      and the scan lines are reversed.  This routine could be made even
- *      simpler (faster) if the screen buffer is an array of packed 3-bytes
- *      per pixels (no padding) and where the origin is the top-left corner.
- */
-void
-NanoVis::ppmWrite(const char *prefix)
-{
-#define PPM_MAXVAL 255
-    char header[200];
-
-    TRACE("Enter (%dx%d)", winWidth, winHeight);
-    // Generate the PPM binary file header
-    sprintf(header, "P6 %d %d %d\n", winWidth, winHeight, PPM_MAXVAL);
-
-    size_t header_length = strlen(header);
-    size_t data_length = winWidth * winHeight * 3;
-
-    char command[200];
-    sprintf(command, "%s %lu\n", prefix, 
-            (unsigned long)header_length + data_length);
-
-    size_t wordsPerRow = (winWidth * 24 + 31) / 32;
-    size_t bytesPerRow = wordsPerRow * 4;
-    size_t rowLength = winWidth * 3;
-    size_t nRecs = winHeight + 2;
-
-    struct iovec *iov;
-    iov = (struct iovec *)malloc(sizeof(struct iovec) * nRecs);
-
-    // Write the nanovisviewer command, then the image header and data.
-    // Command
-    iov[0].iov_base = command;
-    iov[0].iov_len = strlen(command);
-    // Header of image data
-    iov[1].iov_base = header;
-    iov[1].iov_len = header_length;
-    // Image data.
-    int y;
-    unsigned char *srcRowPtr = screenBuffer;
-    for (y = winHeight + 1; y >= 2; y--) {
-        iov[y].iov_base = srcRowPtr;
-        iov[y].iov_len = rowLength;
-        srcRowPtr += bytesPerRow;
-    }
-    if (writev(g_fdOut, iov, nRecs) < 0) {
-        ERROR("write failed: %s", strerror(errno));
-    }
-    free(iov);
-    g_stats.nFrames++;
-    g_stats.nFrameBytes += (bytesPerRow * winHeight);
-    TRACE("Leave (%dx%d)", winWidth, winHeight);
 }
 
Index: /nanovis/branches/1.2/nanovis.h
===================================================================
--- /nanovis/branches/1.2/nanovis.h	(revision 4936)
+++ /nanovis/branches/1.2/nanovis.h	(revision 4937)
@@ -89,8 +89,4 @@
     static void eventuallyRedraw();
 
-    static void ppmWrite(const char *prefix);
-    static void bmpWrite(const char *prefix);
-    static void bmpWriteToFile(int frameNum, const char* dirName);
- 
     static TransferFunction *getTransferFunction(const TransferFunctionId& id);
     static TransferFunction *defineTransferFunction(const TransferFunctionId& id, 
@@ -108,5 +104,5 @@
     static void readScreen()
     {
-        //glPixelStorei(GL_PACK_ALIGNMENT, 1);
+        glPixelStorei(GL_PACK_ALIGNMENT, 1);
         glReadPixels(0, 0, winWidth, winHeight, GL_RGB, GL_UNSIGNED_BYTE, 
                      screenBuffer);
Index: /nanovis/branches/1.2/nanovisServer.cpp
===================================================================
--- /nanovis/branches/1.2/nanovisServer.cpp	(revision 4936)
+++ /nanovis/branches/1.2/nanovisServer.cpp	(revision 4937)
@@ -10,5 +10,4 @@
 #include <cstring>
 #include <csignal>
-#include <ctime>
 
 #include <fcntl.h>
@@ -34,6 +33,11 @@
 #include "define.h"
 #include "Command.h"
+#include "PPMWriter.h"
 #include "ReadBuffer.h"
 #include "Shader.h"
+#ifdef USE_THREADS
+#include <pthread.h>
+#include "ResponseQueue.h"
+#endif
 #include "Trace.h"
 
@@ -43,4 +47,5 @@
 Stats nv::g_stats;
 int nv::g_statsFile = -1; ///< Stats output file descriptor.
+
 int nv::g_fdIn = STDIN_FILENO;     ///< Input file descriptor
 int nv::g_fdOut = STDOUT_FILENO;   ///< Output file descriptor
@@ -49,17 +54,31 @@
 FILE *nv::g_fLog = NULL;           ///< Trace logging file handle
 ReadBuffer *nv::g_inBufPtr = NULL; ///< Socket read buffer
+#ifdef USE_THREADS
+ResponseQueue *nv::g_queue = NULL;
+#endif
+
+#ifdef USE_THREADS
+
+static void
+queueFrame(ResponseQueue *queue, unsigned char *imgData)
+{
+    queuePPM(queue, "nv>image -type image -bytes",
+             imgData,
+             NanoVis::winWidth,
+             NanoVis::winHeight);
+}
+
+#else
 
 static void
 writeFrame(int fd, unsigned char *imgData)
 {
-#if 0
     writePPM(fd, "nv>image -type image -bytes",
              imgData,
              NanoVis::winWidth,
              NanoVis::winHeight);
-#else
-    NanoVis::ppmWrite("nv>image -type image -bytes");
-#endif
-}
+}
+
+#endif /*USE_THREADS*/
 
 static int
@@ -72,8 +91,12 @@
 
     TRACE("Sending OK for commands through %lu", g_stats.nCommands);
+#ifdef USE_THREADS
+    queueResponse(str.c_str(), numBytes, Response::VOLATILE, Response::OK);
+#else
     if (write(g_fdOut, str.c_str(), numBytes) < 0) {
         ERROR("write failed: %s", strerror(errno));
         return -1;
     }
+#endif
     return 0;
 }
@@ -263,8 +286,16 @@
 /**
  * \brief Send a command with data payload
+ *
+ * data pointer is freed on completion of the response
  */
 void
-nv::sendDataToClient(const char *command, const char *data, size_t dlen)
-{
+nv::sendDataToClient(const char *command, char *data, size_t dlen)
+{
+#ifdef USE_THREADS
+    char *buf = (char *)malloc(strlen(command) + dlen);
+    memcpy(buf, command, strlen(command));
+    memcpy(buf + strlen(command), data, dlen);
+    queueResponse(buf, strlen(command) + dlen, Response::DYNAMIC);
+#else
     size_t numRecords = 2;
     struct iovec *iov = new iovec[numRecords];
@@ -275,5 +306,5 @@
     iov[0].iov_len = strlen(command);
     // Data
-    iov[1].iov_base = const_cast<char *>(data);
+    iov[1].iov_base = data;
     iov[1].iov_len = dlen;
     if (writev(g_fdOut, iov, numRecords) < 0) {
@@ -281,4 +312,6 @@
     }
     delete [] iov;
+#endif
+    free(data);
 }
 
@@ -349,5 +382,5 @@
 }
 
-#ifndef USE_NEW_EVENT_LOOP
+#if !defined(USE_NEW_EVENT_LOOP) && !defined(USE_THREADS)
 
 static int
@@ -452,5 +485,5 @@
     glutSetWindow(NanoVis::renderWindow);
 
-#ifdef USE_NEW_EVENT_LOOP
+#if defined(USE_NEW_EVENT_LOOP) || defined(USE_THREADS)
     if (nv::processCommands(NanoVis::interp, g_inBufPtr, g_fdOut) < 0) {
         exitService(1);
@@ -469,5 +502,11 @@
         TRACE("Rendering new frame");
         NanoVis::readScreen();
+#ifdef USE_THREADS
+        queueFrame(g_queue, NanoVis::screenBuffer);
+#else
         writeFrame(g_fdOut, NanoVis::screenBuffer);
+#endif
+        g_stats.nFrames++;
+        g_stats.nFrameBytes += NanoVis::winWidth * NanoVis::winHeight * 3;
     } else {
         TRACE("No render required");
@@ -475,5 +514,5 @@
     }
 
-#ifdef USE_NEW_EVENT_LOOP
+#if defined(USE_NEW_EVENT_LOOP) || defined(USE_THREADS)
     if (g_inBufPtr->status() == ReadBuffer::ENDFILE) {
         exitService(0);
@@ -487,4 +526,32 @@
     TRACE("Leave");
 }
+
+#ifdef USE_THREADS
+
+static void *
+writerThread(void *clientData)
+{
+    ResponseQueue *queue = (ResponseQueue *)clientData;
+
+    TRACE("Starting writer thread");
+    for (;;) {
+        Response *response = queue->dequeue();
+        if (response == NULL)
+            continue;
+        if (fwrite(response->message(), sizeof(char), response->length(),
+                   g_fOut) != response->length()) {
+            ERROR("short write while trying to write %ld bytes",
+                  response->length());
+        }
+        fflush(g_fOut);
+        TRACE("Wrote response of type %d", response->type());
+        delete response;
+        if (feof(g_fOut))
+            break;
+    }
+    return NULL;
+}
+
+#endif /*USE_THREADS*/
 
 static
@@ -579,5 +646,5 @@
     fflush(g_fOut);
 
-#ifdef USE_NEW_EVENT_LOOP
+#if defined(USE_NEW_EVENT_LOOP) || defined(USE_THREADS)
     g_inBufPtr = new ReadBuffer(g_fdIn, 1<<12);
 #endif
@@ -585,6 +652,16 @@
     Tcl_Interp *interp = Tcl_CreateInterp();
     ClientData clientData = NULL;
-
+#ifdef USE_THREADS
+    g_queue = new ResponseQueue();
+    clientData = (ClientData)g_queue;
     initTcl(interp, clientData);
+
+    pthread_t writerThreadId;
+    if (pthread_create(&writerThreadId, NULL, &writerThread, g_queue) < 0) {
+        ERROR("Can't create writer thread: %s", strerror(errno));
+    }
+#else
+    initTcl(interp, clientData);
+#endif
     NanoVis::interp = interp;
 
Index: /nanovis/branches/1.2/nanovisServer.h
===================================================================
--- /nanovis/branches/1.2/nanovisServer.h	(revision 4936)
+++ /nanovis/branches/1.2/nanovisServer.h	(revision 4937)
@@ -26,4 +26,5 @@
 namespace nv {
     class ReadBuffer;
+    class ResponseQueue;
 
     typedef struct {
@@ -46,4 +47,7 @@
     extern FILE *g_fLog;           ///< Trace logging file handle
     extern ReadBuffer *g_inBufPtr; ///< Socket read buffer
+#ifdef USE_THREADS
+    extern ResponseQueue *g_queue;
+#endif
 
 #ifdef KEEPSTATS
@@ -51,5 +55,5 @@
     extern int writeToStatsFile(int f, const char *s, size_t length);
 #endif
-    extern void sendDataToClient(const char *command, const char *data,
+    extern void sendDataToClient(const char *command, char *data,
                                  size_t dlen);
 }
