mirror of
https://github.com/emu-russia/pureikyubu.git
synced 2025-04-02 10:42:15 -04:00
413 lines
13 KiB
C++
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;
|
|
}
|