/*
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.doubleColor;
import static jpcsp.graphics.RE.software.PixelColor.getColor;
import static jpcsp.util.Utilities.invertMatrix3x3;
import static jpcsp.util.Utilities.matrixMult;
import static jpcsp.util.Utilities.max;
import static jpcsp.util.Utilities.maxInt;
import static jpcsp.util.Utilities.min;
import static jpcsp.util.Utilities.minInt;
import static jpcsp.util.Utilities.round;
import static jpcsp.util.Utilities.transposeMatrix3x3;
import static jpcsp.util.Utilities.vectorMult44;
import java.util.Arrays;
import java.util.HashMap;
import jpcsp.Allegrex.compiler.RuntimeContext;
import jpcsp.graphics.GeCommands;
import jpcsp.graphics.GeContext;
import jpcsp.graphics.VertexState;
import jpcsp.util.DurationStatistics;
import jpcsp.util.LongLongKey;
import jpcsp.util.Utilities;
/**
* @author gid15
*
* This class extends the BaseRenderer class to include
* vertex specific information.
* The methods from this class can be used to set the vertex
* information specific for the rendering of one primitive (e.g. one triangle)
*/
public abstract class BasePrimitiveRenderer extends BaseRenderer {
private static HashMap<Integer, DurationStatistics> pixelsStatistics = new HashMap<Integer, DurationStatistics>();
private DurationStatistics pixelStatistics = new DurationStatistics();
public final PixelState pixel = new PixelState();
public PrimitiveState prim = new PrimitiveState();
protected boolean needScissoringX;
protected boolean needScissoringY;
protected boolean needSourceDepthRead;
protected boolean needDestinationDepthRead;
protected boolean needDepthWrite;
protected boolean needTextureUV;
protected boolean swapTextureUV;
protected boolean simpleTextureUV;
protected boolean needTextureWrapU;
protected boolean needTextureWrapV;
protected boolean sameVertexColor;
protected boolean needSourceDepthClamp;
public int fbAddress;
public int depthAddress;
public IRendererWriter rendererWriter;
protected void copy(BasePrimitiveRenderer from) {
super.copy(from);
pixel.copy(from.pixel);
prim.copy(from.prim);
needScissoringX = from.needScissoringX;
needScissoringY = from.needScissoringY;
needSourceDepthRead = from.needSourceDepthRead;
needDestinationDepthRead = from.needDestinationDepthRead;
needDepthWrite = from.needDepthWrite;
needTextureUV = from.needTextureUV;
simpleTextureUV = from.simpleTextureUV;
needTextureWrapU = from.needTextureWrapU;
needTextureWrapV = from.needTextureWrapV;
sameVertexColor = from.sameVertexColor;
fbAddress = from.fbAddress;
depthAddress = from.depthAddress;
rendererWriter = from.rendererWriter;
needSourceDepthClamp = from.needSourceDepthClamp;
}
@Override
protected void init(GeContext context, CachedTextureResampled texture, boolean useVertexTexture, boolean isTriangle) {
super.init(context, texture, useVertexTexture, isTriangle);
prim.pxMax = Integer.MIN_VALUE;
prim.pxMin = Integer.MAX_VALUE;
prim.pyMax = Integer.MIN_VALUE;
prim.pyMin = Integer.MAX_VALUE;
prim.pzMax = Integer.MIN_VALUE;
prim.pzMin = Integer.MAX_VALUE;
if (!transform2D) {
// Pre-compute the Model-View matrix
matrixMult(pixel.modelViewMatrix, context.view_uploaded_matrix, context.model_uploaded_matrix);
// Pre-compute the Model-View-Projection matrix
matrixMult(pixel.modelViewProjectionMatrix, context.proj_uploaded_matrix, pixel.modelViewMatrix);
}
}
@Override
protected void initRendering(GeContext context) {
if (renderingInitialized) {
return;
}
super.initRendering(context);
pixel.materialAmbient = getColor(context.mat_ambient);
pixel.materialDiffuse = getColor(context.mat_diffuse);
pixel.materialSpecular = getColor(context.mat_specular);
if (!transform2D) {
if (context.tex_map_mode == GeCommands.TMAP_TEXTURE_MAP_MODE_TEXTURE_MATRIX) {
// Copy the Texture matrix
System.arraycopy(context.texture_uploaded_matrix, 0, pixel.textureMatrix, 0, pixel.textureMatrix.length);
}
pixel.hasNormal = context.vinfo.normal != 0;
if (pixel.hasNormal) {
// Pre-compute the matrix to transform a normal to the eye coordinates
// See http://www.lighthouse3d.com/tutorials/glsl-tutorial/the-normal-matrix/
float[] invertedModelViewMatrix = new float[16];
if (invertMatrix3x3(invertedModelViewMatrix, pixel.modelViewMatrix)) {
transposeMatrix3x3(pixel.normalMatrix, invertedModelViewMatrix);
} else {
// What is using the PSP in this case? Assume it just takes the Model-View matrix
System.arraycopy(pixel.modelViewMatrix, 0, pixel.normalMatrix, 0, pixel.normalMatrix.length);
if (isLogDebugEnabled) {
log.debug(String.format("ModelView matrix cannot be inverted, taking the Model-View matrix itself!"));
}
}
}
}
}
protected void addPosition(float[] p) {
float[] screenCoordinates = new float[4];
getScreenCoordinates(screenCoordinates, p);
prim.pxMax = maxInt(prim.pxMax, screenCoordinates[0]);
prim.pxMin = minInt(prim.pxMin, screenCoordinates[0]);
prim.pyMax = maxInt(prim.pyMax, screenCoordinates[1]);
prim.pyMin = minInt(prim.pyMin, screenCoordinates[1]);
prim.pzMax = maxInt(prim.pzMax, screenCoordinates[2]);
prim.pzMin = minInt(prim.pzMin, screenCoordinates[2]);
}
protected void setVertexPositions(VertexState v1, VertexState v2, VertexState v3) {
setPositions(v1, v2, v3);
}
protected void setVertexPositions(VertexState v1, VertexState v2) {
setPositions(v1, v2);
}
protected void setVertexTextures(GeContext context, VertexState v1, VertexState v2, VertexState v3) {
setTextures(v1, v2, v3);
setVertexTextures(context, v1.c, v2.c, v3.c);
}
protected void setVertexTextures(GeContext context, VertexState v1, VertexState v2) {
setTextures(v1, v2);
setVertexTextures(context, v1.c, v2.c, null);
}
private void setVertexTextures(GeContext context, float[] c1, float[] c2, float[] c3) {
textureWidth = context.texture_width[mipmapLevel];
textureHeight = context.texture_height[mipmapLevel];
// The rendering will be performed into the following ranges:
// 3D:
// - x: [pxMin..pxMax] (min and max included)
// - y: [pxMin..pxMax] (min and max included)
// 2D:
// - x: [pxMin..pxMax-1] (min included but max excluded)
// - y: [pxMin..pxMax-1] (min included but max excluded)
if (transform2D) {
prim.pxMax--;
prim.pyMax--;
} else {
// Restrict the drawn area to the scissor area.
// We can just update the min/max values, the TextureMapping filter
// will take are of the correct texture mapping.
// We do no longer need a scissoring filter.
if (needScissoringX) {
prim.pxMin = max(prim.pxMin, scissorX1);
prim.pxMax = min(prim.pxMax, scissorX2);
needScissoringX = false;
}
if (needScissoringY) {
prim.pyMin = max(prim.pyMin, scissorY1);
prim.pyMax = min(prim.pyMax, scissorY2);
needScissoringY = false;
}
}
prim.destinationWidth = prim.pxMax - prim.pxMin + 1;
prim.destinationHeight = prim.pyMax - prim.pyMin + 1;
if (isUsingTexture(context)) {
simpleTextureUV = !isTriangle;
if (!simpleTextureUV && isTriangle && transform2D) {
// Check if the 2D triangle can be rendered using a simple texture UV mapping:
// this is only possible when the triangle has a square angle.
//
// 1---2 1---2 1 1
// | / \ | | \ / |
// | / \ | | \ / |
// 3 3 3---2 3---2
//
// 1---3 1---3 1 1
// | / \ | | \ / |
// | / \ | | \ / |
// 2 2 2---3 2---3
//
if (prim.p1x == prim.p2x && prim.t1u == prim.t2u) {
if (prim.p1y == prim.p3y && prim.t1v == prim.t3v) {
simpleTextureUV = true;
} else if (prim.p2y == prim.p3y && prim.t2v == prim.t3v){
simpleTextureUV = true;
}
} else if (prim.p1x == prim.p3x && prim.t1u == prim.t3u) {
if (prim.p1y == prim.p2y && prim.t1v == prim.t2v) {
simpleTextureUV = true;
} else if (prim.p2y == prim.p3y && prim.t2v == prim.t3v){
simpleTextureUV = true;
}
} else if (prim.p2x == prim.p3x && prim.t2u == prim.t3u) {
if (prim.p1y == prim.p2y && prim.t1v == prim.t2v) {
simpleTextureUV = true;
} else if (prim.p1y == prim.p1y && prim.t2v == prim.t3v){
simpleTextureUV = true;
}
}
}
if (simpleTextureUV) {
if (transform2D) {
boolean flipX = false;
boolean flipY = false;
if (isTriangle) {
// Compute texture flips for a triangle
if (prim.t1u != prim.t2u) {
flipX = (prim.t1u > prim.t2u) ^ (prim.p1x > prim.p2x);
}
if (prim.t1v != prim.t2v) {
flipY = (prim.t1v > prim.t2v) ^ (prim.p1y > prim.p2y);
}
if (!flipX && prim.t2u != prim.t3u) {
flipX = (prim.t2u > prim.t3u) ^ (prim.p2x > prim.p3x);
}
if (!flipY && prim.t2v != prim.t3v) {
flipY = (prim.t2v > prim.t3v) ^ (prim.p2y > prim.p3y);
}
} else {
// Compute texture flips for a sprite
flipX = (prim.t1u > prim.t2u) ^ (prim.p1x > prim.p2x);
flipY = (prim.t1v > prim.t2v) ^ (prim.p1y > prim.p2y);
if (flipX && flipY) {
swapTextureUV = true;
flipX = false;
flipY = false;
}
}
if (isLogTraceEnabled) {
log.trace(String.format("2D texture flipX=%b, flipY=%b, swapUV=%b, point (%d,%d)-(%d,%d), texture (%d,%d)-(%d,%d)", flipX, flipY, swapTextureUV, prim.pxMin, prim.pyMin, prim.pxMax, prim.pyMax, prim.tuMin, prim.tvMin, prim.tuMax, prim.tvMax));
}
prim.uStart = flipX ? prim.tuMax : prim.tuMin;
float uEnd = flipX ? prim.tuMin : prim.tuMax;
prim.vStart = flipY ? prim.tvMax : prim.tvMin;
float vEnd = flipY ? prim.tvMin : prim.tvMax;
prim.uStep = (uEnd - prim.uStart) / (swapTextureUV ? prim.destinationHeight : prim.destinationWidth);
prim.vStep = (vEnd - prim.vStart) / (swapTextureUV ? prim.destinationWidth : prim.destinationHeight);
} else {
// 3D sprite
prim.uStart = prim.t1u;
float uEnd = prim.t2u;
prim.vStart = prim.t1v;
float vEnd = prim.t2v;
if (prim.p1x == prim.p2x) {
prim.uStep = 1f;
} else {
prim.uStep = (uEnd - prim.uStart) / Math.abs(prim.p2x - prim.p1x);
}
if (prim.p1y == prim.p2y) {
prim.vStep = 1f;
} else {
prim.vStep = (vEnd - prim.vStart) / Math.abs(prim.p2y - prim.p1y);
}
if (isLogTraceEnabled) {
log.trace(String.format("3D sprite uStart=%f, uStep=%f, vStart=%f, vStep=%f, texTranslateX=%f, texTranslateY=%f, texScaleX=%f, texScaleY=%f, point (%d,%d)-(%d,%d), texture (%d,%d)-(%d,%d)", prim.uStart, prim.uStep, prim.vStart, prim.vStep, texTranslateX, texTranslateY, texScaleX, texScaleY, prim.pxMin, prim.pyMin, prim.pxMax, prim.pyMax, prim.tuMin, prim.tvMin, prim.tuMax, prim.tvMax));
}
}
// Perform scissoring and update uStart/uStep and vStart/vStep
if (needScissoringX) {
int deltaX = scissorX1 - prim.pxMin;
if (deltaX > 0) {
prim.uStart += prim.uStep * deltaX;
prim.pxMin += deltaX;
if (transform2D) {
prim.tuMin += round(prim.uStep * deltaX);
}
}
deltaX = prim.pxMax - scissorX2;
if (deltaX > 0) {
prim.pxMax -= deltaX;
if (transform2D) {
prim.tuMax -= round(prim.uStep * deltaX);
}
}
prim.destinationWidth = prim.pxMax - prim.pxMin + 1;
needScissoringX = false;
}
if (needScissoringY) {
int deltaY = scissorY1 - prim.pyMin;
if (deltaY > 0) {
prim.vStart += prim.vStep * deltaY;
prim.pyMin += deltaY;
if (transform2D) {
prim.tvMin += round(prim.vStep * deltaY);
}
}
deltaY = prim.pyMax - scissorY2;
if (deltaY > 0) {
prim.pyMax -= deltaY;
if (transform2D) {
prim.tvMax -= round(prim.vStep * deltaY);
}
}
prim.destinationHeight = prim.pyMax - prim.pyMin + 1;
needScissoringY = false;
}
}
}
if (setVertexPrimaryColor) {
if (c3 != null) {
pixel.c1a = getColor(c1[3]);
pixel.c1b = getColor(c1[2]);
pixel.c1g = getColor(c1[1]);
pixel.c1r = getColor(c1[0]);
pixel.c2a = getColor(c2[3]);
pixel.c2b = getColor(c2[2]);
pixel.c2g = getColor(c2[1]);
pixel.c2r = getColor(c2[0]);
pixel.c3a = getColor(c3[3]);
pixel.c3b = getColor(c3[2]);
pixel.c3g = getColor(c3[1]);
pixel.c3r = getColor(c3[0]);
pixel.c3 = getColor(c3);
}
if (isTriangle) {
if (context.shadeModel == GeCommands.SHADE_TYPE_FLAT) {
// Flat shade model: always use the color of the 3rd triangle vertex
sameVertexColor = true;
} else {
sameVertexColor = Utilities.sameColor(c1, c2, c3);
}
// For triangles, take the weighted color from the 3 vertices.
} else {
// For sprites, take only the color from the 2nd vertex
primaryColor = getColor(c2);
if (context.textureColorDoubled) {
primaryColor = doubleColor(primaryColor);
}
}
}
// Try to avoid to compute expensive values
needDepthWrite = getNeedDepthWrite(context);
needSourceDepthRead = needDepthWrite || getNeedSourceDepthRead(context);
needDestinationDepthRead = getNeedDestinationDepthRead(context, needDepthWrite);
if (zbw <= 0) {
needDepthWrite = false;
needSourceDepthRead = false;
needDestinationDepthRead = false;
}
needTextureUV = getNeedTextureUV(context);
if (transform2D) {
needTextureWrapU = prim.tuMin < 0 || prim.tuMax >= context.texture_width[mipmapLevel];
needTextureWrapV = prim.tvMin < 0 || prim.tvMax >= context.texture_height[mipmapLevel];
} else {
if (context.tex_map_mode != GeCommands.TMAP_TEXTURE_MAP_MODE_TEXTURE_COORDIATES_UV) {
needTextureWrapU = true;
needTextureWrapV = true;
} else if (isTriangle && (prim.p1w <= 0f || prim.p2w <= 0f || prim.p3w <= 0f)) {
// Need texture wrapping if one triangle point is behind the eye:
// the texture coordinates might exceed the calculated range due to the perspective correction.
needTextureWrapU = true;
needTextureWrapV = true;
} else {
float tuMin, tuMax;
float tvMin, tvMax;
if (isTriangle) {
tuMin = Utilities.min(prim.t1u, Utilities.min(prim.t2u, prim.t3u));
tuMax = Utilities.max(prim.t1u, Utilities.max(prim.t2u, prim.t3u));
tvMin = Utilities.min(prim.t1v, Utilities.min(prim.t2v, prim.t3v));
tvMax = Utilities.max(prim.t1v, Utilities.max(prim.t2v, prim.t3v));
} else {
tuMin = Utilities.min(prim.t1u, prim.t2u);
tuMax = Utilities.max(prim.t1u, prim.t2u);
tvMin = Utilities.min(prim.t1v, prim.t2v);
tvMax = Utilities.max(prim.t1v, prim.t2v);
}
tuMin = tuMin * texScaleX + texTranslateX;
tuMax = tuMax * texScaleX + texTranslateX;
tvMin = tvMin * texScaleY + texTranslateY;
tvMax = tvMax * texScaleY + texTranslateY;
needTextureWrapU = tuMin < 0f || tuMin >= 0.99999f || tuMax < 0f || tuMax >= 0.99999f;
needTextureWrapV = tvMin < 0f || tvMin >= 0.99999f || tvMax < 0f || tvMax >= 0.99999f;
}
}
needSourceDepthClamp = false;
if (needDepthWrite && needSourceDepthRead && isTriangle) {
if (prim.p1z < 0f || prim.p2z < 0f || prim.p3z < 0f) {
needSourceDepthClamp = true;
} else if (prim.p1z > 65535f || prim.p2z > 65535f || prim.p3z > 65535f) {
needSourceDepthClamp = true;
}
}
prepareWriters();
LongLongKey rendererKey = getRendererKey();
if (compiledRenderer == null || !rendererKey.equals(compiledRendererKey)) {
compiledRendererKey = rendererKey;
compiledRenderer = FilterCompiler.getInstance().getCompiledRenderer(this, rendererKey, context);
if (isLogTraceEnabled) {
log.trace(String.format("Rendering using compiled renderer %s", compiledRenderer.getClass().getName()));
}
}
if (c3 != null) {
prim.preComputeTriangleWeights();
}
}
private boolean getNeedSourceDepthRead(GeContext context) {
if (!clearMode && context.depthTestFlag.isEnabled()) {
if (context.depthFunc != GeCommands.ZTST_FUNCTION_NEVER_PASS_PIXEL && context.depthFunc != GeCommands.ZTST_FUNCTION_ALWAYS_PASS_PIXEL) {
return true;
}
}
if (!clearMode && !transform2D) {
if (nearZ != 0x0000 || farZ != 0xFFFF) {
return true;
}
}
return false;
}
private boolean getNeedDestinationDepthRead(GeContext context, boolean needDepthWrite) {
if (!clearMode && context.depthTestFlag.isEnabled()) {
if (context.depthFunc != GeCommands.ZTST_FUNCTION_NEVER_PASS_PIXEL && context.depthFunc != GeCommands.ZTST_FUNCTION_ALWAYS_PASS_PIXEL) {
return true;
}
}
if (clearMode) {
// Depth writes disabled
if (!context.clearModeDepth) {
return true;
}
} else if (!context.depthTestFlag.isEnabled() && needDepthWrite) {
// Depth writes are disabled when the depth test is not enabled.
return true;
} else if (!context.depthMask && needDepthWrite) {
// Depth writes disabled
return true;
}
return false;
}
private boolean getNeedDepthWrite(GeContext context) {
if (clearMode) {
// Depth writes disabled
if (!context.clearModeDepth) {
return false;
}
} else if (!context.depthTestFlag.isEnabled()) {
// Depth writes are disabled when the depth test is not enabled.
return false;
} else if (!context.depthMask) {
// Depth writes disabled
return false;
}
return true;
}
private boolean getNeedTextureUV(GeContext context) {
if (context.textureFlag.isEnabled() && (!transform2D || useVertexTexture) && !clearMode) {
// UV always required by the texture reader
return true;
}
return false;
}
/**
* Transform a floating-point 2D position coordinate to a screen coordinate.
* The PSP (tested using 3DStudio) is applying the following transformation:
* 0.562 transformed to 0
* 0.563 transformed to 1
*
* @param value floating-point 2D position coordinate
* @return screen coordinate
*/
protected static final int positionCoordinate2D(float value) {
return (int) (value + 0.437f);
}
private void setPositions(VertexState v1, VertexState v2) {
pixel.v1x = v1.p[0];
pixel.v1y = v1.p[1];
pixel.v1z = v1.p[2];
pixel.n1x = v1.n[0];
pixel.n1y = v1.n[1];
pixel.n1z = v1.n[2];
pixel.v2x = v2.p[0];
pixel.v2y = v2.p[1];
pixel.v2z = v2.p[2];
pixel.n2x = v2.n[0];
pixel.n2y = v2.n[1];
pixel.n2z = v2.n[2];
if (transform2D) {
prim.p1x = pixel.v1x;
prim.p1y = pixel.v1y;
prim.p1z = pixel.v1z;
prim.p2x = pixel.v2x;
prim.p2y = pixel.v2y;
prim.p2z = pixel.v2z;
// TODO Need more investigation
// prim.pxMax = max(positionCoordinate2D(prim.p1x), positionCoordinate2D(prim.p2x));
// prim.pxMin = min(positionCoordinate2D(prim.p1x), positionCoordinate2D(prim.p2x));
// prim.pyMax = max(positionCoordinate2D(prim.p1y), positionCoordinate2D(prim.p2y));
// prim.pyMin = min(positionCoordinate2D(prim.p1y), positionCoordinate2D(prim.p2y));
// prim.pzMax = max(positionCoordinate2D(prim.p1z), positionCoordinate2D(prim.p2z));
// prim.pzMin = min(positionCoordinate2D(prim.p1z), positionCoordinate2D(prim.p2z));
prim.pxMax = maxInt(prim.p1x, prim.p2x);
prim.pxMin = minInt(prim.p1x, prim.p2x);
prim.pyMax = maxInt(prim.p1y, prim.p2y);
prim.pyMin = minInt(prim.p1y, prim.p2y);
prim.pzMax = maxInt(prim.p1z, prim.p2z);
prim.pzMin = minInt(prim.p1z, prim.p2z);
} else {
float[] screenCoordinates = new float[4];
getScreenCoordinates(screenCoordinates, pixel.v1x, pixel.v1y, pixel.v1z);
prim.p1x = screenCoordinates[0];
prim.p1y = screenCoordinates[1];
prim.p1z = screenCoordinates[2];
prim.p1w = screenCoordinates[3];
prim.p1wInverted = 1.f / prim.p1w;
getScreenCoordinates(screenCoordinates, pixel.v2x, pixel.v2y, pixel.v2z);
prim.p2x = screenCoordinates[0];
prim.p2y = screenCoordinates[1];
prim.p2z = screenCoordinates[2];
prim.p2w = screenCoordinates[3];
prim.p2wInverted = 1.f / prim.p2w;
prim.pxMax = maxInt(prim.p1x, prim.p2x);
prim.pxMin = minInt(prim.p1x, prim.p2x);
prim.pyMax = maxInt(prim.p1y, prim.p2y);
prim.pyMin = minInt(prim.p1y, prim.p2y);
prim.pzMax = maxInt(prim.p1z, prim.p2z);
prim.pzMin = minInt(prim.p1z, prim.p2z);
}
}
private void setPositions(VertexState v1, VertexState v2, VertexState v3) {
setPositions(v1, v2);
pixel.v3x = v3.p[0];
pixel.v3y = v3.p[1];
pixel.v3z = v3.p[2];
pixel.n3x = v3.n[0];
pixel.n3y = v3.n[1];
pixel.n3z = v3.n[2];
if (transform2D) {
prim.p3x = pixel.v3x;
prim.p3y = pixel.v3y;
prim.p3z = pixel.v3z;
// TODO Need more investigation
// prim.pxMax = max(prim.pxMax, positionCoordinate2D(prim.p3x));
// prim.pxMin = min(prim.pxMin, positionCoordinate2D(prim.p3x));
// prim.pyMax = max(prim.pyMax, positionCoordinate2D(prim.p3y));
// prim.pyMin = min(prim.pyMin, positionCoordinate2D(prim.p3y));
// prim.pzMax = max(prim.pzMax, positionCoordinate2D(prim.p3z));
// prim.pzMin = min(prim.pzMin, positionCoordinate2D(prim.p3z));
prim.pxMax = maxInt(prim.pxMax, prim.p3x);
prim.pxMin = minInt(prim.pxMin, prim.p3x);
prim.pyMax = maxInt(prim.pyMax, prim.p3y);
prim.pyMin = minInt(prim.pyMin, prim.p3y);
prim.pzMax = maxInt(prim.pzMax, prim.p3z);
prim.pzMin = minInt(prim.pzMin, prim.p3z);
} else {
float[] screenCoordinates = new float[4];
getScreenCoordinates(screenCoordinates, pixel.v3x, pixel.v3y, pixel.v3z);
prim.p3x = screenCoordinates[0];
prim.p3y = screenCoordinates[1];
prim.p3z = screenCoordinates[2];
prim.p3w = screenCoordinates[3];
prim.p3wInverted = 1.f / prim.p3w;
prim.pxMax = maxInt(prim.pxMax, prim.p3x);
prim.pxMin = minInt(prim.pxMin, prim.p3x);
prim.pyMax = maxInt(prim.pyMax, prim.p3y);
prim.pyMin = minInt(prim.pyMin, prim.p3y);
prim.pzMax = maxInt(prim.pzMax, prim.p3z);
prim.pzMin = minInt(prim.pzMin, prim.p3z);
}
}
private void setTextures(VertexState v1, VertexState v2) {
prim.t1u = v1.t[0];
prim.t1v = v1.t[1];
prim.t2u = v2.t[0];
prim.t2v = v2.t[1];
if (transform2D) {
prim.tuMax = max(round(prim.t1u), round(prim.t2u));
prim.tuMin = min(round(prim.t1u), round(prim.t2u));
prim.tvMax = max(round(prim.t1v), round(prim.t2v));
prim.tvMin = min(round(prim.t1v), round(prim.t2v));
}
}
private void setTextures(VertexState v1, VertexState v2, VertexState v3) {
setTextures(v1, v2);
prim.t3u = v3.t[0];
prim.t3v = v3.t[1];
if (transform2D) {
prim.tuMax = max(prim.tuMax, round(prim.t3u));
prim.tuMin = min(prim.tuMin, round(prim.t3u));
prim.tvMax = max(prim.tvMax, round(prim.t3v));
prim.tvMin = min(prim.tvMin, round(prim.t3v));
}
}
private void prepareWriters() {
fbAddress = getTextureAddress(fbp, prim.pxMin, prim.pyMin, fbw, psm);
depthAddress = getTextureAddress(zbp, prim.pxMin, prim.pyMin, zbw, depthBufferPixelFormat);
if (!RendererTemplate.isRendererWriterNative(RuntimeContext.getMemoryInt(), psm)) {
rendererWriter = RendererWriter.getRendererWriter(fbAddress, fbw, psm, depthAddress, zbw, depthBufferPixelFormat, needDestinationDepthRead, needDepthWrite);
}
imageWriterSkipEOL = fbw - prim.destinationWidth;
depthWriterSkipEOL = zbw - prim.destinationWidth;
}
protected boolean isVisible() {
if (!transform2D) {
// Each vertex screen coordinates (without offset) has to be in the range:
// - x: [0..4095]
// - y: [0..4095]
// - z: [..65535]
// If one of the vertex coordinate is not in the valid range, the whole
// primitive is discarded.
if ((prim.pxMin + screenOffsetX) < 0 ||
(prim.pxMax + screenOffsetX) >= 4096 ||
(prim.pyMin + screenOffsetY) < 0 ||
(prim.pyMax + screenOffsetY) >= 4096 ||
prim.pzMax >= 65536) {
if (isLogTraceEnabled) {
log.trace(String.format("Screen coordinates outside valid range %d-%d, %d-%d, %d", prim.pxMin + screenOffsetX, prim.pxMax + screenOffsetX, prim.pyMin + screenOffsetY, prim.pyMax + screenOffsetY, prim.pzMax));
}
return false;
}
// This is probably a rounding error when one triangle
// extends from back to front over a very large distance
// (more than the allowed range for Z values).
if (prim.pzMin < 0 && prim.pzMax > 0 && prim.pzMax - prim.pzMin > 65536) {
if (isLogTraceEnabled) {
log.trace(String.format("Z range too large: %d, %d", prim.pzMin, prim.pzMax));
}
return false;
}
if (!clipPlanesEnabled) {
// The primitive is discarded when one of the vertex is behind the viewpoint
// (only the the ClipPlanes flag is not enabled).
if (prim.pzMin < 0) {
if (isLogTraceEnabled) {
log.trace(String.format("Z behind clip plane %d", prim.pzMin));
}
return false;
}
} else {
// TODO Implement proper triangle clipping against the near plane
if (prim.p1w < 0f || prim.p2w < 0f || prim.p3w < 0f) {
if (isLogTraceEnabled) {
log.trace(String.format("W negative %f, %f, %f", prim.p1w, prim.p2w, prim.p3w));
}
return false;
}
}
}
if (!useVertexTexture) {
prim.pxMin = Math.max(prim.pxMin, scissorX1);
prim.pxMax = Math.min(prim.pxMax, Math.min(scissorX2 + 1, fbw));
prim.pyMin = Math.max(prim.pyMin, scissorY1);
prim.pyMax = Math.min(prim.pyMax, scissorY2 + 1);
}
if (prim.pxMin == prim.pxMax || prim.pyMin == prim.pyMax) {
// Empty area to be displayed
if (isLogTraceEnabled) {
log.trace("Empty area");
}
return false;
}
if (isTriangle) {
if ((pixel.v1x == pixel.v2x && pixel.v1y == pixel.v2y && pixel.v1z == pixel.v2z) ||
(pixel.v1x == pixel.v3x && pixel.v1y == pixel.v3y && pixel.v1z == pixel.v3z) ||
(pixel.v2x == pixel.v3x && pixel.v2y == pixel.v3y && pixel.v2z == pixel.v3z)) {
// 2 vertices are equal in the triangle, nothing has to be displayed
if (isLogTraceEnabled) {
log.trace("2 vertices equal in triangle");
}
return false;
}
}
if (!insideScissor()) {
if (isLogTraceEnabled) {
log.trace("Not inside scissor");
}
return false;
}
return true;
}
protected boolean insideScissor() {
needScissoringX = false;
needScissoringY = false;
// Scissoring (also applied in clear mode)
if (prim.pxMax < scissorX1 || prim.pxMin > scissorX2) {
// Completely outside the scissor area, skip
if (isLogTraceEnabled) {
log.trace(String.format("X outside scissor area %d-%d, %d-%d", prim.pxMin, prim.pxMax, scissorX1, scissorX2));
}
return false;
}
if (prim.pyMax < scissorY1 || prim.pyMin > scissorY2) {
// Completely outside the scissor area, skip
if (isLogTraceEnabled) {
log.trace(String.format("Y outside scissor area %d-%d, %d-%d", prim.pyMin, prim.pyMax, scissorY1, scissorY2));
}
return false;
}
if (!transform2D) {
if ((nearZ > 0x0000 && prim.pzMax < nearZ) || (farZ < 0xFFFF && prim.pzMin > farZ)) {
// Completely outside the view area, skip
if (isLogTraceEnabled) {
log.trace(String.format("Z outside view area %d-%d, %d-%d", prim.pzMin, prim.pzMax, nearZ, farZ));
}
return false;
}
}
if (prim.pxMin < scissorX1 || prim.pxMax > scissorX2) {
// partially outside the scissor area, use the scissoring filter
needScissoringX = true;
}
if (prim.pyMin < scissorY1 || prim.pyMax > scissorY2) {
// partially outside the scissor area, use the scissoring filter
needScissoringY = true;
}
return true;
}
private void getScreenCoordinates(float[] screenCoordinates, float[] position) {
getScreenCoordinates(screenCoordinates, position[0], position[1], position[2]);
}
private void getScreenCoordinates(float[] screenCoordinates, float x, float y, float z) {
float[] position4 = new float[4];
position4[0] = x;
position4[1] = y;
position4[2] = z;
position4[3] = 1.f;
float[] projectedCoordinates = new float[4];
vectorMult44(projectedCoordinates, pixel.modelViewProjectionMatrix, position4);
float w = projectedCoordinates[3];
float wInverted = 1.f / w;
screenCoordinates[0] = projectedCoordinates[0] * wInverted * viewportWidth + viewportX - screenOffsetX;
screenCoordinates[1] = projectedCoordinates[1] * wInverted * viewportHeight + viewportY - screenOffsetY;
screenCoordinates[2] = projectedCoordinates[2] * wInverted * zscale + zpos;
screenCoordinates[3] = w;
if (isLogTraceEnabled) {
log.trace(String.format("X,Y,Z = %f, %f, %f, projected X,Y,Z,W = %f, %f, %f, %f -> Screen %.3f, %.3f, %.3f", x, y, z, projectedCoordinates[0] / w, projectedCoordinates[1] / w, projectedCoordinates[2] / w, w, screenCoordinates[0], screenCoordinates[1], screenCoordinates[2]));
}
}
@Override
public void postRender() {
if (DurationStatistics.collectStatistics && isLogInfoEnabled) {
pixelStatistics.end();
final int pixelsGrouping = 1000;
int n = pixel.getNumberPixels() / pixelsGrouping;
if (!pixelsStatistics.containsKey(n)) {
pixelsStatistics.put(n, new DurationStatistics(String.format("Pixels count=%d", n * pixelsGrouping)));
}
if (isLogTraceEnabled) {
log.trace(String.format("Pixels statistics count=%d, real count=%d", n * pixelsGrouping, pixel.getNumberPixels()));
}
pixelsStatistics.get(n).add(pixelStatistics);
}
if (rendererWriter != null) {
rendererWriter.flush();
}
super.postRender();
statisticsFilters(pixel.getNumberPixels());
}
@Override
public void preRender() {
pixel.reset();
super.preRender();
if (DurationStatistics.collectStatistics && isLogInfoEnabled) {
pixelStatistics.reset();
pixelStatistics.start();
}
}
protected void writerSkip(int count) {
rendererWriter.skip(count, count);
}
protected void writerSkipEOL(int count) {
rendererWriter.skip(count + imageWriterSkipEOL, count + depthWriterSkipEOL);
}
protected void writerSkipEOL() {
rendererWriter.skip(imageWriterSkipEOL, depthWriterSkipEOL);
}
@Override
public void render() {
compiledRenderer.render(this);
}
public static void exit() {
if (!log.isInfoEnabled() || pixelsStatistics.isEmpty()) {
return;
}
DurationStatistics[] sortedPixelsStatistics = pixelsStatistics.values().toArray(new DurationStatistics[pixelsStatistics.size()]);
Arrays.sort(sortedPixelsStatistics);
for (DurationStatistics durationStatistics : sortedPixelsStatistics) {
log.info(durationStatistics);
}
}
protected LongLongKey getRendererKey() {
LongLongKey key = new LongLongKey(baseRendererKey);
key.addKeyComponent(needSourceDepthRead);
key.addKeyComponent(needDestinationDepthRead);
key.addKeyComponent(needDepthWrite);
key.addKeyComponent(needTextureUV);
key.addKeyComponent(simpleTextureUV);
key.addKeyComponent(swapTextureUV);
key.addKeyComponent(needScissoringX);
key.addKeyComponent(needScissoringY);
key.addKeyComponent(needTextureWrapU);
key.addKeyComponent(needTextureWrapV);
key.addKeyComponent(sameVertexColor);
key.addKeyComponent(needSourceDepthClamp);
return key;
}
}