-#include <stdlib.h>
-#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 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);
-}