Files
archived-pcsx2/pcsx2/IPU/IPU.cpp
2025-01-20 05:07:26 +01:00

534 lines
12 KiB
C++

// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "Common.h"
#include "IPU.h"
#include "IPU_MultiISA.h"
#include "IPUdma.h"
#include <limits.h>
#include "Config.h"
// the BP doesn't advance and returns -1 if there is no data to be read
alignas(16) tIPU_cmd ipu_cmd;
alignas(16) tIPU_BP g_BP;
alignas(16) decoder_t decoder;
IPUStatus IPUCoreStatus;
static void (*IPUWorker)();
// Color conversion stuff, the memory layout is a total hack
// convert_data_buffer is a pointer to the internal rgb struct (the first param in convert_init_t)
//char convert_data_buffer[sizeof(convert_rgb_t)];
//char convert_data_buffer[0x1C]; // unused?
//u8 PCT[] = {'r', 'I', 'P', 'B', 'D', '-', '-', '-'}; // unused?
// Quantization matrix
rgb16_t g_ipu_vqclut[16]; //clut conversion table
u16 g_ipu_thresh[2]; //thresholds for color conversions
int coded_block_pattern = 0;
alignas(16) u8 g_ipu_indx4[16*16/2];
alignas(16) const int non_linear_quantizer_scale[32] =
{
0, 1, 2, 3, 4, 5, 6, 7,
8, 10, 12, 14, 16, 18, 20, 22,
24, 28, 32, 36, 40, 44, 48, 52,
56, 64, 72, 80, 88, 96, 104, 112
};
uint eecount_on_last_vdec = 0;
bool FMVstarted = false;
bool EnableFMV = false;
// Also defined in IPU_MultiISA.cpp, but IPU.cpp is not unshared.
// whenever reading fractions of bytes. The low bits always come from the next byte
// while the high bits come from the current byte
__ri static u8 getBits32(u8* address, bool advance)
{
if (!g_BP.FillBuffer(32))
return 0;
const u8* readpos = &g_BP.internal_qwc->_u8[g_BP.BP / 8];
if (uint shift = (g_BP.BP & 7))
{
u32 mask = (0xff >> shift);
mask = mask | (mask << 8) | (mask << 16) | (mask << 24);
*(u32*)address = ((~mask & *(u32*)(readpos + 1)) >> (8 - shift)) | (((mask) & *(u32*)readpos) << shift);
}
else
{
// Bit position-aligned -- no masking/shifting necessary
*(u32*)address = *(u32*)readpos;
}
if (advance)
g_BP.Advance(32);
return 1;
}
void tIPU_cmd::clear()
{
std::memset(this, 0, sizeof(*this));
current = 0xffffffff;
}
__fi void IPUProcessInterrupt()
{
if (ipuRegs.ctrl.BUSY)
IPUWorker();
}
/////////////////////////////////////////////////////////
// Register accesses (run on EE thread)
void ipuReset()
{
IPUWorker = MULTI_ISA_SELECT(IPUWorker);
std::memset(&ipuRegs, 0, sizeof(ipuRegs));
std::memset(&g_BP, 0, sizeof(g_BP));
std::memset(&decoder, 0, sizeof(decoder));
IPUCoreStatus.DataRequested = false;
IPUCoreStatus.WaitingOnIPUFrom= false;
IPUCoreStatus.WaitingOnIPUTo = false;
decoder.picture_structure = FRAME_PICTURE; //default: progressive...my guess:P
ipu_fifo.init();
ipu_cmd.clear();
ipuDmaReset();
}
void ReportIPU()
{
//Console.WriteLn(g_nDMATransfer.desc());
Console.WriteLn(ipu_fifo.in.desc());
Console.WriteLn(ipu_fifo.out.desc());
Console.WriteLn(g_BP.desc());
Console.WriteLn("vqclut = 0x%x.", g_ipu_vqclut);
Console.WriteLn("thresh = 0x%x.", g_ipu_thresh);
Console.WriteLn("coded_block_pattern = 0x%x.", coded_block_pattern);
Console.WriteLn("g_decoder = 0x%x.", &decoder);
Console.WriteLn(ipu_cmd.desc());
Console.WriteLn();
}
bool SaveStateBase::ipuFreeze()
{
// Get a report of the status of the ipu variables when saving and loading savestates.
//ReportIPU();
if (!FreezeTag("IPU"))
return false;
Freeze(ipu_fifo);
Freeze(g_BP);
Freeze(g_ipu_vqclut);
Freeze(g_ipu_thresh);
Freeze(coded_block_pattern);
Freeze(decoder);
Freeze(ipu_cmd);
Freeze(IPUCoreStatus);
return IsOkay();
}
void tIPU_CMD_IDEC::log() const
{
IPU_LOG("IDEC command.");
if (FB) IPU_LOG(" Skip %d bits.", FB);
IPU_LOG(" Quantizer step code=0x%X.", QSC);
if (DTD == 0)
IPU_LOG(" Does not decode DT.");
else
IPU_LOG(" Decodes DT.");
if (SGN == 0)
IPU_LOG(" No bias.");
else
IPU_LOG(" Bias=128.");
if (DTE == 1) IPU_LOG(" Dither Enabled.");
if (OFM == 0)
IPU_LOG(" Output format is RGB32.");
else
IPU_LOG(" Output format is RGB16.");
IPU_LOG("");
}
void tIPU_CMD_BDEC::log(int s_bdec) const
{
IPU_LOG("BDEC(macroblock decode) command %x, num: 0x%x", cpuRegs.pc, s_bdec);
if (FB) IPU_LOG(" Skip 0x%X bits.", FB);
if (MBI)
IPU_LOG(" Intra MB.");
else
IPU_LOG(" Non-intra MB.");
if (DCR)
IPU_LOG(" Resets DC prediction value.");
else
IPU_LOG(" Doesn't reset DC prediction value.");
if (DT)
IPU_LOG(" Use field DCT.");
else
IPU_LOG(" Use frame DCT.");
IPU_LOG(" Quantizer step=0x%X", QSC);
}
void tIPU_CMD_CSC::log_from_YCbCr() const
{
IPU_LOG("CSC(Colorspace conversion from YCbCr) command (%d).", MBC);
if (OFM)
IPU_LOG("Output format is RGB16. ");
else
IPU_LOG("Output format is RGB32. ");
if (DTE) IPU_LOG("Dithering enabled.");
}
void tIPU_CMD_CSC::log_from_RGB32() const
{
IPU_LOG("PACK (Colorspace conversion from RGB32) command.");
if (OFM)
IPU_LOG("Output format is RGB16. ");
else
IPU_LOG("Output format is INDX4. ");
if (DTE) IPU_LOG("Dithering enabled.");
IPU_LOG("Number of macroblocks to be converted: %d", MBC);
}
__fi u32 ipuRead32(u32 mem)
{
// Note: It's assumed that mem's input value is always in the 0x10002000 page
// of memory (if not, it's probably bad code).
pxAssert((mem & ~0xff) == 0x10002000);
mem &= 0xff; // ipu repeats every 0x100
switch (mem)
{
ipucase(IPU_CMD) : // IPU_CMD
{
if (ipu_cmd.CMD != SCE_IPU_FDEC && ipu_cmd.CMD != SCE_IPU_VDEC)
{
if (getBits32((u8*)&ipuRegs.cmd.DATA, 0))
ipuRegs.cmd.DATA = BigEndian(ipuRegs.cmd.DATA);
}
return ipuRegs.cmd.DATA;
}
ipucase(IPU_CTRL): // IPU_CTRL
{
ipuRegs.ctrl.IFC = g_BP.IFC;
ipuRegs.ctrl.CBP = coded_block_pattern;
if (!ipuRegs.ctrl.BUSY)
IPU_LOG("read32: IPU_CTRL=0x%08X", ipuRegs.ctrl._u32);
return ipuRegs.ctrl._u32;
}
ipucase(IPU_BP): // IPU_BP
{
pxAssume(g_BP.FP <= 2);
ipuRegs.ipubp = g_BP.BP & 0x7f;
ipuRegs.ipubp |= g_BP.IFC << 8;
ipuRegs.ipubp |= g_BP.FP << 16;
IPU_LOG("read32: IPU_BP=0x%08X", ipuRegs.ipubp);
return ipuRegs.ipubp;
}
default:
IPU_LOG("read32: Addr=0x%08X Value = 0x%08X", mem, psHu32(IPU_CMD + mem));
}
return psHu32(IPU_CMD + mem);
}
__fi u64 ipuRead64(u32 mem)
{
// Note: It's assumed that mem's input value is always in the 0x10002000 page
// of memory (if not, it's probably bad code).
pxAssert((mem & ~0xff) == 0x10002000);
mem &= 0xff; // ipu repeats every 0x100
switch (mem)
{
ipucase(IPU_CMD): // IPU_CMD
{
if (ipu_cmd.CMD != SCE_IPU_FDEC && ipu_cmd.CMD != SCE_IPU_VDEC)
{
if (getBits32((u8*)&ipuRegs.cmd.DATA, 0))
ipuRegs.cmd.DATA = BigEndian(ipuRegs.cmd.DATA);
}
if (ipuRegs.cmd.DATA & 0xffffff)
IPU_LOG("read64: IPU_CMD=BUSY=%x, DATA=%08X", ipuRegs.cmd.BUSY ? 1 : 0, ipuRegs.cmd.DATA);
return ipuRegs.cmd._u64;
}
ipucase(IPU_CTRL):
IPU_LOG("reading 64bit IPU ctrl");
break;
ipucase(IPU_BP):
IPU_LOG("reading 64bit IPU top");
break;
ipucase(IPU_TOP): // IPU_TOP
IPU_LOG("read64: IPU_TOP=%x, bp = %d", ipuRegs.top, g_BP.BP);
break;
default:
IPU_LOG("read64: Unknown=%x", mem);
break;
}
return psHu64(IPU_CMD + mem);
}
void ipuSoftReset()
{
ipu_fifo.clear();
std::memset(&g_BP, 0, sizeof(g_BP));
coded_block_pattern = 0;
g_ipu_thresh[0] = 0;
g_ipu_thresh[1] = 0;
ipuRegs.ctrl.reset();
ipuRegs.top = 0;
ipu_cmd.clear();
ipuRegs.cmd.BUSY = 0;
ipuRegs.cmd.DATA = 0; // required for Enthusia - Professional Racing after fix, or will freeze at start of next video.
hwIntcIrq(INTC_IPU); // required for FightBox
}
__fi bool ipuWrite32(u32 mem, u32 value)
{
// Note: It's assumed that mem's input value is always in the 0x10002000 page
// of memory (if not, it's probably bad code).
pxAssert((mem & ~0xfff) == 0x10002000);
mem &= 0xfff;
switch (mem)
{
ipucase(IPU_CMD): // IPU_CMD
IPU_LOG("write32: IPU_CMD=0x%08X", value);
IPUCMD_WRITE(value);
return false;
ipucase(IPU_CTRL): // IPU_CTRL
// CTRL = the first 16 bits of ctrl [0x8000ffff], + value for the next 16 bits,
// minus the reserved bits. (18-19; 27-29) [0x47f30000]
ipuRegs.ctrl.write(value);
if (ipuRegs.ctrl.IDP == 3)
{
Console.WriteLn("IPU Invalid Intra DC Precision, switching to 9 bits");
ipuRegs.ctrl.IDP = 1;
}
if (ipuRegs.ctrl.RST) ipuSoftReset(); // RESET
IPU_LOG("write32: IPU_CTRL=0x%08X", value);
return false;
}
return true;
}
// returns FALSE when the writeback is handled, TRUE if the caller should do the
// writeback itself.
__fi bool ipuWrite64(u32 mem, u64 value)
{
// Note: It's assumed that mem's input value is always in the 0x10002000 page
// of memory (if not, it's probably bad code).
pxAssert((mem & ~0xfff) == 0x10002000);
mem &= 0xfff;
switch (mem)
{
ipucase(IPU_CMD):
IPU_LOG("write64: IPU_CMD=0x%08X", value);
IPUCMD_WRITE((u32)value);
return false;
}
return true;
}
//////////////////////////////////////////////////////
// IPU Commands (exec on worker thread only)
static void ipuBCLR(u32 val)
{
ipu_fifo.in.clear();
std::memset(&g_BP, 0, sizeof(g_BP));
g_BP.BP = val & 0x7F;
ipuRegs.cmd.BUSY = 0;
IPU_LOG("Clear IPU input FIFO. Set Bit offset=0x%X", g_BP.BP);
}
static __ri void ipuIDEC(tIPU_CMD_IDEC idec)
{
idec.log();
//from IPU_CTRL
ipuRegs.ctrl.PCT = I_TYPE; //Intra DECoding;)
decoder.coding_type = ipuRegs.ctrl.PCT;
decoder.mpeg1 = ipuRegs.ctrl.MP1;
decoder.q_scale_type = ipuRegs.ctrl.QST;
decoder.intra_vlc_format = ipuRegs.ctrl.IVF;
decoder.scantype = ipuRegs.ctrl.AS;
decoder.intra_dc_precision = ipuRegs.ctrl.IDP;
//from IDEC value
decoder.quantizer_scale = idec.QSC;
decoder.frame_pred_frame_dct= !idec.DTD;
decoder.sgn = idec.SGN;
decoder.dte = idec.DTE;
decoder.ofm = idec.OFM;
//other stuff
decoder.dcr = 1; // resets DC prediction value
}
static int s_bdec = 0;
static __ri void ipuBDEC(tIPU_CMD_BDEC bdec)
{
bdec.log(s_bdec);
if (IsDebugBuild) s_bdec++;
decoder.coding_type = I_TYPE;
decoder.mpeg1 = ipuRegs.ctrl.MP1;
decoder.q_scale_type = ipuRegs.ctrl.QST;
decoder.intra_vlc_format = ipuRegs.ctrl.IVF;
decoder.scantype = ipuRegs.ctrl.AS;
decoder.intra_dc_precision = ipuRegs.ctrl.IDP;
//from BDEC value
decoder.quantizer_scale = decoder.q_scale_type ? non_linear_quantizer_scale [bdec.QSC] : bdec.QSC << 1;
decoder.macroblock_modes = bdec.DT ? DCT_TYPE_INTERLACED : 0;
decoder.dcr = bdec.DCR;
decoder.macroblock_modes |= bdec.MBI ? MACROBLOCK_INTRA : MACROBLOCK_PATTERN;
std::memset(&decoder.mb8, 0, sizeof(decoder.mb8));
std::memset(&decoder.mb16, 0, sizeof(decoder.mb16));
}
static void ipuSETTH(u32 val)
{
g_ipu_thresh[0] = (val & 0x1ff);
g_ipu_thresh[1] = ((val >> 16) & 0x1ff);
IPU_LOG("SETTH (Set threshold value)command %x.", val&0x1ff01ff);
}
// --------------------------------------------------------------------------------------
// IPU Worker / Dispatcher
// --------------------------------------------------------------------------------------
// When a command is written, we set some various busy flags and clear some other junk.
// The actual decoding will be handled by IPUworker.
__fi void IPUCMD_WRITE(u32 val)
{
// don't process anything if currently busy
//if (ipuRegs.ctrl.BUSY) Console.WriteLn("IPU BUSY!"); // wait for thread
ipuRegs.ctrl.ECD = 0;
ipuRegs.ctrl.SCD = 0;
ipu_cmd.clear();
ipu_cmd.current = val;
switch (ipu_cmd.CMD)
{
// BCLR and SETTH require no data so they always execute inline:
case SCE_IPU_BCLR:
ipuBCLR(val);
hwIntcIrq(INTC_IPU); //DMAC_TO_IPU
ipuRegs.ctrl.BUSY = 0;
return;
case SCE_IPU_SETTH:
ipuSETTH(val);
hwIntcIrq(INTC_IPU);
ipuRegs.ctrl.BUSY = 0;
return;
case SCE_IPU_IDEC:
g_BP.Advance(val & 0x3F);
ipuIDEC(val);
ipuRegs.SetTopBusy();
break;
case SCE_IPU_BDEC:
g_BP.Advance(val & 0x3F);
ipuBDEC(val);
ipuRegs.SetTopBusy();
break;
case SCE_IPU_VDEC:
g_BP.Advance(val & 0x3F);
ipuRegs.SetDataBusy();
break;
case SCE_IPU_FDEC:
IPU_LOG("FDEC command. Skip 0x%X bits, FIFO 0x%X qwords, BP 0x%X, CHCR 0x%x",
val & 0x3f, g_BP.IFC, g_BP.BP, ipu1ch.chcr._u32);
g_BP.Advance(val & 0x3F);
ipuRegs.SetDataBusy();
break;
case SCE_IPU_SETIQ:
IPU_LOG("SETIQ command.");
g_BP.Advance(val & 0x3F);
break;
case SCE_IPU_SETVQ:
break;
case SCE_IPU_CSC:
break;
case SCE_IPU_PACK:
break;
jNO_DEFAULT;
}
ipuRegs.ctrl.BUSY = 1;
// Have a short delay immitating the time it takes to run IDEC/BDEC, other commands are near instant.
// Mana Khemia/Metal Saga start IDEC then change IPU0 expecting there to be a delay before IDEC sends data.
if (ipu_cmd.CMD == SCE_IPU_IDEC || ipu_cmd.CMD == SCE_IPU_BDEC)
{
IPUCoreStatus.WaitingOnIPUFrom = false;
IPUCoreStatus.WaitingOnIPUTo = false;
IPU_INT_PROCESS(64);
}
else
IPUWorker();
}