LightingShaderGen: Always calculate lighting for both color channels

Cel-damage depends on lighting being calculated for the first channel
even though there is no color in the vertex format (defaults to the
material color). If lighting for the channel is not enabled, the vertex
will use the default color as before.

The default value of the color is determined by the number of elements in
the vertex format. This fixes the grey cubes in Super Mario Sunshine.

If the color channel count is zero, we set the color to black before the
end of the vertex shader. It's possible that this would be undefined
behavior on hardware if a vertex color index that was greater than the
channel count was used within TEV.
This commit is contained in:
Stenzek 2017-01-29 22:38:48 +10:00 committed by Pokechu22
parent fa73b1a23f
commit 51724c1ccd
11 changed files with 129 additions and 104 deletions

View File

@ -58,7 +58,7 @@ struct VertexShaderConstants
u32 components; // .x
u32 xfmem_dualTexInfo; // .y
u32 xfmem_numColorChans; // .z
u32 pad1; // .w
u32 color_chan_alpha; // .w
std::array<float4, 6> posnormalmatrix;
std::array<float4, 4> projection;

View File

@ -20,7 +20,7 @@ namespace VideoCommon
// As pipelines encompass both shader UIDs and render states, changes to either of these should
// also increment the pipeline UID version. Incrementing the UID version will cause all UID
// caches to be invalidated.
constexpr u32 GX_PIPELINE_UID_VERSION = 1; // Last changed in PR 6431
constexpr u32 GX_PIPELINE_UID_VERSION = 2; // Last changed in PR 9122
struct GXPipelineUid
{

View File

@ -78,7 +78,7 @@ static void GenerateLightShader(ShaderCode& object, const LightingUidData& uid_d
// materials name is I_MATERIALS in vs and I_PMATERIALS in ps
// inColorName is color in vs and colors_ in ps
// dest is o.colors_ in vs and colors_ in ps
void GenerateLightingShaderCode(ShaderCode& object, const LightingUidData& uid_data, int components,
void GenerateLightingShaderCode(ShaderCode& object, const LightingUidData& uid_data,
std::string_view in_color_name, std::string_view dest)
{
for (u32 j = 0; j < NUM_XF_COLOR_CHANNELS; j++)
@ -87,44 +87,17 @@ void GenerateLightingShaderCode(ShaderCode& object, const LightingUidData& uid_d
const bool colormatsource = !!(uid_data.matsource & (1 << j));
if (colormatsource) // from vertex
{
if ((components & (VB_HAS_COL0 << j)) != 0)
object.Write("int4 mat = int4(round({}{} * 255.0));\n", in_color_name, j);
else if ((components & VB_HAS_COL0) != 0)
object.Write("int4 mat = int4(round({}0 * 255.0));\n", in_color_name);
else
object.Write("int4 mat = int4(255, 255, 255, 255);\n");
}
else // from color
{
object.Write("int4 mat = {}[{}];\n", I_MATERIALS, j + 2);
}
if ((uid_data.enablelighting & (1 << j)) != 0)
{
if ((uid_data.ambsource & (1 << j)) != 0) // from vertex
{
if ((components & (VB_HAS_COL0 << j)) != 0)
{
object.Write("lacc = int4(round({}{} * 255.0));\n", in_color_name, j);
}
else if ((components & VB_HAS_COL0) != 0)
{
object.Write("lacc = int4(round({}0 * 255.0));\n", in_color_name);
}
else
{
// TODO: this isn't verified. Here we want to read the ambient from the vertex,
// but the vertex itself has no color. So we don't know which value to read.
// Returning 1.0 is the same as disabled lightning, so this could be fine
object.Write("lacc = int4(255, 255, 255, 255);\n");
}
}
else // from color
{
object.Write("lacc = {}[{}];\n", I_MATERIALS, j);
}
}
else
{
object.Write("lacc = int4(255, 255, 255, 255);\n");
@ -135,43 +108,18 @@ void GenerateLightingShaderCode(ShaderCode& object, const LightingUidData& uid_d
if (alphamatsource != colormatsource)
{
if (alphamatsource) // from vertex
{
if ((components & (VB_HAS_COL0 << j)) != 0)
object.Write("mat.w = int(round({}{}.w * 255.0));\n", in_color_name, j);
else if ((components & VB_HAS_COL0) != 0)
object.Write("mat.w = int(round({}0.w * 255.0));\n", in_color_name);
else
object.Write("mat.w = 255;\n");
}
else // from color
{
object.Write("mat.w = {}[{}].w;\n", I_MATERIALS, j + 2);
}
}
if ((uid_data.enablelighting & (1 << (j + 2))) != 0)
{
if ((uid_data.ambsource & (1 << (j + 2))) != 0) // from vertex
{
if ((components & (VB_HAS_COL0 << j)) != 0)
{
object.Write("lacc.w = int(round({}{}.w * 255.0));\n", in_color_name, j);
}
else if ((components & VB_HAS_COL0) != 0)
{
object.Write("lacc.w = int(round({}0.w * 255.0));\n", in_color_name);
}
else
{
// TODO: The same for alpha: We want to read from vertex, but the vertex has no color
object.Write("lacc.w = 255;\n");
}
}
else // from color
{
object.Write("lacc.w = {}[{}].w;\n", I_MATERIALS, j);
}
}
else
{
object.Write("lacc.w = 255;\n");

View File

@ -45,6 +45,6 @@ constexpr inline char s_lighting_struct[] = "struct Light {\n"
"\tfloat4 dir;\n"
"};\n";
void GenerateLightingShaderCode(ShaderCode& object, const LightingUidData& uid_data, int components,
void GenerateLightingShaderCode(ShaderCode& object, const LightingUidData& uid_data,
std::string_view in_color_name, std::string_view dest);
void GetLightingShaderUid(LightingUidData& uid_data);

View File

@ -203,9 +203,6 @@ PixelShaderUid GetPixelShaderUid()
if (g_ActiveConfig.bEnablePixelLighting)
{
// The lighting shader only needs the two color bits of the 23bit component bit array.
uid_data->components =
(VertexLoaderManager::g_current_components & (VB_HAS_COL0 | VB_HAS_COL1)) >> VB_COL_SHIFT;
uid_data->numColorChans = xfmem.numChan.numColorChans;
GetLightingShaderUid(uid_data->lighting);
}
@ -768,8 +765,11 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos
// out.SetConstantsUsed(C_PLIGHT_COLORS, C_PLIGHT_COLORS+7); // TODO: Can be optimized further
// out.SetConstantsUsed(C_PLIGHTS, C_PLIGHTS+31); // TODO: Can be optimized further
// out.SetConstantsUsed(C_PMATERIALS, C_PMATERIALS+3);
GenerateLightingShaderCode(out, uid_data->lighting, uid_data->components << VB_COL_SHIFT,
"colors_", "col");
GenerateLightingShaderCode(out, uid_data->lighting, "colors_", "col");
if (uid_data->numColorChans == 0)
out.Write("col0 = float4(0.0, 0.0, 0.0, 0.0);\n");
if (uid_data->numColorChans <= 1)
out.Write("col1 = float4(0.0, 0.0, 0.0, 0.0);\n");
}
// HACK to handle cases where the tex gen is not enabled

View File

@ -17,8 +17,7 @@ struct pixel_shader_uid_data
u32 num_values; // TODO: Shouldn't be a u32
u32 NumValues() const { return num_values; }
u32 components : 2;
u32 pad0 : 2;
u32 pad0 : 4;
u32 useDstAlpha : 1;
u32 Pretest : 2;
u32 nIndirectStagesUsed : 4;

View File

@ -229,6 +229,7 @@ const char* GetInterpolationQualifier(bool msaa, bool ssaa, bool in_glsl_interfa
static const char s_shader_uniforms[] = "\tuint components;\n"
"\tuint xfmem_dualTexInfo;\n"
"\tuint xfmem_numColorChans;\n"
"\tuint color_chan_alpha;\n"
"\tfloat4 " I_POSNORMALMATRIX "[6];\n"
"\tfloat4 " I_PROJECTION "[4];\n"
"\tint4 " I_MATERIALS "[4];\n"

View File

@ -103,42 +103,23 @@ void WriteVertexLighting(ShaderCode& out, APIType api_type, std::string_view wor
" int4 lacc = int4(255, 255, 255, 255);\n"
"\n");
out.Write(" if ({} != 0u) {{\n", BitfieldExtract("colorreg", LitChannel().matsource));
out.Write(" if ((components & ({}u << chan)) != 0u) // VB_HAS_COL0\n", VB_HAS_COL0);
out.Write(" if ({} != 0u)\n", BitfieldExtract("colorreg", LitChannel().matsource));
out.Write(" mat.xyz = int3(round(((chan == 0u) ? {}.xyz : {}.xyz) * 255.0));\n",
in_color_0_var, in_color_1_var);
out.Write(" else if ((components & {}u) != 0u) // VB_HAS_COLO0\n", VB_HAS_COL0);
out.Write(" mat.xyz = int3(round({}.xyz * 255.0));\n", in_color_0_var);
out.Write(" else\n"
" mat.xyz = int3(255, 255, 255);\n"
" }}\n"
"\n");
out.Write(" if ({} != 0u) {{\n", BitfieldExtract("alphareg", LitChannel().matsource));
out.Write(" if ((components & ({}u << chan)) != 0u) // VB_HAS_COL0\n", VB_HAS_COL0);
out.Write(" if ({} != 0u)\n", BitfieldExtract("alphareg", LitChannel().matsource));
out.Write(" mat.w = int(round(((chan == 0u) ? {}.w : {}.w) * 255.0));\n", in_color_0_var,
in_color_1_var);
out.Write(" else if ((components & {}u) != 0u) // VB_HAS_COLO0\n", VB_HAS_COL0);
out.Write(" mat.w = int(round({}.w * 255.0));\n", in_color_0_var);
out.Write(" else\n"
" mat.w = 255;\n"
" }} else {{\n"
" mat.w = " I_MATERIALS " [chan + 2u].w;\n"
" }}\n"
"\n");
out.Write(" if ({} != 0u) {{\n", BitfieldExtract("colorreg", LitChannel().enablelighting));
out.Write(" if ({} != 0u) {{\n", BitfieldExtract("colorreg", LitChannel().ambsource));
out.Write(" if ((components & ({}u << chan)) != 0u) // VB_HAS_COL0\n", VB_HAS_COL0);
out.Write(" if ({} != 0u)\n", BitfieldExtract("colorreg", LitChannel().ambsource));
out.Write(" lacc.xyz = int3(round(((chan == 0u) ? {}.xyz : {}.xyz) * 255.0));\n",
in_color_0_var, in_color_1_var);
out.Write(" else if ((components & {}u) != 0u) // VB_HAS_COLO0\n", VB_HAS_COL0);
out.Write(" lacc.xyz = int3(round({}.xyz * 255.0));\n", in_color_0_var);
out.Write(" else\n"
" lacc.xyz = int3(255, 255, 255);\n"
" }} else {{\n"
" lacc.xyz = " I_MATERIALS " [chan].xyz;\n"
" }}\n"
"\n");
out.Write(" uint light_mask = {} | ({} << 4u);\n",
BitfieldExtract("colorreg", LitChannel().lightMask0_3),

View File

@ -176,8 +176,43 @@ ShaderCode GenVertexShader(APIType api_type, const ShaderHostConfig& host_config
"\n");
// Hardware Lighting
WriteVertexLighting(out, api_type, "pos.xyz", "_norm0", "rawcolor0", "rawcolor1", "o.colors_0",
"o.colors_1");
out.Write("// xfmem.numColorChans controls the number of color channels available to TEV,\n"
"// but we still need to generate all channels here, as it can be used in texgen.\n"
"// Cel-damage is an example of this.\n"
"float4 vertex_color_0, vertex_color_1;\n"
"\n");
out.Write("// To use color 1, the vertex descriptor must have color 0 and 1.\n"
"// If color 1 is present but not color 0, it is used for lighting channel 0.\n"
"bool use_color_1 = ((components & {0}) == {0}); // VB_HAS_COL0 | VB_HAS_COL1\n",
VB_HAS_COL0 | VB_HAS_COL1);
out.Write("for (uint color = 0; color < {}; color++) {{\n", NUM_XF_COLOR_CHANNELS);
out.Write(" if ((color == 0 || use_color_1) && (components & ({} << color)) != 0) {{\n",
VB_HAS_COL0);
out.Write(" float4 color_value;\n"
" // Use color0 for channel 0, and color1 for channel 1 if both colors 0 and 1 are "
"present.\n"
" if (color == 0u)\n"
" vertex_color_0 = rawcolor0;\n"
" else\n"
" vertex_color_1 = rawcolor1;\n"
" }} else if (color == 0 && (components & {}) != 0) {{\n",
VB_HAS_COL1);
out.Write(" // Use color1 for channel 0 if color0 is not present.\n"
" vertex_color_0 = rawcolor1;\n"
" }} else {{\n"
" // The default alpha channel depends on the number of components in the vertex.\n"
" float alpha = float((color_chan_alpha >> color) & 1u);\n"
" if (color == 0u)\n"
" vertex_color_0 = float4(1.0, 1.0, 1.0, alpha);\n"
" else\n"
" vertex_color_1 = float4(1.0, 1.0, 1.0, alpha);\n"
" }}\n"
"}}\n"
"\n");
WriteVertexLighting(out, api_type, "pos.xyz", "_norm0", "vertex_color_0", "vertex_color_1",
"o.colors_0", "o.colors_1");
// Texture Coordinates
if (num_texgen > 0)
@ -207,14 +242,27 @@ ShaderCode GenVertexShader(APIType api_type, const ShaderHostConfig& host_config
if (per_pixel_lighting)
{
out.Write("o.Normal = _norm0;\n"
"o.WorldPos = pos.xyz;\n");
"o.WorldPos = pos.xyz;\n"
"// Pass through the vertex colors unmodified so we can evaluate the lighting\n"
"// in the same manner.\n");
out.Write("if ((components & {}u) != 0u) // VB_HAS_COL0\n"
" o.colors_0 = rawcolor0;\n",
" o.colors_0 = vertex_color_0;\n",
VB_HAS_COL0);
out.Write("if ((components & {}u) != 0u) // VB_HAS_COL1\n"
" o.colors_1 = rawcolor1;\n",
" o.colors_1 = vertex_color_1;\n",
VB_HAS_COL1);
}
else
{
out.Write("// The number of colors available to TEV is determined by numColorChans.\n"
"// We have to provide the fields to match the interface, so set to zero\n"
"// if it's not enabled.\n"
"if (xfmem_numColorChans == 0u)\n"
" o.colors_0 = float4(0.0, 0.0, 0.0, 0.0);\n"
"if (xfmem_numColorChans <= 1u)\n"
" o.colors_1 = float4(0.0, 0.0, 0.0, 0.0);\n"
"\n");
}
// If we can disable the incorrect depth clipping planes using depth clamping, then we can do
// our own depth clipping and calculate the depth range before the perspective divide if

View File

@ -191,6 +191,35 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho
out.Write("VS_OUTPUT o;\n");
// xfmem.numColorChans controls the number of color channels available to TEV, but we still need
// to generate all channels here, as it can be used in texgen. Cel-damage is an example of this.
out.Write("float4 vertex_color_0, vertex_color_1;\n");
// To use color 1, the vertex descriptor must have color 0 and 1.
// If color 1 is present but not color 0, it is used for lighting channel 0.
const bool use_color_1 =
(uid_data->components & (VB_HAS_COL0 | VB_HAS_COL1)) == (VB_HAS_COL0 | VB_HAS_COL1);
for (u32 color = 0; color < NUM_XF_COLOR_CHANNELS; color++)
{
if ((color == 0 || use_color_1) && (uid_data->components & (VB_HAS_COL0 << color)) != 0)
{
// Use color0 for channel 0, and color1 for channel 1 if both colors 0 and 1 are present.
out.Write("vertex_color_{0} = rawcolor{0};\n", color);
}
else if (color == 0 && (uid_data->components & VB_HAS_COL1) != 0)
{
// Use color1 for channel 0 if color0 is not present.
out.Write("vertex_color_{} = rawcolor1;\n", color);
}
else
{
// The default alpha channel depends on the number of components in the vertex format.
out.Write(
"vertex_color_{0} = float4(1.0, 1.0, 1.0, float((color_chan_alpha >> {0}) & 1u));\n",
color);
}
}
// transforms
if ((uid_data->components & VB_HAS_POSMTXIDX) != 0)
{
@ -256,8 +285,7 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho
"float3 ldir, h, cosAttn, distAttn;\n"
"float dist, dist2, attn;\n");
GenerateLightingShaderCode(out, uid_data->lighting, uid_data->components, "rawcolor",
"o.colors_");
GenerateLightingShaderCode(out, uid_data->lighting, "vertex_color_", "o.colors_");
// transform texcoords
out.Write("float4 coord = float4(0.0, 0.0, 1.0, 1.0);\n");
@ -434,11 +462,21 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho
out.Write("o.Normal = _norm0;\n"
"o.WorldPos = pos.xyz;\n");
// Pass through the vertex colors unmodified so we can evaluate the lighting in the same manner.
if ((uid_data->components & VB_HAS_COL0) != 0)
out.Write("o.colors_0 = rawcolor0;\n");
out.Write("o.colors_0 = vertex_color_0;\n");
if ((uid_data->components & VB_HAS_COL1) != 0)
out.Write("o.colors_1 = rawcolor1;\n");
out.Write("o.colors_1 = vertex_color_1;\n");
}
else
{
// The number of colors available to TEV is determined by numColorChans.
// We have to provide the fields to match the interface, so set to zero if it's not enabled.
if (uid_data->numColorChans == 0)
out.Write("o.colors_0 = float4(0.0, 0.0, 0.0, 0.0);\n");
if (uid_data->numColorChans <= 1)
out.Write("o.colors_1 = float4(0.0, 0.0, 0.0, 0.0);\n");
}
// If we can disable the incorrect depth clipping planes using depth clamping, then we can do

View File

@ -452,7 +452,6 @@ void VertexShaderManager::SetConstants()
constants.xfmem_pack1[i][3] = xfmem.alpha[i].hex;
}
constants.xfmem_numColorChans = xfmem.numChan.numColorChans;
dirty = true;
}
}
@ -617,6 +616,17 @@ void VertexShaderManager::SetVertexFormat(u32 components)
constants.components = components;
dirty = true;
}
// The default alpha channel seems to depend on the number of components in the vertex format.
// If the vertex attribute has an alpha channel, zero is used, otherwise one.
const u32 color_chan_alpha =
(g_main_cp_state.vtx_attr[g_main_cp_state.last_id].g0.Color0Elements ^ 1) |
((g_main_cp_state.vtx_attr[g_main_cp_state.last_id].g0.Color1Elements ^ 1) << 1);
if (color_chan_alpha != constants.color_chan_alpha)
{
constants.color_chan_alpha = color_chan_alpha;
dirty = true;
}
}
void VertexShaderManager::SetTexMatrixInfoChanged(int index)