/*
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 java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import jpcsp.Allegrex.compiler.RuntimeContext;
import jpcsp.util.DurationStatistics;
import jpcsp.util.NativeCpuInfo;
import jpcsp.util.Utilities;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import sun.misc.Unsafe;
/**
* @author gid15
*
*/
public class NativeUtils {
public static Logger log = ExternalGE.log;
private static boolean isInitialized = false;
private static boolean isAvailable = false;
private static Unsafe unsafe = null;
private static boolean unsafeInitialized = false;
private static long intArrayBaseOffset = 0L;
private static long memoryIntAddress = 0L;
private static Object[] arrayObject = new Object[] { null, 0x123456789ABCDEFL, 0x1111111122222222L };
private static long arrayObjectBaseOffset = 0L;
private static int arrayObjectIndexScale = 0;
private static int addressSize = 0;
private static DurationStatistics coreInterpret = new DurationStatistics("coreInterpret");
public static final int EVENT_GE_START_LIST = 0;
public static final int EVENT_GE_FINISH_LIST = 1;
public static final int EVENT_GE_ENQUEUE_LIST = 2;
public static final int EVENT_GE_UPDATE_STALL_ADDR = 3;
public static final int EVENT_GE_WAIT_FOR_LIST = 4;
public static final int EVENT_DISPLAY_WAIT_VBLANK = 5;
public static final int EVENT_DISPLAY_VBLANK = 6;
public static final int INTR_STAT_SIGNAL = 0x1;
public static final int INTR_STAT_END = 0x2;
public static final int INTR_STAT_FINISH = 0x4;
public static final int CTRL_ACTIVE = 0x1;
public static void init() {
if (!isInitialized) {
List<String> libraries = new LinkedList<String>();
if (NativeCpuInfo.isAvailable()) {
NativeCpuInfo.init();
if (NativeCpuInfo.hasAVX2()) {
libraries.add("software-ge-renderer-AVX2");
}
if (NativeCpuInfo.hasAVX()) {
libraries.add("software-ge-renderer-AVX");
}
if (NativeCpuInfo.hasSSE41()) {
libraries.add("software-ge-renderer-SSE41");
}
if (NativeCpuInfo.hasSSE3()) {
libraries.add("software-ge-renderer-SSE3");
}
if (NativeCpuInfo.hasSSE2()) {
libraries.add("software-ge-renderer-SSE2");
}
}
libraries.add("software-ge-renderer");
boolean libraryExisting = false;
// Search for an available library in preference order
for (String library : libraries) {
if (Utilities.isSystemLibraryExisting(library)) {
libraryExisting = true;
try {
System.loadLibrary(library);
RuntimeContext.updateMemory();
initNative();
log.info(String.format("Loaded %s library", library));
isAvailable = true;
} catch (UnsatisfiedLinkError e) {
log.error(String.format("Could not load external software library %s: %s", library, e));
isAvailable = false;
}
break;
}
}
if (!libraryExisting) {
log.error(String.format("Missing external software library"));
}
isInitialized = true;
}
}
public static void exit() {
if (DurationStatistics.collectStatistics) {
log.info(coreInterpret.toString());
}
}
public static boolean isAvailable() {
return isAvailable;
}
public static void checkMemoryIntAddress() {
long address = getMemoryUnsafeAddr();
if (address == 0L) {
return;
}
int[] memoryInt = RuntimeContext.getMemoryInt();
memoryInt[0] = 0x12345678;
int x = unsafe.getInt(memoryIntAddress);
if (x != memoryInt[0]) {
log.error(String.format("Non matching value 0x%08X - 0x%08X", x, memoryInt[0]));
} else {
log.info(String.format("Matching value 0x%08X - 0x%08X", x, memoryInt[0]));
}
memoryInt[0] = 0;
}
public static long getMemoryUnsafeAddr() {
if (!ExternalGE.useUnsafe || !RuntimeContext.hasMemoryInt()) {
return 0L;
}
if (!unsafeInitialized) {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
if (f != null) {
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
intArrayBaseOffset = unsafe.arrayBaseOffset(RuntimeContext.getMemoryInt().getClass());
arrayObjectBaseOffset = unsafe.arrayBaseOffset(arrayObject.getClass());
arrayObjectIndexScale = unsafe.arrayIndexScale(arrayObject.getClass());
addressSize = unsafe.addressSize();
if (log.isInfoEnabled()) {
log.info(String.format("Unsafe address information: addressSize=%d, arrayBaseOffset=%d, indexScale=%d", addressSize, arrayObjectBaseOffset, arrayObjectIndexScale));
}
if (addressSize != 4 && addressSize != 8) {
log.error(String.format("Unknown addressSize=%d", addressSize));
}
if (arrayObjectIndexScale != 4 && arrayObjectIndexScale != 8) {
log.error(String.format("Unknown addressSize=%d, indexScale=%d", addressSize, arrayObjectIndexScale));
}
if (arrayObjectIndexScale > addressSize) {
log.error(String.format("Unknown addressSize=%d, indexScale=%d", addressSize, arrayObjectIndexScale));
}
}
} catch (NoSuchFieldException e) {
log.error("getMemoryUnsafeAddr", e);
} catch (SecurityException e) {
log.error("getMemoryUnsafeAddr", e);
} catch (IllegalArgumentException e) {
log.error("getMemoryUnsafeAddr", e);
} catch (IllegalAccessException e) {
log.error("getMemoryUnsafeAddr", e);
}
unsafeInitialized = true;
}
if (unsafe == null) {
return 0L;
}
arrayObject[0] = RuntimeContext.getMemoryInt();
long address = 0L;
if (addressSize == 4) {
address = unsafe.getInt(arrayObject, arrayObjectBaseOffset);
address &= 0xFFFFFFFFL;
} else if (addressSize == 8) {
if (arrayObjectIndexScale == 8) {
// The JVM is running with the following option disabled:
// -XX:-UseCompressedOops
// Object addresses are stored as 64-bit values.
address = unsafe.getLong(arrayObject, arrayObjectBaseOffset);
} else if (arrayObjectIndexScale == 4) {
// The JVM is running with the following option enabled
// -XX:+UseCompressedOops
// Object addresses are stored as compressed 32-bit values (shifted by 3).
address = unsafe.getInt(arrayObject, arrayObjectBaseOffset) & 0xFFFFFFFFL;
address <<= 3;
}
}
if (address == 0L) {
return address;
}
if (false) {
// Perform a self-test
int[] memoryInt = RuntimeContext.getMemoryInt();
int testValue = 0x12345678;
int originalValue = memoryInt[0];
memoryInt[0] = testValue;
int resultValue = unsafe.getInt(address + intArrayBaseOffset);
memoryInt[0] = originalValue;
if (resultValue != testValue) {
log.error(String.format("Unsafe self-test failed: 0x%08X != 0x%08X", testValue, resultValue));
}
}
return address + intArrayBaseOffset;
}
public static void updateMemoryUnsafeAddr() {
long address = getMemoryUnsafeAddr();
if (memoryIntAddress != address) {
if (log.isDebugEnabled()) {
log.debug(String.format("memoryInt at 0x%X", address));
}
if (log.isInfoEnabled() && memoryIntAddress != 0L) {
log.info(String.format("memoryInt MOVED from 0x%X to 0x%X", memoryIntAddress, address));
}
memoryIntAddress = address;
setMemoryUnsafeAddr(memoryIntAddress);
}
}
public static void setLogLevel() {
setLogLevel(log);
}
public static void setLogLevel(Logger log) {
int level = 7; // E_DEFAULT
switch (log.getEffectiveLevel().toInt()) {
case Level.ALL_INT:
level = 7; // E_FORCE
break;
case Level.TRACE_INT:
level = 6; // E_TRACE
break;
case Level.DEBUG_INT:
level = 5; // E_DEBUG
break;
case Level.INFO_INT:
level = 4; // E_INFO
break;
case Level.WARN_INT:
level = 3; // E_WARN
break;
case Level.ERROR_INT:
level = 2; // E_ERROR
break;
case Level.FATAL_INT:
level = 1; // E_FATAL
break;
case Level.OFF_INT:
level = 0; // E_OFF
break;
}
setLogLevel(level);
}
public static boolean coreInterpretWithStatistics() {
coreInterpret.start();
boolean result = coreInterpret();
coreInterpret.end();
return result;
}
public static boolean isCoreCtrlActive() {
return (getCoreCtrl() & CTRL_ACTIVE) != 0;
}
public static native int initNative();
public static native boolean coreInterpret();
public static native int getCoreCtrl();
public static native void setCoreCtrl(int ctrl);
public static native void setCoreCtrlActive();
public static native int getCoreStat();
public static native void setCoreStat(int stat);
public static native int getCoreMadr();
public static native void setCoreMadr(int madr);
public static native int getCoreSadr();
public static native void setCoreSadr(int sadr);
public static native int getCoreIntrStat();
public static native void setCoreIntrStat(int intrStat);
public static native int getCoreCmdArray(int cmd);
public static native void setCoreCmdArray(int cmd, int value);
public static native float getCoreMtxArray(int mtx);
public static native void setCoreMtxArray(int mtx, float value);
public static native void setLogLevel(int level);
public static native void setMemoryUnsafeAddr(long addr);
public static native void startEvent(int event);
public static native void stopEvent(int event);
public static native void notifyEvent(int event);
public static native void setRendererAsyncRendering(boolean asyncRendering);
public static native int getRendererIndexCount();
public static native void rendererRender(int lineMask);
public static native void rendererTerminate();
public static native void setDumpFrames(boolean dumpFrames);
public static native void setDumpTextures(boolean dumpTextures);
public static native void saveCoreContext(int addr);
public static native void restoreCoreContext(int addr);
public static native void setScreenScale(int screenScale);
public static native ByteBuffer getScaledScreen(int address, int bufferWidth, int height, int pixelFormat);
public static native void addVideoTexture(int destinationAddress, int sourceAddress, int length);
public static native void setMaxTextureSizeLog2(int maxTextureSizeLog2);
public static native void setDoubleTexture2DCoords(boolean doubleTexture2DCoords);
public static native void doTests();
}