fixes for assembler - label addrs and DAT statements
[dcpu16] / display.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <errno.h>
5 #include <assert.h>
6
7 #include "dcpu16.h"
8 #include "display.h"
9 #include "chargen-4x8.h"
10
11 /* preliminary attempt at handling display */
12 /* currently keeps display state in buffer, and just updates a PNM file on each screen update */
13
14 #define DISPLAY_BASE 0x8000
15 #define DISPLAY_END 0x8179
16 #define DISPLAY_CELL_MAP 0x8180
17 #define DISPLAY_MISC 0x8280
18
19 #define CELL_X 32
20 #define CELL_Y 12
21
22 #define CELL_X_SZ 4
23 #define CELL_Y_SZ 8
24
25 #define PIX_X 160
26 #define PIX_Y 128
27 #define PIX_BORDER 16
28
29 /* total display is 160 pixels by 128 pixels */
30 /* active display is 32x12 cells (each 4x8 pixels), surrounded by 16 pixel border */
31 /* cells are rendered from cell map, which is bitmap of two words, defining four eight-bit columns */
32
33 struct pixel_ {
34 char r;
35 char g;
36 char b;
37 };
38
39 static inline
40 DPIX pcolor_(unsigned int c) {
41 DPIX p = { 0, 0, 0 };
42
43 switch (c) {
44 case 0x1: p.r=0x00, p.g=0x00, p.b=0xaa; break; /* dark blue */
45 case 0x2: p.r=0x00, p.g=0xaa, p.b=0x00; break; /* green */
46 case 0x3: p.r=0x00, p.g=0xaa, p.b=0xaa; break; /* cyan */
47 case 0x4: p.r=0xaa, p.g=0x00, p.b=0x00; break; /* red */
48 case 0x5: p.r=0xaa, p.g=0x00, p.b=0xaa; break; /* magenta */
49 case 0x6: p.r=0xaa, p.g=0xaa, p.b=0x55; break; /* yellow [] */
50 case 0x7: p.r=0xaa, p.g=0xaa, p.b=0xff; break; /* pale blue */
51 case 0x8: p.r=0x55, p.g=0x55, p.b=0x55; break; /* grey */
52 case 0x9: p.r=0x55, p.g=0x55, p.b=0xff; break; /* also blue */
53 case 0xa: p.r=0x55, p.g=0xff, p.b=0x55; break; /* light green */
54 case 0xb: p.r=0x55, p.g=0xff, p.b=0xff; break; /* light cyan */
55 case 0xc: p.r=0xff, p.g=0x55, p.b=0x55; break; /* light red */
56 case 0xd: p.r=0xff, p.g=0x55, p.b=0xff; break; /* light magenta */
57 case 0xe: p.r=0xff, p.g=0xff, p.b=0x55; break; /* light yellow */
58 case 0xf: p.r=0xff, p.g=0xff, p.b=0xff; break; /* white */
59 default: p.r=0x00, p.g=0x00, p.b=0x00; /* black */
60 }
61
62 return p;
63 }
64
65 /* should this just flood-fill entire display? */
66 static inline
67 void display_draw_border(DPIX *pixbuf, DPIX color) {
68 size_t x, y;
69 size_t i;
70
71 /* top */
72 for (y = 0; y < PIX_BORDER; y++) {
73 for (x = 0; x < PIX_X; x++) {
74 i = (y * PIX_X) + x;
75 pixbuf[i] = color;
76 i = ((PIX_Y - y) * PIX_X) + x;
77 pixbuf[i] = color;
78 }
79 }
80
81 /* sides */
82 for (y = PIX_BORDER; y < (PIX_Y - PIX_BORDER + 1); y++)
83 for (x = 0; x < PIX_BORDER; x++) {
84 i = (y * PIX_X) + x;
85 pixbuf[i] = color;
86 pixbuf[i + (PIX_X - PIX_BORDER)] = color;
87 }
88
89 }
90
91 /* render a character cell to the display */
92 static inline
93 void display_draw_cell(DPIX *pixbuf, DCPU16_WORD *cell_map, DCPU16_WORD index, int cell_x, int cell_y, DPIX fg, DPIX bg) {
94 DPIX *cellstart = pixbuf; /* start of display */
95 unsigned int pix_x, pix_y;
96 unsigned char *cell_bitmap = (unsigned char *)(cell_map + (index * sizeof index));
97
98 assert(cell_x < CELL_X);
99 assert(cell_y < CELL_Y);
100
101 cellstart += (PIX_X * PIX_BORDER); /* skip top border */
102
103 cellstart += (CELL_Y_SZ * PIX_X) * cell_y; /* skip down to row */
104
105 cellstart += PIX_BORDER; /* skip side border */
106
107 cellstart += (CELL_X_SZ) * cell_x; /* skip to column */
108
109 for (pix_x = 0; pix_x < CELL_X_SZ; pix_x++) {
110 for (pix_y = 0; pix_y < CELL_Y_SZ; pix_y++) {
111 if ((cell_bitmap[pix_x] >> pix_y) & 0x01)
112 cellstart[((CELL_Y_SZ - pix_y - 1) * PIX_X) + pix_x] = fg;
113 else
114 cellstart[((CELL_Y_SZ - pix_y - 1) * PIX_X) + pix_x] = bg;
115 }
116 }
117 }
118
119 /* write pnm file */
120 void display_pnm_write(DPIX *pixbuf, const char *filename) {
121 size_t i;
122 FILE *f;
123
124 f = fopen(filename, "w");
125 if (f == NULL) {
126 fprintf(stderr, "%s('%s'):%s\n", "fopen", filename, strerror(errno));
127 return;
128 }
129
130 /* write header... */
131 /* PNM binary */
132 /* x y */
133 /* max value */
134 fprintf(f, "P6\n%d %d\n255\n", PIX_X, PIX_Y);
135
136 /* write out image bytes in r g b order */
137 for (i = 0; i < PIX_X * PIX_Y; i++) {
138 fwrite(&pixbuf[i].r, 1, 1, f);
139 fwrite(&pixbuf[i].g, 1, 1, f);
140 fwrite(&pixbuf[i].b, 1, 1, f);
141 }
142
143 fclose(f);
144 }
145
146
147 /* the callback to register to be run on display init/reset */
148 /* currently this populates the chargen map 'from rom'.. */
149 /* and clears the display buffers */
150 void display_reset_fn(struct dcpu16 *vm, dcpu16_acct_event e, DCPU16_WORD addr, void *data) {
151 DPIX *pixbuf = (DPIX *)data;
152
153 (void)e, (void)addr;
154
155 fprintf(stderr, "DEBUG: event:%u\n", e);
156
157 fprintf(stderr, "DEBUG: loading chargen map from 0x%04x to 0x%04x (%zuo)\n",
158 DISPLAY_CELL_MAP,
159 DISPLAY_CELL_MAP + (unsigned short)(sizeof chargen_4x8_glyphs / sizeof addr),
160 sizeof chargen_4x8_glyphs);
161 memcpy(vm->ram + DISPLAY_CELL_MAP, chargen_4x8_glyphs, sizeof chargen_4x8_glyphs);
162
163 fprintf(stderr, "DEBUG: clearing display buffer from 0x%04x to 0x%04x (%zuo)\n",
164 DISPLAY_BASE,
165 DISPLAY_END,
166 (DISPLAY_END - DISPLAY_BASE) * sizeof *(vm->ram));
167 memset(vm->ram + DISPLAY_BASE, 0, (DISPLAY_END - DISPLAY_BASE) * sizeof *(vm->ram));
168
169 fprintf(stderr, "DEBUG: clearing pixel buffer (%zuo)\n",
170 PIX_X * PIX_Y * sizeof *pixbuf);
171 memset(pixbuf, 0, PIX_X * PIX_Y * sizeof *pixbuf);
172 }
173
174 /* the callback to register with the cpu for watching memory updates */
175 /* user data is an allocated display buffer */
176 void display_fn(struct dcpu16 *vm, dcpu16_acct_event e, DCPU16_WORD addr, void *data) {
177 const char * const outfile = "dcpu16-display.pnm";
178 DPIX *pixbuf = (DPIX *)data;
179 unsigned char index, blink, bg, fg;
180 unsigned int cell_x, cell_y;
181
182 (void)e;
183
184 if (addr < DISPLAY_BASE || addr > DISPLAY_MISC)
185 return;
186
187 if (addr > DISPLAY_END && addr < DISPLAY_MISC) {
188 unsigned char updated_index = addr - DISPLAY_CELL_MAP;
189 /* a cell map was updated, refresh entire screen */
190 for (cell_x = 0; cell_x < CELL_X; cell_x++) {
191 for (cell_y = 0; cell_y < CELL_Y; cell_y++) {
192 addr = DISPLAY_BASE + cell_x + (cell_y * CELL_X);
193 index = vm->ram[addr] & 0x7f;
194 if (index != updated_index)
195 continue;
196 blink = (vm->ram[addr] >> 7) & 0x01;
197 bg = (vm->ram[addr] >> 8) & 0x0f;
198 fg = (vm->ram[addr] >> 12) & 0x0f;
199 display_draw_cell(pixbuf, vm->ram + DISPLAY_CELL_MAP, index, cell_x, cell_y, pcolor_(fg), pcolor_(bg));
200 }
201 }
202
203 display_pnm_write(pixbuf, outfile);
204 return;
205 }
206
207 if (addr == DISPLAY_MISC) {
208 /* new border color */
209 char border = vm->ram[addr] & 0x0f;
210 fprintf(stderr, "display event: new border\n");
211 display_draw_border(pixbuf, pcolor_(border));
212
213 display_pnm_write(pixbuf, outfile);
214 return;
215 }
216
217 cell_x = (addr - DISPLAY_BASE) % CELL_X;
218 cell_y = (addr - DISPLAY_BASE) / CELL_X;
219
220 index = vm->ram[addr] & 0x7f;
221 blink = (vm->ram[addr] >> 7) & 0x01;
222 bg = (vm->ram[addr] >> 8) & 0x0f;
223 fg = (vm->ram[addr] >> 12) & 0x0f;
224
225 fprintf(stderr, "display event: cell %ux%u:%u '%c'\n", cell_x, cell_y, index, index);
226
227 display_draw_cell(pixbuf, vm->ram + DISPLAY_CELL_MAP, index, cell_x, cell_y, pcolor_(fg), pcolor_(bg));
228 display_pnm_write(pixbuf, outfile);
229 }
230
231 /* init the pixel buffer */
232 DPIX *display_init_pixbuf(void) {
233 DPIX *pixbuf;
234
235 pixbuf = calloc(PIX_X * PIX_Y, sizeof *pixbuf);
236
237 return pixbuf;
238 }