BizHawk/waterbox/pcfx/huc6270/vdc.cpp

1797 lines
40 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 emulation */
#include "../defs.h"
//#include <mednafen/video.h>
#include <math.h>
#include "vdc.h"
#define VDC_DEBUG(x, ...) { }
//#define VDC_DEBUG(x, ...) printf(x ": HPhase=%d, HPhaseCounter=%d, RCRCount=%d\n", ## __VA_ARGS__, HPhase, HPhaseCounter, RCRCount);
#define VDC_UNDEFINED(format, ...) { }
//#define VDC_UNDEFINED(format, ...) printf(format " RCRCount=%d" "\n", ## __VA_ARGS__, RCRCount)
#define VDC_WARNING(format, ...) { }
//#define VDC_WARNING(format, ...) { printf(format "\n", ## __VA_ARGS__); }
#define ULE_BG 1
#define ULE_SPR 2
static const unsigned int bat_width_tab[4] = { 32, 64, 128, 128 };
static const unsigned int bat_width_shift_tab[4] = { 5, 6, 7, 7 };
static const unsigned int bat_height_tab[2] = { 32, 64 };
void VDC::FixTileCache(uint16 A)
{
uint32 charname = (A >> 4);
uint32 y = (A & 0x7);
uint8 *tc = bg_tile_cache[charname][y];
uint32 bitplane01 = VRAM[y + charname * 16];
uint32 bitplane23 = VRAM[y+ 8 + charname * 16];
for(int 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;
tc[7 - x] = raw_pixel;
}
}
// Some virtual vdc macros to make code simpler to read
#define M_vdc_HSW (HSR & 0x1F) // Horizontal Synchro Width
#define M_vdc_HDS ((HSR >> 8) & 0x7F) // Horizontal Display Start
#define M_vdc_HDW (HDR & 0x7F) // Horizontal Display Width
#define M_vdc_HDE ((HDR >> 8) & 0x7F) // Horizontal Display End
#define M_vdc_VSW (VSR & 0x1F) // Vertical synchro width
#define M_vdc_VDS ((VSR >> 8) & 0xFF) // Vertical Display Start
#define M_vdc_VDW (VDR & 0x1FF) // Vertical Display Width(Height? :b)
#define M_vdc_VCR (VCR & 0xFF)
#define M_vdc_EX ((CR >> 4) & 0x3)
#define M_vdc_TE ((CR >> 8) & 0x3)
#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??
uint32 VDC::GetRegister(const unsigned int id, char *special, const uint32 special_len)
{
uint32 value = 0xDEADBEEF;
switch(id)
{
case GSREG_SELECT:
value = select;
break;
case GSREG_STATUS:
value = status;
break;
case GSREG_MAWR:
value = MAWR;
break;
case GSREG_MARR:
value = MARR;
break;
case GSREG_CR:
value = CR;
if(special)
{
trio_snprintf(special, special_len, "Sprite Hit IRQ: %s, Sprite Overflow IRQ: %s, RCR IRQ: %s, VBlank IRQ: %s, Sprites: %s, Background: %s", (value & 1) ? "On" : "Off", (value & 2) ? "On" : "Off",
(value & 4) ? "On" : "Off", (value & 8) ? "On" : "Off", (value & 0x40) ? "On" : "Off", (value & 0x80) ? "On" : "Off");
}
break;
case GSREG_RCR:
value = RCR;
break;
case GSREG_BXR:
value = BXR;
break;
case GSREG_BYR:
value = BYR;
break;
case GSREG_MWR:
value = MWR;
if(special)
{
trio_snprintf(special, special_len, "CG Mode: %d, BAT Width: %d(tiles), BAT Height: %d(tiles)", (int)(bool)(value & 0x80),
bat_width_tab[(value >> 4) & 0x3],
bat_height_tab[(value >> 6) & 0x1]);
}
break;
case GSREG_HSR:
value = HSR;
if(special)
{
trio_snprintf(special, special_len, "HSW: %02x, HDS: %02x", value & 0x1F, (value >> 8) & 0x7F);
}
break;
case GSREG_HDR:
value = HDR;
if(special)
{
trio_snprintf(special, special_len, "HDW: %02x, HDE: %02x", value & 0x7F, (value >> 8) & 0x7F);
}
break;
case GSREG_VSR:
value = VSR;
if(special)
{
trio_snprintf(special, special_len, "VSW: %02x, VDS: %02x", value & 0x1F, (value >> 8) & 0xFF);
}
break;
case GSREG_VDR:
value = VDR;
break;
case GSREG_VCR:
value = VCR;
break;
case GSREG_DCR:
value = DCR;
if(special)
{
trio_snprintf(special, special_len, "SATB DMA IRQ: %s, VRAM DMA IRQ: %s, DMA Source Address: %s, DMA Dest Address: %s, Auto SATB DMA: %s",
(DCR & 0x1) ? "On" : "Off", (DCR & 0x2) ? "On" : "Off", (DCR & 0x4) ? "Decrement" : "Increment", (DCR & 0x8) ? "Decrement" : "Increment",
(DCR & 0x10) ? "On" : "Off");
}
break;
case GSREG_SOUR:
value = SOUR;
break;
case GSREG_DESR:
value = DESR;
break;
case GSREG_LENR:
value = LENR;
break;
case GSREG_DVSSR:
value = DVSSR;
break;
}
return(value);
}
void VDC::SetRegister(const unsigned int id, const uint32 value)
{
switch(id)
{
default: break;
case GSREG_STATUS:
status = value & 0x3F;
break;
case GSREG_SELECT:
select = value & 0x1F;
break;
case GSREG_MAWR:
MAWR = value & 0xFFFF;
break;
case GSREG_MARR:
MARR = value & 0xFFFF;
break;
case GSREG_CR:
CR = value & 0xFFFF;
break;
case GSREG_RCR:
RCR = value & 0x3FF;
break;
case GSREG_BXR:
BXR = value & 0x3FF;
break;
case GSREG_BYR:
BYR = value & 0x1FF;
break;
case GSREG_MWR:
MWR = value & 0xFFFF;
break;
case GSREG_HSR:
HSR = value & 0xFFFF;
break;
case GSREG_HDR:
HDR = value & 0xFFFF;
break;
case GSREG_VSR:
VSR = value & 0xFFFF;
break;
case GSREG_VDR:
VDR = value & 0xFFFF;
break;
case GSREG_VCR:
VCR = value & 0xFFFF;
break;
case GSREG_DCR:
DCR = value & 0xFFFF;
break;
case GSREG_SOUR:
SOUR = value & 0xFFFF;
break;
case GSREG_DESR:
DESR = value & 0xFFFF;
break;
case GSREG_LENR:
LENR = value & 0xFFFF;
break;
case GSREG_DVSSR:
DVSSR = value & 0xFFFF;
break;
}
}
void VDC::SetLayerEnableMask(uint64 mask)
{
userle = mask;
}
void VDC::RunSATDMA(int32 cycles, bool force_completion)
{
assert(sat_dma_counter > 0);
if(force_completion)
cycles = sat_dma_counter;
sat_dma_counter -= cycles;
if(sat_dma_counter <= 0)
{
if(DCR & 0x01)
{
VDC_DEBUG("Sprite DMA IRQ");
status |= VDCS_DS;
IRQHook(TRUE);
}
CheckAndCommitPending();
burst_mode = true;
}
}
void VDC::RunDMA(int32 cycles, bool force_completion)
{
int num_transfers = 0;
if(force_completion)
{
VDMA_CycleCounter = 0;
num_transfers = 65536 * 2;
}
else
{
VDMA_CycleCounter += cycles;
num_transfers = VDMA_CycleCounter >> 1;
VDMA_CycleCounter -= num_transfers << 1;
}
while(num_transfers--)
{
if(!DMAReadWrite)
{
if(SOUR >= VRAM_Size)
VDC_UNDEFINED("Unmapped VRAM DMA read");
DMAReadBuffer = VRAM[SOUR];
//printf("DMA Read: %04x, %04x\n", SOUR, DMAReadBuffer);
}
else
{
if(DESR < VRAM_Size)
{
VRAM[DESR] = DMAReadBuffer;
FixTileCache(DESR);
}
SOUR += (((DCR & 0x4) >> 1) ^ 2) - 1;
DESR += (((DCR & 0x8) >> 2) ^ 2) - 1;
LENR--;
if(LENR == 0xFFFF) // DMA is done.
{
DMARunning = 0; // Clear this BEFORE CheckAndCommitPending()
CheckAndCommitPending();
if(DCR & 0x02)
{
status |= VDCS_DV;
IRQHook(TRUE);
VDC_DEBUG("DMA IRQ");
}
break;
}
}
DMAReadWrite ^= 1;
}
}
/*
<RyphZomb> ChrlyMac: Was it you who determined exactly how many VDC clocks the SAT DMA took?
<RyphZomb> I know someone did, but I can't remember the results...
<ChrlyMac> 1024
<ChrlyMac> It happens at the VDW->VCR transition
*/
void VDC::IncRCR(void)
{
if(NeedBGYInc)
{
NeedBGYInc = false;
if(0 == RCRCount)
BG_YMoo = BYR;
else
BG_YMoo++;
}
NeedBGYInc = true;
RCRCount++;
VPhaseCounter--;
if(VPhaseCounter <= 0)
{
VPhase = (VPhase + 1) % VPHASE_COUNT;
switch(VPhase)
{
case VPHASE_VDS: VPhaseCounter = VDS_cache + 2;
break;
case VPHASE_VDW: VPhaseCounter = VDW_cache + 1;
//BG_YMoo = BYR - 1;
RCRCount = 0;
burst_mode = !(CR & 0xC0);
NeedVBIRQTest = true;
NeedSATDMATest = true;
if(!burst_mode)
{
if(sat_dma_counter > 0)
{
printf("SAT DMA cancelled???\n");
sat_dma_counter = 0;
CheckAndCommitPending();
}
if(DMARunning)
{
printf("DMA Running Cancelled\n");
DMARunning = false;
CheckAndCommitPending();
}
}
break;
case VPHASE_VCR: VPhaseCounter = VCR_cache;
break;
case VPHASE_VSW: VPhaseCounter = VSW_cache + 1;
MWR_cache = MWR;
VDS_cache = M_vdc_VDS;
VSW_cache = M_vdc_VSW;
VDW_cache = M_vdc_VDW;
VCR_cache = M_vdc_VCR;
//VDC_WARNING("VSW Started");
break;
}
}
if(VPhase == VPHASE_VDW && !burst_mode)
{
FetchSpriteData();
}
if((int)RCRCount == ((int)RCR - 0x40) && (CR & 0x04))
{
VDC_DEBUG("RCR IRQ");
status |= VDCS_RR;
IRQHook(TRUE);
}
}
void VDC::DoVBIRQTest(void)
{
if(CR & 0x08)
{
VDC_DEBUG("VBlank IRQ");
status |= VDCS_VD;
IRQHook(TRUE);
}
}
static const int Cycles_Between_RCRIRQ_And_HDWEnd = 4;
int VDC::TimeFromHDSStartToBYRLatch(void)
{
int ret = 1;
if(HDS_cache > 2)
ret += ((HDS_cache + 1) * 8) - 24 + 2;
//printf("%d, %d\n", HDS_cache, ret);
return(ret);
}
int VDC::TimeFromBYRLatchToBXRLatch(void)
{
int ret = 2;
if(HDS_cache > 2)
ret = 1;
return(ret);
}
void VDC::HDS_Start(void)
{
if(NeedRCRInc)
{
IncRCR();
NeedRCRInc = false;
}
if(sprite_cg_fetch_counter > 0)
{
VDC_WARNING("Sprite truncation on %d. Wanted sprites: %d, cycles needed but not left: %d\n", RCRCount, active_sprites, sprite_cg_fetch_counter);
sprite_cg_fetch_counter = 0;
CheckAndCommitPending();
}
HSW_cache = M_vdc_HSW;
HDS_cache = M_vdc_HDS;
HDW_cache = M_vdc_HDW;
HDE_cache = M_vdc_HDE;
VDC_DEBUG("HDS Start! HSW: %d, HDW: %d, HDW: %d, HDE: %d\n", HSW_cache, HDS_cache, HDW_cache, HDE_cache);
CR_cache = CR;
HPhase = HPHASE_HDS;
HPhaseCounter = TimeFromHDSStartToBYRLatch();
}
int32 VDC::HSync(bool hb)
{
if(M_vdc_EX)
{
in_exhsync = 0;
return(CalcNextEvent());
}
in_exhsync = hb;
if(hb) // Going into hsync
{
mystery_counter = 48;
mystery_phase = false;
}
else // Leaving hsync
{
HPhase = HPHASE_HSW;
HPhaseCounter = 8;
//HDS_Start();
//HPhaseCounter += 8;
pixel_copy_count = 0;
}
return(CalcNextEvent());
}
int32 VDC::VSync(bool vb)
{
if(M_vdc_EX >= 0x2)
{
in_exvsync = 0;
return(CalcNextEvent());
}
in_exvsync = vb;
//printf("VBlank: %d\n", vb);
if(vb) // Going into vsync
{
NeedRCRInc = false;
NeedBGYInc = false;
/* if(NeedRCRInc)
{
IncRCR();
NeedRCRInc = false;
}
*/
MWR_cache = MWR;
VDS_cache = M_vdc_VDS;
VSW_cache = M_vdc_VSW;
VDW_cache = M_vdc_VDW;
VCR_cache = M_vdc_VCR;
VPhase = VPHASE_VSW;
VPhaseCounter = VSW_cache + 1;
}
else // Leaving vsync
{
}
return(CalcNextEvent());
}
//int32 VDC::Run(int32 clocks, bool hs, bool vs, uint16 *pixels, bool skip)
int32 VDC::Run(int32 clocks, uint16 *pixels, bool skip)
{
//uint16 *spixels = pixels;
//puts("Run begin");
//fflush(stdout);
while(clocks > 0)
{
int32 chunk_clocks = clocks;
if(chunk_clocks > HPhaseCounter)
{
chunk_clocks = HPhaseCounter;
}
if(sat_dma_counter > 0 && chunk_clocks > sat_dma_counter)
chunk_clocks = sat_dma_counter;
if(sprite_cg_fetch_counter > 0 && chunk_clocks > sprite_cg_fetch_counter)
chunk_clocks = sprite_cg_fetch_counter;
if(mystery_counter > 0 && chunk_clocks > mystery_counter)
chunk_clocks = mystery_counter;
if(mystery_counter > 0)
{
mystery_counter -= chunk_clocks;
if(mystery_counter <= 0)
{
mystery_phase = !mystery_phase;
if(mystery_phase)
mystery_counter = 16;
else
CheckAndCommitPending();
}
}
if(sprite_cg_fetch_counter > 0)
{
sprite_cg_fetch_counter -= chunk_clocks;
if(sprite_cg_fetch_counter <= 0)
CheckAndCommitPending();
}
if(VPhase != VPHASE_VDW)
{
if(NeedSATDMATest)
{
NeedSATDMATest = false;
if(SATBPending || (DCR & 0x10))
{
SATBPending = 0;
sat_dma_counter = 1024;
if(DVSSR > (VRAM_Size - 0x100))
VDC_UNDEFINED("Unmapped VRAM DVSSR DMA read");
if(DVSSR < VRAM_Size)
{
uint32 len = 256;
if(DVSSR > (VRAM_Size - 0x100))
len = VRAM_Size - DVSSR;
memcpy(SAT, &VRAM[DVSSR], len * sizeof(uint16));
}
}
else
burst_mode = true;
}
}
if(DMAPending && burst_mode)
{
VDC_DEBUG("DMA Started");
DMAPending = false;
DMARunning = true;
VDMA_CycleCounter = 0;
DMAReadWrite = 0;
}
if(sat_dma_counter > 0)
RunSATDMA(chunk_clocks);
else if(DMARunning)
RunDMA(chunk_clocks);
if(pixel_copy_count > 0)
{
if(!skip)
{
for(int i = 0; i < chunk_clocks; i++)
pixels[i] = linebuf[pixel_desu + i];
//memcpy(pixels, linebuf + pixel_desu, chunk_clocks * sizeof(uint16));
if(M_vdc_TE == 0x1)
for(int i = 0; i < chunk_clocks; i++)
pixels[i] |= VDC_DISP_OUT_MASK;
}
pixel_desu += chunk_clocks;
pixel_copy_count -= chunk_clocks;
}
else
{
uint16 pix = 0x100;
if(M_vdc_TE == 0x1)
{
if(HPhase != HPHASE_HDS && HPhase != HPHASE_HDS_PART2 && HPhase != HPHASE_HDS_PART3)
pix |= VDC_DISP_OUT_MASK;
}
if(HPhase == HPHASE_HSW)
{
if(M_vdc_EX >= 0x1)
pix |= VDC_HSYNC_OUT_MASK;
if(M_vdc_TE >= 0x2)
pix |= VDC_DISP_OUT_MASK;
}
if(VPhase == VPHASE_VSW && M_vdc_EX >= 0x2)
pix |= VDC_VSYNC_OUT_MASK;
if(!(userle & 1))
pix |= VDC_BGDISABLE_OUT_MASK;
if(!skip)
{
for(int i = 0; i < chunk_clocks; i++)
pixels[i] = pix;
}
}
HPhaseCounter -= chunk_clocks;
assert(HPhaseCounter >= 0);
while(HPhaseCounter <= 0)
{
HPhase = (HPhase + 1) % HPHASE_COUNT;
switch(HPhase)
{
case HPHASE_HDS: HDS_Start();
break;
case HPHASE_HDS_PART2:
HPhaseCounter = TimeFromBYRLatchToBXRLatch();
if(NeedBGYInc && !in_exhsync)
{
NeedBGYInc = false;
if(0 == RCRCount)
BG_YMoo = BYR;
else
BG_YMoo++;
}
BG_YOffset = BG_YMoo;
break;
case HPHASE_HDS_PART3:
HPhaseCounter = (HDS_cache + 1) * 8 - TimeFromHDSStartToBYRLatch() - TimeFromBYRLatchToBXRLatch();
assert(HPhaseCounter > 0);
BG_XOffset = BXR;
break;
case HPHASE_HDW:
NeedRCRInc = true;
if(VPhase != VPHASE_VDW && NeedVBIRQTest)
{
DoVBIRQTest();
NeedVBIRQTest = false;
}
CheckAndCommitPending();
HPhaseCounter = (HDW_cache + 1) * 8 - Cycles_Between_RCRIRQ_And_HDWEnd;
if(VPhase == VPHASE_VDW)
{
if(!burst_mode)
{
pixel_desu = 0;
pixel_copy_count = (HDW_cache + 1) * 8;
// BG off, sprite on: fill = 0x100. bg off, sprite off: fill = 0x000
if(!(CR_cache & 0x80))
{
uint16 fill_val;
if(!(CR_cache & 0xC0)) // Sprites and BG off
fill_val = 0x000;
else // Only BG off
fill_val = 0x100 | ((userle & ULE_BG) ? 0 : VDC_BGDISABLE_OUT_MASK);
if(!(userle & ULE_BG))
fill_val |= VDC_BGDISABLE_OUT_MASK;
for(int i = 0; i < pixel_copy_count; i++)
linebuf[i] = fill_val;
}
if(!skip)
if(CR_cache & 0x80)
{
DrawBG(linebuf, userle & ULE_BG);
}
//printf("%d %02x %02x\n", RCRCount, CR, CR_cache);
if(CR_cache & 0x40)
DrawSprites(linebuf, (userle & ULE_SPR) && !skip);
}
}
break;
case HPHASE_HDW_FINAL:
if(NeedRCRInc)
{
IncRCR();
NeedRCRInc = false;
}
HPhaseCounter = Cycles_Between_RCRIRQ_And_HDWEnd;
break;
case HPHASE_HDE: //if(!burst_mode) //if(VPhase == VPHASE_VDW) //if(!burst_mode)
// lastats = 16; // + 16;
//else
// lastats = 16;
HPhaseCounter = (HDE_cache + 1) * 8;
break;
case HPHASE_HSW: HPhaseCounter = (HSW_cache + 1) * 8; break;
}
}
pixels += chunk_clocks;
clocks -= chunk_clocks;
}
//puts("Run end");
//fflush(stdout);
return(CalcNextEvent());
}
void VDC::CalcWidthStartEnd(uint32 &display_width, uint32 &start, uint32 &end)
{
display_width = (M_vdc_HDW + 1) * 8;
start = 0;
end = start + display_width;
}
void VDC::DrawBG(uint16 *target, int enabled)
{
uint32 width;
uint32 start;
uint32 end;
int bat_width = bat_width_tab[(MWR_cache >> 4) & 3];
int bat_width_mask = bat_width - 1;
int bat_width_shift = bat_width_shift_tab[(MWR_cache >> 4) & 3];
int bat_height_mask = bat_height_tab[(MWR_cache >> 6) & 1] - 1;
CalcWidthStartEnd(width, start, end);
if(!enabled)
{
for(uint32 x = start; x < end; x++)
target[x] = 0x000 | VDC_BGDISABLE_OUT_MASK;
return;
}
{
int bat_y = ((BG_YOffset >> 3) & bat_height_mask) << bat_width_shift;
uint32 first_end = start + 8 - (BG_XOffset & 7);
uint32 dohmask = 0xFFFFFFFF;
if((MWR_cache & 0x3) == 0x3)
{
if(MWR_cache & 0x80)
dohmask = 0xCCCCCCCC;
else
dohmask = 0x33333333;
}
// Draw the first pixels of the first tile, depending on the lower 3 bits of the xscroll/xoffset register, to
// we can render the rest of the line in 8x1 chunks, which is faster.
for(uint32 x = start; x < first_end; x++)
{
int bat_x = (BG_XOffset >> 3) & bat_width_mask;
uint16 bat = VRAM[bat_x | bat_y];
const uint8 pal_or = ((bat >> 8) & 0xF0);
int palette_index = ((bat >> 12) & 0x0F) << 4;
uint32 raw_pixel;
raw_pixel = bg_tile_cache[bat & 0xFFF][BG_YOffset & 7][BG_XOffset & 0x7] & dohmask;
target[x] = palette_index | raw_pixel | pal_or;
if((bat & 0xFFF) > VRAM_BGTileNoMask)
VDC_UNDEFINED("Unmapped BG tile read");
BG_XOffset++;
}
int bat_boom = (BG_XOffset >> 3) & bat_width_mask;
int line_sub = BG_YOffset & 7;
if((MWR_cache & 0x3) == 0x3)
{
for(uint32 x = first_end; x < end; x+=8)
{
const uint16 bat = VRAM[bat_boom | bat_y];
const uint8 pal_or = ((bat >> 8) & 0xF0);
uint8 *pix_lut = bg_tile_cache[bat & 0xFFF][line_sub];
if((bat & 0xFFF) > VRAM_BGTileNoMask)
VDC_UNDEFINED("Unmapped BG tile read");
(target + 0)[x] = (pix_lut[0] & dohmask) | pal_or;
(target + 1)[x] = (pix_lut[1] & dohmask) | pal_or;
(target + 2)[x] = (pix_lut[2] & dohmask) | pal_or;
(target + 3)[x] = (pix_lut[3] & dohmask) | pal_or;
(target + 4)[x] = (pix_lut[4] & dohmask) | pal_or;
(target + 5)[x] = (pix_lut[5] & dohmask) | pal_or;
(target + 6)[x] = (pix_lut[6] & dohmask) | pal_or;
(target + 7)[x] = (pix_lut[7] & dohmask) | pal_or;
bat_boom = (bat_boom + 1) & bat_width_mask;
BG_XOffset++;
}
}
else
for(uint32 x = first_end; x < end; x+=8) // This will draw past the right side of the buffer, but since our pitch is 1024, and max width is ~512, we're safe. Also,
// any overflow that is on the visible screen are will be hidden by the overscan color code below this code.
{
const uint16 bat = VRAM[bat_boom | bat_y];
const uint8 pal_or = ((bat >> 8) & 0xF0);
uint8 *pix_lut = bg_tile_cache[bat & 0xFFF][line_sub];
if((bat & 0xFFF) > VRAM_BGTileNoMask)
VDC_UNDEFINED("Unmapped BG tile read");
(target + 0)[x] = pix_lut[0] | pal_or;
(target + 1)[x] = pix_lut[1] | pal_or;
(target + 2)[x] = pix_lut[2] | pal_or;
(target + 3)[x] = pix_lut[3] | pal_or;
(target + 4)[x] = pix_lut[4] | pal_or;
(target + 5)[x] = pix_lut[5] | pal_or;
(target + 6)[x] = pix_lut[6] | pal_or;
(target + 7)[x] = pix_lut[7] | pal_or;
bat_boom = (bat_boom + 1) & bat_width_mask;
BG_XOffset++;
}
}
}
#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 const unsigned int sprite_width_tab[2] = { 16, 32 };
void VDC::FetchSpriteData(void)
{
active_sprites = 0;
// First, grab the up to 16 sprites.
for(int i = 0; i < 64; i++)
{
int16 y = (SAT[i * 4 + 0] & 0x3FF) - 0x40;
uint16 x = (SAT[i * 4 + 1] & 0x3FF);
uint16 no = (SAT[i * 4 + 2] >> 1) & 0x3FF; // Todo, cg mode bit
uint16 flags = (SAT[i * 4 + 3]);
uint32 palette_index = (flags & 0xF) << 4;
uint32 height = sprite_height_tab[(flags >> 12) & 3];
uint32 width = sprite_width_tab[(flags >> 8) & 1];
if((int32)RCRCount >= y && (int32)RCRCount < (int32)(y + height))
{
bool second_half = 0;
uint32 y_offset = RCRCount - y;
if(y_offset > height) continue;
breepbreep:
if(active_sprites == 16)
{
if(CR & 0x2)
{
status |= VDCS_OR;
IRQHook(TRUE);
VDC_DEBUG("Overflow IRQ");
}
if(!unlimited_sprites)
break;
}
{
if(flags & SPRF_VFLIP)
y_offset = height - 1 - y_offset;
no &= sprite_height_no_mask[(flags >> 12) & 3];
no |= (y_offset & 0x30) >> 3;
if(width == 32) no &= ~1;
if(second_half)
no |= 1;
SpriteList[active_sprites].flags = flags;
if(flags & SPRF_HFLIP && width == 32)
no ^= 1;
//printf("Found: %d %d\n", RCRCount, x);
SpriteList[active_sprites].x = x;
SpriteList[active_sprites].palette_index = palette_index;
if((no * 64) >= VRAM_Size)
VDC_UNDEFINED("Unmapped VRAM sprite tile read");
if((MWR_cache & 0xC) == 4)
{
if(SAT[i * 4 + 2] & 1)
{
SpriteList[active_sprites].pattern_data[0] = VRAM[no * 64 + (y_offset & 15) + 32];
SpriteList[active_sprites].pattern_data[1] = VRAM[no * 64 + (y_offset & 15) + 48];
SpriteList[active_sprites].pattern_data[2] = 0;
SpriteList[active_sprites].pattern_data[3] = 0;
}
else
{
SpriteList[active_sprites].pattern_data[0] = VRAM[no * 64 + (y_offset & 15) ];
SpriteList[active_sprites].pattern_data[1] = VRAM[no * 64 + (y_offset & 15) + 16];
SpriteList[active_sprites].pattern_data[2] = 0;
SpriteList[active_sprites].pattern_data[3] = 0;
}
}
else
{
SpriteList[active_sprites].pattern_data[0] = VRAM[no * 64 + (y_offset & 15) ];
SpriteList[active_sprites].pattern_data[1] = VRAM[no * 64 + (y_offset & 15) + 16];
SpriteList[active_sprites].pattern_data[2] = VRAM[no * 64 + (y_offset & 15) + 32];
SpriteList[active_sprites].pattern_data[3] = VRAM[no * 64 + (y_offset & 15) + 48];
}
SpriteList[active_sprites].flags |= i ? 0 : SPRF_SPRITE0;
active_sprites++;
if(width == 32 && !second_half)
{
second_half = 1;
x += 16;
y_offset = RCRCount - y; // Fix the y offset so that sprites that are hflipped + vflipped display properly
goto breepbreep;
}
}
}
}
sprite_cg_fetch_counter = ((active_sprites < 16) ? active_sprites : 16) * 4;
}
void VDC::DrawSprites(uint16 *target, int enabled)
{
alignas(16) uint16 sprite_line_buf[1024];
uint32 display_width, start, end;
CalcWidthStartEnd(display_width, start, end);
for(unsigned int i = start; i < end; i++)
sprite_line_buf[i] = 0;
for(int i = (active_sprites - 1) ; i >= 0; i--)
{
int32 pos = SpriteList[i].x - 0x20 + start;
uint32 prio_or = 0;
if(SpriteList[i].flags & SPRF_PRIORITY)
prio_or = 0x200;
if((SpriteList[i].flags & SPRF_SPRITE0) && (CR & 0x01))
{
for(uint32 x = 0; x < 16; x++)
{
uint32 raw_pixel;
uint32 pi = SpriteList[i].palette_index;
uint32 rev_x = 15 - x;
if(SpriteList[i].flags & SPRF_HFLIP)
rev_x = x;
raw_pixel = (SpriteList[i].pattern_data[0] >> rev_x) & 1;
raw_pixel |= ((SpriteList[i].pattern_data[1] >> rev_x) & 1) << 1;
raw_pixel |= ((SpriteList[i].pattern_data[2] >> rev_x) & 1) << 2;
raw_pixel |= ((SpriteList[i].pattern_data[3] >> rev_x) & 1) << 3;
if(raw_pixel)
{
pi |= 0x100;
uint32 tx = pos + x;
if(tx >= end) // Covers negative and overflowing the right side.
continue;
if(sprite_line_buf[tx] & 0xF)
{
status |= VDCS_CR;
VDC_DEBUG("Sprite hit IRQ");
IRQHook(TRUE);
}
sprite_line_buf[tx] = pi | raw_pixel | prio_or;
}
}
} // End sprite hit loop
else
{
for(uint32 x = 0; x < 16; x++)
{
uint32 raw_pixel;
uint32 pi = SpriteList[i].palette_index;
uint32 rev_x = 15 - x;
if(SpriteList[i].flags & SPRF_HFLIP)
rev_x = x;
raw_pixel = (SpriteList[i].pattern_data[0] >> rev_x) & 1;
raw_pixel |= ((SpriteList[i].pattern_data[1] >> rev_x) & 1) << 1;
raw_pixel |= ((SpriteList[i].pattern_data[2] >> rev_x) & 1) << 2;
raw_pixel |= ((SpriteList[i].pattern_data[3] >> rev_x) & 1) << 3;
if(raw_pixel)
{
pi |= 0x100;
uint32 tx = pos + x;
if(tx >= end) // Covers negative and overflowing the right side.
continue;
sprite_line_buf[tx] = pi | raw_pixel | prio_or;
}
}
} // End non-sprite-hit loop
}
if(enabled)
{
for(unsigned int x = start; x < end; x++)
{
if(sprite_line_buf[x] & 0x0F)
{
if(!(target[x] & 0x0F) || (sprite_line_buf[x] & 0x200))
target[x] = sprite_line_buf[x] & 0x1FF;
}
}
}
active_sprites = 0;
}
/*
Caution: If we ever add something to Write() or Read() that will affect the timing of the next event, make sure
to set the passed-by-reference next_event BEFORE calling this function, or otherwise re-engineer this convoluted setup.
*/
void VDC::DoWaitStates(void)
{
//bool did_wait = VDC_IS_BSY;
while(VDC_IS_BSY)
{
//int32 to_wait = CalcNextEvent();
//if(!WSHook || !WSHook(to_wait))
if(!WSHook || !WSHook(-1)) // Event-counter-based wait-stating
{
if(DMARunning)
{
VDC_WARNING("VRAM DMA completion forced.");
RunDMA(0, TRUE);
}
if(sat_dma_counter > 0)
{
VDC_WARNING("SAT DMA completion forced.");
RunSATDMA(0, TRUE);
}
if(mystery_phase)
{
bool backup_mystery_phase = mystery_phase;
mystery_phase = false;
CheckAndCommitPending();
mystery_phase = backup_mystery_phase;
}
break;
}
}
//if(did_wait)
// printf("End of wait stating: %d %d\n", VDMA_CycleCounter, sat_dma_counter);
assert(!pending_read);
assert(!pending_write);
}
uint8 VDC::Read(uint32 A, int32 &next_event, bool peek)
{
uint8 ret = 0;
int msb = A & 1;
A &= 0x3;
switch(A)
{
case 0x0: ret = status | (VDC_IS_BSY ? 0x40 : 0x00);
if(!peek)
{
status &= ~0x3F;
IRQHook(FALSE);
}
break;
case 0x2:
case 0x3:
if(!peek)
{
// Should we only wait on MSB reads...
DoWaitStates();
}
ret = VDC_REGGETP(read_buffer, msb);
if(select == 0x2) // VRR - VRAM Read Register
{
if(msb)
{
if(!peek)
{
pending_read = TRUE;
pending_read_addr = MARR;
MARR += vram_inc_tab[(CR >> 11) & 0x3];
CheckAndCommitPending();
}
}
}
break;
}
return(ret);
}
uint16 VDC::Read16(bool A, bool peek)
{
uint16 ret = 0;
if(!A)
{
ret = status | (VDC_IS_BSY ? 0x40 : 0x00);
if(!peek)
{
status &= ~0x3F;
IRQHook(FALSE);
}
}
else
{
if(!peek)
DoWaitStates();
ret = read_buffer;
if(select == 0x2) // VRR - VRAM Read Register
{
if(!peek)
{
pending_read = TRUE;
pending_read_addr = MARR;
MARR += vram_inc_tab[(CR >> 11) & 0x3];
CheckAndCommitPending();
}
}
}
return(ret);
}
void VDC::CheckAndCommitPending(void)
{
if(sat_dma_counter <= 0 && !DMARunning /* && sprite_cg_fetch_counter <= 0*/ && !mystery_phase)
{
if(pending_write)
{
if(pending_write_addr < VRAM_Size)
{
VRAM[pending_write_addr] = pending_write_latch;
FixTileCache(pending_write_addr);
}
//else
// VDC_UNDEFINED("Unmapped VRAM write");
pending_write = FALSE;
}
if(pending_read)
{
if(pending_read_addr >= VRAM_Size)
VDC_UNDEFINED("Unmapped VRAM VRR read");
read_buffer = VRAM[pending_read_addr];
pending_read = FALSE;
}
}
}
void VDC::Write(uint32 A, uint8 V, int32 &next_event)
{
int msb = A & 1;
A &= 0x3;
//if((A == 0x2 || A == 0x3) && (select >= 0xF && select <= 0x12))
//if((A == 2 || A == 3) && select != 2)
// printf("VDC Write(RCRCount=%d): A=%02x, Select=%02x, V=%02x\n", RCRCount, A, select, V);
switch(A)
{
case 0x0: select = V & 0x1F;
break;
case 0x2:
case 0x3:
//if((select & 0x1F) >= 0x9 && (select & 0x1F) <= 0x1F)
// VDC_DEBUG("%02x %d, %02x", select & 0x1F, msb, V);
switch(select & 0x1F)
{
case 0x00: VDC_REGSETP(MAWR, V, msb);
break;
case 0x01: VDC_REGSETP(MARR, V, msb);
if(msb)
{
DoWaitStates();
pending_read = TRUE;
pending_read_addr = MARR;
MARR += vram_inc_tab[(CR >> 11) & 0x3];
CheckAndCommitPending();
}
break;
case 0x02: if(!msb)
{
write_latch = V;
}
else
{
// We must call CommitPendingWrite at the end of SAT/VRAM DMA for this to work!
DoWaitStates();
pending_write = TRUE;
pending_write_addr = MAWR;
pending_write_latch = write_latch | (V << 8);
MAWR += vram_inc_tab[(CR >> 11) & 0x3];
CheckAndCommitPending();
}
break;
case 0x05: VDC_REGSETP(CR, V, msb);
//printf("CR: %04x, %d\n", CR, msb);
break;
case 0x06: VDC_REGSETP(RCR, V, msb);
RCR &= 0x3FF;
break;
case 0x07: VDC_REGSETP(BXR, V, msb);
BXR &= 0x3FF;
//VDC_DEBUG("BXR Set");
break;
case 0x08: VDC_REGSETP(BYR, V, msb);
BYR &= 0x1FF;
BG_YMoo = BYR; // Set it on LSB and MSB writes(only changing on MSB breaks Youkai Douchuuki)
//VDC_DEBUG("BYR Set");
break;
case 0x09: VDC_REGSETP(MWR, V, msb); break;
case 0x0a: VDC_REGSETP(HSR, V, msb); break;
case 0x0b: VDC_REGSETP(HDR, V, msb); break;
case 0x0c: VDC_REGSETP(VSR, V, msb); break;
case 0x0d: VDC_REGSETP(VDR, V, msb); break;
case 0x0e: VDC_REGSETP(VCR, V, msb); break;
case 0x0f: VDC_REGSETP(DCR, V, msb);
if(DMARunning)
{
VDC_UNDEFINED("Set DCR during DMA: %04x\n", DCR);
}
if(DMAPending)
{
VDC_UNDEFINED("Set DCR while DMAPending: %04x\n", DCR);
}
break;
case 0x10: VDC_REGSETP(SOUR, V, msb);
if(DMARunning)
{
VDC_UNDEFINED("Set SOUR during DMA: %04x\n", SOUR);
}
if(DMAPending)
{
VDC_UNDEFINED("Set SOUR while DMAPending: %04x\n", SOUR);
}
break;
case 0x11: VDC_REGSETP(DESR, V, msb);
if(DMARunning)
{
VDC_UNDEFINED("Set DESR during DMA: %04x\n", DESR);
}
if(DMAPending)
{
VDC_UNDEFINED("Set DESR while DMAPending: %04x\n", DESR);
}
break;
case 0x12: VDC_REGSETP(LENR, V, msb);
if(DMARunning)
{
VDC_UNDEFINED("Set LENR during DMA: %04x\n", LENR);
}
if(DMAPending)
{
VDC_UNDEFINED("Set LENR while DMAPending: %04x\n", LENR);
}
if(msb)
{
VDC_DEBUG("DMA: %04x %04x %04x, %02x", SOUR, DESR, LENR, DCR);
DMAPending = 1;
}
break;
case 0x13: VDC_REGSETP(DVSSR, V, msb);
SATBPending = 1;
break;
default: VDC_WARNING("Unknown VDC register write: %04x %02x", select, V);
break;
}
break;
}
}
void VDC::Write16(bool A, uint16 V)
{
if(!A)
select = V & 0x1F;
else
{
switch(select & 0x1F)
{
case 0x00: MAWR = V;
break;
case 0x01: MARR = V;
DoWaitStates();
pending_read = TRUE;
pending_read_addr = MARR;
MARR += vram_inc_tab[(CR >> 11) & 0x3];
CheckAndCommitPending();
break;
case 0x02: // We must call CommitPendingWrite at the end of SAT/VRAM DMA for this to work!
DoWaitStates();
pending_write = TRUE;
pending_write_addr = MAWR;
pending_write_latch = V;
MAWR += vram_inc_tab[(CR >> 11) & 0x3];
CheckAndCommitPending();
break;
case 0x05: CR = V;
break;
case 0x06: RCR = V & 0x3FF;
break;
case 0x07: BXR = V & 0x3FF;
//VDC_DEBUG("BXR Set");
break;
case 0x08: BYR = V & 0x1FF;
BG_YMoo = BYR;
//VDC_DEBUG("BYR Set");
break;
case 0x09: MWR = V; break;
case 0x0a: HSR = V; break;
case 0x0b: HDR = V; break;
case 0x0c: VSR = V; break;
case 0x0d: VDR = V; break;
case 0x0e: VCR = V; break;
case 0x0f: DCR = V;
if(DMARunning)
{
VDC_UNDEFINED("Set DCR during DMA: %04x\n", DCR);
}
if(DMAPending)
{
VDC_UNDEFINED("Set DCR while DMAPending: %04x\n", DCR);
}
break;
case 0x10: SOUR = V;
if(DMARunning)
{
VDC_UNDEFINED("Set SOUR during DMA: %04x\n", SOUR);
}
if(DMAPending)
{
VDC_UNDEFINED("Set SOUR while DMAPending: %04x\n", SOUR);
}
break;
case 0x11: DESR = V;
if(DMARunning)
{
VDC_UNDEFINED("Set DESR during DMA: %04x\n", DESR);
}
if(DMAPending)
{
VDC_UNDEFINED("Set DESR while DMAPending: %04x\n", DESR);
}
break;
case 0x12: LENR = V;
if(DMARunning)
{
VDC_UNDEFINED("Set LENR during DMA: %04x\n", LENR);
}
if(DMAPending)
{
VDC_UNDEFINED("Set LENR while DMAPending: %04x\n", LENR);
}
VDC_DEBUG("DMA: %04x %04x %04x, %02x", SOUR, DESR, LENR, DCR);
DMAPending = 1;
break;
case 0x13: DVSSR = V;
SATBPending = 1;
break;
default: VDC_WARNING("Oops 2: %04x %02x", select, V);
break;
}
}
}
int32 VDC::Reset(void)
{
memset(VRAM, 0, sizeof(VRAM));
memset(SAT, 0, sizeof(SAT));
memset(SpriteList, 0, sizeof(SpriteList));
for(uint32 A = 0; A < 65536; A += 16)
FixTileCache(A);
pending_read = false;
pending_read_addr = 0xFFFF;
read_buffer = 0xFFFF;
write_latch = 0;
pending_write = false;
pending_write_addr = 0xFFFF;
pending_write_latch = 0xFFFF;
status = 0;
HSR = 0;
HDR = 0;
VSR = 0;
VDR = 0;
VCR = 0;
HSW_cache = M_vdc_HSW;
HDS_cache = M_vdc_HDS;
HDW_cache = M_vdc_HDW;
HDE_cache = M_vdc_HDE;
VDS_cache = M_vdc_VDS;
VSW_cache = M_vdc_VSW;
VDW_cache = M_vdc_VDW;
VCR_cache = M_vdc_VCR;
MAWR = 0;
MARR = 0;
CR = CR_cache = 0;
RCR = 0;
BXR = 0;
BYR = 0;
MWR = 0;
MWR_cache = 0;
DCR = 0;
SOUR = 0;
DESR = 0;
LENR = 0;
DVSSR = 0;
VDMA_CycleCounter = 0;
RCRCount = 0;
DMAReadBuffer = 0;
DMAReadWrite = 0;
DMARunning = 0;
DMAPending = 0;
SATBPending = 0;
burst_mode = 0;
BG_XOffset = 0;
BG_YOffset = 0;
BG_YMoo = 0;
sat_dma_counter = 0;
select = 0;
pixel_copy_count = 0;
NeedRCRInc = false;
NeedVBIRQTest = false;
NeedSATDMATest = false;
NeedBGYInc = false;
HPhase = 0;
VPhase = 0;
HPhaseCounter = 1;
VPhaseCounter = 1;
sprite_cg_fetch_counter = 0;
mystery_counter = 0;
mystery_phase = false;
pixel_desu = 0;
pixel_copy_count = 0;
active_sprites = 0;
return(CalcNextEvent());
}
VDC::VDC()
{
SetUnlimitedSprites(false);
SetVRAMSize(65536);
userle = ~0;
WSHook = NULL;
IRQHook = NULL;
in_exhsync = false;
in_exvsync = false;
}
void VDC::SetUnlimitedSprites(const bool nospritelimit)
{
unlimited_sprites = nospritelimit;
}
void VDC::SetVRAMSize(const uint32 par_VRAM_Size)
{
//const uint32 old_VRAM_Size;
assert(par_VRAM_Size == round_up_pow2(par_VRAM_Size));
assert(par_VRAM_Size >= 16 && par_VRAM_Size <= 65536);
VRAM_Size = par_VRAM_Size;
VRAM_SizeMask = VRAM_Size - 1;
VRAM_BGTileNoMask = VRAM_SizeMask / 16;
// for(uint32 A = std::min<uint32>(old_VRAM_Size, par_VRAM_Size); A < 65536; A += 16)
// FixTileCache(A);
}
VDC::~VDC()
{
}
#ifdef WANT_DEBUGGER
bool VDC::DoGfxDecode(uint32 *target, const uint32 *color_table, const uint32 TransparentColor, bool DecodeSprites,
int32 w, int32 h, int32 scroll)
{
const uint32 *palette_ptr = color_table;
if(DecodeSprites)
{
for(int y = 0; y < h; y++)
{
for(int x = 0; x < w; x += 16)
{
int which_tile = (x / 16) + (scroll + (y / 16)) * (w / 16);
if(which_tile >= VRAM_Size / 64)
{
for(int sx = 0; sx < 16; sx++)
{
target[x + sx] = TransparentColor;
target[x + w * 1 + sx] = 0;
target[x + w * 2 + sx] = 0;
}
continue;
}
uint16 cg[4];
cg[0] = VRAM[which_tile * 64 + (y & 15)];
cg[1] = VRAM[which_tile * 64 + (y & 15) + 16];
cg[2] = VRAM[which_tile * 64 + (y & 15) + 32];
cg[3] = VRAM[which_tile * 64 + (y & 15) + 48];
for(int sx = 0; sx < 16; sx++)
{
int rev_sx = 15 - sx;
target[x + sx] = palette_ptr[(((cg[0] >> rev_sx) & 1) << 0) |
(((cg[1] >> rev_sx) & 1) << 1) | (((cg[2] >> rev_sx) & 1) << 2) | (((cg[3] >> rev_sx) & 1) << 3)];
target[x + w * 1 + sx] = which_tile;
target[x + w * 2 + sx] = which_tile * 64;
}
}
target += w * 3;
}
}
else for(int y = 0; y < h; y++)
{
for(int x = 0; x < w; x+=8)
{
int which_tile = (x / 8) + (scroll + (y / 8)) * (w / 8);
if(which_tile >= (VRAM_Size / 16))
{
for(int sx = 0; sx < 8; sx++)
{
target[x + sx] = TransparentColor;
target[x + w * 1 + sx] = 0;
target[x + w * 2 + sx] = 0;
}
continue;
}
target[x + 0] = palette_ptr[ bg_tile_cache[which_tile][y & 0x7][0]];
target[x + 1] = palette_ptr[ bg_tile_cache[which_tile][y & 0x7][1]];
target[x + 2] = palette_ptr[ bg_tile_cache[which_tile][y & 0x7][2]];
target[x + 3] = palette_ptr[ bg_tile_cache[which_tile][y & 0x7][3]];
target[x + 4] = palette_ptr[ bg_tile_cache[which_tile][y & 0x7][4]];
target[x + 5] = palette_ptr[ bg_tile_cache[which_tile][y & 0x7][5]];
target[x + 6] = palette_ptr[ bg_tile_cache[which_tile][y & 0x7][6]];
target[x + 7] = palette_ptr[ bg_tile_cache[which_tile][y & 0x7][7]];
target[x + w*1 + 0]=target[x + w*1 + 1]=target[x + w*1 + 2]=target[x + w*1 + 3] =
target[x + w*1 + 4]=target[x + w*1 + 5]=target[x + w*1 + 6]=target[x + w*1 + 7] = which_tile;
target[x + w*2 + 0]=target[x + w*2 + 1]=target[x + w*2 + 2]=target[x + w*2 + 3] =
target[x + w*2 + 4]=target[x + w*2 + 5]=target[x + w*2 + 6]=target[x + w*2 + 7] = which_tile * 16;
}
target += w * 3;
}
return(1);
}
#endif