/*
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 jpcsp.HLE.modules.sceDisplay.getTexturePixelFormat;
import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_4BIT_INDEXED;
import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jpcsp.HLE.Modules;
import jpcsp.HLE.modules.sceDisplay;
import jpcsp.graphics.GeCommands;
import jpcsp.graphics.Uniforms;
import jpcsp.graphics.VertexInfo;
import jpcsp.graphics.VideoEngine;
import jpcsp.graphics.textures.FBTexture;
import jpcsp.graphics.textures.GETexture;
import jpcsp.graphics.textures.Texture;
import jpcsp.graphics.textures.TextureCache;
import jpcsp.settings.Settings;
import jpcsp.util.CpuDurationStatistics;
import jpcsp.util.DurationStatistics;
import jpcsp.util.Utilities;
/**
* @author gid15
*
* This RenderingEngine class implements the required logic
* to use OpenGL vertex and fragment shaders, i.e. without using
* the OpenGL fixed-function pipeline.
* If geometry shaders are available, implement the GU_SPRITES primitive by
* inserting a geometry shader into the shader program.
*
* This class is implemented as a Proxy, forwarding the non-relevant calls
* to the proxy.
*/
public class REShader extends BaseRenderingEngineFunction {
public final static int ACTIVE_TEXTURE_NORMAL = 0;
protected final static int ACTIVE_TEXTURE_CLUT = 1;
protected final static int ACTIVE_TEXTURE_FRAMEBUFFER = 2;
protected final static int ACTIVE_TEXTURE_INTEGER = 3;
protected final static float[] positionScale = new float[] { 1, 0x7F, 0x7FFF, 1 };
protected final static float[] normalScale = new float[] { 1, 0x7F, 0x7FFF, 1 };
protected final static float[] textureScale = new float[] { 1, 0x80, 0x8000, 1 };
protected final static float[] weightScale = new float[] { 1, 0x80, 0x8000, 1 };
protected final static String attributeNameTexture = "pspTexture";
protected final static String attributeNameColor = "pspColor";
protected final static String attributeNameNormal = "pspNormal";
protected final static String attributeNamePosition = "pspPosition";
protected final static String attributeNameWeights1 = "pspWeights1";
protected final static String attributeNameWeights2 = "pspWeights2";
protected ShaderContext shaderContext;
protected ShaderProgram defaultShaderProgram;
protected ShaderProgram defaultSpriteShaderProgram;
protected int numberOfWeightsForShader;
protected final static int spriteGeometryShaderInputType = GU_LINES;
protected final static int spriteGeometryShaderOutputType = GU_TRIANGLE_STRIP;
protected boolean useGeometryShader;
protected boolean useUniformBufferObject = true;
protected boolean useNativeClut;
protected boolean useShaderDepthTest = false;
protected boolean useShaderStencilTest = false;
protected boolean useShaderColorMask = false;
// Always use the alpha test implementation in the shader
// to support the alpha test mask (not being supported by OpenGL)
protected boolean useShaderAlphaTest = true;
protected boolean useShaderBlendTest = false;
protected boolean useRenderToTexture = false;
protected boolean useTextureBarrier = false;
protected int clutTextureId = -1;
protected ByteBuffer clutBuffer;
protected DurationStatistics textureCacheLookupStatistics = new CpuDurationStatistics("Lookup in TextureCache for CLUTs");
protected String shaderStaticDefines;
protected String shaderDummyDynamicDefines;
protected int shaderVersion = 120;
protected ShaderProgramManager shaderProgramManager;
protected boolean useDynamicShaders;
protected ShaderProgram currentShaderProgram;
protected StringBuilder infoLogs;
protected GETexture fbTexture;
protected boolean stencilTestFlag;
protected int viewportWidth;
protected int viewportHeight;
protected FBTexture renderTexture;
protected FBTexture copyOfRenderTexture;
protected int pixelFormat;
public REShader(IRenderingEngine proxy) {
super(proxy);
initShader();
}
protected void initShader() {
log.info("Using shaders with Skinning");
useDynamicShaders = Settings.getInstance().readBool("emu.enabledynamicshaders");
if (useDynamicShaders) {
log.info("Using dynamic shaders");
shaderProgramManager = new ShaderProgramManager();
}
useGeometryShader = Settings.getInstance().readBool("emu.useGeometryShader");
if (!re.isExtensionAvailable("GL_ARB_geometry_shader4")) {
useGeometryShader = false;
}
if (useGeometryShader) {
log.info("Using Geometry Shader for SPRITES");
}
if (!ShaderContextUBO.useUBO(re)) {
useUniformBufferObject = false;
}
if (useUniformBufferObject) {
log.info("Using Uniform Buffer Object (UBO)");
}
useNativeClut = Settings.getInstance().readBool("emu.enablenativeclut");
if (useNativeClut) {
if (!super.canNativeClut(0, false)) {
log.warn("Disabling Native Color Lookup Tables (CLUT)");
useNativeClut = false;
} else {
log.info("Using Native Color Lookup Tables (CLUT)");
}
}
useShaderStencilTest = Settings.getInstance().readBool("emu.enableshaderstenciltest");
useShaderColorMask = Settings.getInstance().readBool("emu.enableshadercolormask");
if (useUniformBufferObject) {
shaderContext = new ShaderContextUBO(re);
} else {
shaderContext = new ShaderContext();
}
if (useShaderStencilTest) {
// When implementing the stencil test in the fragment shader,
// we need to implement the alpha test and blend test
// in the shader as well.
// The alpha test has to be performed before the stencil test
// in order to test the correct alpha values because these are updated
// by the stencil test.
// The alpha test of the fixed OpenGL functionality is always
// executed after the shader execution and would then use
// incorrect alpha test values.
// The blend test has also to use the correct alpha value,
// i.e. the alpha value before the stencil test.
useShaderAlphaTest = true;
useShaderBlendTest = true;
useShaderDepthTest = true;
}
if (useShaderStencilTest || useShaderBlendTest || useShaderColorMask) {
// If we are using shaders requiring the current frame buffer content
// as a texture, activate the rendering to a texture if available.
if (re.isFramebufferObjectAvailable()) {
useRenderToTexture = true;
useTextureBarrier = re.isTextureBarrierAvailable();
log.info(String.format("Rendering to a texture with %s", useTextureBarrier ? "texture barrier" : "texture blit (slow)"));
} else {
log.info("Not rendering to a texture, FBO's are not supported by your graphics card. This will have a negative performance impact.");
}
}
initShadersDefines();
loadShaders();
if (defaultShaderProgram == null) {
return;
}
shaderContext.setColorDoubling(1);
shaderContext.setTex(ACTIVE_TEXTURE_NORMAL);
shaderContext.setFbTex(ACTIVE_TEXTURE_FRAMEBUFFER);
if (useNativeClut) {
shaderContext.setClut(ACTIVE_TEXTURE_CLUT);
shaderContext.setUtex(ACTIVE_TEXTURE_INTEGER);
clutBuffer = ByteBuffer.allocateDirect(4096 * 4).order(ByteOrder.LITTLE_ENDIAN);
}
}
protected boolean isValidShader() {
if (defaultShaderProgram == null) {
return false;
}
return true;
}
protected static void addDefine(StringBuilder defines, String name, String value) {
defines.append(String.format("#define %s %s%s", name, escapeString(value), System.getProperty("line.separator")));
}
protected static void addDefine(StringBuilder defines, String name, int value) {
addDefine(defines, name, Integer.toString(value));
}
protected static void addDefine(StringBuilder defines, String name, boolean value) {
addDefine(defines, name, value ? 1 : 0);
}
protected void replace(StringBuilder s, String oldText, String newText) {
int offset = s.indexOf(oldText);
if (offset >= 0) {
s.replace(offset, offset + oldText.length(), newText);
}
}
protected static String escapeString(String s) {
return s.replace('\n', ' ');
}
protected int getAvailableShadingLanguageVersion() {
int availableVersion = 0;
String shadingLanguageVersion = re.getShadingLanguageVersion();
if (shadingLanguageVersion == null) {
return availableVersion;
}
Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+).*").matcher(shadingLanguageVersion);
if (!matcher.matches()) {
log.error(String.format("Cannot parse Shading Language Version '%s'", shadingLanguageVersion));
return availableVersion;
}
try {
int majorNumber = Integer.parseInt(matcher.group(1));
int minorNumber = Integer.parseInt(matcher.group(2));
availableVersion = majorNumber * 100 + minorNumber;
} catch (NumberFormatException e) {
log.error(String.format("Cannot parse Shading Language Version '%s'", shadingLanguageVersion));
}
return availableVersion;
}
protected void initShadersDefines() {
StringBuilder staticDefines = new StringBuilder();
if (getAvailableShadingLanguageVersion() >= 140) {
// Use at least version 1.40 when available.
// Version 1.20/1.30 is causing problems with AMD drivers.
shaderVersion = Math.max(140, shaderVersion);
}
addDefine(staticDefines, "USE_GEOMETRY_SHADER", useGeometryShader);
addDefine(staticDefines, "USE_UBO", useUniformBufferObject);
if (useUniformBufferObject) {
// UBO requires at least shader version 1.40
shaderVersion = Math.max(140, shaderVersion);
addDefine(staticDefines, "UBO_STRUCTURE", ((ShaderContextUBO) shaderContext).getShaderUniformText());
}
addDefine(staticDefines, "USE_NATIVE_CLUT", useNativeClut);
if (useNativeClut) {
// Native clut requires at least shader version 1.30
shaderVersion = Math.max(130, shaderVersion);
}
if (useShaderStencilTest || useShaderBlendTest || useShaderColorMask) {
// Function texelFetch requires at least shader version 1.30
shaderVersion = Math.max(130, shaderVersion);
}
boolean useBitOperators = re.isExtensionAvailable("GL_EXT_gpu_shader4");
addDefine(staticDefines, "USE_BIT_OPERATORS", useBitOperators);
if (!useBitOperators) {
log.info("Extension GL_EXT_gpu_shader4 not available: not using bit operators in shader");
}
shaderStaticDefines = staticDefines.toString();
shaderDummyDynamicDefines = ShaderProgram.getDummyDynamicDefines();
if (log.isDebugEnabled()) {
log.debug(String.format("Using shader version %d, available shading language version %d", shaderVersion, getAvailableShadingLanguageVersion()));
}
}
protected void preprocessShader(StringBuilder src, ShaderProgram shaderProgram) {
StringBuilder defines = new StringBuilder(shaderStaticDefines);
boolean useDynamicDefines;
if (shaderProgram != null) {
defines.append(shaderProgram.getDynamicDefines());
useDynamicDefines = true;
} else {
// Set dummy values to the dynamic defines
// so that the preprocessor doesn't complain about undefined values
defines.append(shaderDummyDynamicDefines);
useDynamicDefines = false;
}
addDefine(defines, "USE_DYNAMIC_DEFINES", useDynamicDefines);
replace(src, "// INSERT VERSION", String.format("#version %d", shaderVersion));
replace(src, "// INSERT DEFINES", defines.toString());
}
protected boolean loadShader(int shader, String resourceName, boolean silentError, ShaderProgram shaderProgram) {
StringBuilder src = new StringBuilder();
try {
InputStream resourceStream = getClass().getResourceAsStream(resourceName);
if (resourceStream == null) {
return false;
}
src.append(Utilities.toString(resourceStream, true));
} catch (IOException e) {
log.error(e);
return false;
}
preprocessShader(src, shaderProgram);
if (log.isTraceEnabled()) {
log.trace(String.format("Compiling shader %d from %s:\n%s", shader, resourceName, src.toString()));
}
boolean compiled = re.compilerShader(shader, src.toString());
if (compiled || !silentError) {
addShaderInfoLog(shader);
}
return compiled;
}
/**
* Link and validate a shader program.
*
* @param program the program to be linked
* @return true if the link was successful
* false if the link was not successful
*/
protected boolean linkShaderProgram(int program) {
boolean linked = re.linkProgram(program);
addProgramInfoLog(program);
// Trying to avoid warning message from AMD drivers:
// "Validation warning! - Sampler value tex has not been set."
// and error message:
// "Validation failed! - Different sampler types for same sample texture unit in fragment shader"
re.useProgram(program);
re.setUniform(re.getUniformLocation(program, Uniforms.tex.getUniformString()), ACTIVE_TEXTURE_NORMAL);
re.setUniform(re.getUniformLocation(program, Uniforms.fbTex.getUniformString()), ACTIVE_TEXTURE_FRAMEBUFFER);
if (useNativeClut) {
re.setUniform(re.getUniformLocation(program, Uniforms.clut.getUniformString()), ACTIVE_TEXTURE_CLUT);
re.setUniform(re.getUniformLocation(program, Uniforms.utex.getUniformString()), ACTIVE_TEXTURE_INTEGER);
}
boolean validated = re.validateProgram(program);
addProgramInfoLog(program);
return linked && validated;
}
protected ShaderProgram createShader(boolean hasGeometryShader, ShaderProgram shaderProgram) {
infoLogs = new StringBuilder();
int programId = tryCreateShader(hasGeometryShader, shaderProgram);
if (programId == -1) {
printInfoLog(true);
shaderProgram = null;
} else {
if (shaderProgram == null) {
shaderProgram = new ShaderProgram();
}
shaderProgram.setProgramId(re, programId);
printInfoLog(false);
}
return shaderProgram;
}
private int tryCreateShader(boolean hasGeometryShader, ShaderProgram shaderProgram) {
int vertexShader = re.createShader(RE_VERTEX_SHADER);
int fragmentShader = re.createShader(RE_FRAGMENT_SHADER);
if (!loadShader(vertexShader, "/jpcsp/graphics/shader.vert", false, shaderProgram)) {
return -1;
}
if (!loadShader(fragmentShader, "/jpcsp/graphics/shader.frag", false, shaderProgram)) {
return -1;
}
int program = re.createProgram();
re.attachShader(program, vertexShader);
re.attachShader(program, fragmentShader);
if (hasGeometryShader) {
int geometryShader = re.createShader(RE_GEOMETRY_SHADER);
boolean compiled;
compiled = loadShader(geometryShader, "/jpcsp/graphics/shader-150.geom", false, shaderProgram);
if (compiled) {
log.info("Using Geometry Shader shader-150.geom");
} else {
compiled = loadShader(geometryShader, "/jpcsp/graphics/shader-120.geom", false, shaderProgram);
if (compiled) {
log.info("Using Geometry Shader shader-120.geom");
}
}
if (!compiled) {
return -1;
}
re.attachShader(program, geometryShader);
re.setProgramParameter(program, RE_GEOMETRY_INPUT_TYPE, spriteGeometryShaderInputType);
re.setProgramParameter(program, RE_GEOMETRY_OUTPUT_TYPE, spriteGeometryShaderOutputType);
re.setProgramParameter(program, RE_GEOMETRY_VERTICES_OUT, 4);
}
// Use the same attribute index values for all shader programs.
//
// Issue: AMD driver is incorrectly handling attributes referenced in a shader
// (even when not really used, e.g. in an "if" statement)
// but disabled using disableVertexAttribArray. The solution for this
// issue is to use dynamic shaders.
//
// Read on AMD forum: the vertex attribute 0 has to be defined.
// Using the position here, as it is always defined.
//
int index = 0;
re.bindAttribLocation(program, index++, attributeNamePosition);
re.bindAttribLocation(program, index++, attributeNameTexture);
re.bindAttribLocation(program, index++, attributeNameColor);
re.bindAttribLocation(program, index++, attributeNameNormal);
re.bindAttribLocation(program, index++, attributeNameWeights1);
re.bindAttribLocation(program, index++, attributeNameWeights2);
boolean linked = linkShaderProgram(program);
if (!linked) {
return -1;
}
re.useProgram(program);
if (log.isDebugEnabled()) {
int shaderAttribWeights1 = re.getAttribLocation(program, attributeNameWeights1);
int shaderAttribWeights2 = re.getAttribLocation(program, attributeNameWeights2);
int shaderAttribPosition = re.getAttribLocation(program, attributeNamePosition);
int shaderAttribNormal = re.getAttribLocation(program, attributeNameNormal);
int shaderAttribColor = re.getAttribLocation(program, attributeNameColor);
int shaderAttribTexture = re.getAttribLocation(program, attributeNameTexture);
log.debug(String.format("Program %d attribute locations: weights1=%d, weights2=%d, position=%d, normal=%d, color=%d, texture=%d", program, shaderAttribWeights1, shaderAttribWeights2, shaderAttribPosition, shaderAttribNormal, shaderAttribColor, shaderAttribTexture));
}
for (Uniforms uniform : Uniforms.values()) {
uniform.allocateId(re, program);
}
shaderContext.initShaderProgram(re, program);
return program;
}
protected void loadShaders() {
defaultShaderProgram = createShader(false, null);
if (defaultShaderProgram != null) {
if (useGeometryShader) {
defaultSpriteShaderProgram = createShader(true, null);
}
defaultShaderProgram.use(re);
}
if (defaultSpriteShaderProgram == null) {
useGeometryShader = false;
}
}
public static boolean useShaders(IRenderingEngine re) {
if (!Settings.getInstance().readBool("emu.useshaders")) {
return false;
}
if (!re.isShaderAvailable()) {
log.info("Shaders are not available on your computer. They have been automatically disabled.");
return false;
}
REShader reTestShader = new REShader(re);
if (!reTestShader.isValidShader()) {
log.warn("Shaders do not run correctly on your computer. They have been automatically disabled.");
return false;
}
return true;
}
protected void printInfoLog(boolean isError) {
if (infoLogs != null && infoLogs.length() > 0) {
if (isError) {
log.error("Shader error log: " + infoLogs);
} else {
// Remove all the useless AMD messages
String infoLog = infoLogs.toString();
infoLog = infoLog.replace("Vertex shader was successfully compiled to run on hardware.\n", "");
infoLog = infoLog.replace("Fragment shader was successfully compiled to run on hardware.\n", "");
infoLog = infoLog.replace("Geometry shader was successfully compiled to run on hardware.\n", "");
infoLog = infoLog.replace("Fragment shader(s) linked, vertex shader(s) linked. \n", "");
infoLog = infoLog.replace("Vertex shader(s) linked, fragment shader(s) linked. \n", "");
infoLog = infoLog.replace("Vertex shader(s) linked, fragment shader(s) linked.\n", "");
infoLog = infoLog.replace("Validation successful.\n", "");
if (infoLog.length() > 0) {
log.warn("Shader log: " + infoLog);
}
}
}
}
protected void addInfoLog(String infoLog) {
if (infoLog != null && infoLog.length() > 0) {
infoLogs.append(infoLog);
}
}
protected void addShaderInfoLog(int shader) {
String infoLog = re.getShaderInfoLog(shader);
addInfoLog(infoLog);
}
protected void addProgramInfoLog(int program) {
String infoLog = re.getProgramInfoLog(program);
addInfoLog(infoLog);
}
/**
* Set the given flag in the shader context, if relevant.
* @param flag the flag to be set
* @param value the value of the flag (0 is disabled, 1 is enabled)
* @return true if the flag as to be enabled in OpenGL as well,
* false if the flag has to stay disabled in OpenGL.
*/
protected boolean setShaderFlag(int flag, int value) {
boolean setFlag = true;
switch (flag) {
case IRenderingEngine.GU_LIGHT0:
case IRenderingEngine.GU_LIGHT1:
case IRenderingEngine.GU_LIGHT2:
case IRenderingEngine.GU_LIGHT3:
shaderContext.setLightEnabled(flag - IRenderingEngine.GU_LIGHT0, value);
setFlag = false;
break;
case IRenderingEngine.GU_COLOR_TEST:
shaderContext.setCtestEnable(value);
setFlag = false;
break;
case IRenderingEngine.GU_LIGHTING:
shaderContext.setLightingEnable(value);
setFlag = false;
break;
case IRenderingEngine.GU_TEXTURE_2D:
shaderContext.setTexEnable(value);
break;
case IRenderingEngine.GU_DEPTH_TEST:
if (useShaderDepthTest) {
shaderContext.setDepthTestEnable(value);
}
break;
case IRenderingEngine.GU_STENCIL_TEST:
if (useShaderStencilTest) {
shaderContext.setStencilTestEnable(value);
stencilTestFlag = (value != 0);
setAlphaMask(stencilTestFlag);
setFlag = false;
}
break;
case IRenderingEngine.GU_ALPHA_TEST:
if (useShaderAlphaTest) {
shaderContext.setAlphaTestEnable(value);
setFlag = false;
}
break;
case IRenderingEngine.GU_BLEND:
if (useShaderBlendTest) {
shaderContext.setBlendTestEnable(value);
setFlag = false;
}
break;
case IRenderingEngine.GU_FOG:
shaderContext.setFogEnable(value);
setFlag = false;
break;
}
return setFlag;
}
@Override
public void exit() {
if (DurationStatistics.collectStatistics) {
if (useNativeClut) {
log.info(textureCacheLookupStatistics);
}
}
super.exit();
}
@Override
public void enableFlag(int flag) {
if (canUpdateFlag(flag)) {
if (setShaderFlag(flag, 1)) {
super.enableFlag(flag);
}
}
}
@Override
public void disableFlag(int flag) {
if (canUpdateFlag(flag)) {
if (setShaderFlag(flag, 0)) {
super.disableFlag(flag);
}
}
}
@Override
public void setDepthRange(float zpos, float zscale, int near, int far) {
shaderContext.setZPos(zpos);
shaderContext.setZScale(zscale);
super.setDepthRange(zpos, zscale, near, far);
}
@Override
public void setLightMode(int mode) {
shaderContext.setLightMode(mode);
super.setLightMode(mode);
}
@Override
public void setLightType(int light, int type, int kind) {
shaderContext.setLightType(light, type);
shaderContext.setLightKind(light, kind);
super.setLightType(light, type, kind);
}
@Override
public void setTextureEnvironmentMapping(int u, int v) {
shaderContext.setTexShade(0, u);
shaderContext.setTexShade(1, v);
super.setTextureEnvironmentMapping(u, v);
}
@Override
public void setColorTestFunc(int func) {
shaderContext.setCtestFunc(func);
super.setColorTestFunc(func);
}
@Override
public void setColorTestMask(int[] values) {
shaderContext.setCtestMsk(0, values[0]);
shaderContext.setCtestMsk(1, values[1]);
shaderContext.setCtestMsk(2, values[2]);
super.setColorTestMask(values);
}
@Override
public void setColorTestReference(int[] values) {
shaderContext.setCtestRef(0, values[0]);
shaderContext.setCtestRef(1, values[1]);
shaderContext.setCtestRef(2, values[2]);
super.setColorTestReference(values);
}
@Override
public void setTextureFunc(int func, boolean alphaUsed, boolean colorDoubled) {
shaderContext.setTexEnvMode(0, func);
shaderContext.setTexEnvMode(1, alphaUsed ? 1 : 0);
shaderContext.setColorDoubling(colorDoubled ? 2.f : 1.f);
super.setTextureFunc(func, alphaUsed, colorDoubled);
}
@Override
public int setBones(int count, float[] values) {
shaderContext.setNumberBones(count);
shaderContext.setBoneMatrix(count, values);
numberOfWeightsForShader = count;
super.setBones(count, values);
return numberOfWeightsForShader; // Number of weights to be copied into the Buffer
}
@Override
public void setTextureMapMode(int mode, int proj) {
shaderContext.setTexMapMode(mode);
shaderContext.setTexMapProj(proj);
super.setTextureMapMode(mode, proj);
}
@Override
public void setColorMaterial(boolean ambient, boolean diffuse, boolean specular) {
shaderContext.setMatFlags(0, ambient ? 1 : 0);
shaderContext.setMatFlags(1, diffuse ? 1 : 0);
shaderContext.setMatFlags(2, specular ? 1 : 0);
super.setColorMaterial(ambient, diffuse, specular);
}
@Override
public void startDisplay() {
defaultShaderProgram.use(re);
if (useRenderToTexture) {
sceDisplay display = Modules.sceDisplayModule;
int width = display.getWidthFb();
int height = display.getHeightFb();
int bufferWidth = display.getBufferWidthFb();
int pixelFormat = display.getPixelFormatFb();
// if the format of the Frame Buffer has changed, re-create a new texture
if (renderTexture != null && !renderTexture.isCompatible(width, height, bufferWidth, pixelFormat)) {
renderTexture.delete(re);
renderTexture = null;
if (copyOfRenderTexture != null) {
copyOfRenderTexture.delete(re);
copyOfRenderTexture = null;
}
}
// Activate the rendering to a texture
if (renderTexture == null) {
renderTexture = new FBTexture(display.getTopAddrFb(), bufferWidth, width, height, getTexturePixelFormat(pixelFormat));
renderTexture.bind(re, false);
re.bindActiveTexture(ACTIVE_TEXTURE_FRAMEBUFFER, renderTexture.getTextureId());
} else {
renderTexture.bind(re, false);
}
}
super.startDisplay();
// We don't use Client States
super.disableClientState(IRenderingEngine.RE_TEXTURE);
super.disableClientState(IRenderingEngine.RE_COLOR);
super.disableClientState(IRenderingEngine.RE_NORMAL);
super.disableClientState(IRenderingEngine.RE_VERTEX);
}
@Override
public void endDisplay() {
if (useRenderToTexture) {
// Copy the rendered texture back to the main frame buffer
renderTexture.copyTextureToScreen(re);
}
re.useProgram(0);
super.endDisplay();
}
@Override
public void enableClientState(int type) {
switch (type) {
case RE_VERTEX:
re.enableVertexAttribArray(currentShaderProgram.getShaderAttribPosition());
if (numberOfWeightsForShader > 0) {
re.enableVertexAttribArray(currentShaderProgram.getShaderAttribWeights1());
if (numberOfWeightsForShader > 4) {
re.enableVertexAttribArray(currentShaderProgram.getShaderAttribWeights2());
} else {
re.disableVertexAttribArray(currentShaderProgram.getShaderAttribWeights2());
}
} else {
re.disableVertexAttribArray(currentShaderProgram.getShaderAttribWeights1());
re.disableVertexAttribArray(currentShaderProgram.getShaderAttribWeights2());
}
break;
case RE_NORMAL:
re.enableVertexAttribArray(currentShaderProgram.getShaderAttribNormal());
break;
case RE_COLOR:
re.enableVertexAttribArray(currentShaderProgram.getShaderAttribColor());
break;
case RE_TEXTURE:
re.enableVertexAttribArray(currentShaderProgram.getShaderAttribTexture());
break;
}
}
@Override
public void disableClientState(int type) {
switch (type) {
case RE_VERTEX:
re.disableVertexAttribArray(currentShaderProgram.getShaderAttribPosition());
re.disableVertexAttribArray(currentShaderProgram.getShaderAttribWeights1());
re.disableVertexAttribArray(currentShaderProgram.getShaderAttribWeights2());
break;
case RE_NORMAL:
re.disableVertexAttribArray(currentShaderProgram.getShaderAttribNormal());
break;
case RE_COLOR:
re.disableVertexAttribArray(currentShaderProgram.getShaderAttribColor());
break;
case RE_TEXTURE:
re.disableVertexAttribArray(currentShaderProgram.getShaderAttribTexture());
break;
}
}
@Override
public void setWeightPointer(int size, int type, int stride, int bufferSize, Buffer buffer) {
if (size > 0) {
re.setVertexAttribPointer(currentShaderProgram.getShaderAttribWeights1(), Math.min(size, 4), type, false, stride, bufferSize, buffer);
if (size > 4) {
re.setVertexAttribPointer(currentShaderProgram.getShaderAttribWeights2(), size - 4, type, false, stride, bufferSize, buffer);
}
}
}
@Override
public void setWeightPointer(int size, int type, int stride, long offset) {
if (size > 0) {
re.setVertexAttribPointer(currentShaderProgram.getShaderAttribWeights1(), Math.min(size, 4), type, false, stride, offset);
if (size > 4) {
re.setVertexAttribPointer(currentShaderProgram.getShaderAttribWeights2(), size - 4, type, false, stride, offset + sizeOfType[type] * 4);
}
}
}
@Override
public boolean canAllNativeVertexInfo() {
// Shader supports all PSP native vertex formats
return true;
}
@Override
public boolean canNativeSpritesPrimitive() {
// Geometry shader supports native GU_SPRITES primitive
return useGeometryShader;
}
@Override
public void setVertexInfo(VertexInfo vinfo, boolean allNativeVertexInfo, boolean useVertexColor, boolean useTexture, int type) {
if (allNativeVertexInfo) {
// Weight
shaderContext.setWeightScale(weightScale[vinfo.weight]);
// Texture
shaderContext.setVinfoTexture(useTexture ? (vinfo.texture != 0 ? vinfo.texture : 3) : 0);
shaderContext.setTextureScale(vinfo.transform2D ? 1.f : textureScale[vinfo.texture]);
// Color
shaderContext.setVinfoColor(useVertexColor ? vinfo.color : 8);
// Normal
shaderContext.setVinfoNormal(vinfo.normal);
shaderContext.setNormalScale(vinfo.transform2D ? 1.f : normalScale[vinfo.normal]);
// Position
shaderContext.setVinfoPosition(vinfo.position);
shaderContext.setPositionScale(vinfo.transform2D ? 1.f : positionScale[vinfo.position]);
} else {
// Weight
shaderContext.setWeightScale(1);
// Texture
shaderContext.setVinfoTexture(useTexture ? 3 : 0);
shaderContext.setTextureScale(1);
// Color
shaderContext.setVinfoColor(useVertexColor ? 0 : 8);
// Normal
shaderContext.setVinfoNormal(vinfo == null || vinfo.normal == 0 ? 0 : 3);
shaderContext.setNormalScale(1);
// Position
shaderContext.setVinfoPosition(vinfo != null && vinfo.position == 0 ? 0 : 3);
shaderContext.setPositionScale(1);
}
shaderContext.setVinfoTransform2D(vinfo == null || vinfo.transform2D ? 1 : 0);
setCurrentShaderProgram(type);
super.setVertexInfo(vinfo, allNativeVertexInfo, useVertexColor, useTexture, type);
}
private void setCurrentShaderProgram(int type) {
ShaderProgram shaderProgram;
boolean hasGeometryShader = (type == GU_SPRITES);
if (useDynamicShaders) {
shaderProgram = shaderProgramManager.getShaderProgram(shaderContext, hasGeometryShader);
if (shaderProgram.getProgramId() == -1) {
shaderProgram = createShader(hasGeometryShader, shaderProgram);
if (log.isDebugEnabled()) {
log.debug("Created shader " + shaderProgram);
}
if (shaderProgram == null) {
log.error("Cannot create shader");
return;
}
}
} else if (hasGeometryShader) {
shaderProgram = defaultSpriteShaderProgram;
} else {
shaderProgram = defaultShaderProgram;
}
shaderProgram.use(re);
if (log.isTraceEnabled()) {
log.trace("Using shader " + shaderProgram);
}
currentShaderProgram = shaderProgram;
}
/**
* Check if the fragment shader requires the frame buffer texture (fbTex sampler)
* to be updated with the current screen content.
*
* A copy of the current screen content to the frame buffer texture
* can be avoided if this method returns "false". The execution of such a copy
* is quite expensive and should be avoided as much as possible.
*
* We need to copy the current screen to the FrameBuffer texture only when one
* of the following applies:
* - the STENCIL_TEST flag is enabled
* - the BLEND_TEST flag is enabled
* - the Color mask is enabled
*
* @return true if the shader will use the fbTex sampler
* false if the shader will not use the fbTex sampler
*/
private boolean isFbTextureNeeded() {
if (useShaderDepthTest && shaderContext.getDepthTestEnable() != 0) {
return true;
}
if (useShaderStencilTest && shaderContext.getStencilTestEnable() != 0) {
return true;
}
if (useShaderBlendTest && shaderContext.getBlendTestEnable() != 0) {
return true;
}
if (useShaderColorMask && shaderContext.getColorMaskEnable() != 0) {
return true;
}
return false;
}
/**
* If necessary, load the frame buffer texture with the current screen
* content so that it can be used by the fragment shader (fbTex sampler).
*/
private void loadFbTexture() {
if (!isFbTextureNeeded()) {
return;
}
int width = viewportWidth;
int height = viewportHeight;
int bufferWidth = context.fbw;
int pixelFormat = context.psm;
if (useRenderToTexture) {
// Use the render texture if it is compatible with the current GE settings.
if (renderTexture.getResizedWidth() >= width && renderTexture.getResizedHeight() >= height && renderTexture.getBufferWidth() >= bufferWidth) {
if (useTextureBarrier) {
// The shader can use as input the texture used as output for the frame buffer.
// For this feature, the texture barrier extension is required.
renderTexture.bind(re, false);
// Tell the shader which texture has to be used for the fbTex sampler.
re.bindActiveTexture(ACTIVE_TEXTURE_FRAMEBUFFER, renderTexture.getTextureId());
re.textureBarrier();
} else {
// The shader cannot use as input the texture used as output for the frame buffer,
// we need to copy the output texture to another texture and use the copy
// as input for the shader.
if (copyOfRenderTexture == null) {
copyOfRenderTexture = new FBTexture(renderTexture);
}
copyOfRenderTexture.blitFrom(re, renderTexture);
// Tell the shader which texture has to be used for the fbTex sampler.
re.bindActiveTexture(ACTIVE_TEXTURE_FRAMEBUFFER, copyOfRenderTexture.getTextureId());
}
return;
}
// If the render texture is not compatible with the current GE settings,
// we are not lucky and have to copy the current screen to a compatible
// texture.
}
// Delete the texture and recreate a new one if its dimension has changed
if (fbTexture != null && !fbTexture.isCompatible(width, height, bufferWidth, pixelFormat)) {
fbTexture.delete(re);
fbTexture = null;
}
// Create a new texture
if (fbTexture == null) {
fbTexture = new GETexture(Modules.sceDisplayModule.getTopAddrGe(), bufferWidth, width, height, pixelFormat, true);
}
// Copy the current screen (FrameBuffer) content to the texture
re.setActiveTexture(ACTIVE_TEXTURE_FRAMEBUFFER);
fbTexture.copyScreenToTexture(re);
re.setActiveTexture(ACTIVE_TEXTURE_NORMAL);
}
@Override
public void setVertexPointer(int size, int type, int stride, int bufferSize, Buffer buffer) {
re.setVertexAttribPointer(currentShaderProgram.getShaderAttribPosition(), size, type, false, stride, bufferSize, buffer);
}
@Override
public void setVertexPointer(int size, int type, int stride, long offset) {
re.setVertexAttribPointer(currentShaderProgram.getShaderAttribPosition(), size, type, false, stride, offset);
}
@Override
public void setColorPointer(int size, int type, int stride, int bufferSize, Buffer buffer) {
re.setVertexAttribPointer(currentShaderProgram.getShaderAttribColor(), size, type, false, stride, bufferSize, buffer);
}
@Override
public void setColorPointer(int size, int type, int stride, long offset) {
re.setVertexAttribPointer(currentShaderProgram.getShaderAttribColor(), size, type, false, stride, offset);
}
@Override
public void setNormalPointer(int type, int stride, int bufferSize, Buffer buffer) {
re.setVertexAttribPointer(currentShaderProgram.getShaderAttribNormal(), 3, type, false, stride, bufferSize, buffer);
}
@Override
public void setNormalPointer(int type, int stride, long offset) {
re.setVertexAttribPointer(currentShaderProgram.getShaderAttribNormal(), 3, type, false, stride, offset);
}
@Override
public void setTexCoordPointer(int size, int type, int stride, int bufferSize, Buffer buffer) {
re.setVertexAttribPointer(currentShaderProgram.getShaderAttribTexture(), size, type, false, stride, bufferSize, buffer);
}
@Override
public void setTexCoordPointer(int size, int type, int stride, long offset) {
re.setVertexAttribPointer(currentShaderProgram.getShaderAttribTexture(), size, type, false, stride, offset);
}
private int prepareDraw(int primitive, boolean burstMode) {
if (primitive == GU_SPRITES) {
primitive = spriteGeometryShaderInputType;
}
if (!burstMode) {
// The uniform values are specific to a shader program:
// update the uniform values after switching the active shader program.
shaderContext.setUniforms(re, currentShaderProgram.getProgramId());
}
loadFbTexture();
loadIntegerTexture();
return primitive;
}
@Override
public void drawArrays(int primitive, int first, int count) {
primitive = prepareDraw(primitive, false);
super.drawArrays(primitive, first, count);
}
@Override
public void drawArraysBurstMode(int primitive, int first, int count) {
// drawArraysBurstMode is equivalent to drawArrays
// but without the need to set the uniforms (they are unchanged
// since the last call to drawArrays).
primitive = prepareDraw(primitive, true);
super.drawArraysBurstMode(primitive, first, count);
}
@Override
public void drawElements(int primitive, int count, int indexType, Buffer indices, int indicesOffset) {
primitive = prepareDraw(primitive, false);
super.drawElements(primitive, count, indexType, indices, indicesOffset);
}
@Override
public void drawElements(int primitive, int count, int indexType, long indicesOffset) {
primitive = prepareDraw(primitive, false);
super.drawElements(primitive, count, indexType, indicesOffset);
}
@Override
public void drawElementsBurstMode(int primitive, int count, int indexType, long indicesOffset) {
// drawElementsBurstMode is equivalent to drawElements
// but without the need to set the uniforms (they are unchanged
// since the last call to drawElements).
primitive = prepareDraw(primitive, true);
super.drawElementsBurstMode(primitive, count, indexType, indicesOffset);
}
@Override
public boolean canNativeClut(int textureAddress, boolean textureSwizzle) {
// The clut processing is implemented into the fragment shader
// and the clut values are passed as a sampler2D.
// Do not process clut's for swizzled texture, there is no performance gain.
return useNativeClut && !textureSwizzle;
}
private int getClutIndexHint(int pixelFormat, int numEntries) {
if (context.tex_clut_start == 0 && context.tex_clut_mask == numEntries - 1) {
int currentBit = 0;
if (context.tex_clut_shift == currentBit) {
return IRenderingEngine.RE_CLUT_INDEX_RED_ONLY;
}
switch (pixelFormat) {
case IRenderingEngine.RE_PIXEL_STORAGE_16BIT_INDEXED_BGR5650: currentBit += 5; break;
case IRenderingEngine.RE_PIXEL_STORAGE_16BIT_INDEXED_ABGR5551: currentBit += 5; break;
case IRenderingEngine.RE_PIXEL_STORAGE_16BIT_INDEXED_ABGR4444: currentBit += 4; break;
case IRenderingEngine.RE_PIXEL_STORAGE_32BIT_INDEXED_ABGR8888: currentBit += 8; break;
default:
switch (context.tex_clut_mode) {
case GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650: currentBit += 5; break;
case GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR5551: currentBit += 5; break;
case GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444: currentBit += 4; break;
case GeCommands.TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888: currentBit += 8; break;
}
}
if (context.tex_clut_shift == currentBit) {
return IRenderingEngine.RE_CLUT_INDEX_GREEN_ONLY;
}
switch (pixelFormat) {
case IRenderingEngine.RE_PIXEL_STORAGE_16BIT_INDEXED_BGR5650: currentBit += 6; break;
case IRenderingEngine.RE_PIXEL_STORAGE_16BIT_INDEXED_ABGR5551: currentBit += 5; break;
case IRenderingEngine.RE_PIXEL_STORAGE_16BIT_INDEXED_ABGR4444: currentBit += 4; break;
case IRenderingEngine.RE_PIXEL_STORAGE_32BIT_INDEXED_ABGR8888: currentBit += 8; break;
default:
switch (context.tex_clut_mode) {
case GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650: currentBit += 6; break;
case GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR5551: currentBit += 5; break;
case GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444: currentBit += 4; break;
case GeCommands.TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888: currentBit += 8; break;
}
}
if (context.tex_clut_shift == currentBit) {
return IRenderingEngine.RE_CLUT_INDEX_BLUE_ONLY;
}
switch (pixelFormat) {
case IRenderingEngine.RE_PIXEL_STORAGE_16BIT_INDEXED_BGR5650: currentBit += 5; break;
case IRenderingEngine.RE_PIXEL_STORAGE_16BIT_INDEXED_ABGR5551: currentBit += 5; break;
case IRenderingEngine.RE_PIXEL_STORAGE_16BIT_INDEXED_ABGR4444: currentBit += 4; break;
case IRenderingEngine.RE_PIXEL_STORAGE_32BIT_INDEXED_ABGR8888: currentBit += 8; break;
default:
switch (context.tex_clut_mode) {
case GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650: currentBit += 5; break;
case GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR5551: currentBit += 5; break;
case GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444: currentBit += 4; break;
case GeCommands.TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888: currentBit += 8; break;
}
}
if (context.tex_clut_shift == currentBit) {
return IRenderingEngine.RE_CLUT_INDEX_ALPHA_ONLY;
}
}
return IRenderingEngine.RE_CLUT_INDEX_NO_HINT;
}
private void loadIntegerTexture() {
if (!context.textureFlag.isEnabled() || context.clearMode) {
// Not using a texture
return;
}
if (!useNativeClut || !isTextureTypeIndexed[pixelFormat]) {
// Not using a native clut
return;
}
if (pixelFormat == TPSM_PIXEL_STORAGE_MODE_4BIT_INDEXED) {
// 4bit index has been decoded in the VideoEngine
return;
}
// Associate the current texture to the integer texture sampler.
// AMD/ATI driver requires different texture units for samplers of different types.
// Otherwise, the shader compilation fails with the following error:
// "Different sampler types for same sample texture unit in fragment shader"
re.bindActiveTexture(ACTIVE_TEXTURE_INTEGER, context.currentTextureId);
}
private void loadClut(int pixelFormat) {
int numEntries = VideoEngine.getInstance().getClutNumEntries();
int clutPixelFormat = context.tex_clut_mode;
int bytesPerEntry = sizeOfTextureType[clutPixelFormat];
shaderContext.setClutShift(context.tex_clut_shift);
shaderContext.setClutMask(context.tex_clut_mask);
shaderContext.setClutOffset(context.tex_clut_start << 4);
shaderContext.setMipmapShareClut(context.mipmapShareClut);
shaderContext.setClutIndexHint(getClutIndexHint(pixelFormat, numEntries));
int[] clut32 = (bytesPerEntry == 4 ? VideoEngine.getInstance().readClut32(0) : null);
short[] clut16 = (bytesPerEntry == 2 ? VideoEngine.getInstance().readClut16(0) : null);
Texture texture;
int textureId;
if (VideoEngine.useTextureCache) {
TextureCache textureCache = TextureCache.getInstance();
textureCacheLookupStatistics.start();
texture = textureCache.getTexture(context.tex_clut_addr, numEntries, numEntries, 1, clutPixelFormat, 0, 0, 0, 0, 0, 0, 0, false, clut16, clut32);
textureCacheLookupStatistics.end();
if (texture == null) {
texture = new Texture(textureCache, context.tex_clut_addr, numEntries, numEntries, 1, clutPixelFormat, 0, 0, 0, 0, 0, 0, 0, false, clut16, clut32);
textureCache.addTexture(re, texture);
}
textureId = texture.getTextureId(re);
} else {
texture = null;
if (clutTextureId == -1) {
clutTextureId = re.genTexture();
}
textureId = clutTextureId;
}
if (texture == null || !texture.isLoaded()) {
re.setActiveTexture(ACTIVE_TEXTURE_CLUT);
re.bindTexture(textureId);
clutBuffer.clear();
if (clut32 != null) {
clutBuffer.asIntBuffer().put(clut32, 0, numEntries);
} else {
clutBuffer.asShortBuffer().put(clut16, 0, numEntries);
}
re.setPixelStore(numEntries, bytesPerEntry);
re.setTextureMipmapMagFilter(GeCommands.TFLT_NEAREST);
re.setTextureMipmapMinFilter(GeCommands.TFLT_NEAREST);
re.setTextureMipmapMinLevel(0);
re.setTextureMipmapMaxLevel(0);
re.setTextureWrapMode(GeCommands.TWRAP_WRAP_MODE_CLAMP, GeCommands.TWRAP_WRAP_MODE_CLAMP);
// Load the CLUT as a Nx1 texture.
// (gid15) I did not manage to make this code work with 1D textures,
// probably because they are very seldom used and buggy.
// To use a 2D texture Nx1 is the safest way...
int clutSize = bytesPerEntry * numEntries;
re.setTexImage(0, clutPixelFormat, numEntries, 1, clutPixelFormat, clutPixelFormat, clutSize, clutBuffer);
if (texture != null) {
texture.setIsLoaded();
}
re.setActiveTexture(ACTIVE_TEXTURE_NORMAL);
} else {
// The call
// bindActiveTexture(ACTIVE_TEXTURE_CLUT, textureId)
// is equivalent to
// setActiveTexture(ACTIVE_TEXTURE_CLUT)
// bindTexture(textureId)
// setActiveTexture(ACTIVE_TEXTURE_NORMAL)
// but executes faster: StateProxy can eliminate the 3 OpenGL calls
// if they are redundant.
re.bindActiveTexture(ACTIVE_TEXTURE_CLUT, textureId);
}
}
@Override
public void setTextureFormat(int pixelFormat, boolean swizzle) {
this.pixelFormat = pixelFormat;
if (isTextureTypeIndexed[pixelFormat]) {
if (pixelFormat == GeCommands.TPSM_PIXEL_STORAGE_MODE_4BIT_INDEXED) {
// 4bit index has been decoded in the VideoEngine
pixelFormat = context.tex_clut_mode;
} else if (!canNativeClut(context.texture_base_pointer[0], swizzle)) {
// Textures are decoded in the VideoEngine when not using native CLUTs
pixelFormat = context.tex_clut_mode;
} else {
loadClut(pixelFormat);
}
}
shaderContext.setTexPixelFormat(pixelFormat);
super.setTextureFormat(pixelFormat, swizzle);
}
@Override
public void setVertexColor(float[] color) {
shaderContext.setVertexColor(color);
super.setVertexColor(color);
}
@Override
public void setDepthFunc(int func) {
if (useShaderDepthTest) {
shaderContext.setDepthFunc(func);
}
super.setDepthFunc(func);
}
@Override
public void setDepthMask(boolean depthWriteEnabled) {
if (useShaderDepthTest) {
shaderContext.setDepthMask(depthWriteEnabled ? 0xFF : 0x00);
}
super.setDepthMask(depthWriteEnabled);
}
@Override
public void setStencilFunc(int func, int ref, int mask) {
if (useShaderStencilTest) {
shaderContext.setStencilFunc(func);
// Pre-mask the reference value with the mask value
shaderContext.setStencilRef(ref & mask);
shaderContext.setStencilMask(mask);
}
super.setStencilFunc(func, ref, mask);
}
@Override
public void setStencilOp(int fail, int zfail, int zpass) {
if (useShaderStencilTest) {
shaderContext.setStencilOpFail(fail);
shaderContext.setStencilOpZFail(zfail);
shaderContext.setStencilOpZPass(zpass);
}
super.setStencilOp(fail, zfail, zpass);
}
@Override
public void setColorMask(int redMask, int greenMask, int blueMask, int alphaMask) {
if (useShaderColorMask) {
shaderContext.setColorMask(redMask, greenMask, blueMask, alphaMask);
// The pre-computed not-values for the color masks
shaderContext.setNotColorMask((~redMask) & 0xFF, (~greenMask) & 0xFF, (~blueMask) & 0xFF, (~alphaMask) & 0xFF);
// The color mask is enabled when at least one color mask is non-zero
shaderContext.setColorMaskEnable(redMask != 0x00 || greenMask != 0x00 || blueMask != 0x00 || alphaMask != 0x00 ? 1 : 0);
// Do not call the "super" method in BaseRenderingEngineFunction
proxy.setColorMask(redMask, greenMask, blueMask, alphaMask);
// Set the on/off color masks
re.setColorMask(redMask != 0xFF, greenMask != 0xFF, blueMask != 0xFF, stencilTestFlag);
} else {
super.setColorMask(redMask, greenMask, blueMask, alphaMask);
}
}
@Override
public void setAlphaFunc(int func, int ref, int mask) {
if (useShaderAlphaTest) {
shaderContext.setAlphaTestFunc(func);
shaderContext.setAlphaTestRef(ref & mask);
shaderContext.setAlphaTestMask(mask);
}
super.setAlphaFunc(func, ref, mask);
}
@Override
public void setBlendEquation(int mode) {
if (useShaderBlendTest) {
shaderContext.setBlendEquation(mode);
}
super.setBlendEquation(mode);
}
@Override
public void setBlendFunc(int src, int dst) {
if (useShaderBlendTest) {
shaderContext.setBlendSrc(src);
shaderContext.setBlendDst(dst);
// Do not call the "super" method in BaseRenderingEngineFunction
proxy.setBlendFunc(src, dst);
} else {
super.setBlendFunc(src, dst);
}
}
@Override
public void setBlendSFix(int sfix, float[] color) {
if (useShaderBlendTest) {
shaderContext.setBlendSFix(color);
// Do not call the "super" method in BaseRenderingEngineFunction
proxy.setBlendSFix(sfix, color);
} else {
super.setBlendSFix(sfix, color);
}
}
@Override
public void setBlendDFix(int dfix, float[] color) {
if (useShaderBlendTest) {
shaderContext.setBlendDFix(color);
// Do not call the "super" method in BaseRenderingEngineFunction
proxy.setBlendDFix(dfix, color);
} else {
super.setBlendDFix(dfix, color);
}
}
@Override
public void setViewport(int x, int y, int width, int height) {
// Remember the viewport size for later use in loadFbTexture().
viewportWidth = width;
viewportHeight = height;
super.setViewport(x, y, width, height);
}
@Override
public boolean setCopyRedToAlpha(boolean copyRedToAlpha) {
shaderContext.setCopyRedToAlpha(copyRedToAlpha ? 1 : 0);
return super.setCopyRedToAlpha(copyRedToAlpha);
}
@Override
public void setTextureWrapMode(int s, int t) {
shaderContext.setWrapModeS(s);
shaderContext.setWrapModeT(t);
super.setTextureWrapMode(s, t);
}
@Override
public void setFogColor(float[] color) {
shaderContext.setFogColor(color);
super.setFogColor(color);
}
@Override
public void setFogDist(float end, float scale) {
shaderContext.setFogEnd(end);
shaderContext.setFogScale(scale);
super.setFogDist(end, scale);
}
}