// 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