gl_shader_gen: Make use of fmt where applicable

Avoids string churn and makes it significantly easier to read the shader
template strings that are being used as the basis for formatting.
This commit is contained in:
Lioncash 2020-04-21 00:30:50 -04:00
parent 36809b2e2e
commit cd201cd60f
1 changed files with 195 additions and 198 deletions

View File

@ -4,8 +4,8 @@
#include <array>
#include <cstddef>
#include <cstring>
#include <string_view>
#include <fmt/format.h>
#include "common/assert.h"
#include "common/bit_field.h"
#include "common/bit_set.h"
@ -82,11 +82,11 @@ layout (std140) uniform shader_data {
static std::string GetVertexInterfaceDeclaration(bool is_output, bool separable_shader) {
std::string out;
auto append_variable = [&](const char* var, int location) {
const auto append_variable = [&](std::string_view var, int location) {
if (separable_shader) {
out += "layout (location=" + std::to_string(location) + ") ";
out += fmt::format("layout (location={}) ", location);
}
out += std::string(is_output ? "out " : "in ") + var + ";\n";
out += fmt::format("{}{};\n", is_output ? "out " : "in ", var);
};
append_variable("vec4 primary_color", ATTRIBUTE_COLOR);
@ -267,11 +267,12 @@ void PicaGSConfigCommonRaw::Init(const Pica::Regs& regs) {
semantic_maps.fill({16, 0});
for (u32 attrib = 0; attrib < regs.rasterizer.vs_output_total; ++attrib) {
std::array<VSOutputAttributes::Semantic, 4> semantics = {
regs.rasterizer.vs_output_attributes[attrib].map_x,
regs.rasterizer.vs_output_attributes[attrib].map_y,
regs.rasterizer.vs_output_attributes[attrib].map_z,
regs.rasterizer.vs_output_attributes[attrib].map_w};
const std::array semantics{
regs.rasterizer.vs_output_attributes[attrib].map_x.Value(),
regs.rasterizer.vs_output_attributes[attrib].map_y.Value(),
regs.rasterizer.vs_output_attributes[attrib].map_z.Value(),
regs.rasterizer.vs_output_attributes[attrib].map_w.Value(),
};
for (u32 comp = 0; comp < 4; ++comp) {
const auto semantic = semantics[comp];
if (static_cast<std::size_t>(semantic) < 24) {
@ -342,7 +343,7 @@ static std::string SampleTexture(const PicaFSConfig& config, unsigned texture_un
/// Writes the specified TEV stage source component(s)
static void AppendSource(std::string& out, const PicaFSConfig& config,
TevStageConfig::Source source, const std::string& index_name) {
TevStageConfig::Source source, std::string_view index_name) {
using Source = TevStageConfig::Source;
switch (source) {
case Source::PrimaryColor:
@ -370,7 +371,9 @@ static void AppendSource(std::string& out, const PicaFSConfig& config,
out += "combiner_buffer";
break;
case Source::Constant:
((out += "const_color[") += index_name) += ']';
out += "const_color[";
out += index_name;
out += ']';
break;
case Source::Previous:
out += "last_tex_env_out";
@ -385,7 +388,7 @@ static void AppendSource(std::string& out, const PicaFSConfig& config,
/// Writes the color components to use for the specified TEV stage color modifier
static void AppendColorModifier(std::string& out, const PicaFSConfig& config,
TevStageConfig::ColorModifier modifier,
TevStageConfig::Source source, const std::string& index_name) {
TevStageConfig::Source source, std::string_view index_name) {
using ColorModifier = TevStageConfig::ColorModifier;
switch (modifier) {
case ColorModifier::SourceColor:
@ -491,40 +494,38 @@ static void AppendAlphaModifier(std::string& out, const PicaFSConfig& config,
/// Writes the combiner function for the color components for the specified TEV stage operation
static void AppendColorCombiner(std::string& out, TevStageConfig::Operation operation,
const std::string& variable_name) {
std::string_view variable_name) {
out += "clamp(";
using Operation = TevStageConfig::Operation;
switch (operation) {
case Operation::Replace:
out += variable_name + "[0]";
out += fmt::format("{}[0]", variable_name);
break;
case Operation::Modulate:
out += variable_name + "[0] * " + variable_name + "[1]";
out += fmt::format("{0}[0] * {0}[1]", variable_name);
break;
case Operation::Add:
out += variable_name + "[0] + " + variable_name + "[1]";
out += fmt::format("{0}[0] + {0}[1]", variable_name);
break;
case Operation::AddSigned:
out += variable_name + "[0] + " + variable_name + "[1] - vec3(0.5)";
out += fmt::format("{0}[0] + {0}[1] - vec3(0.5)", variable_name);
break;
case Operation::Lerp:
out += variable_name + "[0] * " + variable_name + "[2] + " + variable_name +
"[1] * (vec3(1.0) - " + variable_name + "[2])";
out += fmt::format("{0}[0] * {0}[2] + {0}[1] * (vec3(1.0) - {0}[2])", variable_name);
break;
case Operation::Subtract:
out += variable_name + "[0] - " + variable_name + "[1]";
out += fmt::format("{0}[0] - {0}[1]", variable_name);
break;
case Operation::MultiplyThenAdd:
out += variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2]";
out += fmt::format("{0}[0] * {0}[1] + {0}[2]", variable_name);
break;
case Operation::AddThenMultiply:
out += "min(" + variable_name + "[0] + " + variable_name + "[1], vec3(1.0)) * " +
variable_name + "[2]";
out += fmt::format("min({0}[0] + {0}[1], vec3(1.0)) * {0}[2]", variable_name);
break;
case Operation::Dot3_RGB:
case Operation::Dot3_RGBA:
out += "vec3(dot(" + variable_name + "[0] - vec3(0.5), " + variable_name +
"[1] - vec3(0.5)) * 4.0)";
out +=
fmt::format("vec3(dot({0}[0] - vec3(0.5), {0}[1] - vec3(0.5)) * 4.0)", variable_name);
break;
default:
out += "vec3(0.0)";
@ -537,35 +538,33 @@ static void AppendColorCombiner(std::string& out, TevStageConfig::Operation oper
/// Writes the combiner function for the alpha component for the specified TEV stage operation
static void AppendAlphaCombiner(std::string& out, TevStageConfig::Operation operation,
const std::string& variable_name) {
std::string_view variable_name) {
out += "clamp(";
using Operation = TevStageConfig::Operation;
switch (operation) {
case Operation::Replace:
out += variable_name + "[0]";
out += fmt::format("{}[0]", variable_name);
break;
case Operation::Modulate:
out += variable_name + "[0] * " + variable_name + "[1]";
out += fmt::format("{0}[0] * {0}[1]", variable_name);
break;
case Operation::Add:
out += variable_name + "[0] + " + variable_name + "[1]";
out += fmt::format("{0}[0] + {0}[1]", variable_name);
break;
case Operation::AddSigned:
out += variable_name + "[0] + " + variable_name + "[1] - 0.5";
out += fmt::format("{0}[0] + {0}[1] - 0.5", variable_name);
break;
case Operation::Lerp:
out += variable_name + "[0] * " + variable_name + "[2] + " + variable_name +
"[1] * (1.0 - " + variable_name + "[2])";
out += fmt::format("{0}[0] * {0}[2] + {0}[1] * (1.0 - {0}[2])", variable_name);
break;
case Operation::Subtract:
out += variable_name + "[0] - " + variable_name + "[1]";
out += fmt::format("{0}[0] - {0}[1]", variable_name);
break;
case Operation::MultiplyThenAdd:
out += variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2]";
out += fmt::format("{0}[0] * {0}[1] + {0}[2]", variable_name);
break;
case Operation::AddThenMultiply:
out += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0) * " + variable_name +
"[2]";
out += fmt::format("min({0}[0] + {0}[1], 1.0) * {0}[2]", variable_name);
break;
default:
out += "0.0";
@ -592,9 +591,9 @@ static void AppendAlphaTestCondition(std::string& out, FramebufferRegs::CompareF
case CompareFunc::LessThanOrEqual:
case CompareFunc::GreaterThan:
case CompareFunc::GreaterThanOrEqual: {
static const char* op[] = {"!=", "==", ">=", ">", "<=", "<"};
unsigned index = (unsigned)func - (unsigned)CompareFunc::Equal;
out += "int(last_tex_env_out.a * 255.0) " + std::string(op[index]) + " alphatest_ref";
static constexpr std::array op{"!=", "==", ">=", ">", "<=", "<"};
const auto index = static_cast<u32>(func) - static_cast<u32>(CompareFunc::Equal);
out += fmt::format("int(last_tex_env_out.a * 255.0) {} alphatest_ref", op[index]);
break;
}
@ -610,9 +609,9 @@ static void WriteTevStage(std::string& out, const PicaFSConfig& config, unsigned
const auto stage =
static_cast<const TexturingRegs::TevStageConfig>(config.state.tev_stages[index]);
if (!IsPassThroughTevStage(stage)) {
std::string index_name = std::to_string(index);
const std::string index_name = std::to_string(index);
out += "vec3 color_results_" + index_name + "[3] = vec3[3](";
out += fmt::format("vec3 color_results_{}[3] = vec3[3](", index_name);
AppendColorModifier(out, config, stage.color_modifier1, stage.color_source1, index_name);
out += ", ";
AppendColorModifier(out, config, stage.color_modifier2, stage.color_source2, index_name);
@ -621,15 +620,15 @@ static void WriteTevStage(std::string& out, const PicaFSConfig& config, unsigned
out += ");\n";
// Round the output of each TEV stage to maintain the PICA's 8 bits of precision
out += "vec3 color_output_" + index_name + " = byteround(";
out += fmt::format("vec3 color_output_{} = byteround(", index_name);
AppendColorCombiner(out, stage.color_op, "color_results_" + index_name);
out += ");\n";
if (stage.color_op == TevStageConfig::Operation::Dot3_RGBA) {
// result of Dot3_RGBA operation is also placed to the alpha component
out += "float alpha_output_" + index_name + " = color_output_" + index_name + "[0];\n";
out += fmt::format("float alpha_output_{0} = color_output_{0}[0];\n", index_name);
} else {
out += "float alpha_results_" + index_name + "[3] = float[3](";
out += fmt::format("float alpha_results_{}[3] = float[3](", index_name);
AppendAlphaModifier(out, config, stage.alpha_modifier1, stage.alpha_source1,
index_name);
out += ", ";
@ -640,18 +639,16 @@ static void WriteTevStage(std::string& out, const PicaFSConfig& config, unsigned
index_name);
out += ");\n";
out += "float alpha_output_" + index_name + " = byteround(";
out += fmt::format("float alpha_output_{} = byteround(", index_name);
AppendAlphaCombiner(out, stage.alpha_op, "alpha_results_" + index_name);
out += ");\n";
}
out += "last_tex_env_out = vec4("
"clamp(color_output_" +
index_name + " * " + std::to_string(stage.GetColorMultiplier()) +
".0, vec3(0.0), vec3(1.0)),"
"clamp(alpha_output_" +
index_name + " * " + std::to_string(stage.GetAlphaMultiplier()) +
".0, 0.0, 1.0));\n";
out += fmt::format("last_tex_env_out = vec4("
"clamp(color_output_{} * {}.0, vec3(0.0), vec3(1.0)), "
"clamp(alpha_output_{} * {}.0, 0.0, 1.0));\n",
index_name, stage.GetColorMultiplier(), index_name,
stage.GetAlphaMultiplier());
}
out += "combiner_buffer = next_combiner_buffer;\n";
@ -679,26 +676,26 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
"float geo_factor = 1.0;\n";
// Compute fragment normals and tangents
auto Perturbation = [&]() {
return "2.0 * (" + SampleTexture(config, lighting.bump_selector) + ").rgb - 1.0";
const auto Perturbation = [&] {
return fmt::format("2.0 * ({}).rgb - 1.0", SampleTexture(config, lighting.bump_selector));
};
if (lighting.bump_mode == LightingRegs::LightingBumpMode::NormalMap) {
// Bump mapping is enabled using a normal map
out += "vec3 surface_normal = " + Perturbation() + ";\n";
out += fmt::format("vec3 surface_normal = {};\n", Perturbation());
// Recompute Z-component of perturbation if 'renorm' is enabled, this provides a higher
// precision result
if (lighting.bump_renorm) {
std::string val =
constexpr std::string_view val =
"(1.0 - (surface_normal.x*surface_normal.x + surface_normal.y*surface_normal.y))";
out += "surface_normal.z = sqrt(max(" + val + ", 0.0));\n";
out += fmt::format("surface_normal.z = sqrt(max({}, 0.0));\n", val);
}
// The tangent vector is not perturbed by the normal map and is just a unit vector.
out += "vec3 surface_tangent = vec3(1.0, 0.0, 0.0);\n";
} else if (lighting.bump_mode == LightingRegs::LightingBumpMode::TangentMap) {
// Bump mapping is enabled using a tangent map
out += "vec3 surface_tangent = " + Perturbation() + ";\n";
out += fmt::format("vec3 surface_tangent = {};\n", Perturbation());
// Mathematically, recomputing Z-component of the tangent vector won't affect the relevant
// computation below, which is also confirmed on 3DS. So we don't bother recomputing here
// even if 'renorm' is enabled.
@ -707,22 +704,22 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
out += "vec3 surface_normal = vec3(0.0, 0.0, 1.0);\n";
} else {
// No bump mapping - surface local normal and tangent are just unit vectors
out += "vec3 surface_normal = vec3(0.0, 0.0, 1.0);\n";
out += "vec3 surface_tangent = vec3(1.0, 0.0, 0.0);\n";
out += "vec3 surface_normal = vec3(0.0, 0.0, 1.0);\n"
"vec3 surface_tangent = vec3(1.0, 0.0, 0.0);\n";
}
// Rotate the surface-local normal by the interpolated normal quaternion to convert it to
// eyespace.
out += "vec4 normalized_normquat = normalize(normquat);\n";
out += "vec3 normal = quaternion_rotate(normalized_normquat, surface_normal);\n";
out += "vec3 tangent = quaternion_rotate(normalized_normquat, surface_tangent);\n";
out += "vec4 normalized_normquat = normalize(normquat);\n"
"vec3 normal = quaternion_rotate(normalized_normquat, surface_normal);\n"
"vec3 tangent = quaternion_rotate(normalized_normquat, surface_tangent);\n";
if (lighting.enable_shadow) {
std::string shadow_texture = SampleTexture(config, lighting.shadow_selector);
if (lighting.shadow_invert) {
out += "vec4 shadow = vec4(1.0) - " + shadow_texture + ";\n";
out += fmt::format("vec4 shadow = vec4(1.0) - {};\n", shadow_texture);
} else {
out += "vec4 shadow = " + shadow_texture + ";\n";
out += fmt::format("vec4 shadow = {};\n", shadow_texture);
}
} else {
out += "vec4 shadow = vec4(1.0);\n";
@ -738,19 +735,19 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
break;
case LightingRegs::LightingLutInput::VH:
index = std::string("dot(normalize(view), normalize(half_vector))");
index = "dot(normalize(view), normalize(half_vector))";
break;
case LightingRegs::LightingLutInput::NV:
index = std::string("dot(normal, normalize(view))");
index = "dot(normal, normalize(view))";
break;
case LightingRegs::LightingLutInput::LN:
index = std::string("dot(light_vector, normal)");
index = "dot(light_vector, normal)";
break;
case LightingRegs::LightingLutInput::SP:
index = std::string("dot(light_vector, spot_dir)");
index = "dot(light_vector, spot_dir)";
break;
case LightingRegs::LightingLutInput::CP:
@ -759,11 +756,11 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
// Note: even if the normal vector is modified by normal map, which is not the
// normal of the tangent plane anymore, the half angle vector is still projected
// using the modified normal vector.
std::string half_angle_proj =
constexpr std::string_view half_angle_proj =
"normalize(half_vector) - normal * dot(normal, normalize(half_vector))";
// Note: the half angle vector projection is confirmed not normalized before the dot
// product. The result is in fact not cos(phi) as the name suggested.
index = "dot(" + half_angle_proj + ", tangent)";
index = fmt::format("dot({}, tangent)", half_angle_proj);
} else {
index = "0.0";
}
@ -776,31 +773,33 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
break;
}
std::string sampler_string = std::to_string(static_cast<unsigned>(sampler));
const auto sampler_index = static_cast<u32>(sampler);
if (abs) {
// LUT index is in the range of (0.0, 1.0)
index = lighting.light[light_num].two_sided_diffuse ? "abs(" + index + ")"
: "max(" + index + ", 0.0)";
return "LookupLightingLUTUnsigned(" + sampler_string + ", " + index + ")";
index = lighting.light[light_num].two_sided_diffuse
? fmt::format("abs({})", index)
: fmt::format("max({}, 0.0)", index);
return fmt::format("LookupLightingLUTUnsigned({}, {})", sampler_index, index);
} else {
// LUT index is in the range of (-1.0, 1.0)
return "LookupLightingLUTSigned(" + sampler_string + ", " + index + ")";
return fmt::format("LookupLightingLUTSigned({}, {})", sampler_index, index);
}
};
// Write the code to emulate each enabled light
for (unsigned light_index = 0; light_index < lighting.src_num; ++light_index) {
const auto& light_config = lighting.light[light_index];
std::string light_src = "light_src[" + std::to_string(light_config.num) + "]";
const std::string light_src = fmt::format("light_src[{}]", light_config.num);
// Compute light vector (directional or positional)
if (light_config.directional)
out += "light_vector = normalize(" + light_src + ".position);\n";
else
out += "light_vector = normalize(" + light_src + ".position + view);\n";
if (light_config.directional) {
out += fmt::format("light_vector = normalize({}.position);\n", light_src);
} else {
out += fmt::format("light_vector = normalize({}.position + view);\n", light_src);
}
out += "spot_dir = " + light_src + ".spot_direction;\n";
out += fmt::format("spot_dir = {}.spot_direction;\n", light_src);
out += "half_vector = normalize(view) + light_vector;\n";
// Compute dot product of light_vector and normal, adjust if lighting is one-sided or
@ -819,21 +818,21 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
if (light_config.spot_atten_enable &&
LightingRegs::IsLightingSamplerSupported(
lighting.config, LightingRegs::LightingSampler::SpotlightAttenuation)) {
std::string value =
const std::string value =
GetLutValue(LightingRegs::SpotlightAttenuationSampler(light_config.num),
light_config.num, lighting.lut_sp.type, lighting.lut_sp.abs_input);
spot_atten = "(" + std::to_string(lighting.lut_sp.scale) + " * " + value + ")";
spot_atten = fmt::format("({} * {})", lighting.lut_sp.scale, value);
}
// If enabled, compute distance attenuation value
std::string dist_atten = "1.0";
if (light_config.dist_atten_enable) {
std::string index = "clamp(" + light_src + ".dist_atten_scale * length(-view - " +
light_src + ".position) + " + light_src +
".dist_atten_bias, 0.0, 1.0)";
auto sampler = LightingRegs::DistanceAttenuationSampler(light_config.num);
dist_atten = "LookupLightingLUTUnsigned(" +
std::to_string(static_cast<unsigned>(sampler)) + "," + index + ")";
const std::string index = fmt::format("clamp({}.dist_atten_scale * length(-view - "
"{}.position) + {}.dist_atten_bias, 0.0, 1.0)",
light_src, light_src, light_src);
const auto sampler = LightingRegs::DistanceAttenuationSampler(light_config.num);
dist_atten =
fmt::format("LookupLightingLUTUnsigned({}, {})", static_cast<u32>(sampler), index);
}
if (light_config.geometric_factor_0 || light_config.geometric_factor_1) {
@ -848,14 +847,14 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
LightingRegs::IsLightingSamplerSupported(
lighting.config, LightingRegs::LightingSampler::Distribution0)) {
// Lookup specular "distribution 0" LUT value
std::string value =
const std::string value =
GetLutValue(LightingRegs::LightingSampler::Distribution0, light_config.num,
lighting.lut_d0.type, lighting.lut_d0.abs_input);
d0_lut_value = "(" + std::to_string(lighting.lut_d0.scale) + " * " + value + ")";
d0_lut_value = fmt::format("({} * {})", lighting.lut_d0.scale, value);
}
std::string specular_0 = "(" + d0_lut_value + " * " + light_src + ".specular_0)";
std::string specular_0 = fmt::format("({} * {}.specular_0)", d0_lut_value, light_src);
if (light_config.geometric_factor_0) {
specular_0 = "(" + specular_0 + " * geo_factor)";
specular_0 = fmt::format("({} * geo_factor)", specular_0);
}
// If enabled, lookup ReflectRed value, otherwise, 1.0 is used
@ -865,8 +864,8 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
std::string value =
GetLutValue(LightingRegs::LightingSampler::ReflectRed, light_config.num,
lighting.lut_rr.type, lighting.lut_rr.abs_input);
value = "(" + std::to_string(lighting.lut_rr.scale) + " * " + value + ")";
out += "refl_value.r = " + value + ";\n";
value = fmt::format("({} * {})", lighting.lut_rr.scale, value);
out += fmt::format("refl_value.r = {};\n", value);
} else {
out += "refl_value.r = 1.0;\n";
}
@ -878,8 +877,8 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
std::string value =
GetLutValue(LightingRegs::LightingSampler::ReflectGreen, light_config.num,
lighting.lut_rg.type, lighting.lut_rg.abs_input);
value = "(" + std::to_string(lighting.lut_rg.scale) + " * " + value + ")";
out += "refl_value.g = " + value + ";\n";
value = fmt::format("({} * {})", lighting.lut_rg.scale, value);
out += fmt::format("refl_value.g = {};\n", value);
} else {
out += "refl_value.g = refl_value.r;\n";
}
@ -891,8 +890,8 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
std::string value =
GetLutValue(LightingRegs::LightingSampler::ReflectBlue, light_config.num,
lighting.lut_rb.type, lighting.lut_rb.abs_input);
value = "(" + std::to_string(lighting.lut_rb.scale) + " * " + value + ")";
out += "refl_value.b = " + value + ";\n";
value = fmt::format("({} * {})", lighting.lut_rb.scale, value);
out += fmt::format("refl_value.b = {};\n", value);
} else {
out += "refl_value.b = refl_value.r;\n";
}
@ -903,15 +902,15 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
LightingRegs::IsLightingSamplerSupported(
lighting.config, LightingRegs::LightingSampler::Distribution1)) {
// Lookup specular "distribution 1" LUT value
std::string value =
const std::string value =
GetLutValue(LightingRegs::LightingSampler::Distribution1, light_config.num,
lighting.lut_d1.type, lighting.lut_d1.abs_input);
d1_lut_value = "(" + std::to_string(lighting.lut_d1.scale) + " * " + value + ")";
d1_lut_value = fmt::format("({} * {})", lighting.lut_d1.scale, value);
}
std::string specular_1 =
"(" + d1_lut_value + " * refl_value * " + light_src + ".specular_1)";
fmt::format("({} * refl_value * {}.specular_1)", d1_lut_value, light_src);
if (light_config.geometric_factor_1) {
specular_1 = "(" + specular_1 + " * geo_factor)";
specular_1 = fmt::format("({} * geo_factor)", specular_1);
}
// Fresnel
@ -923,16 +922,16 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
std::string value =
GetLutValue(LightingRegs::LightingSampler::Fresnel, light_config.num,
lighting.lut_fr.type, lighting.lut_fr.abs_input);
value = "(" + std::to_string(lighting.lut_fr.scale) + " * " + value + ")";
value = fmt::format("({} * {})", lighting.lut_fr.scale, value);
// Enabled for diffuse lighting alpha component
if (lighting.enable_primary_alpha) {
out += "diffuse_sum.a = " + value + ";\n";
out += fmt::format("diffuse_sum.a = {};\n", value);
}
// Enabled for the specular lighting alpha component
if (lighting.enable_secondary_alpha) {
out += "specular_sum.a = " + value + ";\n";
out += fmt::format("specular_sum.a = {};\n", value);
}
}
@ -942,13 +941,13 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
std::string shadow_secondary = shadow_secondary_enable ? " * shadow.rgb" : "";
// Compute primary fragment color (diffuse lighting) function
out += "diffuse_sum.rgb += ((" + light_src + ".diffuse * dot_product) + " + light_src +
".ambient) * " + dist_atten + " * " + spot_atten + shadow_primary + ";\n";
out += fmt::format(
"diffuse_sum.rgb += (({}.diffuse * dot_product) + {}.ambient) * {} * {}{};\n",
light_src, light_src, dist_atten, spot_atten, shadow_primary);
// Compute secondary fragment color (specular lighting) function
out += "specular_sum.rgb += (" + specular_0 + " + " + specular_1 +
") * clamp_highlights * " + dist_atten + " * " + spot_atten + shadow_secondary +
";\n";
out += fmt::format("specular_sum.rgb += ({} + {}) * clamp_highlights * {} * {}{};\n",
specular_0, specular_1, dist_atten, spot_atten, shadow_secondary);
}
// Apply shadow attenuation to alpha components if enabled
@ -962,9 +961,9 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
}
// Sum final lighting result
out += "diffuse_sum.rgb += lighting_global_ambient;\n";
out += "primary_fragment_color = clamp(diffuse_sum, vec4(0.0), vec4(1.0));\n";
out += "secondary_fragment_color = clamp(specular_sum, vec4(0.0), vec4(1.0));\n";
out += "diffuse_sum.rgb += lighting_global_ambient;\n"
"primary_fragment_color = clamp(diffuse_sum, vec4(0.0), vec4(1.0));\n"
"secondary_fragment_color = clamp(specular_sum, vec4(0.0), vec4(1.0));\n";
}
using ProcTexClamp = TexturingRegs::ProcTexClamp;
@ -972,18 +971,18 @@ using ProcTexShift = TexturingRegs::ProcTexShift;
using ProcTexCombiner = TexturingRegs::ProcTexCombiner;
using ProcTexFilter = TexturingRegs::ProcTexFilter;
void AppendProcTexShiftOffset(std::string& out, const std::string& v, ProcTexShift mode,
void AppendProcTexShiftOffset(std::string& out, std::string_view v, ProcTexShift mode,
ProcTexClamp clamp_mode) {
std::string offset = (clamp_mode == ProcTexClamp::MirroredRepeat) ? "1.0" : "0.5";
const std::string_view offset = (clamp_mode == ProcTexClamp::MirroredRepeat) ? "1.0" : "0.5";
switch (mode) {
case ProcTexShift::None:
out += "0.0";
break;
case ProcTexShift::Odd:
out += offset + " * float((int(" + v + ") / 2) % 2)";
out += fmt::format("{} * float((int({}) / 2) % 2)", offset, v);
break;
case ProcTexShift::Even:
out += offset + " * float(((int(" + v + ") + 1) / 2) % 2)";
out += fmt::format("{} * float(((int({}) + 1) / 2) % 2)", offset, v);
break;
default:
LOG_CRITICAL(HW_GPU, "Unknown shift mode {}", static_cast<u32>(mode));
@ -992,34 +991,33 @@ void AppendProcTexShiftOffset(std::string& out, const std::string& v, ProcTexShi
}
}
void AppendProcTexClamp(std::string& out, const std::string& var, ProcTexClamp mode) {
void AppendProcTexClamp(std::string& out, std::string_view var, ProcTexClamp mode) {
switch (mode) {
case ProcTexClamp::ToZero:
out += var + " = " + var + " > 1.0 ? 0 : " + var + ";\n";
out += fmt::format("{0} = {0} > 1.0 ? 0 : {0};\n", var);
break;
case ProcTexClamp::ToEdge:
out += var + " = " + "min(" + var + ", 1.0);\n";
out += fmt::format("{0} = min({0}, 1.0);\n", var);
break;
case ProcTexClamp::SymmetricalRepeat:
out += var + " = " + "fract(" + var + ");\n";
out += fmt::format("{0} = fract({0});\n", var);
break;
case ProcTexClamp::MirroredRepeat: {
out +=
var + " = int(" + var + ") % 2 == 0 ? fract(" + var + ") : 1.0 - fract(" + var + ");\n";
out += fmt::format("{0} = int({0}) % 2 == 0 ? fract({0}) : 1.0 - fract({0});\n", var);
break;
}
case ProcTexClamp::Pulse:
out += var + " = " + var + " > 0.5 ? 1.0 : 0.0;\n";
out += fmt::format("{0} = {0} > 0.5 ? 1.0 : 0.0;\n", var);
break;
default:
LOG_CRITICAL(HW_GPU, "Unknown clamp mode {}", static_cast<u32>(mode));
out += var + " = " + "min(" + var + ", 1.0);\n";
out += fmt::format("{0} = min({0}, 1.0);\n", var);
break;
}
}
void AppendProcTexCombineAndMap(std::string& out, ProcTexCombiner combiner,
const std::string& offset) {
std::string_view offset) {
std::string combined;
switch (combiner) {
case ProcTexCombiner::U:
@ -1057,7 +1055,7 @@ void AppendProcTexCombineAndMap(std::string& out, ProcTexCombiner combiner,
combined = "0.0";
break;
}
out += "ProcTexLookupLUT(" + offset + ", " + combined + ")";
out += fmt::format("ProcTexLookupLUT({}, {})", offset, combined);
}
void AppendProcTexSampler(std::string& out, const PicaFSConfig& config) {
@ -1117,14 +1115,11 @@ float ProcTexNoiseCoef(vec2 x) {
}
out += "vec4 SampleProcTexColor(float lut_coord, int level) {\n";
out += "int lut_width = " + std::to_string(config.state.proctex.lut_width) + " >> level;\n";
std::string offset0 = std::to_string(config.state.proctex.lut_offset0);
std::string offset1 = std::to_string(config.state.proctex.lut_offset1);
std::string offset2 = std::to_string(config.state.proctex.lut_offset2);
std::string offset3 = std::to_string(config.state.proctex.lut_offset3);
out += fmt::format("int lut_width = {} >> level;\n", config.state.proctex.lut_width);
// Offsets for level 4-7 seem to be hardcoded
out += "int lut_offsets[8] = int[](" + offset0 + ", " + offset1 + ", " + offset2 + ", " +
offset3 + ", 0xF0, 0xF8, 0xFC, 0xFE);\n";
out += fmt::format("int lut_offsets[8] = int[]({}, {}, {}, {}, 0xF0, 0xF8, 0xFC, 0xFE);\n",
config.state.proctex.lut_offset0, config.state.proctex.lut_offset1,
config.state.proctex.lut_offset2, config.state.proctex.lut_offset3);
out += "int lut_offset = lut_offsets[level];\n";
// For the color lut, coord=0.0 is lut[offset] and coord=1.0 is lut[offset+width-1]
out += "lut_coord *= float(lut_width - 1);\n";
@ -1153,7 +1148,7 @@ float ProcTexNoiseCoef(vec2 x) {
out += "vec4 ProcTex() {\n";
if (config.state.proctex.coord < 3) {
out += "vec2 uv = abs(texcoord" + std::to_string(config.state.proctex.coord) + ");\n";
out += fmt::format("vec2 uv = abs(texcoord{});\n", config.state.proctex.coord);
} else {
LOG_CRITICAL(Render_OpenGL, "Unexpected proctex.coord >= 3");
out += "vec2 uv = abs(texcoord0);\n";
@ -1165,12 +1160,12 @@ float ProcTexNoiseCoef(vec2 x) {
// Note: this is different from the one normal 2D textures use.
out += "vec2 duv = max(abs(dFdx(uv)), abs(dFdy(uv)));\n";
// unlike normal texture, the bias is inside the log2
out += "float lod = log2(abs(float(" + std::to_string(config.state.proctex.lut_width) +
") * proctex_bias) * (duv.x + duv.y));\n";
out += fmt::format("float lod = log2(abs(float({}) * proctex_bias) * (duv.x + duv.y));\n",
config.state.proctex.lut_width);
out += "if (proctex_bias == 0.0) lod = 0.0;\n";
out += "lod = clamp(lod, " +
std::to_string(std::max<float>(0.0f, config.state.proctex.lod_min)) + ", " +
std::to_string(std::min<float>(7.0f, config.state.proctex.lod_max)) + ");\n";
out += fmt::format("lod = clamp(lod, {}, {});\n",
std::max(0.0f, static_cast<float>(config.state.proctex.lod_min)),
std::min(7.0f, static_cast<float>(config.state.proctex.lod_max)));
// Get shift offset before noise generation
out += "float u_shift = ";
AppendProcTexShiftOffset(out, "uv.y", config.state.proctex.u_shift,
@ -1183,13 +1178,13 @@ float ProcTexNoiseCoef(vec2 x) {
// Generate noise
if (config.state.proctex.noise_enable) {
out += "uv += proctex_noise_a * ProcTexNoiseCoef(uv);\n";
out += "uv = abs(uv);\n";
out += "uv += proctex_noise_a * ProcTexNoiseCoef(uv);\n"
"uv = abs(uv);\n";
}
// Shift
out += "float u = uv.x + u_shift;\n";
out += "float v = uv.y + v_shift;\n";
out += "float u = uv.x + u_shift;\n"
"float v = uv.y + v_shift;\n";
// Clamp
AppendProcTexClamp(out, "u", config.state.proctex.u_clamp);
@ -1212,9 +1207,9 @@ float ProcTexNoiseCoef(vec2 x) {
break;
case ProcTexFilter::NearestMipmapLinear:
case ProcTexFilter::LinearMipmapLinear:
out += "int lod_i = int(lod);\n";
out += "float lod_f = fract(lod);\n";
out += "vec4 final_color = mix(SampleProcTexColor(lut_coord, lod_i), "
out += "int lod_i = int(lod);\n"
"float lod_f = fract(lod);\n"
"vec4 final_color = mix(SampleProcTexColor(lut_coord, lod_i), "
"SampleProcTexColor(lut_coord, lod_i + 1), lod_f);\n";
break;
}
@ -1491,8 +1486,9 @@ vec4 secondary_fragment_color = vec4(0.0);
if (state.scissor_test_mode != RasterizerRegs::ScissorMode::Disabled) {
out += "if (";
// Negate the condition if we have to keep only the pixels outside the scissor box
if (state.scissor_test_mode == RasterizerRegs::ScissorMode::Include)
out += "!";
if (state.scissor_test_mode == RasterizerRegs::ScissorMode::Include) {
out += '!';
}
out += "(gl_FragCoord.x >= float(scissor_x1) && "
"gl_FragCoord.y >= float(scissor_y1) && "
"gl_FragCoord.x < float(scissor_x2) && "
@ -1502,8 +1498,8 @@ vec4 secondary_fragment_color = vec4(0.0);
// After perspective divide, OpenGL transform z_over_w from [-1, 1] to [near, far]. Here we use
// default near = 0 and far = 1, and undo the transformation to get the original z_over_w, then
// do our own transformation according to PICA specification.
out += "float z_over_w = 2.0 * gl_FragCoord.z - 1.0;\n";
out += "float depth = z_over_w * depth_scale + depth_offset;\n";
out += "float z_over_w = 2.0 * gl_FragCoord.z - 1.0;\n"
"float depth = z_over_w * depth_scale + depth_offset;\n";
if (state.depthmap_enable == RasterizerRegs::DepthBuffering::WBuffering) {
out += "depth /= gl_FragCoord.w;\n";
}
@ -1511,12 +1507,13 @@ vec4 secondary_fragment_color = vec4(0.0);
if (state.lighting.enable)
WriteLighting(out, config);
out += "vec4 combiner_buffer = vec4(0.0);\n";
out += "vec4 next_combiner_buffer = tev_combiner_buffer_color;\n";
out += "vec4 last_tex_env_out = vec4(0.0);\n";
out += "vec4 combiner_buffer = vec4(0.0);\n"
"vec4 next_combiner_buffer = tev_combiner_buffer_color;\n"
"vec4 last_tex_env_out = vec4(0.0);\n";
for (std::size_t index = 0; index < state.tev_stages.size(); ++index)
WriteTevStage(out, config, (unsigned)index);
for (std::size_t index = 0; index < state.tev_stages.size(); ++index) {
WriteTevStage(out, config, static_cast<u32>(index));
}
if (state.alpha_test_func != FramebufferRegs::CompareFunc::Always) {
out += "if (";
@ -1534,12 +1531,12 @@ vec4 secondary_fragment_color = vec4(0.0);
}
// Generate clamped fog factor from LUT for given fog index
out += "float fog_i = clamp(floor(fog_index), 0.0, 127.0);\n";
out += "float fog_f = fog_index - fog_i;\n";
out += "vec2 fog_lut_entry = texelFetch(texture_buffer_lut_rg, int(fog_i) + "
"fog_lut_offset).rg;\n";
out += "float fog_factor = fog_lut_entry.r + fog_lut_entry.g * fog_f;\n";
out += "fog_factor = clamp(fog_factor, 0.0, 1.0);\n";
out += "float fog_i = clamp(floor(fog_index), 0.0, 127.0);\n"
"float fog_f = fog_index - fog_i;\n"
"vec2 fog_lut_entry = texelFetch(texture_buffer_lut_rg, int(fog_i) + "
"fog_lut_offset).rg;\n"
"float fog_factor = fog_lut_entry.r + fog_lut_entry.g * fog_f;\n"
"fog_factor = clamp(fog_factor, 0.0, 1.0);\n";
// Blend the fog
out += "last_tex_env_out.rgb = mix(fog_color.rgb, last_tex_env_out.rgb, fog_factor);\n";
@ -1584,31 +1581,33 @@ do {
out += "color = byteround(last_tex_env_out);\n";
}
out += "}";
out += '}';
return {out};
}
ShaderDecompiler::ProgramResult GenerateTrivialVertexShader(bool separable_shader) {
std::string out = "";
std::string out;
if (separable_shader) {
out += "#extension GL_ARB_separate_shader_objects : enable\n";
}
out += "layout(location = " + std::to_string((int)ATTRIBUTE_POSITION) +
") in vec4 vert_position;\n";
out += "layout(location = " + std::to_string((int)ATTRIBUTE_COLOR) + ") in vec4 vert_color;\n";
out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD0) +
") in vec2 vert_texcoord0;\n";
out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD1) +
") in vec2 vert_texcoord1;\n";
out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD2) +
") in vec2 vert_texcoord2;\n";
out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD0_W) +
") in float vert_texcoord0_w;\n";
out += "layout(location = " + std::to_string((int)ATTRIBUTE_NORMQUAT) +
") in vec4 vert_normquat;\n";
out += "layout(location = " + std::to_string((int)ATTRIBUTE_VIEW) + ") in vec3 vert_view;\n";
out += fmt::format("layout(location = {}) in vec4 vert_position;\n",
static_cast<int>(ATTRIBUTE_POSITION));
out += fmt::format("layout(location = {}) in vec4 vert_color;\n",
static_cast<int>(ATTRIBUTE_COLOR));
out += fmt::format("layout(location = {}) in vec2 vert_texcoord0;\n",
static_cast<int>(ATTRIBUTE_TEXCOORD0));
out += fmt::format("layout(location = {}) in vec2 vert_texcoord1;\n",
static_cast<int>(ATTRIBUTE_TEXCOORD1));
out += fmt::format("layout(location = {}) in vec2 vert_texcoord2;\n",
static_cast<int>(ATTRIBUTE_TEXCOORD2));
out += fmt::format("layout(location = {}) in float vert_texcoord0_w;\n",
static_cast<int>(ATTRIBUTE_TEXCOORD0_W));
out += fmt::format("layout(location = {}) in vec4 vert_normquat;\n",
static_cast<int>(ATTRIBUTE_NORMQUAT));
out +=
fmt::format("layout(location = {}) in vec3 vert_view;\n", static_cast<int>(ATTRIBUTE_VIEW));
out += GetVertexInterfaceDeclaration(true, separable_shader);
@ -1645,16 +1644,16 @@ std::optional<ShaderDecompiler::ProgramResult> GenerateVertexShader(
out += ShaderDecompiler::GetCommonDeclarations();
std::array<bool, 16> used_regs{};
auto get_input_reg = [&](u32 reg) -> std::string {
const auto get_input_reg = [&used_regs](u32 reg) {
ASSERT(reg < 16);
used_regs[reg] = true;
return "vs_in_reg" + std::to_string(reg);
return fmt::format("vs_in_reg{}", reg);
};
auto get_output_reg = [&](u32 reg) -> std::string {
const auto get_output_reg = [&](u32 reg) -> std::string {
ASSERT(reg < 16);
if (config.state.output_map[reg] < config.state.num_outputs) {
return "vs_out_attr" + std::to_string(config.state.output_map[reg]);
return fmt::format("vs_out_attr{}", config.state.output_map[reg]);
}
return "";
};
@ -1678,11 +1677,10 @@ layout (std140) uniform vs_config {
// input attributes declaration
for (std::size_t i = 0; i < used_regs.size(); ++i) {
if (used_regs[i]) {
out += "layout(location = " + std::to_string(i) + ") in vec4 vs_in_reg" +
std::to_string(i) + ";\n";
out += fmt::format("layout(location = {0}) in vec4 vs_in_reg{0};\n", i);
}
}
out += "\n";
out += '\n';
// output attributes declaration
for (u32 i = 0; i < config.state.num_outputs; ++i) {
@ -1692,7 +1690,7 @@ layout (std140) uniform vs_config {
out += "\nvoid main() {\n";
for (u32 i = 0; i < config.state.num_outputs; ++i) {
out += " vs_out_attr" + std::to_string(i) + " = vec4(0.0, 0.0, 0.0, 1.0);\n";
out += fmt::format(" vs_out_attr{} = vec4(0.0, 0.0, 0.0, 1.0);\n", i);
}
out += "\n exec_shader();\n}\n\n";
@ -1715,15 +1713,15 @@ static std::string GetGSCommonSource(const PicaGSConfigCommonRaw& config, bool s
out += R"(
struct Vertex {
)";
out += " vec4 attributes[" + std::to_string(config.gs_output_attributes) + "];\n";
out += fmt::format(" vec4 attributes[{}];\n", config.gs_output_attributes);
out += "};\n\n";
auto semantic = [&config](VSOutputAttributes::Semantic slot_semantic) -> std::string {
u32 slot = static_cast<u32>(slot_semantic);
u32 attrib = config.semantic_maps[slot].attribute_index;
u32 comp = config.semantic_maps[slot].component_index;
const auto semantic = [&config](VSOutputAttributes::Semantic slot_semantic) -> std::string {
const u32 slot = static_cast<u32>(slot_semantic);
const u32 attrib = config.semantic_maps[slot].attribute_index;
const u32 comp = config.semantic_maps[slot].component_index;
if (attrib < config.gs_output_attributes) {
return "vtx.attributes[" + std::to_string(attrib) + "]." + "xyzw"[comp];
return fmt::format("vtx.attributes[{}].{}", attrib, "xyzw"[comp]);
}
return "0.0";
};
@ -1806,11 +1804,10 @@ void main() {
Vertex prim_buffer[3];
)";
for (u32 vtx = 0; vtx < 3; ++vtx) {
out += " prim_buffer[" + std::to_string(vtx) + "].attributes = vec4[" +
std::to_string(config.state.gs_output_attributes) + "](";
out += fmt::format(" prim_buffer[{}].attributes = vec4[{}](", vtx,
config.state.gs_output_attributes);
for (u32 i = 0; i < config.state.vs_output_attributes; ++i) {
out += std::string(i == 0 ? "" : ", ") + "vs_out_attr" + std::to_string(i) + "[" +
std::to_string(vtx) + "]";
out += fmt::format("{}vs_out_attr{}[{}]", i == 0 ? "" : ", ", i, vtx);
}
out += ");\n";
}