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