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 |