12 #endif /* HAVE_LIBPNG */
16 #include "chargen-4x8.h"
19 preliminary attempt at handling display
21 currently keeps raw display output in a pixel buffer, which is updated on every write to cpu's memory in display-space.
22 every [n] cycles, display buffer is rendered into an image file (png or pnm) in memory
27 #define DISPLAY_BASE 0x8000
28 #define DISPLAY_END 0x8179
29 #define DISPLAY_CELL_MAP 0x8180
30 #define DISPLAY_MISC 0x8280
42 /* total display is 160 pixels by 128 pixels */
43 /* active display is 32x12 cells (each 4x8 pixels), surrounded by 16 pixel border */
44 /* cells are rendered from cell map, which is bitmap of two words, defining four eight-bit columns */
52 /* buf will hold image file in memory */
53 struct memstream_cookie_
{
61 struct dcpu16_display_
{
63 struct pixel_
*pixbuf
; /* raw pixels */
64 struct memstream_cookie_ memstream_cookie
; /* streambuffer contains rendered image file from pixbuf */
65 unsigned long long cycle_last_write
;
66 unsigned int pixbuf_dirty
: 1;
67 unsigned int file_dirty
: 1;
71 struct pixel_
pcolor_(unsigned int c
) {
72 struct pixel_ p
= { 0, 0, 0 };
75 case 0x1: p
.r
=0x00, p
.g
=0x00, p
.b
=0xaa; break; /* dark blue */
76 case 0x2: p
.r
=0x00, p
.g
=0xaa, p
.b
=0x00; break; /* green */
77 case 0x3: p
.r
=0x00, p
.g
=0xaa, p
.b
=0xaa; break; /* cyan */
78 case 0x4: p
.r
=0xaa, p
.g
=0x00, p
.b
=0x00; break; /* red */
79 case 0x5: p
.r
=0xaa, p
.g
=0x00, p
.b
=0xaa; break; /* magenta */
80 case 0x6: p
.r
=0xaa, p
.g
=0xaa, p
.b
=0x55; break; /* yellow [] */
81 case 0x7: p
.r
=0xaa, p
.g
=0xaa, p
.b
=0xff; break; /* pale blue */
82 case 0x8: p
.r
=0x55, p
.g
=0x55, p
.b
=0x55; break; /* grey */
83 case 0x9: p
.r
=0x55, p
.g
=0x55, p
.b
=0xff; break; /* also blue */
84 case 0xa: p
.r
=0x55, p
.g
=0xff, p
.b
=0x55; break; /* light green */
85 case 0xb: p
.r
=0x55, p
.g
=0xff, p
.b
=0xff; break; /* light cyan */
86 case 0xc: p
.r
=0xff, p
.g
=0x55, p
.b
=0x55; break; /* light red */
87 case 0xd: p
.r
=0xff, p
.g
=0x55, p
.b
=0xff; break; /* light magenta */
88 case 0xe: p
.r
=0xff, p
.g
=0xff, p
.b
=0x55; break; /* light yellow */
89 case 0xf: p
.r
=0xff, p
.g
=0xff, p
.b
=0xff; break; /* white */
90 default: p
.r
=0x00, p
.g
=0x00, p
.b
=0x00; /* black */
96 /* support functions for writing streams into memory buffer */
98 void memstream_cookie_init_(struct memstream_cookie_
*c
, size_t chunk_size
) {
106 c
->chunk_size
= chunk_size
;
110 void memstream_cookie_fini_(struct memstream_cookie_
*c
) {
114 c
->allocated
= c
->endpos
= c
->offset
= 0;
118 /* of course these can't be the same */
119 #ifdef HAVE_FOPENCOOKIE
120 ssize_t
memstream_write_fn_(void *c
, const char *buf
, size_t size
)
121 #else /* HAVE_FOPENCOOKIE */
122 int memstream_write_fn_(void *c
, const char *buf
, int size
)
123 #endif /* HAVE_FOPENCOOKIE */
125 struct memstream_cookie_
*cookie
= c
;
127 while ((size_t)size
+ cookie
->offset
> cookie
->allocated
) {
128 size_t new_allocated
= cookie
->allocated
+ cookie
->chunk_size
;
129 void *tmp_ptr
= realloc(cookie
->buf
, new_allocated
);
130 if (tmp_ptr
== NULL
) {
133 cookie
->buf
= tmp_ptr
;
134 cookie
->allocated
= new_allocated
;
137 memcpy(cookie
->buf
+ cookie
->offset
, buf
, size
);
138 cookie
->offset
+= size
;
139 if (cookie
->offset
> (off_t
)cookie
->endpos
) {
140 cookie
->endpos
= cookie
->offset
;
146 /* should this just flood-fill entire display? */
148 void display_draw_border_(struct pixel_
*pixbuf
, struct pixel_ color
) {
153 for (y
= 0; y
< PIX_BORDER
; y
++) {
154 for (x
= 0; x
< PIX_X
; x
++) {
157 i
= ((PIX_Y
- y
) * PIX_X
) + x
;
163 for (y
= PIX_BORDER
; y
< (PIX_Y
- PIX_BORDER
+ 1); y
++)
164 for (x
= 0; x
< PIX_BORDER
; x
++) {
167 pixbuf
[i
+ (PIX_X
- PIX_BORDER
)] = color
;
172 /* render a character cell to the display */
174 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
) {
175 struct pixel_
*cellstart
= pixbuf
; /* start of display */
176 unsigned int pix_x
, pix_y
;
177 unsigned char *cell_bitmap
= (unsigned char *)(cell_map
+ (index
* sizeof index
));
179 assert(cell_x
< CELL_X
);
180 assert(cell_y
< CELL_Y
);
182 cellstart
+= (PIX_X
* PIX_BORDER
); /* skip top border */
184 cellstart
+= (CELL_Y_SZ
* PIX_X
) * cell_y
; /* skip down to row */
186 cellstart
+= PIX_BORDER
; /* skip side border */
188 cellstart
+= (CELL_X_SZ
) * cell_x
; /* skip to column */
190 for (pix_x
= 0; pix_x
< CELL_X_SZ
; pix_x
++) {
191 for (pix_y
= 0; pix_y
< CELL_Y_SZ
; pix_y
++) {
192 if ((cell_bitmap
[pix_x
] >> pix_y
) & 0x01)
193 cellstart
[((CELL_Y_SZ
- pix_y
- 1) * PIX_X
) + pix_x
] = fg
;
195 cellstart
[((CELL_Y_SZ
- pix_y
- 1) * PIX_X
) + pix_x
] = bg
;
201 /* write status callback */
203 void display_png_write_row_cb_(png_structp png_ptr
, png_uint_32 row
, int pass
) {
204 (void)png_ptr
, (void)row
, (void)pass
;
206 fprintf(stderr, "%s:%u:%d\n", __func__, row, pass);
211 void display_png_user_error_fn_(png_structp png_ptr
, png_const_charp error_msg
) {
213 fprintf(stderr
, "PNG:ERROR:%s\n", error_msg
);
218 void display_png_user_warning_fn_(png_structp png_ptr
, png_const_charp warning_msg
) {
220 fprintf(stderr
, "PNG:WARNING:%s\n", warning_msg
);
226 void display_png_write_(struct dcpu16_display_
*d
) {
232 png_voidp user_error_ptr
= NULL
;
234 #ifdef HAVE_FOPENCOOKIE
235 /* linux-style memory stream */
236 cookie_io_functions_t cookie_io_functions
= {
238 .write
= memstream_write_fn_
,
242 f
= fopencookie(&d
->memstream_cookie
, "wb", cookie_io_functions
);
244 fprintf(stderr
, "%s():%s\n", "fopencookie", strerror(errno
));
248 #else /* HAVE_FOPENCOOKIE */
250 /* BSD-style memory stream */
251 f
= funopen(&d
->memstream_cookie
,
252 NULL
, /* don't care about read */
254 NULL
, /* don't care about seek */
255 NULL
/* don't care about close */
258 fprintf(stderr
, "%s():%s\n", "funopen", strerror(errno
));
262 #else /* HAVE_FUNOPEN */
263 /* default to writing file if we can't buffer in memory */
264 f
= fopen(d
->outfile
, "wb");
268 #endif /* HAVE_FUNOPEN */
269 #endif /* HAVE_FOPENCOOKIE */
271 png_ptr
= png_create_write_struct(PNG_LIBPNG_VER_STRING
, user_error_ptr
, display_png_user_error_fn_
, display_png_user_warning_fn_
);
272 if (png_ptr
== NULL
) {
276 info_ptr
= png_create_info_struct(png_ptr
);
277 if (info_ptr
== NULL
) {
278 png_destroy_write_struct(&png_ptr
, (png_infopp
)NULL
);
282 if (setjmp(png_jmpbuf(png_ptr
))) {
283 png_destroy_write_struct(&png_ptr
, &info_ptr
);
287 png_init_io(png_ptr
, f
);
288 png_set_write_status_fn(png_ptr
, display_png_write_row_cb_
);
290 /* png_set_filter(png_ptr, 0, PNG_FILTER_NONE); */
291 /* png_set_compression_level(png_ptr, Z_BEST_COMPRESSION); */
293 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
);
295 png_write_info(png_ptr
, info_ptr
);
297 for (i
= 0; i
< PIX_Y
; i
++) {
298 png_write_row(png_ptr
, (unsigned char *)(d
->pixbuf
+ (i
* PIX_X
)));
301 png_write_end(png_ptr
, info_ptr
);
303 png_destroy_write_struct(&png_ptr
, &info_ptr
);
308 #else /* HAVE_LIBPNG */
310 /* if PNG not available, just write pnm file */
312 void display_pnm_write_(struct dcpu16_display_
*d
) {
316 f
= fopen(d
->outfile
, "w");
318 fprintf(stderr
, "%s('%s'):%s\n", "fopen", filename
, strerror(errno
));
322 /* write header... */
326 fprintf(f
, "P6\n%d %d\n255\n", PIX_X
, PIX_Y
);
328 /* write out image bytes in r g b order */
329 for (i
= 0; i
< PIX_X
* PIX_Y
; i
++) {
330 fwrite(&d
->pixbuf
[i
].r
, 1, 1, f
);
331 fwrite(&d
->pixbuf
[i
].g
, 1, 1, f
);
332 fwrite(&d
->pixbuf
[i
].b
, 1, 1, f
);
338 #endif /* HAVE_LIBPNG */
341 void display_image_render_(struct dcpu16_display_
*d
) {
343 display_png_write_(d
);
344 #else /* HAVE_LIBPNG */
345 display_pnm_write_(d
);
346 #endif /* HAVE_LIBPNG */
351 /* the callback to register to be run on display init/reset */
352 /* currently this populates the chargen map 'from rom'.. */
353 /* and clears the display buffers */
354 void display_reset_fn(struct dcpu16
*vm
, dcpu16_acct_event e
, DCPU16_WORD addr
, void *data
) {
355 struct dcpu16_display_
*d
= (struct dcpu16_display_
*)data
;
359 memcpy(vm
->ram
+ DISPLAY_CELL_MAP
, chargen_4x8_glyphs
, sizeof chargen_4x8_glyphs
);
361 memset(vm
->ram
+ DISPLAY_BASE
, 0, (DISPLAY_END
- DISPLAY_BASE
) * sizeof *(vm
->ram
));
363 memset(d
->pixbuf
, 0, PIX_X
* PIX_Y
* sizeof *(d
->pixbuf
));
368 /* the callback to register with the cpu for occasionally doing something with the display buffer */
369 void display_cycle_fn(struct dcpu16
*vm
, dcpu16_acct_event e
, DCPU16_WORD addr
, void *data
) {
370 struct dcpu16_display_
*d
= (struct dcpu16_display_
*)data
;
372 const unsigned long long min_cycles_between_writes
= 1536;
373 const time_t min_seconds_between_file_write
= 5;
375 static time_t last_file_write_
= 0;
380 if (d
->pixbuf_dirty
) {
381 if (vm
->cycle
> d
->cycle_last_write
+ min_cycles_between_writes
) {
382 display_image_render_(d
);
384 d
->cycle_last_write
= vm
->cycle
;
390 if (now
> last_file_write_
+ min_seconds_between_file_write
) {
393 of
= fopen(d
->outfile
, "wb");
395 fprintf(stderr
, "%s('%s'):%s\n", "fopen", d
->outfile
, strerror(errno
));
399 fwrite(d
->memstream_cookie
.buf
, d
->memstream_cookie
.endpos
, 1, of
);
403 last_file_write_
= now
;
408 /* the callback to register with the cpu for watching memory updates */
409 /* 'data' is an allocated display buffer */
410 void display_fn(struct dcpu16
*vm
, dcpu16_acct_event e
, DCPU16_WORD addr
, void *data
) {
411 DCPU16_DISPLAY
*d
= (DCPU16_DISPLAY
*)data
;
412 unsigned char index
, blink
, bg
, fg
;
413 unsigned int cell_x
, cell_y
;
417 if (addr
< DISPLAY_BASE
|| addr
> DISPLAY_MISC
)
420 if (addr
> DISPLAY_END
&& addr
< DISPLAY_MISC
) {
421 unsigned char updated_index
= addr
- DISPLAY_CELL_MAP
;
422 /* a cell map was updated, refresh entire screen */
423 for (cell_x
= 0; cell_x
< CELL_X
; cell_x
++) {
424 for (cell_y
= 0; cell_y
< CELL_Y
; cell_y
++) {
425 addr
= DISPLAY_BASE
+ cell_x
+ (cell_y
* CELL_X
);
426 index
= vm
->ram
[addr
] & 0x7f;
427 if (index
!= updated_index
)
429 blink
= (vm
->ram
[addr
] >> 7) & 0x01;
430 bg
= (vm
->ram
[addr
] >> 8) & 0x0f;
431 fg
= (vm
->ram
[addr
] >> 12) & 0x0f;
432 display_draw_cell_(d
->pixbuf
, vm
->ram
+ DISPLAY_CELL_MAP
, index
, cell_x
, cell_y
, pcolor_(fg
), pcolor_(bg
));
440 if (addr
== DISPLAY_MISC
) {
441 /* new border color */
442 char border
= vm
->ram
[addr
] & 0x0f;
443 display_draw_border_(d
->pixbuf
, pcolor_(border
));
449 cell_x
= (addr
- DISPLAY_BASE
) % CELL_X
;
450 cell_y
= (addr
- DISPLAY_BASE
) / CELL_X
;
452 index
= vm
->ram
[addr
] & 0x7f;
453 blink
= (vm
->ram
[addr
] >> 7) & 0x01;
454 bg
= (vm
->ram
[addr
] >> 8) & 0x0f;
455 fg
= (vm
->ram
[addr
] >> 12) & 0x0f;
457 display_draw_cell_(d
->pixbuf
, vm
->ram
+ DISPLAY_CELL_MAP
, index
, cell_x
, cell_y
, pcolor_(fg
), pcolor_(bg
));
462 /* init the pixel buffer */
463 DCPU16_DISPLAY
*display_new(const char *filename
) {
464 DCPU16_DISPLAY
*d
= calloc(1, sizeof *d
);
468 d
->pixbuf
= calloc(PIX_X
* PIX_Y
, sizeof *(d
->pixbuf
));
469 if (d
->pixbuf
== NULL
) {
474 d
->outfile
= strdup(filename
);
475 if (d
->outfile
== NULL
) {
481 memstream_cookie_init_(&d
->memstream_cookie
, 1024 * 8);
483 d
->cycle_last_write
= 0;
490 void display_free(DCPU16_DISPLAY
*d
) {
496 memstream_cookie_fini_(&d
->memstream_cookie
);