/*
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.RE.software.PixelColor.divideBy2;
import static jpcsp.graphics.RE.software.PixelColor.divideBy4;
import static jpcsp.graphics.RE.software.PixelColor.multiply;
import static jpcsp.util.Utilities.pixelToTexel;
import static jpcsp.util.Utilities.makePow2;
import static jpcsp.util.Utilities.round;
import java.util.LinkedList;
import jpcsp.graphics.GeCommands;
import jpcsp.graphics.VideoEngine;
import jpcsp.graphics.RE.software.CachedTexture.CachedTexturePow2;
/**
* @author gid15
*
*/
public class CachedTextureResampled {
private static final boolean disableResampleAllTextures = false;
private static final boolean disableResampleVRAMTexture = true;
protected LinkedList<ResampleInfo> resampleInfos = new LinkedList<CachedTextureResampled.ResampleInfo>();
protected CachedTexture cachedTextureOriginal;
public CachedTextureResampled(CachedTexture cachedTexture) {
cachedTextureOriginal = cachedTexture;
ResampleInfo resampleInfo = new ResampleInfo(cachedTexture.width, cachedTexture.height, cachedTexture);
resampleInfos.add(resampleInfo);
}
public CachedTexture getOriginalTexture() {
return cachedTextureOriginal;
}
public boolean canResample(float widthFactor, float heightFactor) {
if (disableResampleAllTextures) {
return false;
}
if (disableResampleVRAMTexture && cachedTextureOriginal.isVRAMTexture()) {
// VRAM textures are often minimized or magnified by a factor of 2.
// Allow these resamplings.
if ((widthFactor == .5f && heightFactor == .5f) || (widthFactor == 2f && heightFactor == 2f)) {
return true;
}
return false;
}
return widthFactor >= .5f && heightFactor >= .5f && widthFactor <= 2f && heightFactor <= 2f;
}
public CachedTexture resample(float widthFactor, float heightFactor) {
if (widthFactor == 1f && heightFactor == 1f) {
return cachedTextureOriginal;
}
int width = round(widthFactor * cachedTextureOriginal.width);
int height = round(heightFactor * cachedTextureOriginal.height);
return resample(width, height);
}
/**
* This method has to be synchronized because it can be used but multiple
* renderer threads in parallel (see RendererExecutor).
*/
private synchronized CachedTexture resample(int width, int height) {
// Was the texture already resampled at the given size?
for (ResampleInfo resampleInfo : resampleInfos) {
if (resampleInfo.matches(width, height)) {
return resampleInfo.getCachedTextureResampled();
}
}
// A resampled texture was not yet available, compute one.
return resampleTexture(width, height);
}
private CachedTexture resampleTexture(int width, int height) {
if (resampleInfos.size() >= 5 && VideoEngine.log.isInfoEnabled()) {
VideoEngine.log.info(String.format("Resampling texture from (%d,%d) to (%d,%d), pixelFormat=%d, resampled %d times", cachedTextureOriginal.width, cachedTextureOriginal.height, width, height, cachedTextureOriginal.pixelFormat, resampleInfos.size()));
} else if (VideoEngine.log.isDebugEnabled()) {
VideoEngine.log.debug(String.format("Resampling texture from (%d,%d) to (%d,%d), pixelFormat=%d, resampled %d times", cachedTextureOriginal.width, cachedTextureOriginal.height, width, height, cachedTextureOriginal.pixelFormat, resampleInfos.size()));
}
RESoftware.textureResamplingStatistics.start();
int widthPow2 = makePow2(width);
int heightPow2 = makePow2(height);
int[] buffer = new int[widthPow2 * heightPow2];
int widthSkipEOL = widthPow2 - width;
if (cachedTextureOriginal.width == (width << 1) &&
cachedTextureOriginal.height == (height << 1)) {
// Optimized common case: minimize texture by a factor of 2
resampleTextureMinimize2(buffer, width, height, widthSkipEOL);
} else if ((cachedTextureOriginal.width << 1) == width &&
(cachedTextureOriginal.height << 1) == height) {
// Optimized common case: magnify texture by a factor of 2
resampleTextureMagnify2(buffer, width, height, widthSkipEOL);
} else {
// Generic case: magnify/minimize by arbitrary factors
float widthFactor = cachedTextureOriginal.width / (float) width;
float heightFactor = cachedTextureOriginal.height / (float) height;
resampleTexture(buffer, width, height, widthSkipEOL, widthFactor, heightFactor);
}
CachedTexture cachedTextureResampled = new CachedTexturePow2(widthPow2, heightPow2, width, height, GeCommands.TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888);
cachedTextureResampled.setBuffer(buffer, 0, buffer.length);
ResampleInfo resampleInfo = new ResampleInfo(width, height, cachedTextureResampled);
resampleInfos.add(resampleInfo);
RESoftware.textureResamplingStatistics.end();
return cachedTextureResampled;
}
private void resampleTexture(int[] buffer, int width, int height, int widthSkipEOL, float widthFactor, float heightFactor) {
float v = 0f;
int index = 0;
for (int y = 0; y < height; y++) {
float u = 0f;
for (int x = 0; x < width; x++) {
buffer[index++] = readTexturePixelInterpolated(u, v, widthFactor, heightFactor);
u += widthFactor;
}
index += widthSkipEOL;
v += heightFactor;
}
}
private void resampleTextureMinimize2(int[] buffer, int width, int height, int widthSkipEOL) {
int index = 0;
int pixel;
for (int y = 0, v = 0; y < height; y++, v += 2) {
for (int x = 0, u = 0; x < width; x++, u += 2) {
pixel = divideBy4(cachedTextureOriginal.readPixel(u, v));
pixel += divideBy4(cachedTextureOriginal.readPixel(u + 1, v));
pixel += divideBy4(cachedTextureOriginal.readPixel(u, v + 1));
pixel += divideBy4(cachedTextureOriginal.readPixel(u + 1, v + 1));
buffer[index++] = pixel;
}
index += widthSkipEOL;
}
}
private void resampleTextureMagnify2(int[] buffer, int width, int height, int widthSkipEOL) {
int index = 0;
int pixel;
height -= 2;
width -= 2;
int lastU = width / 2;
int lastV = height / 2;
for (int y = 0, v = 0; y < height; y += 2, v++) {
int currentPixel = cachedTextureOriginal.readPixel(0, v);
for (int x = 0, u = 1; x < width; x += 2, u++) {
buffer[index++] = currentPixel;
pixel = divideBy2(currentPixel);
currentPixel = cachedTextureOriginal.readPixel(u, v);
pixel += divideBy2(currentPixel);
buffer[index++] = pixel;
}
int pixelLastU = cachedTextureOriginal.readPixel(lastU, v);
buffer[index++] = pixelLastU;
buffer[index++] = pixelLastU;
index += widthSkipEOL;
for (int x = 0, u = 0; x < width; x += 2, u++) {
pixel = divideBy2(cachedTextureOriginal.readPixel(u, v));
pixel += divideBy2(cachedTextureOriginal.readPixel(u, v + 1));
buffer[index++] = pixel;
pixel = divideBy2(pixel);
pixel += divideBy4(cachedTextureOriginal.readPixel(u + 1, v));
pixel += divideBy4(cachedTextureOriginal.readPixel(u + 1, v + 1));
buffer[index++] = pixel;
}
pixel = divideBy2(pixelLastU);
pixel += divideBy2(cachedTextureOriginal.readPixel(lastU, v + 1));
buffer[index++] = pixel;
buffer[index++] = pixel;
index += widthSkipEOL;
}
int currentPixel = cachedTextureOriginal.readPixel(0, lastV);
int index2 = index + width + widthSkipEOL + 2;
for (int x = 0, u = 0; x < width; x += 2, u++) {
buffer[index++] = currentPixel;
buffer[index2++] = currentPixel;
pixel = divideBy2(currentPixel);
currentPixel = cachedTextureOriginal.readPixel(u, lastV);
pixel += divideBy2(currentPixel);
buffer[index++] = pixel;
buffer[index2++] = pixel;
}
int pixelLastU = cachedTextureOriginal.readPixel(lastU, lastV);
buffer[index++] = pixelLastU;
buffer[index] = pixelLastU;
buffer[index2++] = pixelLastU;
buffer[index2] = pixelLastU;
}
/**
* Interpolate a texture value at position (u,v) based on its 4 neighboring texels.
*
* (u0,v0)-------(u1,v0)
* | \ / |
* | (u,v) |
* | / \ |
* (u0,v1)-------(u1,v1)
*
* Example: for the pixel at (u=1.3, v=1.6), the following texture value is returned:
* texel(1,1) * 0.7 * 0.4 +
* texel(1,2) * 0.7 * 0.6 +
* texel(2,1) * 0.3 * 0.4 +
* texel(2,2) * 0.3 * 0.6
*
* @param u pixel position along X-axis
* @param v pixel position along Y-axis
* @param widthFactor factor between original and resampled width
* @param heightFactor factor between original and resampled height
* @return interpolated texture value at (u,v)
*/
private int readTexturePixelInterpolated(float u, float v, float widthFactor, float heightFactor) {
int texelU0 = pixelToTexel(u);
int texelV0 = pixelToTexel(v);
int texelU1 = texelU0 + 1;
int texelV1 = texelV0 + 1;
if (texelU1 >= cachedTextureOriginal.width) {
texelU1 = texelU0;
}
if (texelV1 >= cachedTextureOriginal.height) {
texelV1 = texelV0;
}
float factorU1 = u - texelU0;
float factorV1 = v - texelV0;
float factorU0 = 1f - factorU1;
float factorV0 = 1f - factorV1;
// If we fall exactly on one texel, take also the next texel into account
// if we are minimizing the texture
// (i.e. if the resampling factor is larger than 1)
if (factorU1 == 0f && widthFactor > 1f) {
factorU1 = (widthFactor - 1f) / widthFactor;
factorU0 = 1f / widthFactor;
}
if (factorV1 == 0f && heightFactor > 1f) {
factorV1 = (heightFactor - 1f) / heightFactor;
factorV0 = 1f / heightFactor;
}
int pixel;
if (factorU0 > 0f && factorV0 > 0f) {
pixel = multiply(cachedTextureOriginal.readPixel(texelU0, texelV0), factorU0 * factorV0);
} else {
pixel = 0;
}
if (factorU1 > 0f && factorV0 > 0f) {
pixel += multiply(cachedTextureOriginal.readPixel(texelU1, texelV0), factorU1 * factorV0);
}
if (factorU0 > 0f && factorV1 > 0f) {
pixel += multiply(cachedTextureOriginal.readPixel(texelU0, texelV1), factorU0 * factorV1);
}
if (factorU1 > 0f && factorV1 > 0f) {
pixel += multiply(cachedTextureOriginal.readPixel(texelU1, texelV1), factorU1 * factorV1);
}
return pixel;
}
public void setClut() {
cachedTextureOriginal.setClut();
}
private static class ResampleInfo {
private int resampleWidth;
private int resampleHeight;
private CachedTexture cachedTextureResampled;
public ResampleInfo(int resampleWidth, int resampleHeight, CachedTexture cachedTextureResampled) {
this.resampleWidth = resampleWidth;
this.resampleHeight = resampleHeight;
this.cachedTextureResampled = cachedTextureResampled;
}
public boolean matches(int width, int height) {
return width == resampleWidth && height == resampleHeight;
}
public CachedTexture getCachedTextureResampled() {
return cachedTextureResampled;
}
}
}