#include "aurora/gx_dl.hpp" #include #include #include "../internal.hpp" #include "gx.hpp" #include namespace aurora::gx::dl { static Module Log("aurora::gx::dl"); static bool is_mtx_idx_attr(GXAttr attr) { return attr >= GX_VA_PNMTXIDX && attr <= GX_VA_TEX7MTXIDX; } static bool is_draw_opcode(u8 opcode) { return opcode == GX_QUADS || opcode == GX_TRIANGLES || opcode == GX_TRIANGLESTRIP || opcode == GX_TRIANGLEFAN || opcode == GX_LINES || opcode == GX_LINESTRIP || opcode == GX_POINTS; } static u16 read_be16(const u8* data) { return static_cast(data[0]) << 8 | data[1]; } static u32 read_be32(const u8* data) { return static_cast(data[0]) << 24 | static_cast(data[1]) << 16 | static_cast(data[2]) << 8 | data[3]; } static const GXVtxAttrFmtList* find_attr_fmt(const GXVtxAttrFmtList* list, GXAttr attr) { if (list == nullptr) { return nullptr; } for (; list->attr != GX_VA_NULL; ++list) { if (list->attr == attr) { return list; } } return nullptr; } static std::optional compute_layout(const GXVtxDescList* desc, const GXVtxAttrFmtList* fmt) { VtxLayout out{}; u32 stride = 0; for (; desc->attr != GX_VA_NULL; ++desc) { if (desc->attr >= GX_VA_MAX_ATTR) { return std::nullopt; } u32 size = 0; switch (desc->type) { case GX_NONE: continue; case GX_DIRECT: { if (is_mtx_idx_attr(desc->attr)) { size = 1; break; } const auto* attrFmt = find_attr_fmt(fmt, desc->attr); if (attrFmt == nullptr) { return std::nullopt; } size = comp_type_size(desc->attr, attrFmt->type) * comp_cnt_count(desc->attr, attrFmt->cnt); break; } case GX_INDEX8: size = 1; break; case GX_INDEX16: size = 2; break; default: return std::nullopt; } if (desc->attr == GX_VA_NRM && (desc->type == GX_INDEX8 || desc->type == GX_INDEX16)) { // GX_NRM_NBT3 normals are three separate indices const auto* attrFmt = find_attr_fmt(fmt, GX_VA_NRM); if (attrFmt != nullptr && attrFmt->cnt == GX_NRM_NBT3) { size *= 3; } } auto& attr = out.attrs[desc->attr]; attr.offset = static_cast(stride); attr.size = static_cast(size); attr.type = desc->type; stride += size; if (stride > 0xFF) { return std::nullopt; } } if (stride == 0) { return std::nullopt; } out.stride = static_cast(stride); return out; } u16 DrawCmd::attr_idx(u32 vtxIdx, GXAttr attr) const { const auto& a = layout->attrs[attr]; const u8* ptr = vertices + vtxIdx * layout->stride + a.offset; switch (a.type) { case GX_INDEX8: return ptr[0]; case GX_INDEX16: return read_be16(ptr); case GX_DIRECT: // matrix indices only return ptr[0]; default: return 0; } } u16 DrawCmd::index(u32 i) const { u16 value; std::memcpy(&value, indices + i * sizeof(u16), sizeof(u16)); return value; } Reader::Reader(const u8* dl, u32 size, const GXVtxDescList* desc, const VtxFmtLists* fmts) : mData(dl), mSize(size), mDesc(desc), mFmts(fmts) {} Reader::Reader(const u8* dl, u32 size, u8 stride) : mData(dl), mSize(size) { VtxLayout layout{}; layout.stride = stride; for (u32 fmt = 0; fmt < GX_MAX_VTXFMT; ++fmt) { mLayouts[fmt] = layout; mLayoutComputed[fmt] = true; } } const VtxLayout* Reader::layout(GXVtxFmt fmt) { if (!mLayoutComputed[fmt]) { mLayoutComputed[fmt] = true; if (mDesc != nullptr) { mLayouts[fmt] = compute_layout(mDesc, mFmts != nullptr ? (*mFmts)[fmt] : nullptr); } } return mLayouts[fmt].has_value() ? &*mLayouts[fmt] : nullptr; } std::optional Reader::next() { if (mFailed || mPos >= mSize) { return std::nullopt; } const u32 start = mPos; const u8 cmd = mData[start]; const u8 opcode = cmd & GX_OPCODE_MASK; const auto fail = [&](const char* what) -> std::optional { Log.warn("Reader: {} (opcode 0x{:02X} at offset {})", what, cmd, start); mFailed = true; return std::nullopt; }; const auto passthrough = [&](u32 cmdSize) -> std::optional { if (start + cmdSize > mSize) { return fail("command overrun"); } mPos = start + cmdSize; return Command{Command::Kind::Passthrough, mData + start, cmdSize, {}}; }; switch (opcode) { case GX_NOP: case GX_CMD_INVL_VC: return passthrough(1); case GX_LOAD_BP_REG & GX_OPCODE_MASK: return passthrough(5); case GX_LOAD_CP_REG: return passthrough(6); case GX_LOAD_XF_REG: { if (start + 5 > mSize) { return fail("XF load overrun"); } const u32 count = read_be16(mData + start + 1) + 1; return passthrough(5 + count * 4); } case GX_LOAD_INDX_A: case GX_LOAD_INDX_B: case GX_LOAD_INDX_C: case GX_LOAD_INDX_D: return passthrough(5); case GX_CMD_CALL_DL: return passthrough(9); case GX_LOAD_AURORA: { if (start + 3 > mSize) { return fail("Aurora subcommand overrun"); } if (read_be16(mData + start + 1) != GX_LOAD_AURORA_DRAW_INDEXED) { return fail("unsupported Aurora subcommand"); } if (start + 10 > mSize) { return fail("DRAW_INDEXED header overrun"); } const u8 drawCmd = mData[start + 3]; const auto fmt = static_cast(drawCmd & GX_VAT_MASK); const u16 vtxCount = read_be16(mData + start + 4); const u32 indexCount = read_be32(mData + start + 6); const VtxLayout* lo = layout(fmt); if (lo == nullptr) { return fail("no layout for DRAW_INDEXED vertex format"); } const u32 idxBytes = indexCount * sizeof(u16); const u32 cmdSize = 10 + idxBytes + vtxCount * lo->stride; if (start + cmdSize > mSize) { return fail("DRAW_INDEXED data overrun"); } mPos = start + cmdSize; return Command{ Command::Kind::DrawIndexed, mData + start, cmdSize, DrawCmd{ static_cast(drawCmd & GX_OPCODE_MASK), fmt, vtxCount, mData + start + 10 + idxBytes, lo, mData + start + 10, indexCount, }, }; } default: { if (!is_draw_opcode(opcode)) { return fail("unknown opcode"); } if (start + 3 > mSize) { return fail("draw header overrun"); } const auto fmt = static_cast(cmd & GX_VAT_MASK); const u16 vtxCount = read_be16(mData + start + 1); const VtxLayout* lo = layout(fmt); if (lo == nullptr) { return fail("no layout for draw vertex format"); } const u32 cmdSize = 3 + vtxCount * lo->stride; if (start + cmdSize > mSize) { return fail("draw data overrun"); } mPos = start + cmdSize; return Command{ Command::Kind::Draw, mData + start, cmdSize, DrawCmd{ static_cast(opcode), fmt, vtxCount, mData + start + 3, lo, nullptr, 0, }, }; } } } namespace { struct MergeRun { GXVtxFmt fmt = GX_VTXFMT0; u16 vtxCount = 0; bool allTriangles = true; std::vector verts; std::vector indices; }; void append_be16(std::vector& out, u16 value) { out.push_back(value >> 8); out.push_back(value & 0xFF); } void append_be32(std::vector& out, u32 value) { out.push_back(value >> 24); out.push_back(value >> 16 & 0xFF); out.push_back(value >> 8 & 0xFF); out.push_back(value & 0xFF); } void flush_run(std::vector& out, MergeRun& run) { if (run.vtxCount != 0) { if (run.allTriangles) { // Identity index list: a plain triangle draw is drawn non-indexed at runtime out.push_back(static_cast(GX_TRIANGLES) | static_cast(run.fmt)); append_be16(out, run.vtxCount); } else { out.push_back(GX_LOAD_AURORA); append_be16(out, GX_LOAD_AURORA_DRAW_INDEXED); out.push_back(static_cast(GX_TRIANGLES) | static_cast(run.fmt)); append_be16(out, run.vtxCount); append_be32(out, static_cast(run.indices.size())); // Index data is host-endian by definition; see GX_LOAD_AURORA_DRAW_INDEXED const auto* idxData = reinterpret_cast(run.indices.data()); out.insert(out.end(), idxData, idxData + run.indices.size() * sizeof(u16)); } out.insert(out.end(), run.verts.begin(), run.verts.end()); } run.vtxCount = 0; run.allTriangles = true; run.verts.clear(); run.indices.clear(); } } // namespace std::optional> optimize(const u8* dl, u32 size, const GXVtxDescList* desc, const VtxFmtLists* fmts) { Reader reader{dl, size, desc, fmts}; std::vector out; out.reserve(size); MergeRun run; while (const auto cmd = reader.next()) { const auto copy_verbatim = [&] { flush_run(out, run); out.insert(out.end(), cmd->data, cmd->data + cmd->size); }; switch (cmd->kind) { case Command::Kind::Passthrough: if (cmd->data[0] != GX_NOP) { copy_verbatim(); } break; case Command::Kind::DrawIndexed: // Already optimized copy_verbatim(); break; case Command::Kind::Draw: { const auto& draw = cmd->draw; if (run.vtxCount != 0 && (run.fmt != draw.fmt || static_cast(run.vtxCount) + draw.vtxCount > 0xFFFF)) { flush_run(out, run); } const u16 base = run.vtxCount; const bool expanded = expand_triangles(draw.prim, draw.vtxCount, [&](u16 i0, u16 i1, u16 i2) { run.indices.push_back(base + i0); run.indices.push_back(base + i1); run.indices.push_back(base + i2); }); if (!expanded) { // Lines/points or degenerate counts; expand_triangles validates before emitting copy_verbatim(); break; } if (run.vtxCount == 0) { run.fmt = draw.fmt; } if (draw.prim != GX_TRIANGLES) { run.allTriangles = false; } run.verts.insert(run.verts.end(), draw.vertices, draw.vertices + draw.vtxCount * draw.layout->stride); run.vtxCount += draw.vtxCount; break; } } } if (reader.failed()) { return std::nullopt; } flush_run(out, run); return out; } } // namespace aurora::gx::dl