// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+

#include "Common.h"
#include "GS.h"
#include "Gif_Unit.h"
#include "Vif_Dma.h"

// A three-way toggle used to determine if the GIF is stalling (transferring) or done (finished).
// Should be a gifstate_t rather then int, but I don't feel like possibly interfering with savestates right now.


alignas(16) GIF_Fifo gif_fifo;
alignas(16) gifStruct gif;

static __fi void GifDMAInt(int cycles)
{
	if (dmacRegs.ctrl.MFD == MFD_GIF)
	{
		if (!(cpuRegs.interrupt & (1 << DMAC_MFIFO_GIF)) || cpuRegs.eCycle[DMAC_MFIFO_GIF] < (u32)cycles)
		{
			CPU_INT(DMAC_MFIFO_GIF, cycles);
		}
	}
	else if (!(cpuRegs.interrupt & (1 << DMAC_GIF)) || cpuRegs.eCycle[DMAC_GIF] < (u32)cycles)
	{
		CPU_INT(DMAC_GIF, cycles);
	}
}
__fi void clearFIFOstuff(bool full)
{
	CSRreg.FIFO = full ? CSR_FIFO_FULL : CSR_FIFO_EMPTY;
}

//I suspect this is GS side which should really be handled by GS which also doesn't current have a fifo, but we can guess from our fifo
static __fi void CalculateFIFOCSR()
{
	if (gifRegs.stat.FQC >= 15)
	{
		CSRreg.FIFO = CSR_FIFO_FULL;
	}
	else if (gifRegs.stat.FQC == 0)
	{
		CSRreg.FIFO = CSR_FIFO_EMPTY;
	}
	else
	{
		CSRreg.FIFO = CSR_FIFO_NORMAL;
	}
}


bool CheckPaths()
{
	// Can't do Path 3, so try dma again later...
	if (!gifUnit.CanDoPath3())
	{
		if (!gifUnit.Path3Masked())
		{
			//DevCon.Warning("Path3 stalled APATH %x PSE %x DIR %x Signal %x", gifRegs.stat.APATH, gifRegs.stat.PSE, gifRegs.stat.DIR, gifUnit.gsSIGNAL.queued);
			GifDMAInt(128);
		}
		return false;
	}
	return true;
}

void GIF_Fifo::init()
{
	std::memset(data, 0, sizeof(data));
	fifoSize = 0;
	gifRegs.stat.FQC = 0;

	gif.gifstate = GIF_STATE_READY;
	gif.gspath3done = true;

	gif.gscycles = 0;
	gif.prevcycles = 0;
	gif.mfifocycles = 0;
}


int GIF_Fifo::write_fifo(u32* pMem, int size)
{
	if (fifoSize == 16)
	{
		//GIF_LOG("GIF FIFO Full");
		return 0;
	}

	const int transferSize = std::min(size, 16 - (int)fifoSize);

	int writePos = fifoSize * 4;

	memcpy(&data[writePos], pMem, transferSize * 16);

	fifoSize += transferSize;

	GIF_LOG("GIF FIFO Adding %d QW to GIF FIFO at offset %d FIFO now contains %d QW", transferSize, writePos, fifoSize);

	gifRegs.stat.FQC = fifoSize;
	CalculateFIFOCSR();

	return transferSize;
}

int GIF_Fifo::read_fifo()
{
	if (!fifoSize || !gifUnit.CanDoPath3())
	{
		gifRegs.stat.FQC = fifoSize;
		CalculateFIFOCSR();
		if (fifoSize)
		{
			GIF_LOG("GIF FIFO Can't read, GIF paused/busy. Waiting");
			GifDMAInt(128);
		}
		return 0;
	}

	const int sizeRead = gifUnit.TransferGSPacketData(GIF_TRANS_DMA, (u8*)&data, fifoSize * 16) / 16; //returns the size actually read

	GIF_LOG("GIF FIFO Read %d QW from FIFO Current Size %d", sizeRead, fifoSize);

	if (sizeRead < (int)fifoSize)
	{
		if (sizeRead > 0)
		{
			const int copyAmount = fifoSize - sizeRead;
			const int readpos = sizeRead * 4;

			for (int i = 0; i < copyAmount; i++)
				CopyQWC(&data[i * 4], &data[readpos + (i * 4)]);

			fifoSize = copyAmount;

			GIF_LOG("GIF FIFO rearranged to now only contain %d QW", fifoSize);
		}
		else
		{
			GIF_LOG("GIF FIFO not read");
		}
	}
	else
	{
		GIF_LOG("GIF FIFO now empty");
		fifoSize = 0;
	}

	gifRegs.stat.FQC = fifoSize;
	CalculateFIFOCSR();

	return sizeRead;
}

void incGifChAddr(u32 qwc)
{
	if (gifch.chcr.STR)
	{
		gifch.madr += qwc * 16;
		gifch.qwc -= qwc;
		hwDmacSrcTadrInc(gifch);
	}
	else
		DevCon.Error("incGifAddr() Error!");
}

__fi void gifCheckPathStatus(bool calledFromGIF)
{
	// If GIF is running on it's own, let it handle its own timing.
	if (calledFromGIF && gifch.chcr.STR)
	{
		if (gif_fifo.fifoSize == 16)
			GifDMAInt(16);
		return;
	}

	// Required for Path3 Masking timing!
	if (gifUnit.gifPath[GIF_PATH_3].state == GIF_PATH_WAIT)
		gifUnit.gifPath[GIF_PATH_3].state = GIF_PATH_IDLE;

	if (gifRegs.stat.APATH == 3)
	{
		gifRegs.stat.APATH = 0;
		gifRegs.stat.OPH = 0;

		if (!calledFromGIF && (gifUnit.gifPath[GIF_PATH_3].state == GIF_PATH_IDLE || gifUnit.gifPath[GIF_PATH_3].state == GIF_PATH_WAIT))
		{
			if (gifUnit.checkPaths(1, 1, 0))
				gifUnit.Execute(false, true);
		}
	}

	// GIF DMA isn't running but VIF might be waiting on PATH3 so resume it here
	if (calledFromGIF && gifUnit.gifPath[GIF_PATH_3].state == GIF_PATH_IDLE)
	{
		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);

			// Make sure it loops if the GIF packet is empty to prepare for the next packet
			// or end if it was the end of a packet.
			// This must trigger after VIF retriggers as VIf might instantly mask Path3
			if ((!gifUnit.Path3Masked() || gifch.qwc == 0) && (gifch.chcr.STR || gif_fifo.fifoSize))
			{
				GifDMAInt(16);
			}
		}
	}
}

__fi void gifInterrupt()
{
	GIF_LOG("gifInterrupt caught qwc=%d fifo=%d(%d) apath=%d oph=%d state=%d!", gifch.qwc, gifRegs.stat.FQC, gif_fifo.fifoSize, gifRegs.stat.APATH, gifRegs.stat.OPH, gifUnit.gifPath[GIF_PATH_3].state);
	gifCheckPathStatus(false);

	if (gifUnit.gifPath[GIF_PATH_3].state == GIF_PATH_IDLE)
	{
		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);

			// Make sure it loops if the GIF packet is empty to prepare for the next packet
			// or end if it was the end of a packet.
			// This must trigger after VIF retriggers as VIf might instantly mask Path3
			if (!gifUnit.Path3Masked() || gifch.qwc == 0)
			{
				GifDMAInt(16);
			}
			CPU_SET_DMASTALL(DMAC_GIF, gifUnit.Path3Masked() || !gifUnit.CanDoPath3());
			return;
		}
	}

	if (dmacRegs.ctrl.MFD == MFD_GIF)
	{ // GIF MFIFO
		//Console.WriteLn("GIF MFIFO");
		gifMFIFOInterrupt();
		return;
	}

	if (gifUnit.gsSIGNAL.queued)
	{
		GIF_LOG("Path 3 Paused");
		GifDMAInt(128);
		CPU_SET_DMASTALL(DMAC_GIF, true);
		if (gif_fifo.fifoSize == 16)
			return;
	}

	// If there's something in the FIFO and we can do PATH3, empty the FIFO.
	if (gif_fifo.fifoSize > 0)
	{
		const int readSize = gif_fifo.read_fifo();

		if (readSize)
			GifDMAInt(readSize * BIAS);

		// The following is quite timing sensitive so we need to pause/resume the DMA in these certain scenarios
		// If the DMA is masked/blocked and the fifo is full, no need to run the DMA
		// If we just read from the fifo, we want to loop and not read more DMA
		// If there is no DMA data waiting and the DMA is active, let the DMA progress until there is
		if ((!CheckPaths() && gif_fifo.fifoSize == 16) || readSize)
		{
			CPU_SET_DMASTALL(DMAC_GIF, gifUnit.Path3Masked() || !gifUnit.CanDoPath3());
			return;
		}
	}

	if (!(gifch.chcr.STR))
		return;

	if ((gifch.qwc > 0) || (!gif.gspath3done))
	{
		if (!dmacRegs.ctrl.DMAE)
		{
			Console.Warning("gs dma masked, re-scheduling...");
			// Re-raise the int shortly in the future
			GifDMAInt(64);
			CPU_SET_DMASTALL(DMAC_GIF, true);
			return;
		}
		GIFdma();

		return;
	}

	gif.gscycles = 0;
	gifch.chcr.STR = false;
	gifRegs.stat.FQC = gif_fifo.fifoSize;
	CalculateFIFOCSR();
	hwDmacIrq(DMAC_GIF);

	if (gif_fifo.fifoSize)
		GifDMAInt(8 * BIAS);
	GIF_LOG("GIF DMA End QWC in fifo %x (%x) APATH = %x OPH = %x state = %x", gifRegs.stat.FQC, gif_fifo.fifoSize, gifRegs.stat.APATH, gifRegs.stat.OPH, gifUnit.gifPath[GIF_PATH_3].state);
}

static u32 WRITERING_DMA(u32* pMem, u32 qwc)
{
	const u32 originalQwc = qwc;

	if (gifRegs.stat.IMT)
	{
		// Splitting by 8qw can be really slow, so on bigger packets be less picky.
		// Games seem to be more concerned with other channels finishing before PATH 3 finishes
		// so we can get away with transferring "most" of it when it's a big packet.
		// Use Wallace and Gromit Project Zoo or The Suffering for testing
		if (qwc > 64)
			qwc = qwc * 0.5f;
		else
			qwc = std::min(qwc, 8u);
	}
	// If the packet is larger than 8qw, try to time the packet somewhat so any "finish" signals don't fire way too early and GIF syncs with other units.
	// (Mana Khemia exhibits flickering characters without).
	else if (qwc > 8)
		qwc -= 8;

	uint size;

	if (CheckPaths() == false || ((qwc < 8 || gif_fifo.fifoSize > 0) && CHECK_GIFFIFOHACK))
	{
		if (gif_fifo.fifoSize < 16)
		{
			size = gif_fifo.write_fifo((u32*)pMem, originalQwc); // Use original QWC here, the intermediate mode is for the GIF unit, not DMA
			incGifChAddr(size);
			return size;
		}
		return 4; // Arbitrary value, probably won't schedule a DMA anwyay since the FIFO is full and GIF is paused
	}

	size = gifUnit.TransferGSPacketData(GIF_TRANS_DMA, (u8*)pMem, qwc * 16) / 16;
	incGifChAddr(size);
	return size;
}

static __fi void GIFchain()
{
	tDMA_TAG* pMem;

	pMem = dmaGetAddr(gifch.madr, false);
	if (pMem == NULL)
	{
		// Must increment madr and clear qwc, else it loops
		gifch.madr += gifch.qwc * 16;
		gifch.qwc = 0;
		Console.Warning("Hackfix - NULL GIFchain");
		return;
	}

	const int transferred = WRITERING_DMA((u32*)pMem, gifch.qwc);
	gif.gscycles += transferred * BIAS;

	if (!gifUnit.Path3Masked() || (gif_fifo.fifoSize < 16))
		GifDMAInt(gif.gscycles);
}

static __fi bool checkTieBit(tDMA_TAG*& ptag)
{
	if (gifch.chcr.TIE && ptag->IRQ)
	{
		GIF_LOG("dmaIrq Set");
		gif.gspath3done = true;
		return true;
	}
	return false;
}

static __fi tDMA_TAG* ReadTag()
{
	tDMA_TAG* ptag = dmaGetAddr(gifch.tadr, false); // Set memory pointer to TADR

	if (!(gifch.transfer("Gif", ptag)))
		return NULL;

	gifch.madr = ptag[1]._u32; // MADR = ADDR field + SPR
	gif.gscycles += 2; // Add 1 cycles from the QW read for the tag

	gif.gspath3done = hwDmacSrcChainWithStack(gifch, ptag->ID);
	return ptag;
}

void GIFdma()
{
	while (gifch.qwc > 0 || !gif.gspath3done)
	{
		tDMA_TAG* ptag;
		gif.gscycles = gif.prevcycles;

		if (gifRegs.ctrl.PSE)
		{ // Temporarily stop
			DevCon.WriteLn("Gif dma paused by PSE bit.");
			GifDMAInt(16);
			CPU_SET_DMASTALL(DMAC_GIF, true);
			return;
		}

		if ((dmacRegs.ctrl.STD == STD_GIF) && (gif.prevcycles != 0))
		{
			//Console.WriteLn("GS Stall Control Source = %x, Drain = %x\n MADR = %x, STADR = %x", (psHu32(0xe000) >> 4) & 0x3, (psHu32(0xe000) >> 6) & 0x3, gifch.madr, psHu32(DMAC_STADR));
			if ((gifch.madr + (gifch.qwc * 16)) > dmacRegs.stadr.ADDR)
			{
				GifDMAInt(4);
				CPU_SET_DMASTALL(DMAC_GIF, true);
				gif.gscycles = 0;
				return;
			}
			gif.prevcycles = 0;
			gifch.qwc = 0;
		}

		if ((gifch.chcr.MOD == CHAIN_MODE) && (!gif.gspath3done) && gifch.qwc == 0) // Chain Mode
		{
			ptag = ReadTag();
			if (ptag == NULL)
				return;
			//DevCon.Warning("GIF Reading Tag MSK = %x", vif1Regs.mskpath3);
			GIF_LOG("gifdmaChain %8.8x_%8.8x size=%d, id=%d, addr=%lx tadr=%lx", ptag[1]._u32, ptag[0]._u32, gifch.qwc, ptag->ID, gifch.madr, gifch.tadr);
			gifRegs.stat.FQC = std::min((u32)0x10, gifch.qwc);
			CalculateFIFOCSR();

			if (dmacRegs.ctrl.STD == STD_GIF)
			{
				// there are still bugs, need to also check if gifch.madr +16*qwc >= stadr, if not, stall
				if ((ptag->ID == TAG_REFS) && ((gifch.madr + (gifch.qwc * 16)) > dmacRegs.stadr.ADDR))
				{
					// stalled.
					// We really need to test this. Pay attention to prevcycles, as it used to trigger GIFchains in the code above. (rama)
					//DevCon.Warning("GS Stall Control start Source = %x, Drain = %x\n MADR = %x, STADR = %x", (psHu32(0xe000) >> 4) & 0x3, (psHu32(0xe000) >> 6) & 0x3,gifch.madr, psHu32(DMAC_STADR));
					gif.prevcycles = gif.gscycles;
					gifch.tadr -= 16;
					gifch.qwc = 0;
					hwDmacIrq(DMAC_STALL_SIS);
					GifDMAInt(128);
					gif.gscycles = 0;
					CPU_SET_DMASTALL(DMAC_GIF, true);
					return;
				}
			}

			checkTieBit(ptag);
		}
		else if (dmacRegs.ctrl.STD == STD_GIF && gifch.chcr.MOD == NORMAL_MODE)
		{
			Console.WriteLn("GIF DMA Stall in Normal mode not implemented - Report which game to PCSX2 Team");
		}

		// Transfer Dn_QWC from Dn_MADR to GIF
		if (gifch.qwc > 0) // Normal Mode
		{
			GIFchain(); // Transfers the data set by the switch
			CPU_SET_DMASTALL(DMAC_GIF, gifUnit.Path3Masked() || !gifUnit.CanDoPath3());
			return;
		}
	}

	gif.prevcycles = 0;
	GifDMAInt(16);
}

void dmaGIF()
{
	// DevCon.Warning("dmaGIFstart chcr = %lx, madr = %lx, qwc  = %lx\n tadr = %lx, asr0 = %lx, asr1 = %lx", gifch.chcr._u32, gifch.madr, gifch.qwc, gifch.tadr, gifch.asr0, gifch.asr1);

	gif.gspath3done = false; // For some reason this doesn't clear? So when the system starts the thread, we will clear it :)
	CPU_SET_DMASTALL(DMAC_GIF, false);
	if (gifch.chcr.MOD == NORMAL_MODE)
	{ // Else it really is a normal transfer and we want to quit, else it gets confused with chains
		gif.gspath3done = true;
	}

	if (gifch.chcr.MOD == CHAIN_MODE && gifch.qwc > 0)
	{
		//DevCon.Warning(L"GIF QWC on Chain " + gifch.chcr.desc());
		if ((gifch.chcr.tag().ID == TAG_REFE) || (gifch.chcr.tag().ID == TAG_END) || (gifch.chcr.tag().IRQ && gifch.chcr.TIE))
		{
			gif.gspath3done = true;
		}
	}

	gifInterrupt();
}

static u32 QWCinGIFMFIFO(u32 DrainADDR)
{
	u32 ret;

	SPR_LOG("GIF MFIFO Requesting %x QWC from the MFIFO Base %x, SPR MADR %x Drain %x", gifch.qwc, dmacRegs.rbor.ADDR, spr0ch.madr, DrainADDR);
	// Calculate what we have in the fifo.
	if (DrainADDR <= spr0ch.madr)
	{
		// Drain is below the write position, calculate the difference between them
		ret = (spr0ch.madr - DrainADDR) >> 4;
	}
	else
	{
		u32 limit = dmacRegs.rbor.ADDR + dmacRegs.rbsr.RMSK + 16;
		// Drain is higher than SPR so it has looped round,
		// calculate from base to the SPR tag addr and what is left in the top of the ring
		ret = ((spr0ch.madr - dmacRegs.rbor.ADDR) + (limit - DrainADDR)) >> 4;
	}
	if (ret == 0)
		gif.gifstate = GIF_STATE_EMPTY;

	SPR_LOG("%x Available of the %x requested", ret, gifch.qwc);
	return ret;
}

static __fi bool mfifoGIFrbTransfer()
{
	const u32 qwc = std::min(QWCinGIFMFIFO(gifch.madr), gifch.qwc);

	if (qwc == 0) // Either gifch.qwc is 0 (shouldn't get here) or the FIFO is empty.
		return true;

	u8* src = (u8*)PSM(gifch.madr);
	if (src == NULL)
		return false;

	const u32 MFIFOUntilEnd = ((dmacRegs.rbor.ADDR + dmacRegs.rbsr.RMSK + 16) - gifch.madr) >> 4;
	const bool needWrap = MFIFOUntilEnd < qwc;
	const u32 firstTransQWC = needWrap ? MFIFOUntilEnd : qwc;
	const u32 transferred = WRITERING_DMA((u32*)src, firstTransQWC); // First part

	gifch.madr = dmacRegs.rbor.ADDR + (gifch.madr & dmacRegs.rbsr.RMSK);
	gifch.tadr = dmacRegs.rbor.ADDR + (gifch.tadr & dmacRegs.rbsr.RMSK);

	if (needWrap && transferred == MFIFOUntilEnd)
	{
		src = (u8*)PSM(dmacRegs.rbor.ADDR);
		if (src == NULL)
			return false;

		// Need to do second transfer to wrap around
		const uint secondTransQWC = qwc - MFIFOUntilEnd;
		const u32 transferred2 = WRITERING_DMA((u32*)src, secondTransQWC); // Second part

		gif.mfifocycles += (transferred2 + transferred) * 2;
	}
	else
		gif.mfifocycles += transferred * 2;

	return true;
}

static __fi void mfifoGIFchain()
{
	// Is QWC = 0? if so there is nothing to transfer
	if (gifch.qwc == 0)
	{
		gif.mfifocycles += 4;
		return;
	}

	if ((gifch.madr & ~dmacRegs.rbsr.RMSK) == dmacRegs.rbor.ADDR)
	{
		if (QWCinGIFMFIFO(gifch.madr) == 0)
		{
			SPR_LOG("GIF FIFO EMPTY before transfer");
			gif.gifstate = GIF_STATE_EMPTY;
			gif.mfifocycles += 4;
			return;
		}

		if (!mfifoGIFrbTransfer())
		{
			gif.mfifocycles += 4;
			gifch.qwc = 0;
			gif.gspath3done = true;
			return;
		}

		// This ends up being done more often but it's safer :P
		// Make sure we wrap the addresses, dont want it being stuck outside the ring when reading from the ring!
		gifch.madr = dmacRegs.rbor.ADDR + (gifch.madr & dmacRegs.rbsr.RMSK);
		gifch.tadr = gifch.madr;
	}
	else
	{
		SPR_LOG("Non-MFIFO Location transfer doing %x Total QWC", gifch.qwc);
		tDMA_TAG* pMem = dmaGetAddr(gifch.madr, false);
		if (pMem == NULL)
		{
			gif.mfifocycles += 4;
			gifch.qwc = 0;
			gif.gspath3done = true;
			return;
		}

		gif.mfifocycles += WRITERING_DMA((u32*)pMem, gifch.qwc) * 2;
	}

	return;
}

static u32 qwctag(u32 mask)
{
	return (dmacRegs.rbor.ADDR + (mask & dmacRegs.rbsr.RMSK));
}

void mfifoGifMaskMem(int id)
{
	switch (id)
	{
		// These five transfer data following the tag, need to check its within the buffer (Front Mission 4)
		case TAG_CNT:
		case TAG_NEXT:
		case TAG_CALL:
		case TAG_RET:
		case TAG_END:
			if (gifch.madr < dmacRegs.rbor.ADDR) // Probably not needed but we will check anyway.
			{
				SPR_LOG("GIF MFIFO MADR below bottom of ring buffer, wrapping GIF MADR = %x Ring Bottom %x", gifch.madr, dmacRegs.rbor.ADDR);
				gifch.madr = qwctag(gifch.madr);
			}
			else if (gifch.madr > (dmacRegs.rbor.ADDR + (u32)dmacRegs.rbsr.RMSK)) // Usual scenario is the tag is near the end (Front Mission 4)
			{
				SPR_LOG("GIF MFIFO MADR outside top of ring buffer, wrapping GIF MADR = %x Ring Top %x", gifch.madr, (dmacRegs.rbor.ADDR + dmacRegs.rbsr.RMSK) + 16);
				gifch.madr = qwctag(gifch.madr);
			}
			break;
		default:
			// Do nothing as the MADR could be outside
			break;
	}
}

void mfifoGIFtransfer()
{
	gif.mfifocycles = 0;

	if (gifRegs.ctrl.PSE)
	{ // Temporarily stop
		DevCon.WriteLn("Gif MFIFO dma paused by PSE bit.");
		CPU_INT(DMAC_MFIFO_GIF, 16);
		CPU_SET_DMASTALL(DMAC_MFIFO_GIF, true);
		return;
	}

	if (gifch.qwc == 0)
	{
		gifch.tadr = qwctag(gifch.tadr);

		if (QWCinGIFMFIFO(gifch.tadr) == 0)
		{
			SPR_LOG("GIF FIFO EMPTY before tag read");
			gif.gifstate = GIF_STATE_EMPTY;
			GifDMAInt(4);
			CPU_SET_DMASTALL(DMAC_MFIFO_GIF, true);
			return;
		}

		tDMA_TAG* ptag = dmaGetAddr(gifch.tadr, false);
		gifch.unsafeTransfer(ptag);
		gifch.madr = ptag[1]._u32;

		gifRegs.stat.FQC = std::min((u32)0x10, gifch.qwc);
		CalculateFIFOCSR();

		gif.mfifocycles += 2;

		GIF_LOG("dmaChain %8.8x_%8.8x size=%d, id=%d, madr=%lx, tadr=%lx mfifo qwc = %x spr0 madr = %x",
			ptag[1]._u32, ptag[0]._u32, gifch.qwc, ptag->ID, gifch.madr, gifch.tadr, gif.gifqwc, spr0ch.madr);

		gif.gspath3done = hwDmacSrcChainWithStack(gifch, ptag->ID);

		if (dmacRegs.ctrl.STD == STD_GIF && (ptag->ID == TAG_REFS))
		{
			Console.WriteLn("GIF MFIFO DMA Stall not implemented - Report which game to PCSX2 Team");
		}
		mfifoGifMaskMem(ptag->ID);

		gifch.tadr = qwctag(gifch.tadr);

		if ((gifch.chcr.TIE) && (ptag->IRQ))
		{
			SPR_LOG("dmaIrq Set");
			gif.gspath3done = true;
		}
	}

	mfifoGIFchain();

	GifDMAInt(std::max(gif.mfifocycles, (u32)4));

	SPR_LOG("mfifoGIFtransfer end %x madr %x, tadr %x", gifch.chcr._u32, gifch.madr, gifch.tadr);
}

void gifMFIFOInterrupt()
{
	//DevCon.Warning("gifMFIFOInterrupt");
	gif.mfifocycles = 0;

	if (dmacRegs.ctrl.MFD != MFD_GIF)
	{ // GIF not in MFIFO anymore, come out.
		DevCon.WriteLn("GIF Leaving MFIFO - Report if any errors");
		gifInterrupt();
		CPU_SET_DMASTALL(DMAC_MFIFO_GIF, true);
		return;
	}

	gifCheckPathStatus(false);

	if (gifUnit.gifPath[GIF_PATH_3].state == GIF_PATH_IDLE)
	{
		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);

			// Make sure it loops if the GIF packet is empty to prepare for the next packet
			// or end if it was the end of a packet.
			// This must trigger after VIF retriggers as VIf might instantly mask Path3
			if (!gifUnit.Path3Masked() || gifch.qwc == 0)
			{
				GifDMAInt(16);
			}
			CPU_SET_DMASTALL(DMAC_MFIFO_GIF, gifUnit.Path3Masked() || !gifUnit.CanDoPath3());
			return;
		}
	}

	if (gifUnit.gsSIGNAL.queued)
	{
		GifDMAInt(128);
		CPU_SET_DMASTALL(DMAC_MFIFO_GIF, true);
		return;
	}

	// If there's something in the FIFO and we can do PATH3, empty the FIFO.
	if (gif_fifo.fifoSize > 0)
	{
		const int readSize = gif_fifo.read_fifo();

		if (readSize)
			GifDMAInt(readSize * BIAS);

		// The following is quite timing sensitive so we need to pause/resume the DMA in these certain scenarios
		// If the DMA is masked/blocked and the fifo is full, no need to run the DMA
		// If we just read from the fifo, we want to loop and not read more DMA
		// If there is no DMA data waiting and the DMA is active, let the DMA progress until there is
		if ((!CheckPaths() && gif_fifo.fifoSize == 16) || readSize)
		{
			CPU_SET_DMASTALL(DMAC_MFIFO_GIF, gifUnit.Path3Masked() || !gifUnit.CanDoPath3());
			return;
		}
	}

	if (!gifch.chcr.STR)
		return;

	if (spr0ch.madr == gifch.tadr || (gif.gifstate & GIF_STATE_EMPTY))
	{
		gif.gifstate = GIF_STATE_EMPTY; // In case of madr = tadr we need to set it
		FireMFIFOEmpty();

		if (gifch.qwc > 0 || !gif.gspath3done)
		{
			CPU_SET_DMASTALL(DMAC_MFIFO_GIF, true);
			return;
		}
	}

	if (gifch.qwc > 0 || !gif.gspath3done)
	{
		mfifoGIFtransfer();
		CPU_SET_DMASTALL(DMAC_MFIFO_GIF, gifUnit.Path3Masked() || !gifUnit.CanDoPath3());
		return;
	}

	gif.gscycles = 0;

	gifch.chcr.STR = false;
	gif.gifstate = GIF_STATE_READY;
	gifRegs.stat.FQC = gif_fifo.fifoSize;
	CalculateFIFOCSR();
	hwDmacIrq(DMAC_GIF);
	CPU_SET_DMASTALL(DMAC_MFIFO_GIF, false);
	if (gif_fifo.fifoSize)
		GifDMAInt(8 * BIAS);
	DMA_LOG("GIF MFIFO DMA End");
}

bool SaveStateBase::gifDmaFreeze()
{
	// Note: mfifocycles is not a persistent var, so no need to save it here.
	if (!FreezeTag("GIFdma"))
		return false;

	Freeze(gif);
	Freeze(gif_fifo);

	return IsOkay();
}
