diff options
Diffstat (limited to 'src')
194 files changed, 9433 insertions, 9904 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/filter.h b/src/audio_core/hle/filter.h index 4281a5898..5350e2857 100644 --- a/src/audio_core/hle/filter.h +++ b/src/audio_core/hle/filter.h @@ -27,7 +27,7 @@ public: * See also: SourceConfiguration::Configuration::simple_filter_enabled, * SourceConfiguration::Configuration::biquad_filter_enabled. * @param simple If true, enables the simple filter. If false, disables it. - * @param simple If true, enables the biquad filter. If false, disables it. + * @param biquad If true, enables the biquad filter. If false, disables it. */ void Enable(bool simple, bool biquad); 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/interpolate.h b/src/audio_core/interpolate.h index dd06fdda9..19a7b66cb 100644 --- a/src/audio_core/interpolate.h +++ b/src/audio_core/interpolate.h @@ -21,6 +21,7 @@ struct State { /** * No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay. + * @param state Interpolation state. * @param input Input buffer. * @param rate_multiplier Stretch factor. Must be a positive non-zero value. * rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 @@ -31,6 +32,7 @@ StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multip /** * Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay. + * @param state Interpolation state. * @param input Input buffer. * @param rate_multiplier Stretch factor. Must be a positive non-zero value. * rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 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..c69cb2c74 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. + * @param 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/audio_core/time_stretch.h b/src/audio_core/time_stretch.h index e3e4dc353..c98b16705 100644 --- a/src/audio_core/time_stretch.h +++ b/src/audio_core/time_stretch.h @@ -25,7 +25,7 @@ public: /** * Add samples to be processed. * @param sample_buffer Buffer of samples in interleaved stereo PCM16 format. - * @param num_sample Number of samples. + * @param num_samples Number of samples. */ void AddSamples(const s16* sample_buffer, size_t num_samples); 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 1d0faf193..fac1c9a0e 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -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 = @@ -92,6 +93,21 @@ void Config::ReadValues() { 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 7996813b4..435ba6f00 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -91,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 @@ -105,6 +109,22 @@ is_new_3ds = # -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 81a3abe3f..00d00905a 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -79,8 +79,8 @@ EmuWindow_SDL2::EmuWindow_SDL2() { SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); - std::string window_title = - Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); + std::string window_title = Common::StringFromFormat("Citra %s| %s-%s ", Common::g_build_name, + Common::g_scm_branch, Common::g_scm_desc); render_window = SDL_CreateWindow( window_title.c_str(), SDL_WINDOWPOS_UNDEFINED, // x position diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index d4460bf01..15a6ccf9a 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -69,7 +69,6 @@ set(HEADERS set(UIS debugger/callstack.ui debugger/disassembler.ui - debugger/profiler.ui debugger/registers.ui configure.ui configure_audio.ui diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 948db384d..69d18cf0c 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -101,8 +101,8 @@ private: GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) : 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); + std::string window_title = Common::StringFromFormat("Citra %s| %s-%s", Common::g_build_name, + Common::g_scm_branch, Common::g_scm_desc); setWindowTitle(QString::fromStdString(window_title)); keyboard_id = KeyMap::NewDeviceId(); diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index 8021667d0..5fe57dfa2 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -63,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"); @@ -128,6 +146,7 @@ void Config::ReadValues() { UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool(); UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool(); + UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool(); UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); @@ -169,6 +188,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"); @@ -217,6 +253,7 @@ void Config::SaveValues() { qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode); qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar); + qt_config->setValue("showStatusBar", UISettings::values.show_status_bar); qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); qt_config->setValue("firstStart", UISettings::values.first_start); 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.ui b/src/citra_qt/configure_general.ui index 0f3352a1d..c739605a4 100644 --- a/src/citra_qt/configure_general.ui +++ b/src/citra_qt/configure_general.ui @@ -27,7 +27,7 @@ <item> <widget class="QCheckBox" name="toggle_deepscan"> <property name="text"> - <string>Recursive scan for game folder</string> + <string>Search sub-directories for games</string> </property> </widget> </item> 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_system.cpp b/src/citra_qt/configure_system.cpp index eb1276ef3..040185e82 100644 --- a/src/citra_qt/configure_system.cpp +++ b/src/citra_qt/configure_system.cpp @@ -4,6 +4,7 @@ #include "citra_qt/configure_system.h" #include "citra_qt/ui_settings.h" +#include "core/core.h" #include "core/hle/service/cfg/cfg.h" #include "core/hle/service/fs/archive.h" #include "ui_configure_system.h" diff --git a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp index f5a2ec761..c68fe753b 100644 --- a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp @@ -18,15 +18,16 @@ #include "citra_qt/util/util.h" #include "common/vector_math.h" #include "video_core/debug_utils/debug_utils.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/regs.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); @@ -70,7 +72,7 @@ QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const { if (role == Qt::DisplayRole) { switch (index.column()) { case 0: - return QString::fromLatin1(Pica::Regs::GetCommandName(write.cmd_id).c_str()); + return QString::fromLatin1(Pica::Regs::GetRegisterName(write.cmd_id)); case 1: return QString("%1").arg(write.cmd_id, 3, 16, QLatin1Char('0')); case 2: @@ -121,15 +123,16 @@ void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) { const unsigned int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toUInt(); - if (COMMAND_IN_RANGE(command_id, texture0) || COMMAND_IN_RANGE(command_id, texture1) || - COMMAND_IN_RANGE(command_id, texture2)) { + if (COMMAND_IN_RANGE(command_id, texturing.texture0) || + COMMAND_IN_RANGE(command_id, texturing.texture1) || + COMMAND_IN_RANGE(command_id, texturing.texture2)) { unsigned texture_index; - if (COMMAND_IN_RANGE(command_id, texture0)) { + if (COMMAND_IN_RANGE(command_id, texturing.texture0)) { texture_index = 0; - } else if (COMMAND_IN_RANGE(command_id, texture1)) { + } else if (COMMAND_IN_RANGE(command_id, texturing.texture1)) { texture_index = 1; - } else if (COMMAND_IN_RANGE(command_id, texture2)) { + } else if (COMMAND_IN_RANGE(command_id, texturing.texture2)) { texture_index = 2; } else { UNREACHABLE_MSG("Unknown texture command"); @@ -144,23 +147,24 @@ void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) { const unsigned int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toUInt(); - if (COMMAND_IN_RANGE(command_id, texture0) || COMMAND_IN_RANGE(command_id, texture1) || - COMMAND_IN_RANGE(command_id, texture2)) { + if (COMMAND_IN_RANGE(command_id, texturing.texture0) || + COMMAND_IN_RANGE(command_id, texturing.texture1) || + COMMAND_IN_RANGE(command_id, texturing.texture2)) { unsigned texture_index; - if (COMMAND_IN_RANGE(command_id, texture0)) { + if (COMMAND_IN_RANGE(command_id, texturing.texture0)) { texture_index = 0; - } else if (COMMAND_IN_RANGE(command_id, texture1)) { + } else if (COMMAND_IN_RANGE(command_id, texturing.texture1)) { texture_index = 1; } else { texture_index = 2; } - const auto texture = Pica::g_state.regs.GetTextures()[texture_index]; + const auto texture = Pica::g_state.regs.texturing.GetTextures()[texture_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..47d9924e1 100644 --- a/src/citra_qt/debugger/graphics/graphics_surface.cpp +++ b/src/citra_qt/debugger/graphics/graphics_surface.cpp @@ -16,8 +16,10 @@ #include "common/color.h" #include "core/hw/gpu.h" #include "core/memory.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_texturing.h" +#include "video_core/texture/texture_decode.h" #include "video_core/utils.h" SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) @@ -413,30 +415,30 @@ void GraphicsSurfaceWidget::OnUpdate() { // TODO: Store a reference to the registers in the debug context instead of accessing them // directly... - const auto& framebuffer = Pica::g_state.regs.framebuffer; + const auto& framebuffer = Pica::g_state.regs.framebuffer.framebuffer; surface_address = framebuffer.GetColorBufferPhysicalAddress(); surface_width = framebuffer.GetWidth(); surface_height = framebuffer.GetHeight(); switch (framebuffer.color_format) { - case Pica::Regs::ColorFormat::RGBA8: + case Pica::FramebufferRegs::ColorFormat::RGBA8: surface_format = Format::RGBA8; break; - case Pica::Regs::ColorFormat::RGB8: + case Pica::FramebufferRegs::ColorFormat::RGB8: surface_format = Format::RGB8; break; - case Pica::Regs::ColorFormat::RGB5A1: + case Pica::FramebufferRegs::ColorFormat::RGB5A1: surface_format = Format::RGB5A1; break; - case Pica::Regs::ColorFormat::RGB565: + case Pica::FramebufferRegs::ColorFormat::RGB565: surface_format = Format::RGB565; break; - case Pica::Regs::ColorFormat::RGBA4: + case Pica::FramebufferRegs::ColorFormat::RGBA4: surface_format = Format::RGBA4; break; @@ -449,22 +451,22 @@ void GraphicsSurfaceWidget::OnUpdate() { } case Source::DepthBuffer: { - const auto& framebuffer = Pica::g_state.regs.framebuffer; + const auto& framebuffer = Pica::g_state.regs.framebuffer.framebuffer; surface_address = framebuffer.GetDepthBufferPhysicalAddress(); surface_width = framebuffer.GetWidth(); surface_height = framebuffer.GetHeight(); switch (framebuffer.depth_format) { - case Pica::Regs::DepthFormat::D16: + case Pica::FramebufferRegs::DepthFormat::D16: surface_format = Format::D16; break; - case Pica::Regs::DepthFormat::D24: + case Pica::FramebufferRegs::DepthFormat::D24: surface_format = Format::D24; break; - case Pica::Regs::DepthFormat::D24S8: + case Pica::FramebufferRegs::DepthFormat::D24S8: surface_format = Format::D24X8; break; @@ -477,14 +479,14 @@ void GraphicsSurfaceWidget::OnUpdate() { } case Source::StencilBuffer: { - const auto& framebuffer = Pica::g_state.regs.framebuffer; + const auto& framebuffer = Pica::g_state.regs.framebuffer.framebuffer; surface_address = framebuffer.GetDepthBufferPhysicalAddress(); surface_width = framebuffer.GetWidth(); surface_height = framebuffer.GetHeight(); switch (framebuffer.depth_format) { - case Pica::Regs::DepthFormat::D24S8: + case Pica::FramebufferRegs::DepthFormat::D24S8: surface_format = Format::X24S8; break; @@ -511,8 +513,8 @@ void GraphicsSurfaceWidget::OnUpdate() { break; } - const auto texture = Pica::g_state.regs.GetTextures()[texture_index]; - auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(texture.config, texture.format); + const auto texture = Pica::g_state.regs.texturing.GetTextures()[texture_index]; + auto info = Pica::Texture::TextureInfo::FromPicaRegister(texture.config, texture.format); surface_address = info.physical_address; surface_width = info.width; @@ -567,28 +569,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.format = static_cast<Pica::TexturingRegs::TextureFormat>(surface_format); + 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"); @@ -689,7 +690,8 @@ void GraphicsSurfaceWidget::SaveSurface() { unsigned int GraphicsSurfaceWidget::NibblesPerPixel(GraphicsSurfaceWidget::Format format) { if (format <= Format::MaxTextureFormat) { - return Pica::Regs::NibblesPerPixel(static_cast<Pica::Regs::TextureFormat>(format)); + return Pica::TexturingRegs::NibblesPerPixel( + static_cast<Pica::TexturingRegs::TextureFormat>(format)); } switch (format) { diff --git a/src/citra_qt/debugger/graphics/graphics_tracing.cpp b/src/citra_qt/debugger/graphics/graphics_tracing.cpp index 716ed50b8..40d5bed51 100644 --- a/src/citra_qt/debugger/graphics/graphics_tracing.cpp +++ b/src/citra_qt/debugger/graphics/graphics_tracing.cpp @@ -18,7 +18,6 @@ #include "core/hw/lcd.h" #include "core/tracer/recorder.h" #include "nihstro/float24.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" GraphicsTracingWidget::GraphicsTracingWidget(std::shared_ptr<Pica::DebugContext> debug_context, @@ -71,8 +70,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_tracing.h b/src/citra_qt/debugger/graphics/graphics_tracing.h index 3f73bcd2e..eb1292c29 100644 --- a/src/citra_qt/debugger/graphics/graphics_tracing.h +++ b/src/citra_qt/debugger/graphics/graphics_tracing.h @@ -15,6 +15,9 @@ public: explicit GraphicsTracingWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr); + void OnEmulationStarting(EmuThread* emu_thread); + void OnEmulationStopping(); + private slots: void StartRecording(); void StopRecording(); @@ -23,9 +26,6 @@ private slots: void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override; void OnResumed() override; - void OnEmulationStarting(EmuThread* emu_thread); - void OnEmulationStopping(); - signals: void SetStartTracingButtonEnabled(bool enable); void SetStopTracingButtonEnabled(bool enable); diff --git a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp index ff2e7e363..e3f3194db 100644 --- a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp +++ b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp @@ -16,9 +16,10 @@ #include <QTreeView> #include "citra_qt/debugger/graphics/graphics_vertex_shader.h" #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; @@ -357,7 +358,7 @@ void GraphicsVertexShaderWidget::DumpShader() { auto& config = Pica::g_state.regs.vs; Pica::DebugUtils::DumpShader(filename.toStdString(), config, setup, - Pica::g_state.regs.vs_output_attributes); + Pica::g_state.regs.rasterizer.vs_output_attributes); } GraphicsVertexShaderWidget::GraphicsVertexShaderWidget( @@ -509,7 +510,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}); @@ -518,12 +519,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/profiler.cpp b/src/citra_qt/debugger/profiler.cpp index cee10403d..f060bbe08 100644 --- a/src/citra_qt/debugger/profiler.cpp +++ b/src/citra_qt/debugger/profiler.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QAction> +#include <QLayout> #include <QMouseEvent> #include <QPainter> #include <QString> @@ -9,121 +11,12 @@ #include "citra_qt/util/util.h" #include "common/common_types.h" #include "common/microprofile.h" -#include "common/profiler_reporting.h" // Include the implementation of the UI in this file. This isn't in microprofile.cpp because the // non-Qt frontends don't need it (and don't implement the UI drawing hooks either). #if MICROPROFILE_ENABLED #define MICROPROFILEUI_IMPL 1 #include "common/microprofileui.h" -#endif - -using namespace Common::Profiling; - -static QVariant GetDataForColumn(int col, const AggregatedDuration& duration) { - static auto duration_to_float = [](Duration dur) -> float { - using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>; - return std::chrono::duration_cast<FloatMs>(dur).count(); - }; - - switch (col) { - case 1: - return duration_to_float(duration.avg); - case 2: - return duration_to_float(duration.min); - case 3: - return duration_to_float(duration.max); - default: - return QVariant(); - } -} - -ProfilerModel::ProfilerModel(QObject* parent) : QAbstractItemModel(parent) { - updateProfilingInfo(); -} - -QVariant ProfilerModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { - switch (section) { - case 0: - return tr("Category"); - case 1: - return tr("Avg"); - case 2: - return tr("Min"); - case 3: - return tr("Max"); - } - } - - return QVariant(); -} - -QModelIndex ProfilerModel::index(int row, int column, const QModelIndex& parent) const { - return createIndex(row, column); -} - -QModelIndex ProfilerModel::parent(const QModelIndex& child) const { - return QModelIndex(); -} - -int ProfilerModel::columnCount(const QModelIndex& parent) const { - return 4; -} - -int ProfilerModel::rowCount(const QModelIndex& parent) const { - if (parent.isValid()) { - return 0; - } else { - return 2; - } -} - -QVariant ProfilerModel::data(const QModelIndex& index, int role) const { - if (role == Qt::DisplayRole) { - if (index.row() == 0) { - if (index.column() == 0) { - return tr("Frame"); - } else { - return GetDataForColumn(index.column(), results.frame_time); - } - } else if (index.row() == 1) { - if (index.column() == 0) { - return tr("Frame (with swapping)"); - } else { - return GetDataForColumn(index.column(), results.interframe_time); - } - } - } - - return QVariant(); -} - -void ProfilerModel::updateProfilingInfo() { - results = GetTimingResultsAggregator()->GetAggregatedResults(); - emit dataChanged(createIndex(0, 1), createIndex(rowCount() - 1, 3)); -} - -ProfilerWidget::ProfilerWidget(QWidget* parent) : QDockWidget(parent) { - ui.setupUi(this); - - model = new ProfilerModel(this); - ui.treeView->setModel(model); - - connect(this, SIGNAL(visibilityChanged(bool)), SLOT(setProfilingInfoUpdateEnabled(bool))); - connect(&update_timer, SIGNAL(timeout()), model, SLOT(updateProfilingInfo())); -} - -void ProfilerWidget::setProfilingInfoUpdateEnabled(bool enable) { - if (enable) { - update_timer.start(100); - model->updateProfilingInfo(); - } else { - update_timer.stop(); - } -} - -#if MICROPROFILE_ENABLED class MicroProfileWidget : public QWidget { public: diff --git a/src/citra_qt/debugger/profiler.h b/src/citra_qt/debugger/profiler.h index c8912fd5a..eae1e9e3c 100644 --- a/src/citra_qt/debugger/profiler.h +++ b/src/citra_qt/debugger/profiler.h @@ -8,46 +8,6 @@ #include <QDockWidget> #include <QTimer> #include "common/microprofile.h" -#include "common/profiler_reporting.h" -#include "ui_profiler.h" - -class ProfilerModel : public QAbstractItemModel { - Q_OBJECT - -public: - explicit ProfilerModel(QObject* parent); - - QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, - const QModelIndex& parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex& child) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - -public slots: - void updateProfilingInfo(); - -private: - Common::Profiling::AggregatedFrameResult results; -}; - -class ProfilerWidget : public QDockWidget { - Q_OBJECT - -public: - explicit ProfilerWidget(QWidget* parent = nullptr); - -private slots: - void setProfilingInfoUpdateEnabled(bool enable); - -private: - Ui::Profiler ui; - ProfilerModel* model; - - QTimer update_timer; -}; class MicroProfileDialog : public QWidget { Q_OBJECT diff --git a/src/citra_qt/debugger/profiler.ui b/src/citra_qt/debugger/profiler.ui deleted file mode 100644 index d3c9a9a1f..000000000 --- a/src/citra_qt/debugger/profiler.ui +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>Profiler</class> - <widget class="QDockWidget" name="Profiler"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>400</width> - <height>300</height> - </rect> - </property> - <property name="windowTitle"> - <string>Profiler</string> - </property> - <widget class="QWidget" name="dockWidgetContents"> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QTreeView" name="treeView"> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <property name="uniformRowHeights"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </widget> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index 09469f3c5..a9ec9e830 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QFileInfo> #include <QHeaderView> #include <QMenu> #include <QThreadPool> @@ -38,11 +39,13 @@ GameList::GameList(QWidget* parent) : QWidget{parent} { connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); + connect(&watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); // We must register all custom types with the Qt Automoc system so that we are able to use it // with signals/slots. In this case, QList falls under the umbrells of custom types. qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); + layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(tree_view); setLayout(layout); } @@ -102,6 +105,12 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { item_model->removeRows(0, item_model->rowCount()); emit ShouldCancelWorker(); + + auto watch_dirs = watcher.directories(); + if (!watch_dirs.isEmpty()) { + watcher.removePaths(watch_dirs); + } + UpdateWatcherList(dir_path.toStdString(), deep_scan ? 256 : 0); GameListWorker* worker = new GameListWorker(dir_path, deep_scan); connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); @@ -131,6 +140,53 @@ void GameList::LoadInterfaceLayout() { item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); } +const QStringList GameList::supported_file_extensions = {"3ds", "3dsx", "elf", "axf", + "cci", "cxi", "app"}; + +static bool HasSupportedFileExtension(const std::string& file_name) { + QFileInfo file = QFileInfo(file_name.c_str()); + return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); +} + +void GameList::RefreshGameDirectory() { + if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { + LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); + PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); + } +} + +/** + * Adds the game list folder to the QFileSystemWatcher to check for updates. + * + * The file watcher will fire off an update to the game list when a change is detected in the game + * list folder. + * + * Notice: This method is run on the UI thread because QFileSystemWatcher is not thread safe and + * this function is fast enough to not stall the UI thread. If performance is an issue, it should + * be moved to another thread and properly locked to prevent concurrency issues. + * + * @param dir folder to check for changes in + * @param recursion 0 if recursion is disabled. Any positive number passed to this will add each + * directory recursively to the watcher and will update the file list if any of the folders + * change. The number determines how deep the recursion should traverse. + */ +void GameList::UpdateWatcherList(const std::string& dir, unsigned int recursion) { + const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { + std::string physical_name = directory + DIR_SEP + virtual_name; + + if (FileUtil::IsDirectory(physical_name)) { + UpdateWatcherList(physical_name, recursion - 1); + } + return true; + }; + + watcher.addPath(QString::fromStdString(dir)); + if (recursion > 0) { + FileUtil::ForeachDirectoryEntry(nullptr, dir, callback); + } +} + void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, const std::string& virtual_name) -> bool { @@ -139,7 +195,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign if (stop_processing) return false; // Breaks the callback loop. - if (!FileUtil::IsDirectory(physical_name)) { + if (!FileUtil::IsDirectory(physical_name) && HasSupportedFileExtension(physical_name)) { std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name); if (!loader) return true; @@ -173,6 +229,6 @@ void GameListWorker::run() { } void GameListWorker::Cancel() { - disconnect(this, nullptr, nullptr, nullptr); + this->disconnect(); stop_processing = true; } diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index 1abf10051..b141fa3a5 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -4,6 +4,7 @@ #pragma once +#include <QFileSystemWatcher> #include <QModelIndex> #include <QSettings> #include <QStandardItem> @@ -33,6 +34,8 @@ public: void SaveInterfaceLayout(); void LoadInterfaceLayout(); + static const QStringList supported_file_extensions; + signals: void GameChosen(QString game_path); void ShouldCancelWorker(); @@ -44,8 +47,11 @@ private: void DonePopulating(); void PopupContextMenu(const QPoint& menu_location); + void UpdateWatcherList(const std::string& path, unsigned int recursion); + void RefreshGameDirectory(); QTreeView* tree_view = nullptr; QStandardItemModel* item_model = nullptr; GameListWorker* current_worker = nullptr; + QFileSystemWatcher watcher; }; diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index a15f06c5f..3c11b6dd1 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -16,8 +16,8 @@ #include "video_core/utils.h" /** - * Gets game icon from SMDH - * @param sdmh SMDH data + * Gets the game icon from SMDH data. + * @param smdh SMDH data * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) * @return QPixmap game icon */ @@ -42,8 +42,8 @@ static QPixmap GetDefaultIcon(bool large) { } /** - * Gets the short game title fromn SMDH - * @param sdmh SMDH data + * Gets the short game title from SMDH data. + * @param smdh SMDH data * @param language title language * @return QString short title */ diff --git a/src/citra_qt/hotkeys.h b/src/citra_qt/hotkeys.h index 46f48c2d8..a4ccc193b 100644 --- a/src/citra_qt/hotkeys.h +++ b/src/citra_qt/hotkeys.h @@ -29,6 +29,8 @@ void RegisterHotkey(const QString& group, const QString& action, /** * Returns a QShortcut object whose activated() signal can be connected to other QObjects' slots. * + * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger"). + * @param action Name of the action (e.g. "Start Emulation", "Load Image"). * @param widget Parent widget of the returned QShortcut. * @warning If multiple QWidgets' call this function for the same action, the returned QShortcut * will be the same. Thus, you shouldn't rely on the caller really being the QShortcut's parent. diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index f765c0147..fd51659b9 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -54,28 +54,30 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { Pica::g_debug_context = Pica::DebugContext::Construct(); - + setAcceptDrops(true); ui.setupUi(this); statusBar()->hide(); InitializeWidgets(); - InitializeDebugMenuActions(); + InitializeDebugWidgets(); InitializeRecentFileMenuActions(); InitializeHotkeys(); SetDefaultUIGeometry(); RestoreUIState(); + ConnectMenuEvents(); ConnectWidgetEvents(); - setWindowTitle(QString("Citra | %1-%2").arg(Common::g_scm_branch, Common::g_scm_desc)); + setWindowTitle(QString("Citra %1| %2-%3") + .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); show(); game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); QStringList args = QApplication::arguments(); if (args.length() >= 2) { - BootGame(args[1].toStdString()); + BootGame(args[1]); } } @@ -94,73 +96,99 @@ void GMainWindow::InitializeWidgets() { game_list = new GameList(); ui.horizontalLayout->addWidget(game_list); - profilerWidget = new ProfilerWidget(this); - addDockWidget(Qt::BottomDockWidgetArea, profilerWidget); - profilerWidget->hide(); + // Create status bar + emu_speed_label = new QLabel(); + emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% " + "indicate emulation is running faster or slower than a 3DS.")); + game_fps_label = new QLabel(); + game_fps_label->setToolTip(tr("How many frames per second the game is currently displaying. " + "This will vary from game to game and scene to scene.")); + emu_frametime_label = new QLabel(); + emu_frametime_label->setToolTip( + tr("Time taken to emulate a 3DS frame, not counting framelimiting or v-sync. For " + "full-speed emulation this should be at most 16.67 ms.")); + + for (auto& label : {emu_speed_label, game_fps_label, emu_frametime_label}) { + label->setVisible(false); + label->setFrameStyle(QFrame::NoFrame); + label->setContentsMargins(4, 0, 4, 0); + statusBar()->addPermanentWidget(label); + } + statusBar()->setVisible(true); +} + +void GMainWindow::InitializeDebugWidgets() { + connect(ui.action_Create_Pica_Surface_Viewer, &QAction::triggered, this, + &GMainWindow::OnCreateGraphicsSurfaceViewer); + + QMenu* debug_menu = ui.menu_View_Debugging; #if MICROPROFILE_ENABLED microProfileDialog = new MicroProfileDialog(this); microProfileDialog->hide(); + debug_menu->addAction(microProfileDialog->toggleViewAction()); #endif disasmWidget = new DisassemblerWidget(this, emu_thread.get()); addDockWidget(Qt::BottomDockWidgetArea, disasmWidget); disasmWidget->hide(); + debug_menu->addAction(disasmWidget->toggleViewAction()); + connect(this, &GMainWindow::EmulationStarting, disasmWidget, + &DisassemblerWidget::OnEmulationStarting); + connect(this, &GMainWindow::EmulationStopping, disasmWidget, + &DisassemblerWidget::OnEmulationStopping); registersWidget = new RegistersWidget(this); addDockWidget(Qt::RightDockWidgetArea, registersWidget); registersWidget->hide(); + debug_menu->addAction(registersWidget->toggleViewAction()); + connect(this, &GMainWindow::EmulationStarting, registersWidget, + &RegistersWidget::OnEmulationStarting); + connect(this, &GMainWindow::EmulationStopping, registersWidget, + &RegistersWidget::OnEmulationStopping); callstackWidget = new CallstackWidget(this); addDockWidget(Qt::RightDockWidgetArea, callstackWidget); callstackWidget->hide(); + debug_menu->addAction(callstackWidget->toggleViewAction()); graphicsWidget = new GPUCommandStreamWidget(this); addDockWidget(Qt::RightDockWidgetArea, graphicsWidget); graphicsWidget->hide(); + debug_menu->addAction(graphicsWidget->toggleViewAction()); graphicsCommandsWidget = new GPUCommandListWidget(this); addDockWidget(Qt::RightDockWidgetArea, graphicsCommandsWidget); graphicsCommandsWidget->hide(); + debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(Pica::g_debug_context, this); addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); graphicsBreakpointsWidget->hide(); + debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); graphicsVertexShaderWidget = new GraphicsVertexShaderWidget(Pica::g_debug_context, this); addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget); graphicsVertexShaderWidget->hide(); + debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction()); graphicsTracingWidget = new GraphicsTracingWidget(Pica::g_debug_context, this); addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget); graphicsTracingWidget->hide(); + debug_menu->addAction(graphicsTracingWidget->toggleViewAction()); + connect(this, &GMainWindow::EmulationStarting, graphicsTracingWidget, + &GraphicsTracingWidget::OnEmulationStarting); + connect(this, &GMainWindow::EmulationStopping, graphicsTracingWidget, + &GraphicsTracingWidget::OnEmulationStopping); waitTreeWidget = new WaitTreeWidget(this); addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget); waitTreeWidget->hide(); -} - -void GMainWindow::InitializeDebugMenuActions() { - auto graphicsSurfaceViewerAction = new QAction(tr("Create Pica Surface Viewer"), this); - connect(graphicsSurfaceViewerAction, SIGNAL(triggered()), this, - SLOT(OnCreateGraphicsSurfaceViewer())); - - QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); - debug_menu->addAction(graphicsSurfaceViewerAction); - debug_menu->addSeparator(); - debug_menu->addAction(profilerWidget->toggleViewAction()); -#if MICROPROFILE_ENABLED - debug_menu->addAction(microProfileDialog->toggleViewAction()); -#endif - debug_menu->addAction(disasmWidget->toggleViewAction()); - debug_menu->addAction(registersWidget->toggleViewAction()); - debug_menu->addAction(callstackWidget->toggleViewAction()); - debug_menu->addAction(graphicsWidget->toggleViewAction()); - debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); - debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); - debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction()); - debug_menu->addAction(graphicsTracingWidget->toggleViewAction()); debug_menu->addAction(waitTreeWidget->toggleViewAction()); + connect(this, &GMainWindow::EmulationStarting, waitTreeWidget, + &WaitTreeWidget::OnEmulationStarting); + connect(this, &GMainWindow::EmulationStopping, waitTreeWidget, + &WaitTreeWidget::OnEmulationStopping); } void GMainWindow::InitializeRecentFileMenuActions() { @@ -215,41 +243,46 @@ void GMainWindow::RestoreUIState() { ui.action_Single_Window_Mode->setChecked(UISettings::values.single_window_mode); ToggleWindowMode(); - ui.actionDisplay_widget_title_bars->setChecked(UISettings::values.display_titlebar); - OnDisplayTitleBars(ui.actionDisplay_widget_title_bars->isChecked()); + ui.action_Display_Dock_Widget_Headers->setChecked(UISettings::values.display_titlebar); + OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked()); + + ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar); + statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); } void GMainWindow::ConnectWidgetEvents() { - connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)), - Qt::DirectConnection); + connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString))); connect(game_list, SIGNAL(OpenSaveFolderRequested(u64)), this, - SLOT(OnGameListOpenSaveFolder(u64)), Qt::DirectConnection); - connect(ui.action_Configure, SIGNAL(triggered()), this, SLOT(OnConfigure())); - connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile()), - Qt::DirectConnection); - connect(ui.action_Load_Symbol_Map, SIGNAL(triggered()), this, SLOT(OnMenuLoadSymbolMap())); - connect(ui.action_Select_Game_List_Root, SIGNAL(triggered()), this, - SLOT(OnMenuSelectGameListRoot())); - connect(ui.action_Start, SIGNAL(triggered()), this, SLOT(OnStartGame())); - connect(ui.action_Pause, SIGNAL(triggered()), this, SLOT(OnPauseGame())); - connect(ui.action_Stop, SIGNAL(triggered()), this, SLOT(OnStopGame())); - connect(ui.action_Single_Window_Mode, SIGNAL(triggered(bool)), this, SLOT(ToggleWindowMode())); - - connect(this, SIGNAL(EmulationStarting(EmuThread*)), disasmWidget, - SLOT(OnEmulationStarting(EmuThread*))); - connect(this, SIGNAL(EmulationStopping()), disasmWidget, SLOT(OnEmulationStopping())); - connect(this, SIGNAL(EmulationStarting(EmuThread*)), registersWidget, - SLOT(OnEmulationStarting(EmuThread*))); - connect(this, SIGNAL(EmulationStopping()), registersWidget, SLOT(OnEmulationStopping())); + SLOT(OnGameListOpenSaveFolder(u64))); + connect(this, SIGNAL(EmulationStarting(EmuThread*)), render_window, SLOT(OnEmulationStarting(EmuThread*))); connect(this, SIGNAL(EmulationStopping()), render_window, SLOT(OnEmulationStopping())); - connect(this, SIGNAL(EmulationStarting(EmuThread*)), graphicsTracingWidget, - SLOT(OnEmulationStarting(EmuThread*))); - connect(this, SIGNAL(EmulationStopping()), graphicsTracingWidget, SLOT(OnEmulationStopping())); - connect(this, SIGNAL(EmulationStarting(EmuThread*)), waitTreeWidget, - SLOT(OnEmulationStarting(EmuThread*))); - connect(this, SIGNAL(EmulationStopping()), waitTreeWidget, SLOT(OnEmulationStopping())); + + connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar); +} + +void GMainWindow::ConnectMenuEvents() { + // File + connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile); + connect(ui.action_Load_Symbol_Map, &QAction::triggered, this, + &GMainWindow::OnMenuLoadSymbolMap); + connect(ui.action_Select_Game_List_Root, &QAction::triggered, this, + &GMainWindow::OnMenuSelectGameListRoot); + connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close); + + // Emulation + connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame); + connect(ui.action_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame); + connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame); + connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); + + // View + connect(ui.action_Single_Window_Mode, &QAction::triggered, this, + &GMainWindow::ToggleWindowMode); + connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this, + &GMainWindow::OnDisplayTitleBars); + connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); } void GMainWindow::OnDisplayTitleBars(bool show) { @@ -272,7 +305,7 @@ void GMainWindow::OnDisplayTitleBars(bool show) { } } -bool GMainWindow::LoadROM(const std::string& filename) { +bool GMainWindow::LoadROM(const QString& filename) { // Shutdown previous session if the emu thread is still active... if (emu_thread != nullptr) ShutdownGame(); @@ -290,12 +323,13 @@ bool GMainWindow::LoadROM(const std::string& filename) { Core::System& system{Core::System::GetInstance()}; - const Core::System::ResultStatus result{system.Load(render_window, filename)}; + const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())}; if (result != Core::System::ResultStatus::Success) { switch (result) { case Core::System::ResultStatus::ErrorGetLoader: - LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", filename.c_str()); + LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", + filename.toStdString().c_str()); QMessageBox::critical(this, tr("Error while loading ROM!"), tr("The ROM format is not supported.")); break; @@ -335,7 +369,7 @@ bool GMainWindow::LoadROM(const std::string& filename) { return true; } -void GMainWindow::BootGame(const std::string& filename) { +void GMainWindow::BootGame(const QString& filename) { LOG_INFO(Frontend, "Citra starting..."); StoreRecentFile(filename); // Put the filename on top of the list @@ -374,6 +408,8 @@ void GMainWindow::BootGame(const std::string& filename) { if (ui.action_Single_Window_Mode->isChecked()) { game_list->hide(); } + status_bar_update_timer.start(2000); + render_window->show(); render_window->setFocus(); @@ -408,11 +444,17 @@ void GMainWindow::ShutdownGame() { render_window->hide(); game_list->show(); + // Disable status bar updates + status_bar_update_timer.stop(); + emu_speed_label->setVisible(false); + game_fps_label->setVisible(false); + emu_frametime_label->setVisible(false); + emulation_running = false; } -void GMainWindow::StoreRecentFile(const std::string& filename) { - UISettings::values.recent_files.prepend(QString::fromStdString(filename)); +void GMainWindow::StoreRecentFile(const QString& filename) { + UISettings::values.recent_files.prepend(filename); UISettings::values.recent_files.removeDuplicates(); while (UISettings::values.recent_files.size() > max_recent_files_item) { UISettings::values.recent_files.removeLast(); @@ -447,7 +489,7 @@ void GMainWindow::UpdateRecentFiles() { } void GMainWindow::OnGameListLoadFile(QString game_path) { - BootGame(game_path.toStdString()); + BootGame(game_path); } void GMainWindow::OnGameListOpenSaveFolder(u64 program_id) { @@ -466,19 +508,25 @@ void GMainWindow::OnGameListOpenSaveFolder(u64 program_id) { } void GMainWindow::OnMenuLoadFile() { - QString filename = - QFileDialog::getOpenFileName(this, tr("Load File"), UISettings::values.roms_path, - tr("3DS executable (*.3ds *.3dsx *.elf *.axf *.cci *.cxi)")); + QString extensions; + for (const auto& piece : game_list->supported_file_extensions) + extensions += "*." + piece + " "; + + QString file_filter = tr("3DS Executable") + " (" + extensions + ")"; + file_filter += ";;" + tr("All Files (*.*)"); + + QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), + UISettings::values.roms_path, file_filter); if (!filename.isEmpty()) { UISettings::values.roms_path = QFileInfo(filename).path(); - BootGame(filename.toStdString()); + BootGame(filename); } } void GMainWindow::OnMenuLoadSymbolMap() { QString filename = QFileDialog::getOpenFileName( - this, tr("Load Symbol Map"), UISettings::values.symbols_path, tr("Symbol map (*)")); + this, tr("Load Symbol Map"), UISettings::values.symbols_path, tr("Symbol Map (*.*)")); if (!filename.isEmpty()) { UISettings::values.symbols_path = QFileInfo(filename).path(); @@ -501,7 +549,7 @@ void GMainWindow::OnMenuRecentFile() { QString filename = action->data().toString(); QFileInfo file_info(filename); if (file_info.exists()) { - BootGame(filename.toStdString()); + BootGame(filename); } else { // Display an error message and remove the file from the list. QMessageBox::information(this, tr("File not found"), @@ -581,6 +629,23 @@ void GMainWindow::OnCreateGraphicsSurfaceViewer() { graphicsSurfaceViewerWidget->show(); } +void GMainWindow::UpdateStatusBar() { + if (emu_thread == nullptr) { + status_bar_update_timer.stop(); + return; + } + + auto results = Core::System::GetInstance().GetAndResetPerfStats(); + + emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0)); + game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0)); + emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2)); + + emu_speed_label->setVisible(true); + game_fps_label->setVisible(true); + emu_frametime_label->setVisible(true); +} + bool GMainWindow::ConfirmClose() { if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) return true; @@ -605,7 +670,8 @@ void GMainWindow::closeEvent(QCloseEvent* event) { UISettings::values.microprofile_visible = microProfileDialog->isVisible(); #endif UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); - UISettings::values.display_titlebar = ui.actionDisplay_widget_title_bars->isChecked(); + UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); + UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked(); UISettings::values.first_start = false; game_list->SaveInterfaceLayout(); @@ -620,6 +686,40 @@ void GMainWindow::closeEvent(QCloseEvent* event) { QWidget::closeEvent(event); } +static bool IsSingleFileDropEvent(QDropEvent* event) { + const QMimeData* mimeData = event->mimeData(); + return mimeData->hasUrls() && mimeData->urls().length() == 1; +} + +void GMainWindow::dropEvent(QDropEvent* event) { + if (IsSingleFileDropEvent(event) && ConfirmChangeGame()) { + const QMimeData* mimeData = event->mimeData(); + QString filename = mimeData->urls().at(0).toLocalFile(); + BootGame(filename); + } +} + +void GMainWindow::dragEnterEvent(QDragEnterEvent* event) { + if (IsSingleFileDropEvent(event)) { + event->acceptProposedAction(); + } +} + +void GMainWindow::dragMoveEvent(QDragMoveEvent* event) { + event->acceptProposedAction(); +} + +bool GMainWindow::ConfirmChangeGame() { + if (emu_thread == nullptr) + return true; + + auto answer = QMessageBox::question( + this, tr("Citra"), + tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + return answer != QMessageBox::No; +} + #ifdef main #undef main #endif diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index a2fd45c47..ec841eaa5 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -64,7 +64,7 @@ signals: private: void InitializeWidgets(); - void InitializeDebugMenuActions(); + void InitializeDebugWidgets(); void InitializeRecentFileMenuActions(); void InitializeHotkeys(); @@ -72,15 +72,10 @@ private: void RestoreUIState(); void ConnectWidgetEvents(); + void ConnectMenuEvents(); - /** - * Initializes the emulation system. - * @param system_mode The system mode with which to intialize the kernel. - * @returns Whether the system was properly initialized. - */ - bool InitializeSystem(u32 system_mode); - bool LoadROM(const std::string& filename); - void BootGame(const std::string& filename); + bool LoadROM(const QString& filename); + void BootGame(const QString& filename); void ShutdownGame(); /** @@ -94,7 +89,7 @@ private: * * @param filename the filename to store */ - void StoreRecentFile(const std::string& filename); + void StoreRecentFile(const QString& filename); /** * Updates the recent files menu. @@ -110,6 +105,7 @@ private: * @return true if the user confirmed */ bool ConfirmClose(); + bool ConfirmChangeGame(); void closeEvent(QCloseEvent* event) override; private slots: @@ -131,17 +127,26 @@ private slots: void OnCreateGraphicsSurfaceViewer(); private: + void UpdateStatusBar(); + Ui::MainWindow ui; GRenderWindow* render_window; GameList* game_list; + // Status bar elements + QLabel* emu_speed_label = nullptr; + QLabel* game_fps_label = nullptr; + QLabel* emu_frametime_label = nullptr; + QTimer status_bar_update_timer; + std::unique_ptr<Config> config; // Whether emulation is currently running in Citra. bool emulation_running = false; std::unique_ptr<EmuThread> emu_thread; + // Debugger panes ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; DisassemblerWidget* disasmWidget; @@ -155,6 +160,11 @@ private: WaitTreeWidget* waitTreeWidget; QAction* actions_recent_files[max_recent_files_item]; + +protected: + void dropEvent(QDropEvent* event) override; + void dragEnterEvent(QDragEnterEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; }; #endif // _CITRA_QT_MAIN_HXX_ diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index adfa3689e..47dbb6ef7 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -79,8 +79,17 @@ <property name="title"> <string>&View</string> </property> + <widget class="QMenu" name="menu_View_Debugging"> + <property name="title"> + <string>Debugging</string> + </property> + <addaction name="action_Create_Pica_Surface_Viewer"/> + <addaction name="separator"/> + </widget> <addaction name="action_Single_Window_Mode"/> - <addaction name="actionDisplay_widget_title_bars"/> + <addaction name="action_Display_Dock_Widget_Headers"/> + <addaction name="action_Show_Status_Bar"/> + <addaction name="menu_View_Debugging"/> </widget> <widget class="QMenu" name="menu_Help"> <property name="title"> @@ -93,7 +102,6 @@ <addaction name="menu_View"/> <addaction name="menu_Help"/> </widget> - <widget class="QStatusBar" name="statusbar"/> <action name="action_Load_File"> <property name="text"> <string>Load File...</string> @@ -151,7 +159,7 @@ <string>Configure...</string> </property> </action> - <action name="actionDisplay_widget_title_bars"> + <action name="action_Display_Dock_Widget_Headers"> <property name="checkable"> <bool>true</bool> </property> @@ -159,6 +167,14 @@ <string>Display Dock Widget Headers</string> </property> </action> + <action name="action_Show_Status_Bar"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="text"> + <string>Show Status Bar</string> + </property> + </action> <action name="action_Select_Game_List_Root"> <property name="text"> <string>Select Game Directory...</string> @@ -167,44 +183,11 @@ <string>Selects a folder to display in the game list</string> </property> </action> + <action name="action_Create_Pica_Surface_Viewer"> + <property name="text"> + <string>Create Pica Surface Viewer</string> + </property> + </action> </widget> <resources/> - <connections> - <connection> - <sender>action_Exit</sender> - <signal>triggered()</signal> - <receiver>MainWindow</receiver> - <slot>close()</slot> - <hints> - <hint type="sourcelabel"> - <x>-1</x> - <y>-1</y> - </hint> - <hint type="destinationlabel"> - <x>367</x> - <y>314</y> - </hint> - </hints> - </connection> - <connection> - <sender>actionDisplay_widget_title_bars</sender> - <signal>triggered(bool)</signal> - <receiver>MainWindow</receiver> - <slot>OnDisplayTitleBars(bool)</slot> - <hints> - <hint type="sourcelabel"> - <x>-1</x> - <y>-1</y> - </hint> - <hint type="destinationlabel"> - <x>540</x> - <y>364</y> - </hint> - </hints> - </connection> - </connections> - <slots> - <slot>OnConfigure()</slot> - <slot>OnDisplayTitleBars(bool)</slot> - </slots> </ui> diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h index ed7fdff7e..6408ece2b 100644 --- a/src/citra_qt/ui_settings.h +++ b/src/citra_qt/ui_settings.h @@ -27,6 +27,7 @@ struct Values { bool single_window_mode; bool display_titlebar; + bool show_status_bar; bool confirm_before_closing; bool first_start; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index a7a4a688c..8a6170257 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,4 +1,27 @@ # Generate cpp with Git revision from template +# Also if this is a CI build, add the build name (ie: Nightly, Bleeding Edge) to the scm_rev file as well +set(REPO_NAME "") +if ($ENV{CI}) + if ($ENV{TRAVIS}) + set(BUILD_REPOSITORY $ENV{TRAVIS_REPO_SLUG}) + elseif($ENV{APPVEYOR}) + set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME}) + endif() + # regex capture the string nightly or bleeding-edge into CMAKE_MATCH_1 + string(REGEX MATCH "citra-emu/citra-?(.*)" OUTVAR ${BUILD_REPOSITORY}) + if (${CMAKE_MATCH_COUNT} GREATER 0) + # capitalize the first letter of each word in the repo name. + string(REPLACE "-" ";" REPO_NAME_LIST ${CMAKE_MATCH_1}) + foreach(WORD ${REPO_NAME_LIST}) + string(SUBSTRING ${WORD} 0 1 FIRST_LETTER) + string(SUBSTRING ${WORD} 1 -1 REMAINDER) + string(TOUPPER ${FIRST_LETTER} FIRST_LETTER) + # this leaves a trailing space on the last word, but we actually want that + # because of how its styled in the title bar. + set(REPO_NAME "${REPO_NAME}${FIRST_LETTER}${REMAINDER} ") + endforeach() + endif() +endif() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp" @ONLY) set(SRCS @@ -12,7 +35,6 @@ set(SRCS memory_util.cpp microprofile.cpp misc.cpp - profiler.cpp scm_rev.cpp string_util.cpp symbols.cpp @@ -45,7 +67,6 @@ set(HEADERS microprofile.h microprofileui.h platform.h - profiler_reporting.h quaternion.h scm_rev.h scope_exit.h @@ -61,14 +82,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/common_paths.h b/src/common/common_paths.h index b56105306..d5b510cdb 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -45,3 +45,4 @@ // Sys files #define SHARED_FONT "shared_font.bin" +#define AES_KEYS "aes_keys.txt" 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 2ef3e6b05..737e1d57f 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -55,6 +55,7 @@ namespace Log { SUB(Service, DSP) \ SUB(Service, DLP) \ SUB(Service, HID) \ + SUB(Service, HTTP) \ SUB(Service, SOC) \ SUB(Service, IR) \ SUB(Service, Y2R) \ @@ -62,6 +63,7 @@ namespace Log { SUB(HW, Memory) \ SUB(HW, LCD) \ SUB(HW, GPU) \ + SUB(HW, AES) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Software) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 4330ef879..4b0f8ff03 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -72,6 +72,7 @@ enum class Class : ClassType { Service_DSP, ///< The DSP (DSP control) service Service_DLP, ///< The DLP (Download Play) service Service_HID, ///< The HID (Human interface device) service + Service_HTTP, ///< The HTTP service Service_SOC, ///< The SOC (Socket) service Service_IR, ///< The IR service Service_Y2R, ///< The Y2R (YUV to RGB conversion) service @@ -79,6 +80,7 @@ enum class Class : ClassType { HW_Memory, ///< Memory-map and address translation HW_LCD, ///< LCD register emulation HW_GPU, ///< GPU control emulation + HW_AES, ///< AES engine emulation Frontend, ///< Emulator UI Render, ///< Emulator video output and hardware acceleration Render_Software, ///< Software renderer backend diff --git a/src/common/profiler.cpp b/src/common/profiler.cpp deleted file mode 100644 index b40e7205d..000000000 --- a/src/common/profiler.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <cstddef> -#include <vector> -#include "common/assert.h" -#include "common/profiler_reporting.h" -#include "common/synchronized_wrapper.h" - -namespace Common { -namespace Profiling { - -ProfilingManager::ProfilingManager() - : last_frame_end(Clock::now()), this_frame_start(Clock::now()) {} - -void ProfilingManager::BeginFrame() { - this_frame_start = Clock::now(); -} - -void ProfilingManager::FinishFrame() { - Clock::time_point now = Clock::now(); - - results.interframe_time = now - last_frame_end; - results.frame_time = now - this_frame_start; - - last_frame_end = now; -} - -TimingResultsAggregator::TimingResultsAggregator(size_t window_size) - : max_window_size(window_size), window_size(0) { - interframe_times.resize(window_size, Duration::zero()); - frame_times.resize(window_size, Duration::zero()); -} - -void TimingResultsAggregator::Clear() { - window_size = cursor = 0; -} - -void TimingResultsAggregator::AddFrame(const ProfilingFrameResult& frame_result) { - interframe_times[cursor] = frame_result.interframe_time; - frame_times[cursor] = frame_result.frame_time; - - ++cursor; - if (cursor == max_window_size) - cursor = 0; - if (window_size < max_window_size) - ++window_size; -} - -static AggregatedDuration AggregateField(const std::vector<Duration>& v, size_t len) { - AggregatedDuration result; - result.avg = Duration::zero(); - result.min = result.max = (len == 0 ? Duration::zero() : v[0]); - - for (size_t i = 0; i < len; ++i) { - Duration value = v[i]; - result.avg += value; - result.min = std::min(result.min, value); - result.max = std::max(result.max, value); - } - if (len != 0) - result.avg /= len; - - return result; -} - -static float tof(Common::Profiling::Duration dur) { - using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>; - return std::chrono::duration_cast<FloatMs>(dur).count(); -} - -AggregatedFrameResult TimingResultsAggregator::GetAggregatedResults() const { - AggregatedFrameResult result; - - result.interframe_time = AggregateField(interframe_times, window_size); - result.frame_time = AggregateField(frame_times, window_size); - - if (result.interframe_time.avg != Duration::zero()) { - result.fps = 1000.0f / tof(result.interframe_time.avg); - } else { - result.fps = 0.0f; - } - - return result; -} - -ProfilingManager& GetProfilingManager() { - // Takes advantage of "magic" static initialization for race-free initialization. - static ProfilingManager manager; - return manager; -} - -SynchronizedRef<TimingResultsAggregator> GetTimingResultsAggregator() { - static SynchronizedWrapper<TimingResultsAggregator> aggregator(30); - return SynchronizedRef<TimingResultsAggregator>(aggregator); -} - -} // namespace Profiling -} // namespace Common diff --git a/src/common/profiler_reporting.h b/src/common/profiler_reporting.h deleted file mode 100644 index e9ce6d41c..000000000 --- a/src/common/profiler_reporting.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <chrono> -#include <cstddef> -#include <vector> -#include "common/synchronized_wrapper.h" - -namespace Common { -namespace Profiling { - -using Clock = std::chrono::high_resolution_clock; -using Duration = Clock::duration; - -struct ProfilingFrameResult { - /// Time since the last delivered frame - Duration interframe_time; - - /// Time spent processing a frame, excluding VSync - Duration frame_time; -}; - -class ProfilingManager final { -public: - ProfilingManager(); - - /// This should be called after swapping screen buffers. - void BeginFrame(); - /// This should be called before swapping screen buffers. - void FinishFrame(); - - /// Get the timing results from the previous frame. This is updated when you call FinishFrame(). - const ProfilingFrameResult& GetPreviousFrameResults() const { - return results; - } - -private: - Clock::time_point last_frame_end; - Clock::time_point this_frame_start; - - ProfilingFrameResult results; -}; - -struct AggregatedDuration { - Duration avg, min, max; -}; - -struct AggregatedFrameResult { - /// Time since the last delivered frame - AggregatedDuration interframe_time; - - /// Time spent processing a frame, excluding VSync - AggregatedDuration frame_time; - - float fps; -}; - -class TimingResultsAggregator final { -public: - TimingResultsAggregator(size_t window_size); - - void Clear(); - - void AddFrame(const ProfilingFrameResult& frame_result); - - AggregatedFrameResult GetAggregatedResults() const; - - size_t max_window_size; - size_t window_size; - size_t cursor; - - std::vector<Duration> interframe_times; - std::vector<Duration> frame_times; -}; - -ProfilingManager& GetProfilingManager(); -SynchronizedRef<TimingResultsAggregator> GetTimingResultsAggregator(); - -} // namespace Profiling -} // namespace Common diff --git a/src/common/scm_rev.cpp.in b/src/common/scm_rev.cpp.in index 79b404bb8..0080db5d5 100644 --- a/src/common/scm_rev.cpp.in +++ b/src/common/scm_rev.cpp.in @@ -7,12 +7,14 @@ #define GIT_REV "@GIT_REV@" #define GIT_BRANCH "@GIT_BRANCH@" #define GIT_DESC "@GIT_DESC@" +#define BUILD_NAME "@REPO_NAME@" namespace Common { const char g_scm_rev[] = GIT_REV; const char g_scm_branch[] = GIT_BRANCH; const char g_scm_desc[] = GIT_DESC; +const char g_build_name[] = BUILD_NAME; } // namespace diff --git a/src/common/scm_rev.h b/src/common/scm_rev.h index 0ef190afa..e22389803 100644 --- a/src/common/scm_rev.h +++ b/src/common/scm_rev.h @@ -9,5 +9,6 @@ namespace Common { extern const char g_scm_rev[]; extern const char g_scm_branch[]; extern const char g_scm_desc[]; +extern const char g_build_name[]; } // namespace diff --git a/src/common/synchronized_wrapper.h b/src/common/synchronized_wrapper.h index 04b4f2e51..4a1984c46 100644 --- a/src/common/synchronized_wrapper.h +++ b/src/common/synchronized_wrapper.h @@ -9,25 +9,8 @@ namespace Common { -/** - * Wraps an object, only allowing access to it via a locking reference wrapper. Good to ensure no - * one forgets to lock a mutex before acessing an object. To access the wrapped object construct a - * SyncronizedRef on this wrapper. Inspired by Rust's Mutex type - * (http://doc.rust-lang.org/std/sync/struct.Mutex.html). - */ template <typename T> -class SynchronizedWrapper { -public: - template <typename... Args> - SynchronizedWrapper(Args&&... args) : data(std::forward<Args>(args)...) {} - -private: - template <typename U> - friend class SynchronizedRef; - - std::mutex mutex; - T data; -}; +class SynchronizedWrapper; /** * Synchronized reference, that keeps a SynchronizedWrapper's mutex locked during its lifetime. This @@ -75,4 +58,28 @@ private: SynchronizedWrapper<T>* wrapper; }; +/** + * Wraps an object, only allowing access to it via a locking reference wrapper. Good to ensure no + * one forgets to lock a mutex before acessing an object. To access the wrapped object construct a + * SyncronizedRef on this wrapper. Inspired by Rust's Mutex type + * (http://doc.rust-lang.org/std/sync/struct.Mutex.html). + */ +template <typename T> +class SynchronizedWrapper { +public: + template <typename... Args> + SynchronizedWrapper(Args&&... args) : data(std::forward<Args>(args)...) {} + + SynchronizedRef<T> Lock() { + return {*this}; + } + +private: + template <typename U> + friend class SynchronizedRef; + + std::mutex mutex; + T data; +}; + } // namespace Common 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/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp index 370ae2c80..2cb3ab9cc 100644 --- a/src/common/x64/cpu_detect.cpp +++ b/src/common/x64/cpu_detect.cpp @@ -8,9 +8,9 @@ #include "common/common_types.h" #include "cpu_detect.h" -namespace Common { - -#ifndef _MSC_VER +#ifdef _MSC_VER +#include <intrin.h> +#else #if defined(__DragonFly__) || defined(__FreeBSD__) // clang-format off @@ -37,13 +37,15 @@ static inline void __cpuid(int info[4], int function_id) { } #define _XCR_XFEATURE_ENABLED_MASK 0 -static u64 _xgetbv(u32 index) { +static inline u64 _xgetbv(u32 index) { u32 eax, edx; __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index)); return ((u64)edx << 32) | eax; } -#endif // ifndef _MSC_VER +#endif // _MSC_VER + +namespace Common { // Detects the various CPU features static CPUCaps Detect() { 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 faad0a561..ffd67f074 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 @@ -19,16 +20,19 @@ set(SRCS file_sys/archive_extsavedata.cpp file_sys/archive_ncch.cpp file_sys/archive_other_savedata.cpp - file_sys/archive_romfs.cpp file_sys/archive_savedata.cpp file_sys/archive_sdmc.cpp file_sys/archive_sdmcwriteonly.cpp + file_sys/archive_selfncch.cpp file_sys/archive_source_sd_savedata.cpp file_sys/archive_systemsavedata.cpp file_sys/disk_archive.cpp 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 @@ -37,6 +41,7 @@ set(SRCS 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 @@ -154,6 +159,9 @@ set(SRCS hle/service/y2r_u.cpp hle/shared_page.cpp hle/svc.cpp + hw/aes/arithmetic128.cpp + hw/aes/ccm.cpp + hw/aes/key.cpp hw/gpu.cpp hw/hw.cpp hw/lcd.cpp @@ -165,6 +173,7 @@ set(SRCS loader/smdh.cpp tracer/recorder.cpp memory.cpp + perf_stats.cpp settings.cpp ) @@ -173,6 +182,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 @@ -191,18 +201,22 @@ set(HEADERS file_sys/archive_extsavedata.h file_sys/archive_ncch.h file_sys/archive_other_savedata.h - file_sys/archive_romfs.h file_sys/archive_savedata.h file_sys/archive_sdmc.h file_sys/archive_sdmcwriteonly.h + file_sys/archive_selfncch.h file_sys/archive_source_sd_savedata.h file_sys/archive_systemsavedata.h file_sys/directory_backend.h file_sys/disk_archive.h + file_sys/errors.h file_sys/file_backend.h 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 @@ -210,9 +224,11 @@ set(HEADERS hle/config_mem.h hle/function_wrappers.h hle/ipc.h + hle/ipc_helpers.h 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 @@ -331,6 +347,9 @@ set(HEADERS hle/service/y2r_u.h hle/shared_page.h hle/svc.h + hw/aes/arithmetic128.h + hw/aes/ccm.h + hw/aes/key.h hw/gpu.h hw/hw.h hw/lcd.h @@ -345,13 +364,15 @@ set(HEADERS memory.h memory_setup.h mmio.h + perf_stats.h settings.h ) include_directories(../../externals/dynarmic/include) +include_directories(../../externals/cryptopp) create_directory_groups(${SRCS} ${HEADERS}) add_library(core STATIC ${SRCS} ${HEADERS}) -target_link_libraries(core dynarmic) +target_link_libraries(core dynarmic cryptopp) 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/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp index 67c45640a..273bc8167 100644 --- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp +++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp @@ -3928,13 +3928,13 @@ SXTB16_INST : { if (inst_cream->Rn == 15) { u32 lo = (u32)(s8)rm_val; u32 hi = (u32)(s8)(rm_val >> 16); - RD = (lo | (hi << 16)); + RD = (lo & 0xFFFF) | (hi << 16); } // SXTAB16 else { - u32 lo = (rn_val & 0xFFFF) + (u32)(s8)(rm_val & 0xFF); - u32 hi = ((rn_val >> 16) & 0xFFFF) + (u32)(s8)((rm_val >> 16) & 0xFF); - RD = (lo | (hi << 16)); + u32 lo = rn_val + (u32)(s8)(rm_val & 0xFF); + u32 hi = (rn_val >> 16) + (u32)(s8)((rm_val >> 16) & 0xFF); + RD = (lo & 0xFFFF) | (hi << 16); } } diff --git a/src/core/core.cpp b/src/core/core.cpp index 202cd332b..140ff6451 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -67,10 +67,6 @@ System::ResultStatus System::SingleStep() { } System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& filepath) { - if (app_loader) { - app_loader.reset(); - } - app_loader = Loader::GetLoader(filepath); if (!app_loader) { @@ -113,6 +109,10 @@ void System::PrepareReschedule() { reschedule_pending = true; } +PerfStats::Results System::GetAndResetPerfStats() { + return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs()); +} + void System::Reschedule() { if (!reschedule_pending) { return; @@ -123,10 +123,6 @@ void System::Reschedule() { } System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { - if (cpu_core) { - cpu_core.reset(); - } - Memory::Init(); if (Settings::values.use_cpu_jit) { @@ -148,6 +144,10 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { LOG_DEBUG(Core, "Initialized OK"); + // Reset counters and set time origin to current frame + GetAndResetPerfStats(); + perf_stats.BeginSystemFrame(); + return ResultStatus::Success; } @@ -159,7 +159,8 @@ void System::Shutdown() { Kernel::Shutdown(); HW::Shutdown(); CoreTiming::Shutdown(); - cpu_core.reset(); + cpu_core = nullptr; + app_loader = nullptr; LOG_DEBUG(Core, "Shutdown OK"); } diff --git a/src/core/core.h b/src/core/core.h index 1015e8847..6c9c936b5 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -6,9 +6,9 @@ #include <memory> #include <string> - #include "common/common_types.h" #include "core/memory.h" +#include "core/perf_stats.h" class EmuWindow; class ARM_Interface; @@ -83,6 +83,8 @@ public: /// Prepare the core emulation for a reschedule void PrepareReschedule(); + PerfStats::Results GetAndResetPerfStats(); + /** * Gets a reference to the emulated CPU. * @returns A reference to the emulated CPU. @@ -91,6 +93,9 @@ public: return *cpu_core; } + PerfStats perf_stats; + FrameLimiter frame_limiter; + private: /** * Initialize the emulated system. @@ -115,7 +120,7 @@ private: static System s_instance; }; -static ARM_Interface& CPU() { +inline ARM_Interface& CPU() { return System::GetInstance().CPU(); } 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_extsavedata.h b/src/core/file_sys/archive_extsavedata.h index 6a3431e94..f705ade1c 100644 --- a/src/core/file_sys/archive_extsavedata.h +++ b/src/core/file_sys/archive_extsavedata.h @@ -52,7 +52,7 @@ private: /** * This holds the full directory path for this archive, it is only set after a successful call - * to Open, this is formed as <base extsavedatapath>/<type>/<high>/<low>. + * to Open, this is formed as `<base extsavedatapath>/<type>/<high>/<low>`. * See GetExtSaveDataPath for the code that extracts this data from an archive path. */ std::string mount_point; diff --git a/src/core/file_sys/archive_romfs.cpp b/src/core/file_sys/archive_romfs.cpp deleted file mode 100644 index 6c99ca5b4..000000000 --- a/src/core/file_sys/archive_romfs.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <memory> -#include "common/common_types.h" -#include "common/logging/log.h" -#include "core/file_sys/archive_romfs.h" -#include "core/file_sys/ivfc_archive.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// FileSys namespace - -namespace FileSys { - -ArchiveFactory_RomFS::ArchiveFactory_RomFS(Loader::AppLoader& app_loader) { - // Load the RomFS from the app - if (Loader::ResultStatus::Success != app_loader.ReadRomFS(romfs_file, data_offset, data_size)) { - LOG_ERROR(Service_FS, "Unable to read RomFS!"); - } -} - -ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_RomFS::Open(const Path& path) { - auto archive = std::make_unique<IVFCArchive>(romfs_file, data_offset, data_size); - return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); -} - -ResultCode ArchiveFactory_RomFS::Format(const Path& path, - const FileSys::ArchiveFormatInfo& format_info) { - LOG_ERROR(Service_FS, "Attempted to format a RomFS archive."); - // TODO: Verify error code - return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported, - ErrorLevel::Permanent); -} - -ResultVal<ArchiveFormatInfo> ArchiveFactory_RomFS::GetFormatInfo(const Path& path) const { - // TODO(Subv): Implement - LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); - return ResultCode(-1); -} - -} // namespace FileSys 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/archive_selfncch.cpp b/src/core/file_sys/archive_selfncch.cpp new file mode 100644 index 000000000..298a37a44 --- /dev/null +++ b/src/core/file_sys/archive_selfncch.cpp @@ -0,0 +1,257 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/swap.h" +#include "core/file_sys/archive_selfncch.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/ivfc_archive.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +namespace FileSys { + +enum class SelfNCCHFilePathType : u32 { + RomFS = 0, + Code = 1, // This is not supported by SelfNCCHArchive but by archive 0x2345678E + ExeFS = 2, + UpdateRomFS = 5, // This is presumably for accessing the RomFS of the update patch. +}; + +struct SelfNCCHFilePath { + u32_le type; + std::array<char, 8> exefs_filename; +}; +static_assert(sizeof(SelfNCCHFilePath) == 12, "NCCHFilePath has wrong size!"); + +// A read-only file created from a block of data. It only allows you to read the entire file at +// once, in a single read operation. +class ExeFSSectionFile final : public FileBackend { +public: + explicit ExeFSSectionFile(std::shared_ptr<std::vector<u8>> data_) : data(std::move(data_)) {} + + ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override { + if (offset != 0) { + LOG_ERROR(Service_FS, "offset must be zero!"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + if (length != data->size()) { + LOG_ERROR(Service_FS, "size must match the file size!"); + return ERROR_INCORRECT_EXEFS_READ_SIZE; + } + + std::memcpy(buffer, data->data(), data->size()); + return MakeResult<size_t>(data->size()); + } + + ResultVal<size_t> Write(u64 offset, size_t length, bool flush, + const u8* buffer) const override { + LOG_ERROR(Service_FS, "The file is read-only!"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + u64 GetSize() const override { + return data->size(); + } + + bool SetSize(u64 size) const override { + return false; + } + + bool Close() const override { + return true; + } + + void Flush() const override {} + +private: + std::shared_ptr<std::vector<u8>> data; +}; + +// SelfNCCHArchive represents the running application itself. From this archive the application can +// open RomFS and ExeFS, excluding the .code section. +class SelfNCCHArchive final : public ArchiveBackend { +public: + explicit SelfNCCHArchive(const NCCHData& ncch_data_) : ncch_data(ncch_data_) {} + + std::string GetName() const override { + return "SelfNCCHArchive"; + } + + ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode&) const override { + // Note: SelfNCCHArchive doesn't check the open mode. + + if (path.GetType() != LowPathType::Binary) { + LOG_ERROR(Service_FS, "Path need to be Binary"); + return ERROR_INVALID_PATH; + } + + std::vector<u8> binary = path.AsBinary(); + if (binary.size() != sizeof(SelfNCCHFilePath)) { + LOG_ERROR(Service_FS, "Wrong path size %zu", binary.size()); + return ERROR_INVALID_PATH; + } + + SelfNCCHFilePath file_path; + std::memcpy(&file_path, binary.data(), sizeof(SelfNCCHFilePath)); + + switch (static_cast<SelfNCCHFilePathType>(file_path.type)) { + case SelfNCCHFilePathType::UpdateRomFS: + LOG_WARNING(Service_FS, "(STUBBED) open update RomFS"); + return OpenRomFS(); + + case SelfNCCHFilePathType::RomFS: + return OpenRomFS(); + + case SelfNCCHFilePathType::Code: + LOG_ERROR(Service_FS, "Reading the code section is not supported!"); + return ERROR_COMMAND_NOT_ALLOWED; + + case SelfNCCHFilePathType::ExeFS: { + const auto& raw = file_path.exefs_filename; + auto end = std::find(raw.begin(), raw.end(), '\0'); + std::string filename(raw.begin(), end); + return OpenExeFS(filename); + } + default: + LOG_ERROR(Service_FS, "Unknown file type %u!", static_cast<u32>(file_path.type)); + return ERROR_INVALID_PATH; + } + } + + ResultCode DeleteFile(const Path& path) const override { + LOG_ERROR(Service_FS, "Unsupported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override { + LOG_ERROR(Service_FS, "Unsupported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + ResultCode DeleteDirectory(const Path& path) const override { + LOG_ERROR(Service_FS, "Unsupported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + ResultCode DeleteDirectoryRecursively(const Path& path) const override { + LOG_ERROR(Service_FS, "Unsupported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + ResultCode CreateFile(const Path& path, u64 size) const override { + LOG_ERROR(Service_FS, "Unsupported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + ResultCode CreateDirectory(const Path& path) const override { + LOG_ERROR(Service_FS, "Unsupported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override { + LOG_ERROR(Service_FS, "Unsupported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override { + LOG_ERROR(Service_FS, "Unsupported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + u64 GetFreeBytes() const override { + return 0; + } + +private: + ResultVal<std::unique_ptr<FileBackend>> OpenRomFS() const { + if (ncch_data.romfs_file) { + return MakeResult<std::unique_ptr<FileBackend>>(std::make_unique<IVFCFile>( + ncch_data.romfs_file, ncch_data.romfs_offset, ncch_data.romfs_size)); + } else { + LOG_INFO(Service_FS, "Unable to read RomFS"); + return ERROR_ROMFS_NOT_FOUND; + } + } + + ResultVal<std::unique_ptr<FileBackend>> OpenExeFS(const std::string& filename) const { + if (filename == "icon") { + if (ncch_data.icon) { + return MakeResult<std::unique_ptr<FileBackend>>( + std::make_unique<ExeFSSectionFile>(ncch_data.icon)); + } + + LOG_WARNING(Service_FS, "Unable to read icon"); + return ERROR_EXEFS_SECTION_NOT_FOUND; + } + + if (filename == "logo") { + if (ncch_data.logo) { + return MakeResult<std::unique_ptr<FileBackend>>( + std::make_unique<ExeFSSectionFile>(ncch_data.logo)); + } + + LOG_WARNING(Service_FS, "Unable to read logo"); + return ERROR_EXEFS_SECTION_NOT_FOUND; + } + + if (filename == "banner") { + if (ncch_data.banner) { + return MakeResult<std::unique_ptr<FileBackend>>( + std::make_unique<ExeFSSectionFile>(ncch_data.banner)); + } + + LOG_WARNING(Service_FS, "Unable to read banner"); + return ERROR_EXEFS_SECTION_NOT_FOUND; + } + + LOG_ERROR(Service_FS, "Unknown ExeFS section %s!", filename.c_str()); + return ERROR_INVALID_PATH; + } + + NCCHData ncch_data; +}; + +ArchiveFactory_SelfNCCH::ArchiveFactory_SelfNCCH(Loader::AppLoader& app_loader) { + std::shared_ptr<FileUtil::IOFile> romfs_file_; + if (Loader::ResultStatus::Success == + app_loader.ReadRomFS(romfs_file_, ncch_data.romfs_offset, ncch_data.romfs_size)) { + + ncch_data.romfs_file = std::move(romfs_file_); + } + + std::vector<u8> buffer; + + if (Loader::ResultStatus::Success == app_loader.ReadIcon(buffer)) + ncch_data.icon = std::make_shared<std::vector<u8>>(std::move(buffer)); + + buffer.clear(); + if (Loader::ResultStatus::Success == app_loader.ReadLogo(buffer)) + ncch_data.logo = std::make_shared<std::vector<u8>>(std::move(buffer)); + + buffer.clear(); + if (Loader::ResultStatus::Success == app_loader.ReadBanner(buffer)) + ncch_data.banner = std::make_shared<std::vector<u8>>(std::move(buffer)); +} + +ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SelfNCCH::Open(const Path& path) { + auto archive = std::make_unique<SelfNCCHArchive>(ncch_data); + return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); +} + +ResultCode ArchiveFactory_SelfNCCH::Format(const Path&, const FileSys::ArchiveFormatInfo&) { + LOG_ERROR(Service_FS, "Attempted to format a SelfNCCH archive."); + return ERROR_INVALID_PATH; +} + +ResultVal<ArchiveFormatInfo> ArchiveFactory_SelfNCCH::GetFormatInfo(const Path&) const { + LOG_ERROR(Service_FS, "Attempted to get format info of a SelfNCCH archive"); + return ERROR_INVALID_PATH; +} + +} // namespace FileSys diff --git a/src/core/file_sys/archive_romfs.h b/src/core/file_sys/archive_selfncch.h index 1eaf99b54..f1b971296 100644 --- a/src/core/file_sys/archive_romfs.h +++ b/src/core/file_sys/archive_selfncch.h @@ -1,4 +1,4 @@ -// Copyright 2014 Citra Emulator Project +// Copyright 2017 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -17,22 +17,29 @@ namespace FileSys { -/// File system interface to the RomFS archive -class ArchiveFactory_RomFS final : public ArchiveFactory { +struct NCCHData { + std::shared_ptr<std::vector<u8>> icon; + std::shared_ptr<std::vector<u8>> logo; + std::shared_ptr<std::vector<u8>> banner; + std::shared_ptr<FileUtil::IOFile> romfs_file; + u64 romfs_offset = 0; + u64 romfs_size = 0; +}; + +/// File system interface to the SelfNCCH archive +class ArchiveFactory_SelfNCCH final : public ArchiveFactory { public: - explicit ArchiveFactory_RomFS(Loader::AppLoader& app_loader); + explicit ArchiveFactory_SelfNCCH(Loader::AppLoader& app_loader); std::string GetName() const override { - return "RomFS"; + return "SelfNCCH"; } ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override; ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) override; ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override; private: - std::shared_ptr<FileUtil::IOFile> romfs_file; - u64 data_offset; - u64 data_size; + NCCHData ncch_data; }; } // namespace FileSys diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h index 4d5f62b08..9fc8d753b 100644 --- a/src/core/file_sys/errors.h +++ b/src/core/file_sys/errors.h @@ -39,5 +39,15 @@ const ResultCode ERROR_DIRECTORY_NOT_EMPTY(ErrorDescription::FS_DirectoryNotEmpt const ResultCode ERROR_GAMECARD_NOT_INSERTED(ErrorDescription::FS_GameCardNotInserted, ErrorModule::FS, ErrorSummary::NotFound, ErrorLevel::Status); +const ResultCode ERROR_INCORRECT_EXEFS_READ_SIZE(ErrorDescription::FS_IncorrectExeFSReadSize, + ErrorModule::FS, ErrorSummary::NotSupported, + ErrorLevel::Usage); +const ResultCode ERROR_ROMFS_NOT_FOUND(ErrorDescription::FS_RomFSNotFound, ErrorModule::FS, + ErrorSummary::NotFound, ErrorLevel::Status); +const ResultCode ERROR_COMMAND_NOT_ALLOWED(ErrorDescription::FS_CommandNotAllowed, ErrorModule::FS, + ErrorSummary::WrongArgument, ErrorLevel::Permanent); +const ResultCode ERROR_EXEFS_SECTION_NOT_FOUND(ErrorDescription::FS_ExeFSSectionNotFound, + ErrorModule::FS, ErrorSummary::NotFound, + ErrorLevel::Status); } // namespace FileSys 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..f46413fa7 --- /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. + * @param 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 1541cc39d..a155b657d 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -5,7 +5,7 @@ #include <algorithm> #include <cmath> #include "common/assert.h" -#include "common/profiler_reporting.h" +#include "core/core.h" #include "core/frontend/emu_window.h" #include "core/frontend/key_map.h" #include "video_core/video_core.h" @@ -70,14 +70,12 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) { (framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top); touch_pressed = true; - pad_state.touch.Assign(1); } void EmuWindow::TouchReleased() { touch_pressed = false; touch_x = 0; touch_y = 0; - pad_state.touch.Assign(0); } void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) { @@ -98,20 +96,19 @@ void EmuWindow::AccelerometerChanged(float x, float y, float z) { // 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 = x * coef; - accel_y = y * coef; - accel_z = z * coef; + 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; + float stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale(); std::lock_guard<std::mutex> lock(gyro_mutex); - gyro_x = x * coef * stretch; - gyro_y = y * coef * stretch; - gyro_z = z * coef * stretch; + 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) { diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index 5cf45ada5..123fe7cd4 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -230,6 +230,7 @@ static void GdbHexToMem(u8* dest, const u8* src, size_t len) { * Convert a u32 into a gdb-formatted hex string. * * @param dest Pointer to buffer to store output hex string characters. + * @param v Value to convert. */ static void IntToGdbHex(u8* dest, u32 v) { for (int i = 0; i < 8; i += 2) { 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/ipc.h b/src/core/hle/ipc.h index 4e094faa7..cd9a5863d 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h @@ -18,12 +18,26 @@ static const int kCommandHeaderOffset = 0x80; ///< Offset into command buffer of * the thread's TLS to an intermediate buffer in kernel memory, and then copied again to * the service handler process' memory. * @param offset Optional offset into command buffer + * @param offset Optional offset into command buffer (in bytes) * @return Pointer to command buffer */ inline u32* GetCommandBuffer(const int offset = 0) { return (u32*)Memory::GetPointer(GetCurrentThread()->GetTLSAddress() + kCommandHeaderOffset + offset); } + +static const int kStaticBuffersOffset = + 0x100; ///< Offset into static buffers, relative to command buffer header + +/** + * Returns a pointer to the static buffers area in the current thread's TLS + * TODO(Subv): cf. GetCommandBuffer + * @param offset Optional offset into static buffers area (in bytes) + * @return Pointer to static buffers area + */ +inline u32* GetStaticBuffers(const int offset = 0) { + return GetCommandBuffer(kStaticBuffersOffset + offset); +} } namespace IPC { @@ -40,10 +54,17 @@ enum DescriptorType : u32 { CallingPid = 0x20, }; +union Header { + u32 raw; + BitField<0, 6, u32> translate_params_size; + BitField<6, 6, u32> normal_params_size; + BitField<16, 16, u32> command_id; +}; + /** * @brief Creates a command header to be used for IPC * @param command_id ID of the command to create a header for. - * @param normal_params Size of the normal parameters in words. Up to 63. + * @param normal_params_size Size of the normal parameters in words. Up to 63. * @param translate_params_size Size of the translate parameters in words. Up to 63. * @return The created IPC header. * @@ -51,24 +72,16 @@ enum DescriptorType : u32 { * through modifications and checks by the kernel. * The translate parameters are described by headers generated with the IPC::*Desc functions. * - * @note While #normal_params is equivalent to the number of normal parameters, - * #translate_params_size includes the size occupied by the translate parameters headers. + * @note While @p normal_params_size is equivalent to the number of normal parameters, + * @p translate_params_size includes the size occupied by the translate parameters headers. */ -constexpr u32 MakeHeader(u16 command_id, unsigned int normal_params, - unsigned int translate_params_size) { - return (u32(command_id) << 16) | ((u32(normal_params) & 0x3F) << 6) | - (u32(translate_params_size) & 0x3F); -} - -union Header { - u32 raw; - BitField<0, 6, u32> translate_params_size; - BitField<6, 6, u32> normal_params; - BitField<16, 16, u32> command_id; -}; - -inline Header ParseHeader(u32 header) { - return {header}; +inline u32 MakeHeader(u16 command_id, unsigned int normal_params_size, + unsigned int translate_params_size) { + Header header{}; + header.command_id.Assign(command_id); + header.normal_params_size.Assign(normal_params_size); + header.translate_params_size.Assign(translate_params_size); + return header.raw; } constexpr u32 MoveHandleDesc(u32 num_handles = 1) { @@ -83,7 +96,7 @@ constexpr u32 CallingPidDesc() { return CallingPid; } -constexpr bool isHandleDescriptor(u32 descriptor) { +constexpr bool IsHandleDescriptor(u32 descriptor) { return (descriptor & 0xF) == 0x0; } @@ -91,18 +104,19 @@ constexpr u32 HandleNumberFromDesc(u32 handle_descriptor) { return (handle_descriptor >> 26) + 1; } -constexpr u32 StaticBufferDesc(u32 size, u8 buffer_id) { - return StaticBuffer | (size << 14) | ((buffer_id & 0xF) << 10); -} - union StaticBufferDescInfo { u32 raw; + BitField<0, 4, u32> descriptor_type; BitField<10, 4, u32> buffer_id; BitField<14, 18, u32> size; }; -inline StaticBufferDescInfo ParseStaticBufferDesc(const u32 desc) { - return {desc}; +inline u32 StaticBufferDesc(u32 size, u8 buffer_id) { + StaticBufferDescInfo info{}; + info.descriptor_type.Assign(StaticBuffer); + info.buffer_id.Assign(buffer_id); + info.size.Assign(size); + return info.raw; } /** @@ -122,29 +136,30 @@ inline u32 PXIBufferDesc(u32 size, unsigned buffer_id, bool is_read_only) { return type | (size << 8) | ((buffer_id & 0xF) << 4); } -enum MappedBufferPermissions { +enum MappedBufferPermissions : u32 { R = 1, W = 2, RW = R | W, }; -constexpr u32 MappedBufferDesc(u32 size, MappedBufferPermissions perms) { - return MappedBuffer | (size << 4) | (u32(perms) << 1); -} - union MappedBufferDescInfo { u32 raw; - BitField<4, 28, u32> size; + BitField<0, 4, u32> flags; BitField<1, 2, MappedBufferPermissions> perms; + BitField<4, 28, u32> size; }; -inline MappedBufferDescInfo ParseMappedBufferDesc(const u32 desc) { - return {desc}; +inline u32 MappedBufferDesc(u32 size, MappedBufferPermissions perms) { + MappedBufferDescInfo info{}; + info.flags.Assign(MappedBuffer); + info.perms.Assign(perms); + info.size.Assign(size); + return info.raw; } inline DescriptorType GetDescriptorType(u32 descriptor) { // Note: Those checks must be done in this order - if (isHandleDescriptor(descriptor)) + if (IsHandleDescriptor(descriptor)) return (DescriptorType)(descriptor & 0x30); // handle the fact that the following descriptors can have rights diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h new file mode 100644 index 000000000..323158bb5 --- /dev/null +++ b/src/core/hle/ipc_helpers.h @@ -0,0 +1,275 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include "core/hle/ipc.h" +#include "core/hle/kernel/kernel.h" + +namespace IPC { + +class RequestHelperBase { +protected: + u32* cmdbuf; + ptrdiff_t index = 1; + Header header; + +public: + RequestHelperBase(u32* command_buffer, Header command_header) + : cmdbuf(command_buffer), header(command_header) {} + + /// Returns the total size of the request in words + size_t TotalSize() const { + return 1 /* command header */ + header.normal_params_size + header.translate_params_size; + } + + void ValidateHeader() { + DEBUG_ASSERT_MSG(index == TotalSize(), "Operations do not match the header (cmd 0x%x)", + header.raw); + } +}; + +class RequestBuilder : public RequestHelperBase { +public: + RequestBuilder(u32* command_buffer, Header command_header) + : RequestHelperBase(command_buffer, command_header) { + cmdbuf[0] = header.raw; + } + explicit RequestBuilder(u32* command_buffer, u32 command_header) + : RequestBuilder(command_buffer, Header{command_header}) {} + RequestBuilder(u32* command_buffer, u16 command_id, unsigned normal_params_size, + unsigned translate_params_size) + : RequestBuilder(command_buffer, + MakeHeader(command_id, normal_params_size, translate_params_size)) {} + + // Validate on destruction, as there shouldn't be any case where we don't want it + ~RequestBuilder() { + ValidateHeader(); + } + + template <typename T> + void Push(T value); + + void Push(u32 value) { + cmdbuf[index++] = value; + } + template <typename First, typename... Other> + void Push(const First& first_value, const Other&... other_values) { + Push(first_value); + Push(other_values...); + } + + /** + * @brief Copies the content of the given trivially copyable class to the buffer as a normal + * param + * @note: The input class must be correctly packed/padded to fit hardware layout. + */ + template <typename T> + void PushRaw(const T& value) { + static_assert(std::is_trivially_copyable<T>(), "Raw types should be trivially copyable"); + std::memcpy(cmdbuf + index, &value, sizeof(T)); + index += (sizeof(T) + 3) / 4; // round up to word length + } + + // TODO : ensure that translate params are added after all regular params + template <typename... H> + void PushCopyHandles(H... handles) { + Push(CopyHandleDesc(sizeof...(H))); + Push(static_cast<Kernel::Handle>(handles)...); + } + + template <typename... H> + void PushMoveHandles(H... handles) { + Push(MoveHandleDesc(sizeof...(H))); + Push(static_cast<Kernel::Handle>(handles)...); + } + + void PushCurrentPIDHandle() { + Push(CallingPidDesc()); + Push(u32(0)); + } + + void PushStaticBuffer(VAddr buffer_vaddr, u32 size, u8 buffer_id) { + Push(StaticBufferDesc(size, buffer_id)); + Push(buffer_vaddr); + } + + void PushMappedBuffer(VAddr buffer_vaddr, u32 size, MappedBufferPermissions perms) { + Push(MappedBufferDesc(size, perms)); + Push(buffer_vaddr); + } +}; + +/// Push /// + +template <> +inline void RequestBuilder::Push<u32>(u32 value) { + Push(value); +} + +template <> +inline void RequestBuilder::Push<u64>(u64 value) { + Push(static_cast<u32>(value)); + Push(static_cast<u32>(value >> 32)); +} + +template <> +inline void RequestBuilder::Push<ResultCode>(ResultCode value) { + Push(value.raw); +} + +class RequestParser : public RequestHelperBase { +public: + RequestParser(u32* command_buffer, Header command_header) + : RequestHelperBase(command_buffer, command_header) {} + explicit RequestParser(u32* command_buffer, u32 command_header) + : RequestParser(command_buffer, Header{command_header}) {} + RequestParser(u32* command_buffer, u16 command_id, unsigned normal_params_size, + unsigned translate_params_size) + : RequestParser(command_buffer, + MakeHeader(command_id, normal_params_size, translate_params_size)) {} + + RequestBuilder MakeBuilder(u32 normal_params_size, u32 translate_params_size, + bool validateHeader = true) { + if (validateHeader) + ValidateHeader(); + Header builderHeader{ + MakeHeader(header.command_id, normal_params_size, translate_params_size)}; + return {cmdbuf, builderHeader}; + } + + template <typename T> + T Pop(); + + template <typename T> + void Pop(T& value); + + template <typename First, typename... Other> + void Pop(First& first_value, Other&... other_values); + + Kernel::Handle PopHandle(); + + template <typename... H> + void PopHandles(H&... handles); + + /** + * @brief Pops the static buffer vaddr + * @return The virtual address of the buffer + * @param[out] data_size If non-null, the pointed value will be set to the size of the data + * @param[out] useStaticBuffersToGetVaddr Indicates if we should read the vaddr from the static + * buffers (which is the correct thing to do, but no service presently implement it) instead of + * using the same value as the process who sent the request + * given by the source process + * + * Static buffers must be set up before any IPC request using those is sent. + * It is the duty of the process (usually services) to allocate and set up the receiving static + * buffer information + * Please note that the setup uses virtual addresses. + */ + VAddr PopStaticBuffer(size_t* data_size = nullptr, bool useStaticBuffersToGetVaddr = false); + + /** + * @brief Pops the mapped buffer vaddr + * @return The virtual address of the buffer + * @param[out] data_size If non-null, the pointed value will be set to the size of the data + * given by the source process + * @param[out] buffer_perms If non-null, the pointed value will be set to the permissions of the + * buffer + */ + VAddr PopMappedBuffer(size_t* data_size = nullptr, + MappedBufferPermissions* buffer_perms = nullptr); + + /** + * @brief Reads the next normal parameters as a struct, by copying it + * @note: The output class must be correctly packed/padded to fit hardware layout. + */ + template <typename T> + void PopRaw(T& value); +}; + +/// Pop /// + +template <> +inline u32 RequestParser::Pop<u32>() { + return cmdbuf[index++]; +} + +template <> +inline u64 RequestParser::Pop<u64>() { + const u64 lsw = Pop<u32>(); + const u64 msw = Pop<u32>(); + return msw << 32 | lsw; +} + +template <> +inline ResultCode RequestParser::Pop<ResultCode>() { + return ResultCode{Pop<u32>()}; +} + +template <typename T> +void RequestParser::Pop(T& value) { + value = Pop<T>(); +} + +template <typename First, typename... Other> +void RequestParser::Pop(First& first_value, Other&... other_values) { + first_value = Pop<First>(); + Pop(other_values...); +} + +inline Kernel::Handle RequestParser::PopHandle() { + const u32 handle_descriptor = Pop<u32>(); + DEBUG_ASSERT_MSG(IsHandleDescriptor(handle_descriptor), + "Tried to pop handle(s) but the descriptor is not a handle descriptor"); + DEBUG_ASSERT_MSG(HandleNumberFromDesc(handle_descriptor) == 1, + "Descriptor indicates that there isn't exactly one handle"); + return Pop<Kernel::Handle>(); +} + +template <typename... H> +void RequestParser::PopHandles(H&... handles) { + const u32 handle_descriptor = Pop<u32>(); + const int handles_number = sizeof...(H); + DEBUG_ASSERT_MSG(IsHandleDescriptor(handle_descriptor), + "Tried to pop handle(s) but the descriptor is not a handle descriptor"); + DEBUG_ASSERT_MSG(handles_number == HandleNumberFromDesc(handle_descriptor), + "Number of handles doesn't match the descriptor"); + Pop(static_cast<Kernel::Handle&>(handles)...); +} + +inline VAddr RequestParser::PopStaticBuffer(size_t* data_size, bool useStaticBuffersToGetVaddr) { + const u32 sbuffer_descriptor = Pop<u32>(); + StaticBufferDescInfo bufferInfo{sbuffer_descriptor}; + if (data_size != nullptr) + *data_size = bufferInfo.size; + if (!useStaticBuffersToGetVaddr) + return Pop<VAddr>(); + else { + ASSERT_MSG(0, "remove the assert if multiprocess/IPC translation are implemented."); + // The buffer has already been copied to the static buffer by the kernel during + // translation + Pop<VAddr>(); // Pop the calling process buffer address + // and get the vaddr from the static buffers + return cmdbuf[(0x100 >> 2) + bufferInfo.buffer_id * 2 + 1]; + } +} + +inline VAddr RequestParser::PopMappedBuffer(size_t* data_size, + MappedBufferPermissions* buffer_perms) { + const u32 sbuffer_descriptor = Pop<u32>(); + MappedBufferDescInfo bufferInfo{sbuffer_descriptor}; + if (data_size != nullptr) + *data_size = bufferInfo.size; + if (buffer_perms != nullptr) + *buffer_perms = bufferInfo.perms; + return Pop<VAddr>(); +} + +template <typename T> +void RequestParser::PopRaw(T& value) { + static_assert(std::is_trivially_copyable<T>(), "Raw types should be trivially copyable"); + std::memcpy(&value, cmdbuf + index, sizeof(T)); + index += (sizeof(T) + 3) / 4; // round up to word length +} + +} // namespace IPC diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index c088b9a19..761fc4781 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -4,6 +4,7 @@ #pragma once +#include <memory> #include <string> #include "common/assert.h" #include "common/common_types.h" @@ -44,7 +45,8 @@ public: /** * Creates a pair of ServerSession and an associated ClientSession. - * @param name Optional name of the ports + * @param name Optional name of the ports. + * @param hle_handler Optional HLE handler for this server session. * @return The created session tuple */ static SessionPair CreateSessionPair( diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index c557a2279..6ab31c70b 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -11,7 +11,6 @@ #include <boost/container/flat_set.hpp> #include "common/common_types.h" #include "core/arm/arm_interface.h" -#include "core/core.h" #include "core/hle/kernel/kernel.h" #include "core/hle/result.h" diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp index 60537f355..a00c75679 100644 --- a/src/core/hle/kernel/timer.cpp +++ b/src/core/hle/kernel/timer.cpp @@ -52,9 +52,14 @@ void Timer::Set(s64 initial, s64 interval) { initial_delay = initial; interval_delay = interval; - u64 initial_microseconds = initial / 1000; - CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), timer_callback_event_type, - callback_handle); + if (initial == 0) { + // Immediately invoke the callback + Signal(0); + } else { + u64 initial_microseconds = initial / 1000; + CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), timer_callback_event_type, + callback_handle); + } } void Timer::Cancel() { @@ -72,6 +77,22 @@ void Timer::WakeupAllWaitingThreads() { signaled = false; } +void Timer::Signal(int cycles_late) { + LOG_TRACE(Kernel, "Timer %u fired", GetObjectId()); + + signaled = true; + + // Resume all waiting threads + WakeupAllWaitingThreads(); + + if (interval_delay != 0) { + // Reschedule the timer with the interval delay + u64 interval_microseconds = interval_delay / 1000; + CoreTiming::ScheduleEvent(usToCycles(interval_microseconds) - cycles_late, + timer_callback_event_type, callback_handle); + } +} + /// The timer callback event, called when a timer is fired static void TimerCallback(u64 timer_handle, int cycles_late) { SharedPtr<Timer> timer = @@ -82,19 +103,7 @@ static void TimerCallback(u64 timer_handle, int cycles_late) { return; } - LOG_TRACE(Kernel, "Timer %08" PRIx64 " fired", timer_handle); - - timer->signaled = true; - - // Resume all waiting threads - timer->WakeupAllWaitingThreads(); - - if (timer->interval_delay != 0) { - // Reschedule the timer with the interval delay - u64 interval_microseconds = timer->interval_delay / 1000; - CoreTiming::ScheduleEvent(usToCycles(interval_microseconds) - cycles_late, - timer_callback_event_type, timer_handle); - } + timer->Signal(cycles_late); } void TimersInit() { diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h index c174f5664..b0f818933 100644 --- a/src/core/hle/kernel/timer.h +++ b/src/core/hle/kernel/timer.h @@ -54,6 +54,14 @@ public: void Cancel(); void Clear(); + /** + * Signals the timer, waking up any waiting threads and rescheduling it + * for the next interval. + * This method should not be called from outside the timer callback handler, + * lest multiple callback events get scheduled. + */ + void Signal(int cycles_late); + private: Timer(); ~Timer() override; diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 53864a3a7..cfefbbc64 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -20,6 +20,7 @@ enum class ErrorDescription : u32 { OS_InvalidBufferDescriptor = 48, MaxConnectionsReached = 52, WrongAddress = 53, + FS_RomFSNotFound = 100, FS_ArchiveNotMounted = 101, FS_FileNotFound = 112, FS_PathNotFound = 113, @@ -35,10 +36,13 @@ enum class ErrorDescription : u32 { OutofRangeOrMisalignedAddress = 513, // TODO(purpasmart): Check if this name fits its actual usage GPU_FirstInitialization = 519, + FS_ExeFSSectionNotFound = 567, + FS_CommandNotAllowed = 630, FS_InvalidReadFlag = 700, FS_InvalidPath = 702, FS_WriteBeyondEnd = 705, FS_UnsupportedOpenFlags = 760, + FS_IncorrectExeFSReadSize = 761, FS_UnexpectedFileOrDirectory = 770, InvalidSection = 1000, TooLarge = 1001, diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 615fe31ea..e57b19c2d 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -18,6 +18,8 @@ #include "core/hle/service/fs/archive.h" #include "core/hle/service/ptm/ptm.h" #include "core/hle/service/service.h" +#include "core/hw/aes/ccm.h" +#include "core/hw/aes/key.h" namespace Service { namespace APT { @@ -470,6 +472,107 @@ void GetStartupArgument(Service::Interface* self) { cmd_buff[2] = 0; } +void Wrap(Service::Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x46, 4, 4); + const u32 output_size = rp.Pop<u32>(); + const u32 input_size = rp.Pop<u32>(); + const u32 nonce_offset = rp.Pop<u32>(); + u32 nonce_size = rp.Pop<u32>(); + size_t desc_size; + IPC::MappedBufferPermissions desc_permission; + const VAddr input = rp.PopMappedBuffer(&desc_size, &desc_permission); + ASSERT(desc_size == input_size && desc_permission == IPC::MappedBufferPermissions::R); + const VAddr output = rp.PopMappedBuffer(&desc_size, &desc_permission); + ASSERT(desc_size == output_size && desc_permission == IPC::MappedBufferPermissions::W); + + // Note: real 3DS still returns SUCCESS when the sizes don't match. It seems that it doesn't + // check the buffer size and writes data with potential overflow. + ASSERT_MSG(output_size == input_size + HW::AES::CCM_MAC_SIZE, + "input_size (%d) doesn't match to output_size (%d)", input_size, output_size); + + LOG_DEBUG(Service_APT, "called, output_size=%u, input_size=%u, nonce_offset=%u, nonce_size=%u", + output_size, input_size, nonce_offset, nonce_size); + + // Note: This weird nonce size modification is verified against real 3DS + nonce_size = std::min<u32>(nonce_size & ~3, HW::AES::CCM_NONCE_SIZE); + + // Reads nonce and concatenates the rest of the input as plaintext + HW::AES::CCMNonce nonce{}; + Memory::ReadBlock(input + nonce_offset, nonce.data(), nonce_size); + u32 pdata_size = input_size - nonce_size; + std::vector<u8> pdata(pdata_size); + Memory::ReadBlock(input, pdata.data(), nonce_offset); + Memory::ReadBlock(input + nonce_offset + nonce_size, pdata.data() + nonce_offset, + pdata_size - nonce_offset); + + // Encrypts the plaintext using AES-CCM + auto cipher = HW::AES::EncryptSignCCM(pdata, nonce, HW::AES::KeySlotID::APTWrap); + + // Puts the nonce to the beginning of the output, with ciphertext followed + Memory::WriteBlock(output, nonce.data(), nonce_size); + Memory::WriteBlock(output + nonce_size, cipher.data(), cipher.size()); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); + rb.Push(RESULT_SUCCESS); + + // Unmap buffer + rb.PushMappedBuffer(input, input_size, IPC::MappedBufferPermissions::R); + rb.PushMappedBuffer(output, output_size, IPC::MappedBufferPermissions::W); +} + +void Unwrap(Service::Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x47, 4, 4); + const u32 output_size = rp.Pop<u32>(); + const u32 input_size = rp.Pop<u32>(); + const u32 nonce_offset = rp.Pop<u32>(); + u32 nonce_size = rp.Pop<u32>(); + size_t desc_size; + IPC::MappedBufferPermissions desc_permission; + const VAddr input = rp.PopMappedBuffer(&desc_size, &desc_permission); + ASSERT(desc_size == input_size && desc_permission == IPC::MappedBufferPermissions::R); + const VAddr output = rp.PopMappedBuffer(&desc_size, &desc_permission); + ASSERT(desc_size == output_size && desc_permission == IPC::MappedBufferPermissions::W); + + // Note: real 3DS still returns SUCCESS when the sizes don't match. It seems that it doesn't + // check the buffer size and writes data with potential overflow. + ASSERT_MSG(output_size == input_size - HW::AES::CCM_MAC_SIZE, + "input_size (%d) doesn't match to output_size (%d)", input_size, output_size); + + LOG_DEBUG(Service_APT, "called, output_size=%u, input_size=%u, nonce_offset=%u, nonce_size=%u", + output_size, input_size, nonce_offset, nonce_size); + + // Note: This weird nonce size modification is verified against real 3DS + nonce_size = std::min<u32>(nonce_size & ~3, HW::AES::CCM_NONCE_SIZE); + + // Reads nonce and cipher text + HW::AES::CCMNonce nonce{}; + Memory::ReadBlock(input, nonce.data(), nonce_size); + u32 cipher_size = input_size - nonce_size; + std::vector<u8> cipher(cipher_size); + Memory::ReadBlock(input + nonce_size, cipher.data(), cipher_size); + + // Decrypts the ciphertext using AES-CCM + auto pdata = HW::AES::DecryptVerifyCCM(cipher, nonce, HW::AES::KeySlotID::APTWrap); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + if (!pdata.empty()) { + // Splits the plaintext and put the nonce in between + Memory::WriteBlock(output, pdata.data(), nonce_offset); + Memory::WriteBlock(output + nonce_offset, nonce.data(), nonce_size); + Memory::WriteBlock(output + nonce_offset + nonce_size, pdata.data() + nonce_offset, + pdata.size() - nonce_offset); + rb.Push(RESULT_SUCCESS); + } else { + LOG_ERROR(Service_APT, "Failed to decrypt data"); + rb.Push(ResultCode(static_cast<ErrorDescription>(1), ErrorModule::PS, + ErrorSummary::WrongArgument, ErrorLevel::Status)); + } + + // Unmap buffer + rb.PushMappedBuffer(input, input_size, IPC::MappedBufferPermissions::R); + rb.PushMappedBuffer(output, output_size, IPC::MappedBufferPermissions::W); +} + void CheckNew3DSApp(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index 80325361f..e63b61450 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -137,6 +137,46 @@ void Initialize(Service::Interface* self); void GetSharedFont(Service::Interface* self); /** + * APT::Wrap service function + * Inputs: + * 1 : Output buffer size + * 2 : Input buffer size + * 3 : Nonce offset to the input buffer + * 4 : Nonce size + * 5 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xA) + * 6 : Input buffer address + * 7 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xC) + * 8 : Output buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xA) + * 3 : Input buffer address + * 4 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xC) + * 5 : Output buffer address + */ +void Wrap(Service::Interface* self); + +/** + * APT::Unwrap service function + * Inputs: + * 1 : Output buffer size + * 2 : Input buffer size + * 3 : Nonce offset to the output buffer + * 4 : Nonce size + * 5 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xA) + * 6 : Input buffer address + * 7 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xC) + * 8 : Output buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xA) + * 3 : Input buffer address + * 4 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xC) + * 5 : Output buffer address + */ +void Unwrap(Service::Interface* self); + +/** * APT::NotifyToWait service function * Inputs: * 1 : AppID diff --git a/src/core/hle/service/apt/apt_a.cpp b/src/core/hle/service/apt/apt_a.cpp index 62dc2d61d..c496cba8d 100644 --- a/src/core/hle/service/apt/apt_a.cpp +++ b/src/core/hle/service/apt/apt_a.cpp @@ -78,8 +78,8 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00430040, NotifyToWait, "NotifyToWait"}, {0x00440000, GetSharedFont, "GetSharedFont"}, {0x00450040, nullptr, "GetWirelessRebootInfo"}, - {0x00460104, nullptr, "Wrap"}, - {0x00470104, nullptr, "Unwrap"}, + {0x00460104, Wrap, "Wrap"}, + {0x00470104, Unwrap, "Unwrap"}, {0x00480100, nullptr, "GetProgramInfo"}, {0x00490180, nullptr, "Reboot"}, {0x004A0040, nullptr, "GetCaptureInfo"}, diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp index effd23dce..ec5668d05 100644 --- a/src/core/hle/service/apt/apt_s.cpp +++ b/src/core/hle/service/apt/apt_s.cpp @@ -78,8 +78,8 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00430040, NotifyToWait, "NotifyToWait"}, {0x00440000, GetSharedFont, "GetSharedFont"}, {0x00450040, nullptr, "GetWirelessRebootInfo"}, - {0x00460104, nullptr, "Wrap"}, - {0x00470104, nullptr, "Unwrap"}, + {0x00460104, Wrap, "Wrap"}, + {0x00470104, Unwrap, "Unwrap"}, {0x00480100, nullptr, "GetProgramInfo"}, {0x00490180, nullptr, "Reboot"}, {0x004A0040, nullptr, "GetCaptureInfo"}, diff --git a/src/core/hle/service/apt/apt_u.cpp b/src/core/hle/service/apt/apt_u.cpp index e06084a1e..9dd002590 100644 --- a/src/core/hle/service/apt/apt_u.cpp +++ b/src/core/hle/service/apt/apt_u.cpp @@ -78,8 +78,8 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00430040, NotifyToWait, "NotifyToWait"}, {0x00440000, GetSharedFont, "GetSharedFont"}, {0x00450040, nullptr, "GetWirelessRebootInfo"}, - {0x00460104, nullptr, "Wrap"}, - {0x00470104, nullptr, "Unwrap"}, + {0x00460104, Wrap, "Wrap"}, + {0x00470104, Unwrap, "Unwrap"}, {0x00480100, nullptr, "GetProgramInfo"}, {0x00490180, nullptr, "Reboot"}, {0x004A0040, nullptr, "GetCaptureInfo"}, 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..34a9c8479 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 59dd6d1cd..4ddb1bc90 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -3,6 +3,8 @@ // Refer to the license.txt file included. #include <algorithm> +#include <array> +#include <cryptopp/sha.h> #include "common/file_util.h" #include "common/logging/log.h" #include "common/string_util.h" @@ -176,14 +178,29 @@ void SecureInfoGetRegion(Service::Interface* self) { } void GenHashConsoleUnique(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id_salt = cmd_buff[1]; - - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0x33646D6F ^ (app_id_salt & 0xFFFFF); // 3dmoo hash - cmd_buff[3] = 0x6F534841 ^ (app_id_salt & 0xFFFFF); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x03, 1, 0); + const u32 app_id_salt = rp.Pop<u32>() & 0x000FFFFF; + + IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); + + std::array<u8, 12> buffer; + const ResultCode result = GetConfigInfoBlock(ConsoleUniqueID2BlockID, 8, 2, buffer.data()); + rb.Push(result); + if (result.IsSuccess()) { + std::memcpy(&buffer[8], &app_id_salt, sizeof(u32)); + std::array<u8, CryptoPP::SHA256::DIGESTSIZE> hash; + CryptoPP::SHA256().CalculateDigest(hash.data(), buffer.data(), sizeof(buffer)); + u32 low, high; + memcpy(&low, &hash[hash.size() - 8], sizeof(u32)); + memcpy(&high, &hash[hash.size() - 4], sizeof(u32)); + rb.Push(low); + rb.Push(high); + } else { + rb.Push<u32>(0); + rb.Push<u32>(0); + } - LOG_WARNING(Service_CFG, "(STUBBED) called app_id_salt=0x%X", app_id_salt); + LOG_DEBUG(Service_CFG, "called app_id_salt=0x%X", app_id_salt); } void GetRegionCanadaUSA(Service::Interface* self) { @@ -322,47 +339,11 @@ static ResultVal<void*> GetConfigInfoBlockPointer(u32 block_id, u32 size, u32 fl return MakeResult<void*>(pointer); } -/// Checks if the language is available in the chosen region, and returns a proper one -static u8 AdjustLanguageInfoBlock(u32 region, u8 language) { - static const std::array<std::vector<u8>, 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; -} - 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); - // override the language setting if the region setting is auto - if (block_id == LanguageBlockID && - Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) { - u8 language; - memcpy(&language, output, sizeof(u8)); - language = AdjustLanguageInfoBlock(preferred_region_code, language); - memcpy(output, &language, sizeof(u8)); - } - return RESULT_SUCCESS; } @@ -586,9 +567,47 @@ void Init() { 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) { 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/fs/archive.h b/src/core/hle/service/fs/archive.h index 519c1f3a9..2ea956e0b 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -26,7 +26,7 @@ namespace FS { /// Supported archive types enum class ArchiveIdCode : u32 { - RomFS = 0x00000003, + SelfNCCH = 0x00000003, SaveData = 0x00000004, ExtSaveData = 0x00000006, SharedExtSaveData = 0x00000007, diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index 337da1387..33b290699 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -54,15 +54,17 @@ static void Initialize(Service::Interface* self) { * 3 : File handle */ static void OpenFile(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + // The helper should be passed by argument to the function + IPC::RequestParser rp(Kernel::GetCommandBuffer(), {0x080201C2}); + rp.Pop<u32>(); // Always 0 ? - ArchiveHandle archive_handle = MakeArchiveHandle(cmd_buff[2], cmd_buff[3]); - auto filename_type = static_cast<FileSys::LowPathType>(cmd_buff[4]); - u32 filename_size = cmd_buff[5]; + ArchiveHandle archive_handle = rp.Pop<u64>(); + auto filename_type = static_cast<FileSys::LowPathType>(rp.Pop<u32>()); + u32 filename_size = rp.Pop<u32>(); FileSys::Mode mode; - mode.hex = cmd_buff[6]; - u32 attributes = cmd_buff[7]; // TODO(Link Mauve): do something with those attributes. - u32 filename_ptr = cmd_buff[9]; + mode.hex = rp.Pop<u32>(); + u32 attributes = rp.Pop<u32>(); // TODO(Link Mauve): do something with those attributes. + VAddr filename_ptr = rp.PopStaticBuffer(); FileSys::Path file_path(filename_type, filename_size, filename_ptr); LOG_DEBUG(Service_FS, "path=%s, mode=%u attrs=%u", file_path.DebugStr().c_str(), mode.hex, @@ -70,16 +72,17 @@ static void OpenFile(Service::Interface* self) { ResultVal<std::shared_ptr<File>> file_res = OpenFileFromArchive(archive_handle, file_path, mode); - cmd_buff[1] = file_res.Code().raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(file_res.Code()); if (file_res.Succeeded()) { std::shared_ptr<File> file = *file_res; auto sessions = ServerSession::CreateSessionPair(file->GetName(), file); file->ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions)); - cmd_buff[3] = Kernel::g_handle_table - .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) - .MoveFrom(); + rb.PushMoveHandles(Kernel::g_handle_table + .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) + .MoveFrom()); } else { - cmd_buff[3] = 0; + rb.PushMoveHandles(0); LOG_ERROR(Service_FS, "failed to get a handle for file %s", file_path.DebugStr().c_str()); } } diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index a8c1331ed..a960778a7 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -4,6 +4,7 @@ #include "common/bit_field.h" #include "common/microprofile.h" +#include "core/core.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/result.h" @@ -118,10 +119,10 @@ static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, VAddr data_va * Updates sequential GSP GPU hardware registers using parallel arrays of source data and masks. * For each register, the value is updated only where the mask is high * - * @param base_address The address of the first register in the sequence + * @param base_address The address of the first register in the sequence * @param size_in_bytes The number of registers to update (size of data) - * @param data A pointer to the source data to use for updates - * @param masks A pointer to the masks + * @param data_vaddr A virtual address to the source data to use for updates + * @param masks_vaddr A virtual address to the masks * @return RESULT_SUCCESS if the parameters are valid, error code otherwise */ static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, VAddr data_vaddr, @@ -280,6 +281,7 @@ ResultCode SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) { if (screen_id == 0) { MicroProfileFlip(); + Core::System::GetInstance().perf_stats.EndGameFrame(); } return RESULT_SUCCESS; @@ -705,6 +707,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 +765,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 f14ab3811..fb3acb507 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -32,8 +32,8 @@ static u32 next_touch_index; static u32 next_accelerometer_index; static u32 next_gyroscope_index; -static int enable_accelerometer_count = 0; // positive means enabled -static int enable_gyroscope_count = 0; // positive means enabled +static int enable_accelerometer_count; // positive means enabled +static int enable_gyroscope_count; // positive means enabled static int pad_update_event; static int accelerometer_update_event; @@ -323,6 +323,9 @@ void Init() { next_accelerometer_index = 0; next_gyroscope_index = 0; + enable_accelerometer_count = 0; + enable_gyroscope_count = 0; + // Create event handles event_pad_or_touch_1 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch1"); event_pad_or_touch_2 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch2"); diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 21e66dfe0..c7f4ee138 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -42,8 +42,6 @@ struct PadState { BitField<14, 1, u32> zl; BitField<15, 1, u32> zr; - BitField<20, 1, u32> touch; - BitField<24, 1, u32> c_right; BitField<25, 1, u32> c_left; BitField<26, 1, u32> c_up; @@ -203,8 +201,6 @@ const PadState PAD_Y = {{1u << 11}}; const PadState PAD_ZL = {{1u << 14}}; const PadState PAD_ZR = {{1u << 15}}; -const PadState PAD_TOUCH = {{1u << 20}}; - const PadState PAD_C_RIGHT = {{1u << 24}}; const PadState PAD_C_LEFT = {{1u << 25}}; const PadState PAD_C_UP = {{1u << 26}}; diff --git a/src/core/hle/service/ir/ir.cpp b/src/core/hle/service/ir/ir.cpp index 7f1731a50..7ac34a990 100644 --- a/src/core/hle/service/ir/ir.cpp +++ b/src/core/hle/service/ir/ir.cpp @@ -2,9 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "core/hle/kernel/event.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/shared_memory.h" #include "core/hle/service/ir/ir.h" #include "core/hle/service/ir/ir_rst.h" #include "core/hle/service/ir/ir_u.h" @@ -14,101 +11,18 @@ namespace Service { namespace IR { -static Kernel::SharedPtr<Kernel::Event> handle_event; -static Kernel::SharedPtr<Kernel::Event> conn_status_event; -static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; -static Kernel::SharedPtr<Kernel::SharedMemory> transfer_shared_memory; - -void GetHandles(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0x4000000; - cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(); - cmd_buff[4] = Kernel::g_handle_table.Create(Service::IR::handle_event).MoveFrom(); -} - -void InitializeIrNopShared(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - u32 transfer_buff_size = cmd_buff[1]; - u32 recv_buff_size = cmd_buff[2]; - u32 unk1 = cmd_buff[3]; - u32 send_buff_size = cmd_buff[4]; - u32 unk2 = cmd_buff[5]; - u8 baud_rate = cmd_buff[6] & 0xFF; - Kernel::Handle handle = cmd_buff[8]; - - if (Kernel::g_handle_table.IsValid(handle)) { - transfer_shared_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(handle); - transfer_shared_memory->name = "IR:TransferSharedMemory"; - } - - cmd_buff[1] = RESULT_SUCCESS.raw; - - LOG_WARNING(Service_IR, "(STUBBED) called, transfer_buff_size=%d, recv_buff_size=%d, " - "unk1=%d, send_buff_size=%d, unk2=%d, baud_rate=%u, handle=0x%08X", - transfer_buff_size, recv_buff_size, unk1, send_buff_size, unk2, baud_rate, handle); -} - -void RequireConnection(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - conn_status_event->Signal(); - - cmd_buff[1] = RESULT_SUCCESS.raw; - - LOG_WARNING(Service_IR, "(STUBBED) called"); -} - -void Disconnect(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = RESULT_SUCCESS.raw; - - LOG_WARNING(Service_IR, "(STUBBED) called"); -} - -void GetConnectionStatusEvent(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::conn_status_event).MoveFrom(); - - LOG_WARNING(Service_IR, "(STUBBED) called"); -} - -void FinalizeIrNop(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = RESULT_SUCCESS.raw; - - LOG_WARNING(Service_IR, "(STUBBED) called"); -} - void Init() { - using namespace Kernel; - AddService(new IR_RST_Interface); AddService(new IR_U_Interface); AddService(new IR_User_Interface); - using Kernel::MemoryPermission; - shared_memory = SharedMemory::Create(nullptr, 0x1000, Kernel::MemoryPermission::ReadWrite, - Kernel::MemoryPermission::ReadWrite, 0, - Kernel::MemoryRegion::BASE, "IR:SharedMemory"); - transfer_shared_memory = nullptr; - - // Create event handle(s) - handle_event = Event::Create(ResetType::OneShot, "IR:HandleEvent"); - conn_status_event = Event::Create(ResetType::OneShot, "IR:ConnectionStatusEvent"); + InitUser(); + InitRST(); } void Shutdown() { - transfer_shared_memory = nullptr; - shared_memory = nullptr; - handle_event = nullptr; - conn_status_event = nullptr; + ShutdownUser(); + ShutdownRST(); } } // namespace IR diff --git a/src/core/hle/service/ir/ir.h b/src/core/hle/service/ir/ir.h index 72d44ce60..c741498e2 100644 --- a/src/core/hle/service/ir/ir.h +++ b/src/core/hle/service/ir/ir.h @@ -10,63 +10,6 @@ class Interface; namespace IR { -/** - * IR::GetHandles service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Translate header, used by the ARM11-kernel - * 3 : Shared memory handle - * 4 : Event handle - */ -void GetHandles(Interface* self); - -/** - * IR::InitializeIrNopShared service function - * Inputs: - * 1 : Size of transfer buffer - * 2 : Recv buffer size - * 3 : unknown - * 4 : Send buffer size - * 5 : unknown - * 6 : BaudRate (u8) - * 7 : 0 - * 8 : Handle of transfer shared memory - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -void InitializeIrNopShared(Interface* self); - -/** - * IR::FinalizeIrNop service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -void FinalizeIrNop(Interface* self); - -/** - * IR::GetConnectionStatusEvent service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Connection Status Event handle - */ -void GetConnectionStatusEvent(Interface* self); - -/** - * IR::Disconnect service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -void Disconnect(Interface* self); - -/** - * IR::RequireConnection service function - * Inputs: - * 1 : unknown (u8), looks like always 1 - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -void RequireConnection(Interface* self); - /// Initialize IR service void Init(); diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp index 1f10ebd3d..3f1275c53 100644 --- a/src/core/hle/service/ir/ir_rst.cpp +++ b/src/core/hle/service/ir/ir_rst.cpp @@ -2,12 +2,34 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/kernel/event.h" +#include "core/hle/kernel/shared_memory.h" #include "core/hle/service/ir/ir.h" #include "core/hle/service/ir/ir_rst.h" namespace Service { namespace IR { +static Kernel::SharedPtr<Kernel::Event> handle_event; +static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; + +/** + * IR::GetHandles service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Translate header, used by the ARM11-kernel + * 3 : Shared memory handle + * 4 : Event handle + */ +static void GetHandles(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = 0x4000000; + cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(); + cmd_buff[4] = Kernel::g_handle_table.Create(Service::IR::handle_event).MoveFrom(); +} + const Interface::FunctionInfo FunctionTable[] = { {0x00010000, GetHandles, "GetHandles"}, {0x00020080, nullptr, "Initialize"}, @@ -19,5 +41,20 @@ IR_RST_Interface::IR_RST_Interface() { Register(FunctionTable); } +void InitRST() { + using namespace Kernel; + + shared_memory = + SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, + MemoryPermission::ReadWrite, 0, MemoryRegion::BASE, "IR:SharedMemory"); + + handle_event = Event::Create(ResetType::OneShot, "IR:HandleEvent"); +} + +void ShutdownRST() { + shared_memory = nullptr; + handle_event = nullptr; +} + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir_rst.h b/src/core/hle/service/ir/ir_rst.h index a492e15c9..75b732627 100644 --- a/src/core/hle/service/ir/ir_rst.h +++ b/src/core/hle/service/ir/ir_rst.h @@ -18,5 +18,8 @@ public: } }; +void InitRST(); +void ShutdownRST(); + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir_u.cpp b/src/core/hle/service/ir/ir_u.cpp index 429615f31..ce00d5732 100644 --- a/src/core/hle/service/ir/ir_u.cpp +++ b/src/core/hle/service/ir/ir_u.cpp @@ -27,7 +27,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00100000, nullptr, "GetErrorStatus"}, {0x00110040, nullptr, "SetSleepModeActive"}, {0x00120040, nullptr, "SetSleepModeState"}, - // clang-format off + // clang-format on }; IR_U_Interface::IR_U_Interface() { diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp index 6cff1d544..b326d7fc7 100644 --- a/src/core/hle/service/ir/ir_user.cpp +++ b/src/core/hle/service/ir/ir_user.cpp @@ -2,12 +2,112 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/kernel/event.h" +#include "core/hle/kernel/shared_memory.h" #include "core/hle/service/ir/ir.h" #include "core/hle/service/ir/ir_user.h" namespace Service { namespace IR { +static Kernel::SharedPtr<Kernel::Event> conn_status_event; +static Kernel::SharedPtr<Kernel::SharedMemory> transfer_shared_memory; + +/** + * IR::InitializeIrNopShared service function + * Inputs: + * 1 : Size of transfer buffer + * 2 : Recv buffer size + * 3 : unknown + * 4 : Send buffer size + * 5 : unknown + * 6 : BaudRate (u8) + * 7 : 0 + * 8 : Handle of transfer shared memory + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void InitializeIrNopShared(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 transfer_buff_size = cmd_buff[1]; + u32 recv_buff_size = cmd_buff[2]; + u32 unk1 = cmd_buff[3]; + u32 send_buff_size = cmd_buff[4]; + u32 unk2 = cmd_buff[5]; + u8 baud_rate = cmd_buff[6] & 0xFF; + Kernel::Handle handle = cmd_buff[8]; + + if (Kernel::g_handle_table.IsValid(handle)) { + transfer_shared_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(handle); + transfer_shared_memory->name = "IR:TransferSharedMemory"; + } + + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_IR, "(STUBBED) called, transfer_buff_size=%d, recv_buff_size=%d, " + "unk1=%d, send_buff_size=%d, unk2=%d, baud_rate=%u, handle=0x%08X", + transfer_buff_size, recv_buff_size, unk1, send_buff_size, unk2, baud_rate, handle); +} + +/** + * IR::RequireConnection service function + * Inputs: + * 1 : unknown (u8), looks like always 1 + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void RequireConnection(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + conn_status_event->Signal(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_IR, "(STUBBED) called"); +} + +/** + * IR::Disconnect service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void Disconnect(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_IR, "(STUBBED) called"); +} + +/** + * IR::GetConnectionStatusEvent service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Connection Status Event handle + */ +static void GetConnectionStatusEvent(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::conn_status_event).MoveFrom(); + + LOG_WARNING(Service_IR, "(STUBBED) called"); +} + +/** + * IR::FinalizeIrNop service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void FinalizeIrNop(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_IR, "(STUBBED) called"); +} + const Interface::FunctionInfo FunctionTable[] = { {0x00010182, nullptr, "InitializeIrNop"}, {0x00020000, FinalizeIrNop, "FinalizeIrNop"}, @@ -41,5 +141,17 @@ IR_User_Interface::IR_User_Interface() { Register(FunctionTable); } +void InitUser() { + using namespace Kernel; + + transfer_shared_memory = nullptr; + conn_status_event = Event::Create(ResetType::OneShot, "IR:ConnectionStatusEvent"); +} + +void ShutdownUser() { + transfer_shared_memory = nullptr; + conn_status_event = nullptr; +} + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir_user.h b/src/core/hle/service/ir/ir_user.h index 71c932ffa..3849bd923 100644 --- a/src/core/hle/service/ir/ir_user.h +++ b/src/core/hle/service/ir/ir_user.h @@ -18,5 +18,8 @@ public: } }; +void InitUser(); +void ShutdownUser(); + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ldr_ro/cro_helper.h b/src/core/hle/service/ldr_ro/cro_helper.h index 060d5a55f..3bc10dbdc 100644 --- a/src/core/hle/service/ldr_ro/cro_helper.h +++ b/src/core/hle/service/ldr_ro/cro_helper.h @@ -57,7 +57,7 @@ public: * @param is_crs true if the module itself is the static module * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. */ - ResultCode Rebase(VAddr crs_address, u32 cro_size, VAddr data_segment_addresss, + ResultCode Rebase(VAddr crs_address, u32 cro_size, VAddr data_segment_address, u32 data_segment_size, VAddr bss_segment_address, u32 bss_segment_size, bool is_crs); @@ -102,7 +102,7 @@ public: /** * Registers this module and adds it to the module list. * @param crs_address the virtual address of the static module - * @auto_link whether to register as an auto link module + * @param auto_link whether to register as an auto link module */ void Register(VAddr crs_address, bool auto_link); diff --git a/src/core/hle/service/ldr_ro/ldr_ro.cpp b/src/core/hle/service/ldr_ro/ldr_ro.cpp index 8d00a7577..7af76676b 100644 --- a/src/core/hle/service/ldr_ro/ldr_ro.cpp +++ b/src/core/hle/service/ldr_ro/ldr_ro.cpp @@ -6,6 +6,7 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "core/arm/arm_interface.h" +#include "core/core.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/vm_manager.h" #include "core/hle/service/ldr_ro/cro_helper.h" diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp index c62f8afc6..e98388560 100644 --- a/src/core/hle/service/mic_u.cpp +++ b/src/core/hle/service/mic_u.cpp @@ -93,7 +93,7 @@ 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; @@ -202,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); } @@ -252,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); } @@ -282,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 e248285f9..fd3c7d9c2 100644 --- a/src/core/hle/service/nfc/nfc.cpp +++ b/src/core/hle/service/nfc/nfc.cpp @@ -11,6 +11,81 @@ 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(); @@ -22,16 +97,46 @@ void GetTagInRangeEvent(Interface* self) { 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 b02354201..a013bdae7 100644 --- a/src/core/hle/service/nfc/nfc.h +++ b/src/core/hle/service/nfc/nfc.h @@ -4,12 +4,103 @@ #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: @@ -21,6 +112,37 @@ namespace NFC { */ 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(); diff --git a/src/core/hle/service/nfc/nfc_m.cpp b/src/core/hle/service/nfc/nfc_m.cpp index f43b4029a..ebe637650 100644 --- a/src/core/hle/service/nfc/nfc_m.cpp +++ b/src/core/hle/service/nfc/nfc_m.cpp @@ -11,18 +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"}, {0x000B0000, GetTagInRangeEvent, "GetTagInRangeEvent"}, - {0x000D0000, nullptr, "GetTagState"}, - {0x000F0000, nullptr, "CommunicationGetStatus"}, + {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 4b5200ae8..5a40c7874 100644 --- a/src/core/hle/service/nfc/nfc_u.cpp +++ b/src/core/hle/service/nfc/nfc_u.cpp @@ -10,18 +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"}, {0x000B0000, GetTagInRangeEvent, "GetTagInRangeEvent"}, - {0x000D0000, nullptr, "GetTagState"}, - {0x000F0000, nullptr, "CommunicationGetStatus"}, + {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/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp index 0be94322c..63c334cb2 100644 --- a/src/core/hle/service/nim/nim.cpp +++ b/src/core/hle/service/nim/nim.cpp @@ -19,7 +19,7 @@ void CheckSysUpdateAvailable(Service::Interface* self) { cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 0; // No update available - LOG_WARNING(Service_NWM, "(STUBBED) called"); + LOG_WARNING(Service_NIM, "(STUBBED) called"); } void Init() { diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index a7ba7688f..e6a5f1417 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -10,6 +10,7 @@ #include <boost/container/flat_map.hpp> #include "common/common_types.h" #include "core/hle/ipc.h" +#include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/thread.h" #include "core/hle/result.h" diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp index 31bb466fc..907d9c8fa 100644 --- a/src/core/hle/service/y2r_u.cpp +++ b/src/core/hle/service/y2r_u.cpp @@ -281,37 +281,39 @@ static void GetTransferEndEvent(Interface* self) { } static void SetSendingY(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - conversion.src_Y.address = cmd_buff[1]; - conversion.src_Y.image_size = cmd_buff[2]; - conversion.src_Y.transfer_unit = cmd_buff[3]; - conversion.src_Y.gap = cmd_buff[4]; + // The helper should be passed by argument to the function + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x00100102); + conversion.src_Y.address = rp.Pop<u32>(); + conversion.src_Y.image_size = rp.Pop<u32>(); + conversion.src_Y.transfer_unit = rp.Pop<u32>(); + conversion.src_Y.gap = rp.Pop<u32>(); + Kernel::Handle src_process_handle = rp.PopHandle(); - cmd_buff[0] = IPC::MakeHeader(0x10, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "src_process_handle=0x%08X", conversion.src_Y.image_size, conversion.src_Y.transfer_unit, conversion.src_Y.gap, - cmd_buff[6]); + src_process_handle); } static void SetSendingU(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + // The helper should be passed by argument to the function + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x00110102); + conversion.src_U.address = rp.Pop<u32>(); + conversion.src_U.image_size = rp.Pop<u32>(); + conversion.src_U.transfer_unit = rp.Pop<u32>(); + conversion.src_U.gap = rp.Pop<u32>(); + Kernel::Handle src_process_handle = rp.PopHandle(); - conversion.src_U.address = cmd_buff[1]; - conversion.src_U.image_size = cmd_buff[2]; - conversion.src_U.transfer_unit = cmd_buff[3]; - conversion.src_U.gap = cmd_buff[4]; - - cmd_buff[0] = IPC::MakeHeader(0x11, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "src_process_handle=0x%08X", conversion.src_U.image_size, conversion.src_U.transfer_unit, conversion.src_U.gap, - cmd_buff[6]); + src_process_handle); } static void SetSendingV(Interface* self) { @@ -561,11 +563,10 @@ static void GetAlpha(Interface* self) { } static void SetDitheringWeightParams(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - std::memcpy(&dithering_weight_params, &cmd_buff[1], sizeof(DitheringWeightParams)); - - cmd_buff[0] = IPC::MakeHeader(0x24, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x24, 8, 0); // 0x240200 + rp.PopRaw(dithering_weight_params); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called"); } diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index 96db39ad9..4e0c3fb8b 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -556,11 +556,21 @@ static ResultCode CreateThread(Kernel::Handle* out_handle, s32 priority, u32 ent break; } - if (processor_id == THREADPROCESSORID_1 || processor_id == THREADPROCESSORID_ALL || - (processor_id == THREADPROCESSORID_DEFAULT && - Kernel::g_current_process->ideal_processor == THREADPROCESSORID_1)) { - LOG_WARNING(Kernel_SVC, - "Newly created thread is allowed to be run in the SysCore, unimplemented."); + if (processor_id == THREADPROCESSORID_ALL) { + LOG_INFO(Kernel_SVC, + "Newly created thread is allowed to be run in any Core, unimplemented."); + } + + if (processor_id == THREADPROCESSORID_DEFAULT && + Kernel::g_current_process->ideal_processor == THREADPROCESSORID_1) { + LOG_WARNING( + Kernel_SVC, + "Newly created thread is allowed to be run in the SysCore (Core1), unimplemented."); + } + + if (processor_id == THREADPROCESSORID_1) { + LOG_ERROR(Kernel_SVC, + "Newly created thread must run in the SysCore (Core1), unimplemented."); } CASCADE_RESULT(SharedPtr<Thread> thread, Kernel::Thread::Create(name, entry_point, priority, @@ -837,6 +847,11 @@ static ResultCode SetTimer(Kernel::Handle handle, s64 initial, s64 interval) { LOG_TRACE(Kernel_SVC, "called timer=0x%08X", handle); + if (initial < 0 || interval < 0) { + return ResultCode(ErrorDescription::OutOfRange, ErrorModule::Kernel, + ErrorSummary::InvalidArgument, ErrorLevel::Permanent); + } + SharedPtr<Timer> timer = Kernel::g_handle_table.Get<Timer>(handle); if (timer == nullptr) return ERR_INVALID_HANDLE; diff --git a/src/core/hw/aes/arithmetic128.cpp b/src/core/hw/aes/arithmetic128.cpp new file mode 100644 index 000000000..55b954a52 --- /dev/null +++ b/src/core/hw/aes/arithmetic128.cpp @@ -0,0 +1,47 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <functional> +#include "core/hw/aes/arithmetic128.h" + +namespace HW { +namespace AES { + +AESKey Lrot128(const AESKey& in, u32 rot) { + AESKey out; + rot %= 128; + const u32 byte_shift = rot / 8; + const u32 bit_shift = rot % 8; + + for (u32 i = 0; i < 16; i++) { + const u32 wrap_index_a = (i + byte_shift) % 16; + const u32 wrap_index_b = (i + byte_shift + 1) % 16; + out[i] = ((in[wrap_index_a] << bit_shift) | (in[wrap_index_b] >> (8 - bit_shift))) & 0xFF; + } + return out; +} + +AESKey Add128(const AESKey& a, const AESKey& b) { + AESKey out; + u32 carry = 0; + u32 sum = 0; + + for (int i = 15; i >= 0; i--) { + sum = a[i] + b[i] + carry; + carry = sum >> 8; + out[i] = static_cast<u8>(sum & 0xff); + } + + return out; +} + +AESKey Xor128(const AESKey& a, const AESKey& b) { + AESKey out; + std::transform(a.cbegin(), a.cend(), b.cbegin(), out.begin(), std::bit_xor<>()); + return out; +} + +} // namespace AES +} // namespace HW diff --git a/src/core/hw/aes/arithmetic128.h b/src/core/hw/aes/arithmetic128.h new file mode 100644 index 000000000..d670e2ce2 --- /dev/null +++ b/src/core/hw/aes/arithmetic128.h @@ -0,0 +1,17 @@ +// 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 "core/hw/aes/key.h" + +namespace HW { +namespace AES { +AESKey Lrot128(const AESKey& in, u32 rot); +AESKey Add128(const AESKey& a, const AESKey& b); +AESKey Xor128(const AESKey& a, const AESKey& b); + +} // namspace AES +} // namespace HW diff --git a/src/core/hw/aes/ccm.cpp b/src/core/hw/aes/ccm.cpp new file mode 100644 index 000000000..dc7035ab6 --- /dev/null +++ b/src/core/hw/aes/ccm.cpp @@ -0,0 +1,95 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <cryptopp/aes.h> +#include <cryptopp/ccm.h> +#include <cryptopp/cryptlib.h> +#include <cryptopp/filters.h> +#include "common/alignment.h" +#include "common/logging/log.h" +#include "core/hw/aes/ccm.h" +#include "core/hw/aes/key.h" + +namespace HW { +namespace AES { + +namespace { + +// 3DS uses a non-standard AES-CCM algorithm, so we need to derive a sub class from the standard one +// and override with the non-standard part. +using CryptoPP::lword; +using CryptoPP::AES; +using CryptoPP::CCM_Final; +using CryptoPP::CCM_Base; +template <bool T_IsEncryption> +class CCM_3DSVariant_Final : public CCM_Final<AES, CCM_MAC_SIZE, T_IsEncryption> { +public: + void UncheckedSpecifyDataLengths(lword header_length, lword message_length, + lword footer_length) override { + // 3DS uses the aligned size to generate B0 for authentication, instead of the original size + lword aligned_message_length = Common::AlignUp(message_length, AES_BLOCK_SIZE); + CCM_Base::UncheckedSpecifyDataLengths(header_length, aligned_message_length, footer_length); + CCM_Base::m_messageLength = message_length; // restore the actual message size + } +}; + +class CCM_3DSVariant { +public: + using Encryption = CCM_3DSVariant_Final<true>; + using Decryption = CCM_3DSVariant_Final<false>; +}; + +} // namespace + +std::vector<u8> EncryptSignCCM(const std::vector<u8>& pdata, const CCMNonce& nonce, + size_t slot_id) { + if (!IsNormalKeyAvailable(slot_id)) { + LOG_ERROR(HW_AES, "Key slot %d not available. Will use zero key.", slot_id); + } + const AESKey normal = GetNormalKey(slot_id); + std::vector<u8> cipher(pdata.size() + CCM_MAC_SIZE); + + try { + CCM_3DSVariant::Encryption e; + e.SetKeyWithIV(normal.data(), AES_BLOCK_SIZE, nonce.data(), CCM_NONCE_SIZE); + e.SpecifyDataLengths(0, pdata.size(), 0); + CryptoPP::ArraySource as(pdata.data(), pdata.size(), true, + new CryptoPP::AuthenticatedEncryptionFilter( + e, new CryptoPP::ArraySink(cipher.data(), cipher.size()))); + } catch (const CryptoPP::Exception& e) { + LOG_ERROR(HW_AES, "FAILED with: %s", e.what()); + } + return cipher; +} + +std::vector<u8> DecryptVerifyCCM(const std::vector<u8>& cipher, const CCMNonce& nonce, + size_t slot_id) { + if (!IsNormalKeyAvailable(slot_id)) { + LOG_ERROR(HW_AES, "Key slot %d not available. Will use zero key.", slot_id); + } + const AESKey normal = GetNormalKey(slot_id); + const std::size_t pdata_size = cipher.size() - CCM_MAC_SIZE; + std::vector<u8> pdata(pdata_size); + + try { + CCM_3DSVariant::Decryption d; + d.SetKeyWithIV(normal.data(), AES_BLOCK_SIZE, nonce.data(), CCM_NONCE_SIZE); + d.SpecifyDataLengths(0, pdata_size, 0); + CryptoPP::AuthenticatedDecryptionFilter df( + d, new CryptoPP::ArraySink(pdata.data(), pdata_size)); + CryptoPP::ArraySource as(cipher.data(), cipher.size(), true, new CryptoPP::Redirector(df)); + if (!df.GetLastResult()) { + LOG_ERROR(HW_AES, "FAILED"); + return {}; + } + } catch (const CryptoPP::Exception& e) { + LOG_ERROR(HW_AES, "FAILED with: %s", e.what()); + return {}; + } + return pdata; +} + +} // namespace AES +} // namespace HW diff --git a/src/core/hw/aes/ccm.h b/src/core/hw/aes/ccm.h new file mode 100644 index 000000000..bf4146e80 --- /dev/null +++ b/src/core/hw/aes/ccm.h @@ -0,0 +1,40 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include <vector> +#include "common/common_types.h" + +namespace HW { +namespace AES { + +constexpr size_t CCM_NONCE_SIZE = 12; +constexpr size_t CCM_MAC_SIZE = 16; + +using CCMNonce = std::array<u8, CCM_NONCE_SIZE>; + +/** + * Encrypts and adds a MAC to the given data using AES-CCM algorithm. + * @param pdata The plain text data to encrypt + * @param nonce The nonce data to use for encryption + * @param slot_id The slot ID of the key to use for encryption + * @returns a vector of u8 containing the encrypted data with MAC at the end + */ +std::vector<u8> EncryptSignCCM(const std::vector<u8>& pdata, const CCMNonce& nonce, size_t slot_id); + +/** + * Decrypts and verify the MAC of the given data using AES-CCM algorithm. + * @param cipher The cipher text data to decrypt, with MAC at the end to verify + * @param nonce The nonce data to use for decryption + * @param slot_id The slot ID of the key to use for decryption + * @returns a vector of u8 containing the decrypted data; an empty vector if the verification fails + */ +std::vector<u8> DecryptVerifyCCM(const std::vector<u8>& cipher, const CCMNonce& nonce, + size_t slot_id); + +} // namespace AES +} // namespace HW diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp new file mode 100644 index 000000000..4e8a8a59a --- /dev/null +++ b/src/core/hw/aes/key.cpp @@ -0,0 +1,173 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <exception> +#include <sstream> +#include <boost/optional.hpp> +#include "common/common_paths.h" +#include "common/file_util.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/hw/aes/arithmetic128.h" +#include "core/hw/aes/key.h" + +namespace HW { +namespace AES { + +namespace { + +boost::optional<AESKey> generator_constant; + +struct KeySlot { + boost::optional<AESKey> x; + boost::optional<AESKey> y; + boost::optional<AESKey> normal; + + void SetKeyX(const AESKey& key) { + x = key; + if (y && generator_constant) { + GenerateNormalKey(); + } + } + + void SetKeyY(const AESKey& key) { + y = key; + if (x && generator_constant) { + GenerateNormalKey(); + } + } + + void SetNormalKey(const AESKey& key) { + normal = key; + } + + void GenerateNormalKey() { + normal = Lrot128(Add128(Xor128(Lrot128(*x, 2), *y), *generator_constant), 87); + } + + void Clear() { + x.reset(); + y.reset(); + normal.reset(); + } +}; + +std::array<KeySlot, KeySlotID::MaxKeySlotID> key_slots; + +void ClearAllKeys() { + for (KeySlot& slot : key_slots) { + slot.Clear(); + } + generator_constant.reset(); +} + +AESKey HexToKey(const std::string& hex) { + if (hex.size() < 32) { + throw std::invalid_argument("hex string is too short"); + } + + AESKey key; + for (size_t i = 0; i < key.size(); ++i) { + key[i] = static_cast<u8>(std::stoi(hex.substr(i * 2, 2), 0, 16)); + } + + return key; +} + +void LoadPresetKeys() { + const std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + AES_KEYS; + FileUtil::CreateFullPath(filepath); // Create path if not already created + std::ifstream file; + OpenFStream(file, filepath, std::ios_base::in); + if (!file) { + return; + } + + while (!file.eof()) { + std::string line; + std::getline(file, line); + std::vector<std::string> parts; + Common::SplitString(line, '=', parts); + if (parts.size() != 2) { + LOG_ERROR(HW_AES, "Failed to parse %s", line.c_str()); + continue; + } + + const std::string& name = parts[0]; + AESKey key; + try { + key = HexToKey(parts[1]); + } catch (const std::logic_error& e) { + LOG_ERROR(HW_AES, "Invalid key %s: %s", parts[1].c_str(), e.what()); + continue; + } + + if (name == "generator") { + generator_constant = key; + continue; + } + + size_t slot_id; + char key_type; + if (std::sscanf(name.c_str(), "slot0x%zXKey%c", &slot_id, &key_type) != 2) { + LOG_ERROR(HW_AES, "Invalid key name %s", name.c_str()); + continue; + } + + if (slot_id >= MaxKeySlotID) { + LOG_ERROR(HW_AES, "Out of range slot ID 0x%zX", slot_id); + continue; + } + + switch (key_type) { + case 'X': + key_slots.at(slot_id).SetKeyX(key); + break; + case 'Y': + key_slots.at(slot_id).SetKeyY(key); + break; + case 'N': + key_slots.at(slot_id).SetNormalKey(key); + break; + default: + LOG_ERROR(HW_AES, "Invalid key type %c", key_type); + break; + } + } +} + +} // namespace + +void InitKeys() { + ClearAllKeys(); + LoadPresetKeys(); +} + +void SetGeneratorConstant(const AESKey& key) { + generator_constant = key; +} + +void SetKeyX(size_t slot_id, const AESKey& key) { + key_slots.at(slot_id).SetKeyX(key); +} + +void SetKeyY(size_t slot_id, const AESKey& key) { + key_slots.at(slot_id).SetKeyY(key); +} + +void SetNormalKey(size_t slot_id, const AESKey& key) { + key_slots.at(slot_id).SetNormalKey(key); +} + +bool IsNormalKeyAvailable(size_t slot_id) { + return key_slots.at(slot_id).normal.is_initialized(); +} + +AESKey GetNormalKey(size_t slot_id) { + return key_slots.at(slot_id).normal.value_or(AESKey{}); +} + +} // namespace AES +} // namespace HW diff --git a/src/core/hw/aes/key.h b/src/core/hw/aes/key.h new file mode 100644 index 000000000..b01d04f13 --- /dev/null +++ b/src/core/hw/aes/key.h @@ -0,0 +1,35 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include "common/common_types.h" + +namespace HW { +namespace AES { + +enum KeySlotID : size_t { + APTWrap = 0x31, + + MaxKeySlotID = 0x40, +}; + +constexpr size_t AES_BLOCK_SIZE = 16; + +using AESKey = std::array<u8, AES_BLOCK_SIZE>; + +void InitKeys(); + +void SetGeneratorConstant(const AESKey& key); +void SetKeyX(size_t slot_id, const AESKey& key); +void SetKeyY(size_t slot_id, const AESKey& key); +void SetNormalKey(size_t slot_id, const AESKey& key); + +bool IsNormalKeyAvailable(size_t slot_id); +AESKey GetNormalKey(size_t slot_id); + +} // namspace AES +} // namespace HW diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index fa8c13d36..42809c731 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -8,17 +8,13 @@ #include "common/color.h" #include "common/common_types.h" #include "common/logging/log.h" -#include "common/math_util.h" #include "common/microprofile.h" -#include "common/thread.h" -#include "common/timer.h" #include "common/vector_math.h" #include "core/core_timing.h" #include "core/hle/service/gsp_gpu.h" #include "core/hw/gpu.h" #include "core/hw/hw.h" #include "core/memory.h" -#include "core/settings.h" #include "core/tracer/recorder.h" #include "video_core/command_processor.h" #include "video_core/debug_utils/debug_utils.h" @@ -32,19 +28,9 @@ namespace GPU { Regs g_regs; /// 268MHz CPU clocks / 60Hz frames per second -const u64 frame_ticks = BASE_CLOCK_RATE_ARM11 / 60; +const u64 frame_ticks = BASE_CLOCK_RATE_ARM11 / SCREEN_REFRESH_RATE; /// Event id for CoreTiming static int vblank_event; -/// Total number of frames drawn -static u64 frame_count; -/// Start clock for frame limiter -static u32 time_point; -/// Total delay caused by slow frames -static float time_delay; -constexpr float FIXED_FRAME_TIME = 1000.0f / 60; -// Max lag caused by slow frames. Can be adjusted to compensate for too many slow frames. Higher -// values increases time needed to limit frame rate after spikes -constexpr float MAX_LAG_TIME = 18; template <typename T> inline void Read(T& var, const u32 raw_addr) { @@ -522,24 +508,8 @@ template void Write<u32>(u32 addr, const u32 data); template void Write<u16>(u32 addr, const u16 data); template void Write<u8>(u32 addr, const u8 data); -static void FrameLimiter() { - time_delay += FIXED_FRAME_TIME; - time_delay = MathUtil::Clamp(time_delay, -MAX_LAG_TIME, MAX_LAG_TIME); - s32 desired_time = static_cast<s32>(time_delay); - s32 elapsed_time = static_cast<s32>(Common::Timer::GetTimeMs() - time_point); - - if (elapsed_time < desired_time) { - Common::SleepCurrentThread(desired_time - elapsed_time); - } - - u32 frame_time = Common::Timer::GetTimeMs() - time_point; - - time_delay -= frame_time; -} - /// Update hardware static void VBlankCallback(u64 userdata, int cycles_late) { - frame_count++; VideoCore::g_renderer->SwapBuffers(); // Signal to GSP that GPU interrupt has occurred @@ -550,12 +520,6 @@ static void VBlankCallback(u64 userdata, int cycles_late) { Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC0); Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC1); - if (!Settings::values.use_vsync && Settings::values.toggle_framelimit) { - FrameLimiter(); - } - - time_point = Common::Timer::GetTimeMs(); - // Reschedule recurrent event CoreTiming::ScheduleEvent(frame_ticks - cycles_late, vblank_event); } @@ -590,9 +554,6 @@ void Init() { framebuffer_sub.color_format.Assign(Regs::PixelFormat::RGB8); framebuffer_sub.active_fb = 0; - frame_count = 0; - time_point = Common::Timer::GetTimeMs(); - vblank_event = CoreTiming::RegisterEvent("GPU::VBlankCallback", VBlankCallback); CoreTiming::ScheduleEvent(frame_ticks, vblank_event); diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h index d53381216..bdd997b2a 100644 --- a/src/core/hw/gpu.h +++ b/src/core/hw/gpu.h @@ -13,6 +13,8 @@ namespace GPU { +constexpr float SCREEN_REFRESH_RATE = 60; + // Returns index corresponding to the Regs member labeled by field_name // TODO: Due to Visual studio bug 209229, offsetof does not return constant expressions // when used with array elements (e.g. GPU_REG_INDEX(memory_fill_config[0])). diff --git a/src/core/hw/hw.cpp b/src/core/hw/hw.cpp index 9ff8825b2..8499f2ce6 100644 --- a/src/core/hw/hw.cpp +++ b/src/core/hw/hw.cpp @@ -4,6 +4,7 @@ #include "common/common_types.h" #include "common/logging/log.h" +#include "core/hw/aes/key.h" #include "core/hw/gpu.h" #include "core/hw/hw.h" #include "core/hw/lcd.h" @@ -85,6 +86,7 @@ void Update() {} /// Initialize hardware void Init() { + AES::InitKeys(); GPU::Init(); LCD::Init(); LOG_DEBUG(HW, "initialized OK"); diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 09266e8b0..74e336487 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -5,7 +5,7 @@ #include <algorithm> #include <vector> #include "common/logging/log.h" -#include "core/file_sys/archive_romfs.h" +#include "core/file_sys/archive_selfncch.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/service/fs/archive.h" @@ -277,8 +277,8 @@ ResultStatus AppLoader_THREEDSX::Load() { Kernel::g_current_process->Run(48, Kernel::DEFAULT_STACK_SIZE); - Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this), - Service::FS::ArchiveIdCode::RomFS); + Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_SelfNCCH>(*this), + Service::FS::ArchiveIdCode::SelfNCCH); is_loaded = true; return ResultStatus::Success; diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index a6c2a745f..1d80766ae 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -54,7 +54,7 @@ FileType IdentifyFile(const std::string& file_name); * @return FileType of file. Note: this will return FileType::Unknown if it is unable to determine * a filetype, and will never return FileType::Error. */ -FileType GuessFromExtension(const std::string& extension_); +FileType GuessFromExtension(const std::string& extension); /** * Convert a FileType into a string which can be displayed to the user. diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 5df33f6d2..98b8259d9 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -8,7 +8,7 @@ #include "common/logging/log.h" #include "common/string_util.h" #include "common/swap.h" -#include "core/file_sys/archive_romfs.h" +#include "core/file_sys/archive_selfncch.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/service/cfg/cfg.h" @@ -342,8 +342,8 @@ ResultStatus AppLoader_NCCH::Load() { if (ResultStatus::Success != result) return result; - Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this), - Service::FS::ArchiveIdCode::RomFS); + Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_SelfNCCH>(*this), + Service::FS::ArchiveIdCode::SelfNCCH); ParseRegionLockoutInfo(); diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp new file mode 100644 index 000000000..2cdfb9ded --- /dev/null +++ b/src/core/perf_stats.cpp @@ -0,0 +1,105 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <chrono> +#include <mutex> +#include <thread> +#include "common/math_util.h" +#include "core/hw/gpu.h" +#include "core/perf_stats.h" +#include "core/settings.h" + +using namespace std::chrono_literals; +using DoubleSecs = std::chrono::duration<double, std::chrono::seconds::period>; +using std::chrono::duration_cast; +using std::chrono::microseconds; + +namespace Core { + +void PerfStats::BeginSystemFrame() { + std::lock_guard<std::mutex> lock(object_mutex); + + frame_begin = Clock::now(); +} + +void PerfStats::EndSystemFrame() { + std::lock_guard<std::mutex> lock(object_mutex); + + auto frame_end = Clock::now(); + accumulated_frametime += frame_end - frame_begin; + system_frames += 1; + + previous_frame_length = frame_end - previous_frame_end; + previous_frame_end = frame_end; +} + +void PerfStats::EndGameFrame() { + std::lock_guard<std::mutex> lock(object_mutex); + + game_frames += 1; +} + +PerfStats::Results PerfStats::GetAndResetStats(u64 current_system_time_us) { + std::lock_guard<std::mutex> lock(object_mutex); + + auto now = Clock::now(); + // Walltime elapsed since stats were reset + auto interval = duration_cast<DoubleSecs>(now - reset_point).count(); + + auto system_us_per_second = + static_cast<double>(current_system_time_us - reset_point_system_us) / interval; + + Results results{}; + results.system_fps = static_cast<double>(system_frames) / interval; + results.game_fps = static_cast<double>(game_frames) / interval; + results.frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() / + static_cast<double>(system_frames); + results.emulation_speed = system_us_per_second / 1'000'000.0; + + // Reset counters + reset_point = now; + reset_point_system_us = current_system_time_us; + accumulated_frametime = Clock::duration::zero(); + system_frames = 0; + game_frames = 0; + + return results; +} + +double PerfStats::GetLastFrameTimeScale() { + std::lock_guard<std::mutex> lock(object_mutex); + + constexpr double FRAME_LENGTH = 1.0 / GPU::SCREEN_REFRESH_RATE; + return duration_cast<DoubleSecs>(previous_frame_length).count() / FRAME_LENGTH; +} + +void FrameLimiter::DoFrameLimiting(u64 current_system_time_us) { + // Max lag caused by slow frames. Can be adjusted to compensate for too many slow frames. Higher + // values increase the time needed to recover and limit framerate again after spikes. + constexpr microseconds MAX_LAG_TIME_US = 25ms; + + if (!Settings::values.toggle_framelimit) { + return; + } + + auto now = Clock::now(); + + frame_limiting_delta_err += microseconds(current_system_time_us - previous_system_time_us); + frame_limiting_delta_err -= duration_cast<microseconds>(now - previous_walltime); + frame_limiting_delta_err = + MathUtil::Clamp(frame_limiting_delta_err, -MAX_LAG_TIME_US, MAX_LAG_TIME_US); + + if (frame_limiting_delta_err > microseconds::zero()) { + std::this_thread::sleep_for(frame_limiting_delta_err); + + auto now_after_sleep = Clock::now(); + frame_limiting_delta_err -= duration_cast<microseconds>(now_after_sleep - now); + now = now_after_sleep; + } + + previous_system_time_us = current_system_time_us; + previous_walltime = now; +} + +} // namespace Core diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h new file mode 100644 index 000000000..362b205c8 --- /dev/null +++ b/src/core/perf_stats.h @@ -0,0 +1,83 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <chrono> +#include <mutex> +#include "common/common_types.h" + +namespace Core { + +/** + * Class to manage and query performance/timing statistics. All public functions of this class are + * thread-safe unless stated otherwise. + */ +class PerfStats { +public: + using Clock = std::chrono::high_resolution_clock; + + struct Results { + /// System FPS (LCD VBlanks) in Hz + double system_fps; + /// Game FPS (GSP frame submissions) in Hz + double game_fps; + /// Walltime per system frame, in seconds, excluding any waits + double frametime; + /// Ratio of walltime / emulated time elapsed + double emulation_speed; + }; + + void BeginSystemFrame(); + void EndSystemFrame(); + void EndGameFrame(); + + Results GetAndResetStats(u64 current_system_time_us); + + /** + * Gets the ratio between walltime and the emulated time of the previous system frame. This is + * useful for scaling inputs or outputs moving between the two time domains. + */ + double GetLastFrameTimeScale(); + +private: + std::mutex object_mutex; + + /// Point when the cumulative counters were reset + Clock::time_point reset_point = Clock::now(); + /// System time when the cumulative counters were reset + u64 reset_point_system_us = 0; + + /// Cumulative duration (excluding v-sync/frame-limiting) of frames since last reset + Clock::duration accumulated_frametime = Clock::duration::zero(); + /// Cumulative number of system frames (LCD VBlanks) presented since last reset + u32 system_frames = 0; + /// Cumulative number of game frames (GSP frame submissions) since last reset + u32 game_frames = 0; + + /// Point when the previous system frame ended + Clock::time_point previous_frame_end = reset_point; + /// Point when the current system frame began + Clock::time_point frame_begin = reset_point; + /// Total visible duration (including frame-limiting, etc.) of the previous system frame + Clock::duration previous_frame_length = Clock::duration::zero(); +}; + +class FrameLimiter { +public: + using Clock = std::chrono::high_resolution_clock; + + void DoFrameLimiting(u64 current_system_time_us); + +private: + /// Emulated system time (in microseconds) at the last limiter invocation + u64 previous_system_time_us = 0; + /// Walltime at the last limiter invocation + Clock::time_point previous_walltime = Clock::now(); + + /// Accumulated difference between walltime and emulated time + std::chrono::microseconds frame_limiting_delta_err{0}; +}; + +} // namespace Core diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 9afaf79ec..3a32b70aa 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -15,7 +15,7 @@ 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; diff --git a/src/core/settings.h b/src/core/settings.h index 8dbda653a..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 { @@ -104,6 +105,11 @@ 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; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 6ca319b59..5317719e8 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -1,26 +1,46 @@ set(SRCS + command_processor.cpp + debug_utils/debug_utils.cpp + pica.cpp + primitive_assembly.cpp + regs.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 - debug_utils/debug_utils.cpp - clipper.cpp - command_processor.cpp - pica.cpp - primitive_assembly.cpp - rasterizer.cpp - renderer_base.cpp shader/shader.cpp shader/shader_interpreter.cpp - swrasterizer.cpp + swrasterizer/clipper.cpp + swrasterizer/framebuffer.cpp + swrasterizer/rasterizer.cpp + swrasterizer/swrasterizer.cpp + swrasterizer/texturing.cpp + texture/etc1.cpp + texture/texture_decode.cpp vertex_loader.cpp video_core.cpp ) set(HEADERS + command_processor.h debug_utils/debug_utils.h + gpu_debugger.h + pica.h + pica_state.h + pica_types.h + primitive_assembly.h + rasterizer_interface.h + regs.h + regs_framebuffer.h + regs_lighting.h + regs_pipeline.h + regs_rasterizer.h + regs_shader.h + regs_texturing.h + renderer_base.h renderer_opengl/gl_rasterizer.h renderer_opengl/gl_rasterizer_cache.h renderer_opengl/gl_resource_manager.h @@ -29,20 +49,16 @@ set(HEADERS renderer_opengl/gl_state.h renderer_opengl/pica_to_gl.h renderer_opengl/renderer_opengl.h - clipper.h - command_processor.h - gpu_debugger.h - pica.h - pica_state.h - pica_types.h - primitive_assembly.h - rasterizer.h - rasterizer_interface.h - renderer_base.h shader/debug_data.h shader/shader.h shader/shader_interpreter.h - swrasterizer.h + swrasterizer/clipper.h + swrasterizer/framebuffer.h + swrasterizer/rasterizer.h + swrasterizer/swrasterizer.h + swrasterizer/texturing.h + texture/etc1.h + texture/texture_decode.h utils.h vertex_loader.h video_core.h @@ -50,10 +66,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/command_processor.cpp b/src/video_core/command_processor.cpp index ea58e9f54..2e32ff905 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -16,11 +16,13 @@ #include "core/tracer/recorder.h" #include "video_core/command_processor.h" #include "video_core/debug_utils/debug_utils.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" #include "video_core/pica_types.h" #include "video_core/primitive_assembly.h" #include "video_core/rasterizer_interface.h" +#include "video_core/regs.h" +#include "video_core/regs_pipeline.h" +#include "video_core/regs_texturing.h" #include "video_core/renderer_base.h" #include "video_core/shader/shader.h" #include "video_core/vertex_loader.h" @@ -49,19 +51,23 @@ MICROPROFILE_DEFINE(GPU_Drawing, "GPU", "Drawing", MP_RGB(50, 50, 240)); static void WritePicaReg(u32 id, u32 value, u32 mask) { auto& regs = g_state.regs; - if (id >= regs.NumIds()) + if (id >= Regs::NUM_REGS) { + LOG_ERROR(HW_GPU, + "Commandlist tried to write to invalid register 0x%03X (value: %08X, mask: %X)", + id, value, mask); return; + } // TODO: Figure out how register masking acts on e.g. vs.uniform_setup.set_value - u32 old_value = regs[id]; + u32 old_value = regs.reg_array[id]; const u32 write_mask = expand_bits_to_bytes[mask]; - regs[id] = (old_value & ~write_mask) | (value & write_mask); + regs.reg_array[id] = (old_value & ~write_mask) | (value & write_mask); // Double check for is_pica_tracing to avoid call overhead if (DebugUtils::IsPicaTracing()) { - DebugUtils::OnPicaRegWrite({(u16)id, (u16)mask, regs[id]}); + DebugUtils::OnPicaRegWrite({(u16)id, (u16)mask, regs.reg_array[id]}); } if (g_debug_context) @@ -74,23 +80,23 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { Service::GSP::SignalInterrupt(Service::GSP::InterruptId::P3D); break; - case PICA_REG_INDEX_WORKAROUND(triangle_topology, 0x25E): - g_state.primitive_assembler.Reconfigure(regs.triangle_topology); + case PICA_REG_INDEX(pipeline.triangle_topology): + g_state.primitive_assembler.Reconfigure(regs.pipeline.triangle_topology); break; - case PICA_REG_INDEX_WORKAROUND(restart_primitive, 0x25F): + case PICA_REG_INDEX(pipeline.restart_primitive): g_state.primitive_assembler.Reset(); break; - case PICA_REG_INDEX_WORKAROUND(vs_default_attributes_setup.index, 0x232): + case PICA_REG_INDEX(pipeline.vs_default_attributes_setup.index): g_state.immediate.current_attribute = 0; default_attr_counter = 0; break; // Load default vertex input attributes - case PICA_REG_INDEX_WORKAROUND(vs_default_attributes_setup.set_value[0], 0x233): - case PICA_REG_INDEX_WORKAROUND(vs_default_attributes_setup.set_value[1], 0x234): - case PICA_REG_INDEX_WORKAROUND(vs_default_attributes_setup.set_value[2], 0x235): { + case PICA_REG_INDEX_WORKAROUND(pipeline.vs_default_attributes_setup.set_value[0], 0x233): + case PICA_REG_INDEX_WORKAROUND(pipeline.vs_default_attributes_setup.set_value[1], 0x234): + case PICA_REG_INDEX_WORKAROUND(pipeline.vs_default_attributes_setup.set_value[2], 0x235): { // TODO: Does actual hardware indeed keep an intermediate buffer or does // it directly write the values? default_attr_write_buffer[default_attr_counter++] = value; @@ -102,7 +108,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { if (default_attr_counter >= 3) { default_attr_counter = 0; - auto& setup = regs.vs_default_attributes_setup; + auto& setup = regs.pipeline.vs_default_attributes_setup; if (setup.index >= 16) { LOG_ERROR(HW_GPU, "Invalid VS default attribute index %d", (int)setup.index); @@ -125,33 +131,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.pipeline.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,15 +170,17 @@ 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.rasterizer, output), + AddTriangle); } } } break; } - case PICA_REG_INDEX(gpu_mode): - if (regs.gpu_mode == Regs::GPUMode::Configuring) { + case PICA_REG_INDEX(pipeline.gpu_mode): + if (regs.pipeline.gpu_mode == PipelineRegs::GPUMode::Configuring) { MICROPROFILE_SCOPE(GPU_Drawing); // Draw immediate mode triangles when GPU Mode is set to GPUMode::Configuring @@ -180,19 +192,20 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { } break; - case PICA_REG_INDEX_WORKAROUND(command_buffer.trigger[0], 0x23c): - case PICA_REG_INDEX_WORKAROUND(command_buffer.trigger[1], 0x23d): { - unsigned index = static_cast<unsigned>(id - PICA_REG_INDEX(command_buffer.trigger[0])); - u32* head_ptr = - (u32*)Memory::GetPhysicalPointer(regs.command_buffer.GetPhysicalAddress(index)); + case PICA_REG_INDEX_WORKAROUND(pipeline.command_buffer.trigger[0], 0x23c): + case PICA_REG_INDEX_WORKAROUND(pipeline.command_buffer.trigger[1], 0x23d): { + unsigned index = + static_cast<unsigned>(id - PICA_REG_INDEX(pipeline.command_buffer.trigger[0])); + u32* head_ptr = (u32*)Memory::GetPhysicalPointer( + regs.pipeline.command_buffer.GetPhysicalAddress(index)); g_state.cmd_list.head_ptr = g_state.cmd_list.current_ptr = head_ptr; - g_state.cmd_list.length = regs.command_buffer.GetSize(index) / sizeof(u32); + g_state.cmd_list.length = regs.pipeline.command_buffer.GetSize(index) / sizeof(u32); break; } // It seems like these trigger vertex rendering - case PICA_REG_INDEX(trigger_draw): - case PICA_REG_INDEX(trigger_draw_indexed): { + case PICA_REG_INDEX(pipeline.trigger_draw): + case PICA_REG_INDEX(pipeline.trigger_draw_indexed): { MICROPROFILE_SCOPE(GPU_Drawing); #if PICA_LOG_TEV @@ -204,13 +217,13 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { // Processes information about internal vertex attributes to figure out how a vertex is // loaded. // Later, these can be compiled and cached. - const u32 base_address = regs.vertex_attributes.GetPhysicalBaseAddress(); - VertexLoader loader(regs); + const u32 base_address = regs.pipeline.vertex_attributes.GetPhysicalBaseAddress(); + VertexLoader loader(regs.pipeline); // Load vertices - bool is_indexed = (id == PICA_REG_INDEX(trigger_draw_indexed)); + bool is_indexed = (id == PICA_REG_INDEX(pipeline.trigger_draw_indexed)); - const auto& index_info = regs.index_array; + const auto& index_info = regs.pipeline.index_array; const u8* index_address_8 = Memory::GetPhysicalPointer(base_address + index_info.offset); const u16* index_address_16 = reinterpret_cast<const u16*>(index_address_8); bool index_u16 = index_info.format != 0; @@ -219,13 +232,13 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { if (g_debug_context && g_debug_context->recorder) { for (int i = 0; i < 3; ++i) { - const auto texture = regs.GetTextures()[i]; + const auto texture = regs.texturing.GetTextures()[i]; if (!texture.enabled) continue; u8* texture_data = Memory::GetPhysicalPointer(texture.config.GetPhysicalAddress()); g_debug_context->recorder->MemoryAccessed( - texture_data, Pica::Regs::NibblesPerPixel(texture.format) * + texture_data, Pica::TexturingRegs::NibblesPerPixel(texture.format) * texture.config.width / 2 * texture.config.height, texture.config.GetPhysicalAddress()); } @@ -243,14 +256,16 @@ 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(); - for (unsigned int index = 0; index < regs.num_vertices; ++index) { + shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset); + + for (unsigned int index = 0; index < regs.pipeline.num_vertices; ++index) { // Indexed rendering doesn't use the start offset unsigned int vertex = is_indexed ? (index_u16 ? index_address_16[index] : index_address_8[index]) - : (index + regs.vertex_offset); + : (index + regs.pipeline.vertex_offset); // -1 is a common special value used for primitive restart. Since it's unknown if // the PICA supports it, and it would mess up the caching, guard against it here. @@ -276,17 +291,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.rasterizer, output); if (is_indexed) { vertex_cache[vertex_cache_pos] = output_vertex; @@ -428,16 +445,16 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { break; } - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[0], 0xe8): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[1], 0xe9): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[2], 0xea): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[3], 0xeb): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[4], 0xec): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[5], 0xed): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[6], 0xee): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[7], 0xef): { - g_state.fog.lut[regs.fog_lut_offset % 128].raw = value; - regs.fog_lut_offset.Assign(regs.fog_lut_offset + 1); + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[0], 0xe8): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[1], 0xe9): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[2], 0xea): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[3], 0xeb): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[4], 0xec): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[5], 0xed): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[6], 0xee): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[7], 0xef): { + g_state.fog.lut[regs.texturing.fog_lut_offset % 128].raw = value; + regs.texturing.fog_lut_offset.Assign(regs.texturing.fog_lut_offset + 1); break; } diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index c44b3d95a..47dbc8cc8 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -29,12 +29,15 @@ #include "common/math_util.h" #include "common/vector_math.h" #include "video_core/debug_utils/debug_utils.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" #include "video_core/pica_types.h" #include "video_core/rasterizer_interface.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_shader.h" +#include "video_core/regs_texturing.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" @@ -87,9 +90,9 @@ std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global namespace DebugUtils { -void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, +void DumpShader(const std::string& filename, const ShaderRegs& config, const Shader::ShaderSetup& setup, - const Regs::VSOutputAttributes* output_attributes) { + const RasterizerRegs::VSOutputAttributes* output_attributes) { struct StuffToWrite { const u8* pointer; u32 size; @@ -128,7 +131,7 @@ void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, // This is put into a try-catch block to make sure we notice unknown configurations. std::vector<OutputRegisterInfo> output_info_table; for (unsigned i = 0; i < 7; ++i) { - using OutputAttributes = Pica::Regs::VSOutputAttributes; + using OutputAttributes = Pica::RasterizerRegs::VSOutputAttributes; // TODO: It's still unclear how the attribute components map to the register! // Once we know that, this code probably will not make much sense anymore. @@ -315,257 +318,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) { @@ -581,7 +333,7 @@ static void FlushIOFile(png_structp png_ptr) { } #endif -void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { +void DumpTexture(const TexturingRegs::TextureConfig& texture_config, u8* data) { #ifndef HAVE_PNG return; #else @@ -642,12 +394,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); + info.format = g_state.regs.texturing.texture0_format; + 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(); @@ -684,8 +436,10 @@ static std::string ReplacePattern(const std::string& input, const std::string& p return ret; } -static std::string GetTevStageConfigSourceString(const Pica::Regs::TevStageConfig::Source& source) { - using Source = Pica::Regs::TevStageConfig::Source; +static std::string GetTevStageConfigSourceString( + const TexturingRegs::TevStageConfig::Source& source) { + + using Source = TexturingRegs::TevStageConfig::Source; static const std::map<Source, std::string> source_map = { {Source::PrimaryColor, "PrimaryColor"}, {Source::PrimaryFragmentColor, "PrimaryFragmentColor"}, @@ -707,9 +461,10 @@ static std::string GetTevStageConfigSourceString(const Pica::Regs::TevStageConfi } static std::string GetTevStageConfigColorSourceString( - const Pica::Regs::TevStageConfig::Source& source, - const Pica::Regs::TevStageConfig::ColorModifier modifier) { - using ColorModifier = Pica::Regs::TevStageConfig::ColorModifier; + const TexturingRegs::TevStageConfig::Source& source, + const TexturingRegs::TevStageConfig::ColorModifier modifier) { + + using ColorModifier = TexturingRegs::TevStageConfig::ColorModifier; static const std::map<ColorModifier, std::string> color_modifier_map = { {ColorModifier::SourceColor, "%source.rgb"}, {ColorModifier::OneMinusSourceColor, "(1.0 - %source.rgb)"}, @@ -733,9 +488,10 @@ static std::string GetTevStageConfigColorSourceString( } static std::string GetTevStageConfigAlphaSourceString( - const Pica::Regs::TevStageConfig::Source& source, - const Pica::Regs::TevStageConfig::AlphaModifier modifier) { - using AlphaModifier = Pica::Regs::TevStageConfig::AlphaModifier; + const TexturingRegs::TevStageConfig::Source& source, + const TexturingRegs::TevStageConfig::AlphaModifier modifier) { + + using AlphaModifier = TexturingRegs::TevStageConfig::AlphaModifier; static const std::map<AlphaModifier, std::string> alpha_modifier_map = { {AlphaModifier::SourceAlpha, "%source.a"}, {AlphaModifier::OneMinusSourceAlpha, "(1.0 - %source.a)"}, @@ -757,8 +513,9 @@ static std::string GetTevStageConfigAlphaSourceString( } static std::string GetTevStageConfigOperationString( - const Pica::Regs::TevStageConfig::Operation& operation) { - using Operation = Pica::Regs::TevStageConfig::Operation; + const TexturingRegs::TevStageConfig::Operation& operation) { + + using Operation = TexturingRegs::TevStageConfig::Operation; static const std::map<Operation, std::string> combiner_map = { {Operation::Replace, "%source1"}, {Operation::Modulate, "(%source1 * %source2)"}, @@ -778,7 +535,7 @@ static std::string GetTevStageConfigOperationString( return op_it->second; } -std::string GetTevStageConfigColorCombinerString(const Pica::Regs::TevStageConfig& tev_stage) { +std::string GetTevStageConfigColorCombinerString(const TexturingRegs::TevStageConfig& tev_stage) { auto op_str = GetTevStageConfigOperationString(tev_stage.color_op); op_str = ReplacePattern( op_str, "%source1", @@ -791,7 +548,7 @@ std::string GetTevStageConfigColorCombinerString(const Pica::Regs::TevStageConfi GetTevStageConfigColorSourceString(tev_stage.color_source3, tev_stage.color_modifier3)); } -std::string GetTevStageConfigAlphaCombinerString(const Pica::Regs::TevStageConfig& tev_stage) { +std::string GetTevStageConfigAlphaCombinerString(const TexturingRegs::TevStageConfig& tev_stage) { auto op_str = GetTevStageConfigOperationString(tev_stage.alpha_op); op_str = ReplacePattern( op_str, "%source1", @@ -804,7 +561,7 @@ std::string GetTevStageConfigAlphaCombinerString(const Pica::Regs::TevStageConfi GetTevStageConfigAlphaSourceString(tev_stage.alpha_source3, tev_stage.alpha_modifier3)); } -void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig, 6>& stages) { +void DumpTevStageConfig(const std::array<TexturingRegs::TevStageConfig, 6>& stages) { std::string stage_info = "Tev setup:\n"; for (size_t index = 0; index < stages.size(); ++index) { const auto& tev_stage = stages[index]; diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index 46ea8d9c7..c1f29c527 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -17,7 +17,9 @@ #include <vector> #include "common/common_types.h" #include "common/vector_math.h" -#include "video_core/pica.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_shader.h" +#include "video_core/regs_texturing.h" namespace CiTrace { class Recorder; @@ -85,7 +87,7 @@ public: * @param data Optional data pointer (if unused, this is a nullptr) * @note This function will perform nothing unless it is overridden in the child class. */ - virtual void OnPicaBreakPointHit(Event, void*) {} + virtual void OnPicaBreakPointHit(Event event, void* data) {} /** * Action to perform when emulation is resumed from a breakpoint. @@ -182,9 +184,9 @@ namespace DebugUtils { #define PICA_DUMP_TEXTURES 0 #define PICA_LOG_TEV 0 -void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, +void DumpShader(const std::string& filename, const ShaderRegs& config, const Shader::ShaderSetup& setup, - const Regs::VSOutputAttributes* output_attributes); + const RasterizerRegs::VSOutputAttributes* output_attributes); // Utility class to log Pica commands. struct PicaTrace { @@ -205,38 +207,13 @@ 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; +void DumpTexture(const TexturingRegs::TextureConfig& texture_config, u8* data); - 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); -std::string GetTevStageConfigAlphaCombinerString(const Pica::Regs::TevStageConfig& tev_stage); +std::string GetTevStageConfigColorCombinerString(const TexturingRegs::TevStageConfig& tev_stage); +std::string GetTevStageConfigAlphaCombinerString(const TexturingRegs::TevStageConfig& tev_stage); /// Dumps the Tev stage config to log at trace level -void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig, 6>& stages); +void DumpTevStageConfig(const std::array<TexturingRegs::TevStageConfig, 6>& stages); /** * Used in the vertex loader to merge access records. TODO: Investigate if actually useful. diff --git a/src/video_core/pica.cpp b/src/video_core/pica.cpp index ce2bd455e..b95148a6a 100644 --- a/src/video_core/pica.cpp +++ b/src/video_core/pica.cpp @@ -3,503 +3,20 @@ // Refer to the license.txt file included. #include <cstring> -#include <iterator> -#include <unordered_map> -#include <utility> #include "video_core/pica.h" #include "video_core/pica_state.h" -#include "video_core/primitive_assembly.h" -#include "video_core/shader/shader.h" +#include "video_core/regs_pipeline.h" namespace Pica { State g_state; -static const std::pair<u16, const char*> register_names[] = { - {0x010, "GPUREG_FINALIZE"}, - - {0x040, "GPUREG_FACECULLING_CONFIG"}, - {0x041, "GPUREG_VIEWPORT_WIDTH"}, - {0x042, "GPUREG_VIEWPORT_INVW"}, - {0x043, "GPUREG_VIEWPORT_HEIGHT"}, - {0x044, "GPUREG_VIEWPORT_INVH"}, - - {0x047, "GPUREG_FRAGOP_CLIP"}, - {0x048, "GPUREG_FRAGOP_CLIP_DATA0"}, - {0x049, "GPUREG_FRAGOP_CLIP_DATA1"}, - {0x04A, "GPUREG_FRAGOP_CLIP_DATA2"}, - {0x04B, "GPUREG_FRAGOP_CLIP_DATA3"}, - - {0x04D, "GPUREG_DEPTHMAP_SCALE"}, - {0x04E, "GPUREG_DEPTHMAP_OFFSET"}, - {0x04F, "GPUREG_SH_OUTMAP_TOTAL"}, - {0x050, "GPUREG_SH_OUTMAP_O0"}, - {0x051, "GPUREG_SH_OUTMAP_O1"}, - {0x052, "GPUREG_SH_OUTMAP_O2"}, - {0x053, "GPUREG_SH_OUTMAP_O3"}, - {0x054, "GPUREG_SH_OUTMAP_O4"}, - {0x055, "GPUREG_SH_OUTMAP_O5"}, - {0x056, "GPUREG_SH_OUTMAP_O6"}, - - {0x061, "GPUREG_EARLYDEPTH_FUNC"}, - {0x062, "GPUREG_EARLYDEPTH_TEST1"}, - {0x063, "GPUREG_EARLYDEPTH_CLEAR"}, - {0x064, "GPUREG_SH_OUTATTR_MODE"}, - {0x065, "GPUREG_SCISSORTEST_MODE"}, - {0x066, "GPUREG_SCISSORTEST_POS"}, - {0x067, "GPUREG_SCISSORTEST_DIM"}, - {0x068, "GPUREG_VIEWPORT_XY"}, - - {0x06A, "GPUREG_EARLYDEPTH_DATA"}, - - {0x06D, "GPUREG_DEPTHMAP_ENABLE"}, - {0x06E, "GPUREG_RENDERBUF_DIM"}, - {0x06F, "GPUREG_SH_OUTATTR_CLOCK"}, - - {0x080, "GPUREG_TEXUNIT_CONFIG"}, - {0x081, "GPUREG_TEXUNIT0_BORDER_COLOR"}, - {0x082, "GPUREG_TEXUNIT0_DIM"}, - {0x083, "GPUREG_TEXUNIT0_PARAM"}, - {0x084, "GPUREG_TEXUNIT0_LOD"}, - {0x085, "GPUREG_TEXUNIT0_ADDR1"}, - {0x086, "GPUREG_TEXUNIT0_ADDR2"}, - {0x087, "GPUREG_TEXUNIT0_ADDR3"}, - {0x088, "GPUREG_TEXUNIT0_ADDR4"}, - {0x089, "GPUREG_TEXUNIT0_ADDR5"}, - {0x08A, "GPUREG_TEXUNIT0_ADDR6"}, - {0x08B, "GPUREG_TEXUNIT0_SHADOW"}, - - {0x08E, "GPUREG_TEXUNIT0_TYPE"}, - {0x08F, "GPUREG_LIGHTING_ENABLE0"}, - - {0x091, "GPUREG_TEXUNIT1_BORDER_COLOR"}, - {0x092, "GPUREG_TEXUNIT1_DIM"}, - {0x093, "GPUREG_TEXUNIT1_PARAM"}, - {0x094, "GPUREG_TEXUNIT1_LOD"}, - {0x095, "GPUREG_TEXUNIT1_ADDR"}, - {0x096, "GPUREG_TEXUNIT1_TYPE"}, - - {0x099, "GPUREG_TEXUNIT2_BORDER_COLOR"}, - {0x09A, "GPUREG_TEXUNIT2_DIM"}, - {0x09B, "GPUREG_TEXUNIT2_PARAM"}, - {0x09C, "GPUREG_TEXUNIT2_LOD"}, - {0x09D, "GPUREG_TEXUNIT2_ADDR"}, - {0x09E, "GPUREG_TEXUNIT2_TYPE"}, - - {0x0A8, "GPUREG_TEXUNIT3_PROCTEX0"}, - {0x0A9, "GPUREG_TEXUNIT3_PROCTEX1"}, - {0x0AA, "GPUREG_TEXUNIT3_PROCTEX2"}, - {0x0AB, "GPUREG_TEXUNIT3_PROCTEX3"}, - {0x0AC, "GPUREG_TEXUNIT3_PROCTEX4"}, - {0x0AD, "GPUREG_TEXUNIT3_PROCTEX5"}, - - {0x0AF, "GPUREG_PROCTEX_LUT"}, - {0x0B0, "GPUREG_PROCTEX_LUT_DATA0"}, - {0x0B1, "GPUREG_PROCTEX_LUT_DATA1"}, - {0x0B2, "GPUREG_PROCTEX_LUT_DATA2"}, - {0x0B3, "GPUREG_PROCTEX_LUT_DATA3"}, - {0x0B4, "GPUREG_PROCTEX_LUT_DATA4"}, - {0x0B5, "GPUREG_PROCTEX_LUT_DATA5"}, - {0x0B6, "GPUREG_PROCTEX_LUT_DATA6"}, - {0x0B7, "GPUREG_PROCTEX_LUT_DATA7"}, - - {0x0C0, "GPUREG_TEXENV0_SOURCE"}, - {0x0C1, "GPUREG_TEXENV0_OPERAND"}, - {0x0C2, "GPUREG_TEXENV0_COMBINER"}, - {0x0C3, "GPUREG_TEXENV0_COLOR"}, - {0x0C4, "GPUREG_TEXENV0_SCALE"}, - - {0x0C8, "GPUREG_TEXENV1_SOURCE"}, - {0x0C9, "GPUREG_TEXENV1_OPERAND"}, - {0x0CA, "GPUREG_TEXENV1_COMBINER"}, - {0x0CB, "GPUREG_TEXENV1_COLOR"}, - {0x0CC, "GPUREG_TEXENV1_SCALE"}, - - {0x0D0, "GPUREG_TEXENV2_SOURCE"}, - {0x0D1, "GPUREG_TEXENV2_OPERAND"}, - {0x0D2, "GPUREG_TEXENV2_COMBINER"}, - {0x0D3, "GPUREG_TEXENV2_COLOR"}, - {0x0D4, "GPUREG_TEXENV2_SCALE"}, - - {0x0D8, "GPUREG_TEXENV3_SOURCE"}, - {0x0D9, "GPUREG_TEXENV3_OPERAND"}, - {0x0DA, "GPUREG_TEXENV3_COMBINER"}, - {0x0DB, "GPUREG_TEXENV3_COLOR"}, - {0x0DC, "GPUREG_TEXENV3_SCALE"}, - - {0x0E0, "GPUREG_TEXENV_UPDATE_BUFFER"}, - {0x0E1, "GPUREG_FOG_COLOR"}, - - {0x0E4, "GPUREG_GAS_ATTENUATION"}, - {0x0E5, "GPUREG_GAS_ACCMAX"}, - {0x0E6, "GPUREG_FOG_LUT_INDEX"}, - - {0x0E8, "GPUREG_FOG_LUT_DATA0"}, - {0x0E9, "GPUREG_FOG_LUT_DATA1"}, - {0x0EA, "GPUREG_FOG_LUT_DATA2"}, - {0x0EB, "GPUREG_FOG_LUT_DATA3"}, - {0x0EC, "GPUREG_FOG_LUT_DATA4"}, - {0x0ED, "GPUREG_FOG_LUT_DATA5"}, - {0x0EE, "GPUREG_FOG_LUT_DATA6"}, - {0x0EF, "GPUREG_FOG_LUT_DATA7"}, - {0x0F0, "GPUREG_TEXENV4_SOURCE"}, - {0x0F1, "GPUREG_TEXENV4_OPERAND"}, - {0x0F2, "GPUREG_TEXENV4_COMBINER"}, - {0x0F3, "GPUREG_TEXENV4_COLOR"}, - {0x0F4, "GPUREG_TEXENV4_SCALE"}, - - {0x0F8, "GPUREG_TEXENV5_SOURCE"}, - {0x0F9, "GPUREG_TEXENV5_OPERAND"}, - {0x0FA, "GPUREG_TEXENV5_COMBINER"}, - {0x0FB, "GPUREG_TEXENV5_COLOR"}, - {0x0FC, "GPUREG_TEXENV5_SCALE"}, - {0x0FD, "GPUREG_TEXENV_BUFFER_COLOR"}, - - {0x100, "GPUREG_COLOR_OPERATION"}, - {0x101, "GPUREG_BLEND_FUNC"}, - {0x102, "GPUREG_LOGIC_OP"}, - {0x103, "GPUREG_BLEND_COLOR"}, - {0x104, "GPUREG_FRAGOP_ALPHA_TEST"}, - {0x105, "GPUREG_STENCIL_TEST"}, - {0x106, "GPUREG_STENCIL_OP"}, - {0x107, "GPUREG_DEPTH_COLOR_MASK"}, - - {0x110, "GPUREG_FRAMEBUFFER_INVALIDATE"}, - {0x111, "GPUREG_FRAMEBUFFER_FLUSH"}, - {0x112, "GPUREG_COLORBUFFER_READ"}, - {0x113, "GPUREG_COLORBUFFER_WRITE"}, - {0x114, "GPUREG_DEPTHBUFFER_READ"}, - {0x115, "GPUREG_DEPTHBUFFER_WRITE"}, - {0x116, "GPUREG_DEPTHBUFFER_FORMAT"}, - {0x117, "GPUREG_COLORBUFFER_FORMAT"}, - {0x118, "GPUREG_EARLYDEPTH_TEST2"}, - - {0x11B, "GPUREG_FRAMEBUFFER_BLOCK32"}, - {0x11C, "GPUREG_DEPTHBUFFER_LOC"}, - {0x11D, "GPUREG_COLORBUFFER_LOC"}, - {0x11E, "GPUREG_FRAMEBUFFER_DIM"}, - - {0x120, "GPUREG_GAS_LIGHT_XY"}, - {0x121, "GPUREG_GAS_LIGHT_Z"}, - {0x122, "GPUREG_GAS_LIGHT_Z_COLOR"}, - {0x123, "GPUREG_GAS_LUT_INDEX"}, - {0x124, "GPUREG_GAS_LUT_DATA"}, - - {0x126, "GPUREG_GAS_DELTAZ_DEPTH"}, - - {0x130, "GPUREG_FRAGOP_SHADOW"}, - - {0x140, "GPUREG_LIGHT0_SPECULAR0"}, - {0x141, "GPUREG_LIGHT0_SPECULAR1"}, - {0x142, "GPUREG_LIGHT0_DIFFUSE"}, - {0x143, "GPUREG_LIGHT0_AMBIENT"}, - {0x144, "GPUREG_LIGHT0_XY"}, - {0x145, "GPUREG_LIGHT0_Z"}, - {0x146, "GPUREG_LIGHT0_SPOTDIR_XY"}, - {0x147, "GPUREG_LIGHT0_SPOTDIR_Z"}, - - {0x149, "GPUREG_LIGHT0_CONFIG"}, - {0x14A, "GPUREG_LIGHT0_ATTENUATION_BIAS"}, - {0x14B, "GPUREG_LIGHT0_ATTENUATION_SCALE"}, - - {0x150, "GPUREG_LIGHT1_SPECULAR0"}, - {0x151, "GPUREG_LIGHT1_SPECULAR1"}, - {0x152, "GPUREG_LIGHT1_DIFFUSE"}, - {0x153, "GPUREG_LIGHT1_AMBIENT"}, - {0x154, "GPUREG_LIGHT1_XY"}, - {0x155, "GPUREG_LIGHT1_Z"}, - {0x156, "GPUREG_LIGHT1_SPOTDIR_XY"}, - {0x157, "GPUREG_LIGHT1_SPOTDIR_Z"}, - - {0x159, "GPUREG_LIGHT1_CONFIG"}, - {0x15A, "GPUREG_LIGHT1_ATTENUATION_BIAS"}, - {0x15B, "GPUREG_LIGHT1_ATTENUATION_SCALE"}, - - {0x160, "GPUREG_LIGHT2_SPECULAR0"}, - {0x161, "GPUREG_LIGHT2_SPECULAR1"}, - {0x162, "GPUREG_LIGHT2_DIFFUSE"}, - {0x163, "GPUREG_LIGHT2_AMBIENT"}, - {0x164, "GPUREG_LIGHT2_XY"}, - {0x165, "GPUREG_LIGHT2_Z"}, - {0x166, "GPUREG_LIGHT2_SPOTDIR_XY"}, - {0x167, "GPUREG_LIGHT2_SPOTDIR_Z"}, - - {0x169, "GPUREG_LIGHT2_CONFIG"}, - {0x16A, "GPUREG_LIGHT2_ATTENUATION_BIAS"}, - {0x16B, "GPUREG_LIGHT2_ATTENUATION_SCALE"}, - - {0x170, "GPUREG_LIGHT3_SPECULAR0"}, - {0x171, "GPUREG_LIGHT3_SPECULAR1"}, - {0x172, "GPUREG_LIGHT3_DIFFUSE"}, - {0x173, "GPUREG_LIGHT3_AMBIENT"}, - {0x174, "GPUREG_LIGHT3_XY"}, - {0x175, "GPUREG_LIGHT3_Z"}, - {0x176, "GPUREG_LIGHT3_SPOTDIR_XY"}, - {0x177, "GPUREG_LIGHT3_SPOTDIR_Z"}, - - {0x179, "GPUREG_LIGHT3_CONFIG"}, - {0x17A, "GPUREG_LIGHT3_ATTENUATION_BIAS"}, - {0x17B, "GPUREG_LIGHT3_ATTENUATION_SCALE"}, - - {0x180, "GPUREG_LIGHT4_SPECULAR0"}, - {0x181, "GPUREG_LIGHT4_SPECULAR1"}, - {0x182, "GPUREG_LIGHT4_DIFFUSE"}, - {0x183, "GPUREG_LIGHT4_AMBIENT"}, - {0x184, "GPUREG_LIGHT4_XY"}, - {0x185, "GPUREG_LIGHT4_Z"}, - {0x186, "GPUREG_LIGHT4_SPOTDIR_XY"}, - {0x187, "GPUREG_LIGHT4_SPOTDIR_Z"}, - - {0x189, "GPUREG_LIGHT4_CONFIG"}, - {0x18A, "GPUREG_LIGHT4_ATTENUATION_BIAS"}, - {0x18B, "GPUREG_LIGHT4_ATTENUATION_SCALE"}, - - {0x190, "GPUREG_LIGHT5_SPECULAR0"}, - {0x191, "GPUREG_LIGHT5_SPECULAR1"}, - {0x192, "GPUREG_LIGHT5_DIFFUSE"}, - {0x193, "GPUREG_LIGHT5_AMBIENT"}, - {0x194, "GPUREG_LIGHT5_XY"}, - {0x195, "GPUREG_LIGHT5_Z"}, - {0x196, "GPUREG_LIGHT5_SPOTDIR_XY"}, - {0x197, "GPUREG_LIGHT5_SPOTDIR_Z"}, - - {0x199, "GPUREG_LIGHT5_CONFIG"}, - {0x19A, "GPUREG_LIGHT5_ATTENUATION_BIAS"}, - {0x19B, "GPUREG_LIGHT5_ATTENUATION_SCALE"}, - - {0x1A0, "GPUREG_LIGHT6_SPECULAR0"}, - {0x1A1, "GPUREG_LIGHT6_SPECULAR1"}, - {0x1A2, "GPUREG_LIGHT6_DIFFUSE"}, - {0x1A3, "GPUREG_LIGHT6_AMBIENT"}, - {0x1A4, "GPUREG_LIGHT6_XY"}, - {0x1A5, "GPUREG_LIGHT6_Z"}, - {0x1A6, "GPUREG_LIGHT6_SPOTDIR_XY"}, - {0x1A7, "GPUREG_LIGHT6_SPOTDIR_Z"}, - - {0x1A9, "GPUREG_LIGHT6_CONFIG"}, - {0x1AA, "GPUREG_LIGHT6_ATTENUATION_BIAS"}, - {0x1AB, "GPUREG_LIGHT6_ATTENUATION_SCALE"}, - - {0x1B0, "GPUREG_LIGHT7_SPECULAR0"}, - {0x1B1, "GPUREG_LIGHT7_SPECULAR1"}, - {0x1B2, "GPUREG_LIGHT7_DIFFUSE"}, - {0x1B3, "GPUREG_LIGHT7_AMBIENT"}, - {0x1B4, "GPUREG_LIGHT7_XY"}, - {0x1B5, "GPUREG_LIGHT7_Z"}, - {0x1B6, "GPUREG_LIGHT7_SPOTDIR_XY"}, - {0x1B7, "GPUREG_LIGHT7_SPOTDIR_Z"}, - - {0x1B9, "GPUREG_LIGHT7_CONFIG"}, - {0x1BA, "GPUREG_LIGHT7_ATTENUATION_BIAS"}, - {0x1BB, "GPUREG_LIGHT7_ATTENUATION_SCALE"}, - - {0x1C0, "GPUREG_LIGHTING_AMBIENT"}, - - {0x1C2, "GPUREG_LIGHTING_NUM_LIGHTS"}, - {0x1C3, "GPUREG_LIGHTING_CONFIG0"}, - {0x1C4, "GPUREG_LIGHTING_CONFIG1"}, - {0x1C5, "GPUREG_LIGHTING_LUT_INDEX"}, - {0x1C6, "GPUREG_LIGHTING_ENABLE1"}, - - {0x1C8, "GPUREG_LIGHTING_LUT_DATA0"}, - {0x1C9, "GPUREG_LIGHTING_LUT_DATA1"}, - {0x1CA, "GPUREG_LIGHTING_LUT_DATA2"}, - {0x1CB, "GPUREG_LIGHTING_LUT_DATA3"}, - {0x1CC, "GPUREG_LIGHTING_LUT_DATA4"}, - {0x1CD, "GPUREG_LIGHTING_LUT_DATA5"}, - {0x1CE, "GPUREG_LIGHTING_LUT_DATA6"}, - {0x1CF, "GPUREG_LIGHTING_LUT_DATA7"}, - {0x1D0, "GPUREG_LIGHTING_LUTINPUT_ABS"}, - {0x1D1, "GPUREG_LIGHTING_LUTINPUT_SELECT"}, - {0x1D2, "GPUREG_LIGHTING_LUTINPUT_SCALE"}, - - {0x1D9, "GPUREG_LIGHTING_LIGHT_PERMUTATION"}, - - {0x200, "GPUREG_ATTRIBBUFFERS_LOC"}, - {0x201, "GPUREG_ATTRIBBUFFERS_FORMAT_LOW"}, - {0x202, "GPUREG_ATTRIBBUFFERS_FORMAT_HIGH"}, - {0x203, "GPUREG_ATTRIBBUFFER0_OFFSET"}, - {0x204, "GPUREG_ATTRIBBUFFER0_CONFIG1"}, - {0x205, "GPUREG_ATTRIBBUFFER0_CONFIG2"}, - {0x206, "GPUREG_ATTRIBBUFFER1_OFFSET"}, - {0x207, "GPUREG_ATTRIBBUFFER1_CONFIG1"}, - {0x208, "GPUREG_ATTRIBBUFFER1_CONFIG2"}, - {0x209, "GPUREG_ATTRIBBUFFER2_OFFSET"}, - {0x20A, "GPUREG_ATTRIBBUFFER2_CONFIG1"}, - {0x20B, "GPUREG_ATTRIBBUFFER2_CONFIG2"}, - {0x20C, "GPUREG_ATTRIBBUFFER3_OFFSET"}, - {0x20D, "GPUREG_ATTRIBBUFFER3_CONFIG1"}, - {0x20E, "GPUREG_ATTRIBBUFFER3_CONFIG2"}, - {0x20F, "GPUREG_ATTRIBBUFFER4_OFFSET"}, - {0x210, "GPUREG_ATTRIBBUFFER4_CONFIG1"}, - {0x211, "GPUREG_ATTRIBBUFFER4_CONFIG2"}, - {0x212, "GPUREG_ATTRIBBUFFER5_OFFSET"}, - {0x213, "GPUREG_ATTRIBBUFFER5_CONFIG1"}, - {0x214, "GPUREG_ATTRIBBUFFER5_CONFIG2"}, - {0x215, "GPUREG_ATTRIBBUFFER6_OFFSET"}, - {0x216, "GPUREG_ATTRIBBUFFER6_CONFIG1"}, - {0x217, "GPUREG_ATTRIBBUFFER6_CONFIG2"}, - {0x218, "GPUREG_ATTRIBBUFFER7_OFFSET"}, - {0x219, "GPUREG_ATTRIBBUFFER7_CONFIG1"}, - {0x21A, "GPUREG_ATTRIBBUFFER7_CONFIG2"}, - {0x21B, "GPUREG_ATTRIBBUFFER8_OFFSET"}, - {0x21C, "GPUREG_ATTRIBBUFFER8_CONFIG1"}, - {0x21D, "GPUREG_ATTRIBBUFFER8_CONFIG2"}, - {0x21E, "GPUREG_ATTRIBBUFFER9_OFFSET"}, - {0x21F, "GPUREG_ATTRIBBUFFER9_CONFIG1"}, - {0x220, "GPUREG_ATTRIBBUFFER9_CONFIG2"}, - {0x221, "GPUREG_ATTRIBBUFFER10_OFFSET"}, - {0x222, "GPUREG_ATTRIBBUFFER10_CONFIG1"}, - {0x223, "GPUREG_ATTRIBBUFFER10_CONFIG2"}, - {0x224, "GPUREG_ATTRIBBUFFER11_OFFSET"}, - {0x225, "GPUREG_ATTRIBBUFFER11_CONFIG1"}, - {0x226, "GPUREG_ATTRIBBUFFER11_CONFIG2"}, - {0x227, "GPUREG_INDEXBUFFER_CONFIG"}, - {0x228, "GPUREG_NUMVERTICES"}, - {0x229, "GPUREG_GEOSTAGE_CONFIG"}, - {0x22A, "GPUREG_VERTEX_OFFSET"}, - - {0x22D, "GPUREG_POST_VERTEX_CACHE_NUM"}, - {0x22E, "GPUREG_DRAWARRAYS"}, - {0x22F, "GPUREG_DRAWELEMENTS"}, - - {0x231, "GPUREG_VTX_FUNC"}, - {0x232, "GPUREG_FIXEDATTRIB_INDEX"}, - {0x233, "GPUREG_FIXEDATTRIB_DATA0"}, - {0x234, "GPUREG_FIXEDATTRIB_DATA1"}, - {0x235, "GPUREG_FIXEDATTRIB_DATA2"}, - - {0x238, "GPUREG_CMDBUF_SIZE0"}, - {0x239, "GPUREG_CMDBUF_SIZE1"}, - {0x23A, "GPUREG_CMDBUF_ADDR0"}, - {0x23B, "GPUREG_CMDBUF_ADDR1"}, - {0x23C, "GPUREG_CMDBUF_JUMP0"}, - {0x23D, "GPUREG_CMDBUF_JUMP1"}, - - {0x242, "GPUREG_VSH_NUM_ATTR"}, - - {0x244, "GPUREG_VSH_COM_MODE"}, - {0x245, "GPUREG_START_DRAW_FUNC0"}, - - {0x24A, "GPUREG_VSH_OUTMAP_TOTAL1"}, - - {0x251, "GPUREG_VSH_OUTMAP_TOTAL2"}, - {0x252, "GPUREG_GSH_MISC0"}, - {0x253, "GPUREG_GEOSTAGE_CONFIG2"}, - {0x254, "GPUREG_GSH_MISC1"}, - - {0x25E, "GPUREG_PRIMITIVE_CONFIG"}, - {0x25F, "GPUREG_RESTART_PRIMITIVE"}, - - {0x280, "GPUREG_GSH_BOOLUNIFORM"}, - {0x281, "GPUREG_GSH_INTUNIFORM_I0"}, - {0x282, "GPUREG_GSH_INTUNIFORM_I1"}, - {0x283, "GPUREG_GSH_INTUNIFORM_I2"}, - {0x284, "GPUREG_GSH_INTUNIFORM_I3"}, - - {0x289, "GPUREG_GSH_INPUTBUFFER_CONFIG"}, - {0x28A, "GPUREG_GSH_ENTRYPOINT"}, - {0x28B, "GPUREG_GSH_ATTRIBUTES_PERMUTATION_LOW"}, - {0x28C, "GPUREG_GSH_ATTRIBUTES_PERMUTATION_HIGH"}, - {0x28D, "GPUREG_GSH_OUTMAP_MASK"}, - - {0x28F, "GPUREG_GSH_CODETRANSFER_END"}, - {0x290, "GPUREG_GSH_FLOATUNIFORM_INDEX"}, - {0x291, "GPUREG_GSH_FLOATUNIFORM_DATA0"}, - {0x292, "GPUREG_GSH_FLOATUNIFORM_DATA1"}, - {0x293, "GPUREG_GSH_FLOATUNIFORM_DATA2"}, - {0x294, "GPUREG_GSH_FLOATUNIFORM_DATA3"}, - {0x295, "GPUREG_GSH_FLOATUNIFORM_DATA4"}, - {0x296, "GPUREG_GSH_FLOATUNIFORM_DATA5"}, - {0x297, "GPUREG_GSH_FLOATUNIFORM_DATA6"}, - {0x298, "GPUREG_GSH_FLOATUNIFORM_DATA7"}, - - {0x29B, "GPUREG_GSH_CODETRANSFER_INDEX"}, - {0x29C, "GPUREG_GSH_CODETRANSFER_DATA0"}, - {0x29D, "GPUREG_GSH_CODETRANSFER_DATA1"}, - {0x29E, "GPUREG_GSH_CODETRANSFER_DATA2"}, - {0x29F, "GPUREG_GSH_CODETRANSFER_DATA3"}, - {0x2A0, "GPUREG_GSH_CODETRANSFER_DATA4"}, - {0x2A1, "GPUREG_GSH_CODETRANSFER_DATA5"}, - {0x2A2, "GPUREG_GSH_CODETRANSFER_DATA6"}, - {0x2A3, "GPUREG_GSH_CODETRANSFER_DATA7"}, - - {0x2A5, "GPUREG_GSH_OPDESCS_INDEX"}, - {0x2A6, "GPUREG_GSH_OPDESCS_DATA0"}, - {0x2A7, "GPUREG_GSH_OPDESCS_DATA1"}, - {0x2A8, "GPUREG_GSH_OPDESCS_DATA2"}, - {0x2A9, "GPUREG_GSH_OPDESCS_DATA3"}, - {0x2AA, "GPUREG_GSH_OPDESCS_DATA4"}, - {0x2AB, "GPUREG_GSH_OPDESCS_DATA5"}, - {0x2AC, "GPUREG_GSH_OPDESCS_DATA6"}, - {0x2AD, "GPUREG_GSH_OPDESCS_DATA7"}, - - {0x2B0, "GPUREG_VSH_BOOLUNIFORM"}, - {0x2B1, "GPUREG_VSH_INTUNIFORM_I0"}, - {0x2B2, "GPUREG_VSH_INTUNIFORM_I1"}, - {0x2B3, "GPUREG_VSH_INTUNIFORM_I2"}, - {0x2B4, "GPUREG_VSH_INTUNIFORM_I3"}, - - {0x2B9, "GPUREG_VSH_INPUTBUFFER_CONFIG"}, - {0x2BA, "GPUREG_VSH_ENTRYPOINT"}, - {0x2BB, "GPUREG_VSH_ATTRIBUTES_PERMUTATION_LOW"}, - {0x2BC, "GPUREG_VSH_ATTRIBUTES_PERMUTATION_HIGH"}, - {0x2BD, "GPUREG_VSH_OUTMAP_MASK"}, - - {0x2BF, "GPUREG_VSH_CODETRANSFER_END"}, - {0x2C0, "GPUREG_VSH_FLOATUNIFORM_INDEX"}, - {0x2C1, "GPUREG_VSH_FLOATUNIFORM_DATA0"}, - {0x2C2, "GPUREG_VSH_FLOATUNIFORM_DATA1"}, - {0x2C3, "GPUREG_VSH_FLOATUNIFORM_DATA2"}, - {0x2C4, "GPUREG_VSH_FLOATUNIFORM_DATA3"}, - {0x2C5, "GPUREG_VSH_FLOATUNIFORM_DATA4"}, - {0x2C6, "GPUREG_VSH_FLOATUNIFORM_DATA5"}, - {0x2C7, "GPUREG_VSH_FLOATUNIFORM_DATA6"}, - {0x2C8, "GPUREG_VSH_FLOATUNIFORM_DATA7"}, - - {0x2CB, "GPUREG_VSH_CODETRANSFER_INDEX"}, - {0x2CC, "GPUREG_VSH_CODETRANSFER_DATA0"}, - {0x2CD, "GPUREG_VSH_CODETRANSFER_DATA1"}, - {0x2CE, "GPUREG_VSH_CODETRANSFER_DATA2"}, - {0x2CF, "GPUREG_VSH_CODETRANSFER_DATA3"}, - {0x2D0, "GPUREG_VSH_CODETRANSFER_DATA4"}, - {0x2D1, "GPUREG_VSH_CODETRANSFER_DATA5"}, - {0x2D2, "GPUREG_VSH_CODETRANSFER_DATA6"}, - {0x2D3, "GPUREG_VSH_CODETRANSFER_DATA7"}, - - {0x2D5, "GPUREG_VSH_OPDESCS_INDEX"}, - {0x2D6, "GPUREG_VSH_OPDESCS_DATA0"}, - {0x2D7, "GPUREG_VSH_OPDESCS_DATA1"}, - {0x2D8, "GPUREG_VSH_OPDESCS_DATA2"}, - {0x2D9, "GPUREG_VSH_OPDESCS_DATA3"}, - {0x2DA, "GPUREG_VSH_OPDESCS_DATA4"}, - {0x2DB, "GPUREG_VSH_OPDESCS_DATA5"}, - {0x2DC, "GPUREG_VSH_OPDESCS_DATA6"}, - {0x2DD, "GPUREG_VSH_OPDESCS_DATA7"}, -}; - -std::string Regs::GetCommandName(int index) { - static std::unordered_map<u32, const char*> map; - - if (map.empty()) { - map.insert(std::begin(register_names), std::end(register_names)); - } - - // Return empty string if no match is found - auto it = map.find(index); - if (it != map.end()) { - return it->second; - } else { - return std::string(); - } -} - void Init() { g_state.Reset(); } void Shutdown() { - Shader::ClearCache(); + Shader::Shutdown(); } template <typename T> @@ -513,6 +30,6 @@ void State::Reset() { Zero(gs); Zero(cmd_list); Zero(immediate); - primitive_assembler.Reconfigure(Regs::TriangleTopology::List); + primitive_assembler.Reconfigure(PipelineRegs::TriangleTopology::List); } } diff --git a/src/video_core/pica.h b/src/video_core/pica.h index b2db609ec..dc8aa6670 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -4,1412 +4,9 @@ #pragma once -#include <array> -#include <cstddef> -#include <string> - -#ifndef _MSC_VER -#include <type_traits> // for std::enable_if -#endif - -#include "common/assert.h" -#include "common/bit_field.h" -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "common/logging/log.h" -#include "common/vector_math.h" - +#include "video_core/regs_texturing.h" namespace Pica { -// Returns index corresponding to the Regs member labeled by field_name -// TODO: Due to Visual studio bug 209229, offsetof does not return constant expressions -// when used with array elements (e.g. PICA_REG_INDEX(vs_uniform_setup.set_value[1])). -// For details cf. -// https://connect.microsoft.com/VisualStudio/feedback/details/209229/offsetof-does-not-produce-a-constant-expression-for-array-members -// Hopefully, this will be fixed sometime in the future. -// For lack of better alternatives, we currently hardcode the offsets when constant -// expressions are needed via PICA_REG_INDEX_WORKAROUND (on sane compilers, static_asserts -// will then make sure the offsets indeed match the automatically calculated ones). -#define PICA_REG_INDEX(field_name) (offsetof(Pica::Regs, field_name) / sizeof(u32)) -#if defined(_MSC_VER) -#define PICA_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) (backup_workaround_index) -#else -// NOTE: Yeah, hacking in a static_assert here just to workaround the lacking MSVC compiler -// really is this annoying. This macro just forwards its first argument to PICA_REG_INDEX -// and then performs a (no-op) cast to size_t iff the second argument matches the expected -// field offset. Otherwise, the compiler will fail to compile this code. -#define PICA_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) \ - ((typename std::enable_if<backup_workaround_index == PICA_REG_INDEX(field_name), \ - size_t>::type)PICA_REG_INDEX(field_name)) -#endif // _MSC_VER - -struct Regs { - - INSERT_PADDING_WORDS(0x10); - - u32 trigger_irq; - - INSERT_PADDING_WORDS(0x2f); - - enum class CullMode : u32 { - // Select which polygons are considered to be "frontfacing". - KeepAll = 0, - KeepClockWise = 1, - KeepCounterClockWise = 2, - // TODO: What does the third value imply? - }; - - union { - BitField<0, 2, CullMode> cull_mode; - }; - - BitField<0, 24, u32> viewport_size_x; - - INSERT_PADDING_WORDS(0x1); - - BitField<0, 24, u32> viewport_size_y; - - INSERT_PADDING_WORDS(0x9); - - BitField<0, 24, u32> viewport_depth_range; // float24 - BitField<0, 24, u32> viewport_depth_near_plane; // float24 - - BitField<0, 3, u32> vs_output_total; - - union VSOutputAttributes { - // Maps components of output vertex attributes to semantics - enum Semantic : u32 { - POSITION_X = 0, - POSITION_Y = 1, - POSITION_Z = 2, - POSITION_W = 3, - - QUATERNION_X = 4, - QUATERNION_Y = 5, - QUATERNION_Z = 6, - QUATERNION_W = 7, - - COLOR_R = 8, - COLOR_G = 9, - COLOR_B = 10, - COLOR_A = 11, - - TEXCOORD0_U = 12, - TEXCOORD0_V = 13, - TEXCOORD1_U = 14, - TEXCOORD1_V = 15, - - // TODO: Not verified - VIEW_X = 18, - VIEW_Y = 19, - VIEW_Z = 20, - - TEXCOORD2_U = 22, - TEXCOORD2_V = 23, - - INVALID = 31, - }; - - BitField<0, 5, Semantic> map_x; - BitField<8, 5, Semantic> map_y; - BitField<16, 5, Semantic> map_z; - BitField<24, 5, Semantic> map_w; - } vs_output_attributes[7]; - - INSERT_PADDING_WORDS(0xe); - - enum class ScissorMode : u32 { - Disabled = 0, - Exclude = 1, // Exclude pixels inside the scissor box - - Include = 3 // Exclude pixels outside the scissor box - }; - - struct { - BitField<0, 2, ScissorMode> mode; - - union { - BitField<0, 16, u32> x1; - BitField<16, 16, u32> y1; - }; - - union { - BitField<0, 16, u32> x2; - BitField<16, 16, u32> y2; - }; - } scissor_test; - - union { - BitField<0, 10, s32> x; - BitField<16, 10, s32> y; - } viewport_corner; - - INSERT_PADDING_WORDS(0x1); - - // TODO: early depth - INSERT_PADDING_WORDS(0x1); - - INSERT_PADDING_WORDS(0x2); - - enum DepthBuffering : u32 { - WBuffering = 0, - ZBuffering = 1, - }; - BitField<0, 1, DepthBuffering> depthmap_enable; - - INSERT_PADDING_WORDS(0x12); - - struct TextureConfig { - enum TextureType : u32 { - Texture2D = 0, - TextureCube = 1, - Shadow2D = 2, - Projection2D = 3, - ShadowCube = 4, - Disabled = 5, - }; - - enum WrapMode : u32 { - ClampToEdge = 0, - ClampToBorder = 1, - Repeat = 2, - MirroredRepeat = 3, - }; - - enum TextureFilter : u32 { - Nearest = 0, - Linear = 1, - }; - - union { - u32 raw; - BitField<0, 8, u32> r; - BitField<8, 8, u32> g; - BitField<16, 8, u32> b; - BitField<24, 8, u32> a; - } border_color; - - union { - BitField<0, 16, u32> height; - BitField<16, 16, u32> width; - }; - - union { - BitField<1, 1, TextureFilter> mag_filter; - BitField<2, 1, TextureFilter> min_filter; - BitField<8, 2, WrapMode> wrap_t; - BitField<12, 2, WrapMode> wrap_s; - BitField<28, 2, TextureType> - type; ///< @note Only valid for texture 0 according to 3DBrew. - }; - - INSERT_PADDING_WORDS(0x1); - - u32 address; - - u32 GetPhysicalAddress() const { - return DecodeAddressRegister(address); - } - - // texture1 and texture2 store the texture format directly after the address - // whereas texture0 inserts some additional flags inbetween. - // Hence, we store the format separately so that all other parameters can be described - // in a single structure. - }; - - enum class TextureFormat : u32 { - RGBA8 = 0, - RGB8 = 1, - RGB5A1 = 2, - RGB565 = 3, - RGBA4 = 4, - IA8 = 5, - RG8 = 6, ///< @note Also called HILO8 in 3DBrew. - I8 = 7, - A8 = 8, - IA4 = 9, - I4 = 10, - A4 = 11, - ETC1 = 12, // compressed - ETC1A4 = 13, // compressed - }; - - enum class LogicOp : u32 { - Clear = 0, - And = 1, - AndReverse = 2, - Copy = 3, - Set = 4, - CopyInverted = 5, - NoOp = 6, - Invert = 7, - Nand = 8, - Or = 9, - Nor = 10, - Xor = 11, - Equiv = 12, - AndInverted = 13, - OrReverse = 14, - OrInverted = 15, - }; - - static unsigned NibblesPerPixel(TextureFormat format) { - switch (format) { - case TextureFormat::RGBA8: - return 8; - - case TextureFormat::RGB8: - return 6; - - case TextureFormat::RGB5A1: - case TextureFormat::RGB565: - case TextureFormat::RGBA4: - case TextureFormat::IA8: - case TextureFormat::RG8: - return 4; - - case TextureFormat::I4: - case TextureFormat::A4: - return 1; - - case TextureFormat::I8: - case TextureFormat::A8: - case TextureFormat::IA4: - default: // placeholder for yet unknown formats - return 2; - } - } - - union { - BitField<0, 1, u32> texture0_enable; - BitField<1, 1, u32> texture1_enable; - BitField<2, 1, u32> texture2_enable; - }; - TextureConfig texture0; - INSERT_PADDING_WORDS(0x8); - BitField<0, 4, TextureFormat> texture0_format; - BitField<0, 1, u32> fragment_lighting_enable; - INSERT_PADDING_WORDS(0x1); - TextureConfig texture1; - BitField<0, 4, TextureFormat> texture1_format; - INSERT_PADDING_WORDS(0x2); - TextureConfig texture2; - BitField<0, 4, TextureFormat> texture2_format; - INSERT_PADDING_WORDS(0x21); - - struct FullTextureConfig { - const bool enabled; - const TextureConfig config; - const TextureFormat format; - }; - const std::array<FullTextureConfig, 3> GetTextures() const { - return {{ - {texture0_enable.ToBool(), texture0, texture0_format}, - {texture1_enable.ToBool(), texture1, texture1_format}, - {texture2_enable.ToBool(), texture2, texture2_format}, - }}; - } - - // 0xc0-0xff: Texture Combiner (akin to glTexEnv) - struct TevStageConfig { - enum class Source : u32 { - PrimaryColor = 0x0, - PrimaryFragmentColor = 0x1, - SecondaryFragmentColor = 0x2, - - Texture0 = 0x3, - Texture1 = 0x4, - Texture2 = 0x5, - Texture3 = 0x6, - - PreviousBuffer = 0xd, - Constant = 0xe, - Previous = 0xf, - }; - - enum class ColorModifier : u32 { - SourceColor = 0x0, - OneMinusSourceColor = 0x1, - SourceAlpha = 0x2, - OneMinusSourceAlpha = 0x3, - SourceRed = 0x4, - OneMinusSourceRed = 0x5, - - SourceGreen = 0x8, - OneMinusSourceGreen = 0x9, - - SourceBlue = 0xc, - OneMinusSourceBlue = 0xd, - }; - - enum class AlphaModifier : u32 { - SourceAlpha = 0x0, - OneMinusSourceAlpha = 0x1, - SourceRed = 0x2, - OneMinusSourceRed = 0x3, - SourceGreen = 0x4, - OneMinusSourceGreen = 0x5, - SourceBlue = 0x6, - OneMinusSourceBlue = 0x7, - }; - - enum class Operation : u32 { - Replace = 0, - Modulate = 1, - Add = 2, - AddSigned = 3, - Lerp = 4, - Subtract = 5, - Dot3_RGB = 6, - - MultiplyThenAdd = 8, - AddThenMultiply = 9, - }; - - union { - u32 sources_raw; - BitField<0, 4, Source> color_source1; - BitField<4, 4, Source> color_source2; - BitField<8, 4, Source> color_source3; - BitField<16, 4, Source> alpha_source1; - BitField<20, 4, Source> alpha_source2; - BitField<24, 4, Source> alpha_source3; - }; - - union { - u32 modifiers_raw; - BitField<0, 4, ColorModifier> color_modifier1; - BitField<4, 4, ColorModifier> color_modifier2; - BitField<8, 4, ColorModifier> color_modifier3; - BitField<12, 3, AlphaModifier> alpha_modifier1; - BitField<16, 3, AlphaModifier> alpha_modifier2; - BitField<20, 3, AlphaModifier> alpha_modifier3; - }; - - union { - u32 ops_raw; - BitField<0, 4, Operation> color_op; - BitField<16, 4, Operation> alpha_op; - }; - - union { - u32 const_color; - BitField<0, 8, u32> const_r; - BitField<8, 8, u32> const_g; - BitField<16, 8, u32> const_b; - BitField<24, 8, u32> const_a; - }; - - union { - u32 scales_raw; - BitField<0, 2, u32> color_scale; - BitField<16, 2, u32> alpha_scale; - }; - - inline unsigned GetColorMultiplier() const { - return (color_scale < 3) ? (1 << color_scale) : 1; - } - - inline unsigned GetAlphaMultiplier() const { - return (alpha_scale < 3) ? (1 << alpha_scale) : 1; - } - }; - - TevStageConfig tev_stage0; - INSERT_PADDING_WORDS(0x3); - TevStageConfig tev_stage1; - INSERT_PADDING_WORDS(0x3); - TevStageConfig tev_stage2; - INSERT_PADDING_WORDS(0x3); - TevStageConfig tev_stage3; - INSERT_PADDING_WORDS(0x3); - - enum class FogMode : u32 { - None = 0, - Fog = 5, - Gas = 7, - }; - - union { - BitField<0, 3, FogMode> fog_mode; - BitField<16, 1, u32> fog_flip; - - union { - // Tev stages 0-3 write their output to the combiner buffer if the corresponding bit in - // these masks are set - BitField<8, 4, u32> update_mask_rgb; - BitField<12, 4, u32> update_mask_a; - - bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const { - return (stage_index < 4) && (update_mask_rgb & (1 << stage_index)); - } - - bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const { - return (stage_index < 4) && (update_mask_a & (1 << stage_index)); - } - } tev_combiner_buffer_input; - }; - - union { - u32 raw; - BitField<0, 8, u32> r; - BitField<8, 8, u32> g; - BitField<16, 8, u32> b; - } fog_color; - - INSERT_PADDING_WORDS(0x4); - - BitField<0, 16, u32> fog_lut_offset; - - INSERT_PADDING_WORDS(0x1); - - u32 fog_lut_data[8]; - - TevStageConfig tev_stage4; - INSERT_PADDING_WORDS(0x3); - TevStageConfig tev_stage5; - - union { - u32 raw; - BitField<0, 8, u32> r; - BitField<8, 8, u32> g; - BitField<16, 8, u32> b; - BitField<24, 8, u32> a; - } tev_combiner_buffer_color; - - INSERT_PADDING_WORDS(0x2); - - const std::array<Regs::TevStageConfig, 6> GetTevStages() const { - return {{tev_stage0, tev_stage1, tev_stage2, tev_stage3, tev_stage4, tev_stage5}}; - }; - - enum class BlendEquation : u32 { - Add = 0, - Subtract = 1, - ReverseSubtract = 2, - Min = 3, - Max = 4, - }; - - enum class BlendFactor : u32 { - Zero = 0, - One = 1, - SourceColor = 2, - OneMinusSourceColor = 3, - DestColor = 4, - OneMinusDestColor = 5, - SourceAlpha = 6, - OneMinusSourceAlpha = 7, - DestAlpha = 8, - OneMinusDestAlpha = 9, - ConstantColor = 10, - OneMinusConstantColor = 11, - ConstantAlpha = 12, - OneMinusConstantAlpha = 13, - SourceAlphaSaturate = 14, - }; - - enum class CompareFunc : u32 { - Never = 0, - Always = 1, - Equal = 2, - NotEqual = 3, - LessThan = 4, - LessThanOrEqual = 5, - GreaterThan = 6, - GreaterThanOrEqual = 7, - }; - - enum class StencilAction : u32 { - Keep = 0, - Zero = 1, - Replace = 2, - Increment = 3, - Decrement = 4, - Invert = 5, - IncrementWrap = 6, - DecrementWrap = 7, - }; - - struct { - union { - // If false, logic blending is used - BitField<8, 1, u32> alphablend_enable; - }; - - union { - BitField<0, 8, BlendEquation> blend_equation_rgb; - BitField<8, 8, BlendEquation> blend_equation_a; - - BitField<16, 4, BlendFactor> factor_source_rgb; - BitField<20, 4, BlendFactor> factor_dest_rgb; - - BitField<24, 4, BlendFactor> factor_source_a; - BitField<28, 4, BlendFactor> factor_dest_a; - } alpha_blending; - - union { - BitField<0, 4, LogicOp> logic_op; - }; - - union { - u32 raw; - BitField<0, 8, u32> r; - BitField<8, 8, u32> g; - BitField<16, 8, u32> b; - BitField<24, 8, u32> a; - } blend_const; - - union { - BitField<0, 1, u32> enable; - BitField<4, 3, CompareFunc> func; - BitField<8, 8, u32> ref; - } alpha_test; - - struct { - union { - // Raw value of this register - u32 raw_func; - - // If true, enable stencil testing - BitField<0, 1, u32> enable; - - // Comparison operation for stencil testing - BitField<4, 3, CompareFunc> func; - - // Mask used to control writing to the stencil buffer - BitField<8, 8, u32> write_mask; - - // Value to compare against for stencil testing - BitField<16, 8, u32> reference_value; - - // Mask to apply on stencil test inputs - BitField<24, 8, u32> input_mask; - }; - - union { - // Raw value of this register - u32 raw_op; - - // Action to perform when the stencil test fails - BitField<0, 3, StencilAction> action_stencil_fail; - - // Action to perform when stencil testing passed but depth testing fails - BitField<4, 3, StencilAction> action_depth_fail; - - // Action to perform when both stencil and depth testing pass - BitField<8, 3, StencilAction> action_depth_pass; - }; - } stencil_test; - - union { - BitField<0, 1, u32> depth_test_enable; - BitField<4, 3, CompareFunc> depth_test_func; - BitField<8, 1, u32> red_enable; - BitField<9, 1, u32> green_enable; - BitField<10, 1, u32> blue_enable; - BitField<11, 1, u32> alpha_enable; - BitField<12, 1, u32> depth_write_enable; - }; - - INSERT_PADDING_WORDS(0x8); - } output_merger; - - // Components are laid out in reverse byte order, most significant bits first. - enum class ColorFormat : u32 { - RGBA8 = 0, - RGB8 = 1, - RGB5A1 = 2, - RGB565 = 3, - RGBA4 = 4, - }; - - enum class DepthFormat : u32 { - D16 = 0, - D24 = 2, - D24S8 = 3, - }; - - // Returns the number of bytes in the specified color format - static unsigned BytesPerColorPixel(ColorFormat format) { - switch (format) { - case ColorFormat::RGBA8: - return 4; - case ColorFormat::RGB8: - return 3; - case ColorFormat::RGB5A1: - case ColorFormat::RGB565: - case ColorFormat::RGBA4: - return 2; - default: - LOG_CRITICAL(HW_GPU, "Unknown color format %u", format); - UNIMPLEMENTED(); - } - } - - struct FramebufferConfig { - INSERT_PADDING_WORDS(0x3); - - union { - BitField<0, 4, u32> allow_color_write; // 0 = disable, else enable - }; - - INSERT_PADDING_WORDS(0x1); - - union { - BitField<0, 2, u32> allow_depth_stencil_write; // 0 = disable, else enable - }; - - DepthFormat depth_format; // TODO: Should be a BitField! - BitField<16, 3, ColorFormat> color_format; - - INSERT_PADDING_WORDS(0x4); - - u32 depth_buffer_address; - u32 color_buffer_address; - - union { - // Apparently, the framebuffer width is stored as expected, - // while the height is stored as the actual height minus one. - // Hence, don't access these fields directly but use the accessors - // GetWidth() and GetHeight() instead. - BitField<0, 11, u32> width; - BitField<12, 10, u32> height; - }; - - INSERT_PADDING_WORDS(0x1); - - inline u32 GetColorBufferPhysicalAddress() const { - return DecodeAddressRegister(color_buffer_address); - } - inline u32 GetDepthBufferPhysicalAddress() const { - return DecodeAddressRegister(depth_buffer_address); - } - - inline u32 GetWidth() const { - return width; - } - - inline u32 GetHeight() const { - return height + 1; - } - } framebuffer; - - // Returns the number of bytes in the specified depth format - static u32 BytesPerDepthPixel(DepthFormat format) { - switch (format) { - case DepthFormat::D16: - return 2; - case DepthFormat::D24: - return 3; - case DepthFormat::D24S8: - return 4; - default: - LOG_CRITICAL(HW_GPU, "Unknown depth format %u", format); - UNIMPLEMENTED(); - } - } - - // Returns the number of bits per depth component of the specified depth format - static u32 DepthBitsPerPixel(DepthFormat format) { - switch (format) { - case DepthFormat::D16: - return 16; - case DepthFormat::D24: - case DepthFormat::D24S8: - return 24; - default: - LOG_CRITICAL(HW_GPU, "Unknown depth format %u", format); - UNIMPLEMENTED(); - } - } - - INSERT_PADDING_WORDS(0x20); - - enum class LightingSampler { - Distribution0 = 0, - Distribution1 = 1, - Fresnel = 3, - ReflectBlue = 4, - ReflectGreen = 5, - ReflectRed = 6, - SpotlightAttenuation = 8, - DistanceAttenuation = 16, - }; - - /** - * Pica fragment lighting supports using different LUTs for each lighting component: - * Reflectance R, G, and B channels, distribution function for specular components 0 and 1, - * fresnel factor, and spotlight attenuation. Furthermore, which LUTs are used for each channel - * (or whether a channel is enabled at all) is specified by various pre-defined lighting - * configurations. With configurations that require more LUTs, more cycles are required on HW to - * perform lighting computations. - */ - enum class LightingConfig { - Config0 = 0, ///< Reflect Red, Distribution 0, Spotlight - Config1 = 1, ///< Reflect Red, Fresnel, Spotlight - Config2 = 2, ///< Reflect Red, Distribution 0/1 - Config3 = 3, ///< Distribution 0/1, Fresnel - Config4 = 4, ///< Reflect Red/Green/Blue, Distribution 0/1, Spotlight - Config5 = 5, ///< Reflect Red/Green/Blue, Distribution 0, Fresnel, Spotlight - Config6 = 6, ///< Reflect Red, Distribution 0/1, Fresnel, Spotlight - Config7 = 8, ///< Reflect Red/Green/Blue, Distribution 0/1, Fresnel, Spotlight - ///< NOTE: '8' is intentional, '7' does not appear to be a valid configuration - }; - - /// Selects which lighting components are affected by fresnel - enum class LightingFresnelSelector { - None = 0, ///< Fresnel is disabled - PrimaryAlpha = 1, ///< Primary (diffuse) lighting alpha is affected by fresnel - SecondaryAlpha = 2, ///< Secondary (specular) lighting alpha is affected by fresnel - Both = - PrimaryAlpha | - SecondaryAlpha, ///< Both primary and secondary lighting alphas are affected by fresnel - }; - - /// Factor used to scale the output of a lighting LUT - enum class LightingScale { - Scale1 = 0, ///< Scale is 1x - Scale2 = 1, ///< Scale is 2x - Scale4 = 2, ///< Scale is 4x - Scale8 = 3, ///< Scale is 8x - Scale1_4 = 6, ///< Scale is 0.25x - Scale1_2 = 7, ///< Scale is 0.5x - }; - - enum class LightingLutInput { - NH = 0, // Cosine of the angle between the normal and half-angle vectors - VH = 1, // Cosine of the angle between the view and half-angle vectors - NV = 2, // Cosine of the angle between the normal and the view vector - LN = 3, // Cosine of the angle between the light and the normal vectors - }; - - enum class LightingBumpMode : u32 { - None = 0, - NormalMap = 1, - TangentMap = 2, - }; - - union LightColor { - BitField<0, 10, u32> b; - BitField<10, 10, u32> g; - BitField<20, 10, u32> r; - - Math::Vec3f ToVec3f() const { - // These fields are 10 bits wide, however 255 corresponds to 1.0f for each color - // component - return Math::MakeVec((f32)r / 255.f, (f32)g / 255.f, (f32)b / 255.f); - } - }; - - /// Returns true if the specified lighting sampler is supported by the current Pica lighting - /// configuration - static bool IsLightingSamplerSupported(LightingConfig config, LightingSampler sampler) { - switch (sampler) { - case LightingSampler::Distribution0: - return (config != LightingConfig::Config1); - - case LightingSampler::Distribution1: - return (config != LightingConfig::Config0) && (config != LightingConfig::Config1) && - (config != LightingConfig::Config5); - - case LightingSampler::Fresnel: - return (config != LightingConfig::Config0) && (config != LightingConfig::Config2) && - (config != LightingConfig::Config4); - - case LightingSampler::ReflectRed: - return (config != LightingConfig::Config3); - - case LightingSampler::ReflectGreen: - case LightingSampler::ReflectBlue: - return (config == LightingConfig::Config4) || (config == LightingConfig::Config5) || - (config == LightingConfig::Config7); - default: - UNREACHABLE_MSG("Regs::IsLightingSamplerSupported: Reached " - "unreachable section, sampler should be one " - "of Distribution0, Distribution1, Fresnel, " - "ReflectRed, ReflectGreen or ReflectBlue, instead " - "got %i", - static_cast<int>(config)); - } - } - - struct { - struct LightSrc { - LightColor specular_0; // material.specular_0 * light.specular_0 - LightColor specular_1; // material.specular_1 * light.specular_1 - LightColor diffuse; // material.diffuse * light.diffuse - LightColor ambient; // material.ambient * light.ambient - - // Encoded as 16-bit floating point - union { - BitField<0, 16, u32> x; - BitField<16, 16, u32> y; - }; - union { - BitField<0, 16, u32> z; - }; - - INSERT_PADDING_WORDS(0x3); - - union { - BitField<0, 1, u32> directional; - BitField<1, 1, u32> two_sided_diffuse; // When disabled, clamp dot-product to 0 - } config; - - BitField<0, 20, u32> dist_atten_bias; - BitField<0, 20, u32> dist_atten_scale; - - INSERT_PADDING_WORDS(0x4); - }; - static_assert(sizeof(LightSrc) == 0x10 * sizeof(u32), - "LightSrc structure must be 0x10 words"); - - 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 - - union { - BitField<2, 2, LightingFresnelSelector> fresnel_selector; - BitField<4, 4, LightingConfig> config; - BitField<22, 2, u32> bump_selector; // 0: Texture 0, 1: Texture 1, 2: Texture 2 - BitField<27, 1, u32> clamp_highlights; - BitField<28, 2, LightingBumpMode> bump_mode; - BitField<30, 1, u32> disable_bump_renorm; - } config0; - - union { - BitField<16, 1, u32> disable_lut_d0; - BitField<17, 1, u32> disable_lut_d1; - BitField<19, 1, u32> disable_lut_fr; - BitField<20, 1, u32> disable_lut_rr; - BitField<21, 1, u32> disable_lut_rg; - BitField<22, 1, u32> disable_lut_rb; - - // Each bit specifies whether distance attenuation should be applied for the - // corresponding light - - BitField<24, 1, u32> disable_dist_atten_light_0; - BitField<25, 1, u32> disable_dist_atten_light_1; - BitField<26, 1, u32> disable_dist_atten_light_2; - BitField<27, 1, u32> disable_dist_atten_light_3; - BitField<28, 1, u32> disable_dist_atten_light_4; - BitField<29, 1, u32> disable_dist_atten_light_5; - BitField<30, 1, u32> disable_dist_atten_light_6; - BitField<31, 1, u32> disable_dist_atten_light_7; - } config1; - - bool IsDistAttenDisabled(unsigned index) const { - const unsigned disable[] = { - config1.disable_dist_atten_light_0, config1.disable_dist_atten_light_1, - config1.disable_dist_atten_light_2, config1.disable_dist_atten_light_3, - config1.disable_dist_atten_light_4, config1.disable_dist_atten_light_5, - config1.disable_dist_atten_light_6, config1.disable_dist_atten_light_7}; - return disable[index] != 0; - } - - union { - BitField<0, 8, u32> index; ///< Index at which to set data in the LUT - BitField<8, 5, u32> type; ///< Type of LUT for which to set data - } lut_config; - - BitField<0, 1, u32> disable; - INSERT_PADDING_WORDS(0x1); - - // When data is written to any of these registers, it gets written to the lookup table of - // the selected type at the selected index, specified above in the `lut_config` register. - // With each write, `lut_config.index` is incremented. It does not matter which of these - // registers is written to, the behavior will be the same. - u32 lut_data[8]; - - // These are used to specify if absolute (abs) value should be used for each LUT index. When - // abs mode is disabled, LUT indexes are in the range of (-1.0, 1.0). Otherwise, they are in - // the range of (0.0, 1.0). - union { - BitField<1, 1, u32> disable_d0; - BitField<5, 1, u32> disable_d1; - BitField<9, 1, u32> disable_sp; - BitField<13, 1, u32> disable_fr; - BitField<17, 1, u32> disable_rb; - BitField<21, 1, u32> disable_rg; - BitField<25, 1, u32> disable_rr; - } abs_lut_input; - - union { - BitField<0, 3, LightingLutInput> d0; - BitField<4, 3, LightingLutInput> d1; - BitField<8, 3, LightingLutInput> sp; - BitField<12, 3, LightingLutInput> fr; - BitField<16, 3, LightingLutInput> rb; - BitField<20, 3, LightingLutInput> rg; - BitField<24, 3, LightingLutInput> rr; - } lut_input; - - union { - BitField<0, 3, LightingScale> d0; - BitField<4, 3, LightingScale> d1; - BitField<8, 3, LightingScale> sp; - BitField<12, 3, LightingScale> fr; - BitField<16, 3, LightingScale> rb; - BitField<20, 3, LightingScale> rg; - BitField<24, 3, LightingScale> rr; - - static float GetScale(LightingScale scale) { - switch (scale) { - case LightingScale::Scale1: - return 1.0f; - case LightingScale::Scale2: - return 2.0f; - case LightingScale::Scale4: - return 4.0f; - case LightingScale::Scale8: - return 8.0f; - case LightingScale::Scale1_4: - return 0.25f; - case LightingScale::Scale1_2: - return 0.5f; - } - return 0.0f; - } - } lut_scale; - - INSERT_PADDING_WORDS(0x6); - - union { - // There are 8 light enable "slots", corresponding to the total number of lights - // supported by Pica. For N enabled lights (specified by register 0x1c2, or 'src_num' - // above), the first N slots below will be set to integers within the range of 0-7, - // corresponding to the actual light that is enabled for each slot. - - BitField<0, 3, u32> slot_0; - BitField<4, 3, u32> slot_1; - BitField<8, 3, u32> slot_2; - BitField<12, 3, u32> slot_3; - BitField<16, 3, u32> slot_4; - BitField<20, 3, u32> slot_5; - BitField<24, 3, u32> slot_6; - BitField<28, 3, u32> slot_7; - - unsigned GetNum(unsigned index) const { - const unsigned enable_slots[] = {slot_0, slot_1, slot_2, slot_3, - slot_4, slot_5, slot_6, slot_7}; - return enable_slots[index]; - } - } light_enable; - } lighting; - - INSERT_PADDING_WORDS(0x26); - - enum class VertexAttributeFormat : u64 { - BYTE = 0, - UBYTE = 1, - SHORT = 2, - FLOAT = 3, - }; - - struct { - BitField<0, 29, u32> base_address; - - u32 GetPhysicalBaseAddress() const { - return DecodeAddressRegister(base_address); - } - - // Descriptor for internal vertex attributes - union { - BitField<0, 2, VertexAttributeFormat> format0; // size of one element - BitField<2, 2, u64> size0; // number of elements minus 1 - BitField<4, 2, VertexAttributeFormat> format1; - BitField<6, 2, u64> size1; - BitField<8, 2, VertexAttributeFormat> format2; - BitField<10, 2, u64> size2; - BitField<12, 2, VertexAttributeFormat> format3; - BitField<14, 2, u64> size3; - BitField<16, 2, VertexAttributeFormat> format4; - BitField<18, 2, u64> size4; - BitField<20, 2, VertexAttributeFormat> format5; - BitField<22, 2, u64> size5; - BitField<24, 2, VertexAttributeFormat> format6; - BitField<26, 2, u64> size6; - BitField<28, 2, VertexAttributeFormat> format7; - BitField<30, 2, u64> size7; - BitField<32, 2, VertexAttributeFormat> format8; - BitField<34, 2, u64> size8; - BitField<36, 2, VertexAttributeFormat> format9; - BitField<38, 2, u64> size9; - BitField<40, 2, VertexAttributeFormat> format10; - BitField<42, 2, u64> size10; - BitField<44, 2, VertexAttributeFormat> format11; - BitField<46, 2, u64> size11; - - BitField<48, 12, u64> attribute_mask; - - // number of total attributes minus 1 - BitField<60, 4, u64> num_extra_attributes; - }; - - inline VertexAttributeFormat GetFormat(int n) const { - VertexAttributeFormat formats[] = {format0, format1, format2, format3, - format4, format5, format6, format7, - format8, format9, format10, format11}; - return formats[n]; - } - - inline int GetNumElements(int n) const { - u64 sizes[] = {size0, size1, size2, size3, size4, size5, - size6, size7, size8, size9, size10, size11}; - return (int)sizes[n] + 1; - } - - inline int GetElementSizeInBytes(int n) const { - return (GetFormat(n) == VertexAttributeFormat::FLOAT) - ? 4 - : (GetFormat(n) == VertexAttributeFormat::SHORT) ? 2 : 1; - } - - inline int GetStride(int n) const { - return GetNumElements(n) * GetElementSizeInBytes(n); - } - - inline bool IsDefaultAttribute(int id) const { - return (id >= 12) || (attribute_mask & (1ULL << id)) != 0; - } - - inline int GetNumTotalAttributes() const { - return (int)num_extra_attributes + 1; - } - - // Attribute loaders map the source vertex data to input attributes - // This e.g. allows to load different attributes from different memory locations - struct { - // Source attribute data offset from the base address - u32 data_offset; - - union { - BitField<0, 4, u64> comp0; - BitField<4, 4, u64> comp1; - BitField<8, 4, u64> comp2; - BitField<12, 4, u64> comp3; - BitField<16, 4, u64> comp4; - BitField<20, 4, u64> comp5; - BitField<24, 4, u64> comp6; - BitField<28, 4, u64> comp7; - BitField<32, 4, u64> comp8; - BitField<36, 4, u64> comp9; - BitField<40, 4, u64> comp10; - BitField<44, 4, u64> comp11; - - // bytes for a single vertex in this loader - BitField<48, 8, u64> byte_count; - - BitField<60, 4, u64> component_count; - }; - - inline int GetComponent(int n) const { - u64 components[] = {comp0, comp1, comp2, comp3, comp4, comp5, - comp6, comp7, comp8, comp9, comp10, comp11}; - return (int)components[n]; - } - } attribute_loaders[12]; - } vertex_attributes; - - struct { - enum IndexFormat : u32 { - BYTE = 0, - SHORT = 1, - }; - - union { - BitField<0, 31, u32> offset; // relative to base attribute address - BitField<31, 1, IndexFormat> format; - }; - } index_array; - - // Number of vertices to render - u32 num_vertices; - - INSERT_PADDING_WORDS(0x1); - - // The index of the first vertex to render - u32 vertex_offset; - - INSERT_PADDING_WORDS(0x3); - - // These two trigger rendering of triangles - u32 trigger_draw; - u32 trigger_draw_indexed; - - INSERT_PADDING_WORDS(0x2); - - // These registers are used to setup the default "fall-back" vertex shader attributes - struct { - // Index of the current default attribute - u32 index; - - // Writing to these registers sets the "current" default attribute. - u32 set_value[3]; - } vs_default_attributes_setup; - - INSERT_PADDING_WORDS(0x2); - - struct { - // There are two channels that can be used to configure the next command buffer, which - // can be then executed by writing to the "trigger" registers. There are two reasons why a - // game might use this feature: - // 1) With this, an arbitrary number of additional command buffers may be executed in - // sequence without requiring any intervention of the CPU after the initial one is - // kicked off. - // 2) Games can configure these registers to provide a command list subroutine mechanism. - - BitField<0, 20, u32> size[2]; ///< Size (in bytes / 8) of each channel's command buffer - BitField<0, 28, u32> addr[2]; ///< Physical address / 8 of each channel's command buffer - u32 trigger[2]; ///< Triggers execution of the channel's command buffer when written to - - unsigned GetSize(unsigned index) const { - ASSERT(index < 2); - return 8 * size[index]; - } - - PAddr GetPhysicalAddress(unsigned index) const { - ASSERT(index < 2); - return (PAddr)(8 * addr[index]); - } - } command_buffer; - - INSERT_PADDING_WORDS(0x07); - - enum class GPUMode : u32 { - Drawing = 0, - Configuring = 1, - }; - - GPUMode gpu_mode; - - INSERT_PADDING_WORDS(0x18); - - enum class TriangleTopology : u32 { - List = 0, - Strip = 1, - Fan = 2, - Shader = 3, // Programmable setup unit implemented in a geometry shader - }; - - BitField<8, 2, TriangleTopology> triangle_topology; - - u32 restart_primitive; - - INSERT_PADDING_WORDS(0x20); - - struct ShaderConfig { - BitField<0, 16, u32> bool_uniforms; - - union { - BitField<0, 8, u32> x; - BitField<8, 8, u32> y; - BitField<16, 8, u32> z; - BitField<24, 8, u32> w; - } int_uniforms[4]; - - INSERT_PADDING_WORDS(0x4); - - union { - // Number of input attributes to shader unit - 1 - BitField<0, 4, u32> num_input_attributes; - }; - - // 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; - - BitField<0, 16, u32> output_mask; - - // 0x28E, CODETRANSFER_END - INSERT_PADDING_WORDS(0x2); - - struct { - enum Format : u32 { - FLOAT24 = 0, - FLOAT32 = 1, - }; - - bool IsFloat32() const { - return format == FLOAT32; - } - - union { - // Index of the next uniform to write to - // TODO: ctrulib uses 8 bits for this, however that seems to yield lots of invalid - // indices - // TODO: Maybe the uppermost index is for the geometry shader? Investigate! - BitField<0, 7, u32> index; - - BitField<31, 1, Format> format; - }; - - // Writing to these registers sets the current uniform. - u32 set_value[8]; - - } uniform_setup; - - INSERT_PADDING_WORDS(0x2); - - struct { - // Offset of the next instruction to write code to. - // Incremented with each instruction write. - u32 offset; - - // Writing to these registers sets the "current" word in the shader program. - u32 set_word[8]; - } program; - - INSERT_PADDING_WORDS(0x1); - - // This register group is used to load an internal table of swizzling patterns, - // which are indexed by each shader instruction to specify vector component swizzling. - struct { - // Offset of the next swizzle pattern to write code to. - // Incremented with each instruction write. - u32 offset; - - // Writing to these registers sets the current swizzle pattern in the table. - u32 set_word[8]; - } swizzle_patterns; - - INSERT_PADDING_WORDS(0x2); - }; - - ShaderConfig gs; - ShaderConfig vs; - - INSERT_PADDING_WORDS(0x20); - - // Map register indices to names readable by humans - // Used for debugging purposes, so performance is not an issue here - static std::string GetCommandName(int index); - - static constexpr size_t NumIds() { - return sizeof(Regs) / sizeof(u32); - } - - const u32& operator[](int index) const { - const u32* content = reinterpret_cast<const u32*>(this); - return content[index]; - } - - u32& operator[](int index) { - u32* content = reinterpret_cast<u32*>(this); - return content[index]; - } - -private: - /* - * Most physical addresses which Pica registers refer to are 8-byte aligned. - * This function should be used to get the address from a raw register value. - */ - static inline u32 DecodeAddressRegister(u32 register_value) { - return register_value * 8; - } -}; - -// TODO: MSVC does not support using offsetof() on non-static data members even though this -// is technically allowed since C++11. This macro should be enabled once MSVC adds -// support for that. -#ifndef _MSC_VER -#define ASSERT_REG_POSITION(field_name, position) \ - static_assert(offsetof(Regs, field_name) == position * 4, \ - "Field " #field_name " has invalid position") - -ASSERT_REG_POSITION(trigger_irq, 0x10); -ASSERT_REG_POSITION(cull_mode, 0x40); -ASSERT_REG_POSITION(viewport_size_x, 0x41); -ASSERT_REG_POSITION(viewport_size_y, 0x43); -ASSERT_REG_POSITION(viewport_depth_range, 0x4d); -ASSERT_REG_POSITION(viewport_depth_near_plane, 0x4e); -ASSERT_REG_POSITION(vs_output_attributes[0], 0x50); -ASSERT_REG_POSITION(vs_output_attributes[1], 0x51); -ASSERT_REG_POSITION(scissor_test, 0x65); -ASSERT_REG_POSITION(viewport_corner, 0x68); -ASSERT_REG_POSITION(depthmap_enable, 0x6D); -ASSERT_REG_POSITION(texture0_enable, 0x80); -ASSERT_REG_POSITION(texture0, 0x81); -ASSERT_REG_POSITION(texture0_format, 0x8e); -ASSERT_REG_POSITION(fragment_lighting_enable, 0x8f); -ASSERT_REG_POSITION(texture1, 0x91); -ASSERT_REG_POSITION(texture1_format, 0x96); -ASSERT_REG_POSITION(texture2, 0x99); -ASSERT_REG_POSITION(texture2_format, 0x9e); -ASSERT_REG_POSITION(tev_stage0, 0xc0); -ASSERT_REG_POSITION(tev_stage1, 0xc8); -ASSERT_REG_POSITION(tev_stage2, 0xd0); -ASSERT_REG_POSITION(tev_stage3, 0xd8); -ASSERT_REG_POSITION(tev_combiner_buffer_input, 0xe0); -ASSERT_REG_POSITION(fog_mode, 0xe0); -ASSERT_REG_POSITION(fog_color, 0xe1); -ASSERT_REG_POSITION(fog_lut_offset, 0xe6); -ASSERT_REG_POSITION(fog_lut_data, 0xe8); -ASSERT_REG_POSITION(tev_stage4, 0xf0); -ASSERT_REG_POSITION(tev_stage5, 0xf8); -ASSERT_REG_POSITION(tev_combiner_buffer_color, 0xfd); -ASSERT_REG_POSITION(output_merger, 0x100); -ASSERT_REG_POSITION(framebuffer, 0x110); -ASSERT_REG_POSITION(lighting, 0x140); -ASSERT_REG_POSITION(vertex_attributes, 0x200); -ASSERT_REG_POSITION(index_array, 0x227); -ASSERT_REG_POSITION(num_vertices, 0x228); -ASSERT_REG_POSITION(vertex_offset, 0x22a); -ASSERT_REG_POSITION(trigger_draw, 0x22e); -ASSERT_REG_POSITION(trigger_draw_indexed, 0x22f); -ASSERT_REG_POSITION(vs_default_attributes_setup, 0x232); -ASSERT_REG_POSITION(command_buffer, 0x238); -ASSERT_REG_POSITION(gpu_mode, 0x245); -ASSERT_REG_POSITION(triangle_topology, 0x25e); -ASSERT_REG_POSITION(restart_primitive, 0x25f); -ASSERT_REG_POSITION(gs, 0x280); -ASSERT_REG_POSITION(vs, 0x2b0); - -#undef ASSERT_REG_POSITION -#endif // !defined(_MSC_VER) - -static_assert(sizeof(Regs::ShaderConfig) == 0x30 * sizeof(u32), - "ShaderConfig structure has incorrect size"); - -// The total number of registers is chosen arbitrarily, but let's make sure it's not some odd value -// anyway. -static_assert(sizeof(Regs) <= 0x300 * sizeof(u32), - "Register set structure larger than it should be"); -static_assert(sizeof(Regs) >= 0x300 * sizeof(u32), - "Register set structure smaller than it should be"); - /// Initialize Pica state void Init(); diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h index e4f2e6d5d..af7536d11 100644 --- a/src/video_core/pica_state.h +++ b/src/video_core/pica_state.h @@ -7,8 +7,8 @@ #include <array> #include "common/bit_field.h" #include "common/common_types.h" -#include "video_core/pica.h" #include "video_core/primitive_assembly.h" +#include "video_core/regs.h" #include "video_core/shader/shader.h" namespace Pica { @@ -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..acd2ac5e2 100644 --- a/src/video_core/primitive_assembly.cpp +++ b/src/video_core/primitive_assembly.cpp @@ -3,23 +3,23 @@ // Refer to the license.txt file included. #include "common/logging/log.h" -#include "video_core/pica.h" #include "video_core/primitive_assembly.h" +#include "video_core/regs_pipeline.h" #include "video_core/shader/shader.h" namespace Pica { template <typename VertexType> -PrimitiveAssembler<VertexType>::PrimitiveAssembler(Regs::TriangleTopology topology) +PrimitiveAssembler<VertexType>::PrimitiveAssembler(PipelineRegs::TriangleTopology topology) : 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. - case Regs::TriangleTopology::List: - case Regs::TriangleTopology::Shader: + case PipelineRegs::TriangleTopology::List: + case PipelineRegs::TriangleTopology::Shader: if (buffer_index < 2) { buffer[buffer_index++] = vtx; } else { @@ -29,8 +29,8 @@ void PrimitiveAssembler<VertexType>::SubmitVertex(VertexType& vtx, } break; - case Regs::TriangleTopology::Strip: - case Regs::TriangleTopology::Fan: + case PipelineRegs::TriangleTopology::Strip: + case PipelineRegs::TriangleTopology::Fan: if (strip_ready) triangle_handler(buffer[0], buffer[1], vtx); @@ -38,9 +38,9 @@ void PrimitiveAssembler<VertexType>::SubmitVertex(VertexType& vtx, strip_ready |= (buffer_index == 1); - if (topology == Regs::TriangleTopology::Strip) + if (topology == PipelineRegs::TriangleTopology::Strip) buffer_index = !buffer_index; - else if (topology == Regs::TriangleTopology::Fan) + else if (topology == PipelineRegs::TriangleTopology::Fan) buffer_index = 1; break; @@ -57,7 +57,7 @@ void PrimitiveAssembler<VertexType>::Reset() { } template <typename VertexType> -void PrimitiveAssembler<VertexType>::Reconfigure(Regs::TriangleTopology topology) { +void PrimitiveAssembler<VertexType>::Reconfigure(PipelineRegs::TriangleTopology topology) { Reset(); this->topology = topology; } diff --git a/src/video_core/primitive_assembly.h b/src/video_core/primitive_assembly.h index 0384d5984..e8eccdf27 100644 --- a/src/video_core/primitive_assembly.h +++ b/src/video_core/primitive_assembly.h @@ -5,7 +5,7 @@ #pragma once #include <functional> -#include "video_core/pica.h" +#include "video_core/regs_pipeline.h" namespace Pica { @@ -15,9 +15,11 @@ 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); + PrimitiveAssembler( + PipelineRegs::TriangleTopology topology = PipelineRegs::TriangleTopology::List); /* * Queues a vertex, builds primitives from the vertex queue according to the given @@ -25,7 +27,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. @@ -35,10 +37,10 @@ struct PrimitiveAssembler { /** * Reconfigures the PrimitiveAssembler to use a different triangle topology. */ - void Reconfigure(Regs::TriangleTopology topology); + void Reconfigure(PipelineRegs::TriangleTopology topology); private: - Regs::TriangleTopology topology; + PipelineRegs::TriangleTopology topology; int buffer_index; VertexType buffer[2]; diff --git a/src/video_core/rasterizer.h b/src/video_core/rasterizer.h deleted file mode 100644 index 6cbda3067..000000000 --- a/src/video_core/rasterizer.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -namespace Pica { - -namespace Shader { -struct OutputVertex; -} - -namespace Rasterizer { - -void ProcessTriangle(const Shader::OutputVertex& v0, const Shader::OutputVertex& v1, - const Shader::OutputVertex& v2); - -} // namespace Rasterizer - -} // namespace Pica diff --git a/src/video_core/regs.cpp b/src/video_core/regs.cpp new file mode 100644 index 000000000..2699e710a --- /dev/null +++ b/src/video_core/regs.cpp @@ -0,0 +1,488 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <iterator> +#include <utility> + +#include "common/common_types.h" +#include "video_core/regs.h" + +namespace Pica { + +static const std::pair<u16, const char*> register_names[] = { + {0x010, "GPUREG_FINALIZE"}, + + {0x040, "GPUREG_FACECULLING_CONFIG"}, + {0x041, "GPUREG_VIEWPORT_WIDTH"}, + {0x042, "GPUREG_VIEWPORT_INVW"}, + {0x043, "GPUREG_VIEWPORT_HEIGHT"}, + {0x044, "GPUREG_VIEWPORT_INVH"}, + + {0x047, "GPUREG_FRAGOP_CLIP"}, + {0x048, "GPUREG_FRAGOP_CLIP_DATA0"}, + {0x049, "GPUREG_FRAGOP_CLIP_DATA1"}, + {0x04A, "GPUREG_FRAGOP_CLIP_DATA2"}, + {0x04B, "GPUREG_FRAGOP_CLIP_DATA3"}, + + {0x04D, "GPUREG_DEPTHMAP_SCALE"}, + {0x04E, "GPUREG_DEPTHMAP_OFFSET"}, + {0x04F, "GPUREG_SH_OUTMAP_TOTAL"}, + {0x050, "GPUREG_SH_OUTMAP_O0"}, + {0x051, "GPUREG_SH_OUTMAP_O1"}, + {0x052, "GPUREG_SH_OUTMAP_O2"}, + {0x053, "GPUREG_SH_OUTMAP_O3"}, + {0x054, "GPUREG_SH_OUTMAP_O4"}, + {0x055, "GPUREG_SH_OUTMAP_O5"}, + {0x056, "GPUREG_SH_OUTMAP_O6"}, + + {0x061, "GPUREG_EARLYDEPTH_FUNC"}, + {0x062, "GPUREG_EARLYDEPTH_TEST1"}, + {0x063, "GPUREG_EARLYDEPTH_CLEAR"}, + {0x064, "GPUREG_SH_OUTATTR_MODE"}, + {0x065, "GPUREG_SCISSORTEST_MODE"}, + {0x066, "GPUREG_SCISSORTEST_POS"}, + {0x067, "GPUREG_SCISSORTEST_DIM"}, + {0x068, "GPUREG_VIEWPORT_XY"}, + + {0x06A, "GPUREG_EARLYDEPTH_DATA"}, + + {0x06D, "GPUREG_DEPTHMAP_ENABLE"}, + {0x06E, "GPUREG_RENDERBUF_DIM"}, + {0x06F, "GPUREG_SH_OUTATTR_CLOCK"}, + + {0x080, "GPUREG_TEXUNIT_CONFIG"}, + {0x081, "GPUREG_TEXUNIT0_BORDER_COLOR"}, + {0x082, "GPUREG_TEXUNIT0_DIM"}, + {0x083, "GPUREG_TEXUNIT0_PARAM"}, + {0x084, "GPUREG_TEXUNIT0_LOD"}, + {0x085, "GPUREG_TEXUNIT0_ADDR1"}, + {0x086, "GPUREG_TEXUNIT0_ADDR2"}, + {0x087, "GPUREG_TEXUNIT0_ADDR3"}, + {0x088, "GPUREG_TEXUNIT0_ADDR4"}, + {0x089, "GPUREG_TEXUNIT0_ADDR5"}, + {0x08A, "GPUREG_TEXUNIT0_ADDR6"}, + {0x08B, "GPUREG_TEXUNIT0_SHADOW"}, + + {0x08E, "GPUREG_TEXUNIT0_TYPE"}, + {0x08F, "GPUREG_LIGHTING_ENABLE0"}, + + {0x091, "GPUREG_TEXUNIT1_BORDER_COLOR"}, + {0x092, "GPUREG_TEXUNIT1_DIM"}, + {0x093, "GPUREG_TEXUNIT1_PARAM"}, + {0x094, "GPUREG_TEXUNIT1_LOD"}, + {0x095, "GPUREG_TEXUNIT1_ADDR"}, + {0x096, "GPUREG_TEXUNIT1_TYPE"}, + + {0x099, "GPUREG_TEXUNIT2_BORDER_COLOR"}, + {0x09A, "GPUREG_TEXUNIT2_DIM"}, + {0x09B, "GPUREG_TEXUNIT2_PARAM"}, + {0x09C, "GPUREG_TEXUNIT2_LOD"}, + {0x09D, "GPUREG_TEXUNIT2_ADDR"}, + {0x09E, "GPUREG_TEXUNIT2_TYPE"}, + + {0x0A8, "GPUREG_TEXUNIT3_PROCTEX0"}, + {0x0A9, "GPUREG_TEXUNIT3_PROCTEX1"}, + {0x0AA, "GPUREG_TEXUNIT3_PROCTEX2"}, + {0x0AB, "GPUREG_TEXUNIT3_PROCTEX3"}, + {0x0AC, "GPUREG_TEXUNIT3_PROCTEX4"}, + {0x0AD, "GPUREG_TEXUNIT3_PROCTEX5"}, + + {0x0AF, "GPUREG_PROCTEX_LUT"}, + {0x0B0, "GPUREG_PROCTEX_LUT_DATA0"}, + {0x0B1, "GPUREG_PROCTEX_LUT_DATA1"}, + {0x0B2, "GPUREG_PROCTEX_LUT_DATA2"}, + {0x0B3, "GPUREG_PROCTEX_LUT_DATA3"}, + {0x0B4, "GPUREG_PROCTEX_LUT_DATA4"}, + {0x0B5, "GPUREG_PROCTEX_LUT_DATA5"}, + {0x0B6, "GPUREG_PROCTEX_LUT_DATA6"}, + {0x0B7, "GPUREG_PROCTEX_LUT_DATA7"}, + + {0x0C0, "GPUREG_TEXENV0_SOURCE"}, + {0x0C1, "GPUREG_TEXENV0_OPERAND"}, + {0x0C2, "GPUREG_TEXENV0_COMBINER"}, + {0x0C3, "GPUREG_TEXENV0_COLOR"}, + {0x0C4, "GPUREG_TEXENV0_SCALE"}, + + {0x0C8, "GPUREG_TEXENV1_SOURCE"}, + {0x0C9, "GPUREG_TEXENV1_OPERAND"}, + {0x0CA, "GPUREG_TEXENV1_COMBINER"}, + {0x0CB, "GPUREG_TEXENV1_COLOR"}, + {0x0CC, "GPUREG_TEXENV1_SCALE"}, + + {0x0D0, "GPUREG_TEXENV2_SOURCE"}, + {0x0D1, "GPUREG_TEXENV2_OPERAND"}, + {0x0D2, "GPUREG_TEXENV2_COMBINER"}, + {0x0D3, "GPUREG_TEXENV2_COLOR"}, + {0x0D4, "GPUREG_TEXENV2_SCALE"}, + + {0x0D8, "GPUREG_TEXENV3_SOURCE"}, + {0x0D9, "GPUREG_TEXENV3_OPERAND"}, + {0x0DA, "GPUREG_TEXENV3_COMBINER"}, + {0x0DB, "GPUREG_TEXENV3_COLOR"}, + {0x0DC, "GPUREG_TEXENV3_SCALE"}, + + {0x0E0, "GPUREG_TEXENV_UPDATE_BUFFER"}, + {0x0E1, "GPUREG_FOG_COLOR"}, + + {0x0E4, "GPUREG_GAS_ATTENUATION"}, + {0x0E5, "GPUREG_GAS_ACCMAX"}, + {0x0E6, "GPUREG_FOG_LUT_INDEX"}, + + {0x0E8, "GPUREG_FOG_LUT_DATA0"}, + {0x0E9, "GPUREG_FOG_LUT_DATA1"}, + {0x0EA, "GPUREG_FOG_LUT_DATA2"}, + {0x0EB, "GPUREG_FOG_LUT_DATA3"}, + {0x0EC, "GPUREG_FOG_LUT_DATA4"}, + {0x0ED, "GPUREG_FOG_LUT_DATA5"}, + {0x0EE, "GPUREG_FOG_LUT_DATA6"}, + {0x0EF, "GPUREG_FOG_LUT_DATA7"}, + {0x0F0, "GPUREG_TEXENV4_SOURCE"}, + {0x0F1, "GPUREG_TEXENV4_OPERAND"}, + {0x0F2, "GPUREG_TEXENV4_COMBINER"}, + {0x0F3, "GPUREG_TEXENV4_COLOR"}, + {0x0F4, "GPUREG_TEXENV4_SCALE"}, + + {0x0F8, "GPUREG_TEXENV5_SOURCE"}, + {0x0F9, "GPUREG_TEXENV5_OPERAND"}, + {0x0FA, "GPUREG_TEXENV5_COMBINER"}, + {0x0FB, "GPUREG_TEXENV5_COLOR"}, + {0x0FC, "GPUREG_TEXENV5_SCALE"}, + {0x0FD, "GPUREG_TEXENV_BUFFER_COLOR"}, + + {0x100, "GPUREG_COLOR_OPERATION"}, + {0x101, "GPUREG_BLEND_FUNC"}, + {0x102, "GPUREG_LOGIC_OP"}, + {0x103, "GPUREG_BLEND_COLOR"}, + {0x104, "GPUREG_FRAGOP_ALPHA_TEST"}, + {0x105, "GPUREG_STENCIL_TEST"}, + {0x106, "GPUREG_STENCIL_OP"}, + {0x107, "GPUREG_DEPTH_COLOR_MASK"}, + + {0x110, "GPUREG_FRAMEBUFFER_INVALIDATE"}, + {0x111, "GPUREG_FRAMEBUFFER_FLUSH"}, + {0x112, "GPUREG_COLORBUFFER_READ"}, + {0x113, "GPUREG_COLORBUFFER_WRITE"}, + {0x114, "GPUREG_DEPTHBUFFER_READ"}, + {0x115, "GPUREG_DEPTHBUFFER_WRITE"}, + {0x116, "GPUREG_DEPTHBUFFER_FORMAT"}, + {0x117, "GPUREG_COLORBUFFER_FORMAT"}, + {0x118, "GPUREG_EARLYDEPTH_TEST2"}, + + {0x11B, "GPUREG_FRAMEBUFFER_BLOCK32"}, + {0x11C, "GPUREG_DEPTHBUFFER_LOC"}, + {0x11D, "GPUREG_COLORBUFFER_LOC"}, + {0x11E, "GPUREG_FRAMEBUFFER_DIM"}, + + {0x120, "GPUREG_GAS_LIGHT_XY"}, + {0x121, "GPUREG_GAS_LIGHT_Z"}, + {0x122, "GPUREG_GAS_LIGHT_Z_COLOR"}, + {0x123, "GPUREG_GAS_LUT_INDEX"}, + {0x124, "GPUREG_GAS_LUT_DATA"}, + + {0x126, "GPUREG_GAS_DELTAZ_DEPTH"}, + + {0x130, "GPUREG_FRAGOP_SHADOW"}, + + {0x140, "GPUREG_LIGHT0_SPECULAR0"}, + {0x141, "GPUREG_LIGHT0_SPECULAR1"}, + {0x142, "GPUREG_LIGHT0_DIFFUSE"}, + {0x143, "GPUREG_LIGHT0_AMBIENT"}, + {0x144, "GPUREG_LIGHT0_XY"}, + {0x145, "GPUREG_LIGHT0_Z"}, + {0x146, "GPUREG_LIGHT0_SPOTDIR_XY"}, + {0x147, "GPUREG_LIGHT0_SPOTDIR_Z"}, + + {0x149, "GPUREG_LIGHT0_CONFIG"}, + {0x14A, "GPUREG_LIGHT0_ATTENUATION_BIAS"}, + {0x14B, "GPUREG_LIGHT0_ATTENUATION_SCALE"}, + + {0x150, "GPUREG_LIGHT1_SPECULAR0"}, + {0x151, "GPUREG_LIGHT1_SPECULAR1"}, + {0x152, "GPUREG_LIGHT1_DIFFUSE"}, + {0x153, "GPUREG_LIGHT1_AMBIENT"}, + {0x154, "GPUREG_LIGHT1_XY"}, + {0x155, "GPUREG_LIGHT1_Z"}, + {0x156, "GPUREG_LIGHT1_SPOTDIR_XY"}, + {0x157, "GPUREG_LIGHT1_SPOTDIR_Z"}, + + {0x159, "GPUREG_LIGHT1_CONFIG"}, + {0x15A, "GPUREG_LIGHT1_ATTENUATION_BIAS"}, + {0x15B, "GPUREG_LIGHT1_ATTENUATION_SCALE"}, + + {0x160, "GPUREG_LIGHT2_SPECULAR0"}, + {0x161, "GPUREG_LIGHT2_SPECULAR1"}, + {0x162, "GPUREG_LIGHT2_DIFFUSE"}, + {0x163, "GPUREG_LIGHT2_AMBIENT"}, + {0x164, "GPUREG_LIGHT2_XY"}, + {0x165, "GPUREG_LIGHT2_Z"}, + {0x166, "GPUREG_LIGHT2_SPOTDIR_XY"}, + {0x167, "GPUREG_LIGHT2_SPOTDIR_Z"}, + + {0x169, "GPUREG_LIGHT2_CONFIG"}, + {0x16A, "GPUREG_LIGHT2_ATTENUATION_BIAS"}, + {0x16B, "GPUREG_LIGHT2_ATTENUATION_SCALE"}, + + {0x170, "GPUREG_LIGHT3_SPECULAR0"}, + {0x171, "GPUREG_LIGHT3_SPECULAR1"}, + {0x172, "GPUREG_LIGHT3_DIFFUSE"}, + {0x173, "GPUREG_LIGHT3_AMBIENT"}, + {0x174, "GPUREG_LIGHT3_XY"}, + {0x175, "GPUREG_LIGHT3_Z"}, + {0x176, "GPUREG_LIGHT3_SPOTDIR_XY"}, + {0x177, "GPUREG_LIGHT3_SPOTDIR_Z"}, + + {0x179, "GPUREG_LIGHT3_CONFIG"}, + {0x17A, "GPUREG_LIGHT3_ATTENUATION_BIAS"}, + {0x17B, "GPUREG_LIGHT3_ATTENUATION_SCALE"}, + + {0x180, "GPUREG_LIGHT4_SPECULAR0"}, + {0x181, "GPUREG_LIGHT4_SPECULAR1"}, + {0x182, "GPUREG_LIGHT4_DIFFUSE"}, + {0x183, "GPUREG_LIGHT4_AMBIENT"}, + {0x184, "GPUREG_LIGHT4_XY"}, + {0x185, "GPUREG_LIGHT4_Z"}, + {0x186, "GPUREG_LIGHT4_SPOTDIR_XY"}, + {0x187, "GPUREG_LIGHT4_SPOTDIR_Z"}, + + {0x189, "GPUREG_LIGHT4_CONFIG"}, + {0x18A, "GPUREG_LIGHT4_ATTENUATION_BIAS"}, + {0x18B, "GPUREG_LIGHT4_ATTENUATION_SCALE"}, + + {0x190, "GPUREG_LIGHT5_SPECULAR0"}, + {0x191, "GPUREG_LIGHT5_SPECULAR1"}, + {0x192, "GPUREG_LIGHT5_DIFFUSE"}, + {0x193, "GPUREG_LIGHT5_AMBIENT"}, + {0x194, "GPUREG_LIGHT5_XY"}, + {0x195, "GPUREG_LIGHT5_Z"}, + {0x196, "GPUREG_LIGHT5_SPOTDIR_XY"}, + {0x197, "GPUREG_LIGHT5_SPOTDIR_Z"}, + + {0x199, "GPUREG_LIGHT5_CONFIG"}, + {0x19A, "GPUREG_LIGHT5_ATTENUATION_BIAS"}, + {0x19B, "GPUREG_LIGHT5_ATTENUATION_SCALE"}, + + {0x1A0, "GPUREG_LIGHT6_SPECULAR0"}, + {0x1A1, "GPUREG_LIGHT6_SPECULAR1"}, + {0x1A2, "GPUREG_LIGHT6_DIFFUSE"}, + {0x1A3, "GPUREG_LIGHT6_AMBIENT"}, + {0x1A4, "GPUREG_LIGHT6_XY"}, + {0x1A5, "GPUREG_LIGHT6_Z"}, + {0x1A6, "GPUREG_LIGHT6_SPOTDIR_XY"}, + {0x1A7, "GPUREG_LIGHT6_SPOTDIR_Z"}, + + {0x1A9, "GPUREG_LIGHT6_CONFIG"}, + {0x1AA, "GPUREG_LIGHT6_ATTENUATION_BIAS"}, + {0x1AB, "GPUREG_LIGHT6_ATTENUATION_SCALE"}, + + {0x1B0, "GPUREG_LIGHT7_SPECULAR0"}, + {0x1B1, "GPUREG_LIGHT7_SPECULAR1"}, + {0x1B2, "GPUREG_LIGHT7_DIFFUSE"}, + {0x1B3, "GPUREG_LIGHT7_AMBIENT"}, + {0x1B4, "GPUREG_LIGHT7_XY"}, + {0x1B5, "GPUREG_LIGHT7_Z"}, + {0x1B6, "GPUREG_LIGHT7_SPOTDIR_XY"}, + {0x1B7, "GPUREG_LIGHT7_SPOTDIR_Z"}, + + {0x1B9, "GPUREG_LIGHT7_CONFIG"}, + {0x1BA, "GPUREG_LIGHT7_ATTENUATION_BIAS"}, + {0x1BB, "GPUREG_LIGHT7_ATTENUATION_SCALE"}, + + {0x1C0, "GPUREG_LIGHTING_AMBIENT"}, + + {0x1C2, "GPUREG_LIGHTING_NUM_LIGHTS"}, + {0x1C3, "GPUREG_LIGHTING_CONFIG0"}, + {0x1C4, "GPUREG_LIGHTING_CONFIG1"}, + {0x1C5, "GPUREG_LIGHTING_LUT_INDEX"}, + {0x1C6, "GPUREG_LIGHTING_ENABLE1"}, + + {0x1C8, "GPUREG_LIGHTING_LUT_DATA0"}, + {0x1C9, "GPUREG_LIGHTING_LUT_DATA1"}, + {0x1CA, "GPUREG_LIGHTING_LUT_DATA2"}, + {0x1CB, "GPUREG_LIGHTING_LUT_DATA3"}, + {0x1CC, "GPUREG_LIGHTING_LUT_DATA4"}, + {0x1CD, "GPUREG_LIGHTING_LUT_DATA5"}, + {0x1CE, "GPUREG_LIGHTING_LUT_DATA6"}, + {0x1CF, "GPUREG_LIGHTING_LUT_DATA7"}, + {0x1D0, "GPUREG_LIGHTING_LUTINPUT_ABS"}, + {0x1D1, "GPUREG_LIGHTING_LUTINPUT_SELECT"}, + {0x1D2, "GPUREG_LIGHTING_LUTINPUT_SCALE"}, + + {0x1D9, "GPUREG_LIGHTING_LIGHT_PERMUTATION"}, + + {0x200, "GPUREG_ATTRIBBUFFERS_LOC"}, + {0x201, "GPUREG_ATTRIBBUFFERS_FORMAT_LOW"}, + {0x202, "GPUREG_ATTRIBBUFFERS_FORMAT_HIGH"}, + {0x203, "GPUREG_ATTRIBBUFFER0_OFFSET"}, + {0x204, "GPUREG_ATTRIBBUFFER0_CONFIG1"}, + {0x205, "GPUREG_ATTRIBBUFFER0_CONFIG2"}, + {0x206, "GPUREG_ATTRIBBUFFER1_OFFSET"}, + {0x207, "GPUREG_ATTRIBBUFFER1_CONFIG1"}, + {0x208, "GPUREG_ATTRIBBUFFER1_CONFIG2"}, + {0x209, "GPUREG_ATTRIBBUFFER2_OFFSET"}, + {0x20A, "GPUREG_ATTRIBBUFFER2_CONFIG1"}, + {0x20B, "GPUREG_ATTRIBBUFFER2_CONFIG2"}, + {0x20C, "GPUREG_ATTRIBBUFFER3_OFFSET"}, + {0x20D, "GPUREG_ATTRIBBUFFER3_CONFIG1"}, + {0x20E, "GPUREG_ATTRIBBUFFER3_CONFIG2"}, + {0x20F, "GPUREG_ATTRIBBUFFER4_OFFSET"}, + {0x210, "GPUREG_ATTRIBBUFFER4_CONFIG1"}, + {0x211, "GPUREG_ATTRIBBUFFER4_CONFIG2"}, + {0x212, "GPUREG_ATTRIBBUFFER5_OFFSET"}, + {0x213, "GPUREG_ATTRIBBUFFER5_CONFIG1"}, + {0x214, "GPUREG_ATTRIBBUFFER5_CONFIG2"}, + {0x215, "GPUREG_ATTRIBBUFFER6_OFFSET"}, + {0x216, "GPUREG_ATTRIBBUFFER6_CONFIG1"}, + {0x217, "GPUREG_ATTRIBBUFFER6_CONFIG2"}, + {0x218, "GPUREG_ATTRIBBUFFER7_OFFSET"}, + {0x219, "GPUREG_ATTRIBBUFFER7_CONFIG1"}, + {0x21A, "GPUREG_ATTRIBBUFFER7_CONFIG2"}, + {0x21B, "GPUREG_ATTRIBBUFFER8_OFFSET"}, + {0x21C, "GPUREG_ATTRIBBUFFER8_CONFIG1"}, + {0x21D, "GPUREG_ATTRIBBUFFER8_CONFIG2"}, + {0x21E, "GPUREG_ATTRIBBUFFER9_OFFSET"}, + {0x21F, "GPUREG_ATTRIBBUFFER9_CONFIG1"}, + {0x220, "GPUREG_ATTRIBBUFFER9_CONFIG2"}, + {0x221, "GPUREG_ATTRIBBUFFER10_OFFSET"}, + {0x222, "GPUREG_ATTRIBBUFFER10_CONFIG1"}, + {0x223, "GPUREG_ATTRIBBUFFER10_CONFIG2"}, + {0x224, "GPUREG_ATTRIBBUFFER11_OFFSET"}, + {0x225, "GPUREG_ATTRIBBUFFER11_CONFIG1"}, + {0x226, "GPUREG_ATTRIBBUFFER11_CONFIG2"}, + {0x227, "GPUREG_INDEXBUFFER_CONFIG"}, + {0x228, "GPUREG_NUMVERTICES"}, + {0x229, "GPUREG_GEOSTAGE_CONFIG"}, + {0x22A, "GPUREG_VERTEX_OFFSET"}, + + {0x22D, "GPUREG_POST_VERTEX_CACHE_NUM"}, + {0x22E, "GPUREG_DRAWARRAYS"}, + {0x22F, "GPUREG_DRAWELEMENTS"}, + + {0x231, "GPUREG_VTX_FUNC"}, + {0x232, "GPUREG_FIXEDATTRIB_INDEX"}, + {0x233, "GPUREG_FIXEDATTRIB_DATA0"}, + {0x234, "GPUREG_FIXEDATTRIB_DATA1"}, + {0x235, "GPUREG_FIXEDATTRIB_DATA2"}, + + {0x238, "GPUREG_CMDBUF_SIZE0"}, + {0x239, "GPUREG_CMDBUF_SIZE1"}, + {0x23A, "GPUREG_CMDBUF_ADDR0"}, + {0x23B, "GPUREG_CMDBUF_ADDR1"}, + {0x23C, "GPUREG_CMDBUF_JUMP0"}, + {0x23D, "GPUREG_CMDBUF_JUMP1"}, + + {0x242, "GPUREG_VSH_NUM_ATTR"}, + + {0x244, "GPUREG_VSH_COM_MODE"}, + {0x245, "GPUREG_START_DRAW_FUNC0"}, + + {0x24A, "GPUREG_VSH_OUTMAP_TOTAL1"}, + + {0x251, "GPUREG_VSH_OUTMAP_TOTAL2"}, + {0x252, "GPUREG_GSH_MISC0"}, + {0x253, "GPUREG_GEOSTAGE_CONFIG2"}, + {0x254, "GPUREG_GSH_MISC1"}, + + {0x25E, "GPUREG_PRIMITIVE_CONFIG"}, + {0x25F, "GPUREG_RESTART_PRIMITIVE"}, + + {0x280, "GPUREG_GSH_BOOLUNIFORM"}, + {0x281, "GPUREG_GSH_INTUNIFORM_I0"}, + {0x282, "GPUREG_GSH_INTUNIFORM_I1"}, + {0x283, "GPUREG_GSH_INTUNIFORM_I2"}, + {0x284, "GPUREG_GSH_INTUNIFORM_I3"}, + + {0x289, "GPUREG_GSH_INPUTBUFFER_CONFIG"}, + {0x28A, "GPUREG_GSH_ENTRYPOINT"}, + {0x28B, "GPUREG_GSH_ATTRIBUTES_PERMUTATION_LOW"}, + {0x28C, "GPUREG_GSH_ATTRIBUTES_PERMUTATION_HIGH"}, + {0x28D, "GPUREG_GSH_OUTMAP_MASK"}, + + {0x28F, "GPUREG_GSH_CODETRANSFER_END"}, + {0x290, "GPUREG_GSH_FLOATUNIFORM_INDEX"}, + {0x291, "GPUREG_GSH_FLOATUNIFORM_DATA0"}, + {0x292, "GPUREG_GSH_FLOATUNIFORM_DATA1"}, + {0x293, "GPUREG_GSH_FLOATUNIFORM_DATA2"}, + {0x294, "GPUREG_GSH_FLOATUNIFORM_DATA3"}, + {0x295, "GPUREG_GSH_FLOATUNIFORM_DATA4"}, + {0x296, "GPUREG_GSH_FLOATUNIFORM_DATA5"}, + {0x297, "GPUREG_GSH_FLOATUNIFORM_DATA6"}, + {0x298, "GPUREG_GSH_FLOATUNIFORM_DATA7"}, + + {0x29B, "GPUREG_GSH_CODETRANSFER_INDEX"}, + {0x29C, "GPUREG_GSH_CODETRANSFER_DATA0"}, + {0x29D, "GPUREG_GSH_CODETRANSFER_DATA1"}, + {0x29E, "GPUREG_GSH_CODETRANSFER_DATA2"}, + {0x29F, "GPUREG_GSH_CODETRANSFER_DATA3"}, + {0x2A0, "GPUREG_GSH_CODETRANSFER_DATA4"}, + {0x2A1, "GPUREG_GSH_CODETRANSFER_DATA5"}, + {0x2A2, "GPUREG_GSH_CODETRANSFER_DATA6"}, + {0x2A3, "GPUREG_GSH_CODETRANSFER_DATA7"}, + + {0x2A5, "GPUREG_GSH_OPDESCS_INDEX"}, + {0x2A6, "GPUREG_GSH_OPDESCS_DATA0"}, + {0x2A7, "GPUREG_GSH_OPDESCS_DATA1"}, + {0x2A8, "GPUREG_GSH_OPDESCS_DATA2"}, + {0x2A9, "GPUREG_GSH_OPDESCS_DATA3"}, + {0x2AA, "GPUREG_GSH_OPDESCS_DATA4"}, + {0x2AB, "GPUREG_GSH_OPDESCS_DATA5"}, + {0x2AC, "GPUREG_GSH_OPDESCS_DATA6"}, + {0x2AD, "GPUREG_GSH_OPDESCS_DATA7"}, + + {0x2B0, "GPUREG_VSH_BOOLUNIFORM"}, + {0x2B1, "GPUREG_VSH_INTUNIFORM_I0"}, + {0x2B2, "GPUREG_VSH_INTUNIFORM_I1"}, + {0x2B3, "GPUREG_VSH_INTUNIFORM_I2"}, + {0x2B4, "GPUREG_VSH_INTUNIFORM_I3"}, + + {0x2B9, "GPUREG_VSH_INPUTBUFFER_CONFIG"}, + {0x2BA, "GPUREG_VSH_ENTRYPOINT"}, + {0x2BB, "GPUREG_VSH_ATTRIBUTES_PERMUTATION_LOW"}, + {0x2BC, "GPUREG_VSH_ATTRIBUTES_PERMUTATION_HIGH"}, + {0x2BD, "GPUREG_VSH_OUTMAP_MASK"}, + + {0x2BF, "GPUREG_VSH_CODETRANSFER_END"}, + {0x2C0, "GPUREG_VSH_FLOATUNIFORM_INDEX"}, + {0x2C1, "GPUREG_VSH_FLOATUNIFORM_DATA0"}, + {0x2C2, "GPUREG_VSH_FLOATUNIFORM_DATA1"}, + {0x2C3, "GPUREG_VSH_FLOATUNIFORM_DATA2"}, + {0x2C4, "GPUREG_VSH_FLOATUNIFORM_DATA3"}, + {0x2C5, "GPUREG_VSH_FLOATUNIFORM_DATA4"}, + {0x2C6, "GPUREG_VSH_FLOATUNIFORM_DATA5"}, + {0x2C7, "GPUREG_VSH_FLOATUNIFORM_DATA6"}, + {0x2C8, "GPUREG_VSH_FLOATUNIFORM_DATA7"}, + + {0x2CB, "GPUREG_VSH_CODETRANSFER_INDEX"}, + {0x2CC, "GPUREG_VSH_CODETRANSFER_DATA0"}, + {0x2CD, "GPUREG_VSH_CODETRANSFER_DATA1"}, + {0x2CE, "GPUREG_VSH_CODETRANSFER_DATA2"}, + {0x2CF, "GPUREG_VSH_CODETRANSFER_DATA3"}, + {0x2D0, "GPUREG_VSH_CODETRANSFER_DATA4"}, + {0x2D1, "GPUREG_VSH_CODETRANSFER_DATA5"}, + {0x2D2, "GPUREG_VSH_CODETRANSFER_DATA6"}, + {0x2D3, "GPUREG_VSH_CODETRANSFER_DATA7"}, + + {0x2D5, "GPUREG_VSH_OPDESCS_INDEX"}, + {0x2D6, "GPUREG_VSH_OPDESCS_DATA0"}, + {0x2D7, "GPUREG_VSH_OPDESCS_DATA1"}, + {0x2D8, "GPUREG_VSH_OPDESCS_DATA2"}, + {0x2D9, "GPUREG_VSH_OPDESCS_DATA3"}, + {0x2DA, "GPUREG_VSH_OPDESCS_DATA4"}, + {0x2DB, "GPUREG_VSH_OPDESCS_DATA5"}, + {0x2DC, "GPUREG_VSH_OPDESCS_DATA6"}, + {0x2DD, "GPUREG_VSH_OPDESCS_DATA7"}, +}; + +const char* Regs::GetRegisterName(u16 index) { + auto found = std::lower_bound(std::begin(register_names), std::end(register_names), index, + [](auto p, auto i) { return p.first < i; }); + if (found->first == index) { + return found->second; + } else { + // Return empty string if no match is found + return ""; + } +} + +} // namespace Pica diff --git a/src/video_core/regs.h b/src/video_core/regs.h new file mode 100644 index 000000000..86826088b --- /dev/null +++ b/src/video_core/regs.h @@ -0,0 +1,142 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include <string> +#ifndef _MSC_VER +#include <type_traits> // for std::enable_if +#endif + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_lighting.h" +#include "video_core/regs_pipeline.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_shader.h" +#include "video_core/regs_texturing.h" + +namespace Pica { + +// Returns index corresponding to the Regs member labeled by field_name +// TODO: Due to Visual studio bug 209229, offsetof does not return constant expressions +// when used with array elements (e.g. PICA_REG_INDEX(vs_uniform_setup.set_value[1])). +// For details cf. +// https://connect.microsoft.com/VisualStudio/feedback/details/209229/offsetof-does-not-produce-a-constant-expression-for-array-members +// Hopefully, this will be fixed sometime in the future. +// For lack of better alternatives, we currently hardcode the offsets when constant +// expressions are needed via PICA_REG_INDEX_WORKAROUND (on sane compilers, static_asserts +// will then make sure the offsets indeed match the automatically calculated ones). +#define PICA_REG_INDEX(field_name) (offsetof(Pica::Regs, field_name) / sizeof(u32)) +#if defined(_MSC_VER) +#define PICA_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) (backup_workaround_index) +#else +// NOTE: Yeah, hacking in a static_assert here just to workaround the lacking MSVC compiler +// really is this annoying. This macro just forwards its first argument to PICA_REG_INDEX +// and then performs a (no-op) cast to size_t iff the second argument matches the expected +// field offset. Otherwise, the compiler will fail to compile this code. +#define PICA_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) \ + ((typename std::enable_if<backup_workaround_index == PICA_REG_INDEX(field_name), \ + size_t>::type)PICA_REG_INDEX(field_name)) +#endif // _MSC_VER + +struct Regs { + static constexpr size_t NUM_REGS = 0x300; + + union { + struct { + INSERT_PADDING_WORDS(0x10); + u32 trigger_irq; + INSERT_PADDING_WORDS(0x2f); + RasterizerRegs rasterizer; + TexturingRegs texturing; + FramebufferRegs framebuffer; + LightingRegs lighting; + PipelineRegs pipeline; + ShaderRegs gs; + ShaderRegs vs; + INSERT_PADDING_WORDS(0x20); + }; + std::array<u32, NUM_REGS> reg_array; + }; + + /// Map register indices to names readable by humans + static const char* GetRegisterName(u16 index); +}; + +static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32), "Regs struct has wrong size"); + +// TODO: MSVC does not support using offsetof() on non-static data members even though this +// is technically allowed since C++11. This macro should be enabled once MSVC adds +// support for that. +#ifndef _MSC_VER +#define ASSERT_REG_POSITION(field_name, position) \ + static_assert(offsetof(Regs, field_name) == position * 4, \ + "Field " #field_name " has invalid position") + +ASSERT_REG_POSITION(trigger_irq, 0x10); + +ASSERT_REG_POSITION(rasterizer, 0x40); +ASSERT_REG_POSITION(rasterizer.cull_mode, 0x40); +ASSERT_REG_POSITION(rasterizer.viewport_size_x, 0x41); +ASSERT_REG_POSITION(rasterizer.viewport_size_y, 0x43); +ASSERT_REG_POSITION(rasterizer.viewport_depth_range, 0x4d); +ASSERT_REG_POSITION(rasterizer.viewport_depth_near_plane, 0x4e); +ASSERT_REG_POSITION(rasterizer.vs_output_attributes[0], 0x50); +ASSERT_REG_POSITION(rasterizer.vs_output_attributes[1], 0x51); +ASSERT_REG_POSITION(rasterizer.scissor_test, 0x65); +ASSERT_REG_POSITION(rasterizer.viewport_corner, 0x68); +ASSERT_REG_POSITION(rasterizer.depthmap_enable, 0x6D); + +ASSERT_REG_POSITION(texturing, 0x80); +ASSERT_REG_POSITION(texturing.texture0_enable, 0x80); +ASSERT_REG_POSITION(texturing.texture0, 0x81); +ASSERT_REG_POSITION(texturing.texture0_format, 0x8e); +ASSERT_REG_POSITION(texturing.fragment_lighting_enable, 0x8f); +ASSERT_REG_POSITION(texturing.texture1, 0x91); +ASSERT_REG_POSITION(texturing.texture1_format, 0x96); +ASSERT_REG_POSITION(texturing.texture2, 0x99); +ASSERT_REG_POSITION(texturing.texture2_format, 0x9e); +ASSERT_REG_POSITION(texturing.tev_stage0, 0xc0); +ASSERT_REG_POSITION(texturing.tev_stage1, 0xc8); +ASSERT_REG_POSITION(texturing.tev_stage2, 0xd0); +ASSERT_REG_POSITION(texturing.tev_stage3, 0xd8); +ASSERT_REG_POSITION(texturing.tev_combiner_buffer_input, 0xe0); +ASSERT_REG_POSITION(texturing.fog_mode, 0xe0); +ASSERT_REG_POSITION(texturing.fog_color, 0xe1); +ASSERT_REG_POSITION(texturing.fog_lut_offset, 0xe6); +ASSERT_REG_POSITION(texturing.fog_lut_data, 0xe8); +ASSERT_REG_POSITION(texturing.tev_stage4, 0xf0); +ASSERT_REG_POSITION(texturing.tev_stage5, 0xf8); +ASSERT_REG_POSITION(texturing.tev_combiner_buffer_color, 0xfd); + +ASSERT_REG_POSITION(framebuffer, 0x100); +ASSERT_REG_POSITION(framebuffer.output_merger, 0x100); +ASSERT_REG_POSITION(framebuffer.framebuffer, 0x110); + +ASSERT_REG_POSITION(lighting, 0x140); + +ASSERT_REG_POSITION(pipeline, 0x200); +ASSERT_REG_POSITION(pipeline.vertex_attributes, 0x200); +ASSERT_REG_POSITION(pipeline.index_array, 0x227); +ASSERT_REG_POSITION(pipeline.num_vertices, 0x228); +ASSERT_REG_POSITION(pipeline.vertex_offset, 0x22a); +ASSERT_REG_POSITION(pipeline.trigger_draw, 0x22e); +ASSERT_REG_POSITION(pipeline.trigger_draw_indexed, 0x22f); +ASSERT_REG_POSITION(pipeline.vs_default_attributes_setup, 0x232); +ASSERT_REG_POSITION(pipeline.command_buffer, 0x238); +ASSERT_REG_POSITION(pipeline.gpu_mode, 0x245); +ASSERT_REG_POSITION(pipeline.triangle_topology, 0x25e); +ASSERT_REG_POSITION(pipeline.restart_primitive, 0x25f); + +ASSERT_REG_POSITION(gs, 0x280); +ASSERT_REG_POSITION(vs, 0x2b0); + +#undef ASSERT_REG_POSITION +#endif // !defined(_MSC_VER) + +} // namespace Pica diff --git a/src/video_core/regs_framebuffer.h b/src/video_core/regs_framebuffer.h new file mode 100644 index 000000000..366782080 --- /dev/null +++ b/src/video_core/regs_framebuffer.h @@ -0,0 +1,284 @@ +// 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/assert.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/logging/log.h" + +namespace Pica { + +struct FramebufferRegs { + enum class LogicOp : u32 { + Clear = 0, + And = 1, + AndReverse = 2, + Copy = 3, + Set = 4, + CopyInverted = 5, + NoOp = 6, + Invert = 7, + Nand = 8, + Or = 9, + Nor = 10, + Xor = 11, + Equiv = 12, + AndInverted = 13, + OrReverse = 14, + OrInverted = 15, + }; + + enum class BlendEquation : u32 { + Add = 0, + Subtract = 1, + ReverseSubtract = 2, + Min = 3, + Max = 4, + }; + + enum class BlendFactor : u32 { + Zero = 0, + One = 1, + SourceColor = 2, + OneMinusSourceColor = 3, + DestColor = 4, + OneMinusDestColor = 5, + SourceAlpha = 6, + OneMinusSourceAlpha = 7, + DestAlpha = 8, + OneMinusDestAlpha = 9, + ConstantColor = 10, + OneMinusConstantColor = 11, + ConstantAlpha = 12, + OneMinusConstantAlpha = 13, + SourceAlphaSaturate = 14, + }; + + enum class CompareFunc : u32 { + Never = 0, + Always = 1, + Equal = 2, + NotEqual = 3, + LessThan = 4, + LessThanOrEqual = 5, + GreaterThan = 6, + GreaterThanOrEqual = 7, + }; + + enum class StencilAction : u32 { + Keep = 0, + Zero = 1, + Replace = 2, + Increment = 3, + Decrement = 4, + Invert = 5, + IncrementWrap = 6, + DecrementWrap = 7, + }; + + struct { + union { + // If false, logic blending is used + BitField<8, 1, u32> alphablend_enable; + }; + + union { + BitField<0, 8, BlendEquation> blend_equation_rgb; + BitField<8, 8, BlendEquation> blend_equation_a; + + BitField<16, 4, BlendFactor> factor_source_rgb; + BitField<20, 4, BlendFactor> factor_dest_rgb; + + BitField<24, 4, BlendFactor> factor_source_a; + BitField<28, 4, BlendFactor> factor_dest_a; + } alpha_blending; + + union { + BitField<0, 4, LogicOp> logic_op; + }; + + union { + u32 raw; + BitField<0, 8, u32> r; + BitField<8, 8, u32> g; + BitField<16, 8, u32> b; + BitField<24, 8, u32> a; + } blend_const; + + union { + BitField<0, 1, u32> enable; + BitField<4, 3, CompareFunc> func; + BitField<8, 8, u32> ref; + } alpha_test; + + struct { + union { + // Raw value of this register + u32 raw_func; + + // If true, enable stencil testing + BitField<0, 1, u32> enable; + + // Comparison operation for stencil testing + BitField<4, 3, CompareFunc> func; + + // Mask used to control writing to the stencil buffer + BitField<8, 8, u32> write_mask; + + // Value to compare against for stencil testing + BitField<16, 8, u32> reference_value; + + // Mask to apply on stencil test inputs + BitField<24, 8, u32> input_mask; + }; + + union { + // Raw value of this register + u32 raw_op; + + // Action to perform when the stencil test fails + BitField<0, 3, StencilAction> action_stencil_fail; + + // Action to perform when stencil testing passed but depth testing fails + BitField<4, 3, StencilAction> action_depth_fail; + + // Action to perform when both stencil and depth testing pass + BitField<8, 3, StencilAction> action_depth_pass; + }; + } stencil_test; + + union { + BitField<0, 1, u32> depth_test_enable; + BitField<4, 3, CompareFunc> depth_test_func; + BitField<8, 1, u32> red_enable; + BitField<9, 1, u32> green_enable; + BitField<10, 1, u32> blue_enable; + BitField<11, 1, u32> alpha_enable; + BitField<12, 1, u32> depth_write_enable; + }; + + INSERT_PADDING_WORDS(0x8); + } output_merger; + + // Components are laid out in reverse byte order, most significant bits first. + enum class ColorFormat : u32 { + RGBA8 = 0, + RGB8 = 1, + RGB5A1 = 2, + RGB565 = 3, + RGBA4 = 4, + }; + + enum class DepthFormat : u32 { + D16 = 0, + D24 = 2, + D24S8 = 3, + }; + + // Returns the number of bytes in the specified color format + static unsigned BytesPerColorPixel(ColorFormat format) { + switch (format) { + case ColorFormat::RGBA8: + return 4; + case ColorFormat::RGB8: + return 3; + case ColorFormat::RGB5A1: + case ColorFormat::RGB565: + case ColorFormat::RGBA4: + return 2; + default: + LOG_CRITICAL(HW_GPU, "Unknown color format %u", format); + UNIMPLEMENTED(); + } + } + + struct FramebufferConfig { + INSERT_PADDING_WORDS(0x3); + + union { + BitField<0, 4, u32> allow_color_write; // 0 = disable, else enable + }; + + INSERT_PADDING_WORDS(0x1); + + union { + BitField<0, 2, u32> allow_depth_stencil_write; // 0 = disable, else enable + }; + + DepthFormat depth_format; // TODO: Should be a BitField! + BitField<16, 3, ColorFormat> color_format; + + INSERT_PADDING_WORDS(0x4); + + u32 depth_buffer_address; + u32 color_buffer_address; + + union { + // Apparently, the framebuffer width is stored as expected, + // while the height is stored as the actual height minus one. + // Hence, don't access these fields directly but use the accessors + // GetWidth() and GetHeight() instead. + BitField<0, 11, u32> width; + BitField<12, 10, u32> height; + }; + + INSERT_PADDING_WORDS(0x1); + + inline PAddr GetColorBufferPhysicalAddress() const { + return color_buffer_address * 8; + } + inline PAddr GetDepthBufferPhysicalAddress() const { + return depth_buffer_address * 8; + } + + inline u32 GetWidth() const { + return width; + } + + inline u32 GetHeight() const { + return height + 1; + } + } framebuffer; + + // Returns the number of bytes in the specified depth format + static u32 BytesPerDepthPixel(DepthFormat format) { + switch (format) { + case DepthFormat::D16: + return 2; + case DepthFormat::D24: + return 3; + case DepthFormat::D24S8: + return 4; + default: + LOG_CRITICAL(HW_GPU, "Unknown depth format %u", format); + UNIMPLEMENTED(); + } + } + + // Returns the number of bits per depth component of the specified depth format + static u32 DepthBitsPerPixel(DepthFormat format) { + switch (format) { + case DepthFormat::D16: + return 16; + case DepthFormat::D24: + case DepthFormat::D24S8: + return 24; + default: + LOG_CRITICAL(HW_GPU, "Unknown depth format %u", format); + UNIMPLEMENTED(); + } + } + + INSERT_PADDING_WORDS(0x20); +}; + +static_assert(sizeof(FramebufferRegs) == 0x40 * sizeof(u32), + "FramebufferRegs struct has incorrect size"); + +} // namespace Pica diff --git a/src/video_core/regs_lighting.h b/src/video_core/regs_lighting.h new file mode 100644 index 000000000..6793405d9 --- /dev/null +++ b/src/video_core/regs_lighting.h @@ -0,0 +1,294 @@ +// 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/assert.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/vector_math.h" + +namespace Pica { + +struct LightingRegs { + enum class LightingSampler { + Distribution0 = 0, + Distribution1 = 1, + Fresnel = 3, + ReflectBlue = 4, + ReflectGreen = 5, + ReflectRed = 6, + SpotlightAttenuation = 8, + DistanceAttenuation = 16, + }; + + /** + * Pica fragment lighting supports using different LUTs for each lighting component: Reflectance + * R, G, and B channels, distribution function for specular components 0 and 1, fresnel factor, + * and spotlight attenuation. Furthermore, which LUTs are used for each channel (or whether a + * channel is enabled at all) is specified by various pre-defined lighting configurations. With + * configurations that require more LUTs, more cycles are required on HW to perform lighting + * computations. + */ + enum class LightingConfig : u32 { + Config0 = 0, ///< Reflect Red, Distribution 0, Spotlight + Config1 = 1, ///< Reflect Red, Fresnel, Spotlight + Config2 = 2, ///< Reflect Red, Distribution 0/1 + Config3 = 3, ///< Distribution 0/1, Fresnel + Config4 = 4, ///< Reflect Red/Green/Blue, Distribution 0/1, Spotlight + Config5 = 5, ///< Reflect Red/Green/Blue, Distribution 0, Fresnel, Spotlight + Config6 = 6, ///< Reflect Red, Distribution 0/1, Fresnel, Spotlight + + Config7 = 8, ///< Reflect Red/Green/Blue, Distribution 0/1, Fresnel, Spotlight + ///< NOTE: '8' is intentional, '7' does not appear to be a valid configuration + }; + + /// Selects which lighting components are affected by fresnel + enum class LightingFresnelSelector : u32 { + None = 0, ///< Fresnel is disabled + PrimaryAlpha = 1, ///< Primary (diffuse) lighting alpha is affected by fresnel + SecondaryAlpha = 2, ///< Secondary (specular) lighting alpha is affected by fresnel + Both = + PrimaryAlpha | + SecondaryAlpha, ///< Both primary and secondary lighting alphas are affected by fresnel + }; + + /// Factor used to scale the output of a lighting LUT + enum class LightingScale : u32 { + Scale1 = 0, ///< Scale is 1x + Scale2 = 1, ///< Scale is 2x + Scale4 = 2, ///< Scale is 4x + Scale8 = 3, ///< Scale is 8x + + Scale1_4 = 6, ///< Scale is 0.25x + Scale1_2 = 7, ///< Scale is 0.5x + }; + + enum class LightingLutInput : u32 { + NH = 0, // Cosine of the angle between the normal and half-angle vectors + VH = 1, // Cosine of the angle between the view and half-angle vectors + NV = 2, // Cosine of the angle between the normal and the view vector + LN = 3, // Cosine of the angle between the light and the normal vectors + }; + + enum class LightingBumpMode : u32 { + None = 0, + NormalMap = 1, + TangentMap = 2, + }; + + union LightColor { + BitField<0, 10, u32> b; + BitField<10, 10, u32> g; + BitField<20, 10, u32> r; + + Math::Vec3f ToVec3f() const { + // These fields are 10 bits wide, however 255 corresponds to 1.0f for each color + // component + return Math::MakeVec((f32)r / 255.f, (f32)g / 255.f, (f32)b / 255.f); + } + }; + + /// Returns true if the specified lighting sampler is supported by the current Pica lighting + /// configuration + static bool IsLightingSamplerSupported(LightingConfig config, LightingSampler sampler) { + switch (sampler) { + case LightingSampler::Distribution0: + return (config != LightingConfig::Config1); + + case LightingSampler::Distribution1: + return (config != LightingConfig::Config0) && (config != LightingConfig::Config1) && + (config != LightingConfig::Config5); + + case LightingSampler::Fresnel: + return (config != LightingConfig::Config0) && (config != LightingConfig::Config2) && + (config != LightingConfig::Config4); + + case LightingSampler::ReflectRed: + return (config != LightingConfig::Config3); + + case LightingSampler::ReflectGreen: + case LightingSampler::ReflectBlue: + return (config == LightingConfig::Config4) || (config == LightingConfig::Config5) || + (config == LightingConfig::Config7); + default: + UNREACHABLE_MSG("Regs::IsLightingSamplerSupported: Reached " + "unreachable section, sampler should be one " + "of Distribution0, Distribution1, Fresnel, " + "ReflectRed, ReflectGreen or ReflectBlue, instead " + "got %i", + static_cast<int>(config)); + } + } + + struct LightSrc { + LightColor specular_0; // material.specular_0 * light.specular_0 + LightColor specular_1; // material.specular_1 * light.specular_1 + LightColor diffuse; // material.diffuse * light.diffuse + LightColor ambient; // material.ambient * light.ambient + + // Encoded as 16-bit floating point + union { + BitField<0, 16, u32> x; + BitField<16, 16, u32> y; + }; + union { + BitField<0, 16, u32> z; + }; + + INSERT_PADDING_WORDS(0x3); + + union { + BitField<0, 1, u32> directional; + BitField<1, 1, u32> two_sided_diffuse; // When disabled, clamp dot-product to 0 + } config; + + BitField<0, 20, u32> dist_atten_bias; + BitField<0, 20, u32> dist_atten_scale; + + INSERT_PADDING_WORDS(0x4); + }; + static_assert(sizeof(LightSrc) == 0x10 * sizeof(u32), "LightSrc structure must be 0x10 words"); + + LightSrc light[8]; + LightColor global_ambient; // Emission + (material.ambient * lighting.ambient) + INSERT_PADDING_WORDS(0x1); + BitField<0, 3, u32> max_light_index; // Number of enabled lights - 1 + + union { + BitField<2, 2, LightingFresnelSelector> fresnel_selector; + BitField<4, 4, LightingConfig> config; + BitField<22, 2, u32> bump_selector; // 0: Texture 0, 1: Texture 1, 2: Texture 2 + BitField<27, 1, u32> clamp_highlights; + BitField<28, 2, LightingBumpMode> bump_mode; + BitField<30, 1, u32> disable_bump_renorm; + } config0; + + union { + BitField<16, 1, u32> disable_lut_d0; + BitField<17, 1, u32> disable_lut_d1; + BitField<19, 1, u32> disable_lut_fr; + BitField<20, 1, u32> disable_lut_rr; + BitField<21, 1, u32> disable_lut_rg; + BitField<22, 1, u32> disable_lut_rb; + + // Each bit specifies whether distance attenuation should be applied for the corresponding + // light. + BitField<24, 1, u32> disable_dist_atten_light_0; + BitField<25, 1, u32> disable_dist_atten_light_1; + BitField<26, 1, u32> disable_dist_atten_light_2; + BitField<27, 1, u32> disable_dist_atten_light_3; + BitField<28, 1, u32> disable_dist_atten_light_4; + BitField<29, 1, u32> disable_dist_atten_light_5; + BitField<30, 1, u32> disable_dist_atten_light_6; + BitField<31, 1, u32> disable_dist_atten_light_7; + } config1; + + bool IsDistAttenDisabled(unsigned index) const { + const unsigned disable[] = { + config1.disable_dist_atten_light_0, config1.disable_dist_atten_light_1, + config1.disable_dist_atten_light_2, config1.disable_dist_atten_light_3, + config1.disable_dist_atten_light_4, config1.disable_dist_atten_light_5, + config1.disable_dist_atten_light_6, config1.disable_dist_atten_light_7}; + return disable[index] != 0; + } + + union { + BitField<0, 8, u32> index; ///< Index at which to set data in the LUT + BitField<8, 5, u32> type; ///< Type of LUT for which to set data + } lut_config; + + BitField<0, 1, u32> disable; + INSERT_PADDING_WORDS(0x1); + + // When data is written to any of these registers, it gets written to the lookup table of the + // selected type at the selected index, specified above in the `lut_config` register. With each + // write, `lut_config.index` is incremented. It does not matter which of these registers is + // written to, the behavior will be the same. + u32 lut_data[8]; + + // These are used to specify if absolute (abs) value should be used for each LUT index. When + // abs mode is disabled, LUT indexes are in the range of (-1.0, 1.0). Otherwise, they are in + // the range of (0.0, 1.0). + union { + BitField<1, 1, u32> disable_d0; + BitField<5, 1, u32> disable_d1; + BitField<9, 1, u32> disable_sp; + BitField<13, 1, u32> disable_fr; + BitField<17, 1, u32> disable_rb; + BitField<21, 1, u32> disable_rg; + BitField<25, 1, u32> disable_rr; + } abs_lut_input; + + union { + BitField<0, 3, LightingLutInput> d0; + BitField<4, 3, LightingLutInput> d1; + BitField<8, 3, LightingLutInput> sp; + BitField<12, 3, LightingLutInput> fr; + BitField<16, 3, LightingLutInput> rb; + BitField<20, 3, LightingLutInput> rg; + BitField<24, 3, LightingLutInput> rr; + } lut_input; + + union { + BitField<0, 3, LightingScale> d0; + BitField<4, 3, LightingScale> d1; + BitField<8, 3, LightingScale> sp; + BitField<12, 3, LightingScale> fr; + BitField<16, 3, LightingScale> rb; + BitField<20, 3, LightingScale> rg; + BitField<24, 3, LightingScale> rr; + + static float GetScale(LightingScale scale) { + switch (scale) { + case LightingScale::Scale1: + return 1.0f; + case LightingScale::Scale2: + return 2.0f; + case LightingScale::Scale4: + return 4.0f; + case LightingScale::Scale8: + return 8.0f; + case LightingScale::Scale1_4: + return 0.25f; + case LightingScale::Scale1_2: + return 0.5f; + } + return 0.0f; + } + } lut_scale; + + INSERT_PADDING_WORDS(0x6); + + union { + // There are 8 light enable "slots", corresponding to the total number of lights supported + // by Pica. For N enabled lights (specified by register 0x1c2, or 'src_num' above), the + // first N slots below will be set to integers within the range of 0-7, corresponding to the + // actual light that is enabled for each slot. + + BitField<0, 3, u32> slot_0; + BitField<4, 3, u32> slot_1; + BitField<8, 3, u32> slot_2; + BitField<12, 3, u32> slot_3; + BitField<16, 3, u32> slot_4; + BitField<20, 3, u32> slot_5; + BitField<24, 3, u32> slot_6; + BitField<28, 3, u32> slot_7; + + unsigned GetNum(unsigned index) const { + const unsigned enable_slots[] = {slot_0, slot_1, slot_2, slot_3, + slot_4, slot_5, slot_6, slot_7}; + return enable_slots[index]; + } + } light_enable; + + INSERT_PADDING_WORDS(0x26); +}; + +static_assert(sizeof(LightingRegs) == 0xC0 * sizeof(u32), "LightingRegs struct has incorrect size"); + +} // namespace Pica diff --git a/src/video_core/regs_pipeline.h b/src/video_core/regs_pipeline.h new file mode 100644 index 000000000..0a4ec6e1e --- /dev/null +++ b/src/video_core/regs_pipeline.h @@ -0,0 +1,230 @@ +// 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/assert.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Pica { + +struct PipelineRegs { + enum class VertexAttributeFormat : u32 { + BYTE = 0, + UBYTE = 1, + SHORT = 2, + FLOAT = 3, + }; + + struct { + BitField<0, 29, u32> base_address; + + PAddr GetPhysicalBaseAddress() const { + return base_address * 8; + } + + // Descriptor for internal vertex attributes + union { + BitField<0, 2, VertexAttributeFormat> format0; // size of one element + BitField<2, 2, u32> size0; // number of elements minus 1 + BitField<4, 2, VertexAttributeFormat> format1; + BitField<6, 2, u32> size1; + BitField<8, 2, VertexAttributeFormat> format2; + BitField<10, 2, u32> size2; + BitField<12, 2, VertexAttributeFormat> format3; + BitField<14, 2, u32> size3; + BitField<16, 2, VertexAttributeFormat> format4; + BitField<18, 2, u32> size4; + BitField<20, 2, VertexAttributeFormat> format5; + BitField<22, 2, u32> size5; + BitField<24, 2, VertexAttributeFormat> format6; + BitField<26, 2, u32> size6; + BitField<28, 2, VertexAttributeFormat> format7; + BitField<30, 2, u32> size7; + }; + + union { + BitField<0, 2, VertexAttributeFormat> format8; + BitField<2, 2, u32> size8; + BitField<4, 2, VertexAttributeFormat> format9; + BitField<6, 2, u32> size9; + BitField<8, 2, VertexAttributeFormat> format10; + BitField<10, 2, u32> size10; + BitField<12, 2, VertexAttributeFormat> format11; + BitField<14, 2, u32> size11; + + BitField<16, 12, u32> attribute_mask; + + // number of total attributes minus 1 + BitField<28, 4, u32> max_attribute_index; + }; + + inline VertexAttributeFormat GetFormat(int n) const { + VertexAttributeFormat formats[] = {format0, format1, format2, format3, + format4, format5, format6, format7, + format8, format9, format10, format11}; + return formats[n]; + } + + inline int GetNumElements(int n) const { + u32 sizes[] = {size0, size1, size2, size3, size4, size5, + size6, size7, size8, size9, size10, size11}; + return (int)sizes[n] + 1; + } + + inline int GetElementSizeInBytes(int n) const { + return (GetFormat(n) == VertexAttributeFormat::FLOAT) + ? 4 + : (GetFormat(n) == VertexAttributeFormat::SHORT) ? 2 : 1; + } + + inline int GetStride(int n) const { + return GetNumElements(n) * GetElementSizeInBytes(n); + } + + inline bool IsDefaultAttribute(int id) const { + return (id >= 12) || (attribute_mask & (1ULL << id)) != 0; + } + + inline int GetNumTotalAttributes() const { + return (int)max_attribute_index + 1; + } + + // Attribute loaders map the source vertex data to input attributes + // This e.g. allows to load different attributes from different memory locations + struct { + // Source attribute data offset from the base address + u32 data_offset; + + union { + BitField<0, 4, u32> comp0; + BitField<4, 4, u32> comp1; + BitField<8, 4, u32> comp2; + BitField<12, 4, u32> comp3; + BitField<16, 4, u32> comp4; + BitField<20, 4, u32> comp5; + BitField<24, 4, u32> comp6; + BitField<28, 4, u32> comp7; + }; + + union { + BitField<0, 4, u32> comp8; + BitField<4, 4, u32> comp9; + BitField<8, 4, u32> comp10; + BitField<12, 4, u32> comp11; + + // bytes for a single vertex in this loader + BitField<16, 8, u32> byte_count; + + BitField<28, 4, u32> component_count; + }; + + inline int GetComponent(int n) const { + u32 components[] = {comp0, comp1, comp2, comp3, comp4, comp5, + comp6, comp7, comp8, comp9, comp10, comp11}; + return (int)components[n]; + } + } attribute_loaders[12]; + } vertex_attributes; + + struct { + enum IndexFormat : u32 { + BYTE = 0, + SHORT = 1, + }; + + union { + BitField<0, 31, u32> offset; // relative to base attribute address + BitField<31, 1, IndexFormat> format; + }; + } index_array; + + // Number of vertices to render + u32 num_vertices; + + INSERT_PADDING_WORDS(0x1); + + // The index of the first vertex to render + u32 vertex_offset; + + INSERT_PADDING_WORDS(0x3); + + // These two trigger rendering of triangles + u32 trigger_draw; + u32 trigger_draw_indexed; + + INSERT_PADDING_WORDS(0x2); + + // These registers are used to setup the default "fall-back" vertex shader attributes + struct { + // Index of the current default attribute + u32 index; + + // Writing to these registers sets the "current" default attribute. + u32 set_value[3]; + } vs_default_attributes_setup; + + INSERT_PADDING_WORDS(0x2); + + struct { + // There are two channels that can be used to configure the next command buffer, which can + // be then executed by writing to the "trigger" registers. There are two reasons why a game + // might use this feature: + // 1) With this, an arbitrary number of additional command buffers may be executed in + // sequence without requiring any intervention of the CPU after the initial one is + // kicked off. + // 2) Games can configure these registers to provide a command list subroutine mechanism. + + BitField<0, 20, u32> size[2]; ///< Size (in bytes / 8) of each channel's command buffer + BitField<0, 28, u32> addr[2]; ///< Physical address / 8 of each channel's command buffer + u32 trigger[2]; ///< Triggers execution of the channel's command buffer when written to + + unsigned GetSize(unsigned index) const { + ASSERT(index < 2); + return 8 * size[index]; + } + + PAddr GetPhysicalAddress(unsigned index) const { + ASSERT(index < 2); + return (PAddr)(8 * addr[index]); + } + } command_buffer; + + 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, + Configuring = 1, + }; + + GPUMode gpu_mode; + + INSERT_PADDING_WORDS(0x18); + + enum class TriangleTopology : u32 { + List = 0, + Strip = 1, + Fan = 2, + Shader = 3, // Programmable setup unit implemented in a geometry shader + }; + + BitField<8, 2, TriangleTopology> triangle_topology; + + u32 restart_primitive; + + INSERT_PADDING_WORDS(0x20); +}; + +static_assert(sizeof(PipelineRegs) == 0x80 * sizeof(u32), "PipelineRegs struct has incorrect size"); + +} // namespace Pica diff --git a/src/video_core/regs_rasterizer.h b/src/video_core/regs_rasterizer.h new file mode 100644 index 000000000..a471a3b38 --- /dev/null +++ b/src/video_core/regs_rasterizer.h @@ -0,0 +1,129 @@ +// 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/common_funcs.h" +#include "common/common_types.h" + +namespace Pica { + +struct RasterizerRegs { + enum class CullMode : u32 { + // Select which polygons are considered to be "frontfacing". + KeepAll = 0, + KeepClockWise = 1, + KeepCounterClockWise = 2, + // TODO: What does the third value imply? + }; + + union { + BitField<0, 2, CullMode> cull_mode; + }; + + BitField<0, 24, u32> viewport_size_x; + + INSERT_PADDING_WORDS(0x1); + + BitField<0, 24, u32> viewport_size_y; + + INSERT_PADDING_WORDS(0x9); + + BitField<0, 24, u32> viewport_depth_range; // float24 + BitField<0, 24, u32> viewport_depth_near_plane; // float24 + + BitField<0, 3, u32> vs_output_total; + + union VSOutputAttributes { + // Maps components of output vertex attributes to semantics + enum Semantic : u32 { + POSITION_X = 0, + POSITION_Y = 1, + POSITION_Z = 2, + POSITION_W = 3, + + QUATERNION_X = 4, + QUATERNION_Y = 5, + QUATERNION_Z = 6, + QUATERNION_W = 7, + + COLOR_R = 8, + COLOR_G = 9, + COLOR_B = 10, + COLOR_A = 11, + + TEXCOORD0_U = 12, + TEXCOORD0_V = 13, + TEXCOORD1_U = 14, + TEXCOORD1_V = 15, + + TEXCOORD0_W = 16, + + VIEW_X = 18, + VIEW_Y = 19, + VIEW_Z = 20, + + TEXCOORD2_U = 22, + TEXCOORD2_V = 23, + + INVALID = 31, + }; + + BitField<0, 5, Semantic> map_x; + BitField<8, 5, Semantic> map_y; + BitField<16, 5, Semantic> map_z; + BitField<24, 5, Semantic> map_w; + } vs_output_attributes[7]; + + INSERT_PADDING_WORDS(0xe); + + enum class ScissorMode : u32 { + Disabled = 0, + Exclude = 1, // Exclude pixels inside the scissor box + + Include = 3 // Exclude pixels outside the scissor box + }; + + struct { + BitField<0, 2, ScissorMode> mode; + + union { + BitField<0, 16, u32> x1; + BitField<16, 16, u32> y1; + }; + + union { + BitField<0, 16, u32> x2; + BitField<16, 16, u32> y2; + }; + } scissor_test; + + union { + BitField<0, 10, s32> x; + BitField<16, 10, s32> y; + } viewport_corner; + + INSERT_PADDING_WORDS(0x1); + + // TODO: early depth + INSERT_PADDING_WORDS(0x1); + + INSERT_PADDING_WORDS(0x2); + + enum DepthBuffering : u32 { + WBuffering = 0, + ZBuffering = 1, + }; + BitField<0, 1, DepthBuffering> depthmap_enable; + + INSERT_PADDING_WORDS(0x12); +}; + +static_assert(sizeof(RasterizerRegs) == 0x40 * sizeof(u32), + "RasterizerRegs struct has incorrect size"); + +} // namespace Pica diff --git a/src/video_core/regs_shader.h b/src/video_core/regs_shader.h new file mode 100644 index 000000000..ddb1ee451 --- /dev/null +++ b/src/video_core/regs_shader.h @@ -0,0 +1,104 @@ +// 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/common_funcs.h" +#include "common/common_types.h" + +namespace Pica { + +struct ShaderRegs { + BitField<0, 16, u32> bool_uniforms; + + union { + BitField<0, 8, u32> x; + BitField<8, 8, u32> y; + BitField<16, 8, u32> z; + BitField<24, 8, u32> w; + } int_uniforms[4]; + + INSERT_PADDING_WORDS(0x4); + + union { + // Number of input attributes to shader unit - 1 + BitField<0, 4, u32> max_input_attribute_index; + }; + + // Offset to shader program entry point (in words) + BitField<0, 16, u32> main_offset; + + /// 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; + + // 0x28E, CODETRANSFER_END + INSERT_PADDING_WORDS(0x2); + + struct { + enum Format : u32 { + FLOAT24 = 0, + FLOAT32 = 1, + }; + + bool IsFloat32() const { + return format == FLOAT32; + } + + union { + // Index of the next uniform to write to + // TODO: ctrulib uses 8 bits for this, however that seems to yield lots of invalid + // indices + // TODO: Maybe the uppermost index is for the geometry shader? Investigate! + BitField<0, 7, u32> index; + + BitField<31, 1, Format> format; + }; + + // Writing to these registers sets the current uniform. + u32 set_value[8]; + + } uniform_setup; + + INSERT_PADDING_WORDS(0x2); + + struct { + // Offset of the next instruction to write code to. + // Incremented with each instruction write. + u32 offset; + + // Writing to these registers sets the "current" word in the shader program. + u32 set_word[8]; + } program; + + INSERT_PADDING_WORDS(0x1); + + // This register group is used to load an internal table of swizzling patterns, + // which are indexed by each shader instruction to specify vector component swizzling. + struct { + // Offset of the next swizzle pattern to write code to. + // Incremented with each instruction write. + u32 offset; + + // Writing to these registers sets the current swizzle pattern in the table. + u32 set_word[8]; + } swizzle_patterns; + + INSERT_PADDING_WORDS(0x2); +}; + +static_assert(sizeof(ShaderRegs) == 0x30 * sizeof(u32), "ShaderRegs struct has incorrect size"); + +} // namespace Pica diff --git a/src/video_core/regs_texturing.h b/src/video_core/regs_texturing.h new file mode 100644 index 000000000..be8bc6826 --- /dev/null +++ b/src/video_core/regs_texturing.h @@ -0,0 +1,328 @@ +// 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/assert.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Pica { + +struct TexturingRegs { + struct TextureConfig { + enum TextureType : u32 { + Texture2D = 0, + TextureCube = 1, + Shadow2D = 2, + Projection2D = 3, + ShadowCube = 4, + Disabled = 5, + }; + + enum WrapMode : u32 { + ClampToEdge = 0, + ClampToBorder = 1, + Repeat = 2, + MirroredRepeat = 3, + }; + + enum TextureFilter : u32 { + Nearest = 0, + Linear = 1, + }; + + union { + u32 raw; + BitField<0, 8, u32> r; + BitField<8, 8, u32> g; + BitField<16, 8, u32> b; + BitField<24, 8, u32> a; + } border_color; + + union { + BitField<0, 16, u32> height; + BitField<16, 16, u32> width; + }; + + union { + BitField<1, 1, TextureFilter> mag_filter; + BitField<2, 1, TextureFilter> min_filter; + BitField<8, 2, WrapMode> wrap_t; + BitField<12, 2, WrapMode> wrap_s; + BitField<28, 2, TextureType> + type; ///< @note Only valid for texture 0 according to 3DBrew. + }; + + INSERT_PADDING_WORDS(0x1); + + u32 address; + + PAddr GetPhysicalAddress() const { + return address * 8; + } + + // texture1 and texture2 store the texture format directly after the address + // whereas texture0 inserts some additional flags inbetween. + // Hence, we store the format separately so that all other parameters can be described + // in a single structure. + }; + + enum class TextureFormat : u32 { + RGBA8 = 0, + RGB8 = 1, + RGB5A1 = 2, + RGB565 = 3, + RGBA4 = 4, + IA8 = 5, + RG8 = 6, ///< @note Also called HILO8 in 3DBrew. + I8 = 7, + A8 = 8, + IA4 = 9, + I4 = 10, + A4 = 11, + ETC1 = 12, // compressed + ETC1A4 = 13, // compressed + }; + + static unsigned NibblesPerPixel(TextureFormat format) { + switch (format) { + case TextureFormat::RGBA8: + return 8; + + case TextureFormat::RGB8: + return 6; + + case TextureFormat::RGB5A1: + case TextureFormat::RGB565: + case TextureFormat::RGBA4: + case TextureFormat::IA8: + case TextureFormat::RG8: + return 4; + + case TextureFormat::I4: + case TextureFormat::A4: + return 1; + + case TextureFormat::I8: + case TextureFormat::A8: + case TextureFormat::IA4: + + default: // placeholder for yet unknown formats + UNIMPLEMENTED(); + return 0; + } + } + + union { + BitField<0, 1, u32> texture0_enable; + BitField<1, 1, u32> texture1_enable; + BitField<2, 1, u32> texture2_enable; + }; + TextureConfig texture0; + INSERT_PADDING_WORDS(0x8); + BitField<0, 4, TextureFormat> texture0_format; + BitField<0, 1, u32> fragment_lighting_enable; + INSERT_PADDING_WORDS(0x1); + TextureConfig texture1; + BitField<0, 4, TextureFormat> texture1_format; + INSERT_PADDING_WORDS(0x2); + TextureConfig texture2; + BitField<0, 4, TextureFormat> texture2_format; + INSERT_PADDING_WORDS(0x21); + + struct FullTextureConfig { + const bool enabled; + const TextureConfig config; + const TextureFormat format; + }; + const std::array<FullTextureConfig, 3> GetTextures() const { + return {{ + {texture0_enable.ToBool(), texture0, texture0_format}, + {texture1_enable.ToBool(), texture1, texture1_format}, + {texture2_enable.ToBool(), texture2, texture2_format}, + }}; + } + + // 0xc0-0xff: Texture Combiner (akin to glTexEnv) + struct TevStageConfig { + enum class Source : u32 { + PrimaryColor = 0x0, + PrimaryFragmentColor = 0x1, + SecondaryFragmentColor = 0x2, + + Texture0 = 0x3, + Texture1 = 0x4, + Texture2 = 0x5, + Texture3 = 0x6, + + PreviousBuffer = 0xd, + Constant = 0xe, + Previous = 0xf, + }; + + enum class ColorModifier : u32 { + SourceColor = 0x0, + OneMinusSourceColor = 0x1, + SourceAlpha = 0x2, + OneMinusSourceAlpha = 0x3, + SourceRed = 0x4, + OneMinusSourceRed = 0x5, + + SourceGreen = 0x8, + OneMinusSourceGreen = 0x9, + + SourceBlue = 0xc, + OneMinusSourceBlue = 0xd, + }; + + enum class AlphaModifier : u32 { + SourceAlpha = 0x0, + OneMinusSourceAlpha = 0x1, + SourceRed = 0x2, + OneMinusSourceRed = 0x3, + SourceGreen = 0x4, + OneMinusSourceGreen = 0x5, + SourceBlue = 0x6, + OneMinusSourceBlue = 0x7, + }; + + enum class Operation : u32 { + Replace = 0, + Modulate = 1, + Add = 2, + AddSigned = 3, + Lerp = 4, + Subtract = 5, + Dot3_RGB = 6, + + MultiplyThenAdd = 8, + AddThenMultiply = 9, + }; + + union { + u32 sources_raw; + BitField<0, 4, Source> color_source1; + BitField<4, 4, Source> color_source2; + BitField<8, 4, Source> color_source3; + BitField<16, 4, Source> alpha_source1; + BitField<20, 4, Source> alpha_source2; + BitField<24, 4, Source> alpha_source3; + }; + + union { + u32 modifiers_raw; + BitField<0, 4, ColorModifier> color_modifier1; + BitField<4, 4, ColorModifier> color_modifier2; + BitField<8, 4, ColorModifier> color_modifier3; + BitField<12, 3, AlphaModifier> alpha_modifier1; + BitField<16, 3, AlphaModifier> alpha_modifier2; + BitField<20, 3, AlphaModifier> alpha_modifier3; + }; + + union { + u32 ops_raw; + BitField<0, 4, Operation> color_op; + BitField<16, 4, Operation> alpha_op; + }; + + union { + u32 const_color; + BitField<0, 8, u32> const_r; + BitField<8, 8, u32> const_g; + BitField<16, 8, u32> const_b; + BitField<24, 8, u32> const_a; + }; + + union { + u32 scales_raw; + BitField<0, 2, u32> color_scale; + BitField<16, 2, u32> alpha_scale; + }; + + inline unsigned GetColorMultiplier() const { + return (color_scale < 3) ? (1 << color_scale) : 1; + } + + inline unsigned GetAlphaMultiplier() const { + return (alpha_scale < 3) ? (1 << alpha_scale) : 1; + } + }; + + TevStageConfig tev_stage0; + INSERT_PADDING_WORDS(0x3); + TevStageConfig tev_stage1; + INSERT_PADDING_WORDS(0x3); + TevStageConfig tev_stage2; + INSERT_PADDING_WORDS(0x3); + TevStageConfig tev_stage3; + INSERT_PADDING_WORDS(0x3); + + enum class FogMode : u32 { + None = 0, + Fog = 5, + Gas = 7, + }; + + union { + BitField<0, 3, FogMode> fog_mode; + BitField<16, 1, u32> fog_flip; + + union { + // Tev stages 0-3 write their output to the combiner buffer if the corresponding bit in + // these masks are set + BitField<8, 4, u32> update_mask_rgb; + BitField<12, 4, u32> update_mask_a; + + bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const { + return (stage_index < 4) && (update_mask_rgb & (1 << stage_index)); + } + + bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const { + return (stage_index < 4) && (update_mask_a & (1 << stage_index)); + } + } tev_combiner_buffer_input; + }; + + union { + u32 raw; + BitField<0, 8, u32> r; + BitField<8, 8, u32> g; + BitField<16, 8, u32> b; + } fog_color; + + INSERT_PADDING_WORDS(0x4); + + BitField<0, 16, u32> fog_lut_offset; + + INSERT_PADDING_WORDS(0x1); + + u32 fog_lut_data[8]; + + TevStageConfig tev_stage4; + INSERT_PADDING_WORDS(0x3); + TevStageConfig tev_stage5; + + union { + u32 raw; + BitField<0, 8, u32> r; + BitField<8, 8, u32> g; + BitField<16, 8, u32> b; + BitField<24, 8, u32> a; + } tev_combiner_buffer_color; + + INSERT_PADDING_WORDS(0x2); + + const std::array<TevStageConfig, 6> GetTevStages() const { + return {{tev_stage0, tev_stage1, tev_stage2, tev_stage3, tev_stage4, tev_stage5}}; + }; +}; + +static_assert(sizeof(TexturingRegs) == 0x80 * sizeof(u32), + "TexturingRegs struct has incorrect size"); + +} // namespace Pica diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index fd38175b3..f6ece5c4b 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -6,7 +6,7 @@ #include <memory> #include "video_core/renderer_base.h" #include "video_core/renderer_opengl/gl_rasterizer.h" -#include "video_core/swrasterizer.h" +#include "video_core/swrasterizer/swrasterizer.h" #include "video_core/video_core.h" void RendererBase::RefreshRasterizerSetting() { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 5a306a5c8..de1d5eba7 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -14,8 +14,10 @@ #include "common/microprofile.h" #include "common/vector_math.h" #include "core/hw/gpu.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_texturing.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_gen.h" #include "video_core/renderer_opengl/gl_shader_util.h" @@ -26,16 +28,6 @@ MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192)); MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(100, 100, 255)); MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100)); -static bool IsPassThroughTevStage(const Pica::Regs::TevStageConfig& stage) { - return (stage.color_op == Pica::Regs::TevStageConfig::Operation::Replace && - stage.alpha_op == Pica::Regs::TevStageConfig::Operation::Replace && - stage.color_source1 == Pica::Regs::TevStageConfig::Source::Previous && - stage.alpha_source1 == Pica::Regs::TevStageConfig::Source::Previous && - stage.color_modifier1 == Pica::Regs::TevStageConfig::ColorModifier::SourceColor && - stage.alpha_modifier1 == Pica::Regs::TevStageConfig::AlphaModifier::SourceAlpha && - stage.GetColorMultiplier() == 1 && stage.GetAlphaMultiplier() == 1); -} - RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) { // Create sampler objects for (size_t i = 0; i < texture_samplers.size(); ++i) { @@ -181,7 +173,7 @@ void RasterizerOpenGL::DrawTriangles() { CachedSurface* depth_surface; MathUtil::Rectangle<int> rect; std::tie(color_surface, depth_surface, rect) = - res_cache.GetFramebufferSurfaces(regs.framebuffer); + res_cache.GetFramebufferSurfaces(regs.framebuffer.framebuffer); state.draw.draw_framebuffer = framebuffer.handle; state.Apply(); @@ -190,20 +182,24 @@ void RasterizerOpenGL::DrawTriangles() { color_surface != nullptr ? color_surface->texture.handle : 0, 0); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_surface != nullptr ? depth_surface->texture.handle : 0, 0); - bool has_stencil = regs.framebuffer.depth_format == Pica::Regs::DepthFormat::D24S8; + bool has_stencil = + regs.framebuffer.framebuffer.depth_format == Pica::FramebufferRegs::DepthFormat::D24S8; glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, (has_stencil && depth_surface != nullptr) ? depth_surface->texture.handle : 0, 0); // Sync the viewport // These registers hold half-width and half-height, so must be multiplied by 2 - GLsizei viewport_width = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_x).ToFloat32() * 2; - GLsizei viewport_height = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_y).ToFloat32() * 2; + GLsizei viewport_width = + (GLsizei)Pica::float24::FromRaw(regs.rasterizer.viewport_size_x).ToFloat32() * 2; + GLsizei viewport_height = + (GLsizei)Pica::float24::FromRaw(regs.rasterizer.viewport_size_y).ToFloat32() * 2; - glViewport((GLint)(rect.left + regs.viewport_corner.x * color_surface->res_scale_width), - (GLint)(rect.bottom + regs.viewport_corner.y * color_surface->res_scale_height), - (GLsizei)(viewport_width * color_surface->res_scale_width), - (GLsizei)(viewport_height * color_surface->res_scale_height)); + glViewport( + (GLint)(rect.left + regs.rasterizer.viewport_corner.x * color_surface->res_scale_width), + (GLint)(rect.bottom + regs.rasterizer.viewport_corner.y * color_surface->res_scale_height), + (GLsizei)(viewport_width * color_surface->res_scale_width), + (GLsizei)(viewport_height * color_surface->res_scale_height)); if (uniform_block_data.data.framebuffer_scale[0] != color_surface->res_scale_width || uniform_block_data.data.framebuffer_scale[1] != color_surface->res_scale_height) { @@ -215,16 +211,16 @@ void RasterizerOpenGL::DrawTriangles() { // Scissor checks are window-, not viewport-relative, which means that if the cached texture // sub-rect changes, the scissor bounds also need to be updated. - GLint scissor_x1 = - static_cast<GLint>(rect.left + regs.scissor_test.x1 * color_surface->res_scale_width); - GLint scissor_y1 = - static_cast<GLint>(rect.bottom + regs.scissor_test.y1 * color_surface->res_scale_height); + GLint scissor_x1 = static_cast<GLint>( + rect.left + regs.rasterizer.scissor_test.x1 * color_surface->res_scale_width); + GLint scissor_y1 = static_cast<GLint>( + rect.bottom + regs.rasterizer.scissor_test.y1 * color_surface->res_scale_height); // x2, y2 have +1 added to cover the entire pixel area, otherwise you might get cracks when // scaling or doing multisampling. - GLint scissor_x2 = - static_cast<GLint>(rect.left + (regs.scissor_test.x2 + 1) * color_surface->res_scale_width); + GLint scissor_x2 = static_cast<GLint>( + rect.left + (regs.rasterizer.scissor_test.x2 + 1) * color_surface->res_scale_width); GLint scissor_y2 = static_cast<GLint>( - rect.bottom + (regs.scissor_test.y2 + 1) * color_surface->res_scale_height); + rect.bottom + (regs.rasterizer.scissor_test.y2 + 1) * color_surface->res_scale_height); if (uniform_block_data.data.scissor_x1 != scissor_x1 || uniform_block_data.data.scissor_x2 != scissor_x2 || @@ -239,7 +235,7 @@ void RasterizerOpenGL::DrawTriangles() { } // Sync and bind the texture surfaces - const auto pica_textures = regs.GetTextures(); + const auto pica_textures = regs.texturing.GetTextures(); for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) { const auto& texture = pica_textures[texture_index]; @@ -316,69 +312,69 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { switch (id) { // Culling - case PICA_REG_INDEX(cull_mode): + case PICA_REG_INDEX(rasterizer.cull_mode): SyncCullMode(); break; // Depth modifiers - case PICA_REG_INDEX(viewport_depth_range): + case PICA_REG_INDEX(rasterizer.viewport_depth_range): SyncDepthScale(); break; - case PICA_REG_INDEX(viewport_depth_near_plane): + case PICA_REG_INDEX(rasterizer.viewport_depth_near_plane): SyncDepthOffset(); break; // Depth buffering - case PICA_REG_INDEX(depthmap_enable): + case PICA_REG_INDEX(rasterizer.depthmap_enable): shader_dirty = true; break; // Blending - case PICA_REG_INDEX(output_merger.alphablend_enable): + case PICA_REG_INDEX(framebuffer.output_merger.alphablend_enable): SyncBlendEnabled(); break; - case PICA_REG_INDEX(output_merger.alpha_blending): + case PICA_REG_INDEX(framebuffer.output_merger.alpha_blending): SyncBlendFuncs(); break; - case PICA_REG_INDEX(output_merger.blend_const): + case PICA_REG_INDEX(framebuffer.output_merger.blend_const): SyncBlendColor(); break; // Fog state - case PICA_REG_INDEX(fog_color): + case PICA_REG_INDEX(texturing.fog_color): SyncFogColor(); break; - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[0], 0xe8): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[1], 0xe9): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[2], 0xea): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[3], 0xeb): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[4], 0xec): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[5], 0xed): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[6], 0xee): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[7], 0xef): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[0], 0xe8): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[1], 0xe9): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[2], 0xea): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[3], 0xeb): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[4], 0xec): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[5], 0xed): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[6], 0xee): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[7], 0xef): uniform_block_data.fog_lut_dirty = true; break; // Alpha test - case PICA_REG_INDEX(output_merger.alpha_test): + case PICA_REG_INDEX(framebuffer.output_merger.alpha_test): SyncAlphaTest(); shader_dirty = true; break; // Sync GL stencil test + stencil write mask // (Pica stencil test function register also contains a stencil write mask) - case PICA_REG_INDEX(output_merger.stencil_test.raw_func): + case PICA_REG_INDEX(framebuffer.output_merger.stencil_test.raw_func): SyncStencilTest(); SyncStencilWriteMask(); break; - case PICA_REG_INDEX(output_merger.stencil_test.raw_op): - case PICA_REG_INDEX(framebuffer.depth_format): + case PICA_REG_INDEX(framebuffer.output_merger.stencil_test.raw_op): + case PICA_REG_INDEX(framebuffer.framebuffer.depth_format): SyncStencilTest(); break; // Sync GL depth test + depth and color write mask // (Pica depth test function register also contains a depth and color write mask) - case PICA_REG_INDEX(output_merger.depth_test_enable): + case PICA_REG_INDEX(framebuffer.output_merger.depth_test_enable): SyncDepthTest(); SyncDepthWriteMask(); SyncColorWriteMask(); @@ -386,88 +382,88 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { // Sync GL depth and stencil write mask // (This is a dedicated combined depth / stencil write-enable register) - case PICA_REG_INDEX(framebuffer.allow_depth_stencil_write): + case PICA_REG_INDEX(framebuffer.framebuffer.allow_depth_stencil_write): SyncDepthWriteMask(); SyncStencilWriteMask(); break; // Sync GL color write mask // (This is a dedicated color write-enable register) - case PICA_REG_INDEX(framebuffer.allow_color_write): + case PICA_REG_INDEX(framebuffer.framebuffer.allow_color_write): SyncColorWriteMask(); break; // Scissor test - case PICA_REG_INDEX(scissor_test.mode): + case PICA_REG_INDEX(rasterizer.scissor_test.mode): shader_dirty = true; break; // Logic op - case PICA_REG_INDEX(output_merger.logic_op): + case PICA_REG_INDEX(framebuffer.output_merger.logic_op): SyncLogicOp(); break; // Texture 0 type - case PICA_REG_INDEX(texture0.type): + case PICA_REG_INDEX(texturing.texture0.type): shader_dirty = true; break; // TEV stages // (This also syncs fog_mode and fog_flip which are part of tev_combiner_buffer_input) - case PICA_REG_INDEX(tev_stage0.color_source1): - case PICA_REG_INDEX(tev_stage0.color_modifier1): - case PICA_REG_INDEX(tev_stage0.color_op): - case PICA_REG_INDEX(tev_stage0.color_scale): - case PICA_REG_INDEX(tev_stage1.color_source1): - case PICA_REG_INDEX(tev_stage1.color_modifier1): - case PICA_REG_INDEX(tev_stage1.color_op): - case PICA_REG_INDEX(tev_stage1.color_scale): - case PICA_REG_INDEX(tev_stage2.color_source1): - case PICA_REG_INDEX(tev_stage2.color_modifier1): - case PICA_REG_INDEX(tev_stage2.color_op): - case PICA_REG_INDEX(tev_stage2.color_scale): - case PICA_REG_INDEX(tev_stage3.color_source1): - case PICA_REG_INDEX(tev_stage3.color_modifier1): - case PICA_REG_INDEX(tev_stage3.color_op): - case PICA_REG_INDEX(tev_stage3.color_scale): - case PICA_REG_INDEX(tev_stage4.color_source1): - case PICA_REG_INDEX(tev_stage4.color_modifier1): - case PICA_REG_INDEX(tev_stage4.color_op): - case PICA_REG_INDEX(tev_stage4.color_scale): - case PICA_REG_INDEX(tev_stage5.color_source1): - case PICA_REG_INDEX(tev_stage5.color_modifier1): - case PICA_REG_INDEX(tev_stage5.color_op): - case PICA_REG_INDEX(tev_stage5.color_scale): - case PICA_REG_INDEX(tev_combiner_buffer_input): + case PICA_REG_INDEX(texturing.tev_stage0.color_source1): + case PICA_REG_INDEX(texturing.tev_stage0.color_modifier1): + case PICA_REG_INDEX(texturing.tev_stage0.color_op): + case PICA_REG_INDEX(texturing.tev_stage0.color_scale): + case PICA_REG_INDEX(texturing.tev_stage1.color_source1): + case PICA_REG_INDEX(texturing.tev_stage1.color_modifier1): + case PICA_REG_INDEX(texturing.tev_stage1.color_op): + case PICA_REG_INDEX(texturing.tev_stage1.color_scale): + case PICA_REG_INDEX(texturing.tev_stage2.color_source1): + case PICA_REG_INDEX(texturing.tev_stage2.color_modifier1): + case PICA_REG_INDEX(texturing.tev_stage2.color_op): + case PICA_REG_INDEX(texturing.tev_stage2.color_scale): + case PICA_REG_INDEX(texturing.tev_stage3.color_source1): + case PICA_REG_INDEX(texturing.tev_stage3.color_modifier1): + case PICA_REG_INDEX(texturing.tev_stage3.color_op): + case PICA_REG_INDEX(texturing.tev_stage3.color_scale): + case PICA_REG_INDEX(texturing.tev_stage4.color_source1): + case PICA_REG_INDEX(texturing.tev_stage4.color_modifier1): + case PICA_REG_INDEX(texturing.tev_stage4.color_op): + case PICA_REG_INDEX(texturing.tev_stage4.color_scale): + case PICA_REG_INDEX(texturing.tev_stage5.color_source1): + case PICA_REG_INDEX(texturing.tev_stage5.color_modifier1): + case PICA_REG_INDEX(texturing.tev_stage5.color_op): + case PICA_REG_INDEX(texturing.tev_stage5.color_scale): + case PICA_REG_INDEX(texturing.tev_combiner_buffer_input): shader_dirty = true; break; - case PICA_REG_INDEX(tev_stage0.const_r): - SyncTevConstColor(0, regs.tev_stage0); + case PICA_REG_INDEX(texturing.tev_stage0.const_r): + SyncTevConstColor(0, regs.texturing.tev_stage0); break; - case PICA_REG_INDEX(tev_stage1.const_r): - SyncTevConstColor(1, regs.tev_stage1); + case PICA_REG_INDEX(texturing.tev_stage1.const_r): + SyncTevConstColor(1, regs.texturing.tev_stage1); break; - case PICA_REG_INDEX(tev_stage2.const_r): - SyncTevConstColor(2, regs.tev_stage2); + case PICA_REG_INDEX(texturing.tev_stage2.const_r): + SyncTevConstColor(2, regs.texturing.tev_stage2); break; - case PICA_REG_INDEX(tev_stage3.const_r): - SyncTevConstColor(3, regs.tev_stage3); + case PICA_REG_INDEX(texturing.tev_stage3.const_r): + SyncTevConstColor(3, regs.texturing.tev_stage3); break; - case PICA_REG_INDEX(tev_stage4.const_r): - SyncTevConstColor(4, regs.tev_stage4); + case PICA_REG_INDEX(texturing.tev_stage4.const_r): + SyncTevConstColor(4, regs.texturing.tev_stage4); break; - case PICA_REG_INDEX(tev_stage5.const_r): - SyncTevConstColor(5, regs.tev_stage5); + case PICA_REG_INDEX(texturing.tev_stage5.const_r): + SyncTevConstColor(5, regs.texturing.tev_stage5); break; // TEV combiner buffer color - case PICA_REG_INDEX(tev_combiner_buffer_color): + case PICA_REG_INDEX(texturing.tev_combiner_buffer_color): SyncCombinerColor(); break; // 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 +712,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 +742,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 @@ -977,7 +972,9 @@ void RasterizerOpenGL::SamplerInfo::Create() { // Other attributes have correct defaults } -void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Pica::Regs::TextureConfig& config) { +void RasterizerOpenGL::SamplerInfo::SyncWithConfig( + const Pica::TexturingRegs::TextureConfig& config) { + GLuint s = sampler.handle; if (mag_filter != config.mag_filter) { @@ -1074,67 +1071,69 @@ void RasterizerOpenGL::SetShader() { current_shader = shader_cache.emplace(config, std::move(shader)).first->second.get(); - unsigned int block_index = - glGetUniformBlockIndex(current_shader->shader.handle, "shader_data"); - GLint block_size; - glGetActiveUniformBlockiv(current_shader->shader.handle, block_index, - GL_UNIFORM_BLOCK_DATA_SIZE, &block_size); - ASSERT_MSG(block_size == sizeof(UniformData), - "Uniform block size did not match! Got %d, expected %zu", - static_cast<int>(block_size), sizeof(UniformData)); - glUniformBlockBinding(current_shader->shader.handle, block_index, 0); - - // Update uniforms - SyncDepthScale(); - SyncDepthOffset(); - SyncAlphaTest(); - SyncCombinerColor(); - auto& tev_stages = Pica::g_state.regs.GetTevStages(); - for (int index = 0; index < tev_stages.size(); ++index) - SyncTevConstColor(index, tev_stages[index]); + GLuint block_index = glGetUniformBlockIndex(current_shader->shader.handle, "shader_data"); + if (block_index != GL_INVALID_INDEX) { + GLint block_size; + glGetActiveUniformBlockiv(current_shader->shader.handle, block_index, + GL_UNIFORM_BLOCK_DATA_SIZE, &block_size); + ASSERT_MSG(block_size == sizeof(UniformData), + "Uniform block size did not match! Got %d, expected %zu", + static_cast<int>(block_size), sizeof(UniformData)); + glUniformBlockBinding(current_shader->shader.handle, block_index, 0); + + // Update uniforms + SyncDepthScale(); + SyncDepthOffset(); + SyncAlphaTest(); + SyncCombinerColor(); + auto& tev_stages = Pica::g_state.regs.texturing.GetTevStages(); + for (int index = 0; index < tev_stages.size(); ++index) + SyncTevConstColor(index, tev_stages[index]); + + SyncGlobalAmbient(); + for (int light_index = 0; light_index < 8; light_index++) { + SyncLightSpecular0(light_index); + SyncLightSpecular1(light_index); + SyncLightDiffuse(light_index); + SyncLightAmbient(light_index); + SyncLightPosition(light_index); + SyncLightDistanceAttenuationBias(light_index); + SyncLightDistanceAttenuationScale(light_index); + } - SyncGlobalAmbient(); - for (int light_index = 0; light_index < 8; light_index++) { - SyncLightSpecular0(light_index); - SyncLightSpecular1(light_index); - SyncLightDiffuse(light_index); - SyncLightAmbient(light_index); - SyncLightPosition(light_index); - SyncLightDistanceAttenuationBias(light_index); - SyncLightDistanceAttenuationScale(light_index); + SyncFogColor(); } - - SyncFogColor(); } } void RasterizerOpenGL::SyncCullMode() { const auto& regs = Pica::g_state.regs; - switch (regs.cull_mode) { - case Pica::Regs::CullMode::KeepAll: + switch (regs.rasterizer.cull_mode) { + case Pica::RasterizerRegs::CullMode::KeepAll: state.cull.enabled = false; break; - case Pica::Regs::CullMode::KeepClockWise: + case Pica::RasterizerRegs::CullMode::KeepClockWise: state.cull.enabled = true; state.cull.front_face = GL_CW; break; - case Pica::Regs::CullMode::KeepCounterClockWise: + case Pica::RasterizerRegs::CullMode::KeepCounterClockWise: state.cull.enabled = true; state.cull.front_face = GL_CCW; break; default: - LOG_CRITICAL(Render_OpenGL, "Unknown cull mode %d", regs.cull_mode.Value()); + LOG_CRITICAL(Render_OpenGL, "Unknown cull mode %d", regs.rasterizer.cull_mode.Value()); UNIMPLEMENTED(); break; } } void RasterizerOpenGL::SyncDepthScale() { - float depth_scale = Pica::float24::FromRaw(Pica::g_state.regs.viewport_depth_range).ToFloat32(); + float depth_scale = + Pica::float24::FromRaw(Pica::g_state.regs.rasterizer.viewport_depth_range).ToFloat32(); if (depth_scale != uniform_block_data.data.depth_scale) { uniform_block_data.data.depth_scale = depth_scale; uniform_block_data.dirty = true; @@ -1143,7 +1142,7 @@ void RasterizerOpenGL::SyncDepthScale() { void RasterizerOpenGL::SyncDepthOffset() { float depth_offset = - Pica::float24::FromRaw(Pica::g_state.regs.viewport_depth_near_plane).ToFloat32(); + Pica::float24::FromRaw(Pica::g_state.regs.rasterizer.viewport_depth_near_plane).ToFloat32(); if (depth_offset != uniform_block_data.data.depth_offset) { uniform_block_data.data.depth_offset = depth_offset; uniform_block_data.dirty = true; @@ -1151,25 +1150,28 @@ void RasterizerOpenGL::SyncDepthOffset() { } void RasterizerOpenGL::SyncBlendEnabled() { - state.blend.enabled = (Pica::g_state.regs.output_merger.alphablend_enable == 1); + state.blend.enabled = (Pica::g_state.regs.framebuffer.output_merger.alphablend_enable == 1); } void RasterizerOpenGL::SyncBlendFuncs() { const auto& regs = Pica::g_state.regs; state.blend.rgb_equation = - PicaToGL::BlendEquation(regs.output_merger.alpha_blending.blend_equation_rgb); + PicaToGL::BlendEquation(regs.framebuffer.output_merger.alpha_blending.blend_equation_rgb); state.blend.a_equation = - PicaToGL::BlendEquation(regs.output_merger.alpha_blending.blend_equation_a); + PicaToGL::BlendEquation(regs.framebuffer.output_merger.alpha_blending.blend_equation_a); state.blend.src_rgb_func = - PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_source_rgb); + PicaToGL::BlendFunc(regs.framebuffer.output_merger.alpha_blending.factor_source_rgb); state.blend.dst_rgb_func = - PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_dest_rgb); - state.blend.src_a_func = PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_source_a); - state.blend.dst_a_func = PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_dest_a); + PicaToGL::BlendFunc(regs.framebuffer.output_merger.alpha_blending.factor_dest_rgb); + state.blend.src_a_func = + PicaToGL::BlendFunc(regs.framebuffer.output_merger.alpha_blending.factor_source_a); + state.blend.dst_a_func = + PicaToGL::BlendFunc(regs.framebuffer.output_merger.alpha_blending.factor_dest_a); } void RasterizerOpenGL::SyncBlendColor() { - auto blend_color = PicaToGL::ColorRGBA8(Pica::g_state.regs.output_merger.blend_const.raw); + auto blend_color = + PicaToGL::ColorRGBA8(Pica::g_state.regs.framebuffer.output_merger.blend_const.raw); state.blend.color.red = blend_color[0]; state.blend.color.green = blend_color[1]; state.blend.color.blue = blend_color[2]; @@ -1179,8 +1181,8 @@ void RasterizerOpenGL::SyncBlendColor() { void RasterizerOpenGL::SyncFogColor() { const auto& regs = Pica::g_state.regs; uniform_block_data.data.fog_color = { - regs.fog_color.r.Value() / 255.0f, regs.fog_color.g.Value() / 255.0f, - regs.fog_color.b.Value() / 255.0f, + regs.texturing.fog_color.r.Value() / 255.0f, regs.texturing.fog_color.g.Value() / 255.0f, + regs.texturing.fog_color.b.Value() / 255.0f, }; uniform_block_data.dirty = true; } @@ -1201,70 +1203,78 @@ void RasterizerOpenGL::SyncFogLUT() { void RasterizerOpenGL::SyncAlphaTest() { const auto& regs = Pica::g_state.regs; - if (regs.output_merger.alpha_test.ref != uniform_block_data.data.alphatest_ref) { - uniform_block_data.data.alphatest_ref = regs.output_merger.alpha_test.ref; + if (regs.framebuffer.output_merger.alpha_test.ref != uniform_block_data.data.alphatest_ref) { + uniform_block_data.data.alphatest_ref = regs.framebuffer.output_merger.alpha_test.ref; uniform_block_data.dirty = true; } } void RasterizerOpenGL::SyncLogicOp() { - state.logic_op = PicaToGL::LogicOp(Pica::g_state.regs.output_merger.logic_op); + state.logic_op = PicaToGL::LogicOp(Pica::g_state.regs.framebuffer.output_merger.logic_op); } void RasterizerOpenGL::SyncColorWriteMask() { const auto& regs = Pica::g_state.regs; auto IsColorWriteEnabled = [&](u32 value) { - return (regs.framebuffer.allow_color_write != 0 && value != 0) ? GL_TRUE : GL_FALSE; + return (regs.framebuffer.framebuffer.allow_color_write != 0 && value != 0) ? GL_TRUE + : GL_FALSE; }; - state.color_mask.red_enabled = IsColorWriteEnabled(regs.output_merger.red_enable); - state.color_mask.green_enabled = IsColorWriteEnabled(regs.output_merger.green_enable); - state.color_mask.blue_enabled = IsColorWriteEnabled(regs.output_merger.blue_enable); - state.color_mask.alpha_enabled = IsColorWriteEnabled(regs.output_merger.alpha_enable); + state.color_mask.red_enabled = IsColorWriteEnabled(regs.framebuffer.output_merger.red_enable); + state.color_mask.green_enabled = + IsColorWriteEnabled(regs.framebuffer.output_merger.green_enable); + state.color_mask.blue_enabled = IsColorWriteEnabled(regs.framebuffer.output_merger.blue_enable); + state.color_mask.alpha_enabled = + IsColorWriteEnabled(regs.framebuffer.output_merger.alpha_enable); } void RasterizerOpenGL::SyncStencilWriteMask() { const auto& regs = Pica::g_state.regs; - state.stencil.write_mask = (regs.framebuffer.allow_depth_stencil_write != 0) - ? static_cast<GLuint>(regs.output_merger.stencil_test.write_mask) - : 0; + state.stencil.write_mask = + (regs.framebuffer.framebuffer.allow_depth_stencil_write != 0) + ? static_cast<GLuint>(regs.framebuffer.output_merger.stencil_test.write_mask) + : 0; } void RasterizerOpenGL::SyncDepthWriteMask() { const auto& regs = Pica::g_state.regs; - state.depth.write_mask = - (regs.framebuffer.allow_depth_stencil_write != 0 && regs.output_merger.depth_write_enable) - ? GL_TRUE - : GL_FALSE; + state.depth.write_mask = (regs.framebuffer.framebuffer.allow_depth_stencil_write != 0 && + regs.framebuffer.output_merger.depth_write_enable) + ? GL_TRUE + : GL_FALSE; } void RasterizerOpenGL::SyncStencilTest() { const auto& regs = Pica::g_state.regs; - state.stencil.test_enabled = regs.output_merger.stencil_test.enable && - regs.framebuffer.depth_format == Pica::Regs::DepthFormat::D24S8; - state.stencil.test_func = PicaToGL::CompareFunc(regs.output_merger.stencil_test.func); - state.stencil.test_ref = regs.output_merger.stencil_test.reference_value; - state.stencil.test_mask = regs.output_merger.stencil_test.input_mask; + state.stencil.test_enabled = + regs.framebuffer.output_merger.stencil_test.enable && + regs.framebuffer.framebuffer.depth_format == Pica::FramebufferRegs::DepthFormat::D24S8; + state.stencil.test_func = + PicaToGL::CompareFunc(regs.framebuffer.output_merger.stencil_test.func); + state.stencil.test_ref = regs.framebuffer.output_merger.stencil_test.reference_value; + state.stencil.test_mask = regs.framebuffer.output_merger.stencil_test.input_mask; state.stencil.action_stencil_fail = - PicaToGL::StencilOp(regs.output_merger.stencil_test.action_stencil_fail); + PicaToGL::StencilOp(regs.framebuffer.output_merger.stencil_test.action_stencil_fail); state.stencil.action_depth_fail = - PicaToGL::StencilOp(regs.output_merger.stencil_test.action_depth_fail); + PicaToGL::StencilOp(regs.framebuffer.output_merger.stencil_test.action_depth_fail); state.stencil.action_depth_pass = - PicaToGL::StencilOp(regs.output_merger.stencil_test.action_depth_pass); + PicaToGL::StencilOp(regs.framebuffer.output_merger.stencil_test.action_depth_pass); } void RasterizerOpenGL::SyncDepthTest() { const auto& regs = Pica::g_state.regs; - state.depth.test_enabled = - regs.output_merger.depth_test_enable == 1 || regs.output_merger.depth_write_enable == 1; - state.depth.test_func = regs.output_merger.depth_test_enable == 1 - ? PicaToGL::CompareFunc(regs.output_merger.depth_test_func) - : GL_ALWAYS; + state.depth.test_enabled = regs.framebuffer.output_merger.depth_test_enable == 1 || + regs.framebuffer.output_merger.depth_write_enable == 1; + state.depth.test_func = + regs.framebuffer.output_merger.depth_test_enable == 1 + ? PicaToGL::CompareFunc(regs.framebuffer.output_merger.depth_test_func) + : GL_ALWAYS; } void RasterizerOpenGL::SyncCombinerColor() { - auto combiner_color = PicaToGL::ColorRGBA8(Pica::g_state.regs.tev_combiner_buffer_color.raw); + auto combiner_color = + PicaToGL::ColorRGBA8(Pica::g_state.regs.texturing.tev_combiner_buffer_color.raw); if (combiner_color != uniform_block_data.data.tev_combiner_buffer_color) { uniform_block_data.data.tev_combiner_buffer_color = combiner_color; uniform_block_data.dirty = true; @@ -1272,7 +1282,7 @@ void RasterizerOpenGL::SyncCombinerColor() { } void RasterizerOpenGL::SyncTevConstColor(int stage_index, - const Pica::Regs::TevStageConfig& tev_stage) { + const Pica::TexturingRegs::TevStageConfig& tev_stage) { auto const_color = PicaToGL::ColorRGBA8(tev_stage.const_color); if (const_color != uniform_block_data.data.const_color[stage_index]) { uniform_block_data.data.const_color[stage_index] = const_color; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index e1a9cb361..ecf737438 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -16,10 +16,13 @@ #include "common/hash.h" #include "common/vector_math.h" #include "core/hw/gpu.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" #include "video_core/pica_types.h" #include "video_core/rasterizer_interface.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_lighting.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_texturing.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" #include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_state.h" @@ -52,20 +55,20 @@ union PicaShaderConfig { const auto& regs = Pica::g_state.regs; - state.scissor_test_mode = regs.scissor_test.mode; + state.scissor_test_mode = regs.rasterizer.scissor_test.mode; - state.depthmap_enable = regs.depthmap_enable; + state.depthmap_enable = regs.rasterizer.depthmap_enable; - state.alpha_test_func = regs.output_merger.alpha_test.enable - ? regs.output_merger.alpha_test.func.Value() - : Pica::Regs::CompareFunc::Always; + state.alpha_test_func = regs.framebuffer.output_merger.alpha_test.enable + ? regs.framebuffer.output_merger.alpha_test.func.Value() + : Pica::FramebufferRegs::CompareFunc::Always; - state.texture0_type = regs.texture0.type; + state.texture0_type = regs.texturing.texture0.type; // Copy relevant tev stages fields. // We don't sync const_color here because of the high variance, it is a // shader uniform instead. - const auto& tev_stages = regs.GetTevStages(); + const auto& tev_stages = regs.texturing.GetTevStages(); DEBUG_ASSERT(state.tev_stages.size() == tev_stages.size()); for (size_t i = 0; i < tev_stages.size(); i++) { const auto& tev_stage = tev_stages[i]; @@ -75,16 +78,17 @@ union PicaShaderConfig { state.tev_stages[i].scales_raw = tev_stage.scales_raw; } - state.fog_mode = regs.fog_mode; - state.fog_flip = regs.fog_flip; + state.fog_mode = regs.texturing.fog_mode; + state.fog_flip = regs.texturing.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; + state.combiner_buffer_input = + regs.texturing.tev_combiner_buffer_input.update_mask_rgb.Value() | + regs.texturing.tev_combiner_buffer_input.update_mask_a.Value() << 4; // 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); @@ -159,8 +163,8 @@ union PicaShaderConfig { u32 modifiers_raw; u32 ops_raw; u32 scales_raw; - explicit operator Pica::Regs::TevStageConfig() const noexcept { - Pica::Regs::TevStageConfig stage; + explicit operator Pica::TexturingRegs::TevStageConfig() const noexcept { + Pica::TexturingRegs::TevStageConfig stage; stage.sources_raw = sources_raw; stage.modifiers_raw = modifiers_raw; stage.ops_raw = ops_raw; @@ -171,14 +175,14 @@ union PicaShaderConfig { }; struct State { - Pica::Regs::CompareFunc alpha_test_func; - Pica::Regs::ScissorMode scissor_test_mode; - Pica::Regs::TextureConfig::TextureType texture0_type; + Pica::FramebufferRegs::CompareFunc alpha_test_func; + Pica::RasterizerRegs::ScissorMode scissor_test_mode; + Pica::TexturingRegs::TextureConfig::TextureType texture0_type; std::array<TevStageConfigRaw, 6> tev_stages; u8 combiner_buffer_input; - Pica::Regs::DepthBuffering depthmap_enable; - Pica::Regs::FogMode fog_mode; + Pica::RasterizerRegs::DepthBuffering depthmap_enable; + Pica::TexturingRegs::FogMode fog_mode; bool fog_flip; struct { @@ -191,18 +195,18 @@ union PicaShaderConfig { bool enable; unsigned src_num; - Pica::Regs::LightingBumpMode bump_mode; + Pica::LightingRegs::LightingBumpMode bump_mode; unsigned bump_selector; bool bump_renorm; bool clamp_highlights; - Pica::Regs::LightingConfig config; - Pica::Regs::LightingFresnelSelector fresnel_selector; + Pica::LightingRegs::LightingConfig config; + Pica::LightingRegs::LightingFresnelSelector fresnel_selector; struct { bool enable; bool abs_input; - Pica::Regs::LightingLutInput type; + Pica::LightingRegs::LightingLutInput type; float scale; } lut_d0, lut_d1, lut_fr, lut_rr, lut_rg, lut_rb; } lighting; @@ -251,7 +255,7 @@ public: private: struct SamplerInfo { - using TextureConfig = Pica::Regs::TextureConfig; + using TextureConfig = Pica::TexturingRegs::TextureConfig; OGLSampler sampler; @@ -398,7 +402,7 @@ private: void SyncCombinerColor(); /// Syncs the TEV constant color to match the PICA register - void SyncTevConstColor(int tev_index, const Pica::Regs::TevStageConfig& tev_stage); + void SyncTevConstColor(int tev_index, const Pica::TexturingRegs::TevStageConfig& tev_stage); /// Syncs the lighting global ambient color to match the PICA register void SyncGlobalAmbient(); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index ef3b06a7b..0818a87b3 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.format = (Pica::TexturingRegs::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); } } @@ -512,9 +510,10 @@ 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); + const Pica::TexturingRegs::FullTextureConfig& config) { + + Pica::Texture::TextureInfo info = + Pica::Texture::TextureInfo::FromPicaRegister(config.config, config.format); CachedSurface params; params.addr = info.physical_address; @@ -526,7 +525,9 @@ CachedSurface* RasterizerCacheOpenGL::GetTextureSurface( } std::tuple<CachedSurface*, CachedSurface*, MathUtil::Rectangle<int>> -RasterizerCacheOpenGL::GetFramebufferSurfaces(const Pica::Regs::FramebufferConfig& config) { +RasterizerCacheOpenGL::GetFramebufferSurfaces( + const Pica::FramebufferRegs::FramebufferConfig& config) { + const auto& regs = Pica::g_state.regs; // Make sur that framebuffers don't overlap if both color and depth are being used @@ -538,11 +539,12 @@ RasterizerCacheOpenGL::GetFramebufferSurfaces(const Pica::Regs::FramebufferConfi config.GetColorBufferPhysicalAddress(), fb_area * GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(config.color_format.Value())), config.GetDepthBufferPhysicalAddress(), - fb_area * Pica::Regs::BytesPerDepthPixel(config.depth_format)); + fb_area * Pica::FramebufferRegs::BytesPerDepthPixel(config.depth_format)); bool using_color_fb = config.GetColorBufferPhysicalAddress() != 0; - bool using_depth_fb = config.GetDepthBufferPhysicalAddress() != 0 && - (regs.output_merger.depth_test_enable || - regs.output_merger.depth_write_enable || !framebuffers_overlap); + bool using_depth_fb = + config.GetDepthBufferPhysicalAddress() != 0 && + (regs.framebuffer.output_merger.depth_test_enable || + regs.framebuffer.output_merger.depth_write_enable || !framebuffers_overlap); if (framebuffers_overlap && using_color_fb && using_depth_fb) { LOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer memory regions overlap; " diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index b50e8292b..aea20c693 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -8,13 +8,21 @@ #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" #include "common/common_types.h" #include "core/hw/gpu.h" -#include "video_core/pica.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_texturing.h" #include "video_core/renderer_opengl/gl_resource_manager.h" namespace MathUtil { @@ -89,15 +97,15 @@ struct CachedSurface { return bpp_table[(unsigned int)format]; } - static PixelFormat PixelFormatFromTextureFormat(Pica::Regs::TextureFormat format) { + static PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format) { return ((unsigned int)format < 14) ? (PixelFormat)format : PixelFormat::Invalid; } - static PixelFormat PixelFormatFromColorFormat(Pica::Regs::ColorFormat format) { + static PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format) { return ((unsigned int)format < 5) ? (PixelFormat)format : PixelFormat::Invalid; } - static PixelFormat PixelFormatFromDepthFormat(Pica::Regs::DepthFormat format) { + static PixelFormat PixelFormatFromDepthFormat(Pica::FramebufferRegs::DepthFormat format) { return ((unsigned int)format < 4) ? (PixelFormat)((unsigned int)format + 14) : PixelFormat::Invalid; } @@ -205,12 +213,12 @@ public: bool load_if_create, MathUtil::Rectangle<int>& out_rect); /// Gets a surface based on the texture configuration - CachedSurface* GetTextureSurface(const Pica::Regs::FullTextureConfig& config); + CachedSurface* GetTextureSurface(const Pica::TexturingRegs::FullTextureConfig& config); /// Gets the color and depth surfaces and rect (resolution scaled) based on the framebuffer /// configuration std::tuple<CachedSurface*, CachedSurface*, MathUtil::Rectangle<int>> GetFramebufferSurfaces( - const Pica::Regs::FramebufferConfig& config); + const Pica::FramebufferRegs::FramebufferConfig& config); /// Attempt to get a surface that exactly matches the fill region and format CachedSurface* TryGetFillSurface(const GPU::Regs::MemoryFillConfig& config); diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 4c4f98ac9..7abdeba05 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -7,13 +7,19 @@ #include "common/assert.h" #include "common/bit_field.h" #include "common/logging/log.h" -#include "video_core/pica.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_lighting.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_texturing.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_gen.h" #include "video_core/renderer_opengl/gl_shader_util.h" -using Pica::Regs; -using TevStageConfig = Regs::TevStageConfig; +using Pica::FramebufferRegs; +using Pica::LightingRegs; +using Pica::RasterizerRegs; +using Pica::TexturingRegs; +using TevStageConfig = TexturingRegs::TevStageConfig; namespace GLShader { @@ -46,10 +52,10 @@ static void AppendSource(std::string& out, const PicaShaderConfig& config, case Source::Texture0: // Only unit 0 respects the texturing type (according to 3DBrew) switch (state.texture0_type) { - case Pica::Regs::TextureConfig::Texture2D: + case TexturingRegs::TextureConfig::Texture2D: out += "texture(tex[0], texcoord[0])"; break; - case Pica::Regs::TextureConfig::Projection2D: + case TexturingRegs::TextureConfig::Projection2D: out += "textureProj(tex[0], vec3(texcoord[0], texcoord0_w))"; break; default: @@ -276,8 +282,8 @@ static void AppendAlphaCombiner(std::string& out, TevStageConfig::Operation oper } /// Writes the if-statement condition used to evaluate alpha testing -static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) { - using CompareFunc = Regs::CompareFunc; +static void AppendAlphaTestCondition(std::string& out, FramebufferRegs::CompareFunc func) { + using CompareFunc = FramebufferRegs::CompareFunc; switch (func) { case CompareFunc::Never: out += "true"; @@ -307,7 +313,7 @@ static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) { /// Writes the code to emulate the specified TEV stage static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsigned index) { const auto stage = - static_cast<const Pica::Regs::TevStageConfig>(config.state.tev_stages[index]); + static_cast<const TexturingRegs::TevStageConfig>(config.state.tev_stages[index]); if (!IsPassThroughTevStage(stage)) { std::string index_name = std::to_string(index); @@ -364,7 +370,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { "vec3 refl_value = vec3(0.0);\n"; // Compute fragment normals - if (lighting.bump_mode == Pica::Regs::LightingBumpMode::NormalMap) { + if (lighting.bump_mode == LightingRegs::LightingBumpMode::NormalMap) { // Bump mapping is enabled using a normal map, read perturbation vector from the selected // texture std::string bump_selector = std::to_string(lighting.bump_selector); @@ -378,7 +384,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { "(1.0 - (surface_normal.x*surface_normal.x + surface_normal.y*surface_normal.y))"; out += "surface_normal.z = sqrt(max(" + val + ", 0.0));\n"; } - } else if (lighting.bump_mode == Pica::Regs::LightingBumpMode::TangentMap) { + } else if (lighting.bump_mode == LightingRegs::LightingBumpMode::TangentMap) { // Bump mapping is enabled using a tangent map LOG_CRITICAL(HW_GPU, "unimplemented bump mapping mode (tangent mapping)"); UNIMPLEMENTED(); @@ -392,23 +398,24 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { out += "vec3 normal = normalize(quaternion_rotate(normquat, surface_normal));\n"; // Gets the index into the specified lookup table for specular lighting - auto GetLutIndex = [&lighting](unsigned light_num, Regs::LightingLutInput input, bool abs) { + auto GetLutIndex = [&lighting](unsigned light_num, LightingRegs::LightingLutInput input, + bool abs) { const std::string half_angle = "normalize(normalize(view) + light_vector)"; std::string index; switch (input) { - case Regs::LightingLutInput::NH: + case LightingRegs::LightingLutInput::NH: index = "dot(normal, " + half_angle + ")"; break; - case Regs::LightingLutInput::VH: + case LightingRegs::LightingLutInput::VH: index = std::string("dot(normalize(view), " + half_angle + ")"); break; - case Regs::LightingLutInput::NV: + case LightingRegs::LightingLutInput::NV: index = std::string("dot(normal, normalize(view))"); break; - case Regs::LightingLutInput::LN: + case LightingRegs::LightingLutInput::LN: index = std::string("dot(light_vector, normal)"); break; @@ -432,7 +439,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { }; // Gets the lighting lookup table value given the specified sampler and index - auto GetLutValue = [](Regs::LightingSampler sampler, std::string lut_index) { + auto GetLutValue = [](LightingRegs::LightingSampler sampler, std::string lut_index) { return std::string("texture(lut[" + std::to_string((unsigned)sampler / 4) + "], " + lut_index + ")[" + std::to_string((unsigned)sampler & 3) + "]"); }; @@ -461,8 +468,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { light_src + ".position) + " + light_src + ".dist_atten_bias)"; 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); + ((unsigned)LightingRegs::LightingSampler::DistanceAttenuation + light_config.num); + dist_atten = GetLutValue((LightingRegs::LightingSampler)lut_num, index); } // If enabled, clamp specular component if lighting result is negative @@ -472,24 +479,24 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { // Specular 0 component std::string d0_lut_value = "1.0"; if (lighting.lut_d0.enable && - Pica::Regs::IsLightingSamplerSupported(lighting.config, - Pica::Regs::LightingSampler::Distribution0)) { + LightingRegs::IsLightingSamplerSupported( + lighting.config, LightingRegs::LightingSampler::Distribution0)) { // Lookup specular "distribution 0" LUT value std::string index = GetLutIndex(light_config.num, lighting.lut_d0.type, lighting.lut_d0.abs_input); d0_lut_value = "(" + std::to_string(lighting.lut_d0.scale) + " * " + - GetLutValue(Regs::LightingSampler::Distribution0, index) + ")"; + GetLutValue(LightingRegs::LightingSampler::Distribution0, index) + ")"; } std::string specular_0 = "(" + d0_lut_value + " * " + light_src + ".specular_0)"; // If enabled, lookup ReflectRed value, otherwise, 1.0 is used if (lighting.lut_rr.enable && - Pica::Regs::IsLightingSamplerSupported(lighting.config, - Pica::Regs::LightingSampler::ReflectRed)) { + LightingRegs::IsLightingSamplerSupported(lighting.config, + LightingRegs::LightingSampler::ReflectRed)) { std::string index = GetLutIndex(light_config.num, lighting.lut_rr.type, lighting.lut_rr.abs_input); std::string value = "(" + std::to_string(lighting.lut_rr.scale) + " * " + - GetLutValue(Regs::LightingSampler::ReflectRed, index) + ")"; + GetLutValue(LightingRegs::LightingSampler::ReflectRed, index) + ")"; out += "refl_value.r = " + value + ";\n"; } else { out += "refl_value.r = 1.0;\n"; @@ -497,12 +504,13 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { // If enabled, lookup ReflectGreen value, otherwise, ReflectRed value is used if (lighting.lut_rg.enable && - Pica::Regs::IsLightingSamplerSupported(lighting.config, - Pica::Regs::LightingSampler::ReflectGreen)) { + LightingRegs::IsLightingSamplerSupported(lighting.config, + LightingRegs::LightingSampler::ReflectGreen)) { std::string index = GetLutIndex(light_config.num, lighting.lut_rg.type, lighting.lut_rg.abs_input); std::string value = "(" + std::to_string(lighting.lut_rg.scale) + " * " + - GetLutValue(Regs::LightingSampler::ReflectGreen, index) + ")"; + GetLutValue(LightingRegs::LightingSampler::ReflectGreen, index) + + ")"; out += "refl_value.g = " + value + ";\n"; } else { out += "refl_value.g = refl_value.r;\n"; @@ -510,12 +518,13 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { // If enabled, lookup ReflectBlue value, otherwise, ReflectRed value is used if (lighting.lut_rb.enable && - Pica::Regs::IsLightingSamplerSupported(lighting.config, - Pica::Regs::LightingSampler::ReflectBlue)) { + LightingRegs::IsLightingSamplerSupported(lighting.config, + LightingRegs::LightingSampler::ReflectBlue)) { std::string index = GetLutIndex(light_config.num, lighting.lut_rb.type, lighting.lut_rb.abs_input); std::string value = "(" + std::to_string(lighting.lut_rb.scale) + " * " + - GetLutValue(Regs::LightingSampler::ReflectBlue, index) + ")"; + GetLutValue(LightingRegs::LightingSampler::ReflectBlue, index) + + ")"; out += "refl_value.b = " + value + ";\n"; } else { out += "refl_value.b = refl_value.r;\n"; @@ -524,35 +533,39 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { // Specular 1 component std::string d1_lut_value = "1.0"; if (lighting.lut_d1.enable && - Pica::Regs::IsLightingSamplerSupported(lighting.config, - Pica::Regs::LightingSampler::Distribution1)) { + LightingRegs::IsLightingSamplerSupported( + lighting.config, LightingRegs::LightingSampler::Distribution1)) { // Lookup specular "distribution 1" LUT value std::string index = GetLutIndex(light_config.num, lighting.lut_d1.type, lighting.lut_d1.abs_input); d1_lut_value = "(" + std::to_string(lighting.lut_d1.scale) + " * " + - GetLutValue(Regs::LightingSampler::Distribution1, index) + ")"; + GetLutValue(LightingRegs::LightingSampler::Distribution1, index) + ")"; } std::string specular_1 = "(" + d1_lut_value + " * refl_value * " + light_src + ".specular_1)"; // Fresnel - if (lighting.lut_fr.enable && Pica::Regs::IsLightingSamplerSupported( - lighting.config, Pica::Regs::LightingSampler::Fresnel)) { + if (lighting.lut_fr.enable && + LightingRegs::IsLightingSamplerSupported(lighting.config, + LightingRegs::LightingSampler::Fresnel)) { // Lookup fresnel LUT value std::string index = GetLutIndex(light_config.num, lighting.lut_fr.type, lighting.lut_fr.abs_input); std::string value = "(" + std::to_string(lighting.lut_fr.scale) + " * " + - GetLutValue(Regs::LightingSampler::Fresnel, index) + ")"; + GetLutValue(LightingRegs::LightingSampler::Fresnel, index) + ")"; // Enabled for difffuse lighting alpha component - if (lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::PrimaryAlpha || - lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::Both) + if (lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::PrimaryAlpha || + lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { out += "diffuse_sum.a *= " + value + ";\n"; + } // Enabled for the specular lighting alpha component - if (lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::SecondaryAlpha || - lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::Both) + if (lighting.fresnel_selector == + LightingRegs::LightingFresnelSelector::SecondaryAlpha || + lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { out += "specular_sum.a *= " + value + ";\n"; + } } // Compute primary fragment color (diffuse lighting) function @@ -633,16 +646,16 @@ vec4 secondary_fragment_color = vec4(0.0); )"; // Do not do any sort of processing if it's obvious we're not going to pass the alpha test - if (state.alpha_test_func == Regs::CompareFunc::Never) { + if (state.alpha_test_func == FramebufferRegs::CompareFunc::Never) { out += "discard; }"; return out; } // Append the scissor test - if (state.scissor_test_mode != Regs::ScissorMode::Disabled) { + if (state.scissor_test_mode != RasterizerRegs::ScissorMode::Disabled) { out += "if ("; // Negate the condition if we have to keep only the pixels outside the scissor box - if (state.scissor_test_mode == Regs::ScissorMode::Include) + if (state.scissor_test_mode == RasterizerRegs::ScissorMode::Include) out += "!"; out += "(gl_FragCoord.x >= scissor_x1 && " "gl_FragCoord.y >= scissor_y1 && " @@ -652,7 +665,7 @@ vec4 secondary_fragment_color = vec4(0.0); out += "float z_over_w = 1.0 - gl_FragCoord.z * 2.0;\n"; out += "float depth = z_over_w * depth_scale + depth_offset;\n"; - if (state.depthmap_enable == Pica::Regs::DepthBuffering::WBuffering) { + if (state.depthmap_enable == RasterizerRegs::DepthBuffering::WBuffering) { out += "depth /= gl_FragCoord.w;\n"; } @@ -666,14 +679,14 @@ vec4 secondary_fragment_color = vec4(0.0); for (size_t index = 0; index < state.tev_stages.size(); ++index) WriteTevStage(out, config, (unsigned)index); - if (state.alpha_test_func != Regs::CompareFunc::Always) { + if (state.alpha_test_func != FramebufferRegs::CompareFunc::Always) { out += "if ("; AppendAlphaTestCondition(out, state.alpha_test_func); out += ") discard;\n"; } // Append fog combiner - if (state.fog_mode == Regs::FogMode::Fog) { + if (state.fog_mode == TexturingRegs::FogMode::Fog) { // Get index into fog LUT if (state.fog_flip) { out += "float fog_index = (1.0 - depth) * 128.0;\n"; diff --git a/src/video_core/renderer_opengl/pica_to_gl.h b/src/video_core/renderer_opengl/pica_to_gl.h index cc49867c8..93d7b0b71 100644 --- a/src/video_core/renderer_opengl/pica_to_gl.h +++ b/src/video_core/renderer_opengl/pica_to_gl.h @@ -12,7 +12,9 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/logging/log.h" -#include "video_core/pica.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_lighting.h" +#include "video_core/regs_texturing.h" using GLvec2 = std::array<GLfloat, 2>; using GLvec3 = std::array<GLfloat, 3>; @@ -20,7 +22,7 @@ using GLvec4 = std::array<GLfloat, 4>; namespace PicaToGL { -inline GLenum TextureFilterMode(Pica::Regs::TextureConfig::TextureFilter mode) { +inline GLenum TextureFilterMode(Pica::TexturingRegs::TextureConfig::TextureFilter mode) { static const GLenum filter_mode_table[] = { GL_NEAREST, // TextureFilter::Nearest GL_LINEAR, // TextureFilter::Linear @@ -47,7 +49,7 @@ inline GLenum TextureFilterMode(Pica::Regs::TextureConfig::TextureFilter mode) { return gl_mode; } -inline GLenum WrapMode(Pica::Regs::TextureConfig::WrapMode mode) { +inline GLenum WrapMode(Pica::TexturingRegs::TextureConfig::WrapMode mode) { static const GLenum wrap_mode_table[] = { GL_CLAMP_TO_EDGE, // WrapMode::ClampToEdge GL_CLAMP_TO_BORDER, // WrapMode::ClampToBorder @@ -76,7 +78,7 @@ inline GLenum WrapMode(Pica::Regs::TextureConfig::WrapMode mode) { return gl_mode; } -inline GLenum BlendEquation(Pica::Regs::BlendEquation equation) { +inline GLenum BlendEquation(Pica::FramebufferRegs::BlendEquation equation) { static const GLenum blend_equation_table[] = { GL_FUNC_ADD, // BlendEquation::Add GL_FUNC_SUBTRACT, // BlendEquation::Subtract @@ -96,7 +98,7 @@ inline GLenum BlendEquation(Pica::Regs::BlendEquation equation) { return blend_equation_table[(unsigned)equation]; } -inline GLenum BlendFunc(Pica::Regs::BlendFactor factor) { +inline GLenum BlendFunc(Pica::FramebufferRegs::BlendFactor factor) { static const GLenum blend_func_table[] = { GL_ZERO, // BlendFactor::Zero GL_ONE, // BlendFactor::One @@ -126,7 +128,7 @@ inline GLenum BlendFunc(Pica::Regs::BlendFactor factor) { return blend_func_table[(unsigned)factor]; } -inline GLenum LogicOp(Pica::Regs::LogicOp op) { +inline GLenum LogicOp(Pica::FramebufferRegs::LogicOp op) { static const GLenum logic_op_table[] = { GL_CLEAR, // Clear GL_AND, // And @@ -157,7 +159,7 @@ inline GLenum LogicOp(Pica::Regs::LogicOp op) { return logic_op_table[(unsigned)op]; } -inline GLenum CompareFunc(Pica::Regs::CompareFunc func) { +inline GLenum CompareFunc(Pica::FramebufferRegs::CompareFunc func) { static const GLenum compare_func_table[] = { GL_NEVER, // CompareFunc::Never GL_ALWAYS, // CompareFunc::Always @@ -180,7 +182,7 @@ inline GLenum CompareFunc(Pica::Regs::CompareFunc func) { return compare_func_table[(unsigned)func]; } -inline GLenum StencilOp(Pica::Regs::StencilAction action) { +inline GLenum StencilOp(Pica::FramebufferRegs::StencilAction action) { static const GLenum stencil_op_table[] = { GL_KEEP, // StencilAction::Keep GL_ZERO, // StencilAction::Zero @@ -210,7 +212,7 @@ inline GLvec4 ColorRGBA8(const u32 color) { }}; } -inline std::array<GLfloat, 3> LightColor(const Pica::Regs::LightColor& color) { +inline std::array<GLfloat, 3> LightColor(const Pica::LightingRegs::LightColor& color) { return {{ color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, }}; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 2aa90e5c1..e19375466 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -10,8 +10,8 @@ #include "common/assert.h" #include "common/bit_field.h" #include "common/logging/log.h" -#include "common/profiler_reporting.h" -#include "common/synchronized_wrapper.h" +#include "core/core.h" +#include "core/core_timing.h" #include "core/frontend/emu_window.h" #include "core/hw/gpu.h" #include "core/hw/hw.h" @@ -145,21 +145,16 @@ void RendererOpenGL::SwapBuffers() { DrawScreens(); - auto& profiler = Common::Profiling::GetProfilingManager(); - profiler.FinishFrame(); - { - auto aggregator = Common::Profiling::GetTimingResultsAggregator(); - aggregator->AddFrame(profiler.GetPreviousFrameResults()); - } + Core::System::GetInstance().perf_stats.EndSystemFrame(); // Swap buffers render_window->PollEvents(); render_window->SwapBuffers(); - prev_state.Apply(); - - profiler.BeginFrame(); + Core::System::GetInstance().frame_limiter.DoFrameLimiting(CoreTiming::GetGlobalTimeUs()); + Core::System::GetInstance().perf_stats.BeginSystemFrame(); + prev_state.Apply(); RefreshRasterizerSetting(); if (Pica::g_debug_context && Pica::g_debug_context->recorder) { diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp index a4aa3c9e0..67ed19ba8 100644 --- a/src/video_core/shader/shader.cpp +++ b/src/video_core/shader/shader.cpp @@ -2,18 +2,14 @@ // 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" #include "video_core/pica_state.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_shader.h" #include "video_core/shader/shader.h" #include "video_core/shader/shader_interpreter.h" #ifdef ARCHITECTURE_x86_64 @@ -25,37 +21,31 @@ namespace Pica { namespace Shader { -OutputVertex OutputRegisters::ToVertex(const Regs::ShaderConfig& config) const { +OutputVertex OutputVertex::FromAttributeBuffer(const RasterizerRegs& 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}; + RasterizerRegs::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)); + RasterizerRegs::VSOutputAttributes::Semantic semantic = semantics[comp]; + if (semantic < vertex_slots.size()) { + vertex_slots[semantic] = input.attr[i][comp]; + } else if (semantic != RasterizerRegs::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 ShaderRegs& 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 ShaderRegs& 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..38ea717ab 100644 --- a/src/video_core/shader/shader.h +++ b/src/video_core/shader/shader.h @@ -6,16 +6,15 @@ #include <array> #include <cstddef> -#include <memory> #include <type_traits> #include <nihstro/shader_bytecode.h> #include "common/assert.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/vector_math.h" -#include "video_core/pica.h" #include "video_core/pica_types.h" -#include "video_core/shader/debug_data.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_shader.h" using nihstro::RegisterType; using nihstro::SourceRegister; @@ -25,14 +24,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 +40,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 RasterizerRegs& regs, AttributeBuffer& output); }; +#define ASSERT_POS(var, pos) \ + static_assert(offsetof(OutputVertex, var) == pos * sizeof(float24), "Semantic at wrong " \ + "offset.") +ASSERT_POS(pos, RasterizerRegs::VSOutputAttributes::POSITION_X); +ASSERT_POS(quat, RasterizerRegs::VSOutputAttributes::QUATERNION_X); +ASSERT_POS(color, RasterizerRegs::VSOutputAttributes::COLOR_R); +ASSERT_POS(tc0, RasterizerRegs::VSOutputAttributes::TEXCOORD0_U); +ASSERT_POS(tc1, RasterizerRegs::VSOutputAttributes::TEXCOORD1_U); +ASSERT_POS(tc0_w, RasterizerRegs::VSOutputAttributes::TEXCOORD0_W); +ASSERT_POS(view, RasterizerRegs::VSOutputAttributes::VIEW_X); +ASSERT_POS(tc2, RasterizerRegs::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 +69,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 +98,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 +110,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 ShaderRegs& config, const AttributeBuffer& input); -struct ShaderSetup { + void WriteOutput(const ShaderRegs& 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 +147,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 20fb9754b..f4d1c46c5 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" @@ -37,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 @@ -73,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]; @@ -170,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; @@ -513,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; @@ -647,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 ShaderRegs& 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..50fd7c69d 100644 --- a/src/video_core/shader/shader_interpreter.h +++ b/src/video_core/shader/shader_interpreter.h @@ -4,18 +4,28 @@ #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 setup Shader engine state + * @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 ShaderRegs& 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..2dbc8b147 --- /dev/null +++ b/src/video_core/shader/shader_jit_x64_compiler.cpp @@ -0,0 +1,897 @@ +// 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) { + // 0 * inf and inf * 0 in the PICA should return 0 instead of NaN. This can be implemented by + // checking for NaNs before and after the multiplication. If the multiplication result is NaN + // where neither source was, this NaN was generated by a 0 * inf multiplication, and so the + // result should be transformed to 0 to match PICA fp rules. + + // Set scratch to mask of (src1 != NaN and src2 != NaN) + movaps(scratch, src1); + cmpordps(scratch, src2); + + mulps(src1, src2); + + // Set src2 to mask of (result == NaN) + movaps(src2, src1); + cmpunordps(src2, src2); + + // Clear components where scratch != src2 (i.e. if result is NaN where neither source was NaN) + 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..f27675560 --- /dev/null +++ b/src/video_core/shader/shader_jit_x64_compiler.h @@ -0,0 +1,125 @@ +// 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 condition Condition to be evaluated. + * @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/clipper.cpp b/src/video_core/swrasterizer/clipper.cpp index 05b5cea73..2d80822d9 100644 --- a/src/video_core/clipper.cpp +++ b/src/video_core/swrasterizer/clipper.cpp @@ -11,12 +11,13 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "common/vector_math.h" -#include "video_core/clipper.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" #include "video_core/pica_types.h" -#include "video_core/rasterizer.h" #include "video_core/shader/shader.h" +#include "video_core/swrasterizer/clipper.h" +#include "video_core/swrasterizer/rasterizer.h" + +using Pica::Rasterizer::Vertex; namespace Pica { @@ -29,20 +30,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 +52,7 @@ private: Math::Vec4<float24> bias; }; -static void InitScreenCoordinates(OutputVertex& vtx) { +static void InitScreenCoordinates(Vertex& vtx) { struct { float24 halfsize_x; float24 offset_x; @@ -62,10 +63,10 @@ static void InitScreenCoordinates(OutputVertex& vtx) { } viewport; const auto& regs = g_state.regs; - viewport.halfsize_x = float24::FromRaw(regs.viewport_size_x); - viewport.halfsize_y = float24::FromRaw(regs.viewport_size_y); - viewport.offset_x = float24::FromFloat32(static_cast<float>(regs.viewport_corner.x)); - viewport.offset_y = float24::FromFloat32(static_cast<float>(regs.viewport_corner.y)); + viewport.halfsize_x = float24::FromRaw(regs.rasterizer.viewport_size_x); + viewport.halfsize_y = float24::FromRaw(regs.rasterizer.viewport_size_y); + viewport.offset_x = float24::FromFloat32(static_cast<float>(regs.rasterizer.viewport_corner.x)); + viewport.offset_y = float24::FromFloat32(static_cast<float>(regs.rasterizer.viewport_corner.y)); float24 inv_w = float24::FromFloat32(1.f) / vtx.pos.w; vtx.color *= inv_w; @@ -91,8 +92,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 +124,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 +149,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/clipper.h b/src/video_core/swrasterizer/clipper.h index b51af0af9..b51af0af9 100644 --- a/src/video_core/clipper.h +++ b/src/video_core/swrasterizer/clipper.h diff --git a/src/video_core/swrasterizer/framebuffer.cpp b/src/video_core/swrasterizer/framebuffer.cpp new file mode 100644 index 000000000..7de3aac75 --- /dev/null +++ b/src/video_core/swrasterizer/framebuffer.cpp @@ -0,0 +1,358 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> + +#include "common/assert.h" +#include "common/color.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/vector_math.h" +#include "core/hw/gpu.h" +#include "core/memory.h" +#include "video_core/pica_state.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/swrasterizer/framebuffer.h" +#include "video_core/utils.h" + +namespace Pica { +namespace Rasterizer { + +void DrawPixel(int x, int y, const Math::Vec4<u8>& color) { + const auto& framebuffer = g_state.regs.framebuffer.framebuffer; + const PAddr addr = framebuffer.GetColorBufferPhysicalAddress(); + + // Similarly to textures, the render framebuffer is laid out from bottom to top, too. + // NOTE: The framebuffer height register contains the actual FB height minus one. + y = framebuffer.height - y; + + const u32 coarse_y = y & ~7; + u32 bytes_per_pixel = + GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(framebuffer.color_format.Value())); + u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + + coarse_y * framebuffer.width * bytes_per_pixel; + u8* dst_pixel = Memory::GetPhysicalPointer(addr) + dst_offset; + + switch (framebuffer.color_format) { + case FramebufferRegs::ColorFormat::RGBA8: + Color::EncodeRGBA8(color, dst_pixel); + break; + + case FramebufferRegs::ColorFormat::RGB8: + Color::EncodeRGB8(color, dst_pixel); + break; + + case FramebufferRegs::ColorFormat::RGB5A1: + Color::EncodeRGB5A1(color, dst_pixel); + break; + + case FramebufferRegs::ColorFormat::RGB565: + Color::EncodeRGB565(color, dst_pixel); + break; + + case FramebufferRegs::ColorFormat::RGBA4: + Color::EncodeRGBA4(color, dst_pixel); + break; + + default: + LOG_CRITICAL(Render_Software, "Unknown framebuffer color format %x", + framebuffer.color_format.Value()); + UNIMPLEMENTED(); + } +} + +const Math::Vec4<u8> GetPixel(int x, int y) { + const auto& framebuffer = g_state.regs.framebuffer.framebuffer; + const PAddr addr = framebuffer.GetColorBufferPhysicalAddress(); + + y = framebuffer.height - y; + + const u32 coarse_y = y & ~7; + u32 bytes_per_pixel = + GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(framebuffer.color_format.Value())); + u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + + coarse_y * framebuffer.width * bytes_per_pixel; + u8* src_pixel = Memory::GetPhysicalPointer(addr) + src_offset; + + switch (framebuffer.color_format) { + case FramebufferRegs::ColorFormat::RGBA8: + return Color::DecodeRGBA8(src_pixel); + + case FramebufferRegs::ColorFormat::RGB8: + return Color::DecodeRGB8(src_pixel); + + case FramebufferRegs::ColorFormat::RGB5A1: + return Color::DecodeRGB5A1(src_pixel); + + case FramebufferRegs::ColorFormat::RGB565: + return Color::DecodeRGB565(src_pixel); + + case FramebufferRegs::ColorFormat::RGBA4: + return Color::DecodeRGBA4(src_pixel); + + default: + LOG_CRITICAL(Render_Software, "Unknown framebuffer color format %x", + framebuffer.color_format.Value()); + UNIMPLEMENTED(); + } + + return {0, 0, 0, 0}; +} + +u32 GetDepth(int x, int y) { + const auto& framebuffer = g_state.regs.framebuffer.framebuffer; + const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress(); + u8* depth_buffer = Memory::GetPhysicalPointer(addr); + + y = framebuffer.height - y; + + const u32 coarse_y = y & ~7; + u32 bytes_per_pixel = FramebufferRegs::BytesPerDepthPixel(framebuffer.depth_format); + u32 stride = framebuffer.width * bytes_per_pixel; + + u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; + u8* src_pixel = depth_buffer + src_offset; + + switch (framebuffer.depth_format) { + case FramebufferRegs::DepthFormat::D16: + return Color::DecodeD16(src_pixel); + case FramebufferRegs::DepthFormat::D24: + return Color::DecodeD24(src_pixel); + case FramebufferRegs::DepthFormat::D24S8: + return Color::DecodeD24S8(src_pixel).x; + default: + LOG_CRITICAL(HW_GPU, "Unimplemented depth format %u", framebuffer.depth_format); + UNIMPLEMENTED(); + return 0; + } +} + +u8 GetStencil(int x, int y) { + const auto& framebuffer = g_state.regs.framebuffer.framebuffer; + const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress(); + u8* depth_buffer = Memory::GetPhysicalPointer(addr); + + y = framebuffer.height - y; + + const u32 coarse_y = y & ~7; + u32 bytes_per_pixel = Pica::FramebufferRegs::BytesPerDepthPixel(framebuffer.depth_format); + u32 stride = framebuffer.width * bytes_per_pixel; + + u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; + u8* src_pixel = depth_buffer + src_offset; + + switch (framebuffer.depth_format) { + case FramebufferRegs::DepthFormat::D24S8: + return Color::DecodeD24S8(src_pixel).y; + + default: + LOG_WARNING( + HW_GPU, + "GetStencil called for function which doesn't have a stencil component (format %u)", + framebuffer.depth_format); + return 0; + } +} + +void SetDepth(int x, int y, u32 value) { + const auto& framebuffer = g_state.regs.framebuffer.framebuffer; + const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress(); + u8* depth_buffer = Memory::GetPhysicalPointer(addr); + + y = framebuffer.height - y; + + const u32 coarse_y = y & ~7; + u32 bytes_per_pixel = FramebufferRegs::BytesPerDepthPixel(framebuffer.depth_format); + u32 stride = framebuffer.width * bytes_per_pixel; + + u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; + u8* dst_pixel = depth_buffer + dst_offset; + + switch (framebuffer.depth_format) { + case FramebufferRegs::DepthFormat::D16: + Color::EncodeD16(value, dst_pixel); + break; + + case FramebufferRegs::DepthFormat::D24: + Color::EncodeD24(value, dst_pixel); + break; + + case FramebufferRegs::DepthFormat::D24S8: + Color::EncodeD24X8(value, dst_pixel); + break; + + default: + LOG_CRITICAL(HW_GPU, "Unimplemented depth format %u", framebuffer.depth_format); + UNIMPLEMENTED(); + break; + } +} + +void SetStencil(int x, int y, u8 value) { + const auto& framebuffer = g_state.regs.framebuffer.framebuffer; + const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress(); + u8* depth_buffer = Memory::GetPhysicalPointer(addr); + + y = framebuffer.height - y; + + const u32 coarse_y = y & ~7; + u32 bytes_per_pixel = Pica::FramebufferRegs::BytesPerDepthPixel(framebuffer.depth_format); + u32 stride = framebuffer.width * bytes_per_pixel; + + u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; + u8* dst_pixel = depth_buffer + dst_offset; + + switch (framebuffer.depth_format) { + case Pica::FramebufferRegs::DepthFormat::D16: + case Pica::FramebufferRegs::DepthFormat::D24: + // Nothing to do + break; + + case Pica::FramebufferRegs::DepthFormat::D24S8: + Color::EncodeX24S8(value, dst_pixel); + break; + + default: + LOG_CRITICAL(HW_GPU, "Unimplemented depth format %u", framebuffer.depth_format); + UNIMPLEMENTED(); + break; + } +} + +u8 PerformStencilAction(FramebufferRegs::StencilAction action, u8 old_stencil, u8 ref) { + switch (action) { + case FramebufferRegs::StencilAction::Keep: + return old_stencil; + + case FramebufferRegs::StencilAction::Zero: + return 0; + + case FramebufferRegs::StencilAction::Replace: + return ref; + + case FramebufferRegs::StencilAction::Increment: + // Saturated increment + return std::min<u8>(old_stencil, 254) + 1; + + case FramebufferRegs::StencilAction::Decrement: + // Saturated decrement + return std::max<u8>(old_stencil, 1) - 1; + + case FramebufferRegs::StencilAction::Invert: + return ~old_stencil; + + case FramebufferRegs::StencilAction::IncrementWrap: + return old_stencil + 1; + + case FramebufferRegs::StencilAction::DecrementWrap: + return old_stencil - 1; + + default: + LOG_CRITICAL(HW_GPU, "Unknown stencil action %x", (int)action); + UNIMPLEMENTED(); + return 0; + } +} + +Math::Vec4<u8> EvaluateBlendEquation(const Math::Vec4<u8>& src, const Math::Vec4<u8>& srcfactor, + const Math::Vec4<u8>& dest, const Math::Vec4<u8>& destfactor, + FramebufferRegs::BlendEquation equation) { + Math::Vec4<int> result; + + auto src_result = (src * srcfactor).Cast<int>(); + auto dst_result = (dest * destfactor).Cast<int>(); + + switch (equation) { + case FramebufferRegs::BlendEquation::Add: + result = (src_result + dst_result) / 255; + break; + + case FramebufferRegs::BlendEquation::Subtract: + result = (src_result - dst_result) / 255; + break; + + case FramebufferRegs::BlendEquation::ReverseSubtract: + result = (dst_result - src_result) / 255; + break; + + // TODO: How do these two actually work? OpenGL doesn't include the blend factors in the + // min/max computations, but is this what the 3DS actually does? + case FramebufferRegs::BlendEquation::Min: + result.r() = std::min(src.r(), dest.r()); + result.g() = std::min(src.g(), dest.g()); + result.b() = std::min(src.b(), dest.b()); + result.a() = std::min(src.a(), dest.a()); + break; + + case FramebufferRegs::BlendEquation::Max: + result.r() = std::max(src.r(), dest.r()); + result.g() = std::max(src.g(), dest.g()); + result.b() = std::max(src.b(), dest.b()); + result.a() = std::max(src.a(), dest.a()); + break; + + default: + LOG_CRITICAL(HW_GPU, "Unknown RGB blend equation %x", equation); + UNIMPLEMENTED(); + } + + return Math::Vec4<u8>(MathUtil::Clamp(result.r(), 0, 255), MathUtil::Clamp(result.g(), 0, 255), + MathUtil::Clamp(result.b(), 0, 255), MathUtil::Clamp(result.a(), 0, 255)); +}; + +u8 LogicOp(u8 src, u8 dest, FramebufferRegs::LogicOp op) { + switch (op) { + case FramebufferRegs::LogicOp::Clear: + return 0; + + case FramebufferRegs::LogicOp::And: + return src & dest; + + case FramebufferRegs::LogicOp::AndReverse: + return src & ~dest; + + case FramebufferRegs::LogicOp::Copy: + return src; + + case FramebufferRegs::LogicOp::Set: + return 255; + + case FramebufferRegs::LogicOp::CopyInverted: + return ~src; + + case FramebufferRegs::LogicOp::NoOp: + return dest; + + case FramebufferRegs::LogicOp::Invert: + return ~dest; + + case FramebufferRegs::LogicOp::Nand: + return ~(src & dest); + + case FramebufferRegs::LogicOp::Or: + return src | dest; + + case FramebufferRegs::LogicOp::Nor: + return ~(src | dest); + + case FramebufferRegs::LogicOp::Xor: + return src ^ dest; + + case FramebufferRegs::LogicOp::Equiv: + return ~(src ^ dest); + + case FramebufferRegs::LogicOp::AndInverted: + return ~src & dest; + + case FramebufferRegs::LogicOp::OrReverse: + return src | ~dest; + + case FramebufferRegs::LogicOp::OrInverted: + return ~src | dest; + } +}; + +} // namespace Rasterizer +} // namespace Pica diff --git a/src/video_core/swrasterizer/framebuffer.h b/src/video_core/swrasterizer/framebuffer.h new file mode 100644 index 000000000..4a32a4979 --- /dev/null +++ b/src/video_core/swrasterizer/framebuffer.h @@ -0,0 +1,29 @@ +// 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/regs_framebuffer.h" + +namespace Pica { +namespace Rasterizer { + +void DrawPixel(int x, int y, const Math::Vec4<u8>& color); +const Math::Vec4<u8> GetPixel(int x, int y); +u32 GetDepth(int x, int y); +u8 GetStencil(int x, int y); +void SetDepth(int x, int y, u32 value); +void SetStencil(int x, int y, u8 value); +u8 PerformStencilAction(FramebufferRegs::StencilAction action, u8 old_stencil, u8 ref); + +Math::Vec4<u8> EvaluateBlendEquation(const Math::Vec4<u8>& src, const Math::Vec4<u8>& srcfactor, + const Math::Vec4<u8>& dest, const Math::Vec4<u8>& destfactor, + FramebufferRegs::BlendEquation equation); + +u8 LogicOp(u8 src, u8 dest, FramebufferRegs::LogicOp op); + +} // namespace Rasterizer +} // namespace Pica diff --git a/src/video_core/rasterizer.cpp b/src/video_core/swrasterizer/rasterizer.cpp index b9f5d4533..7557fcb89 100644 --- a/src/video_core/rasterizer.cpp +++ b/src/video_core/swrasterizer/rasterizer.cpp @@ -16,253 +16,21 @@ #include "core/hw/gpu.h" #include "core/memory.h" #include "video_core/debug_utils/debug_utils.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" #include "video_core/pica_types.h" -#include "video_core/rasterizer.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_texturing.h" #include "video_core/shader/shader.h" +#include "video_core/swrasterizer/framebuffer.h" +#include "video_core/swrasterizer/rasterizer.h" +#include "video_core/swrasterizer/texturing.h" +#include "video_core/texture/texture_decode.h" #include "video_core/utils.h" namespace Pica { - namespace Rasterizer { -static void DrawPixel(int x, int y, const Math::Vec4<u8>& color) { - const auto& framebuffer = g_state.regs.framebuffer; - const PAddr addr = framebuffer.GetColorBufferPhysicalAddress(); - - // Similarly to textures, the render framebuffer is laid out from bottom to top, too. - // NOTE: The framebuffer height register contains the actual FB height minus one. - y = framebuffer.height - y; - - const u32 coarse_y = y & ~7; - u32 bytes_per_pixel = - GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(framebuffer.color_format.Value())); - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + - coarse_y * framebuffer.width * bytes_per_pixel; - u8* dst_pixel = Memory::GetPhysicalPointer(addr) + dst_offset; - - switch (framebuffer.color_format) { - case Regs::ColorFormat::RGBA8: - Color::EncodeRGBA8(color, dst_pixel); - break; - - case Regs::ColorFormat::RGB8: - Color::EncodeRGB8(color, dst_pixel); - break; - - case Regs::ColorFormat::RGB5A1: - Color::EncodeRGB5A1(color, dst_pixel); - break; - - case Regs::ColorFormat::RGB565: - Color::EncodeRGB565(color, dst_pixel); - break; - - case Regs::ColorFormat::RGBA4: - Color::EncodeRGBA4(color, dst_pixel); - break; - - default: - LOG_CRITICAL(Render_Software, "Unknown framebuffer color format %x", - framebuffer.color_format.Value()); - UNIMPLEMENTED(); - } -} - -static const Math::Vec4<u8> GetPixel(int x, int y) { - const auto& framebuffer = g_state.regs.framebuffer; - const PAddr addr = framebuffer.GetColorBufferPhysicalAddress(); - - y = framebuffer.height - y; - - const u32 coarse_y = y & ~7; - u32 bytes_per_pixel = - GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(framebuffer.color_format.Value())); - u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + - coarse_y * framebuffer.width * bytes_per_pixel; - u8* src_pixel = Memory::GetPhysicalPointer(addr) + src_offset; - - switch (framebuffer.color_format) { - case Regs::ColorFormat::RGBA8: - return Color::DecodeRGBA8(src_pixel); - - case Regs::ColorFormat::RGB8: - return Color::DecodeRGB8(src_pixel); - - case Regs::ColorFormat::RGB5A1: - return Color::DecodeRGB5A1(src_pixel); - - case Regs::ColorFormat::RGB565: - return Color::DecodeRGB565(src_pixel); - - case Regs::ColorFormat::RGBA4: - return Color::DecodeRGBA4(src_pixel); - - default: - LOG_CRITICAL(Render_Software, "Unknown framebuffer color format %x", - framebuffer.color_format.Value()); - UNIMPLEMENTED(); - } - - return {0, 0, 0, 0}; -} - -static u32 GetDepth(int x, int y) { - const auto& framebuffer = g_state.regs.framebuffer; - const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress(); - u8* depth_buffer = Memory::GetPhysicalPointer(addr); - - y = framebuffer.height - y; - - const u32 coarse_y = y & ~7; - u32 bytes_per_pixel = Regs::BytesPerDepthPixel(framebuffer.depth_format); - u32 stride = framebuffer.width * bytes_per_pixel; - - u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; - u8* src_pixel = depth_buffer + src_offset; - - switch (framebuffer.depth_format) { - case Regs::DepthFormat::D16: - return Color::DecodeD16(src_pixel); - case Regs::DepthFormat::D24: - return Color::DecodeD24(src_pixel); - case Regs::DepthFormat::D24S8: - return Color::DecodeD24S8(src_pixel).x; - default: - LOG_CRITICAL(HW_GPU, "Unimplemented depth format %u", framebuffer.depth_format); - UNIMPLEMENTED(); - return 0; - } -} - -static u8 GetStencil(int x, int y) { - const auto& framebuffer = g_state.regs.framebuffer; - const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress(); - u8* depth_buffer = Memory::GetPhysicalPointer(addr); - - y = framebuffer.height - y; - - const u32 coarse_y = y & ~7; - u32 bytes_per_pixel = Pica::Regs::BytesPerDepthPixel(framebuffer.depth_format); - u32 stride = framebuffer.width * bytes_per_pixel; - - u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; - u8* src_pixel = depth_buffer + src_offset; - - switch (framebuffer.depth_format) { - case Regs::DepthFormat::D24S8: - return Color::DecodeD24S8(src_pixel).y; - - default: - LOG_WARNING( - HW_GPU, - "GetStencil called for function which doesn't have a stencil component (format %u)", - framebuffer.depth_format); - return 0; - } -} - -static void SetDepth(int x, int y, u32 value) { - const auto& framebuffer = g_state.regs.framebuffer; - const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress(); - u8* depth_buffer = Memory::GetPhysicalPointer(addr); - - y = framebuffer.height - y; - - const u32 coarse_y = y & ~7; - u32 bytes_per_pixel = Regs::BytesPerDepthPixel(framebuffer.depth_format); - u32 stride = framebuffer.width * bytes_per_pixel; - - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; - u8* dst_pixel = depth_buffer + dst_offset; - - switch (framebuffer.depth_format) { - case Regs::DepthFormat::D16: - Color::EncodeD16(value, dst_pixel); - break; - - case Regs::DepthFormat::D24: - Color::EncodeD24(value, dst_pixel); - break; - - case Regs::DepthFormat::D24S8: - Color::EncodeD24X8(value, dst_pixel); - break; - - default: - LOG_CRITICAL(HW_GPU, "Unimplemented depth format %u", framebuffer.depth_format); - UNIMPLEMENTED(); - break; - } -} - -static void SetStencil(int x, int y, u8 value) { - const auto& framebuffer = g_state.regs.framebuffer; - const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress(); - u8* depth_buffer = Memory::GetPhysicalPointer(addr); - - y = framebuffer.height - y; - - const u32 coarse_y = y & ~7; - u32 bytes_per_pixel = Pica::Regs::BytesPerDepthPixel(framebuffer.depth_format); - u32 stride = framebuffer.width * bytes_per_pixel; - - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; - u8* dst_pixel = depth_buffer + dst_offset; - - switch (framebuffer.depth_format) { - case Pica::Regs::DepthFormat::D16: - case Pica::Regs::DepthFormat::D24: - // Nothing to do - break; - - case Pica::Regs::DepthFormat::D24S8: - Color::EncodeX24S8(value, dst_pixel); - break; - - default: - LOG_CRITICAL(HW_GPU, "Unimplemented depth format %u", framebuffer.depth_format); - UNIMPLEMENTED(); - break; - } -} - -static u8 PerformStencilAction(Regs::StencilAction action, u8 old_stencil, u8 ref) { - switch (action) { - case Regs::StencilAction::Keep: - return old_stencil; - - case Regs::StencilAction::Zero: - return 0; - - case Regs::StencilAction::Replace: - return ref; - - case Regs::StencilAction::Increment: - // Saturated increment - return std::min<u8>(old_stencil, 254) + 1; - - case Regs::StencilAction::Decrement: - // Saturated decrement - return std::max<u8>(old_stencil, 1) - 1; - - case Regs::StencilAction::Invert: - return ~old_stencil; - - case Regs::StencilAction::IncrementWrap: - return old_stencil + 1; - - case Regs::StencilAction::DecrementWrap: - return old_stencil - 1; - - default: - LOG_CRITICAL(HW_GPU, "Unknown stencil action %x", (int)action); - UNIMPLEMENTED(); - return 0; - } -} - // NOTE: Assuming that rasterizer coordinates are 12.4 fixed-point values struct Fix12P4 { Fix12P4() {} @@ -307,8 +75,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); @@ -326,14 +94,14 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader ScreenToRasterizerCoordinates(v1.screenpos), ScreenToRasterizerCoordinates(v2.screenpos)}; - if (regs.cull_mode == Regs::CullMode::KeepAll) { + if (regs.rasterizer.cull_mode == RasterizerRegs::CullMode::KeepAll) { // Make sure we always end up with a triangle wound counter-clockwise if (!reversed && SignedArea(vtxpos[0].xy(), vtxpos[1].xy(), vtxpos[2].xy()) <= 0) { ProcessTriangleInternal(v0, v2, v1, true); return; } } else { - if (!reversed && regs.cull_mode == Regs::CullMode::KeepClockWise) { + if (!reversed && regs.rasterizer.cull_mode == RasterizerRegs::CullMode::KeepClockWise) { // Reverse vertex order and use the CCW code path. ProcessTriangleInternal(v0, v2, v1, true); return; @@ -350,13 +118,13 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader u16 max_y = std::max({vtxpos[0].y, vtxpos[1].y, vtxpos[2].y}); // Convert the scissor box coordinates to 12.4 fixed point - u16 scissor_x1 = (u16)(regs.scissor_test.x1 << 4); - u16 scissor_y1 = (u16)(regs.scissor_test.y1 << 4); + u16 scissor_x1 = (u16)(regs.rasterizer.scissor_test.x1 << 4); + u16 scissor_y1 = (u16)(regs.rasterizer.scissor_test.y1 << 4); // x2,y2 have +1 added to cover the entire sub-pixel area - u16 scissor_x2 = (u16)((regs.scissor_test.x2 + 1) << 4); - u16 scissor_y2 = (u16)((regs.scissor_test.y2 + 1) << 4); + u16 scissor_x2 = (u16)((regs.rasterizer.scissor_test.x2 + 1) << 4); + u16 scissor_y2 = (u16)((regs.rasterizer.scissor_test.y2 + 1) << 4); - if (regs.scissor_test.mode == Regs::ScissorMode::Include) { + if (regs.rasterizer.scissor_test.mode == RasterizerRegs::ScissorMode::Include) { // Calculate the new bounds min_x = std::max(min_x, scissor_x1); min_y = std::max(min_y, scissor_y1); @@ -396,12 +164,13 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader auto w_inverse = Math::MakeVec(v0.pos.w, v1.pos.w, v2.pos.w); - auto textures = regs.GetTextures(); - auto tev_stages = regs.GetTevStages(); + auto textures = regs.texturing.GetTextures(); + auto tev_stages = regs.texturing.GetTevStages(); - bool stencil_action_enable = g_state.regs.output_merger.stencil_test.enable && - g_state.regs.framebuffer.depth_format == Regs::DepthFormat::D24S8; - const auto stencil_test = g_state.regs.output_merger.stencil_test; + bool stencil_action_enable = + g_state.regs.framebuffer.output_merger.stencil_test.enable && + g_state.regs.framebuffer.framebuffer.depth_format == FramebufferRegs::DepthFormat::D24S8; + const auto stencil_test = g_state.regs.framebuffer.output_merger.stencil_test; // Enter rasterization loop, starting at the center of the topleft bounding box corner. // TODO: Not sure if looping through x first might be faster @@ -410,7 +179,7 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader // Do not process the pixel if it's inside the scissor box and the scissor mode is set // to Exclude - if (regs.scissor_test.mode == Regs::ScissorMode::Exclude) { + if (regs.rasterizer.scissor_test.mode == RasterizerRegs::ScissorMode::Exclude) { if (x >= scissor_x1 && x < scissor_x2 && y >= scissor_y1 && y < scissor_y2) continue; } @@ -440,12 +209,14 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader // Not fully accurate. About 3 bits in precision are missing. // Z-Buffer (z / w * scale + offset) - float depth_scale = float24::FromRaw(regs.viewport_depth_range).ToFloat32(); - float depth_offset = float24::FromRaw(regs.viewport_depth_near_plane).ToFloat32(); + float depth_scale = float24::FromRaw(regs.rasterizer.viewport_depth_range).ToFloat32(); + float depth_offset = + float24::FromRaw(regs.rasterizer.viewport_depth_near_plane).ToFloat32(); float depth = interpolated_z_over_w * depth_scale + depth_offset; // Potentially switch to W-Buffer - if (regs.depthmap_enable == Pica::Regs::DepthBuffering::WBuffering) { + if (regs.rasterizer.depthmap_enable == + Pica::RasterizerRegs::DepthBuffering::WBuffering) { // W-Buffer (z * scale + w * offset = (z / w * scale + offset) * w) depth *= interpolated_w_inverse.ToFloat32() * wsum; } @@ -512,9 +283,9 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader // TODO: Refactor so cubemaps and shadowmaps can be handled if (i == 0) { switch (texture.config.type) { - case Regs::TextureConfig::Texture2D: + case TexturingRegs::TextureConfig::Texture2D: break; - case Regs::TextureConfig::Projection2D: { + case TexturingRegs::TextureConfig::Projection2D: { auto tc0_w = GetInterpolatedAttribute(v0.tc0_w, v1.tc0_w, v2.tc0_w); u /= tc0_w; v /= tc0_w; @@ -533,37 +304,9 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader int t = (int)(v * float24::FromFloat32(static_cast<float>(texture.config.height))) .ToFloat32(); - static auto GetWrappedTexCoord = [](Regs::TextureConfig::WrapMode mode, int val, - unsigned size) { - switch (mode) { - case Regs::TextureConfig::ClampToEdge: - val = std::max(val, 0); - val = std::min(val, (int)size - 1); - return val; - - case Regs::TextureConfig::ClampToBorder: - return val; - - case Regs::TextureConfig::Repeat: - return (int)((unsigned)val % size); - - case Regs::TextureConfig::MirroredRepeat: { - unsigned int coord = ((unsigned)val % (2 * size)); - if (coord >= size) - coord = 2 * size - 1 - coord; - return (int)coord; - } - - default: - LOG_ERROR(HW_GPU, "Unknown texture coordinate wrapping mode %x", (int)mode); - UNIMPLEMENTED(); - return 0; - } - }; - - if ((texture.config.wrap_s == Regs::TextureConfig::ClampToBorder && + if ((texture.config.wrap_s == TexturingRegs::TextureConfig::ClampToBorder && (s < 0 || static_cast<u32>(s) >= texture.config.width)) || - (texture.config.wrap_t == Regs::TextureConfig::ClampToBorder && + (texture.config.wrap_t == TexturingRegs::TextureConfig::ClampToBorder && (t < 0 || static_cast<u32>(t) >= texture.config.height))) { auto border_color = texture.config.border_color; texture_color[i] = {border_color.r, border_color.g, border_color.b, @@ -579,10 +322,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 @@ -599,17 +342,16 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader Math::Vec4<u8> combiner_output; Math::Vec4<u8> combiner_buffer = {0, 0, 0, 0}; Math::Vec4<u8> next_combiner_buffer = { - regs.tev_combiner_buffer_color.r, regs.tev_combiner_buffer_color.g, - regs.tev_combiner_buffer_color.b, regs.tev_combiner_buffer_color.a, + regs.texturing.tev_combiner_buffer_color.r, + regs.texturing.tev_combiner_buffer_color.g, + regs.texturing.tev_combiner_buffer_color.b, + regs.texturing.tev_combiner_buffer_color.a, }; for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size(); ++tev_stage_index) { const auto& tev_stage = tev_stages[tev_stage_index]; - using Source = Regs::TevStageConfig::Source; - using ColorModifier = Regs::TevStageConfig::ColorModifier; - using AlphaModifier = Regs::TevStageConfig::AlphaModifier; - using Operation = Regs::TevStageConfig::Operation; + using Source = TexturingRegs::TevStageConfig::Source; auto GetSource = [&](Source source) -> Math::Vec4<u8> { switch (source) { @@ -649,187 +391,6 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader } }; - static auto GetColorModifier = [](ColorModifier factor, - const Math::Vec4<u8>& values) -> Math::Vec3<u8> { - switch (factor) { - case ColorModifier::SourceColor: - return values.rgb(); - - case ColorModifier::OneMinusSourceColor: - return (Math::Vec3<u8>(255, 255, 255) - values.rgb()).Cast<u8>(); - - case ColorModifier::SourceAlpha: - return values.aaa(); - - case ColorModifier::OneMinusSourceAlpha: - return (Math::Vec3<u8>(255, 255, 255) - values.aaa()).Cast<u8>(); - - case ColorModifier::SourceRed: - return values.rrr(); - - case ColorModifier::OneMinusSourceRed: - return (Math::Vec3<u8>(255, 255, 255) - values.rrr()).Cast<u8>(); - - case ColorModifier::SourceGreen: - return values.ggg(); - - case ColorModifier::OneMinusSourceGreen: - return (Math::Vec3<u8>(255, 255, 255) - values.ggg()).Cast<u8>(); - - case ColorModifier::SourceBlue: - return values.bbb(); - - case ColorModifier::OneMinusSourceBlue: - return (Math::Vec3<u8>(255, 255, 255) - values.bbb()).Cast<u8>(); - } - }; - - static auto GetAlphaModifier = [](AlphaModifier factor, - const Math::Vec4<u8>& values) -> u8 { - switch (factor) { - case AlphaModifier::SourceAlpha: - return values.a(); - - case AlphaModifier::OneMinusSourceAlpha: - return 255 - values.a(); - - case AlphaModifier::SourceRed: - return values.r(); - - case AlphaModifier::OneMinusSourceRed: - return 255 - values.r(); - - case AlphaModifier::SourceGreen: - return values.g(); - - case AlphaModifier::OneMinusSourceGreen: - return 255 - values.g(); - - case AlphaModifier::SourceBlue: - return values.b(); - - case AlphaModifier::OneMinusSourceBlue: - return 255 - values.b(); - } - }; - - static auto ColorCombine = [](Operation op, - const Math::Vec3<u8> input[3]) -> Math::Vec3<u8> { - switch (op) { - case Operation::Replace: - return input[0]; - - case Operation::Modulate: - return ((input[0] * input[1]) / 255).Cast<u8>(); - - case Operation::Add: { - auto result = input[0] + input[1]; - result.r() = std::min(255, result.r()); - result.g() = std::min(255, result.g()); - result.b() = std::min(255, result.b()); - return result.Cast<u8>(); - } - - case Operation::AddSigned: { - // TODO(bunnei): Verify that the color conversion from (float) 0.5f to - // (byte) 128 is correct - auto result = input[0].Cast<int>() + input[1].Cast<int>() - - Math::MakeVec<int>(128, 128, 128); - result.r() = MathUtil::Clamp<int>(result.r(), 0, 255); - result.g() = MathUtil::Clamp<int>(result.g(), 0, 255); - result.b() = MathUtil::Clamp<int>(result.b(), 0, 255); - return result.Cast<u8>(); - } - - case Operation::Lerp: - return ((input[0] * input[2] + - input[1] * - (Math::MakeVec<u8>(255, 255, 255) - input[2]).Cast<u8>()) / - 255) - .Cast<u8>(); - - case Operation::Subtract: { - auto result = input[0].Cast<int>() - input[1].Cast<int>(); - result.r() = std::max(0, result.r()); - result.g() = std::max(0, result.g()); - result.b() = std::max(0, result.b()); - return result.Cast<u8>(); - } - - case Operation::MultiplyThenAdd: { - auto result = (input[0] * input[1] + 255 * input[2].Cast<int>()) / 255; - result.r() = std::min(255, result.r()); - result.g() = std::min(255, result.g()); - result.b() = std::min(255, result.b()); - return result.Cast<u8>(); - } - - case Operation::AddThenMultiply: { - auto result = input[0] + input[1]; - result.r() = std::min(255, result.r()); - result.g() = std::min(255, result.g()); - result.b() = std::min(255, result.b()); - result = (result * input[2].Cast<int>()) / 255; - return result.Cast<u8>(); - } - case Operation::Dot3_RGB: { - // Not fully accurate. - // Worst case scenario seems to yield a +/-3 error - // Some HW results indicate that the per-component computation can't have a - // higher precision than 1/256, - // while dot3_rgb( (0x80,g0,b0),(0x7F,g1,b1) ) and dot3_rgb( - // (0x80,g0,b0),(0x80,g1,b1) ) give different results - int result = - ((input[0].r() * 2 - 255) * (input[1].r() * 2 - 255) + 128) / 256 + - ((input[0].g() * 2 - 255) * (input[1].g() * 2 - 255) + 128) / 256 + - ((input[0].b() * 2 - 255) * (input[1].b() * 2 - 255) + 128) / 256; - result = std::max(0, std::min(255, result)); - return {(u8)result, (u8)result, (u8)result}; - } - default: - LOG_ERROR(HW_GPU, "Unknown color combiner operation %d", (int)op); - UNIMPLEMENTED(); - return {0, 0, 0}; - } - }; - - static auto AlphaCombine = [](Operation op, const std::array<u8, 3>& input) -> u8 { - switch (op) { - case Operation::Replace: - return input[0]; - - case Operation::Modulate: - return input[0] * input[1] / 255; - - case Operation::Add: - return std::min(255, input[0] + input[1]); - - case Operation::AddSigned: { - // TODO(bunnei): Verify that the color conversion from (float) 0.5f to - // (byte) 128 is correct - auto result = static_cast<int>(input[0]) + static_cast<int>(input[1]) - 128; - return static_cast<u8>(MathUtil::Clamp<int>(result, 0, 255)); - } - - case Operation::Lerp: - return (input[0] * input[2] + input[1] * (255 - input[2])) / 255; - - case Operation::Subtract: - return std::max(0, (int)input[0] - (int)input[1]); - - case Operation::MultiplyThenAdd: - return std::min(255, (input[0] * input[1] + 255 * input[2]) / 255); - - case Operation::AddThenMultiply: - return (std::min(255, (input[0] + input[1])) * input[2]) / 255; - - default: - LOG_ERROR(HW_GPU, "Unknown alpha combiner operation %d", (int)op); - UNIMPLEMENTED(); - return 0; - } - }; - // color combiner // NOTE: Not sure if the alpha combiner might use the color output of the previous // stage as input. Hence, we currently don't directly write the result to @@ -861,54 +422,54 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader combiner_buffer = next_combiner_buffer; - if (regs.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferColor( + if (regs.texturing.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferColor( tev_stage_index)) { next_combiner_buffer.r() = combiner_output.r(); next_combiner_buffer.g() = combiner_output.g(); next_combiner_buffer.b() = combiner_output.b(); } - if (regs.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferAlpha( + if (regs.texturing.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferAlpha( tev_stage_index)) { next_combiner_buffer.a() = combiner_output.a(); } } - const auto& output_merger = regs.output_merger; + const auto& output_merger = regs.framebuffer.output_merger; // TODO: Does alpha testing happen before or after stencil? if (output_merger.alpha_test.enable) { bool pass = false; switch (output_merger.alpha_test.func) { - case Regs::CompareFunc::Never: + case FramebufferRegs::CompareFunc::Never: pass = false; break; - case Regs::CompareFunc::Always: + case FramebufferRegs::CompareFunc::Always: pass = true; break; - case Regs::CompareFunc::Equal: + case FramebufferRegs::CompareFunc::Equal: pass = combiner_output.a() == output_merger.alpha_test.ref; break; - case Regs::CompareFunc::NotEqual: + case FramebufferRegs::CompareFunc::NotEqual: pass = combiner_output.a() != output_merger.alpha_test.ref; break; - case Regs::CompareFunc::LessThan: + case FramebufferRegs::CompareFunc::LessThan: pass = combiner_output.a() < output_merger.alpha_test.ref; break; - case Regs::CompareFunc::LessThanOrEqual: + case FramebufferRegs::CompareFunc::LessThanOrEqual: pass = combiner_output.a() <= output_merger.alpha_test.ref; break; - case Regs::CompareFunc::GreaterThan: + case FramebufferRegs::CompareFunc::GreaterThan: pass = combiner_output.a() > output_merger.alpha_test.ref; break; - case Regs::CompareFunc::GreaterThanOrEqual: + case FramebufferRegs::CompareFunc::GreaterThanOrEqual: pass = combiner_output.a() >= output_merger.alpha_test.ref; break; } @@ -921,16 +482,16 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader // Not fully accurate. We'd have to know what data type is used to // store the depth etc. Using float for now until we know more // about Pica datatypes - if (regs.fog_mode == Regs::FogMode::Fog) { + if (regs.texturing.fog_mode == TexturingRegs::FogMode::Fog) { const Math::Vec3<u8> fog_color = { - static_cast<u8>(regs.fog_color.r.Value()), - static_cast<u8>(regs.fog_color.g.Value()), - static_cast<u8>(regs.fog_color.b.Value()), + static_cast<u8>(regs.texturing.fog_color.r.Value()), + static_cast<u8>(regs.texturing.fog_color.g.Value()), + static_cast<u8>(regs.texturing.fog_color.b.Value()), }; // Get index into fog LUT float fog_index; - if (g_state.regs.fog_flip) { + if (g_state.regs.texturing.fog_flip) { fog_index = (1.0f - depth) * 128.0f; } else { fog_index = depth * 128.0f; @@ -954,10 +515,10 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader u8 old_stencil = 0; auto UpdateStencil = [stencil_test, x, y, - &old_stencil](Pica::Regs::StencilAction action) { + &old_stencil](Pica::FramebufferRegs::StencilAction action) { u8 new_stencil = PerformStencilAction(action, old_stencil, stencil_test.reference_value); - if (g_state.regs.framebuffer.allow_depth_stencil_write != 0) + if (g_state.regs.framebuffer.framebuffer.allow_depth_stencil_write != 0) SetStencil(x >> 4, y >> 4, (new_stencil & stencil_test.write_mask) | (old_stencil & ~stencil_test.write_mask)); }; @@ -969,35 +530,35 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader bool pass = false; switch (stencil_test.func) { - case Regs::CompareFunc::Never: + case FramebufferRegs::CompareFunc::Never: pass = false; break; - case Regs::CompareFunc::Always: + case FramebufferRegs::CompareFunc::Always: pass = true; break; - case Regs::CompareFunc::Equal: + case FramebufferRegs::CompareFunc::Equal: pass = (ref == dest); break; - case Regs::CompareFunc::NotEqual: + case FramebufferRegs::CompareFunc::NotEqual: pass = (ref != dest); break; - case Regs::CompareFunc::LessThan: + case FramebufferRegs::CompareFunc::LessThan: pass = (ref < dest); break; - case Regs::CompareFunc::LessThanOrEqual: + case FramebufferRegs::CompareFunc::LessThanOrEqual: pass = (ref <= dest); break; - case Regs::CompareFunc::GreaterThan: + case FramebufferRegs::CompareFunc::GreaterThan: pass = (ref > dest); break; - case Regs::CompareFunc::GreaterThanOrEqual: + case FramebufferRegs::CompareFunc::GreaterThanOrEqual: pass = (ref >= dest); break; } @@ -1009,7 +570,8 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader } // Convert float to integer - unsigned num_bits = Regs::DepthBitsPerPixel(regs.framebuffer.depth_format); + unsigned num_bits = + FramebufferRegs::DepthBitsPerPixel(regs.framebuffer.framebuffer.depth_format); u32 z = (u32)(depth * ((1 << num_bits) - 1)); if (output_merger.depth_test_enable) { @@ -1018,35 +580,35 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader bool pass = false; switch (output_merger.depth_test_func) { - case Regs::CompareFunc::Never: + case FramebufferRegs::CompareFunc::Never: pass = false; break; - case Regs::CompareFunc::Always: + case FramebufferRegs::CompareFunc::Always: pass = true; break; - case Regs::CompareFunc::Equal: + case FramebufferRegs::CompareFunc::Equal: pass = z == ref_z; break; - case Regs::CompareFunc::NotEqual: + case FramebufferRegs::CompareFunc::NotEqual: pass = z != ref_z; break; - case Regs::CompareFunc::LessThan: + case FramebufferRegs::CompareFunc::LessThan: pass = z < ref_z; break; - case Regs::CompareFunc::LessThanOrEqual: + case FramebufferRegs::CompareFunc::LessThanOrEqual: pass = z <= ref_z; break; - case Regs::CompareFunc::GreaterThan: + case FramebufferRegs::CompareFunc::GreaterThan: pass = z > ref_z; break; - case Regs::CompareFunc::GreaterThanOrEqual: + case FramebufferRegs::CompareFunc::GreaterThanOrEqual: pass = z >= ref_z; break; } @@ -1058,8 +620,11 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader } } - if (regs.framebuffer.allow_depth_stencil_write != 0 && output_merger.depth_write_enable) + if (regs.framebuffer.framebuffer.allow_depth_stencil_write != 0 && + output_merger.depth_write_enable) { + SetDepth(x >> 4, y >> 4, z); + } // The stencil depth_pass action is executed even if depth testing is disabled if (stencil_action_enable) @@ -1071,7 +636,8 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader if (output_merger.alphablend_enable) { auto params = output_merger.alpha_blending; - auto LookupFactor = [&](unsigned channel, Regs::BlendFactor factor) -> u8 { + auto LookupFactor = [&](unsigned channel, + FramebufferRegs::BlendFactor factor) -> u8 { DEBUG_ASSERT(channel < 4); const Math::Vec4<u8> blend_const = { @@ -1082,49 +648,49 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader }; switch (factor) { - case Regs::BlendFactor::Zero: + case FramebufferRegs::BlendFactor::Zero: return 0; - case Regs::BlendFactor::One: + case FramebufferRegs::BlendFactor::One: return 255; - case Regs::BlendFactor::SourceColor: + case FramebufferRegs::BlendFactor::SourceColor: return combiner_output[channel]; - case Regs::BlendFactor::OneMinusSourceColor: + case FramebufferRegs::BlendFactor::OneMinusSourceColor: return 255 - combiner_output[channel]; - case Regs::BlendFactor::DestColor: + case FramebufferRegs::BlendFactor::DestColor: return dest[channel]; - case Regs::BlendFactor::OneMinusDestColor: + case FramebufferRegs::BlendFactor::OneMinusDestColor: return 255 - dest[channel]; - case Regs::BlendFactor::SourceAlpha: + case FramebufferRegs::BlendFactor::SourceAlpha: return combiner_output.a(); - case Regs::BlendFactor::OneMinusSourceAlpha: + case FramebufferRegs::BlendFactor::OneMinusSourceAlpha: return 255 - combiner_output.a(); - case Regs::BlendFactor::DestAlpha: + case FramebufferRegs::BlendFactor::DestAlpha: return dest.a(); - case Regs::BlendFactor::OneMinusDestAlpha: + case FramebufferRegs::BlendFactor::OneMinusDestAlpha: return 255 - dest.a(); - case Regs::BlendFactor::ConstantColor: + case FramebufferRegs::BlendFactor::ConstantColor: return blend_const[channel]; - case Regs::BlendFactor::OneMinusConstantColor: + case FramebufferRegs::BlendFactor::OneMinusConstantColor: return 255 - blend_const[channel]; - case Regs::BlendFactor::ConstantAlpha: + case FramebufferRegs::BlendFactor::ConstantAlpha: return blend_const.a(); - case Regs::BlendFactor::OneMinusConstantAlpha: + case FramebufferRegs::BlendFactor::OneMinusConstantAlpha: return 255 - blend_const.a(); - case Regs::BlendFactor::SourceAlphaSaturate: + case FramebufferRegs::BlendFactor::SourceAlphaSaturate: // Returns 1.0 for the alpha channel if (channel == 3) return 255; @@ -1139,55 +705,6 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader return combiner_output[channel]; }; - static auto EvaluateBlendEquation = []( - const Math::Vec4<u8>& src, const Math::Vec4<u8>& srcfactor, - const Math::Vec4<u8>& dest, const Math::Vec4<u8>& destfactor, - Regs::BlendEquation equation) { - Math::Vec4<int> result; - - auto src_result = (src * srcfactor).Cast<int>(); - auto dst_result = (dest * destfactor).Cast<int>(); - - switch (equation) { - case Regs::BlendEquation::Add: - result = (src_result + dst_result) / 255; - break; - - case Regs::BlendEquation::Subtract: - result = (src_result - dst_result) / 255; - break; - - case Regs::BlendEquation::ReverseSubtract: - result = (dst_result - src_result) / 255; - break; - - // TODO: How do these two actually work? - // OpenGL doesn't include the blend factors in the min/max computations, - // but is this what the 3DS actually does? - case Regs::BlendEquation::Min: - result.r() = std::min(src.r(), dest.r()); - result.g() = std::min(src.g(), dest.g()); - result.b() = std::min(src.b(), dest.b()); - result.a() = std::min(src.a(), dest.a()); - break; - - case Regs::BlendEquation::Max: - result.r() = std::max(src.r(), dest.r()); - result.g() = std::max(src.g(), dest.g()); - result.b() = std::max(src.b(), dest.b()); - result.a() = std::max(src.a(), dest.a()); - break; - - default: - LOG_CRITICAL(HW_GPU, "Unknown RGB blend equation %x", equation); - UNIMPLEMENTED(); - } - - return Math::Vec4<u8>( - MathUtil::Clamp(result.r(), 0, 255), MathUtil::Clamp(result.g(), 0, 255), - MathUtil::Clamp(result.b(), 0, 255), MathUtil::Clamp(result.a(), 0, 255)); - }; - auto srcfactor = Math::MakeVec(LookupFactor(0, params.factor_source_rgb), LookupFactor(1, params.factor_source_rgb), LookupFactor(2, params.factor_source_rgb), @@ -1204,58 +721,6 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader dstfactor, params.blend_equation_a) .a(); } else { - static auto LogicOp = [](u8 src, u8 dest, Regs::LogicOp op) -> u8 { - switch (op) { - case Regs::LogicOp::Clear: - return 0; - - case Regs::LogicOp::And: - return src & dest; - - case Regs::LogicOp::AndReverse: - return src & ~dest; - - case Regs::LogicOp::Copy: - return src; - - case Regs::LogicOp::Set: - return 255; - - case Regs::LogicOp::CopyInverted: - return ~src; - - case Regs::LogicOp::NoOp: - return dest; - - case Regs::LogicOp::Invert: - return ~dest; - - case Regs::LogicOp::Nand: - return ~(src & dest); - - case Regs::LogicOp::Or: - return src | dest; - - case Regs::LogicOp::Nor: - return ~(src | dest); - - case Regs::LogicOp::Xor: - return src ^ dest; - - case Regs::LogicOp::Equiv: - return ~(src ^ dest); - - case Regs::LogicOp::AndInverted: - return ~src & dest; - - case Regs::LogicOp::OrReverse: - return src | ~dest; - - case Regs::LogicOp::OrInverted: - return ~src | dest; - } - }; - blend_output = Math::MakeVec(LogicOp(combiner_output.r(), dest.r(), output_merger.logic_op), LogicOp(combiner_output.g(), dest.g(), output_merger.logic_op), @@ -1270,14 +735,13 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader output_merger.alpha_enable ? blend_output.a() : dest.a(), }; - if (regs.framebuffer.allow_color_write != 0) + if (regs.framebuffer.framebuffer.allow_color_write != 0) DrawPixel(x >> 4, y >> 4, result); } } } -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/swrasterizer/rasterizer.h b/src/video_core/swrasterizer/rasterizer.h new file mode 100644 index 000000000..3a72ac343 --- /dev/null +++ b/src/video_core/swrasterizer/rasterizer.h @@ -0,0 +1,48 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "video_core/shader/shader.h" + +namespace Pica { + +namespace Rasterizer { + +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 + +} // namespace Pica diff --git a/src/video_core/swrasterizer.cpp b/src/video_core/swrasterizer/swrasterizer.cpp index 9cd21f72b..402b705dd 100644 --- a/src/video_core/swrasterizer.cpp +++ b/src/video_core/swrasterizer/swrasterizer.cpp @@ -2,8 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "video_core/clipper.h" -#include "video_core/swrasterizer.h" +#include "video_core/swrasterizer/clipper.h" +#include "video_core/swrasterizer/swrasterizer.h" namespace VideoCore { diff --git a/src/video_core/swrasterizer.h b/src/video_core/swrasterizer/swrasterizer.h index 6d42d7409..6d42d7409 100644 --- a/src/video_core/swrasterizer.h +++ b/src/video_core/swrasterizer/swrasterizer.h diff --git a/src/video_core/swrasterizer/texturing.cpp b/src/video_core/swrasterizer/texturing.cpp new file mode 100644 index 000000000..eb18e4ba4 --- /dev/null +++ b/src/video_core/swrasterizer/texturing.cpp @@ -0,0 +1,228 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> + +#include "common/assert.h" +#include "common/common_types.h" +#include "common/math_util.h" +#include "common/vector_math.h" +#include "video_core/regs_texturing.h" +#include "video_core/swrasterizer/texturing.h" + +namespace Pica { +namespace Rasterizer { + +using TevStageConfig = TexturingRegs::TevStageConfig; + +int GetWrappedTexCoord(TexturingRegs::TextureConfig::WrapMode mode, int val, unsigned size) { + switch (mode) { + case TexturingRegs::TextureConfig::ClampToEdge: + val = std::max(val, 0); + val = std::min(val, (int)size - 1); + return val; + + case TexturingRegs::TextureConfig::ClampToBorder: + return val; + + case TexturingRegs::TextureConfig::Repeat: + return (int)((unsigned)val % size); + + case TexturingRegs::TextureConfig::MirroredRepeat: { + unsigned int coord = ((unsigned)val % (2 * size)); + if (coord >= size) + coord = 2 * size - 1 - coord; + return (int)coord; + } + + default: + LOG_ERROR(HW_GPU, "Unknown texture coordinate wrapping mode %x", (int)mode); + UNIMPLEMENTED(); + return 0; + } +}; + +Math::Vec3<u8> GetColorModifier(TevStageConfig::ColorModifier factor, + const Math::Vec4<u8>& values) { + using ColorModifier = TevStageConfig::ColorModifier; + + switch (factor) { + case ColorModifier::SourceColor: + return values.rgb(); + + case ColorModifier::OneMinusSourceColor: + return (Math::Vec3<u8>(255, 255, 255) - values.rgb()).Cast<u8>(); + + case ColorModifier::SourceAlpha: + return values.aaa(); + + case ColorModifier::OneMinusSourceAlpha: + return (Math::Vec3<u8>(255, 255, 255) - values.aaa()).Cast<u8>(); + + case ColorModifier::SourceRed: + return values.rrr(); + + case ColorModifier::OneMinusSourceRed: + return (Math::Vec3<u8>(255, 255, 255) - values.rrr()).Cast<u8>(); + + case ColorModifier::SourceGreen: + return values.ggg(); + + case ColorModifier::OneMinusSourceGreen: + return (Math::Vec3<u8>(255, 255, 255) - values.ggg()).Cast<u8>(); + + case ColorModifier::SourceBlue: + return values.bbb(); + + case ColorModifier::OneMinusSourceBlue: + return (Math::Vec3<u8>(255, 255, 255) - values.bbb()).Cast<u8>(); + } +}; + +u8 GetAlphaModifier(TevStageConfig::AlphaModifier factor, const Math::Vec4<u8>& values) { + using AlphaModifier = TevStageConfig::AlphaModifier; + + switch (factor) { + case AlphaModifier::SourceAlpha: + return values.a(); + + case AlphaModifier::OneMinusSourceAlpha: + return 255 - values.a(); + + case AlphaModifier::SourceRed: + return values.r(); + + case AlphaModifier::OneMinusSourceRed: + return 255 - values.r(); + + case AlphaModifier::SourceGreen: + return values.g(); + + case AlphaModifier::OneMinusSourceGreen: + return 255 - values.g(); + + case AlphaModifier::SourceBlue: + return values.b(); + + case AlphaModifier::OneMinusSourceBlue: + return 255 - values.b(); + } +}; + +Math::Vec3<u8> ColorCombine(TevStageConfig::Operation op, const Math::Vec3<u8> input[3]) { + using Operation = TevStageConfig::Operation; + + switch (op) { + case Operation::Replace: + return input[0]; + + case Operation::Modulate: + return ((input[0] * input[1]) / 255).Cast<u8>(); + + case Operation::Add: { + auto result = input[0] + input[1]; + result.r() = std::min(255, result.r()); + result.g() = std::min(255, result.g()); + result.b() = std::min(255, result.b()); + return result.Cast<u8>(); + } + + case Operation::AddSigned: { + // TODO(bunnei): Verify that the color conversion from (float) 0.5f to + // (byte) 128 is correct + auto result = + input[0].Cast<int>() + input[1].Cast<int>() - Math::MakeVec<int>(128, 128, 128); + result.r() = MathUtil::Clamp<int>(result.r(), 0, 255); + result.g() = MathUtil::Clamp<int>(result.g(), 0, 255); + result.b() = MathUtil::Clamp<int>(result.b(), 0, 255); + return result.Cast<u8>(); + } + + case Operation::Lerp: + return ((input[0] * input[2] + + input[1] * (Math::MakeVec<u8>(255, 255, 255) - input[2]).Cast<u8>()) / + 255) + .Cast<u8>(); + + case Operation::Subtract: { + auto result = input[0].Cast<int>() - input[1].Cast<int>(); + result.r() = std::max(0, result.r()); + result.g() = std::max(0, result.g()); + result.b() = std::max(0, result.b()); + return result.Cast<u8>(); + } + + case Operation::MultiplyThenAdd: { + auto result = (input[0] * input[1] + 255 * input[2].Cast<int>()) / 255; + result.r() = std::min(255, result.r()); + result.g() = std::min(255, result.g()); + result.b() = std::min(255, result.b()); + return result.Cast<u8>(); + } + + case Operation::AddThenMultiply: { + auto result = input[0] + input[1]; + result.r() = std::min(255, result.r()); + result.g() = std::min(255, result.g()); + result.b() = std::min(255, result.b()); + result = (result * input[2].Cast<int>()) / 255; + return result.Cast<u8>(); + } + case Operation::Dot3_RGB: { + // Not fully accurate. Worst case scenario seems to yield a +/-3 error. Some HW results + // indicate that the per-component computation can't have a higher precision than 1/256, + // while dot3_rgb((0x80,g0,b0), (0x7F,g1,b1)) and dot3_rgb((0x80,g0,b0), (0x80,g1,b1)) give + // different results. + int result = ((input[0].r() * 2 - 255) * (input[1].r() * 2 - 255) + 128) / 256 + + ((input[0].g() * 2 - 255) * (input[1].g() * 2 - 255) + 128) / 256 + + ((input[0].b() * 2 - 255) * (input[1].b() * 2 - 255) + 128) / 256; + result = std::max(0, std::min(255, result)); + return {(u8)result, (u8)result, (u8)result}; + } + default: + LOG_ERROR(HW_GPU, "Unknown color combiner operation %d", (int)op); + UNIMPLEMENTED(); + return {0, 0, 0}; + } +}; + +u8 AlphaCombine(TevStageConfig::Operation op, const std::array<u8, 3>& input) { + switch (op) { + using Operation = TevStageConfig::Operation; + case Operation::Replace: + return input[0]; + + case Operation::Modulate: + return input[0] * input[1] / 255; + + case Operation::Add: + return std::min(255, input[0] + input[1]); + + case Operation::AddSigned: { + // TODO(bunnei): Verify that the color conversion from (float) 0.5f to (byte) 128 is correct + auto result = static_cast<int>(input[0]) + static_cast<int>(input[1]) - 128; + return static_cast<u8>(MathUtil::Clamp<int>(result, 0, 255)); + } + + case Operation::Lerp: + return (input[0] * input[2] + input[1] * (255 - input[2])) / 255; + + case Operation::Subtract: + return std::max(0, (int)input[0] - (int)input[1]); + + case Operation::MultiplyThenAdd: + return std::min(255, (input[0] * input[1] + 255 * input[2]) / 255); + + case Operation::AddThenMultiply: + return (std::min(255, (input[0] + input[1])) * input[2]) / 255; + + default: + LOG_ERROR(HW_GPU, "Unknown alpha combiner operation %d", (int)op); + UNIMPLEMENTED(); + return 0; + } +}; + +} // namespace Rasterizer +} // namespace Pica diff --git a/src/video_core/swrasterizer/texturing.h b/src/video_core/swrasterizer/texturing.h new file mode 100644 index 000000000..24f74a5a3 --- /dev/null +++ b/src/video_core/swrasterizer/texturing.h @@ -0,0 +1,28 @@ +// 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/regs_texturing.h" + +namespace Pica { +namespace Rasterizer { + +int GetWrappedTexCoord(TexturingRegs::TextureConfig::WrapMode mode, int val, unsigned size); + +Math::Vec3<u8> GetColorModifier(TexturingRegs::TevStageConfig::ColorModifier factor, + const Math::Vec4<u8>& values); + +u8 GetAlphaModifier(TexturingRegs::TevStageConfig::AlphaModifier factor, + const Math::Vec4<u8>& values); + +Math::Vec3<u8> ColorCombine(TexturingRegs::TevStageConfig::Operation op, + const Math::Vec3<u8> input[3]); + +u8 AlphaCombine(TexturingRegs::TevStageConfig::Operation op, const std::array<u8, 3>& input); + +} // namespace Rasterizer +} // namespace Pica diff --git a/src/video_core/texture/etc1.cpp b/src/video_core/texture/etc1.cpp new file mode 100644 index 000000000..43f7f56db --- /dev/null +++ b/src/video_core/texture/etc1.cpp @@ -0,0 +1,122 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#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<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..0818d652c --- /dev/null +++ b/src/video_core/texture/texture_decode.cpp @@ -0,0 +1,227 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#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/regs_texturing.h" +#include "video_core/texture/etc1.h" +#include "video_core/texture/texture_decode.h" +#include "video_core/utils.h" + +using TextureFormat = Pica::TexturingRegs::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 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 TextureFormat::RGB8: { + auto res = Color::DecodeRGB8(source + MortonInterleave(x, y) * 3); + return {res.r(), res.g(), res.b(), 255}; + } + + case 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 TextureFormat::RGB565: { + auto res = Color::DecodeRGB565(source + MortonInterleave(x, y) * 2); + return {res.r(), res.g(), res.b(), 255}; + } + + case 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 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 TextureFormat::RG8: { + auto res = Color::DecodeRG8(source + MortonInterleave(x, y) * 2); + return {res.r(), res.g(), 0, 255}; + } + + case TextureFormat::I8: { + const u8* source_ptr = source + MortonInterleave(x, y); + return {*source_ptr, *source_ptr, *source_ptr, 255}; + } + + case 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 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 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 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 TextureFormat::ETC1: + case TextureFormat::ETC1A4: { + bool has_alpha = (info.format == 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 TexturingRegs::TextureConfig& config, + const TexturingRegs::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..8507cfeb8 --- /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/regs_texturing.h" + +namespace Pica { +namespace Texture { + +/// Returns the byte size of a 8*8 tile of the specified texture format. +size_t CalculateTileSize(TexturingRegs::TextureFormat format); + +struct TextureInfo { + PAddr physical_address; + unsigned int width; + unsigned int height; + ptrdiff_t stride; + TexturingRegs::TextureFormat format; + + static TextureInfo FromPicaRegister(const TexturingRegs::TextureConfig& config, + const TexturingRegs::TextureFormat& format); + + /// Calculates stride from format and width, assuming that the entire texture is contiguous. + void SetDefaultStride() { + stride = 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..37c5224a9 100644 --- a/src/video_core/vertex_loader.cpp +++ b/src/video_core/vertex_loader.cpp @@ -8,15 +8,15 @@ #include "common/vector_math.h" #include "core/memory.h" #include "video_core/debug_utils/debug_utils.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" #include "video_core/pica_types.h" +#include "video_core/regs_pipeline.h" #include "video_core/shader/shader.h" #include "video_core/vertex_loader.h" namespace Pica { -void VertexLoader::Setup(const Pica::Regs& regs) { +void VertexLoader::Setup(const PipelineRegs& regs) { ASSERT_MSG(!is_setup, "VertexLoader is not intended to be setup more than once."); const auto& attribute_config = regs.vertex_attributes; @@ -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."); @@ -84,15 +85,16 @@ void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::I memory_accesses.AddAccess( source_addr, vertex_attribute_elements[i] * - ((vertex_attribute_formats[i] == Regs::VertexAttributeFormat::FLOAT) + ((vertex_attribute_formats[i] == PipelineRegs::VertexAttributeFormat::FLOAT) ? 4 - : (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::SHORT) + : (vertex_attribute_formats[i] == + PipelineRegs::VertexAttributeFormat::SHORT) ? 2 : 1)); } switch (vertex_attribute_formats[i]) { - case Regs::VertexAttributeFormat::BYTE: { + case PipelineRegs::VertexAttributeFormat::BYTE: { const s8* srcdata = reinterpret_cast<const s8*>(Memory::GetPhysicalPointer(source_addr)); for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { @@ -100,7 +102,7 @@ void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::I } break; } - case Regs::VertexAttributeFormat::UBYTE: { + case PipelineRegs::VertexAttributeFormat::UBYTE: { const u8* srcdata = reinterpret_cast<const u8*>(Memory::GetPhysicalPointer(source_addr)); for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { @@ -108,7 +110,7 @@ void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::I } break; } - case Regs::VertexAttributeFormat::SHORT: { + case PipelineRegs::VertexAttributeFormat::SHORT: { const s16* srcdata = reinterpret_cast<const s16*>(Memory::GetPhysicalPointer(source_addr)); for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { @@ -116,7 +118,7 @@ void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::I } break; } - case Regs::VertexAttributeFormat::FLOAT: { + case PipelineRegs::VertexAttributeFormat::FLOAT: { const float* srcdata = reinterpret_cast<const float*>(Memory::GetPhysicalPointer(source_addr)); for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { @@ -142,7 +144,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..02db10aee 100644 --- a/src/video_core/vertex_loader.h +++ b/src/video_core/vertex_loader.h @@ -2,7 +2,7 @@ #include <array> #include "common/common_types.h" -#include "video_core/pica.h" +#include "video_core/regs_pipeline.h" namespace Pica { @@ -11,18 +11,18 @@ class MemoryAccessTracker; } namespace Shader { -struct InputVertex; +struct AttributeBuffer; } class VertexLoader { public: VertexLoader() = default; - explicit VertexLoader(const Pica::Regs& regs) { + explicit VertexLoader(const PipelineRegs& regs) { Setup(regs); } - void Setup(const Pica::Regs& regs); - void LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input, + void Setup(const PipelineRegs& regs); + void LoadVertex(u32 base_address, int index, int vertex, Shader::AttributeBuffer& input, DebugUtils::MemoryAccessTracker& memory_accesses); int GetNumTotalAttributes() const { @@ -32,7 +32,7 @@ public: private: std::array<u32, 16> vertex_attribute_sources; std::array<u32, 16> vertex_attribute_strides{}; - std::array<Regs::VertexAttributeFormat, 16> vertex_attribute_formats; + std::array<PipelineRegs::VertexAttributeFormat, 16> vertex_attribute_formats; std::array<u32, 16> vertex_attribute_elements{}; std::array<bool, 16> vertex_attribute_is_default; int num_total_attributes = 0; |