/*
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.GeCommands.LMODE_SEPARATE_SPECULAR_COLOR;
import static jpcsp.graphics.GeCommands.SOP_REPLACE_STENCIL_VALUE;
import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888;
import static jpcsp.graphics.RE.software.PixelColor.addComponent;
import static jpcsp.graphics.RE.software.PixelColor.doubleColor;
import static jpcsp.graphics.RE.software.PixelColor.ONE;
import static jpcsp.graphics.RE.software.PixelColor.ZERO;
import static jpcsp.graphics.RE.software.PixelColor.absBGR;
import static jpcsp.graphics.RE.software.PixelColor.add;
import static jpcsp.graphics.RE.software.PixelColor.addBGR;
import static jpcsp.graphics.RE.software.PixelColor.combineComponent;
import static jpcsp.graphics.RE.software.PixelColor.doubleComponent;
import static jpcsp.graphics.RE.software.PixelColor.getAlpha;
import static jpcsp.graphics.RE.software.PixelColor.getBlue;
import static jpcsp.graphics.RE.software.PixelColor.getColor;
import static jpcsp.graphics.RE.software.PixelColor.getColorBGR;
import static jpcsp.graphics.RE.software.PixelColor.getGreen;
import static jpcsp.graphics.RE.software.PixelColor.getRed;
import static jpcsp.graphics.RE.software.PixelColor.maxBGR;
import static jpcsp.graphics.RE.software.PixelColor.minBGR;
import static jpcsp.graphics.RE.software.PixelColor.multiply;
import static jpcsp.graphics.RE.software.PixelColor.multiplyBGR;
import static jpcsp.graphics.RE.software.PixelColor.multiplyComponent;
import static jpcsp.graphics.RE.software.PixelColor.setAlpha;
import static jpcsp.graphics.RE.software.PixelColor.setBGR;
import static jpcsp.graphics.RE.software.PixelColor.substractBGR;
import static jpcsp.graphics.RE.software.PixelColor.substractComponent;
import static jpcsp.util.Utilities.pixelToTexel;
import static jpcsp.util.Utilities.wrap;
import static jpcsp.util.Utilities.clamp;
import static jpcsp.util.Utilities.dot3;
import static jpcsp.util.Utilities.max;
import static jpcsp.util.Utilities.min;
import static jpcsp.util.Utilities.normalize3;
import static jpcsp.util.Utilities.round;
import jpcsp.Allegrex.compiler.RuntimeContext;
import jpcsp.graphics.GeCommands;
import jpcsp.graphics.VideoEngine;
import jpcsp.graphics.RE.software.Rasterizer.Range;
import jpcsp.util.DurationStatistics;
/**
* @author gid15
*
*/
public class RendererTemplate {
public static boolean hasMemInt;
public static boolean needSourceDepthRead;
public static boolean needDestinationDepthRead;
public static boolean needDepthWrite;
public static boolean needTextureUV;
public static boolean simpleTextureUV;
public static boolean swapTextureUV;
public static boolean needScissoringX;
public static boolean needScissoringY;
public static boolean transform2D;
public static boolean clearMode;
public static boolean clearModeColor;
public static boolean clearModeStencil;
public static boolean clearModeDepth;
public static int nearZ;
public static int farZ;
public static boolean colorTestFlagEnabled;
public static int colorTestFunc;
public static boolean alphaTestFlagEnabled;
public static int alphaFunc;
public static int alphaRef;
public static int alphaMask;
public static boolean stencilTestFlagEnabled;
public static int stencilFunc;
public static int stencilRef;
public static int stencilOpFail;
public static int stencilOpZFail;
public static int stencilOpZPass;
public static boolean depthTestFlagEnabled;
public static int depthFunc;
public static boolean blendFlagEnabled;
public static int blendEquation;
public static int blendSrc;
public static int blendDst;
public static int sfix;
public static int dfix;
public static boolean colorLogicOpFlagEnabled;
public static int logicOp;
public static int colorMask;
public static boolean depthMask;
public static boolean textureFlagEnabled;
public static boolean useVertexTexture;
public static boolean lightingFlagEnabled;
public static boolean sameVertexColor;
public static boolean setVertexPrimaryColor;
public static boolean primaryColorSetGlobally;
public static boolean isTriangle;
public static boolean matFlagAmbient;
public static boolean matFlagDiffuse;
public static boolean matFlagSpecular;
public static boolean useVertexColor;
public static boolean textureColorDoubled;
public static int lightMode;
public static int texMapMode;
public static int texProjMapMode;
public static float texTranslateX;
public static float texTranslateY;
public static float texScaleX;
public static float texScaleY;
public static int texWrapS;
public static int texWrapT;
public static int textureFunc;
public static boolean textureAlphaUsed;
public static int psm;
public static int texMagFilter;
public static boolean needTextureWrapU;
public static boolean needTextureWrapV;
public static boolean needSourceDepthClamp;
public static boolean isLogTraceEnabled;
public static boolean collectStatistics;
public static boolean ditherFlagEnabled;
private static final boolean resampleTextureForMag = true;
private static DurationStatistics statistics;
public RendererTemplate() {
if (collectStatistics) {
if (statistics == null) {
statistics = new DurationStatistics(String.format("Duration %s", getClass().getName()));
}
}
}
public static boolean isRendererWriterNative(int[] memInt, int psm) {
return memInt != null && psm == GeCommands.TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888;
}
public DurationStatistics getStatistics() {
return statistics;
}
public void render(final BasePrimitiveRenderer renderer) {
doRender(renderer);
}
private static void doRenderStart(final BasePrimitiveRenderer renderer) {
if (isLogTraceEnabled) {
final PrimitiveState prim = renderer.prim;
String comment;
if (isTriangle) {
if (transform2D) {
comment = "Triangle doRender 2D";
} else {
comment = "Triangle doRender 3D";
}
} else {
if (transform2D) {
comment = "Sprite doRender 2D";
} else {
comment = "Sprite doRender 3D";
}
}
VideoEngine.log.trace(String.format("%s (%d,%d)-(%d,%d) skip=%d", comment, prim.pxMin, prim.pyMin, prim.pxMax, prim.pyMax, renderer.imageWriterSkipEOL));
}
renderer.preRender();
if (collectStatistics) {
if (isTriangle) {
if (transform2D) {
RESoftware.triangleRender2DStatistics.start();
} else {
RESoftware.triangleRender3DStatistics.start();
}
} else {
RESoftware.spriteRenderStatistics.start();
}
statistics.start();
}
}
private static void doRenderEnd(final BasePrimitiveRenderer renderer) {
if (collectStatistics) {
statistics.end();
if (isTriangle) {
if (transform2D) {
RESoftware.triangleRender2DStatistics.end();
} else {
RESoftware.triangleRender3DStatistics.end();
}
} else {
RESoftware.spriteRenderStatistics.end();
}
}
renderer.postRender();
}
private static IRandomTextureAccess resampleTexture(final BasePrimitiveRenderer renderer) {
IRandomTextureAccess textureAccess = renderer.textureAccess;
renderer.prim.needResample = false;
if (textureFlagEnabled && (!transform2D || useVertexTexture) && !clearMode) {
if (texMagFilter == GeCommands.TFLT_LINEAR) {
final PrimitiveState prim = renderer.prim;
if (needTextureUV && simpleTextureUV && transform2D) {
if (renderer.cachedTexture != null) {
if (Math.abs(prim.uStep) != 1f || Math.abs(prim.vStep) != 1f) {
prim.resampleFactorWidth = 1f / Math.abs(prim.uStep);
prim.resampleFactorHeight = 1f / Math.abs(prim.vStep);
if (renderer.cachedTexture.canResample(prim.resampleFactorWidth, prim.resampleFactorHeight)) {
prim.needResample = true;
prim.uStart *= prim.resampleFactorWidth;
prim.vStart *= prim.resampleFactorHeight;
prim.uStep = prim.uStep < 0f ? -1f : 1f;
prim.vStep = prim.vStep < 0f ? -1f : 1f;
} else if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Cannot resample with factors %f, %f", prim.resampleFactorWidth, prim.resampleFactorHeight));
}
}
}
} else {
if (resampleTextureForMag && !transform2D && renderer.cachedTexture != null) {
prim.resampleFactorWidth = 2f;
prim.resampleFactorHeight = 2f;
prim.needResample = renderer.cachedTexture.canResample(prim.resampleFactorWidth, prim.resampleFactorHeight);
}
}
}
}
return textureAccess;
}
private static void doRender(final BasePrimitiveRenderer renderer) {
final PixelState pixel = renderer.pixel;
final PrimitiveState prim = renderer.prim;
final IRendererWriter rendererWriter = renderer.rendererWriter;
final Lighting lighting = renderer.lighting;
IRandomTextureAccess textureAccess = resampleTexture(renderer);
doRenderStart(renderer);
int stencilRefAlpha = 0;
if (stencilTestFlagEnabled && !clearMode && stencilRef != 0) {
if (stencilOpFail == SOP_REPLACE_STENCIL_VALUE || stencilOpZFail == SOP_REPLACE_STENCIL_VALUE || stencilOpZPass == SOP_REPLACE_STENCIL_VALUE) {
// Prepare stencilRef as a ready-to-use alpha value
stencilRefAlpha = renderer.stencilRef << 24;
}
}
int stencilRefMasked = renderer.stencilRef & renderer.stencilMask;
int notColorMask = 0xFFFFFFFF;
if (!clearMode && colorMask != 0x00000000) {
notColorMask = ~renderer.colorMask;
}
int alpha, a, b, g, r;
int textureWidthMask = renderer.textureWidth - 1;
int textureHeightMask = renderer.textureHeight - 1;
float textureWidthFloat = renderer.textureWidth;
float textureHeightFloat = renderer.textureHeight;
final int alphaRef = renderer.alphaRef;
final int primSourceDepth = (int) prim.p2z;
float u = prim.uStart;
float v = prim.vStart;
ColorDepth colorDepth = new ColorDepth();
PrimarySecondaryColors colors = new PrimarySecondaryColors();
Range range = null;
Rasterizer rasterizer = null;
float t1uw = 0f;
float t1vw = 0f;
float t2uw = 0f;
float t2vw = 0f;
float t3uw = 0f;
float t3vw = 0f;
if (isTriangle) {
if (transform2D) {
t1uw = prim.t1u;
t1vw = prim.t1v;
t2uw = prim.t2u;
t2vw = prim.t2v;
t3uw = prim.t3u;
t3vw = prim.t3v;
} else {
t1uw = prim.t1u * prim.p1wInverted;
t1vw = prim.t1v * prim.p1wInverted;
t2uw = prim.t2u * prim.p2wInverted;
t2vw = prim.t2v * prim.p2wInverted;
t3uw = prim.t3u * prim.p3wInverted;
t3vw = prim.t3v * prim.p3wInverted;
}
range = new Range();
// No need to use a Rasterizer when rendering very small area.
// The overhead of the Rasterizer would lead to slower rendering.
if (prim.destinationWidth >= Rasterizer.MINIMUM_WIDTH && prim.destinationHeight >= Rasterizer.MINIMUM_HEIGHT) {
rasterizer = new Rasterizer(prim.p1x, prim.p1y, prim.p2x, prim.p2y, prim.p3x, prim.p3y, prim.pyMin, prim.pyMax);
rasterizer.setY(prim.pyMin);
}
}
int fbIndex = 0;
int depthIndex = 0;
int depthOffset = 0;
final int[] memInt = RuntimeContext.getMemoryInt();
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex = renderer.fbAddress >> 2;
depthIndex = renderer.depthAddress >> 2;
depthOffset = (renderer.depthAddress >> 1) & 1;
}
// Use local variables instead of "pixel" members.
// The Java JIT is then producing a slightly faster code.
int sourceColor = 0;
int sourceDepth = 0;
int destinationColor = 0;
int destinationDepth = 0;
int primaryColor = renderer.primaryColor;
int secondaryColor = 0;
float pixelU = 0f;
float pixelV = 0f;
boolean needResample = prim.needResample;
for (int y = prim.pyMin; y <= prim.pyMax; y++) {
int startX = prim.pxMin;
int endX = prim.pxMax;
if (isTriangle && rasterizer != null) {
rasterizer.getNextRange(range);
startX = max(range.xMin, startX);
endX = min(range.xMax, endX);
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Rasterizer line (%d-%d,%d)", startX, endX, y));
}
}
if (isTriangle && startX > endX) {
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex += prim.destinationWidth + renderer.imageWriterSkipEOL;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset += prim.destinationWidth + renderer.depthWriterSkipEOL;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(prim.destinationWidth + renderer.imageWriterSkipEOL, prim.destinationWidth + renderer.depthWriterSkipEOL);
}
} else {
if (needTextureUV && simpleTextureUV) {
if (swapTextureUV) {
v = prim.vStart;
} else {
u = prim.uStart;
}
}
if (isTriangle) {
int startSkip = startX - prim.pxMin;
if (startSkip > 0) {
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex += startSkip;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset += startSkip;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(startSkip, startSkip);
}
if (simpleTextureUV) {
if (swapTextureUV) {
v += startSkip * prim.vStep;
} else {
u += startSkip * prim.uStep;
}
}
}
prim.computeTriangleWeights(pixel, startX, y);
}
for (int x = startX; x <= endX; x++) {
// Use a dummy "do { } while (false);" loop to allow to exit
// quickly from the pixel rendering when a filter does not pass.
// When a filter does not pass, the following is executed:
// rendererWriter.skip(1, 1); // Skip the pixel
// continue;
do {
//
// Test if the pixel is inside the triangle
//
if (isTriangle && !pixel.isInsideTriangle()) {
// Pixel not inside triangle, skip the pixel
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d) outside triangle (%f, %f, %f)", x, y, pixel.triangleWeight1, pixel.triangleWeight2, pixel.triangleWeight3));
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
//
// Start rendering the pixel
//
if (transform2D) {
pixel.newPixel2D();
} else {
pixel.newPixel3D();
}
//
// ScissorTest (performed as soon as the pixel screen coordinates are available)
//
if (transform2D) {
if (needScissoringX && needScissoringY) {
if (!(x >= renderer.scissorX1 && x <= renderer.scissorX2 && y >= renderer.scissorY1 && y <= renderer.scissorY2)) {
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
} else if (needScissoringX) {
if (!(x >= renderer.scissorX1 && x <= renderer.scissorX2)) {
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
} else if (needScissoringY) {
if (!(y >= renderer.scissorY1 && y <= renderer.scissorY2)) {
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
}
}
//
// Pixel source depth
//
if (needSourceDepthRead) {
if (isTriangle) {
sourceDepth = round(pixel.getTriangleWeightedValue(prim.p1z, prim.p2z, prim.p3z));
} else {
sourceDepth = primSourceDepth;
}
}
//
// ScissorDepthTest (performed as soon as the pixel source depth is available)
//
if (!transform2D && !clearMode && needSourceDepthRead) {
if (nearZ != 0x0000 || farZ != 0xFFFF) {
if (sourceDepth < renderer.nearZ || sourceDepth > renderer.farZ) {
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
}
}
//
// Pixel destination color and depth
//
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
destinationColor = memInt[fbIndex];
if (needDestinationDepthRead) {
if (depthOffset == 0) {
destinationDepth = memInt[depthIndex] & 0x0000FFFF;
} else {
destinationDepth = memInt[depthIndex] >>> 16;
}
}
} else {
rendererWriter.readCurrent(colorDepth);
destinationColor = colorDepth.color;
if (needDestinationDepthRead) {
destinationDepth = colorDepth.depth;
}
}
//
// StencilTest (performed as soon as destination color is known)
//
if (stencilTestFlagEnabled && !clearMode) {
switch (stencilFunc) {
case GeCommands.STST_FUNCTION_NEVER_PASS_STENCIL_TEST:
if (stencilOpFail != GeCommands.SOP_KEEP_STENCIL_VALUE) {
destinationColor = stencilOpFail(destinationColor, stencilRefAlpha);
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
case GeCommands.STST_FUNCTION_ALWAYS_PASS_STENCIL_TEST:
// Nothing to do
break;
case GeCommands.STST_FUNCTION_PASS_TEST_IF_MATCHES:
if (stencilRefMasked != (getAlpha(destinationColor) & renderer.stencilMask)) {
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), stencil test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (stencilOpFail != GeCommands.SOP_KEEP_STENCIL_VALUE) {
destinationColor = stencilOpFail(destinationColor, stencilRefAlpha);
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
case GeCommands.STST_FUNCTION_PASS_TEST_IF_DIFFERS:
if (stencilRefMasked == (getAlpha(destinationColor) & renderer.stencilMask)) {
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), stencil test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (stencilOpFail != GeCommands.SOP_KEEP_STENCIL_VALUE) {
destinationColor = stencilOpFail(destinationColor, stencilRefAlpha);
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
case GeCommands.STST_FUNCTION_PASS_TEST_IF_LESS:
// Pass if the reference value is less than the destination value
if (stencilRefMasked >= (getAlpha(destinationColor) & renderer.stencilMask)) {
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), stencil test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (stencilOpFail != GeCommands.SOP_KEEP_STENCIL_VALUE) {
destinationColor = stencilOpFail(destinationColor, stencilRefAlpha);
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
case GeCommands.STST_FUNCTION_PASS_TEST_IF_LESS_OR_EQUAL:
// Pass if the reference value is less or equal to the destination value
if (stencilRefMasked > (getAlpha(destinationColor) & renderer.stencilMask)) {
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), stencil test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (stencilOpFail != GeCommands.SOP_KEEP_STENCIL_VALUE) {
destinationColor = stencilOpFail(destinationColor, stencilRefAlpha);
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
case GeCommands.STST_FUNCTION_PASS_TEST_IF_GREATER:
// Pass if the reference value is greater than the destination value
if (stencilRefMasked <= (getAlpha(destinationColor) & renderer.stencilMask)) {
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), stencil test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (stencilOpFail != GeCommands.SOP_KEEP_STENCIL_VALUE) {
destinationColor = stencilOpFail(destinationColor, stencilRefAlpha);
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
case GeCommands.STST_FUNCTION_PASS_TEST_IF_GREATER_OR_EQUAL:
// Pass if the reference value is greater or equal to the destination value
if (stencilRefMasked < (getAlpha(destinationColor) & renderer.stencilMask)) {
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), stencil test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (stencilOpFail != GeCommands.SOP_KEEP_STENCIL_VALUE) {
destinationColor = stencilOpFail(destinationColor, stencilRefAlpha);
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
}
}
//
// DepthTest (performed as soon as depths are known, but after the stencil test)
//
if (depthTestFlagEnabled && !clearMode) {
switch (depthFunc) {
case GeCommands.ZTST_FUNCTION_NEVER_PASS_PIXEL:
if (stencilTestFlagEnabled && stencilOpZFail != GeCommands.SOP_KEEP_STENCIL_VALUE) {
destinationColor = stencilOpZFail(destinationColor, stencilRefAlpha);
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
case GeCommands.ZTST_FUNCTION_ALWAYS_PASS_PIXEL:
// No filter required
break;
case GeCommands.ZTST_FUNCTION_PASS_PX_WHEN_DEPTH_IS_EQUAL:
if (sourceDepth != destinationDepth) {
if (stencilTestFlagEnabled && stencilOpZFail != GeCommands.SOP_KEEP_STENCIL_VALUE) {
destinationColor = stencilOpZFail(destinationColor, stencilRefAlpha);
}
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), depth test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
case GeCommands.ZTST_FUNCTION_PASS_PX_WHEN_DEPTH_ISNOT_EQUAL:
if (sourceDepth == destinationDepth) {
if (stencilTestFlagEnabled && stencilOpZFail != GeCommands.SOP_KEEP_STENCIL_VALUE) {
destinationColor = stencilOpZFail(destinationColor, stencilRefAlpha);
}
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), depth test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
case GeCommands.ZTST_FUNCTION_PASS_PX_WHEN_DEPTH_IS_LESS:
if (sourceDepth >= destinationDepth) {
if (stencilTestFlagEnabled && stencilOpZFail != GeCommands.SOP_KEEP_STENCIL_VALUE) {
destinationColor = stencilOpZFail(destinationColor, stencilRefAlpha);
}
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), depth test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
case GeCommands.ZTST_FUNCTION_PASS_PX_WHEN_DEPTH_IS_LESS_OR_EQUAL:
if (sourceDepth > destinationDepth) {
if (stencilTestFlagEnabled && stencilOpZFail != GeCommands.SOP_KEEP_STENCIL_VALUE) {
destinationColor = stencilOpZFail(destinationColor, stencilRefAlpha);
}
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), depth test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
case GeCommands.ZTST_FUNCTION_PASS_PX_WHEN_DEPTH_IS_GREATER:
if (sourceDepth <= destinationDepth) {
if (stencilTestFlagEnabled && stencilOpZFail != GeCommands.SOP_KEEP_STENCIL_VALUE) {
destinationColor = stencilOpZFail(destinationColor, stencilRefAlpha);
}
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), depth test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
case GeCommands.ZTST_FUNCTION_PASS_PX_WHEN_DEPTH_IS_GREATER_OR_EQUAL:
if (sourceDepth < destinationDepth) {
if (stencilTestFlagEnabled && stencilOpZFail != GeCommands.SOP_KEEP_STENCIL_VALUE) {
destinationColor = stencilOpZFail(destinationColor, stencilRefAlpha);
}
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), depth test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
}
}
//
// Primary color
//
if (setVertexPrimaryColor) {
if (isTriangle) {
if (sameVertexColor) {
primaryColor = pixel.c3;
} else {
primaryColor = pixel.getTriangleColorWeightedValue();
}
}
}
//
// Material Flags
//
if (lightingFlagEnabled && !transform2D && useVertexColor && isTriangle) {
if (matFlagAmbient) {
pixel.materialAmbient = primaryColor;
}
if (matFlagDiffuse) {
pixel.materialDiffuse = primaryColor;
}
if (matFlagSpecular) {
pixel.materialSpecular = primaryColor;
}
}
//
// Lighting
//
if (lightingFlagEnabled && !transform2D) {
lighting.applyLighting(colors, pixel);
primaryColor = colors.primaryColor;
secondaryColor = colors.secondaryColor;
}
//
// Pixel texture U,V
//
if (needTextureUV) {
if (simpleTextureUV) {
pixelU = u;
pixelV = v;
} else {
// Compute the mapped texture u,v coordinates
// based on the Barycentric coordinates.
pixelU = pixel.getTriangleWeightedValue(t1uw, t2uw, t3uw);
pixelV = pixel.getTriangleWeightedValue(t1vw, t2vw, t3vw);
if (!transform2D) {
// In 3D, apply a perspective correction by weighting
// the coordinates by their "w" value. See
// http://en.wikipedia.org/wiki/Texture_mapping#Perspective_correctness
float weightInverted = 1.f / pixel.getTriangleWeightedValue(prim.p1wInverted, prim.p2wInverted, prim.p3wInverted);
pixelU *= weightInverted;
pixelV *= weightInverted;
}
}
}
//
// Texture
//
if (textureFlagEnabled && (!transform2D || useVertexTexture) && !clearMode) {
//
// TextureMapping
//
if (!transform2D) {
switch (texMapMode) {
case GeCommands.TMAP_TEXTURE_MAP_MODE_TEXTURE_COORDIATES_UV:
if (texScaleX != 1f) {
pixelU *= renderer.texScaleX;
}
if (texTranslateX != 0f) {
pixelU += renderer.texTranslateX;
}
if (texScaleY != 1f) {
pixelV *= renderer.texScaleY;
}
if (texTranslateY != 0f) {
pixelV += renderer.texTranslateY;
}
break;
case GeCommands.TMAP_TEXTURE_MAP_MODE_TEXTURE_MATRIX:
switch (texProjMapMode) {
case GeCommands.TMAP_TEXTURE_PROJECTION_MODE_POSITION:
final float[] V = pixel.getV();
pixelU = V[0] * pixel.textureMatrix[0] + V[1] * pixel.textureMatrix[4] + V[2] * pixel.textureMatrix[8] + pixel.textureMatrix[12];
pixelV = V[0] * pixel.textureMatrix[1] + V[1] * pixel.textureMatrix[5] + V[2] * pixel.textureMatrix[9] + pixel.textureMatrix[13];
//pixelQ = V[0] * pixel.textureMatrix[2] + V[1] * pixel.textureMatrix[6] + V[2] * pixel.textureMatrix[10] + pixel.textureMatrix[14];
break;
case GeCommands.TMAP_TEXTURE_PROJECTION_MODE_TEXTURE_COORDINATES:
float tu = pixelU;
float tv = pixelV;
pixelU = tu * pixel.textureMatrix[0] + tv * pixel.textureMatrix[4] + pixel.textureMatrix[12];
pixelV = tu * pixel.textureMatrix[1] + tv * pixel.textureMatrix[5] + pixel.textureMatrix[13];
//pixelQ = tu * pixel.textureMatrix[2] + tv * pixel.textureMatrix[6] + pixel.textureMatrix[14];
break;
case GeCommands.TMAP_TEXTURE_PROJECTION_MODE_NORMALIZED_NORMAL:
final float[] normalizedN = pixel.getNormalizedN();
pixelU = normalizedN[0] * pixel.textureMatrix[0] + normalizedN[1] * pixel.textureMatrix[4] + normalizedN[2] * pixel.textureMatrix[8] + pixel.textureMatrix[12];
pixelV = normalizedN[0] * pixel.textureMatrix[1] + normalizedN[1] * pixel.textureMatrix[5] + normalizedN[2] * pixel.textureMatrix[9] + pixel.textureMatrix[13];
//pixelQ = normalizedN[0] * pixel.textureMatrix[2] + normalizedN[1] * pixel.textureMatrix[6] + normalizedN[2] * pixel.textureMatrix[10] + pixel.textureMatrix[14];
break;
case GeCommands.TMAP_TEXTURE_PROJECTION_MODE_NORMAL:
final float[] N = pixel.getN();
pixelU = N[0] * pixel.textureMatrix[0] + N[1] * pixel.textureMatrix[4] + N[2] * pixel.textureMatrix[8] + pixel.textureMatrix[12];
pixelV = N[0] * pixel.textureMatrix[1] + N[1] * pixel.textureMatrix[5] + N[2] * pixel.textureMatrix[9] + pixel.textureMatrix[13];
//pixelQ = N[0] * pixel.textureMatrix[2] + N[1] * pixel.textureMatrix[6] + N[2] * pixel.textureMatrix[10] + pixel.textureMatrix[14];
break;
}
break;
case GeCommands.TMAP_TEXTURE_MAP_MODE_ENVIRONMENT_MAP:
// Implementation based on shader.vert/ApplyTexture:
//
// vec3 Nn = normalize(N);
// vec3 Ve = vec3(gl_ModelViewMatrix * V);
// float k = gl_FrontMaterial.shininess;
// vec3 Lu = gl_LightSource[texShade.x].position.xyz - Ve.xyz * gl_LightSource[texShade.x].position.w;
// vec3 Lv = gl_LightSource[texShade.y].position.xyz - Ve.xyz * gl_LightSource[texShade.y].position.w;
// float Pu = psp_lightKind[texShade.x] == 0 ? dot(Nn, normalize(Lu)) : pow(dot(Nn, normalize(Lu + vec3(0.0, 0.0, 1.0))), k);
// float Pv = psp_lightKind[texShade.y] == 0 ? dot(Nn, normalize(Lv)) : pow(dot(Nn, normalize(Lv + vec3(0.0, 0.0, 1.0))), k);
// T.xyz = vec3(0.5*vec2(1.0 + Pu, 1.0 + Pv), 1.0);
//
final float[] Ve = new float[3];
final float[] Ne = new float[3];
final float[] Lu = new float[3];
final float[] Lv = new float[3];
pixel.getVe(Ve);
pixel.getNormalizedNe(Ne);
for (int i = 0; i < 3; i++) {
Lu[i] = renderer.envMapLightPosU[i] - Ve[i] * renderer.envMapLightPosU[3];
Lv[i] = renderer.envMapLightPosV[i] - Ve[i] * renderer.envMapLightPosV[3];
}
float Pu;
if (renderer.envMapDiffuseLightU) {
normalize3(Lu, Lu);
Pu = dot3(Ne, Lu);
} else {
Lu[2] += 1f;
normalize3(Lu, Lu);
Pu = (float) Math.pow(dot3(Ne, Lu), renderer.envMapShininess);
}
float Pv;
if (renderer.envMapDiffuseLightV) {
normalize3(Lv, Lv);
Pv = dot3(Ne, Lv);
} else {
Lv[2] += 1f;
normalize3(Lv, Lv);
Pv = (float) Math.pow(dot3(Ne, Lv), renderer.envMapShininess);
}
pixelU = (Pu + 1f) * 0.5f;
pixelV = (Pv + 1f) * 0.5f;
//pixelQ = 1f;
break;
}
}
//
// Texture resampling (as late as possible)
//
if (texMagFilter == GeCommands.TFLT_LINEAR) {
if (needResample) {
// Perform the resampling as late as possible.
// We might be lucky that all the pixels were eliminated
// by the depth or stencil tests. In which case,
// we don't need to resample.
textureAccess = renderer.cachedTexture.resample(prim.resampleFactorWidth, prim.resampleFactorHeight);
textureWidthMask = textureAccess.getWidth() - 1;
textureHeightMask = textureAccess.getHeight() - 1;
textureWidthFloat = textureAccess.getWidth();
textureHeightFloat = textureAccess.getHeight();
needResample = false;
}
}
//
// TextureWrap
//
if (needTextureWrapU) {
switch (texWrapS) {
case GeCommands.TWRAP_WRAP_MODE_REPEAT:
if (transform2D) {
pixelU = wrap(pixelU, textureWidthMask);
} else {
pixelU = wrap(pixelU);
}
break;
case GeCommands.TWRAP_WRAP_MODE_CLAMP:
if (transform2D) {
pixelU = clamp(pixelU, 0f, textureWidthMask);
} else {
// Clamp to [0..1[ (1 is excluded)
pixelU = clamp(pixelU, 0f, 0.99999f);
}
break;
}
}
if (needTextureWrapV) {
switch (texWrapT) {
case GeCommands.TWRAP_WRAP_MODE_REPEAT:
if (transform2D) {
pixelV = wrap(pixelV, textureHeightMask);
} else {
pixelV = wrap(pixelV);
}
break;
case GeCommands.TWRAP_WRAP_MODE_CLAMP:
if (transform2D) {
pixelV = clamp(pixelV, 0f, textureHeightMask);
} else {
// Clamp to [0..1[ (1 is excluded)
pixelV = clamp(pixelV, 0f, 0.99999f);
}
break;
}
}
//
// TextureReader
//
if (transform2D) {
sourceColor = textureAccess.readPixel(pixelToTexel(pixelU), pixelToTexel(pixelV));
} else {
sourceColor = textureAccess.readPixel(pixelToTexel(pixelU * textureWidthFloat), pixelToTexel(pixelV * textureHeightFloat));
}
//
// TextureFunction
//
switch (textureFunc) {
case GeCommands.TFUNC_FRAGMENT_DOUBLE_TEXTURE_EFECT_MODULATE:
if (textureAlphaUsed) {
sourceColor = multiply(sourceColor, primaryColor);
} else {
sourceColor = multiply(sourceColor | 0xFF000000, primaryColor);
}
break;
case GeCommands.TFUNC_FRAGMENT_DOUBLE_TEXTURE_EFECT_DECAL:
if (textureAlphaUsed) {
alpha = getAlpha(sourceColor);
a = getAlpha(primaryColor);
b = combineComponent(getBlue(primaryColor), getBlue(sourceColor), alpha);
g = combineComponent(getGreen(primaryColor), getGreen(sourceColor), alpha);
r = combineComponent(getRed(primaryColor), getRed(sourceColor), alpha);
sourceColor = getColor(a, b, g, r);
} else {
sourceColor = (sourceColor & 0x00FFFFFF) | (primaryColor & 0xFF000000);
}
break;
case GeCommands.TFUNC_FRAGMENT_DOUBLE_TEXTURE_EFECT_BLEND:
if (textureAlphaUsed) {
a = multiplyComponent(getAlpha(sourceColor), getAlpha(primaryColor));
b = combineComponent(getBlue(primaryColor), renderer.texEnvColorB, getBlue(sourceColor));
g = combineComponent(getGreen(primaryColor), renderer.texEnvColorG, getGreen(sourceColor));
r = combineComponent(getRed(primaryColor), renderer.texEnvColorR, getRed(sourceColor));
sourceColor = getColor(a, b, g, r);
} else {
a = getAlpha(primaryColor);
b = combineComponent(getBlue(primaryColor), renderer.texEnvColorB, getBlue(sourceColor));
g = combineComponent(getGreen(primaryColor), renderer.texEnvColorG, getGreen(sourceColor));
r = combineComponent(getRed(primaryColor), renderer.texEnvColorR, getRed(sourceColor));
sourceColor = getColor(a, b, g, r);
}
break;
case GeCommands.TFUNC_FRAGMENT_DOUBLE_TEXTURE_EFECT_REPLACE:
if (!textureAlphaUsed) {
sourceColor = (sourceColor & 0x00FFFFFF) | (primaryColor & 0xFF000000);
}
break;
case GeCommands.TFUNC_FRAGMENT_DOUBLE_TEXTURE_EFECT_ADD:
if (textureAlphaUsed) {
a = multiplyComponent(getAlpha(sourceColor), getAlpha(primaryColor));
sourceColor = setAlpha(addBGR(sourceColor, primaryColor), a);
} else {
sourceColor = add(sourceColor & 0x00FFFFFF, primaryColor);
}
break;
}
//
// ColorDoubling
//
if (textureColorDoubled) {
if (!transform2D && lightingFlagEnabled && lightMode == LMODE_SEPARATE_SPECULAR_COLOR) {
sourceColor = doubleColor(sourceColor);
secondaryColor = doubleColor(secondaryColor);
} else {
sourceColor = doubleColor(sourceColor);
}
}
//
// SourceColor
//
if (!transform2D && lightingFlagEnabled && lightMode == LMODE_SEPARATE_SPECULAR_COLOR) {
sourceColor = add(sourceColor, secondaryColor);
}
} else {
//
// ColorDoubling
//
if (textureColorDoubled) {
if (!transform2D && lightingFlagEnabled && lightMode == LMODE_SEPARATE_SPECULAR_COLOR) {
primaryColor = doubleColor(primaryColor);
secondaryColor = doubleColor(secondaryColor);
} else if (!primaryColorSetGlobally) {
primaryColor = doubleColor(primaryColor);
}
}
//
// SourceColor
//
if (!transform2D && lightingFlagEnabled && lightMode == LMODE_SEPARATE_SPECULAR_COLOR) {
sourceColor = add(primaryColor, secondaryColor);
} else {
sourceColor = primaryColor;
}
}
//
// ColorTest
//
if (colorTestFlagEnabled && !clearMode) {
switch (colorTestFunc) {
case GeCommands.CTST_COLOR_FUNCTION_ALWAYS_PASS_PIXEL:
// Nothing to do
break;
case GeCommands.CTST_COLOR_FUNCTION_NEVER_PASS_PIXEL:
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
case GeCommands.CTST_COLOR_FUNCTION_PASS_PIXEL_IF_COLOR_MATCHES:
if ((sourceColor & renderer.colorTestMsk) != renderer.colorTestRef) {
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
case GeCommands.CTST_COLOR_FUNCTION_PASS_PIXEL_IF_COLOR_DIFFERS:
if ((sourceColor & renderer.colorTestMsk) == renderer.colorTestRef) {
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
}
}
//
// AlphaTest
//
if (alphaTestFlagEnabled && !clearMode) {
switch (alphaFunc) {
case GeCommands.ATST_ALWAYS_PASS_PIXEL:
// Nothing to do
break;
case GeCommands.ATST_NEVER_PASS_PIXEL:
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
case GeCommands.ATST_PASS_PIXEL_IF_MATCHES:
if ((getAlpha(sourceColor) & alphaMask) != alphaRef) {
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), alpha test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
case GeCommands.ATST_PASS_PIXEL_IF_DIFFERS:
if ((getAlpha(sourceColor) & alphaMask) == alphaRef) {
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), alpha test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
case GeCommands.ATST_PASS_PIXEL_IF_LESS:
if ((getAlpha(sourceColor) & alphaMask) >= alphaRef) {
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), alpha test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
case GeCommands.ATST_PASS_PIXEL_IF_LESS_OR_EQUAL:
// No test if alphaRef==0xFF
if (RendererTemplate.alphaRef < 0xFF) {
if ((getAlpha(sourceColor) & alphaMask) > alphaRef) {
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), alpha test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
}
break;
case GeCommands.ATST_PASS_PIXEL_IF_GREATER:
if ((getAlpha(sourceColor) & alphaMask) <= alphaRef) {
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), alpha test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
break;
case GeCommands.ATST_PASS_PIXEL_IF_GREATER_OR_EQUAL:
// No test if alphaRef==0x00
if (RendererTemplate.alphaRef > 0x00) {
if ((getAlpha(sourceColor) & alphaMask) < alphaRef) {
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), alpha test failed, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex++;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(1, 1);
}
continue;
}
}
break;
}
}
//
// AlphaBlend
//
if (blendFlagEnabled && !clearMode) {
int filteredSrc;
int filteredDst;
switch (blendEquation) {
case GeCommands.ALPHA_SOURCE_BLEND_OPERATION_ADD:
if (blendSrc == GeCommands.ALPHA_FIX && sfix == 0xFFFFFF &&
blendDst == GeCommands.ALPHA_FIX && dfix == 0x000000) {
// Nothing to do, this is a NOP
} else if (blendSrc == GeCommands.ALPHA_FIX && sfix == 0xFFFFFF &&
blendDst == GeCommands.ALPHA_FIX && dfix == 0xFFFFFF) {
sourceColor = PixelColor.add(sourceColor, destinationColor & 0x00FFFFFF);
} else if (blendSrc == GeCommands.ALPHA_SOURCE_ALPHA && blendDst == GeCommands.ALPHA_ONE_MINUS_SOURCE_ALPHA) {
// This is the most common case and can be optimized
int srcAlpha = sourceColor >>> 24;
if (srcAlpha == ZERO) {
// Set color of destination
sourceColor = (sourceColor & 0xFF000000) | (destinationColor & 0x00FFFFFF);
} else if (srcAlpha == ONE) {
// Nothing to change
} else {
int oneMinusSrcAlpha = ONE - srcAlpha;
filteredSrc = multiplyBGR(sourceColor, srcAlpha, srcAlpha, srcAlpha);
filteredDst = multiplyBGR(destinationColor, oneMinusSrcAlpha, oneMinusSrcAlpha, oneMinusSrcAlpha);
sourceColor = setBGR(sourceColor, addBGR(filteredSrc, filteredDst));
}
} else {
filteredSrc = multiplyBGR(sourceColor, blendSrc(sourceColor, destinationColor, renderer.sfix));
filteredDst = multiplyBGR(destinationColor, blendDst(sourceColor, destinationColor, renderer.dfix));
sourceColor = setBGR(sourceColor, addBGR(filteredSrc, filteredDst));
}
break;
case GeCommands.ALPHA_SOURCE_BLEND_OPERATION_SUBTRACT:
filteredSrc = multiplyBGR(sourceColor, blendSrc(sourceColor, destinationColor, renderer.sfix));
filteredDst = multiplyBGR(destinationColor, blendDst(sourceColor, destinationColor, renderer.dfix));
sourceColor = setBGR(sourceColor, substractBGR(filteredSrc, filteredDst));
break;
case GeCommands.ALPHA_SOURCE_BLEND_OPERATION_REVERSE_SUBTRACT:
filteredSrc = multiplyBGR(sourceColor, blendSrc(sourceColor, destinationColor, renderer.sfix));
filteredDst = multiplyBGR(destinationColor, blendDst(sourceColor, destinationColor, renderer.dfix));
sourceColor = setBGR(sourceColor, substractBGR(filteredDst, filteredSrc));
break;
case GeCommands.ALPHA_SOURCE_BLEND_OPERATION_MINIMUM_VALUE:
// Source and destination factors are not applied
sourceColor = setBGR(sourceColor, minBGR(sourceColor, destinationColor));
break;
case GeCommands.ALPHA_SOURCE_BLEND_OPERATION_MAXIMUM_VALUE:
// Source and destination factors are not applied
sourceColor = setBGR(sourceColor, maxBGR(sourceColor, destinationColor));
break;
case GeCommands.ALPHA_SOURCE_BLEND_OPERATION_ABSOLUTE_VALUE:
// Source and destination factors are not applied
sourceColor = setBGR(sourceColor, absBGR(sourceColor, destinationColor));
break;
}
}
if (ditherFlagEnabled) {
int ditherValue = renderer.ditherMatrix[((y & 0x3) << 2) + (x & 0x3)];
if (ditherValue > 0) {
b = addComponent(getBlue(sourceColor), ditherValue);
g = addComponent(getGreen(sourceColor), ditherValue);
r = addComponent(getRed(sourceColor), ditherValue);
sourceColor = setBGR(sourceColor, getColorBGR(b, g, r));
} else if (ditherValue < 0) {
ditherValue = -ditherValue;
b = substractComponent(getBlue(sourceColor), ditherValue);
g = substractComponent(getGreen(sourceColor), ditherValue);
r = substractComponent(getRed(sourceColor), ditherValue);
sourceColor = setBGR(sourceColor, getColorBGR(b, g, r));
}
}
//
// StencilOpZPass
//
if (stencilTestFlagEnabled && !clearMode) {
switch (stencilOpZPass) {
case GeCommands.SOP_KEEP_STENCIL_VALUE:
sourceColor = (sourceColor & 0x00FFFFFF) | (destinationColor & 0xFF000000);
break;
case GeCommands.SOP_ZERO_STENCIL_VALUE:
sourceColor &= 0x00FFFFFF;
break;
case GeCommands.SOP_REPLACE_STENCIL_VALUE:
if (stencilRef == 0) {
// SOP_REPLACE_STENCIL_VALUE with a 0 value is equivalent
// to SOP_ZERO_STENCIL_VALUE
sourceColor &= 0x00FFFFFF;
} else {
sourceColor = (sourceColor & 0x00FFFFFF) | stencilRefAlpha;
}
break;
case GeCommands.SOP_INVERT_STENCIL_VALUE:
sourceColor = (sourceColor & 0x00FFFFFF) | ((~destinationColor) & 0xFF000000);
break;
case GeCommands.SOP_INCREMENT_STENCIL_VALUE:
alpha = destinationColor & 0xFF000000;
if (alpha != 0xFF000000) {
alpha += 0x01000000;
}
sourceColor = (sourceColor & 0x00FFFFFF) | alpha;
break;
case GeCommands.SOP_DECREMENT_STENCIL_VALUE:
alpha = destinationColor & 0xFF000000;
if (alpha != 0x00000000) {
alpha -= 0x01000000;
}
sourceColor = (sourceColor & 0x00FFFFFF) | alpha;
break;
}
} else if (!clearMode) {
// Write the alpha/stencil value to the frame buffer
// only when the stencil test is enabled
sourceColor = (sourceColor & 0x00FFFFFF) | (destinationColor & 0xFF000000);
}
//
// ColorLogicalOperation
//
if (colorLogicOpFlagEnabled && !clearMode) {
switch (logicOp) {
case GeCommands.LOP_CLEAR:
sourceColor = ZERO;
break;
case GeCommands.LOP_AND:
sourceColor &= destinationColor;
break;
case GeCommands.LOP_REVERSE_AND:
sourceColor &= (~destinationColor);
break;
case GeCommands.LOP_COPY:
// This is a NOP
break;
case GeCommands.LOP_INVERTED_AND:
sourceColor = (~sourceColor) & destinationColor;
break;
case GeCommands.LOP_NO_OPERATION:
sourceColor = destinationColor;
break;
case GeCommands.LOP_EXLUSIVE_OR:
sourceColor ^= destinationColor;
break;
case GeCommands.LOP_OR:
sourceColor |= destinationColor;
break;
case GeCommands.LOP_NEGATED_OR:
sourceColor = ~(sourceColor | destinationColor);
break;
case GeCommands.LOP_EQUIVALENCE:
sourceColor = ~(sourceColor ^ destinationColor);
break;
case GeCommands.LOP_INVERTED:
sourceColor = ~destinationColor;
break;
case GeCommands.LOP_REVERSE_OR:
sourceColor |= (~destinationColor);
break;
case GeCommands.LOP_INVERTED_COPY:
sourceColor = ~sourceColor;
break;
case GeCommands.LOP_INVERTED_OR:
sourceColor = (~sourceColor) | destinationColor;
break;
case GeCommands.LOP_NEGATED_AND:
sourceColor = ~(sourceColor & destinationColor);
break;
case GeCommands.LOP_SET:
sourceColor = 0xFFFFFFFF;
break;
}
}
//
// ColorMask
//
if (clearMode) {
if (clearModeColor) {
if (!clearModeStencil) {
sourceColor = (sourceColor & 0x00FFFFFF) | (destinationColor & 0xFF000000);
}
} else {
if (clearModeStencil) {
sourceColor = (sourceColor & 0xFF000000) | (destinationColor & 0x00FFFFFF);
} else {
sourceColor = destinationColor;
}
}
} else {
if (colorMask != 0x00000000) {
sourceColor = (sourceColor & notColorMask) | (destinationColor & renderer.colorMask);
}
}
//
// DepthMask
//
if (needDepthWrite) {
if (clearMode) {
if (!clearModeDepth) {
sourceDepth = destinationDepth;
}
} else if (!depthTestFlagEnabled) {
// Depth writes are disabled when the depth test is not enabled.
sourceDepth = destinationDepth;
} else if (!depthMask) {
sourceDepth = destinationDepth;
}
}
//
// Filter passed
//
if (isLogTraceEnabled) {
VideoEngine.log.trace(String.format("Pixel (%d,%d), passed=true, tex (%f, %f), source=0x%08X, dest=0x%08X, prim=0x%08X, sec=0x%08X, sourceDepth=%d, destDepth=%d", x, y, pixelU, pixelV, sourceColor, destinationColor, primaryColor, secondaryColor, sourceDepth, destinationDepth));
}
if (needDepthWrite && needSourceDepthClamp) {
// Clamp between 0 and 65535
sourceDepth = Math.max(0, Math.min(sourceDepth, 65535));
}
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
memInt[fbIndex] = sourceColor;
fbIndex++;
if (needDepthWrite) {
if (depthOffset == 0) {
memInt[depthIndex] = (memInt[depthIndex] & 0xFFFF0000) | (sourceDepth & 0x0000FFFF);
depthOffset = 1;
} else {
memInt[depthIndex] = (memInt[depthIndex] & 0x0000FFFF) | (sourceDepth << 16);
depthIndex++;
depthOffset = 0;
}
} else if (needDestinationDepthRead) {
depthOffset++;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
if (needDepthWrite) {
colorDepth.color = sourceColor;
colorDepth.depth = sourceDepth;
rendererWriter.writeNext(colorDepth);
} else {
rendererWriter.writeNextColor(sourceColor);
}
}
} while (false);
if (needTextureUV && simpleTextureUV) {
if (swapTextureUV) {
v += prim.vStep;
} else {
u += prim.uStep;
}
}
if (isTriangle) {
prim.deltaXTriangleWeigths(pixel);
}
}
int skip = prim.pxMax - endX;
if (hasMemInt && psm == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
fbIndex += skip + renderer.imageWriterSkipEOL;
if (needDepthWrite || needDestinationDepthRead) {
depthOffset += skip + renderer.depthWriterSkipEOL;
depthIndex += depthOffset >> 1;
depthOffset &= 1;
}
} else {
rendererWriter.skip(skip + renderer.imageWriterSkipEOL, skip + renderer.depthWriterSkipEOL);
}
}
if (needTextureUV && simpleTextureUV) {
if (swapTextureUV) {
u += prim.uStep;
} else {
v += prim.vStep;
}
}
}
doRenderEnd(renderer);
}
protected static int stencilOpFail(int destination, int stencilRefAlpha) {
int alpha;
switch (stencilOpFail) {
case GeCommands.SOP_KEEP_STENCIL_VALUE:
return destination;
case GeCommands.SOP_ZERO_STENCIL_VALUE:
return destination & 0x00FFFFFF;
case GeCommands.SOP_REPLACE_STENCIL_VALUE:
if (stencilRef == 0) {
// SOP_REPLACE_STENCIL_VALUE with a 0 value is equivalent
// to SOP_ZERO_STENCIL_VALUE
return destination & 0x00FFFFFF;
}
return (destination & 0x00FFFFFF) | stencilRefAlpha;
case GeCommands.SOP_INVERT_STENCIL_VALUE:
return destination ^ 0xFF000000;
case GeCommands.SOP_INCREMENT_STENCIL_VALUE:
alpha = destination & 0xFF000000;
if (alpha != 0xFF000000) {
alpha += 0x01000000;
}
return (destination & 0x00FFFFFF) | alpha;
case GeCommands.SOP_DECREMENT_STENCIL_VALUE:
alpha = destination & 0xFF000000;
if (alpha != 0x00000000) {
alpha -= 0x01000000;
}
return (destination & 0x00FFFFFF) | alpha;
}
return destination;
}
protected static int stencilOpZFail(int destination, int stencilRefAlpha) {
int alpha;
switch (stencilOpZFail) {
case GeCommands.SOP_KEEP_STENCIL_VALUE:
return destination;
case GeCommands.SOP_ZERO_STENCIL_VALUE:
return destination & 0x00FFFFFF;
case GeCommands.SOP_REPLACE_STENCIL_VALUE:
if (stencilRef == 0) {
// SOP_REPLACE_STENCIL_VALUE with a 0 value is equivalent
// to SOP_ZERO_STENCIL_VALUE
return destination & 0x00FFFFFF;
}
return (destination & 0x00FFFFFF) | stencilRefAlpha;
case GeCommands.SOP_INVERT_STENCIL_VALUE:
return destination ^ 0xFF000000;
case GeCommands.SOP_INCREMENT_STENCIL_VALUE:
alpha = destination & 0xFF000000;
if (alpha != 0xFF000000) {
alpha += 0x01000000;
}
return (destination & 0x00FFFFFF) | alpha;
case GeCommands.SOP_DECREMENT_STENCIL_VALUE:
alpha = destination & 0xFF000000;
if (alpha != 0x00000000) {
alpha -= 0x01000000;
}
return (destination & 0x00FFFFFF) | alpha;
}
return destination;
}
protected static int blendSrc(int source, int destination, int fix) {
int alpha;
switch (blendSrc) {
case GeCommands.ALPHA_SOURCE_COLOR:
return source;
case GeCommands.ALPHA_ONE_MINUS_SOURCE_COLOR:
return 0xFFFFFFFF - source;
case GeCommands.ALPHA_SOURCE_ALPHA:
alpha = getAlpha(source);
return getColorBGR(alpha, alpha, alpha);
case GeCommands.ALPHA_ONE_MINUS_SOURCE_ALPHA:
alpha = ONE - getAlpha(source);
return getColorBGR(alpha, alpha, alpha);
case GeCommands.ALPHA_DESTINATION_ALPHA:
alpha = getAlpha(destination);
return getColorBGR(alpha, alpha, alpha);
case GeCommands.ALPHA_ONE_MINUS_DESTINATION_ALPHA:
alpha = ONE - getAlpha(destination);
return getColorBGR(alpha, alpha, alpha);
case GeCommands.ALPHA_DOUBLE_SOURCE_ALPHA:
alpha = doubleComponent(getAlpha(source));
return getColorBGR(alpha, alpha, alpha);
case GeCommands.ALPHA_ONE_MINUS_DOUBLE_SOURCE_ALPHA:
alpha = ONE - doubleComponent(getAlpha(source));
return getColorBGR(alpha, alpha, alpha);
case GeCommands.ALPHA_DOUBLE_DESTINATION_ALPHA:
alpha = doubleComponent(getAlpha(destination));
return getColorBGR(alpha, alpha, alpha);
case GeCommands.ALPHA_ONE_MINUS_DOUBLE_DESTINATION_ALPHA:
alpha = ONE - doubleComponent(getAlpha(destination));
return getColorBGR(alpha, alpha, alpha);
case GeCommands.ALPHA_FIX:
return fix;
}
return source;
}
protected static int blendDst(int source, int destination, int fix) {
int alpha;
switch (blendDst) {
case GeCommands.ALPHA_DESTINATION_COLOR:
return destination;
case GeCommands.ALPHA_ONE_MINUS_DESTINATION_COLOR:
return 0xFFFFFFFF - destination;
case GeCommands.ALPHA_SOURCE_ALPHA:
alpha = getAlpha(source);
return getColorBGR(alpha, alpha, alpha);
case GeCommands.ALPHA_ONE_MINUS_SOURCE_ALPHA:
alpha = ONE - getAlpha(source);
return getColorBGR(alpha, alpha, alpha);
case GeCommands.ALPHA_DESTINATION_ALPHA:
alpha = getAlpha(destination);
return getColorBGR(alpha, alpha, alpha);
case GeCommands.ALPHA_ONE_MINUS_DESTINATION_ALPHA:
alpha = ONE - getAlpha(destination);
return getColorBGR(alpha, alpha, alpha);
case GeCommands.ALPHA_DOUBLE_SOURCE_ALPHA:
alpha = doubleComponent(getAlpha(source));
return getColorBGR(alpha, alpha, alpha);
case GeCommands.ALPHA_ONE_MINUS_DOUBLE_SOURCE_ALPHA:
alpha = ONE - doubleComponent(getAlpha(source));
return getColorBGR(alpha, alpha, alpha);
case GeCommands.ALPHA_DOUBLE_DESTINATION_ALPHA:
alpha = doubleComponent(getAlpha(destination));
return getColorBGR(alpha, alpha, alpha);
case GeCommands.ALPHA_ONE_MINUS_DOUBLE_DESTINATION_ALPHA:
alpha = ONE - doubleComponent(getAlpha(destination));
return getColorBGR(alpha, alpha, alpha);
case GeCommands.ALPHA_FIX:
return fix;
}
return destination;
}
}