/*
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.textures;
import jpcsp.graphics.GeCommands;
import jpcsp.graphics.VideoEngine;
import jpcsp.graphics.RE.IRenderingEngine;
import jpcsp.util.Hash;
public class Texture {
private int addr;
private int lineWidth;
private int width;
private int height;
private int pixelStorage;
private int clutAddr;
private int clutMode;
private int clutStart;
private int clutShift;
private int clutMask;
private int clutNumBlocks;
private int hashCode;
private int mipmapLevels;
private boolean mipmapShareClut;
private int textureId = -1; // id created by genTexture
private boolean loaded = false; // is the texture already loaded?
private TextureCache textureCache;
private final static int defaultHashStride = 64 + 8;
private final static int smallHashStride = 12;
private short[] cachedValues16;
private int[] cachedValues32;
private int bufferLengthInBytes;
private int lineWidthInBytes;
private int hashStrideInBytes;
public Texture(TextureCache textureCache, int addr, int lineWidth, int width, int height, int pixelStorage, int clutAddr, int clutMode, int clutStart, int clutShift, int clutMask, int clutNumBlocks, int mipmapLevels, boolean mipmapShareClut, short[] values16, int[] values32) {
this.textureCache = textureCache;
this.addr = addr;
this.lineWidth = lineWidth;
this.width = width;
this.height = height;
this.pixelStorage = pixelStorage;
this.clutAddr = clutAddr;
this.clutMode = clutMode;
this.clutStart = clutStart;
this.clutShift = clutShift;
this.clutMask = clutMask;
this.clutNumBlocks = clutNumBlocks;
this.mipmapLevels = mipmapLevels;
this.mipmapShareClut = mipmapShareClut;
bufferLengthInBytes = lineWidth * height;
lineWidthInBytes = lineWidth;
hashStrideInBytes = defaultHashStride;
int bytesPerPixel = IRenderingEngine.sizeOfTextureType[pixelStorage];
if (bytesPerPixel <= 0) {
// Special texture types
switch (pixelStorage) {
case GeCommands.TPSM_PIXEL_STORAGE_MODE_DXT1:
bufferLengthInBytes = VideoEngine.getCompressedTextureSize(lineWidth, height, 8);
break;
case GeCommands.TPSM_PIXEL_STORAGE_MODE_DXT3:
case GeCommands.TPSM_PIXEL_STORAGE_MODE_DXT5:
bufferLengthInBytes = VideoEngine.getCompressedTextureSize(lineWidth, height, 4);
break;
case GeCommands.TPSM_PIXEL_STORAGE_MODE_4BIT_INDEXED:
bufferLengthInBytes >>= 1;
lineWidthInBytes >>= 1;
// Take a smaller hash stride for 4-bit indexed textures to better detect small texture changes
// (e.g. for textures representing text)
hashStrideInBytes = smallHashStride;
break;
}
} else {
bufferLengthInBytes *= bytesPerPixel;
lineWidthInBytes *= bytesPerPixel;
}
if (values16 != null) {
cachedValues16 = new short[lineWidth];
System.arraycopy(values16, 0, cachedValues16, 0, lineWidth);
} else if (values32 != null) {
cachedValues32 = new int[lineWidth];
System.arraycopy(values32, 0, cachedValues32, 0, lineWidth);
} else {
if (lineWidthInBytes < hashStrideInBytes) {
if (lineWidthInBytes <= 32) {
// No stride at all for narrow textures
hashStrideInBytes = 0;
} else {
hashStrideInBytes = lineWidthInBytes - 4;
}
}
hashCode = hashCode(addr, bufferLengthInBytes, lineWidthInBytes, hashStrideInBytes, clutAddr, clutNumBlocks, mipmapLevels);
}
}
/**
* Compute the Texture hashCode value,
* based on the pixel buffer and the clut table.
*
* @param addr pixel buffer
* @param bufferLengthInBytes texture buffer length in bytes
* @param lineWidthInBytes texture buffer line width in bytes
* @param clutAddr clut table address
* @param clutNumBlocks clut number of blocks
* @param mipmapLevels number of mipmaps
* @return hashcode value
*/
private static int hashCode(int addr, int bufferLengthInBytes, int lineWidthInBytes, int strideInBytes, int clutAddr, int clutNumBlocks, int mipmapLevels) {
int hashCode = mipmapLevels;
if (addr != 0) {
if (VideoEngine.log.isDebugEnabled()) {
VideoEngine.log.debug("Texture.hashCode: " + bufferLengthInBytes + " bytes");
}
hashCode = Hash.getHashCode(hashCode, addr, bufferLengthInBytes, strideInBytes);
}
if (clutAddr != 0) {
hashCode = Hash.getHashCode(hashCode, clutAddr, clutNumBlocks * 32);
}
return hashCode;
}
@Override
public int hashCode() {
return hashCode;
}
public boolean equals(int addr, int lineWidth, int width, int height, int pixelStorage, int clutAddr, int clutMode, int clutStart, int clutShift, int clutMask, int clutNumBlocks, int mipmapLevels, boolean mipmapShareClut, short[] values16, int[] values32) {
if (this.addr != addr ||
this.lineWidth != lineWidth ||
this.width != width ||
this.height != height ||
this.pixelStorage != pixelStorage ||
this.clutAddr != clutAddr ||
this.clutMode != clutMode ||
this.clutStart != clutStart ||
this.clutShift != clutShift ||
this.clutMask != clutMask ||
this.clutNumBlocks != clutNumBlocks ||
this.mipmapLevels != mipmapLevels ||
this.mipmapShareClut != mipmapShareClut)
{
return false;
}
// Do not compute the hashCode of the new texture if it has already
// been checked during this display cycle
if (!textureCache.textureAlreadyHashed(addr, clutAddr, clutStart, clutMode)) {
if (values16 != null) {
return equals(values16);
}
if (values32 != null) {
return equals(values32);
}
int hashCode = hashCode(addr, bufferLengthInBytes, lineWidthInBytes, hashStrideInBytes, clutAddr, clutNumBlocks, mipmapLevels);
if (hashCode != hashCode()) {
return false;
}
textureCache.setTextureAlreadyHashed(addr, clutAddr, clutStart, clutMode);
}
return true;
}
private boolean equals(short[] values16) {
if (cachedValues16 == null) {
return false;
}
for (int i = 0; i < lineWidth; i++) {
if (values16[i] != cachedValues16[i]) {
return false;
}
}
return true;
}
private boolean equals(int[] values32) {
if (cachedValues32 == null) {
return false;
}
for (int i = 0; i < lineWidth; i++) {
if (values32[i] != cachedValues32[i]) {
return false;
}
}
return true;
}
public void bindTexture(IRenderingEngine re) {
re.bindTexture(getTextureId(re));
}
public int getTextureId(IRenderingEngine re) {
if (textureId == -1) {
textureId = re.genTexture();
}
return textureId;
}
public void deleteTexture(IRenderingEngine re) {
if (textureId != -1) {
re.deleteTexture(textureId);
textureId = -1;
}
setLoaded(false);
}
public boolean isLoaded() {
return loaded;
}
public void setIsLoaded() {
setLoaded(true);
}
public void setLoaded(boolean loaded) {
this.loaded = loaded;
}
public int getAddr() {
return addr;
}
public int getClutAddr() {
return clutAddr;
}
public int getClutMode() {
return clutMode;
}
public int getClutStart() {
return clutStart;
}
public int getGlId() {
return textureId;
}
public int getMipmapLevels() {
return mipmapLevels;
}
public boolean isInsideMemory(int fromAddr, int toAddr) {
if (addr >= fromAddr && addr < toAddr) {
return true;
}
if (addr + bufferLengthInBytes >= fromAddr && addr + bufferLengthInBytes < toAddr) {
return true;
}
return false;
}
@Override
public String toString() {
return String.format("Texture[0x%08X, %dx%d, bufferWidth=%d, %s]", addr, width, height, lineWidth, VideoEngine.getPsmName(pixelStorage));
}
}