diff options
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;  };  | 
