diff options
Diffstat (limited to 'src')
38 files changed, 1242 insertions, 609 deletions
diff --git a/src/citra_qt/debugger/callstack.cpp b/src/citra_qt/debugger/callstack.cpp index 895851be3..a9ec2f7fe 100644 --- a/src/citra_qt/debugger/callstack.cpp +++ b/src/citra_qt/debugger/callstack.cpp @@ -27,10 +27,10 @@ void CallstackWidget::OnCPUStepped() ARM_Interface* app_core = Core::g_app_core; u32 sp = app_core->GetReg(13); //stack pointer - u32 addr, ret_addr, call_addr, func_addr; + u32 ret_addr, call_addr, func_addr; int counter = 0; - for (int addr = 0x10000000; addr >= sp; addr -= 4) + for (u32 addr = 0x10000000; addr >= sp; addr -= 4) { ret_addr = Memory::Read32(addr); call_addr = ret_addr - 4; //get call address??? diff --git a/src/citra_qt/debugger/graphics_breakpoints.cpp b/src/citra_qt/debugger/graphics_breakpoints.cpp index e391f895b..9486f06cc 100644 --- a/src/citra_qt/debugger/graphics_breakpoints.cpp +++ b/src/citra_qt/debugger/graphics_breakpoints.cpp @@ -39,15 +39,17 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const switch (index.column()) { case 0: { - std::map<Pica::DebugContext::Event, QString> map; - map.insert({Pica::DebugContext::Event::CommandLoaded, tr("Pica command loaded")}); - map.insert({Pica::DebugContext::Event::CommandProcessed, tr("Pica command processed")}); - map.insert({Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incoming primitive batch")}); - map.insert({Pica::DebugContext::Event::FinishedPrimitiveBatch, tr("Finished primitive batch")}); + static const std::map<Pica::DebugContext::Event, QString> map = { + { Pica::DebugContext::Event::CommandLoaded, tr("Pica command loaded") }, + { Pica::DebugContext::Event::CommandProcessed, tr("Pica command processed") }, + { Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incoming primitive batch") }, + { Pica::DebugContext::Event::FinishedPrimitiveBatch, tr("Finished primitive batch") }, + { Pica::DebugContext::Event::VertexLoaded, tr("Vertex Loaded") } + }; _dbg_assert_(Debug_GPU, map.size() == static_cast<size_t>(Pica::DebugContext::Event::NumEvents)); - return map[event]; + return (map.find(event) != map.end()) ? map.at(event) : QString(); } case 1: diff --git a/src/citra_qt/debugger/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics_cmdlists.cpp index 83102c647..753cc25da 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics_cmdlists.cpp @@ -24,7 +24,7 @@ QImage LoadTexture(u8* src, const Pica::DebugUtils::TextureInfo& info) { QImage decoded_image(info.width, info.height, QImage::Format_ARGB32); for (int y = 0; y < info.height; ++y) { for (int x = 0; x < info.width; ++x) { - Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(src, x, y, info); + Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(src, x, y, info, true); decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); } } @@ -47,7 +47,7 @@ public: }; TextureInfoDockWidget::TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo& info, QWidget* parent) - : QDockWidget(tr("Texture 0x%1").arg(info.address, 8, 16, QLatin1Char('0'))), + : QDockWidget(tr("Texture 0x%1").arg(info.physical_address, 8, 16, QLatin1Char('0'))), info(info) { QWidget* main_widget = new QWidget; @@ -60,7 +60,7 @@ TextureInfoDockWidget::TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo phys_address_spinbox->SetBase(16); phys_address_spinbox->SetRange(0, 0xFFFFFFFF); phys_address_spinbox->SetPrefix("0x"); - phys_address_spinbox->SetValue(info.address); + phys_address_spinbox->SetValue(info.physical_address); connect(phys_address_spinbox, SIGNAL(ValueChanged(qint64)), this, SLOT(OnAddressChanged(qint64))); QComboBox* format_choice = new QComboBox; @@ -69,6 +69,13 @@ TextureInfoDockWidget::TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo format_choice->addItem(tr("RGBA5551")); format_choice->addItem(tr("RGB565")); format_choice->addItem(tr("RGBA4")); + format_choice->addItem(tr("IA8")); + format_choice->addItem(tr("UNK6")); + format_choice->addItem(tr("I8")); + format_choice->addItem(tr("A8")); + format_choice->addItem(tr("IA4")); + format_choice->addItem(tr("UNK10")); + format_choice->addItem(tr("A4")); format_choice->setCurrentIndex(static_cast<int>(info.format)); connect(format_choice, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFormatChanged(int))); @@ -125,7 +132,7 @@ TextureInfoDockWidget::TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo } void TextureInfoDockWidget::OnAddressChanged(qint64 value) { - info.address = value; + info.physical_address = value; emit UpdatePixmap(ReloadPixmap()); } @@ -150,7 +157,7 @@ void TextureInfoDockWidget::OnStrideChanged(int value) { } QPixmap TextureInfoDockWidget::ReloadPixmap() const { - u8* src = Memory::GetPointer(info.address); + u8* src = Memory::GetPointer(Pica::PAddrToVAddr(info.physical_address)); return QPixmap::fromImage(LoadTexture(src, info)); } @@ -223,9 +230,21 @@ void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) { const int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toInt(); - if (COMMAND_IN_RANGE(command_id, texture0)) { - auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(Pica::registers.texture0, - Pica::registers.texture0_format); + if (COMMAND_IN_RANGE(command_id, texture0) || + COMMAND_IN_RANGE(command_id, texture1) || + COMMAND_IN_RANGE(command_id, texture2)) { + + unsigned index; + if (COMMAND_IN_RANGE(command_id, texture0)) { + index = 0; + } else if (COMMAND_IN_RANGE(command_id, texture1)) { + index = 1; + } else { + index = 2; + } + auto config = Pica::registers.GetTextures()[index].config; + auto format = Pica::registers.GetTextures()[index].format; + auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format); // TODO: Instead, emit a signal here to be caught by the main window widget. auto main_window = static_cast<QMainWindow*>(parent()); @@ -237,10 +256,23 @@ void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) { QWidget* new_info_widget; const int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toInt(); - if (COMMAND_IN_RANGE(command_id, texture0)) { - u8* src = Memory::GetPointer(Pica::registers.texture0.GetPhysicalAddress()); - auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(Pica::registers.texture0, - Pica::registers.texture0_format); + if (COMMAND_IN_RANGE(command_id, texture0) || + COMMAND_IN_RANGE(command_id, texture1) || + COMMAND_IN_RANGE(command_id, texture2)) { + + unsigned index; + if (COMMAND_IN_RANGE(command_id, texture0)) { + index = 0; + } else if (COMMAND_IN_RANGE(command_id, texture1)) { + index = 1; + } else { + index = 2; + } + auto config = Pica::registers.GetTextures()[index].config; + auto format = Pica::registers.GetTextures()[index].format; + + auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format); + u8* src = Memory::GetPointer(Pica::PAddrToVAddr(config.GetPhysicalAddress())); new_info_widget = new TextureInfoWidget(src, info); } else { new_info_widget = new QWidget; diff --git a/src/citra_qt/debugger/graphics_framebuffer.cpp b/src/citra_qt/debugger/graphics_framebuffer.cpp index fb62e8f17..dd41c3880 100644 --- a/src/citra_qt/debugger/graphics_framebuffer.cpp +++ b/src/citra_qt/debugger/graphics_framebuffer.cpp @@ -125,7 +125,8 @@ GraphicsFramebufferWidget::GraphicsFramebufferWidget(std::shared_ptr<Pica::Debug setWidget(main_widget); // Load current data - TODO: Make sure this works when emulation is not running - emit Update(); + if (debug_context && debug_context->at_breakpoint) + emit Update(); widget()->setEnabled(false); // TODO: Only enable if currently at breakpoint } @@ -198,7 +199,7 @@ void GraphicsFramebufferWidget::OnUpdate() auto framebuffer = Pica::registers.framebuffer; using Framebuffer = decltype(framebuffer); - framebuffer_address = framebuffer.GetColorBufferAddress(); + framebuffer_address = framebuffer.GetColorBufferPhysicalAddress(); framebuffer_width = framebuffer.GetWidth(); framebuffer_height = framebuffer.GetHeight(); framebuffer_format = static_cast<Format>(framebuffer.color_format); @@ -223,9 +224,9 @@ void GraphicsFramebufferWidget::OnUpdate() case Format::RGBA8: { QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); - u32* color_buffer = (u32*)Memory::GetPointer(framebuffer_address); - for (int y = 0; y < framebuffer_height; ++y) { - for (int x = 0; x < framebuffer_width; ++x) { + u32* color_buffer = (u32*)Memory::GetPointer(Pica::PAddrToVAddr(framebuffer_address)); + for (unsigned y = 0; y < framebuffer_height; ++y) { + for (unsigned x = 0; x < framebuffer_width; ++x) { u32 value = *(color_buffer + x + y * framebuffer_width); decoded_image.setPixel(x, y, qRgba((value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF, 255/*value >> 24*/)); @@ -238,9 +239,9 @@ void GraphicsFramebufferWidget::OnUpdate() case Format::RGB8: { QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); - u8* color_buffer = Memory::GetPointer(framebuffer_address); - for (int y = 0; y < framebuffer_height; ++y) { - for (int x = 0; x < framebuffer_width; ++x) { + u8* color_buffer = Memory::GetPointer(Pica::PAddrToVAddr(framebuffer_address)); + for (unsigned y = 0; y < framebuffer_height; ++y) { + for (unsigned x = 0; x < framebuffer_width; ++x) { u8* pixel_pointer = color_buffer + x * 3 + y * 3 * framebuffer_width; decoded_image.setPixel(x, y, qRgba(pixel_pointer[0], pixel_pointer[1], pixel_pointer[2], 255/*value >> 24*/)); @@ -253,9 +254,9 @@ void GraphicsFramebufferWidget::OnUpdate() case Format::RGBA5551: { QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); - u32* color_buffer = (u32*)Memory::GetPointer(framebuffer_address); - for (int y = 0; y < framebuffer_height; ++y) { - for (int x = 0; x < framebuffer_width; ++x) { + u32* color_buffer = (u32*)Memory::GetPointer(Pica::PAddrToVAddr(framebuffer_address)); + for (unsigned y = 0; y < framebuffer_height; ++y) { + for (unsigned x = 0; x < framebuffer_width; ++x) { u16 value = *(u16*)(((u8*)color_buffer) + x * 2 + y * framebuffer_width * 2); u8 r = (value >> 11) & 0x1F; u8 g = (value >> 6) & 0x1F; diff --git a/src/citra_qt/util/spinbox.cpp b/src/citra_qt/util/spinbox.cpp index 3f28fdbab..f9988409f 100644 --- a/src/citra_qt/util/spinbox.cpp +++ b/src/citra_qt/util/spinbox.cpp @@ -238,7 +238,7 @@ QValidator::State CSpinBox::validate(QString& input, int& pos) const if (!prefix.isEmpty() && input.left(prefix.length()) != prefix) return QValidator::Invalid; - unsigned strpos = prefix.length(); + int strpos = prefix.length(); // Empty "numbers" allowed as intermediate values if (strpos >= input.length() - HasSign() - suffix.length()) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 15989708d..3c3419bbc 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -49,6 +49,7 @@ set(HEADERS logging/filter.h logging/log.h logging/backend.h + make_unique.h math_util.h mem_arena.h memory_util.h diff --git a/src/common/bit_field.h b/src/common/bit_field.h index b5977ee56..c38a3fb08 100644 --- a/src/common/bit_field.h +++ b/src/common/bit_field.h @@ -142,7 +142,7 @@ public: __forceinline BitField& operator=(T val) { - storage = (storage & ~GetMask()) | (((StorageType)val << position) & GetMask()); + Assign(val); return *this; } @@ -151,6 +151,10 @@ public: return Value(); } + __forceinline void Assign(const T& value) { + storage = (storage & ~GetMask()) | (((StorageType)value << position) & GetMask()); + } + __forceinline T Value() const { if (std::numeric_limits<T>::is_signed) diff --git a/src/common/make_unique.h b/src/common/make_unique.h new file mode 100644 index 000000000..2a7b76412 --- /dev/null +++ b/src/common/make_unique.h @@ -0,0 +1,16 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> + +namespace Common { + +template <typename T, typename... Args> +std::unique_ptr<T> make_unique(Args&&... args) { + return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); +} + +} // namespace diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp index 68012bffd..84b4a38f0 100644 --- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp +++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp @@ -1266,6 +1266,13 @@ typedef struct _smla_inst { unsigned int Rn; } smla_inst; +typedef struct umaal_inst { + unsigned int Rn; + unsigned int Rm; + unsigned int RdHi; + unsigned int RdLo; +} umaal_inst; + typedef struct _umlal_inst { unsigned int S; unsigned int Rm; @@ -3010,7 +3017,26 @@ ARM_INST_PTR INTERPRETER_TRANSLATE(uhaddsubx)(unsigned int inst, int index) { UN ARM_INST_PTR INTERPRETER_TRANSLATE(uhsub16)(unsigned int inst, int index) { UNIMPLEMENTED_INSTRUCTION("UHSUB16"); } ARM_INST_PTR INTERPRETER_TRANSLATE(uhsub8)(unsigned int inst, int index) { UNIMPLEMENTED_INSTRUCTION("UHSUB8"); } ARM_INST_PTR INTERPRETER_TRANSLATE(uhsubaddx)(unsigned int inst, int index) { UNIMPLEMENTED_INSTRUCTION("UHSUBADDX"); } -ARM_INST_PTR INTERPRETER_TRANSLATE(umaal)(unsigned int inst, int index) { UNIMPLEMENTED_INSTRUCTION("UMAAL"); } +ARM_INST_PTR INTERPRETER_TRANSLATE(umaal)(unsigned int inst, int index) +{ + arm_inst* const inst_base = (arm_inst*)AllocBuffer(sizeof(arm_inst) + sizeof(umaal_inst)); + umaal_inst* const inst_cream = (umaal_inst*)inst_base->component; + + inst_base->cond = BITS(inst, 28, 31); + inst_base->idx = index; + inst_base->br = NON_BRANCH; + inst_base->load_r15 = 0; + + inst_cream->Rm = BITS(inst, 8, 11); + inst_cream->Rn = BITS(inst, 0, 3); + inst_cream->RdLo = BITS(inst, 12, 15); + inst_cream->RdHi = BITS(inst, 16, 19); + + if (CHECK_RM || CHECK_RN) + inst_base->load_r15 = 1; + + return inst_base; +} ARM_INST_PTR INTERPRETER_TRANSLATE(umlal)(unsigned int inst, int index) { arm_inst *inst_base = (arm_inst *)AllocBuffer(sizeof(arm_inst) + sizeof(umlal_inst)); @@ -6374,6 +6400,26 @@ unsigned InterpreterMainLoop(ARMul_State* state) UHSUB8_INST: UHSUBADDX_INST: UMAAL_INST: + { + INC_ICOUNTER; + if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { + umaal_inst* const inst_cream = (umaal_inst*)inst_base->component; + + const u32 rm = RM; + const u32 rn = RN; + const u32 rd_lo = RDLO; + const u32 rd_hi = RDHI; + + const u64 result = (rm * rn) + rd_lo + rd_hi; + + RDLO = (result & 0xFFFFFFFF); + RDHI = ((result >> 32) & 0xFFFFFFFF); + } + cpu->Reg[15] += GET_INST_SIZE(cpu); + INC_PC(sizeof(umaal_inst)); + FETCH_INST; + GOTO_NEXT_INST; + } UMLAL_INST: { INC_ICOUNTER; diff --git a/src/core/arm/interpreter/armemu.cpp b/src/core/arm/interpreter/armemu.cpp index 07d205755..610e04f10 100644 --- a/src/core/arm/interpreter/armemu.cpp +++ b/src/core/arm/interpreter/armemu.cpp @@ -5681,11 +5681,8 @@ L_stm_s_takeabort: /* Attempt to emulate an ARMv6 instruction. Returns non-zero upon success. */ - static int - handle_v6_insn (ARMul_State * state, ARMword instr) { - ARMword lhs, temp; - - switch (BITS (20, 27)) { + static int handle_v6_insn(ARMul_State* state, ARMword instr) { + switch (BITS(20, 27)) { case 0x03: printf ("Unhandled v6 insn: ldr\n"); break; @@ -5719,7 +5716,7 @@ L_stm_s_takeabort: /* strex */ u32 l = LHSReg; u32 r = RHSReg; - lhs = LHS; + u32 lhs = LHS; bool enter = false; @@ -5744,7 +5741,7 @@ L_stm_s_takeabort: case 0x19: /* ldrex */ if (BITS(4, 7) == 0x9) { - lhs = LHS; + u32 lhs = LHS; state->currentexaddr = lhs; state->currentexval = ARMul_ReadWord(state, lhs); @@ -5763,7 +5760,7 @@ L_stm_s_takeabort: case 0x1c: if (BITS(4, 7) == 0x9) { /* strexb */ - lhs = LHS; + u32 lhs = LHS; bool enter = false; @@ -5793,11 +5790,11 @@ L_stm_s_takeabort: case 0x1d: if ((BITS(4, 7)) == 0x9) { /* ldrexb */ - temp = LHS; - LoadByte(state, instr, temp, LUNSIGNED); + u32 lhs = LHS; + LoadByte(state, instr, lhs, LUNSIGNED); - state->currentexaddr = temp; - state->currentexval = (u32)ARMul_ReadByte(state, temp); + state->currentexaddr = lhs; + state->currentexval = (u32)ARMul_ReadByte(state, lhs); //state->Reg[BITS(12, 15)] = ARMul_LoadByte(state, state->Reg[BITS(16, 19)]); //printf("ldrexb\n"); @@ -5827,9 +5824,9 @@ L_stm_s_takeabort: case 0x3f: printf ("Unhandled v6 insn: rbit\n"); break; - case 0x61: // SSUB16, SADD16, SSAX, and SASX - if ((instr & 0xFF0) == 0xf70 || (instr & 0xFF0) == 0xf10 || - (instr & 0xFF0) == 0xf50 || (instr & 0xFF0) == 0xf30) + case 0x61: // SADD16, SASX, SSAX, and SSUB16 + if ((instr & 0xFF0) == 0xf10 || (instr & 0xFF0) == 0xf30 || + (instr & 0xFF0) == 0xf50 || (instr & 0xFF0) == 0xf70) { const u8 rd_idx = BITS(12, 15); const u8 rm_idx = BITS(0, 3); @@ -5842,25 +5839,25 @@ L_stm_s_takeabort: s32 lo_result; s32 hi_result; - // SSUB16 - if ((instr & 0xFF0) == 0xf70) { - lo_result = (rn_lo - rm_lo); - hi_result = (rn_hi - rm_hi); - } // SADD16 - else if ((instr & 0xFF0) == 0xf10) { + if ((instr & 0xFF0) == 0xf10) { lo_result = (rn_lo + rm_lo); hi_result = (rn_hi + rm_hi); } + // SASX + else if ((instr & 0xFF0) == 0xf30) { + lo_result = (rn_lo - rm_hi); + hi_result = (rn_hi + rm_lo); + } // SSAX else if ((instr & 0xFF0) == 0xf50) { lo_result = (rn_lo + rm_hi); hi_result = (rn_hi - rm_lo); } - // SASX + // SSUB16 else { - lo_result = (rn_lo - rm_hi); - hi_result = (rn_hi + rm_lo); + lo_result = (rn_lo - rm_lo); + hi_result = (rn_hi - rm_hi); } state->Reg[rd_idx] = (lo_result & 0xFFFF) | ((hi_result & 0xFFFF) << 16); @@ -5881,12 +5878,87 @@ L_stm_s_takeabort: state->Cpsr &= ~(1 << 19); } return 1; - } else { - printf("Unhandled v6 insn: %08x", BITS(20, 27)); + } + // SADD8/SSUB8 + else if ((instr & 0xFF0) == 0xf90 || (instr & 0xFF0) == 0xff0) + { + const u8 rd_idx = BITS(12, 15); + const u8 rm_idx = BITS(0, 3); + const u8 rn_idx = BITS(16, 19); + const u32 rm_val = state->Reg[rm_idx]; + const u32 rn_val = state->Reg[rn_idx]; + + u8 lo_val1; + u8 lo_val2; + u8 hi_val1; + u8 hi_val2; + + // SADD8 + if ((instr & 0xFF0) == 0xf90) { + lo_val1 = (u8)((rn_val & 0xFF) + (rm_val & 0xFF)); + lo_val2 = (u8)(((rn_val >> 8) & 0xFF) + ((rm_val >> 8) & 0xFF)); + hi_val1 = (u8)(((rn_val >> 16) & 0xFF) + ((rm_val >> 16) & 0xFF)); + hi_val2 = (u8)(((rn_val >> 24) & 0xFF) + ((rm_val >> 24) & 0xFF)); + + if (lo_val1 & 0x80) + state->Cpsr |= (1 << 16); + else + state->Cpsr &= ~(1 << 16); + + if (lo_val2 & 0x80) + state->Cpsr |= (1 << 17); + else + state->Cpsr &= ~(1 << 17); + + if (hi_val1 & 0x80) + state->Cpsr |= (1 << 18); + else + state->Cpsr &= ~(1 << 18); + + if (hi_val2 & 0x80) + state->Cpsr |= (1 << 19); + else + state->Cpsr &= ~(1 << 19); + } + // SSUB8 + else { + lo_val1 = (u8)((rn_val & 0xFF) - (rm_val & 0xFF)); + lo_val2 = (u8)(((rn_val >> 8) & 0xFF) - ((rm_val >> 8) & 0xFF)); + hi_val1 = (u8)(((rn_val >> 16) & 0xFF) - ((rm_val >> 16) & 0xFF)); + hi_val2 = (u8)(((rn_val >> 24) & 0xFF) - ((rm_val >> 24) & 0xFF)); + + if (!(lo_val1 & 0x80)) + state->Cpsr |= (1 << 16); + else + state->Cpsr &= ~(1 << 16); + + if (!(lo_val2 & 0x80)) + state->Cpsr |= (1 << 17); + else + state->Cpsr &= ~(1 << 17); + + if (!(hi_val1 & 0x80)) + state->Cpsr |= (1 << 18); + else + state->Cpsr &= ~(1 << 18); + + if (!(hi_val2 & 0x80)) + state->Cpsr |= (1 << 19); + else + state->Cpsr &= ~(1 << 19); + } + + state->Reg[rd_idx] = (lo_val1 | lo_val2 << 8 | hi_val1 << 16 | hi_val2 << 24); + return 1; + } + else { + printf("Unhandled v6 insn: %08x", instr); } break; - case 0x62: // QSUB16 and QADD16 - if ((instr & 0xFF0) == 0xf70 || (instr & 0xFF0) == 0xf10) { + case 0x62: // QADD16, QASX, QSAX, and QSUB16 + if ((instr & 0xFF0) == 0xf10 || (instr & 0xFF0) == 0xf30 || + (instr & 0xFF0) == 0xf50 || (instr & 0xFF0) == 0xf70) + { const u8 rd_idx = BITS(12, 15); const u8 rn_idx = BITS(16, 19); const u8 rm_idx = BITS(0, 3); @@ -5898,15 +5970,26 @@ L_stm_s_takeabort: s32 lo_result; s32 hi_result; + // QADD16 + if ((instr & 0xFF0) == 0xf10) { + lo_result = (rn_lo + rm_lo); + hi_result = (rn_hi + rm_hi); + } + // QASX + else if ((instr & 0xFF0) == 0xf30) { + lo_result = (rn_lo - rm_hi); + hi_result = (rn_hi + rm_lo); + } + // QSAX + else if ((instr & 0xFF0) == 0xf50) { + lo_result = (rn_lo + rm_hi); + hi_result = (rn_hi - rm_lo); + } // QSUB16 - if ((instr & 0xFF0) == 0xf70) { + else { lo_result = (rn_lo - rm_lo); hi_result = (rn_hi - rm_hi); } - else { // QADD16 - lo_result = (rn_lo + rm_lo); - hi_result = (rn_hi + rm_hi); - } if (lo_result > 0x7FFF) lo_result = 0x7FFF; @@ -6081,22 +6164,28 @@ L_stm_s_takeabort: //ichfly //SSAT16 { - u8 tar = BITS(12, 15); - u8 src = BITS(0, 3); - u8 val = BITS(16, 19) + 1; - s16 a1 = (state->Reg[src]); - s16 a2 = (state->Reg[src] >> 0x10); - s16 min = (s16)(0x8000 >> (16 - val)); - s16 max = 0x7FFF >> (16 - val); - if (min > a1) a1 = min; - if (max < a1) a1 = max; - if (min > a2) a2 = min; - if (max < a2) a2 = max; - u32 temp2 = ((u32)(a2)) << 0x10; - state->Reg[tar] = (a1 & 0xFFFF) | (temp2); + const u8 rd_idx = BITS(12, 15); + const u8 rn_idx = BITS(0, 3); + const u8 num_bits = BITS(16, 19) + 1; + const s16 min = -(0x8000 >> (16 - num_bits)); + const s16 max = (0x7FFF >> (16 - num_bits)); + s16 rn_lo = (state->Reg[rn_idx]); + s16 rn_hi = (state->Reg[rn_idx] >> 16); + + if (rn_lo > max) + rn_lo = max; + else if (rn_lo < min) + rn_lo = min; + + if (rn_hi > max) + rn_hi = max; + else if (rn_hi < min) + rn_hi = min; + + state->Reg[rd_idx] = (rn_lo & 0xFFFF) | ((rn_hi & 0xFFFF) << 16); + return 1; } - return 1; default: break; } @@ -6109,7 +6198,7 @@ L_stm_s_takeabort: break; } - Rm = ((state->Reg[BITS(0, 3)] >> ror) & 0xFF) | ((state->Reg[BITS(0, 3)] << (32 - ror)) & 0xFF) & 0xFF; + Rm = ((state->Reg[BITS(0, 3)] >> ror) & 0xFF) | (((state->Reg[BITS(0, 3)] << (32 - ror)) & 0xFF) & 0xFF); if (Rm & 0x80) Rm |= 0xffffff00; @@ -6154,7 +6243,7 @@ L_stm_s_takeabort: if (ror == -1) break; - Rm = ((state->Reg[BITS(0, 3)] >> ror) & 0xFFFF) | ((state->Reg[BITS(0, 3)] << (32 - ror)) & 0xFFFF) & 0xFFFF; + Rm = ((state->Reg[BITS(0, 3)] >> ror) & 0xFFFF) | (((state->Reg[BITS(0, 3)] << (32 - ror)) & 0xFFFF) & 0xFFFF); if (Rm & 0x8000) Rm |= 0xffff0000; @@ -6250,7 +6339,7 @@ L_stm_s_takeabort: break; } - Rm = ((state->Reg[BITS(0, 3)] >> ror) & 0xFF) | ((state->Reg[BITS(0, 3)] << (32 - ror)) & 0xFF) & 0xFF; + Rm = ((state->Reg[BITS(0, 3)] >> ror) & 0xFF) | (((state->Reg[BITS(0, 3)] << (32 - ror)) & 0xFF) & 0xFF); if (BITS(16, 19) == 0xf) /* UXTB */ @@ -6294,7 +6383,7 @@ L_stm_s_takeabort: if (ror == -1) break; - Rm = ((state->Reg[BITS(0, 3)] >> ror) & 0xFFFF) | ((state->Reg[BITS(0, 3)] << (32 - ror)) & 0xFFFF) & 0xFFFF; + Rm = ((state->Reg[BITS(0, 3)] >> ror) & 0xFFFF) | (((state->Reg[BITS(0, 3)] << (32 - ror)) & 0xFFFF) & 0xFFFF); /* UXT */ /* state->Reg[BITS (12, 15)] = Rm; */ @@ -6317,11 +6406,14 @@ L_stm_s_takeabort: } case 0x70: // ichfly - // SMUAD, SMUSD, SMLAD - if ((instr & 0xf0d0) == 0xf010 || (instr & 0xf0d0) == 0xf050 || (instr & 0xd0) == 0x10) { + // SMUAD, SMUSD, SMLAD, and SMLSD + if ((instr & 0xf0d0) == 0xf010 || (instr & 0xf0d0) == 0xf050 || + (instr & 0xd0) == 0x10 || (instr & 0xd0) == 0x50) + { const u8 rd_idx = BITS(16, 19); const u8 rn_idx = BITS(0, 3); const u8 rm_idx = BITS(8, 11); + const u8 ra_idx = BITS(12, 15); const bool do_swap = (BIT(5) == 1); u32 rm_val = state->Reg[rm_idx]; @@ -6344,13 +6436,14 @@ L_stm_s_takeabort: state->Reg[rd_idx] = (rn_lo * rm_lo) - (rn_hi * rm_hi); } // SMLAD - else { - const u8 ra_idx = BITS(12, 15); + else if ((instr & 0xd0) == 0x10) { state->Reg[rd_idx] = (rn_lo * rm_lo) + (rn_hi * rm_hi) + (s32)state->Reg[ra_idx]; } + // SMLSD + else { + state->Reg[rd_idx] = ((rn_lo * rm_lo) - (rn_hi * rm_hi)) + (s32)state->Reg[ra_idx]; + } return 1; - } else { - printf ("Unhandled v6 insn: smlsd\n"); } break; case 0x74: @@ -6360,7 +6453,30 @@ L_stm_s_takeabort: printf ("Unhandled v6 insn: smmla/smmls/smmul\n"); break; case 0x78: - printf ("Unhandled v6 insn: usad/usada8\n"); + if (BITS(20, 24) == 0x18) + { + const u8 rm_idx = BITS(8, 11); + const u8 rn_idx = BITS(0, 3); + const u8 rd_idx = BITS(16, 19); + + const u32 rm_val = state->Reg[rm_idx]; + const u32 rn_val = state->Reg[rn_idx]; + + const u8 diff1 = (u8)std::labs((rn_val & 0xFF) - (rm_val & 0xFF)); + const u8 diff2 = (u8)std::labs(((rn_val >> 8) & 0xFF) - ((rm_val >> 8) & 0xFF)); + const u8 diff3 = (u8)std::labs(((rn_val >> 16) & 0xFF) - ((rm_val >> 16) & 0xFF)); + const u8 diff4 = (u8)std::labs(((rn_val >> 24) & 0xFF) - ((rm_val >> 24) & 0xFF)); + + u32 finalDif = (diff1 + diff2 + diff3 + diff4); + + // Op is USADA8 if true. + const u8 ra_idx = BITS(12, 15); + if (ra_idx != 15) + finalDif += state->Reg[ra_idx]; + + state->Reg[rd_idx] = finalDif; + return 1; + } break; case 0x7a: printf ("Unhandled v6 insn: usbfx\n"); diff --git a/src/core/file_sys/archive_backend.h b/src/core/file_sys/archive_backend.h index 065b22e5d..eb1fdaa1f 100644 --- a/src/core/file_sys/archive_backend.h +++ b/src/core/file_sys/archive_backend.h @@ -143,7 +143,16 @@ public: case Char: return std::vector<u8>(string.begin(), string.end()); case Wchar: - return std::vector<u8>(u16str.begin(), u16str.end()); + { + // use two u8 for each character of u16str + std::vector<u8> to_return(u16str.size() * 2); + for (size_t i = 0; i < u16str.size(); ++i) { + u16 tmp_char = u16str.at(i); + to_return[i*2] = (tmp_char & 0xFF00) >> 8; + to_return[i*2 + 1] = (tmp_char & 0x00FF); + } + return to_return; + } case Empty: return {}; default: @@ -200,6 +209,14 @@ public: virtual bool DeleteDirectory(const FileSys::Path& path) const = 0; /** + * Create a file specified by its path + * @param path Path relative to the Archive + * @param size The size of the new file, filled with zeroes + * @return File creation result code + */ + virtual ResultCode CreateFile(const Path& path, u32 size) const = 0; + + /** * Create a directory specified by its path * @param path Path relative to the archive * @return Whether the directory could be created diff --git a/src/core/file_sys/archive_romfs.cpp b/src/core/file_sys/archive_romfs.cpp index 6ef6ea2fb..ced0794ef 100644 --- a/src/core/file_sys/archive_romfs.cpp +++ b/src/core/file_sys/archive_romfs.cpp @@ -5,6 +5,7 @@ #include <memory> #include "common/common_types.h" +#include "common/make_unique.h" #include "core/file_sys/archive_romfs.h" #include "core/file_sys/directory_romfs.h" @@ -29,7 +30,7 @@ Archive_RomFS::Archive_RomFS(const Loader::AppLoader& app_loader) { * @return Opened file, or nullptr */ std::unique_ptr<FileBackend> Archive_RomFS::OpenFile(const Path& path, const Mode mode) const { - return std::make_unique<File_RomFS>(this); + return Common::make_unique<File_RomFS>(this); } /** @@ -57,6 +58,12 @@ bool Archive_RomFS::DeleteDirectory(const FileSys::Path& path) const { return false; } +ResultCode Archive_RomFS::CreateFile(const Path& path, u32 size) const { + LOG_WARNING(Service_FS, "Attempted to create a file in ROMFS."); + // TODO: Verify error code + return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported, ErrorLevel::Permanent); +} + /** * Create a directory specified by its path * @param path Path relative to the archive @@ -78,7 +85,7 @@ bool Archive_RomFS::RenameDirectory(const FileSys::Path& src_path, const FileSys * @return Opened directory, or nullptr */ std::unique_ptr<DirectoryBackend> Archive_RomFS::OpenDirectory(const Path& path) const { - return std::make_unique<Directory_RomFS>(); + return Common::make_unique<Directory_RomFS>(); } } // namespace FileSys diff --git a/src/core/file_sys/archive_romfs.h b/src/core/file_sys/archive_romfs.h index 564c23f70..2fafd0d2a 100644 --- a/src/core/file_sys/archive_romfs.h +++ b/src/core/file_sys/archive_romfs.h @@ -54,6 +54,14 @@ public: bool DeleteDirectory(const FileSys::Path& path) const override; /** + * Create a file specified by its path + * @param path Path relative to the Archive + * @param size The size of the new file, filled with zeroes + * @return File creation result code + */ + ResultCode CreateFile(const Path& path, u32 size) const override; + + /** * Create a directory specified by its path * @param path Path relative to the archive * @return Whether the directory could be created diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp index fc9ee4acf..1689a1a91 100644 --- a/src/core/file_sys/disk_archive.cpp +++ b/src/core/file_sys/disk_archive.cpp @@ -35,6 +35,27 @@ bool DiskArchive::DeleteDirectory(const FileSys::Path& path) const { return FileUtil::DeleteDir(GetMountPoint() + path.AsString()); } +ResultCode DiskArchive::CreateFile(const FileSys::Path& path, u32 size) const { + std::string full_path = GetMountPoint() + path.AsString(); + + if (FileUtil::Exists(full_path)) + return ResultCode(ErrorDescription::AlreadyExists, ErrorModule::FS, ErrorSummary::NothingHappened, ErrorLevel::Info); + + if (size == 0) { + FileUtil::CreateEmptyFile(full_path); + return RESULT_SUCCESS; + } + + FileUtil::IOFile file(full_path, "wb"); + // Creates a sparse file (or a normal file on filesystems without the concept of sparse files) + // We do this by seeking to the right size, then writing a single null byte. + if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) + return RESULT_SUCCESS; + + return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource, ErrorLevel::Info); +} + + bool DiskArchive::CreateDirectory(const Path& path) const { return FileUtil::CreateDir(GetMountPoint() + path.AsString()); } diff --git a/src/core/file_sys/disk_archive.h b/src/core/file_sys/disk_archive.h index 73fce2fce..c1784e870 100644 --- a/src/core/file_sys/disk_archive.h +++ b/src/core/file_sys/disk_archive.h @@ -28,6 +28,7 @@ public: bool DeleteFile(const FileSys::Path& path) const override; bool RenameFile(const FileSys::Path& src_path, const FileSys::Path& dest_path) const override; bool DeleteDirectory(const FileSys::Path& path) const override; + ResultCode CreateFile(const Path& path, u32 size) const override; bool CreateDirectory(const Path& path) const override; bool RenameDirectory(const FileSys::Path& src_path, const FileSys::Path& dest_path) const override; std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const override; diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index dca87d93a..32258d5a0 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -14,6 +14,10 @@ typedef s32 Result; namespace Kernel { +// From kernel.h. Declarations duplicated here to avoid a circular header dependency. +class Thread; +Thread* GetCurrentThread(); + enum KernelHandle { CurrentThread = 0xFFFF8000, CurrentProcess = 0xFFFF8001, @@ -81,6 +85,10 @@ public: template <class T> T* Get(Handle handle) { + if (handle == CurrentThread) { + return reinterpret_cast<T*>(GetCurrentThread()); + } + if (handle < HANDLE_OFFSET || handle >= HANDLE_OFFSET + MAX_COUNT || !occupied[handle - HANDLE_OFFSET]) { if (handle != 0) { LOG_ERROR(Kernel, "Bad object handle %08x", handle); @@ -99,6 +107,10 @@ public: // ONLY use this when you know the handle is valid. template <class T> T *GetFast(Handle handle) { + if (handle == CurrentThread) { + return reinterpret_cast<T*>(GetCurrentThread()); + } + const Handle realHandle = handle - HANDLE_OFFSET; _dbg_assert_(Kernel, realHandle >= 0 && realHandle < MAX_COUNT && occupied[realHandle]); return static_cast<T*>(pool[realHandle]); diff --git a/src/core/hle/kernel/semaphore.cpp b/src/core/hle/kernel/semaphore.cpp index 1572e8ab2..b81d0b26a 100644 --- a/src/core/hle/kernel/semaphore.cpp +++ b/src/core/hle/kernel/semaphore.cpp @@ -20,8 +20,8 @@ public: static Kernel::HandleType GetStaticHandleType() { return Kernel::HandleType::Semaphore; } Kernel::HandleType GetHandleType() const override { return Kernel::HandleType::Semaphore; } - u32 max_count; ///< Maximum number of simultaneous holders the semaphore can have - u32 available_count; ///< Number of free slots left in the semaphore + s32 max_count; ///< Maximum number of simultaneous holders the semaphore can have + s32 available_count; ///< Number of free slots left in the semaphore std::queue<Handle> waiting_threads; ///< Threads that are waiting for the semaphore std::string name; ///< Name of semaphore (optional) @@ -49,8 +49,8 @@ public: //////////////////////////////////////////////////////////////////////////////////////////////////// -ResultCode CreateSemaphore(Handle* handle, u32 initial_count, - u32 max_count, const std::string& name) { +ResultCode CreateSemaphore(Handle* handle, s32 initial_count, + s32 max_count, const std::string& name) { if (initial_count > max_count) return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::Kernel, diff --git a/src/core/hle/kernel/semaphore.h b/src/core/hle/kernel/semaphore.h index 934499e7b..8644ecf0c 100644 --- a/src/core/hle/kernel/semaphore.h +++ b/src/core/hle/kernel/semaphore.h @@ -18,7 +18,7 @@ namespace Kernel { * @param name Optional name of semaphore * @return ResultCode of the error */ -ResultCode CreateSemaphore(Handle* handle, u32 initial_count, u32 max_count, const std::string& name = "Unknown"); +ResultCode CreateSemaphore(Handle* handle, s32 initial_count, s32 max_count, const std::string& name = "Unknown"); /** * Releases a certain number of slots from a semaphore. diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index a47bde9dd..c6a8dc7b9 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -83,8 +83,7 @@ static Thread* current_thread; static const u32 INITIAL_THREAD_ID = 1; ///< The first available thread id at startup static u32 next_thread_id; ///< The next available thread id -/// Gets the current thread -inline Thread* GetCurrentThread() { +Thread* GetCurrentThread() { return current_thread; } @@ -148,16 +147,19 @@ void ChangeReadyState(Thread* t, bool ready) { } } -/// Verify that a thread has not been released from waiting -static bool VerifyWait(const Thread* thread, WaitType type, Handle wait_handle) { - _dbg_assert_(Kernel, thread != nullptr); - return (type == thread->wait_type) && (wait_handle == thread->wait_handle) && (thread->IsWaiting()); +/// Check if a thread is blocking on a specified wait type +static bool CheckWaitType(const Thread* thread, WaitType type) { + return (type == thread->wait_type) && (thread->IsWaiting()); } -/// Verify that a thread has not been released from waiting (with wait address) -static bool VerifyWait(const Thread* thread, WaitType type, Handle wait_handle, VAddr wait_address) { - _dbg_assert_(Kernel, thread != nullptr); - return VerifyWait(thread, type, wait_handle) && (wait_address == thread->wait_address); +/// Check if a thread is blocking on a specified wait type with a specified handle +static bool CheckWaitType(const Thread* thread, WaitType type, Handle wait_handle) { + return CheckWaitType(thread, type) && (wait_handle == thread->wait_handle); +} + +/// Check if a thread is blocking on a specified wait type with a specified handle and address +static bool CheckWaitType(const Thread* thread, WaitType type, Handle wait_handle, VAddr wait_address) { + return CheckWaitType(thread, type, wait_handle) && (wait_address == thread->wait_address); } /// Stops the current thread @@ -172,9 +174,9 @@ ResultCode StopThread(Handle handle, const char* reason) { thread->status = THREADSTATUS_DORMANT; for (Handle waiting_handle : thread->waiting_threads) { Thread* waiting_thread = g_object_pool.Get<Thread>(waiting_handle); - if (VerifyWait(waiting_thread, WAITTYPE_THREADEND, handle)) { + + if (CheckWaitType(waiting_thread, WAITTYPE_THREADEND, handle)) ResumeThreadFromWait(waiting_handle); - } } thread->waiting_threads.clear(); @@ -210,7 +212,7 @@ Handle ArbitrateHighestPriorityThread(u32 arbiter, u32 address) { for (Handle handle : thread_queue) { Thread* thread = g_object_pool.Get<Thread>(handle); - if (!VerifyWait(thread, WAITTYPE_ARB, arbiter, address)) + if (!CheckWaitType(thread, WAITTYPE_ARB, arbiter, address)) continue; if (thread == nullptr) @@ -235,7 +237,7 @@ void ArbitrateAllThreads(u32 arbiter, u32 address) { for (Handle handle : thread_queue) { Thread* thread = g_object_pool.Get<Thread>(handle); - if (VerifyWait(thread, WAITTYPE_ARB, arbiter, address)) + if (CheckWaitType(thread, WAITTYPE_ARB, arbiter, address)) ResumeThreadFromWait(handle); } } @@ -306,6 +308,8 @@ void ResumeThreadFromWait(Handle handle) { Thread* thread = Kernel::g_object_pool.Get<Thread>(handle); if (thread) { thread->status &= ~THREADSTATUS_WAIT; + thread->wait_handle = 0; + thread->wait_type = WAITTYPE_NONE; if (!(thread->status & (THREADSTATUS_WAITSUSPEND | THREADSTATUS_DORMANT | THREADSTATUS_DEAD))) { ChangeReadyState(thread, true); } @@ -469,19 +473,27 @@ void Reschedule() { Thread* prev = GetCurrentThread(); Thread* next = NextThread(); HLE::g_reschedule = false; - if (next > 0) { - LOG_TRACE(Kernel, "context switch 0x%08X -> 0x%08X", prev->GetHandle(), next->GetHandle()); + if (next != nullptr) { + LOG_TRACE(Kernel, "context switch 0x%08X -> 0x%08X", prev->GetHandle(), next->GetHandle()); SwitchContext(next); + } else { + LOG_TRACE(Kernel, "cannot context switch from 0x%08X, no higher priority thread!", prev->GetHandle()); - // Hack - There is no mechanism yet to waken the primary thread if it has been put to sleep - // by a simulated VBLANK thread switch. So, we'll just immediately set it to "ready" again. - // This results in the current thread yielding on a VBLANK once, and then it will be - // immediately placed back in the queue for execution. - if (prev->wait_type == WAITTYPE_VBLANK) { - ResumeThreadFromWait(prev->GetHandle()); + for (Handle handle : thread_queue) { + Thread* thread = g_object_pool.Get<Thread>(handle); + LOG_TRACE(Kernel, "\thandle=0x%08X prio=0x%02X, status=0x%08X wait_type=0x%08X wait_handle=0x%08X", + thread->GetHandle(), thread->current_priority, thread->status, thread->wait_type, thread->wait_handle); } } + + // TODO(bunnei): Hack - There is no timing mechanism yet to wake up a thread if it has been put + // to sleep. So, we'll just immediately set it to "ready" again after an attempted context + // switch has occurred. This results in the current thread yielding on a sleep once, and then it + // will immediately be placed back in the queue for execution. + + if (CheckWaitType(prev, WAITTYPE_SLEEP)) + ResumeThreadFromWait(prev->GetHandle()); } ResultCode GetThreadId(u32* thread_id, Handle handle) { diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 34333ef6e..9396b6b26 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -40,7 +40,6 @@ enum WaitType { WAITTYPE_SEMA, WAITTYPE_EVENT, WAITTYPE_THREADEND, - WAITTYPE_VBLANK, WAITTYPE_MUTEX, WAITTYPE_SYNCH, WAITTYPE_ARB, @@ -78,6 +77,9 @@ Handle ArbitrateHighestPriorityThread(u32 arbiter, u32 address); /// Arbitrate all threads currently waiting... void ArbitrateAllThreads(u32 arbiter, u32 address); +/// Gets the current thread +Thread* GetCurrentThread(); + /// Gets the current thread handle Handle GetCurrentThreadHandle(); diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index d92444160..e2a59a069 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -7,6 +7,7 @@ #include "common/common_types.h" #include "common/file_util.h" +#include "common/make_unique.h" #include "common/math_util.h" #include "core/file_sys/archive_savedata.h" @@ -260,7 +261,7 @@ ResultCode CloseArchive(ArchiveHandle handle) { // TODO(yuriks): This might be what the fs:REG service is for. See the Register/Unregister calls in // http://3dbrew.org/wiki/Filesystem_services#ProgramRegistry_service_.22fs:REG.22 ResultCode CreateArchive(std::unique_ptr<FileSys::ArchiveBackend>&& backend, ArchiveIdCode id_code) { - auto result = id_code_map.emplace(id_code, std::make_unique<Archive>(std::move(backend), id_code)); + auto result = id_code_map.emplace(id_code, Common::make_unique<Archive>(std::move(backend), id_code)); bool inserted = result.second; _dbg_assert_msg_(Service_FS, inserted, "Tried to register more than one archive with same id code"); @@ -281,7 +282,7 @@ ResultVal<Handle> OpenFileFromArchive(ArchiveHandle archive_handle, const FileSy ErrorSummary::NotFound, ErrorLevel::Status); } - auto file = std::make_unique<File>(std::move(backend), path); + auto file = Common::make_unique<File>(std::move(backend), path); Handle handle = Kernel::g_object_pool.Create(file.release()); return MakeResult<Handle>(handle); } @@ -329,6 +330,14 @@ ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy ErrorSummary::Canceled, ErrorLevel::Status); } +ResultCode CreateFileInArchive(Handle archive_handle, const FileSys::Path& path, u32 file_size) { + Archive* archive = GetArchive(archive_handle); + if (archive == nullptr) + return InvalidHandle(ErrorModule::FS); + + return archive->backend->CreateFile(path, file_size); +} + ResultCode CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) { Archive* archive = GetArchive(archive_handle); if (archive == nullptr) @@ -378,7 +387,7 @@ ResultVal<Handle> OpenDirectoryFromArchive(ArchiveHandle archive_handle, const F ErrorSummary::NotFound, ErrorLevel::Permanent); } - auto directory = std::make_unique<Directory>(std::move(backend), path); + auto directory = Common::make_unique<Directory>(std::move(backend), path); Handle handle = Kernel::g_object_pool.Create(directory.release()); return MakeResult<Handle>(handle); } @@ -392,7 +401,7 @@ ResultCode FormatSaveData() { // Create the SaveData archive std::string savedata_directory = FileUtil::GetUserPath(D_SAVEDATA_IDX); - auto savedata_archive = std::make_unique<FileSys::Archive_SaveData>(savedata_directory, + auto savedata_archive = Common::make_unique<FileSys::Archive_SaveData>(savedata_directory, Kernel::g_program_id); if (savedata_archive->Initialize()) { @@ -414,14 +423,14 @@ void ArchiveInit() { // archive type is SDMC, so it is the only one getting exposed. std::string sdmc_directory = FileUtil::GetUserPath(D_SDMC_IDX); - auto sdmc_archive = std::make_unique<FileSys::Archive_SDMC>(sdmc_directory); + auto sdmc_archive = Common::make_unique<FileSys::Archive_SDMC>(sdmc_directory); if (sdmc_archive->Initialize()) CreateArchive(std::move(sdmc_archive), ArchiveIdCode::SDMC); else LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s", sdmc_directory.c_str()); std::string systemsavedata_directory = FileUtil::GetUserPath(D_SYSSAVEDATA_IDX); - auto systemsavedata_archive = std::make_unique<FileSys::Archive_SDMC>(systemsavedata_directory); + auto systemsavedata_archive = Common::make_unique<FileSys::Archive_SDMC>(systemsavedata_directory); if (systemsavedata_archive->Initialize()) { CreateArchive(std::move(systemsavedata_archive), ArchiveIdCode::SystemSaveData); } else { diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index df04bdb6b..b39bc41b6 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -83,6 +83,15 @@ ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle, const Fil ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path); /** + * Create a File in an Archive + * @param archive_handle Handle to an open Archive object + * @param path Path to the File inside of the Archive + * @param file_size The size of the new file, filled with zeroes + * @return File creation result code + */ +ResultCode CreateFileInArchive(Handle archive_handle, const FileSys::Path& path, u32 file_size); + +/** * Create a Directory from an Archive * @param archive_handle Handle to an open Archive object * @param path Path to the Directory inside of the Archive diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index 8d11e64b5..5e9b85cc7 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -226,6 +226,35 @@ static void DeleteDirectory(Service::Interface* self) { } /* + * FS_User::CreateFile service function + * Inputs: + * 0 : Command header 0x08080202 + * 2 : Archive handle lower word + * 3 : Archive handle upper word + * 4 : File path string type + * 5 : File path string size + * 7 : File size (filled with zeroes) + * 10: File path string data + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void CreateFile(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + ArchiveHandle archive_handle = MakeArchiveHandle(cmd_buff[2], cmd_buff[3]); + auto filename_type = static_cast<FileSys::LowPathType>(cmd_buff[4]); + u32 filename_size = cmd_buff[5]; + u32 file_size = cmd_buff[7]; + u32 filename_ptr = cmd_buff[10]; + + FileSys::Path file_path(filename_type, filename_size, filename_ptr); + + LOG_DEBUG(Service_FS, "type=%d size=%d data=%s", filename_type, filename_size, file_path.DebugStr().c_str()); + + cmd_buff[1] = CreateFileInArchive(archive_handle, file_path, file_size).raw; +} + +/* * FS_User::CreateDirectory service function * Inputs: * 2 : Archive handle lower word @@ -397,16 +426,44 @@ static void IsSdmcDetected(Service::Interface* self) { } /** - * FS_User::FormatSaveData service function + * FS_User::FormatSaveData service function, + * formats the SaveData specified by the input path. * Inputs: + * 0 : 0x084C0242 + * 1 : Archive ID + * 2 : Archive low path type + * 3 : Archive low path size + * 10 : (LowPathSize << 14) | 2 + * 11 : Archive low path * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ static void FormatSaveData(Service::Interface* self) { + // TODO(Subv): Find out what the other inputs and outputs of this function are u32* cmd_buff = Kernel::GetCommandBuffer(); LOG_DEBUG(Service_FS, "(STUBBED)"); - // TODO(Subv): Find out what the inputs and outputs of this function are + auto archive_id = static_cast<FS::ArchiveIdCode>(cmd_buff[1]); + auto archivename_type = static_cast<FileSys::LowPathType>(cmd_buff[2]); + u32 archivename_size = cmd_buff[3]; + u32 archivename_ptr = cmd_buff[11]; + FileSys::Path archive_path(archivename_type, archivename_size, archivename_ptr); + + LOG_DEBUG(Service_FS, "archive_path=%s", archive_path.DebugStr().c_str()); + + if (archive_id != FS::ArchiveIdCode::SaveData) { + // TODO(Subv): What should happen if somebody attempts to format a different archive? + LOG_ERROR(Service_FS, "tried to format an archive different than SaveData, %u", cmd_buff[1]); + cmd_buff[1] = UnimplementedFunction(ErrorModule::FS).raw; + return; + } + + if (archive_path.GetType() != FileSys::LowPathType::Empty) { + // TODO(Subv): Implement formatting the SaveData of other games + LOG_ERROR(Service_FS, "archive LowPath type other than empty is currently unsupported"); + cmd_buff[1] = UnimplementedFunction(ErrorModule::FS).raw; + return; + } cmd_buff[1] = FormatSaveData().raw; } @@ -414,6 +471,7 @@ static void FormatSaveData(Service::Interface* self) { /** * FS_User::FormatThisUserSaveData service function * Inputs: + * 0: 0x080F0180 * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ @@ -436,7 +494,7 @@ const FSUserInterface::FunctionInfo FunctionTable[] = { {0x08050244, RenameFile, "RenameFile"}, {0x08060142, DeleteDirectory, "DeleteDirectory"}, {0x08070142, nullptr, "DeleteDirectoryRecursively"}, - {0x08080202, nullptr, "CreateFile"}, + {0x08080202, CreateFile, "CreateFile"}, {0x08090182, CreateDirectory, "CreateDirectory"}, {0x080A0244, RenameDirectory, "RenameDirectory"}, {0x080B0102, OpenDirectory, "OpenDirectory"}, diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index 10c157ba6..1f841078a 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -145,6 +145,30 @@ static void SetBufferSwap(Service::Interface* self) { } /** + * GSP_GPU::FlushDataCache service function + * + * This Function is a no-op, We aren't emulating the CPU cache any time soon. + * + * Inputs: + * 1 : Address + * 2 : Size + * 3 : Value 0, some descriptor for the KProcess Handle + * 4 : KProcess handle + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void FlushDataCache(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 address = cmd_buff[1]; + u32 size = cmd_buff[2]; + u32 process = cmd_buff[4]; + + // TODO(purpasmart96): Verify return header on HW + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error +} + +/** * GSP_GPU::RegisterInterruptRelayQueue service function * Inputs: * 1 : "Flags" field, purpose is unknown @@ -335,7 +359,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00050200, SetBufferSwap, "SetBufferSwap"}, {0x00060082, nullptr, "SetCommandList"}, {0x000700C2, nullptr, "RequestDma"}, - {0x00080082, nullptr, "FlushDataCache"}, + {0x00080082, FlushDataCache, "FlushDataCache"}, {0x00090082, nullptr, "InvalidateDataCache"}, {0x000A0044, nullptr, "RegisterInterruptEvents"}, {0x000B0040, nullptr, "SetLcdForceBlack"}, diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index 4d39e7d0b..c98168e51 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -352,7 +352,8 @@ static Result ClearEvent(Handle evt) { static void SleepThread(s64 nanoseconds) { LOG_TRACE(Kernel_SVC, "called nanoseconds=%lld", nanoseconds); - // Check for next thread to schedule + // Sleep current thread and check for next thread to schedule + Kernel::WaitCurrentThread(WAITTYPE_SLEEP); HLE::Reschedule(__func__); } diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 5a4f7e7d9..4d072871a 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -223,9 +223,7 @@ int THREEDSXReader::Load3DSXFile(const std::string& filename, u32 base_addr) LOG_INFO(Loader, "Loading 3DSX file %s...", filename.c_str()); FileUtil::IOFile file(filename, "rb"); if (file.IsOpen()) { - - THREEDSXReader reader; - reader.Load3DSXFile(filename, 0x00100000); + THREEDSXReader::Load3DSXFile(filename, 0x00100000); Kernel::LoadExec(0x00100000); } else { return ResultStatus::Error; diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 74a29ac61..87580cb2a 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -2,7 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <memory> +#include <string> + +#include "common/make_unique.h" #include "core/file_sys/archive_romfs.h" #include "core/loader/3dsx.h" @@ -75,7 +77,7 @@ ResultStatus LoadFile(const std::string& filename) { // Load application and RomFS if (ResultStatus::Success == app_loader.Load()) { Kernel::g_program_id = app_loader.GetProgramId(); - Service::FS::CreateArchive(std::make_unique<FileSys::Archive_RomFS>(app_loader), Service::FS::ArchiveIdCode::RomFS); + Service::FS::CreateArchive(Common::make_unique<FileSys::Archive_RomFS>(app_loader), Service::FS::ArchiveIdCode::RomFS); return ResultStatus::Success; } break; diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index d77dd2237..ea29e5027 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -56,10 +56,11 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { g_debug_context->OnEvent(DebugContext::Event::IncomingPrimitiveBatch, nullptr); const auto& attribute_config = registers.vertex_attributes; - const u8* const base_address = Memory::GetPointer(attribute_config.GetBaseAddress()); + const u32 base_address = attribute_config.GetPhysicalBaseAddress(); // Information about internal vertex attributes - const u8* vertex_attribute_sources[16]; + u32 vertex_attribute_sources[16]; + std::fill(vertex_attribute_sources, &vertex_attribute_sources[16], 0xdeadbeef); u32 vertex_attribute_strides[16]; u32 vertex_attribute_formats[16]; u32 vertex_attribute_elements[16]; @@ -69,7 +70,7 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { for (int loader = 0; loader < 12; ++loader) { const auto& loader_config = attribute_config.attribute_loaders[loader]; - const u8* load_address = base_address + loader_config.data_offset; + u32 load_address = base_address + loader_config.data_offset; // TODO: What happens if a loader overwrites a previous one's data? for (unsigned component = 0; component < loader_config.component_count; ++component) { @@ -87,7 +88,7 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { bool is_indexed = (id == PICA_REG_INDEX(trigger_draw_indexed)); const auto& index_info = registers.index_array; - const u8* index_address_8 = (u8*)base_address + index_info.offset; + const u8* index_address_8 = Memory::GetPointer(PAddrToVAddr(base_address + index_info.offset)); const u16* index_address_16 = (u16*)index_address_8; bool index_u16 = (bool)index_info.format; @@ -108,7 +109,14 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { for (int i = 0; i < attribute_config.GetNumTotalAttributes(); ++i) { for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { - const u8* srcdata = vertex_attribute_sources[i] + vertex_attribute_strides[i] * vertex + comp * vertex_attribute_element_size[i]; + const u8* srcdata = Memory::GetPointer(PAddrToVAddr(vertex_attribute_sources[i] + vertex_attribute_strides[i] * vertex + comp * vertex_attribute_element_size[i])); + + // TODO(neobrain): Ocarina of Time 3D has GetNumTotalAttributes return 8, + // yet only provides 2 valid source data addresses. Need to figure out + // what's wrong there, until then we just continue when address lookup fails + if (srcdata == nullptr) + continue; + const float srcval = (vertex_attribute_formats[i] == 0) ? *(s8*)srcdata : (vertex_attribute_formats[i] == 1) ? *(u8*)srcdata : (vertex_attribute_formats[i] == 2) ? *(s16*)srcdata : @@ -116,13 +124,16 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { 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.GetBaseAddress(), + attribute_config.GetPhysicalBaseAddress(), vertex_attribute_sources[i] - base_address, - srcdata - vertex_attribute_sources[i], + vertex_attribute_strides[i] * vertex + comp * vertex_attribute_element_size[i], input.attr[i][comp].ToFloat32()); } } + if (g_debug_context) + g_debug_context->OnEvent(DebugContext::Event::VertexLoaded, (void*)&input); + // NOTE: When dumping geometry, we simply assume that the first input attribute // corresponds to the position for now. DebugUtils::GeometryDumper::Vertex dumped_vertex = { @@ -151,6 +162,12 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { break; } + case PICA_REG_INDEX(vs_bool_uniforms): + for (unsigned i = 0; i < 16; ++i) + VertexShader::GetBoolUniform(i) = (registers.vs_bool_uniforms.Value() & (1 << i)); + + break; + case PICA_REG_INDEX_WORKAROUND(vs_uniform_setup.set_value[0], 0x2c1): case PICA_REG_INDEX_WORKAROUND(vs_uniform_setup.set_value[1], 0x2c2): case PICA_REG_INDEX_WORKAROUND(vs_uniform_setup.set_value[2], 0x2c3): diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index 1a20f19ec..328386b7e 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -14,6 +14,8 @@ #include <png.h> #endif +#include <nihstro/shader_binary.h> + #include "common/log.h" #include "common/file_util.h" @@ -22,6 +24,10 @@ #include "debug_utils.h" +using nihstro::DVLBHeader; +using nihstro::DVLEHeader; +using nihstro::DVLPHeader; + namespace Pica { void DebugContext::OnEvent(Event event, void* data) { @@ -98,65 +104,6 @@ void GeometryDumper::Dump() { } } -#pragma pack(1) -struct DVLBHeader { - enum : u32 { - MAGIC_WORD = 0x424C5644, // "DVLB" - }; - - u32 magic_word; - u32 num_programs; -// u32 dvle_offset_table[]; -}; -static_assert(sizeof(DVLBHeader) == 0x8, "Incorrect structure size"); - -struct DVLPHeader { - enum : u32 { - MAGIC_WORD = 0x504C5644, // "DVLP" - }; - - u32 magic_word; - u32 version; - u32 binary_offset; // relative to DVLP start - u32 binary_size_words; - u32 swizzle_patterns_offset; - u32 swizzle_patterns_num_entries; - u32 unk2; -}; -static_assert(sizeof(DVLPHeader) == 0x1C, "Incorrect structure size"); - -struct DVLEHeader { - enum : u32 { - MAGIC_WORD = 0x454c5644, // "DVLE" - }; - - enum class ShaderType : u8 { - VERTEX = 0, - GEOMETRY = 1, - }; - - u32 magic_word; - u16 pad1; - ShaderType type; - u8 pad2; - u32 main_offset_words; // offset within binary blob - u32 endmain_offset_words; - u32 pad3; - u32 pad4; - u32 constant_table_offset; - u32 constant_table_size; // number of entries - u32 label_table_offset; - u32 label_table_size; - u32 output_register_table_offset; - u32 output_register_table_size; - u32 uniform_table_offset; - u32 uniform_table_size; - u32 symbol_table_offset; - u32 symbol_table_size; - -}; -static_assert(sizeof(DVLEHeader) == 0x40, "Incorrect structure size"); -#pragma pack() void DumpShader(const u32* binary_data, u32 binary_size, const u32* swizzle_data, u32 swizzle_size, u32 main_offset, const Regs::VSOutputAttributes* output_attributes) @@ -276,8 +223,8 @@ void DumpShader(const u32* binary_data, u32 binary_size, const u32* swizzle_data dvlp.binary_size_words = binary_size; QueueForWriting((u8*)binary_data, binary_size * sizeof(u32)); - dvlp.swizzle_patterns_offset = write_offset - dvlp_offset; - dvlp.swizzle_patterns_num_entries = swizzle_size; + dvlp.swizzle_info_offset = write_offset - dvlp_offset; + dvlp.swizzle_info_num_entries = swizzle_size; u32 dummy = 0; for (unsigned int i = 0; i < swizzle_size; ++i) { QueueForWriting((u8*)&swizzle_data[i], sizeof(swizzle_data[i])); @@ -356,10 +303,29 @@ std::unique_ptr<PicaTrace> FinishPicaTracing() return std::move(ret); } -const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const TextureInfo& info) { - _dbg_assert_(Debug_GPU, info.format == Pica::Regs::TextureFormat::RGB8); - - // Cf. rasterizer code for an explanation of this algorithm. +const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const TextureInfo& info, bool disable_alpha) { + + // Images are split into 8x8 tiles. Each tile is composed of four 4x4 subtiles each + // of which is composed of four 2x2 subtiles each of which is composed of four texels. + // Each structure is embedded into the next-bigger one in a diagonal pattern, e.g. + // texels are laid out in a 2x2 subtile like this: + // 2 3 + // 0 1 + // + // The full 8x8 tile has the texels arranged like this: + // + // 42 43 46 47 58 59 62 63 + // 40 41 44 45 56 57 60 61 + // 34 35 38 39 50 51 54 55 + // 32 33 36 37 48 49 52 53 + // 10 11 14 15 26 27 30 31 + // 08 09 12 13 24 25 28 29 + // 02 03 06 07 18 19 22 23 + // 00 01 04 05 16 17 20 21 + + // TODO(neobrain): Not sure if this swizzling pattern is used for all textures. + // To be flexible in case different but similar patterns are used, we keep this + // somewhat inefficient code around for now. int texel_index_within_tile = 0; for (int block_size_index = 0; block_size_index < 3; ++block_size_index) { int sub_tile_width = 1 << block_size_index; @@ -376,19 +342,134 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const Texture int coarse_x = (x / block_width) * block_width; int coarse_y = (y / block_height) * block_height; - const u8* source_ptr = source + coarse_x * block_height * 3 + coarse_y * info.stride + texel_index_within_tile * 3; - return { source_ptr[2], source_ptr[1], source_ptr[0], 255 }; + switch (info.format) { + case Regs::TextureFormat::RGBA8: + { + const u8* source_ptr = source + coarse_x * block_height * 4 + coarse_y * info.stride + texel_index_within_tile * 4; + return { source_ptr[3], source_ptr[2], source_ptr[1], disable_alpha ? (u8)255 : source_ptr[0] }; + } + + case Regs::TextureFormat::RGB8: + { + const u8* source_ptr = source + coarse_x * block_height * 3 + coarse_y * info.stride + texel_index_within_tile * 3; + return { source_ptr[2], source_ptr[1], source_ptr[0], 255 }; + } + + case Regs::TextureFormat::RGBA5551: + { + const u16 source_ptr = *(const u16*)(source + coarse_x * block_height * 2 + coarse_y * info.stride + texel_index_within_tile * 2); + u8 r = (source_ptr >> 11) & 0x1F; + u8 g = ((source_ptr) >> 6) & 0x1F; + u8 b = (source_ptr >> 1) & 0x1F; + u8 a = source_ptr & 1; + return Math::MakeVec<u8>((r << 3) | (r >> 2), (g << 3) | (g >> 2), (b << 3) | (b >> 2), disable_alpha ? 255 : (a * 255)); + } + + case Regs::TextureFormat::RGB565: + { + const u16 source_ptr = *(const u16*)(source + coarse_x * block_height * 2 + coarse_y * info.stride + texel_index_within_tile * 2); + u8 r = (source_ptr >> 11) & 0x1F; + u8 g = ((source_ptr) >> 5) & 0x3F; + u8 b = (source_ptr) & 0x1F; + return Math::MakeVec<u8>((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2), 255); + } + + case Regs::TextureFormat::RGBA4: + { + const u8* source_ptr = source + coarse_x * block_height * 2 + coarse_y * info.stride + texel_index_within_tile * 2; + u8 r = source_ptr[1] >> 4; + u8 g = source_ptr[1] & 0xFF; + u8 b = source_ptr[0] >> 4; + u8 a = source_ptr[0] & 0xFF; + r = (r << 4) | r; + g = (g << 4) | g; + b = (b << 4) | b; + a = (a << 4) | a; + return { r, g, b, disable_alpha ? (u8)255 : a }; + } + + case Regs::TextureFormat::IA8: + { + const u8* source_ptr = source + coarse_x * block_height * 2 + coarse_y * info.stride + texel_index_within_tile * 2; + + // TODO: component order not verified + + if (disable_alpha) { + // Show intensity as red, alpha as green + return { source_ptr[0], source_ptr[1], 0, 255 }; + } else { + return { source_ptr[0], source_ptr[0], source_ptr[0], source_ptr[1]}; + } + } + + case Regs::TextureFormat::I8: + { + const u8* source_ptr = source + coarse_x * block_height + coarse_y * info.stride + texel_index_within_tile; + return { *source_ptr, *source_ptr, *source_ptr, 255 }; + } + + case Regs::TextureFormat::A8: + { + const u8* source_ptr = source + coarse_x * block_height + coarse_y * info.stride + texel_index_within_tile; + + if (disable_alpha) { + return { *source_ptr, *source_ptr, *source_ptr, 255 }; + } else { + return { 0, 0, 0, *source_ptr }; + } + } + + case Regs::TextureFormat::IA4: + { + const u8* source_ptr = source + coarse_x * block_height / 2 + coarse_y * info.stride + texel_index_within_tile / 2; + + // TODO: component order not verified + + u8 i = (*source_ptr) & 0xF; + u8 a = ((*source_ptr) & 0xF0) >> 4; + a |= a << 4; + i |= i << 4; + + if (disable_alpha) { + // Show intensity as red, alpha as green + return { i, a, 0, 255 }; + } else { + return { i, i, i, a }; + } + } + + case Regs::TextureFormat::A4: + { + const u8* source_ptr = source + coarse_x * block_height / 2 + coarse_y * info.stride + texel_index_within_tile / 2; + + // TODO: component order not verified + + u8 a = (coarse_x % 2) ? ((*source_ptr)&0xF) : (((*source_ptr) & 0xF0) >> 4); + a |= a << 4; + + if (disable_alpha) { + return { *source_ptr, *source_ptr, *source_ptr, 255 }; + } else { + return { 0, 0, 0, *source_ptr }; + } + } + + default: + LOG_ERROR(HW_GPU, "Unknown texture format: %x", (u32)info.format); + _dbg_assert_(HW_GPU, 0); + return {}; + } } TextureInfo TextureInfo::FromPicaRegister(const Regs::TextureConfig& config, const Regs::TextureFormat& format) { TextureInfo info; - info.address = config.GetPhysicalAddress(); + info.physical_address = config.GetPhysicalAddress(); info.width = config.width; info.height = config.height; info.format = format; - info.stride = Pica::Regs::BytesPerPixel(info.format) * info.width; + info.stride = Pica::Regs::NibblesPerPixel(info.format) * info.width / 2; return info; } @@ -499,26 +580,32 @@ void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages) for (size_t index = 0; index < stages.size(); ++index) { const auto& tev_stage = stages[index]; - const std::map<Source, std::string> source_map = { + static const std::map<Source, std::string> source_map = { { Source::PrimaryColor, "PrimaryColor" }, { Source::Texture0, "Texture0" }, + { Source::Texture1, "Texture1" }, + { Source::Texture2, "Texture2" }, { Source::Constant, "Constant" }, { Source::Previous, "Previous" }, }; - const std::map<ColorModifier, std::string> color_modifier_map = { - { ColorModifier::SourceColor, { "%source.rgb" } } + static const std::map<ColorModifier, std::string> color_modifier_map = { + { ColorModifier::SourceColor, { "%source.rgb" } }, + { ColorModifier::SourceAlpha, { "%source.aaa" } }, }; - const std::map<AlphaModifier, std::string> alpha_modifier_map = { - { AlphaModifier::SourceAlpha, "%source.a" } + static const std::map<AlphaModifier, std::string> alpha_modifier_map = { + { AlphaModifier::SourceAlpha, "%source.a" }, + { AlphaModifier::OneMinusSourceAlpha, "(255 - %source.a)" }, }; - std::map<Operation, std::string> combiner_map = { + static const std::map<Operation, std::string> combiner_map = { { Operation::Replace, "%source1" }, { Operation::Modulate, "(%source1 * %source2) / 255" }, + { Operation::Add, "(%source1 + %source2)" }, + { Operation::Lerp, "lerp(%source1, %source2, %source3)" }, }; - auto ReplacePattern = + static auto ReplacePattern = [](const std::string& input, const std::string& pattern, const std::string& replacement) -> std::string { size_t start = input.find(pattern); if (start == std::string::npos) @@ -528,8 +615,8 @@ void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages) ret.replace(start, pattern.length(), replacement); return ret; }; - auto GetColorSourceStr = - [&source_map,&color_modifier_map,&ReplacePattern](const Source& src, const ColorModifier& modifier) { + static auto GetColorSourceStr = + [](const Source& src, const ColorModifier& modifier) { auto src_it = source_map.find(src); std::string src_str = "Unknown"; if (src_it != source_map.end()) @@ -542,8 +629,8 @@ void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages) return ReplacePattern(modifier_str, "%source", src_str); }; - auto GetColorCombinerStr = - [&](const Regs::TevStageConfig& tev_stage) { + static auto GetColorCombinerStr = + [](const Regs::TevStageConfig& tev_stage) { auto op_it = combiner_map.find(tev_stage.color_op); std::string op_str = "Unknown op (%source1, %source2, %source3)"; if (op_it != combiner_map.end()) @@ -553,8 +640,8 @@ void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages) op_str = ReplacePattern(op_str, "%source2", GetColorSourceStr(tev_stage.color_source2, tev_stage.color_modifier2)); return ReplacePattern(op_str, "%source3", GetColorSourceStr(tev_stage.color_source3, tev_stage.color_modifier3)); }; - auto GetAlphaSourceStr = - [&source_map,&alpha_modifier_map,&ReplacePattern](const Source& src, const AlphaModifier& modifier) { + static auto GetAlphaSourceStr = + [](const Source& src, const AlphaModifier& modifier) { auto src_it = source_map.find(src); std::string src_str = "Unknown"; if (src_it != source_map.end()) @@ -567,8 +654,8 @@ void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages) return ReplacePattern(modifier_str, "%source", src_str); }; - auto GetAlphaCombinerStr = - [&](const Regs::TevStageConfig& tev_stage) { + static auto GetAlphaCombinerStr = + [](const Regs::TevStageConfig& tev_stage) { auto op_it = combiner_map.find(tev_stage.alpha_op); std::string op_str = "Unknown op (%source1, %source2, %source3)"; if (op_it != combiner_map.end()) diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index 51f14f12f..f361a5385 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -26,6 +26,7 @@ public: CommandProcessed, IncomingPrimitiveBatch, FinishedPrimitiveBatch, + VertexLoaded, NumEvents }; @@ -192,7 +193,7 @@ void OnPicaRegWrite(u32 id, u32 value); std::unique_ptr<PicaTrace> FinishPicaTracing(); struct TextureInfo { - unsigned int address; + PAddr physical_address; int width; int height; int stride; @@ -202,7 +203,17 @@ struct TextureInfo { const Pica::Regs::TextureFormat& format); }; -const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const TextureInfo& info); +/** + * Lookup texel located at the given coordinates and return an RGBA vector of its color. + * @param source Source pointer to read data from + * @param s,t Texture coordinates to read from + * @param info TextureInfo object describing the texture setup + * @param disable_alpha This is used for debug widgets which use this method to display textures without providing a good way to visualize alpha by themselves. If true, this will return 255 for the alpha component, and either drop the information entirely or store it in an "unused" color channel. + * @todo Eventually we should get rid of the disable_alpha parameter. + */ +const Math::Vec4<u8> LookupTexture(const u8* source, int s, int t, const TextureInfo& info, + bool disable_alpha = false); + void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data); void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages); diff --git a/src/video_core/gpu_debugger.h b/src/video_core/gpu_debugger.h index 5c5ece82d..a51d49c92 100644 --- a/src/video_core/gpu_debugger.h +++ b/src/video_core/gpu_debugger.h @@ -85,7 +85,7 @@ public: void UnregisterObserver(DebuggerObserver* observer) { - std::remove(observers.begin(), observers.end(), observer); + observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end()); observer->observed = nullptr; } diff --git a/src/video_core/pica.h b/src/video_core/pica.h index a3d5f708f..1fac9ff36 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -8,6 +8,7 @@ #include <cstddef> #include <initializer_list> #include <map> +#include <vector> #include "common/bit_field.h" #include "common/common_types.h" @@ -104,6 +105,11 @@ struct Regs { INSERT_PADDING_WORDS(0x17); struct TextureConfig { + enum WrapMode : u32 { + ClampToEdge = 0, + Repeat = 2, + }; + INSERT_PADDING_WORDS(0x1); union { @@ -111,12 +117,17 @@ struct Regs { BitField<16, 16, u32> width; }; - INSERT_PADDING_WORDS(0x2); + union { + BitField< 8, 2, WrapMode> wrap_s; + BitField<11, 2, WrapMode> wrap_t; + }; + + INSERT_PADDING_WORDS(0x1); u32 address; u32 GetPhysicalAddress() const { - return DecodeAddressRegister(address) - Memory::FCRAM_PADDR + Memory::HEAP_LINEAR_VADDR; + return DecodeAddressRegister(address); } // texture1 and texture2 store the texture format directly after the address @@ -131,36 +142,70 @@ struct Regs { RGBA5551 = 2, RGB565 = 3, RGBA4 = 4, + IA8 = 5, + + I8 = 7, + A8 = 8, + IA4 = 9, + A4 = 11, // TODO: Support for the other formats is not implemented, yet. // Seems like they are luminance formats and compressed textures. }; - static unsigned BytesPerPixel(TextureFormat format) { + static unsigned NibblesPerPixel(TextureFormat format) { switch (format) { case TextureFormat::RGBA8: - return 4; + return 8; case TextureFormat::RGB8: - return 3; + return 6; case TextureFormat::RGBA5551: case TextureFormat::RGB565: case TextureFormat::RGBA4: - return 2; + case TextureFormat::IA8: + return 4; - default: - // placeholder for yet unknown formats + case TextureFormat::A4: return 1; + + case TextureFormat::I8: + case TextureFormat::A8: + case TextureFormat::IA4: + default: // placeholder for yet unknown formats + return 2; } } - BitField< 0, 1, u32> texturing_enable; + union { + BitField< 0, 1, u32> texture0_enable; + BitField< 1, 1, u32> texture1_enable; + BitField< 2, 1, u32> texture2_enable; + }; TextureConfig texture0; INSERT_PADDING_WORDS(0x8); BitField<0, 4, TextureFormat> texture0_format; - - INSERT_PADDING_WORDS(0x31); + INSERT_PADDING_WORDS(0x2); + TextureConfig texture1; + BitField<0, 4, TextureFormat> texture1_format; + INSERT_PADDING_WORDS(0x2); + TextureConfig texture2; + BitField<0, 4, TextureFormat> texture2_format; + INSERT_PADDING_WORDS(0x21); + + struct FullTextureConfig { + const bool enabled; + const TextureConfig config; + const TextureFormat format; + }; + const std::array<FullTextureConfig, 3> GetTextures() const { + return {{ + { static_cast<bool>(texture0_enable), texture0, texture0_format }, + { static_cast<bool>(texture1_enable), texture1, texture1_format }, + { static_cast<bool>(texture2_enable), texture2, texture2_format } + }}; + } // 0xc0-0xff: Texture Combiner (akin to glTexEnv) struct TevStageConfig { @@ -282,11 +327,11 @@ struct Regs { INSERT_PADDING_WORDS(0x1); - inline u32 GetColorBufferAddress() const { - return Memory::PhysicalToVirtualAddress(DecodeAddressRegister(color_buffer_address)); + inline u32 GetColorBufferPhysicalAddress() const { + return DecodeAddressRegister(color_buffer_address); } - inline u32 GetDepthBufferAddress() const { - return Memory::PhysicalToVirtualAddress(DecodeAddressRegister(depth_buffer_address)); + inline u32 GetDepthBufferPhysicalAddress() const { + return DecodeAddressRegister(depth_buffer_address); } inline u32 GetWidth() const { @@ -310,9 +355,8 @@ struct Regs { BitField<0, 29, u32> base_address; - inline u32 GetBaseAddress() const { - // TODO: Ugly, should fix PhysicalToVirtualAddress instead - return DecodeAddressRegister(base_address) - Memory::FCRAM_PADDR + Memory::HEAP_LINEAR_VADDR; + u32 GetPhysicalBaseAddress() const { + return DecodeAddressRegister(base_address); } // Descriptor for internal vertex attributes @@ -448,7 +492,11 @@ struct Regs { BitField<8, 2, TriangleTopology> triangle_topology; - INSERT_PADDING_WORDS(0x5b); + INSERT_PADDING_WORDS(0x51); + + BitField<0, 16, u32> vs_bool_uniforms; + + INSERT_PADDING_WORDS(0x9); // Offset to shader program entry point (in words) BitField<0, 16, u32> vs_main_offset; @@ -556,9 +604,13 @@ struct Regs { ADD_FIELD(viewport_depth_range); ADD_FIELD(viewport_depth_far_plane); ADD_FIELD(viewport_corner); - ADD_FIELD(texturing_enable); + ADD_FIELD(texture0_enable); ADD_FIELD(texture0); ADD_FIELD(texture0_format); + ADD_FIELD(texture1); + ADD_FIELD(texture1_format); + ADD_FIELD(texture2); + ADD_FIELD(texture2_format); ADD_FIELD(tev_stage0); ADD_FIELD(tev_stage1); ADD_FIELD(tev_stage2); @@ -572,6 +624,7 @@ struct Regs { ADD_FIELD(trigger_draw); ADD_FIELD(trigger_draw_indexed); ADD_FIELD(triangle_topology); + ADD_FIELD(vs_bool_uniforms); ADD_FIELD(vs_main_offset); ADD_FIELD(vs_input_register_map); ADD_FIELD(vs_uniform_setup); @@ -622,9 +675,13 @@ ASSERT_REG_POSITION(viewport_depth_far_plane, 0x4e); ASSERT_REG_POSITION(vs_output_attributes[0], 0x50); ASSERT_REG_POSITION(vs_output_attributes[1], 0x51); ASSERT_REG_POSITION(viewport_corner, 0x68); -ASSERT_REG_POSITION(texturing_enable, 0x80); +ASSERT_REG_POSITION(texture0_enable, 0x80); ASSERT_REG_POSITION(texture0, 0x81); ASSERT_REG_POSITION(texture0_format, 0x8e); +ASSERT_REG_POSITION(texture1, 0x91); +ASSERT_REG_POSITION(texture1_format, 0x96); +ASSERT_REG_POSITION(texture2, 0x99); +ASSERT_REG_POSITION(texture2_format, 0x9e); ASSERT_REG_POSITION(tev_stage0, 0xc0); ASSERT_REG_POSITION(tev_stage1, 0xc8); ASSERT_REG_POSITION(tev_stage2, 0xd0); @@ -638,6 +695,7 @@ ASSERT_REG_POSITION(num_vertices, 0x228); ASSERT_REG_POSITION(trigger_draw, 0x22e); ASSERT_REG_POSITION(trigger_draw_indexed, 0x22f); ASSERT_REG_POSITION(triangle_topology, 0x25e); +ASSERT_REG_POSITION(vs_bool_uniforms, 0x2b0); ASSERT_REG_POSITION(vs_main_offset, 0x2ba); ASSERT_REG_POSITION(vs_input_register_map, 0x2bb); ASSERT_REG_POSITION(vs_uniform_setup, 0x2c0); @@ -719,6 +777,14 @@ struct float24 { return ToFloat32() <= flt.ToFloat32(); } + bool operator == (const float24& flt) const { + return ToFloat32() == flt.ToFloat32(); + } + + bool operator != (const float24& flt) const { + return ToFloat32() != flt.ToFloat32(); + } + private: // Stored as a regular float, merely for convenience // TODO: Perform proper arithmetic on this! @@ -736,5 +802,15 @@ union CommandHeader { BitField<31, 1, u32> group_commands; }; +// TODO: Ugly, should fix PhysicalToVirtualAddress instead +inline static u32 PAddrToVAddr(u32 addr) { + if (addr >= Memory::VRAM_PADDR && addr < Memory::VRAM_PADDR + Memory::VRAM_SIZE) { + return addr - Memory::VRAM_PADDR + Memory::VRAM_VADDR; + } else if (addr >= Memory::FCRAM_PADDR && addr < Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { + return addr - Memory::FCRAM_PADDR + Memory::HEAP_LINEAR_VADDR; + } else { + return 0; + } +} } // namespace diff --git a/src/video_core/primitive_assembly.cpp b/src/video_core/primitive_assembly.cpp index ad3b9d566..242a07e26 100644 --- a/src/video_core/primitive_assembly.cpp +++ b/src/video_core/primitive_assembly.cpp @@ -30,20 +30,27 @@ void PrimitiveAssembler<VertexType>::SubmitVertex(VertexType& vtx, TriangleHandl } break; + case Regs::TriangleTopology::Strip: case Regs::TriangleTopology::Fan: - if (buffer_index == 2) { - buffer_index = 0; - - triangle_handler(buffer[0], buffer[1], vtx); + if (strip_ready) { + // TODO: Should be "buffer[0], buffer[1], vtx" instead! + // Not quite sure why we need this order for things to show up properly. + // Maybe a bug in the rasterizer? + triangle_handler(buffer[1], buffer[0], vtx); + } + buffer[buffer_index] = vtx; - buffer[1] = vtx; - } else { - buffer[buffer_index++] = vtx; + if (topology == Regs::TriangleTopology::Strip) { + strip_ready |= (buffer_index == 1); + buffer_index = !buffer_index; + } else if (topology == Regs::TriangleTopology::Fan) { + buffer_index = 1; + strip_ready = true; } break; default: - LOG_ERROR(Render_Software, "Unknown triangle topology %x:", (int)topology); + LOG_ERROR(HW_GPU, "Unknown triangle topology %x:", (int)topology); break; } } diff --git a/src/video_core/primitive_assembly.h b/src/video_core/primitive_assembly.h index 116bd5de1..52ff4cd89 100644 --- a/src/video_core/primitive_assembly.h +++ b/src/video_core/primitive_assembly.h @@ -37,6 +37,7 @@ private: int buffer_index; VertexType buffer[2]; + bool strip_ready = false; }; diff --git a/src/video_core/rasterizer.cpp b/src/video_core/rasterizer.cpp index 4708c5ed8..df1f88c79 100644 --- a/src/video_core/rasterizer.cpp +++ b/src/video_core/rasterizer.cpp @@ -18,7 +18,7 @@ namespace Pica { namespace Rasterizer { static void DrawPixel(int x, int y, const Math::Vec4<u8>& color) { - u32* color_buffer = (u32*)Memory::GetPointer(registers.framebuffer.GetColorBufferAddress()); + u32* color_buffer = reinterpret_cast<u32*>(Memory::GetPointer(PAddrToVAddr(registers.framebuffer.GetColorBufferPhysicalAddress()))); u32 value = (color.a() << 24) | (color.r() << 16) | (color.g() << 8) | color.b(); // Assuming RGBA8 format until actual framebuffer format handling is implemented @@ -26,14 +26,14 @@ static void DrawPixel(int x, int y, const Math::Vec4<u8>& color) { } static u32 GetDepth(int x, int y) { - u16* depth_buffer = (u16*)Memory::GetPointer(registers.framebuffer.GetDepthBufferAddress()); + u16* depth_buffer = reinterpret_cast<u16*>(Memory::GetPointer(PAddrToVAddr(registers.framebuffer.GetDepthBufferPhysicalAddress()))); // Assuming 16-bit depth buffer format until actual format handling is implemented return *(depth_buffer + x + y * registers.framebuffer.GetWidth()); } static void SetDepth(int x, int y, u16 value) { - u16* depth_buffer = (u16*)Memory::GetPointer(registers.framebuffer.GetDepthBufferAddress()); + u16* depth_buffer = reinterpret_cast<u16*>(Memory::GetPointer(PAddrToVAddr(registers.framebuffer.GetDepthBufferPhysicalAddress()))); // Assuming 16-bit depth buffer format until actual format handling is implemented *(depth_buffer + x + y * registers.framebuffer.GetWidth()) = value; @@ -167,60 +167,48 @@ void ProcessTriangle(const VertexShader::OutputVertex& v0, (u8)(GetInterpolatedAttribute(v0.color.a(), v1.color.a(), v2.color.a()).ToFloat32() * 255) }; - Math::Vec4<u8> texture_color{}; - float24 u = GetInterpolatedAttribute(v0.tc0.u(), v1.tc0.u(), v2.tc0.u()); - float24 v = GetInterpolatedAttribute(v0.tc0.v(), v1.tc0.v(), v2.tc0.v()); - if (registers.texturing_enable) { - // Images are split into 8x8 tiles. Each tile is composed of four 4x4 subtiles each - // of which is composed of four 2x2 subtiles each of which is composed of four texels. - // Each structure is embedded into the next-bigger one in a diagonal pattern, e.g. - // texels are laid out in a 2x2 subtile like this: - // 2 3 - // 0 1 - // - // The full 8x8 tile has the texels arranged like this: - // - // 42 43 46 47 58 59 62 63 - // 40 41 44 45 56 57 60 61 - // 34 35 38 39 50 51 54 55 - // 32 33 36 37 48 49 52 53 - // 10 11 14 15 26 27 30 31 - // 08 09 12 13 24 25 28 29 - // 02 03 06 07 18 19 22 23 - // 00 01 04 05 16 17 20 21 - - // TODO: This is currently hardcoded for RGB8 - u32* texture_data = (u32*)Memory::GetPointer(registers.texture0.GetPhysicalAddress()); - - // TODO(neobrain): Not sure if this swizzling pattern is used for all textures. - // To be flexible in case different but similar patterns are used, we keep this - // somewhat inefficient code around for now. - int s = (int)(u * float24::FromFloat32(static_cast<float>(registers.texture0.width))).ToFloat32(); - int t = (int)(v * float24::FromFloat32(static_cast<float>(registers.texture0.height))).ToFloat32(); - int texel_index_within_tile = 0; - for (int block_size_index = 0; block_size_index < 3; ++block_size_index) { - int sub_tile_width = 1 << block_size_index; - int sub_tile_height = 1 << block_size_index; - - int sub_tile_index = (s & sub_tile_width) << block_size_index; - sub_tile_index += 2 * ((t & sub_tile_height) << block_size_index); - texel_index_within_tile += sub_tile_index; - } - - const int block_width = 8; - const int block_height = 8; - - int coarse_s = (s / block_width) * block_width; - int coarse_t = (t / block_height) * block_height; - - const int row_stride = registers.texture0.width * 3; - u8* source_ptr = (u8*)texture_data + coarse_s * block_height * 3 + coarse_t * row_stride + texel_index_within_tile * 3; - texture_color.r() = source_ptr[2]; - texture_color.g() = source_ptr[1]; - texture_color.b() = source_ptr[0]; - texture_color.a() = 0xFF; - - DebugUtils::DumpTexture(registers.texture0, (u8*)texture_data); + Math::Vec2<float24> uv[3]; + uv[0].u() = GetInterpolatedAttribute(v0.tc0.u(), v1.tc0.u(), v2.tc0.u()); + uv[0].v() = GetInterpolatedAttribute(v0.tc0.v(), v1.tc0.v(), v2.tc0.v()); + uv[1].u() = GetInterpolatedAttribute(v0.tc1.u(), v1.tc1.u(), v2.tc1.u()); + uv[1].v() = GetInterpolatedAttribute(v0.tc1.v(), v1.tc1.v(), v2.tc1.v()); + uv[2].u() = GetInterpolatedAttribute(v0.tc2.u(), v1.tc2.u(), v2.tc2.u()); + uv[2].v() = GetInterpolatedAttribute(v0.tc2.v(), v1.tc2.v(), v2.tc2.v()); + + Math::Vec4<u8> texture_color[3]{}; + for (int i = 0; i < 3; ++i) { + auto texture = registers.GetTextures()[i]; + if (!texture.enabled) + continue; + + _dbg_assert_(HW_GPU, 0 != texture.config.address); + + int s = (int)(uv[i].u() * float24::FromFloat32(static_cast<float>(texture.config.width))).ToFloat32(); + int t = (int)(uv[i].v() * float24::FromFloat32(static_cast<float>(texture.config.height))).ToFloat32(); + auto GetWrappedTexCoord = [](Regs::TextureConfig::WrapMode mode, int val, unsigned size) { + switch (mode) { + case Regs::TextureConfig::ClampToEdge: + val = std::max(val, 0); + val = std::min(val, (int)size - 1); + return val; + + case Regs::TextureConfig::Repeat: + return (int)(((unsigned)val) % size); + + default: + LOG_ERROR(HW_GPU, "Unknown texture coordinate wrapping mode %x\n", (int)mode); + _dbg_assert_(HW_GPU, 0); + return 0; + } + }; + s = GetWrappedTexCoord(registers.texture0.wrap_s, s, registers.texture0.width); + t = GetWrappedTexCoord(registers.texture0.wrap_t, t, registers.texture0.height); + + u8* texture_data = Memory::GetPointer(PAddrToVAddr(texture.config.GetPhysicalAddress())); + auto info = DebugUtils::TextureInfo::FromPicaRegister(texture.config, texture.format); + + texture_color[i] = DebugUtils::LookupTexture(texture_data, s, t, info); + DebugUtils::DumpTexture(texture.config, texture_data); } // Texture environment - consists of 6 stages of color and alpha combining. @@ -237,22 +225,29 @@ void ProcessTriangle(const VertexShader::OutputVertex& v0, using AlphaModifier = Regs::TevStageConfig::AlphaModifier; using Operation = Regs::TevStageConfig::Operation; - auto GetColorSource = [&](Source source) -> Math::Vec3<u8> { + auto GetColorSource = [&](Source source) -> Math::Vec4<u8> { switch (source) { case Source::PrimaryColor: - return primary_color.rgb(); + return primary_color; case Source::Texture0: - return texture_color.rgb(); + return texture_color[0]; + + case Source::Texture1: + return texture_color[1]; + + case Source::Texture2: + return texture_color[2]; case Source::Constant: - return {tev_stage.const_r, tev_stage.const_g, tev_stage.const_b}; + return {tev_stage.const_r, tev_stage.const_g, tev_stage.const_b, tev_stage.const_a}; case Source::Previous: - return combiner_output.rgb(); + return combiner_output; default: LOG_ERROR(HW_GPU, "Unknown color combiner source %d\n", (int)source); + _dbg_assert_(HW_GPU, 0); return {}; } }; @@ -263,7 +258,13 @@ void ProcessTriangle(const VertexShader::OutputVertex& v0, return primary_color.a(); case Source::Texture0: - return texture_color.a(); + return texture_color[0].a(); + + case Source::Texture1: + return texture_color[1].a(); + + case Source::Texture2: + return texture_color[2].a(); case Source::Constant: return tev_stage.const_a; @@ -273,17 +274,23 @@ void ProcessTriangle(const VertexShader::OutputVertex& v0, default: LOG_ERROR(HW_GPU, "Unknown alpha combiner source %d\n", (int)source); + _dbg_assert_(HW_GPU, 0); return 0; } }; - auto GetColorModifier = [](ColorModifier factor, const Math::Vec3<u8>& values) -> Math::Vec3<u8> { + auto GetColorModifier = [](ColorModifier factor, const Math::Vec4<u8>& values) -> Math::Vec3<u8> { switch (factor) { case ColorModifier::SourceColor: - return values; + return values.rgb(); + + case ColorModifier::SourceAlpha: + return { values.a(), values.a(), values.a() }; + default: LOG_ERROR(HW_GPU, "Unknown color factor %d\n", (int)factor); + _dbg_assert_(HW_GPU, 0); return {}; } }; @@ -292,8 +299,13 @@ void ProcessTriangle(const VertexShader::OutputVertex& v0, switch (factor) { case AlphaModifier::SourceAlpha: return value; + + case AlphaModifier::OneMinusSourceAlpha: + return 255 - value; + default: - LOG_ERROR(HW_GPU, "Unknown color factor %d\n", (int)factor); + LOG_ERROR(HW_GPU, "Unknown alpha factor %d\n", (int)factor); + _dbg_assert_(HW_GPU, 0); return 0; } }; @@ -306,8 +318,21 @@ void ProcessTriangle(const VertexShader::OutputVertex& v0, case Operation::Modulate: return ((input[0] * input[1]) / 255).Cast<u8>(); + case Operation::Add: + { + auto result = input[0] + input[1]; + result.r() = std::min(255, result.r()); + result.g() = std::min(255, result.g()); + result.b() = std::min(255, result.b()); + return result.Cast<u8>(); + } + + case Operation::Lerp: + return ((input[0] * input[2] + input[1] * (Math::MakeVec<u8>(255, 255, 255) - input[2]).Cast<u8>()) / 255).Cast<u8>(); + default: LOG_ERROR(HW_GPU, "Unknown color combiner operation %d\n", (int)op); + _dbg_assert_(HW_GPU, 0); return {}; } }; @@ -320,8 +345,15 @@ void ProcessTriangle(const VertexShader::OutputVertex& v0, case Operation::Modulate: return input[0] * input[1] / 255; + case Operation::Add: + return std::min(255, input[0] + input[1]); + + case Operation::Lerp: + return (input[0] * input[2] + input[1] * (255 - input[2])) / 255; + default: LOG_ERROR(HW_GPU, "Unknown alpha combiner operation %d\n", (int)op); + _dbg_assert_(HW_GPU, 0); return 0; } }; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 7bf36eca6..4df3a5e25 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -240,14 +240,14 @@ MathUtil::Rectangle<unsigned> RendererOpenGL::GetViewportExtent() { MathUtil::Rectangle<unsigned> viewport_extent; if (window_aspect_ratio > emulation_aspect_ratio) { // Window is narrower than the emulation content => apply borders to the top and bottom - unsigned viewport_height = std::round(emulation_aspect_ratio * framebuffer_width); + unsigned viewport_height = static_cast<unsigned>(std::round(emulation_aspect_ratio * framebuffer_width)); viewport_extent.left = 0; viewport_extent.top = (framebuffer_height - viewport_height) / 2; viewport_extent.right = viewport_extent.left + framebuffer_width; viewport_extent.bottom = viewport_extent.top + viewport_height; } else { // Otherwise, apply borders to the left and right sides of the window. - unsigned viewport_width = std::round(framebuffer_height / emulation_aspect_ratio); + unsigned viewport_width = static_cast<unsigned>(std::round(framebuffer_height / emulation_aspect_ratio)); viewport_extent.left = (framebuffer_width - viewport_width) / 2; viewport_extent.top = 0; viewport_extent.right = viewport_extent.left + viewport_width; diff --git a/src/video_core/vertex_shader.cpp b/src/video_core/vertex_shader.cpp index 04d439cc6..935fe66f0 100644 --- a/src/video_core/vertex_shader.cpp +++ b/src/video_core/vertex_shader.cpp @@ -2,16 +2,25 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <stack> + #include <boost/range/algorithm.hpp> #include <common/file_util.h> #include <core/mem_map.h> -#include "debug_utils/debug_utils.h" +#include <nihstro/shader_bytecode.h> + #include "pica.h" #include "vertex_shader.h" +#include "debug_utils/debug_utils.h" + +using nihstro::Instruction; +using nihstro::RegisterType; +using nihstro::SourceRegister; +using nihstro::SwizzlePattern; namespace Pica { @@ -19,13 +28,14 @@ namespace VertexShader { static struct { Math::Vec4<float24> f[96]; -} shader_uniforms; + std::array<bool,16> b; +} shader_uniforms; // TODO: Not sure where the shader binary and swizzle patterns are supposed to be loaded to! // For now, we just keep these local arrays around. -static u32 shader_memory[1024]; -static u32 swizzle_data[1024]; +static std::array<u32, 1024> shader_memory; +static std::array<u32, 1024> swizzle_data; void SubmitShaderMemoryChange(u32 addr, u32 value) { @@ -42,6 +52,21 @@ Math::Vec4<float24>& GetFloatUniform(u32 index) return shader_uniforms.f[index]; } +bool& GetBoolUniform(u32 index) +{ + return shader_uniforms.b[index]; +} + +const std::array<u32, 1024>& GetShaderBinary() +{ + return shader_memory; +} + +const std::array<u32, 1024>& GetSwizzlePatterns() +{ + return swizzle_data; +} + struct VertexShaderState { u32* program_counter; @@ -49,13 +74,23 @@ struct VertexShaderState { float24* output_register_table[7*4]; Math::Vec4<float24> temporary_registers[16]; - bool status_registers[2]; + bool conditional_code[2]; + + // Two Address registers and one loop counter + // TODO: How many bits do these actually have? + s32 address_registers[3]; enum { INVALID_ADDRESS = 0xFFFFFFFF }; - u32 call_stack[8]; // TODO: What is the maximal call stack depth? - u32* call_stack_pointer; + + struct CallStackElement { + u32 final_address; + u32 return_address; + }; + + // TODO: Is there a maximal size for this? + std::stack<CallStackElement> call_stack; struct { u32 max_offset; // maximum program counter ever reached @@ -64,49 +99,105 @@ struct VertexShaderState { }; static void ProcessShaderCode(VertexShaderState& state) { + + // Placeholder for invalid inputs + static float24 dummy_vec4_float24[4]; + while (true) { - bool increment_pc = true; + if (!state.call_stack.empty()) { + if (state.program_counter - shader_memory.data() == state.call_stack.top().final_address) { + state.program_counter = &shader_memory[state.call_stack.top().return_address]; + state.call_stack.pop(); + + // TODO: Is "trying again" accurate to hardware? + continue; + } + } + bool exit_loop = false; const Instruction& instr = *(const Instruction*)state.program_counter; - state.debug.max_offset = std::max<u32>(state.debug.max_offset, 1 + (state.program_counter - shader_memory)); - - const float24* src1_ = (instr.common.src1 < 0x10) ? state.input_register_table[instr.common.src1.GetIndex()] - : (instr.common.src1 < 0x20) ? &state.temporary_registers[instr.common.src1.GetIndex()].x - : (instr.common.src1 < 0x80) ? &shader_uniforms.f[instr.common.src1.GetIndex()].x - : nullptr; - const float24* src2_ = (instr.common.src2 < 0x10) ? state.input_register_table[instr.common.src2.GetIndex()] - : &state.temporary_registers[instr.common.src2.GetIndex()].x; - float24* dest = (instr.common.dest < 0x08) ? state.output_register_table[4*instr.common.dest.GetIndex()] - : (instr.common.dest < 0x10) ? nullptr - : (instr.common.dest < 0x20) ? &state.temporary_registers[instr.common.dest.GetIndex()][0] - : nullptr; - const SwizzlePattern& swizzle = *(SwizzlePattern*)&swizzle_data[instr.common.operand_desc_id]; - const bool negate_src1 = (swizzle.negate != 0); - float24 src1[4] = { - src1_[(int)swizzle.GetSelectorSrc1(0)], - src1_[(int)swizzle.GetSelectorSrc1(1)], - src1_[(int)swizzle.GetSelectorSrc1(2)], - src1_[(int)swizzle.GetSelectorSrc1(3)], + auto call = [&](VertexShaderState& state, u32 offset, u32 num_instructions, u32 return_offset) { + state.program_counter = &shader_memory[offset] - 1; // -1 to make sure when incrementing the PC we end up at the correct offset + state.call_stack.push({ offset + num_instructions, return_offset }); }; - if (negate_src1) { - src1[0] = src1[0] * float24::FromFloat32(-1); - src1[1] = src1[1] * float24::FromFloat32(-1); - src1[2] = src1[2] * float24::FromFloat32(-1); - src1[3] = src1[3] * float24::FromFloat32(-1); - } - const float24 src2[4] = { - src2_[(int)swizzle.GetSelectorSrc2(0)], - src2_[(int)swizzle.GetSelectorSrc2(1)], - src2_[(int)swizzle.GetSelectorSrc2(2)], - src2_[(int)swizzle.GetSelectorSrc2(3)], + u32 binary_offset = state.program_counter - shader_memory.data(); + + state.debug.max_offset = std::max<u32>(state.debug.max_offset, 1 + binary_offset); + + auto LookupSourceRegister = [&](const SourceRegister& source_reg) -> const float24* { + switch (source_reg.GetRegisterType()) { + case RegisterType::Input: + return state.input_register_table[source_reg.GetIndex()]; + + case RegisterType::Temporary: + return &state.temporary_registers[source_reg.GetIndex()].x; + + case RegisterType::FloatUniform: + return &shader_uniforms.f[source_reg.GetIndex()].x; + + default: + return dummy_vec4_float24; + } }; - switch (instr.opcode) { + switch (instr.opcode.GetInfo().type) { + case Instruction::OpCodeType::Arithmetic: + { + bool is_inverted = 0 != (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::SrcInversed); + if (is_inverted) { + // TODO: We don't really support this properly: For instance, the address register + // offset needs to be applied to SRC2 instead, etc. + // For now, we just abort in this situation. + LOG_CRITICAL(HW_GPU, "Bad condition..."); + exit(0); + } + + const int address_offset = (instr.common.address_register_index == 0) + ? 0 : state.address_registers[instr.common.address_register_index - 1]; + + const float24* src1_ = LookupSourceRegister(instr.common.GetSrc1(is_inverted) + address_offset); + const float24* src2_ = LookupSourceRegister(instr.common.GetSrc2(is_inverted)); + + const bool negate_src1 = (swizzle.negate_src1 != false); + const bool negate_src2 = (swizzle.negate_src2 != false); + + float24 src1[4] = { + src1_[(int)swizzle.GetSelectorSrc1(0)], + src1_[(int)swizzle.GetSelectorSrc1(1)], + src1_[(int)swizzle.GetSelectorSrc1(2)], + src1_[(int)swizzle.GetSelectorSrc1(3)], + }; + if (negate_src1) { + src1[0] = src1[0] * float24::FromFloat32(-1); + src1[1] = src1[1] * float24::FromFloat32(-1); + src1[2] = src1[2] * float24::FromFloat32(-1); + src1[3] = src1[3] * float24::FromFloat32(-1); + } + float24 src2[4] = { + src2_[(int)swizzle.GetSelectorSrc2(0)], + src2_[(int)swizzle.GetSelectorSrc2(1)], + src2_[(int)swizzle.GetSelectorSrc2(2)], + src2_[(int)swizzle.GetSelectorSrc2(3)], + }; + if (negate_src2) { + src2[0] = src2[0] * float24::FromFloat32(-1); + src2[1] = src2[1] * float24::FromFloat32(-1); + src2[2] = src2[2] * float24::FromFloat32(-1); + src2[3] = src2[3] * float24::FromFloat32(-1); + } + + float24* dest = (instr.common.dest < 0x08) ? state.output_register_table[4*instr.common.dest.GetIndex()] + : (instr.common.dest < 0x10) ? dummy_vec4_float24 + : (instr.common.dest < 0x20) ? &state.temporary_registers[instr.common.dest.GetIndex()][0] + : dummy_vec4_float24; + + state.debug.max_opdesc_id = std::max<u32>(state.debug.max_opdesc_id, 1+instr.common.operand_desc_id); + + switch (instr.opcode.EffectiveOpCode()) { case Instruction::OpCode::ADD: { - state.debug.max_opdesc_id = std::max<u32>(state.debug.max_opdesc_id, 1+instr.common.operand_desc_id); for (int i = 0; i < 4; ++i) { if (!swizzle.DestComponentEnabled(i)) continue; @@ -119,7 +210,6 @@ static void ProcessShaderCode(VertexShaderState& state) { case Instruction::OpCode::MUL: { - state.debug.max_opdesc_id = std::max<u32>(state.debug.max_opdesc_id, 1+instr.common.operand_desc_id); for (int i = 0; i < 4; ++i) { if (!swizzle.DestComponentEnabled(i)) continue; @@ -130,10 +220,18 @@ static void ProcessShaderCode(VertexShaderState& state) { break; } + case Instruction::OpCode::MAX: + for (int i = 0; i < 4; ++i) { + if (!swizzle.DestComponentEnabled(i)) + continue; + + dest[i] = std::max(src1[i], src2[i]); + } + break; + case Instruction::OpCode::DP3: case Instruction::OpCode::DP4: { - state.debug.max_opdesc_id = std::max<u32>(state.debug.max_opdesc_id, 1+instr.common.operand_desc_id); float24 dot = float24::FromFloat32(0.f); int num_components = (instr.opcode == Instruction::OpCode::DP3) ? 3 : 4; for (int i = 0; i < num_components; ++i) @@ -151,7 +249,6 @@ static void ProcessShaderCode(VertexShaderState& state) { // Reciprocal case Instruction::OpCode::RCP: { - state.debug.max_opdesc_id = std::max<u32>(state.debug.max_opdesc_id, 1+instr.common.operand_desc_id); for (int i = 0; i < 4; ++i) { if (!swizzle.DestComponentEnabled(i)) continue; @@ -167,7 +264,6 @@ static void ProcessShaderCode(VertexShaderState& state) { // Reciprocal Square Root case Instruction::OpCode::RSQ: { - state.debug.max_opdesc_id = std::max<u32>(state.debug.max_opdesc_id, 1+instr.common.operand_desc_id); for (int i = 0; i < 4; ++i) { if (!swizzle.DestComponentEnabled(i)) continue; @@ -180,9 +276,21 @@ static void ProcessShaderCode(VertexShaderState& state) { break; } + case Instruction::OpCode::MOVA: + { + for (int i = 0; i < 2; ++i) { + if (!swizzle.DestComponentEnabled(i)) + continue; + + // TODO: Figure out how the rounding is done on hardware + state.address_registers[i] = static_cast<s32>(src1[i].ToFloat32()); + } + + break; + } + case Instruction::OpCode::MOV: { - state.debug.max_opdesc_id = std::max<u32>(state.debug.max_opdesc_id, 1+instr.common.operand_desc_id); for (int i = 0; i < 4; ++i) { if (!swizzle.DestComponentEnabled(i)) continue; @@ -192,39 +300,137 @@ static void ProcessShaderCode(VertexShaderState& state) { break; } - case Instruction::OpCode::RET: - if (*state.call_stack_pointer == VertexShaderState::INVALID_ADDRESS) { - exit_loop = true; - } else { - // Jump back to call stack position, invalidate call stack entry, move up call stack pointer - state.program_counter = &shader_memory[*state.call_stack_pointer]; - *state.call_stack_pointer-- = VertexShaderState::INVALID_ADDRESS; + case Instruction::OpCode::CMP: + for (int i = 0; i < 2; ++i) { + // TODO: Can you restrict to one compare via dest masking? + + auto compare_op = instr.common.compare_op; + auto op = (i == 0) ? compare_op.x.Value() : compare_op.y.Value(); + + switch (op) { + case compare_op.Equal: + state.conditional_code[i] = (src1[i] == src2[i]); + break; + + case compare_op.NotEqual: + state.conditional_code[i] = (src1[i] != src2[i]); + break; + + case compare_op.LessThan: + state.conditional_code[i] = (src1[i] < src2[i]); + break; + + case compare_op.LessEqual: + state.conditional_code[i] = (src1[i] <= src2[i]); + break; + + case compare_op.GreaterThan: + state.conditional_code[i] = (src1[i] > src2[i]); + break; + + case compare_op.GreaterEqual: + state.conditional_code[i] = (src1[i] >= src2[i]); + break; + + default: + LOG_ERROR(HW_GPU, "Unknown compare mode %x", static_cast<int>(op)); + break; + } } + break; + default: + LOG_ERROR(HW_GPU, "Unhandled arithmetic instruction: 0x%02x (%s): 0x%08x", + (int)instr.opcode.Value(), instr.opcode.GetInfo().name, instr.hex); + _dbg_assert_(HW_GPU, 0); + break; + } + + break; + } + default: + // Handle each instruction on its own + switch (instr.opcode) { + case Instruction::OpCode::END: + exit_loop = true; break; case Instruction::OpCode::CALL: - increment_pc = false; + call(state, + instr.flow_control.dest_offset, + instr.flow_control.num_instructions, + binary_offset + 1); + break; + + case Instruction::OpCode::NOP: + break; - _dbg_assert_(HW_GPU, state.call_stack_pointer - state.call_stack < sizeof(state.call_stack)); + case Instruction::OpCode::IFU: + if (shader_uniforms.b[instr.flow_control.bool_uniform_id]) { + call(state, + binary_offset + 1, + instr.flow_control.dest_offset - binary_offset - 1, + instr.flow_control.dest_offset + instr.flow_control.num_instructions); + } else { + call(state, + instr.flow_control.dest_offset, + instr.flow_control.num_instructions, + instr.flow_control.dest_offset + instr.flow_control.num_instructions); + } - *++state.call_stack_pointer = state.program_counter - shader_memory; - // TODO: Does this offset refer to the beginning of shader memory? - state.program_counter = &shader_memory[instr.flow_control.offset_words]; break; - case Instruction::OpCode::FLS: - // TODO: Do whatever needs to be done here? + case Instruction::OpCode::IFC: + { + // TODO: Do we need to consider swizzlers here? + + auto flow_control = instr.flow_control; + bool results[3] = { flow_control.refx == state.conditional_code[0], + flow_control.refy == state.conditional_code[1] }; + + switch (flow_control.op) { + case flow_control.Or: + results[2] = results[0] || results[1]; + break; + + case flow_control.And: + results[2] = results[0] && results[1]; + break; + + case flow_control.JustX: + results[2] = results[0]; + break; + + case flow_control.JustY: + results[2] = results[1]; + break; + } + + if (results[2]) { + call(state, + binary_offset + 1, + instr.flow_control.dest_offset - binary_offset - 1, + instr.flow_control.dest_offset + instr.flow_control.num_instructions); + } else { + call(state, + instr.flow_control.dest_offset, + instr.flow_control.num_instructions, + instr.flow_control.dest_offset + instr.flow_control.num_instructions); + } + break; + } default: LOG_ERROR(HW_GPU, "Unhandled instruction: 0x%02x (%s): 0x%08x", - (int)instr.opcode.Value(), instr.GetOpCodeName().c_str(), instr.hex); + (int)instr.opcode.Value(), instr.opcode.GetInfo().name, instr.hex); break; + } + + break; } - if (increment_pc) - ++state.program_counter; + ++state.program_counter; if (exit_loop) break; @@ -275,13 +481,11 @@ OutputVertex RunShader(const InputVertex& input, int num_attributes) state.output_register_table[4*i+comp] = ((float24*)&ret) + semantics[comp]; } - state.status_registers[0] = false; - state.status_registers[1] = false; - boost::fill(state.call_stack, VertexShaderState::INVALID_ADDRESS); - state.call_stack_pointer = &state.call_stack[0]; + state.conditional_code[0] = false; + state.conditional_code[1] = false; ProcessShaderCode(state); - DebugUtils::DumpShader(shader_memory, state.debug.max_offset, swizzle_data, + DebugUtils::DumpShader(shader_memory.data(), state.debug.max_offset, swizzle_data.data(), state.debug.max_opdesc_id, registers.vs_main_offset, registers.vs_output_attributes); diff --git a/src/video_core/vertex_shader.h b/src/video_core/vertex_shader.h index e5f256bcb..af3fb2a2f 100644 --- a/src/video_core/vertex_shader.h +++ b/src/video_core/vertex_shader.h @@ -27,15 +27,18 @@ struct OutputVertex { Math::Vec4<float24> dummy; // quaternions (not implemented, yet) Math::Vec4<float24> color; Math::Vec2<float24> tc0; + Math::Vec2<float24> tc1; + float24 pad[6]; + Math::Vec2<float24> tc2; // Padding for optimal alignment - float24 pad[14]; + float24 pad2[4]; // Attributes used to store intermediate results // position after perspective divide Math::Vec3<float24> screenpos; - float24 pad2; + float24 pad3; // Linear interpolation // factor: 0=this, 1=vtx @@ -44,6 +47,8 @@ struct OutputVertex { // TODO: Should perform perspective correct interpolation here... tc0 = tc0 * factor + vtx.tc0 * (float24::FromFloat32(1) - factor); + tc1 = tc1 * factor + vtx.tc1 * (float24::FromFloat32(1) - factor); + tc2 = tc2 * factor + vtx.tc2 * (float24::FromFloat32(1) - factor); screenpos = screenpos * factor + vtx.screenpos * (float24::FromFloat32(1) - factor); @@ -61,222 +66,16 @@ struct OutputVertex { static_assert(std::is_pod<OutputVertex>::value, "Structure is not POD"); static_assert(sizeof(OutputVertex) == 32 * sizeof(float), "OutputVertex has invalid size"); -union Instruction { - enum class OpCode : u32 { - ADD = 0x0, - DP3 = 0x1, - DP4 = 0x2, - - MUL = 0x8, - - MAX = 0xC, - MIN = 0xD, - RCP = 0xE, - RSQ = 0xF, - - MOV = 0x13, - - RET = 0x21, - FLS = 0x22, // Flush - CALL = 0x24, - }; - - std::string GetOpCodeName() const { - std::map<OpCode, std::string> map = { - { OpCode::ADD, "ADD" }, - { OpCode::DP3, "DP3" }, - { OpCode::DP4, "DP4" }, - { OpCode::MUL, "MUL" }, - { OpCode::MAX, "MAX" }, - { OpCode::MIN, "MIN" }, - { OpCode::RCP, "RCP" }, - { OpCode::RSQ, "RSQ" }, - { OpCode::MOV, "MOV" }, - { OpCode::RET, "RET" }, - { OpCode::FLS, "FLS" }, - }; - auto it = map.find(opcode); - if (it == map.end()) - return "UNK"; - else - return it->second; - } - - u32 hex; - - BitField<0x1a, 0x6, OpCode> opcode; - - // General notes: - // - // When two input registers are used, one of them uses a 5-bit index while the other - // one uses a 7-bit index. This is because at most one floating point uniform may be used - // as an input. - - - // Format used e.g. by arithmetic instructions and comparisons - // "src1" and "src2" specify register indices (i.e. indices referring to groups of 4 floats), - // while "dest" addresses individual floats. - union { - BitField<0x00, 0x5, u32> operand_desc_id; - - template<class BitFieldType> - struct SourceRegister : BitFieldType { - enum RegisterType { - Input, - Temporary, - FloatUniform - }; - - RegisterType GetRegisterType() const { - if (BitFieldType::Value() < 0x10) - return Input; - else if (BitFieldType::Value() < 0x20) - return Temporary; - else - return FloatUniform; - } - - int GetIndex() const { - if (GetRegisterType() == Input) - return BitFieldType::Value(); - else if (GetRegisterType() == Temporary) - return BitFieldType::Value() - 0x10; - else // if (GetRegisterType() == FloatUniform) - return BitFieldType::Value() - 0x20; - } - - std::string GetRegisterName() const { - std::map<RegisterType, std::string> type = { - { Input, "i" }, - { Temporary, "t" }, - { FloatUniform, "f" }, - }; - return type[GetRegisterType()] + std::to_string(GetIndex()); - } - }; - - SourceRegister<BitField<0x07, 0x5, u32>> src2; - SourceRegister<BitField<0x0c, 0x7, u32>> src1; - - struct : BitField<0x15, 0x5, u32> - { - enum RegisterType { - Output, - Temporary, - Unknown - }; - RegisterType GetRegisterType() const { - if (Value() < 0x8) - return Output; - else if (Value() < 0x10) - return Unknown; - else - return Temporary; - } - int GetIndex() const { - if (GetRegisterType() == Output) - return Value(); - else if (GetRegisterType() == Temporary) - return Value() - 0x10; - else - return Value(); - } - std::string GetRegisterName() const { - std::map<RegisterType, std::string> type = { - { Output, "o" }, - { Temporary, "t" }, - { Unknown, "u" } - }; - return type[GetRegisterType()] + std::to_string(GetIndex()); - } - } dest; - } common; - - // Format used for flow control instructions ("if") - union { - BitField<0x00, 0x8, u32> num_instructions; - BitField<0x0a, 0xc, u32> offset_words; - } flow_control; -}; -static_assert(std::is_standard_layout<Instruction>::value, "Structure is not using standard layout!"); - -union SwizzlePattern { - u32 hex; - - enum class Selector : u32 { - x = 0, - y = 1, - z = 2, - w = 3 - }; - - Selector GetSelectorSrc1(int comp) const { - Selector selectors[] = { - src1_selector_0, src1_selector_1, src1_selector_2, src1_selector_3 - }; - return selectors[comp]; - } - - Selector GetSelectorSrc2(int comp) const { - Selector selectors[] = { - src2_selector_0, src2_selector_1, src2_selector_2, src2_selector_3 - }; - return selectors[comp]; - } - - bool DestComponentEnabled(int i) const { - return (dest_mask & (0x8 >> i)) != 0; - } - - std::string SelectorToString(bool src2) const { - std::map<Selector, std::string> map = { - { Selector::x, "x" }, - { Selector::y, "y" }, - { Selector::z, "z" }, - { Selector::w, "w" } - }; - std::string ret; - for (int i = 0; i < 4; ++i) { - ret += map.at(src2 ? GetSelectorSrc2(i) : GetSelectorSrc1(i)); - } - return ret; - } - - std::string DestMaskToString() const { - std::string ret; - for (int i = 0; i < 4; ++i) { - if (!DestComponentEnabled(i)) - ret += "_"; - else - ret += "xyzw"[i]; - } - return ret; - } - - // Components of "dest" that should be written to: LSB=dest.w, MSB=dest.x - BitField< 0, 4, u32> dest_mask; - - BitField< 4, 1, u32> negate; // negates src1 - - BitField< 5, 2, Selector> src1_selector_3; - BitField< 7, 2, Selector> src1_selector_2; - BitField< 9, 2, Selector> src1_selector_1; - BitField<11, 2, Selector> src1_selector_0; - - BitField<14, 2, Selector> src2_selector_3; - BitField<16, 2, Selector> src2_selector_2; - BitField<18, 2, Selector> src2_selector_1; - BitField<20, 2, Selector> src2_selector_0; - - BitField<31, 1, u32> flag; // not sure what this means, maybe it's the sign? -}; - void SubmitShaderMemoryChange(u32 addr, u32 value); void SubmitSwizzleDataChange(u32 addr, u32 value); OutputVertex RunShader(const InputVertex& input, int num_attributes); Math::Vec4<float24>& GetFloatUniform(u32 index); +bool& GetBoolUniform(u32 index); + +const std::array<u32, 1024>& GetShaderBinary(); +const std::array<u32, 1024>& GetSwizzlePatterns(); } // namespace |