/*
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;
import static java.lang.Math.max;
import static java.lang.Math.min;
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_END_REACHED;
import static jpcsp.HLE.modules.sceGe_user.PSP_GE_LIST_STALL_REACHED;
import static jpcsp.HLE.modules.sceGe_user.PSP_GE_MATRIX_BONE0;
import static jpcsp.HLE.modules.sceGe_user.PSP_GE_MATRIX_BONE1;
import static jpcsp.HLE.modules.sceGe_user.PSP_GE_MATRIX_BONE2;
import static jpcsp.HLE.modules.sceGe_user.PSP_GE_MATRIX_BONE3;
import static jpcsp.HLE.modules.sceGe_user.PSP_GE_MATRIX_BONE4;
import static jpcsp.HLE.modules.sceGe_user.PSP_GE_MATRIX_BONE5;
import static jpcsp.HLE.modules.sceGe_user.PSP_GE_MATRIX_BONE6;
import static jpcsp.HLE.modules.sceGe_user.PSP_GE_MATRIX_BONE7;
import static jpcsp.HLE.modules.sceGe_user.PSP_GE_MATRIX_PROJECTION;
import static jpcsp.HLE.modules.sceGe_user.PSP_GE_MATRIX_TEXGEN;
import static jpcsp.HLE.modules.sceGe_user.PSP_GE_MATRIX_VIEW;
import static jpcsp.HLE.modules.sceGe_user.PSP_GE_MATRIX_WORLD;
import static jpcsp.graphics.GeCommands.*;
import static jpcsp.util.Utilities.matrixMult;
import static jpcsp.util.Utilities.round4;
import static jpcsp.util.Utilities.vectorMult;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.Semaphore;
import javax.imageio.ImageIO;
import jpcsp.Emulator;
import jpcsp.Memory;
import jpcsp.MemoryMap;
import jpcsp.State;
import jpcsp.Allegrex.compiler.RuntimeContext;
import jpcsp.HLE.Modules;
import jpcsp.HLE.kernel.types.IAction;
import jpcsp.HLE.kernel.types.PspGeList;
import jpcsp.HLE.modules.sceDisplay;
import jpcsp.HLE.modules.sceGe_user;
import jpcsp.graphics.GeContext.EnableDisableFlag;
import jpcsp.graphics.RE.IRenderingEngine;
import jpcsp.graphics.RE.buffer.IREBufferManager;
import jpcsp.graphics.RE.externalge.ExternalGE;
import jpcsp.graphics.RE.software.PixelColor;
import jpcsp.graphics.capture.CaptureImage;
import jpcsp.graphics.capture.CaptureManager;
import jpcsp.graphics.export.IGraphicsExporter;
import jpcsp.graphics.export.WavefrontExporter;
import jpcsp.graphics.textures.GETexture;
import jpcsp.graphics.textures.GETextureManager;
import jpcsp.graphics.textures.Texture;
import jpcsp.graphics.textures.TextureCache;
import jpcsp.hardware.Screen;
import jpcsp.memory.IMemoryReader;
import jpcsp.memory.ImageReader;
import jpcsp.memory.MemoryReader;
import jpcsp.settings.AbstractBoolSettingsListener;
import jpcsp.settings.Settings;
import jpcsp.util.CpuDurationStatistics;
import jpcsp.util.DurationStatistics;
import jpcsp.util.Utilities;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
//
// Ideas for Optimization:
// - compile GE lists (or part of it) into OpenGL display list (glNewList/glCallList).
// For example, immutable subroutines called using CALL could be compiled into a display list.
// A first run of the game using a profiler option could be used to detect which parts
// are immutable. This information could be stored in a file for subsequent runs and
// used as hints for the next runs.
// - Unswizzle textures in shader (is this possible?)
//
public class VideoEngine {
public static final int NUM_LIGHTS = 4;
public static final int SIZEOF_FLOAT = IRenderingEngine.sizeOfType[IRenderingEngine.RE_FLOAT];
public final static String[] psm_names = new String[]{
"PSM_5650",
"PSM_5551",
"PSM_4444",
"PSM_8888",
"PSM_4BIT_INDEXED",
"PSM_8BIT_INDEXED",
"PSM_16BIT_INDEXED",
"PSM_32BIT_INDEXED",
"PSM_DXT1",
"PSM_DXT3",
"PSM_DXT5",
"RE_PIXEL_STORAGE_16BIT_INDEXED_BGR5650",
"RE_PIXEL_STORAGE_16BIT_INDEXED_ABGR5551",
"RE_PIXEL_STORAGE_16BIT_INDEXED_ABGR4444",
"RE_PIXEL_STORAGE_32BIT_INDEXED_ABGR8888",
"RE_DEPTH_COMPONENT",
"RE_STENCIL_INDEX",
"RE_DEPTH_STENCIL"
};
public final static String[] logical_ops_names = new String[]{
"LOP_CLEAR",
"LOP_AND",
"LOP_REVERSE_AND",
"LOP_COPY",
"LOP_INVERTED_AND",
"LOP_NO_OPERATION",
"LOP_EXLUSIVE_OR",
"LOP_OR",
"LOP_NEGATED_OR",
"LOP_EQUIVALENCE",
"LOP_INVERTED",
"LOP_REVERSE_OR",
"LOP_INVERTED_COPY",
"LOP_INVERTED_OR",
"LOP_NEGATED_AND",
"LOP_SET"
};
private static final int[] textureByteAlignmentMapping = {2, 2, 2, 4};
private static final int[] minimumNumberOfVertex = {
1, // PRIM_POINT
2, // PRIM_LINE
2, // PRIM_LINES_STRIPS
3, // PRIM_TRIANGLE
3, // PRIM_TRIANGLE_STRIPS
3, // PRIM_TRIANGLE_FANS
2 // PRIM_SPRITES
};
private static final int[] ditherMatrixValueMapping = {
// value [0..7]
0, 1, 2, 3, 4, 5, 6, 7,
// value [8..F]
-8, -7, -6, -5, -4, -3, -2, -1
};
private static final int[] indexTypes = new int[]{
0,
IRenderingEngine.RE_UNSIGNED_BYTE,
IRenderingEngine.RE_UNSIGNED_SHORT,
IRenderingEngine.RE_UNSIGNED_INT
};
private static VideoEngine instance;
private sceDisplay display;
private IRenderingEngine re;
private GeContext context;
private IREBufferManager bufferManager;
public static Logger log = Logger.getLogger("ge");
public static final boolean useTextureCache = true;
private boolean useVertexCache = false;
private boolean useAsyncVertexCache = true;
public boolean useOptimisticVertexCache = false;
private boolean useTextureAnisotropicFilter = false;
private boolean usexBRZFilter = false;
private boolean disableOptimizedVertexInfoReading = false;
private boolean avoidDrawElementsWithNonZeroIndexOffset = false;
private boolean enableTextureModding = true;
private static GeCommands helper;
private int command;
private int normalArgument;
private int waitForSyncCount;
private VertexInfoReader vertexInfoReader = new VertexInfoReader();
private DurationStatistics statistics = new CpuDurationStatistics("VideoEngine Statistics");
private DurationStatistics vertexStatistics = new CpuDurationStatistics("Vertex");
private DurationStatistics vertexReadingStatistics = new CpuDurationStatistics("Vertex Reading");
private DurationStatistics drawArraysStatistics = new CpuDurationStatistics("glDrawArrays");
private DurationStatistics waitSignalStatistics = new DurationStatistics("Wait for GE Signal completion");
private DurationStatistics waitStallStatistics = new DurationStatistics("Wait on stall");
private DurationStatistics textureCacheLookupStatistics = new CpuDurationStatistics("Lookup in TextureCache");
private DurationStatistics vertexCacheLookupStatistics = new CpuDurationStatistics("Lookup in VertexCache");
private DurationStatistics[] commandStatistics;
private int errorCount;
private static final int maxErrorCount = 5; // Abort list processing when detecting more errors
private boolean isLogTraceEnabled;
private boolean isLogDebugEnabled;
private boolean isLogInfoEnabled;
private boolean isLogWarnEnabled;
private boolean isGeProfilerEnabled;
private int primCount;
private int nopCount;
private long listCount;
private boolean viewportChanged;
public MatrixUpload projectionMatrixUpload;
public MatrixUpload modelMatrixUpload;
public MatrixUpload viewMatrixUpload;
public MatrixUpload textureMatrixUpload;
private int boneMatrixIndex;
private int boneMatrixLinearUpdatedMatrix; // number of updated matrix
private static final float[] blackColor = new float[]{0, 0, 0, 0};
private boolean lightingChanged;
private boolean materialChanged;
private boolean textureChanged;
private int[] patch_prim_types = {PRIM_TRIANGLE_STRIPS, PRIM_LINES_STRIPS, PRIM_POINT};
private boolean clutIsDirty;
private boolean usingTRXKICK;
private int maxSpriteHeight;
private int maxSpriteWidth;
private boolean depthChanged;
private boolean scissorChanged;
// opengl needed information/buffers
private int textureId = -1;
private boolean textureFlipped;
private float textureFlipTranslateY;
private int[] tmp_texture_buffer32;
private short[] tmp_texture_buffer16;
private int[] clut_buffer32 = new int[4096];
private short[] clut_buffer16 = new short[4096];
private boolean listHasEnded;
private PspGeList currentList; // The currently executing list
private static final int drawBufferSizeInBytes = 2048 * 1024;
private static final int indexDrawBufferSizeInBytes = 512 * 1024;
private int bufferId;
private int nativeBufferId;
private int indexBufferId;
private ByteBuffer indexByteBuffer;
private float[] floatBufferArray;
private List<Integer> buffersToBeDeleted = new LinkedList<Integer>();
float[][] bboxVertices;
final private LinkedList<PspGeList> drawListQueue = new LinkedList<PspGeList>();
private boolean somethingDisplayed;
private boolean forceLoadGEToScreen;
private boolean geBufChanged;
private boolean fbBufChanged;
private IAction hleAction;
private int[] currentCMDValues;
private int[] currentListCMDValues;
private int previousPrim;
private LinkedList<AddressRange> videoTextures;
private IntBuffer multiDrawFirst;
private IntBuffer multiDrawCount;
private static final int maxMultiDrawElements = 1000;
private static final String name = "VideoEngine";
private int maxWaitForSyncCount;
private VertexState v = new VertexState();
private VertexState v1 = new VertexState();
private VertexState v2 = new VertexState();
private boolean isBoundingBox;
private boolean skipThisFrame;
// It is not safe in every application to simply skip the whole list.
// This could be a compatibility option.
private boolean skipListWhenSkippingFrame = false;
private boolean export3D;
private boolean export3DOnlyVisible = true;
private String export3DDirectory;
private IGraphicsExporter exporter;
private boolean hasModdedTextureDirectory;
private HashMap<Integer, int[]> cachedInstructions;
private long listStartMicroTime;
private boolean wantClearTextureCache;
private boolean wantClearVertexCache;
// The PSP can handle textures of maximum size 512x512.
// The PS3 PSP emulator can handle larger textures (for HD Remasters).
private int maxTextureSizeLog2 = 9;
private boolean doubleTexture2DCoords = false;
public static class MatrixUpload {
private final float[] matrix;
private boolean changed;
private int[] matrixIndex;
private int index;
private int maxIndex;
public MatrixUpload(float[] matrix, int matrixWidth, int matrixHeight) {
changed = true;
this.matrix = matrix;
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
matrix[y * 4 + x] = (x == y ? 1 : 0);
}
}
maxIndex = matrixWidth * matrixHeight;
matrixIndex = new int[maxIndex];
for (int i = 0; i < maxIndex; i++) {
matrixIndex[i] = (i % matrixWidth) + (i / matrixWidth) * 4;
}
}
public void startUpload(int startIndex) {
index = startIndex;
}
public final boolean uploadValue(float value) {
if (index >= maxIndex) {
if (VideoEngine.getInstance().isLogDebugEnabled) {
VideoEngine.log(String.format("Ignored Matrix upload value (idx=%08X)", index));
}
} else {
int i = matrixIndex[index];
if (matrix[i] != value) {
matrix[i] = value;
changed = true;
}
}
index++;
return index >= maxIndex;
}
public boolean isChanged() {
return changed;
}
public void setChanged(boolean changed) {
this.changed = changed;
}
}
private class UseVertexCacheSettingsListerner extends AbstractBoolSettingsListener {
@Override
protected void settingsValueChanged(boolean value) {
setUseVertexCache(value);
}
}
private class UseTextureAnisotropicFilterSettingsListerner extends AbstractBoolSettingsListener {
@Override
protected void settingsValueChanged(boolean value) {
setUseTextureAnisotropicFilter(value);
}
}
private class DisableOptimizedVertexInfoReadingListener extends AbstractBoolSettingsListener {
@Override
protected void settingsValueChanged(boolean value) {
setDisableOptimizedVertexInfoReading(value);
}
}
private class UsexBRZFilterSettingsListerner extends AbstractBoolSettingsListener {
@Override
protected void settingsValueChanged(boolean value) {
setUsexBRZFilter(value);
}
}
private static void log(String msg) {
log.debug(msg);
}
public static VideoEngine getInstance() {
if (instance == null) {
helper = new GeCommands();
instance = new VideoEngine();
}
return instance;
}
private VideoEngine() {
context = new GeContext();
modelMatrixUpload = new MatrixUpload(context.model_uploaded_matrix, 3, 4);
viewMatrixUpload = new MatrixUpload(context.view_uploaded_matrix, 3, 4);
textureMatrixUpload = new MatrixUpload(context.texture_uploaded_matrix, 3, 4);
projectionMatrixUpload = new MatrixUpload(context.proj_uploaded_matrix, 4, 4);
boneMatrixLinearUpdatedMatrix = 8;
commandStatistics = new DurationStatistics[256];
for (int i = 0; i < commandStatistics.length; i++) {
commandStatistics[i] = new DurationStatistics(String.format("%-11s", helper.getCommandString(i)));
}
bboxVertices = new float[8][3];
for (int i = 0; i < 8; i++) {
bboxVertices[i] = new float[3];
}
currentCMDValues = new int[256];
currentListCMDValues = new int[256];
videoTextures = new LinkedList<AddressRange>();
multiDrawFirst = ByteBuffer.allocateDirect(maxMultiDrawElements * 4).order(ByteOrder.nativeOrder()).asIntBuffer();
multiDrawCount = ByteBuffer.allocateDirect(maxMultiDrawElements * 4).order(ByteOrder.nativeOrder()).asIntBuffer();
if (avoidDrawElementsWithNonZeroIndexOffset) {
indexByteBuffer = ByteBuffer.allocateDirect(indexDrawBufferSizeInBytes).order(ByteOrder.nativeOrder());
}
}
/**
* Called from pspge module
*/
public void pushDrawList(PspGeList list) {
synchronized (drawListQueue) {
drawListQueue.addLast(list);
}
}
/**
* Called from pspge module
*/
public void pushDrawListHead(PspGeList list) {
synchronized (drawListQueue) {
drawListQueue.addFirst(list);
}
}
public int numberDrawLists() {
synchronized (drawListQueue) {
return drawListQueue.size();
}
}
public boolean hasDrawLists() {
synchronized (drawListQueue) {
return !drawListQueue.isEmpty();
}
}
public 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 PspGeList getFirstDrawList() {
PspGeList firstList;
synchronized (drawListQueue) {
firstList = currentList;
if (firstList == null) {
firstList = drawListQueue.peek();
}
}
return firstList;
}
public PspGeList getLastDrawList() {
PspGeList lastList = null;
synchronized (drawListQueue) {
for (PspGeList list : drawListQueue) {
if (list != null) {
lastList = list;
}
}
if (lastList == null) {
lastList = currentList;
}
}
return lastList;
}
private void deletePendingBuffers() {
while (!buffersToBeDeleted.isEmpty()) {
int buffer = buffersToBeDeleted.remove(0);
bufferManager.deleteBuffer(buffer);
}
}
public void stop() {
// If we are still drawing a list, stop the list processing
if (currentList != null) {
synchronized (drawListQueue) {
drawListQueue.clear();
}
listHasEnded = true;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// Ignore Exception
}
}
// The buffers have to be deleted from the GUI thread
buffersToBeDeleted.add(bufferId);
buffersToBeDeleted.add(nativeBufferId);
buffersToBeDeleted.add(indexBufferId);
bufferId = -1;
nativeBufferId = -1;
indexBufferId = -1;
floatBufferArray = null;
Settings.getInstance().removeSettingsListener(name);
}
public void start() {
Settings.getInstance().registerSettingsListener(name, "emu.useVertexCache", new UseVertexCacheSettingsListerner());
Settings.getInstance().registerSettingsListener(name, "emu.graphics.filters.anisotropic", new UseTextureAnisotropicFilterSettingsListerner());
Settings.getInstance().registerSettingsListener(name, "emu.plugins.xbrz", new UsexBRZFilterSettingsListerner());
Settings.getInstance().registerSettingsListener(name, "emu.disableoptimizedvertexinforeading", new DisableOptimizedVertexInfoReadingListener());
setMaxTextureSize(Settings.getInstance().readInt("maxTextureSize", 512));
setDoubleTexture2DCoords(Settings.getInstance().readBool("doubleTexture2DCoords"));
display = Modules.sceDisplayModule;
re = display.getRenderingEngine();
re.setGeContext(context);
context.setRenderingEngine(re);
bufferManager = re.getBufferManager();
if (!re.getBufferManager().useVBO()) {
// VertexCache is relying on VBO
useVertexCache = false;
}
deletePendingBuffers();
bufferId = bufferManager.genBuffer(IRenderingEngine.RE_ARRAY_BUFFER, IRenderingEngine.RE_FLOAT, drawBufferSizeInBytes / SIZEOF_FLOAT, IRenderingEngine.RE_STREAM_DRAW);
nativeBufferId = bufferManager.genBuffer(IRenderingEngine.RE_ARRAY_BUFFER, IRenderingEngine.RE_BYTE, drawBufferSizeInBytes, IRenderingEngine.RE_STREAM_DRAW);
indexBufferId = bufferManager.genBuffer(IRenderingEngine.RE_ELEMENT_ARRAY_BUFFER, IRenderingEngine.RE_UNSIGNED_BYTE, indexDrawBufferSizeInBytes, IRenderingEngine.RE_STREAM_DRAW);
floatBufferArray = new float[drawBufferSizeInBytes / SIZEOF_FLOAT];
if (useAsyncVertexCache) {
AsyncVertexCache.getInstance().setUseVertexArray(re.isVertexArrayAvailable());
}
context.setDirty();
projectionMatrixUpload.setChanged(true);
modelMatrixUpload.setChanged(true);
viewMatrixUpload.setChanged(true);
textureMatrixUpload.setChanged(true);
lightingChanged = true;
textureChanged = true;
geBufChanged = true;
viewportChanged = true;
depthChanged = true;
materialChanged = true;
previousPrim = PRIM_SPRITES;
listCount = 0;
cachedInstructions = new HashMap<Integer, int[]>();
}
public IRenderingEngine getRenderingEngine() {
return re;
}
public GeContext getContext() {
return context;
}
public static void exit() {
if (instance != null) {
if (instance.re != null) {
instance.re.exit();
}
if (DurationStatistics.collectStatistics) {
log.info(instance.statistics);
Arrays.sort(instance.commandStatistics);
final int numberCommands = 20;
log.info(String.format("%d most time intensive Video commands:", numberCommands));
for (int i = 0; i < numberCommands; i++) {
log.info(String.format(" %s", instance.commandStatistics[i]));
}
log.info(instance.vertexStatistics);
log.info(instance.vertexReadingStatistics);
log.info(instance.drawArraysStatistics);
log.info(instance.waitSignalStatistics);
log.info(instance.waitStallStatistics);
log.info(instance.textureCacheLookupStatistics);
log.info(instance.vertexCacheLookupStatistics);
VertexBufferManager.exit();
VertexArrayManager.exit();
}
}
}
public static DurationStatistics getStatistics() {
if (instance == null) {
return null;
}
return instance.statistics;
}
/**
* call from GL thread
*
* @return true if an update was made
*/
public boolean update() {
int listCount;
synchronized (drawListQueue) {
listCount = drawListQueue.size();
currentList = drawListQueue.poll();
}
if (currentList == null) {
return false;
}
startUpdate();
if (State.captureGeNextFrame) {
CaptureManager.startCapture("capture.bin", currentList);
}
if (State.replayGeNextFrame) {
// Load the replay list into drawListQueue
CaptureManager.startReplay("capture.bin");
// Hijack the current list with the replay list
// TODO this is assuming there is only 1 list in drawListQueue at this point, only the last list is the replay list
PspGeList replayList = drawListQueue.poll();
replayList.id = currentList.id;
replayList.blockedThreadIds.clear();
replayList.blockedThreadIds.addAll(currentList.blockedThreadIds);
currentList = replayList;
}
// Draw only as many lists as currently available in the drawListQueue.
// Some game add automatically a new list to the queue when the current
// list is finishing.
do {
executeList();
listCount--;
if (listCount <= 0) {
break;
}
synchronized (drawListQueue) {
currentList = drawListQueue.poll();
}
} while (currentList != null);
if (State.captureGeNextFrame) {
// Can't end capture until we get a sceDisplaySetFrameBuf after the list has executed
CaptureManager.markListExecuted();
}
if (State.replayGeNextFrame) {
State.replayGeNextFrame = false;
CaptureManager.endReplay();
}
endUpdate();
synchronized (drawListQueue) {
currentList = null;
}
return somethingDisplayed;
}
private void logLevelUpdated() {
isLogTraceEnabled = log.isTraceEnabled();
isLogDebugEnabled = log.isDebugEnabled();
isLogInfoEnabled = log.isInfoEnabled();
isLogWarnEnabled = log.isEnabledFor(Level.WARN);
}
public void setLogLevel(Level level) {
log.setLevel(level);
logLevelUpdated();
}
/**
* The memory used by GE has been updated or changed. Update the caches so
* that they see these changes.
*/
private void memoryForGEUpdated() {
if (useTextureCache) {
TextureCache.getInstance().resetTextureAlreadyHashed();
}
if (useVertexCache) {
VertexCache.getInstance().resetVertexAlreadyChecked();
}
VertexBufferManager.getInstance().resetAddressAlreadyChecked();
}
public void hleSetFrameBuf(int topAddr, int bufferWidth, int pixelFormat) {
if (context.fbp != topAddr || context.fbw != bufferWidth || context.psm != pixelFormat) {
// Update the frame buffer parameters at next display start.
// Do not update the context here, possibly in the middle of a rendering...
fbBufChanged = true;
}
}
public void resetCurrentListCMDValues() {
// Reset all the values
for (int i = 0; i < currentListCMDValues.length; i++) {
currentListCMDValues[i] = -1;
}
}
private void startUpdate() {
// Wait longer for a sync when the compiler is not enabled... Jpcsp is then much slower
maxWaitForSyncCount = RuntimeContext.isCompilerEnabled() ? 100 : 10000;
statistics.start();
logLevelUpdated();
isGeProfilerEnabled = GEProfiler.isProfilerEnabled();
memoryForGEUpdated();
somethingDisplayed = false;
geBufChanged = true;
forceLoadGEToScreen = true;
textureChanged = true;
projectionMatrixUpload.setChanged(true);
modelMatrixUpload.setChanged(true);
viewMatrixUpload.setChanged(true);
textureMatrixUpload.setChanged(true);
clutIsDirty = true;
lightingChanged = true;
viewportChanged = true;
depthChanged = true;
materialChanged = true;
scissorChanged = true;
errorCount = 0;
usingTRXKICK = false;
maxSpriteHeight = 0;
maxSpriteWidth = 0;
primCount = 0;
nopCount = 0;
listCount++;
resetCurrentListCMDValues();
if (fbBufChanged) {
context.fbp = display.getTopAddrFb();
context.fbw = display.getBufferWidthFb();
context.psm = display.getPixelFormatFb();
geBufChanged = true;
fbBufChanged = false;
}
context.update();
if (wantClearTextureCache) {
TextureCache.getInstance().reset(re);
GETextureManager.getInstance().reset(re);
resetVideoTextures();
wantClearTextureCache = false;
}
if (wantClearVertexCache) {
VertexCache.getInstance().reset(re);
VertexBufferManager.getInstance().reset(re);
wantClearVertexCache = false;
}
deletePendingBuffers();
hasModdedTextureDirectory = new File(getModdedTextureDirectory()).isDirectory();
if (!videoTextures.isEmpty()) {
synchronized (videoTextures) {
// Check if some video textures are obsolete
for (ListIterator<AddressRange> lit = videoTextures.listIterator(); lit.hasNext();) {
AddressRange videoTexture = lit.next();
if (videoTexture.isObsolete()) {
lit.remove();
}
}
}
}
if (State.exportGeNextFrame) {
startExport3D();
}
}
private static String getExportDirectory() {
for (int i = 1; true; i++) {
String directory = String.format("%sExport-%d%c", IGraphicsExporter.exportDirectory, i, File.separatorChar);
if (!new File(directory).exists()) {
return directory;
}
}
}
private void startExport3D() {
export3D = true;
export3DOnlyVisible = State.exportGeOnlyVisibleElements;
State.exportGeNextFrame = false;
export3DDirectory = getExportDirectory();
if (new File(export3DDirectory).mkdirs()) {
exporter = new WavefrontExporter();
exporter.startExport(context, export3DDirectory);
} else {
log.error(String.format("Cannot create export directory '%s'", export3DDirectory));
}
}
private void endExport3D() {
exporter.endExport();
exporter = null;
export3D = false;
}
private void endUpdate() {
if (export3D) {
endExport3D();
}
if (re.isVertexArrayAvailable()) {
re.bindVertexArray(0);
}
re.waitForRenderingCompletion();
context.reTextureGenS.setEnabled(false);
context.reTextureGenT.setEnabled(false);
if (useVertexCache) {
if (primCount > VertexCache.cacheMaxSize) {
log.warn(String.format("VertexCache size (%d) too small to execute %d PRIM commands", VertexCache.cacheMaxSize, primCount));
}
}
statistics.end();
}
public void error(String message) {
errorCount++;
log.error(message);
if (errorCount >= maxErrorCount) {
if (tryToFallback()) {
log.error("Aborting current list processing due to too many errors");
}
}
}
private boolean tryToFallback() {
boolean abort = false;
if (!currentList.isStackEmpty()) {
// When have some CALLs on the stack, try to return from the last CALL
int oldPc = currentList.getPc();
currentList.ret();
int newPc = currentList.getPc();
if (isLogDebugEnabled) {
log(String.format("tryToFallback old PC: 0x%08X, new PC: 0x%08X", oldPc, newPc));
}
} else {
// Finish this list
currentList.finishList();
// Trigger a FINISH callback to avoid hanging the application...
currentList.pushFinishCallback(currentList.id, 0);
listHasEnded = true;
abort = true;
}
return abort;
}
private void checkCurrentListPc() {
Memory mem = Memory.getInstance();
while (!Memory.isAddressGood(currentList.getPc())) {
if (!mem.isIgnoreInvalidMemoryAccess()) {
error("Reading GE list from invalid address 0x" + Integer.toHexString(currentList.getPc()));
break;
}
// Ignoring memory read errors.
// Try to fall back and continue the list processing.
log.warn("Reading GE list from invalid address 0x" + Integer.toHexString(currentList.getPc()));
if (tryToFallback()) {
break;
}
}
}
private void executeHleAction() {
if (hleAction != null) {
hleAction.execute();
hleAction = null;
}
}
private void executeListStalled() {
waitStallStatistics.start();
if (isLogDebugEnabled) {
log.debug(String.format("Stall address 0x%08X reached, waiting for Sync", currentList.getPc()));
}
currentList.status = PSP_GE_LIST_STALL_REACHED;
long startWaitClockMillis = Emulator.getClock().milliTime();
int waitMillis = 10;
if (!currentList.waitForSync(waitMillis)) {
long endWaitClockMillis = Emulator.getClock().milliTime();
if (isLogDebugEnabled) {
log.debug("Wait for sync while stall reached");
}
// Count only when the clock is not paused
if (endWaitClockMillis - startWaitClockMillis >= waitMillis - 1) {
waitForSyncCount++;
}
// Waiting maximum 100 * 10ms (= 1 second) on a stall address.
// After this timeout, abort the list.
//
// When the stall address is at the very beginning of the list
// (i.e. the list has just been enqueued, but the stall has not yet been updated),
// allow waiting for a longer time (the CPU might be busy
// compiling a huge CodeBlock on the first call).
// This avoids aborting the first list enqueued.
int maxStallCount = maxWaitForSyncCount;
if (currentList.getPc() == currentList.list_addr) {
maxStallCount *= 60; // Waiting for 60 seconds...
}
if (isLogDebugEnabled) {
maxStallCount = Integer.MAX_VALUE;
}
if (waitForSyncCount > maxStallCount) {
error(String.format("Waiting too long on stall address 0x%08X, aborting the list %s", currentList.getPc(), currentList));
}
} else {
waitForSyncCount = 0;
}
executeHleAction();
if (!currentList.isStallReached()) {
currentList.status = PSP_GE_LIST_DRAWING;
}
waitStallStatistics.end();
}
public boolean isWaitingOnStall() {
return currentList != null && currentList.status == PSP_GE_LIST_STALL_REACHED && waitForSyncCount > 0;
}
private boolean executeListPaused() {
if (isLogDebugEnabled) {
log.debug(String.format("FINISH / SIGNAL / END reached, waiting for Sync (%s)", currentList.toString()));
}
currentList.status = PSP_GE_LIST_END_REACHED;
// No need to wait of the END command for a FINISH.
if (currentList.isFinished()) {
listHasEnded = true;
return true;
}
waitSignalStatistics.start();
long startWaitClockMillis = Emulator.getClock().milliTime();
if (!currentList.waitForSync(10)) {
long endWaitClockMillis = Emulator.getClock().milliTime();
if (isLogDebugEnabled) {
log.debug("Wait for sync while END reached");
}
// Count only when the clock is not paused
if (startWaitClockMillis != endWaitClockMillis) {
waitForSyncCount++;
}
// Waiting maximum 100 * 10ms (= 1 second) on an END command.
// After this timeout, abort the list.
if (waitForSyncCount > maxWaitForSyncCount) {
error(String.format("Waiting too long on an END command, aborting the list %s", currentList));
}
} else {
waitForSyncCount = 0;
}
executeHleAction();
if (currentList.isRestarted()) {
currentList.clearRestart();
currentList.clearPaused();
}
if (!currentList.isPaused()) {
if (currentList.isFinished()) {
listHasEnded = true;
return true;
}
currentList.status = PSP_GE_LIST_DRAWING;
}
waitSignalStatistics.end();
return false;
}
// call from GL thread
// There is an issue here with Emulator.pause
// - We want to stop on errors
// - But user may also press pause button
// - Either continue drawing to the end of the list (bad if the list contains an infinite loop)
// - Or we want to be able to restart drawing when the user presses the run button
private void executeList() {
if (skipThisFrame && skipListWhenSkippingFrame) {
listHasEnded = true;
currentList.status = PSP_GE_LIST_DONE;
Modules.sceGe_userModule.hleGeListSyncDone(currentList);
executeHleAction();
return;
}
listHasEnded = false;
currentList.status = PSP_GE_LIST_DRAWING;
if (isLogDebugEnabled) {
log("executeList " + currentList);
}
executeHleAction();
// Save the context at the beginning of the list processing to the given address (used by sceGu).
if (currentList.hasSaveContextAddr()) {
saveContext(currentList.getSaveContextAddr());
}
if (isGeProfilerEnabled) {
listStartMicroTime = Emulator.getClock().microTime();
GEProfiler.startGeList();
}
waitForSyncCount = 0;
while (!listHasEnded && (!Emulator.pause || State.captureGeNextFrame)) {
if (currentList.isPaused() || currentList.isEnded()) {
if (executeListPaused()) {
break;
}
} else if (currentList.isStallReached()) {
executeListStalled();
} else {
int ins = currentList.readNextInstruction();
executeCommand(ins);
}
}
if (Emulator.pause && !listHasEnded) {
if (isLogInfoEnabled) {
log.info("Emulator paused - cancelling current list id=" + currentList.id);
}
currentList.status = PSP_GE_LIST_CANCEL_DONE;
}
// let DONE take priority over STALL_REACHED
if (listHasEnded) {
currentList.status = PSP_GE_LIST_END_REACHED;
// Tested on PSP:
// A list is only DONE after a combination of FINISH + END.
if (currentList.isEnded()) {
currentList.status = PSP_GE_LIST_DONE;
}
}
if (currentList.isDone()) {
if (isGeProfilerEnabled) {
long listEndMicroTime = Emulator.getClock().microTime();
GEProfiler.geListDuration(listEndMicroTime - listStartMicroTime);
}
Modules.sceGe_userModule.hleGeListSyncDone(currentList);
}
executeHleAction();
// Restore the context to the state at the beginning of the list processing (used by sceGu).
if (currentList.hasSaveContextAddr()) {
restoreContext(currentList.getSaveContextAddr());
}
}
public PspGeList getCurrentList() {
return currentList;
}
public float[] getMatrix(int mtxtype) {
float[] resmtx;
switch (mtxtype) {
case PSP_GE_MATRIX_BONE0:
case PSP_GE_MATRIX_BONE1:
case PSP_GE_MATRIX_BONE2:
case PSP_GE_MATRIX_BONE3:
case PSP_GE_MATRIX_BONE4:
case PSP_GE_MATRIX_BONE5:
case PSP_GE_MATRIX_BONE6:
case PSP_GE_MATRIX_BONE7:
resmtx = context.bone_uploaded_matrix[mtxtype - PSP_GE_MATRIX_BONE0];
break;
case PSP_GE_MATRIX_WORLD:
resmtx = context.model_uploaded_matrix;
break;
case PSP_GE_MATRIX_VIEW:
resmtx = context.view_uploaded_matrix;
break;
case PSP_GE_MATRIX_PROJECTION:
resmtx = context.proj_uploaded_matrix;
break;
case PSP_GE_MATRIX_TEXGEN:
resmtx = context.texture_uploaded_matrix;
break;
default:
resmtx = null;
break;
}
return resmtx;
}
public int getCommandValue(int cmd) {
if (cmd < 0 || cmd >= currentCMDValues.length) {
return 0;
}
return currentCMDValues[cmd];
}
public String commandToString(int cmd) {
return GeCommands.getInstance().getCommandString(cmd);
}
public static int command(int instruction) {
return (instruction >>> 24);
}
private static int intArgument(int instruction) {
return (instruction & 0x00FFFFFF);
}
private static float floatArgument(int normalArgument) {
return Float.intBitsToFloat(normalArgument << 8);
}
private int getClutAddr(int level, int clutNumEntries, int clutEntrySize) {
return context.tex_clut_addr + (context.tex_clut_start << 4) * clutEntrySize;
}
private void readClut() {
if (!clutIsDirty || context.tex_clut_addr == 0) {
return;
}
if (!Memory.isAddressGood(context.tex_clut_addr)) {
if (isLogWarnEnabled) {
log.warn(String.format("Invalid clut address 0x%08X", context.tex_clut_addr));
}
return;
}
if (context.tex_clut_mode == CMODE_FORMAT_32BIT_ABGR8888) {
readClut32(0);
} else {
readClut16(0);
}
}
public short[] readClut16(int level) {
// Update the clut_buffer only if some clut parameters have been changed
// since last update.
if (clutIsDirty) {
int clutNumEntries = context.tex_clut_num_blocks << 4;
int clutOffset = context.tex_clut_start << 4;
IMemoryReader memoryReader = MemoryReader.getMemoryReader(getClutAddr(level, clutNumEntries, 2), (clutNumEntries - clutOffset) << 1, 2);
for (int i = clutOffset; i < clutNumEntries; i++) {
clut_buffer16[i] = (short) memoryReader.readNext();
}
clutIsDirty = false;
}
if (State.captureGeNextFrame) {
log.info("Capture readClut16");
CaptureManager.captureRAM(context.tex_clut_addr, context.tex_clut_num_blocks * 32);
}
return clut_buffer16;
}
public int[] readClut32(int level) {
// Update the clut_buffer only if some clut parameters have been changed
// since last update.
if (clutIsDirty) {
int clutNumEntries = context.tex_clut_num_blocks << 3;
int clutOffset = context.tex_clut_start << 4;
IMemoryReader memoryReader = MemoryReader.getMemoryReader(getClutAddr(level, clutNumEntries, 4), (clutNumEntries - clutOffset) << 2, 4);
for (int i = clutOffset; i < clutNumEntries; i++) {
clut_buffer32[i] = memoryReader.readNext();
}
clutIsDirty = false;
}
if (State.captureGeNextFrame) {
log.info("Capture readClut32");
CaptureManager.captureRAM(context.tex_clut_addr, context.tex_clut_num_blocks * 32);
}
return clut_buffer32;
}
private int getClutIndex(int index) {
return ((index >> context.tex_clut_shift) & context.tex_clut_mask) | (context.tex_clut_start << 4);
}
// UnSwizzling based on pspplayer
private Buffer unswizzleTextureFromMemory(int texaddr, int bytesPerPixel, int level, int textureBufferWidthInPixels) {
int rowWidth = (bytesPerPixel > 0) ? (textureBufferWidthInPixels * bytesPerPixel) : (textureBufferWidthInPixels / 2);
int pitch = rowWidth / 4;
int bxc = rowWidth / 16;
int byc = Math.max((context.texture_height[level] + 7) / 8, 1);
int ydest = 0;
IMemoryReader memoryReader = MemoryReader.getMemoryReader(texaddr, 4);
for (int by = 0; by < byc; by++) {
if (rowWidth >= 16) {
int xdest = ydest;
for (int bx = 0; bx < bxc; bx++) {
int dest = xdest;
for (int n = 0; n < 8; n++) {
tmp_texture_buffer32[dest] = memoryReader.readNext();
tmp_texture_buffer32[dest + 1] = memoryReader.readNext();
tmp_texture_buffer32[dest + 2] = memoryReader.readNext();
tmp_texture_buffer32[dest + 3] = memoryReader.readNext();
dest += pitch;
}
xdest += 4;
}
ydest += (rowWidth * 8) / 4;
} else if (rowWidth == 8) {
for (int n = 0; n < 8; n++, ydest += 2) {
tmp_texture_buffer32[ydest] = memoryReader.readNext();
tmp_texture_buffer32[ydest + 1] = memoryReader.readNext();
memoryReader.skip(2);
}
} else if (rowWidth == 4) {
for (int n = 0; n < 8; n++, ydest++) {
tmp_texture_buffer32[ydest] = memoryReader.readNext();
memoryReader.skip(3);
}
} else if (rowWidth == 2) {
for (int n = 0; n < 4; n++, ydest++) {
int n1 = memoryReader.readNext() & 0xFFFF;
memoryReader.skip(3);
int n2 = memoryReader.readNext() & 0xFFFF;
memoryReader.skip(3);
tmp_texture_buffer32[ydest] = n1 | (n2 << 16);
}
} else if (rowWidth == 1) {
for (int n = 0; n < 2; n++, ydest++) {
int n1 = memoryReader.readNext() & 0xFF;
memoryReader.skip(3);
int n2 = memoryReader.readNext() & 0xFF;
memoryReader.skip(3);
int n3 = memoryReader.readNext() & 0xFF;
memoryReader.skip(3);
int n4 = memoryReader.readNext() & 0xFF;
memoryReader.skip(3);
tmp_texture_buffer32[ydest] = n1 | (n2 << 8) | (n3 << 16) | (n4 << 24);
}
}
}
if (State.captureGeNextFrame) {
log.info("Capture unswizzleTextureFromMemory");
CaptureManager.captureRAM(texaddr, rowWidth * context.texture_height[level]);
}
return IntBuffer.wrap(tmp_texture_buffer32);
}
private String getArgumentLog(int normalArgument) {
if (normalArgument == 0) {
return "(0)"; // a very common case...
}
return String.format("(hex=%08X,int=%d,float=%f)", normalArgument, normalArgument, floatArgument(normalArgument));
}
public void executeCommand(int instruction) {
command = command(instruction);
// Quick check: pure state commands can be ignored when they are
// repeated with the same parameters. These are redundant commands.
if (GeCommands.pureStateCommands[command]) {
if (currentListCMDValues[command] == instruction) {
if (isLogDebugEnabled) {
log.debug(String.format("%s 0x%06X redundant pure state cmd ignored", helper.getCommandString(command), intArgument(instruction)));
}
return;
}
currentListCMDValues[command] = instruction;
}
normalArgument = intArgument(instruction);
// Compute floatArgument only on demand, most commands do not use it.
//float floatArgument = floatArgument(instruction);
currentCMDValues[command] = normalArgument;
if (DurationStatistics.collectStatistics) {
commandStatistics[command].start();
}
switch (command) {
case NOP:
executeCommandNOP();
break;
case VADDR:
executeCommandVADDR();
break;
case IADDR:
executeCommandIADDR();
break;
case PRIM:
executeCommandPRIM();
break;
case BEZIER:
executeCommandBEZIER();
break;
case SPLINE:
executeCommandSPLINE();
break;
case BBOX:
executeCommandBBOX();
break;
case JUMP:
executeCommandJUMP();
break;
case BJUMP:
executeCommandBJUMP();
break;
case CALL:
executeCommandCALL();
break;
case RET:
executeCommandRET();
break;
case END:
executeCommandEND();
break;
case SIGNAL:
executeCommandSIGNAL();
break;
case FINISH:
executeCommandFINISH();
break;
case BASE:
executeCommandBASE();
break;
case VTYPE:
executeCommandVTYPE();
break;
case OFFSET_ADDR:
executeCommandOFFSET_ADDR();
break;
case ORIGIN_ADDR:
executeCommandORIGIN_ADDR();
break;
case REGION1:
executeCommandREGION1();
break;
case REGION2:
executeCommandREGION2();
break;
case LTE:
executeCommandLTE();
break;
case LTE0:
case LTE1:
case LTE2:
case LTE3:
executeCommandLTEn();
break;
case CPE:
executeCommandCPE();
break;
case BCE:
executeCommandBCE();
break;
case TME:
executeCommandTME();
break;
case FGE:
executeCommandFGE();
break;
case DTE:
executeCommandDTE();
break;
case ABE:
executeCommandABE();
break;
case ATE:
executeCommandATE();
break;
case ZTE:
executeCommandZTE();
break;
case STE:
executeCommandSTE();
break;
case AAE:
executeCommandAAE();
break;
case PCE:
executeCommandPCE();
break;
case CTE:
executeCommandCTE();
break;
case LOE:
executeCommandLOE();
break;
case BOFS:
executeCommandBOFS();
break;
case BONE:
executeCommandBONE();
break;
case MW0:
case MW1:
case MW2:
case MW3:
case MW4:
case MW5:
case MW6:
case MW7:
executeCommandMWn();
break;
case PSUB:
executeCommandPSUB();
break;
case PPRIM:
executeCommandPPRIM();
break;
case PFACE:
executeCommandPFACE();
break;
case MMS:
executeCommandMMS();
break;
case MODEL:
executeCommandMODEL();
break;
case VMS:
executeCommandVMS();
break;
case VIEW:
executeCommandVIEW();
break;
case PMS:
executeCommandPMS();
break;
case PROJ:
executeCommandPROJ();
break;
case TMS:
executeCommandTMS();
break;
case TMATRIX:
executeCommandTMATRIX();
break;
case XSCALE:
executeCommandXSCALE();
break;
case YSCALE:
executeCommandYSCALE();
break;
case ZSCALE:
executeCommandZSCALE();
break;
case XPOS:
executeCommandXPOS();
break;
case YPOS:
executeCommandYPOS();
break;
case ZPOS:
executeCommandZPOS();
break;
case USCALE:
executeCommandUSCALE();
break;
case VSCALE:
executeCommandVSCALE();
break;
case UOFFSET:
executeCommandUOFFSET();
break;
case VOFFSET:
executeCommandVOFFSET();
break;
case OFFSETX:
executeCommandOFFSETX();
break;
case OFFSETY:
executeCommandOFFSETY();
break;
case SHADE:
executeCommandSHADE();
break;
case RNORM:
executeCommandRNORM();
break;
case CMAT:
executeCommandCMAT();
break;
case EMC:
executeCommandEMC();
break;
case AMC:
executeCommandAMC();
break;
case DMC:
executeCommandDMC();
break;
case SMC:
executeCommandSMC();
break;
case AMA:
executeCommandAMA();
break;
case SPOW:
executeCommandSPOW();
break;
case ALC:
executeCommandALC();
break;
case ALA:
executeCommandALA();
break;
case LMODE:
executeCommandLMODE();
break;
case LT0:
case LT1:
case LT2:
case LT3:
executeCommandLTn();
break;
case LXP0:
case LXP1:
case LXP2:
case LXP3:
case LYP0:
case LYP1:
case LYP2:
case LYP3:
case LZP0:
case LZP1:
case LZP2:
case LZP3:
executeCommandLXPn();
break;
case LXD0:
case LXD1:
case LXD2:
case LXD3:
case LYD0:
case LYD1:
case LYD2:
case LYD3:
case LZD0:
case LZD1:
case LZD2:
case LZD3:
executeCommandLXDn();
break;
case LCA0:
case LCA1:
case LCA2:
case LCA3:
executeCommandLCAn();
break;
case LLA0:
case LLA1:
case LLA2:
case LLA3:
executeCommandLLAn();
break;
case LQA0:
case LQA1:
case LQA2:
case LQA3:
executeCommandLQAn();
break;
case SLE0:
case SLE1:
case SLE2:
case SLE3:
executeCommandSLEn();
break;
case SLF0:
case SLF1:
case SLF2:
case SLF3:
executeCommandSLFn();
break;
case ALC0:
case ALC1:
case ALC2:
case ALC3:
executeCommandALCn();
break;
case DLC0:
case DLC1:
case DLC2:
case DLC3:
executeCommandDLCn();
break;
case SLC0:
case SLC1:
case SLC2:
case SLC3:
executeCommandSLCn();
break;
case FFACE:
executeCommandFFACE();
break;
case FBP:
executeCommandFBP();
break;
case FBW:
executeCommandFBW();
break;
case ZBP:
executeCommandZBP();
break;
case ZBW:
executeCommandZBW();
break;
case TBP0:
case TBP1:
case TBP2:
case TBP3:
case TBP4:
case TBP5:
case TBP6:
case TBP7:
executeCommandTBPn();
break;
case TBW0:
case TBW1:
case TBW2:
case TBW3:
case TBW4:
case TBW5:
case TBW6:
case TBW7:
executeCommandTBWn();
break;
case CBP:
executeCommandCBP();
break;
case CBPH:
executeCommandCBPH();
break;
case TRXSBP:
executeCommandTRXSBP();
break;
case TRXSBW:
executeCommandTRXSBW();
break;
case TRXDBP:
executeCommandTRXDBP();
break;
case TRXDBW:
executeCommandTRXDBW();
break;
case TSIZE0:
case TSIZE1:
case TSIZE2:
case TSIZE3:
case TSIZE4:
case TSIZE5:
case TSIZE6:
case TSIZE7:
executeCommandTSIZEn();
break;
case TMAP:
executeCommandTMAP();
break;
case TEXTURE_ENV_MAP_MATRIX:
executeCommandTEXTURE_ENV_MAP_MATRIX();
break;
case TMODE:
executeCommandTMODE();
break;
case TPSM:
executeCommandTPSM();
break;
case CLOAD:
executeCommandCLOAD();
break;
case CMODE:
executeCommandCMODE();
break;
case TFLT:
executeCommandTFLT();
break;
case TWRAP:
executeCommandTWRAP();
break;
case TBIAS:
executeCommandTBIAS();
break;
case TFUNC:
executeCommandTFUNC();
break;
case TEC:
executeCommandTEC();
break;
case TFLUSH:
executeCommandTFLUSH();
break;
case TSYNC:
executeCommandTSYNC();
break;
case FFAR:
executeCommandFFAR();
break;
case FDIST:
executeCommandFDIST();
break;
case FCOL:
executeCommandFCOL();
break;
case TSLOPE:
executeCommandTSLOPE();
break;
case PSM:
executeCommandPSM();
break;
case CLEAR:
executeCommandCLEAR();
break;
case SCISSOR1:
executeCommandSCISSOR1();
break;
case SCISSOR2:
executeCommandSCISSOR2();
break;
case NEARZ:
executeCommandNEARZ();
break;
case FARZ:
executeCommandFARZ();
break;
case CTST:
executeCommandCTST();
break;
case CREF:
executeCommandCREF();
break;
case CMSK:
executeCommandCMSK();
break;
case ATST:
executeCommandATST();
break;
case STST:
executeCommandSTST();
break;
case SOP:
executeCommandSOP();
break;
case ZTST:
executeCommandZTST();
break;
case ALPHA:
executeCommandALPHA();
break;
case SFIX:
executeCommandSFIX();
break;
case DFIX:
executeCommandDFIX();
break;
case DTH0:
executeCommandDTH0();
break;
case DTH1:
executeCommandDTH1();
break;
case DTH2:
executeCommandDTH2();
break;
case DTH3:
executeCommandDTH3();
break;
case LOP:
executeCommandLOP();
break;
case ZMSK:
executeCommandZMSK();
break;
case PMSKC:
executeCommandPMSKC();
break;
case PMSKA:
executeCommandPMSKA();
break;
case TRXKICK:
executeCommandTRXKICK();
break;
case TRXPOS:
executeCommandTRXPOS();
break;
case TRXDPOS:
executeCommandTRXDPOS();
break;
case TRXSIZE:
executeCommandTRXSIZE();
break;
case VSCX:
executeCommandVSCX();
break;
case VSCY:
executeCommandVSCY();
break;
case VSCZ:
executeCommandVSCZ();
break;
case VTCS:
executeCommandVTCS();
break;
case VTCT:
executeCommandVTCT();
break;
case VTCQ:
executeCommandVTCQ();
break;
case VCV:
executeCommandVCV();
break;
case VAP:
executeCommandVAP();
break;
case VFC:
executeCommandVFC();
break;
case VSCV:
executeCommandVSCV();
break;
case DUMMY:
executeCommandDUMMY();
break;
default:
executeCommandUNKNOWN();
break;
}
if (DurationStatistics.collectStatistics) {
commandStatistics[command].end();
}
}
private void executeCommandUNKNOWN() {
if (isLogWarnEnabled) {
log.warn(String.format("Unknown/unimplemented video command [%s]%s at 0x%08X", helper.getCommandString(command), getArgumentLog(normalArgument), currentList.getPc() - 4));
}
}
private void executeCommandCLEAR() {
if ((normalArgument & 1) == 0) {
if (!context.clearMode) {
return;
}
context.clearMode = false;
re.endClearMode();
if (isLogDebugEnabled) {
log("clear mode end");
}
} else {
// TODO Add more disabling in clear mode, we also need to reflect the change to the internal GE registers
boolean color = (normalArgument & 0x100) != 0;
boolean stencil = (normalArgument & 0x200) != 0;
boolean depth = (normalArgument & 0x400) != 0;
updateGeBuf();
re.startClearMode(color, stencil, depth);
context.clearMode = true;
context.clearModeColor = color;
context.clearModeStencil = stencil;
context.clearModeDepth = depth;
if (isLogDebugEnabled) {
log(String.format("clear mode: %d (%s%s%s)", normalArgument >> 8, color ? "COLOR" : "", stencil ? " STENCIL" : "", depth ? " DEPTH" : ""));
}
}
lightingChanged = true;
projectionMatrixUpload.setChanged(true);
modelMatrixUpload.setChanged(true);
viewMatrixUpload.setChanged(true);
textureMatrixUpload.setChanged(true);
viewportChanged = true;
depthChanged = true;
materialChanged = true;
}
private void executeCommandTFUNC() {
context.textureFunc = normalArgument & 0x7;
if (context.textureFunc >= TFUNC_FRAGMENT_DOUBLE_TEXTURE_EFECT_UNKNOW1) {
// All 3 unknown values have the same function as TFUNC_FRAGMENT_DOUBLE_TEXTURE_EFECT_ADD.
// Tested on PSP using 3DStudio.
context.textureFunc = TFUNC_FRAGMENT_DOUBLE_TEXTURE_EFECT_ADD;
}
context.textureAlphaUsed = ((normalArgument >> 8) & 0x1) != TFUNC_FRAGMENT_DOUBLE_TEXTURE_COLOR_ALPHA_IS_IGNORED;
context.textureColorDoubled = ((normalArgument >> 16) & 0x1) != TFUNC_FRAGMENT_DOUBLE_ENABLE_COLOR_UNTOUCHED;
re.setTextureFunc(context.textureFunc, context.textureAlphaUsed, context.textureColorDoubled);
if (isLogDebugEnabled) {
log(String.format("sceGuTexFunc mode %06X", normalArgument)
+ (((normalArgument & 0x10000) != 0) ? " SCALE" : "")
+ (((normalArgument & 0x100) != 0) ? " ALPHA" : ""));
}
}
private int fixNativeBufferOffset(Buffer vertexData, int addr, int size) {
// Handle buffer address not aligned with memory Buffer object.
// E.g. ptr_vertex = 0xNNNNNN2 and vertexData is an IntBuffer
// starting at 0xNNNNNN0
int nativeBufferOffset = getBufferOffset(vertexData, addr);
size += nativeBufferOffset;
vertexInfoReader.addNativeOffset(nativeBufferOffset);
return size;
}
private int getBufferOffset(Buffer buffer, int addr) {
if (buffer instanceof IntBuffer || buffer instanceof FloatBuffer) {
return addr & 3;
}
if (buffer instanceof ShortBuffer) {
return addr & 1;
}
return 0;
}
private int checkMultiDraw(int currentFirst, int currentType, int currentNumberOfVertex, IntBuffer bufferFirst, IntBuffer bufferCount, boolean hasIndex) {
if (avoidDrawElementsWithNonZeroIndexOffset && hasIndex) {
// Multiple drawElements can only mixed into a multi-draw when non-zero index offsets are allowed.
if (isLogDebugEnabled) {
log(String.format("checkMultiDraw hasIndex=%b disabled to avoid non-zero index offsets", hasIndex));
}
return -1;
}
if (isLogDebugEnabled) {
log(String.format("checkMultiDraw at 0x%08X", currentList.getPc()));
}
int beforeMultiPc = currentList.getPc();
int afterMultiPc = currentList.getPc();
boolean hasMultiDraw = false;
int initialFirst = currentFirst;
int currentSkip = 0;
bufferFirst.clear();
bufferCount.clear();
int currentPtrVertex = context.vinfo.ptr_vertex + context.vinfo.vertexSize * currentNumberOfVertex;
boolean frontFaceCw = context.frontFaceCw;
// Leave at least one entry free to put the last item
while (bufferFirst.remaining() > 1) {
if (currentList.isStallReached()) {
if (isLogDebugEnabled) {
log.debug(String.format("Stopped integration in MultiDrawArrays at stall address 0x%08X", currentList.getPc()));
}
break;
}
int instruction = currentList.readNextInstruction();
int cmd = command(instruction);
if (cmd == PRIM) {
if (context.frontFaceCw != frontFaceCw) {
if (isLogDebugEnabled) {
log.debug(String.format("%s 0x%06X non matching FFACE has stopped integration in MultiDrawArrays", helper.getCommandString(cmd), intArgument(instruction)));
}
break;
}
int type = ((instruction >> 16) & 0x7);
if (type != currentType) {
if (isLogDebugEnabled) {
log.debug(String.format("%s 0x%06X non matching vertex type has stopped integration in MultiDrawArrays", helper.getCommandString(cmd), intArgument(instruction)));
}
break;
}
int numberOfVertex = instruction & 0xFFFF;
bufferFirst.put(currentFirst);
bufferCount.put(currentNumberOfVertex);
currentFirst += currentNumberOfVertex + currentSkip;
currentNumberOfVertex = numberOfVertex;
currentPtrVertex += context.vinfo.vertexSize * (numberOfVertex + currentSkip);
currentSkip = 0;
hasMultiDraw = true;
afterMultiPc = currentList.getPc();
if (isLogDebugEnabled) {
log.debug(String.format("%s type=%d, numberOfVertex=%d integrated in MultiDrawArrays", helper.getCommandString(cmd), type, numberOfVertex));
}
} else if (cmd == VADDR) {
int arg = intArgument(instruction);
int ptr_vertex = currentList.getAddressRelOffset(arg);
if (ptr_vertex == currentPtrVertex) {
// VADDR in sequence, skip the command
if (isLogDebugEnabled) {
log.debug(String.format("%s 0x%08X integrated in MultiDrawArrays", helper.getCommandString(cmd), ptr_vertex));
}
} else if (ptr_vertex > currentPtrVertex && ((ptr_vertex - currentPtrVertex) % context.vinfo.vertexSize) == 0) {
// VADDR almost in sequence with an aligned hole, skip the command
currentSkip = (ptr_vertex - currentPtrVertex) / context.vinfo.vertexSize;
if (isLogDebugEnabled) {
log.debug(String.format("%s 0x%08X (skip=%d) integrated in MultiDrawArrays", helper.getCommandString(cmd), ptr_vertex, currentSkip));
}
} else {
if (isLogDebugEnabled) {
log.debug(String.format("%s 0x%08X not integrated in MultiDrawArrays (needed 0x%08X)", helper.getCommandString(cmd), ptr_vertex, currentPtrVertex));
}
break;
}
} else if (cmd == TBIAS) {
int tex_mipmap_mode = instruction & 0x3;
if (context.tex_mipmap_mode == tex_mipmap_mode && tex_mipmap_mode == TBIAS_MODE_AUTO) {
// Skip TBIAS with TBIAS_MODE_AUTO, ignore tex_mipmap_bias parameter
if (isLogDebugEnabled) {
log.debug(String.format("%s 0x%06X integrated in MultiDrawArrays", helper.getCommandString(cmd), intArgument(instruction)));
}
} else {
if (isLogDebugEnabled) {
log.debug(String.format("%s 0x%06X has stopped integration in MultiDrawArrays", helper.getCommandString(cmd), intArgument(instruction)));
}
break;
}
} else if (cmd == NOP) {
if (isLogDebugEnabled) {
log.debug(String.format("%s 0x%06X integrated in MultiDrawArrays", helper.getCommandString(cmd), intArgument(instruction)));
}
} else if (GeCommands.pureStateCommands[cmd]) {
if (cmd == FFACE) {
// Some applications generate the following sequence:
// FFACE 0
// PRIM xxx
// FFACE 1
// FFACE 0
// PRIM xxx
// Detect such sequences (changing the FFACE with no effect)
// and integrate them in multiDraw.
frontFaceCw = intArgument(instruction) != 0;
if (isLogDebugEnabled) {
log.debug(String.format("%s 0x%06X trying to integrate in MultiDrawArrays", helper.getCommandString(cmd), intArgument(instruction)));
}
} else if (currentListCMDValues[cmd] == instruction) {
// The command has been repeated with the same parameters,
// it can be ignored.
if (isLogDebugEnabled) {
log.debug(String.format("%s 0x%06X pure state cmd integrated in MultiDrawArrays", helper.getCommandString(cmd), intArgument(instruction)));
}
} else {
if (isLogDebugEnabled) {
log.debug(String.format("%s 0x%06X pure state cmd has stopped integration in MultiDrawArrays", helper.getCommandString(cmd), intArgument(instruction)));
}
break;
}
} else {
if (isLogDebugEnabled) {
log.debug(String.format("%s 0x%06X has stopped integration in MultiDrawArrays", helper.getCommandString(cmd), intArgument(instruction)));
}
break;
}
}
if (!hasMultiDraw) {
currentList.setPc(beforeMultiPc);
return -1;
}
bufferFirst.put(currentFirst);
bufferCount.put(currentNumberOfVertex);
bufferFirst.limit(bufferFirst.position());
bufferFirst.rewind();
bufferCount.limit(bufferCount.position());
bufferCount.rewind();
currentList.setPc(afterMultiPc);
return currentFirst + currentNumberOfVertex - initialFirst;
}
private VertexIndexInfo getVertexIndexInfo(int bytesPerIndex, int numberOfVertex) {
int maxIndex = -1;
int minIndex = Integer.MAX_VALUE;
int indexBufferSize = numberOfVertex * bytesPerIndex;
boolean sequence = true;
int previousIndex = -1;
IMemoryReader memoryReader = MemoryReader.getMemoryReader(context.vinfo.ptr_index, indexBufferSize, bytesPerIndex);
for (int i = 0; i < numberOfVertex; i++) {
int index = memoryReader.readNext();
maxIndex = max(maxIndex, index);
minIndex = min(minIndex, index);
if (i > 0) {
// Are the indices all in sequence?
if (index != previousIndex + 1) {
sequence = false;
}
}
previousIndex = index;
}
if (isLogDebugEnabled) {
log.debug(String.format("getIndexedNumberOfVertexInfo %d: [%d..%d], sequence %b", numberOfVertex, minIndex, maxIndex, sequence));
}
return new VertexIndexInfo(minIndex, maxIndex, sequence);
}
private void executeCommandPRIM() {
int numberOfVertex = normalArgument & 0xFFFF;
int type = (normalArgument >> 16) & 0x7;
if (numberOfVertex == 0) {
return;
}
if (!Memory.isAddressGood(context.vinfo.ptr_vertex)) {
// Abort here to avoid a lot of useless memory read errors...
error(String.format("%s: Invalid vertex address 0x%08X", helper.getCommandString(PRIM), context.vinfo.ptr_vertex));
return;
}
if (type == GeCommands.PRIM_LINE || type == GeCommands.PRIM_LINES_STRIPS) {
if (context.lineSmoothFlag.isEnabled() && context.textureFunc == TFUNC_FRAGMENT_DOUBLE_TEXTURE_EFECT_REPLACE) {
if (isLogDebugEnabled) {
log.debug(String.format("The drawing of antialiasing lines is not supported, discarding them"));
}
endRendering(numberOfVertex);
return;
}
}
if (context.textureFlag.isEnabled() && !context.clearMode) {
int textureAddr = context.texture_base_pointer[0] & Memory.addressMask;
if (textureAddr > MemoryMap.END_VRAM && textureAddr < MemoryMap.START_VRAM + 0x800000) {
if (isLogWarnEnabled) {
log.warn(String.format("Texture in swizzled VRAM not supported 0x%08X", textureAddr));
}
endRendering(numberOfVertex);
return;
}
}
if (type == GeCommands.PRIM_CONTINUE_PREVIOUS_PRIM) {
// The PSP is continuing the previous PRIM command.
// If the previous PRIM was a strip or a fan, the strip/fan is
// continued as it was part of the previous PRIM command.
switch (previousPrim) {
case PRIM_LINES_STRIPS:
case PRIM_TRIANGLE_STRIPS:
case PRIM_TRIANGLE_FANS:
// Continuing the previous strip/fan is not implemented.
if (isLogWarnEnabled) {
log.warn(String.format("PRIM type=7 cannot continue previous strip/fan (%d)", previousPrim));
}
break;
}
type = previousPrim;
}
previousPrim = type;
if (numberOfVertex < minimumNumberOfVertex[type]) {
if (isLogDebugEnabled) {
log.debug(String.format("%s type %d unsufficient number of vertex %d", helper.getCommandString(PRIM), type, numberOfVertex));
}
endRendering(numberOfVertex);
return;
}
if (skipThisFrame) {
endRendering(numberOfVertex);
return;
}
updateGeBuf();
somethingDisplayed = true;
primCount++;
if (isGeProfilerEnabled) {
GEProfiler.startGeCmd(PRIM);
}
loadTexture();
// Logging
if (isLogDebugEnabled) {
switch (type) {
case PRIM_POINT:
log(String.format("prim %d point (%d vertices)", numberOfVertex, numberOfVertex));
break;
case PRIM_LINE:
log(String.format("prim %d line (%d vertices)", numberOfVertex / 2, numberOfVertex));
break;
case PRIM_LINES_STRIPS:
log(String.format("prim %d line strips (%d vertices)", numberOfVertex - 1, numberOfVertex));
break;
case PRIM_TRIANGLE:
log(String.format("prim %d triangle (%d vertices)", numberOfVertex / 3, numberOfVertex));
break;
case PRIM_TRIANGLE_STRIPS:
log(String.format("prim %d triangle strips (%d vertices)", numberOfVertex - 2, numberOfVertex));
break;
case PRIM_TRIANGLE_FANS:
log(String.format("prim %d triangle fans (%d vertices)", numberOfVertex - 2, numberOfVertex));
break;
case PRIM_SPRITES:
log(String.format("prim %d sprites (%d vertices)", numberOfVertex / 2, numberOfVertex));
break;
}
}
Memory mem = Memory.getInstance();
initRendering();
int nTexCoord = 2;
int nColor = 4;
int nVertex = 3;
boolean useTexture = false;
boolean useTextureFromNormal = false;
boolean useTextureFromNormalizedNormal = false;
boolean useTextureFromPosition = false;
// Use the texture from the vertex only is the texture flag is enabled or
// if the use of VAO is enabled
// (a VAO depends only on the vinfo.vtype structure, not on the texture flag setting)
if (context.textureFlag.isEnabled() || re.isVertexArrayAvailable()) {
if (context.vinfo.transform2D) {
// 2D is always using UV-mapping
if (context.vinfo.texture != 0) {
useTexture = true;
}
} else {
switch (context.tex_map_mode) {
case TMAP_TEXTURE_MAP_MODE_TEXTURE_COORDIATES_UV:
if (context.vinfo.texture != 0) {
useTexture = true;
}
break;
case TMAP_TEXTURE_MAP_MODE_TEXTURE_MATRIX: {
switch (context.tex_proj_map_mode) {
case TMAP_TEXTURE_PROJECTION_MODE_POSITION:
if (context.vinfo.position != 0) {
useTexture = true;
useTextureFromPosition = true;
nTexCoord = nVertex;
}
break;
case TMAP_TEXTURE_PROJECTION_MODE_TEXTURE_COORDINATES:
if (context.vinfo.texture != 0) {
useTexture = true;
}
break;
case TMAP_TEXTURE_PROJECTION_MODE_NORMAL:
if (context.vinfo.normal != 0) {
useTexture = true;
useTextureFromNormal = true;
nTexCoord = 3;
}
break;
case TMAP_TEXTURE_PROJECTION_MODE_NORMALIZED_NORMAL:
if (context.vinfo.normal != 0) {
useTexture = true;
useTextureFromNormalizedNormal = true;
nTexCoord = 3;
}
break;
}
break;
}
case TMAP_TEXTURE_MAP_MODE_ENVIRONMENT_MAP:
break;
default:
log.warn(String.format("Unhandled texture matrix mode %d", context.tex_map_mode));
break;
}
}
}
vertexStatistics.start();
context.vinfo.setMorphWeights(context.morph_weight);
context.vinfo.setDirty();
int numberOfWeightsForBuffer;
boolean mustComputeWeights;
if (context.vinfo.weight != 0) {
numberOfWeightsForBuffer = re.setBones(context.vinfo.skinningWeightCount, context.boneMatrixLinear);
mustComputeWeights = (numberOfWeightsForBuffer == 0);
} else {
numberOfWeightsForBuffer = re.setBones(0, null);
mustComputeWeights = false;
}
if (context.scissor_x2 < maxSpriteWidth) {
maxSpriteWidth = context.scissor_x2;
}
if (context.scissor_y2 < maxSpriteHeight) {
maxSpriteHeight = context.scissor_y2;
}
boolean needSetDataPointers = true;
if (re.canReadAllVertexInfo()) {
// The rendering engine can read the vertex infos by himself.
re.setVertexInfo(context.vinfo, true, context.useVertexColor, useTexture, type);
re.drawArrays(type, 0, numberOfVertex);
} else {
// In clear mode STENCIL, set the stencil value to the alpha value of the rendered color
if (context.clearMode && context.clearModeStencil && context.vinfo.color != 0) {
// Retrieve the alpha value of the 1st vertex
// (2nd vertex for a SPRITE as this is the vertex setting the rendered color for a SPRITE).
int addr = context.vinfo.getAddress(mem, type == PRIM_SPRITES ? 1 : 0);
context.vinfo.readVertex(mem, addr, v, false, isDoubleTexture2DCoords());
float alpha = v.c[3];
int stencilValue = PixelColor.getColor(alpha);
re.setStencilFunc(GeCommands.STST_FUNCTION_ALWAYS_PASS_STENCIL_TEST, stencilValue, 0xFF);
re.setStencilOp(GeCommands.SOP_KEEP_STENCIL_VALUE, GeCommands.SOP_KEEP_STENCIL_VALUE, GeCommands.SOP_REPLACE_STENCIL_VALUE);
}
// Do not use optimized VertexInfo reading when
// - disableOptimizedVertexInfoReading is true
// - using Vertex Cache unless all the vertices are supported natively
// - the Vertex are indexed
// - the PRIM_SPRITE primitive is used where it is not supported natively
// - the normals have to be normalized for the texture mapping
// - the weights have to be computed and are not supported natively
// - the vertex address is invalid
if ((!useVertexCache || re.canAllNativeVertexInfo())
&& context.vinfo.morphingVertexCount == 1
&& (type != PRIM_SPRITES || re.canNativeSpritesPrimitive())
&& !useTextureFromNormalizedNormal
&& !mustComputeWeights
&& Memory.isAddressGood(context.vinfo.ptr_vertex)
&& !disableOptimizedVertexInfoReading()) {
//
// Optimized VertexInfo reading:
// - do not copy the info already available in the OpenGL format
// (native format), load it into nativeBuffer (a direct buffer
// is required by OpenGL).
// - try to keep the info in "int" format when possible, convert
// to "float" only when necessary
// The best case is no reading and no conversion at all when all the
// vertex info are available in a format usable by OpenGL.
//
int numberOfVertexInfo = numberOfVertex;
int bytesPerIndex = VertexInfo.size_mapping[context.vinfo.index];
long indicesBufferOffset = 0;
int firstVertexInfo = 0;
int firstVertex = 0;
boolean hasIndex = context.vinfo.index != 0;
if (hasIndex) {
int indexBufferSize = numberOfVertex * bytesPerIndex;
VertexIndexInfo vertexIndexInfo = getVertexIndexInfo(bytesPerIndex, numberOfVertex);
numberOfVertexInfo = vertexIndexInfo.getNumberOfVertex();
firstVertexInfo = vertexIndexInfo.getMinIndex();
// No need to use indexed vertices when all the index are in sequence!
// Disable the index in such cases.
hasIndex = !vertexIndexInfo.isSequence();
if (hasIndex) {
Buffer indicesBuffer = mem.getBuffer(context.vinfo.ptr_index, indexBufferSize);
indicesBufferOffset = getBufferOffset(indicesBuffer, context.vinfo.ptr_index);
//
// The AMD/ATI driver seems to have problems using glDrawElements with a non-zero index offset.
// Provide a work-around by copying the indices buffer into a byte buffer where the correct
// index offset can be set. The index offset passed to glDrawElements can then be set to 0.
//
if (avoidDrawElementsWithNonZeroIndexOffset && indicesBufferOffset != 0) {
indexByteBuffer.clear();
Utilities.putBuffer(indexByteBuffer, indicesBuffer, ByteOrder.LITTLE_ENDIAN);
indexByteBuffer.limit(indexBufferSize + (int) indicesBufferOffset);
indexByteBuffer.position((int) indicesBufferOffset);
indicesBuffer = indexByteBuffer;
indicesBufferOffset = 0;
}
bufferManager.setBufferSubData(IRenderingEngine.RE_ELEMENT_ARRAY_BUFFER, indexBufferId, 0, indexBufferSize + (int) indicesBufferOffset, indicesBuffer, IRenderingEngine.RE_DYNAMIC_DRAW);
} else {
firstVertex = firstVertexInfo;
}
}
vertexReadingStatistics.start();
Buffer buffer = vertexInfoReader.read(context.vinfo, context.vinfo.ptr_vertex, firstVertexInfo, numberOfVertexInfo, re.canAllNativeVertexInfo());
vertexReadingStatistics.end();
int stride;
int size = context.vinfo.vertexSize * numberOfVertexInfo;
boolean useBufferManager;
boolean multiDrawArrays = false;
if (useVertexCache && buffer == null) {
stride = context.vinfo.vertexSize;
useBufferManager = false;
int vertexAddress = context.vinfo.ptr_vertex + firstVertexInfo * context.vinfo.vertexSize;
VertexBuffer vertexBuffer = VertexBufferManager.getInstance().getVertexBuffer(re, vertexAddress, size, stride, re.isVertexArrayAvailable());
Buffer vertexData = mem.getBuffer(vertexAddress, size);
vertexBuffer.load(re, vertexData, vertexAddress, size);
int multiDrawFirstVertex = 0;
// Don't try to mix VAO's with indexed vertices...
if (re.isVertexArrayAvailable() && firstVertex == 0) {
VertexArray vertexArray = VertexArrayManager.getInstance().getVertexArray(re, context.vinfo.vtype, vertexBuffer, vertexAddress, stride);
needSetDataPointers = vertexArray.bind(re);
multiDrawFirstVertex = vertexArray.getVertexOffset(vertexAddress);
} else {
// add buffer offset relative to vinfo.ptr_vertex (and not relative to vertexAddress)
vertexInfoReader.addNativeOffset(vertexBuffer.getBufferOffset(context.vinfo.ptr_vertex));
}
// Check if multiple PRIM's are defined in sequence and
// try to merge them into a single multiDrawArrays call.
int multiDrawNumberOfVertex = checkMultiDraw(multiDrawFirstVertex, type, numberOfVertex, multiDrawFirst, multiDrawCount, context.vinfo.index != 0);
if (multiDrawNumberOfVertex > 0) {
firstVertex = multiDrawFirstVertex;
multiDrawArrays = true;
numberOfVertex = multiDrawNumberOfVertex;
if (context.vinfo.index != 0) {
// Reload the now extended buffer for indices
VertexIndexInfo vertexIndexInfo = getVertexIndexInfo(bytesPerIndex, multiDrawNumberOfVertex);
numberOfVertexInfo = vertexIndexInfo.getNumberOfVertex();
firstVertexInfo = vertexIndexInfo.getMinIndex();
// No need to use indexed vertices when all the index are in sequence!
// Disable the index in such cases.
hasIndex = !vertexIndexInfo.isSequence();
if (hasIndex) {
int indexBufferSize = multiDrawNumberOfVertex * bytesPerIndex;
Buffer indicesBuffer = mem.getBuffer(context.vinfo.ptr_index, indexBufferSize);
indicesBufferOffset = getBufferOffset(indicesBuffer, context.vinfo.ptr_index);
bufferManager.setBufferSubData(IRenderingEngine.RE_ELEMENT_ARRAY_BUFFER, indexBufferId, 0, indexBufferSize + (int) indicesBufferOffset, indicesBuffer, IRenderingEngine.RE_DYNAMIC_DRAW);
}
vertexAddress = context.vinfo.ptr_vertex + firstVertexInfo * context.vinfo.vertexSize;
size = context.vinfo.vertexSize * numberOfVertexInfo;
} else {
size = context.vinfo.vertexSize * multiDrawNumberOfVertex;
}
vertexData = mem.getBuffer(vertexAddress, size);
vertexBuffer.load(re, vertexData, vertexAddress, size);
} else if (multiDrawFirstVertex > 0) {
// The VAO requires an updated first vertex
firstVertex = multiDrawFirstVertex;
}
if (needSetDataPointers) {
vertexBuffer.bind(re);
}
} else {
if (re.isVertexArrayAvailable()) {
re.bindVertexArray(0);
}
stride = vertexInfoReader.getStride();
useBufferManager = true;
if (buffer != null) {
bufferManager.setBufferSubData(IRenderingEngine.RE_ARRAY_BUFFER, bufferId, firstVertexInfo * stride, stride * numberOfVertexInfo, buffer, IRenderingEngine.RE_STREAM_DRAW);
}
if (vertexInfoReader.hasNative()) {
// Copy the VertexInfo from Memory to the nativeBuffer
// (a direct buffer is required by glXXXPointer())
int vertexAddr = context.vinfo.ptr_vertex + firstVertexInfo * context.vinfo.vertexSize;
Buffer vertexData = mem.getBuffer(vertexAddr, size);
size = fixNativeBufferOffset(vertexData, vertexAddr, size);
bufferManager.setBufferSubData(IRenderingEngine.RE_ARRAY_BUFFER, nativeBufferId, firstVertexInfo * context.vinfo.vertexSize, size, vertexData, IRenderingEngine.RE_STREAM_DRAW);
}
}
re.setVertexInfo(context.vinfo, re.canAllNativeVertexInfo(), context.useVertexColor, useTexture, type);
if (needSetDataPointers) {
if (hasIndex) {
bufferManager.bindBuffer(IRenderingEngine.RE_ELEMENT_ARRAY_BUFFER, indexBufferId);
}
if (useTexture) {
boolean textureNative;
int textureOffset;
int textureType;
if (useTextureFromNormal) {
textureNative = vertexInfoReader.isNormalNative();
textureOffset = vertexInfoReader.getNormalOffset();
textureType = vertexInfoReader.getNormalType();
nTexCoord = vertexInfoReader.getNormalNumberValues();
} else if (useTextureFromPosition) {
textureNative = vertexInfoReader.isPositionNative();
textureOffset = vertexInfoReader.getPositionOffset();
textureType = vertexInfoReader.getPositionType();
nTexCoord = vertexInfoReader.getPositionNumberValues();
} else {
textureNative = vertexInfoReader.isTextureNative();
textureOffset = vertexInfoReader.getTextureOffset();
textureType = vertexInfoReader.getTextureType();
nTexCoord = vertexInfoReader.getTextureNumberValues();
}
setTexCoordPointer(useTexture, nTexCoord, textureType, stride, textureOffset, textureNative, useBufferManager);
}
nVertex = vertexInfoReader.getPositionNumberValues();
nColor = vertexInfoReader.getColorNumberValues();
int nWeight = vertexInfoReader.getWeightNumberValues();
enableClientState(context.useVertexColor, useTexture);
setColorPointer(context.useVertexColor, nColor, vertexInfoReader.getColorType(), stride, vertexInfoReader.getColorOffset(), vertexInfoReader.isColorNative(), useBufferManager);
setNormalPointer(vertexInfoReader.getNormalType(), stride, vertexInfoReader.getNormalOffset(), vertexInfoReader.isNormalNative(), useBufferManager);
setWeightPointer(nWeight, vertexInfoReader.getWeightType(), stride, vertexInfoReader.getWeightOffset(), vertexInfoReader.isWeightNative(), useBufferManager);
setVertexPointer(nVertex, vertexInfoReader.getPositionType(), stride, vertexInfoReader.getPositionOffset(), vertexInfoReader.isPositionNative(), useBufferManager);
}
if (isLogDebugEnabled) {
if (!hasIndex && context.vinfo.index != 0) {
log.debug("Indexed vertex has been disabled, all the indices were sequential");
}
}
drawArraysStatistics.start();
if (hasIndex) {
if (multiDrawArrays) {
re.multiDrawElements(type, multiDrawFirst, multiDrawCount, indexTypes[context.vinfo.index], indicesBufferOffset);
} else {
re.drawElements(type, numberOfVertex, indexTypes[context.vinfo.index], indicesBufferOffset);
}
} else if (multiDrawArrays) {
re.multiDrawArrays(type, multiDrawFirst, multiDrawCount);
} else {
re.drawArrays(type, firstVertex, numberOfVertex);
}
drawArraysStatistics.end();
} else {
// Non-optimized VertexInfo reading
VertexInfo cachedVertexInfo = null;
if (useVertexCache) {
vertexCacheLookupStatistics.start();
cachedVertexInfo = VertexCache.getInstance().getVertex(context.vinfo, numberOfVertex, context.bone_uploaded_matrix, numberOfWeightsForBuffer);
vertexCacheLookupStatistics.end();
}
if (!useVertexCache && re.isVertexArrayAvailable()) {
re.bindVertexArray(0);
}
boolean readTexture = context.textureFlag.isEnabled() && !context.clearMode;
if (useVertexCache) {
// When using the vertex cache, do not try to optimize the texture reading.
// The cached vertex could be reused in other situations where the texture
// is required.
readTexture = true;
}
switch (type) {
case PRIM_POINT:
case PRIM_LINE:
case PRIM_LINES_STRIPS:
case PRIM_TRIANGLE:
case PRIM_TRIANGLE_STRIPS:
case PRIM_TRIANGLE_FANS:
re.setVertexInfo(context.vinfo, false, context.useVertexColor, useTexture, type);
if (cachedVertexInfo == null) {
vertexReadingStatistics.start();
int ii = 0;
for (int i = 0; i < numberOfVertex; i++) {
int addr = context.vinfo.getAddress(mem, i);
context.vinfo.readVertex(mem, addr, v, readTexture, isDoubleTexture2DCoords());
// Do skinning first as it modifies v.p and v.n
if (mustComputeWeights && context.vinfo.position != 0) {
doSkinning(context.bone_uploaded_matrix, context.vinfo, v);
}
if (useTextureFromNormal) {
floatBufferArray[ii++] = v.n[0];
floatBufferArray[ii++] = v.n[1];
floatBufferArray[ii++] = v.n[2];
} else if (useTextureFromNormalizedNormal) {
float normalLength = (float) Math.sqrt(v.n[0] * v.n[0] + v.n[1] * v.n[1] + v.n[2] * v.n[2]);
floatBufferArray[ii++] = v.n[0] / normalLength;
floatBufferArray[ii++] = v.n[1] / normalLength;
floatBufferArray[ii++] = v.n[2] / normalLength;
} else if (useTextureFromPosition) {
floatBufferArray[ii++] = v.p[0];
floatBufferArray[ii++] = v.p[1];
floatBufferArray[ii++] = v.p[2];
} else if (useTexture || context.vinfo.texture != 0) {
floatBufferArray[ii++] = v.t[0];
floatBufferArray[ii++] = v.t[1];
}
if (context.useVertexColor) {
floatBufferArray[ii++] = v.c[0];
floatBufferArray[ii++] = v.c[1];
floatBufferArray[ii++] = v.c[2];
floatBufferArray[ii++] = v.c[3];
}
if (context.vinfo.normal != 0) {
floatBufferArray[ii++] = v.n[0];
floatBufferArray[ii++] = v.n[1];
floatBufferArray[ii++] = v.n[2];
}
if (context.vinfo.position != 0) {
floatBufferArray[ii++] = v.p[0];
floatBufferArray[ii++] = v.p[1];
floatBufferArray[ii++] = v.p[2];
}
if (numberOfWeightsForBuffer > 0) {
for (int j = 0; j < numberOfWeightsForBuffer; j++) {
floatBufferArray[ii++] = v.boneWeights[j];
}
}
if (isLogTraceEnabled) {
if (context.vinfo.texture != 0 && context.vinfo.position != 0) {
log.trace(" vertex#" + i + " (" + ((int) v.t[0]) + "," + ((int) v.t[1]) + ") at (" + ((int) v.p[0]) + "," + ((int) v.p[1]) + "," + ((int) v.p[2]) + ")");
}
}
}
int bufferSizeInFloats = ii;
vertexReadingStatistics.end();
if (useVertexCache) {
cachedVertexInfo = new VertexInfo(context.vinfo);
VertexCache.getInstance().addVertex(re, cachedVertexInfo, numberOfVertex, context.bone_uploaded_matrix, numberOfWeightsForBuffer);
needSetDataPointers = cachedVertexInfo.loadVertex(re, floatBufferArray, bufferSizeInFloats);
} else {
ByteBuffer byteBuffer = bufferManager.getBuffer(bufferId);
byteBuffer.clear();
byteBuffer.asFloatBuffer().put(floatBufferArray, 0, bufferSizeInFloats);
bufferManager.setBufferSubData(IRenderingEngine.RE_ARRAY_BUFFER, bufferId, 0, bufferSizeInFloats * SIZEOF_FLOAT, byteBuffer, IRenderingEngine.RE_STREAM_DRAW);
}
} else {
if (isLogDebugEnabled) {
log.debug("Reusing cached Vertex Data");
}
needSetDataPointers = cachedVertexInfo.bindVertex(re);
}
if (needSetDataPointers) {
setDataPointers(nVertex, context.useVertexColor, nColor, useTexture, nTexCoord, context.vinfo.normal != 0, numberOfWeightsForBuffer, cachedVertexInfo == null);
}
drawArraysStatistics.start();
re.drawArrays(type, 0, numberOfVertex);
drawArraysStatistics.end();
maxSpriteHeight = Integer.MAX_VALUE;
maxSpriteWidth = Integer.MAX_VALUE;
break;
case PRIM_SPRITES:
re.setVertexInfo(context.vinfo, false, context.useVertexColor, useTexture, IRenderingEngine.RE_QUADS);
if (!context.clearMode) {
re.disableFlag(IRenderingEngine.GU_CULL_FACE);
}
float[] mvpMatrix = null;
if (!context.vinfo.transform2D) {
mvpMatrix = new float[4 * 4];
// pre-Compute the MVP (Model-View-Projection) matrix
matrixMult(mvpMatrix, context.model_uploaded_matrix, context.view_uploaded_matrix);
matrixMult(mvpMatrix, mvpMatrix, getProjectionMatrix());
}
if (cachedVertexInfo == null) {
vertexReadingStatistics.start();
int ii = 0;
for (int i = 0; i < numberOfVertex; i += 2) {
int addr1 = context.vinfo.getAddress(mem, i);
int addr2 = context.vinfo.getAddress(mem, i + 1);
context.vinfo.readVertex(mem, addr1, v1, readTexture, isDoubleTexture2DCoords());
context.vinfo.readVertex(mem, addr2, v2, readTexture, isDoubleTexture2DCoords());
v1.p[2] = v2.p[2];
if (v2.p[1] > maxSpriteHeight) {
maxSpriteHeight = (int) v2.p[1];
}
if (v2.p[1] > maxSpriteWidth) {
maxSpriteWidth = (int) v2.p[1];
}
//
// Texture flip tested using the GElist application:
// - it depends on the X and Y coordinates:
// GU_TRANSFORM_3D:
// X1 < X2 && Y1 < Y2 : flipped
// X1 > X2 && Y1 > Y2 : flipped
// X1 < X2 && Y1 > Y2 : not flipped
// X1 > X2 && Y1 < Y2 : not flipped
// GU_TRANSFORM_2D: opposite results because
// the Y-Axis is upside-down in 2D
// X1 < X2 && Y1 < Y2 : not flipped
// X1 > X2 && Y1 > Y2 : not flipped
// X1 < X2 && Y1 > Y2 : flipped
// X1 > X2 && Y1 < Y2 : flipped
// - the tests for GU_TRANSFORM_3D are based on the coordinates
// after the MVP (Model-View-Projection) transformation
// - texture coordinates are irrelevant
//
float x1, y1, x2, y2;
if (mvpMatrix == null) {
x1 = v1.p[0];
y1 = -v1.p[1]; // Y-Axis is upside-down in 2D
x2 = v2.p[0];
y2 = -v2.p[1]; // Y-Axis is upside-down in 2D
} else {
// Apply the MVP transformation to both position coordinates
float[] mvpPosition = new float[2];
vectorMult(mvpPosition, mvpMatrix, v1.p);
x1 = mvpPosition[0];
y1 = mvpPosition[1];
vectorMult(mvpPosition, mvpMatrix, v2.p);
x2 = mvpPosition[0];
y2 = mvpPosition[1];
}
boolean flippedTexture = (y1 < y2 && x1 < x2)
|| (y1 > y2 && x1 > x2);
if (isLogDebugEnabled) {
log(String.format(" sprite (%.0f,%.0f)-(%.0f,%.0f) at (%.0f,%.0f,%.0f)-(%.0f,%.0f,%.0f)%s", v1.t[0], v1.t[1], v2.t[0], v2.t[1], v1.p[0], v1.p[1], v1.p[2], v2.p[0], v2.p[1], v2.p[2], flippedTexture ? " flipped" : ""));
}
// V1
if (context.vinfo.texture != 0) {
floatBufferArray[ii++] = v1.t[0];
floatBufferArray[ii++] = v1.t[1];
}
if (context.useVertexColor) {
floatBufferArray[ii++] = v2.c[0];
floatBufferArray[ii++] = v2.c[1];
floatBufferArray[ii++] = v2.c[2];
floatBufferArray[ii++] = v2.c[3];
}
if (context.vinfo.normal != 0) {
floatBufferArray[ii++] = v2.n[0];
floatBufferArray[ii++] = v2.n[1];
floatBufferArray[ii++] = v2.n[2];
}
if (context.vinfo.position != 0) {
floatBufferArray[ii++] = v1.p[0];
floatBufferArray[ii++] = v1.p[1];
floatBufferArray[ii++] = v1.p[2];
}
if (context.vinfo.texture != 0) {
if (flippedTexture) {
floatBufferArray[ii++] = v2.t[0];
floatBufferArray[ii++] = v1.t[1];
} else {
floatBufferArray[ii++] = v1.t[0];
floatBufferArray[ii++] = v2.t[1];
}
}
if (context.useVertexColor) {
floatBufferArray[ii++] = v2.c[0];
floatBufferArray[ii++] = v2.c[1];
floatBufferArray[ii++] = v2.c[2];
floatBufferArray[ii++] = v2.c[3];
}
if (context.vinfo.normal != 0) {
floatBufferArray[ii++] = v2.n[0];
floatBufferArray[ii++] = v2.n[1];
floatBufferArray[ii++] = v2.n[2];
}
if (context.vinfo.position != 0) {
floatBufferArray[ii++] = v1.p[0];
floatBufferArray[ii++] = v2.p[1];
floatBufferArray[ii++] = v2.p[2];
}
// V2
if (context.vinfo.texture != 0) {
floatBufferArray[ii++] = v2.t[0];
floatBufferArray[ii++] = v2.t[1];
}
if (context.useVertexColor) {
floatBufferArray[ii++] = v2.c[0];
floatBufferArray[ii++] = v2.c[1];
floatBufferArray[ii++] = v2.c[2];
floatBufferArray[ii++] = v2.c[3];
}
if (context.vinfo.normal != 0) {
floatBufferArray[ii++] = v2.n[0];
floatBufferArray[ii++] = v2.n[1];
floatBufferArray[ii++] = v2.n[2];
}
if (context.vinfo.position != 0) {
floatBufferArray[ii++] = v2.p[0];
floatBufferArray[ii++] = v2.p[1];
floatBufferArray[ii++] = v2.p[2];
}
if (context.vinfo.texture != 0) {
if (flippedTexture) {
floatBufferArray[ii++] = v1.t[0];
floatBufferArray[ii++] = v2.t[1];
} else {
floatBufferArray[ii++] = v2.t[0];
floatBufferArray[ii++] = v1.t[1];
}
}
if (context.useVertexColor) {
floatBufferArray[ii++] = v2.c[0];
floatBufferArray[ii++] = v2.c[1];
floatBufferArray[ii++] = v2.c[2];
floatBufferArray[ii++] = v2.c[3];
}
if (context.vinfo.normal != 0) {
floatBufferArray[ii++] = v2.n[0];
floatBufferArray[ii++] = v2.n[1];
floatBufferArray[ii++] = v2.n[2];
}
if (context.vinfo.position != 0) {
floatBufferArray[ii++] = v2.p[0];
floatBufferArray[ii++] = v1.p[1];
floatBufferArray[ii++] = v2.p[2];
}
}
int bufferSizeInFloats = ii;
vertexReadingStatistics.end();
if (useVertexCache) {
cachedVertexInfo = new VertexInfo(context.vinfo);
VertexCache.getInstance().addVertex(re, cachedVertexInfo, numberOfVertex, context.bone_uploaded_matrix, numberOfWeightsForBuffer);
needSetDataPointers = cachedVertexInfo.loadVertex(re, floatBufferArray, bufferSizeInFloats);
} else {
ByteBuffer byteBuffer = bufferManager.getBuffer(bufferId);
byteBuffer.clear();
byteBuffer.asFloatBuffer().put(floatBufferArray, 0, bufferSizeInFloats);
bufferManager.setBufferSubData(IRenderingEngine.RE_ARRAY_BUFFER, bufferId, 0, bufferSizeInFloats * SIZEOF_FLOAT, byteBuffer, IRenderingEngine.RE_STREAM_DRAW);
}
} else {
if (isLogDebugEnabled) {
log.debug("Reusing cached Vertex Data");
}
needSetDataPointers = cachedVertexInfo.bindVertex(re);
}
if (needSetDataPointers) {
setDataPointers(nVertex, context.useVertexColor, nColor, useTexture, nTexCoord, context.vinfo.normal != 0, 0, cachedVertexInfo == null);
}
drawArraysStatistics.start();
re.drawArrays(IRenderingEngine.RE_QUADS, 0, numberOfVertex * 2);
drawArraysStatistics.end();
if (!context.clearMode) {
context.cullFaceFlag.updateEnabled();
}
break;
}
}
}
vertexStatistics.end();
// Don't capture the ram if the vertex list is embedded in the display list. TODO handle stall_addr == 0 better
// TODO may need to move inside the loop if indices are used, or find the largest index so we can calculate the size of the vertex list
if (State.captureGeNextFrame) {
if (!isVertexBufferEmbedded()) {
log.info("Capture PRIM");
CaptureManager.captureRAM(context.vinfo.ptr_vertex, context.vinfo.vertexSize * numberOfVertex);
}
display.captureGeImage();
textureChanged = true;
}
if (export3D) {
exportCommandPRIM(numberOfVertex, type);
}
endRendering(numberOfVertex);
}
private void exportCommandPRIM(int numberOfVertex, int type) {
// Do not export 2D
if (context.vinfo.transform2D) {
return;
}
// Only export triangles and triangle strips.
if (type != PRIM_TRIANGLE && type != PRIM_TRIANGLE_STRIPS) {
return;
}
exporter.startPrimitive(numberOfVertex, type);
Memory mem = Memory.getInstance();
final float[] position = new float[4];
final float[] modelViewMatrix = new float[4 * 4];
final float[] vertexPosition = new float[4];
final float[] normalizedN = new float[3];
final VertexState transformedV = new VertexState();
Utilities.matrixMult(modelViewMatrix, context.view_uploaded_matrix, context.model_uploaded_matrix);
for (int i = 0; i < numberOfVertex; i++) {
int addr = context.vinfo.getAddress(mem, i);
context.vinfo.readVertex(mem, addr, v, true, isDoubleTexture2DCoords());
if (context.vinfo.weight != 0 && context.vinfo.position != 0) {
doSkinning(context.bone_uploaded_matrix, context.vinfo, v);
}
// Multiply the vertex position by the model/view matrix and adjust with W
vertexPosition[0] = v.p[0];
vertexPosition[1] = v.p[1];
vertexPosition[2] = v.p[2];
vertexPosition[3] = 1f;
Utilities.vectorMult44(position, modelViewMatrix, vertexPosition);
float invertedW = 1f / position[3];
transformedV.p[0] = position[0] * invertedW;
transformedV.p[1] = position[1] * invertedW;
transformedV.p[2] = position[2] * invertedW;
transformedV.c[0] = v.c[0];
transformedV.c[1] = v.c[1];
transformedV.c[2] = v.c[2];
transformedV.n[0] = v.n[0];
transformedV.n[1] = v.n[1];
transformedV.n[2] = v.n[2];
transformedV.t[0] = v.t[0];
transformedV.t[1] = v.t[1];
switch (context.tex_map_mode) {
case GeCommands.TMAP_TEXTURE_MAP_MODE_TEXTURE_COORDIATES_UV:
transformedV.t[0] = v.t[0] * context.tex_scale_x + context.tex_translate_x;
transformedV.t[1] = v.t[1] * context.tex_scale_y + context.tex_translate_y;
break;
case GeCommands.TMAP_TEXTURE_MAP_MODE_TEXTURE_MATRIX:
float x = v.t[0];
float y = v.t[1];
float z = 0f;
switch (context.tex_proj_map_mode) {
case GeCommands.TMAP_TEXTURE_PROJECTION_MODE_POSITION:
x = v.p[0];
y = v.p[1];
z = v.p[2];
break;
case GeCommands.TMAP_TEXTURE_PROJECTION_MODE_TEXTURE_COORDINATES:
x = v.t[0];
y = v.t[1];
z = 0f;
break;
case GeCommands.TMAP_TEXTURE_PROJECTION_MODE_NORMALIZED_NORMAL:
Utilities.normalize3(normalizedN, v.n);
x = normalizedN[0];
y = normalizedN[1];
z = normalizedN[2];
break;
case GeCommands.TMAP_TEXTURE_PROJECTION_MODE_NORMAL:
x = v.n[0];
y = v.n[1];
z = v.n[2];
break;
}
transformedV.t[0] = x * context.texture_uploaded_matrix[0] + y * context.texture_uploaded_matrix[4] + z * context.texture_uploaded_matrix[8] + context.texture_uploaded_matrix[12];
transformedV.t[1] = x * context.texture_uploaded_matrix[1] + y * context.texture_uploaded_matrix[5] + z * context.texture_uploaded_matrix[9] + context.texture_uploaded_matrix[13];
break;
case GeCommands.TMAP_TEXTURE_MAP_MODE_ENVIRONMENT_MAP:
// TODO Implement TMAP_TEXTURE_MAP_MODE_ENVIRONMENT_MAP
break;
}
// Textures on PSP are upside-down as compared to the export format:
// (0,0) is the upper-left corner of the texture on a PSP,
// (0,0) is the lower-left corner of the texture in the export format.
transformedV.t[1] = 1f - transformedV.t[1];
exporter.exportVertex(v, transformedV);
}
exporter.endVertex(numberOfVertex, type);
// Export the texture
String textureFileName = null;
if (context.textureFlag.isEnabled()) {
final int level = 0;
int textureAddr = context.texture_base_pointer[level];
int textureWidth = context.texture_width[level];
int textureHeight = context.texture_height[level];
int textureBufferWidth = context.texture_buffer_width[level];
IMemoryReader imageReader = ImageReader.getImageReader(textureAddr, textureWidth, textureHeight, textureBufferWidth, context.texture_storage, context.texture_swizzle, context.tex_clut_addr, context.tex_clut_mode, context.tex_clut_num_blocks, context.tex_clut_start, context.tex_clut_shift, context.tex_clut_mask, clut_buffer32, clut_buffer16);
CaptureImage captureImage = new CaptureImage(textureAddr, level, imageReader, textureWidth, textureHeight, textureBufferWidth, false, true, null);
captureImage.setDirectory(export3DDirectory);
captureImage.setFileFormat("png");
if (IRenderingEngine.isTextureTypeIndexed[context.texture_storage]) {
// When the image is using a CLUT, add the clut address to the file name.
// Some games are reusing the same texture with different cluts.
String fileNameSuffix = String.format("_%08X", context.tex_clut_addr);
captureImage.setFileNameSuffix(fileNameSuffix);
}
try {
if (!captureImage.fileExists()) {
captureImage.write();
}
textureFileName = captureImage.getFileName();
} catch (IOException e) {
log.error("Export Texture", e);
}
}
exporter.exportTexture(textureFileName);
exporter.endPrimitive(numberOfVertex, type);
}
private void executeCommandTRXKICK() {
context.textureTx_pixelSize = normalArgument & 0x1;
context.textureTx_sourceAddress &= Memory.addressMask;
context.textureTx_destinationAddress &= Memory.addressMask;
if (isLogDebugEnabled) {
log(String.format("%s from 0x%08X(%d,%d) to 0x%08X(%d,%d), width=%d, height=%d, pixelSize=%d",
helper.getCommandString(TRXKICK),
context.textureTx_sourceAddress,
context.textureTx_sx,
context.textureTx_sy,
context.textureTx_destinationAddress,
context.textureTx_dx,
context.textureTx_dy,
context.textureTx_width,
context.textureTx_height,
context.textureTx_pixelSize));
}
if (!Memory.isAddressGood(context.textureTx_sourceAddress)) {
error(String.format("%s invalid source address 0x%08X", helper.getCommandString(TRXKICK), context.textureTx_sourceAddress));
return;
}
if (!Memory.isAddressGood(context.textureTx_destinationAddress)) {
error(String.format("%s invalid destination address 0x%08X", helper.getCommandString(TRXKICK), context.textureTx_destinationAddress));
return;
}
if (isGeProfilerEnabled) {
GEProfiler.startGeCmd(TRXKICK);
}
updateGeBuf();
int pixelFormatGe = context.psm;
int bpp = (context.textureTx_pixelSize == TRXKICK_16BIT_TEXEL_SIZE) ? 2 : 4;
int bppGe = sceDisplay.getPixelFormatBytes(pixelFormatGe);
memoryForGEUpdated();
boolean transferUsingMemcpy = false;
if (!display.isGeAddress(context.textureTx_destinationAddress) || bpp != bppGe || display.isUsingSoftwareRenderer()) {
transferUsingMemcpy = true;
}
if (display.isGeAddress(context.textureTx_sourceAddress)) {
re.waitForRenderingCompletion();
// Force a copy to the memory if performing the transfer using memcpy
display.copyGeToMemory(true, transferUsingMemcpy);
}
if (transferUsingMemcpy) {
if (isLogDebugEnabled) {
if (bpp != bppGe) {
log(helper.getCommandString(TRXKICK) + " BPP not compatible with GE");
} else {
log(helper.getCommandString(TRXKICK) + " not in Ge Address space");
}
}
usingTRXKICK = true;
// Remove the destination address from the texture cache
if (canCacheTexture(context.textureTx_destinationAddress)) {
TextureCache textureCache = TextureCache.getInstance();
textureCache.resetTextureAlreadyHashed(context.textureTx_destinationAddress, context.tex_clut_addr, context.tex_clut_start, context.tex_clut_mode);
textureCache.resetTextureAlreadyHashed(context.textureTx_destinationAddress, 0, 0, 0);
}
if (context.textureTx_destinationAddress == (context.texture_base_pointer[0] & Memory.addressMask)) {
textureChanged = true;
}
int width = context.textureTx_width;
int height = context.textureTx_height;
int srcAddress = context.textureTx_sourceAddress + (context.textureTx_sy * context.textureTx_sourceLineWidth + context.textureTx_sx) * bpp;
int dstAddress = context.textureTx_destinationAddress + (context.textureTx_dy * context.textureTx_destinationLineWidth + context.textureTx_dx) * bpp;
Memory memory = Memory.getInstance();
if (context.textureTx_sourceLineWidth == width && context.textureTx_destinationLineWidth == width) {
// All the lines are adjacent in memory,
// copy them all in a single memcpy operation.
int copyLength = height * width * bpp;
if (isLogDebugEnabled) {
log(String.format("%s memcpy(0x%08X-0x%08X, 0x%08X, 0x%X)", helper.getCommandString(TRXKICK), dstAddress, dstAddress + copyLength, srcAddress, copyLength));
}
memory.memcpy(dstAddress, srcAddress, copyLength);
} else {
// The lines are not adjacent in memory: copy line by line.
int copyLength = width * bpp;
int srcLineLength = context.textureTx_sourceLineWidth * bpp;
int dstLineLength = context.textureTx_destinationLineWidth * bpp;
for (int y = 0; y < height; y++) {
if (isLogDebugEnabled) {
log(String.format("%s memcpy(0x%08X-0x%08X, 0x%08X, 0x%X)", helper.getCommandString(TRXKICK), dstAddress, dstAddress + copyLength, srcAddress, copyLength));
}
memory.memcpy(dstAddress, srcAddress, copyLength);
srcAddress += srcLineLength;
dstAddress += dstLineLength;
}
}
if (State.captureGeNextFrame) {
log.warn("TRXKICK outside of Ge Address space not supported in capture yet");
}
} else if (!skipThisFrame) { // TRXKICK in GE space can be skipped when skipping this frame
int width = context.textureTx_width;
int height = context.textureTx_height;
int dx = context.textureTx_dx;
int dy = context.textureTx_dy;
int lineWidth = context.textureTx_sourceLineWidth;
int geAddr = display.getTopAddrGe();
dy += (context.textureTx_destinationAddress - geAddr) / (display.getBufferWidthGe() * bpp);
dx += ((context.textureTx_destinationAddress - geAddr) % (display.getBufferWidthGe() * bpp)) / bpp;
if (isLogDebugEnabled) {
log(helper.getCommandString(TRXKICK) + " in Ge Address space: dx=" + dx + ", dy=" + dy + ", width=" + width + ", height=" + height + ", lineWidth=" + lineWidth + ", bpp=" + bpp);
}
if (re.isVertexArrayAvailable()) {
re.bindVertexArray(0);
}
int bufferHeight = Utilities.makePow2(height);
int textureSize = lineWidth * bufferHeight * bpp;
int sourceAddr = context.textureTx_sourceAddress + (context.textureTx_sy * lineWidth + context.textureTx_sx) * bpp;
Buffer buffer = Memory.getInstance().getBuffer(sourceAddr, textureSize);
if (State.captureGeNextFrame) {
log.info("Capture TRXKICK");
CaptureManager.captureRAM(context.textureTx_sourceAddress, lineWidth * height * bpp);
}
//
// glTexImage2D only supports
// width = (1 << n) for some integer n
// height = (1 << m) for some integer m
//
// This the reason why we are also using glTexSubImage2D.
//
int texture = re.genTexture();
re.bindTexture(texture);
re.setTextureFormat(pixelFormatGe, false);
re.setTexImage(0, pixelFormatGe, lineWidth, bufferHeight, pixelFormatGe, pixelFormatGe, 0, null);
re.setTextureMipmapMinFilter(TFLT_NEAREST);
re.setTextureMipmapMagFilter(TFLT_NEAREST);
re.setTextureMipmapMinLevel(0);
re.setTextureMipmapMaxLevel(0);
re.setTextureWrapMode(TWRAP_WRAP_MODE_CLAMP, TWRAP_WRAP_MODE_CLAMP);
re.setPixelStore(lineWidth, bpp);
re.setTexSubImage(0, 0, 0, width, height, pixelFormatGe, pixelFormatGe, textureSize, buffer);
re.startDirectRendering(true, false, true, true, false, 480, 272);
re.setTextureFormat(pixelFormatGe, false);
float texCoordX = width / (float) lineWidth;
float texCoordY = height / (float) bufferHeight;
int i = 0;
floatBufferArray[i++] = texCoordX;
floatBufferArray[i++] = texCoordY;
floatBufferArray[i++] = dx + width;
floatBufferArray[i++] = dy + height;
floatBufferArray[i++] = 0;
floatBufferArray[i++] = texCoordY;
floatBufferArray[i++] = dx;
floatBufferArray[i++] = dy + height;
floatBufferArray[i++] = 0;
floatBufferArray[i++] = 0;
floatBufferArray[i++] = dx;
floatBufferArray[i++] = dy;
floatBufferArray[i++] = texCoordX;
floatBufferArray[i++] = 0;
floatBufferArray[i++] = dx + width;
floatBufferArray[i++] = dy;
IREBufferManager bufferManager = re.getBufferManager();
ByteBuffer byteBuffer = bufferManager.getBuffer(bufferId);
byteBuffer.clear();
int bufferSizeInFloats = i;
byteBuffer.asFloatBuffer().put(floatBufferArray, 0, bufferSizeInFloats);
re.setVertexInfo(null, false, false, true, IRenderingEngine.RE_QUADS);
re.enableClientState(IRenderingEngine.RE_TEXTURE);
re.disableClientState(IRenderingEngine.RE_COLOR);
re.disableClientState(IRenderingEngine.RE_NORMAL);
re.enableClientState(IRenderingEngine.RE_VERTEX);
bufferManager.setTexCoordPointer(bufferId, 2, IRenderingEngine.RE_FLOAT, 4 * SIZEOF_FLOAT, 0);
bufferManager.setVertexPointer(bufferId, 2, IRenderingEngine.RE_FLOAT, 4 * SIZEOF_FLOAT, 2 * SIZEOF_FLOAT);
bufferManager.setBufferSubData(IRenderingEngine.RE_ARRAY_BUFFER, bufferId, 0, bufferSizeInFloats * SIZEOF_FLOAT, byteBuffer, IRenderingEngine.RE_STREAM_DRAW);
re.drawArrays(IRenderingEngine.RE_QUADS, 0, 4);
re.endDirectRendering();
re.deleteTexture(texture);
somethingDisplayed = true;
}
}
private void executeCommandBBOX() {
Memory mem = Memory.getInstance();
int numberOfVertexBoundingBox = normalArgument & 0xFF;
if (context.vinfo.ptr_vertex == 0) {
// The GE is initialized with a NULL vertex address, do not log an error
if (isLogDebugEnabled) {
log.debug(String.format("%s null vertex address", helper.getCommandString(BBOX)));
}
return;
} else if (!Memory.isAddressGood(context.vinfo.ptr_vertex)) {
// Abort here to avoid a lot of useless memory read errors...
error(String.format("%s Invalid vertex address 0x%08X", helper.getCommandString(BBOX), context.vinfo.ptr_vertex));
return;
} else if (context.vinfo.position == 0) {
log.warn(helper.getCommandString(BBOX) + " no positions for vertex!");
return;
} else if ((numberOfVertexBoundingBox % 8) != 0) {
// How to interpret non-multiple of 8?
log.warn(helper.getCommandString(BBOX) + " unsupported numberOfVertex=" + numberOfVertexBoundingBox);
} else if (isLogDebugEnabled) {
log.debug(helper.getCommandString(BBOX) + " numberOfVertex=" + numberOfVertexBoundingBox);
}
if (skipThisFrame) {
return;
}
isBoundingBox = true;
if (isGeProfilerEnabled) {
GEProfiler.startGeCmd(BBOX);
}
initRendering();
re.beginBoundingBox(numberOfVertexBoundingBox);
for (int i = 0; i < numberOfVertexBoundingBox; i++) {
int addr = context.vinfo.getAddress(mem, i);
context.vinfo.readVertex(mem, addr, v, false, isDoubleTexture2DCoords());
if (context.vinfo.weight != 0 && context.vinfo.position != 0) {
doSkinning(context.bone_uploaded_matrix, context.vinfo, v);
}
if (isLogDebugEnabled) {
log.debug(String.format("%s (%f,%f,%f)", helper.getCommandString(BBOX), v.p[0], v.p[1], v.p[2]));
}
int vertexIndex = i % 8;
bboxVertices[vertexIndex][0] = v.p[0];
bboxVertices[vertexIndex][1] = v.p[1];
bboxVertices[vertexIndex][2] = v.p[2];
if (vertexIndex == 7) {
re.drawBoundingBox(bboxVertices);
}
}
re.endBoundingBox(context.vinfo);
endRendering(numberOfVertexBoundingBox);
isBoundingBox = false;
}
private void executeCommandBJUMP() {
boolean takeConditionalJump = false;
if (skipThisFrame) {
takeConditionalJump = true;
} else if (export3D && !export3DOnlyVisible) {
takeConditionalJump = false;
} else {
takeConditionalJump = !re.isBoundingBoxVisible();
}
if (takeConditionalJump) {
int oldPc = currentList.getPc();
currentList.jumpRelativeOffset(normalArgument);
int newPc = currentList.getPc();
if (isLogDebugEnabled) {
log(String.format("%s old PC: 0x%08X, new PC: 0x%08X", helper.getCommandString(BJUMP), oldPc, newPc));
}
} else {
if (isLogDebugEnabled) {
log(String.format("%s not taking Conditional Jump", helper.getCommandString(BJUMP)));
}
}
}
private void executeCommandBONE() {
// Multiple BONE matrix can be loaded in sequence
// without having to issue a BOFS for each matrix.
int matrixIndex = boneMatrixIndex / 12;
int elementIndex = boneMatrixIndex % 12;
if (matrixIndex >= context.bone_uploaded_matrix.length) {
if (isLogDebugEnabled) {
log.debug(String.format("Ignoring BONE matrix element: boneMatrixIndex=%d", boneMatrixIndex));
}
} else {
float floatArgument = floatArgument(normalArgument);
context.bone_uploaded_matrix[matrixIndex][elementIndex] = floatArgument;
context.boneMatrixLinear[(boneMatrixIndex / 3) * 4 + (boneMatrixIndex % 3)] = floatArgument;
if (matrixIndex >= boneMatrixLinearUpdatedMatrix) {
boneMatrixLinearUpdatedMatrix = matrixIndex + 1;
}
boneMatrixIndex++;
if (isLogDebugEnabled && (boneMatrixIndex % 12) == 0) {
for (int x = 0; x < 3; x++) {
log.debug(String.format("bone matrix %d %.2f %.2f %.2f %.2f",
matrixIndex,
context.bone_uploaded_matrix[matrixIndex][x + 0],
context.bone_uploaded_matrix[matrixIndex][x + 3],
context.bone_uploaded_matrix[matrixIndex][x + 6],
context.bone_uploaded_matrix[matrixIndex][x + 9]));
}
}
}
}
private void executeCommandNOP() {
if (isLogDebugEnabled) {
log(helper.getCommandString(NOP));
}
nopCount++;
// Some application do have more than 5000 NOP instructions inside the first 2 lists
// (i.e. at application initialization), so exclude these lists.
if (listCount > 2 && nopCount > 5000) {
// More than 5000 NOP instructions executed during this list,
// something must be wrong...
error(String.format("Too many NOP instructions executed (%d) at 0x%08X, list %s", nopCount, currentList.getPc(), currentList));
} else {
// Check if we are not reading from an invalid memory region.
// Abort the list if this is the case.
// This is only done in the NOP command to not impact performance.
checkCurrentListPc();
}
}
private void executeCommandVADDR() {
context.vinfo.ptr_vertex = currentList.getAddressRelOffset(normalArgument);
if (isLogDebugEnabled) {
log(helper.getCommandString(VADDR) + " " + String.format("%08x", context.vinfo.ptr_vertex));
}
}
private void executeCommandIADDR() {
context.vinfo.ptr_index = currentList.getAddressRelOffset(normalArgument);
if (isLogDebugEnabled) {
log(helper.getCommandString(IADDR) + " " + String.format("%08x", context.vinfo.ptr_index));
}
}
private void executeCommandBEZIER() {
int ucount = normalArgument & 0xFF;
int vcount = (normalArgument >> 8) & 0xFF;
if (isLogDebugEnabled) {
log(helper.getCommandString(BEZIER) + " ucount=" + ucount + ", vcount=" + vcount);
}
if (skipThisFrame) {
endRendering(ucount * vcount);
return;
}
if (isGeProfilerEnabled) {
GEProfiler.startGeCmd(BEZIER);
}
updateGeBuf();
somethingDisplayed = true;
loadTexture();
drawBezier(ucount, vcount);
}
private void executeCommandSPLINE() {
// Number of control points.
int sp_ucount = normalArgument & 0xFF;
int sp_vcount = (normalArgument >> 8) & 0xFF;
// Knot types.
int sp_utype = (normalArgument >> 16) & 0x3;
int sp_vtype = (normalArgument >> 18) & 0x3;
if (isLogDebugEnabled) {
log(helper.getCommandString(SPLINE) + " sp_ucount=" + sp_ucount + ", sp_vcount=" + sp_vcount
+ " sp_utype=" + sp_utype + ", sp_vtype=" + sp_vtype);
}
if (skipThisFrame) {
endRendering(sp_ucount * sp_vcount);
return;
}
updateGeBuf();
somethingDisplayed = true;
if (isGeProfilerEnabled) {
GEProfiler.startGeCmd(SPLINE);
}
loadTexture();
drawSpline(sp_ucount, sp_vcount, sp_utype, sp_vtype);
}
private void executeCommandJUMP() {
int oldPc = currentList.getPc();
currentList.jumpRelativeOffset(normalArgument);
int newPc = currentList.getPc();
if (isLogDebugEnabled) {
log(String.format("%s old PC: 0x%08X, new PC: 0x%08X", helper.getCommandString(JUMP), oldPc, newPc));
}
}
private void executeCommandCALL() {
int oldPc = currentList.getPc();
currentList.callRelativeOffset(normalArgument);
int newPc = currentList.getPc();
if (!Memory.isAddressGood(newPc)) {
error(String.format("Call instruction to invalid address 0x%08X", newPc));
// Return immediately
currentList.ret();
} else {
if (cachedInstructions.containsKey(newPc)) {
int[] instructions = cachedInstructions.get(newPc);
if (instructions != null) {
int memorySize = instructions.length << 2;
if (isLogInfoEnabled) {
log.info(String.format("call using cached instructions 0x%08X-0x%08X", newPc, newPc + memorySize));
}
IMemoryReader memoryReader = MemoryReader.getMemoryReader(newPc, instructions, 0, memorySize);
currentList.setMemoryReader(memoryReader);
}
}
}
if (isLogDebugEnabled) {
log(String.format("%s old PC: 0x%08X, new PC: 0x%08X", helper.getCommandString(CALL), oldPc, newPc));
}
}
private void executeCommandRET() {
int oldPc = currentList.getPc();
currentList.ret();
int newPc = currentList.getPc();
if (isLogDebugEnabled) {
log(String.format("%s old PC: 0x%08X, new PC: 0x%08X", helper.getCommandString(RET), oldPc, newPc));
}
}
private void executeCommandEND() {
int previousCommand = command(currentList.readPreviousInstruction());
// Ignore the END command if the command before was not SIGNAL or FINISH
if (previousCommand == SIGNAL || previousCommand == FINISH) {
// Try to end the current list.
// The list only ends (isEnded() == true) if FINISH was called previously.
// In SIGNAL + END cases, isEnded() still remains false.
currentList.endList();
currentList.pauseList();
if (isLogDebugEnabled) {
log(String.format("%s pc=0x%08X", helper.getCommandString(END), currentList.getPc()));
}
updateGeBuf();
} else if (isLogWarnEnabled) {
log.warn(String.format("Ignoring %s 0x%06X command without %s/%s at pc=0x%08X", helper.getCommandString(END), normalArgument, helper.getCommandString(SIGNAL), helper.getCommandString(FINISH), currentList.getPc() - 4));
}
}
private void executeCommandSIGNAL() {
int behavior = (normalArgument >> 16) & 0xFF;
int signal = normalArgument & 0xFFFF;
if (isLogDebugEnabled) {
log(String.format("%s (behavior=%d, signal=0x%X)", helper.getCommandString(SIGNAL), behavior, signal));
}
switch (behavior) {
case sceGe_user.PSP_GE_SIGNAL_SYNC: {
// Skip END / FINISH / END
if (command(currentList.readNextInstruction()) == END) {
if (command(currentList.readNextInstruction()) == FINISH) {
if (command(currentList.readNextInstruction()) == END) {
// OK, skipped END / FINISH / END sequence
} else {
currentList.undoRead(3);
}
} else {
currentList.undoRead(2);
}
} else {
currentList.undoRead(1);
}
if (isLogDebugEnabled) {
log(String.format("PSP_GE_SIGNAL_SYNC ignored PC: 0x%08X", currentList.getPc()));
}
break;
}
case sceGe_user.PSP_GE_SIGNAL_CALL: {
// Call list using absolute address from SIGNAL + END.
int nextInstruction = currentList.readNextInstruction();
if (command(nextInstruction) == END) {
int hi16 = signal & 0x0FFF;
// Read & skip END
int lo16 = nextInstruction & 0xFFFF;
int addr = (hi16 << 16) | lo16;
int oldPc = currentList.getPc();
currentList.callAbsolute(addr);
int newPc = currentList.getPc();
if (isLogDebugEnabled) {
log(String.format("PSP_GE_SIGNAL_CALL old PC: 0x%08X, new PC: 0x%08X", oldPc, newPc));
}
} else {
currentList.undoRead();
}
break;
}
case sceGe_user.PSP_GE_SIGNAL_RETURN: {
// Return from PSP_GE_SIGNAL_CALL.
int nextInstruction = currentList.readNextInstruction();
if (command(nextInstruction) == END) {
// Skip END
int oldPc = currentList.getPc();
currentList.ret();
int newPc = currentList.getPc();
if (isLogDebugEnabled) {
log(String.format("PSP_GE_SIGNAL_RETURN old PC: 0x%08X, new PC: 0x%08X", oldPc, newPc));
}
} else {
currentList.undoRead();
}
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 nextInstruction = currentList.readNextInstruction();
if (command(nextInstruction) == END) {
int hi16 = signal & 0xFFFF;
int lo16 = nextInstruction & 0xFFFF;
int width = (nextInstruction >> 16) & 0xFF;
int addr = currentList.getAddressRel((hi16 << 16) | lo16);
context.texture_base_pointer[behavior - sceGe_user.PSP_GE_SIGNAL_TBP0_REL] = addr;
context.texture_buffer_width[behavior - sceGe_user.PSP_GE_SIGNAL_TBP0_REL] = width;
} else {
currentList.undoRead();
}
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 nextInstruction = currentList.readNextInstruction();
if (command(nextInstruction) == END) {
int hi16 = signal & 0xFFFF;
int lo16 = nextInstruction & 0xFFFF;
int width = (nextInstruction >> 16) & 0xFF;
int addr = currentList.getAddressRelOffset((hi16 << 16) | lo16);
context.texture_base_pointer[behavior - sceGe_user.PSP_GE_SIGNAL_TBP0_REL_OFFSET] = addr;
context.texture_buffer_width[behavior - sceGe_user.PSP_GE_SIGNAL_TBP7_REL_OFFSET] = width;
} else {
currentList.undoRead();
}
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: {
currentList.clearRestart();
currentList.pushSignalCallback(currentList.id, behavior, signal);
break;
}
default: {
if (isLogWarnEnabled) {
log.warn(String.format("%s (behavior=%d, signal=0x%X) unknown behavior at 0x%08X", helper.getCommandString(SIGNAL), behavior, signal, currentList.getPc() - 4));
}
}
}
}
private void executeCommandFINISH() {
if (isLogDebugEnabled) {
log(helper.getCommandString(FINISH) + " " + getArgumentLog(normalArgument));
}
currentList.clearRestart();
currentList.finishList();
currentList.pushFinishCallback(currentList.id, normalArgument);
}
private void executeCommandBASE() {
context.base = (normalArgument << 8) & 0xFF000000;
// Bits of (normalArgument & 0x0000FFFF) are ignored
// (tested: "Ape Escape On the Loose")
if (isLogDebugEnabled) {
log(String.format("%s 0x%08X", helper.getCommandString(BASE), context.base));
}
}
private void executeCommandVTYPE() {
int old_transform_mode = context.transform_mode;
boolean old_vertex_hasColor = context.vinfo.color != 0;
context.vinfo.processType(normalArgument);
context.transform_mode = (normalArgument >> 23) & 0x1;
boolean vertex_hasColor = context.vinfo.color != 0;
//Switching from 2D to 3D or 3D to 2D?
if (old_transform_mode != context.transform_mode) {
projectionMatrixUpload.setChanged(true);
modelMatrixUpload.setChanged(true);
viewMatrixUpload.setChanged(true);
textureMatrixUpload.setChanged(true);
viewportChanged = true;
depthChanged = true;
materialChanged = true;
// Switching from 2D to 3D?
if (context.transform_mode == VTYPE_TRANSFORM_PIPELINE_TRANS_COORD) {
lightingChanged = true;
}
} else if (old_vertex_hasColor != vertex_hasColor) {
// Materials have to be reloaded when the vertex color presence is changing
materialChanged = true;
}
if (isLogDebugEnabled) {
log(helper.getCommandString(VTYPE) + " " + context.vinfo.toString());
}
}
private void executeCommandOFFSET_ADDR() {
context.baseOffset = normalArgument << 8;
if (isLogDebugEnabled) {
log(String.format("%s 0x%08X", helper.getCommandString(OFFSET_ADDR), context.baseOffset));
}
}
private void executeCommandORIGIN_ADDR() {
context.baseOffset = currentList.getPc() - 4;
if (normalArgument != 0) {
log.warn(String.format("%s unknown argument 0x%08X", helper.getCommandString(ORIGIN_ADDR), normalArgument));
} else if (isLogDebugEnabled) {
log(String.format("%s 0x%08X originAddr=0x%08X", helper.getCommandString(ORIGIN_ADDR), normalArgument, context.baseOffset));
}
}
private void executeCommandREGION1() {
int old_region_x1 = context.region_x1;
int old_region_y1 = context.region_y1;
context.region_x1 = normalArgument & 0x3ff;
context.region_y1 = (normalArgument >> 10) & 0x3ff;
if (old_region_x1 != context.region_x1 || old_region_y1 != context.region_y1) {
scissorChanged = true;
}
}
private void executeCommandREGION2() {
int old_region_x2 = context.region_x2;
int old_region_y2 = context.region_y2;
context.region_x2 = normalArgument & 0x3ff;
context.region_y2 = (normalArgument >> 10) & 0x3ff;
context.region_width = (context.region_x2 + 1) - context.region_x1;
context.region_height = (context.region_y2 + 1) - context.region_y1;
if (isLogDebugEnabled) {
log("drawRegion(" + context.region_x1 + "," + context.region_y1 + "," + context.region_width + "," + context.region_height + ")");
}
if (old_region_x2 != context.region_x2 || old_region_y2 != context.region_y2) {
scissorChanged = true;
}
}
private void executeCommandLTE() {
context.lightingFlag.setEnabled(normalArgument);
if (context.lightingFlag.isEnabled()) {
lightingChanged = true;
materialChanged = true;
}
}
private void executeCommandLTEn() {
int lnum = command - LTE0;
EnableDisableFlag lightFlag = context.lightFlags[lnum];
lightFlag.setEnabled(normalArgument);
if (lightFlag.isEnabled()) {
lightingChanged = true;
}
}
private void executeCommandCPE() {
context.clipPlanesFlag.setEnabled(normalArgument);
}
private void executeCommandBCE() {
context.cullFaceFlag.setEnabled(normalArgument);
}
private void executeCommandTME() {
context.textureFlag.setEnabled(normalArgument);
}
private void executeCommandFGE() {
context.fogFlag.setEnabled(normalArgument);
if (context.fogFlag.isEnabled()) {
re.setFogHint();
}
}
private void executeCommandDTE() {
context.ditherFlag.setEnabled(normalArgument);
}
private void executeCommandABE() {
context.blendFlag.setEnabled(normalArgument);
}
private void executeCommandATE() {
context.alphaTestFlag.setEnabled(normalArgument);
}
private void executeCommandZTE() {
context.depthTestFlag.setEnabled(normalArgument);
if (context.depthTestFlag.isEnabled()) {
// OpenGL requires the Depth parameters to be reloaded
depthChanged = true;
}
}
private void executeCommandSTE() {
context.stencilTestFlag.setEnabled(normalArgument);
}
private void executeCommandAAE() {
context.lineSmoothFlag.setEnabled(normalArgument);
if (context.lineSmoothFlag.isEnabled()) {
re.setLineSmoothHint();
}
}
private void executeCommandPCE() {
context.patchCullFaceFlag.setEnabled(normalArgument);
}
private void executeCommandCTE() {
context.colorTestFlag.setEnabled(normalArgument);
}
private void executeCommandLOE() {
context.colorLogicOpFlag.setEnabled(normalArgument);
}
private void executeCommandBOFS() {
boneMatrixIndex = normalArgument;
if (isLogDebugEnabled) {
log(String.format("bone matrix offset %d", normalArgument));
}
}
private void executeCommandMWn() {
int index = command - MW0;
float floatArgument = floatArgument(normalArgument);
context.morph_weight[index] = floatArgument;
re.setMorphWeight(index, floatArgument);
if (isLogDebugEnabled) {
log(String.format("morph weight %d %f", index, floatArgument));
}
}
private void executeCommandPSUB() {
// A patch division of 0 has the same effect as 1 (checked on PSP using splinesurface demo)
context.patch_div_s = max(normalArgument & 0xFF, 1);
context.patch_div_t = max((normalArgument >> 8) & 0xFF, 1);
re.setPatchDiv(context.patch_div_s, context.patch_div_t);
if (isLogDebugEnabled) {
log(String.format("%s patch_div_s=%d, patch_div_t=%d", helper.getCommandString(PSUB), context.patch_div_s, context.patch_div_t));
}
}
private void executeCommandPPRIM() {
context.patch_prim = (normalArgument & 0x3);
// Primitive type to use in patch division:
// 0 - Triangle.
// 1 - Line.
// 2 - Point.
re.setPatchPrim(context.patch_prim);
if (isLogDebugEnabled) {
log(helper.getCommandString(PPRIM) + " patch_prim=" + context.patch_prim);
}
}
private void executeCommandPFACE() {
// 0 - Clockwise oriented patch / 1 - Counter clockwise oriented patch.
context.patchFaceFlag.setEnabled(normalArgument);
}
private void executeCommandMMS() {
modelMatrixUpload.startUpload(normalArgument);
if (isLogDebugEnabled) {
log("sceGumMatrixMode GU_MODEL " + normalArgument);
}
}
private void executeCommandMODEL() {
if (modelMatrixUpload.uploadValue(floatArgument(normalArgument))) {
log("glLoadMatrixf", context.model_uploaded_matrix);
}
}
private void executeCommandVMS() {
viewMatrixUpload.startUpload(normalArgument);
if (isLogDebugEnabled) {
log("sceGumMatrixMode GU_VIEW " + normalArgument);
}
}
private void executeCommandVIEW() {
if (viewMatrixUpload.uploadValue(floatArgument(normalArgument))) {
log("glLoadMatrixf", context.view_uploaded_matrix);
}
}
private void executeCommandPMS() {
projectionMatrixUpload.startUpload(normalArgument);
if (isLogDebugEnabled) {
log("sceGumMatrixMode GU_PROJECTION " + normalArgument);
}
}
private void executeCommandPROJ() {
if (projectionMatrixUpload.uploadValue(floatArgument(normalArgument))) {
log("glLoadMatrixf", context.proj_uploaded_matrix);
}
}
private void executeCommandTMS() {
textureMatrixUpload.startUpload(normalArgument);
if (isLogDebugEnabled) {
log("sceGumMatrixMode GU_TEXTURE " + normalArgument);
}
}
private void executeCommandTMATRIX() {
if (textureMatrixUpload.uploadValue(floatArgument(normalArgument))) {
log("glLoadMatrixf", context.texture_uploaded_matrix);
}
}
private void executeCommandXSCALE() {
int old_viewport_width = context.viewport_width;
context.viewport_width = (int) floatArgument(normalArgument);
if (old_viewport_width != context.viewport_width) {
viewportChanged = true;
if ((old_viewport_width < 0 && context.viewport_width > 0)
|| (old_viewport_width > 0 && context.viewport_width < 0)) {
// Projection matrix has to be reloaded when X-axis flipped
projectionMatrixUpload.setChanged(true);
}
}
}
private void executeCommandYSCALE() {
int old_viewport_height = context.viewport_height;
context.viewport_height = (int) floatArgument(normalArgument);
if (old_viewport_height != context.viewport_height) {
viewportChanged = true;
if ((old_viewport_height < 0 && context.viewport_height > 0)
|| (old_viewport_height > 0 && context.viewport_height < 0)) {
// Projection matrix has to be reloaded when Y-axis flipped
projectionMatrixUpload.setChanged(true);
}
}
if (isLogDebugEnabled) {
log.debug("sceGuViewport(cx=" + context.viewport_cx + ", cy=" + context.viewport_cy + ", w=" + context.viewport_width + ", h=" + context.viewport_height + ")");
}
}
private void executeCommandZSCALE() {
float old_zscale = context.zscale;
float floatArgument = floatArgument(normalArgument);
context.zscale = floatArgument;
if (old_zscale != context.zscale) {
depthChanged = true;
}
if (isLogDebugEnabled) {
log(helper.getCommandString(ZSCALE) + " " + floatArgument);
}
}
private void executeCommandXPOS() {
int old_viewport_cx = context.viewport_cx;
context.viewport_cx = (int) floatArgument(normalArgument);
if (old_viewport_cx != context.viewport_cx) {
viewportChanged = true;
}
}
private void executeCommandYPOS() {
int old_viewport_cy = context.viewport_cy;
context.viewport_cy = (int) floatArgument(normalArgument);
if (old_viewport_cy != context.viewport_cy) {
viewportChanged = true;
}
if (isLogDebugEnabled) {
log.debug("sceGuViewport(cx=" + context.viewport_cx + ", cy=" + context.viewport_cy + ", w=" + context.viewport_width + ", h=" + context.viewport_height + ")");
}
}
private void executeCommandZPOS() {
float old_zpos = context.zpos;
float floatArgument = floatArgument(normalArgument);
context.zpos = floatArgument;
if (old_zpos != context.zpos) {
depthChanged = true;
}
if (isLogDebugEnabled) {
log(String.format("%s %f", helper.getCommandString(ZPOS), floatArgument));
}
}
private void executeCommandUSCALE() {
float old_tex_scale_x = context.tex_scale_x;
context.tex_scale_x = floatArgument(normalArgument);
if (old_tex_scale_x != context.tex_scale_x) {
textureMatrixUpload.setChanged(true);
}
if (isLogDebugEnabled) {
log(String.format("sceGuTexScale(u=%f, X)", context.tex_scale_x));
}
}
private void executeCommandVSCALE() {
float old_tex_scale_y = context.tex_scale_y;
context.tex_scale_y = floatArgument(normalArgument);
if (old_tex_scale_y != context.tex_scale_y) {
textureMatrixUpload.setChanged(true);
}
if (isLogDebugEnabled) {
log(String.format("sceGuTexScale(X, v=%f)", context.tex_scale_y));
}
}
private void executeCommandUOFFSET() {
float old_tex_translate_x = context.tex_translate_x;
context.tex_translate_x = floatArgument(normalArgument);
if (old_tex_translate_x != context.tex_translate_x) {
textureMatrixUpload.setChanged(true);
}
if (isLogDebugEnabled) {
log(String.format("sceGuTexOffset(u=%f, X)", context.tex_translate_x));
}
}
private void executeCommandVOFFSET() {
float old_tex_translate_y = context.tex_translate_y;
context.tex_translate_y = floatArgument(normalArgument);
if (old_tex_translate_y != context.tex_translate_y) {
textureMatrixUpload.setChanged(true);
}
if (isLogDebugEnabled) {
log(String.format("sceGuTexOffset(X, v=%f)", context.tex_translate_y));
}
}
private void executeCommandOFFSETX() {
int old_offset_x = context.offset_x;
context.offset_x = normalArgument >> 4;
if (old_offset_x != context.offset_x) {
viewportChanged = true;
}
}
private void executeCommandOFFSETY() {
int old_offset_y = context.offset_y;
context.offset_y = normalArgument >> 4;
if (old_offset_y != context.offset_y) {
viewportChanged = true;
}
if (isLogDebugEnabled) {
log.debug("sceGuOffset(x=" + context.offset_x + ",y=" + context.offset_y + ")");
}
}
private void executeCommandSHADE() {
context.shadeModel = normalArgument & 1;
re.setShadeModel(context.shadeModel);
if (isLogDebugEnabled) {
log("sceGuShadeModel(" + ((context.shadeModel != 0) ? "smooth" : "flat") + ")");
}
}
private void executeCommandRNORM() {
// This seems to be taked into account when calculating the lighting
// for the current normal.
context.faceNormalReverseFlag.setEnabled(normalArgument);
}
private void executeCommandCMAT() {
int old_mat_flags = context.mat_flags;
context.mat_flags = normalArgument & 7;
if (old_mat_flags != context.mat_flags) {
materialChanged = true;
}
if (isLogDebugEnabled) {
log("sceGuColorMaterial " + context.mat_flags);
}
}
private void executeCommandEMC() {
context.mat_emissive[0] = ((normalArgument) & 255) / 255.f;
context.mat_emissive[1] = ((normalArgument >> 8) & 255) / 255.f;
context.mat_emissive[2] = ((normalArgument >> 16) & 255) / 255.f;
context.mat_emissive[3] = 1.f;
materialChanged = true;
re.setMaterialEmissiveColor(context.mat_emissive);
if (isLogDebugEnabled) {
log("material emission " + String.format("r=%.1f g=%.1f b=%.1f (%08X)",
context.mat_emissive[0], context.mat_emissive[1], context.mat_emissive[2], normalArgument));
}
}
private void executeCommandAMC() {
context.mat_ambient[0] = ((normalArgument) & 255) / 255.f;
context.mat_ambient[1] = ((normalArgument >> 8) & 255) / 255.f;
context.mat_ambient[2] = ((normalArgument >> 16) & 255) / 255.f;
materialChanged = true;
if (isLogDebugEnabled) {
log(String.format("material ambient r=%.1f g=%.1f b=%.1f (%08X)",
context.mat_ambient[0], context.mat_ambient[1], context.mat_ambient[2], normalArgument));
}
}
private void executeCommandDMC() {
context.mat_diffuse[0] = ((normalArgument) & 255) / 255.f;
context.mat_diffuse[1] = ((normalArgument >> 8) & 255) / 255.f;
context.mat_diffuse[2] = ((normalArgument >> 16) & 255) / 255.f;
context.mat_diffuse[3] = 1.f;
materialChanged = true;
if (isLogDebugEnabled) {
log("material diffuse " + String.format("r=%.1f g=%.1f b=%.1f (%08X)",
context.mat_diffuse[0], context.mat_diffuse[1], context.mat_diffuse[2], normalArgument));
}
}
private void executeCommandSMC() {
context.mat_specular[0] = ((normalArgument) & 255) / 255.f;
context.mat_specular[1] = ((normalArgument >> 8) & 255) / 255.f;
context.mat_specular[2] = ((normalArgument >> 16) & 255) / 255.f;
context.mat_specular[3] = 1.f;
materialChanged = true;
if (isLogDebugEnabled) {
log("material specular " + String.format("r=%.1f g=%.1f b=%.1f (%08X)",
context.mat_specular[0], context.mat_specular[1], context.mat_specular[2], normalArgument));
}
}
private void executeCommandAMA() {
context.mat_ambient[3] = ((normalArgument) & 255) / 255.f;
materialChanged = true;
if (isLogDebugEnabled) {
log(String.format("material ambient a=%.1f (%02X)",
context.mat_ambient[3], normalArgument & 255));
}
}
private void executeCommandSPOW() {
context.materialShininess = floatArgument(normalArgument);
re.setMaterialShininess(context.materialShininess);
if (isLogDebugEnabled) {
log("material shininess " + context.materialShininess);
}
}
private void executeCommandALC() {
context.ambient_light[0] = ((normalArgument) & 255) / 255.f;
context.ambient_light[1] = ((normalArgument >> 8) & 255) / 255.f;
context.ambient_light[2] = ((normalArgument >> 16) & 255) / 255.f;
re.setLightModelAmbientColor(context.ambient_light);
if (isLogDebugEnabled) {
log.debug(String.format("ambient light r=%.1f g=%.1f b=%.1f (%06X)",
context.ambient_light[0], context.ambient_light[1], context.ambient_light[2], normalArgument));
}
}
private void executeCommandALA() {
context.ambient_light[3] = ((normalArgument) & 255) / 255.f;
re.setLightModelAmbientColor(context.ambient_light);
}
private void executeCommandLMODE() {
context.lightMode = normalArgument & 1;
re.setLightMode(context.lightMode);
if (isLogDebugEnabled) {
log.debug("sceGuLightMode(" + ((context.lightMode != 0) ? "GU_SEPARATE_SPECULAR_COLOR" : "GU_SINGLE_COLOR") + ")");
}
// Check if other values than 0 and 1 are set
if ((normalArgument & ~1) != 0) {
log.warn(String.format("Unknown light mode sceGuLightMode(%06X)", normalArgument));
}
}
private void executeCommandLTn() {
int lnum = command - LT0;
int old_light_type = context.light_type[lnum];
int old_light_kind = context.light_kind[lnum];
context.light_type[lnum] = (normalArgument >> 8) & 3;
context.light_kind[lnum] = normalArgument & 3;
if (old_light_type != context.light_type[lnum] || old_light_kind != context.light_kind[lnum]) {
lightingChanged = true;
}
if (context.light_type[lnum] == LIGHT_TYPE_UNKNOWN) {
// Confirmed by testing with 3DStudio: light type 3 is equivalent to light type 2.
context.light_type[lnum] = LIGHT_SPOT;
}
switch (context.light_type[lnum]) {
case LIGHT_DIRECTIONAL:
context.light_pos[lnum][3] = 0.f;
break;
case LIGHT_POINT:
re.setLightSpotCutoff(lnum, 180);
context.light_pos[lnum][3] = 1.f;
break;
case LIGHT_SPOT:
context.light_pos[lnum][3] = 1.f;
break;
default:
error(String.format("Unknown light type: 0x%06X", normalArgument));
}
re.setLightType(lnum, context.light_type[lnum], context.light_kind[lnum]);
if (isLogDebugEnabled) {
log.debug(String.format("Light #%d: type %d, kind %d", lnum, (normalArgument >> 8) & 3, normalArgument & 3));
}
}
private void executeCommandLXPn() {
int lnum = (command - LXP0) / 3;
int component = (command - LXP0) % 3;
float old_light_pos = context.light_pos[lnum][component];
context.light_pos[lnum][component] = floatArgument(normalArgument);
if (old_light_pos != context.light_pos[lnum][component]) {
lightingChanged = true;
// Environment mapping is using light positions
if (context.tex_map_mode == TMAP_TEXTURE_MAP_MODE_ENVIRONMENT_MAP) {
if (context.tex_shade_u == lnum || context.tex_shade_v == lnum) {
textureMatrixUpload.setChanged(true);
}
}
}
if (isLogDebugEnabled) {
log.debug(String.format("Light %d position (%f, %f, %f)", lnum, context.light_pos[lnum][0], context.light_pos[lnum][1], context.light_pos[lnum][2]));
}
}
private void executeCommandLXDn() {
int lnum = (command - LXD0) / 3;
int component = (command - LXD0) % 3;
float old_light_dir = context.light_dir[lnum][component];
// OpenGL requires a normal in the opposite direction as the PSP
context.light_dir[lnum][component] = -floatArgument(normalArgument);
if (old_light_dir != context.light_dir[lnum][component]) {
lightingChanged = true;
}
if (isLogDebugEnabled) {
log.debug(String.format("Light %d direction (%f, %f, %f)", lnum, context.light_dir[lnum][0], context.light_dir[lnum][1], context.light_dir[lnum][2]));
}
// OpenGL parameter for light direction is set in initRendering
// because it depends on the model/view matrix
}
private void executeCommandLCAn() {
int lnum = (command - LCA0) / 3;
context.lightConstantAttenuation[lnum] = floatArgument(normalArgument);
re.setLightConstantAttenuation(lnum, context.lightConstantAttenuation[lnum]);
}
private void executeCommandLLAn() {
int lnum = (command - LLA0) / 3;
context.lightLinearAttenuation[lnum] = floatArgument(normalArgument);
re.setLightLinearAttenuation(lnum, context.lightLinearAttenuation[lnum]);
}
private void executeCommandLQAn() {
int lnum = (command - LQA0) / 3;
context.lightQuadraticAttenuation[lnum] = floatArgument(normalArgument);
re.setLightQuadraticAttenuation(lnum, context.lightQuadraticAttenuation[lnum]);
}
private void executeCommandSLEn() {
int lnum = command - SLE0;
float old_spotLightExponent = context.spotLightExponent[lnum];
context.spotLightExponent[lnum] = floatArgument(normalArgument);
if (old_spotLightExponent != context.spotLightExponent[lnum]) {
lightingChanged = true;
}
if (isLogDebugEnabled) {
VideoEngine.log.debug("sceGuLightSpot(" + lnum + ",X," + context.spotLightExponent[lnum] + ",X)");
}
}
private void executeCommandSLFn() {
int lnum = command - SLF0;
float old_spotLightCutoff = context.spotLightCutoff[lnum];
// PSP Cutoff is cosine of angle, OpenGL expects degrees
float floatArgument = floatArgument(normalArgument);
float degreeCutoff = (float) Math.toDegrees(Math.acos(floatArgument));
if ((degreeCutoff >= 0 && degreeCutoff <= 90) || degreeCutoff == 180) {
context.spotLightCutoff[lnum] = degreeCutoff;
context.spotLightCosCutoff[lnum] = floatArgument;
if (old_spotLightCutoff != context.spotLightCutoff[lnum]) {
lightingChanged = true;
}
if (isLogDebugEnabled) {
log.debug("sceGuLightSpot(" + lnum + ",X,X," + floatArgument + "=" + degreeCutoff + ")");
}
} else {
log.warn("sceGuLightSpot(" + lnum + ",X,X," + floatArgument + ") invalid argument value");
}
}
private void executeCommandALCn() {
int lnum = (command - ALC0) / 3;
context.lightAmbientColor[lnum][0] = ((normalArgument) & 255) / 255.f;
context.lightAmbientColor[lnum][1] = ((normalArgument >> 8) & 255) / 255.f;
context.lightAmbientColor[lnum][2] = ((normalArgument >> 16) & 255) / 255.f;
context.lightAmbientColor[lnum][3] = 1.f;
re.setLightAmbientColor(lnum, context.lightAmbientColor[lnum]);
if (isLogDebugEnabled) {
log(String.format("sceGuLightColor GU_LIGHT%d, GU_AMBIENT r=%.3f, b=%.3f, g=%.3f, a=%.3f", lnum, context.lightAmbientColor[lnum][0], context.lightAmbientColor[lnum][1], context.lightAmbientColor[lnum][2], context.lightAmbientColor[lnum][3]));
}
}
private void executeCommandDLCn() {
int lnum = (command - DLC0) / 3;
context.lightDiffuseColor[lnum][0] = ((normalArgument) & 255) / 255.f;
context.lightDiffuseColor[lnum][1] = ((normalArgument >> 8) & 255) / 255.f;
context.lightDiffuseColor[lnum][2] = ((normalArgument >> 16) & 255) / 255.f;
context.lightDiffuseColor[lnum][3] = 1.f;
re.setLightDiffuseColor(lnum, context.lightDiffuseColor[lnum]);
if (isLogDebugEnabled) {
log(String.format("sceGuLightColor GU_LIGHT%d, GU_DIFFUSE r=%.3f, b=%.3f, g=%.3f, a=%.3f", lnum, context.lightDiffuseColor[lnum][0], context.lightDiffuseColor[lnum][1], context.lightDiffuseColor[lnum][2], context.lightDiffuseColor[lnum][3]));
}
}
private void executeCommandSLCn() {
int lnum = (command - SLC0) / 3;
float old_lightSpecularColor0 = context.lightSpecularColor[lnum][0];
float old_lightSpecularColor1 = context.lightSpecularColor[lnum][1];
float old_lightSpecularColor2 = context.lightSpecularColor[lnum][2];
context.lightSpecularColor[lnum][0] = ((normalArgument) & 255) / 255.f;
context.lightSpecularColor[lnum][1] = ((normalArgument >> 8) & 255) / 255.f;
context.lightSpecularColor[lnum][2] = ((normalArgument >> 16) & 255) / 255.f;
context.lightSpecularColor[lnum][3] = 1.f;
if (old_lightSpecularColor0 != context.lightSpecularColor[lnum][0] || old_lightSpecularColor1 != context.lightSpecularColor[lnum][1] || old_lightSpecularColor2 != context.lightSpecularColor[lnum][2]) {
lightingChanged = true;
}
re.setLightSpecularColor(lnum, context.lightDiffuseColor[lnum]);
if (isLogDebugEnabled) {
log(String.format("sceGuLightColor GU_LIGHT%d, GU_SPECULAR r=%.3f, b=%.3f, g=%.3f, a=%.3f", lnum, context.lightSpecularColor[lnum][0], context.lightSpecularColor[lnum][1], context.lightSpecularColor[lnum][2], context.lightSpecularColor[lnum][3]));
}
}
private void executeCommandFFACE() {
context.frontFaceCw = normalArgument != 0;
re.setFrontFace(context.frontFaceCw);
if (isLogDebugEnabled) {
log(helper.getCommandString(FFACE) + " " + ((normalArgument != 0) ? "clockwise" : "counter-clockwise"));
}
}
private void executeCommandFBP() {
// FBP can be called before or after FBW
context.fbp = (context.fbp & 0xff000000) | normalArgument;
if (isLogDebugEnabled) {
log(String.format("%s fbp=0x%08X, fbw=%d", helper.getCommandString(FBP), context.fbp, context.fbw));
}
geBufChanged = true;
}
private void executeCommandFBW() {
context.fbw = normalArgument & 0xffff;
if (isLogDebugEnabled) {
log(String.format("%s fbp=0x%08X, fbw=%d", helper.getCommandString(FBW), context.fbp, context.fbw));
}
geBufChanged = true;
}
private void executeCommandZBP() {
context.zbp = (context.zbp & 0xff000000) | normalArgument;
if (isLogDebugEnabled) {
log(String.format("%s zbp=0x%08X, zbw=%d", helper.getCommandString(ZBP), context.zbp, context.zbw));
}
}
private void executeCommandZBW() {
context.zbw = normalArgument & 0xffff;
if (isLogDebugEnabled) {
log(String.format("%s zbp=0x%08X, zbw=%d", helper.getCommandString(ZBW), context.zbp, context.zbw));
}
}
private void executeCommandTBPn() {
int level = command - TBP0;
int old_texture_base_pointer = context.texture_base_pointer[level];
context.texture_base_pointer[level] = (context.texture_base_pointer[level] & 0xff000000) | normalArgument;
if (old_texture_base_pointer != context.texture_base_pointer[level]) {
textureChanged = true;
}
if (isLogDebugEnabled) {
log(String.format("sceGuTexImage(level=%d, X, X, X, lo(pointer=0x%08X)", level, context.texture_base_pointer[level]));
}
}
private void executeCommandTBWn() {
int level = command - TBW0;
int old_texture_base_pointer = context.texture_base_pointer[level];
int old_texture_buffer_width = context.texture_buffer_width[level];
context.texture_base_pointer[level] = (context.texture_base_pointer[level] & 0x00ffffff) | ((normalArgument << 8) & 0xff000000);
context.texture_buffer_width[level] = normalArgument & 0xffff;
if (old_texture_base_pointer != context.texture_base_pointer[level] || old_texture_buffer_width != context.texture_buffer_width[level]) {
textureChanged = true;
}
if (isLogDebugEnabled) {
log(String.format("sceGuTexImage(level=%d, X, X, texBufferWidth=%d, hi(pointer=0x%08X))", level, context.texture_buffer_width[level], context.texture_base_pointer[level]));
}
}
private void executeCommandCBP() {
int old_tex_clut_addr = context.tex_clut_addr;
context.tex_clut_addr = (context.tex_clut_addr & 0xff000000) | normalArgument;
clutIsDirty = true;
if (old_tex_clut_addr != context.tex_clut_addr) {
textureChanged = true;
}
if (isLogDebugEnabled) {
log(String.format("sceGuClutLoad(X, lo(cbp=0x%08X)", context.tex_clut_addr));
}
}
private void executeCommandCBPH() {
int old_tex_clut_addr = context.tex_clut_addr;
context.tex_clut_addr = (context.tex_clut_addr & 0x00ffffff) | ((normalArgument << 8) & 0x0f000000);
clutIsDirty = true;
if (old_tex_clut_addr != context.tex_clut_addr) {
textureChanged = true;
}
if (isLogDebugEnabled) {
log(String.format("sceGuClutLoad(X, hi(cbp=0x%08X)", context.tex_clut_addr));
}
}
private void executeCommandTRXSBP() {
context.textureTx_sourceAddress = (context.textureTx_sourceAddress & 0xFF000000) | normalArgument;
if (isLogDebugEnabled) {
log.debug(String.format("%s sourceAddress=0x%08X", helper.getCommandString(command), context.textureTx_sourceAddress));
}
}
private void executeCommandTRXSBW() {
context.textureTx_sourceAddress = (context.textureTx_sourceAddress & 0x00FFFFFF) | ((normalArgument << 8) & 0xFF000000);
context.textureTx_sourceLineWidth = normalArgument & 0x0000FFFF;
if (isLogDebugEnabled) {
log.debug(String.format("%s sourceAddress=0x%08X, sourceLineWidth=%d", helper.getCommandString(command), context.textureTx_sourceAddress, context.textureTx_sourceLineWidth));
}
// TODO Check when sx and sy are reset to 0. Here or after TRXKICK?
context.textureTx_sx = 0;
context.textureTx_sy = 0;
}
private void executeCommandTRXDBP() {
context.textureTx_destinationAddress = (context.textureTx_destinationAddress & 0xFF000000) | normalArgument;
if (isLogDebugEnabled) {
log.debug(String.format("%s destinationAddress=0x%08X", helper.getCommandString(command), context.textureTx_destinationAddress));
}
}
private void executeCommandTRXDBW() {
context.textureTx_destinationAddress = (context.textureTx_destinationAddress & 0x00FFFFFF) | ((normalArgument << 8) & 0xFF000000);
context.textureTx_destinationLineWidth = normalArgument & 0x0000FFFF;
if (isLogDebugEnabled) {
log.debug(String.format("%s destinationAddress=0x%08X, destinationLineWidth=%d", helper.getCommandString(command), context.textureTx_destinationAddress, context.textureTx_destinationLineWidth));
}
// TODO Check when dx and dy are reset to 0. Here or after TRXKICK?
context.textureTx_dx = 0;
context.textureTx_dy = 0;
}
private void executeCommandTSIZEn() {
int level = command - TSIZE0;
int old_texture_height = context.texture_height[level];
int old_texture_width = context.texture_width[level];
// Astonishia Story is using normalArgument = 0x1804
// -> use texture_height = 1 << 0x08 (and not 1 << 0x18)
// texture_width = 1 << 0x04
// On a PSP, the maximum texture size is 512x512: the exponent value must be [0..9].
// On the PS3 PSP emulator, the maximum texture size can be higher (e.g. 1024x1024 is valid).
int height_exp2 = Math.min((normalArgument >> 8) & 0x0F, maxTextureSizeLog2);
int width_exp2 = Math.min((normalArgument) & 0x0F, maxTextureSizeLog2);
context.texture_height[level] = 1 << height_exp2;
context.texture_width[level] = 1 << width_exp2;
if (old_texture_height != context.texture_height[level] || old_texture_width != context.texture_width[level]) {
if (context.transform_mode == VTYPE_TRANSFORM_PIPELINE_RAW_COORD && level == 0) {
textureMatrixUpload.setChanged(true);
}
textureChanged = true;
}
if (isLogDebugEnabled) {
log(String.format("sceGuTexImage(level=%d, width=%d, height=%d, X, X)", level, context.texture_width[level], context.texture_height[level]));
}
}
private void executeCommandTMAP() {
int old_tex_map_mode = context.tex_map_mode;
context.tex_map_mode = normalArgument & 3;
context.tex_proj_map_mode = (normalArgument >> 8) & 3;
if (old_tex_map_mode != context.tex_map_mode) {
textureMatrixUpload.setChanged(true);
}
if (context.tex_map_mode == TMAP_TEXTURE_MAP_MODE_UNKNOW) {
// Confirmed by testing with 3DStudio: map mode 3 is equivalent to map mode 0
context.tex_map_mode = TMAP_TEXTURE_MAP_MODE_TEXTURE_COORDIATES_UV;
}
if (isLogDebugEnabled) {
log("sceGuTexMapMode(mode=" + context.tex_map_mode + ", X, X)");
log("sceGuTexProjMapMode(mode=" + context.tex_proj_map_mode + ")");
}
}
private void executeCommandTEXTURE_ENV_MAP_MATRIX() {
context.tex_shade_u = (normalArgument >> 0) & 0x3;
context.tex_shade_v = (normalArgument >> 8) & 0x3;
textureMatrixUpload.setChanged(true);
if (isLogDebugEnabled) {
log("sceGuTexMapMode(X, " + context.tex_shade_u + ", " + context.tex_shade_v + ")");
}
}
private void executeCommandTMODE() {
int old_texture_num_mip_maps = context.texture_num_mip_maps;
boolean old_mipmapShareClut = context.mipmapShareClut;
boolean old_texture_swizzle = context.texture_swizzle;
context.texture_num_mip_maps = (normalArgument >> 16) & 0x7;
// This parameter has only a meaning when
// texture_storage == GU_PSM_T4 and texture_num_mip_maps > 0
// when parameter==0: all the mipmaps share the same clut entries (normal behavior)
// when parameter==1: each mipmap has its own clut table, 16 entries each, stored sequentially
context.mipmapShareClut = ((normalArgument >> 8) & 0x1) == 0;
context.texture_swizzle = ((normalArgument) & 0x1) != 0;
if (old_texture_num_mip_maps != context.texture_num_mip_maps || old_mipmapShareClut != context.mipmapShareClut || old_texture_swizzle != context.texture_swizzle) {
textureChanged = true;
}
if (isLogDebugEnabled) {
log("sceGuTexMode(X, mipmaps=" + context.texture_num_mip_maps + ", mipmapShareClut=" + context.mipmapShareClut + ", swizzle=" + context.texture_swizzle + ")");
}
}
private void executeCommandTPSM() {
int old_texture_storage = context.texture_storage;
context.texture_storage = normalArgument & 0xF; // Lower four bits.
if (old_texture_storage != context.texture_storage) {
textureChanged = true;
}
if (isLogDebugEnabled) {
log("sceGuTexMode(tpsm=" + context.texture_storage + "(" + getPsmName(context.texture_storage) + "), X, X, X)");
}
}
private void executeCommandCLOAD() {
int old_tex_clut_num_blocks = context.tex_clut_num_blocks;
context.tex_clut_num_blocks = normalArgument & 0x3F;
clutIsDirty = true;
if (old_tex_clut_num_blocks != context.tex_clut_num_blocks) {
textureChanged = true;
}
// Some games use the following sequence:
// - sceGuClutLoad(num_blocks=32, X)
// - sceGuClutLoad(num_blocks=1, X)
// - tflush
// - prim ... (texture data is referencing the clut entries from 32 blocks)
//
readClut();
if (isLogDebugEnabled) {
log("sceGuClutLoad(num_blocks=" + context.tex_clut_num_blocks + ", X)");
}
}
private void executeCommandCMODE() {
int old_tex_clut_mode = context.tex_clut_mode;
int old_tex_clut_shift = context.tex_clut_shift;
int old_tex_clut_mask = context.tex_clut_mask;
int old_tex_clut_start = context.tex_clut_start;
context.tex_clut_mode = normalArgument & 0x03;
context.tex_clut_shift = (normalArgument >> 2) & 0x1F;
context.tex_clut_mask = (normalArgument >> 8) & 0xFF;
context.tex_clut_start = (normalArgument >> 16) & 0x1F;
clutIsDirty = true;
if (old_tex_clut_mode != context.tex_clut_mode || old_tex_clut_shift != context.tex_clut_shift || old_tex_clut_mask != context.tex_clut_mask || old_tex_clut_start != context.tex_clut_start) {
textureChanged = true;
}
if (isLogDebugEnabled) {
log("sceGuClutMode(cpsm=" + context.tex_clut_mode + "(" + getPsmName(context.tex_clut_mode) + "), shift=" + context.tex_clut_shift + ", mask=0x" + Integer.toHexString(context.tex_clut_mask) + ", start=" + context.tex_clut_start + ")");
}
}
private void executeCommandTFLT() {
int old_tex_mag_filter = context.tex_mag_filter;
int old_tex_min_filter = context.tex_min_filter;
context.tex_min_filter = normalArgument & 0x7;
context.tex_mag_filter = (normalArgument >> 8) & 0x1;
if (isLogDebugEnabled) {
log("sceGuTexFilter(min=" + context.tex_min_filter + ", mag=" + context.tex_mag_filter + ") (mm#" + context.texture_num_mip_maps + ")");
}
if (context.tex_min_filter == TFLT_UNKNOW1 || context.tex_min_filter == TFLT_UNKNOW2) {
log.warn("Unknown minimizing filter " + (normalArgument & 0xFF));
context.tex_min_filter = TFLT_NEAREST;
}
if (old_tex_mag_filter != context.tex_mag_filter || old_tex_min_filter != context.tex_min_filter) {
textureChanged = true;
}
}
private void executeCommandTWRAP() {
context.tex_wrap_s = normalArgument & 0x1;
context.tex_wrap_t = (normalArgument >> 8) & 0x1;
if (isLogDebugEnabled) {
log.debug(String.format("sceGuTexWrap(%d, %d)", context.tex_wrap_s, context.tex_wrap_t));
}
}
private void executeCommandTBIAS() {
int old_tex_mipmap_mode = context.tex_mipmap_mode;
int old_tex_mipmap_bias_int = context.tex_mipmap_bias_int;
float old_tex_mipmap_bias = context.tex_mipmap_bias;
context.tex_mipmap_mode = normalArgument & 0x3;
int biasValue = (int) (byte) (normalArgument >> 16); // Signed 8-bit 4.4 fixed point value
context.tex_mipmap_bias_int = biasValue >> 4;
context.tex_mipmap_bias = biasValue / 16.0f;
if (isLogDebugEnabled) {
log.debug("sceGuTexLevelMode(mode=" + context.tex_mipmap_mode + ", bias=" + context.tex_mipmap_bias + ")");
}
if (context.tex_mipmap_mode != old_tex_mipmap_mode || context.tex_mipmap_bias_int != old_tex_mipmap_bias_int || context.tex_mipmap_bias != old_tex_mipmap_bias) {
textureChanged = true;
}
}
private void executeCommandTEC() {
context.tex_env_color[0] = ((normalArgument) & 255) / 255.f;
context.tex_env_color[1] = ((normalArgument >> 8) & 255) / 255.f;
context.tex_env_color[2] = ((normalArgument >> 16) & 255) / 255.f;
context.tex_env_color[3] = 1.f;
re.setTextureEnvColor(context.tex_env_color);
if (isLogDebugEnabled) {
log(String.format("sceGuTexEnvColor %06X (no alpha)", normalArgument));
}
}
private void executeCommandTFLUSH() {
// Do not load the texture right now, clut parameters can still be
// defined after the TFLUSH and before the PRIM command.
// Delay the texture loading until the PRIM command.
if (isLogDebugEnabled) {
log("tflush (deferring to prim)");
}
}
private void executeCommandTSYNC() {
// Synchronize the GE when a drawing result is used as a texture.
if (isLogDebugEnabled) {
log(helper.getCommandString(TSYNC) + " wait for rendering completion.");
}
re.waitForRenderingCompletion();
}
private void executeCommandFFAR() {
context.fog_far = floatArgument(normalArgument);
}
private void executeCommandFDIST() {
context.fog_dist = floatArgument(normalArgument);
re.setFogDist(context.fog_far, context.fog_dist);
if (isLogDebugEnabled) {
log(String.format("sceGuFog(far=%f, dist=%f, X)", context.fog_far, context.fog_dist));
}
}
private void executeCommandFCOL() {
context.fog_color[0] = ((normalArgument) & 255) / 255.f;
context.fog_color[1] = ((normalArgument >> 8) & 255) / 255.f;
context.fog_color[2] = ((normalArgument >> 16) & 255) / 255.f;
context.fog_color[3] = 1.f;
re.setFogColor(context.fog_color);
if (isLogDebugEnabled) {
log(String.format("sceGuFog(X, X, color=0x%06X) (no alpha)", normalArgument));
}
}
private void executeCommandTSLOPE() {
context.tslope_level = floatArgument(normalArgument);
if (isLogDebugEnabled) {
log(helper.getCommandString(TSLOPE) + " tslope_level=" + context.tslope_level);
}
}
private void executeCommandPSM() {
context.psm = normalArgument & 0x3;
if (isLogDebugEnabled) {
log(String.format("psm=%d %s (0x%X)", context.psm, getPsmName(context.psm), normalArgument));
}
geBufChanged = true;
}
private void executeCommandSCISSOR1() {
context.scissor_x1 = normalArgument & 0x3ff;
context.scissor_y1 = (normalArgument >> 10) & 0x3ff;
// Already update width&height in case SCISSOR2 is not coming...
context.scissor_width = 1 + context.scissor_x2 - context.scissor_x1;
context.scissor_height = 1 + context.scissor_y2 - context.scissor_y1;
scissorChanged = true;
}
private void executeCommandSCISSOR2() {
context.scissor_x2 = normalArgument & 0x3ff;
context.scissor_y2 = (normalArgument >> 10) & 0x3ff;
context.scissor_width = 1 + context.scissor_x2 - context.scissor_x1;
context.scissor_height = 1 + context.scissor_y2 - context.scissor_y1;
if (isLogDebugEnabled) {
log("sceGuScissor(" + context.scissor_x1 + "," + context.scissor_y1 + "," + context.scissor_width + "," + context.scissor_height + ")");
}
scissorChanged = true;
}
private void executeCommandNEARZ() {
float old_nearZ = context.nearZ;
context.nearZ = normalArgument & 0xFFFF;
if (old_nearZ != context.nearZ) {
depthChanged = true;
}
}
private void executeCommandFARZ() {
float old_farZ = context.farZ;
context.farZ = normalArgument & 0xFFFF;
if (old_farZ != context.farZ) {
// OpenGL requires the Depth parameters to be reloaded
depthChanged = true;
}
if (isLogDebugEnabled) {
log.debug("sceGuDepthRange(" + context.nearZ + ", " + context.farZ + ")");
}
}
private void executeCommandCTST() {
context.colorTestFunc = normalArgument & 3;
re.setColorTestFunc(context.colorTestFunc);
if (isLogDebugEnabled) {
log.debug(String.format("sceGuColorFunc colorTestFunc=%d", context.colorTestFunc));
}
}
private void executeCommandCREF() {
context.colorTestRef[0] = (normalArgument) & 0xFF;
context.colorTestRef[1] = (normalArgument >> 8) & 0xFF;
context.colorTestRef[2] = (normalArgument >> 16) & 0xFF;
re.setColorTestReference(context.colorTestRef);
if (isLogDebugEnabled) {
log.debug(String.format("sceGuColorFunc colorTestRef=0x%06X", normalArgument));
}
}
private void executeCommandCMSK() {
context.colorTestMsk[0] = (normalArgument) & 0xFF;
context.colorTestMsk[1] = (normalArgument >> 8) & 0xFF;
context.colorTestMsk[2] = (normalArgument >> 16) & 0xFF;
re.setColorTestMask(context.colorTestMsk);
if (isLogDebugEnabled) {
log.debug(String.format("sceGuColorFunc colorTestMsk=0x%06X", normalArgument));
}
}
private void executeCommandATST() {
context.alphaFunc = normalArgument & 0x7;
context.alphaRef = (normalArgument >> 8) & 0xFF;
context.alphaMask = (normalArgument >> 16) & 0xFF;
re.setAlphaFunc(context.alphaFunc, context.alphaRef, context.alphaMask);
if (isLogDebugEnabled) {
log(String.format("sceGuAlphaFunc(func=%d, ref=0x%02X, mask=0x%02X)", context.alphaFunc, context.alphaRef, context.alphaMask));
}
}
private void executeCommandSTST() {
context.stencilFunc = normalArgument & 0x7;
context.stencilRef = (normalArgument >> 8) & 0xFF;
switch (context.psm) {
case TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650:
context.stencilRef = 0;
break;
case TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR5551:
context.stencilRef &= 0x80;
break;
case TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444:
context.stencilRef &= 0xF0;
break;
}
context.stencilMask = (normalArgument >> 16) & 0xFF;
re.setStencilFunc(context.stencilFunc, context.stencilRef, context.stencilMask);
if (isLogDebugEnabled) {
log(String.format("sceGuStencilFunc(func=%d, ref=0x%02X, mask=0x%02X)", context.stencilFunc, context.stencilRef, context.stencilMask));
}
}
private void executeCommandSOP() {
context.stencilOpFail = normalArgument & 0xFF;
context.stencilOpZFail = (normalArgument >> 8) & 0xFF;
context.stencilOpZPass = (normalArgument >> 16) & 0xFF;
if (context.stencilOpFail > SOP_DECREMENT_STENCIL_VALUE) {
log.warn("Unknown stencil operation " + context.stencilOpFail);
context.stencilOpFail &= 0x7;
if (context.stencilOpFail > SOP_DECREMENT_STENCIL_VALUE) {
context.stencilOpFail = SOP_KEEP_STENCIL_VALUE;
}
}
if (context.stencilOpZFail > SOP_DECREMENT_STENCIL_VALUE) {
log.warn("Unknown stencil operation " + context.stencilOpZFail);
context.stencilOpZFail &= 0x7;
if (context.stencilOpZFail > SOP_DECREMENT_STENCIL_VALUE) {
context.stencilOpZFail = SOP_KEEP_STENCIL_VALUE;
}
}
if (context.stencilOpZPass > SOP_DECREMENT_STENCIL_VALUE) {
log.warn("Unknown stencil operation " + context.stencilOpZPass);
context.stencilOpZPass &= 0x7;
if (context.stencilOpZPass > SOP_DECREMENT_STENCIL_VALUE) {
context.stencilOpZPass = SOP_KEEP_STENCIL_VALUE;
}
}
re.setStencilOp(context.stencilOpFail, context.stencilOpZFail, context.stencilOpZPass);
if (isLogDebugEnabled) {
log("sceGuStencilOp(fail=" + (normalArgument & 0xFF) + ", zfail=" + ((normalArgument >> 8) & 0xFF) + ", zpass=" + ((normalArgument >> 16) & 0xFF) + ")");
}
}
private void executeCommandZTST() {
int oldDepthFunc = context.depthFunc;
context.depthFunc = normalArgument & 0xFF;
if (context.depthFunc > ZTST_FUNCTION_PASS_PX_WHEN_DEPTH_IS_GREATER_OR_EQUAL) {
log.warn(String.format("Unknown ztst function %d", context.depthFunc));
context.depthFunc &= 0x7;
}
if (oldDepthFunc != context.depthFunc) {
depthChanged = true;
}
if (isLogDebugEnabled) {
log("sceGuDepthFunc(" + normalArgument + ")");
}
}
private void executeCommandALPHA() {
context.blend_src = normalArgument & 0xF;
context.blend_dst = (normalArgument >> 4) & 0xF;
context.blendEquation = (normalArgument >> 8) & 0xF;
if (context.blendEquation > ALPHA_SOURCE_BLEND_OPERATION_ABSOLUTE_VALUE) {
log.warn("Unhandled blend operation " + context.blendEquation);
context.blendEquation = ALPHA_SOURCE_BLEND_OPERATION_ADD;
}
// Tested on PSP: alpha blend src/dst values in range [11..15] are equivalent to ALPHA_FIX
if (context.blend_src > ALPHA_FIX) {
if (isLogDebugEnabled) {
log.debug(String.format("alpha blend src %d changed to ALPHA_FIX", context.blend_src));
}
context.blend_src = ALPHA_FIX;
}
if (context.blend_dst > ALPHA_FIX) {
if (isLogDebugEnabled) {
log.debug(String.format("alpha blend dst %d changed to ALPHA_FIX", context.blend_dst));
}
context.blend_dst = ALPHA_FIX;
}
re.setBlendEquation(context.blendEquation);
re.setBlendFunc(context.blend_src, context.blend_dst);
if (isLogDebugEnabled) {
log("sceGuBlendFunc(op=" + context.blendEquation + ", src=" + context.blend_src + ", dst=" + context.blend_dst + ")");
}
}
private void executeCommandSFIX() {
context.sfix = normalArgument;
context.sfix_color[0] = ((context.sfix) & 255) / 255.f;
context.sfix_color[1] = ((context.sfix >> 8) & 255) / 255.f;
context.sfix_color[2] = ((context.sfix >> 16) & 255) / 255.f;
context.sfix_color[3] = 1.f;
re.setBlendSFix(context.sfix, context.sfix_color);
if (isLogDebugEnabled) {
log(String.format("%s : 0x%06X", helper.getCommandString(SFIX), context.sfix));
}
}
private void executeCommandDFIX() {
context.dfix = normalArgument;
context.dfix_color[0] = ((context.dfix) & 255) / 255.f;
context.dfix_color[1] = ((context.dfix >> 8) & 255) / 255.f;
context.dfix_color[2] = ((context.dfix >> 16) & 255) / 255.f;
context.dfix_color[3] = 1.f;
re.setBlendDFix(context.dfix, context.dfix_color);
if (isLogDebugEnabled) {
log(String.format("%s : 0x%06X", helper.getCommandString(DFIX), context.dfix));
}
}
private void setDitherMatrixValue(int index, int value) {
// The dither matrix's values can vary between -8 and 7.
context.dither_matrix[index] = ditherMatrixValueMapping[value & 0xF];
}
private void executeCommandDTH0() {
setDitherMatrixValue(0, normalArgument);
setDitherMatrixValue(1, normalArgument >> 4);
setDitherMatrixValue(2, normalArgument >> 8);
setDitherMatrixValue(3, normalArgument >> 12);
if (isLogDebugEnabled) {
log.debug(String.format("DTH0(%04X): %d %d %d %d", normalArgument, context.dither_matrix[0], context.dither_matrix[1], context.dither_matrix[2], context.dither_matrix[3]));
}
}
private void executeCommandDTH1() {
setDitherMatrixValue(4, normalArgument);
setDitherMatrixValue(5, normalArgument >> 4);
setDitherMatrixValue(6, normalArgument >> 8);
setDitherMatrixValue(7, normalArgument >> 12);
if (isLogDebugEnabled) {
log.debug(String.format("DTH1(%04X): %d %d %d %d", normalArgument, context.dither_matrix[4], context.dither_matrix[5], context.dither_matrix[6], context.dither_matrix[7]));
}
}
private void executeCommandDTH2() {
setDitherMatrixValue(8, normalArgument);
setDitherMatrixValue(9, normalArgument >> 4);
setDitherMatrixValue(10, normalArgument >> 8);
setDitherMatrixValue(11, normalArgument >> 12);
if (isLogDebugEnabled) {
log.debug(String.format("DTH2(%04X): %d %d %d %d", normalArgument, context.dither_matrix[8], context.dither_matrix[9], context.dither_matrix[10], context.dither_matrix[11]));
}
}
private void executeCommandDTH3() {
setDitherMatrixValue(12, normalArgument);
setDitherMatrixValue(13, normalArgument >> 4);
setDitherMatrixValue(14, normalArgument >> 8);
setDitherMatrixValue(15, normalArgument >> 12);
if (isLogDebugEnabled) {
log.debug(String.format("DTH3(%04X): %d %d %d %d", normalArgument, context.dither_matrix[12], context.dither_matrix[13], context.dither_matrix[14], context.dither_matrix[15]));
}
}
private void executeCommandLOP() {
context.logicOp = normalArgument & 0xF;
re.setLogicOp(context.logicOp);
if (isLogDebugEnabled) {
log.debug("sceGuLogicalOp(LogicOp=" + context.logicOp + "(" + getLOpName(context.logicOp) + "))");
}
}
private void executeCommandZMSK() {
// NOTE: PSP depth mask as 1 is meant to avoid depth writes,
// with OpenGL it's the opposite
context.depthMask = (normalArgument == 0);
re.setDepthMask(context.depthMask);
if (context.depthMask) {
// OpenGL requires the Depth parameters to be reloaded
depthChanged = true;
}
if (isLogDebugEnabled) {
log("sceGuDepthMask(" + (normalArgument != 0 ? "disableWrites" : "enableWrites") + ")");
}
}
private void executeCommandPMSKC() {
context.colorMask[0] = normalArgument & 0xFF;
context.colorMask[1] = (normalArgument >> 8) & 0xFF;
context.colorMask[2] = (normalArgument >> 16) & 0xFF;
re.setColorMask(context.colorMask[0], context.colorMask[1], context.colorMask[2], context.colorMask[3]);
if (isLogDebugEnabled) {
log(String.format("%s color mask=0x%06X", helper.getCommandString(PMSKC), normalArgument));
}
}
private void executeCommandPMSKA() {
context.colorMask[3] = normalArgument & 0xFF;
re.setColorMask(context.colorMask[0], context.colorMask[1], context.colorMask[2], context.colorMask[3]);
if (isLogDebugEnabled) {
log(String.format("%s alpha mask=0x%02X", helper.getCommandString(PMSKA), normalArgument));
}
}
private void executeCommandTRXPOS() {
context.textureTx_sx = normalArgument & 0x1FF;
context.textureTx_sy = (normalArgument >> 10) & 0x1FF;
}
private void executeCommandTRXDPOS() {
context.textureTx_dx = normalArgument & 0x1FF;
context.textureTx_dy = (normalArgument >> 10) & 0x1FF;
if (isLogDebugEnabled) {
log.debug(String.format("%s dx=%d, dy=%d", helper.getCommandString(command), context.textureTx_dx, context.textureTx_dy));
}
}
private void executeCommandTRXSIZE() {
context.textureTx_width = (normalArgument & 0x3FF) + 1;
context.textureTx_height = ((normalArgument >> 10) & 0x3FF) + 1;
if (isLogDebugEnabled) {
log.debug(String.format("%s width=%d, height=%d", helper.getCommandString(command), context.textureTx_width, context.textureTx_height));
}
}
private void executeCommandVSCX() {
int coordX = normalArgument & 0xFFFF;
log.warn("Unimplemented VSCX: coordX=" + coordX);
}
private void executeCommandVSCY() {
int coordY = normalArgument & 0xFFFF;
log.warn("Unimplemented VSCY: coordY=" + coordY);
}
private void executeCommandVSCZ() {
int coordZ = normalArgument & 0xFFFF;
log.warn("Unimplemented VSCZ: coordZ=" + coordZ);
}
private void executeCommandVTCS() {
float coordS = floatArgument(normalArgument);
log.warn("Unimplemented VTCS: coordS=" + coordS);
}
private void executeCommandVTCT() {
float coordT = floatArgument(normalArgument);
log.warn("Unimplemented VTCT: coordT=" + coordT);
}
private void executeCommandVTCQ() {
float coordQ = floatArgument(normalArgument);
log.warn("Unimplemented VTCQ: coordQ=" + coordQ);
}
private void executeCommandVCV() {
int colorR = normalArgument & 0xFF;
int colorG = (normalArgument >> 8) & 0xFF;
int colorB = (normalArgument >> 16) & 0xFF;
log.warn("Unimplemented VCV: colorR=" + colorR + ", colorG=" + colorG + ", colorB=" + colorB);
}
private void executeCommandVAP() {
int color = normalArgument & 0xFF; // Vertex color (8bit unsigned).
int prim_type = (normalArgument >> 8) & 0x7; // Primitive used.
int antialias = (normalArgument >> 11) & 0x1; // Perform antialiasing or not (only for PRIM_LINE or PRIM_LINES_STRIPS).
int clip = (normalArgument >> 12) & 0x3F; // Clipping value (6bit unsigned).
int shading = (normalArgument >> 18) & 0x1; // Shading mode.
int cull = (normalArgument >> 19) & 0x1; // Back face culling.
int v_order = (normalArgument >> 20) & 0x1; // Vertex order (only for PRIM_TRIANGLE).
int map_texture = (normalArgument >> 21) & 0x1; // Texture mapping.
int fog = (normalArgument >> 22) & 0x1; // Fogging.
int dither = (normalArgument >> 23) & 0x1; // Dithering.
log.warn(String.format("Unimplemented VAP: color=%d, prim_type=%d, antialias=%d, clip=%d, shading=%d, cull=%d"
+ ", v_order=%d, map_texture=%d, fog=%d, dither=%d",
color, prim_type, antialias, clip, shading, cull, v_order, map_texture, fog, dither));
}
private void executeCommandVFC() {
int fog = normalArgument & 0xFF;
log.warn("Unimplemented VFC: fog=" + fog);
}
private void executeCommandVSCV() {
int colorR2 = normalArgument & 0xFF;
int colorG2 = (normalArgument >> 8) & 0xFF;
int colorB2 = (normalArgument >> 16) & 0xFF;
log.warn("Unimplemented VSCV: colorR2=" + colorR2 + ", colorG2=" + colorG2 + ", colorB2=" + colorB2);
}
private void executeCommandDUMMY() {
// This command always appears before a BOFS command and seems to have
// no special meaning.
// The command also appears sometimes after a PRIM command.
// Confirmed on PSP to be a dummy command and can be safely ignored.
// This commands' normalArgument may not be always 0, as it's totally
// discarded on the PSP.
if (isLogDebugEnabled) {
log.debug("Ignored DUMMY video command.");
}
}
private void enableClientState(boolean useVertexColor, boolean useTexture) {
if (useTexture) {
re.enableClientState(IRenderingEngine.RE_TEXTURE);
} else {
re.disableClientState(IRenderingEngine.RE_TEXTURE);
}
if (useVertexColor) {
re.enableClientState(IRenderingEngine.RE_COLOR);
} else {
re.disableClientState(IRenderingEngine.RE_COLOR);
}
if (context.vinfo.normal != 0) {
re.enableClientState(IRenderingEngine.RE_NORMAL);
} else {
re.disableClientState(IRenderingEngine.RE_NORMAL);
}
re.enableClientState(IRenderingEngine.RE_VERTEX);
}
private void setTexCoordPointer(boolean useTexture, int nTexCoord, int type, int stride, int offset, boolean isNative, boolean useBufferManager) {
if (useTexture) {
if (!useBufferManager) {
re.setTexCoordPointer(nTexCoord, type, stride, offset);
} else if (isNative) {
bufferManager.setTexCoordPointer(nativeBufferId, nTexCoord, type, context.vinfo.vertexSize, offset);
} else {
bufferManager.setTexCoordPointer(bufferId, nTexCoord, type, stride, offset);
}
}
}
private void setColorPointer(boolean useVertexColor, int nColor, int type, int stride, int offset, boolean isNative, boolean useBufferManager) {
if (useVertexColor) {
if (!useBufferManager) {
re.setColorPointer(nColor, type, stride, offset);
} else if (isNative) {
bufferManager.setColorPointer(nativeBufferId, nColor, type, context.vinfo.vertexSize, offset);
} else {
bufferManager.setColorPointer(bufferId, nColor, type, stride, offset);
}
}
}
private void setVertexPointer(int nVertex, int type, int stride, int offset, boolean isNative, boolean useBufferManager) {
if (!useBufferManager) {
re.setVertexPointer(nVertex, type, stride, offset);
} else if (isNative) {
bufferManager.setVertexPointer(nativeBufferId, nVertex, type, context.vinfo.vertexSize, offset);
} else {
bufferManager.setVertexPointer(bufferId, nVertex, type, stride, offset);
}
}
private void setNormalPointer(int type, int stride, int offset, boolean isNative, boolean useBufferManager) {
if (context.vinfo.normal != 0) {
if (!useBufferManager) {
re.setNormalPointer(type, stride, offset);
} else if (isNative) {
bufferManager.setNormalPointer(nativeBufferId, type, context.vinfo.vertexSize, offset);
} else {
bufferManager.setNormalPointer(bufferId, type, stride, offset);
}
}
}
private void setWeightPointer(int numberOfWeightsForBuffer, int type, int stride, int offset, boolean isNative, boolean useBufferManager) {
if (numberOfWeightsForBuffer > 0) {
if (!useBufferManager) {
re.setWeightPointer(numberOfWeightsForBuffer, type, stride, offset);
} else if (isNative) {
re.setWeightPointer(numberOfWeightsForBuffer, type, context.vinfo.vertexSize, offset);
} else {
re.setWeightPointer(numberOfWeightsForBuffer, type, stride, offset);
}
}
}
private void setDataPointers(int nVertex, boolean useVertexColor, int nColor, boolean useTexture, int nTexCoord, boolean useNormal, int numberOfWeightsForBuffer, boolean useBufferManager) {
int stride = 0, cpos = 0, npos = 0, vpos = 0, wpos = 0;
if (context.vinfo.texture != 0 || useTexture) {
stride += SIZEOF_FLOAT * nTexCoord;
cpos = npos = vpos = stride;
}
if (useVertexColor) {
stride += SIZEOF_FLOAT * 4;
npos = vpos = stride;
}
if (useNormal) {
stride += SIZEOF_FLOAT * 3;
vpos = stride;
}
stride += SIZEOF_FLOAT * 3;
if (numberOfWeightsForBuffer > 0) {
wpos = stride;
stride += SIZEOF_FLOAT * numberOfWeightsForBuffer;
}
enableClientState(useVertexColor, useTexture);
setTexCoordPointer(useTexture, nTexCoord, IRenderingEngine.RE_FLOAT, stride, 0, false, useBufferManager);
setColorPointer(useVertexColor, nColor, IRenderingEngine.RE_FLOAT, stride, cpos, false, useBufferManager);
setNormalPointer(IRenderingEngine.RE_FLOAT, stride, npos, false, useBufferManager);
setWeightPointer(numberOfWeightsForBuffer, IRenderingEngine.RE_FLOAT, stride, wpos, false, useBufferManager);
setVertexPointer(nVertex, IRenderingEngine.RE_FLOAT, stride, vpos, false, useBufferManager);
}
public void doPositionSkinning(VertexInfo vinfo, float[] boneWeights, float[] position) {
float x = 0, y = 0, z = 0;
for (int i = 0; i < context.vinfo.skinningWeightCount; i++) {
if (boneWeights[i] != 0) {
x += (position[0] * context.bone_uploaded_matrix[i][0]
+ position[1] * context.bone_uploaded_matrix[i][3]
+ position[2] * context.bone_uploaded_matrix[i][6]
+ context.bone_uploaded_matrix[i][9]) * boneWeights[i];
y += (position[0] * context.bone_uploaded_matrix[i][1]
+ position[1] * context.bone_uploaded_matrix[i][4]
+ position[2] * context.bone_uploaded_matrix[i][7]
+ context.bone_uploaded_matrix[i][10]) * boneWeights[i];
z += (position[0] * context.bone_uploaded_matrix[i][2]
+ position[1] * context.bone_uploaded_matrix[i][5]
+ position[2] * context.bone_uploaded_matrix[i][8]
+ context.bone_uploaded_matrix[i][11]) * boneWeights[i];
}
}
position[0] = x;
position[1] = y;
position[2] = z;
}
public void doNormalSkinning(VertexInfo vinfo, float[] boneWeights, float[] normal) {
float nx = 0, ny = 0, nz = 0;
for (int i = 0; i < context.vinfo.skinningWeightCount; i++) {
if (boneWeights[i] != 0) {
// Normals shouldn't be translated :)
nx += (normal[0] * context.bone_uploaded_matrix[i][0]
+ normal[1] * context.bone_uploaded_matrix[i][3]
+ normal[2] * context.bone_uploaded_matrix[i][6]) * boneWeights[i];
ny += (normal[0] * context.bone_uploaded_matrix[i][1]
+ normal[1] * context.bone_uploaded_matrix[i][4]
+ normal[2] * context.bone_uploaded_matrix[i][7]) * boneWeights[i];
nz += (normal[0] * context.bone_uploaded_matrix[i][2]
+ normal[1] * context.bone_uploaded_matrix[i][5]
+ normal[2] * context.bone_uploaded_matrix[i][8]) * boneWeights[i];
}
}
/*
// TODO: I doubt psp hardware normalizes normals after skinning,
// but if it does, this should be uncommented :)
float length = nx*nx + ny*ny + nz*nz;
if (length > 0.f) {
length = 1.f / (float)Math.sqrt(length);
nx *= length;
ny *= length;
nz *= length;
}
*/
normal[0] = nx;
normal[1] = ny;
normal[2] = nz;
}
public static void doSkinning(float[][] boneMatrix, VertexInfo vinfo, VertexState v) {
float x = 0, y = 0, z = 0;
float nx = 0, ny = 0, nz = 0;
boolean hasNormal = vinfo.normal != 0;
for (int i = 0; i < vinfo.skinningWeightCount; ++i) {
float boneWeight = v.boneWeights[i];
if (boneWeight != 0.f) {
x += (v.p[0] * boneMatrix[i][0]
+ v.p[1] * boneMatrix[i][3]
+ v.p[2] * boneMatrix[i][6]
+ boneMatrix[i][9]) * boneWeight;
y += (v.p[0] * boneMatrix[i][1]
+ v.p[1] * boneMatrix[i][4]
+ v.p[2] * boneMatrix[i][7]
+ boneMatrix[i][10]) * boneWeight;
z += (v.p[0] * boneMatrix[i][2]
+ v.p[1] * boneMatrix[i][5]
+ v.p[2] * boneMatrix[i][8]
+ boneMatrix[i][11]) * boneWeight;
if (hasNormal) {
// Normals shouldn't be translated :)
nx += (v.n[0] * boneMatrix[i][0]
+ v.n[1] * boneMatrix[i][3]
+ v.n[2] * boneMatrix[i][6]) * boneWeight;
ny += (v.n[0] * boneMatrix[i][1]
+ v.n[1] * boneMatrix[i][4]
+ v.n[2] * boneMatrix[i][7]) * boneWeight;
nz += (v.n[0] * boneMatrix[i][2]
+ v.n[1] * boneMatrix[i][5]
+ v.n[2] * boneMatrix[i][8]) * boneWeight;
}
}
}
v.p[0] = x;
v.p[1] = y;
v.p[2] = z;
/*
// TODO: I doubt psp hardware normalizes normals after skinning,
// but if it does, this should be uncommented :)
float length = nx*nx + ny*ny + nz*nz;
if (length > 0.f) {
length = 1.f / (float)Math.sqrt(length);
nx *= length;
ny *= length;
nz *= length;
}
*/
if (hasNormal) {
v.n[0] = nx;
v.n[1] = ny;
v.n[2] = nz;
}
}
private void log(String commandString, float[] matrix) {
if (isLogDebugEnabled) {
for (int y = 0; y < 4; y++) {
log(String.format("%s %.4f %.4f %.4f %.4f", commandString, matrix[0 + y * 4], matrix[1 + y * 4], matrix[2 + y * 4], matrix[3 + y * 4]));
}
}
}
public boolean isVideoTexture(int tex_addr) {
if (!videoTextures.isEmpty()) {
// Synchronize the access to videoTextures as it can be accessed
// from a parallel threads (async display and PSP thread)
synchronized (videoTextures) {
for (AddressRange addressRange : videoTextures) {
if (addressRange.contains(tex_addr)) {
if (log.isDebugEnabled()) {
log.debug(String.format("Texture at 0x%08X is a video texture", tex_addr));
}
return true;
}
}
}
}
return false;
}
private boolean canCacheTexture(int tex_addr) {
if (!useTextureCache) {
return false;
}
// Some games are storing compressed textures in VRAM (e.g. Skate Park City).
if (context.texture_storage >= TPSM_PIXEL_STORAGE_MODE_DXT1) {
return true;
}
// Do not cache the texture if we are using the current GE directly as a texture
if (display.isGeAddress(tex_addr)) {
return false;
}
if (isVideoTexture(tex_addr)) {
return false;
}
return true;
}
private void setFlippedTexture(GETexture geTexture) {
// Textures are normally created as flipped textures (upside-down)
// but the GE textures are not flipped, so we have to flip
// them when rendering
float newTextureFlipTranslateY = geTexture.getHeight() / (float) context.texture_height[0];
if (!textureFlipped || textureFlipTranslateY != newTextureFlipTranslateY) {
textureFlipped = true;
textureFlipTranslateY = newTextureFlipTranslateY;
textureMatrixUpload.setChanged(true);
}
}
private boolean loadGETexture(int tex_addr) {
if (!display.getSaveGEToTexture() || isVideoTexture(tex_addr)) {
return false;
}
int widthGe = display.getWidthGe();
int heightGe = display.getHeightGe();
int bufferWidth = context.texture_buffer_width[0];
int pixelFormat = context.texture_storage;
GETexture geTexture;
if (IRenderingEngine.isTextureTypeIndexed[pixelFormat]) {
if (pixelFormat == TPSM_PIXEL_STORAGE_MODE_32BIT_INDEXED) {
geTexture = GETextureManager.getInstance().checkGETexture(tex_addr, bufferWidth, widthGe, heightGe, TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888);
} else {
// We only know that the texture is 16-bit indexed, but we don't
// know its exact pixel format (5650, 5551 or 4444)
geTexture = null;
if (context.tex_clut_mode >= TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650 && context.tex_clut_mode <= TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444) {
// First try with the same pixel format as the texture clut
geTexture = GETextureManager.getInstance().checkGETexture(tex_addr, bufferWidth, widthGe, heightGe, context.tex_clut_mode);
}
if (geTexture == null) {
// As a last chance, try all the pixel formats: 5650, 5551, 4444
for (int i = TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650; i <= TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444; i++) {
geTexture = GETextureManager.getInstance().checkGETexture(tex_addr, bufferWidth, widthGe, heightGe, i);
if (geTexture != null) {
break;
}
}
}
}
if (geTexture != null) {
if (tex_addr == display.getTopAddrGe()) {
// Using the active GE as texture
geTexture.copyScreenToTexture(re);
}
if (!re.canNativeClut(tex_addr, context.texture_swizzle) || context.texture_swizzle) {
// Save the texture to memory, it will be reloaded using the CLUT
geTexture.copyTextureToMemory(re);
return false;
}
geTexture = GETextureManager.getInstance().getGEIndexedTexture(re, geTexture, tex_addr, bufferWidth, context.texture_width[0], context.texture_height[0], pixelFormat);
geTexture.bind(re, true);
context.currentTextureId = geTexture.getTextureId();
setFlippedTexture(geTexture);
return true;
}
} else {
geTexture = GETextureManager.getInstance().checkGETexture(tex_addr, bufferWidth, widthGe, heightGe, pixelFormat);
}
if (geTexture == null) {
return false;
}
if (tex_addr == display.getTopAddrGe()) {
// Using the active GE as texture
geTexture.copyScreenToTexture(re);
}
int width = context.texture_width[0];
int height = context.texture_height[0];
if (geTexture.getWidthPow2() == width && geTexture.getHeightPow2() == height) {
if (isLogDebugEnabled) {
log.debug(String.format("Reusing GETexture %s", geTexture));
}
} else {
// Resize the GETexture to the requested texture size
if (isLogDebugEnabled) {
log.debug(String.format("Resizing GETexture %s to %dx%d", geTexture, width, height));
}
geTexture = GETextureManager.getInstance().getGEResizedTexture(re, geTexture, tex_addr, bufferWidth, width, height, pixelFormat);
}
geTexture.bind(re, true);
context.currentTextureId = geTexture.getTextureId();
setFlippedTexture(geTexture);
return true;
}
private int getValidNumberMipmaps(boolean silent) {
for (int level = 0; level <= context.texture_num_mip_maps; level++) {
int texaddr = context.texture_base_pointer[level] & Memory.addressMask;
if (!Memory.isAddressGood(texaddr)) {
if (texaddr == 0) {
if (isLogDebugEnabled) {
log.debug(String.format("Invalid texture address 0x%08X for texture level %d", texaddr, level));
}
} else {
if (isLogWarnEnabled) {
log.warn(String.format("Invalid texture address 0x%08X for texture level %d", texaddr, level));
}
}
return Math.max(level - 1, 0);
}
if (level > 0) {
int previousWidth = context.texture_width[level - 1];
int currentWidth = context.texture_width[level];
int previousHeight = context.texture_height[level - 1];
int currentHeight = context.texture_height[level];
if (currentWidth * 2 != previousWidth || currentHeight * 2 != previousHeight) {
if (!silent && isLogWarnEnabled) {
log.warn(String.format("Texture mipmap with invalid dimension at level %d: (%dx%d)@0x%08X -> (%dx%d)@0x%08X", level, previousWidth, previousHeight, context.texture_base_pointer[level - 1] & Memory.addressMask, currentWidth, currentHeight, texaddr));
if (context.tex_mipmap_mode == TBIAS_MODE_CONST && context.tex_mipmap_bias_int >= level) {
log.warn(String.format("... and this invalid Texture mipmap will be used with mipmap_mode=%d, mipmap_bias=%d", context.tex_mipmap_mode, context.tex_mipmap_bias_int));
}
}
return level - 1;
}
}
}
return context.texture_num_mip_maps;
}
private String getModdedTextureDirectory() {
String tmpPath = Settings.getInstance().readString("emu.tmppath");
char sep = File.separatorChar;
return String.format("%s%c%s%cTextures%c", tmpPath, sep, State.discId, sep, sep);
}
private boolean hasModdedTexture(int level, StringBuilder moddedTextureFileName) {
// Quick check: if there is no Texture directory, no need to check for an image file
if (!hasModdedTextureDirectory) {
return false;
}
String directory = getModdedTextureDirectory();
int origLength = moddedTextureFileName.length();
for (String extension : ImageIO.getReaderFileSuffixes()) {
moddedTextureFileName.setLength(origLength);
String fileNameSuffix = "";
if (IRenderingEngine.isTextureTypeIndexed[context.texture_storage]) {
// For textures using a CLUT, add the clut address to the file name.
fileNameSuffix = String.format("_%08X", context.tex_clut_addr);
}
moddedTextureFileName.append(String.format("%sImage%08X%s.%s", directory, context.texture_base_pointer[level], fileNameSuffix, extension));
File moddedTextureFile = new File(moddedTextureFileName.toString());
if (moddedTextureFile.canRead()) {
return true;
}
}
return false;
}
public static int alignBufferWidth(int bufferWidth, int pixelFormat) {
int alignment = IRenderingEngine.alignementOfTextureBufferWidth[pixelFormat] - 1;
if (alignment <= 0) {
return bufferWidth;
}
bufferWidth &= ~alignment;
if (bufferWidth == 0) {
// bufferWidth of 0 is the same as the smallest valid bufferWidth
// (tested on PSP)
bufferWidth = alignment + 1;
}
return bufferWidth;
}
public static int readLittleEndianShort(InputStream is) throws IOException {
byte[] buffer = new byte[2];
if (is.read(buffer) != buffer.length) {
return -1;
}
return (buffer[0] & 0xFF) | ((buffer[1] & 0xFF) << 8);
}
public static int readLittleEndianInt(InputStream is) throws IOException {
byte[] buffer = new byte[4];
if (is.read(buffer) != buffer.length) {
return -1;
}
return (buffer[0] & 0xFF) | ((buffer[1] & 0xFF) << 8) | ((buffer[2] & 0xFF) << 16) | ((buffer[3] & 0xFF) << 24);
}
private void loadTexture(int level, int reTextureLevel) {
Buffer final_buffer;
int texclut = context.tex_clut_addr;
int textureByteAlignment = 4; // 32 bits
boolean compressedTexture = false;
// Extract texture information with the minor conversion possible
// TODO: Get rid of information copying, and implement all the available formats
int texaddr = context.texture_base_pointer[level] & Memory.addressMask;
int compressedTextureSize = 0;
int buffer_storage = context.texture_storage;
// The texture buffer width must be a multiple of 4, 8, 16 or 32 depending
// on the pixel format
int textureBufferWidthInPixels = alignBufferWidth(context.texture_buffer_width[level], context.texture_storage);
StringBuilder moddedTextureFileName = new StringBuilder();
if (enableTextureModding && hasModdedTexture(level, moddedTextureFileName)) {
try {
File moddedTextureFile = new File(moddedTextureFileName.toString());
int width = Math.max(textureBufferWidthInPixels, context.texture_width[level]);
int height = context.texture_height[level];
boolean readUsingImageIO = true;
// ImageIO is losing the alpha color values while reading a BMP file :-(.
// Read BMP files manually to retrieve the correct alpha color values.
if (moddedTextureFile.getName().endsWith(".bmp")) {
InputStream is = new BufferedInputStream(new FileInputStream(moddedTextureFile));
// Reading file header
int magic = readLittleEndianShort(is);
int fileSize = readLittleEndianInt(is);
is.skip(4);
int dataOffset = readLittleEndianInt(is);
// Reading DIB header
int dibHeaderLength = readLittleEndianInt(is);
int imageWidth = readLittleEndianInt(is);
int imageHeight = readLittleEndianInt(is);
int numberPlanes = readLittleEndianShort(is);
int bitsPerPixel = readLittleEndianShort(is);
// Skip rest of DIB header until data start
is.skip(dataOffset - 14 - 16);
if (magic == (('M' << 8) | 'B') && dibHeaderLength >= 16 && fileSize >= dataOffset && numberPlanes == 1 && bitsPerPixel == 32 && imageWidth == width && imageHeight == height) {
for (int y = height - 1; y >= 0; y--) {
for (int x = 0, i = y * width; x < width; x++, i++) {
int argb = readLittleEndianInt(is);
// Convert ARGB into ABGR
int abgr = (argb & 0xFF00FF00) | ((argb & 0x00FF0000) >> 16) | ((argb & 0x000000FF) << 16);
tmp_texture_buffer32[i] = abgr;
}
}
readUsingImageIO = false;
}
is.close();
}
if (readUsingImageIO) {
BufferedImage img = ImageIO.read(moddedTextureFile);
for (int y = height - 1; y >= 0; y--) {
for (int x = 0, i = y * width; x < width; x++, i++) {
int argb = img.getRGB(x, y);
// Convert ARGB into ABGR
int abgr = (argb & 0xFF00FF00) | ((argb & 0x00FF0000) >> 16) | ((argb & 0x000000FF) << 16);
tmp_texture_buffer32[i] = abgr;
}
}
}
final_buffer = IntBuffer.wrap(tmp_texture_buffer32);
textureByteAlignment = 4;
buffer_storage = TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888;
} catch (IOException e) {
log.error("Error while reading modded texture file", e);
return;
}
} else {
switch (context.texture_storage) {
case TPSM_PIXEL_STORAGE_MODE_4BIT_INDEXED: {
if (texclut == 0) {
return;
}
buffer_storage = context.tex_clut_mode;
switch (context.tex_clut_mode) {
case CMODE_FORMAT_16BIT_BGR5650:
case CMODE_FORMAT_16BIT_ABGR5551:
case CMODE_FORMAT_16BIT_ABGR4444: {
textureByteAlignment = 2; // 16 bits
short[] clut = readClut16(level);
int clutSharingOffset = context.mipmapShareClut ? 0 : level * 16;
if (!context.texture_swizzle) {
int length = Math.max(textureBufferWidthInPixels, context.texture_width[level]) * context.texture_height[level];
IMemoryReader memoryReader = MemoryReader.getMemoryReader(texaddr, length / 2, 1);
for (int i = 0; i < length; i += 2) {
int index = memoryReader.readNext();
tmp_texture_buffer16[i] = clut[getClutIndex(index & 0xF) + clutSharingOffset];
tmp_texture_buffer16[i + 1] = clut[getClutIndex((index >> 4) & 0xF) + clutSharingOffset];
}
final_buffer = ShortBuffer.wrap(tmp_texture_buffer16);
if (State.captureGeNextFrame) {
log.info("Capture loadTexture clut 4/16 unswizzled");
CaptureManager.captureRAM(texaddr, length / 2);
}
} else {
unswizzleTextureFromMemory(texaddr, 0, level, textureBufferWidthInPixels);
int pixels = textureBufferWidthInPixels * context.texture_height[level];
for (int i = 0, j = 0; i < pixels; i += 8, j++) {
int n = tmp_texture_buffer32[j];
int index = n & 0xF;
tmp_texture_buffer16[i + 0] = clut[getClutIndex(index) + clutSharingOffset];
index = (n >> 4) & 0xF;
tmp_texture_buffer16[i + 1] = clut[getClutIndex(index) + clutSharingOffset];
index = (n >> 8) & 0xF;
tmp_texture_buffer16[i + 2] = clut[getClutIndex(index) + clutSharingOffset];
index = (n >> 12) & 0xF;
tmp_texture_buffer16[i + 3] = clut[getClutIndex(index) + clutSharingOffset];
index = (n >> 16) & 0xF;
tmp_texture_buffer16[i + 4] = clut[getClutIndex(index) + clutSharingOffset];
index = (n >> 20) & 0xF;
tmp_texture_buffer16[i + 5] = clut[getClutIndex(index) + clutSharingOffset];
index = (n >> 24) & 0xF;
tmp_texture_buffer16[i + 6] = clut[getClutIndex(index) + clutSharingOffset];
index = (n >> 28) & 0xF;
tmp_texture_buffer16[i + 7] = clut[getClutIndex(index) + clutSharingOffset];
}
final_buffer = ShortBuffer.wrap(tmp_texture_buffer16);
break;
}
break;
}
case CMODE_FORMAT_32BIT_ABGR8888: {
int[] clut = readClut32(level);
int clutSharingOffset = context.mipmapShareClut ? 0 : level * 16;
if (!context.texture_swizzle) {
int length = Math.max(textureBufferWidthInPixels, context.texture_width[level]) * context.texture_height[level];
IMemoryReader memoryReader = MemoryReader.getMemoryReader(texaddr, length / 2, 1);
for (int i = 0; i < length; i += 2) {
int index = memoryReader.readNext();
tmp_texture_buffer32[i + 1] = clut[getClutIndex((index >> 4) & 0xF) + clutSharingOffset];
tmp_texture_buffer32[i] = clut[getClutIndex(index & 0xF) + clutSharingOffset];
}
final_buffer = IntBuffer.wrap(tmp_texture_buffer32);
if (State.captureGeNextFrame) {
log.info("Capture loadTexture clut 4/32 unswizzled");
CaptureManager.captureRAM(texaddr, length / 2);
}
} else {
unswizzleTextureFromMemory(texaddr, 0, level, textureBufferWidthInPixels);
int pixels = textureBufferWidthInPixels * context.texture_height[level];
for (int i = pixels - 8, j = (pixels / 8) - 1; i >= 0; i -= 8, j--) {
int n = tmp_texture_buffer32[j];
int index = n & 0xF;
tmp_texture_buffer32[i + 0] = clut[getClutIndex(index) + clutSharingOffset];
index = (n >> 4) & 0xF;
tmp_texture_buffer32[i + 1] = clut[getClutIndex(index) + clutSharingOffset];
index = (n >> 8) & 0xF;
tmp_texture_buffer32[i + 2] = clut[getClutIndex(index) + clutSharingOffset];
index = (n >> 12) & 0xF;
tmp_texture_buffer32[i + 3] = clut[getClutIndex(index) + clutSharingOffset];
index = (n >> 16) & 0xF;
tmp_texture_buffer32[i + 4] = clut[getClutIndex(index) + clutSharingOffset];
index = (n >> 20) & 0xF;
tmp_texture_buffer32[i + 5] = clut[getClutIndex(index) + clutSharingOffset];
index = (n >> 24) & 0xF;
tmp_texture_buffer32[i + 6] = clut[getClutIndex(index) + clutSharingOffset];
index = (n >> 28) & 0xF;
tmp_texture_buffer32[i + 7] = clut[getClutIndex(index) + clutSharingOffset];
}
final_buffer = IntBuffer.wrap(tmp_texture_buffer32);
}
break;
}
default: {
error("Unhandled clut4 texture mode " + context.tex_clut_mode);
return;
}
}
break;
}
case TPSM_PIXEL_STORAGE_MODE_8BIT_INDEXED: {
if (re.canNativeClut(texaddr, context.texture_swizzle)) {
final_buffer = getTextureBuffer(texaddr, 1, level, textureBufferWidthInPixels);
textureByteAlignment = 1; // 8 bits
} else {
final_buffer = readIndexedTexture(level, texaddr, texclut, 1, textureBufferWidthInPixels);
buffer_storage = context.tex_clut_mode;
textureByteAlignment = textureByteAlignmentMapping[context.tex_clut_mode];
}
break;
}
case TPSM_PIXEL_STORAGE_MODE_16BIT_INDEXED: {
if (re.canNativeClut(texaddr, context.texture_swizzle)) {
final_buffer = getTextureBuffer(texaddr, 2, level, textureBufferWidthInPixels);
textureByteAlignment = 2; // 16 bits
} else {
final_buffer = readIndexedTexture(level, texaddr, texclut, 2, textureBufferWidthInPixels);
buffer_storage = context.tex_clut_mode;
textureByteAlignment = textureByteAlignmentMapping[context.tex_clut_mode];
}
break;
}
case TPSM_PIXEL_STORAGE_MODE_32BIT_INDEXED: {
if (re.canNativeClut(texaddr, context.texture_swizzle)) {
final_buffer = getTextureBuffer(texaddr, 4, level, textureBufferWidthInPixels);
textureByteAlignment = 4; // 32 bits
} else {
final_buffer = readIndexedTexture(level, texaddr, texclut, 4, textureBufferWidthInPixels);
buffer_storage = context.tex_clut_mode;
textureByteAlignment = textureByteAlignmentMapping[context.tex_clut_mode];
}
break;
}
case TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650:
case TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR5551:
case TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444: {
textureByteAlignment = 2; // 16 bits
if (!context.texture_swizzle) {
int length = Math.max(textureBufferWidthInPixels, context.texture_width[level]) * context.texture_height[level];
final_buffer = Memory.getInstance().getBuffer(texaddr, length * 2);
if (final_buffer == null) {
IMemoryReader memoryReader = MemoryReader.getMemoryReader(texaddr, length * 2, 2);
for (int i = 0; i < length; i++) {
int pixel = memoryReader.readNext();
tmp_texture_buffer16[i] = (short) pixel;
}
final_buffer = ShortBuffer.wrap(tmp_texture_buffer16);
}
if (State.captureGeNextFrame) {
log.info("Capture loadTexture 16 unswizzled");
CaptureManager.captureRAM(texaddr, length * 2);
}
} else {
final_buffer = unswizzleTextureFromMemory(texaddr, 2, level, textureBufferWidthInPixels);
}
break;
}
case TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888: {
final_buffer = getTextureBuffer(texaddr, 4, level, textureBufferWidthInPixels);
break;
}
case TPSM_PIXEL_STORAGE_MODE_DXT1: {
if (isLogDebugEnabled) {
log.debug("Loading texture TPSM_PIXEL_STORAGE_MODE_DXT1 " + Integer.toHexString(texaddr));
}
compressedTexture = true;
compressedTextureSize = getCompressedTextureSize(level, 8);
IMemoryReader memoryReader = MemoryReader.getMemoryReader(texaddr, compressedTextureSize, 4);
// PSP DXT1 hardware format reverses the colors and the per-pixel
// bits, and encodes the color in RGB 565 format
int i = 0;
int bufferWidth4 = round4(context.texture_buffer_width[level]);
int width4 = round4(context.texture_width[level]);
int height4 = round4(context.texture_height[level]);
int readWidth = min(bufferWidth4, width4);
// Skip width if the buffer width is larger than the texture width
int skipWidth = max(0, (bufferWidth4 - width4) >> 1);
for (int y = 0; y < height4; y += 4) {
for (int x = 0; x < readWidth; x += 4, i += 2) {
tmp_texture_buffer32[i + 1] = memoryReader.readNext();
tmp_texture_buffer32[i + 0] = memoryReader.readNext();
}
memoryReader.skip(skipWidth);
for (int x = readWidth; x < width4; x += 4, i += 2) {
tmp_texture_buffer32[i + 0] = 0;
tmp_texture_buffer32[i + 1] = 0;
}
}
final_buffer = IntBuffer.wrap(tmp_texture_buffer32);
break;
}
case TPSM_PIXEL_STORAGE_MODE_DXT3: {
if (isLogDebugEnabled) {
log.debug("Loading texture TPSM_PIXEL_STORAGE_MODE_DXT3 " + Integer.toHexString(texaddr));
}
compressedTexture = true;
compressedTextureSize = getCompressedTextureSize(level, 4);
IMemoryReader memoryReader = MemoryReader.getMemoryReader(texaddr, compressedTextureSize, 4);
// PSP DXT3 format reverses the alpha and color parts of each block,
// and reverses the color and per-pixel terms in the color part.
int i = 0;
int bufferWidth4 = round4(context.texture_buffer_width[level]);
int width4 = round4(context.texture_width[level]);
int height4 = round4(context.texture_height[level]);
int readWidth = min(bufferWidth4, width4);
// Skip width if the buffer width is larger than the texture width
int skipWidth = max(0, bufferWidth4 - width4);
for (int y = 0; y < height4; y += 4) {
for (int x = 0; x < readWidth; x += 4, i += 4) {
// Color
tmp_texture_buffer32[i + 3] = memoryReader.readNext();
tmp_texture_buffer32[i + 2] = memoryReader.readNext();
// Alpha
tmp_texture_buffer32[i + 0] = memoryReader.readNext();
tmp_texture_buffer32[i + 1] = memoryReader.readNext();
}
memoryReader.skip(skipWidth);
for (int x = readWidth; x < width4; x += 4, i += 4) {
tmp_texture_buffer32[i + 0] = 0;
tmp_texture_buffer32[i + 1] = 0;
tmp_texture_buffer32[i + 2] = 0;
tmp_texture_buffer32[i + 3] = 0;
}
}
final_buffer = IntBuffer.wrap(tmp_texture_buffer32);
break;
}
case TPSM_PIXEL_STORAGE_MODE_DXT5: {
if (isLogDebugEnabled) {
log.debug("Loading texture TPSM_PIXEL_STORAGE_MODE_DXT5 " + Integer.toHexString(texaddr));
}
compressedTexture = true;
compressedTextureSize = getCompressedTextureSize(level, 4);
IMemoryReader memoryReader = MemoryReader.getMemoryReader(texaddr, compressedTextureSize, 2);
// PSP DXT5 format reverses the alpha and color parts of each block,
// and reverses the color and per-pixel terms in the color part. In
// the alpha part, the 2 reference alpha values are swapped with the
// alpha interpolation values.
int i = 0;
int bufferWidth4 = round4(context.texture_buffer_width[level]);
int width4 = round4(context.texture_width[level]);
int height4 = round4(context.texture_height[level]);
int readWidth = min(bufferWidth4, width4);
// Skip width if the buffer width is larger than the texture width
int skipWidth = max(0, bufferWidth4 - width4);
for (int y = 0; y < height4; y += 4) {
for (int x = 0; x < readWidth; x += 4, i += 8) {
// Color
tmp_texture_buffer16[i + 6] = (short) memoryReader.readNext();
tmp_texture_buffer16[i + 7] = (short) memoryReader.readNext();
tmp_texture_buffer16[i + 4] = (short) memoryReader.readNext();
tmp_texture_buffer16[i + 5] = (short) memoryReader.readNext();
// Alpha
tmp_texture_buffer16[i + 1] = (short) memoryReader.readNext();
tmp_texture_buffer16[i + 2] = (short) memoryReader.readNext();
tmp_texture_buffer16[i + 3] = (short) memoryReader.readNext();
tmp_texture_buffer16[i + 0] = (short) memoryReader.readNext();
}
memoryReader.skip(skipWidth);
for (int x = readWidth; x < width4; x += 4, i += 8) {
tmp_texture_buffer16[i + 0] = 0;
tmp_texture_buffer16[i + 1] = 0;
tmp_texture_buffer16[i + 2] = 0;
tmp_texture_buffer16[i + 3] = 0;
tmp_texture_buffer16[i + 4] = 0;
tmp_texture_buffer16[i + 5] = 0;
tmp_texture_buffer16[i + 6] = 0;
tmp_texture_buffer16[i + 7] = 0;
}
}
final_buffer = ShortBuffer.wrap(tmp_texture_buffer16);
break;
}
default: {
error("Unhandled texture storage " + context.texture_storage);
return;
}
}
}
// Check if scaling is needed for xBRZ.
boolean scale = isUsexBRZFilter();
if ((texaddr > 83886080) && (texaddr < 142606336)) {
scale = false;
}
// Upload texture to openGL.
re.setPixelStore(textureBufferWidthInPixels, textureByteAlignment);
if (compressedTexture) {
re.setCompressedTexImage(
reTextureLevel,
buffer_storage,
context.texture_width[level], context.texture_height[level],
compressedTextureSize,
final_buffer);
} else if (isUsexBRZFilter() && scale) {
int textureSize = Math.max(textureBufferWidthInPixels, this.context.texture_width[level]) * this.context.texture_height[level] * textureByteAlignment;
this.re.setTexImagexBRZ(
reTextureLevel,
buffer_storage,
this.context.texture_width[level], this.context.texture_height[level],
this.context.texture_buffer_width[level],
buffer_storage,
buffer_storage,
textureSize,
final_buffer);
} else {
int textureSize = Math.max(textureBufferWidthInPixels, context.texture_width[level]) * context.texture_height[level] * textureByteAlignment;
re.setTexImage(
reTextureLevel,
buffer_storage,
context.texture_width[level], context.texture_height[level],
buffer_storage,
buffer_storage,
textureSize,
final_buffer);
}
if (State.captureGeNextFrame) {
boolean vramImage = Memory.isVRAM(texaddr);
boolean overwriteFile = !vramImage;
if (vramImage || !CaptureManager.isImageCaptured(texaddr)) {
CaptureManager.captureImage(texaddr, level, final_buffer, context.texture_width[level], context.texture_height[level], context.texture_buffer_width[level], buffer_storage, compressedTexture, compressedTextureSize, false, overwriteFile);
}
}
}
private void loadTexture() {
if (display.isGeAddress(context.texture_base_pointer[0])) {
textureChanged = true;
// wait for rendering completion when using the current GE as a texture...
re.waitForRenderingCompletion();
}
// No need to reload or check the texture cache if no texture parameter
// has been changed since last call loadTexture()
if (!textureChanged) {
return;
}
// HACK: avoid texture uploads of null pointers
// This can come from Sony's GE init code (pspsdk GE init is ok)
if (context.texture_base_pointer[0] == 0) {
return;
}
// Texture not used when disabled or in clear mode.
if (!context.textureFlag.isEnabled() || context.clearMode) {
return;
}
int mipmapIndex = 0;
//
// Basic support of mipmaps that are indexed with a constant value (TBIAS_MODE_CONST).
//
// Load such a mipmap as a stand-alone texture. Some applications do define
// multiple mipmap levels all having the same size (width & height).
// The application is then accessing each mipmap with TBIAS_MODE_CONST.
// Such a case is easier being implemented with independent textures
// instead of trying to use OpenGL texture mipmaps
// (which do require decreasing sizes at each level).
if (context.tex_mipmap_mode == TBIAS_MODE_CONST) {
mipmapIndex = Math.max(context.tex_mipmap_bias_int, 0);
}
int tex_addr = context.texture_base_pointer[mipmapIndex] & Memory.addressMask;
if (!Memory.isAddressGood(tex_addr)) {
if (isLogWarnEnabled) {
log.warn(String.format("Invalid texture address 0x%08X for texture level 0", tex_addr));
}
return;
}
re.setTextureFormat(context.texture_storage, context.texture_swizzle);
boolean compressedTexture = (context.texture_storage >= TPSM_PIXEL_STORAGE_MODE_DXT1 && context.texture_storage <= TPSM_PIXEL_STORAGE_MODE_DXT5);
if (loadGETexture(tex_addr)) {
re.setTextureMipmapMagFilter(context.tex_mag_filter);
re.setTextureMipmapMinFilter(context.tex_min_filter);
checkTextureMinFilter(compressedTexture, context.texture_num_mip_maps);
textureChanged = false;
return;
}
Texture texture;
if (!canCacheTexture(tex_addr)) {
texture = null;
// Generate a texture id if we don't have one
if (textureId == -1) {
textureId = re.genTexture();
}
re.bindTexture(textureId);
context.currentTextureId = textureId;
} else {
TextureCache textureCache = TextureCache.getInstance();
boolean textureRequiresClut = IRenderingEngine.isTextureTypeIndexed[context.texture_storage];
if (textureRequiresClut && re.canNativeClut(tex_addr, context.texture_swizzle)) {
if (context.texture_storage >= TPSM_PIXEL_STORAGE_MODE_8BIT_INDEXED && context.texture_storage <= TPSM_PIXEL_STORAGE_MODE_32BIT_INDEXED) {
// The Clut will be resolved by the shader
textureRequiresClut = false;
}
}
textureCacheLookupStatistics.start();
// Check if the texture is in the cache
if (textureRequiresClut) {
texture = textureCache.getTexture(context.texture_base_pointer[mipmapIndex], context.texture_buffer_width[mipmapIndex], context.texture_width[mipmapIndex], context.texture_height[mipmapIndex], context.texture_storage, context.tex_clut_addr, context.tex_clut_mode, context.tex_clut_start, context.tex_clut_shift, context.tex_clut_mask, context.tex_clut_num_blocks, context.texture_num_mip_maps, context.mipmapShareClut, null, null);
} else {
texture = textureCache.getTexture(context.texture_base_pointer[mipmapIndex], context.texture_buffer_width[mipmapIndex], context.texture_width[mipmapIndex], context.texture_height[mipmapIndex], context.texture_storage, 0, 0, 0, 0, 0, 0, context.texture_num_mip_maps, false, null, null);
}
textureCacheLookupStatistics.end();
// Create the texture if not yet in the cache
if (texture == null) {
if (textureRequiresClut) {
texture = new Texture(textureCache, context.texture_base_pointer[mipmapIndex], context.texture_buffer_width[mipmapIndex], context.texture_width[mipmapIndex], context.texture_height[mipmapIndex], context.texture_storage, context.tex_clut_addr, context.tex_clut_mode, context.tex_clut_start, context.tex_clut_shift, context.tex_clut_mask, context.tex_clut_num_blocks, context.texture_num_mip_maps, context.mipmapShareClut, null, null);
} else {
texture = new Texture(textureCache, context.texture_base_pointer[mipmapIndex], context.texture_buffer_width[mipmapIndex], context.texture_width[mipmapIndex], context.texture_height[mipmapIndex], context.texture_storage, 0, 0, 0, 0, 0, 0, context.texture_num_mip_maps, false, null, null);
}
textureCache.addTexture(re, texture);
}
texture.bindTexture(re);
context.currentTextureId = texture.getTextureId(re);
}
if (textureFlipped) {
textureFlipped = false;
textureMatrixUpload.setChanged(true);
}
// Load the texture if not yet loaded
if (texture == null || !texture.isLoaded() || State.captureGeNextFrame) {
if (isLogDebugEnabled) {
log(helper.getCommandString(TFLUSH)
+ " " + String.format("0x%08X", context.texture_base_pointer[mipmapIndex])
+ ", buffer_width=" + context.texture_buffer_width[mipmapIndex]
+ " (" + context.texture_width[mipmapIndex] + "," + context.texture_height[mipmapIndex] + ")");
log(helper.getCommandString(TFLUSH)
+ " texture_storage=0x" + Integer.toHexString(context.texture_storage)
+ "(" + getPsmName(context.texture_storage)
+ "), tex_clut_mode=0x" + Integer.toHexString(context.tex_clut_mode)
+ ", tex_clut_addr=" + String.format("0x%08X", context.tex_clut_addr)
+ ", texture_swizzle=" + context.texture_swizzle);
}
if (isGeProfilerEnabled) {
GEProfiler.loadTexture();
}
// If the texture is the current GE
// first save the GE to memory before loading the texture.
if (tex_addr == context.fbp && context.texture_storage == context.psm && context.texture_buffer_width[mipmapIndex] == context.fbw) {
display.copyGeToMemory(true, false);
// Re-bind the texture to be loaded, as the bind might have been changed during the GE copy.
re.bindTexture(context.currentTextureId);
}
int numberMipmaps = getValidNumberMipmaps(false);
// Set the texture min/mag filters before uploading the texture
// (some drivers have problems changing the parameters afterwards)
re.setTextureMipmapMagFilter(context.tex_mag_filter);
re.setTextureMipmapMinFilter(context.tex_min_filter);
checkTextureMinFilter(compressedTexture, numberMipmaps);
if (mipmapIndex == 0) {
for (int level = 0; level <= numberMipmaps; level++) {
loadTexture(level, level);
}
} else {
// Load a single mipmap as a whole texture
loadTexture(mipmapIndex, 0);
}
if (texture != null) {
texture.setIsLoaded();
if (isLogDebugEnabled) {
log(helper.getCommandString(TFLUSH) + " Loaded texture " + texture.getGlId());
}
}
} else {
re.setTextureMipmapMagFilter(context.tex_mag_filter);
re.setTextureMipmapMinFilter(context.tex_min_filter);
checkTextureMinFilter(compressedTexture, context.texture_num_mip_maps);
if (isLogDebugEnabled) {
log(helper.getCommandString(TFLUSH) + " Reusing cached texture " + texture.getGlId());
}
}
textureChanged = false;
}
private void checkTextureMinFilter(boolean compressedTexture, int numberMipmaps) {
// OpenGL/Hardware cannot interpolate between compressed textures;
// this restriction has been checked on NVIDIA GeForce 8500 GT and 9800 GT
if (compressedTexture) {
int new_tex_min_filter;
if (context.tex_min_filter == TFLT_NEAREST || context.tex_min_filter == TFLT_NEAREST_MIPMAP_LINEAR || context.tex_min_filter == TFLT_NEAREST_MIPMAP_NEAREST) {
new_tex_min_filter = TFLT_NEAREST;
} else {
new_tex_min_filter = TFLT_LINEAR;
}
if (new_tex_min_filter != context.tex_min_filter) {
re.setTextureMipmapMinFilter(new_tex_min_filter);
if (isLogDebugEnabled) {
log("Overwriting texture min filter, no mipmap was generated but filter was set to use mipmap");
}
}
}
}
private Buffer readIndexedTexture(int level, int texaddr, int texclut, int bytesPerIndex, int textureBufferWidthInPixels) {
Buffer buffer = null;
int length = textureBufferWidthInPixels * context.texture_height[level];
switch (context.tex_clut_mode) {
case CMODE_FORMAT_16BIT_BGR5650:
case CMODE_FORMAT_16BIT_ABGR5551:
case CMODE_FORMAT_16BIT_ABGR4444: {
if (texclut == 0) {
return null;
}
short[] clut = readClut16(level);
if (!context.texture_swizzle) {
IMemoryReader memoryReader = MemoryReader.getMemoryReader(texaddr, length * bytesPerIndex, bytesPerIndex);
for (int i = 0; i < length; i++) {
int index = memoryReader.readNext();
tmp_texture_buffer16[i] = clut[getClutIndex(index)];
}
buffer = ShortBuffer.wrap(tmp_texture_buffer16);
if (State.captureGeNextFrame) {
log.info("Capture loadTexture clut 8/16 unswizzled");
CaptureManager.captureRAM(texaddr, length * bytesPerIndex);
}
} else {
unswizzleTextureFromMemory(texaddr, bytesPerIndex, level, textureBufferWidthInPixels);
switch (bytesPerIndex) {
case 1: {
for (int i = 0, j = 0; i < length; i += 4, j++) {
int n = tmp_texture_buffer32[j];
int index = n & 0xFF;
tmp_texture_buffer16[i + 0] = clut[getClutIndex(index)];
index = (n >> 8) & 0xFF;
tmp_texture_buffer16[i + 1] = clut[getClutIndex(index)];
index = (n >> 16) & 0xFF;
tmp_texture_buffer16[i + 2] = clut[getClutIndex(index)];
index = (n >> 24) & 0xFF;
tmp_texture_buffer16[i + 3] = clut[getClutIndex(index)];
}
break;
}
case 2: {
for (int i = 0, j = 0; i < length; i += 2, j++) {
int n = tmp_texture_buffer32[j];
tmp_texture_buffer16[i + 0] = clut[getClutIndex(n & 0xFFFF)];
tmp_texture_buffer16[i + 1] = clut[getClutIndex(n >>> 16)];
}
break;
}
case 4: {
for (int i = 0; i < length; i++) {
int n = tmp_texture_buffer32[i];
tmp_texture_buffer16[i] = clut[getClutIndex(n)];
}
break;
}
}
buffer = ShortBuffer.wrap(tmp_texture_buffer16);
}
break;
}
case CMODE_FORMAT_32BIT_ABGR8888: {
if (texclut == 0) {
return null;
}
int[] clut = readClut32(level);
if (!context.texture_swizzle) {
IMemoryReader memoryReader = MemoryReader.getMemoryReader(texaddr, length * bytesPerIndex, bytesPerIndex);
for (int i = 0; i < length; i++) {
int index = memoryReader.readNext();
tmp_texture_buffer32[i] = clut[getClutIndex(index)];
}
buffer = IntBuffer.wrap(tmp_texture_buffer32);
if (State.captureGeNextFrame) {
log.info("Capture loadTexture clut 8/32 unswizzled");
CaptureManager.captureRAM(texaddr, length * bytesPerIndex);
}
} else {
unswizzleTextureFromMemory(texaddr, bytesPerIndex, level, textureBufferWidthInPixels);
switch (bytesPerIndex) {
case 1: {
for (int i = length - 4, j = (length / 4) - 1; i >= 0; i -= 4, j--) {
int n = tmp_texture_buffer32[j];
int index = n & 0xFF;
tmp_texture_buffer32[i + 0] = clut[getClutIndex(index)];
index = (n >> 8) & 0xFF;
tmp_texture_buffer32[i + 1] = clut[getClutIndex(index)];
index = (n >> 16) & 0xFF;
tmp_texture_buffer32[i + 2] = clut[getClutIndex(index)];
index = (n >> 24) & 0xFF;
tmp_texture_buffer32[i + 3] = clut[getClutIndex(index)];
}
break;
}
case 2: {
for (int i = length - 2, j = (length / 2) - 1; i >= 0; i -= 2, j--) {
int n = tmp_texture_buffer32[j];
tmp_texture_buffer32[i + 0] = clut[getClutIndex(n & 0xFFFF)];
tmp_texture_buffer32[i + 1] = clut[getClutIndex(n >>> 16)];
}
break;
}
case 4: {
for (int i = 0; i < length; i++) {
int n = tmp_texture_buffer32[i];
tmp_texture_buffer32[i] = clut[getClutIndex(n)];
}
break;
}
}
buffer = IntBuffer.wrap(tmp_texture_buffer32);
}
break;
}
default: {
error("Unhandled clut8 texture mode " + context.tex_clut_mode);
break;
}
}
return buffer;
}
private void setScissor() {
if (context.scissor_x1 >= 0 && context.scissor_y1 >= 0
&& context.scissor_width <= context.region_width
&& context.scissor_height <= context.region_height) {
int scissorX = context.scissor_x1;
int scissorY = context.scissor_y1;
int scissorWidth = context.scissor_width;
int scissorHeight = context.scissor_height;
if (scissorHeight < Screen.height) {
scissorY = Screen.height - scissorHeight - scissorY;
}
re.setScissor(scissorX, scissorY, scissorWidth, scissorHeight);
context.scissorTestFlag.setEnabled(true);
} else {
context.scissorTestFlag.setEnabled(false);
}
}
private float[] getProjectionMatrix() {
if (context.transform_mode == VTYPE_TRANSFORM_PIPELINE_RAW_COORD) {
// 2D
return null;
}
if (context.viewport_height <= 0 && context.viewport_width >= 0) {
// Non-flipped 3D
return context.proj_uploaded_matrix;
}
float[] flippedMatrix = new float[16];
System.arraycopy(context.proj_uploaded_matrix, 0, flippedMatrix, 0, flippedMatrix.length);
if (context.viewport_height > 0) {
// Flip upside-down
flippedMatrix[1] = -flippedMatrix[1];
flippedMatrix[5] = -flippedMatrix[5];
flippedMatrix[9] = -flippedMatrix[9];
flippedMatrix[13] = -flippedMatrix[13];
}
if (context.viewport_width < 0) {
// Flip right-to-left
flippedMatrix[0] = -flippedMatrix[0];
flippedMatrix[4] = -flippedMatrix[4];
flippedMatrix[8] = -flippedMatrix[8];
flippedMatrix[12] = -flippedMatrix[12];
}
return flippedMatrix;
}
private void initRendering() {
/*
* Defer transformations until primitive rendering
*/
/*
* Set Scissor
*/
if (scissorChanged) {
setScissor();
scissorChanged = false;
}
/*
* Apply projection matrix
*/
if (projectionMatrixUpload.isChanged()) {
re.setProjectionMatrix(getProjectionMatrix());
projectionMatrixUpload.setChanged(false);
// The viewport has to be reloaded when the projection matrix has changed
viewportChanged = true;
}
/*
* Apply viewport
*/
boolean loadOrtho2D = false;
if (viewportChanged) {
if (context.transform_mode == VTYPE_TRANSFORM_PIPELINE_RAW_COORD) {
re.setViewport(0, 0, Screen.width, Screen.height);
// Load the ortho for 2D after the depth settings
loadOrtho2D = true;
} else {
int halfHeight = Math.abs(context.viewport_height);
int halfWidth = Math.abs(context.viewport_width);
int viewportX = context.viewport_cx - halfWidth - context.offset_x;
int viewportY = context.viewport_cy - halfHeight - context.offset_y;
int viewportWidth = 2 * halfWidth;
int viewportHeight = 2 * halfHeight;
// For OpenGL, translate the viewportY from the upper left corner
// to the lower left corner.
viewportY = Screen.height - viewportY - viewportHeight;
re.setViewport(viewportX, viewportY, viewportWidth, viewportHeight);
}
viewportChanged = false;
}
/*
* Apply depth handling
*/
if (depthChanged) {
if (context.transform_mode == VTYPE_TRANSFORM_PIPELINE_TRANS_COORD) {
re.setDepthFunc(context.depthFunc);
re.setDepthRange(context.zpos, context.zscale, context.nearZ, context.farZ);
} else {
re.setDepthFunc(context.depthFunc);
re.setDepthRange(32767.5f, 32767.5f, 0x0000, 0xFFFF);
}
depthChanged = false;
}
/*
* Load the 2D ortho (only after the depth settings
*/
if (loadOrtho2D) {
re.setProjectionMatrix(getOrthoMatrix(0, 480, 272, 0, 0, -0xFFFF));
}
/*
* 2D mode handling
*/
if (context.transform_mode == VTYPE_TRANSFORM_PIPELINE_RAW_COORD) {
// 2D mode shouldn't be affected by the lighting and fog
re.disableFlag(IRenderingEngine.GU_LIGHTING);
re.disableFlag(IRenderingEngine.GU_FOG);
// TODO I don't know why, but the GL_MODELVIEW matrix has to be reloaded
// each time in 2D mode... Otherwise textures are not displayed.
modelMatrixUpload.setChanged(true);
} else {
context.lightingFlag.update();
context.fogFlag.update();
}
/*
* Model-View matrix has to reloaded when
* - model matrix changed
* - view matrix changed
* - lighting has to be reloaded
*/
boolean loadLightingSettings = (viewMatrixUpload.isChanged() || lightingChanged) && context.lightingFlag.isEnabled() && context.transform_mode == VTYPE_TRANSFORM_PIPELINE_TRANS_COORD;
boolean modelViewMatrixChanged = modelMatrixUpload.isChanged() || viewMatrixUpload.isChanged() || loadLightingSettings;
/*
* Apply view matrix
*/
if (modelViewMatrixChanged) {
if (context.transform_mode == VTYPE_TRANSFORM_PIPELINE_TRANS_COORD) {
re.setViewMatrix(context.view_uploaded_matrix);
} else {
re.setViewMatrix(null);
}
viewMatrixUpload.setChanged(false);
}
/*
* Setup lights on when view transformation is set up.
* The light positions and directions are defined in the View-Projection world.
* The Model transformation does not apply for the lights.
*/
if (loadLightingSettings || (context.tex_map_mode == TMAP_TEXTURE_MAP_MODE_ENVIRONMENT_MAP && !context.vinfo.transform2D)) {
for (int i = 0; i < NUM_LIGHTS; i++) {
if (context.lightFlags[i].isEnabled() || (context.tex_map_mode == TMAP_TEXTURE_MAP_MODE_ENVIRONMENT_MAP && (context.tex_shade_u == i || context.tex_shade_v == i))) {
re.setLightPosition(i, context.light_pos[i]);
re.setLightDirection(i, context.light_dir[i]);
if (context.light_type[i] == LIGHT_SPOT) {
re.setLightSpotExponent(i, context.spotLightExponent[i]);
re.setLightSpotCutoff(i, context.spotLightCutoff[i]);
} else {
// uniform light distribution
re.setLightSpotExponent(i, 0);
re.setLightSpotCutoff(i, 180);
}
// Light kind:
// LIGHT_DIFFUSE_SPECULAR: use ambient, diffuse and specular colors
// all other light kinds: use ambient and diffuse colors (not specular)
if (context.light_kind[i] != LIGHT_AMBIENT_DIFFUSE) {
re.setLightSpecularColor(i, context.lightSpecularColor[i]);
} else {
re.setLightSpecularColor(i, blackColor);
}
}
}
lightingChanged = false;
}
if (modelViewMatrixChanged) {
// Apply model matrix
if (context.transform_mode == VTYPE_TRANSFORM_PIPELINE_TRANS_COORD) {
re.setModelMatrix(context.model_uploaded_matrix);
} else {
re.setModelMatrix(null);
}
modelMatrixUpload.setChanged(false);
re.endModelViewMatrixUpdate();
}
/*
* Apply texture transforms
*/
if (textureMatrixUpload.isChanged()) {
if (context.transform_mode != VTYPE_TRANSFORM_PIPELINE_TRANS_COORD) {
re.setTextureMapMode(TMAP_TEXTURE_MAP_MODE_TEXTURE_COORDIATES_UV, TMAP_TEXTURE_PROJECTION_MODE_TEXTURE_COORDINATES);
context.reTextureGenS.setEnabled(false);
context.reTextureGenT.setEnabled(false);
float[] textureMatrix = new float[]{
1.f / context.texture_width[0], 0, 0, 0,
0, 1.f / context.texture_height[0], 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
};
if (textureFlipped) {
textureMatrix[5] = -textureMatrix[5];
textureMatrix[13] = textureFlipTranslateY;
if (isLogDebugEnabled) {
log.debug("Flipped 2D");
}
}
re.setTextureMatrix(textureMatrix);
} else {
re.setTextureMapMode(context.tex_map_mode, context.tex_proj_map_mode);
switch (context.tex_map_mode) {
case TMAP_TEXTURE_MAP_MODE_TEXTURE_COORDIATES_UV: {
context.reTextureGenS.setEnabled(false);
context.reTextureGenT.setEnabled(false);
float[] textureMatrix = new float[]{
context.tex_scale_x, 0, 0, 0,
0, context.tex_scale_y, 0, 0,
0, 0, 1, 0,
context.tex_translate_x, context.tex_translate_y, 0, 1
};
if (textureFlipped) {
if (textureMatrix[5] < 0f) {
// If the texture was mapped upside-down, also invert the translation
textureMatrix[13] = 1f - textureMatrix[13];
}
textureMatrix[5] = -textureMatrix[5];
if (isLogDebugEnabled) {
log.debug("Flipped TMAP_TEXTURE_MAP_MODE_TEXTURE_COORDIATES_UV");
}
}
re.setTextureMatrix(textureMatrix);
break;
}
case TMAP_TEXTURE_MAP_MODE_TEXTURE_MATRIX: {
context.reTextureGenS.setEnabled(false);
context.reTextureGenT.setEnabled(false);
float[] textureMatrix = context.texture_uploaded_matrix;
if (textureFlipped) {
// Map the (U,V) from ([0..1],[0..1]) to ([0..1],[1..0])
float[] flippedTextureMatrix = new float[]{
textureMatrix[0], textureMatrix[1], textureMatrix[2], textureMatrix[3],
-textureMatrix[4], -textureMatrix[5], -textureMatrix[6], textureMatrix[7],
textureMatrix[8], textureMatrix[9], textureMatrix[10], textureMatrix[11],
textureMatrix[12] + textureMatrix[4], textureMatrix[13] + textureMatrix[5], textureMatrix[14] + textureMatrix[6], textureMatrix[15]
};
textureMatrix = flippedTextureMatrix;
if (isLogDebugEnabled) {
log.debug("Flipped TMAP_TEXTURE_MAP_MODE_TEXTURE_MATRIX");
}
}
re.setTextureMatrix(textureMatrix);
break;
}
case TMAP_TEXTURE_MAP_MODE_ENVIRONMENT_MAP: {
re.setTextureEnvironmentMapping(context.tex_shade_u, context.tex_shade_v);
context.reTextureGenS.setEnabled(true);
context.reTextureGenT.setEnabled(true);
for (int i = 0; i < 3; i++) {
context.tex_envmap_matrix[i + 0] = context.light_pos[context.tex_shade_u][i];
context.tex_envmap_matrix[i + 4] = context.light_pos[context.tex_shade_v][i];
}
float[] textureMatrix = context.tex_envmap_matrix;
if (textureFlipped) {
// Map the (U,V) from ([0..1],[0..1]) to ([0..1],[1..0])
float[] flippedTextureMatrix = new float[]{
textureMatrix[0], textureMatrix[1], textureMatrix[2], textureMatrix[3],
-textureMatrix[4], -textureMatrix[5], -textureMatrix[6], textureMatrix[7],
textureMatrix[8], textureMatrix[9], textureMatrix[10], textureMatrix[11],
textureMatrix[12] + textureMatrix[4], textureMatrix[13] + textureMatrix[5], textureMatrix[14] + textureMatrix[6], textureMatrix[15]
};
textureMatrix = flippedTextureMatrix;
if (isLogDebugEnabled) {
log.debug("Flipped TMAP_TEXTURE_MAP_MODE_ENVIRONMENT_MAP");
}
}
re.setTextureMatrix(textureMatrix);
break;
}
default:
log.warn(String.format("Unhandled texture matrix mode %d", context.tex_map_mode));
}
}
textureMatrixUpload.setChanged(false);
}
context.useVertexColor = false;
if (!context.lightingFlag.isEnabled() || context.transform_mode == VTYPE_TRANSFORM_PIPELINE_RAW_COORD) {
context.reColorMaterial.setEnabled(false);
if (context.vinfo.color != 0) {
context.useVertexColor = true;
} else {
re.setVertexColor(context.mat_ambient);
}
} else if (context.vinfo.color != 0 && context.mat_flags != 0) {
context.useVertexColor = true;
if (materialChanged) {
boolean ambient = (context.mat_flags & 1) != 0;
boolean diffuse = (context.mat_flags & 2) != 0;
boolean specular = (context.mat_flags & 4) != 0;
re.setColorMaterial(ambient, diffuse, specular);
context.reColorMaterial.setEnabled(true);
if (!ambient) {
re.setMaterialAmbientColor(context.mat_ambient);
}
if (!diffuse) {
re.setMaterialDiffuseColor(context.mat_diffuse);
}
if (!specular) {
re.setMaterialSpecularColor(context.mat_specular);
}
materialChanged = false;
}
re.setVertexColor(context.mat_ambient);
} else {
context.reColorMaterial.setEnabled(false);
if (materialChanged) {
re.setColorMaterial(false, false, false);
re.setMaterialAmbientColor(context.mat_ambient);
re.setMaterialDiffuseColor(context.mat_diffuse);
re.setMaterialSpecularColor(context.mat_specular);
materialChanged = false;
}
re.setVertexColor(context.mat_ambient);
}
if (context.textureFlag.isEnabled() && !context.clearMode && !isBoundingBox) {
re.setTextureWrapMode(context.tex_wrap_s, context.tex_wrap_t);
int validNumberMipmaps = getValidNumberMipmaps(true);
int mipmapBaseLevel = 0;
int mipmapMaxLevel = validNumberMipmaps;
if (context.tex_mipmap_mode == TBIAS_MODE_CONST) {
// TBIAS_MODE_CONST uses the tex_mipmap_bias_int level supplied by TBIAS.
mipmapBaseLevel = context.tex_mipmap_bias_int;
mipmapMaxLevel = context.tex_mipmap_bias_int;
if (isLogDebugEnabled) {
log.debug("TBIAS_MODE_CONST " + context.tex_mipmap_bias_int);
}
} else if (context.tex_mipmap_mode == TBIAS_MODE_AUTO) {
// TODO implement TBIAS_MODE_AUTO. The following is not correct
// TBIAS_MODE_AUTO performs a comparison between the texture's weight and height at level 0.
// int maxValue = Math.max(context.texture_width[0], context.texture_height[0]);
//
// if(maxValue <= 1) {
// mipmapBaseLevel = 0;
// } else {
// mipmapBaseLevel = (int) ((Math.log((Math.abs(maxValue) / Math.abs(context.zpos))) / Math.log(2)) + context.tex_mipmap_bias);
// }
// mipmapMaxLevel = mipmapBaseLevel;
// if (isLogDebugEnabled) {
// log.debug("TBIAS_MODE_AUTO " + context.tex_mipmap_bias + ", param=" + maxValue);
// }
} else if (context.tex_mipmap_mode == TBIAS_MODE_SLOPE) {
// TBIAS_MODE_SLOPE uses the tslope_level level supplied by TSLOPE.
mipmapBaseLevel = (int) ((Math.log(Math.abs(context.tslope_level) / Math.abs(context.zpos)) / Math.log(2)) + context.tex_mipmap_bias);
mipmapMaxLevel = mipmapBaseLevel;
if (isLogDebugEnabled) {
log.debug("TBIAS_MODE_SLOPE " + context.tex_mipmap_bias + ", slope=" + context.tslope_level);
}
}
// Clamp to [0..validNumberMipmaps]
mipmapBaseLevel = Math.max(0, Math.min(mipmapBaseLevel, validNumberMipmaps));
// Clamp to [mipmapBaseLevel..validNumberMipmaps]
mipmapMaxLevel = Math.max(mipmapBaseLevel, Math.min(mipmapMaxLevel, validNumberMipmaps));
if (isLogDebugEnabled) {
log.debug(String.format("Texture Mipmap base=%d, max=%d, validNumberMipmaps=%d", mipmapBaseLevel, mipmapMaxLevel, validNumberMipmaps));
}
re.setTextureMipmapMinLevel(mipmapBaseLevel);
re.setTextureMipmapMaxLevel(mipmapMaxLevel);
}
}
private void endRendering(int numberOfVertex) {
// VADDR/IADDR are updated after vertex rendering
// (IADDR when indexed and VADDR when not).
// Some games rely on this and don't reload VADDR/IADDR between 2 PRIM/BBOX calls.
if (context.vinfo.index == 0) {
context.vinfo.ptr_vertex = context.vinfo.getAddress(Memory.getInstance(), numberOfVertex);
} else {
context.vinfo.ptr_index += numberOfVertex * context.vinfo.index;
}
}
public static float[] getOrthoMatrix(float left, float right, float bottom, float top, float near, float far) {
float dx = right - left;
float dy = top - bottom;
float dz = far - near;
float[] orthoMatrix = {
2.f / dx, 0, 0, 0,
0, 2.f / dy, 0, 0,
0, 0, -2.f / dz, 0,
-(right + left) / dx, -(top + bottom) / dy, -(far + near) / dz, 1
};
return orthoMatrix;
}
float spline_n(int i, int j, float u, int[] knot) {
if (j == 0) {
if (knot[i] <= u && u < knot[i + 1]) {
return 1;
}
return 0;
}
float res = 0;
if (knot[i + j] - knot[i] != 0) {
res += (u - knot[i]) / (knot[i + j] - knot[i]) * spline_n(i, j - 1, u, knot);
}
if (knot[i + j + 1] - knot[i + 1] != 0) {
res += (knot[i + j + 1] - u) / (knot[i + j + 1] - knot[i + 1]) * spline_n(i + 1, j - 1, u, knot);
}
return res;
}
int[] spline_knot(int n, int type) {
int[] knot = new int[n + 5];
for (int i = 0; i < n - 1; i++) {
knot[i + 3] = i;
}
if ((type & 1) == 0) {
knot[0] = -3;
knot[1] = -2;
knot[2] = -1;
}
if ((type & 2) == 0) {
knot[n + 2] = n - 1;
knot[n + 3] = n;
knot[n + 4] = n + 1;
} else {
knot[n + 2] = n - 2;
knot[n + 3] = n - 2;
knot[n + 4] = n - 2;
}
return knot;
}
private void drawSpline(int ucount, int vcount, int utype, int vtype) {
if (ucount < 4 || vcount < 4) {
log.warn("Unsupported spline parameters uc=" + ucount + " vc=" + vcount);
return;
}
if (context.patch_div_s <= 0 || context.patch_div_t <= 0) {
log.warn("Unsupported spline patches patch_div_s=" + context.patch_div_s + " patch_div_t=" + context.patch_div_t);
return;
}
initRendering();
boolean useTexture = context.vinfo.texture != 0 || context.textureFlag.isEnabled();
boolean useNormal = context.lightingFlag.isEnabled();
VertexInfo cachedVertexInfo = null;
if (useVertexCache) {
int numberOfVertex = context.patch_div_t * (context.patch_div_s + 1) * 2;
vertexCacheLookupStatistics.start();
cachedVertexInfo = VertexCache.getInstance().getVertex(context.vinfo, numberOfVertex, context.bone_uploaded_matrix, 0);
vertexCacheLookupStatistics.end();
}
VertexState[][] patch = null;
if (cachedVertexInfo == null) {
// Generate control points.
VertexState[][] ctrlpoints = getControlPoints(ucount, vcount);
// GE capture.
if (State.captureGeNextFrame && !isVertexBufferEmbedded()) {
log.info("Capture drawSpline");
CaptureManager.captureRAM(context.vinfo.ptr_vertex, context.vinfo.vertexSize * ucount * vcount);
}
// Generate patch VertexState.
patch = new VertexState[context.patch_div_s + 1][context.patch_div_t + 1];
// Calculate knot arrays.
int n = ucount - 1;
int m = vcount - 1;
int[] knot_u = spline_knot(n, utype);
int[] knot_v = spline_knot(m, vtype);
// The spline grows to a limit defined by n - 2 for u and m - 2 for v.
// This limit is open, so we need to get a very close approximation of it.
float limit = 2.000001f;
// Process spline vertexes with Cox-deBoor's algorithm.
for (int j = 0; j <= context.patch_div_t; j++) {
float cv = (float) j * (float) (m - limit) / (float) context.patch_div_t;
for (int i = 0; i <= context.patch_div_s; i++) {
float cu = (float) i * (float) (n - limit) / (float) context.patch_div_s;
patch[i][j] = new VertexState();
VertexState p = patch[i][j];
for (int ii = 0; ii <= n; ii++) {
for (int jj = 0; jj <= m; jj++) {
float f = spline_n(ii, 3, cu, knot_u) * spline_n(jj, 3, cv, knot_v);
if (f != 0) {
pointMultAdd(p, ctrlpoints[ii][jj], f, context.useVertexColor, useTexture, useNormal);
}
}
}
if (useTexture && context.vinfo.texture == 0) {
p.t[0] = cu;
p.t[1] = cv;
}
}
}
}
drawCurvedSurface(patch, ucount, vcount, cachedVertexInfo, context.useVertexColor, useTexture, useNormal);
}
private void pointMultAdd(VertexState dest, VertexState src, float f, boolean useVertexColor, boolean useTexture, boolean useNormal) {
dest.p[0] += f * src.p[0];
dest.p[1] += f * src.p[1];
dest.p[2] += f * src.p[2];
if (useTexture) {
dest.t[0] += f * src.t[0];
dest.t[1] += f * src.t[1];
}
if (useVertexColor) {
dest.c[0] += f * src.c[0];
dest.c[1] += f * src.c[1];
dest.c[2] += f * src.c[2];
dest.c[3] += f * src.c[3];
}
if (useNormal) {
dest.n[0] += f * src.n[0];
dest.n[1] += f * src.n[1];
dest.n[2] += f * src.n[2];
}
}
private void drawBezier(int ucount, int vcount) {
if ((ucount - 1) % 3 != 0 || (vcount - 1) % 3 != 0) {
log.warn("Unsupported bezier parameters ucount=" + ucount + " vcount=" + vcount);
return;
}
if (context.patch_div_s <= 0 || context.patch_div_t <= 0) {
log.warn("Unsupported bezier patches patch_div_s=" + context.patch_div_s + " patch_div_t=" + context.patch_div_t);
return;
}
ucount = Math.max(ucount, 4);
vcount = Math.max(vcount, 4);
initRendering();
boolean useTexture = context.vinfo.texture != 0 || context.textureFlag.isEnabled();
boolean useNormal = context.lightingFlag.isEnabled();
VertexInfo cachedVertexInfo = null;
if (useVertexCache) {
int numberOfVertex = context.patch_div_t * (context.patch_div_s + 1) * 2;
vertexCacheLookupStatistics.start();
cachedVertexInfo = VertexCache.getInstance().getVertex(context.vinfo, numberOfVertex, context.bone_uploaded_matrix, 0);
vertexCacheLookupStatistics.end();
}
VertexState[][] patch = null;
if (cachedVertexInfo == null) {
VertexState[][] anchors = getControlPoints(ucount, vcount);
// Don't capture the ram if the vertex list is embedded in the display list. TODO handle stall_addr == 0 better
// TODO may need to move inside the loop if indices are used, or find the largest index so we can calculate the size of the vertex list
if (State.captureGeNextFrame && !isVertexBufferEmbedded()) {
log.info("Capture drawBezier");
CaptureManager.captureRAM(context.vinfo.ptr_vertex, context.vinfo.vertexSize * ucount * vcount);
}
// Generate patch VertexState.
patch = new VertexState[context.patch_div_s + 1][context.patch_div_t + 1];
// Number of patches in the U and V directions
int upcount = ucount / 3;
int vpcount = vcount / 3;
float[][] ucoeff = new float[context.patch_div_s + 1][];
for (int j = 0; j <= context.patch_div_t; j++) {
float vglobal = (float) j * vpcount / (float) context.patch_div_t;
int vpatch = (int) vglobal; // Patch number
float cv = vglobal - vpatch;
if (j == context.patch_div_t) {
vpatch--;
cv = 1.f;
}
float[] vcoeff = BernsteinCoeff(cv);
for (int i = 0; i <= context.patch_div_s; i++) {
float uglobal = (float) i * upcount / (float) context.patch_div_s;
int upatch = (int) uglobal;
float cu = uglobal - upatch;
if (i == context.patch_div_s) {
upatch--;
cu = 1.f;
}
ucoeff[i] = BernsteinCoeff(cu);
VertexState p = new VertexState();
patch[i][j] = p;
for (int ii = 0; ii < 4; ++ii) {
for (int jj = 0; jj < 4; ++jj) {
pointMultAdd(p,
anchors[3 * upatch + ii][3 * vpatch + jj],
ucoeff[i][ii] * vcoeff[jj],
context.useVertexColor, useTexture, useNormal);
}
}
if (useTexture && context.vinfo.texture == 0) {
p.t[0] = uglobal;
p.t[1] = vglobal;
}
}
}
}
drawCurvedSurface(patch, ucount, vcount, cachedVertexInfo, context.useVertexColor, useTexture, useNormal);
}
private void drawCurvedSurface(VertexState[][] patch, int ucount, int vcount, VertexInfo cachedVertexInfo, boolean useVertexColor, boolean useTexture, boolean useNormal) {
if (re.isVertexArrayAvailable()) {
re.bindVertexArray(0);
}
int type = patch_prim_types[context.patch_prim];
re.setVertexInfo(context.vinfo, false, useVertexColor, useTexture, type);
// Triangle strips can be combined across rows into one single drawArrays call.
// Two dummy vertices have to be added when switching from one row to the next one.
// These dummy vertices each render an empty triangle
// (e.g. a triangle where two corners are equal).
boolean combineRowPrimitives = (type == PRIM_TRIANGLE_STRIPS);
// Row combination currently disabled for testing on ATI/AMD hardware.
// Might be crashing the video driver.
combineRowPrimitives = false;
boolean needSetDataPointers = true;
int numberOfVertexPerRow = (context.patch_div_s + 1) * 2;
if (cachedVertexInfo == null) {
int ii = 0;
for (int j = 0; j < context.patch_div_t; j++) {
for (int i = 0; i <= context.patch_div_s; i++) {
VertexState vs1 = patch[i][j];
VertexState vs2 = patch[i][j + 1];
if (useTexture) {
floatBufferArray[ii++] = vs1.t[0];
floatBufferArray[ii++] = vs1.t[1];
}
if (useVertexColor) {
floatBufferArray[ii++] = vs1.c[0];
floatBufferArray[ii++] = vs1.c[1];
floatBufferArray[ii++] = vs1.c[2];
floatBufferArray[ii++] = vs1.c[3];
}
if (useNormal) {
floatBufferArray[ii++] = vs1.n[0];
floatBufferArray[ii++] = vs1.n[1];
floatBufferArray[ii++] = vs1.n[2];
}
floatBufferArray[ii++] = vs1.p[0];
floatBufferArray[ii++] = vs1.p[1];
floatBufferArray[ii++] = vs1.p[2];
if (combineRowPrimitives && i == 0 && j > 0) {
// First dummy vertex: add v1 again
if (useTexture) {
floatBufferArray[ii++] = vs1.t[0];
floatBufferArray[ii++] = vs1.t[1];
}
if (useVertexColor) {
floatBufferArray[ii++] = vs1.c[0];
floatBufferArray[ii++] = vs1.c[1];
floatBufferArray[ii++] = vs1.c[2];
floatBufferArray[ii++] = vs1.c[3];
}
if (useNormal) {
floatBufferArray[ii++] = vs1.n[0];
floatBufferArray[ii++] = vs1.n[1];
floatBufferArray[ii++] = vs1.n[2];
}
floatBufferArray[ii++] = vs1.p[0];
floatBufferArray[ii++] = vs1.p[1];
floatBufferArray[ii++] = vs1.p[2];
}
if (useTexture) {
floatBufferArray[ii++] = vs2.t[0];
floatBufferArray[ii++] = vs2.t[1];
}
if (useVertexColor) {
floatBufferArray[ii++] = vs2.c[0];
floatBufferArray[ii++] = vs2.c[1];
floatBufferArray[ii++] = vs2.c[2];
floatBufferArray[ii++] = vs2.c[3];
}
if (useNormal) {
floatBufferArray[ii++] = vs2.n[0];
floatBufferArray[ii++] = vs2.n[1];
floatBufferArray[ii++] = vs2.n[2];
}
floatBufferArray[ii++] = vs2.p[0];
floatBufferArray[ii++] = vs2.p[1];
floatBufferArray[ii++] = vs2.p[2];
if (combineRowPrimitives && i == context.patch_div_s && j < context.patch_div_t - 1) {
// Second dummy vertex: add v2 again
if (useTexture) {
floatBufferArray[ii++] = vs2.t[0];
floatBufferArray[ii++] = vs2.t[1];
}
if (useVertexColor) {
floatBufferArray[ii++] = vs2.c[0];
floatBufferArray[ii++] = vs2.c[1];
floatBufferArray[ii++] = vs2.c[2];
floatBufferArray[ii++] = vs2.c[3];
}
if (useNormal) {
floatBufferArray[ii++] = vs2.n[0];
floatBufferArray[ii++] = vs2.n[1];
floatBufferArray[ii++] = vs2.n[2];
}
floatBufferArray[ii++] = vs2.p[0];
floatBufferArray[ii++] = vs2.p[1];
floatBufferArray[ii++] = vs2.p[2];
}
}
}
int bufferSizeInFloats = ii;
if (useVertexCache) {
cachedVertexInfo = new VertexInfo();
int vtype = context.vinfo.vtype;
if (useTexture) {
// Float texture values
vtype = (vtype & ~(0x3 << 0)) | VTYPE_TEXTURE_FORMAT_32_BIT;
}
if (useVertexColor) {
// 8888 color values
vtype = (vtype & ~(0x7 << 2)) | VTYPE_COLOR_FORMAT_32BIT_ABGR_8888;
}
if (useNormal) {
// Float normal values
vtype = (vtype & ~(0x3 << 5)) | VTYPE_NORMAL_FORMAT_32_BIT;
}
cachedVertexInfo.processType(vtype);
int numberOfVertex = numberOfVertexPerRow * context.patch_div_t;
VertexCache.getInstance().addVertex(re, cachedVertexInfo, numberOfVertex, context.bone_uploaded_matrix, 0);
needSetDataPointers = cachedVertexInfo.loadVertex(re, floatBufferArray, bufferSizeInFloats);
} else {
ByteBuffer byteBuffer = bufferManager.getBuffer(bufferId);
byteBuffer.clear();
byteBuffer.asFloatBuffer().put(floatBufferArray, 0, bufferSizeInFloats);
bufferManager.setBufferSubData(IRenderingEngine.RE_ARRAY_BUFFER, bufferId, 0, bufferSizeInFloats * SIZEOF_FLOAT, byteBuffer, IRenderingEngine.RE_STREAM_DRAW);
}
} else {
needSetDataPointers = cachedVertexInfo.bindVertex(re);
}
if (needSetDataPointers) {
// TODO: Compute the normals
setDataPointers(3, useVertexColor, 4, useTexture, 2, useNormal, 0, cachedVertexInfo == null);
}
if (combineRowPrimitives) {
// Draw all the vertices in one call
int numberOfVertexToDraw = numberOfVertexPerRow * context.patch_div_t + 2 * (context.patch_div_t - 1);
drawArraysStatistics.start();
re.drawArrays(type, 0, numberOfVertexToDraw);
drawArraysStatistics.end();
} else {
// Draw the vertices one row at a time
drawArraysStatistics.start();
re.drawArrays(type, 0, numberOfVertexPerRow);
for (int j = 1, first = numberOfVertexPerRow; j < context.patch_div_t; j++, first += numberOfVertexPerRow) {
re.drawArraysBurstMode(type, first, numberOfVertexPerRow);
}
drawArraysStatistics.end();
}
if (State.captureGeNextFrame) {
display.captureGeImage();
textureChanged = true;
}
endRendering(ucount * vcount);
}
private VertexState[][] getControlPoints(int ucount, int vcount) {
VertexState[][] controlPoints = new VertexState[ucount][vcount];
boolean readTexture = context.textureFlag.isEnabled();
Memory mem = Memory.getInstance();
for (int cu = 0; cu < ucount; cu++) {
for (int cv = 0; cv < vcount; cv++) {
int addr = context.vinfo.getAddress(mem, cv * ucount + cu);
VertexState vs = context.vinfo.readVertex(mem, addr, readTexture, isDoubleTexture2DCoords());
if (context.vinfo.weight != 0 && context.vinfo.position != 0) {
doSkinning(context.bone_uploaded_matrix, context.vinfo, vs);
}
if (isLogDebugEnabled) {
log(String.format("control point #%d,%d p(%f,%f,%f) t(%f,%f), c(0x%08X)",
cu, cv,
vs.p[0], vs.p[1], vs.p[2],
vs.t[0], vs.t[1],
PixelColor.getColor(vs.c)));
}
controlPoints[cu][cv] = vs;
}
}
return controlPoints;
}
private float[] BernsteinCoeff(float u) {
float uPow2 = u * u;
float uPow3 = uPow2 * u;
float u1 = 1 - u;
float u1Pow2 = u1 * u1;
float u1Pow3 = u1Pow2 * u1;
return new float[]{u1Pow3, 3 * u * u1Pow2, 3 * uPow2 * u1, uPow3};
}
private Buffer getTextureBuffer(int texaddr, int bytesPerPixel, int level, int textureBufferWidthInPixels) {
Buffer final_buffer = null;
if (!context.texture_swizzle) {
// texture_width might be larger than texture_buffer_width
int bufferlen = Math.max(textureBufferWidthInPixels, context.texture_width[level]) * context.texture_height[level] * bytesPerPixel;
final_buffer = Memory.getInstance().getBuffer(texaddr, bufferlen);
if (State.captureGeNextFrame) {
log.info("Capture getTextureBuffer unswizzled");
CaptureManager.captureRAM(texaddr, bufferlen);
}
} else {
final_buffer = unswizzleTextureFromMemory(texaddr, bytesPerPixel, level, textureBufferWidthInPixels);
}
return final_buffer;
}
public static String getPsmName(final int psm) {
return (psm >= 0 && psm < psm_names.length)
? psm_names[psm % psm_names.length]
: "PSM_UNKNOWN" + psm;
}
public static String getLOpName(final int ops) {
return (ops >= 0 && ops < logical_ops_names.length)
? logical_ops_names[ops % logical_ops_names.length]
: "UNKNOWN_LOP" + ops;
}
private int getCompressedTextureSize(int level, int compressionRatio) {
// We load the texture with the size (texture_width, texture_height) using OpenGL.
// Do not use the texture_buffer_width here.
return getCompressedTextureSize(context.texture_width[level], context.texture_height[level], compressionRatio);
}
public static int getCompressedTextureSize(int width, int height, int compressionRatio) {
return round4(width) * round4(height) * 4 / compressionRatio;
}
private void updateGeBuf() {
if (geBufChanged) {
display.hleDisplaySetGeBuf(context.fbp, context.fbw, context.psm, somethingDisplayed, forceLoadGEToScreen);
if (useTextureCache) {
TextureCache.getInstance().deleteVramTextures(re, context.fbp, context.fbw * context.scissor_y2 * IRenderingEngine.sizeOfTextureType[context.psm]);
}
forceLoadGEToScreen = false;
geBufChanged = false;
textureChanged = true;
maxSpriteHeight = 0;
maxSpriteWidth = 0;
projectionMatrixUpload.setChanged(true);
modelMatrixUpload.setChanged(true);
viewMatrixUpload.setChanged(true);
textureMatrixUpload.setChanged(true);
viewportChanged = true;
depthChanged = true;
materialChanged = true;
// wait for rendering completion when switching the GE buffer (in case it is reused as a texture)
re.waitForRenderingCompletion();
}
}
// For capture/replay
public int getFBP() {
return context.fbp;
}
public int getFBW() {
return context.fbw;
}
public int getZBP() {
return context.zbp;
}
public int getZBW() {
return context.zbw;
}
public int getPSM() {
return context.psm;
}
private boolean isVertexBufferEmbedded() {
// stall_addr may be 0
return (context.vinfo.ptr_vertex >= currentList.list_addr && context.vinfo.ptr_vertex < currentList.getStallAddr());
}
public int getClutNumEntries() {
// E.g. mask==0xFF requires 256 entries
// also mask==0xF0 requires 256 entries
return Integer.highestOneBit(context.tex_clut_mask | (context.tex_clut_start << 4)) << 1;
}
private void hlePerformAction(IAction action, Semaphore sync) {
hleAction = action;
while (true) {
try {
sync.acquire();
break;
} catch (InterruptedException e) {
// Retry again..
}
}
}
public void hleSaveContext(int addr) {
// If we are rendering, we have to wait for a consistent state
// before saving the context: let the display thread perform
// the save when appropriate.
if (hasDrawLists() || currentList != null) {
Semaphore sync = new Semaphore(0);
hlePerformAction(new SaveContextAction(addr, sync), sync);
} else {
saveContext(addr);
}
}
public void hleRestoreContext(int addr) {
// If we are rendering, we have to wait for a consistent state
// before restoring the context: let the display thread perform
// the restore when appropriate.
if (hasDrawLists() || currentList != null) {
Semaphore sync = new Semaphore(0);
hlePerformAction(new RestoreContextAction(addr, sync), sync);
} else {
restoreContext(addr);
}
}
private void saveContext(int addr) {
context.write(Memory.getInstance(), addr);
}
private void restoreContext(int addr) {
context.read(Memory.getInstance(), addr);
context.setDirty();
projectionMatrixUpload.setChanged(true);
modelMatrixUpload.setChanged(true);
viewMatrixUpload.setChanged(true);
textureMatrixUpload.setChanged(true);
lightingChanged = true;
textureChanged = true;
geBufChanged = true;
viewportChanged = true;
depthChanged = true;
materialChanged = true;
}
public boolean isUsingTRXKICK() {
return usingTRXKICK;
}
public int getMaxSpriteHeight() {
return maxSpriteHeight;
}
public int getMaxSpriteWidth() {
return maxSpriteWidth;
}
private void setUseVertexCache(boolean useVertexCache) {
// VertexCache is relying on VBO
if (bufferManager != null && !bufferManager.useVBO()) {
useVertexCache = false;
}
this.useVertexCache = useVertexCache;
if (useVertexCache) {
if (useAsyncVertexCache) {
AsyncVertexCache.getInstance();
if (useOptimisticVertexCache) {
log.info("Using Optimistic Async Vertex Cache");
} else {
log.info("Using Async Vertex Cache");
}
} else {
VertexCache.getInstance();
if (useOptimisticVertexCache) {
log.info("Using Optimistic Vertex Cache");
} else {
log.info("Using Vertex Cache");
}
}
}
}
public boolean useAsyncVertexCache() {
return useVertexCache && useAsyncVertexCache;
}
public boolean disableOptimizedVertexInfoReading() {
return disableOptimizedVertexInfoReading;
}
private void setDisableOptimizedVertexInfoReading(boolean disableOptimizedVertexInfoReading) {
this.disableOptimizedVertexInfoReading = disableOptimizedVertexInfoReading;
if (disableOptimizedVertexInfoReading) {
log.info("Using non-optimized VertexInfo reading");
}
}
public int getBase() {
return context.base;
}
public void setBase(int base) {
context.base = base;
}
public int getBaseOffset() {
return context.baseOffset;
}
public void setBaseOffset(int baseOffset) {
context.baseOffset = baseOffset;
}
private void addToVideoTextures(int startAddress, int endAddress) {
// Synchronize the access to videoTextures as it can be accessed
// from parallel threads (async display thread and PSP thread)
synchronized (videoTextures) {
for (AddressRange addressRange : videoTextures) {
if (addressRange.equals(startAddress, endAddress)) {
addressRange.hit();
return;
}
}
AddressRange addressRange = new AddressRange(startAddress, endAddress);
videoTextures.add(addressRange);
}
}
public void addVideoTexture(int destinationAddress, int sourceAddress, int length) {
if (ExternalGE.isActive()) {
ExternalGE.addVideoTexture(destinationAddress, sourceAddress, length);
}
addToVideoTextures(destinationAddress, destinationAddress + length);
}
public void addVideoTexture(int startAddress, int endAddress) {
if (ExternalGE.isActive()) {
ExternalGE.addVideoTexture(startAddress, startAddress, endAddress - startAddress);
}
addToVideoTextures(startAddress, endAddress);
if (display.isFbAddress(startAddress)) {
display.setGeDirty(true);
}
}
public void resetVideoTextures() {
// Synchronize the access to videoTextures as it can be accessed
// from a parallel threads (async display and PSP thread)
synchronized (videoTextures) {
videoTextures.clear();
}
}
public boolean isUseTextureAnisotropicFilter() {
return useTextureAnisotropicFilter;
}
public void setUseTextureAnisotropicFilter(boolean useTextureAnisotropicFilter) {
this.useTextureAnisotropicFilter = useTextureAnisotropicFilter;
}
public boolean isUsexBRZFilter() {
return usexBRZFilter;
}
public void setUsexBRZFilter(boolean usexBRZFilter) {
this.usexBRZFilter = usexBRZFilter;
}
public void setSkipThisFrame(boolean skipThisFrame) {
this.skipThisFrame = skipThisFrame;
}
public boolean isSkipThisFrame() {
return skipThisFrame;
}
public void addCachedInstructions(int address, int[] instructions) {
cachedInstructions.put(address, instructions);
}
public void clearCachedInstructions() {
// TODO When should be cached instructions be cleared?
cachedInstructions.clear();
}
public void clearTextureCache() {
// Clear the cache before starting the rendering
wantClearTextureCache = true;
}
public void clearVertexCache() {
// Clear the cache before starting the rendering
wantClearVertexCache = true;
}
private void initTextureBuffers() {
int maxTextureSize = 1 << maxTextureSizeLog2;
// We might need more space for a texture of size 512x512 and bufferWidth=1024
int tmpBufferSize = Math.max(maxTextureSize, 1024) * maxTextureSize;
if (tmp_texture_buffer32 == null || tmp_texture_buffer32.length != tmpBufferSize) {
// Tell the garbage collector that the old arrays are no longer in use.
tmp_texture_buffer32 = null;
tmp_texture_buffer16 = null;
// Create the new arrays
tmp_texture_buffer32 = new int[tmpBufferSize];
tmp_texture_buffer16 = new short[tmpBufferSize];
}
}
public void setMaxTextureSize(int maxTextureSize) {
if ((1 << maxTextureSizeLog2) != maxTextureSize) {
maxTextureSizeLog2 = 31 - Integer.numberOfLeadingZeros(maxTextureSize);
log.info(String.format("Using maxTextureSize=%d(log2=%d)", maxTextureSize, maxTextureSizeLog2));
}
initTextureBuffers();
}
public void setDoubleTexture2DCoords(boolean doubleTexture2DCoords) {
if (this.doubleTexture2DCoords != doubleTexture2DCoords) {
this.doubleTexture2DCoords = doubleTexture2DCoords;
log.info(String.format("Using DoubleTexture2DCoords %b", doubleTexture2DCoords));
}
}
public boolean isDoubleTexture2DCoords() {
return doubleTexture2DCoords;
}
private class SaveContextAction implements IAction {
private int addr;
private Semaphore sync;
public SaveContextAction(int addr, Semaphore sync) {
this.addr = addr;
this.sync = sync;
}
@Override
public void execute() {
saveContext(addr);
sync.release();
}
}
private class RestoreContextAction implements IAction {
private int addr;
private Semaphore sync;
public RestoreContextAction(int addr, Semaphore sync) {
this.addr = addr;
this.sync = sync;
}
@Override
public void execute() {
restoreContext(addr);
context.update();
sync.release();
}
}
private static class AddressRange {
private int start;
private int end;
private long timestamp;
private static final long expirationTime = 1000; // 1 second
public AddressRange(int start, int end) {
this.start = start & Memory.addressMask;
this.end = end & Memory.addressMask;
hit();
}
public boolean contains(int address) {
address &= Memory.addressMask;
return address >= start && address < end;
}
public boolean equals(int start, int end) {
start &= Memory.addressMask;
end &= Memory.addressMask;
return start == this.start && end == this.end;
}
private static long getCurrentTimestamp() {
return Emulator.getClock().currentTimeMillis();
}
public void hit() {
timestamp = getCurrentTimestamp();
}
public boolean isObsolete() {
long now = getCurrentTimestamp();
return now - timestamp >= expirationTime;
}
}
protected static class VertexIndexInfo {
private int minIndex;
private int maxIndex;
private boolean sequence;
public VertexIndexInfo(int minIndex, int maxIndex, boolean sequence) {
this.minIndex = minIndex;
this.maxIndex = maxIndex;
this.sequence = sequence;
}
public int getMinIndex() {
return minIndex;
}
public int getMaxIndex() {
return maxIndex;
}
public int getNumberOfVertex() {
return maxIndex - minIndex + 1;
}
public boolean isSequence() {
return sequence;
}
}
}