/* This file is part of jpcsp. Jpcsp 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, either version 3 of the License, or (at your option) any later version. Jpcsp 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 Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.HLE.kernel.types; import static jpcsp.HLE.modules.sceGe_user.PSP_GE_LIST_CANCEL_DONE; import static jpcsp.HLE.modules.sceGe_user.PSP_GE_LIST_DONE; import static jpcsp.HLE.modules.sceGe_user.PSP_GE_LIST_DRAWING; import static jpcsp.HLE.modules.sceGe_user.PSP_GE_LIST_QUEUED; import static jpcsp.HLE.modules.sceGe_user.PSP_GE_LIST_STALL_REACHED; import static jpcsp.HLE.modules.sceGe_user.PSP_GE_LIST_STRINGS; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import jpcsp.HLE.Modules; import jpcsp.HLE.modules.sceGe_user; import jpcsp.graphics.VideoEngine; import jpcsp.graphics.RE.externalge.ExternalGE; import jpcsp.memory.IMemoryReader; import jpcsp.memory.MemoryReader; import jpcsp.Memory; import jpcsp.MemoryMap; public class PspGeList { private VideoEngine videoEngine; private static final int pcAddressMask = 0xFFFFFFFC & Memory.addressMask; public int list_addr; private int stall_addr; public int cbid; public pspGeListOptParam optParams; private int stackAddr; private int pc; // a stack entry contains the PC and the baseOffset private int[] stack = new int[32*2]; private int stackIndex; public int status; public int id; public List<Integer> blockedThreadIds; // the threads we are blocking private boolean finished; private boolean paused; private boolean ended; private boolean reset; private boolean restarted; private Semaphore sync; // Used for async display private IMemoryReader memoryReader; private int saveContextAddr; private IMemoryReader baseMemoryReader; private int baseMemoryReaderStartAddress; private int baseMemoryReaderEndAddress; public PspGeList(int id) { videoEngine = VideoEngine.getInstance(); this.id = id; blockedThreadIds = new LinkedList<Integer>(); reset(); } private void init() { stackIndex = 0; blockedThreadIds.clear(); finished = true; paused = false; reset = true; ended = true; restarted = false; memoryReader = null; baseMemoryReader = null; baseMemoryReaderStartAddress = 0; baseMemoryReaderEndAddress = 0; pc = 0; saveContextAddr = 0; } public void init(int list_addr, int stall_addr, int cbid, pspGeListOptParam optParams) { init(); list_addr &= pcAddressMask; stall_addr &= pcAddressMask; this.list_addr = list_addr; this.stall_addr = stall_addr; this.cbid = cbid; this.optParams = optParams; if (optParams != null) { stackAddr = optParams.stackAddr; } else { stackAddr = 0; } setPc(list_addr); status = (pc == stall_addr) ? PSP_GE_LIST_STALL_REACHED : PSP_GE_LIST_QUEUED; finished = false; reset = false; ended = false; sync = new Semaphore(0); } public void reset() { status = PSP_GE_LIST_DONE; init(); } public void pushSignalCallback(int listId, int behavior, int signal) { int listPc = getPc(); if (!ExternalGE.isActive()) { // PC address after the END command listPc += 4; } Modules.sceGe_userModule.triggerSignalCallback(cbid, listId, listPc, behavior, signal); } public void pushFinishCallback(int listId, int arg) { int listPc = getPc(); if (!ExternalGE.isActive()) { // PC address after the END command listPc += 4; } Modules.sceGe_userModule.triggerFinishCallback(cbid, listId, listPc, arg); } private void pushStack(int value) { stack[stackIndex++] = value; } private int popStack() { return stack[--stackIndex]; } public int getAddressRel(int argument) { return Memory.normalizeAddress((videoEngine.getBase() | argument)); } public int getAddressRelOffset(int argument) { return Memory.normalizeAddress((videoEngine.getBase() | argument) + videoEngine.getBaseOffset()); } public boolean isStackEmpty() { return stackIndex <= 0; } public void setPc(int pc) { pc &= pcAddressMask; if (this.pc != pc) { int oldPc = this.pc; this.pc = pc; resetMemoryReader(oldPc); } } public int getPc() { return pc; } public void jumpAbsolute(int argument) { setPc(Memory.normalizeAddress(argument)); } public void jumpRelative(int argument) { setPc(getAddressRel(argument)); } public void jumpRelativeOffset(int argument) { setPc(getAddressRelOffset(argument)); } public void callAbsolute(int argument) { pushStack(pc); pushStack(videoEngine.getBaseOffset()); jumpAbsolute(argument); } public void callRelative(int argument) { pushStack(pc); pushStack(videoEngine.getBaseOffset()); jumpRelative(argument); } public void callRelativeOffset(int argument) { pushStack(pc); pushStack(videoEngine.getBaseOffset()); jumpRelativeOffset(argument); } public void ret() { if (!isStackEmpty()) { videoEngine.setBaseOffset(popStack()); setPc(popStack()); } } public void sync() { if (sync != null) { sync.release(); } } public boolean waitForSync(int millis) { while (true) { try { int availablePermits = sync.drainPermits(); if (availablePermits > 0) { break; } if (sync.tryAcquire(millis, TimeUnit.MILLISECONDS)) { break; } return false; } catch (InterruptedException e) { // Ignore exception and retry again sceGe_user.log.debug(String.format("PspGeList waitForSync %s", e)); } } return true; } public void setStallAddr(int stall_addr) { stall_addr &= pcAddressMask; if (this.stall_addr != stall_addr) { this.stall_addr = stall_addr; ExternalGE.onStallAddrUpdated(this); sync(); } } public synchronized void setStallAddr(int stall_addr, IMemoryReader baseMemoryReader, int startAddress, int endAddress) { // Both the stall address and the base memory reader need to be set at the same // time in a synchronized call in order to avoid any race condition // with the GUI thread (VideoEngine). setStallAddr(stall_addr); this.baseMemoryReader = baseMemoryReader; this.baseMemoryReaderStartAddress = startAddress; this.baseMemoryReaderEndAddress = endAddress; resetMemoryReader(pc); } public int getStallAddr() { return stall_addr; } public boolean isStallReached() { return pc == stall_addr && stall_addr != 0; } public boolean hasStallAddr() { return stall_addr != 0; } public boolean isStalledAtStart() { return isStallReached() && pc == list_addr; } public void startList() { paused = false; ExternalGE.onGeStartList(this); if (ExternalGE.isActive()) { ExternalGE.startList(this); } else { videoEngine.pushDrawList(this); } sync(); } public void startListHead() { paused = false; ExternalGE.onGeStartList(this); if (ExternalGE.isActive()) { ExternalGE.startListHead(this); } else { videoEngine.pushDrawListHead(this); } } public void pauseList() { paused = true; } public void restartList() { paused = false; restarted = true; sync(); ExternalGE.onRestartList(this); } public void clearRestart() { restarted = false; } public void clearPaused() { paused = false; } public boolean isRestarted() { return restarted; } public boolean isPaused() { return paused; } public boolean isFinished() { return finished; } public boolean isEnded() { return ended; } public void finishList() { finished = true; ExternalGE.onGeFinishList(this); } public void endList() { if (isFinished()) { ended = true; } else { ended = false; } } public boolean isDone() { return status == PSP_GE_LIST_DONE || status == PSP_GE_LIST_CANCEL_DONE; } public boolean isReset() { return reset; } public boolean isDrawing() { return status == PSP_GE_LIST_DRAWING; } private void resetMemoryReader(int oldPc) { if (pc >= baseMemoryReaderStartAddress && pc < baseMemoryReaderEndAddress) { memoryReader = baseMemoryReader; memoryReader.skip((pc - baseMemoryReader.getCurrentAddress()) >> 2); } else if (memoryReader == null || memoryReader == baseMemoryReader || pc < oldPc) { memoryReader = MemoryReader.getMemoryReader(pc, 4); } else if (oldPc < MemoryMap.START_RAM && pc >= MemoryMap.START_RAM) { // Jumping from VRAM to RAM memoryReader = MemoryReader.getMemoryReader(pc, 4); } else { memoryReader.skip((pc - oldPc) >> 2); } } public synchronized void setMemoryReader(IMemoryReader memoryReader) { this.memoryReader = memoryReader; } public boolean hasBaseMemoryReader() { return baseMemoryReader != null; } public synchronized int readNextInstruction() { pc += 4; return memoryReader.readNext(); } public synchronized int readPreviousInstruction() { memoryReader.skip(-2); int previousInstruction = memoryReader.readNext(); memoryReader.skip(1); return previousInstruction; } public synchronized void undoRead() { undoRead(1); } public synchronized void undoRead(int n) { memoryReader.skip(-n); } public int getSaveContextAddr() { return saveContextAddr; } public void setSaveContextAddr(int saveContextAddr) { this.saveContextAddr = saveContextAddr; } public boolean hasSaveContextAddr() { return saveContextAddr != 0; } public boolean isInUse(int listAddr, int stackAddr) { if (list_addr == listAddr) { return true; } if (stackAddr != 0 && this.stackAddr == stackAddr) { return true; } return false; } @Override public String toString() { return String.format("PspGeList[id=0x%X, status=%s, list=0x%08X, pc=0x%08X, stall=0x%08X, cbid=0x%X, ended=%b, finished=%b, paused=%b, restarted=%b, reset=%b]", id, PSP_GE_LIST_STRINGS[status], list_addr, pc, stall_addr, cbid, ended, finished, paused, restarted, reset); } }