mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-12 01:38:50 -04:00
792 lines
29 KiB
C++
792 lines
29 KiB
C++
#include "gx_internal.h"
|
|
#include "gx_stream_common.h"
|
|
#include "gx_cp_decode.h"
|
|
#include "isa/big_endian.h"
|
|
|
|
// Opcode constants and the stream helpers this file shares with gx_dl.cpp /
|
|
// gx_vertex.cpp; see gx_stream_common.h.
|
|
using namespace GxCmd;
|
|
using namespace GxStream;
|
|
|
|
HleGxState g_hleGxState;
|
|
|
|
namespace aurora::gx::fifo {
|
|
bool submit_raw_draw(GXPrimitive prim, GXVtxFmt fmt, const uint8_t* vertices, uint16_t vtxCount,
|
|
uint32_t vertexBytes);
|
|
}
|
|
|
|
void HleGxState::ResetVertex() {
|
|
currentAttr = NextEnabledAttr(GX_VA_PNMTXIDX - 1);
|
|
currentComp = 0;
|
|
}
|
|
|
|
GXAttr HleGxState::NextEnabledAttr(int startAttr) {
|
|
for (int i = startAttr + 1; i < 26; ++i) {
|
|
if (vtxDesc[i] != GX_NONE) {
|
|
return static_cast<GXAttr>(i);
|
|
}
|
|
}
|
|
return GX_VA_NULL;
|
|
}
|
|
|
|
int HleGxState::GetExpectedCompCount(GXAttr attr, const VtxAttrFmt& fmt) {
|
|
// Attributes with no component layout (matrix indices, XF arrays, GX_VA_NBT)
|
|
// count as one component here: the incremental parser advances one element
|
|
// per raw stream item for them.
|
|
return static_cast<int>(AttrCompCount(attr, fmt, 1u));
|
|
}
|
|
|
|
static void ApplyAuroraVtxStateForRawBegin(GXVtxFmt fmt) {
|
|
// KNOWN DIVERGENCE: this is the only publish site with includeNbt=true, so it also
|
|
// publishes GX_VA_NBT after GX_VA_NRM, which makes aurora's SETVAT fall-through clobber the
|
|
// NRM VAT with the default. Raw-FIFO geometry renders correctly with it; do not "fix" without
|
|
// an in-race A/B run.
|
|
PublishAuroraVtxState(fmt, AuroraVtxPublishOptions{/*includeNbt=*/true,
|
|
/*fmtLoopFirst=*/0,
|
|
/*fmtLoopLast=*/25});
|
|
}
|
|
|
|
// There is deliberately no indexed-aurora path here. Immediate-mode indexed
|
|
// draws are parsed incrementally, and aurora's indexed-array upload has to
|
|
// precede the draw carrying the max-index bounds, which cannot be known without
|
|
// buffering the whole primitive. Indexed attributes are therefore expanded into
|
|
// the packed direct stream above (GX_INDEX8/16 -> GX_DIRECT).
|
|
|
|
static uint32_t GetDirectAttrByteSizeForFifo(GXAttr attr, const VtxAttrFmt& fmt) {
|
|
return DirectAttrByteSize(attr, fmt, /*matrixAttrIsOneByte=*/true, /*fallbackComps=*/1u);
|
|
}
|
|
|
|
static bool TryGetRawDirectFifoVertexSize(GXVtxFmt fmt, uint32_t& vertexSize) {
|
|
vertexSize = 0;
|
|
if (fmt >= GX_MAX_VTXFMT) {
|
|
return false;
|
|
}
|
|
|
|
for (int attr = 0; attr < 26; ++attr) {
|
|
const GXAttrType type = g_hleGxState.vtxDesc[attr];
|
|
if (type == GX_NONE) {
|
|
continue;
|
|
}
|
|
if (type != GX_DIRECT) {
|
|
return false;
|
|
}
|
|
|
|
const GXAttr gxAttr = static_cast<GXAttr>(attr);
|
|
const uint32_t attrBytes =
|
|
GetDirectAttrByteSizeForFifo(gxAttr, g_hleGxState.vtxAttrFmt[fmt][attr]);
|
|
if (attrBytes == 0) {
|
|
return false;
|
|
}
|
|
vertexSize += attrBytes;
|
|
}
|
|
|
|
return vertexSize != 0;
|
|
}
|
|
|
|
static bool TrySubmitRawDirectFifoDraw(const uint8_t* packet, uint32_t packetBytes, GXPrimitive prim,
|
|
GXVtxFmt vtxFmt, uint16_t vtxCount) {
|
|
if (packet == nullptr || packetBytes == 0 || vtxCount == 0) {
|
|
return false;
|
|
}
|
|
|
|
EnsureAuroraFrameActive();
|
|
|
|
g_hleGxState.currentVtxFmt = vtxFmt;
|
|
g_hleGxState.currentPrim = prim;
|
|
g_hleGxState.vertsRemaining = vtxCount;
|
|
g_hleGxState.inBegin = false;
|
|
g_hleGxState.auroraBeginCalled = false;
|
|
g_hleGxState.ResetVertex();
|
|
|
|
ApplyAuroraVtxStateForRawBegin(vtxFmt);
|
|
EnsureDefaultGxAlphaCompare();
|
|
|
|
if (!aurora::gx::fifo::submit_raw_draw(prim, vtxFmt, packet + 3, vtxCount, packetBytes - 3u)) {
|
|
return false;
|
|
}
|
|
GXMarkFrameWork();
|
|
return true;
|
|
}
|
|
|
|
static void DecodeColorFromArray(uint32_t addr, GXCompType type, GXCompCnt cnt, GXColor& out) {
|
|
// Gather exactly the bytes the shared decoder consumes. GX_RGBX8 is the one
|
|
// format whose stream footprint (4 bytes) exceeds what is decoded (3), and
|
|
// the pre-dedup code read only those 3 from the guest array - keep it that
|
|
// way so a 3-byte tail at the end of an array cannot start faulting.
|
|
const uint32_t byteCount =
|
|
(type == GX_RGBX8) ? 3u : ColorByteSize(type, cnt);
|
|
uint8_t bytes[4] = {0, 0, 0, 0};
|
|
for (uint32_t i = 0; i < byteCount && i < 4u; ++i) {
|
|
bytes[i] = Memory::Read8(addr + i);
|
|
}
|
|
DecodeColorBytes(bytes, type, cnt, out);
|
|
}
|
|
|
|
void SubmitIndexedAttribute(GXAttr attr, uint32_t index) {
|
|
const auto& fmt = g_hleGxState.vtxAttrFmt[g_hleGxState.currentVtxFmt][attr];
|
|
const auto& arr = g_hleGxState.vtxArray[attr];
|
|
if (arr.base == 0 || arr.stride == 0) {
|
|
float comps[9]{};
|
|
switch (attr) {
|
|
case GX_VA_CLR0:
|
|
case GX_VA_CLR1:
|
|
if (fmt.cnt == GX_CLR_RGB) {
|
|
GXColor3u8(0, 0, 0);
|
|
} else {
|
|
GXColor4u8(0, 0, 0, 0xFF);
|
|
}
|
|
break;
|
|
case GX_VA_POS:
|
|
case GX_VA_NRM:
|
|
case GX_VA_TEX0:
|
|
case GX_VA_TEX1:
|
|
case GX_VA_TEX2:
|
|
case GX_VA_TEX3:
|
|
case GX_VA_TEX4:
|
|
case GX_VA_TEX5:
|
|
case GX_VA_TEX6:
|
|
case GX_VA_TEX7:
|
|
SubmitAttribute(attr, comps, fmt);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
const uint32_t baseAddr = arr.base + index * arr.stride;
|
|
float comps[9]{};
|
|
u32 rawComps[9]{};
|
|
switch (attr) {
|
|
case GX_VA_POS: {
|
|
const int count = static_cast<int>(AttrCompCount(GX_VA_POS, fmt, 0u));
|
|
const int step = GetCompSizeBytes(fmt.type);
|
|
uint32_t addr = baseAddr;
|
|
for (int i = 0; i < count; ++i, addr += step) {
|
|
comps[i] = ReadArrayComp(addr, fmt.type, fmt.frac);
|
|
rawComps[i] = ReadArrayRawComp(addr, fmt.type);
|
|
}
|
|
SubmitAttribute(attr, comps, fmt, rawComps);
|
|
break;
|
|
}
|
|
case GX_VA_NRM: {
|
|
const int count = static_cast<int>(AttrCompCount(GX_VA_NRM, fmt, 0u));
|
|
const int step = GetCompSizeBytes(fmt.type);
|
|
uint32_t addr = baseAddr;
|
|
for (int i = 0; i < count; ++i, addr += step) {
|
|
comps[i] = ReadArrayComp(addr, fmt.type, fmt.frac);
|
|
rawComps[i] = ReadArrayRawComp(addr, fmt.type);
|
|
}
|
|
SubmitAttribute(attr, comps, fmt, rawComps);
|
|
break;
|
|
}
|
|
case GX_VA_CLR0:
|
|
case GX_VA_CLR1: {
|
|
GXColor color{};
|
|
DecodeColorFromArray(baseAddr, fmt.type, fmt.cnt, color);
|
|
if (fmt.cnt == GX_CLR_RGB) {
|
|
GXColor3u8(color.r, color.g, color.b);
|
|
} else {
|
|
GXColor4u8(color.r, color.g, color.b, color.a);
|
|
}
|
|
break;
|
|
}
|
|
case GX_VA_TEX0: case GX_VA_TEX1: case GX_VA_TEX2: case GX_VA_TEX3:
|
|
case GX_VA_TEX4: case GX_VA_TEX5: case GX_VA_TEX6: case GX_VA_TEX7: {
|
|
// Every GX_VA_TEXn shares one component layout, so the constant here is
|
|
// exact for whichever of them `attr` is.
|
|
const int count = static_cast<int>(AttrCompCount(GX_VA_TEX0, fmt, 0u));
|
|
const int step = GetCompSizeBytes(fmt.type);
|
|
uint32_t addr = baseAddr;
|
|
for (int i = 0; i < count; ++i, addr += step) {
|
|
comps[i] = ReadArrayComp(addr, fmt.type, fmt.frac);
|
|
rawComps[i] = ReadArrayRawComp(addr, fmt.type);
|
|
}
|
|
SubmitAttribute(attr, comps, fmt, rawComps);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Mirrors the raw-integer fast paths in SubmitAttribute below: for those
|
|
// (attr, type) pairs the float component buffer is never read, so the direct
|
|
// FIFO path can skip building it.
|
|
static bool SubmitAttributeReadsFloatComps(GXAttr attr, const VtxAttrFmt& fmt) {
|
|
switch (attr) {
|
|
case GX_VA_POS:
|
|
case GX_VA_NRM:
|
|
case GX_VA_TEX0: case GX_VA_TEX1: case GX_VA_TEX2: case GX_VA_TEX3:
|
|
case GX_VA_TEX4: case GX_VA_TEX5: case GX_VA_TEX6: case GX_VA_TEX7:
|
|
break;
|
|
default:
|
|
return true;
|
|
}
|
|
switch (fmt.type) {
|
|
case GX_U8:
|
|
case GX_S8:
|
|
case GX_U16:
|
|
case GX_S16:
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void SubmitAttribute(GXAttr attr, float* comps, const VtxAttrFmt& fmt, const u32* rawComps) {
|
|
auto r8 = [rawComps](int idx) { return static_cast<u8>(rawComps[idx]); };
|
|
auto rs8 = [rawComps](int idx) { return static_cast<s8>(rawComps[idx]); };
|
|
auto r16 = [rawComps](int idx) { return static_cast<u16>(rawComps[idx]); };
|
|
auto rs16 = [rawComps](int idx) { return static_cast<s16>(rawComps[idx]); };
|
|
|
|
switch (attr) {
|
|
case GX_VA_POS:
|
|
if (rawComps) {
|
|
if (fmt.cnt == GX_POS_XY) {
|
|
switch (fmt.type) {
|
|
case GX_U8: GXPosition2u8(r8(0), r8(1)); return;
|
|
case GX_S8: GXPosition2s8(rs8(0), rs8(1)); return;
|
|
case GX_U16: GXPosition2u16(r16(0), r16(1)); return;
|
|
case GX_S16: GXPosition2s16(rs16(0), rs16(1)); return;
|
|
default: break;
|
|
}
|
|
} else {
|
|
switch (fmt.type) {
|
|
case GX_U8: GXPosition3u8(r8(0), r8(1), r8(2)); return;
|
|
case GX_S8: GXPosition3s8(rs8(0), rs8(1), rs8(2)); return;
|
|
case GX_U16: GXPosition3u16(r16(0), r16(1), r16(2)); return;
|
|
case GX_S16: GXPosition3s16(rs16(0), rs16(1), rs16(2)); return;
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
if (fmt.cnt == GX_POS_XY) {
|
|
GXPosition2f32(comps[0], comps[1]);
|
|
} else {
|
|
GXPosition3f32(comps[0], comps[1], comps[2]);
|
|
}
|
|
break;
|
|
case GX_VA_NRM:
|
|
if (rawComps) {
|
|
const bool nbt = fmt.cnt == GX_NRM_NBT || fmt.cnt == GX_NRM_NBT3;
|
|
const int groups = nbt ? 3 : 1;
|
|
switch (fmt.type) {
|
|
case GX_U8:
|
|
for (int i = 0; i < groups; ++i) {
|
|
GXNormal3u8(r8(i * 3), r8(i * 3 + 1), r8(i * 3 + 2));
|
|
}
|
|
return;
|
|
case GX_S8:
|
|
for (int i = 0; i < groups; ++i) {
|
|
GXNormal3s8(rs8(i * 3), rs8(i * 3 + 1), rs8(i * 3 + 2));
|
|
}
|
|
return;
|
|
case GX_U16:
|
|
for (int i = 0; i < groups; ++i) {
|
|
GXNormal3u16(r16(i * 3), r16(i * 3 + 1), r16(i * 3 + 2));
|
|
}
|
|
return;
|
|
case GX_S16:
|
|
for (int i = 0; i < groups; ++i) {
|
|
GXNormal3s16(rs16(i * 3), rs16(i * 3 + 1), rs16(i * 3 + 2));
|
|
}
|
|
return;
|
|
default: break;
|
|
}
|
|
}
|
|
if (fmt.cnt == GX_NRM_NBT || fmt.cnt == GX_NRM_NBT3) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
GXNormal3f32(comps[i * 3], comps[i * 3 + 1], comps[i * 3 + 2]);
|
|
}
|
|
} else {
|
|
GXNormal3f32(comps[0], comps[1], comps[2]);
|
|
}
|
|
break;
|
|
case GX_VA_CLR0:
|
|
case GX_VA_CLR1:
|
|
if (fmt.cnt == GX_CLR_RGB) {
|
|
GXColor3u8(static_cast<u8>(comps[0]), static_cast<u8>(comps[1]),
|
|
static_cast<u8>(comps[2]));
|
|
} else {
|
|
GXColor4u8(static_cast<u8>(comps[0]), static_cast<u8>(comps[1]),
|
|
static_cast<u8>(comps[2]), static_cast<u8>(comps[3]));
|
|
}
|
|
break;
|
|
case GX_VA_TEX0: case GX_VA_TEX1: case GX_VA_TEX2: case GX_VA_TEX3:
|
|
case GX_VA_TEX4: case GX_VA_TEX5: case GX_VA_TEX6: case GX_VA_TEX7:
|
|
if (rawComps) {
|
|
if (fmt.cnt == GX_TEX_S) {
|
|
switch (fmt.type) {
|
|
case GX_U8: GXTexCoord1u8(r8(0)); return;
|
|
case GX_S8: GXTexCoord1s8(rs8(0)); return;
|
|
case GX_U16: GXTexCoord1u16(r16(0)); return;
|
|
case GX_S16: GXTexCoord1s16(rs16(0)); return;
|
|
default: break;
|
|
}
|
|
} else {
|
|
switch (fmt.type) {
|
|
case GX_U8: GXTexCoord2u8(r8(0), r8(1)); return;
|
|
case GX_S8: GXTexCoord2s8(rs8(0), rs8(1)); return;
|
|
case GX_U16: GXTexCoord2u16(r16(0), r16(1)); return;
|
|
case GX_S16: GXTexCoord2s16(rs16(0), rs16(1)); return;
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
if (fmt.cnt == GX_TEX_S) {
|
|
GXTexCoord1f32(comps[0]);
|
|
} else {
|
|
GXTexCoord2f32(comps[0], comps[1]);
|
|
}
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
// `val` is a raw big-endian bit pattern: the FIFO stream is type-agnostic, and
|
|
// the float entry point converts before it gets here.
|
|
void HleFifoWrite(u32 val, uint32_t sizeBytes) {
|
|
const bool recordOnly = IsDisplayListActive();
|
|
if (recordOnly) {
|
|
WriteDisplayListData(val, sizeBytes);
|
|
return;
|
|
}
|
|
|
|
auto resetFifoBuffer = [&]() {
|
|
g_hleGxState.fifoReadOffset = 0;
|
|
g_hleGxState.fifoByteCount = 0;
|
|
};
|
|
|
|
auto compactFifoBuffer = [&]() {
|
|
if (g_hleGxState.fifoByteCount == 0) {
|
|
g_hleGxState.fifoReadOffset = 0;
|
|
return;
|
|
}
|
|
if (g_hleGxState.fifoReadOffset == 0) {
|
|
return;
|
|
}
|
|
std::memmove(g_hleGxState.fifoBytes.data(),
|
|
g_hleGxState.fifoBytes.data() + g_hleGxState.fifoReadOffset,
|
|
g_hleGxState.fifoByteCount);
|
|
g_hleGxState.fifoReadOffset = 0;
|
|
};
|
|
|
|
auto fifoData = [&]() -> uint8_t* {
|
|
return g_hleGxState.fifoBytes.data() + g_hleGxState.fifoReadOffset;
|
|
};
|
|
|
|
auto pushBytes = [&](u32 value, uint32_t count) {
|
|
if (count == 0) return;
|
|
if (count > 4) count = 4;
|
|
if (g_hleGxState.fifoReadOffset + g_hleGxState.fifoByteCount + count > g_hleGxState.fifoBytes.size()) {
|
|
compactFifoBuffer();
|
|
if (g_hleGxState.fifoReadOffset + g_hleGxState.fifoByteCount + count > g_hleGxState.fifoBytes.size()) {
|
|
resetFifoBuffer();
|
|
}
|
|
}
|
|
const size_t writeOffset = g_hleGxState.fifoReadOffset + g_hleGxState.fifoByteCount;
|
|
switch (count) {
|
|
case 4:
|
|
BigEndian::Write32(g_hleGxState.fifoBytes.data() + writeOffset, value);
|
|
break;
|
|
case 2:
|
|
BigEndian::Write16(g_hleGxState.fifoBytes.data() + writeOffset,
|
|
static_cast<uint16_t>(value));
|
|
break;
|
|
default:
|
|
g_hleGxState.fifoBytes[writeOffset] = static_cast<uint8_t>(value & 0xFF);
|
|
break;
|
|
}
|
|
g_hleGxState.fifoByteCount += count;
|
|
};
|
|
|
|
auto consumeBytes = [&](uint32_t count, u32& out) -> bool {
|
|
if (count == 0 || g_hleGxState.fifoByteCount < count) return false;
|
|
u32 value = 0;
|
|
const uint32_t foldCount = (count > 4) ? 4 : count;
|
|
uint8_t* data = fifoData();
|
|
for (uint32_t i = 0; i < foldCount; ++i) {
|
|
value = (value << 8) | data[i];
|
|
}
|
|
g_hleGxState.fifoReadOffset += count;
|
|
g_hleGxState.fifoByteCount -= count;
|
|
if (g_hleGxState.fifoByteCount == 0) {
|
|
g_hleGxState.fifoReadOffset = 0;
|
|
}
|
|
out = value;
|
|
return true;
|
|
};
|
|
|
|
pushBytes(val, sizeBytes == 0 ? 4 : sizeBytes);
|
|
|
|
// Parse raw FIFO command packets written directly to the gather pipe
|
|
// (e.g. NW4R/G3D paths that do not call the GXBegin wrapper function).
|
|
while (!g_hleGxState.inBegin) {
|
|
if (g_hleGxState.fifoByteCount < 1) {
|
|
break;
|
|
}
|
|
|
|
uint8_t* data = fifoData();
|
|
const uint8_t cmd = data[0];
|
|
const uint8_t opcode = cmd & GX_OPCODE_MASK_CMD;
|
|
u32 sink = 0;
|
|
|
|
if (cmd == GX_NOP_CMD || opcode == GX_CMD_INVL_VC_CMD) {
|
|
if (!consumeBytes(1, sink)) break;
|
|
continue;
|
|
}
|
|
|
|
if (cmd == GX_LOAD_BP_REG_CMD) {
|
|
if (g_hleGxState.fifoByteCount < 5) break;
|
|
const uint32_t bpWord = ReadBE32(data + 1);
|
|
GXApplyBPReg(static_cast<uint8_t>(bpWord >> 24), bpWord & 0x00FFFFFFu);
|
|
if (!consumeBytes(5, sink)) break;
|
|
continue;
|
|
}
|
|
|
|
if (opcode == GX_LOAD_CP_REG_CMD) {
|
|
if (g_hleGxState.fifoByteCount < 6) break;
|
|
const uint8_t reg = data[1];
|
|
const uint32_t cpValue = ReadBE32(data + 2);
|
|
GxCpDecode::ApplyCpRegWrite(reg, cpValue);
|
|
if (!consumeBytes(6, sink)) break;
|
|
continue;
|
|
}
|
|
|
|
if (opcode == GX_LOAD_XF_REG_CMD) {
|
|
if (g_hleGxState.fifoByteCount < 5) break;
|
|
const uint16_t countWords = ReadBE16(data + 1);
|
|
const uint32_t packetBytes = 1u + 4u + (static_cast<uint32_t>(countWords) + 1u) * 4u;
|
|
if (g_hleGxState.fifoByteCount < packetBytes) break;
|
|
GXCallDisplayList(data, packetBytes);
|
|
GXMarkFrameWork();
|
|
if (!consumeBytes(packetBytes, sink)) break;
|
|
continue;
|
|
}
|
|
|
|
if (opcode >= GX_LOAD_INDX_A_CMD && opcode <= GX_LOAD_INDX_D_CMD) {
|
|
if (g_hleGxState.fifoByteCount < 5) break;
|
|
const uint32_t xfValue = ReadBE32(data + 1);
|
|
ApplyIndexedXfArrayForPacket(cmd, xfValue);
|
|
GXCallDisplayList(data, 5);
|
|
GXMarkFrameWork();
|
|
if (!consumeBytes(5, sink)) break;
|
|
continue;
|
|
}
|
|
|
|
if (opcode == GX_CMD_CALL_DL_CMD) {
|
|
if (g_hleGxState.fifoByteCount < 9) break;
|
|
const uint32_t listAddr = ReadBE32(data + 1);
|
|
const uint32_t listSize = ReadBE32(data + 5);
|
|
if (!consumeBytes(9, sink)) break;
|
|
if (listAddr != 0 && listSize > 0) {
|
|
GX__CallDisplayList_80172f64(listAddr, listSize);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (IsDrawOpcode(opcode)) {
|
|
if (g_hleGxState.fifoByteCount < 3) break;
|
|
const uint16_t vtxCount = ReadBE16(data + 1);
|
|
const GXVtxFmt vtxFmt = static_cast<GXVtxFmt>(cmd & GX_VAT_MASK_CMD);
|
|
const GXPrimitive prim = OpcodeToGXPrimitive(cmd);
|
|
uint32_t rawVertexSize = 0;
|
|
if (TryGetRawDirectFifoVertexSize(vtxFmt, rawVertexSize)) {
|
|
const uint32_t packetBytes = 3u + static_cast<uint32_t>(vtxCount) * rawVertexSize;
|
|
if (g_hleGxState.fifoByteCount < packetBytes) {
|
|
break;
|
|
}
|
|
if (TrySubmitRawDirectFifoDraw(data, packetBytes, prim, vtxFmt, vtxCount)) {
|
|
if (!consumeBytes(packetBytes, sink)) break;
|
|
continue;
|
|
}
|
|
}
|
|
if (!consumeBytes(3, sink)) break;
|
|
|
|
g_hleGxState.currentVtxFmt = vtxFmt;
|
|
g_hleGxState.currentPrim = prim;
|
|
g_hleGxState.vertsRemaining = vtxCount;
|
|
g_hleGxState.inBegin = true;
|
|
g_hleGxState.auroraBeginCalled = false;
|
|
g_hleGxState.ResetVertex();
|
|
|
|
break;
|
|
}
|
|
|
|
// Unknown FIFO command byte outside a begin packet; discard it so stream parsing can recover.
|
|
if (!consumeBytes(1, sink)) break;
|
|
}
|
|
|
|
if (!g_hleGxState.inBegin) {
|
|
return;
|
|
}
|
|
|
|
if (!recordOnly && !g_hleGxState.auroraBeginCalled) {
|
|
EnsureAuroraFrameActive();
|
|
ApplyAuroraVtxStateForRawBegin(g_hleGxState.currentVtxFmt);
|
|
EnsureDefaultGxAlphaCompare();
|
|
GXBegin(g_hleGxState.currentPrim, g_hleGxState.currentVtxFmt, static_cast<u16>(g_hleGxState.vertsRemaining));
|
|
g_hleGxState.auroraBeginCalled = true;
|
|
GXMarkFrameWork();
|
|
}
|
|
|
|
auto finishVertexIfNeeded = [&](GXAttr prevAttr) {
|
|
g_hleGxState.currentAttr = g_hleGxState.NextEnabledAttr(prevAttr);
|
|
g_hleGxState.currentComp = 0;
|
|
if (g_hleGxState.currentAttr == GX_VA_NULL) {
|
|
g_hleGxState.ResetVertex();
|
|
if (g_hleGxState.vertsRemaining > 0) {
|
|
g_hleGxState.vertsRemaining--;
|
|
if (g_hleGxState.vertsRemaining == 0) {
|
|
g_hleGxState.inBegin = false;
|
|
g_hleGxState.auroraBeginCalled = false;
|
|
if (!recordOnly) {
|
|
GXEnd();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
while (true) {
|
|
if (!g_hleGxState.inBegin) {
|
|
break;
|
|
}
|
|
GXAttr attr = g_hleGxState.currentAttr;
|
|
if (attr == GX_VA_NULL) {
|
|
resetFifoBuffer();
|
|
return;
|
|
}
|
|
|
|
GXAttrType inputType = g_hleGxState.vtxDesc[attr];
|
|
const VtxAttrFmt& fmt = g_hleGxState.vtxAttrFmt[g_hleGxState.currentVtxFmt][attr];
|
|
|
|
if (IsMatrixIndexAttr(attr)) {
|
|
if (g_hleGxState.fifoByteCount < 1) break;
|
|
u32 raw = 0;
|
|
if (!consumeBytes(1, raw)) break;
|
|
if (!recordOnly) {
|
|
GXMatrixIndex1u8(attr, static_cast<u8>(raw));
|
|
}
|
|
finishVertexIfNeeded(attr);
|
|
continue;
|
|
}
|
|
|
|
if (inputType == GX_DIRECT) {
|
|
if (attr == GX_VA_CLR0 || attr == GX_VA_CLR1) {
|
|
const uint32_t colorSize = ColorByteSize(fmt.type, fmt.cnt);
|
|
|
|
if (g_hleGxState.fifoByteCount < colorSize) break;
|
|
|
|
uint8_t colorBytes[4] = {0, 0, 0, 0};
|
|
uint8_t* data = fifoData();
|
|
for (uint32_t i = 0; i < colorSize && i < 4; ++i) {
|
|
colorBytes[i] = data[i];
|
|
}
|
|
g_hleGxState.fifoReadOffset += colorSize;
|
|
g_hleGxState.fifoByteCount -= colorSize;
|
|
if (g_hleGxState.fifoByteCount == 0) {
|
|
g_hleGxState.fifoReadOffset = 0;
|
|
}
|
|
|
|
GXColor color{};
|
|
DecodeColorBytes(colorBytes, fmt.type, fmt.cnt, color);
|
|
|
|
if (!recordOnly) {
|
|
if (fmt.cnt == GX_CLR_RGB) {
|
|
GXColor3u8(color.r, color.g, color.b);
|
|
} else {
|
|
GXColor4u8(color.r, color.g, color.b, color.a);
|
|
}
|
|
}
|
|
finishVertexIfNeeded(attr);
|
|
continue;
|
|
}
|
|
|
|
const int compSize = GetCompSizeBytes(fmt.type);
|
|
if (compSize <= 0 || g_hleGxState.fifoByteCount < static_cast<size_t>(compSize)) break;
|
|
u32 raw = 0;
|
|
if (!consumeBytes(static_cast<uint32_t>(compSize), raw)) break;
|
|
constexpr int kMaxComps = 9;
|
|
if (g_hleGxState.currentComp < kMaxComps) {
|
|
if (SubmitAttributeReadsFloatComps(attr, fmt)) {
|
|
g_hleGxState.compBuffer[g_hleGxState.currentComp] =
|
|
ConvertCompToFloat(raw, fmt.type, fmt.frac);
|
|
}
|
|
g_hleGxState.rawCompBuffer[g_hleGxState.currentComp] = raw;
|
|
}
|
|
g_hleGxState.currentComp++;
|
|
const int expected = g_hleGxState.GetExpectedCompCount(attr, fmt);
|
|
if (g_hleGxState.currentComp >= expected) {
|
|
if (!recordOnly) {
|
|
SubmitAttribute(attr, g_hleGxState.compBuffer, fmt, g_hleGxState.rawCompBuffer);
|
|
}
|
|
finishVertexIfNeeded(attr);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (inputType == GX_INDEX8 || inputType == GX_INDEX16) {
|
|
const uint32_t idxSize = (inputType == GX_INDEX8) ? 1u : 2u;
|
|
const uint32_t indexCount = (attr == GX_VA_NRM) ? NormalIndexCount(fmt) : 1u;
|
|
if (g_hleGxState.fifoByteCount < idxSize * indexCount) break;
|
|
u32 raw[3]{};
|
|
for (uint32_t i = 0; i < indexCount; ++i) {
|
|
if (!consumeBytes(idxSize, raw[i])) break;
|
|
}
|
|
if (!recordOnly) {
|
|
if (attr == GX_VA_NRM && indexCount == 3u) {
|
|
SubmitIndexedNormalNBT3(raw, fmt);
|
|
} else {
|
|
SubmitIndexedAttribute(attr, raw[0]);
|
|
}
|
|
}
|
|
finishVertexIfNeeded(attr);
|
|
continue;
|
|
}
|
|
|
|
g_hleGxState.currentAttr = g_hleGxState.NextEnabledAttr(attr);
|
|
g_hleGxState.currentComp = 0;
|
|
if (g_hleGxState.currentAttr == GX_VA_NULL) {
|
|
g_hleGxState.ResetVertex();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Must behave exactly like GX_HLE_FIFO_Write32/16/8 run byte by byte. The ring is not
|
|
// bulk-appended ahead of a single parse because the parser can re-enter GX HLE (nested display
|
|
// lists, XF/draw calls), which would misorder that work relative to the stream.
|
|
static void HleFifoWriteBurstChunked(const uint8_t* data, uint32_t sizeBytes) {
|
|
uint32_t offset = 0;
|
|
for (; offset + 4u <= sizeBytes; offset += 4u) {
|
|
HleFifoWrite(ReadBE32(data + offset), 4);
|
|
}
|
|
if (offset + 2u <= sizeBytes) {
|
|
HleFifoWrite(static_cast<u32>(ReadBE16(data + offset)), 2);
|
|
offset += 2u;
|
|
}
|
|
if (offset < sizeBytes) {
|
|
HleFifoWrite(static_cast<u32>(data[offset]), 1);
|
|
}
|
|
}
|
|
|
|
// Applies complete register-load packets straight to the parser's own entry points, skipping the
|
|
// serialize/ring/re-parse round trip; returns bytes consumed, caller hands the rest to
|
|
// HleFifoWriteBurstChunked. Safe (per gd_fifo_hle.cpp's GdCanApplyDirect) only on a packet
|
|
// boundary with nothing buffered, outside a GXBegin packet and outside display-list recording;
|
|
// any other opcode or a truncated packet just stops the walk.
|
|
static uint32_t ApplyFifoPacketsDirect(const uint8_t* data, uint32_t sizeBytes) {
|
|
uint32_t offset = 0;
|
|
|
|
while (offset < sizeBytes) {
|
|
// Re-tested per packet, not once per burst: nothing currently re-enters GX HLE mid-walk,
|
|
// but if it ever does, breaking here just hands the remainder to the ring.
|
|
if (IsDisplayListActive() || g_hleGxState.inBegin || g_hleGxState.fifoByteCount != 0) {
|
|
break;
|
|
}
|
|
|
|
const uint8_t* packet = data + offset;
|
|
const uint32_t avail = sizeBytes - offset;
|
|
const uint8_t cmd = packet[0];
|
|
const uint8_t opcode = cmd & GX_OPCODE_MASK_CMD;
|
|
|
|
if (cmd == GX_NOP_CMD) {
|
|
offset += 1u;
|
|
continue;
|
|
}
|
|
|
|
if (cmd == GX_LOAD_BP_REG_CMD) {
|
|
if (avail < 5u) break;
|
|
const uint32_t bpWord = ReadBE32(packet + 1);
|
|
GXApplyBPReg(static_cast<uint8_t>(bpWord >> 24), bpWord & 0x00FFFFFFu);
|
|
offset += 5u;
|
|
continue;
|
|
}
|
|
|
|
if (opcode == GX_LOAD_CP_REG_CMD) {
|
|
if (avail < 6u) break;
|
|
const uint8_t reg = packet[1];
|
|
const uint32_t cpValue = ReadBE32(packet + 2);
|
|
// Same function the parser calls, so the CP registers it does not
|
|
// decode (0x30/0x40 among them) are dropped here identically.
|
|
GxCpDecode::ApplyCpRegWrite(reg, cpValue);
|
|
offset += 6u;
|
|
continue;
|
|
}
|
|
|
|
if (opcode == GX_LOAD_XF_REG_CMD) {
|
|
if (avail < 5u) break;
|
|
const uint16_t countWords = ReadBE16(packet + 1);
|
|
const uint32_t packetBytes = 1u + 4u + (static_cast<uint32_t>(countWords) + 1u) * 4u;
|
|
if (avail < packetBytes) break;
|
|
GXCallDisplayList(packet, packetBytes);
|
|
GXMarkFrameWork();
|
|
offset += packetBytes;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
// Display-list recording just copies bytes into the guest list buffer and advances the shadow
|
|
// cursor, so a burst is one logical append as long as it doesn't cross the list end; a burst that
|
|
// would wrap falls back to the per-write path instead. Returns false to signal that fallback.
|
|
static bool WriteDisplayListBurst(const uint8_t* data, uint32_t sizeBytes) {
|
|
GxDisplayListState& dl = g_dlRecordState;
|
|
if (!dl.active || dl.base == 0 || dl.size == 0 || dl.writePtr == 0) {
|
|
// Same guard WriteDisplayListData applies; let the per-write path
|
|
// reproduce its (silent) drop.
|
|
return false;
|
|
}
|
|
|
|
const uint32_t end = dl.base + dl.size;
|
|
if (dl.writePtr > end || (end - dl.writePtr) < sizeBytes) {
|
|
return false;
|
|
}
|
|
|
|
// Guest-visible bytes are written through the ordinary store path, so
|
|
// unaligned cursors, MMIO policy and executable-write guards all behave as
|
|
// they do for a single store. The cursor and count advance per element so a
|
|
// failed write in the middle leaves exactly the completed prefix recorded.
|
|
try {
|
|
uint32_t offset = 0;
|
|
for (; offset + 4u <= sizeBytes; offset += 4u) {
|
|
Memory::Write32(dl.writePtr, ReadBE32(data + offset));
|
|
dl.writePtr += 4u;
|
|
dl.count += 4u;
|
|
}
|
|
if (offset + 2u <= sizeBytes) {
|
|
Memory::Write16(dl.writePtr, ReadBE16(data + offset));
|
|
dl.writePtr += 2u;
|
|
dl.count += 2u;
|
|
offset += 2u;
|
|
}
|
|
if (offset < sizeBytes) {
|
|
Memory::Write8(dl.writePtr, data[offset]);
|
|
dl.writePtr += 1u;
|
|
dl.count += 1u;
|
|
}
|
|
} catch (const Memory::AccessViolation&) {
|
|
}
|
|
return true;
|
|
}
|
|
|
|
extern "C" void GX_HLE_FIFO_WriteBurst(const uint8_t* data, uint32_t sizeBytes) {
|
|
if (data == nullptr || sizeBytes == 0) {
|
|
return;
|
|
}
|
|
|
|
if (IsDisplayListActive() && WriteDisplayListBurst(data, sizeBytes)) {
|
|
return;
|
|
}
|
|
|
|
const uint32_t applied = ApplyFifoPacketsDirect(data, sizeBytes);
|
|
if (applied < sizeBytes) {
|
|
HleFifoWriteBurstChunked(data + applied, sizeBytes - applied);
|
|
}
|
|
}
|