/*
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.HLE.modules.sceGe_user.PSP_GE_MATRIX_PROJECTION;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import jpcsp.Emulator;
import jpcsp.Memory;
import jpcsp.State;
import jpcsp.HLE.Modules;
import jpcsp.HLE.kernel.types.PspGeList;
import jpcsp.HLE.kernel.types.SceKernelErrors;
import jpcsp.HLE.modules.sceDisplay;
import jpcsp.HLE.modules.sceGe_user;
import jpcsp.graphics.capture.CaptureManager;
import jpcsp.settings.AbstractBoolSettingsListener;
import jpcsp.settings.Settings;
import jpcsp.util.DurationStatistics;
import jpcsp.util.Utilities;
/**
* @author gid15
*
*/
public class ExternalGE {
public static final int numberRendererThread = 4;
public static boolean activateWhenAvailable = false;
public static final boolean useUnsafe = false;
public static Logger log = Logger.getLogger("externalge");
private static ConcurrentLinkedQueue<PspGeList> drawListQueue;
private static volatile PspGeList currentList;
private static RendererThread[] rendererThreads;
private static Semaphore rendererThreadsDone;
private static Level logLevel;
private static SetLogLevelThread setLogLevelThread;
private static int screenScale = 1;
private static Object screenScaleLock = new Object();
private static ExternalGESettingsListerner externalGESettingsListerner;
private static class SetLogLevelThread extends Thread {
private volatile boolean exit;
public void exit() {
exit = true;
}
@Override
public void run() {
while (!exit) {
NativeUtils.setLogLevel();
Utilities.sleep(100);
}
}
}
private static class ExternalGESettingsListerner extends AbstractBoolSettingsListener {
@Override
protected void settingsValueChanged(boolean value) {
activateWhenAvailable = value;
init();
}
}
private static void activate() {
drawListQueue = new ConcurrentLinkedQueue<PspGeList>();
setLogLevelThread = new SetLogLevelThread();
setLogLevelThread.setName("ExternelGE Set Log Level Thread");
setLogLevelThread.setDaemon(true);
setLogLevelThread.start();
if (numberRendererThread > 0) {
rendererThreads = new RendererThread[numberRendererThread];
int[] lineMasks = new int[numberRendererThread];
switch (numberRendererThread) {
case 1:
lineMasks[0] = 0xFFFFFFFF;
break;
case 2:
lineMasks[0] = 0xFF00FF00;
lineMasks[1] = 0x00FF00FF;
break;
case 3:
lineMasks[0] = 0xF801F001;
lineMasks[1] = 0x07C00F80;
lineMasks[3] = 0x003E007E;
break;
case 4:
case 5:
case 6:
case 7:
lineMasks[0] = 0xFF000000;
lineMasks[1] = 0x00FF0000;
lineMasks[2] = 0x0000FF00;
lineMasks[3] = 0x000000FF;
break;
case 8:
default:
lineMasks[0] = 0xC000C000;
lineMasks[1] = 0x30003000;
lineMasks[2] = 0x0C000C00;
lineMasks[3] = 0x03000300;
lineMasks[4] = 0x00C000C0;
lineMasks[5] = 0x00300030;
lineMasks[6] = 0x000C000C;
lineMasks[7] = 0x00030003;
break;
}
int allLineMasks = 0;
for (int i = 0; i < rendererThreads.length; i++) {
int lineMask = lineMasks[i];
rendererThreads[i] = new RendererThread(lineMask);
rendererThreads[i].setName(String.format("Renderer Thread #%d", i));
rendererThreads[i].start();
if ((allLineMasks & lineMask) != 0) {
log.error(String.format("Incorrect line masks for the renderer threads (number=%d)", numberRendererThread));
}
allLineMasks |= lineMask;
}
if (allLineMasks != 0xFFFFFFFF) {
log.error(String.format("Incorrect line masks for the renderer threads (number=%d)", numberRendererThread));
}
rendererThreadsDone = new Semaphore(0);
}
NativeUtils.setRendererAsyncRendering(numberRendererThread > 0);
setScreenScale(sceDisplay.getResizedWidthPow2(1));
synchronized (screenScaleLock) {
NativeUtils.setScreenScale(getScreenScale());
}
// Used by HD Remaster
int maxTextureSize = Settings.getInstance().readInt("maxTextureSize", 512);
int maxTextureSizeLog2 = 31 - Integer.numberOfLeadingZeros(maxTextureSize);
NativeUtils.setMaxTextureSizeLog2(maxTextureSizeLog2);
boolean doubleTexture2DCoords = Settings.getInstance().readBool("doubleTexture2DCoords");
NativeUtils.setDoubleTexture2DCoords(doubleTexture2DCoords);
}
private static void deactivate() {
drawListQueue = null;
if (setLogLevelThread != null) {
setLogLevelThread.exit();
setLogLevelThread = null;
}
CoreThread.exit();
if (rendererThreads != null) {
for (int i = 0; i < rendererThreads.length; i++) {
rendererThreads[i].exit();
}
rendererThreads = null;
}
}
public static void init() {
if (externalGESettingsListerner == null) {
externalGESettingsListerner = new ExternalGESettingsListerner();
Settings.getInstance().registerSettingsListener("ExternalGE", "emu.useExternalSoftwareRenderer", externalGESettingsListerner);
}
if (activateWhenAvailable) {
NativeUtils.init();
if (isAvailable()) {
activate();
}
} else {
deactivate();
}
}
public static void exit() {
if (externalGESettingsListerner != null) {
Settings.getInstance().removeSettingsListener("ExternalGE");
externalGESettingsListerner = null;
}
if (isActive()) {
NativeUtils.exit();
NativeCallbacks.exit();
CoreThread.exit();
setLogLevelThread.exit();
if (numberRendererThread > 0) {
for (int i = 0; i < rendererThreads.length; i++) {
rendererThreads[i].exit();
}
}
}
}
public static boolean isActive() {
return activateWhenAvailable && isAvailable();
}
public static boolean isAvailable() {
return NativeUtils.isAvailable();
}
public static void startList(PspGeList list) {
if (list == null) {
return;
}
synchronized (drawListQueue) {
if (currentList == null) {
if (State.captureGeNextFrame) {
State.captureGeNextFrame = false;
CaptureManager.captureInProgress = true;
NativeUtils.setDumpFrames(true);
NativeUtils.setDumpTextures(true);
logLevel = log.getLevel();
log.setLevel(Level.TRACE);
}
// Save the context at the beginning of the list processing to the given address (used by sceGu).
if (list.hasSaveContextAddr()) {
saveContext(list.getSaveContextAddr());
}
list.status = sceGe_user.PSP_GE_LIST_DRAWING;
NativeUtils.setLogLevel();
NativeUtils.setCoreSadr(list.getStallAddr());
NativeUtils.setCoreCtrlActive();
synchronized (screenScaleLock) {
// Update the screen scale only at the start of a new list
NativeUtils.setScreenScale(getScreenScale());
}
currentList = list;
CoreThread.getInstance().sync();
} else {
drawListQueue.add(list);
}
}
}
public static void startListHead(PspGeList list) {
if (list == null) {
return;
}
if (currentList == null) {
startList(list);
} else {
// The ConcurrentLinkedQueue type doesn't allow adding
// objects directly at the head of the queue.
// This function creates a new array using the given list as it's head
// and constructs a new ConcurrentLinkedQueue based on it.
// The actual drawListQueue is then replaced by this new one.
int arraySize = drawListQueue.size();
if (arraySize > 0) {
PspGeList[] array = drawListQueue.toArray(new PspGeList[arraySize]);
ConcurrentLinkedQueue<PspGeList> newQueue = new ConcurrentLinkedQueue<PspGeList>();
PspGeList[] newArray = new PspGeList[arraySize + 1];
newArray[0] = list;
for (int i = 0; i < arraySize; i++) {
newArray[i + 1] = array[i];
newQueue.add(newArray[i]);
}
drawListQueue = newQueue;
} else { // If the queue is empty.
drawListQueue.add(list);
}
}
}
public static void onStallAddrUpdated(PspGeList list) {
if (isAvailable() && DurationStatistics.collectStatistics) {
NativeUtils.stopEvent(NativeUtils.EVENT_GE_UPDATE_STALL_ADDR);
}
if (isActive()) {
if (list == null) {
return;
}
if (list == currentList) {
NativeUtils.setCoreSadr(list.getStallAddr());
CoreThread.getInstance().sync();
}
}
}
public static void onRestartList(PspGeList list) {
if (isActive()) {
if (list == null || list.isFinished()) {
return;
}
synchronized (drawListQueue) {
if (list == currentList) {
list.status = sceGe_user.PSP_GE_LIST_DRAWING;
NativeUtils.setCoreCtrlActive();
CoreThread.getInstance().sync();
list.sync();
}
}
}
}
public static void finishList(PspGeList list) {
Modules.sceGe_userModule.hleGeListSyncDone(list);
synchronized (drawListQueue) {
if (list == currentList) {
if (CaptureManager.captureInProgress) {
log.setLevel(logLevel);
NativeUtils.setDumpFrames(false);
NativeUtils.setDumpTextures(false);
NativeUtils.setLogLevel();
CaptureManager.captureInProgress = false;
Emulator.PauseEmu();
}
// Restore the context to the state at the beginning of the list processing (used by sceGu).
if (list.hasSaveContextAddr()) {
restoreContext(list.getSaveContextAddr());
}
currentList = null;
} else {
drawListQueue.remove(list);
}
}
if (currentList == null) {
startList(drawListQueue.poll());
}
}
public static PspGeList getLastDrawList() {
PspGeList lastList = null;
synchronized (drawListQueue) {
for (PspGeList list : drawListQueue) {
if (list != null) {
lastList = list;
}
}
if (lastList == null) {
lastList = currentList;
}
}
return lastList;
}
public static PspGeList getFirstDrawList() {
PspGeList firstList;
synchronized (drawListQueue) {
firstList = currentList;
if (firstList == null) {
firstList = drawListQueue.peek();
}
}
return firstList;
}
public static PspGeList getCurrentList() {
return currentList;
}
public static void onGeStartWaitList() {
if (isAvailable() && DurationStatistics.collectStatistics) {
NativeUtils.startEvent(NativeUtils.EVENT_GE_WAIT_FOR_LIST);
}
}
public static void onGeStopWaitList() {
if (isAvailable() && DurationStatistics.collectStatistics) {
NativeUtils.stopEvent(NativeUtils.EVENT_GE_WAIT_FOR_LIST);
}
}
public static void onDisplayStartWaitVblank() {
if (isAvailable() && DurationStatistics.collectStatistics) {
NativeUtils.startEvent(NativeUtils.EVENT_DISPLAY_WAIT_VBLANK);
}
}
public static void onDisplayStopWaitVblank() {
if (isAvailable() && DurationStatistics.collectStatistics) {
NativeUtils.stopEvent(NativeUtils.EVENT_DISPLAY_WAIT_VBLANK);
}
}
public static void onDisplayVblank() {
if (isAvailable() && DurationStatistics.collectStatistics) {
NativeUtils.notifyEvent(NativeUtils.EVENT_DISPLAY_VBLANK);
}
}
public static void onGeStartList(PspGeList list) {
if (isAvailable() && DurationStatistics.collectStatistics) {
NativeUtils.notifyEvent(NativeUtils.EVENT_GE_START_LIST);
}
}
public static void onGeFinishList(PspGeList list) {
if (isAvailable() && DurationStatistics.collectStatistics) {
NativeUtils.notifyEvent(NativeUtils.EVENT_GE_FINISH_LIST);
}
}
public static void render() {
if (log.isDebugEnabled()) {
log.debug(String.format("ExternalGE starting rendering"));
}
for (int i = 0; i < rendererThreads.length; i++) {
rendererThreads[i].sync(rendererThreadsDone);
}
try {
if (log.isDebugEnabled()) {
log.debug(String.format("Waiting for async rendering completion"));
}
rendererThreadsDone.acquire(rendererThreads.length);
} catch (InterruptedException e) {
log.error("render", e);
}
if (log.isDebugEnabled()) {
log.debug(String.format("Async rendering completion"));
}
NativeUtils.rendererTerminate();
if (log.isDebugEnabled()) {
log.debug(String.format("ExternalGE terminating rendering"));
}
}
public static int saveContext(int addr) {
if (NativeUtils.isCoreCtrlActive()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Saving Core context to 0x%08X - Core busy", addr));
}
return -1;
}
if (log.isDebugEnabled()) {
log.debug(String.format("Saving Core context to 0x%08X", addr));
}
NativeUtils.saveCoreContext(addr);
return 0;
}
public static int restoreContext(int addr) {
if (NativeUtils.isCoreCtrlActive()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Restoring Core context from 0x%08X - Core busy", addr));
}
return SceKernelErrors.ERROR_BUSY;
}
if (log.isDebugEnabled()) {
log.debug(String.format("Restoring Core context from 0x%08X", addr));
}
NativeUtils.restoreCoreContext(addr);
return 0;
}
public static int getCmd(int cmd) {
return NativeUtils.getCoreCmdArray(cmd);
}
public static float[] getMatrix(int mtxType) {
int size = 12;
int offset = mtxType * size;
if (mtxType == PSP_GE_MATRIX_PROJECTION) {
// Projection matrix has 16 elements
size = 16;
} else if (mtxType > PSP_GE_MATRIX_PROJECTION) {
// Projection matrix has 4 elements more
offset += 4;
}
float mtx[] = new float[size];
for (int i = 0; i < size; i++) {
mtx[i] = NativeUtils.getCoreMtxArray(offset + i);
}
return mtx;
}
public static int getScreenScale() {
return screenScale;
}
public static void setScreenScale(int screenScale) {
log.info(String.format("Setting screen scale to factor %d", screenScale));
ExternalGE.screenScale = screenScale;
}
public static ByteBuffer getScaledScreen(int address, int bufferWidth, int height, int pixelFormat) {
synchronized (screenScaleLock) {
return NativeUtils.getScaledScreen(address, bufferWidth, height, pixelFormat);
}
}
public static void addVideoTexture(int destinationAddress, int sourceAddress, int length) {
NativeUtils.addVideoTexture(destinationAddress, sourceAddress, length);
}
public static void onGeUserStop() {
synchronized (drawListQueue) {
drawListQueue.clear();
if (currentList != null) {
currentList.sync();
}
currentList = null;
CoreThread.getInstance().sync();
}
}
public static boolean hasDrawList(int listAddr, int stackAddr) {
boolean result = false;
boolean waitAndRetry = false;
synchronized (drawListQueue) {
if (currentList != null && currentList.isInUse(listAddr, stackAddr)) {
result = true;
// The current list has already reached the FINISH command,
// but the list processing is not yet completed.
// Wait a little for the list to complete.
if (currentList.isFinished()) {
waitAndRetry = true;
}
} else {
for (PspGeList list : drawListQueue) {
if (list != null && list.isInUse(listAddr, stackAddr)) {
result = true;
break;
}
}
}
}
if (waitAndRetry) {
// The current list is already finished but its processing is not yet
// completed. Wait a little (100ms) and check again to avoid
// the "can't enqueue duplicate list address" error.
for (int i = 0; i < 100; i++) {
if (log.isDebugEnabled()) {
log.debug(String.format("hasDrawList(0x%08X) waiting on finished list %s", listAddr, currentList));
}
Utilities.sleep(1, 0);
synchronized (drawListQueue) {
if (currentList == null || currentList.list_addr != listAddr) {
result = false;
break;
}
}
}
}
return result;
}
public static boolean isGeAddress(int address) {
return Memory.isVRAM(address);
}
public static boolean isInsideRendering() {
if (CoreThread.getInstance().isInsideRendering()) {
return true;
}
if (currentList == null) {
return false;
}
if (currentList.isStallReached()) {
return false;
}
if (currentList.status == sceGe_user.PSP_GE_LIST_END_REACHED) {
return false;
}
return true;
}
}