msx: fix loading small cartridge images
[qemu/z80.git] / hw / v9918.c
blob420910c53d2231540c2570d41ac8a21129f447e2
1 /*
2 * Texas Instruments TMS9918A Video Display Processor emulation
4 * Copyright (c) 2009 Juha Riihimäki
5 * Sprite code based on fMSX X11 screen drivers written by Arnold Metselaar
7 * This code is licensed under the GPL version 2
8 */
9 #include "hw.h"
10 #include "qemu-timer.h"
11 #include "isa.h"
12 #include "console.h"
13 #include "msx.h"
15 #define SCREEN_WIDTH 256
16 #define SCREEN_HEIGHT 192
17 #define BORDER_SIZE 16
19 #define VRAM_SIZE 0x4000
20 #define VRAM_ADDR(addr) ((addr) & (VRAM_SIZE - 1))
21 #define VRAM_ADDR_INC(addr) addr = VRAM_ADDR(addr + 1)
23 typedef struct {
24 qemu_irq irq;
25 DisplayState *ds;
26 int invalidate;
27 int render_dirty;
28 int vdp_dirty;
29 int zoom;
30 QEMUTimer *timer;
32 uint8_t *vram;
34 uint16_t addr;
35 int addr_mode;
36 int addr_seq;
37 uint8_t addr_latch;
38 uint8_t data;
39 uint8_t status;
40 uint8_t ctrl[8];
42 struct {
43 uint8_t plane;
44 uint8_t line;
45 } line_sprite[4];
46 } V9918State;
48 static void v9918_ctrl(V9918State *s, uint8_t reg, uint8_t value)
50 static const uint8_t mask[8] = {0x03, 0xfb, 0x0f, 0xff, 0x07, 0x7f, 0x07, 0xff};
51 reg &= 0x07;
52 value &= mask[reg];
53 s->ctrl[reg] = value;
54 if (reg == 1 && (s->status & 0x80)) {
55 qemu_set_irq(s->irq, value & 0x20);
59 uint32_t v9918_read(void *opaque, uint32_t addr)
61 uint32_t result;
62 V9918State *s = (V9918State *)opaque;
63 s->addr_seq = 1;
64 if (addr & 1) {
65 result = s->status;
66 s->status &= 0x1f;
67 qemu_irq_lower(s->irq);
68 } else {
69 result = s->data;
70 s->data = s->vram[s->addr];
71 VRAM_ADDR_INC(s->addr);
73 return result;
76 void v9918_write(void *opaque, uint32_t addr, uint32_t value)
78 V9918State *s = (V9918State *)opaque;
79 if (addr & 1) {
80 if (s->addr_seq) {
81 s->addr_seq = 0;
82 s->addr_latch = value;
83 } else {
84 s->addr_seq = 1;
85 if (value & 0x80) {
86 v9918_ctrl(s, value, s->addr_latch);
87 s->vdp_dirty = 1;
88 } else {
89 s->addr = VRAM_ADDR((value << 8) + s->addr_latch);
90 s->addr_mode = value & 0x40;
91 if (!s->addr_mode) {
92 s->data = s->vram[s->addr];
93 VRAM_ADDR_INC(s->addr);
97 } else {
98 s->addr_seq = 1;
99 if (s->addr_mode) {
100 s->vram[s->addr] = s->data = value;
101 VRAM_ADDR_INC(s->addr);
102 } else {
103 s->data = s->vram[s->addr];
104 VRAM_ADDR_INC(s->addr);
105 s->vram[s->addr] = value;
107 s->vdp_dirty = 1;
111 static void v9918_sprite_collision_check(V9918State *s)
113 const uint8_t *spr_tab = s->vram + ((int)(s->ctrl[5]) << 7);
114 const uint8_t *spr_gen = s->vram + ((int)(s->ctrl[6]) << 11);
115 const uint8_t *src;
116 uint8_t n;
117 for (n = 0, src = spr_tab; n < 32 && *src != 208; n++, src += 4);
118 if (s->ctrl[1] & 0x02) { /* 16x16 sprites */
119 uint8_t j;
120 for (j = 0, src = spr_tab; j < n; j++, src += 4) {
121 if (src[3] & 0x0f) { /* non-transparent color */
122 uint8_t i;
123 const uint8_t *d;
124 for (i = j + 1, d = src + 4; i < n; i++, d += 4) {
125 if (d[3] & 0x0f) { /* non-transparent color */
126 uint8_t dv = src[0] - d[0];
127 if (dv < 16 || dv > 240) {
128 uint8_t dh = src[1] - d[1];
129 if (dh < 16 || dh > 240) {
130 const uint8_t *ps = spr_gen + ((int)(src[2] & 0xfc) << 3);
131 const uint8_t *pd = spr_gen + ((int)(d[2] & 0xfc) << 3);
132 if (dv < 16) {
133 pd += dv;
134 } else {
135 dv = 256 - dv;
136 ps += dv;
138 if (dh > 240) {
139 dh = 256 - dh;
140 const uint8_t *t = ps;
141 ps = pd;
142 pd = t;
144 while (dv < 16) {
145 uint16_t ls = (((uint16_t)*ps << 8) + *(ps + 16));
146 uint16_t ld = (((uint16_t)*pd << 8) + *(pd + 16));
147 if (ld & (ls >> dh)) {
148 break;
149 } else {
150 dv++;
151 ps++;
152 pd++;
155 if (dv < 16) {
156 s->status |= 0x20; /* sprite collision */
157 return;
165 } else { /* 8x8 sprites */
166 uint8_t j;
167 for (j = 0, src = spr_tab; j < n; j++, src += 4) {
168 if (src[3] & 0x0f) { /* non-transparent color */
169 uint8_t i;
170 const uint8_t *d;
171 for (i = j + 1, d = src + 4; i < n; i++, d += 4) {
172 if (d[3] & 0x0f) { /* non-transparent color */
173 uint8_t dv = src[0] - d[0];
174 if (dv < 8 || dv > 248) {
175 uint8_t dh = src[1] - d[1];
176 if (dh < 8 || dh > 248) {
177 const uint8_t *ps = spr_gen + ((int)src[2] << 3);
178 const uint8_t *pd = spr_gen + ((int)d[2] << 3);
179 if (dv < 8) {
180 pd += dv;
181 } else {
182 dv = 256 - dv;
183 ps += dv;
185 if (dh > 248) {
186 dh = 256 - dh;
187 const uint8_t *t = ps;
188 ps = pd;
189 pd = t;
191 while (dv < 8 && !(*pd & (*ps >> dh))) {
192 dv++;
193 ps++;
194 pd++;
196 if (dv < 8) {
197 s->status |= 0x20; /* sprite collision */
198 return;
209 static unsigned int V9918Palette[16 * 3] = {
210 0, 0, 0,
211 0, 0, 0,
212 33, 200, 66,
213 94, 220, 120,
214 84, 85, 237,
215 125, 118, 252,
216 212, 82, 77,
217 66, 235, 245,
218 252, 85, 84,
219 255, 121, 120,
220 212, 193, 84,
221 230, 206, 128,
222 33, 176, 59,
223 201, 91, 186,
224 204, 204, 204,
225 255, 255, 255
228 typedef void (*v9918_render_fn_t)(V9918State *s, int scanline,
229 void *dest, unsigned int width);
231 #define ADDR_MSK(r, sh) ((((int)s->ctrl[r] + 1) << (sh)) - 1)
232 #define T_OFF(n, msk, len) ((msk) & (((1L << 17) - (len)) | (n)))
233 #define CHRTAB_MSK(p, sh) ((((int)(s->ctrl[2] & ((p) ? 0xdf : 0xff )) + 1) << (sh)) - 1)
234 #define CHRTAB(n, p, len) T_OFF((n), CHRTAB_MSK(p, 10), (len))
235 #define CHRGEN_MSK ADDR_MSK(4, 11)
236 #define CHRGEN(n, len) T_OFF(n, CHRGEN_MSK, len)
237 #define COLTAB_MSK ADDR_MSK(3, 6)
238 #define COLTAB(n, len) T_OFF(n, COLTAB_MSK, len)
239 #define SPRTAB_MSK ADDR_MSK(5, 7)
240 #define SPRTAB(n, len) T_OFF(n, SPRTAB_MSK, len)
242 #define BG_COLOR (s->ctrl[7] & 0x0f)
243 #define FG_COLOR (s->ctrl[7] >> 4)
245 #define BLANK_ENABLE (!(s->ctrl[1] & 0x40))
246 #define SPRITE_MAG (s->ctrl[1] & 0x01)
247 #define SPRITE_SIZE (s->ctrl[1] & 0x02)
249 /* IMPORTANT: for the sprite functions, "scanline" parameter is expected to be
250 * a visible line number (0-191) instead of a true scanline number as in what
251 * is passed to the graphics rendering functions */
252 static int v9918_scan_sprites(V9918State *s, int scanline,
253 int breakline, int maxsprites)
255 const uint8_t *spr_tab = s->vram + ((int)(s->ctrl[5]) << 7);
256 const uint8_t height = SPRITE_SIZE ? 16 : 8;
257 const uint8_t b = SPRITE_MAG;
258 int n, count;
259 for (n = 0, count = 0; n < 32 && count <= maxsprites; n++, spr_tab += 4) {
260 if (*spr_tab == breakline) {
261 break;
263 if (*spr_tab == breakline + 1) {
264 continue;
266 uint8_t sprline = (uint8_t)((scanline - (*spr_tab)) >> b);
267 if (sprline < height) {
268 if (count == maxsprites) {
269 if (!(s->status & 0x40)) {
270 s->status = (s->status & 0xa0) | 0x40 | n;
272 //count--;
273 break;
275 s->line_sprite[count].plane = n;
276 s->line_sprite[count++].line = sprline;
279 return count;
282 static uint8_t *v9918_render_sprites(V9918State *s, int scanline)
284 int nspr = v9918_scan_sprites(s, scanline, 208, 4);
285 if (!nspr) {
286 return NULL;
288 static uint8_t zbuf[SCREEN_WIDTH + 32];
289 memset(zbuf, 0, sizeof(zbuf));
291 const uint8_t *spr_gen = s->vram + ((int)(s->ctrl[6]) << 11);
292 const uint8_t *spr_tab = s->vram + ((int)(s->ctrl[5]) << 7);
293 const uint8_t b = SPRITE_MAG;
294 uint8_t h = SPRITE_SIZE ? 0xfc : 0xff;
295 int visible_count = 0;
296 while (nspr--) {
297 const uint8_t *sp = spr_tab + (s->line_sprite[nspr].plane << 2);
298 uint8_t c = sp[3];
299 if (c & 0x0f) {
300 int l = sp[1] - ((c & 0x80) >> 2);
301 const uint8_t *p = spr_gen + (((int)(sp[2] & h)) << 3) + s->line_sprite[nspr].line;
302 int k = ((int)*p) << 8;
303 if (h == 0xfc) {
304 k |= p[16];
306 if (l < 0) {
307 k <<= (-l) >> b;
308 l = 0;
310 if (k && l < 256) {
311 visible_count++;
312 c &= 0x0f;
313 for (; k && l < 256; k <<= 1, l += b + 1) {
314 if (k & 0x8000) {
315 zbuf[l] = c;
316 if (b && l + 1 < 256) {
317 zbuf[l + 1] = c;
325 return visible_count ? zbuf : NULL;
328 #include "pixel_ops.h"
329 #include "v9918_render_template.h"
331 static void v9918_render_screen(V9918State *s)
333 if (!is_graphic_console()) {
334 return;
337 int mode = ((s->ctrl[0] >> 1) & 1) | ((s->ctrl[1] >> 2) & 6);
338 if (mode < 3) {
339 mode++;
340 } else if (mode == 4) {
341 mode = 0;
342 } else {
343 return;
346 v9918_render_fn_t render_fn = 0;
347 if (s->zoom == 1) {
348 switch (ds_get_bits_per_pixel(s->ds)) {
349 case 8: render_fn = v9918_render_fn_8_z1[mode]; break;
350 case 15: render_fn = v9918_render_fn_15_z1[mode]; break;
351 case 16: render_fn = v9918_render_fn_16_z1[mode]; break;
352 case 24: render_fn = v9918_render_fn_24_z1[mode]; break;
353 case 32: render_fn = v9918_render_fn_32_z1[mode]; break;
354 default: break;
356 } else if (s->zoom == 2) {
357 switch (ds_get_bits_per_pixel(s->ds)) {
358 case 8: render_fn = v9918_render_fn_8_z2[mode]; break;
359 case 15: render_fn = v9918_render_fn_15_z2[mode]; break;
360 case 16: render_fn = v9918_render_fn_16_z2[mode]; break;
361 case 24: render_fn = v9918_render_fn_24_z2[mode]; break;
362 case 32: render_fn = v9918_render_fn_32_z2[mode]; break;
363 default: break;
366 uint8_t *fb = ds_get_data(s->ds);
367 int linesize = ds_get_linesize(s->ds);
368 if (!render_fn || !fb || linesize < s->zoom * SCREEN_WIDTH ||
369 ds_get_width(s->ds) < s->zoom * SCREEN_WIDTH ||
370 ds_get_height(s->ds) < s->zoom * SCREEN_HEIGHT) {
371 return;
374 int i = BG_COLOR * 3 ?: 3;
375 V9918Palette[0] = V9918Palette[i];
376 V9918Palette[1] = V9918Palette[i + 1];
377 V9918Palette[2] = V9918Palette[i + 2];
379 for (i = 0; i < SCREEN_HEIGHT + 2 * BORDER_SIZE; i++) {
380 render_fn(s, i, fb, linesize);
381 fb += linesize * s->zoom;
384 s->render_dirty = 1;
387 static void v9918_vertical_retrace(V9918State *s)
389 if (s->vdp_dirty) {
390 if (!(s->status & 0x20)) {
391 v9918_sprite_collision_check(s);
393 v9918_render_screen(s);
394 s->vdp_dirty = 0;
396 s->status |= 0x80;
397 if (s->ctrl[1] & 0x20) {
398 qemu_irq_raise(s->irq);
402 static void v9918_timer(void *opaque)
404 V9918State *s = (V9918State *)opaque;
405 v9918_vertical_retrace(s);
406 int64_t next = qemu_get_clock(vm_clock) + muldiv64(1, ticks_per_sec, 50);
407 qemu_mod_timer(s->timer, next);
410 static void v9918_invalidate_display(void *opaque)
412 V9918State *s = (V9918State *)opaque;
413 s->invalidate = 1;
416 static void v9918_update_display(void *opaque)
418 V9918State *s = (V9918State *)opaque;
419 if (s->invalidate) {
420 s->invalidate = 0;
421 if (ds_get_width(s->ds) != s->zoom * (SCREEN_WIDTH + 2 * BORDER_SIZE) ||
422 ds_get_height(s->ds) != s->zoom * (SCREEN_HEIGHT + 2 * BORDER_SIZE)) {
423 qemu_console_resize(s->ds,
424 s->zoom * (SCREEN_WIDTH + 2 * BORDER_SIZE),
425 s->zoom * (SCREEN_HEIGHT + 2 * BORDER_SIZE));
427 v9918_render_screen(s);
430 if (s->render_dirty) {
431 s->render_dirty = 0;
432 dpy_update(s->ds, 0, 0, ds_get_width(s->ds), ds_get_height(s->ds));
436 void v9918_change_zoom(void *opaque)
438 V9918State *s = (V9918State *)opaque;
439 s->invalidate = 1;
440 if (s->zoom == 1) {
441 s->zoom = 2;
442 } else {
443 s->zoom = 1;
447 void v9918_reset(void *opaque)
449 V9918State *s = (V9918State *)opaque;
450 s->addr = 0;
451 s->addr_mode = 0;
452 s->addr_seq = 0;
453 s->addr_latch = 0;
454 s->data = 0;
455 s->status = 0;
456 memset(s->ctrl, 0, sizeof(s->ctrl));
457 memset(s->vram, 0, VRAM_SIZE);
460 void *v9918_init(qemu_irq irq)
462 V9918State *s = (V9918State *)qemu_mallocz(sizeof(*s));
463 s->irq = irq;
464 s->invalidate = 1;
465 s->zoom = 1;
466 s->ds = graphic_console_init(v9918_update_display,
467 v9918_invalidate_display,
468 NULL, NULL, s);
469 s->vram = qemu_mallocz(VRAM_SIZE);
470 s->timer = qemu_new_timer(vm_clock, v9918_timer, s);
471 v9918_reset(s);
472 qemu_mod_timer(s->timer, qemu_get_clock(vm_clock));
473 return s;