aurora: Start implementing TCGs

This commit is contained in:
Luke Street 2022-03-14 18:00:03 -04:00
parent cdcfc7eccc
commit af856de6a8
12 changed files with 378 additions and 79 deletions

View File

@ -162,24 +162,24 @@ void CCubeMaterial::SetCurrent(const CModelFlags& flags, const CCubeSurface& sur
u32 fullTcgCount = SBig(*tcgs); u32 fullTcgCount = SBig(*tcgs);
tcgCount = std::min(fullTcgCount, 2u); tcgCount = std::min(fullTcgCount, 2u);
for (u32 i = 0; i < tcgCount; ++i) { for (u32 i = 0; i < tcgCount; ++i) {
// TODO set TCG CGX::SetTexCoordGen(GX::TexCoordID(i), SBig(tcgs[i + 1]));
} }
tcgs += fullTcgCount + 1; tcgs += fullTcgCount + 1;
} else { } else {
tcgCount = SBig(*tcgs); tcgCount = SBig(*tcgs);
for (u32 i = 0; i < tcgCount; ++i) { for (u32 i = 0; i < tcgCount; ++i) {
// TODO set TCG CGX::SetTexCoordGen(GX::TexCoordID(i), SBig(tcgs[i + 1]));
} }
tcgs += tcgCount + 1; tcgs += tcgCount + 1;
} }
const u32* uvAnim = tcgs; const u32* uvAnim = tcgs;
u32 animCount = uvAnim[1]; u32 animCount = SBig(uvAnim[1]);
uvAnim += 2; uvAnim += 2;
u32 texMtx = 30; u32 texMtx = GX::TEXMTX0;
u32 pttTexMtx = 64; u32 pttTexMtx = GX::PTTEXMTX0;
for (u32 i = 0; i < animCount; ++i) { for (u32 i = 0; i < animCount; ++i) {
u32 size = HandleAnimatedUV(uvAnim, texMtx, pttTexMtx); u32 size = HandleAnimatedUV(uvAnim, static_cast<GX::TexMtx>(texMtx), static_cast<GX::PTTexMtx>(pttTexMtx));
if (size == 0) if (size == 0)
break; break;
uvAnim += size; uvAnim += size;
@ -356,31 +356,73 @@ void CCubeMaterial::HandleTev(u32 tevCur, const u32* materialDataCur, const u32*
CGX::SetTevKAlphaSel(stage, static_cast<GX::TevKAlphaSel>(matFlags >> 0x10 & 0xFF)); CGX::SetTevKAlphaSel(stage, static_cast<GX::TevKAlphaSel>(matFlags >> 0x10 & 0xFF));
} }
u32 CCubeMaterial::HandleAnimatedUV(const u32* uvAnim, u32 texMtx, u32 pttTexMtx) { constexpr zeus::CTransform MvPostXf{
{zeus::CVector3f{0.5f, 0.f, 0.f}, {0.f, 0.f, 0.f}, {0.f, 0.5f, 0.f}},
{0.5f, 0.5f, 1.f},
};
u32 CCubeMaterial::HandleAnimatedUV(const u32* uvAnim, GX::TexMtx texMtx, GX::PTTexMtx pttTexMtx) {
u32 type = SBig(*uvAnim); u32 type = SBig(*uvAnim);
const float* params = reinterpret_cast<const float*>(uvAnim + 1);
switch (type) { switch (type) {
case 0: case 0: {
// TODO auto xf = CGraphics::g_GXModelViewInvXpose;
GXLoadTexMtxImm(&xf, texMtx, GX::MTX3x4);
GXLoadTexMtxImm(&MvPostXf, pttTexMtx, GX::MTX3x4);
return 1; return 1;
case 1: }
// TODO case 1: {
auto xf = CGraphics::g_GXModelViewInvXpose;
xf.origin = CGraphics::g_ViewMatrix.inverse() * CGraphics::g_GXModelMatrix.origin;
GXLoadTexMtxImm(&xf, texMtx, GX::MTX3x4);
GXLoadTexMtxImm(&MvPostXf, pttTexMtx, GX::MTX3x4);
return 1; return 1;
case 2: }
// TODO case 2: {
const float f1 = SBig(params[0]);
const float f2 = SBig(params[1]);
const float f3 = SBig(params[2]);
const float f4 = SBig(params[3]);
const float seconds = CGraphics::GetSecondsMod900();
const auto xf = zeus::CTransform::Translate(seconds * f3 + f1, seconds * f4 + f2, 0.f);
GXLoadTexMtxImm(&xf, texMtx, GX::MTX3x4);
return 5; return 5;
case 3: }
// TODO case 3: {
const float angle = CGraphics::GetSecondsMod900() * SBig(params[1]) + SBig(params[0]);
const float acos = std::cos(angle);
const float asin = std::sin(angle);
zeus::CTransform xf;
xf.basis[0][0] = acos;
xf.basis[0][1] = asin;
xf.basis[1][0] = -asin;
xf.basis[1][1] = acos;
xf.origin[0] = (1.f - (acos - asin)) * 0.5f;
xf.origin[1] = (1.f - (asin + acos)) * 0.5f;
GXLoadTexMtxImm(&xf, texMtx, GX::MTX3x4);
return 3; return 3;
}
case 4: case 4:
case 5: case 5: {
// TODO // TODO
zeus::CTransform xf;
GXLoadTexMtxImm(&xf, texMtx, GX::MTX3x4);
return 5; return 5;
case 6: }
case 6: {
// TODO // TODO
zeus::CTransform xf;
GXLoadTexMtxImm(&xf, texMtx, GX::MTX3x4);
GXLoadTexMtxImm(&xf, pttTexMtx, GX::MTX3x4);
return 1; return 1;
case 7: }
case 7: {
// TODO // TODO
return 2; zeus::CTransform xf;
GXLoadTexMtxImm(&xf, texMtx, GX::MTX3x4);
GXLoadTexMtxImm(&xf, pttTexMtx, GX::MTX3x4);
return 3;
}
default: default:
return 0; return 0;
} }

View File

@ -93,7 +93,7 @@ private:
static void HandleDepth(CModelFlagsFlags modelFlags, CCubeMaterialFlags matFlags); static void HandleDepth(CModelFlagsFlags modelFlags, CCubeMaterialFlags matFlags);
static u32 HandleColorChannels(u32 chanCount, u32 firstChan); static u32 HandleColorChannels(u32 chanCount, u32 firstChan);
static void HandleTev(u32 tevCur, const u32* materialDataCur, const u32* texMapTexCoordFlags, bool shadowMapsEnabled); static void HandleTev(u32 tevCur, const u32* materialDataCur, const u32* texMapTexCoordFlags, bool shadowMapsEnabled);
static u32 HandleAnimatedUV(const u32* uvAnim, u32 texMtx, u32 pttTexMtx); static u32 HandleAnimatedUV(const u32* uvAnim, GX::TexMtx texMtx, GX::PTTexMtx pttTexMtx);
static void HandleTransparency(u32& finalTevCount, u32& finalKColorCount, const CModelFlags& modelFlags, static void HandleTransparency(u32& finalTevCount, u32& finalKColorCount, const CModelFlags& modelFlags,
u32 blendFactors, u32& finalCCFlags, u32& finalACFlags); u32 blendFactors, u32& finalCCFlags, u32& finalACFlags);
static u32 HandleReflection(bool usesTevReg2, u32 indTexSlot, u32 r5, u32 finalTevCount, u32 texCount, u32 tcgCount, static u32 HandleReflection(bool usesTevReg2, u32 indTexSlot, u32 r5, u32 finalTevCount, u32 texCount, u32 tcgCount,

View File

@ -176,7 +176,7 @@ void CCubeModel::DrawFlat(TConstVectorRef positions, TConstVectorRef normals, ES
void CCubeModel::DrawNormal(TConstVectorRef positions, TConstVectorRef normals, ESurfaceSelection surfaces) { void CCubeModel::DrawNormal(TConstVectorRef positions, TConstVectorRef normals, ESurfaceSelection surfaces) {
CGX::SetNumIndStages(0); CGX::SetNumIndStages(0);
CGX::SetNumTevStages(1); CGX::SetNumTevStages(1);
CGX::SetNumTexGens(1); CGX::SetNumTexGens(1); // TODO should this be 0?
CGX::SetZMode(true, GX::LEQUAL, true); CGX::SetZMode(true, GX::LEQUAL, true);
CGX::SetTevOrder(GX::TEVSTAGE0, GX::TEXCOORD_NULL, GX::TEXMAP_NULL, GX::COLOR_NULL); CGX::SetTevOrder(GX::TEVSTAGE0, GX::TEXCOORD_NULL, GX::TEXMAP_NULL, GX::COLOR_NULL);
CGX::SetTevColorIn(GX::TEVSTAGE0, GX::CC_ZERO, GX::CC_ZERO, GX::CC_ZERO, GX::CC_ZERO); CGX::SetTevColorIn(GX::TEVSTAGE0, GX::CC_ZERO, GX::CC_ZERO, GX::CC_ZERO, GX::CC_ZERO);

View File

@ -44,7 +44,7 @@ struct SGXState {
u16 x56_blendMode = 0; u16 x56_blendMode = 0;
std::array<GXColor, GX::MAX_KCOLOR> x58_kColors; std::array<GXColor, GX::MAX_KCOLOR> x58_kColors;
std::array<STevState, GX::MAX_TEVSTAGE> x68_tevStates; std::array<STevState, GX::MAX_TEVSTAGE> x68_tevStates;
std::array<STexState, GX::MAX_TEXMAP> x228_texStates; std::array<STexState, GX::MAX_TEXCOORD> x228_texStates;
u32 x248_alphaCompare = 0; u32 x248_alphaCompare = 0;
float x24c_fogStartZ = 0.f; float x24c_fogStartZ = 0.f;
float x250_fogEndZ = 0.f; float x250_fogEndZ = 0.f;
@ -367,9 +367,25 @@ static inline void SetTevOrder(GX::TevStageID stageId, GX::TexCoordID texCoord,
} }
} }
static inline void SetTexCoordGen(GX::TexCoordID dstCoord, GX::TexGenType fn, GX::TexGenSrc src, u32 mtx, static inline void SetTexCoordGen(GX::TexCoordID dstCoord, GX::TexGenType fn, GX::TexGenSrc src, GX::TexMtx mtx,
GXBool normalize, u32 postMtx) noexcept { GXBool normalize, GX::PTTexMtx postMtx) noexcept {
// TODO u32 flags = ((postMtx - GX::PTTEXMTX0) & 63) << 15 | (u8(normalize) & 1) << 14 | ((mtx - GX::TEXMTX0) & 31) << 9 |
(src & 31) << 4 | (fn & 15);
auto& state = sGXState.x228_texStates[dstCoord].x0_coordGen;
if (flags != state) {
state = flags;
GXSetTexCoordGen2(dstCoord, fn, src, mtx, normalize, postMtx);
}
}
static inline void SetTexCoordGen(GX::TexCoordID dstCoord, u32 flags) noexcept {
auto& state = sGXState.x228_texStates[dstCoord].x0_coordGen;
if (flags != state) {
state = flags;
GXSetTexCoordGen2(dstCoord, GX::TexGenType(flags & 15), GX::TexGenSrc(flags >> 4 & 31),
GX::TexMtx((flags >> 9 & 31) + GX::TEXMTX0), GXBool(flags >> 14 & 1),
GX::PTTexMtx((flags >> 15 & 63) + GX::PTTEXMTX0));
}
} }
void SetVtxDescv(GX::VtxDescList* descList) noexcept; void SetVtxDescv(GX::VtxDescList* descList) noexcept;

View File

@ -185,8 +185,7 @@ void CGraphics::Render2D(CTexture& tex, u32 x, u32 y, u32 w, u32 h, const zeus::
const auto oldLights = g_LightActive; const auto oldLights = g_LightActive;
SetOrtho(-g_Viewport.x10_halfWidth, g_Viewport.x10_halfWidth, g_Viewport.x14_halfHeight, -g_Viewport.x14_halfHeight, SetOrtho(-g_Viewport.x10_halfWidth, g_Viewport.x10_halfWidth, g_Viewport.x14_halfHeight, -g_Viewport.x14_halfHeight,
-1.f, -10.f); -1.f, -10.f);
// disable Y/Z swap TODO do we need to do this elsewhere? GXLoadPosMtxImm({}, GX::PNMTX0);
aurora::gfx::update_model_view(zeus::CMatrix4f{}, zeus::CMatrix4f{});
DisableAllLights(); DisableAllLights();
SetCullMode(ERglCullMode::None); SetCullMode(ERglCullMode::None);
tex.Load(GX::TEXMAP0, EClampMode::Repeat); tex.Load(GX::TEXMAP0, EClampMode::Repeat);
@ -252,12 +251,13 @@ void CGraphics::SetViewMatrix() {
else else
g_GXModelView = g_CameraMatrix * g_GXModelMatrix; g_GXModelView = g_CameraMatrix * g_GXModelMatrix;
/* Load position matrix */ /* Load position matrix */
GXLoadPosMtxImm(g_GXModelView, GX::PNMTX0);
/* Inverse-transpose */ /* Inverse-transpose */
g_GXModelViewInvXpose = g_GXModelView.inverse(); g_GXModelViewInvXpose = g_GXModelView.inverse();
g_GXModelViewInvXpose.origin.zeroOut(); g_GXModelViewInvXpose.origin.zeroOut();
g_GXModelViewInvXpose.basis.transpose(); g_GXModelViewInvXpose.basis.transpose();
/* Load normal matrix */ /* Load normal matrix */
aurora::gfx::update_model_view(g_GXModelView.toMatrix4f(), g_GXModelViewInvXpose.toMatrix4f()); GXLoadNrmMtxImm(g_GXModelViewInvXpose, GX::PNMTX0);
} }
void CGraphics::SetModelMatrix(const zeus::CTransform& xf) { void CGraphics::SetModelMatrix(const zeus::CTransform& xf) {
@ -365,12 +365,14 @@ void CGraphics::SetOrtho(float left, float right, float top, float bottom, float
} }
void CGraphics::FlushProjection() { void CGraphics::FlushProjection() {
const auto mtx = GetPerspectiveProjectionMatrix();
if (g_Proj.x0_persp) { if (g_Proj.x0_persp) {
// Convert and load persp // Convert and load persp
GXSetProjection(mtx, GX::PERSPECTIVE);
} else { } else {
// Convert and load ortho // Convert and load ortho
GXSetProjection(mtx, GX::ORTHOGRAPHIC);
} }
aurora::gfx::update_projection(GetPerspectiveProjectionMatrix());
} }
zeus::CVector2i CGraphics::ProjectPoint(const zeus::CVector3f& point) { zeus::CVector2i CGraphics::ProjectPoint(const zeus::CVector3f& point) {
@ -460,17 +462,19 @@ void CGraphics::SetViewport(int leftOff, int bottomOff, int width, int height) {
CachedVP.position[1] = bottomOff; CachedVP.position[1] = bottomOff;
CachedVP.size[0] = width; CachedVP.size[0] = width;
CachedVP.size[1] = height; CachedVP.size[1] = height;
aurora::gfx::set_viewport(CachedVP, g_CachedDepthRange[0], g_CachedDepthRange[1]); GXSetViewport(CachedVP.position[0], CachedVP.position[1], CachedVP.size[0], CachedVP.size[1], g_CachedDepthRange[0],
g_CachedDepthRange[1]);
} }
void CGraphics::SetScissor(int leftOff, int bottomOff, int width, int height) { void CGraphics::SetScissor(int leftOff, int bottomOff, int width, int height) {
aurora::gfx::set_scissor(leftOff, bottomOff, width, height); GXSetScissor(leftOff, bottomOff, width, height);
} }
void CGraphics::SetDepthRange(float znear, float zfar) { void CGraphics::SetDepthRange(float znear, float zfar) {
g_CachedDepthRange[0] = znear; g_CachedDepthRange[0] = znear;
g_CachedDepthRange[1] = zfar; g_CachedDepthRange[1] = zfar;
aurora::gfx::set_viewport(CachedVP, g_CachedDepthRange[0], g_CachedDepthRange[1]); GXSetViewport(CachedVP.position[0], CachedVP.position[1], CachedVP.size[0], CachedVP.size[1], g_CachedDepthRange[0],
g_CachedDepthRange[1]);
} }
CTimeProvider* CGraphics::g_ExternalTimeProvider = nullptr; CTimeProvider* CGraphics::g_ExternalTimeProvider = nullptr;
@ -600,7 +604,8 @@ void CGraphics::SetTevStates(EStreamFlags flags) noexcept {
} }
CGX::SetNumChans(1); CGX::SetNumChans(1);
CGX::SetNumIndStages(0); CGX::SetNumIndStages(0);
// TODO load TCGs CGX::SetTexCoordGen(GX::TEXCOORD0, GX::TG_MTX2x4, GX::TG_TEX0, GX::IDENTITY, false, GX::PTIDENTITY);
CGX::SetTexCoordGen(GX::TEXCOORD1, GX::TG_MTX2x4, GX::TG_TEX1, GX::IDENTITY, false, GX::PTIDENTITY);
const bool hasLights = g_LightActive.any(); const bool hasLights = g_LightActive.any();
CGX::SetChanCtrl(CGX::EChannelId::Channel0, hasLights, GX::SRC_REG, CGX::SetChanCtrl(CGX::EChannelId::Channel0, hasLights, GX::SRC_REG,
flags & EStreamFlagBits::fHasColor ? GX::SRC_VTX : GX::SRC_REG, g_LightActive, flags & EStreamFlagBits::fHasColor ? GX::SRC_VTX : GX::SRC_REG, g_LightActive,

View File

@ -6,6 +6,8 @@
#include <bitset> #include <bitset>
#include <zeus/CColor.hpp> #include <zeus/CColor.hpp>
#include <zeus/CMatrix4f.hpp>
#include <zeus/CTransform.hpp>
namespace GX { namespace GX {
enum Attr { enum Attr {
@ -246,7 +248,8 @@ enum TexGenSrc {
TG_TEXCOORD5, TG_TEXCOORD5,
TG_TEXCOORD6, TG_TEXCOORD6,
TG_COLOR0, TG_COLOR0,
TG_COLOR1 TG_COLOR1,
MAX_TEXGENSRC = 0xFF,
}; };
enum TexMtx { enum TexMtx {
@ -644,6 +647,24 @@ enum FogType {
FOG_ORTHO_REVEXP2 = 0x0F, FOG_ORTHO_REVEXP2 = 0x0F,
}; };
enum PosNrmMtx {
PNMTX0,
PNMTX1,
PNMTX2,
PNMTX3,
PNMTX4,
PNMTX5,
PNMTX6,
PNMTX7,
PNMTX8,
PNMTX9,
};
enum ProjectionType {
PERSPECTIVE,
ORTHOGRAPHIC,
};
} // namespace GX } // namespace GX
using GXColor = zeus::CColor; using GXColor = zeus::CColor;
@ -684,4 +705,13 @@ void GXClearVtxDesc() noexcept;
void GXSetArray(GX::Attr attr, const void* data, u8 stride) noexcept; void GXSetArray(GX::Attr attr, const void* data, u8 stride) noexcept;
void GXSetTevDirect(GX::TevStageID stageId) noexcept; void GXSetTevDirect(GX::TevStageID stageId) noexcept;
void GXSetFog(GX::FogType type, float startZ, float endZ, float nearZ, float farZ, const GXColor& color) noexcept; void GXSetFog(GX::FogType type, float startZ, float endZ, float nearZ, float farZ, const GXColor& color) noexcept;
void GXSetFogColor(const GXColor& color) noexcept;
void GXCallDisplayList(const void* data, u32 nbytes) noexcept; void GXCallDisplayList(const void* data, u32 nbytes) noexcept;
void GXSetTexCoordGen2(GX::TexCoordID dst, GX::TexGenType type, GX::TexGenSrc src, GX::TexMtx mtx, GXBool normalize,
GX::PTTexMtx postMtx) noexcept;
void GXLoadTexMtxImm(const void* data, u32 id /* GX::TexMtx or GX::PTTexMtx */, GX::TexMtxType type) noexcept;
void GXLoadPosMtxImm(const zeus::CTransform& xf, GX::PosNrmMtx id) noexcept;
void GXLoadNrmMtxImm(const zeus::CTransform& xf, GX::PosNrmMtx id) noexcept;
void GXSetProjection(const zeus::CMatrix4f& mtx, GX::ProjectionType type) noexcept;
void GXSetViewport(float left, float top, float width, float height, float nearZ, float farZ) noexcept;
void GXSetScissor(u32 left, u32 top, u32 width, u32 height) noexcept;

View File

@ -15,6 +15,7 @@ struct Vec2 {
T x{}; T x{};
T y{}; T y{};
constexpr Vec2() = default;
constexpr Vec2(T x, T y) : x(x), y(y) {} constexpr Vec2(T x, T y) : x(x), y(y) {}
constexpr Vec2(const zeus::CVector2f& vec) : x(vec.x()), y(vec.y()) {} constexpr Vec2(const zeus::CVector2f& vec) : x(vec.x()), y(vec.y()) {}
}; };
@ -24,6 +25,7 @@ struct Vec3 {
T y{}; T y{};
T z{}; T z{};
constexpr Vec3() = default;
constexpr Vec3(T x, T y, T z) : x(x), y(y), z(z) {} constexpr Vec3(T x, T y, T z) : x(x), y(y), z(z) {}
constexpr Vec3(const zeus::CVector3f& vec) : x(vec.x()), y(vec.y()), z(vec.z()) {} constexpr Vec3(const zeus::CVector3f& vec) : x(vec.x()), y(vec.y()), z(vec.z()) {}
}; };
@ -34,20 +36,34 @@ struct Vec4 {
T z{}; T z{};
T w{}; T w{};
constexpr Vec4() = default;
constexpr Vec4(T x, T y, T z, T w) : x(x), y(y), z(z), w(w) {} constexpr Vec4(T x, T y, T z, T w) : x(x), y(y), z(z), w(w) {}
constexpr Vec4(const zeus::CVector4f& vec) : x(vec.x()), y(vec.y()), z(vec.z()), w(vec.w()) {} constexpr Vec4(const zeus::CVector4f& vec) : x(vec.x()), y(vec.y()), z(vec.z()), w(vec.w()) {}
constexpr Vec4(const zeus::CColor& color) : x(color.r()), y(color.g()), z(color.b()), w(color.a()) {} constexpr Vec4(const zeus::CColor& color) : x(color.r()), y(color.g()), z(color.b()), w(color.a()) {}
}; };
template <typename T> template <typename T>
struct Mat4x2 {
Vec2<T> m0{};
Vec2<T> m1{};
Vec2<T> m2{};
Vec2<T> m3{};
constexpr Mat4x2() = default;
constexpr Mat4x2(const Vec2<T>& m0, const Vec2<T>& m1, const Vec2<T>& m2, const Vec2<T>& m3)
: m0(m0), m1(m1), m2(m2), m3(m3) {}
};
template <typename T>
struct Mat4x4 { struct Mat4x4 {
Vec4<T> m0{}; Vec4<T> m0{};
Vec4<T> m1{}; Vec4<T> m1{};
Vec4<T> m2{}; Vec4<T> m2{};
Vec4<T> m3{}; Vec4<T> m3{};
constexpr Mat4x4() = default;
constexpr Mat4x4(const Vec4<T>& m0, const Vec4<T>& m1, const Vec4<T>& m2, const Vec4<T>& m3) constexpr Mat4x4(const Vec4<T>& m0, const Vec4<T>& m1, const Vec4<T>& m2, const Vec4<T>& m3)
: m0(m0), m1(m1), m2(m2), m3(m3) {} : m0(m0), m1(m1), m2(m2), m3(m3) {}
constexpr Mat4x4(const zeus::CMatrix4f& m) : m0(m[0]), m1(m[1]), m2(m[2]), m3(m[3]) {} constexpr Mat4x4(const zeus::CMatrix4f& m) : m0(m[0]), m1(m[1]), m2(m[2]), m3(m[3]) {}
constexpr Mat4x4(const zeus::CTransform& m) : Mat4x4(m.toMatrix4f()) {}
}; };
constexpr Mat4x4<float> Mat4x4_Identity{ constexpr Mat4x4<float> Mat4x4_Identity{
Vec4<float>{1.f, 0.f, 0.f, 0.f}, Vec4<float>{1.f, 0.f, 0.f, 0.f},

View File

@ -141,12 +141,10 @@ void stream_end() noexcept;
void bind_texture(GX::TexMapID id, metaforce::EClampMode clamp, const TextureHandle& tex, float lod) noexcept; void bind_texture(GX::TexMapID id, metaforce::EClampMode clamp, const TextureHandle& tex, float lod) noexcept;
void unbind_texture(GX::TexMapID id) noexcept; void unbind_texture(GX::TexMapID id) noexcept;
void update_model_view(const zeus::CMatrix4f& mv, const zeus::CMatrix4f& mv_inv) noexcept;
void update_projection(const zeus::CMatrix4f& proj) noexcept;
void update_fog_state(const metaforce::CFogState& state) noexcept; void update_fog_state(const metaforce::CFogState& state) noexcept;
void load_light(GX::LightID id, const Light& light) noexcept; void load_light(GX::LightID id, const Light& light) noexcept;
void load_light_ambient(GX::LightID id, const zeus::CColor& ambient) noexcept; void load_light_ambient(GX::LightID id, const zeus::CColor& ambient) noexcept;
void set_viewport(const zeus::CRectangle& rect, float znear, float zfar) noexcept; void set_viewport(float left, float top, float width, float height, float znear, float zfar) noexcept;
void set_scissor(uint32_t x, uint32_t y, uint32_t w, uint32_t h) noexcept; void set_scissor(uint32_t x, uint32_t y, uint32_t w, uint32_t h) noexcept;
void resolve_color(const ClipRect& rect, uint32_t bind, bool clear_depth) noexcept; void resolve_color(const ClipRect& rect, uint32_t bind, bool clear_depth) noexcept;

View File

@ -55,7 +55,10 @@ struct Command {
CommandType type; CommandType type;
union Data { union Data {
struct SetViewportCommand { struct SetViewportCommand {
zeus::CRectangle rect; float left;
float top;
float width;
float height;
float znear; float znear;
float zfar; float zfar;
} setViewport; } setViewport;
@ -443,8 +446,7 @@ void render(const wgpu::RenderPassEncoder& pass) {
switch (cmd.type) { switch (cmd.type) {
case CommandType::SetViewport: { case CommandType::SetViewport: {
const auto& vp = cmd.data.setViewport; const auto& vp = cmd.data.setViewport;
pass.SetViewport(vp.rect.position.x(), vp.rect.position.y(), vp.rect.size.x(), vp.rect.size.y(), vp.znear, pass.SetViewport(vp.left, vp.top, vp.width, vp.height, vp.znear, vp.zfar);
vp.zfar);
} break; } break;
case CommandType::SetScissor: { case CommandType::SetScissor: {
const auto& sc = cmd.data.setScissor; const auto& sc = cmd.data.setScissor;

View File

@ -116,7 +116,73 @@ void GXSetAlphaCompare(GX::Compare comp0, float ref0, GX::AlphaOp op, GX::Compar
unreachable(); unreachable();
} }
} }
void GXSetVtxDescv(GX::VtxDescList* list) noexcept; void GXSetTexCoordGen2(GX::TexCoordID dst, GX::TexGenType type, GX::TexGenSrc src, GX::TexMtx mtx, GXBool normalize,
GX::PTTexMtx postMtx) noexcept {
if (dst < GX::TEXCOORD0 || dst > GX::TEXCOORD7) {
Log.report(logvisor::Fatal, FMT_STRING("invalid tex coord {}"), dst);
unreachable();
}
g_gxState.tcgs[dst] = {type, src, mtx, postMtx, normalize};
}
void GXLoadTexMtxImm(const void* data, u32 id, GX::TexMtxType type) noexcept {
if ((id < GX::TEXMTX0 || id > GX::IDENTITY) && (id < GX::PTTEXMTX0 || id > GX::PTIDENTITY)) {
Log.report(logvisor::Fatal, FMT_STRING("invalid tex mtx {}"), id);
unreachable();
}
if (id >= GX::PTTEXMTX0) {
if (type != GX::MTX3x4) {
Log.report(logvisor::Fatal, FMT_STRING("invalid pt mtx type {}"), type);
unreachable();
}
const auto idx = (id - GX::PTTEXMTX0) / 3;
g_gxState.ptTexMtxs[idx] = *static_cast<const zeus::CTransform*>(data);
} else {
const auto idx = (id - GX::TEXMTX0) / 3;
switch (type) {
case GX::MTX3x4:
g_gxState.texMtxs[idx] = aurora::Mat4x4<float>{*static_cast<const zeus::CTransform*>(data)};
break;
case GX::MTX2x4:
g_gxState.texMtxs[idx] = *static_cast<const aurora::Mat4x2<float>*>(data);
break;
}
}
}
void GXLoadPosMtxImm(const zeus::CTransform& xf, GX::PosNrmMtx id) noexcept {
if (id != GX::PNMTX0) {
Log.report(logvisor::Fatal, FMT_STRING("invalid pn mtx {}"), id);
unreachable();
}
g_gxState.mv = xf.toMatrix4f();
}
void GXLoadNrmMtxImm(const zeus::CTransform& xf, GX::PosNrmMtx id) noexcept {
if (id != GX::PNMTX0) {
Log.report(logvisor::Fatal, FMT_STRING("invalid pn mtx {}"), id);
unreachable();
}
g_gxState.mvInv = xf.toMatrix4f();
}
constexpr zeus::CMatrix4f DepthCorrect{
// clang-format off
1.f, 0.f, 0.f, 0.f,
0.f, 1.f, 0.f, 0.f,
0.f, 0.f, 0.5f, 0.5f,
0.f, 0.f, 0.f, 1.f,
// clang-format on
};
void GXSetProjection(const zeus::CMatrix4f& mtx, GX::ProjectionType type) noexcept {
if (type == GX::PERSPECTIVE) {
g_gxState.proj = DepthCorrect * mtx;
} else {
g_gxState.proj = mtx;
}
}
void GXSetViewport(float left, float top, float width, float height, float nearZ, float farZ) noexcept {
aurora::gfx::set_viewport(left, top, width, height, nearZ, farZ);
}
void GXSetScissor(u32 left, u32 top, u32 width, u32 height) noexcept {
aurora::gfx::set_scissor(left, top, width, height);
}
namespace aurora::gfx { namespace aurora::gfx {
static logvisor::Module Log("aurora::gfx::gx"); static logvisor::Module Log("aurora::gfx::gx");
@ -130,19 +196,6 @@ void bind_texture(GX::TexMapID id, metaforce::EClampMode clamp, const TextureHan
} }
void unbind_texture(GX::TexMapID id) noexcept { gx::g_gxState.textures[static_cast<size_t>(id)].reset(); } void unbind_texture(GX::TexMapID id) noexcept { gx::g_gxState.textures[static_cast<size_t>(id)].reset(); }
void update_model_view(const zeus::CMatrix4f& mv, const zeus::CMatrix4f& mv_inv) noexcept {
gx::g_gxState.mv = mv;
gx::g_gxState.mvInv = mv_inv;
}
constexpr zeus::CMatrix4f DepthCorrect{
// clang-format off
1.f, 0.f, 0.f, 0.f,
0.f, 1.f, 0.f, 0.f,
0.f, 0.f, 0.5f, 0.5f,
0.f, 0.f, 0.f, 1.f,
// clang-format on
};
void update_projection(const zeus::CMatrix4f& proj) noexcept { gx::g_gxState.proj = DepthCorrect * proj; }
void update_fog_state(const metaforce::CFogState& state) noexcept { gx::g_gxState.fogState = state; } void update_fog_state(const metaforce::CFogState& state) noexcept { gx::g_gxState.fogState = state; }
void load_light(GX::LightID id, const Light& light) noexcept { gx::g_gxState.lights[std::log2<u32>(id)] = light; } void load_light(GX::LightID id, const Light& light) noexcept { gx::g_gxState.lights[std::log2<u32>(id)] = light; }
@ -338,6 +391,9 @@ ShaderInfo populate_pipeline_config(PipelineConfig& config, GX::Primitive primit
for (u8 i = 0; i < g_gxState.numChans; ++i) { for (u8 i = 0; i < g_gxState.numChans; ++i) {
config.shaderConfig.colorChannels[i] = g_gxState.colorChannelConfig[i]; config.shaderConfig.colorChannels[i] = g_gxState.colorChannelConfig[i];
} }
for (u8 i = 0; i < g_gxState.numTexGens; ++i) {
config.shaderConfig.tcgs[i] = g_gxState.tcgs[i];
}
config.shaderConfig.alphaDiscard = g_gxState.alphaDiscard; config.shaderConfig.alphaDiscard = g_gxState.alphaDiscard;
config = { config = {
.shaderConfig = config.shaderConfig, .shaderConfig = config.shaderConfig,
@ -412,6 +468,39 @@ Range build_uniform(const ShaderInfo& info) noexcept {
} }
buf.append(&g_gxState.kcolors[i], 16); buf.append(&g_gxState.kcolors[i], 16);
} }
for (int i = 0; i < info.usesTexMtx.size(); ++i) {
if (!info.usesTexMtx.test(i)) {
continue;
}
switch (info.texMtxTypes[i]) {
case GX::TG_MTX2x4:
if (std::holds_alternative<Mat4x2<float>>(g_gxState.texMtxs[i])) {
buf.append(&std::get<Mat4x2<float>>(g_gxState.texMtxs[i]), 32);
} else {
Log.report(logvisor::Fatal, FMT_STRING("expected 2x4 mtx in idx {}"), i);
unreachable();
}
break;
case GX::TG_MTX3x4:
if (std::holds_alternative<Mat4x4<float>>(g_gxState.texMtxs[i])) {
const auto& mat = std::get<Mat4x4<float>>(g_gxState.texMtxs[i]);
buf.append(&mat, 64);
} else {
// Log.report(logvisor::Fatal, FMT_STRING("expected 3x4 mtx in idx {}"), i);
buf.append(&Mat4x4_Identity, 64);
}
break;
default:
Log.report(logvisor::Fatal, FMT_STRING("unhandled tex mtx type {}"), info.texMtxTypes[i]);
unreachable();
}
}
for (int i = 0; i < info.usesPTTexMtx.size(); ++i) {
if (!info.usesPTTexMtx.test(i)) {
continue;
}
buf.append(&g_gxState.ptTexMtxs[i], 48);
}
for (int i = 0; i < info.sampledTextures.size(); ++i) { for (int i = 0; i < info.sampledTextures.size(); ++i) {
if (!info.sampledTextures.test(i)) { if (!info.sampledTextures.test(i)) {
continue; continue;

View File

@ -10,6 +10,9 @@ constexpr u32 MaxTevStages = GX::MAX_TEVSTAGE;
constexpr u32 MaxColorChannels = 2; // COLOR0A0, COLOR1A1 constexpr u32 MaxColorChannels = 2; // COLOR0A0, COLOR1A1
constexpr u32 MaxTevRegs = 3; // TEVREG0-2 constexpr u32 MaxTevRegs = 3; // TEVREG0-2
constexpr u32 MaxKColors = GX::MAX_KCOLOR; constexpr u32 MaxKColors = GX::MAX_KCOLOR;
constexpr u32 MaxTexMtx = 10;
constexpr u32 MaxPTTexMtx = 20;
constexpr u32 MaxTexCoord = GX::MAX_TEXCOORD;
template <typename Arg, Arg Default> template <typename Arg, Arg Default>
struct TevPass { struct TevPass {
@ -61,6 +64,15 @@ struct ColorChannelState {
GX::LightMask lightState; GX::LightMask lightState;
}; };
using LightVariant = std::variant<std::monostate, Light, zeus::CColor>; using LightVariant = std::variant<std::monostate, Light, zeus::CColor>;
// Mat4x4 used instead of Mat4x3 for padding purposes
using TexMtxVariant = std::variant<std::monostate, Mat4x2<float>, Mat4x4<float>>;
struct TcgConfig {
GX::TexGenType type = GX::TG_MTX2x4;
GX::TexGenSrc src = GX::MAX_TEXGENSRC;
GX::TexMtx mtx = GX::IDENTITY;
GX::PTTexMtx postMtx = GX::PTIDENTITY;
bool normalize = false;
};
struct GXState { struct GXState {
zeus::CMatrix4f mv; zeus::CMatrix4f mv;
@ -83,6 +95,9 @@ struct GXState {
std::array<LightVariant, GX::MaxLights> lights; std::array<LightVariant, GX::MaxLights> lights;
std::array<TevStage, MaxTevStages> tevStages; std::array<TevStage, MaxTevStages> tevStages;
std::array<TextureBind, MaxTextures> textures; std::array<TextureBind, MaxTextures> textures;
std::array<TexMtxVariant, MaxTexMtx> texMtxs;
std::array<Mat4x4<float>, MaxPTTexMtx> ptTexMtxs;
std::array<TcgConfig, MaxTexCoord> tcgs;
bool depthCompare = true; bool depthCompare = true;
bool depthUpdate = true; bool depthUpdate = true;
bool alphaUpdate = true; bool alphaUpdate = true;
@ -101,6 +116,7 @@ const TextureBind& get_texture(GX::TexMapID id) noexcept;
struct ShaderConfig { struct ShaderConfig {
std::array<std::optional<TevStage>, MaxTevStages> tevStages; std::array<std::optional<TevStage>, MaxTevStages> tevStages;
std::array<ColorChannelConfig, MaxColorChannels> colorChannels; std::array<ColorChannelConfig, MaxColorChannels> colorChannels;
std::array<TcgConfig, MaxTexCoord> tcgs;
std::optional<float> alphaDiscard; std::optional<float> alphaDiscard;
bool denormalizedVertexAttributes = false; bool denormalizedVertexAttributes = false;
bool denormalizedHasNrm = false; // TODO this is a hack bool denormalizedHasNrm = false; // TODO this is a hack
@ -133,6 +149,9 @@ struct ShaderInfo {
std::bitset<MaxKColors> sampledKColors; std::bitset<MaxKColors> sampledKColors;
std::bitset<MaxColorChannels> sampledColorChannels; std::bitset<MaxColorChannels> sampledColorChannels;
std::bitset<MaxTevRegs> usesTevReg; std::bitset<MaxTevRegs> usesTevReg;
std::bitset<MaxTexMtx> usesTexMtx;
std::bitset<MaxPTTexMtx> usesPTTexMtx;
std::array<GX::TexGenType, MaxTexMtx> texMtxTypes;
u32 uniformSize = 0; u32 uniformSize = 0;
bool usesVtxColor : 1 = false; bool usesVtxColor : 1 = false;
bool usesNormal : 1 = false; bool usesNormal : 1 = false;
@ -204,6 +223,14 @@ inline void xxh3_update(XXH3_state_t& state, const gfx::gx::ColorChannelConfig&
} }
} }
template <> template <>
inline void xxh3_update(XXH3_state_t& state, const gfx::gx::TcgConfig& input) {
XXH3_64bits_update(&state, &input.type, sizeof(gfx::gx::TcgConfig::type));
XXH3_64bits_update(&state, &input.src, sizeof(gfx::gx::TcgConfig::src));
XXH3_64bits_update(&state, &input.mtx, sizeof(gfx::gx::TcgConfig::mtx));
XXH3_64bits_update(&state, &input.postMtx, sizeof(gfx::gx::TcgConfig::postMtx));
XXH3_64bits_update(&state, &input.normalize, sizeof(gfx::gx::TcgConfig::normalize));
}
template <>
inline void xxh3_update(XXH3_state_t& state, const gfx::gx::ShaderConfig& input) { inline void xxh3_update(XXH3_state_t& state, const gfx::gx::ShaderConfig& input) {
for (const auto& item : input.tevStages) { for (const auto& item : input.tevStages) {
if (!item) { if (!item) {
@ -214,6 +241,9 @@ inline void xxh3_update(XXH3_state_t& state, const gfx::gx::ShaderConfig& input)
for (const auto& item : input.colorChannels) { for (const auto& item : input.colorChannels) {
xxh3_update(state, item); xxh3_update(state, item);
} }
for (const auto& item : input.tcgs) {
xxh3_update(state, item);
}
if (input.alphaDiscard) { if (input.alphaDiscard) {
XXH3_64bits_update(&state, &*input.alphaDiscard, sizeof(float)); XXH3_64bits_update(&state, &*input.alphaDiscard, sizeof(float));
} }

View File

@ -334,6 +334,16 @@ static std::string_view tev_scale(GX::TevScale scale) {
} }
} }
static std::string in_uv(u32 idx) {
if (idx == 0) {
return "v_packed_uvs.data[in_uv_0_4_idx[0]]";
}
if (idx < 4) {
return fmt::format(FMT_STRING("v_uvs.data[in_uv_0_4_idx[{}]]"), idx);
}
return fmt::format(FMT_STRING("v_uvs.data[in_uv_5_7_idx[{}]]"), idx - 4);
}
std::pair<wgpu::ShaderModule, ShaderInfo> build_shader(const ShaderConfig& config) noexcept { std::pair<wgpu::ShaderModule, ShaderInfo> build_shader(const ShaderConfig& config) noexcept {
const auto hash = xxh3_hash(config); const auto hash = xxh3_hash(config);
if (g_gxCachedShaders.contains(hash)) { if (g_gxCachedShaders.contains(hash)) {
@ -376,10 +386,19 @@ std::pair<wgpu::ShaderModule, ShaderInfo> build_shader(const ShaderConfig& confi
Log.report(logvisor::Info, FMT_STRING(" texMapId: {}"), stage->texMapId); Log.report(logvisor::Info, FMT_STRING(" texMapId: {}"), stage->texMapId);
Log.report(logvisor::Info, FMT_STRING(" channelId: {}"), stage->channelId); Log.report(logvisor::Info, FMT_STRING(" channelId: {}"), stage->channelId);
} }
// for (int i = 0; i < config.channelMatSrcs.size(); ++i) { for (int i = 0; i < config.colorChannels.size(); ++i) {
// Log.report(logvisor::Info, FMT_STRING(" channelMatSrcs[{}]: {}"), i, config.channelMatSrcs[i]); const auto& chan = config.colorChannels[i];
// } Log.report(logvisor::Info, FMT_STRING(" colorChannels[{}]: enabled {} mat {} amb {}"), i, chan.lightingEnabled,
// Log.report(logvisor::Info, FMT_STRING(" alphaDiscard: {}"), config.alphaDiscard); chan.matSrc, chan.ambSrc);
}
for (int i = 0; i < config.tcgs.size(); ++i) {
const auto& tcg = config.tcgs[i];
if (tcg.src != GX::MAX_TEXGENSRC) {
Log.report(logvisor::Info, FMT_STRING(" tcg[{}]: src {} mtx {} post {} type {} norm {}"), i, tcg.src, tcg.mtx,
tcg.postMtx, tcg.type, tcg.normalize);
}
}
Log.report(logvisor::Info, FMT_STRING(" alphaDiscard: {}"), config.alphaDiscard.value_or(0.f));
Log.report(logvisor::Info, FMT_STRING(" denormalizedVertexAttributes: {}"), config.denormalizedVertexAttributes); Log.report(logvisor::Info, FMT_STRING(" denormalizedVertexAttributes: {}"), config.denormalizedVertexAttributes);
} }
@ -593,6 +612,78 @@ var<storage, read> v_packed_uvs: Vec2Block;
info.uniformSize += 16; info.uniformSize += 16;
} }
size_t texBindIdx = 0; size_t texBindIdx = 0;
for (int i = 0; i < info.sampledTextures.size(); ++i) {
if (!info.sampledTextures.test(i)) {
continue;
}
const auto& tcg = config.tcgs[i];
if (config.denormalizedVertexAttributes) {
vtxOutAttrs += fmt::format(FMT_STRING("\n @location({}) tex{}_uv: vec2<f32>;"), locIdx, i);
vtxInAttrs += fmt::format(FMT_STRING("\n , @location({}) in_tex{}_uv: vec2<f32>"), locIdx + 1, i);
// TODO check tcg src for denorm?
vtxXfrAttrs += fmt::format(FMT_STRING("\n var tc{0} = vec4<f32>(in_tex{0}_uv, 0.0, 1.0);"), i);
} else {
vtxOutAttrs += fmt::format(FMT_STRING("\n @location({}) tex{}_uv: vec2<f32>;"), locIdx, i);
if (tcg.src >= GX::TG_TEX0 && tcg.src <= GX::TG_TEX7) {
vtxXfrAttrs += fmt::format(FMT_STRING("\n var tc{} = vec4<f32>({}, 0.0, 1.0);"), i, in_uv(tcg.src - GX::TG_TEX0));
} else if (tcg.src == GX::TG_POS) {
vtxXfrAttrs += fmt::format(FMT_STRING("\n var tc{} = vec4<f32>(obj_pos.xyz, 1.0);"), i);
} else if (tcg.src == GX::TG_NRM) {
vtxXfrAttrs += fmt::format(FMT_STRING("\n var tc{} = vec4<f32>(obj_norm.xyz, 1.0);"), i);
} else {
Log.report(logvisor::Fatal, FMT_STRING("unhandled tcg src {}"), tcg.src);
unreachable();
}
}
// TODO this all assumes MTX3x4 currently
if (tcg.mtx == GX::IDENTITY) {
vtxXfrAttrs += fmt::format(FMT_STRING("\n var tc{0}_tmp = tc{0}.xyz;"), i);
} else {
u32 texMtxIdx = (tcg.mtx - GX::TEXMTX0) / 3;
info.usesTexMtx.set(texMtxIdx);
info.texMtxTypes[texMtxIdx] = tcg.type;
vtxXfrAttrs += fmt::format(FMT_STRING("\n var tc{0}_tmp = ubuf.texmtx{1} * tc{0};"), i, texMtxIdx);
}
if (tcg.normalize) {
vtxXfrAttrs += fmt::format(FMT_STRING("\n tc{0}_tmp = normalize(tc{0}_tmp);"), i);
}
if (tcg.postMtx == GX::PTIDENTITY) {
vtxXfrAttrs += fmt::format(FMT_STRING("\n var tc{0}_proj = tc{0}_tmp;"), i);
} else {
u32 postMtxIdx = (tcg.postMtx - GX::PTTEXMTX0) / 3;
info.usesPTTexMtx.set(postMtxIdx);
vtxXfrAttrs += fmt::format(FMT_STRING("\n var tc{0}_proj = ubuf.postmtx{1} * vec4<f32>(tc{0}_tmp.xyz, 1.0);"), i, postMtxIdx);
}
vtxXfrAttrs += fmt::format(FMT_STRING("\n out.tex{0}_uv = tc{0}_proj.xy;"), i);
fragmentFnPre += fmt::format(
FMT_STRING("\n var sampled{0} = textureSampleBias(tex{0}, tex{0}_samp, in.tex{0}_uv, ubuf.tex{0}_lod);"), i);
locIdx++;
}
for (int i = 0; i < info.usesTexMtx.size(); ++i) {
if (!info.usesTexMtx.test(i)) {
continue;
}
switch (info.texMtxTypes[i]) {
case GX::TG_MTX2x4:
uniBufAttrs += fmt::format(FMT_STRING("\n texmtx{}: mat4x2<f32>;"), i);
info.uniformSize += 32;
break;
case GX::TG_MTX3x4:
uniBufAttrs += fmt::format(FMT_STRING("\n texmtx{}: mat4x3<f32>;"), i);
info.uniformSize += 64;
break;
default:
Log.report(logvisor::Fatal, FMT_STRING("unhandled tex mtx type {}"), info.texMtxTypes[i]);
unreachable();
}
}
for (int i = 0; i < info.usesPTTexMtx.size(); ++i) {
if (!info.usesPTTexMtx.test(i)) {
continue;
}
uniBufAttrs += fmt::format(FMT_STRING("\n postmtx{}: mat4x3<f32>;"), i);
info.uniformSize += 64;
}
for (int i = 0; i < info.sampledTextures.size(); ++i) { for (int i = 0; i < info.sampledTextures.size(); ++i) {
if (!info.sampledTextures.test(i)) { if (!info.sampledTextures.test(i)) {
continue; continue;
@ -607,26 +698,6 @@ var<storage, read> v_packed_uvs: Vec2Block;
"var tex{}: texture_2d<f32>;"), "var tex{}: texture_2d<f32>;"),
texBindIdx, i); texBindIdx, i);
++texBindIdx; ++texBindIdx;
if (config.denormalizedVertexAttributes) {
vtxOutAttrs += fmt::format(FMT_STRING("\n @location({}) tex{}_uv: vec2<f32>;"), locIdx, i);
vtxInAttrs += fmt::format(FMT_STRING("\n , @location({}) in_tex{}_uv: vec2<f32>"), locIdx + 1, i);
vtxXfrAttrs += fmt::format(FMT_STRING("\n out.tex{0}_uv = in_tex{0}_uv;"), i);
} else {
vtxOutAttrs += fmt::format(FMT_STRING("\n @location({}) tex{}_uv: vec2<f32>;"), locIdx, i);
if (i < 4) {
if (i == 0) {
vtxXfrAttrs += fmt::format(FMT_STRING("\n out.tex{}_uv = v_packed_uvs.data[in_uv_0_4_idx[{}]];"), i, i);
} else {
vtxXfrAttrs += fmt::format(FMT_STRING("\n out.tex{}_uv = v_uvs.data[in_uv_0_4_idx[{}]];"), i, i);
}
} else {
vtxXfrAttrs += fmt::format(FMT_STRING("\n out.tex{}_uv = v_uvs.data[in_uv_5_7_idx[{}]];"), i, i - 4);
}
}
fragmentFnPre += fmt::format(
FMT_STRING("\n var sampled{0} = textureSampleBias(tex{0}, tex{0}_samp, in.tex{0}_uv, ubuf.tex{0}_lod);"), i);
locIdx++;
} }
if (config.alphaDiscard) { if (config.alphaDiscard) {
fragmentFn += fmt::format(FMT_STRING("\n if (prev.a < {}f) {{ discard; }}"), *config.alphaDiscard); fragmentFn += fmt::format(FMT_STRING("\n if (prev.a < {}f) {{ discard; }}"), *config.alphaDiscard);