pureikyubu/SRC/Hardware/VI.cpp
2020-07-23 01:54:58 +03:00

413 lines
13 KiB
C++

// VI - video interface (TV stuff).
#include "pch.h"
/* ---------------------------------------------------------------------------
Timing
------
small note for VI frame timing :
---------------------------------------------------
| | PAL | NTSC, MPAL, EURGB60 |
| value +---------------------|---------------------|
| | NIN | INT | NIN | INT |
|=======+==========+==========+==========+==========|
| hz | 50 | 25 | 60 | 30 |
|-------+----------+----------+----------+----------|
| lines | 312.5 | 625 | 262.5 | 525 |
|-------+----------+----------+----------+----------|
| active| 262 | 574 | 218 | 480 |
-------+----------+----------+----------+----------
NIN - non-intelaced (double-strike) mode (both odd and even lines in one frame)
INT - interlaced mode (odd and even lines in alternating frames)
25/50 hz refer to the frequency of which a full videoframe (==all lines of framebuffer) is displayed
progressive = double-strike = non-interlaced;
"field" is the tv-frame as in "odd- and even- field" makes one frame
Registers
---------
VI registers block takes 0x76 bytes (from reversing of VI library) :
static struct
{
uint16_t regs[59]; // regs are copied to shdwRegs
uint16_t shdwRegs[59]; // shdwRegs are copied to hardware registers
VIHorVer HorVer; // used for temporary calculations
} vi;
useful VI regs : VI_DISP_CR, VI_TFBL, VI_DISP_POS, VI_INT0.
dont care about others. Nintendo promised to add HCOUNT (line) interrupt,
but until it is not used by IPL, we will not use it too. there is not
much time before GC farewell (year 2006), so if N will not update
production boards, this will never happen.
--------------------------------------------------------------------------- */
using namespace Debug;
// VI state (registers and other data)
VIControl vi;
// ---------------------------------------------------------------------------
// drawing of XFB
// YUV to RGB conversion
#define yuv2rs(y, u, v) ( (uint32_t)bound((76283*(y - 16) + 104595*(v - 128))>>16) )
#define yuv2gs(y, u, v) ( (uint32_t)bound((76283*(y - 16) - 53281 *(v - 128) - 25624*(u - 128))>>16) << 8 )
#define yuv2bs(y, u, v) ( (uint32_t)bound((76283*(y - 16) + 132252*(u - 128))>>16) << 16 )
// clamping routine
static inline int bound(int x)
{
if(x < 0) x = 0;
if(x > 255) x = 255;
return x;
}
// copy XFB to screen
void YUVBlit(uint8_t *yuvbuf, RGB *dib)
{
uint32_t *rgbbuf = (uint32_t *)dib;
int count = 320 * 480;
if(!yuvbuf || !rgbbuf) return;
// simple blitting, without effects
while(count--)
{
int y1 = *yuvbuf++,
v = *yuvbuf++,
y2 = *yuvbuf++,
u = *yuvbuf++;
*rgbbuf++ = yuv2bs(y1, u, v) | yuv2gs(y1, u, v) | yuv2rs(y1, u, v);
*rgbbuf++ = yuv2bs(y2, u, v) | yuv2gs(y2, u, v) | yuv2rs(y2, u, v);
}
VideoOutRefresh();
}
// ---------------------------------------------------------------------------
// frame timing
// reset VI timing
static void vi_set_timing()
{
uint16_t reg = vi.disp_cr;
vi.inter = (reg & VI_CR_NIN) ? 0 : 1;
vi.mode = VI_CR_FMT(reg);
if(vi.mode == 2) vi.mode = VI_NTSC_LIKE; // MPAL same as NTSC
vi.vtime = Gekko::Gekko->GetTicks();
switch(vi.mode)
{
case VI_NTSC_LIKE:
vi.one_frame = vi.one_second / 30;
vi.vcount = (vi.inter) ? VI_NTSC_INTER : VI_NTSC_NON_INTER;
break;
case VI_PAL_LIKE:
vi.one_frame = vi.one_second / 25;
vi.vcount = (vi.inter) ? VI_PAL_INTER : VI_PAL_NON_INTER;
break;
}
}
// step line counter(s), update GUI and poll controller
void VIUpdate()
{
if((Gekko::Gekko->GetTicks() - vi.vtime) >= (vi.one_frame / vi.vcount))
{
vi.vtime = Gekko::Gekko->GetTicks();
uint32_t currentBeamPos = VI_POS_VCT(vi.pos);
uint32_t triggerBeamPos = VI_INT_VCT(vi.int0);
// generate VIINT ?
currentBeamPos++;
if(currentBeamPos == triggerBeamPos)
{
vi.int0 |= VI_INT_INT;
if(vi.int0 & VI_INT_ENB)
{
PIAssertInt(PI_INTERRUPT_VI);
}
}
// vertical counter
if (currentBeamPos >= vi.vcount)
{
currentBeamPos = 1;
// draw XFB
if (vi.xfb)
{
YUVBlit(vi.xfbbuf, vi.gfxbuf);
vi.frames++;
}
}
vi.pos &= ~0x07ff0000;
vi.pos |= (currentBeamPos & 0x7ff) << 16;
}
}
// ---------------------------------------------------------------------------
// accessing VI registers.
static void vi_read8(uint32_t addr, uint32_t *reg)
{
// TODO
Report(Channel::VI, "VI READ8");
*reg = 0;
}
static void vi_write8(uint32_t addr, uint32_t data)
{
Report(Channel::VI, "VI WRITE8");
}
static void vi_read16(uint32_t addr, uint32_t *reg)
{
switch(addr & 0x7f)
{
case 0x02: // display control
*reg = vi.disp_cr & ~1;
*reg |= vi.videoEncoderFuse & 1;
return;
case 0x1C: // video buffer hi (TOP)
*reg = vi.tfbl >> 16;
return;
case 0x1E: // video buffer low (TOP)
*reg = (uint16_t)vi.tfbl;
return;
case 0x24: // video buffer hi (BOTTOM)
*reg = vi.bfbl >> 16;
return;
case 0x26: // video buffer low (BOTTOM)
*reg = (uint16_t)vi.bfbl;
return;
case 0x2C: // beam position hi
*reg = vi.pos >> 16;
return;
case 0x2E: // beam position low
*reg = (uint16_t)vi.pos;
return;
case 0x30: // int0 control hi
*reg = vi.int0 >> 16;
return;
case 0x32: // int0 control low
*reg = (uint16_t)vi.int0;
return;
}
*reg = 0;
}
static void vi_write16(uint32_t addr, uint32_t data)
{
switch(addr & 0x7f)
{
case 0x02: // display control
vi.disp_cr = (uint16_t)data;
vi_set_timing();
return;
case 0x1C: // video buffer hi (TOP)
vi.tfbl &= 0x0000ffff;
vi.tfbl |= data << 16;
if (vi.log)
{
Report(Channel::VI, "TFBL set to %08X (xof=%i)\n", vi.tfbl, (vi.tfbl >> 24) & 0xf);
}
vi.tfbl &= 0xffffff;
if(vi.tfbl >= mi.ramSize) vi.xfbbuf = NULL;
else vi.xfbbuf = &mi.ram[vi.tfbl & RAMMASK];
return;
case 0x1E: // video buffer low (TOP)
vi.tfbl &= 0xffff0000;
vi.tfbl |= (uint16_t)data;
if (vi.log)
{
Report(Channel::VI, "TFBL set to %08X (xof=%i)\n", vi.tfbl, (vi.tfbl >> 24) & 0xf);
}
vi.tfbl &= 0xffffff;
if(vi.tfbl >= mi.ramSize) vi.xfbbuf = NULL;
else vi.xfbbuf = &mi.ram[vi.tfbl & RAMMASK];
return;
case 0x24: // video buffer hi (BOTTOM)
vi.bfbl &= 0x0000ffff;
vi.bfbl |= data << 16;
vi.bfbl &= 0xffffff;
if (vi.log)
{
Report(Channel::VI, "BFBL set to %08X\n", vi.bfbl);
}
//if(vi.bfbl >= RAMSIZE) vi.xfbbuf = NULL;
//else vi.xfbbuf = &RAM[vi.bfbl];
return;
case 0x26: // video buffer low (BOTTOM)
vi.bfbl &= 0xffff0000;
vi.bfbl |= (uint16_t)data;
vi.bfbl &= 0xffffff;
if (vi.log)
{
Report(Channel::VI, "BFBL set to %08X\n", vi.bfbl);
}
//if(vi.bfbl >= RAMSIZE) vi.xfbbuf = NULL;
//else vi.xfbbuf = &RAM[vi.bfbl];
return;
case 0x2C: // beam position hi
vi.pos &= 0x0000ffff;
vi.pos |= data << 16;
return;
case 0x2E: // beam position low
vi.pos &= 0xffff0000;
vi.pos |= (uint16_t)data;
return;
case 0x30: // int0 control hi
vi.int0 &= 0x0000ffff;
vi.int0 |= data << 16;
if((vi.int0 & VI_INT_INT) == 0)
{
PIClearInt(PI_INTERRUPT_VI);
}
return;
case 0x32: // int0 control low
vi.int0 &= 0xffff0000;
vi.int0 |= (uint16_t)data;
return;
}
}
static void vi_read32(uint32_t addr, uint32_t *reg)
{
switch(addr & 0x7f)
{
case 0x00: // display control
*reg = (uint32_t)(vi.disp_cr & ~1);
*reg |= vi.videoEncoderFuse & 1;
return;
case 0x1C: // video buffer (TOP)
*reg = vi.tfbl;
return;
case 0x24: // video buffer (BOTTOM)
*reg = vi.bfbl;
return;
case 0x2C: // beam position
*reg = vi.pos;
return;
case 0x30: // int0 control
*reg = vi.int0;
return;
}
*reg = 0;
}
static void vi_write32(uint32_t addr, uint32_t data)
{
switch(addr & 0x7f)
{
case 0x00: // display control
vi.disp_cr = (uint16_t)data;
vi_set_timing();
return;
case 0x1C: // video buffer (TOP)
vi.tfbl = data & 0xffffff;
if (vi.log)
{
Report(Channel::VI, "TFBL set to %08X (xof=%i)\n", vi.tfbl, (data >> 24) & 0xf);
}
if(vi.tfbl >= mi.ramSize) vi.xfbbuf = NULL;
else vi.xfbbuf = &mi.ram[vi.tfbl & RAMMASK];
return;
case 0x24: // video buffer (BOTTOM)
vi.bfbl = data & 0xffffff;
if (vi.log)
{
Report(Channel::VI, "BFBL set to %08X\n", vi.bfbl);
}
//if(vi.bfbl >= RAMSIZE) vi.xfbbuf = NULL;
//else vi.xfbbuf = &RAM[vi.bfbl];
return;
case 0x2C: // beam position
vi.pos = data;
return;
case 0x30: // int0 control
vi.int0 = data;
if((vi.int0 & VI_INT_INT) == 0)
{
PIClearInt(PI_INTERRUPT_VI);
}
return;
}
}
// show VI info
void VIStats()
{
uint32_t currentBeamPos = VI_POS_VCT(vi.pos);
uint32_t triggerBeamPos = VI_INT_VCT(vi.int0);
Report(Channel::Norm, " VI interrupt : [%i x x x]\n", vi.int0 >> 31);
Report(Channel::Norm, " VI int mask : [%i x x x]\n", (vi.int0 >> 28) & 1);
Report(Channel::Norm, " VI int pos : %i == %i, x == x, x == x, x == x (line)\n", currentBeamPos, triggerBeamPos);
Report(Channel::Norm, " VI XFB : T%08X B%08X (phys), enabled: %i\n", vi.tfbl, vi.bfbl, vi.xfb);
}
// ---------------------------------------------------------------------------
// init
void VIOpen(HWConfig * config)
{
Report(Channel::VI, "Video-out hardware interface\n");
// clear VI regs
memset(&vi, 0, sizeof(VIControl));
vi.one_second = Gekko::Gekko->OneSecond();
// read VI settings
vi.log = config->vi_log;
vi.xfb = config->vi_xfb;
vi.videoEncoderFuse = config->videoEncoderFuse;
// reset VI timing
vi_set_timing();
// XFB is not yet specified
vi.gfxbuf = NULL;
vi.xfbbuf = NULL;
// open GDI (if need)
if(vi.xfb)
{
bool res = VideoOutOpen(config, 640, 480, &vi.gfxbuf);
if(!res)
{
Report(Channel::VI, "VI cant startup VideoOut backend!");
vi.xfb = false;
}
}
// set traps to VI registers
for(uint32_t ofs=0; ofs<0x80; ofs++)
{
MISetTrap(8, 0x0C002000 + ofs, vi_read8, vi_write8);
if((ofs % 2) == 0) MISetTrap(16, 0x0C002000 + ofs, vi_read16, vi_write16);
if((ofs % 4) == 0) MISetTrap(32, 0x0C002000 + ofs, vi_read32, vi_write32);
}
}
void VIClose()
{
// XFB can be enabled during emulation,
// so we must be sure, that GDI is closed
// even if XFB wasn't enabled, before start
VideoOutClose();
}
void VISetEncoderFuse(int value)
{
vi.videoEncoderFuse = value;
}