diff options
114 files changed, 2567 insertions, 890 deletions
diff --git a/.gitmodules b/.gitmodules index f98725622..36caa59f8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@  [submodule "cryptopp"]      path = externals/cryptopp/cryptopp      url = https://github.com/weidai11/cryptopp.git +[submodule "fmt"] +    path = externals/fmt +    url = https://github.com/fmtlib/fmt.git diff --git a/.travis-deps.sh b/.travis-deps.sh index 1404fe19f..451886984 100755 --- a/.travis-deps.sh +++ b/.travis-deps.sh @@ -11,7 +11,7 @@ if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then      if [ ! -e $HOME/.local/bin/cmake ]; then          echo "CMake not found in the cache, get and extract it..." -        curl -L http://www.cmake.org/files/v3.2/cmake-3.2.0-Linux-i386.tar.gz \ +        curl -L http://www.cmake.org/files/v3.6/cmake-3.6.3-Linux-x86_64.tar.gz \              | tar -xz -C $HOME/.local --strip-components=1      else          echo "Using cached CMake" diff --git a/CMakeLists.txt b/CMakeLists.txt index 306959e24..1f0af2d41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,20 +1,25 @@ -# 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) +# CMake 3.6 required for FindBoost to define IMPORTED libs properly on unknown Boost versions +cmake_minimum_required(VERSION 3.6) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules") -function(download_bundled_external remote_path lib_name prefix_var) -    set(prefix "${CMAKE_BINARY_DIR}/externals/${lib_name}") -    if (NOT EXISTS "${prefix}") -        message(STATUS "Downloading binaries for ${lib_name}...") -        file(DOWNLOAD -            https://github.com/citra-emu/ext-windows-bin/raw/master/${remote_path}${lib_name}.7z -            "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z" SHOW_PROGRESS) -        execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z" -            WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals") -    endif() -    message(STATUS "Using bundled binaries at ${prefix}") -    set(${prefix_var} "${prefix}" PARENT_SCOPE) -endfunction() +project(citra) + +option(ENABLE_SDL2 "Enable the SDL2 frontend" ON) +option(CITRA_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" OFF) + +option(ENABLE_QT "Enable the Qt frontend" ON) +option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF) + +if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit) +    message(STATUS "Copying pre-commit hook") +    file(COPY hooks/pre-commit +        DESTINATION ${CMAKE_SOURCE_DIR}/.git/hooks) +endif() + + +# Detect current compilation architecture and create standard definitions +# =======================================================================  include(CheckSymbolExists)  function(detect_architecture symbol arch) @@ -33,20 +38,6 @@ function(detect_architecture symbol arch)      endif()  endfunction() -project(citra) - -option(ENABLE_SDL2 "Enable the SDL2 frontend" ON) -option(CITRA_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" OFF) - -option(ENABLE_QT "Enable the Qt frontend" ON) -option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF) - -if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/.git/hooks/pre-commit) -    message(STATUS "Copying pre-commit hook") -    file(COPY hooks/pre-commit -        DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/.git/hooks) -endif() -  if (MSVC)      detect_architecture("_M_AMD64" x86_64)      detect_architecture("_M_IX86" x86) @@ -63,6 +54,10 @@ if (NOT DEFINED ARCHITECTURE)  endif()  message(STATUS "Target architecture: ${ARCHITECTURE}") + +# Configure compilation flags +# =========================== +  set(CMAKE_CXX_STANDARD 14)  set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -130,28 +125,44 @@ add_definitions(-DSINGLETHREADED)  set_property(DIRECTORY APPEND PROPERTY      COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>) + +# System imported libraries +# ====================== + +# This function downloads a binary library package from our external repo. +# Params: +#   remote_path: path to the file to download, relative to the remote repository root +#   prefix_var: name of a variable which will be set with the path to the extracted contents +function(download_bundled_external remote_path lib_name prefix_var) +    set(prefix "${CMAKE_BINARY_DIR}/externals/${lib_name}") +    if (NOT EXISTS "${prefix}") +        message(STATUS "Downloading binaries for ${lib_name}...") +        file(DOWNLOAD +            https://github.com/citra-emu/ext-windows-bin/raw/master/${remote_path}${lib_name}.7z +            "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z" SHOW_PROGRESS) +        execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z" +            WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals") +    endif() +    message(STATUS "Using bundled binaries at ${prefix}") +    set(${prefix_var} "${prefix}" PARENT_SCOPE) +endfunction() +  find_package(PNG QUIET) -if (PNG_FOUND) -    add_definitions(-DHAVE_PNG) -else() +if (NOT PNG_FOUND)      message(STATUS "libpng not found. Some debugging features have been disabled.")  endif() -find_package(Boost 1.57.0 QUIET) +find_package(Boost 1.63.0 QUIET)  if (NOT Boost_FOUND) -    message(STATUS "Boost 1.57.0 or newer not found, falling back to externals") -    set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/externals/boost") -endif() -include_directories(${Boost_INCLUDE_DIR}) - -# Include bundled CMake modules -list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/externals/cmake-modules") +    message(STATUS "Boost 1.63.0 or newer not found, falling back to externals") -find_package(OpenGL REQUIRED) -include_directories(${OPENGL_INCLUDE_DIR}) +    set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/externals/boost") +    set(Boost_NO_SYSTEM_PATHS OFF) +    find_package(Boost QUIET REQUIRED) +endif()  # Prefer the -pthread flag on Linux. -set (THREADS_PREFER_PTHREAD_FLAG ON) +set(THREADS_PREFER_PTHREAD_FLAG ON)  find_package(Threads REQUIRED)  if (ENABLE_SDL2) @@ -174,10 +185,43 @@ if (ENABLE_SDL2)      else()          find_package(SDL2 REQUIRED)      endif() + +    if (SDL2_FOUND) +        # TODO(yuriks): Make FindSDL2.cmake export an IMPORTED library instead +        add_library(SDL2 INTERFACE) +        target_link_libraries(SDL2 INTERFACE "${SDL2_LIBRARY}") +        target_include_directories(SDL2 INTERFACE "${SDL2_INCLUDE_DIR}") +    endif()  else()      set(SDL2_FOUND NO)  endif() +if (ENABLE_QT) +    if (CITRA_USE_BUNDLED_QT) +        if (MSVC14 AND ARCHITECTURE_x86_64) +            set(QT_VER qt-5.7-msvc2015_64) +        else() +            message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable CITRA_USE_BUNDLED_QT and provide your own.") +        endif() + +        if (DEFINED QT_VER) +            download_bundled_external("qt/" ${QT_VER} QT_PREFIX) +        endif() + +        set(QT_PREFIX_HINT HINTS "${QT_PREFIX}") +    else() +        # Passing an empty HINTS seems to cause default system paths to get ignored in CMake 2.8 so +        # make sure to not pass anything if we don't have one. +        set(QT_PREFIX_HINT) +    endif() + +    find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT}) +endif() + + +# Platform-specific library requirements +# ====================================== +  IF (APPLE)      FIND_LIBRARY(COCOA_LIBRARY Cocoa)           # Umbrella framework for everything GUI-related      set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY}) @@ -206,28 +250,9 @@ if (UNIX OR MINGW)      endif()  endif() -if (ENABLE_QT) -    if (CITRA_USE_BUNDLED_QT) -        if (MSVC14 AND ARCHITECTURE_x86_64) -            set(QT_VER qt-5.7-msvc2015_64) -        else() -            message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable CITRA_USE_BUNDLED_QT and provide your own.") -        endif() - -        if (DEFINED QT_VER) -            download_bundled_external("qt/" ${QT_VER} QT_PREFIX) -        endif() -        set(QT_PREFIX_HINT HINTS "${QT_PREFIX}") -    else() -        # Passing an empty HINTS seems to cause default system paths to get ignored in CMake 2.8 so -        # make sure to not pass anything if we don't have one. -        set(QT_PREFIX_HINT) -    endif() - -    find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT}) -    set(CITRA_QT_LIBS Qt5::Widgets Qt5::OpenGL) -endif() +# Include source code +# ===================  # This function should be passed a list of all files in a target. It will automatically generate  # file groups following the directory hierarchy, so that the layout of the files in IDEs matches the @@ -251,29 +276,13 @@ get_git_head_revision(GIT_REF_SPEC GIT_REV)  git_describe(GIT_DESC --always --long --dirty)  git_branch_name(GIT_BRANCH) -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) - -add_subdirectory(externals/glad) -include_directories(externals/microprofile) -include_directories(externals/nihstro/include) - -if (MSVC) -    add_subdirectory(externals/getopt) -endif() - -# process subdirectories -add_subdirectory(externals/soundtouch) - +add_subdirectory(src)  enable_testing() -add_subdirectory(src) + +# Installation instructions +# =========================  # Install freedesktop.org metadata files, following those specifications:  # http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 309e98464..1e04931ee 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -1,11 +1,52 @@ +# Definitions for all external bundled libraries + +# Catch +add_library(catch-single-include INTERFACE) +target_include_directories(catch-single-include INTERFACE catch/single_include) + +# Crypto++ +add_subdirectory(cryptopp) + +# Dynarmic +# Dynarmic will skip defining xbyak if it's already defined, we then define it below +add_library(xbyak INTERFACE) +option(DYNARMIC_TESTS OFF) +set(DYNARMIC_NO_BUNDLED_FMT ON) +add_subdirectory(dynarmic) + +# libfmt +add_subdirectory(fmt) + +# getopt +if (MSVC) +    add_subdirectory(getopt) +endif() + +# Glad +add_subdirectory(glad) + +# inih +add_subdirectory(inih) + +# MicroProfile +add_library(microprofile INTERFACE) +target_include_directories(microprofile INTERFACE ./microprofile) + +# Nihstro +add_library(nihstro-headers INTERFACE) +target_include_directories(nihstro-headers INTERFACE ./nihstro/include) + +# SoundTouch +add_subdirectory(soundtouch) +# The SoundTouch target doesn't export the necessary include paths as properties by default +target_include_directories(SoundTouch INTERFACE ./soundtouch/include) +  # Xbyak  if (ARCHITECTURE_x86_64) -    add_library(xbyak INTERFACE) -    target_include_directories(xbyak INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/xbyak/xbyak) +    # Defined before "dynarmic" above +    # add_library(xbyak INTERFACE) +    target_include_directories(xbyak INTERFACE ./xbyak/xbyak)      if (NOT MSVC)          target_compile_options(xbyak INTERFACE -fno-operator-names)      endif()  endif() - -add_subdirectory(cryptopp) - diff --git a/externals/cryptopp/CMakeLists.txt b/externals/cryptopp/CMakeLists.txt index 653af1e4b..864de18bb 100644 --- a/externals/cryptopp/CMakeLists.txt +++ b/externals/cryptopp/CMakeLists.txt @@ -10,6 +10,7 @@  #  - disabled installation  #  - disabled documentation  #  - configured to build a static library only +#  - adds include directories to the library target  include(TestBigEndian)  include(CheckCXXCompilerFlag) @@ -148,14 +149,15 @@ endif()  # Compile targets  #============================================================================  add_library(cryptopp STATIC ${cryptopp_SOURCES}) +target_include_directories(cryptopp INTERFACE .)  #============================================================================  # Third-party libraries  #============================================================================  if(WIN32) -    target_link_libraries(cryptopp ws2_32) +    target_link_libraries(cryptopp PRIVATE ws2_32)  endif()  find_package(Threads) -target_link_libraries(cryptopp ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(cryptopp PRIVATE ${CMAKE_THREAD_LIBS_INIT}) diff --git a/externals/dynarmic b/externals/dynarmic -Subproject 358cf7c32205a5114964865c86a8455daf81073 +Subproject 7707ff13e981b0aecf87f3156ee0b641469f7bb diff --git a/externals/fmt b/externals/fmt new file mode 160000 +Subproject ac5484c4e7365b59d8c7e14db6778de26635e42 diff --git a/externals/glad/CMakeLists.txt b/externals/glad/CMakeLists.txt index a97d4aa73..6d35a844b 100644 --- a/externals/glad/CMakeLists.txt +++ b/externals/glad/CMakeLists.txt @@ -9,6 +9,7 @@ set(HEADERS  create_directory_groups(${SRCS} ${HEADERS})  add_library(glad STATIC ${SRCS} ${HEADERS})  target_include_directories(glad PUBLIC "include/") +  if ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") -    target_link_libraries(glad dl) +    target_link_libraries(glad PRIVATE dl)  endif() diff --git a/externals/inih/CMakeLists.txt b/externals/inih/CMakeLists.txt index c87f78bfc..cff36a581 100644 --- a/externals/inih/CMakeLists.txt +++ b/externals/inih/CMakeLists.txt @@ -9,3 +9,4 @@ set(HEADERS  create_directory_groups(${SRCS} ${HEADERS})  add_library(inih ${SRCS} ${HEADERS}) +target_include_directories(inih INTERFACE .) diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index a72a907ef..0ad86bb7a 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -27,20 +27,18 @@ set(HEADERS              time_stretch.h              ) -include_directories(../../externals/soundtouch/include) -  if(SDL2_FOUND)      set(SRCS ${SRCS} sdl2_sink.cpp)      set(HEADERS ${HEADERS} sdl2_sink.h) -    include_directories(${SDL2_INCLUDE_DIR})  endif()  create_directory_groups(${SRCS} ${HEADERS})  add_library(audio_core STATIC ${SRCS} ${HEADERS}) -target_link_libraries(audio_core SoundTouch) +target_link_libraries(audio_core PUBLIC common core) +target_link_libraries(audio_core PRIVATE SoundTouch)  if(SDL2_FOUND) -    target_link_libraries(audio_core ${SDL2_LIBRARY}) -    set_property(TARGET audio_core APPEND PROPERTY COMPILE_DEFINITIONS HAVE_SDL2) +    target_link_libraries(audio_core PRIVATE SDL2) +    target_compile_definitions(audio_core PRIVATE HAVE_SDL2)  endif() diff --git a/src/citra/CMakeLists.txt b/src/citra/CMakeLists.txt index 47231ba71..d72d2b5f4 100644 --- a/src/citra/CMakeLists.txt +++ b/src/citra/CMakeLists.txt @@ -15,15 +15,13 @@ set(HEADERS  create_directory_groups(${SRCS} ${HEADERS}) -include_directories(${SDL2_INCLUDE_DIR}) -  add_executable(citra ${SRCS} ${HEADERS}) -target_link_libraries(citra core video_core audio_core common input_common) -target_link_libraries(citra ${SDL2_LIBRARY} ${OPENGL_gl_LIBRARY} inih glad) +target_link_libraries(citra PRIVATE common core input_common) +target_link_libraries(citra PRIVATE inih glad)  if (MSVC) -    target_link_libraries(citra getopt) +    target_link_libraries(citra PRIVATE getopt)  endif() -target_link_libraries(citra ${PLATFORM_LIBRARIES} Threads::Threads) +target_link_libraries(citra PRIVATE ${PLATFORM_LIBRARIES} SDL2 Threads::Threads)  if(UNIX AND NOT APPLE)      install(TARGETS citra RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index 76f5caeb1..c0dac9e8f 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -33,7 +33,6 @@  #include "core/gdbstub/gdbstub.h"  #include "core/loader/loader.h"  #include "core/settings.h" -#include "video_core/video_core.h"  static void PrintHelp(const char* argv0) {      std::cout << "Usage: " << argv0 diff --git a/src/citra/config.cpp b/src/citra/config.cpp index a4162e9ad..f08b4069c 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -5,11 +5,11 @@  #include <memory>  #include <SDL.h>  #include <inih/cpp/INIReader.h> +#include "citra/config.h"  #include "citra/default_ini.h"  #include "common/file_util.h"  #include "common/logging/log.h"  #include "common/param_package.h" -#include "config.h"  #include "core/settings.h"  #include "input_common/main.h" @@ -21,6 +21,8 @@ Config::Config() {      Reload();  } +Config::~Config() = default; +  bool Config::LoadINI(const std::string& default_contents, bool retry) {      const char* location = this->sdl2_config_loc.c_str();      if (sdl2_config->ParseError() < 0) { diff --git a/src/citra/config.h b/src/citra/config.h index b1c31f59c..abc90f642 100644 --- a/src/citra/config.h +++ b/src/citra/config.h @@ -6,7 +6,8 @@  #include <memory>  #include <string> -#include <inih/cpp/INIReader.h> + +class INIReader;  class Config {      std::unique_ptr<INIReader> sdl2_config; @@ -17,6 +18,7 @@ class Config {  public:      Config(); +    ~Config();      void Reload();  }; diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index 6bc0b0d00..47aadd60c 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -12,10 +12,10 @@  #include "common/logging/log.h"  #include "common/scm_rev.h"  #include "common/string_util.h" +#include "core/3ds.h"  #include "core/settings.h"  #include "input_common/keyboard.h"  #include "input_common/main.h" -#include "video_core/video_core.h"  void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {      TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); @@ -80,12 +80,12 @@ EmuWindow_SDL2::EmuWindow_SDL2() {      std::string window_title = Common::StringFromFormat("Citra %s| %s-%s ", Common::g_build_name,                                                          Common::g_scm_branch, Common::g_scm_desc); -    render_window = SDL_CreateWindow( -        window_title.c_str(), -        SDL_WINDOWPOS_UNDEFINED, // x position -        SDL_WINDOWPOS_UNDEFINED, // y position -        VideoCore::kScreenTopWidth, VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight, -        SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); +    render_window = +        SDL_CreateWindow(window_title.c_str(), +                         SDL_WINDOWPOS_UNDEFINED, // x position +                         SDL_WINDOWPOS_UNDEFINED, // y position +                         Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight, +                         SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);      if (render_window == nullptr) {          LOG_CRITICAL(Frontend, "Failed to create SDL2 window! Exiting..."); diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 4e837668e..4841cbf05 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -91,9 +91,9 @@ if (APPLE)  else()      add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS})  endif() -target_link_libraries(citra-qt core video_core audio_core common input_common) -target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS}) -target_link_libraries(citra-qt ${PLATFORM_LIBRARIES} Threads::Threads) +target_link_libraries(citra-qt PRIVATE audio_core common core input_common video_core) +target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::OpenGL Qt5::Widgets) +target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)  if(UNIX AND NOT APPLE)      install(TARGETS citra-qt RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index bae576d6a..06b62f44c 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -12,12 +12,11 @@  #include "common/microprofile.h"  #include "common/scm_rev.h"  #include "common/string_util.h" +#include "core/3ds.h"  #include "core/core.h"  #include "core/settings.h"  #include "input_common/keyboard.h"  #include "input_common/main.h" -#include "video_core/debug_utils/debug_utils.h" -#include "video_core/video_core.h"  EmuThread::EmuThread(GRenderWindow* render_window)      : exec_step(false), running(false), stop_run(false), render_window(render_window) {} @@ -266,8 +265,7 @@ void GRenderWindow::InitRenderTarget() {      child = new GGLWidgetInternal(fmt, this);      QBoxLayout* layout = new QHBoxLayout(this); -    resize(VideoCore::kScreenTopWidth, -           VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight); +    resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);      layout->addWidget(child);      layout->setMargin(0);      setLayout(layout); diff --git a/src/citra_qt/debugger/wait_tree.cpp b/src/citra_qt/debugger/wait_tree.cpp index b6ecf3819..8c244b6b2 100644 --- a/src/citra_qt/debugger/wait_tree.cpp +++ b/src/citra_qt/debugger/wait_tree.cpp @@ -10,6 +10,7 @@  #include "core/hle/kernel/semaphore.h"  #include "core/hle/kernel/thread.h"  #include "core/hle/kernel/timer.h" +#include "core/hle/kernel/wait_object.h"  WaitTreeItem::~WaitTreeItem() {} diff --git a/src/citra_qt/debugger/wait_tree.h b/src/citra_qt/debugger/wait_tree.h index ee9708fc1..06ef58ea7 100644 --- a/src/citra_qt/debugger/wait_tree.h +++ b/src/citra_qt/debugger/wait_tree.h @@ -4,12 +4,10 @@  #pragma once -#include <boost/container/flat_set.hpp> -  #include <QAbstractItemModel>  #include <QDockWidget>  #include <QTreeView> - +#include <boost/container/flat_set.hpp>  #include "core/core.h"  #include "core/hle/kernel/kernel.h" diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index d7fad555f..eb2c7d613 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -43,7 +43,6 @@  #include "core/gdbstub/gdbstub.h"  #include "core/loader/loader.h"  #include "core/settings.h" -#include "video_core/video_core.h"  #ifdef QT_STATICPLUGIN  Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 4b30185f1..7e83e64b0 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -27,7 +27,6 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOU  set(SRCS              break_points.cpp              file_util.cpp -            framebuffer_layout.cpp              hash.cpp              logging/filter.cpp              logging/text_formatter.cpp @@ -38,6 +37,7 @@ set(SRCS              param_package.cpp              scm_rev.cpp              string_util.cpp +            telemetry.cpp              thread.cpp              timer.cpp              ) @@ -55,7 +55,6 @@ set(HEADERS              common_paths.h              common_types.h              file_util.h -            framebuffer_layout.h              hash.h              linear_disk_cache.h              logging/text_formatter.h @@ -74,6 +73,7 @@ set(HEADERS              string_util.h              swap.h              synchronized_wrapper.h +            telemetry.h              thread.h              thread_queue_list.h              timer.h @@ -95,6 +95,7 @@ endif()  create_directory_groups(${SRCS} ${HEADERS})  add_library(common STATIC ${SRCS} ${HEADERS}) +target_link_libraries(common PUBLIC Boost::boost microprofile)  if (ARCHITECTURE_x86_64) -    target_link_libraries(common xbyak) +    target_link_libraries(common PRIVATE xbyak)  endif() diff --git a/src/common/bit_field.h b/src/common/bit_field.h index 030f7caeb..0cc0a1be0 100644 --- a/src/common/bit_field.h +++ b/src/common/bit_field.h @@ -108,7 +108,7 @@   * symptoms.   */  #pragma pack(1) -template <std::size_t position, std::size_t bits, typename T> +template <std::size_t Position, std::size_t Bits, typename T>  struct BitField {  private:      // We hide the copy assigment operator here, because the default copy @@ -117,7 +117,45 @@ private:      // We don't delete it because we want BitField to be trivially copyable.      BitField& operator=(const BitField&) = default; +    // StorageType is T for non-enum types and the underlying type of T if +    // T is an enumeration. Note that T is wrapped within an enable_if in the +    // former case to workaround compile errors which arise when using +    // std::underlying_type<T>::type directly. +    using StorageType = typename std::conditional_t<std::is_enum<T>::value, std::underlying_type<T>, +                                                    std::enable_if<true, T>>::type; + +    // Unsigned version of StorageType +    using StorageTypeU = std::make_unsigned_t<StorageType>; +  public: +    /// Constants to allow limited introspection of fields if needed +    static constexpr size_t position = Position; +    static constexpr size_t bits = Bits; +    static constexpr StorageType mask = (((StorageTypeU)~0) >> (8 * sizeof(T) - bits)) << position; + +    /** +     * Formats a value by masking and shifting it according to the field parameters. A value +     * containing several bitfields can be assembled by formatting each of their values and ORing +     * the results together. +     */ +    static constexpr FORCE_INLINE StorageType FormatValue(const T& value) { +        return ((StorageType)value << position) & mask; +    } + +    /** +     * Extracts a value from the passed storage. In most situations prefer use the member functions +     * (such as Value() or operator T), but this can be used to extract a value from a bitfield +     * union in a constexpr context. +     */ +    static constexpr FORCE_INLINE T ExtractValue(const StorageType& storage) { +        if (std::numeric_limits<T>::is_signed) { +            std::size_t shift = 8 * sizeof(T) - bits; +            return (T)((storage << (shift - position)) >> shift); +        } else { +            return (T)((storage & mask) >> position); +        } +    } +      // This constructor and assignment operator might be considered ambiguous:      // Would they initialize the storage or just the bitfield?      // Hence, delete them. Use the Assign method to set bitfield values! @@ -126,23 +164,18 @@ public:      // Force default constructor to be created      // so that we can use this within unions -    BitField() = default; +    constexpr BitField() = default;      FORCE_INLINE operator T() const {          return Value();      }      FORCE_INLINE void Assign(const T& value) { -        storage = (storage & ~GetMask()) | (((StorageType)value << position) & GetMask()); +        storage = (storage & ~mask) | FormatValue(value);      }      FORCE_INLINE T Value() const { -        if (std::numeric_limits<T>::is_signed) { -            std::size_t shift = 8 * sizeof(T) - bits; -            return (T)((storage << (shift - position)) >> shift); -        } else { -            return (T)((storage & GetMask()) >> position); -        } +        return ExtractValue(storage);      }      // TODO: we may want to change this to explicit operator bool() if it's bug-free in VS2015 @@ -151,20 +184,6 @@ public:      }  private: -    // StorageType is T for non-enum types and the underlying type of T if -    // T is an enumeration. Note that T is wrapped within an enable_if in the -    // former case to workaround compile errors which arise when using -    // std::underlying_type<T>::type directly. -    typedef typename std::conditional<std::is_enum<T>::value, std::underlying_type<T>, -                                      std::enable_if<true, T>>::type::type StorageType; - -    // Unsigned version of StorageType -    typedef typename std::make_unsigned<StorageType>::type StorageTypeU; - -    FORCE_INLINE StorageType GetMask() const { -        return (((StorageTypeU)~0) >> (8 * sizeof(T) - bits)) << position; -    } -      StorageType storage;      static_assert(bits + position <= 8 * sizeof(T), "Bitfield out of range"); diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index b141e79ed..2e7877500 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -7,7 +7,7 @@  #if !defined(ARCHITECTURE_x86_64) && !defined(_M_ARM)  #include <cstdlib> // for exit  #endif -#include "common_types.h" +#include "common/common_types.h"  #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) diff --git a/src/common/hash.cpp b/src/common/hash.cpp index f3d390dc5..a02e9e5b9 100644 --- a/src/common/hash.cpp +++ b/src/common/hash.cpp @@ -5,9 +5,9 @@  #if defined(_MSC_VER)  #include <stdlib.h>  #endif -#include "common_funcs.h" -#include "common_types.h" -#include "hash.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/hash.h"  namespace Common { diff --git a/src/common/telemetry.cpp b/src/common/telemetry.cpp new file mode 100644 index 000000000..bf1f54886 --- /dev/null +++ b/src/common/telemetry.cpp @@ -0,0 +1,40 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include "common/telemetry.h" + +namespace Telemetry { + +void FieldCollection::Accept(VisitorInterface& visitor) const { +    for (const auto& field : fields) { +        field.second->Accept(visitor); +    } +} + +void FieldCollection::AddField(std::unique_ptr<FieldInterface> field) { +    fields[field->GetName()] = std::move(field); +} + +template <class T> +void Field<T>::Accept(VisitorInterface& visitor) const { +    visitor.Visit(*this); +} + +template class Field<bool>; +template class Field<double>; +template class Field<float>; +template class Field<u8>; +template class Field<u16>; +template class Field<u32>; +template class Field<u64>; +template class Field<s8>; +template class Field<s16>; +template class Field<s32>; +template class Field<s64>; +template class Field<std::string>; +template class Field<const char*>; +template class Field<std::chrono::microseconds>; + +} // namespace Telemetry diff --git a/src/common/telemetry.h b/src/common/telemetry.h new file mode 100644 index 000000000..dd6bbd759 --- /dev/null +++ b/src/common/telemetry.h @@ -0,0 +1,196 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <chrono> +#include <map> +#include <memory> +#include <string> +#include "common/common_types.h" + +namespace Telemetry { + +/// Field type, used for grouping fields together in the final submitted telemetry log +enum class FieldType : u8 { +    None = 0,     ///< No specified field group +    App,          ///< Citra application fields (e.g. version, branch, etc.) +    Session,      ///< Emulated session fields (e.g. title ID, log, etc.) +    Performance,  ///< Emulated performance (e.g. fps, emulated CPU speed, etc.) +    UserFeedback, ///< User submitted feedback (e.g. star rating, user notes, etc.) +    UserConfig,   ///< User configuration fields (e.g. emulated CPU core, renderer, etc.) +    UserSystem,   ///< User system information (e.g. host CPU type, RAM, etc.) +}; + +struct VisitorInterface; + +/** + * Interface class for telemetry data fields. + */ +class FieldInterface : NonCopyable { +public: +    virtual ~FieldInterface() = default; + +    /** +     * Accept method for the visitor pattern. +     * @param visitor Reference to the visitor that will visit this field. +     */ +    virtual void Accept(VisitorInterface& visitor) const = 0; + +    /** +     * Gets the name of this field. +     * @returns Name of this field as a string. +     */ +    virtual const std::string& GetName() const = 0; +}; + +/** + * Represents a telemetry data field, i.e. a unit of data that gets logged and submitted to our + * telemetry web service. + */ +template <typename T> +class Field : public FieldInterface { +public: +    Field(FieldType type, std::string name, const T& value) +        : type(type), name(std::move(name)), value(value) {} + +    Field(FieldType type, std::string name, T&& value) +        : type(type), name(std::move(name)), value(std::move(value)) {} + +    Field(const Field& other) : Field(other.type, other.name, other.value) {} + +    Field& operator=(const Field& other) { +        type = other.type; +        name = other.name; +        value = other.value; +        return *this; +    } + +    Field& operator=(Field&& other) { +        type = other.type; +        name = std::move(other.name); +        value = std::move(other.value); +        return *this; +    } + +    void Accept(VisitorInterface& visitor) const override; + +    const std::string& GetName() const override { +        return name; +    } + +    /** +     * Returns the type of the field. +     */ +    FieldType GetType() const { +        return type; +    } + +    /** +     * Returns the value of the field. +     */ +    const T& GetValue() const { +        return value; +    } + +    inline bool operator==(const Field<T>& other) { +        return (type == other.type) && (name == other.name) && (value == other.value); +    } + +    inline bool operator!=(const Field<T>& other) { +        return !(*this == other); +    } + +private: +    std::string name; ///< Field name, must be unique +    FieldType type{}; ///< Field type, used for grouping fields together +    T value;          ///< Field value +}; + +/** + * Collection of data fields that have been logged. + */ +class FieldCollection final : NonCopyable { +public: +    FieldCollection() = default; + +    /** +     * Accept method for the visitor pattern, visits each field in the collection. +     * @param visitor Reference to the visitor that will visit each field. +     */ +    void Accept(VisitorInterface& visitor) const; + +    /** +     * Creates a new field and adds it to the field collection. +     * @param type Type of the field to add. +     * @param name Name of the field to add. +     * @param value Value for the field to add. +     */ +    template <typename T> +    void AddField(FieldType type, const char* name, T value) { +        return AddField(std::make_unique<Field<T>>(type, name, std::move(value))); +    } + +    /** +     * Adds a new field to the field collection. +     * @param field Field to add to the field collection. +     */ +    void AddField(std::unique_ptr<FieldInterface> field); + +private: +    std::map<std::string, std::unique_ptr<FieldInterface>> fields; +}; + +/** + * Telemetry fields visitor interface class. A backend to log to a web service should implement + * this interface. + */ +struct VisitorInterface : NonCopyable { +    virtual ~VisitorInterface() = default; + +    virtual void Visit(const Field<bool>& field) = 0; +    virtual void Visit(const Field<double>& field) = 0; +    virtual void Visit(const Field<float>& field) = 0; +    virtual void Visit(const Field<u8>& field) = 0; +    virtual void Visit(const Field<u16>& field) = 0; +    virtual void Visit(const Field<u32>& field) = 0; +    virtual void Visit(const Field<u64>& field) = 0; +    virtual void Visit(const Field<s8>& field) = 0; +    virtual void Visit(const Field<s16>& field) = 0; +    virtual void Visit(const Field<s32>& field) = 0; +    virtual void Visit(const Field<s64>& field) = 0; +    virtual void Visit(const Field<std::string>& field) = 0; +    virtual void Visit(const Field<const char*>& field) = 0; +    virtual void Visit(const Field<std::chrono::microseconds>& field) = 0; + +    /// Completion method, called once all fields have been visited +    virtual void Complete() = 0; +}; + +/** + * Empty implementation of VisitorInterface that drops all fields. Used when a functional + * backend implementation is not available. + */ +struct NullVisitor : public VisitorInterface { +    ~NullVisitor() = default; + +    void Visit(const Field<bool>& /*field*/) override {} +    void Visit(const Field<double>& /*field*/) override {} +    void Visit(const Field<float>& /*field*/) override {} +    void Visit(const Field<u8>& /*field*/) override {} +    void Visit(const Field<u16>& /*field*/) override {} +    void Visit(const Field<u32>& /*field*/) override {} +    void Visit(const Field<u64>& /*field*/) override {} +    void Visit(const Field<s8>& /*field*/) override {} +    void Visit(const Field<s16>& /*field*/) override {} +    void Visit(const Field<s32>& /*field*/) override {} +    void Visit(const Field<s64>& /*field*/) override {} +    void Visit(const Field<std::string>& /*field*/) override {} +    void Visit(const Field<const char*>& /*field*/) override {} +    void Visit(const Field<std::chrono::microseconds>& /*field*/) override {} + +    void Complete() override {} +}; + +} // namespace Telemetry diff --git a/src/common/vector_math.h b/src/common/vector_math.h index 7ca8e15f5..c7a461a1e 100644 --- a/src/common/vector_math.h +++ b/src/common/vector_math.h @@ -652,6 +652,16 @@ static inline decltype((X{} * int{} + X{} * int{}) / base) LerpInt(const X& begi      return (begin * (base - t) + end * t) / base;  } +// bilinear interpolation. s is for interpolating x00-x01 and x10-x11, and t is for the second +// interpolation. +template <typename X> +inline auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x11, const float s, +                           const float t) { +    auto y0 = Lerp(x00, x01, s); +    auto y1 = Lerp(x10, x11, s); +    return Lerp(y0, y1, t); +} +  // Utility vector factories  template <typename T>  static inline Vec2<T> MakeVec(const T& x, const T& y) { diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp index 2cb3ab9cc..62f17fbb5 100644 --- a/src/common/x64/cpu_detect.cpp +++ b/src/common/x64/cpu_detect.cpp @@ -6,7 +6,7 @@  #include <string>  #include <thread>  #include "common/common_types.h" -#include "cpu_detect.h" +#include "common/x64/cpu_detect.h"  #ifdef _MSC_VER  #include <intrin.h> diff --git a/src/core/3ds.h b/src/core/3ds.h new file mode 100644 index 000000000..8715e27db --- /dev/null +++ b/src/core/3ds.h @@ -0,0 +1,21 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace Core { + +// 3DS Video Constants +// ------------------- + +// NOTE: The LCDs actually rotate the image 90 degrees when displaying. Because of that the +// framebuffers in video memory are stored in column-major order and rendered sideways, causing +// the widths and heights of the framebuffers read by the LCD to be switched compared to the +// heights and widths of the screens listed here. +constexpr int kScreenTopWidth = 400;     ///< 3DS top screen width +constexpr int kScreenTopHeight = 240;    ///< 3DS top screen height +constexpr int kScreenBottomWidth = 320;  ///< 3DS bottom screen width +constexpr int kScreenBottomHeight = 240; ///< 3DS bottom screen height + +} // namespace Core diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c733e5d21..d66139c9c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -32,6 +32,7 @@ set(SRCS              frontend/camera/factory.cpp              frontend/camera/interface.cpp              frontend/emu_window.cpp +            frontend/framebuffer_layout.cpp              frontend/motion_emu.cpp              gdbstub/gdbstub.cpp              hle/config_mem.cpp @@ -44,6 +45,7 @@ set(SRCS              hle/kernel/client_port.cpp              hle/kernel/client_session.cpp              hle/kernel/event.cpp +            hle/kernel/handle_table.cpp              hle/kernel/kernel.cpp              hle/kernel/memory.cpp              hle/kernel/mutex.cpp @@ -56,6 +58,7 @@ set(SRCS              hle/kernel/thread.cpp              hle/kernel/timer.cpp              hle/kernel/vm_manager.cpp +            hle/kernel/wait_object.cpp              hle/service/ac/ac.cpp              hle/service/ac/ac_i.cpp              hle/service/ac/ac_u.cpp @@ -174,9 +177,11 @@ set(SRCS              memory.cpp              perf_stats.cpp              settings.cpp +            telemetry_session.cpp              )  set(HEADERS +            3ds.h              arm/arm_interface.h              arm/dynarmic/arm_dynarmic.h              arm/dynarmic/arm_dynarmic_cp15.h @@ -215,6 +220,7 @@ set(HEADERS              frontend/camera/factory.h              frontend/camera/interface.h              frontend/emu_window.h +            frontend/framebuffer_layout.h              frontend/input.h              frontend/motion_emu.h              gdbstub/gdbstub.h @@ -230,7 +236,9 @@ set(HEADERS              hle/kernel/address_arbiter.h              hle/kernel/client_port.h              hle/kernel/client_session.h +            hle/kernel/errors.h              hle/kernel/event.h +            hle/kernel/handle_table.h              hle/kernel/kernel.h              hle/kernel/memory.h              hle/kernel/mutex.h @@ -244,6 +252,7 @@ set(HEADERS              hle/kernel/thread.h              hle/kernel/timer.h              hle/kernel/vm_manager.h +            hle/kernel/wait_object.h              hle/result.h              hle/service/ac/ac.h              hle/service/ac/ac_i.h @@ -366,13 +375,10 @@ set(HEADERS              mmio.h              perf_stats.h              settings.h +            telemetry_session.h              ) -include_directories(../../externals/dynarmic/include) -include_directories(../../externals/cryptopp) -  create_directory_groups(${SRCS} ${HEADERS}) -  add_library(core STATIC ${SRCS} ${HEADERS}) - -target_link_libraries(core dynarmic cryptopp) +target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) +target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic) diff --git a/src/core/core.cpp b/src/core/core.cpp index 881f1e93c..450e7566d 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -132,6 +132,8 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {          cpu_core = std::make_unique<ARM_DynCom>(USER32MODE);      } +    telemetry_session = std::make_unique<Core::TelemetrySession>(); +      CoreTiming::Init();      HW::Init();      Kernel::Init(system_mode); @@ -162,6 +164,7 @@ void System::Shutdown() {      CoreTiming::Shutdown();      cpu_core = nullptr;      app_loader = nullptr; +    telemetry_session = nullptr;      LOG_DEBUG(Core, "Shutdown OK");  } diff --git a/src/core/core.h b/src/core/core.h index 6c9c936b5..6af772831 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -9,6 +9,7 @@  #include "common/common_types.h"  #include "core/memory.h"  #include "core/perf_stats.h" +#include "core/telemetry_session.h"  class EmuWindow;  class ARM_Interface; @@ -80,6 +81,14 @@ public:          return cpu_core != nullptr;      } +    /** +     * Returns a reference to the telemetry session for this emulation session. +     * @returns Reference to the telemetry session. +     */ +    Core::TelemetrySession& TelemetrySession() const { +        return *telemetry_session; +    } +      /// Prepare the core emulation for a reschedule      void PrepareReschedule(); @@ -117,6 +126,9 @@ private:      /// When true, signals that a reschedule should happen      bool reschedule_pending{}; +    /// Telemetry session for this emulation session +    std::unique_ptr<Core::TelemetrySession> telemetry_session; +      static System s_instance;  }; @@ -124,4 +136,8 @@ inline ARM_Interface& CPU() {      return System::GetInstance().CPU();  } +inline TelemetrySession& Telemetry() { +    return System::GetInstance().TelemetrySession(); +} +  } // namespace Core diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp index f454e7840..4867c9d17 100644 --- a/src/core/file_sys/archive_extsavedata.cpp +++ b/src/core/file_sys/archive_extsavedata.cpp @@ -38,8 +38,7 @@ public:      ResultVal<size_t> Write(u64 offset, size_t length, bool flush,                              const u8* buffer) const override {          if (offset > size) { -            return ResultCode(ErrorDescription::FS_WriteBeyondEnd, ErrorModule::FS, -                              ErrorSummary::InvalidArgument, ErrorLevel::Usage); +            return ERR_WRITE_BEYOND_END;          } else if (offset == size) {              return MakeResult<size_t>(0);          } @@ -191,11 +190,9 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(cons          // TODO(Subv): Verify the archive behavior of SharedExtSaveData compared to ExtSaveData.          // ExtSaveData seems to return FS_NotFound (120) when the archive doesn't exist.          if (!shared) { -            return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, -                              ErrorSummary::InvalidState, ErrorLevel::Status); +            return ERR_NOT_FOUND_INVALID_STATE;          } else { -            return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS, -                              ErrorSummary::InvalidState, ErrorLevel::Status); +            return ERR_NOT_FORMATTED;          }      }      auto archive = std::make_unique<ExtSaveDataArchive>(fullpath); @@ -230,8 +227,7 @@ ResultVal<ArchiveFormatInfo> ArchiveFactory_ExtSaveData::GetFormatInfo(const Pat      if (!file.IsOpen()) {          LOG_ERROR(Service_FS, "Could not open metadata information for archive");          // TODO(Subv): Verify error code -        return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS, -                          ErrorSummary::InvalidState, ErrorLevel::Status); +        return ERR_NOT_FORMATTED;      }      ArchiveFormatInfo info = {}; diff --git a/src/core/file_sys/archive_source_sd_savedata.cpp b/src/core/file_sys/archive_source_sd_savedata.cpp index f31a68038..a7e331724 100644 --- a/src/core/file_sys/archive_source_sd_savedata.cpp +++ b/src/core/file_sys/archive_source_sd_savedata.cpp @@ -6,6 +6,7 @@  #include "common/logging/log.h"  #include "common/string_util.h"  #include "core/file_sys/archive_source_sd_savedata.h" +#include "core/file_sys/errors.h"  #include "core/file_sys/savedata_archive.h"  #include "core/hle/service/fs/archive.h" @@ -49,8 +50,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveSource_SDSaveData::Open(u64 pr          // save file/directory structure expected by the game has not yet been initialized.          // Returning the NotFormatted error code will signal the game to provision the SaveData          // archive with the files and folders that it expects. -        return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS, -                          ErrorSummary::InvalidState, ErrorLevel::Status); +        return ERR_NOT_FORMATTED;      }      auto archive = std::make_unique<SaveDataArchive>(std::move(concrete_mount_point)); @@ -81,8 +81,7 @@ ResultVal<ArchiveFormatInfo> ArchiveSource_SDSaveData::GetFormatInfo(u64 program      if (!file.IsOpen()) {          LOG_ERROR(Service_FS, "Could not open metadata information for archive");          // TODO(Subv): Verify error code -        return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS, -                          ErrorSummary::InvalidState, ErrorLevel::Status); +        return ERR_NOT_FORMATTED;      }      ArchiveFormatInfo info = {}; diff --git a/src/core/file_sys/archive_systemsavedata.cpp b/src/core/file_sys/archive_systemsavedata.cpp index 8986b5c0e..81423bffd 100644 --- a/src/core/file_sys/archive_systemsavedata.cpp +++ b/src/core/file_sys/archive_systemsavedata.cpp @@ -9,6 +9,7 @@  #include "common/file_util.h"  #include "common/string_util.h"  #include "core/file_sys/archive_systemsavedata.h" +#include "core/file_sys/errors.h"  #include "core/file_sys/savedata_archive.h"  #include "core/hle/service/fs/archive.h" @@ -53,8 +54,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(c      std::string fullpath = GetSystemSaveDataPath(base_path, path);      if (!FileUtil::Exists(fullpath)) {          // TODO(Subv): Check error code, this one is probably wrong -        return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS, -                          ErrorSummary::InvalidState, ErrorLevel::Status); +        return ERR_NOT_FORMATTED;      }      auto archive = std::make_unique<SaveDataArchive>(fullpath);      return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp index a243d9a13..98d80aabc 100644 --- a/src/core/file_sys/disk_archive.cpp +++ b/src/core/file_sys/disk_archive.cpp @@ -9,6 +9,7 @@  #include "common/file_util.h"  #include "common/logging/log.h"  #include "core/file_sys/disk_archive.h" +#include "core/file_sys/errors.h"  ////////////////////////////////////////////////////////////////////////////////////////////////////  // FileSys namespace @@ -17,8 +18,7 @@ namespace FileSys {  ResultVal<size_t> DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const {      if (!mode.read_flag) -        return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, -                          ErrorSummary::Canceled, ErrorLevel::Status); +        return ERROR_INVALID_OPEN_FLAGS;      file->Seek(offset, SEEK_SET);      return MakeResult<size_t>(file->ReadBytes(buffer, length)); @@ -27,8 +27,7 @@ ResultVal<size_t> DiskFile::Read(const u64 offset, const size_t length, u8* buff  ResultVal<size_t> DiskFile::Write(const u64 offset, const size_t length, const bool flush,                                    const u8* buffer) const {      if (!mode.write_flag) -        return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, -                          ErrorSummary::Canceled, ErrorLevel::Status); +        return ERROR_INVALID_OPEN_FLAGS;      file->Seek(offset, SEEK_SET);      size_t written = file->WriteBytes(buffer, length); diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h index 9fc8d753b..a974bc775 100644 --- a/src/core/file_sys/errors.h +++ b/src/core/file_sys/errors.h @@ -2,52 +2,93 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#pragma once +  #include "core/hle/result.h"  namespace FileSys { -const ResultCode ERROR_INVALID_PATH(ErrorDescription::FS_InvalidPath, ErrorModule::FS, -                                    ErrorSummary::InvalidArgument, ErrorLevel::Usage); -const ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(ErrorDescription::FS_UnsupportedOpenFlags, -                                              ErrorModule::FS, ErrorSummary::NotSupported, -                                              ErrorLevel::Usage); -const ResultCode ERROR_INVALID_OPEN_FLAGS(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, -                                          ErrorSummary::Canceled, ErrorLevel::Status); -const ResultCode ERROR_INVALID_READ_FLAG(ErrorDescription::FS_InvalidReadFlag, ErrorModule::FS, -                                         ErrorSummary::InvalidArgument, ErrorLevel::Usage); -const ResultCode ERROR_FILE_NOT_FOUND(ErrorDescription::FS_FileNotFound, ErrorModule::FS, -                                      ErrorSummary::NotFound, ErrorLevel::Status); -const ResultCode ERROR_PATH_NOT_FOUND(ErrorDescription::FS_PathNotFound, ErrorModule::FS, -                                      ErrorSummary::NotFound, ErrorLevel::Status); -const ResultCode ERROR_NOT_FOUND(ErrorDescription::FS_NotFound, ErrorModule::FS, -                                 ErrorSummary::NotFound, ErrorLevel::Status); -const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY(ErrorDescription::FS_UnexpectedFileOrDirectory, -                                                    ErrorModule::FS, ErrorSummary::NotSupported, -                                                    ErrorLevel::Usage); -const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC(ErrorDescription::FS_NotAFile, -                                                         ErrorModule::FS, ErrorSummary::Canceled, -                                                         ErrorLevel::Status); -const ResultCode ERROR_DIRECTORY_ALREADY_EXISTS(ErrorDescription::FS_DirectoryAlreadyExists, -                                                ErrorModule::FS, ErrorSummary::NothingHappened, -                                                ErrorLevel::Status); -const ResultCode ERROR_FILE_ALREADY_EXISTS(ErrorDescription::FS_FileAlreadyExists, ErrorModule::FS, -                                           ErrorSummary::NothingHappened, ErrorLevel::Status); -const ResultCode ERROR_ALREADY_EXISTS(ErrorDescription::FS_AlreadyExists, ErrorModule::FS, -                                      ErrorSummary::NothingHappened, ErrorLevel::Status); -const ResultCode ERROR_DIRECTORY_NOT_EMPTY(ErrorDescription::FS_DirectoryNotEmpty, ErrorModule::FS, -                                           ErrorSummary::Canceled, ErrorLevel::Status); -const ResultCode ERROR_GAMECARD_NOT_INSERTED(ErrorDescription::FS_GameCardNotInserted, -                                             ErrorModule::FS, ErrorSummary::NotFound, -                                             ErrorLevel::Status); -const ResultCode ERROR_INCORRECT_EXEFS_READ_SIZE(ErrorDescription::FS_IncorrectExeFSReadSize, -                                                 ErrorModule::FS, ErrorSummary::NotSupported, -                                                 ErrorLevel::Usage); -const ResultCode ERROR_ROMFS_NOT_FOUND(ErrorDescription::FS_RomFSNotFound, ErrorModule::FS, -                                       ErrorSummary::NotFound, ErrorLevel::Status); -const ResultCode ERROR_COMMAND_NOT_ALLOWED(ErrorDescription::FS_CommandNotAllowed, ErrorModule::FS, -                                           ErrorSummary::WrongArgument, ErrorLevel::Permanent); -const ResultCode ERROR_EXEFS_SECTION_NOT_FOUND(ErrorDescription::FS_ExeFSSectionNotFound, -                                               ErrorModule::FS, ErrorSummary::NotFound, -                                               ErrorLevel::Status); +namespace ErrCodes { +enum { +    RomFSNotFound = 100, +    ArchiveNotMounted = 101, +    FileNotFound = 112, +    PathNotFound = 113, +    GameCardNotInserted = 141, +    NotFound = 120, +    FileAlreadyExists = 180, +    DirectoryAlreadyExists = 185, +    AlreadyExists = 190, +    InvalidOpenFlags = 230, +    DirectoryNotEmpty = 240, +    NotAFile = 250, +    NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive +    ExeFSSectionNotFound = 567, +    CommandNotAllowed = 630, +    InvalidReadFlag = 700, +    InvalidPath = 702, +    WriteBeyondEnd = 705, +    UnsupportedOpenFlags = 760, +    IncorrectExeFSReadSize = 761, +    UnexpectedFileOrDirectory = 770, +}; +} + +constexpr ResultCode ERROR_INVALID_PATH(ErrCodes::InvalidPath, ErrorModule::FS, +                                        ErrorSummary::InvalidArgument, ErrorLevel::Usage); +constexpr ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(ErrCodes::UnsupportedOpenFlags, ErrorModule::FS, +                                                  ErrorSummary::NotSupported, ErrorLevel::Usage); +constexpr ResultCode ERROR_INVALID_OPEN_FLAGS(ErrCodes::InvalidOpenFlags, ErrorModule::FS, +                                              ErrorSummary::Canceled, ErrorLevel::Status); +constexpr ResultCode ERROR_INVALID_READ_FLAG(ErrCodes::InvalidReadFlag, ErrorModule::FS, +                                             ErrorSummary::InvalidArgument, ErrorLevel::Usage); +constexpr ResultCode ERROR_FILE_NOT_FOUND(ErrCodes::FileNotFound, ErrorModule::FS, +                                          ErrorSummary::NotFound, ErrorLevel::Status); +constexpr ResultCode ERROR_PATH_NOT_FOUND(ErrCodes::PathNotFound, ErrorModule::FS, +                                          ErrorSummary::NotFound, ErrorLevel::Status); +constexpr ResultCode ERROR_NOT_FOUND(ErrCodes::NotFound, ErrorModule::FS, ErrorSummary::NotFound, +                                     ErrorLevel::Status); +constexpr ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY(ErrCodes::UnexpectedFileOrDirectory, +                                                        ErrorModule::FS, ErrorSummary::NotSupported, +                                                        ErrorLevel::Usage); +constexpr ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC(ErrCodes::NotAFile, ErrorModule::FS, +                                                             ErrorSummary::Canceled, +                                                             ErrorLevel::Status); +constexpr ResultCode ERROR_DIRECTORY_ALREADY_EXISTS(ErrCodes::DirectoryAlreadyExists, +                                                    ErrorModule::FS, ErrorSummary::NothingHappened, +                                                    ErrorLevel::Status); +constexpr ResultCode ERROR_FILE_ALREADY_EXISTS(ErrCodes::FileAlreadyExists, ErrorModule::FS, +                                               ErrorSummary::NothingHappened, ErrorLevel::Status); +constexpr ResultCode ERROR_ALREADY_EXISTS(ErrCodes::AlreadyExists, ErrorModule::FS, +                                          ErrorSummary::NothingHappened, ErrorLevel::Status); +constexpr ResultCode ERROR_DIRECTORY_NOT_EMPTY(ErrCodes::DirectoryNotEmpty, ErrorModule::FS, +                                               ErrorSummary::Canceled, ErrorLevel::Status); +constexpr ResultCode ERROR_GAMECARD_NOT_INSERTED(ErrCodes::GameCardNotInserted, ErrorModule::FS, +                                                 ErrorSummary::NotFound, ErrorLevel::Status); +constexpr ResultCode ERROR_INCORRECT_EXEFS_READ_SIZE(ErrCodes::IncorrectExeFSReadSize, +                                                     ErrorModule::FS, ErrorSummary::NotSupported, +                                                     ErrorLevel::Usage); +constexpr ResultCode ERROR_ROMFS_NOT_FOUND(ErrCodes::RomFSNotFound, ErrorModule::FS, +                                           ErrorSummary::NotFound, ErrorLevel::Status); +constexpr ResultCode ERROR_COMMAND_NOT_ALLOWED(ErrCodes::CommandNotAllowed, ErrorModule::FS, +                                               ErrorSummary::WrongArgument, ErrorLevel::Permanent); +constexpr ResultCode ERROR_EXEFS_SECTION_NOT_FOUND(ErrCodes::ExeFSSectionNotFound, ErrorModule::FS, +                                                   ErrorSummary::NotFound, ErrorLevel::Status); + +/// Returned when a function is passed an invalid archive handle. +constexpr ResultCode ERR_INVALID_ARCHIVE_HANDLE(ErrCodes::ArchiveNotMounted, ErrorModule::FS, +                                                ErrorSummary::NotFound, +                                                ErrorLevel::Status); // 0xC8804465 +constexpr ResultCode ERR_WRITE_BEYOND_END(ErrCodes::WriteBeyondEnd, ErrorModule::FS, +                                          ErrorSummary::InvalidArgument, ErrorLevel::Usage); + +/** + * Variant of ERROR_NOT_FOUND returned in some places in the code. Unknown if these usages are + * correct or a bug. + */ +constexpr ResultCode ERR_NOT_FOUND_INVALID_STATE(ErrCodes::NotFound, ErrorModule::FS, +                                                 ErrorSummary::InvalidState, ErrorLevel::Status); +constexpr ResultCode ERR_NOT_FORMATTED(ErrCodes::NotFormatted, ErrorModule::FS, +                                       ErrorSummary::InvalidState, ErrorLevel::Status);  } // namespace FileSys diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 5fdb3a7e8..4f7d54a33 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -5,10 +5,10 @@  #include <algorithm>  #include <cmath>  #include "common/assert.h" +#include "core/3ds.h"  #include "core/core.h"  #include "core/frontend/emu_window.h"  #include "core/settings.h" -#include "video_core/video_core.h"  /**   * Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout @@ -38,11 +38,9 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {      if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))          return; -    touch_x = VideoCore::kScreenBottomWidth * -              (framebuffer_x - framebuffer_layout.bottom_screen.left) / +    touch_x = Core::kScreenBottomWidth * (framebuffer_x - framebuffer_layout.bottom_screen.left) /                (framebuffer_layout.bottom_screen.right - framebuffer_layout.bottom_screen.left); -    touch_y = VideoCore::kScreenBottomHeight * -              (framebuffer_y - framebuffer_layout.bottom_screen.top) / +    touch_y = Core::kScreenBottomHeight * (framebuffer_y - framebuffer_layout.bottom_screen.top) /                (framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top);      touch_pressed = true; diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 36f2667fa..9414123a4 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -8,8 +8,8 @@  #include <tuple>  #include <utility>  #include "common/common_types.h" -#include "common/framebuffer_layout.h"  #include "common/math_util.h" +#include "core/frontend/framebuffer_layout.h"  /**   * Abstraction class used to provide an interface between emulation code and the frontend diff --git a/src/common/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index a2a0e7dad..d2d02f9ff 100644 --- a/src/common/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp @@ -5,16 +5,20 @@  #include <cmath>  #include "common/assert.h" -#include "common/framebuffer_layout.h" +#include "core/3ds.h" +#include "core/frontend/framebuffer_layout.h"  #include "core/settings.h" -#include "video_core/video_core.h"  namespace Layout {  static const float TOP_SCREEN_ASPECT_RATIO = -    static_cast<float>(VideoCore::kScreenTopHeight) / VideoCore::kScreenTopWidth; +    static_cast<float>(Core::kScreenTopHeight) / Core::kScreenTopWidth;  static const float BOT_SCREEN_ASPECT_RATIO = -    static_cast<float>(VideoCore::kScreenBottomHeight) / VideoCore::kScreenBottomWidth; +    static_cast<float>(Core::kScreenBottomHeight) / Core::kScreenBottomWidth; + +float FramebufferLayout::GetScalingRatio() const { +    return static_cast<float>(top_screen.GetWidth()) / Core::kScreenTopWidth; +}  // Finds the largest size subrectangle contained in window area that is confined to the aspect ratio  template <class T> @@ -106,10 +110,10 @@ FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool swapped      float window_aspect_ratio = static_cast<float>(height) / width;      float emulation_aspect_ratio =          swapped -            ? VideoCore::kScreenBottomHeight * 4 / -                  (VideoCore::kScreenBottomWidth * 4.0f + VideoCore::kScreenTopWidth) -            : VideoCore::kScreenTopHeight * 4 / -                  (VideoCore::kScreenTopWidth * 4.0f + VideoCore::kScreenBottomWidth); +            ? Core::kScreenBottomHeight * 4 / +                  (Core::kScreenBottomWidth * 4.0f + Core::kScreenTopWidth) +            : Core::kScreenTopHeight * 4 / +                  (Core::kScreenTopWidth * 4.0f + Core::kScreenBottomWidth);      float large_screen_aspect_ratio = swapped ? BOT_SCREEN_ASPECT_RATIO : TOP_SCREEN_ASPECT_RATIO;      float small_screen_aspect_ratio = swapped ? TOP_SCREEN_ASPECT_RATIO : BOT_SCREEN_ASPECT_RATIO; diff --git a/src/common/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h index f1df5c55a..9a7738969 100644 --- a/src/common/framebuffer_layout.h +++ b/src/core/frontend/framebuffer_layout.h @@ -5,7 +5,9 @@  #pragma once  #include "common/math_util.h" +  namespace Layout { +  /// Describes the layout of the window framebuffer (size and top/bottom screen positions)  struct FramebufferLayout {      unsigned width; @@ -14,6 +16,12 @@ struct FramebufferLayout {      bool bottom_screen_enabled;      MathUtil::Rectangle<unsigned> top_screen;      MathUtil::Rectangle<unsigned> bottom_screen; + +    /** +     * Returns the ration of pixel size of the top screen, compared to the native size of the 3DS +     * screen. +     */ +    float GetScalingRatio() const;  };  /** @@ -52,4 +60,5 @@ FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool is_swap   * @return Newly created FramebufferLayout object with default screen regions initialized   */  FramebufferLayout CustomFrameLayout(unsigned width, unsigned height); -} + +} // namespace Layout diff --git a/src/core/hle/applets/mii_selector.cpp b/src/core/hle/applets/mii_selector.cpp index 07c7f5b99..89f08daa2 100644 --- a/src/core/hle/applets/mii_selector.cpp +++ b/src/core/hle/applets/mii_selector.cpp @@ -11,7 +11,6 @@  #include "core/hle/kernel/kernel.h"  #include "core/hle/kernel/shared_memory.h"  #include "core/hle/result.h" -#include "video_core/video_core.h"  //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/hle/applets/swkbd.cpp b/src/core/hle/applets/swkbd.cpp index 059297fbc..fdf8807b0 100644 --- a/src/core/hle/applets/swkbd.cpp +++ b/src/core/hle/applets/swkbd.cpp @@ -14,7 +14,6 @@  #include "core/hle/service/gsp_gpu.h"  #include "core/hle/service/hid/hid.h"  #include "core/memory.h" -#include "video_core/video_core.h"  //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/hle/function_wrappers.h b/src/core/hle/function_wrappers.h index f6eb900f0..18b6e7017 100644 --- a/src/core/hle/function_wrappers.h +++ b/src/core/hle/function_wrappers.h @@ -53,7 +53,7 @@ void Wrap() {      FuncReturn(retval);  } -template <ResultCode func(u32*, s32, u32, u32, u32, s32)> +template <ResultCode func(u32*, u32, u32, u32, u32, s32)>  void Wrap() {      u32 param_1 = 0;      u32 retval = func(¶m_1, PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)).raw; diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h index 3a5d481a5..303ca090d 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h @@ -5,6 +5,7 @@  #pragma once  #include "common/common_types.h" +#include "core/hle/kernel/errors.h"  #include "core/hle/kernel/thread.h"  #include "core/memory.h" @@ -43,6 +44,12 @@ inline u32* GetStaticBuffers(const int offset = 0) {  namespace IPC { +// These errors are commonly returned by invalid IPC translations, so alias them here for +// convenience. +// TODO(yuriks): These will probably go away once translation is implemented inside the kernel. +using Kernel::ERR_INVALID_BUFFER_DESCRIPTOR; +constexpr auto ERR_INVALID_HANDLE = Kernel::ERR_INVALID_HANDLE_OS; +  enum DescriptorType : u32 {      // Buffer related desciptors types (mask : 0x0F)      StaticBuffer = 0x02, diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 06c4c5a85..d7348c09d 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -3,7 +3,9 @@  // Refer to the license.txt file included.  #pragma once +  #include "core/hle/ipc.h" +#include "core/hle/kernel/handle_table.h"  #include "core/hle/kernel/kernel.h"  namespace IPC { diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp index 01fab123e..776d342f0 100644 --- a/src/core/hle/kernel/address_arbiter.cpp +++ b/src/core/hle/kernel/address_arbiter.cpp @@ -5,6 +5,7 @@  #include "common/common_types.h"  #include "common/logging/log.h"  #include "core/hle/kernel/address_arbiter.h" +#include "core/hle/kernel/errors.h"  #include "core/hle/kernel/thread.h"  #include "core/memory.h" @@ -74,8 +75,7 @@ ResultCode AddressArbiter::ArbitrateAddress(ArbitrationType type, VAddr address,      default:          LOG_ERROR(Kernel, "unknown type=%d", type); -        return ResultCode(ErrorDescription::InvalidEnumValue, ErrorModule::Kernel, -                          ErrorSummary::WrongArgument, ErrorLevel::Usage); +        return ERR_INVALID_ENUM_VALUE_FND;      }      // The calls that use a timeout seem to always return a Timeout error even if they did not put @@ -83,8 +83,7 @@ ResultCode AddressArbiter::ArbitrateAddress(ArbitrationType type, VAddr address,      if (type == ArbitrationType::WaitIfLessThanWithTimeout ||          type == ArbitrationType::DecrementAndWaitIfLessThanWithTimeout) { -        return ResultCode(ErrorDescription::Timeout, ErrorModule::OS, ErrorSummary::StatusChanged, -                          ErrorLevel::Info); +        return RESULT_TIMEOUT;      }      return RESULT_SUCCESS;  } diff --git a/src/core/hle/kernel/address_arbiter.h b/src/core/hle/kernel/address_arbiter.h index 6a7af93a9..1d24401b1 100644 --- a/src/core/hle/kernel/address_arbiter.h +++ b/src/core/hle/kernel/address_arbiter.h @@ -6,6 +6,7 @@  #include "common/common_types.h"  #include "core/hle/kernel/kernel.h" +#include "core/hle/result.h"  // Address arbiters are an underlying kernel synchronization object that can be created/used via  // supervisor calls (SVCs). They function as sort of a global lock. Typically, games/other CTR diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp index ddcf4c916..03ffdece1 100644 --- a/src/core/hle/kernel/client_port.cpp +++ b/src/core/hle/kernel/client_port.cpp @@ -5,6 +5,7 @@  #include "common/assert.h"  #include "core/hle/kernel/client_port.h"  #include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/errors.h"  #include "core/hle/kernel/kernel.h"  #include "core/hle/kernel/server_port.h"  #include "core/hle/kernel/server_session.h" @@ -19,8 +20,7 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {      // AcceptSession before returning from this call.      if (active_sessions >= max_sessions) { -        return ResultCode(ErrorDescription::MaxConnectionsReached, ErrorModule::OS, -                          ErrorSummary::WouldBlock, ErrorLevel::Temporary); +        return ERR_MAX_CONNECTIONS_REACHED;      }      active_sessions++; diff --git a/src/core/hle/kernel/client_port.h b/src/core/hle/kernel/client_port.h index 511490c7c..8f7d6ac44 100644 --- a/src/core/hle/kernel/client_port.h +++ b/src/core/hle/kernel/client_port.h @@ -7,6 +7,7 @@  #include <string>  #include "common/common_types.h"  #include "core/hle/kernel/kernel.h" +#include "core/hle/result.h"  namespace Kernel { diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp index e297b7464..783b1c061 100644 --- a/src/core/hle/kernel/client_session.cpp +++ b/src/core/hle/kernel/client_session.cpp @@ -30,8 +30,7 @@ ResultCode ClientSession::SendSyncRequest() {      if (parent->server)          return parent->server->HandleSyncRequest(); -    return ResultCode(ErrorDescription::SessionClosedByRemote, ErrorModule::OS, -                      ErrorSummary::Canceled, ErrorLevel::Status); +    return ERR_SESSION_CLOSED_BY_REMOTE;  }  } // namespace diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h index 9f3adb72b..2de379c09 100644 --- a/src/core/hle/kernel/client_session.h +++ b/src/core/hle/kernel/client_session.h @@ -6,10 +6,9 @@  #include <memory>  #include <string> -  #include "common/common_types.h" -  #include "core/hle/kernel/kernel.h" +#include "core/hle/result.h"  namespace Kernel { diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h new file mode 100644 index 000000000..b3b60e7df --- /dev/null +++ b/src/core/hle/kernel/errors.h @@ -0,0 +1,98 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/result.h" + +namespace Kernel { + +namespace ErrCodes { +enum { +    OutOfHandles = 19, +    SessionClosedByRemote = 26, +    PortNameTooLong = 30, +    WrongPermission = 46, +    InvalidBufferDescriptor = 48, +    MaxConnectionsReached = 52, +}; +} + +// WARNING: The kernel is quite inconsistent in it's usage of errors code. Make sure to always +// double check that the code matches before re-using the constant. + +constexpr ResultCode ERR_OUT_OF_HANDLES(ErrCodes::OutOfHandles, ErrorModule::Kernel, +                                        ErrorSummary::OutOfResource, +                                        ErrorLevel::Permanent); // 0xD8600413 +constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE(ErrCodes::SessionClosedByRemote, ErrorModule::OS, +                                                  ErrorSummary::Canceled, +                                                  ErrorLevel::Status); // 0xC920181A +constexpr ResultCode ERR_PORT_NAME_TOO_LONG(ErrCodes::PortNameTooLong, ErrorModule::OS, +                                            ErrorSummary::InvalidArgument, +                                            ErrorLevel::Usage); // 0xE0E0181E +constexpr ResultCode ERR_WRONG_PERMISSION(ErrCodes::WrongPermission, ErrorModule::OS, +                                          ErrorSummary::WrongArgument, ErrorLevel::Permanent); +constexpr ResultCode ERR_INVALID_BUFFER_DESCRIPTOR(ErrCodes::InvalidBufferDescriptor, +                                                   ErrorModule::OS, ErrorSummary::WrongArgument, +                                                   ErrorLevel::Permanent); +constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED(ErrCodes::MaxConnectionsReached, ErrorModule::OS, +                                                 ErrorSummary::WouldBlock, +                                                 ErrorLevel::Temporary); // 0xD0401834 + +constexpr ResultCode ERR_NOT_AUTHORIZED(ErrorDescription::NotAuthorized, ErrorModule::OS, +                                        ErrorSummary::WrongArgument, +                                        ErrorLevel::Permanent); // 0xD9001BEA +constexpr ResultCode ERR_INVALID_ENUM_VALUE(ErrorDescription::InvalidEnumValue, ErrorModule::Kernel, +                                            ErrorSummary::InvalidArgument, +                                            ErrorLevel::Permanent); // 0xD8E007ED +constexpr ResultCode ERR_INVALID_ENUM_VALUE_FND(ErrorDescription::InvalidEnumValue, +                                                ErrorModule::FND, ErrorSummary::InvalidArgument, +                                                ErrorLevel::Permanent); // 0xD8E093ED +constexpr ResultCode ERR_INVALID_COMBINATION(ErrorDescription::InvalidCombination, ErrorModule::OS, +                                             ErrorSummary::InvalidArgument, +                                             ErrorLevel::Usage); // 0xE0E01BEE +constexpr ResultCode ERR_INVALID_COMBINATION_KERNEL(ErrorDescription::InvalidCombination, +                                                    ErrorModule::Kernel, +                                                    ErrorSummary::WrongArgument, +                                                    ErrorLevel::Permanent); // 0xD90007EE +constexpr ResultCode ERR_MISALIGNED_ADDRESS(ErrorDescription::MisalignedAddress, ErrorModule::OS, +                                            ErrorSummary::InvalidArgument, +                                            ErrorLevel::Usage); // 0xE0E01BF1 +constexpr ResultCode ERR_MISALIGNED_SIZE(ErrorDescription::MisalignedSize, ErrorModule::OS, +                                         ErrorSummary::InvalidArgument, +                                         ErrorLevel::Usage); // 0xE0E01BF2 +constexpr ResultCode ERR_OUT_OF_MEMORY(ErrorDescription::OutOfMemory, ErrorModule::Kernel, +                                       ErrorSummary::OutOfResource, +                                       ErrorLevel::Permanent); // 0xD86007F3 +constexpr ResultCode ERR_NOT_IMPLEMENTED(ErrorDescription::NotImplemented, ErrorModule::OS, +                                         ErrorSummary::InvalidArgument, +                                         ErrorLevel::Usage); // 0xE0E01BF4 +constexpr ResultCode ERR_INVALID_ADDRESS(ErrorDescription::InvalidAddress, ErrorModule::OS, +                                         ErrorSummary::InvalidArgument, +                                         ErrorLevel::Usage); // 0xE0E01BF5 +constexpr ResultCode ERR_INVALID_ADDRESS_STATE(ErrorDescription::InvalidAddress, ErrorModule::OS, +                                               ErrorSummary::InvalidState, +                                               ErrorLevel::Usage); // 0xE0A01BF5 +constexpr ResultCode ERR_INVALID_POINTER(ErrorDescription::InvalidPointer, ErrorModule::Kernel, +                                         ErrorSummary::InvalidArgument, +                                         ErrorLevel::Permanent); // 0xD8E007F6 +constexpr ResultCode ERR_INVALID_HANDLE(ErrorDescription::InvalidHandle, ErrorModule::Kernel, +                                        ErrorSummary::InvalidArgument, +                                        ErrorLevel::Permanent); // 0xD8E007F7 +/// Alternate code returned instead of ERR_INVALID_HANDLE in some code paths. +constexpr ResultCode ERR_INVALID_HANDLE_OS(ErrorDescription::InvalidHandle, ErrorModule::OS, +                                           ErrorSummary::WrongArgument, +                                           ErrorLevel::Permanent); // 0xD9001BF7 +constexpr ResultCode ERR_NOT_FOUND(ErrorDescription::NotFound, ErrorModule::Kernel, +                                   ErrorSummary::NotFound, ErrorLevel::Permanent); // 0xD88007FA +constexpr ResultCode ERR_OUT_OF_RANGE(ErrorDescription::OutOfRange, ErrorModule::OS, +                                      ErrorSummary::InvalidArgument, +                                      ErrorLevel::Usage); // 0xE0E01BFD +constexpr ResultCode ERR_OUT_OF_RANGE_KERNEL(ErrorDescription::OutOfRange, ErrorModule::Kernel, +                                             ErrorSummary::InvalidArgument, +                                             ErrorLevel::Permanent); // 0xD8E007FD +constexpr ResultCode RESULT_TIMEOUT(ErrorDescription::Timeout, ErrorModule::OS, +                                    ErrorSummary::StatusChanged, ErrorLevel::Info); + +} // namespace Kernel diff --git a/src/core/hle/kernel/event.h b/src/core/hle/kernel/event.h index 3e3673508..cc41abb85 100644 --- a/src/core/hle/kernel/event.h +++ b/src/core/hle/kernel/event.h @@ -6,6 +6,7 @@  #include "common/common_types.h"  #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/wait_object.h"  namespace Kernel { diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp new file mode 100644 index 000000000..c7322d883 --- /dev/null +++ b/src/core/hle/kernel/handle_table.cpp @@ -0,0 +1,97 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <utility> +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/thread.h" + +namespace Kernel { + +HandleTable g_handle_table; + +HandleTable::HandleTable() { +    next_generation = 1; +    Clear(); +} + +ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) { +    DEBUG_ASSERT(obj != nullptr); + +    u16 slot = next_free_slot; +    if (slot >= generations.size()) { +        LOG_ERROR(Kernel, "Unable to allocate Handle, too many slots in use."); +        return ERR_OUT_OF_HANDLES; +    } +    next_free_slot = generations[slot]; + +    u16 generation = next_generation++; + +    // Overflow count so it fits in the 15 bits dedicated to the generation in the handle. +    // CTR-OS doesn't use generation 0, so skip straight to 1. +    if (next_generation >= (1 << 15)) +        next_generation = 1; + +    generations[slot] = generation; +    objects[slot] = std::move(obj); + +    Handle handle = generation | (slot << 15); +    return MakeResult<Handle>(handle); +} + +ResultVal<Handle> HandleTable::Duplicate(Handle handle) { +    SharedPtr<Object> object = GetGeneric(handle); +    if (object == nullptr) { +        LOG_ERROR(Kernel, "Tried to duplicate invalid handle: %08X", handle); +        return ERR_INVALID_HANDLE; +    } +    return Create(std::move(object)); +} + +ResultCode HandleTable::Close(Handle handle) { +    if (!IsValid(handle)) +        return ERR_INVALID_HANDLE; + +    u16 slot = GetSlot(handle); + +    objects[slot] = nullptr; + +    generations[slot] = next_free_slot; +    next_free_slot = slot; +    return RESULT_SUCCESS; +} + +bool HandleTable::IsValid(Handle handle) const { +    size_t slot = GetSlot(handle); +    u16 generation = GetGeneration(handle); + +    return slot < MAX_COUNT && objects[slot] != nullptr && generations[slot] == generation; +} + +SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const { +    if (handle == CurrentThread) { +        return GetCurrentThread(); +    } else if (handle == CurrentProcess) { +        return g_current_process; +    } + +    if (!IsValid(handle)) { +        return nullptr; +    } +    return objects[GetSlot(handle)]; +} + +void HandleTable::Clear() { +    for (u16 i = 0; i < MAX_COUNT; ++i) { +        generations[i] = i + 1; +        objects[i] = nullptr; +    } +    next_free_slot = 0; +} + +} // namespace diff --git a/src/core/hle/kernel/handle_table.h b/src/core/hle/kernel/handle_table.h new file mode 100644 index 000000000..d6aaefbf7 --- /dev/null +++ b/src/core/hle/kernel/handle_table.h @@ -0,0 +1,126 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include "common/common_types.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/result.h" + +namespace Kernel { + +enum KernelHandle : Handle { +    CurrentThread = 0xFFFF8000, +    CurrentProcess = 0xFFFF8001, +}; + +/** + * This class allows the creation of Handles, which are references to objects that can be tested + * for validity and looked up. Here they are used to pass references to kernel objects to/from the + * emulated process. it has been designed so that it follows the same handle format and has + * approximately the same restrictions as the handle manager in the CTR-OS. + * + * Handles contain two sub-fields: a slot index (bits 31:15) and a generation value (bits 14:0). + * The slot index is used to index into the arrays in this class to access the data corresponding + * to the Handle. + * + * To prevent accidental use of a freed Handle whose slot has already been reused, a global counter + * is kept and incremented every time a Handle is created. This is the Handle's "generation". The + * value of the counter is stored into the Handle as well as in the handle table (in the + * "generations" array). When looking up a handle, the Handle's generation must match with the + * value stored on the class, otherwise the Handle is considered invalid. + * + * To find free slots when allocating a Handle without needing to scan the entire object array, the + * generations field of unallocated slots is re-purposed as a linked list of indices to free slots. + * When a Handle is created, an index is popped off the list and used for the new Handle. When it + * is destroyed, it is again pushed onto the list to be re-used by the next allocation. It is + * likely that this allocation strategy differs from the one used in CTR-OS, but this hasn't been + * verified and isn't likely to cause any problems. + */ +class HandleTable final : NonCopyable { +public: +    HandleTable(); + +    /** +     * Allocates a handle for the given object. +     * @return The created Handle or one of the following errors: +     *           - `ERR_OUT_OF_HANDLES`: the maximum number of handles has been exceeded. +     */ +    ResultVal<Handle> Create(SharedPtr<Object> obj); + +    /** +     * Returns a new handle that points to the same object as the passed in handle. +     * @return The duplicated Handle or one of the following errors: +     *           - `ERR_INVALID_HANDLE`: an invalid handle was passed in. +     *           - Any errors returned by `Create()`. +     */ +    ResultVal<Handle> Duplicate(Handle handle); + +    /** +     * Closes a handle, removing it from the table and decreasing the object's ref-count. +     * @return `RESULT_SUCCESS` or one of the following errors: +     *           - `ERR_INVALID_HANDLE`: an invalid handle was passed in. +     */ +    ResultCode Close(Handle handle); + +    /// Checks if a handle is valid and points to an existing object. +    bool IsValid(Handle handle) const; + +    /** +     * Looks up a handle. +     * @return Pointer to the looked-up object, or `nullptr` if the handle is not valid. +     */ +    SharedPtr<Object> GetGeneric(Handle handle) const; + +    /** +     * Looks up a handle while verifying its type. +     * @return Pointer to the looked-up object, or `nullptr` if the handle is not valid or its +     *         type differs from the requested one. +     */ +    template <class T> +    SharedPtr<T> Get(Handle handle) const { +        return DynamicObjectCast<T>(GetGeneric(handle)); +    } + +    /// Closes all handles held in this table. +    void Clear(); + +private: +    /** +     * This is the maximum limit of handles allowed per process in CTR-OS. It can be further +     * reduced by ExHeader values, but this is not emulated here. +     */ +    static const size_t MAX_COUNT = 4096; + +    static u16 GetSlot(Handle handle) { +        return handle >> 15; +    } +    static u16 GetGeneration(Handle handle) { +        return handle & 0x7FFF; +    } + +    /// Stores the Object referenced by the handle or null if the slot is empty. +    std::array<SharedPtr<Object>, MAX_COUNT> objects; + +    /** +     * The value of `next_generation` when the handle was created, used to check for validity. For +     * empty slots, contains the index of the next free slot in the list. +     */ +    std::array<u16, MAX_COUNT> generations; + +    /** +     * Global counter of the number of created handles. Stored in `generations` when a handle is +     * created, and wraps around to 1 when it hits 0x8000. +     */ +    u16 next_generation; + +    /// Head of the free slots linked list. +    u16 next_free_slot; +}; + +extern HandleTable g_handle_table; + +} // namespace diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index f599916f0..7470a97ca 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -2,10 +2,8 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. -#include <algorithm> -#include "common/assert.h" -#include "common/logging/log.h"  #include "core/hle/config_mem.h" +#include "core/hle/kernel/handle_table.h"  #include "core/hle/kernel/kernel.h"  #include "core/hle/kernel/memory.h"  #include "core/hle/kernel/process.h" @@ -17,165 +15,6 @@  namespace Kernel {  unsigned int Object::next_object_id; -HandleTable g_handle_table; - -void WaitObject::AddWaitingThread(SharedPtr<Thread> thread) { -    auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); -    if (itr == waiting_threads.end()) -        waiting_threads.push_back(std::move(thread)); -} - -void WaitObject::RemoveWaitingThread(Thread* thread) { -    auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); -    // If a thread passed multiple handles to the same object, -    // the kernel might attempt to remove the thread from the object's -    // waiting threads list multiple times. -    if (itr != waiting_threads.end()) -        waiting_threads.erase(itr); -} - -SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { -    Thread* candidate = nullptr; -    s32 candidate_priority = THREADPRIO_LOWEST + 1; - -    for (const auto& thread : waiting_threads) { -        // The list of waiting threads must not contain threads that are not waiting to be awakened. -        ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY || -                       thread->status == THREADSTATUS_WAIT_SYNCH_ALL, -                   "Inconsistent thread statuses in waiting_threads"); - -        if (thread->current_priority >= candidate_priority) -            continue; - -        if (ShouldWait(thread.get())) -            continue; - -        // A thread is ready to run if it's either in THREADSTATUS_WAIT_SYNCH_ANY or -        // in THREADSTATUS_WAIT_SYNCH_ALL and the rest of the objects it is waiting on are ready. -        bool ready_to_run = true; -        if (thread->status == THREADSTATUS_WAIT_SYNCH_ALL) { -            ready_to_run = std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), -                                        [&thread](const SharedPtr<WaitObject>& object) { -                                            return object->ShouldWait(thread.get()); -                                        }); -        } - -        if (ready_to_run) { -            candidate = thread.get(); -            candidate_priority = thread->current_priority; -        } -    } - -    return candidate; -} - -void WaitObject::WakeupAllWaitingThreads() { -    while (auto thread = GetHighestPriorityReadyThread()) { -        if (!thread->IsSleepingOnWaitAll()) { -            Acquire(thread.get()); -            // 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(thread.get()); -            } -            // Note: This case doesn't update the output index of WaitSynchronizationN. -        } - -        for (auto& object : thread->wait_objects) -            object->RemoveWaitingThread(thread.get()); -        thread->wait_objects.clear(); - -        thread->SetWaitSynchronizationResult(RESULT_SUCCESS); -        thread->ResumeFromWait(); -    } -} - -const std::vector<SharedPtr<Thread>>& WaitObject::GetWaitingThreads() const { -    return waiting_threads; -} - -HandleTable::HandleTable() { -    next_generation = 1; -    Clear(); -} - -ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) { -    DEBUG_ASSERT(obj != nullptr); - -    u16 slot = next_free_slot; -    if (slot >= generations.size()) { -        LOG_ERROR(Kernel, "Unable to allocate Handle, too many slots in use."); -        return ERR_OUT_OF_HANDLES; -    } -    next_free_slot = generations[slot]; - -    u16 generation = next_generation++; - -    // Overflow count so it fits in the 15 bits dedicated to the generation in the handle. -    // CTR-OS doesn't use generation 0, so skip straight to 1. -    if (next_generation >= (1 << 15)) -        next_generation = 1; - -    generations[slot] = generation; -    objects[slot] = std::move(obj); - -    Handle handle = generation | (slot << 15); -    return MakeResult<Handle>(handle); -} - -ResultVal<Handle> HandleTable::Duplicate(Handle handle) { -    SharedPtr<Object> object = GetGeneric(handle); -    if (object == nullptr) { -        LOG_ERROR(Kernel, "Tried to duplicate invalid handle: %08X", handle); -        return ERR_INVALID_HANDLE; -    } -    return Create(std::move(object)); -} - -ResultCode HandleTable::Close(Handle handle) { -    if (!IsValid(handle)) -        return ERR_INVALID_HANDLE; - -    u16 slot = GetSlot(handle); - -    objects[slot] = nullptr; - -    generations[slot] = next_free_slot; -    next_free_slot = slot; -    return RESULT_SUCCESS; -} - -bool HandleTable::IsValid(Handle handle) const { -    size_t slot = GetSlot(handle); -    u16 generation = GetGeneration(handle); - -    return slot < MAX_COUNT && objects[slot] != nullptr && generations[slot] == generation; -} - -SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const { -    if (handle == CurrentThread) { -        return GetCurrentThread(); -    } else if (handle == CurrentProcess) { -        return g_current_process; -    } - -    if (!IsValid(handle)) { -        return nullptr; -    } -    return objects[GetSlot(handle)]; -} - -void HandleTable::Clear() { -    for (u16 i = 0; i < MAX_COUNT; ++i) { -        generations[i] = i + 1; -        objects[i] = nullptr; -    } -    next_free_slot = 0; -}  /// Initialize the kernel  void Init(u32 system_mode) { diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index bb8b99bb5..9cf288b08 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -4,33 +4,16 @@  #pragma once -#include <algorithm> -#include <array>  #include <cstddef>  #include <string> -#include <vector> +#include <utility>  #include <boost/smart_ptr/intrusive_ptr.hpp>  #include "common/common_types.h" -#include "core/hle/result.h"  namespace Kernel {  using Handle = u32; -class Thread; - -// TODO: Verify code -const ResultCode ERR_OUT_OF_HANDLES(ErrorDescription::OutOfMemory, ErrorModule::Kernel, -                                    ErrorSummary::OutOfResource, ErrorLevel::Temporary); -// TOOD: Verify code -const ResultCode ERR_INVALID_HANDLE(ErrorDescription::InvalidHandle, ErrorModule::Kernel, -                                    ErrorSummary::InvalidArgument, ErrorLevel::Permanent); - -enum KernelHandle : Handle { -    CurrentThread = 0xFFFF8000, -    CurrentProcess = 0xFFFF8001, -}; -  enum class HandleType : u32 {      Unknown,      Event, @@ -128,170 +111,17 @@ inline void intrusive_ptr_release(Object* object) {  template <typename T>  using SharedPtr = boost::intrusive_ptr<T>; -/// Class that represents a Kernel object that a thread can be waiting on -class WaitObject : public Object { -public: -    /** -     * Check if the specified thread should wait until the object is available -     * @param thread The thread about which we're deciding. -     * @return True if the current thread should wait due to this object being unavailable -     */ -    virtual bool ShouldWait(Thread* thread) const = 0; - -    /// Acquire/lock the object for the specified thread if it is available -    virtual void Acquire(Thread* thread) = 0; - -    /** -     * Add a thread to wait on this object -     * @param thread Pointer to thread to add -     */ -    virtual void AddWaitingThread(SharedPtr<Thread> thread); - -    /** -     * Removes a thread from waiting on this object (e.g. if it was resumed already) -     * @param thread Pointer to thread to remove -     */ -    virtual void RemoveWaitingThread(Thread* thread); - -    /** -     * 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. -     */ -    virtual 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; - -private: -    /// Threads waiting for this object to become available -    std::vector<SharedPtr<Thread>> waiting_threads; -}; -  /** - * This class allows the creation of Handles, which are references to objects that can be tested - * for validity and looked up. Here they are used to pass references to kernel objects to/from the - * emulated process. it has been designed so that it follows the same handle format and has - * approximately the same restrictions as the handle manager in the CTR-OS. - * - * Handles contain two sub-fields: a slot index (bits 31:15) and a generation value (bits 14:0). - * The slot index is used to index into the arrays in this class to access the data corresponding - * to the Handle. - * - * To prevent accidental use of a freed Handle whose slot has already been reused, a global counter - * is kept and incremented every time a Handle is created. This is the Handle's "generation". The - * value of the counter is stored into the Handle as well as in the handle table (in the - * "generations" array). When looking up a handle, the Handle's generation must match with the - * value stored on the class, otherwise the Handle is considered invalid. - * - * To find free slots when allocating a Handle without needing to scan the entire object array, the - * generations field of unallocated slots is re-purposed as a linked list of indices to free slots. - * When a Handle is created, an index is popped off the list and used for the new Handle. When it - * is destroyed, it is again pushed onto the list to be re-used by the next allocation. It is - * likely that this allocation strategy differs from the one used in CTR-OS, but this hasn't been - * verified and isn't likely to cause any problems. + * Attempts to downcast the given Object pointer to a pointer to T. + * @return Derived pointer to the object, or `nullptr` if `object` isn't of type T.   */ -class HandleTable final : NonCopyable { -public: -    HandleTable(); - -    /** -     * Allocates a handle for the given object. -     * @return The created Handle or one of the following errors: -     *           - `ERR_OUT_OF_HANDLES`: the maximum number of handles has been exceeded. -     */ -    ResultVal<Handle> Create(SharedPtr<Object> obj); - -    /** -     * Returns a new handle that points to the same object as the passed in handle. -     * @return The duplicated Handle or one of the following errors: -     *           - `ERR_INVALID_HANDLE`: an invalid handle was passed in. -     *           - Any errors returned by `Create()`. -     */ -    ResultVal<Handle> Duplicate(Handle handle); - -    /** -     * Closes a handle, removing it from the table and decreasing the object's ref-count. -     * @return `RESULT_SUCCESS` or one of the following errors: -     *           - `ERR_INVALID_HANDLE`: an invalid handle was passed in. -     */ -    ResultCode Close(Handle handle); - -    /// Checks if a handle is valid and points to an existing object. -    bool IsValid(Handle handle) const; - -    /** -     * Looks up a handle. -     * @return Pointer to the looked-up object, or `nullptr` if the handle is not valid. -     */ -    SharedPtr<Object> GetGeneric(Handle handle) const; - -    /** -     * Looks up a handle while verifying its type. -     * @return Pointer to the looked-up object, or `nullptr` if the handle is not valid or its -     *         type differs from the handle type `T::HANDLE_TYPE`. -     */ -    template <class T> -    SharedPtr<T> Get(Handle handle) const { -        SharedPtr<Object> object = GetGeneric(handle); -        if (object != nullptr && object->GetHandleType() == T::HANDLE_TYPE) { -            return boost::static_pointer_cast<T>(std::move(object)); -        } -        return nullptr; -    } - -    /** -     * Looks up a handle while verifying that it is an object that a thread can wait on -     * @return Pointer to the looked-up object, or `nullptr` if the handle is not valid or it is -     *         not a waitable object. -     */ -    SharedPtr<WaitObject> GetWaitObject(Handle handle) const { -        SharedPtr<Object> object = GetGeneric(handle); -        if (object != nullptr && object->IsWaitable()) { -            return boost::static_pointer_cast<WaitObject>(std::move(object)); -        } -        return nullptr; -    } - -    /// Closes all handles held in this table. -    void Clear(); - -private: -    /** -     * This is the maximum limit of handles allowed per process in CTR-OS. It can be further -     * reduced by ExHeader values, but this is not emulated here. -     */ -    static const size_t MAX_COUNT = 4096; - -    static u16 GetSlot(Handle handle) { -        return handle >> 15; -    } -    static u16 GetGeneration(Handle handle) { -        return handle & 0x7FFF; +template <typename T> +inline SharedPtr<T> DynamicObjectCast(SharedPtr<Object> object) { +    if (object != nullptr && object->GetHandleType() == T::HANDLE_TYPE) { +        return boost::static_pointer_cast<T>(std::move(object));      } - -    /// Stores the Object referenced by the handle or null if the slot is empty. -    std::array<SharedPtr<Object>, MAX_COUNT> objects; - -    /** -     * The value of `next_generation` when the handle was created, used to check for validity. For -     * empty slots, contains the index of the next free slot in the list. -     */ -    std::array<u16, MAX_COUNT> generations; - -    /** -     * Global counter of the number of created handles. Stored in `generations` when a handle is -     * created, and wraps around to 1 when it hits 0x8000. -     */ -    u16 next_generation; - -    /// Head of the free slots linked list. -    u16 next_free_slot; -}; - -extern HandleTable g_handle_table; +    return nullptr; +}  /// Initialize the kernel with the specified system mode.  void Init(u32 system_mode); diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp index 8250a90b5..804f23b1c 100644 --- a/src/core/hle/kernel/memory.cpp +++ b/src/core/hle/kernel/memory.cpp @@ -2,6 +2,7 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <algorithm>  #include <cinttypes>  #include <map>  #include <memory> diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h index c57adf400..bacacd690 100644 --- a/src/core/hle/kernel/mutex.h +++ b/src/core/hle/kernel/mutex.h @@ -7,6 +7,7 @@  #include <string>  #include "common/common_types.h"  #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/wait_object.h"  namespace Kernel { diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 32cb25fb7..1c31ec950 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -6,6 +6,7 @@  #include "common/assert.h"  #include "common/common_funcs.h"  #include "common/logging/log.h" +#include "core/hle/kernel/errors.h"  #include "core/hle/kernel/memory.h"  #include "core/hle/kernel/process.h"  #include "core/hle/kernel/resource_limit.h" diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp index 3f51bc5de..a8f10a3ee 100644 --- a/src/core/hle/kernel/resource_limit.cpp +++ b/src/core/hle/kernel/resource_limit.cpp @@ -3,6 +3,7 @@  // Refer to the license.txt file included.  #include <cstring> +#include "common/assert.h"  #include "common/logging/log.h"  #include "core/hle/kernel/resource_limit.h" diff --git a/src/core/hle/kernel/semaphore.cpp b/src/core/hle/kernel/semaphore.cpp index 8bda2f75d..fcf586728 100644 --- a/src/core/hle/kernel/semaphore.cpp +++ b/src/core/hle/kernel/semaphore.cpp @@ -3,6 +3,7 @@  // Refer to the license.txt file included.  #include "common/assert.h" +#include "core/hle/kernel/errors.h"  #include "core/hle/kernel/kernel.h"  #include "core/hle/kernel/semaphore.h"  #include "core/hle/kernel/thread.h" @@ -16,8 +17,7 @@ ResultVal<SharedPtr<Semaphore>> Semaphore::Create(s32 initial_count, s32 max_cou                                                    std::string name) {      if (initial_count > max_count) -        return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::Kernel, -                          ErrorSummary::WrongArgument, ErrorLevel::Permanent); +        return ERR_INVALID_COMBINATION_KERNEL;      SharedPtr<Semaphore> semaphore(new Semaphore); @@ -42,8 +42,7 @@ void Semaphore::Acquire(Thread* thread) {  ResultVal<s32> Semaphore::Release(s32 release_count) {      if (max_count - available_count < release_count) -        return ResultCode(ErrorDescription::OutOfRange, ErrorModule::Kernel, -                          ErrorSummary::InvalidArgument, ErrorLevel::Permanent); +        return ERR_OUT_OF_RANGE_KERNEL;      s32 previous_count = available_count;      available_count += release_count; diff --git a/src/core/hle/kernel/semaphore.h b/src/core/hle/kernel/semaphore.h index cde94f7cc..7b0cacf2e 100644 --- a/src/core/hle/kernel/semaphore.h +++ b/src/core/hle/kernel/semaphore.h @@ -8,6 +8,8 @@  #include <string>  #include "common/common_types.h"  #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/wait_object.h" +#include "core/hle/result.h"  namespace Kernel { diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h index 6f8bdb6a9..2a24d8412 100644 --- a/src/core/hle/kernel/server_port.h +++ b/src/core/hle/kernel/server_port.h @@ -9,6 +9,7 @@  #include <tuple>  #include "common/common_types.h"  #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/wait_object.h"  namespace Service {  class SessionRequestHandler; diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index c907d487c..f1b76d8aa 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -10,7 +10,7 @@  #include "common/common_types.h"  #include "core/hle/kernel/kernel.h"  #include "core/hle/kernel/session.h" -#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/wait_object.h"  #include "core/hle/result.h"  #include "core/hle/service/service.h"  #include "core/memory.h" @@ -20,6 +20,7 @@ namespace Kernel {  class ClientSession;  class ClientPort;  class ServerSession; +class Thread;  /**   * Kernel object representing the server endpoint of an IPC session. Sessions are the basic CTR-OS diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp index bc1560d12..922e5ab58 100644 --- a/src/core/hle/kernel/shared_memory.cpp +++ b/src/core/hle/kernel/shared_memory.cpp @@ -4,6 +4,7 @@  #include <cstring>  #include "common/logging/log.h" +#include "core/hle/kernel/errors.h"  #include "core/hle/kernel/memory.h"  #include "core/hle/kernel/shared_memory.h"  #include "core/memory.h" @@ -102,24 +103,21 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi      // Automatically allocated memory blocks can only be mapped with other_permissions = DontCare      if (base_address == 0 && other_permissions != MemoryPermission::DontCare) { -        return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, -                          ErrorSummary::InvalidArgument, ErrorLevel::Usage); +        return ERR_INVALID_COMBINATION;      }      // Error out if the requested permissions don't match what the creator process allows.      if (static_cast<u32>(permissions) & ~static_cast<u32>(own_other_permissions)) {          LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s, permissions don't match",                    GetObjectId(), address, name.c_str()); -        return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, -                          ErrorSummary::InvalidArgument, ErrorLevel::Usage); +        return ERR_INVALID_COMBINATION;      }      // Heap-backed memory blocks can not be mapped with other_permissions = DontCare      if (base_address != 0 && other_permissions == MemoryPermission::DontCare) {          LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s, permissions don't match",                    GetObjectId(), address, name.c_str()); -        return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, -                          ErrorSummary::InvalidArgument, ErrorLevel::Usage); +        return ERR_INVALID_COMBINATION;      }      // Error out if the provided permissions are not compatible with what the creator process needs. @@ -127,8 +125,7 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi          static_cast<u32>(this->permissions) & ~static_cast<u32>(other_permissions)) {          LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s, permissions don't match",                    GetObjectId(), address, name.c_str()); -        return ResultCode(ErrorDescription::WrongPermission, ErrorModule::OS, -                          ErrorSummary::WrongArgument, ErrorLevel::Permanent); +        return ERR_WRONG_PERMISSION;      }      // TODO(Subv): Check for the Shared Device Mem flag in the creator process. @@ -144,8 +141,7 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi          if (address < Memory::HEAP_VADDR || address + size >= Memory::SHARED_MEMORY_VADDR_END) {              LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s, invalid address",                        GetObjectId(), address, name.c_str()); -            return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, -                              ErrorSummary::InvalidArgument, ErrorLevel::Usage); +            return ERR_INVALID_ADDRESS;          }      } diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 3b7555d87..75ce626f8 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -14,6 +14,8 @@  #include "core/arm/skyeye_common/armstate.h"  #include "core/core.h"  #include "core/core_timing.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/handle_table.h"  #include "core/hle/kernel/kernel.h"  #include "core/hle/kernel/memory.h"  #include "core/hle/kernel/mutex.h" @@ -241,9 +243,7 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {          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)); +        thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);      }      thread->ResumeFromWait(); @@ -351,10 +351,20 @@ static void ResetThreadContext(ARM_Interface::ThreadContext& context, u32 stack_      context.cpsr = USER32MODE | ((entry_point & 1) << 5); // Usermode and THUMB mode  } -ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, s32 priority, +ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, u32 priority,                                              u32 arg, s32 processor_id, VAddr stack_top) { -    ASSERT_MSG(priority >= THREADPRIO_HIGHEST && priority <= THREADPRIO_LOWEST, -               "Invalid thread priority"); +    // Check if priority is in ranged. Lowest priority -> highest priority id. +    if (priority > THREADPRIO_LOWEST) { +        LOG_ERROR(Kernel_SVC, "Invalid thread priority: %d", priority); +        return ERR_OUT_OF_RANGE; +    } + +    if (processor_id > THREADPROCESSORID_MAX) { +        LOG_ERROR(Kernel_SVC, "Invalid processor id: %d", processor_id); +        return ERR_OUT_OF_RANGE_KERNEL; +    } + +    // TODO(yuriks): Other checks, returning 0xD9001BEA      if (!Memory::IsValidVirtualAddress(entry_point)) {          LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %08x", name.c_str(), entry_point); @@ -399,8 +409,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,          if (linheap_memory->size() + Memory::PAGE_SIZE > memory_region->size) {              LOG_ERROR(Kernel_SVC,                        "Not enough space in region to allocate a new TLS page for thread"); -            return ResultCode(ErrorDescription::OutOfMemory, ErrorModule::Kernel, -                              ErrorSummary::OutOfResource, ErrorLevel::Permanent); +            return ERR_OUT_OF_MEMORY;          }          u32 offset = linheap_memory->size(); diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 6ab31c70b..6a3566f15 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -12,6 +12,7 @@  #include "common/common_types.h"  #include "core/arm/arm_interface.h"  #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/wait_object.h"  #include "core/hle/result.h"  enum ThreadPriority : s32 { @@ -57,7 +58,7 @@ public:       * @param stack_top The address of the thread's stack top       * @return A shared pointer to the newly created thread       */ -    static ResultVal<SharedPtr<Thread>> Create(std::string name, VAddr entry_point, s32 priority, +    static ResultVal<SharedPtr<Thread>> Create(std::string name, VAddr entry_point, u32 priority,                                                 u32 arg, s32 processor_id, VAddr stack_top);      std::string GetName() const override { diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp index a00c75679..6f2cf3b02 100644 --- a/src/core/hle/kernel/timer.cpp +++ b/src/core/hle/kernel/timer.cpp @@ -6,6 +6,7 @@  #include "common/assert.h"  #include "common/logging/log.h"  #include "core/core_timing.h" +#include "core/hle/kernel/handle_table.h"  #include "core/hle/kernel/kernel.h"  #include "core/hle/kernel/thread.h"  #include "core/hle/kernel/timer.h" diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h index b0f818933..82552372d 100644 --- a/src/core/hle/kernel/timer.h +++ b/src/core/hle/kernel/timer.h @@ -6,6 +6,7 @@  #include "common/common_types.h"  #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/wait_object.h"  namespace Kernel { diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 6dd24f846..cef1f7fa8 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -4,6 +4,7 @@  #include <iterator>  #include "common/assert.h" +#include "core/hle/kernel/errors.h"  #include "core/hle/kernel/vm_manager.h"  #include "core/memory.h"  #include "core/memory_setup.h" diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index 9055664b2..38e0d74d0 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -13,14 +13,6 @@  namespace Kernel { -const ResultCode ERR_INVALID_ADDRESS{// 0xE0E01BF5 -                                     ErrorDescription::InvalidAddress, ErrorModule::OS, -                                     ErrorSummary::InvalidArgument, ErrorLevel::Usage}; - -const ResultCode ERR_INVALID_ADDRESS_STATE{// 0xE0A01BF5 -                                           ErrorDescription::InvalidAddress, ErrorModule::OS, -                                           ErrorSummary::InvalidState, ErrorLevel::Usage}; -  enum class VMAType : u8 {      /// VMA represents an unmapped region of the address space.      Free, diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp new file mode 100644 index 000000000..f245eda6c --- /dev/null +++ b/src/core/hle/kernel/wait_object.cpp @@ -0,0 +1,99 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/hle/config_mem.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/memory.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/resource_limit.h" +#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/timer.h" +#include "core/hle/shared_page.h" + +namespace Kernel { + +void WaitObject::AddWaitingThread(SharedPtr<Thread> thread) { +    auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); +    if (itr == waiting_threads.end()) +        waiting_threads.push_back(std::move(thread)); +} + +void WaitObject::RemoveWaitingThread(Thread* thread) { +    auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); +    // If a thread passed multiple handles to the same object, +    // the kernel might attempt to remove the thread from the object's +    // waiting threads list multiple times. +    if (itr != waiting_threads.end()) +        waiting_threads.erase(itr); +} + +SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { +    Thread* candidate = nullptr; +    s32 candidate_priority = THREADPRIO_LOWEST + 1; + +    for (const auto& thread : waiting_threads) { +        // The list of waiting threads must not contain threads that are not waiting to be awakened. +        ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY || +                       thread->status == THREADSTATUS_WAIT_SYNCH_ALL, +                   "Inconsistent thread statuses in waiting_threads"); + +        if (thread->current_priority >= candidate_priority) +            continue; + +        if (ShouldWait(thread.get())) +            continue; + +        // A thread is ready to run if it's either in THREADSTATUS_WAIT_SYNCH_ANY or +        // in THREADSTATUS_WAIT_SYNCH_ALL and the rest of the objects it is waiting on are ready. +        bool ready_to_run = true; +        if (thread->status == THREADSTATUS_WAIT_SYNCH_ALL) { +            ready_to_run = std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), +                                        [&thread](const SharedPtr<WaitObject>& object) { +                                            return object->ShouldWait(thread.get()); +                                        }); +        } + +        if (ready_to_run) { +            candidate = thread.get(); +            candidate_priority = thread->current_priority; +        } +    } + +    return candidate; +} + +void WaitObject::WakeupAllWaitingThreads() { +    while (auto thread = GetHighestPriorityReadyThread()) { +        if (!thread->IsSleepingOnWaitAll()) { +            Acquire(thread.get()); +            // 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(thread.get()); +            } +            // Note: This case doesn't update the output index of WaitSynchronizationN. +        } + +        for (auto& object : thread->wait_objects) +            object->RemoveWaitingThread(thread.get()); +        thread->wait_objects.clear(); + +        thread->SetWaitSynchronizationResult(RESULT_SUCCESS); +        thread->ResumeFromWait(); +    } +} + +const std::vector<SharedPtr<Thread>>& WaitObject::GetWaitingThreads() const { +    return waiting_threads; +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/wait_object.h b/src/core/hle/kernel/wait_object.h new file mode 100644 index 000000000..861578186 --- /dev/null +++ b/src/core/hle/kernel/wait_object.h @@ -0,0 +1,67 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include <boost/smart_ptr/intrusive_ptr.hpp> +#include "common/common_types.h" +#include "core/hle/kernel/kernel.h" + +namespace Kernel { + +class Thread; + +/// Class that represents a Kernel object that a thread can be waiting on +class WaitObject : public Object { +public: +    /** +     * Check if the specified thread should wait until the object is available +     * @param thread The thread about which we're deciding. +     * @return True if the current thread should wait due to this object being unavailable +     */ +    virtual bool ShouldWait(Thread* thread) const = 0; + +    /// Acquire/lock the object for the specified thread if it is available +    virtual void Acquire(Thread* thread) = 0; + +    /** +     * Add a thread to wait on this object +     * @param thread Pointer to thread to add +     */ +    virtual void AddWaitingThread(SharedPtr<Thread> thread); + +    /** +     * Removes a thread from waiting on this object (e.g. if it was resumed already) +     * @param thread Pointer to thread to remove +     */ +    virtual void RemoveWaitingThread(Thread* thread); + +    /** +     * 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. +     */ +    virtual 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; + +private: +    /// Threads waiting for this object to become available +    std::vector<SharedPtr<Thread>> waiting_threads; +}; + +// Specialization of DynamicObjectCast for WaitObjects +template <> +inline SharedPtr<WaitObject> DynamicObjectCast<WaitObject>(SharedPtr<Object> object) { +    if (object != nullptr && object->IsWaitable()) { +        return boost::static_pointer_cast<WaitObject>(std::move(object)); +    } +    return nullptr; +} + +} // namespace Kernel diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 13b948871..c49650f7d 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -13,38 +13,15 @@  // All the constants in this file come from http://3dbrew.org/wiki/Error_codes -/// Detailed description of the error. This listing is likely incomplete. +/** + * Detailed description of the error. Code 0 always means success. Codes 1000 and above are + * considered "well-known" and have common values between all modules. The meaning of other codes + * vary by module. + */  enum class ErrorDescription : u32 {      Success = 0, -    SessionClosedByRemote = 26, -    WrongPermission = 46, -    OS_InvalidBufferDescriptor = 48, -    MaxConnectionsReached = 52, -    WrongAddress = 53, -    FS_RomFSNotFound = 100, -    FS_ArchiveNotMounted = 101, -    FS_FileNotFound = 112, -    FS_PathNotFound = 113, -    FS_GameCardNotInserted = 141, -    FS_NotFound = 120, -    FS_FileAlreadyExists = 180, -    FS_DirectoryAlreadyExists = 185, -    FS_AlreadyExists = 190, -    FS_InvalidOpenFlags = 230, -    FS_DirectoryNotEmpty = 240, -    FS_NotAFile = 250, -    FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive -    OutofRangeOrMisalignedAddress = -        513, // TODO(purpasmart): Check if this name fits its actual usage -    GPU_FirstInitialization = 519, -    FS_ExeFSSectionNotFound = 567, -    FS_CommandNotAllowed = 630, -    FS_InvalidReadFlag = 700, -    FS_InvalidPath = 702, -    FS_WriteBeyondEnd = 705, -    FS_UnsupportedOpenFlags = 760, -    FS_IncorrectExeFSReadSize = 761, -    FS_UnexpectedFileOrDirectory = 770, + +    // Codes 1000 and above are considered "well-known" and have common values between all modules.      InvalidSection = 1000,      TooLarge = 1001,      NotAuthorized = 1002, @@ -218,7 +195,7 @@ enum class ErrorLevel : u32 {  union ResultCode {      u32 raw; -    BitField<0, 10, ErrorDescription> description; +    BitField<0, 10, u32> description;      BitField<10, 8, ErrorModule> module;      BitField<21, 6, ErrorSummary> summary; @@ -228,45 +205,46 @@ union ResultCode {      // error      BitField<31, 1, u32> is_error; -    explicit ResultCode(u32 raw) : raw(raw) {} -    ResultCode(ErrorDescription description_, ErrorModule module_, ErrorSummary summary_, -               ErrorLevel level_) -        : raw(0) { -        description.Assign(description_); -        module.Assign(module_); -        summary.Assign(summary_); -        level.Assign(level_); -    } +    constexpr explicit ResultCode(u32 raw) : raw(raw) {} + +    constexpr ResultCode(ErrorDescription description, ErrorModule module, ErrorSummary summary, +                         ErrorLevel level) +        : ResultCode(static_cast<u32>(description), module, summary, level) {} + +    constexpr ResultCode(u32 description_, ErrorModule module_, ErrorSummary summary_, +                         ErrorLevel level_) +        : raw(description.FormatValue(description_) | module.FormatValue(module_) | +              summary.FormatValue(summary_) | level.FormatValue(level_)) {} -    ResultCode& operator=(const ResultCode& o) { +    constexpr ResultCode& operator=(const ResultCode& o) {          raw = o.raw;          return *this;      } -    bool IsSuccess() const { -        return is_error == 0; +    constexpr bool IsSuccess() const { +        return is_error.ExtractValue(raw) == 0;      } -    bool IsError() const { -        return is_error == 1; +    constexpr bool IsError() const { +        return is_error.ExtractValue(raw) == 1;      }  }; -inline bool operator==(const ResultCode& a, const ResultCode& b) { +constexpr bool operator==(const ResultCode& a, const ResultCode& b) {      return a.raw == b.raw;  } -inline bool operator!=(const ResultCode& a, const ResultCode& b) { +constexpr bool operator!=(const ResultCode& a, const ResultCode& b) {      return a.raw != b.raw;  }  // Convenience functions for creating some common kinds of errors:  /// The default success `ResultCode`. -const ResultCode RESULT_SUCCESS(0); +constexpr ResultCode RESULT_SUCCESS(0);  /// Might be returned instead of a dummy success for unimplemented APIs. -inline ResultCode UnimplementedFunction(ErrorModule module) { +constexpr ResultCode UnimplementedFunction(ErrorModule module) {      return ResultCode(ErrorDescription::NotImplemented, module, ErrorSummary::NotSupported,                        ErrorLevel::Permanent);  } diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index e63b61450..ee80926d2 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -4,6 +4,8 @@  #pragma once +#include <vector> +#include "common/common_funcs.h"  #include "common/common_types.h"  #include "common/swap.h"  #include "core/hle/kernel/kernel.h" diff --git a/src/core/hle/service/boss/boss.cpp b/src/core/hle/service/boss/boss.cpp index e0de037f8..91056189a 100644 --- a/src/core/hle/service/boss/boss.cpp +++ b/src/core/hle/service/boss/boss.cpp @@ -24,9 +24,7 @@ void InitializeSession(Service::Interface* self) {      if (translation != IPC::CallingPidDesc()) {          cmd_buff[0] = IPC::MakeHeader(0, 0x1, 0); // 0x40 -        cmd_buff[1] = ResultCode(ErrorDescription::OS_InvalidBufferDescriptor, ErrorModule::OS, -                                 ErrorSummary::WrongArgument, ErrorLevel::Permanent) -                          .raw; +        cmd_buff[1] = IPC::ERR_INVALID_BUFFER_DESCRIPTOR.raw;          LOG_ERROR(Service_BOSS, "The translation was invalid, translation=0x%08X", translation);          return;      } diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 8c8c1ec77..caa41ded7 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -11,6 +11,7 @@  #include "common/string_util.h"  #include "common/swap.h"  #include "core/file_sys/archive_systemsavedata.h" +#include "core/file_sys/errors.h"  #include "core/file_sys/file_backend.h"  #include "core/hle/result.h"  #include "core/hle/service/cfg/cfg.h" @@ -411,7 +412,7 @@ ResultCode UpdateConfigNANDSavegame() {  ResultCode FormatConfig() {      ResultCode res = DeleteConfigNANDSaveFile();      // The delete command fails if the file doesn't exist, so we have to check that too -    if (!res.IsSuccess() && res.description != ErrorDescription::FS_FileNotFound) { +    if (!res.IsSuccess() && res != FileSys::ERROR_FILE_NOT_FOUND) {          return res;      }      // Delete the old data @@ -534,7 +535,7 @@ ResultCode LoadConfigNANDSaveFile() {          Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SystemSaveData, archive_path);      // If the archive didn't exist, create the files inside -    if (archive_result.Code().description == ErrorDescription::FS_NotFormatted) { +    if (archive_result.Code() == FileSys::ERR_NOT_FORMATTED) {          // Format the archive to create the directories          Service::FS::FormatArchive(Service::FS::ArchiveIdCode::SystemSaveData,                                     FileSys::ArchiveFormatInfo(), archive_path); diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp index 39711ea97..79171a0bc 100644 --- a/src/core/hle/service/dsp_dsp.cpp +++ b/src/core/hle/service/dsp_dsp.cpp @@ -289,9 +289,7 @@ static void WriteProcessPipe(Service::Interface* self) {                                 "size=0x%X, buffer=0x%08X",                    cmd_buff[3], pipe_index, size, buffer);          cmd_buff[0] = IPC::MakeHeader(0, 1, 0); -        cmd_buff[1] = ResultCode(ErrorDescription::OS_InvalidBufferDescriptor, ErrorModule::OS, -                                 ErrorSummary::WrongArgument, ErrorLevel::Permanent) -                          .raw; +        cmd_buff[1] = IPC::ERR_INVALID_BUFFER_DESCRIPTOR.raw;          return;      } diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index 6cddc1fdb..632712f2c 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -22,6 +22,7 @@  #include "core/file_sys/archive_sdmcwriteonly.h"  #include "core/file_sys/archive_systemsavedata.h"  #include "core/file_sys/directory_backend.h" +#include "core/file_sys/errors.h"  #include "core/file_sys/file_backend.h"  #include "core/hle/kernel/client_session.h"  #include "core/hle/result.h" @@ -50,16 +51,6 @@ static constexpr Kernel::Handle INVALID_HANDLE{};  namespace Service {  namespace FS { -// TODO: Verify code -/// Returned when a function is passed an invalid handle. -const ResultCode ERR_INVALID_HANDLE(ErrorDescription::InvalidHandle, ErrorModule::FS, -                                    ErrorSummary::InvalidArgument, ErrorLevel::Permanent); - -/// Returned when a function is passed an invalid archive handle. -const ResultCode ERR_INVALID_ARCHIVE_HANDLE(ErrorDescription::FS_ArchiveNotMounted, ErrorModule::FS, -                                            ErrorSummary::NotFound, -                                            ErrorLevel::Status); // 0xC8804465 -  // Command to access archive file  enum class FileCommand : u32 {      Dummy1 = 0x000100C6, @@ -284,7 +275,7 @@ ResultVal<ArchiveHandle> OpenArchive(ArchiveIdCode id_code, FileSys::Path& archi  ResultCode CloseArchive(ArchiveHandle handle) {      if (handle_map.erase(handle) == 0) -        return ERR_INVALID_ARCHIVE_HANDLE; +        return FileSys::ERR_INVALID_ARCHIVE_HANDLE;      else          return RESULT_SUCCESS;  } @@ -309,7 +300,7 @@ ResultVal<std::shared_ptr<File>> OpenFileFromArchive(ArchiveHandle archive_handl                                                       const FileSys::Mode mode) {      ArchiveBackend* archive = GetArchive(archive_handle);      if (archive == nullptr) -        return ERR_INVALID_ARCHIVE_HANDLE; +        return FileSys::ERR_INVALID_ARCHIVE_HANDLE;      auto backend = archive->OpenFile(path, mode);      if (backend.Failed()) @@ -322,7 +313,7 @@ ResultVal<std::shared_ptr<File>> OpenFileFromArchive(ArchiveHandle archive_handl  ResultCode DeleteFileFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) {      ArchiveBackend* archive = GetArchive(archive_handle);      if (archive == nullptr) -        return ERR_INVALID_ARCHIVE_HANDLE; +        return FileSys::ERR_INVALID_ARCHIVE_HANDLE;      return archive->DeleteFile(path);  } @@ -334,7 +325,7 @@ ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle,      ArchiveBackend* src_archive = GetArchive(src_archive_handle);      ArchiveBackend* dest_archive = GetArchive(dest_archive_handle);      if (src_archive == nullptr || dest_archive == nullptr) -        return ERR_INVALID_ARCHIVE_HANDLE; +        return FileSys::ERR_INVALID_ARCHIVE_HANDLE;      if (src_archive == dest_archive) {          return src_archive->RenameFile(src_path, dest_path); @@ -347,7 +338,7 @@ ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle,  ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) {      ArchiveBackend* archive = GetArchive(archive_handle);      if (archive == nullptr) -        return ERR_INVALID_ARCHIVE_HANDLE; +        return FileSys::ERR_INVALID_ARCHIVE_HANDLE;      return archive->DeleteDirectory(path);  } @@ -356,7 +347,7 @@ ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle,                                                   const FileSys::Path& path) {      ArchiveBackend* archive = GetArchive(archive_handle);      if (archive == nullptr) -        return ERR_INVALID_ARCHIVE_HANDLE; +        return FileSys::ERR_INVALID_ARCHIVE_HANDLE;      return archive->DeleteDirectoryRecursively(path);  } @@ -365,7 +356,7 @@ ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path                                 u64 file_size) {      ArchiveBackend* archive = GetArchive(archive_handle);      if (archive == nullptr) -        return ERR_INVALID_ARCHIVE_HANDLE; +        return FileSys::ERR_INVALID_ARCHIVE_HANDLE;      return archive->CreateFile(path, file_size);  } @@ -373,7 +364,7 @@ ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path  ResultCode CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) {      ArchiveBackend* archive = GetArchive(archive_handle);      if (archive == nullptr) -        return ERR_INVALID_ARCHIVE_HANDLE; +        return FileSys::ERR_INVALID_ARCHIVE_HANDLE;      return archive->CreateDirectory(path);  } @@ -385,7 +376,7 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle,      ArchiveBackend* src_archive = GetArchive(src_archive_handle);      ArchiveBackend* dest_archive = GetArchive(dest_archive_handle);      if (src_archive == nullptr || dest_archive == nullptr) -        return ERR_INVALID_ARCHIVE_HANDLE; +        return FileSys::ERR_INVALID_ARCHIVE_HANDLE;      if (src_archive == dest_archive) {          return src_archive->RenameDirectory(src_path, dest_path); @@ -399,7 +390,7 @@ ResultVal<std::shared_ptr<Directory>> OpenDirectoryFromArchive(ArchiveHandle arc                                                                 const FileSys::Path& path) {      ArchiveBackend* archive = GetArchive(archive_handle);      if (archive == nullptr) -        return ERR_INVALID_ARCHIVE_HANDLE; +        return FileSys::ERR_INVALID_ARCHIVE_HANDLE;      auto backend = archive->OpenDirectory(path);      if (backend.Failed()) @@ -412,7 +403,7 @@ ResultVal<std::shared_ptr<Directory>> OpenDirectoryFromArchive(ArchiveHandle arc  ResultVal<u64> GetFreeBytesInArchive(ArchiveHandle archive_handle) {      ArchiveBackend* archive = GetArchive(archive_handle);      if (archive == nullptr) -        return ERR_INVALID_ARCHIVE_HANDLE; +        return FileSys::ERR_INVALID_ARCHIVE_HANDLE;      return MakeResult<u64>(archive->GetFreeBytes());  } diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index 33b290699..e53a970d3 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/file_sys/errors.h"  #include "core/hle/kernel/client_session.h"  #include "core/hle/result.h"  #include "core/hle/service/fs/archive.h" @@ -539,9 +540,7 @@ static void FormatSaveData(Service::Interface* self) {      if (archive_id != FS::ArchiveIdCode::SaveData) {          LOG_ERROR(Service_FS, "tried to format an archive different than SaveData, %u",                    static_cast<u32>(archive_id)); -        cmd_buff[1] = ResultCode(ErrorDescription::FS_InvalidPath, ErrorModule::FS, -                                 ErrorSummary::InvalidArgument, ErrorLevel::Usage) -                          .raw; +        cmd_buff[1] = FileSys::ERROR_INVALID_PATH.raw;          return;      } @@ -802,9 +801,7 @@ static void InitializeWithSdkVersion(Service::Interface* self) {          cmd_buff[1] = RESULT_SUCCESS.raw;      } else {          LOG_ERROR(Service_FS, "ProcessId Header must be 0x20"); -        cmd_buff[1] = ResultCode(ErrorDescription::OS_InvalidBufferDescriptor, ErrorModule::OS, -                                 ErrorSummary::WrongArgument, ErrorLevel::Permanent) -                          .raw; +        cmd_buff[1] = IPC::ERR_INVALID_BUFFER_DESCRIPTOR.raw;      }  } diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index a960778a7..94f6b8a9c 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -8,11 +8,11 @@  #include "core/hle/kernel/event.h"  #include "core/hle/kernel/shared_memory.h"  #include "core/hle/result.h" +#include "core/hle/service/gsp_gpu.h"  #include "core/hw/gpu.h"  #include "core/hw/hw.h"  #include "core/hw/lcd.h"  #include "core/memory.h" -#include "gsp_gpu.h"  #include "video_core/debug_utils/debug_utils.h"  #include "video_core/gpu_debugger.h" @@ -25,13 +25,24 @@ namespace GSP {  // Beginning address of HW regs  const u32 REGS_BEGIN = 0x1EB00000; -const ResultCode ERR_GSP_REGS_OUTOFRANGE_OR_MISALIGNED( -    ErrorDescription::OutofRangeOrMisalignedAddress, ErrorModule::GX, ErrorSummary::InvalidArgument, -    ErrorLevel::Usage); // 0xE0E02A01 -const ResultCode ERR_GSP_REGS_MISALIGNED(ErrorDescription::MisalignedSize, ErrorModule::GX, +namespace ErrCodes { +enum { +    // TODO(purpasmart): Check if this name fits its actual usage +    OutofRangeOrMisalignedAddress = 513, +    FirstInitialization = 519, +}; +} + +constexpr ResultCode RESULT_FIRST_INITIALIZATION(ErrCodes::FirstInitialization, ErrorModule::GX, +                                                 ErrorSummary::Success, ErrorLevel::Success); +constexpr ResultCode ERR_REGS_OUTOFRANGE_OR_MISALIGNED(ErrCodes::OutofRangeOrMisalignedAddress, +                                                       ErrorModule::GX, +                                                       ErrorSummary::InvalidArgument, +                                                       ErrorLevel::Usage); // 0xE0E02A01 +constexpr ResultCode ERR_REGS_MISALIGNED(ErrorDescription::MisalignedSize, ErrorModule::GX,                                           ErrorSummary::InvalidArgument,                                           ErrorLevel::Usage); // 0xE0E02BF2 -const ResultCode ERR_GSP_REGS_INVALID_SIZE(ErrorDescription::InvalidSize, ErrorModule::GX, +constexpr ResultCode ERR_REGS_INVALID_SIZE(ErrorDescription::InvalidSize, ErrorModule::GX,                                             ErrorSummary::InvalidArgument,                                             ErrorLevel::Usage); // 0xE0E02BEC @@ -93,11 +104,11 @@ static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, VAddr data_va          LOG_ERROR(Service_GSP,                    "Write address was out of range or misaligned! (address=0x%08x, size=0x%08x)",                    base_address, size_in_bytes); -        return ERR_GSP_REGS_OUTOFRANGE_OR_MISALIGNED; +        return ERR_REGS_OUTOFRANGE_OR_MISALIGNED;      } else if (size_in_bytes <= max_size_in_bytes) {          if (size_in_bytes & 3) {              LOG_ERROR(Service_GSP, "Misaligned size 0x%08x", size_in_bytes); -            return ERR_GSP_REGS_MISALIGNED; +            return ERR_REGS_MISALIGNED;          } else {              while (size_in_bytes > 0) {                  WriteSingleHWReg(base_address, Memory::Read32(data_vaddr)); @@ -111,7 +122,7 @@ static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, VAddr data_va      } else {          LOG_ERROR(Service_GSP, "Out of range size 0x%08x", size_in_bytes); -        return ERR_GSP_REGS_INVALID_SIZE; +        return ERR_REGS_INVALID_SIZE;      }  } @@ -134,11 +145,11 @@ static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, VAddr          LOG_ERROR(Service_GSP,                    "Write address was out of range or misaligned! (address=0x%08x, size=0x%08x)",                    base_address, size_in_bytes); -        return ERR_GSP_REGS_OUTOFRANGE_OR_MISALIGNED; +        return ERR_REGS_OUTOFRANGE_OR_MISALIGNED;      } else if (size_in_bytes <= max_size_in_bytes) {          if (size_in_bytes & 3) {              LOG_ERROR(Service_GSP, "Misaligned size 0x%08x", size_in_bytes); -            return ERR_GSP_REGS_MISALIGNED; +            return ERR_REGS_MISALIGNED;          } else {              while (size_in_bytes > 0) {                  const u32 reg_address = base_address + REGS_BEGIN; @@ -164,7 +175,7 @@ static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, VAddr      } else {          LOG_ERROR(Service_GSP, "Out of range size 0x%08x", size_in_bytes); -        return ERR_GSP_REGS_INVALID_SIZE; +        return ERR_REGS_INVALID_SIZE;      }  } @@ -372,9 +383,7 @@ static void RegisterInterruptRelayQueue(Interface* self) {      if (first_initialization) {          // This specific code is required for a successful initialization, rather than 0          first_initialization = false; -        cmd_buff[1] = ResultCode(ErrorDescription::GPU_FirstInitialization, ErrorModule::GX, -                                 ErrorSummary::Success, ErrorLevel::Success) -                          .raw; +        cmd_buff[1] = RESULT_FIRST_INITIALIZATION.raw;      } else {          cmd_buff[1] = RESULT_SUCCESS.raw;      } diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp index 226af0083..369115f09 100644 --- a/src/core/hle/service/ir/ir_user.cpp +++ b/src/core/hle/service/ir/ir_user.cpp @@ -267,8 +267,7 @@ static void InitializeIrNopShared(Interface* self) {      shared_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(handle);      if (!shared_memory) {          LOG_CRITICAL(Service_IR, "invalid shared memory handle 0x%08X", handle); -        rb.Push(ResultCode(ErrorDescription::InvalidHandle, ErrorModule::OS, -                           ErrorSummary::WrongArgument, ErrorLevel::Permanent)); +        rb.Push(IPC::ERR_INVALID_HANDLE);          return;      }      shared_memory->name = "IR_USER: shared memory"; diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index 319e8c946..39382ef09 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp @@ -3,6 +3,7 @@  // Refer to the license.txt file included.  #include "common/logging/log.h" +#include "core/file_sys/errors.h"  #include "core/file_sys/file_backend.h"  #include "core/hle/service/fs/archive.h"  #include "core/hle/service/ptm/ptm.h" @@ -134,7 +135,7 @@ void Init() {      auto archive_result =          Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path);      // If the archive didn't exist, create the files inside -    if (archive_result.Code().description == ErrorDescription::FS_NotFormatted) { +    if (archive_result.Code() == FileSys::ERR_NOT_FORMATTED) {          // Format the archive to create the directories          Service::FS::FormatArchive(Service::FS::ArchiveIdCode::SharedExtSaveData,                                     FileSys::ArchiveFormatInfo(), archive_path); diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index e6a5f1417..ffabc24a4 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -12,7 +12,6 @@  #include "core/hle/ipc.h"  #include "core/hle/ipc_helpers.h"  #include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/thread.h"  #include "core/hle/result.h"  #include "core/memory.h" diff --git a/src/core/hle/service/srv.cpp b/src/core/hle/service/srv.cpp index 3bd787147..130c9d25e 100644 --- a/src/core/hle/service/srv.cpp +++ b/src/core/hle/service/srv.cpp @@ -30,9 +30,7 @@ static void RegisterClient(Interface* self) {      if (cmd_buff[1] != IPC::CallingPidDesc()) {          cmd_buff[0] = IPC::MakeHeader(0x0, 0x1, 0); // 0x40 -        cmd_buff[1] = ResultCode(ErrorDescription::OS_InvalidBufferDescriptor, ErrorModule::OS, -                                 ErrorSummary::WrongArgument, ErrorLevel::Permanent) -                          .raw; +        cmd_buff[1] = IPC::ERR_INVALID_BUFFER_DESCRIPTOR.raw;          return;      }      cmd_buff[0] = IPC::MakeHeader(0x1, 0x1, 0); // 0x10040 diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index 8538cfc9d..e68b9f16a 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -2,6 +2,7 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <algorithm>  #include <cinttypes>  #include <map>  #include "common/logging/log.h" @@ -14,7 +15,9 @@  #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/errors.h"  #include "core/hle/kernel/event.h" +#include "core/hle/kernel/handle_table.h"  #include "core/hle/kernel/memory.h"  #include "core/hle/kernel/mutex.h"  #include "core/hle/kernel/process.h" @@ -26,6 +29,7 @@  #include "core/hle/kernel/thread.h"  #include "core/hle/kernel/timer.h"  #include "core/hle/kernel/vm_manager.h" +#include "core/hle/kernel/wait_object.h"  #include "core/hle/result.h"  #include "core/hle/service/service.h" @@ -37,25 +41,6 @@ using Kernel::ERR_INVALID_HANDLE;  namespace SVC { -const ResultCode ERR_NOT_FOUND(ErrorDescription::NotFound, ErrorModule::Kernel, -                               ErrorSummary::NotFound, ErrorLevel::Permanent); // 0xD88007FA -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}; -const ResultCode ERR_MISALIGNED_SIZE{// 0xE0E01BF2 -                                     ErrorDescription::MisalignedSize, ErrorModule::OS, -                                     ErrorSummary::InvalidArgument, ErrorLevel::Usage}; -const ResultCode ERR_INVALID_COMBINATION{// 0xE0E01BEE -                                         ErrorDescription::InvalidCombination, ErrorModule::OS, -                                         ErrorSummary::InvalidArgument, ErrorLevel::Usage}; -  enum ControlMemoryOperation {      MEMOP_FREE = 1,      MEMOP_RESERVE = 2, // This operation seems to be unsupported in the kernel @@ -195,8 +180,7 @@ static ResultCode MapMemoryBlock(Kernel::Handle handle, u32 addr, u32 permission          LOG_ERROR(Kernel_SVC, "unknown permissions=0x%08X", permissions);      } -    return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, -                      ErrorSummary::InvalidArgument, ErrorLevel::Usage); +    return Kernel::ERR_INVALID_COMBINATION;  }  static ResultCode UnmapMemoryBlock(Kernel::Handle handle, u32 addr) { @@ -216,16 +200,16 @@ static ResultCode UnmapMemoryBlock(Kernel::Handle handle, u32 addr) {  /// Connect to an OS service given the port name, returns the handle to the port to out  static ResultCode ConnectToPort(Kernel::Handle* out_handle, const char* port_name) {      if (port_name == nullptr) -        return ERR_NOT_FOUND; +        return Kernel::ERR_NOT_FOUND;      if (std::strlen(port_name) > 11) -        return ERR_PORT_NAME_TOO_LONG; +        return Kernel::ERR_PORT_NAME_TOO_LONG;      LOG_TRACE(Kernel_SVC, "called port_name=%s", port_name);      auto it = Service::g_kernel_named_ports.find(port_name);      if (it == Service::g_kernel_named_ports.end()) {          LOG_WARNING(Kernel_SVC, "tried to connect to unknown port: %s", port_name); -        return ERR_NOT_FOUND; +        return Kernel::ERR_NOT_FOUND;      }      auto client_port = it->second; @@ -263,7 +247,7 @@ static ResultCode CloseHandle(Kernel::Handle handle) {  /// Wait for a handle to synchronize, timeout after the specified nanoseconds  static ResultCode WaitSynchronization1(Kernel::Handle handle, s64 nano_seconds) { -    auto object = Kernel::g_handle_table.GetWaitObject(handle); +    auto object = Kernel::g_handle_table.Get<Kernel::WaitObject>(handle);      Kernel::Thread* thread = Kernel::GetCurrentThread();      if (object == nullptr) @@ -275,7 +259,7 @@ static ResultCode WaitSynchronization1(Kernel::Handle handle, s64 nano_seconds)      if (object->ShouldWait(thread)) {          if (nano_seconds == 0) -            return ERR_SYNC_TIMEOUT; +            return Kernel::RESULT_TIMEOUT;          thread->wait_objects = {object};          object->AddWaitingThread(thread); @@ -289,7 +273,7 @@ static ResultCode WaitSynchronization1(Kernel::Handle handle, s64 nano_seconds)          // 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; +        return Kernel::RESULT_TIMEOUT;      }      object->Acquire(thread); @@ -304,8 +288,7 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha      // Check if 'handles' is invalid      if (handles == nullptr) -        return ResultCode(ErrorDescription::InvalidPointer, ErrorModule::Kernel, -                          ErrorSummary::InvalidArgument, ErrorLevel::Permanent); +        return Kernel::ERR_INVALID_POINTER;      // NOTE: on real hardware, there is no nullptr check for 'out' (tested with firmware 4.4). If      // this happens, the running application will crash. @@ -313,14 +296,13 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha      // Check if 'handle_count' is invalid      if (handle_count < 0) -        return ResultCode(ErrorDescription::OutOfRange, ErrorModule::OS, -                          ErrorSummary::InvalidArgument, ErrorLevel::Usage); +        return Kernel::ERR_OUT_OF_RANGE;      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]); +        auto object = Kernel::g_handle_table.Get<Kernel::WaitObject>(handles[i]);          if (object == nullptr)              return ERR_INVALID_HANDLE;          objects[i] = object; @@ -344,7 +326,7 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha          // 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; +            return Kernel::RESULT_TIMEOUT;          // Put the thread to sleep          thread->status = THREADSTATUS_WAIT_SYNCH_ALL; @@ -365,7 +347,7 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha          *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; +        return Kernel::RESULT_TIMEOUT;      } else {          // Find the first object that is acquirable in the provided list of objects          auto itr = std::find_if(objects.begin(), objects.end(), [thread](const ObjectPtr& object) { @@ -385,7 +367,7 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha          // 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; +            return Kernel::RESULT_TIMEOUT;          // Put the thread to sleep          thread->status = THREADSTATUS_WAIT_SYNCH_ANY; @@ -411,7 +393,7 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha          // 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; +        return Kernel::RESULT_TIMEOUT;      }  } @@ -520,22 +502,20 @@ static ResultCode GetResourceLimitLimitValues(s64* values, Kernel::Handle resour  }  /// Creates a new thread -static ResultCode CreateThread(Kernel::Handle* out_handle, s32 priority, u32 entry_point, u32 arg, +static ResultCode CreateThread(Kernel::Handle* out_handle, u32 priority, u32 entry_point, u32 arg,                                 u32 stack_top, s32 processor_id) {      using Kernel::Thread;      std::string name = Common::StringFromFormat("unknown-%08" PRIX32, entry_point);      if (priority > THREADPRIO_LOWEST) { -        return ResultCode(ErrorDescription::OutOfRange, ErrorModule::OS, -                          ErrorSummary::InvalidArgument, ErrorLevel::Usage); +        return Kernel::ERR_OUT_OF_RANGE;      }      using Kernel::ResourceLimit;      Kernel::SharedPtr<ResourceLimit>& resource_limit = Kernel::g_current_process->resource_limit;      if (resource_limit->GetMaxResourceValue(Kernel::ResourceTypes::PRIORITY) > priority) { -        return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::OS, -                          ErrorSummary::WrongArgument, ErrorLevel::Permanent); +        return Kernel::ERR_NOT_AUTHORIZED;      }      switch (processor_id) { @@ -605,8 +585,7 @@ static ResultCode GetThreadPriority(s32* priority, Kernel::Handle handle) {  /// Sets the priority for the specified thread  static ResultCode SetThreadPriority(Kernel::Handle handle, s32 priority) {      if (priority > THREADPRIO_LOWEST) { -        return ResultCode(ErrorDescription::OutOfRange, ErrorModule::OS, -                          ErrorSummary::InvalidArgument, ErrorLevel::Usage); +        return Kernel::ERR_OUT_OF_RANGE;      }      SharedPtr<Kernel::Thread> thread = Kernel::g_handle_table.Get<Kernel::Thread>(handle); @@ -618,8 +597,7 @@ static ResultCode SetThreadPriority(Kernel::Handle handle, s32 priority) {      // the one from the thread owner's resource limit.      Kernel::SharedPtr<ResourceLimit>& resource_limit = Kernel::g_current_process->resource_limit;      if (resource_limit->GetMaxResourceValue(Kernel::ResourceTypes::PRIORITY) > priority) { -        return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::OS, -                          ErrorSummary::WrongArgument, ErrorLevel::Permanent); +        return Kernel::ERR_NOT_AUTHORIZED;      }      thread->SetPriority(priority); @@ -743,8 +721,7 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_inf      auto vma = process->vm_manager.FindVMA(addr);      if (vma == Kernel::g_current_process->vm_manager.vma_map.end()) -        return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, -                          ErrorSummary::InvalidArgument, ErrorLevel::Usage); +        return Kernel::ERR_INVALID_ADDRESS;      memory_info->base_address = vma->second.base;      memory_info->permission = static_cast<u32>(vma->second.permissions); @@ -842,8 +819,7 @@ static ResultCode SetTimer(Kernel::Handle handle, s64 initial, s64 interval) {      LOG_TRACE(Kernel_SVC, "called timer=0x%08X", handle);      if (initial < 0 || interval < 0) { -        return ResultCode(ErrorDescription::OutOfRange, ErrorModule::Kernel, -                          ErrorSummary::InvalidArgument, ErrorLevel::Permanent); +        return Kernel::ERR_OUT_OF_RANGE_KERNEL;      }      SharedPtr<Timer> timer = Kernel::g_handle_table.Get<Timer>(handle); @@ -902,8 +878,7 @@ static ResultCode CreateMemoryBlock(Kernel::Handle* out_handle, u32 addr, u32 si      using Kernel::SharedMemory;      if (size % Memory::PAGE_SIZE != 0) -        return ResultCode(ErrorDescription::MisalignedSize, ErrorModule::OS, -                          ErrorSummary::InvalidArgument, ErrorLevel::Usage); +        return Kernel::ERR_MISALIGNED_SIZE;      SharedPtr<SharedMemory> shared_memory = nullptr; @@ -924,16 +899,14 @@ static ResultCode CreateMemoryBlock(Kernel::Handle* out_handle, u32 addr, u32 si      if (!VerifyPermissions(static_cast<MemoryPermission>(my_permission)) ||          !VerifyPermissions(static_cast<MemoryPermission>(other_permission))) -        return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, -                          ErrorSummary::InvalidArgument, ErrorLevel::Usage); +        return Kernel::ERR_INVALID_COMBINATION;      // TODO(Subv): Processes with memory type APPLICATION are not allowed      // to create memory blocks with addr = 0, any attempts to do so      // should return error 0xD92007EA.      if ((addr < Memory::PROCESS_IMAGE_VADDR || addr + size > Memory::SHARED_MEMORY_VADDR_END) &&          addr != 0) { -        return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, -                          ErrorSummary::InvalidArgument, ErrorLevel::Usage); +        return Kernel::ERR_INVALID_ADDRESS;      }      // When trying to create a memory block with address = 0, @@ -1035,7 +1008,7 @@ static ResultCode GetProcessInfo(s64* out, Kernel::Handle process_handle, u32 ty          *out = process->heap_used + process->linear_heap_used + process->misc_memory_used;          if (*out % Memory::PAGE_SIZE != 0) {              LOG_ERROR(Kernel_SVC, "called, memory size not page-aligned"); -            return ERR_MISALIGNED_SIZE; +            return Kernel::ERR_MISALIGNED_SIZE;          }          break;      case 1: @@ -1051,19 +1024,15 @@ static ResultCode GetProcessInfo(s64* out, Kernel::Handle process_handle, u32 ty      case 20:          *out = Memory::FCRAM_PADDR - process->GetLinearHeapBase();          break; +    case 21: +    case 22: +    case 23: +        // These return a different error value than higher invalid values +        LOG_ERROR(Kernel_SVC, "unknown GetProcessInfo type=%u", type); +        return Kernel::ERR_NOT_IMPLEMENTED;      default:          LOG_ERROR(Kernel_SVC, "unknown GetProcessInfo type=%u", type); - -        if (type >= 21 && type <= 23) { -            return ResultCode( // 0xE0E01BF4 -                ErrorDescription::NotImplemented, ErrorModule::OS, ErrorSummary::InvalidArgument, -                ErrorLevel::Usage); -        } else { -            return ResultCode( // 0xD8E007ED -                ErrorDescription::InvalidEnumValue, ErrorModule::Kernel, -                ErrorSummary::InvalidArgument, ErrorLevel::Permanent); -        } -        break; +        return Kernel::ERR_INVALID_ENUM_VALUE;      }      return RESULT_SUCCESS; diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 1a4e3efa8..beeb13ffa 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -9,6 +9,7 @@  #include "common/logging/log.h"  #include "common/string_util.h"  #include "common/swap.h" +#include "core/core.h"  #include "core/file_sys/archive_selfncch.h"  #include "core/hle/kernel/process.h"  #include "core/hle/kernel/resource_limit.h" @@ -339,6 +340,8 @@ ResultStatus AppLoader_NCCH::Load() {      LOG_INFO(Loader, "Program ID: %016" PRIX64, ncch_header.program_id); +    Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", ncch_header.program_id); +      is_loaded = true; // Set state to loaded      result = LoadExec(); // Load the executable into memory for booting diff --git a/src/core/settings.cpp b/src/core/settings.cpp index d2e7c6b97..d4f0429d1 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -6,7 +6,7 @@  #include "core/gdbstub/gdbstub.h"  #include "core/hle/service/hid/hid.h"  #include "core/hle/service/ir/ir.h" -#include "settings.h" +#include "core/settings.h"  #include "video_core/video_core.h"  #include "core/frontend/emu_window.h" diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp new file mode 100644 index 000000000..ddc8b262e --- /dev/null +++ b/src/core/telemetry_session.cpp @@ -0,0 +1,42 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> + +#include "common/scm_rev.h" +#include "core/telemetry_session.h" + +namespace Core { + +TelemetrySession::TelemetrySession() { +    // TODO(bunnei): Replace with a backend that logs to our web service +    backend = std::make_unique<Telemetry::NullVisitor>(); + +    // Log one-time session start information +    const auto duration{std::chrono::steady_clock::now().time_since_epoch()}; +    const auto start_time{std::chrono::duration_cast<std::chrono::microseconds>(duration).count()}; +    AddField(Telemetry::FieldType::Session, "StartTime", start_time); + +    // Log one-time application information +    const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr}; +    AddField(Telemetry::FieldType::App, "GitIsDirty", is_git_dirty); +    AddField(Telemetry::FieldType::App, "GitBranch", Common::g_scm_branch); +    AddField(Telemetry::FieldType::App, "GitRevision", Common::g_scm_rev); +} + +TelemetrySession::~TelemetrySession() { +    // Log one-time session end information +    const auto duration{std::chrono::steady_clock::now().time_since_epoch()}; +    const auto end_time{std::chrono::duration_cast<std::chrono::microseconds>(duration).count()}; +    AddField(Telemetry::FieldType::Session, "EndTime", end_time); + +    // Complete the session, submitting to web service if necessary +    // This is just a placeholder to wrap up the session once the core completes and this is +    // destroyed. This will be moved elsewhere once we are actually doing real I/O with the service. +    field_collection.Accept(*backend); +    backend->Complete(); +    backend = nullptr; +} + +} // namespace Core diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h new file mode 100644 index 000000000..cf53835c3 --- /dev/null +++ b/src/core/telemetry_session.h @@ -0,0 +1,38 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "common/telemetry.h" + +namespace Core { + +/** + * Instruments telemetry for this emulation session. Creates a new set of telemetry fields on each + * session, logging any one-time fields. Interfaces with the telemetry backend used for submitting + * data to the web service. Submits session data on close. + */ +class TelemetrySession : NonCopyable { +public: +    TelemetrySession(); +    ~TelemetrySession(); + +    /** +     * Wrapper around the Telemetry::FieldCollection::AddField method. +     * @param type Type of the field to add. +     * @param name Name of the field to add. +     * @param value Value for the field to add. +     */ +    template <typename T> +    void AddField(Telemetry::FieldType type, const char* name, T value) { +        field_collection.AddField(type, name, std::move(value)); +    } + +private: +    Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session +    std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields +}; + +} // namespace Core diff --git a/src/core/tracer/recorder.cpp b/src/core/tracer/recorder.cpp index 276a5b288..55b3b5efc 100644 --- a/src/core/tracer/recorder.cpp +++ b/src/core/tracer/recorder.cpp @@ -6,7 +6,7 @@  #include "common/assert.h"  #include "common/file_util.h"  #include "common/logging/log.h" -#include "recorder.h" +#include "core/tracer/recorder.h"  namespace CiTrace { diff --git a/src/core/tracer/recorder.h b/src/core/tracer/recorder.h index aea363b95..39e6ec4fd 100644 --- a/src/core/tracer/recorder.h +++ b/src/core/tracer/recorder.h @@ -8,8 +8,8 @@  #include <unordered_map>  #include <vector>  #include <boost/crc.hpp> -#include "citrace.h"  #include "common/common_types.h" +#include "core/tracer/citrace.h"  namespace CiTrace { diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index cfe5caaa3..e3e36ada7 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -13,15 +13,14 @@ set(HEADERS  if(SDL2_FOUND)      set(SRCS ${SRCS} sdl/sdl.cpp)      set(HEADERS ${HEADERS} sdl/sdl.h) -    include_directories(${SDL2_INCLUDE_DIR})  endif()  create_directory_groups(${SRCS} ${HEADERS})  add_library(input_common STATIC ${SRCS} ${HEADERS}) -target_link_libraries(input_common common core) +target_link_libraries(input_common PUBLIC core PRIVATE common)  if(SDL2_FOUND) -    target_link_libraries(input_common ${SDL2_LIBRARY}) -    set_property(TARGET input_common APPEND PROPERTY COMPILE_DEFINITIONS HAVE_SDL2) +    target_link_libraries(input_common PRIVATE SDL2) +    target_compile_definitions(input_common PRIVATE HAVE_SDL2)  endif() diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index d1144ba77..00d7c636a 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -10,10 +10,9 @@ set(HEADERS  create_directory_groups(${SRCS} ${HEADERS}) -include_directories(../../externals/catch/single_include/) -  add_executable(tests ${SRCS} ${HEADERS}) -target_link_libraries(tests core video_core audio_core common) -target_link_libraries(tests ${PLATFORM_LIBRARIES} Threads::Threads) +target_link_libraries(tests PRIVATE common core) +target_link_libraries(tests PRIVATE glad) # To support linker work-around +target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} catch-single-include Threads::Threads) -add_test(NAME tests COMMAND $<TARGET_FILE:tests>) +add_test(NAME tests COMMAND tests) diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 5317719e8..0961a3251 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -15,6 +15,7 @@ set(SRCS              shader/shader_interpreter.cpp              swrasterizer/clipper.cpp              swrasterizer/framebuffer.cpp +            swrasterizer/proctex.cpp              swrasterizer/rasterizer.cpp              swrasterizer/swrasterizer.cpp              swrasterizer/texturing.cpp @@ -54,6 +55,7 @@ set(HEADERS              shader/shader_interpreter.h              swrasterizer/clipper.h              swrasterizer/framebuffer.h +            swrasterizer/proctex.h              swrasterizer/rasterizer.h              swrasterizer/swrasterizer.h              swrasterizer/texturing.h @@ -77,13 +79,14 @@ endif()  create_directory_groups(${SRCS} ${HEADERS})  add_library(video_core STATIC ${SRCS} ${HEADERS}) -target_link_libraries(video_core glad) +target_link_libraries(video_core PUBLIC common core) +target_link_libraries(video_core PRIVATE glad nihstro-headers) +  if (ARCHITECTURE_x86_64) -    target_link_libraries(video_core xbyak) +    target_link_libraries(video_core PRIVATE xbyak)  endif()  if (PNG_FOUND) -    target_link_libraries(video_core ${PNG_LIBRARIES}) -    include_directories(${PNG_INCLUDE_DIRS}) -    add_definitions(${PNG_DEFINITIONS}) +    target_link_libraries(video_core PRIVATE PNG::PNG) +    target_compile_definitions(video_core PRIVATE HAVE_PNG)  endif() diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 8d3f76bde..4633a1df1 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -556,6 +556,37 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {          break;      } +    case PICA_REG_INDEX_WORKAROUND(texturing.proctex_lut_data[0], 0xb0): +    case PICA_REG_INDEX_WORKAROUND(texturing.proctex_lut_data[1], 0xb1): +    case PICA_REG_INDEX_WORKAROUND(texturing.proctex_lut_data[2], 0xb2): +    case PICA_REG_INDEX_WORKAROUND(texturing.proctex_lut_data[3], 0xb3): +    case PICA_REG_INDEX_WORKAROUND(texturing.proctex_lut_data[4], 0xb4): +    case PICA_REG_INDEX_WORKAROUND(texturing.proctex_lut_data[5], 0xb5): +    case PICA_REG_INDEX_WORKAROUND(texturing.proctex_lut_data[6], 0xb6): +    case PICA_REG_INDEX_WORKAROUND(texturing.proctex_lut_data[7], 0xb7): { +        auto& index = regs.texturing.proctex_lut_config.index; +        auto& pt = g_state.proctex; + +        switch (regs.texturing.proctex_lut_config.ref_table.Value()) { +        case TexturingRegs::ProcTexLutTable::Noise: +            pt.noise_table[index % pt.noise_table.size()].raw = value; +            break; +        case TexturingRegs::ProcTexLutTable::ColorMap: +            pt.color_map_table[index % pt.color_map_table.size()].raw = value; +            break; +        case TexturingRegs::ProcTexLutTable::AlphaMap: +            pt.alpha_map_table[index % pt.alpha_map_table.size()].raw = value; +            break; +        case TexturingRegs::ProcTexLutTable::Color: +            pt.color_table[index % pt.color_table.size()].raw = value; +            break; +        case TexturingRegs::ProcTexLutTable::ColorDiff: +            pt.color_diff_table[index % pt.color_diff_table.size()].raw = value; +            break; +        } +        index.Assign(index + 1); +        break; +    }      default:          break;      } diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h index af7536d11..f46db09fb 100644 --- a/src/video_core/pica_state.h +++ b/src/video_core/pica_state.h @@ -7,6 +7,7 @@  #include <array>  #include "common/bit_field.h"  #include "common/common_types.h" +#include "common/vector_math.h"  #include "video_core/primitive_assembly.h"  #include "video_core/regs.h"  #include "video_core/shader/shader.h" @@ -25,6 +26,59 @@ struct State {      Shader::AttributeBuffer input_default_attributes; +    struct ProcTex { +        union ValueEntry { +            u32 raw; + +            // LUT value, encoded as 12-bit fixed point, with 12 fraction bits +            BitField<0, 12, u32> value; // 0.0.12 fixed point + +            // Difference between two entry values. Used for efficient interpolation. +            // 0.0.12 fixed point with two's complement. The range is [-0.5, 0.5). +            // Note: the type of this is different from the one of lighting LUT +            BitField<12, 12, s32> difference; + +            float ToFloat() const { +                return static_cast<float>(value) / 4095.f; +            } + +            float DiffToFloat() const { +                return static_cast<float>(difference) / 4095.f; +            } +        }; + +        union ColorEntry { +            u32 raw; +            BitField<0, 8, u32> r; +            BitField<8, 8, u32> g; +            BitField<16, 8, u32> b; +            BitField<24, 8, u32> a; + +            Math::Vec4<u8> ToVector() const { +                return {static_cast<u8>(r), static_cast<u8>(g), static_cast<u8>(b), +                        static_cast<u8>(a)}; +            } +        }; + +        union ColorDifferenceEntry { +            u32 raw; +            BitField<0, 8, s32> r; // half of the difference between two ColorEntry +            BitField<8, 8, s32> g; +            BitField<16, 8, s32> b; +            BitField<24, 8, s32> a; + +            Math::Vec4<s32> ToVector() const { +                return Math::Vec4<s32>{r, g, b, a} * 2; +            } +        }; + +        std::array<ValueEntry, 128> noise_table; +        std::array<ValueEntry, 128> color_map_table; +        std::array<ValueEntry, 128> alpha_map_table; +        std::array<ColorEntry, 256> color_table; +        std::array<ColorDifferenceEntry, 256> color_diff_table; +    } proctex; +      struct {          union LutEntry {              // Used for raw access diff --git a/src/video_core/regs.h b/src/video_core/regs.h index 1776dad89..6d5f98cac 100644 --- a/src/video_core/regs.h +++ b/src/video_core/regs.h @@ -101,6 +101,13 @@ ASSERT_REG_POSITION(texturing.texture1, 0x91);  ASSERT_REG_POSITION(texturing.texture1_format, 0x96);  ASSERT_REG_POSITION(texturing.texture2, 0x99);  ASSERT_REG_POSITION(texturing.texture2_format, 0x9e); +ASSERT_REG_POSITION(texturing.proctex, 0xa8); +ASSERT_REG_POSITION(texturing.proctex_noise_u, 0xa9); +ASSERT_REG_POSITION(texturing.proctex_noise_v, 0xaa); +ASSERT_REG_POSITION(texturing.proctex_noise_frequency, 0xab); +ASSERT_REG_POSITION(texturing.proctex_lut, 0xac); +ASSERT_REG_POSITION(texturing.proctex_lut_offset, 0xad); +ASSERT_REG_POSITION(texturing.proctex_lut_config, 0xaf);  ASSERT_REG_POSITION(texturing.tev_stage0, 0xc0);  ASSERT_REG_POSITION(texturing.tev_stage1, 0xc8);  ASSERT_REG_POSITION(texturing.tev_stage2, 0xd0); diff --git a/src/video_core/regs_texturing.h b/src/video_core/regs_texturing.h index 3318812da..3f5355fa9 100644 --- a/src/video_core/regs_texturing.h +++ b/src/video_core/regs_texturing.h @@ -127,13 +127,38 @@ struct TexturingRegs {          BitField<0, 1, u32> texture0_enable;          BitField<1, 1, u32> texture1_enable;          BitField<2, 1, u32> texture2_enable; -        BitField<8, 2, u32> texture3_coordinates; // TODO: unimplemented -        BitField<10, 1, u32> texture3_enable;     // TODO: unimplemented +        BitField<8, 2, u32> texture3_coordinates; +        BitField<10, 1, u32> texture3_enable;          BitField<13, 1, u32> texture2_use_coord1;          BitField<16, 1, u32> clear_texture_cache; // TODO: unimplemented      } main_config;      TextureConfig texture0; -    INSERT_PADDING_WORDS(0x8); + +    enum class CubeFace { +        PositiveX = 0, +        NegativeX = 1, +        PositiveY = 2, +        NegativeY = 3, +        PositiveZ = 4, +        NegativeZ = 5, +    }; + +    BitField<0, 22, u32> cube_address[5]; + +    PAddr GetCubePhysicalAddress(CubeFace face) const { +        PAddr address = texture0.address; +        if (face != CubeFace::PositiveX) { +            // Bits [22:27] from the main texture address is shared with all cubemap additional +            // addresses. +            auto& face_addr = cube_address[static_cast<size_t>(face) - 1]; +            address &= ~face_addr.mask; +            address |= face_addr; +        } +        // A multiplier of 8 is also needed in the same way as the main address. +        return address * 8; +    } + +    INSERT_PADDING_WORDS(0x3);      BitField<0, 4, TextureFormat> texture0_format;      BitField<0, 1, u32> fragment_lighting_enable;      INSERT_PADDING_WORDS(0x1); @@ -142,7 +167,7 @@ struct TexturingRegs {      INSERT_PADDING_WORDS(0x2);      TextureConfig texture2;      BitField<0, 4, TextureFormat> texture2_format; -    INSERT_PADDING_WORDS(0x21); +    INSERT_PADDING_WORDS(0x9);      struct FullTextureConfig {          const bool enabled; @@ -157,6 +182,96 @@ struct TexturingRegs {          }};      } +    // 0xa8-0xad: ProcTex Config +    enum class ProcTexClamp : u32 { +        ToZero = 0, +        ToEdge = 1, +        SymmetricalRepeat = 2, +        MirroredRepeat = 3, +        Pulse = 4, +    }; + +    enum class ProcTexCombiner : u32 { +        U = 0,        // u +        U2 = 1,       // u * u +        V = 2,        // v +        V2 = 3,       // v * v +        Add = 4,      // (u + v) / 2 +        Add2 = 5,     // (u * u + v * v) / 2 +        SqrtAdd2 = 6, // sqrt(u * u + v * v) +        Min = 7,      // min(u, v) +        Max = 8,      // max(u, v) +        RMax = 9,     // Average of Max and SqrtAdd2 +    }; + +    enum class ProcTexShift : u32 { +        None = 0, +        Odd = 1, +        Even = 2, +    }; + +    union { +        BitField<0, 3, ProcTexClamp> u_clamp; +        BitField<3, 3, ProcTexClamp> v_clamp; +        BitField<6, 4, ProcTexCombiner> color_combiner; +        BitField<10, 4, ProcTexCombiner> alpha_combiner; +        BitField<14, 1, u32> separate_alpha; +        BitField<15, 1, u32> noise_enable; +        BitField<16, 2, ProcTexShift> u_shift; +        BitField<18, 2, ProcTexShift> v_shift; +        BitField<20, 8, u32> bias_low; // float16 TODO: unimplemented +    } proctex; + +    union ProcTexNoiseConfig { +        BitField<0, 16, s32> amplitude; // fixed1.3.12 +        BitField<16, 16, u32> phase;    // float16 +    }; + +    ProcTexNoiseConfig proctex_noise_u; +    ProcTexNoiseConfig proctex_noise_v; + +    union { +        BitField<0, 16, u32> u;  // float16 +        BitField<16, 16, u32> v; // float16 +    } proctex_noise_frequency; + +    enum class ProcTexFilter : u32 { +        Nearest = 0, +        Linear = 1, +        NearestMipmapNearest = 2, +        LinearMipmapNearest = 3, +        NearestMipmapLinear = 4, +        LinearMipmapLinear = 5, +    }; + +    union { +        BitField<0, 3, ProcTexFilter> filter; +        BitField<11, 8, u32> width; +        BitField<19, 8, u32> bias_high; // TODO: unimplemented +    } proctex_lut; + +    BitField<0, 8, u32> proctex_lut_offset; + +    INSERT_PADDING_WORDS(0x1); + +    // 0xaf-0xb7: ProcTex LUT +    enum class ProcTexLutTable : u32 { +        Noise = 0, +        ColorMap = 2, +        AlphaMap = 3, +        Color = 4, +        ColorDiff = 5, +    }; + +    union { +        BitField<0, 8, u32> index; +        BitField<8, 4, ProcTexLutTable> ref_table; +    } proctex_lut_config; + +    u32 proctex_lut_data[8]; + +    INSERT_PADDING_WORDS(0x8); +      // 0xc0-0xff: Texture Combiner (akin to glTexEnv)      struct TevStageConfig {          enum class Source : u32 { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 12ac9bbd9..aa9b831dd 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -55,6 +55,12 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {      uniform_block_data.fog_lut_dirty = true; +    uniform_block_data.proctex_noise_lut_dirty = true; +    uniform_block_data.proctex_color_map_dirty = true; +    uniform_block_data.proctex_alpha_map_dirty = true; +    uniform_block_data.proctex_lut_dirty = true; +    uniform_block_data.proctex_diff_lut_dirty = true; +      // Set vertex attributes      glVertexAttribPointer(GLShader::ATTRIBUTE_POSITION, 4, GL_FLOAT, GL_FALSE,                            sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, position)); @@ -115,6 +121,51 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {      glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);      glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); +    // Setup the noise LUT for proctex +    proctex_noise_lut.Create(); +    state.proctex_noise_lut.texture_1d = proctex_noise_lut.handle; +    state.Apply(); +    glActiveTexture(GL_TEXTURE10); +    glTexImage1D(GL_TEXTURE_1D, 0, GL_RG32F, 128, 0, GL_RG, GL_FLOAT, nullptr); +    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); +    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + +    // Setup the color map for proctex +    proctex_color_map.Create(); +    state.proctex_color_map.texture_1d = proctex_color_map.handle; +    state.Apply(); +    glActiveTexture(GL_TEXTURE11); +    glTexImage1D(GL_TEXTURE_1D, 0, GL_RG32F, 128, 0, GL_RG, GL_FLOAT, nullptr); +    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); +    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + +    // Setup the alpha map for proctex +    proctex_alpha_map.Create(); +    state.proctex_alpha_map.texture_1d = proctex_alpha_map.handle; +    state.Apply(); +    glActiveTexture(GL_TEXTURE12); +    glTexImage1D(GL_TEXTURE_1D, 0, GL_RG32F, 128, 0, GL_RG, GL_FLOAT, nullptr); +    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); +    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + +    // Setup the LUT for proctex +    proctex_lut.Create(); +    state.proctex_lut.texture_1d = proctex_lut.handle; +    state.Apply(); +    glActiveTexture(GL_TEXTURE13); +    glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, 256, 0, GL_RGBA, GL_FLOAT, nullptr); +    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); +    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + +    // Setup the difference LUT for proctex +    proctex_diff_lut.Create(); +    state.proctex_diff_lut.texture_1d = proctex_diff_lut.handle; +    state.Apply(); +    glActiveTexture(GL_TEXTURE14); +    glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, 256, 0, GL_RGBA, GL_FLOAT, nullptr); +    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); +    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); +      // Sync fixed function OpenGL state      SyncCullMode();      SyncBlendEnabled(); @@ -272,6 +323,36 @@ void RasterizerOpenGL::DrawTriangles() {          uniform_block_data.fog_lut_dirty = false;      } +    // Sync the proctex noise lut +    if (uniform_block_data.proctex_noise_lut_dirty) { +        SyncProcTexNoiseLUT(); +        uniform_block_data.proctex_noise_lut_dirty = false; +    } + +    // Sync the proctex color map +    if (uniform_block_data.proctex_color_map_dirty) { +        SyncProcTexColorMap(); +        uniform_block_data.proctex_color_map_dirty = false; +    } + +    // Sync the proctex alpha map +    if (uniform_block_data.proctex_alpha_map_dirty) { +        SyncProcTexAlphaMap(); +        uniform_block_data.proctex_alpha_map_dirty = false; +    } + +    // Sync the proctex lut +    if (uniform_block_data.proctex_lut_dirty) { +        SyncProcTexLUT(); +        uniform_block_data.proctex_lut_dirty = false; +    } + +    // Sync the proctex difference lut +    if (uniform_block_data.proctex_diff_lut_dirty) { +        SyncProcTexDiffLUT(); +        uniform_block_data.proctex_diff_lut_dirty = false; +    } +      // Sync the uniform data      if (uniform_block_data.dirty) {          glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformData), &uniform_block_data.data, @@ -354,6 +435,47 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {          uniform_block_data.fog_lut_dirty = true;          break; +    // ProcTex state +    case PICA_REG_INDEX(texturing.proctex): +    case PICA_REG_INDEX(texturing.proctex_lut): +    case PICA_REG_INDEX(texturing.proctex_lut_offset): +        shader_dirty = true; +        break; + +    case PICA_REG_INDEX(texturing.proctex_noise_u): +    case PICA_REG_INDEX(texturing.proctex_noise_v): +    case PICA_REG_INDEX(texturing.proctex_noise_frequency): +        SyncProcTexNoise(); +        break; + +    case PICA_REG_INDEX_WORKAROUND(texturing.proctex_lut_data[0], 0xb0): +    case PICA_REG_INDEX_WORKAROUND(texturing.proctex_lut_data[1], 0xb1): +    case PICA_REG_INDEX_WORKAROUND(texturing.proctex_lut_data[2], 0xb2): +    case PICA_REG_INDEX_WORKAROUND(texturing.proctex_lut_data[3], 0xb3): +    case PICA_REG_INDEX_WORKAROUND(texturing.proctex_lut_data[4], 0xb4): +    case PICA_REG_INDEX_WORKAROUND(texturing.proctex_lut_data[5], 0xb5): +    case PICA_REG_INDEX_WORKAROUND(texturing.proctex_lut_data[6], 0xb6): +    case PICA_REG_INDEX_WORKAROUND(texturing.proctex_lut_data[7], 0xb7): +        using Pica::TexturingRegs; +        switch (regs.texturing.proctex_lut_config.ref_table.Value()) { +        case TexturingRegs::ProcTexLutTable::Noise: +            uniform_block_data.proctex_noise_lut_dirty = true; +            break; +        case TexturingRegs::ProcTexLutTable::ColorMap: +            uniform_block_data.proctex_color_map_dirty = true; +            break; +        case TexturingRegs::ProcTexLutTable::AlphaMap: +            uniform_block_data.proctex_alpha_map_dirty = true; +            break; +        case TexturingRegs::ProcTexLutTable::Color: +            uniform_block_data.proctex_lut_dirty = true; +            break; +        case TexturingRegs::ProcTexLutTable::ColorDiff: +            uniform_block_data.proctex_diff_lut_dirty = true; +            break; +        } +        break; +      // Alpha test      case PICA_REG_INDEX(framebuffer.output_merger.alpha_test):          SyncAlphaTest(); @@ -1072,6 +1194,35 @@ void RasterizerOpenGL::SetShader() {              glUniform1i(uniform_fog_lut, 9);          } +        GLuint uniform_proctex_noise_lut = +            glGetUniformLocation(shader->shader.handle, "proctex_noise_lut"); +        if (uniform_proctex_noise_lut != -1) { +            glUniform1i(uniform_proctex_noise_lut, 10); +        } + +        GLuint uniform_proctex_color_map = +            glGetUniformLocation(shader->shader.handle, "proctex_color_map"); +        if (uniform_proctex_color_map != -1) { +            glUniform1i(uniform_proctex_color_map, 11); +        } + +        GLuint uniform_proctex_alpha_map = +            glGetUniformLocation(shader->shader.handle, "proctex_alpha_map"); +        if (uniform_proctex_alpha_map != -1) { +            glUniform1i(uniform_proctex_alpha_map, 12); +        } + +        GLuint uniform_proctex_lut = glGetUniformLocation(shader->shader.handle, "proctex_lut"); +        if (uniform_proctex_lut != -1) { +            glUniform1i(uniform_proctex_lut, 13); +        } + +        GLuint uniform_proctex_diff_lut = +            glGetUniformLocation(shader->shader.handle, "proctex_diff_lut"); +        if (uniform_proctex_diff_lut != -1) { +            glUniform1i(uniform_proctex_diff_lut, 14); +        } +          current_shader = shader_cache.emplace(config, std::move(shader)).first->second.get();          GLuint block_index = glGetUniformBlockIndex(current_shader->shader.handle, "shader_data"); @@ -1105,6 +1256,7 @@ void RasterizerOpenGL::SetShader() {              }              SyncFogColor(); +            SyncProcTexNoise();          }      }  } @@ -1204,6 +1356,86 @@ void RasterizerOpenGL::SyncFogLUT() {      }  } +void RasterizerOpenGL::SyncProcTexNoise() { +    const auto& regs = Pica::g_state.regs.texturing; +    uniform_block_data.data.proctex_noise_f = { +        Pica::float16::FromRaw(regs.proctex_noise_frequency.u).ToFloat32(), +        Pica::float16::FromRaw(regs.proctex_noise_frequency.v).ToFloat32(), +    }; +    uniform_block_data.data.proctex_noise_a = { +        regs.proctex_noise_u.amplitude / 4095.0f, regs.proctex_noise_v.amplitude / 4095.0f, +    }; +    uniform_block_data.data.proctex_noise_p = { +        Pica::float16::FromRaw(regs.proctex_noise_u.phase).ToFloat32(), +        Pica::float16::FromRaw(regs.proctex_noise_v.phase).ToFloat32(), +    }; + +    uniform_block_data.dirty = true; +} + +// helper function for SyncProcTexNoiseLUT/ColorMap/AlphaMap +static void SyncProcTexValueLUT(const std::array<Pica::State::ProcTex::ValueEntry, 128>& lut, +                                std::array<GLvec2, 128>& lut_data, GLenum texture) { +    std::array<GLvec2, 128> new_data; +    std::transform(lut.begin(), lut.end(), new_data.begin(), [](const auto& entry) { +        return GLvec2{entry.ToFloat(), entry.DiffToFloat()}; +    }); + +    if (new_data != lut_data) { +        lut_data = new_data; +        glActiveTexture(texture); +        glTexSubImage1D(GL_TEXTURE_1D, 0, 0, 128, GL_RG, GL_FLOAT, lut_data.data()); +    } +} + +void RasterizerOpenGL::SyncProcTexNoiseLUT() { +    SyncProcTexValueLUT(Pica::g_state.proctex.noise_table, proctex_noise_lut_data, GL_TEXTURE10); +} + +void RasterizerOpenGL::SyncProcTexColorMap() { +    SyncProcTexValueLUT(Pica::g_state.proctex.color_map_table, proctex_color_map_data, +                        GL_TEXTURE11); +} + +void RasterizerOpenGL::SyncProcTexAlphaMap() { +    SyncProcTexValueLUT(Pica::g_state.proctex.alpha_map_table, proctex_alpha_map_data, +                        GL_TEXTURE12); +} + +void RasterizerOpenGL::SyncProcTexLUT() { +    std::array<GLvec4, 256> new_data; + +    std::transform(Pica::g_state.proctex.color_table.begin(), +                   Pica::g_state.proctex.color_table.end(), new_data.begin(), +                   [](const auto& entry) { +                       auto rgba = entry.ToVector() / 255.0f; +                       return GLvec4{rgba.r(), rgba.g(), rgba.b(), rgba.a()}; +                   }); + +    if (new_data != proctex_lut_data) { +        proctex_lut_data = new_data; +        glActiveTexture(GL_TEXTURE13); +        glTexSubImage1D(GL_TEXTURE_1D, 0, 0, 256, GL_RGBA, GL_FLOAT, proctex_lut_data.data()); +    } +} + +void RasterizerOpenGL::SyncProcTexDiffLUT() { +    std::array<GLvec4, 256> new_data; + +    std::transform(Pica::g_state.proctex.color_diff_table.begin(), +                   Pica::g_state.proctex.color_diff_table.end(), new_data.begin(), +                   [](const auto& entry) { +                       auto rgba = entry.ToVector() / 255.0f; +                       return GLvec4{rgba.r(), rgba.g(), rgba.b(), rgba.a()}; +                   }); + +    if (new_data != proctex_diff_lut_data) { +        proctex_diff_lut_data = new_data; +        glActiveTexture(GL_TEXTURE14); +        glTexSubImage1D(GL_TEXTURE_1D, 0, 0, 256, GL_RGBA, GL_FLOAT, proctex_diff_lut_data.data()); +    } +} +  void RasterizerOpenGL::SyncAlphaTest() {      const auto& regs = Pica::g_state.regs;      if (regs.framebuffer.output_merger.alpha_test.ref != uniform_block_data.data.alphatest_ref) { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 3e1770d77..a9ad7d660 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -143,6 +143,9 @@ private:          GLint scissor_x2;          GLint scissor_y2;          alignas(16) GLvec3 fog_color; +        alignas(8) GLvec2 proctex_noise_f; +        alignas(8) GLvec2 proctex_noise_a; +        alignas(8) GLvec2 proctex_noise_p;          alignas(16) GLvec3 lighting_global_ambient;          LightSrc light_src[8];          alignas(16) GLvec4 const_color[6]; // A vec4 color for each of the six tev stages @@ -150,7 +153,7 @@ private:      };      static_assert( -        sizeof(UniformData) == 0x3C0, +        sizeof(UniformData) == 0x3E0,          "The size of the UniformData structure has changed, update the structure in the shader");      static_assert(sizeof(UniformData) < 16384,                    "UniformData structure must be less than 16kb as per the OpenGL spec"); @@ -180,6 +183,16 @@ private:      void SyncFogColor();      void SyncFogLUT(); +    /// Sync the procedural texture noise configuration to match the PICA register +    void SyncProcTexNoise(); + +    /// Sync the procedural texture lookup tables +    void SyncProcTexNoiseLUT(); +    void SyncProcTexColorMap(); +    void SyncProcTexAlphaMap(); +    void SyncProcTexLUT(); +    void SyncProcTexDiffLUT(); +      /// Syncs the alpha test states to match the PICA register      void SyncAlphaTest(); @@ -248,6 +261,11 @@ private:          UniformData data;          bool lut_dirty[6];          bool fog_lut_dirty; +        bool proctex_noise_lut_dirty; +        bool proctex_color_map_dirty; +        bool proctex_alpha_map_dirty; +        bool proctex_lut_dirty; +        bool proctex_diff_lut_dirty;          bool dirty;      } uniform_block_data = {}; @@ -262,4 +280,19 @@ private:      OGLTexture fog_lut;      std::array<GLuint, 128> fog_lut_data{}; + +    OGLTexture proctex_noise_lut; +    std::array<GLvec2, 128> proctex_noise_lut_data{}; + +    OGLTexture proctex_color_map; +    std::array<GLvec2, 128> proctex_color_map_data{}; + +    OGLTexture proctex_alpha_map; +    std::array<GLvec2, 128> proctex_alpha_map_data{}; + +    OGLTexture proctex_lut; +    std::array<GLvec4, 256> proctex_lut_data{}; + +    OGLTexture proctex_diff_lut; +    std::array<GLvec4, 256> proctex_diff_lut_data{};  }; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 456443e86..8b717e43d 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -561,20 +561,16 @@ RasterizerCacheOpenGL::GetFramebufferSurfaces(      color_params.is_tiled = depth_params.is_tiled = true;      // Set the internal resolution, assume the same scaling factor for top and bottom screens -    const Layout::FramebufferLayout& layout = VideoCore::g_emu_window->GetFramebufferLayout(); -    if (Settings::values.resolution_factor == 0.0f) { +    float resolution_scale_factor = Settings::values.resolution_factor; +    if (resolution_scale_factor == 0.0f) {          // Auto - scale resolution to the window size -        color_params.res_scale_width = depth_params.res_scale_width = -            (float)layout.top_screen.GetWidth() / VideoCore::kScreenTopWidth; -        color_params.res_scale_height = depth_params.res_scale_height = -            (float)layout.top_screen.GetHeight() / VideoCore::kScreenTopHeight; -    } else { -        // Otherwise, scale the resolution by the specified factor -        color_params.res_scale_width = Settings::values.resolution_factor; -        depth_params.res_scale_width = Settings::values.resolution_factor; -        color_params.res_scale_height = Settings::values.resolution_factor; -        depth_params.res_scale_height = Settings::values.resolution_factor; +        resolution_scale_factor = VideoCore::g_emu_window->GetFramebufferLayout().GetScalingRatio();      } +    // Scale the resolution by the specified factor +    color_params.res_scale_width = resolution_scale_factor; +    depth_params.res_scale_width = resolution_scale_factor; +    color_params.res_scale_height = resolution_scale_factor; +    depth_params.res_scale_height = resolution_scale_factor;      color_params.addr = config.GetColorBufferPhysicalAddress();      color_params.pixel_format = CachedSurface::PixelFormatFromColorFormat(config.color_format); diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 7b44dade8..ffe419863 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -114,6 +114,22 @@ PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) {      state.lighting.bump_renorm = regs.lighting.config0.disable_bump_renorm == 0;      state.lighting.clamp_highlights = regs.lighting.config0.clamp_highlights != 0; +    state.proctex.enable = regs.texturing.main_config.texture3_enable; +    if (state.proctex.enable) { +        state.proctex.coord = regs.texturing.main_config.texture3_coordinates; +        state.proctex.u_clamp = regs.texturing.proctex.u_clamp; +        state.proctex.v_clamp = regs.texturing.proctex.v_clamp; +        state.proctex.color_combiner = regs.texturing.proctex.color_combiner; +        state.proctex.alpha_combiner = regs.texturing.proctex.alpha_combiner; +        state.proctex.separate_alpha = regs.texturing.proctex.separate_alpha; +        state.proctex.noise_enable = regs.texturing.proctex.noise_enable; +        state.proctex.u_shift = regs.texturing.proctex.u_shift; +        state.proctex.v_shift = regs.texturing.proctex.v_shift; +        state.proctex.lut_width = regs.texturing.proctex_lut.width; +        state.proctex.lut_offset = regs.texturing.proctex_lut_offset; +        state.proctex.lut_filter = regs.texturing.proctex_lut.filter; +    } +      return res;  } @@ -128,13 +144,40 @@ static bool IsPassThroughTevStage(const TevStageConfig& stage) {              stage.GetColorMultiplier() == 1 && stage.GetAlphaMultiplier() == 1);  } -static std::string TexCoord(const PicaShaderConfig& config, int texture_unit) { -    if (texture_unit == 2 && config.state.texture2_use_coord1) { -        return "texcoord[1]"; +static std::string SampleTexture(const PicaShaderConfig& config, unsigned texture_unit) { +    const auto& state = config.state; +    switch (texture_unit) { +    case 0: +        // Only unit 0 respects the texturing type +        switch (state.texture0_type) { +        case TexturingRegs::TextureConfig::Texture2D: +            return "texture(tex[0], texcoord[0])"; +        case TexturingRegs::TextureConfig::Projection2D: +            return "textureProj(tex[0], vec3(texcoord[0], texcoord0_w))"; +        default: +            LOG_CRITICAL(HW_GPU, "Unhandled texture type %x", +                         static_cast<int>(state.texture0_type)); +            UNIMPLEMENTED(); +            return "texture(tex[0], texcoord[0])"; +        } +    case 1: +        return "texture(tex[1], texcoord[1])"; +    case 2: +        if (state.texture2_use_coord1) +            return "texture(tex[2], texcoord[1])"; +        else +            return "texture(tex[2], texcoord[2])"; +    case 3: +        if (state.proctex.enable) { +            return "ProcTex()"; +        } else { +            LOG_ERROR(Render_OpenGL, "Using Texture3 without enabling it"); +            return "vec4(0.0)"; +        } +    default: +        UNREACHABLE(); +        return "";      } -    // TODO: if texture unit 3 (procedural texture) implementation also uses this function, -    //       config.state.texture3_coordinates should be repected here. -    return "texcoord[" + std::to_string(texture_unit) + "]";  }  /// Writes the specified TEV stage source component(s) @@ -153,27 +196,16 @@ static void AppendSource(std::string& out, const PicaShaderConfig& config,          out += "secondary_fragment_color";          break;      case Source::Texture0: -        // Only unit 0 respects the texturing type (according to 3DBrew) -        switch (state.texture0_type) { -        case TexturingRegs::TextureConfig::Texture2D: -            out += "texture(tex[0], texcoord[0])"; -            break; -        case TexturingRegs::TextureConfig::Projection2D: -            out += "textureProj(tex[0], vec3(texcoord[0], texcoord0_w))"; -            break; -        default: -            out += "texture(tex[0], texcoord[0])"; -            LOG_CRITICAL(HW_GPU, "Unhandled texture type %x", -                         static_cast<int>(state.texture0_type)); -            UNIMPLEMENTED(); -            break; -        } +        out += SampleTexture(config, 0);          break;      case Source::Texture1: -        out += "texture(tex[1], texcoord[1])"; +        out += SampleTexture(config, 1);          break;      case Source::Texture2: -        out += "texture(tex[2], " + TexCoord(config, 2) + ")"; +        out += SampleTexture(config, 2); +        break; +    case Source::Texture3: +        out += SampleTexture(config, 3);          break;      case Source::PreviousBuffer:          out += "combiner_buffer"; @@ -483,9 +515,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {      if (lighting.bump_mode == LightingRegs::LightingBumpMode::NormalMap) {          // Bump mapping is enabled using a normal map, read perturbation vector from the selected          // texture -        std::string bump_selector = std::to_string(lighting.bump_selector); -        out += "vec3 surface_normal = 2.0 * texture(tex[" + bump_selector + "], " + -               TexCoord(config, lighting.bump_selector) + ").rgb - 1.0;\n"; +        out += "vec3 surface_normal = 2.0 * (" + SampleTexture(config, lighting.bump_selector) + +               ").rgb - 1.0;\n";          // Recompute Z-component of perturbation if 'renorm' is enabled, this provides a higher          // precision result @@ -504,8 +535,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {      }      // Rotate the surface-local normal by the interpolated normal quaternion to convert it to -    // eyespace -    out += "vec3 normal = normalize(quaternion_rotate(normquat, surface_normal));\n"; +    // eyespace. +    out += "vec3 normal = quaternion_rotate(normalize(normquat), surface_normal);\n";      // Gets the index into the specified lookup table for specular lighting      auto GetLutIndex = [&lighting](unsigned light_num, LightingRegs::LightingLutInput input, @@ -693,6 +724,221 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {      out += "secondary_fragment_color = clamp(specular_sum, vec4(0.0), vec4(1.0));\n";  } +using ProcTexClamp = TexturingRegs::ProcTexClamp; +using ProcTexShift = TexturingRegs::ProcTexShift; +using ProcTexCombiner = TexturingRegs::ProcTexCombiner; +using ProcTexFilter = TexturingRegs::ProcTexFilter; + +void AppendProcTexShiftOffset(std::string& out, const std::string& v, ProcTexShift mode, +                              ProcTexClamp clamp_mode) { +    std::string offset = (clamp_mode == ProcTexClamp::MirroredRepeat) ? "1.0" : "0.5"; +    switch (mode) { +    case ProcTexShift::None: +        out += "0"; +        break; +    case ProcTexShift::Odd: +        out += offset + " * ((int(" + v + ") / 2) % 2)"; +        break; +    case ProcTexShift::Even: +        out += offset + " * (((int(" + v + ") + 1) / 2) % 2)"; +        break; +    default: +        LOG_CRITICAL(HW_GPU, "Unknown shift mode %u", static_cast<u32>(mode)); +        out += "0"; +        break; +    } +} + +void AppendProcTexClamp(std::string& out, const std::string& var, ProcTexClamp mode) { +    switch (mode) { +    case ProcTexClamp::ToZero: +        out += var + " = " + var + " > 1.0 ? 0 : " + var + ";\n"; +        break; +    case ProcTexClamp::ToEdge: +        out += var + " = " + "min(" + var + ", 1.0);\n"; +        break; +    case ProcTexClamp::SymmetricalRepeat: +        out += var + " = " + "fract(" + var + ");\n"; +        break; +    case ProcTexClamp::MirroredRepeat: { +        out += +            var + " = int(" + var + ") % 2 == 0 ? fract(" + var + ") : 1.0 - fract(" + var + ");\n"; +        break; +    } +    case ProcTexClamp::Pulse: +        out += var + " = " + var + " > 0.5 ? 1.0 : 0.0;\n"; +        break; +    default: +        LOG_CRITICAL(HW_GPU, "Unknown clamp mode %u", static_cast<u32>(mode)); +        out += var + " = " + "min(" + var + ", 1.0);\n"; +        break; +    } +} + +void AppendProcTexCombineAndMap(std::string& out, ProcTexCombiner combiner, +                                const std::string& map_lut) { +    std::string combined; +    switch (combiner) { +    case ProcTexCombiner::U: +        combined = "u"; +        break; +    case ProcTexCombiner::U2: +        combined = "(u * u)"; +        break; +    case TexturingRegs::ProcTexCombiner::V: +        combined = "v"; +        break; +    case TexturingRegs::ProcTexCombiner::V2: +        combined = "(v * v)"; +        break; +    case TexturingRegs::ProcTexCombiner::Add: +        combined = "((u + v) * 0.5)"; +        break; +    case TexturingRegs::ProcTexCombiner::Add2: +        combined = "((u * u + v * v) * 0.5)"; +        break; +    case TexturingRegs::ProcTexCombiner::SqrtAdd2: +        combined = "min(sqrt(u * u + v * v), 1.0)"; +        break; +    case TexturingRegs::ProcTexCombiner::Min: +        combined = "min(u, v)"; +        break; +    case TexturingRegs::ProcTexCombiner::Max: +        combined = "max(u, v)"; +        break; +    case TexturingRegs::ProcTexCombiner::RMax: +        combined = "min(((u + v) * 0.5 + sqrt(u * u + v * v)) * 0.5, 1.0)"; +        break; +    default: +        LOG_CRITICAL(HW_GPU, "Unknown combiner %u", static_cast<u32>(combiner)); +        combined = "0.0"; +        break; +    } +    out += "ProcTexLookupLUT(" + map_lut + ", " + combined + ")"; +} + +void AppendProcTexSampler(std::string& out, const PicaShaderConfig& config) { +    // LUT sampling uitlity +    // For NoiseLUT/ColorMap/AlphaMap, coord=0.0 is lut[0], coord=127.0/128.0 is lut[127] and +    // coord=1.0 is lut[127]+lut_diff[127]. For other indices, the result is interpolated using +    // value entries and difference entries. +    out += R"( +float ProcTexLookupLUT(sampler1D lut, float coord) { +    coord *= 128; +    float index_i = clamp(floor(coord), 0.0, 127.0); +    float index_f = coord - index_i; // fract() cannot be used here because 128.0 needs to be +                                     // extracted as index_i = 127.0 and index_f = 1.0 +    vec2 entry = texelFetch(lut, int(index_i), 0).rg; +    return clamp(entry.r + entry.g * index_f, 0.0, 1.0); +} +    )"; + +    // Noise utility +    if (config.state.proctex.noise_enable) { +        // See swrasterizer/proctex.cpp for more information about these functions +        out += R"( +int ProcTexNoiseRand1D(int v) { +    const int table[] = int[](0,4,10,8,4,9,7,12,5,15,13,14,11,15,2,11); +    return ((v % 9 + 2) * 3 & 0xF) ^ table[(v / 9) & 0xF]; +} + +float ProcTexNoiseRand2D(vec2 point) { +    const int table[] = int[](10,2,15,8,0,7,4,5,5,13,2,6,13,9,3,14); +    int u2 = ProcTexNoiseRand1D(int(point.x)); +    int v2 = ProcTexNoiseRand1D(int(point.y)); +    v2 += ((u2 & 3) == 1) ? 4 : 0; +    v2 ^= (u2 & 1) * 6; +    v2 += 10 + u2; +    v2 &= 0xF; +    v2 ^= table[u2]; +    return -1.0 + float(v2) * 2.0/ 15.0; +} + +float ProcTexNoiseCoef(vec2 x) { +    vec2 grid  = 9.0 * proctex_noise_f * abs(x + proctex_noise_p); +    vec2 point = floor(grid); +    vec2 frac  = grid - point; + +    float g0 = ProcTexNoiseRand2D(point) * (frac.x + frac.y); +    float g1 = ProcTexNoiseRand2D(point + vec2(1.0, 0.0)) * (frac.x + frac.y - 1.0); +    float g2 = ProcTexNoiseRand2D(point + vec2(0.0, 1.0)) * (frac.x + frac.y - 1.0); +    float g3 = ProcTexNoiseRand2D(point + vec2(1.0, 1.0)) * (frac.x + frac.y - 2.0); + +    float x_noise = ProcTexLookupLUT(proctex_noise_lut, frac.x); +    float y_noise = ProcTexLookupLUT(proctex_noise_lut, frac.y); +    float x0 = mix(g0, g1, x_noise); +    float x1 = mix(g2, g3, x_noise); +    return mix(x0, x1, y_noise); +} +        )"; +    } + +    out += "vec4 ProcTex() {\n"; +    out += "vec2 uv = abs(texcoord[" + std::to_string(config.state.proctex.coord) + "]);\n"; + +    // Get shift offset before noise generation +    out += "float u_shift = "; +    AppendProcTexShiftOffset(out, "uv.y", config.state.proctex.u_shift, +                             config.state.proctex.u_clamp); +    out += ";\n"; +    out += "float v_shift = "; +    AppendProcTexShiftOffset(out, "uv.x", config.state.proctex.v_shift, +                             config.state.proctex.v_clamp); +    out += ";\n"; + +    // Generate noise +    if (config.state.proctex.noise_enable) { +        out += "uv += proctex_noise_a * ProcTexNoiseCoef(uv);\n"; +        out += "uv = abs(uv);\n"; +    } + +    // Shift +    out += "float u = uv.x + u_shift;\n"; +    out += "float v = uv.y + v_shift;\n"; + +    // Clamp +    AppendProcTexClamp(out, "u", config.state.proctex.u_clamp); +    AppendProcTexClamp(out, "v", config.state.proctex.v_clamp); + +    // Combine and map +    out += "float lut_coord = "; +    AppendProcTexCombineAndMap(out, config.state.proctex.color_combiner, "proctex_color_map"); +    out += ";\n"; + +    // Look up color +    // For the color lut, coord=0.0 is lut[offset] and coord=1.0 is lut[offset+width-1] +    out += "lut_coord *= " + std::to_string(config.state.proctex.lut_width - 1) + ";\n"; +    // TODO(wwylele): implement mipmap +    switch (config.state.proctex.lut_filter) { +    case ProcTexFilter::Linear: +    case ProcTexFilter::LinearMipmapLinear: +    case ProcTexFilter::LinearMipmapNearest: +        out += "int lut_index_i = int(lut_coord) + " + +               std::to_string(config.state.proctex.lut_offset) + ";\n"; +        out += "float lut_index_f = fract(lut_coord);\n"; +        out += "vec4 final_color = texelFetch(proctex_lut, lut_index_i, 0) + lut_index_f * " +               "texelFetch(proctex_diff_lut, lut_index_i, 0);\n"; +        break; +    case ProcTexFilter::Nearest: +    case ProcTexFilter::NearestMipmapLinear: +    case ProcTexFilter::NearestMipmapNearest: +        out += "lut_coord += " + std::to_string(config.state.proctex.lut_offset) + ";\n"; +        out += "vec4 final_color = texelFetch(proctex_lut, int(round(lut_coord)), 0);\n"; +        break; +    } + +    if (config.state.proctex.separate_alpha) { +        // Note: in separate alpha mode, the alpha channel skips the color LUT look up stage. It +        // uses the output of CombineAndMap directly instead. +        out += "float final_alpha = "; +        AppendProcTexCombineAndMap(out, config.state.proctex.alpha_combiner, "proctex_alpha_map"); +        out += ";\n"; +        out += "return vec4(final_color.xyz, final_alpha);\n}\n"; +    } else { +        out += "return final_color;\n}\n"; +    } +} +  std::string GenerateFragmentShader(const PicaShaderConfig& config) {      const auto& state = config.state; @@ -735,6 +981,9 @@ layout (std140) uniform shader_data {      int scissor_x2;      int scissor_y2;      vec3 fog_color; +    vec2 proctex_noise_f; +    vec2 proctex_noise_a; +    vec2 proctex_noise_p;      vec3 lighting_global_ambient;      LightSrc light_src[NUM_LIGHTS];      vec4 const_color[NUM_TEV_STAGES]; @@ -744,12 +993,23 @@ layout (std140) uniform shader_data {  uniform sampler2D tex[3];  uniform sampler1D lut[6];  uniform usampler1D fog_lut; +uniform sampler1D proctex_noise_lut; +uniform sampler1D proctex_color_map; +uniform sampler1D proctex_alpha_map; +uniform sampler1D proctex_lut; +uniform sampler1D proctex_diff_lut;  // Rotate the vector v by the quaternion q  vec3 quaternion_rotate(vec4 q, vec3 v) {      return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v);  } +)"; + +    if (config.state.proctex.enable) +        AppendProcTexSampler(out, config); + +    out += R"(  void main() {  vec4 primary_fragment_color = vec4(0.0);  vec4 secondary_fragment_color = vec4(0.0); diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index 3fb046b76..ea6d216d1 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -113,6 +113,19 @@ union PicaShaderConfig {              } lut_d0, lut_d1, lut_fr, lut_rr, lut_rg, lut_rb;          } lighting; +        struct { +            bool enable; +            u32 coord; +            Pica::TexturingRegs::ProcTexClamp u_clamp, v_clamp; +            Pica::TexturingRegs::ProcTexCombiner color_combiner, alpha_combiner; +            bool separate_alpha; +            bool noise_enable; +            Pica::TexturingRegs::ProcTexShift u_shift, v_shift; +            u32 lut_width; +            u32 lut_offset; +            Pica::TexturingRegs::ProcTexFilter lut_filter; +        } proctex; +      } state;  };  #if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER) diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index 3c03b424a..bf837a7fb 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -58,6 +58,12 @@ OpenGLState::OpenGLState() {      fog_lut.texture_1d = 0; +    proctex_lut.texture_1d = 0; +    proctex_diff_lut.texture_1d = 0; +    proctex_color_map.texture_1d = 0; +    proctex_alpha_map.texture_1d = 0; +    proctex_noise_lut.texture_1d = 0; +      draw.read_framebuffer = 0;      draw.draw_framebuffer = 0;      draw.vertex_array = 0; @@ -201,6 +207,36 @@ void OpenGLState::Apply() const {          glBindTexture(GL_TEXTURE_1D, fog_lut.texture_1d);      } +    // ProcTex Noise LUT +    if (proctex_noise_lut.texture_1d != cur_state.proctex_noise_lut.texture_1d) { +        glActiveTexture(GL_TEXTURE10); +        glBindTexture(GL_TEXTURE_1D, proctex_noise_lut.texture_1d); +    } + +    // ProcTex Color Map +    if (proctex_color_map.texture_1d != cur_state.proctex_color_map.texture_1d) { +        glActiveTexture(GL_TEXTURE11); +        glBindTexture(GL_TEXTURE_1D, proctex_color_map.texture_1d); +    } + +    // ProcTex Alpha Map +    if (proctex_alpha_map.texture_1d != cur_state.proctex_alpha_map.texture_1d) { +        glActiveTexture(GL_TEXTURE12); +        glBindTexture(GL_TEXTURE_1D, proctex_alpha_map.texture_1d); +    } + +    // ProcTex LUT +    if (proctex_lut.texture_1d != cur_state.proctex_lut.texture_1d) { +        glActiveTexture(GL_TEXTURE13); +        glBindTexture(GL_TEXTURE_1D, proctex_lut.texture_1d); +    } + +    // ProcTex Diff LUT +    if (proctex_diff_lut.texture_1d != cur_state.proctex_diff_lut.texture_1d) { +        glActiveTexture(GL_TEXTURE14); +        glBindTexture(GL_TEXTURE_1D, proctex_diff_lut.texture_1d); +    } +      // Framebuffer      if (draw.read_framebuffer != cur_state.draw.read_framebuffer) {          glBindFramebuffer(GL_READ_FRAMEBUFFER, draw.read_framebuffer); diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index aee3c2946..7dcc03bd5 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -72,6 +72,26 @@ public:      } fog_lut;      struct { +        GLuint texture_1d; // GL_TEXTURE_BINDING_1D +    } proctex_noise_lut; + +    struct { +        GLuint texture_1d; // GL_TEXTURE_BINDING_1D +    } proctex_color_map; + +    struct { +        GLuint texture_1d; // GL_TEXTURE_BINDING_1D +    } proctex_alpha_map; + +    struct { +        GLuint texture_1d; // GL_TEXTURE_BINDING_1D +    } proctex_lut; + +    struct { +        GLuint texture_1d; // GL_TEXTURE_BINDING_1D +    } proctex_diff_lut; + +    struct {          GLuint read_framebuffer; // GL_READ_FRAMEBUFFER_BINDING          GLuint draw_framebuffer; // GL_DRAW_FRAMEBUFFER_BINDING          GLuint vertex_array;     // GL_VERTEX_ARRAY_BINDING diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index e19375466..d90c776f9 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -94,14 +94,8 @@ static std::array<GLfloat, 3 * 2> MakeOrthographicMatrix(const float width, cons      return matrix;  } -/// RendererOpenGL constructor -RendererOpenGL::RendererOpenGL() { -    resolution_width = std::max(VideoCore::kScreenTopWidth, VideoCore::kScreenBottomWidth); -    resolution_height = VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight; -} - -/// RendererOpenGL destructor -RendererOpenGL::~RendererOpenGL() {} +RendererOpenGL::RendererOpenGL() = default; +RendererOpenGL::~RendererOpenGL() = default;  /// Swap buffers (render frame)  void RendererOpenGL::SwapBuffers() { diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 87c556cff..0b4f69e8f 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -68,9 +68,6 @@ private:      EmuWindow* render_window; ///< Handle to render window -    int resolution_width;  ///< Current resolution width -    int resolution_height; ///< Current resolution height -      OpenGLState state;      // OpenGL object IDs diff --git a/src/video_core/swrasterizer/proctex.cpp b/src/video_core/swrasterizer/proctex.cpp new file mode 100644 index 000000000..b69892778 --- /dev/null +++ b/src/video_core/swrasterizer/proctex.cpp @@ -0,0 +1,223 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <cmath> +#include "common/math_util.h" +#include "video_core/swrasterizer/proctex.h" + +namespace Pica { +namespace Rasterizer { + +using ProcTexClamp = TexturingRegs::ProcTexClamp; +using ProcTexShift = TexturingRegs::ProcTexShift; +using ProcTexCombiner = TexturingRegs::ProcTexCombiner; +using ProcTexFilter = TexturingRegs::ProcTexFilter; + +static float LookupLUT(const std::array<State::ProcTex::ValueEntry, 128>& lut, float coord) { +    // For NoiseLUT/ColorMap/AlphaMap, coord=0.0 is lut[0], coord=127.0/128.0 is lut[127] and +    // coord=1.0 is lut[127]+lut_diff[127]. For other indices, the result is interpolated using +    // value entries and difference entries. +    coord *= 128; +    const int index_int = std::min(static_cast<int>(coord), 127); +    const float frac = coord - index_int; +    return lut[index_int].ToFloat() + frac * lut[index_int].DiffToFloat(); +} + +// These function are used to generate random noise for procedural texture. Their results are +// verified against real hardware, but it's not known if the algorithm is the same as hardware. +static unsigned int NoiseRand1D(unsigned int v) { +    static constexpr std::array<unsigned int, 16> table{ +        {0, 4, 10, 8, 4, 9, 7, 12, 5, 15, 13, 14, 11, 15, 2, 11}}; +    return ((v % 9 + 2) * 3 & 0xF) ^ table[(v / 9) & 0xF]; +} + +static float NoiseRand2D(unsigned int x, unsigned int y) { +    static constexpr std::array<unsigned int, 16> table{ +        {10, 2, 15, 8, 0, 7, 4, 5, 5, 13, 2, 6, 13, 9, 3, 14}}; +    unsigned int u2 = NoiseRand1D(x); +    unsigned int v2 = NoiseRand1D(y); +    v2 += ((u2 & 3) == 1) ? 4 : 0; +    v2 ^= (u2 & 1) * 6; +    v2 += 10 + u2; +    v2 &= 0xF; +    v2 ^= table[u2]; +    return -1.0f + v2 * 2.0f / 15.0f; +} + +static float NoiseCoef(float u, float v, TexturingRegs regs, State::ProcTex state) { +    const float freq_u = float16::FromRaw(regs.proctex_noise_frequency.u).ToFloat32(); +    const float freq_v = float16::FromRaw(regs.proctex_noise_frequency.v).ToFloat32(); +    const float phase_u = float16::FromRaw(regs.proctex_noise_u.phase).ToFloat32(); +    const float phase_v = float16::FromRaw(regs.proctex_noise_v.phase).ToFloat32(); +    const float x = 9 * freq_u * std::abs(u + phase_u); +    const float y = 9 * freq_v * std::abs(v + phase_v); +    const int x_int = static_cast<int>(x); +    const int y_int = static_cast<int>(y); +    const float x_frac = x - x_int; +    const float y_frac = y - y_int; + +    const float g0 = NoiseRand2D(x_int, y_int) * (x_frac + y_frac); +    const float g1 = NoiseRand2D(x_int + 1, y_int) * (x_frac + y_frac - 1); +    const float g2 = NoiseRand2D(x_int, y_int + 1) * (x_frac + y_frac - 1); +    const float g3 = NoiseRand2D(x_int + 1, y_int + 1) * (x_frac + y_frac - 2); +    const float x_noise = LookupLUT(state.noise_table, x_frac); +    const float y_noise = LookupLUT(state.noise_table, y_frac); +    return Math::BilinearInterp(g0, g1, g2, g3, x_noise, y_noise); +} + +static float GetShiftOffset(float v, ProcTexShift mode, ProcTexClamp clamp_mode) { +    const float offset = (clamp_mode == ProcTexClamp::MirroredRepeat) ? 1 : 0.5f; +    switch (mode) { +    case ProcTexShift::None: +        return 0; +    case ProcTexShift::Odd: +        return offset * (((int)v / 2) % 2); +    case ProcTexShift::Even: +        return offset * ((((int)v + 1) / 2) % 2); +    default: +        LOG_CRITICAL(HW_GPU, "Unknown shift mode %u", static_cast<u32>(mode)); +        return 0; +    } +}; + +static void ClampCoord(float& coord, ProcTexClamp mode) { +    switch (mode) { +    case ProcTexClamp::ToZero: +        if (coord > 1.0f) +            coord = 0.0f; +        break; +    case ProcTexClamp::ToEdge: +        coord = std::min(coord, 1.0f); +        break; +    case ProcTexClamp::SymmetricalRepeat: +        coord = coord - std::floor(coord); +        break; +    case ProcTexClamp::MirroredRepeat: { +        int integer = static_cast<int>(coord); +        float frac = coord - integer; +        coord = (integer % 2) == 0 ? frac : (1.0f - frac); +        break; +    } +    case ProcTexClamp::Pulse: +        if (coord <= 0.5f) +            coord = 0.0f; +        else +            coord = 1.0f; +        break; +    default: +        LOG_CRITICAL(HW_GPU, "Unknown clamp mode %u", static_cast<u32>(mode)); +        coord = std::min(coord, 1.0f); +        break; +    } +} + +float CombineAndMap(float u, float v, ProcTexCombiner combiner, +                    const std::array<State::ProcTex::ValueEntry, 128>& map_table) { +    float f; +    switch (combiner) { +    case ProcTexCombiner::U: +        f = u; +        break; +    case ProcTexCombiner::U2: +        f = u * u; +        break; +    case TexturingRegs::ProcTexCombiner::V: +        f = v; +        break; +    case TexturingRegs::ProcTexCombiner::V2: +        f = v * v; +        break; +    case TexturingRegs::ProcTexCombiner::Add: +        f = (u + v) * 0.5f; +        break; +    case TexturingRegs::ProcTexCombiner::Add2: +        f = (u * u + v * v) * 0.5f; +        break; +    case TexturingRegs::ProcTexCombiner::SqrtAdd2: +        f = std::min(std::sqrt(u * u + v * v), 1.0f); +        break; +    case TexturingRegs::ProcTexCombiner::Min: +        f = std::min(u, v); +        break; +    case TexturingRegs::ProcTexCombiner::Max: +        f = std::max(u, v); +        break; +    case TexturingRegs::ProcTexCombiner::RMax: +        f = std::min(((u + v) * 0.5f + std::sqrt(u * u + v * v)) * 0.5f, 1.0f); +        break; +    default: +        LOG_CRITICAL(HW_GPU, "Unknown combiner %u", static_cast<u32>(combiner)); +        f = 0.0f; +        break; +    } +    return LookupLUT(map_table, f); +} + +Math::Vec4<u8> ProcTex(float u, float v, TexturingRegs regs, State::ProcTex state) { +    u = std::abs(u); +    v = std::abs(v); + +    // Get shift offset before noise generation +    const float u_shift = GetShiftOffset(v, regs.proctex.u_shift, regs.proctex.u_clamp); +    const float v_shift = GetShiftOffset(u, regs.proctex.v_shift, regs.proctex.v_clamp); + +    // Generate noise +    if (regs.proctex.noise_enable) { +        float noise = NoiseCoef(u, v, regs, state); +        u += noise * regs.proctex_noise_u.amplitude / 4095.0f; +        v += noise * regs.proctex_noise_v.amplitude / 4095.0f; +        u = std::abs(u); +        v = std::abs(v); +    } + +    // Shift +    u += u_shift; +    v += v_shift; + +    // Clamp +    ClampCoord(u, regs.proctex.u_clamp); +    ClampCoord(v, regs.proctex.v_clamp); + +    // Combine and map +    const float lut_coord = CombineAndMap(u, v, regs.proctex.color_combiner, state.color_map_table); + +    // Look up the color +    // For the color lut, coord=0.0 is lut[offset] and coord=1.0 is lut[offset+width-1] +    const u32 offset = regs.proctex_lut_offset; +    const u32 width = regs.proctex_lut.width; +    const float index = offset + (lut_coord * (width - 1)); +    Math::Vec4<u8> final_color; +    // TODO(wwylele): implement mipmap +    switch (regs.proctex_lut.filter) { +    case ProcTexFilter::Linear: +    case ProcTexFilter::LinearMipmapLinear: +    case ProcTexFilter::LinearMipmapNearest: { +        const int index_int = static_cast<int>(index); +        const float frac = index - index_int; +        const auto color_value = state.color_table[index_int].ToVector().Cast<float>(); +        const auto color_diff = state.color_diff_table[index_int].ToVector().Cast<float>(); +        final_color = (color_value + frac * color_diff).Cast<u8>(); +        break; +    } +    case ProcTexFilter::Nearest: +    case ProcTexFilter::NearestMipmapLinear: +    case ProcTexFilter::NearestMipmapNearest: +        final_color = state.color_table[static_cast<int>(std::round(index))].ToVector(); +        break; +    } + +    if (regs.proctex.separate_alpha) { +        // Note: in separate alpha mode, the alpha channel skips the color LUT look up stage. It +        // uses the output of CombineAndMap directly instead. +        const float final_alpha = +            CombineAndMap(u, v, regs.proctex.alpha_combiner, state.alpha_map_table); +        return Math::MakeVec<u8>(final_color.rgb(), static_cast<u8>(final_alpha * 255)); +    } else { +        return final_color; +    } +} + +} // namespace Rasterizer +} // namespace Pica diff --git a/src/video_core/swrasterizer/proctex.h b/src/video_core/swrasterizer/proctex.h new file mode 100644 index 000000000..036e4620e --- /dev/null +++ b/src/video_core/swrasterizer/proctex.h @@ -0,0 +1,16 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/common_types.h" +#include "common/vector_math.h" +#include "video_core/pica_state.h" + +namespace Pica { +namespace Rasterizer { + +/// Generates procedural texture color for the given coordinates +Math::Vec4<u8> ProcTex(float u, float v, TexturingRegs regs, State::ProcTex state); + +} // namespace Rasterizer +} // namespace Pica diff --git a/src/video_core/swrasterizer/rasterizer.cpp b/src/video_core/swrasterizer/rasterizer.cpp index 20addf0bd..8b7b1defb 100644 --- a/src/video_core/swrasterizer/rasterizer.cpp +++ b/src/video_core/swrasterizer/rasterizer.cpp @@ -5,6 +5,7 @@  #include <algorithm>  #include <array>  #include <cmath> +#include <tuple>  #include "common/assert.h"  #include "common/bit_field.h"  #include "common/color.h" @@ -23,6 +24,7 @@  #include "video_core/regs_texturing.h"  #include "video_core/shader/shader.h"  #include "video_core/swrasterizer/framebuffer.h" +#include "video_core/swrasterizer/proctex.h"  #include "video_core/swrasterizer/rasterizer.h"  #include "video_core/swrasterizer/texturing.h"  #include "video_core/texture/texture_decode.h" @@ -69,6 +71,49 @@ static int SignedArea(const Math::Vec2<Fix12P4>& vtx1, const Math::Vec2<Fix12P4>      return Math::Cross(vec1, vec2).z;  }; +/// Convert a 3D vector for cube map coordinates to 2D texture coordinates along with the face name +static std::tuple<float24, float24, PAddr> ConvertCubeCoord(float24 u, float24 v, float24 w, +                                                            const TexturingRegs& regs) { +    const float abs_u = std::abs(u.ToFloat32()); +    const float abs_v = std::abs(v.ToFloat32()); +    const float abs_w = std::abs(w.ToFloat32()); +    float24 x, y, z; +    PAddr addr; +    if (abs_u > abs_v && abs_u > abs_w) { +        if (u > float24::FromFloat32(0)) { +            addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::PositiveX); +            y = -v; +        } else { +            addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::NegativeX); +            y = v; +        } +        x = -w; +        z = u; +    } else if (abs_v > abs_w) { +        if (v > float24::FromFloat32(0)) { +            addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::PositiveY); +            x = u; +        } else { +            addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::NegativeY); +            x = -u; +        } +        y = w; +        z = v; +    } else { +        if (w > float24::FromFloat32(0)) { +            addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::PositiveZ); +            y = -v; +        } else { +            addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::NegativeZ); +            y = v; +        } +        x = u; +        z = w; +    } +    const float24 half = float24::FromFloat32(0.5f); +    return std::make_tuple(x / z * half + half, y / z * half + half, addr); +} +  MICROPROFILE_DEFINE(GPU_Rasterization, "GPU", "Rasterization", MP_RGB(50, 50, 240));  /** @@ -268,7 +313,7 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve              uv[2].u() = GetInterpolatedAttribute(v0.tc2.u(), v1.tc2.u(), v2.tc2.u());              uv[2].v() = GetInterpolatedAttribute(v0.tc2.v(), v1.tc2.v(), v2.tc2.v()); -            Math::Vec4<u8> texture_color[3]{}; +            Math::Vec4<u8> texture_color[4]{};              for (int i = 0; i < 3; ++i) {                  const auto& texture = textures[i];                  if (!texture.enabled) @@ -283,10 +328,16 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve                  // Only unit 0 respects the texturing type (according to 3DBrew)                  // TODO: Refactor so cubemaps and shadowmaps can be handled +                PAddr texture_address = texture.config.GetPhysicalAddress();                  if (i == 0) {                      switch (texture.config.type) {                      case TexturingRegs::TextureConfig::Texture2D:                          break; +                    case TexturingRegs::TextureConfig::TextureCube: { +                        auto w = GetInterpolatedAttribute(v0.tc0_w, v1.tc0_w, v2.tc0_w); +                        std::tie(u, v, texture_address) = ConvertCubeCoord(u, v, w, regs.texturing); +                        break; +                    }                      case TexturingRegs::TextureConfig::Projection2D: {                          auto tc0_w = GetInterpolatedAttribute(v0.tc0_w, v1.tc0_w, v2.tc0_w);                          u /= tc0_w; @@ -321,8 +372,7 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve                      t = texture.config.height - 1 -                          GetWrappedTexCoord(texture.config.wrap_t, t, texture.config.height); -                    u8* texture_data = -                        Memory::GetPhysicalPointer(texture.config.GetPhysicalAddress()); +                    const u8* texture_data = Memory::GetPhysicalPointer(texture_address);                      auto info =                          Texture::TextureInfo::FromPicaRegister(texture.config, texture.format); @@ -334,6 +384,13 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve                  }              } +            // sample procedural texture +            if (regs.texturing.main_config.texture3_enable) { +                const auto& proctex_uv = uv[regs.texturing.main_config.texture3_coordinates]; +                texture_color[3] = ProcTex(proctex_uv.u().ToFloat32(), proctex_uv.v().ToFloat32(), +                                           g_state.regs.texturing, g_state.proctex); +            } +              // Texture environment - consists of 6 stages of color and alpha combining.              //              // Color combiners take three input color values from some source (e.g. interpolated @@ -376,6 +433,9 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve                      case Source::Texture2:                          return texture_color[2]; +                    case Source::Texture3: +                        return texture_color[3]; +                      case Source::PreviousBuffer:                          return combiner_buffer; diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h index 4aba19ca0..94e0867f0 100644 --- a/src/video_core/video_core.h +++ b/src/video_core/video_core.h @@ -15,21 +15,6 @@ class RendererBase;  namespace VideoCore { -// 3DS Video Constants -// ------------------- - -// NOTE: The LCDs actually rotate the image 90 degrees when displaying. Because of that the -// framebuffers in video memory are stored in column-major order and rendered sideways, causing -// the widths and heights of the framebuffers read by the LCD to be switched compared to the -// heights and widths of the screens listed here. -static const int kScreenTopWidth = 400;     ///< 3DS top screen width -static const int kScreenTopHeight = 240;    ///< 3DS top screen height -static const int kScreenBottomWidth = 320;  ///< 3DS bottom screen width -static const int kScreenBottomHeight = 240; ///< 3DS bottom screen height - -//  Video core renderer -// --------------------- -  extern std::unique_ptr<RendererBase> g_renderer; ///< Renderer plugin  extern EmuWindow* g_emu_window;                  ///< Emu window  | 
