/*
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 static jpcsp.graphics.GeCommands.TFLT_NEAREST;
import static jpcsp.graphics.GeCommands.TWRAP_WRAP_MODE_CLAMP;
import static jpcsp.graphics.VideoEngine.SIZEOF_FLOAT;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import org.apache.log4j.Logger;
import jpcsp.Memory;
import jpcsp.HLE.Modules;
import jpcsp.HLE.modules.sceDisplay;
import jpcsp.graphics.GeCommands;
import jpcsp.graphics.VideoEngine;
import jpcsp.graphics.RE.IRenderingEngine;
import jpcsp.graphics.RE.buffer.IREBufferManager;
import jpcsp.graphics.capture.CaptureManager;
import jpcsp.util.Utilities;
/**
* @author gid15
*
*/
public class GETexture {
protected static Logger log = VideoEngine.log;
protected int address;
protected int length;
protected int bufferWidth;
protected int width;
protected int height;
protected int widthPow2;
protected int heightPow2;
protected int pixelFormat;
protected int bytesPerPixel;
protected int textureId = -1;
protected int drawBufferId = -1;
protected float texS;
protected float texT;
private boolean changed;
protected int bufferLength;
protected Buffer buffer;
protected boolean useViewportResize;
protected float resizeScale;
// For copying Stencil to texture Alpha
private int stencilFboId = -1;
private int stencilTextureId = -1;
private static final int stencilPixelFormat = IRenderingEngine.RE_DEPTH_STENCIL;
public GETexture(int address, int bufferWidth, int width, int height, int pixelFormat, boolean useViewportResize) {
this.address = address;
this.bufferWidth = bufferWidth;
this.width = width;
this.height = height;
this.pixelFormat = pixelFormat;
bytesPerPixel = sceDisplay.getPixelFormatBytes(pixelFormat);
length = bufferWidth * height * bytesPerPixel;
widthPow2 = Utilities.makePow2(width);
heightPow2 = Utilities.makePow2(height);
this.useViewportResize = useViewportResize;
changed = true;
resizeScale = getViewportResizeScaleFactor();
}
private int getTextureBufferLength() {
return getTexImageWidth() * getTexImageHeight() * bytesPerPixel;
}
private float getViewportResizeScaleFactor() {
if (!useViewportResize) {
return 1;
}
return Modules.sceDisplayModule.getViewportResizeScaleFactor();
}
public void bind(IRenderingEngine re, boolean forDrawing) {
float viewportResizeScaleFactor = getViewportResizeScaleFactor();
// Create the texture if not yet created or
// re-create it if the viewport resize factor has been changed dynamically.
if (textureId == -1 || viewportResizeScaleFactor != resizeScale) {
// The Jpcsp window has been resized. Recreate all the textures using the new size.
if (textureId != -1) {
re.deleteTexture(textureId);
textureId = -1;
}
if (stencilTextureId != -1) {
re.deleteTexture(stencilTextureId);
stencilTextureId = -1;
}
if (stencilFboId != -1) {
re.deleteFramebuffer(stencilFboId);
stencilFboId = -1;
}
resizeScale = viewportResizeScaleFactor;
if (useViewportResize) {
texS = sceDisplay.getResizedWidth(width) / (float) getTexImageWidth();
texT = sceDisplay.getResizedHeight(height) / (float) getTexImageHeight();
} else {
texS = width / (float) bufferWidth;
texT = height / (float) heightPow2;
}
textureId = re.genTexture();
re.bindTexture(textureId);
re.setTexImage(0, pixelFormat, getTexImageWidth(), getTexImageHeight(), pixelFormat, pixelFormat, 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);
if (drawBufferId == -1) {
drawBufferId = re.getBufferManager().genBuffer(IRenderingEngine.RE_ARRAY_BUFFER, IRenderingEngine.RE_FLOAT, 16, IRenderingEngine.RE_DYNAMIC_DRAW);
}
} else {
re.bindTexture(textureId);
}
if (forDrawing) {
re.setTextureFormat(pixelFormat, false);
}
}
public int getBufferWidth() {
return bufferWidth;
}
public int getTexImageWidth() {
return useViewportResize ? sceDisplay.getResizedWidthPow2(bufferWidth) : bufferWidth;
}
public int getTexImageHeight() {
return useViewportResize ? sceDisplay.getResizedHeightPow2(heightPow2) : heightPow2;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int getResizedWidth() {
return useViewportResize ? sceDisplay.getResizedWidth(width) : width;
}
public int getResizedHeight() {
return useViewportResize ? sceDisplay.getResizedHeight(height) : height;
}
public int getWidthPow2() {
return widthPow2;
}
public int getHeightPow2() {
return heightPow2;
}
public int getPixelFormat() {
return pixelFormat;
}
public void copyScreenToTexture(IRenderingEngine re) {
if (log.isDebugEnabled()) {
log.debug(String.format("GETexture.copyScreenToTexture %s", toString()));
}
bind(re, false);
int texWidth = Math.min(bufferWidth, width);
int texHeight = height;
if (useViewportResize) {
texWidth = sceDisplay.getResizedWidth(texWidth);
texHeight = sceDisplay.getResizedHeight(texHeight);
}
re.copyTexSubImage(0, 0, 0, 0, 0, texWidth, texHeight);
if (Modules.sceDisplayModule.isSaveStencilToMemory()) {
if (!copyStencilToTextureAlpha(re, texWidth, texHeight)) {
Modules.sceDisplayModule.setSaveStencilToMemory(false);
}
}
setChanged(true);
}
public void copyTextureToScreen(IRenderingEngine re) {
copyTextureToScreen(re, 0, 0, width, height, true, true, true, true, true);
}
protected void copyTextureToScreen(IRenderingEngine re, int x, int y, int projectionWidth, int projectionHeight, boolean scaleToCanvas, boolean redWriteEnabled, boolean greenWriteEnabled, boolean blueWriteEnabled, boolean alphaWriteEnabled) {
if (log.isDebugEnabled()) {
log.debug(String.format("GETexture.copyTextureToScreen %s at %dx%d", toString(), x, y));
}
bind(re, true);
drawTexture(re, x, y, projectionWidth, projectionHeight, scaleToCanvas, redWriteEnabled, greenWriteEnabled, blueWriteEnabled, alphaWriteEnabled);
}
private void drawTexture(IRenderingEngine re, int x, int y, int projectionWidth, int projectionHeight, boolean scaleToCanvas, boolean redWriteEnabled, boolean greenWriteEnabled, boolean blueWriteEnabled, boolean alphaWriteEnabled) {
re.startDirectRendering(true, false, true, true, true, projectionWidth, projectionHeight);
re.setColorMask(redWriteEnabled, greenWriteEnabled, blueWriteEnabled, alphaWriteEnabled);
if (scaleToCanvas) {
re.setViewport(0, 0, Modules.sceDisplayModule.getCanvasWidth(), Modules.sceDisplayModule.getCanvasHeight());
} else {
re.setViewport(0, 0, projectionWidth, projectionHeight);
}
IREBufferManager bufferManager = re.getBufferManager();
ByteBuffer drawByteBuffer = bufferManager.getBuffer(drawBufferId);
drawByteBuffer.clear();
FloatBuffer drawFloatBuffer = drawByteBuffer.asFloatBuffer();
drawFloatBuffer.clear();
drawFloatBuffer.put(texS);
drawFloatBuffer.put(texT);
drawFloatBuffer.put(x + width);
drawFloatBuffer.put(y + height);
drawFloatBuffer.put(0.f);
drawFloatBuffer.put(texT);
drawFloatBuffer.put(x);
drawFloatBuffer.put(y + height);
drawFloatBuffer.put(0.f);
drawFloatBuffer.put(0.f);
drawFloatBuffer.put(x);
drawFloatBuffer.put(y);
drawFloatBuffer.put(texS);
drawFloatBuffer.put(0.f);
drawFloatBuffer.put(x + width);
drawFloatBuffer.put(y);
if (re.isVertexArrayAvailable()) {
re.bindVertexArray(0);
}
re.setVertexInfo(null, false, false, true, -1);
re.enableClientState(IRenderingEngine.RE_TEXTURE);
re.disableClientState(IRenderingEngine.RE_COLOR);
re.disableClientState(IRenderingEngine.RE_NORMAL);
re.enableClientState(IRenderingEngine.RE_VERTEX);
bufferManager.setTexCoordPointer(drawBufferId, 2, IRenderingEngine.RE_FLOAT, 4 * SIZEOF_FLOAT, 0);
bufferManager.setVertexPointer(drawBufferId, 2, IRenderingEngine.RE_FLOAT, 4 * SIZEOF_FLOAT, 2 * SIZEOF_FLOAT);
bufferManager.setBufferData(IRenderingEngine.RE_ARRAY_BUFFER, drawBufferId, drawFloatBuffer.position() * SIZEOF_FLOAT, drawByteBuffer.rewind(), IRenderingEngine.RE_DYNAMIC_DRAW);
re.drawArrays(IRenderingEngine.RE_QUADS, 0, 4);
re.endDirectRendering();
}
protected void setChanged(boolean changed) {
this.changed = changed;
}
protected boolean hasChanged() {
return changed;
}
private void prepareBuffer() {
// Is the current buffer large enough?
if (buffer != null && bufferLength < getTextureBufferLength()) {
// Reallocate a new larger buffer
buffer = null;
}
if (buffer == null) {
bufferLength = getTextureBufferLength();
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bufferLength).order(ByteOrder.LITTLE_ENDIAN);
if (Memory.getInstance().getMainMemoryByteBuffer() instanceof IntBuffer) {
buffer = byteBuffer.asIntBuffer();
} else {
buffer = byteBuffer;
}
} else {
buffer.clear();
}
}
public void copyTextureToMemory(IRenderingEngine re) {
if (textureId == -1) {
// Texture not yet created... nothing to copy
return;
}
if (!hasChanged()) {
// Texture unchanged... don't copy again
return;
}
if (log.isDebugEnabled()) {
log.debug(String.format("GETexture.copyTextureToMemory %s", toString()));
}
Buffer memoryBuffer = Memory.getInstance().getBuffer(address, length);
prepareBuffer();
re.bindTexture(textureId);
re.setTextureFormat(pixelFormat, false);
re.setPixelStore(bufferWidth, sceDisplay.getPixelFormatBytes(pixelFormat));
re.getTexImage(0, pixelFormat, pixelFormat, buffer);
buffer.clear();
if (buffer instanceof IntBuffer) {
IntBuffer src = (IntBuffer) buffer;
IntBuffer dst = (IntBuffer) memoryBuffer;
int pixelsPerElement = 4 / bytesPerPixel;
int copyWidth = Math.min(width, bufferWidth);
int widthLimit = (copyWidth + pixelsPerElement - 1) / pixelsPerElement;
int step = bufferWidth / pixelsPerElement;
int srcOffset = 0;
int dstOffset = (height - 1) * step;
// We have received the texture data upside-down, invert it
for (int y = 0; y < height; y++, srcOffset += step, dstOffset -= step) {
src.limit(srcOffset + widthLimit);
src.position(srcOffset);
dst.position(dstOffset);
dst.put(src);
}
} else {
ByteBuffer src = (ByteBuffer) buffer;
ByteBuffer dst = (ByteBuffer) memoryBuffer;
int copyWidth = Math.min(width, bufferWidth);
int widthLimit = copyWidth * bytesPerPixel;
int step = bufferWidth * bytesPerPixel;
int srcOffset = 0;
int dstOffset = (height - 1) * step;
// We have received the texture data upside-down, invert it
for (int y = 0; y < height; y++, srcOffset += step, dstOffset -= step) {
src.limit(srcOffset + widthLimit);
src.position(srcOffset);
dst.position(dstOffset);
dst.put(src);
}
}
setChanged(false);
}
public void delete(IRenderingEngine re) {
if (drawBufferId != -1) {
re.getBufferManager().deleteBuffer(drawBufferId);
drawBufferId = -1;
}
if (textureId != -1) {
re.deleteTexture(textureId);
textureId = -1;
}
}
public int getTextureId() {
return textureId;
}
public boolean isCompatible(int width, int height, int bufferWidth, int pixelFormat) {
if (width != this.width || height != this.height || bufferWidth != this.bufferWidth) {
return false;
}
if (useViewportResize) {
if (resizeScale != getViewportResizeScaleFactor()) {
return false;
}
}
return true;
}
protected boolean copyStencilToTextureAlpha(IRenderingEngine re, int texWidth, int texHeight) {
re.checkAndLogErrors(null);
if (stencilFboId == -1) {
// Create a FBO
stencilFboId = re.genFramebuffer();
re.bindFramebuffer(IRenderingEngine.RE_DRAW_FRAMEBUFFER, stencilFboId);
// Create stencil texture and attach it to the FBO
stencilTextureId = re.genTexture();
re.bindTexture(stencilTextureId);
re.checkAndLogErrors("bindTexture");
re.setTexImage(0, stencilPixelFormat, getTexImageWidth(), getTexImageHeight(), stencilPixelFormat, stencilPixelFormat, 0, null);
if (re.checkAndLogErrors("setTexImage")) {
return false;
}
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.setFramebufferTexture(IRenderingEngine.RE_DRAW_FRAMEBUFFER, IRenderingEngine.RE_DEPTH_STENCIL_ATTACHMENT, stencilTextureId, 0);
if (re.checkAndLogErrors("setFramebufferTexture RE_STENCIL_ATTACHMENT")) {
return false;
}
// Attach the GE texture to the FBO as well
re.setFramebufferTexture(IRenderingEngine.RE_DRAW_FRAMEBUFFER, IRenderingEngine.RE_COLOR_ATTACHMENT0, textureId, 0);
if (re.checkAndLogErrors("setFramebufferTexture RE_COLOR_ATTACHMENT0")) {
return false;
}
} else {
re.bindFramebuffer(IRenderingEngine.RE_DRAW_FRAMEBUFFER, stencilFboId);
}
// Copy screen stencil buffer to stencil texture:
// - read framebuffer is screen (0)
// - draw/write framebuffer is our stencil FBO (stencilFboId)
re.blitFramebuffer(0, 0, texWidth, texHeight, 0, 0, texWidth, texHeight, IRenderingEngine.RE_STENCIL_BUFFER_BIT, GeCommands.TFLT_NEAREST);
if (re.checkAndLogErrors("blitFramebuffer")) {
return false;
}
re.bindTexture(stencilTextureId);
if (!re.setCopyRedToAlpha(true)) {
return false;
}
// Draw the stencil texture and update only the alpha channel of the GE texture
drawTexture(re, 0, 0, width, height, true, false, false, false, true);
re.checkAndLogErrors("drawTexture");
// Reset the framebuffer to the default one
re.bindFramebuffer(IRenderingEngine.RE_DRAW_FRAMEBUFFER, 0);
re.setCopyRedToAlpha(false);
// Success
return true;
}
public void capture(IRenderingEngine re) {
if (textureId == -1) {
// Texture not yet created... nothing to capture
return;
}
if (log.isDebugEnabled()) {
log.debug(String.format("GETexture.capture %s", toString()));
}
prepareBuffer();
re.bindTexture(textureId);
re.setTextureFormat(pixelFormat, false);
re.setPixelStore(bufferWidth, sceDisplay.getPixelFormatBytes(pixelFormat));
re.getTexImage(0, pixelFormat, pixelFormat, buffer);
CaptureManager.captureImage(address, 0, buffer, width, height, bufferWidth, pixelFormat, false, 0, true, false);
}
@Override
public String toString() {
return String.format("GETexture[0x%08X-0x%08X, %dx%d (texture %dx%d), bufferWidth=%d, pixelFormat=%d(%s)]", address, address + length, width, height, getTexImageWidth(), getTexImageHeight(), bufferWidth, pixelFormat, VideoEngine.getPsmName(pixelFormat));
}
}