diff options
Diffstat (limited to 'src')
25 files changed, 434 insertions, 128 deletions
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index b12bd858..a96fbea5 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -94,7 +94,7 @@ private: }; GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) : - QWidget(parent), emu_thread(emu_thread), keyboard_id(0) { + QWidget(parent), keyboard_id(0), emu_thread(emu_thread) { std::string window_title = Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); setWindowTitle(QString::fromStdString(window_title)); diff --git a/src/citra_qt/debugger/disassembler.cpp b/src/citra_qt/debugger/disassembler.cpp index 1e5ef529..d3629bbf 100644 --- a/src/citra_qt/debugger/disassembler.cpp +++ b/src/citra_qt/debugger/disassembler.cpp @@ -159,7 +159,7 @@ void DisassemblerModel::SetNextInstruction(unsigned int address) { } DisassemblerWidget::DisassemblerWidget(QWidget* parent, EmuThread* emu_thread) : - QDockWidget(parent), emu_thread(emu_thread), base_addr(0) { + QDockWidget(parent), base_addr(0), emu_thread(emu_thread) { disasm_ui.setupUi(this); diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 34831f2e..6b030c17 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -122,9 +122,8 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) y = (screenRect.y() + screenRect.height()) / 2 - h * 55 / 100; setGeometry(x, y, w, h); - // Restore UI state - QSettings settings(QSettings::IniFormat, QSettings::UserScope, "Citra team", "Citra"); + QSettings settings; restoreGeometry(settings.value("geometry").toByteArray()); restoreState(settings.value("state").toByteArray()); render_window->restoreGeometry(settings.value("geometryRenderWindow").toByteArray()); @@ -207,7 +206,7 @@ void GMainWindow::OnDisplayTitleBars(bool show) } } -void GMainWindow::BootGame(std::string filename) { +void GMainWindow::BootGame(const std::string& filename) { LOG_INFO(Frontend, "Citra starting...\n"); // Initialize the core emulation @@ -271,8 +270,13 @@ void GMainWindow::ShutdownGame() { void GMainWindow::OnMenuLoadFile() { - QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), QString(), tr("3DS executable (*.3ds *.3dsx *.elf *.axf *.cci *.cxi)")); + QSettings settings; + QString rom_path = settings.value("romsPath", QString()).toString(); + + QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), rom_path, tr("3DS executable (*.3ds *.3dsx *.elf *.axf *.cci *.cxi)")); if (filename.size()) { + settings.setValue("romsPath", QFileInfo(filename).path()); + // Shutdown previous session if the emu thread is still active... if (emu_thread != nullptr) ShutdownGame(); @@ -282,9 +286,15 @@ void GMainWindow::OnMenuLoadFile() } void GMainWindow::OnMenuLoadSymbolMap() { - QString filename = QFileDialog::getOpenFileName(this, tr("Load Symbol Map"), QString(), tr("Symbol map (*)")); - if (filename.size()) + QSettings settings; + QString symbol_path = settings.value("symbolsPath", QString()).toString(); + + QString filename = QFileDialog::getOpenFileName(this, tr("Load Symbol Map"), symbol_path, tr("Symbol map (*)")); + if (filename.size()) { + settings.setValue("symbolsPath", QFileInfo(filename).path()); + LoadSymbolMap(filename.toLatin1().data()); + } } void GMainWindow::OnStartGame() @@ -375,6 +385,11 @@ int main(int argc, char* argv[]) Log::Filter log_filter(Log::Level::Info); Log::SetFilter(&log_filter); + // Init settings params + QSettings::setDefaultFormat(QSettings::IniFormat); + QCoreApplication::setOrganizationName("Citra team"); + QCoreApplication::setApplicationName("Citra"); + QApplication::setAttribute(Qt::AA_X11InitThreads); QApplication app(argc, argv); diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 242b08c3..9fe9e0c9 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -55,7 +55,7 @@ signals: void EmulationStopping(); private: - void BootGame(std::string filename); + void BootGame(const std::string& filename); void ShutdownGame(); void closeEvent(QCloseEvent* event) override; diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index d85e5837..0a081e7d 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -6,6 +6,7 @@ #include <array> #include <cstdio> +#include "common/assert.h" #include "common/common_funcs.h" // snprintf compatibility define #include "common/logging/backend.h" #include "common/logging/filter.h" @@ -78,8 +79,9 @@ const char* GetLevelName(Level log_level) { LVL(Warning); LVL(Error); LVL(Critical); + case Level::Count: + UNREACHABLE(); } - return "Unknown"; #undef LVL } diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp index 94f3dfc1..de195b0f 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp @@ -14,6 +14,7 @@ #include "common/logging/log.h" #include "common/logging/text_formatter.h" +#include "common/assert.h" #include "common/common_funcs.h" #include "common/string_util.h" @@ -82,6 +83,8 @@ void PrintColoredMessage(const Entry& entry) { color = FOREGROUND_RED | FOREGROUND_INTENSITY; break; case Level::Critical: // Bright magenta color = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break; + case Level::Count: + UNREACHABLE(); } SetConsoleTextAttribute(console_handle, color); @@ -101,6 +104,8 @@ void PrintColoredMessage(const Entry& entry) { color = ESC "[1;31m"; break; case Level::Critical: // Bright magenta color = ESC "[1;35m"; break; + case Level::Count: + UNREACHABLE(); } fputs(color, stderr); diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index b2f7f7b1..6d6fc591 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -296,14 +296,28 @@ std::string ReplaceAll(std::string result, const std::string& src, const std::st std::string UTF16ToUTF8(const std::u16string& input) { +#if _MSC_VER >= 1900 + // Workaround for missing char16_t/char32_t instantiations in MSVC2015 + std::wstring_convert<std::codecvt_utf8_utf16<__int16>, __int16> convert; + std::basic_string<__int16> tmp_buffer(input.cbegin(), input.cend()); + return convert.to_bytes(tmp_buffer); +#else std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert; return convert.to_bytes(input); +#endif } std::u16string UTF8ToUTF16(const std::string& input) { +#if _MSC_VER >= 1900 + // Workaround for missing char16_t/char32_t instantiations in MSVC2015 + std::wstring_convert<std::codecvt_utf8_utf16<__int16>, __int16> convert; + auto tmp_buffer = convert.from_bytes(input); + return std::u16string(tmp_buffer.cbegin(), tmp_buffer.cend()); +#else std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert; return convert.from_bytes(input); +#endif } static std::string UTF16ToUTF8(const std::wstring& input) diff --git a/src/core/arm/disassembler/arm_disasm.cpp b/src/core/arm/disassembler/arm_disasm.cpp index aaf47b3f..964e3011 100644 --- a/src/core/arm/disassembler/arm_disasm.cpp +++ b/src/core/arm/disassembler/arm_disasm.cpp @@ -4,6 +4,7 @@ #include "common/string_util.h" #include "core/arm/disassembler/arm_disasm.h" +#include "core/arm/skyeye_common/armsupp.h" static const char *cond_names[] = { "eq", @@ -37,6 +38,7 @@ static const char *opcode_names[] = { "blx", "bx", "cdp", + "clrex", "clz", "cmn", "cmp", @@ -46,6 +48,10 @@ static const char *opcode_names[] = { "ldr", "ldrb", "ldrbt", + "ldrex", + "ldrexb", + "ldrexd", + "ldrexh", "ldrh", "ldrsb", "ldrsh", @@ -58,11 +64,13 @@ static const char *opcode_names[] = { "msr", "mul", "mvn", + "nop", "orr", "pld", "rsb", "rsc", "sbc", + "sev", "smlal", "smull", "stc", @@ -70,6 +78,10 @@ static const char *opcode_names[] = { "str", "strb", "strbt", + "strex", + "strexb", + "strexd", + "strexh", "strh", "strt", "sub", @@ -80,6 +92,9 @@ static const char *opcode_names[] = { "tst", "umlal", "umull", + "wfe", + "wfi", + "yield", "undefined", "adc", @@ -172,6 +187,8 @@ std::string ARM_Disasm::Disassemble(uint32_t addr, uint32_t insn) return DisassembleBX(insn); case OP_CDP: return "cdp"; + case OP_CLREX: + return "clrex"; case OP_CLZ: return DisassembleCLZ(insn); case OP_LDC: @@ -188,6 +205,15 @@ std::string ARM_Disasm::Disassemble(uint32_t addr, uint32_t insn) case OP_STRBT: case OP_STRT: return DisassembleMem(insn); + case OP_LDREX: + case OP_LDREXB: + case OP_LDREXD: + case OP_LDREXH: + case OP_STREX: + case OP_STREXB: + case OP_STREXD: + case OP_STREXH: + return DisassembleREX(opcode, insn); case OP_LDRH: case OP_LDRSB: case OP_LDRSH: @@ -204,6 +230,12 @@ std::string ARM_Disasm::Disassemble(uint32_t addr, uint32_t insn) return DisassembleMSR(insn); case OP_MUL: return DisassembleMUL(opcode, insn); + case OP_NOP: + case OP_SEV: + case OP_WFE: + case OP_WFI: + case OP_YIELD: + return DisassembleNoOperands(opcode, insn); case OP_PLD: return DisassemblePLD(insn); case OP_STC: @@ -646,6 +678,12 @@ std::string ARM_Disasm::DisassembleMSR(uint32_t insn) cond_to_str(cond), pd ? "spsr" : "cpsr", flags, rm); } +std::string ARM_Disasm::DisassembleNoOperands(Opcode opcode, uint32_t insn) +{ + uint32_t cond = BITS(insn, 28, 31); + return Common::StringFromFormat("%s%s", opcode_names[opcode], cond_to_str(cond)); +} + std::string ARM_Disasm::DisassemblePLD(uint32_t insn) { uint8_t is_reg = (insn >> 25) & 0x1; @@ -669,6 +707,36 @@ std::string ARM_Disasm::DisassemblePLD(uint32_t insn) } } +std::string ARM_Disasm::DisassembleREX(Opcode opcode, uint32_t insn) { + uint32_t rn = BITS(insn, 16, 19); + uint32_t rd = BITS(insn, 12, 15); + uint32_t rt = BITS(insn, 0, 3); + uint32_t cond = BITS(insn, 28, 31); + + switch (opcode) { + case OP_STREX: + case OP_STREXB: + case OP_STREXH: + return Common::StringFromFormat("%s%s\tr%d, r%d, [r%d]", opcode_names[opcode], + cond_to_str(cond), rd, rt, rn); + case OP_STREXD: + return Common::StringFromFormat("%s%s\tr%d, r%d, r%d, [r%d]", opcode_names[opcode], + cond_to_str(cond), rd, rt, rt + 1, rn); + + // for LDREX instructions, rd corresponds to Rt from reference manual + case OP_LDREX: + case OP_LDREXB: + case OP_LDREXH: + return Common::StringFromFormat("%s%s\tr%d, [r%d]", opcode_names[opcode], + cond_to_str(cond), rd, rn); + case OP_LDREXD: + return Common::StringFromFormat("%s%s\tr%d, r%d, [r%d]", opcode_names[opcode], + cond_to_str(cond), rd, rd + 1, rn); + default: + return opcode_names[OP_UNDEFINED]; + } +} + std::string ARM_Disasm::DisassembleSWI(uint32_t insn) { uint8_t cond = (insn >> 28) & 0xf; @@ -721,12 +789,9 @@ Opcode ARM_Disasm::Decode00(uint32_t insn) { } uint32_t bits7_4 = (insn >> 4) & 0xf; if (bits7_4 == 0x9) { - if ((insn & 0x0ff00ff0) == 0x01000090) { - // Swp instruction - uint8_t bit22 = (insn >> 22) & 0x1; - if (bit22) - return OP_SWPB; - return OP_SWP; + uint32_t bit24 = BIT(insn, 24); + if (bit24) { + return DecodeSyncPrimitive(insn); } // One of the multiply instructions return DecodeMUL(insn); @@ -739,6 +804,12 @@ Opcode ARM_Disasm::Decode00(uint32_t insn) { } } + uint32_t op1 = BITS(insn, 20, 24); + if (bit25 && (op1 == 0x12 || op1 == 0x16)) { + // One of the MSR (immediate) and hints instructions + return DecodeMSRImmAndHints(insn); + } + // One of the data processing instructions return DecodeALU(insn); } @@ -754,6 +825,10 @@ Opcode ARM_Disasm::Decode01(uint32_t insn) { // Pre-load return OP_PLD; } + if (insn == 0xf57ff01f) { + // Clear-Exclusive + return OP_CLREX; + } if (is_load) { if (is_byte) { // Load byte @@ -836,6 +911,35 @@ Opcode ARM_Disasm::Decode11(uint32_t insn) { return OP_MCR; } +Opcode ARM_Disasm::DecodeSyncPrimitive(uint32_t insn) { + uint32_t op = BITS(insn, 20, 23); + uint32_t bit22 = BIT(insn, 22); + switch (op) { + case 0x0: + if (bit22) + return OP_SWPB; + return OP_SWP; + case 0x8: + return OP_STREX; + case 0x9: + return OP_LDREX; + case 0xA: + return OP_STREXD; + case 0xB: + return OP_LDREXD; + case 0xC: + return OP_STREXB; + case 0xD: + return OP_LDREXB; + case 0xE: + return OP_STREXH; + case 0xF: + return OP_LDREXH; + default: + return OP_UNDEFINED; + } +} + Opcode ARM_Disasm::DecodeMUL(uint32_t insn) { uint8_t bit24 = (insn >> 24) & 0x1; if (bit24 != 0) { @@ -870,6 +974,31 @@ Opcode ARM_Disasm::DecodeMUL(uint32_t insn) { return OP_SMLAL; } +Opcode ARM_Disasm::DecodeMSRImmAndHints(uint32_t insn) { + uint32_t op = BIT(insn, 22); + uint32_t op1 = BITS(insn, 16, 19); + uint32_t op2 = BITS(insn, 0, 7); + + if (op == 0 && op1 == 0) { + switch (op2) { + case 0x0: + return OP_NOP; + case 0x1: + return OP_YIELD; + case 0x2: + return OP_WFE; + case 0x3: + return OP_WFI; + case 0x4: + return OP_SEV; + default: + return OP_UNDEFINED; + } + } + + return OP_MSR; +} + Opcode ARM_Disasm::DecodeLDRH(uint32_t insn) { uint8_t is_load = (insn >> 20) & 0x1; uint8_t bits_65 = (insn >> 5) & 0x3; diff --git a/src/core/arm/disassembler/arm_disasm.h b/src/core/arm/disassembler/arm_disasm.h index f94bd466..d04fd21e 100644 --- a/src/core/arm/disassembler/arm_disasm.h +++ b/src/core/arm/disassembler/arm_disasm.h @@ -20,6 +20,7 @@ enum Opcode { OP_BLX, OP_BX, OP_CDP, + OP_CLREX, OP_CLZ, OP_CMN, OP_CMP, @@ -29,6 +30,10 @@ enum Opcode { OP_LDR, OP_LDRB, OP_LDRBT, + OP_LDREX, + OP_LDREXB, + OP_LDREXD, + OP_LDREXH, OP_LDRH, OP_LDRSB, OP_LDRSH, @@ -41,11 +46,13 @@ enum Opcode { OP_MSR, OP_MUL, OP_MVN, + OP_NOP, OP_ORR, OP_PLD, OP_RSB, OP_RSC, OP_SBC, + OP_SEV, OP_SMLAL, OP_SMULL, OP_STC, @@ -53,6 +60,10 @@ enum Opcode { OP_STR, OP_STRB, OP_STRBT, + OP_STREX, + OP_STREXB, + OP_STREXD, + OP_STREXH, OP_STRH, OP_STRT, OP_SUB, @@ -63,6 +74,9 @@ enum Opcode { OP_TST, OP_UMLAL, OP_UMULL, + OP_WFE, + OP_WFI, + OP_YIELD, // Define thumb opcodes OP_THUMB_UNDEFINED, @@ -117,7 +131,9 @@ class ARM_Disasm { static Opcode Decode01(uint32_t insn); static Opcode Decode10(uint32_t insn); static Opcode Decode11(uint32_t insn); + static Opcode DecodeSyncPrimitive(uint32_t insn); static Opcode DecodeMUL(uint32_t insn); + static Opcode DecodeMSRImmAndHints(uint32_t insn); static Opcode DecodeLDRH(uint32_t insn); static Opcode DecodeALU(uint32_t insn); @@ -135,7 +151,9 @@ class ARM_Disasm { static std::string DisassembleMUL(Opcode opcode, uint32_t insn); static std::string DisassembleMRS(uint32_t insn); static std::string DisassembleMSR(uint32_t insn); + static std::string DisassembleNoOperands(Opcode opcode, uint32_t insn); static std::string DisassemblePLD(uint32_t insn); + static std::string DisassembleREX(Opcode opcode, uint32_t insn); static std::string DisassembleSWI(uint32_t insn); static std::string DisassembleSWP(Opcode opcode, uint32_t insn); }; diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp index 0c20c2bc..b88b7475 100644 --- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp +++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp @@ -3886,7 +3886,6 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { #endif arm_inst* inst_base; unsigned int addr; - unsigned int phys_addr; unsigned int num_instrs = 0; int ptr; @@ -3905,8 +3904,6 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { else cpu->Reg[15] &= 0xfffffffc; - phys_addr = cpu->Reg[15]; - // Find the cached instruction cream, otherwise translate it... auto itr = cpu->instruction_cache.find(cpu->Reg[15]); if (itr != cpu->instruction_cache.end()) { @@ -5997,7 +5994,12 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { ldst_inst* inst_cream = (ldst_inst*)inst_base->component; inst_cream->get_addr(cpu, inst_cream->inst, addr); - unsigned int value = cpu->Reg[BITS(inst_cream->inst, 12, 15)]; + unsigned int reg = BITS(inst_cream->inst, 12, 15); + unsigned int value = cpu->Reg[reg]; + + if (reg == 15) + value += 2 * cpu->GetInstructionSize(); + cpu->WriteMemory32(addr, value); } cpu->Reg[15] += cpu->GetInstructionSize(); diff --git a/src/core/arm/skyeye_common/vfp/vfpinstr.cpp b/src/core/arm/skyeye_common/vfp/vfpinstr.cpp index 9b99fc5b..49298d7b 100644 --- a/src/core/arm/skyeye_common/vfp/vfpinstr.cpp +++ b/src/core/arm/skyeye_common/vfp/vfpinstr.cpp @@ -1511,19 +1511,26 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vstm)(unsigned int inst, int index) #ifdef VFP_INTERPRETER_IMPL VSTM_INST: /* encoding 1 */ { - if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { + if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { CHECK_VFP_ENABLED; - vstm_inst *inst_cream = (vstm_inst *)inst_base->component; + vstm_inst* inst_cream = (vstm_inst*)inst_base->component; + + u32 address = cpu->Reg[inst_cream->n]; - addr = (inst_cream->add ? cpu->Reg[inst_cream->n] : cpu->Reg[inst_cream->n] - inst_cream->imm32); + // Only possible in ARM mode, where PC accesses have an 8 byte offset. + if (inst_cream->n == 15) + address += 8; + + if (inst_cream->add == 0) + address -= inst_cream->imm32; for (unsigned int i = 0; i < inst_cream->regs; i++) { if (inst_cream->single) { - cpu->WriteMemory32(addr, cpu->ExtReg[inst_cream->d+i]); - addr += 4; + cpu->WriteMemory32(address, cpu->ExtReg[inst_cream->d+i]); + address += 4; } else { @@ -1531,17 +1538,17 @@ VSTM_INST: /* encoding 1 */ const u32 word2 = cpu->ExtReg[(inst_cream->d+i)*2+1]; if (cpu->InBigEndianMode()) { - cpu->WriteMemory32(addr + 0, word2); - cpu->WriteMemory32(addr + 4, word1); + cpu->WriteMemory32(address + 0, word2); + cpu->WriteMemory32(address + 4, word1); } else { - cpu->WriteMemory32(addr + 0, word1); - cpu->WriteMemory32(addr + 4, word2); + cpu->WriteMemory32(address + 0, word1); + cpu->WriteMemory32(address + 4, word2); } - addr += 8; + address += 8; } } - if (inst_cream->wback){ + if (inst_cream->wback) { cpu->Reg[inst_cream->n] = (inst_cream->add ? cpu->Reg[inst_cream->n] + inst_cream->imm32 : cpu->Reg[inst_cream->n] - inst_cream->imm32); } @@ -1731,24 +1738,31 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vldm)(unsigned int inst, int index) #ifdef VFP_INTERPRETER_IMPL VLDM_INST: { - if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { + if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { CHECK_VFP_ENABLED; - vldm_inst *inst_cream = (vldm_inst *)inst_base->component; + vldm_inst* inst_cream = (vldm_inst*)inst_base->component; + + u32 address = cpu->Reg[inst_cream->n]; + + // Only possible in ARM mode, where PC accesses have an 8 byte offset. + if (inst_cream->n == 15) + address += 8; - addr = (inst_cream->add ? cpu->Reg[inst_cream->n] : cpu->Reg[inst_cream->n] - inst_cream->imm32); + if (inst_cream->add == 0) + address -= inst_cream->imm32; for (unsigned int i = 0; i < inst_cream->regs; i++) { if (inst_cream->single) { - cpu->ExtReg[inst_cream->d+i] = cpu->ReadMemory32(addr); - addr += 4; + cpu->ExtReg[inst_cream->d+i] = cpu->ReadMemory32(address); + address += 4; } else { - const u32 word1 = cpu->ReadMemory32(addr + 0); - const u32 word2 = cpu->ReadMemory32(addr + 4); + const u32 word1 = cpu->ReadMemory32(address + 0); + const u32 word2 = cpu->ReadMemory32(address + 4); if (cpu->InBigEndianMode()) { cpu->ExtReg[(inst_cream->d+i)*2+0] = word2; @@ -1758,10 +1772,10 @@ VLDM_INST: cpu->ExtReg[(inst_cream->d+i)*2+1] = word2; } - addr += 8; + address += 8; } } - if (inst_cream->wback){ + if (inst_cream->wback) { cpu->Reg[inst_cream->n] = (inst_cream->add ? cpu->Reg[inst_cream->n] + inst_cream->imm32 : cpu->Reg[inst_cream->n] - inst_cream->imm32); } diff --git a/src/core/hle/applets/applet.cpp b/src/core/hle/applets/applet.cpp index 826f6cbb..bc2a1829 100644 --- a/src/core/hle/applets/applet.cpp +++ b/src/core/hle/applets/applet.cpp @@ -89,12 +89,21 @@ ResultCode Applet::Start(const Service::APT::AppletStartupParameter& parameter) return result; } +bool IsLibraryAppletRunning() { + // Check the applets map for instances of any applet + for (auto itr = applets.begin(); itr != applets.end(); ++itr) + if (itr->second != nullptr) + return true; + return false; +} + void Init() { // Register the applet update callback applet_update_event = CoreTiming::RegisterEvent("HLE Applet Update Event", AppletUpdateEvent); } void Shutdown() { + CoreTiming::RemoveEvent(applet_update_event); } } diff --git a/src/core/hle/applets/applet.h b/src/core/hle/applets/applet.h index b235d0b8..af442f81 100644 --- a/src/core/hle/applets/applet.h +++ b/src/core/hle/applets/applet.h @@ -67,6 +67,9 @@ protected: Service::APT::AppletId id; ///< Id of this Applet }; +/// Returns whether a library applet is currently running +bool IsLibraryAppletRunning(); + /// Initializes the HLE applets void Init(); diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 7332478f..7ae4859a 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -5,6 +5,7 @@ #include "common/logging/log.h" #include "core/hle/service/service.h" +#include "core/hle/service/am/am.h" #include "core/hle/service/am/am_app.h" #include "core/hle/service/am/am_net.h" #include "core/hle/service/am/am_sys.h" @@ -35,7 +36,7 @@ void GetTitleIDList(Service::Interface* self) { cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 0; - LOG_WARNING(Service_AM, "(STUBBED) Requested %u titles from media type %u", num_titles, media_type); + LOG_WARNING(Service_AM, "(STUBBED) Requested %u titles from media type %u. Address=0x%08X", num_titles, media_type, addr); } void GetNumContentInfos(Service::Interface* self) { diff --git a/src/core/hle/service/am/am_net.cpp b/src/core/hle/service/am/am_net.cpp index b1af0e9d..aa391f3b 100644 --- a/src/core/hle/service/am/am_net.cpp +++ b/src/core/hle/service/am/am_net.cpp @@ -28,7 +28,8 @@ const Interface::FunctionInfo FunctionTable[] = { {0x08130000, nullptr, "GetTotalContents"}, {0x08140042, nullptr, "GetContentIndexes"}, {0x08150044, nullptr, "GetContentsInfo"}, - {0x08190108, nullptr, "Unknown"}, + {0x08180042, nullptr, "GetCTCert"}, + {0x08190108, nullptr, "SetCertificates"}, {0x081B00C2, nullptr, "InstallTitlesFinish"}, }; diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 7b6ab4ce..35402341 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -101,18 +101,19 @@ void NotifyToWait(Service::Interface* self) { void GetLockHandle(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 flags = cmd_buff[1]; // TODO(bunnei): Figure out the purpose of the flag field + // Bits [0:2] are the applet type (System, Library, etc) + // Bit 5 tells the application that there's a pending APT parameter, + // this will cause the app to wait until parameter_event is signaled. + u32 applet_attributes = cmd_buff[1]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error - // Not sure what these parameters are used for, but retail apps check that they are 0 after - // GetLockHandle has been called. - cmd_buff[2] = 0; // Applet Attributes, this value is passed to Enable. - cmd_buff[3] = 0; - cmd_buff[4] = 0; - + cmd_buff[2] = applet_attributes; // Applet Attributes, this value is passed to Enable. + cmd_buff[3] = 0; // Least significant bit = power button state + cmd_buff[4] = IPC::CopyHandleDesc(); cmd_buff[5] = Kernel::g_handle_table.Create(lock).MoveFrom(); - LOG_TRACE(Service_APT, "called handle=0x%08X", cmd_buff[5]); + + LOG_WARNING(Service_APT, "(STUBBED) called handle=0x%08X applet_attributes=0x%08X", cmd_buff[5], applet_attributes); } void Enable(Service::Interface* self) { @@ -139,13 +140,16 @@ void IsRegistered(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 app_id = cmd_buff[1]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error - /// TODO(Subv): It is currently unknown what this value (0x400) means, - /// but i believe it is used as a global "LibraryApplet" id, to verify if there's - /// any LibApplet currently running. This is not verified. - if (app_id != 0x400) + + // TODO(Subv): An application is considered "registered" if it has already called APT::Enable + // handle this properly once we implement multiprocess support. + cmd_buff[2] = 0; // Set to not registered by default + + if (app_id == static_cast<u32>(AppletId::AnyLibraryApplet)) { + cmd_buff[2] = HLE::Applets::IsLibraryAppletRunning() ? 1 : 0; + } else if (auto applet = HLE::Applets::Applet::Get(static_cast<AppletId>(app_id))) { cmd_buff[2] = 1; // Set to registered - else - cmd_buff[2] = 0; // Set to not registered + } LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); } @@ -330,7 +334,26 @@ void GetAppCpuTimeLimit(Service::Interface* self) { void PrepareToStartLibraryApplet(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); AppletId applet_id = static_cast<AppletId>(cmd_buff[1]); - cmd_buff[1] = HLE::Applets::Applet::Create(applet_id).raw; + auto applet = HLE::Applets::Applet::Get(applet_id); + if (applet) { + LOG_WARNING(Service_APT, "applet has already been started id=%08X", applet_id); + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + cmd_buff[1] = HLE::Applets::Applet::Create(applet_id).raw; + } + LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id); +} + +void PreloadLibraryApplet(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + AppletId applet_id = static_cast<AppletId>(cmd_buff[1]); + auto applet = HLE::Applets::Applet::Get(applet_id); + if (applet) { + LOG_WARNING(Service_APT, "applet has already been started id=%08X", applet_id); + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + cmd_buff[1] = HLE::Applets::Applet::Create(applet_id).raw; + } LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id); } diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index 72972d05..4a72b6b5 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -62,6 +62,7 @@ enum class AppletId : u32 { Extrapad = 0x208, Memolib = 0x209, Application = 0x300, + AnyLibraryApplet = 0x400, SoftwareKeyboard2 = 0x401, }; @@ -96,8 +97,26 @@ void GetSharedFont(Service::Interface* self); */ void NotifyToWait(Service::Interface* self); +/** + * APT::GetLockHandle service function + * Inputs: + * 1 : Applet attributes + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Applet attributes + * 3 : Power button state + * 4 : IPC handle descriptor + * 5 : APT mutex handle + */ void GetLockHandle(Service::Interface* self); +/** + * APT::Enable service function + * Inputs: + * 1 : Applet attributes + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ void Enable(Service::Interface* self); /** @@ -284,6 +303,17 @@ void GetAppCpuTimeLimit(Service::Interface* self); void PrepareToStartLibraryApplet(Service::Interface* self); /** + * APT::PreloadLibraryApplet service function + * Inputs: + * 0 : Command header [0x00160040] + * 1 : Id of the applet to start + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + */ +void PreloadLibraryApplet(Service::Interface* self); + +/** * APT::StartLibraryApplet service function * Inputs: * 0 : Command header [0x001E0084] diff --git a/src/core/hle/service/apt/apt_a.cpp b/src/core/hle/service/apt/apt_a.cpp index 88de339f..22800c56 100644 --- a/src/core/hle/service/apt/apt_a.cpp +++ b/src/core/hle/service/apt/apt_a.cpp @@ -21,6 +21,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x000D0080, ReceiveParameter, "ReceiveParameter"}, {0x000E0080, GlanceParameter, "GlanceParameter"}, {0x000F0100, CancelParameter, "CancelParameter"}, + {0x00160040, PreloadLibraryApplet, "PreloadLibraryApplet"}, {0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"}, {0x001E0084, StartLibraryApplet, "StartLibraryApplet"}, {0x003B0040, nullptr, "CancelLibraryApplet?"}, diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp index 396d1f04..3ac6ff94 100644 --- a/src/core/hle/service/apt/apt_s.cpp +++ b/src/core/hle/service/apt/apt_s.cpp @@ -32,9 +32,9 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00130000, nullptr, "GetPreparationState"}, {0x00140040, nullptr, "SetPreparationState"}, {0x00150140, nullptr, "PrepareToStartApplication"}, - {0x00160040, nullptr, "PreloadLibraryApplet"}, + {0x00160040, PreloadLibraryApplet, "PreloadLibraryApplet"}, {0x00170040, nullptr, "FinishPreloadingLibraryApplet"}, - {0x00180040, nullptr, "PrepareToStartLibraryApplet"}, + {0x00180040, PrepareToStartLibraryApplet,"PrepareToStartLibraryApplet"}, {0x00190040, nullptr, "PrepareToStartSystemApplet"}, {0x001A0000, nullptr, "PrepareToStartNewestHomeMenu"}, {0x001B00C4, nullptr, "StartApplication"}, diff --git a/src/core/hle/service/apt/apt_u.cpp b/src/core/hle/service/apt/apt_u.cpp index b724cd72..146bfd59 100644 --- a/src/core/hle/service/apt/apt_u.cpp +++ b/src/core/hle/service/apt/apt_u.cpp @@ -33,7 +33,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00130000, nullptr, "GetPreparationState"}, {0x00140040, nullptr, "SetPreparationState"}, {0x00150140, nullptr, "PrepareToStartApplication"}, - {0x00160040, nullptr, "PreloadLibraryApplet"}, + {0x00160040, PreloadLibraryApplet, "PreloadLibraryApplet"}, {0x00170040, nullptr, "FinishPreloadingLibraryApplet"}, {0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"}, {0x00190040, nullptr, "PrepareToStartSystemApplet"}, diff --git a/src/core/hle/service/ldr_ro.cpp b/src/core/hle/service/ldr_ro.cpp index 155b97f6..f84ce4d7 100644 --- a/src/core/hle/service/ldr_ro.cpp +++ b/src/core/hle/service/ldr_ro.cpp @@ -40,7 +40,8 @@ static void Initialize(Service::Interface* self) { cmd_buff[1] = RESULT_SUCCESS.raw; // No error - LOG_WARNING(Service_LDR, "(STUBBED) called"); + LOG_WARNING(Service_LDR, "(STUBBED) called. crs_buffer_ptr=0x%08X, crs_size=0x%08X, address=0x%08X, value=0x%08X, process=0x%08X", + crs_buffer_ptr, crs_size, address, value, process); } /** @@ -69,7 +70,8 @@ static void LoadCRR(Service::Interface* self) { cmd_buff[1] = RESULT_SUCCESS.raw; // No error - LOG_WARNING(Service_LDR, "(STUBBED) called"); + LOG_WARNING(Service_LDR, "(STUBBED) called. crs_buffer_ptr=0x%08X, crs_size=0x%08X, value=0x%08X, process=0x%08X", + crs_buffer_ptr, crs_size, value, process); } const Interface::FunctionInfo FunctionTable[] = { diff --git a/src/core/hw/y2r.cpp b/src/core/hw/y2r.cpp index f80e26ec..082a4db8 100644 --- a/src/core/hw/y2r.cpp +++ b/src/core/hw/y2r.cpp @@ -14,6 +14,7 @@ #include "common/vector_math.h" #include "core/hle/service/y2r_u.h" +#include "core/hw/y2r.h" #include "core/memory.h" namespace HW { diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index ef9584ab..243abe84 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -206,92 +206,115 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { std::map<u32, u32> ranges; } memory_accesses; + // Simple circular-replacement vertex cache + // The size has been tuned for optimal balance between hit-rate and the cost of lookup + const size_t VERTEX_CACHE_SIZE = 32; + std::array<u16, VERTEX_CACHE_SIZE> vertex_cache_ids; + std::array<VertexShader::OutputVertex, VERTEX_CACHE_SIZE> vertex_cache; + + unsigned int vertex_cache_pos = 0; + vertex_cache_ids.fill(-1); + for (unsigned int index = 0; index < regs.num_vertices; ++index) { unsigned int vertex = is_indexed ? (index_u16 ? index_address_16[index] : index_address_8[index]) : index; + // -1 is a common special value used for primitive restart. Since it's unknown if + // the PICA supports it, and it would mess up the caching, guard against it here. + ASSERT(vertex != -1); + + bool vertex_cache_hit = false; + VertexShader::OutputVertex output; + if (is_indexed) { - // TODO: Implement some sort of vertex cache! if (g_debug_context && Pica::g_debug_context->recorder) { int size = index_u16 ? 2 : 1; memory_accesses.AddAccess(base_address + index_info.offset + size * index, size); } - } - - // Initialize data for the current vertex - VertexShader::InputVertex input; - - // Load a debugging token to check whether this gets loaded by the running - // application or not. - static const float24 debug_token = float24::FromRawFloat24(0x00abcdef); - input.attr[0].w = debug_token; - - for (int i = 0; i < attribute_config.GetNumTotalAttributes(); ++i) { - // Load the default attribute if we're configured to do so, this data will be overwritten by the loader data if it's set - if (attribute_config.IsDefaultAttribute(i)) { - input.attr[i] = g_state.vs.default_attributes[i]; - LOG_TRACE(HW_GPU, "Loaded default attribute %x for vertex %x (index %x): (%f, %f, %f, %f)", - i, vertex, index, - input.attr[i][0].ToFloat32(), input.attr[i][1].ToFloat32(), - input.attr[i][2].ToFloat32(), input.attr[i][3].ToFloat32()); - } - // Load per-vertex data from the loader arrays - for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { - u32 source_addr = vertex_attribute_sources[i] + vertex_attribute_strides[i] * vertex + comp * vertex_attribute_element_size[i]; - const u8* srcdata = Memory::GetPhysicalPointer(source_addr); - - if (g_debug_context && Pica::g_debug_context->recorder) { - memory_accesses.AddAccess(source_addr, - (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::FLOAT) ? 4 - : (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::SHORT) ? 2 : 1); + for (unsigned int i = 0; i < VERTEX_CACHE_SIZE; ++i) { + if (vertex == vertex_cache_ids[i]) { + output = vertex_cache[i]; + vertex_cache_hit = true; + break; } - - const float srcval = (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::BYTE) ? *(s8*)srcdata : - (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::UBYTE) ? *(u8*)srcdata : - (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::SHORT) ? *(s16*)srcdata : - *(float*)srcdata; - - input.attr[i][comp] = float24::FromFloat32(srcval); - LOG_TRACE(HW_GPU, "Loaded component %x of attribute %x for vertex %x (index %x) from 0x%08x + 0x%08lx + 0x%04lx: %f", - comp, i, vertex, index, - attribute_config.GetPhysicalBaseAddress(), - vertex_attribute_sources[i] - base_address, - vertex_attribute_strides[i] * vertex + comp * vertex_attribute_element_size[i], - input.attr[i][comp].ToFloat32()); } } - // HACK: Some games do not initialize the vertex position's w component. This leads - // to critical issues since it messes up perspective division. As a - // workaround, we force the fourth component to 1.0 if we find this to be the - // case. - // To do this, we additionally have to assume that the first input attribute - // is the vertex position, since there's no information about this other than - // the empiric observation that this is usually the case. - if (input.attr[0].w == debug_token) - input.attr[0].w = float24::FromFloat32(1.0); + if (!vertex_cache_hit) { + // Initialize data for the current vertex + VertexShader::InputVertex input; + + for (int i = 0; i < attribute_config.GetNumTotalAttributes(); ++i) { + if (vertex_attribute_elements[i] != 0) { + // Default attribute values set if array elements have < 4 components. This + // is *not* carried over from the default attribute settings even if they're + // enabled for this attribute. + static const float24 zero = float24::FromFloat32(0.0f); + static const float24 one = float24::FromFloat32(1.0f); + input.attr[i] = Math::Vec4<float24>(zero, zero, zero, one); + + // Load per-vertex data from the loader arrays + for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { + u32 source_addr = vertex_attribute_sources[i] + vertex_attribute_strides[i] * vertex + comp * vertex_attribute_element_size[i]; + const u8* srcdata = Memory::GetPhysicalPointer(source_addr); + + if (g_debug_context && Pica::g_debug_context->recorder) { + memory_accesses.AddAccess(source_addr, + (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::FLOAT) ? 4 + : (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::SHORT) ? 2 : 1); + } + + const float srcval = (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::BYTE) ? *(s8*)srcdata : + (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::UBYTE) ? *(u8*)srcdata : + (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::SHORT) ? *(s16*)srcdata : + *(float*)srcdata; + + input.attr[i][comp] = float24::FromFloat32(srcval); + LOG_TRACE(HW_GPU, "Loaded component %x of attribute %x for vertex %x (index %x) from 0x%08x + 0x%08lx + 0x%04lx: %f", + comp, i, vertex, index, + attribute_config.GetPhysicalBaseAddress(), + vertex_attribute_sources[i] - base_address, + vertex_attribute_strides[i] * vertex + comp * vertex_attribute_element_size[i], + input.attr[i][comp].ToFloat32()); + } + } else if (attribute_config.IsDefaultAttribute(i)) { + // Load the default attribute if we're configured to do so + input.attr[i] = g_state.vs.default_attributes[i]; + LOG_TRACE(HW_GPU, "Loaded default attribute %x for vertex %x (index %x): (%f, %f, %f, %f)", + i, vertex, index, + input.attr[i][0].ToFloat32(), input.attr[i][1].ToFloat32(), + input.attr[i][2].ToFloat32(), input.attr[i][3].ToFloat32()); + } else { + // TODO(yuriks): In this case, no data gets loaded and the vertex + // remains with the last value it had. This isn't currently maintained + // as global state, however, and so won't work in Citra yet. + } + } - if (g_debug_context) - g_debug_context->OnEvent(DebugContext::Event::VertexLoaded, (void*)&input); + if (g_debug_context) + g_debug_context->OnEvent(DebugContext::Event::VertexLoaded, (void*)&input); #if PICA_DUMP_GEOMETRY - // NOTE: When dumping geometry, we simply assume that the first input attribute - // corresponds to the position for now. - DebugUtils::GeometryDumper::Vertex dumped_vertex = { - input.attr[0][0].ToFloat32(), input.attr[0][1].ToFloat32(), input.attr[0][2].ToFloat32() - }; - using namespace std::placeholders; - dumping_primitive_assembler.SubmitVertex(dumped_vertex, - std::bind(&DebugUtils::GeometryDumper::AddTriangle, - &geometry_dumper, _1, _2, _3)); + // NOTE: When dumping geometry, we simply assume that the first input attribute + // corresponds to the position for now. + DebugUtils::GeometryDumper::Vertex dumped_vertex = { + input.attr[0][0].ToFloat32(), input.attr[0][1].ToFloat32(), input.attr[0][2].ToFloat32() + }; + using namespace std::placeholders; + dumping_primitive_assembler.SubmitVertex(dumped_vertex, + std::bind(&DebugUtils::GeometryDumper::AddTriangle, + &geometry_dumper, _1, _2, _3)); #endif - // Send to vertex shader - VertexShader::OutputVertex output = VertexShader::RunShader(input, attribute_config.GetNumTotalAttributes(), g_state.regs.vs, g_state.vs); + // Send to vertex shader + output = VertexShader::RunShader(input, attribute_config.GetNumTotalAttributes(), g_state.regs.vs, g_state.vs); - if (is_indexed) { - // TODO: Add processed vertex to vertex cache! + if (is_indexed) { + vertex_cache[vertex_cache_pos] = output; + vertex_cache_ids[vertex_cache_pos] = vertex; + vertex_cache_pos = (vertex_cache_pos + 1) % VERTEX_CACHE_SIZE; + } } if (Settings::values.use_hw_renderer) { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 2db845da..1fc4e56b 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -7,6 +7,7 @@ #include "common/color.h" #include "common/math_util.h" +#include "common/profiler.h" #include "core/hw/gpu.h" #include "core/memory.h" @@ -873,11 +874,15 @@ void RasterizerOpenGL::ReloadDepthBuffer() { state.Apply(); } +Common::Profiling::TimingCategory buffer_commit_category("Framebuffer Commit"); + void RasterizerOpenGL::CommitColorBuffer() { if (last_fb_color_addr != 0) { u8* color_buffer = Memory::GetPhysicalPointer(last_fb_color_addr); if (color_buffer != nullptr) { + Common::Profiling::ScopeTimer timer(buffer_commit_category); + u32 bytes_per_pixel = Pica::Regs::BytesPerColorPixel(fb_color_texture.format); std::unique_ptr<u8[]> temp_gl_color_buffer(new u8[fb_color_texture.width * fb_color_texture.height * bytes_per_pixel]); @@ -913,6 +918,8 @@ void RasterizerOpenGL::CommitDepthBuffer() { u8* depth_buffer = Memory::GetPhysicalPointer(last_fb_depth_addr); if (depth_buffer != nullptr) { + Common::Profiling::ScopeTimer timer(buffer_commit_category); + u32 bytes_per_pixel = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format); // OpenGL needs 4 bpp alignment for D24 diff --git a/src/video_core/vertex_shader.cpp b/src/video_core/vertex_shader.cpp index 960ae577..5f66f345 100644 --- a/src/video_core/vertex_shader.cpp +++ b/src/video_core/vertex_shader.cpp @@ -609,6 +609,12 @@ OutputVertex RunShader(const InputVertex& input, int num_attributes, const Regs: } } + // The hardware takes the absolute and saturates vertex colors like this, *before* doing interpolation + for (int i = 0; i < 4; ++i) { + ret.color[i] = float24::FromFloat32( + std::fmin(std::fabs(ret.color[i].ToFloat32()), 1.0f)); + } + LOG_TRACE(Render_Software, "Output vertex: pos (%.2f, %.2f, %.2f, %.2f), col(%.2f, %.2f, %.2f, %.2f), tc0(%.2f, %.2f)", ret.pos.x.ToFloat32(), ret.pos.y.ToFloat32(), ret.pos.z.ToFloat32(), ret.pos.w.ToFloat32(), ret.color.x.ToFloat32(), ret.color.y.ToFloat32(), ret.color.z.ToFloat32(), ret.color.w.ToFloat32(), |