support png output, buffered file writing
authorJustin Wind <justin.wind@gmail.com>
Fri, 4 May 2012 21:26:53 +0000 (14:26 -0700)
committerJustin Wind <justin.wind@gmail.com>
Fri, 4 May 2012 21:26:53 +0000 (14:26 -0700)
Now uses libpng to generate image files, if available.
Writes image files into memory buffer first, writes out to disk no more than once every few seconds.

Makefile
chargen-4x8.h
dcpu16.c
dcpu16.h
display.c
display.h
vm-dcpu16.c

index 0da0a0e2778f7a2c0a9ff465c33154836bbabc02..70445ff8f27955f86faadebf6200ff1805748700 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -8,9 +8,18 @@ endif
 PROGRAMS = as-dcpu16 vm-dcpu16
 SOURCES = common.c dcpu16.c as-dcpu16.c vm-dcpu16.c display.c
 
-CPPFLAGS = -D_XOPEN_SOURCE=600
+CPPFLAGS += -DHAVE_LIBPNG -I/usr/X11/include/
 CFLAGS = -g -Wall -Wextra -pedantic -std=c99
-LDFLAGS = -lreadline
+LDFLAGS += -lreadline
+LDFLAGS += -L/usr/X11/lib -lpng
+
+UNAME := $(shell uname)
+ifeq ($(UNAME),Darwin)
+CPPFLAGS += -DHAVE_FUNOPEN
+endif
+ifeq ($(UNAME),Linux)
+CPPFLAGS += -DHAVE_FOPENCOOKIE -D_XOPEN_SOURCE=600
+endif
 
 
 all:   $(PROGRAMS)
index 2c8a98e95c81b8f01ba0bb6c3907572126ee22a9..580e0874b8c452b45a21853bdeb95b04301e2371 100644 (file)
@@ -3,7 +3,7 @@
 
 /* 4x8 pixel default font map */
 
-const char chargen_4x8_glyphs[][4] = {
+const unsigned char chargen_4x8_glyphs[][4] = {
     /* 00 */
     { 0x00, 0xf0, 0x10, 0x10 },
     /* 01 */
index 51d755dfa7731e4479e136bf85ac5d8bf2f3616a..23ef53a41747da7aadb9a51baec085033d5e1bab 100644 (file)
--- a/dcpu16.c
+++ b/dcpu16.c
@@ -693,6 +693,8 @@ void dcpu16_step(struct dcpu16 *d) {
 
     if (!d) return;
 
+    acct_event_(d, DCPU16_ACCT_EV_CYCLE, d->pc);
+
     /*
         PC is advanced while decoding the operands by the opcode functions.
         Things like this could be organized a little better..
index 7741c93ebe263c511c14360bd2ca604153c97f13..c893347ea3485e9b6aecb7bd0893b12c1a111ccf 100644 (file)
--- a/dcpu16.h
+++ b/dcpu16.h
@@ -28,10 +28,11 @@ struct dcpu16 {
 /* these are used for accounting/watchpointing/modules/&c */
 typedef unsigned int dcpu16_acct_event;
 typedef void (dcpu16_ev_cb_t)(struct dcpu16 *, dcpu16_acct_event, DCPU16_WORD, void *);
-#define DCPU16_ACCT_EV_READ     (1<<1)
-#define DCPU16_ACCT_EV_WRITE    (1<<2)
-#define DCPU16_ACCT_EV_NOP      (1<<3)
-#define DCPU16_ACCT_EV_RESET    (1<<4)
+#define DCPU16_ACCT_EV_CYCLE    (1<<1)
+#define DCPU16_ACCT_EV_READ     (1<<2)
+#define DCPU16_ACCT_EV_WRITE    (1<<3)
+#define DCPU16_ACCT_EV_NOP      (1<<4)
+#define DCPU16_ACCT_EV_RESET    (1<<5)
 struct dcpu16_acct_cb {
     dcpu16_ev_cb_t *fn;
     void *data;
index 2c8485a2e41309402405ef5d21312660308487d7..b6488a52df7b85098e2a4ff4266930b363986102 100644 (file)
--- a/display.c
+++ b/display.c
@@ -2,14 +2,27 @@
 #include <stdio.h>
 #include <string.h>
 #include <errno.h>
+#include <sysexits.h>
+#include <time.h>
 #include <assert.h>
 
+#ifdef HAVE_LIBPNG
+#include <setjmp.h>
+#include <png.h>
+#endif /* HAVE_LIBPNG */
+
 #include "dcpu16.h"
 #include "display.h"
 #include "chargen-4x8.h"
 
-/* preliminary attempt at handling display */
-/* currently keeps display state in buffer, and just updates a PNM file on each screen update */
+/*
+    preliminary attempt at handling display
+
+    currently keeps raw display output in a pixel buffer, which is updated on every write to cpu's memory in display-space.
+    every [n] cycles, display buffer is rendered into an image file (png or pnm) in memory
+
+    TODO: blinking
+*/
 
 #define DISPLAY_BASE 0x8000
 #define DISPLAY_END  0x8179
@@ -36,9 +49,22 @@ struct pixel_ {
     char b;
 };
 
+/* buf will hold image file in memory */
+struct memstream_cookie_ {
+    char *buf;
+    size_t allocated;
+    size_t endpos;
+    off_t offset;
+    size_t chunk_size;
+};
+
 struct dcpu16_display_ {
     char *outfile;
-    struct pixel_ *pixbuf;
+    struct pixel_ *pixbuf; /* raw pixels */
+    struct memstream_cookie_ memstream_cookie; /* streambuffer contains rendered image file from pixbuf */
+    unsigned long long cycle_last_write;
+    unsigned int pixbuf_dirty : 1;
+    unsigned int file_dirty : 1;
 };
 
 static inline
@@ -67,9 +93,53 @@ struct pixel_ pcolor_(unsigned int c) {
     return p;
 }
 
+/* support functions for writing streams into memory buffer */
+static
+void memstream_cookie_init_(struct memstream_cookie_ *c, size_t chunk_size) {
+    assert(c);
+    assert(chunk_size);
+
+    c->buf = NULL;
+    c->allocated = 0;
+    c->endpos = 0;
+    c->offset = 0;
+    c->chunk_size = chunk_size;
+}
+
+static
+void memstream_cookie_fini_(struct memstream_cookie_ *c) {
+    assert(c);
+
+    free(c->buf);
+    c->allocated = c->endpos = c->offset = 0;
+}
+
+static
+int memstream_write_fn_(void *c, const char *buf, int size) {
+    struct memstream_cookie_ *cookie = c;
+
+    while ((size_t)size + cookie->offset > cookie->allocated) {
+        size_t new_allocated = cookie->allocated + cookie->chunk_size;
+        void *tmp_ptr = realloc(cookie->buf, new_allocated);
+        if (tmp_ptr == NULL) {
+            return -1;
+        }
+        cookie->buf = tmp_ptr;
+        cookie->allocated = new_allocated;
+    }
+
+    memcpy(cookie->buf + cookie->offset, buf, size);
+    cookie->offset += size;
+    if (cookie->offset > (off_t)cookie->endpos) {
+        cookie->endpos = cookie->offset;
+    }
+
+    return size;
+}
+
 /* should this just flood-fill entire display? */
 static inline
-void display_draw_border(struct pixel_ *pixbuf, struct pixel_ color) {
+void display_draw_border_(struct pixel_ *pixbuf, struct pixel_ color) {
     size_t x, y;
     size_t i;
 
@@ -95,7 +165,7 @@ void display_draw_border(struct pixel_ *pixbuf, struct pixel_ color) {
 
 /* render a character cell to the display */
 static inline
-void display_draw_cell(struct pixel_ *pixbuf, DCPU16_WORD *cell_map, DCPU16_WORD index, int cell_x, int cell_y, struct pixel_ fg, struct pixel_ bg) {
+void display_draw_cell_(struct pixel_ *pixbuf, DCPU16_WORD *cell_map, DCPU16_WORD index, int cell_x, int cell_y, struct pixel_ fg, struct pixel_ bg) {
     struct pixel_ *cellstart = pixbuf; /* start of display */
     unsigned int pix_x, pix_y;
     unsigned char *cell_bitmap = (unsigned char *)(cell_map + (index * sizeof index));
@@ -121,12 +191,123 @@ void display_draw_cell(struct pixel_ *pixbuf, DCPU16_WORD *cell_map, DCPU16_WORD
     }
 }
 
-/* write pnm file */
-void display_pnm_write(struct pixel_ *pixbuf, const char *filename) {
+#ifdef HAVE_LIBPNG
+/* write status callback */
+static
+void display_png_write_row_cb_(png_structp png_ptr, png_uint_32 row, int pass) {
+    (void)png_ptr, (void)row, (void)pass;
+    /*
+    fprintf(stderr, "%s:%u:%d\n", __func__, row, pass);
+    */
+}
+
+static
+void display_png_user_error_fn_(png_structp png_ptr, png_const_charp error_msg) {
+    (void)png_ptr;
+    fprintf(stderr, "PNG:ERROR:%s\n", error_msg);
+    exit(EX_SOFTWARE);
+}
+
+static
+void display_png_user_warning_fn_(png_structp png_ptr, png_const_charp warning_msg) {
+    (void)png_ptr;
+    fprintf(stderr, "PNG:WARNING:%s\n", warning_msg);
+    return;
+}
+
+/* write png file */
+static
+void display_png_write_(struct dcpu16_display_ *d) {
+    FILE *f;
+    png_structp png_ptr;
+    png_infop info_ptr;
+    size_t i;
+
+    png_voidp user_error_ptr = NULL;
+
+#ifdef HAVE_FOPENCOOKIE
+    /* linux-style memory stream */
+    struct cookie_io_functions_t cookie_io_functions = {
+        .read = NULL,
+        .write = memstream_write_fn_,
+        .seek = NULL,
+        .close = NULL
+    };
+    f = fopencookie(&d->memstream_cookie, "wb", cookie_io_functions);
+    if (f == NULL) {
+        fprintf(stderr, "%s():%s\n", "fopencookie", strerror(errno));
+        return;
+    }
+
+#else /* HAVE_FOPENCOOKIE */
+#ifdef HAVE_FUNOPEN
+    /* BSD-style memory stream */
+    f = funopen(&d->memstream_cookie,
+                NULL, /* don't care about read */
+                memstream_write_fn_,
+                NULL, /* don't care about seek */
+                NULL /* don't care about close */
+                );
+    if (f == NULL) {
+        fprintf(stderr, "%s():%s\n", "funopen", strerror(errno));
+        return;
+    }
+
+#else /* HAVE_FUNOPEN */
+    /* default to writing file if we can't buffer in memory */
+    f = fopen(d->outfile, "wb");
+    if (f == NULL) {
+        return;
+    }
+#endif /* HAVE_FUNOPEN */
+#endif /* HAVE_FOPENCOOKIE */
+
+    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, user_error_ptr, display_png_user_error_fn_, display_png_user_warning_fn_);
+    if (png_ptr == NULL) {
+        goto f_done;
+    }
+
+    info_ptr = png_create_info_struct(png_ptr);
+    if (info_ptr == NULL) {
+        png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
+        goto f_done;
+    }
+
+    if (setjmp(png_jmpbuf(png_ptr))) {
+        png_destroy_write_struct(&png_ptr, &info_ptr);
+        goto f_done;
+}
+
+    png_init_io(png_ptr, f);
+    png_set_write_status_fn(png_ptr, display_png_write_row_cb_);
+
+    /* png_set_filter(png_ptr, 0, PNG_FILTER_NONE); */
+    /* png_set_compression_level(png_ptr, Z_BEST_COMPRESSION); */
+
+    png_set_IHDR(png_ptr, info_ptr, PIX_X, PIX_Y, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+    png_write_info(png_ptr, info_ptr);
+
+    for (i = 0; i < PIX_Y; i++) {
+        png_write_row(png_ptr, (unsigned char *)(d->pixbuf + (i * PIX_X)));
+    }
+
+    png_write_end(png_ptr, info_ptr);
+
+    png_destroy_write_struct(&png_ptr, &info_ptr);
+
+f_done:
+    fclose(f);
+}
+#else /* HAVE_LIBPNG */
+
+/* if PNG not available, just write pnm file */
+static
+void display_pnm_write_(struct dcpu16_display_ *d) {
     size_t i;
     FILE *f;
 
-    f = fopen(filename, "w");
+    f = fopen(d->outfile, "w");
     if (f == NULL) {
         fprintf(stderr, "%s('%s'):%s\n", "fopen", filename, strerror(errno));
         return;
@@ -140,14 +321,26 @@ void display_pnm_write(struct pixel_ *pixbuf, const char *filename) {
 
     /* write out image bytes in r g b order */
     for (i = 0; i < PIX_X * PIX_Y; i++) {
-        fwrite(&pixbuf[i].r, 1, 1, f);
-        fwrite(&pixbuf[i].g, 1, 1, f);
-        fwrite(&pixbuf[i].b, 1, 1, f);
+        fwrite(&d->pixbuf[i].r, 1, 1, f);
+        fwrite(&d->pixbuf[i].g, 1, 1, f);
+        fwrite(&d->pixbuf[i].b, 1, 1, f);
     }
 
     fclose(f);
 }
 
+#endif /* HAVE_LIBPNG */
+
+static
+void display_image_render_(struct dcpu16_display_ *d) {
+#ifdef HAVE_LIBPNG
+    display_png_write_(d);
+#else /* HAVE_LIBPNG */
+    display_pnm_write_(d);
+#endif /* HAVE_LIBPNG */
+    d->file_dirty = 1;
+}
+
 
 /* the callback to register to be run on display init/reset */
 /* currently this populates the chargen map 'from rom'.. */
@@ -162,10 +355,52 @@ void display_reset_fn(struct dcpu16 *vm, dcpu16_acct_event e, DCPU16_WORD addr,
     memset(vm->ram + DISPLAY_BASE, 0, (DISPLAY_END - DISPLAY_BASE) * sizeof *(vm->ram));
 
     memset(d->pixbuf, 0, PIX_X * PIX_Y * sizeof *(d->pixbuf));
+
+    d->pixbuf_dirty = 1;
+}
+
+/* the callback to register with the cpu for occasionally doing something with the display buffer */
+void display_cycle_fn(struct dcpu16 *vm, dcpu16_acct_event e, DCPU16_WORD addr, void *data) {
+    struct dcpu16_display_ *d = (struct dcpu16_display_ *)data;
+
+    const unsigned long long min_cycles_between_writes = 1536;
+    const time_t min_seconds_between_file_write = 5;
+
+    static time_t last_file_write_ = 0;
+    time_t now;
+
+    (void)e, (void)addr;
+
+    if (d->pixbuf_dirty) {
+        if (vm->cycle > d->cycle_last_write + min_cycles_between_writes) {
+            display_image_render_(d);
+            d->pixbuf_dirty = 0;
+            d->cycle_last_write = vm->cycle;
+        }
+    }
+
+    if (d->file_dirty) {
+        now = time(NULL);
+        if (now > last_file_write_ + min_seconds_between_file_write) {
+            FILE *of;
+
+            of = fopen(d->outfile, "wb");
+            if (of == NULL) {
+                fprintf(stderr, "%s('%s'):%s\n", "fopen", d->outfile, strerror(errno));
+                return;
+            }
+
+            fwrite(d->memstream_cookie.buf, d->memstream_cookie.endpos, 1, of);
+            fclose(of);
+
+            d->file_dirty = 0;
+            last_file_write_ = now;
+        }
+    }
 }
 
 /* the callback to register with the cpu for watching memory updates */
-/* user data is an allocated display buffer */
+/* 'data' is an allocated display buffer */
 void display_fn(struct dcpu16 *vm, dcpu16_acct_event e, DCPU16_WORD addr, void *data) {
     DCPU16_DISPLAY *d = (DCPU16_DISPLAY *)data;
     unsigned char index, blink, bg, fg;
@@ -188,20 +423,20 @@ void display_fn(struct dcpu16 *vm, dcpu16_acct_event e, DCPU16_WORD addr, void *
                 blink = (vm->ram[addr] >> 7) & 0x01;
                 bg = (vm->ram[addr] >> 8) & 0x0f;
                 fg = (vm->ram[addr] >> 12) & 0x0f;
-                display_draw_cell(d->pixbuf, vm->ram + DISPLAY_CELL_MAP, index, cell_x, cell_y, pcolor_(fg), pcolor_(bg));
+                display_draw_cell_(d->pixbuf, vm->ram + DISPLAY_CELL_MAP, index, cell_x, cell_y, pcolor_(fg), pcolor_(bg));
             }
         }
 
-        display_pnm_write(d->pixbuf, d->outfile);
+        d->pixbuf_dirty = 1;
         return;
     }
 
     if (addr == DISPLAY_MISC) {
         /* new border color */
         char border = vm->ram[addr] & 0x0f;
-        display_draw_border(d->pixbuf, pcolor_(border));
+        display_draw_border_(d->pixbuf, pcolor_(border));
 
-        display_pnm_write(d->pixbuf, d->outfile);
+        d->pixbuf_dirty = 1;
         return;
     }
 
@@ -213,8 +448,9 @@ void display_fn(struct dcpu16 *vm, dcpu16_acct_event e, DCPU16_WORD addr, void *
     bg = (vm->ram[addr] >> 8) & 0x0f;
     fg = (vm->ram[addr] >> 12) & 0x0f;
 
-    display_draw_cell(d->pixbuf, vm->ram + DISPLAY_CELL_MAP, index, cell_x, cell_y, pcolor_(fg), pcolor_(bg));
-    display_pnm_write(d->pixbuf, d->outfile);
+    display_draw_cell_(d->pixbuf, vm->ram + DISPLAY_CELL_MAP, index, cell_x, cell_y, pcolor_(fg), pcolor_(bg));
+
+    d->pixbuf_dirty = 1;
 }
 
 /* init the pixel buffer */
@@ -236,5 +472,22 @@ DCPU16_DISPLAY *display_new(const char *filename) {
         return NULL;
     }
 
+    memstream_cookie_init_(&d->memstream_cookie, 1024 * 8);
+
+    d->cycle_last_write = 0;
+    d->pixbuf_dirty = 0;
+    d->file_dirty = 0;
+
     return d;
 }
+
+void display_free(DCPU16_DISPLAY *d) {
+    if (d) {
+        free(d->pixbuf);
+        d->pixbuf = NULL;
+        free(d->outfile);
+        d->outfile = NULL;
+        memstream_cookie_fini_(&d->memstream_cookie);
+    }
+    free(d);
+}
index bc5651daae856d2c360155604f4620b6399c1459..fe88cc31264dde1fa0c6072ef10d062c9debf692 100644 (file)
--- a/display.h
+++ b/display.h
@@ -6,7 +6,9 @@
 typedef struct dcpu16_display_ DCPU16_DISPLAY;
 
 void display_reset_fn(struct dcpu16 *, dcpu16_acct_event, DCPU16_WORD, void *);
+void display_cycle_fn(struct dcpu16 *, dcpu16_acct_event, DCPU16_WORD, void *);
 void display_fn(struct dcpu16 *, dcpu16_acct_event, DCPU16_WORD, void *);
-DCPU16_DISPLAY *display_new(const char *filename);
+DCPU16_DISPLAY *display_new(const char *);
+void display_free(DCPU16_DISPLAY *);
 
 #endif /* DISPLAY_H_MJYI1IAV */
index 5f652739e6c477aa428cae2bb55a82e8d2693af3..5924074aa468ce5c5370538fd819864ef69d53c2 100644 (file)
@@ -365,17 +365,28 @@ COMMAND_HELP(run) {
                "May be interrupted with SIGINT.\n");
 }
 
+static const char * const display_filename_default_ =
+#ifdef HAVE_LIBPNG
+    "dcpu16-display.png"
+#else /* HAVE_LIBPNG */
+    "dcpu16-display.pnm"
+#endif /* HAVE_LIBPNG */
+;
 COMMAND_IMPL(display) {
-    (void)arg_count, (void)arg_vector;
-
     static DCPU16_DISPLAY *display = NULL;
+    const char *filename = display_filename_default_;
+
+    if (arg_count == 2) {
+        filename = arg_vector[1];
+    }
 
     if (display) {
         printf("display already enabled..\n");
         return 0;
     }
 
-    display = display_new("dcpu16-display.pnm");
+    display = display_new(filename);
+
     if (display == NULL) {
         fprintf(stderr, "failed to initialize display buffer\n");
         return 0;
@@ -391,17 +402,24 @@ COMMAND_IMPL(display) {
         return 0;
     }
 
+    if (dcpu16_acct_add(vm, DCPU16_ACCT_EV_CYCLE, display_cycle_fn, display)) {
+        fprintf(stderr, "failed to register display cycle callback\n");
+        return 0;
+    }
+
     /* init display as if reset occurred */
     display_reset_fn(vm, DCPU16_ACCT_EV_RESET, 0, display);
 
     return 0;
 }
 COMMAND_HELP(display) {
-    fprintf(f, "\tdisplay\n");
+    fprintf(f, "\tdisplay [file]\n");
     if (summary) return;
 
-    fprintf(f, "Begins updating a PNM of current display contents...\n"
-               "This is not a fully-baked feature...\n");
+    fprintf(f, "Attaches display interface, begins updating an image file of display contents...\n"
+               "Image filename may be specified, defaults to '%s'\n",
+               display_filename_default_
+            );
 }
 
 /* gather all these together into a searchable table */
@@ -419,7 +437,7 @@ static struct command_ command_table_[] = {
     COMMAND_ENTRY(step, 0, 1),
     COMMAND_ENTRY(run, 0, 0),
     COMMAND_ENTRY(reset, 0, 0),
-    COMMAND_ENTRY(display, 0, 0),
+    COMMAND_ENTRY(display, 0, 1),
     { NULL, 0, 0, NULL, NULL }
 };