starting to add timing to vm driver
[dcpu16] / hw_lem1802.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <errno.h>
5
6 #ifdef HAVE_LIBPNG
7 #include <setjmp.h>
8 #include <png.h>
9 #endif /* HAVE_LIBPNG */
10
11 #ifdef HAVE_LIBVNCSERVER
12 #include <rfb/rfb.h>
13 #include <rfb/keysym.h>
14 #endif /* HAVE_LIBVNCSERVER */
15
16 #include "dcpu16.h"
17 #include "chargen-4x8.h"
18 #include "hw_lem1802.h"
19
20 #undef DEBUG
21 #ifdef DEBUG
22 #define TRACE(...) do { printf("[debug] "); printf(__VA_ARGS__); printf("\n"); } while (0)
23 #else /* DEBUG */
24 #define TRACE(...) do {} while (0)
25 #endif /* DEBUG */
26
27 static dcpu16_hw_signal_t lem1802_reset_;
28 static dcpu16_hw_signal_t lem1802_cycle_;
29 static dcpu16_hw_signal_t lem1802_hwi_;
30 static struct dcpu16_hw hw_ = {
31 .name_ = "LEM1802 - Low Energy Monitor",
32 .id_l = 0xf615,
33 .id_h = 0x7349,
34 .ver = 0x1802,
35 .mfg_l = 0x8b36,
36 .mfg_h = 0x1c6c,
37 .hwi = lem1802_hwi_,
38 .cycle = lem1802_cycle_,
39 .reset = lem1802_reset_,
40 .data = (struct lem1802_ *)NULL
41 };
42
43 #define PIX_X 160 /* pixels in display */
44 #define PIX_Y 128 /* pixels in display */
45 #define PIX_BORDER 16 /* border pixels from edge to first tile */
46 #define CELL_X_SZ 4
47 #define CELL_Y_SZ 8
48 #define CELL_X ((PIX_X - (2 * PIX_BORDER)) / CELL_X_SZ) /* tiles in display */
49 #define CELL_Y ((PIX_Y - (2 * PIX_BORDER)) / CELL_Y_SZ) /* tiles in display */
50
51 #define PALETTE_ENTRIES 16
52 static const DCPU16_WORD palette_default_[PALETTE_ENTRIES] = {
53 0x0000, /* black */
54 0x000a, /* blue */
55 0x00a0, /* green */
56 0x00aa, /* cyan */
57 0x0a05, /* red */
58 0x0a0f, /* magenta */
59 0x0aa5, /* yellow */
60 0x0aaf, /* pale blue */
61 0x0555, /* grey */
62 0x055f, /* light blue */
63 0x05f5, /* light green*/
64 0x05ff, /* light cyan */
65 0x0f55, /* light red */
66 0x0f5f, /* light magenta */
67 0x0ff5, /* light yellow */
68 0x0fff, /* white */
69 };
70
71 struct pixel_ {
72 unsigned char r;
73 unsigned char g;
74 unsigned char b;
75 unsigned char a;
76 };
77
78 struct lem1802_ {
79 long long cycle_activated; /* for tracking 'turn on delay' */
80
81 DCPU16_WORD video_base;
82 DCPU16_WORD font_base;
83 DCPU16_WORD palette_base;
84 DCPU16_WORD border_color;
85 struct pixel_ *pixbuf;
86
87 unsigned int refresh_rate; /* redraw every n cycles */
88 unsigned int refresh_tally_; /* tick */
89
90 unsigned int blink_rate; /* toggle every n cycles? still figuring this out.. */
91 unsigned int blink_tally_; /* tick */
92 unsigned int blink_state;
93
94 int (*render)(void *, struct pixel_ *, size_t, size_t);
95 void *renderer_data;
96 };
97
98 static inline
99 void pixel_color_(struct pixel_ *pix, DCPU16_WORD color) {
100 pix->r = (color & 0x000f) | ((color & 0x000f) << 8);
101 pix->g = ((color & 0x00f0) >> 8) | (color & 0x00f0);
102 pix->b = ((color & 0x0f00) >> 8) | ((color & 0x0f00) >> 16);
103 pix->a = ((color & 0xf000) >> 16) | ((color & 0xf000) >> 24);
104 }
105
106 static
107 void pixbuf_border_paint_(struct pixel_ *pixbuf, struct pixel_ *border) {
108 size_t x, y, i;
109
110 TRACE("%s>> painted border", __func__);
111
112 /* top */
113 for (y = 0; y < PIX_BORDER; y++) {
114 for (x = 0; x < PIX_X; x++) {
115 i = (y * PIX_X) + x;
116 pixbuf[i] = *border;
117 i = ((PIX_Y - y) * PIX_X) + x;
118 pixbuf[i] = *border;
119 }
120 }
121
122 /* sides */
123 for (y = PIX_BORDER; y < (PIX_Y - PIX_BORDER + 1); y++)
124 for (x = 0; x < PIX_BORDER; x++) {
125 i = (y * PIX_X) + x;
126 pixbuf[i] = *border;
127 pixbuf[i + (PIX_X - PIX_BORDER)] = *border;
128 }
129 }
130
131 static
132 void font_tile_paint_(struct pixel_ *p, struct pixel_ *fg, struct pixel_ *bg, DCPU16_WORD *tile) {
133 size_t pix_x, pix_y;
134 unsigned char *font_bitmap = (unsigned char *)tile;
135
136 TRACE("%s>> fg:(%u,%u,%u) bg:(%u,%u,%u) font_bitmap:%02x %02x %02x %02x", __func__,
137 fg->r, fg->g, fg->b,
138 bg->r, bg->g, bg->b,
139 font_bitmap[0], font_bitmap[1], font_bitmap[2], font_bitmap[3]);
140
141 for (pix_x = 0; pix_x < CELL_X_SZ; pix_x++) {
142 for (pix_y = 0; pix_y < CELL_Y_SZ; pix_y++) {
143 if ((font_bitmap[pix_x] >> pix_y) & 0x01)
144 p[((CELL_Y_SZ - pix_y - 1) * PIX_X) + pix_x] = *fg;
145 else
146 p[((CELL_Y_SZ - pix_y - 1) * PIX_X) + pix_x] = *bg;
147 }
148 }
149 }
150
151 static
152 void pixbuf_addr_paint_(struct pixel_ *pixbuf, DCPU16_WORD *mem, DCPU16_WORD base, DCPU16_WORD addr, DCPU16_WORD *palette, DCPU16_WORD *tiles, unsigned int blink_state) {
153 struct pixel_ *tilestart = pixbuf; /* start of display */
154 unsigned int cell_x = addr % (PIX_X / CELL_X_SZ),
155 cell_y = addr / (PIX_X / CELL_X_SZ);
156 struct pixel_ fg, bg;
157 DCPU16_WORD *font_bitmap;
158 int blink;
159
160 cell_x = (addr - base) % CELL_X;
161 cell_y = (addr - base) / CELL_X;
162
163 TRACE("%s>> addr:0x%04x col:%u row:%u v:%hu",
164 __func__,
165 addr,
166 cell_x, cell_y, mem[addr]);
167
168 blink = mem[addr] & 0x0080;
169
170 /* tiles take two words each */
171 font_bitmap = tiles + (2 * (mem[addr] & 0x7f));
172
173 pixel_color_(&bg, palette[(mem[addr] >> 8) & 0x0f]);
174 if (blink && blink_state)
175 pixel_color_(&fg, palette[(mem[addr] >> 8) & 0x0f]);
176 else
177 pixel_color_(&fg, palette[(mem[addr] >> 12) & 0x0f]);
178
179 tilestart += (PIX_X * PIX_BORDER); /* skip top border */
180 tilestart += (CELL_Y_SZ * PIX_X) * cell_y; /* skip down to row */
181
182 tilestart += PIX_BORDER; /* skip side border */
183 tilestart += (CELL_X_SZ) * cell_x; /* skip to column */
184
185 font_tile_paint_(tilestart, &fg, &bg, font_bitmap);
186 }
187
188 static
189 void lem1802_pixbuf_refresh_full_(struct lem1802_ *display, DCPU16_WORD *mem) {
190 struct pixel_ border;
191 size_t tile;
192
193 TRACE("%s>> video_base:0x%04x", __func__, display->video_base);
194
195 if (display->video_base == 0) {
196 /* disconnected, blank display. static might be fun, too */
197 memset(display->pixbuf, 0, PIX_X * PIX_Y * sizeof *display->pixbuf);
198 return;
199 }
200
201 pixel_color_(&border, display->border_color);
202 pixbuf_border_paint_(display->pixbuf, &border);
203
204 for (tile = 0; tile < CELL_X * CELL_Y; tile++) {
205 pixbuf_addr_paint_(display->pixbuf,
206 mem,
207 display->video_base,
208 display->video_base + tile,
209 display->palette_base ? mem + display->palette_base : palette_default_,
210 display->font_base ? mem + display->font_base : (unsigned short *)chargen_4x8_glyphs,
211 display->blink_state);
212 }
213 }
214
215 static
216 int pixbuf_render_pnm_(void *data, struct pixel_ *pixbuf, size_t x, size_t y) {
217 FILE *f = (FILE *)data;
218 size_t i;
219 fprintf(f, "P6\n"
220 "%lu %lu\n"
221 "255\n",
222 x, y);
223 for (i = 0; i < x * y; i++) {
224 fwrite(&pixbuf[i].r, 1, 1, f);
225 fwrite(&pixbuf[i].g, 1, 1, f);
226 fwrite(&pixbuf[i].b, 1, 1, f);
227 }
228 fclose(f);
229
230 return 0;
231 }
232
233 #ifdef HAVE_LIBPNG
234 static
235 void pixbuf_render_png_user_warning_(png_structp png, png_const_charp msg) {
236 (void)png, (void)msg;
237 }
238 static
239 void pixbuf_render_png_user_error_(png_structp png, png_const_charp msg) {
240 (void)png, (void)msg;
241 }
242 static
243 int pixbuf_render_png_(void *data, struct pixel_ *pixbuf, size_t x, size_t y) {
244 FILE *f = (FILE *)data;
245 int retval = 0;
246 png_structp png;
247 png_infop info;
248 size_t i;
249
250 png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, pixbuf_render_png_user_error_, pixbuf_render_png_user_warning_);
251 if (png == NULL) {
252 goto f_done;
253 }
254
255 info = png_create_info_struct(png);
256 if (info == NULL) {
257 png_destroy_write_struct(&png, (png_infopp)NULL);
258 goto f_done;
259 }
260
261 if (setjmp(png_jmpbuf(png))) {
262 png_destroy_write_struct(&png, &info);
263 goto f_done;
264 }
265
266 png_init_io(png, f);
267 png_set_IHDR(png, info, x, y, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
268 png_set_invert_alpha(png);
269 png_write_info(png, info);
270 for (i = 0; i < y; i++) {
271 png_write_row(png, (unsigned char *)(pixbuf + (i * x)));
272 }
273 png_write_end(png, info);
274
275 png_destroy_write_struct(&png, &info);
276
277 f_done:
278 fclose(f);
279
280 return retval;
281 }
282 #endif /* HAVE_LIBPNG */
283
284 #ifdef HAVE_LIBVNCSERVER
285 struct vnc_server_ {
286 rfbScreenInfoPtr rfbScreen;
287 };
288
289 /* create and return a new struct vnc_server_, for use as a vnc renderer's generic data */
290 void *lem1802_vnc_init_data(int argc, char *argv[], struct dcpu16_hw *hw) {
291 struct vnc_server_ *s;
292 struct pixel_ *pixbuf = ((struct lem1802_ *)(hw->data))->pixbuf;
293 int paddedWidth = PIX_X + ( (PIX_X & 3) ? (4 - (PIX_X & 3)) : 0 );
294 int height = PIX_Y;
295 int bitsPerSample = 8;
296 int samplesPerPixel = 3;
297 int bytesPerPixel = 4;
298
299 s = calloc(1, sizeof *s);
300 if (s == NULL)
301 return NULL;
302
303 s->rfbScreen = rfbGetScreen(&argc, argv, paddedWidth, height, bitsPerSample, samplesPerPixel, bytesPerPixel);
304 if (s->rfbScreen == NULL) {
305 free(s);
306 return NULL;
307 }
308
309 s->rfbScreen->desktopName = "NYA ELEKTRISKA LEM1802";
310 s->rfbScreen->alwaysShared = TRUE;
311
312 #if 0
313 s->rfbScreen->kbdAddEvent = HandleKey; */
314 s->rfbScreen->httpDir = "../classes";
315 #endif
316
317 s->rfbScreen->frameBuffer = (char *)pixbuf;
318
319 rfbInitServer(s->rfbScreen);
320
321 rfbRunEventLoop(s->rfbScreen,-1,TRUE);
322
323 TRACE("%s>> initialized new vnc server", __func__);
324
325 return s;
326 }
327
328 static
329 int pixbuf_render_vnc_(void *data, struct pixel_ *pixbuf, size_t x, size_t y) {
330 int retval = 0;
331 struct vnc_server_ *s = (struct vnc_server_ *)data;
332
333 (void)pixbuf;
334
335 /* derp */
336 rfbMarkRectAsModified(s->rfbScreen,0,0,x,y);
337
338 TRACE("%s>>", __func__);
339
340 return retval;
341 }
342 #endif /* HAVE_LIBVNCSERVER */
343
344
345 static
346 void lem1802_reset_(struct dcpu16 *vm, void *data) {
347 struct lem1802_ *display = (struct lem1802_ *)data;
348
349 (void)vm;
350
351 display->cycle_activated = 0;
352 display->video_base = 0;
353 display->font_base = 0;
354 display->palette_base = 0;
355 display->border_color = 0;
356
357 memset(display->pixbuf, 0, PIX_X * PIX_Y * sizeof *display->pixbuf);
358
359 display->refresh_tally_ = 0;
360 display->blink_tally_ = 0;
361 display->blink_state = 0;
362
363 #if DEBUG
364 vm->trace_cb_("%s>>", __func__);
365 #endif /* DEBUG */
366 }
367
368 static
369 void lem1802_cycle_(struct dcpu16 *vm, void *data) {
370 struct lem1802_ *display = (struct lem1802_ *)data;
371
372 (void)vm;
373 /*
374 maybe just step through video memory (if set)
375 one word per clock..? could just cheat and
376 use accounting callbacks..
377
378 for now just count cycles and issue a full refresh/render
379 every so often
380 */
381
382 display->blink_tally_++;
383 if (display->blink_tally_ >= display->blink_rate) {
384 display->blink_tally_ = 0;
385 display->blink_state ^= 1;
386 TRACE("%s>> blink:%u (%u cycles)", __func__, display->blink_state, display->blink_rate);
387 }
388
389 display->refresh_tally_++;
390 if (display->refresh_tally_ >= display->refresh_rate) {
391 display->refresh_tally_ = 0;
392 if (display->render)
393 TRACE("%s>> refresh", __func__);
394 lem1802_pixbuf_refresh_full_(display, vm->ram);
395 display->render(display->renderer_data, display->pixbuf, PIX_X, PIX_Y);
396 }
397 }
398
399 static
400 void lem1802_hwi_(struct dcpu16 *vm, void *data) {
401 struct lem1802_ *display = (struct lem1802_ *)data;
402 DCPU16_WORD reg_a = vm->reg[DCPU16_REG_A];
403 DCPU16_WORD reg_b = vm->reg[DCPU16_REG_B];
404 size_t i;
405
406 TRACE("%s>> A:0x%04x B:0x%04x", __func__, reg_a, reg_b);
407
408 switch (reg_a) {
409 case 0: /* MEM_MAP_SCREEN */
410 if (display->cycle_activated == 0 && reg_b) {
411 display->cycle_activated = vm->cycle;
412 }
413 display->video_base = reg_b;
414 if (reg_b == 0)
415 display->cycle_activated = 0;
416 break;
417
418 case 1: /* MEM_MAP_FONT */
419 display->font_base = reg_b;
420 break;
421
422 case 2: /* MEM_MAP_PALETTE */
423 display->palette_base = reg_b;
424 break;
425
426 case 3: /* SET_BORDER_COLOR */
427 display->border_color = reg_b & 0x000f;
428 break;
429
430 case 4: /* MEM_DUMP_FONT */
431 for (i = 0; i < 128 ; i++) {
432 vm->ram[reg_b] = chargen_4x8_glyphs[reg_b][0] << 8;
433 vm->ram[reg_b] |= chargen_4x8_glyphs[reg_b][1];
434 reg_b += 1;
435 vm->ram[reg_b] = chargen_4x8_glyphs[reg_b][2] << 8;
436 vm->ram[reg_b] |= chargen_4x8_glyphs[reg_b][3];
437 reg_b += 1;
438 }
439 vm->cycle += 256;
440 break;
441
442 case 5: /* MEM_DUMP_PALETTE */
443 for (i = 0; i < 16; i++) {
444 vm->ram[reg_b] = palette_default_[i];
445 reg_b += 1;
446 }
447 vm->cycle += 16;
448 break;
449 }
450 }
451
452 static struct renderer_ {
453 char *name;
454 char *args;
455 int (*renderer)(void *, struct pixel_ *, size_t, size_t);
456 } lem1802_renderers_[] = {
457 { "pnm", "filename", pixbuf_render_pnm_ },
458 #ifdef HAVE_LIBPNG
459 { "png", "filename", pixbuf_render_png_ },
460 #endif /* HAVE_LIBPNG */
461 #ifdef HAVE_LIBVNCSERVER
462 { "vnc", "", pixbuf_render_vnc_ },
463 #endif /* HAVE_LIBVNCSERVER */
464 { "none", "", NULL },
465 { NULL, NULL, NULL }
466 };
467
468 int lem1802_renderer_set(struct dcpu16_hw *hw, const char *renderer, void *data) {
469 struct renderer_ *r;
470
471 for (r = lem1802_renderers_; r->renderer; r++) {
472 if (strcmp(renderer, r->name) == 0) {
473 ((struct lem1802_ *)(hw->data))->render = r->renderer;
474 ((struct lem1802_ *)(hw->data))->renderer_data = data;
475 TRACE("%s>> renderer set to %s", __func__, renderer);
476 return 0;
477 }
478 }
479
480 return -1;
481 }
482
483 char *lem1802_renderers_iter(void **iterp, char **name, char **args) {
484 struct renderer_ **r = (struct renderer_ **)iterp;
485
486 if (*r == NULL)
487 *r = lem1802_renderers_;
488 else
489 (*r)++;
490
491 if ((*r)->name == NULL) {
492 *r = NULL;
493 return NULL;
494 }
495
496 *name = (*r)->name;
497 *args = (*r)->args;
498
499 return (*r)->name;
500 }
501
502 /* instantitate a new display */
503 struct dcpu16_hw *lem1802_new(struct dcpu16 *vm) {
504 struct dcpu16_hw *hw;
505
506 hw = calloc(1, sizeof *hw);
507 if (hw == NULL) {
508 vm->warn_cb_("%s():%s", "calloc", strerror(errno));
509 return NULL;
510 }
511
512 memcpy(hw, &hw_, sizeof *hw);
513
514 hw->data = calloc(1, sizeof(struct dcpu16_hw));
515 if (hw->data == NULL) {
516 vm->warn_cb_("%s():%s", "calloc", strerror(errno));
517 free(hw);
518 return NULL;
519 }
520
521 ((struct lem1802_ *)(hw->data))->pixbuf = calloc(1, PIX_X * PIX_Y * sizeof *((struct lem1802_ *)(hw->data))->pixbuf);
522 if (((struct lem1802_ *)(hw->data))->pixbuf == NULL) {
523 vm->warn_cb_("%s():%s", "calloc", strerror(errno));
524 free(hw->data);
525 free(hw);
526 return NULL;
527 }
528
529 ((struct lem1802_ *)(hw->data))->refresh_rate = 1666;
530 ((struct lem1802_ *)(hw->data))->blink_rate = 100000;
531
532 return hw;
533 }
534
535 void lem1802_del(struct dcpu16_hw **hw) {
536 if (hw) {
537 if (*hw) {
538 if ((*hw)->data) {
539 if (((struct lem1802_ *)(*hw)->data)->pixbuf) {
540 free(((struct lem1802_ *)(*hw)->data)->pixbuf);
541 ((struct lem1802_ *)(*hw)->data)->pixbuf = NULL;
542 }
543 free((*hw)->data);
544 (*hw)->data = NULL;
545 }
546 free(*hw);
547 }
548 *hw = NULL;
549 }
550 }