diff options
Diffstat (limited to 'src')
164 files changed, 5657 insertions, 6890 deletions
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index ba6acf28e..84f9c03a7 100644 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp @@ -56,20 +56,8 @@ void AddAddressSpace(Kernel::VMManager& address_space) { } void SelectSink(std::string sink_id) { - auto iter = - std::find_if(g_sink_details.begin(), g_sink_details.end(), - [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); - - if (sink_id == "auto" || iter == g_sink_details.end()) { - if (sink_id != "auto") { - LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id %s", sink_id.c_str()); - } - // Auto-select. - // g_sink_details is ordered in terms of desirability, with the best choice at the front. - iter = g_sink_details.begin(); - } - - DSP::HLE::SetSink(iter->factory()); + const SinkDetails& sink_details = GetSinkDetails(sink_id); + DSP::HLE::SetSink(sink_details.factory()); } void EnableStretching(bool enable) { diff --git a/src/audio_core/hle/source.cpp b/src/audio_core/hle/source.cpp index 2bbf7146e..92484c526 100644 --- a/src/audio_core/hle/source.cpp +++ b/src/audio_core/hle/source.cpp @@ -158,6 +158,14 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, static_cast<size_t>(state.mono_or_stereo)); } + u32_dsp play_position = {}; + if (config.play_position_dirty && config.play_position != 0) { + config.play_position_dirty.Assign(0); + play_position = config.play_position; + // play_position applies only to the embedded buffer, and defaults to 0 w/o a dirty bit + // This will be the starting sample for the first time the buffer is played. + } + if (config.embedded_buffer_dirty) { config.embedded_buffer_dirty.Assign(0); state.input_queue.emplace(Buffer{ @@ -171,9 +179,18 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, state.mono_or_stereo, state.format, false, + play_position, + false, }); - LOG_TRACE(Audio_DSP, "enqueuing embedded addr=0x%08x len=%u id=%hu", - config.physical_address, config.length, config.buffer_id); + LOG_TRACE(Audio_DSP, "enqueuing embedded addr=0x%08x len=%u id=%hu start=%u", + config.physical_address, config.length, config.buffer_id, + static_cast<u32>(config.play_position)); + } + + if (config.loop_related_dirty && config.loop_related != 0) { + config.loop_related_dirty.Assign(0); + LOG_WARNING(Audio_DSP, "Unhandled complex loop with loop_related=0x%08x", + static_cast<u32>(config.loop_related)); } if (config.buffer_queue_dirty) { @@ -192,6 +209,8 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, state.mono_or_stereo, state.format, true, + {}, // 0 in u32_dsp + false, }); LOG_TRACE(Audio_DSP, "enqueuing queued %zu addr=0x%08x len=%u id=%hu", i, b.physical_address, b.length, b.buffer_id); @@ -247,18 +266,18 @@ bool Source::DequeueBuffer() { if (state.input_queue.empty()) return false; - const Buffer buf = state.input_queue.top(); - state.input_queue.pop(); + Buffer buf = state.input_queue.top(); + + // if we're in a loop, the current sound keeps playing afterwards, so leave the queue alone + if (!buf.is_looping) { + state.input_queue.pop(); + } if (buf.adpcm_dirty) { state.adpcm_state.yn1 = buf.adpcm_yn[0]; state.adpcm_state.yn2 = buf.adpcm_yn[1]; } - if (buf.is_looping) { - LOG_ERROR(Audio_DSP, "Looped buffers are unimplemented at the moment"); - } - const u8* const memory = Memory::GetPhysicalPointer(buf.physical_address); if (memory) { const unsigned num_channels = buf.mono_or_stereo == MonoOrStereo::Stereo ? 2 : 1; @@ -305,10 +324,13 @@ bool Source::DequeueBuffer() { break; } - state.current_sample_number = 0; - state.next_sample_number = 0; + // the first playthrough starts at play_position, loops start at the beginning of the buffer + state.current_sample_number = (!buf.has_played) ? buf.play_position : 0; + state.next_sample_number = state.current_sample_number; state.current_buffer_id = buf.buffer_id; - state.buffer_update = buf.from_queue; + state.buffer_update = buf.from_queue && !buf.has_played; + + buf.has_played = true; LOG_TRACE(Audio_DSP, "source_id=%zu buffer_id=%hu from_queue=%s current_buffer.size()=%zu", source_id, buf.buffer_id, buf.from_queue ? "true" : "false", diff --git a/src/audio_core/hle/source.h b/src/audio_core/hle/source.h index 3d725f2a3..ccb7f064f 100644 --- a/src/audio_core/hle/source.h +++ b/src/audio_core/hle/source.h @@ -76,6 +76,8 @@ private: Format format; bool from_queue; + u32_dsp play_position; // = 0; + bool has_played; // = false; }; struct BufferOrder { diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h index e7668438c..c732926a2 100644 --- a/src/audio_core/null_sink.h +++ b/src/audio_core/null_sink.h @@ -23,6 +23,12 @@ public: size_t SamplesInQueue() const override { return 0; } + + void SetDevice(int device_id) override {} + + std::vector<std::string> GetDeviceList() const override { + return {}; + } }; } // namespace AudioCore diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp index 4b66cd826..933c5f16d 100644 --- a/src/audio_core/sdl2_sink.cpp +++ b/src/audio_core/sdl2_sink.cpp @@ -4,12 +4,12 @@ #include <list> #include <numeric> -#include <vector> #include <SDL.h> #include "audio_core/audio_core.h" #include "audio_core/sdl2_sink.h" #include "common/assert.h" #include "common/logging/log.h" +#include "core/settings.h" namespace AudioCore { @@ -42,10 +42,24 @@ SDL2Sink::SDL2Sink() : impl(std::make_unique<Impl>()) { SDL_AudioSpec obtained_audiospec; SDL_zero(obtained_audiospec); - impl->audio_device_id = - SDL_OpenAudioDevice(nullptr, false, &desired_audiospec, &obtained_audiospec, 0); + int device_count = SDL_GetNumAudioDevices(0); + device_list.clear(); + for (int i = 0; i < device_count; ++i) { + device_list.push_back(SDL_GetAudioDeviceName(i, 0)); + } + + const char* device = nullptr; + + if (device_count >= 1 && Settings::values.audio_device_id != "auto" && + !Settings::values.audio_device_id.empty()) { + device = Settings::values.audio_device_id.c_str(); + } + + impl->audio_device_id = SDL_OpenAudioDevice(device, false, &desired_audiospec, + &obtained_audiospec, SDL_AUDIO_ALLOW_ANY_CHANGE); if (impl->audio_device_id <= 0) { - LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed with: %s", SDL_GetError()); + LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed with code %d for device \"%s\"", + impl->audio_device_id, Settings::values.audio_device_id.c_str()); return; } @@ -69,6 +83,10 @@ unsigned int SDL2Sink::GetNativeSampleRate() const { return impl->sample_rate; } +std::vector<std::string> SDL2Sink::GetDeviceList() const { + return device_list; +} + void SDL2Sink::EnqueueSamples(const s16* samples, size_t sample_count) { if (impl->audio_device_id <= 0) return; @@ -96,6 +114,10 @@ size_t SDL2Sink::SamplesInQueue() const { return total_size; } +void SDL2Sink::SetDevice(int device_id) { + this->device_id = device_id; +} + void SDL2Sink::Impl::Callback(void* impl_, u8* buffer, int buffer_size_in_bytes) { Impl* impl = reinterpret_cast<Impl*>(impl_); diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h index ccd0f7c7e..bcc725369 100644 --- a/src/audio_core/sdl2_sink.h +++ b/src/audio_core/sdl2_sink.h @@ -21,9 +21,14 @@ public: size_t SamplesInQueue() const override; + std::vector<std::string> GetDeviceList() const override; + void SetDevice(int device_id) override; + private: struct Impl; std::unique_ptr<Impl> impl; + int device_id; + std::vector<std::string> device_list; }; } // namespace AudioCore diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h index 08f3bab5b..558c8c0fe 100644 --- a/src/audio_core/sink.h +++ b/src/audio_core/sink.h @@ -31,6 +31,15 @@ public: /// Samples enqueued that have not been played yet. virtual std::size_t SamplesInQueue() const = 0; + + /** + * Sets the desired output device. + * @paran device_id Id of the desired device. + */ + virtual void SetDevice(int device_id) = 0; + + /// Returns the list of available devices. + virtual std::vector<std::string> GetDeviceList() const = 0; }; } // namespace diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp index 95ccc9e9d..6972395af 100644 --- a/src/audio_core/sink_details.cpp +++ b/src/audio_core/sink_details.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <memory> #include <vector> #include "audio_core/null_sink.h" @@ -9,6 +10,7 @@ #ifdef HAVE_SDL2 #include "audio_core/sdl2_sink.h" #endif +#include "common/logging/log.h" namespace AudioCore { @@ -20,4 +22,21 @@ const std::vector<SinkDetails> g_sink_details = { {"null", []() { return std::make_unique<NullSink>(); }}, }; +const SinkDetails& GetSinkDetails(std::string sink_id) { + auto iter = + std::find_if(g_sink_details.begin(), g_sink_details.end(), + [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); + + if (sink_id == "auto" || iter == g_sink_details.end()) { + if (sink_id != "auto") { + LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id %s", sink_id.c_str()); + } + // Auto-select. + // g_sink_details is ordered in terms of desirability, with the best choice at the front. + iter = g_sink_details.begin(); + } + + return *iter; +} + } // namespace AudioCore diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h index 4b30cf835..9d3735171 100644 --- a/src/audio_core/sink_details.h +++ b/src/audio_core/sink_details.h @@ -24,4 +24,6 @@ struct SinkDetails { extern const std::vector<SinkDetails> g_sink_details; +const SinkDetails& GetSinkDetails(std::string sink_id); + } // namespace AudioCore diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index 99c096ac7..76f5caeb1 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -141,6 +141,26 @@ int main(int argc, char** argv) { case Core::System::ResultStatus::ErrorLoader: LOG_CRITICAL(Frontend, "Failed to load ROM!"); return -1; + case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: + LOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before " + "being used with Citra. \n\n For more information on dumping and " + "decrypting games, please refer to: " + "https://citra-emu.org/wiki/Dumping-Game-Cartridges"); + return -1; + case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: + LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported."); + return -1; + case Core::System::ResultStatus::ErrorNotInitialized: + LOG_CRITICAL(Frontend, "CPUCore not initialized"); + return -1; + case Core::System::ResultStatus::ErrorSystemMode: + LOG_CRITICAL(Frontend, "Failed to determine system mode!"); + return -1; + case Core::System::ResultStatus::ErrorVideoCore: + LOG_CRITICAL(Frontend, "VideoCore not initialized"); + return -1; + case Core::System::ResultStatus::Success: + break; // Expected case } while (emu_window->IsOpen()) { diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 29462c982..fac1c9a0e 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -63,8 +63,8 @@ void Config::ReadValues() { // Renderer Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", true); Settings::values.use_shader_jit = sdl2_config->GetBoolean("Renderer", "use_shader_jit", true); - Settings::values.use_scaled_resolution = - sdl2_config->GetBoolean("Renderer", "use_scaled_resolution", false); + Settings::values.resolution_factor = + (float)sdl2_config->GetReal("Renderer", "resolution_factor", 1.0); Settings::values.use_vsync = sdl2_config->GetBoolean("Renderer", "use_vsync", false); Settings::values.toggle_framelimit = sdl2_config->GetBoolean("Renderer", "toggle_framelimit", true); @@ -82,6 +82,7 @@ void Config::ReadValues() { Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); Settings::values.enable_audio_stretching = sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true); + Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto"); // Data Storage Settings::values.use_virtual_sd = @@ -89,7 +90,23 @@ void Config::ReadValues() { // System Settings::values.is_new_3ds = sdl2_config->GetBoolean("System", "is_new_3ds", false); - Settings::values.region_value = sdl2_config->GetInteger("System", "region_value", 1); + Settings::values.region_value = + sdl2_config->GetInteger("System", "region_value", Settings::REGION_VALUE_AUTO_SELECT); + + // Camera + using namespace Service::CAM; + Settings::values.camera_name[OuterRightCamera] = + sdl2_config->Get("Camera", "camera_outer_right_name", "blank"); + Settings::values.camera_config[OuterRightCamera] = + sdl2_config->Get("Camera", "camera_outer_right_config", ""); + Settings::values.camera_name[InnerCamera] = + sdl2_config->Get("Camera", "camera_inner_name", "blank"); + Settings::values.camera_config[InnerCamera] = + sdl2_config->Get("Camera", "camera_inner_config", ""); + Settings::values.camera_name[OuterLeftCamera] = + sdl2_config->Get("Camera", "camera_outer_left_name", "blank"); + Settings::values.camera_config[OuterLeftCamera] = + sdl2_config->Get("Camera", "camera_outer_left_config", ""); // Miscellaneous Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Info"); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 001b18ac2..435ba6f00 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -51,14 +51,21 @@ use_hw_renderer = # 0: Interpreter (slow), 1 (default): JIT (fast) use_shader_jit = -# Whether to use native 3DS screen resolution or to scale rendering resolution to the displayed screen size. -# 0 (default): Native, 1: Scaled -use_scaled_resolution = +# Resolution scale factor +# 0: Auto (scales resolution to window size), 1: Native 3DS screen resolution, Otherwise a scale +# factor for the 3DS resolution +resolution_factor = # Whether to enable V-Sync (caps the framerate at 60FPS) or not. # 0 (default): Off, 1: On use_vsync = +# The clear color for the renderer. What shows up on the sides of the bottom screen. +# Must be in range of 0.0-1.0. Defaults to 1.0 for all. +bg_red = +bg_blue = +bg_green = + [Layout] # Layout for the screen inside the render window. # 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen @@ -73,12 +80,6 @@ toggle_framelimit = # 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent swap_screen = -# The clear color for the renderer. What shows up on the sides of the bottom screen. -# Must be in range of 0.0-1.0. Defaults to 1.0 for all. -bg_red = -bg_blue = -bg_green = - [Audio] # Which audio output engine to use. # auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available) @@ -90,6 +91,10 @@ output_engine = # 0: No, 1 (default): Yes enable_audio_stretching = +# Which audio device to use. +# auto (default): Auto-select +output_device = + [Data Storage] # Whether to create a virtual SD card. # 1 (default): Yes, 0: No @@ -101,9 +106,25 @@ use_virtual_sd = is_new_3ds = # The system region that Citra will use during emulation -# 0: Japan, 1: USA (default), 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan +# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan region_value = +[Camera] +# Which camera engine to use for the right outer camera +# blank (default): a dummy camera that always returns black image +camera_outer_right_name = + +# A config string for the right outer camera. Its meaning is defined by the camera engine +camera_outer_right_config = + +# ... for the left outer camera +camera_outer_left_name = +camera_outer_left_config = + +# ... for the inner camera +camera_inner_name = +camera_inner_config = + [Miscellaneous] # A filter which removes logs below a certain logging level. # Examples: *:Debug Kernel.SVC:Trace Service.*:Critical diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index b0d82b670..81a3abe3f 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -19,16 +19,22 @@ void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); + motion_emu->Tilt(x, y); } void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { - if (button != SDL_BUTTON_LEFT) - return; - - if (state == SDL_PRESSED) { - TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); - } else { - TouchReleased(); + if (button == SDL_BUTTON_LEFT) { + if (state == SDL_PRESSED) { + TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); + } else { + TouchReleased(); + } + } else if (button == SDL_BUTTON_RIGHT) { + if (state == SDL_PRESSED) { + motion_emu->BeginTilt(x, y); + } else { + motion_emu->EndTilt(); + } } } @@ -54,6 +60,7 @@ EmuWindow_SDL2::EmuWindow_SDL2() { keyboard_id = KeyMap::NewDeviceId(); ReloadSetKeymaps(); + motion_emu = std::make_unique<Motion::MotionEmu>(*this); SDL_SetMainReady(); @@ -109,6 +116,7 @@ EmuWindow_SDL2::EmuWindow_SDL2() { EmuWindow_SDL2::~EmuWindow_SDL2() { SDL_GL_DeleteContext(gl_context); SDL_Quit(); + motion_emu = nullptr; } void EmuWindow_SDL2::SwapBuffers() { diff --git a/src/citra/emu_window/emu_window_sdl2.h b/src/citra/emu_window/emu_window_sdl2.h index c8cd919c6..b1cbf16d7 100644 --- a/src/citra/emu_window/emu_window_sdl2.h +++ b/src/citra/emu_window/emu_window_sdl2.h @@ -4,8 +4,10 @@ #pragma once +#include <memory> #include <utility> #include "core/frontend/emu_window.h" +#include "core/frontend/motion_emu.h" struct SDL_Window; @@ -61,4 +63,7 @@ private: /// Device id of keyboard for use with KeyMap int keyboard_id; + + /// Motion sensors emulation + std::unique_ptr<Motion::MotionEmu> motion_emu; }; diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 93f1c339d..d4460bf01 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -14,7 +14,6 @@ set(SRCS debugger/graphics/graphics_tracing.cpp debugger/graphics/graphics_vertex_shader.cpp debugger/profiler.cpp - debugger/ramview.cpp debugger/registers.cpp debugger/wait_tree.cpp util/spinbox.cpp @@ -48,7 +47,6 @@ set(HEADERS debugger/graphics/graphics_tracing.h debugger/graphics/graphics_vertex_shader.h debugger/profiler.h - debugger/ramview.h debugger/registers.h debugger/wait_tree.h util/spinbox.h @@ -100,7 +98,7 @@ if (APPLE) else() add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS}) endif() -target_link_libraries(citra-qt core video_core audio_core common qhexedit) +target_link_libraries(citra-qt core video_core audio_core common) target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS}) target_link_libraries(citra-qt ${PLATFORM_LIBRARIES} Threads::Threads) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 57fde6caa..948db384d 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -99,7 +99,7 @@ private: }; GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) - : QWidget(parent), keyboard_id(0), emu_thread(emu_thread), child(nullptr) { + : QWidget(parent), child(nullptr), keyboard_id(0), emu_thread(emu_thread) { std::string window_title = Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); @@ -191,6 +191,7 @@ qreal GRenderWindow::windowPixelRatio() { } void GRenderWindow::closeEvent(QCloseEvent* event) { + motion_emu = nullptr; emit Closed(); QWidget::closeEvent(event); } @@ -204,11 +205,13 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { } void GRenderWindow::mousePressEvent(QMouseEvent* event) { + auto pos = event->pos(); if (event->button() == Qt::LeftButton) { - auto pos = event->pos(); qreal pixelRatio = windowPixelRatio(); this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio), static_cast<unsigned>(pos.y() * pixelRatio)); + } else if (event->button() == Qt::RightButton) { + motion_emu->BeginTilt(pos.x(), pos.y()); } } @@ -217,11 +220,14 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { qreal pixelRatio = windowPixelRatio(); this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u), std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u)); + motion_emu->Tilt(pos.x(), pos.y()); } void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) this->TouchReleased(); + else if (event->button() == Qt::RightButton) + motion_emu->EndTilt(); } void GRenderWindow::ReloadSetKeymaps() { @@ -279,11 +285,13 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest( } void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { + motion_emu = std::make_unique<Motion::MotionEmu>(*this); this->emu_thread = emu_thread; child->DisablePainting(); } void GRenderWindow::OnEmulationStopping() { + motion_emu = nullptr; emu_thread = nullptr; child->EnablePainting(); } diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 43015390b..7dac1c480 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -11,6 +11,7 @@ #include <QThread> #include "common/thread.h" #include "core/frontend/emu_window.h" +#include "core/frontend/motion_emu.h" class QKeyEvent; class QScreen; @@ -156,6 +157,9 @@ private: EmuThread* emu_thread; + /// Motion sensors emulation + std::unique_ptr<Motion::MotionEmu> motion_emu; + protected: void showEvent(QShowEvent* event) override; }; diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index 06a4e9d25..b65f57fdc 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -44,8 +44,7 @@ void Config::ReadValues() { qt_config->beginGroup("Renderer"); Settings::values.use_hw_renderer = qt_config->value("use_hw_renderer", true).toBool(); Settings::values.use_shader_jit = qt_config->value("use_shader_jit", true).toBool(); - Settings::values.use_scaled_resolution = - qt_config->value("use_scaled_resolution", false).toBool(); + Settings::values.resolution_factor = qt_config->value("resolution_factor", 1.0).toFloat(); Settings::values.use_vsync = qt_config->value("use_vsync", false).toBool(); Settings::values.toggle_framelimit = qt_config->value("toggle_framelimit", true).toBool(); @@ -64,6 +63,24 @@ void Config::ReadValues() { Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString(); Settings::values.enable_audio_stretching = qt_config->value("enable_audio_stretching", true).toBool(); + Settings::values.audio_device_id = + qt_config->value("output_device", "auto").toString().toStdString(); + qt_config->endGroup(); + + using namespace Service::CAM; + qt_config->beginGroup("Camera"); + Settings::values.camera_name[OuterRightCamera] = + qt_config->value("camera_outer_right_name", "blank").toString().toStdString(); + Settings::values.camera_config[OuterRightCamera] = + qt_config->value("camera_outer_right_config", "").toString().toStdString(); + Settings::values.camera_name[InnerCamera] = + qt_config->value("camera_inner_name", "blank").toString().toStdString(); + Settings::values.camera_config[InnerCamera] = + qt_config->value("camera_inner_config", "").toString().toStdString(); + Settings::values.camera_name[OuterLeftCamera] = + qt_config->value("camera_outer_left_name", "blank").toString().toStdString(); + Settings::values.camera_config[OuterLeftCamera] = + qt_config->value("camera_outer_left_config", "").toString().toStdString(); qt_config->endGroup(); qt_config->beginGroup("Data Storage"); @@ -72,7 +89,8 @@ void Config::ReadValues() { qt_config->beginGroup("System"); Settings::values.is_new_3ds = qt_config->value("is_new_3ds", false).toBool(); - Settings::values.region_value = qt_config->value("region_value", 1).toInt(); + Settings::values.region_value = + qt_config->value("region_value", Settings::REGION_VALUE_AUTO_SELECT).toInt(); qt_config->endGroup(); qt_config->beginGroup("Miscellaneous"); @@ -151,7 +169,7 @@ void Config::SaveValues() { qt_config->beginGroup("Renderer"); qt_config->setValue("use_hw_renderer", Settings::values.use_hw_renderer); qt_config->setValue("use_shader_jit", Settings::values.use_shader_jit); - qt_config->setValue("use_scaled_resolution", Settings::values.use_scaled_resolution); + qt_config->setValue("resolution_factor", (double)Settings::values.resolution_factor); qt_config->setValue("use_vsync", Settings::values.use_vsync); qt_config->setValue("toggle_framelimit", Settings::values.toggle_framelimit); @@ -169,6 +187,23 @@ void Config::SaveValues() { qt_config->beginGroup("Audio"); qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id)); qt_config->setValue("enable_audio_stretching", Settings::values.enable_audio_stretching); + qt_config->setValue("output_device", QString::fromStdString(Settings::values.audio_device_id)); + qt_config->endGroup(); + + using namespace Service::CAM; + qt_config->beginGroup("Camera"); + qt_config->setValue("camera_outer_right_name", + QString::fromStdString(Settings::values.camera_name[OuterRightCamera])); + qt_config->setValue("camera_outer_right_config", + QString::fromStdString(Settings::values.camera_config[OuterRightCamera])); + qt_config->setValue("camera_inner_name", + QString::fromStdString(Settings::values.camera_name[InnerCamera])); + qt_config->setValue("camera_inner_config", + QString::fromStdString(Settings::values.camera_config[InnerCamera])); + qt_config->setValue("camera_outer_left_name", + QString::fromStdString(Settings::values.camera_name[OuterLeftCamera])); + qt_config->setValue("camera_outer_left_config", + QString::fromStdString(Settings::values.camera_config[OuterLeftCamera])); qt_config->endGroup(); qt_config->beginGroup("Data Storage"); diff --git a/src/citra_qt/configure_audio.cpp b/src/citra_qt/configure_audio.cpp index 3cdd4c780..3ddcf9232 100644 --- a/src/citra_qt/configure_audio.cpp +++ b/src/citra_qt/configure_audio.cpp @@ -2,6 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <memory> +#include "audio_core/audio_core.h" +#include "audio_core/sink.h" #include "audio_core/sink_details.h" #include "citra_qt/configure_audio.h" #include "core/settings.h" @@ -18,6 +21,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) } this->setConfiguration(); + connect(ui->output_sink_combo_box, SIGNAL(currentIndexChanged(int)), this, + SLOT(updateAudioDevices(int))); } ConfigureAudio::~ConfigureAudio() {} @@ -33,6 +38,19 @@ void ConfigureAudio::setConfiguration() { ui->output_sink_combo_box->setCurrentIndex(new_sink_index); ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching); + + // The device list cannot be pre-populated (nor listed) until the output sink is known. + updateAudioDevices(new_sink_index); + + int new_device_index = -1; + for (int index = 0; index < ui->audio_device_combo_box->count(); index++) { + if (ui->audio_device_combo_box->itemText(index).toStdString() == + Settings::values.audio_device_id) { + new_device_index = index; + break; + } + } + ui->audio_device_combo_box->setCurrentIndex(new_device_index); } void ConfigureAudio::applyConfiguration() { @@ -40,5 +58,20 @@ void ConfigureAudio::applyConfiguration() { ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) .toStdString(); Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked(); + Settings::values.audio_device_id = + ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) + .toStdString(); Settings::Apply(); } + +void ConfigureAudio::updateAudioDevices(int sink_index) { + ui->audio_device_combo_box->clear(); + ui->audio_device_combo_box->addItem("auto"); + + std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); + std::vector<std::string> device_list = + AudioCore::GetSinkDetails(sink_id).factory()->GetDeviceList(); + for (const auto& device : device_list) { + ui->audio_device_combo_box->addItem(device.c_str()); + } +} diff --git a/src/citra_qt/configure_audio.h b/src/citra_qt/configure_audio.h index 51df2e27b..8190e694f 100644 --- a/src/citra_qt/configure_audio.h +++ b/src/citra_qt/configure_audio.h @@ -20,6 +20,9 @@ public: void applyConfiguration(); +public slots: + void updateAudioDevices(int sink_index); + private: void setConfiguration(); diff --git a/src/citra_qt/configure_audio.ui b/src/citra_qt/configure_audio.ui index 3e2b4635f..dd870eb61 100644 --- a/src/citra_qt/configure_audio.ui +++ b/src/citra_qt/configure_audio.ui @@ -35,6 +35,21 @@ </property> </widget> </item> + <item> + <layout class="QHBoxLayout"> + <item> + <widget class="QLabel"> + <property name="text"> + <string>Audio Device:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="audio_device_combo_box"> + </widget> + </item> + </layout> + </item> </layout> </widget> </item> diff --git a/src/citra_qt/configure_general.cpp b/src/citra_qt/configure_general.cpp index 03cd8835b..ac90a6df4 100644 --- a/src/citra_qt/configure_general.cpp +++ b/src/citra_qt/configure_general.cpp @@ -23,13 +23,15 @@ void ConfigureGeneral::setConfiguration() { ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan); ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit); - ui->region_combobox->setCurrentIndex(Settings::values.region_value); + + // The first item is "auto-select" with actual value -1, so plus one here will do the trick + ui->region_combobox->setCurrentIndex(Settings::values.region_value + 1); } void ConfigureGeneral::applyConfiguration() { UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked(); UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); - Settings::values.region_value = ui->region_combobox->currentIndex(); + Settings::values.region_value = ui->region_combobox->currentIndex() - 1; Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked(); Settings::Apply(); } diff --git a/src/citra_qt/configure_general.ui b/src/citra_qt/configure_general.ui index 81688113f..0f3352a1d 100644 --- a/src/citra_qt/configure_general.ui +++ b/src/citra_qt/configure_general.ui @@ -43,26 +43,26 @@ </layout> </widget> </item> - <item> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string>Performance</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_7"> - <item> - <layout class="QVBoxLayout" name="verticalLayout_5"> - <item> - <widget class="QCheckBox" name="toggle_cpu_jit"> - <property name="text"> - <string>Enable CPU JIT</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Performance</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <widget class="QCheckBox" name="toggle_cpu_jit"> + <property name="text"> + <string>Enable CPU JIT</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> <item> <widget class="QGroupBox" name="groupBox_4"> <property name="title"> @@ -84,6 +84,11 @@ <widget class="QComboBox" name="region_combobox"> <item> <property name="text"> + <string>Auto-select</string> + </property> + </item> + <item> + <property name="text"> <string notr="true">JPN</string> </property> </item> diff --git a/src/citra_qt/configure_graphics.cpp b/src/citra_qt/configure_graphics.cpp index cea7db388..54f799b47 100644 --- a/src/citra_qt/configure_graphics.cpp +++ b/src/citra_qt/configure_graphics.cpp @@ -18,10 +18,81 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) ConfigureGraphics::~ConfigureGraphics() {} +enum class Resolution : int { + Auto, + Scale1x, + Scale2x, + Scale3x, + Scale4x, + Scale5x, + Scale6x, + Scale7x, + Scale8x, + Scale9x, + Scale10x, +}; + +float ToResolutionFactor(Resolution option) { + switch (option) { + case Resolution::Auto: + return 0.f; + case Resolution::Scale1x: + return 1.f; + case Resolution::Scale2x: + return 2.f; + case Resolution::Scale3x: + return 3.f; + case Resolution::Scale4x: + return 4.f; + case Resolution::Scale5x: + return 5.f; + case Resolution::Scale6x: + return 6.f; + case Resolution::Scale7x: + return 7.f; + case Resolution::Scale8x: + return 8.f; + case Resolution::Scale9x: + return 9.f; + case Resolution::Scale10x: + return 10.f; + } + return 0.f; +} + +Resolution FromResolutionFactor(float factor) { + if (factor == 0.f) { + return Resolution::Auto; + } else if (factor == 1.f) { + return Resolution::Scale1x; + } else if (factor == 2.f) { + return Resolution::Scale2x; + } else if (factor == 3.f) { + return Resolution::Scale3x; + } else if (factor == 4.f) { + return Resolution::Scale4x; + } else if (factor == 5.f) { + return Resolution::Scale5x; + } else if (factor == 6.f) { + return Resolution::Scale6x; + } else if (factor == 7.f) { + return Resolution::Scale7x; + } else if (factor == 8.f) { + return Resolution::Scale8x; + } else if (factor == 9.f) { + return Resolution::Scale9x; + } else if (factor == 10.f) { + return Resolution::Scale10x; + } + return Resolution::Auto; +} + void ConfigureGraphics::setConfiguration() { ui->toggle_hw_renderer->setChecked(Settings::values.use_hw_renderer); + ui->resolution_factor_combobox->setEnabled(Settings::values.use_hw_renderer); ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit); - ui->toggle_scaled_resolution->setChecked(Settings::values.use_scaled_resolution); + ui->resolution_factor_combobox->setCurrentIndex( + static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor))); ui->toggle_vsync->setChecked(Settings::values.use_vsync); ui->toggle_framelimit->setChecked(Settings::values.toggle_framelimit); ui->layout_combobox->setCurrentIndex(static_cast<int>(Settings::values.layout_option)); @@ -31,7 +102,8 @@ void ConfigureGraphics::setConfiguration() { void ConfigureGraphics::applyConfiguration() { Settings::values.use_hw_renderer = ui->toggle_hw_renderer->isChecked(); Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked(); - Settings::values.use_scaled_resolution = ui->toggle_scaled_resolution->isChecked(); + Settings::values.resolution_factor = + ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex())); Settings::values.use_vsync = ui->toggle_vsync->isChecked(); Settings::values.toggle_framelimit = ui->toggle_framelimit->isChecked(); Settings::values.layout_option = diff --git a/src/citra_qt/configure_graphics.ui b/src/citra_qt/configure_graphics.ui index 964aa0bbd..a091f4c60 100644 --- a/src/citra_qt/configure_graphics.ui +++ b/src/citra_qt/configure_graphics.ui @@ -37,13 +37,6 @@ </widget> </item> <item> - <widget class="QCheckBox" name="toggle_scaled_resolution"> - <property name="text"> - <string>Enable scaled resolution</string> - </property> - </widget> - </item> - <item> <widget class="QCheckBox" name="toggle_vsync"> <property name="text"> <string>Enable V-Sync</string> @@ -57,6 +50,76 @@ </property> </widget> </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Internal Resolution:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="resolution_factor_combobox"> + <item> + <property name="text"> + <string notr="true">Auto (Window Size)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">Native (400x240)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">2x Native (800x480)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">3x Native (1200x720)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">4x Native (1600x960)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">5x Native (2000x1200)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">6x Native (2400x1440)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">7x Native (2800x1680)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">8x Native (3200x1920)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">9x Native (3600x2160)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">10x Native (4000x2400)</string> + </property> + </item> + </widget> + </item> + </layout> + </item> </layout> </widget> </item> @@ -69,9 +132,9 @@ </property> <layout class="QHBoxLayout" name="horizontalLayout_3"> <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> + <layout class="QVBoxLayout" name="verticalLayout_4"> <item> - <layout class="QHBoxLayout" name="horizontalLayout_3"> + <layout class="QHBoxLayout" name="horizontalLayout_4"> <item> <widget class="QLabel" name="label1"> <property name="text"> @@ -128,5 +191,12 @@ </layout> </widget> <resources/> - <connections/> + <connections> + <connection> + <sender>toggle_hw_renderer</sender> + <signal>toggled(bool)</signal> + <receiver>resolution_factor_combobox</receiver> + <slot>setEnabled(bool)</slot> + </connection> + </connections> </ui> diff --git a/src/citra_qt/configure_input.cpp b/src/citra_qt/configure_input.cpp index 3e6803b8a..c29652f32 100644 --- a/src/citra_qt/configure_input.cpp +++ b/src/citra_qt/configure_input.cpp @@ -17,7 +17,6 @@ static QString getKeyName(Qt::Key key_code) { case Qt::Key_Alt: return QObject::tr("Alt"); case Qt::Key_Meta: - case -1: return ""; default: return QKeySequence(key_code).toString(); diff --git a/src/citra_qt/configure_input.ui b/src/citra_qt/configure_input.ui index a040d4df4..2760787e5 100644 --- a/src/citra_qt/configure_input.ui +++ b/src/citra_qt/configure_input.ui @@ -275,7 +275,6 @@ </layout> </item> </layout> - <zorder></zorder> </widget> </item> <item row="1" column="1"> @@ -521,7 +520,7 @@ <item row="1" column="1"> <layout class="QVBoxLayout" name="verticalLayout_28"> <item> - <widget class="QLabel" name="label_34"> + <widget class="QLabel" name="label_36"> <property name="text"> <string>Circle Mod:</string> </property> diff --git a/src/citra_qt/configure_system.ui b/src/citra_qt/configure_system.ui index 6a906b61b..cc54fa37f 100644 --- a/src/citra_qt/configure_system.ui +++ b/src/citra_qt/configure_system.ui @@ -129,6 +129,9 @@ </item> <item row="2" column="1"> <widget class="QComboBox" name="combo_language"> + <property name="toolTip"> + <string>Note: this can be overridden when region setting is auto-select</string> + </property> <item> <property name="text"> <string>Japanese (日本語)</string> diff --git a/src/citra_qt/debugger/callstack.cpp b/src/citra_qt/debugger/callstack.cpp index c1db93583..08d2e7a22 100644 --- a/src/citra_qt/debugger/callstack.cpp +++ b/src/citra_qt/debugger/callstack.cpp @@ -45,7 +45,6 @@ void CallstackWidget::OnDebugModeEntered() { if (ARM_Disasm::Decode(insn) == OP_BL) { std::string name; // ripped from disasm - u8 cond = (insn >> 28) & 0xf; u32 i_offset = insn & 0xffffff; // Sign-extend the 24-bit offset if ((i_offset >> 23) & 1) diff --git a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp index dab529e3a..ee79f0edf 100644 --- a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp @@ -20,13 +20,14 @@ #include "video_core/debug_utils/debug_utils.h" #include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/texture/texture_decode.h" namespace { -QImage LoadTexture(const u8* src, const Pica::DebugUtils::TextureInfo& info) { +QImage LoadTexture(const u8* src, const Pica::Texture::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, true); + Math::Vec4<u8> color = Pica::Texture::LookupTexture(src, x, y, info, true); decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); } } @@ -36,9 +37,10 @@ QImage LoadTexture(const u8* src, const Pica::DebugUtils::TextureInfo& info) { class TextureInfoWidget : public QWidget { public: - TextureInfoWidget(const u8* src, const Pica::DebugUtils::TextureInfo& info, + TextureInfoWidget(const u8* src, const Pica::Texture::TextureInfo& info, QWidget* parent = nullptr) : QWidget(parent) { + QLabel* image_widget = new QLabel; QPixmap image_pixmap = QPixmap::fromImage(LoadTexture(src, info)); image_pixmap = image_pixmap.scaled(200, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation); @@ -135,11 +137,6 @@ void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) { UNREACHABLE_MSG("Unknown texture command"); } - const auto texture = Pica::g_state.regs.GetTextures()[texture_index]; - const auto config = texture.config; - const auto format = texture.format; - const auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format); - // TODO: Open a surface debugger } } @@ -165,7 +162,7 @@ void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) { const auto config = texture.config; const auto format = texture.format; - const auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format); + const auto info = Pica::Texture::TextureInfo::FromPicaRegister(config, format); const u8* src = Memory::GetPhysicalPointer(config.GetPhysicalAddress()); new_info_widget = new TextureInfoWidget(src, info); } diff --git a/src/citra_qt/debugger/graphics/graphics_surface.cpp b/src/citra_qt/debugger/graphics/graphics_surface.cpp index 4efd95d3c..bd82b00d4 100644 --- a/src/citra_qt/debugger/graphics/graphics_surface.cpp +++ b/src/citra_qt/debugger/graphics/graphics_surface.cpp @@ -18,6 +18,7 @@ #include "core/memory.h" #include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/texture/texture_decode.h" #include "video_core/utils.h" SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) @@ -512,7 +513,7 @@ void GraphicsSurfaceWidget::OnUpdate() { } const auto texture = Pica::g_state.regs.GetTextures()[texture_index]; - auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(texture.config, texture.format); + auto info = Pica::Texture::TextureInfo::FromPicaRegister(texture.config, texture.format); surface_address = info.physical_address; surface_width = info.width; @@ -567,28 +568,27 @@ void GraphicsSurfaceWidget::OnUpdate() { surface_picture_label->show(); - unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format); - unsigned stride = nibbles_per_pixel * surface_width / 2; - - // We handle depth formats here because DebugUtils only supports TextureFormats if (surface_format <= Format::MaxTextureFormat) { - // Generate a virtual texture - Pica::DebugUtils::TextureInfo info; + Pica::Texture::TextureInfo info; info.physical_address = surface_address; info.width = surface_width; info.height = surface_height; info.format = static_cast<Pica::Regs::TextureFormat>(surface_format); - info.stride = stride; + info.SetDefaultStride(); for (unsigned int y = 0; y < surface_height; ++y) { for (unsigned int x = 0; x < surface_width; ++x) { - Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(buffer, x, y, info, true); + Math::Vec4<u8> color = Pica::Texture::LookupTexture(buffer, x, y, info, true); decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); } } - } else { + // We handle depth formats here because DebugUtils only supports TextureFormats + + // TODO(yuriks): Convert to newer tile-based addressing + unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format); + unsigned stride = nibbles_per_pixel * surface_width / 2; ASSERT_MSG(nibbles_per_pixel >= 2, "Depth decoder only supports formats with at least one byte per pixel"); diff --git a/src/citra_qt/debugger/graphics/graphics_tracing.cpp b/src/citra_qt/debugger/graphics/graphics_tracing.cpp index 716ed50b8..17f1c5ce2 100644 --- a/src/citra_qt/debugger/graphics/graphics_tracing.cpp +++ b/src/citra_qt/debugger/graphics/graphics_tracing.cpp @@ -71,8 +71,8 @@ void GraphicsTracingWidget::StartRecording() { std::array<u32, 4 * 16> default_attributes; for (unsigned i = 0; i < 16; ++i) { for (unsigned comp = 0; comp < 3; ++comp) { - default_attributes[4 * i + comp] = - nihstro::to_float24(Pica::g_state.vs_default_attributes[i][comp].ToFloat32()); + default_attributes[4 * i + comp] = nihstro::to_float24( + Pica::g_state.input_default_attributes.attr[i][comp].ToFloat32()); } } diff --git a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp index b75b94ef8..489ec5f21 100644 --- a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp +++ b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp @@ -18,7 +18,9 @@ #include "citra_qt/util/util.h" #include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/shader/debug_data.h" #include "video_core/shader/shader.h" +#include "video_core/shader/shader_interpreter.h" using nihstro::OpCode; using nihstro::Instruction; @@ -276,9 +278,6 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con output << 'b' << instr.flow_control.bool_uniform_id << ' '; } - u32 target_addr = instr.flow_control.dest_offset; - u32 target_addr_else = instr.flow_control.dest_offset; - if (opcode_info.subtype & OpCode::Info::HasAlternative) { output << "else jump to 0x" << std::setw(4) << std::right << std::setfill('0') << std::hex @@ -473,7 +472,6 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget( } void GraphicsVertexShaderWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) { - auto input = static_cast<Pica::Shader::InputVertex*>(data); if (event == Pica::DebugContext::Event::VertexShaderInvocation) { Reload(true, data); } else { @@ -513,7 +511,7 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d auto& shader_config = Pica::g_state.regs.vs; for (auto instr : shader_setup.program_code) info.code.push_back({instr}); - int num_attributes = Pica::g_state.regs.vertex_attributes.GetNumTotalAttributes(); + int num_attributes = shader_config.max_input_attribute_index + 1; for (auto pattern : shader_setup.swizzle_data) info.swizzle_info.push_back({pattern}); @@ -522,12 +520,13 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d info.labels.insert({entry_point, "main"}); // Generate debug information - debug_data = Pica::g_state.vs.ProduceDebugInfo(input_vertex, num_attributes, shader_config, - shader_setup); + Pica::Shader::InterpreterEngine shader_engine; + shader_engine.SetupBatch(shader_setup, entry_point); + debug_data = shader_engine.ProduceDebugInfo(shader_setup, input_vertex, shader_config); // Reload widget state for (int attr = 0; attr < num_attributes; ++attr) { - unsigned source_attr = shader_config.input_register_map.GetRegisterForAttribute(attr); + unsigned source_attr = shader_config.GetRegisterForAttribute(attr); input_data_mapping[attr]->setText(QString("-> v%1").arg(source_attr)); input_data_container[attr]->setVisible(true); } diff --git a/src/citra_qt/debugger/graphics/graphics_vertex_shader.h b/src/citra_qt/debugger/graphics/graphics_vertex_shader.h index bedea0bed..c249a2ff8 100644 --- a/src/citra_qt/debugger/graphics/graphics_vertex_shader.h +++ b/src/citra_qt/debugger/graphics/graphics_vertex_shader.h @@ -8,6 +8,7 @@ #include <QTreeView> #include "citra_qt/debugger/graphics/graphics_breakpoint_observer.h" #include "nihstro/parser_shbin.h" +#include "video_core/shader/debug_data.h" #include "video_core/shader/shader.h" class QLabel; @@ -81,7 +82,7 @@ private: nihstro::ShaderInfo info; Pica::Shader::DebugData<true> debug_data; - Pica::Shader::InputVertex input_vertex; + Pica::Shader::AttributeBuffer input_vertex; friend class GraphicsVertexShaderModel; }; diff --git a/src/citra_qt/debugger/ramview.cpp b/src/citra_qt/debugger/ramview.cpp deleted file mode 100644 index 10a09dda8..000000000 --- a/src/citra_qt/debugger/ramview.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "citra_qt/debugger/ramview.h" - -GRamView::GRamView(QWidget* parent) : QHexEdit(parent) {} - -void GRamView::OnCPUStepped() { - // TODO: QHexEdit doesn't show vertical scroll bars for > 10MB data streams... - // setData(QByteArray((const char*)Mem_RAM,sizeof(Mem_RAM)/8)); -} diff --git a/src/citra_qt/debugger/ramview.h b/src/citra_qt/debugger/ramview.h deleted file mode 100644 index d01cea93b..000000000 --- a/src/citra_qt/debugger/ramview.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "qhexedit.h" - -class GRamView : public QHexEdit { - Q_OBJECT - -public: - explicit GRamView(QWidget* parent = nullptr); - -public slots: - void OnCPUStepped(); -}; diff --git a/src/citra_qt/debugger/wait_tree.cpp b/src/citra_qt/debugger/wait_tree.cpp index 1d2de5185..b6ecf3819 100644 --- a/src/citra_qt/debugger/wait_tree.cpp +++ b/src/citra_qt/debugger/wait_tree.cpp @@ -153,7 +153,8 @@ QString WaitTreeThread::GetText() const { case THREADSTATUS_WAIT_SLEEP: status = tr("sleeping"); break; - case THREADSTATUS_WAIT_SYNCH: + case THREADSTATUS_WAIT_SYNCH_ALL: + case THREADSTATUS_WAIT_SYNCH_ANY: status = tr("waiting for objects"); break; case THREADSTATUS_DORMANT: @@ -180,7 +181,8 @@ QColor WaitTreeThread::GetColor() const { return QColor(Qt::GlobalColor::darkRed); case THREADSTATUS_WAIT_SLEEP: return QColor(Qt::GlobalColor::darkYellow); - case THREADSTATUS_WAIT_SYNCH: + case THREADSTATUS_WAIT_SYNCH_ALL: + case THREADSTATUS_WAIT_SYNCH_ANY: return QColor(Qt::GlobalColor::red); case THREADSTATUS_DORMANT: return QColor(Qt::GlobalColor::darkCyan); @@ -228,7 +230,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { } else { list.push_back(std::make_unique<WaitTreeMutexList>(thread.held_mutexes)); } - if (thread.status == THREADSTATUS_WAIT_SYNCH) { + if (thread.status == THREADSTATUS_WAIT_SYNCH_ANY || + thread.status == THREADSTATUS_WAIT_SYNCH_ALL) { list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects, thread.IsSleepingOnWaitAll())); } diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 6d59cf640..f765c0147 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -12,6 +12,7 @@ #include <QFileDialog> #include <QMessageBox> #include <QtGui> +#include <QtWidgets> #include "citra_qt/bootmanager.h" #include "citra_qt/config.h" #include "citra_qt/configure_dialog.h" @@ -24,7 +25,6 @@ #include "citra_qt/debugger/graphics/graphics_tracing.h" #include "citra_qt/debugger/graphics/graphics_vertex_shader.h" #include "citra_qt/debugger/profiler.h" -#include "citra_qt/debugger/ramview.h" #include "citra_qt/debugger/registers.h" #include "citra_qt/debugger/wait_tree.h" #include "citra_qt/game_list.h" @@ -46,7 +46,6 @@ #include "core/gdbstub/gdbstub.h" #include "core/loader/loader.h" #include "core/settings.h" -#include "qhexedit.h" #include "video_core/video_core.h" #ifdef QT_STATICPLUGIN diff --git a/src/citra_qt/util/spinbox.cpp b/src/citra_qt/util/spinbox.cpp index feb0ea1b3..212709007 100644 --- a/src/citra_qt/util/spinbox.cpp +++ b/src/citra_qt/util/spinbox.cpp @@ -165,13 +165,6 @@ void CSpinBox::UpdateText() { // Uppercase digits greater than 9. mask += ">"; - // The greatest signed 64-bit number has 19 decimal digits. - // TODO: Could probably make this more generic with some logarithms. - // For reference, unsigned 64-bit can have up to 20 decimal digits. - int digits = (num_digits != 0) - ? num_digits - : (base == 16) ? 16 : (base == 10) ? 19 : 0xFF; // fallback case... - // Match num_digits digits // Digits irrelevant to the chosen number base are filtered in the validator mask += QString("H").repeated(std::max(num_digits, 1)); diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 5aecf6e6e..592911c2b 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -46,6 +46,7 @@ set(HEADERS microprofileui.h platform.h profiler_reporting.h + quaternion.h scm_rev.h scope_exit.h string_util.h @@ -60,14 +61,11 @@ set(HEADERS if(ARCHITECTURE_x86_64) set(SRCS ${SRCS} - x64/abi.cpp x64/cpu_detect.cpp - x64/emitter.cpp) + ) set(HEADERS ${HEADERS} - x64/abi.h x64/cpu_detect.h - x64/emitter.h x64/xbyak_abi.h x64/xbyak_util.h ) diff --git a/src/common/bit_set.h b/src/common/bit_set.h index 3059d0cb0..9c2e6b28c 100644 --- a/src/common/bit_set.h +++ b/src/common/bit_set.h @@ -121,22 +121,19 @@ public: class Iterator { public: Iterator(const Iterator& other) : m_val(other.m_val), m_bit(other.m_bit) {} - Iterator(IntTy val, int bit) : m_val(val), m_bit(bit) {} + Iterator(IntTy val) : m_val(val), m_bit(0) {} Iterator& operator=(Iterator other) { new (this) Iterator(other); return *this; } int operator*() { - return m_bit; + return m_bit + ComputeLsb(); } Iterator& operator++() { - if (m_val == 0) { - m_bit = -1; - } else { - int bit = LeastSignificantSetBit(m_val); - m_val &= ~(1 << bit); - m_bit = bit; - } + int lsb = ComputeLsb(); + m_val >>= lsb + 1; + m_bit += lsb + 1; + m_has_lsb = false; return *this; } Iterator operator++(int _) { @@ -145,15 +142,24 @@ public: return other; } bool operator==(Iterator other) const { - return m_bit == other.m_bit; + return m_val == other.m_val; } bool operator!=(Iterator other) const { - return m_bit != other.m_bit; + return m_val != other.m_val; } private: + int ComputeLsb() { + if (!m_has_lsb) { + m_lsb = LeastSignificantSetBit(m_val); + m_has_lsb = true; + } + return m_lsb; + } IntTy m_val; int m_bit; + int m_lsb = -1; + bool m_has_lsb = false; }; BitSet() : m_val(0) {} @@ -221,11 +227,10 @@ public: } Iterator begin() const { - Iterator it(m_val, 0); - return ++it; + return Iterator(m_val); } Iterator end() const { - return Iterator(m_val, -1); + return Iterator(0); } IntTy m_val; diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 1a1f5d9b5..df234c225 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -303,7 +303,7 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) { // copy loop while (!feof(input)) { // read input - int rnum = fread(buffer, sizeof(char), BSIZE, input); + size_t rnum = fread(buffer, sizeof(char), BSIZE, input); if (rnum != BSIZE) { if (ferror(input) != 0) { LOG_ERROR(Common_Filesystem, "failed reading from source, %s --> %s: %s", @@ -313,7 +313,7 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) { } // write output - int wnum = fwrite(buffer, sizeof(char), rnum, output); + size_t wnum = fwrite(buffer, sizeof(char), rnum, output); if (wnum != rnum) { LOG_ERROR(Common_Filesystem, "failed writing to output, %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg()); diff --git a/src/common/hash.cpp b/src/common/hash.cpp index 2309320bb..f3d390dc5 100644 --- a/src/common/hash.cpp +++ b/src/common/hash.cpp @@ -16,7 +16,7 @@ namespace Common { // Block read - if your platform needs to do endian-swapping or can only handle aligned reads, do // the conversion here -static FORCE_INLINE u64 getblock64(const u64* p, int i) { +static FORCE_INLINE u64 getblock64(const u64* p, size_t i) { return p[i]; } @@ -34,9 +34,9 @@ static FORCE_INLINE u64 fmix64(u64 k) { // This is the 128-bit variant of the MurmurHash3 hash function that is targeted for 64-bit // platforms (MurmurHash3_x64_128). It was taken from: // https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp -void MurmurHash3_128(const void* key, int len, u32 seed, void* out) { +void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out) { const u8* data = (const u8*)key; - const int nblocks = len / 16; + const size_t nblocks = len / 16; u64 h1 = seed; u64 h2 = seed; @@ -48,7 +48,7 @@ void MurmurHash3_128(const void* key, int len, u32 seed, void* out) { const u64* blocks = (const u64*)(data); - for (int i = 0; i < nblocks; i++) { + for (size_t i = 0; i < nblocks; i++) { u64 k1 = getblock64(blocks, i * 2 + 0); u64 k2 = getblock64(blocks, i * 2 + 1); diff --git a/src/common/hash.h b/src/common/hash.h index a3850be68..ee2560dad 100644 --- a/src/common/hash.h +++ b/src/common/hash.h @@ -4,11 +4,12 @@ #pragma once +#include <cstddef> #include "common/common_types.h" namespace Common { -void MurmurHash3_128(const void* key, int len, u32 seed, void* out); +void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out); /** * Computes a 64-bit hash over the specified block of data @@ -16,7 +17,7 @@ void MurmurHash3_128(const void* key, int len, u32 seed, void* out); * @param len Length of data (in bytes) to compute hash over * @returns 64-bit hash value that was computed over the data block */ -static inline u64 ComputeHash64(const void* data, int len) { +static inline u64 ComputeHash64(const void* data, size_t len) { u64 res[2]; MurmurHash3_128(data, len, 0, res); return res[0]; diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 3ea102229..2ef3e6b05 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -45,6 +45,7 @@ namespace Log { SUB(Service, LDR) \ SUB(Service, MIC) \ SUB(Service, NDM) \ + SUB(Service, NFC) \ SUB(Service, NIM) \ SUB(Service, NWM) \ SUB(Service, CAM) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 9d8c18d8e..4330ef879 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -62,6 +62,7 @@ enum class Class : ClassType { Service_LDR, ///< The LDR (3ds dll loader) service Service_MIC, ///< The MIC (Microphone) service Service_NDM, ///< The NDM (Network daemon manager) service + Service_NFC, ///< The NFC service Service_NIM, ///< The NIM (Network interface manager) service Service_NWM, ///< The NWM (Network wlan manager) service Service_CAM, ///< The CAM (Camera) service diff --git a/src/common/math_util.h b/src/common/math_util.h index cdeaeb733..45a1ed367 100644 --- a/src/common/math_util.h +++ b/src/common/math_util.h @@ -10,6 +10,8 @@ namespace MathUtil { +static constexpr float PI = 3.14159265f; + inline bool IntervalsIntersect(unsigned start0, unsigned length0, unsigned start1, unsigned length1) { return (std::max(start0, start1) < std::min(start0 + length0, start1 + length1)); diff --git a/src/common/quaternion.h b/src/common/quaternion.h new file mode 100644 index 000000000..84ac82ed3 --- /dev/null +++ b/src/common/quaternion.h @@ -0,0 +1,44 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/vector_math.h" + +namespace Math { + +template <typename T> +class Quaternion { +public: + Math::Vec3<T> xyz; + T w; + + Quaternion<decltype(-T{})> Inverse() const { + return {-xyz, w}; + } + + Quaternion<decltype(T{} + T{})> operator+(const Quaternion& other) const { + return {xyz + other.xyz, w + other.w}; + } + + Quaternion<decltype(T{} - T{})> operator-(const Quaternion& other) const { + return {xyz - other.xyz, w - other.w}; + } + + Quaternion<decltype(T{} * T{} - T{} * T{})> operator*(const Quaternion& other) const { + return {xyz * other.w + other.xyz * w + Cross(xyz, other.xyz), + w * other.w - Dot(xyz, other.xyz)}; + } +}; + +template <typename T> +auto QuaternionRotate(const Quaternion<T>& q, const Math::Vec3<T>& v) { + return v + 2 * Cross(q.xyz, Cross(q.xyz, v) + v * q.w); +} + +inline Quaternion<float> MakeQuaternion(const Math::Vec3<float>& axis, float angle) { + return {axis * std::sin(angle / 2), std::cos(angle / 2)}; +} + +} // namspace Math diff --git a/src/common/thread.h b/src/common/thread.h index 9c08be7e3..fa475ab51 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -4,6 +4,7 @@ #pragma once +#include <chrono> #include <condition_variable> #include <cstddef> #include <mutex> @@ -54,6 +55,15 @@ public: is_set = false; } + template <class Clock, class Duration> + bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) { + std::unique_lock<std::mutex> lk(mutex); + if (!condvar.wait_until(lk, time, [this] { return is_set; })) + return false; + is_set = false; + return true; + } + void Reset() { std::unique_lock<std::mutex> lk(mutex); // no other action required, since wait loops on the predicate and any lingering signal will diff --git a/src/common/vector_math.h b/src/common/vector_math.h index a57d86d88..7ca8e15f5 100644 --- a/src/common/vector_math.h +++ b/src/common/vector_math.h @@ -186,6 +186,18 @@ Vec2<T> operator*(const V& f, const Vec2<T>& vec) { typedef Vec2<float> Vec2f; +template <> +inline float Vec2<float>::Length() const { + return std::sqrt(x * x + y * y); +} + +template <> +inline float Vec2<float>::Normalize() { + float length = Length(); + *this /= length; + return length; +} + template <typename T> class Vec3 { public: @@ -388,6 +400,13 @@ inline Vec3<float> Vec3<float>::Normalized() const { return *this / Length(); } +template <> +inline float Vec3<float>::Normalize() { + float length = Length(); + *this /= length; + return length; +} + typedef Vec3<float> Vec3f; template <typename T> diff --git a/src/common/x64/abi.cpp b/src/common/x64/abi.cpp deleted file mode 100644 index 504b9c940..000000000 --- a/src/common/x64/abi.cpp +++ /dev/null @@ -1,350 +0,0 @@ -// Copyright (C) 2003 Dolphin Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official SVN repository and contact information can be found at -// http://code.google.com/p/dolphin-emu/ - -#include "abi.h" -#include "emitter.h" - -using namespace Gen; - -// Shared code between Win64 and Unix64 - -void XEmitter::ABI_CalculateFrameSize(BitSet32 mask, size_t rsp_alignment, size_t needed_frame_size, - size_t* shadowp, size_t* subtractionp, size_t* xmm_offsetp) { - size_t shadow = 0; -#if defined(_WIN32) - shadow = 0x20; -#endif - - int count = (mask & ABI_ALL_GPRS).Count(); - rsp_alignment -= count * 8; - size_t subtraction = 0; - int fpr_count = (mask & ABI_ALL_FPRS).Count(); - if (fpr_count) { - // If we have any XMMs to save, we must align the stack here. - subtraction = rsp_alignment & 0xf; - } - subtraction += 16 * fpr_count; - size_t xmm_base_subtraction = subtraction; - subtraction += needed_frame_size; - subtraction += shadow; - // Final alignment. - rsp_alignment -= subtraction; - subtraction += rsp_alignment & 0xf; - - *shadowp = shadow; - *subtractionp = subtraction; - *xmm_offsetp = subtraction - xmm_base_subtraction; -} - -size_t XEmitter::ABI_PushRegistersAndAdjustStack(BitSet32 mask, size_t rsp_alignment, - size_t needed_frame_size) { - size_t shadow, subtraction, xmm_offset; - ABI_CalculateFrameSize(mask, rsp_alignment, needed_frame_size, &shadow, &subtraction, - &xmm_offset); - - for (int r : mask& ABI_ALL_GPRS) - PUSH((X64Reg)r); - - if (subtraction) - SUB(64, R(RSP), subtraction >= 0x80 ? Imm32((u32)subtraction) : Imm8((u8)subtraction)); - - for (int x : mask& ABI_ALL_FPRS) { - MOVAPD(MDisp(RSP, (int)xmm_offset), (X64Reg)(x - 16)); - xmm_offset += 16; - } - - return shadow; -} - -void XEmitter::ABI_PopRegistersAndAdjustStack(BitSet32 mask, size_t rsp_alignment, - size_t needed_frame_size) { - size_t shadow, subtraction, xmm_offset; - ABI_CalculateFrameSize(mask, rsp_alignment, needed_frame_size, &shadow, &subtraction, - &xmm_offset); - - for (int x : mask& ABI_ALL_FPRS) { - MOVAPD((X64Reg)(x - 16), MDisp(RSP, (int)xmm_offset)); - xmm_offset += 16; - } - - if (subtraction) - ADD(64, R(RSP), subtraction >= 0x80 ? Imm32((u32)subtraction) : Imm8((u8)subtraction)); - - for (int r = 15; r >= 0; r--) { - if (mask[r]) - POP((X64Reg)r); - } -} - -// Common functions -void XEmitter::ABI_CallFunction(const void* func) { - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionC16(const void* func, u16 param1) { - MOV(32, R(ABI_PARAM1), Imm32((u32)param1)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionCC16(const void* func, u32 param1, u16 param2) { - MOV(32, R(ABI_PARAM1), Imm32(param1)); - MOV(32, R(ABI_PARAM2), Imm32((u32)param2)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionC(const void* func, u32 param1) { - MOV(32, R(ABI_PARAM1), Imm32(param1)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionCC(const void* func, u32 param1, u32 param2) { - MOV(32, R(ABI_PARAM1), Imm32(param1)); - MOV(32, R(ABI_PARAM2), Imm32(param2)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionCCC(const void* func, u32 param1, u32 param2, u32 param3) { - MOV(32, R(ABI_PARAM1), Imm32(param1)); - MOV(32, R(ABI_PARAM2), Imm32(param2)); - MOV(32, R(ABI_PARAM3), Imm32(param3)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionCCP(const void* func, u32 param1, u32 param2, void* param3) { - MOV(32, R(ABI_PARAM1), Imm32(param1)); - MOV(32, R(ABI_PARAM2), Imm32(param2)); - MOV(64, R(ABI_PARAM3), ImmPtr(param3)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionCCCP(const void* func, u32 param1, u32 param2, u32 param3, - void* param4) { - MOV(32, R(ABI_PARAM1), Imm32(param1)); - MOV(32, R(ABI_PARAM2), Imm32(param2)); - MOV(32, R(ABI_PARAM3), Imm32(param3)); - MOV(64, R(ABI_PARAM4), ImmPtr(param4)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionP(const void* func, void* param1) { - MOV(64, R(ABI_PARAM1), ImmPtr(param1)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionPA(const void* func, void* param1, const Gen::OpArg& arg2) { - MOV(64, R(ABI_PARAM1), ImmPtr(param1)); - if (!arg2.IsSimpleReg(ABI_PARAM2)) - MOV(32, R(ABI_PARAM2), arg2); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionPAA(const void* func, void* param1, const Gen::OpArg& arg2, - const Gen::OpArg& arg3) { - MOV(64, R(ABI_PARAM1), ImmPtr(param1)); - if (!arg2.IsSimpleReg(ABI_PARAM2)) - MOV(32, R(ABI_PARAM2), arg2); - if (!arg3.IsSimpleReg(ABI_PARAM3)) - MOV(32, R(ABI_PARAM3), arg3); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionPPC(const void* func, void* param1, void* param2, u32 param3) { - MOV(64, R(ABI_PARAM1), ImmPtr(param1)); - MOV(64, R(ABI_PARAM2), ImmPtr(param2)); - MOV(32, R(ABI_PARAM3), Imm32(param3)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -// Pass a register as a parameter. -void XEmitter::ABI_CallFunctionR(const void* func, X64Reg reg1) { - if (reg1 != ABI_PARAM1) - MOV(32, R(ABI_PARAM1), R(reg1)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -// Pass two registers as parameters. -void XEmitter::ABI_CallFunctionRR(const void* func, X64Reg reg1, X64Reg reg2) { - if (reg2 != ABI_PARAM1) { - if (reg1 != ABI_PARAM1) - MOV(64, R(ABI_PARAM1), R(reg1)); - if (reg2 != ABI_PARAM2) - MOV(64, R(ABI_PARAM2), R(reg2)); - } else { - if (reg2 != ABI_PARAM2) - MOV(64, R(ABI_PARAM2), R(reg2)); - if (reg1 != ABI_PARAM1) - MOV(64, R(ABI_PARAM1), R(reg1)); - } - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionAC(const void* func, const Gen::OpArg& arg1, u32 param2) { - if (!arg1.IsSimpleReg(ABI_PARAM1)) - MOV(32, R(ABI_PARAM1), arg1); - MOV(32, R(ABI_PARAM2), Imm32(param2)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionACC(const void* func, const Gen::OpArg& arg1, u32 param2, - u32 param3) { - if (!arg1.IsSimpleReg(ABI_PARAM1)) - MOV(32, R(ABI_PARAM1), arg1); - MOV(32, R(ABI_PARAM2), Imm32(param2)); - MOV(64, R(ABI_PARAM3), Imm64(param3)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionA(const void* func, const Gen::OpArg& arg1) { - if (!arg1.IsSimpleReg(ABI_PARAM1)) - MOV(32, R(ABI_PARAM1), arg1); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionAA(const void* func, const Gen::OpArg& arg1, - const Gen::OpArg& arg2) { - if (!arg1.IsSimpleReg(ABI_PARAM1)) - MOV(32, R(ABI_PARAM1), arg1); - if (!arg2.IsSimpleReg(ABI_PARAM2)) - MOV(32, R(ABI_PARAM2), arg2); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -}
\ No newline at end of file diff --git a/src/common/x64/abi.h b/src/common/x64/abi.h deleted file mode 100644 index eaaf81d89..000000000 --- a/src/common/x64/abi.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2008 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include "common/bit_set.h" -#include "emitter.h" - -// x64 ABI:s, and helpers to help follow them when JIT-ing code. -// All convensions return values in EAX (+ possibly EDX). - -// Windows 64-bit -// * 4-reg "fastcall" variant, very new-skool stack handling -// * Callee moves stack pointer, to make room for shadow regs for the biggest function _it itself -// calls_ -// * Parameters passed in RCX, RDX, ... further parameters are MOVed into the allocated stack space. -// Scratch: RAX RCX RDX R8 R9 R10 R11 -// Callee-save: RBX RSI RDI RBP R12 R13 R14 R15 -// Parameters: RCX RDX R8 R9, further MOV-ed - -// Linux 64-bit -// * 6-reg "fastcall" variant, old skool stack handling (parameters are pushed) -// Scratch: RAX RCX RDX RSI RDI R8 R9 R10 R11 -// Callee-save: RBX RBP R12 R13 R14 R15 -// Parameters: RDI RSI RDX RCX R8 R9 - -#define ABI_ALL_FPRS BitSet32(0xffff0000) -#define ABI_ALL_GPRS BitSet32(0x0000ffff) - -#ifdef _WIN32 // 64-bit Windows - the really exotic calling convention - -#define ABI_PARAM1 RCX -#define ABI_PARAM2 RDX -#define ABI_PARAM3 R8 -#define ABI_PARAM4 R9 - -// xmm0-xmm15 use the upper 16 bits in the functions that push/pop registers. -#define ABI_ALL_CALLER_SAVED \ - (BitSet32{RAX, RCX, RDX, R8, R9, R10, R11, XMM0 + 16, XMM1 + 16, XMM2 + 16, XMM3 + 16, \ - XMM4 + 16, XMM5 + 16}) -#else // 64-bit Unix / OS X - -#define ABI_PARAM1 RDI -#define ABI_PARAM2 RSI -#define ABI_PARAM3 RDX -#define ABI_PARAM4 RCX -#define ABI_PARAM5 R8 -#define ABI_PARAM6 R9 - -// TODO: Avoid pushing all 16 XMM registers when possible. Most functions we call probably -// don't actually clobber them. -#define ABI_ALL_CALLER_SAVED (BitSet32{RAX, RCX, RDX, RDI, RSI, R8, R9, R10, R11} | ABI_ALL_FPRS) -#endif // WIN32 - -#define ABI_ALL_CALLEE_SAVED (~ABI_ALL_CALLER_SAVED) - -#define ABI_RETURN RAX diff --git a/src/common/x64/emitter.cpp b/src/common/x64/emitter.cpp deleted file mode 100644 index f5930abec..000000000 --- a/src/common/x64/emitter.cpp +++ /dev/null @@ -1,2583 +0,0 @@ -// Copyright (C) 2003 Dolphin Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official SVN repository and contact information can be found at -// http://code.google.com/p/dolphin-emu/ - -#include <cinttypes> -#include <cstring> -#include "abi.h" -#include "common/assert.h" -#include "common/logging/log.h" -#include "common/memory_util.h" -#include "cpu_detect.h" -#include "emitter.h" - -namespace Gen { - -struct NormalOpDef { - u8 toRm8, toRm32, fromRm8, fromRm32, imm8, imm32, simm8, eaximm8, eaximm32, ext; -}; - -// 0xCC is code for invalid combination of immediates -static const NormalOpDef normalops[11] = { - {0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x83, 0x04, 0x05, 0}, // ADD - {0x10, 0x11, 0x12, 0x13, 0x80, 0x81, 0x83, 0x14, 0x15, 2}, // ADC - - {0x28, 0x29, 0x2A, 0x2B, 0x80, 0x81, 0x83, 0x2C, 0x2D, 5}, // SUB - {0x18, 0x19, 0x1A, 0x1B, 0x80, 0x81, 0x83, 0x1C, 0x1D, 3}, // SBB - - {0x20, 0x21, 0x22, 0x23, 0x80, 0x81, 0x83, 0x24, 0x25, 4}, // AND - {0x08, 0x09, 0x0A, 0x0B, 0x80, 0x81, 0x83, 0x0C, 0x0D, 1}, // OR - - {0x30, 0x31, 0x32, 0x33, 0x80, 0x81, 0x83, 0x34, 0x35, 6}, // XOR - {0x88, 0x89, 0x8A, 0x8B, 0xC6, 0xC7, 0xCC, 0xCC, 0xCC, 0}, // MOV - - {0x84, 0x85, 0x84, 0x85, 0xF6, 0xF7, 0xCC, 0xA8, 0xA9, 0}, // TEST (to == from) - {0x38, 0x39, 0x3A, 0x3B, 0x80, 0x81, 0x83, 0x3C, 0x3D, 7}, // CMP - - {0x86, 0x87, 0x86, 0x87, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 7}, // XCHG -}; - -enum NormalSSEOps { - sseCMP = 0xC2, - sseADD = 0x58, // ADD - sseSUB = 0x5C, // SUB - sseAND = 0x54, // AND - sseANDN = 0x55, // ANDN - sseOR = 0x56, - sseXOR = 0x57, - sseMUL = 0x59, // MUL - sseDIV = 0x5E, // DIV - sseMIN = 0x5D, // MIN - sseMAX = 0x5F, // MAX - sseCOMIS = 0x2F, // COMIS - sseUCOMIS = 0x2E, // UCOMIS - sseSQRT = 0x51, // SQRT - sseRSQRT = 0x52, // RSQRT (NO DOUBLE PRECISION!!!) - sseRCP = 0x53, // RCP - sseMOVAPfromRM = 0x28, // MOVAP from RM - sseMOVAPtoRM = 0x29, // MOVAP to RM - sseMOVUPfromRM = 0x10, // MOVUP from RM - sseMOVUPtoRM = 0x11, // MOVUP to RM - sseMOVLPfromRM = 0x12, - sseMOVLPtoRM = 0x13, - sseMOVHPfromRM = 0x16, - sseMOVHPtoRM = 0x17, - sseMOVHLPS = 0x12, - sseMOVLHPS = 0x16, - sseMOVDQfromRM = 0x6F, - sseMOVDQtoRM = 0x7F, - sseMASKMOVDQU = 0xF7, - sseLDDQU = 0xF0, - sseSHUF = 0xC6, - sseMOVNTDQ = 0xE7, - sseMOVNTP = 0x2B, - sseHADD = 0x7C, -}; - -void XEmitter::SetCodePtr(u8* ptr) { - code = ptr; -} - -const u8* XEmitter::GetCodePtr() const { - return code; -} - -u8* XEmitter::GetWritableCodePtr() { - return code; -} - -void XEmitter::Write8(u8 value) { - *code++ = value; -} - -void XEmitter::Write16(u16 value) { - std::memcpy(code, &value, sizeof(u16)); - code += sizeof(u16); -} - -void XEmitter::Write32(u32 value) { - std::memcpy(code, &value, sizeof(u32)); - code += sizeof(u32); -} - -void XEmitter::Write64(u64 value) { - std::memcpy(code, &value, sizeof(u64)); - code += sizeof(u64); -} - -void XEmitter::ReserveCodeSpace(int bytes) { - for (int i = 0; i < bytes; i++) - *code++ = 0xCC; -} - -const u8* XEmitter::AlignCode4() { - int c = int((u64)code & 3); - if (c) - ReserveCodeSpace(4 - c); - return code; -} - -const u8* XEmitter::AlignCode16() { - int c = int((u64)code & 15); - if (c) - ReserveCodeSpace(16 - c); - return code; -} - -const u8* XEmitter::AlignCodePage() { - int c = int((u64)code & 4095); - if (c) - ReserveCodeSpace(4096 - c); - return code; -} - -// This operation modifies flags; check to see the flags are locked. -// If the flags are locked, we should immediately and loudly fail before -// causing a subtle JIT bug. -void XEmitter::CheckFlags() { - ASSERT_MSG(!flags_locked, "Attempt to modify flags while flags locked!"); -} - -void XEmitter::WriteModRM(int mod, int reg, int rm) { - Write8((u8)((mod << 6) | ((reg & 7) << 3) | (rm & 7))); -} - -void XEmitter::WriteSIB(int scale, int index, int base) { - Write8((u8)((scale << 6) | ((index & 7) << 3) | (base & 7))); -} - -void OpArg::WriteRex(XEmitter* emit, int opBits, int bits, int customOp) const { - if (customOp == -1) - customOp = operandReg; -#ifdef ARCHITECTURE_x86_64 - u8 op = 0x40; - // REX.W (whether operation is a 64-bit operation) - if (opBits == 64) - op |= 8; - // REX.R (whether ModR/M reg field refers to R8-R15. - if (customOp & 8) - op |= 4; - // REX.X (whether ModR/M SIB index field refers to R8-R15) - if (indexReg & 8) - op |= 2; - // REX.B (whether ModR/M rm or SIB base or opcode reg field refers to R8-R15) - if (offsetOrBaseReg & 8) - op |= 1; - // Write REX if wr have REX bits to write, or if the operation accesses - // SIL, DIL, BPL, or SPL. - if (op != 0x40 || (scale == SCALE_NONE && bits == 8 && (offsetOrBaseReg & 0x10c) == 4) || - (opBits == 8 && (customOp & 0x10c) == 4)) { - emit->Write8(op); - // Check the operation doesn't access AH, BH, CH, or DH. - DEBUG_ASSERT((offsetOrBaseReg & 0x100) == 0); - DEBUG_ASSERT((customOp & 0x100) == 0); - } -#else - DEBUG_ASSERT(opBits != 64); - DEBUG_ASSERT((customOp & 8) == 0 || customOp == -1); - DEBUG_ASSERT((indexReg & 8) == 0); - DEBUG_ASSERT((offsetOrBaseReg & 8) == 0); - DEBUG_ASSERT(opBits != 8 || (customOp & 0x10c) != 4 || customOp == -1); - DEBUG_ASSERT(scale == SCALE_ATREG || bits != 8 || (offsetOrBaseReg & 0x10c) != 4); -#endif -} - -void OpArg::WriteVex(XEmitter* emit, X64Reg regOp1, X64Reg regOp2, int L, int pp, int mmmmm, - int W) const { - int R = !(regOp1 & 8); - int X = !(indexReg & 8); - int B = !(offsetOrBaseReg & 8); - - int vvvv = (regOp2 == X64Reg::INVALID_REG) ? 0xf : (regOp2 ^ 0xf); - - // do we need any VEX fields that only appear in the three-byte form? - if (X == 1 && B == 1 && W == 0 && mmmmm == 1) { - u8 RvvvvLpp = (R << 7) | (vvvv << 3) | (L << 2) | pp; - emit->Write8(0xC5); - emit->Write8(RvvvvLpp); - } else { - u8 RXBmmmmm = (R << 7) | (X << 6) | (B << 5) | mmmmm; - u8 WvvvvLpp = (W << 7) | (vvvv << 3) | (L << 2) | pp; - emit->Write8(0xC4); - emit->Write8(RXBmmmmm); - emit->Write8(WvvvvLpp); - } -} - -void OpArg::WriteRest(XEmitter* emit, int extraBytes, X64Reg _operandReg, - bool warn_64bit_offset) const { - if (_operandReg == INVALID_REG) - _operandReg = (X64Reg)this->operandReg; - int mod = 0; - int ireg = indexReg; - bool SIB = false; - int _offsetOrBaseReg = this->offsetOrBaseReg; - - if (scale == SCALE_RIP) // Also, on 32-bit, just an immediate address - { - // Oh, RIP addressing. - _offsetOrBaseReg = 5; - emit->WriteModRM(0, _operandReg, _offsetOrBaseReg); -// TODO : add some checks -#ifdef ARCHITECTURE_x86_64 - u64 ripAddr = (u64)emit->GetCodePtr() + 4 + extraBytes; - s64 distance = (s64)offset - (s64)ripAddr; - ASSERT_MSG((distance < 0x80000000LL && distance >= -0x80000000LL) || !warn_64bit_offset, - "WriteRest: op out of range (0x%" PRIx64 " uses 0x%" PRIx64 ")", ripAddr, - offset); - s32 offs = (s32)distance; - emit->Write32((u32)offs); -#else - emit->Write32((u32)offset); -#endif - return; - } - - if (scale == 0) { - // Oh, no memory, Just a reg. - mod = 3; // 11 - } else if (scale >= 1) { - // Ah good, no scaling. - if (scale == SCALE_ATREG && !((_offsetOrBaseReg & 7) == 4 || (_offsetOrBaseReg & 7) == 5)) { - // Okay, we're good. No SIB necessary. - int ioff = (int)offset; - if (ioff == 0) { - mod = 0; - } else if (ioff < -128 || ioff > 127) { - mod = 2; // 32-bit displacement - } else { - mod = 1; // 8-bit displacement - } - } else if (scale >= SCALE_NOBASE_2 && scale <= SCALE_NOBASE_8) { - SIB = true; - mod = 0; - _offsetOrBaseReg = 5; - } else // if (scale != SCALE_ATREG) - { - if ((_offsetOrBaseReg & 7) == 4) // this would occupy the SIB encoding :( - { - // So we have to fake it with SIB encoding :( - SIB = true; - } - - if (scale >= SCALE_1 && scale < SCALE_ATREG) { - SIB = true; - } - - if (scale == SCALE_ATREG && ((_offsetOrBaseReg & 7) == 4)) { - SIB = true; - ireg = _offsetOrBaseReg; - } - - // Okay, we're fine. Just disp encoding. - // We need displacement. Which size? - int ioff = (int)(s64)offset; - if (ioff < -128 || ioff > 127) { - mod = 2; // 32-bit displacement - } else { - mod = 1; // 8-bit displacement - } - } - } - - // Okay. Time to do the actual writing - // ModRM byte: - int oreg = _offsetOrBaseReg; - if (SIB) - oreg = 4; - - // TODO(ector): WTF is this if about? I don't remember writing it :-) - // if (RIP) - // oreg = 5; - - emit->WriteModRM(mod, _operandReg & 7, oreg & 7); - - if (SIB) { - // SIB byte - int ss; - switch (scale) { - case SCALE_NONE: - _offsetOrBaseReg = 4; - ss = 0; - break; // RSP - case SCALE_1: - ss = 0; - break; - case SCALE_2: - ss = 1; - break; - case SCALE_4: - ss = 2; - break; - case SCALE_8: - ss = 3; - break; - case SCALE_NOBASE_2: - ss = 1; - break; - case SCALE_NOBASE_4: - ss = 2; - break; - case SCALE_NOBASE_8: - ss = 3; - break; - case SCALE_ATREG: - ss = 0; - break; - default: - ASSERT_MSG(0, "Invalid scale for SIB byte"); - ss = 0; - break; - } - emit->Write8((u8)((ss << 6) | ((ireg & 7) << 3) | (_offsetOrBaseReg & 7))); - } - - if (mod == 1) // 8-bit disp - { - emit->Write8((u8)(s8)(s32)offset); - } else if (mod == 2 || (scale >= SCALE_NOBASE_2 && scale <= SCALE_NOBASE_8)) // 32-bit disp - { - emit->Write32((u32)offset); - } -} - -// W = operand extended width (1 if 64-bit) -// R = register# upper bit -// X = scale amnt upper bit -// B = base register# upper bit -void XEmitter::Rex(int w, int r, int x, int b) { - w = w ? 1 : 0; - r = r ? 1 : 0; - x = x ? 1 : 0; - b = b ? 1 : 0; - u8 rx = (u8)(0x40 | (w << 3) | (r << 2) | (x << 1) | (b)); - if (rx != 0x40) - Write8(rx); -} - -void XEmitter::JMP(const u8* addr, bool force5Bytes) { - u64 fn = (u64)addr; - if (!force5Bytes) { - s64 distance = (s64)(fn - ((u64)code + 2)); - ASSERT_MSG(distance >= -0x80 && distance < 0x80, - "Jump target too far away, needs force5Bytes = true"); - // 8 bits will do - Write8(0xEB); - Write8((u8)(s8)distance); - } else { - s64 distance = (s64)(fn - ((u64)code + 5)); - - ASSERT_MSG(distance >= -0x80000000LL && distance < 0x80000000LL, - "Jump target too far away, needs indirect register"); - Write8(0xE9); - Write32((u32)(s32)distance); - } -} - -void XEmitter::JMPptr(const OpArg& arg2) { - OpArg arg = arg2; - if (arg.IsImm()) - ASSERT_MSG(0, "JMPptr - Imm argument"); - arg.operandReg = 4; - arg.WriteRex(this, 0, 0); - Write8(0xFF); - arg.WriteRest(this); -} - -// Can be used to trap other processors, before overwriting their code -// not used in dolphin -void XEmitter::JMPself() { - Write8(0xEB); - Write8(0xFE); -} - -void XEmitter::CALLptr(OpArg arg) { - if (arg.IsImm()) - ASSERT_MSG(0, "CALLptr - Imm argument"); - arg.operandReg = 2; - arg.WriteRex(this, 0, 0); - Write8(0xFF); - arg.WriteRest(this); -} - -void XEmitter::CALL(const void* fnptr) { - u64 distance = u64(fnptr) - (u64(code) + 5); - ASSERT_MSG(distance < 0x0000000080000000ULL || distance >= 0xFFFFFFFF80000000ULL, - "CALL out of range (%p calls %p)", code, fnptr); - Write8(0xE8); - Write32(u32(distance)); -} - -FixupBranch XEmitter::CALL() { - FixupBranch branch; - branch.type = 1; - branch.ptr = code + 5; - - Write8(0xE8); - Write32(0); - - return branch; -} - -FixupBranch XEmitter::J(bool force5bytes) { - FixupBranch branch; - branch.type = force5bytes ? 1 : 0; - branch.ptr = code + (force5bytes ? 5 : 2); - if (!force5bytes) { - // 8 bits will do - Write8(0xEB); - Write8(0); - } else { - Write8(0xE9); - Write32(0); - } - return branch; -} - -FixupBranch XEmitter::J_CC(CCFlags conditionCode, bool force5bytes) { - FixupBranch branch; - branch.type = force5bytes ? 1 : 0; - branch.ptr = code + (force5bytes ? 6 : 2); - if (!force5bytes) { - // 8 bits will do - Write8(0x70 + conditionCode); - Write8(0); - } else { - Write8(0x0F); - Write8(0x80 + conditionCode); - Write32(0); - } - return branch; -} - -void XEmitter::J_CC(CCFlags conditionCode, const u8* addr, bool force5bytes) { - u64 fn = (u64)addr; - s64 distance = (s64)(fn - ((u64)code + 2)); - if (distance < -0x80 || distance >= 0x80 || force5bytes) { - distance = (s64)(fn - ((u64)code + 6)); - ASSERT_MSG(distance >= -0x80000000LL && distance < 0x80000000LL, - "Jump target too far away, needs indirect register"); - Write8(0x0F); - Write8(0x80 + conditionCode); - Write32((u32)(s32)distance); - } else { - Write8(0x70 + conditionCode); - Write8((u8)(s8)distance); - } -} - -void XEmitter::SetJumpTarget(const FixupBranch& branch) { - if (branch.type == 0) { - s64 distance = (s64)(code - branch.ptr); - ASSERT_MSG(distance >= -0x80 && distance < 0x80, - "Jump target too far away, needs force5Bytes = true"); - branch.ptr[-1] = (u8)(s8)distance; - } else if (branch.type == 1) { - s64 distance = (s64)(code - branch.ptr); - ASSERT_MSG(distance >= -0x80000000LL && distance < 0x80000000LL, - "Jump target too far away, needs indirect register"); - ((s32*)branch.ptr)[-1] = (s32)distance; - } -} - -void XEmitter::SetJumpTarget(const FixupBranch& branch, const u8* target) { - if (branch.type == 0) { - s64 distance = (s64)(target - branch.ptr); - ASSERT_MSG(distance >= -0x80 && distance < 0x80, - "Jump target too far away, needs force5Bytes = true"); - branch.ptr[-1] = (u8)(s8)distance; - } else if (branch.type == 1) { - s64 distance = (s64)(target - branch.ptr); - ASSERT_MSG(distance >= -0x80000000LL && distance < 0x80000000LL, - "Jump target too far away, needs indirect register"); - ((s32*)branch.ptr)[-1] = (s32)distance; - } -} - -// Single byte opcodes -// There is no PUSHAD/POPAD in 64-bit mode. -void XEmitter::INT3() { - Write8(0xCC); -} -void XEmitter::RET() { - Write8(0xC3); -} -void XEmitter::RET_FAST() { - Write8(0xF3); - Write8(0xC3); -} // two-byte return (rep ret) - recommended by AMD optimization manual for the case of jumping to a - // ret - -// The first sign of decadence: optimized NOPs. -void XEmitter::NOP(size_t size) { - DEBUG_ASSERT((int)size > 0); - while (true) { - switch (size) { - case 0: - return; - case 1: - Write8(0x90); - return; - case 2: - Write8(0x66); - Write8(0x90); - return; - case 3: - Write8(0x0F); - Write8(0x1F); - Write8(0x00); - return; - case 4: - Write8(0x0F); - Write8(0x1F); - Write8(0x40); - Write8(0x00); - return; - case 5: - Write8(0x0F); - Write8(0x1F); - Write8(0x44); - Write8(0x00); - Write8(0x00); - return; - case 6: - Write8(0x66); - Write8(0x0F); - Write8(0x1F); - Write8(0x44); - Write8(0x00); - Write8(0x00); - return; - case 7: - Write8(0x0F); - Write8(0x1F); - Write8(0x80); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - return; - case 8: - Write8(0x0F); - Write8(0x1F); - Write8(0x84); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - return; - case 9: - Write8(0x66); - Write8(0x0F); - Write8(0x1F); - Write8(0x84); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - return; - case 10: - Write8(0x66); - Write8(0x66); - Write8(0x0F); - Write8(0x1F); - Write8(0x84); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - return; - default: - // Even though x86 instructions are allowed to be up to 15 bytes long, - // AMD advises against using NOPs longer than 11 bytes because they - // carry a performance penalty on CPUs older than AMD family 16h. - Write8(0x66); - Write8(0x66); - Write8(0x66); - Write8(0x0F); - Write8(0x1F); - Write8(0x84); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - size -= 11; - continue; - } - } -} - -void XEmitter::PAUSE() { - Write8(0xF3); - NOP(); -} // use in tight spinloops for energy saving on some cpu -void XEmitter::CLC() { - CheckFlags(); - Write8(0xF8); -} // clear carry -void XEmitter::CMC() { - CheckFlags(); - Write8(0xF5); -} // flip carry -void XEmitter::STC() { - CheckFlags(); - Write8(0xF9); -} // set carry - -// TODO: xchg ah, al ??? -void XEmitter::XCHG_AHAL() { - Write8(0x86); - Write8(0xe0); - // alt. 86 c4 -} - -// These two can not be executed on early Intel 64-bit CPU:s, only on AMD! -void XEmitter::LAHF() { - Write8(0x9F); -} -void XEmitter::SAHF() { - CheckFlags(); - Write8(0x9E); -} - -void XEmitter::PUSHF() { - Write8(0x9C); -} -void XEmitter::POPF() { - CheckFlags(); - Write8(0x9D); -} - -void XEmitter::LFENCE() { - Write8(0x0F); - Write8(0xAE); - Write8(0xE8); -} -void XEmitter::MFENCE() { - Write8(0x0F); - Write8(0xAE); - Write8(0xF0); -} -void XEmitter::SFENCE() { - Write8(0x0F); - Write8(0xAE); - Write8(0xF8); -} - -void XEmitter::WriteSimple1Byte(int bits, u8 byte, X64Reg reg) { - if (bits == 16) - Write8(0x66); - Rex(bits == 64, 0, 0, (int)reg >> 3); - Write8(byte + ((int)reg & 7)); -} - -void XEmitter::WriteSimple2Byte(int bits, u8 byte1, u8 byte2, X64Reg reg) { - if (bits == 16) - Write8(0x66); - Rex(bits == 64, 0, 0, (int)reg >> 3); - Write8(byte1); - Write8(byte2 + ((int)reg & 7)); -} - -void XEmitter::CWD(int bits) { - if (bits == 16) - Write8(0x66); - Rex(bits == 64, 0, 0, 0); - Write8(0x99); -} - -void XEmitter::CBW(int bits) { - if (bits == 8) - Write8(0x66); - Rex(bits == 32, 0, 0, 0); - Write8(0x98); -} - -// Simple opcodes - -// push/pop do not need wide to be 64-bit -void XEmitter::PUSH(X64Reg reg) { - WriteSimple1Byte(32, 0x50, reg); -} -void XEmitter::POP(X64Reg reg) { - WriteSimple1Byte(32, 0x58, reg); -} - -void XEmitter::PUSH(int bits, const OpArg& reg) { - if (reg.IsSimpleReg()) - PUSH(reg.GetSimpleReg()); - else if (reg.IsImm()) { - switch (reg.GetImmBits()) { - case 8: - Write8(0x6A); - Write8((u8)(s8)reg.offset); - break; - case 16: - Write8(0x66); - Write8(0x68); - Write16((u16)(s16)(s32)reg.offset); - break; - case 32: - Write8(0x68); - Write32((u32)reg.offset); - break; - default: - ASSERT_MSG(0, "PUSH - Bad imm bits"); - break; - } - } else { - if (bits == 16) - Write8(0x66); - reg.WriteRex(this, bits, bits); - Write8(0xFF); - reg.WriteRest(this, 0, (X64Reg)6); - } -} - -void XEmitter::POP(int /*bits*/, const OpArg& reg) { - if (reg.IsSimpleReg()) - POP(reg.GetSimpleReg()); - else - ASSERT_MSG(0, "POP - Unsupported encoding"); -} - -void XEmitter::BSWAP(int bits, X64Reg reg) { - if (bits >= 32) { - WriteSimple2Byte(bits, 0x0F, 0xC8, reg); - } else if (bits == 16) { - ROL(16, R(reg), Imm8(8)); - } else if (bits == 8) { - // Do nothing - can't bswap a single byte... - } else { - ASSERT_MSG(0, "BSWAP - Wrong number of bits"); - } -} - -// Undefined opcode - reserved -// If we ever need a way to always cause a non-breakpoint hard exception... -void XEmitter::UD2() { - Write8(0x0F); - Write8(0x0B); -} - -void XEmitter::PREFETCH(PrefetchLevel level, OpArg arg) { - ASSERT_MSG(!arg.IsImm(), "PREFETCH - Imm argument"); - arg.operandReg = (u8)level; - arg.WriteRex(this, 0, 0); - Write8(0x0F); - Write8(0x18); - arg.WriteRest(this); -} - -void XEmitter::SETcc(CCFlags flag, OpArg dest) { - ASSERT_MSG(!dest.IsImm(), "SETcc - Imm argument"); - dest.operandReg = 0; - dest.WriteRex(this, 0, 8); - Write8(0x0F); - Write8(0x90 + (u8)flag); - dest.WriteRest(this); -} - -void XEmitter::CMOVcc(int bits, X64Reg dest, OpArg src, CCFlags flag) { - ASSERT_MSG(!src.IsImm(), "CMOVcc - Imm argument"); - ASSERT_MSG(bits != 8, "CMOVcc - 8 bits unsupported"); - if (bits == 16) - Write8(0x66); - src.operandReg = dest; - src.WriteRex(this, bits, bits); - Write8(0x0F); - Write8(0x40 + (u8)flag); - src.WriteRest(this); -} - -void XEmitter::WriteMulDivType(int bits, OpArg src, int ext) { - ASSERT_MSG(!src.IsImm(), "WriteMulDivType - Imm argument"); - CheckFlags(); - src.operandReg = ext; - if (bits == 16) - Write8(0x66); - src.WriteRex(this, bits, bits, 0); - if (bits == 8) { - Write8(0xF6); - } else { - Write8(0xF7); - } - src.WriteRest(this); -} - -void XEmitter::MUL(int bits, const OpArg& src) { - WriteMulDivType(bits, src, 4); -} -void XEmitter::DIV(int bits, const OpArg& src) { - WriteMulDivType(bits, src, 6); -} -void XEmitter::IMUL(int bits, const OpArg& src) { - WriteMulDivType(bits, src, 5); -} -void XEmitter::IDIV(int bits, const OpArg& src) { - WriteMulDivType(bits, src, 7); -} -void XEmitter::NEG(int bits, const OpArg& src) { - WriteMulDivType(bits, src, 3); -} -void XEmitter::NOT(int bits, const OpArg& src) { - WriteMulDivType(bits, src, 2); -} - -void XEmitter::WriteBitSearchType(int bits, X64Reg dest, OpArg src, u8 byte2, bool rep) { - ASSERT_MSG(!src.IsImm(), "WriteBitSearchType - Imm argument"); - CheckFlags(); - src.operandReg = (u8)dest; - if (bits == 16) - Write8(0x66); - if (rep) - Write8(0xF3); - src.WriteRex(this, bits, bits); - Write8(0x0F); - Write8(byte2); - src.WriteRest(this); -} - -void XEmitter::MOVNTI(int bits, const OpArg& dest, X64Reg src) { - if (bits <= 16) - ASSERT_MSG(0, "MOVNTI - bits<=16"); - WriteBitSearchType(bits, src, dest, 0xC3); -} - -void XEmitter::BSF(int bits, X64Reg dest, const OpArg& src) { - WriteBitSearchType(bits, dest, src, 0xBC); -} // Bottom bit to top bit -void XEmitter::BSR(int bits, X64Reg dest, const OpArg& src) { - WriteBitSearchType(bits, dest, src, 0xBD); -} // Top bit to bottom bit - -void XEmitter::TZCNT(int bits, X64Reg dest, const OpArg& src) { - CheckFlags(); - if (!Common::GetCPUCaps().bmi1) - ASSERT_MSG(0, "Trying to use BMI1 on a system that doesn't support it. Bad programmer."); - WriteBitSearchType(bits, dest, src, 0xBC, true); -} -void XEmitter::LZCNT(int bits, X64Reg dest, const OpArg& src) { - CheckFlags(); - if (!Common::GetCPUCaps().lzcnt) - ASSERT_MSG(0, "Trying to use LZCNT on a system that doesn't support it. Bad programmer."); - WriteBitSearchType(bits, dest, src, 0xBD, true); -} - -void XEmitter::MOVSX(int dbits, int sbits, X64Reg dest, OpArg src) { - ASSERT_MSG(!src.IsImm(), "MOVSX - Imm argument"); - if (dbits == sbits) { - MOV(dbits, R(dest), src); - return; - } - src.operandReg = (u8)dest; - if (dbits == 16) - Write8(0x66); - src.WriteRex(this, dbits, sbits); - if (sbits == 8) { - Write8(0x0F); - Write8(0xBE); - } else if (sbits == 16) { - Write8(0x0F); - Write8(0xBF); - } else if (sbits == 32 && dbits == 64) { - Write8(0x63); - } else { - Crash(); - } - src.WriteRest(this); -} - -void XEmitter::MOVZX(int dbits, int sbits, X64Reg dest, OpArg src) { - ASSERT_MSG(!src.IsImm(), "MOVZX - Imm argument"); - if (dbits == sbits) { - MOV(dbits, R(dest), src); - return; - } - src.operandReg = (u8)dest; - if (dbits == 16) - Write8(0x66); - // the 32bit result is automatically zero extended to 64bit - src.WriteRex(this, dbits == 64 ? 32 : dbits, sbits); - if (sbits == 8) { - Write8(0x0F); - Write8(0xB6); - } else if (sbits == 16) { - Write8(0x0F); - Write8(0xB7); - } else if (sbits == 32 && dbits == 64) { - Write8(0x8B); - } else { - ASSERT_MSG(0, "MOVZX - Invalid size"); - } - src.WriteRest(this); -} - -void XEmitter::MOVBE(int bits, const OpArg& dest, const OpArg& src) { - ASSERT_MSG(Common::GetCPUCaps().movbe, - "Generating MOVBE on a system that does not support it."); - if (bits == 8) { - MOV(bits, dest, src); - return; - } - - if (bits == 16) - Write8(0x66); - - if (dest.IsSimpleReg()) { - ASSERT_MSG(!src.IsSimpleReg() && !src.IsImm(), "MOVBE: Loading from !mem"); - src.WriteRex(this, bits, bits, dest.GetSimpleReg()); - Write8(0x0F); - Write8(0x38); - Write8(0xF0); - src.WriteRest(this, 0, dest.GetSimpleReg()); - } else if (src.IsSimpleReg()) { - ASSERT_MSG(!dest.IsSimpleReg() && !dest.IsImm(), "MOVBE: Storing to !mem"); - dest.WriteRex(this, bits, bits, src.GetSimpleReg()); - Write8(0x0F); - Write8(0x38); - Write8(0xF1); - dest.WriteRest(this, 0, src.GetSimpleReg()); - } else { - ASSERT_MSG(0, "MOVBE: Not loading or storing to mem"); - } -} - -void XEmitter::LEA(int bits, X64Reg dest, OpArg src) { - ASSERT_MSG(!src.IsImm(), "LEA - Imm argument"); - src.operandReg = (u8)dest; - if (bits == 16) - Write8(0x66); // TODO: performance warning - src.WriteRex(this, bits, bits); - Write8(0x8D); - src.WriteRest(this, 0, INVALID_REG, bits == 64); -} - -// shift can be either imm8 or cl -void XEmitter::WriteShift(int bits, OpArg dest, const OpArg& shift, int ext) { - CheckFlags(); - bool writeImm = false; - if (dest.IsImm()) { - ASSERT_MSG(0, "WriteShift - can't shift imms"); - } - if ((shift.IsSimpleReg() && shift.GetSimpleReg() != ECX) || - (shift.IsImm() && shift.GetImmBits() != 8)) { - ASSERT_MSG(0, "WriteShift - illegal argument"); - } - dest.operandReg = ext; - if (bits == 16) - Write8(0x66); - dest.WriteRex(this, bits, bits, 0); - if (shift.GetImmBits() == 8) { - // ok an imm - u8 imm = (u8)shift.offset; - if (imm == 1) { - Write8(bits == 8 ? 0xD0 : 0xD1); - } else { - writeImm = true; - Write8(bits == 8 ? 0xC0 : 0xC1); - } - } else { - Write8(bits == 8 ? 0xD2 : 0xD3); - } - dest.WriteRest(this, writeImm ? 1 : 0); - if (writeImm) - Write8((u8)shift.offset); -} - -// large rotates and shift are slower on intel than amd -// intel likes to rotate by 1, and the op is smaller too -void XEmitter::ROL(int bits, const OpArg& dest, const OpArg& shift) { - WriteShift(bits, dest, shift, 0); -} -void XEmitter::ROR(int bits, const OpArg& dest, const OpArg& shift) { - WriteShift(bits, dest, shift, 1); -} -void XEmitter::RCL(int bits, const OpArg& dest, const OpArg& shift) { - WriteShift(bits, dest, shift, 2); -} -void XEmitter::RCR(int bits, const OpArg& dest, const OpArg& shift) { - WriteShift(bits, dest, shift, 3); -} -void XEmitter::SHL(int bits, const OpArg& dest, const OpArg& shift) { - WriteShift(bits, dest, shift, 4); -} -void XEmitter::SHR(int bits, const OpArg& dest, const OpArg& shift) { - WriteShift(bits, dest, shift, 5); -} -void XEmitter::SAR(int bits, const OpArg& dest, const OpArg& shift) { - WriteShift(bits, dest, shift, 7); -} - -// index can be either imm8 or register, don't use memory destination because it's slow -void XEmitter::WriteBitTest(int bits, const OpArg& dest, const OpArg& index, int ext) { - CheckFlags(); - if (dest.IsImm()) { - ASSERT_MSG(0, "WriteBitTest - can't test imms"); - } - if ((index.IsImm() && index.GetImmBits() != 8)) { - ASSERT_MSG(0, "WriteBitTest - illegal argument"); - } - if (bits == 16) - Write8(0x66); - if (index.IsImm()) { - dest.WriteRex(this, bits, bits); - Write8(0x0F); - Write8(0xBA); - dest.WriteRest(this, 1, (X64Reg)ext); - Write8((u8)index.offset); - } else { - X64Reg operand = index.GetSimpleReg(); - dest.WriteRex(this, bits, bits, operand); - Write8(0x0F); - Write8(0x83 + 8 * ext); - dest.WriteRest(this, 1, operand); - } -} - -void XEmitter::BT(int bits, const OpArg& dest, const OpArg& index) { - WriteBitTest(bits, dest, index, 4); -} -void XEmitter::BTS(int bits, const OpArg& dest, const OpArg& index) { - WriteBitTest(bits, dest, index, 5); -} -void XEmitter::BTR(int bits, const OpArg& dest, const OpArg& index) { - WriteBitTest(bits, dest, index, 6); -} -void XEmitter::BTC(int bits, const OpArg& dest, const OpArg& index) { - WriteBitTest(bits, dest, index, 7); -} - -// shift can be either imm8 or cl -void XEmitter::SHRD(int bits, const OpArg& dest, const OpArg& src, const OpArg& shift) { - CheckFlags(); - if (dest.IsImm()) { - ASSERT_MSG(0, "SHRD - can't use imms as destination"); - } - if (!src.IsSimpleReg()) { - ASSERT_MSG(0, "SHRD - must use simple register as source"); - } - if ((shift.IsSimpleReg() && shift.GetSimpleReg() != ECX) || - (shift.IsImm() && shift.GetImmBits() != 8)) { - ASSERT_MSG(0, "SHRD - illegal shift"); - } - if (bits == 16) - Write8(0x66); - X64Reg operand = src.GetSimpleReg(); - dest.WriteRex(this, bits, bits, operand); - if (shift.GetImmBits() == 8) { - Write8(0x0F); - Write8(0xAC); - dest.WriteRest(this, 1, operand); - Write8((u8)shift.offset); - } else { - Write8(0x0F); - Write8(0xAD); - dest.WriteRest(this, 0, operand); - } -} - -void XEmitter::SHLD(int bits, const OpArg& dest, const OpArg& src, const OpArg& shift) { - CheckFlags(); - if (dest.IsImm()) { - ASSERT_MSG(0, "SHLD - can't use imms as destination"); - } - if (!src.IsSimpleReg()) { - ASSERT_MSG(0, "SHLD - must use simple register as source"); - } - if ((shift.IsSimpleReg() && shift.GetSimpleReg() != ECX) || - (shift.IsImm() && shift.GetImmBits() != 8)) { - ASSERT_MSG(0, "SHLD - illegal shift"); - } - if (bits == 16) - Write8(0x66); - X64Reg operand = src.GetSimpleReg(); - dest.WriteRex(this, bits, bits, operand); - if (shift.GetImmBits() == 8) { - Write8(0x0F); - Write8(0xA4); - dest.WriteRest(this, 1, operand); - Write8((u8)shift.offset); - } else { - Write8(0x0F); - Write8(0xA5); - dest.WriteRest(this, 0, operand); - } -} - -void OpArg::WriteSingleByteOp(XEmitter* emit, u8 op, X64Reg _operandReg, int bits) { - if (bits == 16) - emit->Write8(0x66); - - this->operandReg = (u8)_operandReg; - WriteRex(emit, bits, bits); - emit->Write8(op); - WriteRest(emit); -} - -// operand can either be immediate or register -void OpArg::WriteNormalOp(XEmitter* emit, bool toRM, NormalOp op, const OpArg& operand, - int bits) const { - X64Reg _operandReg; - if (IsImm()) { - ASSERT_MSG(0, "WriteNormalOp - Imm argument, wrong order"); - } - - if (bits == 16) - emit->Write8(0x66); - - int immToWrite = 0; - - if (operand.IsImm()) { - WriteRex(emit, bits, bits); - - if (!toRM) { - ASSERT_MSG(0, "WriteNormalOp - Writing to Imm (!toRM)"); - } - - if (operand.scale == SCALE_IMM8 && bits == 8) { - // op al, imm8 - if (!scale && offsetOrBaseReg == AL && normalops[op].eaximm8 != 0xCC) { - emit->Write8(normalops[op].eaximm8); - emit->Write8((u8)operand.offset); - return; - } - // mov reg, imm8 - if (!scale && op == nrmMOV) { - emit->Write8(0xB0 + (offsetOrBaseReg & 7)); - emit->Write8((u8)operand.offset); - return; - } - // op r/m8, imm8 - emit->Write8(normalops[op].imm8); - immToWrite = 8; - } else if ((operand.scale == SCALE_IMM16 && bits == 16) || - (operand.scale == SCALE_IMM32 && bits == 32) || - (operand.scale == SCALE_IMM32 && bits == 64)) { - // Try to save immediate size if we can, but first check to see - // if the instruction supports simm8. - // op r/m, imm8 - if (normalops[op].simm8 != 0xCC && - ((operand.scale == SCALE_IMM16 && (s16)operand.offset == (s8)operand.offset) || - (operand.scale == SCALE_IMM32 && (s32)operand.offset == (s8)operand.offset))) { - emit->Write8(normalops[op].simm8); - immToWrite = 8; - } else { - // mov reg, imm - if (!scale && op == nrmMOV && bits != 64) { - emit->Write8(0xB8 + (offsetOrBaseReg & 7)); - if (bits == 16) - emit->Write16((u16)operand.offset); - else - emit->Write32((u32)operand.offset); - return; - } - // op eax, imm - if (!scale && offsetOrBaseReg == EAX && normalops[op].eaximm32 != 0xCC) { - emit->Write8(normalops[op].eaximm32); - if (bits == 16) - emit->Write16((u16)operand.offset); - else - emit->Write32((u32)operand.offset); - return; - } - // op r/m, imm - emit->Write8(normalops[op].imm32); - immToWrite = bits == 16 ? 16 : 32; - } - } else if ((operand.scale == SCALE_IMM8 && bits == 16) || - (operand.scale == SCALE_IMM8 && bits == 32) || - (operand.scale == SCALE_IMM8 && bits == 64)) { - // op r/m, imm8 - emit->Write8(normalops[op].simm8); - immToWrite = 8; - } else if (operand.scale == SCALE_IMM64 && bits == 64) { - if (scale) { - ASSERT_MSG(0, "WriteNormalOp - MOV with 64-bit imm requres register destination"); - } - // mov reg64, imm64 - else if (op == nrmMOV) { - emit->Write8(0xB8 + (offsetOrBaseReg & 7)); - emit->Write64((u64)operand.offset); - return; - } - ASSERT_MSG(0, "WriteNormalOp - Only MOV can take 64-bit imm"); - } else { - ASSERT_MSG(0, "WriteNormalOp - Unhandled case"); - } - _operandReg = (X64Reg)normalops[op].ext; // pass extension in REG of ModRM - } else { - _operandReg = (X64Reg)operand.offsetOrBaseReg; - WriteRex(emit, bits, bits, _operandReg); - // op r/m, reg - if (toRM) { - emit->Write8(bits == 8 ? normalops[op].toRm8 : normalops[op].toRm32); - } - // op reg, r/m - else { - emit->Write8(bits == 8 ? normalops[op].fromRm8 : normalops[op].fromRm32); - } - } - WriteRest(emit, immToWrite >> 3, _operandReg); - switch (immToWrite) { - case 0: - break; - case 8: - emit->Write8((u8)operand.offset); - break; - case 16: - emit->Write16((u16)operand.offset); - break; - case 32: - emit->Write32((u32)operand.offset); - break; - default: - ASSERT_MSG(0, "WriteNormalOp - Unhandled case"); - } -} - -void XEmitter::WriteNormalOp(XEmitter* emit, int bits, NormalOp op, const OpArg& a1, - const OpArg& a2) { - if (a1.IsImm()) { - // Booh! Can't write to an imm - ASSERT_MSG(0, "WriteNormalOp - a1 cannot be imm"); - return; - } - if (a2.IsImm()) { - a1.WriteNormalOp(emit, true, op, a2, bits); - } else { - if (a1.IsSimpleReg()) { - a2.WriteNormalOp(emit, false, op, a1, bits); - } else { - ASSERT_MSG(a2.IsSimpleReg() || a2.IsImm(), - "WriteNormalOp - a1 and a2 cannot both be memory"); - a1.WriteNormalOp(emit, true, op, a2, bits); - } - } -} - -void XEmitter::ADD(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmADD, a1, a2); -} -void XEmitter::ADC(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmADC, a1, a2); -} -void XEmitter::SUB(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmSUB, a1, a2); -} -void XEmitter::SBB(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmSBB, a1, a2); -} -void XEmitter::AND(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmAND, a1, a2); -} -void XEmitter::OR(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmOR, a1, a2); -} -void XEmitter::XOR(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmXOR, a1, a2); -} -void XEmitter::MOV(int bits, const OpArg& a1, const OpArg& a2) { - if (a1.IsSimpleReg() && a2.IsSimpleReg() && a1.GetSimpleReg() == a2.GetSimpleReg()) - LOG_ERROR(Common, "Redundant MOV @ %p - bug in JIT?", code); - WriteNormalOp(this, bits, nrmMOV, a1, a2); -} -void XEmitter::TEST(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmTEST, a1, a2); -} -void XEmitter::CMP(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmCMP, a1, a2); -} -void XEmitter::XCHG(int bits, const OpArg& a1, const OpArg& a2) { - WriteNormalOp(this, bits, nrmXCHG, a1, a2); -} - -void XEmitter::IMUL(int bits, X64Reg regOp, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - if (bits == 8) { - ASSERT_MSG(0, "IMUL - illegal bit size!"); - return; - } - - if (a1.IsImm()) { - ASSERT_MSG(0, "IMUL - second arg cannot be imm!"); - return; - } - - if (!a2.IsImm()) { - ASSERT_MSG(0, "IMUL - third arg must be imm!"); - return; - } - - if (bits == 16) - Write8(0x66); - a1.WriteRex(this, bits, bits, regOp); - - if (a2.GetImmBits() == 8 || (a2.GetImmBits() == 16 && (s8)a2.offset == (s16)a2.offset) || - (a2.GetImmBits() == 32 && (s8)a2.offset == (s32)a2.offset)) { - Write8(0x6B); - a1.WriteRest(this, 1, regOp); - Write8((u8)a2.offset); - } else { - Write8(0x69); - if (a2.GetImmBits() == 16 && bits == 16) { - a1.WriteRest(this, 2, regOp); - Write16((u16)a2.offset); - } else if (a2.GetImmBits() == 32 && (bits == 32 || bits == 64)) { - a1.WriteRest(this, 4, regOp); - Write32((u32)a2.offset); - } else { - ASSERT_MSG(0, "IMUL - unhandled case!"); - } - } -} - -void XEmitter::IMUL(int bits, X64Reg regOp, const OpArg& a) { - CheckFlags(); - if (bits == 8) { - ASSERT_MSG(0, "IMUL - illegal bit size!"); - return; - } - - if (a.IsImm()) { - IMUL(bits, regOp, R(regOp), a); - return; - } - - if (bits == 16) - Write8(0x66); - a.WriteRex(this, bits, bits, regOp); - Write8(0x0F); - Write8(0xAF); - a.WriteRest(this, 0, regOp); -} - -void XEmitter::WriteSSEOp(u8 opPrefix, u16 op, X64Reg regOp, OpArg arg, int extrabytes) { - if (opPrefix) - Write8(opPrefix); - arg.operandReg = regOp; - arg.WriteRex(this, 0, 0); - Write8(0x0F); - if (op > 0xFF) - Write8((op >> 8) & 0xFF); - Write8(op & 0xFF); - arg.WriteRest(this, extrabytes); -} - -void XEmitter::WriteAVXOp(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes) { - WriteAVXOp(opPrefix, op, regOp, INVALID_REG, arg, extrabytes); -} - -static int GetVEXmmmmm(u16 op) { - // Currently, only 0x38 and 0x3A are used as secondary escape byte. - if ((op >> 8) == 0x3A) - return 3; - if ((op >> 8) == 0x38) - return 2; - - return 1; -} - -static int GetVEXpp(u8 opPrefix) { - if (opPrefix == 0x66) - return 1; - if (opPrefix == 0xF3) - return 2; - if (opPrefix == 0xF2) - return 3; - - return 0; -} - -void XEmitter::WriteAVXOp(u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg, - int extrabytes) { - if (!Common::GetCPUCaps().avx) - ASSERT_MSG(0, "Trying to use AVX on a system that doesn't support it. Bad programmer."); - int mmmmm = GetVEXmmmmm(op); - int pp = GetVEXpp(opPrefix); - // FIXME: we currently don't support 256-bit instructions, and "size" is not the vector size - // here - arg.WriteVex(this, regOp1, regOp2, 0, pp, mmmmm); - Write8(op & 0xFF); - arg.WriteRest(this, extrabytes, regOp1); -} - -// Like the above, but more general; covers GPR-based VEX operations, like BMI1/2 -void XEmitter::WriteVEXOp(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, - const OpArg& arg, int extrabytes) { - if (size != 32 && size != 64) - ASSERT_MSG(0, "VEX GPR instructions only support 32-bit and 64-bit modes!"); - int mmmmm = GetVEXmmmmm(op); - int pp = GetVEXpp(opPrefix); - arg.WriteVex(this, regOp1, regOp2, 0, pp, mmmmm, size == 64); - Write8(op & 0xFF); - arg.WriteRest(this, extrabytes, regOp1); -} - -void XEmitter::WriteBMI1Op(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, - const OpArg& arg, int extrabytes) { - CheckFlags(); - if (!Common::GetCPUCaps().bmi1) - ASSERT_MSG(0, "Trying to use BMI1 on a system that doesn't support it. Bad programmer."); - WriteVEXOp(size, opPrefix, op, regOp1, regOp2, arg, extrabytes); -} - -void XEmitter::WriteBMI2Op(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, - const OpArg& arg, int extrabytes) { - CheckFlags(); - if (!Common::GetCPUCaps().bmi2) - ASSERT_MSG(0, "Trying to use BMI2 on a system that doesn't support it. Bad programmer."); - WriteVEXOp(size, opPrefix, op, regOp1, regOp2, arg, extrabytes); -} - -void XEmitter::MOVD_xmm(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x6E, dest, arg, 0); -} -void XEmitter::MOVD_xmm(const OpArg& arg, X64Reg src) { - WriteSSEOp(0x66, 0x7E, src, arg, 0); -} - -void XEmitter::MOVQ_xmm(X64Reg dest, OpArg arg) { -#ifdef ARCHITECTURE_x86_64 - // Alternate encoding - // This does not display correctly in MSVC's debugger, it thinks it's a MOVD - arg.operandReg = dest; - Write8(0x66); - arg.WriteRex(this, 64, 0); - Write8(0x0f); - Write8(0x6E); - arg.WriteRest(this, 0); -#else - arg.operandReg = dest; - Write8(0xF3); - Write8(0x0f); - Write8(0x7E); - arg.WriteRest(this, 0); -#endif -} - -void XEmitter::MOVQ_xmm(OpArg arg, X64Reg src) { - if (src > 7 || arg.IsSimpleReg()) { - // Alternate encoding - // This does not display correctly in MSVC's debugger, it thinks it's a MOVD - arg.operandReg = src; - Write8(0x66); - arg.WriteRex(this, 64, 0); - Write8(0x0f); - Write8(0x7E); - arg.WriteRest(this, 0); - } else { - arg.operandReg = src; - arg.WriteRex(this, 0, 0); - Write8(0x66); - Write8(0x0f); - Write8(0xD6); - arg.WriteRest(this, 0); - } -} - -void XEmitter::WriteMXCSR(OpArg arg, int ext) { - if (arg.IsImm() || arg.IsSimpleReg()) - ASSERT_MSG(0, "MXCSR - invalid operand"); - - arg.operandReg = ext; - arg.WriteRex(this, 0, 0); - Write8(0x0F); - Write8(0xAE); - arg.WriteRest(this); -} - -void XEmitter::STMXCSR(const OpArg& memloc) { - WriteMXCSR(memloc, 3); -} -void XEmitter::LDMXCSR(const OpArg& memloc) { - WriteMXCSR(memloc, 2); -} - -void XEmitter::MOVNTDQ(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x66, sseMOVNTDQ, regOp, arg); -} -void XEmitter::MOVNTPS(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x00, sseMOVNTP, regOp, arg); -} -void XEmitter::MOVNTPD(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x66, sseMOVNTP, regOp, arg); -} - -void XEmitter::ADDSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseADD, regOp, arg); -} -void XEmitter::ADDSD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseADD, regOp, arg); -} -void XEmitter::SUBSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseSUB, regOp, arg); -} -void XEmitter::SUBSD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseSUB, regOp, arg); -} -void XEmitter::CMPSS(X64Reg regOp, const OpArg& arg, u8 compare) { - WriteSSEOp(0xF3, sseCMP, regOp, arg, 1); - Write8(compare); -} -void XEmitter::CMPSD(X64Reg regOp, const OpArg& arg, u8 compare) { - WriteSSEOp(0xF2, sseCMP, regOp, arg, 1); - Write8(compare); -} -void XEmitter::MULSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseMUL, regOp, arg); -} -void XEmitter::MULSD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseMUL, regOp, arg); -} -void XEmitter::DIVSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseDIV, regOp, arg); -} -void XEmitter::DIVSD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseDIV, regOp, arg); -} -void XEmitter::MINSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseMIN, regOp, arg); -} -void XEmitter::MINSD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseMIN, regOp, arg); -} -void XEmitter::MAXSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseMAX, regOp, arg); -} -void XEmitter::MAXSD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseMAX, regOp, arg); -} -void XEmitter::SQRTSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseSQRT, regOp, arg); -} -void XEmitter::SQRTSD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseSQRT, regOp, arg); -} -void XEmitter::RCPSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseRCP, regOp, arg); -} -void XEmitter::RSQRTSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseRSQRT, regOp, arg); -} - -void XEmitter::ADDPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseADD, regOp, arg); -} -void XEmitter::ADDPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseADD, regOp, arg); -} -void XEmitter::SUBPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseSUB, regOp, arg); -} -void XEmitter::SUBPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseSUB, regOp, arg); -} -void XEmitter::CMPPS(X64Reg regOp, const OpArg& arg, u8 compare) { - WriteSSEOp(0x00, sseCMP, regOp, arg, 1); - Write8(compare); -} -void XEmitter::CMPPD(X64Reg regOp, const OpArg& arg, u8 compare) { - WriteSSEOp(0x66, sseCMP, regOp, arg, 1); - Write8(compare); -} -void XEmitter::ANDPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseAND, regOp, arg); -} -void XEmitter::ANDPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseAND, regOp, arg); -} -void XEmitter::ANDNPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseANDN, regOp, arg); -} -void XEmitter::ANDNPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseANDN, regOp, arg); -} -void XEmitter::ORPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseOR, regOp, arg); -} -void XEmitter::ORPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseOR, regOp, arg); -} -void XEmitter::XORPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseXOR, regOp, arg); -} -void XEmitter::XORPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseXOR, regOp, arg); -} -void XEmitter::MULPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseMUL, regOp, arg); -} -void XEmitter::MULPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseMUL, regOp, arg); -} -void XEmitter::DIVPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseDIV, regOp, arg); -} -void XEmitter::DIVPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseDIV, regOp, arg); -} -void XEmitter::MINPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseMIN, regOp, arg); -} -void XEmitter::MINPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseMIN, regOp, arg); -} -void XEmitter::MAXPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseMAX, regOp, arg); -} -void XEmitter::MAXPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseMAX, regOp, arg); -} -void XEmitter::SQRTPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseSQRT, regOp, arg); -} -void XEmitter::SQRTPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseSQRT, regOp, arg); -} -void XEmitter::RCPPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseRCP, regOp, arg); -} -void XEmitter::RSQRTPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseRSQRT, regOp, arg); -} -void XEmitter::SHUFPS(X64Reg regOp, const OpArg& arg, u8 shuffle) { - WriteSSEOp(0x00, sseSHUF, regOp, arg, 1); - Write8(shuffle); -} -void XEmitter::SHUFPD(X64Reg regOp, const OpArg& arg, u8 shuffle) { - WriteSSEOp(0x66, sseSHUF, regOp, arg, 1); - Write8(shuffle); -} - -void XEmitter::HADDPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseHADD, regOp, arg); -} - -void XEmitter::COMISS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseCOMIS, regOp, arg); -} // weird that these should be packed -void XEmitter::COMISD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseCOMIS, regOp, arg); -} // ordered -void XEmitter::UCOMISS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseUCOMIS, regOp, arg); -} // unordered -void XEmitter::UCOMISD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseUCOMIS, regOp, arg); -} - -void XEmitter::MOVAPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseMOVAPfromRM, regOp, arg); -} -void XEmitter::MOVAPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseMOVAPfromRM, regOp, arg); -} -void XEmitter::MOVAPS(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x00, sseMOVAPtoRM, regOp, arg); -} -void XEmitter::MOVAPD(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x66, sseMOVAPtoRM, regOp, arg); -} - -void XEmitter::MOVUPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseMOVUPfromRM, regOp, arg); -} -void XEmitter::MOVUPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseMOVUPfromRM, regOp, arg); -} -void XEmitter::MOVUPS(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x00, sseMOVUPtoRM, regOp, arg); -} -void XEmitter::MOVUPD(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x66, sseMOVUPtoRM, regOp, arg); -} - -void XEmitter::MOVDQA(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseMOVDQfromRM, regOp, arg); -} -void XEmitter::MOVDQA(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x66, sseMOVDQtoRM, regOp, arg); -} -void XEmitter::MOVDQU(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseMOVDQfromRM, regOp, arg); -} -void XEmitter::MOVDQU(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0xF3, sseMOVDQtoRM, regOp, arg); -} - -void XEmitter::MOVSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseMOVUPfromRM, regOp, arg); -} -void XEmitter::MOVSD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseMOVUPfromRM, regOp, arg); -} -void XEmitter::MOVSS(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0xF3, sseMOVUPtoRM, regOp, arg); -} -void XEmitter::MOVSD(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0xF2, sseMOVUPtoRM, regOp, arg); -} - -void XEmitter::MOVLPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseMOVLPfromRM, regOp, arg); -} -void XEmitter::MOVLPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseMOVLPfromRM, regOp, arg); -} -void XEmitter::MOVLPS(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x00, sseMOVLPtoRM, regOp, arg); -} -void XEmitter::MOVLPD(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x66, sseMOVLPtoRM, regOp, arg); -} - -void XEmitter::MOVHPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseMOVHPfromRM, regOp, arg); -} -void XEmitter::MOVHPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseMOVHPfromRM, regOp, arg); -} -void XEmitter::MOVHPS(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x00, sseMOVHPtoRM, regOp, arg); -} -void XEmitter::MOVHPD(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x66, sseMOVHPtoRM, regOp, arg); -} - -void XEmitter::MOVHLPS(X64Reg regOp1, X64Reg regOp2) { - WriteSSEOp(0x00, sseMOVHLPS, regOp1, R(regOp2)); -} -void XEmitter::MOVLHPS(X64Reg regOp1, X64Reg regOp2) { - WriteSSEOp(0x00, sseMOVLHPS, regOp1, R(regOp2)); -} - -void XEmitter::CVTPS2PD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, 0x5A, regOp, arg); -} -void XEmitter::CVTPD2PS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, 0x5A, regOp, arg); -} - -void XEmitter::CVTSD2SS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, 0x5A, regOp, arg); -} -void XEmitter::CVTSS2SD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, 0x5A, regOp, arg); -} -void XEmitter::CVTSD2SI(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, 0x2D, regOp, arg); -} -void XEmitter::CVTSS2SI(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, 0x2D, regOp, arg); -} -void XEmitter::CVTSI2SD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, 0x2A, regOp, arg); -} -void XEmitter::CVTSI2SS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, 0x2A, regOp, arg); -} - -void XEmitter::CVTDQ2PD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, 0xE6, regOp, arg); -} -void XEmitter::CVTDQ2PS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, 0x5B, regOp, arg); -} -void XEmitter::CVTPD2DQ(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, 0xE6, regOp, arg); -} -void XEmitter::CVTPS2DQ(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, 0x5B, regOp, arg); -} - -void XEmitter::CVTTSD2SI(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, 0x2C, regOp, arg); -} -void XEmitter::CVTTSS2SI(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, 0x2C, regOp, arg); -} -void XEmitter::CVTTPS2DQ(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, 0x5B, regOp, arg); -} -void XEmitter::CVTTPD2DQ(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, 0xE6, regOp, arg); -} - -void XEmitter::MASKMOVDQU(X64Reg dest, X64Reg src) { - WriteSSEOp(0x66, sseMASKMOVDQU, dest, R(src)); -} - -void XEmitter::MOVMSKPS(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x00, 0x50, dest, arg); -} -void XEmitter::MOVMSKPD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x50, dest, arg); -} - -void XEmitter::LDDQU(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0xF2, sseLDDQU, dest, arg); -} // For integer data only - -// THESE TWO ARE UNTESTED. -void XEmitter::UNPCKLPS(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x00, 0x14, dest, arg); -} -void XEmitter::UNPCKHPS(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x00, 0x15, dest, arg); -} - -void XEmitter::UNPCKLPD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x14, dest, arg); -} -void XEmitter::UNPCKHPD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x15, dest, arg); -} - -void XEmitter::MOVDDUP(X64Reg regOp, const OpArg& arg) { - if (Common::GetCPUCaps().sse3) { - WriteSSEOp(0xF2, 0x12, regOp, arg); // SSE3 movddup - } else { - // Simulate this instruction with SSE2 instructions - if (!arg.IsSimpleReg(regOp)) - MOVSD(regOp, arg); - UNPCKLPD(regOp, R(regOp)); - } -} - -// There are a few more left - -// Also some integer instructions are missing -void XEmitter::PACKSSDW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x6B, dest, arg); -} -void XEmitter::PACKSSWB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x63, dest, arg); -} -void XEmitter::PACKUSWB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x67, dest, arg); -} - -void XEmitter::PUNPCKLBW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x60, dest, arg); -} -void XEmitter::PUNPCKLWD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x61, dest, arg); -} -void XEmitter::PUNPCKLDQ(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x62, dest, arg); -} -void XEmitter::PUNPCKLQDQ(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x6C, dest, arg); -} - -void XEmitter::PSRLW(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x71, (X64Reg)2, R(reg)); - Write8(shift); -} - -void XEmitter::PSRLD(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x72, (X64Reg)2, R(reg)); - Write8(shift); -} - -void XEmitter::PSRLQ(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x73, (X64Reg)2, R(reg)); - Write8(shift); -} - -void XEmitter::PSRLQ(X64Reg reg, const OpArg& arg) { - WriteSSEOp(0x66, 0xd3, reg, arg); -} - -void XEmitter::PSRLDQ(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x73, (X64Reg)3, R(reg)); - Write8(shift); -} - -void XEmitter::PSLLW(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x71, (X64Reg)6, R(reg)); - Write8(shift); -} - -void XEmitter::PSLLD(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x72, (X64Reg)6, R(reg)); - Write8(shift); -} - -void XEmitter::PSLLQ(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x73, (X64Reg)6, R(reg)); - Write8(shift); -} - -void XEmitter::PSLLDQ(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x73, (X64Reg)7, R(reg)); - Write8(shift); -} - -void XEmitter::PSRAW(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x71, (X64Reg)4, R(reg)); - Write8(shift); -} - -void XEmitter::PSRAD(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x72, (X64Reg)4, R(reg)); - Write8(shift); -} - -void XEmitter::WriteSSSE3Op(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes) { - if (!Common::GetCPUCaps().ssse3) - ASSERT_MSG(0, "Trying to use SSSE3 on a system that doesn't support it. Bad programmer."); - WriteSSEOp(opPrefix, op, regOp, arg, extrabytes); -} - -void XEmitter::WriteSSE41Op(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes) { - if (!Common::GetCPUCaps().sse4_1) - ASSERT_MSG(0, "Trying to use SSE4.1 on a system that doesn't support it. Bad programmer."); - WriteSSEOp(opPrefix, op, regOp, arg, extrabytes); -} - -void XEmitter::PSHUFB(X64Reg dest, const OpArg& arg) { - WriteSSSE3Op(0x66, 0x3800, dest, arg); -} -void XEmitter::PTEST(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3817, dest, arg); -} -void XEmitter::PACKUSDW(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x382b, dest, arg); -} -void XEmitter::DPPS(X64Reg dest, const OpArg& arg, u8 mask) { - WriteSSE41Op(0x66, 0x3A40, dest, arg, 1); - Write8(mask); -} - -void XEmitter::PMINSB(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3838, dest, arg); -} -void XEmitter::PMINSD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3839, dest, arg); -} -void XEmitter::PMINUW(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x383a, dest, arg); -} -void XEmitter::PMINUD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x383b, dest, arg); -} -void XEmitter::PMAXSB(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x383c, dest, arg); -} -void XEmitter::PMAXSD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x383d, dest, arg); -} -void XEmitter::PMAXUW(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x383e, dest, arg); -} -void XEmitter::PMAXUD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x383f, dest, arg); -} - -void XEmitter::PMOVSXBW(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3820, dest, arg); -} -void XEmitter::PMOVSXBD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3821, dest, arg); -} -void XEmitter::PMOVSXBQ(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3822, dest, arg); -} -void XEmitter::PMOVSXWD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3823, dest, arg); -} -void XEmitter::PMOVSXWQ(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3824, dest, arg); -} -void XEmitter::PMOVSXDQ(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3825, dest, arg); -} -void XEmitter::PMOVZXBW(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3830, dest, arg); -} -void XEmitter::PMOVZXBD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3831, dest, arg); -} -void XEmitter::PMOVZXBQ(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3832, dest, arg); -} -void XEmitter::PMOVZXWD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3833, dest, arg); -} -void XEmitter::PMOVZXWQ(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3834, dest, arg); -} -void XEmitter::PMOVZXDQ(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3835, dest, arg); -} - -void XEmitter::PBLENDVB(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3810, dest, arg); -} -void XEmitter::BLENDVPS(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3814, dest, arg); -} -void XEmitter::BLENDVPD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3815, dest, arg); -} -void XEmitter::BLENDPS(X64Reg dest, const OpArg& arg, u8 blend) { - WriteSSE41Op(0x66, 0x3A0C, dest, arg, 1); - Write8(blend); -} -void XEmitter::BLENDPD(X64Reg dest, const OpArg& arg, u8 blend) { - WriteSSE41Op(0x66, 0x3A0D, dest, arg, 1); - Write8(blend); -} - -void XEmitter::ROUNDSS(X64Reg dest, const OpArg& arg, u8 mode) { - WriteSSE41Op(0x66, 0x3A0A, dest, arg, 1); - Write8(mode); -} -void XEmitter::ROUNDSD(X64Reg dest, const OpArg& arg, u8 mode) { - WriteSSE41Op(0x66, 0x3A0B, dest, arg, 1); - Write8(mode); -} -void XEmitter::ROUNDPS(X64Reg dest, const OpArg& arg, u8 mode) { - WriteSSE41Op(0x66, 0x3A08, dest, arg, 1); - Write8(mode); -} -void XEmitter::ROUNDPD(X64Reg dest, const OpArg& arg, u8 mode) { - WriteSSE41Op(0x66, 0x3A09, dest, arg, 1); - Write8(mode); -} - -void XEmitter::PAND(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xDB, dest, arg); -} -void XEmitter::PANDN(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xDF, dest, arg); -} -void XEmitter::PXOR(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xEF, dest, arg); -} -void XEmitter::POR(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xEB, dest, arg); -} - -void XEmitter::PADDB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xFC, dest, arg); -} -void XEmitter::PADDW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xFD, dest, arg); -} -void XEmitter::PADDD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xFE, dest, arg); -} -void XEmitter::PADDQ(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xD4, dest, arg); -} - -void XEmitter::PADDSB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xEC, dest, arg); -} -void XEmitter::PADDSW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xED, dest, arg); -} -void XEmitter::PADDUSB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xDC, dest, arg); -} -void XEmitter::PADDUSW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xDD, dest, arg); -} - -void XEmitter::PSUBB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xF8, dest, arg); -} -void XEmitter::PSUBW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xF9, dest, arg); -} -void XEmitter::PSUBD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xFA, dest, arg); -} -void XEmitter::PSUBQ(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xFB, dest, arg); -} - -void XEmitter::PSUBSB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xE8, dest, arg); -} -void XEmitter::PSUBSW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xE9, dest, arg); -} -void XEmitter::PSUBUSB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xD8, dest, arg); -} -void XEmitter::PSUBUSW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xD9, dest, arg); -} - -void XEmitter::PAVGB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xE0, dest, arg); -} -void XEmitter::PAVGW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xE3, dest, arg); -} - -void XEmitter::PCMPEQB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x74, dest, arg); -} -void XEmitter::PCMPEQW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x75, dest, arg); -} -void XEmitter::PCMPEQD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x76, dest, arg); -} - -void XEmitter::PCMPGTB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x64, dest, arg); -} -void XEmitter::PCMPGTW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x65, dest, arg); -} -void XEmitter::PCMPGTD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x66, dest, arg); -} - -void XEmitter::PEXTRW(X64Reg dest, const OpArg& arg, u8 subreg) { - WriteSSEOp(0x66, 0xC5, dest, arg, 1); - Write8(subreg); -} -void XEmitter::PINSRW(X64Reg dest, const OpArg& arg, u8 subreg) { - WriteSSEOp(0x66, 0xC4, dest, arg, 1); - Write8(subreg); -} - -void XEmitter::PMADDWD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xF5, dest, arg); -} -void XEmitter::PSADBW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xF6, dest, arg); -} - -void XEmitter::PMAXSW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xEE, dest, arg); -} -void XEmitter::PMAXUB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xDE, dest, arg); -} -void XEmitter::PMINSW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xEA, dest, arg); -} -void XEmitter::PMINUB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xDA, dest, arg); -} - -void XEmitter::PMOVMSKB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xD7, dest, arg); -} -void XEmitter::PSHUFD(X64Reg regOp, const OpArg& arg, u8 shuffle) { - WriteSSEOp(0x66, 0x70, regOp, arg, 1); - Write8(shuffle); -} -void XEmitter::PSHUFLW(X64Reg regOp, const OpArg& arg, u8 shuffle) { - WriteSSEOp(0xF2, 0x70, regOp, arg, 1); - Write8(shuffle); -} -void XEmitter::PSHUFHW(X64Reg regOp, const OpArg& arg, u8 shuffle) { - WriteSSEOp(0xF3, 0x70, regOp, arg, 1); - Write8(shuffle); -} - -// VEX -void XEmitter::VADDSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0xF2, sseADD, regOp1, regOp2, arg); -} -void XEmitter::VSUBSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0xF2, sseSUB, regOp1, regOp2, arg); -} -void XEmitter::VMULSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0xF2, sseMUL, regOp1, regOp2, arg); -} -void XEmitter::VDIVSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0xF2, sseDIV, regOp1, regOp2, arg); -} -void XEmitter::VADDPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, sseADD, regOp1, regOp2, arg); -} -void XEmitter::VSUBPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, sseSUB, regOp1, regOp2, arg); -} -void XEmitter::VMULPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, sseMUL, regOp1, regOp2, arg); -} -void XEmitter::VDIVPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, sseDIV, regOp1, regOp2, arg); -} -void XEmitter::VSQRTSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0xF2, sseSQRT, regOp1, regOp2, arg); -} -void XEmitter::VSHUFPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg, u8 shuffle) { - WriteAVXOp(0x66, sseSHUF, regOp1, regOp2, arg, 1); - Write8(shuffle); -} -void XEmitter::VUNPCKLPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x14, regOp1, regOp2, arg); -} -void XEmitter::VUNPCKHPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x15, regOp1, regOp2, arg); -} - -void XEmitter::VANDPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x00, sseAND, regOp1, regOp2, arg); -} -void XEmitter::VANDPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, sseAND, regOp1, regOp2, arg); -} -void XEmitter::VANDNPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x00, sseANDN, regOp1, regOp2, arg); -} -void XEmitter::VANDNPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, sseANDN, regOp1, regOp2, arg); -} -void XEmitter::VORPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x00, sseOR, regOp1, regOp2, arg); -} -void XEmitter::VORPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, sseOR, regOp1, regOp2, arg); -} -void XEmitter::VXORPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x00, sseXOR, regOp1, regOp2, arg); -} -void XEmitter::VXORPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, sseXOR, regOp1, regOp2, arg); -} - -void XEmitter::VPAND(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0xDB, regOp1, regOp2, arg); -} -void XEmitter::VPANDN(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0xDF, regOp1, regOp2, arg); -} -void XEmitter::VPOR(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0xEB, regOp1, regOp2, arg); -} -void XEmitter::VPXOR(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0xEF, regOp1, regOp2, arg); -} - -void XEmitter::VFMADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x3898, regOp1, regOp2, arg); -} -void XEmitter::VFMADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38A8, regOp1, regOp2, arg); -} -void XEmitter::VFMADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38B8, regOp1, regOp2, arg); -} -void XEmitter::VFMADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x3898, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38A8, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38B8, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMADD132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x3899, regOp1, regOp2, arg); -} -void XEmitter::VFMADD213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38A9, regOp1, regOp2, arg); -} -void XEmitter::VFMADD231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38B9, regOp1, regOp2, arg); -} -void XEmitter::VFMADD132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x3899, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMADD213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38A9, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMADD231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38B9, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389A, regOp1, regOp2, arg); -} -void XEmitter::VFMSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AA, regOp1, regOp2, arg); -} -void XEmitter::VFMSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BA, regOp1, regOp2, arg); -} -void XEmitter::VFMSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389A, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AA, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BA, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUB132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389B, regOp1, regOp2, arg); -} -void XEmitter::VFMSUB213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AB, regOp1, regOp2, arg); -} -void XEmitter::VFMSUB231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BB, regOp1, regOp2, arg); -} -void XEmitter::VFMSUB132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389B, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUB213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AB, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUB231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BB, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389C, regOp1, regOp2, arg); -} -void XEmitter::VFNMADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AC, regOp1, regOp2, arg); -} -void XEmitter::VFNMADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BC, regOp1, regOp2, arg); -} -void XEmitter::VFNMADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389C, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AC, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BC, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMADD132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389D, regOp1, regOp2, arg); -} -void XEmitter::VFNMADD213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AD, regOp1, regOp2, arg); -} -void XEmitter::VFNMADD231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BD, regOp1, regOp2, arg); -} -void XEmitter::VFNMADD132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389D, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMADD213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AD, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMADD231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BD, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389E, regOp1, regOp2, arg); -} -void XEmitter::VFNMSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AE, regOp1, regOp2, arg); -} -void XEmitter::VFNMSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BE, regOp1, regOp2, arg); -} -void XEmitter::VFNMSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389E, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AE, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BE, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMSUB132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389F, regOp1, regOp2, arg); -} -void XEmitter::VFNMSUB213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AF, regOp1, regOp2, arg); -} -void XEmitter::VFNMSUB231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BF, regOp1, regOp2, arg); -} -void XEmitter::VFNMSUB132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389F, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMSUB213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AF, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMSUB231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BF, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMADDSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x3896, regOp1, regOp2, arg); -} -void XEmitter::VFMADDSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38A6, regOp1, regOp2, arg); -} -void XEmitter::VFMADDSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38B6, regOp1, regOp2, arg); -} -void XEmitter::VFMADDSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x3896, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMADDSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38A6, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMADDSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38B6, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUBADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x3897, regOp1, regOp2, arg); -} -void XEmitter::VFMSUBADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38A7, regOp1, regOp2, arg); -} -void XEmitter::VFMSUBADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38B7, regOp1, regOp2, arg); -} -void XEmitter::VFMSUBADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x3897, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUBADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38A7, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUBADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38B7, regOp1, regOp2, arg, 1); -} - -void XEmitter::SARX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2) { - WriteBMI2Op(bits, 0xF3, 0x38F7, regOp1, regOp2, arg); -} -void XEmitter::SHLX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2) { - WriteBMI2Op(bits, 0x66, 0x38F7, regOp1, regOp2, arg); -} -void XEmitter::SHRX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2) { - WriteBMI2Op(bits, 0xF2, 0x38F7, regOp1, regOp2, arg); -} -void XEmitter::RORX(int bits, X64Reg regOp, const OpArg& arg, u8 rotate) { - WriteBMI2Op(bits, 0xF2, 0x3AF0, regOp, INVALID_REG, arg, 1); - Write8(rotate); -} -void XEmitter::PEXT(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteBMI2Op(bits, 0xF3, 0x38F5, regOp1, regOp2, arg); -} -void XEmitter::PDEP(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteBMI2Op(bits, 0xF2, 0x38F5, regOp1, regOp2, arg); -} -void XEmitter::MULX(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteBMI2Op(bits, 0xF2, 0x38F6, regOp2, regOp1, arg); -} -void XEmitter::BZHI(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2) { - WriteBMI2Op(bits, 0x00, 0x38F5, regOp1, regOp2, arg); -} -void XEmitter::BLSR(int bits, X64Reg regOp, const OpArg& arg) { - WriteBMI1Op(bits, 0x00, 0x38F3, (X64Reg)0x1, regOp, arg); -} -void XEmitter::BLSMSK(int bits, X64Reg regOp, const OpArg& arg) { - WriteBMI1Op(bits, 0x00, 0x38F3, (X64Reg)0x2, regOp, arg); -} -void XEmitter::BLSI(int bits, X64Reg regOp, const OpArg& arg) { - WriteBMI1Op(bits, 0x00, 0x38F3, (X64Reg)0x3, regOp, arg); -} -void XEmitter::BEXTR(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2) { - WriteBMI1Op(bits, 0x00, 0x38F7, regOp1, regOp2, arg); -} -void XEmitter::ANDN(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteBMI1Op(bits, 0x00, 0x38F2, regOp1, regOp2, arg); -} - -// Prefixes - -void XEmitter::LOCK() { - Write8(0xF0); -} -void XEmitter::REP() { - Write8(0xF3); -} -void XEmitter::REPNE() { - Write8(0xF2); -} -void XEmitter::FSOverride() { - Write8(0x64); -} -void XEmitter::GSOverride() { - Write8(0x65); -} - -void XEmitter::FWAIT() { - Write8(0x9B); -} - -// TODO: make this more generic -void XEmitter::WriteFloatLoadStore(int bits, FloatOp op, FloatOp op_80b, const OpArg& arg) { - int mf = 0; - ASSERT_MSG(!(bits == 80 && op_80b == floatINVALID), - "WriteFloatLoadStore: 80 bits not supported for this instruction"); - switch (bits) { - case 32: - mf = 0; - break; - case 64: - mf = 4; - break; - case 80: - mf = 2; - break; - default: - ASSERT_MSG(0, "WriteFloatLoadStore: invalid bits (should be 32/64/80)"); - } - Write8(0xd9 | mf); - // x87 instructions use the reg field of the ModR/M byte as opcode: - if (bits == 80) - op = op_80b; - arg.WriteRest(this, 0, (X64Reg)op); -} - -void XEmitter::FLD(int bits, const OpArg& src) { - WriteFloatLoadStore(bits, floatLD, floatLD80, src); -} -void XEmitter::FST(int bits, const OpArg& dest) { - WriteFloatLoadStore(bits, floatST, floatINVALID, dest); -} -void XEmitter::FSTP(int bits, const OpArg& dest) { - WriteFloatLoadStore(bits, floatSTP, floatSTP80, dest); -} -void XEmitter::FNSTSW_AX() { - Write8(0xDF); - Write8(0xE0); -} - -void XEmitter::RDTSC() { - Write8(0x0F); - Write8(0x31); -} - -void XCodeBlock::PoisonMemory() { - // x86/64: 0xCC = breakpoint - memset(region, 0xCC, region_size); -} -} diff --git a/src/common/x64/emitter.h b/src/common/x64/emitter.h deleted file mode 100644 index 7d7cdde16..000000000 --- a/src/common/x64/emitter.h +++ /dev/null @@ -1,1206 +0,0 @@ -// Copyright (C) 2003 Dolphin Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official SVN repository and contact information can be found at -// http://code.google.com/p/dolphin-emu/ - -#pragma once - -#include <cstddef> -#include "common/assert.h" -#include "common/bit_set.h" -#include "common/code_block.h" -#include "common/common_types.h" - -#if defined(ARCHITECTURE_x86_64) && !defined(_ARCH_64) -#define _ARCH_64 -#endif - -#ifdef _ARCH_64 -#define PTRBITS 64 -#else -#define PTRBITS 32 -#endif - -namespace Gen { - -enum X64Reg { - EAX = 0, - EBX = 3, - ECX = 1, - EDX = 2, - ESI = 6, - EDI = 7, - EBP = 5, - ESP = 4, - - RAX = 0, - RBX = 3, - RCX = 1, - RDX = 2, - RSI = 6, - RDI = 7, - RBP = 5, - RSP = 4, - R8 = 8, - R9 = 9, - R10 = 10, - R11 = 11, - R12 = 12, - R13 = 13, - R14 = 14, - R15 = 15, - - AL = 0, - BL = 3, - CL = 1, - DL = 2, - SIL = 6, - DIL = 7, - BPL = 5, - SPL = 4, - AH = 0x104, - BH = 0x107, - CH = 0x105, - DH = 0x106, - - AX = 0, - BX = 3, - CX = 1, - DX = 2, - SI = 6, - DI = 7, - BP = 5, - SP = 4, - - XMM0 = 0, - XMM1, - XMM2, - XMM3, - XMM4, - XMM5, - XMM6, - XMM7, - XMM8, - XMM9, - XMM10, - XMM11, - XMM12, - XMM13, - XMM14, - XMM15, - - YMM0 = 0, - YMM1, - YMM2, - YMM3, - YMM4, - YMM5, - YMM6, - YMM7, - YMM8, - YMM9, - YMM10, - YMM11, - YMM12, - YMM13, - YMM14, - YMM15, - - INVALID_REG = 0xFFFFFFFF -}; - -enum CCFlags { - CC_O = 0, - CC_NO = 1, - CC_B = 2, - CC_C = 2, - CC_NAE = 2, - CC_NB = 3, - CC_NC = 3, - CC_AE = 3, - CC_Z = 4, - CC_E = 4, - CC_NZ = 5, - CC_NE = 5, - CC_BE = 6, - CC_NA = 6, - CC_NBE = 7, - CC_A = 7, - CC_S = 8, - CC_NS = 9, - CC_P = 0xA, - CC_PE = 0xA, - CC_NP = 0xB, - CC_PO = 0xB, - CC_L = 0xC, - CC_NGE = 0xC, - CC_NL = 0xD, - CC_GE = 0xD, - CC_LE = 0xE, - CC_NG = 0xE, - CC_NLE = 0xF, - CC_G = 0xF -}; - -enum { - NUMGPRs = 16, - NUMXMMs = 16, -}; - -enum { - SCALE_NONE = 0, - SCALE_1 = 1, - SCALE_2 = 2, - SCALE_4 = 4, - SCALE_8 = 8, - SCALE_ATREG = 16, - // SCALE_NOBASE_1 is not supported and can be replaced with SCALE_ATREG - SCALE_NOBASE_2 = 34, - SCALE_NOBASE_4 = 36, - SCALE_NOBASE_8 = 40, - SCALE_RIP = 0xFF, - SCALE_IMM8 = 0xF0, - SCALE_IMM16 = 0xF1, - SCALE_IMM32 = 0xF2, - SCALE_IMM64 = 0xF3, -}; - -enum NormalOp { - nrmADD, - nrmADC, - nrmSUB, - nrmSBB, - nrmAND, - nrmOR, - nrmXOR, - nrmMOV, - nrmTEST, - nrmCMP, - nrmXCHG, -}; - -enum { - CMP_EQ = 0, - CMP_LT = 1, - CMP_LE = 2, - CMP_UNORD = 3, - CMP_NEQ = 4, - CMP_NLT = 5, - CMP_NLE = 6, - CMP_ORD = 7, -}; - -enum FloatOp { - floatLD = 0, - floatST = 2, - floatSTP = 3, - floatLD80 = 5, - floatSTP80 = 7, - - floatINVALID = -1, -}; - -enum FloatRound { - FROUND_NEAREST = 0, - FROUND_FLOOR = 1, - FROUND_CEIL = 2, - FROUND_ZERO = 3, - FROUND_MXCSR = 4, - - FROUND_RAISE_PRECISION = 0, - FROUND_IGNORE_PRECISION = 8, -}; - -class XEmitter; - -// RIP addressing does not benefit from micro op fusion on Core arch -struct OpArg { - friend class XEmitter; - - constexpr OpArg() = default; // dummy op arg, used for storage - constexpr OpArg(u64 offset_, int scale_, X64Reg rmReg = RAX, X64Reg scaledReg = RAX) - : scale(static_cast<u8>(scale_)), offsetOrBaseReg(static_cast<u16>(rmReg)), - indexReg(static_cast<u16>(scaledReg)), offset(offset_) {} - - constexpr bool operator==(const OpArg& b) const { - return operandReg == b.operandReg && scale == b.scale && - offsetOrBaseReg == b.offsetOrBaseReg && indexReg == b.indexReg && offset == b.offset; - } - - void WriteRex(XEmitter* emit, int opBits, int bits, int customOp = -1) const; - void WriteVex(XEmitter* emit, X64Reg regOp1, X64Reg regOp2, int L, int pp, int mmmmm, - int W = 0) const; - void WriteRest(XEmitter* emit, int extraBytes = 0, X64Reg operandReg = INVALID_REG, - bool warn_64bit_offset = true) const; - void WriteSingleByteOp(XEmitter* emit, u8 op, X64Reg operandReg, int bits); - void WriteNormalOp(XEmitter* emit, bool toRM, NormalOp op, const OpArg& operand, - int bits) const; - - constexpr bool IsImm() const { - return scale == SCALE_IMM8 || scale == SCALE_IMM16 || scale == SCALE_IMM32 || - scale == SCALE_IMM64; - } - constexpr bool IsSimpleReg() const { - return scale == SCALE_NONE; - } - constexpr bool IsSimpleReg(X64Reg reg) const { - return IsSimpleReg() && GetSimpleReg() == reg; - } - - int GetImmBits() const { - switch (scale) { - case SCALE_IMM8: - return 8; - case SCALE_IMM16: - return 16; - case SCALE_IMM32: - return 32; - case SCALE_IMM64: - return 64; - default: - return -1; - } - } - - void SetImmBits(int bits) { - switch (bits) { - case 8: - scale = SCALE_IMM8; - break; - case 16: - scale = SCALE_IMM16; - break; - case 32: - scale = SCALE_IMM32; - break; - case 64: - scale = SCALE_IMM64; - break; - } - } - - constexpr X64Reg GetSimpleReg() const { - return scale == SCALE_NONE ? static_cast<X64Reg>(offsetOrBaseReg) : INVALID_REG; - } - - constexpr u32 GetImmValue() const { - return static_cast<u32>(offset); - } - - // For loops. - void IncreaseOffset(int sz) { - offset += sz; - } - -private: - u8 scale = 0; - u16 offsetOrBaseReg = 0; - u16 indexReg = 0; - u64 offset = 0; // use RIP-relative as much as possible - 64-bit immediates are not available. - u16 operandReg = 0; -}; - -template <typename T> -inline OpArg M(const T* ptr) { - return OpArg(reinterpret_cast<u64>(ptr), static_cast<int>(SCALE_RIP)); -} -constexpr OpArg R(X64Reg value) { - return OpArg(0, SCALE_NONE, value); -} -constexpr OpArg MatR(X64Reg value) { - return OpArg(0, SCALE_ATREG, value); -} - -constexpr OpArg MDisp(X64Reg value, int offset) { - return OpArg(static_cast<u32>(offset), SCALE_ATREG, value); -} - -constexpr OpArg MComplex(X64Reg base, X64Reg scaled, int scale, int offset) { - return OpArg(offset, scale, base, scaled); -} - -constexpr OpArg MScaled(X64Reg scaled, int scale, int offset) { - return scale == SCALE_1 ? OpArg(offset, SCALE_ATREG, scaled) - : OpArg(offset, scale | 0x20, RAX, scaled); -} - -constexpr OpArg MRegSum(X64Reg base, X64Reg offset) { - return MComplex(base, offset, 1, 0); -} - -constexpr OpArg Imm8(u8 imm) { - return OpArg(imm, SCALE_IMM8); -} -constexpr OpArg Imm16(u16 imm) { - return OpArg(imm, SCALE_IMM16); -} // rarely used -constexpr OpArg Imm32(u32 imm) { - return OpArg(imm, SCALE_IMM32); -} -constexpr OpArg Imm64(u64 imm) { - return OpArg(imm, SCALE_IMM64); -} -constexpr OpArg UImmAuto(u32 imm) { - return OpArg(imm, imm >= 128 ? SCALE_IMM32 : SCALE_IMM8); -} -constexpr OpArg SImmAuto(s32 imm) { - return OpArg(imm, (imm >= 128 || imm < -128) ? SCALE_IMM32 : SCALE_IMM8); -} - -template <typename T> -OpArg ImmPtr(const T* imm) { -#ifdef _ARCH_64 - return Imm64(reinterpret_cast<u64>(imm)); -#else - return Imm32(reinterpret_cast<u32>(imm)); -#endif -} - -inline u32 PtrOffset(const void* ptr, const void* base) { -#ifdef _ARCH_64 - s64 distance = (s64)ptr - (s64)base; - if (distance >= 0x80000000LL || distance < -0x80000000LL) { - ASSERT_MSG(0, "pointer offset out of range"); - return 0; - } - - return (u32)distance; -#else - return (u32)ptr - (u32)base; -#endif -} - -// usage: int a[]; ARRAY_OFFSET(a,10) -#define ARRAY_OFFSET(array, index) ((u32)((u64) & (array)[index] - (u64) & (array)[0])) -// usage: struct {int e;} s; STRUCT_OFFSET(s,e) -#define STRUCT_OFFSET(str, elem) ((u32)((u64) & (str).elem - (u64) & (str))) - -struct FixupBranch { - u8* ptr; - int type; // 0 = 8bit 1 = 32bit -}; - -enum SSECompare { - EQ = 0, - LT, - LE, - UNORD, - NEQ, - NLT, - NLE, - ORD, -}; - -class XEmitter { - friend struct OpArg; // for Write8 etc -private: - u8* code; - bool flags_locked; - - void CheckFlags(); - - void Rex(int w, int r, int x, int b); - void WriteSimple1Byte(int bits, u8 byte, X64Reg reg); - void WriteSimple2Byte(int bits, u8 byte1, u8 byte2, X64Reg reg); - void WriteMulDivType(int bits, OpArg src, int ext); - void WriteBitSearchType(int bits, X64Reg dest, OpArg src, u8 byte2, bool rep = false); - void WriteShift(int bits, OpArg dest, const OpArg& shift, int ext); - void WriteBitTest(int bits, const OpArg& dest, const OpArg& index, int ext); - void WriteMXCSR(OpArg arg, int ext); - void WriteSSEOp(u8 opPrefix, u16 op, X64Reg regOp, OpArg arg, int extrabytes = 0); - void WriteSSSE3Op(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes = 0); - void WriteSSE41Op(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes = 0); - void WriteAVXOp(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes = 0); - void WriteAVXOp(u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg, - int extrabytes = 0); - void WriteVEXOp(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg, - int extrabytes = 0); - void WriteBMI1Op(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg, - int extrabytes = 0); - void WriteBMI2Op(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg, - int extrabytes = 0); - void WriteFloatLoadStore(int bits, FloatOp op, FloatOp op_80b, const OpArg& arg); - void WriteNormalOp(XEmitter* emit, int bits, NormalOp op, const OpArg& a1, const OpArg& a2); - - void ABI_CalculateFrameSize(BitSet32 mask, size_t rsp_alignment, size_t needed_frame_size, - size_t* shadowp, size_t* subtractionp, size_t* xmm_offsetp); - -protected: - void Write8(u8 value); - void Write16(u16 value); - void Write32(u32 value); - void Write64(u64 value); - -public: - XEmitter() { - code = nullptr; - flags_locked = false; - } - XEmitter(u8* code_ptr) { - code = code_ptr; - flags_locked = false; - } - virtual ~XEmitter() {} - - void WriteModRM(int mod, int rm, int reg); - void WriteSIB(int scale, int index, int base); - - void SetCodePtr(u8* ptr); - void ReserveCodeSpace(int bytes); - const u8* AlignCode4(); - const u8* AlignCode16(); - const u8* AlignCodePage(); - const u8* GetCodePtr() const; - u8* GetWritableCodePtr(); - - void LockFlags() { - flags_locked = true; - } - void UnlockFlags() { - flags_locked = false; - } - - // Looking for one of these? It's BANNED!! Some instructions are slow on modern CPU - // INC, DEC, LOOP, LOOPNE, LOOPE, ENTER, LEAVE, XCHG, XLAT, REP MOVSB/MOVSD, REP SCASD + other - // string instr., - // INC and DEC are slow on Intel Core, but not on AMD. They create a - // false flag dependency because they only update a subset of the flags. - // XCHG is SLOW and should be avoided. - - // Debug breakpoint - void INT3(); - - // Do nothing - void NOP(size_t count = 1); - - // Save energy in wait-loops on P4 only. Probably not too useful. - void PAUSE(); - - // Flag control - void STC(); - void CLC(); - void CMC(); - - // These two can not be executed in 64-bit mode on early Intel 64-bit CPU:s, only on Core2 and - // AMD! - void LAHF(); // 3 cycle vector path - void SAHF(); // direct path fast - - // Stack control - void PUSH(X64Reg reg); - void POP(X64Reg reg); - void PUSH(int bits, const OpArg& reg); - void POP(int bits, const OpArg& reg); - void PUSHF(); - void POPF(); - - // Flow control - void RET(); - void RET_FAST(); - void UD2(); - FixupBranch J(bool force5bytes = false); - - void JMP(const u8* addr, bool force5Bytes = false); - void JMPptr(const OpArg& arg); - void JMPself(); // infinite loop! -#ifdef CALL -#undef CALL -#endif - void CALL(const void* fnptr); - FixupBranch CALL(); - void CALLptr(OpArg arg); - - FixupBranch J_CC(CCFlags conditionCode, bool force5bytes = false); - void J_CC(CCFlags conditionCode, const u8* addr, bool force5Bytes = false); - - void SetJumpTarget(const FixupBranch& branch); - void SetJumpTarget(const FixupBranch& branch, const u8* target); - - void SETcc(CCFlags flag, OpArg dest); - // Note: CMOV brings small if any benefit on current cpus. - void CMOVcc(int bits, X64Reg dest, OpArg src, CCFlags flag); - - // Fences - void LFENCE(); - void MFENCE(); - void SFENCE(); - - // Bit scan - void BSF(int bits, X64Reg dest, const OpArg& src); // Bottom bit to top bit - void BSR(int bits, X64Reg dest, const OpArg& src); // Top bit to bottom bit - - // Cache control - enum PrefetchLevel { - PF_NTA, // Non-temporal (data used once and only once) - PF_T0, // All cache levels - PF_T1, // Levels 2+ (aliased to T0 on AMD) - PF_T2, // Levels 3+ (aliased to T0 on AMD) - }; - void PREFETCH(PrefetchLevel level, OpArg arg); - void MOVNTI(int bits, const OpArg& dest, X64Reg src); - void MOVNTDQ(const OpArg& arg, X64Reg regOp); - void MOVNTPS(const OpArg& arg, X64Reg regOp); - void MOVNTPD(const OpArg& arg, X64Reg regOp); - - // Multiplication / division - void MUL(int bits, const OpArg& src); // UNSIGNED - void IMUL(int bits, const OpArg& src); // SIGNED - void IMUL(int bits, X64Reg regOp, const OpArg& src); - void IMUL(int bits, X64Reg regOp, const OpArg& src, const OpArg& imm); - void DIV(int bits, const OpArg& src); - void IDIV(int bits, const OpArg& src); - - // Shift - void ROL(int bits, const OpArg& dest, const OpArg& shift); - void ROR(int bits, const OpArg& dest, const OpArg& shift); - void RCL(int bits, const OpArg& dest, const OpArg& shift); - void RCR(int bits, const OpArg& dest, const OpArg& shift); - void SHL(int bits, const OpArg& dest, const OpArg& shift); - void SHR(int bits, const OpArg& dest, const OpArg& shift); - void SAR(int bits, const OpArg& dest, const OpArg& shift); - - // Bit Test - void BT(int bits, const OpArg& dest, const OpArg& index); - void BTS(int bits, const OpArg& dest, const OpArg& index); - void BTR(int bits, const OpArg& dest, const OpArg& index); - void BTC(int bits, const OpArg& dest, const OpArg& index); - - // Double-Precision Shift - void SHRD(int bits, const OpArg& dest, const OpArg& src, const OpArg& shift); - void SHLD(int bits, const OpArg& dest, const OpArg& src, const OpArg& shift); - - // Extend EAX into EDX in various ways - void CWD(int bits = 16); - void CDQ() { - CWD(32); - } - void CQO() { - CWD(64); - } - void CBW(int bits = 8); - void CWDE() { - CBW(16); - } - void CDQE() { - CBW(32); - } - - // Load effective address - void LEA(int bits, X64Reg dest, OpArg src); - - // Integer arithmetic - void NEG(int bits, const OpArg& src); - void ADD(int bits, const OpArg& a1, const OpArg& a2); - void ADC(int bits, const OpArg& a1, const OpArg& a2); - void SUB(int bits, const OpArg& a1, const OpArg& a2); - void SBB(int bits, const OpArg& a1, const OpArg& a2); - void AND(int bits, const OpArg& a1, const OpArg& a2); - void CMP(int bits, const OpArg& a1, const OpArg& a2); - - // Bit operations - void NOT(int bits, const OpArg& src); - void OR(int bits, const OpArg& a1, const OpArg& a2); - void XOR(int bits, const OpArg& a1, const OpArg& a2); - void MOV(int bits, const OpArg& a1, const OpArg& a2); - void TEST(int bits, const OpArg& a1, const OpArg& a2); - - // Are these useful at all? Consider removing. - void XCHG(int bits, const OpArg& a1, const OpArg& a2); - void XCHG_AHAL(); - - // Byte swapping (32 and 64-bit only). - void BSWAP(int bits, X64Reg reg); - - // Sign/zero extension - void MOVSX(int dbits, int sbits, X64Reg dest, - OpArg src); // automatically uses MOVSXD if necessary - void MOVZX(int dbits, int sbits, X64Reg dest, OpArg src); - - // Available only on Atom or >= Haswell so far. Test with GetCPUCaps().movbe. - void MOVBE(int dbits, const OpArg& dest, const OpArg& src); - - // Available only on AMD >= Phenom or Intel >= Haswell - void LZCNT(int bits, X64Reg dest, const OpArg& src); - // Note: this one is actually part of BMI1 - void TZCNT(int bits, X64Reg dest, const OpArg& src); - - // WARNING - These two take 11-13 cycles and are VectorPath! (AMD64) - void STMXCSR(const OpArg& memloc); - void LDMXCSR(const OpArg& memloc); - - // Prefixes - void LOCK(); - void REP(); - void REPNE(); - void FSOverride(); - void GSOverride(); - - // x87 - enum x87StatusWordBits { - x87_InvalidOperation = 0x1, - x87_DenormalizedOperand = 0x2, - x87_DivisionByZero = 0x4, - x87_Overflow = 0x8, - x87_Underflow = 0x10, - x87_Precision = 0x20, - x87_StackFault = 0x40, - x87_ErrorSummary = 0x80, - x87_C0 = 0x100, - x87_C1 = 0x200, - x87_C2 = 0x400, - x87_TopOfStack = 0x2000 | 0x1000 | 0x800, - x87_C3 = 0x4000, - x87_FPUBusy = 0x8000, - }; - - void FLD(int bits, const OpArg& src); - void FST(int bits, const OpArg& dest); - void FSTP(int bits, const OpArg& dest); - void FNSTSW_AX(); - void FWAIT(); - - // SSE/SSE2: Floating point arithmetic - void ADDSS(X64Reg regOp, const OpArg& arg); - void ADDSD(X64Reg regOp, const OpArg& arg); - void SUBSS(X64Reg regOp, const OpArg& arg); - void SUBSD(X64Reg regOp, const OpArg& arg); - void MULSS(X64Reg regOp, const OpArg& arg); - void MULSD(X64Reg regOp, const OpArg& arg); - void DIVSS(X64Reg regOp, const OpArg& arg); - void DIVSD(X64Reg regOp, const OpArg& arg); - void MINSS(X64Reg regOp, const OpArg& arg); - void MINSD(X64Reg regOp, const OpArg& arg); - void MAXSS(X64Reg regOp, const OpArg& arg); - void MAXSD(X64Reg regOp, const OpArg& arg); - void SQRTSS(X64Reg regOp, const OpArg& arg); - void SQRTSD(X64Reg regOp, const OpArg& arg); - void RCPSS(X64Reg regOp, const OpArg& arg); - void RSQRTSS(X64Reg regOp, const OpArg& arg); - - // SSE/SSE2: Floating point bitwise (yes) - void CMPSS(X64Reg regOp, const OpArg& arg, u8 compare); - void CMPSD(X64Reg regOp, const OpArg& arg, u8 compare); - - void CMPEQSS(X64Reg regOp, const OpArg& arg) { - CMPSS(regOp, arg, CMP_EQ); - } - void CMPLTSS(X64Reg regOp, const OpArg& arg) { - CMPSS(regOp, arg, CMP_LT); - } - void CMPLESS(X64Reg regOp, const OpArg& arg) { - CMPSS(regOp, arg, CMP_LE); - } - void CMPUNORDSS(X64Reg regOp, const OpArg& arg) { - CMPSS(regOp, arg, CMP_UNORD); - } - void CMPNEQSS(X64Reg regOp, const OpArg& arg) { - CMPSS(regOp, arg, CMP_NEQ); - } - void CMPNLTSS(X64Reg regOp, const OpArg& arg) { - CMPSS(regOp, arg, CMP_NLT); - } - void CMPORDSS(X64Reg regOp, const OpArg& arg) { - CMPSS(regOp, arg, CMP_ORD); - } - - // SSE/SSE2: Floating point packed arithmetic (x4 for float, x2 for double) - void ADDPS(X64Reg regOp, const OpArg& arg); - void ADDPD(X64Reg regOp, const OpArg& arg); - void SUBPS(X64Reg regOp, const OpArg& arg); - void SUBPD(X64Reg regOp, const OpArg& arg); - void CMPPS(X64Reg regOp, const OpArg& arg, u8 compare); - void CMPPD(X64Reg regOp, const OpArg& arg, u8 compare); - void MULPS(X64Reg regOp, const OpArg& arg); - void MULPD(X64Reg regOp, const OpArg& arg); - void DIVPS(X64Reg regOp, const OpArg& arg); - void DIVPD(X64Reg regOp, const OpArg& arg); - void MINPS(X64Reg regOp, const OpArg& arg); - void MINPD(X64Reg regOp, const OpArg& arg); - void MAXPS(X64Reg regOp, const OpArg& arg); - void MAXPD(X64Reg regOp, const OpArg& arg); - void SQRTPS(X64Reg regOp, const OpArg& arg); - void SQRTPD(X64Reg regOp, const OpArg& arg); - void RCPPS(X64Reg regOp, const OpArg& arg); - void RSQRTPS(X64Reg regOp, const OpArg& arg); - - // SSE/SSE2: Floating point packed bitwise (x4 for float, x2 for double) - void ANDPS(X64Reg regOp, const OpArg& arg); - void ANDPD(X64Reg regOp, const OpArg& arg); - void ANDNPS(X64Reg regOp, const OpArg& arg); - void ANDNPD(X64Reg regOp, const OpArg& arg); - void ORPS(X64Reg regOp, const OpArg& arg); - void ORPD(X64Reg regOp, const OpArg& arg); - void XORPS(X64Reg regOp, const OpArg& arg); - void XORPD(X64Reg regOp, const OpArg& arg); - - // SSE/SSE2: Shuffle components. These are tricky - see Intel documentation. - void SHUFPS(X64Reg regOp, const OpArg& arg, u8 shuffle); - void SHUFPD(X64Reg regOp, const OpArg& arg, u8 shuffle); - - // SSE/SSE2: Useful alternative to shuffle in some cases. - void MOVDDUP(X64Reg regOp, const OpArg& arg); - - // SSE3: Horizontal operations in SIMD registers. Very slow! shufps-based code beats it handily - // on Ivy. - void HADDPS(X64Reg dest, const OpArg& src); - - // SSE4: Further horizontal operations - dot products. These are weirdly flexible, the arg - // contains both a read mask and a write "mask". - void DPPS(X64Reg dest, const OpArg& src, u8 arg); - - void UNPCKLPS(X64Reg dest, const OpArg& src); - void UNPCKHPS(X64Reg dest, const OpArg& src); - void UNPCKLPD(X64Reg dest, const OpArg& src); - void UNPCKHPD(X64Reg dest, const OpArg& src); - - // SSE/SSE2: Compares. - void COMISS(X64Reg regOp, const OpArg& arg); - void COMISD(X64Reg regOp, const OpArg& arg); - void UCOMISS(X64Reg regOp, const OpArg& arg); - void UCOMISD(X64Reg regOp, const OpArg& arg); - - // SSE/SSE2: Moves. Use the right data type for your data, in most cases. - void MOVAPS(X64Reg regOp, const OpArg& arg); - void MOVAPD(X64Reg regOp, const OpArg& arg); - void MOVAPS(const OpArg& arg, X64Reg regOp); - void MOVAPD(const OpArg& arg, X64Reg regOp); - - void MOVUPS(X64Reg regOp, const OpArg& arg); - void MOVUPD(X64Reg regOp, const OpArg& arg); - void MOVUPS(const OpArg& arg, X64Reg regOp); - void MOVUPD(const OpArg& arg, X64Reg regOp); - - void MOVDQA(X64Reg regOp, const OpArg& arg); - void MOVDQA(const OpArg& arg, X64Reg regOp); - void MOVDQU(X64Reg regOp, const OpArg& arg); - void MOVDQU(const OpArg& arg, X64Reg regOp); - - void MOVSS(X64Reg regOp, const OpArg& arg); - void MOVSD(X64Reg regOp, const OpArg& arg); - void MOVSS(const OpArg& arg, X64Reg regOp); - void MOVSD(const OpArg& arg, X64Reg regOp); - - void MOVLPS(X64Reg regOp, const OpArg& arg); - void MOVLPD(X64Reg regOp, const OpArg& arg); - void MOVLPS(const OpArg& arg, X64Reg regOp); - void MOVLPD(const OpArg& arg, X64Reg regOp); - - void MOVHPS(X64Reg regOp, const OpArg& arg); - void MOVHPD(X64Reg regOp, const OpArg& arg); - void MOVHPS(const OpArg& arg, X64Reg regOp); - void MOVHPD(const OpArg& arg, X64Reg regOp); - - void MOVHLPS(X64Reg regOp1, X64Reg regOp2); - void MOVLHPS(X64Reg regOp1, X64Reg regOp2); - - void MOVD_xmm(X64Reg dest, const OpArg& arg); - void MOVQ_xmm(X64Reg dest, OpArg arg); - void MOVD_xmm(const OpArg& arg, X64Reg src); - void MOVQ_xmm(OpArg arg, X64Reg src); - - // SSE/SSE2: Generates a mask from the high bits of the components of the packed register in - // question. - void MOVMSKPS(X64Reg dest, const OpArg& arg); - void MOVMSKPD(X64Reg dest, const OpArg& arg); - - // SSE2: Selective byte store, mask in src register. EDI/RDI specifies store address. This is a - // weird one. - void MASKMOVDQU(X64Reg dest, X64Reg src); - void LDDQU(X64Reg dest, const OpArg& src); - - // SSE/SSE2: Data type conversions. - void CVTPS2PD(X64Reg dest, const OpArg& src); - void CVTPD2PS(X64Reg dest, const OpArg& src); - void CVTSS2SD(X64Reg dest, const OpArg& src); - void CVTSI2SS(X64Reg dest, const OpArg& src); - void CVTSD2SS(X64Reg dest, const OpArg& src); - void CVTSI2SD(X64Reg dest, const OpArg& src); - void CVTDQ2PD(X64Reg regOp, const OpArg& arg); - void CVTPD2DQ(X64Reg regOp, const OpArg& arg); - void CVTDQ2PS(X64Reg regOp, const OpArg& arg); - void CVTPS2DQ(X64Reg regOp, const OpArg& arg); - - void CVTTPS2DQ(X64Reg regOp, const OpArg& arg); - void CVTTPD2DQ(X64Reg regOp, const OpArg& arg); - - // Destinations are X64 regs (rax, rbx, ...) for these instructions. - void CVTSS2SI(X64Reg xregdest, const OpArg& src); - void CVTSD2SI(X64Reg xregdest, const OpArg& src); - void CVTTSS2SI(X64Reg xregdest, const OpArg& arg); - void CVTTSD2SI(X64Reg xregdest, const OpArg& arg); - - // SSE2: Packed integer instructions - void PACKSSDW(X64Reg dest, const OpArg& arg); - void PACKSSWB(X64Reg dest, const OpArg& arg); - void PACKUSDW(X64Reg dest, const OpArg& arg); - void PACKUSWB(X64Reg dest, const OpArg& arg); - - void PUNPCKLBW(X64Reg dest, const OpArg& arg); - void PUNPCKLWD(X64Reg dest, const OpArg& arg); - void PUNPCKLDQ(X64Reg dest, const OpArg& arg); - void PUNPCKLQDQ(X64Reg dest, const OpArg& arg); - - void PTEST(X64Reg dest, const OpArg& arg); - void PAND(X64Reg dest, const OpArg& arg); - void PANDN(X64Reg dest, const OpArg& arg); - void PXOR(X64Reg dest, const OpArg& arg); - void POR(X64Reg dest, const OpArg& arg); - - void PADDB(X64Reg dest, const OpArg& arg); - void PADDW(X64Reg dest, const OpArg& arg); - void PADDD(X64Reg dest, const OpArg& arg); - void PADDQ(X64Reg dest, const OpArg& arg); - - void PADDSB(X64Reg dest, const OpArg& arg); - void PADDSW(X64Reg dest, const OpArg& arg); - void PADDUSB(X64Reg dest, const OpArg& arg); - void PADDUSW(X64Reg dest, const OpArg& arg); - - void PSUBB(X64Reg dest, const OpArg& arg); - void PSUBW(X64Reg dest, const OpArg& arg); - void PSUBD(X64Reg dest, const OpArg& arg); - void PSUBQ(X64Reg dest, const OpArg& arg); - - void PSUBSB(X64Reg dest, const OpArg& arg); - void PSUBSW(X64Reg dest, const OpArg& arg); - void PSUBUSB(X64Reg dest, const OpArg& arg); - void PSUBUSW(X64Reg dest, const OpArg& arg); - - void PAVGB(X64Reg dest, const OpArg& arg); - void PAVGW(X64Reg dest, const OpArg& arg); - - void PCMPEQB(X64Reg dest, const OpArg& arg); - void PCMPEQW(X64Reg dest, const OpArg& arg); - void PCMPEQD(X64Reg dest, const OpArg& arg); - - void PCMPGTB(X64Reg dest, const OpArg& arg); - void PCMPGTW(X64Reg dest, const OpArg& arg); - void PCMPGTD(X64Reg dest, const OpArg& arg); - - void PEXTRW(X64Reg dest, const OpArg& arg, u8 subreg); - void PINSRW(X64Reg dest, const OpArg& arg, u8 subreg); - - void PMADDWD(X64Reg dest, const OpArg& arg); - void PSADBW(X64Reg dest, const OpArg& arg); - - void PMAXSW(X64Reg dest, const OpArg& arg); - void PMAXUB(X64Reg dest, const OpArg& arg); - void PMINSW(X64Reg dest, const OpArg& arg); - void PMINUB(X64Reg dest, const OpArg& arg); - // SSE4: More MAX/MIN instructions. - void PMINSB(X64Reg dest, const OpArg& arg); - void PMINSD(X64Reg dest, const OpArg& arg); - void PMINUW(X64Reg dest, const OpArg& arg); - void PMINUD(X64Reg dest, const OpArg& arg); - void PMAXSB(X64Reg dest, const OpArg& arg); - void PMAXSD(X64Reg dest, const OpArg& arg); - void PMAXUW(X64Reg dest, const OpArg& arg); - void PMAXUD(X64Reg dest, const OpArg& arg); - - void PMOVMSKB(X64Reg dest, const OpArg& arg); - void PSHUFD(X64Reg dest, const OpArg& arg, u8 shuffle); - void PSHUFB(X64Reg dest, const OpArg& arg); - - void PSHUFLW(X64Reg dest, const OpArg& arg, u8 shuffle); - void PSHUFHW(X64Reg dest, const OpArg& arg, u8 shuffle); - - void PSRLW(X64Reg reg, int shift); - void PSRLD(X64Reg reg, int shift); - void PSRLQ(X64Reg reg, int shift); - void PSRLQ(X64Reg reg, const OpArg& arg); - void PSRLDQ(X64Reg reg, int shift); - - void PSLLW(X64Reg reg, int shift); - void PSLLD(X64Reg reg, int shift); - void PSLLQ(X64Reg reg, int shift); - void PSLLDQ(X64Reg reg, int shift); - - void PSRAW(X64Reg reg, int shift); - void PSRAD(X64Reg reg, int shift); - - // SSE4: data type conversions - void PMOVSXBW(X64Reg dest, const OpArg& arg); - void PMOVSXBD(X64Reg dest, const OpArg& arg); - void PMOVSXBQ(X64Reg dest, const OpArg& arg); - void PMOVSXWD(X64Reg dest, const OpArg& arg); - void PMOVSXWQ(X64Reg dest, const OpArg& arg); - void PMOVSXDQ(X64Reg dest, const OpArg& arg); - void PMOVZXBW(X64Reg dest, const OpArg& arg); - void PMOVZXBD(X64Reg dest, const OpArg& arg); - void PMOVZXBQ(X64Reg dest, const OpArg& arg); - void PMOVZXWD(X64Reg dest, const OpArg& arg); - void PMOVZXWQ(X64Reg dest, const OpArg& arg); - void PMOVZXDQ(X64Reg dest, const OpArg& arg); - - // SSE4: variable blend instructions (xmm0 implicit argument) - void PBLENDVB(X64Reg dest, const OpArg& arg); - void BLENDVPS(X64Reg dest, const OpArg& arg); - void BLENDVPD(X64Reg dest, const OpArg& arg); - void BLENDPS(X64Reg dest, const OpArg& arg, u8 blend); - void BLENDPD(X64Reg dest, const OpArg& arg, u8 blend); - - // SSE4: rounding (see FloatRound for mode or use ROUNDNEARSS, etc. helpers.) - void ROUNDSS(X64Reg dest, const OpArg& arg, u8 mode); - void ROUNDSD(X64Reg dest, const OpArg& arg, u8 mode); - void ROUNDPS(X64Reg dest, const OpArg& arg, u8 mode); - void ROUNDPD(X64Reg dest, const OpArg& arg, u8 mode); - - void ROUNDNEARSS(X64Reg dest, const OpArg& arg) { - ROUNDSS(dest, arg, FROUND_NEAREST); - } - void ROUNDFLOORSS(X64Reg dest, const OpArg& arg) { - ROUNDSS(dest, arg, FROUND_FLOOR); - } - void ROUNDCEILSS(X64Reg dest, const OpArg& arg) { - ROUNDSS(dest, arg, FROUND_CEIL); - } - void ROUNDZEROSS(X64Reg dest, const OpArg& arg) { - ROUNDSS(dest, arg, FROUND_ZERO); - } - - void ROUNDNEARSD(X64Reg dest, const OpArg& arg) { - ROUNDSD(dest, arg, FROUND_NEAREST); - } - void ROUNDFLOORSD(X64Reg dest, const OpArg& arg) { - ROUNDSD(dest, arg, FROUND_FLOOR); - } - void ROUNDCEILSD(X64Reg dest, const OpArg& arg) { - ROUNDSD(dest, arg, FROUND_CEIL); - } - void ROUNDZEROSD(X64Reg dest, const OpArg& arg) { - ROUNDSD(dest, arg, FROUND_ZERO); - } - - void ROUNDNEARPS(X64Reg dest, const OpArg& arg) { - ROUNDPS(dest, arg, FROUND_NEAREST); - } - void ROUNDFLOORPS(X64Reg dest, const OpArg& arg) { - ROUNDPS(dest, arg, FROUND_FLOOR); - } - void ROUNDCEILPS(X64Reg dest, const OpArg& arg) { - ROUNDPS(dest, arg, FROUND_CEIL); - } - void ROUNDZEROPS(X64Reg dest, const OpArg& arg) { - ROUNDPS(dest, arg, FROUND_ZERO); - } - - void ROUNDNEARPD(X64Reg dest, const OpArg& arg) { - ROUNDPD(dest, arg, FROUND_NEAREST); - } - void ROUNDFLOORPD(X64Reg dest, const OpArg& arg) { - ROUNDPD(dest, arg, FROUND_FLOOR); - } - void ROUNDCEILPD(X64Reg dest, const OpArg& arg) { - ROUNDPD(dest, arg, FROUND_CEIL); - } - void ROUNDZEROPD(X64Reg dest, const OpArg& arg) { - ROUNDPD(dest, arg, FROUND_ZERO); - } - - // AVX - void VADDSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VSUBSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VMULSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VDIVSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VADDPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VSUBPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VMULPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VDIVPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VSQRTSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VSHUFPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg, u8 shuffle); - void VUNPCKLPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VUNPCKHPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - - void VANDPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VANDPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VANDNPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VANDNPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VORPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VORPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VXORPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VXORPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - - void VPAND(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VPANDN(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VPOR(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VPXOR(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - - // FMA3 - void VFMADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADDSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADDSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADDSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADDSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADDSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADDSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUBADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUBADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUBADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUBADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUBADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUBADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - - // VEX GPR instructions - void SARX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2); - void SHLX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2); - void SHRX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2); - void RORX(int bits, X64Reg regOp, const OpArg& arg, u8 rotate); - void PEXT(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void PDEP(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void MULX(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void BZHI(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2); - void BLSR(int bits, X64Reg regOp, const OpArg& arg); - void BLSMSK(int bits, X64Reg regOp, const OpArg& arg); - void BLSI(int bits, X64Reg regOp, const OpArg& arg); - void BEXTR(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2); - void ANDN(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - - void RDTSC(); - - // Utility functions - // The difference between this and CALL is that this aligns the stack - // where appropriate. - void ABI_CallFunction(const void* func); - template <typename T> - void ABI_CallFunction(T (*func)()) { - ABI_CallFunction((const void*)func); - } - - void ABI_CallFunction(const u8* func) { - ABI_CallFunction((const void*)func); - } - void ABI_CallFunctionC16(const void* func, u16 param1); - void ABI_CallFunctionCC16(const void* func, u32 param1, u16 param2); - - // These only support u32 parameters, but that's enough for a lot of uses. - // These will destroy the 1 or 2 first "parameter regs". - void ABI_CallFunctionC(const void* func, u32 param1); - void ABI_CallFunctionCC(const void* func, u32 param1, u32 param2); - void ABI_CallFunctionCCC(const void* func, u32 param1, u32 param2, u32 param3); - void ABI_CallFunctionCCP(const void* func, u32 param1, u32 param2, void* param3); - void ABI_CallFunctionCCCP(const void* func, u32 param1, u32 param2, u32 param3, void* param4); - void ABI_CallFunctionP(const void* func, void* param1); - void ABI_CallFunctionPA(const void* func, void* param1, const OpArg& arg2); - void ABI_CallFunctionPAA(const void* func, void* param1, const OpArg& arg2, const OpArg& arg3); - void ABI_CallFunctionPPC(const void* func, void* param1, void* param2, u32 param3); - void ABI_CallFunctionAC(const void* func, const OpArg& arg1, u32 param2); - void ABI_CallFunctionACC(const void* func, const OpArg& arg1, u32 param2, u32 param3); - void ABI_CallFunctionA(const void* func, const OpArg& arg1); - void ABI_CallFunctionAA(const void* func, const OpArg& arg1, const OpArg& arg2); - - // Pass a register as a parameter. - void ABI_CallFunctionR(const void* func, X64Reg reg1); - void ABI_CallFunctionRR(const void* func, X64Reg reg1, X64Reg reg2); - - template <typename Tr, typename T1> - void ABI_CallFunctionC(Tr (*func)(T1), u32 param1) { - ABI_CallFunctionC((const void*)func, param1); - } - - /** - * Saves specified registers and adjusts the stack to be 16-byte aligned as required by the ABI - * - * @param mask Registers to push on the stack (high 16 bits are XMMs, low 16 bits are GPRs) - * @param rsp_alignment Current alignment of the stack pointer, must be 0 or 8 - * @param needed_frame_size Additional space needed, e.g., for function arguments passed on the - * stack - * @return Size of the shadow space, i.e., offset of the frame - */ - size_t ABI_PushRegistersAndAdjustStack(BitSet32 mask, size_t rsp_alignment, - size_t needed_frame_size = 0); - - /** - * Restores specified registers and adjusts the stack to its original alignment, i.e., the - * alignment before - * the matching PushRegistersAndAdjustStack. - * - * @param mask Registers to restores from the stack (high 16 bits are XMMs, low 16 bits are - * GPRs) - * @param rsp_alignment Original alignment before the matching PushRegistersAndAdjustStack, must - * be 0 or 8 - * @param needed_frame_size Additional space that was needed - * @warning Stack must be currently 16-byte aligned - */ - void ABI_PopRegistersAndAdjustStack(BitSet32 mask, size_t rsp_alignment, - size_t needed_frame_size = 0); - -#ifdef _M_IX86 - static int ABI_GetNumXMMRegs() { - return 8; - } -#else - static int ABI_GetNumXMMRegs() { - return 16; - } -#endif -}; // class XEmitter - -// Everything that needs to generate X86 code should inherit from this. -// You get memory management for free, plus, you can use all the MOV etc functions without -// having to prefix them with gen-> or something similar. - -class XCodeBlock : public CodeBlock<XEmitter> { -public: - void PoisonMemory() override; -}; - -} // namespace diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c33555bd5..b178914dd 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2,6 +2,7 @@ set(SRCS arm/disassembler/arm_disasm.cpp arm/disassembler/load_symbol_map.cpp arm/dynarmic/arm_dynarmic.cpp + arm/dynarmic/arm_dynarmic_cp15.cpp arm/dyncom/arm_dyncom.cpp arm/dyncom/arm_dyncom_dec.cpp arm/dyncom/arm_dyncom_interpreter.cpp @@ -29,13 +30,18 @@ set(SRCS file_sys/ivfc_archive.cpp file_sys/path_parser.cpp file_sys/savedata_archive.cpp + frontend/camera/blank_camera.cpp + frontend/camera/factory.cpp + frontend/camera/interface.cpp frontend/emu_window.cpp frontend/key_map.cpp + frontend/motion_emu.cpp gdbstub/gdbstub.cpp hle/config_mem.cpp hle/applets/applet.cpp hle/applets/erreula.cpp hle/applets/mii_selector.cpp + hle/applets/mint.cpp hle/applets/swkbd.cpp hle/kernel/address_arbiter.cpp hle/kernel/client_port.cpp @@ -53,7 +59,9 @@ set(SRCS hle/kernel/thread.cpp hle/kernel/timer.cpp hle/kernel/vm_manager.cpp - hle/service/ac_u.cpp + hle/service/ac/ac.cpp + hle/service/ac/ac_i.cpp + hle/service/ac/ac_u.cpp hle/service/act/act.cpp hle/service/act/act_a.cpp hle/service/act/act_u.cpp @@ -170,6 +178,7 @@ set(HEADERS arm/disassembler/arm_disasm.h arm/disassembler/load_symbol_map.h arm/dynarmic/arm_dynarmic.h + arm/dynarmic/arm_dynarmic_cp15.h arm/dyncom/arm_dyncom.h arm/dyncom/arm_dyncom_dec.h arm/dyncom/arm_dyncom_interpreter.h @@ -200,8 +209,12 @@ set(HEADERS file_sys/ivfc_archive.h file_sys/path_parser.h file_sys/savedata_archive.h + frontend/camera/blank_camera.h + frontend/camera/factory.h + frontend/camera/interface.h frontend/emu_window.h frontend/key_map.h + frontend/motion_emu.h gdbstub/gdbstub.h hle/config_mem.h hle/function_wrappers.h @@ -210,6 +223,7 @@ set(HEADERS hle/applets/applet.h hle/applets/erreula.h hle/applets/mii_selector.h + hle/applets/mint.h hle/applets/swkbd.h hle/kernel/address_arbiter.h hle/kernel/client_port.h @@ -228,7 +242,9 @@ set(HEADERS hle/kernel/timer.h hle/kernel/vm_manager.h hle/result.h - hle/service/ac_u.h + hle/service/ac/ac.h + hle/service/ac/ac_i.h + hle/service/ac/ac_u.h hle/service/act/act.h hle/service/act/act_a.h hle/service/act/act_u.h diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 9f25e3b00..7d2790b08 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -7,6 +7,7 @@ #include "common/assert.h" #include "common/microprofile.h" #include "core/arm/dynarmic/arm_dynarmic.h" +#include "core/arm/dynarmic/arm_dynarmic_cp15.h" #include "core/arm/dyncom/arm_dyncom_interpreter.h" #include "core/core.h" #include "core/core_timing.h" @@ -39,28 +40,30 @@ static bool IsReadOnlyMemory(u32 vaddr) { return false; } -static Dynarmic::UserCallbacks GetUserCallbacks(ARMul_State* interpeter_state) { +static Dynarmic::UserCallbacks GetUserCallbacks( + const std::shared_ptr<ARMul_State>& interpeter_state) { Dynarmic::UserCallbacks user_callbacks{}; user_callbacks.InterpreterFallback = &InterpreterFallback; - user_callbacks.user_arg = static_cast<void*>(interpeter_state); + user_callbacks.user_arg = static_cast<void*>(interpeter_state.get()); user_callbacks.CallSVC = &SVC::CallSVC; - user_callbacks.IsReadOnlyMemory = &IsReadOnlyMemory; - user_callbacks.MemoryReadCode = &Memory::Read32; - user_callbacks.MemoryRead8 = &Memory::Read8; - user_callbacks.MemoryRead16 = &Memory::Read16; - user_callbacks.MemoryRead32 = &Memory::Read32; - user_callbacks.MemoryRead64 = &Memory::Read64; - user_callbacks.MemoryWrite8 = &Memory::Write8; - user_callbacks.MemoryWrite16 = &Memory::Write16; - user_callbacks.MemoryWrite32 = &Memory::Write32; - user_callbacks.MemoryWrite64 = &Memory::Write64; + user_callbacks.memory.IsReadOnlyMemory = &IsReadOnlyMemory; + user_callbacks.memory.ReadCode = &Memory::Read32; + user_callbacks.memory.Read8 = &Memory::Read8; + user_callbacks.memory.Read16 = &Memory::Read16; + user_callbacks.memory.Read32 = &Memory::Read32; + user_callbacks.memory.Read64 = &Memory::Read64; + user_callbacks.memory.Write8 = &Memory::Write8; + user_callbacks.memory.Write16 = &Memory::Write16; + user_callbacks.memory.Write32 = &Memory::Write32; + user_callbacks.memory.Write64 = &Memory::Write64; user_callbacks.page_table = Memory::GetCurrentPageTablePointers(); + user_callbacks.coprocessors[15] = std::make_shared<DynarmicCP15>(interpeter_state); return user_callbacks; } ARM_Dynarmic::ARM_Dynarmic(PrivilegeMode initial_mode) { - interpreter_state = std::make_unique<ARMul_State>(initial_mode); - jit = std::make_unique<Dynarmic::Jit>(GetUserCallbacks(interpreter_state.get())); + interpreter_state = std::make_shared<ARMul_State>(initial_mode); + jit = std::make_unique<Dynarmic::Jit>(GetUserCallbacks(interpreter_state)); } void ARM_Dynarmic::SetPC(u32 pc) { diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h index 87ab53d81..834dc989e 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.h +++ b/src/core/arm/dynarmic/arm_dynarmic.h @@ -39,5 +39,5 @@ public: private: std::unique_ptr<Dynarmic::Jit> jit; - std::unique_ptr<ARMul_State> interpreter_state; + std::shared_ptr<ARMul_State> interpreter_state; }; diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp new file mode 100644 index 000000000..b1fdce096 --- /dev/null +++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp @@ -0,0 +1,88 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/arm/dynarmic/arm_dynarmic_cp15.h" +#include "core/arm/skyeye_common/arm_regformat.h" +#include "core/arm/skyeye_common/armstate.h" + +using Callback = Dynarmic::Coprocessor::Callback; +using CallbackOrAccessOneWord = Dynarmic::Coprocessor::CallbackOrAccessOneWord; +using CallbackOrAccessTwoWords = Dynarmic::Coprocessor::CallbackOrAccessTwoWords; + +DynarmicCP15::DynarmicCP15(const std::shared_ptr<ARMul_State>& state) : interpreter_state(state) {} + +DynarmicCP15::~DynarmicCP15() = default; + +boost::optional<Callback> DynarmicCP15::CompileInternalOperation(bool two, unsigned opc1, + CoprocReg CRd, CoprocReg CRn, + CoprocReg CRm, unsigned opc2) { + return boost::none; +} + +CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn, + CoprocReg CRm, unsigned opc2) { + // TODO(merry): Privileged CP15 registers + + if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C5 && opc2 == 4) { + // This is a dummy write, we ignore the value written here. + return &interpreter_state->CP15[CP15_FLUSH_PREFETCH_BUFFER]; + } + + if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C10) { + switch (opc2) { + case 4: + // This is a dummy write, we ignore the value written here. + return &interpreter_state->CP15[CP15_DATA_SYNC_BARRIER]; + case 5: + // This is a dummy write, we ignore the value written here. + return &interpreter_state->CP15[CP15_DATA_MEMORY_BARRIER]; + default: + return boost::blank{}; + } + } + + if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0 && opc2 == 2) { + return &interpreter_state->CP15[CP15_THREAD_UPRW]; + } + + return boost::blank{}; +} + +CallbackOrAccessTwoWords DynarmicCP15::CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) { + return boost::blank{}; +} + +CallbackOrAccessOneWord DynarmicCP15::CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn, + CoprocReg CRm, unsigned opc2) { + // TODO(merry): Privileged CP15 registers + + if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0) { + switch (opc2) { + case 2: + return &interpreter_state->CP15[CP15_THREAD_UPRW]; + case 3: + return &interpreter_state->CP15[CP15_THREAD_URO]; + default: + return boost::blank{}; + } + } + + return boost::blank{}; +} + +CallbackOrAccessTwoWords DynarmicCP15::CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) { + return boost::blank{}; +} + +boost::optional<Callback> DynarmicCP15::CompileLoadWords(bool two, bool long_transfer, + CoprocReg CRd, + boost::optional<u8> option) { + return boost::none; +} + +boost::optional<Callback> DynarmicCP15::CompileStoreWords(bool two, bool long_transfer, + CoprocReg CRd, + boost::optional<u8> option) { + return boost::none; +} diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.h b/src/core/arm/dynarmic/arm_dynarmic_cp15.h new file mode 100644 index 000000000..7fa54e14c --- /dev/null +++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.h @@ -0,0 +1,32 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include <dynarmic/coprocessor.h> +#include "common/common_types.h" + +struct ARMul_State; + +class DynarmicCP15 final : public Dynarmic::Coprocessor { +public: + explicit DynarmicCP15(const std::shared_ptr<ARMul_State>&); + ~DynarmicCP15() override; + + boost::optional<Callback> CompileInternalOperation(bool two, unsigned opc1, CoprocReg CRd, + CoprocReg CRn, CoprocReg CRm, + unsigned opc2) override; + CallbackOrAccessOneWord CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn, + CoprocReg CRm, unsigned opc2) override; + CallbackOrAccessTwoWords CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) override; + CallbackOrAccessOneWord CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm, + unsigned opc2) override; + CallbackOrAccessTwoWords CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) override; + boost::optional<Callback> CompileLoadWords(bool two, bool long_transfer, CoprocReg CRd, + boost::optional<u8> option) override; + boost::optional<Callback> CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd, + boost::optional<u8> option) override; + +private: + std::shared_ptr<ARMul_State> interpreter_state; +}; diff --git a/src/core/core.cpp b/src/core/core.cpp index ee5237096..202cd332b 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -159,6 +159,7 @@ void System::Shutdown() { Kernel::Shutdown(); HW::Shutdown(); CoreTiming::Shutdown(); + cpu_core.reset(); LOG_DEBUG(Core, "Shutdown OK"); } diff --git a/src/core/core.h b/src/core/core.h index 1015e8847..17572a74f 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -115,7 +115,7 @@ private: static System s_instance; }; -static ARM_Interface& CPU() { +inline ARM_Interface& CPU() { return System::GetInstance().CPU(); } diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index a437d0823..276ecfdf6 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -13,7 +13,7 @@ #include "core/core.h" #include "core/core_timing.h" -int g_clock_rate_arm11 = 268123480; +int g_clock_rate_arm11 = BASE_CLOCK_RATE_ARM11; // is this really necessary? #define INITIAL_SLICE_LENGTH 20000 diff --git a/src/core/core_timing.h b/src/core/core_timing.h index b72a1b500..d2f85cd4d 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -21,6 +21,7 @@ // inside callback: // ScheduleEvent(periodInCycles - cycles_late, callback, "whatever") +constexpr int BASE_CLOCK_RATE_ARM11 = 268123480; extern int g_clock_rate_arm11; inline s64 msToCycles(int ms) { diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp index 51ce78435..dd2fb167f 100644 --- a/src/core/file_sys/archive_extsavedata.cpp +++ b/src/core/file_sys/archive_extsavedata.cpp @@ -107,6 +107,8 @@ public: case PathParser::NotFound: LOG_ERROR(Service_FS, "%s not found", full_path.c_str()); return ERROR_FILE_NOT_FOUND; + case PathParser::FileFound: + break; // Expected 'success' case } FileUtil::IOFile file(full_path, "r+b"); diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp index 333dfb92e..72ff05c65 100644 --- a/src/core/file_sys/archive_sdmc.cpp +++ b/src/core/file_sys/archive_sdmc.cpp @@ -72,6 +72,8 @@ ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFileBase(const Path& pa FileUtil::CreateEmptyFile(full_path); } break; + case PathParser::FileFound: + break; // Expected 'success' case } FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb"); @@ -106,6 +108,8 @@ ResultCode SDMCArchive::DeleteFile(const Path& path) const { case PathParser::DirectoryFound: LOG_ERROR(Service_FS, "%s is not a file", full_path.c_str()); return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; + case PathParser::FileFound: + break; // Expected 'success' case } if (FileUtil::Delete(full_path)) { @@ -154,6 +158,8 @@ static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mou case PathParser::FileFound: LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; + case PathParser::DirectoryFound: + break; // Expected 'success' case } if (deleter(full_path)) { @@ -197,6 +203,8 @@ ResultCode SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const { case PathParser::FileFound: LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); return ERROR_ALREADY_EXISTS; + case PathParser::NotFound: + break; // Expected 'success' case } if (size == 0) { @@ -238,6 +246,8 @@ ResultCode SDMCArchive::CreateDirectory(const Path& path) const { case PathParser::FileFound: LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); return ERROR_ALREADY_EXISTS; + case PathParser::NotFound: + break; // Expected 'success' case } if (FileUtil::CreateDir(mount_point + path.AsString())) { @@ -281,6 +291,8 @@ ResultVal<std::unique_ptr<DirectoryBackend>> SDMCArchive::OpenDirectory(const Pa case PathParser::FileInPath: LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; + case PathParser::DirectoryFound: + break; // Expected 'success' case } auto directory = std::make_unique<DiskDirectory>(full_path); diff --git a/src/core/file_sys/savedata_archive.cpp b/src/core/file_sys/savedata_archive.cpp index f2e6a06bc..f540c4a93 100644 --- a/src/core/file_sys/savedata_archive.cpp +++ b/src/core/file_sys/savedata_archive.cpp @@ -57,6 +57,8 @@ ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& pa FileUtil::CreateEmptyFile(full_path); } break; + case PathParser::FileFound: + break; // Expected 'success' case } FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb"); @@ -91,6 +93,8 @@ ResultCode SaveDataArchive::DeleteFile(const Path& path) const { case PathParser::NotFound: LOG_ERROR(Service_FS, "File not found %s", full_path.c_str()); return ERROR_FILE_NOT_FOUND; + case PathParser::FileFound: + break; // Expected 'success' case } if (FileUtil::Delete(full_path)) { @@ -139,6 +143,8 @@ static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mou case PathParser::FileFound: LOG_ERROR(Service_FS, "Unexpected file or directory %s", full_path.c_str()); return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; + case PathParser::DirectoryFound: + break; // Expected 'success' case } if (deleter(full_path)) { @@ -182,6 +188,8 @@ ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) cons case PathParser::FileFound: LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); return ERROR_FILE_ALREADY_EXISTS; + case PathParser::NotFound: + break; // Expected 'success' case } if (size == 0) { @@ -225,6 +233,8 @@ ResultCode SaveDataArchive::CreateDirectory(const Path& path) const { case PathParser::FileFound: LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); return ERROR_DIRECTORY_ALREADY_EXISTS; + case PathParser::NotFound: + break; // Expected 'success' case } if (FileUtil::CreateDir(mount_point + path.AsString())) { @@ -269,6 +279,8 @@ ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory( case PathParser::FileFound: LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; + case PathParser::DirectoryFound: + break; // Expected 'success' case } auto directory = std::make_unique<DiskDirectory>(full_path); diff --git a/src/core/frontend/camera/blank_camera.cpp b/src/core/frontend/camera/blank_camera.cpp new file mode 100644 index 000000000..7995abcbd --- /dev/null +++ b/src/core/frontend/camera/blank_camera.cpp @@ -0,0 +1,31 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/frontend/camera/blank_camera.h" + +namespace Camera { + +void BlankCamera::StartCapture() {} + +void BlankCamera::StopCapture() {} + +void BlankCamera::SetFormat(Service::CAM::OutputFormat output_format) { + output_rgb = output_format == Service::CAM::OutputFormat::RGB565; +} + +void BlankCamera::SetResolution(const Service::CAM::Resolution& resolution) { + width = resolution.width; + height = resolution.height; +}; + +void BlankCamera::SetFlip(Service::CAM::Flip) {} + +void BlankCamera::SetEffect(Service::CAM::Effect) {} + +std::vector<u16> BlankCamera::ReceiveFrame() const { + // Note: 0x80008000 stands for two black pixels in YUV422 + return std::vector<u16>(width * height, output_rgb ? 0 : 0x8000); +} + +} // namespace Camera diff --git a/src/core/frontend/camera/blank_camera.h b/src/core/frontend/camera/blank_camera.h new file mode 100644 index 000000000..c6619bd88 --- /dev/null +++ b/src/core/frontend/camera/blank_camera.h @@ -0,0 +1,28 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/frontend/camera/factory.h" +#include "core/frontend/camera/interface.h" + +namespace Camera { + +class BlankCamera final : public CameraInterface { +public: + void StartCapture() override; + void StopCapture() override; + void SetResolution(const Service::CAM::Resolution&) override; + void SetFlip(Service::CAM::Flip) override; + void SetEffect(Service::CAM::Effect) override; + void SetFormat(Service::CAM::OutputFormat) override; + std::vector<u16> ReceiveFrame() const override; + +private: + int width = 0; + int height = 0; + bool output_rgb = false; +}; + +} // namespace Camera diff --git a/src/core/frontend/camera/factory.cpp b/src/core/frontend/camera/factory.cpp new file mode 100644 index 000000000..4b4da50dd --- /dev/null +++ b/src/core/frontend/camera/factory.cpp @@ -0,0 +1,32 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <unordered_map> +#include "common/logging/log.h" +#include "core/frontend/camera/blank_camera.h" +#include "core/frontend/camera/factory.h" + +namespace Camera { + +static std::unordered_map<std::string, std::unique_ptr<CameraFactory>> factories; + +CameraFactory::~CameraFactory() = default; + +void RegisterFactory(const std::string& name, std::unique_ptr<CameraFactory> factory) { + factories[name] = std::move(factory); +} + +std::unique_ptr<CameraInterface> CreateCamera(const std::string& name, const std::string& config) { + auto pair = factories.find(name); + if (pair != factories.end()) { + return pair->second->Create(config); + } + + if (name != "blank") { + LOG_ERROR(Service_CAM, "Unknown camera \"%s\"", name.c_str()); + } + return std::make_unique<BlankCamera>(); +} + +} // namespace Camera diff --git a/src/core/frontend/camera/factory.h b/src/core/frontend/camera/factory.h new file mode 100644 index 000000000..d68be16e5 --- /dev/null +++ b/src/core/frontend/camera/factory.h @@ -0,0 +1,41 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> +#include "core/frontend/camera/interface.h" + +namespace Camera { + +class CameraFactory { +public: + virtual ~CameraFactory(); + + /** + * Creates a camera object based on the configuration string. + * @params config Configuration string to create the camera. The implementation can decide the + * meaning of this string. + * @returns a unique_ptr to the created camera object. + */ + virtual std::unique_ptr<CameraInterface> Create(const std::string& config) const = 0; +}; + +/** + * Registers an external camera factory. + * @param name Identifier of the camera factory. + * @param factory Camera factory to register. + */ +void RegisterFactory(const std::string& name, std::unique_ptr<CameraFactory> factory); + +/** + * Creates a camera from the factory. + * @param name Identifier of the camera factory. + * @param config Configuration string to create the camera. The meaning of this string is + * defined by the factory. + */ +std::unique_ptr<CameraInterface> CreateCamera(const std::string& name, const std::string& config); + +} // namespace Camera diff --git a/src/core/frontend/camera/interface.cpp b/src/core/frontend/camera/interface.cpp new file mode 100644 index 000000000..9aec9e7f1 --- /dev/null +++ b/src/core/frontend/camera/interface.cpp @@ -0,0 +1,11 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/frontend/camera/interface.h" + +namespace Camera { + +CameraInterface::~CameraInterface() = default; + +} // namespace Camera diff --git a/src/core/frontend/camera/interface.h b/src/core/frontend/camera/interface.h new file mode 100644 index 000000000..a55a495c9 --- /dev/null +++ b/src/core/frontend/camera/interface.h @@ -0,0 +1,61 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include "common/common_types.h" +#include "core/hle/service/cam/cam.h" + +namespace Camera { + +/// An abstract class standing for a camera. All camera implementations should inherit from this. +class CameraInterface { +public: + virtual ~CameraInterface(); + + /// Starts the camera for video capturing. + virtual void StartCapture() = 0; + + /// Stops the camera for video capturing. + virtual void StopCapture() = 0; + + /** + * Sets the video resolution from raw CAM service parameters. + * For the meaning of the parameters, please refer to Service::CAM::Resolution. Note that the + * actual camera implementation doesn't need to respect all the parameters. However, the width + * and the height parameters must be respected and be used to determine the size of output + * frames. + * @param resolution The resolution parameters to set + */ + virtual void SetResolution(const Service::CAM::Resolution& resolution) = 0; + + /** + * Configures how received frames should be flipped by the camera. + * @param flip Flip applying to the frame + */ + virtual void SetFlip(Service::CAM::Flip flip) = 0; + + /** + * Configures what effect should be applied to received frames by the camera. + * @param effect Effect applying to the frame + */ + virtual void SetEffect(Service::CAM::Effect effect) = 0; + + /** + * Sets the output format of the all frames received after this function is called. + * @param format Output format of the frame + */ + virtual void SetFormat(Service::CAM::OutputFormat format) = 0; + + /** + * Receives a frame from the camera. + * This function should be only called between a StartCapture call and a StopCapture call. + * @returns A std::vector<u16> containing pixels. The total size of the vector is width * height + * where width and height are set by a call to SetResolution. + */ + virtual std::vector<u16> ReceiveFrame() const = 0; +}; + +} // namespace Camera diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index f6f90f9e1..4f0f786ce 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -5,6 +5,7 @@ #include <algorithm> #include <cmath> #include "common/assert.h" +#include "common/profiler_reporting.h" #include "core/frontend/emu_window.h" #include "core/frontend/key_map.h" #include "video_core/video_core.h" @@ -89,6 +90,30 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) { TouchPressed(framebuffer_x, framebuffer_y); } +void EmuWindow::AccelerometerChanged(float x, float y, float z) { + constexpr float coef = 512; + + std::lock_guard<std::mutex> lock(accel_mutex); + + // TODO(wwylele): do a time stretch as it in GyroscopeChanged + // The time stretch formula should be like + // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity + accel_x = static_cast<s16>(x * coef); + accel_y = static_cast<s16>(y * coef); + accel_z = static_cast<s16>(z * coef); +} + +void EmuWindow::GyroscopeChanged(float x, float y, float z) { + constexpr float FULL_FPS = 60; + float coef = GetGyroscopeRawToDpsCoefficient(); + float stretch = + FULL_FPS / Common::Profiling::GetTimingResultsAggregator()->GetAggregatedResults().fps; + std::lock_guard<std::mutex> lock(gyro_mutex); + gyro_x = static_cast<s16>(x * coef * stretch); + gyro_y = static_cast<s16>(y * coef * stretch); + gyro_z = static_cast<s16>(z * coef * stretch); +} + void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) { Layout::FramebufferLayout layout; switch (Settings::values.layout_option) { diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 835c4d500..1ba64c92b 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -4,6 +4,7 @@ #pragma once +#include <mutex> #include <tuple> #include <utility> #include "common/common_types.h" @@ -93,6 +94,27 @@ public: void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y); /** + * Signal accelerometer state has changed. + * @param x X-axis accelerometer value + * @param y Y-axis accelerometer value + * @param z Z-axis accelerometer value + * @note all values are in unit of g (gravitational acceleration). + * e.g. x = 1.0 means 9.8m/s^2 in x direction. + * @see GetAccelerometerState for axis explanation. + */ + void AccelerometerChanged(float x, float y, float z); + + /** + * Signal gyroscope state has changed. + * @param x X-axis accelerometer value + * @param y Y-axis accelerometer value + * @param z Z-axis accelerometer value + * @note all values are in deg/sec. + * @see GetGyroscopeState for axis explanation. + */ + void GyroscopeChanged(float x, float y, float z); + + /** * Gets the current pad state (which buttons are pressed). * @note This should be called by the core emu thread to get a state set by the window thread. * @note This doesn't include analog input like circle pad direction @@ -134,12 +156,11 @@ public: * 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); + std::tuple<s16, s16, s16> GetAccelerometerState() { + std::lock_guard<std::mutex> lock(accel_mutex); + return std::make_tuple(accel_x, accel_y, accel_z); } /** @@ -153,12 +174,11 @@ public: * 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); + std::tuple<s16, s16, s16> GetGyroscopeState() { + std::lock_guard<std::mutex> lock(gyro_mutex); + return std::make_tuple(gyro_x, gyro_y, gyro_z); } /** @@ -216,6 +236,12 @@ protected: circle_pad_x = 0; circle_pad_y = 0; touch_pressed = false; + accel_x = 0; + accel_y = -512; + accel_z = 0; + gyro_x = 0; + gyro_y = 0; + gyro_z = 0; } virtual ~EmuWindow() {} @@ -281,6 +307,16 @@ private: s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156) s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156) + std::mutex accel_mutex; + s16 accel_x; ///< Accelerometer X-axis value in native 3DS units + s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units + s16 accel_z; ///< Accelerometer Z-axis value in native 3DS units + + std::mutex gyro_mutex; + s16 gyro_x; ///< Gyroscope X-axis value in native 3DS units + s16 gyro_y; ///< Gyroscope Y-axis value in native 3DS units + s16 gyro_z; ///< Gyroscope Z-axis value in native 3DS units + /** * Clip the provided coordinates to be inside the touchscreen area. */ diff --git a/src/core/frontend/motion_emu.cpp b/src/core/frontend/motion_emu.cpp new file mode 100644 index 000000000..9a5b3185d --- /dev/null +++ b/src/core/frontend/motion_emu.cpp @@ -0,0 +1,89 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/math_util.h" +#include "common/quaternion.h" +#include "core/frontend/emu_window.h" +#include "core/frontend/motion_emu.h" + +namespace Motion { + +static constexpr int update_millisecond = 100; +static constexpr auto update_duration = + std::chrono::duration_cast<std::chrono::steady_clock::duration>( + std::chrono::milliseconds(update_millisecond)); + +MotionEmu::MotionEmu(EmuWindow& emu_window) + : motion_emu_thread(&MotionEmu::MotionEmuThread, this, std::ref(emu_window)) {} + +MotionEmu::~MotionEmu() { + if (motion_emu_thread.joinable()) { + shutdown_event.Set(); + motion_emu_thread.join(); + } +} + +void MotionEmu::MotionEmuThread(EmuWindow& emu_window) { + auto update_time = std::chrono::steady_clock::now(); + Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0); + Math::Quaternion<float> old_q; + + while (!shutdown_event.WaitUntil(update_time)) { + update_time += update_duration; + old_q = q; + + { + std::lock_guard<std::mutex> guard(tilt_mutex); + + // Find the quaternion describing current 3DS tilting + q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x), + tilt_angle); + } + + auto inv_q = q.Inverse(); + + // Set the gravity vector in world space + auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f); + + // Find the angular rate vector in world space + auto angular_rate = ((q - old_q) * inv_q).xyz * 2; + angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180; + + // Transform the two vectors from world space to 3DS space + gravity = QuaternionRotate(inv_q, gravity); + angular_rate = QuaternionRotate(inv_q, angular_rate); + + // Update the sensor state + emu_window.AccelerometerChanged(gravity.x, gravity.y, gravity.z); + emu_window.GyroscopeChanged(angular_rate.x, angular_rate.y, angular_rate.z); + } +} + +void MotionEmu::BeginTilt(int x, int y) { + mouse_origin = Math::MakeVec(x, y); + is_tilting = true; +} + +void MotionEmu::Tilt(int x, int y) { + constexpr float SENSITIVITY = 0.01f; + auto mouse_move = Math::MakeVec(x, y) - mouse_origin; + if (is_tilting) { + std::lock_guard<std::mutex> guard(tilt_mutex); + if (mouse_move.x == 0 && mouse_move.y == 0) { + tilt_angle = 0; + } else { + tilt_direction = mouse_move.Cast<float>(); + tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * SENSITIVITY, 0.0f, + MathUtil::PI * 0.5f); + } + } +} + +void MotionEmu::EndTilt() { + std::lock_guard<std::mutex> guard(tilt_mutex); + tilt_angle = 0; + is_tilting = false; +} + +} // namespace Motion diff --git a/src/core/frontend/motion_emu.h b/src/core/frontend/motion_emu.h new file mode 100644 index 000000000..99d41a726 --- /dev/null +++ b/src/core/frontend/motion_emu.h @@ -0,0 +1,52 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include "common/thread.h" +#include "common/vector_math.h" + +class EmuWindow; + +namespace Motion { + +class MotionEmu final { +public: + MotionEmu(EmuWindow& emu_window); + ~MotionEmu(); + + /** + * Signals that a motion sensor tilt has begun. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + */ + void BeginTilt(int x, int y); + + /** + * Signals that a motion sensor tilt is occurring. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + */ + void Tilt(int x, int y); + + /** + * Signals that a motion sensor tilt has ended. + */ + void EndTilt(); + +private: + Math::Vec2<int> mouse_origin; + + std::mutex tilt_mutex; + Math::Vec2<float> tilt_direction; + float tilt_angle = 0; + + bool is_tilting = false; + + Common::Event shutdown_event; + std::thread motion_emu_thread; + + void MotionEmuThread(EmuWindow& emu_window); +}; + +} // namespace Motion diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index d88e25073..5cf45ada5 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -57,7 +57,6 @@ const u32 SIGTERM = 15; const u32 MSG_WAITALL = 8; #endif -const u32 R0_REGISTER = 0; const u32 R15_REGISTER = 15; const u32 CPSR_REGISTER = 25; const u32 FPSCR_REGISTER = 58; @@ -816,10 +815,6 @@ static void RemoveBreakpoint() { auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset)); - start_offset = addr_pos + 1; - u32 len = - HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset)); - if (type == BreakpointType::Access) { // Access is made up of Read and Write types, so add both breakpoints type = BreakpointType::Read; diff --git a/src/core/hle/applets/applet.cpp b/src/core/hle/applets/applet.cpp index 645b2d5fe..9c43ed2fd 100644 --- a/src/core/hle/applets/applet.cpp +++ b/src/core/hle/applets/applet.cpp @@ -12,6 +12,7 @@ #include "core/hle/applets/applet.h" #include "core/hle/applets/erreula.h" #include "core/hle/applets/mii_selector.h" +#include "core/hle/applets/mint.h" #include "core/hle/applets/swkbd.h" #include "core/hle/result.h" #include "core/hle/service/apt/apt.h" @@ -56,6 +57,10 @@ ResultCode Applet::Create(Service::APT::AppletId id) { case Service::APT::AppletId::Error2: applets[id] = std::make_shared<ErrEula>(id); break; + case Service::APT::AppletId::Mint: + case Service::APT::AppletId::Mint2: + applets[id] = std::make_shared<Mint>(id); + break; default: LOG_ERROR(Service_APT, "Could not create applet %u", id); // TODO(Subv): Find the right error code diff --git a/src/core/hle/applets/mint.cpp b/src/core/hle/applets/mint.cpp new file mode 100644 index 000000000..31a79ea17 --- /dev/null +++ b/src/core/hle/applets/mint.cpp @@ -0,0 +1,72 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/string_util.h" +#include "core/hle/applets/mint.h" +#include "core/hle/service/apt/apt.h" + +namespace HLE { +namespace Applets { + +ResultCode Mint::ReceiveParameter(const Service::APT::MessageParameter& parameter) { + if (parameter.signal != static_cast<u32>(Service::APT::SignalType::Request)) { + LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal); + UNIMPLEMENTED(); + // TODO(Subv): Find the right error code + return ResultCode(-1); + } + + // The Request message contains a buffer with the size of the framebuffer shared + // memory. + // Create the SharedMemory that will hold the framebuffer data + Service::APT::CaptureBufferInfo capture_info; + ASSERT(sizeof(capture_info) == parameter.buffer.size()); + + memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info)); + + // TODO: allocated memory never released + using Kernel::MemoryPermission; + // Allocate a heap block of the required size for this applet. + heap_memory = std::make_shared<std::vector<u8>>(capture_info.size); + // Create a SharedMemory that directly points to this heap block. + framebuffer_memory = Kernel::SharedMemory::CreateForApplet( + heap_memory, 0, heap_memory->size(), MemoryPermission::ReadWrite, + MemoryPermission::ReadWrite, "Mint Memory"); + + // Send the response message with the newly created SharedMemory + Service::APT::MessageParameter result; + result.signal = static_cast<u32>(Service::APT::SignalType::Response); + result.buffer.clear(); + result.destination_id = static_cast<u32>(Service::APT::AppletId::Application); + result.sender_id = static_cast<u32>(id); + result.object = framebuffer_memory; + + Service::APT::SendParameter(result); + return RESULT_SUCCESS; +} + +ResultCode Mint::StartImpl(const Service::APT::AppletStartupParameter& parameter) { + is_running = true; + + // TODO(Subv): Set the expected fields in the response buffer before resending it to the + // application. + // TODO(Subv): Reverse the parameter format for the Mint applet + + // Let the application know that we're closing + Service::APT::MessageParameter message; + message.buffer.resize(parameter.buffer.size()); + std::fill(message.buffer.begin(), message.buffer.end(), 0); + message.signal = static_cast<u32>(Service::APT::SignalType::WakeupByExit); + message.destination_id = static_cast<u32>(Service::APT::AppletId::Application); + message.sender_id = static_cast<u32>(id); + Service::APT::SendParameter(message); + + is_running = false; + return RESULT_SUCCESS; +} + +void Mint::Update() {} + +} // namespace Applets +} // namespace HLE diff --git a/src/core/hle/applets/mint.h b/src/core/hle/applets/mint.h new file mode 100644 index 000000000..d23dc40f9 --- /dev/null +++ b/src/core/hle/applets/mint.h @@ -0,0 +1,29 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/applets/applet.h" +#include "core/hle/kernel/shared_memory.h" + +namespace HLE { +namespace Applets { + +class Mint final : public Applet { +public: + explicit Mint(Service::APT::AppletId id) : Applet(id) {} + + ResultCode ReceiveParameter(const Service::APT::MessageParameter& parameter) override; + ResultCode StartImpl(const Service::APT::AppletStartupParameter& parameter) override; + void Update() override; + +private: + /// This SharedMemory will be created when we receive the Request message. + /// It holds the framebuffer info retrieved by the application with + /// GSPGPU::ImportDisplayCaptureInfo + Kernel::SharedPtr<Kernel::SharedMemory> framebuffer_memory; +}; + +} // namespace Applets +} // namespace HLE diff --git a/src/core/hle/config_mem.cpp b/src/core/hle/config_mem.cpp index ccd73cfcb..e386ccdc6 100644 --- a/src/core/hle/config_mem.cpp +++ b/src/core/hle/config_mem.cpp @@ -14,15 +14,18 @@ ConfigMemDef config_mem; void Init() { std::memset(&config_mem, 0, sizeof(config_mem)); - config_mem.update_flag = 0; // No update + // Values extracted from firmware 11.2.0-35E + config_mem.kernel_version_min = 0x34; + config_mem.kernel_version_maj = 0x2; + config_mem.ns_tid = 0x0004013000008002; config_mem.sys_core_ver = 0x2; config_mem.unit_info = 0x1; // Bit 0 set for Retail - config_mem.prev_firm = 0; - config_mem.firm_unk = 0; - config_mem.firm_version_rev = 0; - config_mem.firm_version_min = 0x40; + config_mem.prev_firm = 0x1; + config_mem.ctr_sdk_ver = 0x0000F297; + config_mem.firm_version_min = 0x34; config_mem.firm_version_maj = 0x2; config_mem.firm_sys_core_ver = 0x2; + config_mem.firm_ctr_sdk_ver = 0x0000F297; } } // namespace diff --git a/src/core/hle/hle.cpp b/src/core/hle/hle.cpp deleted file mode 100644 index d73d98a70..000000000 --- a/src/core/hle/hle.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/assert.h" -#include "common/logging/log.h" -#include "core/arm/arm_interface.h" -#include "core/core.h" -#include "core/hle/hle.h" -#include "core/hle/service/service.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -namespace { - -bool reschedule; ///< If true, immediately reschedules the CPU to a new thread -} - -namespace HLE { - -void Reschedule(const char* reason) { - DEBUG_ASSERT_MSG(reason != nullptr && strlen(reason) < 256, - "Reschedule: Invalid or too long reason."); - - // TODO(bunnei): It seems that games depend on some CPU execution time elapsing during HLE - // routines. This simulates that time by artificially advancing the number of CPU "ticks". - // The value was chosen empirically, it seems to work well enough for everything tested, but - // is likely not ideal. We should find a more accurate way to simulate timing with HLE. - Core::AppCore().AddTicks(4000); - - Core::AppCore().PrepareReschedule(); - - reschedule = true; -} - -bool IsReschedulePending() { - return reschedule; -} - -void DoneRescheduling() { - reschedule = false; -} - -void Init() { - Service::Init(); - - reschedule = false; - - LOG_DEBUG(Kernel, "initialized OK"); -} - -void Shutdown() { - Service::Shutdown(); - - LOG_DEBUG(Kernel, "shutdown OK"); -} - -} // namespace diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/event.cpp index 3e116e3df..23f9df0d6 100644 --- a/src/core/hle/kernel/event.cpp +++ b/src/core/hle/kernel/event.cpp @@ -22,23 +22,17 @@ SharedPtr<Event> Event::Create(ResetType reset_type, std::string name) { evt->reset_type = reset_type; evt->name = std::move(name); - if (reset_type == ResetType::Pulse) { - LOG_ERROR(Kernel, "Unimplemented event reset type Pulse"); - UNIMPLEMENTED(); - } - return evt; } -bool Event::ShouldWait() { +bool Event::ShouldWait(Thread* thread) const { return !signaled; } -void Event::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Event::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); - // Release the event if it's not sticky... - if (reset_type != ResetType::Sticky) + if (reset_type == ResetType::OneShot) signaled = false; } @@ -51,4 +45,11 @@ void Event::Clear() { signaled = false; } +void Event::WakeupAllWaitingThreads() { + WaitObject::WakeupAllWaitingThreads(); + + if (reset_type == ResetType::Pulse) + signaled = false; +} + } // namespace diff --git a/src/core/hle/kernel/event.h b/src/core/hle/kernel/event.h index 8dcd23edb..3e3673508 100644 --- a/src/core/hle/kernel/event.h +++ b/src/core/hle/kernel/event.h @@ -35,8 +35,10 @@ public: bool signaled; ///< Whether the event has already been signaled std::string name; ///< Name of event (optional) - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; + + void WakeupAllWaitingThreads() override; void Signal(); void Clear(); diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 1db8e102f..f599916f0 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -3,7 +3,6 @@ // Refer to the license.txt file included. #include <algorithm> -#include <boost/range/algorithm_ext/erase.hpp> #include "common/assert.h" #include "common/logging/log.h" #include "core/hle/config_mem.h" @@ -28,32 +27,39 @@ void WaitObject::AddWaitingThread(SharedPtr<Thread> thread) { void WaitObject::RemoveWaitingThread(Thread* thread) { auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); + // If a thread passed multiple handles to the same object, + // the kernel might attempt to remove the thread from the object's + // waiting threads list multiple times. if (itr != waiting_threads.end()) waiting_threads.erase(itr); } SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { - // Remove the threads that are ready or already running from our waitlist - boost::range::remove_erase_if(waiting_threads, [](const SharedPtr<Thread>& thread) { - return thread->status == THREADSTATUS_RUNNING || thread->status == THREADSTATUS_READY || - thread->status == THREADSTATUS_DEAD; - }); - - // TODO(Subv): This call should be performed inside the loop below to check if an object can be - // acquired by a particular thread. This is useful for things like recursive locking of Mutexes. - if (ShouldWait()) - return nullptr; - Thread* candidate = nullptr; s32 candidate_priority = THREADPRIO_LOWEST + 1; for (const auto& thread : waiting_threads) { + // The list of waiting threads must not contain threads that are not waiting to be awakened. + ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY || + thread->status == THREADSTATUS_WAIT_SYNCH_ALL, + "Inconsistent thread statuses in waiting_threads"); + if (thread->current_priority >= candidate_priority) continue; - bool ready_to_run = - std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), - [](const SharedPtr<WaitObject>& object) { return object->ShouldWait(); }); + if (ShouldWait(thread.get())) + continue; + + // A thread is ready to run if it's either in THREADSTATUS_WAIT_SYNCH_ANY or + // in THREADSTATUS_WAIT_SYNCH_ALL and the rest of the objects it is waiting on are ready. + bool ready_to_run = true; + if (thread->status == THREADSTATUS_WAIT_SYNCH_ALL) { + ready_to_run = std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), + [&thread](const SharedPtr<WaitObject>& object) { + return object->ShouldWait(thread.get()); + }); + } + if (ready_to_run) { candidate = thread.get(); candidate_priority = thread->current_priority; @@ -66,7 +72,7 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { void WaitObject::WakeupAllWaitingThreads() { while (auto thread = GetHighestPriorityReadyThread()) { if (!thread->IsSleepingOnWaitAll()) { - Acquire(); + Acquire(thread.get()); // Set the output index of the WaitSynchronizationN call to the index of this object. if (thread->wait_set_output) { thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(this)); @@ -74,18 +80,17 @@ void WaitObject::WakeupAllWaitingThreads() { } } else { for (auto& object : thread->wait_objects) { - object->Acquire(); - object->RemoveWaitingThread(thread.get()); + object->Acquire(thread.get()); } // Note: This case doesn't update the output index of WaitSynchronizationN. - // Clear the thread's waitlist - thread->wait_objects.clear(); } + for (auto& object : thread->wait_objects) + object->RemoveWaitingThread(thread.get()); + thread->wait_objects.clear(); + thread->SetWaitSynchronizationResult(RESULT_SUCCESS); thread->ResumeFromWait(); - // Note: Removing the thread from the object's waitlist will be - // done by GetHighestPriorityReadyThread. } } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 9503e7d04..bb8b99bb5 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -132,31 +132,32 @@ using SharedPtr = boost::intrusive_ptr<T>; class WaitObject : public Object { public: /** - * Check if the current thread should wait until the object is available + * Check if the specified thread should wait until the object is available + * @param thread The thread about which we're deciding. * @return True if the current thread should wait due to this object being unavailable */ - virtual bool ShouldWait() = 0; + virtual bool ShouldWait(Thread* thread) const = 0; - /// Acquire/lock the object if it is available - virtual void Acquire() = 0; + /// Acquire/lock the object for the specified thread if it is available + virtual void Acquire(Thread* thread) = 0; /** * Add a thread to wait on this object * @param thread Pointer to thread to add */ - void AddWaitingThread(SharedPtr<Thread> thread); + virtual void AddWaitingThread(SharedPtr<Thread> thread); /** * Removes a thread from waiting on this object (e.g. if it was resumed already) * @param thread Pointer to thread to remove */ - void RemoveWaitingThread(Thread* thread); + virtual void RemoveWaitingThread(Thread* thread); /** * Wake up all threads waiting on this object that can be awoken, in priority order, * and set the synchronization result and output of the thread. */ - void WakeupAllWaitingThreads(); + virtual void WakeupAllWaitingThreads(); /// Obtains the highest priority thread that is ready to run from this object's waiting list. SharedPtr<Thread> GetHighestPriorityReadyThread(); diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp index 736944bae..cef961289 100644 --- a/src/core/hle/kernel/mutex.cpp +++ b/src/core/hle/kernel/mutex.cpp @@ -6,26 +6,18 @@ #include <vector> #include <boost/range/algorithm_ext/erase.hpp> #include "common/assert.h" +#include "core/core.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/thread.h" namespace Kernel { -/** - * Resumes a thread waiting for the specified mutex - * @param mutex The mutex that some thread is waiting on - */ -static void ResumeWaitingThread(Mutex* mutex) { - // Reset mutex lock thread handle, nothing is waiting - mutex->lock_count = 0; - mutex->holding_thread = nullptr; - mutex->WakeupAllWaitingThreads(); -} - void ReleaseThreadMutexes(Thread* thread) { for (auto& mtx : thread->held_mutexes) { - ResumeWaitingThread(mtx.get()); + mtx->lock_count = 0; + mtx->holding_thread = nullptr; + mtx->WakeupAllWaitingThreads(); } thread->held_mutexes.clear(); } @@ -40,52 +32,74 @@ SharedPtr<Mutex> Mutex::Create(bool initial_locked, std::string name) { mutex->name = std::move(name); mutex->holding_thread = nullptr; - // Acquire mutex with current thread if initialized as locked... + // Acquire mutex with current thread if initialized as locked if (initial_locked) - mutex->Acquire(); + mutex->Acquire(GetCurrentThread()); return mutex; } -bool Mutex::ShouldWait() { - auto thread = GetCurrentThread(); - bool wait = lock_count > 0 && holding_thread != thread; - - // If the holding thread of the mutex is lower priority than this thread, that thread should - // temporarily inherit this thread's priority - if (wait && thread->current_priority < holding_thread->current_priority) - holding_thread->BoostPriority(thread->current_priority); - - return wait; -} - -void Mutex::Acquire() { - Acquire(GetCurrentThread()); +bool Mutex::ShouldWait(Thread* thread) const { + return lock_count > 0 && thread != holding_thread; } -void Mutex::Acquire(SharedPtr<Thread> thread) { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Mutex::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); - // Actually "acquire" the mutex only if we don't already have it... + // Actually "acquire" the mutex only if we don't already have it if (lock_count == 0) { + priority = thread->current_priority; thread->held_mutexes.insert(this); - holding_thread = std::move(thread); + holding_thread = thread; + thread->UpdatePriority(); + Core::System::GetInstance().PrepareReschedule(); } lock_count++; } void Mutex::Release() { - // Only release if the mutex is held... + // Only release if the mutex is held if (lock_count > 0) { lock_count--; - // Yield to the next thread only if we've fully released the mutex... + // Yield to the next thread only if we've fully released the mutex if (lock_count == 0) { holding_thread->held_mutexes.erase(this); - ResumeWaitingThread(this); + holding_thread->UpdatePriority(); + holding_thread = nullptr; + WakeupAllWaitingThreads(); + Core::System::GetInstance().PrepareReschedule(); } } } +void Mutex::AddWaitingThread(SharedPtr<Thread> thread) { + WaitObject::AddWaitingThread(thread); + thread->pending_mutexes.insert(this); + UpdatePriority(); +} + +void Mutex::RemoveWaitingThread(Thread* thread) { + WaitObject::RemoveWaitingThread(thread); + thread->pending_mutexes.erase(this); + UpdatePriority(); +} + +void Mutex::UpdatePriority() { + if (!holding_thread) + return; + + s32 best_priority = THREADPRIO_LOWEST; + for (auto& waiter : GetWaitingThreads()) { + if (waiter->current_priority < best_priority) + best_priority = waiter->current_priority; + } + + if (best_priority != priority) { + priority = best_priority; + holding_thread->UpdatePriority(); + } +} + } // namespace diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h index 53c3dc1f1..c57adf400 100644 --- a/src/core/hle/kernel/mutex.h +++ b/src/core/hle/kernel/mutex.h @@ -35,17 +35,22 @@ public: } int lock_count; ///< Number of times the mutex has been acquired + u32 priority; ///< The priority of the mutex, used for priority inheritance. std::string name; ///< Name of mutex (optional) SharedPtr<Thread> holding_thread; ///< Thread that has acquired the mutex - bool ShouldWait() override; - void Acquire() override; - /** - * Acquires the specified mutex for the specified thread - * @param thread Thread that will acquire the mutex + * Elevate the mutex priority to the best priority + * among the priorities of all its waiting threads. */ - void Acquire(SharedPtr<Thread> thread); + void UpdatePriority(); + + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; + + void AddWaitingThread(SharedPtr<Thread> thread) override; + void RemoveWaitingThread(Thread* thread) override; + void Release(); private: diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp index 253ab7045..3f51bc5de 100644 --- a/src/core/hle/kernel/resource_limit.cpp +++ b/src/core/hle/kernel/resource_limit.cpp @@ -62,6 +62,8 @@ s32 ResourceLimit::GetCurrentResourceValue(u32 resource) const { s32 ResourceLimit::GetMaxResourceValue(u32 resource) const { switch (resource) { + case PRIORITY: + return max_priority; case COMMIT: return max_commit; case THREAD: diff --git a/src/core/hle/kernel/semaphore.cpp b/src/core/hle/kernel/semaphore.cpp index bf7600780..8bda2f75d 100644 --- a/src/core/hle/kernel/semaphore.cpp +++ b/src/core/hle/kernel/semaphore.cpp @@ -30,12 +30,13 @@ ResultVal<SharedPtr<Semaphore>> Semaphore::Create(s32 initial_count, s32 max_cou return MakeResult<SharedPtr<Semaphore>>(std::move(semaphore)); } -bool Semaphore::ShouldWait() { +bool Semaphore::ShouldWait(Thread* thread) const { return available_count <= 0; } -void Semaphore::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Semaphore::Acquire(Thread* thread) { + if (available_count <= 0) + return; --available_count; } diff --git a/src/core/hle/kernel/semaphore.h b/src/core/hle/kernel/semaphore.h index e01908a25..cde94f7cc 100644 --- a/src/core/hle/kernel/semaphore.h +++ b/src/core/hle/kernel/semaphore.h @@ -39,8 +39,8 @@ public: s32 available_count; ///< Number of free slots left in the semaphore std::string name; ///< Name of semaphore (optional) - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; /** * Releases a certain number of slots from a semaphore. diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp index 6c19aa7c0..fd3bbbcad 100644 --- a/src/core/hle/kernel/server_port.cpp +++ b/src/core/hle/kernel/server_port.cpp @@ -14,13 +14,13 @@ namespace Kernel { ServerPort::ServerPort() {} ServerPort::~ServerPort() {} -bool ServerPort::ShouldWait() { +bool ServerPort::ShouldWait(Thread* thread) const { // If there are no pending sessions, we wait until a new one is added. return pending_sessions.size() == 0; } -void ServerPort::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void ServerPort::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); } std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortPair( diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h index b0f8df62c..6f8bdb6a9 100644 --- a/src/core/hle/kernel/server_port.h +++ b/src/core/hle/kernel/server_port.h @@ -53,8 +53,8 @@ public: /// ServerSessions created from this port inherit a reference to this handler. std::shared_ptr<Service::SessionRequestHandler> hle_handler; - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; private: ServerPort(); diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index 146458c1c..9447ff236 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -29,12 +29,12 @@ ResultVal<SharedPtr<ServerSession>> ServerSession::Create( return MakeResult<SharedPtr<ServerSession>>(std::move(server_session)); } -bool ServerSession::ShouldWait() { +bool ServerSession::ShouldWait(Thread* thread) const { return !signaled; } -void ServerSession::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void ServerSession::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); signaled = false; } diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index 458284a5d..c088b9a19 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -57,9 +57,9 @@ public: */ ResultCode HandleSyncRequest(); - bool ShouldWait() override; + bool ShouldWait(Thread* thread) const override; - void Acquire() override; + void Acquire(Thread* thread) override; std::string name; ///< The name of this session (optional) bool signaled; ///< Whether there's new data available to this ServerSession diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 5fb95dada..3b7555d87 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -27,12 +27,12 @@ namespace Kernel { /// Event type for the thread wake up event static int ThreadWakeupEventType; -bool Thread::ShouldWait() { +bool Thread::ShouldWait(Thread* thread) const { return status != THREADSTATUS_DEAD; } -void Thread::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Thread::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); } // TODO(yuriks): This can be removed if Thread objects are explicitly pooled in the future, allowing @@ -66,20 +66,6 @@ Thread* GetCurrentThread() { } /** - * Check if a thread is waiting on the specified wait object - * @param thread The thread to test - * @param wait_object The object to test against - * @return True if the thread is waiting, false otherwise - */ -static bool CheckWait_WaitObject(const Thread* thread, WaitObject* wait_object) { - if (thread->status != THREADSTATUS_WAIT_SYNCH) - return false; - - auto itr = std::find(thread->wait_objects.begin(), thread->wait_objects.end(), wait_object); - return itr != thread->wait_objects.end(); -} - -/** * Check if the specified thread is waiting on the specified address to be arbitrated * @param thread The thread to test * @param wait_address The address to test against @@ -90,9 +76,6 @@ static bool CheckWait_AddressArbiter(const Thread* thread, VAddr wait_address) { } void Thread::Stop() { - // Release all the mutexes that this thread holds - ReleaseThreadMutexes(this); - // Cancel any outstanding wakeup events for this thread CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle); wakeup_callback_handle_table.Close(callback_handle); @@ -114,6 +97,9 @@ void Thread::Stop() { } wait_objects.clear(); + // Release all the mutexes that this thread holds + ReleaseThreadMutexes(this); + // Mark the TLS slot in the thread's page as free. u32 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE; u32 tls_slot = @@ -155,28 +141,6 @@ void ArbitrateAllThreads(u32 address) { } } -/// Boost low priority threads (temporarily) that have been starved -static void PriorityBoostStarvedThreads() { - u64 current_ticks = CoreTiming::GetTicks(); - - for (auto& thread : thread_list) { - // TODO(bunnei): Threads that have been waiting to be scheduled for `boost_ticks` (or - // longer) will have their priority temporarily adjusted to 1 higher than the highest - // priority thread to prevent thread starvation. This general behavior has been verified - // on hardware. However, this is almost certainly not perfect, and the real CTR OS scheduler - // should probably be reversed to verify this. - - const u64 boost_timeout = 2000000; // Boost threads that have been ready for > this long - - u64 delta = current_ticks - thread->last_running_ticks; - - if (thread->status == THREADSTATUS_READY && delta > boost_timeout) { - const s32 priority = std::max(ready_queue.get_first()->current_priority - 1, 0); - thread->BoostPriority(priority); - } - } -} - /** * Switches the CPU's active thread context to that of the specified thread * @param new_thread The thread to switch to @@ -199,8 +163,8 @@ static void SwitchContext(Thread* new_thread) { // Load context of new thread if (new_thread) { - DEBUG_ASSERT_MSG(new_thread->status == THREADSTATUS_READY, - "Thread must be ready to become running."); + ASSERT_MSG(new_thread->status == THREADSTATUS_READY, + "Thread must be ready to become running."); // Cancel any outstanding wakeup events for this thread CoreTiming::UnscheduleEvent(ThreadWakeupEventType, new_thread->callback_handle); @@ -210,9 +174,6 @@ static void SwitchContext(Thread* new_thread) { ready_queue.remove(new_thread->current_priority, new_thread); new_thread->status = THREADSTATUS_RUNNING; - // Restores thread to its nominal priority if it has been temporarily changed - new_thread->current_priority = new_thread->nominal_priority; - Core::CPU().LoadContext(new_thread->context); Core::CPU().SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress()); } else { @@ -248,14 +209,6 @@ void WaitCurrentThread_Sleep() { thread->status = THREADSTATUS_WAIT_SLEEP; } -void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects, - bool wait_set_output) { - Thread* thread = GetCurrentThread(); - thread->wait_set_output = wait_set_output; - thread->wait_objects = std::move(wait_objects); - thread->status = THREADSTATUS_WAIT_SYNCH; -} - void WaitCurrentThread_ArbitrateAddress(VAddr wait_address) { Thread* thread = GetCurrentThread(); thread->wait_address = wait_address; @@ -281,7 +234,8 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) { return; } - if (thread->status == THREADSTATUS_WAIT_SYNCH || thread->status == THREADSTATUS_WAIT_ARB) { + if (thread->status == THREADSTATUS_WAIT_SYNCH_ANY || + thread->status == THREADSTATUS_WAIT_SYNCH_ALL || thread->status == THREADSTATUS_WAIT_ARB) { thread->wait_set_output = false; // Remove the thread from each of its waiting objects' waitlists for (auto& object : thread->wait_objects) @@ -305,8 +259,11 @@ void Thread::WakeAfterDelay(s64 nanoseconds) { } void Thread::ResumeFromWait() { + ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects"); + switch (status) { - case THREADSTATUS_WAIT_SYNCH: + case THREADSTATUS_WAIT_SYNCH_ALL: + case THREADSTATUS_WAIT_SYNCH_ANY: case THREADSTATUS_WAIT_ARB: case THREADSTATUS_WAIT_SLEEP: break; @@ -396,14 +353,8 @@ static void ResetThreadContext(ARM_Interface::ThreadContext& context, u32 stack_ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, s32 priority, u32 arg, s32 processor_id, VAddr stack_top) { - if (priority < THREADPRIO_HIGHEST || priority > THREADPRIO_LOWEST) { - s32 new_priority = MathUtil::Clamp<s32>(priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST); - LOG_WARNING(Kernel_SVC, "(name=%s): invalid priority=%d, clamping to %d", name.c_str(), - priority, new_priority); - // TODO(bunnei): Clamping to a valid priority is not necessarily correct behavior... Confirm - // validity of this - priority = new_priority; - } + ASSERT_MSG(priority >= THREADPRIO_HIGHEST && priority <= THREADPRIO_LOWEST, + "Invalid thread priority"); if (!Memory::IsValidVirtualAddress(entry_point)) { LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %08x", name.c_str(), entry_point); @@ -487,25 +438,9 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, return MakeResult<SharedPtr<Thread>>(std::move(thread)); } -// TODO(peachum): Remove this. Range checking should be done, and an appropriate error should be -// returned. -static void ClampPriority(const Thread* thread, s32* priority) { - if (*priority < THREADPRIO_HIGHEST || *priority > THREADPRIO_LOWEST) { - DEBUG_ASSERT_MSG( - false, "Application passed an out of range priority. An error should be returned."); - - s32 new_priority = MathUtil::Clamp<s32>(*priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST); - LOG_WARNING(Kernel_SVC, "(name=%s): invalid priority=%d, clamping to %d", - thread->name.c_str(), *priority, new_priority); - // TODO(bunnei): Clamping to a valid priority is not necessarily correct behavior... Confirm - // validity of this - *priority = new_priority; - } -} - void Thread::SetPriority(s32 priority) { - ClampPriority(this, &priority); - + ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST, + "Invalid priority value."); // If thread was ready, adjust queues if (status == THREADSTATUS_READY) ready_queue.move(this, current_priority, priority); @@ -515,8 +450,21 @@ void Thread::SetPriority(s32 priority) { nominal_priority = current_priority = priority; } +void Thread::UpdatePriority() { + s32 best_priority = nominal_priority; + for (auto& mutex : held_mutexes) { + if (mutex->priority < best_priority) + best_priority = mutex->priority; + } + BoostPriority(best_priority); +} + void Thread::BoostPriority(s32 priority) { - ready_queue.move(this, current_priority, priority); + // If thread was ready, adjust queues + if (status == THREADSTATUS_READY) + ready_queue.move(this, current_priority, priority); + else + ready_queue.prepare(priority); current_priority = priority; } @@ -538,9 +486,11 @@ SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority) { return thread; } -void Reschedule() { - PriorityBoostStarvedThreads(); +bool HaveReadyThreads() { + return ready_queue.get_first() != nullptr; +} +void Reschedule() { Thread* cur = GetCurrentThread(); Thread* next = PopNextReadyThread(); @@ -563,6 +513,12 @@ void Thread::SetWaitSynchronizationOutput(s32 output) { context.cpu_registers[1] = output; } +s32 Thread::GetWaitObjectIndex(WaitObject* object) const { + ASSERT_MSG(!wait_objects.empty(), "Thread is not waiting for anything"); + auto match = std::find(wait_objects.rbegin(), wait_objects.rend(), object); + return std::distance(match, wait_objects.rend()) - 1; +} + //////////////////////////////////////////////////////////////////////////////////////////////////// void ThreadingInit() { diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index c77ac644d..c557a2279 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -31,13 +31,14 @@ enum ThreadProcessorId : s32 { }; enum ThreadStatus { - THREADSTATUS_RUNNING, ///< Currently running - THREADSTATUS_READY, ///< Ready to run - THREADSTATUS_WAIT_ARB, ///< Waiting on an address arbiter - THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC - THREADSTATUS_WAIT_SYNCH, ///< Waiting due to a WaitSynchronization SVC - THREADSTATUS_DORMANT, ///< Created but not yet made ready - THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated + THREADSTATUS_RUNNING, ///< Currently running + THREADSTATUS_READY, ///< Ready to run + THREADSTATUS_WAIT_ARB, ///< Waiting on an address arbiter + THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC + THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false + THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true + THREADSTATUS_DORMANT, ///< Created but not yet made ready + THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated }; namespace Kernel { @@ -72,8 +73,8 @@ public: return HANDLE_TYPE; } - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; /** * Gets the thread's current priority @@ -90,6 +91,12 @@ public: void SetPriority(s32 priority); /** + * Boost's a thread's priority to the best priority among the thread's held mutexes. + * This prevents priority inversion via priority inheritance. + */ + void UpdatePriority(); + + /** * Temporarily boosts the thread's priority until the next time it is scheduled * @param priority The new priority */ @@ -128,13 +135,14 @@ public: /** * Retrieves the index that this particular object occupies in the list of objects - * that the thread passed to WaitSynchronizationN. + * that the thread passed to WaitSynchronizationN, starting the search from the last element. * It is used to set the output value of WaitSynchronizationN when the thread is awakened. + * When a thread wakes up due to an object signal, the kernel will use the index of the last + * matching object in the wait objects list in case of having multiple instances of the same + * object in the list. * @param object Object to query the index of. */ - s32 GetWaitObjectIndex(const WaitObject* object) const { - return wait_objects_index.at(object->GetObjectId()); - } + s32 GetWaitObjectIndex(WaitObject* object) const; /** * Stops a thread, invalidating it from further use @@ -152,10 +160,10 @@ public: /** * Returns whether this thread is waiting for all the objects in * its wait list to become ready, as a result of a WaitSynchronizationN call - * with wait_all = true, or a ReplyAndReceive call. + * with wait_all = true. */ bool IsSleepingOnWaitAll() const { - return !wait_objects.empty(); + return status == THREADSTATUS_WAIT_SYNCH_ALL; } ARM_Interface::ThreadContext context; @@ -178,15 +186,15 @@ public: /// Mutexes currently held by this thread, which will be released when it exits. boost::container::flat_set<SharedPtr<Mutex>> held_mutexes; + /// Mutexes that this thread is currently waiting for. + boost::container::flat_set<SharedPtr<Mutex>> pending_mutexes; + SharedPtr<Process> owner_process; ///< Process that owns this thread - /// Objects that the thread is waiting on. - /// This is only populated when the thread should wait for all the objects to become ready. + /// Objects that the thread is waiting on, in the same order as they were + // passed to WaitSynchronization1/N. std::vector<SharedPtr<WaitObject>> wait_objects; - /// Mapping of Object ids to their position in the last waitlist that this object waited on. - boost::container::flat_map<int, s32> wait_objects_index; - VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address /// True if the WaitSynchronizationN output parameter should be set on thread wakeup. @@ -211,6 +219,11 @@ private: SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority); /** + * Returns whether there are any threads that are ready to run. + */ +bool HaveReadyThreads(); + +/** * Reschedules to the next available thread (call after current thread is suspended) */ void Reschedule(); @@ -238,15 +251,6 @@ Thread* GetCurrentThread(); void WaitCurrentThread_Sleep(); /** - * Waits the current thread from a WaitSynchronization call - * @param wait_objects Kernel objects that we are waiting on - * @param wait_set_output If true, set the output parameter on thread wakeup (for - * WaitSynchronizationN only) - */ -void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects, - bool wait_set_output); - -/** * Waits the current thread from an ArbitrateAddress call * @param wait_address Arbitration address used to resume from wait */ diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp index b50cf520d..60537f355 100644 --- a/src/core/hle/kernel/timer.cpp +++ b/src/core/hle/kernel/timer.cpp @@ -31,20 +31,15 @@ SharedPtr<Timer> Timer::Create(ResetType reset_type, std::string name) { timer->interval_delay = 0; timer->callback_handle = timer_callback_handle_table.Create(timer).MoveFrom(); - if (reset_type == ResetType::Pulse) { - LOG_ERROR(Kernel, "Unimplemented timer reset type Pulse"); - UNIMPLEMENTED(); - } - return timer; } -bool Timer::ShouldWait() { +bool Timer::ShouldWait(Thread* thread) const { return !signaled; } -void Timer::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Timer::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); if (reset_type == ResetType::OneShot) signaled = false; @@ -70,6 +65,13 @@ void Timer::Clear() { signaled = false; } +void Timer::WakeupAllWaitingThreads() { + WaitObject::WakeupAllWaitingThreads(); + + if (reset_type == ResetType::Pulse) + signaled = false; +} + /// The timer callback event, called when a timer is fired static void TimerCallback(u64 timer_handle, int cycles_late) { SharedPtr<Timer> timer = diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h index 18ea0236b..c174f5664 100644 --- a/src/core/hle/kernel/timer.h +++ b/src/core/hle/kernel/timer.h @@ -39,8 +39,10 @@ public: u64 initial_delay; ///< The delay until the timer fires for the first time u64 interval_delay; ///< The delay until the timer fires after the first time - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; + + void WakeupAllWaitingThreads() override; /** * Starts the timer, with the specified initial delay and interval. diff --git a/src/core/hle/service/ac/ac.cpp b/src/core/hle/service/ac/ac.cpp new file mode 100644 index 000000000..aa270a2c3 --- /dev/null +++ b/src/core/hle/service/ac/ac.cpp @@ -0,0 +1,181 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> + +#include "common/logging/log.h" +#include "core/hle/kernel/event.h" +#include "core/hle/service/ac/ac.h" +#include "core/hle/service/ac/ac_i.h" +#include "core/hle/service/ac/ac_u.h" + +namespace Service { +namespace AC { + +struct ACConfig { + std::array<u8, 0x200> data; +}; + +static ACConfig default_config{}; + +static bool ac_connected = false; + +static Kernel::SharedPtr<Kernel::Event> close_event; +static Kernel::SharedPtr<Kernel::Event> connect_event; +static Kernel::SharedPtr<Kernel::Event> disconnect_event; + +void CreateDefaultConfig(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 ac_config_addr = cmd_buff[65]; + + ASSERT_MSG(cmd_buff[64] == (sizeof(ACConfig) << 14 | 2), + "Output buffer size not equal ACConfig size"); + + Memory::WriteBlock(ac_config_addr, &default_config, sizeof(ACConfig)); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void ConnectAsync(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + connect_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); + if (connect_event) { + connect_event->name = "AC:connect_event"; + connect_event->Signal(); + ac_connected = true; + } + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void GetConnectResult(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void CloseAsync(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + if (ac_connected && disconnect_event) { + disconnect_event->Signal(); + } + + close_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); + if (close_event) { + close_event->name = "AC:close_event"; + close_event->Signal(); + } + + ac_connected = false; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void GetCloseResult(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void GetWifiStatus(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + // TODO(purpasmart96): This function is only a stub, + // it returns a valid result without implementing full functionality. + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 0; // Connection type set to none + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void GetInfraPriority(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 0; // Infra Priority, default 0 + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void SetRequestEulaVersion(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 major = cmd_buff[1] & 0xFF; + u32 minor = cmd_buff[2] & 0xFF; + + ASSERT_MSG(cmd_buff[3] == (sizeof(ACConfig) << 14 | 2), + "Input buffer size not equal ACConfig size"); + ASSERT_MSG(cmd_buff[64] == (sizeof(ACConfig) << 14 | 2), + "Output buffer size not equal ACConfig size"); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 0; // Infra Priority + + LOG_WARNING(Service_AC, "(STUBBED) called, major=%u, minor=%u", major, minor); +} + +void RegisterDisconnectEvent(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + disconnect_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); + if (disconnect_event) { + disconnect_event->name = "AC:disconnect_event"; + } + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void IsConnected(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = ac_connected; + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void SetClientVersion(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + const u32 version = cmd_buff[1]; + self->SetVersion(version); + + LOG_WARNING(Service_AC, "(STUBBED) called, version: 0x%08X", version); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error +} + +void Init() { + AddService(new AC_I); + AddService(new AC_U); + + ac_connected = false; + + close_event = nullptr; + connect_event = nullptr; + disconnect_event = nullptr; +} + +void Shutdown() { + ac_connected = false; + + close_event = nullptr; + connect_event = nullptr; + disconnect_event = nullptr; +} + +} // namespace AC +} // namespace Service diff --git a/src/core/hle/service/ac/ac.h b/src/core/hle/service/ac/ac.h new file mode 100644 index 000000000..6185faf9b --- /dev/null +++ b/src/core/hle/service/ac/ac.h @@ -0,0 +1,134 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace Service { + +class Interface; + +namespace AC { + +/** + * AC::CreateDefaultConfig service function + * Inputs: + * 64 : ACConfig size << 14 | 2 + * 65 : pointer to ACConfig struct + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void CreateDefaultConfig(Interface* self); + +/** + * AC::ConnectAsync service function + * Inputs: + * 1 : ProcessId Header + * 3 : Copy Handle Header + * 4 : Connection Event handle + * 5 : ACConfig size << 14 | 2 + * 6 : pointer to ACConfig struct + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void ConnectAsync(Interface* self); + +/** + * AC::GetConnectResult service function + * Inputs: + * 1 : ProcessId Header + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void GetConnectResult(Interface* self); + +/** + * AC::CloseAsync service function + * Inputs: + * 1 : ProcessId Header + * 3 : Copy Handle Header + * 4 : Event handle, should be signaled when AC connection is closed + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void CloseAsync(Interface* self); + +/** + * AC::GetCloseResult service function + * Inputs: + * 1 : ProcessId Header + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void GetCloseResult(Interface* self); + +/** + * AC::GetWifiStatus service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Output connection type, 0 = none, 1 = Old3DS Internet, 2 = New3DS Internet. + */ +void GetWifiStatus(Interface* self); + +/** + * AC::GetInfraPriority service function + * Inputs: + * 1 : ACConfig size << 14 | 2 + * 2 : pointer to ACConfig struct + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Infra Priority + */ +void GetInfraPriority(Interface* self); + +/** + * AC::SetRequestEulaVersion service function + * Inputs: + * 1 : Eula Version major + * 2 : Eula Version minor + * 3 : ACConfig size << 14 | 2 + * 4 : Input pointer to ACConfig struct + * 64 : ACConfig size << 14 | 2 + * 65 : Output pointer to ACConfig struct + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Infra Priority + */ +void SetRequestEulaVersion(Interface* self); + +/** + * AC::RegisterDisconnectEvent service function + * Inputs: + * 1 : ProcessId Header + * 3 : Copy Handle Header + * 4 : Event handle, should be signaled when AC connection is closed + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void RegisterDisconnectEvent(Interface* self); + +/** + * AC::IsConnected service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : bool, is connected + */ +void IsConnected(Interface* self); + +/** + * AC::SetClientVersion service function + * Inputs: + * 1 : Used SDK Version + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void SetClientVersion(Interface* self); + +/// Initialize AC service +void Init(); + +/// Shutdown AC service +void Shutdown(); + +} // namespace AC +} // namespace Service diff --git a/src/core/hle/service/ac/ac_i.cpp b/src/core/hle/service/ac/ac_i.cpp new file mode 100644 index 000000000..b22fe3698 --- /dev/null +++ b/src/core/hle/service/ac/ac_i.cpp @@ -0,0 +1,39 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/ac/ac.h" +#include "core/hle/service/ac/ac_i.h" + +namespace Service { +namespace AC { + +const Interface::FunctionInfo FunctionTable[] = { + {0x00010000, CreateDefaultConfig, "CreateDefaultConfig"}, + {0x00040006, ConnectAsync, "ConnectAsync"}, + {0x00050002, GetConnectResult, "GetConnectResult"}, + {0x00070002, nullptr, "CancelConnectAsync"}, + {0x00080004, CloseAsync, "CloseAsync"}, + {0x00090002, GetCloseResult, "GetCloseResult"}, + {0x000A0000, nullptr, "GetLastErrorCode"}, + {0x000C0000, nullptr, "GetStatus"}, + {0x000D0000, GetWifiStatus, "GetWifiStatus"}, + {0x000E0042, nullptr, "GetCurrentAPInfo"}, + {0x00100042, nullptr, "GetCurrentNZoneInfo"}, + {0x00110042, nullptr, "GetNZoneApNumService"}, + {0x001D0042, nullptr, "ScanAPs"}, + {0x00240042, nullptr, "AddDenyApType"}, + {0x00270002, GetInfraPriority, "GetInfraPriority"}, + {0x002D0082, SetRequestEulaVersion, "SetRequestEulaVersion"}, + {0x00300004, RegisterDisconnectEvent, "RegisterDisconnectEvent"}, + {0x003C0042, nullptr, "GetAPSSIDList"}, + {0x003E0042, IsConnected, "IsConnected"}, + {0x00400042, SetClientVersion, "SetClientVersion"}, +}; + +AC_I::AC_I() { + Register(FunctionTable); +} + +} // namespace AC +} // namespace Service diff --git a/src/core/hle/service/ac/ac_i.h b/src/core/hle/service/ac/ac_i.h new file mode 100644 index 000000000..465bba59c --- /dev/null +++ b/src/core/hle/service/ac/ac_i.h @@ -0,0 +1,22 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service { +namespace AC { + +class AC_I final : public Interface { +public: + AC_I(); + + std::string GetPortName() const override { + return "ac:i"; + } +}; + +} // namespace AC +} // namespace Service diff --git a/src/core/hle/service/ac/ac_u.cpp b/src/core/hle/service/ac/ac_u.cpp new file mode 100644 index 000000000..346671b4a --- /dev/null +++ b/src/core/hle/service/ac/ac_u.cpp @@ -0,0 +1,39 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/ac/ac.h" +#include "core/hle/service/ac/ac_u.h" + +namespace Service { +namespace AC { + +const Interface::FunctionInfo FunctionTable[] = { + {0x00010000, CreateDefaultConfig, "CreateDefaultConfig"}, + {0x00040006, ConnectAsync, "ConnectAsync"}, + {0x00050002, GetConnectResult, "GetConnectResult"}, + {0x00070002, nullptr, "CancelConnectAsync"}, + {0x00080004, CloseAsync, "CloseAsync"}, + {0x00090002, GetCloseResult, "GetCloseResult"}, + {0x000A0000, nullptr, "GetLastErrorCode"}, + {0x000C0000, nullptr, "GetStatus"}, + {0x000D0000, GetWifiStatus, "GetWifiStatus"}, + {0x000E0042, nullptr, "GetCurrentAPInfo"}, + {0x00100042, nullptr, "GetCurrentNZoneInfo"}, + {0x00110042, nullptr, "GetNZoneApNumService"}, + {0x001D0042, nullptr, "ScanAPs"}, + {0x00240042, nullptr, "AddDenyApType"}, + {0x00270002, GetInfraPriority, "GetInfraPriority"}, + {0x002D0082, SetRequestEulaVersion, "SetRequestEulaVersion"}, + {0x00300004, RegisterDisconnectEvent, "RegisterDisconnectEvent"}, + {0x003C0042, nullptr, "GetAPSSIDList"}, + {0x003E0042, IsConnected, "IsConnected"}, + {0x00400042, SetClientVersion, "SetClientVersion"}, +}; + +AC_U::AC_U() { + Register(FunctionTable); +} + +} // namespace AC +} // namespace Service diff --git a/src/core/hle/service/ac_u.h b/src/core/hle/service/ac/ac_u.h index 573c32d7e..f9d21e112 100644 --- a/src/core/hle/service/ac_u.h +++ b/src/core/hle/service/ac/ac_u.h @@ -12,7 +12,6 @@ namespace AC { class AC_U final : public Interface { public: AC_U(); - ~AC_U(); std::string GetPortName() const override { return "ac:u"; diff --git a/src/core/hle/service/ac_u.cpp b/src/core/hle/service/ac_u.cpp deleted file mode 100644 index 36204db4d..000000000 --- a/src/core/hle/service/ac_u.cpp +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <array> - -#include "common/logging/log.h" -#include "core/hle/kernel/event.h" -#include "core/hle/service/ac_u.h" - -namespace Service { -namespace AC { - -struct ACConfig { - std::array<u8, 0x200> data; -}; - -static ACConfig default_config{}; - -static bool ac_connected = false; - -static Kernel::SharedPtr<Kernel::Event> close_event; -static Kernel::SharedPtr<Kernel::Event> connect_event; -static Kernel::SharedPtr<Kernel::Event> disconnect_event; - -/** - * AC_U::CreateDefaultConfig service function - * Inputs: - * 64 : ACConfig size << 14 | 2 - * 65 : pointer to ACConfig struct - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -static void CreateDefaultConfig(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - u32 ac_config_addr = cmd_buff[65]; - - ASSERT_MSG(cmd_buff[64] == (sizeof(ACConfig) << 14 | 2), - "Output buffer size not equal ACConfig size"); - - Memory::WriteBlock(ac_config_addr, &default_config, sizeof(ACConfig)); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::ConnectAsync service function - * Inputs: - * 1 : ProcessId Header - * 3 : Copy Handle Header - * 4 : Connection Event handle - * 5 : ACConfig size << 14 | 2 - * 6 : pointer to ACConfig struct - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -static void ConnectAsync(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - connect_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); - if (connect_event) { - connect_event->name = "AC_U:connect_event"; - connect_event->Signal(); - ac_connected = true; - } - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::GetConnectResult service function - * Inputs: - * 1 : ProcessId Header - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -static void GetConnectResult(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::CloseAsync service function - * Inputs: - * 1 : ProcessId Header - * 3 : Copy Handle Header - * 4 : Event handle, should be signaled when AC connection is closed - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -static void CloseAsync(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - if (ac_connected && disconnect_event) { - disconnect_event->Signal(); - } - - close_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); - if (close_event) { - close_event->name = "AC_U:close_event"; - close_event->Signal(); - } - - ac_connected = false; - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::GetCloseResult service function - * Inputs: - * 1 : ProcessId Header - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -static void GetCloseResult(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::GetWifiStatus service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Output connection type, 0 = none, 1 = Old3DS Internet, 2 = New3DS Internet. - */ -static void GetWifiStatus(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - // TODO(purpasmart96): This function is only a stub, - // it returns a valid result without implementing full functionality. - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 0; // Connection type set to none - - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::GetInfraPriority service function - * Inputs: - * 1 : ACConfig size << 14 | 2 - * 2 : pointer to ACConfig struct - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Infra Priority - */ -static void GetInfraPriority(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 0; // Infra Priority, default 0 - - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::SetRequestEulaVersion service function - * Inputs: - * 1 : Eula Version major - * 2 : Eula Version minor - * 3 : ACConfig size << 14 | 2 - * 4 : Input pointer to ACConfig struct - * 64 : ACConfig size << 14 | 2 - * 65 : Output pointer to ACConfig struct - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Infra Priority - */ -static void SetRequestEulaVersion(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - u32 major = cmd_buff[1] & 0xFF; - u32 minor = cmd_buff[2] & 0xFF; - - ASSERT_MSG(cmd_buff[3] == (sizeof(ACConfig) << 14 | 2), - "Input buffer size not equal ACConfig size"); - ASSERT_MSG(cmd_buff[64] == (sizeof(ACConfig) << 14 | 2), - "Output buffer size not equal ACConfig size"); - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 0; // Infra Priority - - LOG_WARNING(Service_AC, "(STUBBED) called, major=%u, minor=%u", major, minor); -} - -/** - * AC_U::RegisterDisconnectEvent service function - * Inputs: - * 1 : ProcessId Header - * 3 : Copy Handle Header - * 4 : Event handle, should be signaled when AC connection is closed - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -static void RegisterDisconnectEvent(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - disconnect_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); - if (disconnect_event) { - disconnect_event->name = "AC_U:disconnect_event"; - } - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::IsConnected service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : bool, is connected - */ -static void IsConnected(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = ac_connected; - - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::SetClientVersion service function - * Inputs: - * 1 : Used SDK Version - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -static void SetClientVersion(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - const u32 version = cmd_buff[1]; - self->SetVersion(version); - - LOG_WARNING(Service_AC, "(STUBBED) called, version: 0x%08X", version); - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error -} - -const Interface::FunctionInfo FunctionTable[] = { - {0x00010000, CreateDefaultConfig, "CreateDefaultConfig"}, - {0x00040006, ConnectAsync, "ConnectAsync"}, - {0x00050002, GetConnectResult, "GetConnectResult"}, - {0x00070002, nullptr, "CancelConnectAsync"}, - {0x00080004, CloseAsync, "CloseAsync"}, - {0x00090002, GetCloseResult, "GetCloseResult"}, - {0x000A0000, nullptr, "GetLastErrorCode"}, - {0x000C0000, nullptr, "GetStatus"}, - {0x000D0000, GetWifiStatus, "GetWifiStatus"}, - {0x000E0042, nullptr, "GetCurrentAPInfo"}, - {0x00100042, nullptr, "GetCurrentNZoneInfo"}, - {0x00110042, nullptr, "GetNZoneApNumService"}, - {0x001D0042, nullptr, "ScanAPs"}, - {0x00240042, nullptr, "AddDenyApType"}, - {0x00270002, GetInfraPriority, "GetInfraPriority"}, - {0x002D0082, SetRequestEulaVersion, "SetRequestEulaVersion"}, - {0x00300004, RegisterDisconnectEvent, "RegisterDisconnectEvent"}, - {0x003C0042, nullptr, "GetAPSSIDList"}, - {0x003E0042, IsConnected, "IsConnected"}, - {0x00400042, SetClientVersion, "SetClientVersion"}, -}; - -AC_U::AC_U() { - Register(FunctionTable); - - ac_connected = false; - - close_event = nullptr; - connect_event = nullptr; - disconnect_event = nullptr; -} - -AC_U::~AC_U() { - close_event = nullptr; - connect_event = nullptr; - disconnect_event = nullptr; -} - -} // namespace AC -} // namespace Service diff --git a/src/core/hle/service/boss/boss.cpp b/src/core/hle/service/boss/boss.cpp index 6ab16ccd5..e0de037f8 100644 --- a/src/core/hle/service/boss/boss.cpp +++ b/src/core/hle/service/boss/boss.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> #include "core/hle/service/boss/boss.h" #include "core/hle/service/boss/boss_p.h" #include "core/hle/service/boss/boss_u.h" @@ -33,7 +34,8 @@ void InitializeSession(Service::Interface* self) { cmd_buff[0] = IPC::MakeHeader(0x1, 0x1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_BOSS, "(STUBBED) unk_param=0x%016X, translation=0x%08X, unk_param4=0x%08X", + LOG_WARNING(Service_BOSS, + "(STUBBED) unk_param=0x%016" PRIX64 ", translation=0x%08X, unk_param4=0x%08X", unk_param, translation, unk_param4); } diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp index 5594aedab..95665e754 100644 --- a/src/core/hle/service/cam/cam.cpp +++ b/src/core/hle/service/cam/cam.cpp @@ -2,7 +2,15 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> +#include <array> +#include <future> +#include <memory> +#include <vector> +#include "common/bit_set.h" #include "common/logging/log.h" +#include "core/core_timing.h" +#include "core/frontend/camera/factory.h" #include "core/hle/kernel/event.h" #include "core/hle/service/cam/cam.h" #include "core/hle/service/cam/cam_c.h" @@ -10,206 +18,924 @@ #include "core/hle/service/cam/cam_s.h" #include "core/hle/service/cam/cam_u.h" #include "core/hle/service/service.h" +#include "core/settings.h" namespace Service { namespace CAM { -static const u32 TRANSFER_BYTES = 5 * 1024; +namespace { + +struct ContextConfig { + Flip flip; + Effect effect; + OutputFormat format; + Resolution resolution; +}; + +struct CameraConfig { + std::unique_ptr<Camera::CameraInterface> impl; + std::array<ContextConfig, 2> contexts; + int current_context; + FrameRate frame_rate; +}; + +struct PortConfig { + int camera_id; + + bool is_active; // set when the port is activated by an Activate call. + bool is_pending_receiving; // set if SetReceiving is called when is_busy = false. When + // StartCapture is called then, this will trigger a receiving + // process and reset itself. + bool is_busy; // set when StartCapture is called and reset when StopCapture is called. + bool is_receiving; // set when there is an ongoing receiving process. + + bool is_trimming; + u16 x0; // x-coordinate of starting position for trimming + u16 y0; // y-coordinate of starting position for trimming + u16 x1; // x-coordinate of ending position for trimming + u16 y1; // y-coordinate of ending position for trimming + + u32 transfer_bytes; + + Kernel::SharedPtr<Kernel::Event> completion_event; + Kernel::SharedPtr<Kernel::Event> buffer_error_interrupt_event; + Kernel::SharedPtr<Kernel::Event> vsync_interrupt_event; + + std::future<std::vector<u16>> capture_result; // will hold the received frame. + VAddr dest; // the destination address of a receiving process + u32 dest_size; // the destination size of a receiving process + + void Clear() { + completion_event->Clear(); + buffer_error_interrupt_event->Clear(); + vsync_interrupt_event->Clear(); + is_receiving = false; + is_active = false; + is_pending_receiving = false; + is_busy = false; + is_trimming = false; + x0 = 0; + y0 = 0; + x1 = 0; + y1 = 0; + transfer_bytes = 256; + } +}; + +// built-in resolution parameters +constexpr std::array<Resolution, 8> PRESET_RESOLUTION{{ + {640, 480, 0, 0, 639, 479}, // VGA + {320, 240, 0, 0, 639, 479}, // QVGA + {160, 120, 0, 0, 639, 479}, // QQVGA + {352, 288, 26, 0, 613, 479}, // CIF + {176, 144, 26, 0, 613, 479}, // QCIF + {256, 192, 0, 0, 639, 479}, // DS_LCD + {512, 384, 0, 0, 639, 479}, // DS_LCDx4 + {400, 240, 0, 48, 639, 431}, // CTR_TOP_LCD +}}; + +// latency in ms for each frame rate option +constexpr std::array<int, 13> LATENCY_BY_FRAME_RATE{{ + 67, // Rate_15 + 67, // Rate_15_To_5 + 67, // Rate_15_To_2 + 100, // Rate_10 + 118, // Rate_8_5 + 200, // Rate_5 + 50, // Rate_20 + 50, // Rate_20_To_5 + 33, // Rate_30 + 33, // Rate_30_To_5 + 67, // Rate_15_To_10 + 50, // Rate_20_To_10 + 33, // Rate_30_To_10 +}}; + +std::array<CameraConfig, NumCameras> cameras; +std::array<PortConfig, 2> ports; +int completion_event_callback; + +const ResultCode ERROR_INVALID_ENUM_VALUE(ErrorDescription::InvalidEnumValue, ErrorModule::CAM, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); +const ResultCode ERROR_OUT_OF_RANGE(ErrorDescription::OutOfRange, ErrorModule::CAM, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); + +void CompletionEventCallBack(u64 port_id, int) { + PortConfig& port = ports[port_id]; + const CameraConfig& camera = cameras[port.camera_id]; + const auto buffer = port.capture_result.get(); + + if (port.is_trimming) { + u32 trim_width; + u32 trim_height; + const int original_width = camera.contexts[camera.current_context].resolution.width; + const int original_height = camera.contexts[camera.current_context].resolution.height; + if (port.x1 <= port.x0 || port.y1 <= port.y0 || port.x1 > original_width || + port.y1 > original_height) { + LOG_ERROR(Service_CAM, "Invalid trimming coordinates x0=%u, y0=%u, x1=%u, y1=%u", + port.x0, port.y0, port.x1, port.y1); + trim_width = 0; + trim_height = 0; + } else { + trim_width = port.x1 - port.x0; + trim_height = port.y1 - port.y0; + } + + u32 trim_size = (port.x1 - port.x0) * (port.y1 - port.y0) * 2; + if (port.dest_size != trim_size) { + LOG_ERROR(Service_CAM, "The destination size (%u) doesn't match the source (%u)!", + port.dest_size, trim_size); + } + + const u32 src_offset = port.y0 * original_width + port.x0; + const u16* src_ptr = buffer.data() + src_offset; + // Note: src_size_left is int because it can be negative if the buffer size doesn't match. + int src_size_left = static_cast<int>((buffer.size() - src_offset) * sizeof(u16)); + VAddr dest_ptr = port.dest; + // Note: dest_size_left and line_bytes are int to match the type of src_size_left. + int dest_size_left = static_cast<int>(port.dest_size); + const int line_bytes = static_cast<int>(trim_width * sizeof(u16)); + + for (u32 y = 0; y < trim_height; ++y) { + int copy_length = std::min({line_bytes, dest_size_left, src_size_left}); + if (copy_length <= 0) { + break; + } + Memory::WriteBlock(dest_ptr, src_ptr, copy_length); + dest_ptr += copy_length; + dest_size_left -= copy_length; + src_ptr += original_width; + src_size_left -= original_width * sizeof(u16); + } + } else { + std::size_t buffer_size = buffer.size() * sizeof(u16); + if (port.dest_size != buffer_size) { + LOG_ERROR(Service_CAM, "The destination size (%u) doesn't match the source (%zu)!", + port.dest_size, buffer_size); + } + Memory::WriteBlock(port.dest, buffer.data(), std::min<u32>(port.dest_size, buffer_size)); + } + + port.is_receiving = false; + port.completion_event->Signal(); +} + +// Starts a receiving process on the specified port. This can only be called when is_busy = true and +// is_receiving = false. +void StartReceiving(int port_id) { + PortConfig& port = ports[port_id]; + port.is_receiving = true; + + // launches a capture task asynchronously + const CameraConfig& camera = cameras[port.camera_id]; + port.capture_result = + std::async(std::launch::async, &Camera::CameraInterface::ReceiveFrame, camera.impl.get()); + + // schedules a completion event according to the frame rate. The event will block on the + // capture task if it is not finished within the expected time + CoreTiming::ScheduleEvent( + msToCycles(LATENCY_BY_FRAME_RATE[static_cast<int>(camera.frame_rate)]), + completion_event_callback, port_id); +} + +// Cancels any ongoing receiving processes at the specified port. This is used by functions that +// stop capturing. +// TODO: what is the exact behaviour on real 3DS when stopping capture during an ongoing process? +// Will the completion event still be signaled? +void CancelReceiving(int port_id) { + if (!ports[port_id].is_receiving) + return; + LOG_WARNING(Service_CAM, "tries to cancel an ongoing receiving process."); + CoreTiming::UnscheduleEvent(completion_event_callback, port_id); + ports[port_id].capture_result.wait(); + ports[port_id].is_receiving = false; +} + +// Activates the specified port with the specfied camera. +static void ActivatePort(int port_id, int camera_id) { + if (ports[port_id].is_busy && ports[port_id].camera_id != camera_id) { + CancelReceiving(port_id); + cameras[ports[port_id].camera_id].impl->StopCapture(); + ports[port_id].is_busy = false; + } + ports[port_id].is_active = true; + ports[port_id].camera_id = camera_id; +} + +template <int max_index> +class CommandParamBitSet : public BitSet8 { +public: + explicit CommandParamBitSet(u32 command_param) + : BitSet8(static_cast<u8>(command_param & 0xFF)) {} -static Kernel::SharedPtr<Kernel::Event> completion_event_cam1; -static Kernel::SharedPtr<Kernel::Event> completion_event_cam2; -static Kernel::SharedPtr<Kernel::Event> interrupt_error_event; -static Kernel::SharedPtr<Kernel::Event> vsync_interrupt_error_event; + bool IsValid() const { + return m_val < (1 << max_index); + } + + bool IsSingle() const { + return IsValid() && Count() == 1; + } +}; + +using PortSet = CommandParamBitSet<2>; +using ContextSet = CommandParamBitSet<2>; +using CameraSet = CommandParamBitSet<3>; + +} // namespace void StartCapture(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u8 port = cmd_buff[1] & 0xFF; + const PortSet port_select(cmd_buff[1]); + + if (port_select.IsValid()) { + for (int i : port_select) { + if (!ports[i].is_busy) { + if (!ports[i].is_active) { + // This doesn't return an error, but seems to put the camera in an undefined + // state + LOG_ERROR(Service_CAM, "port %u hasn't been activated", i); + } else { + cameras[ports[i].camera_id].impl->StartCapture(); + ports[i].is_busy = true; + if (ports[i].is_pending_receiving) { + ports[i].is_pending_receiving = false; + StartReceiving(i); + } + } + } else { + LOG_WARNING(Service_CAM, "port %u already started", i); + } + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port); + LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val); } void StopCapture(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u8 port = cmd_buff[1] & 0xFF; + const PortSet port_select(cmd_buff[1]); + + if (port_select.IsValid()) { + for (int i : port_select) { + if (ports[i].is_busy) { + CancelReceiving(i); + cameras[ports[i].camera_id].impl->StopCapture(); + ports[i].is_busy = false; + } else { + LOG_WARNING(Service_CAM, "port %u already stopped", i); + } + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } cmd_buff[0] = IPC::MakeHeader(0x2, 1, 0); + + LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val); +} + +void IsBusy(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + const PortSet port_select(cmd_buff[1]); + + if (port_select.IsValid()) { + bool is_busy = true; + // Note: the behaviour on no or both ports selected are verified against real 3DS. + for (int i : port_select) { + is_busy &= ports[i].is_busy; + } + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = is_busy ? 1 : 0; + } else { + LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } + + cmd_buff[0] = IPC::MakeHeader(0x3, 2, 0); + + LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val); +} + +void ClearBuffer(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + const PortSet port_select(cmd_buff[1]); + + cmd_buff[0] = IPC::MakeHeader(0x4, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port); + LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u", port_select.m_val); } void GetVsyncInterruptEvent(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u8 port = cmd_buff[1] & 0xFF; + const PortSet port_select(cmd_buff[1]); + + if (port_select.IsSingle()) { + int port = *port_select.begin(); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = IPC::CopyHandleDesc(); + cmd_buff[3] = Kernel::g_handle_table.Create(ports[port].vsync_interrupt_event).MoveFrom(); + } else { + LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + cmd_buff[2] = IPC::CopyHandleDesc(); + cmd_buff[2] = 0; + } cmd_buff[0] = IPC::MakeHeader(0x5, 1, 2); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = IPC::CopyHandleDesc(); - cmd_buff[3] = Kernel::g_handle_table.Create(vsync_interrupt_error_event).MoveFrom(); - LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port); + LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u", port_select.m_val); } void GetBufferErrorInterruptEvent(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u8 port = cmd_buff[1] & 0xFF; - - cmd_buff[0] = IPC::MakeHeader(0x6, 1, 2); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = IPC::CopyHandleDesc(); - cmd_buff[3] = Kernel::g_handle_table.Create(interrupt_error_event).MoveFrom(); - - LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port); + const PortSet port_select(cmd_buff[1]); + + if (port_select.IsSingle()) { + int port = *port_select.begin(); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = IPC::CopyHandleDesc(); + cmd_buff[3] = + Kernel::g_handle_table.Create(ports[port].buffer_error_interrupt_event).MoveFrom(); + } else { + LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + cmd_buff[2] = IPC::CopyHandleDesc(); + cmd_buff[2] = 0; + } + + LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u", port_select.m_val); } void SetReceiving(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - VAddr dest = cmd_buff[1]; - u8 port = cmd_buff[2] & 0xFF; - u32 image_size = cmd_buff[3]; - u16 trans_unit = cmd_buff[4] & 0xFFFF; + const VAddr dest = cmd_buff[1]; + const PortSet port_select(cmd_buff[2]); + const u32 image_size = cmd_buff[3]; + const u32 trans_unit = cmd_buff[4] & 0xFFFF; + + if (port_select.IsSingle()) { + int port_id = *port_select.begin(); + PortConfig& port = ports[port_id]; + CancelReceiving(port_id); + port.completion_event->Clear(); + port.dest = dest; + port.dest_size = image_size; + + if (port.is_busy) { + StartReceiving(port_id); + } else { + port.is_pending_receiving = true; + } + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = IPC::CopyHandleDesc(); + cmd_buff[3] = Kernel::g_handle_table.Create(port.completion_event).MoveFrom(); + } else { + LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } + + cmd_buff[0] = IPC::MakeHeader(0x7, 1, 2); - Kernel::Event* completion_event = - (Port)port == Port::Cam2 ? completion_event_cam2.get() : completion_event_cam1.get(); + LOG_DEBUG(Service_CAM, "called, addr=0x%X, port_select=%u, image_size=%u, trans_unit=%u", dest, + port_select.m_val, image_size, trans_unit); +} - completion_event->Signal(); +void IsFinishedReceiving(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); - cmd_buff[0] = IPC::MakeHeader(0x7, 1, 2); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = IPC::CopyHandleDesc(); - cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).MoveFrom(); + const PortSet port_select(cmd_buff[1]); + + if (port_select.IsSingle()) { + int port = *port_select.begin(); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = (ports[port].is_receiving || ports[port].is_pending_receiving) ? 0 : 1; + } else { + LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } - LOG_WARNING(Service_CAM, "(STUBBED) called, addr=0x%X, port=%d, image_size=%d, trans_unit=%d", - dest, port, image_size, trans_unit); + cmd_buff[0] = IPC::MakeHeader(0x8, 2, 0); + + LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val); } void SetTransferLines(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u8 port = cmd_buff[1] & 0xFF; - u16 transfer_lines = cmd_buff[2] & 0xFFFF; - u16 width = cmd_buff[3] & 0xFFFF; - u16 height = cmd_buff[4] & 0xFFFF; + const PortSet port_select(cmd_buff[1]); + const u32 transfer_lines = cmd_buff[2] & 0xFFFF; + const u32 width = cmd_buff[3] & 0xFFFF; + const u32 height = cmd_buff[4] & 0xFFFF; + + if (port_select.IsValid()) { + for (int i : port_select) { + ports[i].transfer_bytes = transfer_lines * width * 2; + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } cmd_buff[0] = IPC::MakeHeader(0x9, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d, lines=%d, width=%d, height=%d", port, - transfer_lines, width, height); + LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u, lines=%u, width=%u, height=%u", + port_select.m_val, transfer_lines, width, height); } void GetMaxLines(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u16 width = cmd_buff[1] & 0xFFFF; - u16 height = cmd_buff[2] & 0xFFFF; + const u32 width = cmd_buff[1] & 0xFFFF; + const u32 height = cmd_buff[2] & 0xFFFF; + + // Note: the result of the algorithm below are hwtested with width < 640 and with height < 480 + constexpr u32 MIN_TRANSFER_UNIT = 256; + constexpr u32 MAX_BUFFER_SIZE = 2560; + if (width * height * 2 % MIN_TRANSFER_UNIT != 0) { + cmd_buff[1] = ERROR_OUT_OF_RANGE.raw; + } else { + u32 lines = MAX_BUFFER_SIZE / width; + if (lines > height) { + lines = height; + } + cmd_buff[1] = RESULT_SUCCESS.raw; + while (height % lines != 0 || (lines * width * 2 % MIN_TRANSFER_UNIT != 0)) { + --lines; + if (lines == 0) { + cmd_buff[1] = ERROR_OUT_OF_RANGE.raw; + break; + } + } + cmd_buff[2] = lines; + } cmd_buff[0] = IPC::MakeHeader(0xA, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = TRANSFER_BYTES / (2 * width); - LOG_WARNING(Service_CAM, "(STUBBED) called, width=%d, height=%d, lines = %d", width, height, - cmd_buff[2]); + LOG_DEBUG(Service_CAM, "called, width=%u, height=%u", width, height); +} + +void SetTransferBytes(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + const PortSet port_select(cmd_buff[1]); + const u32 transfer_bytes = cmd_buff[2] & 0xFFFF; + const u32 width = cmd_buff[3] & 0xFFFF; + const u32 height = cmd_buff[4] & 0xFFFF; + + if (port_select.IsValid()) { + for (int i : port_select) { + ports[i].transfer_bytes = transfer_bytes; + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } + + cmd_buff[0] = IPC::MakeHeader(0xB, 1, 0); + + LOG_WARNING(Service_CAM, "(STUBBED)called, port_select=%u, bytes=%u, width=%u, height=%u", + port_select.m_val, transfer_bytes, width, height); } void GetTransferBytes(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u8 port = cmd_buff[1] & 0xFF; + const PortSet port_select(cmd_buff[1]); + + if (port_select.IsSingle()) { + int port = *port_select.begin(); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = ports[port].transfer_bytes; + } else { + LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } cmd_buff[0] = IPC::MakeHeader(0xC, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = TRANSFER_BYTES; - LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port); + LOG_WARNING(Service_CAM, "(STUBBED)called, port_select=%u", port_select.m_val); +} + +void GetMaxBytes(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + const u32 width = cmd_buff[1] & 0xFFFF; + const u32 height = cmd_buff[2] & 0xFFFF; + + // Note: the result of the algorithm below are hwtested with width < 640 and with height < 480 + constexpr u32 MIN_TRANSFER_UNIT = 256; + constexpr u32 MAX_BUFFER_SIZE = 2560; + if (width * height * 2 % MIN_TRANSFER_UNIT != 0) { + cmd_buff[1] = ERROR_OUT_OF_RANGE.raw; + } else { + u32 bytes = MAX_BUFFER_SIZE; + + while (width * height * 2 % bytes != 0) { + bytes -= MIN_TRANSFER_UNIT; + } + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = bytes; + } + cmd_buff[0] = IPC::MakeHeader(0xD, 2, 0); + + LOG_DEBUG(Service_CAM, "called, width=%u, height=%u", width, height); } void SetTrimming(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u8 port = cmd_buff[1] & 0xFF; - bool trim = (cmd_buff[2] & 0xFF) != 0; + const PortSet port_select(cmd_buff[1]); + const bool trim = (cmd_buff[2] & 0xFF) != 0; + + if (port_select.IsValid()) { + for (int i : port_select) { + ports[i].is_trimming = trim; + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } cmd_buff[0] = IPC::MakeHeader(0xE, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d, trim=%d", port, trim); + LOG_DEBUG(Service_CAM, "called, port_select=%u, trim=%d", port_select.m_val, trim); +} + +void IsTrimming(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + const PortSet port_select(cmd_buff[1]); + + if (port_select.IsSingle()) { + int port = *port_select.begin(); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = ports[port].is_trimming; + } else { + LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } + + cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0); + + LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val); +} + +void SetTrimmingParams(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + const PortSet port_select(cmd_buff[1]); + const u16 x0 = static_cast<u16>(cmd_buff[2] & 0xFFFF); + const u16 y0 = static_cast<u16>(cmd_buff[3] & 0xFFFF); + const u16 x1 = static_cast<u16>(cmd_buff[4] & 0xFFFF); + const u16 y1 = static_cast<u16>(cmd_buff[5] & 0xFFFF); + + if (port_select.IsValid()) { + for (int i : port_select) { + ports[i].x0 = x0; + ports[i].y0 = y0; + ports[i].x1 = x1; + ports[i].y1 = y1; + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } + + cmd_buff[0] = IPC::MakeHeader(0x10, 1, 0); + + LOG_DEBUG(Service_CAM, "called, port_select=%u, x0=%u, y0=%u, x1=%u, y1=%u", port_select.m_val, + x0, y0, x1, y1); +} + +void GetTrimmingParams(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + const PortSet port_select(cmd_buff[1]); + + if (port_select.IsSingle()) { + int port = *port_select.begin(); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = ports[port].x0; + cmd_buff[3] = ports[port].y0; + cmd_buff[4] = ports[port].x1; + cmd_buff[5] = ports[port].y1; + } else { + LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } + + cmd_buff[0] = IPC::MakeHeader(0x11, 5, 0); + + LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val); } void SetTrimmingParamsCenter(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u8 port = cmd_buff[1] & 0xFF; - s16 trimW = cmd_buff[2] & 0xFFFF; - s16 trimH = cmd_buff[3] & 0xFFFF; - s16 camW = cmd_buff[4] & 0xFFFF; - s16 camH = cmd_buff[5] & 0xFFFF; + const PortSet port_select(cmd_buff[1]); + const u16 trim_w = static_cast<u16>(cmd_buff[2] & 0xFFFF); + const u16 trim_h = static_cast<u16>(cmd_buff[3] & 0xFFFF); + const u16 cam_w = static_cast<u16>(cmd_buff[4] & 0xFFFF); + const u16 cam_h = static_cast<u16>(cmd_buff[5] & 0xFFFF); + + if (port_select.IsValid()) { + for (int i : port_select) { + ports[i].x0 = (cam_w - trim_w) / 2; + ports[i].y0 = (cam_h - trim_h) / 2; + ports[i].x1 = ports[i].x0 + trim_w; + ports[i].y1 = ports[i].y0 + trim_h; + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } cmd_buff[0] = IPC::MakeHeader(0x12, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d, trimW=%d, trimH=%d, camW=%d, camH=%d", - port, trimW, trimH, camW, camH); + LOG_DEBUG(Service_CAM, "called, port_select=%u, trim_w=%u, trim_h=%u, cam_w=%u, cam_h=%u", + port_select.m_val, trim_w, trim_h, cam_w, cam_h); } void Activate(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u8 cam_select = cmd_buff[1] & 0xFF; + const CameraSet camera_select(cmd_buff[1]); + + if (camera_select.IsValid()) { + if (camera_select.m_val == 0) { // deactive all + for (int i = 0; i < 2; ++i) { + if (ports[i].is_busy) { + CancelReceiving(i); + cameras[ports[i].camera_id].impl->StopCapture(); + ports[i].is_busy = false; + } + ports[i].is_active = false; + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } else if (camera_select[0] && camera_select[1]) { + LOG_ERROR(Service_CAM, "camera 0 and 1 can't be both activated"); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } else { + if (camera_select[0]) { + ActivatePort(0, 0); + } else if (camera_select[1]) { + ActivatePort(0, 1); + } + + if (camera_select[2]) { + ActivatePort(1, 2); + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } + } else { + LOG_ERROR(Service_CAM, "invalid camera_select=%u", camera_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } cmd_buff[0] = IPC::MakeHeader(0x13, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_CAM, "(STUBBED) called, cam_select=%d", cam_select); + LOG_DEBUG(Service_CAM, "called, camera_select=%u", camera_select.m_val); +} + +void SwitchContext(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + const CameraSet camera_select(cmd_buff[1]); + const ContextSet context_select(cmd_buff[2]); + + if (camera_select.IsValid() && context_select.IsSingle()) { + int context = *context_select.begin(); + for (int camera : camera_select) { + cameras[camera].current_context = context; + const ContextConfig& context_config = cameras[camera].contexts[context]; + cameras[camera].impl->SetFlip(context_config.flip); + cameras[camera].impl->SetEffect(context_config.effect); + cameras[camera].impl->SetFormat(context_config.format); + cameras[camera].impl->SetResolution(context_config.resolution); + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val, + context_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } + + cmd_buff[0] = IPC::MakeHeader(0x14, 1, 0); + + LOG_DEBUG(Service_CAM, "called, camera_select=%u, context_select=%u", camera_select.m_val, + context_select.m_val); } void FlipImage(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u8 cam_select = cmd_buff[1] & 0xFF; - u8 flip = cmd_buff[2] & 0xFF; - u8 context = cmd_buff[3] & 0xFF; + const CameraSet camera_select(cmd_buff[1]); + const Flip flip = static_cast<Flip>(cmd_buff[2] & 0xFF); + const ContextSet context_select(cmd_buff[3]); + + if (camera_select.IsValid() && context_select.IsValid()) { + for (int camera : camera_select) { + for (int context : context_select) { + cameras[camera].contexts[context].flip = flip; + if (cameras[camera].current_context == context) { + cameras[camera].impl->SetFlip(flip); + } + } + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val, + context_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } cmd_buff[0] = IPC::MakeHeader(0x1D, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_CAM, "(STUBBED) called, cam_select=%d, flip=%d, context=%d", cam_select, - flip, context); + LOG_DEBUG(Service_CAM, "called, camera_select=%u, flip=%d, context_select=%u", + camera_select.m_val, static_cast<int>(flip), context_select.m_val); +} + +void SetDetailSize(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + const CameraSet camera_select(cmd_buff[1]); + Resolution resolution; + resolution.width = static_cast<u16>(cmd_buff[2] & 0xFFFF); + resolution.height = static_cast<u16>(cmd_buff[3] & 0xFFFF); + resolution.crop_x0 = static_cast<u16>(cmd_buff[4] & 0xFFFF); + resolution.crop_y0 = static_cast<u16>(cmd_buff[5] & 0xFFFF); + resolution.crop_x1 = static_cast<u16>(cmd_buff[6] & 0xFFFF); + resolution.crop_y1 = static_cast<u16>(cmd_buff[7] & 0xFFFF); + const ContextSet context_select(cmd_buff[8]); + + if (camera_select.IsValid() && context_select.IsValid()) { + for (int camera : camera_select) { + for (int context : context_select) { + cameras[camera].contexts[context].resolution = resolution; + if (cameras[camera].current_context == context) { + cameras[camera].impl->SetResolution(resolution); + } + } + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val, + context_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } + + cmd_buff[0] = IPC::MakeHeader(0x1E, 1, 0); + + LOG_DEBUG(Service_CAM, "called, camera_select=%u, width=%u, height=%u, crop_x0=%u, crop_y0=%u, " + "crop_x1=%u, crop_y1=%u, context_select=%u", + camera_select.m_val, resolution.width, resolution.height, resolution.crop_x0, + resolution.crop_y0, resolution.crop_x1, resolution.crop_y1, context_select.m_val); } void SetSize(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u8 cam_select = cmd_buff[1] & 0xFF; - u8 size = cmd_buff[2] & 0xFF; - u8 context = cmd_buff[3] & 0xFF; + const CameraSet camera_select(cmd_buff[1]); + const u32 size = cmd_buff[2] & 0xFF; + const ContextSet context_select(cmd_buff[3]); + + if (camera_select.IsValid() && context_select.IsValid()) { + for (int camera : camera_select) { + for (int context : context_select) { + cameras[camera].contexts[context].resolution = PRESET_RESOLUTION[size]; + if (cameras[camera].current_context == context) { + cameras[camera].impl->SetResolution(PRESET_RESOLUTION[size]); + } + } + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val, + context_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } cmd_buff[0] = IPC::MakeHeader(0x1F, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_CAM, "(STUBBED) called, cam_select=%d, size=%d, context=%d", cam_select, - size, context); + LOG_DEBUG(Service_CAM, "called, camera_select=%u, size=%u, context_select=%u", + camera_select.m_val, size, context_select.m_val); } void SetFrameRate(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u8 cam_select = cmd_buff[1] & 0xFF; - u8 frame_rate = cmd_buff[2] & 0xFF; + const CameraSet camera_select(cmd_buff[1]); + const FrameRate frame_rate = static_cast<FrameRate>(cmd_buff[2] & 0xFF); + + if (camera_select.IsValid()) { + for (int camera : camera_select) { + cameras[camera].frame_rate = frame_rate; + // TODO(wwylele): consider hinting the actual camera with the expected frame rate + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + LOG_ERROR(Service_CAM, "invalid camera_select=%u", camera_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } cmd_buff[0] = IPC::MakeHeader(0x20, 1, 0); + + LOG_WARNING(Service_CAM, "(STUBBED) called, camera_select=%u, frame_rate=%d", + camera_select.m_val, static_cast<int>(frame_rate)); +} + +void SetEffect(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + const CameraSet camera_select(cmd_buff[1]); + const Effect effect = static_cast<Effect>(cmd_buff[2] & 0xFF); + const ContextSet context_select(cmd_buff[3]); + + if (camera_select.IsValid() && context_select.IsValid()) { + for (int camera : camera_select) { + for (int context : context_select) { + cameras[camera].contexts[context].effect = effect; + if (cameras[camera].current_context == context) { + cameras[camera].impl->SetEffect(effect); + } + } + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val, + context_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } + + cmd_buff[0] = IPC::MakeHeader(0x22, 1, 0); + + LOG_DEBUG(Service_CAM, "called, camera_select=%u, effect=%d, context_select=%u", + camera_select.m_val, static_cast<int>(effect), context_select.m_val); +} + +void SetOutputFormat(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + const CameraSet camera_select(cmd_buff[1]); + const OutputFormat format = static_cast<OutputFormat>(cmd_buff[2] & 0xFF); + const ContextSet context_select(cmd_buff[3]); + + if (camera_select.IsValid() && context_select.IsValid()) { + for (int camera : camera_select) { + for (int context : context_select) { + cameras[camera].contexts[context].format = format; + if (cameras[camera].current_context == context) { + cameras[camera].impl->SetFormat(format); + } + } + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val, + context_select.m_val); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } + + cmd_buff[0] = IPC::MakeHeader(0x25, 1, 0); + + LOG_DEBUG(Service_CAM, "called, camera_select=%u, format=%d, context_select=%u", + camera_select.m_val, static_cast<int>(format), context_select.m_val); +} + +void SynchronizeVsyncTiming(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + const u32 camera_select1 = cmd_buff[1] & 0xFF; + const u32 camera_select2 = cmd_buff[2] & 0xFF; + + cmd_buff[0] = IPC::MakeHeader(0x29, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_CAM, "(STUBBED) called, cam_select=%d, frame_rate=%d", cam_select, - frame_rate); + LOG_WARNING(Service_CAM, "(STUBBED) called, camera_select1=%u, camera_select2=%u", + camera_select1, camera_select2); } void GetStereoCameraCalibrationData(Service::Interface* self) { @@ -239,6 +965,67 @@ void GetStereoCameraCalibrationData(Service::Interface* self) { LOG_TRACE(Service_CAM, "called"); } +void SetPackageParameterWithoutContext(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + PackageParameterWithoutContext package; + std::memcpy(&package, cmd_buff + 1, sizeof(package)); + + cmd_buff[0] = IPC::MakeHeader(0x33, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_CAM, "(STUBBED) called"); +} + +template <typename PackageParameterType, int command_id> +static void SetPackageParameter() { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + PackageParameterType package; + std::memcpy(&package, cmd_buff + 1, sizeof(package)); + + const CameraSet camera_select(static_cast<u32>(package.camera_select)); + const ContextSet context_select(static_cast<u32>(package.context_select)); + + if (camera_select.IsValid() && context_select.IsValid()) { + for (int camera_id : camera_select) { + CameraConfig& camera = cameras[camera_id]; + for (int context_id : context_select) { + ContextConfig& context = camera.contexts[context_id]; + context.effect = package.effect; + context.flip = package.flip; + context.resolution = package.GetResolution(); + if (context_id == camera.current_context) { + camera.impl->SetEffect(context.effect); + camera.impl->SetFlip(context.flip); + camera.impl->SetResolution(context.resolution); + } + } + } + cmd_buff[1] = RESULT_SUCCESS.raw; + } else { + LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", package.camera_select, + package.context_select); + cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; + } + + cmd_buff[0] = IPC::MakeHeader(command_id, 1, 0); + + LOG_DEBUG(Service_CAM, "called"); +} + +Resolution PackageParameterWithContext::GetResolution() { + return PRESET_RESOLUTION[static_cast<int>(size)]; +} + +void SetPackageParameterWithContext(Service::Interface* self) { + SetPackageParameter<PackageParameterWithContext, 0x34>(); +} + +void SetPackageParameterWithContextDetail(Service::Interface* self) { + SetPackageParameter<PackageParameterWithContextDetail, 0x35>(); +} + void GetSuitableY2rStandardCoefficient(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); @@ -263,24 +1050,50 @@ void PlayShutterSound(Service::Interface* self) { void DriverInitialize(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - completion_event_cam1->Clear(); - completion_event_cam2->Clear(); - interrupt_error_event->Clear(); - vsync_interrupt_error_event->Clear(); + for (int camera_id = 0; camera_id < NumCameras; ++camera_id) { + CameraConfig& camera = cameras[camera_id]; + camera.current_context = 0; + for (int context_id = 0; context_id < 2; ++context_id) { + // Note: the following default values are verified against real 3DS + ContextConfig& context = camera.contexts[context_id]; + context.flip = camera_id == 1 ? Flip::Horizontal : Flip::None; + context.effect = Effect::None; + context.format = OutputFormat::YUV422; + context.resolution = + context_id == 0 ? PRESET_RESOLUTION[5 /*DS_LCD*/] : PRESET_RESOLUTION[0 /*VGA*/]; + } + camera.impl = Camera::CreateCamera(Settings::values.camera_name[camera_id], + Settings::values.camera_config[camera_id]); + camera.impl->SetFlip(camera.contexts[0].flip); + camera.impl->SetEffect(camera.contexts[0].effect); + camera.impl->SetFormat(camera.contexts[0].format); + camera.impl->SetResolution(camera.contexts[0].resolution); + } + + for (PortConfig& port : ports) { + port.Clear(); + } cmd_buff[0] = IPC::MakeHeader(0x39, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_CAM, "(STUBBED) called"); + LOG_DEBUG(Service_CAM, "called"); } void DriverFinalize(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + CancelReceiving(0); + CancelReceiving(1); + + for (CameraConfig& camera : cameras) { + camera.impl = nullptr; + } + cmd_buff[0] = IPC::MakeHeader(0x3A, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_CAM, "(STUBBED) called"); + LOG_DEBUG(Service_CAM, "called"); } void Init() { @@ -291,21 +1104,28 @@ void Init() { AddService(new CAM_S_Interface); AddService(new CAM_U_Interface); - completion_event_cam1 = - Kernel::Event::Create(ResetType::OneShot, "CAM_U::completion_event_cam1"); - completion_event_cam2 = - Kernel::Event::Create(ResetType::OneShot, "CAM_U::completion_event_cam2"); - interrupt_error_event = - Kernel::Event::Create(ResetType::OneShot, "CAM_U::interrupt_error_event"); - vsync_interrupt_error_event = - Kernel::Event::Create(ResetType::OneShot, "CAM_U::vsync_interrupt_error_event"); + for (PortConfig& port : ports) { + port.completion_event = Event::Create(ResetType::Sticky, "CAM_U::completion_event"); + port.buffer_error_interrupt_event = + Event::Create(ResetType::OneShot, "CAM_U::buffer_error_interrupt_event"); + port.vsync_interrupt_event = + Event::Create(ResetType::OneShot, "CAM_U::vsync_interrupt_event"); + } + completion_event_callback = + CoreTiming::RegisterEvent("CAM_U::CompletionEventCallBack", CompletionEventCallBack); } void Shutdown() { - completion_event_cam1 = nullptr; - completion_event_cam2 = nullptr; - interrupt_error_event = nullptr; - vsync_interrupt_error_event = nullptr; + CancelReceiving(0); + CancelReceiving(1); + for (PortConfig& port : ports) { + port.completion_event = nullptr; + port.buffer_error_interrupt_event = nullptr; + port.vsync_interrupt_event = nullptr; + } + for (CameraConfig& camera : cameras) { + camera.impl = nullptr; + } } } // namespace CAM diff --git a/src/core/hle/service/cam/cam.h b/src/core/hle/service/cam/cam.h index c9b6f8acf..f6bff8bc6 100644 --- a/src/core/hle/service/cam/cam.h +++ b/src/core/hle/service/cam/cam.h @@ -13,17 +13,12 @@ namespace Service { namespace CAM { -enum class Port : u8 { None = 0, Cam1 = 1, Cam2 = 2, Both = Cam1 | Cam2 }; +enum CameraIndex { + OuterRightCamera = 0, + InnerCamera = 1, + OuterLeftCamera = 2, -enum class CameraSelect : u8 { - None = 0, - Out1 = 1, - In1 = 2, - Out2 = 4, - In1Out1 = Out1 | In1, - Out1Out2 = Out1 | Out2, - In1Out2 = In1 | Out2, - All = Out1 | In1 | Out2, + NumCameras = 3, }; enum class Effect : u8 { @@ -35,13 +30,6 @@ enum class Effect : u8 { Sepia01 = 5, }; -enum class Context : u8 { - None = 0, - A = 1, - B = 2, - Both = A | B, -}; - enum class Flip : u8 { None = 0, Horizontal = 1, @@ -160,8 +148,23 @@ struct StereoCameraCalibrationData { static_assert(sizeof(StereoCameraCalibrationData) == 64, "StereoCameraCalibrationData structure size is wrong"); -struct PackageParameterCameraSelect { - CameraSelect camera; +/** + * Resolution parameters for the camera. + * The native resolution of 3DS camera is 640 * 480. The captured image will be cropped in the + * region [crop_x0, crop_x1] * [crop_y0, crop_y1], and then scaled to size width * height as the + * output image. Note that all cropping coordinates are inclusive. + */ +struct Resolution { + u16 width; + u16 height; + u16 crop_x0; + u16 crop_y0; + u16 crop_x1; + u16 crop_y1; +}; + +struct PackageParameterWithoutContext { + u8 camera_select; s8 exposure; WhiteBalance white_balance; s8 sharpness; @@ -183,14 +186,43 @@ struct PackageParameterCameraSelect { s16 auto_white_balance_window_height; }; -static_assert(sizeof(PackageParameterCameraSelect) == 28, - "PackageParameterCameraSelect structure size is wrong"); +static_assert(sizeof(PackageParameterWithoutContext) == 28, + "PackageParameterCameraWithoutContext structure size is wrong"); + +struct PackageParameterWithContext { + u8 camera_select; + u8 context_select; + Flip flip; + Effect effect; + Size size; + INSERT_PADDING_BYTES(3); + + Resolution GetResolution(); +}; + +static_assert(sizeof(PackageParameterWithContext) == 8, + "PackageParameterWithContext structure size is wrong"); + +struct PackageParameterWithContextDetail { + u8 camera_select; + u8 context_select; + Flip flip; + Effect effect; + Resolution resolution; + + Resolution GetResolution() { + return resolution; + } +}; + +static_assert(sizeof(PackageParameterWithContextDetail) == 16, + "PackageParameterWithContextDetail structure size is wrong"); /** - * Unknown + * Starts capturing at the selected port. * Inputs: * 0: 0x00010040 - * 1: u8 Camera port (`Port` enum) + * 1: u8 selected port * Outputs: * 0: 0x00010040 * 1: ResultCode @@ -198,10 +230,10 @@ static_assert(sizeof(PackageParameterCameraSelect) == 28, void StartCapture(Service::Interface* self); /** - * Unknown + * Stops capturing from the selected port. * Inputs: * 0: 0x00020040 - * 1: u8 Camera port (`Port` enum) + * 1: u8 selected port * Outputs: * 0: 0x00020040 * 1: ResultCode @@ -209,10 +241,33 @@ void StartCapture(Service::Interface* self); void StopCapture(Service::Interface* self); /** + * Gets whether the selected port is currently capturing. + * Inputs: + * 0: 0x00030040 + * 1: u8 selected port + * Outputs: + * 0: 0x00030080 + * 1: ResultCode + * 2: 0 if not capturing, 1 if capturing + */ +void IsBusy(Service::Interface* self); + +/** + * Clears the buffer of selected ports. + * Inputs: + * 0: 0x00040040 + * 1: u8 selected port + * Outputs: + * 0: 0x00040040 + * 2: ResultCode + */ +void ClearBuffer(Service::Interface* self); + +/** * Unknown * Inputs: * 0: 0x00050040 - * 1: u8 Camera port (`Port` enum) + * 1: u8 selected port * Outputs: * 0: 0x00050042 * 1: ResultCode @@ -225,7 +280,7 @@ void GetVsyncInterruptEvent(Service::Interface* self); * Unknown * Inputs: * 0: 0x00060040 - * 1: u8 Camera port (`Port` enum) + * 1: u8 selected port * Outputs: * 0: 0x00060042 * 1: ResultCode @@ -241,9 +296,9 @@ void GetBufferErrorInterruptEvent(Service::Interface* self); * Inputs: * 0: 0x00070102 * 1: Destination address in calling process - * 2: u8 Camera port (`Port` enum) - * 3: Image size (in bytes?) - * 4: u16 Transfer unit size (in bytes?) + * 2: u8 selected port + * 3: Image size (in bytes) + * 4: u16 Transfer unit size (in bytes) * 5: Descriptor: Handle * 6: Handle to destination process * Outputs: @@ -255,21 +310,34 @@ void GetBufferErrorInterruptEvent(Service::Interface* self); void SetReceiving(Service::Interface* self); /** - * Unknown + * Gets whether the selected port finished receiving a frame. + * Inputs: + * 0: 0x00080040 + * 1: u8 selected port + * Outputs: + * 0: 0x00080080 + * 1: ResultCode + * 2: 0 if not finished, 1 if finished + */ +void IsFinishedReceiving(Service::Interface* self); + +/** + * Sets the number of lines the buffer contains. * Inputs: * 0: 0x00090100 - * 1: u8 Camera port (`Port` enum) + * 1: u8 selected port * 2: u16 Number of lines to transfer * 3: u16 Width * 4: u16 Height * Outputs: * 0: 0x00090040 * 1: ResultCode + * @todo figure out how the "buffer" actually works. */ void SetTransferLines(Service::Interface* self); /** - * Unknown + * Gets the maximum number of lines that fit in the buffer * Inputs: * 0: 0x000A0080 * 1: u16 Width @@ -277,27 +345,58 @@ void SetTransferLines(Service::Interface* self); * Outputs: * 0: 0x000A0080 * 1: ResultCode - * 2: Maximum number of lines that fit in the buffer(?) + * 2: Maximum number of lines that fit in the buffer + * @todo figure out how the "buffer" actually works. */ void GetMaxLines(Service::Interface* self); /** - * Unknown + * Sets the number of bytes the buffer contains. + * Inputs: + * 0: 0x000B0100 + * 1: u8 selected port + * 2: u16 Number of bytes to transfer + * 3: u16 Width + * 4: u16 Height + * Outputs: + * 0: 0x000B0040 + * 1: ResultCode + * @todo figure out how the "buffer" actually works. + */ +void SetTransferBytes(Service::Interface* self); + +/** + * Gets the number of bytes to the buffer contains. * Inputs: * 0: 0x000C0040 - * 1: u8 Camera port (`Port` enum) + * 1: u8 selected port * Outputs: * 0: 0x000C0080 * 1: ResultCode - * 2: Total number of bytes for each frame with current settings(?) + * 2: The number of bytes the buffer contains + * @todo figure out how the "buffer" actually works. */ void GetTransferBytes(Service::Interface* self); /** - * Unknown + * Gets the maximum number of bytes that fit in the buffer. + * Inputs: + * 0: 0x000D0080 + * 1: u16 Width + * 2: u16 Height + * Outputs: + * 0: 0x000D0080 + * 1: ResultCode + * 2: Maximum number of bytes that fit in the buffer + * @todo figure out how the "buffer" actually works. + */ +void GetMaxBytes(Service::Interface* self); + +/** + * Enables or disables trimming. * Inputs: * 0: 0x000E0080 - * 1: u8 Camera port (`Port` enum) + * 1: u8 selected port * 2: u8 bool Enable trimming if true * Outputs: * 0: 0x000E0040 @@ -306,14 +405,58 @@ void GetTransferBytes(Service::Interface* self); void SetTrimming(Service::Interface* self); /** - * Unknown + * Gets whether trimming is enabled. + * Inputs: + * 0: 0x000F0040 + * 1: u8 selected port + * Outputs: + * 0: 0x000F0080 + * 1: ResultCode + * 2: u8 bool Enable trimming if true + */ +void IsTrimming(Service::Interface* self); + +/** + * Sets the position to trim. + * Inputs: + * 0: 0x00100140 + * 1: u8 selected port + * 2: x start + * 3: y start + * 4: x end (exclusive) + * 5: y end (exclusive) + * Outputs: + * 0: 0x00100040 + * 1: ResultCode + */ +void SetTrimmingParams(Service::Interface* self); + +/** + * Gets the position to trim. + * Inputs: + * 0: 0x00110040 + * 1: u8 selected port + * + * Outputs: + * 0: 0x00110140 + * 1: ResultCode + * 2: x start + * 3: y start + * 4: x end (exclusive) + * 5: y end (exclusive) + */ +void GetTrimmingParams(Service::Interface* self); + +/** + * Sets the position to trim by giving the width and height. The trimming window is always at the + * center. * Inputs: * 0: 0x00120140 - * 1: u8 Camera port (`Port` enum) - * 2: s16 Trim width(?) - * 3: s16 Trim height(?) - * 4: s16 Camera width(?) - * 5: s16 Camera height(?) + * 1: u8 selected port + * 2: s16 Trim width + * 3: s16 Trim height + * 4: s16 Camera width + * 5: s16 Camera height * Outputs: * 0: 0x00120040 * 1: ResultCode @@ -324,7 +467,7 @@ void SetTrimmingParamsCenter(Service::Interface* self); * Selects up to two physical cameras to enable. * Inputs: * 0: 0x00130040 - * 1: u8 Cameras to activate (`CameraSelect` enum) + * 1: u8 selected camera * Outputs: * 0: 0x00130040 * 1: ResultCode @@ -332,12 +475,24 @@ void SetTrimmingParamsCenter(Service::Interface* self); void Activate(Service::Interface* self); /** - * Unknown + * Switches the context of camera settings. + * Inputs: + * 0: 0x00140080 + * 1: u8 selected camera + * 2: u8 selected context + * Outputs: + * 0: 0x00140040 + * 1: ResultCode + */ +void SwitchContext(Service::Interface* self); + +/** + * Sets flipping of images * Inputs: * 0: 0x001D00C0 - * 1: u8 Camera select (`CameraSelect` enum) + * 1: u8 selected camera * 2: u8 Type of flipping to perform (`Flip` enum) - * 3: u8 Context (`Context` enum) + * 3: u8 selected context * Outputs: * 0: 0x001D0040 * 1: ResultCode @@ -345,12 +500,30 @@ void Activate(Service::Interface* self); void FlipImage(Service::Interface* self); /** - * Unknown + * Sets camera resolution from custom parameters. For more details see the Resolution struct. + * Inputs: + * 0: 0x001E0200 + * 1: u8 selected camera + * 2: width + * 3: height + * 4: crop x0 + * 5: crop y0 + * 6: crop x1 + * 7: crop y1 + * 8: u8 selected context + * Outputs: + * 0: 0x001E0040 + * 1: ResultCode + */ +void SetDetailSize(Service::Interface* self); + +/** + * Sets camera resolution from preset resolution parameters. . * Inputs: * 0: 0x001F00C0 - * 1: u8 Camera select (`CameraSelect` enum) + * 1: u8 selected camera * 2: u8 Camera frame resolution (`Size` enum) - * 3: u8 Context id (`Context` enum) + * 3: u8 selected context * Outputs: * 0: 0x001F0040 * 1: ResultCode @@ -358,10 +531,10 @@ void FlipImage(Service::Interface* self); void SetSize(Service::Interface* self); /** - * Unknown + * Sets camera framerate. * Inputs: * 0: 0x00200080 - * 1: u8 Camera select (`CameraSelect` enum) + * 1: u8 selected camera * 2: u8 Camera framerate (`FrameRate` enum) * Outputs: * 0: 0x00200040 @@ -370,6 +543,44 @@ void SetSize(Service::Interface* self); void SetFrameRate(Service::Interface* self); /** + * Sets effect on the output image + * Inputs: + * 0: 0x002200C0 + * 1: u8 selected camera + * 2: u8 image effect (`Effect` enum) + * 3: u8 selected context + * Outputs: + * 0: 0x00220040 + * 1: ResultCode + */ +void SetEffect(Service::Interface* self); + +/** + * Sets format of the output image + * Inputs: + * 0: 0x002500C0 + * 1: u8 selected camera + * 2: u8 image format (`OutputFormat` enum) + * 3: u8 selected context + * Outputs: + * 0: 0x00250040 + * 1: ResultCode + */ +void SetOutputFormat(Service::Interface* self); + +/** + * Synchronizes the V-Sync timing of two cameras. + * Inputs: + * 0: 0x00290080 + * 1: u8 selected camera 1 + * 2: u8 selected camera 2 + * Outputs: + * 0: 0x00280040 + * 1: ResultCode + */ +void SynchronizeVsyncTiming(Service::Interface* self); + +/** * Returns calibration data relating the outside cameras to eachother, for use in AR applications. * * Inputs: @@ -382,6 +593,45 @@ void SetFrameRate(Service::Interface* self); void GetStereoCameraCalibrationData(Service::Interface* self); /** + * Batch-configures context-free settings. + * + * Inputs: + * 0: 0x003302C0 + * 1-7: struct PachageParameterWithoutContext + * 8-11: unused + * Outputs: + * 0: 0x00330040 + * 1: ResultCode + */ +void SetPackageParameterWithoutContext(Service::Interface* self); + +/** + * Batch-configures context-related settings with preset resolution parameters. + * + * Inputs: + * 0: 0x00340140 + * 1-2: struct PackageParameterWithContext + * 3-5: unused + * Outputs: + * 0: 0x00340040 + * 1: ResultCode + */ +void SetPackageParameterWithContext(Service::Interface* self); + +/** + * Batch-configures context-related settings with custom resolution parameters + * + * Inputs: + * 0: 0x003501C0 + * 1-4: struct PackageParameterWithContextDetail + * 5-7: unused + * Outputs: + * 0: 0x00350040 + * 1: ResultCode + */ +void SetPackageParameterWithContextDetail(Service::Interface* self); + +/** * Unknown * Inputs: * 0: 0x00360000 diff --git a/src/core/hle/service/cam/cam_u.cpp b/src/core/hle/service/cam/cam_u.cpp index af2123e5b..251c1e6d4 100644 --- a/src/core/hle/service/cam/cam_u.cpp +++ b/src/core/hle/service/cam/cam_u.cpp @@ -11,24 +11,24 @@ namespace CAM { const Interface::FunctionInfo FunctionTable[] = { {0x00010040, StartCapture, "StartCapture"}, {0x00020040, StopCapture, "StopCapture"}, - {0x00030040, nullptr, "IsBusy"}, - {0x00040040, nullptr, "ClearBuffer"}, + {0x00030040, IsBusy, "IsBusy"}, + {0x00040040, ClearBuffer, "ClearBuffer"}, {0x00050040, GetVsyncInterruptEvent, "GetVsyncInterruptEvent"}, {0x00060040, GetBufferErrorInterruptEvent, "GetBufferErrorInterruptEvent"}, {0x00070102, SetReceiving, "SetReceiving"}, - {0x00080040, nullptr, "IsFinishedReceiving"}, + {0x00080040, IsFinishedReceiving, "IsFinishedReceiving"}, {0x00090100, SetTransferLines, "SetTransferLines"}, {0x000A0080, GetMaxLines, "GetMaxLines"}, - {0x000B0100, nullptr, "SetTransferBytes"}, + {0x000B0100, SetTransferBytes, "SetTransferBytes"}, {0x000C0040, GetTransferBytes, "GetTransferBytes"}, - {0x000D0080, nullptr, "GetMaxBytes"}, + {0x000D0080, GetMaxBytes, "GetMaxBytes"}, {0x000E0080, SetTrimming, "SetTrimming"}, - {0x000F0040, nullptr, "IsTrimming"}, - {0x00100140, nullptr, "SetTrimmingParams"}, - {0x00110040, nullptr, "GetTrimmingParams"}, + {0x000F0040, IsTrimming, "IsTrimming"}, + {0x00100140, SetTrimmingParams, "SetTrimmingParams"}, + {0x00110040, GetTrimmingParams, "GetTrimmingParams"}, {0x00120140, SetTrimmingParamsCenter, "SetTrimmingParamsCenter"}, {0x00130040, Activate, "Activate"}, - {0x00140080, nullptr, "SwitchContext"}, + {0x00140080, SwitchContext, "SwitchContext"}, {0x00150080, nullptr, "SetExposure"}, {0x00160080, nullptr, "SetWhiteBalance"}, {0x00170080, nullptr, "SetWhiteBalanceWithoutBaseUp"}, @@ -38,18 +38,18 @@ const Interface::FunctionInfo FunctionTable[] = { {0x001B0080, nullptr, "SetAutoWhiteBalance"}, {0x001C0040, nullptr, "IsAutoWhiteBalance"}, {0x001D00C0, FlipImage, "FlipImage"}, - {0x001E0200, nullptr, "SetDetailSize"}, + {0x001E0200, SetDetailSize, "SetDetailSize"}, {0x001F00C0, SetSize, "SetSize"}, {0x00200080, SetFrameRate, "SetFrameRate"}, {0x00210080, nullptr, "SetPhotoMode"}, - {0x002200C0, nullptr, "SetEffect"}, + {0x002200C0, SetEffect, "SetEffect"}, {0x00230080, nullptr, "SetContrast"}, {0x00240080, nullptr, "SetLensCorrection"}, - {0x002500C0, nullptr, "SetOutputFormat"}, + {0x002500C0, SetOutputFormat, "SetOutputFormat"}, {0x00260140, nullptr, "SetAutoExposureWindow"}, {0x00270140, nullptr, "SetAutoWhiteBalanceWindow"}, {0x00280080, nullptr, "SetNoiseFilter"}, - {0x00290080, nullptr, "SynchronizeVsyncTiming"}, + {0x00290080, SynchronizeVsyncTiming, "SynchronizeVsyncTiming"}, {0x002A0080, nullptr, "GetLatestVsyncTiming"}, {0x002B0000, GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"}, {0x002C0400, nullptr, "SetStereoCameraCalibrationData"}, @@ -59,9 +59,9 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00300080, nullptr, "ReadMcuVariableI2cExclusive"}, {0x00310180, nullptr, "SetImageQualityCalibrationData"}, {0x00320000, nullptr, "GetImageQualityCalibrationData"}, - {0x003302C0, nullptr, "SetPackageParameterWithoutContext"}, - {0x00340140, nullptr, "SetPackageParameterWithContext"}, - {0x003501C0, nullptr, "SetPackageParameterWithContextDetail"}, + {0x003302C0, SetPackageParameterWithoutContext, "SetPackageParameterWithoutContext"}, + {0x00340140, SetPackageParameterWithContext, "SetPackageParameterWithContext"}, + {0x003501C0, SetPackageParameterWithContextDetail, "SetPackageParameterWithContextDetail"}, {0x00360000, GetSuitableY2rStandardCoefficient, "GetSuitableY2rStandardCoefficient"}, {0x00370202, nullptr, "PlayShutterSoundWithWave"}, {0x00380040, PlayShutterSound, "PlayShutterSound"}, diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 65655f45d..6f13cde27 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -84,7 +84,6 @@ struct ConsoleCountryInfo { static_assert(sizeof(ConsoleCountryInfo) == 4, "ConsoleCountryInfo must be exactly 4 bytes"); } -static const u64 CFG_SAVE_ID = 0x00010017; static const u64 CONSOLE_UNIQUE_ID = 0xDEADC0DE; static const ConsoleModelInfo CONSOLE_MODEL = {NINTENDO_3DS_XL, {0, 0, 0}}; static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN; @@ -115,6 +114,8 @@ static const std::vector<u8> cfg_system_savedata_id = { 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x01, 0x00, }; +static u32 preferred_region_code = 0; + void GetCountryCodeString(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 country_code_id = cmd_buff[1]; @@ -160,11 +161,18 @@ void GetCountryCodeID(Service::Interface* self) { cmd_buff[2] = country_code_id; } +static u32 GetRegionValue() { + if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) + return preferred_region_code; + + return Settings::values.region_value; +} + void SecureInfoGetRegion(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = Settings::values.region_value; + cmd_buff[2] = GetRegionValue(); } void GenHashConsoleUnique(Service::Interface* self) { @@ -184,7 +192,7 @@ void GetRegionCanadaUSA(Service::Interface* self) { cmd_buff[1] = RESULT_SUCCESS.raw; u8 canada_or_usa = 1; - if (canada_or_usa == Settings::values.region_value) { + if (canada_or_usa == GetRegionValue()) { cmd_buff[2] = 1; } else { cmd_buff[2] = 0; @@ -318,6 +326,7 @@ ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, void* output) { void* pointer; CASCADE_RESULT(pointer, GetConfigInfoBlockPointer(block_id, size, flag)); memcpy(output, pointer, size); + return RESULT_SUCCESS; } @@ -535,10 +544,55 @@ void Init() { AddService(new CFG_U); LoadConfigNANDSaveFile(); + + preferred_region_code = 0; } void Shutdown() {} +/// Checks if the language is available in the chosen region, and returns a proper one +static SystemLanguage AdjustLanguageInfoBlock(u32 region, SystemLanguage language) { + static const std::array<std::vector<SystemLanguage>, 7> region_languages{{ + // JPN + {LANGUAGE_JP}, + // USA + {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_ES, LANGUAGE_PT}, + // EUR + {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_DE, LANGUAGE_IT, LANGUAGE_ES, LANGUAGE_NL, LANGUAGE_PT, + LANGUAGE_RU}, + // AUS + {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_DE, LANGUAGE_IT, LANGUAGE_ES, LANGUAGE_NL, LANGUAGE_PT, + LANGUAGE_RU}, + // CHN + {LANGUAGE_ZH}, + // KOR + {LANGUAGE_KO}, + // TWN + {LANGUAGE_TW}, + }}; + const auto& available = region_languages[region]; + if (std::find(available.begin(), available.end(), language) == available.end()) { + return available[0]; + } + return language; +} + +void SetPreferredRegionCode(u32 region_code) { + preferred_region_code = region_code; + LOG_INFO(Service_CFG, "Preferred region code set to %u", preferred_region_code); + + if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) { + const SystemLanguage current_language = GetSystemLanguage(); + const SystemLanguage adjusted_language = + AdjustLanguageInfoBlock(region_code, current_language); + if (current_language != adjusted_language) { + LOG_WARNING(Service_CFG, "System language %d does not fit the region. Adjusted to %d", + static_cast<int>(current_language), static_cast<int>(adjusted_language)); + SetSystemLanguage(adjusted_language); + } + } +} + void SetUsername(const std::u16string& name) { ASSERT(name.size() <= 10); UsernameBlock block{}; diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index fb47c2aa5..618c9647e 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h @@ -282,6 +282,13 @@ void Init(); /// Shutdown the config service void Shutdown(); +/** + * Set the region code preferred by the game so that CFG will adjust to it when the region setting + * is auto. + * @param region_code the preferred region code to set + */ +void SetPreferredRegionCode(u32 region_code); + // Utilities for frontend to set config data. // Note: before calling these functions, LoadConfigNANDSaveFile should be called, // and UpdateConfigNANDSavegame should be called after making changes to config data. diff --git a/src/core/hle/service/err_f.cpp b/src/core/hle/service/err_f.cpp index cd0a1a598..9da55f328 100644 --- a/src/core/hle/service/err_f.cpp +++ b/src/core/hle/service/err_f.cpp @@ -227,6 +227,8 @@ static void ThrowFatalError(Interface* self) { LOG_CRITICAL(Service_ERR, "FINST2: 0x%08X", errtype.exception_data.exception_info.fpinst2); break; + case ExceptionType::Undefined: + break; // Not logging exception_info for this case } LOG_CRITICAL(Service_ERR, "Datetime: %s", GetCurrentSystemTime().c_str()); break; diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index 947958703..1457518d4 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -149,7 +149,7 @@ static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, VAddr u32 mask = Memory::Read32(masks_vaddr); // Update the current value of the register only for set mask bits - reg_value = (reg_value & ~mask) | (data | mask); + reg_value = (reg_value & ~mask) | (data & mask); WriteSingleHWReg(base_address, reg_value); @@ -705,6 +705,33 @@ static void ReleaseRight(Interface* self) { LOG_WARNING(Service_GSP, "called"); } +/** + * GSP_GPU::StoreDataCache service function + * + * This Function is a no-op, We aren't emulating the CPU cache any time soon. + * + * Inputs: + * 0 : Header code [0x001F0082] + * 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 StoreDataCache(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 address = cmd_buff[1]; + u32 size = cmd_buff[2]; + u32 process = cmd_buff[4]; + + cmd_buff[0] = IPC::MakeHeader(0x1F, 0x1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_DEBUG(Service_GSP, "(STUBBED) called address=0x%08X, size=0x%08X, process=0x%08X", address, + size, process); +} + const Interface::FunctionInfo FunctionTable[] = { {0x00010082, WriteHWRegs, "WriteHWRegs"}, {0x00020084, WriteHWRegsWithMask, "WriteHWRegsWithMask"}, @@ -736,7 +763,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x001C0040, nullptr, "SetLedForceOff"}, {0x001D0040, nullptr, "SetTestCommand"}, {0x001E0080, nullptr, "SetInternalPriorities"}, - {0x001F0082, nullptr, "StoreDataCache"}, + {0x001F0082, StoreDataCache, "StoreDataCache"}, }; GSP_GPU::GSP_GPU() { diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 676154bd4..f14ab3811 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -35,6 +35,15 @@ static u32 next_gyroscope_index; static int enable_accelerometer_count = 0; // positive means enabled static int enable_gyroscope_count = 0; // positive means enabled +static int pad_update_event; +static int accelerometer_update_event; +static int gyroscope_update_event; + +// Updating period for each HID device. These empirical values are measured from a 11.2 3DS. +constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234; +constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104; +constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101; + static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { // 30 degree and 60 degree are angular thresholds for directions constexpr float TAN30 = 0.577350269f; @@ -65,14 +74,9 @@ static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { return state; } -void Update() { +static void UpdatePadCallback(u64 userdata, int cycles_late) { SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); - if (mem == nullptr) { - LOG_DEBUG(Service_HID, "Cannot update HID prior to mapping shared memory!"); - return; - } - PadState state = VideoCore::g_emu_window->GetPadState(); // Get current circle pad position and update circle pad direction @@ -131,59 +135,68 @@ void Update() { 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(); - } + // Reschedule recurrent event + CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event); +} + +static void UpdateAccelerometerCallback(u64 userdata, int cycles_late) { + SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); + + mem->accelerometer.index = next_accelerometer_index; + next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size(); - event_accelerometer->Signal(); + 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(); } - // Update gyroscope - if (enable_gyroscope_count > 0) { - mem->gyroscope.index = next_gyroscope_index; - next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size(); + event_accelerometer->Signal(); - 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(); + // Reschedule recurrent event + CoreTiming::ScheduleEvent(accelerometer_update_ticks - cycles_late, accelerometer_update_event); +} - // 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; +static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) { + SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); - // 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(); - } + 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; - event_gyroscope->Signal(); + // 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(); + + // Reschedule recurrent event + CoreTiming::ScheduleEvent(gyroscope_update_ticks - cycles_late, gyroscope_update_event); } void GetIPCHandles(Service::Interface* self) { @@ -204,7 +217,11 @@ void EnableAccelerometer(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); ++enable_accelerometer_count; - event_accelerometer->Signal(); + + // Schedules the accelerometer update event if the accelerometer was just enabled + if (enable_accelerometer_count == 1) { + CoreTiming::ScheduleEvent(accelerometer_update_ticks, accelerometer_update_event); + } cmd_buff[1] = RESULT_SUCCESS.raw; @@ -215,7 +232,11 @@ void DisableAccelerometer(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); --enable_accelerometer_count; - event_accelerometer->Signal(); + + // Unschedules the accelerometer update event if the accelerometer was just disabled + if (enable_accelerometer_count == 0) { + CoreTiming::UnscheduleEvent(accelerometer_update_event, 0); + } cmd_buff[1] = RESULT_SUCCESS.raw; @@ -226,7 +247,11 @@ void EnableGyroscopeLow(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); ++enable_gyroscope_count; - event_gyroscope->Signal(); + + // Schedules the gyroscope update event if the gyroscope was just enabled + if (enable_gyroscope_count == 1) { + CoreTiming::ScheduleEvent(gyroscope_update_ticks, gyroscope_update_event); + } cmd_buff[1] = RESULT_SUCCESS.raw; @@ -237,7 +262,11 @@ void DisableGyroscopeLow(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); --enable_gyroscope_count; - event_gyroscope->Signal(); + + // Unschedules the gyroscope update event if the gyroscope was just disabled + if (enable_gyroscope_count == 0) { + CoreTiming::UnscheduleEvent(gyroscope_update_event, 0); + } cmd_buff[1] = RESULT_SUCCESS.raw; @@ -291,6 +320,8 @@ void Init() { next_pad_index = 0; next_touch_index = 0; + next_accelerometer_index = 0; + next_gyroscope_index = 0; // Create event handles event_pad_or_touch_1 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch1"); @@ -298,6 +329,15 @@ void Init() { event_accelerometer = Event::Create(ResetType::OneShot, "HID:EventAccelerometer"); event_gyroscope = Event::Create(ResetType::OneShot, "HID:EventGyroscope"); event_debug_pad = Event::Create(ResetType::OneShot, "HID:EventDebugPad"); + + // Register update callbacks + pad_update_event = CoreTiming::RegisterEvent("HID::UpdatePadCallback", UpdatePadCallback); + accelerometer_update_event = + CoreTiming::RegisterEvent("HID::UpdateAccelerometerCallback", UpdateAccelerometerCallback); + gyroscope_update_event = + CoreTiming::RegisterEvent("HID::UpdateGyroscopeCallback", UpdateGyroscopeCallback); + + CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event); } void Shutdown() { diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 7904e7355..21e66dfe0 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -296,9 +296,6 @@ void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self); */ void GetGyroscopeLowCalibrateParam(Service::Interface* self); -/// Checks for user input updates -void Update(); - /// Initialize HID service void Init(); diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp index 4f1dd2fce..e98388560 100644 --- a/src/core/hle/service/mic_u.cpp +++ b/src/core/hle/service/mic_u.cpp @@ -93,13 +93,14 @@ static void StartSampling(Interface* self) { sample_rate = static_cast<SampleRate>(cmd_buff[2] & 0xFF); audio_buffer_offset = cmd_buff[3]; audio_buffer_size = cmd_buff[4]; - audio_buffer_loop = static_cast<bool>(cmd_buff[5] & 0xFF); + audio_buffer_loop = (cmd_buff[5] & 0xFF) != 0; cmd_buff[1] = RESULT_SUCCESS.raw; // No error is_sampling = true; LOG_WARNING(Service_MIC, "(STUBBED) called, encoding=%u, sample_rate=%u, " "audio_buffer_offset=%d, audio_buffer_size=%u, audio_buffer_loop=%u", - encoding, sample_rate, audio_buffer_offset, audio_buffer_size, audio_buffer_loop); + static_cast<u32>(encoding), static_cast<u32>(sample_rate), audio_buffer_offset, + audio_buffer_size, audio_buffer_loop); } /** @@ -114,7 +115,7 @@ static void AdjustSampling(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); sample_rate = static_cast<SampleRate>(cmd_buff[1] & 0xFF); cmd_buff[1] = RESULT_SUCCESS.raw; // No error - LOG_WARNING(Service_MIC, "(STUBBED) called, sample_rate=%u", sample_rate); + LOG_WARNING(Service_MIC, "(STUBBED) called, sample_rate=%u", static_cast<u32>(sample_rate)); } /** @@ -201,7 +202,7 @@ static void GetGain(Interface* self) { */ static void SetPower(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - mic_power = static_cast<bool>(cmd_buff[1] & 0xFF); + mic_power = (cmd_buff[1] & 0xFF) != 0; cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_MIC, "(STUBBED) called, mic_power=%u", mic_power); } @@ -251,7 +252,7 @@ static void SetIirFilterMic(Interface* self) { */ static void SetClamp(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - clamp = static_cast<bool>(cmd_buff[1] & 0xFF); + clamp = (cmd_buff[1] & 0xFF) != 0; cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_MIC, "(STUBBED) called, clamp=%u", clamp); } @@ -281,7 +282,7 @@ static void GetClamp(Interface* self) { */ static void SetAllowShellClosed(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - allow_shell_closed = static_cast<bool>(cmd_buff[1] & 0xFF); + allow_shell_closed = (cmd_buff[1] & 0xFF) != 0; cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_MIC, "(STUBBED) called, allow_shell_closed=%u", allow_shell_closed); } diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp index d9738c6a1..fd3c7d9c2 100644 --- a/src/core/hle/service/nfc/nfc.cpp +++ b/src/core/hle/service/nfc/nfc.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/kernel/event.h" #include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfc/nfc_m.h" #include "core/hle/service/nfc/nfc_u.h" @@ -9,9 +10,133 @@ namespace Service { namespace NFC { +static Kernel::SharedPtr<Kernel::Event> tag_in_range_event; +static Kernel::SharedPtr<Kernel::Event> tag_out_of_range_event; +static TagState nfc_tag_state = TagState::NotInitialized; +static CommunicationStatus nfc_status = CommunicationStatus::NfcInitialized; + +void Initialize(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u8 param = static_cast<u8>(cmd_buff[1] & 0xFF); + + nfc_tag_state = TagState::NotScanning; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NFC, "(STUBBED) called, param=%u", param); +} + +void Shutdown(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u8 param = static_cast<u8>(cmd_buff[1] & 0xFF); + nfc_tag_state = TagState::NotInitialized; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NFC, "(STUBBED) called, param=%u", param); +} + +void StartCommunication(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + +void StopCommunication(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + +void StartTagScanning(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + nfc_tag_state = TagState::TagInRange; + tag_in_range_event->Signal(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + +void StopTagScanning(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + nfc_tag_state = TagState::NotScanning; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + +void LoadAmiiboData(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + nfc_tag_state = TagState::TagDataLoaded; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + +void ResetTagScanState(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + nfc_tag_state = TagState::NotScanning; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + +void GetTagInRangeEvent(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0xB, 1, 2); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = IPC::CopyHandleDesc(); + cmd_buff[3] = Kernel::g_handle_table.Create(tag_in_range_event).MoveFrom(); + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + +void GetTagOutOfRangeEvent(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0xC, 1, 2); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = IPC::CopyHandleDesc(); + cmd_buff[3] = Kernel::g_handle_table.Create(tag_out_of_range_event).MoveFrom(); + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + +void GetTagState(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = static_cast<u8>(nfc_tag_state); + LOG_DEBUG(Service_NFC, "(STUBBED) called"); +} + +void CommunicationGetStatus(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = static_cast<u8>(nfc_status); + LOG_DEBUG(Service_NFC, "(STUBBED) called"); +} + void Init() { AddService(new NFC_M()); AddService(new NFC_U()); + + tag_in_range_event = + Kernel::Event::Create(Kernel::ResetType::OneShot, "NFC::tag_in_range_event"); + tag_out_of_range_event = + Kernel::Event::Create(Kernel::ResetType::OneShot, "NFC::tag_out_range_event"); + nfc_tag_state = TagState::NotInitialized; +} + +void Shutdown() { + tag_in_range_event = nullptr; + tag_out_of_range_event = nullptr; } } // namespace NFC diff --git a/src/core/hle/service/nfc/nfc.h b/src/core/hle/service/nfc/nfc.h index cd65a5fdc..a013bdae7 100644 --- a/src/core/hle/service/nfc/nfc.h +++ b/src/core/hle/service/nfc/nfc.h @@ -4,11 +4,150 @@ #pragma once +#include "common/common_types.h" + namespace Service { + +class Interface; + namespace NFC { +enum class TagState : u8 { + NotInitialized = 0, + NotScanning = 1, + Scanning = 2, + TagInRange = 3, + TagOutOfRange = 4, + TagDataLoaded = 5, +}; + +enum class CommunicationStatus : u8 { + AttemptInitialize = 1, + NfcInitialized = 2, +}; + +/** + * NFC::Initialize service function + * Inputs: + * 0 : Header code [0x00010040] + * 1 : (u8) unknown parameter. Can be either value 0x1 or 0x2 + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void Initialize(Interface* self); + +/** + * NFC::Shutdown service function + * Inputs: + * 0 : Header code [0x00020040] + * 1 : (u8) unknown parameter + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void Shutdown(Interface* self); + +/** + * NFC::StartCommunication service function + * Inputs: + * 0 : Header code [0x00030000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void StartCommunication(Interface* self); + +/** + * NFC::StopCommunication service function + * Inputs: + * 0 : Header code [0x00040000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void StopCommunication(Interface* self); + +/** + * NFC::StartTagScanning service function + * Inputs: + * 0 : Header code [0x00050040] + * 1 : (u16) unknown. This is normally 0x0 + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void StartTagScanning(Interface* self); + +/** + * NFC::StopTagScanning service function + * Inputs: + * 0 : Header code [0x00060000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void StopTagScanning(Interface* self); + +/** + * NFC::LoadAmiiboData service function + * Inputs: + * 0 : Header code [0x00070000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void LoadAmiiboData(Interface* self); + +/** + * NFC::ResetTagScanState service function + * Inputs: + * 0 : Header code [0x00080000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void ResetTagScanState(Interface* self); + +/** + * NFC::GetTagInRangeEvent service function + * Inputs: + * 0 : Header code [0x000B0000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Copy handle descriptor + * 3 : Event Handle + */ +void GetTagInRangeEvent(Interface* self); + +/** + * NFC::GetTagOutOfRangeEvent service function + * Inputs: + * 0 : Header code [0x000C0000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Copy handle descriptor + * 3 : Event Handle + */ +void GetTagOutOfRangeEvent(Interface* self); + +/** + * NFC::GetTagState service function + * Inputs: + * 0 : Header code [0x000D0000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : (u8) Tag state + */ +void GetTagState(Interface* self); + +/** + * NFC::CommunicationGetStatus service function + * Inputs: + * 0 : Header code [0x000F0000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : (u8) Communication state + */ +void CommunicationGetStatus(Interface* self); + /// Initialize all NFC services. void Init(); +/// Shutdown all NFC services. +void Shutdown(); + } // namespace NFC } // namespace Service diff --git a/src/core/hle/service/nfc/nfc_m.cpp b/src/core/hle/service/nfc/nfc_m.cpp index 717335c11..ebe637650 100644 --- a/src/core/hle/service/nfc/nfc_m.cpp +++ b/src/core/hle/service/nfc/nfc_m.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfc/nfc_m.h" namespace Service { @@ -10,17 +11,19 @@ namespace NFC { const Interface::FunctionInfo FunctionTable[] = { // clang-format off // nfc:u shared commands - {0x00010040, nullptr, "Initialize"}, - {0x00020040, nullptr, "Shutdown"}, - {0x00030000, nullptr, "StartCommunication"}, - {0x00040000, nullptr, "StopCommunication"}, - {0x00050040, nullptr, "StartTagScanning"}, - {0x00060000, nullptr, "StopTagScanning"}, - {0x00070000, nullptr, "LoadAmiiboData"}, - {0x00080000, nullptr, "ResetTagScanState"}, + {0x00010040, Initialize, "Initialize"}, + {0x00020040, Shutdown, "Shutdown"}, + {0x00030000, StartCommunication, "StartCommunication"}, + {0x00040000, StopCommunication, "StopCommunication"}, + {0x00050040, StartTagScanning, "StartTagScanning"}, + {0x00060000, StopTagScanning, "StopTagScanning"}, + {0x00070000, LoadAmiiboData, "LoadAmiiboData"}, + {0x00080000, ResetTagScanState, "ResetTagScanState"}, {0x00090002, nullptr, "UpdateStoredAmiiboData"}, - {0x000D0000, nullptr, "GetTagState"}, - {0x000F0000, nullptr, "CommunicationGetStatus"}, + {0x000B0000, GetTagInRangeEvent, "GetTagInRangeEvent"}, + {0x000C0000, GetTagOutOfRangeEvent, "GetTagOutOfRangeEvent"}, + {0x000D0000, GetTagState, "GetTagState"}, + {0x000F0000, CommunicationGetStatus, "CommunicationGetStatus"}, {0x00100000, nullptr, "GetTagInfo2"}, {0x00110000, nullptr, "GetTagInfo"}, {0x00120000, nullptr, "CommunicationGetResult"}, diff --git a/src/core/hle/service/nfc/nfc_u.cpp b/src/core/hle/service/nfc/nfc_u.cpp index deffb0b4f..5a40c7874 100644 --- a/src/core/hle/service/nfc/nfc_u.cpp +++ b/src/core/hle/service/nfc/nfc_u.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfc/nfc_u.h" namespace Service { @@ -9,17 +10,19 @@ namespace NFC { const Interface::FunctionInfo FunctionTable[] = { // clang-format off - {0x00010040, nullptr, "Initialize"}, - {0x00020040, nullptr, "Shutdown"}, - {0x00030000, nullptr, "StartCommunication"}, - {0x00040000, nullptr, "StopCommunication"}, - {0x00050040, nullptr, "StartTagScanning"}, - {0x00060000, nullptr, "StopTagScanning"}, - {0x00070000, nullptr, "LoadAmiiboData"}, - {0x00080000, nullptr, "ResetTagScanState"}, + {0x00010040, Initialize, "Initialize"}, + {0x00020040, Shutdown, "Shutdown"}, + {0x00030000, StartCommunication, "StartCommunication"}, + {0x00040000, StopCommunication, "StopCommunication"}, + {0x00050040, StartTagScanning, "StartTagScanning"}, + {0x00060000, StopTagScanning, "StopTagScanning"}, + {0x00070000, LoadAmiiboData, "LoadAmiiboData"}, + {0x00080000, ResetTagScanState, "ResetTagScanState"}, {0x00090002, nullptr, "UpdateStoredAmiiboData"}, - {0x000D0000, nullptr, "GetTagState"}, - {0x000F0000, nullptr, "CommunicationGetStatus"}, + {0x000B0000, GetTagInRangeEvent, "GetTagInRangeEvent"}, + {0x000C0000, GetTagOutOfRangeEvent, "GetTagOutOfRangeEvent"}, + {0x000D0000, GetTagState, "GetTagState"}, + {0x000F0000, CommunicationGetStatus, "CommunicationGetStatus"}, {0x00100000, nullptr, "GetTagInfo2"}, {0x00110000, nullptr, "GetTagInfo"}, {0x00120000, nullptr, "CommunicationGetResult"}, diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 7e52a05d9..0672ac2e3 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -6,9 +6,8 @@ #include "common/logging/log.h" #include "common/string_util.h" - #include "core/hle/kernel/server_port.h" -#include "core/hle/service/ac_u.h" +#include "core/hle/service/ac/ac.h" #include "core/hle/service/act/act.h" #include "core/hle/service/am/am.h" #include "core/hle/service/apt/apt.h" @@ -138,6 +137,7 @@ void Init() { AddNamedPort(new ERR::ERR_F); FS::ArchiveInit(); + AC::Init(); ACT::Init(); AM::Init(); APT::Init(); @@ -158,7 +158,6 @@ void Init() { PTM::Init(); QTM::Init(); - AddService(new AC::AC_U); AddService(new CSND::CSND_SND); AddService(new DSP_DSP::Interface); AddService(new GSP::GSP_GPU); @@ -178,6 +177,7 @@ void Init() { /// Shutdown ServiceManager void Shutdown() { PTM::Shutdown(); + NFC::Shutdown(); NIM::Shutdown(); NEWS::Shutdown(); NDM::Shutdown(); @@ -191,6 +191,7 @@ void Shutdown() { BOSS::Shutdown(); APT::Shutdown(); AM::Shutdown(); + AC::Shutdown(); FS::ArchiveShutdown(); g_srv_services.clear(); diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp index c3918cdd0..dcc5c3c90 100644 --- a/src/core/hle/service/soc_u.cpp +++ b/src/core/hle/service/soc_u.cpp @@ -603,7 +603,6 @@ static void RecvFrom(Interface* self) { u32 socket_handle = cmd_buffer[1]; u32 len = cmd_buffer[2]; u32 flags = cmd_buffer[3]; - socklen_t addr_len = static_cast<socklen_t>(cmd_buffer[4]); struct { u32 output_buffer_descriptor; @@ -693,7 +692,6 @@ static void Poll(Interface* self) { static void GetSockName(Interface* self) { u32* cmd_buffer = Kernel::GetCommandBuffer(); u32 socket_handle = cmd_buffer[1]; - socklen_t ctr_len = cmd_buffer[2]; // Memory address of the ctr_dest_addr structure VAddr ctr_dest_addr_addr = cmd_buffer[0x104 >> 2]; @@ -734,7 +732,6 @@ static void Shutdown(Interface* self) { static void GetPeerName(Interface* self) { u32* cmd_buffer = Kernel::GetCommandBuffer(); u32 socket_handle = cmd_buffer[1]; - socklen_t len = cmd_buffer[2]; // Memory address of the ctr_dest_addr structure VAddr ctr_dest_addr_addr = cmd_buffer[0x104 >> 2]; @@ -765,7 +762,6 @@ static void Connect(Interface* self) { // performing nonblocking operations and spinlock until the data is available u32* cmd_buffer = Kernel::GetCommandBuffer(); u32 socket_handle = cmd_buffer[1]; - socklen_t len = cmd_buffer[2]; // Memory address of the ctr_input_addr structure VAddr ctr_input_addr_addr = cmd_buffer[6]; diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp index 35eb2b597..907d9c8fa 100644 --- a/src/core/hle/service/y2r_u.cpp +++ b/src/core/hle/service/y2r_u.cpp @@ -533,7 +533,9 @@ static void GetStandardCoefficient(Interface* self) { LOG_DEBUG(Service_Y2R, "called standard_coefficient=%u ", index); } else { cmd_buff[0] = IPC::MakeHeader(0x21, 1, 0); - cmd_buff[1] = -1; // TODO(bunnei): Identify the correct error code for this + cmd_buff[1] = ResultCode(ErrorDescription::InvalidEnumValue, ErrorModule::CAM, + ErrorSummary::InvalidArgument, ErrorLevel::Usage) + .raw; LOG_ERROR(Service_Y2R, "called standard_coefficient=%u The argument is invalid!", index); } diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index 2ca270de3..96db39ad9 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -248,6 +248,8 @@ static ResultCode SendSyncRequest(Kernel::Handle handle) { LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s)", handle, session->GetName().c_str()); + Core::System::GetInstance().PrepareReschedule(); + // TODO(Subv): svcSendSyncRequest should put the caller thread to sleep while the server // responds and cause a reschedule. return session->SendSyncRequest(); @@ -270,27 +272,27 @@ static ResultCode WaitSynchronization1(Kernel::Handle handle, s64 nano_seconds) LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s:%s), nanoseconds=%lld", handle, object->GetTypeName().c_str(), object->GetName().c_str(), nano_seconds); - if (object->ShouldWait()) { + if (object->ShouldWait(thread)) { if (nano_seconds == 0) return ERR_SYNC_TIMEOUT; + thread->wait_objects = {object}; object->AddWaitingThread(thread); - // TODO(Subv): Perform things like update the mutex lock owner's priority to - // prevent priority inversion. Currently this is done in Mutex::ShouldWait, - // but it should be moved to a function that is called from here. - thread->status = THREADSTATUS_WAIT_SYNCH; + thread->status = THREADSTATUS_WAIT_SYNCH_ANY; // Create an event to wake the thread up after the specified nanosecond delay has passed thread->WakeAfterDelay(nano_seconds); + Core::System::GetInstance().PrepareReschedule(); + // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread // resumes due to a signal in its wait objects. // Otherwise we retain the default value of timeout. return ERR_SYNC_TIMEOUT; } - object->Acquire(); + object->Acquire(thread); return RESULT_SUCCESS; } @@ -324,19 +326,14 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha objects[i] = object; } - // Clear the mapping of wait object indices. - // We don't want any lingering state in this map. - // It will be repopulated later in the wait_all = false case. - thread->wait_objects_index.clear(); - if (wait_all) { bool all_available = std::all_of(objects.begin(), objects.end(), - [](const ObjectPtr& object) { return !object->ShouldWait(); }); + [thread](const ObjectPtr& object) { return !object->ShouldWait(thread); }); if (all_available) { // We can acquire all objects right now, do so. for (auto& object : objects) - object->Acquire(); + object->Acquire(thread); // Note: In this case, the `out` parameter is not set, // and retains whatever value it had before. return RESULT_SUCCESS; @@ -350,22 +347,20 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha return ERR_SYNC_TIMEOUT; // Put the thread to sleep - thread->status = THREADSTATUS_WAIT_SYNCH; + thread->status = THREADSTATUS_WAIT_SYNCH_ALL; // Add the thread to each of the objects' waiting threads. for (auto& object : objects) { object->AddWaitingThread(thread); - // TODO(Subv): Perform things like update the mutex lock owner's priority to - // prevent priority inversion. Currently this is done in Mutex::ShouldWait, - // but it should be moved to a function that is called from here. } - // Set the thread's waitlist to the list of objects passed to WaitSynchronizationN thread->wait_objects = std::move(objects); // Create an event to wake the thread up after the specified nanosecond delay has passed thread->WakeAfterDelay(nano_seconds); + Core::System::GetInstance().PrepareReschedule(); + // This value gets set to -1 by default in this case, it is not modified after this. *out = -1; // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to @@ -373,13 +368,14 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha return ERR_SYNC_TIMEOUT; } else { // Find the first object that is acquirable in the provided list of objects - auto itr = std::find_if(objects.begin(), objects.end(), - [](const ObjectPtr& object) { return !object->ShouldWait(); }); + auto itr = std::find_if(objects.begin(), objects.end(), [thread](const ObjectPtr& object) { + return !object->ShouldWait(thread); + }); if (itr != objects.end()) { // We found a ready object, acquire it and set the result value Kernel::WaitObject* object = itr->get(); - object->Acquire(); + object->Acquire(thread); *out = std::distance(objects.begin(), itr); return RESULT_SUCCESS; } @@ -392,28 +388,24 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha return ERR_SYNC_TIMEOUT; // Put the thread to sleep - thread->status = THREADSTATUS_WAIT_SYNCH; - - // Clear the thread's waitlist, we won't use it for wait_all = false - thread->wait_objects.clear(); + thread->status = THREADSTATUS_WAIT_SYNCH_ANY; // Add the thread to each of the objects' waiting threads. for (size_t i = 0; i < objects.size(); ++i) { Kernel::WaitObject* object = objects[i].get(); - // Set the index of this object in the mapping of Objects -> index for this thread. - thread->wait_objects_index[object->GetObjectId()] = static_cast<int>(i); object->AddWaitingThread(thread); - // TODO(Subv): Perform things like update the mutex lock owner's priority to - // prevent priority inversion. Currently this is done in Mutex::ShouldWait, - // but it should be moved to a function that is called from here. } + thread->wait_objects = std::move(objects); + // Note: If no handles and no timeout were given, then the thread will deadlock, this is // consistent with hardware behavior. // Create an event to wake the thread up after the specified nanosecond delay has passed thread->WakeAfterDelay(nano_seconds); + Core::System::GetInstance().PrepareReschedule(); + // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a // signal in one of its wait objects. // Otherwise we retain the default value of timeout, and -1 in the out parameter @@ -448,6 +440,9 @@ static ResultCode ArbitrateAddress(Kernel::Handle handle, u32 address, u32 type, auto res = arbiter->ArbitrateAddress(static_cast<Kernel::ArbitrationType>(type), address, value, nanoseconds); + // TODO(Subv): Identify in which specific cases this call should cause a reschedule. + Core::System::GetInstance().PrepareReschedule(); + return res; } @@ -537,16 +532,18 @@ static ResultCode CreateThread(Kernel::Handle* out_handle, s32 priority, u32 ent name = Common::StringFromFormat("unknown-%08x", entry_point); } - // TODO(bunnei): Implement resource limits to return an error code instead of the below assert. - // The error code should be: Description::NotAuthorized, Module::OS, Summary::WrongArgument, - // Level::Permanent - ASSERT_MSG(priority >= THREADPRIO_USERLAND_MAX, "Unexpected thread priority!"); - if (priority > THREADPRIO_LOWEST) { return ResultCode(ErrorDescription::OutOfRange, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); } + using Kernel::ResourceLimit; + Kernel::SharedPtr<ResourceLimit>& resource_limit = Kernel::g_current_process->resource_limit; + if (resource_limit->GetMaxResourceValue(Kernel::ResourceTypes::PRIORITY) > priority) { + return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::OS, + ErrorSummary::WrongArgument, ErrorLevel::Permanent); + } + switch (processor_id) { case THREADPROCESSORID_ALL: case THREADPROCESSORID_DEFAULT: @@ -574,6 +571,8 @@ static ResultCode CreateThread(Kernel::Handle* out_handle, s32 priority, u32 ent CASCADE_RESULT(*out_handle, Kernel::g_handle_table.Create(std::move(thread))); + Core::System::GetInstance().PrepareReschedule(); + LOG_TRACE(Kernel_SVC, "called entrypoint=0x%08X (%s), arg=0x%08X, stacktop=0x%08X, " "threadpriority=0x%08X, processorid=0x%08X : created handle=0x%08X", entry_point, name.c_str(), arg, stack_top, priority, processor_id, *out_handle); @@ -586,6 +585,7 @@ static void ExitThread() { LOG_TRACE(Kernel_SVC, "called, pc=0x%08X", Core::CPU().GetPC()); Kernel::ExitCurrentThread(); + Core::System::GetInstance().PrepareReschedule(); } /// Gets the priority for the specified thread @@ -600,11 +600,32 @@ static ResultCode GetThreadPriority(s32* priority, Kernel::Handle handle) { /// Sets the priority for the specified thread static ResultCode SetThreadPriority(Kernel::Handle handle, s32 priority) { + if (priority > THREADPRIO_LOWEST) { + return ResultCode(ErrorDescription::OutOfRange, ErrorModule::OS, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); + } + SharedPtr<Kernel::Thread> thread = Kernel::g_handle_table.Get<Kernel::Thread>(handle); if (thread == nullptr) return ERR_INVALID_HANDLE; + using Kernel::ResourceLimit; + // Note: The kernel uses the current process's resource limit instead of + // the one from the thread owner's resource limit. + Kernel::SharedPtr<ResourceLimit>& resource_limit = Kernel::g_current_process->resource_limit; + if (resource_limit->GetMaxResourceValue(Kernel::ResourceTypes::PRIORITY) > priority) { + return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::OS, + ErrorSummary::WrongArgument, ErrorLevel::Permanent); + } + thread->SetPriority(priority); + thread->UpdatePriority(); + + // Update the mutexes that this thread is waiting for + for (auto& mutex : thread->pending_mutexes) + mutex->UpdatePriority(); + + Core::System::GetInstance().PrepareReschedule(); return RESULT_SUCCESS; } @@ -844,11 +865,18 @@ static ResultCode CancelTimer(Kernel::Handle handle) { static void SleepThread(s64 nanoseconds) { LOG_TRACE(Kernel_SVC, "called nanoseconds=%lld", nanoseconds); + // Don't attempt to yield execution if there are no available threads to run, + // this way we avoid a useless reschedule to the idle thread. + if (nanoseconds == 0 && !Kernel::HaveReadyThreads()) + return; + // Sleep current thread and check for next thread to schedule Kernel::WaitCurrentThread_Sleep(); // Create an event to wake the thread up after the specified nanosecond delay has passed Kernel::GetCurrentThread()->WakeAfterDelay(nanoseconds); + + Core::System::GetInstance().PrepareReschedule(); } /// This returns the total CPU ticks elapsed since the CPU was powered-on @@ -890,7 +918,11 @@ static ResultCode CreateMemoryBlock(Kernel::Handle* out_handle, u32 addr, u32 si return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); - if (addr < Memory::PROCESS_IMAGE_VADDR || addr + size > Memory::SHARED_MEMORY_VADDR_END) { + // TODO(Subv): Processes with memory type APPLICATION are not allowed + // to create memory blocks with addr = 0, any attempts to do so + // should return error 0xD92007EA. + if ((addr < Memory::PROCESS_IMAGE_VADDR || addr + size > Memory::SHARED_MEMORY_VADDR_END) && + addr != 0) { return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); } @@ -1184,8 +1216,6 @@ void CallSVC(u32 immediate) { if (info) { if (info->func) { info->func(); - // TODO(Subv): Not all service functions should cause a reschedule in all cases. - Core::System::GetInstance().PrepareReschedule(); } else { LOG_ERROR(Kernel_SVC, "unimplemented SVC function %s(..)", info->name); } diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index 1a1ee90b2..fa8c13d36 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -15,7 +15,6 @@ #include "common/vector_math.h" #include "core/core_timing.h" #include "core/hle/service/gsp_gpu.h" -#include "core/hle/service/hid/hid.h" #include "core/hw/gpu.h" #include "core/hw/hw.h" #include "core/memory.h" @@ -33,7 +32,7 @@ namespace GPU { Regs g_regs; /// 268MHz CPU clocks / 60Hz frames per second -const u64 frame_ticks = 268123480ull / 60; +const u64 frame_ticks = BASE_CLOCK_RATE_ARM11 / 60; /// Event id for CoreTiming static int vblank_event; /// Total number of frames drawn @@ -551,9 +550,6 @@ static void VBlankCallback(u64 userdata, int cycles_late) { Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC0); Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC1); - // Check for user input updates - Service::HID::Update(); - if (!Settings::values.use_vsync && Settings::values.toggle_framelimit) { FrameLimiter(); } diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 1c10740a0..09266e8b0 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -177,18 +177,34 @@ static THREEDSX_Error Load3DSXFile(FileUtil::IOFile& file, u32 base_addr, pos += table.skip; s32 num_patches = table.patch; while (0 < num_patches && pos < end_pos) { - u32 in_addr = - static_cast<u32>(reinterpret_cast<u8*>(pos) - program_image.data()); - u32 addr = TranslateAddr(*pos, &loadinfo, offsets); - LOG_TRACE(Loader, "Patching %08X <-- rel(%08X,%d) (%08X)", - base_addr + in_addr, addr, current_segment_reloc_table, *pos); + u32 in_addr = base_addr + static_cast<u32>(reinterpret_cast<u8*>(pos) - + program_image.data()); + u32 orig_data = *pos; + u32 sub_type = orig_data >> (32 - 4); + u32 addr = TranslateAddr(orig_data & ~0xF0000000, &loadinfo, offsets); + LOG_TRACE(Loader, "Patching %08X <-- rel(%08X,%d) (%08X)", in_addr, addr, + current_segment_reloc_table, *pos); switch (current_segment_reloc_table) { - case 0: - *pos = (addr); + case 0: { + if (sub_type != 0) + return ERROR_READ; + *pos = addr; break; - case 1: - *pos = static_cast<u32>(addr - in_addr); + } + case 1: { + u32 data = addr - in_addr; + switch (sub_type) { + case 0: // 32-bit signed offset + *pos = data; + break; + case 1: // 31-bit signed offset + *pos = data & ~(1U << 31); + break; + default: + return ERROR_READ; + } break; + } default: break; // this should never happen } diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 6f2164428..5df33f6d2 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -11,8 +11,10 @@ #include "core/file_sys/archive_romfs.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" +#include "core/hle/service/cfg/cfg.h" #include "core/hle/service/fs/archive.h" #include "core/loader/ncch.h" +#include "core/loader/smdh.h" #include "core/memory.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -286,7 +288,7 @@ ResultStatus AppLoader_NCCH::LoadExeFS() { LOG_DEBUG(Loader, "Thread priority: 0x%X", priority); LOG_DEBUG(Loader, "Resource limit category: %d", resource_limit_category); LOG_DEBUG(Loader, "System Mode: %d", - exheader_header.arm11_system_local_caps.system_mode); + static_cast<int>(exheader_header.arm11_system_local_caps.system_mode)); if (exheader_header.arm11_system_local_caps.program_id != ncch_header.program_id) { LOG_ERROR(Loader, "ExHeader Program ID mismatch: the ROM is probably encrypted."); @@ -309,6 +311,23 @@ ResultStatus AppLoader_NCCH::LoadExeFS() { return ResultStatus::Success; } +void AppLoader_NCCH::ParseRegionLockoutInfo() { + std::vector<u8> smdh_buffer; + if (ReadIcon(smdh_buffer) == ResultStatus::Success && smdh_buffer.size() >= sizeof(SMDH)) { + SMDH smdh; + memcpy(&smdh, smdh_buffer.data(), sizeof(SMDH)); + u32 region_lockout = smdh.region_lockout; + constexpr u32 REGION_COUNT = 7; + for (u32 region = 0; region < REGION_COUNT; ++region) { + if (region_lockout & 1) { + Service::CFG::SetPreferredRegionCode(region); + break; + } + region_lockout >>= 1; + } + } +} + ResultStatus AppLoader_NCCH::Load() { if (is_loaded) return ResultStatus::ErrorAlreadyLoaded; @@ -325,6 +344,9 @@ ResultStatus AppLoader_NCCH::Load() { Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this), Service::FS::ArchiveIdCode::RomFS); + + ParseRegionLockoutInfo(); + return ResultStatus::Success; } diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index 6afc171a5..4ef95b5c6 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -181,7 +181,7 @@ public: * Loads the Exheader and returns the system mode for this application. * @return Optional with the kernel system mode */ - boost::optional<u32> LoadKernelSystemMode(); + boost::optional<u32> LoadKernelSystemMode() override; ResultStatus ReadCode(std::vector<u8>& buffer) override; @@ -229,6 +229,9 @@ private: */ ResultStatus LoadExeFS(); + /// Reads the region lockout info in the SMDH and send it to CFG service + void ParseRegionLockoutInfo(); + bool is_exefs_loaded = false; bool is_compressed = false; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 5d23c52f9..3a32b70aa 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -15,12 +15,11 @@ Values values = {}; void Apply() { - GDBStub::SetServerPort(static_cast<u32>(values.gdbstub_port)); + GDBStub::SetServerPort(values.gdbstub_port); GDBStub::ToggleServer(values.use_gdbstub); VideoCore::g_hw_renderer_enabled = values.use_hw_renderer; VideoCore::g_shader_jit_enabled = values.use_shader_jit; - VideoCore::g_scaled_resolution_enabled = values.use_scaled_resolution; VideoCore::g_toggle_framelimit_enabled = values.toggle_framelimit; if (VideoCore::g_emu_window) { diff --git a/src/core/settings.h b/src/core/settings.h index db4c8fada..b6c75531f 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -7,6 +7,7 @@ #include <array> #include <string> #include "common/common_types.h" +#include "core/hle/service/cam/cam.h" namespace Settings { @@ -88,7 +89,7 @@ struct Values { // Renderer bool use_hw_renderer; bool use_shader_jit; - bool use_scaled_resolution; + float resolution_factor; bool use_vsync; bool toggle_framelimit; @@ -104,11 +105,20 @@ struct Values { // Audio std::string sink_id; bool enable_audio_stretching; + std::string audio_device_id; + + // Camera + std::array<std::string, Service::CAM::NumCameras> camera_name; + std::array<std::string, Service::CAM::NumCameras> camera_config; // Debugging bool use_gdbstub; u16 gdbstub_port; } extern values; +// a special value for Values::region_value indicating that citra will automatically select a region +// value to fit the region lockout info of the game +static constexpr int REGION_VALUE_AUTO_SELECT = -1; + void Apply(); } diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 6ca319b59..ad984cd94 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -1,36 +1,30 @@ set(SRCS - renderer_opengl/gl_rasterizer.cpp - renderer_opengl/gl_rasterizer_cache.cpp - renderer_opengl/gl_shader_gen.cpp - renderer_opengl/gl_shader_util.cpp - renderer_opengl/gl_state.cpp - renderer_opengl/renderer_opengl.cpp - debug_utils/debug_utils.cpp clipper.cpp command_processor.cpp + debug_utils/debug_utils.cpp pica.cpp primitive_assembly.cpp rasterizer.cpp renderer_base.cpp + renderer_opengl/gl_rasterizer.cpp + renderer_opengl/gl_rasterizer_cache.cpp + renderer_opengl/gl_shader_gen.cpp + renderer_opengl/gl_shader_util.cpp + renderer_opengl/gl_state.cpp + renderer_opengl/renderer_opengl.cpp shader/shader.cpp shader/shader_interpreter.cpp swrasterizer.cpp + texture/etc1.cpp + texture/texture_decode.cpp vertex_loader.cpp video_core.cpp ) set(HEADERS - debug_utils/debug_utils.h - renderer_opengl/gl_rasterizer.h - renderer_opengl/gl_rasterizer_cache.h - renderer_opengl/gl_resource_manager.h - renderer_opengl/gl_shader_gen.h - renderer_opengl/gl_shader_util.h - renderer_opengl/gl_state.h - renderer_opengl/pica_to_gl.h - renderer_opengl/renderer_opengl.h clipper.h command_processor.h + debug_utils/debug_utils.h gpu_debugger.h pica.h pica_state.h @@ -39,10 +33,20 @@ set(HEADERS rasterizer.h rasterizer_interface.h renderer_base.h + renderer_opengl/gl_rasterizer.h + renderer_opengl/gl_rasterizer_cache.h + renderer_opengl/gl_resource_manager.h + renderer_opengl/gl_shader_gen.h + renderer_opengl/gl_shader_util.h + renderer_opengl/gl_state.h + renderer_opengl/pica_to_gl.h + renderer_opengl/renderer_opengl.h shader/debug_data.h shader/shader.h shader/shader_interpreter.h swrasterizer.h + texture/etc1.h + texture/texture_decode.h utils.h vertex_loader.h video_core.h @@ -50,10 +54,12 @@ set(HEADERS if(ARCHITECTURE_x86_64) set(SRCS ${SRCS} - shader/shader_jit_x64.cpp) + shader/shader_jit_x64.cpp + shader/shader_jit_x64_compiler.cpp) set(HEADERS ${HEADERS} - shader/shader_jit_x64.h) + shader/shader_jit_x64.h + shader/shader_jit_x64_compiler.h) endif() create_directory_groups(${SRCS} ${HEADERS}) diff --git a/src/video_core/clipper.cpp b/src/video_core/clipper.cpp index 05b5cea73..0774ffc53 100644 --- a/src/video_core/clipper.cpp +++ b/src/video_core/clipper.cpp @@ -18,6 +18,8 @@ #include "video_core/rasterizer.h" #include "video_core/shader/shader.h" +using Pica::Rasterizer::Vertex; + namespace Pica { namespace Clipper { @@ -29,20 +31,20 @@ public: float24::FromFloat32(0), float24::FromFloat32(0))) : coeffs(coeffs), bias(bias) {} - bool IsInside(const OutputVertex& vertex) const { + bool IsInside(const Vertex& vertex) const { return Math::Dot(vertex.pos + bias, coeffs) <= float24::FromFloat32(0); } - bool IsOutSide(const OutputVertex& vertex) const { + bool IsOutSide(const Vertex& vertex) const { return !IsInside(vertex); } - OutputVertex GetIntersection(const OutputVertex& v0, const OutputVertex& v1) const { + Vertex GetIntersection(const Vertex& v0, const Vertex& v1) const { float24 dp = Math::Dot(v0.pos + bias, coeffs); float24 dp_prev = Math::Dot(v1.pos + bias, coeffs); float24 factor = dp_prev / (dp_prev - dp); - return OutputVertex::Lerp(factor, v0, v1); + return Vertex::Lerp(factor, v0, v1); } private: @@ -51,7 +53,7 @@ private: Math::Vec4<float24> bias; }; -static void InitScreenCoordinates(OutputVertex& vtx) { +static void InitScreenCoordinates(Vertex& vtx) { struct { float24 halfsize_x; float24 offset_x; @@ -91,8 +93,8 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu // introduces at most 1 new vertex to the polygon. Since we start with a triangle and have a // fixed 6 clipping planes, the maximum number of vertices of the clipped polygon is 3 + 6 = 9. static const size_t MAX_VERTICES = 9; - static_vector<OutputVertex, MAX_VERTICES> buffer_a = {v0, v1, v2}; - static_vector<OutputVertex, MAX_VERTICES> buffer_b; + static_vector<Vertex, MAX_VERTICES> buffer_a = {v0, v1, v2}; + static_vector<Vertex, MAX_VERTICES> buffer_b; auto* output_list = &buffer_a; auto* input_list = &buffer_b; @@ -123,7 +125,7 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu std::swap(input_list, output_list); output_list->clear(); - const OutputVertex* reference_vertex = &input_list->back(); + const Vertex* reference_vertex = &input_list->back(); for (const auto& vertex : *input_list) { // NOTE: This algorithm changes vertex order in some cases! @@ -148,9 +150,9 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu InitScreenCoordinates((*output_list)[1]); for (size_t i = 0; i < output_list->size() - 2; i++) { - OutputVertex& vtx0 = (*output_list)[0]; - OutputVertex& vtx1 = (*output_list)[i + 1]; - OutputVertex& vtx2 = (*output_list)[i + 2]; + Vertex& vtx0 = (*output_list)[0]; + Vertex& vtx1 = (*output_list)[i + 1]; + Vertex& vtx2 = (*output_list)[i + 2]; InitScreenCoordinates(vtx2); diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index ea58e9f54..4955ff9f9 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -125,33 +125,37 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { // TODO: Verify that this actually modifies the register! if (setup.index < 15) { - g_state.vs_default_attributes[setup.index] = attribute; + g_state.input_default_attributes.attr[setup.index] = attribute; setup.index++; } else { - // Put each attribute into an immediate input buffer. - // When all specified immediate attributes are present, the Vertex Shader is invoked - // and everything is - // sent to the primitive assembler. + // Put each attribute into an immediate input buffer. When all specified immediate + // attributes are present, the Vertex Shader is invoked and everything is sent to + // the primitive assembler. auto& immediate_input = g_state.immediate.input_vertex; auto& immediate_attribute_id = g_state.immediate.current_attribute; - immediate_input.attr[immediate_attribute_id++] = attribute; + immediate_input.attr[immediate_attribute_id] = attribute; - if (immediate_attribute_id >= regs.vs.num_input_attributes + 1) { + if (immediate_attribute_id < regs.max_input_attrib_index) { + immediate_attribute_id += 1; + } else { MICROPROFILE_SCOPE(GPU_Drawing); immediate_attribute_id = 0; - Shader::UnitState shader_unit; - g_state.vs.Setup(); + auto* shader_engine = Shader::GetEngine(); + shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset); // Send to vertex shader if (g_debug_context) g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, static_cast<void*>(&immediate_input)); - g_state.vs.Run(shader_unit, immediate_input, regs.vs.num_input_attributes + 1); - Shader::OutputVertex output_vertex = - shader_unit.output_registers.ToVertex(regs.vs); + Shader::UnitState shader_unit; + Shader::AttributeBuffer output{}; + + shader_unit.LoadInput(regs.vs, immediate_input); + shader_engine->Run(g_state.vs, shader_unit); + shader_unit.WriteOutput(regs.vs, output); // Send to renderer using Pica::Shader::OutputVertex; @@ -160,7 +164,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2); }; - g_state.primitive_assembler.SubmitVertex(output_vertex, AddTriangle); + g_state.primitive_assembler.SubmitVertex( + Shader::OutputVertex::FromAttributeBuffer(regs, output), AddTriangle); } } } @@ -243,8 +248,10 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { unsigned int vertex_cache_pos = 0; vertex_cache_ids.fill(-1); + auto* shader_engine = Shader::GetEngine(); Shader::UnitState shader_unit; - g_state.vs.Setup(); + + shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset); for (unsigned int index = 0; index < regs.num_vertices; ++index) { // Indexed rendering doesn't use the start offset @@ -276,17 +283,19 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { if (!vertex_cache_hit) { // Initialize data for the current vertex - Shader::InputVertex input; + Shader::AttributeBuffer input, output{}; loader.LoadVertex(base_address, index, vertex, input, memory_accesses); // Send to vertex shader if (g_debug_context) g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, (void*)&input); - g_state.vs.Run(shader_unit, input, loader.GetNumTotalAttributes()); + shader_unit.LoadInput(regs.vs, input); + shader_engine->Run(g_state.vs, shader_unit); + shader_unit.WriteOutput(regs.vs, output); // Retrieve vertex from register data - output_vertex = shader_unit.output_registers.ToVertex(regs.vs); + output_vertex = Shader::OutputVertex::FromAttributeBuffer(regs, output); if (is_indexed) { vertex_cache[vertex_cache_pos] = output_vertex; diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index c44b3d95a..2d40f7d4f 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -35,6 +35,7 @@ #include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" #include "video_core/shader/shader.h" +#include "video_core/texture/texture_decode.h" #include "video_core/utils.h" #include "video_core/video_core.h" @@ -315,257 +316,6 @@ std::unique_ptr<PicaTrace> FinishPicaTracing() { return ret; } -const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const TextureInfo& info, - bool disable_alpha) { - const unsigned int coarse_x = x & ~7; - const unsigned int coarse_y = y & ~7; - - if (info.format != Regs::TextureFormat::ETC1 && info.format != Regs::TextureFormat::ETC1A4) { - // TODO(neobrain): Fix code design to unify vertical block offsets! - source += coarse_y * info.stride; - } - - // TODO: Assert that width/height are multiples of block dimensions - - switch (info.format) { - case Regs::TextureFormat::RGBA8: { - auto res = Color::DecodeRGBA8(source + VideoCore::GetMortonOffset(x, y, 4)); - return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())}; - } - - case Regs::TextureFormat::RGB8: { - auto res = Color::DecodeRGB8(source + VideoCore::GetMortonOffset(x, y, 3)); - return {res.r(), res.g(), res.b(), 255}; - } - - case Regs::TextureFormat::RGB5A1: { - auto res = Color::DecodeRGB5A1(source + VideoCore::GetMortonOffset(x, y, 2)); - return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())}; - } - - case Regs::TextureFormat::RGB565: { - auto res = Color::DecodeRGB565(source + VideoCore::GetMortonOffset(x, y, 2)); - return {res.r(), res.g(), res.b(), 255}; - } - - case Regs::TextureFormat::RGBA4: { - auto res = Color::DecodeRGBA4(source + VideoCore::GetMortonOffset(x, y, 2)); - return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())}; - } - - case Regs::TextureFormat::IA8: { - const u8* source_ptr = source + VideoCore::GetMortonOffset(x, y, 2); - - if (disable_alpha) { - // Show intensity as red, alpha as green - return {source_ptr[1], source_ptr[0], 0, 255}; - } else { - return {source_ptr[1], source_ptr[1], source_ptr[1], source_ptr[0]}; - } - } - - case Regs::TextureFormat::RG8: { - auto res = Color::DecodeRG8(source + VideoCore::GetMortonOffset(x, y, 2)); - return {res.r(), res.g(), 0, 255}; - } - - case Regs::TextureFormat::I8: { - const u8* source_ptr = source + VideoCore::GetMortonOffset(x, y, 1); - return {*source_ptr, *source_ptr, *source_ptr, 255}; - } - - case Regs::TextureFormat::A8: { - const u8* source_ptr = source + VideoCore::GetMortonOffset(x, y, 1); - - 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 + VideoCore::GetMortonOffset(x, y, 1); - - u8 i = Color::Convert4To8(((*source_ptr) & 0xF0) >> 4); - u8 a = Color::Convert4To8((*source_ptr) & 0xF); - - if (disable_alpha) { - // Show intensity as red, alpha as green - return {i, a, 0, 255}; - } else { - return {i, i, i, a}; - } - } - - case Regs::TextureFormat::I4: { - u32 morton_offset = VideoCore::GetMortonOffset(x, y, 1); - const u8* source_ptr = source + morton_offset / 2; - - u8 i = (morton_offset % 2) ? ((*source_ptr & 0xF0) >> 4) : (*source_ptr & 0xF); - i = Color::Convert4To8(i); - - return {i, i, i, 255}; - } - - case Regs::TextureFormat::A4: { - u32 morton_offset = VideoCore::GetMortonOffset(x, y, 1); - const u8* source_ptr = source + morton_offset / 2; - - u8 a = (morton_offset % 2) ? ((*source_ptr & 0xF0) >> 4) : (*source_ptr & 0xF); - a = Color::Convert4To8(a); - - if (disable_alpha) { - return {a, a, a, 255}; - } else { - return {0, 0, 0, a}; - } - } - - case Regs::TextureFormat::ETC1: - case Regs::TextureFormat::ETC1A4: { - bool has_alpha = (info.format == Regs::TextureFormat::ETC1A4); - - // ETC1 further subdivides each 8x8 tile into four 4x4 subtiles - const int subtile_width = 4; - const int subtile_height = 4; - - int subtile_index = ((x / subtile_width) & 1) + 2 * ((y / subtile_height) & 1); - unsigned subtile_bytes = has_alpha ? 2 : 1; // TODO: Name... - - const u64* source_ptr = (const u64*)(source + coarse_x * subtile_bytes * 4 + - coarse_y * subtile_bytes * 4 * (info.width / 8) + - subtile_index * subtile_bytes * 8); - u64 alpha = 0xFFFFFFFFFFFFFFFF; - if (has_alpha) { - alpha = *source_ptr; - source_ptr++; - } - - union ETC1Tile { - // Each of these two is a collection of 16 bits (one per lookup value) - BitField<0, 16, u64> table_subindexes; - BitField<16, 16, u64> negation_flags; - - unsigned GetTableSubIndex(unsigned index) const { - return (table_subindexes >> index) & 1; - } - - bool GetNegationFlag(unsigned index) const { - return ((negation_flags >> index) & 1) == 1; - } - - BitField<32, 1, u64> flip; - BitField<33, 1, u64> differential_mode; - - BitField<34, 3, u64> table_index_2; - BitField<37, 3, u64> table_index_1; - - union { - // delta value + base value - BitField<40, 3, s64> db; - BitField<43, 5, u64> b; - - BitField<48, 3, s64> dg; - BitField<51, 5, u64> g; - - BitField<56, 3, s64> dr; - BitField<59, 5, u64> r; - } differential; - - union { - BitField<40, 4, u64> b2; - BitField<44, 4, u64> b1; - - BitField<48, 4, u64> g2; - BitField<52, 4, u64> g1; - - BitField<56, 4, u64> r2; - BitField<60, 4, u64> r1; - } separate; - - const Math::Vec3<u8> GetRGB(int x, int y) const { - int texel = 4 * x + y; - - if (flip) - std::swap(x, y); - - // Lookup base value - Math::Vec3<int> ret; - if (differential_mode) { - ret.r() = static_cast<int>(differential.r); - ret.g() = static_cast<int>(differential.g); - ret.b() = static_cast<int>(differential.b); - if (x >= 2) { - ret.r() += static_cast<int>(differential.dr); - ret.g() += static_cast<int>(differential.dg); - ret.b() += static_cast<int>(differential.db); - } - ret.r() = Color::Convert5To8(ret.r()); - ret.g() = Color::Convert5To8(ret.g()); - ret.b() = Color::Convert5To8(ret.b()); - } else { - if (x < 2) { - ret.r() = Color::Convert4To8(static_cast<u8>(separate.r1)); - ret.g() = Color::Convert4To8(static_cast<u8>(separate.g1)); - ret.b() = Color::Convert4To8(static_cast<u8>(separate.b1)); - } else { - ret.r() = Color::Convert4To8(static_cast<u8>(separate.r2)); - ret.g() = Color::Convert4To8(static_cast<u8>(separate.g2)); - ret.b() = Color::Convert4To8(static_cast<u8>(separate.b2)); - } - } - - // Add modifier - unsigned table_index = - static_cast<int>((x < 2) ? table_index_1.Value() : table_index_2.Value()); - - static const std::array<std::array<u8, 2>, 8> etc1_modifier_table = {{ - {{2, 8}}, - {{5, 17}}, - {{9, 29}}, - {{13, 42}}, - {{18, 60}}, - {{24, 80}}, - {{33, 106}}, - {{47, 183}}, - }}; - - int modifier = etc1_modifier_table.at(table_index).at(GetTableSubIndex(texel)); - if (GetNegationFlag(texel)) - modifier *= -1; - - ret.r() = MathUtil::Clamp(ret.r() + modifier, 0, 255); - ret.g() = MathUtil::Clamp(ret.g() + modifier, 0, 255); - ret.b() = MathUtil::Clamp(ret.b() + modifier, 0, 255); - - return ret.Cast<u8>(); - } - } const* etc1_tile = reinterpret_cast<const ETC1Tile*>(source_ptr); - - alpha >>= 4 * ((x & 3) * 4 + (y & 3)); - return Math::MakeVec(etc1_tile->GetRGB(x & 3, y & 3), - disable_alpha ? (u8)255 : Color::Convert4To8(alpha & 0xF)); - } - - default: - LOG_ERROR(HW_GPU, "Unknown texture format: %x", (u32)info.format); - DEBUG_ASSERT(false); - return {}; - } -} - -TextureInfo TextureInfo::FromPicaRegister(const Regs::TextureConfig& config, - const Regs::TextureFormat& format) { - TextureInfo info; - info.physical_address = config.GetPhysicalAddress(); - info.width = config.width; - info.height = config.height; - info.format = format; - info.stride = Pica::Regs::NibblesPerPixel(info.format) * info.width / 2; - return info; -} - #ifdef HAVE_PNG // Adapter functions to libpng to write/flush to File::IOFile instances. static void WriteIOFile(png_structp png_ptr, png_bytep data, png_size_t length) { @@ -642,12 +392,12 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { buf = new u8[row_stride * texture_config.height]; for (unsigned y = 0; y < texture_config.height; ++y) { for (unsigned x = 0; x < texture_config.width; ++x) { - TextureInfo info; + Pica::Texture::TextureInfo info; info.width = texture_config.width; info.height = texture_config.height; info.stride = row_stride; info.format = g_state.regs.texture0_format; - Math::Vec4<u8> texture_color = LookupTexture(data, x, y, info); + Math::Vec4<u8> texture_color = Pica::Texture::LookupTexture(data, x, y, info); buf[3 * x + y * row_stride] = texture_color.r(); buf[3 * x + y * row_stride + 1] = texture_color.g(); buf[3 * x + y * row_stride + 2] = texture_color.b(); diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index 46ea8d9c7..938a2e1b5 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -205,31 +205,6 @@ inline bool IsPicaTracing() { void OnPicaRegWrite(PicaTrace::Write write); std::unique_ptr<PicaTrace> FinishPicaTracing(); -struct TextureInfo { - PAddr physical_address; - int width; - int height; - int stride; - Pica::Regs::TextureFormat format; - - static TextureInfo FromPicaRegister(const Pica::Regs::TextureConfig& config, - const Pica::Regs::TextureFormat& format); -}; - -/** - * 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); std::string GetTevStageConfigColorCombinerString(const Pica::Regs::TevStageConfig& tev_stage); diff --git a/src/video_core/pica.cpp b/src/video_core/pica.cpp index ce2bd455e..b4a77c632 100644 --- a/src/video_core/pica.cpp +++ b/src/video_core/pica.cpp @@ -499,7 +499,7 @@ void Init() { } void Shutdown() { - Shader::ClearCache(); + Shader::Shutdown(); } template <typename T> diff --git a/src/video_core/pica.h b/src/video_core/pica.h index b2db609ec..731540b99 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -99,7 +99,8 @@ struct Regs { TEXCOORD1_U = 14, TEXCOORD1_V = 15, - // TODO: Not verified + TEXCOORD0_W = 16, + VIEW_X = 18, VIEW_Y = 19, VIEW_Z = 20, @@ -275,8 +276,11 @@ struct Regs { case TextureFormat::I8: case TextureFormat::A8: case TextureFormat::IA4: - default: // placeholder for yet unknown formats return 2; + + default: // placeholder for yet unknown formats + UNIMPLEMENTED(); + return 0; } } @@ -868,7 +872,7 @@ struct Regs { LightSrc light[8]; LightColor global_ambient; // Emission + (material.ambient * lighting.ambient) INSERT_PADDING_WORDS(0x1); - BitField<0, 3, u32> num_lights; // Number of enabled lights - 1 + BitField<0, 3, u32> max_light_index; // Number of enabled lights - 1 union { BitField<2, 2, LightingFresnelSelector> fresnel_selector; @@ -1045,7 +1049,7 @@ struct Regs { BitField<48, 12, u64> attribute_mask; // number of total attributes minus 1 - BitField<60, 4, u64> num_extra_attributes; + BitField<60, 4, u64> max_attribute_index; }; inline VertexAttributeFormat GetFormat(int n) const { @@ -1076,7 +1080,7 @@ struct Regs { } inline int GetNumTotalAttributes() const { - return (int)num_extra_attributes + 1; + return (int)max_attribute_index + 1; } // Attribute loaders map the source vertex data to input attributes @@ -1176,7 +1180,12 @@ struct Regs { } } command_buffer; - INSERT_PADDING_WORDS(0x07); + INSERT_PADDING_WORDS(4); + + /// Number of input attributes to the vertex shader minus 1 + BitField<0, 4, u32> max_input_attrib_index; + + INSERT_PADDING_WORDS(2); enum class GPUMode : u32 { Drawing = 0, @@ -1214,42 +1223,21 @@ struct Regs { union { // Number of input attributes to shader unit - 1 - BitField<0, 4, u32> num_input_attributes; + BitField<0, 4, u32> max_input_attribute_index; }; // Offset to shader program entry point (in words) BitField<0, 16, u32> main_offset; - union { - BitField<0, 4, u64> attribute0_register; - BitField<4, 4, u64> attribute1_register; - BitField<8, 4, u64> attribute2_register; - BitField<12, 4, u64> attribute3_register; - BitField<16, 4, u64> attribute4_register; - BitField<20, 4, u64> attribute5_register; - BitField<24, 4, u64> attribute6_register; - BitField<28, 4, u64> attribute7_register; - BitField<32, 4, u64> attribute8_register; - BitField<36, 4, u64> attribute9_register; - BitField<40, 4, u64> attribute10_register; - BitField<44, 4, u64> attribute11_register; - BitField<48, 4, u64> attribute12_register; - BitField<52, 4, u64> attribute13_register; - BitField<56, 4, u64> attribute14_register; - BitField<60, 4, u64> attribute15_register; - - int GetRegisterForAttribute(int attribute_index) const { - u64 fields[] = { - attribute0_register, attribute1_register, attribute2_register, - attribute3_register, attribute4_register, attribute5_register, - attribute6_register, attribute7_register, attribute8_register, - attribute9_register, attribute10_register, attribute11_register, - attribute12_register, attribute13_register, attribute14_register, - attribute15_register, - }; - return (int)fields[attribute_index]; - } - } input_register_map; + /// Maps input attributes to registers. 4-bits per attribute, specifying a register index + u32 input_attribute_to_register_map_low; + u32 input_attribute_to_register_map_high; + + unsigned int GetRegisterForAttribute(unsigned int attribute_index) const { + u64 map = ((u64)input_attribute_to_register_map_high << 32) | + (u64)input_attribute_to_register_map_low; + return (map >> (attribute_index * 4)) & 0b1111; + } BitField<0, 16, u32> output_mask; diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h index e4f2e6d5d..785d05650 100644 --- a/src/video_core/pica_state.h +++ b/src/video_core/pica_state.h @@ -23,7 +23,7 @@ struct State { Shader::ShaderSetup vs; Shader::ShaderSetup gs; - std::array<Math::Vec4<float24>, 16> vs_default_attributes; + Shader::AttributeBuffer input_default_attributes; struct { union LutEntry { @@ -66,7 +66,7 @@ struct State { /// Struct used to describe immediate mode rendering state struct ImmediateModeState { // Used to buffer partial vertices for immediate-mode rendering. - Shader::InputVertex input_vertex; + Shader::AttributeBuffer input_vertex; // Index of the next attribute to be loaded into `input_vertex`. u32 current_attribute = 0; } immediate; diff --git a/src/video_core/primitive_assembly.cpp b/src/video_core/primitive_assembly.cpp index be7377290..e71ff5719 100644 --- a/src/video_core/primitive_assembly.cpp +++ b/src/video_core/primitive_assembly.cpp @@ -14,7 +14,7 @@ PrimitiveAssembler<VertexType>::PrimitiveAssembler(Regs::TriangleTopology topolo : topology(topology), buffer_index(0) {} template <typename VertexType> -void PrimitiveAssembler<VertexType>::SubmitVertex(VertexType& vtx, +void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx, TriangleHandler triangle_handler) { switch (topology) { // TODO: Figure out what's different with TriangleTopology::Shader. diff --git a/src/video_core/primitive_assembly.h b/src/video_core/primitive_assembly.h index 0384d5984..24da47382 100644 --- a/src/video_core/primitive_assembly.h +++ b/src/video_core/primitive_assembly.h @@ -15,7 +15,8 @@ namespace Pica { */ template <typename VertexType> struct PrimitiveAssembler { - using TriangleHandler = std::function<void(VertexType& v0, VertexType& v1, VertexType& v2)>; + using TriangleHandler = + std::function<void(const VertexType& v0, const VertexType& v1, const VertexType& v2)>; PrimitiveAssembler(Regs::TriangleTopology topology = Regs::TriangleTopology::List); @@ -25,7 +26,7 @@ struct PrimitiveAssembler { * NOTE: We could specify the triangle handler in the constructor, but this way we can * keep event and handler code next to each other. */ - void SubmitVertex(VertexType& vtx, TriangleHandler triangle_handler); + void SubmitVertex(const VertexType& vtx, TriangleHandler triangle_handler); /** * Resets the internal state of the PrimitiveAssembler. diff --git a/src/video_core/rasterizer.cpp b/src/video_core/rasterizer.cpp index b9f5d4533..287d732b5 100644 --- a/src/video_core/rasterizer.cpp +++ b/src/video_core/rasterizer.cpp @@ -21,6 +21,7 @@ #include "video_core/pica_types.h" #include "video_core/rasterizer.h" #include "video_core/shader/shader.h" +#include "video_core/texture/texture_decode.h" #include "video_core/utils.h" namespace Pica { @@ -307,8 +308,8 @@ MICROPROFILE_DEFINE(GPU_Rasterization, "GPU", "Rasterization", MP_RGB(50, 50, 24 * Helper function for ProcessTriangle with the "reversed" flag to allow for implementing * culling via recursion. */ -static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader::OutputVertex& v1, - const Shader::OutputVertex& v2, bool reversed = false) { +static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Vertex& v2, + bool reversed = false) { const auto& regs = g_state.regs; MICROPROFILE_SCOPE(GPU_Rasterization); @@ -579,10 +580,10 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader u8* texture_data = Memory::GetPhysicalPointer(texture.config.GetPhysicalAddress()); auto info = - DebugUtils::TextureInfo::FromPicaRegister(texture.config, texture.format); + Texture::TextureInfo::FromPicaRegister(texture.config, texture.format); // TODO: Apply the min and mag filters to the texture - texture_color[i] = DebugUtils::LookupTexture(texture_data, s, t, info); + texture_color[i] = Texture::LookupTexture(texture_data, s, t, info); #if PICA_DUMP_TEXTURES DebugUtils::DumpTexture(texture.config, texture_data); #endif @@ -1276,8 +1277,7 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader } } -void ProcessTriangle(const Shader::OutputVertex& v0, const Shader::OutputVertex& v1, - const Shader::OutputVertex& v2) { +void ProcessTriangle(const Vertex& v0, const Vertex& v1, const Vertex& v2) { ProcessTriangleInternal(v0, v1, v2); } diff --git a/src/video_core/rasterizer.h b/src/video_core/rasterizer.h index 6cbda3067..3a72ac343 100644 --- a/src/video_core/rasterizer.h +++ b/src/video_core/rasterizer.h @@ -4,16 +4,44 @@ #pragma once -namespace Pica { +#include "video_core/shader/shader.h" -namespace Shader { -struct OutputVertex; -} +namespace Pica { namespace Rasterizer { -void ProcessTriangle(const Shader::OutputVertex& v0, const Shader::OutputVertex& v1, - const Shader::OutputVertex& v2); +struct Vertex : Shader::OutputVertex { + Vertex(const OutputVertex& v) : OutputVertex(v) {} + + // Attributes used to store intermediate results + // position after perspective divide + Math::Vec3<float24> screenpos; + + // Linear interpolation + // factor: 0=this, 1=vtx + void Lerp(float24 factor, const Vertex& vtx) { + pos = pos * factor + vtx.pos * (float24::FromFloat32(1) - factor); + + // 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); + + color = color * factor + vtx.color * (float24::FromFloat32(1) - factor); + } + + // Linear interpolation + // factor: 0=v0, 1=v1 + static Vertex Lerp(float24 factor, const Vertex& v0, const Vertex& v1) { + Vertex ret = v0; + ret.Lerp(factor, v1); + return ret; + } +}; + +void ProcessTriangle(const Vertex& v0, const Vertex& v1, const Vertex& v2); } // namespace Rasterizer diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 5a306a5c8..071e4ace0 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -467,7 +467,7 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { // Fragment lighting switches case PICA_REG_INDEX(lighting.disable): - case PICA_REG_INDEX(lighting.num_lights): + case PICA_REG_INDEX(lighting.max_light_index): case PICA_REG_INDEX(lighting.config0): case PICA_REG_INDEX(lighting.config1): case PICA_REG_INDEX(lighting.abs_lut_input): @@ -716,8 +716,6 @@ void RasterizerOpenGL::FlushAndInvalidateRegion(PAddr addr, u32 size) { bool RasterizerOpenGL::AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) { MICROPROFILE_SCOPE(OpenGL_Blits); - using PixelFormat = CachedSurface::PixelFormat; - using SurfaceType = CachedSurface::SurfaceType; CachedSurface src_params; src_params.addr = config.GetPhysicalInputAddress(); @@ -748,7 +746,8 @@ bool RasterizerOpenGL::AccelerateDisplayTransfer(const GPU::Regs::DisplayTransfe // Adjust the source rectangle to take into account parts of the input lines being cropped if (config.input_width > config.output_width) { - src_rect.right -= (config.input_width - config.output_width) * src_surface->res_scale_width; + src_rect.right -= static_cast<int>((config.input_width - config.output_width) * + src_surface->res_scale_width); } // Require destination surface to have same resolution scale as source to preserve scaling diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index e1a9cb361..a1aa07074 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -76,7 +76,7 @@ union PicaShaderConfig { } state.fog_mode = regs.fog_mode; - state.fog_flip = regs.fog_flip; + state.fog_flip = regs.fog_flip != 0; state.combiner_buffer_input = regs.tev_combiner_buffer_input.update_mask_rgb.Value() | regs.tev_combiner_buffer_input.update_mask_a.Value() << 4; @@ -84,7 +84,7 @@ union PicaShaderConfig { // Fragment lighting state.lighting.enable = !regs.lighting.disable; - state.lighting.src_num = regs.lighting.num_lights + 1; + state.lighting.src_num = regs.lighting.max_light_index + 1; for (unsigned light_index = 0; light_index < state.lighting.src_num; ++light_index) { unsigned num = regs.lighting.light_enable.GetNum(light_index); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 85aa06cd5..60380257a 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -17,10 +17,10 @@ #include "common/vector_math.h" #include "core/frontend/emu_window.h" #include "core/memory.h" -#include "video_core/debug_utils/debug_utils.h" #include "video_core/pica_state.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" #include "video_core/renderer_opengl/gl_state.h" +#include "video_core/texture/texture_decode.h" #include "video_core/utils.h" #include "video_core/video_core.h" @@ -172,7 +172,6 @@ bool RasterizerCacheOpenGL::TryBlitSurfaces(CachedSurface* src_surface, const MathUtil::Rectangle<int>& src_rect, CachedSurface* dst_surface, const MathUtil::Rectangle<int>& dst_rect) { - using SurfaceType = CachedSurface::SurfaceType; if (!CachedSurface::CheckFormatsBlittable(src_surface->pixel_format, dst_surface->pixel_format)) { @@ -340,17 +339,16 @@ CachedSurface* RasterizerCacheOpenGL::GetSurface(const CachedSurface& params, bo std::vector<Math::Vec4<u8>> tex_buffer(params.width * params.height); - Pica::DebugUtils::TextureInfo tex_info; + Pica::Texture::TextureInfo tex_info; tex_info.width = params.width; tex_info.height = params.height; - tex_info.stride = - params.width * CachedSurface::GetFormatBpp(params.pixel_format) / 8; tex_info.format = (Pica::Regs::TextureFormat)params.pixel_format; + tex_info.SetDefaultStride(); tex_info.physical_address = params.addr; for (unsigned y = 0; y < params.height; ++y) { for (unsigned x = 0; x < params.width; ++x) { - tex_buffer[x + params.width * y] = Pica::DebugUtils::LookupTexture( + tex_buffer[x + params.width * y] = Pica::Texture::LookupTexture( texture_src_data, x, params.height - 1 - y, tex_info); } } @@ -513,8 +511,9 @@ CachedSurface* RasterizerCacheOpenGL::GetSurfaceRect(const CachedSurface& params CachedSurface* RasterizerCacheOpenGL::GetTextureSurface( const Pica::Regs::FullTextureConfig& config) { - Pica::DebugUtils::TextureInfo info = - Pica::DebugUtils::TextureInfo::FromPicaRegister(config.config, config.format); + + Pica::Texture::TextureInfo info = + Pica::Texture::TextureInfo::FromPicaRegister(config.config, config.format); CachedSurface params; params.addr = info.physical_address; @@ -556,14 +555,21 @@ RasterizerCacheOpenGL::GetFramebufferSurfaces(const Pica::Regs::FramebufferConfi color_params.width = depth_params.width = config.GetWidth(); color_params.height = depth_params.height = config.GetHeight(); color_params.is_tiled = depth_params.is_tiled = true; - if (VideoCore::g_scaled_resolution_enabled) { - auto layout = VideoCore::g_emu_window->GetFramebufferLayout(); - // Assume same scaling factor for top and bottom screens + // Set the internal resolution, assume the same scaling factor for top and bottom screens + const Layout::FramebufferLayout& layout = VideoCore::g_emu_window->GetFramebufferLayout(); + if (Settings::values.resolution_factor == 0.0f) { + // Auto - scale resolution to the window size color_params.res_scale_width = depth_params.res_scale_width = (float)layout.top_screen.GetWidth() / VideoCore::kScreenTopWidth; color_params.res_scale_height = depth_params.res_scale_height = (float)layout.top_screen.GetHeight() / VideoCore::kScreenTopHeight; + } else { + // Otherwise, scale the resolution by the specified factor + color_params.res_scale_width = Settings::values.resolution_factor; + depth_params.res_scale_width = Settings::values.resolution_factor; + color_params.res_scale_height = Settings::values.resolution_factor; + depth_params.res_scale_height = Settings::values.resolution_factor; } color_params.addr = config.GetColorBufferPhysicalAddress(); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index b50e8292b..f57fdb3cc 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -8,7 +8,14 @@ #include <memory> #include <set> #include <tuple> +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-local-typedef" +#endif #include <boost/icl/interval_map.hpp> +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif #include <glad/glad.h> #include "common/assert.h" #include "common/common_funcs.h" diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 8f278722d..4c4f98ac9 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -293,7 +293,7 @@ static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) { case CompareFunc::GreaterThanOrEqual: { static const char* op[] = {"!=", "==", ">=", ">", "<=", "<"}; unsigned index = (unsigned)func - (unsigned)CompareFunc::Equal; - out += "int(last_tex_env_out.a * 255.0f) " + std::string(op[index]) + " alphatest_ref"; + out += "int(last_tex_env_out.a * 255.0) " + std::string(op[index]) + " alphatest_ref"; break; } @@ -422,16 +422,13 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { if (abs) { // LUT index is in the range of (0.0, 1.0) index = lighting.light[light_num].two_sided_diffuse ? "abs(" + index + ")" - : "max(" + index + ", 0.f)"; - return "(FLOAT_255 * clamp(" + index + ", 0.0, 1.0))"; + : "max(" + index + ", 0.0)"; } else { // LUT index is in the range of (-1.0, 1.0) - index = "clamp(" + index + ", -1.0, 1.0)"; - return "(FLOAT_255 * ((" + index + " < 0) ? " + index + " + 2.0 : " + index + - ") / 2.0)"; + index = "((" + index + " < 0) ? " + index + " + 2.0 : " + index + ") / 2.0"; } - return std::string(); + return "(OFFSET_256 + SCALE_256 * clamp(" + index + ", 0.0, 1.0))"; }; // Gets the lighting lookup table value given the specified sampler and index @@ -462,7 +459,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { if (light_config.dist_atten_enable) { std::string index = "(" + light_src + ".dist_atten_scale * length(-view - " + light_src + ".position) + " + light_src + ".dist_atten_bias)"; - index = "((clamp(" + index + ", 0.0, FLOAT_255)))"; + index = "(OFFSET_256 + SCALE_256 * clamp(" + index + ", 0.0, 1.0))"; const unsigned lut_num = ((unsigned)Regs::LightingSampler::DistanceAttenuation + light_config.num); dist_atten = GetLutValue((Regs::LightingSampler)lut_num, index); @@ -580,8 +577,10 @@ std::string GenerateFragmentShader(const PicaShaderConfig& config) { #version 330 core #define NUM_TEV_STAGES 6 #define NUM_LIGHTS 8 -#define LIGHTING_LUT_SIZE 256 -#define FLOAT_255 (255.0 / 256.0) + +// Texture coordinate offsets and scales +#define OFFSET_256 (0.5 / 256.0) +#define SCALE_256 (255.0 / 256.0) in vec4 primary_color; in vec2 texcoord[3]; diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp index a4aa3c9e0..f5f7ea61d 100644 --- a/src/video_core/shader/shader.cpp +++ b/src/video_core/shader/shader.cpp @@ -2,14 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <atomic> #include <cmath> #include <cstring> -#include <unordered_map> -#include <utility> -#include <boost/range/algorithm/fill.hpp> -#include "common/bit_field.h" -#include "common/hash.h" +#include "common/bit_set.h" #include "common/logging/log.h" #include "common/microprofile.h" #include "video_core/pica.h" @@ -25,37 +20,32 @@ namespace Pica { namespace Shader { -OutputVertex OutputRegisters::ToVertex(const Regs::ShaderConfig& config) const { +OutputVertex OutputVertex::FromAttributeBuffer(const Regs& regs, AttributeBuffer& input) { // Setup output data - OutputVertex ret; - // TODO(neobrain): Under some circumstances, up to 16 attributes may be output. We need to - // figure out what those circumstances are and enable the remaining outputs then. - unsigned index = 0; - for (unsigned i = 0; i < 7; ++i) { + union { + OutputVertex ret{}; + std::array<float24, 24> vertex_slots; + }; + static_assert(sizeof(vertex_slots) == sizeof(ret), "Struct and array have different sizes."); - if (index >= g_state.regs.vs_output_total) - break; + unsigned int num_attributes = regs.vs_output_total; + ASSERT(num_attributes <= 7); + for (unsigned int i = 0; i < num_attributes; ++i) { + const auto& output_register_map = regs.vs_output_attributes[i]; - if ((config.output_mask & (1 << i)) == 0) - continue; - - const auto& output_register_map = g_state.regs.vs_output_attributes[index]; - - u32 semantics[4] = {output_register_map.map_x, output_register_map.map_y, - output_register_map.map_z, output_register_map.map_w}; + Regs::VSOutputAttributes::Semantic semantics[4] = { + output_register_map.map_x, output_register_map.map_y, output_register_map.map_z, + output_register_map.map_w}; for (unsigned comp = 0; comp < 4; ++comp) { - float24* out = ((float24*)&ret) + semantics[comp]; - if (semantics[comp] != Regs::VSOutputAttributes::INVALID) { - *out = value[i][comp]; - } else { - // Zero output so that attributes which aren't output won't have denormals in them, - // which would slow us down later. - memset(out, 0, sizeof(*out)); + Regs::VSOutputAttributes::Semantic semantic = semantics[comp]; + float24* out = &vertex_slots[semantic]; + if (semantic < vertex_slots.size()) { + *out = input.attr[i][comp]; + } else if (semantic != Regs::VSOutputAttributes::INVALID) { + LOG_ERROR(HW_GPU, "Invalid/unknown semantic id: %u", (unsigned int)semantic); } } - - index++; } // The hardware takes the absolute and saturates vertex colors like this, *before* doing @@ -76,84 +66,47 @@ OutputVertex OutputRegisters::ToVertex(const Regs::ShaderConfig& config) const { return ret; } -#ifdef ARCHITECTURE_x86_64 -static std::unordered_map<u64, std::unique_ptr<JitShader>> shader_map; -static const JitShader* jit_shader; -#endif // ARCHITECTURE_x86_64 +void UnitState::LoadInput(const Regs::ShaderConfig& config, const AttributeBuffer& input) { + const unsigned max_attribute = config.max_input_attribute_index; -void ClearCache() { -#ifdef ARCHITECTURE_x86_64 - shader_map.clear(); -#endif // ARCHITECTURE_x86_64 + for (unsigned attr = 0; attr <= max_attribute; ++attr) { + unsigned reg = config.GetRegisterForAttribute(attr); + registers.input[reg] = input.attr[attr]; + } } -void ShaderSetup::Setup() { -#ifdef ARCHITECTURE_x86_64 - if (VideoCore::g_shader_jit_enabled) { - u64 cache_key = - Common::ComputeHash64(&g_state.vs.program_code, sizeof(g_state.vs.program_code)) ^ - Common::ComputeHash64(&g_state.vs.swizzle_data, sizeof(g_state.vs.swizzle_data)); - - auto iter = shader_map.find(cache_key); - if (iter != shader_map.end()) { - jit_shader = iter->second.get(); - } else { - auto shader = std::make_unique<JitShader>(); - shader->Compile(); - jit_shader = shader.get(); - shader_map[cache_key] = std::move(shader); - } +void UnitState::WriteOutput(const Regs::ShaderConfig& config, AttributeBuffer& output) { + unsigned int output_i = 0; + for (unsigned int reg : Common::BitSet<u32>(config.output_mask)) { + output.attr[output_i++] = registers.output[reg]; } -#endif // ARCHITECTURE_x86_64 } MICROPROFILE_DEFINE(GPU_Shader, "GPU", "Shader", MP_RGB(50, 50, 240)); -void ShaderSetup::Run(UnitState& state, const InputVertex& input, int num_attributes) { - auto& config = g_state.regs.vs; - auto& setup = g_state.vs; - - MICROPROFILE_SCOPE(GPU_Shader); - - // Setup input register table - const auto& attribute_register_map = config.input_register_map; - - for (unsigned i = 0; i < num_attributes; i++) - state.registers.input[attribute_register_map.GetRegisterForAttribute(i)] = input.attr[i]; - - state.conditional_code[0] = false; - state.conditional_code[1] = false; +#ifdef ARCHITECTURE_x86_64 +static std::unique_ptr<JitX64Engine> jit_engine; +#endif // ARCHITECTURE_x86_64 +static InterpreterEngine interpreter_engine; +ShaderEngine* GetEngine() { #ifdef ARCHITECTURE_x86_64 + // TODO(yuriks): Re-initialize on each change rather than being persistent if (VideoCore::g_shader_jit_enabled) { - jit_shader->Run(setup, state, config.main_offset); - } else { - DebugData<false> dummy_debug_data; - RunInterpreter(setup, state, dummy_debug_data, config.main_offset); + if (jit_engine == nullptr) { + jit_engine = std::make_unique<JitX64Engine>(); + } + return jit_engine.get(); } -#else - DebugData<false> dummy_debug_data; - RunInterpreter(setup, state, dummy_debug_data, config.main_offset); #endif // ARCHITECTURE_x86_64 -} - -DebugData<true> ShaderSetup::ProduceDebugInfo(const InputVertex& input, int num_attributes, - const Regs::ShaderConfig& config, - const ShaderSetup& setup) { - UnitState state; - DebugData<true> debug_data; - - // Setup input register table - boost::fill(state.registers.input, Math::Vec4<float24>::AssignToAll(float24::Zero())); - const auto& attribute_register_map = config.input_register_map; - for (unsigned i = 0; i < num_attributes; i++) - state.registers.input[attribute_register_map.GetRegisterForAttribute(i)] = input.attr[i]; - state.conditional_code[0] = false; - state.conditional_code[1] = false; + return &interpreter_engine; +} - RunInterpreter(setup, state, debug_data, config.main_offset); - return debug_data; +void Shutdown() { +#ifdef ARCHITECTURE_x86_64 + jit_engine = nullptr; +#endif // ARCHITECTURE_x86_64 } } // namespace Shader diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h index 2b07759b9..b188d3edf 100644 --- a/src/video_core/shader/shader.h +++ b/src/video_core/shader/shader.h @@ -6,7 +6,6 @@ #include <array> #include <cstddef> -#include <memory> #include <type_traits> #include <nihstro/shader_bytecode.h> #include "common/assert.h" @@ -15,7 +14,6 @@ #include "common/vector_math.h" #include "video_core/pica.h" #include "video_core/pica_types.h" -#include "video_core/shader/debug_data.h" using nihstro::RegisterType; using nihstro::SourceRegister; @@ -25,14 +23,11 @@ namespace Pica { namespace Shader { -struct InputVertex { +struct AttributeBuffer { alignas(16) Math::Vec4<float24> attr[16]; }; struct OutputVertex { - OutputVertex() = default; - - // VS output attributes Math::Vec4<float24> pos; Math::Vec4<float24> quat; Math::Vec4<float24> color; @@ -44,49 +39,22 @@ struct OutputVertex { INSERT_PADDING_WORDS(1); Math::Vec2<float24> tc2; - // Padding for optimal alignment - INSERT_PADDING_WORDS(4); - - // Attributes used to store intermediate results - - // position after perspective divide - Math::Vec3<float24> screenpos; - INSERT_PADDING_WORDS(1); - - // Linear interpolation - // factor: 0=this, 1=vtx - void Lerp(float24 factor, const OutputVertex& vtx) { - pos = pos * factor + vtx.pos * (float24::FromFloat32(1) - factor); - - // 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); - - color = color * factor + vtx.color * (float24::FromFloat32(1) - factor); - } - - // Linear interpolation - // factor: 0=v0, 1=v1 - static OutputVertex Lerp(float24 factor, const OutputVertex& v0, const OutputVertex& v1) { - OutputVertex ret = v0; - ret.Lerp(factor, v1); - return ret; - } + static OutputVertex FromAttributeBuffer(const Regs& regs, AttributeBuffer& output); }; +#define ASSERT_POS(var, pos) \ + static_assert(offsetof(OutputVertex, var) == pos * sizeof(float24), "Semantic at wrong " \ + "offset.") +ASSERT_POS(pos, Regs::VSOutputAttributes::POSITION_X); +ASSERT_POS(quat, Regs::VSOutputAttributes::QUATERNION_X); +ASSERT_POS(color, Regs::VSOutputAttributes::COLOR_R); +ASSERT_POS(tc0, Regs::VSOutputAttributes::TEXCOORD0_U); +ASSERT_POS(tc1, Regs::VSOutputAttributes::TEXCOORD1_U); +ASSERT_POS(tc0_w, Regs::VSOutputAttributes::TEXCOORD0_W); +ASSERT_POS(view, Regs::VSOutputAttributes::VIEW_X); +ASSERT_POS(tc2, Regs::VSOutputAttributes::TEXCOORD2_U); +#undef ASSERT_POS static_assert(std::is_pod<OutputVertex>::value, "Structure is not POD"); -static_assert(sizeof(OutputVertex) == 32 * sizeof(float), "OutputVertex has invalid size"); - -struct OutputRegisters { - OutputRegisters() = default; - - alignas(16) Math::Vec4<float24> value[16]; - - OutputVertex ToVertex(const Regs::ShaderConfig& config) const; -}; -static_assert(std::is_pod<OutputRegisters>::value, "Structure is not POD"); +static_assert(sizeof(OutputVertex) == 24 * sizeof(float), "OutputVertex has invalid size"); /** * This structure contains the state information that needs to be unique for a shader unit. The 3DS @@ -100,11 +68,10 @@ struct UnitState { // required to be 16-byte aligned. alignas(16) Math::Vec4<float24> input[16]; alignas(16) Math::Vec4<float24> temporary[16]; + alignas(16) Math::Vec4<float24> output[16]; } registers; static_assert(std::is_pod<Registers>::value, "Structure is not POD"); - OutputRegisters output_registers; - bool conditional_code[2]; // Two Address registers and one loop counter @@ -130,7 +97,7 @@ struct UnitState { static size_t OutputOffset(const DestRegister& reg) { switch (reg.GetRegisterType()) { case RegisterType::Output: - return offsetof(UnitState, output_registers.value) + + return offsetof(UnitState, registers.output) + reg.GetIndex() * sizeof(Math::Vec4<float24>); case RegisterType::Temporary: @@ -142,13 +109,19 @@ struct UnitState { return 0; } } -}; -/// Clears the shader cache -void ClearCache(); + /** + * Loads the unit state with an input vertex. + * + * @param config Shader configuration registers corresponding to the unit. + * @param input Attribute buffer to load into the input registers. + */ + void LoadInput(const Regs::ShaderConfig& config, const AttributeBuffer& input); -struct ShaderSetup { + void WriteOutput(const Regs::ShaderConfig& config, AttributeBuffer& output); +}; +struct ShaderSetup { struct { // The float uniforms are accessed by the shader JIT using SSE instructions, and are // therefore required to be 16-byte aligned. @@ -173,32 +146,37 @@ struct ShaderSetup { std::array<u32, 1024> program_code; std::array<u32, 1024> swizzle_data; + /// Data private to ShaderEngines + struct EngineData { + unsigned int entry_point; + /// Used by the JIT, points to a compiled shader object. + const void* cached_shader = nullptr; + } engine_data; +}; + +class ShaderEngine { +public: + virtual ~ShaderEngine() = default; + /** * Performs any shader unit setup that only needs to happen once per shader (as opposed to once * per vertex, which would happen within the `Run` function). */ - void Setup(); + virtual void SetupBatch(ShaderSetup& setup, unsigned int entry_point) = 0; /** - * Runs the currently setup shader - * @param state Shader unit state, must be setup per shader and per shader unit - * @param input Input vertex into the shader - * @param num_attributes The number of vertex shader attributes + * Runs the currently setup shader. + * + * @param setup Shader engine state, must be setup with SetupBatch on each shader change. + * @param state Shader unit state, must be setup with input data before each shader invocation. */ - void Run(UnitState& state, const InputVertex& input, int num_attributes); - - /** - * Produce debug information based on the given shader and input vertex - * @param input Input vertex into the shader - * @param num_attributes The number of vertex shader attributes - * @param config Configuration object for the shader pipeline - * @param setup Setup object for the shader pipeline - * @return Debug information for this shader with regards to the given vertex - */ - DebugData<true> ProduceDebugInfo(const InputVertex& input, int num_attributes, - const Regs::ShaderConfig& config, const ShaderSetup& setup); + virtual void Run(const ShaderSetup& setup, UnitState& state) const = 0; }; +// TODO(yuriks): Remove and make it non-global state somewhere +ShaderEngine* GetEngine(); +void Shutdown(); + } // namespace Shader } // namespace Pica diff --git a/src/video_core/shader/shader_interpreter.cpp b/src/video_core/shader/shader_interpreter.cpp index 70db4167e..81522b8f5 100644 --- a/src/video_core/shader/shader_interpreter.cpp +++ b/src/video_core/shader/shader_interpreter.cpp @@ -7,10 +7,12 @@ #include <cmath> #include <numeric> #include <boost/container/static_vector.hpp> +#include <boost/range/algorithm/fill.hpp> #include <nihstro/shader_bytecode.h> #include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" +#include "common/microprofile.h" #include "common/vector_math.h" #include "video_core/pica_state.h" #include "video_core/pica_types.h" @@ -27,8 +29,6 @@ namespace Pica { namespace Shader { -constexpr u32 INVALID_ADDRESS = 0xFFFFFFFF; - struct CallStackElement { u32 final_address; // Address upon which we jump to return_address u32 return_address; // Where to jump when leaving scope @@ -39,12 +39,15 @@ struct CallStackElement { }; template <bool Debug> -void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug>& debug_data, - unsigned offset) { +static void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug>& debug_data, + unsigned offset) { // TODO: Is there a maximal size for this? boost::container::static_vector<CallStackElement, 16> call_stack; u32 program_counter = offset; + state.conditional_code[0] = false; + state.conditional_code[1] = false; + auto call = [&program_counter, &call_stack](u32 offset, u32 num_instructions, u32 return_offset, u8 repeat_count, u8 loop_increment) { // -1 to make sure when incrementing the PC we end up at the correct offset @@ -75,9 +78,9 @@ void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug> } }; - const auto& uniforms = g_state.vs.uniforms; - const auto& swizzle_data = g_state.vs.swizzle_data; - const auto& program_code = g_state.vs.program_code; + const auto& uniforms = setup.uniforms; + const auto& swizzle_data = setup.swizzle_data; + const auto& program_code = setup.program_code; // Placeholder for invalid inputs static float24 dummy_vec4_float24[4]; @@ -172,7 +175,7 @@ void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug> float24* dest = (instr.common.dest.Value() < 0x10) - ? &state.output_registers.value[instr.common.dest.Value().GetIndex()][0] + ? &state.registers.output[instr.common.dest.Value().GetIndex()][0] : (instr.common.dest.Value() < 0x20) ? &state.registers.temporary[instr.common.dest.Value().GetIndex()][0] : dummy_vec4_float24; @@ -515,7 +518,7 @@ void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug> float24* dest = (instr.mad.dest.Value() < 0x10) - ? &state.output_registers.value[instr.mad.dest.Value().GetIndex()][0] + ? &state.registers.output[instr.mad.dest.Value().GetIndex()][0] : (instr.mad.dest.Value() < 0x20) ? &state.registers.temporary[instr.mad.dest.Value().GetIndex()][0] : dummy_vec4_float24; @@ -649,9 +652,33 @@ void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug> } } -// Explicit instantiation -template void RunInterpreter(const ShaderSetup&, UnitState&, DebugData<false>&, unsigned offset); -template void RunInterpreter(const ShaderSetup&, UnitState&, DebugData<true>&, unsigned offset); +void InterpreterEngine::SetupBatch(ShaderSetup& setup, unsigned int entry_point) { + ASSERT(entry_point < 1024); + setup.engine_data.entry_point = entry_point; +} + +MICROPROFILE_DECLARE(GPU_Shader); + +void InterpreterEngine::Run(const ShaderSetup& setup, UnitState& state) const { + + MICROPROFILE_SCOPE(GPU_Shader); + + DebugData<false> dummy_debug_data; + RunInterpreter(setup, state, dummy_debug_data, setup.engine_data.entry_point); +} + +DebugData<true> InterpreterEngine::ProduceDebugInfo(const ShaderSetup& setup, + const AttributeBuffer& input, + const Regs::ShaderConfig& config) const { + UnitState state; + DebugData<true> debug_data; + + // Setup input register table + boost::fill(state.registers.input, Math::Vec4<float24>::AssignToAll(float24::Zero())); + state.LoadInput(config, input); + RunInterpreter(setup, state, debug_data, setup.engine_data.entry_point); + return debug_data; +} } // namespace diff --git a/src/video_core/shader/shader_interpreter.h b/src/video_core/shader/shader_interpreter.h index d31dcd7a6..d7a61e122 100644 --- a/src/video_core/shader/shader_interpreter.h +++ b/src/video_core/shader/shader_interpreter.h @@ -4,18 +4,27 @@ #pragma once +#include "video_core/shader/debug_data.h" +#include "video_core/shader/shader.h" + namespace Pica { namespace Shader { -struct UnitState; - -template <bool Debug> -struct DebugData; - -template <bool Debug> -void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug>& debug_data, - unsigned offset); +class InterpreterEngine final : public ShaderEngine { +public: + void SetupBatch(ShaderSetup& setup, unsigned int entry_point) override; + void Run(const ShaderSetup& setup, UnitState& state) const override; + + /** + * Produce debug information based on the given shader and input vertex + * @param input Input vertex into the shader + * @param config Configuration object for the shader pipeline + * @return Debug information for this shader with regards to the given vertex + */ + DebugData<true> ProduceDebugInfo(const ShaderSetup& setup, const AttributeBuffer& input, + const Regs::ShaderConfig& config) const; +}; } // namespace diff --git a/src/video_core/shader/shader_jit_x64.cpp b/src/video_core/shader/shader_jit_x64.cpp index c588b778b..0ee0dd9ef 100644 --- a/src/video_core/shader/shader_jit_x64.cpp +++ b/src/video_core/shader/shader_jit_x64.cpp @@ -1,888 +1,48 @@ -// Copyright 2015 Citra Emulator Project +// Copyright 2016 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <algorithm> -#include <cmath> -#include <cstdint> -#include <nihstro/shader_bytecode.h> -#include <smmintrin.h> -#include <xmmintrin.h> -#include "common/assert.h" -#include "common/logging/log.h" -#include "common/vector_math.h" -#include "common/x64/cpu_detect.h" -#include "common/x64/xbyak_abi.h" -#include "common/x64/xbyak_util.h" -#include "video_core/pica_state.h" -#include "video_core/pica_types.h" +#include "common/hash.h" +#include "common/microprofile.h" #include "video_core/shader/shader.h" #include "video_core/shader/shader_jit_x64.h" - -using namespace Common::X64; -using namespace Xbyak::util; -using Xbyak::Label; -using Xbyak::Reg32; -using Xbyak::Reg64; -using Xbyak::Xmm; +#include "video_core/shader/shader_jit_x64_compiler.h" namespace Pica { - namespace Shader { -typedef void (JitShader::*JitFunction)(Instruction instr); - -const JitFunction instr_table[64] = { - &JitShader::Compile_ADD, // add - &JitShader::Compile_DP3, // dp3 - &JitShader::Compile_DP4, // dp4 - &JitShader::Compile_DPH, // dph - nullptr, // unknown - &JitShader::Compile_EX2, // ex2 - &JitShader::Compile_LG2, // lg2 - nullptr, // unknown - &JitShader::Compile_MUL, // mul - &JitShader::Compile_SGE, // sge - &JitShader::Compile_SLT, // slt - &JitShader::Compile_FLR, // flr - &JitShader::Compile_MAX, // max - &JitShader::Compile_MIN, // min - &JitShader::Compile_RCP, // rcp - &JitShader::Compile_RSQ, // rsq - nullptr, // unknown - nullptr, // unknown - &JitShader::Compile_MOVA, // mova - &JitShader::Compile_MOV, // mov - nullptr, // unknown - nullptr, // unknown - nullptr, // unknown - nullptr, // unknown - &JitShader::Compile_DPH, // dphi - nullptr, // unknown - &JitShader::Compile_SGE, // sgei - &JitShader::Compile_SLT, // slti - nullptr, // unknown - nullptr, // unknown - nullptr, // unknown - nullptr, // unknown - nullptr, // unknown - &JitShader::Compile_NOP, // nop - &JitShader::Compile_END, // end - nullptr, // break - &JitShader::Compile_CALL, // call - &JitShader::Compile_CALLC, // callc - &JitShader::Compile_CALLU, // callu - &JitShader::Compile_IF, // ifu - &JitShader::Compile_IF, // ifc - &JitShader::Compile_LOOP, // loop - nullptr, // emit - nullptr, // sete - &JitShader::Compile_JMP, // jmpc - &JitShader::Compile_JMP, // jmpu - &JitShader::Compile_CMP, // cmp - &JitShader::Compile_CMP, // cmp - &JitShader::Compile_MAD, // madi - &JitShader::Compile_MAD, // madi - &JitShader::Compile_MAD, // madi - &JitShader::Compile_MAD, // madi - &JitShader::Compile_MAD, // madi - &JitShader::Compile_MAD, // madi - &JitShader::Compile_MAD, // madi - &JitShader::Compile_MAD, // madi - &JitShader::Compile_MAD, // mad - &JitShader::Compile_MAD, // mad - &JitShader::Compile_MAD, // mad - &JitShader::Compile_MAD, // mad - &JitShader::Compile_MAD, // mad - &JitShader::Compile_MAD, // mad - &JitShader::Compile_MAD, // mad - &JitShader::Compile_MAD, // mad -}; - -// The following is used to alias some commonly used registers. Generally, RAX-RDX and XMM0-XMM3 can -// be used as scratch registers within a compiler function. The other registers have designated -// purposes, as documented below: +JitX64Engine::JitX64Engine() = default; +JitX64Engine::~JitX64Engine() = default; -/// Pointer to the uniform memory -static const Reg64 SETUP = r9; -/// The two 32-bit VS address offset registers set by the MOVA instruction -static const Reg64 ADDROFFS_REG_0 = r10; -static const Reg64 ADDROFFS_REG_1 = r11; -/// VS loop count register (Multiplied by 16) -static const Reg32 LOOPCOUNT_REG = r12d; -/// Current VS loop iteration number (we could probably use LOOPCOUNT_REG, but this quicker) -static const Reg32 LOOPCOUNT = esi; -/// Number to increment LOOPCOUNT_REG by on each loop iteration (Multiplied by 16) -static const Reg32 LOOPINC = edi; -/// Result of the previous CMP instruction for the X-component comparison -static const Reg64 COND0 = r13; -/// Result of the previous CMP instruction for the Y-component comparison -static const Reg64 COND1 = r14; -/// Pointer to the UnitState instance for the current VS unit -static const Reg64 STATE = r15; -/// SIMD scratch register -static const Xmm SCRATCH = xmm0; -/// Loaded with the first swizzled source register, otherwise can be used as a scratch register -static const Xmm SRC1 = xmm1; -/// Loaded with the second swizzled source register, otherwise can be used as a scratch register -static const Xmm SRC2 = xmm2; -/// Loaded with the third swizzled source register, otherwise can be used as a scratch register -static const Xmm SRC3 = xmm3; -/// Additional scratch register -static const Xmm SCRATCH2 = xmm4; -/// Constant vector of [1.0f, 1.0f, 1.0f, 1.0f], used to efficiently set a vector to one -static const Xmm ONE = xmm14; -/// Constant vector of [-0.f, -0.f, -0.f, -0.f], used to efficiently negate a vector with XOR -static const Xmm NEGBIT = xmm15; +void JitX64Engine::SetupBatch(ShaderSetup& setup, unsigned int entry_point) { + ASSERT(entry_point < 1024); + setup.engine_data.entry_point = entry_point; -// State registers that must not be modified by external functions calls -// Scratch registers, e.g., SRC1 and SCRATCH, have to be saved on the side if needed -static const BitSet32 persistent_regs = BuildRegSet({ - // Pointers to register blocks - SETUP, STATE, - // Cached registers - ADDROFFS_REG_0, ADDROFFS_REG_1, LOOPCOUNT_REG, COND0, COND1, - // Constants - ONE, NEGBIT, -}); + u64 code_hash = Common::ComputeHash64(&setup.program_code, sizeof(setup.program_code)); + u64 swizzle_hash = Common::ComputeHash64(&setup.swizzle_data, sizeof(setup.swizzle_data)); -/// Raw constant for the source register selector that indicates no swizzling is performed -static const u8 NO_SRC_REG_SWIZZLE = 0x1b; -/// Raw constant for the destination register enable mask that indicates all components are enabled -static const u8 NO_DEST_REG_MASK = 0xf; - -/** - * Get the vertex shader instruction for a given offset in the current shader program - * @param offset Offset in the current shader program of the instruction - * @return Instruction at the specified offset - */ -static Instruction GetVertexShaderInstruction(size_t offset) { - return {g_state.vs.program_code[offset]}; -} - -static void LogCritical(const char* msg) { - LOG_CRITICAL(HW_GPU, "%s", msg); -} - -void JitShader::Compile_Assert(bool condition, const char* msg) { - if (!condition) { - mov(ABI_PARAM1, reinterpret_cast<size_t>(msg)); - CallFarFunction(*this, LogCritical); - } -} - -/** - * Loads and swizzles a source register into the specified XMM register. - * @param instr VS instruction, used for determining how to load the source register - * @param src_num Number indicating which source register to load (1 = src1, 2 = src2, 3 = src3) - * @param src_reg SourceRegister object corresponding to the source register to load - * @param dest Destination XMM register to store the loaded, swizzled source register - */ -void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, - Xmm dest) { - Reg64 src_ptr; - size_t src_offset; - - if (src_reg.GetRegisterType() == RegisterType::FloatUniform) { - src_ptr = SETUP; - src_offset = ShaderSetup::GetFloatUniformOffset(src_reg.GetIndex()); + u64 cache_key = code_hash ^ swizzle_hash; + auto iter = cache.find(cache_key); + if (iter != cache.end()) { + setup.engine_data.cached_shader = iter->second.get(); } else { - src_ptr = STATE; - src_offset = UnitState::InputOffset(src_reg); - } - - int src_offset_disp = (int)src_offset; - ASSERT_MSG(src_offset == src_offset_disp, "Source register offset too large for int type"); - - unsigned operand_desc_id; - - const bool is_inverted = - (0 != (instr.opcode.Value().GetInfo().subtype & OpCode::Info::SrcInversed)); - - unsigned address_register_index; - unsigned offset_src; - - if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD || - instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) { - operand_desc_id = instr.mad.operand_desc_id; - offset_src = is_inverted ? 3 : 2; - address_register_index = instr.mad.address_register_index; - } else { - operand_desc_id = instr.common.operand_desc_id; - offset_src = is_inverted ? 2 : 1; - address_register_index = instr.common.address_register_index; - } - - if (src_num == offset_src && address_register_index != 0) { - switch (address_register_index) { - case 1: // address offset 1 - movaps(dest, xword[src_ptr + ADDROFFS_REG_0 + src_offset_disp]); - break; - case 2: // address offset 2 - movaps(dest, xword[src_ptr + ADDROFFS_REG_1 + src_offset_disp]); - break; - case 3: // address offset 3 - movaps(dest, xword[src_ptr + LOOPCOUNT_REG.cvt64() + src_offset_disp]); - break; - default: - UNREACHABLE(); - break; - } - } else { - // Load the source - movaps(dest, xword[src_ptr + src_offset_disp]); - } - - SwizzlePattern swiz = {g_state.vs.swizzle_data[operand_desc_id]}; - - // Generate instructions for source register swizzling as needed - u8 sel = swiz.GetRawSelector(src_num); - if (sel != NO_SRC_REG_SWIZZLE) { - // Selector component order needs to be reversed for the SHUFPS instruction - sel = ((sel & 0xc0) >> 6) | ((sel & 3) << 6) | ((sel & 0xc) << 2) | ((sel & 0x30) >> 2); - - // Shuffle inputs for swizzle - shufps(dest, dest, sel); - } - - // If the source register should be negated, flip the negative bit using XOR - const bool negate[] = {swiz.negate_src1, swiz.negate_src2, swiz.negate_src3}; - if (negate[src_num - 1]) { - xorps(dest, NEGBIT); + auto shader = std::make_unique<JitShader>(); + shader->Compile(&setup.program_code, &setup.swizzle_data); + setup.engine_data.cached_shader = shader.get(); + cache.emplace_hint(iter, cache_key, std::move(shader)); } } -void JitShader::Compile_DestEnable(Instruction instr, Xmm src) { - DestRegister dest; - unsigned operand_desc_id; - if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD || - instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) { - operand_desc_id = instr.mad.operand_desc_id; - dest = instr.mad.dest.Value(); - } else { - operand_desc_id = instr.common.operand_desc_id; - dest = instr.common.dest.Value(); - } - - SwizzlePattern swiz = {g_state.vs.swizzle_data[operand_desc_id]}; - - size_t dest_offset_disp = UnitState::OutputOffset(dest); - - // If all components are enabled, write the result to the destination register - if (swiz.dest_mask == NO_DEST_REG_MASK) { - // Store dest back to memory - movaps(xword[STATE + dest_offset_disp], src); - - } else { - // Not all components are enabled, so mask the result when storing to the destination - // register... - movaps(SCRATCH, xword[STATE + dest_offset_disp]); - - if (Common::GetCPUCaps().sse4_1) { - u8 mask = ((swiz.dest_mask & 1) << 3) | ((swiz.dest_mask & 8) >> 3) | - ((swiz.dest_mask & 2) << 1) | ((swiz.dest_mask & 4) >> 1); - blendps(SCRATCH, src, mask); - } else { - movaps(SCRATCH2, src); - unpckhps(SCRATCH2, SCRATCH); // Unpack X/Y components of source and destination - unpcklps(SCRATCH, src); // Unpack Z/W components of source and destination - - // Compute selector to selectively copy source components to destination for SHUFPS - // instruction - u8 sel = ((swiz.DestComponentEnabled(0) ? 1 : 0) << 0) | - ((swiz.DestComponentEnabled(1) ? 3 : 2) << 2) | - ((swiz.DestComponentEnabled(2) ? 0 : 1) << 4) | - ((swiz.DestComponentEnabled(3) ? 2 : 3) << 6); - shufps(SCRATCH, SCRATCH2, sel); - } - - // Store dest back to memory - movaps(xword[STATE + dest_offset_disp], SCRATCH); - } -} - -void JitShader::Compile_SanitizedMul(Xmm src1, Xmm src2, Xmm scratch) { - movaps(scratch, src1); - cmpordps(scratch, src2); - - mulps(src1, src2); +MICROPROFILE_DECLARE(GPU_Shader); - movaps(src2, src1); - cmpunordps(src2, src2); +void JitX64Engine::Run(const ShaderSetup& setup, UnitState& state) const { + ASSERT(setup.engine_data.cached_shader != nullptr); - xorps(scratch, src2); - andps(src1, scratch); -} - -void JitShader::Compile_EvaluateCondition(Instruction instr) { - // Note: NXOR is used below to check for equality - switch (instr.flow_control.op) { - case Instruction::FlowControlType::Or: - mov(eax, COND0); - mov(ebx, COND1); - xor(eax, (instr.flow_control.refx.Value() ^ 1)); - xor(ebx, (instr.flow_control.refy.Value() ^ 1)); - or (eax, ebx); - break; - - case Instruction::FlowControlType::And: - mov(eax, COND0); - mov(ebx, COND1); - xor(eax, (instr.flow_control.refx.Value() ^ 1)); - xor(ebx, (instr.flow_control.refy.Value() ^ 1)); - and(eax, ebx); - break; - - case Instruction::FlowControlType::JustX: - mov(eax, COND0); - xor(eax, (instr.flow_control.refx.Value() ^ 1)); - break; - - case Instruction::FlowControlType::JustY: - mov(eax, COND1); - xor(eax, (instr.flow_control.refy.Value() ^ 1)); - break; - } -} + MICROPROFILE_SCOPE(GPU_Shader); -void JitShader::Compile_UniformCondition(Instruction instr) { - size_t offset = ShaderSetup::GetBoolUniformOffset(instr.flow_control.bool_uniform_id); - cmp(byte[SETUP + offset], 0); + const JitShader* shader = static_cast<const JitShader*>(setup.engine_data.cached_shader); + shader->Run(setup, state, setup.engine_data.entry_point); } -BitSet32 JitShader::PersistentCallerSavedRegs() { - return persistent_regs & ABI_ALL_CALLER_SAVED; -} - -void JitShader::Compile_ADD(Instruction instr) { - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); - addps(SRC1, SRC2); - Compile_DestEnable(instr, SRC1); -} - -void JitShader::Compile_DP3(Instruction instr) { - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); - - Compile_SanitizedMul(SRC1, SRC2, SCRATCH); - - movaps(SRC2, SRC1); - shufps(SRC2, SRC2, _MM_SHUFFLE(1, 1, 1, 1)); - - movaps(SRC3, SRC1); - shufps(SRC3, SRC3, _MM_SHUFFLE(2, 2, 2, 2)); - - shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); - addps(SRC1, SRC2); - addps(SRC1, SRC3); - - Compile_DestEnable(instr, SRC1); -} - -void JitShader::Compile_DP4(Instruction instr) { - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); - - Compile_SanitizedMul(SRC1, SRC2, SCRATCH); - - movaps(SRC2, SRC1); - shufps(SRC1, SRC1, _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY - addps(SRC1, SRC2); - - movaps(SRC2, SRC1); - shufps(SRC1, SRC1, _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX - addps(SRC1, SRC2); - - Compile_DestEnable(instr, SRC1); -} - -void JitShader::Compile_DPH(Instruction instr) { - if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::DPHI) { - Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1); - Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2); - } else { - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); - } - - if (Common::GetCPUCaps().sse4_1) { - // Set 4th component to 1.0 - blendps(SRC1, ONE, 0b1000); - } else { - // Set 4th component to 1.0 - movaps(SCRATCH, SRC1); - unpckhps(SCRATCH, ONE); // XYZW, 1111 -> Z1__ - unpcklpd(SRC1, SCRATCH); // XYZW, Z1__ -> XYZ1 - } - - Compile_SanitizedMul(SRC1, SRC2, SCRATCH); - - movaps(SRC2, SRC1); - shufps(SRC1, SRC1, _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY - addps(SRC1, SRC2); - - movaps(SRC2, SRC1); - shufps(SRC1, SRC1, _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX - addps(SRC1, SRC2); - - Compile_DestEnable(instr, SRC1); -} - -void JitShader::Compile_EX2(Instruction instr) { - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - movss(xmm0, SRC1); // ABI_PARAM1 - - ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); - CallFarFunction(*this, exp2f); - ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); - - shufps(xmm0, xmm0, _MM_SHUFFLE(0, 0, 0, 0)); // ABI_RETURN - movaps(SRC1, xmm0); - Compile_DestEnable(instr, SRC1); -} - -void JitShader::Compile_LG2(Instruction instr) { - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - movss(xmm0, SRC1); // ABI_PARAM1 - - ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); - CallFarFunction(*this, log2f); - ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); - - shufps(xmm0, xmm0, _MM_SHUFFLE(0, 0, 0, 0)); // ABI_RETURN - movaps(SRC1, xmm0); - Compile_DestEnable(instr, SRC1); -} - -void JitShader::Compile_MUL(Instruction instr) { - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); - Compile_SanitizedMul(SRC1, SRC2, SCRATCH); - Compile_DestEnable(instr, SRC1); -} - -void JitShader::Compile_SGE(Instruction instr) { - if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::SGEI) { - Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1); - Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2); - } else { - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); - } - - cmpleps(SRC2, SRC1); - andps(SRC2, ONE); - - Compile_DestEnable(instr, SRC2); -} - -void JitShader::Compile_SLT(Instruction instr) { - if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::SLTI) { - Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1); - Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2); - } else { - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); - } - - cmpltps(SRC1, SRC2); - andps(SRC1, ONE); - - Compile_DestEnable(instr, SRC1); -} - -void JitShader::Compile_FLR(Instruction instr) { - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - - if (Common::GetCPUCaps().sse4_1) { - roundps(SRC1, SRC1, _MM_FROUND_FLOOR); - } else { - cvttps2dq(SRC1, SRC1); - cvtdq2ps(SRC1, SRC1); - } - - Compile_DestEnable(instr, SRC1); -} - -void JitShader::Compile_MAX(Instruction instr) { - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); - // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned. - maxps(SRC1, SRC2); - Compile_DestEnable(instr, SRC1); -} - -void JitShader::Compile_MIN(Instruction instr) { - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); - // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned. - minps(SRC1, SRC2); - Compile_DestEnable(instr, SRC1); -} - -void JitShader::Compile_MOVA(Instruction instr) { - SwizzlePattern swiz = {g_state.vs.swizzle_data[instr.common.operand_desc_id]}; - - if (!swiz.DestComponentEnabled(0) && !swiz.DestComponentEnabled(1)) { - return; // NoOp - } - - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - - // Convert floats to integers using truncation (only care about X and Y components) - cvttps2dq(SRC1, SRC1); - - // Get result - movq(rax, SRC1); - - // Handle destination enable - if (swiz.DestComponentEnabled(0) && swiz.DestComponentEnabled(1)) { - // Move and sign-extend low 32 bits - movsxd(ADDROFFS_REG_0, eax); - - // Move and sign-extend high 32 bits - shr(rax, 32); - movsxd(ADDROFFS_REG_1, eax); - - // Multiply by 16 to be used as an offset later - shl(ADDROFFS_REG_0, 4); - shl(ADDROFFS_REG_1, 4); - } else { - if (swiz.DestComponentEnabled(0)) { - // Move and sign-extend low 32 bits - movsxd(ADDROFFS_REG_0, eax); - - // Multiply by 16 to be used as an offset later - shl(ADDROFFS_REG_0, 4); - } else if (swiz.DestComponentEnabled(1)) { - // Move and sign-extend high 32 bits - shr(rax, 32); - movsxd(ADDROFFS_REG_1, eax); - - // Multiply by 16 to be used as an offset later - shl(ADDROFFS_REG_1, 4); - } - } -} - -void JitShader::Compile_MOV(Instruction instr) { - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - Compile_DestEnable(instr, SRC1); -} - -void JitShader::Compile_RCP(Instruction instr) { - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - - // TODO(bunnei): RCPSS is a pretty rough approximation, this might cause problems if Pica - // performs this operation more accurately. This should be checked on hardware. - rcpss(SRC1, SRC1); - shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX - - Compile_DestEnable(instr, SRC1); -} - -void JitShader::Compile_RSQ(Instruction instr) { - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - - // TODO(bunnei): RSQRTSS is a pretty rough approximation, this might cause problems if Pica - // performs this operation more accurately. This should be checked on hardware. - rsqrtss(SRC1, SRC1); - shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX - - Compile_DestEnable(instr, SRC1); -} - -void JitShader::Compile_NOP(Instruction instr) {} - -void JitShader::Compile_END(Instruction instr) { - ABI_PopRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8); - ret(); -} - -void JitShader::Compile_CALL(Instruction instr) { - // Push offset of the return - push(qword, (instr.flow_control.dest_offset + instr.flow_control.num_instructions)); - - // Call the subroutine - call(instruction_labels[instr.flow_control.dest_offset]); - - // Skip over the return offset that's on the stack - add(rsp, 8); -} - -void JitShader::Compile_CALLC(Instruction instr) { - Compile_EvaluateCondition(instr); - Label b; - jz(b); - Compile_CALL(instr); - L(b); -} - -void JitShader::Compile_CALLU(Instruction instr) { - Compile_UniformCondition(instr); - Label b; - jz(b); - Compile_CALL(instr); - L(b); -} - -void JitShader::Compile_CMP(Instruction instr) { - using Op = Instruction::Common::CompareOpType::Op; - Op op_x = instr.common.compare_op.x; - Op op_y = instr.common.compare_op.y; - - Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); - - // SSE doesn't have greater-than (GT) or greater-equal (GE) comparison operators. You need to - // emulate them by swapping the lhs and rhs and using LT and LE. NLT and NLE can't be used here - // because they don't match when used with NaNs. - static const u8 cmp[] = {CMP_EQ, CMP_NEQ, CMP_LT, CMP_LE, CMP_LT, CMP_LE}; - - bool invert_op_x = (op_x == Op::GreaterThan || op_x == Op::GreaterEqual); - Xmm lhs_x = invert_op_x ? SRC2 : SRC1; - Xmm rhs_x = invert_op_x ? SRC1 : SRC2; - - if (op_x == op_y) { - // Compare X-component and Y-component together - cmpps(lhs_x, rhs_x, cmp[op_x]); - movq(COND0, lhs_x); - - mov(COND1, COND0); - } else { - bool invert_op_y = (op_y == Op::GreaterThan || op_y == Op::GreaterEqual); - Xmm lhs_y = invert_op_y ? SRC2 : SRC1; - Xmm rhs_y = invert_op_y ? SRC1 : SRC2; - - // Compare X-component - movaps(SCRATCH, lhs_x); - cmpss(SCRATCH, rhs_x, cmp[op_x]); - - // Compare Y-component - cmpps(lhs_y, rhs_y, cmp[op_y]); - - movq(COND0, SCRATCH); - movq(COND1, lhs_y); - } - - shr(COND0.cvt32(), 31); // ignores upper 32 bits in source - shr(COND1, 63); -} - -void JitShader::Compile_MAD(Instruction instr) { - Compile_SwizzleSrc(instr, 1, instr.mad.src1, SRC1); - - if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) { - Compile_SwizzleSrc(instr, 2, instr.mad.src2i, SRC2); - Compile_SwizzleSrc(instr, 3, instr.mad.src3i, SRC3); - } else { - Compile_SwizzleSrc(instr, 2, instr.mad.src2, SRC2); - Compile_SwizzleSrc(instr, 3, instr.mad.src3, SRC3); - } - - Compile_SanitizedMul(SRC1, SRC2, SCRATCH); - addps(SRC1, SRC3); - - Compile_DestEnable(instr, SRC1); -} - -void JitShader::Compile_IF(Instruction instr) { - Compile_Assert(instr.flow_control.dest_offset >= program_counter, - "Backwards if-statements not supported"); - Label l_else, l_endif; - - // Evaluate the "IF" condition - if (instr.opcode.Value() == OpCode::Id::IFU) { - Compile_UniformCondition(instr); - } else if (instr.opcode.Value() == OpCode::Id::IFC) { - Compile_EvaluateCondition(instr); - } - jz(l_else, T_NEAR); - - // Compile the code that corresponds to the condition evaluating as true - Compile_Block(instr.flow_control.dest_offset); - - // If there isn't an "ELSE" condition, we are done here - if (instr.flow_control.num_instructions == 0) { - L(l_else); - return; - } - - jmp(l_endif, T_NEAR); - - L(l_else); - // This code corresponds to the "ELSE" condition - // Comple the code that corresponds to the condition evaluating as false - Compile_Block(instr.flow_control.dest_offset + instr.flow_control.num_instructions); - - L(l_endif); -} - -void JitShader::Compile_LOOP(Instruction instr) { - Compile_Assert(instr.flow_control.dest_offset >= program_counter, - "Backwards loops not supported"); - Compile_Assert(!looping, "Nested loops not supported"); - - looping = true; - - // This decodes the fields from the integer uniform at index instr.flow_control.int_uniform_id. - // The Y (LOOPCOUNT_REG) and Z (LOOPINC) component are kept multiplied by 16 (Left shifted by - // 4 bits) to be used as an offset into the 16-byte vector registers later - size_t offset = ShaderSetup::GetIntUniformOffset(instr.flow_control.int_uniform_id); - mov(LOOPCOUNT, dword[SETUP + offset]); - mov(LOOPCOUNT_REG, LOOPCOUNT); - shr(LOOPCOUNT_REG, 4); - and(LOOPCOUNT_REG, 0xFF0); // Y-component is the start - mov(LOOPINC, LOOPCOUNT); - shr(LOOPINC, 12); - and(LOOPINC, 0xFF0); // Z-component is the incrementer - movzx(LOOPCOUNT, LOOPCOUNT.cvt8()); // X-component is iteration count - add(LOOPCOUNT, 1); // Iteration count is X-component + 1 - - Label l_loop_start; - L(l_loop_start); - - Compile_Block(instr.flow_control.dest_offset + 1); - - add(LOOPCOUNT_REG, LOOPINC); // Increment LOOPCOUNT_REG by Z-component - sub(LOOPCOUNT, 1); // Increment loop count by 1 - jnz(l_loop_start); // Loop if not equal - - looping = false; -} - -void JitShader::Compile_JMP(Instruction instr) { - if (instr.opcode.Value() == OpCode::Id::JMPC) - Compile_EvaluateCondition(instr); - else if (instr.opcode.Value() == OpCode::Id::JMPU) - Compile_UniformCondition(instr); - else - UNREACHABLE(); - - bool inverted_condition = - (instr.opcode.Value() == OpCode::Id::JMPU) && (instr.flow_control.num_instructions & 1); - - Label& b = instruction_labels[instr.flow_control.dest_offset]; - if (inverted_condition) { - jz(b, T_NEAR); - } else { - jnz(b, T_NEAR); - } -} - -void JitShader::Compile_Block(unsigned end) { - while (program_counter < end) { - Compile_NextInstr(); - } -} - -void JitShader::Compile_Return() { - // Peek return offset on the stack and check if we're at that offset - mov(rax, qword[rsp + 8]); - cmp(eax, (program_counter)); - - // If so, jump back to before CALL - Label b; - jnz(b); - ret(); - L(b); -} - -void JitShader::Compile_NextInstr() { - if (std::binary_search(return_offsets.begin(), return_offsets.end(), program_counter)) { - Compile_Return(); - } - - L(instruction_labels[program_counter]); - - Instruction instr = GetVertexShaderInstruction(program_counter++); - - OpCode::Id opcode = instr.opcode.Value(); - auto instr_func = instr_table[static_cast<unsigned>(opcode)]; - - if (instr_func) { - // JIT the instruction! - ((*this).*instr_func)(instr); - } else { - // Unhandled instruction - LOG_CRITICAL(HW_GPU, "Unhandled instruction: 0x%02x (0x%08x)", - instr.opcode.Value().EffectiveOpCode(), instr.hex); - } -} - -void JitShader::FindReturnOffsets() { - return_offsets.clear(); - - for (size_t offset = 0; offset < g_state.vs.program_code.size(); ++offset) { - Instruction instr = GetVertexShaderInstruction(offset); - - switch (instr.opcode.Value()) { - case OpCode::Id::CALL: - case OpCode::Id::CALLC: - case OpCode::Id::CALLU: - return_offsets.push_back(instr.flow_control.dest_offset + - instr.flow_control.num_instructions); - break; - default: - break; - } - } - - // Sort for efficient binary search later - std::sort(return_offsets.begin(), return_offsets.end()); -} - -void JitShader::Compile() { - // Reset flow control state - program = (CompiledShader*)getCurr(); - program_counter = 0; - looping = false; - instruction_labels.fill(Xbyak::Label()); - - // Find all `CALL` instructions and identify return locations - FindReturnOffsets(); - - // The stack pointer is 8 modulo 16 at the entry of a procedure - ABI_PushRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8); - - mov(SETUP, ABI_PARAM1); - mov(STATE, ABI_PARAM2); - - // Zero address/loop registers - xor(ADDROFFS_REG_0.cvt32(), ADDROFFS_REG_0.cvt32()); - xor(ADDROFFS_REG_1.cvt32(), ADDROFFS_REG_1.cvt32()); - xor(LOOPCOUNT_REG, LOOPCOUNT_REG); - - // Used to set a register to one - static const __m128 one = {1.f, 1.f, 1.f, 1.f}; - mov(rax, reinterpret_cast<size_t>(&one)); - movaps(ONE, xword[rax]); - - // Used to negate registers - static const __m128 neg = {-0.f, -0.f, -0.f, -0.f}; - mov(rax, reinterpret_cast<size_t>(&neg)); - movaps(NEGBIT, xword[rax]); - - // Jump to start of the shader program - jmp(ABI_PARAM3); - - // Compile entire program - Compile_Block(static_cast<unsigned>(g_state.vs.program_code.size())); - - // Free memory that's no longer needed - return_offsets.clear(); - return_offsets.shrink_to_fit(); - - ready(); - - uintptr_t size = reinterpret_cast<uintptr_t>(getCurr()) - reinterpret_cast<uintptr_t>(program); - ASSERT_MSG(size <= MAX_SHADER_SIZE, "Compiled a shader that exceeds the allocated size!"); - LOG_DEBUG(HW_GPU, "Compiled shader size=%lu", size); -} - -JitShader::JitShader() : Xbyak::CodeGenerator(MAX_SHADER_SIZE) {} - } // namespace Shader - } // namespace Pica diff --git a/src/video_core/shader/shader_jit_x64.h b/src/video_core/shader/shader_jit_x64.h index f37548306..078b2cba5 100644 --- a/src/video_core/shader/shader_jit_x64.h +++ b/src/video_core/shader/shader_jit_x64.h @@ -1,121 +1,30 @@ -// Copyright 2015 Citra Emulator Project +// Copyright 2016 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once -#include <array> -#include <cstddef> -#include <utility> -#include <vector> -#include <nihstro/shader_bytecode.h> -#include <xbyak.h> -#include "common/bit_set.h" +#include <memory> +#include <unordered_map> #include "common/common_types.h" -#include "common/x64/emitter.h" #include "video_core/shader/shader.h" -using nihstro::Instruction; -using nihstro::OpCode; -using nihstro::SwizzlePattern; - namespace Pica { - namespace Shader { -/// Memory allocated for each compiled shader (64Kb) -constexpr size_t MAX_SHADER_SIZE = 1024 * 64; +class JitShader; -/** - * This class implements the shader JIT compiler. It recompiles a Pica shader program into x86_64 - * code that can be executed on the host machine directly. - */ -class JitShader : public Xbyak::CodeGenerator { +class JitX64Engine final : public ShaderEngine { public: - JitShader(); - - void Run(const ShaderSetup& setup, UnitState& state, unsigned offset) const { - program(&setup, &state, instruction_labels[offset].getAddress()); - } - - void Compile(); + JitX64Engine(); + ~JitX64Engine() override; - void Compile_ADD(Instruction instr); - void Compile_DP3(Instruction instr); - void Compile_DP4(Instruction instr); - void Compile_DPH(Instruction instr); - void Compile_EX2(Instruction instr); - void Compile_LG2(Instruction instr); - void Compile_MUL(Instruction instr); - void Compile_SGE(Instruction instr); - void Compile_SLT(Instruction instr); - void Compile_FLR(Instruction instr); - void Compile_MAX(Instruction instr); - void Compile_MIN(Instruction instr); - void Compile_RCP(Instruction instr); - void Compile_RSQ(Instruction instr); - void Compile_MOVA(Instruction instr); - void Compile_MOV(Instruction instr); - void Compile_NOP(Instruction instr); - void Compile_END(Instruction instr); - void Compile_CALL(Instruction instr); - void Compile_CALLC(Instruction instr); - void Compile_CALLU(Instruction instr); - void Compile_IF(Instruction instr); - void Compile_LOOP(Instruction instr); - void Compile_JMP(Instruction instr); - void Compile_CMP(Instruction instr); - void Compile_MAD(Instruction instr); + void SetupBatch(ShaderSetup& setup, unsigned int entry_point) override; + void Run(const ShaderSetup& setup, UnitState& state) const override; private: - void Compile_Block(unsigned end); - void Compile_NextInstr(); - - void Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, - Xbyak::Xmm dest); - void Compile_DestEnable(Instruction instr, Xbyak::Xmm dest); - - /** - * Compiles a `MUL src1, src2` operation, properly handling the PICA semantics when multiplying - * zero by inf. Clobbers `src2` and `scratch`. - */ - void Compile_SanitizedMul(Xbyak::Xmm src1, Xbyak::Xmm src2, Xbyak::Xmm scratch); - - void Compile_EvaluateCondition(Instruction instr); - void Compile_UniformCondition(Instruction instr); - - /** - * Emits the code to conditionally return from a subroutine envoked by the `CALL` instruction. - */ - void Compile_Return(); - - BitSet32 PersistentCallerSavedRegs(); - - /** - * Assertion evaluated at compile-time, but only triggered if executed at runtime. - * @param msg Message to be logged if the assertion fails. - */ - void Compile_Assert(bool condition, const char* msg); - - /** - * Analyzes the entire shader program for `CALL` instructions before emitting any code, - * identifying the locations where a return needs to be inserted. - */ - void FindReturnOffsets(); - - /// Mapping of Pica VS instructions to pointers in the emitted code - std::array<Xbyak::Label, 1024> instruction_labels; - - /// Offsets in code where a return needs to be inserted - std::vector<unsigned> return_offsets; - - unsigned program_counter = 0; ///< Offset of the next instruction to decode - bool looping = false; ///< True if compiling a loop, used to check for nested loops - - using CompiledShader = void(const void* setup, void* state, const u8* start_addr); - CompiledShader* program = nullptr; + std::unordered_map<u64, std::unique_ptr<JitShader>> cache; }; -} // Shader - -} // Pica +} // namespace Shader +} // namespace Pica diff --git a/src/video_core/shader/shader_jit_x64_compiler.cpp b/src/video_core/shader/shader_jit_x64_compiler.cpp new file mode 100644 index 000000000..92b35dbc0 --- /dev/null +++ b/src/video_core/shader/shader_jit_x64_compiler.cpp @@ -0,0 +1,889 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <cmath> +#include <cstdint> +#include <nihstro/shader_bytecode.h> +#include <smmintrin.h> +#include <xmmintrin.h> +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/vector_math.h" +#include "common/x64/cpu_detect.h" +#include "common/x64/xbyak_abi.h" +#include "common/x64/xbyak_util.h" +#include "video_core/pica_state.h" +#include "video_core/pica_types.h" +#include "video_core/shader/shader.h" +#include "video_core/shader/shader_jit_x64_compiler.h" + +using namespace Common::X64; +using namespace Xbyak::util; +using Xbyak::Label; +using Xbyak::Reg32; +using Xbyak::Reg64; +using Xbyak::Xmm; + +namespace Pica { + +namespace Shader { + +typedef void (JitShader::*JitFunction)(Instruction instr); + +const JitFunction instr_table[64] = { + &JitShader::Compile_ADD, // add + &JitShader::Compile_DP3, // dp3 + &JitShader::Compile_DP4, // dp4 + &JitShader::Compile_DPH, // dph + nullptr, // unknown + &JitShader::Compile_EX2, // ex2 + &JitShader::Compile_LG2, // lg2 + nullptr, // unknown + &JitShader::Compile_MUL, // mul + &JitShader::Compile_SGE, // sge + &JitShader::Compile_SLT, // slt + &JitShader::Compile_FLR, // flr + &JitShader::Compile_MAX, // max + &JitShader::Compile_MIN, // min + &JitShader::Compile_RCP, // rcp + &JitShader::Compile_RSQ, // rsq + nullptr, // unknown + nullptr, // unknown + &JitShader::Compile_MOVA, // mova + &JitShader::Compile_MOV, // mov + nullptr, // unknown + nullptr, // unknown + nullptr, // unknown + nullptr, // unknown + &JitShader::Compile_DPH, // dphi + nullptr, // unknown + &JitShader::Compile_SGE, // sgei + &JitShader::Compile_SLT, // slti + nullptr, // unknown + nullptr, // unknown + nullptr, // unknown + nullptr, // unknown + nullptr, // unknown + &JitShader::Compile_NOP, // nop + &JitShader::Compile_END, // end + nullptr, // break + &JitShader::Compile_CALL, // call + &JitShader::Compile_CALLC, // callc + &JitShader::Compile_CALLU, // callu + &JitShader::Compile_IF, // ifu + &JitShader::Compile_IF, // ifc + &JitShader::Compile_LOOP, // loop + nullptr, // emit + nullptr, // sete + &JitShader::Compile_JMP, // jmpc + &JitShader::Compile_JMP, // jmpu + &JitShader::Compile_CMP, // cmp + &JitShader::Compile_CMP, // cmp + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad +}; + +// The following is used to alias some commonly used registers. Generally, RAX-RDX and XMM0-XMM3 can +// be used as scratch registers within a compiler function. The other registers have designated +// purposes, as documented below: + +/// Pointer to the uniform memory +static const Reg64 SETUP = r9; +/// The two 32-bit VS address offset registers set by the MOVA instruction +static const Reg64 ADDROFFS_REG_0 = r10; +static const Reg64 ADDROFFS_REG_1 = r11; +/// VS loop count register (Multiplied by 16) +static const Reg32 LOOPCOUNT_REG = r12d; +/// Current VS loop iteration number (we could probably use LOOPCOUNT_REG, but this quicker) +static const Reg32 LOOPCOUNT = esi; +/// Number to increment LOOPCOUNT_REG by on each loop iteration (Multiplied by 16) +static const Reg32 LOOPINC = edi; +/// Result of the previous CMP instruction for the X-component comparison +static const Reg64 COND0 = r13; +/// Result of the previous CMP instruction for the Y-component comparison +static const Reg64 COND1 = r14; +/// Pointer to the UnitState instance for the current VS unit +static const Reg64 STATE = r15; +/// SIMD scratch register +static const Xmm SCRATCH = xmm0; +/// Loaded with the first swizzled source register, otherwise can be used as a scratch register +static const Xmm SRC1 = xmm1; +/// Loaded with the second swizzled source register, otherwise can be used as a scratch register +static const Xmm SRC2 = xmm2; +/// Loaded with the third swizzled source register, otherwise can be used as a scratch register +static const Xmm SRC3 = xmm3; +/// Additional scratch register +static const Xmm SCRATCH2 = xmm4; +/// Constant vector of [1.0f, 1.0f, 1.0f, 1.0f], used to efficiently set a vector to one +static const Xmm ONE = xmm14; +/// Constant vector of [-0.f, -0.f, -0.f, -0.f], used to efficiently negate a vector with XOR +static const Xmm NEGBIT = xmm15; + +// State registers that must not be modified by external functions calls +// Scratch registers, e.g., SRC1 and SCRATCH, have to be saved on the side if needed +static const BitSet32 persistent_regs = BuildRegSet({ + // Pointers to register blocks + SETUP, STATE, + // Cached registers + ADDROFFS_REG_0, ADDROFFS_REG_1, LOOPCOUNT_REG, COND0, COND1, + // Constants + ONE, NEGBIT, + // Loop variables + LOOPCOUNT, LOOPINC, +}); + +/// Raw constant for the source register selector that indicates no swizzling is performed +static const u8 NO_SRC_REG_SWIZZLE = 0x1b; +/// Raw constant for the destination register enable mask that indicates all components are enabled +static const u8 NO_DEST_REG_MASK = 0xf; + +static void LogCritical(const char* msg) { + LOG_CRITICAL(HW_GPU, "%s", msg); +} + +void JitShader::Compile_Assert(bool condition, const char* msg) { + if (!condition) { + mov(ABI_PARAM1, reinterpret_cast<size_t>(msg)); + CallFarFunction(*this, LogCritical); + } +} + +/** + * Loads and swizzles a source register into the specified XMM register. + * @param instr VS instruction, used for determining how to load the source register + * @param src_num Number indicating which source register to load (1 = src1, 2 = src2, 3 = src3) + * @param src_reg SourceRegister object corresponding to the source register to load + * @param dest Destination XMM register to store the loaded, swizzled source register + */ +void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, + Xmm dest) { + Reg64 src_ptr; + size_t src_offset; + + if (src_reg.GetRegisterType() == RegisterType::FloatUniform) { + src_ptr = SETUP; + src_offset = ShaderSetup::GetFloatUniformOffset(src_reg.GetIndex()); + } else { + src_ptr = STATE; + src_offset = UnitState::InputOffset(src_reg); + } + + int src_offset_disp = (int)src_offset; + ASSERT_MSG(src_offset == src_offset_disp, "Source register offset too large for int type"); + + unsigned operand_desc_id; + + const bool is_inverted = + (0 != (instr.opcode.Value().GetInfo().subtype & OpCode::Info::SrcInversed)); + + unsigned address_register_index; + unsigned offset_src; + + if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD || + instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) { + operand_desc_id = instr.mad.operand_desc_id; + offset_src = is_inverted ? 3 : 2; + address_register_index = instr.mad.address_register_index; + } else { + operand_desc_id = instr.common.operand_desc_id; + offset_src = is_inverted ? 2 : 1; + address_register_index = instr.common.address_register_index; + } + + if (src_num == offset_src && address_register_index != 0) { + switch (address_register_index) { + case 1: // address offset 1 + movaps(dest, xword[src_ptr + ADDROFFS_REG_0 + src_offset_disp]); + break; + case 2: // address offset 2 + movaps(dest, xword[src_ptr + ADDROFFS_REG_1 + src_offset_disp]); + break; + case 3: // address offset 3 + movaps(dest, xword[src_ptr + LOOPCOUNT_REG.cvt64() + src_offset_disp]); + break; + default: + UNREACHABLE(); + break; + } + } else { + // Load the source + movaps(dest, xword[src_ptr + src_offset_disp]); + } + + SwizzlePattern swiz = {(*swizzle_data)[operand_desc_id]}; + + // Generate instructions for source register swizzling as needed + u8 sel = swiz.GetRawSelector(src_num); + if (sel != NO_SRC_REG_SWIZZLE) { + // Selector component order needs to be reversed for the SHUFPS instruction + sel = ((sel & 0xc0) >> 6) | ((sel & 3) << 6) | ((sel & 0xc) << 2) | ((sel & 0x30) >> 2); + + // Shuffle inputs for swizzle + shufps(dest, dest, sel); + } + + // If the source register should be negated, flip the negative bit using XOR + const bool negate[] = {swiz.negate_src1, swiz.negate_src2, swiz.negate_src3}; + if (negate[src_num - 1]) { + xorps(dest, NEGBIT); + } +} + +void JitShader::Compile_DestEnable(Instruction instr, Xmm src) { + DestRegister dest; + unsigned operand_desc_id; + if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD || + instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) { + operand_desc_id = instr.mad.operand_desc_id; + dest = instr.mad.dest.Value(); + } else { + operand_desc_id = instr.common.operand_desc_id; + dest = instr.common.dest.Value(); + } + + SwizzlePattern swiz = {(*swizzle_data)[operand_desc_id]}; + + size_t dest_offset_disp = UnitState::OutputOffset(dest); + + // If all components are enabled, write the result to the destination register + if (swiz.dest_mask == NO_DEST_REG_MASK) { + // Store dest back to memory + movaps(xword[STATE + dest_offset_disp], src); + + } else { + // Not all components are enabled, so mask the result when storing to the destination + // register... + movaps(SCRATCH, xword[STATE + dest_offset_disp]); + + if (Common::GetCPUCaps().sse4_1) { + u8 mask = ((swiz.dest_mask & 1) << 3) | ((swiz.dest_mask & 8) >> 3) | + ((swiz.dest_mask & 2) << 1) | ((swiz.dest_mask & 4) >> 1); + blendps(SCRATCH, src, mask); + } else { + movaps(SCRATCH2, src); + unpckhps(SCRATCH2, SCRATCH); // Unpack X/Y components of source and destination + unpcklps(SCRATCH, src); // Unpack Z/W components of source and destination + + // Compute selector to selectively copy source components to destination for SHUFPS + // instruction + u8 sel = ((swiz.DestComponentEnabled(0) ? 1 : 0) << 0) | + ((swiz.DestComponentEnabled(1) ? 3 : 2) << 2) | + ((swiz.DestComponentEnabled(2) ? 0 : 1) << 4) | + ((swiz.DestComponentEnabled(3) ? 2 : 3) << 6); + shufps(SCRATCH, SCRATCH2, sel); + } + + // Store dest back to memory + movaps(xword[STATE + dest_offset_disp], SCRATCH); + } +} + +void JitShader::Compile_SanitizedMul(Xmm src1, Xmm src2, Xmm scratch) { + movaps(scratch, src1); + cmpordps(scratch, src2); + + mulps(src1, src2); + + movaps(src2, src1); + cmpunordps(src2, src2); + + xorps(scratch, src2); + andps(src1, scratch); +} + +void JitShader::Compile_EvaluateCondition(Instruction instr) { + // Note: NXOR is used below to check for equality + switch (instr.flow_control.op) { + case Instruction::FlowControlType::Or: + mov(eax, COND0); + mov(ebx, COND1); + xor(eax, (instr.flow_control.refx.Value() ^ 1)); + xor(ebx, (instr.flow_control.refy.Value() ^ 1)); + or (eax, ebx); + break; + + case Instruction::FlowControlType::And: + mov(eax, COND0); + mov(ebx, COND1); + xor(eax, (instr.flow_control.refx.Value() ^ 1)); + xor(ebx, (instr.flow_control.refy.Value() ^ 1)); + and(eax, ebx); + break; + + case Instruction::FlowControlType::JustX: + mov(eax, COND0); + xor(eax, (instr.flow_control.refx.Value() ^ 1)); + break; + + case Instruction::FlowControlType::JustY: + mov(eax, COND1); + xor(eax, (instr.flow_control.refy.Value() ^ 1)); + break; + } +} + +void JitShader::Compile_UniformCondition(Instruction instr) { + size_t offset = ShaderSetup::GetBoolUniformOffset(instr.flow_control.bool_uniform_id); + cmp(byte[SETUP + offset], 0); +} + +BitSet32 JitShader::PersistentCallerSavedRegs() { + return persistent_regs & ABI_ALL_CALLER_SAVED; +} + +void JitShader::Compile_ADD(Instruction instr) { + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); + addps(SRC1, SRC2); + Compile_DestEnable(instr, SRC1); +} + +void JitShader::Compile_DP3(Instruction instr) { + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); + + Compile_SanitizedMul(SRC1, SRC2, SCRATCH); + + movaps(SRC2, SRC1); + shufps(SRC2, SRC2, _MM_SHUFFLE(1, 1, 1, 1)); + + movaps(SRC3, SRC1); + shufps(SRC3, SRC3, _MM_SHUFFLE(2, 2, 2, 2)); + + shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); + addps(SRC1, SRC2); + addps(SRC1, SRC3); + + Compile_DestEnable(instr, SRC1); +} + +void JitShader::Compile_DP4(Instruction instr) { + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); + + Compile_SanitizedMul(SRC1, SRC2, SCRATCH); + + movaps(SRC2, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY + addps(SRC1, SRC2); + + movaps(SRC2, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX + addps(SRC1, SRC2); + + Compile_DestEnable(instr, SRC1); +} + +void JitShader::Compile_DPH(Instruction instr) { + if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::DPHI) { + Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1); + Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2); + } else { + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); + } + + if (Common::GetCPUCaps().sse4_1) { + // Set 4th component to 1.0 + blendps(SRC1, ONE, 0b1000); + } else { + // Set 4th component to 1.0 + movaps(SCRATCH, SRC1); + unpckhps(SCRATCH, ONE); // XYZW, 1111 -> Z1__ + unpcklpd(SRC1, SCRATCH); // XYZW, Z1__ -> XYZ1 + } + + Compile_SanitizedMul(SRC1, SRC2, SCRATCH); + + movaps(SRC2, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY + addps(SRC1, SRC2); + + movaps(SRC2, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX + addps(SRC1, SRC2); + + Compile_DestEnable(instr, SRC1); +} + +void JitShader::Compile_EX2(Instruction instr) { + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + movss(xmm0, SRC1); // ABI_PARAM1 + + ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); + CallFarFunction(*this, exp2f); + ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); + + shufps(xmm0, xmm0, _MM_SHUFFLE(0, 0, 0, 0)); // ABI_RETURN + movaps(SRC1, xmm0); + Compile_DestEnable(instr, SRC1); +} + +void JitShader::Compile_LG2(Instruction instr) { + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + movss(xmm0, SRC1); // ABI_PARAM1 + + ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); + CallFarFunction(*this, log2f); + ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); + + shufps(xmm0, xmm0, _MM_SHUFFLE(0, 0, 0, 0)); // ABI_RETURN + movaps(SRC1, xmm0); + Compile_DestEnable(instr, SRC1); +} + +void JitShader::Compile_MUL(Instruction instr) { + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); + Compile_SanitizedMul(SRC1, SRC2, SCRATCH); + Compile_DestEnable(instr, SRC1); +} + +void JitShader::Compile_SGE(Instruction instr) { + if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::SGEI) { + Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1); + Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2); + } else { + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); + } + + cmpleps(SRC2, SRC1); + andps(SRC2, ONE); + + Compile_DestEnable(instr, SRC2); +} + +void JitShader::Compile_SLT(Instruction instr) { + if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::SLTI) { + Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1); + Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2); + } else { + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); + } + + cmpltps(SRC1, SRC2); + andps(SRC1, ONE); + + Compile_DestEnable(instr, SRC1); +} + +void JitShader::Compile_FLR(Instruction instr) { + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + + if (Common::GetCPUCaps().sse4_1) { + roundps(SRC1, SRC1, _MM_FROUND_FLOOR); + } else { + cvttps2dq(SRC1, SRC1); + cvtdq2ps(SRC1, SRC1); + } + + Compile_DestEnable(instr, SRC1); +} + +void JitShader::Compile_MAX(Instruction instr) { + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); + // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned. + maxps(SRC1, SRC2); + Compile_DestEnable(instr, SRC1); +} + +void JitShader::Compile_MIN(Instruction instr) { + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); + // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned. + minps(SRC1, SRC2); + Compile_DestEnable(instr, SRC1); +} + +void JitShader::Compile_MOVA(Instruction instr) { + SwizzlePattern swiz = {(*swizzle_data)[instr.common.operand_desc_id]}; + + if (!swiz.DestComponentEnabled(0) && !swiz.DestComponentEnabled(1)) { + return; // NoOp + } + + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + + // Convert floats to integers using truncation (only care about X and Y components) + cvttps2dq(SRC1, SRC1); + + // Get result + movq(rax, SRC1); + + // Handle destination enable + if (swiz.DestComponentEnabled(0) && swiz.DestComponentEnabled(1)) { + // Move and sign-extend low 32 bits + movsxd(ADDROFFS_REG_0, eax); + + // Move and sign-extend high 32 bits + shr(rax, 32); + movsxd(ADDROFFS_REG_1, eax); + + // Multiply by 16 to be used as an offset later + shl(ADDROFFS_REG_0, 4); + shl(ADDROFFS_REG_1, 4); + } else { + if (swiz.DestComponentEnabled(0)) { + // Move and sign-extend low 32 bits + movsxd(ADDROFFS_REG_0, eax); + + // Multiply by 16 to be used as an offset later + shl(ADDROFFS_REG_0, 4); + } else if (swiz.DestComponentEnabled(1)) { + // Move and sign-extend high 32 bits + shr(rax, 32); + movsxd(ADDROFFS_REG_1, eax); + + // Multiply by 16 to be used as an offset later + shl(ADDROFFS_REG_1, 4); + } + } +} + +void JitShader::Compile_MOV(Instruction instr) { + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + Compile_DestEnable(instr, SRC1); +} + +void JitShader::Compile_RCP(Instruction instr) { + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + + // TODO(bunnei): RCPSS is a pretty rough approximation, this might cause problems if Pica + // performs this operation more accurately. This should be checked on hardware. + rcpss(SRC1, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX + + Compile_DestEnable(instr, SRC1); +} + +void JitShader::Compile_RSQ(Instruction instr) { + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + + // TODO(bunnei): RSQRTSS is a pretty rough approximation, this might cause problems if Pica + // performs this operation more accurately. This should be checked on hardware. + rsqrtss(SRC1, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX + + Compile_DestEnable(instr, SRC1); +} + +void JitShader::Compile_NOP(Instruction instr) {} + +void JitShader::Compile_END(Instruction instr) { + ABI_PopRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8, 16); + ret(); +} + +void JitShader::Compile_CALL(Instruction instr) { + // Push offset of the return + push(qword, (instr.flow_control.dest_offset + instr.flow_control.num_instructions)); + + // Call the subroutine + call(instruction_labels[instr.flow_control.dest_offset]); + + // Skip over the return offset that's on the stack + add(rsp, 8); +} + +void JitShader::Compile_CALLC(Instruction instr) { + Compile_EvaluateCondition(instr); + Label b; + jz(b); + Compile_CALL(instr); + L(b); +} + +void JitShader::Compile_CALLU(Instruction instr) { + Compile_UniformCondition(instr); + Label b; + jz(b); + Compile_CALL(instr); + L(b); +} + +void JitShader::Compile_CMP(Instruction instr) { + using Op = Instruction::Common::CompareOpType::Op; + Op op_x = instr.common.compare_op.x; + Op op_y = instr.common.compare_op.y; + + Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); + + // SSE doesn't have greater-than (GT) or greater-equal (GE) comparison operators. You need to + // emulate them by swapping the lhs and rhs and using LT and LE. NLT and NLE can't be used here + // because they don't match when used with NaNs. + static const u8 cmp[] = {CMP_EQ, CMP_NEQ, CMP_LT, CMP_LE, CMP_LT, CMP_LE}; + + bool invert_op_x = (op_x == Op::GreaterThan || op_x == Op::GreaterEqual); + Xmm lhs_x = invert_op_x ? SRC2 : SRC1; + Xmm rhs_x = invert_op_x ? SRC1 : SRC2; + + if (op_x == op_y) { + // Compare X-component and Y-component together + cmpps(lhs_x, rhs_x, cmp[op_x]); + movq(COND0, lhs_x); + + mov(COND1, COND0); + } else { + bool invert_op_y = (op_y == Op::GreaterThan || op_y == Op::GreaterEqual); + Xmm lhs_y = invert_op_y ? SRC2 : SRC1; + Xmm rhs_y = invert_op_y ? SRC1 : SRC2; + + // Compare X-component + movaps(SCRATCH, lhs_x); + cmpss(SCRATCH, rhs_x, cmp[op_x]); + + // Compare Y-component + cmpps(lhs_y, rhs_y, cmp[op_y]); + + movq(COND0, SCRATCH); + movq(COND1, lhs_y); + } + + shr(COND0.cvt32(), 31); // ignores upper 32 bits in source + shr(COND1, 63); +} + +void JitShader::Compile_MAD(Instruction instr) { + Compile_SwizzleSrc(instr, 1, instr.mad.src1, SRC1); + + if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) { + Compile_SwizzleSrc(instr, 2, instr.mad.src2i, SRC2); + Compile_SwizzleSrc(instr, 3, instr.mad.src3i, SRC3); + } else { + Compile_SwizzleSrc(instr, 2, instr.mad.src2, SRC2); + Compile_SwizzleSrc(instr, 3, instr.mad.src3, SRC3); + } + + Compile_SanitizedMul(SRC1, SRC2, SCRATCH); + addps(SRC1, SRC3); + + Compile_DestEnable(instr, SRC1); +} + +void JitShader::Compile_IF(Instruction instr) { + Compile_Assert(instr.flow_control.dest_offset >= program_counter, + "Backwards if-statements not supported"); + Label l_else, l_endif; + + // Evaluate the "IF" condition + if (instr.opcode.Value() == OpCode::Id::IFU) { + Compile_UniformCondition(instr); + } else if (instr.opcode.Value() == OpCode::Id::IFC) { + Compile_EvaluateCondition(instr); + } + jz(l_else, T_NEAR); + + // Compile the code that corresponds to the condition evaluating as true + Compile_Block(instr.flow_control.dest_offset); + + // If there isn't an "ELSE" condition, we are done here + if (instr.flow_control.num_instructions == 0) { + L(l_else); + return; + } + + jmp(l_endif, T_NEAR); + + L(l_else); + // This code corresponds to the "ELSE" condition + // Comple the code that corresponds to the condition evaluating as false + Compile_Block(instr.flow_control.dest_offset + instr.flow_control.num_instructions); + + L(l_endif); +} + +void JitShader::Compile_LOOP(Instruction instr) { + Compile_Assert(instr.flow_control.dest_offset >= program_counter, + "Backwards loops not supported"); + Compile_Assert(!looping, "Nested loops not supported"); + + looping = true; + + // This decodes the fields from the integer uniform at index instr.flow_control.int_uniform_id. + // The Y (LOOPCOUNT_REG) and Z (LOOPINC) component are kept multiplied by 16 (Left shifted by + // 4 bits) to be used as an offset into the 16-byte vector registers later + size_t offset = ShaderSetup::GetIntUniformOffset(instr.flow_control.int_uniform_id); + mov(LOOPCOUNT, dword[SETUP + offset]); + mov(LOOPCOUNT_REG, LOOPCOUNT); + shr(LOOPCOUNT_REG, 4); + and(LOOPCOUNT_REG, 0xFF0); // Y-component is the start + mov(LOOPINC, LOOPCOUNT); + shr(LOOPINC, 12); + and(LOOPINC, 0xFF0); // Z-component is the incrementer + movzx(LOOPCOUNT, LOOPCOUNT.cvt8()); // X-component is iteration count + add(LOOPCOUNT, 1); // Iteration count is X-component + 1 + + Label l_loop_start; + L(l_loop_start); + + Compile_Block(instr.flow_control.dest_offset + 1); + + add(LOOPCOUNT_REG, LOOPINC); // Increment LOOPCOUNT_REG by Z-component + sub(LOOPCOUNT, 1); // Increment loop count by 1 + jnz(l_loop_start); // Loop if not equal + + looping = false; +} + +void JitShader::Compile_JMP(Instruction instr) { + if (instr.opcode.Value() == OpCode::Id::JMPC) + Compile_EvaluateCondition(instr); + else if (instr.opcode.Value() == OpCode::Id::JMPU) + Compile_UniformCondition(instr); + else + UNREACHABLE(); + + bool inverted_condition = + (instr.opcode.Value() == OpCode::Id::JMPU) && (instr.flow_control.num_instructions & 1); + + Label& b = instruction_labels[instr.flow_control.dest_offset]; + if (inverted_condition) { + jz(b, T_NEAR); + } else { + jnz(b, T_NEAR); + } +} + +void JitShader::Compile_Block(unsigned end) { + while (program_counter < end) { + Compile_NextInstr(); + } +} + +void JitShader::Compile_Return() { + // Peek return offset on the stack and check if we're at that offset + mov(rax, qword[rsp + 8]); + cmp(eax, (program_counter)); + + // If so, jump back to before CALL + Label b; + jnz(b); + ret(); + L(b); +} + +void JitShader::Compile_NextInstr() { + if (std::binary_search(return_offsets.begin(), return_offsets.end(), program_counter)) { + Compile_Return(); + } + + L(instruction_labels[program_counter]); + + Instruction instr = {(*program_code)[program_counter++]}; + + OpCode::Id opcode = instr.opcode.Value(); + auto instr_func = instr_table[static_cast<unsigned>(opcode)]; + + if (instr_func) { + // JIT the instruction! + ((*this).*instr_func)(instr); + } else { + // Unhandled instruction + LOG_CRITICAL(HW_GPU, "Unhandled instruction: 0x%02x (0x%08x)", + instr.opcode.Value().EffectiveOpCode(), instr.hex); + } +} + +void JitShader::FindReturnOffsets() { + return_offsets.clear(); + + for (size_t offset = 0; offset < program_code->size(); ++offset) { + Instruction instr = {(*program_code)[offset]}; + + switch (instr.opcode.Value()) { + case OpCode::Id::CALL: + case OpCode::Id::CALLC: + case OpCode::Id::CALLU: + return_offsets.push_back(instr.flow_control.dest_offset + + instr.flow_control.num_instructions); + break; + default: + break; + } + } + + // Sort for efficient binary search later + std::sort(return_offsets.begin(), return_offsets.end()); +} + +void JitShader::Compile(const std::array<u32, 1024>* program_code_, + const std::array<u32, 1024>* swizzle_data_) { + program_code = program_code_; + swizzle_data = swizzle_data_; + + // Reset flow control state + program = (CompiledShader*)getCurr(); + program_counter = 0; + looping = false; + instruction_labels.fill(Xbyak::Label()); + + // Find all `CALL` instructions and identify return locations + FindReturnOffsets(); + + // The stack pointer is 8 modulo 16 at the entry of a procedure + // We reserve 16 bytes and assign a dummy value to the first 8 bytes, to catch any potential + // return checks (see Compile_Return) that happen in shader main routine. + ABI_PushRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8, 16); + mov(qword[rsp + 8], 0xFFFFFFFFFFFFFFFFULL); + + mov(SETUP, ABI_PARAM1); + mov(STATE, ABI_PARAM2); + + // Zero address/loop registers + xor(ADDROFFS_REG_0.cvt32(), ADDROFFS_REG_0.cvt32()); + xor(ADDROFFS_REG_1.cvt32(), ADDROFFS_REG_1.cvt32()); + xor(LOOPCOUNT_REG, LOOPCOUNT_REG); + + // Used to set a register to one + static const __m128 one = {1.f, 1.f, 1.f, 1.f}; + mov(rax, reinterpret_cast<size_t>(&one)); + movaps(ONE, xword[rax]); + + // Used to negate registers + static const __m128 neg = {-0.f, -0.f, -0.f, -0.f}; + mov(rax, reinterpret_cast<size_t>(&neg)); + movaps(NEGBIT, xword[rax]); + + // Jump to start of the shader program + jmp(ABI_PARAM3); + + // Compile entire program + Compile_Block(static_cast<unsigned>(program_code->size())); + + // Free memory that's no longer needed + program_code = nullptr; + swizzle_data = nullptr; + return_offsets.clear(); + return_offsets.shrink_to_fit(); + + ready(); + + ASSERT_MSG(getSize() <= MAX_SHADER_SIZE, "Compiled a shader that exceeds the allocated size!"); + LOG_DEBUG(HW_GPU, "Compiled shader size=%lu", getSize()); +} + +JitShader::JitShader() : Xbyak::CodeGenerator(MAX_SHADER_SIZE) {} + +} // namespace Shader + +} // namespace Pica diff --git a/src/video_core/shader/shader_jit_x64_compiler.h b/src/video_core/shader/shader_jit_x64_compiler.h new file mode 100644 index 000000000..599e43ffd --- /dev/null +++ b/src/video_core/shader/shader_jit_x64_compiler.h @@ -0,0 +1,124 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include <utility> +#include <vector> +#include <nihstro/shader_bytecode.h> +#include <xbyak.h> +#include "common/bit_set.h" +#include "common/common_types.h" +#include "video_core/shader/shader.h" + +using nihstro::Instruction; +using nihstro::OpCode; +using nihstro::SwizzlePattern; + +namespace Pica { + +namespace Shader { + +/// Memory allocated for each compiled shader (64Kb) +constexpr size_t MAX_SHADER_SIZE = 1024 * 64; + +/** + * This class implements the shader JIT compiler. It recompiles a Pica shader program into x86_64 + * code that can be executed on the host machine directly. + */ +class JitShader : public Xbyak::CodeGenerator { +public: + JitShader(); + + void Run(const ShaderSetup& setup, UnitState& state, unsigned offset) const { + program(&setup, &state, instruction_labels[offset].getAddress()); + } + + void Compile(const std::array<u32, 1024>* program_code, + const std::array<u32, 1024>* swizzle_data); + + void Compile_ADD(Instruction instr); + void Compile_DP3(Instruction instr); + void Compile_DP4(Instruction instr); + void Compile_DPH(Instruction instr); + void Compile_EX2(Instruction instr); + void Compile_LG2(Instruction instr); + void Compile_MUL(Instruction instr); + void Compile_SGE(Instruction instr); + void Compile_SLT(Instruction instr); + void Compile_FLR(Instruction instr); + void Compile_MAX(Instruction instr); + void Compile_MIN(Instruction instr); + void Compile_RCP(Instruction instr); + void Compile_RSQ(Instruction instr); + void Compile_MOVA(Instruction instr); + void Compile_MOV(Instruction instr); + void Compile_NOP(Instruction instr); + void Compile_END(Instruction instr); + void Compile_CALL(Instruction instr); + void Compile_CALLC(Instruction instr); + void Compile_CALLU(Instruction instr); + void Compile_IF(Instruction instr); + void Compile_LOOP(Instruction instr); + void Compile_JMP(Instruction instr); + void Compile_CMP(Instruction instr); + void Compile_MAD(Instruction instr); + +private: + void Compile_Block(unsigned end); + void Compile_NextInstr(); + + void Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, + Xbyak::Xmm dest); + void Compile_DestEnable(Instruction instr, Xbyak::Xmm dest); + + /** + * Compiles a `MUL src1, src2` operation, properly handling the PICA semantics when multiplying + * zero by inf. Clobbers `src2` and `scratch`. + */ + void Compile_SanitizedMul(Xbyak::Xmm src1, Xbyak::Xmm src2, Xbyak::Xmm scratch); + + void Compile_EvaluateCondition(Instruction instr); + void Compile_UniformCondition(Instruction instr); + + /** + * Emits the code to conditionally return from a subroutine envoked by the `CALL` instruction. + */ + void Compile_Return(); + + BitSet32 PersistentCallerSavedRegs(); + + /** + * Assertion evaluated at compile-time, but only triggered if executed at runtime. + * @param msg Message to be logged if the assertion fails. + */ + void Compile_Assert(bool condition, const char* msg); + + /** + * Analyzes the entire shader program for `CALL` instructions before emitting any code, + * identifying the locations where a return needs to be inserted. + */ + void FindReturnOffsets(); + + const std::array<u32, 1024>* program_code = nullptr; + const std::array<u32, 1024>* swizzle_data = nullptr; + + /// Mapping of Pica VS instructions to pointers in the emitted code + std::array<Xbyak::Label, 1024> instruction_labels; + + /// Offsets in code where a return needs to be inserted + std::vector<unsigned> return_offsets; + + unsigned program_counter = 0; ///< Offset of the next instruction to decode + bool looping = false; ///< True if compiling a loop, used to check for nested loops + + using CompiledShader = void(const void* setup, void* state, const u8* start_addr); + CompiledShader* program = nullptr; +}; + +} // Shader + +} // Pica diff --git a/src/video_core/texture/etc1.cpp b/src/video_core/texture/etc1.cpp new file mode 100644 index 000000000..af60cde1e --- /dev/null +++ b/src/video_core/texture/etc1.cpp @@ -0,0 +1,124 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include "common/bit_field.h" +#include "common/color.h" +#include "common/common_types.h" +#include "common/math_util.h" +#include "common/vector_math.h" +#include "video_core/texture/etc1.h" + +namespace Pica { +namespace Texture { + +namespace { + +constexpr std::array<u8[2], 8> etc1_modifier_table = {{ + {2, 8}, {5, 17}, {9, 29}, {13, 42}, {18, 60}, {24, 80}, {33, 106}, {47, 183}, +}}; + +union ETC1Tile { + u64 raw; + + // Each of these two is a collection of 16 bits (one per lookup value) + BitField<0, 16, u64> table_subindexes; + BitField<16, 16, u64> negation_flags; + + unsigned GetTableSubIndex(unsigned index) const { + return (table_subindexes >> index) & 1; + } + + bool GetNegationFlag(unsigned index) const { + return ((negation_flags >> index) & 1) == 1; + } + + BitField<32, 1, u64> flip; + BitField<33, 1, u64> differential_mode; + + BitField<34, 3, u64> table_index_2; + BitField<37, 3, u64> table_index_1; + + union { + // delta value + base value + BitField<40, 3, s64> db; + BitField<43, 5, u64> b; + + BitField<48, 3, s64> dg; + BitField<51, 5, u64> g; + + BitField<56, 3, s64> dr; + BitField<59, 5, u64> r; + } differential; + + union { + BitField<40, 4, u64> b2; + BitField<44, 4, u64> b1; + + BitField<48, 4, u64> g2; + BitField<52, 4, u64> g1; + + BitField<56, 4, u64> r2; + BitField<60, 4, u64> r1; + } separate; + + const Math::Vec3<u8> GetRGB(unsigned int x, unsigned int y) const { + int texel = 4 * x + y; + + if (flip) + std::swap(x, y); + + // Lookup base value + Math::Vec3<int> ret; + if (differential_mode) { + ret.r() = static_cast<int>(differential.r); + ret.g() = static_cast<int>(differential.g); + ret.b() = static_cast<int>(differential.b); + if (x >= 2) { + ret.r() += static_cast<int>(differential.dr); + ret.g() += static_cast<int>(differential.dg); + ret.b() += static_cast<int>(differential.db); + } + ret.r() = Color::Convert5To8(ret.r()); + ret.g() = Color::Convert5To8(ret.g()); + ret.b() = Color::Convert5To8(ret.b()); + } else { + if (x < 2) { + ret.r() = Color::Convert4To8(static_cast<u8>(separate.r1)); + ret.g() = Color::Convert4To8(static_cast<u8>(separate.g1)); + ret.b() = Color::Convert4To8(static_cast<u8>(separate.b1)); + } else { + ret.r() = Color::Convert4To8(static_cast<u8>(separate.r2)); + ret.g() = Color::Convert4To8(static_cast<u8>(separate.g2)); + ret.b() = Color::Convert4To8(static_cast<u8>(separate.b2)); + } + } + + // Add modifier + unsigned table_index = + static_cast<int>((x < 2) ? table_index_1.Value() : table_index_2.Value()); + + int modifier = etc1_modifier_table[table_index][GetTableSubIndex(texel)]; + if (GetNegationFlag(texel)) + modifier *= -1; + + ret.r() = MathUtil::Clamp(ret.r() + modifier, 0, 255); + ret.g() = MathUtil::Clamp(ret.g() + modifier, 0, 255); + ret.b() = MathUtil::Clamp(ret.b() + modifier, 0, 255); + + return ret.Cast<u8>(); + } +}; + +} // anonymous namespace + +Math::Vec3<u8> SampleETC1Subtile(u64 value, unsigned int x, unsigned int y) { + ETC1Tile tile{value}; + return tile.GetRGB(x, y); +} + +} // namespace Texture +} // namespace Pica diff --git a/src/video_core/texture/etc1.h b/src/video_core/texture/etc1.h new file mode 100644 index 000000000..e188b19df --- /dev/null +++ b/src/video_core/texture/etc1.h @@ -0,0 +1,16 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "common/vector_math.h" + +namespace Pica { +namespace Texture { + +Math::Vec3<u8> SampleETC1Subtile(u64 value, unsigned int x, unsigned int y); + +} // namespace Texture +} // namespace Pica diff --git a/src/video_core/texture/texture_decode.cpp b/src/video_core/texture/texture_decode.cpp new file mode 100644 index 000000000..f611a1aa9 --- /dev/null +++ b/src/video_core/texture/texture_decode.cpp @@ -0,0 +1,229 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/assert.h" +#include "common/color.h" +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/swap.h" +#include "common/vector_math.h" +#include "video_core/pica.h" +#include "video_core/texture/etc1.h" +#include "video_core/texture/texture_decode.h" +#include "video_core/utils.h" + +using TextureFormat = Pica::Regs::TextureFormat; + +namespace Pica { +namespace Texture { + +constexpr size_t TILE_SIZE = 8 * 8; +constexpr size_t ETC1_SUBTILES = 2 * 2; + +size_t CalculateTileSize(TextureFormat format) { + switch (format) { + case TextureFormat::RGBA8: + return 4 * TILE_SIZE; + + case TextureFormat::RGB8: + return 3 * TILE_SIZE; + + case TextureFormat::RGB5A1: + case TextureFormat::RGB565: + case TextureFormat::RGBA4: + case TextureFormat::IA8: + case TextureFormat::RG8: + return 2 * TILE_SIZE; + + case TextureFormat::I8: + case TextureFormat::A8: + case TextureFormat::IA4: + return 1 * TILE_SIZE; + + case TextureFormat::I4: + case TextureFormat::A4: + return TILE_SIZE / 2; + + case TextureFormat::ETC1: + return ETC1_SUBTILES * 8; + + case TextureFormat::ETC1A4: + return ETC1_SUBTILES * 16; + + default: // placeholder for yet unknown formats + UNIMPLEMENTED(); + return 0; + } +} + +Math::Vec4<u8> LookupTexture(const u8* source, unsigned int x, unsigned int y, + const TextureInfo& info, bool disable_alpha) { + // Coordinate in tiles + const unsigned int coarse_x = x / 8; + const unsigned int coarse_y = y / 8; + + // Coordinate inside the tile + const unsigned int fine_x = x % 8; + const unsigned int fine_y = y % 8; + + const u8* line = source + coarse_y * info.stride; + const u8* tile = line + coarse_x * CalculateTileSize(info.format); + return LookupTexelInTile(tile, fine_x, fine_y, info, disable_alpha); +} + +Math::Vec4<u8> LookupTexelInTile(const u8* source, unsigned int x, unsigned int y, + const TextureInfo& info, bool disable_alpha) { + DEBUG_ASSERT(x < 8); + DEBUG_ASSERT(y < 8); + + using VideoCore::MortonInterleave; + + switch (info.format) { + case Regs::TextureFormat::RGBA8: { + auto res = Color::DecodeRGBA8(source + MortonInterleave(x, y) * 4); + return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())}; + } + + case Regs::TextureFormat::RGB8: { + auto res = Color::DecodeRGB8(source + MortonInterleave(x, y) * 3); + return {res.r(), res.g(), res.b(), 255}; + } + + case Regs::TextureFormat::RGB5A1: { + auto res = Color::DecodeRGB5A1(source + MortonInterleave(x, y) * 2); + return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())}; + } + + case Regs::TextureFormat::RGB565: { + auto res = Color::DecodeRGB565(source + MortonInterleave(x, y) * 2); + return {res.r(), res.g(), res.b(), 255}; + } + + case Regs::TextureFormat::RGBA4: { + auto res = Color::DecodeRGBA4(source + MortonInterleave(x, y) * 2); + return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())}; + } + + case Regs::TextureFormat::IA8: { + const u8* source_ptr = source + MortonInterleave(x, y) * 2; + + if (disable_alpha) { + // Show intensity as red, alpha as green + return {source_ptr[1], source_ptr[0], 0, 255}; + } else { + return {source_ptr[1], source_ptr[1], source_ptr[1], source_ptr[0]}; + } + } + + case Regs::TextureFormat::RG8: { + auto res = Color::DecodeRG8(source + MortonInterleave(x, y) * 2); + return {res.r(), res.g(), 0, 255}; + } + + case Regs::TextureFormat::I8: { + const u8* source_ptr = source + MortonInterleave(x, y); + return {*source_ptr, *source_ptr, *source_ptr, 255}; + } + + case Regs::TextureFormat::A8: { + const u8* source_ptr = source + MortonInterleave(x, y); + + 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 + MortonInterleave(x, y); + + u8 i = Color::Convert4To8(((*source_ptr) & 0xF0) >> 4); + u8 a = Color::Convert4To8((*source_ptr) & 0xF); + + if (disable_alpha) { + // Show intensity as red, alpha as green + return {i, a, 0, 255}; + } else { + return {i, i, i, a}; + } + } + + case Regs::TextureFormat::I4: { + u32 morton_offset = MortonInterleave(x, y); + const u8* source_ptr = source + morton_offset / 2; + + u8 i = (morton_offset % 2) ? ((*source_ptr & 0xF0) >> 4) : (*source_ptr & 0xF); + i = Color::Convert4To8(i); + + return {i, i, i, 255}; + } + + case Regs::TextureFormat::A4: { + u32 morton_offset = MortonInterleave(x, y); + const u8* source_ptr = source + morton_offset / 2; + + u8 a = (morton_offset % 2) ? ((*source_ptr & 0xF0) >> 4) : (*source_ptr & 0xF); + a = Color::Convert4To8(a); + + if (disable_alpha) { + return {a, a, a, 255}; + } else { + return {0, 0, 0, a}; + } + } + + case Regs::TextureFormat::ETC1: + case Regs::TextureFormat::ETC1A4: { + bool has_alpha = (info.format == Regs::TextureFormat::ETC1A4); + size_t subtile_size = has_alpha ? 16 : 8; + + // ETC1 further subdivides each 8x8 tile into four 4x4 subtiles + constexpr unsigned int subtile_width = 4; + constexpr unsigned int subtile_height = 4; + + unsigned int subtile_index = (x / subtile_width) + 2 * (y / subtile_height); + x %= subtile_width; + y %= subtile_height; + + const u8* subtile_ptr = source + subtile_index * subtile_size; + + u8 alpha = 255; + if (has_alpha) { + u64_le packed_alpha; + memcpy(&packed_alpha, subtile_ptr, sizeof(u64)); + subtile_ptr += sizeof(u64); + + alpha = Color::Convert4To8((packed_alpha >> (4 * (x * subtile_width + y))) & 0xF); + } + + u64_le subtile_data; + memcpy(&subtile_data, subtile_ptr, sizeof(u64)); + + return Math::MakeVec(SampleETC1Subtile(subtile_data, x, y), + disable_alpha ? (u8)255 : alpha); + } + + default: + LOG_ERROR(HW_GPU, "Unknown texture format: %x", (u32)info.format); + DEBUG_ASSERT(false); + return {}; + } +} + +TextureInfo TextureInfo::FromPicaRegister(const Regs::TextureConfig& config, + const Regs::TextureFormat& format) { + TextureInfo info; + info.physical_address = config.GetPhysicalAddress(); + info.width = config.width; + info.height = config.height; + info.format = format; + info.SetDefaultStride(); + return info; +} + +} // namespace Texture +} // namespace Pica diff --git a/src/video_core/texture/texture_decode.h b/src/video_core/texture/texture_decode.h new file mode 100644 index 000000000..5c636939a --- /dev/null +++ b/src/video_core/texture/texture_decode.h @@ -0,0 +1,60 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "common/vector_math.h" +#include "video_core/pica.h" + +namespace Pica { +namespace Texture { + +/// Returns the byte size of a 8*8 tile of the specified texture format. +size_t CalculateTileSize(Pica::Regs::TextureFormat format); + +struct TextureInfo { + PAddr physical_address; + unsigned int width; + unsigned int height; + ptrdiff_t stride; + Pica::Regs::TextureFormat format; + + static TextureInfo FromPicaRegister(const Pica::Regs::TextureConfig& config, + const Pica::Regs::TextureFormat& format); + + /// Calculates stride from format and width, assuming that the entire texture is contiguous. + void SetDefaultStride() { + stride = Pica::Texture::CalculateTileSize(format) * (width / 8); + } +}; + +/** + * Lookup texel located at the given coordinates and return an RGBA vector of its color. + * @param source Source pointer to read data from + * @param x,y 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. + */ +Math::Vec4<u8> LookupTexture(const u8* source, unsigned int x, unsigned int y, + const TextureInfo& info, bool disable_alpha = false); + +/** + * Looks up a texel from a single 8x8 texture tile. + * + * @param source Pointer to the beginning of the tile. + * @param x, y In-tile coordinates to read from. Must be < 8. + * @param info TextureInfo describing the texture format. + * @param disable_alpha Used for debugging. Sets the result alpha to 255 and either discards the + * real alpha or inserts it in an otherwise unused channel. + */ +Math::Vec4<u8> LookupTexelInTile(const u8* source, unsigned int x, unsigned int y, + const TextureInfo& info, bool disable_alpha); + +} // namespace Texture +} // namespace Pica diff --git a/src/video_core/vertex_loader.cpp b/src/video_core/vertex_loader.cpp index 2b8ef7018..bf83b61ca 100644 --- a/src/video_core/vertex_loader.cpp +++ b/src/video_core/vertex_loader.cpp @@ -70,7 +70,8 @@ void VertexLoader::Setup(const Pica::Regs& regs) { is_setup = true; } -void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input, +void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, + Shader::AttributeBuffer& input, DebugUtils::MemoryAccessTracker& memory_accesses) { ASSERT_MSG(is_setup, "A VertexLoader needs to be setup before loading vertices."); @@ -142,7 +143,7 @@ void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::I input.attr[i][2].ToFloat32(), input.attr[i][3].ToFloat32()); } else if (vertex_attribute_is_default[i]) { // Load the default attribute if we're configured to do so - input.attr[i] = g_state.vs_default_attributes[i]; + input.attr[i] = g_state.input_default_attributes.attr[i]; LOG_TRACE(HW_GPU, "Loaded default attribute %x for vertex %x (index %x): (%f, %f, %f, %f)", i, vertex, index, input.attr[i][0].ToFloat32(), input.attr[i][1].ToFloat32(), diff --git a/src/video_core/vertex_loader.h b/src/video_core/vertex_loader.h index 9f2098bb2..51f3d45b4 100644 --- a/src/video_core/vertex_loader.h +++ b/src/video_core/vertex_loader.h @@ -11,7 +11,7 @@ class MemoryAccessTracker; } namespace Shader { -struct InputVertex; +struct AttributeBuffer; } class VertexLoader { @@ -22,7 +22,7 @@ public: } void Setup(const Pica::Regs& regs); - void LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input, + void LoadVertex(u32 base_address, int index, int vertex, Shader::AttributeBuffer& input, DebugUtils::MemoryAccessTracker& memory_accesses); int GetNumTotalAttributes() const { diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 8db882f59..7186a7652 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -19,7 +19,6 @@ std::unique_ptr<RendererBase> g_renderer; ///< Renderer plugin std::atomic<bool> g_hw_renderer_enabled; std::atomic<bool> g_shader_jit_enabled; -std::atomic<bool> g_scaled_resolution_enabled; std::atomic<bool> g_vsync_enabled; std::atomic<bool> g_toggle_framelimit_enabled; diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h index c397c1974..4aba19ca0 100644 --- a/src/video_core/video_core.h +++ b/src/video_core/video_core.h @@ -37,7 +37,6 @@ extern EmuWindow* g_emu_window; ///< Emu window // qt ui) extern std::atomic<bool> g_hw_renderer_enabled; extern std::atomic<bool> g_shader_jit_enabled; -extern std::atomic<bool> g_scaled_resolution_enabled; extern std::atomic<bool> g_toggle_framelimit_enabled; /// Start the video core |