/* ===== VDP.C ===== Handle changes to VDP memory, namely graphics changes. */ #define __VDP_INTERNAL__ #include "v9t9_common.h" #include "timer.h" #include "video.h" #include "memory.h" #include "vdp.h" #include "vdpsprites.h" #include "command.h" #include "v9t9.h" #include "demo.h" #include "9901.h" static void vdpwritereg(u16 addr); static vdp_redrawfunc vdp_redraw; static vdp_modify_info vdp_modify; u32 screenxsize, screenysize; u8 vdpregs[8]; u8 vdpbg, vdpfg; static u32 vdpmode; static u8 vdpram[16384]; static u16 vdpaddr; static char vdpaddrflag; static u8 vdpreadahead; static u8 vdpstatus; // We switched into text mode or changed bg color; on next update, // clear sides of screen and tell video module to update. static bool vdp_redraw_text_sides; /* * Configuration variables */ int videoupdatespeed = 30; int vdp_interrupt_rate = 60; bool draw_sprites = true; bool five_sprites_on_a_line = true; // five-sprite limit active? /* * Shared between vdp.c and vdpsprites.c */ #ifdef __VDP_INTERNAL__ u8 vdp_updarea[UPDATEBLOCK_ROW_STRIDE * 256]; vdp_mode_info vdp_mode; vdp_changes_info vdp_changes; static u16 bitpattmask, bitcolormask; u32 sprwidth = 8; /* in pixels */ #endif #if 1 u8 vdp_palette[17][3] = { { 0x00, 0x00, 0x00 }, // clear { 0x00, 0x00, 0x00 }, // black { 0x40, 0xb0, 0x40 }, // medium green { 0x60, 0xc0, 0x60 }, // light green { 0x40, 0x40, 0xc0 }, // dark blue { 0x60, 0x60, 0xf0 }, // light blue { 0xc0, 0x40, 0x40 }, // dark red { 0x40, 0xf0, 0xf0 }, // cyan { 0xf0, 0x40, 0x40 }, // medium red { 0xff, 0x80, 0x60 }, // light red { 0xf0, 0xc0, 0x40 }, // dark yellow { 0xff, 0xe0, 0x60 }, // light yellow { 0x40, 0x80, 0x40 }, // dark green { 0xc0, 0x40, 0xc0 }, // magenta { 0xd0, 0xd0, 0xd0 }, // grey { 0xff, 0xff, 0xff }, // white { 0x00, 0x00, 0x00 }, // text fg }; #else // colors from Sean Young (sean@msxnet.org) u8 vdp_palette[17][3] = { { 0x00, 0x00, 0x00 }, // clear { 0x00, 0x00, 0x00 }, // black { 0x24, 0xDA, 0x24 }, // medium green { 0x6D, 0xFF, 0x48 }, // light green { 0x24, 0x24, 0xFF }, // dark blue { 0x48, 0x6D, 0xFF }, // light blue { 0xB6, 0x24, 0x24 }, // dark red { 0x48, 0xDA, 0xFF }, // cyan { 0xFF, 0x24, 0x24 }, // medium red { 0xFF, 0x6D, 0x6D }, // light red { 0xDA, 0xDA, 0x24 }, // dark yellow { 0xDA, 0xDA, 0x91 }, // light yellow { 0x24, 0x91, 0x24 }, // dark green { 0xDA, 0x48, 0xB6 }, // magenta { 0xB6, 0xB6, 0xB6 }, // grey { 0xFF, 0xFF, 0xFF }, // white { 0x00, 0x00, 0x00 }, // text fg }; #endif #define _L LOG_VIDEO | LOG_INFO /* Interface to v9t9.c. This installs a timer event that periodically refreshes the video display by using the currently selected video module. */ static int video_event_tag, vdp_interrupt_tag; static void vdp_interrupt(int tag) { if (vdpregs[1] & R1_INT) { trigger9901int(M_INT_VDP); // currentints |= M_INT_VDP; // handle9901(); // stateflag |= ST_INTERRUPT; } } static void video_update(int tag) { if (vdpregs[1] & R1_INT) { vdpstatus |= VDP_INTERRUPT; } if (features & FE_SHOWVIDEO) { vdp_update(); } } static void video_changing_rates(void) { if (!video_event_tag) video_event_tag = TM_UniqueTag(); else TM_ResetEvent(video_event_tag); if (videoupdatespeed <= 0) videoupdatespeed = 1; else if (videoupdatespeed > TM_HZ) videoupdatespeed = TM_HZ; TM_SetEvent(video_event_tag, TM_HZ * 100 / videoupdatespeed, 0, TM_REPEAT | TM_FUNC, video_update); if (!vdp_interrupt_tag) vdp_interrupt_tag = TM_UniqueTag(); else TM_ResetEvent(vdp_interrupt_tag); if (vdp_interrupt_rate <= 0) vdp_interrupt_rate = 1; else if (vdp_interrupt_rate > TM_HZ) vdp_interrupt_rate = TM_HZ; TM_SetEvent(vdp_interrupt_tag, TM_HZ * 100 / vdp_interrupt_rate, 0, TM_REPEAT | TM_FUNC, vdp_interrupt); } int video_restart(void) { video_changing_rates(); return 1; } void video_restop(void) { TM_ResetEvent(video_event_tag); TM_ResetEvent(vdp_interrupt_tag); } /*********************************/ /* Write to memory-mapped port for VDP */ static s8 vdp_read_byte(const mrstruct *mr, u32 addr) { return vdpram[addr & 0x3fff]; } static void vdp_write_byte(const mrstruct *mr, u32 addr, s8 val) { addr &= 0x3fff; if (vdpram[addr] != val) { vdpram[addr] = val; if (stateflag & ST_DEMOING) { demo_record_event(demo_type_video, addr, val); } vdp_touch(addr); } } mrstruct vdp_memory_handler = { vdpram, 0L, 0L, NULL, vdp_read_byte, NULL, vdp_write_byte }; void vdp_memory_init(void) { memory_insert_new_entry(MEMENT_VIDEO, 0x0000, 0x4000, "VDP memory", 0L /*filename*/, 0L /*fileoffs*/, &vdp_memory_handler); } /* addr == 0 or addr != 0 based on write to >8C00 or >8C02 */ void vdp_mmio_write(u16 addr, u8 val) { if (addr) { /* >8C02, address write */ vdpaddr = (vdpaddr >> 8) | (val << 8); if (!(vdpaddrflag ^= 1)) { if (vdpaddr & 0x8000) { vdpaddr &= 0x3fff; vdpwritereg(vdpaddr); if (stateflag & ST_DEMOING) { demo_record_event(demo_type_video, vdpaddr | 0x8000); } } else if (vdpaddr & 0x4000) { vdpaddr &= 0x3fff; } else { // read ahead one byte vdpreadahead = vdpram[vdpaddr]; vdpaddr = (vdpaddr+1) & 0x3fff; } } } else { /* >8C00, data write */ /* this flag is used to verify that the VDP address was written as >4000 + vdpaddr. If not, then writing to it functions as a read-before-write. */ vdpaddrflag = 0; domain_write_byte(md_video, vdpaddr, val); vdpaddr = (vdpaddr + 1) & 0x3fff; vdpreadahead = val; } } /* Read a byte from the VDP. */ s8 vdp_mmio_read(u16 addr) { s8 ret; vdpaddrflag = 0; if (addr & 2) { /* >8802, status read */ ret = vdpstatus; vdpstatus &= ~0xe0; // thierry: reset bits when read reset9901int(M_INT_VDP); } else { /* >8800, memory read */ ret = vdpreadahead; vdpreadahead = domain_read_byte(md_video, vdpaddr); vdpaddr = (vdpaddr + 1) & 0x3fff; } return ret; } extern u16 vdp_mmio_get_addr(void) { return vdpaddr; } extern void vdp_mmio_set_addr(u16 addr) { vdpaddr = addr & 0x3fff; } extern bool vdp_mmio_addr_is_complete(void) { return !vdpaddrflag; } extern void vdp_mmio_set_status(u8 status) { vdpstatus = status; } extern u8 vdp_mmio_get_status(void) { return vdpstatus; } /*********************************************************/ /* This section of code handles the mapping from a VDP address to the various areas in VDP memory that affect the generation of the display. */ int vdpchanged; void redraw_graphics(void); void redraw_bitmap(void); void redraw_text(void); void redraw_multi(void); void redraw_blank(void); /************************************************************************/ /* * Notify that an address in VDP has been modified externally. */ void vdp_touch(u32 addr) { if (vdp_mode.screen.base <= addr && addr < vdp_mode.screen.base + vdp_mode.screen.size) vdp_modify.screen(addr - vdp_mode.screen.base); if (vdp_mode.patt.base <= addr && addr < vdp_mode.patt.base + vdp_mode.patt.size) vdp_modify.patt(addr - vdp_mode.patt.base); if (vdp_mode.color.base <= addr && addr < vdp_mode.color.base + vdp_mode.color.size) vdp_modify.color(addr - vdp_mode.color.base); if (vdp_mode.sprite.base <= addr && addr < vdp_mode.sprite.base + vdp_mode.sprite.size) vdp_modify.sprite(addr - vdp_mode.sprite.base); if (vdp_mode.sprpat.base <= addr && addr < vdp_mode.sprpat.base + vdp_mode.sprpat.size) vdp_modify.sprpat(addr - vdp_mode.sprpat.base); } void vdp_dirty_screen(u32 x, u32 y, s32 sx, s32 sy) { u8 *ptr; int width; if (sx < 0 || sy < 0) return; if (vdpregs[1] & R1_TEXT) { // left blank column? if (x < 8) { vdp_redraw_text_sides = true; if (sx <= x) return; sx -= x; x = 0; // right blank column? } else if (x >= 240 + 8) { vdp_redraw_text_sides = true; return; } else { x -= 8; } width = 40; } else { width = 32; } if (x >= screenxsize || y >= screenysize) return; if (width == 40) { sx = (sx + (x % 6) + 5) / 6; x /= 6; } else { sx = (sx + (x & 7) + 7) >> 3; x >>= 3; } sy = (sy + (y & 7) + 7) >> 3; y >>= 3; if (x + sx > width) sx = width - x; if (y + sy > 24) sy = 24 - y; ptr = vdp_changes.screen + (y * width) + x; while (sy--) { memset(ptr, 1, sx); ptr += width; } vdpchanged = 1; } /*************************************************************/ static void vdp_dirty_sprites(void) { if (!draw_sprites) return; vdp_changes.sprite = -1; memset(vdp_changes.sprpat, 1, vdp_mode.sprpat.size >> 3); vdpchanged = 1; } static void vdp_dirty_all(void) { memset(vdp_changes.screen, SC_BACKGROUND, vdp_mode.screen.size); memset(vdp_changes.patt, 1, vdp_mode.patt.size >> 3); vdp_dirty_sprites(); vdpchanged = 1; vdp_redraw_text_sides = true; } /* Force a complete redraw of the display by making it look like the whole VDP context has changed. */ void vdpcompleteredraw(void) { u8 i; vdp_dirty_all(); for (i = 0; i < 8; i++) { vdpwritereg(((i | 0x80) << 8) + vdpregs[i]); } vdp_update(); } /*************************************************************/ /* VDP update routines All of these are type 'updatefunc' and are assigned to function pointers when the VDP mode is changed. These routines are called through 'vdp_touch'. */ static void modify_sprite_default(u32 offs) { vdp_changes.sprite |= SPRBIT(offs >> 2); vdpchanged = 1; /* debug("modify_sprite_default (%d)\n",offs>>2); */ } static void modify_sprpat_default(u32 offs) { u32 patt; if (vdpregs[1] & R1_SPR4) { patt = (offs >> 3) & 0xfc; memset(&vdp_changes.sprpat[patt], 1, 4); } else { patt = offs >> 3; vdp_changes.sprpat[patt] = 1; } vdpchanged = 1; } /*************************************************************/ /* Possible optimization -- if the overall change from the previous refresh to the next is none, don't bother to redraw. This has the deficiency that it would require a copy of the VDP RAM to be saved at each refresh. */ static void modify_screen_graphics(u32 offs) { vdp_changes.screen[offs] = vdpchanged = SC_BACKGROUND; } static void modify_patt_graphics(u32 offs) { vdp_changes.patt[offs >> 3] = vdpchanged = 1; } static void modify_color_graphics(u32 offs) { memset(&vdp_changes.patt[offs << 3], 1, 8); vdpchanged = 1; } /************************************************************/ static void modify_patt_bitmap(u32 offs) { vdp_changes.patt[offs >> 3] = vdpchanged = 1; } static void modify_color_bitmap(u32 offs) { vdp_changes.color[offs >> 3] = vdpchanged = 1; } /************************************************************/ /* This routine updates all the updatefuncs when some register controlling the VDP context has changed. */ static void vdp_update_params(void) { u16 ramsize = (vdpregs[1] & R1_RAMSIZE) ? 0x3fff : 0xfff; /* Is the screen really blank? If so, respond to nothing but calls to vdp_dirty_screen */ if (!(vdpregs[1] & R1_NOBLANK)) { logger(_L | L_1, "vdp_update_params: blank screen\n"); vdp_mode.screen.base = 0; vdp_mode.screen.size = 0; vdp_mode.color.base = 0; vdp_mode.color.size = 0; vdp_mode.patt.base = 0; vdp_mode.patt.size = 0; vdp_mode.sprite.base = 0; vdp_mode.sprite.size = 0; vdp_mode.sprpat.base = 0; vdp_mode.sprpat.size = 0; screenxsize = 256; screenysize = 192; vdp_modify.patt = NULL; vdp_modify.sprite = vdp_modify.sprpat = NULL; vdp_modify.screen = NULL; vdp_modify.color = NULL; vdp_redraw = redraw_blank; return; } switch (vdpmode) { case MODE_GRAPHICS: logger(_L | L_1, "vdp_update_params: graphics mode\n"); vdp_mode.screen.base = (vdpregs[2] * 0x400) & ramsize; vdp_mode.screen.size = 768; vdp_mode.color.base = (vdpregs[3] * 0x40) & ramsize; vdp_mode.color.size = 32; vdp_mode.patt.base = (vdpregs[4] * 0x800) & ramsize; vdp_mode.patt.size = 2048; vdp_mode.sprite.base = (vdpregs[5] * 0x80) & ramsize; vdp_mode.sprite.size = 128; vdp_mode.sprpat.base = (vdpregs[6] * 0x800) & ramsize; vdp_mode.sprpat.size = 2048; screenxsize = 256; screenysize = 192; vdp_modify.screen = modify_screen_graphics; vdp_modify.color = modify_color_graphics; vdp_modify.patt = modify_patt_graphics; vdp_modify.sprite = modify_sprite_default; vdp_modify.sprpat = modify_sprpat_default; vdp_redraw = redraw_graphics; break; case MODE_TEXT: logger(_L | L_1, "vdp_update_params: text mode\n"); vdp_mode.screen.base = (vdpregs[2] * 0x400) & ramsize; vdp_mode.screen.size = 960; vdp_mode.color.base = 0; vdp_mode.color.size = 0; vdp_mode.patt.base = (vdpregs[4] * 0x800) & ramsize; vdp_mode.patt.size = 2048; vdp_mode.sprite.base = 0; vdp_mode.sprite.size = 0; vdp_mode.sprpat.base = 0; vdp_mode.sprpat.size = 0; // screenxsize = 240; screenxsize = 256; screenysize = 192; vdp_modify.patt = modify_patt_graphics; vdp_modify.sprite = vdp_modify.sprpat = NULL; vdp_modify.screen = modify_screen_graphics; vdp_modify.color = NULL; vdp_redraw_text_sides = true; vdp_redraw = redraw_text; break; case MODE_BITMAP: logger(_L | L_1, "vdp_update_params: bitmap mode\n"); vdp_mode.screen.base = (vdpregs[2] * 0x400) & ramsize; vdp_mode.screen.size = 768; vdp_mode.sprite.base = (vdpregs[5] * 0x80) & ramsize; vdp_mode.sprite.size = 128; vdp_mode.sprpat.base = (vdpregs[6] * 0x800) & ramsize; vdp_mode.sprpat.size = 2048; screenxsize = 256; screenysize = 192; vdp_modify.sprite = modify_sprite_default; vdp_modify.sprpat = modify_sprpat_default; vdp_modify.screen = modify_screen_graphics; vdp_modify.color = modify_color_bitmap; vdp_modify.patt = modify_patt_bitmap; vdp_redraw = redraw_bitmap; vdp_mode.color.base = (vdpregs[3] & 0x80) ? 0x2000 : 0; vdp_mode.color.size = 6144; bitcolormask = (((u16) (vdpregs[3] & 0x7f)) << 6) | 0x3f; vdp_mode.patt.base = (vdpregs[4] & 0x4) ? 0x2000 : 0; vdp_mode.patt.size = 6144; // thanks, Thierry! if (vdpregs[1] & 0x10) bitpattmask = (((u16) (vdpregs[4] & 0x03) << 11)) | 0x7ff; else bitpattmask = (((u16) (vdpregs[4] & 0x03) << 11)) | (bitcolormask & 0x7ff); break; case MODE_MULTI: logger(_L | L_1, "vdp_update_params: multi mode\n"); vdp_mode.screen.base = (vdpregs[2] * 0x400) & ramsize; vdp_mode.screen.size = 768; vdp_mode.color.base = 0; vdp_mode.color.size = 0; vdp_mode.patt.base = (vdpregs[4] * 0x800) & ramsize; vdp_mode.patt.size = 1536; vdp_mode.sprite.base = (vdpregs[5] * 0x80) & ramsize; vdp_mode.sprite.size = 128; vdp_mode.sprpat.base = (vdpregs[6] * 0x800) & ramsize; vdp_mode.sprpat.size = 2048; screenxsize = 256; screenysize = 192; vdp_modify.sprite = modify_sprite_default; vdp_modify.sprpat = modify_sprpat_default; vdp_modify.screen = modify_screen_graphics; vdp_modify.color = NULL; vdp_modify.patt = modify_patt_graphics; vdp_redraw = redraw_multi; break; default: logger(_L | LOG_FATAL, "Unknown graphics mode %d set\n\n", vdpmode); break; } } /* Figure out the VDP mode from the VDP mode registers */ static void vdp_update_mode(void) { if (vdpregs[0] & R0_BITMAP) vdpmode = MODE_BITMAP; else if (vdpregs[1] & R1_TEXT) vdpmode = MODE_TEXT; else if (vdpregs[1] & R1_MULTI) vdpmode = MODE_MULTI; else vdpmode = MODE_GRAPHICS; } /**************************/ #define REDRAW_NOW 1 /* same-mode change */ #define REDRAW_SPRITES 2 /* sprites change */ #define REDRAW_MODE 4 /* mode change */ #define REDRAW_BLANK 8 /* make blank */ #define REDRAW_PALETTE 16 /* palette update */ #define CHANGED(r,v) ((vdpregs[r]&(v))!=(val&(v))) static void vdpwritereg(u16 addr) { u8 reg = (addr >> 8) & 0xf; u8 val = addr & 0xff; int redraw = 0; u8 old = vdpregs[reg]; logger(_L | L_1, "vdpwritereg %1X=%2X\n", reg, val); switch (reg) { case 0: /* bitmap/video-in */ if (CHANGED(0, R0_BITMAP)) { redraw |= REDRAW_MODE; } vdpregs[0] = val; break; case 1: /* various modes, sprite stuff */ if (CHANGED(1, R1_NOBLANK)) { redraw |= REDRAW_BLANK | REDRAW_MODE; } if (CHANGED(1, R1_SPRMAG + R1_SPR4)) { redraw |= REDRAW_SPRITES; sprwidth = (val & (R1_SPRMAG + R1_SPR4)) == (R1_SPRMAG | R1_SPR4) ? 32 : (val & (R1_SPRMAG + R1_SPR4)) ? 16 : 8; logger(_L | L_1, "SprWidth=%d\n", sprwidth); } if (CHANGED(1, R1_TEXT | R1_MULTI)) { redraw |= REDRAW_MODE; } vdpregs[1] = val; break; case 2: /* screen image table */ if (vdpregs[2] != val) { redraw |= REDRAW_MODE; vdpregs[2] = val; } break; case 3: /* color table */ if (vdpregs[3] != val) { redraw |= REDRAW_MODE; vdpregs[3] = val; } break; case 4: /* pattern table */ if (vdpregs[4] != val) { redraw |= REDRAW_MODE; vdpregs[4] = val; } break; case 5: /* sprite table */ if (vdpregs[5] != val) { redraw |= REDRAW_MODE; vdpregs[5] = val; } break; case 6: /* sprite pattern table */ if (vdpregs[6] != val) { redraw |= REDRAW_MODE; vdpregs[6] = val; } break; case 7: /* foreground/background color */ if (vdpregs[7] != val) { vdpfg = val >> 4; vdpbg = val & 0xf; redraw |= REDRAW_PALETTE; vdpregs[7] = val; } break; default: logger(_L | L_1, "Undefined VDP register %d\n", reg); } /* This flag must be checked first because it affects the meaning of the following calls and checks. */ if (redraw & REDRAW_MODE) { vdp_update_mode(); vdp_update_params(); if (features & FE_SHOWVIDEO) VIDEO(resize, (screenxsize, screenysize)); /* clear edges? */ vdp_dirty_all(); } if (redraw & (REDRAW_SPRITES)) vdp_dirty_sprites(); if (redraw & REDRAW_PALETTE) if (features & FE_SHOWVIDEO) { VIDEO(setfgbg, (vdpfg, vdpbg)); // if screen is blank, force something to change if (!(vdpregs[1] & R1_NOBLANK)) redraw |= REDRAW_BLANK; vdp_redraw_text_sides = true; vdp_update(); } if (redraw & REDRAW_BLANK) if (!(vdpregs[1] & R1_NOBLANK)) { if (features & FE_SHOWVIDEO) { VIDEO(setblank, (vdpbg)); vdp_update(); } } else { vdp_update_params(); if (features & FE_SHOWVIDEO) { VIDEO(resetfromblank, ()); vdp_update(); } } } /************************************************************/ /* Redraw strategy: Using a 256-color mode, we will present to the video module a list of coordinates and 8x8 blocks of update information. There will only be one visible page, upon which all changes will be made. The point will be that, even with sprites, we won't draw to video memory at one place more than once. Instead, a bitmap is maintained in RAM that is only written to video memory after all applicable changes have been made. */ /* * Tell if an updateblock can be drawn with one color * * If collapse is true, treat color 0 as vdpfg and 16 and vdpbg; * this may cause problems on palettized displays when these values * change. */ bool video_block_is_solid(updateblock *ptr, bool collapse, u8 *color) { u8 *mem; u8 byt; int row; unsigned long long run; // get representative byte (i.e., one color) byt = ptr->data[0]; // duplicate it eight times run = byt | (byt<<8) | (byt<<16) | (byt<<24); run = (run << 32) | run; // compare the 8 rows in memory with the byte mem = ptr->data; for (row = 0; row < 8; row++) { if (*(unsigned long long *)mem != run) return false; mem += UPDATEBLOCK_ROW_STRIDE; } // matched! *color = byt; return true; #if 0 static u8 zeroes[8] = {0,0,0,0,0,0,0,0}; static u8 ones[8] = {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}; u8 fg, bg; bool colors_same; // if pattern is not set, we've got sprites if (!ptr->pattern) return false; // check for a run of the same colors... colors_same = false; // if colors set, we have bitmap mode, and // must make sure all of the colors are the same as each other. if (ptr->colors) { u8 colorrun[8]; memset(colorrun, ptr->colors[0], 8); if (memcmp(colorrun, ptr->colors, 8) == 0) { colors_same = true; // get those colors fg = (ptr->colors[0] & 0xf0) >> 4; bg = (ptr->colors[0] & 0xf); if (collapse) { if (!fg) fg = vdpfg; if (!bg) bg = vdpbg; } } } else { // all one colors are the same... colors_same = true; fg = (!collapse || ptr->fg) ? ptr->fg : vdpfg; bg = (!collapse || ptr->bg) ? ptr->bg : vdpbg; } // it could be solid if the colors are the same... if (!colors_same) return false; if (fg == bg) { *color = fg; return true; } // or if the pattern is solid... else if (memcmp(ptr->pattern, zeroes, 8) == 0) { *color = bg; return true; } else if (memcmp(ptr->pattern, ones, 8) == 0) { *color = fg; return true; } return false; #endif } /* These drawing functions use the highly optimized 256-color routines for mapping eight pixels to eight bytes in vdpdrawrow.c and vdpdrawrowtext.c. */ static void redraw_graphics_block(u8 * pattern, u8 color, updateblock *ull) { int i; u8 fg, bg; u8 *block; block = ull->data = UPDPTR(ull->r, ull->c); bg = color & 0xf; fg = color >> 4; ull->bg = bg; ull->fg = fg; ull->colors = NULL; // simple block ull->pattern = pattern; for (i = 0; i < 8; i++) { vdpdrawrow[*pattern++] (block, fg, bg); block += UPDATEBLOCK_ROW_STRIDE; } } /* The blank screen must be redrawn, i.e., when a minimized window is expanded again. */ void redraw_blank(void) { struct updateblock updatelist[768]; struct updateblock *ull = updatelist; u32 i; u8 *scptr; if (!vdpchanged) return; /* Redraw changed chars */ for (i = 0, scptr = vdp_changes.screen; i < 768; i++, scptr++) { if (*scptr) { /* this screen pos updated? */ ull->r = (i >> 5) << 3; ull->c = (i & 31) << 3; redraw_graphics_block(vdpram /*ignored*/, ((vdpregs[7] & 0xf) << 4) | (vdpregs[7] & 0xf), ull); ull++; /* next slot */ } } memset(vdp_changes.screen, 0, sizeof(vdp_changes.screen)); if (ull) /* any changes? (most likely) */ VIDEO(updatelist, (updatelist, ull - updatelist)); } void redraw_graphics(void) { struct updateblock updatelist[768]; struct updateblock *ull = updatelist; u32 i; u8 *scptr; u32 currchar; if (!vdpchanged) return; /* Set pattern changes in chars, for sprites */ for (i = 0, scptr = vdp_changes.screen; i < 768; i++, scptr++) { currchar = vdpram[vdp_mode.screen.base + i]; /* char # to update */ if (vdp_changes.patt[currchar]) /* this pattern changed? */ *scptr = 1; /* then this char changed */ } /* vdp_changes.sprite makes changes in vdp_changes.screen */ vdp_update_sprites(); /* Redraw changed chars */ for (i = 0, scptr = vdp_changes.screen; i < 768; i++, scptr++) { if (*scptr) { /* this screen pos updated? */ currchar = vdpram[vdp_mode.screen.base + i]; /* char # to update */ ull->r = (i >> 5) << 3; /* for graphics mode */ ull->c = (i & 31) << 3; redraw_graphics_block(&vdpram[vdp_mode.patt.base + (currchar << 3)], vdpram[vdp_mode.color.base + (currchar >> 3)], ull); /* can't redraw easily */ if (*scptr == SC_SPRITE_COVERING) ull->pattern = ull->colors = NULL; ull++; /* next slot */ } } /* draw sprites */ vdp_redraw_sprites(); memset(vdp_changes.screen, 0, sizeof(vdp_changes.screen)); memset(vdp_changes.patt, 0, 256); vdp_changes.sprite = 0; memset(vdp_changes.sprpat, 0, sizeof(vdp_changes.sprpat)); if (ull) /* any changes? (most likely) */ VIDEO(updatelist, (updatelist, ull - updatelist)); } static void redraw_bitmap_block(u8 * pattern, u8 * color, updateblock * ull) { int i; u8 fg, bg; u8 *block; block = ull->data = UPDPTR(ull->r, ull->c); ull->fg = vdpfg; ull->bg = vdpbg; ull->colors = color; ull->pattern = pattern; for (i = 0; i < 8; i++) { bg = *color & 0xf; fg = *color >> 4; color++; vdpdrawrow[*pattern++] (block, fg, bg); block += UPDATEBLOCK_ROW_STRIDE; } } void redraw_bitmap(void) { struct updateblock updatelist[768]; struct updateblock *ull = updatelist; u32 i; u8 *scptr; u32 currchar; u16 pp, cp; if (!vdpchanged) return; /* Set pattern or color changes in chars, for sprites */ for (i = 0, scptr = vdp_changes.screen; i < 768; i++, scptr++) { currchar = vdpram[vdp_mode.screen.base + i]; /* char # to update */ if (vdp_changes.patt[currchar + (i & 0x300)] || vdp_changes.color[currchar + (i & 0x300)]) *scptr = 1; } /* vdp_changes.sprite makes changes in vdp_changes.screen */ vdp_update_sprites(); /* Redraw changed chars */ for (i = 0, scptr = vdp_changes.screen; i < 768; i++, scptr++) { if (*scptr) { /* this screen pos updated? */ currchar = vdpram[vdp_mode.screen.base + i]; /* char # to update */ ull->r = (i >> 5) << 3; /* for graphics mode */ ull->c = (i & 31) << 3; pp = cp = (currchar + (i & 0x300)) << 3; pp &= bitpattmask; cp &= bitcolormask; redraw_bitmap_block(&vdpram[pp + vdp_mode.patt.base], &vdpram[cp + vdp_mode.color.base], ull); /* can't redraw easily */ if (*scptr == SC_SPRITE_COVERING) ull->pattern = ull->colors = NULL; ull++; /* next slot */ } } /* draw sprites */ vdp_redraw_sprites(); memset(vdp_changes.screen, 0, sizeof(vdp_changes.screen)); memset(vdp_changes.patt, 0, sizeof(vdp_changes.patt)); memset(vdp_changes.color, 0, sizeof(vdp_changes.color)); vdp_changes.sprite = 0; memset(vdp_changes.sprpat, 0, sizeof(vdp_changes.sprpat)); if (ull) /* any changes? (most likely) */ VIDEO(updatelist, (updatelist, ull - updatelist)); } static void redraw_text_block(u8 * pattern, updateblock *ull) { int i; u8 *block; block = ull->data = UPDPTR(ull->r, ull->c); ull->fg = vdpfg; ull->bg = vdpbg; ull->colors = NULL; //ull->pattern = pattern; ull->pattern = NULL; // we redraw 8x8 blocks; this is only 6x8 for (i = 0; i < 8; i++) { vdpdrawrowtext[*pattern++] (block); block += UPDATEBLOCK_ROW_STRIDE; } } static void redraw_text_side_block(updateblock *ull) { int i; u8 *block; static u8 ones[8] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; block = ull->data = UPDPTR(ull->r, ull->c); ull->fg = vdpbg; ull->bg = vdpbg; ull->colors = NULL; ull->pattern = ones; for (i = 0; i < 8; i++) { vdpdrawrow[0xff] (block, vdpbg, vdpbg); block += UPDATEBLOCK_ROW_STRIDE; } } void redraw_text(void) { struct updateblock updatelist[960 + 24*2]; struct updateblock *ull = updatelist; u32 i; u8 *scptr; u32 currchar; /* Set update blocks for text sides */ if (vdp_redraw_text_sides) { for (i = 0; i < 24; i++) { ull->r = i << 3; ull->c = 0; redraw_text_side_block(ull); ull++; ull->r = i << 3; ull->c = 256 - (256 - 240) / 2; redraw_text_side_block(ull); ull++; } vdp_redraw_text_sides = false; } if (vdpchanged) { /* Set pattern changes in chars */ for (i = 0, scptr = vdp_changes.screen; i < 960; i++, scptr++) { currchar = vdpram[vdp_mode.screen.base + i]; /* char # to update */ if (vdp_changes.patt[currchar]) /* this pattern changed? */ *scptr = 1; /* then this char changed */ } /* Redraw changed chars */ for (i = 0, scptr = vdp_changes.screen; i < 960; i++, scptr++) { if (*scptr) { /* this screen pos updated? */ currchar = vdpram[vdp_mode.screen.base + i]; /* char # to update */ ull->r = (i / 40) << 3; /* for graphics mode */ ull->c = (i % 40) * 6 + (256 - 240) / 2; redraw_text_block(&vdpram[vdp_mode.patt.base + (currchar << 3)], ull); ull++; /* next slot */ } } memset(vdp_changes.screen, 0, sizeof(vdp_changes.screen)); memset(vdp_changes.patt, 0, sizeof(vdp_changes.patt)); } if (ull) /* any changes? (most likely) */ VIDEO(updatelist, (updatelist, ull - updatelist)); } static void redraw_multi_block(u8 * color, updateblock *ull) { int i; u8 fg, bg; u8 *block; static u8 vdp_multi_block_pattern[] = { 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0 }; block = ull->data = UPDPTR(ull->r, ull->c); ull->fg = vdpfg; ull->bg = vdpbg; ull->colors = color; ull->pattern = vdp_multi_block_pattern; for (i = 0; i < 2; i++) { bg = *color & 0xf; fg = *color >> 4; color++; vdpdrawrow[0xf0] (block, fg, bg); vdpdrawrow[0xf0] (block + UPDATEBLOCK_ROW_STRIDE, fg, bg); vdpdrawrow[0xf0] (block + UPDATEBLOCK_ROW_STRIDE * 2, fg, bg); vdpdrawrow[0xf0] (block + UPDATEBLOCK_ROW_STRIDE * 3, fg, bg); block += UPDATEBLOCK_ROW_STRIDE * 4; } } void redraw_multi(void) { struct updateblock updatelist[768]; struct updateblock *ull = updatelist; u32 i; u8 *scptr; u32 currchar; u32 patt; if (!vdpchanged) return; /* Set pattern changes in chars, for sprites */ for (i = 0, scptr = vdp_changes.screen; i < 768; i++, scptr++) { currchar = vdpram[vdp_mode.screen.base + i]; /* char # to update */ if (vdp_changes.patt[currchar]) /* this pattern changed? */ *scptr = 1; /* then this char changed */ } /* vdp_changes.sprite makes changes in vdp_changes.screen */ vdp_update_sprites(); /* Redraw changed chars */ for (i = 0, scptr = vdp_changes.screen; i < 768; i++, scptr++) { if (*scptr) { /* this screen pos updated? */ currchar = vdpram[vdp_mode.screen.base + i]; /* char # to update */ ull->r = (i >> 5) << 3; /* for graphics mode */ ull->c = (i & 31) << 3; patt = vdp_mode.patt.base + (currchar << 3) + ((i >> 5) & 3) * 2; redraw_multi_block(&vdpram[patt], ull); /* can't redraw easily */ if (*scptr == SC_SPRITE_COVERING) ull->pattern = ull->colors = NULL; ull++; /* next slot */ } } /* draw sprites */ vdp_redraw_sprites(); memset(vdp_changes.screen, 0, sizeof(vdp_changes.screen)); memset(vdp_changes.patt, 0, 256); vdp_changes.sprite = 0; memset(vdp_changes.sprpat, 0, sizeof(vdp_changes.sprpat)); if (ull) /* any changes? (most likely) */ VIDEO(updatelist, (updatelist, ull - updatelist)); } /***************************************/ void vdp_update(void) { if (features & FE_VIDEO) { if (vdp_redraw) vdp_redraw(); } } static DECL_SYMBOL_ACTION(video_showvideo_toggle) { if (task == csa_WRITE) { if (MODULE_ITERATE(vmVideo,vmRestopModule) == vmOk) MODULE_ITERATE(vmVideo,vmRestartModule); } return 1; } static DECL_SYMBOL_ACTION(video_draw_sprites_toggle) { if (task == csa_WRITE) { vdp_dirty_all(); } return 1; } static DECL_SYMBOL_ACTION(video_change_rates) { video_changing_rates(); return 1; } void vdpinit(void) { command_symbol_table *videocommands = command_symbol_table_new("Video Options", "These are generic commands for controlling video emulation", command_symbol_new("ShowVideo", "Control whether the screen is displayed", c_STATIC, video_showvideo_toggle, RET_FIRST_ARG, command_arg_new_toggle ("on|off", "toggle video on or off", NULL /* action */ , ARG_NUM(features), FE_SHOWVIDEO, NULL /* next */ ) , command_symbol_new("VideoUpdateSpeed", "Control how often the screen is updated", c_STATIC, video_change_rates, RET_FIRST_ARG, command_arg_new_num ("hertz", "number of times per second", NULL /* action */ , ARG_NUM (videoupdatespeed), NULL /* next */ ) , command_symbol_new("VDPInterruptRate", "Control how often the VDP interrupts the CPU", c_STATIC, video_change_rates, RET_FIRST_ARG, command_arg_new_num ("hertz", "number of times per second", NULL /* action */ , ARG_NUM (vdp_interrupt_rate), NULL /* next */ ) , command_symbol_new("DrawSprites", "Control whether sprites are displayed", c_STATIC, video_draw_sprites_toggle, RET_FIRST_ARG, command_arg_new_num ("on|off", "toggle sprites on or off", NULL /* action */ , ARG_NUM(draw_sprites), NULL /* next */ ) , command_symbol_new("FiveSpritesOnLine", "Obey five-sprites-on-a-line limit of TMS9918A", c_STATIC, NULL /*action*/, RET_FIRST_ARG, command_arg_new_num ("on|off", "on: fifth sprite on a line not drawn (default); " "off: all sprites always drawn", NULL /* action */ , ARG_NUM(five_sprites_on_a_line), NULL /* next */ ) , NULL /* next */ ))))), NULL /* sub */ , NULL /* next */ ); command_symbol_table_add_subtable(universe, videocommands); features |= FE_VIDEO; memset(vdpregs, 0, 8); vdpregs[1] = 0xe0; vdp_update_mode(); vdp_update_params(); } /* * Callbacks from emulate.c */ DECL_SYMBOL_ACTION(vdp_set_register) { int reg, val; if (task == csa_READ) { #if 0 if (iter > 9) return 0; #else if (iter >= 8) return 0; #endif command_arg_set_num(sym->args, iter); if (iter < 8) command_arg_set_num(sym->args->next, vdpregs[iter]); else if (iter == 8) command_arg_set_num(sym->args->next, vdpreadahead); else if (iter == 9) command_arg_set_num(sym->args->next, vdpaddrflag); return 1; } command_arg_get_num(sym->args, ®); command_arg_get_num(sym->args->next, &val); if (reg < 8) { vdpwritereg(0x8000 | ((reg & 0x7) <<8) | (val & 0xff)); } // deprecated! Use VDPAddrFlag and VDPReadAhead now else if (reg == 8) { vdpreadahead = val; } else if (reg == 9) { vdpaddrflag = val; } return 1; } DECL_SYMBOL_ACTION(vdp_set_addr_flag) { int val; if (task == csa_READ) { if (iter > 0) return 0; command_arg_set_num(sym->args, vdpaddrflag); return 1; } command_arg_get_num(sym->args, &val); vdpaddrflag = val; return 1; } DECL_SYMBOL_ACTION(vdp_set_read_ahead) { int val; if (task == csa_READ) { if (iter > 0) return 0; command_arg_set_num(sym->args, vdpreadahead); return 1; } command_arg_get_num(sym->args, &val); vdpreadahead = val; return 1; } #ifdef WITH_LIB_PNG #include static int vdp_save_screen_png(char *filename) { FILE *fp; png_structp png_ptr; png_infop info_ptr; png_color png_palette[17]; int i; png_time ptime; png_color_16 pbkgd; png_text ptext; png_byte *row_pointers[256]; fp = fopen(filename, "wb"); if (!fp) { logger(_L|LOG_USER|LOG_ERROR, "Could not open '%s' for writing\n", filename); } png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, (png_voidp) 0L /* error_ptr */, 0L /* error_fn */, 0L /* warning_fn */); if (!png_ptr) { logger(_L|LOG_USER|LOG_ERROR, "Could not initialize PNG subsystem\n"); return 0; } info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { logger(_L|LOG_USER|LOG_ERROR, "Could not initialize PNG subsystem\n"); png_destroy_write_struct(&png_ptr, (png_infopp)0L); return 0; } if (setjmp(png_ptr->jmpbuf)) { logger(_L|LOG_USER|LOG_ERROR, "Failed to write PNG file '%s'\n", filename); png_destroy_write_struct(&png_ptr, &info_ptr); fclose(fp); return 0; } /* set up filters */ png_set_filter(png_ptr, 0, PNG_FILTER_NONE | PNG_FILTER_SUB | PNG_FILTER_PAETH); /* set up compression */ png_set_compression_level(png_ptr, Z_BEST_COMPRESSION); /* set up header */ png_set_IHDR(png_ptr, info_ptr, screenxsize, screenysize, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); /* set up palette */ for (i = 0; i <= 16; i++) { int j = (i == 0 ? vdpbg : i == 16 ? vdpfg : i); png_palette[i].red = vdp_palette[j][0]; png_palette[i].green = vdp_palette[j][1]; png_palette[i].blue = vdp_palette[j][2]; } png_set_PLTE(png_ptr, info_ptr, png_palette, 16); /* set up time */ png_convert_from_time_t(&ptime, time(0L)); png_set_tIME(png_ptr, info_ptr, &ptime); /* set up background and transparency */ // pbkgd.index = 0; /* paletted */ // png_set_tRNS(png_ptr, info_ptr, (png_bytep)&pbkgd.index, 1, 0L /*color16*/); // png_set_bKGD(png_ptr, info_ptr, &pbkgd); /* info text */ ptext.compression = PNG_TEXT_COMPRESSION_NONE; ptext.key = "Software"; ptext.text = "V9t9"; ptext.text_length = strlen(ptext.text); png_set_text(png_ptr, info_ptr, &ptext, 1); /* point to file */ png_init_io(png_ptr, fp); /* write info */ png_write_info(png_ptr, info_ptr); /* write rows */ for (i = 0; i < screenysize; i++) { row_pointers[i] = UPDPTR(i, 0); } png_write_image(png_ptr, row_pointers); /* done! */ png_write_end(png_ptr, info_ptr); png_destroy_write_struct(&png_ptr, &info_ptr); fclose(fp); return 1; } #endif // WITH_LIB_PNG /* * Callback from emulate.c */ static char *vdp_auto_name(void) { int count = 0; static char *pattern = "scrn-%03d.png"; static char buffer[OS_NAMESIZE]; OSSpec spec; OSError err; while (count < 999) { sprintf(buffer, pattern, count); if ((err = OS_MakeFileSpec(buffer, &spec)) != OS_NOERR) { logger(_L|LOG_ERROR|LOG_USER, "Cannot make filename '%s'\n", buffer); return NULL; } if ((err = OS_Status(&spec)) == OS_FNFERR) { logger(_L|LOG_USER, "Writing to '%s'\n", buffer); return buffer; } count++; } return NULL; } DECL_SYMBOL_ACTION(vdp_take_screenshot) { if (task == csa_WRITE) { char *filename; if (!command_arg_get_string(SYM_ARG_1st, &filename)) return 0; if (!filename || !*filename) filename = vdp_auto_name(); if (!filename) { logger(_L|LOG_ERROR|LOG_USER, "Could not make a filename for screen shot\n"); return 0; } #ifdef WITH_LIB_PNG return vdp_save_screen_png(filename); #endif logger(_L|LOG_USER|LOG_ERROR, "No graphics file formats supported!\n"); return 0; } return 1; }