// Here we must take all the bits of the gstate that determine what the fragment shader will // look like, and concatenate them together into an ID. void ComputeFragmentShaderID(FragmentShaderID *id) { memset(&id->d[0], 0, sizeof(id->d)); if (gstate.clearmode & 1) { // We only need one clear shader, so let's ignore the rest of the bits. id->d[0] = 1; } else { int lmode = (gstate.lmode & 1) && gstate.isLightingEnabled(); bool enableFog = gstate.isFogEnabled() && !gstate.isModeThrough(); bool enableAlphaTest = gstate.isAlphaTestEnabled() && !IsAlphaTestTriviallyTrue(); bool enableColorTest = gstate.isColorTestEnabled() && !IsColorTestTriviallyTrue(); bool enableColorDoubling = (gstate.texfunc & 0x10000) != 0; // This isn't really correct, but it's a hack to get doubled blend modes to work more correctly. bool enableAlphaDoubling = CanDoubleSrcBlendMode(); bool doTextureProjection = gstate.getUVGenMode() == 1; // id->d[0] |= (gstate.clearmode & 1); if (gstate.isTextureMapEnabled()) { id->d[0] |= 1 << 1; id->d[0] |= (gstate.texfunc & 0x7) << 2; id->d[0] |= ((gstate.texfunc & 0x100) >> 8) << 5; // rgb or rgba id->d[0] |= ((gstate.texfunc & 0x10000) >> 16) << 6; // color double } id->d[0] |= (lmode & 1) << 7; id->d[0] |= gstate.isAlphaTestEnabled() << 8; if (enableAlphaTest) id->d[0] |= (gstate.alphatest & 0x7) << 9; // alpha test func id->d[0] |= gstate.isColorTestEnabled() << 12; if (enableColorTest) id->d[0] |= (gstate.colortest & 0x3) << 13; // color test func id->d[0] |= (enableFog & 1) << 15; id->d[0] |= (doTextureProjection & 1) << 16; id->d[0] |= (enableColorDoubling & 1) << 17; id->d[0] |= (enableAlphaDoubling & 1) << 18; }
// TODO: Setting to disable? bool ShouldUseShaderBlending() { if (!gstate.isAlphaBlendEnabled()) { return false; } // We can't blit on GLES2, so we don't support it. We also want texelFetch (OpenGL 3.0+ / GLES3+.) if (!gl_extensions.VersionGEThan(3, 0, 0) && !gl_extensions.GLES3) { return false; } GEBlendSrcFactor funcA = gstate.getBlendFuncA(); GEBlendDstFactor funcB = gstate.getBlendFuncB(); GEBlendMode eq = gstate.getBlendEq(); if (eq == GE_BLENDMODE_ABSDIFF) { return true; } // This normally involves a blit, so try to skip it. if (AlphaToColorDoubling() || CanDoubleSrcBlendMode()) { return false; } switch (funcA) { case GE_SRCBLEND_DOUBLESRCALPHA: case GE_SRCBLEND_DOUBLEINVSRCALPHA: case GE_SRCBLEND_DOUBLEDSTALPHA: case GE_SRCBLEND_DOUBLEINVDSTALPHA: return true; case GE_SRCBLEND_FIXA: if (funcB == GE_DSTBLEND_FIXB) { u32 fixA = gstate.getFixA(); u32 fixB = gstate.getFixB(); // OpenGL only supports one constant color, so check if we could be more exact. if (fixA != fixB && fixA != 0xFFFFFF - fixB && fixA != 0 && fixB != 0 && fixA != 0xFFFFFF && fixB != 0xFFFFFF) { return true; } } default: break; } switch (funcB) { case GE_DSTBLEND_DOUBLESRCALPHA: case GE_DSTBLEND_DOUBLEINVSRCALPHA: case GE_DSTBLEND_DOUBLEDSTALPHA: case GE_DSTBLEND_DOUBLEINVDSTALPHA: return true; default: break; } return false; }
// Here we must take all the bits of the gstate that determine what the fragment shader will // look like, and concatenate them together into an ID. void ComputeFragmentShaderID(FragmentShaderID *id) { memset(&id->d[0], 0, sizeof(id->d)); if (gstate.isModeClear()) { // We only need one clear shader, so let's ignore the rest of the bits. id->d[0] = 1; } else { bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled(); bool enableFog = gstate.isFogEnabled() && !gstate.isModeThrough(); bool enableAlphaTest = gstate.isAlphaTestEnabled() && !IsAlphaTestTriviallyTrue() && !g_Config.bDisableAlphaTest; bool enableColorTest = gstate.isColorTestEnabled() && !IsColorTestTriviallyTrue(); bool enableColorDoubling = gstate.isColorDoublingEnabled(); // This isn't really correct, but it's a hack to get doubled blend modes to work more correctly. bool enableAlphaDoubling = CanDoubleSrcBlendMode(); bool doTextureProjection = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX; bool doTextureAlpha = gstate.isTextureAlphaUsed(); ReplaceAlphaType stencilToAlpha = ReplaceAlphaWithStencil(); // All texfuncs except replace are the same for RGB as for RGBA with full alpha. if (gstate_c.textureFullAlpha && gstate.getTextureFunction() != GE_TEXFUNC_REPLACE) doTextureAlpha = false; // id->d[0] |= (gstate.isModeClear() & 1); if (gstate.isTextureMapEnabled()) { id->d[0] |= 1 << 1; id->d[0] |= gstate.getTextureFunction() << 2; id->d[0] |= (doTextureAlpha & 1) << 5; // rgb or rgba } id->d[0] |= (lmode & 1) << 7; id->d[0] |= enableAlphaTest << 8; if (enableAlphaTest) id->d[0] |= gstate.getAlphaTestFunction() << 9; id->d[0] |= enableColorTest << 12; if (enableColorTest) id->d[0] |= gstate.getColorTestFunction() << 13; // color test func id->d[0] |= (enableFog & 1) << 15; id->d[0] |= (doTextureProjection & 1) << 16; id->d[0] |= (enableColorDoubling & 1) << 17; id->d[0] |= (enableAlphaDoubling & 1) << 18; id->d[0] |= (stencilToAlpha) << 19; if (stencilToAlpha != REPLACE_ALPHA_NO) { // 3 bits id->d[0] |= ReplaceAlphaWithStencilType() << 21; } if (enableAlphaTest) gpuStats.numAlphaTestedDraws++; else gpuStats.numNonAlphaTestedDraws++; } }
// Missing: Z depth range // Also, logic ops etc, of course. Urgh. void GenerateFragmentShader(char *buffer) { char *p = buffer; #if defined(GLSL_ES_1_0) WRITE(p, "precision lowp float;\n"); #elif !defined(FORCE_OPENGL_2_0) WRITE(p, "#version 110\n"); #endif int lmode = (gstate.lmode & 1) && gstate.isLightingEnabled(); int doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear(); bool enableFog = gstate.isFogEnabled() && !gstate.isModeThrough() && !gstate.isModeClear(); bool enableAlphaTest = gstate.isAlphaTestEnabled() && !IsAlphaTestTriviallyTrue() && !gstate.isModeClear(); bool enableColorTest = gstate.isColorTestEnabled() && !IsColorTestTriviallyTrue() && !gstate.isModeClear(); bool enableColorDoubling = (gstate.texfunc & 0x10000) != 0; // This isn't really correct, but it's a hack to get doubled blend modes to work more correctly. bool enableAlphaDoubling = CanDoubleSrcBlendMode(); bool doTextureProjection = gstate.getUVGenMode() == 1; bool doTextureAlpha = (gstate.texfunc & 0x100) != 0; if (gstate_c.textureFullAlpha && (gstate.texfunc & 0x7) != GE_TEXFUNC_REPLACE) doTextureAlpha = false; if (doTexture) WRITE(p, "uniform sampler2D tex;\n"); if (enableAlphaTest || enableColorTest) { WRITE(p, "uniform vec4 u_alphacolorref;\n"); WRITE(p, "uniform vec4 u_colormask;\n"); } if (gstate.isTextureMapEnabled()) WRITE(p, "uniform vec3 u_texenv;\n"); WRITE(p, "varying vec4 v_color0;\n"); if (lmode) WRITE(p, "varying vec3 v_color1;\n"); if (enableFog) { WRITE(p, "uniform vec3 u_fogcolor;\n"); #if defined(GLSL_ES_1_0) WRITE(p, "varying mediump float v_fogdepth;\n"); #else WRITE(p, "varying float v_fogdepth;\n"); #endif } if (doTexture) { if (doTextureProjection) WRITE(p, "varying vec3 v_texcoord;\n"); else WRITE(p, "varying vec2 v_texcoord;\n"); } WRITE(p, "float roundAndScaleTo255f(in float x) { return floor(x * 255.0 + 0.5); }\n"); WRITE(p, "vec3 roundAndScaleTo255v(in vec3 x) { return floor(x * 255.0 + 0.5); }\n"); WRITE(p, "void main() {\n"); if (gstate.isModeClear()) { // Clear mode does not allow any fancy shading. WRITE(p, " gl_FragColor = v_color0;\n"); } else { const char *secondary = ""; // Secondary color for specular on top of texture if (lmode) { WRITE(p, " vec4 s = vec4(v_color1, 0.0);\n"); secondary = " + s"; } else { secondary = ""; } if (gstate.textureMapEnable & 1) { if (doTextureProjection) { WRITE(p, " vec4 t = texture2DProj(tex, v_texcoord);\n"); } else { WRITE(p, " vec4 t = texture2D(tex, v_texcoord);\n"); } WRITE(p, " vec4 p = v_color0;\n"); if (doTextureAlpha) { // texfmt == RGBA switch (gstate.texfunc & 0x7) { case GE_TEXFUNC_MODULATE: WRITE(p, " vec4 v = p * t%s;\n", secondary); break; case GE_TEXFUNC_DECAL: WRITE(p, " vec4 v = vec4(mix(p.rgb, t.rgb, t.a), p.a)%s;\n", secondary); break; case GE_TEXFUNC_BLEND: WRITE(p, " vec4 v = vec4(mix(p.rgb, u_texenv.rgb, t.rgb), p.a * t.a)%s;\n", secondary); break; case GE_TEXFUNC_REPLACE: WRITE(p, " vec4 v = t%s;\n", secondary); break; case GE_TEXFUNC_ADD: WRITE(p, " vec4 v = vec4(p.rgb + t.rgb, p.a * t.a)%s;\n", secondary); break; default: WRITE(p, " vec4 v = p;\n"); break; } } else { // texfmt == RGB switch (gstate.texfunc & 0x7) { case GE_TEXFUNC_MODULATE: WRITE(p, " vec4 v = vec4(t.rgb * p.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_DECAL: WRITE(p, " vec4 v = vec4(t.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_BLEND: WRITE(p, " vec4 v = vec4(mix(p.rgb, u_texenv.rgb, t.rgb), p.a)%s;\n", secondary); break; case GE_TEXFUNC_REPLACE: WRITE(p, " vec4 v = vec4(t.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_ADD: WRITE(p, " vec4 v = vec4(p.rgb + t.rgb, p.a)%s;\n", secondary); break; default: WRITE(p, " vec4 v = p;\n"); break; } } } else { // No texture mapping WRITE(p, " vec4 v = v_color0 %s;\n", secondary); } if (enableAlphaTest) { int alphaTestFunc = gstate.alphatest & 7; const char *alphaTestFuncs[] = { "#", "#", " != ", " == ", " >= ", " > ", " <= ", " < " }; // never/always don't make sense if (alphaTestFuncs[alphaTestFunc][0] != '#') { WRITE(p, " if (roundAndScaleTo255f(v.a) %s u_alphacolorref.a) discard;\n", alphaTestFuncs[alphaTestFunc]); } } // TODO: Before or after the color test? if (enableColorDoubling && enableAlphaDoubling) { WRITE(p, " v = v * 2.0;\n"); } else if (enableColorDoubling) { WRITE(p, " v.rgb = v.rgb * 2.0;\n"); } else if (enableAlphaDoubling) { WRITE(p, " v.a = v.a * 2.0;\n"); } if (enableColorTest) { int colorTestFunc = gstate.colortest & 3; const char *colorTestFuncs[] = { "#", "#", " != ", " == " }; // never/always don't make sense int colorTestMask = gstate.colormask; if (colorTestFuncs[colorTestFunc][0] != '#') { WRITE(p, "if (roundAndScaleTo255v(v.rgb) %s u_alphacolorref.rgb) discard;\n", colorTestFuncs[colorTestFunc]); } } if (enableFog) { WRITE(p, " float fogCoef = clamp(v_fogdepth, 0.0, 1.0);\n"); WRITE(p, " gl_FragColor = mix(vec4(u_fogcolor, v.a), v, fogCoef);\n"); // WRITE(p, " v.x = v_depth;\n"); } else { WRITE(p, " gl_FragColor = v;\n"); } } #ifdef DEBUG_SHADER if (doTexture) { WRITE(p, " gl_FragColor = texture2D(tex, v_texcoord.xy);\n"); } else { WRITE(p, " gl_FragColor = vec4(1,0,1,1);\n"); } #endif WRITE(p, "}\n"); }
// Missing: Z depth range void GenerateFragmentShader(char *buffer) { char *p = buffer; bool highpFog = false; #if defined(GLSL_ES_1_0) WRITE(p, "#version 100\n"); // GLSL ES 1.0 WRITE(p, "precision lowp float;\n"); // PowerVR needs highp to do the fog in MHU correctly. // Others don't, and some can't handle highp in the fragment shader. highpFog = gl_extensions.gpuVendor == GPU_VENDOR_POWERVR; #elif !defined(FORCE_OPENGL_2_0) WRITE(p, "#version 110\n"); // Remove lowp/mediump in non-mobile implementations WRITE(p, "#define lowp\n"); WRITE(p, "#define mediump\n"); WRITE(p, "#define highp\n"); #else // Remove lowp/mediump in non-mobile implementations WRITE(p, "#define lowp\n"); WRITE(p, "#define mediump\n"); WRITE(p, "#define highp\n"); #endif bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled(); bool doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear(); bool enableFog = gstate.isFogEnabled() && !gstate.isModeThrough() && !gstate.isModeClear(); bool enableAlphaTest = gstate.isAlphaTestEnabled() && !IsAlphaTestTriviallyTrue() && !gstate.isModeClear() && !g_Config.bDisableAlphaTest; bool enableColorTest = gstate.isColorTestEnabled() && !IsColorTestTriviallyTrue() && !gstate.isModeClear(); bool enableColorDoubling = gstate.isColorDoublingEnabled(); // This isn't really correct, but it's a hack to get doubled blend modes to work more correctly. bool enableAlphaDoubling = CanDoubleSrcBlendMode(); bool doTextureProjection = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX; bool doTextureAlpha = gstate.isTextureAlphaUsed(); bool stencilToAlpha = !gstate.isModeClear() && gstate.isStencilTestEnabled() && !gstate.isAlphaBlendEnabled(); if (gstate_c.textureFullAlpha && gstate.getTextureFunction() != GE_TEXFUNC_REPLACE) doTextureAlpha = false; if (doTexture) WRITE(p, "uniform sampler2D tex;\n"); if (enableAlphaTest || enableColorTest) { WRITE(p, "uniform vec4 u_alphacolorref;\n"); } if (stencilToAlpha && ReplaceAlphaWithStencilType() == STENCIL_VALUE_UNIFORM) { WRITE(p, "uniform float u_stencilReplaceValue;\n"); } if (gstate.isTextureMapEnabled() && gstate.getTextureFunction() == GE_TEXFUNC_BLEND) WRITE(p, "uniform lowp vec3 u_texenv;\n"); WRITE(p, "varying lowp vec4 v_color0;\n"); if (lmode) WRITE(p, "varying lowp vec3 v_color1;\n"); if (enableFog) { WRITE(p, "uniform lowp vec3 u_fogcolor;\n"); WRITE(p, "varying %s float v_fogdepth;\n", highpFog ? "highp" : "mediump"); } if (doTexture) { if (doTextureProjection) WRITE(p, "varying mediump vec3 v_texcoord;\n"); else WRITE(p, "varying mediump vec2 v_texcoord;\n"); } if (enableAlphaTest) { if (gl_extensions.gpuVendor == GPU_VENDOR_POWERVR) WRITE(p, "float roundTo255thf(in mediump float x) { mediump float y = x + (0.5/255.0); return y - fract(y * 255.0) * (1.0 / 255.0); }\n"); else WRITE(p, "float roundAndScaleTo255f(in float x) { return floor(x * 255.0 + 0.5); }\n"); } if (enableColorTest) { if (gl_extensions.gpuVendor == GPU_VENDOR_POWERVR) WRITE(p, "vec3 roundTo255thv(in vec3 x) { vec3 y = x + (0.5/255.0); return y - fract(y * 255.0) * (1.0 / 255.0); }\n"); else WRITE(p, "vec3 roundAndScaleTo255v(in vec3 x) { return floor(x * 255.0 + 0.5); }\n"); } WRITE(p, "void main() {\n"); if (gstate.isModeClear()) { // Clear mode does not allow any fancy shading. WRITE(p, " gl_FragColor = v_color0;\n"); } else { const char *secondary = ""; // Secondary color for specular on top of texture if (lmode) { WRITE(p, " vec4 s = vec4(v_color1, 0.0);\n"); secondary = " + s"; } else { secondary = ""; } if (gstate.isTextureMapEnabled()) { if (doTextureProjection) { WRITE(p, " vec4 t = texture2DProj(tex, v_texcoord);\n"); } else { WRITE(p, " vec4 t = texture2D(tex, v_texcoord);\n"); } WRITE(p, " vec4 p = v_color0;\n"); if (doTextureAlpha) { // texfmt == RGBA switch (gstate.getTextureFunction()) { case GE_TEXFUNC_MODULATE: WRITE(p, " vec4 v = p * t%s;\n", secondary); break; case GE_TEXFUNC_DECAL: WRITE(p, " vec4 v = vec4(mix(p.rgb, t.rgb, t.a), p.a)%s;\n", secondary); break; case GE_TEXFUNC_BLEND: WRITE(p, " vec4 v = vec4(mix(p.rgb, u_texenv.rgb, t.rgb), p.a * t.a)%s;\n", secondary); break; case GE_TEXFUNC_REPLACE: WRITE(p, " vec4 v = t%s;\n", secondary); break; case GE_TEXFUNC_ADD: WRITE(p, " vec4 v = vec4(p.rgb + t.rgb, p.a * t.a)%s;\n", secondary); break; default: WRITE(p, " vec4 v = p;\n"); break; } } else { // texfmt == RGB switch (gstate.getTextureFunction()) { case GE_TEXFUNC_MODULATE: WRITE(p, " vec4 v = vec4(t.rgb * p.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_DECAL: WRITE(p, " vec4 v = vec4(t.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_BLEND: WRITE(p, " vec4 v = vec4(mix(p.rgb, u_texenv.rgb, t.rgb), p.a)%s;\n", secondary); break; case GE_TEXFUNC_REPLACE: WRITE(p, " vec4 v = vec4(t.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_ADD: WRITE(p, " vec4 v = vec4(p.rgb + t.rgb, p.a)%s;\n", secondary); break; default: WRITE(p, " vec4 v = p;\n"); break; } } } else { // No texture mapping WRITE(p, " vec4 v = v_color0 %s;\n", secondary); } if (enableAlphaTest) { GEComparison alphaTestFunc = gstate.getAlphaTestFunction(); const char *alphaTestFuncs[] = { "#", "#", " != ", " == ", " >= ", " > ", " <= ", " < " }; // never/always don't make sense if (alphaTestFuncs[alphaTestFunc][0] != '#') { if (gl_extensions.gpuVendor == GPU_VENDOR_POWERVR) { // Work around bad PVR driver problem where equality check + discard just doesn't work. if (alphaTestFunc != 3) WRITE(p, " if (roundTo255thf(v.a) %s u_alphacolorref.a) discard;\n", alphaTestFuncs[alphaTestFunc]); } else { WRITE(p, " if (roundAndScaleTo255f(v.a) %s u_alphacolorref.a) discard;\n", alphaTestFuncs[alphaTestFunc]); } } } // TODO: Before or after the color test? if (enableColorDoubling && enableAlphaDoubling) { WRITE(p, " v = v * 2.0;\n"); } else if (enableColorDoubling) { WRITE(p, " v.rgb = v.rgb * 2.0;\n"); } else if (enableAlphaDoubling) { WRITE(p, " v.a = v.a * 2.0;\n"); } if (enableColorTest) { GEComparison colorTestFunc = gstate.getColorTestFunction(); const char *colorTestFuncs[] = { "#", "#", " != ", " == " }; // never/always don't make sense WARN_LOG_REPORT_ONCE(colortest, G3D, "Color test function : %s", colorTestFuncs[colorTestFunc]); u32 colorTestMask = gstate.getColorTestMask(); if (colorTestFuncs[colorTestFunc][0] != '#') { if (gl_extensions.gpuVendor == GPU_VENDOR_POWERVR) WRITE(p, " if (roundTo255thv(v.rgb) %s u_alphacolorref.rgb) discard;\n", colorTestFuncs[colorTestFunc]); else WRITE(p, " if (roundAndScaleTo255v(v.rgb) %s u_alphacolorref.rgb) discard;\n", colorTestFuncs[colorTestFunc]); } } if (enableFog) { WRITE(p, " float fogCoef = clamp(v_fogdepth, 0.0, 1.0);\n"); WRITE(p, " gl_FragColor = mix(vec4(u_fogcolor, v.a), v, fogCoef);\n"); // WRITE(p, " v.x = v_depth;\n"); } else { WRITE(p, " gl_FragColor = v;\n"); } } if (stencilToAlpha) { switch (ReplaceAlphaWithStencilType()) { case STENCIL_VALUE_UNIFORM: WRITE(p, " gl_FragColor.a = u_stencilReplaceValue;\n"); break; case STENCIL_VALUE_ZERO: WRITE(p, " gl_FragColor.a = 0.0;\n"); break; case STENCIL_VALUE_ONE: WRITE(p, " gl_FragColor.a = 1.0;\n"); break; case STENCIL_VALUE_UNKNOWN: // Maybe we should even mask away alpha using glColorMask and not change it at all? We do get here // if the stencil mode is KEEP for example. WRITE(p, " gl_FragColor.a = 0.0;\n"); break; case STENCIL_VALUE_KEEP: // Do nothing. We'll mask out the alpha using color mask. break; } } #ifdef DEBUG_SHADER if (doTexture) { WRITE(p, " gl_FragColor = texture2D(tex, v_texcoord.xy);\n"); WRITE(p, " gl_FragColor += vec4(0.3,0,0.3,0.3);\n"); } else { WRITE(p, " gl_FragColor = vec4(1,0,1,1);\n"); } #endif WRITE(p, "}\n"); }
// Missing: Z depth range // Also, logic ops etc, of course. Urgh. void GenerateFragmentShaderDX9(char *buffer) { char *p = buffer; bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled(); bool doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear(); bool enableFog = gstate.isFogEnabled() && !gstate.isModeThrough() && !gstate.isModeClear(); bool enableAlphaTest = gstate.isAlphaTestEnabled() && !IsAlphaTestTriviallyTrue() && !gstate.isModeClear(); bool enableColorTest = gstate.isColorTestEnabled() && !IsColorTestTriviallyTrue() && !gstate.isModeClear(); bool enableColorDoubling = gstate.isColorDoublingEnabled(); // This isn't really correct, but it's a hack to get doubled blend modes to work more correctly. bool enableAlphaDoubling = CanDoubleSrcBlendMode(); bool doTextureProjection = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX; bool doTextureAlpha = gstate.isTextureAlphaUsed(); if (gstate_c.textureFullAlpha && gstate.getTextureFunction() != GE_TEXFUNC_REPLACE) doTextureAlpha = false; if (doTexture) WRITE(p, "sampler tex: register(s0);\n"); if (enableAlphaTest || enableColorTest) { WRITE(p, "float4 u_alphacolorref : register(c%i);\n", CONST_PS_ALPHACOLORREF); WRITE(p, "float4 u_alphacolormask : register(c%i);\n", CONST_PS_ALPHACOLORMASK); } if (gstate.isTextureMapEnabled() && gstate.getTextureFunction() == GE_TEXFUNC_BLEND) { WRITE(p, "float3 u_texenv : register(c%i);\n", CONST_PS_TEXENV); } if (enableFog) { WRITE(p, "float3 u_fogcolor : register(c%i);\n", CONST_PS_FOGCOLOR); } if (enableAlphaTest) { WRITE(p, "float roundAndScaleTo255f(float x) { return floor(x * 255.0f + 0.5f); }\n"); } if (enableColorTest) { WRITE(p, "float3 roundAndScaleTo255v(float3 x) { return floor(x * 255.0f + 0.5f); }\n"); } WRITE(p, "struct PS_IN {\n"); if (doTexture) { if (doTextureProjection) WRITE(p, " float3 v_texcoord: TEXCOORD0;\n"); else WRITE(p, " float2 v_texcoord: TEXCOORD0;\n"); } WRITE(p, " float4 v_color0: COLOR0;\n"); if (lmode) { WRITE(p, " float3 v_color1: COLOR1;\n"); } if (enableFog) { WRITE(p, " float2 v_fogdepth: TEXCOORD1;\n"); } WRITE(p, "};\n"); WRITE(p, "float4 main( PS_IN In ) : COLOR\n"); WRITE(p, "{\n"); if (gstate.isModeClear()) { // Clear mode does not allow any fancy shading. WRITE(p, " return In.v_color0;\n"); } else { const char *secondary = ""; // Secondary color for specular on top of texture if (lmode) { WRITE(p, " float4 s = float4(In.v_color1, 0);\n"); secondary = " + s"; } else { secondary = ""; } if (gstate.isTextureMapEnabled()) { if (doTextureProjection) { WRITE(p, " float4 t = tex2Dproj(tex, float4(In.v_texcoord.x, In.v_texcoord.y, 0, In.v_texcoord.z))%s;\n", gstate_c.bgraTexture ? ".bgra" : ""); } else { WRITE(p, " float4 t = tex2D(tex, In.v_texcoord.xy)%s;\n", gstate_c.bgraTexture ? ".bgra" : ""); } WRITE(p, " float4 p = In.v_color0;\n"); if (doTextureAlpha) { // texfmt == RGBA switch (gstate.getTextureFunction()) { case GE_TEXFUNC_MODULATE: WRITE(p, " float4 v = p * t%s;\n", secondary); break; case GE_TEXFUNC_DECAL: WRITE(p, " float4 v = float4(lerp(p.rgb, t.rgb, t.a), p.a)%s;\n", secondary); break; case GE_TEXFUNC_BLEND: WRITE(p, " float4 v = float4(lerp(p.rgb, u_texenv.rgb, t.rgb), p.a * t.a)%s;\n", secondary); break; case GE_TEXFUNC_REPLACE: WRITE(p, " float4 v = t%s;\n", secondary); break; case GE_TEXFUNC_ADD: WRITE(p, " float4 v = float4(p.rgb + t.rgb, p.a * t.a)%s;\n", secondary); break; default: WRITE(p, " float4 v = p;\n"); break; } } else { // texfmt == RGB switch (gstate.getTextureFunction()) { case GE_TEXFUNC_MODULATE: WRITE(p, " float4 v = float4(t.rgb * p.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_DECAL: WRITE(p, " float4 v = float4(t.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_BLEND: WRITE(p, " float4 v = float4(lerp(p.rgb, u_texenv.rgb, t.rgb), p.a)%s;\n", secondary); break; case GE_TEXFUNC_REPLACE: WRITE(p, " float4 v = float4(t.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_ADD: WRITE(p, " float4 v = float4(p.rgb + t.rgb, p.a)%s;\n", secondary); break; default: WRITE(p, " float4 v = p;\n"); break; } } } else { // No texture mapping WRITE(p, " float4 v = In.v_color0 %s;\n", secondary); } #if !defined(DX9_USE_HW_ALPHA_TEST) if (enableAlphaTest) { GEComparison alphaTestFunc = gstate.getAlphaTestFunction(); const char *alphaTestFuncs[] = { "#", "#", " != ", " == ", " >= ", " > ", " <= ", " < " }; // never/always don't make sense if (alphaTestFuncs[alphaTestFunc][0] != '#') { // TODO: Rewrite this to use clip() appropriately (like, clip(v.a - u_alphacolorref.a)) WRITE(p, " if (roundAndScaleTo255f(v.a) %s u_alphacolorref.a) clip(-1);\n", alphaTestFuncs[alphaTestFunc]); } } #endif // TODO: Before or after the color test? if (enableColorDoubling && enableAlphaDoubling) { WRITE(p, " v = v * 2.0;\n"); } else if (enableColorDoubling) { WRITE(p, " v.rgb = v.rgb * 2.0;\n"); } else if (enableAlphaDoubling) { WRITE(p, " v.a = v.a * 2.0;\n"); } if (enableColorTest) { GEComparison colorTestFunc = gstate.getColorTestFunction(); const char *colorTestFuncs[] = { "#", "#", " != ", " == " }; // never/always don't make sense u32 colorTestMask = gstate.getColorTestMask(); if (colorTestFuncs[colorTestFunc][0] != '#') { const char * test = colorTestFuncs[colorTestFunc]; WRITE(p, "float3 colortest = roundAndScaleTo255v(v.rgb);\n"); WRITE(p, "if ((colortest.r %s u_alphacolorref.r) && (colortest.g %s u_alphacolorref.g) && (colortest.b %s u_alphacolorref.b )) clip(-1);\n", test, test, test); } } if (enableFog) { WRITE(p, " float fogCoef = clamp(In.v_fogdepth.x, 0.0, 1.0);\n"); WRITE(p, " return lerp(float4(u_fogcolor, v.a), v, fogCoef);\n"); } else { WRITE(p, " return v;\n"); } } WRITE(p, "}\n"); }
// Missing: Z depth range // Also, logic ops etc, of course. Urgh. void GenerateFragmentShaderDX9(char *buffer) { char *p = buffer; int lmode = lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled(); int doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear(); bool enableFog = gstate.isFogEnabled() && !gstate.isModeThrough() && !gstate.isModeClear(); bool enableAlphaTest = gstate.isAlphaTestEnabled() && !IsAlphaTestTriviallyTrue() && !gstate.isModeClear(); bool enableColorTest = gstate.isColorTestEnabled() && !IsColorTestTriviallyTrue() && !gstate.isModeClear(); bool enableColorDoubling = gstate.isColorDoublingEnabled(); // This isn't really correct, but it's a hack to get doubled blend modes to work more correctly. bool enableAlphaDoubling = CanDoubleSrcBlendMode(); bool doTextureProjection = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX; bool doTextureAlpha = gstate.isTextureAlphaUsed(); if (gstate_c.textureFullAlpha && gstate.getTextureFunction() != GE_TEXFUNC_REPLACE) doTextureAlpha = false; if (doTexture) WRITE(p, "sampler tex: register(s0);\n"); if (enableAlphaTest || enableColorTest) { WRITE(p, "float4 u_alphacolorref;\n"); WRITE(p, "float4 u_alphacolormask;\n"); } if (gstate.isTextureMapEnabled()) WRITE(p, "float3 u_texenv;\n"); if (enableFog) { WRITE(p, "float3 u_fogcolor;\n"); } if (enableAlphaTest) { WRITE(p, "float roundAndScaleTo255f(float x) { return floor(x * 255.0f + 0.5f); }\n"); WRITE(p, "float roundTo255th(float x) { float y = x + (0.5/255.0); return y - frac(y * 255.0) * (1.0 / 255.0); }\n"); } if (enableColorTest) { WRITE(p, "float3 roundAndScaleTo255v(float3 x) { return floor(x * 255.0f + 0.5f); }\n"); WRITE(p, "float3 roundTo255thv(float3 x) { float3 y = x + (0.5/255.0); return y - frac(y * 255.0) * (1.0 / 255.0); }\n"); } WRITE(p, " struct PS_IN \n"); WRITE(p, " { \n"); if (doTexture) { //if (doTextureProjection) // WRITE(p, "float3 v_texcoord: TEXCOORD0;\n"); //else // WRITE(p, "float2 v_texcoord: TEXCOORD0;\n"); } WRITE(p, " float4 v_texcoord: TEXCOORD0; \n"); WRITE(p, " float4 v_color0: COLOR0; \n"); WRITE(p, " float3 v_color1: COLOR1; \n"); if (enableFog) { WRITE(p, "float v_fogdepth:FOG;\n"); } WRITE(p, " }; \n"); WRITE(p, " \n"); WRITE(p, " float4 main( PS_IN In ) : COLOR \n"); WRITE(p, " { \n"); if (gstate.isModeClear()) { // Clear mode does not allow any fancy shading. WRITE(p, " return In.v_color0;\n"); } else { const char *secondary = ""; // Secondary color for specular on top of texture if (lmode) { WRITE(p, " float4 s = float4(In.v_color1, 0);\n"); secondary = " + s"; } else { secondary = ""; } if (gstate.isTextureMapEnabled()) { if (doTextureProjection) { WRITE(p, " float4 t = tex2Dproj(tex, In.v_texcoord);\n"); } else { WRITE(p, " float4 t = tex2D(tex, In.v_texcoord.xy);\n"); } WRITE(p, " float4 p = In.v_color0;\n"); if (doTextureAlpha) { // texfmt == RGBA switch (gstate.getTextureFunction()) { case GE_TEXFUNC_MODULATE: WRITE(p, " float4 v = p * t%s;\n", secondary); break; case GE_TEXFUNC_DECAL: WRITE(p, " float4 v = float4(lerp(p.rgb, t.rgb, t.a), p.a)%s;\n", secondary); break; case GE_TEXFUNC_BLEND: WRITE(p, " float4 v = float4(lerp(p.rgb, u_texenv.rgb, t.rgb), p.a * t.a)%s;\n", secondary); break; case GE_TEXFUNC_REPLACE: WRITE(p, " float4 v = t%s;\n", secondary); break; case GE_TEXFUNC_ADD: WRITE(p, " float4 v = float4(p.rgb + t.rgb, p.a * t.a)%s;\n", secondary); break; default: WRITE(p, " float4 v = p;\n"); break; } } else { // texfmt == RGB switch (gstate.getTextureFunction()) { case GE_TEXFUNC_MODULATE: WRITE(p, " float4 v = float4(t.rgb * p.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_DECAL: WRITE(p, " float4 v = float4(t.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_BLEND: WRITE(p, " float4 v = float4(lerp(p.rgb, u_texenv.rgb, t.rgb), p.a)%s;\n", secondary); break; case GE_TEXFUNC_REPLACE: WRITE(p, " float4 v = float4(t.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_ADD: WRITE(p, " float4 v = float4(p.rgb + t.rgb, p.a)%s;\n", secondary); break; default: WRITE(p, " float4 v = p;\n"); break; } } } else { // No texture mapping WRITE(p, " float4 v = In.v_color0 %s;\n", secondary); } if (enableAlphaTest) { GEComparison alphaTestFunc = gstate.getAlphaTestFunction(); const char *alphaTestFuncs[] = { "#", "#", " != ", " == ", " >= ", " > ", " <= ", " < " }; // never/always don't make sense if (alphaTestFuncs[alphaTestFunc][0] != '#') { // WRITE(p, " if (roundAndScaleTo255f(v.a) %s u_alphacolorref.a) discard;\n", alphaTestFuncs[alphaTestFunc]); //WRITE(p, "clip((roundAndScaleTo255f(v.rgb) %s u_alphacolorref.a)? -1:1);\n", alphaTestFuncs[alphaTestFunc]); //WRITE(p, " if (roundAndScaleTo255f(v.a) %s u_alphacolorref.a) clip(-1);\n", alphaTestFuncs[alphaTestFunc]); WRITE(p, " if (roundTo255th(v.a) %s u_alphacolorref.a) clip(-1);\n", alphaTestFuncs[alphaTestFunc]); //WRITE(p, " if (roundTo255th(v.a) %s u_alphacolorref.a) v.r=1;\n", alphaTestFuncs[alphaTestFunc]); } } // TODO: Before or after the color test? if (enableColorDoubling && enableAlphaDoubling) { WRITE(p, " v = v * 2.0;\n"); } else if (enableColorDoubling) { WRITE(p, " v.rgb = v.rgb * 2.0;\n"); } else if (enableAlphaDoubling) { WRITE(p, " v.a = v.a * 2.0;\n"); } if (enableColorTest) { GEComparison colorTestFunc = gstate.getColorTestFunction(); const char *colorTestFuncs[] = { "#", "#", " != ", " == " }; // never/always don't make sense u32 colorTestMask = gstate.getColorTestMask(); if (colorTestFuncs[colorTestFunc][0] != '#') { //WRITE(p, "clip((roundAndScaleTo255v(v.rgb) %s u_alphacolorref.rgb)? -1:1);\n", colorTestFuncs[colorTestFunc]); //WRITE(p, "if (roundAndScaleTo255v(v.rgb) %s u_alphacolorref.rgb) clip(-1);\n", colorTestFuncs[colorTestFunc]); // cleanup ? const char * test = colorTestFuncs[colorTestFunc]; //WRITE(p, "float3 colortest = roundAndScaleTo255v(v.rgb);\n"); WRITE(p, "float3 colortest = roundTo255thv(v.rgb);\n"); WRITE(p, "if ((colortest.r %s u_alphacolorref.r) && (colortest.g %s u_alphacolorref.g) && (colortest.b %s u_alphacolorref.b )) clip(-1);\n", test, test, test); } } if (enableFog) { WRITE(p, " float fogCoef = clamp(In.v_fogdepth, 0.0, 1.0);\n"); WRITE(p, " return lerp(float4(u_fogcolor, v.a), v, fogCoef);\n"); // WRITE(p, " v.x = v_depth;\n"); } else { WRITE(p, " return v;\n"); } } WRITE(p, "}\n"); }
// Missing: Z depth range void GenerateFragmentShader(char *buffer) { char *p = buffer; // In GLSL ES 3.0, you use "in" variables instead of varying. bool glslES30 = false; const char *varying = "varying"; const char *fragColor0 = "gl_FragColor"; const char *texture = "texture2D"; bool highpFog = false; #if defined(USING_GLES2) // Let's wait until we have a real use for this. // ES doesn't support dual source alpha :( if (gl_extensions.GLES3) { WRITE(p, "#version 300 es\n"); // GLSL ES 1.0 fragColor0 = "fragColor0"; texture = "texture"; glslES30 = true; } else { WRITE(p, "#version 100\n"); // GLSL ES 1.0 } WRITE(p, "precision lowp float;\n"); // PowerVR needs highp to do the fog in MHU correctly. // Others don't, and some can't handle highp in the fragment shader. highpFog = gl_extensions.gpuVendor == GPU_VENDOR_POWERVR; // GL_NV_shader_framebuffer_fetch available on mobile platform and ES 2.0 only but not desktop if (gl_extensions.NV_shader_framebuffer_fetch) { WRITE(p, " #extension GL_NV_shader_framebuffer_fetch : require\n"); } #elif !defined(FORCE_OPENGL_2_0) if (gl_extensions.VersionGEThan(3, 3, 0)) { fragColor0 = "fragColor0"; texture = "texture"; glslES30 = true; WRITE(p, "#version 330\n"); } else if (gl_extensions.VersionGEThan(3, 0, 0)) { fragColor0 = "fragColor0"; WRITE(p, "#version 130\n"); // Remove lowp/mediump in non-mobile non-glsl 3 implementations WRITE(p, "#define lowp\n"); WRITE(p, "#define mediump\n"); WRITE(p, "#define highp\n"); } else { WRITE(p, "#version 110\n"); // Remove lowp/mediump in non-mobile non-glsl 3 implementations WRITE(p, "#define lowp\n"); WRITE(p, "#define mediump\n"); WRITE(p, "#define highp\n"); } #else // Need to remove lowp/mediump for Mac WRITE(p, "#define lowp\n"); WRITE(p, "#define mediump\n"); WRITE(p, "#define highp\n"); #endif if (glslES30) { varying = "in"; } bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled(); bool doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear(); bool enableFog = gstate.isFogEnabled() && !gstate.isModeThrough() && !gstate.isModeClear(); bool enableAlphaTest = gstate.isAlphaTestEnabled() && !IsAlphaTestTriviallyTrue() && !gstate.isModeClear(); bool alphaTestAgainstZero = gstate.getAlphaTestRef() == 0; bool enableColorTest = gstate.isColorTestEnabled() && !IsColorTestTriviallyTrue() && !gstate.isModeClear(); bool alphaToColorDoubling = AlphaToColorDoubling(); bool enableColorDoubling = (gstate.isColorDoublingEnabled() && gstate.isTextureMapEnabled()) || alphaToColorDoubling; // This isn't really correct, but it's a hack to get doubled blend modes to work more correctly. bool enableAlphaDoubling = !alphaToColorDoubling && CanDoubleSrcBlendMode(); bool doTextureProjection = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX; bool doTextureAlpha = gstate.isTextureAlphaUsed(); ReplaceAlphaType stencilToAlpha = ReplaceAlphaWithStencil(); if (gstate_c.textureFullAlpha && gstate.getTextureFunction() != GE_TEXFUNC_REPLACE) doTextureAlpha = false; if (doTexture) WRITE(p, "uniform sampler2D tex;\n"); if (ShouldUseShaderBlending() && !gstate.isModeClear()) { if (!gl_extensions.NV_shader_framebuffer_fetch) { WRITE(p, "uniform sampler2D fbotex;\n"); } if (gstate.getBlendFuncA() == GE_SRCBLEND_FIXA) { WRITE(p, "uniform vec3 u_blendFixA;\n"); } if (gstate.getBlendFuncB() == GE_DSTBLEND_FIXB) { WRITE(p, "uniform vec3 u_blendFixB;\n"); } } if (enableAlphaTest || enableColorTest) { WRITE(p, "uniform vec4 u_alphacolorref;\n"); } if (stencilToAlpha && ReplaceAlphaWithStencilType() == STENCIL_VALUE_UNIFORM) { WRITE(p, "uniform float u_stencilReplaceValue;\n"); } if (gstate.isTextureMapEnabled() && gstate.getTextureFunction() == GE_TEXFUNC_BLEND) WRITE(p, "uniform vec3 u_texenv;\n"); WRITE(p, "%s vec4 v_color0;\n", varying); if (lmode) WRITE(p, "%s vec3 v_color1;\n", varying); if (enableFog) { WRITE(p, "uniform vec3 u_fogcolor;\n"); WRITE(p, "%s %s float v_fogdepth;\n", varying, highpFog ? "highp" : "mediump"); } if (doTexture) { if (doTextureProjection) WRITE(p, "%s mediump vec3 v_texcoord;\n", varying); else WRITE(p, "%s mediump vec2 v_texcoord;\n", varying); } if (enableAlphaTest && !alphaTestAgainstZero) { if (gl_extensions.gpuVendor == GPU_VENDOR_POWERVR) WRITE(p, "float roundTo255thf(in mediump float x) { mediump float y = x + (0.5/255.0); return y - fract(y * 255.0) * (1.0 / 255.0); }\n"); else WRITE(p, "float roundAndScaleTo255f(in float x) { return floor(x * 255.0 + 0.5); }\n"); } if (enableColorTest) { if (gl_extensions.gpuVendor == GPU_VENDOR_POWERVR) WRITE(p, "vec3 roundTo255thv(in vec3 x) { vec3 y = x + (0.5/255.0); return y - fract(y * 255.0) * (1.0 / 255.0); }\n"); else WRITE(p, "vec3 roundAndScaleTo255v(in vec3 x) { return floor(x * 255.0 + 0.5); }\n"); } if (stencilToAlpha == REPLACE_ALPHA_DUALSOURCE) { WRITE(p, "out vec4 fragColor0;\n"); WRITE(p, "out vec4 fragColor1;\n"); } else if (!strcmp(fragColor0, "fragColor0")) { WRITE(p, "out vec4 fragColor0;\n"); } WRITE(p, "void main() {\n"); if (gstate.isModeClear()) { // Clear mode does not allow any fancy shading. WRITE(p, " vec4 v = v_color0;\n"); } else { const char *secondary = ""; // Secondary color for specular on top of texture if (lmode) { WRITE(p, " vec4 s = vec4(v_color1, 0.0);\n"); secondary = " + s"; } else { secondary = ""; } if (gstate.isTextureMapEnabled()) { if (doTextureProjection) { WRITE(p, " vec4 t = %sProj(tex, v_texcoord);\n", texture); } else { WRITE(p, " vec4 t = %s(tex, v_texcoord);\n", texture); } WRITE(p, " vec4 p = v_color0;\n"); if (doTextureAlpha) { // texfmt == RGBA switch (gstate.getTextureFunction()) { case GE_TEXFUNC_MODULATE: WRITE(p, " vec4 v = p * t%s;\n", secondary); break; case GE_TEXFUNC_DECAL: WRITE(p, " vec4 v = vec4(mix(p.rgb, t.rgb, t.a), p.a)%s;\n", secondary); break; case GE_TEXFUNC_BLEND: WRITE(p, " vec4 v = vec4(mix(p.rgb, u_texenv.rgb, t.rgb), p.a * t.a)%s;\n", secondary); break; case GE_TEXFUNC_REPLACE: WRITE(p, " vec4 v = t%s;\n", secondary); break; case GE_TEXFUNC_ADD: case GE_TEXFUNC_UNKNOWN1: case GE_TEXFUNC_UNKNOWN2: case GE_TEXFUNC_UNKNOWN3: WRITE(p, " vec4 v = vec4(p.rgb + t.rgb, p.a * t.a)%s;\n", secondary); break; default: WRITE(p, " vec4 v = p;\n"); break; } } else { // texfmt == RGB switch (gstate.getTextureFunction()) { case GE_TEXFUNC_MODULATE: WRITE(p, " vec4 v = vec4(t.rgb * p.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_DECAL: WRITE(p, " vec4 v = vec4(t.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_BLEND: WRITE(p, " vec4 v = vec4(mix(p.rgb, u_texenv.rgb, t.rgb), p.a)%s;\n", secondary); break; case GE_TEXFUNC_REPLACE: WRITE(p, " vec4 v = vec4(t.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_ADD: case GE_TEXFUNC_UNKNOWN1: case GE_TEXFUNC_UNKNOWN2: case GE_TEXFUNC_UNKNOWN3: WRITE(p, " vec4 v = vec4(p.rgb + t.rgb, p.a)%s;\n", secondary); break; default: WRITE(p, " vec4 v = p;\n"); break; } } } else { // No texture mapping WRITE(p, " vec4 v = v_color0 %s;\n", secondary); } if (enableAlphaTest) { GEComparison alphaTestFunc = gstate.getAlphaTestFunction(); const char *alphaTestFuncs[] = { "#", "#", " != ", " == ", " >= ", " > ", " <= ", " < " }; // never/always don't make sense if (alphaTestFuncs[alphaTestFunc][0] != '#') { if (alphaTestAgainstZero) { // When testing against 0 (extremely common), we can avoid some math. // 0.002 is approximately half of 1.0 / 255.0. if (alphaTestFunc == GE_COMP_NOTEQUAL || alphaTestFunc == GE_COMP_GREATER) { WRITE(p, " if (v.a < 0.002) discard;\n"); } else { // Anything else is a test for == 0. Happens sometimes, actually... WRITE(p, " if (v.a > 0.002) discard;\n"); } } else if (gl_extensions.gpuVendor == GPU_VENDOR_POWERVR) { // Work around bad PVR driver problem where equality check + discard just doesn't work. if (alphaTestFunc != GE_COMP_NOTEQUAL) WRITE(p, " if (roundTo255thf(v.a) %s u_alphacolorref.a) discard;\n", alphaTestFuncs[alphaTestFunc]); } else { WRITE(p, " if (roundAndScaleTo255f(v.a) %s u_alphacolorref.a) discard;\n", alphaTestFuncs[alphaTestFunc]); } } } if (enableColorTest) { GEComparison colorTestFunc = gstate.getColorTestFunction(); const char *colorTestFuncs[] = { "#", "#", " != ", " == " }; // never/always don't make sense WARN_LOG_REPORT_ONCE(colortest, G3D, "Color test function : %s", colorTestFuncs[colorTestFunc]); u32 colorTestMask = gstate.getColorTestMask(); if (colorTestFuncs[colorTestFunc][0] != '#') { if (gl_extensions.gpuVendor == GPU_VENDOR_POWERVR) WRITE(p, " if (roundTo255thv(v.rgb) %s u_alphacolorref.rgb) discard;\n", colorTestFuncs[colorTestFunc]); else WRITE(p, " if (roundAndScaleTo255v(v.rgb) %s u_alphacolorref.rgb) discard;\n", colorTestFuncs[colorTestFunc]); } } // Color doubling happens after the color test. if (enableColorDoubling && enableAlphaDoubling) { WRITE(p, " v = v * 2.0;\n"); } else if (enableColorDoubling) { WRITE(p, " v.rgb = v.rgb * 2.0;\n"); } else if (enableAlphaDoubling) { WRITE(p, " v.a = v.a * 2.0;\n"); } if (enableFog) { WRITE(p, " float fogCoef = clamp(v_fogdepth, 0.0, 1.0);\n"); WRITE(p, " v = mix(vec4(u_fogcolor, v.a), v, fogCoef);\n"); // WRITE(p, " v.x = v_depth;\n"); } if (ShouldUseShaderBlending()) { // If we have NV_shader_framebuffer_fetch / EXT_shader_framebuffer_fetch, we skip the blit. // We can just read the prev value more directly. // TODO: EXT_shader_framebuffer_fetch on iOS 6, possibly others. if (gl_extensions.NV_shader_framebuffer_fetch) { WRITE(p, " lowp vec4 destColor = gl_LastFragData[0];\n"); } else { WRITE(p, " lowp vec4 destColor = texelFetch(fbotex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);\n"); } GEBlendSrcFactor funcA = gstate.getBlendFuncA(); GEBlendDstFactor funcB = gstate.getBlendFuncB(); GEBlendMode eq = gstate.getBlendEq(); const char *srcFactor = "vec3(1.0)"; const char *dstFactor = "vec3(0.0)"; switch (funcA) { case GE_SRCBLEND_DSTCOLOR: srcFactor = "destColor.rgb"; break; case GE_SRCBLEND_INVDSTCOLOR: srcFactor = "(vec3(1.0) - destColor.rgb)"; break; case GE_SRCBLEND_SRCALPHA: srcFactor = "vec3(v.a)"; break; case GE_SRCBLEND_INVSRCALPHA: srcFactor = "vec3(1.0 - v.a)"; break; case GE_SRCBLEND_DSTALPHA: srcFactor = "vec3(destColor.a)"; break; case GE_SRCBLEND_INVDSTALPHA: srcFactor = "vec3(1.0 - destColor.a)"; break; case GE_SRCBLEND_DOUBLESRCALPHA: srcFactor = "vec3(v.a * 2.0)"; break; // TODO: Double inverse, or inverse double? Following softgpu for now... case GE_SRCBLEND_DOUBLEINVSRCALPHA: srcFactor = "vec3(1.0 - v.a * 2.0)"; break; case GE_SRCBLEND_DOUBLEDSTALPHA: srcFactor = "vec3(destColor.a * 2.0)"; break; case GE_SRCBLEND_DOUBLEINVDSTALPHA: srcFactor = "vec3(1.0 - destColor.a * 2.0)"; break; case GE_SRCBLEND_FIXA: srcFactor = "u_blendFixA"; break; } switch (funcB) { case GE_DSTBLEND_SRCCOLOR: dstFactor = "v.rgb"; break; case GE_DSTBLEND_INVSRCCOLOR: dstFactor = "(vec3(1.0) - v.rgb)"; break; case GE_DSTBLEND_SRCALPHA: dstFactor = "vec3(v.a)"; break; case GE_DSTBLEND_INVSRCALPHA: dstFactor = "vec3(1.0 - v.a)"; break; case GE_DSTBLEND_DSTALPHA: dstFactor = "vec3(destColor.a)"; break; case GE_DSTBLEND_INVDSTALPHA: dstFactor = "vec3(1.0 - destColor.a)"; break; case GE_DSTBLEND_DOUBLESRCALPHA: dstFactor = "vec3(v.a * 2.0)"; break; case GE_DSTBLEND_DOUBLEINVSRCALPHA: dstFactor = "vec3(1.0 - v.a * 2.0)"; break; case GE_DSTBLEND_DOUBLEDSTALPHA: dstFactor = "vec3(destColor.a * 2.0)"; break; case GE_DSTBLEND_DOUBLEINVDSTALPHA: dstFactor = "vec3(1.0 - destColor.a * 2.0)"; break; case GE_DSTBLEND_FIXB: dstFactor = "u_blendFixB"; break; } switch (eq) { case GE_BLENDMODE_MUL_AND_ADD: WRITE(p, " v.rgb = v.rgb * %s + destColor.rgb * %s;\n", srcFactor, dstFactor); break; case GE_BLENDMODE_MUL_AND_SUBTRACT: WRITE(p, " v.rgb = v.rgb * %s - destColor.rgb * %s;\n", srcFactor, dstFactor); break; case GE_BLENDMODE_MUL_AND_SUBTRACT_REVERSE: WRITE(p, " v.rgb = destColor.rgb * %s - v.rgb * %s;\n", srcFactor, dstFactor); break; case GE_BLENDMODE_MIN: WRITE(p, " v.rgb = min(v.rgb, destColor.rgb);\n"); break; case GE_BLENDMODE_MAX: WRITE(p, " v.rgb = max(v.rgb, destColor.rgb);\n"); break; case GE_BLENDMODE_ABSDIFF: WRITE(p, " v.rgb = abs(v.rgb - destColor.rgb);\n"); break; } } } switch (stencilToAlpha) { case REPLACE_ALPHA_DUALSOURCE: WRITE(p, " fragColor0 = vec4(v.rgb, 0.0);\n"); WRITE(p, " fragColor1 = vec4(0.0, 0.0, 0.0, v.a);\n"); break; case REPLACE_ALPHA_YES: WRITE(p, " %s = vec4(v.rgb, 0.0);\n", fragColor0); break; case REPLACE_ALPHA_NO: WRITE(p, " %s = v;\n", fragColor0); break; } if (stencilToAlpha != REPLACE_ALPHA_NO) { switch (ReplaceAlphaWithStencilType()) { case STENCIL_VALUE_UNIFORM: WRITE(p, " %s.a = u_stencilReplaceValue;\n", fragColor0); break; case STENCIL_VALUE_ZERO: WRITE(p, " %s.a = 0.0;\n", fragColor0); break; case STENCIL_VALUE_ONE: WRITE(p, " %s.a = 1.0;\n", fragColor0); break; case STENCIL_VALUE_UNKNOWN: // Maybe we should even mask away alpha using glColorMask and not change it at all? We do get here // if the stencil mode is KEEP for example. WRITE(p, " %s.a = 0.0;\n", fragColor0); break; case STENCIL_VALUE_KEEP: // Do nothing. We'll mask out the alpha using color mask. break; } } #ifdef DEBUG_SHADER if (doTexture) { WRITE(p, " %s = texture2D(tex, v_texcoord.xy);\n", fragColor0); WRITE(p, " %s += vec4(0.3,0,0.3,0.3);\n", fragColor0); } else { WRITE(p, " %s = vec4(1,0,1,1);\n", fragColor0); } #endif WRITE(p, "}\n"); }
// Here we must take all the bits of the gstate that determine what the fragment shader will // look like, and concatenate them together into an ID. void ComputeFragmentShaderID(FragmentShaderID *id) { int id0 = 0; int id1 = 0; if (gstate.isModeClear()) { // We only need one clear shader, so let's ignore the rest of the bits. id0 = 1; } else { bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled(); bool enableFog = gstate.isFogEnabled() && !gstate.isModeThrough(); bool enableAlphaTest = gstate.isAlphaTestEnabled() && !IsAlphaTestTriviallyTrue(); bool alphaTestAgainstZero = gstate.getAlphaTestRef() == 0; bool enableColorTest = gstate.isColorTestEnabled() && !IsColorTestTriviallyTrue(); bool alphaToColorDoubling = AlphaToColorDoubling(); bool enableColorDoubling = (gstate.isColorDoublingEnabled() && gstate.isTextureMapEnabled()) || alphaToColorDoubling; // This isn't really correct, but it's a hack to get doubled blend modes to work more correctly. bool enableAlphaDoubling = !alphaToColorDoubling && CanDoubleSrcBlendMode(); bool doTextureProjection = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX; bool doTextureAlpha = gstate.isTextureAlphaUsed(); ReplaceAlphaType stencilToAlpha = ReplaceAlphaWithStencil(); // All texfuncs except replace are the same for RGB as for RGBA with full alpha. if (gstate_c.textureFullAlpha && gstate.getTextureFunction() != GE_TEXFUNC_REPLACE) doTextureAlpha = false; // id0 |= (gstate.isModeClear() & 1); if (gstate.isTextureMapEnabled()) { id0 |= 1 << 1; id0 |= gstate.getTextureFunction() << 2; id0 |= (doTextureAlpha & 1) << 5; // rgb or rgba } // 6 is free. id0 |= (lmode & 1) << 7; if (enableAlphaTest) { id0 |= 1 << 8; id0 |= gstate.getAlphaTestFunction() << 9; } if (enableColorTest) { id0 |= 1 << 12; id0 |= gstate.getColorTestFunction() << 13; // color test func } id0 |= (enableFog & 1) << 15; id0 |= (doTextureProjection & 1) << 16; id0 |= (enableColorDoubling & 1) << 17; id0 |= (enableAlphaDoubling & 1) << 18; id0 |= (stencilToAlpha) << 19; if (stencilToAlpha != REPLACE_ALPHA_NO) { // 3 bits id0 |= ReplaceAlphaWithStencilType() << 21; } id0 |= (alphaTestAgainstZero & 1) << 24; if (enableAlphaTest) gpuStats.numAlphaTestedDraws++; else gpuStats.numNonAlphaTestedDraws++; if (ShouldUseShaderBlending()) { // 12 bits total. id1 |= 1; id1 |= (gstate.getBlendEq() << 1); id1 |= (gstate.getBlendFuncA() << 4); id1 |= (gstate.getBlendFuncB() << 8); } } id->d[0] = id0; id->d[1] = id1; }