#include #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 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 #define DISPLAY_CELL_MAP 0x8180 #define DISPLAY_MISC 0x8280 #define CELL_X 32 #define CELL_Y 12 #define CELL_X_SZ 4 #define CELL_Y_SZ 8 #define PIX_X 160 #define PIX_Y 128 #define PIX_BORDER 16 /* total display is 160 pixels by 128 pixels */ /* active display is 32x12 cells (each 4x8 pixels), surrounded by 16 pixel border */ /* cells are rendered from cell map, which is bitmap of two words, defining four eight-bit columns */ struct pixel_ { unsigned char r; unsigned char g; unsigned 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; /* 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 struct pixel_ pcolor_(unsigned int c) { struct pixel_ p = { 0, 0, 0 }; switch (c) { case 0x1: p.r=0x00, p.g=0x00, p.b=0xaa; break; /* dark blue */ case 0x2: p.r=0x00, p.g=0xaa, p.b=0x00; break; /* green */ case 0x3: p.r=0x00, p.g=0xaa, p.b=0xaa; break; /* cyan */ case 0x4: p.r=0xaa, p.g=0x00, p.b=0x00; break; /* red */ case 0x5: p.r=0xaa, p.g=0x00, p.b=0xaa; break; /* magenta */ case 0x6: p.r=0xaa, p.g=0xaa, p.b=0x55; break; /* yellow [] */ case 0x7: p.r=0xaa, p.g=0xaa, p.b=0xff; break; /* pale blue */ case 0x8: p.r=0x55, p.g=0x55, p.b=0x55; break; /* grey */ case 0x9: p.r=0x55, p.g=0x55, p.b=0xff; break; /* also blue */ case 0xa: p.r=0x55, p.g=0xff, p.b=0x55; break; /* light green */ case 0xb: p.r=0x55, p.g=0xff, p.b=0xff; break; /* light cyan */ case 0xc: p.r=0xff, p.g=0x55, p.b=0x55; break; /* light red */ case 0xd: p.r=0xff, p.g=0x55, p.b=0xff; break; /* light magenta */ case 0xe: p.r=0xff, p.g=0xff, p.b=0x55; break; /* light yellow */ case 0xf: p.r=0xff, p.g=0xff, p.b=0xff; break; /* white */ default: p.r=0x00, p.g=0x00, p.b=0x00; /* black */ } 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 /* of course these can't be the same */ #ifdef HAVE_FOPENCOOKIE ssize_t memstream_write_fn_(void *c, const char *buf, size_t size) #else /* HAVE_FOPENCOOKIE */ int memstream_write_fn_(void *c, const char *buf, int size) #endif /* HAVE_FOPENCOOKIE */ { 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) { size_t x, y; size_t i; /* top */ for (y = 0; y < PIX_BORDER; y++) { for (x = 0; x < PIX_X; x++) { i = (y * PIX_X) + x; pixbuf[i] = color; i = ((PIX_Y - y) * PIX_X) + x; pixbuf[i] = color; } } /* sides */ for (y = PIX_BORDER; y < (PIX_Y - PIX_BORDER + 1); y++) for (x = 0; x < PIX_BORDER; x++) { i = (y * PIX_X) + x; pixbuf[i] = color; pixbuf[i + (PIX_X - PIX_BORDER)] = 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) { struct pixel_ *cellstart = pixbuf; /* start of display */ unsigned int pix_x, pix_y; unsigned char *cell_bitmap = (unsigned char *)(cell_map + (index * sizeof index)); assert(cell_x < CELL_X); assert(cell_y < CELL_Y); cellstart += (PIX_X * PIX_BORDER); /* skip top border */ cellstart += (CELL_Y_SZ * PIX_X) * cell_y; /* skip down to row */ cellstart += PIX_BORDER; /* skip side border */ cellstart += (CELL_X_SZ) * cell_x; /* skip to column */ for (pix_x = 0; pix_x < CELL_X_SZ; pix_x++) { for (pix_y = 0; pix_y < CELL_Y_SZ; pix_y++) { if ((cell_bitmap[pix_x] >> pix_y) & 0x01) cellstart[((CELL_Y_SZ - pix_y - 1) * PIX_X) + pix_x] = fg; else cellstart[((CELL_Y_SZ - pix_y - 1) * PIX_X) + pix_x] = bg; } } } #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 */ 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(d->outfile, "w"); if (f == NULL) { fprintf(stderr, "%s('%s'):%s\n", "fopen", filename, strerror(errno)); return; } /* write header... */ /* PNM binary */ /* x y */ /* max value */ fprintf(f, "P6\n%d %d\n255\n", PIX_X, PIX_Y); /* write out image bytes in r g b order */ for (i = 0; i < PIX_X * PIX_Y; i++) { 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'.. */ /* and clears the display buffers */ void display_reset_fn(struct dcpu16 *vm, dcpu16_acct_event e, DCPU16_WORD addr, void *data) { struct dcpu16_display_ *d = (struct dcpu16_display_ *)data; (void)e, (void)addr; memcpy(vm->ram + DISPLAY_CELL_MAP, chargen_4x8_glyphs, sizeof chargen_4x8_glyphs); 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 */ /* '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; unsigned int cell_x, cell_y; (void)e; if (addr < DISPLAY_BASE || addr > DISPLAY_MISC) return; if (addr > DISPLAY_END && addr < DISPLAY_MISC) { unsigned char updated_index = addr - DISPLAY_CELL_MAP; /* a cell map was updated, refresh entire screen */ for (cell_x = 0; cell_x < CELL_X; cell_x++) { for (cell_y = 0; cell_y < CELL_Y; cell_y++) { addr = DISPLAY_BASE + cell_x + (cell_y * CELL_X); index = vm->ram[addr] & 0x7f; if (index != updated_index) continue; 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)); } } 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)); d->pixbuf_dirty = 1; return; } cell_x = (addr - DISPLAY_BASE) % CELL_X; cell_y = (addr - DISPLAY_BASE) / CELL_X; index = vm->ram[addr] & 0x7f; 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)); d->pixbuf_dirty = 1; } /* init the pixel buffer */ DCPU16_DISPLAY *display_new(const char *filename) { DCPU16_DISPLAY *d = calloc(1, sizeof *d); if (d == NULL) return NULL; d->pixbuf = calloc(PIX_X * PIX_Y, sizeof *(d->pixbuf)); if (d->pixbuf == NULL) { free(d); return NULL; } d->outfile = strdup(filename); if (d->outfile == NULL) { free(d->pixbuf); free(d); 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); }