/* This file is included into vb.c and cannot be compiled on its own. */ #ifdef VBAPI /********************************** Macros ***********************************/ /* Compute how many clocks are in some number of milliseconds */ #define vipClocksMs(x) x * 20000 /* Read a world line parameter value */ #define vipParam(x) busReadBuffer(&sim->vip.ram[x], VB_U16) /******************************** Lookup Data ********************************/ /* BG map arrangement by world background dimensions */ static const uint8_t BG_TEMPLATES[][64] = { { 0 }, /* 1x1 */ { 0, 1 }, /* 1x2 */ { 0, 1, 2, 3 }, /* 1x4 */ { 0, 1, 2, 3, 4, 5, 6, 7 }, /* 1x8 */ { 0, 1 }, /* 2x1 */ { 0, 1, 2, 3 }, /* 2x2 */ { 0, 1, 2, 3, 4, 5, 6, 7 }, /* 2x4 */ { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7 }, /* 2x8 */ { 0, 1, 2, 3 }, /* 4x1 */ { 0, 1, 2, 3, 4, 5, 6, 7 }, /* 4x2 */ { 0, 1, 0, 1, 2, 3, 2, 3, 4, 5, 4, 5, 6, 7, 6, 7 }, /* 4x4 */ { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, /* 4x8 */ 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7 }, { 0, 1, 2, 3, 4, 5, 6, 7 }, /* 8x1 */ { 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7, 4, 5, 6, 7 }, /* 8x2 */ { 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, /* 8x4 */ 4, 5, 4, 5, 4, 5, 4, 5, 6, 7, 6, 7, 6, 7, 6, 7 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, /* 8x8 */ 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7 } }; /* 8-bit color magnitude by brightness level */ static const uint8_t BRIGHT8[] = { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98,100,102,104,106,108,110,112,114,116,118,120,122,124,126, 129,131,133,135,137,139,141,143,145,147,149,151,153,155,157,159, 161,163,165,167,169,171,173,175,177,179,181,183,185,187,189,191, 193,195,197,199,201,203,205,207,209,211,213,215,217,219,221,223, 225,227,229,231,233,235,237,239,241,243,245,247,249,251,253,255 }; /*********************************** Types ***********************************/ /* Intersection of the screen and a world rectangle */ typedef struct { int32_t x1; /* Left edge inclusive */ int32_t x2; /* Right edge exclusive */ int32_t y1; /* Top edge inclusive */ int32_t y2; /* Bottom edge exclusive */ } Window; /***************************** Module Functions ******************************/ /* Read a palette */ static int32_t vipReadPalette(uint8_t *entries) { return entries[3] << 6 | entries[2] << 4 | entries[1] << 2; } /* Raise an interrupt request */ static void vipThrow(VB *sim, uint16_t cause) { sim->vip.intpnd |= cause; if (sim->vip.intenb & cause) sim->cpu.irq |= 0x0010; } /* Write a palette */ static void vipWritePalette(uint8_t *entries, int32_t mask, int32_t value) { if (mask & 0x00FF) return; entries[3] = value >> 6 & 3; entries[2] = value >> 4 & 3; entries[1] = value >> 2 & 3; } /* Read a typed value from an I/O register */ static int32_t vipReadIO(VB *sim, uint32_t address, int type) { int32_t mask = 0; /* Byte access mask */ int32_t value; /* Return value */ /* Adjustments by type */ switch (type) { case VB_S32: /* Word */ return vipReadIO(sim, address, VB_U16) | (uint32_t) vipReadIO(sim, address + 2, VB_U16) << 16; case VB_S8: /* Byte */ case VB_U8: mask = 0x00FF << ((address & 1) << 3); break; case VB_S16: /* Halfword */ case VB_U16: mask = 0xFFFF; } /* Access by register */ switch (address >> 1) { case 0x5F800>>1: /* INTPND */ value = sim->vip.intpnd; break; case 0x5F802>>1: /* INTENB */ value = sim->vip.intenb; break; case 0x5F820>>1: /* DPSTTS */ value = (int32_t) sim->vip.dp.lock << 10 | (int32_t) sim->vip.dp.synce << 9 | (int32_t) sim->vip.dp.re << 8 | (int32_t) sim->vip.dp.fclk << 7 | (int32_t) sim->vip.dp.scanrdy << 6 | (int32_t) sim->vip.dp.r1bsy << 5 | (int32_t) sim->vip.dp.l1bsy << 4 | (int32_t) sim->vip.dp.r0bsy << 3 | (int32_t) sim->vip.dp.l0bsy << 2 | (int32_t) sim->vip.dp.disp << 1 ; break; case 0x5F824>>1: /* BRTA */ value = sim->vip.brtRest[0]; break; case 0x5F826>>1: /* BRTB */ value = sim->vip.brtRest[1]; break; case 0x5F828>>1: /* BRTC */ value = sim->vip.brtRest[2]; break; case 0x5F82A>>1: /* REST */ value = sim->vip.brtRest[3]; break; case 0x5F82E>>1: /* FRMCYC */ value = sim->vip.frmcyc; break; case 0x5F830>>1: /* CTA */ value = (int32_t) sim->vip.cta.cta_r << 8 | sim->vip.cta.cta_l; break; case 0x5F840>>1: /* XPSTTS */ value = (int32_t) !!sim->vip.xp.sbout << 15 | (int32_t) sim->vip.xp.sbcount << 8 | (int32_t) sim->vip.xp.overtime << 4 | (int32_t) sim->vip.xp.f1bsy << 3 | (int32_t) sim->vip.xp.f0bsy << 2 | (int32_t) sim->vip.xp.xpen << 1 ; break; case 0x5F844>>1: /* VER */ value = 2; break; case 0x5F848>>1: /* SPT0 */ value = sim->vip.spt[0]; break; case 0x5F84A>>1: /* SPT1 */ value = sim->vip.spt[1]; break; case 0x5F84C>>1: /* SPT2 */ value = sim->vip.spt[2]; break; case 0x5F84E>>1: /* SPT3 */ value = sim->vip.spt[3]; break; case 0x5F860>>1: /* GPLT0 */ value = vipReadPalette(sim->vip.gplt[0]); break; case 0x5F862>>1: /* GPLT1 */ value = vipReadPalette(sim->vip.gplt[1]); break; case 0x5F864>>1: /* GPLT2 */ value = vipReadPalette(sim->vip.gplt[2]); break; case 0x5F866>>1: /* GPLT3 */ value = vipReadPalette(sim->vip.gplt[3]); break; case 0x5F868>>1: /* JPLT0 */ value = vipReadPalette(sim->vip.jplt[0]); break; case 0x5F86A>>1: /* JPLT1 */ value = vipReadPalette(sim->vip.jplt[1]); break; case 0x5F86C>>1: /* JPLT2 */ value = vipReadPalette(sim->vip.jplt[2]); break; case 0x5F86E>>1: /* JPLT3 */ value = vipReadPalette(sim->vip.jplt[3]); break; case 0x5F870>>1: /* BKCOL */ value = sim->vip.bkcol; break; /* Unmapped */ default: value = 0; } /* Select byte bits as necessary */ return mask == 0x00FF ? value & 0x00FF : mask == 0xFF00 ? value >> 8 : value; } /* Write into BG map cell memory */ static void vipWriteCell(VB *sim, uint32_t offset, int type, int32_t value) { Cell *cell; /* Adjustments for byte and word writes */ switch (type) { case VB_S8: case VB_U8: value = offset & 1 ? value << 8 | sim->vip.ram[0x20000 | offset] : value | (int32_t) sim->vip.ram[0x20001 | offset] << 8 ; break; case VB_S32: vipWriteCell(sim, offset + 2, VB_U16, value >> 16); break; } /* Update pixel pointer */ cell = &sim->vip.cells[offset >> 1]; cell->palette = sim->vip.gplt[value >> 14 & 3]; cell->pixels = sim->vip.characters[value & 0x07FF][value >> 12 & 3]; } /* Write into character memory */ static void vipWriteChr(VB *sim, uint32_t offset, int type, int32_t value) { Character *chr; /* Pixel state */ int b, l, r, t; /* Pixel bounds */ int x, y, X, Y; /* Iterators */ /* Byte advance by data type */ static const uint8_t HORZ[] = { 4, 4, 8, 8, 8 }; static const uint8_t VERT[] = { 8, 8, 8, 8, 16 }; /* Working variables */ chr = &sim->vip.characters[offset >> 4]; l = (offset & 0x1) << 2; t = (offset & 0xE) << 2; r = l + HORZ[type]; b = t + VERT[type]; /* Process all pixels */ for (y = t, Y = 56 - y; y < b; y += 8, Y -= 8) for (x = l, X = 7 - x; x < r; x += 1, X -= 1, value >>= 2) { (*chr)[0][y | x] = (*chr)[1][Y | x] = (*chr)[2][y | X] = (*chr)[3][Y | X] = value & 3; } } /* Write into object attribute memory */ static void vipWriteObject(VB *sim, uint32_t offset, int type, int32_t value) { Object *obj; /* Object state */ /* Adjustments for byte and word writes */ switch (type) { case VB_S8: case VB_U8: value = offset & 1 ? value << 8 | sim->vip.ram[0x3E000 | offset] : value | (int32_t) sim->vip.ram[0x3E001 | offset] << 8 ; break; case VB_S32: vipWriteObject(sim, offset + 2, VB_U16, value >> 16); break; } /* Processing by offset */ obj = &sim->vip.objects[offset >> 3]; switch (offset >> 1 & 3) { case 0: obj->jx = SignExtend(value, 10); break; case 1: obj->jp = SignExtend(value, 10); obj->lron = value >> 14 & 3; break; case 2: value = (int8_t) value; obj->jy = value > -8 ? value : value & 0xFF; break; case 3: obj->cell.palette = sim->vip.jplt[value >> 14 & 3]; obj->cell.pixels = sim->vip.characters[value & 0x07FF][value >> 12 & 3]; } } /* Write into world attribute memory */ static void vipWriteWorld(VB *sim, uint32_t offset, int type, int32_t value) { uint32_t base; /* Base BG map index */ uint8_t *bg; /* Background template */ uint32_t count; /* Number of BG maps in background */ World *world; /* World state */ uint32_t z; /* Iterator */ /* Adjustments for byte and word writes */ switch (type) { case VB_S8: case VB_U8: value = offset & 1 ? value << 8 | sim->vip.ram[0x3D800 | offset] : value | (int32_t) sim->vip.ram[0x3D801 | offset] << 8 ; break; case VB_S32: vipWriteWorld(sim, offset + 2, VB_U16, value >> 16); break; } /* Processing by offset */ world = &sim->vip.worlds[offset >> 5]; switch (offset >> 1 & 15) { case 0: /* Parse attributes */ world->lron = value >> 14 & 3; world->bgm = value >> 12 & 3; world->over = value >> 7 & 1; world->end = value >> 6 & 1; /* Update background fields */ z = value >> 10 & 3; world->bgMaskX = (1 << z) - 1; world->bgWidth = 512 << z; world->bgShift = z; z = value >> 8 & 3; world->bgMaskY = (1 << z) - 1; world->bgHeight = 512 << z; /* Update world dimensions */ if (world->bgm != 2) { world->height = world->hNaffine; world->width = world->wNaffine; } else { world->height = world->hAffine; world->width = world->wAffine; } /* Update background arrangement */ count = (world->bgMaskX + 1) * (world->bgMaskY + 1); base = value & 15 & ~(count > 8 ? 7 : count - 1); bg = (uint8_t *) BG_TEMPLATES[value >> 8 & 15]; for (z = 0; z < count; z++) world->bg[z] = &sim->vip.cells[(base + bg[z]) << 12]; break; case 1: world->gx = SignExtend(value, 10); break; case 2: world->gp = SignExtend(value, 10); break; case 3: world->gy = (int16_t) value; break; case 4: world->mx = SignExtend(value, 13); break; case 5: world->mp = SignExtend(value, 15); break; case 6: world->my = SignExtend(value, 13); break; case 7: /* W */ value = SignExtend(value, 13) + 1; world->wNaffine = value < 0 ? 0 : value; value = SignExtend(value, 10) + 1; world->wAffine = value < 0 ? 0 : value; world->width = world->bgm != 2 ? world->wNaffine : world->wAffine; break; case 8: /* H */ value = (int16_t) value + 1; world->hNaffine = value < 8 ? 8 : value; world->hAffine = value < 0 ? 0 : value; world->height = world->bgm != 2 ? world->hNaffine : world->hAffine; break; case 9: world->paramBase = 0x20000|(uint32_t)(uint16_t)value<<1;break; case 10: world->overplane = &sim->vip.cells[(uint16_t) value]; break; } } /* Write a typed value to an I/O register */ static void vipWriteIO( VB *sim, uint32_t address, int type, int32_t value, int debug) { int32_t mask; /* Byte access mask */ /* Adjustments by type */ switch (type) { case VB_S32: /* Word */ vipWriteIO(sim, address , VB_U16, value , debug); vipWriteIO(sim, address + 2, VB_U16, value >> 16, debug); return; case VB_S8: /* Byte */ case VB_U8: /* Select which half of the register to access */ if (debug) { mask = (address & 1) << 3; value = (value & 0x00FF) << mask; mask = 0xFF00 >> mask; break; } /* Convert to a halfword access */ if ((address & 1) != 0) value = (value & 0x00FF) << 8; /* Fallthrough */ default: /* Halfword */ mask = 0x0000; } /* Access by register */ switch (address >> 1) { case 0x5F802>>1: /* INTENB */ sim->vip.intenb = (sim->vip.intenb & mask) | (value & 0xE01F); break; case 0x5F804>>1: /* INTCLR */ sim->vip.intpnd &= ~value; if ((sim->vip.intpnd & sim->vip.intenb) == 0) sim->cpu.irq &= ~0x0010; break; case 0x5F822>>1: /* DPCTRL */ if ((mask & 0xFF00) == 0) { sim->vip.dp.lock = value >> 10 & 1; sim->vip.dp.synce = value >> 9 & 1; sim->vip.dp.re = value >> 8 & 1; } if ((mask & 0x00FF) == 0) { sim->vip.dp.disp = value >> 1 & 1; /* DPRST */ if (value & 1) { sim->vip.intenb &= ~0x801F; sim->vip.intpnd &= ~0x801F; if ((sim->vip.intpnd & sim->vip.intenb) == 0) sim->cpu.irq &= ~0x0010; /* TODO: Research exact operation */ } } break; case 0x5F824>>1: /* BRTA */ sim->vip.brtRest[0] = (sim->vip.brtRest[0] & mask) | value; break; case 0x5F826>>1: /* BRTB */ sim->vip.brtRest[1] = (sim->vip.brtRest[1] & mask) | value; break; case 0x5F828>>1: /* BRTC */ sim->vip.brtRest[2] = (sim->vip.brtRest[2] & mask) | value; break; case 0x5F82A>>1: /* REST */ sim->vip.brtRest[3] = (sim->vip.brtRest[3] & mask) | value; break; case 0x5F82E>>1: /* FRMCYC */ sim->vip.frmcyc = (sim->vip.frmcyc & mask) | (value & 15); break; case 0x5F830>>1: /* CTA */ if ((mask & 0xFF00) == 0) sim->vip.cta.cta_r = value >> 8; if ((mask & 0x00FF) == 0) sim->vip.cta.cta_l = value; break; case 0x5F842>>1: /* XPCTRL */ if ((mask & 0xFF00) == 0) sim->vip.xp.sbcmp = value >> 8 & 31; if ((mask & 0x00FF) == 0) { sim->vip.xp.xpen = value >> 1 & 1; /* XPRST */ if (value & 1) { sim->vip.intenb &= ~0xE000; sim->vip.intpnd &= ~0xE000; if ((sim->vip.intpnd & sim->vip.intenb) == 0) sim->cpu.irq &= ~0x0010; /* TODO: Research exact operation */ } } break; case 0x5F848>>1: /* SPT0 */ sim->vip.spt[0] = (sim->vip.spt[0]&mask) | (value&0x03FF&~mask); break; case 0x5F84A>>1: /* SPT1 */ sim->vip.spt[1] = (sim->vip.spt[1]&mask) | (value&0x03FF&~mask); break; case 0x5F84C>>1: /* SPT2 */ sim->vip.spt[2] = (sim->vip.spt[2]&mask) | (value&0x03FF&~mask); break; case 0x5F84E>>1: /* SPT3 */ sim->vip.spt[3] = (sim->vip.spt[3]&mask) | (value&0x03FF&~mask); break; case 0x5F860>>1: /* GPLT0 */ vipWritePalette(sim->vip.gplt[0], mask, value); break; case 0x5F862>>1: /* GPLT1 */ vipWritePalette(sim->vip.gplt[1], mask, value); break; case 0x5F864>>1: /* GPLT2 */ vipWritePalette(sim->vip.gplt[2], mask, value); break; case 0x5F866>>1: /* GPLT3 */ vipWritePalette(sim->vip.gplt[3], mask, value); break; case 0x5F868>>1: /* JPLT0 */ vipWritePalette(sim->vip.jplt[0], mask, value); break; case 0x5F86A>>1: /* JPLT1 */ vipWritePalette(sim->vip.jplt[1], mask, value); break; case 0x5F86C>>1: /* JPLT2 */ vipWritePalette(sim->vip.jplt[2], mask, value); break; case 0x5F86E>>1: /* JPLT3 */ vipWritePalette(sim->vip.jplt[3], mask, value); break; case 0x5F870>>1: /* BKCOL */ sim->vip.bkcol = (sim->vip.bkcol & mask) | (value & 3 * ~mask); break; } } /***************************** Callback Handlers *****************************/ /* Prepare to handle an exception */ #ifndef VB_DIRECT_FRAME #define VB_ON_FRAME sim->onFrame #else extern int VB_DIRECT_FRAME(VB *); #define VB_ON_FRAME VB_DIRECT_FRAME #endif static int vipOnFrame(VB *sim) { return sim->onFrame != NULL && VB_ON_FRAME(sim); } #undef VB_ON_FRAME /***************************** Display Processor *****************************/ /* Precompute brightness values for image output */ static void vipComputeBrightness(VB *sim) { int32_t brt[4]; /* Working output */ int32_t repeat; /* Multiplier from column table */ int x; /* Iterator */ /* Compute brightness from hardware state */ repeat = (int32_t) sim->vip.ram[sim->vip.dp.cta + 1] + 1; brt[1] = repeat * sim->vip.brtRest[0]; brt[2] = repeat * sim->vip.brtRest[1]; brt[3] = repeat * sim->vip.brtRest[2] + brt[2] + brt[1]; /* Transform brightness values to 0..255 */ for (x = 1; x < 4; x++) sim->vip.dp.brt[x] = brt[x] > 127 ? 255 : BRIGHT8[brt[x]]; } /* Transfer one column of frame buffer pixels to output */ static void vipTransferColumn(VB *sim, int32_t eye) { int32_t bits; /* Pixel bits from frame buffer */ uint8_t *dest; /* Host frame image output */ uint8_t *src; /* Simulation frame buffer input */ int y, z; /* Iterators */ /* Select destination address in host memory */ dest = &sim->vip.output [sim->vip.dp.buffer][eye][sim->vip.dp.column]; /* Output is disabled */ if (!sim->vip.dp.enabled) { for (y = 0; y < 224; y += 16) for (z = 0; z < 16; z++, dest += 384) *dest = 0; return; } /* Select source address in host memory */ src = &sim->vip.ram[ eye << 16 | sim->vip.dp.buffer << 15 | sim->vip.dp.column << 6 ]; /* Transfer all rows as words */ for (y = 0; y < 224; y += 16, src += 4) { bits = busReadBuffer(src, VB_S32); for (z = 0; z < 16; bits >>= 2, z++, dest += 384) *dest = sim->vip.dp.brt[bits & 3]; } } /* Process sub-component */ static int vipEmulateDisplay(VB *sim, uint32_t clocks) { /* Process all clocks */ for (;;) { /* The next event is after the time remaining */ if (sim->vip.dp.clocks > clocks) { sim->vip.dp.clocks -= clocks; sim->vip.dp.until -= clocks; return 0; } /* Advance forward the component's number of clocks */ clocks -= sim->vip.dp.clocks; sim->vip.dp.until -= sim->vip.dp.clocks; sim->vip.dp.clocks = 0; /* Processing by operation phase */ switch (sim->vip.dp.step) { case 0: /* 0ms - FCLK rising edge */ sim->vip.dp.clocks = vipClocksMs(3); sim->vip.dp.enabled = sim->vip.dp.disp & sim->vip.dp.synce; sim->vip.dp.fclk = 1; sim->vip.dp.step = 1; sim->vip.dp.until = vipClocksMs(8); vipThrow(sim, 0x0010); /* FRAMESTART */ /* Game frame */ if (sim->vip.xp.xpen) { if (sim->vip.xp.frame >= sim->vip.frmcyc) { sim->vip.xp.frame = 0; /* Initiate drawing procedure */ if (sim->vip.xp.step == 0) { sim->vip.dp.buffer ^= 1; sim->vip.xp.enabled = 1; sim->vip.xp.overtime = 0; sim->vip.xp.step = 1; vipThrow(sim, 0x0008); /* GAMESTART */ } /* Drawing procedure has run into overtime */ else { sim->vip.xp.overtime = 1; vipThrow(sim, 0x8000); /* TIMEERR */ } } else sim->vip.xp.frame++; } break; /* 0ms-3ms - Idle */ case 1: /* 3ms - L*BSY rising edge */ if (sim->vip.dp.enabled) { if (sim->vip.dp.buffer == 0) sim->vip.dp.l0bsy = 1; else sim->vip.dp.l1bsy = 1; } sim->vip.dp.column = 0; sim->vip.dp.cta = 0x3DC00 | (uint32_t)sim->vip.cta.cta_l<<1; sim->vip.dp.step = 2; vipComputeBrightness(sim); /* Fallthrough */ case 2: /* 3ms-8ms - Display left frame buffer */ if ((sim->vip.dp.column & 3) == 0) vipComputeBrightness(sim); vipTransferColumn(sim, 0); sim->vip.dp.clocks = 260 + (0xA94 >> sim->vip.dp.column % 12 & 1); sim->vip.dp.column++; if ((sim->vip.dp.column & 3) == 0) sim->vip.dp.cta -= 2; if (sim->vip.dp.column == 384) sim->vip.dp.step = 3; break; case 3: /* 8ms - L*BSY falling edge */ if (sim->vip.dp.enabled) { if (sim->vip.dp.buffer == 0) sim->vip.dp.l0bsy = 0; else sim->vip.dp.l1bsy = 0; vipThrow(sim, 0x0002); /* LFBEND */ } sim->vip.dp.clocks = vipClocksMs(2); sim->vip.dp.step = 4; sim->vip.dp.until = vipClocksMs(10); break; /* 8ms-10ms - Idle */ case 4: /* 10ms - FCLK falling edge */ sim->vip.dp.clocks = vipClocksMs(3); sim->vip.dp.fclk = 0; sim->vip.dp.step = 5; break; /* 10ms-13ms - Idle */ case 5: /* 13ms - R*BSY rising edge */ if (sim->vip.dp.enabled) { if (sim->vip.dp.buffer == 0) sim->vip.dp.r0bsy = 1; else sim->vip.dp.r1bsy = 1; } sim->vip.dp.column = 0; sim->vip.dp.cta = 0x3DE00 | (uint32_t)sim->vip.cta.cta_r<<1; sim->vip.dp.step = 6; /* Fallthrough */ case 6: /* 13ms-18ms - Display right frame buffer */ if ((sim->vip.dp.column & 3) == 0) vipComputeBrightness(sim); vipTransferColumn(sim, 1); sim->vip.dp.clocks = 260 + (0xA94 >> sim->vip.dp.column % 12 & 1); sim->vip.dp.column++; if ((sim->vip.dp.column & 3) == 0) sim->vip.dp.cta -= 2; if (sim->vip.dp.column == 384) sim->vip.dp.step = 7; break; case 7: /* 18ms - R*BSY falling edge */ if (sim->vip.dp.enabled) { if (sim->vip.dp.buffer == 0) sim->vip.dp.r0bsy = 0; else sim->vip.dp.r1bsy = 0; vipThrow(sim, 0x0004); /* RFBEND */ } sim->vip.dp.clocks = vipClocksMs(2); sim->vip.dp.enabled = 0; sim->vip.dp.step = 8; sim->vip.dp.until = vipClocksMs(2); break; /* 18ms-20ms - Idle */ case 8: /* 20ms - Display interval complete */ sim->vip.dp.step = 0; if (vipOnFrame(sim)) return 1; break; } } return 0; } /****************************** Pixel Processor ******************************/ /* Draw a cell into shadow memory */ static void vipDrawCell(uint8_t *shadow, Window *wnd, Cell *cell, int32_t x, int32_t y) { uint8_t *col, *row; /* Output pixel */ uint8_t pixel; /* Character pixel value */ int32_t px1, px2, py1, py2; /* Pixel bounds in cell */ int32_t px, py; /* Pixel origin in cell */ /* Identify the visible pixels */ py1 = y > wnd->y1 ? y : wnd->y1; py2 = y + 8 < wnd->y2 ? y + 8 : wnd->y2; px1 = x > wnd->x1 ? x : wnd->x1; px2 = x + 8 < wnd->x2 ? x + 8 : wnd->x2; /* Process all columns of pixels */ col = &shadow[px1 * 224 + py1]; for (px = px1; px < px2; px++, col += 224) for (py = py1, row = col; py < py2; py++, row++) { pixel = cell->pixels[(py - y) << 3 | (px - x)]; if (pixel != 0) *row = cell->palette[pixel]; } } /* Draw a BG map into shadow memory */ static void vipDrawBGMap(uint8_t *shadow, Window *wnd, uint8_t over, Cell *map, int32_t x, int32_t y) { Cell *col, *row; /* Source cell */ int32_t cx1, cx2, cy1, cy2; /* Cell bounds in BG map pixels */ int32_t cx, cy; /* Cell origin in BG map pixels */ /* Identify the visible cells */ cy1 = (wnd->y1-y ) & ~7; if (cy1 < 0) cy1 = 0; if (cy1 > 512) cy1 = 512; cy2 = (wnd->y2-y+7) & ~7; if (cy2 < 0) cy2 = 0; if (cy2 > 512) cy2 = 512; cx1 = (wnd->x1-x ) & ~7; if (cx1 < 0) cx1 = 0; if (cx1 > 512) cx1 = 512; cx2 = (wnd->x2-x+7) & ~7; if (cx2 < 0) cx2 = 0; if (cx2 > 512) cx2 = 512; /* Process all cells as overplane */ if (over) { for (cy = cy1; cy < cy2; cy += 8) for (cx = cx1; cx < cx2; cx += 8) vipDrawCell(shadow, wnd, map, x + cx, y + cy); return; } /* Process all cells normally */ row = &map[cy1 << 3 | cx1 >> 3]; for (cy = cy1 ; cy < cy2; cy += 8, row += 64) for (cx = cx1, col = row; cx < cx2; cx += 8, col++) vipDrawCell(shadow, wnd, col, x + cx, y + cy); } /* Draw a background into shadow memory */ static void vipDrawBackground(uint8_t *shadow, Window *wnd, World *world, int32_t x, int32_t y) { int32_t bx1, bx2, by1, by2; /* BG map bounds in background pixels */ int32_t bx, by; /* BG map origin in background pixels */ /* Identify the visible BG maps */ by1 = (wnd->y1 - y ) & ~511; by2 = (wnd->y2 - y - 1) & ~511; bx1 = (wnd->x1 - x ) & ~511; bx2 = (wnd->x2 - x - 1) & ~511; /* Process all rows of BG maps */ for (by = by1; by <= by2; by += 512) { /* Row is out of bounds */ if (world->over && (by < 0 || by >= world->bgHeight)) { for (bx = bx1; bx <= bx2; bx++) vipDrawBGMap(shadow, wnd, 1, world->overplane, x + bx, y + by); continue; } /* Process all columns of BG maps */ for (bx = bx1; bx <= bx2; bx += 512) { /* Column is out of bounds */ if (world->over && (bx < 0 || bx >= world->bgWidth)) { vipDrawBGMap(shadow, wnd, 1, world->overplane, x + bx, y + by); continue; } /* Column is in bounds */ vipDrawBGMap(shadow, wnd, 0, world->bg[ (by >> 9 & world->bgMaskY) << world->bgShift | (bx >> 9 & world->bgMaskX) ], x + bx, y + by); } } } /* Draw a Normal world into shadow memory */ static void vipDrawNormal(VB *sim, World *world) { int32_t mx, my; /* Background origin */ Window wnd; /* Window bounds in pixels */ int i; /* Iterator */ /* No visible content */ if (world->width == 0) return; /* Vertical window bounds */ wnd.y1 = (int32_t) sim->vip.xp.sbcount << 3; if (world->gy > wnd.y1) wnd.y1 = world->gy; if (wnd.y1 >= 224) return; wnd.y2 = world->gy + world->height; if (wnd.y2 <= 0) return; my = world->gy - world->my; if (wnd.y1 < 0) wnd.y1 = 0; if (wnd.y2 > 224) wnd.y2 = 224; /* Process both eyes */ for (i = 0; i < 2; i++) { /* Check visibility */ if (!(world->lron >> (i ^ 1) & 1)) continue; /* Horizontal window bounds */ wnd.x1 = world->gx + (i == 0 ? -world->gp : world->gp); if (wnd.x1 >= 384) continue; wnd.x2 = wnd.x1 + world->width; if (wnd.x2 <= 0) continue; mx = wnd.x1 - world->mx - (i == 0 ? -world->mp : world->mp); if (wnd.x1 < 0) wnd.x1 = 0; if (wnd.x2 > 384) wnd.x2 = 384; /* Draw the background into the window */ vipDrawBackground(sim->vip.shadow[i], &wnd, world, mx, my); } } /* Draw an H-bias world into shadow memory */ static void vipDrawHBias(VB *sim, World *world) { int32_t mx, my; /* Background origin */ uint8_t *param; /* World parameter memory */ Window wnd; /* Window bounds in pixels */ int32_t y1, y2; /* Vertical window bounds */ int i, y; /* Iterators */ /* No visible content */ if (world->width == 0) return; /* Vertical window bounds */ y1 = (int32_t) sim->vip.xp.sbcount << 3; if (world->gy > y1) y1 = world->gy; if (y1 >= 224) return; y2 = world->gy + world->height; if (y2 <= 0) return; my = world->gy - world->my; if (y1 < 0) y1 = 0; if (y2 > 224) y2 = 224; /* Process both eyes */ for (i = 0; i < 2; i++) { /* Check visibility */ if (!(world->lron >> (i ^ 1) & 1)) continue; /* Horizontal window bounds */ wnd.x1 = world->gx + (i == 0 ? -world->gp : world->gp); if (wnd.x1 >= 384) continue; wnd.x2 = wnd.x1 + world->width; if (wnd.x2 <= 0) continue; mx = wnd.x1 - world->mx - (i == 0 ? -world->mp : world->mp); if (wnd.x1 < 0) wnd.x1 = 0; if (wnd.x2 > 384) wnd.x2 = 384; /* Process all visible lines */ param = &sim->vip.ram[world->paramBase + ((y1 - world->gy) << 2) + (i << 1)]; for (y = y1; y < y2; y++, param += 2) { /* Configure window bounds */ wnd.y1 = y; wnd.y2 = y + 1; /* Draw the background into the window */ vipDrawBackground(sim->vip.shadow[i], &wnd, world, mx + busReadBuffer(param, VB_S16), my); } } } /* Draw an Affine world into shadow memory */ static void vipDrawAffine(VB *sim, World *world) { Cell *cell; /* Source cell */ uint8_t *col, *row; /* Output pixel */ uint8_t pixel; /* Character pixel value */ uint8_t *param; /* World parameter memory */ int32_t px, py; /* Pixel source coordinates */ int32_t dx, dy, mp, mx, my; /* Affine parameters */ int32_t wx; /* Base world X coordinate */ int32_t x1, x2, y1, y2; /* Window bounds */ int i, x, y; /* Iterators */ /* No visible content */ if (world->width == 0 || world->height == 0) return; /* Vertical window bounds */ y1 = (int32_t) sim->vip.xp.sbcount << 3; if (world->gy > y1) y1 = world->gy; if (y1 >= 224) return; y2 = world->gy + world->height; if (y2 <= 0) return; if (y1 < 0) y1 = 0; if (y2 > 224) y2 = 224; /* Process both eyes */ for (i = 0; i < 2; i++) { /* Check visibility */ if (!(world->lron >> (i ^ 1) & 1)) continue; /* Horizontal window bounds */ x1 = world->gx + (i == 0 ? -world->gp : world->gp); if (x1 >= 384) continue; x2 = x1 + world->width; if (x2 <= 0) continue; wx = x1; if (x1 < 0) x1 = 0; if (x2 > 384) x2 = 384; wx = x1 - wx; /* Process all visible rows */ param = &sim->vip.ram[world->paramBase | (y1 - world->gy) << 4]; row = &sim->vip.shadow[i][x1 * 224 + y1]; for (y = y1; y < y2; y++, param += 16, row++) { /* Parse line parameters */ mx = (int32_t) busReadBuffer(param + 0, VB_S16) << 6; mp = (int32_t) busReadBuffer(param + 2, VB_S16); my = (int32_t) busReadBuffer(param + 4, VB_S16) << 6; dx = (int32_t) busReadBuffer(param + 6, VB_S16); dy = (int32_t) busReadBuffer(param + 8, VB_S16); /* Adjust left-edge parameters */ if ((mp < 0) ^ i) { mx += dx * (wx - mp); my += dy * (wx - mp); } else { mx += dx * wx; my += dy * wx; } /* Process all visible columns */ col = row; for (x = x1; x < x2; x++, mx += dx, my += dy, col += 224) { px = (int16_t) (mx >> 9); py = (int16_t) (my >> 9); /* Overplane */ if (world->over && ( px < 0 || px >= world->bgWidth || py < 0 || py >= world->bgHeight )) cell = world->overplane; /* Regular */ else cell = &world->bg[ (py >> 9 & world->bgMaskY) << world->bgShift | (px >> 9 & world->bgMaskX) ][(py << 3 & 0xFC0) | (px >> 3 & 63)]; /* Sample the pixel */ pixel = cell->pixels[(py & 7) << 3 | (px & 7)]; if (pixel != 0) *col = cell->palette[pixel]; } /* x */ } /* y */ } /* i */ } /* Draw an object group into shadow memory */ static void vipDrawObjects(VB *sim, int group) { Object *obj; /* Object state */ int start; /* First object in the group */ int stop; /* Last object in the group */ Window wnd; /* Window bounds in pixels */ int i, o; /* Iterators */ /* Establish the viewing window */ wnd.x1 = 0; wnd.y1 = (int32_t) sim->vip.xp.sbcount << 3; wnd.x2 = 384; wnd.y2 = 224; /* Process all objects in the group */ start = group == 0 ? 0 : (sim->vip.spt[group - 1] + 1) & 1023; stop = sim->vip.spt[group]; for (o = stop; o != -1; o = o == start ? -1 : (o - 1) & 1023) { obj = &sim->vip.objects[o]; for (i = 0; i < 2; i++) { if (!(obj->lron >> (i ^ 1) & 1)) continue; vipDrawCell(sim->vip.shadow[i], &wnd, &obj->cell, obj->jx + (i == 0 ? -obj->jp : obj->jp), obj->jy); } } } /* Draw the current graphics configuration into shadow memory */ static void vipRender(VB *sim) { int group; /* Next object group index */ uint16_t *timing; /* Column timing in host memory */ World *world; /* World attribute source in host memory */ int32_t x, y; /* Iterators */ /* Erase all pixels */ for (x = 0; x < 384*224; x += 224) for (y = (int32_t) sim->vip.xp.sbcount << 3; y < 224; y++) sim->vip.shadow[0][x + y] = sim->vip.shadow[1][x + y] = sim->vip.bkcol; /* Process all worlds */ group = 3; for (x = 31; x >= 0; x--) { world = &sim->vip.worlds[x]; /* Non-graphical world */ if (world->end) break; /* Control world */ if (world->lron == 0) continue; /* Dummy world */ /* Processing by world type */ switch (world->bgm) { case 0: vipDrawNormal(sim, world); break; case 1: vipDrawHBias (sim, world); break; case 2: vipDrawAffine(sim, world); break; case 3: vipDrawObjects(sim, group); group = (group - 1) & 3; } } /* Supply filler timings for each group of 8 rows of pixels Using 2.8ms experimental measurement for empty frame = 125 clocks every 24 halfwords */ timing = &sim->vip.halfwords[(uint32_t) sim->vip.xp.sbcount * 384]; x = sim->vip.xp.column; for (y = sim->vip.xp.sbcount; y < 28; y++, x = 0) for (; x < 384; x++, timing++) *timing = 5 + ((uint32_t) 0x210884 >> x % 24 & 1); } /* Transfer one halfword from shadow pixel memory to the frame buffer */ static void vipTransferHalfword(VB *sim) { uint32_t bits0; /* Upper 4 pixels from shadow memory */ uint32_t bits1; /* Lower 4 pixels from shadow memory */ uint8_t *dest; /* Pointer to frame buffer data in host memory */ uint32_t offset; /* Position of halfword in shadow memory */ uint8_t *src; /* Pointer to pixel data in host memory */ int i; /* Iterator */ /* Determine the output address in frame buffer memory */ dest = &sim->vip.ram[ (uint32_t) (sim->vip.dp.buffer ^ 1) << 15 | (uint32_t) sim->vip.xp.column << 6 | (uint32_t) sim->vip.xp.sbcount << 1 ]; /* Transfer halfwords to each eye's frame buffer */ offset = (uint32_t) sim->vip.xp.column * 224 + ((uint32_t) sim->vip.xp.sbcount << 3); for (i = 0; i < 2; i++, dest += 0x10000) { src = &sim->vip.shadow[i][offset]; bits0 = busReadBuffer(src , VB_S32); bits1 = busReadBuffer(src + 4, VB_S32); busWriteBuffer(dest, VB_U16, (bits0 & 0x0003) | (bits0 >> 6 & 0x000C) | (bits0 >> 12 & 0x0030) | (bits0 >> 18 & 0x00C0) | (bits1 << 8 & 0x0300) | (bits1 << 2 & 0x0C00) | (bits1 >> 4 & 0x3000) | (bits1 >> 10 & 0xC000) ); } } /* Process sub-component */ static void vipEmulateDrawing(VB *sim, uint32_t clocks) { /* Process all clocks */ for (;;) { /* The next event is after the time remaining */ if (sim->vip.xp.clocks > clocks) { sim->vip.xp.clocks -= clocks; sim->vip.xp.until -= clocks; return; } /* Advance forward the component's number of clocks */ clocks -= sim->vip.xp.clocks; sim->vip.xp.until -= sim->vip.xp.clocks; sim->vip.xp.clocks = 0; /* Processing by operation phase */ switch (sim->vip.xp.step) { case 1: /* Initialization */ sim->vip.xp.halfword = 0; sim->vip.xp.sbcount = 0; if (sim->vip.dp.buffer == 0) sim->vip.xp.f1bsy = 1; else sim->vip.xp.f0bsy = 1; vipRender(sim); /* Fallthrough */ case 2: /* Begin drawing halfwords */ sim->vip.xp.column = 0; sim->vip.xp.sbout = 2240; /* 112us */ sim->vip.xp.step = 3; sim->vip.xp.until = 2000; /* 100us */ if (sim->vip.xp.sbcount == sim->vip.xp.sbcmp) vipThrow(sim, 0x2000); /* SBHIT */ /* Fallthrough */ case 3: /* Draw one halfword */ vipTransferHalfword(sim); sim->vip.xp.clocks = sim->vip.halfwords[sim->vip.xp.halfword]; sim->vip.xp.column ++; sim->vip.xp.halfword++; if (sim->vip.xp.column < 384) break; /* Transition to a new line of halfwords */ sim->vip.xp.sbcount++; sim->vip.xp.step = sim->vip.xp.sbcount == 28 ? 4 : 2; break; case 4: /* Drawing complete */ sim->vip.xp.enabled = 0; sim->vip.xp.step = 0; if (sim->vip.dp.buffer == 0) sim->vip.xp.f1bsy = 0; else sim->vip.xp.f0bsy = 0; vipThrow(sim, 0x4000); /* XPEND */ return; } } return; /* Unreachable */ } /***************************** Library Functions *****************************/ /* Process component */ static int vipEmulate(VB *sim, uint32_t clocks) { /* Process sub-components */ int brk = vipEmulateDisplay(sim, clocks); if (sim->vip.xp.step != 0) vipEmulateDrawing(sim, clocks); /* Process SBOUT */ if (sim->vip.xp.sbout != 0) { if (sim->vip.xp.sbout <= clocks) { sim->vip.xp.sbout = 0; } else sim->vip.xp.sbout -= clocks; } return brk; } /* Read a typed value from the VIP bus */ static void vipRead(VB *sim, uint32_t address, int type, int32_t *value) { /* Working variables */ address &= 0x0007FFFF; /* RAM */ if (address < 0x40000) *value = busReadBuffer(&sim->vip.ram[address], type); /* Unmapped */ else if (address < 0x5E000) *value = 0; /* I/O register */ else if (address < 0x60000) *value = vipReadIO(sim, address, type); /* Unmapped */ else if (address < 0x78000) *value = 0; /* Mirrors of character memory */ else { address = 0x06000 | (address << 2 & 0x18000) | (address & 0x01FFF); *value = busReadBuffer(&sim->vip.ram[address], type); } } /* Simulate a hardware reset */ static void vipReset(VB *sim) { Cell *cell; /* BG map cell state */ Character *chr; /* Character state */ Object *obj; /* Object state */ World *world; /* World state */ int x, y; /* Iterators */ /* Normal */ sim->vip.intenb = 0x0000; sim->vip.dp.disp = 0; sim->vip.dp.re = 0; sim->vip.dp.synce = 0; sim->vip.xp.xpen = 0; /* Extra (the hardware does not do this) */ sim->vip.bkcol = 0; sim->vip.cta.cta_l = 0xFA; sim->vip.cta.cta_r = 0xFA; sim->vip.frmcyc = 0; sim->vip.intpnd = 0x0000; for (x = 0; x < 0x40000; x++) sim->vip.ram[x] = 0; for (x = 0; x < 4; x++) { sim->vip.brtRest[x] = 0; sim->vip.spt [x] = 0; for (y = 0; y < 4; y++) { sim->vip.gplt[x][y] = 0; sim->vip.jplt[x][y] = 0; } } for (x = 0; x < 0x10000; x++) { cell = &sim->vip.cells[x]; cell->palette = sim->vip.gplt[0]; cell->pixels = sim->vip.characters[0][0]; } for (x = 0; x < 2048; x++) { chr = &sim->vip.characters[x]; for (y = 0; y < 64; y++) (*chr)[0][y] = (*chr)[1][y] = (*chr)[2][y] = (*chr)[3][y] = 0; } for (x = 0; x < 32; x++) { world = &sim->vip.worlds[x]; world->bgm = 0; world->end = 0; world->gp = 0; world->gx = 0; world->gy = 0; world->mx = 0; world->mp = 0; world->my = 0; world->over = 0; world->overplane = &sim->vip.cells[0]; for (y = 0; y < 64; y++) world->bg[0] = world->overplane; world->bgHeight = 512; world->bgMaskX = 0; world->bgMaskY = 0; world->bgShift = 0; world->bgWidth = 512; world->hAffine = 0; world->hNaffine = 0; world->height = 8; world->lron = 0; world->paramBase = 0; world->wAffine = 0; world->wNaffine = 0; world->width = 0; } for (x = 0; x < 1024; x++) { obj = &sim->vip.objects[x]; obj->cell.palette = sim->vip.jplt[0]; obj->cell.pixels = sim->vip.characters[0][0]; obj->jp = 0; obj->jx = 0; obj->jy = 0; obj->lron = 0; } /* Display processor extra (the hardware does not do this) */ sim->vip.dp.fclk = 0; sim->vip.dp.l0bsy = 0; sim->vip.dp.l1bsy = 0; sim->vip.dp.lock = 0; sim->vip.dp.r0bsy = 0; sim->vip.dp.r1bsy = 0; sim->vip.dp.scanrdy = 1; /* Pixel processor extra (the hardware does not do this) */ sim->vip.xp.f0bsy = 0; sim->vip.xp.f1bsy = 0; sim->vip.xp.overtime = 0; sim->vip.xp.sbcmp = 0; sim->vip.xp.sbcount = 0; sim->vip.xp.sbout = 0; /* Display processor other */ sim->vip.dp.brt[0] = 0; sim->vip.dp.buffer = 0; /* TODO: Hardware might actually do this */ sim->vip.dp.clocks = 0; sim->vip.dp.step = 0; sim->vip.dp.until = 0; /* Pixel processor other */ sim->vip.xp.clocks = 0; sim->vip.xp.step = 0; sim->vip.xp.until = 0; } /* Determine how many clocks are guaranteed to process */ static uint32_t vipUntil(VB *sim, uint32_t clocks) { if (clocks > sim->vip.dp.until) clocks = sim->vip.dp.until; if (sim->vip.xp.step != 0 && clocks > sim->vip.xp.until) clocks = sim->vip.xp.until; return clocks; } /* Write a typed value to the VIP bus */ static void vipWrite(VB*sim,uint32_t address,int type,int32_t value,int debug){ /* Working variables */ address &= 0x0007FFFF; /* RAM */ if (address < 0x40000) { busWriteBuffer(&sim->vip.ram[address], type, value); /* Character memory */ if ((address & 0x26000) == 0x06000) { address = (address & 0x18000) >> 2 | (address & 0x01FFF); vipWriteChr(sim, address, type, value); } /* BG map memory */ if (address >= 0x20000) vipWriteCell(sim, address & 0x1FFFF, type, value); /* World memory */ if ((address & 0x3FC00) == 0x3D800) vipWriteWorld(sim, address &0x003FF, type, value); /* Object memory */ if (address >= 0x3E000) vipWriteObject(sim, address & 0x01FFF, type, value); } /* Unmapped */ else if (address < 0x5E000) ; /* I/O register */ else if (address < 0x60000) vipWriteIO(sim, address, type, value, debug); /* Unmapped */ else if (address < 0x78000) ; /* Mirrors of character memory */ else { vipWriteChr(sim, address & 0x07FFF, type, value); address = 0x06000 | (address << 2 & 0x18000) | (address & 0x01FFF); busWriteBuffer(&sim->vip.ram[address], type, value); } } #endif /* VBAPI */