#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(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(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(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(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(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(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(rawComps[idx]); }; auto rs8 = [rawComps](int idx) { return static_cast(rawComps[idx]); }; auto r16 = [rawComps](int idx) { return static_cast(rawComps[idx]); }; auto rs16 = [rawComps](int idx) { return static_cast(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(comps[0]), static_cast(comps[1]), static_cast(comps[2])); } else { GXColor4u8(static_cast(comps[0]), static_cast(comps[1]), static_cast(comps[2]), static_cast(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(value)); break; default: g_hleGxState.fifoBytes[writeOffset] = static_cast(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(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(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(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(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(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(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(compSize)) break; u32 raw = 0; if (!consumeBytes(static_cast(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(ReadBE16(data + offset)), 2); offset += 2u; } if (offset < sizeBytes) { HleFifoWrite(static_cast(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(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(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); } }