449 lines
14 KiB
C++
449 lines
14 KiB
C++
// Copyright (C) 2003-2009 Dolphin Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0.
|
|
|
|
// This program 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 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official SVN repository and contact information can be found at
|
|
// http://code.google.com/p/dolphin-emu/
|
|
|
|
#include "Common.h"
|
|
#include "pluginspecs_video.h"
|
|
|
|
#include "CommandProcessor.h"
|
|
#include "OpcodeDecoder.h"
|
|
#include "main.h"
|
|
#include "ChunkFile.h"
|
|
#include "MathUtil.h"
|
|
|
|
|
|
u8* g_pVideoData; // data reader uses this as the read pointer
|
|
|
|
|
|
namespace CommandProcessor
|
|
{
|
|
|
|
enum
|
|
{
|
|
GATHER_PIPE_SIZE = 32,
|
|
INT_CAUSE_CP = 0x800
|
|
};
|
|
|
|
// STATE_TO_SAVE
|
|
// variables
|
|
|
|
const int commandBufferSize = 4 * 1024;
|
|
const int commandBufferCopySize = 32;
|
|
const int maxCommandBufferWrite = commandBufferSize - commandBufferCopySize;
|
|
u8 commandBuffer[commandBufferSize];
|
|
u32 readPos;
|
|
u32 writePos;
|
|
bool interruptSet;
|
|
int et_UpdateInterrupts;
|
|
|
|
CPReg cpreg; // shared between gfx and emulator thread
|
|
|
|
|
|
void DoState(PointerWrap &p)
|
|
{
|
|
p.Do(cpreg);
|
|
}
|
|
|
|
// does it matter that there is no synchronization between threads during writes?
|
|
inline void WriteLow (u32& _reg, u16 lowbits) {_reg = (_reg & 0xFFFF0000) | lowbits;}
|
|
inline void WriteHigh(u32& _reg, u16 highbits) {_reg = (_reg & 0x0000FFFF) | ((u32)highbits << 16);}
|
|
|
|
inline u16 ReadLow (u32 _reg) {return (u16)(_reg & 0xFFFF);}
|
|
inline u16 ReadHigh (u32 _reg) {return (u16)(_reg >> 16);}
|
|
|
|
|
|
void UpdateInterrupts_Wrapper(u64 userdata, int cyclesLate)
|
|
{
|
|
UpdateInterrupts();
|
|
}
|
|
|
|
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 = g_VideoInitialize.pRegisterEvent("UpdateInterrupts", UpdateInterrupts_Wrapper);
|
|
|
|
// internal buffer position
|
|
readPos = 0;
|
|
writePos = 0;
|
|
|
|
interruptSet = false;
|
|
|
|
g_pVideoData = 0;
|
|
}
|
|
|
|
void Shutdown()
|
|
{
|
|
}
|
|
|
|
void RunGpu()
|
|
{
|
|
if (!g_VideoInitialize.bOnThread)
|
|
{
|
|
// We are going to do FP math on the main thread so have to save the current state
|
|
SaveSSEState();
|
|
LoadDefaultSSEState();
|
|
|
|
// run the opcode decoder
|
|
do {
|
|
RunBuffer();
|
|
} while (cpreg.ctrl.GPReadEnable && !cpreg.status.Breakpoint && cpreg.rwdistance >= commandBufferCopySize);
|
|
|
|
LoadSSEState();
|
|
}
|
|
}
|
|
|
|
void Read16(u16& _rReturnValue, const u32 _Address)
|
|
{
|
|
DEBUG_LOG(COMMANDPROCESSOR, "(r): 0x%08x", _Address);
|
|
|
|
u32 regAddr = (_Address & 0xFFF) >> 1;
|
|
if (regAddr < 0x20)
|
|
_rReturnValue = ((u16*)&cpreg)[regAddr];
|
|
else
|
|
_rReturnValue = 0;
|
|
}
|
|
|
|
void Write16(const u16 _Value, const u32 _Address)
|
|
{
|
|
INFO_LOG(COMMANDPROCESSOR, "(write16): 0x%04x @ 0x%08x",_Value,_Address);
|
|
|
|
switch (_Address & 0xFFF)
|
|
{
|
|
case STATUS_REGISTER:
|
|
{
|
|
UCPStatusReg tmpStatus(_Value);
|
|
|
|
if (cpreg.status.Breakpoint != tmpStatus.Breakpoint)
|
|
INFO_LOG(COMMANDPROCESSOR,"Set breakpoint status by writing to STATUS_REGISTER");
|
|
|
|
cpreg.status.Hex = _Value;
|
|
|
|
INFO_LOG(COMMANDPROCESSOR,"\t write to STATUS_REGISTER : %04x", _Value);
|
|
|
|
UpdateInterrupts();
|
|
}
|
|
break;
|
|
|
|
case CTRL_REGISTER:
|
|
{
|
|
cpreg.ctrl.Hex = _Value;
|
|
|
|
// clear breakpoint if BPEnable and CPIntEnable are 0
|
|
if (!cpreg.ctrl.BPEnable) {
|
|
if (!cpreg.ctrl.BreakPointIntEnable) {
|
|
cpreg.status.Breakpoint = 0;
|
|
}
|
|
}
|
|
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to CTRL_REGISTER : %04x", _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR, "\t GPREAD %s | CPULINK %s | BP %s || BPIntEnable %s | OvF %s | UndF %s"
|
|
, cpreg.ctrl.GPReadEnable ? "ON" : "OFF"
|
|
, cpreg.ctrl.GPLinkEnable ? "ON" : "OFF"
|
|
, cpreg.ctrl.BPEnable ? "ON" : "OFF"
|
|
, cpreg.ctrl.BreakPointIntEnable ? "ON" : "OFF"
|
|
, cpreg.ctrl.FifoOverflowIntEnable ? "ON" : "OFF"
|
|
, cpreg.ctrl.FifoUnderflowIntEnable ? "ON" : "OFF"
|
|
);
|
|
|
|
UpdateInterrupts();
|
|
|
|
}
|
|
break;
|
|
|
|
case CLEAR_REGISTER:
|
|
{
|
|
UCPClearReg tmpClear(_Value);
|
|
|
|
if (tmpClear.ClearFifoOverflow)
|
|
cpreg.status.OverflowHiWatermark = 0;
|
|
if (tmpClear.ClearFifoUnderflow)
|
|
cpreg.status.UnderflowLoWatermark = 0;
|
|
|
|
INFO_LOG(COMMANDPROCESSOR,"\t write to CLEAR_REGISTER : %04x",_Value);
|
|
|
|
UpdateInterrupts();
|
|
}
|
|
break;
|
|
|
|
// Fifo Registers
|
|
case FIFO_TOKEN_REGISTER:
|
|
cpreg.token = _Value;
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_TOKEN_REGISTER : %04x", _Value);
|
|
break;
|
|
|
|
case FIFO_BASE_LO:
|
|
WriteLow ((u32 &)cpreg.fifobase, _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_BASE_LO. FIFO base is : %08x", cpreg.fifobase);
|
|
break;
|
|
case FIFO_BASE_HI:
|
|
WriteHigh((u32 &)cpreg.fifobase, _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_BASE_HI. FIFO base is : %08x", cpreg.fifobase);
|
|
break;
|
|
case FIFO_END_LO:
|
|
WriteLow ((u32 &)cpreg.fifoend, _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_END_LO. FIFO end is : %08x", cpreg.fifoend);
|
|
break;
|
|
case FIFO_END_HI:
|
|
WriteHigh((u32 &)cpreg.fifoend, _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_END_HI. FIFO end is : %08x", cpreg.fifoend);
|
|
break;
|
|
|
|
case FIFO_WRITE_POINTER_LO:
|
|
WriteLow ((u32 &)cpreg.writeptr, _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_WRITE_POINTER_LO. write ptr is : %08x", cpreg.writeptr);
|
|
break;
|
|
case FIFO_WRITE_POINTER_HI:
|
|
WriteHigh ((u32 &)cpreg.writeptr, _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_WRITE_POINTER_HI. write ptr is : %08x", cpreg.writeptr);
|
|
break;
|
|
case FIFO_READ_POINTER_LO:
|
|
WriteLow ((u32 &)cpreg.readptr, _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_READ_POINTER_LO. read ptr is : %08x", cpreg.readptr);
|
|
break;
|
|
case FIFO_READ_POINTER_HI:
|
|
WriteHigh ((u32 &)cpreg.readptr, _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_READ_POINTER_HI. read ptr is : %08x", cpreg.readptr);
|
|
break;
|
|
|
|
case FIFO_HI_WATERMARK_LO:
|
|
WriteLow ((u32 &)cpreg.hiwatermark, _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_HI_WATERMARK_LO. hiwatermark is : %08x", cpreg.hiwatermark);
|
|
break;
|
|
case FIFO_HI_WATERMARK_HI:
|
|
WriteHigh ((u32 &)cpreg.hiwatermark, _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_HI_WATERMARK_HI. hiwatermark is : %08x", cpreg.hiwatermark);
|
|
break;
|
|
case FIFO_LO_WATERMARK_LO:
|
|
WriteLow ((u32 &)cpreg.lowatermark, _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_LO_WATERMARK_LO. lowatermark is : %08x", cpreg.lowatermark);
|
|
break;
|
|
case FIFO_LO_WATERMARK_HI:
|
|
WriteHigh ((u32 &)cpreg.lowatermark, _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_LO_WATERMARK_HI. lowatermark is : %08x", cpreg.lowatermark);
|
|
break;
|
|
|
|
case FIFO_BP_LO:
|
|
WriteLow ((u32 &)cpreg.breakpt, _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_BP_LO. breakpt is : %08x", cpreg.breakpt);
|
|
break;
|
|
case FIFO_BP_HI:
|
|
WriteHigh ((u32 &)cpreg.breakpt, _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_BP_HI. breakpt is : %08x", cpreg.breakpt);
|
|
break;
|
|
|
|
// Super monkey try to overwrite CPReadWriteDistance by an old saved RWD value. Which is lame for us.
|
|
// hack: We have to force CPU to think fifo is alway empty and on idle.
|
|
// When we fall here CPReadWriteDistance should be always null and the game should always want to overwrite it by 0.
|
|
// So, we can skip it.
|
|
case FIFO_RW_DISTANCE_LO:
|
|
WriteLow ((u32 &)cpreg.rwdistance, _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_RW_DISTANCE_LO. rwdistance is : %08x", cpreg.rwdistance);
|
|
break;
|
|
case FIFO_RW_DISTANCE_HI:
|
|
WriteHigh ((u32 &)cpreg.rwdistance, _Value);
|
|
DEBUG_LOG(COMMANDPROCESSOR,"\t write to FIFO_RW_DISTANCE_HI. rwdistance is : %08x", cpreg.rwdistance);
|
|
break;
|
|
}
|
|
|
|
RunGpu();
|
|
}
|
|
|
|
void Read32(u32& _rReturnValue, const u32 _Address)
|
|
{
|
|
_rReturnValue = 0;
|
|
_dbg_assert_msg_(COMMANDPROCESSOR, 0, "Read32 from CommandProccessor at 0x%08x", _Address);
|
|
}
|
|
|
|
void Write32(const u32 _Data, const u32 _Address)
|
|
{
|
|
_dbg_assert_msg_(COMMANDPROCESSOR, 0, "Write32 at CommandProccessor at 0x%08x", _Address);
|
|
}
|
|
|
|
void STACKALIGN GatherPipeBursted()
|
|
{
|
|
if (cpreg.ctrl.GPLinkEnable)
|
|
{
|
|
cpreg.writeptr += GATHER_PIPE_SIZE;
|
|
if (cpreg.writeptr >= (cpreg.fifoend & 0xFFFFFFE0))
|
|
cpreg.writeptr = cpreg.fifobase;
|
|
|
|
// the read/write pointers will be managed in RunGpu which will read the current fifo and
|
|
// send as much data as is available and the plugin can take
|
|
// this will have the cost of copying data to the plugin fifo buffer in the main thread
|
|
}
|
|
|
|
RunGpu();
|
|
}
|
|
|
|
void UpdateInterrupts()
|
|
{
|
|
bool bpInt = cpreg.status.Breakpoint && cpreg.ctrl.BreakPointIntEnable;
|
|
bool ovfInt = cpreg.status.OverflowHiWatermark && cpreg.ctrl.FifoOverflowIntEnable;
|
|
bool undfInt = cpreg.status.UnderflowLoWatermark && cpreg.ctrl.FifoUnderflowIntEnable;
|
|
|
|
DEBUG_LOG(COMMANDPROCESSOR, "\tUpdate Interrupts");
|
|
DEBUG_LOG(COMMANDPROCESSOR, "\tBreakPointIntEnable %s | BP %s | OvF %s | UndF %s"
|
|
, cpreg.ctrl.BreakPointIntEnable ? "ON" : "OFF"
|
|
, cpreg.ctrl.BPEnable ? "ON" : "OFF"
|
|
, cpreg.ctrl.FifoOverflowIntEnable ? "ON" : "OFF"
|
|
, cpreg.ctrl.FifoUnderflowIntEnable ? "ON" : "OFF"
|
|
);
|
|
|
|
interruptSet = bpInt || ovfInt || undfInt;
|
|
|
|
if (interruptSet)
|
|
{
|
|
DEBUG_LOG(COMMANDPROCESSOR,"Interrupt set");
|
|
g_VideoInitialize.pSetInterrupt(INT_CAUSE_CP, true);
|
|
}
|
|
else
|
|
{
|
|
DEBUG_LOG(COMMANDPROCESSOR,"Interrupt cleared");
|
|
g_VideoInitialize.pSetInterrupt(INT_CAUSE_CP, false);
|
|
}
|
|
}
|
|
|
|
void UpdateInterruptsFromVideoPlugin()
|
|
{
|
|
g_VideoInitialize.pScheduleEvent_Threadsafe(0, et_UpdateInterrupts, 0);
|
|
}
|
|
|
|
void ReadFifo()
|
|
{
|
|
cpreg.status.ReadIdle = 0;
|
|
|
|
// update rwdistance
|
|
u32 writePtr = cpreg.writeptr;
|
|
if (cpreg.readptr <= writePtr)
|
|
cpreg.rwdistance = writePtr - cpreg.readptr;
|
|
else
|
|
cpreg.rwdistance = ((cpreg.fifoend & 0xFFFFFFE0) - cpreg.fifobase) - (cpreg.readptr - writePtr);
|
|
|
|
// overflow check
|
|
cpreg.status.OverflowHiWatermark = cpreg.rwdistance < cpreg.hiwatermark?0:1;
|
|
|
|
// read from fifo
|
|
u8 *ptr = g_VideoInitialize.pGetMemoryPointer(cpreg.readptr);
|
|
|
|
u32 readptr = cpreg.readptr;
|
|
u32 distance = cpreg.rwdistance;
|
|
|
|
while (distance >= commandBufferCopySize && !cpreg.status.Breakpoint && writePos < maxCommandBufferWrite)
|
|
{
|
|
// check for breakpoint
|
|
// todo - check if this is precise enough
|
|
if (cpreg.ctrl.BPEnable && (cpreg.breakpt & 0xFFFFFFE0) == (readptr & 0xFFFFFFE0))
|
|
{
|
|
cpreg.status.Breakpoint = 1;
|
|
DEBUG_LOG(VIDEO,"Hit breakpoint at %x", readptr);
|
|
}
|
|
else
|
|
{
|
|
// copy to buffer
|
|
memcpy(&commandBuffer[writePos], ptr, commandBufferCopySize);
|
|
writePos += commandBufferCopySize;
|
|
ptr += commandBufferCopySize;
|
|
readptr += commandBufferCopySize;
|
|
distance -= commandBufferCopySize;
|
|
}
|
|
|
|
if (readptr >= (cpreg.fifoend & 0xFFFFFFE0))
|
|
{
|
|
readptr = cpreg.fifobase;
|
|
ptr = g_VideoInitialize.pGetMemoryPointer(readptr);
|
|
}
|
|
}
|
|
|
|
// lock read pointer until rw distance is updated?
|
|
cpreg.readptr = readptr;
|
|
cpreg.rwdistance = distance;
|
|
|
|
// underflow check
|
|
cpreg.status.UnderflowLoWatermark = cpreg.rwdistance > cpreg.lowatermark?0:1;
|
|
|
|
cpreg.status.ReadIdle = 1;
|
|
|
|
bool bpInt = cpreg.status.Breakpoint && cpreg.ctrl.BreakPointIntEnable;
|
|
bool ovfInt = cpreg.status.OverflowHiWatermark && cpreg.ctrl.FifoOverflowIntEnable;
|
|
bool undfInt = cpreg.status.UnderflowLoWatermark && cpreg.ctrl.FifoUnderflowIntEnable;
|
|
|
|
bool interrupt = bpInt || ovfInt || undfInt;
|
|
|
|
if (interrupt != interruptSet)
|
|
{
|
|
interruptSet = interrupt;
|
|
UpdateInterruptsFromVideoPlugin();
|
|
}
|
|
}
|
|
|
|
bool RunBuffer()
|
|
{
|
|
// fifo is read 32 bytes at a time
|
|
// read fifo data to internal buffer
|
|
if (cpreg.ctrl.GPReadEnable)
|
|
ReadFifo();
|
|
|
|
g_pVideoData = &commandBuffer[readPos];
|
|
|
|
u32 availableBytes = writePos - readPos;
|
|
_dbg_assert_(VIDEO, 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 = g_pVideoData - &commandBuffer[0];
|
|
_dbg_assert_(VIDEO, writePos >= readPos);
|
|
availableBytes = writePos - readPos;
|
|
}
|
|
|
|
cpreg.status.CommandIdle = 1;
|
|
|
|
_dbg_assert_(VIDEO, writePos >= readPos);
|
|
|
|
bool ranDecoder = false;
|
|
|
|
// move data remaing in command buffer
|
|
if (readPos > 0)
|
|
{
|
|
memmove(&commandBuffer[0], &commandBuffer[readPos], availableBytes);
|
|
writePos -= readPos;
|
|
readPos = 0;
|
|
|
|
ranDecoder = true;
|
|
}
|
|
|
|
return ranDecoder;
|
|
}
|
|
|
|
} // end of namespace CommandProcessor
|