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
10 #include "qemu-timer.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)
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};
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
)
62 V9918State
*s
= (V9918State
*)opaque
;
67 qemu_irq_lower(s
->irq
);
70 s
->data
= s
->vram
[s
->addr
];
71 VRAM_ADDR_INC(s
->addr
);
76 void v9918_write(void *opaque
, uint32_t addr
, uint32_t value
)
78 V9918State
*s
= (V9918State
*)opaque
;
82 s
->addr_latch
= value
;
86 v9918_ctrl(s
, value
, s
->addr_latch
);
89 s
->addr
= VRAM_ADDR((value
<< 8) + s
->addr_latch
);
90 s
->addr_mode
= value
& 0x40;
92 s
->data
= s
->vram
[s
->addr
];
93 VRAM_ADDR_INC(s
->addr
);
100 s
->vram
[s
->addr
] = s
->data
= value
;
101 VRAM_ADDR_INC(s
->addr
);
103 s
->data
= s
->vram
[s
->addr
];
104 VRAM_ADDR_INC(s
->addr
);
105 s
->vram
[s
->addr
] = value
;
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);
117 for (n
= 0, src
= spr_tab
; n
< 32 && *src
!= 208; n
++, src
+= 4);
118 if (s
->ctrl
[1] & 0x02) { /* 16x16 sprites */
120 for (j
= 0, src
= spr_tab
; j
< n
; j
++, src
+= 4) {
121 if (src
[3] & 0x0f) { /* non-transparent color */
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);
140 const uint8_t *t
= ps
;
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
)) {
156 s
->status
|= 0x20; /* sprite collision */
165 } else { /* 8x8 sprites */
167 for (j
= 0, src
= spr_tab
; j
< n
; j
++, src
+= 4) {
168 if (src
[3] & 0x0f) { /* non-transparent color */
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);
187 const uint8_t *t
= ps
;
191 while (dv
< 8 && !(*pd
& (*ps
>> dh
))) {
197 s
->status
|= 0x20; /* sprite collision */
209 static unsigned int V9918Palette
[16 * 3] = {
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
;
259 for (n
= 0, count
= 0; n
< 32 && count
<= maxsprites
; n
++, spr_tab
+= 4) {
260 if (*spr_tab
== breakline
) {
263 if (*spr_tab
== breakline
+ 1) {
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
;
275 s
->line_sprite
[count
].plane
= n
;
276 s
->line_sprite
[count
++].line
= sprline
;
282 static uint8_t *v9918_render_sprites(V9918State
*s
, int scanline
)
284 int nspr
= v9918_scan_sprites(s
, scanline
, 208, 4);
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;
297 const uint8_t *sp
= spr_tab
+ (s
->line_sprite
[nspr
].plane
<< 2);
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;
313 for (; k
&& l
< 256; k
<<= 1, l
+= b
+ 1) {
316 if (b
&& l
+ 1 < 256) {
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()) {
337 int mode
= ((s
->ctrl
[0] >> 1) & 1) | ((s
->ctrl
[1] >> 2) & 6);
340 } else if (mode
== 4) {
346 v9918_render_fn_t render_fn
= 0;
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;
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;
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
) {
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
;
387 static void v9918_vertical_retrace(V9918State
*s
)
390 if (!(s
->status
& 0x20)) {
391 v9918_sprite_collision_check(s
);
393 v9918_render_screen(s
);
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
;
416 static void v9918_update_display(void *opaque
)
418 V9918State
*s
= (V9918State
*)opaque
;
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
) {
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
;
447 void v9918_reset(void *opaque
)
449 V9918State
*s
= (V9918State
*)opaque
;
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
));
466 s
->ds
= graphic_console_init(v9918_update_display
,
467 v9918_invalidate_display
,
469 s
->vram
= qemu_mallocz(VRAM_SIZE
);
470 s
->timer
= qemu_new_timer(vm_clock
, v9918_timer
, s
);
472 qemu_mod_timer(s
->timer
, qemu_get_clock(vm_clock
));