mirror of https://github.com/PCSX2/pcsx2.git
904 lines
25 KiB
C++
904 lines
25 KiB
C++
/* PCSX2 - PS2 Emulator for PCs
|
|
* Copyright (C) 2002-2010 PCSX2 Dev Team
|
|
*
|
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* PCSX2 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 PCSX2.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#pragma once
|
|
#include <deque>
|
|
#include "Gif.h"
|
|
#include "Vif.h"
|
|
#include "GS.h"
|
|
#include "GS/GSRegs.h"
|
|
#include "MTGS.h"
|
|
|
|
// FIXME common path ?
|
|
#include "common/boost_spsc_queue.hpp"
|
|
|
|
struct GS_Packet;
|
|
extern void Gif_MTGS_Wait(bool isMTVU);
|
|
extern void Gif_FinishIRQ();
|
|
extern bool Gif_HandlerAD(u8* pMem);
|
|
extern bool Gif_HandlerAD_MTVU(u8* pMem);
|
|
extern bool Gif_HandlerAD_Debug(u8* pMem);
|
|
extern void Gif_AddBlankGSPacket(u32 size, GIF_PATH path);
|
|
extern void Gif_AddGSPacketMTVU(GS_Packet& gsPack, GIF_PATH path);
|
|
extern void Gif_AddCompletedGSPacket(GS_Packet& gsPack, GIF_PATH path);
|
|
extern void Gif_ParsePacket(u8* data, u32 size, GIF_PATH path);
|
|
extern void Gif_ParsePacket(GS_Packet& gsPack, GIF_PATH path);
|
|
|
|
struct Gif_Tag
|
|
{
|
|
struct HW_Gif_Tag
|
|
{
|
|
u16 NLOOP : 15;
|
|
u16 EOP : 1;
|
|
u16 _dummy0 : 16;
|
|
u32 _dummy1 : 14;
|
|
u32 PRE : 1;
|
|
u32 PRIM : 11;
|
|
u32 FLG : 2;
|
|
u32 NREG : 4;
|
|
u32 REGS[2];
|
|
} tag;
|
|
|
|
u32 nLoop; // NLOOP left to process
|
|
u32 nRegs; // NREG (1~16)
|
|
u32 nRegIdx; // Current nReg Index (packed mode processing)
|
|
u32 len; // Packet Length in Bytes (not including tag)
|
|
u32 cycles; // Time needed to process packet data in ee-cycles
|
|
u8 regs[16]; // Regs
|
|
bool hasAD; // Has an A+D Write
|
|
bool isValid; // Tag is valid
|
|
|
|
__ri Gif_Tag() { Reset(); }
|
|
__ri Gif_Tag(u8* pMem, bool analyze = false)
|
|
{
|
|
setTag(pMem, analyze);
|
|
}
|
|
|
|
__ri void Reset() { std::memset(this, 0, sizeof(*this)); }
|
|
__ri u8 curReg() { return regs[nRegIdx & 0xf]; }
|
|
|
|
__ri void packedStep()
|
|
{
|
|
if (nLoop > 0)
|
|
{
|
|
nRegIdx++;
|
|
if (nRegIdx >= nRegs)
|
|
{
|
|
nRegIdx = 0;
|
|
nLoop--;
|
|
}
|
|
}
|
|
}
|
|
|
|
__ri void setTag(u8* pMem, bool analyze = false)
|
|
{
|
|
tag = *(HW_Gif_Tag*)pMem;
|
|
nLoop = tag.NLOOP;
|
|
hasAD = false;
|
|
nRegIdx = 0;
|
|
isValid = 1;
|
|
len = 0; // avoid uninitialized compiler warning
|
|
switch (tag.FLG)
|
|
{
|
|
case GIF_FLG_PACKED:
|
|
nRegs = ((tag.NREG - 1) & 0xf) + 1;
|
|
len = (nRegs * tag.NLOOP) * 16;
|
|
cycles = len << 1; // Packed Mode takes 2 ee-cycles
|
|
if (analyze)
|
|
analyzeTag();
|
|
break;
|
|
case GIF_FLG_REGLIST:
|
|
nRegs = ((tag.NREG - 1) & 0xf) + 1;
|
|
len = ((nRegs * tag.NLOOP + 1) >> 1) * 16;
|
|
cycles = len << 2; // Reg-list Mode takes 4 ee-cycles
|
|
break;
|
|
case GIF_FLG_IMAGE:
|
|
case GIF_FLG_IMAGE2:
|
|
nRegs = 0;
|
|
len = tag.NLOOP * 16;
|
|
cycles = len << 2; // Image Mode takes 4 ee-cycles
|
|
tag.FLG = GIF_FLG_IMAGE;
|
|
break;
|
|
jNO_DEFAULT;
|
|
}
|
|
}
|
|
|
|
__ri void analyzeTag()
|
|
{
|
|
#ifdef _M_X86
|
|
// zero out bits for registers which shouldn't be tested
|
|
__m128i vregs = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(tag.REGS));
|
|
vregs = _mm_and_si128(vregs, _mm_srli_epi64(_mm_set1_epi32(0xFFFFFFFFu), (64 - nRegs * 4)));
|
|
|
|
// get upper nibbles, interleave with lower nibbles, clear upper bits from low nibbles
|
|
vregs = _mm_and_si128(_mm_unpacklo_epi8(vregs, _mm_srli_epi32(vregs, 4)), _mm_set1_epi8(0x0F));
|
|
|
|
// compare with GIF_REG_A_D, set hasAD if any lanes passed
|
|
hasAD = (_mm_movemask_epi8(_mm_cmpeq_epi8(vregs, _mm_set1_epi8(GIF_REG_A_D))) != 0);
|
|
|
|
// write out unpacked registers
|
|
_mm_storeu_si128(reinterpret_cast<__m128i*>(regs), vregs);
|
|
#else
|
|
// Reference C implementation.
|
|
hasAD = false;
|
|
u32 t = tag.REGS[0];
|
|
u32 i = 0;
|
|
u32 j = std::min<u32>(nRegs, 8);
|
|
for (; i < j; i++)
|
|
{
|
|
regs[i] = t & 0xf;
|
|
hasAD |= (regs[i] == GIF_REG_A_D);
|
|
t >>= 4;
|
|
}
|
|
t = tag.REGS[1];
|
|
j = nRegs;
|
|
for (; i < j; i++)
|
|
{
|
|
regs[i] = t & 0xf;
|
|
hasAD |= (regs[i] == GIF_REG_A_D);
|
|
t >>= 4;
|
|
}
|
|
#endif
|
|
}
|
|
};
|
|
|
|
struct GS_Packet
|
|
{
|
|
// PERF note: this struct is copied various time in hot path. Don't add
|
|
// new field
|
|
|
|
u32 offset; // Path buffer offset for start of packet
|
|
u32 size; // Full size of GS-Packet
|
|
s32 cycles; // EE Cycles taken to process this GS packet
|
|
s32 readAmount; // Dummy read-amount data needed for proper buffer calculations
|
|
GS_Packet() { Reset(); }
|
|
void Reset() { std::memset(this, 0, sizeof(*this)); }
|
|
};
|
|
|
|
struct GS_SIGNAL
|
|
{
|
|
u32 data[2];
|
|
bool queued;
|
|
void Reset() { std::memset(this, 0, sizeof(*this)); }
|
|
};
|
|
|
|
struct GS_FINISH
|
|
{
|
|
bool gsFINISHFired;
|
|
|
|
void Reset() { std::memset(this, 0, sizeof(*this)); }
|
|
};
|
|
|
|
static __fi void incTag(u32& offset, u32& size, u32 incAmount)
|
|
{
|
|
size += incAmount;
|
|
offset += incAmount;
|
|
}
|
|
|
|
struct Gif_Path_MTVU
|
|
{
|
|
u32 fakePackets; // Fake packets pending to be sent to MTGS
|
|
GS_Packet fakePacket;
|
|
// Set a size based on MTGS but keep a factor 2 to avoid too waste to much
|
|
// memory overhead. Note the struct is instantied 3 times (for each gif
|
|
// path)
|
|
ringbuffer_base<GS_Packet, MTGS::RingBufferSize / 2> gsPackQueue;
|
|
Gif_Path_MTVU() { Reset(); }
|
|
void Reset()
|
|
{
|
|
fakePackets = 0;
|
|
gsPackQueue.reset();
|
|
fakePacket.Reset();
|
|
fakePacket.size = ~0u; // Used to indicate that its a fake packet
|
|
}
|
|
};
|
|
|
|
struct Gif_Path
|
|
{
|
|
std::atomic<int> readAmount; // Amount of data MTGS still needs to read
|
|
u8* buffer; // Path packet buffer
|
|
u32 buffSize; // Full size of buffer
|
|
u32 buffLimit; // Cut off limit to wrap around
|
|
u32 curSize; // Used buffer in bytes
|
|
u32 curOffset; // Offset of current gifTag
|
|
u32 dmaRewind; // Used by path3 when only part of a DMA chain is used
|
|
Gif_Tag gifTag; // Current GS Primitive tag
|
|
GS_Packet gsPack; // Current GS Packet info
|
|
GIF_PATH idx; // Gif Path Index
|
|
GIF_PATH_STATE state; // Path State
|
|
Gif_Path_MTVU mtvu; // Must be last for saved states
|
|
|
|
Gif_Path() { Reset(); }
|
|
~Gif_Path() { _aligned_free(buffer); }
|
|
|
|
void Init(GIF_PATH _idx, u32 _buffSize, u32 _buffSafeZone)
|
|
{
|
|
idx = _idx;
|
|
buffSize = _buffSize;
|
|
buffLimit = _buffSize - _buffSafeZone;
|
|
buffer = (u8*)_aligned_malloc(buffSize, 16);
|
|
Reset();
|
|
}
|
|
|
|
void Reset(bool softReset = false)
|
|
{
|
|
state = GIF_PATH_IDLE;
|
|
if (softReset)
|
|
{
|
|
if (!isMTVU()) // MTVU Freaks out if you try to reset it, so let's just let it transfer
|
|
{
|
|
GUNIT_WARN("Gif Path %d - Soft Reset", idx + 1);
|
|
gifTag.Reset();
|
|
gsPack.Reset();
|
|
curSize = curOffset;
|
|
gsPack.offset = curOffset;
|
|
}
|
|
return;
|
|
}
|
|
mtvu.Reset();
|
|
curSize = 0;
|
|
curOffset = 0;
|
|
readAmount = 0;
|
|
gifTag.Reset();
|
|
gsPack.Reset();
|
|
}
|
|
|
|
bool isMTVU() const { return !idx && THREAD_VU1; }
|
|
s32 getReadAmount() { return readAmount.load(std::memory_order_acquire) + gsPack.readAmount; }
|
|
bool hasDataRemaining() const { return curOffset < curSize; }
|
|
bool isDone() const { return isMTVU() ? !mtvu.fakePackets : (!hasDataRemaining() && (state == GIF_PATH_IDLE || state == GIF_PATH_WAIT)); }
|
|
|
|
// Waits on the MTGS to process gs packets
|
|
void mtgsReadWait()
|
|
{
|
|
if (IsDevBuild)
|
|
{
|
|
DevCon.WriteLn(Color_Red, "Gif Path[%d] - MTGS Wait! [r=0x%x]", idx + 1, getReadAmount());
|
|
Gif_MTGS_Wait(isMTVU());
|
|
DevCon.WriteLn(Color_Green, "Gif Path[%d] - MTGS Wait! [r=0x%x]", idx + 1, getReadAmount());
|
|
return;
|
|
}
|
|
Gif_MTGS_Wait(isMTVU());
|
|
}
|
|
|
|
// Moves packet data to start of buffer
|
|
void RealignPacket()
|
|
{
|
|
GUNIT_LOG("Path Buffer: Realigning packet!");
|
|
s32 offset = curOffset - gsPack.size;
|
|
s32 sizeToAdd = curSize - offset;
|
|
s32 intersect = sizeToAdd - offset;
|
|
if (intersect < 0)
|
|
intersect = 0;
|
|
for (;;)
|
|
{
|
|
s32 frontFree = offset - getReadAmount();
|
|
if (frontFree >= sizeToAdd - intersect)
|
|
break;
|
|
mtgsReadWait();
|
|
}
|
|
if (offset < (s32)buffLimit)
|
|
{ // Needed for correct readAmount values
|
|
if (isMTVU())
|
|
gsPack.readAmount += buffLimit - offset;
|
|
else
|
|
Gif_AddBlankGSPacket(buffLimit - offset, idx);
|
|
}
|
|
//DevCon.WriteLn("Realign Packet [%d]", curSize - offset);
|
|
if (intersect)
|
|
memmove(buffer, &buffer[offset], curSize - offset);
|
|
else
|
|
memcpy(buffer, &buffer[offset], curSize - offset);
|
|
curSize -= offset;
|
|
curOffset = gsPack.size;
|
|
gsPack.offset = 0;
|
|
}
|
|
|
|
void CopyGSPacketData(u8* pMem, u32 size, bool aligned = false)
|
|
{
|
|
if (curSize + size > buffSize)
|
|
{ // Move gsPack to front of buffer
|
|
GUNIT_LOG("CopyGSPacketData: Realigning packet!");
|
|
RealignPacket();
|
|
}
|
|
for (;;)
|
|
{
|
|
s32 offset = curOffset - gsPack.size;
|
|
s32 readPos = offset - getReadAmount();
|
|
if (readPos >= 0)
|
|
break; // MTGS is reading in back of curOffset
|
|
if ((s32)buffLimit + readPos > (s32)curSize + (s32)size)
|
|
break; // Enough free front space
|
|
mtgsReadWait(); // Let MTGS run to free up buffer space
|
|
}
|
|
pxAssertDev(curSize + size <= buffSize, "Gif Path Buffer Overflow!");
|
|
memcpy(&buffer[curSize], pMem, size);
|
|
curSize += size;
|
|
}
|
|
|
|
// If completed a GS packet (with EOP) then set done to true
|
|
// MTVU: This function only should be called called on EE thread
|
|
GS_Packet ExecuteGSPacket(bool& done)
|
|
{
|
|
if (mtvu.fakePackets)
|
|
{ // For MTVU mode...
|
|
mtvu.fakePackets--;
|
|
done = true;
|
|
return mtvu.fakePacket;
|
|
}
|
|
pxAssert(!isMTVU());
|
|
for (;;)
|
|
{
|
|
if (!gifTag.isValid)
|
|
{ // Need new Gif Tag
|
|
// We don't have enough data for a Gif Tag
|
|
if (curOffset + 16 > curSize)
|
|
{
|
|
//GUNIT_LOG("Path Buffer: Not enough data for gif tag! [%d]", curSize-curOffset);
|
|
GUNIT_WARN("PATH %d not enough data pre tag, available %d wanted %d", gifRegs.stat.APATH, curSize - curOffset, 16);
|
|
return gsPack;
|
|
}
|
|
|
|
// Move packet to start of buffer
|
|
if (curOffset > buffLimit)
|
|
{
|
|
RealignPacket();
|
|
}
|
|
|
|
gifTag.setTag(&buffer[curOffset], 1);
|
|
|
|
state = (GIF_PATH_STATE)(gifTag.tag.FLG + 1);
|
|
GUNIT_WARN("PATH %d New tag State %d FLG %d EOP %d NLOOP %d", gifRegs.stat.APATH, gifRegs.stat.APATH, state, gifTag.tag.FLG, gifTag.tag.EOP, gifTag.tag.NLOOP);
|
|
// We don't have enough data for a complete GS packet
|
|
if (!gifTag.hasAD && curOffset + 16 + gifTag.len > curSize)
|
|
{
|
|
gifTag.isValid = false; // So next time we test again
|
|
GUNIT_WARN("PATH %d not enough data, available %d wanted %d", gifRegs.stat.APATH, curSize - curOffset, 16 + gifTag.len);
|
|
return gsPack;
|
|
}
|
|
|
|
incTag(curOffset, gsPack.size, 16); // Tag Size
|
|
gsPack.cycles += 2 + gifTag.cycles; // Tag + Len ee-cycles
|
|
}
|
|
|
|
if (gifTag.hasAD)
|
|
{ // Only can be true if GIF_FLG_PACKED
|
|
bool dblSIGNAL = false;
|
|
while (gifTag.nLoop && !dblSIGNAL)
|
|
{
|
|
if (curOffset + 16 > curSize)
|
|
{
|
|
GUNIT_WARN("PATH %d not enough data AD, available %d wanted %d", gifRegs.stat.APATH, curSize - curOffset, 16);
|
|
return gsPack; // Exit Early
|
|
}
|
|
if (gifTag.curReg() == GIF_REG_A_D)
|
|
{
|
|
if (!isMTVU())
|
|
dblSIGNAL = Gif_HandlerAD(&buffer[curOffset]);
|
|
}
|
|
incTag(curOffset, gsPack.size, 16); // 1 QWC
|
|
gifTag.packedStep();
|
|
}
|
|
if (dblSIGNAL && !(gifTag.tag.EOP && !gifTag.nLoop))
|
|
{
|
|
GUNIT_WARN("PATH %d early exit (double signal)", gifRegs.stat.APATH);
|
|
return gsPack; // Exit Early
|
|
}
|
|
}
|
|
else
|
|
incTag(curOffset, gsPack.size, gifTag.len); // Data length
|
|
|
|
// Reload gif tag next loop
|
|
gifTag.isValid = false;
|
|
|
|
if (gifTag.tag.EOP)
|
|
{
|
|
GS_Packet t = gsPack;
|
|
done = true;
|
|
|
|
dmaRewind = 0;
|
|
|
|
gsPack.Reset();
|
|
gsPack.offset = curOffset;
|
|
GUNIT_WARN("EOP PATH %d", gifRegs.stat.APATH);
|
|
//Path 3 Masking is timing sensitive, we need to simulate its length! (NFSU2/Outrun 2006)
|
|
|
|
if ((gifRegs.stat.APATH - 1) == GIF_PATH_3)
|
|
{
|
|
state = GIF_PATH_WAIT;
|
|
|
|
if (curSize - curOffset > 0 && (gifRegs.stat.M3R || gifRegs.stat.M3P))
|
|
{
|
|
//Including breaking packets early (Rewind DMA to pick up where left off)
|
|
//but only do this when the path is masked, else we're pointlessly slowing things down.
|
|
dmaRewind = curSize - curOffset;
|
|
curSize = curOffset;
|
|
}
|
|
}
|
|
else
|
|
state = GIF_PATH_IDLE;
|
|
|
|
return t; // Complete GS packet
|
|
}
|
|
}
|
|
}
|
|
|
|
// MTVU: Gets called on VU XGkicks on MTVU thread
|
|
void ExecuteGSPacketMTVU()
|
|
{
|
|
// Move packet to start of buffer
|
|
if (curOffset > buffLimit)
|
|
{
|
|
RealignPacket();
|
|
}
|
|
for (;;)
|
|
{ // needed to be processed by pcsx2...
|
|
if (curOffset + 16 > curSize)
|
|
break;
|
|
gifTag.setTag(&buffer[curOffset], 1);
|
|
|
|
if (!gifTag.hasAD && curOffset + 16 + gifTag.len > curSize)
|
|
break;
|
|
incTag(curOffset, gsPack.size, 16); // Tag Size
|
|
|
|
if (gifTag.hasAD)
|
|
{ // Only can be true if GIF_FLG_PACKED
|
|
while (gifTag.nLoop)
|
|
{
|
|
if (curOffset + 16 > curSize)
|
|
break; // Exit Early
|
|
if (gifTag.curReg() == GIF_REG_A_D)
|
|
{
|
|
pxAssert(!Gif_HandlerAD_MTVU(&buffer[curOffset]));
|
|
}
|
|
incTag(curOffset, gsPack.size, 16); // 1 QWC
|
|
gifTag.packedStep();
|
|
}
|
|
}
|
|
else
|
|
incTag(curOffset, gsPack.size, gifTag.len); // Data length
|
|
if (curOffset >= curSize)
|
|
break;
|
|
if (gifTag.tag.EOP)
|
|
break;
|
|
}
|
|
pxAssert(curOffset == curSize);
|
|
gifTag.isValid = false;
|
|
}
|
|
|
|
// MTVU: Gets called after VU1 execution on MTVU thread
|
|
void FinishGSPacketMTVU()
|
|
{
|
|
// Performance note: fetch_add atomic operation might create some stall for atomic
|
|
// operation in gsPack.push
|
|
readAmount.fetch_add(gsPack.size + gsPack.readAmount, std::memory_order_acq_rel);
|
|
while (!mtvu.gsPackQueue.push(gsPack))
|
|
;
|
|
|
|
gsPack.Reset();
|
|
gsPack.offset = curOffset;
|
|
}
|
|
|
|
// MTVU: Gets called by MTGS thread
|
|
GS_Packet GetGSPacketMTVU()
|
|
{
|
|
// FIXME is the error path useful ?
|
|
if (!mtvu.gsPackQueue.empty())
|
|
{
|
|
return mtvu.gsPackQueue.front();
|
|
}
|
|
|
|
Console.Error("MTVU: Expected gsPackQueue to have elements!");
|
|
pxAssert(0);
|
|
return GS_Packet(); // gsPack.size will be 0
|
|
}
|
|
|
|
// MTVU: Gets called by MTGS thread
|
|
void PopGSPacketMTVU()
|
|
{
|
|
mtvu.gsPackQueue.pop();
|
|
}
|
|
|
|
// MTVU: Returns the amount of pending
|
|
// GS Packets that MTGS hasn't yet processed
|
|
u32 GetPendingGSPackets()
|
|
{
|
|
return mtvu.gsPackQueue.size();
|
|
}
|
|
};
|
|
|
|
struct Gif_Unit
|
|
{
|
|
Gif_Path gifPath[3];
|
|
GS_SIGNAL gsSIGNAL; // Stalling Signal
|
|
GS_FINISH gsFINISH; // Finish Signal
|
|
tGIF_STAT& stat;
|
|
GIF_TRANSFER_TYPE lastTranType; // Last Transfer Type
|
|
|
|
Gif_Unit()
|
|
: gsSIGNAL()
|
|
, gsFINISH()
|
|
, stat(gifRegs.stat)
|
|
, lastTranType(GIF_TRANS_INVALID)
|
|
{
|
|
gifPath[0].Init(GIF_PATH_1, _1mb * 9, _1mb + _1kb);
|
|
gifPath[1].Init(GIF_PATH_2, _1mb * 9, _1mb + _1kb);
|
|
gifPath[2].Init(GIF_PATH_3, _1mb * 9, _1mb + _1kb);
|
|
}
|
|
|
|
// Enable softReset when resetting during game emulation
|
|
void Reset(bool softReset = false)
|
|
{
|
|
GUNIT_WARN(Color_Red, "Gif Unit Reset!!! [soft=%d]", softReset);
|
|
ResetRegs();
|
|
gsSIGNAL.Reset();
|
|
gsFINISH.Reset();
|
|
gifPath[0].Reset(softReset);
|
|
gifPath[1].Reset(softReset);
|
|
gifPath[2].Reset(softReset);
|
|
if (!softReset)
|
|
{
|
|
lastTranType = GIF_TRANS_INVALID;
|
|
}
|
|
//If the VIF has paused waiting for PATH3, recheck it after the reset has occurred (Eragon)
|
|
if (vif1Regs.stat.VGW)
|
|
{
|
|
if (!(cpuRegs.interrupt & (1 << DMAC_VIF1)))
|
|
CPU_INT(DMAC_VIF1, 1);
|
|
}
|
|
}
|
|
|
|
// Resets Gif HW Regs
|
|
// Warning: Do not mess with the DMA here, the reset does *NOT* touch this.
|
|
void ResetRegs()
|
|
{
|
|
gifRegs.stat.reset();
|
|
gifRegs.ctrl.reset();
|
|
gifRegs.mode.reset();
|
|
CSRreg.FIFO = CSR_FIFO_EMPTY; // This is the GIF unit side FIFO, not DMA!
|
|
}
|
|
|
|
// Adds a finished GS Packet to the MTGS ring buffer
|
|
__fi void AddCompletedGSPacket(GS_Packet& gsPack, GIF_PATH path)
|
|
{
|
|
if (gsPack.size == ~0u)
|
|
Gif_AddGSPacketMTVU(gsPack, path);
|
|
else
|
|
Gif_AddCompletedGSPacket(gsPack, path);
|
|
if (PRINT_GIF_PACKET)
|
|
Gif_ParsePacket(gsPack, path);
|
|
}
|
|
|
|
// Returns GS Packet Size in bytes
|
|
u32 GetGSPacketSize(GIF_PATH pathIdx, u8* pMem, u32 offset = 0, u32 size = ~0u, bool flush = false)
|
|
{
|
|
u32 memMask = pathIdx ? ~0u : 0x3fffu;
|
|
u32 curSize = 0;
|
|
for (;;)
|
|
{
|
|
Gif_Tag gifTag(&pMem[offset & memMask]);
|
|
incTag(offset, curSize, 16 + gifTag.len); // Tag + Data length
|
|
if (pathIdx == GIF_PATH_1 && curSize >= 0x4000)
|
|
{
|
|
DevCon.Warning("Gif Unit - GS packet size exceeded VU memory size!");
|
|
return 0; // Bios does this... (Fixed if you delay vu1's xgkick by 103 vu cycles)
|
|
}
|
|
if (curSize >= size)
|
|
return size;
|
|
if(((flush && gifTag.tag.EOP) || !flush) && (CHECK_XGKICKHACK || !EmuConfig.Cpu.Recompiler.EnableVU1))
|
|
{
|
|
return curSize | ((u32)gifTag.tag.EOP << 31);
|
|
}
|
|
if (gifTag.tag.EOP )
|
|
{
|
|
return curSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Specify the transfer type you are initiating
|
|
// The return value is the amount of data (in bytes) that was processed
|
|
// If transfer cannot take place at this moment the return value is 0
|
|
u32 TransferGSPacketData(GIF_TRANSFER_TYPE tranType, u8* pMem, u32 size, bool aligned = false)
|
|
{
|
|
|
|
if (THREAD_VU1)
|
|
{
|
|
Gif_Path& path1 = gifPath[GIF_PATH_1];
|
|
if (tranType == GIF_TRANS_XGKICK)
|
|
{ // This is on the MTVU thread
|
|
path1.CopyGSPacketData(pMem, size, aligned);
|
|
path1.ExecuteGSPacketMTVU();
|
|
return size;
|
|
}
|
|
if (tranType == GIF_TRANS_MTVU)
|
|
{ // This is on the EE thread
|
|
path1.mtvu.fakePackets++;
|
|
if (CanDoGif())
|
|
Execute(false, true);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
GUNIT_LOG("%s - [path=%d][size=%d]", Gif_TransferStr[(tranType >> 8) & 0xf], (tranType & 3) + 1, size);
|
|
if (size == 0)
|
|
{
|
|
GUNIT_WARN("Gif Unit - Size == 0");
|
|
return 0;
|
|
}
|
|
if (!CanDoGif())
|
|
{
|
|
GUNIT_WARN("Gif Unit - Signal or PSE Set or Dir = GS to EE");
|
|
}
|
|
//pxAssertDev((stat.APATH==0) || checkPaths(1,1,1), "Gif Unit - APATH wasn't cleared?");
|
|
lastTranType = tranType;
|
|
|
|
if (tranType == GIF_TRANS_FIFO)
|
|
{
|
|
if (!CanDoPath3())
|
|
DevCon.Warning("Gif Unit - Path 3 FIFO transfer while !CanDoPath3()");
|
|
}
|
|
if (tranType == GIF_TRANS_DMA)
|
|
{
|
|
if (!CanDoPath3())
|
|
{
|
|
if (!Path3Masked())
|
|
stat.P3Q = 1;
|
|
return 0;
|
|
} // DMA Stall
|
|
//if (stat.P2Q) DevCon.WriteLn("P2Q while path 3");
|
|
}
|
|
if (tranType == GIF_TRANS_XGKICK)
|
|
{
|
|
if (!CanDoPath1())
|
|
{
|
|
stat.P1Q = 1;
|
|
} // We always buffer path1 packets
|
|
}
|
|
if (tranType == GIF_TRANS_DIRECT)
|
|
{
|
|
if (!CanDoPath2())
|
|
{
|
|
stat.P2Q = 1;
|
|
return 0;
|
|
} // Direct Stall
|
|
}
|
|
if (tranType == GIF_TRANS_DIRECTHL)
|
|
{
|
|
if (!CanDoPath2HL())
|
|
{
|
|
stat.P2Q = 1;
|
|
return 0;
|
|
} // DirectHL Stall
|
|
}
|
|
|
|
gifPath[tranType & 3].CopyGSPacketData(pMem, size, aligned);
|
|
size -= Execute(tranType == GIF_TRANS_DMA, false);
|
|
return size;
|
|
}
|
|
|
|
// Checks path activity for the given paths
|
|
// Returns an int with a bit enabled if the corresponding
|
|
// path is not finished (needs more data/processing for an EOP)
|
|
__fi int checkPaths(bool p1, bool p2, bool p3, bool checkQ = false)
|
|
{
|
|
int ret = 0;
|
|
ret |= (p1 && !gifPath[GIF_PATH_1].isDone()) << 0;
|
|
ret |= (p2 && !gifPath[GIF_PATH_2].isDone()) << 1;
|
|
ret |= (p3 && !gifPath[GIF_PATH_3].isDone()) << 2;
|
|
return ret | (checkQ ? checkQueued(p1, p2, p3) : 0);
|
|
}
|
|
|
|
__fi int checkQueued(bool p1, bool p2, bool p3)
|
|
{
|
|
int ret = 0;
|
|
ret |= (p1 && stat.P1Q) << 0;
|
|
ret |= (p2 && stat.P2Q) << 1;
|
|
ret |= (p3 && stat.P3Q) << 2;
|
|
return ret;
|
|
}
|
|
|
|
// Send processed GS Primitive(s) to the MTGS thread
|
|
// Note: Only does so if current path fully completed all
|
|
// of its given gs primitives (but didn't upload them yet)
|
|
void FlushToMTGS()
|
|
{
|
|
if (!stat.APATH)
|
|
return;
|
|
Gif_Path& path = gifPath[stat.APATH - 1];
|
|
if (path.gsPack.size && !path.gifTag.isValid)
|
|
{
|
|
AddCompletedGSPacket(path.gsPack, (GIF_PATH)(stat.APATH - 1));
|
|
path.gsPack.offset = path.curOffset;
|
|
path.gsPack.size = 0;
|
|
}
|
|
}
|
|
|
|
// Processes gif packets and performs path arbitration
|
|
// on EOPs or on Path 3 Images when IMT is set.
|
|
int Execute(bool isPath3, bool isResume)
|
|
{
|
|
if (!CanDoGif())
|
|
{
|
|
DevCon.Error("Gif Unit - Signal or PSE Set or Dir = GS to EE");
|
|
return 0;
|
|
}
|
|
bool didPath3 = false;
|
|
bool path3Check = isPath3;
|
|
int curPath = stat.APATH > 0 ? stat.APATH - 1 : 0; //Init to zero if no path is already set.
|
|
gifPath[2].dmaRewind = 0;
|
|
stat.OPH = 1;
|
|
|
|
for (;;)
|
|
{
|
|
if (stat.APATH)
|
|
{ // Some Transfer is happening
|
|
Gif_Path& path = gifPath[stat.APATH - 1];
|
|
bool done = false;
|
|
GS_Packet gsPack = path.ExecuteGSPacket(done);
|
|
if (!done)
|
|
{
|
|
if (stat.APATH == 3 && CanDoP3Slice() && !gsSIGNAL.queued)
|
|
{
|
|
if (!didPath3 && /*!Path3Masked() &&*/ checkPaths(1, 1, 0))
|
|
{ // Path3 slicing
|
|
didPath3 = true;
|
|
stat.APATH = 0;
|
|
stat.IP3 = 1;
|
|
GUNIT_LOG(Color_Magenta, "Gif Unit - Path 3 slicing arbitration");
|
|
if (gsPack.size > 16)
|
|
{ // Packet had other tags which we already processed
|
|
u32 subOffset = path.gifTag.isValid ? 16 : 0; // if isValid, image-primitive not finished
|
|
gsPack.size -= subOffset; // Remove the image-tag (should be last thing read)
|
|
AddCompletedGSPacket(gsPack, GIF_PATH_3); // Consider current packet complete
|
|
path.gsPack.Reset(); // Reset gs packet info
|
|
path.curOffset -= subOffset; // Start the next GS packet at the image-tag
|
|
path.gsPack.offset = path.curOffset; // Set to image-tag
|
|
path.gifTag.isValid = false; // Reload tag next ExecuteGSPacket()
|
|
pxAssert((s32)path.curOffset >= 0);
|
|
pxAssert(path.state == GIF_PATH_IMAGE);
|
|
GUNIT_LOG(Color_Magenta, "Gif Unit - Sending path 3 sliced gs packet!");
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
//FlushToMTGS();
|
|
//DevCon.WriteLn("Incomplete GS Packet for path %d, size=%d", stat.APATH, gsPack.size);
|
|
break; // Not finished with GS packet
|
|
}
|
|
//DevCon.WriteLn("Adding GS Packet for path %d", stat.APATH);
|
|
if (gifPath[curPath].state == GIF_PATH_WAIT || gifPath[curPath].state == GIF_PATH_IDLE)
|
|
{
|
|
AddCompletedGSPacket(gsPack, (GIF_PATH)(stat.APATH - 1));
|
|
}
|
|
}
|
|
if (!gsSIGNAL.queued && !gifPath[0].isDone())
|
|
{
|
|
GUNIT_WARN("Swapping to PATH 1");
|
|
stat.APATH = 1;
|
|
stat.P1Q = 0;
|
|
curPath = 0;
|
|
}
|
|
else if (!gsSIGNAL.queued && !gifPath[1].isDone())
|
|
{
|
|
GUNIT_WARN("Swapping to PATH 2");
|
|
stat.APATH = 2;
|
|
stat.P2Q = 0;
|
|
curPath = 1;
|
|
}
|
|
else if (!gsSIGNAL.queued && !gifPath[2].isDone() && !Path3Masked())
|
|
{
|
|
GUNIT_WARN("Swapping to PATH 3");
|
|
stat.APATH = 3;
|
|
stat.P3Q = 0;
|
|
stat.IP3 = 0;
|
|
curPath = 2;
|
|
path3Check = true;
|
|
}
|
|
else
|
|
{
|
|
GUNIT_WARN("Finished Processing");
|
|
// If PATH3 was stalled due to another transfer but the DMA ended, it'll never check this
|
|
// So lets quickly check if it's currently set to path3
|
|
if (stat.APATH == 3 || path3Check)
|
|
gifCheckPathStatus(true);
|
|
else
|
|
{
|
|
if (vif1Regs.stat.VGW)
|
|
{
|
|
// Check if VIF is in a cycle or is currently "idle" waiting for GIF to come back.
|
|
if (!(cpuRegs.interrupt & (1 << DMAC_VIF1)))
|
|
CPU_INT(DMAC_VIF1, 1);
|
|
}
|
|
|
|
stat.APATH = 0;
|
|
stat.OPH = 0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
//Some loaders/Refresh Rate selectors and things dont issue "End of Packet" commands
|
|
//So we look and see if the end of the last tag is all there, if so, stick it in the buffer for the GS :)
|
|
//(Invisible Screens on Terminator 3 and Growlanser 2/3)
|
|
if (gifPath[curPath].curOffset == gifPath[curPath].curSize)
|
|
{
|
|
FlushToMTGS();
|
|
}
|
|
|
|
Gif_FinishIRQ();
|
|
|
|
//Path3 can rewind the DMA, so we send back the amount we go back!
|
|
if (isPath3)
|
|
return gifPath[2].dmaRewind;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
// XGkick
|
|
bool CanDoPath1() const
|
|
{
|
|
return (stat.APATH == 0 || stat.APATH == 1 || (stat.APATH == 3 && CanDoP3Slice())) && CanDoGif();
|
|
}
|
|
// Direct
|
|
bool CanDoPath2() const
|
|
{
|
|
return (stat.APATH == 0 || stat.APATH == 2 || (stat.APATH == 3 && CanDoP3Slice())) && CanDoGif();
|
|
}
|
|
// DirectHL
|
|
bool CanDoPath2HL() const
|
|
{
|
|
return (stat.APATH == 0 || stat.APATH == 2) && CanDoGif();
|
|
}
|
|
// Gif DMA
|
|
bool CanDoPath3() const
|
|
{
|
|
return ((stat.APATH == 0 && !Path3Masked()) || stat.APATH == 3) && CanDoGif();
|
|
}
|
|
|
|
bool CanDoP3Slice() const { return stat.IMT == 1 && gifPath[GIF_PATH_3].state == GIF_PATH_IMAGE; }
|
|
bool CanDoGif() const { return stat.PSE == 0 && stat.DIR == 0 && gsSIGNAL.queued == 0; }
|
|
//Mask stops the next packet which hasnt started from transferring
|
|
bool Path3Masked() const { return ((stat.M3R || stat.M3P) && (gifPath[GIF_PATH_3].state == GIF_PATH_IDLE || gifPath[GIF_PATH_3].state == GIF_PATH_WAIT)); }
|
|
|
|
void PrintInfo(bool printP1 = 1, bool printP2 = 1, bool printP3 = 1)
|
|
{
|
|
u32 a = checkPaths(1, 1, 1), b = checkQueued(1, 1, 1);
|
|
(void)a; // Don't warn about unused variable
|
|
(void)b;
|
|
GUNIT_LOG("Gif Unit - LastTransfer = %s, Paths = [%d,%d,%d], Queued = [%d,%d,%d]",
|
|
Gif_TransferStr[(lastTranType >> 8) & 0xf],
|
|
!!(a & 1), !!(a & 2), !!(a & 4), !!(b & 1), !!(b & 2), !!(b & 4));
|
|
GUNIT_LOG("Gif Unit - [APATH = %d][Signal = %d][PSE = %d][DIR = %d]",
|
|
stat.APATH, gsSIGNAL.queued, stat.PSE, stat.DIR);
|
|
GUNIT_LOG("Gif Unit - [CanDoGif = %d][CanDoPath3 = %d][CanDoP3Slice = %d]",
|
|
CanDoGif(), CanDoPath3(), CanDoP3Slice());
|
|
if (printP1)
|
|
PrintPathInfo(GIF_PATH_1);
|
|
if (printP2)
|
|
PrintPathInfo(GIF_PATH_2);
|
|
if (printP3)
|
|
PrintPathInfo(GIF_PATH_3);
|
|
}
|
|
|
|
void PrintPathInfo(GIF_PATH path)
|
|
{
|
|
GUNIT_LOG("Gif Path %d - [hasData = %d][state = %d]", path,
|
|
gifPath[path].hasDataRemaining(), gifPath[path].state);
|
|
}
|
|
};
|
|
|
|
extern Gif_Unit gifUnit;
|