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 int memstream_write_fn_(void *c
, const char *buf
, int size
) {
119 struct memstream_cookie_
*cookie
= c
;
121 while ((size_t)size
+ cookie
->offset
> cookie
->allocated
) {
122 size_t new_allocated
= cookie
->allocated
+ cookie
->chunk_size
;
123 void *tmp_ptr
= realloc(cookie
->buf
, new_allocated
);
124 if (tmp_ptr
== NULL
) {
127 cookie
->buf
= tmp_ptr
;
128 cookie
->allocated
= new_allocated
;
131 memcpy(cookie
->buf
+ cookie
->offset
, buf
, size
);
132 cookie
->offset
+= size
;
133 if (cookie
->offset
> (off_t
)cookie
->endpos
) {
134 cookie
->endpos
= cookie
->offset
;
140 /* should this just flood-fill entire display? */
142 void display_draw_border_(struct pixel_
*pixbuf
, struct pixel_ color
) {
147 for (y
= 0; y
< PIX_BORDER
; y
++) {
148 for (x
= 0; x
< PIX_X
; x
++) {
151 i
= ((PIX_Y
- y
) * PIX_X
) + x
;
157 for (y
= PIX_BORDER
; y
< (PIX_Y
- PIX_BORDER
+ 1); y
++)
158 for (x
= 0; x
< PIX_BORDER
; x
++) {
161 pixbuf
[i
+ (PIX_X
- PIX_BORDER
)] = color
;
166 /* render a character cell to the display */
168 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
) {
169 struct pixel_
*cellstart
= pixbuf
; /* start of display */
170 unsigned int pix_x
, pix_y
;
171 unsigned char *cell_bitmap
= (unsigned char *)(cell_map
+ (index
* sizeof index
));
173 assert(cell_x
< CELL_X
);
174 assert(cell_y
< CELL_Y
);
176 cellstart
+= (PIX_X
* PIX_BORDER
); /* skip top border */
178 cellstart
+= (CELL_Y_SZ
* PIX_X
) * cell_y
; /* skip down to row */
180 cellstart
+= PIX_BORDER
; /* skip side border */
182 cellstart
+= (CELL_X_SZ
) * cell_x
; /* skip to column */
184 for (pix_x
= 0; pix_x
< CELL_X_SZ
; pix_x
++) {
185 for (pix_y
= 0; pix_y
< CELL_Y_SZ
; pix_y
++) {
186 if ((cell_bitmap
[pix_x
] >> pix_y
) & 0x01)
187 cellstart
[((CELL_Y_SZ
- pix_y
- 1) * PIX_X
) + pix_x
] = fg
;
189 cellstart
[((CELL_Y_SZ
- pix_y
- 1) * PIX_X
) + pix_x
] = bg
;
195 /* write status callback */
197 void display_png_write_row_cb_(png_structp png_ptr
, png_uint_32 row
, int pass
) {
198 (void)png_ptr
, (void)row
, (void)pass
;
200 fprintf(stderr, "%s:%u:%d\n", __func__, row, pass);
205 void display_png_user_error_fn_(png_structp png_ptr
, png_const_charp error_msg
) {
207 fprintf(stderr
, "PNG:ERROR:%s\n", error_msg
);
212 void display_png_user_warning_fn_(png_structp png_ptr
, png_const_charp warning_msg
) {
214 fprintf(stderr
, "PNG:WARNING:%s\n", warning_msg
);
220 void display_png_write_(struct dcpu16_display_
*d
) {
226 png_voidp user_error_ptr
= NULL
;
228 #ifdef HAVE_FOPENCOOKIE
229 /* linux-style memory stream */
230 struct cookie_io_functions_t cookie_io_functions
= {
232 .write
= memstream_write_fn_
,
236 f
= fopencookie(&d
->memstream_cookie
, "wb", cookie_io_functions
);
238 fprintf(stderr
, "%s():%s\n", "fopencookie", strerror(errno
));
242 #else /* HAVE_FOPENCOOKIE */
244 /* BSD-style memory stream */
245 f
= funopen(&d
->memstream_cookie
,
246 NULL
, /* don't care about read */
248 NULL
, /* don't care about seek */
249 NULL
/* don't care about close */
252 fprintf(stderr
, "%s():%s\n", "funopen", strerror(errno
));
256 #else /* HAVE_FUNOPEN */
257 /* default to writing file if we can't buffer in memory */
258 f
= fopen(d
->outfile
, "wb");
262 #endif /* HAVE_FUNOPEN */
263 #endif /* HAVE_FOPENCOOKIE */
265 png_ptr
= png_create_write_struct(PNG_LIBPNG_VER_STRING
, user_error_ptr
, display_png_user_error_fn_
, display_png_user_warning_fn_
);
266 if (png_ptr
== NULL
) {
270 info_ptr
= png_create_info_struct(png_ptr
);
271 if (info_ptr
== NULL
) {
272 png_destroy_write_struct(&png_ptr
, (png_infopp
)NULL
);
276 if (setjmp(png_jmpbuf(png_ptr
))) {
277 png_destroy_write_struct(&png_ptr
, &info_ptr
);
281 png_init_io(png_ptr
, f
);
282 png_set_write_status_fn(png_ptr
, display_png_write_row_cb_
);
284 /* png_set_filter(png_ptr, 0, PNG_FILTER_NONE); */
285 /* png_set_compression_level(png_ptr, Z_BEST_COMPRESSION); */
287 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
);
289 png_write_info(png_ptr
, info_ptr
);
291 for (i
= 0; i
< PIX_Y
; i
++) {
292 png_write_row(png_ptr
, (unsigned char *)(d
->pixbuf
+ (i
* PIX_X
)));
295 png_write_end(png_ptr
, info_ptr
);
297 png_destroy_write_struct(&png_ptr
, &info_ptr
);
302 #else /* HAVE_LIBPNG */
304 /* if PNG not available, just write pnm file */
306 void display_pnm_write_(struct dcpu16_display_
*d
) {
310 f
= fopen(d
->outfile
, "w");
312 fprintf(stderr
, "%s('%s'):%s\n", "fopen", filename
, strerror(errno
));
316 /* write header... */
320 fprintf(f
, "P6\n%d %d\n255\n", PIX_X
, PIX_Y
);
322 /* write out image bytes in r g b order */
323 for (i
= 0; i
< PIX_X
* PIX_Y
; i
++) {
324 fwrite(&d
->pixbuf
[i
].r
, 1, 1, f
);
325 fwrite(&d
->pixbuf
[i
].g
, 1, 1, f
);
326 fwrite(&d
->pixbuf
[i
].b
, 1, 1, f
);
332 #endif /* HAVE_LIBPNG */
335 void display_image_render_(struct dcpu16_display_
*d
) {
337 display_png_write_(d
);
338 #else /* HAVE_LIBPNG */
339 display_pnm_write_(d
);
340 #endif /* HAVE_LIBPNG */
345 /* the callback to register to be run on display init/reset */
346 /* currently this populates the chargen map 'from rom'.. */
347 /* and clears the display buffers */
348 void display_reset_fn(struct dcpu16
*vm
, dcpu16_acct_event e
, DCPU16_WORD addr
, void *data
) {
349 struct dcpu16_display_
*d
= (struct dcpu16_display_
*)data
;
353 memcpy(vm
->ram
+ DISPLAY_CELL_MAP
, chargen_4x8_glyphs
, sizeof chargen_4x8_glyphs
);
355 memset(vm
->ram
+ DISPLAY_BASE
, 0, (DISPLAY_END
- DISPLAY_BASE
) * sizeof *(vm
->ram
));
357 memset(d
->pixbuf
, 0, PIX_X
* PIX_Y
* sizeof *(d
->pixbuf
));
362 /* the callback to register with the cpu for occasionally doing something with the display buffer */
363 void display_cycle_fn(struct dcpu16
*vm
, dcpu16_acct_event e
, DCPU16_WORD addr
, void *data
) {
364 struct dcpu16_display_
*d
= (struct dcpu16_display_
*)data
;
366 const unsigned long long min_cycles_between_writes
= 1536;
367 const time_t min_seconds_between_file_write
= 5;
369 static time_t last_file_write_
= 0;
374 if (d
->pixbuf_dirty
) {
375 if (vm
->cycle
> d
->cycle_last_write
+ min_cycles_between_writes
) {
376 display_image_render_(d
);
378 d
->cycle_last_write
= vm
->cycle
;
384 if (now
> last_file_write_
+ min_seconds_between_file_write
) {
387 of
= fopen(d
->outfile
, "wb");
389 fprintf(stderr
, "%s('%s'):%s\n", "fopen", d
->outfile
, strerror(errno
));
393 fwrite(d
->memstream_cookie
.buf
, d
->memstream_cookie
.endpos
, 1, of
);
397 last_file_write_
= now
;
402 /* the callback to register with the cpu for watching memory updates */
403 /* 'data' is an allocated display buffer */
404 void display_fn(struct dcpu16
*vm
, dcpu16_acct_event e
, DCPU16_WORD addr
, void *data
) {
405 DCPU16_DISPLAY
*d
= (DCPU16_DISPLAY
*)data
;
406 unsigned char index
, blink
, bg
, fg
;
407 unsigned int cell_x
, cell_y
;
411 if (addr
< DISPLAY_BASE
|| addr
> DISPLAY_MISC
)
414 if (addr
> DISPLAY_END
&& addr
< DISPLAY_MISC
) {
415 unsigned char updated_index
= addr
- DISPLAY_CELL_MAP
;
416 /* a cell map was updated, refresh entire screen */
417 for (cell_x
= 0; cell_x
< CELL_X
; cell_x
++) {
418 for (cell_y
= 0; cell_y
< CELL_Y
; cell_y
++) {
419 addr
= DISPLAY_BASE
+ cell_x
+ (cell_y
* CELL_X
);
420 index
= vm
->ram
[addr
] & 0x7f;
421 if (index
!= updated_index
)
423 blink
= (vm
->ram
[addr
] >> 7) & 0x01;
424 bg
= (vm
->ram
[addr
] >> 8) & 0x0f;
425 fg
= (vm
->ram
[addr
] >> 12) & 0x0f;
426 display_draw_cell_(d
->pixbuf
, vm
->ram
+ DISPLAY_CELL_MAP
, index
, cell_x
, cell_y
, pcolor_(fg
), pcolor_(bg
));
434 if (addr
== DISPLAY_MISC
) {
435 /* new border color */
436 char border
= vm
->ram
[addr
] & 0x0f;
437 display_draw_border_(d
->pixbuf
, pcolor_(border
));
443 cell_x
= (addr
- DISPLAY_BASE
) % CELL_X
;
444 cell_y
= (addr
- DISPLAY_BASE
) / CELL_X
;
446 index
= vm
->ram
[addr
] & 0x7f;
447 blink
= (vm
->ram
[addr
] >> 7) & 0x01;
448 bg
= (vm
->ram
[addr
] >> 8) & 0x0f;
449 fg
= (vm
->ram
[addr
] >> 12) & 0x0f;
451 display_draw_cell_(d
->pixbuf
, vm
->ram
+ DISPLAY_CELL_MAP
, index
, cell_x
, cell_y
, pcolor_(fg
), pcolor_(bg
));
456 /* init the pixel buffer */
457 DCPU16_DISPLAY
*display_new(const char *filename
) {
458 DCPU16_DISPLAY
*d
= calloc(1, sizeof *d
);
462 d
->pixbuf
= calloc(PIX_X
* PIX_Y
, sizeof *(d
->pixbuf
));
463 if (d
->pixbuf
== NULL
) {
468 d
->outfile
= strdup(filename
);
469 if (d
->outfile
== NULL
) {
475 memstream_cookie_init_(&d
->memstream_cookie
, 1024 * 8);
477 d
->cycle_last_write
= 0;
484 void display_free(DCPU16_DISPLAY
*d
) {
490 memstream_cookie_fini_(&d
->memstream_cookie
);