diff options
25 files changed, 802 insertions, 165 deletions
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index c4bad8cb0..869da5e83 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -2,13 +2,16 @@ set(SRCS audio_core.cpp codec.cpp hle/dsp.cpp + hle/filter.cpp hle/pipe.cpp ) set(HEADERS audio_core.h codec.h + hle/common.h hle/dsp.h + hle/filter.h hle/pipe.h sink.h ) diff --git a/src/audio_core/hle/common.h b/src/audio_core/hle/common.h new file mode 100644 index 000000000..37d441eb2 --- /dev/null +++ b/src/audio_core/hle/common.h @@ -0,0 +1,35 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <algorithm> +#include <array> + +#include "audio_core/audio_core.h" + +#include "common/common_types.h" + +namespace DSP { +namespace HLE { + +/// The final output to the speakers is stereo. Preprocessing output in Source is also stereo. +using StereoFrame16 = std::array<std::array<s16, 2>, AudioCore::samples_per_frame>; + +/// The DSP is quadraphonic internally. +using QuadFrame32 = std::array<std::array<s32, 4>, AudioCore::samples_per_frame>; + +/** + * This performs the filter operation defined by FilterT::ProcessSample on the frame in-place. + * FilterT::ProcessSample is called sequentially on the samples. + */ +template<typename FrameT, typename FilterT> +void FilterFrame(FrameT& frame, FilterT& filter) { + std::transform(frame.begin(), frame.end(), frame.begin(), [&filter](const typename FrameT::value_type& sample) { + return filter.ProcessSample(sample); + }); +} + +} // namespace HLE +} // namespace DSP diff --git a/src/audio_core/hle/dsp.h b/src/audio_core/hle/dsp.h index 376436c29..c15ef0b7a 100644 --- a/src/audio_core/hle/dsp.h +++ b/src/audio_core/hle/dsp.h @@ -126,8 +126,11 @@ struct SourceConfiguration { union { u32_le dirty_raw; + BitField<0, 1, u32_le> format_dirty; + BitField<1, 1, u32_le> mono_or_stereo_dirty; BitField<2, 1, u32_le> adpcm_coefficients_dirty; BitField<3, 1, u32_le> partial_embedded_buffer_dirty; ///< Tends to be set when a looped buffer is queued. + BitField<4, 1, u32_le> partial_reset_flag; BitField<16, 1, u32_le> enable_dirty; BitField<17, 1, u32_le> interpolation_dirty; @@ -143,8 +146,7 @@ struct SourceConfiguration { BitField<27, 1, u32_le> gain_2_dirty; BitField<28, 1, u32_le> sync_dirty; BitField<29, 1, u32_le> reset_flag; - - BitField<31, 1, u32_le> embedded_buffer_dirty; + BitField<30, 1, u32_le> embedded_buffer_dirty; }; // Gain control @@ -175,7 +177,8 @@ struct SourceConfiguration { /** * This is the simplest normalized first-order digital recursive filter. * The transfer function of this filter is: - * H(z) = b0 / (1 + a1 z^-1) + * H(z) = b0 / (1 - a1 z^-1) + * Note the feedbackward coefficient is negated. * Values are signed fixed point with 15 fractional bits. */ struct SimpleFilter { @@ -192,11 +195,11 @@ struct SourceConfiguration { * Values are signed fixed point with 14 fractional bits. */ struct BiquadFilter { - s16_le b0; - s16_le b1; - s16_le b2; - s16_le a1; s16_le a2; + s16_le a1; + s16_le b2; + s16_le b1; + s16_le b0; }; union { diff --git a/src/audio_core/hle/filter.cpp b/src/audio_core/hle/filter.cpp new file mode 100644 index 000000000..2c65ef026 --- /dev/null +++ b/src/audio_core/hle/filter.cpp @@ -0,0 +1,115 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <cstddef> + +#include "audio_core/hle/common.h" +#include "audio_core/hle/dsp.h" +#include "audio_core/hle/filter.h" + +#include "common/common_types.h" +#include "common/math_util.h" + +namespace DSP { +namespace HLE { + +void SourceFilters::Reset() { + Enable(false, false); +} + +void SourceFilters::Enable(bool simple, bool biquad) { + simple_filter_enabled = simple; + biquad_filter_enabled = biquad; + + if (!simple) + simple_filter.Reset(); + if (!biquad) + biquad_filter.Reset(); +} + +void SourceFilters::Configure(SourceConfiguration::Configuration::SimpleFilter config) { + simple_filter.Configure(config); +} + +void SourceFilters::Configure(SourceConfiguration::Configuration::BiquadFilter config) { + biquad_filter.Configure(config); +} + +void SourceFilters::ProcessFrame(StereoFrame16& frame) { + if (!simple_filter_enabled && !biquad_filter_enabled) + return; + + if (simple_filter_enabled) { + FilterFrame(frame, simple_filter); + } + + if (biquad_filter_enabled) { + FilterFrame(frame, biquad_filter); + } +} + +// SimpleFilter + +void SourceFilters::SimpleFilter::Reset() { + y1.fill(0); + // Configure as passthrough. + a1 = 0; + b0 = 1 << 15; +} + +void SourceFilters::SimpleFilter::Configure(SourceConfiguration::Configuration::SimpleFilter config) { + a1 = config.a1; + b0 = config.b0; +} + +std::array<s16, 2> SourceFilters::SimpleFilter::ProcessSample(const std::array<s16, 2>& x0) { + std::array<s16, 2> y0; + for (size_t i = 0; i < 2; i++) { + const s32 tmp = (b0 * x0[i] + a1 * y1[i]) >> 15; + y0[i] = MathUtil::Clamp(tmp, -32768, 32767); + } + + y1 = y0; + + return y0; +} + +// BiquadFilter + +void SourceFilters::BiquadFilter::Reset() { + x1.fill(0); + x2.fill(0); + y1.fill(0); + y2.fill(0); + // Configure as passthrough. + a1 = a2 = b1 = b2 = 0; + b0 = 1 << 14; +} + +void SourceFilters::BiquadFilter::Configure(SourceConfiguration::Configuration::BiquadFilter config) { + a1 = config.a1; + a2 = config.a2; + b0 = config.b0; + b1 = config.b1; + b2 = config.b2; +} + +std::array<s16, 2> SourceFilters::BiquadFilter::ProcessSample(const std::array<s16, 2>& x0) { + std::array<s16, 2> y0; + for (size_t i = 0; i < 2; i++) { + const s32 tmp = (b0 * x0[i] + b1 * x1[i] + b2 * x2[i] + a1 * y1[i] + a2 * y2[i]) >> 14; + y0[i] = MathUtil::Clamp(tmp, -32768, 32767); + } + + x2 = x1; + x1 = x0; + y2 = y1; + y1 = y0; + + return y0; +} + +} // namespace HLE +} // namespace DSP diff --git a/src/audio_core/hle/filter.h b/src/audio_core/hle/filter.h new file mode 100644 index 000000000..75738f600 --- /dev/null +++ b/src/audio_core/hle/filter.h @@ -0,0 +1,112 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "audio_core/hle/common.h" +#include "audio_core/hle/dsp.h" + +#include "common/common_types.h" + +namespace DSP { +namespace HLE { + +/// Preprocessing filters. There is an independent set of filters for each Source. +class SourceFilters final { + SourceFilters() { Reset(); } + + /// Reset internal state. + void Reset(); + + /** + * Enable/Disable filters + * See also: SourceConfiguration::Configuration::simple_filter_enabled, + * SourceConfiguration::Configuration::biquad_filter_enabled. + * @param simple If true, enables the simple filter. If false, disables it. + * @param simple If true, enables the biquad filter. If false, disables it. + */ + void Enable(bool simple, bool biquad); + + /** + * Configure simple filter. + * @param config Configuration from DSP shared memory. + */ + void Configure(SourceConfiguration::Configuration::SimpleFilter config); + + /** + * Configure biquad filter. + * @param config Configuration from DSP shared memory. + */ + void Configure(SourceConfiguration::Configuration::BiquadFilter config); + + /** + * Processes a frame in-place. + * @param frame Audio samples to process. Modified in-place. + */ + void ProcessFrame(StereoFrame16& frame); + +private: + bool simple_filter_enabled; + bool biquad_filter_enabled; + + struct SimpleFilter { + SimpleFilter() { Reset(); } + + /// Resets internal state. + void Reset(); + + /** + * Configures this filter with application settings. + * @param config Configuration from DSP shared memory. + */ + void Configure(SourceConfiguration::Configuration::SimpleFilter config); + + /** + * Processes a single stereo PCM16 sample. + * @param x0 Input sample + * @return Output sample + */ + std::array<s16, 2> ProcessSample(const std::array<s16, 2>& x0); + + private: + // Configuration + s32 a1, b0; + // Internal state + std::array<s16, 2> y1; + } simple_filter; + + struct BiquadFilter { + BiquadFilter() { Reset(); } + + /// Resets internal state. + void Reset(); + + /** + * Configures this filter with application settings. + * @param config Configuration from DSP shared memory. + */ + void Configure(SourceConfiguration::Configuration::BiquadFilter config); + + /** + * Processes a single stereo PCM16 sample. + * @param x0 Input sample + * @return Output sample + */ + std::array<s16, 2> ProcessSample(const std::array<s16, 2>& x0); + + private: + // Configuration + s32 a1, a2, b0, b1, b2; + // Internal state + std::array<s16, 2> x1; + std::array<s16, 2> x2; + std::array<s16, 2> y1; + std::array<s16, 2> y2; + } biquad_filter; +}; + +} // namespace HLE +} // namespace DSP diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index 8e247ff5c..d1a19ade9 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -16,7 +16,7 @@ Config::Config() { // TODO: Don't hardcode the path; let the frontend decide where to put the config files. qt_config_loc = FileUtil::GetUserPath(D_CONFIG_IDX) + "qt-config.ini"; FileUtil::CreateFullPath(qt_config_loc); - qt_config = new QSettings(QString::fromStdString(qt_config_loc), QSettings::IniFormat); + qt_config = new QSettings(QString::fromLocal8Bit(qt_config_loc.c_str()), QSettings::IniFormat); Reload(); } diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index 1f8d69a03..a0b216b0a 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -66,7 +66,7 @@ void GameList::ValidateEntry(const QModelIndex& item) if (file_path.isEmpty()) return; - std::string std_file_path = file_path.toStdString(); + std::string std_file_path(file_path.toLocal8Bit()); if (!FileUtil::Exists(std_file_path) || FileUtil::IsDirectory(std_file_path)) return; emit GameChosen(file_path); @@ -148,7 +148,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, bool d emit EntryReady({ new GameListItem(QString::fromStdString(Loader::GetFileTypeString(filetype))), - new GameListItemPath(QString::fromStdString(physical_name)), + new GameListItemPath(QString::fromLocal8Bit(physical_name.c_str())), new GameListItemSize(FileUtil::GetSize(physical_name)), }); } diff --git a/src/common/emu_window.h b/src/common/emu_window.h index a0ae4c9fa..7c3486dea 100644 --- a/src/common/emu_window.h +++ b/src/common/emu_window.h @@ -105,7 +105,7 @@ public: * @todo Fix this function to be thread-safe. * @return PadState object indicating the current pad state */ - const Service::HID::PadState GetPadState() const { + Service::HID::PadState GetPadState() const { return pad_state; } @@ -116,11 +116,59 @@ public: * @return std::tuple of (x, y, pressed) where `x` and `y` are the touch coordinates and * `pressed` is true if the touch screen is currently being pressed */ - const std::tuple<u16, u16, bool> GetTouchState() const { + std::tuple<u16, u16, bool> GetTouchState() const { return std::make_tuple(touch_x, touch_y, touch_pressed); } /** + * Gets the current accelerometer state (acceleration along each three axis). + * Axis explained: + * +x is the same direction as LEFT on D-pad. + * +y is normal to the touch screen, pointing outward. + * +z is the same direction as UP on D-pad. + * Units: + * 1 unit of return value = 1/512 g (measured by hw test), + * where g is the gravitational acceleration (9.8 m/sec2). + * @note This should be called by the core emu thread to get a state set by the window thread. + * @todo Implement accelerometer input in front-end. + * @return std::tuple of (x, y, z) + */ + std::tuple<s16, s16, s16> GetAccelerometerState() const { + // stubbed + return std::make_tuple(0, -512, 0); + } + + /** + * Gets the current gyroscope state (angular rates about each three axis). + * Axis explained: + * +x is the same direction as LEFT on D-pad. + * +y is normal to the touch screen, pointing outward. + * +z is the same direction as UP on D-pad. + * Orientation is determined by right-hand rule. + * Units: + * 1 unit of return value = (1/coef) deg/sec, + * where coef is the return value of GetGyroscopeRawToDpsCoefficient(). + * @note This should be called by the core emu thread to get a state set by the window thread. + * @todo Implement gyroscope input in front-end. + * @return std::tuple of (x, y, z) + */ + std::tuple<s16, s16, s16> GetGyroscopeState() const { + // stubbed + return std::make_tuple(0, 0, 0); + } + + /** + * Gets the coefficient for units conversion of gyroscope state. + * The conversion formula is r = coefficient * v, + * where v is angular rate in deg/sec, + * and r is the gyroscope state. + * @return float-type coefficient + */ + f32 GetGyroscopeRawToDpsCoefficient() const { + return 14.375f; // taken from hw test, and gyroscope's document + } + + /** * Returns currently active configuration. * @note Accesses to the returned object need not be consistent because it may be modified in another thread */ diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 97d2a2242..cfbfbc2a7 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -46,6 +46,7 @@ namespace Log { SUB(Service, NIM) \ SUB(Service, NWM) \ SUB(Service, CAM) \ + SUB(Service, CECD) \ SUB(Service, CFG) \ SUB(Service, DSP) \ SUB(Service, DLP) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index d0c6c5f43..4f6856f3d 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -61,6 +61,7 @@ enum class Class : ClassType { Service_NIM, ///< The NIM (Network interface manager) service Service_NWM, ///< The NWM (Network wlan manager) service Service_CAM, ///< The CAM (Camera) service + Service_CECD, ///< The CECD service Service_CFG, ///< The CFG (Configuration) service Service_DSP, ///< The DSP (DSP control) service Service_DLP, ///< The DLP (Download Play) service diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp index 5f8826034..9ed61947e 100644 --- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp +++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp @@ -36,7 +36,8 @@ enum { CALL = (1 << 4), RET = (1 << 5), END_OF_PAGE = (1 << 6), - THUMB = (1 << 7) + THUMB = (1 << 7), + SINGLE_STEP = (1 << 8) }; #define RM BITS(sht_oper, 0, 3) @@ -3466,7 +3467,35 @@ enum { MICROPROFILE_DEFINE(DynCom_Decode, "DynCom", "Decode", MP_RGB(255, 64, 64)); -static int InterpreterTranslate(ARMul_State* cpu, int& bb_start, u32 addr) { +static unsigned int InterpreterTranslateInstruction(const ARMul_State* cpu, const u32 phys_addr, ARM_INST_PTR& inst_base) { + unsigned int inst_size = 4; + unsigned int inst = Memory::Read32(phys_addr & 0xFFFFFFFC); + + // If we are in Thumb mode, we'll translate one Thumb instruction to the corresponding ARM instruction + if (cpu->TFlag) { + u32 arm_inst; + ThumbDecodeStatus state = DecodeThumbInstruction(inst, phys_addr, &arm_inst, &inst_size, &inst_base); + + // We have translated the Thumb branch instruction in the Thumb decoder + if (state == ThumbDecodeStatus::BRANCH) { + return inst_size; + } + inst = arm_inst; + } + + int idx; + if (DecodeARMInstruction(inst, &idx) == ARMDecodeStatus::FAILURE) { + std::string disasm = ARM_Disasm::Disassemble(phys_addr, inst); + LOG_ERROR(Core_ARM11, "Decode failure.\tPC : [0x%x]\tInstruction : %s [%x]", phys_addr, disasm.c_str(), inst); + LOG_ERROR(Core_ARM11, "cpsr=0x%x, cpu->TFlag=%d, r15=0x%x", cpu->Cpsr, cpu->TFlag, cpu->Reg[15]); + CITRA_IGNORE_EXIT(-1); + } + inst_base = arm_instruction_trans[idx](inst, idx); + + return inst_size; +} + +static int InterpreterTranslateBlock(ARMul_State* cpu, int& bb_start, u32 addr) { Common::Profiling::ScopeTimer timer_decode(profile_decode); MICROPROFILE_SCOPE(DynCom_Decode); @@ -3475,8 +3504,6 @@ static int InterpreterTranslate(ARMul_State* cpu, int& bb_start, u32 addr) { // Go on next, until terminal instruction // Save start addr of basicblock in CreamCache ARM_INST_PTR inst_base = nullptr; - unsigned int inst, inst_size = 4; - int idx; int ret = NON_BRANCH; int size = 0; // instruction size of basic block bb_start = top; @@ -3485,30 +3512,10 @@ static int InterpreterTranslate(ARMul_State* cpu, int& bb_start, u32 addr) { u32 pc_start = cpu->Reg[15]; while (ret == NON_BRANCH) { - inst = Memory::Read32(phys_addr & 0xFFFFFFFC); + unsigned int inst_size = InterpreterTranslateInstruction(cpu, phys_addr, inst_base); size++; - // If we are in Thumb mode, we'll translate one Thumb instruction to the corresponding ARM instruction - if (cpu->TFlag) { - u32 arm_inst; - ThumbDecodeStatus state = DecodeThumbInstruction(inst, phys_addr, &arm_inst, &inst_size, &inst_base); - - // We have translated the Thumb branch instruction in the Thumb decoder - if (state == ThumbDecodeStatus::BRANCH) { - goto translated; - } - inst = arm_inst; - } - - if (DecodeARMInstruction(inst, &idx) == ARMDecodeStatus::FAILURE) { - std::string disasm = ARM_Disasm::Disassemble(phys_addr, inst); - LOG_ERROR(Core_ARM11, "Decode failure.\tPC : [0x%x]\tInstruction : %s [%x]", phys_addr, disasm.c_str(), inst); - LOG_ERROR(Core_ARM11, "cpsr=0x%x, cpu->TFlag=%d, r15=0x%x", cpu->Cpsr, cpu->TFlag, cpu->Reg[15]); - CITRA_IGNORE_EXIT(-1); - } - inst_base = arm_instruction_trans[idx](inst, idx); -translated: phys_addr += inst_size; if ((phys_addr & 0xfff) == 0) { @@ -3522,6 +3529,27 @@ translated: return KEEP_GOING; } +static int InterpreterTranslateSingle(ARMul_State* cpu, int& bb_start, u32 addr) { + Common::Profiling::ScopeTimer timer_decode(profile_decode); + MICROPROFILE_SCOPE(DynCom_Decode); + + ARM_INST_PTR inst_base = nullptr; + bb_start = top; + + u32 phys_addr = addr; + u32 pc_start = cpu->Reg[15]; + + InterpreterTranslateInstruction(cpu, phys_addr, inst_base); + + if (inst_base->br == NON_BRANCH) { + inst_base->br = SINGLE_STEP; + } + + cpu->instruction_cache[pc_start] = bb_start; + + return KEEP_GOING; +} + static int clz(unsigned int x) { int n; if (x == 0) return (32); @@ -3871,8 +3899,11 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { auto itr = cpu->instruction_cache.find(cpu->Reg[15]); if (itr != cpu->instruction_cache.end()) { ptr = itr->second; + } else if (cpu->NumInstrsToExecute != 1) { + if (InterpreterTranslateBlock(cpu, ptr, cpu->Reg[15]) == FETCH_EXCEPTION) + goto END; } else { - if (InterpreterTranslate(cpu, ptr, cpu->Reg[15]) == FETCH_EXCEPTION) + if (InterpreterTranslateSingle(cpu, ptr, cpu->Reg[15]) == FETCH_EXCEPTION) goto END; } diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 0cb76ba1c..2d22652d9 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -24,6 +24,7 @@ enum class ErrorDescription : u32 { FS_InvalidOpenFlags = 230, FS_NotAFile = 250, FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive + OutofRangeOrMisalignedAddress = 513, // TODO(purpasmart): Check if this name fits its actual usage FS_InvalidPath = 702, InvalidSection = 1000, TooLarge = 1001, diff --git a/src/core/hle/service/cecd/cecd.cpp b/src/core/hle/service/cecd/cecd.cpp index 6d79ce9b4..e6e36e7ec 100644 --- a/src/core/hle/service/cecd/cecd.cpp +++ b/src/core/hle/service/cecd/cecd.cpp @@ -4,6 +4,7 @@ #include "common/logging/log.h" +#include "core/hle/kernel/event.h" #include "core/hle/service/service.h" #include "core/hle/service/cecd/cecd.h" #include "core/hle/service/cecd/cecd_s.h" @@ -12,14 +13,38 @@ namespace Service { namespace CECD { -void Init() { - using namespace Kernel; +static Kernel::SharedPtr<Kernel::Event> cecinfo_event; +static Kernel::SharedPtr<Kernel::Event> change_state_event; + +void GetCecInfoEventHandle(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[3] = Kernel::g_handle_table.Create(cecinfo_event).MoveFrom(); // Event handle + + LOG_WARNING(Service_CECD, "(STUBBED) called"); +} + +void GetChangeStateEventHandle(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[3] = Kernel::g_handle_table.Create(change_state_event).MoveFrom(); // Event handle + + LOG_WARNING(Service_CECD, "(STUBBED) called"); +} + +void Init() { AddService(new CECD_S_Interface); AddService(new CECD_U_Interface); + + cecinfo_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "CECD_U::cecinfo_event"); + change_state_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "CECD_U::change_state_event"); } void Shutdown() { + cecinfo_event = nullptr; + change_state_event = nullptr; } } // namespace CECD diff --git a/src/core/hle/service/cecd/cecd.h b/src/core/hle/service/cecd/cecd.h index 9e158521b..89a8d67bb 100644 --- a/src/core/hle/service/cecd/cecd.h +++ b/src/core/hle/service/cecd/cecd.h @@ -5,8 +5,31 @@ #pragma once namespace Service { + +class Interface; + namespace CECD { +/** + * GetCecInfoEventHandle service function + * Inputs: + * 0: 0x000F0000 + * Outputs: + * 1: ResultCode + * 3: Event Handle + */ +void GetCecInfoEventHandle(Service::Interface* self); + +/** + * GetChangeStateEventHandle service function + * Inputs: + * 0: 0x00100000 + * Outputs: + * 1: ResultCode + * 3: Event Handle + */ +void GetChangeStateEventHandle(Service::Interface* self); + /// Initialize CECD service(s) void Init(); diff --git a/src/core/hle/service/cecd/cecd_u.cpp b/src/core/hle/service/cecd/cecd_u.cpp index 9b720a738..ace1c73c0 100644 --- a/src/core/hle/service/cecd/cecd_u.cpp +++ b/src/core/hle/service/cecd/cecd_u.cpp @@ -2,13 +2,16 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/service/cecd/cecd.h" #include "core/hle/service/cecd/cecd_u.h" namespace Service { namespace CECD { static const Interface::FunctionInfo FunctionTable[] = { - { 0x00120104, nullptr, "ReadSavedData" }, + {0x000F0000, GetCecInfoEventHandle, "GetCecInfoEventHandle"}, + {0x00100000, GetChangeStateEventHandle, "GetChangeStateEventHandle"}, + {0x00120104, nullptr, "ReadSavedData"}, }; CECD_U_Interface::CECD_U_Interface() { diff --git a/src/core/hle/service/cfg/cfg_i.cpp b/src/core/hle/service/cfg/cfg_i.cpp index 0559a07b2..b18060f6d 100644 --- a/src/core/hle/service/cfg/cfg_i.cpp +++ b/src/core/hle/service/cfg/cfg_i.cpp @@ -9,6 +9,18 @@ namespace Service { namespace CFG { const Interface::FunctionInfo FunctionTable[] = { + // cfg common + {0x00010082, GetConfigInfoBlk2, "GetConfigInfoBlk2"}, + {0x00020000, SecureInfoGetRegion, "SecureInfoGetRegion"}, + {0x00030040, GenHashConsoleUnique, "GenHashConsoleUnique"}, + {0x00040000, GetRegionCanadaUSA, "GetRegionCanadaUSA"}, + {0x00050000, GetSystemModel, "GetSystemModel"}, + {0x00060000, GetModelNintendo2DS, "GetModelNintendo2DS"}, + {0x00070040, nullptr, "WriteToFirstByteCfgSavegame"}, + {0x00080080, nullptr, "GoThroughTable"}, + {0x00090040, GetCountryCodeString, "GetCountryCodeString"}, + {0x000A0040, GetCountryCodeID, "GetCountryCodeID"}, + // cfg:i {0x04010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"}, {0x04020082, nullptr, "SetConfigInfoBlk4"}, {0x04030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"}, diff --git a/src/core/hle/service/cfg/cfg_s.cpp b/src/core/hle/service/cfg/cfg_s.cpp index b03d290e5..e001f7687 100644 --- a/src/core/hle/service/cfg/cfg_s.cpp +++ b/src/core/hle/service/cfg/cfg_s.cpp @@ -9,10 +9,18 @@ namespace Service { namespace CFG { const Interface::FunctionInfo FunctionTable[] = { + // cfg common {0x00010082, GetConfigInfoBlk2, "GetConfigInfoBlk2"}, {0x00020000, SecureInfoGetRegion, "SecureInfoGetRegion"}, {0x00030040, GenHashConsoleUnique, "GenHashConsoleUnique"}, + {0x00040000, GetRegionCanadaUSA, "GetRegionCanadaUSA"}, {0x00050000, GetSystemModel, "GetSystemModel"}, + {0x00060000, GetModelNintendo2DS, "GetModelNintendo2DS"}, + {0x00070040, nullptr, "WriteToFirstByteCfgSavegame"}, + {0x00080080, nullptr, "GoThroughTable"}, + {0x00090040, GetCountryCodeString, "GetCountryCodeString"}, + {0x000A0040, GetCountryCodeID, "GetCountryCodeID"}, + // cfg:s {0x04010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"}, {0x04020082, nullptr, "SetConfigInfoBlk4"}, {0x04030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"}, diff --git a/src/core/hle/service/cfg/cfg_u.cpp b/src/core/hle/service/cfg/cfg_u.cpp index 89ae96c9e..606f7b2eb 100644 --- a/src/core/hle/service/cfg/cfg_u.cpp +++ b/src/core/hle/service/cfg/cfg_u.cpp @@ -9,6 +9,7 @@ namespace Service { namespace CFG { const Interface::FunctionInfo FunctionTable[] = { + // cfg common {0x00010082, GetConfigInfoBlk2, "GetConfigInfoBlk2"}, {0x00020000, SecureInfoGetRegion, "SecureInfoGetRegion"}, {0x00030040, GenHashConsoleUnique, "GenHashConsoleUnique"}, diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index 2ace2cade..0c655395e 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -31,6 +31,13 @@ const static u32 REGS_BEGIN = 0x1EB00000; namespace GSP_GPU { +const ResultCode ERR_GSP_REGS_OUTOFRANGE_OR_MISALIGNED(ErrorDescription::OutofRangeOrMisalignedAddress, ErrorModule::GX, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E02A01 +const ResultCode ERR_GSP_REGS_MISALIGNED(ErrorDescription::MisalignedSize, ErrorModule::GX, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E02BF2 +const ResultCode ERR_GSP_REGS_INVALID_SIZE(ErrorDescription::InvalidSize, ErrorModule::GX, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E02BEC + /// Event triggered when GSP interrupt has been signalled Kernel::SharedPtr<Kernel::Event> g_interrupt_event; /// GSP shared memoryings @@ -59,47 +66,87 @@ static inline InterruptRelayQueue* GetInterruptRelayQueue(u32 thread_id) { } /** - * Checks if the parameters in a register write call are valid and logs in the case that - * they are not - * @param base_address The first address in the sequence of registers that will be written - * @param size_in_bytes The number of registers that will be written - * @return true if the parameters are valid, false otherwise + * Writes sequential GSP GPU hardware registers using an array of source data + * + * @param base_address The address of the first register in the sequence + * @param size_in_bytes The number of registers to update (size of data) + * @param data A pointer to the source data + * @return RESULT_SUCCESS if the parameters are valid, error code otherwise */ -static bool CheckWriteParameters(u32 base_address, u32 size_in_bytes) { - // TODO: Return proper error codes - if (base_address + size_in_bytes >= 0x420000) { - LOG_ERROR(Service_GSP, "Write address out of range! (address=0x%08x, size=0x%08x)", +static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, const u32* data) { + // This magic number is verified to be done by the gsp module + const u32 max_size_in_bytes = 0x80; + + if (base_address & 3 || base_address >= 0x420000) { + LOG_ERROR(Service_GSP, "Write address was out of range or misaligned! (address=0x%08x, size=0x%08x)", base_address, size_in_bytes); - return false; - } + return ERR_GSP_REGS_OUTOFRANGE_OR_MISALIGNED; + } else if (size_in_bytes <= max_size_in_bytes) { + if (size_in_bytes & 3) { + LOG_ERROR(Service_GSP, "Misaligned size 0x%08x", size_in_bytes); + return ERR_GSP_REGS_MISALIGNED; + } else { + while (size_in_bytes > 0) { + HW::Write<u32>(base_address + REGS_BEGIN, *data); + + size_in_bytes -= 4; + ++data; + base_address += 4; + } + return RESULT_SUCCESS; + } - // size should be word-aligned - if ((size_in_bytes % 4) != 0) { - LOG_ERROR(Service_GSP, "Invalid size 0x%08x", size_in_bytes); - return false; + } else { + LOG_ERROR(Service_GSP, "Out of range size 0x%08x", size_in_bytes); + return ERR_GSP_REGS_INVALID_SIZE; } - - return true; } /** - * Writes sequential GSP GPU hardware registers using an array of source data + * Updates sequential GSP GPU hardware registers using parallel arrays of source data and masks. + * For each register, the value is updated only where the mask is high * * @param base_address The address of the first register in the sequence * @param size_in_bytes The number of registers to update (size of data) - * @param data A pointer to the source data + * @param data A pointer to the source data to use for updates + * @param masks A pointer to the masks + * @return RESULT_SUCCESS if the parameters are valid, error code otherwise */ -static void WriteHWRegs(u32 base_address, u32 size_in_bytes, const u32* data) { - // TODO: Return proper error codes - if (!CheckWriteParameters(base_address, size_in_bytes)) - return; +static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, const u32* data, const u32* masks) { + // This magic number is verified to be done by the gsp module + const u32 max_size_in_bytes = 0x80; - while (size_in_bytes > 0) { - HW::Write<u32>(base_address + REGS_BEGIN, *data); + if (base_address & 3 || base_address >= 0x420000) { + LOG_ERROR(Service_GSP, "Write address was out of range or misaligned! (address=0x%08x, size=0x%08x)", + base_address, size_in_bytes); + return ERR_GSP_REGS_OUTOFRANGE_OR_MISALIGNED; + } else if (size_in_bytes <= max_size_in_bytes) { + if (size_in_bytes & 3) { + LOG_ERROR(Service_GSP, "Misaligned size 0x%08x", size_in_bytes); + return ERR_GSP_REGS_MISALIGNED; + } else { + while (size_in_bytes > 0) { + const u32 reg_address = base_address + REGS_BEGIN; + + u32 reg_value; + HW::Read<u32>(reg_value, reg_address); + + // Update the current value of the register only for set mask bits + reg_value = (reg_value & ~*masks) | (*data | *masks); + + HW::Write<u32>(reg_address, reg_value); + + size_in_bytes -= 4; + ++data; + ++masks; + base_address += 4; + } + return RESULT_SUCCESS; + } - size_in_bytes -= 4; - ++data; - base_address += 4; + } else { + LOG_ERROR(Service_GSP, "Out of range size 0x%08x", size_in_bytes); + return ERR_GSP_REGS_INVALID_SIZE; } } @@ -120,39 +167,7 @@ static void WriteHWRegs(Service::Interface* self) { u32* src = (u32*)Memory::GetPointer(cmd_buff[4]); - WriteHWRegs(reg_addr, size, src); -} - -/** - * Updates sequential GSP GPU hardware registers using parallel arrays of source data and masks. - * For each register, the value is updated only where the mask is high - * - * @param base_address The address of the first register in the sequence - * @param size_in_bytes The number of registers to update (size of data) - * @param data A pointer to the source data to use for updates - * @param masks A pointer to the masks - */ -static void WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, const u32* data, const u32* masks) { - // TODO: Return proper error codes - if (!CheckWriteParameters(base_address, size_in_bytes)) - return; - - while (size_in_bytes > 0) { - const u32 reg_address = base_address + REGS_BEGIN; - - u32 reg_value; - HW::Read<u32>(reg_value, reg_address); - - // Update the current value of the register only for set mask bits - reg_value = (reg_value & ~*masks) | (*data | *masks); - - HW::Write<u32>(reg_address, reg_value); - - size_in_bytes -= 4; - ++data; - ++masks; - base_address += 4; - } + cmd_buff[1] = WriteHWRegs(reg_addr, size, src).raw; } /** @@ -174,7 +189,7 @@ static void WriteHWRegsWithMask(Service::Interface* self) { u32* src_data = (u32*)Memory::GetPointer(cmd_buff[4]); u32* mask_data = (u32*)Memory::GetPointer(cmd_buff[6]); - WriteHWRegsWithMask(reg_addr, size, src_data, mask_data); + cmd_buff[1] = WriteHWRegsWithMask(reg_addr, size, src_data, mask_data).raw; } /// Read a GSP GPU hardware register @@ -206,27 +221,27 @@ static void ReadHWRegs(Service::Interface* self) { } } -void SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) { +ResultCode SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) { u32 base_address = 0x400000; PAddr phys_address_left = Memory::VirtualToPhysicalAddress(info.address_left); PAddr phys_address_right = Memory::VirtualToPhysicalAddress(info.address_right); if (info.active_fb == 0) { - WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left1)), 4, - &phys_address_left); - WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right1)), 4, - &phys_address_right); + WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left1)), + 4, &phys_address_left); + WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right1)), + 4, &phys_address_right); } else { - WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left2)), 4, - &phys_address_left); - WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right2)), 4, - &phys_address_right); + WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left2)), + 4, &phys_address_left); + WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right2)), + 4, &phys_address_right); } - WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].stride)), 4, - &info.stride); - WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].color_format)), 4, - &info.format); - WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].active_fb)), 4, - &info.shown_fb); + WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].stride)), + 4, &info.stride); + WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].color_format)), + 4, &info.format); + WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].active_fb)), + 4, &info.shown_fb); if (Pica::g_debug_context) Pica::g_debug_context->OnEvent(Pica::DebugContext::Event::BufferSwapped, nullptr); @@ -234,6 +249,8 @@ void SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) { if (screen_id == 0) { MicroProfileFlip(); } + + return RESULT_SUCCESS; } /** @@ -251,9 +268,8 @@ static void SetBufferSwap(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 screen_id = cmd_buff[1]; FrameBufferInfo* fb_info = (FrameBufferInfo*)&cmd_buff[2]; - SetBufferSwap(screen_id, *fb_info); - cmd_buff[1] = 0; // No error + cmd_buff[1] = SetBufferSwap(screen_id, *fb_info).raw; } /** @@ -286,6 +302,22 @@ static void FlushDataCache(Service::Interface* self) { } /** + * GSP_GPU::SetAxiConfigQoSMode service function + * Inputs: + * 1 : Mode, unused in emulator + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void SetAxiConfigQoSMode(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 mode = cmd_buff[1]; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_GSP, "(STUBBED) called mode=0x%08X", mode); +} + +/** * GSP_GPU::RegisterInterruptRelayQueue service function * Inputs: * 1 : "Flags" field, purpose is unknown @@ -302,6 +334,12 @@ static void RegisterInterruptRelayQueue(Service::Interface* self) { g_interrupt_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[3]); ASSERT_MSG((g_interrupt_event != nullptr), "handle is not valid!"); + g_interrupt_event->name = "GSP_GPU::interrupt_event"; + + using Kernel::MemoryPermission; + g_shared_memory = Kernel::SharedMemory::Create(0x1000, MemoryPermission::ReadWrite, + MemoryPermission::ReadWrite, "GSPSharedMem"); + Handle shmem_handle = Kernel::g_handle_table.Create(g_shared_memory).MoveFrom(); // This specific code is required for a successful initialization, rather than 0 @@ -314,6 +352,22 @@ static void RegisterInterruptRelayQueue(Service::Interface* self) { } /** + * GSP_GPU::UnregisterInterruptRelayQueue service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void UnregisterInterruptRelayQueue(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + g_shared_memory = nullptr; + g_interrupt_event = nullptr; + + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_GSP, "called"); +} + +/** * Signals that the specified interrupt type has occurred to userland code * @param interrupt_id ID of interrupt that is being signalled * @todo This should probably take a thread_id parameter and only signal this thread? @@ -591,11 +645,11 @@ const Interface::FunctionInfo FunctionTable[] = { {0x000D0140, nullptr, "SetDisplayTransfer"}, {0x000E0180, nullptr, "SetTextureCopy"}, {0x000F0200, nullptr, "SetMemoryFill"}, - {0x00100040, nullptr, "SetAxiConfigQoSMode"}, + {0x00100040, SetAxiConfigQoSMode, "SetAxiConfigQoSMode"}, {0x00110040, nullptr, "SetPerfLogMode"}, {0x00120000, nullptr, "GetPerfLog"}, {0x00130042, RegisterInterruptRelayQueue, "RegisterInterruptRelayQueue"}, - {0x00140000, nullptr, "UnregisterInterruptRelayQueue"}, + {0x00140000, UnregisterInterruptRelayQueue, "UnregisterInterruptRelayQueue"}, {0x00150002, nullptr, "TryAcquireRight"}, {0x00160042, nullptr, "AcquireRight"}, {0x00170000, nullptr, "ReleaseRight"}, @@ -616,10 +670,7 @@ Interface::Interface() { Register(FunctionTable); g_interrupt_event = nullptr; - - using Kernel::MemoryPermission; - g_shared_memory = Kernel::SharedMemory::Create(0x1000, MemoryPermission::ReadWrite, - MemoryPermission::ReadWrite, "GSPSharedMem"); + g_shared_memory = nullptr; g_thread_id = 0; } diff --git a/src/core/hle/service/gsp_gpu.h b/src/core/hle/service/gsp_gpu.h index 0e2f7a21e..55a993bb8 100644 --- a/src/core/hle/service/gsp_gpu.h +++ b/src/core/hle/service/gsp_gpu.h @@ -194,7 +194,7 @@ public: */ void SignalInterrupt(InterruptId interrupt_id); -void SetBufferSwap(u32 screen_id, const FrameBufferInfo& info); +ResultCode SetBufferSwap(u32 screen_id, const FrameBufferInfo& info); /** * Retrieves the framebuffer info stored in the GSP shared memory for the diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index cb4fd38e2..1053d0f40 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -33,6 +33,11 @@ static Kernel::SharedPtr<Kernel::Event> event_debug_pad; static u32 next_pad_index; static u32 next_touch_index; +static u32 next_accelerometer_index; +static u32 next_gyroscope_index; + +static int enable_accelerometer_count = 0; // positive means enabled +static int enable_gyroscope_count = 0; // positive means enabled const std::array<Service::HID::PadState, Settings::NativeInput::NUM_INPUTS> pad_mapping = {{ Service::HID::PAD_A, Service::HID::PAD_B, Service::HID::PAD_X, Service::HID::PAD_Y, @@ -78,17 +83,17 @@ void Update() { PadState changed = { { (state.hex ^ old_state.hex) } }; // Get the current Pad entry - PadDataEntry* pad_entry = &mem->pad.entries[mem->pad.index]; + PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index]; // Update entry properties - pad_entry->current_state.hex = state.hex; - pad_entry->delta_additions.hex = changed.hex & state.hex; - pad_entry->delta_removals.hex = changed.hex & old_state.hex;; + pad_entry.current_state.hex = state.hex; + pad_entry.delta_additions.hex = changed.hex & state.hex; + pad_entry.delta_removals.hex = changed.hex & old_state.hex;; // Set circle Pad - pad_entry->circle_pad_x = state.circle_left ? -MAX_CIRCLEPAD_POS : + pad_entry.circle_pad_x = state.circle_left ? -MAX_CIRCLEPAD_POS : state.circle_right ? MAX_CIRCLEPAD_POS : 0x0; - pad_entry->circle_pad_y = state.circle_down ? -MAX_CIRCLEPAD_POS : + pad_entry.circle_pad_y = state.circle_down ? -MAX_CIRCLEPAD_POS : state.circle_up ? MAX_CIRCLEPAD_POS : 0x0; // If we just updated index 0, provide a new timestamp @@ -101,11 +106,11 @@ void Update() { next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); // Get the current touch entry - TouchDataEntry* touch_entry = &mem->touch.entries[mem->touch.index]; + TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; bool pressed = false; - std::tie(touch_entry->x, touch_entry->y, pressed) = VideoCore::g_emu_window->GetTouchState(); - touch_entry->valid.Assign(pressed ? 1 : 0); + std::tie(touch_entry.x, touch_entry.y, pressed) = VideoCore::g_emu_window->GetTouchState(); + touch_entry.valid.Assign(pressed ? 1 : 0); // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which // supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being @@ -120,6 +125,58 @@ void Update() { // Signal both handles when there's an update to Pad or touch event_pad_or_touch_1->Signal(); event_pad_or_touch_2->Signal(); + + // Update accelerometer + if (enable_accelerometer_count > 0) { + mem->accelerometer.index = next_accelerometer_index; + next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size(); + + AccelerometerDataEntry& accelerometer_entry = mem->accelerometer.entries[mem->accelerometer.index]; + std::tie(accelerometer_entry.x, accelerometer_entry.y, accelerometer_entry.z) + = VideoCore::g_emu_window->GetAccelerometerState(); + + // Make up "raw" entry + // TODO(wwylele): + // From hardware testing, the raw_entry values are approximately, + // but not exactly, as twice as corresponding entries (or with a minus sign). + // It may caused by system calibration to the accelerometer. + // Figure out how it works, or, if no game reads raw_entry, + // the following three lines can be removed and leave raw_entry unimplemented. + mem->accelerometer.raw_entry.x = -2 * accelerometer_entry.x; + mem->accelerometer.raw_entry.z = 2 * accelerometer_entry.y; + mem->accelerometer.raw_entry.y = -2 * accelerometer_entry.z; + + // If we just updated index 0, provide a new timestamp + if (mem->accelerometer.index == 0) { + mem->accelerometer.index_reset_ticks_previous = mem->accelerometer.index_reset_ticks; + mem->accelerometer.index_reset_ticks = (s64)CoreTiming::GetTicks(); + } + + event_accelerometer->Signal(); + } + + // Update gyroscope + if (enable_gyroscope_count > 0) { + mem->gyroscope.index = next_gyroscope_index; + next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size(); + + GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index]; + std::tie(gyroscope_entry.x, gyroscope_entry.y, gyroscope_entry.z) + = VideoCore::g_emu_window->GetGyroscopeState(); + + // Make up "raw" entry + mem->gyroscope.raw_entry.x = gyroscope_entry.x; + mem->gyroscope.raw_entry.z = -gyroscope_entry.y; + mem->gyroscope.raw_entry.y = gyroscope_entry.z; + + // If we just updated index 0, provide a new timestamp + if (mem->gyroscope.index == 0) { + mem->gyroscope.index_reset_ticks_previous = mem->gyroscope.index_reset_ticks; + mem->gyroscope.index_reset_ticks = (s64)CoreTiming::GetTicks(); + } + + event_gyroscope->Signal(); + } } void GetIPCHandles(Service::Interface* self) { @@ -139,40 +196,69 @@ void GetIPCHandles(Service::Interface* self) { void EnableAccelerometer(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + ++enable_accelerometer_count; event_accelerometer->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_HID, "(STUBBED) called"); + LOG_DEBUG(Service_HID, "called"); } void DisableAccelerometer(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + --enable_accelerometer_count; event_accelerometer->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_HID, "(STUBBED) called"); + LOG_DEBUG(Service_HID, "called"); } void EnableGyroscopeLow(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + ++enable_gyroscope_count; event_gyroscope->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_HID, "(STUBBED) called"); + LOG_DEBUG(Service_HID, "called"); } void DisableGyroscopeLow(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + --enable_gyroscope_count; event_gyroscope->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; + LOG_DEBUG(Service_HID, "called"); +} + +void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + + f32 coef = VideoCore::g_emu_window->GetGyroscopeRawToDpsCoefficient(); + memcpy(&cmd_buff[2], &coef, 4); +} + +void GetGyroscopeLowCalibrateParam(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + + const s16 param_unit = 6700; // an approximate value taken from hw + GyroscopeCalibrateParam param = { + { 0, param_unit, -param_unit }, + { 0, param_unit, -param_unit }, + { 0, param_unit, -param_unit }, + }; + memcpy(&cmd_buff[2], ¶m, sizeof(param)); + LOG_WARNING(Service_HID, "(STUBBED) called"); } diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 517f4f2ae..170d19ea8 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -78,6 +78,24 @@ struct TouchDataEntry { }; /** + * Structure of a single entry of accelerometer state history within HID shared memory + */ +struct AccelerometerDataEntry { + s16 x; + s16 y; + s16 z; +}; + +/** + * Structure of a single entry of gyroscope state history within HID shared memory + */ +struct GyroscopeDataEntry { + s16 x; + s16 y; + s16 z; +}; + +/** * Structure of data stored in HID shared memory */ struct SharedMem { @@ -112,6 +130,46 @@ struct SharedMem { std::array<TouchDataEntry, 8> entries; ///< Last 8 touch entries, in pixel coordinates } touch; + + /// Accelerometer data + struct { + s64 index_reset_ticks; ///< CPU tick count for when HID module updated entry index 0 + s64 index_reset_ticks_previous; ///< Previous `index_reset_ticks` + u32 index; ///< Index of the last updated accelerometer entry + + INSERT_PADDING_WORDS(0x1); + + AccelerometerDataEntry raw_entry; + INSERT_PADDING_BYTES(2); + + std::array<AccelerometerDataEntry, 8> entries; + } accelerometer; + + /// Gyroscope data + struct { + s64 index_reset_ticks; ///< CPU tick count for when HID module updated entry index 0 + s64 index_reset_ticks_previous; ///< Previous `index_reset_ticks` + u32 index; ///< Index of the last updated accelerometer entry + + INSERT_PADDING_WORDS(0x1); + + GyroscopeDataEntry raw_entry; + INSERT_PADDING_BYTES(2); + + std::array<GyroscopeDataEntry, 32> entries; + } gyroscope; +}; + +/** + * Structure of calibrate params that GetGyroscopeLowCalibrateParam returns + */ +struct GyroscopeCalibrateParam { + struct { + // TODO (wwylele): figure out the exact meaning of these params + s16 zero_point; + s16 positive_unit_point; + s16 negative_unit_point; + } x, y, z; }; // TODO: MSVC does not support using offsetof() on non-static data members even though this @@ -222,6 +280,26 @@ void DisableGyroscopeLow(Interface* self); */ void GetSoundVolume(Interface* self); +/** + * HID::GetGyroscopeLowRawToDpsCoefficient service function + * Inputs: + * None + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : float output value + */ +void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self); + +/** + * HID::GetGyroscopeLowCalibrateParam service function + * Inputs: + * None + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2~6 (18 bytes) : struct GyroscopeCalibrateParam + */ +void GetGyroscopeLowCalibrateParam(Service::Interface* self); + /// Checks for user input updates void Update(); diff --git a/src/core/hle/service/hid/hid_spvr.cpp b/src/core/hle/service/hid/hid_spvr.cpp index c50f597eb..046e65b11 100644 --- a/src/core/hle/service/hid/hid_spvr.cpp +++ b/src/core/hle/service/hid/hid_spvr.cpp @@ -9,16 +9,16 @@ namespace Service { namespace HID { const Interface::FunctionInfo FunctionTable[] = { - {0x000A0000, GetIPCHandles, "GetIPCHandles"}, - {0x000B0000, nullptr, "StartAnalogStickCalibration"}, - {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, - {0x00110000, EnableAccelerometer, "EnableAccelerometer"}, - {0x00120000, DisableAccelerometer, "DisableAccelerometer"}, - {0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"}, - {0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"}, - {0x00150000, nullptr, "GetGyroscopeLowRawToDpsCoefficient"}, - {0x00160000, nullptr, "GetGyroscopeLowCalibrateParam"}, - {0x00170000, GetSoundVolume, "GetSoundVolume"}, + {0x000A0000, GetIPCHandles, "GetIPCHandles"}, + {0x000B0000, nullptr, "StartAnalogStickCalibration"}, + {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, + {0x00110000, EnableAccelerometer, "EnableAccelerometer"}, + {0x00120000, DisableAccelerometer, "DisableAccelerometer"}, + {0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"}, + {0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"}, + {0x00150000, GetGyroscopeLowRawToDpsCoefficient, "GetGyroscopeLowRawToDpsCoefficient"}, + {0x00160000, GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"}, + {0x00170000, GetSoundVolume, "GetSoundVolume"}, }; HID_SPVR_Interface::HID_SPVR_Interface() { diff --git a/src/core/hle/service/hid/hid_user.cpp b/src/core/hle/service/hid/hid_user.cpp index bbdde2abb..bb157b83d 100644 --- a/src/core/hle/service/hid/hid_user.cpp +++ b/src/core/hle/service/hid/hid_user.cpp @@ -9,16 +9,16 @@ namespace Service { namespace HID { const Interface::FunctionInfo FunctionTable[] = { - {0x000A0000, GetIPCHandles, "GetIPCHandles"}, - {0x000B0000, nullptr, "StartAnalogStickCalibration"}, - {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, - {0x00110000, EnableAccelerometer, "EnableAccelerometer"}, - {0x00120000, DisableAccelerometer, "DisableAccelerometer"}, - {0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"}, - {0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"}, - {0x00150000, nullptr, "GetGyroscopeLowRawToDpsCoefficient"}, - {0x00160000, nullptr, "GetGyroscopeLowCalibrateParam"}, - {0x00170000, GetSoundVolume, "GetSoundVolume"}, + {0x000A0000, GetIPCHandles, "GetIPCHandles"}, + {0x000B0000, nullptr, "StartAnalogStickCalibration"}, + {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, + {0x00110000, EnableAccelerometer, "EnableAccelerometer"}, + {0x00120000, DisableAccelerometer, "DisableAccelerometer"}, + {0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"}, + {0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"}, + {0x00150000, GetGyroscopeLowRawToDpsCoefficient, "GetGyroscopeLowRawToDpsCoefficient"}, + {0x00160000, GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"}, + {0x00170000, GetSoundVolume, "GetSoundVolume"}, }; HID_U_Interface::HID_U_Interface() { diff --git a/src/video_core/renderer_opengl/pica_to_gl.h b/src/video_core/renderer_opengl/pica_to_gl.h index 3d6c4e9e5..fd3617d77 100644 --- a/src/video_core/renderer_opengl/pica_to_gl.h +++ b/src/video_core/renderer_opengl/pica_to_gl.h @@ -22,7 +22,7 @@ inline GLenum TextureFilterMode(Pica::Regs::TextureConfig::TextureFilter mode) { }; // Range check table for input - if (mode >= ARRAY_SIZE(filter_mode_table)) { + if (static_cast<size_t>(mode) >= ARRAY_SIZE(filter_mode_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown texture filtering mode %d", mode); UNREACHABLE(); @@ -51,7 +51,7 @@ inline GLenum WrapMode(Pica::Regs::TextureConfig::WrapMode mode) { }; // Range check table for input - if (mode >= ARRAY_SIZE(wrap_mode_table)) { + if (static_cast<size_t>(mode) >= ARRAY_SIZE(wrap_mode_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown texture wrap mode %d", mode); UNREACHABLE(); @@ -91,7 +91,7 @@ inline GLenum BlendFunc(Pica::Regs::BlendFactor factor) { }; // Range check table for input - if ((unsigned)factor >= ARRAY_SIZE(blend_func_table)) { + if (static_cast<size_t>(factor) >= ARRAY_SIZE(blend_func_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown blend factor %d", factor); UNREACHABLE(); @@ -122,7 +122,7 @@ inline GLenum LogicOp(Pica::Regs::LogicOp op) { }; // Range check table for input - if ((unsigned)op >= ARRAY_SIZE(logic_op_table)) { + if (static_cast<size_t>(op) >= ARRAY_SIZE(logic_op_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown logic op %d", op); UNREACHABLE(); @@ -145,7 +145,7 @@ inline GLenum CompareFunc(Pica::Regs::CompareFunc func) { }; // Range check table for input - if ((unsigned)func >= ARRAY_SIZE(compare_func_table)) { + if (static_cast<size_t>(func) >= ARRAY_SIZE(compare_func_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown compare function %d", func); UNREACHABLE(); @@ -168,7 +168,7 @@ inline GLenum StencilOp(Pica::Regs::StencilAction action) { }; // Range check table for input - if ((unsigned)action >= ARRAY_SIZE(stencil_op_table)) { + if (static_cast<size_t>(action) >= ARRAY_SIZE(stencil_op_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown stencil op %d", action); UNREACHABLE(); |