X-Git-Url: http://git.squeep.com/?p=dcpu16;a=blobdiff_plain;f=display.c;h=b6488a52df7b85098e2a4ff4266930b363986102;hp=2c8485a2e41309402405ef5d21312660308487d7;hb=4185a67f55fb99f34f013b939a8ef9e13454c1e5;hpb=a4e2c238ea47badea198132fc5d9311d1f40ca13 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); +}