diff options
Diffstat (limited to '')
107 files changed, 2114 insertions, 1162 deletions
diff --git a/.gitmodules b/.gitmodules index f498a60de..dbb1b0dd3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "dynarmic"] path = externals/dynarmic url = https://github.com/MerryMage/dynarmic.git +[submodule "xbyak"] + path = externals/xbyak + url = https://github.com/herumi/xbyak.git diff --git a/CMakeLists.txt b/CMakeLists.txt index c02c96840..52a1fd492 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ # CMake 3.2 required for cmake to know the right flags for CXX standard on OSX cmake_minimum_required(VERSION 3.2) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) function(download_bundled_external remote_path lib_name prefix_var) set(prefix "${CMAKE_BINARY_DIR}/externals/${lib_name}") @@ -183,8 +184,10 @@ IF (APPLE) FIND_LIBRARY(COCOA_LIBRARY Cocoa) # Umbrella framework for everything GUI-related set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY}) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++") + if (CMAKE_CXX_COMPILER_ID STREQUAL Clang) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++") + endif() ELSEIF (WIN32) # WSAPoll and SHGetKnownFolderPath (AppData/Roaming) didn't exist before WinNT 6.x (Vista) add_definitions(-D_WIN32_WINNT=0x0600 -DWINVER=0x0600) @@ -254,6 +257,8 @@ set(INI_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/externals/inih") include_directories(${INI_PREFIX}) add_subdirectory(${INI_PREFIX}) +add_subdirectory(externals) + option(DYNARMIC_TESTS OFF) add_subdirectory(externals/dynarmic) diff --git a/CMakeModules/CopyCitraQt5Deps.cmake b/CMakeModules/CopyCitraQt5Deps.cmake new file mode 100644 index 000000000..05f58cf9a --- /dev/null +++ b/CMakeModules/CopyCitraQt5Deps.cmake @@ -0,0 +1,17 @@ +function(copy_citra_Qt5_deps target_dir) + include(WindowsCopyFiles) + set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/") + set(Qt5_DLL_DIR "${Qt5_DIR}/../../../bin") + set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/") + set(PLATFORMS ${DLL_DEST}platforms/) + windows_copy_files(${target_dir} ${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>.*) +endfunction(copy_citra_Qt5_deps) diff --git a/CMakeModules/CopyCitraSDLDeps.cmake b/CMakeModules/CopyCitraSDLDeps.cmake new file mode 100644 index 000000000..4f9e4aeb9 --- /dev/null +++ b/CMakeModules/CopyCitraSDLDeps.cmake @@ -0,0 +1,5 @@ +function(copy_citra_SDL_deps target_dir) + include(WindowsCopyFiles) + set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/") + windows_copy_files(${target_dir} ${SDL2_DLL_DIR} ${DLL_DEST} SDL2.dll) +endfunction(copy_citra_SDL_deps) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt new file mode 100644 index 000000000..7e4b05ffc --- /dev/null +++ b/externals/CMakeLists.txt @@ -0,0 +1,8 @@ +# Xbyak +if (ARCHITECTURE_x86_64) + add_library(xbyak INTERFACE) + target_include_directories(xbyak INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/xbyak/xbyak) + if (NOT MSVC) + target_compile_options(xbyak INTERFACE -fno-operator-names) + endif() +endif() diff --git a/externals/dynarmic b/externals/dynarmic -Subproject 54d051977f7a6af9c7596ba6a4e6eb467bd1e0b +Subproject 34e19f135c0dd2feac4f77660f51aa4ea28a738 diff --git a/externals/xbyak b/externals/xbyak new file mode 160000 +Subproject fe4765d2fed4e990ea5e9661b6bc5fc9bf48ec1 diff --git a/src/audio_core/time_stretch.h b/src/audio_core/time_stretch.h index fa81718ed..e3e4dc353 100644 --- a/src/audio_core/time_stretch.h +++ b/src/audio_core/time_stretch.h @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#pragma once + #include <cstddef> #include <memory> #include <vector> 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..93f1c339d 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -1,17 +1,18 @@ 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 debugger/callstack.cpp debugger/disassembler.cpp - debugger/graphics.cpp - debugger/graphics_breakpoint_observer.cpp - debugger/graphics_breakpoints.cpp - debugger/graphics_cmdlists.cpp - debugger/graphics_surface.cpp - debugger/graphics_tracing.cpp - debugger/graphics_vertex_shader.cpp + debugger/graphics/graphics.cpp + debugger/graphics/graphics_breakpoint_observer.cpp + debugger/graphics/graphics_breakpoints.cpp + debugger/graphics/graphics_cmdlists.cpp + debugger/graphics/graphics_surface.cpp + debugger/graphics/graphics_tracing.cpp + debugger/graphics/graphics_vertex_shader.cpp debugger/profiler.cpp debugger/ramview.cpp debugger/registers.cpp @@ -38,14 +39,14 @@ set(HEADERS config.h debugger/callstack.h debugger/disassembler.h - debugger/graphics.h - debugger/graphics_breakpoint_observer.h - debugger/graphics_breakpoints.h - debugger/graphics_breakpoints_p.h - debugger/graphics_cmdlists.h - debugger/graphics_surface.h - debugger/graphics_tracing.h - debugger/graphics_vertex_shader.h + debugger/graphics/graphics.h + debugger/graphics/graphics_breakpoint_observer.h + debugger/graphics/graphics_breakpoints.h + debugger/graphics/graphics_breakpoints_p.h + debugger/graphics/graphics_cmdlists.h + debugger/graphics/graphics_surface.h + debugger/graphics/graphics_tracing.h + debugger/graphics/graphics_vertex_shader.h debugger/profiler.h debugger/ramview.h debugger/registers.h @@ -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/disassembler.h b/src/citra_qt/debugger/disassembler.h index 895f6ac89..a6e59515c 100644 --- a/src/citra_qt/debugger/disassembler.h +++ b/src/citra_qt/debugger/disassembler.h @@ -38,9 +38,7 @@ private: unsigned int program_counter; QModelIndex selection; - - // TODO: Make BreakPoints less crappy (i.e. const-correct) so that this needn't be mutable. - mutable BreakPoints breakpoints; + BreakPoints breakpoints; }; class DisassemblerWidget : public QDockWidget { diff --git a/src/citra_qt/debugger/graphics.cpp b/src/citra_qt/debugger/graphics/graphics.cpp index ef337827a..6a76adeae 100644 --- a/src/citra_qt/debugger/graphics.cpp +++ b/src/citra_qt/debugger/graphics/graphics.cpp @@ -3,7 +3,7 @@ // Refer to the license.txt file included. #include <QListView> -#include "citra_qt/debugger/graphics.h" +#include "citra_qt/debugger/graphics/graphics.h" #include "citra_qt/util/util.h" extern GraphicsDebugger g_debugger; diff --git a/src/citra_qt/debugger/graphics.h b/src/citra_qt/debugger/graphics/graphics.h index 8837fb792..8837fb792 100644 --- a/src/citra_qt/debugger/graphics.h +++ b/src/citra_qt/debugger/graphics/graphics.h diff --git a/src/citra_qt/debugger/graphics_breakpoint_observer.cpp b/src/citra_qt/debugger/graphics/graphics_breakpoint_observer.cpp index e01d3440e..dc6070dea 100644 --- a/src/citra_qt/debugger/graphics_breakpoint_observer.cpp +++ b/src/citra_qt/debugger/graphics/graphics_breakpoint_observer.cpp @@ -3,7 +3,7 @@ // Refer to the license.txt file included. #include <QMetaType> -#include "citra_qt/debugger/graphics_breakpoint_observer.h" +#include "citra_qt/debugger/graphics/graphics_breakpoint_observer.h" BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context, const QString& title, QWidget* parent) diff --git a/src/citra_qt/debugger/graphics_breakpoint_observer.h b/src/citra_qt/debugger/graphics/graphics_breakpoint_observer.h index e77df4f5b..e77df4f5b 100644 --- a/src/citra_qt/debugger/graphics_breakpoint_observer.h +++ b/src/citra_qt/debugger/graphics/graphics_breakpoint_observer.h diff --git a/src/citra_qt/debugger/graphics_breakpoints.cpp b/src/citra_qt/debugger/graphics/graphics_breakpoints.cpp index d2a036dfa..030828ba8 100644 --- a/src/citra_qt/debugger/graphics_breakpoints.cpp +++ b/src/citra_qt/debugger/graphics/graphics_breakpoints.cpp @@ -7,8 +7,8 @@ #include <QPushButton> #include <QTreeView> #include <QVBoxLayout> -#include "citra_qt/debugger/graphics_breakpoints.h" -#include "citra_qt/debugger/graphics_breakpoints_p.h" +#include "citra_qt/debugger/graphics/graphics_breakpoints.h" +#include "citra_qt/debugger/graphics/graphics_breakpoints_p.h" #include "common/assert.h" BreakPointModel::BreakPointModel(std::shared_ptr<Pica::DebugContext> debug_context, QObject* parent) diff --git a/src/citra_qt/debugger/graphics_breakpoints.h b/src/citra_qt/debugger/graphics/graphics_breakpoints.h index bec72a2db..bec72a2db 100644 --- a/src/citra_qt/debugger/graphics_breakpoints.h +++ b/src/citra_qt/debugger/graphics/graphics_breakpoints.h diff --git a/src/citra_qt/debugger/graphics_breakpoints_p.h b/src/citra_qt/debugger/graphics/graphics_breakpoints_p.h index dc64706bd..dc64706bd 100644 --- a/src/citra_qt/debugger/graphics_breakpoints_p.h +++ b/src/citra_qt/debugger/graphics/graphics_breakpoints_p.h diff --git a/src/citra_qt/debugger/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp index 98b619dea..dab529e3a 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp @@ -13,7 +13,7 @@ #include <QSpinBox> #include <QTreeView> #include <QVBoxLayout> -#include "citra_qt/debugger/graphics_cmdlists.h" +#include "citra_qt/debugger/graphics/graphics_cmdlists.h" #include "citra_qt/util/spinbox.h" #include "citra_qt/util/util.h" #include "common/vector_math.h" diff --git a/src/citra_qt/debugger/graphics_cmdlists.h b/src/citra_qt/debugger/graphics/graphics_cmdlists.h index 8f40b94c5..8f40b94c5 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.h +++ b/src/citra_qt/debugger/graphics/graphics_cmdlists.h diff --git a/src/citra_qt/debugger/graphics_surface.cpp b/src/citra_qt/debugger/graphics/graphics_surface.cpp index 906daaa50..4efd95d3c 100644 --- a/src/citra_qt/debugger/graphics_surface.cpp +++ b/src/citra_qt/debugger/graphics/graphics_surface.cpp @@ -11,7 +11,7 @@ #include <QPushButton> #include <QScrollArea> #include <QSpinBox> -#include "citra_qt/debugger/graphics_surface.h" +#include "citra_qt/debugger/graphics/graphics_surface.h" #include "citra_qt/util/spinbox.h" #include "common/color.h" #include "core/hw/gpu.h" diff --git a/src/citra_qt/debugger/graphics_surface.h b/src/citra_qt/debugger/graphics/graphics_surface.h index 19ffb55fb..28f5650a7 100644 --- a/src/citra_qt/debugger/graphics_surface.h +++ b/src/citra_qt/debugger/graphics/graphics_surface.h @@ -6,7 +6,7 @@ #include <QLabel> #include <QPushButton> -#include "citra_qt/debugger/graphics_breakpoint_observer.h" +#include "citra_qt/debugger/graphics/graphics_breakpoint_observer.h" class QComboBox; class QSpinBox; diff --git a/src/citra_qt/debugger/graphics_tracing.cpp b/src/citra_qt/debugger/graphics/graphics_tracing.cpp index 5c6b74034..716ed50b8 100644 --- a/src/citra_qt/debugger/graphics_tracing.cpp +++ b/src/citra_qt/debugger/graphics/graphics_tracing.cpp @@ -12,7 +12,7 @@ #include <QMessageBox> #include <QPushButton> #include <boost/range/algorithm/copy.hpp> -#include "citra_qt/debugger/graphics_tracing.h" +#include "citra_qt/debugger/graphics/graphics_tracing.h" #include "common/common_types.h" #include "core/hw/gpu.h" #include "core/hw/lcd.h" diff --git a/src/citra_qt/debugger/graphics_tracing.h b/src/citra_qt/debugger/graphics/graphics_tracing.h index 20acc91e7..3f73bcd2e 100644 --- a/src/citra_qt/debugger/graphics_tracing.h +++ b/src/citra_qt/debugger/graphics/graphics_tracing.h @@ -4,7 +4,7 @@ #pragma once -#include "citra_qt/debugger/graphics_breakpoint_observer.h" +#include "citra_qt/debugger/graphics/graphics_breakpoint_observer.h" class EmuThread; diff --git a/src/citra_qt/debugger/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp index 96b40db1e..b75b94ef8 100644 --- a/src/citra_qt/debugger/graphics_vertex_shader.cpp +++ b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp @@ -14,7 +14,7 @@ #include <QSignalMapper> #include <QSpinBox> #include <QTreeView> -#include "citra_qt/debugger/graphics_vertex_shader.h" +#include "citra_qt/debugger/graphics/graphics_vertex_shader.h" #include "citra_qt/util/util.h" #include "video_core/pica.h" #include "video_core/pica_state.h" diff --git a/src/citra_qt/debugger/graphics_vertex_shader.h b/src/citra_qt/debugger/graphics/graphics_vertex_shader.h index 6e62fbb9b..bedea0bed 100644 --- a/src/citra_qt/debugger/graphics_vertex_shader.h +++ b/src/citra_qt/debugger/graphics/graphics_vertex_shader.h @@ -6,7 +6,7 @@ #include <QAbstractTableModel> #include <QTreeView> -#include "citra_qt/debugger/graphics_breakpoint_observer.h" +#include "citra_qt/debugger/graphics/graphics_breakpoint_observer.h" #include "nihstro/parser_shbin.h" #include "video_core/shader/shader.h" diff --git a/src/citra_qt/debugger/wait_tree.cpp b/src/citra_qt/debugger/wait_tree.cpp index be5a51e52..5a308bf7f 100644 --- a/src/citra_qt/debugger/wait_tree.cpp +++ b/src/citra_qt/debugger/wait_tree.cpp @@ -8,7 +8,6 @@ #include "core/hle/kernel/event.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/semaphore.h" -#include "core/hle/kernel/session.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/timer.h" @@ -230,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..e16d3196c 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> @@ -16,12 +17,12 @@ #include "citra_qt/configure_dialog.h" #include "citra_qt/debugger/callstack.h" #include "citra_qt/debugger/disassembler.h" -#include "citra_qt/debugger/graphics.h" -#include "citra_qt/debugger/graphics_breakpoints.h" -#include "citra_qt/debugger/graphics_cmdlists.h" -#include "citra_qt/debugger/graphics_surface.h" -#include "citra_qt/debugger/graphics_tracing.h" -#include "citra_qt/debugger/graphics_vertex_shader.h" +#include "citra_qt/debugger/graphics/graphics.h" +#include "citra_qt/debugger/graphics/graphics_breakpoints.h" +#include "citra_qt/debugger/graphics/graphics_cmdlists.h" +#include "citra_qt/debugger/graphics/graphics_surface.h" +#include "citra_qt/debugger/graphics/graphics_tracing.h" +#include "citra_qt/debugger/graphics/graphics_vertex_shader.h" #include "citra_qt/debugger/profiler.h" #include "citra_qt/debugger/ramview.h" #include "citra_qt/debugger/registers.h" @@ -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/CMakeLists.txt b/src/common/CMakeLists.txt index 74a271f08..e6c2ce335 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -71,9 +71,15 @@ if(ARCHITECTURE_x86_64) set(HEADERS ${HEADERS} x64/abi.h x64/cpu_detect.h - x64/emitter.h) + x64/emitter.h + x64/xbyak_abi.h + x64/xbyak_util.h + ) endif() create_directory_groups(${SRCS} ${HEADERS}) add_library(common STATIC ${SRCS} ${HEADERS}) +if (ARCHITECTURE_x86_64) + target_link_libraries(common xbyak) +endif() 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/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h new file mode 100644 index 000000000..6090d93e1 --- /dev/null +++ b/src/common/x64/xbyak_abi.h @@ -0,0 +1,178 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <initializer_list> +#include <xbyak.h> +#include "common/assert.h" +#include "common/bit_set.h" + +namespace Common { +namespace X64 { + +int RegToIndex(const Xbyak::Reg& reg) { + using Kind = Xbyak::Reg::Kind; + ASSERT_MSG((reg.getKind() & (Kind::REG | Kind::XMM)) != 0, + "RegSet only support GPRs and XMM registers."); + ASSERT_MSG(reg.getIdx() < 16, "RegSet only supports XXM0-15."); + return reg.getIdx() + (reg.getKind() == Kind::REG ? 0 : 16); +} + +inline Xbyak::Reg64 IndexToReg64(int reg_index) { + ASSERT(reg_index < 16); + return Xbyak::Reg64(reg_index); +} + +inline Xbyak::Xmm IndexToXmm(int reg_index) { + ASSERT(reg_index >= 16 && reg_index < 32); + return Xbyak::Xmm(reg_index - 16); +} + +inline Xbyak::Reg IndexToReg(int reg_index) { + if (reg_index < 16) { + return IndexToReg64(reg_index); + } else { + return IndexToXmm(reg_index); + } +} + +inline BitSet32 BuildRegSet(std::initializer_list<Xbyak::Reg> regs) { + BitSet32 bits; + for (const Xbyak::Reg& reg : regs) { + bits[RegToIndex(reg)] = true; + } + return bits; +} + +const BitSet32 ABI_ALL_GPRS(0x0000FFFF); +const BitSet32 ABI_ALL_XMMS(0xFFFF0000); + +#ifdef _WIN32 + +// Microsoft x64 ABI +const Xbyak::Reg ABI_RETURN = Xbyak::util::rax; +const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rcx; +const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rdx; +const Xbyak::Reg ABI_PARAM3 = Xbyak::util::r8; +const Xbyak::Reg ABI_PARAM4 = Xbyak::util::r9; + +const BitSet32 ABI_ALL_CALLER_SAVED = BuildRegSet({ + // GPRs + Xbyak::util::rcx, Xbyak::util::rdx, Xbyak::util::r8, Xbyak::util::r9, Xbyak::util::r10, + Xbyak::util::r11, + // XMMs + Xbyak::util::xmm0, Xbyak::util::xmm1, Xbyak::util::xmm2, Xbyak::util::xmm3, Xbyak::util::xmm4, + Xbyak::util::xmm5, +}); + +const BitSet32 ABI_ALL_CALLEE_SAVED = BuildRegSet({ + // GPRs + Xbyak::util::rbx, Xbyak::util::rsi, Xbyak::util::rdi, Xbyak::util::rbp, Xbyak::util::r12, + Xbyak::util::r13, Xbyak::util::r14, Xbyak::util::r15, + // XMMs + Xbyak::util::xmm6, Xbyak::util::xmm7, Xbyak::util::xmm8, Xbyak::util::xmm9, Xbyak::util::xmm10, + Xbyak::util::xmm11, Xbyak::util::xmm12, Xbyak::util::xmm13, Xbyak::util::xmm14, + Xbyak::util::xmm15, +}); + +constexpr size_t ABI_SHADOW_SPACE = 0x20; + +#else + +// System V x86-64 ABI +const Xbyak::Reg ABI_RETURN = Xbyak::util::rax; +const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rdi; +const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rsi; +const Xbyak::Reg ABI_PARAM3 = Xbyak::util::rdx; +const Xbyak::Reg ABI_PARAM4 = Xbyak::util::rcx; + +const BitSet32 ABI_ALL_CALLER_SAVED = BuildRegSet({ + // GPRs + Xbyak::util::rcx, Xbyak::util::rdx, Xbyak::util::rdi, Xbyak::util::rsi, Xbyak::util::r8, + Xbyak::util::r9, Xbyak::util::r10, Xbyak::util::r11, + // XMMs + Xbyak::util::xmm0, Xbyak::util::xmm1, Xbyak::util::xmm2, Xbyak::util::xmm3, Xbyak::util::xmm4, + Xbyak::util::xmm5, Xbyak::util::xmm6, Xbyak::util::xmm7, Xbyak::util::xmm8, Xbyak::util::xmm9, + Xbyak::util::xmm10, Xbyak::util::xmm11, Xbyak::util::xmm12, Xbyak::util::xmm13, + Xbyak::util::xmm14, Xbyak::util::xmm15, +}); + +const BitSet32 ABI_ALL_CALLEE_SAVED = BuildRegSet({ + // GPRs + Xbyak::util::rbx, Xbyak::util::rbp, Xbyak::util::r12, Xbyak::util::r13, Xbyak::util::r14, + Xbyak::util::r15, +}); + +constexpr size_t ABI_SHADOW_SPACE = 0; + +#endif + +void ABI_CalculateFrameSize(BitSet32 regs, size_t rsp_alignment, size_t needed_frame_size, + s32* out_subtraction, s32* out_xmm_offset) { + int count = (regs & ABI_ALL_GPRS).Count(); + rsp_alignment -= count * 8; + size_t subtraction = 0; + int xmm_count = (regs & ABI_ALL_XMMS).Count(); + if (xmm_count) { + // If we have any XMMs to save, we must align the stack here. + subtraction = rsp_alignment & 0xF; + } + subtraction += 0x10 * xmm_count; + size_t xmm_base_subtraction = subtraction; + subtraction += needed_frame_size; + subtraction += ABI_SHADOW_SPACE; + // Final alignment. + rsp_alignment -= subtraction; + subtraction += rsp_alignment & 0xF; + + *out_subtraction = (s32)subtraction; + *out_xmm_offset = (s32)(subtraction - xmm_base_subtraction); +} + +size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, + size_t rsp_alignment, size_t needed_frame_size = 0) { + s32 subtraction, xmm_offset; + ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset); + + for (int reg_index : (regs & ABI_ALL_GPRS)) { + code.push(IndexToReg64(reg_index)); + } + + if (subtraction != 0) { + code.sub(code.rsp, subtraction); + } + + for (int reg_index : (regs & ABI_ALL_XMMS)) { + code.movaps(code.xword[code.rsp + xmm_offset], IndexToXmm(reg_index)); + xmm_offset += 0x10; + } + + return ABI_SHADOW_SPACE; +} + +void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, size_t rsp_alignment, + size_t needed_frame_size = 0) { + s32 subtraction, xmm_offset; + ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset); + + for (int reg_index : (regs & ABI_ALL_XMMS)) { + code.movaps(IndexToXmm(reg_index), code.xword[code.rsp + xmm_offset]); + xmm_offset += 0x10; + } + + if (subtraction != 0) { + code.add(code.rsp, subtraction); + } + + // GPRs need to be popped in reverse order + for (int reg_index = 15; reg_index >= 0; reg_index--) { + if (regs[reg_index]) { + code.pop(IndexToReg64(reg_index)); + } + } +} + +} // namespace X64 +} // namespace Common diff --git a/src/common/x64/xbyak_util.h b/src/common/x64/xbyak_util.h new file mode 100644 index 000000000..0f52f704b --- /dev/null +++ b/src/common/x64/xbyak_util.h @@ -0,0 +1,49 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <type_traits> +#include <xbyak.h> +#include "common/x64/xbyak_abi.h" + +namespace Common { +namespace X64 { + +// Constants for use with cmpps/cmpss +enum { + CMP_EQ = 0, + CMP_LT = 1, + CMP_LE = 2, + CMP_UNORD = 3, + CMP_NEQ = 4, + CMP_NLT = 5, + CMP_NLE = 6, + CMP_ORD = 7, +}; + +inline bool IsWithin2G(uintptr_t ref, uintptr_t target) { + u64 distance = target - (ref + 5); + return !(distance >= 0x8000'0000ULL && distance <= ~0x8000'0000ULL); +} + +inline bool IsWithin2G(const Xbyak::CodeGenerator& code, uintptr_t target) { + return IsWithin2G(reinterpret_cast<uintptr_t>(code.getCurr()), target); +} + +template <typename T> +inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) { + static_assert(std::is_pointer<T>(), "Argument must be a (function) pointer."); + size_t addr = reinterpret_cast<size_t>(f); + if (IsWithin2G(code, addr)) { + code.call(f); + } else { + // ABI_RETURN is a safe temp register to use before a call + code.mov(ABI_RETURN, addr); + code.call(ABI_RETURN); + } +} + +} // namespace X64 +} // namespace Common diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e26677079..af224166a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -38,6 +38,7 @@ set(SRCS hle/applets/swkbd.cpp hle/kernel/address_arbiter.cpp hle/kernel/client_port.cpp + hle/kernel/client_session.cpp hle/kernel/event.cpp hle/kernel/kernel.cpp hle/kernel/memory.cpp @@ -46,14 +47,15 @@ set(SRCS hle/kernel/resource_limit.cpp hle/kernel/semaphore.cpp hle/kernel/server_port.cpp - hle/kernel/session.cpp + hle/kernel/server_session.cpp hle/kernel/shared_memory.cpp hle/kernel/thread.cpp 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 @@ -73,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 @@ -194,12 +197,14 @@ set(HEADERS hle/config_mem.h hle/function_wrappers.h hle/hle.h + hle/ipc.h hle/applets/applet.h hle/applets/erreula.h hle/applets/mii_selector.h hle/applets/swkbd.h hle/kernel/address_arbiter.h hle/kernel/client_port.h + hle/kernel/client_session.h hle/kernel/event.h hle/kernel/kernel.h hle/kernel/memory.h @@ -208,15 +213,16 @@ set(HEADERS hle/kernel/resource_limit.h hle/kernel/semaphore.h hle/kernel/server_port.h - hle/kernel/session.h + hle/kernel/server_session.h hle/kernel/shared_memory.h hle/kernel/thread.h hle/kernel/timer.h 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 @@ -236,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/session.h b/src/core/hle/ipc.h index ec025f732..4e094faa7 100644 --- a/src/core/hle/kernel/session.h +++ b/src/core/hle/ipc.h @@ -1,17 +1,31 @@ -// Copyright 2014 Citra Emulator Project +// Copyright 2016 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once -#include <string> -#include "common/assert.h" #include "common/common_types.h" -#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/thread.h" -#include "core/hle/result.h" #include "core/memory.h" +namespace Kernel { + +static const int kCommandHeaderOffset = 0x80; ///< Offset into command buffer of header + +/** + * Returns a pointer to the command buffer in the current thread's TLS + * TODO(Subv): This is not entirely correct, the command buffer should be copied from + * the thread's TLS to an intermediate buffer in kernel memory, and then copied again to + * the service handler process' memory. + * @param offset Optional offset into command buffer + * @return Pointer to command buffer + */ +inline u32* GetCommandBuffer(const int offset = 0) { + return (u32*)Memory::GetPointer(GetCurrentThread()->GetTLSAddress() + kCommandHeaderOffset + + offset); +} +} + namespace IPC { enum DescriptorType : u32 { @@ -144,75 +158,3 @@ inline DescriptorType GetDescriptorType(u32 descriptor) { } } // namespace IPC - -namespace Kernel { - -static const int kCommandHeaderOffset = 0x80; ///< Offset into command buffer of header - -/** - * Returns a pointer to the command buffer in the current thread's TLS - * TODO(Subv): This is not entirely correct, the command buffer should be copied from - * the thread's TLS to an intermediate buffer in kernel memory, and then copied again to - * the service handler process' memory. - * @param offset Optional offset into command buffer - * @return Pointer to command buffer - */ -inline u32* GetCommandBuffer(const int offset = 0) { - return (u32*)Memory::GetPointer(GetCurrentThread()->GetTLSAddress() + kCommandHeaderOffset + - offset); -} - -/** - * Kernel object representing the client endpoint of an IPC session. Sessions are the basic CTR-OS - * primitive for communication between different processes, and are used to implement service calls - * to the various system services. - * - * To make a service call, the client must write the command header and parameters to the buffer - * located at offset 0x80 of the TLS (Thread-Local Storage) area, then execute a SendSyncRequest - * SVC call with its Session handle. The kernel will read the command header, using it to marshall - * the parameters to the process at the server endpoint of the session. After the server replies to - * the request, the response is marshalled back to the caller's TLS buffer and control is - * transferred back to it. - * - * In Citra, only the client endpoint is currently implemented and only HLE calls, where the IPC - * request is answered by C++ code in the emulator, are supported. When SendSyncRequest is called - * with the session handle, this class's SyncRequest method is called, which should read the TLS - * buffer and emulate the call accordingly. Since the code can directly read the emulated memory, - * no parameter marshalling is done. - * - * In the long term, this should be turned into the full-fledged IPC mechanism implemented by - * CTR-OS so that IPC calls can be optionally handled by the real implementations of processes, as - * opposed to HLE simulations. - */ -class Session : public WaitObject { -public: - Session(); - ~Session() override; - - std::string GetTypeName() const override { - return "Session"; - } - - static const HandleType HANDLE_TYPE = HandleType::Session; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - /** - * Handles a synchronous call to this session using HLE emulation. Emulated <-> emulated calls - * aren't supported yet. - */ - virtual ResultVal<bool> SyncRequest() = 0; - - // TODO(bunnei): These functions exist to satisfy a hardware test with a Session object - // passed into WaitSynchronization. Figure out the meaning of them. - - bool ShouldWait() override { - return true; - } - - void Acquire() override { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); - } -}; -} 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/client_port.cpp b/src/core/hle/kernel/client_port.cpp index aedc6f989..22645f4ec 100644 --- a/src/core/hle/kernel/client_port.cpp +++ b/src/core/hle/kernel/client_port.cpp @@ -4,12 +4,44 @@ #include "common/assert.h" #include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/client_session.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/server_port.h" +#include "core/hle/kernel/server_session.h" namespace Kernel { ClientPort::ClientPort() {} ClientPort::~ClientPort() {} +ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() { + // Note: Threads do not wait for the server endpoint to call + // AcceptSession before returning from this call. + + if (active_sessions >= max_sessions) { + // TODO(Subv): Return an error code in this situation after session disconnection is + // implemented. + /*return ResultCode(ErrorDescription::MaxConnectionsReached, + ErrorModule::OS, ErrorSummary::WouldBlock, + ErrorLevel::Temporary);*/ + } + active_sessions++; + + // Create a new session pair, let the created sessions inherit the parent port's HLE handler. + auto sessions = + ServerSession::CreateSessionPair(server_port->GetName(), server_port->hle_handler); + auto client_session = std::get<SharedPtr<ClientSession>>(sessions); + auto server_session = std::get<SharedPtr<ServerSession>>(sessions); + + if (server_port->hle_handler) + server_port->hle_handler->ClientConnected(server_session); + + server_port->pending_sessions.push_back(std::move(server_session)); + + // Wake the threads waiting on the ServerPort + server_port->WakeupAllWaitingThreads(); + + return MakeResult<SharedPtr<ClientSession>>(std::move(client_session)); +} + } // namespace diff --git a/src/core/hle/kernel/client_port.h b/src/core/hle/kernel/client_port.h index d28147718..511490c7c 100644 --- a/src/core/hle/kernel/client_port.h +++ b/src/core/hle/kernel/client_port.h @@ -11,8 +11,9 @@ namespace Kernel { class ServerPort; +class ClientSession; -class ClientPort : public Object { +class ClientPort final : public Object { public: friend class ServerPort; std::string GetTypeName() const override { @@ -27,12 +28,20 @@ public: return HANDLE_TYPE; } + /** + * Creates a new Session pair, adds the created ServerSession to the associated ServerPort's + * list of pending sessions, and signals the ServerPort, causing any threads + * waiting on it to awake. + * @returns ClientSession The client endpoint of the created Session pair, or error code. + */ + ResultVal<SharedPtr<ClientSession>> Connect(); + SharedPtr<ServerPort> server_port; ///< ServerPort associated with this client port. u32 max_sessions; ///< Maximum number of simultaneous sessions the port can have u32 active_sessions; ///< Number of currently open sessions to this port std::string name; ///< Name of client port (optional) -protected: +private: ClientPort(); ~ClientPort() override; }; diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp new file mode 100644 index 000000000..0331386ec --- /dev/null +++ b/src/core/hle/kernel/client_session.cpp @@ -0,0 +1,40 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" + +#include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/server_session.h" + +namespace Kernel { + +ClientSession::ClientSession() = default; +ClientSession::~ClientSession() { + // This destructor will be called automatically when the last ClientSession handle is closed by + // the emulated application. + + if (server_session->hle_handler) + server_session->hle_handler->ClientDisconnected(server_session); + + // TODO(Subv): If the session is still open, set the connection status to 2 (Closed by client), + // wake up all the ServerSession's waiting threads and set the WaitSynchronization result to + // 0xC920181A. +} + +ResultVal<SharedPtr<ClientSession>> ClientSession::Create(ServerSession* server_session, + std::string name) { + SharedPtr<ClientSession> client_session(new ClientSession); + + client_session->name = std::move(name); + client_session->server_session = server_session; + client_session->session_status = SessionStatus::Open; + return MakeResult<SharedPtr<ClientSession>>(std::move(client_session)); +} + +ResultCode ClientSession::SendSyncRequest() { + // Signal the server session that new data is available + return server_session->HandleSyncRequest(); +} + +} // namespace diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h new file mode 100644 index 000000000..ed468dec6 --- /dev/null +++ b/src/core/hle/kernel/client_session.h @@ -0,0 +1,65 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> + +#include "common/common_types.h" + +#include "core/hle/kernel/kernel.h" + +namespace Kernel { + +class ServerSession; + +enum class SessionStatus { + Open = 1, + ClosedByClient = 2, + ClosedBYServer = 3, +}; + +class ClientSession final : public Object { +public: + friend class ServerSession; + + std::string GetTypeName() const override { + return "ClientSession"; + } + + std::string GetName() const override { + return name; + } + + static const HandleType HANDLE_TYPE = HandleType::ClientSession; + HandleType GetHandleType() const override { + return HANDLE_TYPE; + } + + /** + * Sends an SyncRequest from the current emulated thread. + * @return ResultCode of the operation. + */ + ResultCode SendSyncRequest(); + + std::string name; ///< Name of client port (optional) + ServerSession* server_session; ///< The server session associated with this client session. + SessionStatus session_status; ///< The session's current status. + +private: + ClientSession(); + ~ClientSession() override; + + /** + * Creates a client session. + * @param server_session The server session associated with this client session + * @param name Optional name of client session + * @return The created client session + */ + static ResultVal<SharedPtr<ClientSession>> Create(ServerSession* server_session, + std::string name = "Unknown"); +}; + +} // namespace 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 231cf7b75..1adcf6c71 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -31,22 +31,21 @@ enum KernelHandle : Handle { }; enum class HandleType : u32 { - Unknown = 0, - - Session = 2, - Event = 3, - Mutex = 4, - SharedMemory = 5, - Redirection = 6, - Thread = 7, - Process = 8, - AddressArbiter = 9, - Semaphore = 10, - Timer = 11, - ResourceLimit = 12, - CodeSet = 13, - ClientPort = 14, - ServerPort = 15, + Unknown, + Event, + Mutex, + SharedMemory, + Thread, + Process, + AddressArbiter, + Semaphore, + Timer, + ResourceLimit, + CodeSet, + ClientPort, + ServerPort, + ClientSession, + ServerSession, }; enum { @@ -82,23 +81,23 @@ public: */ bool IsWaitable() const { switch (GetHandleType()) { - case HandleType::Session: - case HandleType::ServerPort: case HandleType::Event: case HandleType::Mutex: case HandleType::Thread: case HandleType::Semaphore: case HandleType::Timer: + case HandleType::ServerPort: + case HandleType::ServerSession: return true; case HandleType::Unknown: case HandleType::SharedMemory: - case HandleType::Redirection: case HandleType::Process: case HandleType::AddressArbiter: case HandleType::ResourceLimit: case HandleType::CodeSet: case HandleType::ClientPort: + case HandleType::ClientSession: return false; } } @@ -152,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/server_port.cpp b/src/core/hle/kernel/server_port.cpp index 8e3ec8a14..6c19aa7c0 100644 --- a/src/core/hle/kernel/server_port.cpp +++ b/src/core/hle/kernel/server_port.cpp @@ -24,12 +24,14 @@ void ServerPort::Acquire() { } std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortPair( - u32 max_sessions, std::string name) { + u32 max_sessions, std::string name, + std::shared_ptr<Service::SessionRequestHandler> hle_handler) { SharedPtr<ServerPort> server_port(new ServerPort); SharedPtr<ClientPort> client_port(new ClientPort); server_port->name = name + "_Server"; + server_port->hle_handler = std::move(hle_handler); client_port->name = name + "_Client"; client_port->server_port = server_port; client_port->max_sessions = max_sessions; diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h index fa9448ca0..b0f8df62c 100644 --- a/src/core/hle/kernel/server_port.h +++ b/src/core/hle/kernel/server_port.h @@ -4,11 +4,16 @@ #pragma once +#include <memory> #include <string> #include <tuple> #include "common/common_types.h" #include "core/hle/kernel/kernel.h" +namespace Service { +class SessionRequestHandler; +} + namespace Kernel { class ClientPort; @@ -19,10 +24,13 @@ public: * Creates a pair of ServerPort and an associated ClientPort. * @param max_sessions Maximum number of sessions to the port * @param name Optional name of the ports + * @param hle_handler Optional HLE handler template for the port, + * ServerSessions crated from this port will inherit a reference to this handler. * @return The created port tuple */ static std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> CreatePortPair( - u32 max_sessions, std::string name = "UnknownPort"); + u32 max_sessions, std::string name = "UnknownPort", + std::shared_ptr<Service::SessionRequestHandler> hle_handler = nullptr); std::string GetTypeName() const override { return "ServerPort"; @@ -41,6 +49,10 @@ public: std::vector<SharedPtr<WaitObject>> pending_sessions; ///< ServerSessions waiting to be accepted by the port + /// This session's HLE request handler template (optional) + /// ServerSessions created from this port inherit a reference to this handler. + std::shared_ptr<Service::SessionRequestHandler> hle_handler; + bool ShouldWait() override; void Acquire() override; diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp new file mode 100644 index 000000000..146458c1c --- /dev/null +++ b/src/core/hle/kernel/server_session.cpp @@ -0,0 +1,79 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <tuple> + +#include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/server_session.h" +#include "core/hle/kernel/thread.h" + +namespace Kernel { + +ServerSession::ServerSession() = default; +ServerSession::~ServerSession() { + // This destructor will be called automatically when the last ServerSession handle is closed by + // the emulated application. + // TODO(Subv): Reduce the ClientPort's connection count, + // if the session is still open, set the connection status to 3 (Closed by server), +} + +ResultVal<SharedPtr<ServerSession>> ServerSession::Create( + std::string name, std::shared_ptr<Service::SessionRequestHandler> hle_handler) { + SharedPtr<ServerSession> server_session(new ServerSession); + + server_session->name = std::move(name); + server_session->signaled = false; + server_session->hle_handler = std::move(hle_handler); + + return MakeResult<SharedPtr<ServerSession>>(std::move(server_session)); +} + +bool ServerSession::ShouldWait() { + return !signaled; +} + +void ServerSession::Acquire() { + ASSERT_MSG(!ShouldWait(), "object unavailable!"); + signaled = false; +} + +ResultCode ServerSession::HandleSyncRequest() { + // The ServerSession received a sync request, this means that there's new data available + // from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or + // similar. + + // If this ServerSession has an associated HLE handler, forward the request to it. + if (hle_handler != nullptr) { + // Attempt to translate the incoming request's command buffer. + ResultCode result = TranslateHLERequest(this); + if (result.IsError()) + return result; + hle_handler->HandleSyncRequest(SharedPtr<ServerSession>(this)); + // TODO(Subv): Translate the response command buffer. + } + + // If this ServerSession does not have an HLE implementation, just wake up the threads waiting + // on it. + signaled = true; + WakeupAllWaitingThreads(); + return RESULT_SUCCESS; +} + +ServerSession::SessionPair ServerSession::CreateSessionPair( + const std::string& name, std::shared_ptr<Service::SessionRequestHandler> hle_handler) { + auto server_session = + ServerSession::Create(name + "_Server", std::move(hle_handler)).MoveFrom(); + // We keep a non-owning pointer to the ServerSession in the ClientSession because we don't want + // to prevent the ServerSession's destructor from being called when the emulated + // application closes the last ServerSession handle. + auto client_session = ClientSession::Create(server_session.get(), name + "_Client").MoveFrom(); + + return std::make_tuple(std::move(server_session), std::move(client_session)); +} + +ResultCode TranslateHLERequest(ServerSession* server_session) { + // TODO(Subv): Implement this function once multiple concurrent processes are supported. + return RESULT_SUCCESS; +} +} diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h new file mode 100644 index 000000000..458284a5d --- /dev/null +++ b/src/core/hle/kernel/server_session.h @@ -0,0 +1,94 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include "common/assert.h" +#include "common/common_types.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/thread.h" +#include "core/hle/result.h" +#include "core/hle/service/service.h" +#include "core/memory.h" + +namespace Kernel { + +class ClientSession; + +/** + * Kernel object representing the server endpoint of an IPC session. Sessions are the basic CTR-OS + * primitive for communication between different processes, and are used to implement service calls + * to the various system services. + * + * To make a service call, the client must write the command header and parameters to the buffer + * located at offset 0x80 of the TLS (Thread-Local Storage) area, then execute a SendSyncRequest + * SVC call with its ClientSession handle. The kernel will read the command header, using it to + * marshall the parameters to the process at the server endpoint of the session. + * After the server replies to the request, the response is marshalled back to the caller's + * TLS buffer and control is transferred back to it. + */ +class ServerSession final : public WaitObject { +public: + std::string GetTypeName() const override { + return "ServerSession"; + } + + static const HandleType HANDLE_TYPE = HandleType::ServerSession; + HandleType GetHandleType() const override { + return HANDLE_TYPE; + } + + using SessionPair = std::tuple<SharedPtr<ServerSession>, SharedPtr<ClientSession>>; + + /** + * Creates a pair of ServerSession and an associated ClientSession. + * @param name Optional name of the ports + * @return The created session tuple + */ + static SessionPair CreateSessionPair( + const std::string& name = "Unknown", + std::shared_ptr<Service::SessionRequestHandler> hle_handler = nullptr); + + /** + * Handle a sync request from the emulated application. + * @returns ResultCode from the operation. + */ + ResultCode HandleSyncRequest(); + + bool ShouldWait() override; + + void Acquire() override; + + std::string name; ///< The name of this session (optional) + bool signaled; ///< Whether there's new data available to this ServerSession + std::shared_ptr<Service::SessionRequestHandler> + hle_handler; ///< This session's HLE request handler (optional) + +private: + ServerSession(); + ~ServerSession() override; + + /** + * Creates a server session. The server session can have an optional HLE handler, + * which will be invoked to handle the IPC requests that this session receives. + * @param name Optional name of the server session. + * @param hle_handler Optional HLE handler for this server session. + * @return The created server session + */ + static ResultVal<SharedPtr<ServerSession>> Create( + std::string name = "Unknown", + std::shared_ptr<Service::SessionRequestHandler> hle_handler = nullptr); +}; + +/** + * Performs command buffer translation for an HLE IPC request. + * The command buffer from the ServerSession thread's TLS is copied into a + * buffer and all descriptors in the buffer are processed. + * TODO(Subv): Implement this function, currently we do not support multiple processes running at + * once, but once that is implemented we'll need to properly translate all descriptors + * in the command buffer. + */ +ResultCode TranslateHLERequest(ServerSession* server_session); +} 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/result.h b/src/core/hle/result.h index 8d29117a8..53864a3a7 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -18,6 +18,7 @@ enum class ErrorDescription : u32 { Success = 0, WrongPermission = 46, OS_InvalidBufferDescriptor = 48, + MaxConnectionsReached = 52, WrongAddress = 53, FS_ArchiveNotMounted = 101, FS_FileNotFound = 112, 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/apt/apt.h b/src/core/hle/service/apt/apt.h index e6a8be870..80325361f 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -14,6 +14,9 @@ class Interface; namespace APT { +/// Each APT service can only have up to 2 sessions connected at the same time. +static const u32 MaxAPTSessions = 2; + /// Holds information about the parameters used in Send/Glance/ReceiveParameter struct MessageParameter { u32 sender_id = 0; diff --git a/src/core/hle/service/apt/apt_a.cpp b/src/core/hle/service/apt/apt_a.cpp index 6e35e1d29..62dc2d61d 100644 --- a/src/core/hle/service/apt/apt_a.cpp +++ b/src/core/hle/service/apt/apt_a.cpp @@ -102,7 +102,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x01050100, nullptr, "IsTitleAllowed"}, }; -APT_A_Interface::APT_A_Interface() { +APT_A_Interface::APT_A_Interface() : Interface(MaxAPTSessions) { Register(FunctionTable); } diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp index 84019e6e5..effd23dce 100644 --- a/src/core/hle/service/apt/apt_s.cpp +++ b/src/core/hle/service/apt/apt_s.cpp @@ -102,7 +102,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x01050100, nullptr, "IsTitleAllowed"}, }; -APT_S_Interface::APT_S_Interface() { +APT_S_Interface::APT_S_Interface() : Interface(MaxAPTSessions) { Register(FunctionTable); } diff --git a/src/core/hle/service/apt/apt_u.cpp b/src/core/hle/service/apt/apt_u.cpp index a731c39f6..e06084a1e 100644 --- a/src/core/hle/service/apt/apt_u.cpp +++ b/src/core/hle/service/apt/apt_u.cpp @@ -99,7 +99,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x01020000, CheckNew3DS, "CheckNew3DS"}, }; -APT_U_Interface::APT_U_Interface() { +APT_U_Interface::APT_U_Interface() : Interface(MaxAPTSessions) { Register(FunctionTable); } 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 bef75f5df..09205e4b2 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -24,6 +24,7 @@ #include "core/file_sys/directory_backend.h" #include "core/file_sys/file_backend.h" #include "core/hle/hle.h" +#include "core/hle/kernel/client_session.h" #include "core/hle/result.h" #include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/fs_user.h" @@ -93,7 +94,7 @@ File::File(std::unique_ptr<FileSys::FileBackend>&& backend, const FileSys::Path& File::~File() {} -ResultVal<bool> File::SyncRequest() { +void File::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) { u32* cmd_buff = Kernel::GetCommandBuffer(); FileCommand cmd = static_cast<FileCommand>(cmd_buff[0]); switch (cmd) { @@ -103,8 +104,8 @@ ResultVal<bool> File::SyncRequest() { 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, @@ -116,7 +117,7 @@ ResultVal<bool> File::SyncRequest() { ResultVal<size_t> read = backend->Read(offset, data.size(), data.data()); if (read.Failed()) { cmd_buff[1] = read.Code().raw; - return read.Code(); + return; } Memory::WriteBlock(address, data.data(), *read); cmd_buff[2] = static_cast<u32>(*read); @@ -129,22 +130,22 @@ ResultVal<bool> File::SyncRequest() { 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()); ResultVal<size_t> written = backend->Write(offset, data.size(), flush != 0, data.data()); if (written.Failed()) { cmd_buff[1] = written.Code().raw; - return written.Code(); + return; } cmd_buff[2] = static_cast<u32>(*written); break; } 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; @@ -153,14 +154,13 @@ ResultVal<bool> File::SyncRequest() { 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; } @@ -173,7 +173,11 @@ ResultVal<bool> File::SyncRequest() { case FileCommand::OpenLinkFile: { LOG_WARNING(Service_FS, "(STUBBED) File command OpenLinkFile %s", GetName().c_str()); - cmd_buff[3] = Kernel::g_handle_table.Create(this).ValueOr(INVALID_HANDLE); + auto sessions = Kernel::ServerSession::CreateSessionPair(GetName(), shared_from_this()); + ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions)); + cmd_buff[3] = Kernel::g_handle_table + .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) + .ValueOr(INVALID_HANDLE); break; } @@ -194,10 +198,9 @@ ResultVal<bool> File::SyncRequest() { LOG_ERROR(Service_FS, "Unknown command=0x%08X!", cmd); ResultCode error = UnimplementedFunction(ErrorModule::FS); cmd_buff[1] = error.raw; // TODO(Link Mauve): use the correct error code for that. - return error; + return; } cmd_buff[1] = RESULT_SUCCESS.raw; // No error - return MakeResult<bool>(false); } Directory::Directory(std::unique_ptr<FileSys::DirectoryBackend>&& backend, @@ -206,18 +209,16 @@ Directory::Directory(std::unique_ptr<FileSys::DirectoryBackend>&& backend, Directory::~Directory() {} -ResultVal<bool> Directory::SyncRequest() { +void Directory::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) { u32* cmd_buff = Kernel::GetCommandBuffer(); DirectoryCommand cmd = static_cast<DirectoryCommand>(cmd_buff[0]); switch (cmd) { - // Read from directory... case DirectoryCommand::Read: { 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()); @@ -227,7 +228,7 @@ ResultVal<bool> Directory::SyncRequest() { } 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; } @@ -237,10 +238,9 @@ ResultVal<bool> Directory::SyncRequest() { LOG_ERROR(Service_FS, "Unknown command=0x%08X!", cmd); ResultCode error = UnimplementedFunction(ErrorModule::FS); cmd_buff[1] = error.raw; // TODO(Link Mauve): use the correct error code for that. - return MakeResult<bool>(false); + return; } cmd_buff[1] = RESULT_SUCCESS.raw; // No error - return MakeResult<bool>(false); } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -307,9 +307,9 @@ ResultCode RegisterArchiveType(std::unique_ptr<FileSys::ArchiveFactory>&& factor return RESULT_SUCCESS; } -ResultVal<Kernel::SharedPtr<File>> OpenFileFromArchive(ArchiveHandle archive_handle, - const FileSys::Path& path, - const FileSys::Mode mode) { +ResultVal<std::shared_ptr<File>> OpenFileFromArchive(ArchiveHandle archive_handle, + const FileSys::Path& path, + const FileSys::Mode mode) { ArchiveBackend* archive = GetArchive(archive_handle); if (archive == nullptr) return ERR_INVALID_ARCHIVE_HANDLE; @@ -318,8 +318,8 @@ ResultVal<Kernel::SharedPtr<File>> OpenFileFromArchive(ArchiveHandle archive_han if (backend.Failed()) return backend.Code(); - auto file = Kernel::SharedPtr<File>(new File(backend.MoveFrom(), path)); - return MakeResult<Kernel::SharedPtr<File>>(std::move(file)); + auto file = std::shared_ptr<File>(new File(backend.MoveFrom(), path)); + return MakeResult<std::shared_ptr<File>>(std::move(file)); } ResultCode DeleteFileFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) { @@ -398,8 +398,8 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, } } -ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle, - const FileSys::Path& path) { +ResultVal<std::shared_ptr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle, + const FileSys::Path& path) { ArchiveBackend* archive = GetArchive(archive_handle); if (archive == nullptr) return ERR_INVALID_ARCHIVE_HANDLE; @@ -408,8 +408,8 @@ ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle a if (backend.Failed()) return backend.Code(); - auto directory = Kernel::SharedPtr<Directory>(new Directory(backend.MoveFrom(), path)); - return MakeResult<Kernel::SharedPtr<Directory>>(std::move(directory)); + auto directory = std::shared_ptr<Directory>(new Directory(backend.MoveFrom(), path)); + return MakeResult<std::shared_ptr<Directory>>(std::move(directory)); } ResultVal<u64> GetFreeBytesInArchive(ArchiveHandle archive_handle) { diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index 87089bd92..7ba62ede0 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -8,7 +8,7 @@ #include <string> #include "common/common_types.h" #include "core/file_sys/archive_backend.h" -#include "core/hle/kernel/session.h" +#include "core/hle/kernel/server_session.h" #include "core/hle/result.h" namespace FileSys { @@ -43,33 +43,37 @@ enum class MediaType : u32 { NAND = 0, SDMC = 1, GameCard = 2 }; typedef u64 ArchiveHandle; -class File : public Kernel::Session { +class File final : public SessionRequestHandler, public std::enable_shared_from_this<File> { public: File(std::unique_ptr<FileSys::FileBackend>&& backend, const FileSys::Path& path); ~File(); - std::string GetName() const override { + std::string GetName() const { return "Path: " + path.DebugStr(); } - ResultVal<bool> SyncRequest() override; FileSys::Path path; ///< Path of the file u32 priority; ///< Priority of the file. TODO(Subv): Find out what this means std::unique_ptr<FileSys::FileBackend> backend; ///< File backend interface + +protected: + void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) override; }; -class Directory : public Kernel::Session { +class Directory final : public SessionRequestHandler { public: Directory(std::unique_ptr<FileSys::DirectoryBackend>&& backend, const FileSys::Path& path); ~Directory(); - std::string GetName() const override { + std::string GetName() const { return "Directory: " + path.DebugStr(); } - ResultVal<bool> SyncRequest() override; FileSys::Path path; ///< Path of the directory std::unique_ptr<FileSys::DirectoryBackend> backend; ///< File backend interface + +protected: + void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) override; }; /** @@ -99,11 +103,11 @@ ResultCode RegisterArchiveType(std::unique_ptr<FileSys::ArchiveFactory>&& factor * @param archive_handle Handle to an open Archive object * @param path Path to the File inside of the Archive * @param mode Mode under which to open the File - * @return The opened File object as a Session + * @return The opened File object */ -ResultVal<Kernel::SharedPtr<File>> OpenFileFromArchive(ArchiveHandle archive_handle, - const FileSys::Path& path, - const FileSys::Mode mode); +ResultVal<std::shared_ptr<File>> OpenFileFromArchive(ArchiveHandle archive_handle, + const FileSys::Path& path, + const FileSys::Mode mode); /** * Delete a File from an Archive @@ -178,10 +182,10 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, * Open a Directory from an Archive * @param archive_handle Handle to an open Archive object * @param path Path to the Directory inside of the Archive - * @return The opened Directory object as a Session + * @return The opened Directory object */ -ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle, - const FileSys::Path& path); +ResultVal<std::shared_ptr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle, + const FileSys::Path& path); /** * Get the free space in an Archive diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index d6ab5b065..337da1387 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -8,6 +8,7 @@ #include "common/logging/log.h" #include "common/scope_exit.h" #include "common/string_util.h" +#include "core/hle/kernel/client_session.h" #include "core/hle/result.h" #include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/fs_user.h" @@ -17,7 +18,7 @@ // Namespace FS_User using Kernel::SharedPtr; -using Kernel::Session; +using Kernel::ServerSession; namespace Service { namespace FS { @@ -67,10 +68,16 @@ static void OpenFile(Service::Interface* self) { LOG_DEBUG(Service_FS, "path=%s, mode=%u attrs=%u", file_path.DebugStr().c_str(), mode.hex, attributes); - ResultVal<SharedPtr<File>> file_res = OpenFileFromArchive(archive_handle, file_path, mode); + ResultVal<std::shared_ptr<File>> file_res = + OpenFileFromArchive(archive_handle, file_path, mode); cmd_buff[1] = file_res.Code().raw; if (file_res.Succeeded()) { - cmd_buff[3] = Kernel::g_handle_table.Create(*file_res).MoveFrom(); + std::shared_ptr<File> file = *file_res; + auto sessions = ServerSession::CreateSessionPair(file->GetName(), file); + file->ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions)); + cmd_buff[3] = Kernel::g_handle_table + .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) + .MoveFrom(); } else { cmd_buff[3] = 0; LOG_ERROR(Service_FS, "failed to get a handle for file %s", file_path.DebugStr().c_str()); @@ -127,10 +134,16 @@ static void OpenFileDirectly(Service::Interface* self) { } SCOPE_EXIT({ CloseArchive(*archive_handle); }); - ResultVal<SharedPtr<File>> file_res = OpenFileFromArchive(*archive_handle, file_path, mode); + ResultVal<std::shared_ptr<File>> file_res = + OpenFileFromArchive(*archive_handle, file_path, mode); cmd_buff[1] = file_res.Code().raw; if (file_res.Succeeded()) { - cmd_buff[3] = Kernel::g_handle_table.Create(*file_res).MoveFrom(); + std::shared_ptr<File> file = *file_res; + auto sessions = ServerSession::CreateSessionPair(file->GetName(), file); + file->ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions)); + cmd_buff[3] = Kernel::g_handle_table + .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) + .MoveFrom(); } else { cmd_buff[3] = 0; LOG_ERROR(Service_FS, "failed to get a handle for file %s mode=%u attributes=%u", @@ -388,10 +401,16 @@ static void OpenDirectory(Service::Interface* self) { LOG_DEBUG(Service_FS, "type=%u size=%u data=%s", static_cast<u32>(dirname_type), dirname_size, dir_path.DebugStr().c_str()); - ResultVal<SharedPtr<Directory>> dir_res = OpenDirectoryFromArchive(archive_handle, dir_path); + ResultVal<std::shared_ptr<Directory>> dir_res = + OpenDirectoryFromArchive(archive_handle, dir_path); cmd_buff[1] = dir_res.Code().raw; if (dir_res.Succeeded()) { - cmd_buff[3] = Kernel::g_handle_table.Create(*dir_res).MoveFrom(); + std::shared_ptr<Directory> directory = *dir_res; + auto sessions = ServerSession::CreateSessionPair(directory->GetName(), directory); + directory->ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions)); + cmd_buff[3] = Kernel::g_handle_table + .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) + .MoveFrom(); } else { LOG_ERROR(Service_FS, "failed to get a handle for directory type=%d size=%d data=%s", dirname_type, dirname_size, dir_path.DebugStr().c_str()); diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index 49f4836b4..947958703 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -636,6 +636,7 @@ static void TriggerCmdReqQueue(Interface* self) { * Inputs: * 0: Header 0x00180000 * Outputs: + * 0: Header Code[0x00180240] * 1: Result code * 2: Left framebuffer virtual address for the main screen * 3: Right framebuffer virtual address for the main screen @@ -658,18 +659,19 @@ static void ImportDisplayCaptureInfo(Interface* self) { FrameBufferUpdate* top_screen = GetFrameBufferInfo(thread_id, 0); FrameBufferUpdate* bottom_screen = GetFrameBufferInfo(thread_id, 1); + cmd_buff[0] = IPC::MakeHeader(0x18, 0x9, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + // Top Screen cmd_buff[2] = top_screen->framebuffer_info[top_screen->index].address_left; cmd_buff[3] = top_screen->framebuffer_info[top_screen->index].address_right; cmd_buff[4] = top_screen->framebuffer_info[top_screen->index].format; cmd_buff[5] = top_screen->framebuffer_info[top_screen->index].stride; - + // Bottom Screen cmd_buff[6] = bottom_screen->framebuffer_info[bottom_screen->index].address_left; cmd_buff[7] = bottom_screen->framebuffer_info[bottom_screen->index].address_right; cmd_buff[8] = bottom_screen->framebuffer_info[bottom_screen->index].format; cmd_buff[9] = bottom_screen->framebuffer_info[bottom_screen->index].stride; - cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_GSP, "called"); } 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 effecc043..25a7aeea8 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -2,11 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <boost/range/algorithm_ext/erase.hpp> + #include "common/logging/log.h" #include "common/string_util.h" + +#include "core/hle/kernel/server_port.h" #include "core/hle/service/ac_u.h" -#include "core/hle/service/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" @@ -44,8 +47,8 @@ namespace Service { -std::unordered_map<std::string, Kernel::SharedPtr<Interface>> g_kernel_named_ports; -std::unordered_map<std::string, Kernel::SharedPtr<Interface>> g_srv_services; +std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_kernel_named_ports; +std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_srv_services; /** * Creates a function string for logging, complete with the name (or header code, depending @@ -64,7 +67,23 @@ static std::string MakeFunctionString(const char* name, const char* port_name, return function_string; } -ResultVal<bool> Interface::SyncRequest() { +void SessionRequestHandler::ClientConnected( + Kernel::SharedPtr<Kernel::ServerSession> server_session) { + connected_sessions.push_back(server_session); +} + +void SessionRequestHandler::ClientDisconnected( + Kernel::SharedPtr<Kernel::ServerSession> server_session) { + boost::range::remove_erase(connected_sessions, server_session); +} + +Interface::Interface(u32 max_sessions) : max_sessions(max_sessions) {} +Interface::~Interface() = default; + +void Interface::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) { + // TODO(Subv): Make use of the server_session in the HLE service handlers to distinguish which + // session triggered each command. + u32* cmd_buff = Kernel::GetCommandBuffer(); auto itr = m_functions.find(cmd_buff[0]); @@ -78,14 +97,12 @@ ResultVal<bool> Interface::SyncRequest() { // TODO(bunnei): Hack - ignore error cmd_buff[1] = 0; - return MakeResult<bool>(false); + return; } LOG_TRACE(Service, "%s", MakeFunctionString(itr->second.name, GetPortName().c_str(), cmd_buff).c_str()); itr->second.func(this); - - return MakeResult<bool>(false); // TODO: Implement return from actual function } void Interface::Register(const FunctionInfo* functions, size_t n) { @@ -100,11 +117,19 @@ void Interface::Register(const FunctionInfo* functions, size_t n) { // Module interface static void AddNamedPort(Interface* interface_) { - g_kernel_named_ports.emplace(interface_->GetPortName(), interface_); + auto ports = + Kernel::ServerPort::CreatePortPair(interface_->GetMaxSessions(), interface_->GetPortName(), + std::shared_ptr<Interface>(interface_)); + auto client_port = std::get<Kernel::SharedPtr<Kernel::ClientPort>>(ports); + g_kernel_named_ports.emplace(interface_->GetPortName(), std::move(client_port)); } void AddService(Interface* interface_) { - g_srv_services.emplace(interface_->GetPortName(), interface_); + auto ports = + Kernel::ServerPort::CreatePortPair(interface_->GetMaxSessions(), interface_->GetPortName(), + std::shared_ptr<Interface>(interface_)); + auto client_port = std::get<Kernel::SharedPtr<Kernel::ClientPort>>(ports); + g_srv_services.emplace(interface_->GetPortName(), std::move(client_port)); } /// Initialize ServiceManager @@ -113,6 +138,7 @@ void Init() { AddNamedPort(new ERR::ERR_F); FS::ArchiveInit(); + ACT::Init(); AM::Init(); APT::Init(); BOSS::Init(); @@ -132,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/service.h b/src/core/hle/service/service.h index 29daacfc4..a7ba7688f 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -9,8 +9,15 @@ #include <unordered_map> #include <boost/container/flat_map.hpp> #include "common/common_types.h" -#include "core/hle/kernel/session.h" +#include "core/hle/ipc.h" +#include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/thread.h" #include "core/hle/result.h" +#include "core/memory.h" + +namespace Kernel { +class ServerSession; +} //////////////////////////////////////////////////////////////////////////////////////////////////// // Namespace Service @@ -18,14 +25,63 @@ namespace Service { static const int kMaxPortSize = 8; ///< Maximum size of a port name (8 characters) +/// Arbitrary default number of maximum connections to an HLE service. +static const u32 DefaultMaxSessions = 10; + +/** + * Interface implemented by HLE Session handlers. + * This can be provided to a ServerSession in order to hook into several relevant events + * (such as a new connection or a SyncRequest) so they can be implemented in the emulator. + */ +class SessionRequestHandler { +public: + /** + * Handles a sync request from the emulated application. + * @param server_session The ServerSession that was triggered for this sync request, + * it should be used to differentiate which client (As in ClientSession) we're answering to. + * TODO(Subv): Use a wrapper structure to hold all the information relevant to + * this request (ServerSession, Originator thread, Translated command buffer, etc). + * @returns ResultCode the result code of the translate operation. + */ + virtual void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) = 0; + + /** + * Signals that a client has just connected to this HLE handler and keeps the + * associated ServerSession alive for the duration of the connection. + * @param server_session Owning pointer to the ServerSession associated with the connection. + */ + void ClientConnected(Kernel::SharedPtr<Kernel::ServerSession> server_session); + + /** + * Signals that a client has just disconnected from this HLE handler and releases the + * associated ServerSession. + * @param server_session ServerSession associated with the connection. + */ + void ClientDisconnected(Kernel::SharedPtr<Kernel::ServerSession> server_session); -/// Interface to a CTROS service -class Interface : public Kernel::Session { - // TODO(yuriks): An "Interface" being a Kernel::Object is mostly non-sense. Interface should be - // just something that encapsulates a session and acts as a helper to implement service - // processes. +protected: + /// List of sessions that are connected to this handler. + /// A ServerSession whose server endpoint is an HLE implementation is kept alive by this list + // for the duration of the connection. + std::vector<Kernel::SharedPtr<Kernel::ServerSession>> connected_sessions; +}; + +/** + * Framework for implementing HLE service handlers which dispatch incoming SyncRequests based on a + * table mapping header ids to handler functions. + */ +class Interface : public SessionRequestHandler { public: - std::string GetName() const override { + /** + * Creates an HLE interface with the specified max sessions. + * @param max_sessions Maximum number of sessions that can be + * connected to this service at the same time. + */ + Interface(u32 max_sessions = DefaultMaxSessions); + + virtual ~Interface(); + + std::string GetName() const { return GetPortName(); } @@ -33,6 +89,15 @@ public: version.raw = raw_version; } + /** + * Gets the maximum allowed number of sessions that can be connected to this service + * at the same time. + * @returns The maximum number of connections allowed. + */ + u32 GetMaxSessions() const { + return max_sessions; + } + typedef void (*Function)(Interface*); struct FunctionInfo { @@ -49,9 +114,9 @@ public: return "[UNKNOWN SERVICE PORT]"; } - ResultVal<bool> SyncRequest() override; - protected: + void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) override; + /** * Registers the functions in the service */ @@ -71,6 +136,7 @@ protected: } version = {}; private: + u32 max_sessions; ///< Maximum number of concurrent sessions that this service can handle. boost::container::flat_map<u32, FunctionInfo> m_functions; }; @@ -81,9 +147,9 @@ void Init(); void Shutdown(); /// Map of named ports managed by the kernel, which can be retrieved using the ConnectToPort SVC. -extern std::unordered_map<std::string, Kernel::SharedPtr<Interface>> g_kernel_named_ports; +extern std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_kernel_named_ports; /// Map of services registered with the "srv:" service, retrieved using GetServiceHandle. -extern std::unordered_map<std::string, Kernel::SharedPtr<Interface>> g_srv_services; +extern std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_srv_services; /// Adds a service to the services table void AddService(Interface* interface_); diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp index fd251fc0a..c3918cdd0 100644 --- a/src/core/hle/service/soc_u.cpp +++ b/src/core/hle/service/soc_u.cpp @@ -11,7 +11,7 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "common/scope_exit.h" -#include "core/hle/kernel/session.h" +#include "core/hle/kernel/server_session.h" #include "core/hle/result.h" #include "core/hle/service/soc_u.h" #include "core/memory.h" diff --git a/src/core/hle/service/srv.cpp b/src/core/hle/service/srv.cpp index f8df38c42..3bd787147 100644 --- a/src/core/hle/service/srv.cpp +++ b/src/core/hle/service/srv.cpp @@ -2,9 +2,13 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <tuple> + #include "common/common_types.h" #include "common/logging/log.h" +#include "core/hle/kernel/client_session.h" #include "core/hle/kernel/event.h" +#include "core/hle/kernel/server_session.h" #include "core/hle/service/srv.h" namespace Service { @@ -79,7 +83,15 @@ static void GetServiceHandle(Interface* self) { auto it = Service::g_srv_services.find(port_name); if (it != Service::g_srv_services.end()) { - cmd_buff[3] = Kernel::g_handle_table.Create(it->second).MoveFrom(); + auto client_port = it->second; + + auto client_session = client_port->Connect(); + res = client_session.Code(); + + if (client_session.Succeeded()) { + // Return the client session + cmd_buff[3] = Kernel::g_handle_table.Create(*client_session).MoveFrom(); + } LOG_TRACE(Service_SRV, "called port=%s, handle=0x%08X", port_name.c_str(), cmd_buff[3]); } else { LOG_ERROR(Service_SRV, "(UNIMPLEMENTED) called port=%s", port_name.c_str()); 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 c6b80dc50..5839d7230 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -13,6 +13,7 @@ #include "core/hle/function_wrappers.h" #include "core/hle/kernel/address_arbiter.h" #include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/client_session.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/memory.h" #include "core/hle/kernel/mutex.h" @@ -20,6 +21,7 @@ #include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/semaphore.h" #include "core/hle/kernel/server_port.h" +#include "core/hle/kernel/server_session.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/timer.h" @@ -41,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}; @@ -222,20 +227,29 @@ static ResultCode ConnectToPort(Handle* out_handle, const char* port_name) { return ERR_NOT_FOUND; } - CASCADE_RESULT(*out_handle, Kernel::g_handle_table.Create(it->second)); + auto client_port = it->second; + + SharedPtr<Kernel::ClientSession> client_session; + CASCADE_RESULT(client_session, client_port->Connect()); + + // Return the client session + CASCADE_RESULT(*out_handle, Kernel::g_handle_table.Create(client_session)); return RESULT_SUCCESS; } -/// Synchronize to an OS service +/// Makes a blocking IPC call to an OS service. static ResultCode SendSyncRequest(Handle handle) { - SharedPtr<Kernel::Session> session = Kernel::g_handle_table.Get<Kernel::Session>(handle); + SharedPtr<Kernel::ClientSession> session = + Kernel::g_handle_table.Get<Kernel::ClientSession>(handle); if (session == nullptr) { return ERR_INVALID_HANDLE; } LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s)", handle, session->GetName().c_str()); - return session->SyncRequest().Code(); + // TODO(Subv): svcSendSyncRequest should put the caller thread to sleep while the server + // responds and cause a reschedule. + return session->SendSyncRequest(); } /// Close a handle @@ -249,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(); @@ -280,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) @@ -300,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) @@ -548,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 @@ -1148,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 581a37897..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 @@ -59,6 +60,9 @@ create_directory_groups(${SRCS} ${HEADERS}) add_library(video_core STATIC ${SRCS} ${HEADERS}) target_link_libraries(video_core glad) +if (ARCHITECTURE_x86_64) + target_link_libraries(video_core xbyak) +endif() if (PNG_FOUND) target_link_libraries(video_core ${PNG_LIBRARIES}) diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 0495a9fac..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, @@ -136,9 +139,10 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { immediate_input.attr[immediate_attribute_id++] = attribute; if (immediate_attribute_id >= regs.vs.num_input_attributes + 1) { + 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 @@ -165,6 +169,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { case PICA_REG_INDEX(gpu_mode): if (regs.gpu_mode == Regs::GPUMode::Configuring) { + MICROPROFILE_SCOPE(GPU_Drawing); + // Draw immediate mode triangles when GPU Mode is set to GPUMode::Configuring VideoCore::g_renderer->Rasterizer()->DrawTriangles(); @@ -237,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) { @@ -251,7 +257,6 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { ASSERT(vertex != -1); bool vertex_cache_hit = false; - Shader::OutputRegisters output_registers; if (is_indexed) { if (g_debug_context && Pica::g_debug_context->recorder) { @@ -279,10 +284,9 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, (void*)&input); g_state.vs.Run(shader_unit, input, loader.GetNumTotalAttributes()); - output_registers = shader_unit.output_registers; // Retrieve vertex from register data - output_vertex = output_registers.ToVertex(regs.vs); + output_vertex = shader_unit.output_registers.ToVertex(regs.vs); if (is_indexed) { vertex_cache[vertex_cache_pos] = output_vertex; diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index 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 cc7e782a4..5a306a5c8 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -11,6 +11,7 @@ #include "common/color.h" #include "common/logging/log.h" #include "common/math_util.h" +#include "common/microprofile.h" #include "common/vector_math.h" #include "core/hw/gpu.h" #include "video_core/pica.h" @@ -21,6 +22,10 @@ #include "video_core/renderer_opengl/pica_to_gl.h" #include "video_core/renderer_opengl/renderer_opengl.h" +MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192)); +MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(100, 100, 255)); +MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100)); + static bool IsPassThroughTevStage(const Pica::Regs::TevStageConfig& stage) { return (stage.color_op == Pica::Regs::TevStageConfig::Operation::Replace && stage.alpha_op == Pica::Regs::TevStageConfig::Operation::Replace && @@ -168,6 +173,7 @@ void RasterizerOpenGL::DrawTriangles() { if (vertex_batch.empty()) return; + MICROPROFILE_SCOPE(OpenGL_Drawing); const auto& regs = Pica::g_state.regs; // Sync and bind the framebuffer surfaces @@ -694,18 +700,22 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { } void RasterizerOpenGL::FlushAll() { + MICROPROFILE_SCOPE(OpenGL_CacheManagement); res_cache.FlushAll(); } void RasterizerOpenGL::FlushRegion(PAddr addr, u32 size) { + MICROPROFILE_SCOPE(OpenGL_CacheManagement); res_cache.FlushRegion(addr, size, nullptr, false); } void RasterizerOpenGL::FlushAndInvalidateRegion(PAddr addr, u32 size) { + MICROPROFILE_SCOPE(OpenGL_CacheManagement); res_cache.FlushRegion(addr, size, nullptr, true); } bool RasterizerOpenGL::AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) { + MICROPROFILE_SCOPE(OpenGL_Blits); using PixelFormat = CachedSurface::PixelFormat; using SurfaceType = CachedSurface::SurfaceType; @@ -778,6 +788,7 @@ bool RasterizerOpenGL::AccelerateTextureCopy(const GPU::Regs::DisplayTransferCon } bool RasterizerOpenGL::AccelerateFill(const GPU::Regs::MemoryFillConfig& config) { + MICROPROFILE_SCOPE(OpenGL_Blits); using PixelFormat = CachedSurface::PixelFormat; using SurfaceType = CachedSurface::SurfaceType; @@ -875,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) { @@ -926,6 +937,7 @@ bool RasterizerOpenGL::AccelerateDisplay(const GPU::Regs::FramebufferConfig& con if (framebuffer_addr == 0) { return false; } + MICROPROFILE_SCOPE(OpenGL_CacheManagement); CachedSurface src_params; src_params.addr = framebuffer_addr; 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 3febe739c..a4aa3c9e0 100644 --- a/src/video_core/shader/shader.cpp +++ b/src/video_core/shader/shader.cpp @@ -25,7 +25,7 @@ namespace Pica { namespace Shader { -OutputVertex OutputRegisters::ToVertex(const Regs::ShaderConfig& config) { +OutputVertex OutputRegisters::ToVertex(const Regs::ShaderConfig& config) const { // Setup output data OutputVertex ret; // TODO(neobrain): Under some circumstances, up to 16 attributes may be output. We need to @@ -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 8858d67f8..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; @@ -85,187 +84,16 @@ struct OutputRegisters { alignas(16) Math::Vec4<float24> value[16]; - OutputVertex ToVertex(const Regs::ShaderConfig& config); + OutputVertex ToVertex(const Regs::ShaderConfig& config) const; }; 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 c96110bb2..c588b778b 100644 --- a/src/video_core/shader/shader_jit_x64.cpp +++ b/src/video_core/shader/shader_jit_x64.cpp @@ -6,24 +6,30 @@ #include <cmath> #include <cstdint> #include <nihstro/shader_bytecode.h> +#include <smmintrin.h> #include <xmmintrin.h> #include "common/assert.h" #include "common/logging/log.h" #include "common/vector_math.h" -#include "common/x64/abi.h" #include "common/x64/cpu_detect.h" -#include "common/x64/emitter.h" -#include "shader.h" -#include "shader_jit_x64.h" +#include "common/x64/xbyak_abi.h" +#include "common/x64/xbyak_util.h" #include "video_core/pica_state.h" #include "video_core/pica_types.h" +#include "video_core/shader/shader.h" +#include "video_core/shader/shader_jit_x64.h" + +using namespace Common::X64; +using namespace Xbyak::util; +using Xbyak::Label; +using Xbyak::Reg32; +using Xbyak::Reg64; +using Xbyak::Xmm; namespace Pica { namespace Shader { -using namespace Gen; - typedef void (JitShader::*JitFunction)(Instruction instr); const JitFunction instr_table[64] = { @@ -98,44 +104,47 @@ const JitFunction instr_table[64] = { // purposes, as documented below: /// Pointer to the uniform memory -static const X64Reg SETUP = R9; +static const Reg64 SETUP = r9; /// The two 32-bit VS address offset registers set by the MOVA instruction -static const X64Reg ADDROFFS_REG_0 = R10; -static const X64Reg ADDROFFS_REG_1 = R11; +static const Reg64 ADDROFFS_REG_0 = r10; +static const Reg64 ADDROFFS_REG_1 = r11; /// VS loop count register (Multiplied by 16) -static const X64Reg LOOPCOUNT_REG = R12; +static const Reg32 LOOPCOUNT_REG = r12d; /// Current VS loop iteration number (we could probably use LOOPCOUNT_REG, but this quicker) -static const X64Reg LOOPCOUNT = RSI; +static const Reg32 LOOPCOUNT = esi; /// Number to increment LOOPCOUNT_REG by on each loop iteration (Multiplied by 16) -static const X64Reg LOOPINC = RDI; +static const Reg32 LOOPINC = edi; /// Result of the previous CMP instruction for the X-component comparison -static const X64Reg COND0 = R13; +static const Reg64 COND0 = r13; /// Result of the previous CMP instruction for the Y-component comparison -static const X64Reg COND1 = R14; +static const Reg64 COND1 = r14; /// Pointer to the UnitState instance for the current VS unit -static const X64Reg STATE = R15; +static const Reg64 STATE = r15; /// SIMD scratch register -static const X64Reg SCRATCH = XMM0; +static const Xmm SCRATCH = xmm0; /// Loaded with the first swizzled source register, otherwise can be used as a scratch register -static const X64Reg SRC1 = XMM1; +static const Xmm SRC1 = xmm1; /// Loaded with the second swizzled source register, otherwise can be used as a scratch register -static const X64Reg SRC2 = XMM2; +static const Xmm SRC2 = xmm2; /// Loaded with the third swizzled source register, otherwise can be used as a scratch register -static const X64Reg SRC3 = XMM3; +static const Xmm SRC3 = xmm3; /// Additional scratch register -static const X64Reg SCRATCH2 = XMM4; +static const Xmm SCRATCH2 = xmm4; /// Constant vector of [1.0f, 1.0f, 1.0f, 1.0f], used to efficiently set a vector to one -static const X64Reg ONE = XMM14; +static const Xmm ONE = xmm14; /// Constant vector of [-0.f, -0.f, -0.f, -0.f], used to efficiently negate a vector with XOR -static const X64Reg NEGBIT = XMM15; +static const Xmm NEGBIT = xmm15; // State registers that must not be modified by external functions calls // Scratch registers, e.g., SRC1 and SCRATCH, have to be saved on the side if needed -static const BitSet32 persistent_regs = { - SETUP, STATE, // Pointers to register blocks - ADDROFFS_REG_0, ADDROFFS_REG_1, LOOPCOUNT_REG, COND0, COND1, // Cached registers - ONE + 16, NEGBIT + 16, // Constants -}; +static const BitSet32 persistent_regs = BuildRegSet({ + // Pointers to register blocks + SETUP, STATE, + // Cached registers + ADDROFFS_REG_0, ADDROFFS_REG_1, LOOPCOUNT_REG, COND0, COND1, + // Constants + ONE, NEGBIT, +}); /// Raw constant for the source register selector that indicates no swizzling is performed static const u8 NO_SRC_REG_SWIZZLE = 0x1b; @@ -157,7 +166,8 @@ static void LogCritical(const char* msg) { void JitShader::Compile_Assert(bool condition, const char* msg) { if (!condition) { - ABI_CallFunctionP(reinterpret_cast<const void*>(LogCritical), const_cast<char*>(msg)); + mov(ABI_PARAM1, reinterpret_cast<size_t>(msg)); + CallFarFunction(*this, LogCritical); } } @@ -169,16 +179,16 @@ void JitShader::Compile_Assert(bool condition, const char* msg) { * @param dest Destination XMM register to store the loaded, swizzled source register */ void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, - X64Reg dest) { - X64Reg src_ptr; + Xmm dest) { + Reg64 src_ptr; size_t src_offset; 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; @@ -206,13 +216,13 @@ void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRe if (src_num == offset_src && address_register_index != 0) { switch (address_register_index) { case 1: // address offset 1 - MOVAPS(dest, MComplex(src_ptr, ADDROFFS_REG_0, SCALE_1, src_offset_disp)); + movaps(dest, xword[src_ptr + ADDROFFS_REG_0 + src_offset_disp]); break; case 2: // address offset 2 - MOVAPS(dest, MComplex(src_ptr, ADDROFFS_REG_1, SCALE_1, src_offset_disp)); + movaps(dest, xword[src_ptr + ADDROFFS_REG_1 + src_offset_disp]); break; case 3: // address offset 3 - MOVAPS(dest, MComplex(src_ptr, LOOPCOUNT_REG, SCALE_1, src_offset_disp)); + movaps(dest, xword[src_ptr + LOOPCOUNT_REG.cvt64() + src_offset_disp]); break; default: UNREACHABLE(); @@ -220,7 +230,7 @@ void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRe } } else { // Load the source - MOVAPS(dest, MDisp(src_ptr, src_offset_disp)); + movaps(dest, xword[src_ptr + src_offset_disp]); } SwizzlePattern swiz = {g_state.vs.swizzle_data[operand_desc_id]}; @@ -232,17 +242,17 @@ void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRe sel = ((sel & 0xc0) >> 6) | ((sel & 3) << 6) | ((sel & 0xc) << 2) | ((sel & 0x30) >> 2); // Shuffle inputs for swizzle - SHUFPS(dest, R(dest), sel); + shufps(dest, dest, sel); } // If the source register should be negated, flip the negative bit using XOR const bool negate[] = {swiz.negate_src1, swiz.negate_src2, swiz.negate_src3}; if (negate[src_num - 1]) { - XORPS(dest, R(NEGBIT)); + xorps(dest, NEGBIT); } } -void JitShader::Compile_DestEnable(Instruction instr, X64Reg src) { +void JitShader::Compile_DestEnable(Instruction instr, Xmm src) { DestRegister dest; unsigned operand_desc_id; if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD || @@ -256,28 +266,26 @@ void JitShader::Compile_DestEnable(Instruction instr, X64Reg 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) { // Store dest back to memory - MOVAPS(MDisp(STATE, dest_offset_disp), src); + movaps(xword[STATE + dest_offset_disp], src); } else { // Not all components are enabled, so mask the result when storing to the destination // register... - MOVAPS(SCRATCH, MDisp(STATE, dest_offset_disp)); + movaps(SCRATCH, xword[STATE + dest_offset_disp]); if (Common::GetCPUCaps().sse4_1) { u8 mask = ((swiz.dest_mask & 1) << 3) | ((swiz.dest_mask & 8) >> 3) | ((swiz.dest_mask & 2) << 1) | ((swiz.dest_mask & 4) >> 1); - BLENDPS(SCRATCH, R(src), mask); + blendps(SCRATCH, src, mask); } else { - MOVAPS(SCRATCH2, R(src)); - UNPCKHPS(SCRATCH2, R(SCRATCH)); // Unpack X/Y components of source and destination - UNPCKLPS(SCRATCH, R(src)); // Unpack Z/W components of source and destination + movaps(SCRATCH2, src); + unpckhps(SCRATCH2, SCRATCH); // Unpack X/Y components of source and destination + unpcklps(SCRATCH, src); // Unpack Z/W components of source and destination // Compute selector to selectively copy source components to destination for SHUFPS // instruction @@ -285,62 +293,61 @@ void JitShader::Compile_DestEnable(Instruction instr, X64Reg src) { ((swiz.DestComponentEnabled(1) ? 3 : 2) << 2) | ((swiz.DestComponentEnabled(2) ? 0 : 1) << 4) | ((swiz.DestComponentEnabled(3) ? 2 : 3) << 6); - SHUFPS(SCRATCH, R(SCRATCH2), sel); + shufps(SCRATCH, SCRATCH2, sel); } // Store dest back to memory - MOVAPS(MDisp(STATE, dest_offset_disp), SCRATCH); + movaps(xword[STATE + dest_offset_disp], SCRATCH); } } -void JitShader::Compile_SanitizedMul(Gen::X64Reg src1, Gen::X64Reg src2, Gen::X64Reg scratch) { - MOVAPS(scratch, R(src1)); - CMPPS(scratch, R(src2), CMP_ORD); +void JitShader::Compile_SanitizedMul(Xmm src1, Xmm src2, Xmm scratch) { + movaps(scratch, src1); + cmpordps(scratch, src2); - MULPS(src1, R(src2)); + mulps(src1, src2); - MOVAPS(src2, R(src1)); - CMPPS(src2, R(src2), CMP_UNORD); + movaps(src2, src1); + cmpunordps(src2, src2); - XORPS(scratch, R(src2)); - ANDPS(src1, R(scratch)); + xorps(scratch, src2); + andps(src1, scratch); } void JitShader::Compile_EvaluateCondition(Instruction instr) { // Note: NXOR is used below to check for equality switch (instr.flow_control.op) { case Instruction::FlowControlType::Or: - MOV(32, R(RAX), R(COND0)); - MOV(32, R(RBX), R(COND1)); - XOR(32, R(RAX), Imm32(instr.flow_control.refx.Value() ^ 1)); - XOR(32, R(RBX), Imm32(instr.flow_control.refy.Value() ^ 1)); - OR(32, R(RAX), R(RBX)); + mov(eax, COND0); + mov(ebx, COND1); + xor(eax, (instr.flow_control.refx.Value() ^ 1)); + xor(ebx, (instr.flow_control.refy.Value() ^ 1)); + or (eax, ebx); break; case Instruction::FlowControlType::And: - MOV(32, R(RAX), R(COND0)); - MOV(32, R(RBX), R(COND1)); - XOR(32, R(RAX), Imm32(instr.flow_control.refx.Value() ^ 1)); - XOR(32, R(RBX), Imm32(instr.flow_control.refy.Value() ^ 1)); - AND(32, R(RAX), R(RBX)); + mov(eax, COND0); + mov(ebx, COND1); + xor(eax, (instr.flow_control.refx.Value() ^ 1)); + xor(ebx, (instr.flow_control.refy.Value() ^ 1)); + and(eax, ebx); break; case Instruction::FlowControlType::JustX: - MOV(32, R(RAX), R(COND0)); - XOR(32, R(RAX), Imm32(instr.flow_control.refx.Value() ^ 1)); + mov(eax, COND0); + xor(eax, (instr.flow_control.refx.Value() ^ 1)); break; case Instruction::FlowControlType::JustY: - MOV(32, R(RAX), R(COND1)); - XOR(32, R(RAX), Imm32(instr.flow_control.refy.Value() ^ 1)); + mov(eax, COND1); + xor(eax, (instr.flow_control.refy.Value() ^ 1)); break; } } void JitShader::Compile_UniformCondition(Instruction instr) { - int offset = - ShaderSetup::UniformOffset(RegisterType::BoolUniform, instr.flow_control.bool_uniform_id); - CMP(sizeof(bool) * 8, MDisp(SETUP, offset), Imm8(0)); + size_t offset = ShaderSetup::GetBoolUniformOffset(instr.flow_control.bool_uniform_id); + cmp(byte[SETUP + offset], 0); } BitSet32 JitShader::PersistentCallerSavedRegs() { @@ -350,7 +357,7 @@ BitSet32 JitShader::PersistentCallerSavedRegs() { void JitShader::Compile_ADD(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); - ADDPS(SRC1, R(SRC2)); + addps(SRC1, SRC2); Compile_DestEnable(instr, SRC1); } @@ -360,15 +367,15 @@ void JitShader::Compile_DP3(Instruction instr) { Compile_SanitizedMul(SRC1, SRC2, SCRATCH); - MOVAPS(SRC2, R(SRC1)); - SHUFPS(SRC2, R(SRC2), _MM_SHUFFLE(1, 1, 1, 1)); + movaps(SRC2, SRC1); + shufps(SRC2, SRC2, _MM_SHUFFLE(1, 1, 1, 1)); - MOVAPS(SRC3, R(SRC1)); - SHUFPS(SRC3, R(SRC3), _MM_SHUFFLE(2, 2, 2, 2)); + movaps(SRC3, SRC1); + shufps(SRC3, SRC3, _MM_SHUFFLE(2, 2, 2, 2)); - SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(0, 0, 0, 0)); - ADDPS(SRC1, R(SRC2)); - ADDPS(SRC1, R(SRC3)); + shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); + addps(SRC1, SRC2); + addps(SRC1, SRC3); Compile_DestEnable(instr, SRC1); } @@ -379,13 +386,13 @@ void JitShader::Compile_DP4(Instruction instr) { Compile_SanitizedMul(SRC1, SRC2, SCRATCH); - MOVAPS(SRC2, R(SRC1)); - SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY - ADDPS(SRC1, R(SRC2)); + movaps(SRC2, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY + addps(SRC1, SRC2); - MOVAPS(SRC2, R(SRC1)); - SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX - ADDPS(SRC1, R(SRC2)); + movaps(SRC2, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX + addps(SRC1, SRC2); Compile_DestEnable(instr, SRC1); } @@ -401,50 +408,50 @@ void JitShader::Compile_DPH(Instruction instr) { if (Common::GetCPUCaps().sse4_1) { // Set 4th component to 1.0 - BLENDPS(SRC1, R(ONE), 0x8); // 0b1000 + blendps(SRC1, ONE, 0b1000); } else { // Set 4th component to 1.0 - MOVAPS(SCRATCH, R(SRC1)); - UNPCKHPS(SCRATCH, R(ONE)); // XYZW, 1111 -> Z1__ - UNPCKLPD(SRC1, R(SCRATCH)); // XYZW, Z1__ -> XYZ1 + movaps(SCRATCH, SRC1); + unpckhps(SCRATCH, ONE); // XYZW, 1111 -> Z1__ + unpcklpd(SRC1, SCRATCH); // XYZW, Z1__ -> XYZ1 } Compile_SanitizedMul(SRC1, SRC2, SCRATCH); - MOVAPS(SRC2, R(SRC1)); - SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY - ADDPS(SRC1, R(SRC2)); + movaps(SRC2, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY + addps(SRC1, SRC2); - MOVAPS(SRC2, R(SRC1)); - SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX - ADDPS(SRC1, R(SRC2)); + movaps(SRC2, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX + addps(SRC1, SRC2); Compile_DestEnable(instr, SRC1); } void JitShader::Compile_EX2(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - MOVSS(XMM0, R(SRC1)); + movss(xmm0, SRC1); // ABI_PARAM1 - ABI_PushRegistersAndAdjustStack(PersistentCallerSavedRegs(), 0); - ABI_CallFunction(reinterpret_cast<const void*>(exp2f)); - ABI_PopRegistersAndAdjustStack(PersistentCallerSavedRegs(), 0); + ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); + CallFarFunction(*this, exp2f); + ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); - SHUFPS(XMM0, R(XMM0), _MM_SHUFFLE(0, 0, 0, 0)); - MOVAPS(SRC1, R(XMM0)); + shufps(xmm0, xmm0, _MM_SHUFFLE(0, 0, 0, 0)); // ABI_RETURN + movaps(SRC1, xmm0); Compile_DestEnable(instr, SRC1); } void JitShader::Compile_LG2(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - MOVSS(XMM0, R(SRC1)); + movss(xmm0, SRC1); // ABI_PARAM1 - ABI_PushRegistersAndAdjustStack(PersistentCallerSavedRegs(), 0); - ABI_CallFunction(reinterpret_cast<const void*>(log2f)); - ABI_PopRegistersAndAdjustStack(PersistentCallerSavedRegs(), 0); + ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); + CallFarFunction(*this, log2f); + ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); - SHUFPS(XMM0, R(XMM0), _MM_SHUFFLE(0, 0, 0, 0)); - MOVAPS(SRC1, R(XMM0)); + shufps(xmm0, xmm0, _MM_SHUFFLE(0, 0, 0, 0)); // ABI_RETURN + movaps(SRC1, xmm0); Compile_DestEnable(instr, SRC1); } @@ -464,8 +471,8 @@ void JitShader::Compile_SGE(Instruction instr) { Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); } - CMPPS(SRC2, R(SRC1), CMP_LE); - ANDPS(SRC2, R(ONE)); + cmpleps(SRC2, SRC1); + andps(SRC2, ONE); Compile_DestEnable(instr, SRC2); } @@ -479,8 +486,8 @@ void JitShader::Compile_SLT(Instruction instr) { Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); } - CMPPS(SRC1, R(SRC2), CMP_LT); - ANDPS(SRC1, R(ONE)); + cmpltps(SRC1, SRC2); + andps(SRC1, ONE); Compile_DestEnable(instr, SRC1); } @@ -489,10 +496,10 @@ void JitShader::Compile_FLR(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); if (Common::GetCPUCaps().sse4_1) { - ROUNDFLOORPS(SRC1, R(SRC1)); + roundps(SRC1, SRC1, _MM_FROUND_FLOOR); } else { - CVTTPS2DQ(SRC1, R(SRC1)); - CVTDQ2PS(SRC1, R(SRC1)); + cvttps2dq(SRC1, SRC1); + cvtdq2ps(SRC1, SRC1); } Compile_DestEnable(instr, SRC1); @@ -502,7 +509,7 @@ void JitShader::Compile_MAX(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned. - MAXPS(SRC1, R(SRC2)); + maxps(SRC1, SRC2); Compile_DestEnable(instr, SRC1); } @@ -510,7 +517,7 @@ void JitShader::Compile_MIN(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned. - MINPS(SRC1, R(SRC2)); + minps(SRC1, SRC2); Compile_DestEnable(instr, SRC1); } @@ -524,37 +531,37 @@ void JitShader::Compile_MOVA(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); // Convert floats to integers using truncation (only care about X and Y components) - CVTTPS2DQ(SRC1, R(SRC1)); + cvttps2dq(SRC1, SRC1); // Get result - MOVQ_xmm(R(RAX), SRC1); + movq(rax, SRC1); // Handle destination enable if (swiz.DestComponentEnabled(0) && swiz.DestComponentEnabled(1)) { // Move and sign-extend low 32 bits - MOVSX(64, 32, ADDROFFS_REG_0, R(RAX)); + movsxd(ADDROFFS_REG_0, eax); // Move and sign-extend high 32 bits - SHR(64, R(RAX), Imm8(32)); - MOVSX(64, 32, ADDROFFS_REG_1, R(RAX)); + shr(rax, 32); + movsxd(ADDROFFS_REG_1, eax); // Multiply by 16 to be used as an offset later - SHL(64, R(ADDROFFS_REG_0), Imm8(4)); - SHL(64, R(ADDROFFS_REG_1), Imm8(4)); + shl(ADDROFFS_REG_0, 4); + shl(ADDROFFS_REG_1, 4); } else { if (swiz.DestComponentEnabled(0)) { // Move and sign-extend low 32 bits - MOVSX(64, 32, ADDROFFS_REG_0, R(RAX)); + movsxd(ADDROFFS_REG_0, eax); // Multiply by 16 to be used as an offset later - SHL(64, R(ADDROFFS_REG_0), Imm8(4)); + shl(ADDROFFS_REG_0, 4); } else if (swiz.DestComponentEnabled(1)) { // Move and sign-extend high 32 bits - SHR(64, R(RAX), Imm8(32)); - MOVSX(64, 32, ADDROFFS_REG_1, R(RAX)); + shr(rax, 32); + movsxd(ADDROFFS_REG_1, eax); // Multiply by 16 to be used as an offset later - SHL(64, R(ADDROFFS_REG_1), Imm8(4)); + shl(ADDROFFS_REG_1, 4); } } } @@ -569,8 +576,8 @@ void JitShader::Compile_RCP(Instruction instr) { // TODO(bunnei): RCPSS is a pretty rough approximation, this might cause problems if Pica // performs this operation more accurately. This should be checked on hardware. - RCPSS(SRC1, R(SRC1)); - SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX + rcpss(SRC1, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX Compile_DestEnable(instr, SRC1); } @@ -580,8 +587,8 @@ void JitShader::Compile_RSQ(Instruction instr) { // TODO(bunnei): RSQRTSS is a pretty rough approximation, this might cause problems if Pica // performs this operation more accurately. This should be checked on hardware. - RSQRTSS(SRC1, R(SRC1)); - SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX + rsqrtss(SRC1, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX Compile_DestEnable(instr, SRC1); } @@ -589,34 +596,35 @@ void JitShader::Compile_RSQ(Instruction instr) { void JitShader::Compile_NOP(Instruction instr) {} void JitShader::Compile_END(Instruction instr) { - ABI_PopRegistersAndAdjustStack(ABI_ALL_CALLEE_SAVED, 8); - RET(); + ABI_PopRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8); + ret(); } void JitShader::Compile_CALL(Instruction instr) { // Push offset of the return - PUSH(64, Imm32(instr.flow_control.dest_offset + instr.flow_control.num_instructions)); + push(qword, (instr.flow_control.dest_offset + instr.flow_control.num_instructions)); // Call the subroutine - FixupBranch b = CALL(); - fixup_branches.push_back({b, instr.flow_control.dest_offset}); + call(instruction_labels[instr.flow_control.dest_offset]); // Skip over the return offset that's on the stack - ADD(64, R(RSP), Imm32(8)); + add(rsp, 8); } void JitShader::Compile_CALLC(Instruction instr) { Compile_EvaluateCondition(instr); - FixupBranch b = J_CC(CC_Z, true); + Label b; + jz(b); Compile_CALL(instr); - SetJumpTarget(b); + L(b); } void JitShader::Compile_CALLU(Instruction instr) { Compile_UniformCondition(instr); - FixupBranch b = J_CC(CC_Z, true); + Label b; + jz(b); Compile_CALL(instr); - SetJumpTarget(b); + L(b); } void JitShader::Compile_CMP(Instruction instr) { @@ -633,33 +641,33 @@ void JitShader::Compile_CMP(Instruction instr) { static const u8 cmp[] = {CMP_EQ, CMP_NEQ, CMP_LT, CMP_LE, CMP_LT, CMP_LE}; bool invert_op_x = (op_x == Op::GreaterThan || op_x == Op::GreaterEqual); - Gen::X64Reg lhs_x = invert_op_x ? SRC2 : SRC1; - Gen::X64Reg rhs_x = invert_op_x ? SRC1 : SRC2; + Xmm lhs_x = invert_op_x ? SRC2 : SRC1; + Xmm rhs_x = invert_op_x ? SRC1 : SRC2; if (op_x == op_y) { // Compare X-component and Y-component together - CMPPS(lhs_x, R(rhs_x), cmp[op_x]); - MOVQ_xmm(R(COND0), lhs_x); + cmpps(lhs_x, rhs_x, cmp[op_x]); + movq(COND0, lhs_x); - MOV(64, R(COND1), R(COND0)); + mov(COND1, COND0); } else { bool invert_op_y = (op_y == Op::GreaterThan || op_y == Op::GreaterEqual); - Gen::X64Reg lhs_y = invert_op_y ? SRC2 : SRC1; - Gen::X64Reg rhs_y = invert_op_y ? SRC1 : SRC2; + Xmm lhs_y = invert_op_y ? SRC2 : SRC1; + Xmm rhs_y = invert_op_y ? SRC1 : SRC2; // Compare X-component - MOVAPS(SCRATCH, R(lhs_x)); - CMPSS(SCRATCH, R(rhs_x), cmp[op_x]); + movaps(SCRATCH, lhs_x); + cmpss(SCRATCH, rhs_x, cmp[op_x]); // Compare Y-component - CMPPS(lhs_y, R(rhs_y), cmp[op_y]); + cmpps(lhs_y, rhs_y, cmp[op_y]); - MOVQ_xmm(R(COND0), SCRATCH); - MOVQ_xmm(R(COND1), lhs_y); + movq(COND0, SCRATCH); + movq(COND1, lhs_y); } - SHR(32, R(COND0), Imm8(31)); - SHR(64, R(COND1), Imm8(63)); + shr(COND0.cvt32(), 31); // ignores upper 32 bits in source + shr(COND1, 63); } void JitShader::Compile_MAD(Instruction instr) { @@ -674,7 +682,7 @@ void JitShader::Compile_MAD(Instruction instr) { } Compile_SanitizedMul(SRC1, SRC2, SCRATCH); - ADDPS(SRC1, R(SRC3)); + addps(SRC1, SRC3); Compile_DestEnable(instr, SRC1); } @@ -682,6 +690,7 @@ void JitShader::Compile_MAD(Instruction instr) { void JitShader::Compile_IF(Instruction instr) { Compile_Assert(instr.flow_control.dest_offset >= program_counter, "Backwards if-statements not supported"); + Label l_else, l_endif; // Evaluate the "IF" condition if (instr.opcode.Value() == OpCode::Id::IFU) { @@ -689,26 +698,25 @@ void JitShader::Compile_IF(Instruction instr) { } else if (instr.opcode.Value() == OpCode::Id::IFC) { Compile_EvaluateCondition(instr); } - FixupBranch b = J_CC(CC_Z, true); + jz(l_else, T_NEAR); // Compile the code that corresponds to the condition evaluating as true Compile_Block(instr.flow_control.dest_offset); // If there isn't an "ELSE" condition, we are done here if (instr.flow_control.num_instructions == 0) { - SetJumpTarget(b); + L(l_else); return; } - FixupBranch b2 = J(true); - - SetJumpTarget(b); + jmp(l_endif, T_NEAR); + L(l_else); // This code corresponds to the "ELSE" condition // Comple the code that corresponds to the condition evaluating as false Compile_Block(instr.flow_control.dest_offset + instr.flow_control.num_instructions); - SetJumpTarget(b2); + L(l_endif); } void JitShader::Compile_LOOP(Instruction instr) { @@ -721,25 +729,25 @@ 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 - int offset = - ShaderSetup::UniformOffset(RegisterType::IntUniform, instr.flow_control.int_uniform_id); - MOV(32, R(LOOPCOUNT), MDisp(SETUP, offset)); - MOV(32, R(LOOPCOUNT_REG), R(LOOPCOUNT)); - SHR(32, R(LOOPCOUNT_REG), Imm8(4)); - AND(32, R(LOOPCOUNT_REG), Imm32(0xFF0)); // Y-component is the start - MOV(32, R(LOOPINC), R(LOOPCOUNT)); - SHR(32, R(LOOPINC), Imm8(12)); - AND(32, R(LOOPINC), Imm32(0xFF0)); // Z-component is the incrementer - MOVZX(32, 8, LOOPCOUNT, R(LOOPCOUNT)); // X-component is iteration count - ADD(32, R(LOOPCOUNT), Imm8(1)); // Iteration count is X-component + 1 - - auto loop_start = GetCodePtr(); + size_t offset = ShaderSetup::GetIntUniformOffset(instr.flow_control.int_uniform_id); + mov(LOOPCOUNT, dword[SETUP + offset]); + mov(LOOPCOUNT_REG, LOOPCOUNT); + shr(LOOPCOUNT_REG, 4); + and(LOOPCOUNT_REG, 0xFF0); // Y-component is the start + mov(LOOPINC, LOOPCOUNT); + shr(LOOPINC, 12); + and(LOOPINC, 0xFF0); // Z-component is the incrementer + movzx(LOOPCOUNT, LOOPCOUNT.cvt8()); // X-component is iteration count + add(LOOPCOUNT, 1); // Iteration count is X-component + 1 + + Label l_loop_start; + L(l_loop_start); Compile_Block(instr.flow_control.dest_offset + 1); - ADD(32, R(LOOPCOUNT_REG), R(LOOPINC)); // Increment LOOPCOUNT_REG by Z-component - SUB(32, R(LOOPCOUNT), Imm8(1)); // Increment loop count by 1 - J_CC(CC_NZ, loop_start); // Loop if not equal + add(LOOPCOUNT_REG, LOOPINC); // Increment LOOPCOUNT_REG by Z-component + sub(LOOPCOUNT, 1); // Increment loop count by 1 + jnz(l_loop_start); // Loop if not equal looping = false; } @@ -755,8 +763,12 @@ void JitShader::Compile_JMP(Instruction instr) { bool inverted_condition = (instr.opcode.Value() == OpCode::Id::JMPU) && (instr.flow_control.num_instructions & 1); - FixupBranch b = J_CC(inverted_condition ? CC_Z : CC_NZ, true); - fixup_branches.push_back({b, instr.flow_control.dest_offset}); + Label& b = instruction_labels[instr.flow_control.dest_offset]; + if (inverted_condition) { + jz(b, T_NEAR); + } else { + jnz(b, T_NEAR); + } } void JitShader::Compile_Block(unsigned end) { @@ -767,13 +779,14 @@ void JitShader::Compile_Block(unsigned end) { void JitShader::Compile_Return() { // Peek return offset on the stack and check if we're at that offset - MOV(64, R(RAX), MDisp(RSP, 8)); - CMP(32, R(RAX), Imm32(program_counter)); + mov(rax, qword[rsp + 8]); + cmp(eax, (program_counter)); // If so, jump back to before CALL - FixupBranch b = J_CC(CC_NZ, true); - RET(); - SetJumpTarget(b); + Label b; + jnz(b); + ret(); + L(b); } void JitShader::Compile_NextInstr() { @@ -781,9 +794,7 @@ void JitShader::Compile_NextInstr() { Compile_Return(); } - ASSERT_MSG(code_ptr[program_counter] == nullptr, - "Tried to compile already compiled shader location!"); - code_ptr[program_counter] = GetCodePtr(); + L(instruction_labels[program_counter]); Instruction instr = GetVertexShaderInstruction(program_counter++); @@ -824,64 +835,53 @@ void JitShader::FindReturnOffsets() { void JitShader::Compile() { // Reset flow control state - program = (CompiledShader*)GetCodePtr(); + program = (CompiledShader*)getCurr(); program_counter = 0; looping = false; - code_ptr.fill(nullptr); - fixup_branches.clear(); + instruction_labels.fill(Xbyak::Label()); // Find all `CALL` instructions and identify return locations FindReturnOffsets(); // The stack pointer is 8 modulo 16 at the entry of a procedure - ABI_PushRegistersAndAdjustStack(ABI_ALL_CALLEE_SAVED, 8); + ABI_PushRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8); - MOV(PTRBITS, R(SETUP), R(ABI_PARAM1)); - MOV(PTRBITS, R(STATE), R(ABI_PARAM2)); + mov(SETUP, ABI_PARAM1); + mov(STATE, ABI_PARAM2); // Zero address/loop registers - XOR(64, R(ADDROFFS_REG_0), R(ADDROFFS_REG_0)); - XOR(64, R(ADDROFFS_REG_1), R(ADDROFFS_REG_1)); - XOR(64, R(LOOPCOUNT_REG), R(LOOPCOUNT_REG)); + xor(ADDROFFS_REG_0.cvt32(), ADDROFFS_REG_0.cvt32()); + xor(ADDROFFS_REG_1.cvt32(), ADDROFFS_REG_1.cvt32()); + xor(LOOPCOUNT_REG, LOOPCOUNT_REG); // Used to set a register to one static const __m128 one = {1.f, 1.f, 1.f, 1.f}; - MOV(PTRBITS, R(RAX), ImmPtr(&one)); - MOVAPS(ONE, MatR(RAX)); + mov(rax, reinterpret_cast<size_t>(&one)); + movaps(ONE, xword[rax]); // Used to negate registers static const __m128 neg = {-0.f, -0.f, -0.f, -0.f}; - MOV(PTRBITS, R(RAX), ImmPtr(&neg)); - MOVAPS(NEGBIT, MatR(RAX)); + mov(rax, reinterpret_cast<size_t>(&neg)); + movaps(NEGBIT, xword[rax]); // Jump to start of the shader program - JMPptr(R(ABI_PARAM3)); + jmp(ABI_PARAM3); // Compile entire program Compile_Block(static_cast<unsigned>(g_state.vs.program_code.size())); - // Set the target for any incomplete branches now that the entire shader program has been - // emitted - for (const auto& branch : fixup_branches) { - SetJumpTarget(branch.first, code_ptr[branch.second]); - } - // Free memory that's no longer needed return_offsets.clear(); return_offsets.shrink_to_fit(); - fixup_branches.clear(); - fixup_branches.shrink_to_fit(); - uintptr_t size = - reinterpret_cast<uintptr_t>(GetCodePtr()) - reinterpret_cast<uintptr_t>(program); - ASSERT_MSG(size <= MAX_SHADER_SIZE, "Compiled a shader that exceeds the allocated size!"); + ready(); + uintptr_t size = reinterpret_cast<uintptr_t>(getCurr()) - reinterpret_cast<uintptr_t>(program); + ASSERT_MSG(size <= MAX_SHADER_SIZE, "Compiled a shader that exceeds the allocated size!"); LOG_DEBUG(HW_GPU, "Compiled shader size=%lu", size); } -JitShader::JitShader() { - AllocCodeSpace(MAX_SHADER_SIZE); -} +JitShader::JitShader() : Xbyak::CodeGenerator(MAX_SHADER_SIZE) {} } // namespace Shader diff --git a/src/video_core/shader/shader_jit_x64.h b/src/video_core/shader/shader_jit_x64.h index 98de5ecef..f37548306 100644 --- a/src/video_core/shader/shader_jit_x64.h +++ b/src/video_core/shader/shader_jit_x64.h @@ -9,6 +9,7 @@ #include <utility> #include <vector> #include <nihstro/shader_bytecode.h> +#include <xbyak.h> #include "common/bit_set.h" #include "common/common_types.h" #include "common/x64/emitter.h" @@ -29,12 +30,12 @@ constexpr size_t MAX_SHADER_SIZE = 1024 * 64; * This class implements the shader JIT compiler. It recompiles a Pica shader program into x86_64 * code that can be executed on the host machine directly. */ -class JitShader : public Gen::XCodeBlock { +class JitShader : public Xbyak::CodeGenerator { public: JitShader(); - void Run(const ShaderSetup& setup, UnitState<false>& state, unsigned offset) const { - program(&setup, &state, code_ptr[offset]); + void Run(const ShaderSetup& setup, UnitState& state, unsigned offset) const { + program(&setup, &state, instruction_labels[offset].getAddress()); } void Compile(); @@ -71,14 +72,14 @@ private: void Compile_NextInstr(); void Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, - Gen::X64Reg dest); - void Compile_DestEnable(Instruction instr, Gen::X64Reg dest); + Xbyak::Xmm dest); + void Compile_DestEnable(Instruction instr, Xbyak::Xmm dest); /** * Compiles a `MUL src1, src2` operation, properly handling the PICA semantics when multiplying * zero by inf. Clobbers `src2` and `scratch`. */ - void Compile_SanitizedMul(Gen::X64Reg src1, Gen::X64Reg src2, Gen::X64Reg scratch); + void Compile_SanitizedMul(Xbyak::Xmm src1, Xbyak::Xmm src2, Xbyak::Xmm scratch); void Compile_EvaluateCondition(Instruction instr); void Compile_UniformCondition(Instruction instr); @@ -103,7 +104,7 @@ private: void FindReturnOffsets(); /// Mapping of Pica VS instructions to pointers in the emitted code - std::array<const u8*, 1024> code_ptr; + std::array<Xbyak::Label, 1024> instruction_labels; /// Offsets in code where a return needs to be inserted std::vector<unsigned> return_offsets; @@ -111,9 +112,6 @@ private: unsigned program_counter = 0; ///< Offset of the next instruction to decode bool looping = false; ///< True if compiling a loop, used to check for nested loops - /// Branches that need to be fixed up once the entire shader program is compiled - std::vector<std::pair<Gen::FixupBranch, unsigned>> fixup_branches; - using CompiledShader = void(const void* setup, void* state, const u8* start_addr); CompiledShader* program = nullptr; }; |