summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/citra/CMakeLists.txt11
-rw-r--r--src/citra_qt/CMakeLists.txt29
-rw-r--r--src/citra_qt/bootmanager.cpp5
-rw-r--r--src/citra_qt/debugger/wait_tree.cpp3
-rw-r--r--src/citra_qt/game_list.cpp28
-rw-r--r--src/citra_qt/game_list.h3
-rw-r--r--src/citra_qt/game_list_p.h5
-rw-r--r--src/citra_qt/main.cpp159
-rw-r--r--src/citra_qt/main.h29
-rw-r--r--src/common/file_util.cpp11
-rw-r--r--src/common/scm_rev.cpp.in2
-rw-r--r--src/core/CMakeLists.txt12
-rw-r--r--src/core/arm/dyncom/arm_dyncom_interpreter.cpp4
-rw-r--r--src/core/arm/dyncom/arm_dyncom_trans.h2
-rw-r--r--src/core/arm/skyeye_common/armstate.cpp2
-rw-r--r--src/core/core.cpp2
-rw-r--r--src/core/file_sys/archive_source_sd_savedata.cpp5
-rw-r--r--src/core/file_sys/archive_source_sd_savedata.h2
-rw-r--r--src/core/gdbstub/gdbstub.cpp37
-rw-r--r--src/core/gdbstub/gdbstub.h11
-rw-r--r--src/core/hle/kernel/address_arbiter.cpp2
-rw-r--r--src/core/hle/kernel/kernel.cpp59
-rw-r--r--src/core/hle/kernel/kernel.h8
-rw-r--r--src/core/hle/kernel/thread.cpp112
-rw-r--r--src/core/hle/kernel/thread.h49
-rw-r--r--src/core/hle/kernel/timer.cpp4
-rw-r--r--src/core/hle/service/act/act.cpp18
-rw-r--r--src/core/hle/service/act/act.h14
-rw-r--r--src/core/hle/service/act/act_a.cpp (renamed from src/core/hle/service/act_a.cpp)3
-rw-r--r--src/core/hle/service/act/act_a.h (renamed from src/core/hle/service/act_a.h)0
-rw-r--r--src/core/hle/service/act/act_u.cpp (renamed from src/core/hle/service/act_u.cpp)3
-rw-r--r--src/core/hle/service/act/act_u.h (renamed from src/core/hle/service/act_u.h)0
-rw-r--r--src/core/hle/service/cecd/cecd.cpp10
-rw-r--r--src/core/hle/service/cecd/cecd_ndm.cpp23
-rw-r--r--src/core/hle/service/cecd/cecd_ndm.h22
-rw-r--r--src/core/hle/service/cecd/cecd_s.cpp26
-rw-r--r--src/core/hle/service/cecd/cecd_s.h4
-rw-r--r--src/core/hle/service/cecd/cecd_u.cpp3
-rw-r--r--src/core/hle/service/cecd/cecd_u.h4
-rw-r--r--src/core/hle/service/dlp/dlp.h2
-rw-r--r--src/core/hle/service/fs/archive.cpp20
-rw-r--r--src/core/hle/service/hid/hid.cpp3
-rw-r--r--src/core/hle/service/service.cpp6
-rw-r--r--src/core/hle/service/srv.h2
-rw-r--r--src/core/hle/svc.cpp189
-rw-r--r--src/core/loader/loader.h9
-rw-r--r--src/core/loader/ncch.cpp12
-rw-r--r--src/core/loader/ncch.h7
-rw-r--r--src/video_core/CMakeLists.txt1
-rw-r--r--src/video_core/command_processor.cpp9
-rw-r--r--src/video_core/debug_utils/debug_utils.cpp20
-rw-r--r--src/video_core/debug_utils/debug_utils.h6
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp8
-rw-r--r--src/video_core/shader/debug_data.h186
-rw-r--r--src/video_core/shader/shader.cpp26
-rw-r--r--src/video_core/shader/shader.h199
-rw-r--r--src/video_core/shader/shader_interpreter.cpp235
-rw-r--r--src/video_core/shader/shader_interpreter.h7
-rw-r--r--src/video_core/shader/shader_jit_x64.cpp16
-rw-r--r--src/video_core/shader/shader_jit_x64.h2
60 files changed, 966 insertions, 725 deletions
diff --git a/src/citra/CMakeLists.txt b/src/citra/CMakeLists.txt
index f9c488a1a..ecb5d2dfe 100644
--- a/src/citra/CMakeLists.txt
+++ b/src/citra/CMakeLists.txt
@@ -1,3 +1,5 @@
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
+
set(SRCS
emu_window/emu_window_sdl2.cpp
citra.cpp
@@ -28,11 +30,6 @@ if(UNIX AND NOT APPLE)
endif()
if (MSVC)
- include(WindowsCopyFiles)
-
- set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/")
-
- windows_copy_files(citra ${SDL2_DLL_DIR} ${DLL_DEST} SDL2.dll)
-
- unset(DLL_DEST)
+ include(CopyCitraSDLDeps)
+ copy_citra_SDL_deps(citra)
endif()
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index a9dacd5f1..e1b3566bf 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -1,5 +1,6 @@
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
set(SRCS
config.cpp
@@ -107,27 +108,9 @@ if(UNIX AND NOT APPLE)
install(TARGETS citra-qt RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
endif()
-if (Qt5_FOUND AND MSVC)
- include(WindowsCopyFiles)
-
- set(Qt5_DLL_DIR "${Qt5_DIR}/../../../bin")
- set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/")
- set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/")
- set(PLATFORMS ${DLL_DEST}platforms/)
-
- windows_copy_files(citra-qt ${Qt5_DLL_DIR} ${DLL_DEST}
- icudt*.dll
- icuin*.dll
- icuuc*.dll
- Qt5Core$<$<CONFIG:Debug>:d>.*
- Qt5Gui$<$<CONFIG:Debug>:d>.*
- Qt5OpenGL$<$<CONFIG:Debug>:d>.*
- Qt5Widgets$<$<CONFIG:Debug>:d>.*
- )
- windows_copy_files(citra-qt ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$<CONFIG:Debug>:d>.*)
-
- unset(Qt5_DLL_DIR)
- unset(Qt5_PLATFORMS_DIR)
- unset(DLL_DEST)
- unset(PLATFORMS)
+if (MSVC)
+ include(CopyCitraQt5Deps)
+ include(CopyCitraSDLDeps)
+ copy_citra_Qt5_deps(citra-qt)
+ copy_citra_SDL_deps(citra-qt)
endif()
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp
index 7699ca8d0..c7eb2aafc 100644
--- a/src/citra_qt/bootmanager.cpp
+++ b/src/citra_qt/bootmanager.cpp
@@ -19,11 +19,6 @@
#include "video_core/debug_utils/debug_utils.h"
#include "video_core/video_core.h"
-#define APP_NAME "citra"
-#define APP_VERSION "0.1-" VERSION
-#define APP_TITLE APP_NAME " " APP_VERSION
-#define COPYRIGHT "Copyright (C) 2013-2014 Citra Team"
-
EmuThread::EmuThread(GRenderWindow* render_window)
: exec_step(false), running(false), stop_run(false), render_window(render_window) {}
diff --git a/src/citra_qt/debugger/wait_tree.cpp b/src/citra_qt/debugger/wait_tree.cpp
index 51e70fae3..5a308bf7f 100644
--- a/src/citra_qt/debugger/wait_tree.cpp
+++ b/src/citra_qt/debugger/wait_tree.cpp
@@ -229,7 +229,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
list.push_back(std::make_unique<WaitTreeMutexList>(thread.held_mutexes));
}
if (thread.status == THREADSTATUS_WAIT_SYNCH) {
- list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects, thread.wait_all));
+ list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects,
+ thread.IsSleepingOnWaitAll()));
}
return list;
diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp
index e536628dd..09469f3c5 100644
--- a/src/citra_qt/game_list.cpp
+++ b/src/citra_qt/game_list.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <QHeaderView>
+#include <QMenu>
#include <QThreadPool>
#include <QVBoxLayout>
#include "common/common_paths.h"
@@ -28,6 +29,7 @@ GameList::GameList(QWidget* parent) : QWidget{parent} {
tree_view->setSortingEnabled(true);
tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
tree_view->setUniformRowHeights(true);
+ tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
item_model->insertColumns(0, COLUMN_COUNT);
item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name");
@@ -35,10 +37,10 @@ GameList::GameList(QWidget* parent) : QWidget{parent} {
item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size");
connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
+ connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
// We must register all custom types with the Qt Automoc system so that we are able to use it
- // with
- // signals/slots. In this case, QList falls under the umbrells of custom types.
+ // with signals/slots. In this case, QList falls under the umbrells of custom types.
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
layout->addWidget(tree_view);
@@ -71,6 +73,23 @@ void GameList::DonePopulating() {
tree_view->setEnabled(true);
}
+void GameList::PopupContextMenu(const QPoint& menu_location) {
+ QModelIndex item = tree_view->indexAt(menu_location);
+ if (!item.isValid())
+ return;
+
+ int row = item_model->itemFromIndex(item)->row();
+ QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME);
+ u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong();
+
+ QMenu context_menu;
+ QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
+ open_save_location->setEnabled(program_id != 0);
+ connect(open_save_location, &QAction::triggered,
+ [&]() { emit OpenSaveFolderRequested(program_id); });
+ context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
+}
+
void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
if (!FileUtil::Exists(dir_path.toStdString()) ||
!FileUtil::IsDirectory(dir_path.toStdString())) {
@@ -128,8 +147,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
std::vector<u8> smdh;
loader->ReadIcon(smdh);
+ u64 program_id = 0;
+ loader->ReadProgramId(program_id);
+
emit EntryReady({
- new GameListItemPath(QString::fromStdString(physical_name), smdh),
+ new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id),
new GameListItem(
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
new GameListItemSize(FileUtil::GetSize(physical_name)),
diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h
index 30b2c79a8..1abf10051 100644
--- a/src/citra_qt/game_list.h
+++ b/src/citra_qt/game_list.h
@@ -36,12 +36,15 @@ public:
signals:
void GameChosen(QString game_path);
void ShouldCancelWorker();
+ void OpenSaveFolderRequested(u64 program_id);
private:
void AddEntry(const QList<QStandardItem*>& entry_items);
void ValidateEntry(const QModelIndex& item);
void DonePopulating();
+ void PopupContextMenu(const QPoint& menu_location);
+
QTreeView* tree_view = nullptr;
QStandardItemModel* item_model = nullptr;
GameListWorker* current_worker = nullptr;
diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h
index 5ca3fe991..a15f06c5f 100644
--- a/src/citra_qt/game_list_p.h
+++ b/src/citra_qt/game_list_p.h
@@ -71,10 +71,13 @@ class GameListItemPath : public GameListItem {
public:
static const int FullPathRole = Qt::UserRole + 1;
static const int TitleRole = Qt::UserRole + 2;
+ static const int ProgramIdRole = Qt::UserRole + 3;
GameListItemPath() : GameListItem() {}
- GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data) : GameListItem() {
+ GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data, u64 program_id)
+ : GameListItem() {
setData(game_path, FullPathRole);
+ setData(qulonglong(program_id), ProgramIdRole);
if (!Loader::IsValidSMDH(smdh_data)) {
// SMDH is not valid, set a default icon
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index a3887f9ab..4486f0870 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cinttypes>
#include <clocale>
#include <memory>
#include <thread>
@@ -41,6 +42,7 @@
#include "common/string_util.h"
#include "core/arm/disassembler/load_symbol_map.h"
#include "core/core.h"
+#include "core/file_sys/archive_source_sd_savedata.h"
#include "core/gdbstub/gdbstub.h"
#include "core/loader/loader.h"
#include "core/settings.h"
@@ -58,6 +60,36 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
ui.setupUi(this);
statusBar()->hide();
+ InitializeWidgets();
+ InitializeDebugMenuActions();
+ InitializeRecentFileMenuActions();
+ InitializeHotkeys();
+
+ SetDefaultUIGeometry();
+ RestoreUIState();
+
+ ConnectWidgetEvents();
+
+ setWindowTitle(QString("Citra | %1-%2").arg(Common::g_scm_branch, Common::g_scm_desc));
+ show();
+
+ game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
+
+ QStringList args = QApplication::arguments();
+ if (args.length() >= 2) {
+ BootGame(args[1].toStdString());
+ }
+}
+
+GMainWindow::~GMainWindow() {
+ // will get automatically deleted otherwise
+ if (render_window->parent() == nullptr)
+ delete render_window;
+
+ Pica::g_debug_context.reset();
+}
+
+void GMainWindow::InitializeWidgets() {
render_window = new GRenderWindow(this, emu_thread.get());
render_window->hide();
@@ -93,25 +125,27 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
addDockWidget(Qt::RightDockWidgetArea, graphicsCommandsWidget);
graphicsCommandsWidget->hide();
- auto graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(Pica::g_debug_context, this);
+ graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(Pica::g_debug_context, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget);
graphicsBreakpointsWidget->hide();
- auto graphicsVertexShaderWidget = new GraphicsVertexShaderWidget(Pica::g_debug_context, this);
+ graphicsVertexShaderWidget = new GraphicsVertexShaderWidget(Pica::g_debug_context, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget);
graphicsVertexShaderWidget->hide();
- auto graphicsTracingWidget = new GraphicsTracingWidget(Pica::g_debug_context, this);
+ graphicsTracingWidget = new GraphicsTracingWidget(Pica::g_debug_context, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget);
graphicsTracingWidget->hide();
- auto graphicsSurfaceViewerAction = new QAction(tr("Create Pica Surface Viewer"), this);
- connect(graphicsSurfaceViewerAction, SIGNAL(triggered()), this,
- SLOT(OnCreateGraphicsSurfaceViewer()));
-
waitTreeWidget = new WaitTreeWidget(this);
addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
waitTreeWidget->hide();
+}
+
+void GMainWindow::InitializeDebugMenuActions() {
+ auto graphicsSurfaceViewerAction = new QAction(tr("Create Pica Surface Viewer"), this);
+ connect(graphicsSurfaceViewerAction, SIGNAL(triggered()), this,
+ SLOT(OnCreateGraphicsSurfaceViewer()));
QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging"));
debug_menu->addAction(graphicsSurfaceViewerAction);
@@ -129,19 +163,47 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction());
debug_menu->addAction(graphicsTracingWidget->toggleViewAction());
debug_menu->addAction(waitTreeWidget->toggleViewAction());
+}
+
+void GMainWindow::InitializeRecentFileMenuActions() {
+ for (int i = 0; i < max_recent_files_item; ++i) {
+ actions_recent_files[i] = new QAction(this);
+ actions_recent_files[i]->setVisible(false);
+ connect(actions_recent_files[i], SIGNAL(triggered()), this, SLOT(OnMenuRecentFile()));
+
+ ui.menu_recent_files->addAction(actions_recent_files[i]);
+ }
+
+ UpdateRecentFiles();
+}
+
+void GMainWindow::InitializeHotkeys() {
+ RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
+ RegisterHotkey("Main Window", "Swap Screens", QKeySequence::NextChild);
+ RegisterHotkey("Main Window", "Start Emulation");
+ LoadHotkeys();
+
+ connect(GetHotkey("Main Window", "Load File", this), SIGNAL(activated()), this,
+ SLOT(OnMenuLoadFile()));
+ connect(GetHotkey("Main Window", "Start Emulation", this), SIGNAL(activated()), this,
+ SLOT(OnStartGame()));
+ connect(GetHotkey("Main Window", "Swap Screens", render_window), SIGNAL(activated()), this,
+ SLOT(OnSwapScreens()));
+}
- // Set default UI state
+void GMainWindow::SetDefaultUIGeometry() {
// geometry: 55% of the window contents are in the upper screen half, 45% in the lower half
- QDesktopWidget* desktop = ((QApplication*)QApplication::instance())->desktop();
- QRect screenRect = desktop->screenGeometry(this);
- int x, y, w, h;
- w = screenRect.width() * 2 / 3;
- h = screenRect.height() / 2;
- x = (screenRect.x() + screenRect.width()) / 2 - w / 2;
- y = (screenRect.y() + screenRect.height()) / 2 - h * 55 / 100;
+ const QRect screenRect = QApplication::desktop()->screenGeometry(this);
+
+ const int w = screenRect.width() * 2 / 3;
+ const int h = screenRect.height() / 2;
+ const int x = (screenRect.x() + screenRect.width()) / 2 - w / 2;
+ const int y = (screenRect.y() + screenRect.height()) / 2 - h * 55 / 100;
+
setGeometry(x, y, w, h);
+}
- // Restore UI state
+void GMainWindow::RestoreUIState() {
restoreGeometry(UISettings::values.geometry);
restoreState(UISettings::values.state);
render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
@@ -157,20 +219,13 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
ui.actionDisplay_widget_title_bars->setChecked(UISettings::values.display_titlebar);
OnDisplayTitleBars(ui.actionDisplay_widget_title_bars->isChecked());
+}
- // Prepare actions for recent files
- for (int i = 0; i < max_recent_files_item; ++i) {
- actions_recent_files[i] = new QAction(this);
- actions_recent_files[i]->setVisible(false);
- connect(actions_recent_files[i], SIGNAL(triggered()), this, SLOT(OnMenuRecentFile()));
-
- ui.menu_recent_files->addAction(actions_recent_files[i]);
- }
- UpdateRecentFiles();
-
- // Setup connections
+void GMainWindow::ConnectWidgetEvents() {
connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)),
Qt::DirectConnection);
+ connect(game_list, SIGNAL(OpenSaveFolderRequested(u64)), this,
+ SLOT(OnGameListOpenSaveFolder(u64)), Qt::DirectConnection);
connect(ui.action_Configure, SIGNAL(triggered()), this, SLOT(OnConfigure()));
connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile()),
Qt::DirectConnection);
@@ -197,40 +252,6 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
connect(this, SIGNAL(EmulationStarting(EmuThread*)), waitTreeWidget,
SLOT(OnEmulationStarting(EmuThread*)));
connect(this, SIGNAL(EmulationStopping()), waitTreeWidget, SLOT(OnEmulationStopping()));
-
- // Setup hotkeys
- RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
- RegisterHotkey("Main Window", "Swap Screens", QKeySequence::NextChild);
- RegisterHotkey("Main Window", "Start Emulation");
- LoadHotkeys();
-
- connect(GetHotkey("Main Window", "Load File", this), SIGNAL(activated()), this,
- SLOT(OnMenuLoadFile()));
- connect(GetHotkey("Main Window", "Start Emulation", this), SIGNAL(activated()), this,
- SLOT(OnStartGame()));
- connect(GetHotkey("Main Window", "Swap Screens", this), SIGNAL(activated()), this,
- SLOT(OnSwapScreens()));
-
- std::string window_title =
- Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc);
- setWindowTitle(window_title.c_str());
-
- show();
-
- game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
-
- QStringList args = QApplication::arguments();
- if (args.length() >= 2) {
- BootGame(args[1].toStdString());
- }
-}
-
-GMainWindow::~GMainWindow() {
- // will get automatically deleted otherwise
- if (render_window->parent() == nullptr)
- delete render_window;
-
- Pica::g_debug_context.reset();
}
void GMainWindow::OnDisplayTitleBars(bool show) {
@@ -386,6 +407,7 @@ void GMainWindow::BootGame(const std::string& filename) {
game_list->hide();
}
render_window->show();
+ render_window->setFocus();
emulation_running = true;
OnStartGame();
@@ -460,6 +482,21 @@ void GMainWindow::OnGameListLoadFile(QString game_path) {
BootGame(game_path.toStdString());
}
+void GMainWindow::OnGameListOpenSaveFolder(u64 program_id) {
+ std::string sdmc_dir = FileUtil::GetUserPath(D_SDMC_IDX);
+ std::string path = FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor(sdmc_dir, program_id);
+ QString qpath = QString::fromStdString(path);
+
+ QDir dir(qpath);
+ if (!dir.exists()) {
+ QMessageBox::critical(this, tr("Error Opening Save Folder"), tr("Folder does not exist!"));
+ return;
+ }
+
+ LOG_INFO(Frontend, "Opening save data path for program_id=%" PRIu64, program_id);
+ QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
+}
+
void GMainWindow::OnMenuLoadFile() {
QString filename =
QFileDialog::getOpenFileName(this, tr("Load File"), UISettings::values.roms_path,
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index f87178227..a2fd45c47 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -9,18 +9,21 @@
#include <QMainWindow>
#include "ui_main.h"
+class CallstackWidget;
class Config;
+class DisassemblerWidget;
+class EmuThread;
class GameList;
class GImageInfo;
+class GPUCommandStreamWidget;
+class GPUCommandListWidget;
+class GraphicsBreakPointsWidget;
+class GraphicsTracingWidget;
+class GraphicsVertexShaderWidget;
class GRenderWindow;
-class EmuThread;
-class ProfilerWidget;
class MicroProfileDialog;
-class DisassemblerWidget;
+class ProfilerWidget;
class RegistersWidget;
-class CallstackWidget;
-class GPUCommandStreamWidget;
-class GPUCommandListWidget;
class WaitTreeWidget;
class GMainWindow : public QMainWindow {
@@ -60,6 +63,16 @@ signals:
void EmulationStopping();
private:
+ void InitializeWidgets();
+ void InitializeDebugMenuActions();
+ void InitializeRecentFileMenuActions();
+ void InitializeHotkeys();
+
+ void SetDefaultUIGeometry();
+ void RestoreUIState();
+
+ void ConnectWidgetEvents();
+
/**
* Initializes the emulation system.
* @param system_mode The system mode with which to intialize the kernel.
@@ -105,6 +118,7 @@ private slots:
void OnStopGame();
/// Called whenever a user selects a game in the game list widget.
void OnGameListLoadFile(QString game_path);
+ void OnGameListOpenSaveFolder(u64 program_id);
void OnMenuLoadFile();
void OnMenuLoadSymbolMap();
/// Called whenever a user selects the "File->Select Game List Root" menu item
@@ -135,6 +149,9 @@ private:
CallstackWidget* callstackWidget;
GPUCommandStreamWidget* graphicsWidget;
GPUCommandListWidget* graphicsCommandsWidget;
+ GraphicsBreakPointsWidget* graphicsBreakpointsWidget;
+ GraphicsVertexShaderWidget* graphicsVertexShaderWidget;
+ GraphicsTracingWidget* graphicsTracingWidget;
WaitTreeWidget* waitTreeWidget;
QAction* actions_recent_files[max_recent_files_item];
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index b6161f2dc..c618495f7 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -40,9 +40,20 @@
#endif
#if defined(__APPLE__)
+// CFURL contains __attribute__ directives that gcc does not know how to parse, so we need to just
+// ignore them if we're not using clang. The macro is only used to prevent linking against
+// functions that don't exist on older versions of macOS, and the worst case scenario is a linker
+// error, so this is perfectly safe, just inconvenient.
+#ifndef __clang__
+#define availability(...)
+#endif
#include <CoreFoundation/CFBundle.h>
#include <CoreFoundation/CFString.h>
#include <CoreFoundation/CFURL.h>
+#ifdef availability
+#undef availability
+#endif
+
#endif
#include <algorithm>
diff --git a/src/common/scm_rev.cpp.in b/src/common/scm_rev.cpp.in
index 09366e801..79b404bb8 100644
--- a/src/common/scm_rev.cpp.in
+++ b/src/common/scm_rev.cpp.in
@@ -1,5 +1,5 @@
// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/scm_rev.h"
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 7b3b4389a..af224166a 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -53,8 +53,9 @@ set(SRCS
hle/kernel/timer.cpp
hle/kernel/vm_manager.cpp
hle/service/ac_u.cpp
- hle/service/act_a.cpp
- hle/service/act_u.cpp
+ hle/service/act/act.cpp
+ hle/service/act/act_a.cpp
+ hle/service/act/act_u.cpp
hle/service/am/am.cpp
hle/service/am/am_app.cpp
hle/service/am/am_net.cpp
@@ -74,6 +75,7 @@ set(SRCS
hle/service/cam/cam_s.cpp
hle/service/cam/cam_u.cpp
hle/service/cecd/cecd.cpp
+ hle/service/cecd/cecd_ndm.cpp
hle/service/cecd/cecd_s.cpp
hle/service/cecd/cecd_u.cpp
hle/service/cfg/cfg.cpp
@@ -218,8 +220,9 @@ set(HEADERS
hle/kernel/vm_manager.h
hle/result.h
hle/service/ac_u.h
- hle/service/act_a.h
- hle/service/act_u.h
+ hle/service/act/act.h
+ hle/service/act/act_a.h
+ hle/service/act/act_u.h
hle/service/am/am.h
hle/service/am/am_app.h
hle/service/am/am_net.h
@@ -239,6 +242,7 @@ set(HEADERS
hle/service/cam/cam_s.h
hle/service/cam/cam_u.h
hle/service/cecd/cecd.h
+ hle/service/cecd/cecd_ndm.h
hle/service/cecd/cecd_s.h
hle/service/cecd/cecd_u.h
hle/service/cfg/cfg.h
diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp
index 7b8616702..67c45640a 100644
--- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp
+++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp
@@ -953,7 +953,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) {
#define GDB_BP_CHECK \
cpu->Cpsr &= ~(1 << 5); \
cpu->Cpsr |= cpu->TFlag << 5; \
- if (GDBStub::g_server_enabled) { \
+ if (GDBStub::IsServerEnabled()) { \
if (GDBStub::IsMemoryBreak() || (breakpoint_data.type != GDBStub::BreakpointType::None && \
PC == breakpoint_data.address)) { \
GDBStub::Break(); \
@@ -1649,7 +1649,7 @@ DISPATCH : {
}
// Find breakpoint if one exists within the block
- if (GDBStub::g_server_enabled && GDBStub::IsConnected()) {
+ if (GDBStub::IsConnected()) {
breakpoint_data =
GDBStub::GetNextBreakpointFromAddress(cpu->Reg[15], GDBStub::BreakpointType::Execute);
}
diff --git a/src/core/arm/dyncom/arm_dyncom_trans.h b/src/core/arm/dyncom/arm_dyncom_trans.h
index b1ec90662..632ff2cd6 100644
--- a/src/core/arm/dyncom/arm_dyncom_trans.h
+++ b/src/core/arm/dyncom/arm_dyncom_trans.h
@@ -1,3 +1,5 @@
+#pragma once
+
#include <cstddef>
#include "common/common_types.h"
diff --git a/src/core/arm/skyeye_common/armstate.cpp b/src/core/arm/skyeye_common/armstate.cpp
index 1465b074e..c36b0208f 100644
--- a/src/core/arm/skyeye_common/armstate.cpp
+++ b/src/core/arm/skyeye_common/armstate.cpp
@@ -182,7 +182,7 @@ void ARMul_State::ResetMPCoreCP15Registers() {
}
static void CheckMemoryBreakpoint(u32 address, GDBStub::BreakpointType type) {
- if (GDBStub::g_server_enabled && GDBStub::CheckBreakpoint(address, type)) {
+ if (GDBStub::IsServerEnabled() && GDBStub::CheckBreakpoint(address, type)) {
LOG_DEBUG(Debug, "Found memory breakpoint @ %08x", address);
GDBStub::Break(true);
}
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 49ac8be6e..6efa18159 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -22,7 +22,7 @@ std::unique_ptr<ARM_Interface> g_sys_core; ///< ARM11 system (OS) core
/// Run the core CPU loop
void RunLoop(int tight_loop) {
- if (GDBStub::g_server_enabled) {
+ if (GDBStub::IsServerEnabled()) {
GDBStub::HandlePacket();
// If the loop is halted and we want to step, use a tiny (1) number of instructions to
diff --git a/src/core/file_sys/archive_source_sd_savedata.cpp b/src/core/file_sys/archive_source_sd_savedata.cpp
index 2d8a950a3..287322d3e 100644
--- a/src/core/file_sys/archive_source_sd_savedata.cpp
+++ b/src/core/file_sys/archive_source_sd_savedata.cpp
@@ -90,4 +90,9 @@ ResultVal<ArchiveFormatInfo> ArchiveSource_SDSaveData::GetFormatInfo(u64 program
return MakeResult<ArchiveFormatInfo>(info);
}
+std::string ArchiveSource_SDSaveData::GetSaveDataPathFor(const std::string& mount_point,
+ u64 program_id) {
+ return GetSaveDataPath(GetSaveDataContainerPath(mount_point), program_id);
+}
+
} // namespace FileSys
diff --git a/src/core/file_sys/archive_source_sd_savedata.h b/src/core/file_sys/archive_source_sd_savedata.h
index b33126c31..b5fe43cc1 100644
--- a/src/core/file_sys/archive_source_sd_savedata.h
+++ b/src/core/file_sys/archive_source_sd_savedata.h
@@ -23,6 +23,8 @@ public:
ResultCode Format(u64 program_id, const FileSys::ArchiveFormatInfo& format_info);
ResultVal<ArchiveFormatInfo> GetFormatInfo(u64 program_id) const;
+ static std::string GetSaveDataPathFor(const std::string& mount_point, u64 program_id);
+
private:
std::string mount_point;
};
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index 21d941363..f96cbde64 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -5,6 +5,7 @@
// Originally written by Sven Peter <sven@fail0verflow.com> for anergistic.
#include <algorithm>
+#include <atomic>
#include <climits>
#include <csignal>
#include <cstdarg>
@@ -130,7 +131,10 @@ static u16 gdbstub_port = 24689;
static bool halt_loop = true;
static bool step_loop = false;
-std::atomic<bool> g_server_enabled(false);
+
+// If set to false, the server will never be started and no
+// gdbstub-related functions will be executed.
+static std::atomic<bool> server_enabled(false);
#ifdef _WIN32
WSADATA InitData;
@@ -181,11 +185,10 @@ static u8 NibbleToHex(u8 n) {
/**
* Converts input hex string characters into an array of equivalent of u8 bytes.
*
-* @param dest Pointer to buffer to store u8 bytes.
* @param src Pointer to array of output hex string characters.
* @param len Length of src array.
*/
-static u32 HexToInt(u8* src, u32 len) {
+static u32 HexToInt(const u8* src, size_t len) {
u32 output = 0;
while (len-- > 0) {
output = (output << 4) | HexCharToValue(src[0]);
@@ -201,7 +204,7 @@ static u32 HexToInt(u8* src, u32 len) {
* @param src Pointer to array of u8 bytes.
* @param len Length of src array.
*/
-static void MemToGdbHex(u8* dest, u8* src, u32 len) {
+static void MemToGdbHex(u8* dest, const u8* src, size_t len) {
while (len-- > 0) {
u8 tmp = *src++;
*dest++ = NibbleToHex(tmp >> 4);
@@ -216,7 +219,7 @@ static void MemToGdbHex(u8* dest, u8* src, u32 len) {
* @param src Pointer to array of output hex string characters.
* @param len Length of src array.
*/
-static void GdbHexToMem(u8* dest, u8* src, u32 len) {
+static void GdbHexToMem(u8* dest, const u8* src, size_t len) {
while (len-- > 0) {
*dest++ = (HexCharToValue(src[0]) << 4) | HexCharToValue(src[1]);
src += 2;
@@ -240,7 +243,7 @@ static void IntToGdbHex(u8* dest, u32 v) {
*
* @param src Pointer to hex string.
*/
-static u32 GdbHexToInt(u8* src) {
+static u32 GdbHexToInt(const u8* src) {
u32 output = 0;
for (int i = 0; i < 8; i += 2) {
@@ -264,7 +267,7 @@ static u8 ReadByte() {
}
/// Calculate the checksum of the current command buffer.
-static u8 CalculateChecksum(u8* buffer, u32 length) {
+static u8 CalculateChecksum(const u8* buffer, size_t length) {
return static_cast<u8>(std::accumulate(buffer, buffer + length, 0, std::plus<u8>()));
}
@@ -582,7 +585,7 @@ static void ReadRegisters() {
/// Modify data of register specified by gdb client.
static void WriteRegister() {
- u8* buffer_ptr = command_buffer + 3;
+ const u8* buffer_ptr = command_buffer + 3;
u32 id = HexCharToValue(command_buffer[1]);
if (command_buffer[2] != '=') {
@@ -608,7 +611,7 @@ static void WriteRegister() {
/// Modify all registers with data received from the client.
static void WriteRegisters() {
- u8* buffer_ptr = command_buffer + 1;
+ const u8* buffer_ptr = command_buffer + 1;
if (command_buffer[0] != 'G')
return SendReply("E01");
@@ -653,7 +656,7 @@ static void ReadMemory() {
SendReply("E01");
}
- u8* data = Memory::GetPointer(addr);
+ const u8* data = Memory::GetPointer(addr);
if (!data) {
return SendReply("E00");
}
@@ -902,7 +905,7 @@ void SetServerPort(u16 port) {
void ToggleServer(bool status) {
if (status) {
- g_server_enabled = status;
+ server_enabled = status;
// Start server
if (!IsConnected() && Core::g_sys_core != nullptr) {
@@ -914,12 +917,12 @@ void ToggleServer(bool status) {
Shutdown();
}
- g_server_enabled = status;
+ server_enabled = status;
}
}
static void Init(u16 port) {
- if (!g_server_enabled) {
+ if (!server_enabled) {
// Set the halt loop to false in case the user enabled the gdbstub mid-execution.
// This way the CPU can still execute normally.
halt_loop = false;
@@ -998,7 +1001,7 @@ void Init() {
}
void Shutdown() {
- if (!g_server_enabled) {
+ if (!server_enabled) {
return;
}
@@ -1015,8 +1018,12 @@ void Shutdown() {
LOG_INFO(Debug_GDBStub, "GDB stopped.");
}
+bool IsServerEnabled() {
+ return server_enabled;
+}
+
bool IsConnected() {
- return g_server_enabled && gdbserver_socket != -1;
+ return IsServerEnabled() && gdbserver_socket != -1;
}
bool GetCpuHaltFlag() {
diff --git a/src/core/gdbstub/gdbstub.h b/src/core/gdbstub/gdbstub.h
index a7483bb10..38177e32c 100644
--- a/src/core/gdbstub/gdbstub.h
+++ b/src/core/gdbstub/gdbstub.h
@@ -5,7 +5,7 @@
// Originally written by Sven Peter <sven@fail0verflow.com> for anergistic.
#pragma once
-#include <atomic>
+
#include "common/common_types.h"
namespace GDBStub {
@@ -24,10 +24,6 @@ struct BreakpointAddress {
BreakpointType type;
};
-/// If set to false, the server will never be started and no gdbstub-related functions will be
-/// executed.
-extern std::atomic<bool> g_server_enabled;
-
/**
* Set the port the gdbstub should use to listen for connections.
*
@@ -36,7 +32,7 @@ extern std::atomic<bool> g_server_enabled;
void SetServerPort(u16 port);
/**
- * Set the g_server_enabled flag and start or stop the server if possible.
+ * Starts or stops the server if possible.
*
* @param status Set the server to enabled or disabled.
*/
@@ -48,6 +44,9 @@ void Init();
/// Stop gdbstub server.
void Shutdown();
+/// Checks if the gdbstub server is enabled.
+bool IsServerEnabled();
+
/// Returns true if there is an active socket connection.
bool IsConnected();
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp
index 37eec4c84..b5a0cc3a3 100644
--- a/src/core/hle/kernel/address_arbiter.cpp
+++ b/src/core/hle/kernel/address_arbiter.cpp
@@ -79,8 +79,6 @@ ResultCode AddressArbiter::ArbitrateAddress(ArbitrationType type, VAddr address,
ErrorSummary::WrongArgument, ErrorLevel::Usage);
}
- HLE::Reschedule(__func__);
-
// The calls that use a timeout seem to always return a Timeout error even if they did not put
// the thread to sleep
if (type == ArbitrationType::WaitIfLessThanWithTimeout ||
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 0c8752670..1db8e102f 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <boost/range/algorithm_ext/erase.hpp>
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/config_mem.h"
@@ -31,13 +32,61 @@ void WaitObject::RemoveWaitingThread(Thread* thread) {
waiting_threads.erase(itr);
}
-void WaitObject::WakeupAllWaitingThreads() {
- for (auto thread : waiting_threads)
- thread->ResumeFromWait();
+SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
+ // Remove the threads that are ready or already running from our waitlist
+ boost::range::remove_erase_if(waiting_threads, [](const SharedPtr<Thread>& thread) {
+ return thread->status == THREADSTATUS_RUNNING || thread->status == THREADSTATUS_READY ||
+ thread->status == THREADSTATUS_DEAD;
+ });
+
+ // TODO(Subv): This call should be performed inside the loop below to check if an object can be
+ // acquired by a particular thread. This is useful for things like recursive locking of Mutexes.
+ if (ShouldWait())
+ return nullptr;
+
+ Thread* candidate = nullptr;
+ s32 candidate_priority = THREADPRIO_LOWEST + 1;
+
+ for (const auto& thread : waiting_threads) {
+ if (thread->current_priority >= candidate_priority)
+ continue;
- waiting_threads.clear();
+ bool ready_to_run =
+ std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(),
+ [](const SharedPtr<WaitObject>& object) { return object->ShouldWait(); });
+ if (ready_to_run) {
+ candidate = thread.get();
+ candidate_priority = thread->current_priority;
+ }
+ }
+
+ return candidate;
+}
- HLE::Reschedule(__func__);
+void WaitObject::WakeupAllWaitingThreads() {
+ while (auto thread = GetHighestPriorityReadyThread()) {
+ if (!thread->IsSleepingOnWaitAll()) {
+ Acquire();
+ // Set the output index of the WaitSynchronizationN call to the index of this object.
+ if (thread->wait_set_output) {
+ thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(this));
+ thread->wait_set_output = false;
+ }
+ } else {
+ for (auto& object : thread->wait_objects) {
+ object->Acquire();
+ object->RemoveWaitingThread(thread.get());
+ }
+ // Note: This case doesn't update the output index of WaitSynchronizationN.
+ // Clear the thread's waitlist
+ thread->wait_objects.clear();
+ }
+
+ thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
+ thread->ResumeFromWait();
+ // Note: Removing the thread from the object's waitlist will be
+ // done by GetHighestPriorityReadyThread.
+ }
}
const std::vector<SharedPtr<Thread>>& WaitObject::GetWaitingThreads() const {
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 0b811c5a7..1adcf6c71 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -151,9 +151,15 @@ public:
*/
void RemoveWaitingThread(Thread* thread);
- /// Wake up all threads waiting on this object
+ /**
+ * Wake up all threads waiting on this object that can be awoken, in priority order,
+ * and set the synchronization result and output of the thread.
+ */
void WakeupAllWaitingThreads();
+ /// Obtains the highest priority thread that is ready to run from this object's waiting list.
+ SharedPtr<Thread> GetHighestPriorityReadyThread();
+
/// Get a const reference to the waiting threads list for debug use
const std::vector<SharedPtr<Thread>>& GetWaitingThreads() const;
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 84d6d24c6..18b696f72 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -46,7 +46,7 @@ static std::vector<SharedPtr<Thread>> thread_list;
// Lists only ready thread ids.
static Common::ThreadQueueList<Thread*, THREADPRIO_LOWEST + 1> ready_queue;
-static Thread* current_thread;
+static SharedPtr<Thread> current_thread;
// The first available thread id at startup
static u32 next_thread_id;
@@ -63,7 +63,7 @@ Thread::Thread() {}
Thread::~Thread() {}
Thread* GetCurrentThread() {
- return current_thread;
+ return current_thread.get();
}
/**
@@ -120,8 +120,6 @@ void Thread::Stop() {
u32 tls_slot =
((tls_address - Memory::TLS_AREA_VADDR) % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE;
Kernel::g_current_process->tls_slots[tls_page].reset(tls_slot);
-
- HLE::Reschedule(__func__);
}
Thread* ArbitrateHighestPriorityThread(u32 address) {
@@ -181,50 +179,6 @@ static void PriorityBoostStarvedThreads() {
}
/**
- * Gets the registers for timeout parameter of the next WaitSynchronization call.
- * @param thread a pointer to the thread that is ready to call WaitSynchronization
- * @returns a tuple of two register pointers to low and high part of the timeout parameter
- */
-static std::tuple<u32*, u32*> GetWaitSynchTimeoutParameterRegister(Thread* thread) {
- bool thumb_mode = (thread->context.cpsr & TBIT) != 0;
- u16 thumb_inst = Memory::Read16(thread->context.pc & 0xFFFFFFFE);
- u32 inst = Memory::Read32(thread->context.pc & 0xFFFFFFFC) & 0x0FFFFFFF;
-
- if ((thumb_mode && thumb_inst == 0xDF24) || (!thumb_mode && inst == 0x0F000024)) {
- // svc #0x24 (WaitSynchronization1)
- return std::make_tuple(&thread->context.cpu_registers[2],
- &thread->context.cpu_registers[3]);
- } else if ((thumb_mode && thumb_inst == 0xDF25) || (!thumb_mode && inst == 0x0F000025)) {
- // svc #0x25 (WaitSynchronizationN)
- return std::make_tuple(&thread->context.cpu_registers[0],
- &thread->context.cpu_registers[4]);
- }
-
- UNREACHABLE();
-}
-
-/**
- * Updates the WaitSynchronization timeout parameter according to the difference
- * between ticks of the last WaitSynchronization call and the incoming one.
- * @param timeout_low a pointer to the register for the low part of the timeout parameter
- * @param timeout_high a pointer to the register for the high part of the timeout parameter
- * @param last_tick tick of the last WaitSynchronization call
- */
-static void UpdateTimeoutParameter(u32* timeout_low, u32* timeout_high, u64 last_tick) {
- s64 timeout = ((s64)*timeout_high << 32) | *timeout_low;
-
- if (timeout != -1) {
- timeout -= cyclesToUs(CoreTiming::GetTicks() - last_tick) * 1000; // in nanoseconds
-
- if (timeout < 0)
- timeout = 0;
-
- *timeout_low = timeout & 0xFFFFFFFF;
- *timeout_high = timeout >> 32;
- }
-}
-
-/**
* Switches the CPU's active thread context to that of the specified thread
* @param new_thread The thread to switch to
*/
@@ -254,32 +208,6 @@ static void SwitchContext(Thread* new_thread) {
current_thread = new_thread;
- // If the thread was waited by a svcWaitSynch call, step back PC by one instruction to rerun
- // the SVC when the thread wakes up. This is necessary to ensure that the thread can acquire
- // the requested wait object(s) before continuing.
- if (new_thread->waitsynch_waited) {
- // CPSR flag indicates CPU mode
- bool thumb_mode = (new_thread->context.cpsr & TBIT) != 0;
-
- // SVC instruction is 2 bytes for THUMB, 4 bytes for ARM
- new_thread->context.pc -= thumb_mode ? 2 : 4;
-
- // Get the register for timeout parameter
- u32 *timeout_low, *timeout_high;
- std::tie(timeout_low, timeout_high) = GetWaitSynchTimeoutParameterRegister(new_thread);
-
- // Update the timeout parameter
- UpdateTimeoutParameter(timeout_low, timeout_high, new_thread->last_running_ticks);
- }
-
- // Clean up the thread's wait_objects, they'll be restored if needed during
- // the svcWaitSynchronization call
- for (size_t i = 0; i < new_thread->wait_objects.size(); ++i) {
- SharedPtr<WaitObject> object = new_thread->wait_objects[i];
- object->RemoveWaitingThread(new_thread);
- }
- new_thread->wait_objects.clear();
-
ready_queue.remove(new_thread->current_priority, new_thread);
new_thread->status = THREADSTATUS_RUNNING;
@@ -319,17 +247,13 @@ static Thread* PopNextReadyThread() {
void WaitCurrentThread_Sleep() {
Thread* thread = GetCurrentThread();
thread->status = THREADSTATUS_WAIT_SLEEP;
-
- HLE::Reschedule(__func__);
}
void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects,
- bool wait_set_output, bool wait_all) {
+ bool wait_set_output) {
Thread* thread = GetCurrentThread();
thread->wait_set_output = wait_set_output;
- thread->wait_all = wait_all;
thread->wait_objects = std::move(wait_objects);
- thread->waitsynch_waited = true;
thread->status = THREADSTATUS_WAIT_SYNCH;
}
@@ -339,6 +263,13 @@ void WaitCurrentThread_ArbitrateAddress(VAddr wait_address) {
thread->status = THREADSTATUS_WAIT_ARB;
}
+void ExitCurrentThread() {
+ Thread* thread = GetCurrentThread();
+ thread->Stop();
+ thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
+ thread_list.end());
+}
+
/**
* Callback that will wake up the thread it was scheduled for
* @param thread_handle The handle of the thread that's been awoken
@@ -351,15 +282,15 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
return;
}
- thread->waitsynch_waited = false;
-
if (thread->status == THREADSTATUS_WAIT_SYNCH || thread->status == THREADSTATUS_WAIT_ARB) {
+ thread->wait_set_output = false;
+ // Remove the thread from each of its waiting objects' waitlists
+ for (auto& object : thread->wait_objects)
+ object->RemoveWaitingThread(thread.get());
+ thread->wait_objects.clear();
thread->SetWaitSynchronizationResult(ResultCode(ErrorDescription::Timeout, ErrorModule::OS,
ErrorSummary::StatusChanged,
ErrorLevel::Info));
-
- if (thread->wait_set_output)
- thread->SetWaitSynchronizationOutput(-1);
}
thread->ResumeFromWait();
@@ -399,6 +330,7 @@ void Thread::ResumeFromWait() {
ready_queue.push_back(current_priority, this);
status = THREADSTATUS_READY;
+ HLE::Reschedule(__func__);
}
/**
@@ -494,13 +426,11 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
thread->last_running_ticks = CoreTiming::GetTicks();
thread->processor_id = processor_id;
thread->wait_set_output = false;
- thread->wait_all = false;
thread->wait_objects.clear();
thread->wait_address = 0;
thread->name = std::move(name);
thread->callback_handle = wakeup_callback_handle_table.Create(thread).MoveFrom();
thread->owner_process = g_current_process;
- thread->waitsynch_waited = false;
// Find the next available TLS index, and mark it as used
auto& tls_slots = Kernel::g_current_process->tls_slots;
@@ -555,8 +485,6 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
ready_queue.push_back(thread->current_priority, thread.get());
thread->status = THREADSTATUS_READY;
- HLE::Reschedule(__func__);
-
return MakeResult<SharedPtr<Thread>>(std::move(thread));
}
@@ -619,14 +547,6 @@ void Reschedule() {
HLE::DoneRescheduling();
- // Don't bother switching to the same thread.
- // But if the thread was waiting on objects, we still need to switch it
- // to perform PC modification, change state to RUNNING, etc.
- // This occurs in the case when an object the thread is waiting on immediately wakes up
- // the current thread before Reschedule() is called.
- if (next == cur && (next == nullptr || next->waitsynch_waited == false))
- return;
-
if (cur && next) {
LOG_TRACE(Kernel, "context switch %u -> %u", cur->GetObjectId(), next->GetObjectId());
} else if (cur) {
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index e0ffcea8a..d4fefc573 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -5,7 +5,9 @@
#pragma once
#include <string>
+#include <unordered_map>
#include <vector>
+#include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp>
#include "common/common_types.h"
#include "core/core.h"
@@ -125,6 +127,16 @@ public:
void SetWaitSynchronizationOutput(s32 output);
/**
+ * Retrieves the index that this particular object occupies in the list of objects
+ * that the thread passed to WaitSynchronizationN.
+ * It is used to set the output value of WaitSynchronizationN when the thread is awakened.
+ * @param object Object to query the index of.
+ */
+ s32 GetWaitObjectIndex(const WaitObject* object) const {
+ return wait_objects_index.at(object->GetObjectId());
+ }
+
+ /**
* Stops a thread, invalidating it from further use
*/
void Stop();
@@ -137,6 +149,15 @@ public:
return tls_address;
}
+ /**
+ * Returns whether this thread is waiting for all the objects in
+ * its wait list to become ready, as a result of a WaitSynchronizationN call
+ * with wait_all = true, or a ReplyAndReceive call.
+ */
+ bool IsSleepingOnWaitAll() const {
+ return !wait_objects.empty();
+ }
+
Core::ThreadContext context;
u32 thread_id;
@@ -154,16 +175,22 @@ public:
VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread
- bool waitsynch_waited; ///< Set to true if the last svcWaitSynch call caused the thread to wait
-
/// Mutexes currently held by this thread, which will be released when it exits.
boost::container::flat_set<SharedPtr<Mutex>> held_mutexes;
- SharedPtr<Process> owner_process; ///< Process that owns this thread
- std::vector<SharedPtr<WaitObject>> wait_objects; ///< Objects that the thread is waiting on
- VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address
- bool wait_all; ///< True if the thread is waiting on all objects before resuming
- bool wait_set_output; ///< True if the output parameter should be set on thread wakeup
+ SharedPtr<Process> owner_process; ///< Process that owns this thread
+
+ /// Objects that the thread is waiting on.
+ /// This is only populated when the thread should wait for all the objects to become ready.
+ std::vector<SharedPtr<WaitObject>> wait_objects;
+
+ /// Mapping of Object ids to their position in the last waitlist that this object waited on.
+ boost::container::flat_map<int, s32> wait_objects_index;
+
+ VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address
+
+ /// True if the WaitSynchronizationN output parameter should be set on thread wakeup.
+ bool wait_set_output;
std::string name;
@@ -215,10 +242,9 @@ void WaitCurrentThread_Sleep();
* @param wait_objects Kernel objects that we are waiting on
* @param wait_set_output If true, set the output parameter on thread wakeup (for
* WaitSynchronizationN only)
- * @param wait_all If true, wait on all objects before resuming (for WaitSynchronizationN only)
*/
void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects,
- bool wait_set_output, bool wait_all);
+ bool wait_set_output);
/**
* Waits the current thread from an ArbitrateAddress call
@@ -227,6 +253,11 @@ void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wa
void WaitCurrentThread_ArbitrateAddress(VAddr wait_address);
/**
+ * Stops the current thread and removes it from the thread_list
+ */
+void ExitCurrentThread();
+
+/**
* Initialize threading
*/
void ThreadingInit();
diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp
index eac181f4e..b50cf520d 100644
--- a/src/core/hle/kernel/timer.cpp
+++ b/src/core/hle/kernel/timer.cpp
@@ -60,14 +60,10 @@ void Timer::Set(s64 initial, s64 interval) {
u64 initial_microseconds = initial / 1000;
CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), timer_callback_event_type,
callback_handle);
-
- HLE::Reschedule(__func__);
}
void Timer::Cancel() {
CoreTiming::UnscheduleEvent(timer_callback_event_type, callback_handle);
-
- HLE::Reschedule(__func__);
}
void Timer::Clear() {
diff --git a/src/core/hle/service/act/act.cpp b/src/core/hle/service/act/act.cpp
new file mode 100644
index 000000000..9600036c0
--- /dev/null
+++ b/src/core/hle/service/act/act.cpp
@@ -0,0 +1,18 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/act/act.h"
+#include "core/hle/service/act/act_a.h"
+#include "core/hle/service/act/act_u.h"
+
+namespace Service {
+namespace ACT {
+
+void Init() {
+ AddService(new ACT_A);
+ AddService(new ACT_U);
+}
+
+} // namespace ACT
+} // namespace Service
diff --git a/src/core/hle/service/act/act.h b/src/core/hle/service/act/act.h
new file mode 100644
index 000000000..1425291aa
--- /dev/null
+++ b/src/core/hle/service/act/act.h
@@ -0,0 +1,14 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+namespace Service {
+namespace ACT {
+
+/// Initializes all ACT services
+void Init();
+
+} // namespace ACT
+} // namespace Service
diff --git a/src/core/hle/service/act_a.cpp b/src/core/hle/service/act/act_a.cpp
index 9880aafff..5c523368f 100644
--- a/src/core/hle/service/act_a.cpp
+++ b/src/core/hle/service/act/act_a.cpp
@@ -2,7 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "core/hle/service/act_a.h"
+#include "core/hle/service/act/act.h"
+#include "core/hle/service/act/act_a.h"
namespace Service {
namespace ACT {
diff --git a/src/core/hle/service/act_a.h b/src/core/hle/service/act/act_a.h
index e3adb03e5..e3adb03e5 100644
--- a/src/core/hle/service/act_a.h
+++ b/src/core/hle/service/act/act_a.h
diff --git a/src/core/hle/service/act_u.cpp b/src/core/hle/service/act/act_u.cpp
index b4f69c57d..cf98aa1d6 100644
--- a/src/core/hle/service/act_u.cpp
+++ b/src/core/hle/service/act/act_u.cpp
@@ -2,7 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "core/hle/service/act_u.h"
+#include "core/hle/service/act/act.h"
+#include "core/hle/service/act/act_u.h"
namespace Service {
namespace ACT {
diff --git a/src/core/hle/service/act_u.h b/src/core/hle/service/act/act_u.h
index 9d8538fbf..9d8538fbf 100644
--- a/src/core/hle/service/act_u.h
+++ b/src/core/hle/service/act/act_u.h
diff --git a/src/core/hle/service/cecd/cecd.cpp b/src/core/hle/service/cecd/cecd.cpp
index 515b344e6..eb04273db 100644
--- a/src/core/hle/service/cecd/cecd.cpp
+++ b/src/core/hle/service/cecd/cecd.cpp
@@ -5,6 +5,7 @@
#include "common/logging/log.h"
#include "core/hle/kernel/event.h"
#include "core/hle/service/cecd/cecd.h"
+#include "core/hle/service/cecd/cecd_ndm.h"
#include "core/hle/service/cecd/cecd_s.h"
#include "core/hle/service/cecd/cecd_u.h"
#include "core/hle/service/service.h"
@@ -43,12 +44,13 @@ void GetChangeStateEventHandle(Service::Interface* self) {
}
void Init() {
- AddService(new CECD_S_Interface);
- AddService(new CECD_U_Interface);
+ AddService(new CECD_NDM);
+ AddService(new CECD_S);
+ AddService(new CECD_U);
- cecinfo_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "CECD_U::cecinfo_event");
+ cecinfo_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "CECD::cecinfo_event");
change_state_event =
- Kernel::Event::Create(Kernel::ResetType::OneShot, "CECD_U::change_state_event");
+ Kernel::Event::Create(Kernel::ResetType::OneShot, "CECD::change_state_event");
}
void Shutdown() {
diff --git a/src/core/hle/service/cecd/cecd_ndm.cpp b/src/core/hle/service/cecd/cecd_ndm.cpp
new file mode 100644
index 000000000..7baf93750
--- /dev/null
+++ b/src/core/hle/service/cecd/cecd_ndm.cpp
@@ -0,0 +1,23 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/cecd/cecd.h"
+#include "core/hle/service/cecd/cecd_ndm.h"
+
+namespace Service {
+namespace CECD {
+
+static const Interface::FunctionInfo FunctionTable[] = {
+ {0x00010000, nullptr, "Initialize"},
+ {0x00020000, nullptr, "Deinitialize"},
+ {0x00030000, nullptr, "ResumeDaemon"},
+ {0x00040040, nullptr, "SuspendDaemon"},
+};
+
+CECD_NDM::CECD_NDM() {
+ Register(FunctionTable);
+}
+
+} // namespace CECD
+} // namespace Service
diff --git a/src/core/hle/service/cecd/cecd_ndm.h b/src/core/hle/service/cecd/cecd_ndm.h
new file mode 100644
index 000000000..2e2e50ada
--- /dev/null
+++ b/src/core/hle/service/cecd/cecd_ndm.h
@@ -0,0 +1,22 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace CECD {
+
+class CECD_NDM : public Interface {
+public:
+ CECD_NDM();
+
+ std::string GetPortName() const override {
+ return "cecd:ndm";
+ }
+};
+
+} // namespace CECD
+} // namespace Service
diff --git a/src/core/hle/service/cecd/cecd_s.cpp b/src/core/hle/service/cecd/cecd_s.cpp
index 7477b9320..eacda7d41 100644
--- a/src/core/hle/service/cecd/cecd_s.cpp
+++ b/src/core/hle/service/cecd/cecd_s.cpp
@@ -2,16 +2,34 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "core/hle/service/cecd/cecd.h"
#include "core/hle/service/cecd/cecd_s.h"
namespace Service {
namespace CECD {
-// Empty arrays are illegal -- commented out until an entry is added.
-// const Interface::FunctionInfo FunctionTable[] = { };
+static const Interface::FunctionInfo FunctionTable[] = {
+ // cecd:u shared commands
+ {0x000100C2, nullptr, "OpenRawFile"},
+ {0x00020042, nullptr, "ReadRawFile"},
+ {0x00030104, nullptr, "ReadMessage"},
+ {0x00040106, nullptr, "ReadMessageWithHMAC"},
+ {0x00050042, nullptr, "WriteRawFile"},
+ {0x00060104, nullptr, "WriteMessage"},
+ {0x00070106, nullptr, "WriteMessageWithHMAC"},
+ {0x00080102, nullptr, "Delete"},
+ {0x000A00C4, nullptr, "GetSystemInfo"},
+ {0x000B0040, nullptr, "RunCommand"},
+ {0x000C0040, nullptr, "RunCommandAlt"},
+ {0x000E0000, GetCecStateAbbreviated, "GetCecStateAbbreviated"},
+ {0x000F0000, GetCecInfoEventHandle, "GetCecInfoEventHandle"},
+ {0x00100000, GetChangeStateEventHandle, "GetChangeStateEventHandle"},
+ {0x00110104, nullptr, "OpenAndWrite"},
+ {0x00120104, nullptr, "OpenAndRead"},
+};
-CECD_S_Interface::CECD_S_Interface() {
- // Register(FunctionTable);
+CECD_S::CECD_S() {
+ Register(FunctionTable);
}
} // namespace CECD
diff --git a/src/core/hle/service/cecd/cecd_s.h b/src/core/hle/service/cecd/cecd_s.h
index df5c01849..ab6c6789a 100644
--- a/src/core/hle/service/cecd/cecd_s.h
+++ b/src/core/hle/service/cecd/cecd_s.h
@@ -9,9 +9,9 @@
namespace Service {
namespace CECD {
-class CECD_S_Interface : public Interface {
+class CECD_S : public Interface {
public:
- CECD_S_Interface();
+ CECD_S();
std::string GetPortName() const override {
return "cecd:s";
diff --git a/src/core/hle/service/cecd/cecd_u.cpp b/src/core/hle/service/cecd/cecd_u.cpp
index 7d98ba6e9..3ed864f0b 100644
--- a/src/core/hle/service/cecd/cecd_u.cpp
+++ b/src/core/hle/service/cecd/cecd_u.cpp
@@ -9,6 +9,7 @@ namespace Service {
namespace CECD {
static const Interface::FunctionInfo FunctionTable[] = {
+ // cecd:u shared commands
{0x000100C2, nullptr, "OpenRawFile"},
{0x00020042, nullptr, "ReadRawFile"},
{0x00030104, nullptr, "ReadMessage"},
@@ -27,7 +28,7 @@ static const Interface::FunctionInfo FunctionTable[] = {
{0x00120104, nullptr, "OpenAndRead"},
};
-CECD_U_Interface::CECD_U_Interface() {
+CECD_U::CECD_U() {
Register(FunctionTable);
}
diff --git a/src/core/hle/service/cecd/cecd_u.h b/src/core/hle/service/cecd/cecd_u.h
index 394030ffc..16e874ff5 100644
--- a/src/core/hle/service/cecd/cecd_u.h
+++ b/src/core/hle/service/cecd/cecd_u.h
@@ -9,9 +9,9 @@
namespace Service {
namespace CECD {
-class CECD_U_Interface : public Interface {
+class CECD_U : public Interface {
public:
- CECD_U_Interface();
+ CECD_U();
std::string GetPortName() const override {
return "cecd:u";
diff --git a/src/core/hle/service/dlp/dlp.h b/src/core/hle/service/dlp/dlp.h
index ec2fe46e8..3185fe322 100644
--- a/src/core/hle/service/dlp/dlp.h
+++ b/src/core/hle/service/dlp/dlp.h
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#pragma once
+
namespace Service {
namespace DLP {
diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp
index f21934108..09205e4b2 100644
--- a/src/core/hle/service/fs/archive.cpp
+++ b/src/core/hle/service/fs/archive.cpp
@@ -104,8 +104,8 @@ void File::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_ses
u64 offset = cmd_buff[1] | ((u64)cmd_buff[2]) << 32;
u32 length = cmd_buff[3];
u32 address = cmd_buff[5];
- LOG_TRACE(Service_FS, "Read %s %s: offset=0x%llx length=%d address=0x%x",
- GetTypeName().c_str(), GetName().c_str(), offset, length, address);
+ LOG_TRACE(Service_FS, "Read %s: offset=0x%llx length=%d address=0x%x", GetName().c_str(),
+ offset, length, address);
if (offset + length > backend->GetSize()) {
LOG_ERROR(Service_FS,
@@ -130,8 +130,8 @@ void File::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_ses
u32 length = cmd_buff[3];
u32 flush = cmd_buff[4];
u32 address = cmd_buff[6];
- LOG_TRACE(Service_FS, "Write %s %s: offset=0x%llx length=%d address=0x%x, flush=0x%x",
- GetTypeName().c_str(), GetName().c_str(), offset, length, address, flush);
+ LOG_TRACE(Service_FS, "Write %s: offset=0x%llx length=%d address=0x%x, flush=0x%x",
+ GetName().c_str(), offset, length, address, flush);
std::vector<u8> data(length);
Memory::ReadBlock(address, data.data(), data.size());
@@ -145,7 +145,7 @@ void File::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_ses
}
case FileCommand::GetSize: {
- LOG_TRACE(Service_FS, "GetSize %s %s", GetTypeName().c_str(), GetName().c_str());
+ LOG_TRACE(Service_FS, "GetSize %s", GetName().c_str());
u64 size = backend->GetSize();
cmd_buff[2] = (u32)size;
cmd_buff[3] = size >> 32;
@@ -154,14 +154,13 @@ void File::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_ses
case FileCommand::SetSize: {
u64 size = cmd_buff[1] | ((u64)cmd_buff[2] << 32);
- LOG_TRACE(Service_FS, "SetSize %s %s size=%llu", GetTypeName().c_str(), GetName().c_str(),
- size);
+ LOG_TRACE(Service_FS, "SetSize %s size=%llu", GetName().c_str(), size);
backend->SetSize(size);
break;
}
case FileCommand::Close: {
- LOG_TRACE(Service_FS, "Close %s %s", GetTypeName().c_str(), GetName().c_str());
+ LOG_TRACE(Service_FS, "Close %s", GetName().c_str());
backend->Close();
break;
}
@@ -219,8 +218,7 @@ void Directory::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> serve
u32 count = cmd_buff[1];
u32 address = cmd_buff[3];
std::vector<FileSys::Entry> entries(count);
- LOG_TRACE(Service_FS, "Read %s %s: count=%d", GetTypeName().c_str(), GetName().c_str(),
- count);
+ LOG_TRACE(Service_FS, "Read %s: count=%d", GetName().c_str(), count);
// Number of entries actually read
u32 read = backend->Read(entries.size(), entries.data());
@@ -230,7 +228,7 @@ void Directory::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> serve
}
case DirectoryCommand::Close: {
- LOG_TRACE(Service_FS, "Close %s %s", GetTypeName().c_str(), GetName().c_str());
+ LOG_TRACE(Service_FS, "Close %s", GetName().c_str());
backend->Close();
break;
}
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 99baded11..18a1b6a16 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -37,7 +37,8 @@ static int enable_gyroscope_count = 0; // positive means enabled
static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
// 30 degree and 60 degree are angular thresholds for directions
- constexpr float TAN30 = 0.577350269, TAN60 = 1 / TAN30;
+ constexpr float TAN30 = 0.577350269f;
+ constexpr float TAN60 = 1 / TAN30;
// a circle pad radius greater than 40 will trigger circle pad direction
constexpr int CIRCLE_PAD_THRESHOLD_SQUARE = 40 * 40;
PadState state;
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 2bc3fdc82..25a7aeea8 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -9,8 +9,7 @@
#include "core/hle/kernel/server_port.h"
#include "core/hle/service/ac_u.h"
-#include "core/hle/service/act_a.h"
-#include "core/hle/service/act_u.h"
+#include "core/hle/service/act/act.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/apt/apt.h"
#include "core/hle/service/boss/boss.h"
@@ -139,6 +138,7 @@ void Init() {
AddNamedPort(new ERR::ERR_F);
FS::ArchiveInit();
+ ACT::Init();
AM::Init();
APT::Init();
BOSS::Init();
@@ -158,8 +158,6 @@ void Init() {
QTM::Init();
AddService(new AC::AC_U);
- AddService(new ACT::ACT_A);
- AddService(new ACT::ACT_U);
AddService(new CSND::CSND_SND);
AddService(new DSP_DSP::Interface);
AddService(new GSP::GSP_GPU);
diff --git a/src/core/hle/service/srv.h b/src/core/hle/service/srv.h
index 6041ca42d..d3a9de879 100644
--- a/src/core/hle/service/srv.h
+++ b/src/core/hle/service/srv.h
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#pragma once
+
#include "core/hle/service/service.h"
namespace Service {
diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp
index e5ba9a484..5839d7230 100644
--- a/src/core/hle/svc.cpp
+++ b/src/core/hle/svc.cpp
@@ -43,6 +43,9 @@ const ResultCode ERR_PORT_NAME_TOO_LONG(ErrorDescription(30), ErrorModule::OS,
ErrorSummary::InvalidArgument,
ErrorLevel::Usage); // 0xE0E0181E
+const ResultCode ERR_SYNC_TIMEOUT(ErrorDescription::Timeout, ErrorModule::OS,
+ ErrorSummary::StatusChanged, ErrorLevel::Info);
+
const ResultCode ERR_MISALIGNED_ADDRESS{// 0xE0E01BF1
ErrorDescription::MisalignedAddress, ErrorModule::OS,
ErrorSummary::InvalidArgument, ErrorLevel::Usage};
@@ -260,27 +263,30 @@ static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) {
auto object = Kernel::g_handle_table.GetWaitObject(handle);
Kernel::Thread* thread = Kernel::GetCurrentThread();
- thread->waitsynch_waited = false;
-
if (object == nullptr)
return ERR_INVALID_HANDLE;
LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s:%s), nanoseconds=%lld", handle,
object->GetTypeName().c_str(), object->GetName().c_str(), nano_seconds);
- HLE::Reschedule(__func__);
-
- // Check for next thread to schedule
if (object->ShouldWait()) {
+ if (nano_seconds == 0)
+ return ERR_SYNC_TIMEOUT;
+
object->AddWaitingThread(thread);
- Kernel::WaitCurrentThread_WaitSynchronization({object}, false, false);
+ // TODO(Subv): Perform things like update the mutex lock owner's priority to
+ // prevent priority inversion. Currently this is done in Mutex::ShouldWait,
+ // but it should be moved to a function that is called from here.
+ thread->status = THREADSTATUS_WAIT_SYNCH;
// Create an event to wake the thread up after the specified nanosecond delay has passed
thread->WakeAfterDelay(nano_seconds);
- // NOTE: output of this SVC will be set later depending on how the thread resumes
- return HLE::RESULT_INVALID;
+ // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread
+ // resumes due to a signal in its wait objects.
+ // Otherwise we retain the default value of timeout.
+ return ERR_SYNC_TIMEOUT;
}
object->Acquire();
@@ -291,11 +297,7 @@ static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) {
/// Wait for the given handles to synchronize, timeout after the specified nanoseconds
static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_count, bool wait_all,
s64 nano_seconds) {
- bool wait_thread = !wait_all;
- int handle_index = 0;
Kernel::Thread* thread = Kernel::GetCurrentThread();
- bool was_waiting = thread->waitsynch_waited;
- thread->waitsynch_waited = false;
// Check if 'handles' is invalid
if (handles == nullptr)
@@ -311,90 +313,113 @@ static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_cou
return ResultCode(ErrorDescription::OutOfRange, ErrorModule::OS,
ErrorSummary::InvalidArgument, ErrorLevel::Usage);
- // If 'handle_count' is non-zero, iterate through each handle and wait the current thread if
- // necessary
- if (handle_count != 0) {
- bool selected = false; // True once an object has been selected
-
- Kernel::SharedPtr<Kernel::WaitObject> wait_object;
-
- for (int i = 0; i < handle_count; ++i) {
- auto object = Kernel::g_handle_table.GetWaitObject(handles[i]);
- if (object == nullptr)
- return ERR_INVALID_HANDLE;
-
- // Check if the current thread should wait on this object...
- if (object->ShouldWait()) {
-
- // Check we are waiting on all objects...
- if (wait_all)
- // Wait the thread
- wait_thread = true;
- } else {
- // Do not wait on this object, check if this object should be selected...
- if (!wait_all && (!selected || (wait_object == object && was_waiting))) {
- // Do not wait the thread
- wait_thread = false;
- handle_index = i;
- wait_object = object;
- selected = true;
- }
- }
- }
- } else {
- // If no handles were passed in, put the thread to sleep only when 'wait_all' is false
- // NOTE: This should deadlock the current thread if no timeout was specified
- if (!wait_all) {
- wait_thread = true;
- }
+ using ObjectPtr = Kernel::SharedPtr<Kernel::WaitObject>;
+ std::vector<ObjectPtr> objects(handle_count);
+
+ for (int i = 0; i < handle_count; ++i) {
+ auto object = Kernel::g_handle_table.GetWaitObject(handles[i]);
+ if (object == nullptr)
+ return ERR_INVALID_HANDLE;
+ objects[i] = object;
}
- SCOPE_EXIT({
- HLE::Reschedule("WaitSynchronizationN");
- }); // Reschedule after putting the threads to sleep.
+ // Clear the mapping of wait object indices.
+ // We don't want any lingering state in this map.
+ // It will be repopulated later in the wait_all = false case.
+ thread->wait_objects_index.clear();
+
+ if (wait_all) {
+ bool all_available =
+ std::all_of(objects.begin(), objects.end(),
+ [](const ObjectPtr& object) { return !object->ShouldWait(); });
+ if (all_available) {
+ // We can acquire all objects right now, do so.
+ for (auto& object : objects)
+ object->Acquire();
+ // Note: In this case, the `out` parameter is not set,
+ // and retains whatever value it had before.
+ return RESULT_SUCCESS;
+ }
+
+ // Not all objects were available right now, prepare to suspend the thread.
- // If thread should wait, then set its state to waiting
- if (wait_thread) {
+ // If a timeout value of 0 was provided, just return the Timeout error code instead of
+ // suspending the thread.
+ if (nano_seconds == 0)
+ return ERR_SYNC_TIMEOUT;
- // Actually wait the current thread on each object if we decided to wait...
- std::vector<SharedPtr<Kernel::WaitObject>> wait_objects;
- wait_objects.reserve(handle_count);
+ // Put the thread to sleep
+ thread->status = THREADSTATUS_WAIT_SYNCH;
- for (int i = 0; i < handle_count; ++i) {
- auto object = Kernel::g_handle_table.GetWaitObject(handles[i]);
- object->AddWaitingThread(Kernel::GetCurrentThread());
- wait_objects.push_back(object);
+ // Add the thread to each of the objects' waiting threads.
+ for (auto& object : objects) {
+ object->AddWaitingThread(thread);
+ // TODO(Subv): Perform things like update the mutex lock owner's priority to
+ // prevent priority inversion. Currently this is done in Mutex::ShouldWait,
+ // but it should be moved to a function that is called from here.
}
- Kernel::WaitCurrentThread_WaitSynchronization(std::move(wait_objects), true, wait_all);
+ // Set the thread's waitlist to the list of objects passed to WaitSynchronizationN
+ thread->wait_objects = std::move(objects);
// Create an event to wake the thread up after the specified nanosecond delay has passed
- Kernel::GetCurrentThread()->WakeAfterDelay(nano_seconds);
-
- // NOTE: output of this SVC will be set later depending on how the thread resumes
- return HLE::RESULT_INVALID;
- }
+ thread->WakeAfterDelay(nano_seconds);
- // Acquire objects if we did not wait...
- for (int i = 0; i < handle_count; ++i) {
- auto object = Kernel::g_handle_table.GetWaitObject(handles[i]);
+ // This value gets set to -1 by default in this case, it is not modified after this.
+ *out = -1;
+ // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to
+ // a signal in one of its wait objects.
+ return ERR_SYNC_TIMEOUT;
+ } else {
+ // Find the first object that is acquirable in the provided list of objects
+ auto itr = std::find_if(objects.begin(), objects.end(),
+ [](const ObjectPtr& object) { return !object->ShouldWait(); });
- // Acquire the object if it is not waiting...
- if (!object->ShouldWait()) {
+ if (itr != objects.end()) {
+ // We found a ready object, acquire it and set the result value
+ Kernel::WaitObject* object = itr->get();
object->Acquire();
+ *out = std::distance(objects.begin(), itr);
+ return RESULT_SUCCESS;
+ }
+
+ // No objects were ready to be acquired, prepare to suspend the thread.
+
+ // If a timeout value of 0 was provided, just return the Timeout error code instead of
+ // suspending the thread.
+ if (nano_seconds == 0)
+ return ERR_SYNC_TIMEOUT;
+
+ // Put the thread to sleep
+ thread->status = THREADSTATUS_WAIT_SYNCH;
- // If this was the first non-waiting object and 'wait_all' is false, don't acquire
- // any other objects
- if (!wait_all)
- break;
+ // Clear the thread's waitlist, we won't use it for wait_all = false
+ thread->wait_objects.clear();
+
+ // Add the thread to each of the objects' waiting threads.
+ for (size_t i = 0; i < objects.size(); ++i) {
+ Kernel::WaitObject* object = objects[i].get();
+ // Set the index of this object in the mapping of Objects -> index for this thread.
+ thread->wait_objects_index[object->GetObjectId()] = static_cast<int>(i);
+ object->AddWaitingThread(thread);
+ // TODO(Subv): Perform things like update the mutex lock owner's priority to
+ // prevent priority inversion. Currently this is done in Mutex::ShouldWait,
+ // but it should be moved to a function that is called from here.
}
- }
- // TODO(bunnei): If 'wait_all' is true, this is probably wrong. However, real hardware does
- // not seem to set it to any meaningful value.
- *out = handle_count != 0 ? (wait_all ? -1 : handle_index) : 0;
+ // Note: If no handles and no timeout were given, then the thread will deadlock, this is
+ // consistent with hardware behavior.
- return RESULT_SUCCESS;
+ // Create an event to wake the thread up after the specified nanosecond delay has passed
+ thread->WakeAfterDelay(nano_seconds);
+
+ // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a
+ // signal in one of its wait objects.
+ // Otherwise we retain the default value of timeout, and -1 in the out parameter
+ thread->wait_set_output = true;
+ *out = -1;
+ return ERR_SYNC_TIMEOUT;
+ }
}
/// Create an address arbiter (to allocate access to shared resources)
@@ -559,7 +584,7 @@ static ResultCode CreateThread(Handle* out_handle, s32 priority, u32 entry_point
static void ExitThread() {
LOG_TRACE(Kernel_SVC, "called, pc=0x%08X", Core::g_app_core->GetPC());
- Kernel::GetCurrentThread()->Stop();
+ Kernel::ExitCurrentThread();
}
/// Gets the priority for the specified thread
@@ -1159,6 +1184,8 @@ void CallSVC(u32 immediate) {
if (info) {
if (info->func) {
info->func();
+ // TODO(Subv): Not all service functions should cause a reschedule in all cases.
+ HLE::Reschedule(__func__);
} else {
LOG_ERROR(Kernel_SVC, "unimplemented SVC function %s(..)", info->name);
}
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 5e3d46638..a6c2a745f 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -144,6 +144,15 @@ public:
}
/**
+ * Get the program id of the application
+ * @param out_program_id Reference to store program id into
+ * @return ResultStatus result of function
+ */
+ virtual ResultStatus ReadProgramId(u64& out_program_id) {
+ return ResultStatus::ErrorNotImplemented;
+ }
+
+ /**
* Get the RomFS of the application
* Since the RomFS can be huge, we return a file reference instead of copying to a buffer
* @param romfs_file The file containing the RomFS
diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp
index d4be61e0e..6f2164428 100644
--- a/src/core/loader/ncch.cpp
+++ b/src/core/loader/ncch.cpp
@@ -344,6 +344,18 @@ ResultStatus AppLoader_NCCH::ReadLogo(std::vector<u8>& buffer) {
return LoadSectionExeFS("logo", buffer);
}
+ResultStatus AppLoader_NCCH::ReadProgramId(u64& out_program_id) {
+ if (!file.IsOpen())
+ return ResultStatus::Error;
+
+ ResultStatus result = LoadExeFS();
+ if (result != ResultStatus::Success)
+ return result;
+
+ out_program_id = ncch_header.program_id;
+ return ResultStatus::Success;
+}
+
ResultStatus AppLoader_NCCH::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset,
u64& size) {
if (!file.IsOpen())
diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h
index bcf3ae6e3..6c93d46d8 100644
--- a/src/core/loader/ncch.h
+++ b/src/core/loader/ncch.h
@@ -220,6 +220,13 @@ public:
ResultStatus ReadLogo(std::vector<u8>& buffer) override;
/**
+ * Get the program id of the application
+ * @param out_program_id Reference to store program id into
+ * @return ResultStatus result of function
+ */
+ ResultStatus ReadProgramId(u64& out_program_id) override;
+
+ /**
* Get the RomFS of the application
* @param romfs_file Reference to buffer to store data
* @param offset Offset in the file to the RomFS
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 9aa446a8f..6ca319b59 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -39,6 +39,7 @@ set(HEADERS
rasterizer.h
rasterizer_interface.h
renderer_base.h
+ shader/debug_data.h
shader/shader.h
shader/shader_interpreter.h
swrasterizer.h
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
index db4fe659c..ea58e9f54 100644
--- a/src/video_core/command_processor.cpp
+++ b/src/video_core/command_processor.cpp
@@ -59,7 +59,10 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
regs[id] = (old_value & ~write_mask) | (value & write_mask);
- DebugUtils::OnPicaRegWrite({(u16)id, (u16)mask, regs[id]});
+ // Double check for is_pica_tracing to avoid call overhead
+ if (DebugUtils::IsPicaTracing()) {
+ DebugUtils::OnPicaRegWrite({(u16)id, (u16)mask, regs[id]});
+ }
if (g_debug_context)
g_debug_context->OnEvent(DebugContext::Event::PicaCommandLoaded,
@@ -139,7 +142,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
MICROPROFILE_SCOPE(GPU_Drawing);
immediate_attribute_id = 0;
- Shader::UnitState<false> shader_unit;
+ Shader::UnitState shader_unit;
g_state.vs.Setup();
// Send to vertex shader
@@ -240,7 +243,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
unsigned int vertex_cache_pos = 0;
vertex_cache_ids.fill(-1);
- Shader::UnitState<false> shader_unit;
+ Shader::UnitState shader_unit;
g_state.vs.Setup();
for (unsigned int index = 0; index < regs.num_vertices; ++index) {
diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp
index 8806464d9..c44b3d95a 100644
--- a/src/video_core/debug_utils/debug_utils.cpp
+++ b/src/video_core/debug_utils/debug_utils.cpp
@@ -276,10 +276,10 @@ void DumpShader(const std::string& filename, const Regs::ShaderConfig& config,
static std::unique_ptr<PicaTrace> pica_trace;
static std::mutex pica_trace_mutex;
-static int is_pica_tracing = false;
+bool g_is_pica_tracing = false;
void StartPicaTracing() {
- if (is_pica_tracing) {
+ if (g_is_pica_tracing) {
LOG_WARNING(HW_GPU, "StartPicaTracing called even though tracing already running!");
return;
}
@@ -287,34 +287,26 @@ void StartPicaTracing() {
std::lock_guard<std::mutex> lock(pica_trace_mutex);
pica_trace = std::make_unique<PicaTrace>();
- is_pica_tracing = true;
-}
-
-bool IsPicaTracing() {
- return is_pica_tracing != 0;
+ g_is_pica_tracing = true;
}
void OnPicaRegWrite(PicaTrace::Write write) {
- // Double check for is_pica_tracing to avoid pointless locking overhead
- if (!is_pica_tracing)
- return;
-
std::lock_guard<std::mutex> lock(pica_trace_mutex);
- if (!is_pica_tracing)
+ if (!g_is_pica_tracing)
return;
pica_trace->writes.push_back(write);
}
std::unique_ptr<PicaTrace> FinishPicaTracing() {
- if (!is_pica_tracing) {
+ if (!g_is_pica_tracing) {
LOG_WARNING(HW_GPU, "FinishPicaTracing called even though tracing isn't running!");
return {};
}
// signalize that no further tracing should be performed
- is_pica_tracing = false;
+ g_is_pica_tracing = false;
// Wait until running tracing is finished
std::lock_guard<std::mutex> lock(pica_trace_mutex);
diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h
index 189c93abb..46ea8d9c7 100644
--- a/src/video_core/debug_utils/debug_utils.h
+++ b/src/video_core/debug_utils/debug_utils.h
@@ -196,8 +196,12 @@ struct PicaTrace {
std::vector<Write> writes;
};
+extern bool g_is_pica_tracing;
+
void StartPicaTracing();
-bool IsPicaTracing();
+inline bool IsPicaTracing() {
+ return g_is_pica_tracing;
+}
void OnPicaRegWrite(PicaTrace::Write write);
std::unique_ptr<PicaTrace> FinishPicaTracing();
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 22b8a0db2..5a306a5c8 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -886,10 +886,10 @@ bool RasterizerOpenGL::AccelerateFill(const GPU::Regs::MemoryFillConfig& config)
}
}
- cur_state.color_mask.red_enabled = true;
- cur_state.color_mask.green_enabled = true;
- cur_state.color_mask.blue_enabled = true;
- cur_state.color_mask.alpha_enabled = true;
+ cur_state.color_mask.red_enabled = GL_TRUE;
+ cur_state.color_mask.green_enabled = GL_TRUE;
+ cur_state.color_mask.blue_enabled = GL_TRUE;
+ cur_state.color_mask.alpha_enabled = GL_TRUE;
cur_state.Apply();
glClearBufferfv(GL_COLOR, 0, color_values);
} else if (dst_type == SurfaceType::Depth) {
diff --git a/src/video_core/shader/debug_data.h b/src/video_core/shader/debug_data.h
new file mode 100644
index 000000000..9e82122e1
--- /dev/null
+++ b/src/video_core/shader/debug_data.h
@@ -0,0 +1,186 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/common_types.h"
+#include "common/vector_math.h"
+#include "video_core/pica_types.h"
+
+namespace Pica {
+namespace Shader {
+
+/// Helper structure used to keep track of data useful for inspection of shader emulation
+template <bool full_debugging>
+struct DebugData;
+
+template <>
+struct DebugData<false> {
+ // TODO: Hide these behind and interface and move them to DebugData<true>
+ u32 max_offset = 0; ///< maximum program counter ever reached
+ u32 max_opdesc_id = 0; ///< maximum swizzle pattern index ever used
+};
+
+template <>
+struct DebugData<true> {
+ /// Records store the input and output operands of a particular instruction.
+ struct Record {
+ enum Type {
+ // Floating point arithmetic operands
+ SRC1 = 0x1,
+ SRC2 = 0x2,
+ SRC3 = 0x4,
+
+ // Initial and final output operand value
+ DEST_IN = 0x8,
+ DEST_OUT = 0x10,
+
+ // Current and next instruction offset (in words)
+ CUR_INSTR = 0x20,
+ NEXT_INSTR = 0x40,
+
+ // Output address register value
+ ADDR_REG_OUT = 0x80,
+
+ // Result of a comparison instruction
+ CMP_RESULT = 0x100,
+
+ // Input values for conditional flow control instructions
+ COND_BOOL_IN = 0x200,
+ COND_CMP_IN = 0x400,
+
+ // Input values for a loop
+ LOOP_INT_IN = 0x800,
+ };
+
+ Math::Vec4<float24> src1;
+ Math::Vec4<float24> src2;
+ Math::Vec4<float24> src3;
+
+ Math::Vec4<float24> dest_in;
+ Math::Vec4<float24> dest_out;
+
+ s32 address_registers[2];
+ bool conditional_code[2];
+ bool cond_bool;
+ bool cond_cmp[2];
+ Math::Vec4<u8> loop_int;
+
+ u32 instruction_offset;
+ u32 next_instruction;
+
+ /// set of enabled fields (as a combination of Type flags)
+ unsigned mask = 0;
+ };
+
+ u32 max_offset = 0; ///< maximum program counter ever reached
+ u32 max_opdesc_id = 0; ///< maximum swizzle pattern index ever used
+
+ /// List of records for each executed shader instruction
+ std::vector<DebugData<true>::Record> records;
+};
+
+/// Type alias for better readability
+using DebugDataRecord = DebugData<true>::Record;
+
+/// Helper function to set a DebugData<true>::Record field based on the template enum parameter.
+template <DebugDataRecord::Type type, typename ValueType>
+inline void SetField(DebugDataRecord& record, ValueType value);
+
+template <>
+inline void SetField<DebugDataRecord::SRC1>(DebugDataRecord& record, float24* value) {
+ record.src1.x = value[0];
+ record.src1.y = value[1];
+ record.src1.z = value[2];
+ record.src1.w = value[3];
+}
+
+template <>
+inline void SetField<DebugDataRecord::SRC2>(DebugDataRecord& record, float24* value) {
+ record.src2.x = value[0];
+ record.src2.y = value[1];
+ record.src2.z = value[2];
+ record.src2.w = value[3];
+}
+
+template <>
+inline void SetField<DebugDataRecord::SRC3>(DebugDataRecord& record, float24* value) {
+ record.src3.x = value[0];
+ record.src3.y = value[1];
+ record.src3.z = value[2];
+ record.src3.w = value[3];
+}
+
+template <>
+inline void SetField<DebugDataRecord::DEST_IN>(DebugDataRecord& record, float24* value) {
+ record.dest_in.x = value[0];
+ record.dest_in.y = value[1];
+ record.dest_in.z = value[2];
+ record.dest_in.w = value[3];
+}
+
+template <>
+inline void SetField<DebugDataRecord::DEST_OUT>(DebugDataRecord& record, float24* value) {
+ record.dest_out.x = value[0];
+ record.dest_out.y = value[1];
+ record.dest_out.z = value[2];
+ record.dest_out.w = value[3];
+}
+
+template <>
+inline void SetField<DebugDataRecord::ADDR_REG_OUT>(DebugDataRecord& record, s32* value) {
+ record.address_registers[0] = value[0];
+ record.address_registers[1] = value[1];
+}
+
+template <>
+inline void SetField<DebugDataRecord::CMP_RESULT>(DebugDataRecord& record, bool* value) {
+ record.conditional_code[0] = value[0];
+ record.conditional_code[1] = value[1];
+}
+
+template <>
+inline void SetField<DebugDataRecord::COND_BOOL_IN>(DebugDataRecord& record, bool value) {
+ record.cond_bool = value;
+}
+
+template <>
+inline void SetField<DebugDataRecord::COND_CMP_IN>(DebugDataRecord& record, bool* value) {
+ record.cond_cmp[0] = value[0];
+ record.cond_cmp[1] = value[1];
+}
+
+template <>
+inline void SetField<DebugDataRecord::LOOP_INT_IN>(DebugDataRecord& record, Math::Vec4<u8> value) {
+ record.loop_int = value;
+}
+
+template <>
+inline void SetField<DebugDataRecord::CUR_INSTR>(DebugDataRecord& record, u32 value) {
+ record.instruction_offset = value;
+}
+
+template <>
+inline void SetField<DebugDataRecord::NEXT_INSTR>(DebugDataRecord& record, u32 value) {
+ record.next_instruction = value;
+}
+
+/// Helper function to set debug information on the current shader iteration.
+template <DebugDataRecord::Type type, typename ValueType>
+inline void Record(DebugData<false>& debug_data, u32 offset, ValueType value) {
+ // Debugging disabled => nothing to do
+}
+
+template <DebugDataRecord::Type type, typename ValueType>
+inline void Record(DebugData<true>& debug_data, u32 offset, ValueType value) {
+ if (offset >= debug_data.records.size())
+ debug_data.records.resize(offset + 1);
+
+ SetField<type, ValueType>(debug_data.records[offset], value);
+ debug_data.records[offset].mask |= type;
+}
+
+} // namespace Shader
+} // namespace Pica
diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp
index c7f23dab9..a4aa3c9e0 100644
--- a/src/video_core/shader/shader.cpp
+++ b/src/video_core/shader/shader.cpp
@@ -109,15 +109,12 @@ void ShaderSetup::Setup() {
MICROPROFILE_DEFINE(GPU_Shader, "GPU", "Shader", MP_RGB(50, 50, 240));
-void ShaderSetup::Run(UnitState<false>& state, const InputVertex& input, int num_attributes) {
+void ShaderSetup::Run(UnitState& state, const InputVertex& input, int num_attributes) {
auto& config = g_state.regs.vs;
auto& setup = g_state.vs;
MICROPROFILE_SCOPE(GPU_Shader);
- state.debug.max_offset = 0;
- state.debug.max_opdesc_id = 0;
-
// Setup input register table
const auto& attribute_register_map = config.input_register_map;
@@ -128,22 +125,23 @@ void ShaderSetup::Run(UnitState<false>& state, const InputVertex& input, int num
state.conditional_code[1] = false;
#ifdef ARCHITECTURE_x86_64
- if (VideoCore::g_shader_jit_enabled)
+ if (VideoCore::g_shader_jit_enabled) {
jit_shader->Run(setup, state, config.main_offset);
- else
- RunInterpreter(setup, state, config.main_offset);
+ } else {
+ DebugData<false> dummy_debug_data;
+ RunInterpreter(setup, state, dummy_debug_data, config.main_offset);
+ }
#else
- RunInterpreter(setup, state, config.main_offset);
+ DebugData<false> dummy_debug_data;
+ RunInterpreter(setup, state, dummy_debug_data, config.main_offset);
#endif // ARCHITECTURE_x86_64
}
DebugData<true> ShaderSetup::ProduceDebugInfo(const InputVertex& input, int num_attributes,
const Regs::ShaderConfig& config,
const ShaderSetup& setup) {
- UnitState<true> state;
-
- state.debug.max_offset = 0;
- state.debug.max_opdesc_id = 0;
+ UnitState state;
+ DebugData<true> debug_data;
// Setup input register table
boost::fill(state.registers.input, Math::Vec4<float24>::AssignToAll(float24::Zero()));
@@ -154,8 +152,8 @@ DebugData<true> ShaderSetup::ProduceDebugInfo(const InputVertex& input, int num_
state.conditional_code[0] = false;
state.conditional_code[1] = false;
- RunInterpreter(setup, state, config.main_offset);
- return state.debug;
+ RunInterpreter(setup, state, debug_data, config.main_offset);
+ return debug_data;
}
} // namespace Shader
diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h
index 0111d8c0f..2b07759b9 100644
--- a/src/video_core/shader/shader.h
+++ b/src/video_core/shader/shader.h
@@ -8,8 +8,6 @@
#include <cstddef>
#include <memory>
#include <type_traits>
-#include <vector>
-#include <boost/container/static_vector.hpp>
#include <nihstro/shader_bytecode.h>
#include "common/assert.h"
#include "common/common_funcs.h"
@@ -17,6 +15,7 @@
#include "common/vector_math.h"
#include "video_core/pica.h"
#include "video_core/pica_types.h"
+#include "video_core/shader/debug_data.h"
using nihstro::RegisterType;
using nihstro::SourceRegister;
@@ -89,183 +88,12 @@ struct OutputRegisters {
};
static_assert(std::is_pod<OutputRegisters>::value, "Structure is not POD");
-// Helper structure used to keep track of data useful for inspection of shader emulation
-template <bool full_debugging>
-struct DebugData;
-
-template <>
-struct DebugData<false> {
- // TODO: Hide these behind and interface and move them to DebugData<true>
- u32 max_offset; // maximum program counter ever reached
- u32 max_opdesc_id; // maximum swizzle pattern index ever used
-};
-
-template <>
-struct DebugData<true> {
- // Records store the input and output operands of a particular instruction.
- struct Record {
- enum Type {
- // Floating point arithmetic operands
- SRC1 = 0x1,
- SRC2 = 0x2,
- SRC3 = 0x4,
-
- // Initial and final output operand value
- DEST_IN = 0x8,
- DEST_OUT = 0x10,
-
- // Current and next instruction offset (in words)
- CUR_INSTR = 0x20,
- NEXT_INSTR = 0x40,
-
- // Output address register value
- ADDR_REG_OUT = 0x80,
-
- // Result of a comparison instruction
- CMP_RESULT = 0x100,
-
- // Input values for conditional flow control instructions
- COND_BOOL_IN = 0x200,
- COND_CMP_IN = 0x400,
-
- // Input values for a loop
- LOOP_INT_IN = 0x800,
- };
-
- Math::Vec4<float24> src1;
- Math::Vec4<float24> src2;
- Math::Vec4<float24> src3;
-
- Math::Vec4<float24> dest_in;
- Math::Vec4<float24> dest_out;
-
- s32 address_registers[2];
- bool conditional_code[2];
- bool cond_bool;
- bool cond_cmp[2];
- Math::Vec4<u8> loop_int;
-
- u32 instruction_offset;
- u32 next_instruction;
-
- // set of enabled fields (as a combination of Type flags)
- unsigned mask = 0;
- };
-
- u32 max_offset; // maximum program counter ever reached
- u32 max_opdesc_id; // maximum swizzle pattern index ever used
-
- // List of records for each executed shader instruction
- std::vector<DebugData<true>::Record> records;
-};
-
-// Type alias for better readability
-using DebugDataRecord = DebugData<true>::Record;
-
-// Helper function to set a DebugData<true>::Record field based on the template enum parameter.
-template <DebugDataRecord::Type type, typename ValueType>
-inline void SetField(DebugDataRecord& record, ValueType value);
-
-template <>
-inline void SetField<DebugDataRecord::SRC1>(DebugDataRecord& record, float24* value) {
- record.src1.x = value[0];
- record.src1.y = value[1];
- record.src1.z = value[2];
- record.src1.w = value[3];
-}
-
-template <>
-inline void SetField<DebugDataRecord::SRC2>(DebugDataRecord& record, float24* value) {
- record.src2.x = value[0];
- record.src2.y = value[1];
- record.src2.z = value[2];
- record.src2.w = value[3];
-}
-
-template <>
-inline void SetField<DebugDataRecord::SRC3>(DebugDataRecord& record, float24* value) {
- record.src3.x = value[0];
- record.src3.y = value[1];
- record.src3.z = value[2];
- record.src3.w = value[3];
-}
-
-template <>
-inline void SetField<DebugDataRecord::DEST_IN>(DebugDataRecord& record, float24* value) {
- record.dest_in.x = value[0];
- record.dest_in.y = value[1];
- record.dest_in.z = value[2];
- record.dest_in.w = value[3];
-}
-
-template <>
-inline void SetField<DebugDataRecord::DEST_OUT>(DebugDataRecord& record, float24* value) {
- record.dest_out.x = value[0];
- record.dest_out.y = value[1];
- record.dest_out.z = value[2];
- record.dest_out.w = value[3];
-}
-
-template <>
-inline void SetField<DebugDataRecord::ADDR_REG_OUT>(DebugDataRecord& record, s32* value) {
- record.address_registers[0] = value[0];
- record.address_registers[1] = value[1];
-}
-
-template <>
-inline void SetField<DebugDataRecord::CMP_RESULT>(DebugDataRecord& record, bool* value) {
- record.conditional_code[0] = value[0];
- record.conditional_code[1] = value[1];
-}
-
-template <>
-inline void SetField<DebugDataRecord::COND_BOOL_IN>(DebugDataRecord& record, bool value) {
- record.cond_bool = value;
-}
-
-template <>
-inline void SetField<DebugDataRecord::COND_CMP_IN>(DebugDataRecord& record, bool* value) {
- record.cond_cmp[0] = value[0];
- record.cond_cmp[1] = value[1];
-}
-
-template <>
-inline void SetField<DebugDataRecord::LOOP_INT_IN>(DebugDataRecord& record, Math::Vec4<u8> value) {
- record.loop_int = value;
-}
-
-template <>
-inline void SetField<DebugDataRecord::CUR_INSTR>(DebugDataRecord& record, u32 value) {
- record.instruction_offset = value;
-}
-
-template <>
-inline void SetField<DebugDataRecord::NEXT_INSTR>(DebugDataRecord& record, u32 value) {
- record.next_instruction = value;
-}
-
-// Helper function to set debug information on the current shader iteration.
-template <DebugDataRecord::Type type, typename ValueType>
-inline void Record(DebugData<false>& debug_data, u32 offset, ValueType value) {
- // Debugging disabled => nothing to do
-}
-
-template <DebugDataRecord::Type type, typename ValueType>
-inline void Record(DebugData<true>& debug_data, u32 offset, ValueType value) {
- if (offset >= debug_data.records.size())
- debug_data.records.resize(offset + 1);
-
- SetField<type, ValueType>(debug_data.records[offset], value);
- debug_data.records[offset].mask |= type;
-}
-
/**
* This structure contains the state information that needs to be unique for a shader unit. The 3DS
* has four shader units that process shaders in parallel. At the present, Citra only implements a
* single shader unit that processes all shaders serially. Putting the state information in a struct
* here will make it easier for us to parallelize the shader processing later.
*/
-template <bool Debug>
struct UnitState {
struct Registers {
// The registers are accessed by the shader JIT using SSE instructions, and are therefore
@@ -283,8 +111,6 @@ struct UnitState {
// TODO: How many bits do these actually have?
s32 address_registers[3];
- DebugData<Debug> debug;
-
static size_t InputOffset(const SourceRegister& reg) {
switch (reg.GetRegisterType()) {
case RegisterType::Input:
@@ -332,21 +158,16 @@ struct ShaderSetup {
std::array<Math::Vec4<u8>, 4> i;
} uniforms;
- static size_t UniformOffset(RegisterType type, unsigned index) {
- switch (type) {
- case RegisterType::FloatUniform:
- return offsetof(ShaderSetup, uniforms.f) + index * sizeof(Math::Vec4<float24>);
-
- case RegisterType::BoolUniform:
- return offsetof(ShaderSetup, uniforms.b) + index * sizeof(bool);
+ static size_t GetFloatUniformOffset(unsigned index) {
+ return offsetof(ShaderSetup, uniforms.f) + index * sizeof(Math::Vec4<float24>);
+ }
- case RegisterType::IntUniform:
- return offsetof(ShaderSetup, uniforms.i) + index * sizeof(Math::Vec4<u8>);
+ static size_t GetBoolUniformOffset(unsigned index) {
+ return offsetof(ShaderSetup, uniforms.b) + index * sizeof(bool);
+ }
- default:
- UNREACHABLE();
- return 0;
- }
+ static size_t GetIntUniformOffset(unsigned index) {
+ return offsetof(ShaderSetup, uniforms.i) + index * sizeof(Math::Vec4<u8>);
}
std::array<u32, 1024> program_code;
@@ -364,7 +185,7 @@ struct ShaderSetup {
* @param input Input vertex into the shader
* @param num_attributes The number of vertex shader attributes
*/
- void Run(UnitState<false>& state, const InputVertex& input, int num_attributes);
+ void Run(UnitState& state, const InputVertex& input, int num_attributes);
/**
* Produce debug information based on the given shader and input vertex
diff --git a/src/video_core/shader/shader_interpreter.cpp b/src/video_core/shader/shader_interpreter.cpp
index 6abb6761f..70db4167e 100644
--- a/src/video_core/shader/shader_interpreter.cpp
+++ b/src/video_core/shader/shader_interpreter.cpp
@@ -6,6 +6,7 @@
#include <array>
#include <cmath>
#include <numeric>
+#include <boost/container/static_vector.hpp>
#include <nihstro/shader_bytecode.h>
#include "common/assert.h"
#include "common/common_types.h"
@@ -38,12 +39,42 @@ struct CallStackElement {
};
template <bool Debug>
-void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned offset) {
+void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug>& debug_data,
+ unsigned offset) {
// TODO: Is there a maximal size for this?
boost::container::static_vector<CallStackElement, 16> call_stack;
-
u32 program_counter = offset;
+ auto call = [&program_counter, &call_stack](u32 offset, u32 num_instructions, u32 return_offset,
+ u8 repeat_count, u8 loop_increment) {
+ // -1 to make sure when incrementing the PC we end up at the correct offset
+ program_counter = offset - 1;
+ ASSERT(call_stack.size() < call_stack.capacity());
+ call_stack.push_back(
+ {offset + num_instructions, return_offset, repeat_count, loop_increment, offset});
+ };
+
+ auto evaluate_condition = [&state](Instruction::FlowControlType flow_control) {
+ using Op = Instruction::FlowControlType::Op;
+
+ bool result_x = flow_control.refx.Value() == state.conditional_code[0];
+ bool result_y = flow_control.refy.Value() == state.conditional_code[1];
+
+ switch (flow_control.op) {
+ case Op::Or:
+ return result_x || result_y;
+ case Op::And:
+ return result_x && result_y;
+ case Op::JustX:
+ return result_x;
+ case Op::JustY:
+ return result_y;
+ default:
+ UNREACHABLE();
+ return false;
+ }
+ };
+
const auto& uniforms = g_state.vs.uniforms;
const auto& swizzle_data = g_state.vs.swizzle_data;
const auto& program_code = g_state.vs.program_code;
@@ -74,20 +105,11 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
const Instruction instr = {program_code[program_counter]};
const SwizzlePattern swizzle = {swizzle_data[instr.common.operand_desc_id]};
- auto call = [&program_counter, &call_stack](UnitState<Debug>& state, u32 offset,
- u32 num_instructions, u32 return_offset,
- u8 repeat_count, u8 loop_increment) {
- // -1 to make sure when incrementing the PC we end up at the correct offset
- program_counter = offset - 1;
- ASSERT(call_stack.size() < call_stack.capacity());
- call_stack.push_back(
- {offset + num_instructions, return_offset, repeat_count, loop_increment, offset});
- };
- Record<DebugDataRecord::CUR_INSTR>(state.debug, iteration, program_counter);
+ Record<DebugDataRecord::CUR_INSTR>(debug_data, iteration, program_counter);
if (iteration > 0)
- Record<DebugDataRecord::NEXT_INSTR>(state.debug, iteration - 1, program_counter);
+ Record<DebugDataRecord::NEXT_INSTR>(debug_data, iteration - 1, program_counter);
- state.debug.max_offset = std::max<u32>(state.debug.max_offset, 1 + program_counter);
+ debug_data.max_offset = std::max<u32>(debug_data.max_offset, 1 + program_counter);
auto LookupSourceRegister = [&](const SourceRegister& source_reg) -> const float24* {
switch (source_reg.GetRegisterType()) {
@@ -155,54 +177,54 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
? &state.registers.temporary[instr.common.dest.Value().GetIndex()][0]
: dummy_vec4_float24;
- state.debug.max_opdesc_id =
- std::max<u32>(state.debug.max_opdesc_id, 1 + instr.common.operand_desc_id);
+ debug_data.max_opdesc_id =
+ std::max<u32>(debug_data.max_opdesc_id, 1 + instr.common.operand_desc_id);
switch (instr.opcode.Value().EffectiveOpCode()) {
case OpCode::Id::ADD: {
- Record<DebugDataRecord::SRC1>(state.debug, iteration, src1);
- Record<DebugDataRecord::SRC2>(state.debug, iteration, src2);
- Record<DebugDataRecord::DEST_IN>(state.debug, iteration, dest);
+ Record<DebugDataRecord::SRC1>(debug_data, iteration, src1);
+ Record<DebugDataRecord::SRC2>(debug_data, iteration, src2);
+ Record<DebugDataRecord::DEST_IN>(debug_data, iteration, dest);
for (int i = 0; i < 4; ++i) {
if (!swizzle.DestComponentEnabled(i))
continue;
dest[i] = src1[i] + src2[i];
}
- Record<DebugDataRecord::DEST_OUT>(state.debug, iteration, dest);
+ Record<DebugDataRecord::DEST_OUT>(debug_data, iteration, dest);
break;
}
case OpCode::Id::MUL: {
- Record<DebugDataRecord::SRC1>(state.debug, iteration, src1);
- Record<DebugDataRecord::SRC2>(state.debug, iteration, src2);
- Record<DebugDataRecord::DEST_IN>(state.debug, iteration, dest);
+ Record<DebugDataRecord::SRC1>(debug_data, iteration, src1);
+ Record<DebugDataRecord::SRC2>(debug_data, iteration, src2);
+ Record<DebugDataRecord::DEST_IN>(debug_data, iteration, dest);
for (int i = 0; i < 4; ++i) {
if (!swizzle.DestComponentEnabled(i))
continue;
dest[i] = src1[i] * src2[i];
}
- Record<DebugDataRecord::DEST_OUT>(state.debug, iteration, dest);
+ Record<DebugDataRecord::DEST_OUT>(debug_data, iteration, dest);
break;
}
case OpCode::Id::FLR:
- Record<DebugDataRecord::SRC1>(state.debug, iteration, src1);
- Record<DebugDataRecord::DEST_IN>(state.debug, iteration, dest);
+ Record<DebugDataRecord::SRC1>(debug_data, iteration, src1);
+ Record<DebugDataRecord::DEST_IN>(debug_data, iteration, dest);
for (int i = 0; i < 4; ++i) {
if (!swizzle.DestComponentEnabled(i))
continue;
dest[i] = float24::FromFloat32(std::floor(src1[i].ToFloat32()));
}
- Record<DebugDataRecord::DEST_OUT>(state.debug, iteration, dest);
+ Record<DebugDataRecord::DEST_OUT>(debug_data, iteration, dest);
break;
case OpCode::Id::MAX:
- Record<DebugDataRecord::SRC1>(state.debug, iteration, src1);
- Record<DebugDataRecord::SRC2>(state.debug, iteration, src2);
- Record<DebugDataRecord::DEST_IN>(state.debug, iteration, dest);
+ Record<DebugDataRecord::SRC1>(debug_data, iteration, src1);
+ Record<DebugDataRecord::SRC2>(debug_data, iteration, src2);
+ Record<DebugDataRecord::DEST_IN>(debug_data, iteration, dest);
for (int i = 0; i < 4; ++i) {
if (!swizzle.DestComponentEnabled(i))
continue;
@@ -212,13 +234,13 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
// max(NaN, 0) -> 0
dest[i] = (src1[i] > src2[i]) ? src1[i] : src2[i];
}
- Record<DebugDataRecord::DEST_OUT>(state.debug, iteration, dest);
+ Record<DebugDataRecord::DEST_OUT>(debug_data, iteration, dest);
break;
case OpCode::Id::MIN:
- Record<DebugDataRecord::SRC1>(state.debug, iteration, src1);
- Record<DebugDataRecord::SRC2>(state.debug, iteration, src2);
- Record<DebugDataRecord::DEST_IN>(state.debug, iteration, dest);
+ Record<DebugDataRecord::SRC1>(debug_data, iteration, src1);
+ Record<DebugDataRecord::SRC2>(debug_data, iteration, src2);
+ Record<DebugDataRecord::DEST_IN>(debug_data, iteration, dest);
for (int i = 0; i < 4; ++i) {
if (!swizzle.DestComponentEnabled(i))
continue;
@@ -228,16 +250,16 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
// min(NaN, 0) -> 0
dest[i] = (src1[i] < src2[i]) ? src1[i] : src2[i];
}
- Record<DebugDataRecord::DEST_OUT>(state.debug, iteration, dest);
+ Record<DebugDataRecord::DEST_OUT>(debug_data, iteration, dest);
break;
case OpCode::Id::DP3:
case OpCode::Id::DP4:
case OpCode::Id::DPH:
case OpCode::Id::DPHI: {
- Record<DebugDataRecord::SRC1>(state.debug, iteration, src1);
- Record<DebugDataRecord::SRC2>(state.debug, iteration, src2);
- Record<DebugDataRecord::DEST_IN>(state.debug, iteration, dest);
+ Record<DebugDataRecord::SRC1>(debug_data, iteration, src1);
+ Record<DebugDataRecord::SRC2>(debug_data, iteration, src2);
+ Record<DebugDataRecord::DEST_IN>(debug_data, iteration, dest);
OpCode::Id opcode = instr.opcode.Value().EffectiveOpCode();
if (opcode == OpCode::Id::DPH || opcode == OpCode::Id::DPHI)
@@ -253,14 +275,14 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
dest[i] = dot;
}
- Record<DebugDataRecord::DEST_OUT>(state.debug, iteration, dest);
+ Record<DebugDataRecord::DEST_OUT>(debug_data, iteration, dest);
break;
}
// Reciprocal
case OpCode::Id::RCP: {
- Record<DebugDataRecord::SRC1>(state.debug, iteration, src1);
- Record<DebugDataRecord::DEST_IN>(state.debug, iteration, dest);
+ Record<DebugDataRecord::SRC1>(debug_data, iteration, src1);
+ Record<DebugDataRecord::DEST_IN>(debug_data, iteration, dest);
float24 rcp_res = float24::FromFloat32(1.0f / src1[0].ToFloat32());
for (int i = 0; i < 4; ++i) {
if (!swizzle.DestComponentEnabled(i))
@@ -268,14 +290,14 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
dest[i] = rcp_res;
}
- Record<DebugDataRecord::DEST_OUT>(state.debug, iteration, dest);
+ Record<DebugDataRecord::DEST_OUT>(debug_data, iteration, dest);
break;
}
// Reciprocal Square Root
case OpCode::Id::RSQ: {
- Record<DebugDataRecord::SRC1>(state.debug, iteration, src1);
- Record<DebugDataRecord::DEST_IN>(state.debug, iteration, dest);
+ Record<DebugDataRecord::SRC1>(debug_data, iteration, src1);
+ Record<DebugDataRecord::DEST_IN>(debug_data, iteration, dest);
float24 rsq_res = float24::FromFloat32(1.0f / std::sqrt(src1[0].ToFloat32()));
for (int i = 0; i < 4; ++i) {
if (!swizzle.DestComponentEnabled(i))
@@ -283,12 +305,12 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
dest[i] = rsq_res;
}
- Record<DebugDataRecord::DEST_OUT>(state.debug, iteration, dest);
+ Record<DebugDataRecord::DEST_OUT>(debug_data, iteration, dest);
break;
}
case OpCode::Id::MOVA: {
- Record<DebugDataRecord::SRC1>(state.debug, iteration, src1);
+ Record<DebugDataRecord::SRC1>(debug_data, iteration, src1);
for (int i = 0; i < 2; ++i) {
if (!swizzle.DestComponentEnabled(i))
continue;
@@ -296,29 +318,29 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
// TODO: Figure out how the rounding is done on hardware
state.address_registers[i] = static_cast<s32>(src1[i].ToFloat32());
}
- Record<DebugDataRecord::ADDR_REG_OUT>(state.debug, iteration,
+ Record<DebugDataRecord::ADDR_REG_OUT>(debug_data, iteration,
state.address_registers);
break;
}
case OpCode::Id::MOV: {
- Record<DebugDataRecord::SRC1>(state.debug, iteration, src1);
- Record<DebugDataRecord::DEST_IN>(state.debug, iteration, dest);
+ Record<DebugDataRecord::SRC1>(debug_data, iteration, src1);
+ Record<DebugDataRecord::DEST_IN>(debug_data, iteration, dest);
for (int i = 0; i < 4; ++i) {
if (!swizzle.DestComponentEnabled(i))
continue;
dest[i] = src1[i];
}
- Record<DebugDataRecord::DEST_OUT>(state.debug, iteration, dest);
+ Record<DebugDataRecord::DEST_OUT>(debug_data, iteration, dest);
break;
}
case OpCode::Id::SGE:
case OpCode::Id::SGEI:
- Record<DebugDataRecord::SRC1>(state.debug, iteration, src1);
- Record<DebugDataRecord::SRC2>(state.debug, iteration, src2);
- Record<DebugDataRecord::DEST_IN>(state.debug, iteration, dest);
+ Record<DebugDataRecord::SRC1>(debug_data, iteration, src1);
+ Record<DebugDataRecord::SRC2>(debug_data, iteration, src2);
+ Record<DebugDataRecord::DEST_IN>(debug_data, iteration, dest);
for (int i = 0; i < 4; ++i) {
if (!swizzle.DestComponentEnabled(i))
continue;
@@ -326,14 +348,14 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
dest[i] = (src1[i] >= src2[i]) ? float24::FromFloat32(1.0f)
: float24::FromFloat32(0.0f);
}
- Record<DebugDataRecord::DEST_OUT>(state.debug, iteration, dest);
+ Record<DebugDataRecord::DEST_OUT>(debug_data, iteration, dest);
break;
case OpCode::Id::SLT:
case OpCode::Id::SLTI:
- Record<DebugDataRecord::SRC1>(state.debug, iteration, src1);
- Record<DebugDataRecord::SRC2>(state.debug, iteration, src2);
- Record<DebugDataRecord::DEST_IN>(state.debug, iteration, dest);
+ Record<DebugDataRecord::SRC1>(debug_data, iteration, src1);
+ Record<DebugDataRecord::SRC2>(debug_data, iteration, src2);
+ Record<DebugDataRecord::DEST_IN>(debug_data, iteration, dest);
for (int i = 0; i < 4; ++i) {
if (!swizzle.DestComponentEnabled(i))
continue;
@@ -341,12 +363,12 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
dest[i] = (src1[i] < src2[i]) ? float24::FromFloat32(1.0f)
: float24::FromFloat32(0.0f);
}
- Record<DebugDataRecord::DEST_OUT>(state.debug, iteration, dest);
+ Record<DebugDataRecord::DEST_OUT>(debug_data, iteration, dest);
break;
case OpCode::Id::CMP:
- Record<DebugDataRecord::SRC1>(state.debug, iteration, src1);
- Record<DebugDataRecord::SRC2>(state.debug, iteration, src2);
+ Record<DebugDataRecord::SRC1>(debug_data, iteration, src1);
+ Record<DebugDataRecord::SRC2>(debug_data, iteration, src2);
for (int i = 0; i < 2; ++i) {
// TODO: Can you restrict to one compare via dest masking?
@@ -383,12 +405,12 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
break;
}
}
- Record<DebugDataRecord::CMP_RESULT>(state.debug, iteration, state.conditional_code);
+ Record<DebugDataRecord::CMP_RESULT>(debug_data, iteration, state.conditional_code);
break;
case OpCode::Id::EX2: {
- Record<DebugDataRecord::SRC1>(state.debug, iteration, src1);
- Record<DebugDataRecord::DEST_IN>(state.debug, iteration, dest);
+ Record<DebugDataRecord::SRC1>(debug_data, iteration, src1);
+ Record<DebugDataRecord::DEST_IN>(debug_data, iteration, dest);
// EX2 only takes first component exp2 and writes it to all dest components
float24 ex2_res = float24::FromFloat32(std::exp2(src1[0].ToFloat32()));
@@ -399,13 +421,13 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
dest[i] = ex2_res;
}
- Record<DebugDataRecord::DEST_OUT>(state.debug, iteration, dest);
+ Record<DebugDataRecord::DEST_OUT>(debug_data, iteration, dest);
break;
}
case OpCode::Id::LG2: {
- Record<DebugDataRecord::SRC1>(state.debug, iteration, src1);
- Record<DebugDataRecord::DEST_IN>(state.debug, iteration, dest);
+ Record<DebugDataRecord::SRC1>(debug_data, iteration, src1);
+ Record<DebugDataRecord::DEST_IN>(debug_data, iteration, dest);
// LG2 only takes the first component log2 and writes it to all dest components
float24 lg2_res = float24::FromFloat32(std::log2(src1[0].ToFloat32()));
@@ -416,7 +438,7 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
dest[i] = lg2_res;
}
- Record<DebugDataRecord::DEST_OUT>(state.debug, iteration, dest);
+ Record<DebugDataRecord::DEST_OUT>(debug_data, iteration, dest);
break;
}
@@ -498,17 +520,17 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
? &state.registers.temporary[instr.mad.dest.Value().GetIndex()][0]
: dummy_vec4_float24;
- Record<DebugDataRecord::SRC1>(state.debug, iteration, src1);
- Record<DebugDataRecord::SRC2>(state.debug, iteration, src2);
- Record<DebugDataRecord::SRC3>(state.debug, iteration, src3);
- Record<DebugDataRecord::DEST_IN>(state.debug, iteration, dest);
+ Record<DebugDataRecord::SRC1>(debug_data, iteration, src1);
+ Record<DebugDataRecord::SRC2>(debug_data, iteration, src2);
+ Record<DebugDataRecord::SRC3>(debug_data, iteration, src3);
+ Record<DebugDataRecord::DEST_IN>(debug_data, iteration, dest);
for (int i = 0; i < 4; ++i) {
if (!swizzle.DestComponentEnabled(i))
continue;
dest[i] = src1[i] * src2[i] + src3[i];
}
- Record<DebugDataRecord::DEST_OUT>(state.debug, iteration, dest);
+ Record<DebugDataRecord::DEST_OUT>(debug_data, iteration, dest);
} else {
LOG_ERROR(HW_GPU, "Unhandled multiply-add instruction: 0x%02x (%s): 0x%08x",
(int)instr.opcode.Value().EffectiveOpCode(),
@@ -518,26 +540,6 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
}
default: {
- static auto evaluate_condition = [](const UnitState<Debug>& state, bool refx, bool refy,
- Instruction::FlowControlType flow_control) {
- bool results[2] = {refx == state.conditional_code[0],
- refy == state.conditional_code[1]};
-
- switch (flow_control.op) {
- case flow_control.Or:
- return results[0] || results[1];
-
- case flow_control.And:
- return results[0] && results[1];
-
- case flow_control.JustX:
- return results[0];
-
- case flow_control.JustY:
- return results[1];
- }
- };
-
// Handle each instruction on its own
switch (instr.opcode.Value()) {
case OpCode::Id::END:
@@ -545,17 +547,15 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
break;
case OpCode::Id::JMPC:
- Record<DebugDataRecord::COND_CMP_IN>(state.debug, iteration,
- state.conditional_code);
- if (evaluate_condition(state, instr.flow_control.refx, instr.flow_control.refy,
- instr.flow_control)) {
+ Record<DebugDataRecord::COND_CMP_IN>(debug_data, iteration, state.conditional_code);
+ if (evaluate_condition(instr.flow_control)) {
program_counter = instr.flow_control.dest_offset - 1;
}
break;
case OpCode::Id::JMPU:
Record<DebugDataRecord::COND_BOOL_IN>(
- state.debug, iteration, uniforms.b[instr.flow_control.bool_uniform_id]);
+ debug_data, iteration, uniforms.b[instr.flow_control.bool_uniform_id]);
if (uniforms.b[instr.flow_control.bool_uniform_id] ==
!(instr.flow_control.num_instructions & 1)) {
@@ -564,25 +564,23 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
break;
case OpCode::Id::CALL:
- call(state, instr.flow_control.dest_offset, instr.flow_control.num_instructions,
+ call(instr.flow_control.dest_offset, instr.flow_control.num_instructions,
program_counter + 1, 0, 0);
break;
case OpCode::Id::CALLU:
Record<DebugDataRecord::COND_BOOL_IN>(
- state.debug, iteration, uniforms.b[instr.flow_control.bool_uniform_id]);
+ debug_data, iteration, uniforms.b[instr.flow_control.bool_uniform_id]);
if (uniforms.b[instr.flow_control.bool_uniform_id]) {
- call(state, instr.flow_control.dest_offset, instr.flow_control.num_instructions,
+ call(instr.flow_control.dest_offset, instr.flow_control.num_instructions,
program_counter + 1, 0, 0);
}
break;
case OpCode::Id::CALLC:
- Record<DebugDataRecord::COND_CMP_IN>(state.debug, iteration,
- state.conditional_code);
- if (evaluate_condition(state, instr.flow_control.refx, instr.flow_control.refy,
- instr.flow_control)) {
- call(state, instr.flow_control.dest_offset, instr.flow_control.num_instructions,
+ Record<DebugDataRecord::COND_CMP_IN>(debug_data, iteration, state.conditional_code);
+ if (evaluate_condition(instr.flow_control)) {
+ call(instr.flow_control.dest_offset, instr.flow_control.num_instructions,
program_counter + 1, 0, 0);
}
break;
@@ -592,14 +590,13 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
case OpCode::Id::IFU:
Record<DebugDataRecord::COND_BOOL_IN>(
- state.debug, iteration, uniforms.b[instr.flow_control.bool_uniform_id]);
+ debug_data, iteration, uniforms.b[instr.flow_control.bool_uniform_id]);
if (uniforms.b[instr.flow_control.bool_uniform_id]) {
- call(state, program_counter + 1,
- instr.flow_control.dest_offset - program_counter - 1,
+ call(program_counter + 1, instr.flow_control.dest_offset - program_counter - 1,
instr.flow_control.dest_offset + instr.flow_control.num_instructions, 0,
0);
} else {
- call(state, instr.flow_control.dest_offset, instr.flow_control.num_instructions,
+ call(instr.flow_control.dest_offset, instr.flow_control.num_instructions,
instr.flow_control.dest_offset + instr.flow_control.num_instructions, 0,
0);
}
@@ -609,16 +606,13 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
case OpCode::Id::IFC: {
// TODO: Do we need to consider swizzlers here?
- Record<DebugDataRecord::COND_CMP_IN>(state.debug, iteration,
- state.conditional_code);
- if (evaluate_condition(state, instr.flow_control.refx, instr.flow_control.refy,
- instr.flow_control)) {
- call(state, program_counter + 1,
- instr.flow_control.dest_offset - program_counter - 1,
+ Record<DebugDataRecord::COND_CMP_IN>(debug_data, iteration, state.conditional_code);
+ if (evaluate_condition(instr.flow_control)) {
+ call(program_counter + 1, instr.flow_control.dest_offset - program_counter - 1,
instr.flow_control.dest_offset + instr.flow_control.num_instructions, 0,
0);
} else {
- call(state, instr.flow_control.dest_offset, instr.flow_control.num_instructions,
+ call(instr.flow_control.dest_offset, instr.flow_control.num_instructions,
instr.flow_control.dest_offset + instr.flow_control.num_instructions, 0,
0);
}
@@ -633,9 +627,8 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
uniforms.i[instr.flow_control.int_uniform_id].w);
state.address_registers[2] = loop_param.y;
- Record<DebugDataRecord::LOOP_INT_IN>(state.debug, iteration, loop_param);
- call(state, program_counter + 1,
- instr.flow_control.dest_offset - program_counter + 1,
+ Record<DebugDataRecord::LOOP_INT_IN>(debug_data, iteration, loop_param);
+ call(program_counter + 1, instr.flow_control.dest_offset - program_counter + 1,
instr.flow_control.dest_offset + 1, loop_param.x, loop_param.z);
break;
}
@@ -657,8 +650,8 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
}
// Explicit instantiation
-template void RunInterpreter(const ShaderSetup& setup, UnitState<false>& state, unsigned offset);
-template void RunInterpreter(const ShaderSetup& setup, UnitState<true>& state, unsigned offset);
+template void RunInterpreter(const ShaderSetup&, UnitState&, DebugData<false>&, unsigned offset);
+template void RunInterpreter(const ShaderSetup&, UnitState&, DebugData<true>&, unsigned offset);
} // namespace
diff --git a/src/video_core/shader/shader_interpreter.h b/src/video_core/shader/shader_interpreter.h
index 48ede0a2e..d31dcd7a6 100644
--- a/src/video_core/shader/shader_interpreter.h
+++ b/src/video_core/shader/shader_interpreter.h
@@ -8,11 +8,14 @@ namespace Pica {
namespace Shader {
-template <bool Debug>
struct UnitState;
template <bool Debug>
-void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned offset);
+struct DebugData;
+
+template <bool Debug>
+void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug>& debug_data,
+ unsigned offset);
} // namespace
diff --git a/src/video_core/shader/shader_jit_x64.cpp b/src/video_core/shader/shader_jit_x64.cpp
index cfdeb8d6a..c588b778b 100644
--- a/src/video_core/shader/shader_jit_x64.cpp
+++ b/src/video_core/shader/shader_jit_x64.cpp
@@ -185,10 +185,10 @@ void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRe
if (src_reg.GetRegisterType() == RegisterType::FloatUniform) {
src_ptr = SETUP;
- src_offset = ShaderSetup::UniformOffset(RegisterType::FloatUniform, src_reg.GetIndex());
+ src_offset = ShaderSetup::GetFloatUniformOffset(src_reg.GetIndex());
} else {
src_ptr = STATE;
- src_offset = UnitState<false>::InputOffset(src_reg);
+ src_offset = UnitState::InputOffset(src_reg);
}
int src_offset_disp = (int)src_offset;
@@ -222,7 +222,7 @@ void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRe
movaps(dest, xword[src_ptr + ADDROFFS_REG_1 + src_offset_disp]);
break;
case 3: // address offset 3
- movaps(dest, xword[src_ptr + LOOPCOUNT_REG + src_offset_disp]);
+ movaps(dest, xword[src_ptr + LOOPCOUNT_REG.cvt64() + src_offset_disp]);
break;
default:
UNREACHABLE();
@@ -266,9 +266,7 @@ void JitShader::Compile_DestEnable(Instruction instr, Xmm src) {
SwizzlePattern swiz = {g_state.vs.swizzle_data[operand_desc_id]};
- int dest_offset_disp = (int)UnitState<false>::OutputOffset(dest);
- ASSERT_MSG(dest_offset_disp == UnitState<false>::OutputOffset(dest),
- "Destinaton offset too large for int type");
+ size_t dest_offset_disp = UnitState::OutputOffset(dest);
// If all components are enabled, write the result to the destination register
if (swiz.dest_mask == NO_DEST_REG_MASK) {
@@ -348,8 +346,7 @@ void JitShader::Compile_EvaluateCondition(Instruction instr) {
}
void JitShader::Compile_UniformCondition(Instruction instr) {
- size_t offset =
- ShaderSetup::UniformOffset(RegisterType::BoolUniform, instr.flow_control.bool_uniform_id);
+ size_t offset = ShaderSetup::GetBoolUniformOffset(instr.flow_control.bool_uniform_id);
cmp(byte[SETUP + offset], 0);
}
@@ -732,8 +729,7 @@ void JitShader::Compile_LOOP(Instruction instr) {
// This decodes the fields from the integer uniform at index instr.flow_control.int_uniform_id.
// The Y (LOOPCOUNT_REG) and Z (LOOPINC) component are kept multiplied by 16 (Left shifted by
// 4 bits) to be used as an offset into the 16-byte vector registers later
- size_t offset =
- ShaderSetup::UniformOffset(RegisterType::IntUniform, instr.flow_control.int_uniform_id);
+ size_t offset = ShaderSetup::GetIntUniformOffset(instr.flow_control.int_uniform_id);
mov(LOOPCOUNT, dword[SETUP + offset]);
mov(LOOPCOUNT_REG, LOOPCOUNT);
shr(LOOPCOUNT_REG, 4);
diff --git a/src/video_core/shader/shader_jit_x64.h b/src/video_core/shader/shader_jit_x64.h
index e0ecde3f2..f37548306 100644
--- a/src/video_core/shader/shader_jit_x64.h
+++ b/src/video_core/shader/shader_jit_x64.h
@@ -34,7 +34,7 @@ class JitShader : public Xbyak::CodeGenerator {
public:
JitShader();
- void Run(const ShaderSetup& setup, UnitState<false>& state, unsigned offset) const {
+ void Run(const ShaderSetup& setup, UnitState& state, unsigned offset) const {
program(&setup, &state, instruction_labels[offset].getAddress());
}