Merge branch 'master' into CompatibiltyReporting
This commit is contained in:
commit
c3afd73592
75 changed files with 1527 additions and 745 deletions
|
@ -23,7 +23,7 @@ add_library(audio_core STATIC
|
|||
time_stretch.cpp
|
||||
time_stretch.h
|
||||
|
||||
$<$<BOOL:SDL2_FOUND>:sdl2_sink.cpp sdl2_sink.h>
|
||||
$<$<BOOL:${SDL2_FOUND}>:sdl2_sink.cpp sdl2_sink.h>
|
||||
)
|
||||
|
||||
create_target_directory_groups(audio_core)
|
||||
|
|
|
@ -11,10 +11,8 @@
|
|||
// This needs to be included before getopt.h because the latter #defines symbols used by it
|
||||
#include "common/microprofile.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <getopt.h>
|
||||
#else
|
||||
#include <getopt.h>
|
||||
#ifndef _MSC_VER
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
|
@ -157,13 +155,14 @@ int main(int argc, char** argv) {
|
|||
errno = EINVAL;
|
||||
if (errno != 0)
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
case 'm': {
|
||||
use_multiplayer = true;
|
||||
std::string str_arg(optarg);
|
||||
const std::string str_arg(optarg);
|
||||
// regex to check if the format is nickname:password@ip:port
|
||||
// with optional :password
|
||||
std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
|
||||
const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
|
||||
if (!std::regex_match(str_arg, re)) {
|
||||
std::cout << "Wrong format for option --multiplayer\n";
|
||||
PrintHelp(argv[0]);
|
||||
|
@ -188,10 +187,6 @@ int main(int argc, char** argv) {
|
|||
std::cout << "Address to room must not be empty.\n";
|
||||
return 0;
|
||||
}
|
||||
if (port > 65535) {
|
||||
std::cout << "Port must be between 0 and 65535.\n";
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'h':
|
||||
|
|
|
@ -76,8 +76,9 @@ void Config::ReadValues() {
|
|||
Settings::values.analogs[i] = default_param;
|
||||
}
|
||||
|
||||
Settings::values.motion_device = sdl2_config->Get(
|
||||
"Controls", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01");
|
||||
Settings::values.motion_device =
|
||||
sdl2_config->Get("Controls", "motion_device",
|
||||
"engine:motion_emu,update_period:100,sensitivity:0.01,tilt_clamp:90.0");
|
||||
Settings::values.touch_device =
|
||||
sdl2_config->Get("Controls", "touch_device", "engine:emu_window");
|
||||
|
||||
|
|
|
@ -89,19 +89,19 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
|
|||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
|
||||
if (render_window == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 window! Exiting...");
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 window: %s", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
gl_context = SDL_GL_CreateContext(render_window);
|
||||
|
||||
if (gl_context == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! Exiting...");
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: %s", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize GL functions! Exiting...");
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize GL functions: %s", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
|
|
@ -90,12 +90,46 @@ file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*)
|
|||
|
||||
qt5_wrap_ui(UI_HDRS ${UIS})
|
||||
|
||||
if (ENABLE_QT_TRANSLATION)
|
||||
set(CITRA_QT_LANGUAGES "${CMAKE_SOURCE_DIR}/dist/languages" CACHE PATH "Path to the translation bundle for the Qt frontend")
|
||||
option(GENERATE_QT_TRANSLATION "Generate en.ts as the translation source file" OFF)
|
||||
|
||||
# Update source TS file if enabled
|
||||
if (GENERATE_QT_TRANSLATION)
|
||||
get_target_property(SRCS citra-qt SOURCES)
|
||||
qt5_create_translation(QM_FILES ${SRCS} ${UIS} ${CITRA_QT_LANGUAGES}/en.ts)
|
||||
add_custom_target(translation ALL DEPENDS ${CITRA_QT_LANGUAGES}/en.ts)
|
||||
endif()
|
||||
|
||||
# Find all TS files except en.ts
|
||||
file(GLOB_RECURSE LANGUAGES_TS ${CITRA_QT_LANGUAGES}/*.ts)
|
||||
list(REMOVE_ITEM LANGUAGES_TS ${CITRA_QT_LANGUAGES}/en.ts)
|
||||
|
||||
# Compile TS files to QM files
|
||||
qt5_add_translation(LANGUAGES_QM ${LANGUAGES_TS})
|
||||
|
||||
# Build a QRC file from the QM file list
|
||||
set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc)
|
||||
file(WRITE ${LANGUAGES_QRC} "<RCC><qresource prefix=\"languages\">\n")
|
||||
foreach (QM ${LANGUAGES_QM})
|
||||
get_filename_component(QM_FILE ${QM} NAME)
|
||||
file(APPEND ${LANGUAGES_QRC} "<file>${QM_FILE}</file>\n")
|
||||
endforeach (QM)
|
||||
file(APPEND ${LANGUAGES_QRC} "</qresource></RCC>")
|
||||
|
||||
# Add the QRC file to package in all QM files
|
||||
qt5_add_resources(LANGUAGES ${LANGUAGES_QRC})
|
||||
else()
|
||||
set(LANGUAGES)
|
||||
endif()
|
||||
|
||||
target_sources(citra-qt
|
||||
PRIVATE
|
||||
${ICONS}
|
||||
${THEMES}
|
||||
${UI_HDRS}
|
||||
${UIS}
|
||||
${LANGUAGES}
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
|
|
|
@ -36,6 +36,7 @@ void EmuThread::run() {
|
|||
|
||||
Core::System::ResultStatus result = Core::System::GetInstance().RunLoop();
|
||||
if (result != Core::System::ResultStatus::Success) {
|
||||
this->SetRunning(false);
|
||||
emit ErrorThrown(result, Core::System::GetInstance().GetStatusDetails());
|
||||
}
|
||||
|
||||
|
|
|
@ -58,7 +58,9 @@ void Config::ReadValues() {
|
|||
}
|
||||
|
||||
Settings::values.motion_device =
|
||||
qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01")
|
||||
qt_config
|
||||
->value("motion_device",
|
||||
"engine:motion_emu,update_period:100,sensitivity:0.01,tilt_clamp:90.0")
|
||||
.toString()
|
||||
.toStdString();
|
||||
Settings::values.touch_device =
|
||||
|
@ -182,6 +184,7 @@ void Config::ReadValues() {
|
|||
UISettings::values.gamedir = qt_config->value("gameListRootDir", ".").toString();
|
||||
UISettings::values.gamedir_deepscan = qt_config->value("gameListDeepScan", false).toBool();
|
||||
UISettings::values.recent_files = qt_config->value("recentFiles").toStringList();
|
||||
UISettings::values.language = qt_config->value("language", "").toString();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Shortcuts");
|
||||
|
@ -333,6 +336,7 @@ void Config::SaveValues() {
|
|||
qt_config->setValue("gameListRootDir", UISettings::values.gamedir);
|
||||
qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan);
|
||||
qt_config->setValue("recentFiles", UISettings::values.recent_files);
|
||||
qt_config->setValue("language", UISettings::values.language);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Shortcuts");
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>740</width>
|
||||
<width>461</width>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
|
@ -34,15 +34,15 @@
|
|||
<string>Input</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="ConfigureGraphics" name="graphicsTab">
|
||||
<attribute name="title">
|
||||
<string>Graphics</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="ConfigureGraphics" name="graphicsTab">
|
||||
<attribute name="title">
|
||||
<string>Graphics</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="ConfigureAudio" name="audioTab">
|
||||
<attribute name="title">
|
||||
<string>Audio</string>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
<string>Audio</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="ConfigureDebug" name="debugTab">
|
||||
<attribute name="title">
|
||||
|
|
|
@ -76,3 +76,7 @@ void ConfigureAudio::updateAudioDevices(int sink_index) {
|
|||
ui->audio_device_combo_box->addItem(device.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureAudio::retranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ public:
|
|||
~ConfigureAudio();
|
||||
|
||||
void applyConfiguration();
|
||||
void retranslateUi();
|
||||
|
||||
public slots:
|
||||
void updateAudioDevices(int sink_index);
|
||||
|
|
|
@ -24,3 +24,7 @@ void ConfigureDebug::applyConfiguration() {
|
|||
Settings::values.gdbstub_port = ui->gdbport_spinbox->value();
|
||||
Settings::Apply();
|
||||
}
|
||||
|
||||
void ConfigureDebug::retranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ public:
|
|||
~ConfigureDebug();
|
||||
|
||||
void applyConfiguration();
|
||||
void retranslateUi();
|
||||
|
||||
private:
|
||||
void setConfiguration();
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
ConfigureDialog::ConfigureDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ConfigureDialog) {
|
||||
ui->setupUi(this);
|
||||
this->setConfiguration();
|
||||
connect(ui->generalTab, &ConfigureGeneral::languageChanged, this,
|
||||
&ConfigureDialog::onLanguageChanged);
|
||||
}
|
||||
|
||||
ConfigureDialog::~ConfigureDialog() {}
|
||||
|
@ -26,3 +28,15 @@ void ConfigureDialog::applyConfiguration() {
|
|||
ui->webTab->applyConfiguration();
|
||||
Settings::Apply();
|
||||
}
|
||||
|
||||
void ConfigureDialog::onLanguageChanged(const QString& locale) {
|
||||
emit languageChanged(locale);
|
||||
ui->retranslateUi(this);
|
||||
ui->generalTab->retranslateUi();
|
||||
ui->systemTab->retranslateUi();
|
||||
ui->inputTab->retranslateUi();
|
||||
ui->graphicsTab->retranslateUi();
|
||||
ui->audioTab->retranslateUi();
|
||||
ui->debugTab->retranslateUi();
|
||||
ui->webTab->retranslateUi();
|
||||
}
|
||||
|
|
|
@ -20,6 +20,12 @@ public:
|
|||
|
||||
void applyConfiguration();
|
||||
|
||||
private slots:
|
||||
void onLanguageChanged(const QString& locale);
|
||||
|
||||
signals:
|
||||
void languageChanged(const QString& locale);
|
||||
|
||||
private:
|
||||
void setConfiguration();
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QDirIterator>
|
||||
#include "citra_qt/configuration/configure_general.h"
|
||||
#include "citra_qt/ui_settings.h"
|
||||
#include "core/core.h"
|
||||
|
@ -12,6 +13,23 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
|
|||
: QWidget(parent), ui(new Ui::ConfigureGeneral) {
|
||||
|
||||
ui->setupUi(this);
|
||||
ui->language_combobox->addItem(tr("<System>"), QString(""));
|
||||
ui->language_combobox->addItem(tr("English"), QString("en"));
|
||||
QDirIterator it(":/languages", QDirIterator::NoIteratorFlags);
|
||||
while (it.hasNext()) {
|
||||
QString locale = it.next();
|
||||
locale.truncate(locale.lastIndexOf('.'));
|
||||
locale.remove(0, locale.lastIndexOf('/') + 1);
|
||||
QString lang = QLocale::languageToString(QLocale(locale).language());
|
||||
ui->language_combobox->addItem(lang, locale);
|
||||
}
|
||||
|
||||
// Unlike other configuration changes, interface language changes need to be reflected on the
|
||||
// interface immediately. This is done by passing a signal to the main window, and then
|
||||
// retranslating when passing back.
|
||||
connect(ui->language_combobox,
|
||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||
&ConfigureGeneral::onLanguageChanged);
|
||||
|
||||
for (auto theme : UISettings::themes) {
|
||||
ui->theme_combobox->addItem(theme.first, theme.second);
|
||||
|
@ -37,6 +55,8 @@ void ConfigureGeneral::setConfiguration() {
|
|||
ui->region_combobox->setCurrentIndex(Settings::values.region_value + 1);
|
||||
|
||||
ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
|
||||
ui->language_combobox->setCurrentIndex(
|
||||
ui->language_combobox->findData(UISettings::values.language));
|
||||
}
|
||||
|
||||
void ConfigureGeneral::applyConfiguration() {
|
||||
|
@ -52,3 +72,14 @@ void ConfigureGeneral::applyConfiguration() {
|
|||
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
|
||||
Settings::Apply();
|
||||
}
|
||||
|
||||
void ConfigureGeneral::onLanguageChanged(int index) {
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
emit languageChanged(ui->language_combobox->itemData(index).toString());
|
||||
}
|
||||
|
||||
void ConfigureGeneral::retranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,13 @@ public:
|
|||
~ConfigureGeneral();
|
||||
|
||||
void applyConfiguration();
|
||||
void retranslateUi();
|
||||
|
||||
private slots:
|
||||
void onLanguageChanged(int index);
|
||||
|
||||
signals:
|
||||
void languageChanged(const QString& locale);
|
||||
|
||||
private:
|
||||
void setConfiguration();
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>300</width>
|
||||
<height>377</height>
|
||||
<width>345</width>
|
||||
<height>493</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -38,6 +38,20 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="language_label">
|
||||
<property name="text">
|
||||
<string>Interface language</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="language_combobox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
|
|
@ -113,3 +113,7 @@ void ConfigureGraphics::applyConfiguration() {
|
|||
Settings::values.swap_screen = ui->swap_screen->isChecked();
|
||||
Settings::Apply();
|
||||
}
|
||||
|
||||
void ConfigureGraphics::retranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ public:
|
|||
~ConfigureGraphics();
|
||||
|
||||
void applyConfiguration();
|
||||
void retranslateUi();
|
||||
|
||||
private:
|
||||
void setConfiguration();
|
||||
|
|
|
@ -277,3 +277,7 @@ void ConfigureInput::keyPressEvent(QKeyEvent* event) {
|
|||
}
|
||||
setPollingResult({}, true);
|
||||
}
|
||||
|
||||
void ConfigureInput::retranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ public:
|
|||
|
||||
/// Save all button configurations to settings file
|
||||
void applyConfiguration();
|
||||
void retranslateUi();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::ConfigureInput> ui;
|
||||
|
|
|
@ -167,3 +167,7 @@ void ConfigureSystem::refreshConsoleID() {
|
|||
Service::CFG::UpdateConfigNANDSavegame();
|
||||
ui->label_console_id->setText("Console ID: 0x" + QString::number(console_id, 16).toUpper());
|
||||
}
|
||||
|
||||
void ConfigureSystem::retranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ public:
|
|||
|
||||
void applyConfiguration();
|
||||
void setConfiguration();
|
||||
void retranslateUi();
|
||||
|
||||
public slots:
|
||||
void updateBirthdayComboBox(int birthmonth_index);
|
||||
|
|
|
@ -100,3 +100,7 @@ void ConfigureWeb::OnLoginVerified() {
|
|||
"correctly, and that your internet connection is working."));
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureWeb::retranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ public:
|
|||
~ConfigureWeb();
|
||||
|
||||
void applyConfiguration();
|
||||
void retranslateUi();
|
||||
|
||||
public slots:
|
||||
void RefreshTelemetryID();
|
||||
|
|
|
@ -98,6 +98,8 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
|
|||
// register size_t to use in slots and signals
|
||||
qRegisterMetaType<size_t>("size_t");
|
||||
|
||||
LoadTranslation();
|
||||
|
||||
Pica::g_debug_context = Pica::DebugContext::Construct();
|
||||
setAcceptDrops(true);
|
||||
ui.setupUi(this);
|
||||
|
@ -115,8 +117,8 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
|
|||
ConnectMenuEvents();
|
||||
ConnectWidgetEvents();
|
||||
|
||||
setWindowTitle(QString("Citra %1| %2-%3")
|
||||
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
|
||||
SetupUIStrings();
|
||||
|
||||
show();
|
||||
|
||||
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
|
||||
|
@ -892,6 +894,8 @@ void GMainWindow::ToggleWindowMode() {
|
|||
|
||||
void GMainWindow::OnConfigure() {
|
||||
ConfigureDialog configureDialog(this);
|
||||
connect(&configureDialog, &ConfigureDialog::languageChanged, this,
|
||||
&GMainWindow::OnLanguageChanged);
|
||||
auto result = configureDialog.exec();
|
||||
if (result == QDialog::Accepted) {
|
||||
configureDialog.applyConfiguration();
|
||||
|
@ -995,6 +999,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
|
|||
} else {
|
||||
// Only show the message if the game is still running.
|
||||
if (emu_thread) {
|
||||
emu_thread->SetRunning(true);
|
||||
message_label->setText(status_message);
|
||||
message_label->setVisible(true);
|
||||
}
|
||||
|
@ -1105,6 +1110,45 @@ void GMainWindow::UpdateUITheme() {
|
|||
}
|
||||
}
|
||||
|
||||
void GMainWindow::LoadTranslation() {
|
||||
// If the selected language is English, no need to install any translation
|
||||
if (UISettings::values.language == "en") {
|
||||
return;
|
||||
}
|
||||
|
||||
bool loaded;
|
||||
|
||||
if (UISettings::values.language.isEmpty()) {
|
||||
// If the selected language is empty, use system locale
|
||||
loaded = translator.load(QLocale(), "", "", ":/languages/");
|
||||
} else {
|
||||
// Otherwise load from the specified file
|
||||
loaded = translator.load(UISettings::values.language, ":/languages/");
|
||||
}
|
||||
|
||||
if (loaded) {
|
||||
qApp->installTranslator(&translator);
|
||||
} else {
|
||||
UISettings::values.language = "en";
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::OnLanguageChanged(const QString& locale) {
|
||||
if (UISettings::values.language != "en") {
|
||||
qApp->removeTranslator(&translator);
|
||||
}
|
||||
|
||||
UISettings::values.language = locale;
|
||||
LoadTranslation();
|
||||
ui.retranslateUi(this);
|
||||
SetupUIStrings();
|
||||
}
|
||||
|
||||
void GMainWindow::SetupUIStrings() {
|
||||
setWindowTitle(
|
||||
tr("Citra %1| %2-%3").arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
|
||||
}
|
||||
|
||||
#ifdef main
|
||||
#undef main
|
||||
#endif
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <memory>
|
||||
#include <QMainWindow>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "ui_main.h"
|
||||
|
@ -152,9 +153,12 @@ private slots:
|
|||
void OnUpdateFound(bool found, bool error);
|
||||
void OnCheckForUpdates();
|
||||
void OnOpenUpdater();
|
||||
void OnLanguageChanged(const QString& locale);
|
||||
|
||||
private:
|
||||
void UpdateStatusBar();
|
||||
void LoadTranslation();
|
||||
void SetupUIStrings();
|
||||
|
||||
Ui::MainWindow ui;
|
||||
|
||||
|
@ -193,6 +197,8 @@ private:
|
|||
|
||||
QAction* actions_recent_files[max_recent_files_item];
|
||||
|
||||
QTranslator translator;
|
||||
|
||||
protected:
|
||||
void dropEvent(QDropEvent* event) override;
|
||||
void dragEnterEvent(QDragEnterEvent* event) override;
|
||||
|
|
|
@ -48,6 +48,7 @@ struct Values {
|
|||
QString gamedir;
|
||||
bool gamedir_deepscan;
|
||||
QStringList recent_files;
|
||||
QString language;
|
||||
|
||||
QString theme;
|
||||
|
||||
|
|
|
@ -32,6 +32,8 @@ add_library(common STATIC
|
|||
break_points.cpp
|
||||
break_points.h
|
||||
chunk_file.h
|
||||
cityhash.cpp
|
||||
cityhash.h
|
||||
code_block.h
|
||||
color.h
|
||||
common_funcs.h
|
||||
|
@ -39,7 +41,6 @@ add_library(common STATIC
|
|||
common_types.h
|
||||
file_util.cpp
|
||||
file_util.h
|
||||
hash.cpp
|
||||
hash.h
|
||||
linear_disk_cache.h
|
||||
logging/backend.cpp
|
||||
|
|
340
src/common/cityhash.cpp
Normal file
340
src/common/cityhash.cpp
Normal file
|
@ -0,0 +1,340 @@
|
|||
// Copyright (c) 2011 Google, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
// CityHash, by Geoff Pike and Jyrki Alakuijala
|
||||
//
|
||||
// This file provides CityHash64() and related functions.
|
||||
//
|
||||
// It's probably possible to create even faster hash functions by
|
||||
// writing a program that systematically explores some of the space of
|
||||
// possible hash functions, by using SIMD instructions, or by
|
||||
// compromising on hash quality.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string.h> // for memcpy and memset
|
||||
#include "cityhash.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
// #include "config.h"
|
||||
#ifdef __GNUC__
|
||||
#define HAVE_BUILTIN_EXPECT 1
|
||||
#endif
|
||||
#ifdef COMMON_BIG_ENDIAN
|
||||
#define WORDS_BIGENDIAN 1
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
|
||||
typedef uint8_t uint8;
|
||||
typedef uint32_t uint32;
|
||||
typedef uint64_t uint64;
|
||||
|
||||
namespace Common {
|
||||
|
||||
static uint64 UNALIGNED_LOAD64(const char* p) {
|
||||
uint64 result;
|
||||
memcpy(&result, p, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint32 UNALIGNED_LOAD32(const char* p) {
|
||||
uint32 result;
|
||||
memcpy(&result, p, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
#define uint32_in_expected_order(x) (swap32(x))
|
||||
#define uint64_in_expected_order(x) (swap64(x))
|
||||
#else
|
||||
#define uint32_in_expected_order(x) (x)
|
||||
#define uint64_in_expected_order(x) (x)
|
||||
#endif
|
||||
|
||||
#if !defined(LIKELY)
|
||||
#if HAVE_BUILTIN_EXPECT
|
||||
#define LIKELY(x) (__builtin_expect(!!(x), 1))
|
||||
#else
|
||||
#define LIKELY(x) (x)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static uint64 Fetch64(const char* p) {
|
||||
return uint64_in_expected_order(UNALIGNED_LOAD64(p));
|
||||
}
|
||||
|
||||
static uint32 Fetch32(const char* p) {
|
||||
return uint32_in_expected_order(UNALIGNED_LOAD32(p));
|
||||
}
|
||||
|
||||
// Some primes between 2^63 and 2^64 for various uses.
|
||||
static const uint64 k0 = 0xc3a5c85c97cb3127ULL;
|
||||
static const uint64 k1 = 0xb492b66fbe98f273ULL;
|
||||
static const uint64 k2 = 0x9ae16a3b2f90404fULL;
|
||||
|
||||
// Bitwise right rotate. Normally this will compile to a single
|
||||
// instruction, especially if the shift is a manifest constant.
|
||||
static uint64 Rotate(uint64 val, int shift) {
|
||||
// Avoid shifting by 64: doing so yields an undefined result.
|
||||
return shift == 0 ? val : ((val >> shift) | (val << (64 - shift)));
|
||||
}
|
||||
|
||||
static uint64 ShiftMix(uint64 val) {
|
||||
return val ^ (val >> 47);
|
||||
}
|
||||
|
||||
static uint64 HashLen16(uint64 u, uint64 v) {
|
||||
return Hash128to64(uint128(u, v));
|
||||
}
|
||||
|
||||
static uint64 HashLen16(uint64 u, uint64 v, uint64 mul) {
|
||||
// Murmur-inspired hashing.
|
||||
uint64 a = (u ^ v) * mul;
|
||||
a ^= (a >> 47);
|
||||
uint64 b = (v ^ a) * mul;
|
||||
b ^= (b >> 47);
|
||||
b *= mul;
|
||||
return b;
|
||||
}
|
||||
|
||||
static uint64 HashLen0to16(const char* s, size_t len) {
|
||||
if (len >= 8) {
|
||||
uint64 mul = k2 + len * 2;
|
||||
uint64 a = Fetch64(s) + k2;
|
||||
uint64 b = Fetch64(s + len - 8);
|
||||
uint64 c = Rotate(b, 37) * mul + a;
|
||||
uint64 d = (Rotate(a, 25) + b) * mul;
|
||||
return HashLen16(c, d, mul);
|
||||
}
|
||||
if (len >= 4) {
|
||||
uint64 mul = k2 + len * 2;
|
||||
uint64 a = Fetch32(s);
|
||||
return HashLen16(len + (a << 3), Fetch32(s + len - 4), mul);
|
||||
}
|
||||
if (len > 0) {
|
||||
uint8 a = s[0];
|
||||
uint8 b = s[len >> 1];
|
||||
uint8 c = s[len - 1];
|
||||
uint32 y = static_cast<uint32>(a) + (static_cast<uint32>(b) << 8);
|
||||
uint32 z = static_cast<uint32>(len) + (static_cast<uint32>(c) << 2);
|
||||
return ShiftMix(y * k2 ^ z * k0) * k2;
|
||||
}
|
||||
return k2;
|
||||
}
|
||||
|
||||
// This probably works well for 16-byte strings as well, but it may be overkill
|
||||
// in that case.
|
||||
static uint64 HashLen17to32(const char* s, size_t len) {
|
||||
uint64 mul = k2 + len * 2;
|
||||
uint64 a = Fetch64(s) * k1;
|
||||
uint64 b = Fetch64(s + 8);
|
||||
uint64 c = Fetch64(s + len - 8) * mul;
|
||||
uint64 d = Fetch64(s + len - 16) * k2;
|
||||
return HashLen16(Rotate(a + b, 43) + Rotate(c, 30) + d, a + Rotate(b + k2, 18) + c, mul);
|
||||
}
|
||||
|
||||
// Return a 16-byte hash for 48 bytes. Quick and dirty.
|
||||
// Callers do best to use "random-looking" values for a and b.
|
||||
static pair<uint64, uint64> WeakHashLen32WithSeeds(uint64 w, uint64 x, uint64 y, uint64 z, uint64 a,
|
||||
uint64 b) {
|
||||
a += w;
|
||||
b = Rotate(b + a + z, 21);
|
||||
uint64 c = a;
|
||||
a += x;
|
||||
a += y;
|
||||
b += Rotate(a, 44);
|
||||
return make_pair(a + z, b + c);
|
||||
}
|
||||
|
||||
// Return a 16-byte hash for s[0] ... s[31], a, and b. Quick and dirty.
|
||||
static pair<uint64, uint64> WeakHashLen32WithSeeds(const char* s, uint64 a, uint64 b) {
|
||||
return WeakHashLen32WithSeeds(Fetch64(s), Fetch64(s + 8), Fetch64(s + 16), Fetch64(s + 24), a,
|
||||
b);
|
||||
}
|
||||
|
||||
// Return an 8-byte hash for 33 to 64 bytes.
|
||||
static uint64 HashLen33to64(const char* s, size_t len) {
|
||||
uint64 mul = k2 + len * 2;
|
||||
uint64 a = Fetch64(s) * k2;
|
||||
uint64 b = Fetch64(s + 8);
|
||||
uint64 c = Fetch64(s + len - 24);
|
||||
uint64 d = Fetch64(s + len - 32);
|
||||
uint64 e = Fetch64(s + 16) * k2;
|
||||
uint64 f = Fetch64(s + 24) * 9;
|
||||
uint64 g = Fetch64(s + len - 8);
|
||||
uint64 h = Fetch64(s + len - 16) * mul;
|
||||
uint64 u = Rotate(a + g, 43) + (Rotate(b, 30) + c) * 9;
|
||||
uint64 v = ((a + g) ^ d) + f + 1;
|
||||
uint64 w = swap64((u + v) * mul) + h;
|
||||
uint64 x = Rotate(e + f, 42) + c;
|
||||
uint64 y = (swap64((v + w) * mul) + g) * mul;
|
||||
uint64 z = e + f + c;
|
||||
a = swap64((x + z) * mul + y) + b;
|
||||
b = ShiftMix((z + a) * mul + d + h) * mul;
|
||||
return b + x;
|
||||
}
|
||||
|
||||
uint64 CityHash64(const char* s, size_t len) {
|
||||
if (len <= 32) {
|
||||
if (len <= 16) {
|
||||
return HashLen0to16(s, len);
|
||||
} else {
|
||||
return HashLen17to32(s, len);
|
||||
}
|
||||
} else if (len <= 64) {
|
||||
return HashLen33to64(s, len);
|
||||
}
|
||||
|
||||
// For strings over 64 bytes we hash the end first, and then as we
|
||||
// loop we keep 56 bytes of state: v, w, x, y, and z.
|
||||
uint64 x = Fetch64(s + len - 40);
|
||||
uint64 y = Fetch64(s + len - 16) + Fetch64(s + len - 56);
|
||||
uint64 z = HashLen16(Fetch64(s + len - 48) + len, Fetch64(s + len - 24));
|
||||
pair<uint64, uint64> v = WeakHashLen32WithSeeds(s + len - 64, len, z);
|
||||
pair<uint64, uint64> w = WeakHashLen32WithSeeds(s + len - 32, y + k1, x);
|
||||
x = x * k1 + Fetch64(s);
|
||||
|
||||
// Decrease len to the nearest multiple of 64, and operate on 64-byte chunks.
|
||||
len = (len - 1) & ~static_cast<size_t>(63);
|
||||
do {
|
||||
x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1;
|
||||
y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1;
|
||||
x ^= w.second;
|
||||
y += v.first + Fetch64(s + 40);
|
||||
z = Rotate(z + w.first, 33) * k1;
|
||||
v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first);
|
||||
w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16));
|
||||
std::swap(z, x);
|
||||
s += 64;
|
||||
len -= 64;
|
||||
} while (len != 0);
|
||||
return HashLen16(HashLen16(v.first, w.first) + ShiftMix(y) * k1 + z,
|
||||
HashLen16(v.second, w.second) + x);
|
||||
}
|
||||
|
||||
uint64 CityHash64WithSeed(const char* s, size_t len, uint64 seed) {
|
||||
return CityHash64WithSeeds(s, len, k2, seed);
|
||||
}
|
||||
|
||||
uint64 CityHash64WithSeeds(const char* s, size_t len, uint64 seed0, uint64 seed1) {
|
||||
return HashLen16(CityHash64(s, len) - seed0, seed1);
|
||||
}
|
||||
|
||||
// A subroutine for CityHash128(). Returns a decent 128-bit hash for strings
|
||||
// of any length representable in signed long. Based on City and Murmur.
|
||||
static uint128 CityMurmur(const char* s, size_t len, uint128 seed) {
|
||||
uint64 a = Uint128Low64(seed);
|
||||
uint64 b = Uint128High64(seed);
|
||||
uint64 c = 0;
|
||||
uint64 d = 0;
|
||||
signed long l = static_cast<long>(len) - 16;
|
||||
if (l <= 0) { // len <= 16
|
||||
a = ShiftMix(a * k1) * k1;
|
||||
c = b * k1 + HashLen0to16(s, len);
|
||||
d = ShiftMix(a + (len >= 8 ? Fetch64(s) : c));
|
||||
} else { // len > 16
|
||||
c = HashLen16(Fetch64(s + len - 8) + k1, a);
|
||||
d = HashLen16(b + len, c + Fetch64(s + len - 16));
|
||||
a += d;
|
||||
do {
|
||||
a ^= ShiftMix(Fetch64(s) * k1) * k1;
|
||||
a *= k1;
|
||||
b ^= a;
|
||||
c ^= ShiftMix(Fetch64(s + 8) * k1) * k1;
|
||||
c *= k1;
|
||||
d ^= c;
|
||||
s += 16;
|
||||
l -= 16;
|
||||
} while (l > 0);
|
||||
}
|
||||
a = HashLen16(a, c);
|
||||
b = HashLen16(d, b);
|
||||
return uint128(a ^ b, HashLen16(b, a));
|
||||
}
|
||||
|
||||
uint128 CityHash128WithSeed(const char* s, size_t len, uint128 seed) {
|
||||
if (len < 128) {
|
||||
return CityMurmur(s, len, seed);
|
||||
}
|
||||
|
||||
// We expect len >= 128 to be the common case. Keep 56 bytes of state:
|
||||
// v, w, x, y, and z.
|
||||
pair<uint64, uint64> v, w;
|
||||
uint64 x = Uint128Low64(seed);
|
||||
uint64 y = Uint128High64(seed);
|
||||
uint64 z = len * k1;
|
||||
v.first = Rotate(y ^ k1, 49) * k1 + Fetch64(s);
|
||||
v.second = Rotate(v.first, 42) * k1 + Fetch64(s + 8);
|
||||
w.first = Rotate(y + z, 35) * k1 + x;
|
||||
w.second = Rotate(x + Fetch64(s + 88), 53) * k1;
|
||||
|
||||
// This is the same inner loop as CityHash64(), manually unrolled.
|
||||
do {
|
||||
x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1;
|
||||
y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1;
|
||||
x ^= w.second;
|
||||
y += v.first + Fetch64(s + 40);
|
||||
z = Rotate(z + w.first, 33) * k1;
|
||||
v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first);
|
||||
w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16));
|
||||
std::swap(z, x);
|
||||
s += 64;
|
||||
x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1;
|
||||
y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1;
|
||||
x ^= w.second;
|
||||
y += v.first + Fetch64(s + 40);
|
||||
z = Rotate(z + w.first, 33) * k1;
|
||||
v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first);
|
||||
w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16));
|
||||
std::swap(z, x);
|
||||
s += 64;
|
||||
len -= 128;
|
||||
} while (LIKELY(len >= 128));
|
||||
x += Rotate(v.first + z, 49) * k0;
|
||||
y = y * k0 + Rotate(w.second, 37);
|
||||
z = z * k0 + Rotate(w.first, 27);
|
||||
w.first *= 9;
|
||||
v.first *= k0;
|
||||
// If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s.
|
||||
for (size_t tail_done = 0; tail_done < len;) {
|
||||
tail_done += 32;
|
||||
y = Rotate(x + y, 42) * k0 + v.second;
|
||||
w.first += Fetch64(s + len - tail_done + 16);
|
||||
x = x * k0 + w.first;
|
||||
z += w.second + Fetch64(s + len - tail_done);
|
||||
w.second += v.first;
|
||||
v = WeakHashLen32WithSeeds(s + len - tail_done, v.first + z, v.second);
|
||||
v.first *= k0;
|
||||
}
|
||||
// At this point our 56 bytes of state should contain more than
|
||||
// enough information for a strong 128-bit hash. We use two
|
||||
// different 56-byte-to-8-byte hashes to get a 16-byte final result.
|
||||
x = HashLen16(x, v.first);
|
||||
y = HashLen16(y + z, w.first);
|
||||
return uint128(HashLen16(x + v.second, w.second) + y, HashLen16(x + w.second, y + v.second));
|
||||
}
|
||||
|
||||
uint128 CityHash128(const char* s, size_t len) {
|
||||
return len >= 16
|
||||
? CityHash128WithSeed(s + 16, len - 16, uint128(Fetch64(s), Fetch64(s + 8) + k0))
|
||||
: CityHash128WithSeed(s, len, uint128(k0, k1));
|
||||
}
|
||||
|
||||
} // namespace Common
|
110
src/common/cityhash.h
Normal file
110
src/common/cityhash.h
Normal file
|
@ -0,0 +1,110 @@
|
|||
// Copyright (c) 2011 Google, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
// CityHash, by Geoff Pike and Jyrki Alakuijala
|
||||
//
|
||||
// http://code.google.com/p/cityhash/
|
||||
//
|
||||
// This file provides a few functions for hashing strings. All of them are
|
||||
// high-quality functions in the sense that they pass standard tests such
|
||||
// as Austin Appleby's SMHasher. They are also fast.
|
||||
//
|
||||
// For 64-bit x86 code, on short strings, we don't know of anything faster than
|
||||
// CityHash64 that is of comparable quality. We believe our nearest competitor
|
||||
// is Murmur3. For 64-bit x86 code, CityHash64 is an excellent choice for hash
|
||||
// tables and most other hashing (excluding cryptography).
|
||||
//
|
||||
// For 64-bit x86 code, on long strings, the picture is more complicated.
|
||||
// On many recent Intel CPUs, such as Nehalem, Westmere, Sandy Bridge, etc.,
|
||||
// CityHashCrc128 appears to be faster than all competitors of comparable
|
||||
// quality. CityHash128 is also good but not quite as fast. We believe our
|
||||
// nearest competitor is Bob Jenkins' Spooky. We don't have great data for
|
||||
// other 64-bit CPUs, but for long strings we know that Spooky is slightly
|
||||
// faster than CityHash on some relatively recent AMD x86-64 CPUs, for example.
|
||||
// Note that CityHashCrc128 is declared in citycrc.h.
|
||||
//
|
||||
// For 32-bit x86 code, we don't know of anything faster than CityHash32 that
|
||||
// is of comparable quality. We believe our nearest competitor is Murmur3A.
|
||||
// (On 64-bit CPUs, it is typically faster to use the other CityHash variants.)
|
||||
//
|
||||
// Functions in the CityHash family are not suitable for cryptography.
|
||||
//
|
||||
// Please see CityHash's README file for more details on our performance
|
||||
// measurements and so on.
|
||||
//
|
||||
// WARNING: This code has been only lightly tested on big-endian platforms!
|
||||
// It is known to work well on little-endian platforms that have a small penalty
|
||||
// for unaligned reads, such as current Intel and AMD moderate-to-high-end CPUs.
|
||||
// It should work on all 32-bit and 64-bit platforms that allow unaligned reads;
|
||||
// bug reports are welcome.
|
||||
//
|
||||
// By the way, for some hash functions, given strings a and b, the hash
|
||||
// of a+b is easily derived from the hashes of a and b. This property
|
||||
// doesn't hold for any hash functions in this file.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h> // for size_t.
|
||||
|
||||
namespace Common {
|
||||
|
||||
typedef std::pair<uint64_t, uint64_t> uint128;
|
||||
|
||||
inline uint64_t Uint128Low64(const uint128& x) {
|
||||
return x.first;
|
||||
}
|
||||
inline uint64_t Uint128High64(const uint128& x) {
|
||||
return x.second;
|
||||
}
|
||||
|
||||
// Hash function for a byte array.
|
||||
uint64_t CityHash64(const char* buf, size_t len);
|
||||
|
||||
// Hash function for a byte array. For convenience, a 64-bit seed is also
|
||||
// hashed into the result.
|
||||
uint64_t CityHash64WithSeed(const char* buf, size_t len, uint64_t seed);
|
||||
|
||||
// Hash function for a byte array. For convenience, two seeds are also
|
||||
// hashed into the result.
|
||||
uint64_t CityHash64WithSeeds(const char* buf, size_t len, uint64_t seed0, uint64_t seed1);
|
||||
|
||||
// Hash function for a byte array.
|
||||
uint128 CityHash128(const char* s, size_t len);
|
||||
|
||||
// Hash function for a byte array. For convenience, a 128-bit seed is also
|
||||
// hashed into the result.
|
||||
uint128 CityHash128WithSeed(const char* s, size_t len, uint128 seed);
|
||||
|
||||
// Hash 128 input bits down to 64 bits of output.
|
||||
// This is intended to be a reasonably good hash function.
|
||||
inline uint64_t Hash128to64(const uint128& x) {
|
||||
// Murmur-inspired hashing.
|
||||
const uint64_t kMul = 0x9ddfea08eb382d69ULL;
|
||||
uint64_t a = (Uint128Low64(x) ^ Uint128High64(x)) * kMul;
|
||||
a ^= (a >> 47);
|
||||
uint64_t b = (Uint128High64(x) ^ a) * kMul;
|
||||
b ^= (b >> 47);
|
||||
b *= kMul;
|
||||
return b;
|
||||
}
|
||||
|
||||
} // namespace Common
|
|
@ -1,141 +0,0 @@
|
|||
// Copyright 2015 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/hash.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
// MurmurHash3 was written by Austin Appleby, and is placed in the public
|
||||
// domain. The author hereby disclaims copyright to this source code.
|
||||
|
||||
// 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, size_t i) {
|
||||
return p[i];
|
||||
}
|
||||
|
||||
// Finalization mix - force all bits of a hash block to avalanche
|
||||
static FORCE_INLINE u64 fmix64(u64 k) {
|
||||
k ^= k >> 33;
|
||||
k *= 0xff51afd7ed558ccdllu;
|
||||
k ^= k >> 33;
|
||||
k *= 0xc4ceb9fe1a85ec53llu;
|
||||
k ^= k >> 33;
|
||||
|
||||
return 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, size_t len, u32 seed, void* out) {
|
||||
const u8* data = (const u8*)key;
|
||||
const size_t nblocks = len / 16;
|
||||
|
||||
u64 h1 = seed;
|
||||
u64 h2 = seed;
|
||||
|
||||
const u64 c1 = 0x87c37b91114253d5llu;
|
||||
const u64 c2 = 0x4cf5ad432745937fllu;
|
||||
|
||||
// Body
|
||||
|
||||
const u64* blocks = (const u64*)(data);
|
||||
|
||||
for (size_t i = 0; i < nblocks; i++) {
|
||||
u64 k1 = getblock64(blocks, i * 2 + 0);
|
||||
u64 k2 = getblock64(blocks, i * 2 + 1);
|
||||
|
||||
k1 *= c1;
|
||||
k1 = _rotl64(k1, 31);
|
||||
k1 *= c2;
|
||||
h1 ^= k1;
|
||||
|
||||
h1 = _rotl64(h1, 27);
|
||||
h1 += h2;
|
||||
h1 = h1 * 5 + 0x52dce729;
|
||||
|
||||
k2 *= c2;
|
||||
k2 = _rotl64(k2, 33);
|
||||
k2 *= c1;
|
||||
h2 ^= k2;
|
||||
|
||||
h2 = _rotl64(h2, 31);
|
||||
h2 += h1;
|
||||
h2 = h2 * 5 + 0x38495ab5;
|
||||
}
|
||||
|
||||
// Tail
|
||||
|
||||
const u8* tail = (const u8*)(data + nblocks * 16);
|
||||
|
||||
u64 k1 = 0;
|
||||
u64 k2 = 0;
|
||||
|
||||
switch (len & 15) {
|
||||
case 15:
|
||||
k2 ^= ((u64)tail[14]) << 48;
|
||||
case 14:
|
||||
k2 ^= ((u64)tail[13]) << 40;
|
||||
case 13:
|
||||
k2 ^= ((u64)tail[12]) << 32;
|
||||
case 12:
|
||||
k2 ^= ((u64)tail[11]) << 24;
|
||||
case 11:
|
||||
k2 ^= ((u64)tail[10]) << 16;
|
||||
case 10:
|
||||
k2 ^= ((u64)tail[9]) << 8;
|
||||
case 9:
|
||||
k2 ^= ((u64)tail[8]) << 0;
|
||||
k2 *= c2;
|
||||
k2 = _rotl64(k2, 33);
|
||||
k2 *= c1;
|
||||
h2 ^= k2;
|
||||
|
||||
case 8:
|
||||
k1 ^= ((u64)tail[7]) << 56;
|
||||
case 7:
|
||||
k1 ^= ((u64)tail[6]) << 48;
|
||||
case 6:
|
||||
k1 ^= ((u64)tail[5]) << 40;
|
||||
case 5:
|
||||
k1 ^= ((u64)tail[4]) << 32;
|
||||
case 4:
|
||||
k1 ^= ((u64)tail[3]) << 24;
|
||||
case 3:
|
||||
k1 ^= ((u64)tail[2]) << 16;
|
||||
case 2:
|
||||
k1 ^= ((u64)tail[1]) << 8;
|
||||
case 1:
|
||||
k1 ^= ((u64)tail[0]) << 0;
|
||||
k1 *= c1;
|
||||
k1 = _rotl64(k1, 31);
|
||||
k1 *= c2;
|
||||
h1 ^= k1;
|
||||
};
|
||||
|
||||
// Finalization
|
||||
|
||||
h1 ^= len;
|
||||
h2 ^= len;
|
||||
|
||||
h1 += h2;
|
||||
h2 += h1;
|
||||
|
||||
h1 = fmix64(h1);
|
||||
h2 = fmix64(h2);
|
||||
|
||||
h1 += h2;
|
||||
h2 += h1;
|
||||
|
||||
((u64*)out)[0] = h1;
|
||||
((u64*)out)[1] = h2;
|
||||
}
|
||||
|
||||
} // namespace Common
|
|
@ -5,12 +5,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include "common/cityhash.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out);
|
||||
|
||||
/**
|
||||
* Computes a 64-bit hash over the specified block of data
|
||||
* @param data Block of data to compute hash over
|
||||
|
@ -18,9 +17,20 @@ void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out);
|
|||
* @returns 64-bit hash value that was computed over the data block
|
||||
*/
|
||||
static inline u64 ComputeHash64(const void* data, size_t len) {
|
||||
u64 res[2];
|
||||
MurmurHash3_128(data, len, 0, res);
|
||||
return res[0];
|
||||
return CityHash64(static_cast<const char*>(data), len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a 64-bit hash of a struct. In addition to being POD (trivially copyable and having
|
||||
* standard layout), it is also critical that either the struct includes no padding, or that any
|
||||
* padding is initialized to a known value by memsetting the struct to 0 before filling it in.
|
||||
*/
|
||||
template <typename T>
|
||||
static inline u64 ComputeStructHash64(const T& data) {
|
||||
static_assert(
|
||||
std::is_trivially_copyable<T>::value && std::is_standard_layout<T>::value,
|
||||
"Type passed to ComputeStructHash64 must be trivially copyable and standard layout");
|
||||
return ComputeHash64(&data, sizeof(data));
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
add_library(core STATIC
|
||||
3ds.h
|
||||
arm/arm_interface.h
|
||||
arm/dynarmic/arm_dynarmic.cpp
|
||||
arm/dynarmic/arm_dynarmic.h
|
||||
arm/dynarmic/arm_dynarmic_cp15.cpp
|
||||
arm/dynarmic/arm_dynarmic_cp15.h
|
||||
arm/dyncom/arm_dyncom.cpp
|
||||
arm/dyncom/arm_dyncom.h
|
||||
arm/dyncom/arm_dyncom_dec.cpp
|
||||
|
@ -404,7 +400,17 @@ add_library(core STATIC
|
|||
create_target_directory_groups(core)
|
||||
|
||||
target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core)
|
||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt)
|
||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp fmt)
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
target_link_libraries(core PUBLIC json-headers web_service)
|
||||
endif()
|
||||
|
||||
if (ARCHITECTURE_x86_64)
|
||||
target_sources(core PRIVATE
|
||||
arm/dynarmic/arm_dynarmic.cpp
|
||||
arm/dynarmic/arm_dynarmic.h
|
||||
arm/dynarmic/arm_dynarmic_cp15.cpp
|
||||
arm/dynarmic/arm_dynarmic_cp15.h
|
||||
)
|
||||
target_link_libraries(core PRIVATE dynarmic)
|
||||
endif()
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
#include "audio_core/audio_core.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||
#endif
|
||||
#include "core/arm/dyncom/arm_dyncom.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
|
@ -147,7 +149,12 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
|||
LOG_DEBUG(HW_Memory, "initialized OK");
|
||||
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
cpu_core = std::make_unique<ARM_Dynarmic>(USER32MODE);
|
||||
#else
|
||||
cpu_core = std::make_unique<ARM_DynCom>(USER32MODE);
|
||||
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
|
||||
#endif
|
||||
} else {
|
||||
cpu_core = std::make_unique<ARM_DynCom>(USER32MODE);
|
||||
}
|
||||
|
|
|
@ -59,7 +59,9 @@ public:
|
|||
|
||||
/// Empty placeholder structure for services with no per-session data. The session data classes
|
||||
/// in each service must inherit from this.
|
||||
struct SessionDataBase {};
|
||||
struct SessionDataBase {
|
||||
virtual ~SessionDataBase() = default;
|
||||
};
|
||||
|
||||
protected:
|
||||
/// Creates the storage for the session data of the service.
|
||||
|
|
|
@ -40,6 +40,7 @@ SharedPtr<Process> Process::Create(SharedPtr<CodeSet> code_set) {
|
|||
process->codeset = std::move(code_set);
|
||||
process->flags.raw = 0;
|
||||
process->flags.memory_region.Assign(MemoryRegion::APPLICATION);
|
||||
process->status = ProcessStatus::Created;
|
||||
|
||||
process_list.push_back(process);
|
||||
return process;
|
||||
|
@ -151,6 +152,8 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) {
|
|||
HandleSpecialMapping(vm_manager, mapping);
|
||||
}
|
||||
|
||||
status = ProcessStatus::Running;
|
||||
|
||||
vm_manager.LogLayout(Log::Level::Debug);
|
||||
Kernel::SetupMainThread(codeset->entrypoint, main_thread_priority, this);
|
||||
}
|
||||
|
|
|
@ -49,6 +49,8 @@ union ProcessFlags {
|
|||
BitField<12, 1, u16> loaded_high; ///< Application loaded high (not at 0x00100000).
|
||||
};
|
||||
|
||||
enum class ProcessStatus { Created, Running, Exited };
|
||||
|
||||
class ResourceLimit;
|
||||
struct MemoryRegionInfo;
|
||||
|
||||
|
@ -122,6 +124,8 @@ public:
|
|||
u16 kernel_version = 0;
|
||||
/// The default CPU for this process, threads are scheduled on this cpu by default.
|
||||
u8 ideal_processor = 0;
|
||||
/// Current status of the process
|
||||
ProcessStatus status;
|
||||
|
||||
/// The id of this process
|
||||
u32 process_id = next_process_id++;
|
||||
|
|
|
@ -143,6 +143,36 @@ static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 add
|
|||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static void ExitProcess() {
|
||||
LOG_INFO(Kernel_SVC, "Process %u exiting", g_current_process->process_id);
|
||||
|
||||
ASSERT_MSG(g_current_process->status == ProcessStatus::Running, "Process has already exited");
|
||||
|
||||
g_current_process->status = ProcessStatus::Exited;
|
||||
|
||||
// Stop all the process threads that are currently waiting for objects.
|
||||
auto& thread_list = GetThreadList();
|
||||
for (auto& thread : thread_list) {
|
||||
if (thread->owner_process != g_current_process)
|
||||
continue;
|
||||
|
||||
if (thread == GetCurrentThread())
|
||||
continue;
|
||||
|
||||
// TODO(Subv): When are the other running/ready threads terminated?
|
||||
ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY ||
|
||||
thread->status == THREADSTATUS_WAIT_SYNCH_ALL,
|
||||
"Exiting processes with non-waiting threads is currently unimplemented");
|
||||
|
||||
thread->Stop();
|
||||
}
|
||||
|
||||
// Kill the current thread
|
||||
GetCurrentThread()->Stop();
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
}
|
||||
|
||||
/// Maps a memory block to specified address
|
||||
static ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 other_permissions) {
|
||||
LOG_TRACE(Kernel_SVC,
|
||||
|
@ -685,7 +715,7 @@ static ResultCode GetResourceLimitLimitValues(VAddr values, Handle resource_limi
|
|||
|
||||
for (unsigned int i = 0; i < name_count; ++i) {
|
||||
u32 name = Memory::Read32(names + i * sizeof(u32));
|
||||
s64 value = resource_limit->GetMaxResourceValue(names);
|
||||
s64 value = resource_limit->GetMaxResourceValue(name);
|
||||
Memory::Write64(values + i * sizeof(u64), value);
|
||||
}
|
||||
|
||||
|
@ -1232,7 +1262,7 @@ static const FunctionDef SVC_Table[] = {
|
|||
{0x00, nullptr, "Unknown"},
|
||||
{0x01, HLE::Wrap<ControlMemory>, "ControlMemory"},
|
||||
{0x02, HLE::Wrap<QueryMemory>, "QueryMemory"},
|
||||
{0x03, nullptr, "ExitProcess"},
|
||||
{0x03, ExitProcess, "ExitProcess"},
|
||||
{0x04, nullptr, "GetProcessAffinityMask"},
|
||||
{0x05, nullptr, "SetProcessAffinityMask"},
|
||||
{0x06, nullptr, "GetProcessIdealProcessor"},
|
||||
|
@ -1373,6 +1403,9 @@ void CallSVC(u32 immediate) {
|
|||
// Lock the global kernel mutex when we enter the kernel HLE.
|
||||
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
|
||||
|
||||
ASSERT_MSG(g_current_process->status == ProcessStatus::Running,
|
||||
"Running threads from exiting processes is unimplemented");
|
||||
|
||||
const FunctionDef* info = GetSVCInfo(immediate);
|
||||
if (info) {
|
||||
if (info->func) {
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include "core/core.h"
|
||||
#include "core/hle/ipc.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/shared_memory.h"
|
||||
#include "core/hle/result.h"
|
||||
|
@ -51,6 +50,20 @@ constexpr ResultCode ERR_REGS_INVALID_SIZE(ErrorDescription::InvalidSize, ErrorM
|
|||
ErrorSummary::InvalidArgument,
|
||||
ErrorLevel::Usage); // 0xE0E02BEC
|
||||
|
||||
/// Maximum number of threads that can be registered at the same time in the GSP module.
|
||||
constexpr u32 MaxGSPThreads = 4;
|
||||
|
||||
/// Thread ids currently in use by the sessions connected to the GSPGPU service.
|
||||
static std::array<bool, MaxGSPThreads> used_thread_ids = {false, false, false, false};
|
||||
|
||||
static u32 GetUnusedThreadId() {
|
||||
for (u32 id = 0; id < MaxGSPThreads; ++id) {
|
||||
if (!used_thread_ids[id])
|
||||
return id;
|
||||
}
|
||||
ASSERT_MSG(false, "All GSP threads are in use");
|
||||
}
|
||||
|
||||
/// Gets a pointer to a thread command buffer in GSP shared memory
|
||||
static inline u8* GetCommandBuffer(Kernel::SharedPtr<Kernel::SharedMemory> shared_memory,
|
||||
u32 thread_id) {
|
||||
|
@ -319,12 +332,16 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
|
|||
IPC::RequestParser rp(ctx, 0x13, 1, 2);
|
||||
u32 flags = rp.Pop<u32>();
|
||||
|
||||
interrupt_event = rp.PopObject<Kernel::Event>();
|
||||
auto interrupt_event = rp.PopObject<Kernel::Event>();
|
||||
// TODO(mailwl): return right error code instead assert
|
||||
ASSERT_MSG((interrupt_event != nullptr), "handle is not valid!");
|
||||
|
||||
interrupt_event->name = "GSP_GSP_GPU::interrupt_event";
|
||||
|
||||
SessionData* session_data = GetSessionData(ctx.Session());
|
||||
session_data->interrupt_event = std::move(interrupt_event);
|
||||
session_data->registered = true;
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
||||
|
||||
if (first_initialization) {
|
||||
|
@ -335,25 +352,60 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
|
|||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
rb.Push(thread_id);
|
||||
rb.Push(session_data->thread_id);
|
||||
rb.PushCopyObjects(shared_memory);
|
||||
|
||||
thread_id++;
|
||||
interrupt_event->Signal(); // TODO(bunnei): Is this correct?
|
||||
|
||||
LOG_WARNING(Service_GSP, "called, flags=0x%08X", flags);
|
||||
LOG_DEBUG(Service_GSP, "called, flags=0x%08X", flags);
|
||||
}
|
||||
|
||||
void GSP_GPU::UnregisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx, 0x14, 0, 0);
|
||||
|
||||
thread_id = 0;
|
||||
interrupt_event = nullptr;
|
||||
SessionData* session_data = GetSessionData(ctx.Session());
|
||||
session_data->interrupt_event = nullptr;
|
||||
session_data->registered = false;
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_WARNING(Service_GSP, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_GSP, "called");
|
||||
}
|
||||
|
||||
void GSP_GPU::SignalInterruptForThread(InterruptId interrupt_id, u32 thread_id) {
|
||||
SessionData* session_data = FindRegisteredThreadData(thread_id);
|
||||
if (session_data == nullptr)
|
||||
return;
|
||||
|
||||
auto interrupt_event = session_data->interrupt_event;
|
||||
if (interrupt_event == nullptr) {
|
||||
LOG_WARNING(Service_GSP, "cannot synchronize until GSP event has been created!");
|
||||
return;
|
||||
}
|
||||
InterruptRelayQueue* interrupt_relay_queue = GetInterruptRelayQueue(shared_memory, thread_id);
|
||||
u8 next = interrupt_relay_queue->index;
|
||||
next += interrupt_relay_queue->number_interrupts;
|
||||
next = next % 0x34; // 0x34 is the number of interrupt slots
|
||||
|
||||
interrupt_relay_queue->number_interrupts += 1;
|
||||
|
||||
interrupt_relay_queue->slot[next] = interrupt_id;
|
||||
interrupt_relay_queue->error_code = 0x0; // No error
|
||||
|
||||
// Update framebuffer information if requested
|
||||
// TODO(yuriks): Confirm where this code should be called. It is definitely updated without
|
||||
// executing any GSP commands, only waiting on the event.
|
||||
// TODO(Subv): The real GSP module triggers PDC0 after updating both the top and bottom
|
||||
// screen, it is currently unknown what PDC1 does.
|
||||
int screen_id =
|
||||
(interrupt_id == InterruptId::PDC0) ? 0 : (interrupt_id == InterruptId::PDC1) ? 1 : -1;
|
||||
if (screen_id != -1) {
|
||||
FrameBufferUpdate* info = GetFrameBufferInfo(thread_id, screen_id);
|
||||
if (info->is_dirty) {
|
||||
GSP::SetBufferSwap(screen_id, info->framebuffer_info[info->index]);
|
||||
info->is_dirty.Assign(false);
|
||||
}
|
||||
}
|
||||
interrupt_event->Signal();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -363,43 +415,26 @@ void GSP_GPU::UnregisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
|
|||
* @todo This probably does not belong in the GSP module, instead move to video_core
|
||||
*/
|
||||
void GSP_GPU::SignalInterrupt(InterruptId interrupt_id) {
|
||||
if (!gpu_right_acquired) {
|
||||
return;
|
||||
}
|
||||
if (nullptr == interrupt_event) {
|
||||
LOG_WARNING(Service_GSP, "cannot synchronize until GSP event has been created!");
|
||||
return;
|
||||
}
|
||||
if (nullptr == shared_memory) {
|
||||
LOG_WARNING(Service_GSP, "cannot synchronize until GSP shared memory has been created!");
|
||||
return;
|
||||
}
|
||||
for (int thread_id = 0; thread_id < 0x4; ++thread_id) {
|
||||
InterruptRelayQueue* interrupt_relay_queue =
|
||||
GetInterruptRelayQueue(shared_memory, thread_id);
|
||||
u8 next = interrupt_relay_queue->index;
|
||||
next += interrupt_relay_queue->number_interrupts;
|
||||
next = next % 0x34; // 0x34 is the number of interrupt slots
|
||||
|
||||
interrupt_relay_queue->number_interrupts += 1;
|
||||
|
||||
interrupt_relay_queue->slot[next] = interrupt_id;
|
||||
interrupt_relay_queue->error_code = 0x0; // No error
|
||||
|
||||
// Update framebuffer information if requested
|
||||
// TODO(yuriks): Confirm where this code should be called. It is definitely updated without
|
||||
// executing any GSP commands, only waiting on the event.
|
||||
int screen_id =
|
||||
(interrupt_id == InterruptId::PDC0) ? 0 : (interrupt_id == InterruptId::PDC1) ? 1 : -1;
|
||||
if (screen_id != -1) {
|
||||
FrameBufferUpdate* info = GetFrameBufferInfo(thread_id, screen_id);
|
||||
if (info->is_dirty) {
|
||||
GSP::SetBufferSwap(screen_id, info->framebuffer_info[info->index]);
|
||||
info->is_dirty.Assign(false);
|
||||
}
|
||||
// The PDC0 and PDC1 interrupts are fired even if the GPU right hasn't been acquired.
|
||||
// Normal interrupts are only signaled for the active thread (ie, the thread that has the GPU
|
||||
// right), but the PDC0/1 interrupts are signaled for every registered thread.
|
||||
if (interrupt_id == InterruptId::PDC0 || interrupt_id == InterruptId::PDC1) {
|
||||
for (u32 thread_id = 0; thread_id < MaxGSPThreads; ++thread_id) {
|
||||
SignalInterruptForThread(interrupt_id, thread_id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
interrupt_event->Signal();
|
||||
|
||||
// For normal interrupts, don't do anything if no process has acquired the GPU right.
|
||||
if (active_thread_id == -1)
|
||||
return;
|
||||
|
||||
SignalInterruptForThread(interrupt_id, active_thread_id);
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(GPU_GSP_DMA, "GPU", "GSP DMA", MP_RGB(100, 0, 255));
|
||||
|
@ -622,18 +657,34 @@ void GSP_GPU::AcquireRight(Kernel::HLERequestContext& ctx) {
|
|||
u32 flag = rp.Pop<u32>();
|
||||
auto process = rp.PopObject<Kernel::Process>();
|
||||
|
||||
gpu_right_acquired = true;
|
||||
SessionData* session_data = GetSessionData(ctx.Session());
|
||||
|
||||
LOG_WARNING(Service_GSP, "called flag=%08X process=%u thread_id=%u", flag, process->process_id,
|
||||
session_data->thread_id);
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_WARNING(Service_GSP, "called flag=%08X process=%u", flag, process->process_id);
|
||||
if (active_thread_id == session_data->thread_id) {
|
||||
rb.Push(ResultCode(ErrorDescription::AlreadyDone, ErrorModule::GX, ErrorSummary::Success,
|
||||
ErrorLevel::Success));
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(Subv): This case should put the caller thread to sleep until the right is released.
|
||||
ASSERT_MSG(active_thread_id == -1, "GPU right has already been acquired");
|
||||
|
||||
active_thread_id = session_data->thread_id;
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void GSP_GPU::ReleaseRight(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx, 0x17, 0, 0);
|
||||
|
||||
gpu_right_acquired = false;
|
||||
SessionData* session_data = GetSessionData(ctx.Session());
|
||||
ASSERT_MSG(active_thread_id == session_data->thread_id,
|
||||
"Wrong thread tried to release GPU right");
|
||||
active_thread_id = -1;
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
@ -655,6 +706,17 @@ void GSP_GPU::StoreDataCache(Kernel::HLERequestContext& ctx) {
|
|||
size, process->process_id);
|
||||
}
|
||||
|
||||
SessionData* GSP_GPU::FindRegisteredThreadData(u32 thread_id) {
|
||||
for (auto& session_info : connected_sessions) {
|
||||
SessionData* data = static_cast<SessionData*>(session_info.data.get());
|
||||
if (!data->registered)
|
||||
continue;
|
||||
if (data->thread_id == thread_id)
|
||||
return data;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GSP_GPU::GSP_GPU() : ServiceFramework("gsp::Gpu", 2) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0x00010082, &GSP_GPU::WriteHWRegs, "WriteHWRegs"},
|
||||
|
@ -691,17 +753,26 @@ GSP_GPU::GSP_GPU() : ServiceFramework("gsp::Gpu", 2) {
|
|||
};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
interrupt_event = nullptr;
|
||||
|
||||
using Kernel::MemoryPermission;
|
||||
shared_memory = Kernel::SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite,
|
||||
MemoryPermission::ReadWrite, 0,
|
||||
Kernel::MemoryRegion::BASE, "GSP:SharedMemory");
|
||||
|
||||
thread_id = 0;
|
||||
gpu_right_acquired = false;
|
||||
first_initialization = true;
|
||||
};
|
||||
|
||||
SessionData::SessionData() {
|
||||
// Assign a new thread id to this session when it connects. Note: In the real GSP service this
|
||||
// is done through a real thread (svcCreateThread) but we have to simulate it since our HLE
|
||||
// services don't have threads.
|
||||
thread_id = GetUnusedThreadId();
|
||||
used_thread_ids[thread_id] = true;
|
||||
}
|
||||
|
||||
SessionData::~SessionData() {
|
||||
// Free the thread id slot so that other sessions can use it.
|
||||
used_thread_ids[thread_id] = false;
|
||||
}
|
||||
|
||||
} // namespace GSP
|
||||
} // namespace Service
|
||||
|
|
|
@ -8,12 +8,12 @@
|
|||
#include <string>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Kernel {
|
||||
class Event;
|
||||
class SharedMemory;
|
||||
} // namespace Kernel
|
||||
|
||||
|
@ -179,7 +179,19 @@ struct CommandBuffer {
|
|||
};
|
||||
static_assert(sizeof(CommandBuffer) == 0x200, "CommandBuffer struct has incorrect size");
|
||||
|
||||
class GSP_GPU final : public ServiceFramework<GSP_GPU> {
|
||||
struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
|
||||
SessionData();
|
||||
~SessionData();
|
||||
|
||||
/// Event triggered when GSP interrupt has been signalled
|
||||
Kernel::SharedPtr<Kernel::Event> interrupt_event;
|
||||
/// Thread index into interrupt relay queue
|
||||
u32 thread_id;
|
||||
/// Whether RegisterInterruptRelayQueue was called for this session
|
||||
bool registered = false;
|
||||
};
|
||||
|
||||
class GSP_GPU final : public ServiceFramework<GSP_GPU, SessionData> {
|
||||
public:
|
||||
GSP_GPU();
|
||||
~GSP_GPU() = default;
|
||||
|
@ -201,6 +213,14 @@ public:
|
|||
FrameBufferUpdate* GetFrameBufferInfo(u32 thread_id, u32 screen_index);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Signals that the specified interrupt type has occurred to userland code for the specified GSP
|
||||
* thread id.
|
||||
* @param interrupt_id ID of interrupt that is being signalled.
|
||||
* @param thread_id GSP thread that will receive the interrupt.
|
||||
*/
|
||||
void SignalInterruptForThread(InterruptId interrupt_id, u32 thread_id);
|
||||
|
||||
/**
|
||||
* GSP_GPU::WriteHWRegs service function
|
||||
*
|
||||
|
@ -351,14 +371,15 @@ private:
|
|||
*/
|
||||
void StoreDataCache(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/// Event triggered when GSP interrupt has been signalled
|
||||
Kernel::SharedPtr<Kernel::Event> interrupt_event;
|
||||
/// GSP shared memoryings
|
||||
Kernel::SharedPtr<Kernel::SharedMemory> shared_memory;
|
||||
/// Thread index into interrupt relay queue
|
||||
u32 thread_id = 0;
|
||||
/// Returns the session data for the specified registered thread id, or nullptr if not found.
|
||||
SessionData* FindRegisteredThreadData(u32 thread_id);
|
||||
|
||||
/// GSP shared memory
|
||||
Kernel::SharedPtr<Kernel::SharedMemory> shared_memory;
|
||||
|
||||
/// Thread id that currently has GPU rights or -1 if none.
|
||||
int active_thread_id = -1;
|
||||
|
||||
bool gpu_right_acquired = false;
|
||||
bool first_initialization = true;
|
||||
};
|
||||
|
||||
|
|
|
@ -3,15 +3,12 @@
|
|||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/3ds.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/frontend/input.h"
|
||||
#include "core/hle/ipc.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/shared_memory.h"
|
||||
|
@ -23,27 +20,7 @@
|
|||
namespace Service {
|
||||
namespace HID {
|
||||
|
||||
// Handle to shared memory region designated to HID_User service
|
||||
static Kernel::SharedPtr<Kernel::SharedMemory> shared_mem;
|
||||
|
||||
// Event handles
|
||||
static Kernel::SharedPtr<Kernel::Event> event_pad_or_touch_1;
|
||||
static Kernel::SharedPtr<Kernel::Event> event_pad_or_touch_2;
|
||||
static Kernel::SharedPtr<Kernel::Event> event_accelerometer;
|
||||
static Kernel::SharedPtr<Kernel::Event> event_gyroscope;
|
||||
static Kernel::SharedPtr<Kernel::Event> event_debug_pad;
|
||||
|
||||
static u32 next_pad_index;
|
||||
static u32 next_touch_index;
|
||||
static u32 next_accelerometer_index;
|
||||
static u32 next_gyroscope_index;
|
||||
|
||||
static int enable_accelerometer_count; // positive means enabled
|
||||
static int enable_gyroscope_count; // positive means enabled
|
||||
|
||||
static CoreTiming::EventType* pad_update_event;
|
||||
static CoreTiming::EventType* accelerometer_update_event;
|
||||
static CoreTiming::EventType* gyroscope_update_event;
|
||||
static std::weak_ptr<Module> current_module;
|
||||
|
||||
// Updating period for each HID device. These empirical values are measured from a 11.2 3DS.
|
||||
constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234;
|
||||
|
@ -53,13 +30,6 @@ constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101;
|
|||
constexpr float accelerometer_coef = 512.0f; // measured from hw test result
|
||||
constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call
|
||||
|
||||
static std::atomic<bool> is_device_reload_pending;
|
||||
static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
|
||||
buttons;
|
||||
static std::unique_ptr<Input::AnalogDevice> circle_pad;
|
||||
static std::unique_ptr<Input::MotionDevice> motion_device;
|
||||
static std::unique_ptr<Input::TouchDevice> touch_device;
|
||||
|
||||
DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
|
||||
// 30 degree and 60 degree are angular thresholds for directions
|
||||
constexpr float TAN30 = 0.577350269f;
|
||||
|
@ -89,7 +59,7 @@ DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
|
|||
return state;
|
||||
}
|
||||
|
||||
static void LoadInputDevices() {
|
||||
void Module::LoadInputDevices() {
|
||||
std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
|
||||
Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
|
||||
buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
|
||||
|
@ -99,16 +69,7 @@ static void LoadInputDevices() {
|
|||
touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device);
|
||||
}
|
||||
|
||||
static void UnloadInputDevices() {
|
||||
for (auto& button : buttons) {
|
||||
button.reset();
|
||||
}
|
||||
circle_pad.reset();
|
||||
motion_device.reset();
|
||||
touch_device.reset();
|
||||
}
|
||||
|
||||
static void UpdatePadCallback(u64 userdata, int cycles_late) {
|
||||
void Module::UpdatePadCallback(u64 userdata, int cycles_late) {
|
||||
SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
|
||||
|
||||
if (is_device_reload_pending.exchange(false))
|
||||
|
@ -198,7 +159,7 @@ static void UpdatePadCallback(u64 userdata, int cycles_late) {
|
|||
CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event);
|
||||
}
|
||||
|
||||
static void UpdateAccelerometerCallback(u64 userdata, int cycles_late) {
|
||||
void Module::UpdateAccelerometerCallback(u64 userdata, int cycles_late) {
|
||||
SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
|
||||
|
||||
mem->accelerometer.index = next_accelerometer_index;
|
||||
|
@ -240,7 +201,7 @@ static void UpdateAccelerometerCallback(u64 userdata, int cycles_late) {
|
|||
CoreTiming::ScheduleEvent(accelerometer_update_ticks - cycles_late, accelerometer_update_event);
|
||||
}
|
||||
|
||||
static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) {
|
||||
void Module::UpdateGyroscopeCallback(u64 userdata, int cycles_late) {
|
||||
SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
|
||||
|
||||
mem->gyroscope.index = next_gyroscope_index;
|
||||
|
@ -273,134 +234,122 @@ static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) {
|
|||
CoreTiming::ScheduleEvent(gyroscope_update_ticks - cycles_late, gyroscope_update_event);
|
||||
}
|
||||
|
||||
void GetIPCHandles(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
|
||||
cmd_buff[1] = 0; // No error
|
||||
cmd_buff[2] = 0x14000000; // IPC Command Structure translate-header
|
||||
// TODO(yuriks): Return error from SendSyncRequest is this fails (part of IPC marshalling)
|
||||
cmd_buff[3] = Kernel::g_handle_table.Create(Service::HID::shared_mem).Unwrap();
|
||||
cmd_buff[4] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_1).Unwrap();
|
||||
cmd_buff[5] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_2).Unwrap();
|
||||
cmd_buff[6] = Kernel::g_handle_table.Create(Service::HID::event_accelerometer).Unwrap();
|
||||
cmd_buff[7] = Kernel::g_handle_table.Create(Service::HID::event_gyroscope).Unwrap();
|
||||
cmd_buff[8] = Kernel::g_handle_table.Create(Service::HID::event_debug_pad).Unwrap();
|
||||
void Module::Interface::GetIPCHandles(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx, 0xA, 0, 0};
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 7);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushCopyObjects(hid->shared_mem, hid->event_pad_or_touch_1, hid->event_pad_or_touch_2,
|
||||
hid->event_accelerometer, hid->event_gyroscope, hid->event_debug_pad);
|
||||
}
|
||||
|
||||
void EnableAccelerometer(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
void Module::Interface::EnableAccelerometer(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx, 0x11, 0, 0};
|
||||
|
||||
++enable_accelerometer_count;
|
||||
++hid->enable_accelerometer_count;
|
||||
|
||||
// Schedules the accelerometer update event if the accelerometer was just enabled
|
||||
if (enable_accelerometer_count == 1) {
|
||||
CoreTiming::ScheduleEvent(accelerometer_update_ticks, accelerometer_update_event);
|
||||
if (hid->enable_accelerometer_count == 1) {
|
||||
CoreTiming::ScheduleEvent(accelerometer_update_ticks, hid->accelerometer_update_event);
|
||||
}
|
||||
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void DisableAccelerometer(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
void Module::Interface::DisableAccelerometer(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx, 0x12, 0, 0};
|
||||
|
||||
--enable_accelerometer_count;
|
||||
--hid->enable_accelerometer_count;
|
||||
|
||||
// Unschedules the accelerometer update event if the accelerometer was just disabled
|
||||
if (enable_accelerometer_count == 0) {
|
||||
CoreTiming::UnscheduleEvent(accelerometer_update_event, 0);
|
||||
if (hid->enable_accelerometer_count == 0) {
|
||||
CoreTiming::UnscheduleEvent(hid->accelerometer_update_event, 0);
|
||||
}
|
||||
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void EnableGyroscopeLow(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
void Module::Interface::EnableGyroscopeLow(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx, 0x13, 0, 0};
|
||||
|
||||
++enable_gyroscope_count;
|
||||
++hid->enable_gyroscope_count;
|
||||
|
||||
// Schedules the gyroscope update event if the gyroscope was just enabled
|
||||
if (enable_gyroscope_count == 1) {
|
||||
CoreTiming::ScheduleEvent(gyroscope_update_ticks, gyroscope_update_event);
|
||||
if (hid->enable_gyroscope_count == 1) {
|
||||
CoreTiming::ScheduleEvent(gyroscope_update_ticks, hid->gyroscope_update_event);
|
||||
}
|
||||
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void DisableGyroscopeLow(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
void Module::Interface::DisableGyroscopeLow(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx, 0x14, 0, 0};
|
||||
|
||||
--enable_gyroscope_count;
|
||||
--hid->enable_gyroscope_count;
|
||||
|
||||
// Unschedules the gyroscope update event if the gyroscope was just disabled
|
||||
if (enable_gyroscope_count == 0) {
|
||||
CoreTiming::UnscheduleEvent(gyroscope_update_event, 0);
|
||||
if (hid->enable_gyroscope_count == 0) {
|
||||
CoreTiming::UnscheduleEvent(hid->gyroscope_update_event, 0);
|
||||
}
|
||||
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
void Module::Interface::GetGyroscopeLowRawToDpsCoefficient(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx, 0x15, 0, 0};
|
||||
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
|
||||
f32 coef = gyroscope_coef;
|
||||
memcpy(&cmd_buff[2], &coef, 4);
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw<f32>(gyroscope_coef);
|
||||
}
|
||||
|
||||
void GetGyroscopeLowCalibrateParam(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
void Module::Interface::GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx, 0x16, 0, 0};
|
||||
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(6, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
const s16 param_unit = 6700; // an approximate value taken from hw
|
||||
GyroscopeCalibrateParam param = {
|
||||
{0, param_unit, -param_unit}, {0, param_unit, -param_unit}, {0, param_unit, -param_unit},
|
||||
};
|
||||
memcpy(&cmd_buff[2], ¶m, sizeof(param));
|
||||
rb.PushRaw(param);
|
||||
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void GetSoundVolume(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
void Module::Interface::GetSoundVolume(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx, 0x17, 0, 0};
|
||||
|
||||
const u8 volume = 0x3F; // TODO(purpasmart): Find out if this is the max value for the volume
|
||||
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = volume;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(volume);
|
||||
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void Init() {
|
||||
Module::Interface::Interface(std::shared_ptr<Module> hid, const char* name, u32 max_session)
|
||||
: ServiceFramework(name, max_session), hid(std::move(hid)) {}
|
||||
|
||||
Module::Module() {
|
||||
using namespace Kernel;
|
||||
|
||||
AddService(new HID_U_Interface);
|
||||
AddService(new HID_SPVR_Interface);
|
||||
|
||||
is_device_reload_pending.store(true);
|
||||
|
||||
using Kernel::MemoryPermission;
|
||||
shared_mem =
|
||||
SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read,
|
||||
0, Kernel::MemoryRegion::BASE, "HID:SharedMemory");
|
||||
|
||||
next_pad_index = 0;
|
||||
next_touch_index = 0;
|
||||
next_accelerometer_index = 0;
|
||||
next_gyroscope_index = 0;
|
||||
|
||||
enable_accelerometer_count = 0;
|
||||
enable_gyroscope_count = 0;
|
||||
0, MemoryRegion::BASE, "HID:SharedMemory");
|
||||
|
||||
// Create event handles
|
||||
event_pad_or_touch_1 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch1");
|
||||
|
@ -410,27 +359,35 @@ void Init() {
|
|||
event_debug_pad = Event::Create(ResetType::OneShot, "HID:EventDebugPad");
|
||||
|
||||
// Register update callbacks
|
||||
pad_update_event = CoreTiming::RegisterEvent("HID::UpdatePadCallback", UpdatePadCallback);
|
||||
accelerometer_update_event =
|
||||
CoreTiming::RegisterEvent("HID::UpdateAccelerometerCallback", UpdateAccelerometerCallback);
|
||||
gyroscope_update_event =
|
||||
CoreTiming::RegisterEvent("HID::UpdateGyroscopeCallback", UpdateGyroscopeCallback);
|
||||
pad_update_event =
|
||||
CoreTiming::RegisterEvent("HID::UpdatePadCallback", [this](u64 userdata, int cycles_late) {
|
||||
UpdatePadCallback(userdata, cycles_late);
|
||||
});
|
||||
accelerometer_update_event = CoreTiming::RegisterEvent(
|
||||
"HID::UpdateAccelerometerCallback", [this](u64 userdata, int cycles_late) {
|
||||
UpdateAccelerometerCallback(userdata, cycles_late);
|
||||
});
|
||||
gyroscope_update_event = CoreTiming::RegisterEvent(
|
||||
"HID::UpdateGyroscopeCallback",
|
||||
[this](u64 userdata, int cycles_late) { UpdateGyroscopeCallback(userdata, cycles_late); });
|
||||
|
||||
CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event);
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
shared_mem = nullptr;
|
||||
event_pad_or_touch_1 = nullptr;
|
||||
event_pad_or_touch_2 = nullptr;
|
||||
event_accelerometer = nullptr;
|
||||
event_gyroscope = nullptr;
|
||||
event_debug_pad = nullptr;
|
||||
UnloadInputDevices();
|
||||
void Module::ReloadInputDevices() {
|
||||
is_device_reload_pending.store(true);
|
||||
}
|
||||
|
||||
void ReloadInputDevices() {
|
||||
is_device_reload_pending.store(true);
|
||||
if (auto hid = current_module.lock())
|
||||
hid->ReloadInputDevices();
|
||||
}
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
auto hid = std::make_shared<Module>();
|
||||
std::make_shared<User>(hid)->InstallAsService(service_manager);
|
||||
std::make_shared<Spvr>(hid)->InstallAsService(service_manager);
|
||||
current_module = hid;
|
||||
}
|
||||
|
||||
} // namespace HID
|
||||
|
|
|
@ -5,17 +5,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#ifndef _MSC_VER
|
||||
#include <cstddef>
|
||||
#endif
|
||||
#include <memory>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/frontend/input.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service {
|
||||
namespace Kernel {
|
||||
class Event;
|
||||
class SharedMemory;
|
||||
}
|
||||
|
||||
class Interface;
|
||||
namespace CoreTiming {
|
||||
class EventType;
|
||||
};
|
||||
|
||||
namespace Service {
|
||||
|
||||
namespace HID {
|
||||
|
||||
|
@ -186,93 +198,140 @@ struct DirectionState {
|
|||
/// Translates analog stick axes to directions. This is exposed for ir_rst module to use.
|
||||
DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y);
|
||||
|
||||
/**
|
||||
* HID::GetIPCHandles service function
|
||||
* Inputs:
|
||||
* None
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
* 2 : IPC Command Structure translate-header
|
||||
* 3 : Handle to HID shared memory
|
||||
* 4 : Event signaled by HID
|
||||
* 5 : Event signaled by HID
|
||||
* 6 : Event signaled by HID
|
||||
* 7 : Gyroscope event
|
||||
* 8 : Event signaled by HID
|
||||
*/
|
||||
void GetIPCHandles(Interface* self);
|
||||
class Module final {
|
||||
public:
|
||||
Module();
|
||||
|
||||
/**
|
||||
* HID::EnableAccelerometer service function
|
||||
* Inputs:
|
||||
* None
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void EnableAccelerometer(Interface* self);
|
||||
class Interface : public ServiceFramework<Interface> {
|
||||
public:
|
||||
Interface(std::shared_ptr<Module> hid, const char* name, u32 max_session);
|
||||
|
||||
/**
|
||||
* HID::DisableAccelerometer service function
|
||||
* Inputs:
|
||||
* None
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void DisableAccelerometer(Interface* self);
|
||||
protected:
|
||||
/**
|
||||
* HID::GetIPCHandles service function
|
||||
* Inputs:
|
||||
* None
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
* 2 : IPC Command Structure translate-header
|
||||
* 3 : Handle to HID shared memory
|
||||
* 4 : Event signaled by HID
|
||||
* 5 : Event signaled by HID
|
||||
* 6 : Event signaled by HID
|
||||
* 7 : Gyroscope event
|
||||
* 8 : Event signaled by HID
|
||||
*/
|
||||
void GetIPCHandles(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HID::EnableGyroscopeLow service function
|
||||
* Inputs:
|
||||
* None
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void EnableGyroscopeLow(Interface* self);
|
||||
/**
|
||||
* HID::EnableAccelerometer service function
|
||||
* Inputs:
|
||||
* None
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void EnableAccelerometer(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HID::DisableGyroscopeLow service function
|
||||
* Inputs:
|
||||
* None
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void DisableGyroscopeLow(Interface* self);
|
||||
/**
|
||||
* HID::DisableAccelerometer service function
|
||||
* Inputs:
|
||||
* None
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void DisableAccelerometer(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HID::GetSoundVolume service function
|
||||
* Inputs:
|
||||
* None
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
* 2 : u8 output value
|
||||
*/
|
||||
void GetSoundVolume(Interface* self);
|
||||
/**
|
||||
* HID::EnableGyroscopeLow service function
|
||||
* Inputs:
|
||||
* None
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void EnableGyroscopeLow(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HID::GetGyroscopeLowRawToDpsCoefficient service function
|
||||
* Inputs:
|
||||
* None
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
* 2 : float output value
|
||||
*/
|
||||
void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self);
|
||||
/**
|
||||
* HID::DisableGyroscopeLow service function
|
||||
* Inputs:
|
||||
* None
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void DisableGyroscopeLow(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HID::GetGyroscopeLowCalibrateParam service function
|
||||
* Inputs:
|
||||
* None
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
* 2~6 (18 bytes) : struct GyroscopeCalibrateParam
|
||||
*/
|
||||
void GetGyroscopeLowCalibrateParam(Service::Interface* self);
|
||||
/**
|
||||
* HID::GetSoundVolume service function
|
||||
* Inputs:
|
||||
* None
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
* 2 : u8 output value
|
||||
*/
|
||||
void GetSoundVolume(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/// Initialize HID service
|
||||
void Init();
|
||||
/**
|
||||
* HID::GetGyroscopeLowRawToDpsCoefficient service function
|
||||
* Inputs:
|
||||
* None
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
* 2 : float output value
|
||||
*/
|
||||
void GetGyroscopeLowRawToDpsCoefficient(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/// Shutdown HID service
|
||||
void Shutdown();
|
||||
/**
|
||||
* HID::GetGyroscopeLowCalibrateParam service function
|
||||
* Inputs:
|
||||
* None
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
* 2~6 (18 bytes) : struct GyroscopeCalibrateParam
|
||||
*/
|
||||
void GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext& ctx);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Module> hid;
|
||||
};
|
||||
|
||||
void ReloadInputDevices();
|
||||
|
||||
private:
|
||||
void LoadInputDevices();
|
||||
void UpdatePadCallback(u64 userdata, int cycles_late);
|
||||
void UpdateAccelerometerCallback(u64 userdata, int cycles_late);
|
||||
void UpdateGyroscopeCallback(u64 userdata, int cycles_late);
|
||||
|
||||
// Handle to shared memory region designated to HID_User service
|
||||
Kernel::SharedPtr<Kernel::SharedMemory> shared_mem;
|
||||
|
||||
// Event handles
|
||||
Kernel::SharedPtr<Kernel::Event> event_pad_or_touch_1;
|
||||
Kernel::SharedPtr<Kernel::Event> event_pad_or_touch_2;
|
||||
Kernel::SharedPtr<Kernel::Event> event_accelerometer;
|
||||
Kernel::SharedPtr<Kernel::Event> event_gyroscope;
|
||||
Kernel::SharedPtr<Kernel::Event> event_debug_pad;
|
||||
|
||||
u32 next_pad_index = 0;
|
||||
u32 next_touch_index = 0;
|
||||
u32 next_accelerometer_index = 0;
|
||||
u32 next_gyroscope_index = 0;
|
||||
|
||||
int enable_accelerometer_count = 0; // positive means enabled
|
||||
int enable_gyroscope_count = 0; // positive means enabled
|
||||
|
||||
CoreTiming::EventType* pad_update_event;
|
||||
CoreTiming::EventType* accelerometer_update_event;
|
||||
CoreTiming::EventType* gyroscope_update_event;
|
||||
|
||||
std::atomic<bool> is_device_reload_pending{true};
|
||||
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
|
||||
buttons;
|
||||
std::unique_ptr<Input::AnalogDevice> circle_pad;
|
||||
std::unique_ptr<Input::MotionDevice> motion_device;
|
||||
std::unique_ptr<Input::TouchDevice> touch_device;
|
||||
};
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager);
|
||||
|
||||
/// Reload input devices. Used when input configuration changed
|
||||
void ReloadInputDevices();
|
||||
|
|
|
@ -2,27 +2,26 @@
|
|||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "core/hle/service/hid/hid_spvr.h"
|
||||
|
||||
namespace Service {
|
||||
namespace HID {
|
||||
|
||||
const Interface::FunctionInfo FunctionTable[] = {
|
||||
{0x000A0000, GetIPCHandles, "GetIPCHandles"},
|
||||
{0x000B0000, nullptr, "StartAnalogStickCalibration"},
|
||||
{0x000E0000, nullptr, "GetAnalogStickCalibrateParam"},
|
||||
{0x00110000, EnableAccelerometer, "EnableAccelerometer"},
|
||||
{0x00120000, DisableAccelerometer, "DisableAccelerometer"},
|
||||
{0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"},
|
||||
{0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"},
|
||||
{0x00150000, GetGyroscopeLowRawToDpsCoefficient, "GetGyroscopeLowRawToDpsCoefficient"},
|
||||
{0x00160000, GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"},
|
||||
{0x00170000, GetSoundVolume, "GetSoundVolume"},
|
||||
};
|
||||
|
||||
HID_SPVR_Interface::HID_SPVR_Interface() {
|
||||
Register(FunctionTable);
|
||||
Spvr::Spvr(std::shared_ptr<Module> hid) : Module::Interface(std::move(hid), "hid:SPVR", 6) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0x000A0000, &Spvr::GetIPCHandles, "GetIPCHandles"},
|
||||
{0x000B0000, nullptr, "StartAnalogStickCalibration"},
|
||||
{0x000E0000, nullptr, "GetAnalogStickCalibrateParam"},
|
||||
{0x00110000, &Spvr::EnableAccelerometer, "EnableAccelerometer"},
|
||||
{0x00120000, &Spvr::DisableAccelerometer, "DisableAccelerometer"},
|
||||
{0x00130000, &Spvr::EnableGyroscopeLow, "EnableGyroscopeLow"},
|
||||
{0x00140000, &Spvr::DisableGyroscopeLow, "DisableGyroscopeLow"},
|
||||
{0x00150000, &Spvr::GetGyroscopeLowRawToDpsCoefficient,
|
||||
"GetGyroscopeLowRawToDpsCoefficient"},
|
||||
{0x00160000, &Spvr::GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"},
|
||||
{0x00170000, &Spvr::GetSoundVolume, "GetSoundVolume"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
} // namespace HID
|
||||
|
|
|
@ -4,19 +4,15 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
|
||||
namespace Service {
|
||||
namespace HID {
|
||||
|
||||
class HID_SPVR_Interface : public Service::Interface {
|
||||
class Spvr final : public Module::Interface {
|
||||
public:
|
||||
HID_SPVR_Interface();
|
||||
|
||||
std::string GetPortName() const override {
|
||||
return "hid:SPVR";
|
||||
}
|
||||
explicit Spvr(std::shared_ptr<Module> hid);
|
||||
};
|
||||
|
||||
} // namespace HID
|
||||
} // namespace Service
|
||||
} // namespace Service
|
||||
|
|
|
@ -2,27 +2,26 @@
|
|||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "core/hle/service/hid/hid_user.h"
|
||||
|
||||
namespace Service {
|
||||
namespace HID {
|
||||
|
||||
const Interface::FunctionInfo FunctionTable[] = {
|
||||
{0x000A0000, GetIPCHandles, "GetIPCHandles"},
|
||||
{0x000B0000, nullptr, "StartAnalogStickCalibration"},
|
||||
{0x000E0000, nullptr, "GetAnalogStickCalibrateParam"},
|
||||
{0x00110000, EnableAccelerometer, "EnableAccelerometer"},
|
||||
{0x00120000, DisableAccelerometer, "DisableAccelerometer"},
|
||||
{0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"},
|
||||
{0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"},
|
||||
{0x00150000, GetGyroscopeLowRawToDpsCoefficient, "GetGyroscopeLowRawToDpsCoefficient"},
|
||||
{0x00160000, GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"},
|
||||
{0x00170000, GetSoundVolume, "GetSoundVolume"},
|
||||
};
|
||||
|
||||
HID_U_Interface::HID_U_Interface() {
|
||||
Register(FunctionTable);
|
||||
User::User(std::shared_ptr<Module> hid) : Module::Interface(std::move(hid), "hid:USER", 6) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0x000A0000, &User::GetIPCHandles, "GetIPCHandles"},
|
||||
{0x000B0000, nullptr, "StartAnalogStickCalibration"},
|
||||
{0x000E0000, nullptr, "GetAnalogStickCalibrateParam"},
|
||||
{0x00110000, &User::EnableAccelerometer, "EnableAccelerometer"},
|
||||
{0x00120000, &User::DisableAccelerometer, "DisableAccelerometer"},
|
||||
{0x00130000, &User::EnableGyroscopeLow, "EnableGyroscopeLow"},
|
||||
{0x00140000, &User::DisableGyroscopeLow, "DisableGyroscopeLow"},
|
||||
{0x00150000, &User::GetGyroscopeLowRawToDpsCoefficient,
|
||||
"GetGyroscopeLowRawToDpsCoefficient"},
|
||||
{0x00160000, &User::GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"},
|
||||
{0x00170000, &User::GetSoundVolume, "GetSoundVolume"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
} // namespace HID
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
|
||||
// This service is used for interfacing to physical user controls.
|
||||
// Uses include game pad controls, touchscreen, accelerometers, gyroscopes, and debug pad.
|
||||
|
@ -12,17 +12,10 @@
|
|||
namespace Service {
|
||||
namespace HID {
|
||||
|
||||
/**
|
||||
* HID service interface.
|
||||
*/
|
||||
class HID_U_Interface : public Service::Interface {
|
||||
class User final : public Module::Interface {
|
||||
public:
|
||||
HID_U_Interface();
|
||||
|
||||
std::string GetPortName() const override {
|
||||
return "hid:USER";
|
||||
}
|
||||
explicit User(std::shared_ptr<Module> hid);
|
||||
};
|
||||
|
||||
} // namespace HID
|
||||
} // namespace Service
|
||||
} // namespace Service
|
||||
|
|
|
@ -277,7 +277,7 @@ void Init() {
|
|||
DLP::Init();
|
||||
FRD::Init();
|
||||
GSP::InstallInterfaces(*SM::g_service_manager);
|
||||
HID::Init();
|
||||
HID::InstallInterfaces(*SM::g_service_manager);
|
||||
IR::InstallInterfaces(*SM::g_service_manager);
|
||||
MVD::Init();
|
||||
NDM::Init();
|
||||
|
@ -307,7 +307,6 @@ void Shutdown() {
|
|||
NIM::Shutdown();
|
||||
NEWS::Shutdown();
|
||||
NDM::Shutdown();
|
||||
HID::Shutdown();
|
||||
FRD::Shutdown();
|
||||
DLP::Shutdown();
|
||||
CFG::Shutdown();
|
||||
|
|
|
@ -84,86 +84,86 @@ ResultCode ConversionConfiguration::SetStandardCoefficient(
|
|||
}
|
||||
|
||||
static void SetInputFormat(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1, 1, 0);
|
||||
|
||||
conversion.input_format = static_cast<InputFormat>(cmd_buff[1]);
|
||||
conversion.input_format = rp.PopEnum<InputFormat>();
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called input_format=%hhu", static_cast<u8>(conversion.input_format));
|
||||
}
|
||||
|
||||
static void GetInputFormat(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x2, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x2, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = static_cast<u32>(conversion.input_format);
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushEnum(conversion.input_format);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called input_format=%hhu", static_cast<u8>(conversion.input_format));
|
||||
}
|
||||
|
||||
static void SetOutputFormat(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x3, 1, 0);
|
||||
|
||||
conversion.output_format = static_cast<OutputFormat>(cmd_buff[1]);
|
||||
conversion.output_format = rp.PopEnum<OutputFormat>();
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x3, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called output_format=%hhu", static_cast<u8>(conversion.output_format));
|
||||
}
|
||||
|
||||
static void GetOutputFormat(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x4, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x4, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = static_cast<u32>(conversion.output_format);
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushEnum(conversion.output_format);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called output_format=%hhu", static_cast<u8>(conversion.output_format));
|
||||
}
|
||||
|
||||
static void SetRotation(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x5, 1, 0);
|
||||
|
||||
conversion.rotation = static_cast<Rotation>(cmd_buff[1]);
|
||||
conversion.rotation = rp.PopEnum<Rotation>();
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x5, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called rotation=%hhu", static_cast<u8>(conversion.rotation));
|
||||
}
|
||||
|
||||
static void GetRotation(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x6, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x6, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = static_cast<u32>(conversion.rotation);
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushEnum(conversion.rotation);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called rotation=%hhu", static_cast<u8>(conversion.rotation));
|
||||
}
|
||||
|
||||
static void SetBlockAlignment(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x7, 1, 0);
|
||||
|
||||
conversion.block_alignment = static_cast<BlockAlignment>(cmd_buff[1]);
|
||||
conversion.block_alignment = rp.PopEnum<BlockAlignment>();
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x7, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called block_alignment=%hhu",
|
||||
static_cast<u8>(conversion.block_alignment));
|
||||
}
|
||||
|
||||
static void GetBlockAlignment(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x8, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x8, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = static_cast<u32>(conversion.block_alignment);
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushEnum(conversion.block_alignment);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called block_alignment=%hhu",
|
||||
static_cast<u8>(conversion.block_alignment));
|
||||
|
@ -177,11 +177,12 @@ static void GetBlockAlignment(Interface* self) {
|
|||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
static void SetSpacialDithering(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
spacial_dithering_enabled = cmd_buff[1] & 0xF;
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x9, 1, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x9, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
spacial_dithering_enabled = rp.Pop<u8>() & 0xF;
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_WARNING(Service_Y2R, "(STUBBED) called");
|
||||
}
|
||||
|
@ -193,7 +194,9 @@ static void SetSpacialDithering(Interface* self) {
|
|||
* 2 : u8, 0 = Disabled, 1 = Enabled
|
||||
*/
|
||||
static void GetSpacialDithering(Interface* self) {
|
||||
IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0xA, 2, 0);
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xA, 0, 0);
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(spacial_dithering_enabled != 0);
|
||||
|
||||
|
@ -208,11 +211,11 @@ static void GetSpacialDithering(Interface* self) {
|
|||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
static void SetTemporalDithering(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
temporal_dithering_enabled = cmd_buff[1] & 0xF;
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xB, 1, 0);
|
||||
temporal_dithering_enabled = rp.Pop<u8>() & 0xF;
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0xB, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_WARNING(Service_Y2R, "(STUBBED) called");
|
||||
}
|
||||
|
@ -224,11 +227,11 @@ static void SetTemporalDithering(Interface* self) {
|
|||
* 2 : u8, 0 = Disabled, 1 = Enabled
|
||||
*/
|
||||
static void GetTemporalDithering(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xC, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0xC, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = temporal_dithering_enabled;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(temporal_dithering_enabled);
|
||||
|
||||
LOG_WARNING(Service_Y2R, "(STUBBED) called");
|
||||
}
|
||||
|
@ -241,11 +244,11 @@ static void GetTemporalDithering(Interface* self) {
|
|||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
static void SetTransferEndInterrupt(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
transfer_end_interrupt_enabled = cmd_buff[1] & 0xf;
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xD, 1, 0);
|
||||
transfer_end_interrupt_enabled = rp.Pop<u8>() & 0xF;
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0xD, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_WARNING(Service_Y2R, "(STUBBED) called");
|
||||
}
|
||||
|
@ -257,11 +260,11 @@ static void SetTransferEndInterrupt(Interface* self) {
|
|||
* 2 : u8, 0 = Disabled, 1 = Enabled
|
||||
*/
|
||||
static void GetTransferEndInterrupt(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xE, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0xE, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = transfer_end_interrupt_enabled;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(transfer_end_interrupt_enabled);
|
||||
|
||||
LOG_WARNING(Service_Y2R, "(STUBBED) called");
|
||||
}
|
||||
|
@ -273,18 +276,18 @@ static void GetTransferEndInterrupt(Interface* self) {
|
|||
* 3 : The handle of the completion event
|
||||
*/
|
||||
static void GetTransferEndEvent(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xF, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).Unwrap();
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushCopyHandles(Kernel::g_handle_table.Create(completion_event).Unwrap());
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called");
|
||||
}
|
||||
|
||||
static void SetSendingY(Interface* self) {
|
||||
// The helper should be passed by argument to the function
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x00100102);
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x10, 4, 2);
|
||||
conversion.src_Y.address = rp.Pop<u32>();
|
||||
conversion.src_Y.image_size = rp.Pop<u32>();
|
||||
conversion.src_Y.transfer_unit = rp.Pop<u32>();
|
||||
|
@ -302,7 +305,7 @@ static void SetSendingY(Interface* self) {
|
|||
|
||||
static void SetSendingU(Interface* self) {
|
||||
// The helper should be passed by argument to the function
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x00110102);
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x11, 4, 2);
|
||||
conversion.src_U.address = rp.Pop<u32>();
|
||||
conversion.src_U.image_size = rp.Pop<u32>();
|
||||
conversion.src_U.transfer_unit = rp.Pop<u32>();
|
||||
|
@ -319,37 +322,41 @@ static void SetSendingU(Interface* self) {
|
|||
}
|
||||
|
||||
static void SetSendingV(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
// The helper should be passed by argument to the function
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x12, 4, 2);
|
||||
|
||||
conversion.src_V.address = cmd_buff[1];
|
||||
conversion.src_V.image_size = cmd_buff[2];
|
||||
conversion.src_V.transfer_unit = cmd_buff[3];
|
||||
conversion.src_V.gap = cmd_buff[4];
|
||||
conversion.src_V.address = rp.Pop<u32>();
|
||||
conversion.src_V.image_size = rp.Pop<u32>();
|
||||
conversion.src_V.transfer_unit = rp.Pop<u32>();
|
||||
conversion.src_V.gap = rp.Pop<u32>();
|
||||
Kernel::Handle src_process_handle = rp.PopHandle();
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x12, 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_V.image_size, conversion.src_V.transfer_unit, conversion.src_V.gap,
|
||||
cmd_buff[6]);
|
||||
static_cast<u32>(src_process_handle));
|
||||
}
|
||||
|
||||
static void SetSendingYUYV(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
// The helper should be passed by argument to the function
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x13, 4, 2);
|
||||
|
||||
conversion.src_YUYV.address = cmd_buff[1];
|
||||
conversion.src_YUYV.image_size = cmd_buff[2];
|
||||
conversion.src_YUYV.transfer_unit = cmd_buff[3];
|
||||
conversion.src_YUYV.gap = cmd_buff[4];
|
||||
conversion.src_YUYV.address = rp.Pop<u32>();
|
||||
conversion.src_YUYV.image_size = rp.Pop<u32>();
|
||||
conversion.src_YUYV.transfer_unit = rp.Pop<u32>();
|
||||
conversion.src_YUYV.gap = rp.Pop<u32>();
|
||||
Kernel::Handle src_process_handle = rp.PopHandle();
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x13, 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_YUYV.image_size, conversion.src_YUYV.transfer_unit,
|
||||
conversion.src_YUYV.gap, cmd_buff[6]);
|
||||
conversion.src_YUYV.gap, static_cast<u32>(src_process_handle));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -359,11 +366,11 @@ static void SetSendingYUYV(Interface* self) {
|
|||
* 2 : u8, 0 = Not Finished, 1 = Finished
|
||||
*/
|
||||
static void IsFinishedSendingYuv(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x14, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x14, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = 1;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(1);
|
||||
|
||||
LOG_WARNING(Service_Y2R, "(STUBBED) called");
|
||||
}
|
||||
|
@ -375,11 +382,11 @@ static void IsFinishedSendingYuv(Interface* self) {
|
|||
* 2 : u8, 0 = Not Finished, 1 = Finished
|
||||
*/
|
||||
static void IsFinishedSendingY(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x15, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x15, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = 1;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(1);
|
||||
|
||||
LOG_WARNING(Service_Y2R, "(STUBBED) called");
|
||||
}
|
||||
|
@ -391,11 +398,11 @@ static void IsFinishedSendingY(Interface* self) {
|
|||
* 2 : u8, 0 = Not Finished, 1 = Finished
|
||||
*/
|
||||
static void IsFinishedSendingU(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x16, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x16, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = 1;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(1);
|
||||
|
||||
LOG_WARNING(Service_Y2R, "(STUBBED) called");
|
||||
}
|
||||
|
@ -407,30 +414,31 @@ static void IsFinishedSendingU(Interface* self) {
|
|||
* 2 : u8, 0 = Not Finished, 1 = Finished
|
||||
*/
|
||||
static void IsFinishedSendingV(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x17, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x17, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = 1;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(1);
|
||||
|
||||
LOG_WARNING(Service_Y2R, "(STUBBED) called");
|
||||
}
|
||||
|
||||
static void SetReceiving(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x18, 4, 2);
|
||||
|
||||
conversion.dst.address = cmd_buff[1];
|
||||
conversion.dst.image_size = cmd_buff[2];
|
||||
conversion.dst.transfer_unit = cmd_buff[3];
|
||||
conversion.dst.gap = cmd_buff[4];
|
||||
conversion.dst.address = rp.Pop<u32>();
|
||||
conversion.dst.image_size = rp.Pop<u32>();
|
||||
conversion.dst.transfer_unit = rp.Pop<u32>();
|
||||
conversion.dst.gap = rp.Pop<u32>();
|
||||
Kernel::Handle dst_process_handle = rp.PopHandle();
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x18, 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, "
|
||||
"dst_process_handle=0x%08X",
|
||||
conversion.dst.image_size, conversion.dst.transfer_unit, conversion.dst.gap,
|
||||
cmd_buff[6]);
|
||||
static_cast<u32>(dst_process_handle));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -440,65 +448,67 @@ static void SetReceiving(Interface* self) {
|
|||
* 2 : u8, 0 = Not Finished, 1 = Finished
|
||||
*/
|
||||
static void IsFinishedReceiving(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x19, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x19, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = 1;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(1);
|
||||
|
||||
LOG_WARNING(Service_Y2R, "(STUBBED) called");
|
||||
}
|
||||
|
||||
static void SetInputLineWidth(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1A, 1, 0);
|
||||
u32 input_line_width = rp.Pop<u32>();
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x1A, 1, 0);
|
||||
cmd_buff[1] = conversion.SetInputLineWidth(cmd_buff[1]).raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(conversion.SetInputLineWidth(input_line_width));
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called input_line_width=%u", cmd_buff[1]);
|
||||
LOG_DEBUG(Service_Y2R, "called input_line_width=%u", input_line_width);
|
||||
}
|
||||
|
||||
static void GetInputLineWidth(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1B, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x1B, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = conversion.input_line_width;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(conversion.input_line_width);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called input_line_width=%u", conversion.input_line_width);
|
||||
}
|
||||
|
||||
static void SetInputLines(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1C, 1, 0);
|
||||
u32 input_lines = rp.Pop<u32>();
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x1C, 1, 0);
|
||||
cmd_buff[1] = conversion.SetInputLines(cmd_buff[1]).raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(conversion.SetInputLines(input_lines));
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called input_lines=%u", cmd_buff[1]);
|
||||
LOG_DEBUG(Service_Y2R, "called input_lines=%u", input_lines);
|
||||
}
|
||||
|
||||
static void GetInputLines(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1D, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x1D, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = static_cast<u32>(conversion.input_lines);
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(static_cast<u32>(conversion.input_lines));
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called input_lines=%u", conversion.input_lines);
|
||||
}
|
||||
|
||||
static void SetCoefficient(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1E, 4, 0);
|
||||
|
||||
const u16* coefficients = reinterpret_cast<const u16*>(&cmd_buff[1]);
|
||||
std::memcpy(conversion.coefficients.data(), coefficients, sizeof(CoefficientSet));
|
||||
rp.PopRaw<CoefficientSet>(conversion.coefficients);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x1E, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called coefficients=[%hX, %hX, %hX, %hX, %hX, %hX, %hX, %hX]",
|
||||
coefficients[0], coefficients[1], coefficients[2], coefficients[3], coefficients[4],
|
||||
coefficients[5], coefficients[6], coefficients[7]);
|
||||
conversion.coefficients[0], conversion.coefficients[1], conversion.coefficients[2],
|
||||
conversion.coefficients[3], conversion.coefficients[4], conversion.coefficients[5],
|
||||
conversion.coefficients[6], conversion.coefficients[7]);
|
||||
}
|
||||
|
||||
static void GetCoefficient(Interface* self) {
|
||||
|
@ -512,12 +522,11 @@ static void GetCoefficient(Interface* self) {
|
|||
}
|
||||
|
||||
static void SetStandardCoefficient(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x20, 1, 0);
|
||||
u32 index = rp.Pop<u32>();
|
||||
|
||||
u32 index = cmd_buff[1];
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x20, 1, 0);
|
||||
cmd_buff[1] = conversion.SetStandardCoefficient((StandardCoefficient)index).raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(conversion.SetStandardCoefficient(static_cast<StandardCoefficient>(index)));
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called standard_coefficient=%u", index);
|
||||
}
|
||||
|
@ -544,22 +553,21 @@ static void GetStandardCoefficient(Interface* self) {
|
|||
}
|
||||
|
||||
static void SetAlpha(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x22, 1, 0);
|
||||
conversion.alpha = rp.Pop<u32>();
|
||||
|
||||
conversion.alpha = cmd_buff[1];
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x22, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called alpha=%hu", conversion.alpha);
|
||||
}
|
||||
|
||||
static void GetAlpha(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x23, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x23, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = conversion.alpha;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(conversion.alpha);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called alpha=%hu", conversion.alpha);
|
||||
}
|
||||
|
@ -584,7 +592,7 @@ static void GetDitheringWeightParams(Interface* self) {
|
|||
}
|
||||
|
||||
static void StartConversion(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x26, 0, 0);
|
||||
|
||||
// dst_image_size would seem to be perfect for this, but it doesn't include the gap :(
|
||||
u32 total_output_size =
|
||||
|
@ -596,17 +604,17 @@ static void StartConversion(Interface* self) {
|
|||
|
||||
completion_event->Signal();
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x26, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called");
|
||||
}
|
||||
|
||||
static void StopConversion(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x27, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x27, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called");
|
||||
}
|
||||
|
@ -618,11 +626,11 @@ static void StopConversion(Interface* self) {
|
|||
* 2 : 1 if there's a conversion running, otherwise 0.
|
||||
*/
|
||||
static void IsBusyConversion(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x28, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x28, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = 0; // StartConversion always finishes immediately
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(0); // StartConversion always finishes immediately
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called");
|
||||
}
|
||||
|
@ -631,59 +639,60 @@ static void IsBusyConversion(Interface* self) {
|
|||
* Y2R_U::SetPackageParameter service function
|
||||
*/
|
||||
static void SetPackageParameter(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x29, 7, 0);
|
||||
auto params = rp.PopRaw<ConversionParameters>();
|
||||
|
||||
auto params = reinterpret_cast<const ConversionParameters*>(&cmd_buff[1]);
|
||||
conversion.input_format = params.input_format;
|
||||
conversion.output_format = params.output_format;
|
||||
conversion.rotation = params.rotation;
|
||||
conversion.block_alignment = params.block_alignment;
|
||||
|
||||
conversion.input_format = params->input_format;
|
||||
conversion.output_format = params->output_format;
|
||||
conversion.rotation = params->rotation;
|
||||
conversion.block_alignment = params->block_alignment;
|
||||
|
||||
ResultCode result = conversion.SetInputLineWidth(params->input_line_width);
|
||||
ResultCode result = conversion.SetInputLineWidth(params.input_line_width);
|
||||
|
||||
if (result.IsError())
|
||||
goto cleanup;
|
||||
|
||||
result = conversion.SetInputLines(params->input_lines);
|
||||
result = conversion.SetInputLines(params.input_lines);
|
||||
|
||||
if (result.IsError())
|
||||
goto cleanup;
|
||||
|
||||
result = conversion.SetStandardCoefficient(params->standard_coefficient);
|
||||
result = conversion.SetStandardCoefficient(params.standard_coefficient);
|
||||
|
||||
if (result.IsError())
|
||||
goto cleanup;
|
||||
|
||||
conversion.padding = params->padding;
|
||||
conversion.alpha = params->alpha;
|
||||
conversion.padding = params.padding;
|
||||
conversion.alpha = params.alpha;
|
||||
|
||||
cleanup:
|
||||
cmd_buff[0] = IPC::MakeHeader(0x29, 1, 0);
|
||||
cmd_buff[1] = result.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(result);
|
||||
|
||||
LOG_DEBUG(
|
||||
Service_Y2R,
|
||||
"called input_format=%hhu output_format=%hhu rotation=%hhu block_alignment=%hhu "
|
||||
"input_line_width=%hu input_lines=%hu standard_coefficient=%hhu reserved=%hhu alpha=%hX",
|
||||
static_cast<u8>(params->input_format), static_cast<u8>(params->output_format),
|
||||
static_cast<u8>(params->rotation), static_cast<u8>(params->block_alignment),
|
||||
params->input_line_width, params->input_lines,
|
||||
static_cast<u8>(params->standard_coefficient), params->padding, params->alpha);
|
||||
static_cast<u8>(params.input_format), static_cast<u8>(params.output_format),
|
||||
static_cast<u8>(params.rotation), static_cast<u8>(params.block_alignment),
|
||||
params.input_line_width, params.input_lines, static_cast<u8>(params.standard_coefficient),
|
||||
params.padding, params.alpha);
|
||||
}
|
||||
|
||||
static void PingProcess(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x2A, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x2A, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = 0;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(0);
|
||||
|
||||
LOG_WARNING(Service_Y2R, "(STUBBED) called");
|
||||
}
|
||||
|
||||
static void DriverInitialize(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x2B, 0, 0);
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
|
||||
conversion.input_format = InputFormat::YUV422_Indiv8;
|
||||
conversion.output_format = OutputFormat::RGBA8;
|
||||
|
@ -702,17 +711,16 @@ static void DriverInitialize(Interface* self) {
|
|||
|
||||
completion_event->Clear();
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x2B, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called");
|
||||
}
|
||||
|
||||
static void DriverFinalize(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x2C, 0, 0);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x2C, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called");
|
||||
}
|
||||
|
@ -787,4 +795,4 @@ Y2R_U::~Y2R_U() {
|
|||
}
|
||||
|
||||
} // namespace Y2R
|
||||
} // namespace Service
|
||||
} // namespace Service
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
#include "common/assert.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/scm_rev.h"
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#endif
|
||||
#include "core/core.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
|
@ -20,6 +22,7 @@
|
|||
|
||||
namespace Core {
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
static const char* CpuVendorToStr(Common::CPUVendor vendor) {
|
||||
switch (vendor) {
|
||||
case Common::CPUVendor::INTEL:
|
||||
|
@ -31,6 +34,7 @@ static const char* CpuVendorToStr(Common::CPUVendor vendor) {
|
|||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
#endif
|
||||
|
||||
static u64 GenerateTelemetryId() {
|
||||
u64 telemetry_id{};
|
||||
|
@ -121,7 +125,8 @@ TelemetrySession::TelemetrySession() {
|
|||
AddField(Telemetry::FieldType::App, "BuildDate", Common::g_build_date);
|
||||
AddField(Telemetry::FieldType::App, "BuildName", Common::g_build_name);
|
||||
|
||||
// Log user system information
|
||||
// Log user system information
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
AddField(Telemetry::FieldType::UserSystem, "CPU_Model", Common::GetCPUCaps().cpu_string);
|
||||
AddField(Telemetry::FieldType::UserSystem, "CPU_BrandString",
|
||||
Common::GetCPUCaps().brand_string);
|
||||
|
@ -143,6 +148,9 @@ TelemetrySession::TelemetrySession() {
|
|||
Common::GetCPUCaps().sse4_1);
|
||||
AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE42",
|
||||
Common::GetCPUCaps().sse4_2);
|
||||
#else
|
||||
AddField(Telemetry::FieldType::UserSystem, "CPU_Model", "Other");
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Apple");
|
||||
#elif defined(_WIN32)
|
||||
|
|
|
@ -8,7 +8,7 @@ add_library(input_common STATIC
|
|||
motion_emu.cpp
|
||||
motion_emu.h
|
||||
|
||||
$<$<BOOL:SDL2_FOUND>:sdl/sdl.cpp sdl/sdl.h>
|
||||
$<$<BOOL:${SDL2_FOUND}>:sdl/sdl.cpp sdl/sdl.h>
|
||||
)
|
||||
|
||||
create_target_directory_groups(input_common)
|
||||
|
|
|
@ -17,11 +17,12 @@ namespace InputCommon {
|
|||
// Implementation class of the motion emulation device
|
||||
class MotionEmuDevice {
|
||||
public:
|
||||
MotionEmuDevice(int update_millisecond, float sensitivity)
|
||||
MotionEmuDevice(int update_millisecond, float sensitivity, float tilt_clamp)
|
||||
: update_millisecond(update_millisecond),
|
||||
update_duration(std::chrono::duration_cast<std::chrono::steady_clock::duration>(
|
||||
std::chrono::milliseconds(update_millisecond))),
|
||||
sensitivity(sensitivity), motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {}
|
||||
sensitivity(sensitivity), tilt_clamp(tilt_clamp),
|
||||
motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {}
|
||||
|
||||
~MotionEmuDevice() {
|
||||
if (motion_emu_thread.joinable()) {
|
||||
|
@ -44,7 +45,7 @@ public:
|
|||
} else {
|
||||
tilt_direction = mouse_move.Cast<float>();
|
||||
tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * sensitivity, 0.0f,
|
||||
MathUtil::PI * 0.5f);
|
||||
MathUtil::PI * this->tilt_clamp / 180.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +71,7 @@ private:
|
|||
std::mutex tilt_mutex;
|
||||
Math::Vec2<float> tilt_direction;
|
||||
float tilt_angle = 0;
|
||||
float tilt_clamp = 90;
|
||||
|
||||
bool is_tilting = false;
|
||||
|
||||
|
@ -126,8 +128,8 @@ private:
|
|||
// can forward all the inputs to the implementation only when it is valid.
|
||||
class MotionEmuDeviceWrapper : public Input::MotionDevice {
|
||||
public:
|
||||
MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) {
|
||||
device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity);
|
||||
MotionEmuDeviceWrapper(int update_millisecond, float sensitivity, float tilt_clamp) {
|
||||
device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity, tilt_clamp);
|
||||
}
|
||||
|
||||
std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() const {
|
||||
|
@ -140,7 +142,9 @@ public:
|
|||
std::unique_ptr<Input::MotionDevice> MotionEmu::Create(const Common::ParamPackage& params) {
|
||||
int update_period = params.Get("update_period", 100);
|
||||
float sensitivity = params.Get("sensitivity", 0.01f);
|
||||
auto device_wrapper = std::make_unique<MotionEmuDeviceWrapper>(update_period, sensitivity);
|
||||
float tilt_clamp = params.Get("tilt_clamp", 90.0f);
|
||||
auto device_wrapper =
|
||||
std::make_unique<MotionEmuDeviceWrapper>(update_period, sensitivity, tilt_clamp);
|
||||
// Previously created device is disconnected here. Having two motion devices for 3DS is not
|
||||
// expected.
|
||||
current_device = device_wrapper->device;
|
||||
|
|
|
@ -221,6 +221,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
|||
MICROPROFILE_SCOPE(GPU_Drawing);
|
||||
immediate_attribute_id = 0;
|
||||
|
||||
Shader::OutputVertex::ValidateSemantics(regs.rasterizer);
|
||||
|
||||
auto* shader_engine = Shader::GetEngine();
|
||||
shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset);
|
||||
|
||||
|
@ -289,6 +291,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
|||
// Later, these can be compiled and cached.
|
||||
const u32 base_address = regs.pipeline.vertex_attributes.GetPhysicalBaseAddress();
|
||||
VertexLoader loader(regs.pipeline);
|
||||
Shader::OutputVertex::ValidateSemantics(regs.rasterizer);
|
||||
|
||||
// Load vertices
|
||||
bool is_indexed = (id == PICA_REG_INDEX(pipeline.trigger_draw_indexed));
|
||||
|
|
|
@ -87,6 +87,8 @@ struct RasterizerRegs {
|
|||
BitField<8, 5, Semantic> map_y;
|
||||
BitField<16, 5, Semantic> map_z;
|
||||
BitField<24, 5, Semantic> map_w;
|
||||
|
||||
u32 raw;
|
||||
} vs_output_attributes[7];
|
||||
|
||||
INSERT_PADDING_WORDS(0xe);
|
||||
|
|
|
@ -65,6 +65,7 @@ PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) {
|
|||
PicaShaderConfig res;
|
||||
|
||||
auto& state = res.state;
|
||||
// Memset structure to zero padding bits, so that they will be deterministic when hashing
|
||||
std::memset(&state, 0, sizeof(PicaShaderConfig::State));
|
||||
|
||||
state.scissor_test_mode = regs.rasterizer.scissor_test.mode;
|
||||
|
|
|
@ -131,10 +131,6 @@ union PicaShaderConfig {
|
|||
|
||||
} state;
|
||||
};
|
||||
#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
|
||||
static_assert(std::is_trivially_copyable<PicaShaderConfig::State>::value,
|
||||
"PicaShaderConfig::State must be trivially copyable");
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Generates the GLSL vertex shader program source code for the current Pica state
|
||||
|
@ -156,7 +152,7 @@ namespace std {
|
|||
template <>
|
||||
struct hash<GLShader::PicaShaderConfig> {
|
||||
size_t operator()(const GLShader::PicaShaderConfig& k) const {
|
||||
return Common::ComputeHash64(&k.state, sizeof(GLShader::PicaShaderConfig::State));
|
||||
return Common::ComputeStructHash64(k.state);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include "common/bit_set.h"
|
||||
|
@ -21,32 +22,41 @@ namespace Pica {
|
|||
|
||||
namespace Shader {
|
||||
|
||||
void OutputVertex::ValidateSemantics(const RasterizerRegs& regs) {
|
||||
unsigned int num_attributes = regs.vs_output_total;
|
||||
ASSERT(num_attributes <= 7);
|
||||
for (size_t attrib = 0; attrib < num_attributes; ++attrib) {
|
||||
u32 output_register_map = regs.vs_output_attributes[attrib].raw;
|
||||
for (size_t comp = 0; comp < 4; ++comp) {
|
||||
u32 semantic = (output_register_map >> (8 * comp)) & 0x1F;
|
||||
ASSERT_MSG(semantic < 24 || semantic == RasterizerRegs::VSOutputAttributes::INVALID,
|
||||
"Invalid/unknown semantic id: %" PRIu32, semantic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OutputVertex OutputVertex::FromAttributeBuffer(const RasterizerRegs& regs,
|
||||
const AttributeBuffer& input) {
|
||||
// Setup output data
|
||||
union {
|
||||
OutputVertex ret{};
|
||||
std::array<float24, 24> vertex_slots;
|
||||
// Allow us to overflow OutputVertex to avoid branches, since
|
||||
// RasterizerRegs::VSOutputAttributes::INVALID would write to slot 31, which
|
||||
// would be out of bounds otherwise.
|
||||
std::array<float24, 32> vertex_slots_overflow;
|
||||
};
|
||||
static_assert(sizeof(vertex_slots) == sizeof(ret), "Struct and array have different sizes.");
|
||||
|
||||
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];
|
||||
// Assert that OutputVertex has enough space for 24 semantic registers
|
||||
static_assert(sizeof(std::array<float24, 24>) == sizeof(ret),
|
||||
"Struct and array have different sizes.");
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
unsigned int num_attributes = regs.vs_output_total & 7;
|
||||
for (size_t attrib = 0; attrib < num_attributes; ++attrib) {
|
||||
const auto output_register_map = regs.vs_output_attributes[attrib];
|
||||
vertex_slots_overflow[output_register_map.map_x] = input.attr[attrib][0];
|
||||
vertex_slots_overflow[output_register_map.map_y] = input.attr[attrib][1];
|
||||
vertex_slots_overflow[output_register_map.map_z] = input.attr[attrib][2];
|
||||
vertex_slots_overflow[output_register_map.map_w] = input.attr[attrib][3];
|
||||
}
|
||||
|
||||
// The hardware takes the absolute and saturates vertex colors like this, *before* doing
|
||||
|
|
|
@ -50,6 +50,7 @@ struct OutputVertex {
|
|||
INSERT_PADDING_WORDS(1);
|
||||
Math::Vec2<float24> tc2;
|
||||
|
||||
static void ValidateSemantics(const RasterizerRegs& regs);
|
||||
static OutputVertex FromAttributeBuffer(const RasterizerRegs& regs,
|
||||
const AttributeBuffer& output);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue