mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-10 09:11:52 -04:00
452b478bb3
* Fix already downloaded toolchain re-use the following mv command would move $work into $toolchain_dir if the $toolchain_dir folder already existed. * resolve z-fighting
747 lines
27 KiB
C++
747 lines
27 KiB
C++
#include "shader_info.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <bitset>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#if !defined(_LIBCPP_VERSION)
|
|
#include <execution>
|
|
#endif
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#include <tracy/Tracy.hpp>
|
|
|
|
namespace aurora::gx {
|
|
// TODO: remove, just for testing
|
|
bool enableLodBias = true;
|
|
|
|
namespace {
|
|
Module Log("aurora::gx");
|
|
|
|
bool is_alpha_bump_channel(GXChannelID id) { return id == GX_ALPHA_BUMP || id == GX_ALPHA_BUMPN; }
|
|
|
|
bool sampled_color_channel(GXChannelID id, size_t& out) {
|
|
switch (id) {
|
|
case GX_COLOR0:
|
|
case GX_ALPHA0:
|
|
case GX_COLOR0A0:
|
|
out = 0;
|
|
return true;
|
|
case GX_COLOR1:
|
|
case GX_ALPHA1:
|
|
case GX_COLOR1A1:
|
|
out = 1;
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Fog fog_uniform() {
|
|
const auto& state = g_gxState.fog;
|
|
Fog fog{
|
|
.color = state.color,
|
|
.a = state.aRaw,
|
|
.b = static_cast<float>(state.bMagnitude),
|
|
.c = state.c,
|
|
.pad = static_cast<float>(state.bShift),
|
|
};
|
|
|
|
const u32 rangeBase = g_gxState.fogRange[0];
|
|
const bool rangeEnabled = (rangeBase & (1u << 10)) != 0;
|
|
float rangeWidth = std::abs(g_gxState.xfViewport[0]) * 2.0f;
|
|
if (rangeWidth <= 0.0f) {
|
|
rangeWidth = 1.0f;
|
|
}
|
|
const int rangeCenter = static_cast<int>(rangeBase & 0x3ffu) - 342;
|
|
const float screenSpaceCenter = rangeEnabled ? ((static_cast<float>(rangeCenter) / rangeWidth) * 2.0f) - 1.0f
|
|
: 0.0f;
|
|
fog.rangeBase = {screenSpaceCenter, rangeEnabled ? rangeWidth : 1.0f, 0.0f, 0.0f};
|
|
|
|
std::array<float, 12> rangeK{};
|
|
size_t kIndex = 0;
|
|
for (size_t i = 1; i < g_gxState.fogRange.size(); ++i) {
|
|
const u32 packed = g_gxState.fogRange[i];
|
|
rangeK[kIndex++] = static_cast<float>((packed >> 12) & 0xfffu) / 64.0f;
|
|
rangeK[kIndex++] = static_cast<float>(packed & 0xfffu) / 64.0f;
|
|
}
|
|
rangeK[10] = rangeK[9];
|
|
rangeK[11] = rangeK[9];
|
|
for (size_t i = 0; i < fog.rangeK.size(); ++i) {
|
|
fog.rangeK[i] = {rangeK[i * 4 + 0], rangeK[i * 4 + 1], rangeK[i * 4 + 2], rangeK[i * 4 + 3]};
|
|
}
|
|
return fog;
|
|
}
|
|
|
|
float sanitize_light_dir_component(float value) noexcept {
|
|
u32 bits = 0;
|
|
std::memcpy(&bits, &value, sizeof(bits));
|
|
if ((bits & 0x7f800000u) != 0x7f800000u) {
|
|
return value;
|
|
}
|
|
if ((bits & 0x007fffffu) != 0) {
|
|
return 0.0f;
|
|
}
|
|
return (bits & 0x80000000u) != 0 ? -1.0f : 1.0f;
|
|
}
|
|
} // namespace
|
|
|
|
Light prepare_shader_light(Light light) noexcept {
|
|
if (std::fabs(light.distAtt[0]) < 0.00001f && std::fabs(light.distAtt[1]) < 0.00001f &&
|
|
std::fabs(light.distAtt[2]) < 0.00001f) {
|
|
light.distAtt[0] = 0.00001f;
|
|
}
|
|
|
|
const double normSq = static_cast<double>(light.dir[0]) * static_cast<double>(light.dir[0]) +
|
|
static_cast<double>(light.dir[1]) * static_cast<double>(light.dir[1]) +
|
|
static_cast<double>(light.dir[2]) * static_cast<double>(light.dir[2]);
|
|
if (normSq == 0.0) {
|
|
light.dir[0] = 0.0f;
|
|
light.dir[1] = 0.0f;
|
|
light.dir[2] = 0.0f;
|
|
return light;
|
|
}
|
|
const double invNorm = 1.0 / std::sqrt(normSq);
|
|
// Aurora is built with fast-math, under which std::isnan/std::isinf may be optimized away.
|
|
light.dir[0] = sanitize_light_dir_component(static_cast<float>(light.dir[0] * invNorm));
|
|
light.dir[1] = sanitize_light_dir_component(static_cast<float>(light.dir[1] * invNorm));
|
|
light.dir[2] = sanitize_light_dir_component(static_cast<float>(light.dir[2] * invNorm));
|
|
return light;
|
|
}
|
|
|
|
namespace {
|
|
Vec4<float> texture_size_bias(const gfx::TextureBind& tex) {
|
|
auto width = static_cast<float>(tex.texObj.width());
|
|
auto height = static_cast<float>(tex.texObj.height());
|
|
const auto vpBias =
|
|
enableLodBias && tex.ref && tex.ref->hasArbitraryMips
|
|
? log2(std::min(g_gxState.renderViewport.width / std::max(g_gxState.logicalViewport.width, 1.f),
|
|
g_gxState.renderViewport.height / std::max(g_gxState.logicalViewport.height, 1.f)))
|
|
: 0.f;
|
|
return {width, height, tex.texObj.lod_bias() + vpBias, 0.0f};
|
|
}
|
|
|
|
Vec4<float> texcoord_scale(const TexCoordScale& scale) {
|
|
return {static_cast<float>(scale.scaleS) + 1.0f, static_cast<float>(scale.scaleT) + 1.0f,
|
|
scale.biasS ? 1.0f : 0.0f, scale.biasT ? 1.0f : 0.0f};
|
|
}
|
|
|
|
void mark_texture_sample(const TevStageTextureDependency& dependency, ShaderInfo& info) {
|
|
if (!dependency.canSampleTexture) {
|
|
return;
|
|
}
|
|
info.sampledTexCoords.set(static_cast<size_t>(dependency.texCoordId));
|
|
info.sampledTextures.set(static_cast<size_t>(dependency.texMapId));
|
|
}
|
|
|
|
void mark_tev_reg_read(GXTevRegID reg, bool alpha, ShaderInfo& info) {
|
|
auto& writes = alpha ? info.writesTevRegAlpha : info.writesTevRegRgb;
|
|
auto& loads = alpha ? info.loadsTevRegAlpha : info.loadsTevRegRgb;
|
|
if (!writes.test(reg)) {
|
|
loads.set(reg);
|
|
}
|
|
}
|
|
|
|
void color_arg_reg_info(GXTevColorArg arg, const TevStage& stage,
|
|
const TevStageTextureDependency& textureDependency, ShaderInfo& info) {
|
|
switch (arg) {
|
|
case GX_CC_CPREV:
|
|
mark_tev_reg_read(GX_TEVPREV, false, info);
|
|
break;
|
|
case GX_CC_APREV:
|
|
mark_tev_reg_read(GX_TEVPREV, true, info);
|
|
break;
|
|
case GX_CC_C0:
|
|
mark_tev_reg_read(GX_TEVREG0, false, info);
|
|
break;
|
|
case GX_CC_A0:
|
|
mark_tev_reg_read(GX_TEVREG0, true, info);
|
|
break;
|
|
case GX_CC_C1:
|
|
mark_tev_reg_read(GX_TEVREG1, false, info);
|
|
break;
|
|
case GX_CC_A1:
|
|
mark_tev_reg_read(GX_TEVREG1, true, info);
|
|
break;
|
|
case GX_CC_C2:
|
|
mark_tev_reg_read(GX_TEVREG2, false, info);
|
|
break;
|
|
case GX_CC_A2:
|
|
mark_tev_reg_read(GX_TEVREG2, true, info);
|
|
break;
|
|
case GX_CC_TEXC:
|
|
case GX_CC_TEXA:
|
|
mark_texture_sample(textureDependency, info);
|
|
break;
|
|
case GX_CC_RASC:
|
|
case GX_CC_RASA:
|
|
if (stage.channelId != GX_COLOR_NULL && stage.channelId != GX_COLOR_ZERO &&
|
|
!is_alpha_bump_channel(stage.channelId)) {
|
|
size_t channel = 0;
|
|
if (sampled_color_channel(stage.channelId, channel)) {
|
|
info.sampledColorChannels.set(channel);
|
|
}
|
|
}
|
|
break;
|
|
case GX_CC_KONST:
|
|
switch (stage.kcSel) {
|
|
case GX_TEV_KCSEL_K0:
|
|
case GX_TEV_KCSEL_K0_R:
|
|
case GX_TEV_KCSEL_K0_G:
|
|
case GX_TEV_KCSEL_K0_B:
|
|
case GX_TEV_KCSEL_K0_A:
|
|
info.sampledKColors.set(0);
|
|
break;
|
|
case GX_TEV_KCSEL_K1:
|
|
case GX_TEV_KCSEL_K1_R:
|
|
case GX_TEV_KCSEL_K1_G:
|
|
case GX_TEV_KCSEL_K1_B:
|
|
case GX_TEV_KCSEL_K1_A:
|
|
info.sampledKColors.set(1);
|
|
break;
|
|
case GX_TEV_KCSEL_K2:
|
|
case GX_TEV_KCSEL_K2_R:
|
|
case GX_TEV_KCSEL_K2_G:
|
|
case GX_TEV_KCSEL_K2_B:
|
|
case GX_TEV_KCSEL_K2_A:
|
|
info.sampledKColors.set(2);
|
|
break;
|
|
case GX_TEV_KCSEL_K3:
|
|
case GX_TEV_KCSEL_K3_R:
|
|
case GX_TEV_KCSEL_K3_G:
|
|
case GX_TEV_KCSEL_K3_B:
|
|
case GX_TEV_KCSEL_K3_A:
|
|
info.sampledKColors.set(3);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void alpha_arg_reg_info(GXTevAlphaArg arg, const TevStage& stage,
|
|
const TevStageTextureDependency& textureDependency, ShaderInfo& info) {
|
|
switch (arg) {
|
|
case GX_CA_APREV:
|
|
mark_tev_reg_read(GX_TEVPREV, true, info);
|
|
break;
|
|
case GX_CA_A0:
|
|
mark_tev_reg_read(GX_TEVREG0, true, info);
|
|
break;
|
|
case GX_CA_A1:
|
|
mark_tev_reg_read(GX_TEVREG1, true, info);
|
|
break;
|
|
case GX_CA_A2:
|
|
mark_tev_reg_read(GX_TEVREG2, true, info);
|
|
break;
|
|
case GX_CA_TEXA:
|
|
mark_texture_sample(textureDependency, info);
|
|
break;
|
|
case GX_CA_RASA:
|
|
if (stage.channelId != GX_COLOR_NULL && stage.channelId != GX_COLOR_ZERO &&
|
|
!is_alpha_bump_channel(stage.channelId)) {
|
|
size_t channel = 0;
|
|
if (sampled_color_channel(stage.channelId, channel)) {
|
|
info.sampledColorChannels.set(channel);
|
|
}
|
|
}
|
|
break;
|
|
case GX_CA_KONST:
|
|
switch (stage.kaSel) {
|
|
case GX_TEV_KASEL_K0_R:
|
|
case GX_TEV_KASEL_K0_G:
|
|
case GX_TEV_KASEL_K0_B:
|
|
case GX_TEV_KASEL_K0_A:
|
|
info.sampledKColors.set(0);
|
|
break;
|
|
case GX_TEV_KASEL_K1_R:
|
|
case GX_TEV_KASEL_K1_G:
|
|
case GX_TEV_KASEL_K1_B:
|
|
case GX_TEV_KASEL_K1_A:
|
|
info.sampledKColors.set(1);
|
|
break;
|
|
case GX_TEV_KASEL_K2_R:
|
|
case GX_TEV_KASEL_K2_G:
|
|
case GX_TEV_KASEL_K2_B:
|
|
case GX_TEV_KASEL_K2_A:
|
|
info.sampledKColors.set(2);
|
|
break;
|
|
case GX_TEV_KASEL_K3_R:
|
|
case GX_TEV_KASEL_K3_G:
|
|
case GX_TEV_KASEL_K3_B:
|
|
case GX_TEV_KASEL_K3_A:
|
|
info.sampledKColors.set(3);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
ShaderInfo build_shader_info(const ShaderConfig& config) noexcept {
|
|
ZoneScoped;
|
|
|
|
ShaderInfo info{
|
|
// vtx_start, current_pnmtx, render/logical viewport size, array_start, pad, proj
|
|
.uniformSize = 4 + 4 + 8 + 8 + 8 + 48 + 64,
|
|
};
|
|
|
|
if (config.lineMode != 0) {
|
|
info.uniformSize += 4 + 4 + 4 + 4; // line_width, line_aspect_y, line_tex_offset, line_texcoord_mask
|
|
info.lineMode = config.lineMode;
|
|
}
|
|
|
|
for (int attr = 0; attr < config.attrs.size(); attr++) {
|
|
const auto attrType = config.attrs[attr].attrType;
|
|
if ((attr == GX_VA_PNMTXIDX || (attr >= GX_VA_TEX0MTXIDX && attr <= GX_VA_TEX7MTXIDX)) && attrType == GX_DIRECT) {
|
|
info.indexAttr.set(attr);
|
|
} else if (attrType == GX_INDEX8 || attrType == GX_INDEX16) {
|
|
info.indexAttr.set(attr);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < config.tevStageCount; ++i) {
|
|
const auto& stage = config.tevStages[i];
|
|
const auto textureDependency = tev_stage_texture_dependency(config, static_cast<u32>(i));
|
|
// Color pass
|
|
color_arg_reg_info(stage.colorPass.a, stage, textureDependency, info);
|
|
color_arg_reg_info(stage.colorPass.b, stage, textureDependency, info);
|
|
color_arg_reg_info(stage.colorPass.c, stage, textureDependency, info);
|
|
color_arg_reg_info(stage.colorPass.d, stage, textureDependency, info);
|
|
info.writesTevRegRgb.set(stage.colorOp.outReg);
|
|
|
|
// Alpha pass
|
|
alpha_arg_reg_info(stage.alphaPass.a, stage, textureDependency, info);
|
|
alpha_arg_reg_info(stage.alphaPass.b, stage, textureDependency, info);
|
|
alpha_arg_reg_info(stage.alphaPass.c, stage, textureDependency, info);
|
|
alpha_arg_reg_info(stage.alphaPass.d, stage, textureDependency, info);
|
|
info.writesTevRegAlpha.set(stage.alphaOp.outReg);
|
|
}
|
|
if (const int zTexStage = tev_z_texture_stage(config); zTexStage >= 0) {
|
|
mark_texture_sample(tev_stage_texture_dependency(config, static_cast<u32>(zTexStage)), info);
|
|
}
|
|
for (int i = 0; i < config.tevStageCount; ++i) {
|
|
const auto& stage = config.tevStages[i];
|
|
const auto textureDependency = tev_stage_texture_dependency(config, static_cast<u32>(i));
|
|
|
|
// Coordinate state is independent of whether this stage has an enabled texture map.
|
|
if (textureDependency.needsFixedTexcoordState && textureDependency.texCoordId >= 0) {
|
|
info.sampledTexCoords.set(static_cast<size_t>(textureDependency.texCoordId));
|
|
}
|
|
|
|
// Skip if not enabled
|
|
if (stage.indTexStage >= config.numIndStages) {
|
|
continue;
|
|
}
|
|
const bool usesIndStage = stage.indTexMtxId != GX_ITM_OFF || is_alpha_bump_channel(stage.channelId);
|
|
if (!usesIndStage) {
|
|
continue;
|
|
}
|
|
info.usedIndStages.set(stage.indTexStage);
|
|
const auto& indStage = config.indStages[stage.indTexStage];
|
|
if (indStage.texMapId < MaxTextures) {
|
|
info.sampledTextures.set(indStage.texMapId);
|
|
info.sampledIndTextures.set(indStage.texMapId);
|
|
}
|
|
const int effectiveIndTexCoord = tev_effective_texcoord(config, indStage.texCoordId);
|
|
if (effectiveIndTexCoord >= 0) {
|
|
info.sampledTexCoords.set(static_cast<size_t>(effectiveIndTexCoord));
|
|
}
|
|
// Track which indirect matrix is used
|
|
if (stage.indTexMtxId >= GX_ITM_0 && stage.indTexMtxId <= GX_ITM_2) {
|
|
info.usedIndTexMtxs.set(stage.indTexMtxId - GX_ITM_0);
|
|
} else if (stage.indTexMtxId >= GX_ITM_S0 && stage.indTexMtxId <= GX_ITM_S2) {
|
|
info.usedIndTexMtxs.set(stage.indTexMtxId - GX_ITM_S0);
|
|
} else if (stage.indTexMtxId >= GX_ITM_T0 && stage.indTexMtxId <= GX_ITM_T2) {
|
|
info.usedIndTexMtxs.set(stage.indTexMtxId - GX_ITM_T0);
|
|
}
|
|
}
|
|
|
|
const auto loadsTevRegs = info.loadsTevRegRgb | info.loadsTevRegAlpha;
|
|
info.uniformSize += loadsTevRegs.count() * sizeof(Vec4<float>);
|
|
for (int i = 0; i < info.sampledColorChannels.size(); ++i) {
|
|
if (info.sampledColorChannels.test(i)) {
|
|
const auto& cc = config.colorChannels[i];
|
|
const auto& cca = config.colorChannels[i + GX_ALPHA0];
|
|
if (cc.lightingEnabled || cca.lightingEnabled) {
|
|
info.lightingEnabled = true;
|
|
}
|
|
}
|
|
}
|
|
if (info.lightingEnabled) {
|
|
// Lights + light state for all channels
|
|
info.uniformSize += 16 + sizeof(Light) * GX::MaxLights;
|
|
}
|
|
for (int i = 0; i < info.sampledColorChannels.size(); ++i) {
|
|
if (info.sampledColorChannels.test(i)) {
|
|
const auto& cc = config.colorChannels[i];
|
|
if (cc.lightingEnabled && cc.ambSrc == GX_SRC_REG) {
|
|
info.uniformSize += sizeof(Vec4<float>);
|
|
}
|
|
if (cc.matSrc == GX_SRC_REG) {
|
|
info.uniformSize += sizeof(Vec4<float>);
|
|
}
|
|
const auto& cca = config.colorChannels[i + GX_ALPHA0];
|
|
if (cca.lightingEnabled && cca.ambSrc == GX_SRC_REG) {
|
|
info.uniformSize += sizeof(Vec4<float>);
|
|
}
|
|
if (cca.matSrc == GX_SRC_REG) {
|
|
info.uniformSize += sizeof(Vec4<float>);
|
|
}
|
|
}
|
|
}
|
|
info.uniformSize += info.sampledKColors.count() * sizeof(Vec4<float>);
|
|
for (int i = 0; i < info.sampledTexCoords.size(); ++i) {
|
|
if (!info.sampledTexCoords.test(i)) {
|
|
continue;
|
|
}
|
|
const auto& tcg = config.tcgs[i];
|
|
if (config.dualTexEnabled && tcg.postMtx != GX_PTIDENTITY) {
|
|
u32 postMtxIdx = (tcg.postMtx - GX_PTTEXMTX0) / 3;
|
|
info.usesPTTexMtx.set(postMtxIdx);
|
|
}
|
|
}
|
|
if (info.usesPTTexMtx.any())
|
|
info.uniformSize += sizeof(Mat3x4<float>) * MaxPTTexMtx;
|
|
|
|
// Resolve the matrix slots this shader can read.
|
|
{
|
|
auto& layout = info.matrixLayout;
|
|
layout.postexRemap.fill(UniformMatrixLayout::kAbsent);
|
|
|
|
const bool dynamicPnMtx = config.attrs[GX_VA_PNMTXIDX].attrType != GX_NONE;
|
|
bool dynamicTexMtx = false;
|
|
std::bitset<MaxPostexMtx> literalSlots;
|
|
for (size_t i = 0; i < info.sampledTexCoords.size(); ++i) {
|
|
if (!info.sampledTexCoords.test(i)) {
|
|
continue;
|
|
}
|
|
const auto& tcg = config.tcgs[i];
|
|
if (tcg.type != GX_TG_MTX2x4 && tcg.type != GX_TG_MTX3x4) {
|
|
continue;
|
|
}
|
|
if (info.indexAttr.test(GX_VA_TEX0MTXIDX + i)) {
|
|
// A per-vertex texture matrix index addresses raw matrix memory and can name any row, including the position rows.
|
|
dynamicTexMtx = true;
|
|
continue;
|
|
}
|
|
if (tcg.mtx == GX_IDENTITY) {
|
|
continue;
|
|
}
|
|
const u32 slot = static_cast<u32>(tcg.mtx) / 3;
|
|
if (slot >= MaxPostexMtx) {
|
|
// Out of range for the shader array; preserve the uncompacted layout rather than inventing a different (still invalid) index.
|
|
dynamicTexMtx = true;
|
|
continue;
|
|
}
|
|
literalSlots.set(slot);
|
|
}
|
|
|
|
// At most MaxPostexMtx entries can ever be pushed: the absolute layout pushes the ten position slots plus at most ten texture slots, and the compacted layout pushes the current matrix plus at most one literal slot per texgen.
|
|
const auto pushPostex = [&](u8 slot) {
|
|
CHECK(layout.postexCount < layout.postexSlots.size(), "postex matrix layout overflow");
|
|
layout.postexSlots[layout.postexCount++] = slot;
|
|
};
|
|
const auto pushNrm = [&](u8 slot) {
|
|
CHECK(layout.nrmCount < layout.nrmSlots.size(), "normal matrix layout overflow");
|
|
layout.nrmSlots[layout.nrmCount++] = slot;
|
|
};
|
|
|
|
layout.absolutePosRegion = dynamicPnMtx || dynamicTexMtx;
|
|
if (!layout.absolutePosRegion) {
|
|
// Compact slot 0 always holds the matrix selected by the current matrix index; build_uniform writes 0 into `current_pnmtx` to match.
|
|
pushPostex(UniformMatrixLayout::kCurrentPnMtx);
|
|
pushNrm(UniformMatrixLayout::kCurrentPnMtx);
|
|
}
|
|
for (u32 slot = 0; slot < MaxPnMtx; ++slot) {
|
|
if (!layout.absolutePosRegion && !literalSlots.test(slot)) {
|
|
continue;
|
|
}
|
|
layout.postexRemap[slot] = layout.postexCount;
|
|
pushPostex(static_cast<u8>(slot));
|
|
if (layout.absolutePosRegion) {
|
|
// The normal array is indexed by the same expression as the position array, so its compact indices have to line up with the position slots.
|
|
pushNrm(static_cast<u8>(slot));
|
|
}
|
|
}
|
|
for (u32 slot = MaxPnMtx; slot < MaxPostexMtx; ++slot) {
|
|
if (!dynamicTexMtx && !literalSlots.test(slot)) {
|
|
continue;
|
|
}
|
|
layout.postexRemap[slot] = layout.postexCount;
|
|
pushPostex(static_cast<u8>(slot));
|
|
}
|
|
// `postex_mtx[in_pnmtxidx]` and `nrm_mtx[in_pnmtxidx]` are emitted unconditionally, so neither array can ever be empty.
|
|
info.uniformSize += sizeof(Mat3x4<float>) * (layout.postexCount + layout.nrmCount);
|
|
}
|
|
if (config.fogType != GX_FOG_NONE) {
|
|
info.usesFog = true;
|
|
info.uniformSize += sizeof(Fog);
|
|
}
|
|
if (info.usedIndTexMtxs.any()) {
|
|
info.uniformSize += MaxIndTexMtxs * 2 * sizeof(Vec4<s32>);
|
|
}
|
|
info.uniformSize += info.sampledTexCoords.count() * sizeof(Vec4<float>);
|
|
info.uniformSize += info.sampledTextures.count() * sizeof(Vec4<float>);
|
|
info.uniformSize = gfx::align_uniform(info.uniformSize);
|
|
if (info.uniformSize > MaxUniformSize) {
|
|
Log.fatal("Uniform size exceeds maximum: {} > {}", info.uniformSize, MaxUniformSize);
|
|
}
|
|
return info;
|
|
}
|
|
|
|
static f32 tex_offset(GXTexOffset offs) noexcept {
|
|
switch (offs) {
|
|
DEFAULT_FATAL("invalid tex offset {}", underlying(offs));
|
|
case GX_TO_ZERO:
|
|
return 0.f;
|
|
case GX_TO_SIXTEENTH:
|
|
return 1.f / 16.f;
|
|
case GX_TO_EIGHTH:
|
|
return 1.f / 8.f;
|
|
case GX_TO_FOURTH:
|
|
return 1.f / 4.f;
|
|
case GX_TO_HALF:
|
|
return 1.f / 2.f;
|
|
case GX_TO_ONE:
|
|
return 1.f;
|
|
}
|
|
}
|
|
|
|
static u32 point_texcoord_mask() noexcept {
|
|
u32 mask = 0;
|
|
for (int i = 0; i < MaxTexCoord; ++i) {
|
|
if (g_gxState.texCoordScales[i].pointOffset) {
|
|
mask |= 1 << i;
|
|
}
|
|
}
|
|
return mask;
|
|
}
|
|
|
|
static u32 line_texcoord_mask() noexcept {
|
|
u32 mask = 0;
|
|
for (int i = 0; i < MaxTexCoord; ++i) {
|
|
if (g_gxState.texCoordScales[i].lineOffset) {
|
|
mask |= 1 << i;
|
|
}
|
|
}
|
|
return mask;
|
|
}
|
|
|
|
namespace {
|
|
// Largest possible staged prefix: scalar head (80) + line/point block (16) + projection matrix + every matrix the uncompacted layout can hold.
|
|
constexpr size_t kStagedUniformBytes =
|
|
96 + sizeof(Mat4x4<float>) + sizeof(Mat3x4<float>) * (MaxPostexMtx + MaxPnMtx);
|
|
|
|
// The host viewport always receives the normalized GX depth window (render_pass_impl clamps to minDepth <= maxDepth).
|
|
//
|
|
// Folds the near/far depth correction the vertex shader used to apply per-vertex directly into the
|
|
// projection matrix instead (matching upstream aurora commit 1dde08fa, "Move depth correction to
|
|
// projection matrix") - valid because the correction is a linear combination of the z/w rows, so
|
|
// applying it once here to the row is equivalent to applying it once per-vertex to the dot product,
|
|
// and it must be applied exactly once: doing it here AND in the shader (the previous bug) canceled
|
|
// the negation out for `flip`, silently making "reversed" Z behave identically to forward Z.
|
|
// `flip` decides which of the two single-application forms this draw needs: true bakes in the
|
|
// reversed-Z inversion (z' = -z), false bakes in the forward-Z near/far combination (z' = z + w) -
|
|
// exactly one always applies, never both, and never neither.
|
|
static Mat4x4<float> effective_projection() noexcept {
|
|
const auto& vp = g_gxState.renderViewport;
|
|
const bool flip = (vp.znear <= vp.zfar) == UseReversedZ;
|
|
Mat4x4<float> proj = g_gxState.proj;
|
|
for (size_t i = 0; i < 4; ++i) {
|
|
proj.m2.m[i] = flip ? -proj.m2.m[i] : (proj.m2.m[i] + proj.m3.m[i]);
|
|
}
|
|
return proj;
|
|
}
|
|
} // namespace
|
|
|
|
UniformRanges build_uniform(const ShaderInfo& info, u32 vtxStart, const BindGroupRanges& ranges,
|
|
const FrameInterpolationDrawIdentity& drawIdentity, bool perspective,
|
|
uint16_t usedPnMtxMask) noexcept {
|
|
ZoneScoped;
|
|
|
|
auto [buf, range] = gfx::map_uniform(info.uniformSize);
|
|
|
|
const auto& layout = info.matrixLayout;
|
|
// `postex_mtx[current_pnmtx]` indexes raw matrix memory, so a current-matrix index of 10 or more selects a texture matrix; the normal array only covers the position rows and clamps.
|
|
const u32 currentPostexSlot = std::min<u32>(g_gxState.currentPnMtx, MaxPostexMtx - 1);
|
|
const u32 currentNrmSlot = std::min<u32>(g_gxState.currentPnMtx, MaxPnMtx - 1);
|
|
|
|
// The mapped uniform range lives in write-combine memory, which wants long sequential stores.
|
|
alignas(16) std::array<uint8_t, kStagedUniformBytes> staged;
|
|
size_t stagedSize = 0;
|
|
const auto stage = [&](const void* data, size_t size) noexcept {
|
|
std::memcpy(staged.data() + stagedSize, data, size);
|
|
stagedSize += size;
|
|
};
|
|
const auto stage_u32 = [&](u32 value) noexcept { stage(&value, sizeof(value)); };
|
|
const auto stage_f32 = [&](f32 value) noexcept { stage(&value, sizeof(value)); };
|
|
|
|
stage_u32(vtxStart);
|
|
// With a compacted position region the live matrix is uploaded to slot 0, so every `postex_mtx[in_pnmtxidx]` / `nrm_mtx[in_pnmtxidx]` read has to resolve to 0 as well.
|
|
stage_u32(layout.absolutePosRegion ? g_gxState.currentPnMtx : 0u);
|
|
stage_f32(g_gxState.renderViewport.width);
|
|
stage_f32(g_gxState.renderViewport.height);
|
|
stage_f32(g_gxState.logicalViewport.width);
|
|
stage_f32(g_gxState.logicalViewport.height);
|
|
std::memset(staged.data() + stagedSize, 0, 8); // pad
|
|
stagedSize += 8;
|
|
for (const auto& vaRange : ranges.vaRanges) {
|
|
stage_u32(vaRange.offset);
|
|
}
|
|
if (info.lineMode != 0) {
|
|
if (info.lineMode == 3) { // GX_POINTS
|
|
stage_f32(static_cast<f32>(g_gxState.pointSize) / 6.f);
|
|
stage_f32(1.0f);
|
|
stage_f32(tex_offset(g_gxState.pointTexOffset));
|
|
stage_u32(point_texcoord_mask());
|
|
} else { // GX_LINES / GX_LINESTRIP
|
|
stage_f32(static_cast<f32>(g_gxState.lineWidth) / 6.f);
|
|
stage_f32(g_gxState.lineHalfAspect ? 0.5f : 1.f);
|
|
stage_f32(tex_offset(g_gxState.lineTexOffset));
|
|
stage_u32(line_texcoord_mask());
|
|
}
|
|
}
|
|
const size_t projectionOffset = stagedSize;
|
|
const Mat4x4<float> effectiveProj = effective_projection();
|
|
stage(&effectiveProj, sizeof(effectiveProj));
|
|
|
|
const size_t positionOffset = stagedSize;
|
|
for (u32 i = 0; i < layout.postexCount; ++i) {
|
|
const u32 slot =
|
|
layout.postexSlots[i] == UniformMatrixLayout::kCurrentPnMtx ? currentPostexSlot
|
|
: layout.postexSlots[i];
|
|
stage(slot < MaxPnMtx ? &g_gxState.pnMtx[slot].pos : &g_gxState.texMtxs[slot - MaxPnMtx],
|
|
sizeof(Mat3x4<float>));
|
|
}
|
|
|
|
const size_t normalOffset = stagedSize;
|
|
for (u32 i = 0; i < layout.nrmCount; ++i) {
|
|
const u32 slot = layout.nrmSlots[i] == UniformMatrixLayout::kCurrentPnMtx ? currentNrmSlot
|
|
: layout.nrmSlots[i];
|
|
stage(&g_gxState.pnMtx[slot].nrm, sizeof(Mat3x4<float>));
|
|
}
|
|
buf.append(staged.data(), stagedSize);
|
|
|
|
const auto loadsTevRegs = info.loadsTevRegRgb | info.loadsTevRegAlpha;
|
|
for (int i = 0; i < loadsTevRegs.size(); ++i) {
|
|
if (loadsTevRegs.test(i)) {
|
|
buf.append(g_gxState.colorRegs[i]);
|
|
}
|
|
}
|
|
if (info.lightingEnabled) {
|
|
// Sanitizing and normalizing light directions is substantially more expensive than copying the uniform data, while lights normally remain unchanged across many draws.
|
|
static_assert(sizeof(g_gxState.lights) == 80 * GX::MaxLights);
|
|
if (g_gxState.preparedLightsDirty) {
|
|
for (size_t i = 0; i < g_gxState.preparedLights.size(); ++i) {
|
|
g_gxState.preparedLights[i] = prepare_shader_light(g_gxState.lights[i]);
|
|
}
|
|
g_gxState.preparedLightsDirty = false;
|
|
}
|
|
buf.append(g_gxState.preparedLights);
|
|
// Light state for all channels
|
|
for (int i = 0; i < 4; ++i) {
|
|
buf.append<u32>(g_gxState.colorChannelState[i].lightMask.to_ulong());
|
|
}
|
|
}
|
|
for (int i = 0; i < info.sampledColorChannels.size(); ++i) {
|
|
if (!info.sampledColorChannels.test(i)) {
|
|
continue;
|
|
}
|
|
const auto& ccc = g_gxState.colorChannelConfig[i];
|
|
const auto& ccs = g_gxState.colorChannelState[i];
|
|
if (ccc.lightingEnabled && ccc.ambSrc == GX_SRC_REG) {
|
|
buf.append(ccs.ambColor);
|
|
}
|
|
if (ccc.matSrc == GX_SRC_REG) {
|
|
buf.append(ccs.matColor);
|
|
}
|
|
const auto& ccca = g_gxState.colorChannelConfig[i + GX_ALPHA0];
|
|
const auto& ccsa = g_gxState.colorChannelState[i + GX_ALPHA0];
|
|
if (ccca.lightingEnabled && ccca.ambSrc == GX_SRC_REG) {
|
|
buf.append(ccsa.ambColor);
|
|
}
|
|
if (ccca.matSrc == GX_SRC_REG) {
|
|
buf.append(ccsa.matColor);
|
|
}
|
|
}
|
|
for (int i = 0; i < info.sampledKColors.size(); ++i) {
|
|
if (info.sampledKColors.test(i)) {
|
|
buf.append(g_gxState.kcolors[i]);
|
|
}
|
|
}
|
|
if (info.usesPTTexMtx.any()) {
|
|
for (int i = 0; i < info.usesPTTexMtx.size(); ++i) {
|
|
buf.append(g_gxState.ptTexMtxs[i]);
|
|
}
|
|
}
|
|
if (info.usesFog) {
|
|
buf.append(fog_uniform());
|
|
}
|
|
if (info.usedIndTexMtxs.any()) {
|
|
for (int i = 0; i < MaxIndTexMtxs; ++i) {
|
|
const auto& mtx = g_gxState.indTexMtxs[i];
|
|
buf.append(Vec4<s32>{indirect_matrix_mantissa(mtx.mtx.m0.x), indirect_matrix_mantissa(mtx.mtx.m0.y),
|
|
indirect_matrix_mantissa(mtx.mtx.m1.x), indirect_matrix_mantissa(mtx.mtx.m1.y)});
|
|
buf.append(Vec4<s32>{indirect_matrix_mantissa(mtx.mtx.m2.x), indirect_matrix_mantissa(mtx.mtx.m2.y),
|
|
indirect_matrix_shift(mtx.scaleExp), 0});
|
|
}
|
|
}
|
|
for (int i = 0; i < info.sampledTexCoords.size(); ++i) {
|
|
if (info.sampledTexCoords.test(i)) {
|
|
buf.append(texcoord_scale(g_gxState.texCoordScales[i]));
|
|
}
|
|
}
|
|
for (int i = 0; i < info.sampledTextures.size(); ++i) {
|
|
if (!info.sampledTextures.test(i)) {
|
|
continue;
|
|
}
|
|
const auto& tex = get_texture(static_cast<GXTexMapID>(i));
|
|
// CHECK(tex, "unbound texture {}", i);
|
|
buf.append(texture_size_bias(tex));
|
|
}
|
|
|
|
if (!perspective || frame_interpolation_fps() == 0) {
|
|
g_gxState.stateDirty = false;
|
|
return {
|
|
.current = range,
|
|
.interpolated = {},
|
|
};
|
|
}
|
|
|
|
const auto interpolatedRanges = record_interpolation_draw(
|
|
drawIdentity, effectiveProj, usedPnMtxMask,
|
|
InterpolatedUniformLayout{
|
|
.sourceUniformData = buf.data(),
|
|
.uniformSize = range.size,
|
|
.projectionOffset = projectionOffset,
|
|
.positionOffset = positionOffset,
|
|
.normalOffset = normalOffset,
|
|
// A compacted position region holds the current matrix at slot 0.
|
|
.currentMatrix = layout.absolutePosRegion
|
|
? std::min<size_t>(g_gxState.currentPnMtx, MaxPnMtx - 1)
|
|
: 0,
|
|
.indexedMatrices = info.indexAttr.test(GX_VA_PNMTXIDX),
|
|
});
|
|
g_gxState.stateDirty = false;
|
|
return {
|
|
.current = range,
|
|
.interpolated = interpolatedRanges,
|
|
};
|
|
}
|
|
} // namespace aurora::gx
|