/*
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.getColor;
import static jpcsp.graphics.RE.software.PixelColor.getColorBGR;
import java.nio.Buffer;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import org.apache.log4j.Logger;
import jpcsp.Memory;
import jpcsp.MemoryMap;
import jpcsp.State;
import jpcsp.Allegrex.compiler.RuntimeContext;
import jpcsp.HLE.Modules;
import jpcsp.graphics.GeCommands;
import jpcsp.graphics.GeContext;
import jpcsp.graphics.VideoEngine;
import jpcsp.graphics.RE.IRenderingEngine;
import jpcsp.graphics.capture.CaptureManager;
import jpcsp.memory.IMemoryReader;
import jpcsp.memory.ImageReader;
import jpcsp.util.DurationStatistics;
import jpcsp.util.LongLongKey;
import jpcsp.util.Utilities;
/**
* @author gid15
*
* This class is the base for all renderers.
* It can be re-used for multiple primitives (e.g. multiple triangles)
* belonging to the same IRenderingEngine.drawArrays call.
* This class contains all the information based
* on the GeContext but has no vertex-specific information.
*/
public abstract class BaseRenderer implements IRenderer {
protected static final Logger log = VideoEngine.log;
protected final boolean isLogTraceEnabled;
protected final boolean isLogDebugEnabled;
protected final boolean isLogInfoEnabled;
protected static final boolean captureEachPrimitive = false;
protected static final boolean captureZbuffer = false;
public static final int depthBufferPixelFormat = GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_INDEXED;
protected static final int MAX_NUMBER_FILTERS = 15;
public int imageWriterSkipEOL;
public int depthWriterSkipEOL;
protected RendererTemplate compiledRenderer;
protected LongLongKey compiledRendererKey;
protected LongLongKey baseRendererKey;
protected boolean transform2D;
protected int viewportWidth;
protected int viewportHeight;
protected int viewportX;
protected int viewportY;
protected int screenOffsetX;
protected int screenOffsetY;
protected float zscale;
protected float zpos;
public int nearZ;
public int farZ;
public int scissorX1, scissorY1;
public int scissorX2, scissorY2;
protected boolean setVertexPrimaryColor;
protected int fbp, fbw, psm;
protected int zbp, zbw;
protected boolean clearMode;
protected boolean cullFaceEnabled;
protected boolean frontFaceCw;
protected boolean clipPlanesEnabled;
protected boolean useVertexTexture;
public IRandomTextureAccess textureAccess;
protected int mipmapLevel = 0;
public Lighting lighting;
private static HashMap<LongLongKey, Integer> filtersStatistics = new HashMap<LongLongKey, Integer>();
private static HashMap<LongLongKey, String> filterNames = new HashMap<LongLongKey, String>();
protected boolean renderingInitialized;
public CachedTextureResampled cachedTexture;
protected boolean isTriangle;
public int colorTestRef;
public int colorTestMsk;
public int alphaRef;
public int alphaMask;
public int stencilRef;
public int stencilMask;
public int sfix;
public int dfix;
public int colorMask;
public boolean primaryColorSetGlobally;
public float texTranslateX;
public float texTranslateY;
public float texScaleX = 1f;
public float texScaleY = 1f;
public int textureWidth;
public int textureHeight;
public int texEnvColorB;
public int texEnvColorG;
public int texEnvColorR;
public float[] envMapLightPosU;
public float[] envMapLightPosV;
public boolean envMapDiffuseLightU;
public boolean envMapDiffuseLightV;
public float envMapShininess;
public int texMinFilter;
public int texMagFilter;
public int primaryColor;
public int[] ditherMatrix;
protected void copy(BaseRenderer from) {
imageWriterSkipEOL = from.imageWriterSkipEOL;
depthWriterSkipEOL = from.depthWriterSkipEOL;
compiledRendererKey = from.compiledRendererKey;
compiledRenderer = from.compiledRenderer;
lighting = from.lighting;
textureAccess = from.textureAccess;
transform2D = from.transform2D;
nearZ = from.nearZ;
farZ = from.farZ;
scissorX1 = from.scissorX1;
scissorY1 = from.scissorY1;
scissorX2 = from.scissorX2;
scissorY2 = from.scissorY2;
cachedTexture = from.cachedTexture;
isTriangle = from.isTriangle;
colorTestRef = from.colorTestRef;
colorTestMsk = from.colorTestMsk;
alphaRef = from.alphaRef;
alphaMask = from.alphaMask;
stencilRef = from.stencilRef;
stencilMask = from.stencilMask;
sfix = from.sfix;
dfix = from.dfix;
colorMask = from.colorMask;
primaryColorSetGlobally = from.primaryColorSetGlobally;
texTranslateX = from.texTranslateX;
texTranslateY = from.texTranslateY;
texScaleX = from.texScaleX;
texScaleY = from.texScaleY;
textureWidth = from.textureWidth;
textureHeight = from.textureHeight;
texEnvColorB = from.texEnvColorB;
texEnvColorG = from.texEnvColorG;
texEnvColorR = from.texEnvColorR;
envMapLightPosU = from.envMapLightPosU;
envMapLightPosV = from.envMapLightPosV;
envMapDiffuseLightU = from.envMapDiffuseLightU;
envMapDiffuseLightV = from.envMapDiffuseLightV;
envMapShininess = from.envMapShininess;
texMinFilter = from.texMinFilter;
texMagFilter = from.texMagFilter;
primaryColor = from.primaryColor;
ditherMatrix = from.ditherMatrix;
}
protected BaseRenderer() {
isLogTraceEnabled = log.isTraceEnabled();
isLogDebugEnabled = log.isDebugEnabled();
isLogInfoEnabled = log.isInfoEnabled();
}
protected int getTextureAddress(int address, int x, int y, int textureWidth, int pixelFormat) {
int bytesPerPixel = IRenderingEngine.sizeOfTextureType[pixelFormat];
int numberOfPixels = y * textureWidth + x;
address &= Memory.addressMask;
// bytesPerPixel == 0 means 2 pixels per byte (4bit indexed)
return address + (bytesPerPixel == 0 ? numberOfPixels >> 1 : numberOfPixels * bytesPerPixel);
}
private static int getFrameBufferAddress(int addr) {
addr &= Memory.addressMask;
if (addr < MemoryMap.START_VRAM) {
addr += MemoryMap.START_VRAM;
}
return addr;
}
protected void init(GeContext context, CachedTextureResampled texture, boolean useVertexTexture, boolean isTriangle) {
this.cachedTexture = texture;
this.useVertexTexture = useVertexTexture;
this.isTriangle = isTriangle;
nearZ = context.nearZ;
farZ = context.farZ;
scissorX1 = context.scissor_x1;
scissorY1 = context.scissor_y1;
scissorX2 = context.scissor_x2;
scissorY2 = context.scissor_y2;
clearMode = context.clearMode;
cullFaceEnabled = context.cullFaceFlag.isEnabled();
frontFaceCw = context.frontFaceCw;
clipPlanesEnabled = context.clipPlanesFlag.isEnabled();
fbw = context.fbw;
zbw = context.zbw;
if (context.ditherFlag.isEnabled()) {
ditherMatrix = context.dither_matrix.clone();
}
transform2D = context.vinfo.transform2D;
if (!transform2D) {
viewportWidth = context.viewport_width;
viewportHeight = context.viewport_height;
viewportX = context.viewport_cx;
viewportY = context.viewport_cy;
screenOffsetX = context.offset_x;
screenOffsetY = context.offset_y;
zscale = context.zscale;
zpos = context.zpos;
if (context.tex_map_mode == GeCommands.TMAP_TEXTURE_MAP_MODE_TEXTURE_COORDIATES_UV) {
texTranslateX = context.tex_translate_x;
texTranslateY = context.tex_translate_y;
texScaleX = context.tex_scale_x;
texScaleY = context.tex_scale_y;
} else if (context.tex_map_mode == GeCommands.TMAP_TEXTURE_MAP_MODE_ENVIRONMENT_MAP) {
envMapLightPosU = new float[4];
envMapLightPosV = new float[4];
Utilities.copy(envMapLightPosU, context.light_pos[context.tex_shade_u]);
Utilities.copy(envMapLightPosV, context.light_pos[context.tex_shade_v]);
envMapDiffuseLightU = context.light_type[context.tex_shade_u] == GeCommands.LIGHT_AMBIENT_DIFFUSE;
envMapDiffuseLightV = context.light_type[context.tex_shade_v] == GeCommands.LIGHT_AMBIENT_DIFFUSE;
envMapShininess = context.materialShininess;
}
}
if (isLogTraceEnabled) {
log.trace(String.format("Context: %s", context.toString()));
}
}
protected boolean isUsingTexture(GeContext context) {
return context.textureFlag.isEnabled() && (!transform2D || useVertexTexture) && !clearMode;
}
protected void initRendering(GeContext context) {
if (renderingInitialized) {
return;
}
fbp = getFrameBufferAddress(context.fbp);
psm = context.psm;
zbp = getFrameBufferAddress(context.zbp);
colorTestRef = getColorBGR(context.colorTestRef);
colorTestMsk = getColorBGR(context.colorTestMsk);
alphaRef = context.alphaRef & context.alphaMask;
alphaMask = context.alphaMask;
stencilRef = context.stencilRef & context.stencilMask;
stencilMask = context.stencilMask;
sfix = context.sfix;
dfix = context.dfix;
colorMask = getColor(context.colorMask);
textureWidth = context.texture_width[mipmapLevel];
textureHeight = context.texture_height[mipmapLevel];
texEnvColorB = getColor(context.tex_env_color[2]);
texEnvColorG = getColor(context.tex_env_color[1]);
texEnvColorR = getColor(context.tex_env_color[0]);
texMinFilter = context.tex_min_filter;
texMagFilter = context.tex_mag_filter;
primaryColor = getColor(context.vertexColor);
baseRendererKey = getBaseRendererKey(context);
if (!transform2D && context.lightingFlag.isEnabled()) {
lighting = new Lighting(context.view_uploaded_matrix,
context.mat_emissive,
context.ambient_light,
context.lightFlags,
context.light_pos,
context.light_kind,
context.light_type,
context.lightAmbientColor,
context.lightDiffuseColor,
context.lightSpecularColor,
context.lightConstantAttenuation,
context.lightLinearAttenuation,
context.lightQuadraticAttenuation,
context.spotLightCutoff,
context.spotLightCosCutoff,
context.light_dir,
context.spotLightExponent,
context.materialShininess,
context.lightMode,
context.vinfo.normal != 0);
}
// Is the lighting model using the material color from the vertex color?
if (!transform2D && context.lightingFlag.isEnabled() && context.mat_flags != 0 && context.useVertexColor && context.vinfo.color != 0 && isTriangle) {
setVertexPrimaryColor = true;
}
primaryColorSetGlobally = false;
if (transform2D || !context.lightingFlag.isEnabled()) {
// No lighting, take the primary color from the vertex.
// This will be done by the BasePrimitiveRenderer when the vertices are known.
if (context.useVertexColor && context.vinfo.color != 0) {
setVertexPrimaryColor = true;
if (!isTriangle) {
// Use the color of the 2nd sprite vertex
primaryColorSetGlobally = true;
}
} else {
// Use context.vertexColor as the primary color
primaryColorSetGlobally = true;
}
}
textureAccess = null;
if (isUsingTexture(context)) {
int textureBufferWidth = VideoEngine.alignBufferWidth(context.texture_buffer_width[mipmapLevel], context.texture_storage);
int textureHeight = context.texture_height[mipmapLevel];
int textureAddress = context.texture_base_pointer[mipmapLevel];
if (cachedTexture == null) {
int[] clut32 = VideoEngine.getInstance().readClut32(mipmapLevel);
short[] clut16 = VideoEngine.getInstance().readClut16(mipmapLevel);
// Always request the whole buffer width
IMemoryReader imageReader = ImageReader.getImageReader(textureAddress, textureBufferWidth, textureHeight, textureBufferWidth, context.texture_storage, context.texture_swizzle, context.tex_clut_addr, context.tex_clut_mode, context.tex_clut_num_blocks, context.tex_clut_start, context.tex_clut_shift, context.tex_clut_mask, clut32, clut16);
textureAccess = new RandomTextureAccessReader(imageReader, textureBufferWidth, textureHeight);
} else {
textureAccess = cachedTexture.getOriginalTexture();
}
// Avoid an access outside the texture area
textureAccess = TextureClip.getTextureClip(context, mipmapLevel, textureAccess, textureBufferWidth, textureHeight);
}
renderingInitialized = true;
}
private LongLongKey getBaseRendererKey(GeContext context) {
LongLongKey key = new LongLongKey();
key.addKeyComponent(RuntimeContext.hasMemoryInt());
key.addKeyComponent(transform2D);
key.addKeyComponent(clearMode);
if (clearMode) {
key.addKeyComponent(context.clearModeColor);
key.addKeyComponent(context.clearModeStencil);
key.addKeyComponent(context.clearModeDepth);
} else {
key.addKeyComponent(false);
key.addKeyComponent(false);
key.addKeyComponent(false);
}
key.addKeyComponent(nearZ == 0x0000);
key.addKeyComponent(farZ == 0xFFFF);
key.addKeyComponent(context.colorTestFlag.isEnabled() ? context.colorTestFunc : GeCommands.CTST_COLOR_FUNCTION_ALWAYS_PASS_PIXEL, 2);
if (context.alphaTestFlag.isEnabled()) {
key.addKeyComponent(context.alphaFunc, 3);
key.addKeyComponent(context.alphaRef == 0x00);
key.addKeyComponent(context.alphaRef == 0xFF);
} else {
key.addKeyComponent(GeCommands.ATST_ALWAYS_PASS_PIXEL, 3);
key.addKeyComponent(false);
key.addKeyComponent(false);
}
if (context.stencilTestFlag.isEnabled()) {
key.addKeyComponent(context.stencilFunc, 3);
key.addKeyComponent(context.stencilOpFail, 3);
key.addKeyComponent(context.stencilOpZFail, 3);
key.addKeyComponent(context.stencilOpZPass, 3);
} else {
key.addKeyComponent(GeCommands.STST_FUNCTION_ALWAYS_PASS_STENCIL_TEST, 3);
// Use invalid stencil operations
key.addKeyComponent(7, 3);
key.addKeyComponent(7, 3);
key.addKeyComponent(7, 3);
}
key.addKeyComponent(context.depthTestFlag.isEnabled() ? context.depthFunc : GeCommands.ZTST_FUNCTION_ALWAYS_PASS_PIXEL, 3);
if (context.blendFlag.isEnabled()) {
key.addKeyComponent(context.blendEquation, 3);
key.addKeyComponent(context.blend_src, 4);
key.addKeyComponent(context.blend_dst, 4);
key.addKeyComponent(context.sfix == 0x000000);
key.addKeyComponent(context.sfix == 0xFFFFFF);
key.addKeyComponent(context.dfix == 0x000000);
key.addKeyComponent(context.dfix == 0xFFFFFF);
} else {
// Use an invalid blend equation value
key.addKeyComponent(7, 3);
key.addKeyComponent(15, 4);
key.addKeyComponent(15, 4);
key.addKeyComponent(false);
key.addKeyComponent(false);
key.addKeyComponent(false);
key.addKeyComponent(false);
}
key.addKeyComponent(context.colorLogicOpFlag.isEnabled() ? context.logicOp : GeCommands.LOP_COPY, 4);
key.addKeyComponent(PixelColor.getColor(context.colorMask) == 0x00000000);
key.addKeyComponent(context.depthMask);
key.addKeyComponent(context.textureFlag.isEnabled());
key.addKeyComponent(useVertexTexture);
key.addKeyComponent(context.lightingFlag.isEnabled());
key.addKeyComponent(setVertexPrimaryColor);
key.addKeyComponent(primaryColorSetGlobally);
key.addKeyComponent(isTriangle);
key.addKeyComponent(context.mat_flags, 3);
key.addKeyComponent(context.useVertexColor);
key.addKeyComponent(context.textureColorDoubled);
key.addKeyComponent(context.lightMode, 1);
key.addKeyComponent(context.tex_map_mode, 2);
if (context.tex_map_mode == GeCommands.TMAP_TEXTURE_MAP_MODE_TEXTURE_MATRIX) {
key.addKeyComponent(context.tex_proj_map_mode, 2);
} else {
key.addKeyComponent(0, 2);
}
key.addKeyComponent(context.tex_translate_x == 0f);
key.addKeyComponent(context.tex_translate_y == 0f);
key.addKeyComponent(context.tex_scale_x == 1f);
key.addKeyComponent(context.tex_scale_y == 1f);
key.addKeyComponent(context.tex_wrap_s, 1);
key.addKeyComponent(context.tex_wrap_t, 1);
key.addKeyComponent(context.textureFunc, 3);
key.addKeyComponent(context.textureAlphaUsed);
key.addKeyComponent(context.psm, 2);
key.addKeyComponent(context.tex_min_filter, 3);
key.addKeyComponent(context.tex_mag_filter, 1);
key.addKeyComponent(isLogTraceEnabled);
key.addKeyComponent(DurationStatistics.collectStatistics);
key.addKeyComponent(context.ditherFlag.isEnabled());
return key;
}
protected void preRender() {
}
protected void postRender() {
if (captureEachPrimitive && State.captureGeNextFrame) {
// Capture the GE screen after each primitive
Modules.sceDisplayModule.captureGeImage();
}
if (captureZbuffer && State.captureGeNextFrame) {
captureZbufferImage();
}
}
protected void captureZbufferImage() {
GeContext context = VideoEngine.getInstance().getContext();
int width = context.zbw;
int height = Modules.sceDisplayModule.getHeightFb();
int address = getTextureAddress(zbp, 0, 0, zbw, depthBufferPixelFormat);
Buffer buffer = Memory.getInstance().getBuffer(address, width * height * IRenderingEngine.sizeOfTextureType[depthBufferPixelFormat]);
CaptureManager.captureImage(address, 0, buffer, width, height, width, depthBufferPixelFormat, false, 0, false, false);
}
protected void statisticsFilters(int numberPixels) {
if (!DurationStatistics.collectStatistics || !isLogInfoEnabled) {
return;
}
Integer count = filtersStatistics.get(compiledRendererKey);
if (count == null) {
count = 0;
}
filtersStatistics.put(compiledRendererKey, count + numberPixels);
if (!filterNames.containsKey(compiledRendererKey)) {
filterNames.put(compiledRendererKey, compiledRenderer.getClass().getName());
}
}
public static void exit() {
if (log.isInfoEnabled() && DurationStatistics.collectStatistics) {
LongLongKey[] filterKeys = filtersStatistics.keySet().toArray(new LongLongKey[filtersStatistics.size()]);
Arrays.sort(filterKeys, new FilterComparator());
for (LongLongKey filterKey : filterKeys) {
Integer count = filtersStatistics.get(filterKey);
log.info(String.format("Filter: count=%d, id=%s, %s", count, filterKey, filterNames.get(filterKey)));
}
}
FilterCompiler.exit();
}
private static class FilterComparator implements Comparator<LongLongKey> {
@Override
public int compare(LongLongKey o1, LongLongKey o2) {
return filtersStatistics.get(o1).compareTo(filtersStatistics.get(o2));
}
}
}