u8* OpcodeDecoder_Run(DataReader src, u32* cycles, bool in_display_list) { u32 totalCycles = 0; u8* opcodeStart; while (true) { opcodeStart = src.GetPointer(); if (!src.size()) goto end; u8 cmd_byte = src.Read<u8>(); int refarray; switch (cmd_byte) { case GX_NOP: totalCycles += 6; // Hm, this means that we scan over nop streams pretty slowly... break; case GX_UNKNOWN_RESET: totalCycles += 6; // Datel software uses this command DEBUG_LOG(VIDEO, "GX Reset?: %08x", cmd_byte); break; case GX_LOAD_CP_REG: { if (src.size() < 1 + 4) goto end; totalCycles += 12; u8 sub_cmd = src.Read<u8>(); u32 value = src.Read<u32>(); LoadCPReg(sub_cmd, value, is_preprocess); if (!is_preprocess) INCSTAT(stats.thisFrame.numCPLoads); } break; case GX_LOAD_XF_REG: { if (src.size() < 4) goto end; u32 Cmd2 = src.Read<u32>(); int transfer_size = ((Cmd2 >> 16) & 15) + 1; if (src.size() < transfer_size * sizeof(u32)) goto end; totalCycles += 18 + 6 * transfer_size; if (!is_preprocess) { u32 xf_address = Cmd2 & 0xFFFF; LoadXFReg(transfer_size, xf_address, src); INCSTAT(stats.thisFrame.numXFLoads); } src.Skip<u32>(transfer_size); } break; case GX_LOAD_INDX_A: //used for position matrices refarray = 0xC; goto load_indx; case GX_LOAD_INDX_B: //used for normal matrices refarray = 0xD; goto load_indx; case GX_LOAD_INDX_C: //used for postmatrices refarray = 0xE; goto load_indx; case GX_LOAD_INDX_D: //used for lights refarray = 0xF; goto load_indx; load_indx: if (src.size() < 4) goto end; totalCycles += 6; if (is_preprocess) PreprocessIndexedXF(src.Read<u32>(), refarray); else LoadIndexedXF(src.Read<u32>(), refarray); break; case GX_CMD_CALL_DL: { if (src.size() < 8) goto end; u32 address = src.Read<u32>(); u32 count = src.Read<u32>(); if (in_display_list) { totalCycles += 6; WARN_LOG(VIDEO,"recursive display list detected"); } else { if (is_preprocess) InterpretDisplayListPreprocess(address, count); else totalCycles += 6 + InterpretDisplayList(address, count); } } break; case GX_CMD_UNKNOWN_METRICS: // zelda 4 swords calls it and checks the metrics registers after that totalCycles += 6; DEBUG_LOG(VIDEO, "GX 0x44: %08x", cmd_byte); break; case GX_CMD_INVL_VC: // Invalidate Vertex Cache totalCycles += 6; DEBUG_LOG(VIDEO, "Invalidate (vertex cache?)"); break; case GX_LOAD_BP_REG: // In skipped_frame case: We have to let BP writes through because they set // tokens and stuff. TODO: Call a much simplified LoadBPReg instead. { if (src.size() < 4) goto end; totalCycles += 12; u32 bp_cmd = src.Read<u32>(); if (is_preprocess) { LoadBPRegPreprocess(bp_cmd); } else { LoadBPReg(bp_cmd); INCSTAT(stats.thisFrame.numBPLoads); } } break; // draw primitives default: if ((cmd_byte & 0xC0) == 0x80) { // load vertices if (src.size() < 2) goto end; u16 num_vertices = src.Read<u16>(); int bytes = VertexLoaderManager::RunVertices( cmd_byte & GX_VAT_MASK, // Vertex loader index (0 - 7) (cmd_byte & GX_PRIMITIVE_MASK) >> GX_PRIMITIVE_SHIFT, num_vertices, src, Fifo::g_bSkipCurrentFrame, is_preprocess); if (bytes < 0) goto end; src.Skip(bytes); // 4 GPU ticks per vertex, 3 CPU ticks per GPU tick totalCycles += num_vertices * 4 * 3 + 6; } else { if (!s_bFifoErrorSeen) UnknownOpcode(cmd_byte, opcodeStart, is_preprocess); ERROR_LOG(VIDEO, "FIFO: Unknown Opcode(0x%02x @ %p, preprocessing = %s)", cmd_byte, opcodeStart, is_preprocess ? "yes" : "no"); s_bFifoErrorSeen = true; totalCycles += 1; } break; } // Display lists get added directly into the FIFO stream if (!is_preprocess && g_bRecordFifoData && cmd_byte != GX_CMD_CALL_DL) { u8* opcodeEnd; opcodeEnd = src.GetPointer(); FifoRecorder::GetInstance().WriteGPCommand(opcodeStart, u32(opcodeEnd - opcodeStart)); } }
u8* OpcodeDecoder_Run(DataReader& reader, u32* cycles) { u32 totalCycles = 0; u8* opcodeStart; while (true) { opcodeStart = reader.GetReadPosition(); if (!reader.size()) goto end; u8 cmd_byte = reader.Read<u8>(); size_t distance = reader.size(); switch (cmd_byte) { case GX_NOP: { totalCycles += GX_NOP_CYCLES; // Hm, this means that we scan over nop streams pretty slowly... } break; case GX_UNKNOWN_RESET: { totalCycles += GX_NOP_CYCLES; // Datel software uses this command DEBUG_LOG(VIDEO, "GX Reset?: %08x", cmd_byte); } break; case GX_LOAD_CP_REG: { if (sizeCheck && distance < GX_LOAD_CP_REG_SIZE) goto end; totalCycles += GX_LOAD_CP_REG_CYCLES; u8 sub_cmd = reader.Read<u8>(); u32 value = reader.Read<u32>(); LoadCPReg<is_preprocess>(sub_cmd, value); if (!is_preprocess) INCSTAT(stats.thisFrame.numCPLoads); } break; case GX_LOAD_XF_REG: { if (sizeCheck && distance < GX_LOAD_XF_REG_SIZE) goto end; u32 Cmd2 = reader.Read<u32>(); distance -= GX_LOAD_XF_REG_SIZE; int transfer_size = ((Cmd2 >> 16) & 15) + 1; if (sizeCheck && distance < (transfer_size * sizeof(u32))) goto end; totalCycles += GX_LOAD_XF_REG_BASE_CYCLES + GX_LOAD_XF_REG_TRANSFER_CYCLES * transfer_size; if (is_preprocess) { reader.ReadSkip(transfer_size * sizeof(u32)); } else { u32 xf_address = Cmd2 & 0xFFFF; LoadXFReg(transfer_size, xf_address); INCSTAT(stats.thisFrame.numXFLoads); } } break; case GX_LOAD_INDX_A: //used for position matrices case GX_LOAD_INDX_B: //used for normal matrices case GX_LOAD_INDX_C: //used for postmatrices case GX_LOAD_INDX_D: //used for lights { if (sizeCheck && distance < GX_LOAD_INDX_SIZE) goto end; totalCycles += GX_LOAD_INDX_CYCLES; const s32 ref_array = (cmd_byte >> 3) + 8; if (is_preprocess) PreprocessIndexedXF(reader.Read<u32>(), ref_array); else LoadIndexedXF(reader.Read<u32>(), ref_array); } break; case GX_CMD_CALL_DL: { if (sizeCheck && distance < GX_CMD_CALL_DL_SIZE) goto end; u32 address = reader.Read<u32>(); u32 count = reader.Read<u32>(); if (is_preprocess) InterpretDisplayListPreprocess(address, count); else totalCycles += GX_CMD_CALL_DL_BASE_CYCLES + InterpretDisplayList(address, count); } break; case GX_CMD_UNKNOWN_METRICS: // zelda 4 swords calls it and checks the metrics registers after that { totalCycles += GX_CMD_UNKNOWN_METRICS_CYCLES; DEBUG_LOG(VIDEO, "GX 0x44: %08x", cmd_byte); } break; case GX_CMD_INVL_VC: // Invalidate Vertex Cache { totalCycles += GX_CMD_INVL_VC_CYCLES; DEBUG_LOG(VIDEO, "Invalidate (vertex cache?)"); } break; case GX_LOAD_BP_REG: { if (sizeCheck && distance < GX_LOAD_BP_REG_SIZE) goto end; totalCycles += GX_LOAD_BP_REG_CYCLES; u32 bp_cmd = reader.Read<u32>(); if (is_preprocess) { LoadBPRegPreprocess(bp_cmd); } else { LoadBPReg(bp_cmd); INCSTAT(stats.thisFrame.numBPLoads); } } break; // draw primitives default: if ((cmd_byte & GX_DRAW_PRIMITIVES) == 0x80) { // load vertices if (sizeCheck && distance < GX_DRAW_PRIMITIVES_SIZE) goto end; u32 count = reader.Read<u16>(); distance -= GX_DRAW_PRIMITIVES_SIZE; if (count) { CPState& state = is_preprocess ? g_preprocess_cp_state : g_main_cp_state; VertexLoaderParameters parameters; parameters.count = count; parameters.buf_size = distance; parameters.primitive = (cmd_byte & GX_PRIMITIVE_MASK) >> GX_PRIMITIVE_SHIFT; u32 vtx_attr_group = cmd_byte & GX_VAT_MASK; parameters.vtx_attr_group = vtx_attr_group; parameters.needloaderrefresh = (state.attr_dirty & (1u << vtx_attr_group)) != 0; parameters.skip_draw = g_bSkipCurrentFrame; parameters.VtxDesc = &state.vtx_desc; parameters.VtxAttr = &state.vtx_attr[vtx_attr_group]; parameters.source = reader.GetReadPosition(); state.attr_dirty &= ~(1 << vtx_attr_group); u32 readsize = 0; if (is_preprocess) { u32 components = 0; VertexLoaderManager::GetVertexSizeAndComponents(parameters, readsize, components); readsize *= count; if (distance >= readsize) { totalCycles += GX_NOP_CYCLES + GX_DRAW_PRIMITIVES_CYCLES * parameters.count; reader.ReadSkip(readsize); } else { goto end; } } else { u32 writesize = 0; if (VertexLoaderManager::ConvertVertices(parameters, readsize, writesize)) { totalCycles += GX_NOP_CYCLES + GX_DRAW_PRIMITIVES_CYCLES * parameters.count; reader.ReadSkip(readsize); VertexManagerBase::s_pCurBufferPointer += writesize; } else { goto end; } } } else { totalCycles += GX_NOP_CYCLES; } } else { if (!s_bFifoErrorSeen) UnknownOpcode(cmd_byte, opcodeStart, is_preprocess); ERROR_LOG(VIDEO, "FIFO: Unknown Opcode(0x%02x @ %p, preprocessing = %s)", cmd_byte, opcodeStart, is_preprocess ? "yes" : "no"); s_bFifoErrorSeen = true; totalCycles += 1; } break; }