/* * QEMU Geforce NV2A pixel shader translation * * Copyright (c) 2013 espes * Copyright (c) 2015 Jannik Vogel * * Based on: * Cxbx, PixelShader.cpp * Copyright (c) 2004 Aaron Robinson * Kingofc * Xeon, XBD3DPixelShader.cpp * Copyright (c) 2003 _SF_ * * This program 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 2 or * (at your option) version 3 of the License. * * This program 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 this program; if not, see . */ #include "qemu/osdep.h" #include #include #include #include #include #include "qapi/qmp/qstring.h" #include "nv2a_shaders_common.h" #include "nv2a_psh.h" /* * This implements translation of register combiners into glsl * fragment shaders, but all terminology is in terms of Xbox DirectX * pixel shaders, since I wanted to be lazy while referencing existing * work / stealing code. * * For some background, see the OpenGL extension: * https://www.opengl.org/registry/specs/NV/register_combiners.txt */ enum PS_TEXTUREMODES { // valid in stage 0 1 2 3 PS_TEXTUREMODES_NONE= 0x00L, // * * * * PS_TEXTUREMODES_PROJECT2D= 0x01L, // * * * * PS_TEXTUREMODES_PROJECT3D= 0x02L, // * * * * PS_TEXTUREMODES_CUBEMAP= 0x03L, // * * * * PS_TEXTUREMODES_PASSTHRU= 0x04L, // * * * * PS_TEXTUREMODES_CLIPPLANE= 0x05L, // * * * * PS_TEXTUREMODES_BUMPENVMAP= 0x06L, // - * * * PS_TEXTUREMODES_BUMPENVMAP_LUM= 0x07L, // - * * * PS_TEXTUREMODES_BRDF= 0x08L, // - - * * PS_TEXTUREMODES_DOT_ST= 0x09L, // - - * * PS_TEXTUREMODES_DOT_ZW= 0x0aL, // - - * * PS_TEXTUREMODES_DOT_RFLCT_DIFF= 0x0bL, // - - * - PS_TEXTUREMODES_DOT_RFLCT_SPEC= 0x0cL, // - - - * PS_TEXTUREMODES_DOT_STR_3D= 0x0dL, // - - - * PS_TEXTUREMODES_DOT_STR_CUBE= 0x0eL, // - - - * PS_TEXTUREMODES_DPNDNT_AR= 0x0fL, // - * * * PS_TEXTUREMODES_DPNDNT_GB= 0x10L, // - * * * PS_TEXTUREMODES_DOTPRODUCT= 0x11L, // - * * - PS_TEXTUREMODES_DOT_RFLCT_SPEC_CONST= 0x12L, // - - - * // 0x13-0x1f reserved }; enum PS_INPUTMAPPING { PS_INPUTMAPPING_UNSIGNED_IDENTITY= 0x00L, // max(0,x) OK for final combiner PS_INPUTMAPPING_UNSIGNED_INVERT= 0x20L, // 1 - max(0,x) OK for final combiner PS_INPUTMAPPING_EXPAND_NORMAL= 0x40L, // 2*max(0,x) - 1 invalid for final combiner PS_INPUTMAPPING_EXPAND_NEGATE= 0x60L, // 1 - 2*max(0,x) invalid for final combiner PS_INPUTMAPPING_HALFBIAS_NORMAL= 0x80L, // max(0,x) - 1/2 invalid for final combiner PS_INPUTMAPPING_HALFBIAS_NEGATE= 0xa0L, // 1/2 - max(0,x) invalid for final combiner PS_INPUTMAPPING_SIGNED_IDENTITY= 0xc0L, // x invalid for final combiner PS_INPUTMAPPING_SIGNED_NEGATE= 0xe0L, // -x invalid for final combiner }; enum PS_REGISTER { PS_REGISTER_ZERO= 0x00L, // r PS_REGISTER_DISCARD= 0x00L, // w PS_REGISTER_C0= 0x01L, // r PS_REGISTER_C1= 0x02L, // r PS_REGISTER_FOG= 0x03L, // r PS_REGISTER_V0= 0x04L, // r/w PS_REGISTER_V1= 0x05L, // r/w PS_REGISTER_T0= 0x08L, // r/w PS_REGISTER_T1= 0x09L, // r/w PS_REGISTER_T2= 0x0aL, // r/w PS_REGISTER_T3= 0x0bL, // r/w PS_REGISTER_R0= 0x0cL, // r/w PS_REGISTER_R1= 0x0dL, // r/w PS_REGISTER_V1R0_SUM= 0x0eL, // r PS_REGISTER_EF_PROD= 0x0fL, // r PS_REGISTER_ONE= PS_REGISTER_ZERO | PS_INPUTMAPPING_UNSIGNED_INVERT, // OK for final combiner PS_REGISTER_NEGATIVE_ONE= PS_REGISTER_ZERO | PS_INPUTMAPPING_EXPAND_NORMAL, // invalid for final combiner PS_REGISTER_ONE_HALF= PS_REGISTER_ZERO | PS_INPUTMAPPING_HALFBIAS_NEGATE, // invalid for final combiner PS_REGISTER_NEGATIVE_ONE_HALF= PS_REGISTER_ZERO | PS_INPUTMAPPING_HALFBIAS_NORMAL, // invalid for final combiner }; enum PS_COMBINERCOUNTFLAGS { PS_COMBINERCOUNT_MUX_LSB= 0x0000L, // mux on r0.a lsb PS_COMBINERCOUNT_MUX_MSB= 0x0001L, // mux on r0.a msb PS_COMBINERCOUNT_SAME_C0= 0x0000L, // c0 same in each stage PS_COMBINERCOUNT_UNIQUE_C0= 0x0010L, // c0 unique in each stage PS_COMBINERCOUNT_SAME_C1= 0x0000L, // c1 same in each stage PS_COMBINERCOUNT_UNIQUE_C1= 0x0100L // c1 unique in each stage }; enum PS_COMBINEROUTPUT { PS_COMBINEROUTPUT_IDENTITY= 0x00L, // y = x PS_COMBINEROUTPUT_BIAS= 0x08L, // y = x - 0.5 PS_COMBINEROUTPUT_SHIFTLEFT_1= 0x10L, // y = x*2 PS_COMBINEROUTPUT_SHIFTLEFT_1_BIAS= 0x18L, // y = (x - 0.5)*2 PS_COMBINEROUTPUT_SHIFTLEFT_2= 0x20L, // y = x*4 PS_COMBINEROUTPUT_SHIFTRIGHT_1= 0x30L, // y = x/2 PS_COMBINEROUTPUT_AB_BLUE_TO_ALPHA= 0x80L, // RGB only PS_COMBINEROUTPUT_CD_BLUE_TO_ALPHA= 0x40L, // RGB only PS_COMBINEROUTPUT_AB_MULTIPLY= 0x00L, PS_COMBINEROUTPUT_AB_DOT_PRODUCT= 0x02L, // RGB only PS_COMBINEROUTPUT_CD_MULTIPLY= 0x00L, PS_COMBINEROUTPUT_CD_DOT_PRODUCT= 0x01L, // RGB only PS_COMBINEROUTPUT_AB_CD_SUM= 0x00L, // 3rd output is AB+CD PS_COMBINEROUTPUT_AB_CD_MUX= 0x04L, // 3rd output is MUX(AB,CD) based on R0.a }; enum PS_CHANNEL { PS_CHANNEL_RGB= 0x00, // used as RGB source PS_CHANNEL_BLUE= 0x00, // used as ALPHA source PS_CHANNEL_ALPHA= 0x10, // used as RGB or ALPHA source }; enum PS_FINALCOMBINERSETTING { PS_FINALCOMBINERSETTING_CLAMP_SUM= 0x80, // V1+R0 sum clamped to [0,1] PS_FINALCOMBINERSETTING_COMPLEMENT_V1= 0x40, // unsigned invert mapping PS_FINALCOMBINERSETTING_COMPLEMENT_R0= 0x20, // unsigned invert mapping }; // Structures to describe the PS definition struct InputInfo { int reg, mod, chan; }; struct InputVarInfo { struct InputInfo a, b, c, d; }; struct FCInputInfo { struct InputInfo a, b, c, d, e, f, g; bool v1r0_sum, clamp_sum, inv_v1, inv_r0, enabled; }; struct OutputInfo { int ab, cd, muxsum, flags, ab_op, cd_op, muxsum_op, mapping, ab_alphablue, cd_alphablue; }; struct PSStageInfo { struct InputVarInfo rgb_input, alpha_input; struct OutputInfo rgb_output, alpha_output; int c0, c1; }; struct PixelShader { PshState state; int num_stages, flags; struct PSStageInfo stage[8]; struct FCInputInfo final_input; int tex_modes[4], input_tex[4]; //uint32_t dot_mapping, input_texture; QString *varE, *varF; QString *code; int cur_stage; int num_var_refs; char var_refs[32][32]; int num_const_refs; char const_refs[32][32]; }; static void add_var_ref(struct PixelShader *ps, const char *var) { int i; for (i=0; inum_var_refs; i++) { if (strcmp((char*)ps->var_refs[i], var) == 0) return; } strcpy((char*)ps->var_refs[ps->num_var_refs++], var); } static void add_const_ref(struct PixelShader *ps, const char *var) { int i; for (i=0; inum_const_refs; i++) { if (strcmp((char*)ps->const_refs[i], var) == 0) return; } strcpy((char*)ps->const_refs[ps->num_const_refs++], var); } // Get the code for a variable used in the program static QString* get_var(struct PixelShader *ps, int reg, bool is_dest) { switch (reg) { case PS_REGISTER_DISCARD: if (is_dest) { return qstring_from_str(""); } else { return qstring_from_str("vec4(0.0)"); } break; case PS_REGISTER_C0: if (ps->flags & PS_COMBINERCOUNT_UNIQUE_C0 || ps->cur_stage == 8) { QString *reg = qstring_from_fmt("c0_%d", ps->cur_stage); add_const_ref(ps, qstring_get_str(reg)); return reg; } else { // Same c0 add_const_ref(ps, "c0_0"); return qstring_from_str("c0_0"); } break; case PS_REGISTER_C1: if (ps->flags & PS_COMBINERCOUNT_UNIQUE_C1 || ps->cur_stage == 8) { QString *reg = qstring_from_fmt("c1_%d", ps->cur_stage); add_const_ref(ps, qstring_get_str(reg)); return reg; } else { // Same c1 add_const_ref(ps, "c1_0"); return qstring_from_str("c1_0"); } break; case PS_REGISTER_FOG: return qstring_from_str("pFog"); case PS_REGISTER_V0: return qstring_from_str("v0"); case PS_REGISTER_V1: return qstring_from_str("v1"); case PS_REGISTER_T0: return qstring_from_str("t0"); case PS_REGISTER_T1: return qstring_from_str("t1"); case PS_REGISTER_T2: return qstring_from_str("t2"); case PS_REGISTER_T3: return qstring_from_str("t3"); case PS_REGISTER_R0: add_var_ref(ps, "r0"); return qstring_from_str("r0"); case PS_REGISTER_R1: add_var_ref(ps, "r1"); return qstring_from_str("r1"); case PS_REGISTER_V1R0_SUM: add_var_ref(ps, "r0"); return qstring_from_str("vec4(v1.rgb + r0.rgb, 0.0)"); case PS_REGISTER_EF_PROD: return qstring_from_fmt("vec4(%s * %s, 0.0)", qstring_get_str(ps->varE), qstring_get_str(ps->varF)); default: assert(false); break; } } // Get input variable code static QString* get_input_var(struct PixelShader *ps, struct InputInfo in, bool is_alpha) { QString *reg = get_var(ps, in.reg, false); if (!is_alpha) { switch (in.chan) { case PS_CHANNEL_RGB: qstring_append(reg, ".rgb"); break; case PS_CHANNEL_ALPHA: qstring_append(reg, ".aaa"); break; default: assert(false); break; } } else { switch (in.chan) { case PS_CHANNEL_BLUE: qstring_append(reg, ".b"); break; case PS_CHANNEL_ALPHA: qstring_append(reg, ".a"); break; default: assert(false); break; } } QString *res; switch (in.mod) { case PS_INPUTMAPPING_UNSIGNED_IDENTITY: res = qstring_from_fmt("max(%s, 0.0)", qstring_get_str(reg)); break; case PS_INPUTMAPPING_UNSIGNED_INVERT: res = qstring_from_fmt("(1.0 - clamp(%s, 0.0, 1.0))", qstring_get_str(reg)); break; case PS_INPUTMAPPING_EXPAND_NORMAL: res = qstring_from_fmt("(2.0 * max(%s, 0.0) - 1.0)", qstring_get_str(reg)); break; case PS_INPUTMAPPING_EXPAND_NEGATE: res = qstring_from_fmt("(-2.0 * max(%s, 0.0) + 1.0)", qstring_get_str(reg)); break; case PS_INPUTMAPPING_HALFBIAS_NORMAL: res = qstring_from_fmt("(max(%s, 0.0) - 0.5)", qstring_get_str(reg)); break; case PS_INPUTMAPPING_HALFBIAS_NEGATE: res = qstring_from_fmt("(-max(%s, 0.0) + 0.5)", qstring_get_str(reg)); break; case PS_INPUTMAPPING_SIGNED_IDENTITY: qobject_ref(reg); res = reg; break; case PS_INPUTMAPPING_SIGNED_NEGATE: res = qstring_from_fmt("-%s", qstring_get_str(reg)); break; default: assert(false); break; } qobject_unref(reg); return res; } // Get code for the output mapping of a stage static QString* get_output(QString *reg, int mapping) { QString *res; switch (mapping) { case PS_COMBINEROUTPUT_IDENTITY: qobject_ref(reg); res = reg; break; case PS_COMBINEROUTPUT_BIAS: res = qstring_from_fmt("(%s - 0.5)", qstring_get_str(reg)); break; case PS_COMBINEROUTPUT_SHIFTLEFT_1: res = qstring_from_fmt("(%s * 2.0)", qstring_get_str(reg)); break; case PS_COMBINEROUTPUT_SHIFTLEFT_1_BIAS: res = qstring_from_fmt("((%s - 0.5) * 2.0)", qstring_get_str(reg)); break; case PS_COMBINEROUTPUT_SHIFTLEFT_2: res = qstring_from_fmt("(%s * 4.0)", qstring_get_str(reg)); break; case PS_COMBINEROUTPUT_SHIFTRIGHT_1: res = qstring_from_fmt("(%s / 2.0)", qstring_get_str(reg)); break; default: assert(false); break; } return res; } // Add the GLSL code for a stage static void add_stage_code(struct PixelShader *ps, struct InputVarInfo input, struct OutputInfo output, const char *write_mask, bool is_alpha) { QString *a = get_input_var(ps, input.a, is_alpha); QString *b = get_input_var(ps, input.b, is_alpha); QString *c = get_input_var(ps, input.c, is_alpha); QString *d = get_input_var(ps, input.d, is_alpha); const char *caster = ""; if (strlen(write_mask) == 3) { caster = "vec3"; } QString *ab; if (output.ab_op == PS_COMBINEROUTPUT_AB_DOT_PRODUCT) { ab = qstring_from_fmt("dot(%s, %s)", qstring_get_str(a), qstring_get_str(b)); } else { ab = qstring_from_fmt("(%s * %s)", qstring_get_str(a), qstring_get_str(b)); } QString *cd; if (output.cd_op == PS_COMBINEROUTPUT_CD_DOT_PRODUCT) { cd = qstring_from_fmt("dot(%s, %s)", qstring_get_str(c), qstring_get_str(d)); } else { cd = qstring_from_fmt("(%s * %s)", qstring_get_str(c), qstring_get_str(d)); } QString *ab_mapping = get_output(ab, output.mapping); QString *cd_mapping = get_output(cd, output.mapping); QString *ab_dest = get_var(ps, output.ab, true); QString *cd_dest = get_var(ps, output.cd, true); QString *sum_dest = get_var(ps, output.muxsum, true); if (qstring_get_length(ab_dest)) { qstring_append_fmt(ps->code, "%s.%s = clamp(%s(%s), -1.0, 1.0);\n", qstring_get_str(ab_dest), write_mask, caster, qstring_get_str(ab_mapping)); } else { qobject_unref(ab_dest); qobject_ref(ab_mapping); ab_dest = ab_mapping; } if (qstring_get_length(cd_dest)) { qstring_append_fmt(ps->code, "%s.%s = clamp(%s(%s), -1.0, 1.0);\n", qstring_get_str(cd_dest), write_mask, caster, qstring_get_str(cd_mapping)); } else { qobject_unref(cd_dest); qobject_ref(cd_mapping); cd_dest = cd_mapping; } if (!is_alpha && output.flags & PS_COMBINEROUTPUT_AB_BLUE_TO_ALPHA) { qstring_append_fmt(ps->code, "%s.a = %s.b;\n", qstring_get_str(ab_dest), qstring_get_str(ab_dest)); } if (!is_alpha && output.flags & PS_COMBINEROUTPUT_CD_BLUE_TO_ALPHA) { qstring_append_fmt(ps->code, "%s.a = %s.b;\n", qstring_get_str(cd_dest), qstring_get_str(cd_dest)); } QString *sum; if (output.muxsum_op == PS_COMBINEROUTPUT_AB_CD_SUM) { sum = qstring_from_fmt("(%s + %s)", qstring_get_str(ab), qstring_get_str(cd)); } else { sum = qstring_from_fmt("((r0.a >= 0.5) ? %s : %s)", qstring_get_str(cd), qstring_get_str(ab)); } QString *sum_mapping = get_output(sum, output.mapping); if (qstring_get_length(sum_dest)) { qstring_append_fmt(ps->code, "%s.%s = clamp(%s(%s), -1.0, 1.0);\n", qstring_get_str(sum_dest), write_mask, caster, qstring_get_str(sum_mapping)); } qobject_unref(a); qobject_unref(b); qobject_unref(c); qobject_unref(d); qobject_unref(ab); qobject_unref(cd); qobject_unref(ab_mapping); qobject_unref(cd_mapping); qobject_unref(ab_dest); qobject_unref(cd_dest); qobject_unref(sum_dest); qobject_unref(sum); qobject_unref(sum_mapping); } // Add code for the final combiner stage static void add_final_stage_code(struct PixelShader *ps, struct FCInputInfo final) { ps->varE = get_input_var(ps, final.e, false); ps->varF = get_input_var(ps, final.f, false); QString *a = get_input_var(ps, final.a, false); QString *b = get_input_var(ps, final.b, false); QString *c = get_input_var(ps, final.c, false); QString *d = get_input_var(ps, final.d, false); QString *g = get_input_var(ps, final.g, true); qstring_append_fmt(ps->code, "fragColor.rgb = %s + mix(vec3(%s), vec3(%s), vec3(%s));\n", qstring_get_str(d), qstring_get_str(c), qstring_get_str(b), qstring_get_str(a)); qstring_append_fmt(ps->code, "fragColor.a = %s;\n", qstring_get_str(g)); qobject_unref(a); qobject_unref(b); qobject_unref(c); qobject_unref(d); qobject_unref(g); qobject_unref(ps->varE); qobject_unref(ps->varF); ps->varE = ps->varF = NULL; } static QString* psh_convert(struct PixelShader *ps) { int i; QString *preflight = qstring_new(); qstring_append(preflight, STRUCT_VERTEX_DATA); qstring_append(preflight, "noperspective in VertexData g_vtx;\n"); qstring_append(preflight, "#define vtx g_vtx\n"); qstring_append(preflight, "\n"); qstring_append(preflight, "out vec4 fragColor;\n"); qstring_append(preflight, "\n"); qstring_append(preflight, "uniform vec4 fogColor;\n"); /* Window Clipping */ QString *clip = qstring_new(); if (ps->state.window_clip_count != 0) { qstring_append_fmt(preflight, "uniform ivec4 clipRegion[%d];\n", ps->state.window_clip_count); qstring_append_fmt(clip, "/* Window-clip (%s) */\n", ps->state.window_clip_exclusive ? "Exclusive" : "Inclusive"); if (!ps->state.window_clip_exclusive) { qstring_append(clip, "bool clipContained = false;\n"); } qstring_append_fmt(clip, "for (int i = 0; i < %d; i++) {\n", ps->state.window_clip_count); qstring_append(clip, " bvec4 clipTest = bvec4(lessThan(gl_FragCoord.xy, clipRegion[i].xy),\n" " greaterThan(gl_FragCoord.xy, clipRegion[i].zw));\n" " if (!any(clipTest)) {\n"); if (ps->state.window_clip_exclusive) { /* Pixel in clip region = exclude by discarding */ qstring_append(clip, " discard;\n"); assert(false); /* Untested */ } else { /* Pixel in clip region = mark pixel as contained and leave */ qstring_append(clip, " clipContained = true;\n" " break;\n"); } qstring_append(clip, " }\n" "}\n"); /* Check for inclusive window clip */ if (!ps->state.window_clip_exclusive) { qstring_append(clip, "if (!clipContained) { discard; }\n"); } } else if (ps->state.window_clip_exclusive) { /* Clip everything */ qstring_append(clip, "discard;\n"); } /* calculate perspective-correct inputs */ QString *vars = qstring_new(); qstring_append(vars, "vec4 pD0 = vtx.D0 / vtx.inv_w;\n"); qstring_append(vars, "vec4 pD1 = vtx.D1 / vtx.inv_w;\n"); qstring_append(vars, "vec4 pB0 = vtx.B0 / vtx.inv_w;\n"); qstring_append(vars, "vec4 pB1 = vtx.B1 / vtx.inv_w;\n"); qstring_append(vars, "vec4 pFog = vec4(fogColor.rgb, clamp(vtx.Fog / vtx.inv_w, 0.0, 1.0));\n"); qstring_append(vars, "vec4 pT0 = vtx.T0 / vtx.inv_w;\n"); qstring_append(vars, "vec4 pT1 = vtx.T1 / vtx.inv_w;\n"); qstring_append(vars, "vec4 pT2 = vtx.T2 / vtx.inv_w;\n"); qstring_append(vars, "vec4 pT3 = vtx.T3 / vtx.inv_w;\n"); qstring_append(vars, "\n"); qstring_append(vars, "vec4 v0 = pD0;\n"); qstring_append(vars, "vec4 v1 = pD1;\n"); ps->code = qstring_new(); for (i = 0; i < 4; i++) { const char *sampler_type = NULL; switch (ps->tex_modes[i]) { case PS_TEXTUREMODES_NONE: qstring_append_fmt(vars, "vec4 t%d = vec4(0.0); /* PS_TEXTUREMODES_NONE */\n", i); break; case PS_TEXTUREMODES_PROJECT2D: if (ps->state.rect_tex[i]) { sampler_type = "sampler2DRect"; } else { sampler_type = "sampler2D"; } qstring_append_fmt(vars, "vec4 t%d = textureProj(texSamp%d, pT%d.xyw);\n", i, i, i); break; case PS_TEXTUREMODES_PROJECT3D: sampler_type = "sampler3D"; qstring_append_fmt(vars, "vec4 t%d = textureProj(texSamp%d, pT%d.xyzw);\n", i, i, i); break; case PS_TEXTUREMODES_CUBEMAP: sampler_type = "samplerCube"; qstring_append_fmt(vars, "vec4 t%d = texture(texSamp%d, pT%d.xyz / pT%d.w);\n", i, i, i, i); break; case PS_TEXTUREMODES_PASSTHRU: qstring_append_fmt(vars, "vec4 t%d = pT%d;\n", i, i); break; case PS_TEXTUREMODES_CLIPPLANE: { int j; qstring_append_fmt(vars, "vec4 t%d = vec4(0.0); /* PS_TEXTUREMODES_CLIPPLANE */\n", i); for (j = 0; j < 4; j++) { qstring_append_fmt(vars, " if(pT%d.%c %s 0.0) { discard; };\n", i, "xyzw"[j], ps->state.compare_mode[i][j] ? ">=" : "<"); } break; } case PS_TEXTUREMODES_BUMPENVMAP: assert(!ps->state.rect_tex[i]); sampler_type = "sampler2D"; qstring_append_fmt(preflight, "uniform mat2 bumpMat%d;\n", i); /* FIXME: Do bumpMat swizzle on CPU before upload */ qstring_append_fmt(vars, "vec4 t%d = texture(texSamp%d, pT%d.xy + t%d.rg * mat2(bumpMat%d[0].xy,bumpMat%d[1].yx));\n", i, i, i, ps->input_tex[i], i, i); break; case PS_TEXTUREMODES_BUMPENVMAP_LUM: qstring_append_fmt(preflight, "uniform float bumpScale%d;\n", i); qstring_append_fmt(preflight, "uniform float bumpOffset%d;\n", i); qstring_append_fmt(ps->code, "/* BUMPENVMAP_LUM for stage %d */\n", i); qstring_append_fmt(ps->code, "t%d = t%d * (bumpScale%d * t%d.b + bumpOffset%d);\n", i, i, i, ps->input_tex[i], i); /* Now the same as BUMPENVMAP */ assert(!ps->state.rect_tex[i]); sampler_type = "sampler2D"; qstring_append_fmt(preflight, "uniform mat2 bumpMat%d;\n", i); /* FIXME: Do bumpMat swizzle on CPU before upload */ qstring_append_fmt(vars, "vec4 t%d = texture(texSamp%d, pT%d.xy + t%d.rg * mat2(bumpMat%d[0].xy,bumpMat%d[1].yx));\n", i, i, i, ps->input_tex[i], i, i); break; case PS_TEXTUREMODES_BRDF: qstring_append_fmt(vars, "vec4 t%d = vec4(0.0); /* PS_TEXTUREMODES_BRDF */\n", i); assert(false); /* Unimplemented */ break; case PS_TEXTUREMODES_DOT_ST: qstring_append_fmt(vars, "vec4 t%d = vec4(0.0); /* PS_TEXTUREMODES_DOT_ST */\n", i); assert(false); /* Unimplemented */ break; case PS_TEXTUREMODES_DOT_ZW: qstring_append_fmt(vars, "vec4 t%d = vec4(0.0); /* PS_TEXTUREMODES_DOT_ZW */\n", i); assert(false); /* Unimplemented */ break; case PS_TEXTUREMODES_DOT_RFLCT_DIFF: qstring_append_fmt(vars, "vec4 t%d = vec4(0.0); /* PS_TEXTUREMODES_DOT_RFLCT_DIFF */\n", i); assert(false); /* Unimplemented */ break; case PS_TEXTUREMODES_DOT_RFLCT_SPEC: qstring_append_fmt(vars, "vec4 t%d = vec4(0.0); /* PS_TEXTUREMODES_DOT_RFLCT_SPEC */\n", i); assert(false); /* Unimplemented */ break; case PS_TEXTUREMODES_DOT_STR_3D: qstring_append_fmt(vars, "vec4 t%d = vec4(0.0); /* PS_TEXTUREMODES_DOT_STR_3D */\n", i); assert(false); /* Unimplemented */ break; case PS_TEXTUREMODES_DOT_STR_CUBE: qstring_append_fmt(vars, "vec4 t%d = vec4(0.0); /* PS_TEXTUREMODES_DOT_STR_CUBE */\n", i); assert(false); /* Unimplemented */ break; case PS_TEXTUREMODES_DPNDNT_AR: assert(!ps->state.rect_tex[i]); sampler_type = "sampler2D"; qstring_append_fmt(vars, "vec4 t%d = texture(texSamp%d, t%d.ar);\n", i, i, ps->input_tex[i]); break; case PS_TEXTUREMODES_DPNDNT_GB: assert(!ps->state.rect_tex[i]); sampler_type = "sampler2D"; qstring_append_fmt(vars, "vec4 t%d = texture(texSamp%d, t%d.gb);\n", i, i, ps->input_tex[i]); break; case PS_TEXTUREMODES_DOTPRODUCT: qstring_append_fmt(vars, "vec4 t%d = vec4(dot(pT%d.xyz, t%d.rgb));\n", i, i, ps->input_tex[i]); break; case PS_TEXTUREMODES_DOT_RFLCT_SPEC_CONST: qstring_append_fmt(vars, "vec4 t%d = vec4(0.0); /* PS_TEXTUREMODES_DOT_RFLCT_SPEC_CONST */\n", i); assert(false); /* Unimplemented */ break; default: fprintf(stderr, "Unknown ps tex mode: 0x%x\n", ps->tex_modes[i]); assert(false); break; } if (sampler_type != NULL) { qstring_append_fmt(preflight, "uniform %s texSamp%d;\n", sampler_type, i); /* As this means a texture fetch does happen, do alphakill */ if (ps->state.alphakill[i]) { qstring_append_fmt(vars, "if (t%d.a == 0.0) { discard; };\n", i); } } } for (i = 0; i < ps->num_stages; i++) { ps->cur_stage = i; qstring_append_fmt(ps->code, "// Stage %d\n", i); add_stage_code(ps, ps->stage[i].rgb_input, ps->stage[i].rgb_output, "rgb", false); add_stage_code(ps, ps->stage[i].alpha_input, ps->stage[i].alpha_output, "a", true); } if (ps->final_input.enabled) { ps->cur_stage = 8; qstring_append(ps->code, "// Final Combiner\n"); add_final_stage_code(ps, ps->final_input); } if (ps->state.alpha_test && ps->state.alpha_func != ALPHA_FUNC_ALWAYS) { qstring_append_fmt(preflight, "uniform float alphaRef;\n"); if (ps->state.alpha_func == ALPHA_FUNC_NEVER) { qstring_append(ps->code, "discard;\n"); } else { const char* alpha_op; switch (ps->state.alpha_func) { case ALPHA_FUNC_LESS: alpha_op = "<"; break; case ALPHA_FUNC_EQUAL: alpha_op = "=="; break; case ALPHA_FUNC_LEQUAL: alpha_op = "<="; break; case ALPHA_FUNC_GREATER: alpha_op = ">"; break; case ALPHA_FUNC_NOTEQUAL: alpha_op = "!="; break; case ALPHA_FUNC_GEQUAL: alpha_op = ">="; break; default: assert(false); break; } qstring_append_fmt(ps->code, "if (!(fragColor.a %s alphaRef)) discard;\n", alpha_op); } } for (i = 0; i < ps->num_const_refs; i++) { qstring_append_fmt(preflight, "uniform vec4 %s;\n", ps->const_refs[i]); } for (i = 0; i < ps->num_var_refs; i++) { qstring_append_fmt(vars, "vec4 %s;\n", ps->var_refs[i]); if (strcmp(ps->var_refs[i], "r0") == 0) { if (ps->tex_modes[0] != PS_TEXTUREMODES_NONE) { qstring_append(vars, "r0.a = t0.a;\n"); } else { qstring_append(vars, "r0.a = 1.0;\n"); } } } QString *final = qstring_new(); qstring_append(final, "#version 330\n\n"); qstring_append(final, qstring_get_str(preflight)); qstring_append(final, "void main() {\n"); qstring_append(final, qstring_get_str(clip)); qstring_append(final, qstring_get_str(vars)); qstring_append(final, qstring_get_str(ps->code)); qstring_append(final, "}\n"); qobject_unref(preflight); qobject_unref(vars); qobject_unref(ps->code); return final; } static void parse_input(struct InputInfo *var, int value) { var->reg = value & 0xF; var->chan = value & 0x10; var->mod = value & 0xE0; } static void parse_combiner_inputs(uint32_t value, struct InputInfo *a, struct InputInfo *b, struct InputInfo *c, struct InputInfo *d) { parse_input(d, value & 0xFF); parse_input(c, (value >> 8) & 0xFF); parse_input(b, (value >> 16) & 0xFF); parse_input(a, (value >> 24) & 0xFF); } static void parse_combiner_output(uint32_t value, struct OutputInfo *out) { out->cd = value & 0xF; out->ab = (value >> 4) & 0xF; out->muxsum = (value >> 8) & 0xF; int flags = value >> 12; out->flags = flags; out->cd_op = flags & 1; out->ab_op = flags & 2; out->muxsum_op = flags & 4; out->mapping = flags & 0x38; out->ab_alphablue = flags & 0x80; out->cd_alphablue = flags & 0x40; } QString *psh_translate(const PshState state) { int i; struct PixelShader ps; memset(&ps, 0, sizeof(ps)); ps.state = state; ps.num_stages = state.combiner_control & 0xFF; ps.flags = state.combiner_control >> 8; for (i = 0; i < 4; i++) { ps.tex_modes[i] = (state.shader_stage_program >> (i * 5)) & 0x1F; } ps.input_tex[0] = -1; ps.input_tex[1] = 0; ps.input_tex[2] = (state.other_stage_input >> 16) & 0xF; ps.input_tex[3] = (state.other_stage_input >> 20) & 0xF; for (i = 0; i < ps.num_stages; i++) { parse_combiner_inputs(state.rgb_inputs[i], &ps.stage[i].rgb_input.a, &ps.stage[i].rgb_input.b, &ps.stage[i].rgb_input.c, &ps.stage[i].rgb_input.d); parse_combiner_inputs(state.alpha_inputs[i], &ps.stage[i].alpha_input.a, &ps.stage[i].alpha_input.b, &ps.stage[i].alpha_input.c, &ps.stage[i].alpha_input.d); parse_combiner_output(state.rgb_outputs[i], &ps.stage[i].rgb_output); parse_combiner_output(state.alpha_outputs[i], &ps.stage[i].alpha_output); } struct InputInfo blank; ps.final_input.enabled = state.final_inputs_0 || state.final_inputs_1; if (ps.final_input.enabled) { parse_combiner_inputs(state.final_inputs_0, &ps.final_input.a, &ps.final_input.b, &ps.final_input.c, &ps.final_input.d); parse_combiner_inputs(state.final_inputs_1, &ps.final_input.e, &ps.final_input.f, &ps.final_input.g, &blank); int flags = state.final_inputs_1 & 0xFF; ps.final_input.clamp_sum = flags & PS_FINALCOMBINERSETTING_CLAMP_SUM; ps.final_input.inv_v1 = flags & PS_FINALCOMBINERSETTING_COMPLEMENT_V1; ps.final_input.inv_r0 = flags & PS_FINALCOMBINERSETTING_COMPLEMENT_R0; } return psh_convert(&ps); }