Files
LibretroAdmin 1396da6572 Video: build the vertex shaders by calls
The specialized vertex shader is built through rslc's builder instead
of written as GLSL for each backend to parse back: Vulkan gets SPIR-V,
D3D its bytecode and OpenGL GLSL straight from the built stage, with no
text generated or parsed on the compile workers.

The pieces the pixel shaders and the ubershaders will build from too
are shared: the vertex uniforms block, dolphin_isnan, the line and
point expansion and the lighting functions. The text generator and the
lighting macros nothing used are gone.

Built stages compile to the same words the text did for Vulkan and
D3D across random uids and host configurations.
2026-11-10 07:48:29 +01:00

452 lines
17 KiB
C++

// Copyright 2016 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/LightingShaderGen.h"
#include <cstring>
#include "Common/Assert.h"
#include "Common/CommonTypes.h"
#include "VideoCommon/ShaderGenCommon.h"
#include "VideoCommon/XFMemory.h"
static void GenerateLightShader(ShaderCode& object, const LightingUidData& uid_data, int index,
int litchan_index, bool alpha)
{
const char* swizzle = alpha ? "a" : "rgb";
const char* swizzle_components = (alpha) ? "" : "3";
const auto attnfunc =
static_cast<AttenuationFunc>((uid_data.attnfunc >> (2 * litchan_index)) & 0x3);
const auto diffusefunc =
static_cast<DiffuseFunc>((uid_data.diffusefunc >> (2 * litchan_index)) & 0x3);
switch (attnfunc)
{
case AttenuationFunc::None:
case AttenuationFunc::Dir:
object.Printf("ldir = normalize(%s[%d].pos.xyz - pos.xyz);\n", (I_LIGHTS), (index));
object.Puts("attn = 1.0;\n");
object.Puts("if (length(ldir) == 0.0)\n"
"\t ldir = _normal;\n");
break;
case AttenuationFunc::Spec:
object.Printf("ldir = normalize(%s[%d].pos.xyz - pos.xyz);\n", (I_LIGHTS), (index));
object.Printf("attn = (dot(_normal, ldir) >= 0.0) ? max(0.0, dot(_normal, %s[%d].dir.xyz)) : 0.0;\n", (I_LIGHTS), (index));
object.Printf("cosAttn = %s[%d].cosatt.xyz;\n", (I_LIGHTS), (index));
object.Printf("distAttn = %s(%s[%d].distatt.xyz);\n", (diffusefunc == DiffuseFunc::None) ? "" : "normalize", (I_LIGHTS), (index));
object.Puts("attn = max(0.0f, dot(cosAttn, float3(1.0, attn, attn*attn))) / dot(distAttn, float3(1.0, attn, attn*attn));\n");
break;
case AttenuationFunc::Spot:
object.Printf("ldir = %s[%d].pos.xyz - pos.xyz;\n", (I_LIGHTS), (index));
object.Printf("dist2 = dot(ldir, ldir);\n"
"dist = sqrt(dist2);\n"
"ldir = ldir / dist;\n"
"attn = max(0.0, dot(ldir, %s[%d].dir.xyz));\n", (I_LIGHTS), (index));
// attn*attn may overflow
object.Printf("attn = max(0.0, %s[%d].cosatt.x + %s[%d].cosatt.y*attn + %s[%d].cosatt.z*attn*attn) / dot(%s[%d].distatt.xyz, float3(1.0,dist,dist2));\n", (I_LIGHTS), (index), (I_LIGHTS), (index), (I_LIGHTS), (index), (I_LIGHTS), (index));
break;
}
switch (diffusefunc)
{
case DiffuseFunc::None:
object.Printf("lacc.%s += int%s(round(attn * float%s(%s[%d].color.%s)));\n", swizzle, swizzle_components, swizzle_components, (I_LIGHTS), (index), (swizzle));
break;
case DiffuseFunc::Sign:
case DiffuseFunc::Clamp:
object.Printf("lacc.%s += int%s(round(attn * %sdot(ldir, _normal)) * float%s(%s[%d].color.%s)));\n", swizzle, swizzle_components, diffusefunc != DiffuseFunc::Sign ? "max(0.0," : "(", swizzle_components, (I_LIGHTS), (index), (swizzle));
break;
default:
ASSERT(false);
}
object.Puts("\n");
}
// vertex shader
// lights/colors
// materials name is I_MATERIALS in vs and I_PMATERIALS in ps
void GenerateLightingShaderHeader(ShaderCode& object, const LightingUidData& uid_data)
{
for (u32 j = 0; j < NUM_XF_COLOR_CHANNELS; j++)
{
object.Printf("vec4 dolphin_calculate_lighting_chn%u(vec4 base_color, vec3 pos, vec3 _normal)\n", j);
object.Puts("{\n");
object.Puts("\tint4 lacc;\n"
"\tvec3 ldir, h, cosAttn, distAttn;\n"
"\tfloat dist, dist2, attn;\n");
const bool colormatsource = !!(uid_data.matsource & (1 << j));
if (colormatsource) // from vertex
object.Puts("\tint4 mat = int4(round(base_color * 255.0));\n");
else // from color
object.Printf("\tint4 mat = %s[%u];\n", I_MATERIALS, j + 2);
if ((uid_data.enablelighting & (1 << j)) != 0)
{
if ((uid_data.ambsource & (1 << j)) != 0) // from vertex
object.Puts("\tlacc = int4(round(base_color * 255.0));\n");
else // from color
object.Printf("\tlacc = %s[%u];\n", I_MATERIALS, j);
}
else
{
object.Puts("\tlacc = int4(255, 255, 255, 255);\n");
}
// check if alpha is different
const bool alphamatsource = !!(uid_data.matsource & (1 << (j + 2)));
if (alphamatsource != colormatsource)
{
if (alphamatsource) // from vertex
object.Puts("\tmat.w = int(round(base_color.w * 255.0));\n");
else // from color
object.Printf("\tmat.w = %s[%u].w;\n", I_MATERIALS, j + 2);
}
if ((uid_data.enablelighting & (1 << (j + 2))) != 0)
{
if ((uid_data.ambsource & (1 << (j + 2))) != 0) // from vertex
object.Puts("\tlacc.w = int(round(base_color.w * 255.0));\n");
else // from color
object.Printf("\tlacc.w = %s[%u].w;\n", I_MATERIALS, j);
}
else
{
object.Puts("\tlacc.w = 255;\n");
}
if ((uid_data.enablelighting & (1 << j)) != 0) // Color lights
{
for (int i = 0; i < 8; ++i)
{
if ((uid_data.light_mask & (1 << (i + 8 * j))) != 0)
GenerateLightShader(object, uid_data, i, j, false);
}
}
if ((uid_data.enablelighting & (1 << (j + 2))) != 0) // Alpha lights
{
for (int i = 0; i < 8; ++i)
{
if ((uid_data.light_mask & (1 << (i + 8 * (j + 2)))) != 0)
GenerateLightShader(object, uid_data, i, j + 2, true);
}
}
object.Puts("\tlacc = clamp(lacc, 0, 255);\n");
object.Puts("\treturn vec4((mat * (lacc + (lacc >> 7))) >> 8) / 255.0;\n");
object.Puts("}\n"
"\n");
}
}
void GetLightingShaderUid(LightingUidData& uid_data)
{
for (u32 j = 0; j < NUM_XF_COLOR_CHANNELS; j++)
{
uid_data.matsource |= static_cast<u32>(xfmem.color[j].matsource.Value()) << j;
uid_data.matsource |= static_cast<u32>(xfmem.alpha[j].matsource.Value()) << (j + 2);
uid_data.enablelighting |= xfmem.color[j].enablelighting << j;
uid_data.enablelighting |= xfmem.alpha[j].enablelighting << (j + 2);
if ((uid_data.enablelighting & (1 << j)) != 0) // Color lights
{
uid_data.ambsource |= static_cast<u32>(xfmem.color[j].ambsource.Value()) << j;
uid_data.attnfunc |= static_cast<u32>(xfmem.color[j].attnfunc.Value()) << (2 * j);
uid_data.diffusefunc |= static_cast<u32>(xfmem.color[j].diffusefunc.Value()) << (2 * j);
uid_data.light_mask |= xfmem.color[j].GetFullLightMask() << (8 * j);
}
if ((uid_data.enablelighting & (1 << (j + 2))) != 0) // Alpha lights
{
uid_data.ambsource |= static_cast<u32>(xfmem.alpha[j].ambsource.Value()) << (j + 2);
uid_data.attnfunc |= static_cast<u32>(xfmem.alpha[j].attnfunc.Value()) << (2 * (j + 2));
uid_data.diffusefunc |= static_cast<u32>(xfmem.alpha[j].diffusefunc.Value()) << (2 * (j + 2));
uid_data.light_mask |= xfmem.alpha[j].GetFullLightMask() << (8 * (j + 2));
}
}
}
/* ---- the same, built by calls ---- */
namespace
{
enum
{
LIGHT_COLOR,
LIGHT_COSATT,
LIGHT_DISTATT,
LIGHT_POS,
LIGHT_DIR
};
struct light_build
{
const struct shader_build* sb;
const LightingUidData* uid_data;
struct rslc_var* materials;
struct rslc_var* lights;
struct rslc_var *base_color, *pos, *normal;
struct rslc_var *lacc, *ldir, *cos_attn, *dist_attn, *dist, *dist2, *attn, *mat;
};
/* clights[index].field */
uint32_t lb_light(const struct light_build* l, int index, int field)
{
const struct shader_build* sb = l->sb;
return sb_mem(sb, sb_idx(sb, sb_ref(sb, l->lights), sb_i(sb, index)),
static_cast<uint32_t>(field));
}
/* cmtrl[index] */
uint32_t lb_material(const struct light_build* l, u32 index)
{
return sb_idx(l->sb, sb_ref(l->sb, l->materials), sb_i(l->sb, static_cast<int32_t>(index)));
}
/* round(e * 255.0) */
uint32_t lb_round255(const struct light_build* l, uint32_t e)
{
const struct shader_build* sb = l->sb;
return sb_call(sb, "round", 1, sb_bin(sb, RSLC_B_MUL, e, sb_f(sb, 255.0)));
}
/* float3(1.0, a, a*a) */
uint32_t lb_poly(const struct light_build* l, uint32_t a, uint32_t b, uint32_t c)
{
return sb_con(l->sb, l->sb->f[3], 3, sb_f(l->sb, 1.0), a, sb_bin(l->sb, RSLC_B_MUL, b, c));
}
void BuildLightShader(const struct light_build* l, int index, int litchan_index, bool alpha)
{
const struct shader_build* sb = l->sb;
const LightingUidData& uid_data = *l->uid_data;
const char* swizzle = alpha ? "a" : "rgb";
const unsigned n = alpha ? 1 : 3;
uint32_t e;
const auto attnfunc =
static_cast<AttenuationFunc>((uid_data.attnfunc >> (2 * litchan_index)) & 0x3);
const auto diffusefunc =
static_cast<DiffuseFunc>((uid_data.diffusefunc >> (2 * litchan_index)) & 0x3);
#define LB(v) sb_ref(sb, l->v)
switch (attnfunc)
{
case AttenuationFunc::None:
case AttenuationFunc::Dir:
sb_set(sb, LB(ldir),
sb_call(sb, "normalize", 1,
sb_bin(sb, RSLC_B_SUB, sb_swz(sb, lb_light(l, index, LIGHT_POS), "xyz"),
sb_swz(sb, LB(pos), "xyz"))));
sb_set(sb, LB(attn), sb_f(sb, 1.0));
rslc_b_stmt(sb->b, rslc_b_if(sb->b,
sb_bin(sb, RSLC_B_EQ, sb_call(sb, "length", 1, LB(ldir)),
sb_f(sb, 0.0)),
rslc_b_expr(sb->b, rslc_b_assign(sb->b, RSLC_B_SET, LB(ldir),
LB(normal))),
0));
break;
case AttenuationFunc::Spec:
sb_set(sb, LB(ldir),
sb_call(sb, "normalize", 1,
sb_bin(sb, RSLC_B_SUB, sb_swz(sb, lb_light(l, index, LIGHT_POS), "xyz"),
sb_swz(sb, LB(pos), "xyz"))));
sb_set(sb, LB(attn),
rslc_b_select(sb->b,
sb_bin(sb, RSLC_B_GE, sb_call(sb, "dot", 2, LB(normal), LB(ldir)),
sb_f(sb, 0.0)),
sb_call(sb, "max", 2, sb_f(sb, 0.0),
sb_call(sb, "dot", 2, LB(normal),
sb_swz(sb, lb_light(l, index, LIGHT_DIR), "xyz"))),
sb_f(sb, 0.0)));
sb_set(sb, LB(cos_attn), sb_swz(sb, lb_light(l, index, LIGHT_COSATT), "xyz"));
e = sb_swz(sb, lb_light(l, index, LIGHT_DISTATT), "xyz");
if (diffusefunc != DiffuseFunc::None)
e = sb_call(sb, "normalize", 1, e);
sb_set(sb, LB(dist_attn), e);
sb_set(sb, LB(attn),
sb_bin(sb, RSLC_B_DIV,
sb_call(sb, "max", 2, sb_f(sb, 0.0),
sb_call(sb, "dot", 2, LB(cos_attn),
lb_poly(l, LB(attn), LB(attn), LB(attn)))),
sb_call(sb, "dot", 2, LB(dist_attn), lb_poly(l, LB(attn), LB(attn), LB(attn)))));
break;
case AttenuationFunc::Spot:
sb_set(sb, LB(ldir),
sb_bin(sb, RSLC_B_SUB, sb_swz(sb, lb_light(l, index, LIGHT_POS), "xyz"),
sb_swz(sb, LB(pos), "xyz")));
sb_set(sb, LB(dist2), sb_call(sb, "dot", 2, LB(ldir), LB(ldir)));
sb_set(sb, LB(dist), sb_call(sb, "sqrt", 1, LB(dist2)));
sb_set(sb, LB(ldir), sb_bin(sb, RSLC_B_DIV, LB(ldir), LB(dist)));
sb_set(sb, LB(attn),
sb_call(sb, "max", 2, sb_f(sb, 0.0),
sb_call(sb, "dot", 2, LB(ldir),
sb_swz(sb, lb_light(l, index, LIGHT_DIR), "xyz"))));
// attn*attn may overflow
e = sb_bin(sb, RSLC_B_ADD,
sb_bin(sb, RSLC_B_ADD, sb_swz(sb, lb_light(l, index, LIGHT_COSATT), "x"),
sb_bin(sb, RSLC_B_MUL, sb_swz(sb, lb_light(l, index, LIGHT_COSATT), "y"),
LB(attn))),
sb_bin(sb, RSLC_B_MUL,
sb_bin(sb, RSLC_B_MUL, sb_swz(sb, lb_light(l, index, LIGHT_COSATT), "z"),
LB(attn)),
LB(attn)));
sb_set(sb, LB(attn),
sb_bin(sb, RSLC_B_DIV, sb_call(sb, "max", 2, sb_f(sb, 0.0), e),
sb_call(sb, "dot", 2, sb_swz(sb, lb_light(l, index, LIGHT_DISTATT), "xyz"),
sb_con(sb, sb->f[3], 3, sb_f(sb, 1.0), LB(dist), LB(dist2)))));
break;
}
/* float3(clights[index].color.rgb), or float(...a) */
e = sb_con(sb, sb->f[n], 1, sb_swz(sb, lb_light(l, index, LIGHT_COLOR), swizzle));
switch (diffusefunc)
{
case DiffuseFunc::None:
e = sb_bin(sb, RSLC_B_MUL, LB(attn), e);
break;
case DiffuseFunc::Sign:
e = sb_bin(sb, RSLC_B_MUL,
sb_bin(sb, RSLC_B_MUL, LB(attn), sb_call(sb, "dot", 2, LB(ldir), LB(normal))), e);
break;
case DiffuseFunc::Clamp:
e = sb_bin(sb, RSLC_B_MUL,
sb_bin(sb, RSLC_B_MUL, LB(attn),
sb_call(sb, "max", 2, sb_f(sb, 0.0),
sb_call(sb, "dot", 2, LB(ldir), LB(normal)))),
e);
break;
default:
ASSERT(false);
return;
}
sb_op_set(sb, RSLC_B_ADD, sb_swz(sb, LB(lacc), swizzle),
sb_con(sb, sb->i[n], 1, sb_call(sb, "round", 1, e)));
#undef LB
}
} // namespace
void BuildLightingShaderHeader(const struct shader_build* sb, const LightingUidData& uid_data,
struct rslc_var* materials, struct rslc_var* lights,
struct rslc_func** chn)
{
static const char* const fn_names[NUM_XF_COLOR_CHANNELS] = {"dolphin_calculate_lighting_chn0",
"dolphin_calculate_lighting_chn1"};
static const char* const param_names[3] = {"base_color", "pos", "_normal"};
static const unsigned dirs[3] = {RSLC_B_IN, RSLC_B_IN, RSLC_B_IN};
const struct rslc_type* param_types[3];
struct light_build l;
u32 j;
param_types[0] = sb->f[4];
param_types[1] = sb->f[3];
param_types[2] = sb->f[3];
memset(&l, 0, sizeof(l));
l.sb = sb;
l.uid_data = &uid_data;
l.materials = materials;
l.lights = lights;
for (j = 0; j < NUM_XF_COLOR_CHANNELS; j++)
{
const bool colormatsource = !!(uid_data.matsource & (1 << j));
const bool alphamatsource = !!(uid_data.matsource & (1 << (j + 2)));
struct rslc_func* f = rslc_b_func(sb->b, fn_names[j], sb->f[4], 3, param_types, dirs,
param_names);
uint32_t e;
int i;
chn[j] = f;
rslc_b_func_begin(sb->b, f);
l.base_color = rslc_b_param(f, 0);
l.pos = rslc_b_param(f, 1);
l.normal = rslc_b_param(f, 2);
#define LB(v) sb_ref(sb, l.v)
l.lacc = rslc_b_local(sb->b, "lacc", sb->i[4], 0, false);
l.ldir = rslc_b_local(sb->b, "ldir", sb->f[3], 0, false);
rslc_b_local(sb->b, "h", sb->f[3], 0, false);
l.cos_attn = rslc_b_local(sb->b, "cosAttn", sb->f[3], 0, false);
l.dist_attn = rslc_b_local(sb->b, "distAttn", sb->f[3], 0, false);
l.dist = rslc_b_local(sb->b, "dist", sb->f[1], 0, false);
l.dist2 = rslc_b_local(sb->b, "dist2", sb->f[1], 0, false);
l.attn = rslc_b_local(sb->b, "attn", sb->f[1], 0, false);
if (colormatsource) // from vertex
e = sb_con(sb, sb->i[4], 1, lb_round255(&l, LB(base_color)));
else // from color
e = lb_material(&l, j + 2);
l.mat = rslc_b_local(sb->b, "mat", sb->i[4], e, false);
if ((uid_data.enablelighting & (1 << j)) != 0)
{
if ((uid_data.ambsource & (1 << j)) != 0) // from vertex
sb_set(sb, LB(lacc), sb_con(sb, sb->i[4], 1, lb_round255(&l, LB(base_color))));
else // from color
sb_set(sb, LB(lacc), lb_material(&l, j));
}
else
{
sb_set(sb, LB(lacc),
sb_con(sb, sb->i[4], 4, sb_i(sb, 255), sb_i(sb, 255), sb_i(sb, 255), sb_i(sb, 255)));
}
// check if alpha is different
if (alphamatsource != colormatsource)
{
if (alphamatsource) // from vertex
sb_set(sb, sb_swz(sb, LB(mat), "w"),
sb_con(sb, sb->i[1], 1, lb_round255(&l, sb_swz(sb, LB(base_color), "w"))));
else // from color
sb_set(sb, sb_swz(sb, LB(mat), "w"), sb_swz(sb, lb_material(&l, j + 2), "w"));
}
if ((uid_data.enablelighting & (1 << (j + 2))) != 0)
{
if ((uid_data.ambsource & (1 << (j + 2))) != 0) // from vertex
sb_set(sb, sb_swz(sb, LB(lacc), "w"),
sb_con(sb, sb->i[1], 1, lb_round255(&l, sb_swz(sb, LB(base_color), "w"))));
else // from color
sb_set(sb, sb_swz(sb, LB(lacc), "w"), sb_swz(sb, lb_material(&l, j), "w"));
}
else
{
sb_set(sb, sb_swz(sb, LB(lacc), "w"), sb_i(sb, 255));
}
if ((uid_data.enablelighting & (1 << j)) != 0) // Color lights
{
for (i = 0; i < 8; ++i)
{
if ((uid_data.light_mask & (1 << (i + 8 * j))) != 0)
BuildLightShader(&l, i, static_cast<int>(j), false);
}
}
if ((uid_data.enablelighting & (1 << (j + 2))) != 0) // Alpha lights
{
for (i = 0; i < 8; ++i)
{
if ((uid_data.light_mask & (1 << (i + 8 * (j + 2)))) != 0)
BuildLightShader(&l, i, static_cast<int>(j) + 2, true);
}
}
sb_set(sb, LB(lacc), sb_call(sb, "clamp", 3, LB(lacc), sb_i(sb, 0), sb_i(sb, 255)));
rslc_b_stmt(
sb->b,
rslc_b_return(
sb->b,
sb_bin(sb, RSLC_B_DIV,
sb_con(sb, sb->f[4], 1,
sb_bin(sb, RSLC_B_SHR,
sb_bin(sb, RSLC_B_MUL, LB(mat),
sb_bin(sb, RSLC_B_ADD, LB(lacc),
sb_bin(sb, RSLC_B_SHR, LB(lacc), sb_i(sb, 7)))),
sb_i(sb, 8))),
sb_f(sb, 255.0))));
#undef LB
rslc_b_func_end(sb->b);
}
}