#include "shader_info.hpp" namespace aurora::gfx::gx { namespace { Module Log("aurora::gfx::gx"); void color_arg_reg_info(GXTevColorArg arg, const TevStage& stage, ShaderInfo& info) { switch (arg) { case GX_CC_CPREV: case GX_CC_APREV: if (!info.writesTevReg.test(GX_TEVPREV)) { info.loadsTevReg.set(GX_TEVPREV); } break; case GX_CC_C0: case GX_CC_A0: if (!info.writesTevReg.test(GX_TEVREG0)) { info.loadsTevReg.set(GX_TEVREG0); } break; case GX_CC_C1: case GX_CC_A1: if (!info.writesTevReg.test(GX_TEVREG1)) { info.loadsTevReg.set(GX_TEVREG1); } break; case GX_CC_C2: case GX_CC_A2: if (!info.writesTevReg.test(GX_TEVREG2)) { info.loadsTevReg.set(GX_TEVREG2); } break; case GX_CC_TEXC: case GX_CC_TEXA: CHECK(stage.texCoordId != GX_TEXCOORD_NULL, "tex coord not bound"); CHECK(stage.texMapId != GX_TEXMAP_NULL, "tex map not bound"); info.sampledTexCoords.set(stage.texCoordId); info.sampledTextures.set(stage.texMapId); break; case GX_CC_RASC: case GX_CC_RASA: if (stage.channelId >= GX_COLOR0A0 && stage.channelId <= GX_COLOR1A1) { info.sampledColorChannels.set(stage.channelId - GX_COLOR0A0); } 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, ShaderInfo& info) { switch (arg) { case GX_CA_APREV: if (!info.writesTevReg.test(GX_TEVPREV)) { info.loadsTevReg.set(GX_TEVPREV); } break; case GX_CA_A0: if (!info.writesTevReg.test(GX_TEVREG0)) { info.loadsTevReg.set(GX_TEVREG0); } break; case GX_CA_A1: if (!info.writesTevReg.test(GX_TEVREG1)) { info.loadsTevReg.set(GX_TEVREG1); } break; case GX_CA_A2: if (!info.writesTevReg.test(GX_TEVREG2)) { info.loadsTevReg.set(GX_TEVREG2); } break; case GX_CA_TEXA: CHECK(stage.texCoordId != GX_TEXCOORD_NULL, "tex coord not bound"); CHECK(stage.texMapId != GX_TEXMAP_NULL, "tex map not bound"); info.sampledTexCoords.set(stage.texCoordId); info.sampledTextures.set(stage.texMapId); break; case GX_CA_RASA: if (stage.channelId >= GX_COLOR0A0 && stage.channelId <= GX_COLOR1A1) { info.sampledColorChannels.set(stage.channelId - GX_COLOR0A0); } 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 { ShaderInfo info{ .uniformSize = sizeof(PnMtx) + sizeof(Mat4x4), // pos_mtx, nrm_mtx, proj }; for (int i = 0; i < config.tevStageCount; ++i) { const auto& stage = config.tevStages[i]; // Color pass color_arg_reg_info(stage.colorPass.a, stage, info); color_arg_reg_info(stage.colorPass.b, stage, info); color_arg_reg_info(stage.colorPass.c, stage, info); color_arg_reg_info(stage.colorPass.d, stage, info); info.writesTevReg.set(stage.colorOp.outReg); // Alpha pass alpha_arg_reg_info(stage.alphaPass.a, stage, info); alpha_arg_reg_info(stage.alphaPass.b, stage, info); alpha_arg_reg_info(stage.alphaPass.c, stage, info); alpha_arg_reg_info(stage.alphaPass.d, stage, info); if (!info.writesTevReg.test(stage.alphaOp.outReg)) { // If we're writing alpha to a register that's not been // written to in the shader, load from uniform buffer info.loadsTevReg.set(stage.alphaOp.outReg); info.writesTevReg.set(stage.alphaOp.outReg); } } info.uniformSize += info.loadsTevReg.count() * sizeof(Vec4); 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); } if (cc.matSrc == GX_SRC_REG) { info.uniformSize += sizeof(Vec4); } const auto& cca = config.colorChannels[i + GX_ALPHA0]; if (cca.lightingEnabled && cca.ambSrc == GX_SRC_REG) { info.uniformSize += sizeof(Vec4); } if (cca.matSrc == GX_SRC_REG) { info.uniformSize += sizeof(Vec4); } } } info.uniformSize += info.sampledKColors.count() * sizeof(Vec4); for (int i = 0; i < info.sampledTexCoords.size(); ++i) { if (!info.sampledTexCoords.test(i)) { continue; } const auto& tcg = config.tcgs[i]; if (tcg.mtx != GX_IDENTITY) { u32 texMtxIdx = (tcg.mtx - GX_TEXMTX0) / 3; info.usesTexMtx.set(texMtxIdx); info.texMtxTypes[texMtxIdx] = tcg.type; } if (tcg.postMtx != GX_PTIDENTITY) { u32 postMtxIdx = (tcg.postMtx - GX_PTTEXMTX0) / 3; info.usesPTTexMtx.set(postMtxIdx); } } for (int i = 0; i < info.usesTexMtx.size(); ++i) { if (info.usesTexMtx.test(i)) { switch (info.texMtxTypes[i]) { case GX_TG_MTX2x4: info.uniformSize += sizeof(Mat2x4); break; case GX_TG_MTX3x4: info.uniformSize += sizeof(Mat3x4); break; default: break; } } } info.uniformSize += info.usesPTTexMtx.count() * sizeof(Mat3x4); if (config.fogType != GX_FOG_NONE) { info.usesFog = true; info.uniformSize += sizeof(Fog); } info.uniformSize += info.sampledTextures.count() * sizeof(u32); info.uniformSize = align_uniform(info.uniformSize); return info; } Range build_uniform(const ShaderInfo& info) noexcept { auto [buf, range] = map_uniform(info.uniformSize); { buf.append(g_gxState.pnMtx[g_gxState.currentPnMtx]); buf.append(g_gxState.proj); } for (int i = 0; i < info.loadsTevReg.size(); ++i) { if (info.loadsTevReg.test(i)) { buf.append(g_gxState.colorRegs[i]); } } if (info.lightingEnabled) { // Lights static_assert(sizeof(g_gxState.lights) == 80 * GX::MaxLights); buf.append(g_gxState.lights); // Light state for all channels for (int i = 0; i < 4; ++i) { buf.append(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]); } } for (int i = 0; i < info.usesTexMtx.size(); ++i) { if (!info.usesTexMtx.test(i)) { continue; } switch (info.texMtxTypes[i]) { DEFAULT_FATAL("unhandled tex mtx type {}", underlying(info.texMtxTypes[i])); case GX_TG_MTX2x4: if (std::holds_alternative>(g_gxState.texMtxs[i])) { buf.append(std::get>(g_gxState.texMtxs[i])); } else UNLIKELY FATAL("expected 2x4 mtx in idx {}", i); break; case GX_TG_MTX3x4: if (std::holds_alternative>(g_gxState.texMtxs[i])) { buf.append(std::get>(g_gxState.texMtxs[i])); } else UNLIKELY FATAL("expected 3x4 mtx in idx {}", i); break; } } for (int i = 0; i < info.usesPTTexMtx.size(); ++i) { if (info.usesPTTexMtx.test(i)) { buf.append(g_gxState.ptTexMtxs[i]); } } if (info.usesFog) { const auto& state = g_gxState.fog; Fog fog{.color = state.color}; if (state.nearZ != state.farZ && state.startZ != state.endZ) { const float depthRange = state.farZ - state.nearZ; const float fogRange = state.endZ - state.startZ; fog.a = (state.farZ * state.nearZ) / (depthRange * fogRange); fog.b = state.farZ / depthRange; fog.c = state.startZ / fogRange; } buf.append(fog); } for (int i = 0; i < info.sampledTextures.size(); ++i) { if (!info.sampledTextures.test(i)) { continue; } const auto& tex = get_texture(static_cast(i)); CHECK(tex, "unbound texture {}", i); buf.append(tex.texObj.lodBias); } g_gxState.stateDirty = false; return range; } } // namespace aurora::gfx::gx