/* 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; import static java.lang.Math.abs; import static jpcsp.graphics.GeCommands.TFLT_NEAREST; import static jpcsp.graphics.GeCommands.TMAP_TEXTURE_MAP_MODE_TEXTURE_COORDIATES_UV; import static jpcsp.graphics.GeCommands.TMAP_TEXTURE_PROJECTION_MODE_TEXTURE_COORDINATES; import static jpcsp.graphics.GeCommands.TWRAP_WRAP_MODE_CLAMP; import static jpcsp.graphics.RE.software.PixelColor.ONE; import static jpcsp.graphics.RE.software.PixelColor.getBlue; 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.util.Utilities.matrixMult; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.IntBuffer; import jpcsp.graphics.GeCommands; import jpcsp.graphics.VideoEngine; import jpcsp.graphics.GeContext.EnableDisableFlag; import jpcsp.settings.Settings; /** * @author gid15 * * Base class the RenderingEngine providing basic functionalities: * - generic clear mode handling: implementation matching the PSP behavior, * but probably not the most efficient implementation. * - merge of View and Model matrix for OpenGL supporting * only combined model-view matrix * - direct rendering mode * - implementation of the bounding box processing * (using OpenGL Query with a partial software implementation) * - mapping of setColorMask(int, int, int, int) to setColorMask(bool, bool, bool, bool) * - bug fix for glMultiDrawArrays * * The partial software implementation for Bounding Boxes tries to find out * if a bounding box is visible or not without using an OpenGL Query. * The OpenGL queries have quite a large overhead to be setup and the software * implementation solves the following bounding box cases: * - if at least one bounding box vertex is visible, * the complete bounding box is visible * - if all the bounding box vertices are not visible and are all placed on the * same side of a frustum plane, then the complete bounding box is not visible. * E.g.: all the vertices are hidden on the left side of the frustum. * * If some vertices are hidden on different sides of the frustum (e.g. one on * the left side and one on the right side), the implementation cannot determine * if some pixels in between are visible or not. A complete intersection test is * necessary in that case. Remark: this could be implemented in software too. * * In all other cases, the OpenGL query has to be used to determine if the bounding * box is visible or not. */ public class BaseRenderingEngineFunction extends BaseRenderingEngineProxy { protected static final boolean useWorkaroundForMultiDrawArrays = true; protected boolean useVertexArray; ClearModeContext clearModeContext = new ClearModeContext(); protected boolean directMode; protected boolean directModeSetOrthoMatrix; protected static final boolean[] flagsValidInClearMode = new boolean[]{ false, // GU_ALPHA_TEST false, // GU_DEPTH_TEST true, // GU_SCISSOR_TEST false, // GU_STENCIL_TEST false, // GU_BLEND false, // GU_CULL_FACE true, // GU_DITHER false, // GU_FOG true, // GU_CLIP_PLANES false, // GU_TEXTURE_2D true, // GU_LIGHTING true, // GU_LIGHT0 true, // GU_LIGHT1 true, // GU_LIGHT2 true, // GU_LIGHT3 true, // GU_LINE_SMOOTH true, // GU_PATCH_CULL_FACE false, // GU_COLOR_TEST false, // GU_COLOR_LOGIC_OP true, // GU_FACE_NORMAL_REVERSE true, // GU_PATCH_FACE true, // GU_FRAGMENT_2X true, // RE_COLOR_MATERIAL true, // RE_TEXTURE_GEN_S true, // RE_TEXTURE_GEN_T }; protected static class ClearModeContext { public boolean color; public boolean stencil; public boolean depth; public int alphaFunc; public int depthFunc; public int textureFunc; public boolean textureAlphaUsed; public boolean textureColorDoubled; public int stencilFunc; public int stencilRef; public int stencilMask; public int stencilOpFail; public int stencilOpZFail; public int stencilOpZPass; }; protected boolean viewMatrixLoaded = false; protected boolean modelMatrixLoaded = false; protected boolean bboxVisible; protected int activeTextureUnit = 0; private boolean[] colorMask = new boolean[4]; private static ByteBuffer emptyBuffer; protected int logicOp = -1; public BaseRenderingEngineFunction(IRenderingEngine proxy) { super(proxy); init(); } protected void init() { useVertexArray = Settings.getInstance().readBool("emu.enablevao") && super.isVertexArrayAvailable(); if (useVertexArray) { log.info("Using VAO (Vertex Array Object)"); } } /** * Allocate an empty direct buffer of at least the given capacity. * * @param capacity the minimum required buffer capacity * @return an empty direct buffer of the given capacity */ private static ByteBuffer getEmptyBuffer(int capacity) { if (emptyBuffer == null || emptyBuffer.capacity() < capacity) { emptyBuffer = ByteBuffer.allocateDirect(capacity); } emptyBuffer.clear(); return emptyBuffer; } @Override public void setRenderingEngine(IRenderingEngine re) { getBufferManager().setRenderingEngine(re); super.setRenderingEngine(re); } protected void setFlag(EnableDisableFlag flag) { if (flag.isEnabled()) { re.enableFlag(flag.getReFlag()); } else { re.disableFlag(flag.getReFlag()); } } protected void setClearModeSettings(boolean color, boolean stencil, boolean depth) { // Disable all the flags invalid in clear mode for (int i = 0; i < flagsValidInClearMode.length; i++) { if (!flagsValidInClearMode[i]) { re.disableFlag(i); } } if (stencil) { re.enableFlag(GU_STENCIL_TEST); re.setStencilFunc(GeCommands.STST_FUNCTION_ALWAYS_PASS_STENCIL_TEST, 0, 0); re.setStencilOp(GeCommands.SOP_KEEP_STENCIL_VALUE, GeCommands.SOP_KEEP_STENCIL_VALUE, GeCommands.SOP_ZERO_STENCIL_VALUE); } if (depth) { re.enableFlag(GU_DEPTH_TEST); context.depthFunc = GeCommands.ZTST_FUNCTION_ALWAYS_PASS_PIXEL; re.setDepthFunc(context.depthFunc); } // Update color, stencil and depth masks. re.setDepthMask(depth); re.setColorMask(color, color, color, stencil); re.setTextureFunc(GeCommands.TFUNC_FRAGMENT_DOUBLE_TEXTURE_EFECT_REPLACE, true, false); re.setBones(0, null); } @Override public void startClearMode(boolean color, boolean stencil, boolean depth) { // Clear mode flags. clearModeContext.color = color; clearModeContext.stencil = stencil; clearModeContext.depth = depth; // Save depth. clearModeContext.depthFunc = context.depthFunc; // Save texture. clearModeContext.textureFunc = context.textureFunc; clearModeContext.textureAlphaUsed = context.textureAlphaUsed; clearModeContext.textureColorDoubled = context.textureColorDoubled; // Save stencil. clearModeContext.stencilFunc = context.stencilFunc; clearModeContext.stencilRef = context.stencilRef; clearModeContext.stencilMask = context.stencilMask; clearModeContext.stencilOpFail = context.stencilOpFail; clearModeContext.stencilOpZFail = context.stencilOpZFail; clearModeContext.stencilOpZPass = context.stencilOpZPass; setClearModeSettings(color, stencil, depth); super.startClearMode(color, stencil, depth); } @Override public void endClearMode() { // Reset all the flags disabled in CLEAR mode for (EnableDisableFlag flag : context.flags) { if (!flagsValidInClearMode[flag.getReFlag()]) { setFlag(flag); } } // Restore depth. context.depthFunc = clearModeContext.depthFunc; re.setDepthFunc(context.depthFunc); // Restore texture. context.textureFunc = clearModeContext.textureFunc; context.textureAlphaUsed = clearModeContext.textureAlphaUsed; context.textureColorDoubled = clearModeContext.textureColorDoubled; re.setTextureFunc(context.textureFunc, context.textureAlphaUsed, context.textureColorDoubled); // Restore stencil. context.stencilFunc = clearModeContext.stencilFunc; context.stencilRef = clearModeContext.stencilRef; context.stencilMask = clearModeContext.stencilMask; re.setStencilFunc(context.stencilFunc, context.stencilRef, context.stencilRef); context.stencilOpFail = clearModeContext.stencilOpFail; context.stencilOpZFail = clearModeContext.stencilOpZFail; context.stencilOpZPass = clearModeContext.stencilOpZPass; re.setStencilOp(context.stencilOpFail, context.stencilOpZFail, context.stencilOpZPass); re.setDepthMask(context.depthMask); re.setColorMask(true, true, true, context.stencilTestFlag.isEnabled()); re.setColorMask(context.colorMask[0], context.colorMask[1], context.colorMask[2], context.colorMask[3]); super.endClearMode(); } protected boolean isClearMode() { return context.clearMode; } protected boolean canUpdateFlag(int flag) { return !context.clearMode || directMode || flagsValidInClearMode[flag]; } protected boolean canUpdate() { return !context.clearMode || directMode; } protected static boolean getBooleanColorMask(String name, int bitMask) { if (bitMask == 0xFF) { return false; } else if (bitMask != 0x00) { log.warn(String.format("Unimplemented %s 0x%02X", name, bitMask)); } return true; } @Override public void setColorMask(boolean redWriteEnabled, boolean greenWriteEnabled, boolean blueWriteEnabled, boolean alphaWriteEnabled) { colorMask[0] = redWriteEnabled; colorMask[1] = greenWriteEnabled; colorMask[2] = blueWriteEnabled; colorMask[3] = alphaWriteEnabled; super.setColorMask(redWriteEnabled, greenWriteEnabled, blueWriteEnabled, alphaWriteEnabled); } @Override public void setColorMask(int redMask, int greenMask, int blueMask, int alphaMask) { boolean redWriteEnabled = getBooleanColorMask("Red color mask", redMask); boolean greenWriteEnabled = getBooleanColorMask("Green color mask", greenMask); boolean blueWriteEnabled = getBooleanColorMask("Blue color mask", blueMask); // boolean alphaWriteEnabled = getBooleanColorMask("Alpha mask", alphaMask); re.setColorMask(redWriteEnabled, greenWriteEnabled, blueWriteEnabled, colorMask[3]); super.setColorMask(redMask, greenMask, blueMask, alphaMask); } @Override public void setDepthMask(boolean depthWriteEnabled) { if (canUpdate()) { super.setDepthMask(depthWriteEnabled); } } @Override public void setViewMatrix(float[] values) { super.setViewMatrix(values); // Reload the Model matrix if it was loaded before the View matrix (wrong order) if (modelMatrixLoaded) { re.setModelMatrix(context.model_uploaded_matrix); } viewMatrixLoaded = true; } @Override public void setModelMatrix(float[] values) { if (!viewMatrixLoaded) { re.setViewMatrix(context.view_uploaded_matrix); } super.setModelMatrix(values); modelMatrixLoaded = true; } @Override public void endModelViewMatrixUpdate() { if (!viewMatrixLoaded) { re.setViewMatrix(context.view_uploaded_matrix); } if (!modelMatrixLoaded) { re.setModelMatrix(context.model_uploaded_matrix); } super.endModelViewMatrixUpdate(); viewMatrixLoaded = false; modelMatrixLoaded = false; } @Override public void startDirectRendering(boolean textureEnabled, boolean depthWriteEnabled, boolean colorWriteEnabled, boolean setOrthoMatrix, boolean orthoInverted, int width, int height) { directMode = true; re.disableFlag(GU_DEPTH_TEST); re.disableFlag(GU_BLEND); re.disableFlag(GU_ALPHA_TEST); re.disableFlag(GU_FOG); re.disableFlag(GU_LIGHTING); re.disableFlag(GU_COLOR_LOGIC_OP); re.disableFlag(GU_STENCIL_TEST); re.disableFlag(GU_CULL_FACE); re.disableFlag(GU_SCISSOR_TEST); if (textureEnabled) { re.enableFlag(GU_TEXTURE_2D); } else { re.disableFlag(GU_TEXTURE_2D); } re.setTextureMipmapMinFilter(TFLT_NEAREST); re.setTextureMipmapMagFilter(TFLT_NEAREST); re.setTextureMipmapMinLevel(0); re.setTextureMipmapMaxLevel(0); re.setTextureWrapMode(TWRAP_WRAP_MODE_CLAMP, TWRAP_WRAP_MODE_CLAMP); int colorMask = colorWriteEnabled ? 0x00 : 0xFF; re.setColorMask(colorMask, colorMask, colorMask, colorMask); re.setColorMask(colorWriteEnabled, colorWriteEnabled, colorWriteEnabled, colorWriteEnabled); re.setDepthMask(depthWriteEnabled); re.setTextureFunc(RE_TEXENV_REPLACE, true, false); re.setTextureMapMode(TMAP_TEXTURE_MAP_MODE_TEXTURE_COORDIATES_UV, TMAP_TEXTURE_PROJECTION_MODE_TEXTURE_COORDINATES); re.setFrontFace(true); re.setBones(0, null); directModeSetOrthoMatrix = setOrthoMatrix; if (setOrthoMatrix) { float[] orthoMatrix; if (orthoInverted) { orthoMatrix = VideoEngine.getOrthoMatrix(0, width, 0, height, -1, 1); } else { orthoMatrix = VideoEngine.getOrthoMatrix(0, width, height, 0, -1, 1); } re.setProjectionMatrix(orthoMatrix); re.setModelViewMatrix(null); re.setTextureMatrix(null); } super.startDirectRendering(textureEnabled, depthWriteEnabled, colorWriteEnabled, setOrthoMatrix, orthoInverted, width, height); } @Override public void endDirectRendering() { // Restore all the values according to the context or the clearMode re.setColorMask(context.colorMask[0], context.colorMask[1], context.colorMask[2], context.colorMask[3]); if (context.clearMode) { setClearModeSettings(clearModeContext.color, clearModeContext.stencil, clearModeContext.depth); } else { context.depthTestFlag.updateEnabled(); context.blendFlag.updateEnabled(); context.alphaTestFlag.updateEnabled(); context.fogFlag.updateEnabled(); context.colorLogicOpFlag.updateEnabled(); context.stencilTestFlag.updateEnabled(); context.cullFaceFlag.updateEnabled(); context.textureFlag.update(); re.setDepthMask(context.depthMask); re.setTextureFunc(context.textureFunc, context.textureAlphaUsed, context.textureColorDoubled); } re.setTextureMapMode(context.tex_map_mode, context.tex_proj_map_mode); context.scissorTestFlag.updateEnabled(); context.lightingFlag.updateEnabled(); re.setTextureMipmapMagFilter(context.tex_mag_filter); re.setTextureMipmapMinFilter(context.tex_min_filter); re.setTextureWrapMode(context.tex_wrap_s, context.tex_wrap_t); re.setFrontFace(context.frontFaceCw); if (directModeSetOrthoMatrix) { VideoEngine videoEngine = VideoEngine.getInstance(); videoEngine.projectionMatrixUpload.setChanged(true); videoEngine.viewMatrixUpload.setChanged(true); videoEngine.modelMatrixUpload.setChanged(true); videoEngine.textureMatrixUpload.setChanged(true); } super.endDirectRendering(); directMode = false; } @Override public void beginBoundingBox(int numberOfVertexBoundingBox) { bboxVisible = true; super.beginBoundingBox(numberOfVertexBoundingBox); } @Override public void drawBoundingBox(float[][] values) { if (bboxVisible) { // Pre-compute the Model-View-Projection matrix final float[] modelViewMatrix = new float[16]; final float[] modelViewProjectionMatrix = new float[16]; matrixMult(modelViewMatrix, context.view_uploaded_matrix, context.model_uploaded_matrix); matrixMult(modelViewProjectionMatrix, context.proj_uploaded_matrix, modelViewMatrix); final float viewportX = context.viewport_cx - context.offset_x; final float viewportY = context.viewport_cy - context.offset_y; final float viewportWidth = context.viewport_width; final float viewportHeight = context.viewport_height; final float[] mvpVertex = new float[4]; float minX = 0f; float minY = 0f; float maxX = 0f; float maxY = 0f; float minW = 0f; float maxW = 0f; for (int i = 0; i < values.length; i++) { multMatrix44(mvpVertex, modelViewProjectionMatrix, values[i]); float w = mvpVertex[3]; float x = minX; float y = minY; if (w != 0.f) { x = mvpVertex[0] / w * viewportWidth + viewportX; y = mvpVertex[1] / w * viewportHeight + viewportY; } if (i == 0) { minX = maxX = x; minY = maxY = y; minW = maxW = w; } else { minX = Math.min(minX, x); maxX = Math.max(maxX, x); minY = Math.min(minY, y); maxY = Math.max(maxY, y); minW = Math.min(minW, w); maxW = Math.max(maxW, w); } if (log.isDebugEnabled()) { log.debug(String.format("drawBoundingBox vertex#%d x=%f, y=%f, w=%f", i, x, y, w)); } } // The Bounding Box is not visible when all vertices are outside the drawing region. if (maxX < context.region_x1 || maxY < context.region_y1 || minX > context.region_x2 || minY > context.region_y2) { // When the bounding box is partially before and behind the viewpoint, // assume the bounding box is visible. Rejecting the bounding box in // such cases is leading to incorrect results. if (minW >= 0f || maxW <= 0f) { bboxVisible = false; } } } if (log.isDebugEnabled()) { log.debug(String.format("drawBoundingBox visible=%b", bboxVisible)); } super.drawBoundingBox(values); } @Override public boolean isBoundingBoxVisible() { if (!bboxVisible) { return false; } return super.isBoundingBoxVisible(); } @Override public void setTextureFunc(int func, boolean alphaUsed, boolean colorDoubled) { re.setTexEnv(RE_TEXENV_RGB_SCALE, colorDoubled ? 2.0f : 1.0f); re.setTexEnv(RE_TEXENV_ENV_MODE, func); super.setTextureFunc(func, alphaUsed, colorDoubled); } protected static void multMatrix44(float[] result4, float[] matrix44, float[] vector4) { float x = vector4[0]; float y = vector4[1]; float z = vector4[2]; float w = vector4.length < 4 ? 1.f : vector4[3]; result4[0] = matrix44[0] * x + matrix44[4] * y + matrix44[ 8] * z + matrix44[12] * w; result4[1] = matrix44[1] * x + matrix44[5] * y + matrix44[ 9] * z + matrix44[13] * w; result4[2] = matrix44[2] * x + matrix44[6] * y + matrix44[10] * z + matrix44[14] * w; result4[3] = matrix44[3] * x + matrix44[7] * y + matrix44[11] * z + matrix44[15] * w; } @Override public void startDisplay() { for (int light = 0; light < context.lightFlags.length; light++) { context.lightFlags[light].update(); } super.startDisplay(); } @Override public boolean isVertexArrayAvailable() { return useVertexArray; } @Override public void multiDrawArrays(int primitive, IntBuffer first, IntBuffer count) { // (gid15) I don't know why, but glMultiDrawArrays doesn't seem to work // as expected... is it a bug in LWJGL or did I misunderstood the effect // of the function? // Workaround using glDrawArrays provided. if (useWorkaroundForMultiDrawArrays) { int primitiveCount = first.remaining(); int positionFirst = first.position(); int positionCount = count.position(); if (primitive == GeCommands.PRIM_POINT || primitive == GeCommands.PRIM_LINE || primitive == GeCommands.PRIM_TRIANGLE || primitive == IRenderingEngine.RE_QUADS) { // Independent elements can be rendered in one drawArrays call // if all the elements are sequentially defined boolean sequential = true; int firstIndex = first.get(positionFirst); int currentIndex = firstIndex; for (int i = 1; i < primitiveCount; i++) { currentIndex += count.get(positionCount + i - 1); if (currentIndex != first.get(positionFirst + i)) { sequential = false; break; } } if (sequential) { re.drawArrays(primitive, firstIndex, currentIndex - firstIndex + count.get(positionCount + primitiveCount - 1)); return; } } // Implement multiDrawArrays using multiple drawArrays. // The first call is using drawArrays and the subsequent calls, // drawArraysBurstMode (allowing a faster implementation). re.drawArrays(primitive, first.get(positionFirst), count.get(positionCount)); for (int i = 1; i < primitiveCount; i++) { re.drawArraysBurstMode(primitive, first.get(positionFirst + i), count.get(positionCount + i)); } } else { super.multiDrawArrays(primitive, first, count); } } @Override public void bindActiveTexture(int index, int texture) { int previousActiveTextureUnit = activeTextureUnit; re.setActiveTexture(index); re.bindTexture(texture); re.setActiveTexture(previousActiveTextureUnit); } @Override public void setActiveTexture(int index) { activeTextureUnit = index; super.setActiveTexture(index); } protected void setAlphaMask(boolean alphaWriteEnabled) { if (colorMask[3] != alphaWriteEnabled) { colorMask[3] = alphaWriteEnabled; re.setColorMask(colorMask[0], colorMask[1], colorMask[2], colorMask[3]); } } @Override public void disableFlag(int flag) { if (flag == IRenderingEngine.GU_STENCIL_TEST) { setAlphaMask(false); } super.disableFlag(flag); } @Override public void enableFlag(int flag) { if (flag == IRenderingEngine.GU_STENCIL_TEST) { setAlphaMask(true); } // Setting the logical operation to LOP_COPY is equivalent // to disabling the logical operation step. if (flag == IRenderingEngine.GU_COLOR_LOGIC_OP && logicOp == GeCommands.LOP_COPY) { disableFlag(flag); } else { super.enableFlag(flag); } } private int getBlendFix(int fixColor) { if (fixColor == 0x000000) { return IRenderingEngine.GU_FIX_BLACK; } else if (fixColor == 0xFFFFFF) { return IRenderingEngine.GU_FIX_WHITE; } else { return IRenderingEngine.GU_FIX_BLEND_COLOR; } } /** * Return the distance between 2 colors. * The distance is the sum of the color component differences. * * @param color1 * @param color2 * @return */ private int colorDistance(int color1, int color2) { int blueDistance = abs(getBlue(color1) - getBlue(color2)); int greenDistance = abs(getGreen(color1) - getGreen(color2)); int redDistance = abs(getRed(color1) - getRed(color2)); return redDistance + greenDistance + blueDistance; } private int oneMinusColor(int color) { int b = ONE - getBlue(color); int g = ONE - getGreen(color); int r = ONE - getRed(color); return getColorBGR(b, g, r); } /** * Return the best distance that could be used with a blend factor * for a given color and blend color. * The possible blend factors are * - BLACK * - WHITE * - the blend color * - one minus the blend color * * @param blendColor * @param color * @return */ private int getBestColorDistance(int blendColor, int color) { int bestDistance = colorDistance(color, blendColor); bestDistance = Math.min(bestDistance, colorDistance(color, oneMinusColor(blendColor))); bestDistance = Math.min(bestDistance, colorDistance(color, 0x000000)); bestDistance = Math.min(bestDistance, colorDistance(color, 0xFFFFFF)); return bestDistance; } /** * Find the best blend factor for a color given a blend color. * * @param blendColor * @param oneMinusBlendColor * @param color * @return */ private int getBestBlend(int blendColor, int oneMinusBlendColor, int color) { // Simple cases... if (blendColor == 0x000000) { return IRenderingEngine.GU_FIX_BLACK; } if (blendColor == 0xFFFFFF) { return IRenderingEngine.GU_FIX_WHITE; } if (blendColor == color) { return IRenderingEngine.GU_FIX_BLEND_COLOR; } if (blendColor == oneMinusBlendColor) { return IRenderingEngine.GU_FIX_BLEND_ONE_MINUS_COLOR; } // Complex case: test which blend would be the closest to the given color int bestDistance = colorDistance(color, blendColor); int bestBlend = IRenderingEngine.GU_FIX_BLEND_COLOR; int distance = colorDistance(color, oneMinusBlendColor); if (distance < bestDistance) { bestDistance = distance; bestBlend = IRenderingEngine.GU_FIX_BLEND_ONE_MINUS_COLOR; } distance = colorDistance(color, 0x000000); if (distance < bestDistance) { bestDistance = distance; bestBlend = IRenderingEngine.GU_FIX_BLACK; } distance = colorDistance(color, 0xFFFFFF); if (distance < bestDistance) { bestDistance = distance; bestBlend = IRenderingEngine.GU_FIX_WHITE; } return bestBlend; } private int getColorFromBlend(int blend, int blendColor, int oneMinusBlendColor) { switch (blend) { case IRenderingEngine.GU_FIX_BLACK: return 0x000000; case IRenderingEngine.GU_FIX_WHITE: return 0xFFFFFF; case IRenderingEngine.GU_FIX_BLEND_COLOR: return blendColor; case IRenderingEngine.GU_FIX_BLEND_ONE_MINUS_COLOR: return oneMinusBlendColor; } // Unknown blend... return -1; } @Override public void setBlendFunc(int src, int dst) { if (src == 10) { // GU_FIX src = getBlendFix(context.sfix); } if (dst == 10) { // GU_FIX if (src == IRenderingEngine.GU_FIX_BLEND_COLOR && (context.sfix + context.dfix == 0xFFFFFF)) { dst = IRenderingEngine.GU_FIX_BLEND_ONE_MINUS_COLOR; } else { dst = getBlendFix(context.dfix); } } float[] blend_color = null; if (src == IRenderingEngine.GU_FIX_BLEND_COLOR) { blend_color = context.sfix_color; if (dst == IRenderingEngine.GU_FIX_BLEND_COLOR) { if (context.sfix != context.dfix) { // We cannot set the correct FIX blend colors. // Try to find the best approximation... int blendColor; // Check which blend color, sfix or dfix, would give the best results // (i.e. would have the smallest distance) if (getBestColorDistance(context.sfix, context.dfix) <= getBestColorDistance(context.dfix, context.sfix)) { // Taking sfix as the blend color leads to the best results blendColor = context.sfix; blend_color = context.sfix_color; } else { // Taking dfix as the blend color leads to the best results blendColor = context.dfix; blend_color = context.dfix_color; } int oneMinusBlendColor = oneMinusColor(blendColor); // Now that we have decided which blend color to take, // find the optimum blend factor for both the source and destination src = getBestBlend(blendColor, oneMinusBlendColor, context.sfix); dst = getBestBlend(blendColor, oneMinusBlendColor, context.dfix); if (log.isInfoEnabled()) { log.warn(String.format("UNSUPPORTED: Both different SFIX (0x%06X) and DFIX (0x%06X) are not supported (blend equation=%d), approximating with 0x%06X/0x%06X", context.sfix, context.dfix, context.blendEquation, getColorFromBlend(src, blendColor, oneMinusBlendColor), getColorFromBlend(dst, blendColor, oneMinusBlendColor))); } } } } else if (dst == IRenderingEngine.GU_FIX_BLEND_COLOR) { blend_color = context.dfix_color; } if (blend_color != null) { re.setBlendColor(blend_color); } super.setBlendFunc(src, dst); } @Override public void setBlendDFix(int sfix, float[] color) { // Update the blend color and functions when the DFIX is changing setBlendFunc(context.blend_src, context.blend_dst); super.setBlendDFix(sfix, color); } @Override public void setBlendSFix(int dfix, float[] color) { // Update the blend color and functions when the SFIX is changing setBlendFunc(context.blend_src, context.blend_dst); super.setBlendSFix(dfix, color); } @Override public void multiDrawElements(int primitive, IntBuffer first, IntBuffer count, int indexType, long indicesOffset) { int primitiveCount = first.remaining(); int positionFirst = first.position(); int positionCount = count.position(); if (primitive == GeCommands.PRIM_POINT || primitive == GeCommands.PRIM_LINE || primitive == GeCommands.PRIM_TRIANGLE || primitive == IRenderingEngine.RE_QUADS) { // Independent elements can be rendered in one drawElements call // if all the elements are sequentially defined and the first // index is 0 boolean sequential = true; int firstIndex = first.get(positionFirst); int currentIndex = firstIndex; if (firstIndex != 0) { sequential = false; } else { for (int i = 1; i < primitiveCount; i++) { currentIndex += count.get(positionCount + i - 1); if (currentIndex != first.get(positionFirst + i)) { sequential = false; break; } } } if (sequential) { re.drawElements(primitive, currentIndex - firstIndex + count.get(positionCount + primitiveCount - 1), indexType, indicesOffset); return; } } // Implement multiDrawElements using multiple drawElements. // The first call is using drawElements and the subsequent calls, // drawElementsBurstMode (allowing a faster implementation). int bytesPerIndex = sizeOfType[indexType]; re.drawElements(primitive, count.get(positionCount), indexType, indicesOffset + first.get(positionFirst) * bytesPerIndex); for (int i = 1; i < primitiveCount; i++) { re.drawElementsBurstMode(primitive, count.get(positionCount + i), indexType, indicesOffset + first.get(positionFirst + i) * bytesPerIndex); } } @Override public void setTexImage(int level, int internalFormat, int width, int height, int format, int type, int textureSize, Buffer buffer) { // When specifying no texture buffer, garbage data is used by some OpenGL drivers for // the initial texture content. This can result in a short display of garbage data on screen. // Avoid this by setting the texture content to an empty buffer (i.e. all black). if (buffer == null) { textureSize = width * height * sizeOfTextureType[format]; // Some video drivers sometimes crash when allocating a buffer exactly of the textureSize. // Allocating double the required size seems to workaround this issue... // This memory waste has no real negative impact on the memory usage // as we have only a single instance of the empty buffer. buffer = getEmptyBuffer(textureSize * 2); if (log.isDebugEnabled()) { log.debug(String.format("setTexImage using an empty buffer of size 0x%X", textureSize)); } } super.setTexImage(level, internalFormat, width, height, format, type, textureSize, buffer); } @Override public void setLogicOp(int logicOp) { if (this.logicOp != logicOp) { // Setting the logical operation to LOP_COPY is equivalent // to disabling the logical operation step. if (logicOp == GeCommands.LOP_COPY) { disableFlag(IRenderingEngine.GU_COLOR_LOGIC_OP); } else { super.setLogicOp(logicOp); } this.logicOp = logicOp; } } }