/*
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 java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import jpcsp.Memory;
import jpcsp.graphics.GeCommands;
import jpcsp.graphics.VideoEngine;
import jpcsp.graphics.RE.IRenderingEngine;
import jpcsp.util.CacheStatistics;
public class TextureCache {
public static final int cacheMaxSize = 1000;
public static final float cacheLoadFactor = 0.75f;
private static Logger log = VideoEngine.log;
private static TextureCache instance = null;
private LinkedHashMap<Integer, Texture> cache;
public CacheStatistics statistics = new CacheStatistics("Texture", cacheMaxSize);
// Remember which textures have already been hashed during one display
// (for applications reusing the same texture multiple times in one display)
private Set<Integer> textureAlreadyHashed;
// Remember which textures are located in VRAM. Only these textures have to be
// scanned when checking for textures updated while rendering to GE.
private LinkedList<Texture> vramTextures = new LinkedList<Texture>();
public static TextureCache getInstance() {
if (instance == null) {
instance = new TextureCache();
}
return instance;
}
private TextureCache() {
//
// Create a cache having
// - initial size large enough so that no rehash will occur
// - the LinkedList is based on access-order for LRU
//
cache = new LinkedHashMap<Integer, Texture>((int) (cacheMaxSize / cacheLoadFactor) + 1, cacheLoadFactor, true);
textureAlreadyHashed = new HashSet<Integer>();
}
private Integer getKey(int addr, int clutAddr, int clutStart, int clutMode) {
// Some games use the same texture address with different cluts.
// Keep a combination of both texture address and clut address in the cache.
// Also, use the clutStart as this parameter can be used to offset the clut address.
int clutEntrySize = clutMode == GeCommands.CMODE_FORMAT_32BIT_ABGR8888 ? 4 : 2;
return new Integer(addr + clutAddr + (clutStart << 4) * clutEntrySize);
}
public boolean hasTexture(int addr, int clutAddr, int clutStart, int clutMode) {
return cache.containsKey(getKey(addr, clutAddr, clutStart, clutMode));
}
private Texture getTexture(int addr, int clutAddr, int clutStart, int clutMode) {
return cache.get(getKey(addr, clutAddr, clutStart, clutMode));
}
public void addTexture(IRenderingEngine re, Texture texture) {
Integer key = getKey(texture.getAddr(), texture.getClutAddr(), texture.getClutStart(), texture.getClutMode());
Texture previousTexture = cache.get(key);
if (previousTexture != null) {
previousTexture.deleteTexture(re);
vramTextures.remove(previousTexture);
} else {
// Check if the cache is not growing too large
if (cache.size() >= cacheMaxSize) {
// Remove the LRU cache entry
Iterator<Map.Entry<Integer, Texture>> it = cache.entrySet().iterator();
if (it.hasNext()) {
Map.Entry<Integer, Texture> entry = it.next();
Texture lruTexture = entry.getValue();
lruTexture.deleteTexture(re);
vramTextures.remove(lruTexture);
it.remove();
statistics.entriesRemoved++;
}
}
}
cache.put(key, texture);
if (isVramTexture(texture)) {
vramTextures.add(texture);
}
if (cache.size() > statistics.maxSizeUsed) {
statistics.maxSizeUsed = cache.size();
}
}
public Texture getTexture(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) {
statistics.totalHits++;
Texture texture = getTexture(addr, clutAddr, clutStart, clutMode);
if (texture == null) {
statistics.notPresentHits++;
return texture;
}
if (texture.equals(addr, lineWidth, width, height, pixelStorage, clutAddr, clutMode, clutStart, clutShift, clutMask, clutNumBlocks, mipmapLevels, mipmapShareClut, values16, values32)) {
statistics.successfulHits++;
return texture;
}
statistics.changedHits++;
return null;
}
public void resetTextureAlreadyHashed() {
textureAlreadyHashed.clear();
}
public boolean textureAlreadyHashed(int addr, int clutAddr, int clutStart, int clutMode) {
return textureAlreadyHashed.contains(getKey(addr, clutAddr, clutStart, clutMode));
}
public void setTextureAlreadyHashed(int addr, int clutAddr, int clutStart, int clutMode) {
textureAlreadyHashed.add(getKey(addr, clutAddr, clutStart, clutMode));
}
public void resetTextureAlreadyHashed(int addr, int clutAddr, int clutStart, int clutMode) {
textureAlreadyHashed.remove(getKey(addr, clutAddr, clutStart, clutMode));
}
public void reset(IRenderingEngine re) {
for (Texture texture : cache.values()) {
texture.deleteTexture(re);
}
cache.clear();
resetTextureAlreadyHashed();
}
private boolean isVramTexture(Texture texture) {
return Memory.isVRAM(texture.getAddr());
}
public void deleteVramTextures(IRenderingEngine re, int addr, int length) {
for (ListIterator<Texture> lit = vramTextures.listIterator(); lit.hasNext(); ) {
Texture texture = lit.next();
if (texture.isInsideMemory(addr, addr + length)) {
if (log.isDebugEnabled()) {
log.debug(String.format("Delete VRAM texture inside GE %s", texture.toString()));
}
texture.deleteTexture(re);
lit.remove();
Integer key = getKey(texture.getAddr(), texture.getClutAddr(), texture.getClutStart(), texture.getClutMode());
cache.remove(key);
statistics.entriesRemoved++;
}
}
}
}