/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.graphics.RE.software;
import static jpcsp.graphics.GeCommands.CMODE_FORMAT_16BIT_ABGR4444;
import static jpcsp.graphics.GeCommands.CMODE_FORMAT_16BIT_ABGR5551;
import static jpcsp.graphics.GeCommands.CMODE_FORMAT_16BIT_BGR5650;
import static jpcsp.graphics.GeCommands.CMODE_FORMAT_32BIT_ABGR8888;
import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444;
import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR5551;
import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650;
import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_INDEXED;
import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888;
import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_32BIT_INDEXED;
import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_8BIT_INDEXED;
import static jpcsp.memory.ImageReader.color4444to8888;
import static jpcsp.memory.ImageReader.color5551to8888;
import static jpcsp.memory.ImageReader.color565to8888;
import static jpcsp.util.Utilities.getPower2;
import jpcsp.Memory;
import jpcsp.Allegrex.compiler.RuntimeContext;
import jpcsp.HLE.Modules;
import jpcsp.graphics.GeContext;
import jpcsp.graphics.VideoEngine;
import jpcsp.graphics.RE.IRenderingEngine;
import jpcsp.memory.IMemoryReader;
import jpcsp.util.Utilities;
/**
* @author gid15
*
* A cached texture for the software Rendering Engine (RESoftware).
* The whole texture is read at initialization.
*/
public abstract class CachedTexture implements IRandomTextureAccess {
protected int width;
protected int height;
protected final int pixelFormat;
protected final int widthPower2;
protected final int heightPower2;
protected final int offset;
protected int[] buffer;
protected boolean useTextureClut;
protected int[] clut;
protected boolean isVRAMTexture;
public static CachedTexture getCachedTexture(int width, int height, int pixelFormat, IMemoryReader imageReader) {
CachedTexture cachedTexture = getCachedTexture(width, height, pixelFormat, 0);
cachedTexture.setBuffer(imageReader);
return cachedTexture;
}
public static CachedTexture getCachedTexture(int width, int height, int pixelFormat, int[] buffer, int bufferOffset, int bufferLength) {
int offset = 0;
// When the texture is directly available from the memory,
// we can reuse the memory array and do not need to copy the whole texture.
if (buffer == RuntimeContext.getMemoryInt()) {
if (pixelFormat == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888 || IRenderingEngine.isTextureTypeIndexed[pixelFormat]) {
// Do not reuse the memory buffer when the texture is inside the current GE,
// copy the texture (this is better matching the PSP texture cache behavior).
int textureAddress = bufferOffset << 2;
if (!Modules.sceDisplayModule.isGeAddress(textureAddress)) {
offset = bufferOffset;
}
}
}
CachedTexture cachedTexture = getCachedTexture(width, height, pixelFormat, offset);
cachedTexture.setBuffer(buffer, bufferOffset, bufferLength);
if (buffer == RuntimeContext.getMemoryInt()) {
int textureAddress = bufferOffset << 2;
if (Memory.isVRAM(textureAddress)) {
cachedTexture.setVRAMTexture(true);
}
}
return cachedTexture;
}
public static CachedTexture getCachedTexture(int width, int height, int pixelFormat, short[] buffer, int bufferOffset, int bufferLength) {
CachedTexture cachedTexture = getCachedTexture(width, height, pixelFormat, 0);
cachedTexture.setBuffer(buffer, bufferOffset, bufferLength);
return cachedTexture;
}
private static CachedTexture getCachedTexture(int width, int height, int pixelFormat, int offset) {
CachedTexture cachedTexture;
if (IRenderingEngine.isTextureTypeIndexed[pixelFormat]) {
switch (pixelFormat) {
case TPSM_PIXEL_STORAGE_MODE_8BIT_INDEXED:
cachedTexture = new CachedTextureIndexed8Bit(width, height, pixelFormat, offset);
break;
case TPSM_PIXEL_STORAGE_MODE_16BIT_INDEXED:
cachedTexture = new CachedTextureIndexed16Bit(width, height, pixelFormat, offset);
break;
case TPSM_PIXEL_STORAGE_MODE_32BIT_INDEXED:
cachedTexture = new CachedTextureIndexed32Bit(width, height, pixelFormat, offset);
break;
default:
VideoEngine.log.error(String.format("CachedTexture: unsupported indexed texture format %d", pixelFormat));
return null;
}
cachedTexture.setClut();
} else if (width == Utilities.makePow2(width)) {
if (offset == 0) {
cachedTexture = new CachedTexturePow2(width, height, pixelFormat);
} else {
cachedTexture = new CachedTextureOffsetPow2(width, height, pixelFormat, offset);
}
} else {
if (offset == 0) {
cachedTexture = new CachedTextureNonPow2(width, height, pixelFormat);
} else {
cachedTexture = new CachedTextureOffsetNonPow2(width, height, pixelFormat, offset);
}
}
return cachedTexture;
}
protected CachedTexture(int width, int height, int pixelFormat, int offset) {
this.width = width;
this.height = height;
this.pixelFormat = pixelFormat;
this.offset = offset;
widthPower2 = getPower2(width);
heightPower2 = getPower2(height);
}
protected void setBuffer(IMemoryReader imageReader) {
buffer = new int[width * height];
for (int i = 0; i < buffer.length; i++) {
buffer[i] = imageReader.readNext();
}
}
protected void setBuffer(int[] buffer, int bufferOffset, int bufferLength) {
switch (pixelFormat) {
case TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650:
this.buffer = new int[bufferLength * 2];
for (int i = 0, j = 0; i < bufferLength; i++) {
int color = buffer[bufferOffset + i];
this.buffer[j++] = color565to8888(color & 0xFFFF);
this.buffer[j++] = color565to8888(color >>> 16);
}
break;
case TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR5551:
this.buffer = new int[bufferLength * 2];
for (int i = 0, j = 0; i < bufferLength; i++) {
int color = buffer[bufferOffset + i];
this.buffer[j++] = color5551to8888(color & 0xFFFF);
this.buffer[j++] = color5551to8888(color >>> 16);
}
break;
case TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444:
this.buffer = new int[bufferLength * 2];
for (int i = 0, j = 0; i < bufferLength; i++) {
int color = buffer[bufferOffset + i];
this.buffer[j++] = color4444to8888(color & 0xFFFF);
this.buffer[j++] = color4444to8888(color >>> 16);
}
break;
case TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888:
case TPSM_PIXEL_STORAGE_MODE_8BIT_INDEXED:
case TPSM_PIXEL_STORAGE_MODE_16BIT_INDEXED:
case TPSM_PIXEL_STORAGE_MODE_32BIT_INDEXED:
// Is the texture directly available from the memory array?
if (buffer == RuntimeContext.getMemoryInt() && offset == bufferOffset) {
// We do not need to copy the whole texture, we can reuse the memory array
this.buffer = buffer;
} else {
this.buffer = new int[bufferLength];
System.arraycopy(buffer, bufferOffset, this.buffer, 0, bufferLength);
}
break;
default:
VideoEngine.log.error(String.format("CachedTexture setBuffer int unsupported pixel format %d", pixelFormat));
break;
}
}
protected void setClut() {
}
protected void setBuffer(short[] buffer, int bufferOffset, int bufferLength) {
switch (pixelFormat) {
case TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650:
this.buffer = new int[bufferLength];
for (int i = 0; i < bufferLength; i++) {
this.buffer[i] = color565to8888(buffer[bufferOffset + i] & 0xFFFF);
}
break;
case TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR5551:
this.buffer = new int[bufferLength];
for (int i = 0; i < bufferLength; i++) {
this.buffer[i] = color5551to8888(buffer[bufferOffset + i] & 0xFFFF);
}
break;
case TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444:
this.buffer = new int[bufferLength];
for (int i = 0; i < bufferLength; i++) {
this.buffer[i] = color4444to8888(buffer[bufferOffset + i] & 0xFFFF);
}
break;
case TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888:
this.buffer = new int[bufferLength / 2];
for (int i = 0, j = bufferOffset; i < this.buffer.length; i++) {
this.buffer[i] = (buffer[j++] & 0xFFFF) | (buffer[j++] << 16);
}
default:
VideoEngine.log.error(String.format("CachedTexture setBuffer short unsupported pixel format %d", pixelFormat));
break;
}
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
public int getPixelFormat() {
return pixelFormat;
}
public boolean isVRAMTexture() {
return isVRAMTexture;
}
public void setVRAMTexture(boolean isVRAMTexture) {
this.isVRAMTexture = isVRAMTexture;
}
/**
* @author gid15
*
* A specialized class when the width is a power of 2 (faster).
*/
protected static class CachedTexturePow2 extends CachedTexture {
public CachedTexturePow2(int widthPow2, int heightPow2, int width, int height, int pixelFormat) {
super(widthPow2, heightPow2, pixelFormat, 0);
this.width = width;
this.height = height;
}
public CachedTexturePow2(int width, int height, int pixelFormat) {
super(width, height, pixelFormat, 0);
}
@Override
public int readPixel(int u, int v) {
return buffer[(v << widthPower2) + u];
}
}
/**
* @author gid15
*
* A specialized class when the width is a power of 2 (faster)
* and using an array offset.
*/
private static class CachedTextureOffsetPow2 extends CachedTexture {
public CachedTextureOffsetPow2(int width, int height, int pixelFormat, int offset) {
super(width, height, pixelFormat, offset);
}
@Override
public int readPixel(int u, int v) {
return buffer[(v << widthPower2) + u + offset];
}
}
/**
* @author gid15
*
* A specialized class when the width is not a power of 2.
*/
private static class CachedTextureNonPow2 extends CachedTexture {
public CachedTextureNonPow2(int width, int height, int pixelFormat) {
super(width, height, pixelFormat, 0);
}
@Override
public int readPixel(int u, int v) {
return buffer[v * width + u];
}
}
/**
* @author gid15
*
* A specialized class when the width is not a power of 2
* and using an array offset.
*/
private static class CachedTextureOffsetNonPow2 extends CachedTexture {
public CachedTextureOffsetNonPow2(int width, int height, int pixelFormat, int offset) {
super(width, height, pixelFormat, offset);
}
@Override
public int readPixel(int u, int v) {
return buffer[v * width + u + offset];
}
}
private static abstract class CachedTextureIndexed extends CachedTexture {
private int[] clut;
private int shift;
private int mask;
private int start;
protected CachedTextureIndexed(int width, int height, int pixelFormat, int offset) {
super(width, height, pixelFormat, offset);
}
protected int getClut(int index) {
return clut[((index >> shift) & mask) | (start << 4)];
}
public void setClut(int[] clut, int shift, int mask, int start) {
this.clut = clut;
this.shift = shift;
this.mask = mask;
this.start = start;
}
@Override
public void setClut() {
VideoEngine videoEngine = VideoEngine.getInstance();
GeContext context = videoEngine.getContext();
int clutNumEntries = videoEngine.getClutNumEntries();
int[] clut = null;
short[] shortClut;
switch (context.tex_clut_mode) {
case CMODE_FORMAT_16BIT_BGR5650:
shortClut = videoEngine.readClut16(0);
clut = new int[clutNumEntries];
for (int i = 0; i < clut.length; i++) {
clut[i] = color565to8888(shortClut[i]);
}
break;
case CMODE_FORMAT_16BIT_ABGR5551:
shortClut = videoEngine.readClut16(0);
clut = new int[clutNumEntries];
for (int i = 0; i < clut.length; i++) {
clut[i] = color5551to8888(shortClut[i]);
}
break;
case CMODE_FORMAT_16BIT_ABGR4444:
shortClut = videoEngine.readClut16(0);
clut = new int[clutNumEntries];
for (int i = 0; i < clut.length; i++) {
clut[i] = color4444to8888(shortClut[i]);
}
break;
case CMODE_FORMAT_32BIT_ABGR8888:
int[] intClut = videoEngine.readClut32(0);
clut = new int[clutNumEntries];
System.arraycopy(intClut, 0, clut, 0, clut.length);
break;
}
if (clut != null) {
setClut(clut, context.tex_clut_shift, context.tex_clut_mask, context.tex_clut_start);
}
}
}
private static class CachedTextureIndexed8Bit extends CachedTextureIndexed {
private static final int[] shift8Bit = new int[] { 0, 8, 16, 24 };
protected CachedTextureIndexed8Bit(int width, int height, int pixelFormat, int offset) {
super(width, height, pixelFormat, offset);
}
@Override
public int readPixel(int u, int v) {
int pixelIndex = v * width + u;
int index = buffer[(pixelIndex >> 2) + offset] >> shift8Bit[pixelIndex & 3];
return getClut(index & 0xFF);
}
}
private static class CachedTextureIndexed16Bit extends CachedTextureIndexed {
private static final int[] shift16Bit = new int[] { 0, 16 };
protected CachedTextureIndexed16Bit(int width, int height, int pixelFormat, int offset) {
super(width, height, pixelFormat, offset);
}
@Override
public int readPixel(int u, int v) {
int pixelIndex = v * width + u;
int index = buffer[(pixelIndex >> 1) + offset] >> shift16Bit[pixelIndex & 1];
return getClut(index & 0xFFFF);
}
}
private static class CachedTextureIndexed32Bit extends CachedTextureIndexed {
protected CachedTextureIndexed32Bit(int width, int height, int pixelFormat, int offset) {
super(width, height, pixelFormat, offset);
}
@Override
public int readPixel(int u, int v) {
int pixelIndex = v * width + u;
int index = buffer[pixelIndex + offset];
return getClut(index);
}
}
@Override
public String toString() {
return String.format("CachedTexture[(%d x %d), %s]", getWidth(), getHeight(), VideoEngine.getPsmName(getPixelFormat()));
}
}