/* 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.graphics.RE.externalge; import static jpcsp.graphics.GeCommands.END; import static jpcsp.graphics.GeCommands.FINISH; import static jpcsp.graphics.RE.externalge.NativeUtils.INTR_STAT_END; import static jpcsp.graphics.RE.externalge.NativeUtils.INTR_STAT_FINISH; import static jpcsp.graphics.RE.externalge.NativeUtils.INTR_STAT_SIGNAL; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import jpcsp.Emulator; import jpcsp.Memory; import jpcsp.HLE.kernel.types.PspGeList; import jpcsp.HLE.modules.sceGe_user; import jpcsp.graphics.GeCommands; import org.apache.log4j.Logger; /** * @author gid15 * */ public class CoreThread extends Thread { protected static Logger log = ExternalGE.log; private static CoreThread instance; private volatile boolean exit; private Semaphore sync; private volatile boolean insideRendering; public static CoreThread getInstance() { if (instance == null) { instance = new CoreThread(); instance.setDaemon(true); instance.setName("ExternalGE - Core Thread"); instance.start(); } return instance; } public static void exit() { if (instance != null) { instance.exit = true; instance = null; } } private CoreThread() { sync = new Semaphore(0); } @Override public void run() { boolean doCoreInterpret = false; while (!exit) { PspGeList list = ExternalGE.getCurrentList(); if (list == null) { if (!Emulator.pause && log.isDebugEnabled()) { log.debug(String.format("CoreThread no current list available... waiting")); } waitForSync(100); } else if (doCoreInterpret || list.waitForSync(100)) { setInsideRendering(true); doCoreInterpret = false; NativeUtils.setCoreMadr(list.getPc()); NativeUtils.updateMemoryUnsafeAddr(); if (log.isDebugEnabled()) { log.debug(String.format("CoreThread processing %s", list)); } while (NativeUtils.coreInterpret()) { NativeUtils.updateMemoryUnsafeAddr(); if (log.isDebugEnabled()) { list.setPc(NativeUtils.getCoreMadr()); log.debug(String.format("CoreThread looping %s", list)); } if (ExternalGE.numberRendererThread > 0 && NativeUtils.getRendererIndexCount() > 0) { break; } } list.setPc(NativeUtils.getCoreMadr()); int intrStat = NativeUtils.getCoreIntrStat(); if ((intrStat & INTR_STAT_END) != 0) { if ((intrStat & INTR_STAT_SIGNAL) != 0) { executeCommandSIGNAL(list); } if ((intrStat & INTR_STAT_FINISH) != 0) { executeCommandFINISH(list); } intrStat &= ~(INTR_STAT_END | INTR_STAT_SIGNAL | INTR_STAT_FINISH); NativeUtils.setCoreIntrStat(intrStat); } if (ExternalGE.numberRendererThread > 0 && NativeUtils.getRendererIndexCount() > 0) { ExternalGE.render(); doCoreInterpret = true; } setInsideRendering(false); } } log.info(String.format("CoreThread exited")); } public void sync() { if (sync != null) { sync.release(); } } private 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 log.debug(String.format("CoreThread waitForSync %s", e)); } } return true; } private static int command(int instruction) { return instruction >>> 24; } private void executeCommandFINISH(PspGeList list) { if (log.isDebugEnabled()) { log.debug(String.format("FINISH %s", list)); } list.clearRestart(); list.finishList(); list.pushFinishCallback(list.id, NativeUtils.getCoreCmdArray(GeCommands.FINISH) & 0x00FFFFFF); list.endList(); list.status = sceGe_user.PSP_GE_LIST_DONE; ExternalGE.finishList(list); } private void executeCommandSIGNAL(PspGeList list) { int args = NativeUtils.getCoreCmdArray(GeCommands.SIGNAL) & 0x00FFFFFF; int behavior = (args >> 16) & 0xFF; int signal = args & 0xFFFF; if (log.isDebugEnabled()) { log.debug(String.format("SIGNAL (behavior=%d, signal=0x%X)", behavior, signal)); } switch (behavior) { case sceGe_user.PSP_GE_SIGNAL_SYNC: { // Skip FINISH / END Memory mem = Memory.getInstance(); if (command(mem.read32(list.getPc())) == FINISH) { list.readNextInstruction(); if (command(mem.read32(list.getPc())) == END) { list.readNextInstruction(); } } if (log.isDebugEnabled()) { log.debug(String.format("PSP_GE_SIGNAL_SYNC ignored PC: 0x%08X", list.getPc())); } break; } case sceGe_user.PSP_GE_SIGNAL_CALL: { // Call list using absolute address from SIGNAL + END. int hi16 = signal & 0x0FFF; int lo16 = NativeUtils.getCoreCmdArray(GeCommands.END) & 0xFFFF; int addr = (hi16 << 16) | lo16; int oldPc = list.getPc(); list.callAbsolute(addr); int newPc = list.getPc(); if (log.isDebugEnabled()) { log.debug(String.format("PSP_GE_SIGNAL_CALL old PC: 0x%08X, new PC: 0x%08X", oldPc, newPc)); } break; } case sceGe_user.PSP_GE_SIGNAL_RETURN: { // Return from PSP_GE_SIGNAL_CALL. int oldPc = list.getPc(); list.ret(); int newPc = list.getPc(); if (log.isDebugEnabled()) { log.debug(String.format("PSP_GE_SIGNAL_RETURN old PC: 0x%08X, new PC: 0x%08X", oldPc, newPc)); } break; } case sceGe_user.PSP_GE_SIGNAL_TBP0_REL: case sceGe_user.PSP_GE_SIGNAL_TBP1_REL: case sceGe_user.PSP_GE_SIGNAL_TBP2_REL: case sceGe_user.PSP_GE_SIGNAL_TBP3_REL: case sceGe_user.PSP_GE_SIGNAL_TBP4_REL: case sceGe_user.PSP_GE_SIGNAL_TBP5_REL: case sceGe_user.PSP_GE_SIGNAL_TBP6_REL: case sceGe_user.PSP_GE_SIGNAL_TBP7_REL: { // Overwrite TBPn and TBPw with SIGNAL + END (uses relative address only). int hi16 = signal & 0xFFFF; int end = NativeUtils.getCoreCmdArray(GeCommands.END); int lo16 = end & 0xFFFF; int width = (end >> 16) & 0xFF; int addr = list.getAddressRel((hi16 << 16) | lo16); int tbpValue = (behavior - sceGe_user.PSP_GE_SIGNAL_TBP0_REL + GeCommands.TBP0) << 24 | (addr & 0x00FFFFFF); int tbwValue = (behavior - sceGe_user.PSP_GE_SIGNAL_TBP0_REL + GeCommands.TBW0) << 24 | ((addr >> 8) & 0x00FF0000) | (width & 0xFFFF); NativeUtils.setCoreCmdArray(command(tbpValue), tbpValue); NativeUtils.setCoreCmdArray(command(tbwValue), tbwValue); break; } case sceGe_user.PSP_GE_SIGNAL_TBP0_REL_OFFSET: case sceGe_user.PSP_GE_SIGNAL_TBP1_REL_OFFSET: case sceGe_user.PSP_GE_SIGNAL_TBP2_REL_OFFSET: case sceGe_user.PSP_GE_SIGNAL_TBP3_REL_OFFSET: case sceGe_user.PSP_GE_SIGNAL_TBP4_REL_OFFSET: case sceGe_user.PSP_GE_SIGNAL_TBP5_REL_OFFSET: case sceGe_user.PSP_GE_SIGNAL_TBP6_REL_OFFSET: case sceGe_user.PSP_GE_SIGNAL_TBP7_REL_OFFSET: { // Overwrite TBPn and TBPw with SIGNAL + END (uses relative address with offset). int hi16 = signal & 0xFFFF; // Read & skip END int end = NativeUtils.getCoreCmdArray(GeCommands.END); int lo16 = end & 0xFFFF; int width = (end >> 16) & 0xFF; int addr = list.getAddressRelOffset((hi16 << 16) | lo16); int tbpValue = (behavior - sceGe_user.PSP_GE_SIGNAL_TBP0_REL + GeCommands.TBP0) << 24 | (addr & 0x00FFFFFF); int tbwValue = (behavior - sceGe_user.PSP_GE_SIGNAL_TBP0_REL + GeCommands.TBW0) << 24 | ((addr >> 8) & 0x00FF0000) | (width & 0xFFFF); NativeUtils.setCoreCmdArray(command(tbpValue), tbpValue); NativeUtils.setCoreCmdArray(command(tbwValue), tbwValue); break; } case sceGe_user.PSP_GE_SIGNAL_HANDLER_SUSPEND: case sceGe_user.PSP_GE_SIGNAL_HANDLER_CONTINUE: case sceGe_user.PSP_GE_SIGNAL_HANDLER_PAUSE: { list.clearRestart(); list.pushSignalCallback(list.id, behavior, signal); list.endList(); list.status = sceGe_user.PSP_GE_LIST_END_REACHED; break; } default: { if (log.isInfoEnabled()) { log.warn(String.format("SIGNAL (behavior=%d, signal=0x%X) unknown behavior at 0x%08X", behavior, signal, list.getPc() - 4)); } } } if (list.isDrawing()) { list.sync(); NativeUtils.setCoreCtrlActive(); } } public boolean isInsideRendering() { return insideRendering; } public void setInsideRendering(boolean insideRendering) { this.insideRendering = insideRendering; } }