dd8d33fdcb48fcdda46cef9f0dd718fc09042f30
[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 unsigned char x;
101
102 x = (color >> 0) & 0x000f;
103 pix->r = x | (x << 4);
104
105 x = (color >> 4) & 0x000f;
106 pix->g = x | (x << 4);
107
108 x = (color >> 8) & 0x000f;
109 pix->b = x | (x << 4);
110
111 x = (color >> 12) & 0x000f;
112 pix->a = x | (x << 4);
113 }
114
115 static
116 void pixbuf_border_paint_(struct pixel_ *pixbuf, struct pixel_ *border) {
117 size_t x, y, i;
118
119 TRACE("%s>> painted border", __func__);
120
121 /* top */
122 for (y = 0; y < PIX_BORDER; y++) {
123 for (x = 0; x < PIX_X; x++) {
124 i = (y * PIX_X) + x;
125 pixbuf[i] = *border;
126 i = ((PIX_Y - y) * PIX_X) + x;
127 pixbuf[i] = *border;
128 }
129 }
130
131 /* sides */
132 for (y = PIX_BORDER; y < (PIX_Y - PIX_BORDER + 1); y++)
133 for (x = 0; x < PIX_BORDER; x++) {
134 i = (y * PIX_X) + x;
135 pixbuf[i] = *border;
136 pixbuf[i + (PIX_X - PIX_BORDER)] = *border;
137 }
138 }
139
140 static
141 void font_tile_paint_(struct pixel_ *p, struct pixel_ *fg, struct pixel_ *bg, DCPU16_WORD *tile) {
142 size_t pix_x, pix_y;
143 unsigned char *font_bitmap = (unsigned char *)tile;
144
145 TRACE("%s>> fg:(%u,%u,%u) bg:(%u,%u,%u) font_bitmap:%02x %02x %02x %02x", __func__,
146 fg->r, fg->g, fg->b,
147 bg->r, bg->g, bg->b,
148 font_bitmap[0], font_bitmap[1], font_bitmap[2], font_bitmap[3]);
149
150 for (pix_x = 0; pix_x < CELL_X_SZ; pix_x++) {
151 for (pix_y = 0; pix_y < CELL_Y_SZ; pix_y++) {
152 if ((font_bitmap[pix_x] >> pix_y) & 0x01)
153 p[((CELL_Y_SZ - pix_y - 1) * PIX_X) + pix_x] = *fg;
154 else
155 p[((CELL_Y_SZ - pix_y - 1) * PIX_X) + pix_x] = *bg;
156 }
157 }
158 }
159
160 static
161 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) {
162 struct pixel_ *tilestart = pixbuf; /* start of display */
163 unsigned int cell_x = addr % (PIX_X / CELL_X_SZ),
164 cell_y = addr / (PIX_X / CELL_X_SZ);
165 struct pixel_ fg, bg;
166 DCPU16_WORD *font_bitmap;
167 int blink;
168
169 cell_x = (addr - base) % CELL_X;
170 cell_y = (addr - base) / CELL_X;
171
172 TRACE("%s>> addr:0x%04x col:%u row:%u v:%hu",
173 __func__,
174 addr,
175 cell_x, cell_y, mem[addr]);
176
177 blink = mem[addr] & 0x0080;
178
179 /* tiles take two words each */
180 font_bitmap = tiles + (2 * (mem[addr] & 0x7f));
181
182 pixel_color_(&bg, palette[(mem[addr] >> 8) & 0x0f]);
183 if (blink && blink_state)
184 pixel_color_(&fg, palette[(mem[addr] >> 8) & 0x0f]);
185 else
186 pixel_color_(&fg, palette[(mem[addr] >> 12) & 0x0f]);
187
188 tilestart += (PIX_X * PIX_BORDER); /* skip top border */
189 tilestart += (CELL_Y_SZ * PIX_X) * cell_y; /* skip down to row */
190
191 tilestart += PIX_BORDER; /* skip side border */
192 tilestart += (CELL_X_SZ) * cell_x; /* skip to column */
193
194 font_tile_paint_(tilestart, &fg, &bg, font_bitmap);
195 }
196
197 static
198 void lem1802_pixbuf_refresh_full_(struct lem1802_ *display, DCPU16_WORD *mem) {
199 struct pixel_ border;
200 size_t tile;
201
202 TRACE("%s>> video_base:0x%04x", __func__, display->video_base);
203
204 if (display->video_base == 0) {
205 /* disconnected, blank display. static might be fun, too */
206 memset(display->pixbuf, 0, PIX_X * PIX_Y * sizeof *display->pixbuf);
207 return;
208 }
209
210 pixel_color_(&border, display->border_color);
211 pixbuf_border_paint_(display->pixbuf, &border);
212
213 for (tile = 0; tile < CELL_X * CELL_Y; tile++) {
214 pixbuf_addr_paint_(display->pixbuf,
215 mem,
216 display->video_base,
217 display->video_base + tile,
218 display->palette_base ? mem + display->palette_base : palette_default_,
219 display->font_base ? mem + display->font_base : (unsigned short *)chargen_4x8_glyphs,
220 display->blink_state);
221 }
222 }
223
224 static
225 int pixbuf_render_pnm_(void *data, struct pixel_ *pixbuf, size_t x, size_t y) {
226 FILE *f = (FILE *)data;
227 size_t i;
228 fprintf(f, "P6\n"
229 "%lu %lu\n"
230 "255\n",
231 x, y);
232 for (i = 0; i < x * y; i++) {
233 fwrite(&pixbuf[i].r, 1, 1, f);
234 fwrite(&pixbuf[i].g, 1, 1, f);
235 fwrite(&pixbuf[i].b, 1, 1, f);
236 }
237 fclose(f);
238
239 return 0;
240 }
241
242 #ifdef HAVE_LIBPNG
243 static
244 void pixbuf_render_png_user_warning_(png_structp png, png_const_charp msg) {
245 (void)png, (void)msg;
246 }
247 static
248 void pixbuf_render_png_user_error_(png_structp png, png_const_charp msg) {
249 (void)png, (void)msg;
250 }
251 static
252 int pixbuf_render_png_(void *data, struct pixel_ *pixbuf, size_t x, size_t y) {
253 FILE *f = (FILE *)data;
254 int retval = 0;
255 png_structp png;
256 png_infop info;
257 size_t i;
258
259 png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, pixbuf_render_png_user_error_, pixbuf_render_png_user_warning_);
260 if (png == NULL) {
261 goto f_done;
262 }
263
264 info = png_create_info_struct(png);
265 if (info == NULL) {
266 png_destroy_write_struct(&png, (png_infopp)NULL);
267 goto f_done;
268 }
269
270 if (setjmp(png_jmpbuf(png))) {
271 png_destroy_write_struct(&png, &info);
272 goto f_done;
273 }
274
275 png_init_io(png, f);
276 png_set_IHDR(png, info, x, y, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
277 png_set_invert_alpha(png);
278 png_write_info(png, info);
279 for (i = 0; i < y; i++) {
280 png_write_row(png, (unsigned char *)(pixbuf + (i * x)));
281 }
282 png_write_end(png, info);
283
284 png_destroy_write_struct(&png, &info);
285
286 f_done:
287 fclose(f);
288
289 return retval;
290 }
291 #endif /* HAVE_LIBPNG */
292
293 #ifdef HAVE_LIBVNCSERVER
294 struct vnc_server_ {
295 rfbScreenInfoPtr rfbScreen;
296 };
297
298 /* create and return a new struct vnc_server_, for use as a vnc renderer's generic data */
299 void *lem1802_vnc_init_data(int argc, char *argv[], struct dcpu16_hw *hw) {
300 struct vnc_server_ *s;
301 struct pixel_ *pixbuf = ((struct lem1802_ *)(hw->data))->pixbuf;
302 int paddedWidth = PIX_X + ( (PIX_X & 3) ? (4 - (PIX_X & 3)) : 0 );
303 int height = PIX_Y;
304 int bitsPerSample = 8;
305 int samplesPerPixel = 3;
306 int bytesPerPixel = 4;
307
308 s = calloc(1, sizeof *s);
309 if (s == NULL)
310 return NULL;
311
312 s->rfbScreen = rfbGetScreen(&argc, argv, paddedWidth, height, bitsPerSample, samplesPerPixel, bytesPerPixel);
313 if (s->rfbScreen == NULL) {
314 free(s);
315 return NULL;
316 }
317
318 s->rfbScreen->desktopName = "NYA ELEKTRISKA LEM1802";
319 s->rfbScreen->alwaysShared = TRUE;
320
321 #if 0
322 s->rfbScreen->kbdAddEvent = HandleKey; */
323 s->rfbScreen->httpDir = "../classes";
324 #endif
325
326 s->rfbScreen->frameBuffer = (char *)pixbuf;
327
328 rfbInitServer(s->rfbScreen);
329
330 rfbRunEventLoop(s->rfbScreen,-1,TRUE);
331
332 TRACE("%s>> initialized new vnc server", __func__);
333
334 return s;
335 }
336
337 static
338 int pixbuf_render_vnc_(void *data, struct pixel_ *pixbuf, size_t x, size_t y) {
339 int retval = 0;
340 struct vnc_server_ *s = (struct vnc_server_ *)data;
341
342 (void)pixbuf;
343
344 /* derp */
345 rfbMarkRectAsModified(s->rfbScreen,0,0,x,y);
346
347 TRACE("%s>>", __func__);
348
349 return retval;
350 }
351 #endif /* HAVE_LIBVNCSERVER */
352
353
354 static
355 void lem1802_reset_(struct dcpu16 *vm, void *data) {
356 struct lem1802_ *display = (struct lem1802_ *)data;
357
358 (void)vm;
359
360 display->cycle_activated = 0;
361 display->video_base = 0;
362 display->font_base = 0;
363 display->palette_base = 0;
364 display->border_color = 0;
365
366 memset(display->pixbuf, 0, PIX_X * PIX_Y * sizeof *display->pixbuf);
367
368 display->refresh_tally_ = 0;
369 display->blink_tally_ = 0;
370 display->blink_state = 0;
371
372 #if DEBUG
373 vm->trace_cb_("%s>>", __func__);
374 #endif /* DEBUG */
375 }
376
377 static
378 void lem1802_cycle_(struct dcpu16 *vm, void *data) {
379 struct lem1802_ *display = (struct lem1802_ *)data;
380
381 (void)vm;
382 /*
383 maybe just step through video memory (if set)
384 one word per clock..? could just cheat and
385 use accounting callbacks..
386
387 for now just count cycles and issue a full refresh/render
388 every so often
389 */
390
391 display->blink_tally_++;
392 if (display->blink_tally_ >= display->blink_rate) {
393 display->blink_tally_ = 0;
394 display->blink_state ^= 1;
395 TRACE("%s>> blink:%u (%u cycles)", __func__, display->blink_state, display->blink_rate);
396 }
397
398 display->refresh_tally_++;
399 if (display->refresh_tally_ >= display->refresh_rate) {
400 display->refresh_tally_ = 0;
401 if (display->render)
402 TRACE("%s>> refresh", __func__);
403 lem1802_pixbuf_refresh_full_(display, vm->ram);
404 display->render(display->renderer_data, display->pixbuf, PIX_X, PIX_Y);
405 }
406 }
407
408 static
409 void lem1802_hwi_(struct dcpu16 *vm, void *data) {
410 struct lem1802_ *display = (struct lem1802_ *)data;
411 DCPU16_WORD reg_a = vm->reg[DCPU16_REG_A];
412 DCPU16_WORD reg_b = vm->reg[DCPU16_REG_B];
413 size_t i;
414
415 TRACE("%s>> A:0x%04x B:0x%04x", __func__, reg_a, reg_b);
416
417 switch (reg_a) {
418 case 0: /* MEM_MAP_SCREEN */
419 if (display->cycle_activated == 0 && reg_b) {
420 display->cycle_activated = vm->cycle;
421 }
422 display->video_base = reg_b;
423 if (reg_b == 0)
424 display->cycle_activated = 0;
425 break;
426
427 case 1: /* MEM_MAP_FONT */
428 display->font_base = reg_b;
429 break;
430
431 case 2: /* MEM_MAP_PALETTE */
432 display->palette_base = reg_b;
433 break;
434
435 case 3: /* SET_BORDER_COLOR */
436 display->border_color = reg_b & 0x000f;
437 break;
438
439 case 4: /* MEM_DUMP_FONT */
440 for (i = 0; i < 128 ; i++) {
441 vm->ram[reg_b] = chargen_4x8_glyphs[reg_b][0] << 8;
442 vm->ram[reg_b] |= chargen_4x8_glyphs[reg_b][1];
443 reg_b += 1;
444 vm->ram[reg_b] = chargen_4x8_glyphs[reg_b][2] << 8;
445 vm->ram[reg_b] |= chargen_4x8_glyphs[reg_b][3];
446 reg_b += 1;
447 }
448 vm->cycle += 256;
449 break;
450
451 case 5: /* MEM_DUMP_PALETTE */
452 for (i = 0; i < 16; i++) {
453 vm->ram[reg_b] = palette_default_[i];
454 reg_b += 1;
455 }
456 vm->cycle += 16;
457 break;
458 }
459 }
460
461 static struct renderer_ {
462 char *name;
463 char *args;
464 int (*renderer)(void *, struct pixel_ *, size_t, size_t);
465 } lem1802_renderers_[] = {
466 { "pnm", "filename", pixbuf_render_pnm_ },
467 #ifdef HAVE_LIBPNG
468 { "png", "filename", pixbuf_render_png_ },
469 #endif /* HAVE_LIBPNG */
470 #ifdef HAVE_LIBVNCSERVER
471 { "vnc", "", pixbuf_render_vnc_ },
472 #endif /* HAVE_LIBVNCSERVER */
473 { "none", "", NULL },
474 { NULL, NULL, NULL }
475 };
476
477 int lem1802_renderer_set(struct dcpu16_hw *hw, const char *renderer, void *data) {
478 struct renderer_ *r;
479
480 for (r = lem1802_renderers_; r->renderer; r++) {
481 if (strcmp(renderer, r->name) == 0) {
482 ((struct lem1802_ *)(hw->data))->render = r->renderer;
483 ((struct lem1802_ *)(hw->data))->renderer_data = data;
484 TRACE("%s>> renderer set to %s", __func__, renderer);
485 return 0;
486 }
487 }
488
489 return -1;
490 }
491
492 char *lem1802_renderers_iter(void **iterp, char **name, char **args) {
493 struct renderer_ **r = (struct renderer_ **)iterp;
494
495 if (*r == NULL)
496 *r = lem1802_renderers_;
497 else
498 (*r)++;
499
500 if ((*r)->name == NULL) {
501 *r = NULL;
502 return NULL;
503 }
504
505 *name = (*r)->name;
506 *args = (*r)->args;
507
508 return (*r)->name;
509 }
510
511 /* instantitate a new display */
512 struct dcpu16_hw *lem1802_new(struct dcpu16 *vm) {
513 struct dcpu16_hw *hw;
514
515 hw = calloc(1, sizeof *hw);
516 if (hw == NULL) {
517 vm->warn_cb_("%s():%s", "calloc", strerror(errno));
518 return NULL;
519 }
520
521 memcpy(hw, &hw_, sizeof *hw);
522
523 hw->data = calloc(1, sizeof(struct dcpu16_hw));
524 if (hw->data == NULL) {
525 vm->warn_cb_("%s():%s", "calloc", strerror(errno));
526 free(hw);
527 return NULL;
528 }
529
530 ((struct lem1802_ *)(hw->data))->pixbuf = calloc(1, PIX_X * PIX_Y * sizeof *((struct lem1802_ *)(hw->data))->pixbuf);
531 if (((struct lem1802_ *)(hw->data))->pixbuf == NULL) {
532 vm->warn_cb_("%s():%s", "calloc", strerror(errno));
533 free(hw->data);
534 free(hw);
535 return NULL;
536 }
537
538 ((struct lem1802_ *)(hw->data))->refresh_rate = 1666;
539 ((struct lem1802_ *)(hw->data))->blink_rate = 100000;
540
541 return hw;
542 }
543
544 void lem1802_del(struct dcpu16_hw **hw) {
545 if (hw) {
546 if (*hw) {
547 if ((*hw)->data) {
548 if (((struct lem1802_ *)(*hw)->data)->pixbuf) {
549 free(((struct lem1802_ *)(*hw)->data)->pixbuf);
550 ((struct lem1802_ *)(*hw)->data)->pixbuf = NULL;
551 }
552 free((*hw)->data);
553 (*hw)->data = NULL;
554 }
555 free(*hw);
556 }
557 *hw = NULL;
558 }
559 }