mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-11 09:25:05 -04:00
734 lines
23 KiB
C++
734 lines
23 KiB
C++
#include "gx.hpp"
|
|
#include "__gx.h"
|
|
#include "../../gx/fifo.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <limits>
|
|
|
|
// Track vertex count between GXBegin/GXEnd for mismatch detection
|
|
static u16 sBeginNVerts = 0;
|
|
static u32 sBeginFifoSize = 0;
|
|
static GXPrimitive sBeginPrimitive = GX_TRIANGLES;
|
|
static GXVtxFmt sBeginVtxFmt = GX_VTXFMT0;
|
|
static bool sInBegin = false;
|
|
static GXAttr sWriteAttr = GX_VA_NULL;
|
|
static u32 sWriteAttrComps = 0;
|
|
|
|
namespace {
|
|
|
|
static u8 normal_frac_bits(GXCompType type) {
|
|
switch (type) {
|
|
case GX_U8:
|
|
return 7;
|
|
case GX_S8:
|
|
return 6;
|
|
case GX_U16:
|
|
return 15;
|
|
case GX_S16:
|
|
return 14;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
struct ActiveAttrFmt {
|
|
GXCompCnt cnt = GX_POS_XYZ;
|
|
GXCompType type = GX_F32;
|
|
u8 frac = 0;
|
|
};
|
|
|
|
enum class ScalarKind {
|
|
Float,
|
|
Signed,
|
|
Unsigned,
|
|
};
|
|
|
|
struct Scalar {
|
|
ScalarKind kind = ScalarKind::Float;
|
|
float f = 0.0f;
|
|
int32_t s = 0;
|
|
uint32_t u = 0;
|
|
};
|
|
|
|
static Scalar scalar_f32(float value) {
|
|
return Scalar{.kind = ScalarKind::Float, .f = value, .s = static_cast<int32_t>(value), .u = static_cast<uint32_t>(value)};
|
|
}
|
|
|
|
static Scalar scalar_u32(uint32_t value) {
|
|
return Scalar{.kind = ScalarKind::Unsigned, .f = static_cast<float>(value), .s = static_cast<int32_t>(value), .u = value};
|
|
}
|
|
|
|
static Scalar scalar_s32(int32_t value) {
|
|
return Scalar{.kind = ScalarKind::Signed, .f = static_cast<float>(value), .s = value, .u = static_cast<uint32_t>(value)};
|
|
}
|
|
|
|
static bool is_matrix_index_attr(GXAttr attr) {
|
|
return attr >= GX_VA_PNMTXIDX && attr <= GX_VA_TEX7MTXIDX;
|
|
}
|
|
|
|
static bool is_color_attr(GXAttr attr) {
|
|
return attr == GX_VA_CLR0 || attr == GX_VA_CLR1;
|
|
}
|
|
|
|
static bool is_tex_attr(GXAttr attr) {
|
|
return attr >= GX_VA_TEX0 && attr <= GX_VA_TEX7;
|
|
}
|
|
|
|
static GXAttrType active_attr_type(GXAttr attr) {
|
|
switch (attr) {
|
|
case GX_VA_PNMTXIDX:
|
|
case GX_VA_TEX0MTXIDX:
|
|
case GX_VA_TEX1MTXIDX:
|
|
case GX_VA_TEX2MTXIDX:
|
|
case GX_VA_TEX3MTXIDX:
|
|
case GX_VA_TEX4MTXIDX:
|
|
case GX_VA_TEX5MTXIDX:
|
|
case GX_VA_TEX6MTXIDX:
|
|
case GX_VA_TEX7MTXIDX:
|
|
return GET_REG_FIELD(__gx->vcdLo, 1, underlying(attr)) ? GX_DIRECT : GX_NONE;
|
|
case GX_VA_POS:
|
|
return static_cast<GXAttrType>(GET_REG_FIELD(__gx->vcdLo, 2, 9));
|
|
case GX_VA_NRM:
|
|
return (__gx->hasNrms || __gx->hasBiNrms) ? static_cast<GXAttrType>(GET_REG_FIELD(__gx->vcdLo, 2, 11)) : GX_NONE;
|
|
case GX_VA_CLR0:
|
|
return static_cast<GXAttrType>(GET_REG_FIELD(__gx->vcdLo, 2, 13));
|
|
case GX_VA_CLR1:
|
|
return static_cast<GXAttrType>(GET_REG_FIELD(__gx->vcdLo, 2, 15));
|
|
case GX_VA_TEX0:
|
|
return static_cast<GXAttrType>(GET_REG_FIELD(__gx->vcdHi, 2, 0));
|
|
case GX_VA_TEX1:
|
|
return static_cast<GXAttrType>(GET_REG_FIELD(__gx->vcdHi, 2, 2));
|
|
case GX_VA_TEX2:
|
|
return static_cast<GXAttrType>(GET_REG_FIELD(__gx->vcdHi, 2, 4));
|
|
case GX_VA_TEX3:
|
|
return static_cast<GXAttrType>(GET_REG_FIELD(__gx->vcdHi, 2, 6));
|
|
case GX_VA_TEX4:
|
|
return static_cast<GXAttrType>(GET_REG_FIELD(__gx->vcdHi, 2, 8));
|
|
case GX_VA_TEX5:
|
|
return static_cast<GXAttrType>(GET_REG_FIELD(__gx->vcdHi, 2, 10));
|
|
case GX_VA_TEX6:
|
|
return static_cast<GXAttrType>(GET_REG_FIELD(__gx->vcdHi, 2, 12));
|
|
case GX_VA_TEX7:
|
|
return static_cast<GXAttrType>(GET_REG_FIELD(__gx->vcdHi, 2, 14));
|
|
default:
|
|
return GX_NONE;
|
|
}
|
|
}
|
|
|
|
static ActiveAttrFmt active_attr_fmt(GXVtxFmt fmt, GXAttr attr) {
|
|
ActiveAttrFmt out{};
|
|
const u32 va = __gx->vatA[fmt];
|
|
const u32 vb = __gx->vatB[fmt];
|
|
const u32 vc = __gx->vatC[fmt];
|
|
|
|
switch (attr) {
|
|
case GX_VA_POS:
|
|
out.cnt = GET_REG_FIELD(va, 1, 0) ? GX_POS_XYZ : GX_POS_XY;
|
|
out.type = static_cast<GXCompType>(GET_REG_FIELD(va, 3, 1));
|
|
out.frac = static_cast<u8>(GET_REG_FIELD(va, 5, 4));
|
|
break;
|
|
case GX_VA_NRM:
|
|
out.type = static_cast<GXCompType>(GET_REG_FIELD(va, 3, 10));
|
|
if (GET_REG_FIELD(va, 1, 31)) {
|
|
out.cnt = GX_NRM_NBT3;
|
|
} else {
|
|
out.cnt = GET_REG_FIELD(va, 1, 9) ? GX_NRM_NBT : GX_NRM_XYZ;
|
|
}
|
|
out.frac = normal_frac_bits(out.type);
|
|
break;
|
|
case GX_VA_CLR0:
|
|
out.cnt = GET_REG_FIELD(va, 1, 13) ? GX_CLR_RGBA : GX_CLR_RGB;
|
|
out.type = static_cast<GXCompType>(GET_REG_FIELD(va, 3, 14));
|
|
out.frac = 0;
|
|
break;
|
|
case GX_VA_CLR1:
|
|
out.cnt = GET_REG_FIELD(va, 1, 17) ? GX_CLR_RGBA : GX_CLR_RGB;
|
|
out.type = static_cast<GXCompType>(GET_REG_FIELD(va, 3, 18));
|
|
out.frac = 0;
|
|
break;
|
|
case GX_VA_TEX0:
|
|
out.cnt = GET_REG_FIELD(va, 1, 21) ? GX_TEX_ST : GX_TEX_S;
|
|
out.type = static_cast<GXCompType>(GET_REG_FIELD(va, 3, 22));
|
|
out.frac = static_cast<u8>(GET_REG_FIELD(va, 5, 25));
|
|
break;
|
|
case GX_VA_TEX1:
|
|
out.cnt = GET_REG_FIELD(vb, 1, 0) ? GX_TEX_ST : GX_TEX_S;
|
|
out.type = static_cast<GXCompType>(GET_REG_FIELD(vb, 3, 1));
|
|
out.frac = static_cast<u8>(GET_REG_FIELD(vb, 5, 4));
|
|
break;
|
|
case GX_VA_TEX2:
|
|
out.cnt = GET_REG_FIELD(vb, 1, 9) ? GX_TEX_ST : GX_TEX_S;
|
|
out.type = static_cast<GXCompType>(GET_REG_FIELD(vb, 3, 10));
|
|
out.frac = static_cast<u8>(GET_REG_FIELD(vb, 5, 13));
|
|
break;
|
|
case GX_VA_TEX3:
|
|
out.cnt = GET_REG_FIELD(vb, 1, 18) ? GX_TEX_ST : GX_TEX_S;
|
|
out.type = static_cast<GXCompType>(GET_REG_FIELD(vb, 3, 19));
|
|
out.frac = static_cast<u8>(GET_REG_FIELD(vb, 5, 22));
|
|
break;
|
|
case GX_VA_TEX4:
|
|
out.cnt = GET_REG_FIELD(vb, 1, 27) ? GX_TEX_ST : GX_TEX_S;
|
|
out.type = static_cast<GXCompType>(GET_REG_FIELD(vb, 3, 28));
|
|
out.frac = static_cast<u8>(GET_REG_FIELD(vc, 5, 0));
|
|
break;
|
|
case GX_VA_TEX5:
|
|
out.cnt = GET_REG_FIELD(vc, 1, 5) ? GX_TEX_ST : GX_TEX_S;
|
|
out.type = static_cast<GXCompType>(GET_REG_FIELD(vc, 3, 6));
|
|
out.frac = static_cast<u8>(GET_REG_FIELD(vc, 5, 9));
|
|
break;
|
|
case GX_VA_TEX6:
|
|
out.cnt = GET_REG_FIELD(vc, 1, 14) ? GX_TEX_ST : GX_TEX_S;
|
|
out.type = static_cast<GXCompType>(GET_REG_FIELD(vc, 3, 15));
|
|
out.frac = static_cast<u8>(GET_REG_FIELD(vc, 5, 18));
|
|
break;
|
|
case GX_VA_TEX7:
|
|
out.cnt = GET_REG_FIELD(vc, 1, 23) ? GX_TEX_ST : GX_TEX_S;
|
|
out.type = static_cast<GXCompType>(GET_REG_FIELD(vc, 3, 24));
|
|
out.frac = static_cast<u8>(GET_REG_FIELD(vc, 5, 27));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static u32 expected_component_count(GXAttr attr, const ActiveAttrFmt& fmt, GXAttrType type) {
|
|
if (is_matrix_index_attr(attr) || is_color_attr(attr)) {
|
|
return 1;
|
|
}
|
|
if (type == GX_INDEX8 || type == GX_INDEX16) {
|
|
return (attr == GX_VA_NRM && fmt.cnt == GX_NRM_NBT3) ? 3u : 1u;
|
|
}
|
|
if (attr == GX_VA_POS) {
|
|
return fmt.cnt == GX_POS_XY ? 2u : 3u;
|
|
}
|
|
if (attr == GX_VA_NRM) {
|
|
return (fmt.cnt == GX_NRM_NBT || fmt.cnt == GX_NRM_NBT3) ? 9u : 3u;
|
|
}
|
|
if (is_tex_attr(attr)) {
|
|
return fmt.cnt == GX_TEX_S ? 1u : 2u;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// Cache decoded vertex settings and refresh them when their GX registers change.
|
|
struct AttrCacheEntry {
|
|
ActiveAttrFmt fmt{};
|
|
GXAttrType type = GX_NONE;
|
|
u32 expected = 1;
|
|
GXAttr next = GX_VA_NULL;
|
|
};
|
|
|
|
struct VtxStateCache {
|
|
bool valid = false;
|
|
GXVtxFmt vtxFmt = GX_VTXFMT0;
|
|
u32 vcdLo = 0;
|
|
u32 vcdHi = 0;
|
|
u32 vatA = 0;
|
|
u32 vatB = 0;
|
|
u32 vatC = 0;
|
|
u8 hasNrms = 0;
|
|
u8 hasBiNrms = 0;
|
|
GXAttr first = GX_VA_NULL;
|
|
std::array<AttrCacheEntry, GX_VA_MAX_ATTR> attrs{};
|
|
};
|
|
|
|
static VtxStateCache sVtxCache;
|
|
|
|
static void rebuild_vtx_cache(GXVtxFmt vtxFmt) {
|
|
sVtxCache.valid = true;
|
|
sVtxCache.vtxFmt = vtxFmt;
|
|
sVtxCache.vcdLo = __gx->vcdLo;
|
|
sVtxCache.vcdHi = __gx->vcdHi;
|
|
sVtxCache.vatA = __gx->vatA[vtxFmt];
|
|
sVtxCache.vatB = __gx->vatB[vtxFmt];
|
|
sVtxCache.vatC = __gx->vatC[vtxFmt];
|
|
sVtxCache.hasNrms = __gx->hasNrms;
|
|
sVtxCache.hasBiNrms = __gx->hasBiNrms;
|
|
|
|
for (int i = 0; i < GX_VA_MAX_ATTR; ++i) {
|
|
const auto attr = static_cast<GXAttr>(i);
|
|
AttrCacheEntry& entry = sVtxCache.attrs[i];
|
|
entry.type = active_attr_type(attr);
|
|
entry.fmt = active_attr_fmt(vtxFmt, attr);
|
|
entry.expected = expected_component_count(attr, entry.fmt, entry.type);
|
|
entry.next = GX_VA_NULL;
|
|
}
|
|
|
|
GXAttr running = GX_VA_NULL;
|
|
for (int i = GX_VA_TEX7; i >= GX_VA_PNMTXIDX; --i) {
|
|
sVtxCache.attrs[i].next = running;
|
|
if (sVtxCache.attrs[i].type != GX_NONE) {
|
|
running = static_cast<GXAttr>(i);
|
|
}
|
|
}
|
|
sVtxCache.first = running;
|
|
}
|
|
|
|
static inline void ensure_vtx_cache(GXVtxFmt vtxFmt) {
|
|
if (sVtxCache.valid && sVtxCache.vtxFmt == vtxFmt && sVtxCache.vcdLo == __gx->vcdLo &&
|
|
sVtxCache.vcdHi == __gx->vcdHi && sVtxCache.vatA == __gx->vatA[vtxFmt] &&
|
|
sVtxCache.vatB == __gx->vatB[vtxFmt] && sVtxCache.vatC == __gx->vatC[vtxFmt] &&
|
|
sVtxCache.hasNrms == __gx->hasNrms && sVtxCache.hasBiNrms == __gx->hasBiNrms) {
|
|
return;
|
|
}
|
|
rebuild_vtx_cache(vtxFmt);
|
|
}
|
|
|
|
static inline const AttrCacheEntry& attr_cache(GXAttr attr) {
|
|
static const AttrCacheEntry kDisabled{};
|
|
const auto idx = static_cast<u32>(attr);
|
|
return idx < GX_VA_MAX_ATTR ? sVtxCache.attrs[idx] : kDisabled;
|
|
}
|
|
|
|
static void reset_attr_writer() {
|
|
sWriteAttr = sVtxCache.first;
|
|
sWriteAttrComps = 0;
|
|
}
|
|
|
|
static GXAttr select_implicit_attr(GXAttr first, GXAttr last) {
|
|
if (sInBegin && sWriteAttr >= first && sWriteAttr <= last && attr_cache(sWriteAttr).type != GX_NONE) {
|
|
return sWriteAttr;
|
|
}
|
|
if (sInBegin) {
|
|
for (int i = static_cast<int>(sWriteAttr); i <= static_cast<int>(last); ++i) {
|
|
const auto attr = static_cast<GXAttr>(i);
|
|
if (attr >= first && attr_cache(attr).type != GX_NONE) {
|
|
sWriteAttr = attr;
|
|
sWriteAttrComps = 0;
|
|
return attr;
|
|
}
|
|
}
|
|
}
|
|
for (int i = static_cast<int>(first); i <= static_cast<int>(last); ++i) {
|
|
const auto attr = static_cast<GXAttr>(i);
|
|
if (attr_cache(attr).type != GX_NONE) {
|
|
sWriteAttr = attr;
|
|
sWriteAttrComps = 0;
|
|
return attr;
|
|
}
|
|
}
|
|
return first;
|
|
}
|
|
|
|
static void align_explicit_attr(GXAttr attr) {
|
|
if (!sInBegin || sWriteAttr == attr) {
|
|
return;
|
|
}
|
|
sWriteAttr = attr;
|
|
sWriteAttrComps = 0;
|
|
}
|
|
|
|
static void advance_attr(GXAttr attr, u32 compsWritten) {
|
|
if (!sInBegin) {
|
|
return;
|
|
}
|
|
const AttrCacheEntry& entry = attr_cache(attr);
|
|
sWriteAttrComps += compsWritten;
|
|
if (sWriteAttrComps < entry.expected) {
|
|
return;
|
|
}
|
|
sWriteAttr = entry.next;
|
|
sWriteAttrComps = 0;
|
|
if (sWriteAttr == GX_VA_NULL) {
|
|
reset_attr_writer();
|
|
}
|
|
}
|
|
|
|
static int64_t encode_float_component(float value, bool isSigned, u8 bits, u8 frac) {
|
|
if (!std::isfinite(value)) {
|
|
value = 0.0f;
|
|
}
|
|
const double scaled = static_cast<double>(value) * static_cast<double>(uint64_t{1} << frac);
|
|
const double rounded = std::round(scaled);
|
|
if (isSigned) {
|
|
const int64_t minVal = -(int64_t{1} << (bits - 1));
|
|
const int64_t maxVal = (int64_t{1} << (bits - 1)) - 1;
|
|
return std::clamp(static_cast<int64_t>(rounded), minVal, maxVal);
|
|
}
|
|
const int64_t maxVal = (int64_t{1} << bits) - 1;
|
|
return std::clamp(static_cast<int64_t>(rounded), int64_t{0}, maxVal);
|
|
}
|
|
|
|
static int64_t scalar_to_signed_raw(const Scalar& value, u8 bits, u8 frac) {
|
|
if (value.kind == ScalarKind::Float) {
|
|
return encode_float_component(value.f, true, bits, frac);
|
|
}
|
|
const int64_t raw = value.kind == ScalarKind::Signed ? value.s : static_cast<int64_t>(value.u);
|
|
const int64_t minVal = -(int64_t{1} << (bits - 1));
|
|
const int64_t maxVal = (int64_t{1} << (bits - 1)) - 1;
|
|
return std::clamp(raw, minVal, maxVal);
|
|
}
|
|
|
|
static uint64_t scalar_to_unsigned_raw(const Scalar& value, u8 bits, u8 frac) {
|
|
if (value.kind == ScalarKind::Float) {
|
|
return static_cast<uint64_t>(encode_float_component(value.f, false, bits, frac));
|
|
}
|
|
const uint64_t raw = value.kind == ScalarKind::Signed ? static_cast<uint64_t>(std::max(value.s, int32_t{0})) : value.u;
|
|
const uint64_t maxVal = (uint64_t{1} << bits) - 1;
|
|
return std::min(raw, maxVal);
|
|
}
|
|
|
|
static float scalar_to_float(const Scalar& value) {
|
|
if (value.kind == ScalarKind::Float) {
|
|
return value.f;
|
|
}
|
|
if (value.kind == ScalarKind::Signed) {
|
|
return static_cast<float>(value.s);
|
|
}
|
|
return static_cast<float>(value.u);
|
|
}
|
|
|
|
static void write_numeric_component(const ActiveAttrFmt& fmt, const Scalar& value) {
|
|
switch (fmt.type) {
|
|
case GX_U8:
|
|
GX_WRITE_U8(static_cast<u8>(scalar_to_unsigned_raw(value, 8, fmt.frac)));
|
|
break;
|
|
case GX_S8:
|
|
GX_WRITE_U8(static_cast<u8>(static_cast<int8_t>(scalar_to_signed_raw(value, 8, fmt.frac))));
|
|
break;
|
|
case GX_U16:
|
|
GX_WRITE_U16(static_cast<u16>(scalar_to_unsigned_raw(value, 16, fmt.frac)));
|
|
break;
|
|
case GX_S16:
|
|
GX_WRITE_U16(static_cast<u16>(static_cast<int16_t>(scalar_to_signed_raw(value, 16, fmt.frac))));
|
|
break;
|
|
case GX_F32:
|
|
default:
|
|
GX_WRITE_F32(scalar_to_float(value));
|
|
break;
|
|
}
|
|
}
|
|
|
|
template <size_t N>
|
|
static void write_numeric_attr(GXAttr attr, const std::array<Scalar, N>& values) {
|
|
ensure_vtx_cache(sBeginVtxFmt);
|
|
align_explicit_attr(attr);
|
|
const AttrCacheEntry& entry = attr_cache(attr);
|
|
if (entry.type == GX_NONE) {
|
|
return;
|
|
}
|
|
if (entry.type == GX_INDEX8 || entry.type == GX_INDEX16) {
|
|
return;
|
|
}
|
|
const u32 expected = entry.expected;
|
|
const u32 remaining = expected > sWriteAttrComps ? expected - sWriteAttrComps : expected;
|
|
const u32 count = std::min<u32>(static_cast<u32>(values.size()), remaining);
|
|
for (u32 i = 0; i < count; ++i) {
|
|
write_numeric_component(entry.fmt, values[i]);
|
|
}
|
|
advance_attr(attr, count);
|
|
}
|
|
|
|
static u8 clamp_color_float(float value) {
|
|
if (!std::isfinite(value)) {
|
|
return 0;
|
|
}
|
|
return static_cast<u8>(std::clamp(std::round(value * 255.0f), 0.0f, 255.0f));
|
|
}
|
|
|
|
static u8 quantize_unorm(u8 value, u8 bits) {
|
|
const u32 maxValue = (1u << bits) - 1u;
|
|
return static_cast<u8>((static_cast<u32>(value) * maxValue + 127u) / 255u);
|
|
}
|
|
|
|
static void write_color_attr(u8 r, u8 g, u8 b, u8 a) {
|
|
ensure_vtx_cache(sBeginVtxFmt);
|
|
const GXAttr attr = select_implicit_attr(GX_VA_CLR0, GX_VA_CLR1);
|
|
const AttrCacheEntry& entry = attr_cache(attr);
|
|
if (entry.type == GX_NONE) {
|
|
return;
|
|
}
|
|
if (entry.type == GX_INDEX8 || entry.type == GX_INDEX16) {
|
|
return;
|
|
}
|
|
|
|
const ActiveAttrFmt& fmt = entry.fmt;
|
|
switch (fmt.type) {
|
|
case GX_RGB565: {
|
|
const u16 packed = static_cast<u16>((quantize_unorm(r, 5) << 11) | (quantize_unorm(g, 6) << 5) |
|
|
quantize_unorm(b, 5));
|
|
GX_WRITE_U16(packed);
|
|
break;
|
|
}
|
|
case GX_RGB8:
|
|
GX_WRITE_U8(r);
|
|
GX_WRITE_U8(g);
|
|
GX_WRITE_U8(b);
|
|
break;
|
|
case GX_RGBX8:
|
|
GX_WRITE_U8(r);
|
|
GX_WRITE_U8(g);
|
|
GX_WRITE_U8(b);
|
|
GX_WRITE_U8(0xFF);
|
|
break;
|
|
case GX_RGBA4: {
|
|
const u16 packed = static_cast<u16>((quantize_unorm(r, 4) << 12) | (quantize_unorm(g, 4) << 8) |
|
|
(quantize_unorm(b, 4) << 4) | quantize_unorm(a, 4));
|
|
GX_WRITE_U16(packed);
|
|
break;
|
|
}
|
|
case GX_RGBA6: {
|
|
const u32 packed = (quantize_unorm(r, 6) << 18) | (quantize_unorm(g, 6) << 12) |
|
|
(quantize_unorm(b, 6) << 6) | quantize_unorm(a, 6);
|
|
GX_WRITE_U8(static_cast<u8>((packed >> 16) & 0xFF));
|
|
GX_WRITE_U8(static_cast<u8>((packed >> 8) & 0xFF));
|
|
GX_WRITE_U8(static_cast<u8>(packed & 0xFF));
|
|
break;
|
|
}
|
|
case GX_RGBA8:
|
|
default:
|
|
GX_WRITE_U8(r);
|
|
GX_WRITE_U8(g);
|
|
GX_WRITE_U8(b);
|
|
GX_WRITE_U8(a);
|
|
break;
|
|
}
|
|
advance_attr(attr, 1);
|
|
}
|
|
|
|
template <size_t N>
|
|
static void write_tex_attr(const std::array<Scalar, N>& values) {
|
|
ensure_vtx_cache(sBeginVtxFmt);
|
|
const GXAttr attr = select_implicit_attr(GX_VA_TEX0, GX_VA_TEX7);
|
|
write_numeric_attr(attr, values);
|
|
}
|
|
|
|
static void write_index_attr(GXAttr attr, u16 index, bool index16) {
|
|
ensure_vtx_cache(sBeginVtxFmt);
|
|
if (attr == GX_VA_TEX0) {
|
|
attr = select_implicit_attr(GX_VA_TEX0, GX_VA_TEX7);
|
|
} else if (attr == GX_VA_CLR0) {
|
|
attr = select_implicit_attr(GX_VA_CLR0, GX_VA_CLR1);
|
|
} else {
|
|
align_explicit_attr(attr);
|
|
}
|
|
|
|
const GXAttrType type = attr_cache(attr).type;
|
|
if (type == GX_INDEX16 || (type != GX_INDEX8 && index16)) {
|
|
GX_WRITE_U16(index);
|
|
} else {
|
|
GX_WRITE_U8(static_cast<u8>(index));
|
|
}
|
|
advance_attr(attr, 1);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
extern "C" {
|
|
|
|
void GXBegin(GXPrimitive primitive, GXVtxFmt vtxFmt, u16 nVerts) {
|
|
CHECK(!sInBegin, "GXBegin: called without matching GXEnd");
|
|
|
|
// Flush dirty state before starting a draw
|
|
if (__gx->dirtyState != 0) {
|
|
__GXSetDirtyState();
|
|
}
|
|
|
|
// Flush pending primitives if needed
|
|
if (*reinterpret_cast<u32*>(&__gx->vNum) != 0) {
|
|
__GXSendFlushPrim();
|
|
}
|
|
|
|
GX_WRITE_U8(vtxFmt | primitive);
|
|
GX_WRITE_U16(nVerts);
|
|
|
|
// Record state for vertex count validation in GXEnd
|
|
sBeginNVerts = nVerts;
|
|
sBeginFifoSize = aurora::gx::fifo::get_buffer_size();
|
|
sBeginPrimitive = primitive;
|
|
sBeginVtxFmt = vtxFmt;
|
|
sInBegin = true;
|
|
ensure_vtx_cache(vtxFmt);
|
|
reset_attr_writer();
|
|
}
|
|
|
|
void GXEnd() {
|
|
if (sInBegin) {
|
|
u32 bytesWritten = aurora::gx::fifo::get_buffer_size() - sBeginFifoSize;
|
|
if (sBeginNVerts > 0 && bytesWritten > 0) {
|
|
u32 vtxSize = bytesWritten / sBeginNVerts;
|
|
u32 remainder = bytesWritten % sBeginNVerts;
|
|
if (remainder != 0) {
|
|
Log.warn("GXEnd: vertex data not evenly divisible: {} bytes for {} vertices prim={} fmt={} "
|
|
"vcdLo=0x{:08x} vcdHi=0x{:08x} vatA=0x{:08x} vatB=0x{:08x} vatC=0x{:08x}",
|
|
bytesWritten, sBeginNVerts, underlying(sBeginPrimitive), underlying(sBeginVtxFmt),
|
|
__gx->vcdLo, __gx->vcdHi, __gx->vatA[sBeginVtxFmt], __gx->vatB[sBeginVtxFmt],
|
|
__gx->vatC[sBeginVtxFmt]);
|
|
}
|
|
u32 actualVerts = (vtxSize > 0) ? bytesWritten / vtxSize : 0;
|
|
CHECK(actualVerts == sBeginNVerts,
|
|
"GXEnd: vertex count mismatch: GXBegin declared {} vertices ({}B each, {}B total) "
|
|
"but {} bytes were written ({} vertices)",
|
|
sBeginNVerts, vtxSize, sBeginNVerts * vtxSize, bytesWritten, actualVerts);
|
|
}
|
|
sInBegin = false;
|
|
sWriteAttr = GX_VA_NULL;
|
|
sWriteAttrComps = 0;
|
|
}
|
|
if (!aurora::gx::fifo::in_display_list()) {
|
|
aurora::gx::fifo::drain();
|
|
}
|
|
}
|
|
|
|
void GXPosition3f32(f32 x, f32 y, f32 z) {
|
|
write_numeric_attr(GX_VA_POS, std::array{scalar_f32(x), scalar_f32(y), scalar_f32(z)});
|
|
}
|
|
|
|
void GXPosition3u16(u16 x, u16 y, u16 z) {
|
|
write_numeric_attr(GX_VA_POS, std::array{scalar_u32(x), scalar_u32(y), scalar_u32(z)});
|
|
}
|
|
|
|
void GXPosition3s16(s16 x, s16 y, s16 z) {
|
|
write_numeric_attr(GX_VA_POS, std::array{scalar_s32(x), scalar_s32(y), scalar_s32(z)});
|
|
}
|
|
|
|
void GXPosition3u8(u8 x, u8 y, u8 z) {
|
|
write_numeric_attr(GX_VA_POS, std::array{scalar_u32(x), scalar_u32(y), scalar_u32(z)});
|
|
}
|
|
|
|
void GXPosition3s8(s8 x, s8 y, s8 z) {
|
|
write_numeric_attr(GX_VA_POS, std::array{scalar_s32(x), scalar_s32(y), scalar_s32(z)});
|
|
}
|
|
|
|
void GXPosition2f32(f32 x, f32 y) {
|
|
write_numeric_attr(GX_VA_POS, std::array{scalar_f32(x), scalar_f32(y)});
|
|
}
|
|
|
|
void GXPosition2u16(u16 x, u16 y) {
|
|
write_numeric_attr(GX_VA_POS, std::array{scalar_u32(x), scalar_u32(y)});
|
|
}
|
|
|
|
void GXPosition2s16(s16 x, s16 y) {
|
|
write_numeric_attr(GX_VA_POS, std::array{scalar_s32(x), scalar_s32(y)});
|
|
}
|
|
|
|
void GXPosition2u8(u8 x, u8 y) {
|
|
write_numeric_attr(GX_VA_POS, std::array{scalar_u32(x), scalar_u32(y)});
|
|
}
|
|
|
|
void GXPosition2s8(s8 x, s8 y) {
|
|
write_numeric_attr(GX_VA_POS, std::array{scalar_s32(x), scalar_s32(y)});
|
|
}
|
|
|
|
void GXPosition1x16(u16 idx) { write_index_attr(GX_VA_POS, idx, true); }
|
|
|
|
void GXPosition1x8(u8 idx) { write_index_attr(GX_VA_POS, idx, false); }
|
|
|
|
void GXNormal3f32(f32 x, f32 y, f32 z) {
|
|
write_numeric_attr(GX_VA_NRM, std::array{scalar_f32(x), scalar_f32(y), scalar_f32(z)});
|
|
}
|
|
|
|
void GXNormal3u16(u16 x, u16 y, u16 z) {
|
|
write_numeric_attr(GX_VA_NRM, std::array{scalar_u32(x), scalar_u32(y), scalar_u32(z)});
|
|
}
|
|
|
|
void GXNormal3s16(s16 x, s16 y, s16 z) {
|
|
write_numeric_attr(GX_VA_NRM, std::array{scalar_s32(x), scalar_s32(y), scalar_s32(z)});
|
|
}
|
|
|
|
void GXNormal3u8(u8 x, u8 y, u8 z) {
|
|
write_numeric_attr(GX_VA_NRM, std::array{scalar_u32(x), scalar_u32(y), scalar_u32(z)});
|
|
}
|
|
|
|
void GXNormal3s8(s8 x, s8 y, s8 z) {
|
|
write_numeric_attr(GX_VA_NRM, std::array{scalar_s32(x), scalar_s32(y), scalar_s32(z)});
|
|
}
|
|
|
|
void GXNormal1x16(u16 index) { write_index_attr(GX_VA_NRM, index, true); }
|
|
|
|
void GXNormal1x8(u8 index) { write_index_attr(GX_VA_NRM, index, false); }
|
|
|
|
void GXColor4f32(f32 r, f32 g, f32 b, f32 a) {
|
|
write_color_attr(clamp_color_float(r), clamp_color_float(g), clamp_color_float(b), clamp_color_float(a));
|
|
}
|
|
|
|
void GXColor4u8(u8 r, u8 g, u8 b, u8 a) {
|
|
write_color_attr(r, g, b, a);
|
|
}
|
|
|
|
void GXColor3u8(u8 r, u8 g, u8 b) {
|
|
write_color_attr(r, g, b, 0xFF);
|
|
}
|
|
|
|
void GXColor1u32(u32 clr) {
|
|
write_color_attr(static_cast<u8>((clr >> 24) & 0xFF), static_cast<u8>((clr >> 16) & 0xFF),
|
|
static_cast<u8>((clr >> 8) & 0xFF), static_cast<u8>(clr & 0xFF));
|
|
}
|
|
|
|
void GXColor1u16(u16 clr) {
|
|
const u8 r = static_cast<u8>(((clr >> 11) & 0x1F) * 255 / 31);
|
|
const u8 g = static_cast<u8>(((clr >> 5) & 0x3F) * 255 / 63);
|
|
const u8 b = static_cast<u8>((clr & 0x1F) * 255 / 31);
|
|
write_color_attr(r, g, b, 0xFF);
|
|
}
|
|
|
|
void GXColor1x16(u16 index) { write_index_attr(GX_VA_CLR0, index, true); }
|
|
|
|
void GXColor1x8(u8 index) { write_index_attr(GX_VA_CLR0, index, false); }
|
|
|
|
void GXTexCoord2f32(f32 s, f32 t) {
|
|
write_tex_attr(std::array{scalar_f32(s), scalar_f32(t)});
|
|
}
|
|
|
|
void GXTexCoord2u16(u16 s, u16 t) {
|
|
write_tex_attr(std::array{scalar_u32(s), scalar_u32(t)});
|
|
}
|
|
|
|
void GXTexCoord2s16(s16 s, s16 t) {
|
|
write_tex_attr(std::array{scalar_s32(s), scalar_s32(t)});
|
|
}
|
|
|
|
void GXTexCoord2u8(u8 s, u8 t) {
|
|
write_tex_attr(std::array{scalar_u32(s), scalar_u32(t)});
|
|
}
|
|
|
|
void GXTexCoord2s8(s8 s, s8 t) {
|
|
write_tex_attr(std::array{scalar_s32(s), scalar_s32(t)});
|
|
}
|
|
|
|
void GXTexCoord1f32(f32 s) { write_tex_attr(std::array{scalar_f32(s)}); }
|
|
|
|
void GXTexCoord1u16(u16 s) { write_tex_attr(std::array{scalar_u32(s)}); }
|
|
|
|
void GXTexCoord1s16(s16 s) { write_tex_attr(std::array{scalar_s32(s)}); }
|
|
|
|
void GXTexCoord1u8(u8 s) { write_tex_attr(std::array{scalar_u32(s)}); }
|
|
|
|
void GXTexCoord1s8(s8 s) { write_tex_attr(std::array{scalar_s32(s)}); }
|
|
|
|
void GXTexCoord1x16(u16 index) { write_index_attr(GX_VA_TEX0, index, true); }
|
|
|
|
void GXTexCoord1x8(u8 index) { write_index_attr(GX_VA_TEX0, index, false); }
|
|
|
|
void GXMatrixIndex1u8(GXAttr attr, u8 index) { write_index_attr(attr, index, false); }
|
|
|
|
void GXCmd1u8(const u8 x) { aurora::gx::fifo::write_u8(x); }
|
|
void GXCmd1u16(const u16 x) { aurora::gx::fifo::write_u16(x); }
|
|
void GXCmd1u32(const u32 x) { aurora::gx::fifo::write_u32(x); }
|
|
void GXCmd1u64(const u64 x) { aurora::gx::fifo::write_u64(x); }
|
|
|
|
void GXParam1u8(const u8 x) { aurora::gx::fifo::write_u8(x); }
|
|
void GXParam1u16(const u16 x) { aurora::gx::fifo::write_u16(x); }
|
|
void GXParam1u32(const u32 x) { aurora::gx::fifo::write_u32(x); }
|
|
void GXParam1s8(const s8 x) { aurora::gx::fifo::write_u8(static_cast<uint8_t>(x)); }
|
|
void GXParam1s16(const s16 x) { aurora::gx::fifo::write_u16(static_cast<uint16_t>(x)); }
|
|
void GXParam1s32(const s32 x) { aurora::gx::fifo::write_u32(static_cast<uint32_t>(x)); }
|
|
void GXParam1f32(const f32 x) { aurora::gx::fifo::write_f32(x); }
|
|
void GXParam3f32(const f32 x, const f32 y, const f32 z) {
|
|
aurora::gx::fifo::write_f32(x);
|
|
aurora::gx::fifo::write_f32(y);
|
|
aurora::gx::fifo::write_f32(z);
|
|
}
|
|
void GXParam4f32(const f32 x, const f32 y, const f32 z, const f32 w) {
|
|
aurora::gx::fifo::write_f32(x);
|
|
aurora::gx::fifo::write_f32(y);
|
|
aurora::gx::fifo::write_f32(z);
|
|
aurora::gx::fifo::write_f32(w);
|
|
}
|
|
|
|
} // extern "C"
|