dolphin/Source/Core/VideoBackends/Software/SWCommandProcessor.cpp

373 lines
8.3 KiB
C++

// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
#include "Common/Atomic.h"
#include "Common/ChunkFile.h"
#include "Common/Common.h"
#include "Common/FPURoundMode.h"
#include "Common/MathUtil.h"
#include "Common/Thread.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/HW/Memmap.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/ProcessorInterface.h"
#include "VideoBackends/Software/OpcodeDecoder.h"
#include "VideoBackends/Software/SWCommandProcessor.h"
#include "VideoBackends/Software/VideoBackend.h"
#include "VideoCommon/DataReader.h"
#include "VideoCommon/Fifo.h"
namespace SWCommandProcessor
{
enum
{
GATHER_PIPE_SIZE = 32,
INT_CAUSE_CP = 0x800
};
// STATE_TO_SAVE
// variables
static const int commandBufferSize = 1024 * 1024;
static const int maxCommandBufferWrite = commandBufferSize - GATHER_PIPE_SIZE;
static u8 commandBuffer[commandBufferSize];
static u32 readPos;
static u32 writePos;
static int et_UpdateInterrupts;
static volatile bool interruptSet;
static volatile bool interruptWaiting;
static CPReg cpreg; // shared between gfx and emulator thread
void DoState(PointerWrap &p)
{
p.DoPOD(cpreg);
p.DoArray(commandBuffer, commandBufferSize);
p.Do(readPos);
p.Do(writePos);
p.Do(et_UpdateInterrupts);
p.Do(interruptSet);
p.Do(interruptWaiting);
// Is this right?
p.DoArray(g_pVideoData,writePos);
}
// does it matter that there is no synchronization between threads during writes?
static inline void WriteLow (u32& _reg, u16 lowbits)
{
_reg = (_reg & 0xFFFF0000) | lowbits;
}
static inline void WriteHigh(u32& _reg, u16 highbits)
{
_reg = (_reg & 0x0000FFFF) | ((u32)highbits << 16);
}
static inline u16 ReadLow(u32 _reg)
{
return (u16)(_reg & 0xFFFF);
}
static inline u16 ReadHigh(u32 _reg)
{
return (u16)(_reg >> 16);
}
static void UpdateInterrupts_Wrapper(u64 userdata, int cyclesLate)
{
UpdateInterrupts(userdata);
}
static inline bool AtBreakpoint()
{
return cpreg.ctrl.BPEnable && (cpreg.readptr == cpreg.breakpt);
}
void Init()
{
cpreg.status.Hex = 0;
cpreg.status.CommandIdle = 1;
cpreg.status.ReadIdle = 1;
cpreg.ctrl.Hex = 0;
cpreg.clear.Hex = 0;
cpreg.bboxleft = 0;
cpreg.bboxtop = 0;
cpreg.bboxright = 0;
cpreg.bboxbottom = 0;
cpreg.token = 0;
et_UpdateInterrupts = CoreTiming::RegisterEvent("UpdateInterrupts", UpdateInterrupts_Wrapper);
// internal buffer position
readPos = 0;
writePos = 0;
interruptSet = false;
interruptWaiting = false;
g_pVideoData = nullptr;
g_bSkipCurrentFrame = false;
}
void Shutdown()
{
}
void RunGpu()
{
if (!SConfig::GetInstance().m_LocalCoreStartupParameter.bCPUThread)
{
// We are going to do FP math on the main thread so have to save the current state
FPURoundMode::SaveSIMDState();
FPURoundMode::LoadDefaultSIMDState();
// run the opcode decoder
do
{
RunBuffer();
} while (cpreg.ctrl.GPReadEnable && !AtBreakpoint() && cpreg.readptr != cpreg.writeptr);
FPURoundMode::LoadSIMDState();
}
}
void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
{
// Directly map reads and writes to the cpreg structure.
for (size_t i = 0; i < sizeof (cpreg) / sizeof (u16); ++i)
{
u16* ptr = ((u16*)&cpreg) + i;
mmio->Register(base | (i * 2),
MMIO::DirectRead<u16>(ptr),
MMIO::DirectWrite<u16>(ptr)
);
}
// Bleh. Apparently SWCommandProcessor does not know about regs 0x40 to
// 0x64...
for (size_t i = 0x40; i < 0x64; ++i)
{
mmio->Register(base | i,
MMIO::Constant<u16>(0),
MMIO::Nop<u16>()
);
}
// The low part of MMIO regs for FIFO addresses needs to be aligned to 32
// bytes.
u32 fifo_addr_lo_regs[] = {
CommandProcessor::FIFO_BASE_LO,
CommandProcessor::FIFO_END_LO,
CommandProcessor::FIFO_WRITE_POINTER_LO,
CommandProcessor::FIFO_READ_POINTER_LO,
CommandProcessor::FIFO_BP_LO,
CommandProcessor::FIFO_RW_DISTANCE_LO,
};
for (u32 reg : fifo_addr_lo_regs)
{
mmio->RegisterWrite(base | reg,
MMIO::DirectWrite<u16>(((u16*)&cpreg) + (reg / 2), 0xFFE0)
);
}
// The clear register needs to perform some more complicated operations on
// writes.
mmio->RegisterWrite(base | CommandProcessor::CLEAR_REGISTER,
MMIO::ComplexWrite<u16>([](u32, u16 val) {
UCPClearReg tmpClear(val);
if (tmpClear.ClearFifoOverflow)
cpreg.status.OverflowHiWatermark = 0;
if (tmpClear.ClearFifoUnderflow)
cpreg.status.UnderflowLoWatermark = 0;
})
);
}
void STACKALIGN GatherPipeBursted()
{
if (cpreg.ctrl.GPLinkEnable)
{
DEBUG_LOG(COMMANDPROCESSOR,"\t WGP burst. write thru : %08x", cpreg.writeptr);
if (cpreg.writeptr == cpreg.fifoend)
cpreg.writeptr = cpreg.fifobase;
else
cpreg.writeptr += GATHER_PIPE_SIZE;
Common::AtomicAdd(cpreg.rwdistance, GATHER_PIPE_SIZE);
}
RunGpu();
}
void UpdateInterrupts(u64 userdata)
{
if (userdata)
{
interruptSet = true;
INFO_LOG(COMMANDPROCESSOR,"Interrupt set");
ProcessorInterface::SetInterrupt(INT_CAUSE_CP, true);
}
else
{
interruptSet = false;
INFO_LOG(COMMANDPROCESSOR,"Interrupt cleared");
ProcessorInterface::SetInterrupt(INT_CAUSE_CP, false);
}
interruptWaiting = false;
}
void UpdateInterruptsFromVideoBackend(u64 userdata)
{
CoreTiming::ScheduleEvent_Threadsafe(0, et_UpdateInterrupts, userdata);
}
static void ReadFifo()
{
bool canRead = cpreg.readptr != cpreg.writeptr && writePos < (int)maxCommandBufferWrite;
bool atBreakpoint = AtBreakpoint();
if (canRead && !atBreakpoint)
{
// read from fifo
u8 *ptr = Memory::GetPointer(cpreg.readptr);
int bytesRead = 0;
do
{
// copy to buffer
memcpy(&commandBuffer[writePos], ptr, GATHER_PIPE_SIZE);
writePos += GATHER_PIPE_SIZE;
bytesRead += GATHER_PIPE_SIZE;
if (cpreg.readptr == cpreg.fifoend)
{
cpreg.readptr = cpreg.fifobase;
ptr = Memory::GetPointer(cpreg.readptr);
}
else
{
cpreg.readptr += GATHER_PIPE_SIZE;
ptr += GATHER_PIPE_SIZE;
}
canRead = cpreg.readptr != cpreg.writeptr && writePos < (int)maxCommandBufferWrite;
atBreakpoint = AtBreakpoint();
} while (canRead && !atBreakpoint);
Common::AtomicAdd(cpreg.rwdistance, -bytesRead);
}
}
static void SetStatus()
{
// overflow check
if (cpreg.rwdistance > cpreg.hiwatermark)
cpreg.status.OverflowHiWatermark = 1;
// underflow check
if (cpreg.rwdistance < cpreg.lowatermark)
cpreg.status.UnderflowLoWatermark = 1;
// breakpoint
if (cpreg.ctrl.BPEnable)
{
if (cpreg.breakpt == cpreg.readptr)
{
if (!cpreg.status.Breakpoint)
INFO_LOG(COMMANDPROCESSOR, "Hit breakpoint at %x", cpreg.readptr);
cpreg.status.Breakpoint = 1;
}
}
else
{
if (cpreg.status.Breakpoint)
INFO_LOG(COMMANDPROCESSOR, "Cleared breakpoint at %x", cpreg.readptr);
cpreg.status.Breakpoint = 0;
}
cpreg.status.ReadIdle = cpreg.readptr == cpreg.writeptr;
bool bpInt = cpreg.status.Breakpoint && cpreg.ctrl.BPInt;
bool ovfInt = cpreg.status.OverflowHiWatermark && cpreg.ctrl.FifoOverflowIntEnable;
bool undfInt = cpreg.status.UnderflowLoWatermark && cpreg.ctrl.FifoUnderflowIntEnable;
bool interrupt = bpInt || ovfInt || undfInt;
if (interrupt != interruptSet && !interruptWaiting)
{
u64 userdata = interrupt?1:0;
if (SConfig::GetInstance().m_LocalCoreStartupParameter.bCPUThread)
{
interruptWaiting = true;
SWCommandProcessor::UpdateInterruptsFromVideoBackend(userdata);
}
else
{
SWCommandProcessor::UpdateInterrupts(userdata);
}
}
}
bool RunBuffer()
{
// fifo is read 32 bytes at a time
// read fifo data to internal buffer
if (cpreg.ctrl.GPReadEnable)
ReadFifo();
SetStatus();
_dbg_assert_(COMMANDPROCESSOR, writePos >= readPos);
g_pVideoData = &commandBuffer[readPos];
u32 availableBytes = writePos - readPos;
while (OpcodeDecoder::CommandRunnable(availableBytes))
{
cpreg.status.CommandIdle = 0;
OpcodeDecoder::Run(availableBytes);
// if data was read by the opcode decoder then the video data pointer changed
readPos = (u32)(g_pVideoData - &commandBuffer[0]);
_dbg_assert_(VIDEO, writePos >= readPos);
availableBytes = writePos - readPos;
}
cpreg.status.CommandIdle = 1;
bool ranDecoder = false;
// move data remaining in the command buffer
if (readPos > 0)
{
memmove(&commandBuffer[0], &commandBuffer[readPos], availableBytes);
writePos -= readPos;
readPos = 0;
ranDecoder = true;
}
return ranDecoder;
}
void SetRendering(bool enabled)
{
g_bSkipCurrentFrame = !enabled;
}
} // end of namespace SWCommandProcessor