From 4185a67f55fb99f34f013b939a8ef9e13454c1e5 Mon Sep 17 00:00:00 2001 From: Justin Wind Date: Fri, 4 May 2012 14:26:53 -0700 Subject: [PATCH] support png output, buffered file writing 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 | 13 ++- chargen-4x8.h | 2 +- dcpu16.c | 2 + dcpu16.h | 9 +- display.c | 289 ++++++++++++++++++++++++++++++++++++++++++++++---- display.h | 4 +- vm-dcpu16.c | 32 ++++-- 7 files changed, 318 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index 0da0a0e..70445ff 100644 --- 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) diff --git a/chargen-4x8.h b/chargen-4x8.h index 2c8a98e..580e087 100644 --- a/chargen-4x8.h +++ b/chargen-4x8.h @@ -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 */ diff --git a/dcpu16.c b/dcpu16.c index 51d755d..23ef53a 100644 --- 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.. diff --git a/dcpu16.h b/dcpu16.h index 7741c93..c893347 100644 --- 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; diff --git a/display.c b/display.c index 2c8485a..b6488a5 100644 --- a/display.c +++ b/display.c @@ -2,14 +2,27 @@ #include #include #include +#include +#include #include +#ifdef HAVE_LIBPNG +#include +#include +#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); +} diff --git a/display.h b/display.h index bc5651d..fe88cc3 100644 --- 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 */ diff --git a/vm-dcpu16.c b/vm-dcpu16.c index 5f65273..5924074 100644 --- a/vm-dcpu16.c +++ b/vm-dcpu16.c @@ -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 } }; -- 2.45.2