mirror of
https://github.com/libretro/beetle-pce-fast-libretro.git
synced 2024-11-26 17:40:40 +00:00
1165 lines
34 KiB
C
1165 lines
34 KiB
C
/* Mednafen - Multi-system Emulator
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
/* VDC and VCE emulation */
|
|
|
|
/*
|
|
"Tonight I hooked up my Turbo Duo(with no games or CDs in it)'s video output to my PC sound card, recorded it,
|
|
and did a FFT and looked at the spectrum(around the line rate, 15-16KHz), and I also counted the number
|
|
of samples between the ~60Hz peaks(just to verify that the math shown below is remotely accurate).
|
|
|
|
The spectrum peaked at 15734 Hz. 21477272.727272... / 3 / 15734 = 455.00(CPU cycles per scanline)"
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <string.h>
|
|
|
|
#include "pce.h"
|
|
#include "../video.h"
|
|
#include "vdc.h"
|
|
#include "huc.h"
|
|
#include "pcecd.h"
|
|
#include "../settings.h"
|
|
#include "../state_helpers.h"
|
|
|
|
static uint16 systemColorMap16[2][512]; // 0 = normal, 1 = strip colorburst
|
|
static uint32 userle; // User layer enable.
|
|
static uint32 disabled_layer_color;
|
|
|
|
static bool unlimited_sprites;
|
|
|
|
#define ULE_BG0 1
|
|
#define ULE_SPR0 2
|
|
#define ULE_BG1 4
|
|
#define ULE_SPR1 8
|
|
|
|
static const uint8 bat_width_shift_tab[4] = { 5, 6, 7, 7 };
|
|
static const uint8 bat_height_mask_tab[2] = { 32 - 1, 64 - 1 };
|
|
static int defined_width[3] = {256, 352, 512};
|
|
|
|
static unsigned int VDS;
|
|
static unsigned int VSW;
|
|
static unsigned int VDW;
|
|
static unsigned int VCR;
|
|
static unsigned int VBlankFL;
|
|
|
|
vce_t vce;
|
|
|
|
vdc_t *vdc = NULL;
|
|
|
|
#define MAKECOLOR_PCE(val) ((((val & 0x038) >> 3) << 13)|(((((val & 0x038) >> 3) & 0x6) << 10) | (((val & 0x1c0) >> 6) << 8) | (((val & 0x1c0) >> 6) << 5) | ((val & 0x007) << 2) | ((val & 0x007) >> 1)))
|
|
|
|
|
|
static INLINE void MDFN_FastU32MemsetM8(uint32_t *array, uint32_t value_32, unsigned int u32len)
|
|
{
|
|
uint32_t *ai;
|
|
|
|
for(ai = array; ai < array + u32len; ai += 2)
|
|
{
|
|
ai[0] = value_32;
|
|
ai[1] = value_32;
|
|
}
|
|
}
|
|
|
|
static INLINE void FixPCache(int entry)
|
|
{
|
|
const uint16 *cm16 = systemColorMap16[vce.CR >> 7];
|
|
|
|
if(!(entry & 0xFF))
|
|
{
|
|
int x;
|
|
uint16_t color0 = cm16[vce.color_table[entry & 0x100]];
|
|
for(x = 0; x < 16; x++)
|
|
vce.color_table_cache[entry + (x << 4)] = color0 ;
|
|
return;
|
|
}
|
|
|
|
if(entry & 0xF)
|
|
vce.color_table_cache[entry] = cm16[vce.color_table[entry]];
|
|
}
|
|
|
|
static INLINE void FixTileCache(vdc_t *which_vdc, uint16 A)
|
|
{
|
|
int x;
|
|
uint32 charname = (A >> 4);
|
|
uint32 y = (A & 0x7);
|
|
uint64 *tc = which_vdc->bg_tile_cache + (charname * 8) + y;
|
|
|
|
uint32 bitplane01 = which_vdc->VRAM[y + charname * 16];
|
|
uint32 bitplane23 = which_vdc->VRAM[y+ 8 + charname * 16];
|
|
|
|
*tc = 0;
|
|
|
|
for(x = 0; x < 8; x++)
|
|
{
|
|
uint32 raw_pixel = ((bitplane01 >> x) & 1);
|
|
raw_pixel |= ((bitplane01 >> (x + 8)) & 1) << 1;
|
|
raw_pixel |= ((bitplane23 >> x) & 1) << 2;
|
|
raw_pixel |= ((bitplane23 >> (x + 8)) & 1) << 3;
|
|
|
|
#ifdef MSB_FIRST
|
|
*tc |= (uint64)raw_pixel << ((x) * 8);
|
|
#else
|
|
*tc |= (uint64)raw_pixel << ((7 - x) * 8);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static INLINE void CheckFixSpriteTileCache(vdc_t *which_vdc, uint16 no, uint32 special)
|
|
{
|
|
if(special != 0x4 && special != 0x5)
|
|
special = 0;
|
|
|
|
if((special | 0x80) == which_vdc->spr_tile_clean[no])
|
|
return;
|
|
|
|
if((no * 64) >= VRAM_Size)
|
|
{
|
|
}
|
|
else if(special)
|
|
{
|
|
int y;
|
|
for(y = 0; y < 16; y++)
|
|
{
|
|
int x;
|
|
uint8 *tc = which_vdc->spr_tile_cache[no][y];
|
|
|
|
uint32 bitplane0 = which_vdc->VRAM[y + 0x00 + no * 0x40 + ((special & 1) << 5)];
|
|
uint32 bitplane1 = which_vdc->VRAM[y + 0x10 + no * 0x40 + ((special & 1) << 5)];
|
|
|
|
for(x = 0; x < 16; x++)
|
|
{
|
|
uint32 raw_pixel;
|
|
raw_pixel = ((bitplane0 >> x) & 1) << 0;
|
|
raw_pixel |= ((bitplane1 >> x) & 1) << 1;
|
|
tc[x] = raw_pixel;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int y;
|
|
for(y = 0; y < 16; y++)
|
|
{
|
|
int x;
|
|
uint8 *tc = which_vdc->spr_tile_cache[no][y];
|
|
|
|
uint32 bitplane0 = which_vdc->VRAM[y + 0x00 + no * 0x40];
|
|
uint32 bitplane1 = which_vdc->VRAM[y + 0x10 + no * 0x40];
|
|
uint32 bitplane2 = which_vdc->VRAM[y + 0x20 + no * 0x40];
|
|
uint32 bitplane3 = which_vdc->VRAM[y + 0x30 + no * 0x40];
|
|
|
|
for(x = 0; x < 16; x++)
|
|
{
|
|
uint32 raw_pixel;
|
|
raw_pixel = ((bitplane0 >> x) & 1) << 0;
|
|
raw_pixel |= ((bitplane1 >> x) & 1) << 1;
|
|
raw_pixel |= ((bitplane2 >> x) & 1) << 2;
|
|
raw_pixel |= ((bitplane3 >> x) & 1) << 3;
|
|
tc[x] = raw_pixel;
|
|
}
|
|
}
|
|
}
|
|
|
|
which_vdc->spr_tile_clean[no] = special | 0x80;
|
|
}
|
|
|
|
|
|
static INLINE void SetVCECR(uint8 V)
|
|
{
|
|
if(((V & 0x80) >> 7) != vce.bw)
|
|
{
|
|
int x;
|
|
vce.bw = V & 0x80;
|
|
for(x = 0; x < 512; x++)
|
|
FixPCache(x);
|
|
}
|
|
|
|
vce.lc263 = (V & 0x04);
|
|
vce.dot_clock = V & 1;
|
|
if(V & 2)
|
|
vce.dot_clock = 2;
|
|
vce.CR = V;
|
|
}
|
|
|
|
static unsigned int frame_counter;
|
|
static int32 need_vbi[2] = { 0, 0 };
|
|
static int32 line_leadin1 = 0;
|
|
static int32 magical, cyc_tot;
|
|
|
|
vpc_t vpc;
|
|
|
|
// Some virtual vdc macros to make code simpler to read
|
|
#define M_vdc_HSW (vdc->HSR & 0x1F) // Horizontal Synchro Width
|
|
#define M_vdc_HDS ((vdc->HSR >> 8) & 0x7F) // Horizontal Display Start
|
|
#define M_vdc_HDW (vdc->HDR & 0x7F) // Horizontal Display Width
|
|
#define M_vdc_HDE ((vdc->HDR >> 8) & 0x7F) // Horizontal Display End
|
|
|
|
#define M_vdc_VSW (vdc->VSR & 0x1F) // Vertical synchro width
|
|
#define M_vdc_VDS ((vdc->VSR >> 8) & 0xFF) // Vertical Display Start
|
|
#define M_vdc_VDW (vdc->VDR & 0x1FF) // Vertical Display Width(Height? :b)
|
|
#define M_vdc_VCR (vdc->VCR & 0xFF)
|
|
|
|
#define VDCS_CR 0x01 // Sprite #0 collision interrupt occurred
|
|
#define VDCS_OR 0x02 // sprite overflow "" ""
|
|
#define VDCS_RR 0x04 // RCR "" ""
|
|
#define VDCS_DS 0x08 // VRAM to SAT DMA completion interrupt occurred
|
|
#define VDCS_DV 0x10 // VRAM to VRAM DMA completion interrupt occurred
|
|
#define VDCS_VD 0x20 // Vertical blank interrupt occurred
|
|
#define VDCS_BSY 0x40 // VDC is waiting for a CPU access slot during the active display area??
|
|
|
|
void VDC_SetPixelFormat(const uint8* CustomColorMap, const uint32 CustomColorMapLen)
|
|
{
|
|
int x;
|
|
for(x = 0; x < 512; x++)
|
|
{
|
|
int r, g, b;
|
|
int sc_r, sc_g, sc_b;
|
|
|
|
if(CustomColorMap)
|
|
{
|
|
r = CustomColorMap[x * 3 + 0];
|
|
g = CustomColorMap[x * 3 + 1];
|
|
b = CustomColorMap[x * 3 + 2];
|
|
}
|
|
else
|
|
{
|
|
b = 36 * (x & 0x007);
|
|
r = 36 * ((x & 0x038) >> 3);
|
|
g = 36 * ((x & 0x1c0) >> 6);
|
|
}
|
|
|
|
if(CustomColorMap && CustomColorMapLen == 1024)
|
|
{
|
|
sc_r = CustomColorMap[(512 + x) * 3 + 0];
|
|
sc_g = CustomColorMap[(512 + x) * 3 + 1];
|
|
sc_b = CustomColorMap[(512 + x) * 3 + 2];
|
|
}
|
|
else
|
|
{
|
|
double y;
|
|
|
|
y = floor(0.5 + 0.300 * r + 0.589 * g + 0.111 * b);
|
|
|
|
if(y < 0)
|
|
y = 0;
|
|
|
|
if(y > 255)
|
|
y = 255;
|
|
|
|
sc_r = sc_g = sc_b = y;
|
|
}
|
|
systemColorMap16[0][x] = MAKECOLOR(r, g, b, 0);
|
|
systemColorMap16[1][x] = MAKECOLOR(sc_r, sc_g, sc_b, 0);
|
|
}
|
|
// I know the temptation is there, but don't combine these two loops just
|
|
// because they loop 512 times ;)
|
|
for(x = 0; x < 512; x++)
|
|
FixPCache(x);
|
|
|
|
disabled_layer_color = MAKECOLOR(0x00, 0xFE, 0x00, 0);
|
|
}
|
|
|
|
DECLFR(VCE_Read)
|
|
{
|
|
switch(A & 0x7)
|
|
{
|
|
case 4:
|
|
return (vce.color_table[vce.ctaddress & 0x1FF]);
|
|
case 5:
|
|
{
|
|
uint8 ret = vce.color_table[vce.ctaddress & 0x1FF] >> 8;
|
|
ret &= 1;
|
|
ret |= 0xFE;
|
|
vce.ctaddress++;
|
|
return(ret);
|
|
}
|
|
}
|
|
|
|
return(0xFF);
|
|
}
|
|
|
|
DECLFW(VCE_Write)
|
|
{
|
|
switch(A&0x7)
|
|
{
|
|
case 0: SetVCECR(V); break;
|
|
case 2: vce.ctaddress &= 0x100; vce.ctaddress |= V; break;
|
|
case 3: vce.ctaddress &= 0x0FF; vce.ctaddress |= (V & 1) << 8; break;
|
|
case 4: vce.color_table[vce.ctaddress & 0x1FF] &= 0x100;
|
|
vce.color_table[vce.ctaddress & 0x1FF] |= V;
|
|
FixPCache(vce.ctaddress & 0x1FF);
|
|
break;
|
|
case 5: vce.color_table[vce.ctaddress & 0x1FF] &= 0xFF;
|
|
vce.color_table[vce.ctaddress & 0x1FF] |= (V & 1) << 8;
|
|
FixPCache(vce.ctaddress & 0x1FF);
|
|
vce.ctaddress++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void VDC_SetLayerEnableMask(uint64 mask)
|
|
{
|
|
userle = mask;
|
|
}
|
|
|
|
void MDFN_FASTCALL VDC_Write_ST(uint32 A, uint8 V)
|
|
{
|
|
VDC_Write(A, V);
|
|
}
|
|
|
|
static void DoDMA(vdc_t *vdc)
|
|
{
|
|
int i;
|
|
// Assuming one cycle for reads, one cycle for write, with DMA?
|
|
for(i = 0; i < 455; i++)
|
|
{
|
|
if(!vdc->DMAReadWrite)
|
|
vdc->DMAReadBuffer = vdc->VRAM[vdc->SOUR];
|
|
else
|
|
{
|
|
if(vdc->DESR < VRAM_Size)
|
|
{
|
|
vdc->VRAM[vdc->DESR] = vdc->DMAReadBuffer;
|
|
FixTileCache(vdc, vdc->DESR);
|
|
vdc->spr_tile_clean[vdc->DESR >> 6] = 0;
|
|
}
|
|
|
|
vdc->SOUR += (((vdc->DCR & 0x4) >> 1) ^ 2) - 1;
|
|
vdc->DESR += (((vdc->DCR & 0x8) >> 2) ^ 2) - 1;
|
|
vdc->LENR--;
|
|
if(vdc->LENR == 0xFFFF) // DMA is done.
|
|
{
|
|
vdc->DMARunning = 0;
|
|
if(vdc->DCR & 0x02)
|
|
{
|
|
vdc->status |= VDCS_DV;
|
|
HuC6280_IRQBegin(MDFN_IQIRQ1);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
vdc->DMAReadWrite ^= 1;
|
|
} // for()
|
|
}
|
|
|
|
DECLFW(VDC_Write)
|
|
{
|
|
int msb = A & 1;
|
|
int chip = 0;
|
|
|
|
A &= 0x3;
|
|
|
|
switch(A)
|
|
{
|
|
case 0x0: vdc->select = V & 0x1F; break;
|
|
case 0x2:
|
|
case 0x3:
|
|
switch(vdc->select & 0x1F)
|
|
{
|
|
case 0x00: REGSETP(vdc->MAWR, V, msb); break;
|
|
case 0x01: REGSETP(vdc->MARR, V, msb);
|
|
if(msb)
|
|
vdc->read_buffer = vdc->VRAM[vdc->MARR];
|
|
break;
|
|
case 0x02: if(!msb) vdc->write_latch = V;
|
|
else
|
|
{
|
|
if(vdc->MAWR < VRAM_Size)
|
|
{
|
|
// A hack to fix Crest of Wolf, and maybe others.
|
|
while(vdc->DMARunning)
|
|
DoDMA(vdc);
|
|
|
|
vdc->VRAM[vdc->MAWR] = (V << 8) | vdc->write_latch;
|
|
FixTileCache(vdc, vdc->MAWR);
|
|
vdc->spr_tile_clean[vdc->MAWR >> 6] = 0;
|
|
}
|
|
vdc->MAWR += vram_inc_tab[(vdc->CR >> 11) & 0x3];
|
|
}
|
|
break;
|
|
case 0x05: REGSETP(vdc->CR, V, msb); break;
|
|
case 0x06: REGSETP(vdc->RCR, V, msb); vdc->RCR &= 0x3FF; break;
|
|
case 0x07: REGSETP(vdc->BXR, V, msb); vdc->BXR &= 0x3FF; break;
|
|
case 0x08: REGSETP(vdc->BYR, V, msb); vdc->BYR &= 0x1FF;
|
|
vdc->BG_YOffset = vdc->BYR; // Set it on LSB and MSB writes(only changing on MSB breaks Youkai Douchuuki)
|
|
break;
|
|
case 0x09: REGSETP(vdc->MWR, V, msb); break;
|
|
case 0x0a: REGSETP(vdc->HSR, V, msb); break;
|
|
case 0x0b: REGSETP(vdc->HDR, V, msb); break;
|
|
case 0x0c: REGSETP(vdc->VSR, V, msb); break;
|
|
case 0x0d: REGSETP(vdc->VDR, V, msb); break;
|
|
case 0x0e: REGSETP(vdc->VCR, V, msb); break;
|
|
case 0x0f: REGSETP(vdc->DCR, V, msb); break;
|
|
case 0x10: REGSETP(vdc->SOUR, V, msb); break;
|
|
case 0x11: REGSETP(vdc->DESR, V, msb); break;
|
|
case 0x12: REGSETP(vdc->LENR, V, msb);
|
|
if(msb)
|
|
{
|
|
vdc->DMARunning = 1;
|
|
vdc->DMAReadWrite = 0;
|
|
if(vdc->burst_mode && !(vdc->DCR & 0x02))
|
|
DoDMA(vdc); // Do one line's worth of DMA transfers
|
|
// because Cosmic Fantasy 4 is evil
|
|
// and uses timed writes to the DMA
|
|
// start register, rather than waiting until
|
|
// the machine says we're done,
|
|
// which would require cycle-accurate VDC emulation...like that's
|
|
// going to happen when I don't even have accurate values
|
|
// for HuC6280 instruction timings. :b
|
|
}
|
|
break;
|
|
case 0x13: REGSETP(vdc->SATB, V, msb); vdc->SATBPending = 1; break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define CB_EXL(n) (((n) << 4) | ((n) << 12) | ((n) << 20) | ((n) << 28) | ((n) << 36) | ((n) << 44) | ((n) << 52) | ((n) << 60))
|
|
static const uint64 cblock_exlut[16] = {
|
|
CB_EXL(0ULL), CB_EXL(1ULL), CB_EXL(2ULL), CB_EXL(3ULL), CB_EXL(4ULL), CB_EXL(5ULL), CB_EXL(6ULL), CB_EXL(7ULL),
|
|
CB_EXL(8ULL), CB_EXL(9ULL), CB_EXL(10ULL), CB_EXL(11ULL), CB_EXL(12ULL), CB_EXL(13ULL), CB_EXL(14ULL), CB_EXL(15ULL)
|
|
};
|
|
|
|
static NO_INLINE void DrawBG(const vdc_t *vdc, const uint32 count, uint8 *target)
|
|
{
|
|
int bat_width_shift = bat_width_shift_tab[(vdc->MWR >> 4) & 3];
|
|
int bat_width_mask = (1U << bat_width_shift) - 1;
|
|
int bat_height_mask = bat_height_mask_tab[(vdc->MWR >> 6) & 1];
|
|
uint64 *target64 = (uint64 *)target;
|
|
|
|
{
|
|
int x;
|
|
int bat_y = ((vdc->BG_YOffset >> 3) & bat_height_mask) << bat_width_shift;
|
|
|
|
int bat_boom = (vdc->BG_XOffset >> 3) & bat_width_mask;
|
|
int line_sub = vdc->BG_YOffset & 7;
|
|
|
|
const uint16 *BAT_Base = &vdc->VRAM[bat_y];
|
|
const uint64 *CG_Base = vdc->bg_tile_cache + (0 * 8) + line_sub;
|
|
|
|
uint64_t cg_mask = 0xFFFFFFFFFFFFFFFFULL;
|
|
|
|
if((vdc->MWR & 0x3) == 0x3)
|
|
cg_mask = (vdc->MWR & 0x80) ? 0xCCCCCCCCCCCCCCCCULL : 0x3333333333333333ULL;
|
|
|
|
for(x = count - 1; x >= 0; x -= 8)
|
|
{
|
|
const uint16 bat = BAT_Base[bat_boom];
|
|
const uint64 color_or = cblock_exlut[bat >> 12];
|
|
|
|
*target64 = (CG_Base[(bat & 0xFFF) * 8] & cg_mask) | color_or;
|
|
|
|
bat_boom = (bat_boom + 1) & bat_width_mask;
|
|
target64++;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define SPRF_PRIORITY 0x00080
|
|
#define SPRF_HFLIP 0x00800
|
|
#define SPRF_VFLIP 0x08000
|
|
#define SPRF_SPRITE0 0x10000
|
|
|
|
static const unsigned int sprite_height_tab[4] = { 16, 32, 64, 64 };
|
|
static const unsigned int sprite_height_no_mask[4] = { ~0U, ~2U, ~6U, ~6U };
|
|
|
|
static INLINE void RebuildSATCache(vdc_t *vdc)
|
|
{
|
|
unsigned i;
|
|
SAT_Cache_t *sat_ptr = (SAT_Cache_t*)vdc->SAT_Cache;
|
|
|
|
vdc->SAT_Cache_Valid = 0;
|
|
|
|
for(i = 0; i < 64; i++)
|
|
{
|
|
uint16 height;
|
|
|
|
const uint16 SATR0 = vdc->SAT[i * 4 + 0x0];
|
|
const uint16 SATR1 = vdc->SAT[i * 4 + 0x1];
|
|
const uint16 SATR2 = vdc->SAT[i * 4 + 0x2];
|
|
const uint16 SATR3 = vdc->SAT[i * 4 + 0x3];
|
|
|
|
|
|
int16 y = (int16)(SATR0 & 0x3FF) - 0x40;
|
|
uint16 x = SATR1 & 0x3FF;
|
|
uint16 no = (SATR2 >> 1) & 0x3FF;
|
|
uint16 flags = (SATR3);
|
|
bool cgmode = SATR2 & 0x1;
|
|
|
|
uint32 width = ((flags >> 8) & 1);
|
|
flags &= ~0x100;
|
|
|
|
height = sprite_height_tab[(flags >> 12) & 3];
|
|
no &= sprite_height_no_mask[(flags >> 12) & 3];
|
|
|
|
no = ((no & ~width) | 0) ^ ((flags & SPRF_HFLIP) ? width : 0);
|
|
|
|
sat_ptr->y = y;
|
|
sat_ptr->height = height;
|
|
sat_ptr->x = x;
|
|
sat_ptr->no = no;
|
|
sat_ptr->flags = flags;
|
|
sat_ptr->cgmode = cgmode;
|
|
|
|
sat_ptr++;
|
|
vdc->SAT_Cache_Valid++;
|
|
|
|
if(width)
|
|
{
|
|
no = ((no & ~width) | 1) ^ ((flags & SPRF_HFLIP) ? width : 0);
|
|
x += 16;
|
|
|
|
*sat_ptr = *(sat_ptr - 1);
|
|
|
|
sat_ptr->no = no;
|
|
sat_ptr->x = x;
|
|
|
|
sat_ptr++;
|
|
vdc->SAT_Cache_Valid++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static INLINE void DoSATDMA(vdc_t *vdc)
|
|
{
|
|
unsigned i;
|
|
for(i = 0; i < 256; i++)
|
|
vdc->SAT[i] = vdc->VRAM[(vdc->SATB + i) & 0xFFFF];
|
|
|
|
RebuildSATCache(vdc);
|
|
}
|
|
|
|
|
|
typedef struct
|
|
{
|
|
uint32 x;
|
|
uint32 flags;
|
|
uint8 palette_index;
|
|
uint16 no;
|
|
uint16 sub_y;
|
|
} SPRLE;
|
|
|
|
#define SPR_HPMASK 0x8000 // High priority bit mask
|
|
|
|
// DrawSprites will write up to 0x20 units before the start of the pointer it's passed.
|
|
static NO_INLINE void DrawSprites(vdc_t *vdc, const int32 end, uint16 *spr_linebuf)
|
|
{
|
|
int i;
|
|
int active_sprites = 0;
|
|
SPRLE SpriteList[64 * 2]; // (see unlimited_sprites option, *2 to accomodate 32-pixel-width sprites ) //16];
|
|
|
|
// First, grab the up to 16(or 128 for unlimited_sprites) sprite units(16xWHATEVER; each 32xWHATEVER sprite counts as 2 sprite units when
|
|
// rendering a scanline) for this scanline.
|
|
for(i = 0; i < vdc->SAT_Cache_Valid; i++)
|
|
{
|
|
const SAT_Cache_t *SATR = &vdc->SAT_Cache[i];
|
|
|
|
int16 y = SATR->y;
|
|
uint16 x = SATR->x;
|
|
uint16 no = SATR->no;
|
|
const uint16 flags = SATR->flags;
|
|
const uint8 cgmode = SATR->cgmode;
|
|
const uint16 height = SATR->height;
|
|
const uint32 palette_index = (flags & 0xF) << 4;
|
|
|
|
uint32 y_offset = vdc->RCRCount - y;
|
|
if(y_offset < height)
|
|
{
|
|
if(active_sprites == 16)
|
|
{
|
|
if(vdc->CR & 0x2)
|
|
{
|
|
vdc->status |= VDCS_OR;
|
|
HuC6280_IRQBegin(MDFN_IQIRQ1);
|
|
}
|
|
if(!unlimited_sprites)
|
|
break;
|
|
}
|
|
|
|
if(flags & SPRF_VFLIP)
|
|
y_offset = height - 1 - y_offset;
|
|
|
|
no |= (y_offset & 0x30) >> 3;
|
|
|
|
SpriteList[active_sprites].flags = flags;
|
|
|
|
SpriteList[active_sprites].x = x;
|
|
SpriteList[active_sprites].palette_index = palette_index;
|
|
|
|
SpriteList[active_sprites].no = no;
|
|
SpriteList[active_sprites].sub_y = (y_offset & 15);
|
|
|
|
|
|
CheckFixSpriteTileCache(vdc, no, (vdc->MWR & 0xC) | cgmode);
|
|
|
|
SpriteList[active_sprites].flags |= i ? 0 : SPRF_SPRITE0;
|
|
|
|
active_sprites++;
|
|
}
|
|
}
|
|
|
|
MDFN_FastU32MemsetM8((uint32 *)spr_linebuf, 0, ((end + 3) >> 1) & ~1);
|
|
|
|
if(!active_sprites)
|
|
return;
|
|
|
|
for(i = (active_sprites - 1) ; i >= 0; i--)
|
|
{
|
|
int increment = -1;
|
|
int32 x_second = 15;
|
|
const uint8 *pix_source;
|
|
int32 pos = SpriteList[i].x - 0x20;
|
|
uint32 prio_or;
|
|
uint16 *dest_pix;
|
|
|
|
if(pos > end)
|
|
continue;
|
|
|
|
dest_pix = &spr_linebuf[pos];
|
|
|
|
prio_or = 0x100 | SpriteList[i].palette_index;
|
|
|
|
if(SpriteList[i].flags & SPRF_PRIORITY)
|
|
prio_or |= SPR_HPMASK;
|
|
|
|
pix_source = vdc->spr_tile_cache[SpriteList[i].no][SpriteList[i].sub_y];
|
|
|
|
if(SpriteList[i].flags & SPRF_HFLIP)
|
|
{
|
|
increment = 1;
|
|
x_second = 0;
|
|
}
|
|
|
|
if((SpriteList[i].flags & SPRF_SPRITE0) && (vdc->CR & 0x01))
|
|
{
|
|
int32 x;
|
|
for(x = 0; x < 16; x++, x_second += increment)
|
|
{
|
|
const uint32 raw_pixel = pix_source[x_second];
|
|
if(raw_pixel)
|
|
{
|
|
if(((uint32)pos + x) >= (uint32)end) // Covers negative and overflowing the right side(to prevent spurious sprite hits)
|
|
continue;
|
|
|
|
if(dest_pix[x] & 0x100)
|
|
{
|
|
vdc->status |= VDCS_CR;
|
|
HuC6280_IRQBegin(MDFN_IQIRQ1);
|
|
}
|
|
dest_pix[x] = raw_pixel | prio_or;
|
|
}
|
|
}
|
|
} // End sprite0 handling
|
|
else // No sprite0 hit:
|
|
{
|
|
int32 x;
|
|
// x must be signed, for "pos + x" to not be promoted to unsigned, which will cause a stack overflow.
|
|
//
|
|
|
|
for(x = 0; x < 16; x++, x_second += increment)
|
|
{
|
|
const uint32 raw_pixel = pix_source[x_second];
|
|
if(raw_pixel)
|
|
dest_pix[x] = raw_pixel | prio_or;
|
|
}
|
|
|
|
} // End no sprite0 hit
|
|
}
|
|
}
|
|
|
|
static INLINE void MixBGSPR(const uint32 count_in, const uint8 *bg_linebuf_in, const uint16 *spr_linebuf_in, uint16_t *target_in)
|
|
{
|
|
unsigned int x;
|
|
for(x = 0; x < count_in; x++)
|
|
{
|
|
const uint32 bg_pixel = bg_linebuf_in[x];
|
|
const uint32 spr_pixel = spr_linebuf_in[x];
|
|
uint32 pixel = bg_pixel;
|
|
|
|
if ((spr_pixel & SPR_HPMASK) || !(bg_pixel & 0x0F))
|
|
pixel = spr_pixel;
|
|
|
|
target_in[x] = vce.color_table_cache[pixel & 0x1FF];
|
|
}
|
|
}
|
|
|
|
static void MixBGOnly(const uint32 count, const uint8 *bg_linebuf, uint16_t *target)
|
|
{
|
|
unsigned x;
|
|
|
|
for(x = 0; x < count; x++)
|
|
target[x] = vce.color_table_cache[bg_linebuf[x]];
|
|
}
|
|
|
|
static void MixSPROnly(const uint32 count, const uint16 *spr_linebuf, uint16_t *target)
|
|
{
|
|
unsigned x;
|
|
|
|
for(x = 0; x < count; x++)
|
|
target[x] = vce.color_table_cache[(spr_linebuf[x] | 0x100) & 0x1FF];
|
|
}
|
|
|
|
static void MixNone(const uint32 count, uint16_t *target)
|
|
{
|
|
unsigned x;
|
|
uint32 bg_color = vce.color_table_cache[0x000];
|
|
|
|
for(x = 0; x < count; x++)
|
|
target[x] = bg_color;
|
|
}
|
|
|
|
static void DrawOverscan(const vdc_t *vdc, uint16_t *target, const MDFN_Rect *lw, const bool full, const int32 vpl, const int32 vpr)
|
|
{
|
|
uint32 os_color = vce.color_table_cache[0x100];
|
|
int x = lw->x;
|
|
|
|
if(!full)
|
|
{
|
|
for(; x < vpl; x++)
|
|
target[x] = os_color;
|
|
|
|
x = vpr;
|
|
}
|
|
|
|
for(; x < lw->x + lw->w; x++)
|
|
target[x] = os_color;
|
|
}
|
|
|
|
void VDC_RunFrame(EmulateSpecStruct *espec, bool IsHES)
|
|
{
|
|
bool fc_vrm;
|
|
int max_dc = 0;
|
|
MDFN_Surface *surface = espec->surface;
|
|
MDFN_Rect *DisplayRect = &espec->DisplayRect;
|
|
bool skip = espec->skip || IsHES;
|
|
|
|
if(!skip){
|
|
DisplayRect->y = MDFN_GetSettingUI("pce_fast.slstart");
|
|
DisplayRect->h = MDFN_GetSettingUI("pce_fast.slend") - DisplayRect->y + 1;
|
|
}
|
|
|
|
//Change 352 mode width without restart
|
|
if (defined_width[1] != MDFN_GetSettingUI("pce_fast.hoverscan"))
|
|
defined_width[1] = MDFN_GetSettingUI("pce_fast.hoverscan");
|
|
|
|
do
|
|
{
|
|
const bool SHOULD_DRAW = (!skip && (int)frame_counter >= (DisplayRect->y + 14) && (int)frame_counter < (DisplayRect->y + DisplayRect->h + 14));
|
|
|
|
if(frame_counter == 0)
|
|
{
|
|
VDS = M_vdc_VDS;
|
|
VSW = M_vdc_VSW;
|
|
VDW = M_vdc_VDW;
|
|
VCR = M_vdc_VCR;
|
|
VBlankFL = VDS + VSW + VDW + 1;
|
|
|
|
if(VBlankFL > 261)
|
|
VBlankFL = 261;
|
|
}
|
|
|
|
need_vbi[0] = need_vbi[1] = 0;
|
|
|
|
#if 1
|
|
line_leadin1 = 0;
|
|
|
|
magical = M_vdc_HDS + (M_vdc_HDW + 1) + M_vdc_HDE;
|
|
magical = (magical + 2) & ~1;
|
|
magical -= M_vdc_HDW + 1;
|
|
cyc_tot = magical * 8; //ClockPixelWidths[vce.dot_clock] - magical * 8;
|
|
cyc_tot-=2;
|
|
switch(vce.dot_clock)
|
|
{
|
|
case 0: cyc_tot = 4 * cyc_tot / 3; break;
|
|
case 1: break;
|
|
case 2: cyc_tot = 2 * cyc_tot / 3; break;
|
|
}
|
|
|
|
if(cyc_tot < 0) cyc_tot = 0;
|
|
line_leadin1 = cyc_tot;
|
|
#endif
|
|
|
|
if(max_dc < vce.dot_clock)
|
|
max_dc = vce.dot_clock;
|
|
|
|
if(!skip)
|
|
{
|
|
DisplayRect->x = 0;
|
|
|
|
// Order of Griffon semi-hack
|
|
if (OrderOfGriffonFix)
|
|
{
|
|
// Force to use specified width to fit status bar inside frame.
|
|
defined_width[0] = defined_width[1] = 320;
|
|
}
|
|
|
|
DisplayRect->w = defined_width[vce.dot_clock];
|
|
}
|
|
|
|
{
|
|
int have_free_time = 1;
|
|
if(frame_counter == 0)
|
|
{
|
|
vdc->display_counter = 0;
|
|
vdc->burst_mode = !(vdc->CR & 0xC0);
|
|
}
|
|
|
|
if(vdc->display_counter == (VDS + VSW))
|
|
{
|
|
vdc->burst_mode = !(vdc->CR & 0xC0);
|
|
vdc->RCRCount = 0;
|
|
}
|
|
|
|
if(!vdc->burst_mode && vdc->display_counter >= (VDS + VSW) && vdc->display_counter < (VDS + VSW + VDW + 1))
|
|
have_free_time = 0;
|
|
|
|
if(have_free_time) // We're outside of the active display area. Weehee
|
|
{
|
|
if(vdc->DMARunning)
|
|
DoDMA(vdc);
|
|
}
|
|
|
|
if(vdc->display_counter == VBlankFL)
|
|
{
|
|
need_vbi[0] = 1;
|
|
if(vdc->SATBPending || (vdc->DCR & 0x10))
|
|
{
|
|
vdc->SATBPending = 0;
|
|
vdc->sat_dma_slcounter = 2;
|
|
|
|
DoSATDMA(vdc);
|
|
}
|
|
}
|
|
if((int)vdc->RCRCount == ((int)vdc->RCR - 0x40) && (vdc->CR & 0x04))
|
|
{
|
|
vdc->status |= VDCS_RR;
|
|
HuC6280_IRQBegin(MDFN_IQIRQ1);
|
|
}
|
|
}
|
|
|
|
HuC6280_Run(line_leadin1);
|
|
|
|
fc_vrm = (frame_counter >= 14 && frame_counter < (14 + 242 + 1));
|
|
|
|
{
|
|
MDFN_ALIGN(8) uint8 bg_linebuf[8 + 1024];
|
|
MDFN_ALIGN(8) uint16 spr_linebuf[16 + 1024];
|
|
|
|
uint16 *target_ptr16 = surface->pixels + (frame_counter - 14) * surface->pitch;
|
|
|
|
if(vdc->burst_mode)
|
|
{
|
|
if(fc_vrm && SHOULD_DRAW)
|
|
{
|
|
DrawOverscan(vdc, target_ptr16, DisplayRect, true, 0, 0);
|
|
}
|
|
}
|
|
else if(vdc->display_counter >= (VDS + VSW) && vdc->display_counter < (VDS + VSW + VDW + 1))
|
|
{
|
|
if(vdc->display_counter == (VDS + VSW))
|
|
vdc->BG_YOffset = vdc->BYR;
|
|
else
|
|
vdc->BG_YOffset++;
|
|
vdc->BG_XOffset = vdc->BXR;
|
|
|
|
if(fc_vrm)
|
|
{
|
|
uint32 start = (M_vdc_HDS + 1) * 8;
|
|
uint32 end = start + (M_vdc_HDW + 1) * 8;
|
|
|
|
if((vdc->CR & 0x80) && SHOULD_DRAW)
|
|
{
|
|
if(userle & (ULE_BG0))
|
|
DrawBG(vdc, end - start + (vdc->BG_XOffset & 7), bg_linebuf);
|
|
else
|
|
memset(bg_linebuf, 0, end - start + (vdc->BG_XOffset & 7));
|
|
}
|
|
|
|
if((vdc->CR & 0x40) && (SHOULD_DRAW || (vdc->CR & 0x03))) // Don't skip sprite drawing if we can generate sprite #0 or sprite overflow IRQs.
|
|
{
|
|
if((userle & (ULE_SPR0)) || (vdc->CR & 0x03))
|
|
DrawSprites(vdc, end - start, spr_linebuf + 0x20);
|
|
|
|
if(!(userle & (ULE_SPR0)))
|
|
memset(spr_linebuf + 0x20, 0, sizeof(uint16) * (end - start));
|
|
}
|
|
|
|
if(SHOULD_DRAW){
|
|
int32 width = end - start;
|
|
int32 source_offset = 0;
|
|
int32 target_offset = 0;
|
|
|
|
//Centre any picture thinner than its display mode width
|
|
if(width > 0 && width < defined_width[vce.dot_clock]){
|
|
target_offset = (defined_width[vce.dot_clock] - width)/2;
|
|
}
|
|
|
|
//Centre overscan cropping
|
|
if(vce.dot_clock == 1 && defined_width[1] < width){
|
|
target_offset += (defined_width[1] - width) / 2;
|
|
}
|
|
|
|
// Align TV Sport Basketball
|
|
if(vce.dot_clock ==2 && width > 512){
|
|
target_offset = - 16;
|
|
}
|
|
|
|
// Semi-hack for Asuka 120%
|
|
if(vce.dot_clock == 1 && M_vdc_HDS == 5 && M_vdc_HDE == 6 && M_vdc_HDW == 43 && M_vdc_HSW == 2)
|
|
target_offset += 8;
|
|
else if(vce.dot_clock == 0 && M_vdc_HDS == 2 && M_vdc_HDE == 3 && M_vdc_HDW == 33 && M_vdc_HSW == 2)
|
|
target_offset = 0;
|
|
// and for Addams Family
|
|
else if(vce.dot_clock == 1 && M_vdc_HDS == 4 && M_vdc_HDE == 4 && M_vdc_HDW == 43 && M_vdc_HSW == 9)
|
|
target_offset += 4;
|
|
// Final Blaster intro fix
|
|
else if(vce.dot_clock == 0 && M_vdc_HDS == 2 && M_vdc_HDE == 26 && M_vdc_HDW == 9 && M_vdc_HSW == 2)
|
|
target_offset -= 88;
|
|
else if(vce.dot_clock == 0 && M_vdc_HDS == 24 && M_vdc_HDE == 4 && M_vdc_HDW == 9 && M_vdc_HSW == 2)
|
|
target_offset += 88;
|
|
|
|
if(target_offset < 0)
|
|
{
|
|
width += target_offset;
|
|
source_offset += 0 - target_offset;
|
|
target_offset = 0;
|
|
}
|
|
|
|
if((target_offset + width) > DisplayRect->w)
|
|
width = (int32)DisplayRect->w - target_offset;
|
|
|
|
if(width > 0)
|
|
{
|
|
//else if(target_ptr16)
|
|
{
|
|
switch(vdc->CR & 0xC0)
|
|
{
|
|
case 0xC0:
|
|
MixBGSPR(width, bg_linebuf + (vdc->BG_XOffset & 7) + source_offset, spr_linebuf + 0x20 + source_offset, target_ptr16 + target_offset);
|
|
break;
|
|
case 0x80:
|
|
MixBGOnly(width, bg_linebuf + (vdc->BG_XOffset & 7) + source_offset, target_ptr16 + target_offset);
|
|
break;
|
|
case 0x40:
|
|
MixSPROnly(width, spr_linebuf + 0x20 + source_offset, target_ptr16 + target_offset);
|
|
break;
|
|
case 0x00:
|
|
MixNone(width, target_ptr16 + target_offset);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//else if(target_ptr16)
|
|
DrawOverscan(vdc, target_ptr16, DisplayRect, false, target_offset, target_offset + width);
|
|
} // end if(SHOULD_DRAW)
|
|
}
|
|
}
|
|
else if(SHOULD_DRAW && fc_vrm) // Hmm, overscan...
|
|
{
|
|
//else if(target_ptr16)
|
|
DrawOverscan(vdc, target_ptr16, DisplayRect, true, 0, 0);
|
|
}
|
|
}
|
|
|
|
if((vdc->CR & 0x08) && need_vbi[0])
|
|
vdc->status |= VDCS_VD;
|
|
|
|
HuC6280_Run(2);
|
|
|
|
if(vdc->status & VDCS_VD)
|
|
{
|
|
HuC6280_IRQBegin(MDFN_IQIRQ1);
|
|
}
|
|
|
|
HuC6280_Run(455 - line_leadin1 - 2);
|
|
|
|
if(PCE_IsCD)
|
|
{
|
|
PCECD_Run(HuCPU.timestamp * 3);
|
|
}
|
|
|
|
{
|
|
vdc->RCRCount++;
|
|
|
|
//vdc->BG_YOffset = (vdc->BG_YOffset + 1);
|
|
vdc->display_counter++;
|
|
|
|
if(vdc->sat_dma_slcounter)
|
|
{
|
|
vdc->sat_dma_slcounter--;
|
|
if(!vdc->sat_dma_slcounter)
|
|
{
|
|
if(vdc->DCR & 0x01)
|
|
{
|
|
vdc->status |= VDCS_DS;
|
|
HuC6280_IRQBegin(MDFN_IQIRQ1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(vdc->display_counter == (VDS + VSW + VDW + VCR + 3))
|
|
{
|
|
vdc->display_counter = 0;
|
|
}
|
|
}
|
|
|
|
frame_counter = (frame_counter + 1) % (vce.lc263 ? 263 : 262);
|
|
} while(frame_counter != VBlankFL); // big frame loop!
|
|
|
|
DisplayRect->w = defined_width[vce.dot_clock];
|
|
}
|
|
|
|
void VDC_Reset(void)
|
|
{
|
|
vdc->read_buffer = 0xFFFF;
|
|
|
|
vpc.priority[0] = vpc.priority[1] = 0x11;
|
|
|
|
vdc->HSR = vdc->HDR = vdc->VSR = vdc->VDR = vdc->VCR = 0xFF; // Needed for Body Conquest 2
|
|
|
|
frame_counter = 0;
|
|
}
|
|
|
|
void VDC_Power(void)
|
|
{
|
|
unsigned i;
|
|
memset(vdc, 0, sizeof(vdc_t));
|
|
VDC_Reset();
|
|
|
|
for(i = 0; i < 0x200; i++)
|
|
{
|
|
vce.color_table[i] = ((i ^ (i >> 3)) & 1) ? 0x000 : 0x1FF;
|
|
FixPCache(i);
|
|
}
|
|
}
|
|
|
|
void VDC_Init(void)
|
|
{
|
|
unlimited_sprites = MDFN_GetSettingB("pce_fast.nospritelimit");
|
|
defined_width[1] = MDFN_GetSettingUI("pce_fast.hoverscan");
|
|
userle = ~0;
|
|
|
|
vdc = (vdc_t *)malloc(sizeof(vdc_t));
|
|
}
|
|
|
|
void VDC_Close(void)
|
|
{
|
|
if(vdc)
|
|
free(vdc);
|
|
vdc = NULL;
|
|
}
|
|
|
|
int VDC_StateAction(StateMem *sm, int load, int data_only)
|
|
{
|
|
SFORMAT VCE_StateRegs[] =
|
|
{
|
|
SFVARN(vce.CR, "VCECR"),
|
|
SFVAR_BOOL(vce.lc263),
|
|
SFVAR_BOOL(vce.bw),
|
|
SFVARN(vce.dot_clock, "dot clock"),
|
|
SFVARN(vce.ctaddress, "ctaddress"),
|
|
SFARRAY16N(vce.color_table, 0x200, "color_table"),
|
|
SFEND
|
|
};
|
|
|
|
|
|
int ret = MDFNSS_StateAction(sm, load, data_only, VCE_StateRegs, "VCE", false);
|
|
|
|
int chip = 0;
|
|
{
|
|
SFORMAT VDC_StateRegs[] =
|
|
{
|
|
SFVARN(vdc->display_counter, "display_counter"),
|
|
SFVARN(vdc->sat_dma_slcounter, "sat_dma_slcounter"),
|
|
|
|
SFVARN(vdc->select, "select"),
|
|
SFVARN(vdc->MAWR, "MAWR"),
|
|
SFVARN(vdc->MARR, "MARR"),
|
|
SFVARN(vdc->CR, "CR"),
|
|
SFVARN(vdc->RCR, "RCR"),
|
|
SFVARN(vdc->BXR, "BXR"),
|
|
SFVARN(vdc->BYR, "BYR"),
|
|
SFVARN(vdc->MWR, "MWR"),
|
|
|
|
SFVARN(vdc->HSR, "HSR"),
|
|
SFVARN(vdc->HDR, "HDR"),
|
|
SFVARN(vdc->VSR, "VSR"),
|
|
SFVARN(vdc->VDR, "VDR"),
|
|
|
|
SFVARN(vdc->VCR, "VCR"),
|
|
SFVARN(vdc->DCR, "DCR"),
|
|
SFVARN(vdc->SOUR, "SOUR"),
|
|
SFVARN(vdc->DESR, "DESR"),
|
|
SFVARN(vdc->LENR, "LENR"),
|
|
SFVARN(vdc->SATB, "SATB"),
|
|
|
|
SFVARN(vdc->RCRCount, "RCRCount"),
|
|
|
|
|
|
SFVARN(vdc->read_buffer, "read_buffer"),
|
|
SFVARN(vdc->write_latch, "write_latch"),
|
|
SFVARN(vdc->status, "status"),
|
|
|
|
SFARRAY16N(vdc->SAT, 0x100, "SAT"),
|
|
|
|
SFARRAY16N(vdc->VRAM, VRAM_Size, "VRAM"),
|
|
SFVARN(vdc->DMAReadBuffer, "DMAReadBuffer"),
|
|
SFVAR_BOOL(vdc->DMAReadWrite),
|
|
SFVAR_BOOL(vdc->DMARunning),
|
|
SFVAR_BOOL(vdc->SATBPending),
|
|
SFVAR_BOOL(vdc->burst_mode),
|
|
|
|
SFVARN(vdc->BG_YOffset, "BG_YOffset"),
|
|
SFVARN(vdc->BG_XOffset, "BG_XOffset"),
|
|
SFVAR(frame_counter),
|
|
SFVARN(VDS, "VDS_cache"),
|
|
SFVARN(VSW, "VSW_cache"),
|
|
SFVARN(VDW, "VDW_cache"),
|
|
SFVARN(VCR, "VCR_cache"),
|
|
SFVARN(VBlankFL, "VBlankFL_cache"),
|
|
|
|
SFARRAY32(need_vbi, 2),
|
|
SFVAR(line_leadin1),
|
|
SFVAR(magical),
|
|
SFVAR(cyc_tot),
|
|
SFEND
|
|
};
|
|
|
|
ret &= MDFNSS_StateAction(sm, load, data_only, VDC_StateRegs, "VDC0", false);
|
|
|
|
if(load)
|
|
{
|
|
int x;
|
|
for(x = 0; x < VRAM_Size; x++)
|
|
{
|
|
FixTileCache(vdc, x);
|
|
vdc->spr_tile_clean[x >> 6] = 0;
|
|
}
|
|
for(x = 0; x < 512; x++)
|
|
FixPCache(x);
|
|
RebuildSATCache(vdc);
|
|
}
|
|
|
|
}
|
|
|
|
return(ret);
|
|
}
|