diff options
Diffstat (limited to '')
234 files changed, 8756 insertions, 9662 deletions
diff --git a/.gitmodules b/.gitmodules index dbb1b0dd3..f98725622 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "xbyak"] path = externals/xbyak url = https://github.com/herumi/xbyak.git +[submodule "cryptopp"] + path = externals/cryptopp/cryptopp + url = https://github.com/weidai11/cryptopp.git diff --git a/.travis-upload.sh b/.travis-upload.sh index 9aed815d4..2cc968298 100755 --- a/.travis-upload.sh +++ b/.travis-upload.sh @@ -5,12 +5,16 @@ if [ "$TRAVIS_EVENT_TYPE" = "push" ]&&[ "$TRAVIS_BRANCH" = "master" ]; then if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then REV_NAME="citra-linux-${GITDATE}-${GITREV}" + ARCHIVE_NAME="${REV_NAME}.tar.xz" + COMPRESSION_FLAGS="-cJvf" mkdir "$REV_NAME" cp build/src/citra/citra "$REV_NAME" cp build/src/citra_qt/citra-qt "$REV_NAME" elif [ "$TRAVIS_OS_NAME" = "osx" ]; then REV_NAME="citra-osx-${GITDATE}-${GITREV}" + ARCHIVE_NAME="${REV_NAME}.tar.gz" + COMPRESSION_FLAGS="-czvf" mkdir "$REV_NAME" cp build/src/citra/Release/citra "$REV_NAME" @@ -118,8 +122,7 @@ EOL cp license.txt "$REV_NAME" cp README.md "$REV_NAME" - ARCHIVE_NAME="${REV_NAME}.tar.xz" - tar -cJvf "$ARCHIVE_NAME" "$REV_NAME" + tar $COMPRESSION_FLAGS "$ARCHIVE_NAME" "$REV_NAME" # move the compiled archive into the artifacts directory to be uploaded by travis releases mv "$ARCHIVE_NAME" artifacts/ diff --git a/.travis.yml b/.travis.yml index cf1e1e26c..cdb638f7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ deploy: api_key: secure: Mck15DIWaJdxDiS3aYVlM9N3G6y8VKUI1rnwII7/iolfm1s94U+tgvbheZDmT7SSbFyaGaYO/E8HrV/uZR9Vvs7ev20sHsTN1u60OTWfDIIyHs9SqjhcGbtq95m9/dMFschOYqTOR+gAs5BsxjuoeAotHdhpQEwvkO2oo5oR0zhGy45gjFnVvtcxT/IfpZBIpVgcK3aLb9zT6ekcJbSiPmEB15iLq3xXd0nFUNtEZdX3D6Veye4n5jB6n72qN8JVoKvPZAwaC2K0pZxpcGJaXDchLsw1q+4eCvdz6UJfUemeQ/uMAmjfeQ3wrzYGXe3nCM3WmX5wosCsB0mw4zYatzl3si6CZ1W+0GkV4Rwlx03dfp7v3EeFhTsXYCaXqhwuLZnWOLUik8t9vaSoFUx4nUIRwfO9kAMUJQSpLuHNO2nT01s3GxvqxzczuLQ9he5nGSi0RRodUzDwek1qUp6I4uV3gRHKz4B07YIc1i2fK88NLXjyQ0uLVZ+7Oq1+kgDp6+N7vvXXZ5qZ17tdaysSbKEE0Y8zsoXw7Rk1tPN19vrCS+TSpomNMyQyne1k+I5iZ/qkxPTLAS5qI6Utc2dL3GJdxWRAEfGNO9AIX3GV/jmmKfdcvwGsCYP8hxqs5vLYfgacw3D8NLf1941lQUwavC17jm9EV9g5G3Pn1Cp516E= file_glob: true - file: "artifacts/*.tar.xz" + file: "artifacts/*.tar.*" skip_cleanup: true on: - repo: citra-emu/citra-nightly
\ No newline at end of file + repo: citra-emu/citra-nightly diff --git a/CMakeLists.txt b/CMakeLists.txt index ce9a29032..306959e24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,8 +72,6 @@ if (NOT MSVC) if (MINGW) add_definitions(-DMINGW_HAS_SECURE_API) - # Microprofile causes crashes when launching titles on MinGW - add_definitions(-DMICROPROFILE_ENABLED=0) if (MINGW_STATIC_BUILD) add_definitions(-DQT_STATICPLUGIN) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 55f520cb4..f0b012ed9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,6 @@ # Reporting Issues -**The issue tracker is not a support forum.** Unless you can provide precise *technical information* regarding an issue, you *should not post in it*. If you need support, first read the [FAQ](https://github.com/citra-emu/citra/wiki/FAQ) and then either visit our IRC channel, [our forum](https://community.citra-emu.org) -.citra-emu.org/) or ask in a general emulation forum such as [/r/emulation](https://www.reddit.com/r/emulation/). If you post support questions, generic messages to the developers or vague reports without technical details, they will be closed and locked. +**The issue tracker is not a support forum.** Unless you can provide precise *technical information* regarding an issue, you *should not post in it*. If you need support, first read the [FAQ](https://github.com/citra-emu/citra/wiki/FAQ) and then either visit our IRC channel, [our forum](https://community.citra-emu.org) or ask in a general emulation forum such as [/r/emulation](https://www.reddit.com/r/emulation/). If you post support questions, generic messages to the developers or vague reports without technical details, they will be closed and locked. If you believe you have a valid issue report, please post text or a screenshot from the log (the console window that opens alongside Citra) and build version (hex string visible in the titlebar and zip filename), as well as your hardware and software information if applicable. @@ -7,7 +7,7 @@ Citra Emulator Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and macOS. Citra only emulates a subset of 3DS hardware, and therefore is generally only useful for running/debugging homebrew applications. At this time, Citra is even able to boot several commercial games! Most of these do not run to a playable state, but we are working every day to advance the project forward. -Citra is licensed under the GPLv2 (or any later version). Refer to the license.txt file included. Please read the [FAQ](https://citra-emu.org/wikis/faq) before getting started with the project. +Citra is licensed under the GPLv2 (or any later version). Refer to the license.txt file included. Please read the [FAQ](https://citra-emu.org/wiki/FAQ/) before getting started with the project. Check out our [website](https://citra-emu.org/)! @@ -27,7 +27,7 @@ If you want to contribute please take a look at the [Contributor's Guide](CONTRI ### Support -We happily accept monetary donations, or donated games and hardware. Please see our [donations page](https://citra-emu.org/page/donate) for more information on how you can contribute to Citra. Any donations received will go towards things like: +We happily accept monetary donations, or donated games and hardware. Please see our [donations page](https://citra-emu.org/donate/) for more information on how you can contribute to Citra. Any donations received will go towards things like: * 3DS consoles for developers to explore the hardware * 3DS games for testing * Any equipment required for homebrew diff --git a/appveyor.yml b/appveyor.yml index c07559479..2a4a15d40 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,6 +20,8 @@ install: - git submodule update --init --recursive before_build: + # Xamarin log spam workaround + - del "C:\Program Files (x86)\MSBuild\14.0\Microsoft.Common.targets\ImportAfter\Xamarin.Common.targets" - mkdir build - cd build - cmake -G "Visual Studio 14 2015 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 .. diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 7e4b05ffc..309e98464 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -6,3 +6,6 @@ if (ARCHITECTURE_x86_64) target_compile_options(xbyak INTERFACE -fno-operator-names) endif() endif() + +add_subdirectory(cryptopp) + diff --git a/externals/boost b/externals/boost -Subproject f005c955f8147a29667aa0b65257abc3dd520b0 +Subproject 351972396392c97a659b9a02f34ce9269293d21 diff --git a/externals/cryptopp/CMakeLists.txt b/externals/cryptopp/CMakeLists.txt new file mode 100644 index 000000000..653af1e4b --- /dev/null +++ b/externals/cryptopp/CMakeLists.txt @@ -0,0 +1,161 @@ +# The CMakeLists.txt shipped with cryptopp pollutes our option list and installation list, +# so we made our own one. This is basically a trimmed down version of the shipped CMakeLists.txt +# The differences are: +# - removed support for legacy CMake versions +# - removed support for 32-bit +# - removed -march=native flag +# - removed rdrand module.asm as a workaround for an issue (see below) +# - added prefix "CRYPTOPP_" to all option names +# - disabled testing +# - disabled installation +# - disabled documentation +# - configured to build a static library only + +include(TestBigEndian) +include(CheckCXXCompilerFlag) + +#============================================================================ +# Settable options +#============================================================================ + +option(CRYPTOPP_DISABLE_ASM "Disable ASM" OFF) +option(CRYPTOPP_DISABLE_SSSE3 "Disable SSSE3" OFF) +option(CRYPTOPP_DISABLE_AESNI "Disable AES-NI" OFF) +option(CRYPTOPP_DISABLE_CXXFLAGS_OPTIMIZATIONS "Disable CXXFLAGS optimizations" OFF) + +#============================================================================ +# Internal compiler options +#============================================================================ + +# Only set when cross-compiling, http://www.vtk.org/Wiki/CMake_Cross_Compiling +if (NOT (CMAKE_SYSTEM_VERSION AND CMAKE_SYSTEM_PROCESSOR)) + set(CRYPTOPP_CROSS_COMPILE 1) +else() + set(CRYPTOPP_CROSS_COMPILE 0) +endif() + +# Don't use RPATH's. The resulting binary could fail a security audit. +if (NOT CMAKE_VERSION VERSION_LESS 2.8.12) + set(CMAKE_MACOSX_RPATH 0) +endif() + +if(CMAKE_CXX_COMPILER_ID MATCHES "Intel") + add_definitions(-wd68 -wd186 -wd279 -wd327 -wd161 -wd3180) +endif() + +# Endianness +TEST_BIG_ENDIAN(IS_BIG_ENDIAN) +if(IS_BIG_ENDIAN) + add_definitions(-DIS_BIG_ENDIAN) +endif() + +if(CRYPTOPP_DISABLE_ASM) + add_definitions(-DCRYPTOPP_DISABLE_ASM) +endif() +if(CRYPTOPP_DISABLE_SSSE3) + add_definitions(-DCRYPTOPP_DISABLE_SSSE3) +endif() +if(CRYPTOPP_DISABLE_AESNI) + add_definitions(-DCRYPTOPP_DISABLE_AESNI) +endif() + +# We need the output 'uname -s' for Unix and Linux system detection +if (NOT CRYPTOPP_CROSS_COMPILE) + set (UNAME_CMD "uname") + set (UNAME_ARG "-s") + execute_process(COMMAND ${UNAME_CMD} ${UNAME_ARG} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE UNAME_RESULT + OUTPUT_VARIABLE UNAME_SYSTEM) + string(REGEX REPLACE "\n$" "" UNAME_SYSTEM "${UNAME_SYSTEM}") +endif() + +# We need the output 'uname -m' for Unix and Linux platform detection +if (NOT CRYPTOPP_CROSS_COMPILE) + set (UNAME_CMD "uname") + set (UNAME_ARG "-m") + execute_process(COMMAND ${UNAME_CMD} ${UNAME_ARG} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE UNAME_RESULT + OUTPUT_VARIABLE UNAME_MACHINE) + string(REGEX REPLACE "\n$" "" UNAME_MACHINE "${UNAME_MACHINE}") +endif() + +if(WINDOWS_STORE OR WINDOWS_PHONE) + if("${CMAKE_SYSTEM_VERSION}" MATCHES "10\\.0.*") + SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D\"_WIN32_WINNT=0x0A00\"" ) + endif() + SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FI\"winapifamily.h\"" ) +endif() + +# Enable PIC for all targets except Windows and 32-bit x86. +# Avoid on 32-bit x86 due to register pressures. +if ((NOT CRYPTOPP_CROSS_COMPILE) AND (NOT (WINDOWS OR WINDOWS_STORE OR WINDOWS_PHONE))) + # Use Regex; match i386, i486, i586 and i686 + if (NOT (${UNAME_MACHINE} MATCHES "i.86")) + SET(CMAKE_POSITION_INDEPENDENT_CODE 1) + endif() +endif() + +# Link is driven through the compiler, but CXXFLAGS are not used. Also see +# http://public.kitware.com/pipermail/cmake/2003-June/003967.html +if (NOT (WINDOWS OR WINDOWS_STORE OR WINDOWS_PHONE)) + SET(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_FLAGS}") +endif() + +#============================================================================ +# Sources & headers +#============================================================================ + +# Library headers +file(GLOB cryptopp_HEADERS cryptopp/*.h) + +# Library sources. You can use the GNUmakefile to generate the list: `make sources`. +file(GLOB cryptopp_SOURCES cryptopp/*.cpp) +list(REMOVE_ITEM cryptopp_SOURCES + # These are removed in the original CMakeLists.txt + ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/pch.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/simple.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/winpipes.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/cryptlib_bds.cpp + ${cryptopp_SOURCES_TEST} + ) + +if(MINGW OR WIN32) + list(APPEND cryptopp_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/winpipes.cpp) +endif() + +if(MSVC AND NOT CRYPTOPP_DISABLE_ASM) + if(${CMAKE_GENERATOR} MATCHES ".*ARM") + message(STATUS "Disabling ASM because ARM is specified as target platform.") + else() + # Note that we removed rdrand.asm. This is a workaround for the issue that rdrand.asm cannnot compiled properly + # on MSVC. Because there is also a rdrand.S file in the submodule, CMake will specify the target path for + # rdrand.asm as "/crytopp.dir/{Debug|Release}/cryptopp/rdrand.asm.obj". The additional target folder "cryptopp" + # is specified because the file rdrand.asm is in the source folder "cryptopp". But MSVC assembler can't build + # target file to an non-existing folder("cryptopp"). + list(APPEND cryptopp_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/x64dll.asm) + list(APPEND cryptopp_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/x64masm.asm) + # list(APPEND cryptopp_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/rdrand.asm) + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/x64dll.asm PROPERTIES COMPILE_FLAGS "/D_M_X64") + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/x64masm.asm PROPERTIES COMPILE_FLAGS "/D_M_X64") + # set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/rdrand.asm PROPERTIES COMPILE_FLAGS "/D_M_X64") + enable_language(ASM_MASM) + endif() +endif() + +#============================================================================ +# Compile targets +#============================================================================ +add_library(cryptopp STATIC ${cryptopp_SOURCES}) + +#============================================================================ +# Third-party libraries +#============================================================================ + +if(WIN32) + target_link_libraries(cryptopp ws2_32) +endif() + +find_package(Threads) +target_link_libraries(cryptopp ${CMAKE_THREAD_LIBS_INIT}) diff --git a/externals/cryptopp/cryptopp b/externals/cryptopp/cryptopp new file mode 160000 +Subproject 841c37e34765487a2968357369ab74db8b10a62 diff --git a/externals/dynarmic b/externals/dynarmic -Subproject 36082087ded632079b16d24137fdd0c450ce82e +Subproject 358cf7c32205a5114964865c86a8455daf81073 diff --git a/externals/microprofile/microprofile.h b/externals/microprofile/microprofile.h index f45c9ba82..384863ccc 100644 --- a/externals/microprofile/microprofile.h +++ b/externals/microprofile/microprofile.h @@ -201,7 +201,7 @@ typedef uint64_t ThreadIdType; int64_t MicroProfileGetTick(); #define MP_TICK() MicroProfileGetTick() #define MP_BREAK() __debugbreak() -#define MP_THREAD_LOCAL __declspec(thread) +#define MP_THREAD_LOCAL thread_local #define MP_STRCASECMP _stricmp #define MP_GETCURRENTTHREADID() GetCurrentThreadId() typedef uint32_t ThreadIdType; diff --git a/externals/microprofile/microprofileui.h b/externals/microprofile/microprofileui.h index 66a73abc5..09223b33f 100644 --- a/externals/microprofile/microprofileui.h +++ b/externals/microprofile/microprofileui.h @@ -1231,7 +1231,7 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY, char ThreadName[MicroProfileThreadLog::THREAD_MAX_LEN + 16]; const char* cLocal = MicroProfileIsLocalThread(nThreadId) ? "*": " "; -#if defined(WIN32) +#if defined(_WIN32) // nThreadId is 32-bit on Windows int nStrLen = snprintf(ThreadName, sizeof(ThreadName)-1, "%04x: %s%s", nThreadId, cLocal, i < nNumThreadsBase ? &S.Pool[i]->ThreadName[0] : MICROPROFILE_THREAD_NAME_FROM_ID(nThreadId) ); #else diff --git a/hooks/pre-commit b/hooks/pre-commit index 04fdaf8ec..098a99216 100755 --- a/hooks/pre-commit +++ b/hooks/pre-commit @@ -24,20 +24,3 @@ If you know what you are doing, you can try 'git commit --no-verify' to bypass t END exit 1 fi - -for f in $(git diff --name-only --diff-filter=ACMRTUXB --cached); do - if ! echo "$f" | egrep -q "[.](cpp|h)$"; then - continue - fi - if ! echo "$f" | egrep -q "^src/"; then - continue - fi - d=$(clang-format "$f" | diff -u "$f" -) - if ! [ -z "$d" ]; then - echo "!!! $f not compliant to coding style, here is the fix:" - echo "$d" - fail=1 - fi -done - -exit "${fail-0}" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1e1245160..a45439481 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(common) add_subdirectory(core) add_subdirectory(video_core) add_subdirectory(audio_core) +add_subdirectory(input_common) add_subdirectory(tests) if (ENABLE_SDL2) add_subdirectory(citra) diff --git a/src/audio_core/hle/filter.h b/src/audio_core/hle/filter.h index 4281a5898..5350e2857 100644 --- a/src/audio_core/hle/filter.h +++ b/src/audio_core/hle/filter.h @@ -27,7 +27,7 @@ public: * See also: SourceConfiguration::Configuration::simple_filter_enabled, * SourceConfiguration::Configuration::biquad_filter_enabled. * @param simple If true, enables the simple filter. If false, disables it. - * @param simple If true, enables the biquad filter. If false, disables it. + * @param biquad If true, enables the biquad filter. If false, disables it. */ void Enable(bool simple, bool biquad); diff --git a/src/audio_core/interpolate.h b/src/audio_core/interpolate.h index dd06fdda9..19a7b66cb 100644 --- a/src/audio_core/interpolate.h +++ b/src/audio_core/interpolate.h @@ -21,6 +21,7 @@ struct State { /** * No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay. + * @param state Interpolation state. * @param input Input buffer. * @param rate_multiplier Stretch factor. Must be a positive non-zero value. * rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 @@ -31,6 +32,7 @@ StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multip /** * Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay. + * @param state Interpolation state. * @param input Input buffer. * @param rate_multiplier Stretch factor. Must be a positive non-zero value. * rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h index 558c8c0fe..c69cb2c74 100644 --- a/src/audio_core/sink.h +++ b/src/audio_core/sink.h @@ -34,7 +34,7 @@ public: /** * Sets the desired output device. - * @paran device_id Id of the desired device. + * @param device_id ID of the desired device. */ virtual void SetDevice(int device_id) = 0; diff --git a/src/audio_core/time_stretch.h b/src/audio_core/time_stretch.h index e3e4dc353..c98b16705 100644 --- a/src/audio_core/time_stretch.h +++ b/src/audio_core/time_stretch.h @@ -25,7 +25,7 @@ public: /** * Add samples to be processed. * @param sample_buffer Buffer of samples in interleaved stereo PCM16 format. - * @param num_sample Number of samples. + * @param num_samples Number of samples. */ void AddSamples(const s16* sample_buffer, size_t num_samples); diff --git a/src/citra/CMakeLists.txt b/src/citra/CMakeLists.txt index ecb5d2dfe..47231ba71 100644 --- a/src/citra/CMakeLists.txt +++ b/src/citra/CMakeLists.txt @@ -18,7 +18,7 @@ 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) +target_link_libraries(citra core video_core audio_core common input_common) target_link_libraries(citra ${SDL2_LIBRARY} ${OPENGL_gl_LIBRARY} inih glad) if (MSVC) target_link_libraries(citra getopt) diff --git a/src/citra/config.cpp b/src/citra/config.cpp index fac1c9a0e..a4162e9ad 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -8,8 +8,10 @@ #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" Config::Config() { // TODO: Don't hardcode the path; let the frontend decide where to put the config files. @@ -37,25 +39,40 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) { return true; } -static const std::array<int, Settings::NativeInput::NUM_INPUTS> defaults = { - // directly mapped keys - SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_Q, SDL_SCANCODE_W, - SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_B, SDL_SCANCODE_T, - SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J, - SDL_SCANCODE_L, - - // indirectly mapped keys - SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_D, +static const std::array<int, Settings::NativeButton::NumButtons> default_buttons = { + SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_T, + SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_Q, SDL_SCANCODE_W, + SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_B, }; +static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs{{ + { + SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_D, + }, + { + SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J, SDL_SCANCODE_L, SDL_SCANCODE_D, + }, +}}; + void Config::ReadValues() { // Controls - for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - Settings::values.input_mappings[Settings::NativeInput::All[i]] = - sdl2_config->GetInteger("Controls", Settings::NativeInput::Mapping[i], defaults[i]); + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + Settings::values.buttons[i] = + sdl2_config->Get("Controls", Settings::NativeButton::mapping[i], default_param); + if (Settings::values.buttons[i].empty()) + Settings::values.buttons[i] = default_param; + } + + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_analogs[i][4], 0.5f); + Settings::values.analogs[i] = + sdl2_config->Get("Controls", Settings::NativeAnalog::mapping[i], default_param); + if (Settings::values.analogs[i].empty()) + Settings::values.analogs[i] = default_param; } - Settings::values.pad_circle_modifier_scale = - (float)sdl2_config->GetReal("Controls", "pad_circle_modifier_scale", 0.5); // Core Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); @@ -77,6 +94,23 @@ void Config::ReadValues() { Settings::values.layout_option = static_cast<Settings::LayoutOption>(sdl2_config->GetInteger("Layout", "layout_option", 0)); Settings::values.swap_screen = sdl2_config->GetBoolean("Layout", "swap_screen", false); + Settings::values.custom_layout = sdl2_config->GetBoolean("Layout", "custom_layout", false); + Settings::values.custom_top_left = + static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_left", 0)); + Settings::values.custom_top_top = + static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_top", 0)); + Settings::values.custom_top_right = + static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_right", 400)); + Settings::values.custom_top_bottom = + static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_bottom", 240)); + Settings::values.custom_bottom_left = + static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_left", 40)); + Settings::values.custom_bottom_top = + static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_top", 240)); + Settings::values.custom_bottom_right = + static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_right", 360)); + Settings::values.custom_bottom_bottom = + static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_bottom", 480)); // Audio Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 435ba6f00..084372df4 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -8,34 +8,47 @@ namespace DefaultINI { const char* sdl2_config_file = R"( [Controls] -pad_start = -pad_select = -pad_home = -pad_dup = -pad_ddown = -pad_dleft = -pad_dright = -pad_a = -pad_b = -pad_x = -pad_y = -pad_l = -pad_r = -pad_zl = -pad_zr = -pad_cup = -pad_cdown = -pad_cleft = -pad_cright = -pad_circle_up = -pad_circle_down = -pad_circle_left = -pad_circle_right = -pad_circle_modifier = - -# The applied modifier scale to circle pad. -# Must be in range of 0.0-1.0. Defaults to 0.5 -pad_circle_modifier_scale = +# The input devices and parameters for each 3DS native input +# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..." +# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values + +# for button input, the following devices are avaible: +# - "keyboard" (default) for keyboard input. Required parameters: +# - "code": the code of the key to bind +# - "sdl" for joystick input using SDL. Required parameters: +# - "joystick": the index of the joystick to bind +# - "button"(optional): the index of the button to bind +# - "hat"(optional): the index of the hat to bind as direction buttons +# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right" +button_a= +button_b= +button_x= +button_y= +button_up= +button_down= +button_left= +button_right= +button_l= +button_r= +button_start= +button_select= +button_zl= +button_zr= +button_home= + +# for analog input, the following devices are avaible: +# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters: +# - "up", "down", "left", "right": sub-devices for each direction. +# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00" +# - "modifier": sub-devices as a modifier. +# - "modifier_scale": a float number representing the applied modifier scale to the analog input. +# Must be in range of 0.0-1.0. Defaults to 0.5 +# - "sdl" for joystick input using SDL. Required parameters: +# - "joystick": the index of the joystick to bind +# - "axis_x": the index of the axis to bind as x-axis (default to 0) +# - "axis_y": the index of the axis to bind as y-axis (default to 1) +circle_pad= +c_stick= [Core] # Whether to use the Just-In-Time (JIT) compiler for CPU emulation @@ -71,6 +84,21 @@ bg_green = # 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen layout_option = +# Toggle custom layout (using the settings below) on or off. +# 0 (default): Off , 1: On +custom_layout = + +# Screen placement when using Custom layout option +# 0x, 0y is the top left corner of the render window. +custom_top_left = +custom_top_top = +custom_top_right = +custom_top_bottom = +custom_bottom_left = +custom_bottom_top = +custom_bottom_right = +custom_bottom_bottom = + #Whether to toggle frame limiter on or off. # 0: Off , 1 (default): On toggle_framelimit = diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index 81a3abe3f..6bc0b0d00 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -12,9 +12,9 @@ #include "common/logging/log.h" #include "common/scm_rev.h" #include "common/string_util.h" -#include "core/frontend/key_map.h" -#include "core/hle/service/hid/hid.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) { @@ -40,9 +40,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) { if (state == SDL_PRESSED) { - KeyMap::PressKey(*this, {key, keyboard_id}); + InputCommon::GetKeyboard()->PressKey(key); } else if (state == SDL_RELEASED) { - KeyMap::ReleaseKey(*this, {key, keyboard_id}); + InputCommon::GetKeyboard()->ReleaseKey(key); } } @@ -57,9 +57,8 @@ void EmuWindow_SDL2::OnResize() { } EmuWindow_SDL2::EmuWindow_SDL2() { - keyboard_id = KeyMap::NewDeviceId(); + InputCommon::Init(); - ReloadSetKeymaps(); motion_emu = std::make_unique<Motion::MotionEmu>(*this); SDL_SetMainReady(); @@ -79,8 +78,8 @@ EmuWindow_SDL2::EmuWindow_SDL2() { SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); - std::string window_title = - Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); + 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 @@ -117,6 +116,7 @@ EmuWindow_SDL2::~EmuWindow_SDL2() { SDL_GL_DeleteContext(gl_context); SDL_Quit(); motion_emu = nullptr; + InputCommon::Shutdown(); } void EmuWindow_SDL2::SwapBuffers() { @@ -169,15 +169,6 @@ void EmuWindow_SDL2::DoneCurrent() { SDL_GL_MakeCurrent(render_window, nullptr); } -void EmuWindow_SDL2::ReloadSetKeymaps() { - KeyMap::ClearKeyMapping(keyboard_id); - for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - KeyMap::SetKeyMapping( - {Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id}, - KeyMap::mapping_targets[i]); - } -} - void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest( const std::pair<unsigned, unsigned>& minimal_size) { diff --git a/src/citra/emu_window/emu_window_sdl2.h b/src/citra/emu_window/emu_window_sdl2.h index b1cbf16d7..1ce2991f7 100644 --- a/src/citra/emu_window/emu_window_sdl2.h +++ b/src/citra/emu_window/emu_window_sdl2.h @@ -31,9 +31,6 @@ public: /// Whether the window is still open, and a close request hasn't yet been sent bool IsOpen() const; - /// Load keymap from configuration - void ReloadSetKeymaps() override; - private: /// Called by PollEvents when a key is pressed or released. void OnKeyEvent(int key, u8 state); @@ -61,9 +58,6 @@ private: /// The OpenGL context associated with the window SDL_GLContext gl_context; - /// Device id of keyboard for use with KeyMap - int keyboard_id; - /// Motion sensors emulation std::unique_ptr<Motion::MotionEmu> motion_emu; }; diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index d4460bf01..3e6106f0a 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -3,7 +3,14 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) set(SRCS - config.cpp + configuration/config.cpp + configuration/configure_audio.cpp + configuration/configure_debug.cpp + configuration/configure_dialog.cpp + configuration/configure_general.cpp + configuration/configure_graphics.cpp + configuration/configure_input.cpp + configuration/configure_system.cpp debugger/callstack.cpp debugger/disassembler.cpp debugger/graphics/graphics.cpp @@ -19,13 +26,6 @@ set(SRCS util/spinbox.cpp util/util.cpp bootmanager.cpp - configure_audio.cpp - configure_debug.cpp - configure_dialog.cpp - configure_general.cpp - configure_graphics.cpp - configure_system.cpp - configure_input.cpp game_list.cpp hotkeys.cpp main.cpp @@ -35,7 +35,14 @@ set(SRCS ) set(HEADERS - config.h + configuration/config.h + configuration/configure_audio.h + configuration/configure_debug.h + configuration/configure_dialog.h + configuration/configure_general.h + configuration/configure_graphics.h + configuration/configure_input.h + configuration/configure_system.h debugger/callstack.h debugger/disassembler.h debugger/graphics/graphics.h @@ -52,13 +59,6 @@ set(HEADERS util/spinbox.h util/util.h bootmanager.h - configure_audio.h - configure_debug.h - configure_dialog.h - configure_general.h - configure_graphics.h - configure_system.h - configure_input.h game_list.h game_list_p.h hotkeys.h @@ -67,17 +67,16 @@ set(HEADERS ) set(UIS + configuration/configure.ui + configuration/configure_audio.ui + configuration/configure_debug.ui + configuration/configure_general.ui + configuration/configure_graphics.ui + configuration/configure_input.ui + configuration/configure_system.ui debugger/callstack.ui debugger/disassembler.ui - debugger/profiler.ui debugger/registers.ui - configure.ui - configure_audio.ui - configure_debug.ui - configure_general.ui - configure_graphics.ui - configure_system.ui - configure_input.ui hotkeys.ui main.ui ) @@ -98,7 +97,7 @@ if (APPLE) else() add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS}) endif() -target_link_libraries(citra-qt core video_core audio_core common) +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) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 948db384d..bae576d6a 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -13,7 +13,9 @@ #include "common/scm_rev.h" #include "common/string_util.h" #include "core/core.h" -#include "core/frontend/key_map.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" @@ -99,14 +101,17 @@ private: }; GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) - : QWidget(parent), child(nullptr), keyboard_id(0), emu_thread(emu_thread) { + : QWidget(parent), child(nullptr), emu_thread(emu_thread) { - std::string window_title = - Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); + std::string window_title = Common::StringFromFormat("Citra %s| %s-%s", Common::g_build_name, + Common::g_scm_branch, Common::g_scm_desc); setWindowTitle(QString::fromStdString(window_title)); - keyboard_id = KeyMap::NewDeviceId(); - ReloadSetKeymaps(); + InputCommon::Init(); +} + +GRenderWindow::~GRenderWindow() { + InputCommon::Shutdown(); } void GRenderWindow::moveContext() { @@ -197,11 +202,11 @@ void GRenderWindow::closeEvent(QCloseEvent* event) { } void GRenderWindow::keyPressEvent(QKeyEvent* event) { - KeyMap::PressKey(*this, {event->key(), keyboard_id}); + InputCommon::GetKeyboard()->PressKey(event->key()); } void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { - KeyMap::ReleaseKey(*this, {event->key(), keyboard_id}); + InputCommon::GetKeyboard()->ReleaseKey(event->key()); } void GRenderWindow::mousePressEvent(QMouseEvent* event) { @@ -230,13 +235,9 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { motion_emu->EndTilt(); } -void GRenderWindow::ReloadSetKeymaps() { - KeyMap::ClearKeyMapping(keyboard_id); - for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - KeyMap::SetKeyMapping( - {Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id}, - KeyMap::mapping_targets[i]); - } +void GRenderWindow::focusOutEvent(QFocusEvent* event) { + QWidget::focusOutEvent(event); + InputCommon::GetKeyboard()->ReleaseAllKeys(); } void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) { diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 7dac1c480..9d39f1af8 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -104,6 +104,7 @@ class GRenderWindow : public QWidget, public EmuWindow { public: GRenderWindow(QWidget* parent, EmuThread* emu_thread); + ~GRenderWindow(); // EmuWindow implementation void SwapBuffers() override; @@ -127,7 +128,7 @@ public: void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; - void ReloadSetKeymaps() override; + void focusOutEvent(QFocusEvent* event) override; void OnClientAreaResized(unsigned width, unsigned height); @@ -152,9 +153,6 @@ private: QByteArray geometry; - /// Device id of keyboard for use with KeyMap - int keyboard_id; - EmuThread* emu_thread; /// Motion sensors emulation diff --git a/src/citra_qt/config.cpp b/src/citra_qt/configuration/config.cpp index b65f57fdc..0b9b73f9e 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -3,9 +3,10 @@ // Refer to the license.txt file included. #include <QSettings> -#include "citra_qt/config.h" +#include "citra_qt/configuration/config.h" #include "citra_qt/ui_settings.h" #include "common/file_util.h" +#include "input_common/main.h" Config::Config() { // TODO: Don't hardcode the path; let the frontend decide where to put the config files. @@ -16,25 +17,46 @@ Config::Config() { Reload(); } -const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> Config::defaults = { - // directly mapped keys - Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2, - Qt::Key_M, Qt::Key_N, Qt::Key_B, Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H, Qt::Key_I, - Qt::Key_K, Qt::Key_J, Qt::Key_L, - - // indirectly mapped keys - Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_D, +const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = { + Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H, + Qt::Key_Q, Qt::Key_W, Qt::Key_M, Qt::Key_N, Qt::Key_1, Qt::Key_2, Qt::Key_B, }; +const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ + { + Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_D, + }, + { + Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L, Qt::Key_D, + }, +}}; + void Config::ReadValues() { qt_config->beginGroup("Controls"); - for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - Settings::values.input_mappings[Settings::NativeInput::All[i]] = - qt_config->value(QString::fromStdString(Settings::NativeInput::Mapping[i]), defaults[i]) - .toInt(); + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + Settings::values.buttons[i] = + qt_config + ->value(Settings::NativeButton::mapping[i], QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (Settings::values.buttons[i].empty()) + Settings::values.buttons[i] = default_param; + } + + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_analogs[i][4], 0.5f); + Settings::values.analogs[i] = + qt_config + ->value(Settings::NativeAnalog::mapping[i], QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (Settings::values.analogs[i].empty()) + Settings::values.analogs[i] = default_param; } - Settings::values.pad_circle_modifier_scale = - qt_config->value("pad_circle_modifier_scale", 0.5).toFloat(); + qt_config->endGroup(); qt_config->beginGroup("Core"); @@ -57,6 +79,15 @@ void Config::ReadValues() { Settings::values.layout_option = static_cast<Settings::LayoutOption>(qt_config->value("layout_option").toInt()); Settings::values.swap_screen = qt_config->value("swap_screen", false).toBool(); + Settings::values.custom_layout = qt_config->value("custom_layout", false).toBool(); + Settings::values.custom_top_left = qt_config->value("custom_top_left", 0).toInt(); + Settings::values.custom_top_top = qt_config->value("custom_top_top", 0).toInt(); + Settings::values.custom_top_right = qt_config->value("custom_top_right", 400).toInt(); + Settings::values.custom_top_bottom = qt_config->value("custom_top_bottom", 240).toInt(); + Settings::values.custom_bottom_left = qt_config->value("custom_bottom_left", 40).toInt(); + Settings::values.custom_bottom_top = qt_config->value("custom_bottom_top", 240).toInt(); + Settings::values.custom_bottom_right = qt_config->value("custom_bottom_right", 360).toInt(); + Settings::values.custom_bottom_bottom = qt_config->value("custom_bottom_bottom", 480).toInt(); qt_config->endGroup(); qt_config->beginGroup("Audio"); @@ -146,6 +177,7 @@ void Config::ReadValues() { UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool(); UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool(); + UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool(); UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); @@ -154,12 +186,14 @@ void Config::ReadValues() { void Config::SaveValues() { qt_config->beginGroup("Controls"); - for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - qt_config->setValue(QString::fromStdString(Settings::NativeInput::Mapping[i]), - Settings::values.input_mappings[Settings::NativeInput::All[i]]); + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + qt_config->setValue(QString::fromStdString(Settings::NativeButton::mapping[i]), + QString::fromStdString(Settings::values.buttons[i])); + } + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]), + QString::fromStdString(Settings::values.analogs[i])); } - qt_config->setValue("pad_circle_modifier_scale", - (double)Settings::values.pad_circle_modifier_scale); qt_config->endGroup(); qt_config->beginGroup("Core"); @@ -182,6 +216,15 @@ void Config::SaveValues() { qt_config->beginGroup("Layout"); qt_config->setValue("layout_option", static_cast<int>(Settings::values.layout_option)); qt_config->setValue("swap_screen", Settings::values.swap_screen); + qt_config->setValue("custom_layout", Settings::values.custom_layout); + qt_config->setValue("custom_top_left", Settings::values.custom_top_left); + qt_config->setValue("custom_top_top", Settings::values.custom_top_top); + qt_config->setValue("custom_top_right", Settings::values.custom_top_right); + qt_config->setValue("custom_top_bottom", Settings::values.custom_top_bottom); + qt_config->setValue("custom_bottom_left", Settings::values.custom_bottom_left); + qt_config->setValue("custom_bottom_top", Settings::values.custom_bottom_top); + qt_config->setValue("custom_bottom_right", Settings::values.custom_bottom_right); + qt_config->setValue("custom_bottom_bottom", Settings::values.custom_bottom_bottom); qt_config->endGroup(); qt_config->beginGroup("Audio"); @@ -252,6 +295,7 @@ void Config::SaveValues() { qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode); qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar); + qt_config->setValue("showStatusBar", UISettings::values.show_status_bar); qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); qt_config->setValue("firstStart", UISettings::values.first_start); diff --git a/src/citra_qt/config.h b/src/citra_qt/configuration/config.h index 79c901804..cbf745ea2 100644 --- a/src/citra_qt/config.h +++ b/src/citra_qt/configuration/config.h @@ -4,6 +4,7 @@ #pragma once +#include <array> #include <string> #include <QVariant> #include "core/settings.h" @@ -23,5 +24,7 @@ public: void Reload(); void Save(); - static const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> defaults; + + static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; + static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs; }; diff --git a/src/citra_qt/configure.ui b/src/citra_qt/configuration/configure.ui index 28b4a3b90..85e206e42 100644 --- a/src/citra_qt/configure.ui +++ b/src/citra_qt/configuration/configure.ui @@ -64,37 +64,37 @@ <customwidget> <class>ConfigureGeneral</class> <extends>QWidget</extends> - <header>configure_general.h</header> + <header>configuration/configure_general.h</header> <container>1</container> </customwidget> <customwidget> <class>ConfigureSystem</class> <extends>QWidget</extends> - <header>configure_system.h</header> + <header>configuration/configure_system.h</header> <container>1</container> </customwidget> <customwidget> <class>ConfigureAudio</class> <extends>QWidget</extends> - <header>configure_audio.h</header> + <header>configuration/configure_audio.h</header> <container>1</container> </customwidget> <customwidget> <class>ConfigureDebug</class> <extends>QWidget</extends> - <header>configure_debug.h</header> + <header>configuration/configure_debug.h</header> <container>1</container> </customwidget> <customwidget> <class>ConfigureInput</class> <extends>QWidget</extends> - <header>configure_input.h</header> + <header>configuration/configure_input.h</header> <container>1</container> </customwidget> <customwidget> <class>ConfigureGraphics</class> <extends>QWidget</extends> - <header>configure_graphics.h</header> + <header>configuration/configure_graphics.h</header> <container>1</container> </customwidget> </customwidgets> diff --git a/src/citra_qt/configure_audio.cpp b/src/citra_qt/configuration/configure_audio.cpp index 3ddcf9232..3fd1d127a 100644 --- a/src/citra_qt/configure_audio.cpp +++ b/src/citra_qt/configuration/configure_audio.cpp @@ -6,7 +6,7 @@ #include "audio_core/audio_core.h" #include "audio_core/sink.h" #include "audio_core/sink_details.h" -#include "citra_qt/configure_audio.h" +#include "citra_qt/configuration/configure_audio.h" #include "core/settings.h" #include "ui_configure_audio.h" diff --git a/src/citra_qt/configure_audio.h b/src/citra_qt/configuration/configure_audio.h index 8190e694f..8190e694f 100644 --- a/src/citra_qt/configure_audio.h +++ b/src/citra_qt/configuration/configure_audio.h diff --git a/src/citra_qt/configure_audio.ui b/src/citra_qt/configuration/configure_audio.ui index dd870eb61..dd870eb61 100644 --- a/src/citra_qt/configure_audio.ui +++ b/src/citra_qt/configuration/configure_audio.ui diff --git a/src/citra_qt/configure_debug.cpp b/src/citra_qt/configuration/configure_debug.cpp index dcc398eee..263f73f38 100644 --- a/src/citra_qt/configure_debug.cpp +++ b/src/citra_qt/configuration/configure_debug.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "citra_qt/configure_debug.h" +#include "citra_qt/configuration/configure_debug.h" #include "core/settings.h" #include "ui_configure_debug.h" diff --git a/src/citra_qt/configure_debug.h b/src/citra_qt/configuration/configure_debug.h index d167eb996..d167eb996 100644 --- a/src/citra_qt/configure_debug.h +++ b/src/citra_qt/configuration/configure_debug.h diff --git a/src/citra_qt/configure_debug.ui b/src/citra_qt/configuration/configure_debug.ui index bbbb0e3f4..bbbb0e3f4 100644 --- a/src/citra_qt/configure_debug.ui +++ b/src/citra_qt/configuration/configure_debug.ui diff --git a/src/citra_qt/configure_dialog.cpp b/src/citra_qt/configuration/configure_dialog.cpp index 525a7cc4e..dfc8c03a7 100644 --- a/src/citra_qt/configure_dialog.cpp +++ b/src/citra_qt/configuration/configure_dialog.cpp @@ -2,8 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "citra_qt/config.h" -#include "citra_qt/configure_dialog.h" +#include "citra_qt/configuration/config.h" +#include "citra_qt/configuration/configure_dialog.h" #include "core/settings.h" #include "ui_configure.h" diff --git a/src/citra_qt/configure_dialog.h b/src/citra_qt/configuration/configure_dialog.h index 21fa1f501..21fa1f501 100644 --- a/src/citra_qt/configure_dialog.h +++ b/src/citra_qt/configuration/configure_dialog.h diff --git a/src/citra_qt/configure_general.cpp b/src/citra_qt/configuration/configure_general.cpp index ac90a6df4..a21176c34 100644 --- a/src/citra_qt/configure_general.cpp +++ b/src/citra_qt/configuration/configure_general.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "citra_qt/configure_general.h" +#include "citra_qt/configuration/configure_general.h" #include "citra_qt/ui_settings.h" #include "core/core.h" #include "core/settings.h" diff --git a/src/citra_qt/configure_general.h b/src/citra_qt/configuration/configure_general.h index 447552d8c..447552d8c 100644 --- a/src/citra_qt/configure_general.h +++ b/src/citra_qt/configuration/configure_general.h diff --git a/src/citra_qt/configure_general.ui b/src/citra_qt/configuration/configure_general.ui index 0f3352a1d..c739605a4 100644 --- a/src/citra_qt/configure_general.ui +++ b/src/citra_qt/configuration/configure_general.ui @@ -27,7 +27,7 @@ <item> <widget class="QCheckBox" name="toggle_deepscan"> <property name="text"> - <string>Recursive scan for game folder</string> + <string>Search sub-directories for games</string> </property> </widget> </item> diff --git a/src/citra_qt/configure_graphics.cpp b/src/citra_qt/configuration/configure_graphics.cpp index 54f799b47..b5a5ab1e1 100644 --- a/src/citra_qt/configure_graphics.cpp +++ b/src/citra_qt/configuration/configure_graphics.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "citra_qt/configure_graphics.h" +#include "citra_qt/configuration/configure_graphics.h" #include "core/core.h" #include "core/settings.h" #include "ui_configure_graphics.h" @@ -14,6 +14,8 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) this->setConfiguration(); ui->toggle_vsync->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + + ui->layoutBox->setEnabled(!Settings::values.custom_layout); } ConfigureGraphics::~ConfigureGraphics() {} diff --git a/src/citra_qt/configure_graphics.h b/src/citra_qt/configuration/configure_graphics.h index 5497a55f7..5497a55f7 100644 --- a/src/citra_qt/configure_graphics.h +++ b/src/citra_qt/configuration/configure_graphics.h diff --git a/src/citra_qt/configure_graphics.ui b/src/citra_qt/configuration/configure_graphics.ui index a091f4c60..228f2a869 100644 --- a/src/citra_qt/configure_graphics.ui +++ b/src/citra_qt/configuration/configure_graphics.ui @@ -126,7 +126,7 @@ </layout> </item> <item> - <widget class="QGroupBox" name="groupBox2"> + <widget class="QGroupBox" name="layoutBox"> <property name="title"> <string>Layout</string> </property> diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp new file mode 100644 index 000000000..daac9b63a --- /dev/null +++ b/src/citra_qt/configuration/configure_input.cpp @@ -0,0 +1,205 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> +#include <utility> +#include <QTimer> +#include "citra_qt/configuration/config.h" +#include "citra_qt/configuration/configure_input.h" +#include "common/param_package.h" +#include "input_common/main.h" + +const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM> + ConfigureInput::analog_sub_buttons{{ + "up", "down", "left", "right", "modifier", + }}; + +static QString getKeyName(int key_code) { + switch (key_code) { + case Qt::Key_Shift: + return QObject::tr("Shift"); + case Qt::Key_Control: + return QObject::tr("Ctrl"); + case Qt::Key_Alt: + return QObject::tr("Alt"); + case Qt::Key_Meta: + return ""; + default: + return QKeySequence(key_code).toString(); + } +} + +static void SetButtonKey(int key, Common::ParamPackage& button_param) { + button_param = Common::ParamPackage{InputCommon::GenerateKeyboardParam(key)}; +} + +static void SetAnalogKey(int key, Common::ParamPackage& analog_param, + const std::string& button_name) { + if (analog_param.Get("engine", "") != "analog_from_button") { + analog_param = { + {"engine", "analog_from_button"}, {"modifier_scale", "0.5"}, + }; + } + analog_param.Set(button_name, InputCommon::GenerateKeyboardParam(key)); +} + +ConfigureInput::ConfigureInput(QWidget* parent) + : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), + timer(std::make_unique<QTimer>()) { + + ui->setupUi(this); + setFocusPolicy(Qt::ClickFocus); + + button_map = { + ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY, ui->buttonDpadUp, + ui->buttonDpadDown, ui->buttonDpadLeft, ui->buttonDpadRight, ui->buttonL, ui->buttonR, + ui->buttonStart, ui->buttonSelect, ui->buttonZL, ui->buttonZR, ui->buttonHome, + }; + + analog_map = {{ + { + ui->buttonCircleUp, ui->buttonCircleDown, ui->buttonCircleLeft, ui->buttonCircleRight, + ui->buttonCircleMod, + }, + { + ui->buttonCStickUp, ui->buttonCStickDown, ui->buttonCStickLeft, ui->buttonCStickRight, + nullptr, + }, + }}; + + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + if (button_map[button_id]) + connect(button_map[button_id], &QPushButton::released, [=]() { + handleClick(button_map[button_id], + [=](int key) { SetButtonKey(key, buttons_param[button_id]); }); + }); + } + + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + if (analog_map[analog_id][sub_button_id] != nullptr) { + connect(analog_map[analog_id][sub_button_id], &QPushButton::released, [=]() { + handleClick(analog_map[analog_id][sub_button_id], [=](int key) { + SetAnalogKey(key, analogs_param[analog_id], + analog_sub_buttons[sub_button_id]); + }); + }); + } + } + } + + connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); + + timer->setSingleShot(true); + connect(timer.get(), &QTimer::timeout, [this]() { + releaseKeyboard(); + releaseMouse(); + key_setter = boost::none; + updateButtonLabels(); + }); + + this->loadConfiguration(); + + // TODO(wwylele): enable these when the input emulation for them is implemented + ui->buttonZL->setEnabled(false); + ui->buttonZR->setEnabled(false); + ui->buttonHome->setEnabled(false); + ui->buttonCStickUp->setEnabled(false); + ui->buttonCStickDown->setEnabled(false); + ui->buttonCStickLeft->setEnabled(false); + ui->buttonCStickRight->setEnabled(false); +} + +void ConfigureInput::applyConfiguration() { + std::transform(buttons_param.begin(), buttons_param.end(), Settings::values.buttons.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); + std::transform(analogs_param.begin(), analogs_param.end(), Settings::values.analogs.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); + + Settings::Apply(); +} + +void ConfigureInput::loadConfiguration() { + std::transform(Settings::values.buttons.begin(), Settings::values.buttons.end(), + buttons_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); + std::transform(Settings::values.analogs.begin(), Settings::values.analogs.end(), + analogs_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); + updateButtonLabels(); +} + +void ConfigureInput::restoreDefaults() { + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + SetButtonKey(Config::default_buttons[button_id], buttons_param[button_id]); + } + + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + SetAnalogKey(Config::default_analogs[analog_id][sub_button_id], + analogs_param[analog_id], analog_sub_buttons[sub_button_id]); + } + } + updateButtonLabels(); + applyConfiguration(); +} + +void ConfigureInput::updateButtonLabels() { + QString non_keyboard(tr("[non-keyboard]")); + + auto KeyToText = [&non_keyboard](const Common::ParamPackage& param) { + if (param.Get("engine", "") != "keyboard") { + return non_keyboard; + } else { + return getKeyName(param.Get("code", 0)); + } + }; + + for (int button = 0; button < Settings::NativeButton::NumButtons; button++) { + button_map[button]->setText(KeyToText(buttons_param[button])); + } + + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + if (analogs_param[analog_id].Get("engine", "") != "analog_from_button") { + for (QPushButton* button : analog_map[analog_id]) { + if (button) + button->setText(non_keyboard); + } + } else { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + Common::ParamPackage param( + analogs_param[analog_id].Get(analog_sub_buttons[sub_button_id], "")); + if (analog_map[analog_id][sub_button_id]) + analog_map[analog_id][sub_button_id]->setText(KeyToText(param)); + } + } + } +} + +void ConfigureInput::handleClick(QPushButton* button, std::function<void(int)> new_key_setter) { + button->setText(tr("[press key]")); + button->setFocus(); + + key_setter = new_key_setter; + + grabKeyboard(); + grabMouse(); + timer->start(5000); // Cancel after 5 seconds +} + +void ConfigureInput::keyPressEvent(QKeyEvent* event) { + releaseKeyboard(); + releaseMouse(); + + if (!key_setter || !event) + return; + + if (event->key() != Qt::Key_Escape) + (*key_setter)(event->key()); + + updateButtonLabels(); + key_setter = boost::none; + timer->stop(); +} diff --git a/src/citra_qt/configuration/configure_input.h b/src/citra_qt/configuration/configure_input.h new file mode 100644 index 000000000..c950fbcb4 --- /dev/null +++ b/src/citra_qt/configuration/configure_input.h @@ -0,0 +1,69 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <functional> +#include <memory> +#include <string> +#include <QKeyEvent> +#include <QWidget> +#include <boost/optional.hpp> +#include "common/param_package.h" +#include "core/settings.h" +#include "ui_configure_input.h" + +class QPushButton; +class QString; +class QTimer; + +namespace Ui { +class ConfigureInput; +} + +class ConfigureInput : public QWidget { + Q_OBJECT + +public: + explicit ConfigureInput(QWidget* parent = nullptr); + + /// Save all button configurations to settings file + void applyConfiguration(); + +private: + std::unique_ptr<Ui::ConfigureInput> ui; + + std::unique_ptr<QTimer> timer; + + /// This will be the the setting function when an input is awaiting configuration. + boost::optional<std::function<void(int)>> key_setter; + + std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; + std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; + + static constexpr int ANALOG_SUB_BUTTONS_NUM = 5; + + /// Each button input is represented by a QPushButton. + std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; + + /// Each analog input is represented by five QPushButtons which represents up, down, left, right + /// and modifier + std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs> + analog_map; + + static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons; + + /// Load configuration settings. + void loadConfiguration(); + /// Restore all buttons to their default values. + void restoreDefaults(); + /// Update UI to reflect current configuration. + void updateButtonLabels(); + + /// Called when the button was pressed. + void handleClick(QPushButton* button, std::function<void(int)> new_key_setter); + /// Handle key press events. + void keyPressEvent(QKeyEvent* event) override; +}; diff --git a/src/citra_qt/configure_input.ui b/src/citra_qt/configuration/configure_input.ui index 2760787e5..2760787e5 100644 --- a/src/citra_qt/configure_input.ui +++ b/src/citra_qt/configuration/configure_input.ui diff --git a/src/citra_qt/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp index eb1276ef3..a3a9015a4 100644 --- a/src/citra_qt/configure_system.cpp +++ b/src/citra_qt/configuration/configure_system.cpp @@ -2,8 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "citra_qt/configure_system.h" +#include "citra_qt/configuration/configure_system.h" #include "citra_qt/ui_settings.h" +#include "core/core.h" #include "core/hle/service/cfg/cfg.h" #include "core/hle/service/fs/archive.h" #include "ui_configure_system.h" diff --git a/src/citra_qt/configure_system.h b/src/citra_qt/configuration/configure_system.h index db0ead13c..db0ead13c 100644 --- a/src/citra_qt/configure_system.h +++ b/src/citra_qt/configuration/configure_system.h diff --git a/src/citra_qt/configure_system.ui b/src/citra_qt/configuration/configure_system.ui index cc54fa37f..cc54fa37f 100644 --- a/src/citra_qt/configure_system.ui +++ b/src/citra_qt/configuration/configure_system.ui diff --git a/src/citra_qt/configure_input.cpp b/src/citra_qt/configure_input.cpp deleted file mode 100644 index 3e6803b8a..000000000 --- a/src/citra_qt/configure_input.cpp +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <memory> -#include <utility> -#include <QTimer> -#include "citra_qt/config.h" -#include "citra_qt/configure_input.h" - -static QString getKeyName(Qt::Key key_code) { - switch (key_code) { - case Qt::Key_Shift: - return QObject::tr("Shift"); - case Qt::Key_Control: - return QObject::tr("Ctrl"); - case Qt::Key_Alt: - return QObject::tr("Alt"); - case Qt::Key_Meta: - case -1: - return ""; - default: - return QKeySequence(key_code).toString(); - } -} - -ConfigureInput::ConfigureInput(QWidget* parent) - : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), - timer(std::make_unique<QTimer>()) { - - ui->setupUi(this); - setFocusPolicy(Qt::ClickFocus); - - button_map = { - {Settings::NativeInput::Values::A, ui->buttonA}, - {Settings::NativeInput::Values::B, ui->buttonB}, - {Settings::NativeInput::Values::X, ui->buttonX}, - {Settings::NativeInput::Values::Y, ui->buttonY}, - {Settings::NativeInput::Values::L, ui->buttonL}, - {Settings::NativeInput::Values::R, ui->buttonR}, - {Settings::NativeInput::Values::ZL, ui->buttonZL}, - {Settings::NativeInput::Values::ZR, ui->buttonZR}, - {Settings::NativeInput::Values::START, ui->buttonStart}, - {Settings::NativeInput::Values::SELECT, ui->buttonSelect}, - {Settings::NativeInput::Values::HOME, ui->buttonHome}, - {Settings::NativeInput::Values::DUP, ui->buttonDpadUp}, - {Settings::NativeInput::Values::DDOWN, ui->buttonDpadDown}, - {Settings::NativeInput::Values::DLEFT, ui->buttonDpadLeft}, - {Settings::NativeInput::Values::DRIGHT, ui->buttonDpadRight}, - {Settings::NativeInput::Values::CUP, ui->buttonCStickUp}, - {Settings::NativeInput::Values::CDOWN, ui->buttonCStickDown}, - {Settings::NativeInput::Values::CLEFT, ui->buttonCStickLeft}, - {Settings::NativeInput::Values::CRIGHT, ui->buttonCStickRight}, - {Settings::NativeInput::Values::CIRCLE_UP, ui->buttonCircleUp}, - {Settings::NativeInput::Values::CIRCLE_DOWN, ui->buttonCircleDown}, - {Settings::NativeInput::Values::CIRCLE_LEFT, ui->buttonCircleLeft}, - {Settings::NativeInput::Values::CIRCLE_RIGHT, ui->buttonCircleRight}, - {Settings::NativeInput::Values::CIRCLE_MODIFIER, ui->buttonCircleMod}, - }; - - for (const auto& entry : button_map) { - const Settings::NativeInput::Values input_id = entry.first; - connect(entry.second, &QPushButton::released, - [this, input_id]() { handleClick(input_id); }); - } - - connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); - - timer->setSingleShot(true); - connect(timer.get(), &QTimer::timeout, [this]() { - releaseKeyboard(); - releaseMouse(); - current_input_id = boost::none; - updateButtonLabels(); - }); - - this->loadConfiguration(); -} - -void ConfigureInput::applyConfiguration() { - for (const auto& input_id : Settings::NativeInput::All) { - const size_t index = static_cast<size_t>(input_id); - Settings::values.input_mappings[index] = static_cast<int>(key_map[input_id]); - } - Settings::Apply(); -} - -void ConfigureInput::loadConfiguration() { - for (const auto& input_id : Settings::NativeInput::All) { - const size_t index = static_cast<size_t>(input_id); - key_map[input_id] = static_cast<Qt::Key>(Settings::values.input_mappings[index]); - } - updateButtonLabels(); -} - -void ConfigureInput::restoreDefaults() { - for (const auto& input_id : Settings::NativeInput::All) { - const size_t index = static_cast<size_t>(input_id); - key_map[input_id] = static_cast<Qt::Key>(Config::defaults[index].toInt()); - } - updateButtonLabels(); - applyConfiguration(); -} - -void ConfigureInput::updateButtonLabels() { - for (const auto& input_id : Settings::NativeInput::All) { - button_map[input_id]->setText(getKeyName(key_map[input_id])); - } -} - -void ConfigureInput::handleClick(Settings::NativeInput::Values input_id) { - QPushButton* button = button_map[input_id]; - button->setText(tr("[press key]")); - button->setFocus(); - - current_input_id = input_id; - - grabKeyboard(); - grabMouse(); - timer->start(5000); // Cancel after 5 seconds -} - -void ConfigureInput::keyPressEvent(QKeyEvent* event) { - releaseKeyboard(); - releaseMouse(); - - if (!current_input_id || !event) - return; - - if (event->key() != Qt::Key_Escape) - setInput(*current_input_id, static_cast<Qt::Key>(event->key())); - - updateButtonLabels(); - current_input_id = boost::none; - timer->stop(); -} - -void ConfigureInput::setInput(Settings::NativeInput::Values input_id, Qt::Key key_pressed) { - // Remove duplicates - for (auto& pair : key_map) { - if (pair.second == key_pressed) - pair.second = Qt::Key_unknown; - } - - key_map[input_id] = key_pressed; -} diff --git a/src/citra_qt/configure_input.h b/src/citra_qt/configure_input.h deleted file mode 100644 index bc343db83..000000000 --- a/src/citra_qt/configure_input.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include <QKeyEvent> -#include <QWidget> -#include <boost/optional.hpp> -#include "core/settings.h" -#include "ui_configure_input.h" - -class QPushButton; -class QString; -class QTimer; - -namespace Ui { -class ConfigureInput; -} - -class ConfigureInput : public QWidget { - Q_OBJECT - -public: - explicit ConfigureInput(QWidget* parent = nullptr); - - /// Save all button configurations to settings file - void applyConfiguration(); - -private: - std::unique_ptr<Ui::ConfigureInput> ui; - - /// This input is currently awaiting configuration. - /// (i.e.: its corresponding QPushButton has been pressed.) - boost::optional<Settings::NativeInput::Values> current_input_id; - std::unique_ptr<QTimer> timer; - - /// Each input is represented by a QPushButton. - std::map<Settings::NativeInput::Values, QPushButton*> button_map; - /// Each input is configured to respond to the press of a Qt::Key. - std::map<Settings::NativeInput::Values, Qt::Key> key_map; - - /// Load configuration settings. - void loadConfiguration(); - /// Restore all buttons to their default values. - void restoreDefaults(); - /// Update UI to reflect current configuration. - void updateButtonLabels(); - - /// Called when the button corresponding to input_id was pressed. - void handleClick(Settings::NativeInput::Values input_id); - /// Handle key press events. - void keyPressEvent(QKeyEvent* event) override; - /// Configure input input_id to respond to key key_pressed. - void setInput(Settings::NativeInput::Values input_id, Qt::Key key_pressed); -}; diff --git a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp index f5a2ec761..c68fe753b 100644 --- a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp @@ -18,15 +18,16 @@ #include "citra_qt/util/util.h" #include "common/vector_math.h" #include "video_core/debug_utils/debug_utils.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/regs.h" +#include "video_core/texture/texture_decode.h" namespace { -QImage LoadTexture(const u8* src, const Pica::DebugUtils::TextureInfo& info) { +QImage LoadTexture(const u8* src, const Pica::Texture::TextureInfo& info) { QImage decoded_image(info.width, info.height, QImage::Format_ARGB32); for (int y = 0; y < info.height; ++y) { for (int x = 0; x < info.width; ++x) { - Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(src, x, y, info, true); + Math::Vec4<u8> color = Pica::Texture::LookupTexture(src, x, y, info, true); decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); } } @@ -36,9 +37,10 @@ QImage LoadTexture(const u8* src, const Pica::DebugUtils::TextureInfo& info) { class TextureInfoWidget : public QWidget { public: - TextureInfoWidget(const u8* src, const Pica::DebugUtils::TextureInfo& info, + TextureInfoWidget(const u8* src, const Pica::Texture::TextureInfo& info, QWidget* parent = nullptr) : QWidget(parent) { + QLabel* image_widget = new QLabel; QPixmap image_pixmap = QPixmap::fromImage(LoadTexture(src, info)); image_pixmap = image_pixmap.scaled(200, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation); @@ -70,7 +72,7 @@ QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const { if (role == Qt::DisplayRole) { switch (index.column()) { case 0: - return QString::fromLatin1(Pica::Regs::GetCommandName(write.cmd_id).c_str()); + return QString::fromLatin1(Pica::Regs::GetRegisterName(write.cmd_id)); case 1: return QString("%1").arg(write.cmd_id, 3, 16, QLatin1Char('0')); case 2: @@ -121,15 +123,16 @@ void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) { const unsigned int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toUInt(); - if (COMMAND_IN_RANGE(command_id, texture0) || COMMAND_IN_RANGE(command_id, texture1) || - COMMAND_IN_RANGE(command_id, texture2)) { + if (COMMAND_IN_RANGE(command_id, texturing.texture0) || + COMMAND_IN_RANGE(command_id, texturing.texture1) || + COMMAND_IN_RANGE(command_id, texturing.texture2)) { unsigned texture_index; - if (COMMAND_IN_RANGE(command_id, texture0)) { + if (COMMAND_IN_RANGE(command_id, texturing.texture0)) { texture_index = 0; - } else if (COMMAND_IN_RANGE(command_id, texture1)) { + } else if (COMMAND_IN_RANGE(command_id, texturing.texture1)) { texture_index = 1; - } else if (COMMAND_IN_RANGE(command_id, texture2)) { + } else if (COMMAND_IN_RANGE(command_id, texturing.texture2)) { texture_index = 2; } else { UNREACHABLE_MSG("Unknown texture command"); @@ -144,23 +147,24 @@ void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) { const unsigned int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toUInt(); - if (COMMAND_IN_RANGE(command_id, texture0) || COMMAND_IN_RANGE(command_id, texture1) || - COMMAND_IN_RANGE(command_id, texture2)) { + if (COMMAND_IN_RANGE(command_id, texturing.texture0) || + COMMAND_IN_RANGE(command_id, texturing.texture1) || + COMMAND_IN_RANGE(command_id, texturing.texture2)) { unsigned texture_index; - if (COMMAND_IN_RANGE(command_id, texture0)) { + if (COMMAND_IN_RANGE(command_id, texturing.texture0)) { texture_index = 0; - } else if (COMMAND_IN_RANGE(command_id, texture1)) { + } else if (COMMAND_IN_RANGE(command_id, texturing.texture1)) { texture_index = 1; } else { texture_index = 2; } - const auto texture = Pica::g_state.regs.GetTextures()[texture_index]; + const auto texture = Pica::g_state.regs.texturing.GetTextures()[texture_index]; const auto config = texture.config; const auto format = texture.format; - const auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format); + const auto info = Pica::Texture::TextureInfo::FromPicaRegister(config, format); const u8* src = Memory::GetPhysicalPointer(config.GetPhysicalAddress()); new_info_widget = new TextureInfoWidget(src, info); } diff --git a/src/citra_qt/debugger/graphics/graphics_surface.cpp b/src/citra_qt/debugger/graphics/graphics_surface.cpp index 4efd95d3c..47d9924e1 100644 --- a/src/citra_qt/debugger/graphics/graphics_surface.cpp +++ b/src/citra_qt/debugger/graphics/graphics_surface.cpp @@ -16,8 +16,10 @@ #include "common/color.h" #include "core/hw/gpu.h" #include "core/memory.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_texturing.h" +#include "video_core/texture/texture_decode.h" #include "video_core/utils.h" SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) @@ -413,30 +415,30 @@ void GraphicsSurfaceWidget::OnUpdate() { // TODO: Store a reference to the registers in the debug context instead of accessing them // directly... - const auto& framebuffer = Pica::g_state.regs.framebuffer; + const auto& framebuffer = Pica::g_state.regs.framebuffer.framebuffer; surface_address = framebuffer.GetColorBufferPhysicalAddress(); surface_width = framebuffer.GetWidth(); surface_height = framebuffer.GetHeight(); switch (framebuffer.color_format) { - case Pica::Regs::ColorFormat::RGBA8: + case Pica::FramebufferRegs::ColorFormat::RGBA8: surface_format = Format::RGBA8; break; - case Pica::Regs::ColorFormat::RGB8: + case Pica::FramebufferRegs::ColorFormat::RGB8: surface_format = Format::RGB8; break; - case Pica::Regs::ColorFormat::RGB5A1: + case Pica::FramebufferRegs::ColorFormat::RGB5A1: surface_format = Format::RGB5A1; break; - case Pica::Regs::ColorFormat::RGB565: + case Pica::FramebufferRegs::ColorFormat::RGB565: surface_format = Format::RGB565; break; - case Pica::Regs::ColorFormat::RGBA4: + case Pica::FramebufferRegs::ColorFormat::RGBA4: surface_format = Format::RGBA4; break; @@ -449,22 +451,22 @@ void GraphicsSurfaceWidget::OnUpdate() { } case Source::DepthBuffer: { - const auto& framebuffer = Pica::g_state.regs.framebuffer; + const auto& framebuffer = Pica::g_state.regs.framebuffer.framebuffer; surface_address = framebuffer.GetDepthBufferPhysicalAddress(); surface_width = framebuffer.GetWidth(); surface_height = framebuffer.GetHeight(); switch (framebuffer.depth_format) { - case Pica::Regs::DepthFormat::D16: + case Pica::FramebufferRegs::DepthFormat::D16: surface_format = Format::D16; break; - case Pica::Regs::DepthFormat::D24: + case Pica::FramebufferRegs::DepthFormat::D24: surface_format = Format::D24; break; - case Pica::Regs::DepthFormat::D24S8: + case Pica::FramebufferRegs::DepthFormat::D24S8: surface_format = Format::D24X8; break; @@ -477,14 +479,14 @@ void GraphicsSurfaceWidget::OnUpdate() { } case Source::StencilBuffer: { - const auto& framebuffer = Pica::g_state.regs.framebuffer; + const auto& framebuffer = Pica::g_state.regs.framebuffer.framebuffer; surface_address = framebuffer.GetDepthBufferPhysicalAddress(); surface_width = framebuffer.GetWidth(); surface_height = framebuffer.GetHeight(); switch (framebuffer.depth_format) { - case Pica::Regs::DepthFormat::D24S8: + case Pica::FramebufferRegs::DepthFormat::D24S8: surface_format = Format::X24S8; break; @@ -511,8 +513,8 @@ void GraphicsSurfaceWidget::OnUpdate() { break; } - const auto texture = Pica::g_state.regs.GetTextures()[texture_index]; - auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(texture.config, texture.format); + const auto texture = Pica::g_state.regs.texturing.GetTextures()[texture_index]; + auto info = Pica::Texture::TextureInfo::FromPicaRegister(texture.config, texture.format); surface_address = info.physical_address; surface_width = info.width; @@ -567,28 +569,27 @@ void GraphicsSurfaceWidget::OnUpdate() { surface_picture_label->show(); - unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format); - unsigned stride = nibbles_per_pixel * surface_width / 2; - - // We handle depth formats here because DebugUtils only supports TextureFormats if (surface_format <= Format::MaxTextureFormat) { - // Generate a virtual texture - Pica::DebugUtils::TextureInfo info; + Pica::Texture::TextureInfo info; info.physical_address = surface_address; info.width = surface_width; info.height = surface_height; - info.format = static_cast<Pica::Regs::TextureFormat>(surface_format); - info.stride = stride; + info.format = static_cast<Pica::TexturingRegs::TextureFormat>(surface_format); + info.SetDefaultStride(); for (unsigned int y = 0; y < surface_height; ++y) { for (unsigned int x = 0; x < surface_width; ++x) { - Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(buffer, x, y, info, true); + Math::Vec4<u8> color = Pica::Texture::LookupTexture(buffer, x, y, info, true); decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); } } - } else { + // We handle depth formats here because DebugUtils only supports TextureFormats + + // TODO(yuriks): Convert to newer tile-based addressing + unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format); + unsigned stride = nibbles_per_pixel * surface_width / 2; ASSERT_MSG(nibbles_per_pixel >= 2, "Depth decoder only supports formats with at least one byte per pixel"); @@ -689,7 +690,8 @@ void GraphicsSurfaceWidget::SaveSurface() { unsigned int GraphicsSurfaceWidget::NibblesPerPixel(GraphicsSurfaceWidget::Format format) { if (format <= Format::MaxTextureFormat) { - return Pica::Regs::NibblesPerPixel(static_cast<Pica::Regs::TextureFormat>(format)); + return Pica::TexturingRegs::NibblesPerPixel( + static_cast<Pica::TexturingRegs::TextureFormat>(format)); } switch (format) { diff --git a/src/citra_qt/debugger/graphics/graphics_tracing.cpp b/src/citra_qt/debugger/graphics/graphics_tracing.cpp index 716ed50b8..40d5bed51 100644 --- a/src/citra_qt/debugger/graphics/graphics_tracing.cpp +++ b/src/citra_qt/debugger/graphics/graphics_tracing.cpp @@ -18,7 +18,6 @@ #include "core/hw/lcd.h" #include "core/tracer/recorder.h" #include "nihstro/float24.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" GraphicsTracingWidget::GraphicsTracingWidget(std::shared_ptr<Pica::DebugContext> debug_context, @@ -71,8 +70,8 @@ void GraphicsTracingWidget::StartRecording() { std::array<u32, 4 * 16> default_attributes; for (unsigned i = 0; i < 16; ++i) { for (unsigned comp = 0; comp < 3; ++comp) { - default_attributes[4 * i + comp] = - nihstro::to_float24(Pica::g_state.vs_default_attributes[i][comp].ToFloat32()); + default_attributes[4 * i + comp] = nihstro::to_float24( + Pica::g_state.input_default_attributes.attr[i][comp].ToFloat32()); } } diff --git a/src/citra_qt/debugger/graphics/graphics_tracing.h b/src/citra_qt/debugger/graphics/graphics_tracing.h index 3f73bcd2e..eb1292c29 100644 --- a/src/citra_qt/debugger/graphics/graphics_tracing.h +++ b/src/citra_qt/debugger/graphics/graphics_tracing.h @@ -15,6 +15,9 @@ public: explicit GraphicsTracingWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr); + void OnEmulationStarting(EmuThread* emu_thread); + void OnEmulationStopping(); + private slots: void StartRecording(); void StopRecording(); @@ -23,9 +26,6 @@ private slots: void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override; void OnResumed() override; - void OnEmulationStarting(EmuThread* emu_thread); - void OnEmulationStopping(); - signals: void SetStartTracingButtonEnabled(bool enable); void SetStopTracingButtonEnabled(bool enable); diff --git a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp index f37524190..e3f3194db 100644 --- a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp +++ b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp @@ -16,7 +16,6 @@ #include <QTreeView> #include "citra_qt/debugger/graphics/graphics_vertex_shader.h" #include "citra_qt/util/util.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" #include "video_core/shader/debug_data.h" #include "video_core/shader/shader.h" @@ -359,7 +358,7 @@ void GraphicsVertexShaderWidget::DumpShader() { auto& config = Pica::g_state.regs.vs; Pica::DebugUtils::DumpShader(filename.toStdString(), config, setup, - Pica::g_state.regs.vs_output_attributes); + Pica::g_state.regs.rasterizer.vs_output_attributes); } GraphicsVertexShaderWidget::GraphicsVertexShaderWidget( @@ -511,7 +510,7 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d auto& shader_config = Pica::g_state.regs.vs; for (auto instr : shader_setup.program_code) info.code.push_back({instr}); - int num_attributes = Pica::g_state.regs.vertex_attributes.GetNumTotalAttributes(); + int num_attributes = shader_config.max_input_attribute_index + 1; for (auto pattern : shader_setup.swizzle_data) info.swizzle_info.push_back({pattern}); @@ -522,11 +521,11 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d // Generate debug information Pica::Shader::InterpreterEngine shader_engine; shader_engine.SetupBatch(shader_setup, entry_point); - debug_data = shader_engine.ProduceDebugInfo(shader_setup, input_vertex, num_attributes); + debug_data = shader_engine.ProduceDebugInfo(shader_setup, input_vertex, shader_config); // Reload widget state for (int attr = 0; attr < num_attributes; ++attr) { - unsigned source_attr = shader_config.input_register_map.GetRegisterForAttribute(attr); + unsigned source_attr = shader_config.GetRegisterForAttribute(attr); input_data_mapping[attr]->setText(QString("-> v%1").arg(source_attr)); input_data_container[attr]->setVisible(true); } diff --git a/src/citra_qt/debugger/graphics/graphics_vertex_shader.h b/src/citra_qt/debugger/graphics/graphics_vertex_shader.h index 3292573f3..c249a2ff8 100644 --- a/src/citra_qt/debugger/graphics/graphics_vertex_shader.h +++ b/src/citra_qt/debugger/graphics/graphics_vertex_shader.h @@ -82,7 +82,7 @@ private: nihstro::ShaderInfo info; Pica::Shader::DebugData<true> debug_data; - Pica::Shader::InputVertex input_vertex; + Pica::Shader::AttributeBuffer input_vertex; friend class GraphicsVertexShaderModel; }; diff --git a/src/citra_qt/debugger/profiler.cpp b/src/citra_qt/debugger/profiler.cpp index cee10403d..f060bbe08 100644 --- a/src/citra_qt/debugger/profiler.cpp +++ b/src/citra_qt/debugger/profiler.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QAction> +#include <QLayout> #include <QMouseEvent> #include <QPainter> #include <QString> @@ -9,121 +11,12 @@ #include "citra_qt/util/util.h" #include "common/common_types.h" #include "common/microprofile.h" -#include "common/profiler_reporting.h" // Include the implementation of the UI in this file. This isn't in microprofile.cpp because the // non-Qt frontends don't need it (and don't implement the UI drawing hooks either). #if MICROPROFILE_ENABLED #define MICROPROFILEUI_IMPL 1 #include "common/microprofileui.h" -#endif - -using namespace Common::Profiling; - -static QVariant GetDataForColumn(int col, const AggregatedDuration& duration) { - static auto duration_to_float = [](Duration dur) -> float { - using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>; - return std::chrono::duration_cast<FloatMs>(dur).count(); - }; - - switch (col) { - case 1: - return duration_to_float(duration.avg); - case 2: - return duration_to_float(duration.min); - case 3: - return duration_to_float(duration.max); - default: - return QVariant(); - } -} - -ProfilerModel::ProfilerModel(QObject* parent) : QAbstractItemModel(parent) { - updateProfilingInfo(); -} - -QVariant ProfilerModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { - switch (section) { - case 0: - return tr("Category"); - case 1: - return tr("Avg"); - case 2: - return tr("Min"); - case 3: - return tr("Max"); - } - } - - return QVariant(); -} - -QModelIndex ProfilerModel::index(int row, int column, const QModelIndex& parent) const { - return createIndex(row, column); -} - -QModelIndex ProfilerModel::parent(const QModelIndex& child) const { - return QModelIndex(); -} - -int ProfilerModel::columnCount(const QModelIndex& parent) const { - return 4; -} - -int ProfilerModel::rowCount(const QModelIndex& parent) const { - if (parent.isValid()) { - return 0; - } else { - return 2; - } -} - -QVariant ProfilerModel::data(const QModelIndex& index, int role) const { - if (role == Qt::DisplayRole) { - if (index.row() == 0) { - if (index.column() == 0) { - return tr("Frame"); - } else { - return GetDataForColumn(index.column(), results.frame_time); - } - } else if (index.row() == 1) { - if (index.column() == 0) { - return tr("Frame (with swapping)"); - } else { - return GetDataForColumn(index.column(), results.interframe_time); - } - } - } - - return QVariant(); -} - -void ProfilerModel::updateProfilingInfo() { - results = GetTimingResultsAggregator()->GetAggregatedResults(); - emit dataChanged(createIndex(0, 1), createIndex(rowCount() - 1, 3)); -} - -ProfilerWidget::ProfilerWidget(QWidget* parent) : QDockWidget(parent) { - ui.setupUi(this); - - model = new ProfilerModel(this); - ui.treeView->setModel(model); - - connect(this, SIGNAL(visibilityChanged(bool)), SLOT(setProfilingInfoUpdateEnabled(bool))); - connect(&update_timer, SIGNAL(timeout()), model, SLOT(updateProfilingInfo())); -} - -void ProfilerWidget::setProfilingInfoUpdateEnabled(bool enable) { - if (enable) { - update_timer.start(100); - model->updateProfilingInfo(); - } else { - update_timer.stop(); - } -} - -#if MICROPROFILE_ENABLED class MicroProfileWidget : public QWidget { public: diff --git a/src/citra_qt/debugger/profiler.h b/src/citra_qt/debugger/profiler.h index c8912fd5a..eae1e9e3c 100644 --- a/src/citra_qt/debugger/profiler.h +++ b/src/citra_qt/debugger/profiler.h @@ -8,46 +8,6 @@ #include <QDockWidget> #include <QTimer> #include "common/microprofile.h" -#include "common/profiler_reporting.h" -#include "ui_profiler.h" - -class ProfilerModel : public QAbstractItemModel { - Q_OBJECT - -public: - explicit ProfilerModel(QObject* parent); - - QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, - const QModelIndex& parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex& child) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - -public slots: - void updateProfilingInfo(); - -private: - Common::Profiling::AggregatedFrameResult results; -}; - -class ProfilerWidget : public QDockWidget { - Q_OBJECT - -public: - explicit ProfilerWidget(QWidget* parent = nullptr); - -private slots: - void setProfilingInfoUpdateEnabled(bool enable); - -private: - Ui::Profiler ui; - ProfilerModel* model; - - QTimer update_timer; -}; class MicroProfileDialog : public QWidget { Q_OBJECT diff --git a/src/citra_qt/debugger/profiler.ui b/src/citra_qt/debugger/profiler.ui deleted file mode 100644 index d3c9a9a1f..000000000 --- a/src/citra_qt/debugger/profiler.ui +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>Profiler</class> - <widget class="QDockWidget" name="Profiler"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>400</width> - <height>300</height> - </rect> - </property> - <property name="windowTitle"> - <string>Profiler</string> - </property> - <widget class="QWidget" name="dockWidgetContents"> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QTreeView" name="treeView"> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <property name="uniformRowHeights"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </widget> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index 09469f3c5..a9ec9e830 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QFileInfo> #include <QHeaderView> #include <QMenu> #include <QThreadPool> @@ -38,11 +39,13 @@ GameList::GameList(QWidget* parent) : QWidget{parent} { connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); + connect(&watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); // We must register all custom types with the Qt Automoc system so that we are able to use it // with signals/slots. In this case, QList falls under the umbrells of custom types. qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); + layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(tree_view); setLayout(layout); } @@ -102,6 +105,12 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { item_model->removeRows(0, item_model->rowCount()); emit ShouldCancelWorker(); + + auto watch_dirs = watcher.directories(); + if (!watch_dirs.isEmpty()) { + watcher.removePaths(watch_dirs); + } + UpdateWatcherList(dir_path.toStdString(), deep_scan ? 256 : 0); GameListWorker* worker = new GameListWorker(dir_path, deep_scan); connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); @@ -131,6 +140,53 @@ void GameList::LoadInterfaceLayout() { item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); } +const QStringList GameList::supported_file_extensions = {"3ds", "3dsx", "elf", "axf", + "cci", "cxi", "app"}; + +static bool HasSupportedFileExtension(const std::string& file_name) { + QFileInfo file = QFileInfo(file_name.c_str()); + return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); +} + +void GameList::RefreshGameDirectory() { + if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { + LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); + PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); + } +} + +/** + * Adds the game list folder to the QFileSystemWatcher to check for updates. + * + * The file watcher will fire off an update to the game list when a change is detected in the game + * list folder. + * + * Notice: This method is run on the UI thread because QFileSystemWatcher is not thread safe and + * this function is fast enough to not stall the UI thread. If performance is an issue, it should + * be moved to another thread and properly locked to prevent concurrency issues. + * + * @param dir folder to check for changes in + * @param recursion 0 if recursion is disabled. Any positive number passed to this will add each + * directory recursively to the watcher and will update the file list if any of the folders + * change. The number determines how deep the recursion should traverse. + */ +void GameList::UpdateWatcherList(const std::string& dir, unsigned int recursion) { + const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { + std::string physical_name = directory + DIR_SEP + virtual_name; + + if (FileUtil::IsDirectory(physical_name)) { + UpdateWatcherList(physical_name, recursion - 1); + } + return true; + }; + + watcher.addPath(QString::fromStdString(dir)); + if (recursion > 0) { + FileUtil::ForeachDirectoryEntry(nullptr, dir, callback); + } +} + void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, const std::string& virtual_name) -> bool { @@ -139,7 +195,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign if (stop_processing) return false; // Breaks the callback loop. - if (!FileUtil::IsDirectory(physical_name)) { + if (!FileUtil::IsDirectory(physical_name) && HasSupportedFileExtension(physical_name)) { std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name); if (!loader) return true; @@ -173,6 +229,6 @@ void GameListWorker::run() { } void GameListWorker::Cancel() { - disconnect(this, nullptr, nullptr, nullptr); + this->disconnect(); stop_processing = true; } diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index 1abf10051..b141fa3a5 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -4,6 +4,7 @@ #pragma once +#include <QFileSystemWatcher> #include <QModelIndex> #include <QSettings> #include <QStandardItem> @@ -33,6 +34,8 @@ public: void SaveInterfaceLayout(); void LoadInterfaceLayout(); + static const QStringList supported_file_extensions; + signals: void GameChosen(QString game_path); void ShouldCancelWorker(); @@ -44,8 +47,11 @@ private: void DonePopulating(); void PopupContextMenu(const QPoint& menu_location); + void UpdateWatcherList(const std::string& path, unsigned int recursion); + void RefreshGameDirectory(); QTreeView* tree_view = nullptr; QStandardItemModel* item_model = nullptr; GameListWorker* current_worker = nullptr; + QFileSystemWatcher watcher; }; diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index a15f06c5f..3c11b6dd1 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -16,8 +16,8 @@ #include "video_core/utils.h" /** - * Gets game icon from SMDH - * @param sdmh SMDH data + * Gets the game icon from SMDH data. + * @param smdh SMDH data * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) * @return QPixmap game icon */ @@ -42,8 +42,8 @@ static QPixmap GetDefaultIcon(bool large) { } /** - * Gets the short game title fromn SMDH - * @param sdmh SMDH data + * Gets the short game title from SMDH data. + * @param smdh SMDH data * @param language title language * @return QString short title */ diff --git a/src/citra_qt/hotkeys.h b/src/citra_qt/hotkeys.h index 46f48c2d8..a4ccc193b 100644 --- a/src/citra_qt/hotkeys.h +++ b/src/citra_qt/hotkeys.h @@ -29,6 +29,8 @@ void RegisterHotkey(const QString& group, const QString& action, /** * Returns a QShortcut object whose activated() signal can be connected to other QObjects' slots. * + * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger"). + * @param action Name of the action (e.g. "Start Emulation", "Load Image"). * @param widget Parent widget of the returned QShortcut. * @warning If multiple QWidgets' call this function for the same action, the returned QShortcut * will be the same. Thus, you shouldn't rely on the caller really being the QShortcut's parent. diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index f765c0147..b17ed6968 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -14,8 +14,8 @@ #include <QtGui> #include <QtWidgets> #include "citra_qt/bootmanager.h" -#include "citra_qt/config.h" -#include "citra_qt/configure_dialog.h" +#include "citra_qt/configuration/config.h" +#include "citra_qt/configuration/configure_dialog.h" #include "citra_qt/debugger/callstack.h" #include "citra_qt/debugger/disassembler.h" #include "citra_qt/debugger/graphics/graphics.h" @@ -54,28 +54,30 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { Pica::g_debug_context = Pica::DebugContext::Construct(); - + setAcceptDrops(true); ui.setupUi(this); statusBar()->hide(); InitializeWidgets(); - InitializeDebugMenuActions(); + InitializeDebugWidgets(); InitializeRecentFileMenuActions(); InitializeHotkeys(); SetDefaultUIGeometry(); RestoreUIState(); + ConnectMenuEvents(); ConnectWidgetEvents(); - setWindowTitle(QString("Citra | %1-%2").arg(Common::g_scm_branch, Common::g_scm_desc)); + setWindowTitle(QString("Citra %1| %2-%3") + .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); show(); game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); QStringList args = QApplication::arguments(); if (args.length() >= 2) { - BootGame(args[1].toStdString()); + BootGame(args[1]); } } @@ -94,73 +96,100 @@ void GMainWindow::InitializeWidgets() { game_list = new GameList(); ui.horizontalLayout->addWidget(game_list); - profilerWidget = new ProfilerWidget(this); - addDockWidget(Qt::BottomDockWidgetArea, profilerWidget); - profilerWidget->hide(); + // Create status bar + emu_speed_label = new QLabel(); + emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% " + "indicate emulation is running faster or slower than a 3DS.")); + game_fps_label = new QLabel(); + game_fps_label->setToolTip(tr("How many frames per second the game is currently displaying. " + "This will vary from game to game and scene to scene.")); + emu_frametime_label = new QLabel(); + emu_frametime_label->setToolTip( + tr("Time taken to emulate a 3DS frame, not counting framelimiting or v-sync. For " + "full-speed emulation this should be at most 16.67 ms.")); + + for (auto& label : {emu_speed_label, game_fps_label, emu_frametime_label}) { + label->setVisible(false); + label->setFrameStyle(QFrame::NoFrame); + label->setContentsMargins(4, 0, 4, 0); + statusBar()->addPermanentWidget(label); + } + statusBar()->setVisible(true); + setStyleSheet("QStatusBar::item{border: none;}"); +} + +void GMainWindow::InitializeDebugWidgets() { + connect(ui.action_Create_Pica_Surface_Viewer, &QAction::triggered, this, + &GMainWindow::OnCreateGraphicsSurfaceViewer); + + QMenu* debug_menu = ui.menu_View_Debugging; #if MICROPROFILE_ENABLED microProfileDialog = new MicroProfileDialog(this); microProfileDialog->hide(); + debug_menu->addAction(microProfileDialog->toggleViewAction()); #endif disasmWidget = new DisassemblerWidget(this, emu_thread.get()); addDockWidget(Qt::BottomDockWidgetArea, disasmWidget); disasmWidget->hide(); + debug_menu->addAction(disasmWidget->toggleViewAction()); + connect(this, &GMainWindow::EmulationStarting, disasmWidget, + &DisassemblerWidget::OnEmulationStarting); + connect(this, &GMainWindow::EmulationStopping, disasmWidget, + &DisassemblerWidget::OnEmulationStopping); registersWidget = new RegistersWidget(this); addDockWidget(Qt::RightDockWidgetArea, registersWidget); registersWidget->hide(); + debug_menu->addAction(registersWidget->toggleViewAction()); + connect(this, &GMainWindow::EmulationStarting, registersWidget, + &RegistersWidget::OnEmulationStarting); + connect(this, &GMainWindow::EmulationStopping, registersWidget, + &RegistersWidget::OnEmulationStopping); callstackWidget = new CallstackWidget(this); addDockWidget(Qt::RightDockWidgetArea, callstackWidget); callstackWidget->hide(); + debug_menu->addAction(callstackWidget->toggleViewAction()); graphicsWidget = new GPUCommandStreamWidget(this); addDockWidget(Qt::RightDockWidgetArea, graphicsWidget); graphicsWidget->hide(); + debug_menu->addAction(graphicsWidget->toggleViewAction()); graphicsCommandsWidget = new GPUCommandListWidget(this); addDockWidget(Qt::RightDockWidgetArea, graphicsCommandsWidget); graphicsCommandsWidget->hide(); + debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(Pica::g_debug_context, this); addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); graphicsBreakpointsWidget->hide(); + debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); graphicsVertexShaderWidget = new GraphicsVertexShaderWidget(Pica::g_debug_context, this); addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget); graphicsVertexShaderWidget->hide(); + debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction()); graphicsTracingWidget = new GraphicsTracingWidget(Pica::g_debug_context, this); addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget); graphicsTracingWidget->hide(); + debug_menu->addAction(graphicsTracingWidget->toggleViewAction()); + connect(this, &GMainWindow::EmulationStarting, graphicsTracingWidget, + &GraphicsTracingWidget::OnEmulationStarting); + connect(this, &GMainWindow::EmulationStopping, graphicsTracingWidget, + &GraphicsTracingWidget::OnEmulationStopping); waitTreeWidget = new WaitTreeWidget(this); addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget); waitTreeWidget->hide(); -} - -void GMainWindow::InitializeDebugMenuActions() { - auto graphicsSurfaceViewerAction = new QAction(tr("Create Pica Surface Viewer"), this); - connect(graphicsSurfaceViewerAction, SIGNAL(triggered()), this, - SLOT(OnCreateGraphicsSurfaceViewer())); - - QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); - debug_menu->addAction(graphicsSurfaceViewerAction); - debug_menu->addSeparator(); - debug_menu->addAction(profilerWidget->toggleViewAction()); -#if MICROPROFILE_ENABLED - debug_menu->addAction(microProfileDialog->toggleViewAction()); -#endif - debug_menu->addAction(disasmWidget->toggleViewAction()); - debug_menu->addAction(registersWidget->toggleViewAction()); - debug_menu->addAction(callstackWidget->toggleViewAction()); - debug_menu->addAction(graphicsWidget->toggleViewAction()); - debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); - debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); - debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction()); - debug_menu->addAction(graphicsTracingWidget->toggleViewAction()); debug_menu->addAction(waitTreeWidget->toggleViewAction()); + connect(this, &GMainWindow::EmulationStarting, waitTreeWidget, + &WaitTreeWidget::OnEmulationStarting); + connect(this, &GMainWindow::EmulationStopping, waitTreeWidget, + &WaitTreeWidget::OnEmulationStopping); } void GMainWindow::InitializeRecentFileMenuActions() { @@ -215,41 +244,46 @@ void GMainWindow::RestoreUIState() { ui.action_Single_Window_Mode->setChecked(UISettings::values.single_window_mode); ToggleWindowMode(); - ui.actionDisplay_widget_title_bars->setChecked(UISettings::values.display_titlebar); - OnDisplayTitleBars(ui.actionDisplay_widget_title_bars->isChecked()); + ui.action_Display_Dock_Widget_Headers->setChecked(UISettings::values.display_titlebar); + OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked()); + + ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar); + statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); } void GMainWindow::ConnectWidgetEvents() { - connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)), - Qt::DirectConnection); + connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString))); connect(game_list, SIGNAL(OpenSaveFolderRequested(u64)), this, - SLOT(OnGameListOpenSaveFolder(u64)), Qt::DirectConnection); - connect(ui.action_Configure, SIGNAL(triggered()), this, SLOT(OnConfigure())); - connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile()), - Qt::DirectConnection); - connect(ui.action_Load_Symbol_Map, SIGNAL(triggered()), this, SLOT(OnMenuLoadSymbolMap())); - connect(ui.action_Select_Game_List_Root, SIGNAL(triggered()), this, - SLOT(OnMenuSelectGameListRoot())); - connect(ui.action_Start, SIGNAL(triggered()), this, SLOT(OnStartGame())); - connect(ui.action_Pause, SIGNAL(triggered()), this, SLOT(OnPauseGame())); - connect(ui.action_Stop, SIGNAL(triggered()), this, SLOT(OnStopGame())); - connect(ui.action_Single_Window_Mode, SIGNAL(triggered(bool)), this, SLOT(ToggleWindowMode())); - - connect(this, SIGNAL(EmulationStarting(EmuThread*)), disasmWidget, - SLOT(OnEmulationStarting(EmuThread*))); - connect(this, SIGNAL(EmulationStopping()), disasmWidget, SLOT(OnEmulationStopping())); - connect(this, SIGNAL(EmulationStarting(EmuThread*)), registersWidget, - SLOT(OnEmulationStarting(EmuThread*))); - connect(this, SIGNAL(EmulationStopping()), registersWidget, SLOT(OnEmulationStopping())); + SLOT(OnGameListOpenSaveFolder(u64))); + connect(this, SIGNAL(EmulationStarting(EmuThread*)), render_window, SLOT(OnEmulationStarting(EmuThread*))); connect(this, SIGNAL(EmulationStopping()), render_window, SLOT(OnEmulationStopping())); - connect(this, SIGNAL(EmulationStarting(EmuThread*)), graphicsTracingWidget, - SLOT(OnEmulationStarting(EmuThread*))); - connect(this, SIGNAL(EmulationStopping()), graphicsTracingWidget, SLOT(OnEmulationStopping())); - connect(this, SIGNAL(EmulationStarting(EmuThread*)), waitTreeWidget, - SLOT(OnEmulationStarting(EmuThread*))); - connect(this, SIGNAL(EmulationStopping()), waitTreeWidget, SLOT(OnEmulationStopping())); + + connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar); +} + +void GMainWindow::ConnectMenuEvents() { + // File + connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile); + connect(ui.action_Load_Symbol_Map, &QAction::triggered, this, + &GMainWindow::OnMenuLoadSymbolMap); + connect(ui.action_Select_Game_List_Root, &QAction::triggered, this, + &GMainWindow::OnMenuSelectGameListRoot); + connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close); + + // Emulation + connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame); + connect(ui.action_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame); + connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame); + connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); + + // View + connect(ui.action_Single_Window_Mode, &QAction::triggered, this, + &GMainWindow::ToggleWindowMode); + connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this, + &GMainWindow::OnDisplayTitleBars); + connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); } void GMainWindow::OnDisplayTitleBars(bool show) { @@ -272,7 +306,7 @@ void GMainWindow::OnDisplayTitleBars(bool show) { } } -bool GMainWindow::LoadROM(const std::string& filename) { +bool GMainWindow::LoadROM(const QString& filename) { // Shutdown previous session if the emu thread is still active... if (emu_thread != nullptr) ShutdownGame(); @@ -290,12 +324,13 @@ bool GMainWindow::LoadROM(const std::string& filename) { Core::System& system{Core::System::GetInstance()}; - const Core::System::ResultStatus result{system.Load(render_window, filename)}; + const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())}; if (result != Core::System::ResultStatus::Success) { switch (result) { case Core::System::ResultStatus::ErrorGetLoader: - LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", filename.c_str()); + LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", + filename.toStdString().c_str()); QMessageBox::critical(this, tr("Error while loading ROM!"), tr("The ROM format is not supported.")); break; @@ -335,7 +370,7 @@ bool GMainWindow::LoadROM(const std::string& filename) { return true; } -void GMainWindow::BootGame(const std::string& filename) { +void GMainWindow::BootGame(const QString& filename) { LOG_INFO(Frontend, "Citra starting..."); StoreRecentFile(filename); // Put the filename on top of the list @@ -374,6 +409,8 @@ void GMainWindow::BootGame(const std::string& filename) { if (ui.action_Single_Window_Mode->isChecked()) { game_list->hide(); } + status_bar_update_timer.start(2000); + render_window->show(); render_window->setFocus(); @@ -408,11 +445,17 @@ void GMainWindow::ShutdownGame() { render_window->hide(); game_list->show(); + // Disable status bar updates + status_bar_update_timer.stop(); + emu_speed_label->setVisible(false); + game_fps_label->setVisible(false); + emu_frametime_label->setVisible(false); + emulation_running = false; } -void GMainWindow::StoreRecentFile(const std::string& filename) { - UISettings::values.recent_files.prepend(QString::fromStdString(filename)); +void GMainWindow::StoreRecentFile(const QString& filename) { + UISettings::values.recent_files.prepend(filename); UISettings::values.recent_files.removeDuplicates(); while (UISettings::values.recent_files.size() > max_recent_files_item) { UISettings::values.recent_files.removeLast(); @@ -447,7 +490,7 @@ void GMainWindow::UpdateRecentFiles() { } void GMainWindow::OnGameListLoadFile(QString game_path) { - BootGame(game_path.toStdString()); + BootGame(game_path); } void GMainWindow::OnGameListOpenSaveFolder(u64 program_id) { @@ -466,19 +509,25 @@ void GMainWindow::OnGameListOpenSaveFolder(u64 program_id) { } void GMainWindow::OnMenuLoadFile() { - QString filename = - QFileDialog::getOpenFileName(this, tr("Load File"), UISettings::values.roms_path, - tr("3DS executable (*.3ds *.3dsx *.elf *.axf *.cci *.cxi)")); + QString extensions; + for (const auto& piece : game_list->supported_file_extensions) + extensions += "*." + piece + " "; + + QString file_filter = tr("3DS Executable") + " (" + extensions + ")"; + file_filter += ";;" + tr("All Files (*.*)"); + + QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), + UISettings::values.roms_path, file_filter); if (!filename.isEmpty()) { UISettings::values.roms_path = QFileInfo(filename).path(); - BootGame(filename.toStdString()); + BootGame(filename); } } void GMainWindow::OnMenuLoadSymbolMap() { QString filename = QFileDialog::getOpenFileName( - this, tr("Load Symbol Map"), UISettings::values.symbols_path, tr("Symbol map (*)")); + this, tr("Load Symbol Map"), UISettings::values.symbols_path, tr("Symbol Map (*.*)")); if (!filename.isEmpty()) { UISettings::values.symbols_path = QFileInfo(filename).path(); @@ -501,7 +550,7 @@ void GMainWindow::OnMenuRecentFile() { QString filename = action->data().toString(); QFileInfo file_info(filename); if (file_info.exists()) { - BootGame(filename.toStdString()); + BootGame(filename); } else { // Display an error message and remove the file from the list. QMessageBox::information(this, tr("File not found"), @@ -564,7 +613,6 @@ void GMainWindow::OnConfigure() { auto result = configureDialog.exec(); if (result == QDialog::Accepted) { configureDialog.applyConfiguration(); - render_window->ReloadSetKeymaps(); config->Save(); } } @@ -581,6 +629,23 @@ void GMainWindow::OnCreateGraphicsSurfaceViewer() { graphicsSurfaceViewerWidget->show(); } +void GMainWindow::UpdateStatusBar() { + if (emu_thread == nullptr) { + status_bar_update_timer.stop(); + return; + } + + auto results = Core::System::GetInstance().GetAndResetPerfStats(); + + emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0)); + game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0)); + emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2)); + + emu_speed_label->setVisible(true); + game_fps_label->setVisible(true); + emu_frametime_label->setVisible(true); +} + bool GMainWindow::ConfirmClose() { if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) return true; @@ -605,7 +670,8 @@ void GMainWindow::closeEvent(QCloseEvent* event) { UISettings::values.microprofile_visible = microProfileDialog->isVisible(); #endif UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); - UISettings::values.display_titlebar = ui.actionDisplay_widget_title_bars->isChecked(); + UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); + UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked(); UISettings::values.first_start = false; game_list->SaveInterfaceLayout(); @@ -620,6 +686,40 @@ void GMainWindow::closeEvent(QCloseEvent* event) { QWidget::closeEvent(event); } +static bool IsSingleFileDropEvent(QDropEvent* event) { + const QMimeData* mimeData = event->mimeData(); + return mimeData->hasUrls() && mimeData->urls().length() == 1; +} + +void GMainWindow::dropEvent(QDropEvent* event) { + if (IsSingleFileDropEvent(event) && ConfirmChangeGame()) { + const QMimeData* mimeData = event->mimeData(); + QString filename = mimeData->urls().at(0).toLocalFile(); + BootGame(filename); + } +} + +void GMainWindow::dragEnterEvent(QDragEnterEvent* event) { + if (IsSingleFileDropEvent(event)) { + event->acceptProposedAction(); + } +} + +void GMainWindow::dragMoveEvent(QDragMoveEvent* event) { + event->acceptProposedAction(); +} + +bool GMainWindow::ConfirmChangeGame() { + if (emu_thread == nullptr) + return true; + + auto answer = QMessageBox::question( + this, tr("Citra"), + tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + return answer != QMessageBox::No; +} + #ifdef main #undef main #endif diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index a2fd45c47..ec841eaa5 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -64,7 +64,7 @@ signals: private: void InitializeWidgets(); - void InitializeDebugMenuActions(); + void InitializeDebugWidgets(); void InitializeRecentFileMenuActions(); void InitializeHotkeys(); @@ -72,15 +72,10 @@ private: void RestoreUIState(); void ConnectWidgetEvents(); + void ConnectMenuEvents(); - /** - * Initializes the emulation system. - * @param system_mode The system mode with which to intialize the kernel. - * @returns Whether the system was properly initialized. - */ - bool InitializeSystem(u32 system_mode); - bool LoadROM(const std::string& filename); - void BootGame(const std::string& filename); + bool LoadROM(const QString& filename); + void BootGame(const QString& filename); void ShutdownGame(); /** @@ -94,7 +89,7 @@ private: * * @param filename the filename to store */ - void StoreRecentFile(const std::string& filename); + void StoreRecentFile(const QString& filename); /** * Updates the recent files menu. @@ -110,6 +105,7 @@ private: * @return true if the user confirmed */ bool ConfirmClose(); + bool ConfirmChangeGame(); void closeEvent(QCloseEvent* event) override; private slots: @@ -131,17 +127,26 @@ private slots: void OnCreateGraphicsSurfaceViewer(); private: + void UpdateStatusBar(); + Ui::MainWindow ui; GRenderWindow* render_window; GameList* game_list; + // Status bar elements + QLabel* emu_speed_label = nullptr; + QLabel* game_fps_label = nullptr; + QLabel* emu_frametime_label = nullptr; + QTimer status_bar_update_timer; + std::unique_ptr<Config> config; // Whether emulation is currently running in Citra. bool emulation_running = false; std::unique_ptr<EmuThread> emu_thread; + // Debugger panes ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; DisassemblerWidget* disasmWidget; @@ -155,6 +160,11 @@ private: WaitTreeWidget* waitTreeWidget; QAction* actions_recent_files[max_recent_files_item]; + +protected: + void dropEvent(QDropEvent* event) override; + void dragEnterEvent(QDragEnterEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; }; #endif // _CITRA_QT_MAIN_HXX_ diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index adfa3689e..47dbb6ef7 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -79,8 +79,17 @@ <property name="title"> <string>&View</string> </property> + <widget class="QMenu" name="menu_View_Debugging"> + <property name="title"> + <string>Debugging</string> + </property> + <addaction name="action_Create_Pica_Surface_Viewer"/> + <addaction name="separator"/> + </widget> <addaction name="action_Single_Window_Mode"/> - <addaction name="actionDisplay_widget_title_bars"/> + <addaction name="action_Display_Dock_Widget_Headers"/> + <addaction name="action_Show_Status_Bar"/> + <addaction name="menu_View_Debugging"/> </widget> <widget class="QMenu" name="menu_Help"> <property name="title"> @@ -93,7 +102,6 @@ <addaction name="menu_View"/> <addaction name="menu_Help"/> </widget> - <widget class="QStatusBar" name="statusbar"/> <action name="action_Load_File"> <property name="text"> <string>Load File...</string> @@ -151,7 +159,7 @@ <string>Configure...</string> </property> </action> - <action name="actionDisplay_widget_title_bars"> + <action name="action_Display_Dock_Widget_Headers"> <property name="checkable"> <bool>true</bool> </property> @@ -159,6 +167,14 @@ <string>Display Dock Widget Headers</string> </property> </action> + <action name="action_Show_Status_Bar"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="text"> + <string>Show Status Bar</string> + </property> + </action> <action name="action_Select_Game_List_Root"> <property name="text"> <string>Select Game Directory...</string> @@ -167,44 +183,11 @@ <string>Selects a folder to display in the game list</string> </property> </action> + <action name="action_Create_Pica_Surface_Viewer"> + <property name="text"> + <string>Create Pica Surface Viewer</string> + </property> + </action> </widget> <resources/> - <connections> - <connection> - <sender>action_Exit</sender> - <signal>triggered()</signal> - <receiver>MainWindow</receiver> - <slot>close()</slot> - <hints> - <hint type="sourcelabel"> - <x>-1</x> - <y>-1</y> - </hint> - <hint type="destinationlabel"> - <x>367</x> - <y>314</y> - </hint> - </hints> - </connection> - <connection> - <sender>actionDisplay_widget_title_bars</sender> - <signal>triggered(bool)</signal> - <receiver>MainWindow</receiver> - <slot>OnDisplayTitleBars(bool)</slot> - <hints> - <hint type="sourcelabel"> - <x>-1</x> - <y>-1</y> - </hint> - <hint type="destinationlabel"> - <x>540</x> - <y>364</y> - </hint> - </hints> - </connection> - </connections> - <slots> - <slot>OnConfigure()</slot> - <slot>OnDisplayTitleBars(bool)</slot> - </slots> </ui> diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h index ed7fdff7e..6408ece2b 100644 --- a/src/citra_qt/ui_settings.h +++ b/src/citra_qt/ui_settings.h @@ -27,6 +27,7 @@ struct Values { bool single_window_mode; bool display_titlebar; + bool show_status_bar; bool confirm_before_closing; bool first_start; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index a7a4a688c..13277a5c2 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,4 +1,27 @@ # Generate cpp with Git revision from template +# Also if this is a CI build, add the build name (ie: Nightly, Bleeding Edge) to the scm_rev file as well +set(REPO_NAME "") +if ($ENV{CI}) + if ($ENV{TRAVIS}) + set(BUILD_REPOSITORY $ENV{TRAVIS_REPO_SLUG}) + elseif($ENV{APPVEYOR}) + set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME}) + endif() + # regex capture the string nightly or bleeding-edge into CMAKE_MATCH_1 + string(REGEX MATCH "citra-emu/citra-?(.*)" OUTVAR ${BUILD_REPOSITORY}) + if (${CMAKE_MATCH_COUNT} GREATER 0) + # capitalize the first letter of each word in the repo name. + string(REPLACE "-" ";" REPO_NAME_LIST ${CMAKE_MATCH_1}) + foreach(WORD ${REPO_NAME_LIST}) + string(SUBSTRING ${WORD} 0 1 FIRST_LETTER) + string(SUBSTRING ${WORD} 1 -1 REMAINDER) + string(TOUPPER ${FIRST_LETTER} FIRST_LETTER) + # this leaves a trailing space on the last word, but we actually want that + # because of how its styled in the title bar. + set(REPO_NAME "${REPO_NAME}${FIRST_LETTER}${REMAINDER} ") + endforeach() + endif() +endif() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp" @ONLY) set(SRCS @@ -12,7 +35,7 @@ set(SRCS memory_util.cpp microprofile.cpp misc.cpp - profiler.cpp + param_package.cpp scm_rev.cpp string_util.cpp symbols.cpp @@ -44,8 +67,8 @@ set(HEADERS memory_util.h microprofile.h microprofileui.h + param_package.h platform.h - profiler_reporting.h quaternion.h scm_rev.h scope_exit.h @@ -61,14 +84,11 @@ set(HEADERS if(ARCHITECTURE_x86_64) set(SRCS ${SRCS} - x64/abi.cpp x64/cpu_detect.cpp - x64/emitter.cpp) + ) set(HEADERS ${HEADERS} - x64/abi.h x64/cpu_detect.h - x64/emitter.h x64/xbyak_abi.h x64/xbyak_util.h ) diff --git a/src/common/bit_set.h b/src/common/bit_set.h index 3059d0cb0..9c2e6b28c 100644 --- a/src/common/bit_set.h +++ b/src/common/bit_set.h @@ -121,22 +121,19 @@ public: class Iterator { public: Iterator(const Iterator& other) : m_val(other.m_val), m_bit(other.m_bit) {} - Iterator(IntTy val, int bit) : m_val(val), m_bit(bit) {} + Iterator(IntTy val) : m_val(val), m_bit(0) {} Iterator& operator=(Iterator other) { new (this) Iterator(other); return *this; } int operator*() { - return m_bit; + return m_bit + ComputeLsb(); } Iterator& operator++() { - if (m_val == 0) { - m_bit = -1; - } else { - int bit = LeastSignificantSetBit(m_val); - m_val &= ~(1 << bit); - m_bit = bit; - } + int lsb = ComputeLsb(); + m_val >>= lsb + 1; + m_bit += lsb + 1; + m_has_lsb = false; return *this; } Iterator operator++(int _) { @@ -145,15 +142,24 @@ public: return other; } bool operator==(Iterator other) const { - return m_bit == other.m_bit; + return m_val == other.m_val; } bool operator!=(Iterator other) const { - return m_bit != other.m_bit; + return m_val != other.m_val; } private: + int ComputeLsb() { + if (!m_has_lsb) { + m_lsb = LeastSignificantSetBit(m_val); + m_has_lsb = true; + } + return m_lsb; + } IntTy m_val; int m_bit; + int m_lsb = -1; + bool m_has_lsb = false; }; BitSet() : m_val(0) {} @@ -221,11 +227,10 @@ public: } Iterator begin() const { - Iterator it(m_val, 0); - return ++it; + return Iterator(m_val); } Iterator end() const { - return Iterator(m_val, -1); + return Iterator(0); } IntTy m_val; diff --git a/src/common/common_paths.h b/src/common/common_paths.h index b56105306..d5b510cdb 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -45,3 +45,4 @@ // Sys files #define SHARED_FONT "shared_font.bin" +#define AES_KEYS "aes_keys.txt" diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 1a1f5d9b5..5ab036b34 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -118,8 +118,7 @@ bool IsDirectory(const std::string& filename) { #endif if (result < 0) { - LOG_WARNING(Common_Filesystem, "stat failed on %s: %s", filename.c_str(), - GetLastErrorMsg()); + LOG_DEBUG(Common_Filesystem, "stat failed on %s: %s", filename.c_str(), GetLastErrorMsg()); return false; } @@ -129,12 +128,12 @@ bool IsDirectory(const std::string& filename) { // Deletes a given filename, return true on success // Doesn't supports deleting a directory bool Delete(const std::string& filename) { - LOG_INFO(Common_Filesystem, "file %s", filename.c_str()); + LOG_TRACE(Common_Filesystem, "file %s", filename.c_str()); // Return true because we care about the file no // being there, not the actual delete. if (!Exists(filename)) { - LOG_WARNING(Common_Filesystem, "%s does not exist", filename.c_str()); + LOG_DEBUG(Common_Filesystem, "%s does not exist", filename.c_str()); return true; } @@ -169,8 +168,7 @@ bool CreateDir(const std::string& path) { return true; DWORD error = GetLastError(); if (error == ERROR_ALREADY_EXISTS) { - LOG_WARNING(Common_Filesystem, "CreateDirectory failed on %s: already exists", - path.c_str()); + LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on %s: already exists", path.c_str()); return true; } LOG_ERROR(Common_Filesystem, "CreateDirectory failed on %s: %i", path.c_str(), error); @@ -182,7 +180,7 @@ bool CreateDir(const std::string& path) { int err = errno; if (err == EEXIST) { - LOG_WARNING(Common_Filesystem, "mkdir failed on %s: already exists", path.c_str()); + LOG_DEBUG(Common_Filesystem, "mkdir failed on %s: already exists", path.c_str()); return true; } @@ -197,7 +195,7 @@ bool CreateFullPath(const std::string& fullPath) { LOG_TRACE(Common_Filesystem, "path %s", fullPath.c_str()); if (FileUtil::Exists(fullPath)) { - LOG_WARNING(Common_Filesystem, "path exists %s", fullPath.c_str()); + LOG_DEBUG(Common_Filesystem, "path exists %s", fullPath.c_str()); return true; } @@ -229,7 +227,7 @@ bool CreateFullPath(const std::string& fullPath) { // Deletes a directory filename, returns true on success bool DeleteDir(const std::string& filename) { - LOG_INFO(Common_Filesystem, "directory %s", filename.c_str()); + LOG_TRACE(Common_Filesystem, "directory %s", filename.c_str()); // check if a directory if (!FileUtil::IsDirectory(filename)) { @@ -303,7 +301,7 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) { // copy loop while (!feof(input)) { // read input - int rnum = fread(buffer, sizeof(char), BSIZE, input); + size_t rnum = fread(buffer, sizeof(char), BSIZE, input); if (rnum != BSIZE) { if (ferror(input) != 0) { LOG_ERROR(Common_Filesystem, "failed reading from source, %s --> %s: %s", @@ -313,7 +311,7 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) { } // write output - int wnum = fwrite(buffer, sizeof(char), rnum, output); + size_t wnum = fwrite(buffer, sizeof(char), rnum, output); if (wnum != rnum) { LOG_ERROR(Common_Filesystem, "failed writing to output, %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg()); @@ -693,6 +691,8 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string& new paths[D_USER_IDX] = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP; if (!FileUtil::IsDirectory(paths[D_USER_IDX])) { paths[D_USER_IDX] = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP; + } else { + LOG_INFO(Common_Filesystem, "Using the local user directory"); } paths[D_CONFIG_IDX] = paths[D_USER_IDX] + CONFIG_DIR DIR_SEP; diff --git a/src/common/framebuffer_layout.cpp b/src/common/framebuffer_layout.cpp index 46c008d9c..a2a0e7dad 100644 --- a/src/common/framebuffer_layout.cpp +++ b/src/common/framebuffer_layout.cpp @@ -6,6 +6,7 @@ #include "common/assert.h" #include "common/framebuffer_layout.h" +#include "core/settings.h" #include "video_core/video_core.h" namespace Layout { @@ -135,4 +136,22 @@ FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool swapped res.bottom_screen = swapped ? large_screen : small_screen; return res; } + +FramebufferLayout CustomFrameLayout(unsigned width, unsigned height) { + ASSERT(width > 0); + ASSERT(height > 0); + + FramebufferLayout res{width, height, true, true, {}, {}}; + + MathUtil::Rectangle<unsigned> top_screen{ + Settings::values.custom_top_left, Settings::values.custom_top_top, + Settings::values.custom_top_right, Settings::values.custom_top_bottom}; + MathUtil::Rectangle<unsigned> bot_screen{ + Settings::values.custom_bottom_left, Settings::values.custom_bottom_top, + Settings::values.custom_bottom_right, Settings::values.custom_bottom_bottom}; + + res.top_screen = top_screen; + res.bottom_screen = bot_screen; + return res; +} } diff --git a/src/common/framebuffer_layout.h b/src/common/framebuffer_layout.h index a125646a3..f1df5c55a 100644 --- a/src/common/framebuffer_layout.h +++ b/src/common/framebuffer_layout.h @@ -44,4 +44,12 @@ FramebufferLayout SingleFrameLayout(unsigned width, unsigned height, bool is_swa * @return Newly created FramebufferLayout object with default screen regions initialized */ FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool is_swapped); + +/** + * Factory method for constructing a custom FramebufferLayout + * @param width Window framebuffer width in pixels + * @param height Window framebuffer height in pixels + * @return Newly created FramebufferLayout object with default screen regions initialized + */ +FramebufferLayout CustomFrameLayout(unsigned width, unsigned height); } diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 2ef3e6b05..42f6a9918 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -55,6 +55,7 @@ namespace Log { SUB(Service, DSP) \ SUB(Service, DLP) \ SUB(Service, HID) \ + SUB(Service, HTTP) \ SUB(Service, SOC) \ SUB(Service, IR) \ SUB(Service, Y2R) \ @@ -62,6 +63,7 @@ namespace Log { SUB(HW, Memory) \ SUB(HW, LCD) \ SUB(HW, GPU) \ + SUB(HW, AES) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Software) \ @@ -69,6 +71,7 @@ namespace Log { CLS(Audio) \ SUB(Audio, DSP) \ SUB(Audio, Sink) \ + CLS(Input) \ CLS(Loader) // GetClassName is a macro defined by Windows.h, grrr... diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 4330ef879..1b905f66c 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -72,6 +72,7 @@ enum class Class : ClassType { Service_DSP, ///< The DSP (DSP control) service Service_DLP, ///< The DLP (Download Play) service Service_HID, ///< The HID (Human interface device) service + Service_HTTP, ///< The HTTP service Service_SOC, ///< The SOC (Socket) service Service_IR, ///< The IR service Service_Y2R, ///< The Y2R (YUV to RGB conversion) service @@ -79,6 +80,7 @@ enum class Class : ClassType { HW_Memory, ///< Memory-map and address translation HW_LCD, ///< LCD register emulation HW_GPU, ///< GPU control emulation + HW_AES, ///< AES engine emulation Frontend, ///< Emulator UI Render, ///< Emulator video output and hardware acceleration Render_Software, ///< Software renderer backend @@ -87,6 +89,7 @@ enum class Class : ClassType { Audio_DSP, ///< The HLE implementation of the DSP Audio_Sink, ///< Emulator audio output backend Loader, ///< ROM loader + Input, ///< Input emulation Count ///< Total number of logging classes }; diff --git a/src/common/param_package.cpp b/src/common/param_package.cpp new file mode 100644 index 000000000..3a6ef8c27 --- /dev/null +++ b/src/common/param_package.cpp @@ -0,0 +1,120 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <vector> +#include "common/logging/log.h" +#include "common/param_package.h" +#include "common/string_util.h" + +namespace Common { + +constexpr char KEY_VALUE_SEPARATOR = ':'; +constexpr char PARAM_SEPARATOR = ','; +constexpr char ESCAPE_CHARACTER = '$'; +const std::string KEY_VALUE_SEPARATOR_ESCAPE{ESCAPE_CHARACTER, '0'}; +const std::string PARAM_SEPARATOR_ESCAPE{ESCAPE_CHARACTER, '1'}; +const std::string ESCAPE_CHARACTER_ESCAPE{ESCAPE_CHARACTER, '2'}; + +ParamPackage::ParamPackage(const std::string& serialized) { + std::vector<std::string> pairs; + Common::SplitString(serialized, PARAM_SEPARATOR, pairs); + + for (const std::string& pair : pairs) { + std::vector<std::string> key_value; + Common::SplitString(pair, KEY_VALUE_SEPARATOR, key_value); + if (key_value.size() != 2) { + LOG_ERROR(Common, "invalid key pair %s", pair.c_str()); + continue; + } + + for (std::string& part : key_value) { + part = Common::ReplaceAll(part, KEY_VALUE_SEPARATOR_ESCAPE, {KEY_VALUE_SEPARATOR}); + part = Common::ReplaceAll(part, PARAM_SEPARATOR_ESCAPE, {PARAM_SEPARATOR}); + part = Common::ReplaceAll(part, ESCAPE_CHARACTER_ESCAPE, {ESCAPE_CHARACTER}); + } + + Set(key_value[0], key_value[1]); + } +} + +ParamPackage::ParamPackage(std::initializer_list<DataType::value_type> list) : data(list) {} + +std::string ParamPackage::Serialize() const { + if (data.empty()) + return ""; + + std::string result; + + for (const auto& pair : data) { + std::array<std::string, 2> key_value{{pair.first, pair.second}}; + for (std::string& part : key_value) { + part = Common::ReplaceAll(part, {ESCAPE_CHARACTER}, ESCAPE_CHARACTER_ESCAPE); + part = Common::ReplaceAll(part, {PARAM_SEPARATOR}, PARAM_SEPARATOR_ESCAPE); + part = Common::ReplaceAll(part, {KEY_VALUE_SEPARATOR}, KEY_VALUE_SEPARATOR_ESCAPE); + } + result += key_value[0] + KEY_VALUE_SEPARATOR + key_value[1] + PARAM_SEPARATOR; + } + + result.pop_back(); // discard the trailing PARAM_SEPARATOR + return result; +} + +std::string ParamPackage::Get(const std::string& key, const std::string& default_value) const { + auto pair = data.find(key); + if (pair == data.end()) { + LOG_DEBUG(Common, "key %s not found", key.c_str()); + return default_value; + } + + return pair->second; +} + +int ParamPackage::Get(const std::string& key, int default_value) const { + auto pair = data.find(key); + if (pair == data.end()) { + LOG_DEBUG(Common, "key %s not found", key.c_str()); + return default_value; + } + + try { + return std::stoi(pair->second); + } catch (const std::logic_error&) { + LOG_ERROR(Common, "failed to convert %s to int", pair->second.c_str()); + return default_value; + } +} + +float ParamPackage::Get(const std::string& key, float default_value) const { + auto pair = data.find(key); + if (pair == data.end()) { + LOG_DEBUG(Common, "key %s not found", key.c_str()); + return default_value; + } + + try { + return std::stof(pair->second); + } catch (const std::logic_error&) { + LOG_ERROR(Common, "failed to convert %s to float", pair->second.c_str()); + return default_value; + } +} + +void ParamPackage::Set(const std::string& key, const std::string& value) { + data[key] = value; +} + +void ParamPackage::Set(const std::string& key, int value) { + data[key] = std::to_string(value); +} + +void ParamPackage::Set(const std::string& key, float value) { + data[key] = std::to_string(value); +} + +bool ParamPackage::Has(const std::string& key) const { + return data.find(key) != data.end(); +} + +} // namespace Common diff --git a/src/common/param_package.h b/src/common/param_package.h new file mode 100644 index 000000000..c4c11b221 --- /dev/null +++ b/src/common/param_package.h @@ -0,0 +1,40 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <initializer_list> +#include <string> +#include <unordered_map> + +namespace Common { + +/// A string-based key-value container supporting serializing to and deserializing from a string +class ParamPackage { +public: + using DataType = std::unordered_map<std::string, std::string>; + + ParamPackage() = default; + explicit ParamPackage(const std::string& serialized); + ParamPackage(std::initializer_list<DataType::value_type> list); + ParamPackage(const ParamPackage& other) = default; + ParamPackage(ParamPackage&& other) = default; + + ParamPackage& operator=(const ParamPackage& other) = default; + ParamPackage& operator=(ParamPackage&& other) = default; + + std::string Serialize() const; + std::string Get(const std::string& key, const std::string& default_value) const; + int Get(const std::string& key, int default_value) const; + float Get(const std::string& key, float default_value) const; + void Set(const std::string& key, const std::string& value); + void Set(const std::string& key, int value); + void Set(const std::string& key, float value); + bool Has(const std::string& key) const; + +private: + DataType data; +}; + +} // namespace Common diff --git a/src/common/profiler.cpp b/src/common/profiler.cpp deleted file mode 100644 index b40e7205d..000000000 --- a/src/common/profiler.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <cstddef> -#include <vector> -#include "common/assert.h" -#include "common/profiler_reporting.h" -#include "common/synchronized_wrapper.h" - -namespace Common { -namespace Profiling { - -ProfilingManager::ProfilingManager() - : last_frame_end(Clock::now()), this_frame_start(Clock::now()) {} - -void ProfilingManager::BeginFrame() { - this_frame_start = Clock::now(); -} - -void ProfilingManager::FinishFrame() { - Clock::time_point now = Clock::now(); - - results.interframe_time = now - last_frame_end; - results.frame_time = now - this_frame_start; - - last_frame_end = now; -} - -TimingResultsAggregator::TimingResultsAggregator(size_t window_size) - : max_window_size(window_size), window_size(0) { - interframe_times.resize(window_size, Duration::zero()); - frame_times.resize(window_size, Duration::zero()); -} - -void TimingResultsAggregator::Clear() { - window_size = cursor = 0; -} - -void TimingResultsAggregator::AddFrame(const ProfilingFrameResult& frame_result) { - interframe_times[cursor] = frame_result.interframe_time; - frame_times[cursor] = frame_result.frame_time; - - ++cursor; - if (cursor == max_window_size) - cursor = 0; - if (window_size < max_window_size) - ++window_size; -} - -static AggregatedDuration AggregateField(const std::vector<Duration>& v, size_t len) { - AggregatedDuration result; - result.avg = Duration::zero(); - result.min = result.max = (len == 0 ? Duration::zero() : v[0]); - - for (size_t i = 0; i < len; ++i) { - Duration value = v[i]; - result.avg += value; - result.min = std::min(result.min, value); - result.max = std::max(result.max, value); - } - if (len != 0) - result.avg /= len; - - return result; -} - -static float tof(Common::Profiling::Duration dur) { - using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>; - return std::chrono::duration_cast<FloatMs>(dur).count(); -} - -AggregatedFrameResult TimingResultsAggregator::GetAggregatedResults() const { - AggregatedFrameResult result; - - result.interframe_time = AggregateField(interframe_times, window_size); - result.frame_time = AggregateField(frame_times, window_size); - - if (result.interframe_time.avg != Duration::zero()) { - result.fps = 1000.0f / tof(result.interframe_time.avg); - } else { - result.fps = 0.0f; - } - - return result; -} - -ProfilingManager& GetProfilingManager() { - // Takes advantage of "magic" static initialization for race-free initialization. - static ProfilingManager manager; - return manager; -} - -SynchronizedRef<TimingResultsAggregator> GetTimingResultsAggregator() { - static SynchronizedWrapper<TimingResultsAggregator> aggregator(30); - return SynchronizedRef<TimingResultsAggregator>(aggregator); -} - -} // namespace Profiling -} // namespace Common diff --git a/src/common/profiler_reporting.h b/src/common/profiler_reporting.h deleted file mode 100644 index e9ce6d41c..000000000 --- a/src/common/profiler_reporting.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <chrono> -#include <cstddef> -#include <vector> -#include "common/synchronized_wrapper.h" - -namespace Common { -namespace Profiling { - -using Clock = std::chrono::high_resolution_clock; -using Duration = Clock::duration; - -struct ProfilingFrameResult { - /// Time since the last delivered frame - Duration interframe_time; - - /// Time spent processing a frame, excluding VSync - Duration frame_time; -}; - -class ProfilingManager final { -public: - ProfilingManager(); - - /// This should be called after swapping screen buffers. - void BeginFrame(); - /// This should be called before swapping screen buffers. - void FinishFrame(); - - /// Get the timing results from the previous frame. This is updated when you call FinishFrame(). - const ProfilingFrameResult& GetPreviousFrameResults() const { - return results; - } - -private: - Clock::time_point last_frame_end; - Clock::time_point this_frame_start; - - ProfilingFrameResult results; -}; - -struct AggregatedDuration { - Duration avg, min, max; -}; - -struct AggregatedFrameResult { - /// Time since the last delivered frame - AggregatedDuration interframe_time; - - /// Time spent processing a frame, excluding VSync - AggregatedDuration frame_time; - - float fps; -}; - -class TimingResultsAggregator final { -public: - TimingResultsAggregator(size_t window_size); - - void Clear(); - - void AddFrame(const ProfilingFrameResult& frame_result); - - AggregatedFrameResult GetAggregatedResults() const; - - size_t max_window_size; - size_t window_size; - size_t cursor; - - std::vector<Duration> interframe_times; - std::vector<Duration> frame_times; -}; - -ProfilingManager& GetProfilingManager(); -SynchronizedRef<TimingResultsAggregator> GetTimingResultsAggregator(); - -} // namespace Profiling -} // namespace Common diff --git a/src/common/scm_rev.cpp.in b/src/common/scm_rev.cpp.in index 79b404bb8..0080db5d5 100644 --- a/src/common/scm_rev.cpp.in +++ b/src/common/scm_rev.cpp.in @@ -7,12 +7,14 @@ #define GIT_REV "@GIT_REV@" #define GIT_BRANCH "@GIT_BRANCH@" #define GIT_DESC "@GIT_DESC@" +#define BUILD_NAME "@REPO_NAME@" namespace Common { const char g_scm_rev[] = GIT_REV; const char g_scm_branch[] = GIT_BRANCH; const char g_scm_desc[] = GIT_DESC; +const char g_build_name[] = BUILD_NAME; } // namespace diff --git a/src/common/scm_rev.h b/src/common/scm_rev.h index 0ef190afa..e22389803 100644 --- a/src/common/scm_rev.h +++ b/src/common/scm_rev.h @@ -9,5 +9,6 @@ namespace Common { extern const char g_scm_rev[]; extern const char g_scm_branch[]; extern const char g_scm_desc[]; +extern const char g_build_name[]; } // namespace diff --git a/src/common/synchronized_wrapper.h b/src/common/synchronized_wrapper.h index 04b4f2e51..4a1984c46 100644 --- a/src/common/synchronized_wrapper.h +++ b/src/common/synchronized_wrapper.h @@ -9,25 +9,8 @@ namespace Common { -/** - * Wraps an object, only allowing access to it via a locking reference wrapper. Good to ensure no - * one forgets to lock a mutex before acessing an object. To access the wrapped object construct a - * SyncronizedRef on this wrapper. Inspired by Rust's Mutex type - * (http://doc.rust-lang.org/std/sync/struct.Mutex.html). - */ template <typename T> -class SynchronizedWrapper { -public: - template <typename... Args> - SynchronizedWrapper(Args&&... args) : data(std::forward<Args>(args)...) {} - -private: - template <typename U> - friend class SynchronizedRef; - - std::mutex mutex; - T data; -}; +class SynchronizedWrapper; /** * Synchronized reference, that keeps a SynchronizedWrapper's mutex locked during its lifetime. This @@ -75,4 +58,28 @@ private: SynchronizedWrapper<T>* wrapper; }; +/** + * Wraps an object, only allowing access to it via a locking reference wrapper. Good to ensure no + * one forgets to lock a mutex before acessing an object. To access the wrapped object construct a + * SyncronizedRef on this wrapper. Inspired by Rust's Mutex type + * (http://doc.rust-lang.org/std/sync/struct.Mutex.html). + */ +template <typename T> +class SynchronizedWrapper { +public: + template <typename... Args> + SynchronizedWrapper(Args&&... args) : data(std::forward<Args>(args)...) {} + + SynchronizedRef<T> Lock() { + return {*this}; + } + +private: + template <typename U> + friend class SynchronizedRef; + + std::mutex mutex; + T data; +}; + } // namespace Common diff --git a/src/common/x64/abi.cpp b/src/common/x64/abi.cpp deleted file mode 100644 index 504b9c940..000000000 --- a/src/common/x64/abi.cpp +++ /dev/null @@ -1,350 +0,0 @@ -// Copyright (C) 2003 Dolphin Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official SVN repository and contact information can be found at -// http://code.google.com/p/dolphin-emu/ - -#include "abi.h" -#include "emitter.h" - -using namespace Gen; - -// Shared code between Win64 and Unix64 - -void XEmitter::ABI_CalculateFrameSize(BitSet32 mask, size_t rsp_alignment, size_t needed_frame_size, - size_t* shadowp, size_t* subtractionp, size_t* xmm_offsetp) { - size_t shadow = 0; -#if defined(_WIN32) - shadow = 0x20; -#endif - - int count = (mask & ABI_ALL_GPRS).Count(); - rsp_alignment -= count * 8; - size_t subtraction = 0; - int fpr_count = (mask & ABI_ALL_FPRS).Count(); - if (fpr_count) { - // If we have any XMMs to save, we must align the stack here. - subtraction = rsp_alignment & 0xf; - } - subtraction += 16 * fpr_count; - size_t xmm_base_subtraction = subtraction; - subtraction += needed_frame_size; - subtraction += shadow; - // Final alignment. - rsp_alignment -= subtraction; - subtraction += rsp_alignment & 0xf; - - *shadowp = shadow; - *subtractionp = subtraction; - *xmm_offsetp = subtraction - xmm_base_subtraction; -} - -size_t XEmitter::ABI_PushRegistersAndAdjustStack(BitSet32 mask, size_t rsp_alignment, - size_t needed_frame_size) { - size_t shadow, subtraction, xmm_offset; - ABI_CalculateFrameSize(mask, rsp_alignment, needed_frame_size, &shadow, &subtraction, - &xmm_offset); - - for (int r : mask& ABI_ALL_GPRS) - PUSH((X64Reg)r); - - if (subtraction) - SUB(64, R(RSP), subtraction >= 0x80 ? Imm32((u32)subtraction) : Imm8((u8)subtraction)); - - for (int x : mask& ABI_ALL_FPRS) { - MOVAPD(MDisp(RSP, (int)xmm_offset), (X64Reg)(x - 16)); - xmm_offset += 16; - } - - return shadow; -} - -void XEmitter::ABI_PopRegistersAndAdjustStack(BitSet32 mask, size_t rsp_alignment, - size_t needed_frame_size) { - size_t shadow, subtraction, xmm_offset; - ABI_CalculateFrameSize(mask, rsp_alignment, needed_frame_size, &shadow, &subtraction, - &xmm_offset); - - for (int x : mask& ABI_ALL_FPRS) { - MOVAPD((X64Reg)(x - 16), MDisp(RSP, (int)xmm_offset)); - xmm_offset += 16; - } - - if (subtraction) - ADD(64, R(RSP), subtraction >= 0x80 ? Imm32((u32)subtraction) : Imm8((u8)subtraction)); - - for (int r = 15; r >= 0; r--) { - if (mask[r]) - POP((X64Reg)r); - } -} - -// Common functions -void XEmitter::ABI_CallFunction(const void* func) { - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionC16(const void* func, u16 param1) { - MOV(32, R(ABI_PARAM1), Imm32((u32)param1)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionCC16(const void* func, u32 param1, u16 param2) { - MOV(32, R(ABI_PARAM1), Imm32(param1)); - MOV(32, R(ABI_PARAM2), Imm32((u32)param2)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionC(const void* func, u32 param1) { - MOV(32, R(ABI_PARAM1), Imm32(param1)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionCC(const void* func, u32 param1, u32 param2) { - MOV(32, R(ABI_PARAM1), Imm32(param1)); - MOV(32, R(ABI_PARAM2), Imm32(param2)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionCCC(const void* func, u32 param1, u32 param2, u32 param3) { - MOV(32, R(ABI_PARAM1), Imm32(param1)); - MOV(32, R(ABI_PARAM2), Imm32(param2)); - MOV(32, R(ABI_PARAM3), Imm32(param3)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionCCP(const void* func, u32 param1, u32 param2, void* param3) { - MOV(32, R(ABI_PARAM1), Imm32(param1)); - MOV(32, R(ABI_PARAM2), Imm32(param2)); - MOV(64, R(ABI_PARAM3), ImmPtr(param3)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionCCCP(const void* func, u32 param1, u32 param2, u32 param3, - void* param4) { - MOV(32, R(ABI_PARAM1), Imm32(param1)); - MOV(32, R(ABI_PARAM2), Imm32(param2)); - MOV(32, R(ABI_PARAM3), Imm32(param3)); - MOV(64, R(ABI_PARAM4), ImmPtr(param4)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionP(const void* func, void* param1) { - MOV(64, R(ABI_PARAM1), ImmPtr(param1)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionPA(const void* func, void* param1, const Gen::OpArg& arg2) { - MOV(64, R(ABI_PARAM1), ImmPtr(param1)); - if (!arg2.IsSimpleReg(ABI_PARAM2)) - MOV(32, R(ABI_PARAM2), arg2); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionPAA(const void* func, void* param1, const Gen::OpArg& arg2, - const Gen::OpArg& arg3) { - MOV(64, R(ABI_PARAM1), ImmPtr(param1)); - if (!arg2.IsSimpleReg(ABI_PARAM2)) - MOV(32, R(ABI_PARAM2), arg2); - if (!arg3.IsSimpleReg(ABI_PARAM3)) - MOV(32, R(ABI_PARAM3), arg3); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionPPC(const void* func, void* param1, void* param2, u32 param3) { - MOV(64, R(ABI_PARAM1), ImmPtr(param1)); - MOV(64, R(ABI_PARAM2), ImmPtr(param2)); - MOV(32, R(ABI_PARAM3), Imm32(param3)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -// Pass a register as a parameter. -void XEmitter::ABI_CallFunctionR(const void* func, X64Reg reg1) { - if (reg1 != ABI_PARAM1) - MOV(32, R(ABI_PARAM1), R(reg1)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -// Pass two registers as parameters. -void XEmitter::ABI_CallFunctionRR(const void* func, X64Reg reg1, X64Reg reg2) { - if (reg2 != ABI_PARAM1) { - if (reg1 != ABI_PARAM1) - MOV(64, R(ABI_PARAM1), R(reg1)); - if (reg2 != ABI_PARAM2) - MOV(64, R(ABI_PARAM2), R(reg2)); - } else { - if (reg2 != ABI_PARAM2) - MOV(64, R(ABI_PARAM2), R(reg2)); - if (reg1 != ABI_PARAM1) - MOV(64, R(ABI_PARAM1), R(reg1)); - } - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionAC(const void* func, const Gen::OpArg& arg1, u32 param2) { - if (!arg1.IsSimpleReg(ABI_PARAM1)) - MOV(32, R(ABI_PARAM1), arg1); - MOV(32, R(ABI_PARAM2), Imm32(param2)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionACC(const void* func, const Gen::OpArg& arg1, u32 param2, - u32 param3) { - if (!arg1.IsSimpleReg(ABI_PARAM1)) - MOV(32, R(ABI_PARAM1), arg1); - MOV(32, R(ABI_PARAM2), Imm32(param2)); - MOV(64, R(ABI_PARAM3), Imm64(param3)); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionA(const void* func, const Gen::OpArg& arg1) { - if (!arg1.IsSimpleReg(ABI_PARAM1)) - MOV(32, R(ABI_PARAM1), arg1); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -} - -void XEmitter::ABI_CallFunctionAA(const void* func, const Gen::OpArg& arg1, - const Gen::OpArg& arg2) { - if (!arg1.IsSimpleReg(ABI_PARAM1)) - MOV(32, R(ABI_PARAM1), arg1); - if (!arg2.IsSimpleReg(ABI_PARAM2)) - MOV(32, R(ABI_PARAM2), arg2); - u64 distance = u64(func) - (u64(code) + 5); - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - MOV(64, R(RAX), ImmPtr(func)); - CALLptr(R(RAX)); - } else { - CALL(func); - } -}
\ No newline at end of file diff --git a/src/common/x64/abi.h b/src/common/x64/abi.h deleted file mode 100644 index eaaf81d89..000000000 --- a/src/common/x64/abi.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2008 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include "common/bit_set.h" -#include "emitter.h" - -// x64 ABI:s, and helpers to help follow them when JIT-ing code. -// All convensions return values in EAX (+ possibly EDX). - -// Windows 64-bit -// * 4-reg "fastcall" variant, very new-skool stack handling -// * Callee moves stack pointer, to make room for shadow regs for the biggest function _it itself -// calls_ -// * Parameters passed in RCX, RDX, ... further parameters are MOVed into the allocated stack space. -// Scratch: RAX RCX RDX R8 R9 R10 R11 -// Callee-save: RBX RSI RDI RBP R12 R13 R14 R15 -// Parameters: RCX RDX R8 R9, further MOV-ed - -// Linux 64-bit -// * 6-reg "fastcall" variant, old skool stack handling (parameters are pushed) -// Scratch: RAX RCX RDX RSI RDI R8 R9 R10 R11 -// Callee-save: RBX RBP R12 R13 R14 R15 -// Parameters: RDI RSI RDX RCX R8 R9 - -#define ABI_ALL_FPRS BitSet32(0xffff0000) -#define ABI_ALL_GPRS BitSet32(0x0000ffff) - -#ifdef _WIN32 // 64-bit Windows - the really exotic calling convention - -#define ABI_PARAM1 RCX -#define ABI_PARAM2 RDX -#define ABI_PARAM3 R8 -#define ABI_PARAM4 R9 - -// xmm0-xmm15 use the upper 16 bits in the functions that push/pop registers. -#define ABI_ALL_CALLER_SAVED \ - (BitSet32{RAX, RCX, RDX, R8, R9, R10, R11, XMM0 + 16, XMM1 + 16, XMM2 + 16, XMM3 + 16, \ - XMM4 + 16, XMM5 + 16}) -#else // 64-bit Unix / OS X - -#define ABI_PARAM1 RDI -#define ABI_PARAM2 RSI -#define ABI_PARAM3 RDX -#define ABI_PARAM4 RCX -#define ABI_PARAM5 R8 -#define ABI_PARAM6 R9 - -// TODO: Avoid pushing all 16 XMM registers when possible. Most functions we call probably -// don't actually clobber them. -#define ABI_ALL_CALLER_SAVED (BitSet32{RAX, RCX, RDX, RDI, RSI, R8, R9, R10, R11} | ABI_ALL_FPRS) -#endif // WIN32 - -#define ABI_ALL_CALLEE_SAVED (~ABI_ALL_CALLER_SAVED) - -#define ABI_RETURN RAX diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp index 370ae2c80..2cb3ab9cc 100644 --- a/src/common/x64/cpu_detect.cpp +++ b/src/common/x64/cpu_detect.cpp @@ -8,9 +8,9 @@ #include "common/common_types.h" #include "cpu_detect.h" -namespace Common { - -#ifndef _MSC_VER +#ifdef _MSC_VER +#include <intrin.h> +#else #if defined(__DragonFly__) || defined(__FreeBSD__) // clang-format off @@ -37,13 +37,15 @@ static inline void __cpuid(int info[4], int function_id) { } #define _XCR_XFEATURE_ENABLED_MASK 0 -static u64 _xgetbv(u32 index) { +static inline u64 _xgetbv(u32 index) { u32 eax, edx; __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index)); return ((u64)edx << 32) | eax; } -#endif // ifndef _MSC_VER +#endif // _MSC_VER + +namespace Common { // Detects the various CPU features static CPUCaps Detect() { diff --git a/src/common/x64/emitter.cpp b/src/common/x64/emitter.cpp deleted file mode 100644 index f5930abec..000000000 --- a/src/common/x64/emitter.cpp +++ /dev/null @@ -1,2583 +0,0 @@ -// Copyright (C) 2003 Dolphin Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official SVN repository and contact information can be found at -// http://code.google.com/p/dolphin-emu/ - -#include <cinttypes> -#include <cstring> -#include "abi.h" -#include "common/assert.h" -#include "common/logging/log.h" -#include "common/memory_util.h" -#include "cpu_detect.h" -#include "emitter.h" - -namespace Gen { - -struct NormalOpDef { - u8 toRm8, toRm32, fromRm8, fromRm32, imm8, imm32, simm8, eaximm8, eaximm32, ext; -}; - -// 0xCC is code for invalid combination of immediates -static const NormalOpDef normalops[11] = { - {0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x83, 0x04, 0x05, 0}, // ADD - {0x10, 0x11, 0x12, 0x13, 0x80, 0x81, 0x83, 0x14, 0x15, 2}, // ADC - - {0x28, 0x29, 0x2A, 0x2B, 0x80, 0x81, 0x83, 0x2C, 0x2D, 5}, // SUB - {0x18, 0x19, 0x1A, 0x1B, 0x80, 0x81, 0x83, 0x1C, 0x1D, 3}, // SBB - - {0x20, 0x21, 0x22, 0x23, 0x80, 0x81, 0x83, 0x24, 0x25, 4}, // AND - {0x08, 0x09, 0x0A, 0x0B, 0x80, 0x81, 0x83, 0x0C, 0x0D, 1}, // OR - - {0x30, 0x31, 0x32, 0x33, 0x80, 0x81, 0x83, 0x34, 0x35, 6}, // XOR - {0x88, 0x89, 0x8A, 0x8B, 0xC6, 0xC7, 0xCC, 0xCC, 0xCC, 0}, // MOV - - {0x84, 0x85, 0x84, 0x85, 0xF6, 0xF7, 0xCC, 0xA8, 0xA9, 0}, // TEST (to == from) - {0x38, 0x39, 0x3A, 0x3B, 0x80, 0x81, 0x83, 0x3C, 0x3D, 7}, // CMP - - {0x86, 0x87, 0x86, 0x87, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 7}, // XCHG -}; - -enum NormalSSEOps { - sseCMP = 0xC2, - sseADD = 0x58, // ADD - sseSUB = 0x5C, // SUB - sseAND = 0x54, // AND - sseANDN = 0x55, // ANDN - sseOR = 0x56, - sseXOR = 0x57, - sseMUL = 0x59, // MUL - sseDIV = 0x5E, // DIV - sseMIN = 0x5D, // MIN - sseMAX = 0x5F, // MAX - sseCOMIS = 0x2F, // COMIS - sseUCOMIS = 0x2E, // UCOMIS - sseSQRT = 0x51, // SQRT - sseRSQRT = 0x52, // RSQRT (NO DOUBLE PRECISION!!!) - sseRCP = 0x53, // RCP - sseMOVAPfromRM = 0x28, // MOVAP from RM - sseMOVAPtoRM = 0x29, // MOVAP to RM - sseMOVUPfromRM = 0x10, // MOVUP from RM - sseMOVUPtoRM = 0x11, // MOVUP to RM - sseMOVLPfromRM = 0x12, - sseMOVLPtoRM = 0x13, - sseMOVHPfromRM = 0x16, - sseMOVHPtoRM = 0x17, - sseMOVHLPS = 0x12, - sseMOVLHPS = 0x16, - sseMOVDQfromRM = 0x6F, - sseMOVDQtoRM = 0x7F, - sseMASKMOVDQU = 0xF7, - sseLDDQU = 0xF0, - sseSHUF = 0xC6, - sseMOVNTDQ = 0xE7, - sseMOVNTP = 0x2B, - sseHADD = 0x7C, -}; - -void XEmitter::SetCodePtr(u8* ptr) { - code = ptr; -} - -const u8* XEmitter::GetCodePtr() const { - return code; -} - -u8* XEmitter::GetWritableCodePtr() { - return code; -} - -void XEmitter::Write8(u8 value) { - *code++ = value; -} - -void XEmitter::Write16(u16 value) { - std::memcpy(code, &value, sizeof(u16)); - code += sizeof(u16); -} - -void XEmitter::Write32(u32 value) { - std::memcpy(code, &value, sizeof(u32)); - code += sizeof(u32); -} - -void XEmitter::Write64(u64 value) { - std::memcpy(code, &value, sizeof(u64)); - code += sizeof(u64); -} - -void XEmitter::ReserveCodeSpace(int bytes) { - for (int i = 0; i < bytes; i++) - *code++ = 0xCC; -} - -const u8* XEmitter::AlignCode4() { - int c = int((u64)code & 3); - if (c) - ReserveCodeSpace(4 - c); - return code; -} - -const u8* XEmitter::AlignCode16() { - int c = int((u64)code & 15); - if (c) - ReserveCodeSpace(16 - c); - return code; -} - -const u8* XEmitter::AlignCodePage() { - int c = int((u64)code & 4095); - if (c) - ReserveCodeSpace(4096 - c); - return code; -} - -// This operation modifies flags; check to see the flags are locked. -// If the flags are locked, we should immediately and loudly fail before -// causing a subtle JIT bug. -void XEmitter::CheckFlags() { - ASSERT_MSG(!flags_locked, "Attempt to modify flags while flags locked!"); -} - -void XEmitter::WriteModRM(int mod, int reg, int rm) { - Write8((u8)((mod << 6) | ((reg & 7) << 3) | (rm & 7))); -} - -void XEmitter::WriteSIB(int scale, int index, int base) { - Write8((u8)((scale << 6) | ((index & 7) << 3) | (base & 7))); -} - -void OpArg::WriteRex(XEmitter* emit, int opBits, int bits, int customOp) const { - if (customOp == -1) - customOp = operandReg; -#ifdef ARCHITECTURE_x86_64 - u8 op = 0x40; - // REX.W (whether operation is a 64-bit operation) - if (opBits == 64) - op |= 8; - // REX.R (whether ModR/M reg field refers to R8-R15. - if (customOp & 8) - op |= 4; - // REX.X (whether ModR/M SIB index field refers to R8-R15) - if (indexReg & 8) - op |= 2; - // REX.B (whether ModR/M rm or SIB base or opcode reg field refers to R8-R15) - if (offsetOrBaseReg & 8) - op |= 1; - // Write REX if wr have REX bits to write, or if the operation accesses - // SIL, DIL, BPL, or SPL. - if (op != 0x40 || (scale == SCALE_NONE && bits == 8 && (offsetOrBaseReg & 0x10c) == 4) || - (opBits == 8 && (customOp & 0x10c) == 4)) { - emit->Write8(op); - // Check the operation doesn't access AH, BH, CH, or DH. - DEBUG_ASSERT((offsetOrBaseReg & 0x100) == 0); - DEBUG_ASSERT((customOp & 0x100) == 0); - } -#else - DEBUG_ASSERT(opBits != 64); - DEBUG_ASSERT((customOp & 8) == 0 || customOp == -1); - DEBUG_ASSERT((indexReg & 8) == 0); - DEBUG_ASSERT((offsetOrBaseReg & 8) == 0); - DEBUG_ASSERT(opBits != 8 || (customOp & 0x10c) != 4 || customOp == -1); - DEBUG_ASSERT(scale == SCALE_ATREG || bits != 8 || (offsetOrBaseReg & 0x10c) != 4); -#endif -} - -void OpArg::WriteVex(XEmitter* emit, X64Reg regOp1, X64Reg regOp2, int L, int pp, int mmmmm, - int W) const { - int R = !(regOp1 & 8); - int X = !(indexReg & 8); - int B = !(offsetOrBaseReg & 8); - - int vvvv = (regOp2 == X64Reg::INVALID_REG) ? 0xf : (regOp2 ^ 0xf); - - // do we need any VEX fields that only appear in the three-byte form? - if (X == 1 && B == 1 && W == 0 && mmmmm == 1) { - u8 RvvvvLpp = (R << 7) | (vvvv << 3) | (L << 2) | pp; - emit->Write8(0xC5); - emit->Write8(RvvvvLpp); - } else { - u8 RXBmmmmm = (R << 7) | (X << 6) | (B << 5) | mmmmm; - u8 WvvvvLpp = (W << 7) | (vvvv << 3) | (L << 2) | pp; - emit->Write8(0xC4); - emit->Write8(RXBmmmmm); - emit->Write8(WvvvvLpp); - } -} - -void OpArg::WriteRest(XEmitter* emit, int extraBytes, X64Reg _operandReg, - bool warn_64bit_offset) const { - if (_operandReg == INVALID_REG) - _operandReg = (X64Reg)this->operandReg; - int mod = 0; - int ireg = indexReg; - bool SIB = false; - int _offsetOrBaseReg = this->offsetOrBaseReg; - - if (scale == SCALE_RIP) // Also, on 32-bit, just an immediate address - { - // Oh, RIP addressing. - _offsetOrBaseReg = 5; - emit->WriteModRM(0, _operandReg, _offsetOrBaseReg); -// TODO : add some checks -#ifdef ARCHITECTURE_x86_64 - u64 ripAddr = (u64)emit->GetCodePtr() + 4 + extraBytes; - s64 distance = (s64)offset - (s64)ripAddr; - ASSERT_MSG((distance < 0x80000000LL && distance >= -0x80000000LL) || !warn_64bit_offset, - "WriteRest: op out of range (0x%" PRIx64 " uses 0x%" PRIx64 ")", ripAddr, - offset); - s32 offs = (s32)distance; - emit->Write32((u32)offs); -#else - emit->Write32((u32)offset); -#endif - return; - } - - if (scale == 0) { - // Oh, no memory, Just a reg. - mod = 3; // 11 - } else if (scale >= 1) { - // Ah good, no scaling. - if (scale == SCALE_ATREG && !((_offsetOrBaseReg & 7) == 4 || (_offsetOrBaseReg & 7) == 5)) { - // Okay, we're good. No SIB necessary. - int ioff = (int)offset; - if (ioff == 0) { - mod = 0; - } else if (ioff < -128 || ioff > 127) { - mod = 2; // 32-bit displacement - } else { - mod = 1; // 8-bit displacement - } - } else if (scale >= SCALE_NOBASE_2 && scale <= SCALE_NOBASE_8) { - SIB = true; - mod = 0; - _offsetOrBaseReg = 5; - } else // if (scale != SCALE_ATREG) - { - if ((_offsetOrBaseReg & 7) == 4) // this would occupy the SIB encoding :( - { - // So we have to fake it with SIB encoding :( - SIB = true; - } - - if (scale >= SCALE_1 && scale < SCALE_ATREG) { - SIB = true; - } - - if (scale == SCALE_ATREG && ((_offsetOrBaseReg & 7) == 4)) { - SIB = true; - ireg = _offsetOrBaseReg; - } - - // Okay, we're fine. Just disp encoding. - // We need displacement. Which size? - int ioff = (int)(s64)offset; - if (ioff < -128 || ioff > 127) { - mod = 2; // 32-bit displacement - } else { - mod = 1; // 8-bit displacement - } - } - } - - // Okay. Time to do the actual writing - // ModRM byte: - int oreg = _offsetOrBaseReg; - if (SIB) - oreg = 4; - - // TODO(ector): WTF is this if about? I don't remember writing it :-) - // if (RIP) - // oreg = 5; - - emit->WriteModRM(mod, _operandReg & 7, oreg & 7); - - if (SIB) { - // SIB byte - int ss; - switch (scale) { - case SCALE_NONE: - _offsetOrBaseReg = 4; - ss = 0; - break; // RSP - case SCALE_1: - ss = 0; - break; - case SCALE_2: - ss = 1; - break; - case SCALE_4: - ss = 2; - break; - case SCALE_8: - ss = 3; - break; - case SCALE_NOBASE_2: - ss = 1; - break; - case SCALE_NOBASE_4: - ss = 2; - break; - case SCALE_NOBASE_8: - ss = 3; - break; - case SCALE_ATREG: - ss = 0; - break; - default: - ASSERT_MSG(0, "Invalid scale for SIB byte"); - ss = 0; - break; - } - emit->Write8((u8)((ss << 6) | ((ireg & 7) << 3) | (_offsetOrBaseReg & 7))); - } - - if (mod == 1) // 8-bit disp - { - emit->Write8((u8)(s8)(s32)offset); - } else if (mod == 2 || (scale >= SCALE_NOBASE_2 && scale <= SCALE_NOBASE_8)) // 32-bit disp - { - emit->Write32((u32)offset); - } -} - -// W = operand extended width (1 if 64-bit) -// R = register# upper bit -// X = scale amnt upper bit -// B = base register# upper bit -void XEmitter::Rex(int w, int r, int x, int b) { - w = w ? 1 : 0; - r = r ? 1 : 0; - x = x ? 1 : 0; - b = b ? 1 : 0; - u8 rx = (u8)(0x40 | (w << 3) | (r << 2) | (x << 1) | (b)); - if (rx != 0x40) - Write8(rx); -} - -void XEmitter::JMP(const u8* addr, bool force5Bytes) { - u64 fn = (u64)addr; - if (!force5Bytes) { - s64 distance = (s64)(fn - ((u64)code + 2)); - ASSERT_MSG(distance >= -0x80 && distance < 0x80, - "Jump target too far away, needs force5Bytes = true"); - // 8 bits will do - Write8(0xEB); - Write8((u8)(s8)distance); - } else { - s64 distance = (s64)(fn - ((u64)code + 5)); - - ASSERT_MSG(distance >= -0x80000000LL && distance < 0x80000000LL, - "Jump target too far away, needs indirect register"); - Write8(0xE9); - Write32((u32)(s32)distance); - } -} - -void XEmitter::JMPptr(const OpArg& arg2) { - OpArg arg = arg2; - if (arg.IsImm()) - ASSERT_MSG(0, "JMPptr - Imm argument"); - arg.operandReg = 4; - arg.WriteRex(this, 0, 0); - Write8(0xFF); - arg.WriteRest(this); -} - -// Can be used to trap other processors, before overwriting their code -// not used in dolphin -void XEmitter::JMPself() { - Write8(0xEB); - Write8(0xFE); -} - -void XEmitter::CALLptr(OpArg arg) { - if (arg.IsImm()) - ASSERT_MSG(0, "CALLptr - Imm argument"); - arg.operandReg = 2; - arg.WriteRex(this, 0, 0); - Write8(0xFF); - arg.WriteRest(this); -} - -void XEmitter::CALL(const void* fnptr) { - u64 distance = u64(fnptr) - (u64(code) + 5); - ASSERT_MSG(distance < 0x0000000080000000ULL || distance >= 0xFFFFFFFF80000000ULL, - "CALL out of range (%p calls %p)", code, fnptr); - Write8(0xE8); - Write32(u32(distance)); -} - -FixupBranch XEmitter::CALL() { - FixupBranch branch; - branch.type = 1; - branch.ptr = code + 5; - - Write8(0xE8); - Write32(0); - - return branch; -} - -FixupBranch XEmitter::J(bool force5bytes) { - FixupBranch branch; - branch.type = force5bytes ? 1 : 0; - branch.ptr = code + (force5bytes ? 5 : 2); - if (!force5bytes) { - // 8 bits will do - Write8(0xEB); - Write8(0); - } else { - Write8(0xE9); - Write32(0); - } - return branch; -} - -FixupBranch XEmitter::J_CC(CCFlags conditionCode, bool force5bytes) { - FixupBranch branch; - branch.type = force5bytes ? 1 : 0; - branch.ptr = code + (force5bytes ? 6 : 2); - if (!force5bytes) { - // 8 bits will do - Write8(0x70 + conditionCode); - Write8(0); - } else { - Write8(0x0F); - Write8(0x80 + conditionCode); - Write32(0); - } - return branch; -} - -void XEmitter::J_CC(CCFlags conditionCode, const u8* addr, bool force5bytes) { - u64 fn = (u64)addr; - s64 distance = (s64)(fn - ((u64)code + 2)); - if (distance < -0x80 || distance >= 0x80 || force5bytes) { - distance = (s64)(fn - ((u64)code + 6)); - ASSERT_MSG(distance >= -0x80000000LL && distance < 0x80000000LL, - "Jump target too far away, needs indirect register"); - Write8(0x0F); - Write8(0x80 + conditionCode); - Write32((u32)(s32)distance); - } else { - Write8(0x70 + conditionCode); - Write8((u8)(s8)distance); - } -} - -void XEmitter::SetJumpTarget(const FixupBranch& branch) { - if (branch.type == 0) { - s64 distance = (s64)(code - branch.ptr); - ASSERT_MSG(distance >= -0x80 && distance < 0x80, - "Jump target too far away, needs force5Bytes = true"); - branch.ptr[-1] = (u8)(s8)distance; - } else if (branch.type == 1) { - s64 distance = (s64)(code - branch.ptr); - ASSERT_MSG(distance >= -0x80000000LL && distance < 0x80000000LL, - "Jump target too far away, needs indirect register"); - ((s32*)branch.ptr)[-1] = (s32)distance; - } -} - -void XEmitter::SetJumpTarget(const FixupBranch& branch, const u8* target) { - if (branch.type == 0) { - s64 distance = (s64)(target - branch.ptr); - ASSERT_MSG(distance >= -0x80 && distance < 0x80, - "Jump target too far away, needs force5Bytes = true"); - branch.ptr[-1] = (u8)(s8)distance; - } else if (branch.type == 1) { - s64 distance = (s64)(target - branch.ptr); - ASSERT_MSG(distance >= -0x80000000LL && distance < 0x80000000LL, - "Jump target too far away, needs indirect register"); - ((s32*)branch.ptr)[-1] = (s32)distance; - } -} - -// Single byte opcodes -// There is no PUSHAD/POPAD in 64-bit mode. -void XEmitter::INT3() { - Write8(0xCC); -} -void XEmitter::RET() { - Write8(0xC3); -} -void XEmitter::RET_FAST() { - Write8(0xF3); - Write8(0xC3); -} // two-byte return (rep ret) - recommended by AMD optimization manual for the case of jumping to a - // ret - -// The first sign of decadence: optimized NOPs. -void XEmitter::NOP(size_t size) { - DEBUG_ASSERT((int)size > 0); - while (true) { - switch (size) { - case 0: - return; - case 1: - Write8(0x90); - return; - case 2: - Write8(0x66); - Write8(0x90); - return; - case 3: - Write8(0x0F); - Write8(0x1F); - Write8(0x00); - return; - case 4: - Write8(0x0F); - Write8(0x1F); - Write8(0x40); - Write8(0x00); - return; - case 5: - Write8(0x0F); - Write8(0x1F); - Write8(0x44); - Write8(0x00); - Write8(0x00); - return; - case 6: - Write8(0x66); - Write8(0x0F); - Write8(0x1F); - Write8(0x44); - Write8(0x00); - Write8(0x00); - return; - case 7: - Write8(0x0F); - Write8(0x1F); - Write8(0x80); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - return; - case 8: - Write8(0x0F); - Write8(0x1F); - Write8(0x84); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - return; - case 9: - Write8(0x66); - Write8(0x0F); - Write8(0x1F); - Write8(0x84); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - return; - case 10: - Write8(0x66); - Write8(0x66); - Write8(0x0F); - Write8(0x1F); - Write8(0x84); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - return; - default: - // Even though x86 instructions are allowed to be up to 15 bytes long, - // AMD advises against using NOPs longer than 11 bytes because they - // carry a performance penalty on CPUs older than AMD family 16h. - Write8(0x66); - Write8(0x66); - Write8(0x66); - Write8(0x0F); - Write8(0x1F); - Write8(0x84); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - Write8(0x00); - size -= 11; - continue; - } - } -} - -void XEmitter::PAUSE() { - Write8(0xF3); - NOP(); -} // use in tight spinloops for energy saving on some cpu -void XEmitter::CLC() { - CheckFlags(); - Write8(0xF8); -} // clear carry -void XEmitter::CMC() { - CheckFlags(); - Write8(0xF5); -} // flip carry -void XEmitter::STC() { - CheckFlags(); - Write8(0xF9); -} // set carry - -// TODO: xchg ah, al ??? -void XEmitter::XCHG_AHAL() { - Write8(0x86); - Write8(0xe0); - // alt. 86 c4 -} - -// These two can not be executed on early Intel 64-bit CPU:s, only on AMD! -void XEmitter::LAHF() { - Write8(0x9F); -} -void XEmitter::SAHF() { - CheckFlags(); - Write8(0x9E); -} - -void XEmitter::PUSHF() { - Write8(0x9C); -} -void XEmitter::POPF() { - CheckFlags(); - Write8(0x9D); -} - -void XEmitter::LFENCE() { - Write8(0x0F); - Write8(0xAE); - Write8(0xE8); -} -void XEmitter::MFENCE() { - Write8(0x0F); - Write8(0xAE); - Write8(0xF0); -} -void XEmitter::SFENCE() { - Write8(0x0F); - Write8(0xAE); - Write8(0xF8); -} - -void XEmitter::WriteSimple1Byte(int bits, u8 byte, X64Reg reg) { - if (bits == 16) - Write8(0x66); - Rex(bits == 64, 0, 0, (int)reg >> 3); - Write8(byte + ((int)reg & 7)); -} - -void XEmitter::WriteSimple2Byte(int bits, u8 byte1, u8 byte2, X64Reg reg) { - if (bits == 16) - Write8(0x66); - Rex(bits == 64, 0, 0, (int)reg >> 3); - Write8(byte1); - Write8(byte2 + ((int)reg & 7)); -} - -void XEmitter::CWD(int bits) { - if (bits == 16) - Write8(0x66); - Rex(bits == 64, 0, 0, 0); - Write8(0x99); -} - -void XEmitter::CBW(int bits) { - if (bits == 8) - Write8(0x66); - Rex(bits == 32, 0, 0, 0); - Write8(0x98); -} - -// Simple opcodes - -// push/pop do not need wide to be 64-bit -void XEmitter::PUSH(X64Reg reg) { - WriteSimple1Byte(32, 0x50, reg); -} -void XEmitter::POP(X64Reg reg) { - WriteSimple1Byte(32, 0x58, reg); -} - -void XEmitter::PUSH(int bits, const OpArg& reg) { - if (reg.IsSimpleReg()) - PUSH(reg.GetSimpleReg()); - else if (reg.IsImm()) { - switch (reg.GetImmBits()) { - case 8: - Write8(0x6A); - Write8((u8)(s8)reg.offset); - break; - case 16: - Write8(0x66); - Write8(0x68); - Write16((u16)(s16)(s32)reg.offset); - break; - case 32: - Write8(0x68); - Write32((u32)reg.offset); - break; - default: - ASSERT_MSG(0, "PUSH - Bad imm bits"); - break; - } - } else { - if (bits == 16) - Write8(0x66); - reg.WriteRex(this, bits, bits); - Write8(0xFF); - reg.WriteRest(this, 0, (X64Reg)6); - } -} - -void XEmitter::POP(int /*bits*/, const OpArg& reg) { - if (reg.IsSimpleReg()) - POP(reg.GetSimpleReg()); - else - ASSERT_MSG(0, "POP - Unsupported encoding"); -} - -void XEmitter::BSWAP(int bits, X64Reg reg) { - if (bits >= 32) { - WriteSimple2Byte(bits, 0x0F, 0xC8, reg); - } else if (bits == 16) { - ROL(16, R(reg), Imm8(8)); - } else if (bits == 8) { - // Do nothing - can't bswap a single byte... - } else { - ASSERT_MSG(0, "BSWAP - Wrong number of bits"); - } -} - -// Undefined opcode - reserved -// If we ever need a way to always cause a non-breakpoint hard exception... -void XEmitter::UD2() { - Write8(0x0F); - Write8(0x0B); -} - -void XEmitter::PREFETCH(PrefetchLevel level, OpArg arg) { - ASSERT_MSG(!arg.IsImm(), "PREFETCH - Imm argument"); - arg.operandReg = (u8)level; - arg.WriteRex(this, 0, 0); - Write8(0x0F); - Write8(0x18); - arg.WriteRest(this); -} - -void XEmitter::SETcc(CCFlags flag, OpArg dest) { - ASSERT_MSG(!dest.IsImm(), "SETcc - Imm argument"); - dest.operandReg = 0; - dest.WriteRex(this, 0, 8); - Write8(0x0F); - Write8(0x90 + (u8)flag); - dest.WriteRest(this); -} - -void XEmitter::CMOVcc(int bits, X64Reg dest, OpArg src, CCFlags flag) { - ASSERT_MSG(!src.IsImm(), "CMOVcc - Imm argument"); - ASSERT_MSG(bits != 8, "CMOVcc - 8 bits unsupported"); - if (bits == 16) - Write8(0x66); - src.operandReg = dest; - src.WriteRex(this, bits, bits); - Write8(0x0F); - Write8(0x40 + (u8)flag); - src.WriteRest(this); -} - -void XEmitter::WriteMulDivType(int bits, OpArg src, int ext) { - ASSERT_MSG(!src.IsImm(), "WriteMulDivType - Imm argument"); - CheckFlags(); - src.operandReg = ext; - if (bits == 16) - Write8(0x66); - src.WriteRex(this, bits, bits, 0); - if (bits == 8) { - Write8(0xF6); - } else { - Write8(0xF7); - } - src.WriteRest(this); -} - -void XEmitter::MUL(int bits, const OpArg& src) { - WriteMulDivType(bits, src, 4); -} -void XEmitter::DIV(int bits, const OpArg& src) { - WriteMulDivType(bits, src, 6); -} -void XEmitter::IMUL(int bits, const OpArg& src) { - WriteMulDivType(bits, src, 5); -} -void XEmitter::IDIV(int bits, const OpArg& src) { - WriteMulDivType(bits, src, 7); -} -void XEmitter::NEG(int bits, const OpArg& src) { - WriteMulDivType(bits, src, 3); -} -void XEmitter::NOT(int bits, const OpArg& src) { - WriteMulDivType(bits, src, 2); -} - -void XEmitter::WriteBitSearchType(int bits, X64Reg dest, OpArg src, u8 byte2, bool rep) { - ASSERT_MSG(!src.IsImm(), "WriteBitSearchType - Imm argument"); - CheckFlags(); - src.operandReg = (u8)dest; - if (bits == 16) - Write8(0x66); - if (rep) - Write8(0xF3); - src.WriteRex(this, bits, bits); - Write8(0x0F); - Write8(byte2); - src.WriteRest(this); -} - -void XEmitter::MOVNTI(int bits, const OpArg& dest, X64Reg src) { - if (bits <= 16) - ASSERT_MSG(0, "MOVNTI - bits<=16"); - WriteBitSearchType(bits, src, dest, 0xC3); -} - -void XEmitter::BSF(int bits, X64Reg dest, const OpArg& src) { - WriteBitSearchType(bits, dest, src, 0xBC); -} // Bottom bit to top bit -void XEmitter::BSR(int bits, X64Reg dest, const OpArg& src) { - WriteBitSearchType(bits, dest, src, 0xBD); -} // Top bit to bottom bit - -void XEmitter::TZCNT(int bits, X64Reg dest, const OpArg& src) { - CheckFlags(); - if (!Common::GetCPUCaps().bmi1) - ASSERT_MSG(0, "Trying to use BMI1 on a system that doesn't support it. Bad programmer."); - WriteBitSearchType(bits, dest, src, 0xBC, true); -} -void XEmitter::LZCNT(int bits, X64Reg dest, const OpArg& src) { - CheckFlags(); - if (!Common::GetCPUCaps().lzcnt) - ASSERT_MSG(0, "Trying to use LZCNT on a system that doesn't support it. Bad programmer."); - WriteBitSearchType(bits, dest, src, 0xBD, true); -} - -void XEmitter::MOVSX(int dbits, int sbits, X64Reg dest, OpArg src) { - ASSERT_MSG(!src.IsImm(), "MOVSX - Imm argument"); - if (dbits == sbits) { - MOV(dbits, R(dest), src); - return; - } - src.operandReg = (u8)dest; - if (dbits == 16) - Write8(0x66); - src.WriteRex(this, dbits, sbits); - if (sbits == 8) { - Write8(0x0F); - Write8(0xBE); - } else if (sbits == 16) { - Write8(0x0F); - Write8(0xBF); - } else if (sbits == 32 && dbits == 64) { - Write8(0x63); - } else { - Crash(); - } - src.WriteRest(this); -} - -void XEmitter::MOVZX(int dbits, int sbits, X64Reg dest, OpArg src) { - ASSERT_MSG(!src.IsImm(), "MOVZX - Imm argument"); - if (dbits == sbits) { - MOV(dbits, R(dest), src); - return; - } - src.operandReg = (u8)dest; - if (dbits == 16) - Write8(0x66); - // the 32bit result is automatically zero extended to 64bit - src.WriteRex(this, dbits == 64 ? 32 : dbits, sbits); - if (sbits == 8) { - Write8(0x0F); - Write8(0xB6); - } else if (sbits == 16) { - Write8(0x0F); - Write8(0xB7); - } else if (sbits == 32 && dbits == 64) { - Write8(0x8B); - } else { - ASSERT_MSG(0, "MOVZX - Invalid size"); - } - src.WriteRest(this); -} - -void XEmitter::MOVBE(int bits, const OpArg& dest, const OpArg& src) { - ASSERT_MSG(Common::GetCPUCaps().movbe, - "Generating MOVBE on a system that does not support it."); - if (bits == 8) { - MOV(bits, dest, src); - return; - } - - if (bits == 16) - Write8(0x66); - - if (dest.IsSimpleReg()) { - ASSERT_MSG(!src.IsSimpleReg() && !src.IsImm(), "MOVBE: Loading from !mem"); - src.WriteRex(this, bits, bits, dest.GetSimpleReg()); - Write8(0x0F); - Write8(0x38); - Write8(0xF0); - src.WriteRest(this, 0, dest.GetSimpleReg()); - } else if (src.IsSimpleReg()) { - ASSERT_MSG(!dest.IsSimpleReg() && !dest.IsImm(), "MOVBE: Storing to !mem"); - dest.WriteRex(this, bits, bits, src.GetSimpleReg()); - Write8(0x0F); - Write8(0x38); - Write8(0xF1); - dest.WriteRest(this, 0, src.GetSimpleReg()); - } else { - ASSERT_MSG(0, "MOVBE: Not loading or storing to mem"); - } -} - -void XEmitter::LEA(int bits, X64Reg dest, OpArg src) { - ASSERT_MSG(!src.IsImm(), "LEA - Imm argument"); - src.operandReg = (u8)dest; - if (bits == 16) - Write8(0x66); // TODO: performance warning - src.WriteRex(this, bits, bits); - Write8(0x8D); - src.WriteRest(this, 0, INVALID_REG, bits == 64); -} - -// shift can be either imm8 or cl -void XEmitter::WriteShift(int bits, OpArg dest, const OpArg& shift, int ext) { - CheckFlags(); - bool writeImm = false; - if (dest.IsImm()) { - ASSERT_MSG(0, "WriteShift - can't shift imms"); - } - if ((shift.IsSimpleReg() && shift.GetSimpleReg() != ECX) || - (shift.IsImm() && shift.GetImmBits() != 8)) { - ASSERT_MSG(0, "WriteShift - illegal argument"); - } - dest.operandReg = ext; - if (bits == 16) - Write8(0x66); - dest.WriteRex(this, bits, bits, 0); - if (shift.GetImmBits() == 8) { - // ok an imm - u8 imm = (u8)shift.offset; - if (imm == 1) { - Write8(bits == 8 ? 0xD0 : 0xD1); - } else { - writeImm = true; - Write8(bits == 8 ? 0xC0 : 0xC1); - } - } else { - Write8(bits == 8 ? 0xD2 : 0xD3); - } - dest.WriteRest(this, writeImm ? 1 : 0); - if (writeImm) - Write8((u8)shift.offset); -} - -// large rotates and shift are slower on intel than amd -// intel likes to rotate by 1, and the op is smaller too -void XEmitter::ROL(int bits, const OpArg& dest, const OpArg& shift) { - WriteShift(bits, dest, shift, 0); -} -void XEmitter::ROR(int bits, const OpArg& dest, const OpArg& shift) { - WriteShift(bits, dest, shift, 1); -} -void XEmitter::RCL(int bits, const OpArg& dest, const OpArg& shift) { - WriteShift(bits, dest, shift, 2); -} -void XEmitter::RCR(int bits, const OpArg& dest, const OpArg& shift) { - WriteShift(bits, dest, shift, 3); -} -void XEmitter::SHL(int bits, const OpArg& dest, const OpArg& shift) { - WriteShift(bits, dest, shift, 4); -} -void XEmitter::SHR(int bits, const OpArg& dest, const OpArg& shift) { - WriteShift(bits, dest, shift, 5); -} -void XEmitter::SAR(int bits, const OpArg& dest, const OpArg& shift) { - WriteShift(bits, dest, shift, 7); -} - -// index can be either imm8 or register, don't use memory destination because it's slow -void XEmitter::WriteBitTest(int bits, const OpArg& dest, const OpArg& index, int ext) { - CheckFlags(); - if (dest.IsImm()) { - ASSERT_MSG(0, "WriteBitTest - can't test imms"); - } - if ((index.IsImm() && index.GetImmBits() != 8)) { - ASSERT_MSG(0, "WriteBitTest - illegal argument"); - } - if (bits == 16) - Write8(0x66); - if (index.IsImm()) { - dest.WriteRex(this, bits, bits); - Write8(0x0F); - Write8(0xBA); - dest.WriteRest(this, 1, (X64Reg)ext); - Write8((u8)index.offset); - } else { - X64Reg operand = index.GetSimpleReg(); - dest.WriteRex(this, bits, bits, operand); - Write8(0x0F); - Write8(0x83 + 8 * ext); - dest.WriteRest(this, 1, operand); - } -} - -void XEmitter::BT(int bits, const OpArg& dest, const OpArg& index) { - WriteBitTest(bits, dest, index, 4); -} -void XEmitter::BTS(int bits, const OpArg& dest, const OpArg& index) { - WriteBitTest(bits, dest, index, 5); -} -void XEmitter::BTR(int bits, const OpArg& dest, const OpArg& index) { - WriteBitTest(bits, dest, index, 6); -} -void XEmitter::BTC(int bits, const OpArg& dest, const OpArg& index) { - WriteBitTest(bits, dest, index, 7); -} - -// shift can be either imm8 or cl -void XEmitter::SHRD(int bits, const OpArg& dest, const OpArg& src, const OpArg& shift) { - CheckFlags(); - if (dest.IsImm()) { - ASSERT_MSG(0, "SHRD - can't use imms as destination"); - } - if (!src.IsSimpleReg()) { - ASSERT_MSG(0, "SHRD - must use simple register as source"); - } - if ((shift.IsSimpleReg() && shift.GetSimpleReg() != ECX) || - (shift.IsImm() && shift.GetImmBits() != 8)) { - ASSERT_MSG(0, "SHRD - illegal shift"); - } - if (bits == 16) - Write8(0x66); - X64Reg operand = src.GetSimpleReg(); - dest.WriteRex(this, bits, bits, operand); - if (shift.GetImmBits() == 8) { - Write8(0x0F); - Write8(0xAC); - dest.WriteRest(this, 1, operand); - Write8((u8)shift.offset); - } else { - Write8(0x0F); - Write8(0xAD); - dest.WriteRest(this, 0, operand); - } -} - -void XEmitter::SHLD(int bits, const OpArg& dest, const OpArg& src, const OpArg& shift) { - CheckFlags(); - if (dest.IsImm()) { - ASSERT_MSG(0, "SHLD - can't use imms as destination"); - } - if (!src.IsSimpleReg()) { - ASSERT_MSG(0, "SHLD - must use simple register as source"); - } - if ((shift.IsSimpleReg() && shift.GetSimpleReg() != ECX) || - (shift.IsImm() && shift.GetImmBits() != 8)) { - ASSERT_MSG(0, "SHLD - illegal shift"); - } - if (bits == 16) - Write8(0x66); - X64Reg operand = src.GetSimpleReg(); - dest.WriteRex(this, bits, bits, operand); - if (shift.GetImmBits() == 8) { - Write8(0x0F); - Write8(0xA4); - dest.WriteRest(this, 1, operand); - Write8((u8)shift.offset); - } else { - Write8(0x0F); - Write8(0xA5); - dest.WriteRest(this, 0, operand); - } -} - -void OpArg::WriteSingleByteOp(XEmitter* emit, u8 op, X64Reg _operandReg, int bits) { - if (bits == 16) - emit->Write8(0x66); - - this->operandReg = (u8)_operandReg; - WriteRex(emit, bits, bits); - emit->Write8(op); - WriteRest(emit); -} - -// operand can either be immediate or register -void OpArg::WriteNormalOp(XEmitter* emit, bool toRM, NormalOp op, const OpArg& operand, - int bits) const { - X64Reg _operandReg; - if (IsImm()) { - ASSERT_MSG(0, "WriteNormalOp - Imm argument, wrong order"); - } - - if (bits == 16) - emit->Write8(0x66); - - int immToWrite = 0; - - if (operand.IsImm()) { - WriteRex(emit, bits, bits); - - if (!toRM) { - ASSERT_MSG(0, "WriteNormalOp - Writing to Imm (!toRM)"); - } - - if (operand.scale == SCALE_IMM8 && bits == 8) { - // op al, imm8 - if (!scale && offsetOrBaseReg == AL && normalops[op].eaximm8 != 0xCC) { - emit->Write8(normalops[op].eaximm8); - emit->Write8((u8)operand.offset); - return; - } - // mov reg, imm8 - if (!scale && op == nrmMOV) { - emit->Write8(0xB0 + (offsetOrBaseReg & 7)); - emit->Write8((u8)operand.offset); - return; - } - // op r/m8, imm8 - emit->Write8(normalops[op].imm8); - immToWrite = 8; - } else if ((operand.scale == SCALE_IMM16 && bits == 16) || - (operand.scale == SCALE_IMM32 && bits == 32) || - (operand.scale == SCALE_IMM32 && bits == 64)) { - // Try to save immediate size if we can, but first check to see - // if the instruction supports simm8. - // op r/m, imm8 - if (normalops[op].simm8 != 0xCC && - ((operand.scale == SCALE_IMM16 && (s16)operand.offset == (s8)operand.offset) || - (operand.scale == SCALE_IMM32 && (s32)operand.offset == (s8)operand.offset))) { - emit->Write8(normalops[op].simm8); - immToWrite = 8; - } else { - // mov reg, imm - if (!scale && op == nrmMOV && bits != 64) { - emit->Write8(0xB8 + (offsetOrBaseReg & 7)); - if (bits == 16) - emit->Write16((u16)operand.offset); - else - emit->Write32((u32)operand.offset); - return; - } - // op eax, imm - if (!scale && offsetOrBaseReg == EAX && normalops[op].eaximm32 != 0xCC) { - emit->Write8(normalops[op].eaximm32); - if (bits == 16) - emit->Write16((u16)operand.offset); - else - emit->Write32((u32)operand.offset); - return; - } - // op r/m, imm - emit->Write8(normalops[op].imm32); - immToWrite = bits == 16 ? 16 : 32; - } - } else if ((operand.scale == SCALE_IMM8 && bits == 16) || - (operand.scale == SCALE_IMM8 && bits == 32) || - (operand.scale == SCALE_IMM8 && bits == 64)) { - // op r/m, imm8 - emit->Write8(normalops[op].simm8); - immToWrite = 8; - } else if (operand.scale == SCALE_IMM64 && bits == 64) { - if (scale) { - ASSERT_MSG(0, "WriteNormalOp - MOV with 64-bit imm requres register destination"); - } - // mov reg64, imm64 - else if (op == nrmMOV) { - emit->Write8(0xB8 + (offsetOrBaseReg & 7)); - emit->Write64((u64)operand.offset); - return; - } - ASSERT_MSG(0, "WriteNormalOp - Only MOV can take 64-bit imm"); - } else { - ASSERT_MSG(0, "WriteNormalOp - Unhandled case"); - } - _operandReg = (X64Reg)normalops[op].ext; // pass extension in REG of ModRM - } else { - _operandReg = (X64Reg)operand.offsetOrBaseReg; - WriteRex(emit, bits, bits, _operandReg); - // op r/m, reg - if (toRM) { - emit->Write8(bits == 8 ? normalops[op].toRm8 : normalops[op].toRm32); - } - // op reg, r/m - else { - emit->Write8(bits == 8 ? normalops[op].fromRm8 : normalops[op].fromRm32); - } - } - WriteRest(emit, immToWrite >> 3, _operandReg); - switch (immToWrite) { - case 0: - break; - case 8: - emit->Write8((u8)operand.offset); - break; - case 16: - emit->Write16((u16)operand.offset); - break; - case 32: - emit->Write32((u32)operand.offset); - break; - default: - ASSERT_MSG(0, "WriteNormalOp - Unhandled case"); - } -} - -void XEmitter::WriteNormalOp(XEmitter* emit, int bits, NormalOp op, const OpArg& a1, - const OpArg& a2) { - if (a1.IsImm()) { - // Booh! Can't write to an imm - ASSERT_MSG(0, "WriteNormalOp - a1 cannot be imm"); - return; - } - if (a2.IsImm()) { - a1.WriteNormalOp(emit, true, op, a2, bits); - } else { - if (a1.IsSimpleReg()) { - a2.WriteNormalOp(emit, false, op, a1, bits); - } else { - ASSERT_MSG(a2.IsSimpleReg() || a2.IsImm(), - "WriteNormalOp - a1 and a2 cannot both be memory"); - a1.WriteNormalOp(emit, true, op, a2, bits); - } - } -} - -void XEmitter::ADD(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmADD, a1, a2); -} -void XEmitter::ADC(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmADC, a1, a2); -} -void XEmitter::SUB(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmSUB, a1, a2); -} -void XEmitter::SBB(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmSBB, a1, a2); -} -void XEmitter::AND(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmAND, a1, a2); -} -void XEmitter::OR(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmOR, a1, a2); -} -void XEmitter::XOR(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmXOR, a1, a2); -} -void XEmitter::MOV(int bits, const OpArg& a1, const OpArg& a2) { - if (a1.IsSimpleReg() && a2.IsSimpleReg() && a1.GetSimpleReg() == a2.GetSimpleReg()) - LOG_ERROR(Common, "Redundant MOV @ %p - bug in JIT?", code); - WriteNormalOp(this, bits, nrmMOV, a1, a2); -} -void XEmitter::TEST(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmTEST, a1, a2); -} -void XEmitter::CMP(int bits, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - WriteNormalOp(this, bits, nrmCMP, a1, a2); -} -void XEmitter::XCHG(int bits, const OpArg& a1, const OpArg& a2) { - WriteNormalOp(this, bits, nrmXCHG, a1, a2); -} - -void XEmitter::IMUL(int bits, X64Reg regOp, const OpArg& a1, const OpArg& a2) { - CheckFlags(); - if (bits == 8) { - ASSERT_MSG(0, "IMUL - illegal bit size!"); - return; - } - - if (a1.IsImm()) { - ASSERT_MSG(0, "IMUL - second arg cannot be imm!"); - return; - } - - if (!a2.IsImm()) { - ASSERT_MSG(0, "IMUL - third arg must be imm!"); - return; - } - - if (bits == 16) - Write8(0x66); - a1.WriteRex(this, bits, bits, regOp); - - if (a2.GetImmBits() == 8 || (a2.GetImmBits() == 16 && (s8)a2.offset == (s16)a2.offset) || - (a2.GetImmBits() == 32 && (s8)a2.offset == (s32)a2.offset)) { - Write8(0x6B); - a1.WriteRest(this, 1, regOp); - Write8((u8)a2.offset); - } else { - Write8(0x69); - if (a2.GetImmBits() == 16 && bits == 16) { - a1.WriteRest(this, 2, regOp); - Write16((u16)a2.offset); - } else if (a2.GetImmBits() == 32 && (bits == 32 || bits == 64)) { - a1.WriteRest(this, 4, regOp); - Write32((u32)a2.offset); - } else { - ASSERT_MSG(0, "IMUL - unhandled case!"); - } - } -} - -void XEmitter::IMUL(int bits, X64Reg regOp, const OpArg& a) { - CheckFlags(); - if (bits == 8) { - ASSERT_MSG(0, "IMUL - illegal bit size!"); - return; - } - - if (a.IsImm()) { - IMUL(bits, regOp, R(regOp), a); - return; - } - - if (bits == 16) - Write8(0x66); - a.WriteRex(this, bits, bits, regOp); - Write8(0x0F); - Write8(0xAF); - a.WriteRest(this, 0, regOp); -} - -void XEmitter::WriteSSEOp(u8 opPrefix, u16 op, X64Reg regOp, OpArg arg, int extrabytes) { - if (opPrefix) - Write8(opPrefix); - arg.operandReg = regOp; - arg.WriteRex(this, 0, 0); - Write8(0x0F); - if (op > 0xFF) - Write8((op >> 8) & 0xFF); - Write8(op & 0xFF); - arg.WriteRest(this, extrabytes); -} - -void XEmitter::WriteAVXOp(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes) { - WriteAVXOp(opPrefix, op, regOp, INVALID_REG, arg, extrabytes); -} - -static int GetVEXmmmmm(u16 op) { - // Currently, only 0x38 and 0x3A are used as secondary escape byte. - if ((op >> 8) == 0x3A) - return 3; - if ((op >> 8) == 0x38) - return 2; - - return 1; -} - -static int GetVEXpp(u8 opPrefix) { - if (opPrefix == 0x66) - return 1; - if (opPrefix == 0xF3) - return 2; - if (opPrefix == 0xF2) - return 3; - - return 0; -} - -void XEmitter::WriteAVXOp(u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg, - int extrabytes) { - if (!Common::GetCPUCaps().avx) - ASSERT_MSG(0, "Trying to use AVX on a system that doesn't support it. Bad programmer."); - int mmmmm = GetVEXmmmmm(op); - int pp = GetVEXpp(opPrefix); - // FIXME: we currently don't support 256-bit instructions, and "size" is not the vector size - // here - arg.WriteVex(this, regOp1, regOp2, 0, pp, mmmmm); - Write8(op & 0xFF); - arg.WriteRest(this, extrabytes, regOp1); -} - -// Like the above, but more general; covers GPR-based VEX operations, like BMI1/2 -void XEmitter::WriteVEXOp(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, - const OpArg& arg, int extrabytes) { - if (size != 32 && size != 64) - ASSERT_MSG(0, "VEX GPR instructions only support 32-bit and 64-bit modes!"); - int mmmmm = GetVEXmmmmm(op); - int pp = GetVEXpp(opPrefix); - arg.WriteVex(this, regOp1, regOp2, 0, pp, mmmmm, size == 64); - Write8(op & 0xFF); - arg.WriteRest(this, extrabytes, regOp1); -} - -void XEmitter::WriteBMI1Op(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, - const OpArg& arg, int extrabytes) { - CheckFlags(); - if (!Common::GetCPUCaps().bmi1) - ASSERT_MSG(0, "Trying to use BMI1 on a system that doesn't support it. Bad programmer."); - WriteVEXOp(size, opPrefix, op, regOp1, regOp2, arg, extrabytes); -} - -void XEmitter::WriteBMI2Op(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, - const OpArg& arg, int extrabytes) { - CheckFlags(); - if (!Common::GetCPUCaps().bmi2) - ASSERT_MSG(0, "Trying to use BMI2 on a system that doesn't support it. Bad programmer."); - WriteVEXOp(size, opPrefix, op, regOp1, regOp2, arg, extrabytes); -} - -void XEmitter::MOVD_xmm(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x6E, dest, arg, 0); -} -void XEmitter::MOVD_xmm(const OpArg& arg, X64Reg src) { - WriteSSEOp(0x66, 0x7E, src, arg, 0); -} - -void XEmitter::MOVQ_xmm(X64Reg dest, OpArg arg) { -#ifdef ARCHITECTURE_x86_64 - // Alternate encoding - // This does not display correctly in MSVC's debugger, it thinks it's a MOVD - arg.operandReg = dest; - Write8(0x66); - arg.WriteRex(this, 64, 0); - Write8(0x0f); - Write8(0x6E); - arg.WriteRest(this, 0); -#else - arg.operandReg = dest; - Write8(0xF3); - Write8(0x0f); - Write8(0x7E); - arg.WriteRest(this, 0); -#endif -} - -void XEmitter::MOVQ_xmm(OpArg arg, X64Reg src) { - if (src > 7 || arg.IsSimpleReg()) { - // Alternate encoding - // This does not display correctly in MSVC's debugger, it thinks it's a MOVD - arg.operandReg = src; - Write8(0x66); - arg.WriteRex(this, 64, 0); - Write8(0x0f); - Write8(0x7E); - arg.WriteRest(this, 0); - } else { - arg.operandReg = src; - arg.WriteRex(this, 0, 0); - Write8(0x66); - Write8(0x0f); - Write8(0xD6); - arg.WriteRest(this, 0); - } -} - -void XEmitter::WriteMXCSR(OpArg arg, int ext) { - if (arg.IsImm() || arg.IsSimpleReg()) - ASSERT_MSG(0, "MXCSR - invalid operand"); - - arg.operandReg = ext; - arg.WriteRex(this, 0, 0); - Write8(0x0F); - Write8(0xAE); - arg.WriteRest(this); -} - -void XEmitter::STMXCSR(const OpArg& memloc) { - WriteMXCSR(memloc, 3); -} -void XEmitter::LDMXCSR(const OpArg& memloc) { - WriteMXCSR(memloc, 2); -} - -void XEmitter::MOVNTDQ(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x66, sseMOVNTDQ, regOp, arg); -} -void XEmitter::MOVNTPS(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x00, sseMOVNTP, regOp, arg); -} -void XEmitter::MOVNTPD(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x66, sseMOVNTP, regOp, arg); -} - -void XEmitter::ADDSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseADD, regOp, arg); -} -void XEmitter::ADDSD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseADD, regOp, arg); -} -void XEmitter::SUBSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseSUB, regOp, arg); -} -void XEmitter::SUBSD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseSUB, regOp, arg); -} -void XEmitter::CMPSS(X64Reg regOp, const OpArg& arg, u8 compare) { - WriteSSEOp(0xF3, sseCMP, regOp, arg, 1); - Write8(compare); -} -void XEmitter::CMPSD(X64Reg regOp, const OpArg& arg, u8 compare) { - WriteSSEOp(0xF2, sseCMP, regOp, arg, 1); - Write8(compare); -} -void XEmitter::MULSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseMUL, regOp, arg); -} -void XEmitter::MULSD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseMUL, regOp, arg); -} -void XEmitter::DIVSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseDIV, regOp, arg); -} -void XEmitter::DIVSD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseDIV, regOp, arg); -} -void XEmitter::MINSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseMIN, regOp, arg); -} -void XEmitter::MINSD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseMIN, regOp, arg); -} -void XEmitter::MAXSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseMAX, regOp, arg); -} -void XEmitter::MAXSD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseMAX, regOp, arg); -} -void XEmitter::SQRTSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseSQRT, regOp, arg); -} -void XEmitter::SQRTSD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseSQRT, regOp, arg); -} -void XEmitter::RCPSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseRCP, regOp, arg); -} -void XEmitter::RSQRTSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseRSQRT, regOp, arg); -} - -void XEmitter::ADDPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseADD, regOp, arg); -} -void XEmitter::ADDPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseADD, regOp, arg); -} -void XEmitter::SUBPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseSUB, regOp, arg); -} -void XEmitter::SUBPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseSUB, regOp, arg); -} -void XEmitter::CMPPS(X64Reg regOp, const OpArg& arg, u8 compare) { - WriteSSEOp(0x00, sseCMP, regOp, arg, 1); - Write8(compare); -} -void XEmitter::CMPPD(X64Reg regOp, const OpArg& arg, u8 compare) { - WriteSSEOp(0x66, sseCMP, regOp, arg, 1); - Write8(compare); -} -void XEmitter::ANDPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseAND, regOp, arg); -} -void XEmitter::ANDPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseAND, regOp, arg); -} -void XEmitter::ANDNPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseANDN, regOp, arg); -} -void XEmitter::ANDNPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseANDN, regOp, arg); -} -void XEmitter::ORPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseOR, regOp, arg); -} -void XEmitter::ORPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseOR, regOp, arg); -} -void XEmitter::XORPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseXOR, regOp, arg); -} -void XEmitter::XORPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseXOR, regOp, arg); -} -void XEmitter::MULPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseMUL, regOp, arg); -} -void XEmitter::MULPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseMUL, regOp, arg); -} -void XEmitter::DIVPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseDIV, regOp, arg); -} -void XEmitter::DIVPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseDIV, regOp, arg); -} -void XEmitter::MINPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseMIN, regOp, arg); -} -void XEmitter::MINPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseMIN, regOp, arg); -} -void XEmitter::MAXPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseMAX, regOp, arg); -} -void XEmitter::MAXPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseMAX, regOp, arg); -} -void XEmitter::SQRTPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseSQRT, regOp, arg); -} -void XEmitter::SQRTPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseSQRT, regOp, arg); -} -void XEmitter::RCPPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseRCP, regOp, arg); -} -void XEmitter::RSQRTPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseRSQRT, regOp, arg); -} -void XEmitter::SHUFPS(X64Reg regOp, const OpArg& arg, u8 shuffle) { - WriteSSEOp(0x00, sseSHUF, regOp, arg, 1); - Write8(shuffle); -} -void XEmitter::SHUFPD(X64Reg regOp, const OpArg& arg, u8 shuffle) { - WriteSSEOp(0x66, sseSHUF, regOp, arg, 1); - Write8(shuffle); -} - -void XEmitter::HADDPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseHADD, regOp, arg); -} - -void XEmitter::COMISS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseCOMIS, regOp, arg); -} // weird that these should be packed -void XEmitter::COMISD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseCOMIS, regOp, arg); -} // ordered -void XEmitter::UCOMISS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseUCOMIS, regOp, arg); -} // unordered -void XEmitter::UCOMISD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseUCOMIS, regOp, arg); -} - -void XEmitter::MOVAPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseMOVAPfromRM, regOp, arg); -} -void XEmitter::MOVAPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseMOVAPfromRM, regOp, arg); -} -void XEmitter::MOVAPS(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x00, sseMOVAPtoRM, regOp, arg); -} -void XEmitter::MOVAPD(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x66, sseMOVAPtoRM, regOp, arg); -} - -void XEmitter::MOVUPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseMOVUPfromRM, regOp, arg); -} -void XEmitter::MOVUPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseMOVUPfromRM, regOp, arg); -} -void XEmitter::MOVUPS(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x00, sseMOVUPtoRM, regOp, arg); -} -void XEmitter::MOVUPD(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x66, sseMOVUPtoRM, regOp, arg); -} - -void XEmitter::MOVDQA(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseMOVDQfromRM, regOp, arg); -} -void XEmitter::MOVDQA(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x66, sseMOVDQtoRM, regOp, arg); -} -void XEmitter::MOVDQU(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseMOVDQfromRM, regOp, arg); -} -void XEmitter::MOVDQU(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0xF3, sseMOVDQtoRM, regOp, arg); -} - -void XEmitter::MOVSS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, sseMOVUPfromRM, regOp, arg); -} -void XEmitter::MOVSD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, sseMOVUPfromRM, regOp, arg); -} -void XEmitter::MOVSS(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0xF3, sseMOVUPtoRM, regOp, arg); -} -void XEmitter::MOVSD(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0xF2, sseMOVUPtoRM, regOp, arg); -} - -void XEmitter::MOVLPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseMOVLPfromRM, regOp, arg); -} -void XEmitter::MOVLPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseMOVLPfromRM, regOp, arg); -} -void XEmitter::MOVLPS(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x00, sseMOVLPtoRM, regOp, arg); -} -void XEmitter::MOVLPD(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x66, sseMOVLPtoRM, regOp, arg); -} - -void XEmitter::MOVHPS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, sseMOVHPfromRM, regOp, arg); -} -void XEmitter::MOVHPD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, sseMOVHPfromRM, regOp, arg); -} -void XEmitter::MOVHPS(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x00, sseMOVHPtoRM, regOp, arg); -} -void XEmitter::MOVHPD(const OpArg& arg, X64Reg regOp) { - WriteSSEOp(0x66, sseMOVHPtoRM, regOp, arg); -} - -void XEmitter::MOVHLPS(X64Reg regOp1, X64Reg regOp2) { - WriteSSEOp(0x00, sseMOVHLPS, regOp1, R(regOp2)); -} -void XEmitter::MOVLHPS(X64Reg regOp1, X64Reg regOp2) { - WriteSSEOp(0x00, sseMOVLHPS, regOp1, R(regOp2)); -} - -void XEmitter::CVTPS2PD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, 0x5A, regOp, arg); -} -void XEmitter::CVTPD2PS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, 0x5A, regOp, arg); -} - -void XEmitter::CVTSD2SS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, 0x5A, regOp, arg); -} -void XEmitter::CVTSS2SD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, 0x5A, regOp, arg); -} -void XEmitter::CVTSD2SI(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, 0x2D, regOp, arg); -} -void XEmitter::CVTSS2SI(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, 0x2D, regOp, arg); -} -void XEmitter::CVTSI2SD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, 0x2A, regOp, arg); -} -void XEmitter::CVTSI2SS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, 0x2A, regOp, arg); -} - -void XEmitter::CVTDQ2PD(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, 0xE6, regOp, arg); -} -void XEmitter::CVTDQ2PS(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x00, 0x5B, regOp, arg); -} -void XEmitter::CVTPD2DQ(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, 0xE6, regOp, arg); -} -void XEmitter::CVTPS2DQ(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, 0x5B, regOp, arg); -} - -void XEmitter::CVTTSD2SI(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF2, 0x2C, regOp, arg); -} -void XEmitter::CVTTSS2SI(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, 0x2C, regOp, arg); -} -void XEmitter::CVTTPS2DQ(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0xF3, 0x5B, regOp, arg); -} -void XEmitter::CVTTPD2DQ(X64Reg regOp, const OpArg& arg) { - WriteSSEOp(0x66, 0xE6, regOp, arg); -} - -void XEmitter::MASKMOVDQU(X64Reg dest, X64Reg src) { - WriteSSEOp(0x66, sseMASKMOVDQU, dest, R(src)); -} - -void XEmitter::MOVMSKPS(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x00, 0x50, dest, arg); -} -void XEmitter::MOVMSKPD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x50, dest, arg); -} - -void XEmitter::LDDQU(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0xF2, sseLDDQU, dest, arg); -} // For integer data only - -// THESE TWO ARE UNTESTED. -void XEmitter::UNPCKLPS(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x00, 0x14, dest, arg); -} -void XEmitter::UNPCKHPS(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x00, 0x15, dest, arg); -} - -void XEmitter::UNPCKLPD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x14, dest, arg); -} -void XEmitter::UNPCKHPD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x15, dest, arg); -} - -void XEmitter::MOVDDUP(X64Reg regOp, const OpArg& arg) { - if (Common::GetCPUCaps().sse3) { - WriteSSEOp(0xF2, 0x12, regOp, arg); // SSE3 movddup - } else { - // Simulate this instruction with SSE2 instructions - if (!arg.IsSimpleReg(regOp)) - MOVSD(regOp, arg); - UNPCKLPD(regOp, R(regOp)); - } -} - -// There are a few more left - -// Also some integer instructions are missing -void XEmitter::PACKSSDW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x6B, dest, arg); -} -void XEmitter::PACKSSWB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x63, dest, arg); -} -void XEmitter::PACKUSWB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x67, dest, arg); -} - -void XEmitter::PUNPCKLBW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x60, dest, arg); -} -void XEmitter::PUNPCKLWD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x61, dest, arg); -} -void XEmitter::PUNPCKLDQ(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x62, dest, arg); -} -void XEmitter::PUNPCKLQDQ(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x6C, dest, arg); -} - -void XEmitter::PSRLW(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x71, (X64Reg)2, R(reg)); - Write8(shift); -} - -void XEmitter::PSRLD(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x72, (X64Reg)2, R(reg)); - Write8(shift); -} - -void XEmitter::PSRLQ(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x73, (X64Reg)2, R(reg)); - Write8(shift); -} - -void XEmitter::PSRLQ(X64Reg reg, const OpArg& arg) { - WriteSSEOp(0x66, 0xd3, reg, arg); -} - -void XEmitter::PSRLDQ(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x73, (X64Reg)3, R(reg)); - Write8(shift); -} - -void XEmitter::PSLLW(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x71, (X64Reg)6, R(reg)); - Write8(shift); -} - -void XEmitter::PSLLD(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x72, (X64Reg)6, R(reg)); - Write8(shift); -} - -void XEmitter::PSLLQ(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x73, (X64Reg)6, R(reg)); - Write8(shift); -} - -void XEmitter::PSLLDQ(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x73, (X64Reg)7, R(reg)); - Write8(shift); -} - -void XEmitter::PSRAW(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x71, (X64Reg)4, R(reg)); - Write8(shift); -} - -void XEmitter::PSRAD(X64Reg reg, int shift) { - WriteSSEOp(0x66, 0x72, (X64Reg)4, R(reg)); - Write8(shift); -} - -void XEmitter::WriteSSSE3Op(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes) { - if (!Common::GetCPUCaps().ssse3) - ASSERT_MSG(0, "Trying to use SSSE3 on a system that doesn't support it. Bad programmer."); - WriteSSEOp(opPrefix, op, regOp, arg, extrabytes); -} - -void XEmitter::WriteSSE41Op(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes) { - if (!Common::GetCPUCaps().sse4_1) - ASSERT_MSG(0, "Trying to use SSE4.1 on a system that doesn't support it. Bad programmer."); - WriteSSEOp(opPrefix, op, regOp, arg, extrabytes); -} - -void XEmitter::PSHUFB(X64Reg dest, const OpArg& arg) { - WriteSSSE3Op(0x66, 0x3800, dest, arg); -} -void XEmitter::PTEST(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3817, dest, arg); -} -void XEmitter::PACKUSDW(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x382b, dest, arg); -} -void XEmitter::DPPS(X64Reg dest, const OpArg& arg, u8 mask) { - WriteSSE41Op(0x66, 0x3A40, dest, arg, 1); - Write8(mask); -} - -void XEmitter::PMINSB(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3838, dest, arg); -} -void XEmitter::PMINSD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3839, dest, arg); -} -void XEmitter::PMINUW(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x383a, dest, arg); -} -void XEmitter::PMINUD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x383b, dest, arg); -} -void XEmitter::PMAXSB(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x383c, dest, arg); -} -void XEmitter::PMAXSD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x383d, dest, arg); -} -void XEmitter::PMAXUW(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x383e, dest, arg); -} -void XEmitter::PMAXUD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x383f, dest, arg); -} - -void XEmitter::PMOVSXBW(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3820, dest, arg); -} -void XEmitter::PMOVSXBD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3821, dest, arg); -} -void XEmitter::PMOVSXBQ(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3822, dest, arg); -} -void XEmitter::PMOVSXWD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3823, dest, arg); -} -void XEmitter::PMOVSXWQ(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3824, dest, arg); -} -void XEmitter::PMOVSXDQ(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3825, dest, arg); -} -void XEmitter::PMOVZXBW(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3830, dest, arg); -} -void XEmitter::PMOVZXBD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3831, dest, arg); -} -void XEmitter::PMOVZXBQ(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3832, dest, arg); -} -void XEmitter::PMOVZXWD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3833, dest, arg); -} -void XEmitter::PMOVZXWQ(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3834, dest, arg); -} -void XEmitter::PMOVZXDQ(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3835, dest, arg); -} - -void XEmitter::PBLENDVB(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3810, dest, arg); -} -void XEmitter::BLENDVPS(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3814, dest, arg); -} -void XEmitter::BLENDVPD(X64Reg dest, const OpArg& arg) { - WriteSSE41Op(0x66, 0x3815, dest, arg); -} -void XEmitter::BLENDPS(X64Reg dest, const OpArg& arg, u8 blend) { - WriteSSE41Op(0x66, 0x3A0C, dest, arg, 1); - Write8(blend); -} -void XEmitter::BLENDPD(X64Reg dest, const OpArg& arg, u8 blend) { - WriteSSE41Op(0x66, 0x3A0D, dest, arg, 1); - Write8(blend); -} - -void XEmitter::ROUNDSS(X64Reg dest, const OpArg& arg, u8 mode) { - WriteSSE41Op(0x66, 0x3A0A, dest, arg, 1); - Write8(mode); -} -void XEmitter::ROUNDSD(X64Reg dest, const OpArg& arg, u8 mode) { - WriteSSE41Op(0x66, 0x3A0B, dest, arg, 1); - Write8(mode); -} -void XEmitter::ROUNDPS(X64Reg dest, const OpArg& arg, u8 mode) { - WriteSSE41Op(0x66, 0x3A08, dest, arg, 1); - Write8(mode); -} -void XEmitter::ROUNDPD(X64Reg dest, const OpArg& arg, u8 mode) { - WriteSSE41Op(0x66, 0x3A09, dest, arg, 1); - Write8(mode); -} - -void XEmitter::PAND(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xDB, dest, arg); -} -void XEmitter::PANDN(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xDF, dest, arg); -} -void XEmitter::PXOR(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xEF, dest, arg); -} -void XEmitter::POR(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xEB, dest, arg); -} - -void XEmitter::PADDB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xFC, dest, arg); -} -void XEmitter::PADDW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xFD, dest, arg); -} -void XEmitter::PADDD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xFE, dest, arg); -} -void XEmitter::PADDQ(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xD4, dest, arg); -} - -void XEmitter::PADDSB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xEC, dest, arg); -} -void XEmitter::PADDSW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xED, dest, arg); -} -void XEmitter::PADDUSB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xDC, dest, arg); -} -void XEmitter::PADDUSW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xDD, dest, arg); -} - -void XEmitter::PSUBB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xF8, dest, arg); -} -void XEmitter::PSUBW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xF9, dest, arg); -} -void XEmitter::PSUBD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xFA, dest, arg); -} -void XEmitter::PSUBQ(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xFB, dest, arg); -} - -void XEmitter::PSUBSB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xE8, dest, arg); -} -void XEmitter::PSUBSW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xE9, dest, arg); -} -void XEmitter::PSUBUSB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xD8, dest, arg); -} -void XEmitter::PSUBUSW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xD9, dest, arg); -} - -void XEmitter::PAVGB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xE0, dest, arg); -} -void XEmitter::PAVGW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xE3, dest, arg); -} - -void XEmitter::PCMPEQB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x74, dest, arg); -} -void XEmitter::PCMPEQW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x75, dest, arg); -} -void XEmitter::PCMPEQD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x76, dest, arg); -} - -void XEmitter::PCMPGTB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x64, dest, arg); -} -void XEmitter::PCMPGTW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x65, dest, arg); -} -void XEmitter::PCMPGTD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0x66, dest, arg); -} - -void XEmitter::PEXTRW(X64Reg dest, const OpArg& arg, u8 subreg) { - WriteSSEOp(0x66, 0xC5, dest, arg, 1); - Write8(subreg); -} -void XEmitter::PINSRW(X64Reg dest, const OpArg& arg, u8 subreg) { - WriteSSEOp(0x66, 0xC4, dest, arg, 1); - Write8(subreg); -} - -void XEmitter::PMADDWD(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xF5, dest, arg); -} -void XEmitter::PSADBW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xF6, dest, arg); -} - -void XEmitter::PMAXSW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xEE, dest, arg); -} -void XEmitter::PMAXUB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xDE, dest, arg); -} -void XEmitter::PMINSW(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xEA, dest, arg); -} -void XEmitter::PMINUB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xDA, dest, arg); -} - -void XEmitter::PMOVMSKB(X64Reg dest, const OpArg& arg) { - WriteSSEOp(0x66, 0xD7, dest, arg); -} -void XEmitter::PSHUFD(X64Reg regOp, const OpArg& arg, u8 shuffle) { - WriteSSEOp(0x66, 0x70, regOp, arg, 1); - Write8(shuffle); -} -void XEmitter::PSHUFLW(X64Reg regOp, const OpArg& arg, u8 shuffle) { - WriteSSEOp(0xF2, 0x70, regOp, arg, 1); - Write8(shuffle); -} -void XEmitter::PSHUFHW(X64Reg regOp, const OpArg& arg, u8 shuffle) { - WriteSSEOp(0xF3, 0x70, regOp, arg, 1); - Write8(shuffle); -} - -// VEX -void XEmitter::VADDSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0xF2, sseADD, regOp1, regOp2, arg); -} -void XEmitter::VSUBSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0xF2, sseSUB, regOp1, regOp2, arg); -} -void XEmitter::VMULSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0xF2, sseMUL, regOp1, regOp2, arg); -} -void XEmitter::VDIVSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0xF2, sseDIV, regOp1, regOp2, arg); -} -void XEmitter::VADDPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, sseADD, regOp1, regOp2, arg); -} -void XEmitter::VSUBPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, sseSUB, regOp1, regOp2, arg); -} -void XEmitter::VMULPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, sseMUL, regOp1, regOp2, arg); -} -void XEmitter::VDIVPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, sseDIV, regOp1, regOp2, arg); -} -void XEmitter::VSQRTSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0xF2, sseSQRT, regOp1, regOp2, arg); -} -void XEmitter::VSHUFPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg, u8 shuffle) { - WriteAVXOp(0x66, sseSHUF, regOp1, regOp2, arg, 1); - Write8(shuffle); -} -void XEmitter::VUNPCKLPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x14, regOp1, regOp2, arg); -} -void XEmitter::VUNPCKHPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x15, regOp1, regOp2, arg); -} - -void XEmitter::VANDPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x00, sseAND, regOp1, regOp2, arg); -} -void XEmitter::VANDPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, sseAND, regOp1, regOp2, arg); -} -void XEmitter::VANDNPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x00, sseANDN, regOp1, regOp2, arg); -} -void XEmitter::VANDNPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, sseANDN, regOp1, regOp2, arg); -} -void XEmitter::VORPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x00, sseOR, regOp1, regOp2, arg); -} -void XEmitter::VORPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, sseOR, regOp1, regOp2, arg); -} -void XEmitter::VXORPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x00, sseXOR, regOp1, regOp2, arg); -} -void XEmitter::VXORPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, sseXOR, regOp1, regOp2, arg); -} - -void XEmitter::VPAND(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0xDB, regOp1, regOp2, arg); -} -void XEmitter::VPANDN(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0xDF, regOp1, regOp2, arg); -} -void XEmitter::VPOR(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0xEB, regOp1, regOp2, arg); -} -void XEmitter::VPXOR(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0xEF, regOp1, regOp2, arg); -} - -void XEmitter::VFMADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x3898, regOp1, regOp2, arg); -} -void XEmitter::VFMADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38A8, regOp1, regOp2, arg); -} -void XEmitter::VFMADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38B8, regOp1, regOp2, arg); -} -void XEmitter::VFMADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x3898, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38A8, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38B8, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMADD132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x3899, regOp1, regOp2, arg); -} -void XEmitter::VFMADD213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38A9, regOp1, regOp2, arg); -} -void XEmitter::VFMADD231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38B9, regOp1, regOp2, arg); -} -void XEmitter::VFMADD132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x3899, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMADD213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38A9, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMADD231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38B9, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389A, regOp1, regOp2, arg); -} -void XEmitter::VFMSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AA, regOp1, regOp2, arg); -} -void XEmitter::VFMSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BA, regOp1, regOp2, arg); -} -void XEmitter::VFMSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389A, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AA, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BA, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUB132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389B, regOp1, regOp2, arg); -} -void XEmitter::VFMSUB213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AB, regOp1, regOp2, arg); -} -void XEmitter::VFMSUB231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BB, regOp1, regOp2, arg); -} -void XEmitter::VFMSUB132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389B, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUB213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AB, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUB231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BB, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389C, regOp1, regOp2, arg); -} -void XEmitter::VFNMADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AC, regOp1, regOp2, arg); -} -void XEmitter::VFNMADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BC, regOp1, regOp2, arg); -} -void XEmitter::VFNMADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389C, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AC, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BC, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMADD132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389D, regOp1, regOp2, arg); -} -void XEmitter::VFNMADD213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AD, regOp1, regOp2, arg); -} -void XEmitter::VFNMADD231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BD, regOp1, regOp2, arg); -} -void XEmitter::VFNMADD132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389D, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMADD213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AD, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMADD231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BD, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389E, regOp1, regOp2, arg); -} -void XEmitter::VFNMSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AE, regOp1, regOp2, arg); -} -void XEmitter::VFNMSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BE, regOp1, regOp2, arg); -} -void XEmitter::VFNMSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389E, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AE, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BE, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMSUB132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389F, regOp1, regOp2, arg); -} -void XEmitter::VFNMSUB213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AF, regOp1, regOp2, arg); -} -void XEmitter::VFNMSUB231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BF, regOp1, regOp2, arg); -} -void XEmitter::VFNMSUB132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x389F, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMSUB213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38AF, regOp1, regOp2, arg, 1); -} -void XEmitter::VFNMSUB231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38BF, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMADDSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x3896, regOp1, regOp2, arg); -} -void XEmitter::VFMADDSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38A6, regOp1, regOp2, arg); -} -void XEmitter::VFMADDSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38B6, regOp1, regOp2, arg); -} -void XEmitter::VFMADDSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x3896, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMADDSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38A6, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMADDSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38B6, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUBADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x3897, regOp1, regOp2, arg); -} -void XEmitter::VFMSUBADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38A7, regOp1, regOp2, arg); -} -void XEmitter::VFMSUBADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38B7, regOp1, regOp2, arg); -} -void XEmitter::VFMSUBADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x3897, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUBADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38A7, regOp1, regOp2, arg, 1); -} -void XEmitter::VFMSUBADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteAVXOp(0x66, 0x38B7, regOp1, regOp2, arg, 1); -} - -void XEmitter::SARX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2) { - WriteBMI2Op(bits, 0xF3, 0x38F7, regOp1, regOp2, arg); -} -void XEmitter::SHLX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2) { - WriteBMI2Op(bits, 0x66, 0x38F7, regOp1, regOp2, arg); -} -void XEmitter::SHRX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2) { - WriteBMI2Op(bits, 0xF2, 0x38F7, regOp1, regOp2, arg); -} -void XEmitter::RORX(int bits, X64Reg regOp, const OpArg& arg, u8 rotate) { - WriteBMI2Op(bits, 0xF2, 0x3AF0, regOp, INVALID_REG, arg, 1); - Write8(rotate); -} -void XEmitter::PEXT(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteBMI2Op(bits, 0xF3, 0x38F5, regOp1, regOp2, arg); -} -void XEmitter::PDEP(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteBMI2Op(bits, 0xF2, 0x38F5, regOp1, regOp2, arg); -} -void XEmitter::MULX(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteBMI2Op(bits, 0xF2, 0x38F6, regOp2, regOp1, arg); -} -void XEmitter::BZHI(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2) { - WriteBMI2Op(bits, 0x00, 0x38F5, regOp1, regOp2, arg); -} -void XEmitter::BLSR(int bits, X64Reg regOp, const OpArg& arg) { - WriteBMI1Op(bits, 0x00, 0x38F3, (X64Reg)0x1, regOp, arg); -} -void XEmitter::BLSMSK(int bits, X64Reg regOp, const OpArg& arg) { - WriteBMI1Op(bits, 0x00, 0x38F3, (X64Reg)0x2, regOp, arg); -} -void XEmitter::BLSI(int bits, X64Reg regOp, const OpArg& arg) { - WriteBMI1Op(bits, 0x00, 0x38F3, (X64Reg)0x3, regOp, arg); -} -void XEmitter::BEXTR(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2) { - WriteBMI1Op(bits, 0x00, 0x38F7, regOp1, regOp2, arg); -} -void XEmitter::ANDN(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { - WriteBMI1Op(bits, 0x00, 0x38F2, regOp1, regOp2, arg); -} - -// Prefixes - -void XEmitter::LOCK() { - Write8(0xF0); -} -void XEmitter::REP() { - Write8(0xF3); -} -void XEmitter::REPNE() { - Write8(0xF2); -} -void XEmitter::FSOverride() { - Write8(0x64); -} -void XEmitter::GSOverride() { - Write8(0x65); -} - -void XEmitter::FWAIT() { - Write8(0x9B); -} - -// TODO: make this more generic -void XEmitter::WriteFloatLoadStore(int bits, FloatOp op, FloatOp op_80b, const OpArg& arg) { - int mf = 0; - ASSERT_MSG(!(bits == 80 && op_80b == floatINVALID), - "WriteFloatLoadStore: 80 bits not supported for this instruction"); - switch (bits) { - case 32: - mf = 0; - break; - case 64: - mf = 4; - break; - case 80: - mf = 2; - break; - default: - ASSERT_MSG(0, "WriteFloatLoadStore: invalid bits (should be 32/64/80)"); - } - Write8(0xd9 | mf); - // x87 instructions use the reg field of the ModR/M byte as opcode: - if (bits == 80) - op = op_80b; - arg.WriteRest(this, 0, (X64Reg)op); -} - -void XEmitter::FLD(int bits, const OpArg& src) { - WriteFloatLoadStore(bits, floatLD, floatLD80, src); -} -void XEmitter::FST(int bits, const OpArg& dest) { - WriteFloatLoadStore(bits, floatST, floatINVALID, dest); -} -void XEmitter::FSTP(int bits, const OpArg& dest) { - WriteFloatLoadStore(bits, floatSTP, floatSTP80, dest); -} -void XEmitter::FNSTSW_AX() { - Write8(0xDF); - Write8(0xE0); -} - -void XEmitter::RDTSC() { - Write8(0x0F); - Write8(0x31); -} - -void XCodeBlock::PoisonMemory() { - // x86/64: 0xCC = breakpoint - memset(region, 0xCC, region_size); -} -} diff --git a/src/common/x64/emitter.h b/src/common/x64/emitter.h deleted file mode 100644 index 7d7cdde16..000000000 --- a/src/common/x64/emitter.h +++ /dev/null @@ -1,1206 +0,0 @@ -// Copyright (C) 2003 Dolphin Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official SVN repository and contact information can be found at -// http://code.google.com/p/dolphin-emu/ - -#pragma once - -#include <cstddef> -#include "common/assert.h" -#include "common/bit_set.h" -#include "common/code_block.h" -#include "common/common_types.h" - -#if defined(ARCHITECTURE_x86_64) && !defined(_ARCH_64) -#define _ARCH_64 -#endif - -#ifdef _ARCH_64 -#define PTRBITS 64 -#else -#define PTRBITS 32 -#endif - -namespace Gen { - -enum X64Reg { - EAX = 0, - EBX = 3, - ECX = 1, - EDX = 2, - ESI = 6, - EDI = 7, - EBP = 5, - ESP = 4, - - RAX = 0, - RBX = 3, - RCX = 1, - RDX = 2, - RSI = 6, - RDI = 7, - RBP = 5, - RSP = 4, - R8 = 8, - R9 = 9, - R10 = 10, - R11 = 11, - R12 = 12, - R13 = 13, - R14 = 14, - R15 = 15, - - AL = 0, - BL = 3, - CL = 1, - DL = 2, - SIL = 6, - DIL = 7, - BPL = 5, - SPL = 4, - AH = 0x104, - BH = 0x107, - CH = 0x105, - DH = 0x106, - - AX = 0, - BX = 3, - CX = 1, - DX = 2, - SI = 6, - DI = 7, - BP = 5, - SP = 4, - - XMM0 = 0, - XMM1, - XMM2, - XMM3, - XMM4, - XMM5, - XMM6, - XMM7, - XMM8, - XMM9, - XMM10, - XMM11, - XMM12, - XMM13, - XMM14, - XMM15, - - YMM0 = 0, - YMM1, - YMM2, - YMM3, - YMM4, - YMM5, - YMM6, - YMM7, - YMM8, - YMM9, - YMM10, - YMM11, - YMM12, - YMM13, - YMM14, - YMM15, - - INVALID_REG = 0xFFFFFFFF -}; - -enum CCFlags { - CC_O = 0, - CC_NO = 1, - CC_B = 2, - CC_C = 2, - CC_NAE = 2, - CC_NB = 3, - CC_NC = 3, - CC_AE = 3, - CC_Z = 4, - CC_E = 4, - CC_NZ = 5, - CC_NE = 5, - CC_BE = 6, - CC_NA = 6, - CC_NBE = 7, - CC_A = 7, - CC_S = 8, - CC_NS = 9, - CC_P = 0xA, - CC_PE = 0xA, - CC_NP = 0xB, - CC_PO = 0xB, - CC_L = 0xC, - CC_NGE = 0xC, - CC_NL = 0xD, - CC_GE = 0xD, - CC_LE = 0xE, - CC_NG = 0xE, - CC_NLE = 0xF, - CC_G = 0xF -}; - -enum { - NUMGPRs = 16, - NUMXMMs = 16, -}; - -enum { - SCALE_NONE = 0, - SCALE_1 = 1, - SCALE_2 = 2, - SCALE_4 = 4, - SCALE_8 = 8, - SCALE_ATREG = 16, - // SCALE_NOBASE_1 is not supported and can be replaced with SCALE_ATREG - SCALE_NOBASE_2 = 34, - SCALE_NOBASE_4 = 36, - SCALE_NOBASE_8 = 40, - SCALE_RIP = 0xFF, - SCALE_IMM8 = 0xF0, - SCALE_IMM16 = 0xF1, - SCALE_IMM32 = 0xF2, - SCALE_IMM64 = 0xF3, -}; - -enum NormalOp { - nrmADD, - nrmADC, - nrmSUB, - nrmSBB, - nrmAND, - nrmOR, - nrmXOR, - nrmMOV, - nrmTEST, - nrmCMP, - nrmXCHG, -}; - -enum { - CMP_EQ = 0, - CMP_LT = 1, - CMP_LE = 2, - CMP_UNORD = 3, - CMP_NEQ = 4, - CMP_NLT = 5, - CMP_NLE = 6, - CMP_ORD = 7, -}; - -enum FloatOp { - floatLD = 0, - floatST = 2, - floatSTP = 3, - floatLD80 = 5, - floatSTP80 = 7, - - floatINVALID = -1, -}; - -enum FloatRound { - FROUND_NEAREST = 0, - FROUND_FLOOR = 1, - FROUND_CEIL = 2, - FROUND_ZERO = 3, - FROUND_MXCSR = 4, - - FROUND_RAISE_PRECISION = 0, - FROUND_IGNORE_PRECISION = 8, -}; - -class XEmitter; - -// RIP addressing does not benefit from micro op fusion on Core arch -struct OpArg { - friend class XEmitter; - - constexpr OpArg() = default; // dummy op arg, used for storage - constexpr OpArg(u64 offset_, int scale_, X64Reg rmReg = RAX, X64Reg scaledReg = RAX) - : scale(static_cast<u8>(scale_)), offsetOrBaseReg(static_cast<u16>(rmReg)), - indexReg(static_cast<u16>(scaledReg)), offset(offset_) {} - - constexpr bool operator==(const OpArg& b) const { - return operandReg == b.operandReg && scale == b.scale && - offsetOrBaseReg == b.offsetOrBaseReg && indexReg == b.indexReg && offset == b.offset; - } - - void WriteRex(XEmitter* emit, int opBits, int bits, int customOp = -1) const; - void WriteVex(XEmitter* emit, X64Reg regOp1, X64Reg regOp2, int L, int pp, int mmmmm, - int W = 0) const; - void WriteRest(XEmitter* emit, int extraBytes = 0, X64Reg operandReg = INVALID_REG, - bool warn_64bit_offset = true) const; - void WriteSingleByteOp(XEmitter* emit, u8 op, X64Reg operandReg, int bits); - void WriteNormalOp(XEmitter* emit, bool toRM, NormalOp op, const OpArg& operand, - int bits) const; - - constexpr bool IsImm() const { - return scale == SCALE_IMM8 || scale == SCALE_IMM16 || scale == SCALE_IMM32 || - scale == SCALE_IMM64; - } - constexpr bool IsSimpleReg() const { - return scale == SCALE_NONE; - } - constexpr bool IsSimpleReg(X64Reg reg) const { - return IsSimpleReg() && GetSimpleReg() == reg; - } - - int GetImmBits() const { - switch (scale) { - case SCALE_IMM8: - return 8; - case SCALE_IMM16: - return 16; - case SCALE_IMM32: - return 32; - case SCALE_IMM64: - return 64; - default: - return -1; - } - } - - void SetImmBits(int bits) { - switch (bits) { - case 8: - scale = SCALE_IMM8; - break; - case 16: - scale = SCALE_IMM16; - break; - case 32: - scale = SCALE_IMM32; - break; - case 64: - scale = SCALE_IMM64; - break; - } - } - - constexpr X64Reg GetSimpleReg() const { - return scale == SCALE_NONE ? static_cast<X64Reg>(offsetOrBaseReg) : INVALID_REG; - } - - constexpr u32 GetImmValue() const { - return static_cast<u32>(offset); - } - - // For loops. - void IncreaseOffset(int sz) { - offset += sz; - } - -private: - u8 scale = 0; - u16 offsetOrBaseReg = 0; - u16 indexReg = 0; - u64 offset = 0; // use RIP-relative as much as possible - 64-bit immediates are not available. - u16 operandReg = 0; -}; - -template <typename T> -inline OpArg M(const T* ptr) { - return OpArg(reinterpret_cast<u64>(ptr), static_cast<int>(SCALE_RIP)); -} -constexpr OpArg R(X64Reg value) { - return OpArg(0, SCALE_NONE, value); -} -constexpr OpArg MatR(X64Reg value) { - return OpArg(0, SCALE_ATREG, value); -} - -constexpr OpArg MDisp(X64Reg value, int offset) { - return OpArg(static_cast<u32>(offset), SCALE_ATREG, value); -} - -constexpr OpArg MComplex(X64Reg base, X64Reg scaled, int scale, int offset) { - return OpArg(offset, scale, base, scaled); -} - -constexpr OpArg MScaled(X64Reg scaled, int scale, int offset) { - return scale == SCALE_1 ? OpArg(offset, SCALE_ATREG, scaled) - : OpArg(offset, scale | 0x20, RAX, scaled); -} - -constexpr OpArg MRegSum(X64Reg base, X64Reg offset) { - return MComplex(base, offset, 1, 0); -} - -constexpr OpArg Imm8(u8 imm) { - return OpArg(imm, SCALE_IMM8); -} -constexpr OpArg Imm16(u16 imm) { - return OpArg(imm, SCALE_IMM16); -} // rarely used -constexpr OpArg Imm32(u32 imm) { - return OpArg(imm, SCALE_IMM32); -} -constexpr OpArg Imm64(u64 imm) { - return OpArg(imm, SCALE_IMM64); -} -constexpr OpArg UImmAuto(u32 imm) { - return OpArg(imm, imm >= 128 ? SCALE_IMM32 : SCALE_IMM8); -} -constexpr OpArg SImmAuto(s32 imm) { - return OpArg(imm, (imm >= 128 || imm < -128) ? SCALE_IMM32 : SCALE_IMM8); -} - -template <typename T> -OpArg ImmPtr(const T* imm) { -#ifdef _ARCH_64 - return Imm64(reinterpret_cast<u64>(imm)); -#else - return Imm32(reinterpret_cast<u32>(imm)); -#endif -} - -inline u32 PtrOffset(const void* ptr, const void* base) { -#ifdef _ARCH_64 - s64 distance = (s64)ptr - (s64)base; - if (distance >= 0x80000000LL || distance < -0x80000000LL) { - ASSERT_MSG(0, "pointer offset out of range"); - return 0; - } - - return (u32)distance; -#else - return (u32)ptr - (u32)base; -#endif -} - -// usage: int a[]; ARRAY_OFFSET(a,10) -#define ARRAY_OFFSET(array, index) ((u32)((u64) & (array)[index] - (u64) & (array)[0])) -// usage: struct {int e;} s; STRUCT_OFFSET(s,e) -#define STRUCT_OFFSET(str, elem) ((u32)((u64) & (str).elem - (u64) & (str))) - -struct FixupBranch { - u8* ptr; - int type; // 0 = 8bit 1 = 32bit -}; - -enum SSECompare { - EQ = 0, - LT, - LE, - UNORD, - NEQ, - NLT, - NLE, - ORD, -}; - -class XEmitter { - friend struct OpArg; // for Write8 etc -private: - u8* code; - bool flags_locked; - - void CheckFlags(); - - void Rex(int w, int r, int x, int b); - void WriteSimple1Byte(int bits, u8 byte, X64Reg reg); - void WriteSimple2Byte(int bits, u8 byte1, u8 byte2, X64Reg reg); - void WriteMulDivType(int bits, OpArg src, int ext); - void WriteBitSearchType(int bits, X64Reg dest, OpArg src, u8 byte2, bool rep = false); - void WriteShift(int bits, OpArg dest, const OpArg& shift, int ext); - void WriteBitTest(int bits, const OpArg& dest, const OpArg& index, int ext); - void WriteMXCSR(OpArg arg, int ext); - void WriteSSEOp(u8 opPrefix, u16 op, X64Reg regOp, OpArg arg, int extrabytes = 0); - void WriteSSSE3Op(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes = 0); - void WriteSSE41Op(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes = 0); - void WriteAVXOp(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes = 0); - void WriteAVXOp(u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg, - int extrabytes = 0); - void WriteVEXOp(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg, - int extrabytes = 0); - void WriteBMI1Op(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg, - int extrabytes = 0); - void WriteBMI2Op(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg, - int extrabytes = 0); - void WriteFloatLoadStore(int bits, FloatOp op, FloatOp op_80b, const OpArg& arg); - void WriteNormalOp(XEmitter* emit, int bits, NormalOp op, const OpArg& a1, const OpArg& a2); - - void ABI_CalculateFrameSize(BitSet32 mask, size_t rsp_alignment, size_t needed_frame_size, - size_t* shadowp, size_t* subtractionp, size_t* xmm_offsetp); - -protected: - void Write8(u8 value); - void Write16(u16 value); - void Write32(u32 value); - void Write64(u64 value); - -public: - XEmitter() { - code = nullptr; - flags_locked = false; - } - XEmitter(u8* code_ptr) { - code = code_ptr; - flags_locked = false; - } - virtual ~XEmitter() {} - - void WriteModRM(int mod, int rm, int reg); - void WriteSIB(int scale, int index, int base); - - void SetCodePtr(u8* ptr); - void ReserveCodeSpace(int bytes); - const u8* AlignCode4(); - const u8* AlignCode16(); - const u8* AlignCodePage(); - const u8* GetCodePtr() const; - u8* GetWritableCodePtr(); - - void LockFlags() { - flags_locked = true; - } - void UnlockFlags() { - flags_locked = false; - } - - // Looking for one of these? It's BANNED!! Some instructions are slow on modern CPU - // INC, DEC, LOOP, LOOPNE, LOOPE, ENTER, LEAVE, XCHG, XLAT, REP MOVSB/MOVSD, REP SCASD + other - // string instr., - // INC and DEC are slow on Intel Core, but not on AMD. They create a - // false flag dependency because they only update a subset of the flags. - // XCHG is SLOW and should be avoided. - - // Debug breakpoint - void INT3(); - - // Do nothing - void NOP(size_t count = 1); - - // Save energy in wait-loops on P4 only. Probably not too useful. - void PAUSE(); - - // Flag control - void STC(); - void CLC(); - void CMC(); - - // These two can not be executed in 64-bit mode on early Intel 64-bit CPU:s, only on Core2 and - // AMD! - void LAHF(); // 3 cycle vector path - void SAHF(); // direct path fast - - // Stack control - void PUSH(X64Reg reg); - void POP(X64Reg reg); - void PUSH(int bits, const OpArg& reg); - void POP(int bits, const OpArg& reg); - void PUSHF(); - void POPF(); - - // Flow control - void RET(); - void RET_FAST(); - void UD2(); - FixupBranch J(bool force5bytes = false); - - void JMP(const u8* addr, bool force5Bytes = false); - void JMPptr(const OpArg& arg); - void JMPself(); // infinite loop! -#ifdef CALL -#undef CALL -#endif - void CALL(const void* fnptr); - FixupBranch CALL(); - void CALLptr(OpArg arg); - - FixupBranch J_CC(CCFlags conditionCode, bool force5bytes = false); - void J_CC(CCFlags conditionCode, const u8* addr, bool force5Bytes = false); - - void SetJumpTarget(const FixupBranch& branch); - void SetJumpTarget(const FixupBranch& branch, const u8* target); - - void SETcc(CCFlags flag, OpArg dest); - // Note: CMOV brings small if any benefit on current cpus. - void CMOVcc(int bits, X64Reg dest, OpArg src, CCFlags flag); - - // Fences - void LFENCE(); - void MFENCE(); - void SFENCE(); - - // Bit scan - void BSF(int bits, X64Reg dest, const OpArg& src); // Bottom bit to top bit - void BSR(int bits, X64Reg dest, const OpArg& src); // Top bit to bottom bit - - // Cache control - enum PrefetchLevel { - PF_NTA, // Non-temporal (data used once and only once) - PF_T0, // All cache levels - PF_T1, // Levels 2+ (aliased to T0 on AMD) - PF_T2, // Levels 3+ (aliased to T0 on AMD) - }; - void PREFETCH(PrefetchLevel level, OpArg arg); - void MOVNTI(int bits, const OpArg& dest, X64Reg src); - void MOVNTDQ(const OpArg& arg, X64Reg regOp); - void MOVNTPS(const OpArg& arg, X64Reg regOp); - void MOVNTPD(const OpArg& arg, X64Reg regOp); - - // Multiplication / division - void MUL(int bits, const OpArg& src); // UNSIGNED - void IMUL(int bits, const OpArg& src); // SIGNED - void IMUL(int bits, X64Reg regOp, const OpArg& src); - void IMUL(int bits, X64Reg regOp, const OpArg& src, const OpArg& imm); - void DIV(int bits, const OpArg& src); - void IDIV(int bits, const OpArg& src); - - // Shift - void ROL(int bits, const OpArg& dest, const OpArg& shift); - void ROR(int bits, const OpArg& dest, const OpArg& shift); - void RCL(int bits, const OpArg& dest, const OpArg& shift); - void RCR(int bits, const OpArg& dest, const OpArg& shift); - void SHL(int bits, const OpArg& dest, const OpArg& shift); - void SHR(int bits, const OpArg& dest, const OpArg& shift); - void SAR(int bits, const OpArg& dest, const OpArg& shift); - - // Bit Test - void BT(int bits, const OpArg& dest, const OpArg& index); - void BTS(int bits, const OpArg& dest, const OpArg& index); - void BTR(int bits, const OpArg& dest, const OpArg& index); - void BTC(int bits, const OpArg& dest, const OpArg& index); - - // Double-Precision Shift - void SHRD(int bits, const OpArg& dest, const OpArg& src, const OpArg& shift); - void SHLD(int bits, const OpArg& dest, const OpArg& src, const OpArg& shift); - - // Extend EAX into EDX in various ways - void CWD(int bits = 16); - void CDQ() { - CWD(32); - } - void CQO() { - CWD(64); - } - void CBW(int bits = 8); - void CWDE() { - CBW(16); - } - void CDQE() { - CBW(32); - } - - // Load effective address - void LEA(int bits, X64Reg dest, OpArg src); - - // Integer arithmetic - void NEG(int bits, const OpArg& src); - void ADD(int bits, const OpArg& a1, const OpArg& a2); - void ADC(int bits, const OpArg& a1, const OpArg& a2); - void SUB(int bits, const OpArg& a1, const OpArg& a2); - void SBB(int bits, const OpArg& a1, const OpArg& a2); - void AND(int bits, const OpArg& a1, const OpArg& a2); - void CMP(int bits, const OpArg& a1, const OpArg& a2); - - // Bit operations - void NOT(int bits, const OpArg& src); - void OR(int bits, const OpArg& a1, const OpArg& a2); - void XOR(int bits, const OpArg& a1, const OpArg& a2); - void MOV(int bits, const OpArg& a1, const OpArg& a2); - void TEST(int bits, const OpArg& a1, const OpArg& a2); - - // Are these useful at all? Consider removing. - void XCHG(int bits, const OpArg& a1, const OpArg& a2); - void XCHG_AHAL(); - - // Byte swapping (32 and 64-bit only). - void BSWAP(int bits, X64Reg reg); - - // Sign/zero extension - void MOVSX(int dbits, int sbits, X64Reg dest, - OpArg src); // automatically uses MOVSXD if necessary - void MOVZX(int dbits, int sbits, X64Reg dest, OpArg src); - - // Available only on Atom or >= Haswell so far. Test with GetCPUCaps().movbe. - void MOVBE(int dbits, const OpArg& dest, const OpArg& src); - - // Available only on AMD >= Phenom or Intel >= Haswell - void LZCNT(int bits, X64Reg dest, const OpArg& src); - // Note: this one is actually part of BMI1 - void TZCNT(int bits, X64Reg dest, const OpArg& src); - - // WARNING - These two take 11-13 cycles and are VectorPath! (AMD64) - void STMXCSR(const OpArg& memloc); - void LDMXCSR(const OpArg& memloc); - - // Prefixes - void LOCK(); - void REP(); - void REPNE(); - void FSOverride(); - void GSOverride(); - - // x87 - enum x87StatusWordBits { - x87_InvalidOperation = 0x1, - x87_DenormalizedOperand = 0x2, - x87_DivisionByZero = 0x4, - x87_Overflow = 0x8, - x87_Underflow = 0x10, - x87_Precision = 0x20, - x87_StackFault = 0x40, - x87_ErrorSummary = 0x80, - x87_C0 = 0x100, - x87_C1 = 0x200, - x87_C2 = 0x400, - x87_TopOfStack = 0x2000 | 0x1000 | 0x800, - x87_C3 = 0x4000, - x87_FPUBusy = 0x8000, - }; - - void FLD(int bits, const OpArg& src); - void FST(int bits, const OpArg& dest); - void FSTP(int bits, const OpArg& dest); - void FNSTSW_AX(); - void FWAIT(); - - // SSE/SSE2: Floating point arithmetic - void ADDSS(X64Reg regOp, const OpArg& arg); - void ADDSD(X64Reg regOp, const OpArg& arg); - void SUBSS(X64Reg regOp, const OpArg& arg); - void SUBSD(X64Reg regOp, const OpArg& arg); - void MULSS(X64Reg regOp, const OpArg& arg); - void MULSD(X64Reg regOp, const OpArg& arg); - void DIVSS(X64Reg regOp, const OpArg& arg); - void DIVSD(X64Reg regOp, const OpArg& arg); - void MINSS(X64Reg regOp, const OpArg& arg); - void MINSD(X64Reg regOp, const OpArg& arg); - void MAXSS(X64Reg regOp, const OpArg& arg); - void MAXSD(X64Reg regOp, const OpArg& arg); - void SQRTSS(X64Reg regOp, const OpArg& arg); - void SQRTSD(X64Reg regOp, const OpArg& arg); - void RCPSS(X64Reg regOp, const OpArg& arg); - void RSQRTSS(X64Reg regOp, const OpArg& arg); - - // SSE/SSE2: Floating point bitwise (yes) - void CMPSS(X64Reg regOp, const OpArg& arg, u8 compare); - void CMPSD(X64Reg regOp, const OpArg& arg, u8 compare); - - void CMPEQSS(X64Reg regOp, const OpArg& arg) { - CMPSS(regOp, arg, CMP_EQ); - } - void CMPLTSS(X64Reg regOp, const OpArg& arg) { - CMPSS(regOp, arg, CMP_LT); - } - void CMPLESS(X64Reg regOp, const OpArg& arg) { - CMPSS(regOp, arg, CMP_LE); - } - void CMPUNORDSS(X64Reg regOp, const OpArg& arg) { - CMPSS(regOp, arg, CMP_UNORD); - } - void CMPNEQSS(X64Reg regOp, const OpArg& arg) { - CMPSS(regOp, arg, CMP_NEQ); - } - void CMPNLTSS(X64Reg regOp, const OpArg& arg) { - CMPSS(regOp, arg, CMP_NLT); - } - void CMPORDSS(X64Reg regOp, const OpArg& arg) { - CMPSS(regOp, arg, CMP_ORD); - } - - // SSE/SSE2: Floating point packed arithmetic (x4 for float, x2 for double) - void ADDPS(X64Reg regOp, const OpArg& arg); - void ADDPD(X64Reg regOp, const OpArg& arg); - void SUBPS(X64Reg regOp, const OpArg& arg); - void SUBPD(X64Reg regOp, const OpArg& arg); - void CMPPS(X64Reg regOp, const OpArg& arg, u8 compare); - void CMPPD(X64Reg regOp, const OpArg& arg, u8 compare); - void MULPS(X64Reg regOp, const OpArg& arg); - void MULPD(X64Reg regOp, const OpArg& arg); - void DIVPS(X64Reg regOp, const OpArg& arg); - void DIVPD(X64Reg regOp, const OpArg& arg); - void MINPS(X64Reg regOp, const OpArg& arg); - void MINPD(X64Reg regOp, const OpArg& arg); - void MAXPS(X64Reg regOp, const OpArg& arg); - void MAXPD(X64Reg regOp, const OpArg& arg); - void SQRTPS(X64Reg regOp, const OpArg& arg); - void SQRTPD(X64Reg regOp, const OpArg& arg); - void RCPPS(X64Reg regOp, const OpArg& arg); - void RSQRTPS(X64Reg regOp, const OpArg& arg); - - // SSE/SSE2: Floating point packed bitwise (x4 for float, x2 for double) - void ANDPS(X64Reg regOp, const OpArg& arg); - void ANDPD(X64Reg regOp, const OpArg& arg); - void ANDNPS(X64Reg regOp, const OpArg& arg); - void ANDNPD(X64Reg regOp, const OpArg& arg); - void ORPS(X64Reg regOp, const OpArg& arg); - void ORPD(X64Reg regOp, const OpArg& arg); - void XORPS(X64Reg regOp, const OpArg& arg); - void XORPD(X64Reg regOp, const OpArg& arg); - - // SSE/SSE2: Shuffle components. These are tricky - see Intel documentation. - void SHUFPS(X64Reg regOp, const OpArg& arg, u8 shuffle); - void SHUFPD(X64Reg regOp, const OpArg& arg, u8 shuffle); - - // SSE/SSE2: Useful alternative to shuffle in some cases. - void MOVDDUP(X64Reg regOp, const OpArg& arg); - - // SSE3: Horizontal operations in SIMD registers. Very slow! shufps-based code beats it handily - // on Ivy. - void HADDPS(X64Reg dest, const OpArg& src); - - // SSE4: Further horizontal operations - dot products. These are weirdly flexible, the arg - // contains both a read mask and a write "mask". - void DPPS(X64Reg dest, const OpArg& src, u8 arg); - - void UNPCKLPS(X64Reg dest, const OpArg& src); - void UNPCKHPS(X64Reg dest, const OpArg& src); - void UNPCKLPD(X64Reg dest, const OpArg& src); - void UNPCKHPD(X64Reg dest, const OpArg& src); - - // SSE/SSE2: Compares. - void COMISS(X64Reg regOp, const OpArg& arg); - void COMISD(X64Reg regOp, const OpArg& arg); - void UCOMISS(X64Reg regOp, const OpArg& arg); - void UCOMISD(X64Reg regOp, const OpArg& arg); - - // SSE/SSE2: Moves. Use the right data type for your data, in most cases. - void MOVAPS(X64Reg regOp, const OpArg& arg); - void MOVAPD(X64Reg regOp, const OpArg& arg); - void MOVAPS(const OpArg& arg, X64Reg regOp); - void MOVAPD(const OpArg& arg, X64Reg regOp); - - void MOVUPS(X64Reg regOp, const OpArg& arg); - void MOVUPD(X64Reg regOp, const OpArg& arg); - void MOVUPS(const OpArg& arg, X64Reg regOp); - void MOVUPD(const OpArg& arg, X64Reg regOp); - - void MOVDQA(X64Reg regOp, const OpArg& arg); - void MOVDQA(const OpArg& arg, X64Reg regOp); - void MOVDQU(X64Reg regOp, const OpArg& arg); - void MOVDQU(const OpArg& arg, X64Reg regOp); - - void MOVSS(X64Reg regOp, const OpArg& arg); - void MOVSD(X64Reg regOp, const OpArg& arg); - void MOVSS(const OpArg& arg, X64Reg regOp); - void MOVSD(const OpArg& arg, X64Reg regOp); - - void MOVLPS(X64Reg regOp, const OpArg& arg); - void MOVLPD(X64Reg regOp, const OpArg& arg); - void MOVLPS(const OpArg& arg, X64Reg regOp); - void MOVLPD(const OpArg& arg, X64Reg regOp); - - void MOVHPS(X64Reg regOp, const OpArg& arg); - void MOVHPD(X64Reg regOp, const OpArg& arg); - void MOVHPS(const OpArg& arg, X64Reg regOp); - void MOVHPD(const OpArg& arg, X64Reg regOp); - - void MOVHLPS(X64Reg regOp1, X64Reg regOp2); - void MOVLHPS(X64Reg regOp1, X64Reg regOp2); - - void MOVD_xmm(X64Reg dest, const OpArg& arg); - void MOVQ_xmm(X64Reg dest, OpArg arg); - void MOVD_xmm(const OpArg& arg, X64Reg src); - void MOVQ_xmm(OpArg arg, X64Reg src); - - // SSE/SSE2: Generates a mask from the high bits of the components of the packed register in - // question. - void MOVMSKPS(X64Reg dest, const OpArg& arg); - void MOVMSKPD(X64Reg dest, const OpArg& arg); - - // SSE2: Selective byte store, mask in src register. EDI/RDI specifies store address. This is a - // weird one. - void MASKMOVDQU(X64Reg dest, X64Reg src); - void LDDQU(X64Reg dest, const OpArg& src); - - // SSE/SSE2: Data type conversions. - void CVTPS2PD(X64Reg dest, const OpArg& src); - void CVTPD2PS(X64Reg dest, const OpArg& src); - void CVTSS2SD(X64Reg dest, const OpArg& src); - void CVTSI2SS(X64Reg dest, const OpArg& src); - void CVTSD2SS(X64Reg dest, const OpArg& src); - void CVTSI2SD(X64Reg dest, const OpArg& src); - void CVTDQ2PD(X64Reg regOp, const OpArg& arg); - void CVTPD2DQ(X64Reg regOp, const OpArg& arg); - void CVTDQ2PS(X64Reg regOp, const OpArg& arg); - void CVTPS2DQ(X64Reg regOp, const OpArg& arg); - - void CVTTPS2DQ(X64Reg regOp, const OpArg& arg); - void CVTTPD2DQ(X64Reg regOp, const OpArg& arg); - - // Destinations are X64 regs (rax, rbx, ...) for these instructions. - void CVTSS2SI(X64Reg xregdest, const OpArg& src); - void CVTSD2SI(X64Reg xregdest, const OpArg& src); - void CVTTSS2SI(X64Reg xregdest, const OpArg& arg); - void CVTTSD2SI(X64Reg xregdest, const OpArg& arg); - - // SSE2: Packed integer instructions - void PACKSSDW(X64Reg dest, const OpArg& arg); - void PACKSSWB(X64Reg dest, const OpArg& arg); - void PACKUSDW(X64Reg dest, const OpArg& arg); - void PACKUSWB(X64Reg dest, const OpArg& arg); - - void PUNPCKLBW(X64Reg dest, const OpArg& arg); - void PUNPCKLWD(X64Reg dest, const OpArg& arg); - void PUNPCKLDQ(X64Reg dest, const OpArg& arg); - void PUNPCKLQDQ(X64Reg dest, const OpArg& arg); - - void PTEST(X64Reg dest, const OpArg& arg); - void PAND(X64Reg dest, const OpArg& arg); - void PANDN(X64Reg dest, const OpArg& arg); - void PXOR(X64Reg dest, const OpArg& arg); - void POR(X64Reg dest, const OpArg& arg); - - void PADDB(X64Reg dest, const OpArg& arg); - void PADDW(X64Reg dest, const OpArg& arg); - void PADDD(X64Reg dest, const OpArg& arg); - void PADDQ(X64Reg dest, const OpArg& arg); - - void PADDSB(X64Reg dest, const OpArg& arg); - void PADDSW(X64Reg dest, const OpArg& arg); - void PADDUSB(X64Reg dest, const OpArg& arg); - void PADDUSW(X64Reg dest, const OpArg& arg); - - void PSUBB(X64Reg dest, const OpArg& arg); - void PSUBW(X64Reg dest, const OpArg& arg); - void PSUBD(X64Reg dest, const OpArg& arg); - void PSUBQ(X64Reg dest, const OpArg& arg); - - void PSUBSB(X64Reg dest, const OpArg& arg); - void PSUBSW(X64Reg dest, const OpArg& arg); - void PSUBUSB(X64Reg dest, const OpArg& arg); - void PSUBUSW(X64Reg dest, const OpArg& arg); - - void PAVGB(X64Reg dest, const OpArg& arg); - void PAVGW(X64Reg dest, const OpArg& arg); - - void PCMPEQB(X64Reg dest, const OpArg& arg); - void PCMPEQW(X64Reg dest, const OpArg& arg); - void PCMPEQD(X64Reg dest, const OpArg& arg); - - void PCMPGTB(X64Reg dest, const OpArg& arg); - void PCMPGTW(X64Reg dest, const OpArg& arg); - void PCMPGTD(X64Reg dest, const OpArg& arg); - - void PEXTRW(X64Reg dest, const OpArg& arg, u8 subreg); - void PINSRW(X64Reg dest, const OpArg& arg, u8 subreg); - - void PMADDWD(X64Reg dest, const OpArg& arg); - void PSADBW(X64Reg dest, const OpArg& arg); - - void PMAXSW(X64Reg dest, const OpArg& arg); - void PMAXUB(X64Reg dest, const OpArg& arg); - void PMINSW(X64Reg dest, const OpArg& arg); - void PMINUB(X64Reg dest, const OpArg& arg); - // SSE4: More MAX/MIN instructions. - void PMINSB(X64Reg dest, const OpArg& arg); - void PMINSD(X64Reg dest, const OpArg& arg); - void PMINUW(X64Reg dest, const OpArg& arg); - void PMINUD(X64Reg dest, const OpArg& arg); - void PMAXSB(X64Reg dest, const OpArg& arg); - void PMAXSD(X64Reg dest, const OpArg& arg); - void PMAXUW(X64Reg dest, const OpArg& arg); - void PMAXUD(X64Reg dest, const OpArg& arg); - - void PMOVMSKB(X64Reg dest, const OpArg& arg); - void PSHUFD(X64Reg dest, const OpArg& arg, u8 shuffle); - void PSHUFB(X64Reg dest, const OpArg& arg); - - void PSHUFLW(X64Reg dest, const OpArg& arg, u8 shuffle); - void PSHUFHW(X64Reg dest, const OpArg& arg, u8 shuffle); - - void PSRLW(X64Reg reg, int shift); - void PSRLD(X64Reg reg, int shift); - void PSRLQ(X64Reg reg, int shift); - void PSRLQ(X64Reg reg, const OpArg& arg); - void PSRLDQ(X64Reg reg, int shift); - - void PSLLW(X64Reg reg, int shift); - void PSLLD(X64Reg reg, int shift); - void PSLLQ(X64Reg reg, int shift); - void PSLLDQ(X64Reg reg, int shift); - - void PSRAW(X64Reg reg, int shift); - void PSRAD(X64Reg reg, int shift); - - // SSE4: data type conversions - void PMOVSXBW(X64Reg dest, const OpArg& arg); - void PMOVSXBD(X64Reg dest, const OpArg& arg); - void PMOVSXBQ(X64Reg dest, const OpArg& arg); - void PMOVSXWD(X64Reg dest, const OpArg& arg); - void PMOVSXWQ(X64Reg dest, const OpArg& arg); - void PMOVSXDQ(X64Reg dest, const OpArg& arg); - void PMOVZXBW(X64Reg dest, const OpArg& arg); - void PMOVZXBD(X64Reg dest, const OpArg& arg); - void PMOVZXBQ(X64Reg dest, const OpArg& arg); - void PMOVZXWD(X64Reg dest, const OpArg& arg); - void PMOVZXWQ(X64Reg dest, const OpArg& arg); - void PMOVZXDQ(X64Reg dest, const OpArg& arg); - - // SSE4: variable blend instructions (xmm0 implicit argument) - void PBLENDVB(X64Reg dest, const OpArg& arg); - void BLENDVPS(X64Reg dest, const OpArg& arg); - void BLENDVPD(X64Reg dest, const OpArg& arg); - void BLENDPS(X64Reg dest, const OpArg& arg, u8 blend); - void BLENDPD(X64Reg dest, const OpArg& arg, u8 blend); - - // SSE4: rounding (see FloatRound for mode or use ROUNDNEARSS, etc. helpers.) - void ROUNDSS(X64Reg dest, const OpArg& arg, u8 mode); - void ROUNDSD(X64Reg dest, const OpArg& arg, u8 mode); - void ROUNDPS(X64Reg dest, const OpArg& arg, u8 mode); - void ROUNDPD(X64Reg dest, const OpArg& arg, u8 mode); - - void ROUNDNEARSS(X64Reg dest, const OpArg& arg) { - ROUNDSS(dest, arg, FROUND_NEAREST); - } - void ROUNDFLOORSS(X64Reg dest, const OpArg& arg) { - ROUNDSS(dest, arg, FROUND_FLOOR); - } - void ROUNDCEILSS(X64Reg dest, const OpArg& arg) { - ROUNDSS(dest, arg, FROUND_CEIL); - } - void ROUNDZEROSS(X64Reg dest, const OpArg& arg) { - ROUNDSS(dest, arg, FROUND_ZERO); - } - - void ROUNDNEARSD(X64Reg dest, const OpArg& arg) { - ROUNDSD(dest, arg, FROUND_NEAREST); - } - void ROUNDFLOORSD(X64Reg dest, const OpArg& arg) { - ROUNDSD(dest, arg, FROUND_FLOOR); - } - void ROUNDCEILSD(X64Reg dest, const OpArg& arg) { - ROUNDSD(dest, arg, FROUND_CEIL); - } - void ROUNDZEROSD(X64Reg dest, const OpArg& arg) { - ROUNDSD(dest, arg, FROUND_ZERO); - } - - void ROUNDNEARPS(X64Reg dest, const OpArg& arg) { - ROUNDPS(dest, arg, FROUND_NEAREST); - } - void ROUNDFLOORPS(X64Reg dest, const OpArg& arg) { - ROUNDPS(dest, arg, FROUND_FLOOR); - } - void ROUNDCEILPS(X64Reg dest, const OpArg& arg) { - ROUNDPS(dest, arg, FROUND_CEIL); - } - void ROUNDZEROPS(X64Reg dest, const OpArg& arg) { - ROUNDPS(dest, arg, FROUND_ZERO); - } - - void ROUNDNEARPD(X64Reg dest, const OpArg& arg) { - ROUNDPD(dest, arg, FROUND_NEAREST); - } - void ROUNDFLOORPD(X64Reg dest, const OpArg& arg) { - ROUNDPD(dest, arg, FROUND_FLOOR); - } - void ROUNDCEILPD(X64Reg dest, const OpArg& arg) { - ROUNDPD(dest, arg, FROUND_CEIL); - } - void ROUNDZEROPD(X64Reg dest, const OpArg& arg) { - ROUNDPD(dest, arg, FROUND_ZERO); - } - - // AVX - void VADDSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VSUBSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VMULSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VDIVSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VADDPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VSUBPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VMULPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VDIVPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VSQRTSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VSHUFPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg, u8 shuffle); - void VUNPCKLPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VUNPCKHPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - - void VANDPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VANDPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VANDNPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VANDNPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VORPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VORPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VXORPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VXORPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - - void VPAND(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VPANDN(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VPOR(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VPXOR(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - - // FMA3 - void VFMADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADD231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUB231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMADD231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFNMSUB231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADDSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADDSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADDSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADDSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADDSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMADDSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUBADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUBADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUBADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUBADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUBADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void VFMSUBADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - - // VEX GPR instructions - void SARX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2); - void SHLX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2); - void SHRX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2); - void RORX(int bits, X64Reg regOp, const OpArg& arg, u8 rotate); - void PEXT(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void PDEP(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void MULX(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - void BZHI(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2); - void BLSR(int bits, X64Reg regOp, const OpArg& arg); - void BLSMSK(int bits, X64Reg regOp, const OpArg& arg); - void BLSI(int bits, X64Reg regOp, const OpArg& arg); - void BEXTR(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2); - void ANDN(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg); - - void RDTSC(); - - // Utility functions - // The difference between this and CALL is that this aligns the stack - // where appropriate. - void ABI_CallFunction(const void* func); - template <typename T> - void ABI_CallFunction(T (*func)()) { - ABI_CallFunction((const void*)func); - } - - void ABI_CallFunction(const u8* func) { - ABI_CallFunction((const void*)func); - } - void ABI_CallFunctionC16(const void* func, u16 param1); - void ABI_CallFunctionCC16(const void* func, u32 param1, u16 param2); - - // These only support u32 parameters, but that's enough for a lot of uses. - // These will destroy the 1 or 2 first "parameter regs". - void ABI_CallFunctionC(const void* func, u32 param1); - void ABI_CallFunctionCC(const void* func, u32 param1, u32 param2); - void ABI_CallFunctionCCC(const void* func, u32 param1, u32 param2, u32 param3); - void ABI_CallFunctionCCP(const void* func, u32 param1, u32 param2, void* param3); - void ABI_CallFunctionCCCP(const void* func, u32 param1, u32 param2, u32 param3, void* param4); - void ABI_CallFunctionP(const void* func, void* param1); - void ABI_CallFunctionPA(const void* func, void* param1, const OpArg& arg2); - void ABI_CallFunctionPAA(const void* func, void* param1, const OpArg& arg2, const OpArg& arg3); - void ABI_CallFunctionPPC(const void* func, void* param1, void* param2, u32 param3); - void ABI_CallFunctionAC(const void* func, const OpArg& arg1, u32 param2); - void ABI_CallFunctionACC(const void* func, const OpArg& arg1, u32 param2, u32 param3); - void ABI_CallFunctionA(const void* func, const OpArg& arg1); - void ABI_CallFunctionAA(const void* func, const OpArg& arg1, const OpArg& arg2); - - // Pass a register as a parameter. - void ABI_CallFunctionR(const void* func, X64Reg reg1); - void ABI_CallFunctionRR(const void* func, X64Reg reg1, X64Reg reg2); - - template <typename Tr, typename T1> - void ABI_CallFunctionC(Tr (*func)(T1), u32 param1) { - ABI_CallFunctionC((const void*)func, param1); - } - - /** - * Saves specified registers and adjusts the stack to be 16-byte aligned as required by the ABI - * - * @param mask Registers to push on the stack (high 16 bits are XMMs, low 16 bits are GPRs) - * @param rsp_alignment Current alignment of the stack pointer, must be 0 or 8 - * @param needed_frame_size Additional space needed, e.g., for function arguments passed on the - * stack - * @return Size of the shadow space, i.e., offset of the frame - */ - size_t ABI_PushRegistersAndAdjustStack(BitSet32 mask, size_t rsp_alignment, - size_t needed_frame_size = 0); - - /** - * Restores specified registers and adjusts the stack to its original alignment, i.e., the - * alignment before - * the matching PushRegistersAndAdjustStack. - * - * @param mask Registers to restores from the stack (high 16 bits are XMMs, low 16 bits are - * GPRs) - * @param rsp_alignment Original alignment before the matching PushRegistersAndAdjustStack, must - * be 0 or 8 - * @param needed_frame_size Additional space that was needed - * @warning Stack must be currently 16-byte aligned - */ - void ABI_PopRegistersAndAdjustStack(BitSet32 mask, size_t rsp_alignment, - size_t needed_frame_size = 0); - -#ifdef _M_IX86 - static int ABI_GetNumXMMRegs() { - return 8; - } -#else - static int ABI_GetNumXMMRegs() { - return 16; - } -#endif -}; // class XEmitter - -// Everything that needs to generate X86 code should inherit from this. -// You get memory management for free, plus, you can use all the MOV etc functions without -// having to prefix them with gen-> or something similar. - -class XCodeBlock : public CodeBlock<XEmitter> { -public: - void PoisonMemory() override; -}; - -} // namespace diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index bd0e3c595..61a0b1cc3 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2,6 +2,7 @@ set(SRCS arm/disassembler/arm_disasm.cpp arm/disassembler/load_symbol_map.cpp arm/dynarmic/arm_dynarmic.cpp + arm/dynarmic/arm_dynarmic_cp15.cpp arm/dyncom/arm_dyncom.cpp arm/dyncom/arm_dyncom_dec.cpp arm/dyncom/arm_dyncom_interpreter.cpp @@ -19,10 +20,10 @@ set(SRCS file_sys/archive_extsavedata.cpp file_sys/archive_ncch.cpp file_sys/archive_other_savedata.cpp - file_sys/archive_romfs.cpp file_sys/archive_savedata.cpp file_sys/archive_sdmc.cpp file_sys/archive_sdmcwriteonly.cpp + file_sys/archive_selfncch.cpp file_sys/archive_source_sd_savedata.cpp file_sys/archive_systemsavedata.cpp file_sys/disk_archive.cpp @@ -33,13 +34,13 @@ set(SRCS frontend/camera/factory.cpp frontend/camera/interface.cpp frontend/emu_window.cpp - frontend/key_map.cpp frontend/motion_emu.cpp gdbstub/gdbstub.cpp hle/config_mem.cpp hle/applets/applet.cpp hle/applets/erreula.cpp hle/applets/mii_selector.cpp + hle/applets/mint.cpp hle/applets/swkbd.cpp hle/kernel/address_arbiter.cpp hle/kernel/client_port.cpp @@ -157,6 +158,9 @@ set(SRCS hle/service/y2r_u.cpp hle/shared_page.cpp hle/svc.cpp + hw/aes/arithmetic128.cpp + hw/aes/ccm.cpp + hw/aes/key.cpp hw/gpu.cpp hw/hw.cpp hw/lcd.cpp @@ -168,6 +172,7 @@ set(SRCS loader/smdh.cpp tracer/recorder.cpp memory.cpp + perf_stats.cpp settings.cpp ) @@ -176,6 +181,7 @@ set(HEADERS arm/disassembler/arm_disasm.h arm/disassembler/load_symbol_map.h arm/dynarmic/arm_dynarmic.h + arm/dynarmic/arm_dynarmic_cp15.h arm/dyncom/arm_dyncom.h arm/dyncom/arm_dyncom_dec.h arm/dyncom/arm_dyncom_interpreter.h @@ -194,14 +200,15 @@ set(HEADERS file_sys/archive_extsavedata.h file_sys/archive_ncch.h file_sys/archive_other_savedata.h - file_sys/archive_romfs.h file_sys/archive_savedata.h file_sys/archive_sdmc.h file_sys/archive_sdmcwriteonly.h + file_sys/archive_selfncch.h file_sys/archive_source_sd_savedata.h file_sys/archive_systemsavedata.h file_sys/directory_backend.h file_sys/disk_archive.h + file_sys/errors.h file_sys/file_backend.h file_sys/ivfc_archive.h file_sys/path_parser.h @@ -210,15 +217,17 @@ set(HEADERS frontend/camera/factory.h frontend/camera/interface.h frontend/emu_window.h - frontend/key_map.h + frontend/input.h frontend/motion_emu.h gdbstub/gdbstub.h hle/config_mem.h hle/function_wrappers.h hle/ipc.h + hle/ipc_helpers.h hle/applets/applet.h hle/applets/erreula.h hle/applets/mii_selector.h + hle/applets/mint.h hle/applets/swkbd.h hle/kernel/address_arbiter.h hle/kernel/client_port.h @@ -337,6 +346,9 @@ set(HEADERS hle/service/y2r_u.h hle/shared_page.h hle/svc.h + hw/aes/arithmetic128.h + hw/aes/ccm.h + hw/aes/key.h hw/gpu.h hw/hw.h hw/lcd.h @@ -351,13 +363,15 @@ set(HEADERS memory.h memory_setup.h mmio.h + perf_stats.h settings.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) +target_link_libraries(core dynarmic cryptopp) diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 9f25e3b00..7d2790b08 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -7,6 +7,7 @@ #include "common/assert.h" #include "common/microprofile.h" #include "core/arm/dynarmic/arm_dynarmic.h" +#include "core/arm/dynarmic/arm_dynarmic_cp15.h" #include "core/arm/dyncom/arm_dyncom_interpreter.h" #include "core/core.h" #include "core/core_timing.h" @@ -39,28 +40,30 @@ static bool IsReadOnlyMemory(u32 vaddr) { return false; } -static Dynarmic::UserCallbacks GetUserCallbacks(ARMul_State* interpeter_state) { +static Dynarmic::UserCallbacks GetUserCallbacks( + const std::shared_ptr<ARMul_State>& interpeter_state) { Dynarmic::UserCallbacks user_callbacks{}; user_callbacks.InterpreterFallback = &InterpreterFallback; - user_callbacks.user_arg = static_cast<void*>(interpeter_state); + user_callbacks.user_arg = static_cast<void*>(interpeter_state.get()); user_callbacks.CallSVC = &SVC::CallSVC; - user_callbacks.IsReadOnlyMemory = &IsReadOnlyMemory; - user_callbacks.MemoryReadCode = &Memory::Read32; - user_callbacks.MemoryRead8 = &Memory::Read8; - user_callbacks.MemoryRead16 = &Memory::Read16; - user_callbacks.MemoryRead32 = &Memory::Read32; - user_callbacks.MemoryRead64 = &Memory::Read64; - user_callbacks.MemoryWrite8 = &Memory::Write8; - user_callbacks.MemoryWrite16 = &Memory::Write16; - user_callbacks.MemoryWrite32 = &Memory::Write32; - user_callbacks.MemoryWrite64 = &Memory::Write64; + user_callbacks.memory.IsReadOnlyMemory = &IsReadOnlyMemory; + user_callbacks.memory.ReadCode = &Memory::Read32; + user_callbacks.memory.Read8 = &Memory::Read8; + user_callbacks.memory.Read16 = &Memory::Read16; + user_callbacks.memory.Read32 = &Memory::Read32; + user_callbacks.memory.Read64 = &Memory::Read64; + user_callbacks.memory.Write8 = &Memory::Write8; + user_callbacks.memory.Write16 = &Memory::Write16; + user_callbacks.memory.Write32 = &Memory::Write32; + user_callbacks.memory.Write64 = &Memory::Write64; user_callbacks.page_table = Memory::GetCurrentPageTablePointers(); + user_callbacks.coprocessors[15] = std::make_shared<DynarmicCP15>(interpeter_state); return user_callbacks; } ARM_Dynarmic::ARM_Dynarmic(PrivilegeMode initial_mode) { - interpreter_state = std::make_unique<ARMul_State>(initial_mode); - jit = std::make_unique<Dynarmic::Jit>(GetUserCallbacks(interpreter_state.get())); + interpreter_state = std::make_shared<ARMul_State>(initial_mode); + jit = std::make_unique<Dynarmic::Jit>(GetUserCallbacks(interpreter_state)); } void ARM_Dynarmic::SetPC(u32 pc) { diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h index 87ab53d81..834dc989e 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.h +++ b/src/core/arm/dynarmic/arm_dynarmic.h @@ -39,5 +39,5 @@ public: private: std::unique_ptr<Dynarmic::Jit> jit; - std::unique_ptr<ARMul_State> interpreter_state; + std::shared_ptr<ARMul_State> interpreter_state; }; diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp new file mode 100644 index 000000000..b1fdce096 --- /dev/null +++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp @@ -0,0 +1,88 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/arm/dynarmic/arm_dynarmic_cp15.h" +#include "core/arm/skyeye_common/arm_regformat.h" +#include "core/arm/skyeye_common/armstate.h" + +using Callback = Dynarmic::Coprocessor::Callback; +using CallbackOrAccessOneWord = Dynarmic::Coprocessor::CallbackOrAccessOneWord; +using CallbackOrAccessTwoWords = Dynarmic::Coprocessor::CallbackOrAccessTwoWords; + +DynarmicCP15::DynarmicCP15(const std::shared_ptr<ARMul_State>& state) : interpreter_state(state) {} + +DynarmicCP15::~DynarmicCP15() = default; + +boost::optional<Callback> DynarmicCP15::CompileInternalOperation(bool two, unsigned opc1, + CoprocReg CRd, CoprocReg CRn, + CoprocReg CRm, unsigned opc2) { + return boost::none; +} + +CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn, + CoprocReg CRm, unsigned opc2) { + // TODO(merry): Privileged CP15 registers + + if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C5 && opc2 == 4) { + // This is a dummy write, we ignore the value written here. + return &interpreter_state->CP15[CP15_FLUSH_PREFETCH_BUFFER]; + } + + if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C10) { + switch (opc2) { + case 4: + // This is a dummy write, we ignore the value written here. + return &interpreter_state->CP15[CP15_DATA_SYNC_BARRIER]; + case 5: + // This is a dummy write, we ignore the value written here. + return &interpreter_state->CP15[CP15_DATA_MEMORY_BARRIER]; + default: + return boost::blank{}; + } + } + + if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0 && opc2 == 2) { + return &interpreter_state->CP15[CP15_THREAD_UPRW]; + } + + return boost::blank{}; +} + +CallbackOrAccessTwoWords DynarmicCP15::CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) { + return boost::blank{}; +} + +CallbackOrAccessOneWord DynarmicCP15::CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn, + CoprocReg CRm, unsigned opc2) { + // TODO(merry): Privileged CP15 registers + + if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0) { + switch (opc2) { + case 2: + return &interpreter_state->CP15[CP15_THREAD_UPRW]; + case 3: + return &interpreter_state->CP15[CP15_THREAD_URO]; + default: + return boost::blank{}; + } + } + + return boost::blank{}; +} + +CallbackOrAccessTwoWords DynarmicCP15::CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) { + return boost::blank{}; +} + +boost::optional<Callback> DynarmicCP15::CompileLoadWords(bool two, bool long_transfer, + CoprocReg CRd, + boost::optional<u8> option) { + return boost::none; +} + +boost::optional<Callback> DynarmicCP15::CompileStoreWords(bool two, bool long_transfer, + CoprocReg CRd, + boost::optional<u8> option) { + return boost::none; +} diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.h b/src/core/arm/dynarmic/arm_dynarmic_cp15.h new file mode 100644 index 000000000..7fa54e14c --- /dev/null +++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.h @@ -0,0 +1,32 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include <dynarmic/coprocessor.h> +#include "common/common_types.h" + +struct ARMul_State; + +class DynarmicCP15 final : public Dynarmic::Coprocessor { +public: + explicit DynarmicCP15(const std::shared_ptr<ARMul_State>&); + ~DynarmicCP15() override; + + boost::optional<Callback> CompileInternalOperation(bool two, unsigned opc1, CoprocReg CRd, + CoprocReg CRn, CoprocReg CRm, + unsigned opc2) override; + CallbackOrAccessOneWord CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn, + CoprocReg CRm, unsigned opc2) override; + CallbackOrAccessTwoWords CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) override; + CallbackOrAccessOneWord CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm, + unsigned opc2) override; + CallbackOrAccessTwoWords CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) override; + boost::optional<Callback> CompileLoadWords(bool two, bool long_transfer, CoprocReg CRd, + boost::optional<u8> option) override; + boost::optional<Callback> CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd, + boost::optional<u8> option) override; + +private: + std::shared_ptr<ARMul_State> interpreter_state; +}; diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp index 67c45640a..273bc8167 100644 --- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp +++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp @@ -3928,13 +3928,13 @@ SXTB16_INST : { if (inst_cream->Rn == 15) { u32 lo = (u32)(s8)rm_val; u32 hi = (u32)(s8)(rm_val >> 16); - RD = (lo | (hi << 16)); + RD = (lo & 0xFFFF) | (hi << 16); } // SXTAB16 else { - u32 lo = (rn_val & 0xFFFF) + (u32)(s8)(rm_val & 0xFF); - u32 hi = ((rn_val >> 16) & 0xFFFF) + (u32)(s8)((rm_val >> 16) & 0xFF); - RD = (lo | (hi << 16)); + u32 lo = rn_val + (u32)(s8)(rm_val & 0xFF); + u32 hi = (rn_val >> 16) + (u32)(s8)((rm_val >> 16) & 0xFF); + RD = (lo & 0xFFFF) | (hi << 16); } } diff --git a/src/core/core.cpp b/src/core/core.cpp index 202cd332b..140ff6451 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -67,10 +67,6 @@ System::ResultStatus System::SingleStep() { } System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& filepath) { - if (app_loader) { - app_loader.reset(); - } - app_loader = Loader::GetLoader(filepath); if (!app_loader) { @@ -113,6 +109,10 @@ void System::PrepareReschedule() { reschedule_pending = true; } +PerfStats::Results System::GetAndResetPerfStats() { + return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs()); +} + void System::Reschedule() { if (!reschedule_pending) { return; @@ -123,10 +123,6 @@ void System::Reschedule() { } System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { - if (cpu_core) { - cpu_core.reset(); - } - Memory::Init(); if (Settings::values.use_cpu_jit) { @@ -148,6 +144,10 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { LOG_DEBUG(Core, "Initialized OK"); + // Reset counters and set time origin to current frame + GetAndResetPerfStats(); + perf_stats.BeginSystemFrame(); + return ResultStatus::Success; } @@ -159,7 +159,8 @@ void System::Shutdown() { Kernel::Shutdown(); HW::Shutdown(); CoreTiming::Shutdown(); - cpu_core.reset(); + cpu_core = nullptr; + app_loader = nullptr; LOG_DEBUG(Core, "Shutdown OK"); } diff --git a/src/core/core.h b/src/core/core.h index 17572a74f..6c9c936b5 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -6,9 +6,9 @@ #include <memory> #include <string> - #include "common/common_types.h" #include "core/memory.h" +#include "core/perf_stats.h" class EmuWindow; class ARM_Interface; @@ -83,6 +83,8 @@ public: /// Prepare the core emulation for a reschedule void PrepareReschedule(); + PerfStats::Results GetAndResetPerfStats(); + /** * Gets a reference to the emulated CPU. * @returns A reference to the emulated CPU. @@ -91,6 +93,9 @@ public: return *cpu_core; } + PerfStats perf_stats; + FrameLimiter frame_limiter; + private: /** * Initialize the emulated system. diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp index dd2fb167f..f454e7840 100644 --- a/src/core/file_sys/archive_extsavedata.cpp +++ b/src/core/file_sys/archive_extsavedata.cpp @@ -173,7 +173,7 @@ Path ConstructExtDataBinaryPath(u32 media_type, u32 high, u32 low) { ArchiveFactory_ExtSaveData::ArchiveFactory_ExtSaveData(const std::string& mount_location, bool shared) : shared(shared), mount_point(GetExtDataContainerPath(mount_location, shared)) { - LOG_INFO(Service_FS, "Directory %s set as base for ExtSaveData.", mount_point.c_str()); + LOG_DEBUG(Service_FS, "Directory %s set as base for ExtSaveData.", mount_point.c_str()); } bool ArchiveFactory_ExtSaveData::Initialize() { diff --git a/src/core/file_sys/archive_extsavedata.h b/src/core/file_sys/archive_extsavedata.h index 6a3431e94..f705ade1c 100644 --- a/src/core/file_sys/archive_extsavedata.h +++ b/src/core/file_sys/archive_extsavedata.h @@ -52,7 +52,7 @@ private: /** * This holds the full directory path for this archive, it is only set after a successful call - * to Open, this is formed as <base extsavedatapath>/<type>/<high>/<low>. + * to Open, this is formed as `<base extsavedatapath>/<type>/<high>/<low>`. * See GetExtSaveDataPath for the code that extracts this data from an archive path. */ std::string mount_point; diff --git a/src/core/file_sys/archive_romfs.cpp b/src/core/file_sys/archive_romfs.cpp deleted file mode 100644 index 6c99ca5b4..000000000 --- a/src/core/file_sys/archive_romfs.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <memory> -#include "common/common_types.h" -#include "common/logging/log.h" -#include "core/file_sys/archive_romfs.h" -#include "core/file_sys/ivfc_archive.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// FileSys namespace - -namespace FileSys { - -ArchiveFactory_RomFS::ArchiveFactory_RomFS(Loader::AppLoader& app_loader) { - // Load the RomFS from the app - if (Loader::ResultStatus::Success != app_loader.ReadRomFS(romfs_file, data_offset, data_size)) { - LOG_ERROR(Service_FS, "Unable to read RomFS!"); - } -} - -ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_RomFS::Open(const Path& path) { - auto archive = std::make_unique<IVFCArchive>(romfs_file, data_offset, data_size); - return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); -} - -ResultCode ArchiveFactory_RomFS::Format(const Path& path, - const FileSys::ArchiveFormatInfo& format_info) { - LOG_ERROR(Service_FS, "Attempted to format a RomFS archive."); - // TODO: Verify error code - return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported, - ErrorLevel::Permanent); -} - -ResultVal<ArchiveFormatInfo> ArchiveFactory_RomFS::GetFormatInfo(const Path& path) const { - // TODO(Subv): Implement - LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); - return ResultCode(-1); -} - -} // namespace FileSys diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp index 72ff05c65..679909d06 100644 --- a/src/core/file_sys/archive_sdmc.cpp +++ b/src/core/file_sys/archive_sdmc.cpp @@ -306,7 +306,7 @@ u64 SDMCArchive::GetFreeBytes() const { ArchiveFactory_SDMC::ArchiveFactory_SDMC(const std::string& sdmc_directory) : sdmc_directory(sdmc_directory) { - LOG_INFO(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str()); + LOG_DEBUG(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str()); } bool ArchiveFactory_SDMC::Initialize() { diff --git a/src/core/file_sys/archive_sdmcwriteonly.cpp b/src/core/file_sys/archive_sdmcwriteonly.cpp index 2aafc9b1d..244aef48a 100644 --- a/src/core/file_sys/archive_sdmcwriteonly.cpp +++ b/src/core/file_sys/archive_sdmcwriteonly.cpp @@ -32,7 +32,7 @@ ResultVal<std::unique_ptr<DirectoryBackend>> SDMCWriteOnlyArchive::OpenDirectory ArchiveFactory_SDMCWriteOnly::ArchiveFactory_SDMCWriteOnly(const std::string& mount_point) : sdmc_directory(mount_point) { - LOG_INFO(Service_FS, "Directory %s set as SDMCWriteOnly.", sdmc_directory.c_str()); + LOG_DEBUG(Service_FS, "Directory %s set as SDMCWriteOnly.", sdmc_directory.c_str()); } bool ArchiveFactory_SDMCWriteOnly::Initialize() { diff --git a/src/core/file_sys/archive_selfncch.cpp b/src/core/file_sys/archive_selfncch.cpp new file mode 100644 index 000000000..298a37a44 --- /dev/null +++ b/src/core/file_sys/archive_selfncch.cpp @@ -0,0 +1,257 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/swap.h" +#include "core/file_sys/archive_selfncch.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/ivfc_archive.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +namespace FileSys { + +enum class SelfNCCHFilePathType : u32 { + RomFS = 0, + Code = 1, // This is not supported by SelfNCCHArchive but by archive 0x2345678E + ExeFS = 2, + UpdateRomFS = 5, // This is presumably for accessing the RomFS of the update patch. +}; + +struct SelfNCCHFilePath { + u32_le type; + std::array<char, 8> exefs_filename; +}; +static_assert(sizeof(SelfNCCHFilePath) == 12, "NCCHFilePath has wrong size!"); + +// A read-only file created from a block of data. It only allows you to read the entire file at +// once, in a single read operation. +class ExeFSSectionFile final : public FileBackend { +public: + explicit ExeFSSectionFile(std::shared_ptr<std::vector<u8>> data_) : data(std::move(data_)) {} + + ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override { + if (offset != 0) { + LOG_ERROR(Service_FS, "offset must be zero!"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + if (length != data->size()) { + LOG_ERROR(Service_FS, "size must match the file size!"); + return ERROR_INCORRECT_EXEFS_READ_SIZE; + } + + std::memcpy(buffer, data->data(), data->size()); + return MakeResult<size_t>(data->size()); + } + + ResultVal<size_t> Write(u64 offset, size_t length, bool flush, + const u8* buffer) const override { + LOG_ERROR(Service_FS, "The file is read-only!"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + u64 GetSize() const override { + return data->size(); + } + + bool SetSize(u64 size) const override { + return false; + } + + bool Close() const override { + return true; + } + + void Flush() const override {} + +private: + std::shared_ptr<std::vector<u8>> data; +}; + +// SelfNCCHArchive represents the running application itself. From this archive the application can +// open RomFS and ExeFS, excluding the .code section. +class SelfNCCHArchive final : public ArchiveBackend { +public: + explicit SelfNCCHArchive(const NCCHData& ncch_data_) : ncch_data(ncch_data_) {} + + std::string GetName() const override { + return "SelfNCCHArchive"; + } + + ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode&) const override { + // Note: SelfNCCHArchive doesn't check the open mode. + + if (path.GetType() != LowPathType::Binary) { + LOG_ERROR(Service_FS, "Path need to be Binary"); + return ERROR_INVALID_PATH; + } + + std::vector<u8> binary = path.AsBinary(); + if (binary.size() != sizeof(SelfNCCHFilePath)) { + LOG_ERROR(Service_FS, "Wrong path size %zu", binary.size()); + return ERROR_INVALID_PATH; + } + + SelfNCCHFilePath file_path; + std::memcpy(&file_path, binary.data(), sizeof(SelfNCCHFilePath)); + + switch (static_cast<SelfNCCHFilePathType>(file_path.type)) { + case SelfNCCHFilePathType::UpdateRomFS: + LOG_WARNING(Service_FS, "(STUBBED) open update RomFS"); + return OpenRomFS(); + + case SelfNCCHFilePathType::RomFS: + return OpenRomFS(); + + case SelfNCCHFilePathType::Code: + LOG_ERROR(Service_FS, "Reading the code section is not supported!"); + return ERROR_COMMAND_NOT_ALLOWED; + + case SelfNCCHFilePathType::ExeFS: { + const auto& raw = file_path.exefs_filename; + auto end = std::find(raw.begin(), raw.end(), '\0'); + std::string filename(raw.begin(), end); + return OpenExeFS(filename); + } + default: + LOG_ERROR(Service_FS, "Unknown file type %u!", static_cast<u32>(file_path.type)); + return ERROR_INVALID_PATH; + } + } + + ResultCode DeleteFile(const Path& path) const override { + LOG_ERROR(Service_FS, "Unsupported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override { + LOG_ERROR(Service_FS, "Unsupported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + ResultCode DeleteDirectory(const Path& path) const override { + LOG_ERROR(Service_FS, "Unsupported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + ResultCode DeleteDirectoryRecursively(const Path& path) const override { + LOG_ERROR(Service_FS, "Unsupported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + ResultCode CreateFile(const Path& path, u64 size) const override { + LOG_ERROR(Service_FS, "Unsupported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + ResultCode CreateDirectory(const Path& path) const override { + LOG_ERROR(Service_FS, "Unsupported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override { + LOG_ERROR(Service_FS, "Unsupported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override { + LOG_ERROR(Service_FS, "Unsupported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + u64 GetFreeBytes() const override { + return 0; + } + +private: + ResultVal<std::unique_ptr<FileBackend>> OpenRomFS() const { + if (ncch_data.romfs_file) { + return MakeResult<std::unique_ptr<FileBackend>>(std::make_unique<IVFCFile>( + ncch_data.romfs_file, ncch_data.romfs_offset, ncch_data.romfs_size)); + } else { + LOG_INFO(Service_FS, "Unable to read RomFS"); + return ERROR_ROMFS_NOT_FOUND; + } + } + + ResultVal<std::unique_ptr<FileBackend>> OpenExeFS(const std::string& filename) const { + if (filename == "icon") { + if (ncch_data.icon) { + return MakeResult<std::unique_ptr<FileBackend>>( + std::make_unique<ExeFSSectionFile>(ncch_data.icon)); + } + + LOG_WARNING(Service_FS, "Unable to read icon"); + return ERROR_EXEFS_SECTION_NOT_FOUND; + } + + if (filename == "logo") { + if (ncch_data.logo) { + return MakeResult<std::unique_ptr<FileBackend>>( + std::make_unique<ExeFSSectionFile>(ncch_data.logo)); + } + + LOG_WARNING(Service_FS, "Unable to read logo"); + return ERROR_EXEFS_SECTION_NOT_FOUND; + } + + if (filename == "banner") { + if (ncch_data.banner) { + return MakeResult<std::unique_ptr<FileBackend>>( + std::make_unique<ExeFSSectionFile>(ncch_data.banner)); + } + + LOG_WARNING(Service_FS, "Unable to read banner"); + return ERROR_EXEFS_SECTION_NOT_FOUND; + } + + LOG_ERROR(Service_FS, "Unknown ExeFS section %s!", filename.c_str()); + return ERROR_INVALID_PATH; + } + + NCCHData ncch_data; +}; + +ArchiveFactory_SelfNCCH::ArchiveFactory_SelfNCCH(Loader::AppLoader& app_loader) { + std::shared_ptr<FileUtil::IOFile> romfs_file_; + if (Loader::ResultStatus::Success == + app_loader.ReadRomFS(romfs_file_, ncch_data.romfs_offset, ncch_data.romfs_size)) { + + ncch_data.romfs_file = std::move(romfs_file_); + } + + std::vector<u8> buffer; + + if (Loader::ResultStatus::Success == app_loader.ReadIcon(buffer)) + ncch_data.icon = std::make_shared<std::vector<u8>>(std::move(buffer)); + + buffer.clear(); + if (Loader::ResultStatus::Success == app_loader.ReadLogo(buffer)) + ncch_data.logo = std::make_shared<std::vector<u8>>(std::move(buffer)); + + buffer.clear(); + if (Loader::ResultStatus::Success == app_loader.ReadBanner(buffer)) + ncch_data.banner = std::make_shared<std::vector<u8>>(std::move(buffer)); +} + +ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SelfNCCH::Open(const Path& path) { + auto archive = std::make_unique<SelfNCCHArchive>(ncch_data); + return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); +} + +ResultCode ArchiveFactory_SelfNCCH::Format(const Path&, const FileSys::ArchiveFormatInfo&) { + LOG_ERROR(Service_FS, "Attempted to format a SelfNCCH archive."); + return ERROR_INVALID_PATH; +} + +ResultVal<ArchiveFormatInfo> ArchiveFactory_SelfNCCH::GetFormatInfo(const Path&) const { + LOG_ERROR(Service_FS, "Attempted to get format info of a SelfNCCH archive"); + return ERROR_INVALID_PATH; +} + +} // namespace FileSys diff --git a/src/core/file_sys/archive_romfs.h b/src/core/file_sys/archive_selfncch.h index 1eaf99b54..f1b971296 100644 --- a/src/core/file_sys/archive_romfs.h +++ b/src/core/file_sys/archive_selfncch.h @@ -1,4 +1,4 @@ -// Copyright 2014 Citra Emulator Project +// Copyright 2017 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -17,22 +17,29 @@ namespace FileSys { -/// File system interface to the RomFS archive -class ArchiveFactory_RomFS final : public ArchiveFactory { +struct NCCHData { + std::shared_ptr<std::vector<u8>> icon; + std::shared_ptr<std::vector<u8>> logo; + std::shared_ptr<std::vector<u8>> banner; + std::shared_ptr<FileUtil::IOFile> romfs_file; + u64 romfs_offset = 0; + u64 romfs_size = 0; +}; + +/// File system interface to the SelfNCCH archive +class ArchiveFactory_SelfNCCH final : public ArchiveFactory { public: - explicit ArchiveFactory_RomFS(Loader::AppLoader& app_loader); + explicit ArchiveFactory_SelfNCCH(Loader::AppLoader& app_loader); std::string GetName() const override { - return "RomFS"; + return "SelfNCCH"; } ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override; ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) override; ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override; private: - std::shared_ptr<FileUtil::IOFile> romfs_file; - u64 data_offset; - u64 data_size; + NCCHData ncch_data; }; } // namespace FileSys diff --git a/src/core/file_sys/archive_source_sd_savedata.cpp b/src/core/file_sys/archive_source_sd_savedata.cpp index e01357891..f31a68038 100644 --- a/src/core/file_sys/archive_source_sd_savedata.cpp +++ b/src/core/file_sys/archive_source_sd_savedata.cpp @@ -39,7 +39,7 @@ std::string GetSaveDataMetadataPath(const std::string& mount_location, u64 progr ArchiveSource_SDSaveData::ArchiveSource_SDSaveData(const std::string& sdmc_directory) : mount_point(GetSaveDataContainerPath(sdmc_directory)) { - LOG_INFO(Service_FS, "Directory %s set as SaveData.", mount_point.c_str()); + LOG_DEBUG(Service_FS, "Directory %s set as SaveData.", mount_point.c_str()); } ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveSource_SDSaveData::Open(u64 program_id) { diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h index 4d5f62b08..9fc8d753b 100644 --- a/src/core/file_sys/errors.h +++ b/src/core/file_sys/errors.h @@ -39,5 +39,15 @@ const ResultCode ERROR_DIRECTORY_NOT_EMPTY(ErrorDescription::FS_DirectoryNotEmpt 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 FileSys diff --git a/src/core/frontend/camera/factory.h b/src/core/frontend/camera/factory.h index d68be16e5..f46413fa7 100644 --- a/src/core/frontend/camera/factory.h +++ b/src/core/frontend/camera/factory.h @@ -16,8 +16,8 @@ public: /** * Creates a camera object based on the configuration string. - * @params config Configuration string to create the camera. The implementation can decide the - * meaning of this string. + * @param config Configuration string to create the camera. The implementation can decide the + * meaning of this string. * @returns a unique_ptr to the created camera object. */ virtual std::unique_ptr<CameraInterface> Create(const std::string& config) const = 0; diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 4f0f786ce..5fdb3a7e8 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -5,35 +5,11 @@ #include <algorithm> #include <cmath> #include "common/assert.h" -#include "common/profiler_reporting.h" +#include "core/core.h" #include "core/frontend/emu_window.h" -#include "core/frontend/key_map.h" +#include "core/settings.h" #include "video_core/video_core.h" -void EmuWindow::ButtonPressed(Service::HID::PadState pad) { - pad_state.hex |= pad.hex; -} - -void EmuWindow::ButtonReleased(Service::HID::PadState pad) { - pad_state.hex &= ~pad.hex; -} - -void EmuWindow::CirclePadUpdated(float x, float y) { - constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position - - // Make sure the coordinates are in the unit circle, - // otherwise normalize it. - float r = x * x + y * y; - if (r > 1) { - r = std::sqrt(r); - x /= r; - y /= r; - } - - circle_pad_x = static_cast<s16>(x * MAX_CIRCLEPAD_POS); - circle_pad_y = static_cast<s16>(y * MAX_CIRCLEPAD_POS); -} - /** * Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout * @param layout FramebufferLayout object describing the framebuffer size and screen positions @@ -70,14 +46,12 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) { (framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top); touch_pressed = true; - pad_state.touch.Assign(1); } void EmuWindow::TouchReleased() { touch_pressed = false; touch_x = 0; touch_y = 0; - pad_state.touch.Assign(0); } void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) { @@ -106,8 +80,7 @@ void EmuWindow::AccelerometerChanged(float x, float y, float z) { void EmuWindow::GyroscopeChanged(float x, float y, float z) { constexpr float FULL_FPS = 60; float coef = GetGyroscopeRawToDpsCoefficient(); - float stretch = - FULL_FPS / Common::Profiling::GetTimingResultsAggregator()->GetAggregatedResults().fps; + float stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale(); std::lock_guard<std::mutex> lock(gyro_mutex); gyro_x = static_cast<s16>(x * coef * stretch); gyro_y = static_cast<s16>(y * coef * stretch); @@ -116,17 +89,21 @@ void EmuWindow::GyroscopeChanged(float x, float y, float z) { void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) { Layout::FramebufferLayout layout; - switch (Settings::values.layout_option) { - case Settings::LayoutOption::SingleScreen: - layout = Layout::SingleFrameLayout(width, height, Settings::values.swap_screen); - break; - case Settings::LayoutOption::LargeScreen: - layout = Layout::LargeFrameLayout(width, height, Settings::values.swap_screen); - break; - case Settings::LayoutOption::Default: - default: - layout = Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen); - break; + if (Settings::values.custom_layout == true) { + layout = Layout::CustomFrameLayout(width, height); + } else { + switch (Settings::values.layout_option) { + case Settings::LayoutOption::SingleScreen: + layout = Layout::SingleFrameLayout(width, height, Settings::values.swap_screen); + break; + case Settings::LayoutOption::LargeScreen: + layout = Layout::LargeFrameLayout(width, height, Settings::values.swap_screen); + break; + case Settings::LayoutOption::Default: + default: + layout = Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen); + break; + } } NotifyFramebufferLayoutChanged(layout); } diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 1ba64c92b..36f2667fa 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -10,7 +10,6 @@ #include "common/common_types.h" #include "common/framebuffer_layout.h" #include "common/math_util.h" -#include "core/hle/service/hid/hid.h" /** * Abstraction class used to provide an interface between emulation code and the frontend @@ -52,30 +51,6 @@ public: /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread virtual void DoneCurrent() = 0; - virtual void ReloadSetKeymaps() = 0; - - /** - * Signals a button press action to the HID module. - * @param pad_state indicates which button to press - * @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad. - */ - void ButtonPressed(Service::HID::PadState pad_state); - - /** - * Signals a button release action to the HID module. - * @param pad_state indicates which button to press - * @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad. - */ - void ButtonReleased(Service::HID::PadState pad_state); - - /** - * Signals a circle pad change action to the HID module. - * @param x new x-coordinate of the circle pad, in the range [-1.0, 1.0] - * @param y new y-coordinate of the circle pad, in the range [-1.0, 1.0] - * @note the coordinates will be normalized if the radius is larger than 1 - */ - void CirclePadUpdated(float x, float y); - /** * Signal that a touch pressed event has occurred (e.g. mouse click pressed) * @param framebuffer_x Framebuffer x-coordinate that was pressed @@ -115,27 +90,6 @@ public: void GyroscopeChanged(float x, float y, float z); /** - * Gets the current pad state (which buttons are pressed). - * @note This should be called by the core emu thread to get a state set by the window thread. - * @note This doesn't include analog input like circle pad direction - * @todo Fix this function to be thread-safe. - * @return PadState object indicating the current pad state - */ - Service::HID::PadState GetPadState() const { - return pad_state; - } - - /** - * Gets the current circle pad state. - * @note This should be called by the core emu thread to get a state set by the window thread. - * @todo Fix this function to be thread-safe. - * @return std::tuple of (x, y), where `x` and `y` are the circle pad coordinates - */ - std::tuple<s16, s16> GetCirclePadState() const { - return std::make_tuple(circle_pad_x, circle_pad_y); - } - - /** * Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed). * @note This should be called by the core emu thread to get a state set by the window thread. * @todo Fix this function to be thread-safe. @@ -230,11 +184,8 @@ protected: // TODO: Find a better place to set this. config.min_client_area_size = std::make_pair(400u, 480u); active_config = config; - pad_state.hex = 0; touch_x = 0; touch_y = 0; - circle_pad_x = 0; - circle_pad_y = 0; touch_pressed = false; accel_x = 0; accel_y = -512; @@ -304,9 +255,6 @@ private: u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320) u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240) - s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156) - s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156) - std::mutex accel_mutex; s16 accel_x; ///< Accelerometer X-axis value in native 3DS units s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units @@ -321,6 +269,4 @@ private: * Clip the provided coordinates to be inside the touchscreen area. */ std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y); - - Service::HID::PadState pad_state; }; diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h new file mode 100644 index 000000000..0a5713dc0 --- /dev/null +++ b/src/core/frontend/input.h @@ -0,0 +1,110 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> +#include <tuple> +#include <unordered_map> +#include <utility> +#include "common/logging/log.h" +#include "common/param_package.h" + +namespace Input { + +/// An abstract class template for an input device (a button, an analog input, etc.). +template <typename StatusType> +class InputDevice { +public: + virtual ~InputDevice() = default; + virtual StatusType GetStatus() const { + return {}; + } +}; + +/// An abstract class template for a factory that can create input devices. +template <typename InputDeviceType> +class Factory { +public: + virtual ~Factory() = default; + virtual std::unique_ptr<InputDeviceType> Create(const Common::ParamPackage&) = 0; +}; + +namespace Impl { + +template <typename InputDeviceType> +using FactoryListType = std::unordered_map<std::string, std::shared_ptr<Factory<InputDeviceType>>>; + +template <typename InputDeviceType> +struct FactoryList { + static FactoryListType<InputDeviceType> list; +}; + +template <typename InputDeviceType> +FactoryListType<InputDeviceType> FactoryList<InputDeviceType>::list; + +} // namespace Impl + +/** + * Registers an input device factory. + * @tparam InputDeviceType the type of input devices the factory can create + * @param name the name of the factory. Will be used to match the "engine" parameter when creating + * a device + * @param factory the factory object to register + */ +template <typename InputDeviceType> +void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDeviceType>> factory) { + auto pair = std::make_pair(name, std::move(factory)); + if (!Impl::FactoryList<InputDeviceType>::list.insert(std::move(pair)).second) { + LOG_ERROR(Input, "Factory %s already registered", name.c_str()); + } +} + +/** + * Unregisters an input device factory. + * @tparam InputDeviceType the type of input devices the factory can create + * @param name the name of the factory to unregister + */ +template <typename InputDeviceType> +void UnregisterFactory(const std::string& name) { + if (Impl::FactoryList<InputDeviceType>::list.erase(name) == 0) { + LOG_ERROR(Input, "Factory %s not registered", name.c_str()); + } +} + +/** + * Create an input device from given paramters. + * @tparam InputDeviceType the type of input devices to create + * @param params a serialized ParamPackage string contains all parameters for creating the device + */ +template <typename InputDeviceType> +std::unique_ptr<InputDeviceType> CreateDevice(const std::string& params) { + const Common::ParamPackage package(params); + const std::string engine = package.Get("engine", "null"); + const auto& factory_list = Impl::FactoryList<InputDeviceType>::list; + const auto pair = factory_list.find(engine); + if (pair == factory_list.end()) { + if (engine != "null") { + LOG_ERROR(Input, "Unknown engine name: %s", engine.c_str()); + } + return std::make_unique<InputDeviceType>(); + } + return pair->second->Create(package); +} + +/** + * A button device is an input device that returns bool as status. + * true for pressed; false for released. + */ +using ButtonDevice = InputDevice<bool>; + +/** + * An analog device is an input device that returns a tuple of x and y coordinates as status. The + * coordinates are within the unit circle. x+ is defined as right direction, and y+ is defined as up + * direction + */ +using AnalogDevice = InputDevice<std::tuple<float, float>>; + +} // namespace Input diff --git a/src/core/frontend/key_map.cpp b/src/core/frontend/key_map.cpp deleted file mode 100644 index 15f0e079c..000000000 --- a/src/core/frontend/key_map.cpp +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <map> -#include "core/frontend/emu_window.h" -#include "core/frontend/key_map.h" - -namespace KeyMap { - -// TODO (wwylele): currently we treat c-stick as four direction buttons -// and map it directly to EmuWindow::ButtonPressed. -// It should go the analog input way like circle pad does. -const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets = {{ - Service::HID::PAD_A, - Service::HID::PAD_B, - Service::HID::PAD_X, - Service::HID::PAD_Y, - Service::HID::PAD_L, - Service::HID::PAD_R, - Service::HID::PAD_ZL, - Service::HID::PAD_ZR, - Service::HID::PAD_START, - Service::HID::PAD_SELECT, - Service::HID::PAD_NONE, - Service::HID::PAD_UP, - Service::HID::PAD_DOWN, - Service::HID::PAD_LEFT, - Service::HID::PAD_RIGHT, - Service::HID::PAD_C_UP, - Service::HID::PAD_C_DOWN, - Service::HID::PAD_C_LEFT, - Service::HID::PAD_C_RIGHT, - - IndirectTarget::CirclePadUp, - IndirectTarget::CirclePadDown, - IndirectTarget::CirclePadLeft, - IndirectTarget::CirclePadRight, - IndirectTarget::CirclePadModifier, -}}; - -static std::map<HostDeviceKey, KeyTarget> key_map; -static int next_device_id = 0; - -static bool circle_pad_up = false; -static bool circle_pad_down = false; -static bool circle_pad_left = false; -static bool circle_pad_right = false; -static bool circle_pad_modifier = false; - -static void UpdateCirclePad(EmuWindow& emu_window) { - constexpr float SQRT_HALF = 0.707106781f; - int x = 0, y = 0; - - if (circle_pad_right) - ++x; - if (circle_pad_left) - --x; - if (circle_pad_up) - ++y; - if (circle_pad_down) - --y; - - float modifier = circle_pad_modifier ? Settings::values.pad_circle_modifier_scale : 1.0f; - emu_window.CirclePadUpdated(x * modifier * (y == 0 ? 1.0f : SQRT_HALF), - y * modifier * (x == 0 ? 1.0f : SQRT_HALF)); -} - -int NewDeviceId() { - return next_device_id++; -} - -void SetKeyMapping(HostDeviceKey key, KeyTarget target) { - key_map[key] = target; -} - -void ClearKeyMapping(int device_id) { - auto iter = key_map.begin(); - while (iter != key_map.end()) { - if (iter->first.device_id == device_id) - key_map.erase(iter++); - else - ++iter; - } -} - -void PressKey(EmuWindow& emu_window, HostDeviceKey key) { - auto target = key_map.find(key); - if (target == key_map.end()) - return; - - if (target->second.direct) { - emu_window.ButtonPressed({{target->second.target.direct_target_hex}}); - } else { - switch (target->second.target.indirect_target) { - case IndirectTarget::CirclePadUp: - circle_pad_up = true; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadDown: - circle_pad_down = true; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadLeft: - circle_pad_left = true; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadRight: - circle_pad_right = true; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadModifier: - circle_pad_modifier = true; - UpdateCirclePad(emu_window); - break; - } - } -} - -void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key) { - auto target = key_map.find(key); - if (target == key_map.end()) - return; - - if (target->second.direct) { - emu_window.ButtonReleased({{target->second.target.direct_target_hex}}); - } else { - switch (target->second.target.indirect_target) { - case IndirectTarget::CirclePadUp: - circle_pad_up = false; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadDown: - circle_pad_down = false; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadLeft: - circle_pad_left = false; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadRight: - circle_pad_right = false; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadModifier: - circle_pad_modifier = false; - UpdateCirclePad(emu_window); - break; - } - } -} -} diff --git a/src/core/frontend/key_map.h b/src/core/frontend/key_map.h deleted file mode 100644 index 040794578..000000000 --- a/src/core/frontend/key_map.h +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <array> -#include <tuple> -#include "core/hle/service/hid/hid.h" - -class EmuWindow; - -namespace KeyMap { - -/** - * Represents key mapping targets that are not real 3DS buttons. - * They will be handled by KeyMap and translated to 3DS input. - */ -enum class IndirectTarget { - CirclePadUp, - CirclePadDown, - CirclePadLeft, - CirclePadRight, - CirclePadModifier, -}; - -/** - * Represents a key mapping target. It can be a PadState that represents real 3DS buttons, - * or an IndirectTarget. - */ -struct KeyTarget { - bool direct; - union { - u32 direct_target_hex; - IndirectTarget indirect_target; - } target; - - KeyTarget() : direct(true) { - target.direct_target_hex = 0; - } - - KeyTarget(Service::HID::PadState pad) : direct(true) { - target.direct_target_hex = pad.hex; - } - - KeyTarget(IndirectTarget i) : direct(false) { - target.indirect_target = i; - } -}; - -/** - * Represents a key for a specific host device. - */ -struct HostDeviceKey { - int key_code; - int device_id; ///< Uniquely identifies a host device - - bool operator<(const HostDeviceKey& other) const { - return std::tie(key_code, device_id) < std::tie(other.key_code, other.device_id); - } - - bool operator==(const HostDeviceKey& other) const { - return std::tie(key_code, device_id) == std::tie(other.key_code, other.device_id); - } -}; - -extern const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets; - -/** - * Generates a new device id, which uniquely identifies a host device within KeyMap. - */ -int NewDeviceId(); - -/** - * Maps a device-specific key to a target (a PadState or an IndirectTarget). - */ -void SetKeyMapping(HostDeviceKey key, KeyTarget target); - -/** - * Clears all key mappings belonging to one device. - */ -void ClearKeyMapping(int device_id); - -/** - * Maps a key press action and call the corresponding function in EmuWindow - */ -void PressKey(EmuWindow& emu_window, HostDeviceKey key); - -/** - * Maps a key release action and call the corresponding function in EmuWindow - */ -void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key); -} diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index 5cf45ada5..123fe7cd4 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -230,6 +230,7 @@ static void GdbHexToMem(u8* dest, const u8* src, size_t len) { * Convert a u32 into a gdb-formatted hex string. * * @param dest Pointer to buffer to store output hex string characters. + * @param v Value to convert. */ static void IntToGdbHex(u8* dest, u32 v) { for (int i = 0; i < 8; i += 2) { diff --git a/src/core/hle/applets/applet.cpp b/src/core/hle/applets/applet.cpp index 645b2d5fe..9c43ed2fd 100644 --- a/src/core/hle/applets/applet.cpp +++ b/src/core/hle/applets/applet.cpp @@ -12,6 +12,7 @@ #include "core/hle/applets/applet.h" #include "core/hle/applets/erreula.h" #include "core/hle/applets/mii_selector.h" +#include "core/hle/applets/mint.h" #include "core/hle/applets/swkbd.h" #include "core/hle/result.h" #include "core/hle/service/apt/apt.h" @@ -56,6 +57,10 @@ ResultCode Applet::Create(Service::APT::AppletId id) { case Service::APT::AppletId::Error2: applets[id] = std::make_shared<ErrEula>(id); break; + case Service::APT::AppletId::Mint: + case Service::APT::AppletId::Mint2: + applets[id] = std::make_shared<Mint>(id); + break; default: LOG_ERROR(Service_APT, "Could not create applet %u", id); // TODO(Subv): Find the right error code diff --git a/src/core/hle/applets/mint.cpp b/src/core/hle/applets/mint.cpp new file mode 100644 index 000000000..31a79ea17 --- /dev/null +++ b/src/core/hle/applets/mint.cpp @@ -0,0 +1,72 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/string_util.h" +#include "core/hle/applets/mint.h" +#include "core/hle/service/apt/apt.h" + +namespace HLE { +namespace Applets { + +ResultCode Mint::ReceiveParameter(const Service::APT::MessageParameter& parameter) { + if (parameter.signal != static_cast<u32>(Service::APT::SignalType::Request)) { + LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal); + UNIMPLEMENTED(); + // TODO(Subv): Find the right error code + return ResultCode(-1); + } + + // The Request message contains a buffer with the size of the framebuffer shared + // memory. + // Create the SharedMemory that will hold the framebuffer data + Service::APT::CaptureBufferInfo capture_info; + ASSERT(sizeof(capture_info) == parameter.buffer.size()); + + memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info)); + + // TODO: allocated memory never released + using Kernel::MemoryPermission; + // Allocate a heap block of the required size for this applet. + heap_memory = std::make_shared<std::vector<u8>>(capture_info.size); + // Create a SharedMemory that directly points to this heap block. + framebuffer_memory = Kernel::SharedMemory::CreateForApplet( + heap_memory, 0, heap_memory->size(), MemoryPermission::ReadWrite, + MemoryPermission::ReadWrite, "Mint Memory"); + + // Send the response message with the newly created SharedMemory + Service::APT::MessageParameter result; + result.signal = static_cast<u32>(Service::APT::SignalType::Response); + result.buffer.clear(); + result.destination_id = static_cast<u32>(Service::APT::AppletId::Application); + result.sender_id = static_cast<u32>(id); + result.object = framebuffer_memory; + + Service::APT::SendParameter(result); + return RESULT_SUCCESS; +} + +ResultCode Mint::StartImpl(const Service::APT::AppletStartupParameter& parameter) { + is_running = true; + + // TODO(Subv): Set the expected fields in the response buffer before resending it to the + // application. + // TODO(Subv): Reverse the parameter format for the Mint applet + + // Let the application know that we're closing + Service::APT::MessageParameter message; + message.buffer.resize(parameter.buffer.size()); + std::fill(message.buffer.begin(), message.buffer.end(), 0); + message.signal = static_cast<u32>(Service::APT::SignalType::WakeupByExit); + message.destination_id = static_cast<u32>(Service::APT::AppletId::Application); + message.sender_id = static_cast<u32>(id); + Service::APT::SendParameter(message); + + is_running = false; + return RESULT_SUCCESS; +} + +void Mint::Update() {} + +} // namespace Applets +} // namespace HLE diff --git a/src/core/hle/applets/mint.h b/src/core/hle/applets/mint.h new file mode 100644 index 000000000..d23dc40f9 --- /dev/null +++ b/src/core/hle/applets/mint.h @@ -0,0 +1,29 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/applets/applet.h" +#include "core/hle/kernel/shared_memory.h" + +namespace HLE { +namespace Applets { + +class Mint final : public Applet { +public: + explicit Mint(Service::APT::AppletId id) : Applet(id) {} + + ResultCode ReceiveParameter(const Service::APT::MessageParameter& parameter) override; + ResultCode StartImpl(const Service::APT::AppletStartupParameter& parameter) override; + void Update() override; + +private: + /// This SharedMemory will be created when we receive the Request message. + /// It holds the framebuffer info retrieved by the application with + /// GSPGPU::ImportDisplayCaptureInfo + Kernel::SharedPtr<Kernel::SharedMemory> framebuffer_memory; +}; + +} // namespace Applets +} // namespace HLE diff --git a/src/core/hle/config_mem.cpp b/src/core/hle/config_mem.cpp index ccd73cfcb..e386ccdc6 100644 --- a/src/core/hle/config_mem.cpp +++ b/src/core/hle/config_mem.cpp @@ -14,15 +14,18 @@ ConfigMemDef config_mem; void Init() { std::memset(&config_mem, 0, sizeof(config_mem)); - config_mem.update_flag = 0; // No update + // Values extracted from firmware 11.2.0-35E + config_mem.kernel_version_min = 0x34; + config_mem.kernel_version_maj = 0x2; + config_mem.ns_tid = 0x0004013000008002; config_mem.sys_core_ver = 0x2; config_mem.unit_info = 0x1; // Bit 0 set for Retail - config_mem.prev_firm = 0; - config_mem.firm_unk = 0; - config_mem.firm_version_rev = 0; - config_mem.firm_version_min = 0x40; + config_mem.prev_firm = 0x1; + config_mem.ctr_sdk_ver = 0x0000F297; + config_mem.firm_version_min = 0x34; config_mem.firm_version_maj = 0x2; config_mem.firm_sys_core_ver = 0x2; + config_mem.firm_ctr_sdk_ver = 0x0000F297; } } // namespace diff --git a/src/core/hle/function_wrappers.h b/src/core/hle/function_wrappers.h index 7875971ce..f6eb900f0 100644 --- a/src/core/hle/function_wrappers.h +++ b/src/core/hle/function_wrappers.h @@ -256,9 +256,9 @@ void Wrap() { func(((s64)PARAM(1) << 32) | PARAM(0)); } -template <void func(const char*)> +template <void func(const char*, int len)> void Wrap() { - func((char*)Memory::GetPointer(PARAM(0))); + func((char*)Memory::GetPointer(PARAM(0)), PARAM(1)); } template <void func(u8)> diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h index 4e094faa7..3a5d481a5 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h @@ -10,7 +10,8 @@ namespace Kernel { -static const int kCommandHeaderOffset = 0x80; ///< Offset into command buffer of header +/// Offset into command buffer of header +static const int kCommandHeaderOffset = 0x80; /** * Returns a pointer to the command buffer in the current thread's TLS @@ -18,12 +19,26 @@ static const int kCommandHeaderOffset = 0x80; ///< Offset into command buffer of * the thread's TLS to an intermediate buffer in kernel memory, and then copied again to * the service handler process' memory. * @param offset Optional offset into command buffer + * @param offset Optional offset into command buffer (in bytes) * @return Pointer to command buffer */ inline u32* GetCommandBuffer(const int offset = 0) { return (u32*)Memory::GetPointer(GetCurrentThread()->GetTLSAddress() + kCommandHeaderOffset + offset); } + +/// Offset into static buffers, relative to command buffer header +static const int kStaticBuffersOffset = 0x100; + +/** + * Returns a pointer to the static buffers area in the current thread's TLS + * TODO(Subv): cf. GetCommandBuffer + * @param offset Optional offset into static buffers area (in bytes) + * @return Pointer to static buffers area + */ +inline u32* GetStaticBuffers(const int offset = 0) { + return GetCommandBuffer(kStaticBuffersOffset + offset); +} } namespace IPC { @@ -40,10 +55,17 @@ enum DescriptorType : u32 { CallingPid = 0x20, }; +union Header { + u32 raw; + BitField<0, 6, u32> translate_params_size; + BitField<6, 6, u32> normal_params_size; + BitField<16, 16, u32> command_id; +}; + /** * @brief Creates a command header to be used for IPC * @param command_id ID of the command to create a header for. - * @param normal_params Size of the normal parameters in words. Up to 63. + * @param normal_params_size Size of the normal parameters in words. Up to 63. * @param translate_params_size Size of the translate parameters in words. Up to 63. * @return The created IPC header. * @@ -51,24 +73,16 @@ enum DescriptorType : u32 { * through modifications and checks by the kernel. * The translate parameters are described by headers generated with the IPC::*Desc functions. * - * @note While #normal_params is equivalent to the number of normal parameters, - * #translate_params_size includes the size occupied by the translate parameters headers. + * @note While @p normal_params_size is equivalent to the number of normal parameters, + * @p translate_params_size includes the size occupied by the translate parameters headers. */ -constexpr u32 MakeHeader(u16 command_id, unsigned int normal_params, - unsigned int translate_params_size) { - return (u32(command_id) << 16) | ((u32(normal_params) & 0x3F) << 6) | - (u32(translate_params_size) & 0x3F); -} - -union Header { - u32 raw; - BitField<0, 6, u32> translate_params_size; - BitField<6, 6, u32> normal_params; - BitField<16, 16, u32> command_id; -}; - -inline Header ParseHeader(u32 header) { - return {header}; +inline u32 MakeHeader(u16 command_id, unsigned int normal_params_size, + unsigned int translate_params_size) { + Header header{}; + header.command_id.Assign(command_id); + header.normal_params_size.Assign(normal_params_size); + header.translate_params_size.Assign(translate_params_size); + return header.raw; } constexpr u32 MoveHandleDesc(u32 num_handles = 1) { @@ -83,7 +97,7 @@ constexpr u32 CallingPidDesc() { return CallingPid; } -constexpr bool isHandleDescriptor(u32 descriptor) { +constexpr bool IsHandleDescriptor(u32 descriptor) { return (descriptor & 0xF) == 0x0; } @@ -91,18 +105,19 @@ constexpr u32 HandleNumberFromDesc(u32 handle_descriptor) { return (handle_descriptor >> 26) + 1; } -constexpr u32 StaticBufferDesc(u32 size, u8 buffer_id) { - return StaticBuffer | (size << 14) | ((buffer_id & 0xF) << 10); -} - union StaticBufferDescInfo { u32 raw; + BitField<0, 4, u32> descriptor_type; BitField<10, 4, u32> buffer_id; BitField<14, 18, u32> size; }; -inline StaticBufferDescInfo ParseStaticBufferDesc(const u32 desc) { - return {desc}; +inline u32 StaticBufferDesc(u32 size, u8 buffer_id) { + StaticBufferDescInfo info{}; + info.descriptor_type.Assign(StaticBuffer); + info.buffer_id.Assign(buffer_id); + info.size.Assign(size); + return info.raw; } /** @@ -122,29 +137,30 @@ inline u32 PXIBufferDesc(u32 size, unsigned buffer_id, bool is_read_only) { return type | (size << 8) | ((buffer_id & 0xF) << 4); } -enum MappedBufferPermissions { +enum MappedBufferPermissions : u32 { R = 1, W = 2, RW = R | W, }; -constexpr u32 MappedBufferDesc(u32 size, MappedBufferPermissions perms) { - return MappedBuffer | (size << 4) | (u32(perms) << 1); -} - union MappedBufferDescInfo { u32 raw; - BitField<4, 28, u32> size; + BitField<0, 4, u32> flags; BitField<1, 2, MappedBufferPermissions> perms; + BitField<4, 28, u32> size; }; -inline MappedBufferDescInfo ParseMappedBufferDesc(const u32 desc) { - return {desc}; +inline u32 MappedBufferDesc(u32 size, MappedBufferPermissions perms) { + MappedBufferDescInfo info{}; + info.flags.Assign(MappedBuffer); + info.perms.Assign(perms); + info.size.Assign(size); + return info.raw; } inline DescriptorType GetDescriptorType(u32 descriptor) { // Note: Those checks must be done in this order - if (isHandleDescriptor(descriptor)) + if (IsHandleDescriptor(descriptor)) return (DescriptorType)(descriptor & 0x30); // handle the fact that the following descriptors can have rights diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h new file mode 100644 index 000000000..06c4c5a85 --- /dev/null +++ b/src/core/hle/ipc_helpers.h @@ -0,0 +1,353 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include "core/hle/ipc.h" +#include "core/hle/kernel/kernel.h" + +namespace IPC { + +class RequestHelperBase { +protected: + u32* cmdbuf; + ptrdiff_t index = 1; + Header header; + +public: + RequestHelperBase(u32* command_buffer, Header command_header) + : cmdbuf(command_buffer), header(command_header) {} + + /// Returns the total size of the request in words + size_t TotalSize() const { + return 1 /* command header */ + header.normal_params_size + header.translate_params_size; + } + + void ValidateHeader() { + DEBUG_ASSERT_MSG(index == TotalSize(), "Operations do not match the header (cmd 0x%x)", + header.raw); + } + + void Skip(unsigned size_in_words, bool set_to_null) { + if (set_to_null) + memset(cmdbuf + index, 0, size_in_words * sizeof(u32)); + index += size_in_words; + } + + /** + * @brief Retrieves the address of a static buffer, used when a buffer is needed for output + * @param buffer_id The index of the static buffer + * @param data_size If non-null, will store the size of the buffer + */ + VAddr PeekStaticBuffer(u8 buffer_id, size_t* data_size = nullptr) const { + u32* static_buffer = cmdbuf + Kernel::kStaticBuffersOffset / sizeof(u32) + buffer_id * 2; + if (data_size) + *data_size = StaticBufferDescInfo{static_buffer[0]}.size; + return static_buffer[1]; + } +}; + +class RequestBuilder : public RequestHelperBase { +public: + RequestBuilder(u32* command_buffer, Header command_header) + : RequestHelperBase(command_buffer, command_header) { + cmdbuf[0] = header.raw; + } + explicit RequestBuilder(u32* command_buffer, u32 command_header) + : RequestBuilder(command_buffer, Header{command_header}) {} + RequestBuilder(u32* command_buffer, u16 command_id, unsigned normal_params_size, + unsigned translate_params_size) + : RequestBuilder(command_buffer, + MakeHeader(command_id, normal_params_size, translate_params_size)) {} + + // Validate on destruction, as there shouldn't be any case where we don't want it + ~RequestBuilder() { + ValidateHeader(); + } + + template <typename T> + void Push(T value); + + template <typename First, typename... Other> + void Push(const First& first_value, const Other&... other_values); + + /** + * @brief Copies the content of the given trivially copyable class to the buffer as a normal + * param + * @note: The input class must be correctly packed/padded to fit hardware layout. + */ + template <typename T> + void PushRaw(const T& value); + + // TODO : ensure that translate params are added after all regular params + template <typename... H> + void PushCopyHandles(H... handles); + + template <typename... H> + void PushMoveHandles(H... handles); + + void PushCurrentPIDHandle(); + + void PushStaticBuffer(VAddr buffer_vaddr, u32 size, u8 buffer_id); + + void PushMappedBuffer(VAddr buffer_vaddr, u32 size, MappedBufferPermissions perms); +}; + +/// Push /// + +template <> +inline void RequestBuilder::Push(u32 value) { + cmdbuf[index++] = value; +} + +template <typename T> +void RequestBuilder::PushRaw(const T& value) { + static_assert(std::is_trivially_copyable<T>(), "Raw types should be trivially copyable"); + std::memcpy(cmdbuf + index, &value, sizeof(T)); + index += (sizeof(T) + 3) / 4; // round up to word length +} + +template <> +inline void RequestBuilder::Push(u8 value) { + PushRaw(value); +} + +template <> +inline void RequestBuilder::Push(u16 value) { + PushRaw(value); +} + +template <> +inline void RequestBuilder::Push(u64 value) { + Push(static_cast<u32>(value)); + Push(static_cast<u32>(value >> 32)); +} + +template <> +inline void RequestBuilder::Push(bool value) { + Push(static_cast<u8>(value)); +} + +template <> +inline void RequestBuilder::Push(ResultCode value) { + Push(value.raw); +} + +template <typename First, typename... Other> +void RequestBuilder::Push(const First& first_value, const Other&... other_values) { + Push(first_value); + Push(other_values...); +} + +template <typename... H> +inline void RequestBuilder::PushCopyHandles(H... handles) { + Push(CopyHandleDesc(sizeof...(H))); + Push(static_cast<Kernel::Handle>(handles)...); +} + +template <typename... H> +inline void RequestBuilder::PushMoveHandles(H... handles) { + Push(MoveHandleDesc(sizeof...(H))); + Push(static_cast<Kernel::Handle>(handles)...); +} + +inline void RequestBuilder::PushCurrentPIDHandle() { + Push(CallingPidDesc()); + Push(u32(0)); +} + +inline void RequestBuilder::PushStaticBuffer(VAddr buffer_vaddr, u32 size, u8 buffer_id) { + Push(StaticBufferDesc(size, buffer_id)); + Push(buffer_vaddr); +} + +inline void RequestBuilder::PushMappedBuffer(VAddr buffer_vaddr, u32 size, + MappedBufferPermissions perms) { + Push(MappedBufferDesc(size, perms)); + Push(buffer_vaddr); +} + +class RequestParser : public RequestHelperBase { +public: + RequestParser(u32* command_buffer, Header command_header) + : RequestHelperBase(command_buffer, command_header) {} + explicit RequestParser(u32* command_buffer, u32 command_header) + : RequestParser(command_buffer, Header{command_header}) {} + RequestParser(u32* command_buffer, u16 command_id, unsigned normal_params_size, + unsigned translate_params_size) + : RequestParser(command_buffer, + MakeHeader(command_id, normal_params_size, translate_params_size)) {} + + RequestBuilder MakeBuilder(u32 normal_params_size, u32 translate_params_size, + bool validateHeader = true) { + if (validateHeader) + ValidateHeader(); + Header builderHeader{ + MakeHeader(header.command_id, normal_params_size, translate_params_size)}; + return {cmdbuf, builderHeader}; + } + + template <typename T> + T Pop(); + + template <typename T> + void Pop(T& value); + + template <typename First, typename... Other> + void Pop(First& first_value, Other&... other_values); + + Kernel::Handle PopHandle(); + + template <typename... H> + void PopHandles(H&... handles); + + /** + * @brief Pops the static buffer vaddr + * @return The virtual address of the buffer + * @param[out] data_size If non-null, the pointed value will be set to the size of the data + * @param[out] useStaticBuffersToGetVaddr Indicates if we should read the vaddr from the static + * buffers (which is the correct thing to do, but no service presently implement it) instead of + * using the same value as the process who sent the request + * given by the source process + * + * Static buffers must be set up before any IPC request using those is sent. + * It is the duty of the process (usually services) to allocate and set up the receiving static + * buffer information + * Please note that the setup uses virtual addresses. + */ + VAddr PopStaticBuffer(size_t* data_size = nullptr, bool useStaticBuffersToGetVaddr = false); + + /** + * @brief Pops the mapped buffer vaddr + * @return The virtual address of the buffer + * @param[out] data_size If non-null, the pointed value will be set to the size of the data + * given by the source process + * @param[out] buffer_perms If non-null, the pointed value will be set to the permissions of the + * buffer + */ + VAddr PopMappedBuffer(size_t* data_size = nullptr, + MappedBufferPermissions* buffer_perms = nullptr); + + /** + * @brief Reads the next normal parameters as a struct, by copying it + * @note: The output class must be correctly packed/padded to fit hardware layout. + */ + template <typename T> + void PopRaw(T& value); + + /** + * @brief Reads the next normal parameters as a struct, by copying it into a new value + * @note: The output class must be correctly packed/padded to fit hardware layout. + */ + template <typename T> + T PopRaw(); +}; + +/// Pop /// + +template <> +inline u32 RequestParser::Pop() { + return cmdbuf[index++]; +} + +template <typename T> +void RequestParser::PopRaw(T& value) { + static_assert(std::is_trivially_copyable<T>(), "Raw types should be trivially copyable"); + std::memcpy(&value, cmdbuf + index, sizeof(T)); + index += (sizeof(T) + 3) / 4; // round up to word length +} + +template <typename T> +T RequestParser::PopRaw() { + T value; + PopRaw(value); + return value; +} + +template <> +inline u8 RequestParser::Pop() { + return PopRaw<u8>(); +} + +template <> +inline u16 RequestParser::Pop() { + return PopRaw<u16>(); +} + +template <> +inline u64 RequestParser::Pop() { + const u64 lsw = Pop<u32>(); + const u64 msw = Pop<u32>(); + return msw << 32 | lsw; +} + +template <> +inline bool RequestParser::Pop() { + return Pop<u8>() != 0; +} + +template <> +inline ResultCode RequestParser::Pop() { + return ResultCode{Pop<u32>()}; +} + +template <typename T> +void RequestParser::Pop(T& value) { + value = Pop<T>(); +} + +template <typename First, typename... Other> +void RequestParser::Pop(First& first_value, Other&... other_values) { + first_value = Pop<First>(); + Pop(other_values...); +} + +inline Kernel::Handle RequestParser::PopHandle() { + const u32 handle_descriptor = Pop<u32>(); + DEBUG_ASSERT_MSG(IsHandleDescriptor(handle_descriptor), + "Tried to pop handle(s) but the descriptor is not a handle descriptor"); + DEBUG_ASSERT_MSG(HandleNumberFromDesc(handle_descriptor) == 1, + "Descriptor indicates that there isn't exactly one handle"); + return Pop<Kernel::Handle>(); +} + +template <typename... H> +void RequestParser::PopHandles(H&... handles) { + const u32 handle_descriptor = Pop<u32>(); + const int handles_number = sizeof...(H); + DEBUG_ASSERT_MSG(IsHandleDescriptor(handle_descriptor), + "Tried to pop handle(s) but the descriptor is not a handle descriptor"); + DEBUG_ASSERT_MSG(handles_number == HandleNumberFromDesc(handle_descriptor), + "Number of handles doesn't match the descriptor"); + Pop(static_cast<Kernel::Handle&>(handles)...); +} + +inline VAddr RequestParser::PopStaticBuffer(size_t* data_size, bool useStaticBuffersToGetVaddr) { + const u32 sbuffer_descriptor = Pop<u32>(); + StaticBufferDescInfo bufferInfo{sbuffer_descriptor}; + if (data_size != nullptr) + *data_size = bufferInfo.size; + if (!useStaticBuffersToGetVaddr) + return Pop<VAddr>(); + else { + ASSERT_MSG(0, "remove the assert if multiprocess/IPC translation are implemented."); + // The buffer has already been copied to the static buffer by the kernel during + // translation + Pop<VAddr>(); // Pop the calling process buffer address + // and get the vaddr from the static buffers + return cmdbuf[(0x100 >> 2) + bufferInfo.buffer_id * 2 + 1]; + } +} + +inline VAddr RequestParser::PopMappedBuffer(size_t* data_size, + MappedBufferPermissions* buffer_perms) { + const u32 sbuffer_descriptor = Pop<u32>(); + MappedBufferDescInfo bufferInfo{sbuffer_descriptor}; + if (data_size != nullptr) + *data_size = bufferInfo.size; + if (buffer_perms != nullptr) + *buffer_perms = bufferInfo.perms; + return Pop<VAddr>(); +} + +} // namespace IPC diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index c088b9a19..761fc4781 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -4,6 +4,7 @@ #pragma once +#include <memory> #include <string> #include "common/assert.h" #include "common/common_types.h" @@ -44,7 +45,8 @@ public: /** * Creates a pair of ServerSession and an associated ClientSession. - * @param name Optional name of the ports + * @param name Optional name of the ports. + * @param hle_handler Optional HLE handler for this server session. * @return The created session tuple */ static SessionPair CreateSessionPair( diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index c557a2279..6ab31c70b 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -11,7 +11,6 @@ #include <boost/container/flat_set.hpp> #include "common/common_types.h" #include "core/arm/arm_interface.h" -#include "core/core.h" #include "core/hle/kernel/kernel.h" #include "core/hle/result.h" diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp index 60537f355..a00c75679 100644 --- a/src/core/hle/kernel/timer.cpp +++ b/src/core/hle/kernel/timer.cpp @@ -52,9 +52,14 @@ void Timer::Set(s64 initial, s64 interval) { initial_delay = initial; interval_delay = interval; - u64 initial_microseconds = initial / 1000; - CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), timer_callback_event_type, - callback_handle); + if (initial == 0) { + // Immediately invoke the callback + Signal(0); + } else { + u64 initial_microseconds = initial / 1000; + CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), timer_callback_event_type, + callback_handle); + } } void Timer::Cancel() { @@ -72,6 +77,22 @@ void Timer::WakeupAllWaitingThreads() { signaled = false; } +void Timer::Signal(int cycles_late) { + LOG_TRACE(Kernel, "Timer %u fired", GetObjectId()); + + signaled = true; + + // Resume all waiting threads + WakeupAllWaitingThreads(); + + if (interval_delay != 0) { + // Reschedule the timer with the interval delay + u64 interval_microseconds = interval_delay / 1000; + CoreTiming::ScheduleEvent(usToCycles(interval_microseconds) - cycles_late, + timer_callback_event_type, callback_handle); + } +} + /// The timer callback event, called when a timer is fired static void TimerCallback(u64 timer_handle, int cycles_late) { SharedPtr<Timer> timer = @@ -82,19 +103,7 @@ static void TimerCallback(u64 timer_handle, int cycles_late) { return; } - LOG_TRACE(Kernel, "Timer %08" PRIx64 " fired", timer_handle); - - timer->signaled = true; - - // Resume all waiting threads - timer->WakeupAllWaitingThreads(); - - if (timer->interval_delay != 0) { - // Reschedule the timer with the interval delay - u64 interval_microseconds = timer->interval_delay / 1000; - CoreTiming::ScheduleEvent(usToCycles(interval_microseconds) - cycles_late, - timer_callback_event_type, timer_handle); - } + timer->Signal(cycles_late); } void TimersInit() { diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h index c174f5664..b0f818933 100644 --- a/src/core/hle/kernel/timer.h +++ b/src/core/hle/kernel/timer.h @@ -54,6 +54,14 @@ public: void Cancel(); void Clear(); + /** + * Signals the timer, waking up any waiting threads and rescheduling it + * for the next interval. + * This method should not be called from outside the timer callback handler, + * lest multiple callback events get scheduled. + */ + void Signal(int cycles_late); + private: Timer(); ~Timer() override; diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 53864a3a7..cfefbbc64 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -20,6 +20,7 @@ enum class ErrorDescription : u32 { OS_InvalidBufferDescriptor = 48, MaxConnectionsReached = 52, WrongAddress = 53, + FS_RomFSNotFound = 100, FS_ArchiveNotMounted = 101, FS_FileNotFound = 112, FS_PathNotFound = 113, @@ -35,10 +36,13 @@ enum class ErrorDescription : u32 { 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, InvalidSection = 1000, TooLarge = 1001, diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 615fe31ea..366d1eacf 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -18,6 +18,8 @@ #include "core/hle/service/fs/archive.h" #include "core/hle/service/ptm/ptm.h" #include "core/hle/service/service.h" +#include "core/hw/aes/ccm.h" +#include "core/hw/aes/key.h" namespace Service { namespace APT { @@ -47,13 +49,13 @@ void SendParameter(const MessageParameter& parameter) { } void Initialize(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - u32 flags = cmd_buff[2]; - - cmd_buff[2] = IPC::CopyHandleDesc(2); - cmd_buff[3] = Kernel::g_handle_table.Create(notification_event).MoveFrom(); - cmd_buff[4] = Kernel::g_handle_table.Create(parameter_event).MoveFrom(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x2, 2, 0); // 0x20080 + u32 app_id = rp.Pop<u32>(); + u32 flags = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 3); + rb.Push(RESULT_SUCCESS); + rb.PushCopyHandles(Kernel::g_handle_table.Create(notification_event).MoveFrom(), + Kernel::g_handle_table.Create(parameter_event).MoveFrom()); // TODO(bunnei): Check if these events are cleared every time Initialize is called. notification_event->Clear(); @@ -62,18 +64,16 @@ void Initialize(Service::Interface* self) { ASSERT_MSG((nullptr != lock), "Cannot initialize without lock"); lock->Release(); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - LOG_DEBUG(Service_APT, "called app_id=0x%08X, flags=0x%08X", app_id, flags); } void GetSharedFont(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x44, 0, 0); // 0x00440000 + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); if (!shared_font_mem) { LOG_ERROR(Service_APT, "shared font file missing - go dump it from your 3ds"); - cmd_buff[0] = IPC::MakeHeader(0x44, 2, 2); - cmd_buff[1] = -1; // TODO: Find the right error code + rb.Push<u32>(-1); // TODO: Find the right error code + rb.Skip(1 + 2, true); return; } @@ -85,103 +85,110 @@ void GetSharedFont(Service::Interface* self) { BCFNT::RelocateSharedFont(shared_font_mem, target_address); shared_font_relocated = true; } - cmd_buff[0] = IPC::MakeHeader(0x44, 2, 2); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + rb.Push(RESULT_SUCCESS); // No error // Since the SharedMemory interface doesn't provide the address at which the memory was // allocated, the real APT service calculates this address by scanning the entire address space // (using svcQueryMemory) and searches for an allocation of the same size as the Shared Font. - cmd_buff[2] = target_address; - cmd_buff[3] = IPC::CopyHandleDesc(); - cmd_buff[4] = Kernel::g_handle_table.Create(shared_font_mem).MoveFrom(); + rb.Push(target_address); + rb.PushCopyHandles(Kernel::g_handle_table.Create(shared_font_mem).MoveFrom()); } void NotifyToWait(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x43, 1, 0); // 0x430040 + u32 app_id = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, "(STUBBED) app_id=%u", app_id); } void GetLockHandle(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1, 1, 0); // 0x10040 + // Bits [0:2] are the applet type (System, Library, etc) // Bit 5 tells the application that there's a pending APT parameter, // this will cause the app to wait until parameter_event is signaled. - u32 applet_attributes = cmd_buff[1]; - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - cmd_buff[2] = applet_attributes; // Applet Attributes, this value is passed to Enable. - cmd_buff[3] = 0; // Least significant bit = power button state - cmd_buff[4] = IPC::CopyHandleDesc(); - cmd_buff[5] = Kernel::g_handle_table.Create(lock).MoveFrom(); - - LOG_WARNING(Service_APT, "(STUBBED) called handle=0x%08X applet_attributes=0x%08X", cmd_buff[5], + u32 applet_attributes = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(applet_attributes); // Applet Attributes, this value is passed to Enable. + rb.Push<u32>(0); // Least significant bit = power button state + Kernel::Handle handle_copy = Kernel::g_handle_table.Create(lock).MoveFrom(); + rb.PushCopyHandles(handle_copy); + + LOG_WARNING(Service_APT, "(STUBBED) called handle=0x%08X applet_attributes=0x%08X", handle_copy, applet_attributes); } void Enable(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 attributes = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - parameter_event->Signal(); // Let the application know that it has been started + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x3, 1, 0); // 0x30040 + u32 attributes = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error + parameter_event->Signal(); // Let the application know that it has been started LOG_WARNING(Service_APT, "(STUBBED) called attributes=0x%08X", attributes); } void GetAppletManInfo(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 unk = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 0; - cmd_buff[3] = 0; - cmd_buff[4] = static_cast<u32>(AppletId::HomeMenu); // Home menu AppID - cmd_buff[5] = static_cast<u32>(AppletId::Application); // TODO(purpasmart96): Do this correctly + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x5, 1, 0); // 0x50040 + u32 unk = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); + rb.Push(RESULT_SUCCESS); // No error + rb.Push<u32>(0); + rb.Push<u32>(0); + rb.Push(static_cast<u32>(AppletId::HomeMenu)); // Home menu AppID + rb.Push(static_cast<u32>(AppletId::Application)); // TODO(purpasmart96): Do this correctly LOG_WARNING(Service_APT, "(STUBBED) called unk=0x%08X", unk); } void IsRegistered(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x9, 1, 0); // 0x90040 + u32 app_id = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); // No error // TODO(Subv): An application is considered "registered" if it has already called APT::Enable // handle this properly once we implement multiprocess support. - cmd_buff[2] = 0; // Set to not registered by default + bool is_registered = false; // Set to not registered by default if (app_id == static_cast<u32>(AppletId::AnyLibraryApplet)) { - cmd_buff[2] = HLE::Applets::IsLibraryAppletRunning() ? 1 : 0; + is_registered = HLE::Applets::IsLibraryAppletRunning(); } else if (auto applet = HLE::Applets::Applet::Get(static_cast<AppletId>(app_id))) { - cmd_buff[2] = 1; // Set to registered + is_registered = true; // Set to registered } + rb.Push(is_registered); + LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); } void InquireNotification(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = static_cast<u32>(SignalType::None); // Signal type + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xB, 1, 0); // 0xB0040 + u32 app_id = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(static_cast<u32>(SignalType::None)); // Signal type LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); } void SendParameter(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 src_app_id = cmd_buff[1]; - u32 dst_app_id = cmd_buff[2]; - u32 signal_type = cmd_buff[3]; - u32 buffer_size = cmd_buff[4]; - u32 value = cmd_buff[5]; - u32 handle = cmd_buff[6]; - u32 size = cmd_buff[7]; - u32 buffer = cmd_buff[8]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xC, 4, 4); // 0xC0104 + u32 src_app_id = rp.Pop<u32>(); + u32 dst_app_id = rp.Pop<u32>(); + u32 signal_type = rp.Pop<u32>(); + u32 buffer_size = rp.Pop<u32>(); + Kernel::Handle handle = rp.PopHandle(); + size_t size; + VAddr buffer = rp.PopStaticBuffer(&size); std::shared_ptr<HLE::Applets::Applet> dest_applet = HLE::Applets::Applet::Get(static_cast<AppletId>(dst_app_id)); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + if (dest_applet == nullptr) { LOG_ERROR(Service_APT, "Unknown applet id=0x%08X", dst_app_id); - cmd_buff[1] = -1; // TODO(Subv): Find the right error code + rb.Push<u32>(-1); // TODO(Subv): Find the right error code return; } @@ -193,88 +200,104 @@ void SendParameter(Service::Interface* self) { param.buffer.resize(buffer_size); Memory::ReadBlock(buffer, param.buffer.data(), param.buffer.size()); - cmd_buff[1] = dest_applet->ReceiveParameter(param).raw; + rb.Push(dest_applet->ReceiveParameter(param)); - LOG_WARNING( - Service_APT, - "(STUBBED) called src_app_id=0x%08X, dst_app_id=0x%08X, signal_type=0x%08X," - "buffer_size=0x%08X, value=0x%08X, handle=0x%08X, size=0x%08X, in_param_buffer_ptr=0x%08X", - src_app_id, dst_app_id, signal_type, buffer_size, value, handle, size, buffer); + LOG_WARNING(Service_APT, + "(STUBBED) called src_app_id=0x%08X, dst_app_id=0x%08X, signal_type=0x%08X," + "buffer_size=0x%08X, handle=0x%08X, size=0x%08zX, in_param_buffer_ptr=0x%08X", + src_app_id, dst_app_id, signal_type, buffer_size, handle, size, buffer); } void ReceiveParameter(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - u32 buffer_size = cmd_buff[2]; - VAddr buffer = cmd_buff[0x104 >> 2]; - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = next_parameter.sender_id; - cmd_buff[3] = next_parameter.signal; // Signal type - cmd_buff[4] = next_parameter.buffer.size(); // Parameter buffer size - cmd_buff[5] = 0x10; - cmd_buff[6] = 0; - if (next_parameter.object != nullptr) - cmd_buff[6] = Kernel::g_handle_table.Create(next_parameter.object).MoveFrom(); - cmd_buff[7] = (next_parameter.buffer.size() << 14) | 2; - cmd_buff[8] = buffer; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xD, 2, 0); // 0xD0080 + u32 app_id = rp.Pop<u32>(); + u32 buffer_size = rp.Pop<u32>(); + + size_t static_buff_size; + VAddr buffer = rp.PeekStaticBuffer(0, &static_buff_size); + if (buffer_size > static_buff_size) + LOG_WARNING( + Service_APT, + "buffer_size is bigger than the size in the buffer descriptor (0x%08X > 0x%08zX)", + buffer_size, static_buff_size); + + IPC::RequestBuilder rb = rp.MakeBuilder(4, 4); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(next_parameter.sender_id); + rb.Push(next_parameter.signal); // Signal type + ASSERT_MSG(next_parameter.buffer.size() <= buffer_size, "Input static buffer is too small !"); + rb.Push(static_cast<u32>(next_parameter.buffer.size())); // Parameter buffer size + + rb.PushMoveHandles((next_parameter.object != nullptr) + ? Kernel::g_handle_table.Create(next_parameter.object).MoveFrom() + : 0); + rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter.buffer.size()), 0); Memory::WriteBlock(buffer, next_parameter.buffer.data(), next_parameter.buffer.size()); - LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size); + LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08zX", app_id, buffer_size); } void GlanceParameter(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - u32 buffer_size = cmd_buff[2]; - VAddr buffer = cmd_buff[0x104 >> 2]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xE, 2, 0); // 0xE0080 + u32 app_id = rp.Pop<u32>(); + u32 buffer_size = rp.Pop<u32>(); + + size_t static_buff_size; + VAddr buffer = rp.PeekStaticBuffer(0, &static_buff_size); + if (buffer_size > static_buff_size) + LOG_WARNING( + Service_APT, + "buffer_size is bigger than the size in the buffer descriptor (0x%08X > 0x%08zX)", + buffer_size, static_buff_size); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = next_parameter.sender_id; - cmd_buff[3] = next_parameter.signal; // Signal type - cmd_buff[4] = next_parameter.buffer.size(); // Parameter buffer size - cmd_buff[5] = 0x10; - cmd_buff[6] = 0; - if (next_parameter.object != nullptr) - cmd_buff[6] = Kernel::g_handle_table.Create(next_parameter.object).MoveFrom(); - cmd_buff[7] = (next_parameter.buffer.size() << 14) | 2; - cmd_buff[8] = buffer; + IPC::RequestBuilder rb = rp.MakeBuilder(4, 4); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(next_parameter.sender_id); + rb.Push(next_parameter.signal); // Signal type + ASSERT_MSG(next_parameter.buffer.size() <= buffer_size, "Input static buffer is too small !"); + rb.Push(static_cast<u32>(next_parameter.buffer.size())); // Parameter buffer size - Memory::WriteBlock(buffer, next_parameter.buffer.data(), - std::min(static_cast<size_t>(buffer_size), next_parameter.buffer.size())); + rb.PushCopyHandles((next_parameter.object != nullptr) + ? Kernel::g_handle_table.Create(next_parameter.object).MoveFrom() + : 0); + rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter.buffer.size()), 0); + + Memory::WriteBlock(buffer, next_parameter.buffer.data(), next_parameter.buffer.size()); - LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size); + LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08zX", app_id, buffer_size); } void CancelParameter(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 flag1 = cmd_buff[1]; - u32 unk = cmd_buff[2]; - u32 flag2 = cmd_buff[3]; - u32 app_id = cmd_buff[4]; - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 1; // Set to Success - - LOG_WARNING(Service_APT, - "(STUBBED) called flag1=0x%08X, unk=0x%08X, flag2=0x%08X, app_id=0x%08X", flag1, - unk, flag2, app_id); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xF, 4, 0); // 0xF0100 + + u32 check_sender = rp.Pop<u32>(); + u32 sender_appid = rp.Pop<u32>(); + u32 check_receiver = rp.Pop<u32>(); + u32 receiver_appid = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(true); // Set to Success + + LOG_WARNING(Service_APT, "(STUBBED) called check_sender=0x%08X, sender_appid=0x%08X, " + "check_receiver=0x%08X, receiver_appid=0x%08X", + check_sender, sender_appid, check_receiver, receiver_appid); } void PrepareToStartApplication(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 title_info1 = cmd_buff[1]; - u32 title_info2 = cmd_buff[2]; - u32 title_info3 = cmd_buff[3]; - u32 title_info4 = cmd_buff[4]; - u32 flags = cmd_buff[5]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x15, 5, 0); // 0x00150140 + u32 title_info1 = rp.Pop<u32>(); + u32 title_info2 = rp.Pop<u32>(); + u32 title_info3 = rp.Pop<u32>(); + u32 title_info4 = rp.Pop<u32>(); + u32 flags = rp.Pop<u32>(); if (flags & 0x00000100) { unknown_ns_state_field = 1; } - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, "(STUBBED) called title_info1=0x%08X, title_info2=0x%08X, title_info3=0x%08X," @@ -283,172 +306,188 @@ void PrepareToStartApplication(Service::Interface* self) { } void StartApplication(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 buffer1_size = cmd_buff[1]; - u32 buffer2_size = cmd_buff[2]; - u32 flag = cmd_buff[3]; - u32 size1 = cmd_buff[4]; - u32 buffer1_ptr = cmd_buff[5]; - u32 size2 = cmd_buff[6]; - u32 buffer2_ptr = cmd_buff[7]; - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1B, 3, 4); // 0x001B00C4 + u32 buffer1_size = rp.Pop<u32>(); + u32 buffer2_size = rp.Pop<u32>(); + u32 flag = rp.Pop<u32>(); + size_t size1; + VAddr buffer1_ptr = rp.PopStaticBuffer(&size1); + size_t size2; + VAddr buffer2_ptr = rp.PopStaticBuffer(&size2); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, "(STUBBED) called buffer1_size=0x%08X, buffer2_size=0x%08X, flag=0x%08X," - "size1=0x%08X, buffer1_ptr=0x%08X, size2=0x%08X, buffer2_ptr=0x%08X", + "size1=0x%08zX, buffer1_ptr=0x%08X, size2=0x%08zX, buffer2_ptr=0x%08X", buffer1_size, buffer2_size, flag, size1, buffer1_ptr, size2, buffer2_ptr); } void AppletUtility(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x4B, 3, 2); // 0x004B00C2 // These are from 3dbrew - I'm not really sure what they're used for. - u32 command = cmd_buff[1]; - u32 buffer1_size = cmd_buff[2]; - u32 buffer2_size = cmd_buff[3]; - u32 buffer1_addr = cmd_buff[5]; - u32 buffer2_addr = cmd_buff[65]; + u32 utility_command = rp.Pop<u32>(); + u32 input_size = rp.Pop<u32>(); + u32 output_size = rp.Pop<u32>(); + VAddr input_addr = rp.PopStaticBuffer(); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + VAddr output_addr = rp.PeekStaticBuffer(0); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, - "(STUBBED) called command=0x%08X, buffer1_size=0x%08X, buffer2_size=0x%08X, " - "buffer1_addr=0x%08X, buffer2_addr=0x%08X", - command, buffer1_size, buffer2_size, buffer1_addr, buffer2_addr); + "(STUBBED) called command=0x%08X, input_size=0x%08X, output_size=0x%08X, " + "input_addr=0x%08X, output_addr=0x%08X", + utility_command, input_size, output_size, input_addr, output_addr); } void SetAppCpuTimeLimit(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 value = cmd_buff[1]; - cpu_percent = cmd_buff[2]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x4F, 2, 0); // 0x4F0080 + u32 value = rp.Pop<u32>(); + cpu_percent = rp.Pop<u32>(); if (value != 1) { LOG_ERROR(Service_APT, "This value should be one, but is actually %u!", value); } - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, "(STUBBED) called cpu_percent=%u, value=%u", cpu_percent, value); } void GetAppCpuTimeLimit(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 value = cmd_buff[1]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x50, 1, 0); // 0x500040 + u32 value = rp.Pop<u32>(); if (value != 1) { LOG_ERROR(Service_APT, "This value should be one, but is actually %u!", value); } - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = cpu_percent; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(cpu_percent); LOG_WARNING(Service_APT, "(STUBBED) called value=%u", value); } void PrepareToStartLibraryApplet(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - AppletId applet_id = static_cast<AppletId>(cmd_buff[1]); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x18, 1, 0); // 0x180040 + AppletId applet_id = static_cast<AppletId>(rp.Pop<u32>()); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); auto applet = HLE::Applets::Applet::Get(applet_id); if (applet) { LOG_WARNING(Service_APT, "applet has already been started id=%08X", applet_id); - cmd_buff[1] = RESULT_SUCCESS.raw; + rb.Push(RESULT_SUCCESS); } else { - cmd_buff[1] = HLE::Applets::Applet::Create(applet_id).raw; + rb.Push(HLE::Applets::Applet::Create(applet_id)); } LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id); } void PreloadLibraryApplet(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - AppletId applet_id = static_cast<AppletId>(cmd_buff[1]); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x16, 1, 0); // 0x160040 + AppletId applet_id = static_cast<AppletId>(rp.Pop<u32>()); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); auto applet = HLE::Applets::Applet::Get(applet_id); if (applet) { LOG_WARNING(Service_APT, "applet has already been started id=%08X", applet_id); - cmd_buff[1] = RESULT_SUCCESS.raw; + rb.Push(RESULT_SUCCESS); } else { - cmd_buff[1] = HLE::Applets::Applet::Create(applet_id).raw; + rb.Push(HLE::Applets::Applet::Create(applet_id)); } LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id); } void StartLibraryApplet(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - AppletId applet_id = static_cast<AppletId>(cmd_buff[1]); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1E, 2, 4); // 0x1E0084 + AppletId applet_id = static_cast<AppletId>(rp.Pop<u32>()); std::shared_ptr<HLE::Applets::Applet> applet = HLE::Applets::Applet::Get(applet_id); LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id); if (applet == nullptr) { LOG_ERROR(Service_APT, "unknown applet id=%08X", applet_id); - cmd_buff[1] = -1; // TODO(Subv): Find the right error code + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0, false); + rb.Push<u32>(-1); // TODO(Subv): Find the right error code return; } - size_t buffer_size = cmd_buff[2]; - VAddr buffer_addr = cmd_buff[6]; + size_t buffer_size = rp.Pop<u32>(); + Kernel::Handle handle = rp.PopHandle(); + VAddr buffer_addr = rp.PopStaticBuffer(); AppletStartupParameter parameter; - parameter.object = Kernel::g_handle_table.GetGeneric(cmd_buff[4]); + parameter.object = Kernel::g_handle_table.GetGeneric(handle); parameter.buffer.resize(buffer_size); Memory::ReadBlock(buffer_addr, parameter.buffer.data(), parameter.buffer.size()); - cmd_buff[1] = applet->Start(parameter).raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(applet->Start(parameter)); } void CancelLibraryApplet(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 exiting = cmd_buff[1] & 0xFF; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x3B, 1, 0); // 0x003B0040 + bool exiting = rp.Pop<bool>(); - cmd_buff[1] = 1; // TODO: Find the return code meaning + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push<u32>(1); // TODO: Find the return code meaning - LOG_WARNING(Service_APT, "(STUBBED) called exiting=%u", exiting); + LOG_WARNING(Service_APT, "(STUBBED) called exiting=%d", exiting); } void SetScreenCapPostPermission(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x55, 1, 0); // 0x00550040 - screen_capture_post_permission = static_cast<ScreencapPostPermission>(cmd_buff[1] & 0xF); + screen_capture_post_permission = static_cast<ScreencapPostPermission>(rp.Pop<u32>() & 0xF); - cmd_buff[0] = IPC::MakeHeader(0x55, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, "(STUBBED) screen_capture_post_permission=%u", screen_capture_post_permission); } void GetScreenCapPostPermission(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x56, 0, 0); // 0x00560000 - cmd_buff[0] = IPC::MakeHeader(0x56, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = static_cast<u32>(screen_capture_post_permission); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(static_cast<u32>(screen_capture_post_permission)); LOG_WARNING(Service_APT, "(STUBBED) screen_capture_post_permission=%u", screen_capture_post_permission); } void GetAppletInfo(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - auto app_id = static_cast<AppletId>(cmd_buff[1]); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x6, 1, 0); // 0x60040 + auto app_id = static_cast<AppletId>(rp.Pop<u32>()); if (auto applet = HLE::Applets::Applet::Get(app_id)) { // TODO(Subv): Get the title id for the current applet and write it in the response[2-3] - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[4] = static_cast<u32>(Service::FS::MediaType::NAND); - cmd_buff[5] = 1; // Registered - cmd_buff[6] = 1; // Loaded - cmd_buff[7] = 0; // Applet Attributes + IPC::RequestBuilder rb = rp.MakeBuilder(7, 0); + rb.Push(RESULT_SUCCESS); + u64 title_id = 0; + rb.Push(title_id); + rb.Push(static_cast<u32>(Service::FS::MediaType::NAND)); + rb.Push(true); // Registered + rb.Push(true); // Loaded + rb.Push<u32>(0); // Applet Attributes } else { - cmd_buff[1] = ResultCode(ErrorDescription::NotFound, ErrorModule::Applet, - ErrorSummary::NotFound, ErrorLevel::Status) - .raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound, + ErrorLevel::Status)); } LOG_WARNING(Service_APT, "(stubbed) called appid=%u", app_id); } void GetStartupArgument(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 parameter_size = cmd_buff[1]; - StartupArgumentType startup_argument_type = static_cast<StartupArgumentType>(cmd_buff[2]); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x51, 2, 0); // 0x00510080 + u32 parameter_size = rp.Pop<u32>(); + StartupArgumentType startup_argument_type = static_cast<StartupArgumentType>(rp.Pop<u8>()); if (parameter_size >= 0x300) { LOG_ERROR( @@ -458,7 +497,14 @@ void GetStartupArgument(Service::Interface* self) { return; } - u32 addr = cmd_buff[65]; + size_t static_buff_size; + VAddr addr = rp.PeekStaticBuffer(0, &static_buff_size); + if (parameter_size > static_buff_size) + LOG_WARNING( + Service_APT, + "parameter_size is bigger than the size in the buffer descriptor (0x%08X > 0x%08zX)", + parameter_size, static_buff_size); + if (addr && parameter_size) { Memory::ZeroBlock(addr, parameter_size); } @@ -466,30 +512,133 @@ void GetStartupArgument(Service::Interface* self) { LOG_WARNING(Service_APT, "(stubbed) called startup_argument_type=%u , parameter_size=0x%08x", startup_argument_type, parameter_size); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); + rb.PushStaticBuffer(addr, parameter_size, 0); +} + +void Wrap(Service::Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x46, 4, 4); + const u32 output_size = rp.Pop<u32>(); + const u32 input_size = rp.Pop<u32>(); + const u32 nonce_offset = rp.Pop<u32>(); + u32 nonce_size = rp.Pop<u32>(); + size_t desc_size; + IPC::MappedBufferPermissions desc_permission; + const VAddr input = rp.PopMappedBuffer(&desc_size, &desc_permission); + ASSERT(desc_size == input_size && desc_permission == IPC::MappedBufferPermissions::R); + const VAddr output = rp.PopMappedBuffer(&desc_size, &desc_permission); + ASSERT(desc_size == output_size && desc_permission == IPC::MappedBufferPermissions::W); + + // Note: real 3DS still returns SUCCESS when the sizes don't match. It seems that it doesn't + // check the buffer size and writes data with potential overflow. + ASSERT_MSG(output_size == input_size + HW::AES::CCM_MAC_SIZE, + "input_size (%d) doesn't match to output_size (%d)", input_size, output_size); + + LOG_DEBUG(Service_APT, "called, output_size=%u, input_size=%u, nonce_offset=%u, nonce_size=%u", + output_size, input_size, nonce_offset, nonce_size); + + // Note: This weird nonce size modification is verified against real 3DS + nonce_size = std::min<u32>(nonce_size & ~3, HW::AES::CCM_NONCE_SIZE); + + // Reads nonce and concatenates the rest of the input as plaintext + HW::AES::CCMNonce nonce{}; + Memory::ReadBlock(input + nonce_offset, nonce.data(), nonce_size); + u32 pdata_size = input_size - nonce_size; + std::vector<u8> pdata(pdata_size); + Memory::ReadBlock(input, pdata.data(), nonce_offset); + Memory::ReadBlock(input + nonce_offset + nonce_size, pdata.data() + nonce_offset, + pdata_size - nonce_offset); + + // Encrypts the plaintext using AES-CCM + auto cipher = HW::AES::EncryptSignCCM(pdata, nonce, HW::AES::KeySlotID::APTWrap); + + // Puts the nonce to the beginning of the output, with ciphertext followed + Memory::WriteBlock(output, nonce.data(), nonce_size); + Memory::WriteBlock(output + nonce_size, cipher.data(), cipher.size()); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); + rb.Push(RESULT_SUCCESS); + + // Unmap buffer + rb.PushMappedBuffer(input, input_size, IPC::MappedBufferPermissions::R); + rb.PushMappedBuffer(output, output_size, IPC::MappedBufferPermissions::W); +} + +void Unwrap(Service::Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x47, 4, 4); + const u32 output_size = rp.Pop<u32>(); + const u32 input_size = rp.Pop<u32>(); + const u32 nonce_offset = rp.Pop<u32>(); + u32 nonce_size = rp.Pop<u32>(); + size_t desc_size; + IPC::MappedBufferPermissions desc_permission; + const VAddr input = rp.PopMappedBuffer(&desc_size, &desc_permission); + ASSERT(desc_size == input_size && desc_permission == IPC::MappedBufferPermissions::R); + const VAddr output = rp.PopMappedBuffer(&desc_size, &desc_permission); + ASSERT(desc_size == output_size && desc_permission == IPC::MappedBufferPermissions::W); + + // Note: real 3DS still returns SUCCESS when the sizes don't match. It seems that it doesn't + // check the buffer size and writes data with potential overflow. + ASSERT_MSG(output_size == input_size - HW::AES::CCM_MAC_SIZE, + "input_size (%d) doesn't match to output_size (%d)", input_size, output_size); + + LOG_DEBUG(Service_APT, "called, output_size=%u, input_size=%u, nonce_offset=%u, nonce_size=%u", + output_size, input_size, nonce_offset, nonce_size); + + // Note: This weird nonce size modification is verified against real 3DS + nonce_size = std::min<u32>(nonce_size & ~3, HW::AES::CCM_NONCE_SIZE); + + // Reads nonce and cipher text + HW::AES::CCMNonce nonce{}; + Memory::ReadBlock(input, nonce.data(), nonce_size); + u32 cipher_size = input_size - nonce_size; + std::vector<u8> cipher(cipher_size); + Memory::ReadBlock(input + nonce_size, cipher.data(), cipher_size); + + // Decrypts the ciphertext using AES-CCM + auto pdata = HW::AES::DecryptVerifyCCM(cipher, nonce, HW::AES::KeySlotID::APTWrap); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); + if (!pdata.empty()) { + // Splits the plaintext and put the nonce in between + Memory::WriteBlock(output, pdata.data(), nonce_offset); + Memory::WriteBlock(output + nonce_offset, nonce.data(), nonce_size); + Memory::WriteBlock(output + nonce_offset + nonce_size, pdata.data() + nonce_offset, + pdata.size() - nonce_offset); + rb.Push(RESULT_SUCCESS); + } else { + LOG_ERROR(Service_APT, "Failed to decrypt data"); + rb.Push(ResultCode(static_cast<ErrorDescription>(1), ErrorModule::PS, + ErrorSummary::WrongArgument, ErrorLevel::Status)); + } + + // Unmap buffer + rb.PushMappedBuffer(input, input_size, IPC::MappedBufferPermissions::R); + rb.PushMappedBuffer(output, output_size, IPC::MappedBufferPermissions::W); } void CheckNew3DSApp(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x101, 0, 0); // 0x01010000 + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); if (unknown_ns_state_field) { - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); } else { - PTM::CheckNew3DS(self); + PTM::CheckNew3DS(rb); } - cmd_buff[0] = IPC::MakeHeader(0x101, 2, 0); LOG_WARNING(Service_APT, "(STUBBED) called"); } void CheckNew3DS(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x102, 0, 0); // 0x01020000 + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); - PTM::CheckNew3DS(self); + PTM::CheckNew3DS(rb); - cmd_buff[0] = IPC::MakeHeader(0x102, 2, 0); LOG_WARNING(Service_APT, "(STUBBED) called"); } diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index 80325361f..e63b61450 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -137,6 +137,46 @@ void Initialize(Service::Interface* self); void GetSharedFont(Service::Interface* self); /** + * APT::Wrap service function + * Inputs: + * 1 : Output buffer size + * 2 : Input buffer size + * 3 : Nonce offset to the input buffer + * 4 : Nonce size + * 5 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xA) + * 6 : Input buffer address + * 7 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xC) + * 8 : Output buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xA) + * 3 : Input buffer address + * 4 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xC) + * 5 : Output buffer address + */ +void Wrap(Service::Interface* self); + +/** + * APT::Unwrap service function + * Inputs: + * 1 : Output buffer size + * 2 : Input buffer size + * 3 : Nonce offset to the output buffer + * 4 : Nonce size + * 5 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xA) + * 6 : Input buffer address + * 7 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xC) + * 8 : Output buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xA) + * 3 : Input buffer address + * 4 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xC) + * 5 : Output buffer address + */ +void Unwrap(Service::Interface* self); + +/** * APT::NotifyToWait service function * Inputs: * 1 : AppID diff --git a/src/core/hle/service/apt/apt_a.cpp b/src/core/hle/service/apt/apt_a.cpp index 62dc2d61d..c496cba8d 100644 --- a/src/core/hle/service/apt/apt_a.cpp +++ b/src/core/hle/service/apt/apt_a.cpp @@ -78,8 +78,8 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00430040, NotifyToWait, "NotifyToWait"}, {0x00440000, GetSharedFont, "GetSharedFont"}, {0x00450040, nullptr, "GetWirelessRebootInfo"}, - {0x00460104, nullptr, "Wrap"}, - {0x00470104, nullptr, "Unwrap"}, + {0x00460104, Wrap, "Wrap"}, + {0x00470104, Unwrap, "Unwrap"}, {0x00480100, nullptr, "GetProgramInfo"}, {0x00490180, nullptr, "Reboot"}, {0x004A0040, nullptr, "GetCaptureInfo"}, diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp index effd23dce..ec5668d05 100644 --- a/src/core/hle/service/apt/apt_s.cpp +++ b/src/core/hle/service/apt/apt_s.cpp @@ -78,8 +78,8 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00430040, NotifyToWait, "NotifyToWait"}, {0x00440000, GetSharedFont, "GetSharedFont"}, {0x00450040, nullptr, "GetWirelessRebootInfo"}, - {0x00460104, nullptr, "Wrap"}, - {0x00470104, nullptr, "Unwrap"}, + {0x00460104, Wrap, "Wrap"}, + {0x00470104, Unwrap, "Unwrap"}, {0x00480100, nullptr, "GetProgramInfo"}, {0x00490180, nullptr, "Reboot"}, {0x004A0040, nullptr, "GetCaptureInfo"}, diff --git a/src/core/hle/service/apt/apt_u.cpp b/src/core/hle/service/apt/apt_u.cpp index e06084a1e..9dd002590 100644 --- a/src/core/hle/service/apt/apt_u.cpp +++ b/src/core/hle/service/apt/apt_u.cpp @@ -78,8 +78,8 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00430040, NotifyToWait, "NotifyToWait"}, {0x00440000, GetSharedFont, "GetSharedFont"}, {0x00450040, nullptr, "GetWirelessRebootInfo"}, - {0x00460104, nullptr, "Wrap"}, - {0x00470104, nullptr, "Unwrap"}, + {0x00460104, Wrap, "Wrap"}, + {0x00470104, Unwrap, "Unwrap"}, {0x00480100, nullptr, "GetProgramInfo"}, {0x00490180, nullptr, "Reboot"}, {0x004A0040, nullptr, "GetCaptureInfo"}, diff --git a/src/core/hle/service/cam/cam.h b/src/core/hle/service/cam/cam.h index f6bff8bc6..34a9c8479 100644 --- a/src/core/hle/service/cam/cam.h +++ b/src/core/hle/service/cam/cam.h @@ -518,7 +518,7 @@ void FlipImage(Service::Interface* self); void SetDetailSize(Service::Interface* self); /** - * Sets camera resolution from preset resolution parameters. . + * Sets camera resolution from preset resolution parameters. * Inputs: * 0: 0x001F00C0 * 1: u8 selected camera diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 6f13cde27..4ddb1bc90 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -3,6 +3,8 @@ // Refer to the license.txt file included. #include <algorithm> +#include <array> +#include <cryptopp/sha.h> #include "common/file_util.h" #include "common/logging/log.h" #include "common/string_util.h" @@ -176,14 +178,29 @@ void SecureInfoGetRegion(Service::Interface* self) { } void GenHashConsoleUnique(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id_salt = cmd_buff[1]; - - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0x33646D6F ^ (app_id_salt & 0xFFFFF); // 3dmoo hash - cmd_buff[3] = 0x6F534841 ^ (app_id_salt & 0xFFFFF); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x03, 1, 0); + const u32 app_id_salt = rp.Pop<u32>() & 0x000FFFFF; + + IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); + + std::array<u8, 12> buffer; + const ResultCode result = GetConfigInfoBlock(ConsoleUniqueID2BlockID, 8, 2, buffer.data()); + rb.Push(result); + if (result.IsSuccess()) { + std::memcpy(&buffer[8], &app_id_salt, sizeof(u32)); + std::array<u8, CryptoPP::SHA256::DIGESTSIZE> hash; + CryptoPP::SHA256().CalculateDigest(hash.data(), buffer.data(), sizeof(buffer)); + u32 low, high; + memcpy(&low, &hash[hash.size() - 8], sizeof(u32)); + memcpy(&high, &hash[hash.size() - 4], sizeof(u32)); + rb.Push(low); + rb.Push(high); + } else { + rb.Push<u32>(0); + rb.Push<u32>(0); + } - LOG_WARNING(Service_CFG, "(STUBBED) called app_id_salt=0x%X", app_id_salt); + LOG_DEBUG(Service_CFG, "called app_id_salt=0x%X", app_id_salt); } void GetRegionCanadaUSA(Service::Interface* self) { diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index 519c1f3a9..2ea956e0b 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -26,7 +26,7 @@ namespace FS { /// Supported archive types enum class ArchiveIdCode : u32 { - RomFS = 0x00000003, + SelfNCCH = 0x00000003, SaveData = 0x00000004, ExtSaveData = 0x00000006, SharedExtSaveData = 0x00000007, diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index 337da1387..33b290699 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -54,15 +54,17 @@ static void Initialize(Service::Interface* self) { * 3 : File handle */ static void OpenFile(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + // The helper should be passed by argument to the function + IPC::RequestParser rp(Kernel::GetCommandBuffer(), {0x080201C2}); + rp.Pop<u32>(); // Always 0 ? - ArchiveHandle archive_handle = MakeArchiveHandle(cmd_buff[2], cmd_buff[3]); - auto filename_type = static_cast<FileSys::LowPathType>(cmd_buff[4]); - u32 filename_size = cmd_buff[5]; + ArchiveHandle archive_handle = rp.Pop<u64>(); + auto filename_type = static_cast<FileSys::LowPathType>(rp.Pop<u32>()); + u32 filename_size = rp.Pop<u32>(); FileSys::Mode mode; - mode.hex = cmd_buff[6]; - u32 attributes = cmd_buff[7]; // TODO(Link Mauve): do something with those attributes. - u32 filename_ptr = cmd_buff[9]; + mode.hex = rp.Pop<u32>(); + u32 attributes = rp.Pop<u32>(); // TODO(Link Mauve): do something with those attributes. + VAddr filename_ptr = rp.PopStaticBuffer(); FileSys::Path file_path(filename_type, filename_size, filename_ptr); LOG_DEBUG(Service_FS, "path=%s, mode=%u attrs=%u", file_path.DebugStr().c_str(), mode.hex, @@ -70,16 +72,17 @@ static void OpenFile(Service::Interface* self) { ResultVal<std::shared_ptr<File>> file_res = OpenFileFromArchive(archive_handle, file_path, mode); - cmd_buff[1] = file_res.Code().raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(file_res.Code()); if (file_res.Succeeded()) { std::shared_ptr<File> file = *file_res; auto sessions = ServerSession::CreateSessionPair(file->GetName(), file); file->ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions)); - cmd_buff[3] = Kernel::g_handle_table - .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) - .MoveFrom(); + rb.PushMoveHandles(Kernel::g_handle_table + .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) + .MoveFrom()); } else { - cmd_buff[3] = 0; + rb.PushMoveHandles(0); LOG_ERROR(Service_FS, "failed to get a handle for file %s", file_path.DebugStr().c_str()); } } diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index a8c1331ed..a960778a7 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -4,6 +4,7 @@ #include "common/bit_field.h" #include "common/microprofile.h" +#include "core/core.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/result.h" @@ -118,10 +119,10 @@ static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, VAddr data_va * Updates sequential GSP GPU hardware registers using parallel arrays of source data and masks. * For each register, the value is updated only where the mask is high * - * @param base_address The address of the first register in the sequence + * @param base_address The address of the first register in the sequence * @param size_in_bytes The number of registers to update (size of data) - * @param data A pointer to the source data to use for updates - * @param masks A pointer to the masks + * @param data_vaddr A virtual address to the source data to use for updates + * @param masks_vaddr A virtual address to the masks * @return RESULT_SUCCESS if the parameters are valid, error code otherwise */ static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, VAddr data_vaddr, @@ -280,6 +281,7 @@ ResultCode SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) { if (screen_id == 0) { MicroProfileFlip(); + Core::System::GetInstance().perf_stats.EndGameFrame(); } return RESULT_SUCCESS; @@ -705,6 +707,33 @@ static void ReleaseRight(Interface* self) { LOG_WARNING(Service_GSP, "called"); } +/** + * GSP_GPU::StoreDataCache service function + * + * This Function is a no-op, We aren't emulating the CPU cache any time soon. + * + * Inputs: + * 0 : Header code [0x001F0082] + * 1 : Address + * 2 : Size + * 3 : Value 0, some descriptor for the KProcess Handle + * 4 : KProcess handle + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void StoreDataCache(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 address = cmd_buff[1]; + u32 size = cmd_buff[2]; + u32 process = cmd_buff[4]; + + cmd_buff[0] = IPC::MakeHeader(0x1F, 0x1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_DEBUG(Service_GSP, "(STUBBED) called address=0x%08X, size=0x%08X, process=0x%08X", address, + size, process); +} + const Interface::FunctionInfo FunctionTable[] = { {0x00010082, WriteHWRegs, "WriteHWRegs"}, {0x00020084, WriteHWRegsWithMask, "WriteHWRegsWithMask"}, @@ -736,7 +765,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x001C0040, nullptr, "SetLedForceOff"}, {0x001D0040, nullptr, "SetTestCommand"}, {0x001E0080, nullptr, "SetInternalPriorities"}, - {0x001F0082, nullptr, "StoreDataCache"}, + {0x001F0082, StoreDataCache, "StoreDataCache"}, }; GSP_GPU::GSP_GPU() { diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index f14ab3811..b19e831fe 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -2,10 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> +#include <atomic> #include <cmath> +#include <memory> #include "common/logging/log.h" #include "core/core_timing.h" #include "core/frontend/emu_window.h" +#include "core/frontend/input.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/service/hid/hid.h" @@ -32,8 +36,8 @@ static u32 next_touch_index; static u32 next_accelerometer_index; static u32 next_gyroscope_index; -static int enable_accelerometer_count = 0; // positive means enabled -static int enable_gyroscope_count = 0; // positive means enabled +static int enable_accelerometer_count; // positive means enabled +static int enable_gyroscope_count; // positive means enabled static int pad_update_event; static int accelerometer_update_event; @@ -44,6 +48,11 @@ constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234; constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104; constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101; +static std::atomic<bool> is_device_reload_pending; +static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID> + buttons; +static std::unique_ptr<Input::AnalogDevice> circle_pad; + static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { // 30 degree and 60 degree are angular thresholds for directions constexpr float TAN30 = 0.577350269f; @@ -74,14 +83,48 @@ static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { return state; } +static void LoadInputDevices() { + std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, + Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END, + buttons.begin(), Input::CreateDevice<Input::ButtonDevice>); + circle_pad = Input::CreateDevice<Input::AnalogDevice>( + Settings::values.analogs[Settings::NativeAnalog::CirclePad]); +} + +static void UnloadInputDevices() { + for (auto& button : buttons) { + button.reset(); + } + circle_pad.reset(); +} + static void UpdatePadCallback(u64 userdata, int cycles_late) { SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); - PadState state = VideoCore::g_emu_window->GetPadState(); + if (is_device_reload_pending.exchange(false)) + LoadInputDevices(); + + PadState state; + using namespace Settings::NativeButton; + state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); + state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); + state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); + state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); + state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus()); + state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus()); + state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus()); + state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus()); + state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); + state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); + state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus()); + state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus()); // Get current circle pad position and update circle pad direction - s16 circle_pad_x, circle_pad_y; - std::tie(circle_pad_x, circle_pad_y) = VideoCore::g_emu_window->GetCirclePadState(); + float circle_pad_x_f, circle_pad_y_f; + std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus(); + constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position + s16 circle_pad_x = static_cast<s16>(circle_pad_x_f * MAX_CIRCLEPAD_POS); + s16 circle_pad_y = static_cast<s16>(circle_pad_y_f * MAX_CIRCLEPAD_POS); state.hex |= GetCirclePadDirectionState(circle_pad_x, circle_pad_y).hex; mem->pad.current_state.hex = state.hex; @@ -313,6 +356,8 @@ void Init() { AddService(new HID_U_Interface); AddService(new HID_SPVR_Interface); + is_device_reload_pending.store(true); + using Kernel::MemoryPermission; shared_mem = SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read, @@ -323,6 +368,9 @@ void Init() { next_accelerometer_index = 0; next_gyroscope_index = 0; + enable_accelerometer_count = 0; + enable_gyroscope_count = 0; + // Create event handles event_pad_or_touch_1 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch1"); event_pad_or_touch_2 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch2"); @@ -347,6 +395,11 @@ void Shutdown() { event_accelerometer = nullptr; event_gyroscope = nullptr; event_debug_pad = nullptr; + UnloadInputDevices(); +} + +void ReloadInputDevices() { + is_device_reload_pending.store(true); } } // namespace HID diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 21e66dfe0..b505cdcd5 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -39,15 +39,6 @@ struct PadState { BitField<10, 1, u32> x; BitField<11, 1, u32> y; - BitField<14, 1, u32> zl; - BitField<15, 1, u32> zr; - - BitField<20, 1, u32> touch; - - BitField<24, 1, u32> c_right; - BitField<25, 1, u32> c_left; - BitField<26, 1, u32> c_up; - BitField<27, 1, u32> c_down; BitField<28, 1, u32> circle_right; BitField<29, 1, u32> circle_left; BitField<30, 1, u32> circle_up; @@ -185,35 +176,6 @@ ASSERT_REG_POSITION(touch.index_reset_ticks, 0x2A); #undef ASSERT_REG_POSITION #endif // !defined(_MSC_VER) -// Pre-defined PadStates for single button presses -const PadState PAD_NONE = {{0}}; -const PadState PAD_A = {{1u << 0}}; -const PadState PAD_B = {{1u << 1}}; -const PadState PAD_SELECT = {{1u << 2}}; -const PadState PAD_START = {{1u << 3}}; -const PadState PAD_RIGHT = {{1u << 4}}; -const PadState PAD_LEFT = {{1u << 5}}; -const PadState PAD_UP = {{1u << 6}}; -const PadState PAD_DOWN = {{1u << 7}}; -const PadState PAD_R = {{1u << 8}}; -const PadState PAD_L = {{1u << 9}}; -const PadState PAD_X = {{1u << 10}}; -const PadState PAD_Y = {{1u << 11}}; - -const PadState PAD_ZL = {{1u << 14}}; -const PadState PAD_ZR = {{1u << 15}}; - -const PadState PAD_TOUCH = {{1u << 20}}; - -const PadState PAD_C_RIGHT = {{1u << 24}}; -const PadState PAD_C_LEFT = {{1u << 25}}; -const PadState PAD_C_UP = {{1u << 26}}; -const PadState PAD_C_DOWN = {{1u << 27}}; -const PadState PAD_CIRCLE_RIGHT = {{1u << 28}}; -const PadState PAD_CIRCLE_LEFT = {{1u << 29}}; -const PadState PAD_CIRCLE_UP = {{1u << 30}}; -const PadState PAD_CIRCLE_DOWN = {{1u << 31}}; - /** * HID::GetIPCHandles service function * Inputs: @@ -301,5 +263,8 @@ void Init(); /// Shutdown HID service void Shutdown(); + +/// Reload input devices. Used when input configuration changed +void ReloadInputDevices(); } } diff --git a/src/core/hle/service/ir/ir.cpp b/src/core/hle/service/ir/ir.cpp index 7f1731a50..7ac34a990 100644 --- a/src/core/hle/service/ir/ir.cpp +++ b/src/core/hle/service/ir/ir.cpp @@ -2,9 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "core/hle/kernel/event.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/shared_memory.h" #include "core/hle/service/ir/ir.h" #include "core/hle/service/ir/ir_rst.h" #include "core/hle/service/ir/ir_u.h" @@ -14,101 +11,18 @@ namespace Service { namespace IR { -static Kernel::SharedPtr<Kernel::Event> handle_event; -static Kernel::SharedPtr<Kernel::Event> conn_status_event; -static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; -static Kernel::SharedPtr<Kernel::SharedMemory> transfer_shared_memory; - -void GetHandles(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0x4000000; - cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(); - cmd_buff[4] = Kernel::g_handle_table.Create(Service::IR::handle_event).MoveFrom(); -} - -void InitializeIrNopShared(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - u32 transfer_buff_size = cmd_buff[1]; - u32 recv_buff_size = cmd_buff[2]; - u32 unk1 = cmd_buff[3]; - u32 send_buff_size = cmd_buff[4]; - u32 unk2 = cmd_buff[5]; - u8 baud_rate = cmd_buff[6] & 0xFF; - Kernel::Handle handle = cmd_buff[8]; - - if (Kernel::g_handle_table.IsValid(handle)) { - transfer_shared_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(handle); - transfer_shared_memory->name = "IR:TransferSharedMemory"; - } - - cmd_buff[1] = RESULT_SUCCESS.raw; - - LOG_WARNING(Service_IR, "(STUBBED) called, transfer_buff_size=%d, recv_buff_size=%d, " - "unk1=%d, send_buff_size=%d, unk2=%d, baud_rate=%u, handle=0x%08X", - transfer_buff_size, recv_buff_size, unk1, send_buff_size, unk2, baud_rate, handle); -} - -void RequireConnection(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - conn_status_event->Signal(); - - cmd_buff[1] = RESULT_SUCCESS.raw; - - LOG_WARNING(Service_IR, "(STUBBED) called"); -} - -void Disconnect(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = RESULT_SUCCESS.raw; - - LOG_WARNING(Service_IR, "(STUBBED) called"); -} - -void GetConnectionStatusEvent(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::conn_status_event).MoveFrom(); - - LOG_WARNING(Service_IR, "(STUBBED) called"); -} - -void FinalizeIrNop(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = RESULT_SUCCESS.raw; - - LOG_WARNING(Service_IR, "(STUBBED) called"); -} - void Init() { - using namespace Kernel; - AddService(new IR_RST_Interface); AddService(new IR_U_Interface); AddService(new IR_User_Interface); - using Kernel::MemoryPermission; - shared_memory = SharedMemory::Create(nullptr, 0x1000, Kernel::MemoryPermission::ReadWrite, - Kernel::MemoryPermission::ReadWrite, 0, - Kernel::MemoryRegion::BASE, "IR:SharedMemory"); - transfer_shared_memory = nullptr; - - // Create event handle(s) - handle_event = Event::Create(ResetType::OneShot, "IR:HandleEvent"); - conn_status_event = Event::Create(ResetType::OneShot, "IR:ConnectionStatusEvent"); + InitUser(); + InitRST(); } void Shutdown() { - transfer_shared_memory = nullptr; - shared_memory = nullptr; - handle_event = nullptr; - conn_status_event = nullptr; + ShutdownUser(); + ShutdownRST(); } } // namespace IR diff --git a/src/core/hle/service/ir/ir.h b/src/core/hle/service/ir/ir.h index 72d44ce60..c741498e2 100644 --- a/src/core/hle/service/ir/ir.h +++ b/src/core/hle/service/ir/ir.h @@ -10,63 +10,6 @@ class Interface; namespace IR { -/** - * IR::GetHandles service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Translate header, used by the ARM11-kernel - * 3 : Shared memory handle - * 4 : Event handle - */ -void GetHandles(Interface* self); - -/** - * IR::InitializeIrNopShared service function - * Inputs: - * 1 : Size of transfer buffer - * 2 : Recv buffer size - * 3 : unknown - * 4 : Send buffer size - * 5 : unknown - * 6 : BaudRate (u8) - * 7 : 0 - * 8 : Handle of transfer shared memory - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -void InitializeIrNopShared(Interface* self); - -/** - * IR::FinalizeIrNop service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -void FinalizeIrNop(Interface* self); - -/** - * IR::GetConnectionStatusEvent service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Connection Status Event handle - */ -void GetConnectionStatusEvent(Interface* self); - -/** - * IR::Disconnect service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -void Disconnect(Interface* self); - -/** - * IR::RequireConnection service function - * Inputs: - * 1 : unknown (u8), looks like always 1 - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -void RequireConnection(Interface* self); - /// Initialize IR service void Init(); diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp index 1f10ebd3d..3f1275c53 100644 --- a/src/core/hle/service/ir/ir_rst.cpp +++ b/src/core/hle/service/ir/ir_rst.cpp @@ -2,12 +2,34 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/kernel/event.h" +#include "core/hle/kernel/shared_memory.h" #include "core/hle/service/ir/ir.h" #include "core/hle/service/ir/ir_rst.h" namespace Service { namespace IR { +static Kernel::SharedPtr<Kernel::Event> handle_event; +static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; + +/** + * IR::GetHandles service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Translate header, used by the ARM11-kernel + * 3 : Shared memory handle + * 4 : Event handle + */ +static void GetHandles(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = 0x4000000; + cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(); + cmd_buff[4] = Kernel::g_handle_table.Create(Service::IR::handle_event).MoveFrom(); +} + const Interface::FunctionInfo FunctionTable[] = { {0x00010000, GetHandles, "GetHandles"}, {0x00020080, nullptr, "Initialize"}, @@ -19,5 +41,20 @@ IR_RST_Interface::IR_RST_Interface() { Register(FunctionTable); } +void InitRST() { + using namespace Kernel; + + shared_memory = + SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, + MemoryPermission::ReadWrite, 0, MemoryRegion::BASE, "IR:SharedMemory"); + + handle_event = Event::Create(ResetType::OneShot, "IR:HandleEvent"); +} + +void ShutdownRST() { + shared_memory = nullptr; + handle_event = nullptr; +} + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir_rst.h b/src/core/hle/service/ir/ir_rst.h index a492e15c9..75b732627 100644 --- a/src/core/hle/service/ir/ir_rst.h +++ b/src/core/hle/service/ir/ir_rst.h @@ -18,5 +18,8 @@ public: } }; +void InitRST(); +void ShutdownRST(); + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir_u.cpp b/src/core/hle/service/ir/ir_u.cpp index 429615f31..ce00d5732 100644 --- a/src/core/hle/service/ir/ir_u.cpp +++ b/src/core/hle/service/ir/ir_u.cpp @@ -27,7 +27,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00100000, nullptr, "GetErrorStatus"}, {0x00110040, nullptr, "SetSleepModeActive"}, {0x00120040, nullptr, "SetSleepModeState"}, - // clang-format off + // clang-format on }; IR_U_Interface::IR_U_Interface() { diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp index 6cff1d544..b326d7fc7 100644 --- a/src/core/hle/service/ir/ir_user.cpp +++ b/src/core/hle/service/ir/ir_user.cpp @@ -2,12 +2,112 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/kernel/event.h" +#include "core/hle/kernel/shared_memory.h" #include "core/hle/service/ir/ir.h" #include "core/hle/service/ir/ir_user.h" namespace Service { namespace IR { +static Kernel::SharedPtr<Kernel::Event> conn_status_event; +static Kernel::SharedPtr<Kernel::SharedMemory> transfer_shared_memory; + +/** + * IR::InitializeIrNopShared service function + * Inputs: + * 1 : Size of transfer buffer + * 2 : Recv buffer size + * 3 : unknown + * 4 : Send buffer size + * 5 : unknown + * 6 : BaudRate (u8) + * 7 : 0 + * 8 : Handle of transfer shared memory + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void InitializeIrNopShared(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 transfer_buff_size = cmd_buff[1]; + u32 recv_buff_size = cmd_buff[2]; + u32 unk1 = cmd_buff[3]; + u32 send_buff_size = cmd_buff[4]; + u32 unk2 = cmd_buff[5]; + u8 baud_rate = cmd_buff[6] & 0xFF; + Kernel::Handle handle = cmd_buff[8]; + + if (Kernel::g_handle_table.IsValid(handle)) { + transfer_shared_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(handle); + transfer_shared_memory->name = "IR:TransferSharedMemory"; + } + + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_IR, "(STUBBED) called, transfer_buff_size=%d, recv_buff_size=%d, " + "unk1=%d, send_buff_size=%d, unk2=%d, baud_rate=%u, handle=0x%08X", + transfer_buff_size, recv_buff_size, unk1, send_buff_size, unk2, baud_rate, handle); +} + +/** + * IR::RequireConnection service function + * Inputs: + * 1 : unknown (u8), looks like always 1 + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void RequireConnection(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + conn_status_event->Signal(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_IR, "(STUBBED) called"); +} + +/** + * IR::Disconnect service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void Disconnect(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_IR, "(STUBBED) called"); +} + +/** + * IR::GetConnectionStatusEvent service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Connection Status Event handle + */ +static void GetConnectionStatusEvent(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::conn_status_event).MoveFrom(); + + LOG_WARNING(Service_IR, "(STUBBED) called"); +} + +/** + * IR::FinalizeIrNop service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void FinalizeIrNop(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_IR, "(STUBBED) called"); +} + const Interface::FunctionInfo FunctionTable[] = { {0x00010182, nullptr, "InitializeIrNop"}, {0x00020000, FinalizeIrNop, "FinalizeIrNop"}, @@ -41,5 +141,17 @@ IR_User_Interface::IR_User_Interface() { Register(FunctionTable); } +void InitUser() { + using namespace Kernel; + + transfer_shared_memory = nullptr; + conn_status_event = Event::Create(ResetType::OneShot, "IR:ConnectionStatusEvent"); +} + +void ShutdownUser() { + transfer_shared_memory = nullptr; + conn_status_event = nullptr; +} + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir_user.h b/src/core/hle/service/ir/ir_user.h index 71c932ffa..3849bd923 100644 --- a/src/core/hle/service/ir/ir_user.h +++ b/src/core/hle/service/ir/ir_user.h @@ -18,5 +18,8 @@ public: } }; +void InitUser(); +void ShutdownUser(); + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ldr_ro/cro_helper.h b/src/core/hle/service/ldr_ro/cro_helper.h index 060d5a55f..3bc10dbdc 100644 --- a/src/core/hle/service/ldr_ro/cro_helper.h +++ b/src/core/hle/service/ldr_ro/cro_helper.h @@ -57,7 +57,7 @@ public: * @param is_crs true if the module itself is the static module * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. */ - ResultCode Rebase(VAddr crs_address, u32 cro_size, VAddr data_segment_addresss, + ResultCode Rebase(VAddr crs_address, u32 cro_size, VAddr data_segment_address, u32 data_segment_size, VAddr bss_segment_address, u32 bss_segment_size, bool is_crs); @@ -102,7 +102,7 @@ public: /** * Registers this module and adds it to the module list. * @param crs_address the virtual address of the static module - * @auto_link whether to register as an auto link module + * @param auto_link whether to register as an auto link module */ void Register(VAddr crs_address, bool auto_link); diff --git a/src/core/hle/service/ldr_ro/ldr_ro.cpp b/src/core/hle/service/ldr_ro/ldr_ro.cpp index 8d00a7577..7af76676b 100644 --- a/src/core/hle/service/ldr_ro/ldr_ro.cpp +++ b/src/core/hle/service/ldr_ro/ldr_ro.cpp @@ -6,6 +6,7 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "core/arm/arm_interface.h" +#include "core/core.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/vm_manager.h" #include "core/hle/service/ldr_ro/cro_helper.h" diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp index 0be94322c..63c334cb2 100644 --- a/src/core/hle/service/nim/nim.cpp +++ b/src/core/hle/service/nim/nim.cpp @@ -19,7 +19,7 @@ void CheckSysUpdateAvailable(Service::Interface* self) { cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 0; // No update available - LOG_WARNING(Service_NWM, "(STUBBED) called"); + LOG_WARNING(Service_NIM, "(STUBBED) called"); } void Init() { diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index 08fade320..ef6c5ebe3 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -1,16 +1,49 @@ -// Copyright 2014 Citra Emulator Project +// Copyright 2017 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cstring> +#include <unordered_map> +#include <vector> #include "common/common_types.h" #include "common/logging/log.h" +#include "core/core_timing.h" #include "core/hle/kernel/event.h" +#include "core/hle/kernel/shared_memory.h" +#include "core/hle/result.h" #include "core/hle/service/nwm/nwm_uds.h" +#include "core/memory.h" namespace Service { namespace NWM { -static Kernel::SharedPtr<Kernel::Event> uds_handle_event; +// Event that is signaled every time the connection status changes. +static Kernel::SharedPtr<Kernel::Event> connection_status_event; + +// Shared memory provided by the application to store the receive buffer. +// This is not currently used. +static Kernel::SharedPtr<Kernel::SharedMemory> recv_buffer_memory; + +// Connection status of this 3DS. +static ConnectionStatus connection_status{}; + +// Node information about the current 3DS. +// TODO(Subv): Keep an array of all nodes connected to the network, +// that data has to be retransmitted in every beacon frame. +static NodeInfo node_info; + +// Mapping of bind node ids to their respective events. +static std::unordered_map<u32, Kernel::SharedPtr<Kernel::Event>> bind_node_events; + +// The WiFi network channel that the network is currently on. +// Since we're not actually interacting with physical radio waves, this is just a dummy value. +static u8 network_channel = DefaultNetworkChannel; + +// Information about the network that we're currently connected to. +static NetworkInfo network_info; + +// Event that will generate and send the 802.11 beacon frames. +static int beacon_broadcast_event; /** * NWM_UDS::Shutdown service function @@ -32,14 +65,14 @@ static void Shutdown(Interface* self) { /** * NWM_UDS::RecvBeaconBroadcastData service function + * Returns the raw beacon data for nearby networks that match the supplied WlanCommId. * Inputs: * 1 : Output buffer max size - * 2 : Unknown - * 3 : Unknown - * 4 : MAC address? - * 6-14 : Unknown, usually zero / uninitialized? - * 15 : WLan Comm ID - * 16 : This is the ID also located at offset 0xE in the CTR-generation structure. + * 2-3 : Unknown + * 4-5 : Host MAC address. + * 6-14 : Unused + * 15 : WLan Comm Id + * 16 : Id * 17 : Value 0 * 18 : Input handle * 19 : (Size<<4) | 12 @@ -77,42 +110,274 @@ static void RecvBeaconBroadcastData(Interface* self) { /** * NWM_UDS::Initialize service function * Inputs: - * 1 : Unknown - * 2-11 : Input Structure - * 12 : Unknown u16 + * 1 : Shared memory size + * 2-11 : Input NodeInfo Structure + * 12 : 2-byte Version * 13 : Value 0 - * 14 : Handle + * 14 : Shared memory handle * Outputs: * 0 : Return header * 1 : Result of function, 0 on success, otherwise error code * 2 : Value 0 - * 3 : Output handle + * 3 : Output event handle */ static void InitializeWithVersion(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 unk1 = cmd_buff[1]; - u32 unk2 = cmd_buff[12]; - u32 value = cmd_buff[13]; - u32 handle = cmd_buff[14]; - - // Because NWM service is not implemented at all, we stub the Initialize function with an error - // code instead of success to prevent games from using the service and from causing more issues. - // The error code is from a real 3DS with wifi off, thus believed to be "network disabled". - /* - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; - cmd_buff[3] = Kernel::g_handle_table.Create(uds_handle_event) - .MoveFrom(); // TODO(purpasmart): Verify if this is a event handle - */ - cmd_buff[0] = IPC::MakeHeader(0x1B, 1, 2); - cmd_buff[1] = ResultCode(static_cast<ErrorDescription>(2), ErrorModule::UDS, - ErrorSummary::StatusChanged, ErrorLevel::Status) - .raw; - cmd_buff[2] = 0; - cmd_buff[3] = 0; - - LOG_WARNING(Service_NWM, "(STUBBED) called unk1=0x%08X, unk2=0x%08X, value=%u, handle=0x%08X", - unk1, unk2, value, handle); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1B, 12, 2); + + u32 sharedmem_size = rp.Pop<u32>(); + + // Update the node information with the data the game gave us. + rp.PopRaw(node_info); + + u16 version; + rp.PopRaw(version); + Kernel::Handle sharedmem_handle = rp.PopHandle(); + + recv_buffer_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(sharedmem_handle); + + ASSERT_MSG(recv_buffer_memory->size == sharedmem_size, "Invalid shared memory size."); + + // Reset the connection status, it contains all zeros after initialization, + // except for the actual status value. + connection_status = {}; + connection_status.status = static_cast<u32>(NetworkStatus::NotConnected); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + rb.PushCopyHandles(Kernel::g_handle_table.Create(connection_status_event).MoveFrom()); + + LOG_DEBUG(Service_NWM, "called sharedmem_size=0x%08X, version=0x%08X, sharedmem_handle=0x%08X", + sharedmem_size, version, sharedmem_handle); +} + +/** + * NWM_UDS::GetConnectionStatus service function. + * Returns the connection status structure for the currently open network connection. + * This structure contains information about the connection, + * like the number of connected nodes, etc. + * Inputs: + * 0 : Command header. + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + * 2-13 : Channel of the current WiFi network connection. + */ +static void GetConnectionStatus(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xB, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(13, 0); + + rb.Push(RESULT_SUCCESS); + rb.PushRaw(connection_status); + + LOG_DEBUG(Service_NWM, "called"); +} + +/** + * NWM_UDS::Bind service function. + * Binds a BindNodeId to a data channel and retrieves a data event. + * Inputs: + * 1 : BindNodeId + * 2 : Receive buffer size. + * 3 : u8 Data channel to bind to. + * 4 : Network node id. + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Copy handle descriptor. + * 3 : Data available event handle. + */ +static void Bind(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x12, 4, 0); + + u32 bind_node_id = rp.Pop<u32>(); + u32 recv_buffer_size = rp.Pop<u32>(); + u8 data_channel; + rp.PopRaw(data_channel); + u16 network_node_id; + rp.PopRaw(network_node_id); + + // TODO(Subv): Store the data channel and verify it when receiving data frames. + + LOG_DEBUG(Service_NWM, "called"); + + if (data_channel == 0) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::UDS, + ErrorSummary::WrongArgument, ErrorLevel::Usage)); + return; + } + + // Create a new event for this bind node. + // TODO(Subv): Signal this event when new data is received on this data channel. + auto event = Kernel::Event::Create(Kernel::ResetType::OneShot, + "NWM::BindNodeEvent" + std::to_string(bind_node_id)); + bind_node_events[bind_node_id] = event; + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + + rb.Push(RESULT_SUCCESS); + rb.PushCopyHandles(Kernel::g_handle_table.Create(event).MoveFrom()); +} + +/** + * NWM_UDS::BeginHostingNetwork service function. + * Creates a network and starts broadcasting its presence. + * Inputs: + * 1 : Passphrase buffer size. + * 3 : VAddr of the NetworkInfo structure. + * 5 : VAddr of the passphrase. + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + */ +static void BeginHostingNetwork(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1D, 1, 4); + + const u32 passphrase_size = rp.Pop<u32>(); + + size_t desc_size; + const VAddr network_info_address = rp.PopStaticBuffer(&desc_size, false); + ASSERT(desc_size == sizeof(NetworkInfo)); + const VAddr passphrase_address = rp.PopStaticBuffer(&desc_size, false); + ASSERT(desc_size == passphrase_size); + + // TODO(Subv): Store the passphrase and verify it when attempting a connection. + + LOG_DEBUG(Service_NWM, "called"); + + Memory::ReadBlock(network_info_address, &network_info, sizeof(NetworkInfo)); + + // The real UDS module throws a fatal error if this assert fails. + ASSERT_MSG(network_info.max_nodes > 1, "Trying to host a network of only one member."); + + connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsHost); + connection_status.max_nodes = network_info.max_nodes; + + // There's currently only one node in the network (the host). + connection_status.total_nodes = 1; + // The host is always the first node + connection_status.network_node_id = 1; + node_info.network_node_id = 1; + // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken. + connection_status.node_bitmask |= 1; + + // If the game has a preferred channel, use that instead. + if (network_info.channel != 0) + network_channel = network_info.channel; + + connection_status_event->Signal(); + + // Start broadcasting the network, send a beacon frame every 102.4ms. + CoreTiming::ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU), + beacon_broadcast_event, 0); + + LOG_WARNING(Service_NWM, + "An UDS network has been created, but broadcasting it is unimplemented."); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + +/** + * NWM_UDS::DestroyNetwork service function. + * Closes the network that we're currently hosting. + * Inputs: + * 0 : Command header. + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + */ +static void DestroyNetwork(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x08, 0, 0); + + // TODO(Subv): Find out what happens if this is called while + // no network is being hosted. + + // Unschedule the beacon broadcast event. + CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0); + + connection_status.status = static_cast<u8>(NetworkStatus::NotConnected); + connection_status_event->Signal(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_NWM, "called"); +} + +/** + * NWM_UDS::GetChannel service function. + * Returns the WiFi channel in which the network we're connected to is transmitting. + * Inputs: + * 0 : Command header. + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Channel of the current WiFi network connection. + */ +static void GetChannel(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1A, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + + bool is_connected = connection_status.status != static_cast<u32>(NetworkStatus::NotConnected); + + u8 channel = is_connected ? network_channel : 0; + + rb.Push(RESULT_SUCCESS); + rb.PushRaw(channel); + + LOG_DEBUG(Service_NWM, "called"); +} + +/** + * NWM_UDS::SetApplicationData service function. + * Updates the application data that is being broadcast in the beacon frames + * for the network that we're hosting. + * Inputs: + * 1 : Data size. + * 3 : VAddr of the data. + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Channel of the current WiFi network connection. + */ +static void SetApplicationData(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1A, 1, 2); + + u32 size = rp.Pop<u32>(); + + size_t desc_size; + const VAddr address = rp.PopStaticBuffer(&desc_size, false); + ASSERT(desc_size == size); + + LOG_DEBUG(Service_NWM, "called"); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + if (size > ApplicationDataSize) { + rb.Push(ResultCode(ErrorDescription::TooLarge, ErrorModule::UDS, + ErrorSummary::WrongArgument, ErrorLevel::Usage)); + return; + } + + network_info.application_data_size = size; + Memory::ReadBlock(address, network_info.application_data.data(), size); + + rb.Push(RESULT_SUCCESS); +} + +// Sends a 802.11 beacon frame with information about the current network. +static void BeaconBroadcastCallback(u64 userdata, int cycles_late) { + // Don't do anything if we're not actually hosting a network + if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) + return; + + // TODO(Subv): Actually generate the beacon and send it. + + // Start broadcasting the network, send a beacon frame every 102.4ms. + CoreTiming::ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU) - cycles_late, + beacon_broadcast_event, 0); } const Interface::FunctionInfo FunctionTable[] = { @@ -123,23 +388,23 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00050040, nullptr, "EjectClient"}, {0x00060000, nullptr, "EjectSpectator"}, {0x00070080, nullptr, "UpdateNetworkAttribute"}, - {0x00080000, nullptr, "DestroyNetwork"}, + {0x00080000, DestroyNetwork, "DestroyNetwork"}, {0x00090442, nullptr, "ConnectNetwork (deprecated)"}, {0x000A0000, nullptr, "DisconnectNetwork"}, - {0x000B0000, nullptr, "GetConnectionStatus"}, + {0x000B0000, GetConnectionStatus, "GetConnectionStatus"}, {0x000D0040, nullptr, "GetNodeInformation"}, {0x000E0006, nullptr, "DecryptBeaconData (deprecated)"}, {0x000F0404, RecvBeaconBroadcastData, "RecvBeaconBroadcastData"}, - {0x00100042, nullptr, "SetApplicationData"}, + {0x00100042, SetApplicationData, "SetApplicationData"}, {0x00110040, nullptr, "GetApplicationData"}, - {0x00120100, nullptr, "Bind"}, + {0x00120100, Bind, "Bind"}, {0x00130040, nullptr, "Unbind"}, {0x001400C0, nullptr, "PullPacket"}, {0x00150080, nullptr, "SetMaxSendDelay"}, {0x00170182, nullptr, "SendTo"}, - {0x001A0000, nullptr, "GetChannel"}, + {0x001A0000, GetChannel, "GetChannel"}, {0x001B0302, InitializeWithVersion, "InitializeWithVersion"}, - {0x001D0044, nullptr, "BeginHostingNetwork"}, + {0x001D0044, BeginHostingNetwork, "BeginHostingNetwork"}, {0x001E0084, nullptr, "ConnectToNetwork"}, {0x001F0006, nullptr, "DecryptBeaconData"}, {0x00200040, nullptr, "Flush"}, @@ -148,13 +413,25 @@ const Interface::FunctionInfo FunctionTable[] = { }; NWM_UDS::NWM_UDS() { - uds_handle_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "NWM::uds_handle_event"); + connection_status_event = + Kernel::Event::Create(Kernel::ResetType::OneShot, "NWM::connection_status_event"); Register(FunctionTable); + + beacon_broadcast_event = + CoreTiming::RegisterEvent("UDS::BeaconBroadcastCallback", BeaconBroadcastCallback); } NWM_UDS::~NWM_UDS() { - uds_handle_event = nullptr; + network_info = {}; + bind_node_events.clear(); + connection_status_event = nullptr; + recv_buffer_memory = nullptr; + + connection_status = {}; + connection_status.status = static_cast<u32>(NetworkStatus::NotConnected); + + CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0); } } // namespace NWM diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h index 55db748f6..65349f9fd 100644 --- a/src/core/hle/service/nwm/nwm_uds.h +++ b/src/core/hle/service/nwm/nwm_uds.h @@ -4,6 +4,10 @@ #pragma once +#include <array> +#include <cstddef> +#include "common/common_types.h" +#include "common/swap.h" #include "core/hle/service/service.h" // Local-WLAN service @@ -11,6 +15,68 @@ namespace Service { namespace NWM { +const size_t ApplicationDataSize = 0xC8; +const u8 DefaultNetworkChannel = 11; + +// Number of milliseconds in a TU. +const double MillisecondsPerTU = 1.024; +// Interval measured in TU, the default value is 100TU = 102.4ms +const u16 DefaultBeaconInterval = 100; + +struct NodeInfo { + u64_le friend_code_seed; + std::array<u16_le, 10> username; + INSERT_PADDING_BYTES(4); + u16_le network_node_id; + INSERT_PADDING_BYTES(6); +}; + +static_assert(sizeof(NodeInfo) == 40, "NodeInfo has incorrect size."); + +enum class NetworkStatus { + NotConnected = 3, + ConnectedAsHost = 6, + ConnectedAsClient = 9, + ConnectedAsSpectator = 10, +}; + +struct ConnectionStatus { + u32_le status; + INSERT_PADDING_WORDS(1); + u16_le network_node_id; + INSERT_PADDING_BYTES(2); + INSERT_PADDING_BYTES(32); + u8 total_nodes; + u8 max_nodes; + u16_le node_bitmask; +}; + +static_assert(sizeof(ConnectionStatus) == 0x30, "ConnectionStatus has incorrect size."); + +struct NetworkInfo { + std::array<u8, 6> host_mac_address; + u8 channel; + INSERT_PADDING_BYTES(1); + u8 initialized; + INSERT_PADDING_BYTES(3); + std::array<u8, 3> oui_value; + u8 oui_type; + // This field is received as BigEndian from the game. + u32_be wlan_comm_id; + u8 id; + INSERT_PADDING_BYTES(1); + u16_be attributes; + u32_be network_id; + u8 total_nodes; + u8 max_nodes; + INSERT_PADDING_BYTES(2); + INSERT_PADDING_BYTES(0x1F); + u8 application_data_size; + std::array<u8, ApplicationDataSize> application_data; +}; + +static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size."); + class NWM_UDS final : public Interface { public: NWM_UDS(); diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index 8ff808fd9..e373ed47a 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp @@ -92,8 +92,7 @@ void GetSoftwareClosedFlag(Service::Interface* self) { LOG_WARNING(Service_PTM, "(STUBBED) called"); } -void CheckNew3DS(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); +void CheckNew3DS(IPC::RequestBuilder& rb) { const bool is_new_3ds = Settings::values.is_new_3ds; if (is_new_3ds) { @@ -101,12 +100,17 @@ void CheckNew3DS(Service::Interface* self) { "settings. Citra does not fully support New 3DS emulation yet!"); } - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = is_new_3ds ? 1 : 0; + rb.Push(RESULT_SUCCESS); + rb.Push(is_new_3ds); LOG_WARNING(Service_PTM, "(STUBBED) called isNew3DS = 0x%08x", static_cast<u32>(is_new_3ds)); } +void CheckNew3DS(Service::Interface* self) { + IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x40A, 0, 0); // 0x040A0000 + CheckNew3DS(rb); +} + void Init() { AddService(new PTM_Gets); AddService(new PTM_Play); @@ -134,9 +138,9 @@ void Init() { ASSERT_MSG(archive_result.Succeeded(), "Could not open the PTM SharedExtSaveData archive!"); FileSys::Path gamecoin_path("/gamecoin.dat"); + Service::FS::CreateFileInArchive(*archive_result, gamecoin_path, sizeof(GameCoin)); FileSys::Mode open_mode = {}; open_mode.write_flag.Assign(1); - open_mode.create_flag.Assign(1); // Open the file and write the default gamecoin information auto gamecoin_result = Service::FS::OpenFileFromArchive(*archive_result, gamecoin_path, open_mode); diff --git a/src/core/hle/service/ptm/ptm.h b/src/core/hle/service/ptm/ptm.h index a1a628012..683fb445b 100644 --- a/src/core/hle/service/ptm/ptm.h +++ b/src/core/hle/service/ptm/ptm.h @@ -5,6 +5,7 @@ #pragma once #include "common/common_types.h" +#include "core/hle/ipc_helpers.h" namespace Service { @@ -97,6 +98,7 @@ void GetSoftwareClosedFlag(Interface* self); * 2: u8 output: 0 = Old3DS, 1 = New3DS. */ void CheckNew3DS(Interface* self); +void CheckNew3DS(IPC::RequestBuilder& rb); /// Initialize the PTM service void Init(); diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index a7ba7688f..e6a5f1417 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -10,6 +10,7 @@ #include <boost/container/flat_map.hpp> #include "common/common_types.h" #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" diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp index dcc5c3c90..530614e6f 100644 --- a/src/core/hle/service/soc_u.cpp +++ b/src/core/hle/service/soc_u.cpp @@ -362,18 +362,18 @@ static void Socket(Interface* self) { return; } - u32 socket_handle = static_cast<u32>(::socket(domain, type, protocol)); + u32 ret = static_cast<u32>(::socket(domain, type, protocol)); - if ((s32)socket_handle != SOCKET_ERROR_VALUE) - open_sockets[socket_handle] = {socket_handle, true}; + if ((s32)ret != SOCKET_ERROR_VALUE) + open_sockets[ret] = {ret, true}; int result = 0; - if ((s32)socket_handle == SOCKET_ERROR_VALUE) - result = TranslateError(GET_ERRNO); + if ((s32)ret == SOCKET_ERROR_VALUE) + ret = TranslateError(GET_ERRNO); cmd_buffer[0] = IPC::MakeHeader(2, 2, 0); cmd_buffer[1] = result; - cmd_buffer[2] = socket_handle; + cmd_buffer[2] = ret; } static void Bind(Interface* self) { @@ -393,15 +393,15 @@ static void Bind(Interface* self) { sockaddr sock_addr = CTRSockAddr::ToPlatform(ctr_sock_addr); - int res = ::bind(socket_handle, &sock_addr, std::max<u32>(sizeof(sock_addr), len)); + int ret = ::bind(socket_handle, &sock_addr, std::max<u32>(sizeof(sock_addr), len)); int result = 0; - if (res != 0) - result = TranslateError(GET_ERRNO); + if (ret != 0) + ret = TranslateError(GET_ERRNO); cmd_buffer[0] = IPC::MakeHeader(5, 2, 0); cmd_buffer[1] = result; - cmd_buffer[2] = res; + cmd_buffer[2] = ret; } static void Fcntl(Interface* self) { @@ -426,8 +426,7 @@ static void Fcntl(Interface* self) { #else int ret = ::fcntl(socket_handle, F_GETFL, 0); if (ret == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); - posix_ret = -1; + posix_ret = TranslateError(GET_ERRNO); return; } posix_ret = 0; @@ -439,8 +438,7 @@ static void Fcntl(Interface* self) { unsigned long tmp = (ctr_arg & 4 /* O_NONBLOCK */) ? 1 : 0; int ret = ioctlsocket(socket_handle, FIONBIO, &tmp); if (ret == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); - posix_ret = -1; + posix_ret = TranslateError(GET_ERRNO); return; } auto iter = open_sockets.find(socket_handle); @@ -449,8 +447,7 @@ static void Fcntl(Interface* self) { #else int flags = ::fcntl(socket_handle, F_GETFL, 0); if (flags == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); - posix_ret = -1; + posix_ret = TranslateError(GET_ERRNO); return; } @@ -460,15 +457,13 @@ static void Fcntl(Interface* self) { int ret = ::fcntl(socket_handle, F_SETFL, flags); if (ret == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); - posix_ret = -1; + posix_ret = TranslateError(GET_ERRNO); return; } #endif } else { LOG_ERROR(Service_SOC, "Unsupported command (%d) in fcntl call", ctr_cmd); - result = TranslateError(EINVAL); // TODO: Find the correct error - posix_ret = -1; + posix_ret = TranslateError(EINVAL); // TODO: Find the correct error return; } } @@ -481,7 +476,7 @@ static void Listen(Interface* self) { int ret = ::listen(socket_handle, backlog); int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[0] = IPC::MakeHeader(3, 2, 0); cmd_buffer[1] = result; @@ -504,7 +499,7 @@ static void Accept(Interface* self) { int result = 0; if ((s32)ret == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); } else { CTRSockAddr ctr_addr = CTRSockAddr::FromPlatform(addr); Memory::WriteBlock(cmd_buffer[0x104 >> 2], &ctr_addr, sizeof(ctr_addr)); @@ -545,7 +540,7 @@ static void Close(Interface* self) { int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[2] = ret; cmd_buffer[1] = result; @@ -589,7 +584,7 @@ static void SendTo(Interface* self) { int result = 0; if (ret == SOCKET_ERROR_VALUE) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[2] = ret; cmd_buffer[1] = result; @@ -638,7 +633,7 @@ static void RecvFrom(Interface* self) { int result = 0; int total_received = ret; if (ret == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); total_received = 0; } else { // Write only the data we received to avoid overwriting parts of the buffer with zeros @@ -673,7 +668,7 @@ static void Poll(Interface* self) { std::vector<pollfd> platform_pollfd(nfds); std::transform(ctr_fds.begin(), ctr_fds.end(), platform_pollfd.begin(), CTRPollFD::ToPlatform); - const int ret = ::poll(platform_pollfd.data(), nfds, timeout); + int ret = ::poll(platform_pollfd.data(), nfds, timeout); // Now update the output pollfd structure std::transform(platform_pollfd.begin(), platform_pollfd.end(), ctr_fds.begin(), @@ -683,7 +678,7 @@ static void Poll(Interface* self) { int result = 0; if (ret == SOCKET_ERROR_VALUE) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[1] = result; cmd_buffer[2] = ret; @@ -710,7 +705,7 @@ static void GetSockName(Interface* self) { int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[2] = ret; cmd_buffer[1] = result; @@ -724,7 +719,7 @@ static void Shutdown(Interface* self) { int ret = ::shutdown(socket_handle, how); int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[2] = ret; cmd_buffer[1] = result; } @@ -750,7 +745,7 @@ static void GetPeerName(Interface* self) { int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[2] = ret; cmd_buffer[1] = result; @@ -777,7 +772,7 @@ static void Connect(Interface* self) { int ret = ::connect(socket_handle, &input_addr, sizeof(input_addr)); int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[0] = IPC::MakeHeader(6, 2, 0); cmd_buffer[1] = result; @@ -815,7 +810,7 @@ static void GetSockOpt(Interface* self) { int optname = TranslateSockOpt(cmd_buffer[3]); socklen_t optlen = (socklen_t)cmd_buffer[4]; - int ret = -1; + int ret = 0; int err = 0; if (optname < 0) { @@ -830,9 +825,8 @@ static void GetSockOpt(Interface* self) { // >> 2 = convert to u32 offset instead of byte offset (cmd_buffer = u32*) char* optval = reinterpret_cast<char*>(Memory::GetPointer(cmd_buffer[0x104 >> 2])); - ret = ::getsockopt(socket_handle, level, optname, optval, &optlen); - err = 0; - if (ret == SOCKET_ERROR_VALUE) { + err = ::getsockopt(socket_handle, level, optname, optval, &optlen); + if (err == SOCKET_ERROR_VALUE) { err = TranslateError(GET_ERRNO); } } @@ -849,7 +843,7 @@ static void SetSockOpt(Interface* self) { u32 level = cmd_buffer[2]; int optname = TranslateSockOpt(cmd_buffer[3]); - int ret = -1; + int ret = 0; int err = 0; if (optname < 0) { @@ -862,9 +856,8 @@ static void SetSockOpt(Interface* self) { socklen_t optlen = static_cast<socklen_t>(cmd_buffer[4]); const char* optval = reinterpret_cast<const char*>(Memory::GetPointer(cmd_buffer[8])); - ret = static_cast<u32>(::setsockopt(socket_handle, level, optname, optval, optlen)); - err = 0; - if (ret == SOCKET_ERROR_VALUE) { + err = static_cast<u32>(::setsockopt(socket_handle, level, optname, optval, optlen)); + if (err == SOCKET_ERROR_VALUE) { err = TranslateError(GET_ERRNO); } } diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp index 31bb466fc..c0837d49d 100644 --- a/src/core/hle/service/y2r_u.cpp +++ b/src/core/hle/service/y2r_u.cpp @@ -189,11 +189,9 @@ static void SetSpacialDithering(Interface* self) { * 2 : u8, 0 = Disabled, 1 = Enabled */ static void GetSpacialDithering(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[0] = IPC::MakeHeader(0xA, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = spacial_dithering_enabled; + IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0xA, 2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(spacial_dithering_enabled != 0); LOG_WARNING(Service_Y2R, "(STUBBED) called"); } @@ -281,37 +279,39 @@ static void GetTransferEndEvent(Interface* self) { } static void SetSendingY(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + // The helper should be passed by argument to the function + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x00100102); + conversion.src_Y.address = rp.Pop<u32>(); + conversion.src_Y.image_size = rp.Pop<u32>(); + conversion.src_Y.transfer_unit = rp.Pop<u32>(); + conversion.src_Y.gap = rp.Pop<u32>(); + Kernel::Handle src_process_handle = rp.PopHandle(); - conversion.src_Y.address = cmd_buff[1]; - conversion.src_Y.image_size = cmd_buff[2]; - conversion.src_Y.transfer_unit = cmd_buff[3]; - conversion.src_Y.gap = cmd_buff[4]; - - cmd_buff[0] = IPC::MakeHeader(0x10, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "src_process_handle=0x%08X", conversion.src_Y.image_size, conversion.src_Y.transfer_unit, conversion.src_Y.gap, - cmd_buff[6]); + src_process_handle); } static void SetSendingU(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - conversion.src_U.address = cmd_buff[1]; - conversion.src_U.image_size = cmd_buff[2]; - conversion.src_U.transfer_unit = cmd_buff[3]; - conversion.src_U.gap = cmd_buff[4]; + // The helper should be passed by argument to the function + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x00110102); + conversion.src_U.address = rp.Pop<u32>(); + conversion.src_U.image_size = rp.Pop<u32>(); + conversion.src_U.transfer_unit = rp.Pop<u32>(); + conversion.src_U.gap = rp.Pop<u32>(); + Kernel::Handle src_process_handle = rp.PopHandle(); - cmd_buff[0] = IPC::MakeHeader(0x11, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "src_process_handle=0x%08X", conversion.src_U.image_size, conversion.src_U.transfer_unit, conversion.src_U.gap, - cmd_buff[6]); + src_process_handle); } static void SetSendingV(Interface* self) { @@ -561,11 +561,10 @@ static void GetAlpha(Interface* self) { } static void SetDitheringWeightParams(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - std::memcpy(&dithering_weight_params, &cmd_buff[1], sizeof(DitheringWeightParams)); - - cmd_buff[0] = IPC::MakeHeader(0x24, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x24, 8, 0); // 0x240200 + rp.PopRaw(dithering_weight_params); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called"); } diff --git a/src/core/hle/shared_page.cpp b/src/core/hle/shared_page.cpp index d0d92487d..5978ccdd4 100644 --- a/src/core/hle/shared_page.cpp +++ b/src/core/hle/shared_page.cpp @@ -6,6 +6,7 @@ #include <cstring> #include <ctime> #include "core/core_timing.h" +#include "core/hle/service/ptm/ptm.h" #include "core/hle/shared_page.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -73,6 +74,12 @@ void Init() { // Some games wait until this value becomes 0x1, before asking running_hw shared_page.unknown_value = 0x1; + // Set to a completely full battery + shared_page.battery_state.charge_level.Assign( + static_cast<u8>(Service::PTM::ChargeLevels::CompletelyFull)); + shared_page.battery_state.is_adapter_connected.Assign(1); + shared_page.battery_state.is_charging.Assign(1); + update_time_event = CoreTiming::RegisterEvent("SharedPage::UpdateTimeCallback", UpdateTimeCallback); CoreTiming::ScheduleEvent(0, update_time_event); diff --git a/src/core/hle/shared_page.h b/src/core/hle/shared_page.h index 106e47efc..864695ae1 100644 --- a/src/core/hle/shared_page.h +++ b/src/core/hle/shared_page.h @@ -10,6 +10,7 @@ * write access, according to 3dbrew; this is not emulated) */ +#include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" @@ -29,6 +30,13 @@ struct DateTime { }; static_assert(sizeof(DateTime) == 0x20, "Datetime size is wrong"); +union BatteryState { + u8 raw; + BitField<0, 1, u8> is_adapter_connected; + BitField<1, 1, u8> is_charging; + BitField<2, 3, u8> charge_level; +}; + struct SharedPageDef { // Most of these names are taken from the 3dbrew page linked above. u32_le date_time_counter; // 0 @@ -44,7 +52,7 @@ struct SharedPageDef { INSERT_PADDING_BYTES(0x80 - 0x68); // 68 float_le sliderstate_3d; // 80 u8 ledstate_3d; // 84 - INSERT_PADDING_BYTES(1); // 85 + BatteryState battery_state; // 85 u8 unknown_value; // 86 INSERT_PADDING_BYTES(0xA0 - 0x87); // 87 u64_le menu_title_id; // A0 diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index 96db39ad9..2db823c61 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -467,8 +467,8 @@ static void Break(u8 break_reason) { } /// Used to output a message on a debug hardware unit - does nothing on a retail unit -static void OutputDebugString(const char* string) { - LOG_DEBUG(Debug_Emulated, "%s", string); +static void OutputDebugString(const char* string, int len) { + LOG_DEBUG(Debug_Emulated, "%.*s", len, string); } /// Get resource limit @@ -556,11 +556,21 @@ static ResultCode CreateThread(Kernel::Handle* out_handle, s32 priority, u32 ent break; } - if (processor_id == THREADPROCESSORID_1 || processor_id == THREADPROCESSORID_ALL || - (processor_id == THREADPROCESSORID_DEFAULT && - Kernel::g_current_process->ideal_processor == THREADPROCESSORID_1)) { - LOG_WARNING(Kernel_SVC, - "Newly created thread is allowed to be run in the SysCore, unimplemented."); + if (processor_id == THREADPROCESSORID_ALL) { + LOG_INFO(Kernel_SVC, + "Newly created thread is allowed to be run in any Core, unimplemented."); + } + + if (processor_id == THREADPROCESSORID_DEFAULT && + Kernel::g_current_process->ideal_processor == THREADPROCESSORID_1) { + LOG_WARNING( + Kernel_SVC, + "Newly created thread is allowed to be run in the SysCore (Core1), unimplemented."); + } + + if (processor_id == THREADPROCESSORID_1) { + LOG_ERROR(Kernel_SVC, + "Newly created thread must run in the SysCore (Core1), unimplemented."); } CASCADE_RESULT(SharedPtr<Thread> thread, Kernel::Thread::Create(name, entry_point, priority, @@ -837,6 +847,11 @@ 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); + } + SharedPtr<Timer> timer = Kernel::g_handle_table.Get<Timer>(handle); if (timer == nullptr) return ERR_INVALID_HANDLE; diff --git a/src/core/hw/aes/arithmetic128.cpp b/src/core/hw/aes/arithmetic128.cpp new file mode 100644 index 000000000..55b954a52 --- /dev/null +++ b/src/core/hw/aes/arithmetic128.cpp @@ -0,0 +1,47 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <functional> +#include "core/hw/aes/arithmetic128.h" + +namespace HW { +namespace AES { + +AESKey Lrot128(const AESKey& in, u32 rot) { + AESKey out; + rot %= 128; + const u32 byte_shift = rot / 8; + const u32 bit_shift = rot % 8; + + for (u32 i = 0; i < 16; i++) { + const u32 wrap_index_a = (i + byte_shift) % 16; + const u32 wrap_index_b = (i + byte_shift + 1) % 16; + out[i] = ((in[wrap_index_a] << bit_shift) | (in[wrap_index_b] >> (8 - bit_shift))) & 0xFF; + } + return out; +} + +AESKey Add128(const AESKey& a, const AESKey& b) { + AESKey out; + u32 carry = 0; + u32 sum = 0; + + for (int i = 15; i >= 0; i--) { + sum = a[i] + b[i] + carry; + carry = sum >> 8; + out[i] = static_cast<u8>(sum & 0xff); + } + + return out; +} + +AESKey Xor128(const AESKey& a, const AESKey& b) { + AESKey out; + std::transform(a.cbegin(), a.cend(), b.cbegin(), out.begin(), std::bit_xor<>()); + return out; +} + +} // namespace AES +} // namespace HW diff --git a/src/core/hw/aes/arithmetic128.h b/src/core/hw/aes/arithmetic128.h new file mode 100644 index 000000000..d670e2ce2 --- /dev/null +++ b/src/core/hw/aes/arithmetic128.h @@ -0,0 +1,17 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "core/hw/aes/key.h" + +namespace HW { +namespace AES { +AESKey Lrot128(const AESKey& in, u32 rot); +AESKey Add128(const AESKey& a, const AESKey& b); +AESKey Xor128(const AESKey& a, const AESKey& b); + +} // namspace AES +} // namespace HW diff --git a/src/core/hw/aes/ccm.cpp b/src/core/hw/aes/ccm.cpp new file mode 100644 index 000000000..dc7035ab6 --- /dev/null +++ b/src/core/hw/aes/ccm.cpp @@ -0,0 +1,95 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <cryptopp/aes.h> +#include <cryptopp/ccm.h> +#include <cryptopp/cryptlib.h> +#include <cryptopp/filters.h> +#include "common/alignment.h" +#include "common/logging/log.h" +#include "core/hw/aes/ccm.h" +#include "core/hw/aes/key.h" + +namespace HW { +namespace AES { + +namespace { + +// 3DS uses a non-standard AES-CCM algorithm, so we need to derive a sub class from the standard one +// and override with the non-standard part. +using CryptoPP::lword; +using CryptoPP::AES; +using CryptoPP::CCM_Final; +using CryptoPP::CCM_Base; +template <bool T_IsEncryption> +class CCM_3DSVariant_Final : public CCM_Final<AES, CCM_MAC_SIZE, T_IsEncryption> { +public: + void UncheckedSpecifyDataLengths(lword header_length, lword message_length, + lword footer_length) override { + // 3DS uses the aligned size to generate B0 for authentication, instead of the original size + lword aligned_message_length = Common::AlignUp(message_length, AES_BLOCK_SIZE); + CCM_Base::UncheckedSpecifyDataLengths(header_length, aligned_message_length, footer_length); + CCM_Base::m_messageLength = message_length; // restore the actual message size + } +}; + +class CCM_3DSVariant { +public: + using Encryption = CCM_3DSVariant_Final<true>; + using Decryption = CCM_3DSVariant_Final<false>; +}; + +} // namespace + +std::vector<u8> EncryptSignCCM(const std::vector<u8>& pdata, const CCMNonce& nonce, + size_t slot_id) { + if (!IsNormalKeyAvailable(slot_id)) { + LOG_ERROR(HW_AES, "Key slot %d not available. Will use zero key.", slot_id); + } + const AESKey normal = GetNormalKey(slot_id); + std::vector<u8> cipher(pdata.size() + CCM_MAC_SIZE); + + try { + CCM_3DSVariant::Encryption e; + e.SetKeyWithIV(normal.data(), AES_BLOCK_SIZE, nonce.data(), CCM_NONCE_SIZE); + e.SpecifyDataLengths(0, pdata.size(), 0); + CryptoPP::ArraySource as(pdata.data(), pdata.size(), true, + new CryptoPP::AuthenticatedEncryptionFilter( + e, new CryptoPP::ArraySink(cipher.data(), cipher.size()))); + } catch (const CryptoPP::Exception& e) { + LOG_ERROR(HW_AES, "FAILED with: %s", e.what()); + } + return cipher; +} + +std::vector<u8> DecryptVerifyCCM(const std::vector<u8>& cipher, const CCMNonce& nonce, + size_t slot_id) { + if (!IsNormalKeyAvailable(slot_id)) { + LOG_ERROR(HW_AES, "Key slot %d not available. Will use zero key.", slot_id); + } + const AESKey normal = GetNormalKey(slot_id); + const std::size_t pdata_size = cipher.size() - CCM_MAC_SIZE; + std::vector<u8> pdata(pdata_size); + + try { + CCM_3DSVariant::Decryption d; + d.SetKeyWithIV(normal.data(), AES_BLOCK_SIZE, nonce.data(), CCM_NONCE_SIZE); + d.SpecifyDataLengths(0, pdata_size, 0); + CryptoPP::AuthenticatedDecryptionFilter df( + d, new CryptoPP::ArraySink(pdata.data(), pdata_size)); + CryptoPP::ArraySource as(cipher.data(), cipher.size(), true, new CryptoPP::Redirector(df)); + if (!df.GetLastResult()) { + LOG_ERROR(HW_AES, "FAILED"); + return {}; + } + } catch (const CryptoPP::Exception& e) { + LOG_ERROR(HW_AES, "FAILED with: %s", e.what()); + return {}; + } + return pdata; +} + +} // namespace AES +} // namespace HW diff --git a/src/core/hw/aes/ccm.h b/src/core/hw/aes/ccm.h new file mode 100644 index 000000000..bf4146e80 --- /dev/null +++ b/src/core/hw/aes/ccm.h @@ -0,0 +1,40 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include <vector> +#include "common/common_types.h" + +namespace HW { +namespace AES { + +constexpr size_t CCM_NONCE_SIZE = 12; +constexpr size_t CCM_MAC_SIZE = 16; + +using CCMNonce = std::array<u8, CCM_NONCE_SIZE>; + +/** + * Encrypts and adds a MAC to the given data using AES-CCM algorithm. + * @param pdata The plain text data to encrypt + * @param nonce The nonce data to use for encryption + * @param slot_id The slot ID of the key to use for encryption + * @returns a vector of u8 containing the encrypted data with MAC at the end + */ +std::vector<u8> EncryptSignCCM(const std::vector<u8>& pdata, const CCMNonce& nonce, size_t slot_id); + +/** + * Decrypts and verify the MAC of the given data using AES-CCM algorithm. + * @param cipher The cipher text data to decrypt, with MAC at the end to verify + * @param nonce The nonce data to use for decryption + * @param slot_id The slot ID of the key to use for decryption + * @returns a vector of u8 containing the decrypted data; an empty vector if the verification fails + */ +std::vector<u8> DecryptVerifyCCM(const std::vector<u8>& cipher, const CCMNonce& nonce, + size_t slot_id); + +} // namespace AES +} // namespace HW diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp new file mode 100644 index 000000000..4e8a8a59a --- /dev/null +++ b/src/core/hw/aes/key.cpp @@ -0,0 +1,173 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <exception> +#include <sstream> +#include <boost/optional.hpp> +#include "common/common_paths.h" +#include "common/file_util.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/hw/aes/arithmetic128.h" +#include "core/hw/aes/key.h" + +namespace HW { +namespace AES { + +namespace { + +boost::optional<AESKey> generator_constant; + +struct KeySlot { + boost::optional<AESKey> x; + boost::optional<AESKey> y; + boost::optional<AESKey> normal; + + void SetKeyX(const AESKey& key) { + x = key; + if (y && generator_constant) { + GenerateNormalKey(); + } + } + + void SetKeyY(const AESKey& key) { + y = key; + if (x && generator_constant) { + GenerateNormalKey(); + } + } + + void SetNormalKey(const AESKey& key) { + normal = key; + } + + void GenerateNormalKey() { + normal = Lrot128(Add128(Xor128(Lrot128(*x, 2), *y), *generator_constant), 87); + } + + void Clear() { + x.reset(); + y.reset(); + normal.reset(); + } +}; + +std::array<KeySlot, KeySlotID::MaxKeySlotID> key_slots; + +void ClearAllKeys() { + for (KeySlot& slot : key_slots) { + slot.Clear(); + } + generator_constant.reset(); +} + +AESKey HexToKey(const std::string& hex) { + if (hex.size() < 32) { + throw std::invalid_argument("hex string is too short"); + } + + AESKey key; + for (size_t i = 0; i < key.size(); ++i) { + key[i] = static_cast<u8>(std::stoi(hex.substr(i * 2, 2), 0, 16)); + } + + return key; +} + +void LoadPresetKeys() { + const std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + AES_KEYS; + FileUtil::CreateFullPath(filepath); // Create path if not already created + std::ifstream file; + OpenFStream(file, filepath, std::ios_base::in); + if (!file) { + return; + } + + while (!file.eof()) { + std::string line; + std::getline(file, line); + std::vector<std::string> parts; + Common::SplitString(line, '=', parts); + if (parts.size() != 2) { + LOG_ERROR(HW_AES, "Failed to parse %s", line.c_str()); + continue; + } + + const std::string& name = parts[0]; + AESKey key; + try { + key = HexToKey(parts[1]); + } catch (const std::logic_error& e) { + LOG_ERROR(HW_AES, "Invalid key %s: %s", parts[1].c_str(), e.what()); + continue; + } + + if (name == "generator") { + generator_constant = key; + continue; + } + + size_t slot_id; + char key_type; + if (std::sscanf(name.c_str(), "slot0x%zXKey%c", &slot_id, &key_type) != 2) { + LOG_ERROR(HW_AES, "Invalid key name %s", name.c_str()); + continue; + } + + if (slot_id >= MaxKeySlotID) { + LOG_ERROR(HW_AES, "Out of range slot ID 0x%zX", slot_id); + continue; + } + + switch (key_type) { + case 'X': + key_slots.at(slot_id).SetKeyX(key); + break; + case 'Y': + key_slots.at(slot_id).SetKeyY(key); + break; + case 'N': + key_slots.at(slot_id).SetNormalKey(key); + break; + default: + LOG_ERROR(HW_AES, "Invalid key type %c", key_type); + break; + } + } +} + +} // namespace + +void InitKeys() { + ClearAllKeys(); + LoadPresetKeys(); +} + +void SetGeneratorConstant(const AESKey& key) { + generator_constant = key; +} + +void SetKeyX(size_t slot_id, const AESKey& key) { + key_slots.at(slot_id).SetKeyX(key); +} + +void SetKeyY(size_t slot_id, const AESKey& key) { + key_slots.at(slot_id).SetKeyY(key); +} + +void SetNormalKey(size_t slot_id, const AESKey& key) { + key_slots.at(slot_id).SetNormalKey(key); +} + +bool IsNormalKeyAvailable(size_t slot_id) { + return key_slots.at(slot_id).normal.is_initialized(); +} + +AESKey GetNormalKey(size_t slot_id) { + return key_slots.at(slot_id).normal.value_or(AESKey{}); +} + +} // namespace AES +} // namespace HW diff --git a/src/core/hw/aes/key.h b/src/core/hw/aes/key.h new file mode 100644 index 000000000..b01d04f13 --- /dev/null +++ b/src/core/hw/aes/key.h @@ -0,0 +1,35 @@ +// Copyright 2017 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" + +namespace HW { +namespace AES { + +enum KeySlotID : size_t { + APTWrap = 0x31, + + MaxKeySlotID = 0x40, +}; + +constexpr size_t AES_BLOCK_SIZE = 16; + +using AESKey = std::array<u8, AES_BLOCK_SIZE>; + +void InitKeys(); + +void SetGeneratorConstant(const AESKey& key); +void SetKeyX(size_t slot_id, const AESKey& key); +void SetKeyY(size_t slot_id, const AESKey& key); +void SetNormalKey(size_t slot_id, const AESKey& key); + +bool IsNormalKeyAvailable(size_t slot_id); +AESKey GetNormalKey(size_t slot_id); + +} // namspace AES +} // namespace HW diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index fa8c13d36..42809c731 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -8,17 +8,13 @@ #include "common/color.h" #include "common/common_types.h" #include "common/logging/log.h" -#include "common/math_util.h" #include "common/microprofile.h" -#include "common/thread.h" -#include "common/timer.h" #include "common/vector_math.h" #include "core/core_timing.h" #include "core/hle/service/gsp_gpu.h" #include "core/hw/gpu.h" #include "core/hw/hw.h" #include "core/memory.h" -#include "core/settings.h" #include "core/tracer/recorder.h" #include "video_core/command_processor.h" #include "video_core/debug_utils/debug_utils.h" @@ -32,19 +28,9 @@ namespace GPU { Regs g_regs; /// 268MHz CPU clocks / 60Hz frames per second -const u64 frame_ticks = BASE_CLOCK_RATE_ARM11 / 60; +const u64 frame_ticks = BASE_CLOCK_RATE_ARM11 / SCREEN_REFRESH_RATE; /// Event id for CoreTiming static int vblank_event; -/// Total number of frames drawn -static u64 frame_count; -/// Start clock for frame limiter -static u32 time_point; -/// Total delay caused by slow frames -static float time_delay; -constexpr float FIXED_FRAME_TIME = 1000.0f / 60; -// Max lag caused by slow frames. Can be adjusted to compensate for too many slow frames. Higher -// values increases time needed to limit frame rate after spikes -constexpr float MAX_LAG_TIME = 18; template <typename T> inline void Read(T& var, const u32 raw_addr) { @@ -522,24 +508,8 @@ template void Write<u32>(u32 addr, const u32 data); template void Write<u16>(u32 addr, const u16 data); template void Write<u8>(u32 addr, const u8 data); -static void FrameLimiter() { - time_delay += FIXED_FRAME_TIME; - time_delay = MathUtil::Clamp(time_delay, -MAX_LAG_TIME, MAX_LAG_TIME); - s32 desired_time = static_cast<s32>(time_delay); - s32 elapsed_time = static_cast<s32>(Common::Timer::GetTimeMs() - time_point); - - if (elapsed_time < desired_time) { - Common::SleepCurrentThread(desired_time - elapsed_time); - } - - u32 frame_time = Common::Timer::GetTimeMs() - time_point; - - time_delay -= frame_time; -} - /// Update hardware static void VBlankCallback(u64 userdata, int cycles_late) { - frame_count++; VideoCore::g_renderer->SwapBuffers(); // Signal to GSP that GPU interrupt has occurred @@ -550,12 +520,6 @@ static void VBlankCallback(u64 userdata, int cycles_late) { Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC0); Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC1); - if (!Settings::values.use_vsync && Settings::values.toggle_framelimit) { - FrameLimiter(); - } - - time_point = Common::Timer::GetTimeMs(); - // Reschedule recurrent event CoreTiming::ScheduleEvent(frame_ticks - cycles_late, vblank_event); } @@ -590,9 +554,6 @@ void Init() { framebuffer_sub.color_format.Assign(Regs::PixelFormat::RGB8); framebuffer_sub.active_fb = 0; - frame_count = 0; - time_point = Common::Timer::GetTimeMs(); - vblank_event = CoreTiming::RegisterEvent("GPU::VBlankCallback", VBlankCallback); CoreTiming::ScheduleEvent(frame_ticks, vblank_event); diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h index d53381216..bdd997b2a 100644 --- a/src/core/hw/gpu.h +++ b/src/core/hw/gpu.h @@ -13,6 +13,8 @@ namespace GPU { +constexpr float SCREEN_REFRESH_RATE = 60; + // Returns index corresponding to the Regs member labeled by field_name // TODO: Due to Visual studio bug 209229, offsetof does not return constant expressions // when used with array elements (e.g. GPU_REG_INDEX(memory_fill_config[0])). diff --git a/src/core/hw/hw.cpp b/src/core/hw/hw.cpp index 9ff8825b2..8499f2ce6 100644 --- a/src/core/hw/hw.cpp +++ b/src/core/hw/hw.cpp @@ -4,6 +4,7 @@ #include "common/common_types.h" #include "common/logging/log.h" +#include "core/hw/aes/key.h" #include "core/hw/gpu.h" #include "core/hw/hw.h" #include "core/hw/lcd.h" @@ -85,6 +86,7 @@ void Update() {} /// Initialize hardware void Init() { + AES::InitKeys(); GPU::Init(); LCD::Init(); LOG_DEBUG(HW, "initialized OK"); diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 09266e8b0..74e336487 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -5,7 +5,7 @@ #include <algorithm> #include <vector> #include "common/logging/log.h" -#include "core/file_sys/archive_romfs.h" +#include "core/file_sys/archive_selfncch.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/service/fs/archive.h" @@ -277,8 +277,8 @@ ResultStatus AppLoader_THREEDSX::Load() { Kernel::g_current_process->Run(48, Kernel::DEFAULT_STACK_SIZE); - Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this), - Service::FS::ArchiveIdCode::RomFS); + Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_SelfNCCH>(*this), + Service::FS::ArchiveIdCode::SelfNCCH); is_loaded = true; return ResultStatus::Success; diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 147bf8591..be719d74c 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -139,7 +139,7 @@ std::unique_ptr<AppLoader> GetLoader(const std::string& filename) { type = filename_type; } - LOG_INFO(Loader, "Loading file %s as %s...", filename.c_str(), GetFileTypeString(type)); + LOG_DEBUG(Loader, "Loading file %s as %s...", filename.c_str(), GetFileTypeString(type)); return GetFileLoader(std::move(file), type, filename_filename, filename); } diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index a6c2a745f..1d80766ae 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -54,7 +54,7 @@ FileType IdentifyFile(const std::string& file_name); * @return FileType of file. Note: this will return FileType::Unknown if it is unable to determine * a filetype, and will never return FileType::Error. */ -FileType GuessFromExtension(const std::string& extension_); +FileType GuessFromExtension(const std::string& extension); /** * Convert a FileType into a string which can be displayed to the user. diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 5df33f6d2..1a4e3efa8 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -3,12 +3,13 @@ // Refer to the license.txt file included. #include <algorithm> +#include <cinttypes> #include <cstring> #include <memory> #include "common/logging/log.h" #include "common/string_util.h" #include "common/swap.h" -#include "core/file_sys/archive_romfs.h" +#include "core/file_sys/archive_selfncch.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/service/cfg/cfg.h" @@ -253,7 +254,7 @@ ResultStatus AppLoader_NCCH::LoadExeFS() { // Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)... if (MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) { - LOG_WARNING(Loader, "Only loading the first (bootable) NCCH within the NCSD file!"); + LOG_DEBUG(Loader, "Only loading the first (bootable) NCCH within the NCSD file!"); ncch_offset = 0x4000; file.Seek(ncch_offset, SEEK_SET); file.ReadBytes(&ncch_header, sizeof(NCCH_Header)); @@ -277,8 +278,8 @@ ResultStatus AppLoader_NCCH::LoadExeFS() { priority = exheader_header.arm11_system_local_caps.priority; resource_limit_category = exheader_header.arm11_system_local_caps.resource_limit_category; - LOG_INFO(Loader, "Name: %s", exheader_header.codeset_info.name); - LOG_INFO(Loader, "Program ID: %016llX", ncch_header.program_id); + LOG_DEBUG(Loader, "Name: %s", exheader_header.codeset_info.name); + LOG_DEBUG(Loader, "Program ID: %016" PRIX64, ncch_header.program_id); LOG_DEBUG(Loader, "Code compressed: %s", is_compressed ? "yes" : "no"); LOG_DEBUG(Loader, "Entry point: 0x%08X", entry_point); LOG_DEBUG(Loader, "Code size: 0x%08X", code_size); @@ -336,14 +337,16 @@ ResultStatus AppLoader_NCCH::Load() { if (result != ResultStatus::Success) return result; + LOG_INFO(Loader, "Program ID: %016" PRIX64, ncch_header.program_id); + is_loaded = true; // Set state to loaded result = LoadExec(); // Load the executable into memory for booting if (ResultStatus::Success != result) return result; - Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this), - Service::FS::ArchiveIdCode::RomFS); + Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_SelfNCCH>(*this), + Service::FS::ArchiveIdCode::SelfNCCH); ParseRegionLockoutInfo(); diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp new file mode 100644 index 000000000..2cdfb9ded --- /dev/null +++ b/src/core/perf_stats.cpp @@ -0,0 +1,105 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <chrono> +#include <mutex> +#include <thread> +#include "common/math_util.h" +#include "core/hw/gpu.h" +#include "core/perf_stats.h" +#include "core/settings.h" + +using namespace std::chrono_literals; +using DoubleSecs = std::chrono::duration<double, std::chrono::seconds::period>; +using std::chrono::duration_cast; +using std::chrono::microseconds; + +namespace Core { + +void PerfStats::BeginSystemFrame() { + std::lock_guard<std::mutex> lock(object_mutex); + + frame_begin = Clock::now(); +} + +void PerfStats::EndSystemFrame() { + std::lock_guard<std::mutex> lock(object_mutex); + + auto frame_end = Clock::now(); + accumulated_frametime += frame_end - frame_begin; + system_frames += 1; + + previous_frame_length = frame_end - previous_frame_end; + previous_frame_end = frame_end; +} + +void PerfStats::EndGameFrame() { + std::lock_guard<std::mutex> lock(object_mutex); + + game_frames += 1; +} + +PerfStats::Results PerfStats::GetAndResetStats(u64 current_system_time_us) { + std::lock_guard<std::mutex> lock(object_mutex); + + auto now = Clock::now(); + // Walltime elapsed since stats were reset + auto interval = duration_cast<DoubleSecs>(now - reset_point).count(); + + auto system_us_per_second = + static_cast<double>(current_system_time_us - reset_point_system_us) / interval; + + Results results{}; + results.system_fps = static_cast<double>(system_frames) / interval; + results.game_fps = static_cast<double>(game_frames) / interval; + results.frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() / + static_cast<double>(system_frames); + results.emulation_speed = system_us_per_second / 1'000'000.0; + + // Reset counters + reset_point = now; + reset_point_system_us = current_system_time_us; + accumulated_frametime = Clock::duration::zero(); + system_frames = 0; + game_frames = 0; + + return results; +} + +double PerfStats::GetLastFrameTimeScale() { + std::lock_guard<std::mutex> lock(object_mutex); + + constexpr double FRAME_LENGTH = 1.0 / GPU::SCREEN_REFRESH_RATE; + return duration_cast<DoubleSecs>(previous_frame_length).count() / FRAME_LENGTH; +} + +void FrameLimiter::DoFrameLimiting(u64 current_system_time_us) { + // Max lag caused by slow frames. Can be adjusted to compensate for too many slow frames. Higher + // values increase the time needed to recover and limit framerate again after spikes. + constexpr microseconds MAX_LAG_TIME_US = 25ms; + + if (!Settings::values.toggle_framelimit) { + return; + } + + auto now = Clock::now(); + + frame_limiting_delta_err += microseconds(current_system_time_us - previous_system_time_us); + frame_limiting_delta_err -= duration_cast<microseconds>(now - previous_walltime); + frame_limiting_delta_err = + MathUtil::Clamp(frame_limiting_delta_err, -MAX_LAG_TIME_US, MAX_LAG_TIME_US); + + if (frame_limiting_delta_err > microseconds::zero()) { + std::this_thread::sleep_for(frame_limiting_delta_err); + + auto now_after_sleep = Clock::now(); + frame_limiting_delta_err -= duration_cast<microseconds>(now_after_sleep - now); + now = now_after_sleep; + } + + previous_system_time_us = current_system_time_us; + previous_walltime = now; +} + +} // namespace Core diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h new file mode 100644 index 000000000..362b205c8 --- /dev/null +++ b/src/core/perf_stats.h @@ -0,0 +1,83 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <chrono> +#include <mutex> +#include "common/common_types.h" + +namespace Core { + +/** + * Class to manage and query performance/timing statistics. All public functions of this class are + * thread-safe unless stated otherwise. + */ +class PerfStats { +public: + using Clock = std::chrono::high_resolution_clock; + + struct Results { + /// System FPS (LCD VBlanks) in Hz + double system_fps; + /// Game FPS (GSP frame submissions) in Hz + double game_fps; + /// Walltime per system frame, in seconds, excluding any waits + double frametime; + /// Ratio of walltime / emulated time elapsed + double emulation_speed; + }; + + void BeginSystemFrame(); + void EndSystemFrame(); + void EndGameFrame(); + + Results GetAndResetStats(u64 current_system_time_us); + + /** + * Gets the ratio between walltime and the emulated time of the previous system frame. This is + * useful for scaling inputs or outputs moving between the two time domains. + */ + double GetLastFrameTimeScale(); + +private: + std::mutex object_mutex; + + /// Point when the cumulative counters were reset + Clock::time_point reset_point = Clock::now(); + /// System time when the cumulative counters were reset + u64 reset_point_system_us = 0; + + /// Cumulative duration (excluding v-sync/frame-limiting) of frames since last reset + Clock::duration accumulated_frametime = Clock::duration::zero(); + /// Cumulative number of system frames (LCD VBlanks) presented since last reset + u32 system_frames = 0; + /// Cumulative number of game frames (GSP frame submissions) since last reset + u32 game_frames = 0; + + /// Point when the previous system frame ended + Clock::time_point previous_frame_end = reset_point; + /// Point when the current system frame began + Clock::time_point frame_begin = reset_point; + /// Total visible duration (including frame-limiting, etc.) of the previous system frame + Clock::duration previous_frame_length = Clock::duration::zero(); +}; + +class FrameLimiter { +public: + using Clock = std::chrono::high_resolution_clock; + + void DoFrameLimiting(u64 current_system_time_us); + +private: + /// Emulated system time (in microseconds) at the last limiter invocation + u64 previous_system_time_us = 0; + /// Walltime at the last limiter invocation + Clock::time_point previous_walltime = Clock::now(); + + /// Accumulated difference between walltime and emulated time + std::chrono::microseconds frame_limiting_delta_err{0}; +}; + +} // namespace Core diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 9afaf79ec..a598f9f2f 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -4,6 +4,7 @@ #include "audio_core/audio_core.h" #include "core/gdbstub/gdbstub.h" +#include "core/hle/service/hid/hid.h" #include "settings.h" #include "video_core/video_core.h" @@ -15,7 +16,7 @@ Values values = {}; void Apply() { - GDBStub::SetServerPort(static_cast<u32>(values.gdbstub_port)); + GDBStub::SetServerPort(values.gdbstub_port); GDBStub::ToggleServer(values.use_gdbstub); VideoCore::g_hw_renderer_enabled = values.use_hw_renderer; @@ -29,6 +30,8 @@ void Apply() { AudioCore::SelectSink(values.sink_id); AudioCore::EnableStretching(values.enable_audio_stretching); + + Service::HID::ReloadInputDevices(); } } // namespace diff --git a/src/core/settings.h b/src/core/settings.h index b6c75531f..03c64c94c 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -15,67 +15,70 @@ enum class LayoutOption { Default, SingleScreen, LargeScreen, - Custom, }; -namespace NativeInput { - +namespace NativeButton { enum Values { - // directly mapped keys A, B, X, Y, + Up, + Down, + Left, + Right, L, R, + Start, + Select, + ZL, ZR, - START, - SELECT, - HOME, - DUP, - DDOWN, - DLEFT, - DRIGHT, - CUP, - CDOWN, - CLEFT, - CRIGHT, - - // indirectly mapped keys - CIRCLE_UP, - CIRCLE_DOWN, - CIRCLE_LEFT, - CIRCLE_RIGHT, - CIRCLE_MODIFIER, - - NUM_INPUTS + + Home, + + NumButtons, }; -static const std::array<const char*, NUM_INPUTS> Mapping = {{ - // directly mapped keys - "pad_a", "pad_b", "pad_x", "pad_y", "pad_l", "pad_r", "pad_zl", "pad_zr", "pad_start", - "pad_select", "pad_home", "pad_dup", "pad_ddown", "pad_dleft", "pad_dright", "pad_cup", - "pad_cdown", "pad_cleft", "pad_cright", +constexpr int BUTTON_HID_BEGIN = A; +constexpr int BUTTON_IR_BEGIN = ZL; +constexpr int BUTTON_NS_BEGIN = Home; + +constexpr int BUTTON_HID_END = BUTTON_IR_BEGIN; +constexpr int BUTTON_IR_END = BUTTON_NS_BEGIN; +constexpr int BUTTON_NS_END = NumButtons; + +constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN; +constexpr int NUM_BUTTONS_IR = BUTTON_IR_END - BUTTON_IR_BEGIN; +constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN; - // indirectly mapped keys - "pad_circle_up", "pad_circle_down", "pad_circle_left", "pad_circle_right", - "pad_circle_modifier", +static const std::array<const char*, NumButtons> mapping = {{ + "button_a", "button_b", "button_x", "button_y", "button_up", "button_down", "button_left", + "button_right", "button_l", "button_r", "button_start", "button_select", "button_zl", + "button_zr", "button_home", }}; -static const std::array<Values, NUM_INPUTS> All = {{ - A, B, X, Y, L, R, ZL, ZR, - START, SELECT, HOME, DUP, DDOWN, DLEFT, DRIGHT, CUP, - CDOWN, CLEFT, CRIGHT, CIRCLE_UP, CIRCLE_DOWN, CIRCLE_LEFT, CIRCLE_RIGHT, CIRCLE_MODIFIER, +} // namespace NativeButton + +namespace NativeAnalog { +enum Values { + CirclePad, + CStick, + + NumAnalogs, +}; + +static const std::array<const char*, NumAnalogs> mapping = {{ + "circle_pad", "c_stick", }}; -} +} // namespace NumAnalog struct Values { // CheckNew3DS bool is_new_3ds; // Controls - std::array<int, NativeInput::NUM_INPUTS> input_mappings; - float pad_circle_modifier_scale; + std::array<std::string, NativeButton::NumButtons> buttons; + std::array<std::string, NativeAnalog::NumAnalogs> analogs; // Core bool use_cpu_jit; @@ -95,6 +98,15 @@ struct Values { LayoutOption layout_option; bool swap_screen; + bool custom_layout; + u16 custom_top_left; + u16 custom_top_top; + u16 custom_top_right; + u16 custom_top_bottom; + u16 custom_bottom_left; + u16 custom_bottom_top; + u16 custom_bottom_right; + u16 custom_bottom_bottom; float bg_red; float bg_green; diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt new file mode 100644 index 000000000..cfe5caaa3 --- /dev/null +++ b/src/input_common/CMakeLists.txt @@ -0,0 +1,27 @@ +set(SRCS + analog_from_button.cpp + keyboard.cpp + main.cpp + ) + +set(HEADERS + analog_from_button.h + keyboard.h + main.h + ) + +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) + +if(SDL2_FOUND) + target_link_libraries(input_common ${SDL2_LIBRARY}) + set_property(TARGET input_common APPEND PROPERTY COMPILE_DEFINITIONS HAVE_SDL2) +endif() diff --git a/src/input_common/analog_from_button.cpp b/src/input_common/analog_from_button.cpp new file mode 100755 index 000000000..e1a260762 --- /dev/null +++ b/src/input_common/analog_from_button.cpp @@ -0,0 +1,58 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "input_common/analog_from_button.h" + +namespace InputCommon { + +class Analog final : public Input::AnalogDevice { +public: + using Button = std::unique_ptr<Input::ButtonDevice>; + + Analog(Button up_, Button down_, Button left_, Button right_, Button modifier_, + float modifier_scale_) + : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)), + right(std::move(right_)), modifier(std::move(modifier_)), + modifier_scale(modifier_scale_) {} + + std::tuple<float, float> GetStatus() const override { + constexpr float SQRT_HALF = 0.707106781f; + int x = 0, y = 0; + + if (right->GetStatus()) + ++x; + if (left->GetStatus()) + --x; + if (up->GetStatus()) + ++y; + if (down->GetStatus()) + --y; + + float coef = modifier->GetStatus() ? modifier_scale : 1.0f; + return std::make_tuple(x * coef * (y == 0 ? 1.0f : SQRT_HALF), + y * coef * (x == 0 ? 1.0f : SQRT_HALF)); + } + +private: + Button up; + Button down; + Button left; + Button right; + Button modifier; + float modifier_scale; +}; + +std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::ParamPackage& params) { + const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize(); + auto up = Input::CreateDevice<Input::ButtonDevice>(params.Get("up", null_engine)); + auto down = Input::CreateDevice<Input::ButtonDevice>(params.Get("down", null_engine)); + auto left = Input::CreateDevice<Input::ButtonDevice>(params.Get("left", null_engine)); + auto right = Input::CreateDevice<Input::ButtonDevice>(params.Get("right", null_engine)); + auto modifier = Input::CreateDevice<Input::ButtonDevice>(params.Get("modifier", null_engine)); + auto modifier_scale = params.Get("modifier_scale", 0.5f); + return std::make_unique<Analog>(std::move(up), std::move(down), std::move(left), + std::move(right), std::move(modifier), modifier_scale); +} + +} // namespace InputCommon diff --git a/src/input_common/analog_from_button.h b/src/input_common/analog_from_button.h new file mode 100755 index 000000000..bbd583dd9 --- /dev/null +++ b/src/input_common/analog_from_button.h @@ -0,0 +1,31 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "core/frontend/input.h" + +namespace InputCommon { + +/** + * An analog device factory that takes direction button devices and combines them into a analog + * device. + */ +class AnalogFromButton final : public Input::Factory<Input::AnalogDevice> { +public: + /** + * Creates an analog device from direction button devices + * @param params contains parameters for creating the device: + * - "up": a serialized ParamPackage for creating a button device for up direction + * - "down": a serialized ParamPackage for creating a button device for down direction + * - "left": a serialized ParamPackage for creating a button device for left direction + * - "right": a serialized ParamPackage for creating a button device for right direction + * - "modifier": a serialized ParamPackage for creating a button device as the modifier + * - "modifier_scale": a float for the multiplier the modifier gives to the position + */ + std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override; +}; + +} // namespace InputCommon diff --git a/src/input_common/keyboard.cpp b/src/input_common/keyboard.cpp new file mode 100644 index 000000000..0f0d10f23 --- /dev/null +++ b/src/input_common/keyboard.cpp @@ -0,0 +1,93 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <atomic> +#include <list> +#include <mutex> +#include "input_common/keyboard.h" + +namespace InputCommon { + +class KeyButton final : public Input::ButtonDevice { +public: + explicit KeyButton(std::shared_ptr<KeyButtonList> key_button_list_) + : key_button_list(key_button_list_) {} + + ~KeyButton(); + + bool GetStatus() const override { + return status.load(); + } + + friend class KeyButtonList; + +private: + std::shared_ptr<KeyButtonList> key_button_list; + std::atomic<bool> status{false}; +}; + +struct KeyButtonPair { + int key_code; + KeyButton* key_button; +}; + +class KeyButtonList { +public: + void AddKeyButton(int key_code, KeyButton* key_button) { + std::lock_guard<std::mutex> guard(mutex); + list.push_back(KeyButtonPair{key_code, key_button}); + } + + void RemoveKeyButton(const KeyButton* key_button) { + std::lock_guard<std::mutex> guard(mutex); + list.remove_if( + [key_button](const KeyButtonPair& pair) { return pair.key_button == key_button; }); + } + + void ChangeKeyStatus(int key_code, bool pressed) { + std::lock_guard<std::mutex> guard(mutex); + for (const KeyButtonPair& pair : list) { + if (pair.key_code == key_code) + pair.key_button->status.store(pressed); + } + } + + void ChangeAllKeyStatus(bool pressed) { + std::lock_guard<std::mutex> guard(mutex); + for (const KeyButtonPair& pair : list) { + pair.key_button->status.store(pressed); + } + } + +private: + std::mutex mutex; + std::list<KeyButtonPair> list; +}; + +Keyboard::Keyboard() : key_button_list{std::make_shared<KeyButtonList>()} {} + +KeyButton::~KeyButton() { + key_button_list->RemoveKeyButton(this); +} + +std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) { + int key_code = params.Get("code", 0); + std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(key_button_list); + key_button_list->AddKeyButton(key_code, button.get()); + return std::move(button); +} + +void Keyboard::PressKey(int key_code) { + key_button_list->ChangeKeyStatus(key_code, true); +} + +void Keyboard::ReleaseKey(int key_code) { + key_button_list->ChangeKeyStatus(key_code, false); +} + +void Keyboard::ReleaseAllKeys() { + key_button_list->ChangeAllKeyStatus(false); +} + +} // namespace InputCommon diff --git a/src/input_common/keyboard.h b/src/input_common/keyboard.h new file mode 100644 index 000000000..861950472 --- /dev/null +++ b/src/input_common/keyboard.h @@ -0,0 +1,47 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "core/frontend/input.h" + +namespace InputCommon { + +class KeyButtonList; + +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class Keyboard final : public Input::Factory<Input::ButtonDevice> { +public: + Keyboard(); + + /** + * Creates a button device from a keyboard key + * @param params contains parameters for creating the device: + * - "code": the code of the key to bind with the button + */ + std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override; + + /** + * Sets the status of all buttons bound with the key to pressed + * @param key_code the code of the key to press + */ + void PressKey(int key_code); + + /** + * Sets the status of all buttons bound with the key to released + * @param key_code the code of the key to release + */ + void ReleaseKey(int key_code); + + void ReleaseAllKeys(); + +private: + std::shared_ptr<KeyButtonList> key_button_list; +}; + +} // namespace InputCommon diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp new file mode 100644 index 000000000..699f41e6b --- /dev/null +++ b/src/input_common/main.cpp @@ -0,0 +1,63 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include "common/param_package.h" +#include "input_common/analog_from_button.h" +#include "input_common/keyboard.h" +#include "input_common/main.h" +#ifdef HAVE_SDL2 +#include "input_common/sdl/sdl.h" +#endif + +namespace InputCommon { + +static std::shared_ptr<Keyboard> keyboard; + +void Init() { + keyboard = std::make_shared<InputCommon::Keyboard>(); + Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); + Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", + std::make_shared<InputCommon::AnalogFromButton>()); +#ifdef HAVE_SDL2 + SDL::Init(); +#endif +} + +void Shutdown() { + Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); + keyboard.reset(); + Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); + +#ifdef HAVE_SDL2 + SDL::Shutdown(); +#endif +} + +Keyboard* GetKeyboard() { + return keyboard.get(); +} + +std::string GenerateKeyboardParam(int key_code) { + Common::ParamPackage param{ + {"engine", "keyboard"}, {"code", std::to_string(key_code)}, + }; + return param.Serialize(); +} + +std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, + int key_modifier, float modifier_scale) { + Common::ParamPackage circle_pad_param{ + {"engine", "analog_from_button"}, + {"up", GenerateKeyboardParam(key_up)}, + {"down", GenerateKeyboardParam(key_down)}, + {"left", GenerateKeyboardParam(key_left)}, + {"right", GenerateKeyboardParam(key_right)}, + {"modifier", GenerateKeyboardParam(key_modifier)}, + {"modifier_scale", std::to_string(modifier_scale)}, + }; + return circle_pad_param.Serialize(); +} + +} // namespace InputCommon diff --git a/src/input_common/main.h b/src/input_common/main.h new file mode 100644 index 000000000..140bbd014 --- /dev/null +++ b/src/input_common/main.h @@ -0,0 +1,29 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> + +namespace InputCommon { + +/// Initializes and registers all built-in input device factories. +void Init(); + +/// Unresisters all build-in input device factories and shut them down. +void Shutdown(); + +class Keyboard; + +/// Gets the keyboard button device factory. +Keyboard* GetKeyboard(); + +/// Generates a serialized param package for creating a keyboard button device +std::string GenerateKeyboardParam(int key_code); + +/// Generates a serialized param package for creating an analog device taking input from keyboard +std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, + int key_modifier, float modifier_scale); + +} // namespace InputCommon diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp new file mode 100644 index 000000000..ae0206909 --- /dev/null +++ b/src/input_common/sdl/sdl.cpp @@ -0,0 +1,202 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cmath> +#include <memory> +#include <string> +#include <tuple> +#include <unordered_map> +#include <SDL.h> +#include "common/math_util.h" +#include "input_common/sdl/sdl.h" + +namespace InputCommon { + +namespace SDL { + +class SDLJoystick; +class SDLButtonFactory; +class SDLAnalogFactory; +static std::unordered_map<int, std::weak_ptr<SDLJoystick>> joystick_list; +static std::shared_ptr<SDLButtonFactory> button_factory; +static std::shared_ptr<SDLAnalogFactory> analog_factory; + +static bool initialized = false; + +class SDLJoystick { +public: + explicit SDLJoystick(int joystick_index) + : joystick{SDL_JoystickOpen(joystick_index), SDL_JoystickClose} { + if (!joystick) { + LOG_ERROR(Input, "failed to open joystick %d", joystick_index); + } + } + + bool GetButton(int button) const { + if (!joystick) + return {}; + SDL_JoystickUpdate(); + return SDL_JoystickGetButton(joystick.get(), button) == 1; + } + + std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const { + if (!joystick) + return {}; + SDL_JoystickUpdate(); + float x = SDL_JoystickGetAxis(joystick.get(), axis_x) / 32767.0f; + float y = SDL_JoystickGetAxis(joystick.get(), axis_y) / 32767.0f; + y = -y; // 3DS uses an y-axis inverse from SDL + + // Make sure the coordinates are in the unit circle, + // otherwise normalize it. + float r = x * x + y * y; + if (r > 1.0f) { + r = std::sqrt(r); + x /= r; + y /= r; + } + + return std::make_tuple(x, y); + } + + bool GetHatDirection(int hat, Uint8 direction) const { + return (SDL_JoystickGetHat(joystick.get(), hat) & direction) != 0; + } + +private: + std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> joystick; +}; + +class SDLButton final : public Input::ButtonDevice { +public: + explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_) + : joystick(joystick_), button(button_) {} + + bool GetStatus() const override { + return joystick->GetButton(button); + } + +private: + std::shared_ptr<SDLJoystick> joystick; + int button; +}; + +class SDLDirectionButton final : public Input::ButtonDevice { +public: + explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_) + : joystick(joystick_), hat(hat_), direction(direction_) {} + + bool GetStatus() const override { + return joystick->GetHatDirection(hat, direction); + } + +private: + std::shared_ptr<SDLJoystick> joystick; + int hat; + Uint8 direction; +}; + +class SDLAnalog final : public Input::AnalogDevice { +public: + SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_) + : joystick(joystick_), axis_x(axis_x_), axis_y(axis_y_) {} + + std::tuple<float, float> GetStatus() const override { + return joystick->GetAnalog(axis_x, axis_y); + } + +private: + std::shared_ptr<SDLJoystick> joystick; + int axis_x; + int axis_y; +}; + +static std::shared_ptr<SDLJoystick> GetJoystick(int joystick_index) { + std::shared_ptr<SDLJoystick> joystick = joystick_list[joystick_index].lock(); + if (!joystick) { + joystick = std::make_shared<SDLJoystick>(joystick_index); + joystick_list[joystick_index] = joystick; + } + return joystick; +} + +/// A button device factory that creates button devices from SDL joystick +class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> { +public: + /** + * Creates a button device from a joystick button + * @param params contains parameters for creating the device: + * - "joystick": the index of the joystick to bind + * - "button"(optional): the index of the button to bind + * - "hat"(optional): the index of the hat to bind as direction buttons + * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", + * "down", "left" or "right" + */ + std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override { + const int joystick_index = params.Get("joystick", 0); + + if (params.Has("hat")) { + const int hat = params.Get("hat", 0); + const std::string direction_name = params.Get("direction", ""); + Uint8 direction; + if (direction_name == "up") { + direction = SDL_HAT_UP; + } else if (direction_name == "down") { + direction = SDL_HAT_DOWN; + } else if (direction_name == "left") { + direction = SDL_HAT_LEFT; + } else if (direction_name == "right") { + direction = SDL_HAT_RIGHT; + } else { + direction = 0; + } + return std::make_unique<SDLDirectionButton>(GetJoystick(joystick_index), hat, + direction); + } + + const int button = params.Get("button", 0); + return std::make_unique<SDLButton>(GetJoystick(joystick_index), button); + } +}; + +/// An analog device factory that creates analog devices from SDL joystick +class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> { +public: + /** + * Creates analog device from joystick axes + * @param params contains parameters for creating the device: + * - "joystick": the index of the joystick to bind + * - "axis_x": the index of the axis to be bind as x-axis + * - "axis_y": the index of the axis to be bind as y-axis + */ + std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override { + const int joystick_index = params.Get("joystick", 0); + const int axis_x = params.Get("axis_x", 0); + const int axis_y = params.Get("axis_y", 1); + return std::make_unique<SDLAnalog>(GetJoystick(joystick_index), axis_x, axis_y); + } +}; + +void Init() { + if (SDL_Init(SDL_INIT_JOYSTICK) < 0) { + LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: %s", SDL_GetError()); + } else { + using namespace Input; + RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>()); + RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>()); + initialized = true; + } +} + +void Shutdown() { + if (initialized) { + using namespace Input; + UnregisterFactory<ButtonDevice>("sdl"); + UnregisterFactory<AnalogDevice>("sdl"); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + } +} + +} // namespace SDL +} // namespace InputCommon diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h new file mode 100644 index 000000000..3e72debcc --- /dev/null +++ b/src/input_common/sdl/sdl.h @@ -0,0 +1,19 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/frontend/input.h" + +namespace InputCommon { +namespace SDL { + +/// Initializes and registers SDL device factories +void Init(); + +/// Unresisters SDL device factories and shut them down. +void Shutdown(); + +} // namespace SDL +} // namespace InputCommon diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index b47156ca4..d1144ba77 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -1,6 +1,7 @@ set(SRCS glad.cpp tests.cpp + common/param_package.cpp core/file_sys/path_parser.cpp ) diff --git a/src/tests/common/param_package.cpp b/src/tests/common/param_package.cpp new file mode 100644 index 000000000..efec2cc86 --- /dev/null +++ b/src/tests/common/param_package.cpp @@ -0,0 +1,25 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <catch.hpp> +#include <math.h> +#include "common/param_package.h" + +namespace Common { + +TEST_CASE("ParamPackage", "[common]") { + ParamPackage original{ + {"abc", "xyz"}, {"def", "42"}, {"jkl", "$$:1:$2$,3"}, + }; + original.Set("ghi", 3.14f); + ParamPackage copy(original.Serialize()); + REQUIRE(copy.Get("abc", "") == "xyz"); + REQUIRE(copy.Get("def", 0) == 42); + REQUIRE(std::abs(copy.Get("ghi", 0.0f) - 3.14f) < 0.01f); + REQUIRE(copy.Get("jkl", "") == "$$:1:$2$,3"); + REQUIRE(copy.Get("mno", "uvw") == "uvw"); + REQUIRE(copy.Get("abc", 42) == 42); +} + +} // namespace Common diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index d55b84ce0..5317719e8 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -1,26 +1,46 @@ set(SRCS + command_processor.cpp + debug_utils/debug_utils.cpp + pica.cpp + primitive_assembly.cpp + regs.cpp + renderer_base.cpp renderer_opengl/gl_rasterizer.cpp renderer_opengl/gl_rasterizer_cache.cpp renderer_opengl/gl_shader_gen.cpp renderer_opengl/gl_shader_util.cpp renderer_opengl/gl_state.cpp renderer_opengl/renderer_opengl.cpp - debug_utils/debug_utils.cpp - clipper.cpp - command_processor.cpp - pica.cpp - primitive_assembly.cpp - rasterizer.cpp - renderer_base.cpp shader/shader.cpp shader/shader_interpreter.cpp - swrasterizer.cpp + swrasterizer/clipper.cpp + swrasterizer/framebuffer.cpp + swrasterizer/rasterizer.cpp + swrasterizer/swrasterizer.cpp + swrasterizer/texturing.cpp + texture/etc1.cpp + texture/texture_decode.cpp vertex_loader.cpp video_core.cpp ) set(HEADERS + command_processor.h debug_utils/debug_utils.h + gpu_debugger.h + pica.h + pica_state.h + pica_types.h + primitive_assembly.h + rasterizer_interface.h + regs.h + regs_framebuffer.h + regs_lighting.h + regs_pipeline.h + regs_rasterizer.h + regs_shader.h + regs_texturing.h + renderer_base.h renderer_opengl/gl_rasterizer.h renderer_opengl/gl_rasterizer_cache.h renderer_opengl/gl_resource_manager.h @@ -29,20 +49,16 @@ set(HEADERS renderer_opengl/gl_state.h renderer_opengl/pica_to_gl.h renderer_opengl/renderer_opengl.h - clipper.h - command_processor.h - gpu_debugger.h - pica.h - pica_state.h - pica_types.h - primitive_assembly.h - rasterizer.h - rasterizer_interface.h - renderer_base.h shader/debug_data.h shader/shader.h shader/shader_interpreter.h - swrasterizer.h + swrasterizer/clipper.h + swrasterizer/framebuffer.h + swrasterizer/rasterizer.h + swrasterizer/swrasterizer.h + swrasterizer/texturing.h + texture/etc1.h + texture/texture_decode.h utils.h vertex_loader.h video_core.h diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index eb79974a8..2e32ff905 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -16,11 +16,13 @@ #include "core/tracer/recorder.h" #include "video_core/command_processor.h" #include "video_core/debug_utils/debug_utils.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" #include "video_core/pica_types.h" #include "video_core/primitive_assembly.h" #include "video_core/rasterizer_interface.h" +#include "video_core/regs.h" +#include "video_core/regs_pipeline.h" +#include "video_core/regs_texturing.h" #include "video_core/renderer_base.h" #include "video_core/shader/shader.h" #include "video_core/vertex_loader.h" @@ -49,19 +51,23 @@ MICROPROFILE_DEFINE(GPU_Drawing, "GPU", "Drawing", MP_RGB(50, 50, 240)); static void WritePicaReg(u32 id, u32 value, u32 mask) { auto& regs = g_state.regs; - if (id >= regs.NumIds()) + if (id >= Regs::NUM_REGS) { + LOG_ERROR(HW_GPU, + "Commandlist tried to write to invalid register 0x%03X (value: %08X, mask: %X)", + id, value, mask); return; + } // TODO: Figure out how register masking acts on e.g. vs.uniform_setup.set_value - u32 old_value = regs[id]; + u32 old_value = regs.reg_array[id]; const u32 write_mask = expand_bits_to_bytes[mask]; - regs[id] = (old_value & ~write_mask) | (value & write_mask); + regs.reg_array[id] = (old_value & ~write_mask) | (value & write_mask); // Double check for is_pica_tracing to avoid call overhead if (DebugUtils::IsPicaTracing()) { - DebugUtils::OnPicaRegWrite({(u16)id, (u16)mask, regs[id]}); + DebugUtils::OnPicaRegWrite({(u16)id, (u16)mask, regs.reg_array[id]}); } if (g_debug_context) @@ -74,23 +80,23 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { Service::GSP::SignalInterrupt(Service::GSP::InterruptId::P3D); break; - case PICA_REG_INDEX_WORKAROUND(triangle_topology, 0x25E): - g_state.primitive_assembler.Reconfigure(regs.triangle_topology); + case PICA_REG_INDEX(pipeline.triangle_topology): + g_state.primitive_assembler.Reconfigure(regs.pipeline.triangle_topology); break; - case PICA_REG_INDEX_WORKAROUND(restart_primitive, 0x25F): + case PICA_REG_INDEX(pipeline.restart_primitive): g_state.primitive_assembler.Reset(); break; - case PICA_REG_INDEX_WORKAROUND(vs_default_attributes_setup.index, 0x232): + case PICA_REG_INDEX(pipeline.vs_default_attributes_setup.index): g_state.immediate.current_attribute = 0; default_attr_counter = 0; break; // Load default vertex input attributes - case PICA_REG_INDEX_WORKAROUND(vs_default_attributes_setup.set_value[0], 0x233): - case PICA_REG_INDEX_WORKAROUND(vs_default_attributes_setup.set_value[1], 0x234): - case PICA_REG_INDEX_WORKAROUND(vs_default_attributes_setup.set_value[2], 0x235): { + case PICA_REG_INDEX_WORKAROUND(pipeline.vs_default_attributes_setup.set_value[0], 0x233): + case PICA_REG_INDEX_WORKAROUND(pipeline.vs_default_attributes_setup.set_value[1], 0x234): + case PICA_REG_INDEX_WORKAROUND(pipeline.vs_default_attributes_setup.set_value[2], 0x235): { // TODO: Does actual hardware indeed keep an intermediate buffer or does // it directly write the values? default_attr_write_buffer[default_attr_counter++] = value; @@ -102,7 +108,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { if (default_attr_counter >= 3) { default_attr_counter = 0; - auto& setup = regs.vs_default_attributes_setup; + auto& setup = regs.pipeline.vs_default_attributes_setup; if (setup.index >= 16) { LOG_ERROR(HW_GPU, "Invalid VS default attribute index %d", (int)setup.index); @@ -125,20 +131,21 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { // TODO: Verify that this actually modifies the register! if (setup.index < 15) { - g_state.vs_default_attributes[setup.index] = attribute; + g_state.input_default_attributes.attr[setup.index] = attribute; setup.index++; } else { - // Put each attribute into an immediate input buffer. - // When all specified immediate attributes are present, the Vertex Shader is invoked - // and everything is - // sent to the primitive assembler. + // Put each attribute into an immediate input buffer. When all specified immediate + // attributes are present, the Vertex Shader is invoked and everything is sent to + // the primitive assembler. auto& immediate_input = g_state.immediate.input_vertex; auto& immediate_attribute_id = g_state.immediate.current_attribute; - immediate_input.attr[immediate_attribute_id++] = attribute; + immediate_input.attr[immediate_attribute_id] = attribute; - if (immediate_attribute_id >= regs.vs.num_input_attributes + 1) { + if (immediate_attribute_id < regs.pipeline.max_input_attrib_index) { + immediate_attribute_id += 1; + } else { MICROPROFILE_SCOPE(GPU_Drawing); immediate_attribute_id = 0; @@ -150,10 +157,11 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, static_cast<void*>(&immediate_input)); Shader::UnitState shader_unit; - shader_unit.LoadInputVertex(immediate_input, regs.vs.num_input_attributes + 1); + Shader::AttributeBuffer output{}; + + shader_unit.LoadInput(regs.vs, immediate_input); shader_engine->Run(g_state.vs, shader_unit); - auto output_vertex = Shader::OutputVertex::FromRegisters( - shader_unit.registers.output, regs, regs.vs.output_mask); + shader_unit.WriteOutput(regs.vs, output); // Send to renderer using Pica::Shader::OutputVertex; @@ -162,15 +170,17 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2); }; - g_state.primitive_assembler.SubmitVertex(output_vertex, AddTriangle); + g_state.primitive_assembler.SubmitVertex( + Shader::OutputVertex::FromAttributeBuffer(regs.rasterizer, output), + AddTriangle); } } } break; } - case PICA_REG_INDEX(gpu_mode): - if (regs.gpu_mode == Regs::GPUMode::Configuring) { + case PICA_REG_INDEX(pipeline.gpu_mode): + if (regs.pipeline.gpu_mode == PipelineRegs::GPUMode::Configuring) { MICROPROFILE_SCOPE(GPU_Drawing); // Draw immediate mode triangles when GPU Mode is set to GPUMode::Configuring @@ -182,19 +192,20 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { } break; - case PICA_REG_INDEX_WORKAROUND(command_buffer.trigger[0], 0x23c): - case PICA_REG_INDEX_WORKAROUND(command_buffer.trigger[1], 0x23d): { - unsigned index = static_cast<unsigned>(id - PICA_REG_INDEX(command_buffer.trigger[0])); - u32* head_ptr = - (u32*)Memory::GetPhysicalPointer(regs.command_buffer.GetPhysicalAddress(index)); + case PICA_REG_INDEX_WORKAROUND(pipeline.command_buffer.trigger[0], 0x23c): + case PICA_REG_INDEX_WORKAROUND(pipeline.command_buffer.trigger[1], 0x23d): { + unsigned index = + static_cast<unsigned>(id - PICA_REG_INDEX(pipeline.command_buffer.trigger[0])); + u32* head_ptr = (u32*)Memory::GetPhysicalPointer( + regs.pipeline.command_buffer.GetPhysicalAddress(index)); g_state.cmd_list.head_ptr = g_state.cmd_list.current_ptr = head_ptr; - g_state.cmd_list.length = regs.command_buffer.GetSize(index) / sizeof(u32); + g_state.cmd_list.length = regs.pipeline.command_buffer.GetSize(index) / sizeof(u32); break; } // It seems like these trigger vertex rendering - case PICA_REG_INDEX(trigger_draw): - case PICA_REG_INDEX(trigger_draw_indexed): { + case PICA_REG_INDEX(pipeline.trigger_draw): + case PICA_REG_INDEX(pipeline.trigger_draw_indexed): { MICROPROFILE_SCOPE(GPU_Drawing); #if PICA_LOG_TEV @@ -206,13 +217,13 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { // Processes information about internal vertex attributes to figure out how a vertex is // loaded. // Later, these can be compiled and cached. - const u32 base_address = regs.vertex_attributes.GetPhysicalBaseAddress(); - VertexLoader loader(regs); + const u32 base_address = regs.pipeline.vertex_attributes.GetPhysicalBaseAddress(); + VertexLoader loader(regs.pipeline); // Load vertices - bool is_indexed = (id == PICA_REG_INDEX(trigger_draw_indexed)); + bool is_indexed = (id == PICA_REG_INDEX(pipeline.trigger_draw_indexed)); - const auto& index_info = regs.index_array; + const auto& index_info = regs.pipeline.index_array; const u8* index_address_8 = Memory::GetPhysicalPointer(base_address + index_info.offset); const u16* index_address_16 = reinterpret_cast<const u16*>(index_address_8); bool index_u16 = index_info.format != 0; @@ -221,13 +232,13 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { if (g_debug_context && g_debug_context->recorder) { for (int i = 0; i < 3; ++i) { - const auto texture = regs.GetTextures()[i]; + const auto texture = regs.texturing.GetTextures()[i]; if (!texture.enabled) continue; u8* texture_data = Memory::GetPhysicalPointer(texture.config.GetPhysicalAddress()); g_debug_context->recorder->MemoryAccessed( - texture_data, Pica::Regs::NibblesPerPixel(texture.format) * + texture_data, Pica::TexturingRegs::NibblesPerPixel(texture.format) * texture.config.width / 2 * texture.config.height, texture.config.GetPhysicalAddress()); } @@ -250,11 +261,11 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset); - for (unsigned int index = 0; index < regs.num_vertices; ++index) { + for (unsigned int index = 0; index < regs.pipeline.num_vertices; ++index) { // Indexed rendering doesn't use the start offset unsigned int vertex = is_indexed ? (index_u16 ? index_address_16[index] : index_address_8[index]) - : (index + regs.vertex_offset); + : (index + regs.pipeline.vertex_offset); // -1 is a common special value used for primitive restart. Since it's unknown if // the PICA supports it, and it would mess up the caching, guard against it here. @@ -280,19 +291,19 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { if (!vertex_cache_hit) { // Initialize data for the current vertex - Shader::InputVertex input; + Shader::AttributeBuffer input, output{}; loader.LoadVertex(base_address, index, vertex, input, memory_accesses); // Send to vertex shader if (g_debug_context) g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, (void*)&input); - shader_unit.LoadInputVertex(input, loader.GetNumTotalAttributes()); + shader_unit.LoadInput(regs.vs, input); shader_engine->Run(g_state.vs, shader_unit); + shader_unit.WriteOutput(regs.vs, output); // Retrieve vertex from register data - output_vertex = Shader::OutputVertex::FromRegisters(shader_unit.registers.output, - regs, regs.vs.output_mask); + output_vertex = Shader::OutputVertex::FromAttributeBuffer(regs.rasterizer, output); if (is_indexed) { vertex_cache[vertex_cache_pos] = output_vertex; @@ -434,16 +445,16 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { break; } - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[0], 0xe8): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[1], 0xe9): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[2], 0xea): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[3], 0xeb): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[4], 0xec): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[5], 0xed): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[6], 0xee): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[7], 0xef): { - g_state.fog.lut[regs.fog_lut_offset % 128].raw = value; - regs.fog_lut_offset.Assign(regs.fog_lut_offset + 1); + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[0], 0xe8): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[1], 0xe9): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[2], 0xea): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[3], 0xeb): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[4], 0xec): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[5], 0xed): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[6], 0xee): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[7], 0xef): { + g_state.fog.lut[regs.texturing.fog_lut_offset % 128].raw = value; + regs.texturing.fog_lut_offset.Assign(regs.texturing.fog_lut_offset + 1); break; } diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index c44b3d95a..47dbc8cc8 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -29,12 +29,15 @@ #include "common/math_util.h" #include "common/vector_math.h" #include "video_core/debug_utils/debug_utils.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" #include "video_core/pica_types.h" #include "video_core/rasterizer_interface.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_shader.h" +#include "video_core/regs_texturing.h" #include "video_core/renderer_base.h" #include "video_core/shader/shader.h" +#include "video_core/texture/texture_decode.h" #include "video_core/utils.h" #include "video_core/video_core.h" @@ -87,9 +90,9 @@ std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global namespace DebugUtils { -void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, +void DumpShader(const std::string& filename, const ShaderRegs& config, const Shader::ShaderSetup& setup, - const Regs::VSOutputAttributes* output_attributes) { + const RasterizerRegs::VSOutputAttributes* output_attributes) { struct StuffToWrite { const u8* pointer; u32 size; @@ -128,7 +131,7 @@ void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, // This is put into a try-catch block to make sure we notice unknown configurations. std::vector<OutputRegisterInfo> output_info_table; for (unsigned i = 0; i < 7; ++i) { - using OutputAttributes = Pica::Regs::VSOutputAttributes; + using OutputAttributes = Pica::RasterizerRegs::VSOutputAttributes; // TODO: It's still unclear how the attribute components map to the register! // Once we know that, this code probably will not make much sense anymore. @@ -315,257 +318,6 @@ std::unique_ptr<PicaTrace> FinishPicaTracing() { return ret; } -const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const TextureInfo& info, - bool disable_alpha) { - const unsigned int coarse_x = x & ~7; - const unsigned int coarse_y = y & ~7; - - if (info.format != Regs::TextureFormat::ETC1 && info.format != Regs::TextureFormat::ETC1A4) { - // TODO(neobrain): Fix code design to unify vertical block offsets! - source += coarse_y * info.stride; - } - - // TODO: Assert that width/height are multiples of block dimensions - - switch (info.format) { - case Regs::TextureFormat::RGBA8: { - auto res = Color::DecodeRGBA8(source + VideoCore::GetMortonOffset(x, y, 4)); - return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())}; - } - - case Regs::TextureFormat::RGB8: { - auto res = Color::DecodeRGB8(source + VideoCore::GetMortonOffset(x, y, 3)); - return {res.r(), res.g(), res.b(), 255}; - } - - case Regs::TextureFormat::RGB5A1: { - auto res = Color::DecodeRGB5A1(source + VideoCore::GetMortonOffset(x, y, 2)); - return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())}; - } - - case Regs::TextureFormat::RGB565: { - auto res = Color::DecodeRGB565(source + VideoCore::GetMortonOffset(x, y, 2)); - return {res.r(), res.g(), res.b(), 255}; - } - - case Regs::TextureFormat::RGBA4: { - auto res = Color::DecodeRGBA4(source + VideoCore::GetMortonOffset(x, y, 2)); - return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())}; - } - - case Regs::TextureFormat::IA8: { - const u8* source_ptr = source + VideoCore::GetMortonOffset(x, y, 2); - - if (disable_alpha) { - // Show intensity as red, alpha as green - return {source_ptr[1], source_ptr[0], 0, 255}; - } else { - return {source_ptr[1], source_ptr[1], source_ptr[1], source_ptr[0]}; - } - } - - case Regs::TextureFormat::RG8: { - auto res = Color::DecodeRG8(source + VideoCore::GetMortonOffset(x, y, 2)); - return {res.r(), res.g(), 0, 255}; - } - - case Regs::TextureFormat::I8: { - const u8* source_ptr = source + VideoCore::GetMortonOffset(x, y, 1); - return {*source_ptr, *source_ptr, *source_ptr, 255}; - } - - case Regs::TextureFormat::A8: { - const u8* source_ptr = source + VideoCore::GetMortonOffset(x, y, 1); - - if (disable_alpha) { - return {*source_ptr, *source_ptr, *source_ptr, 255}; - } else { - return {0, 0, 0, *source_ptr}; - } - } - - case Regs::TextureFormat::IA4: { - const u8* source_ptr = source + VideoCore::GetMortonOffset(x, y, 1); - - u8 i = Color::Convert4To8(((*source_ptr) & 0xF0) >> 4); - u8 a = Color::Convert4To8((*source_ptr) & 0xF); - - if (disable_alpha) { - // Show intensity as red, alpha as green - return {i, a, 0, 255}; - } else { - return {i, i, i, a}; - } - } - - case Regs::TextureFormat::I4: { - u32 morton_offset = VideoCore::GetMortonOffset(x, y, 1); - const u8* source_ptr = source + morton_offset / 2; - - u8 i = (morton_offset % 2) ? ((*source_ptr & 0xF0) >> 4) : (*source_ptr & 0xF); - i = Color::Convert4To8(i); - - return {i, i, i, 255}; - } - - case Regs::TextureFormat::A4: { - u32 morton_offset = VideoCore::GetMortonOffset(x, y, 1); - const u8* source_ptr = source + morton_offset / 2; - - u8 a = (morton_offset % 2) ? ((*source_ptr & 0xF0) >> 4) : (*source_ptr & 0xF); - a = Color::Convert4To8(a); - - if (disable_alpha) { - return {a, a, a, 255}; - } else { - return {0, 0, 0, a}; - } - } - - case Regs::TextureFormat::ETC1: - case Regs::TextureFormat::ETC1A4: { - bool has_alpha = (info.format == Regs::TextureFormat::ETC1A4); - - // ETC1 further subdivides each 8x8 tile into four 4x4 subtiles - const int subtile_width = 4; - const int subtile_height = 4; - - int subtile_index = ((x / subtile_width) & 1) + 2 * ((y / subtile_height) & 1); - unsigned subtile_bytes = has_alpha ? 2 : 1; // TODO: Name... - - const u64* source_ptr = (const u64*)(source + coarse_x * subtile_bytes * 4 + - coarse_y * subtile_bytes * 4 * (info.width / 8) + - subtile_index * subtile_bytes * 8); - u64 alpha = 0xFFFFFFFFFFFFFFFF; - if (has_alpha) { - alpha = *source_ptr; - source_ptr++; - } - - union ETC1Tile { - // Each of these two is a collection of 16 bits (one per lookup value) - BitField<0, 16, u64> table_subindexes; - BitField<16, 16, u64> negation_flags; - - unsigned GetTableSubIndex(unsigned index) const { - return (table_subindexes >> index) & 1; - } - - bool GetNegationFlag(unsigned index) const { - return ((negation_flags >> index) & 1) == 1; - } - - BitField<32, 1, u64> flip; - BitField<33, 1, u64> differential_mode; - - BitField<34, 3, u64> table_index_2; - BitField<37, 3, u64> table_index_1; - - union { - // delta value + base value - BitField<40, 3, s64> db; - BitField<43, 5, u64> b; - - BitField<48, 3, s64> dg; - BitField<51, 5, u64> g; - - BitField<56, 3, s64> dr; - BitField<59, 5, u64> r; - } differential; - - union { - BitField<40, 4, u64> b2; - BitField<44, 4, u64> b1; - - BitField<48, 4, u64> g2; - BitField<52, 4, u64> g1; - - BitField<56, 4, u64> r2; - BitField<60, 4, u64> r1; - } separate; - - const Math::Vec3<u8> GetRGB(int x, int y) const { - int texel = 4 * x + y; - - if (flip) - std::swap(x, y); - - // Lookup base value - Math::Vec3<int> ret; - if (differential_mode) { - ret.r() = static_cast<int>(differential.r); - ret.g() = static_cast<int>(differential.g); - ret.b() = static_cast<int>(differential.b); - if (x >= 2) { - ret.r() += static_cast<int>(differential.dr); - ret.g() += static_cast<int>(differential.dg); - ret.b() += static_cast<int>(differential.db); - } - ret.r() = Color::Convert5To8(ret.r()); - ret.g() = Color::Convert5To8(ret.g()); - ret.b() = Color::Convert5To8(ret.b()); - } else { - if (x < 2) { - ret.r() = Color::Convert4To8(static_cast<u8>(separate.r1)); - ret.g() = Color::Convert4To8(static_cast<u8>(separate.g1)); - ret.b() = Color::Convert4To8(static_cast<u8>(separate.b1)); - } else { - ret.r() = Color::Convert4To8(static_cast<u8>(separate.r2)); - ret.g() = Color::Convert4To8(static_cast<u8>(separate.g2)); - ret.b() = Color::Convert4To8(static_cast<u8>(separate.b2)); - } - } - - // Add modifier - unsigned table_index = - static_cast<int>((x < 2) ? table_index_1.Value() : table_index_2.Value()); - - static const std::array<std::array<u8, 2>, 8> etc1_modifier_table = {{ - {{2, 8}}, - {{5, 17}}, - {{9, 29}}, - {{13, 42}}, - {{18, 60}}, - {{24, 80}}, - {{33, 106}}, - {{47, 183}}, - }}; - - int modifier = etc1_modifier_table.at(table_index).at(GetTableSubIndex(texel)); - if (GetNegationFlag(texel)) - modifier *= -1; - - ret.r() = MathUtil::Clamp(ret.r() + modifier, 0, 255); - ret.g() = MathUtil::Clamp(ret.g() + modifier, 0, 255); - ret.b() = MathUtil::Clamp(ret.b() + modifier, 0, 255); - - return ret.Cast<u8>(); - } - } const* etc1_tile = reinterpret_cast<const ETC1Tile*>(source_ptr); - - alpha >>= 4 * ((x & 3) * 4 + (y & 3)); - return Math::MakeVec(etc1_tile->GetRGB(x & 3, y & 3), - disable_alpha ? (u8)255 : Color::Convert4To8(alpha & 0xF)); - } - - default: - LOG_ERROR(HW_GPU, "Unknown texture format: %x", (u32)info.format); - DEBUG_ASSERT(false); - return {}; - } -} - -TextureInfo TextureInfo::FromPicaRegister(const Regs::TextureConfig& config, - const Regs::TextureFormat& format) { - TextureInfo info; - info.physical_address = config.GetPhysicalAddress(); - info.width = config.width; - info.height = config.height; - info.format = format; - info.stride = Pica::Regs::NibblesPerPixel(info.format) * info.width / 2; - return info; -} - #ifdef HAVE_PNG // Adapter functions to libpng to write/flush to File::IOFile instances. static void WriteIOFile(png_structp png_ptr, png_bytep data, png_size_t length) { @@ -581,7 +333,7 @@ static void FlushIOFile(png_structp png_ptr) { } #endif -void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { +void DumpTexture(const TexturingRegs::TextureConfig& texture_config, u8* data) { #ifndef HAVE_PNG return; #else @@ -642,12 +394,12 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { buf = new u8[row_stride * texture_config.height]; for (unsigned y = 0; y < texture_config.height; ++y) { for (unsigned x = 0; x < texture_config.width; ++x) { - TextureInfo info; + Pica::Texture::TextureInfo info; info.width = texture_config.width; info.height = texture_config.height; info.stride = row_stride; - info.format = g_state.regs.texture0_format; - Math::Vec4<u8> texture_color = LookupTexture(data, x, y, info); + info.format = g_state.regs.texturing.texture0_format; + Math::Vec4<u8> texture_color = Pica::Texture::LookupTexture(data, x, y, info); buf[3 * x + y * row_stride] = texture_color.r(); buf[3 * x + y * row_stride + 1] = texture_color.g(); buf[3 * x + y * row_stride + 2] = texture_color.b(); @@ -684,8 +436,10 @@ static std::string ReplacePattern(const std::string& input, const std::string& p return ret; } -static std::string GetTevStageConfigSourceString(const Pica::Regs::TevStageConfig::Source& source) { - using Source = Pica::Regs::TevStageConfig::Source; +static std::string GetTevStageConfigSourceString( + const TexturingRegs::TevStageConfig::Source& source) { + + using Source = TexturingRegs::TevStageConfig::Source; static const std::map<Source, std::string> source_map = { {Source::PrimaryColor, "PrimaryColor"}, {Source::PrimaryFragmentColor, "PrimaryFragmentColor"}, @@ -707,9 +461,10 @@ static std::string GetTevStageConfigSourceString(const Pica::Regs::TevStageConfi } static std::string GetTevStageConfigColorSourceString( - const Pica::Regs::TevStageConfig::Source& source, - const Pica::Regs::TevStageConfig::ColorModifier modifier) { - using ColorModifier = Pica::Regs::TevStageConfig::ColorModifier; + const TexturingRegs::TevStageConfig::Source& source, + const TexturingRegs::TevStageConfig::ColorModifier modifier) { + + using ColorModifier = TexturingRegs::TevStageConfig::ColorModifier; static const std::map<ColorModifier, std::string> color_modifier_map = { {ColorModifier::SourceColor, "%source.rgb"}, {ColorModifier::OneMinusSourceColor, "(1.0 - %source.rgb)"}, @@ -733,9 +488,10 @@ static std::string GetTevStageConfigColorSourceString( } static std::string GetTevStageConfigAlphaSourceString( - const Pica::Regs::TevStageConfig::Source& source, - const Pica::Regs::TevStageConfig::AlphaModifier modifier) { - using AlphaModifier = Pica::Regs::TevStageConfig::AlphaModifier; + const TexturingRegs::TevStageConfig::Source& source, + const TexturingRegs::TevStageConfig::AlphaModifier modifier) { + + using AlphaModifier = TexturingRegs::TevStageConfig::AlphaModifier; static const std::map<AlphaModifier, std::string> alpha_modifier_map = { {AlphaModifier::SourceAlpha, "%source.a"}, {AlphaModifier::OneMinusSourceAlpha, "(1.0 - %source.a)"}, @@ -757,8 +513,9 @@ static std::string GetTevStageConfigAlphaSourceString( } static std::string GetTevStageConfigOperationString( - const Pica::Regs::TevStageConfig::Operation& operation) { - using Operation = Pica::Regs::TevStageConfig::Operation; + const TexturingRegs::TevStageConfig::Operation& operation) { + + using Operation = TexturingRegs::TevStageConfig::Operation; static const std::map<Operation, std::string> combiner_map = { {Operation::Replace, "%source1"}, {Operation::Modulate, "(%source1 * %source2)"}, @@ -778,7 +535,7 @@ static std::string GetTevStageConfigOperationString( return op_it->second; } -std::string GetTevStageConfigColorCombinerString(const Pica::Regs::TevStageConfig& tev_stage) { +std::string GetTevStageConfigColorCombinerString(const TexturingRegs::TevStageConfig& tev_stage) { auto op_str = GetTevStageConfigOperationString(tev_stage.color_op); op_str = ReplacePattern( op_str, "%source1", @@ -791,7 +548,7 @@ std::string GetTevStageConfigColorCombinerString(const Pica::Regs::TevStageConfi GetTevStageConfigColorSourceString(tev_stage.color_source3, tev_stage.color_modifier3)); } -std::string GetTevStageConfigAlphaCombinerString(const Pica::Regs::TevStageConfig& tev_stage) { +std::string GetTevStageConfigAlphaCombinerString(const TexturingRegs::TevStageConfig& tev_stage) { auto op_str = GetTevStageConfigOperationString(tev_stage.alpha_op); op_str = ReplacePattern( op_str, "%source1", @@ -804,7 +561,7 @@ std::string GetTevStageConfigAlphaCombinerString(const Pica::Regs::TevStageConfi GetTevStageConfigAlphaSourceString(tev_stage.alpha_source3, tev_stage.alpha_modifier3)); } -void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig, 6>& stages) { +void DumpTevStageConfig(const std::array<TexturingRegs::TevStageConfig, 6>& stages) { std::string stage_info = "Tev setup:\n"; for (size_t index = 0; index < stages.size(); ++index) { const auto& tev_stage = stages[index]; diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index 46ea8d9c7..c1f29c527 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -17,7 +17,9 @@ #include <vector> #include "common/common_types.h" #include "common/vector_math.h" -#include "video_core/pica.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_shader.h" +#include "video_core/regs_texturing.h" namespace CiTrace { class Recorder; @@ -85,7 +87,7 @@ public: * @param data Optional data pointer (if unused, this is a nullptr) * @note This function will perform nothing unless it is overridden in the child class. */ - virtual void OnPicaBreakPointHit(Event, void*) {} + virtual void OnPicaBreakPointHit(Event event, void* data) {} /** * Action to perform when emulation is resumed from a breakpoint. @@ -182,9 +184,9 @@ namespace DebugUtils { #define PICA_DUMP_TEXTURES 0 #define PICA_LOG_TEV 0 -void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, +void DumpShader(const std::string& filename, const ShaderRegs& config, const Shader::ShaderSetup& setup, - const Regs::VSOutputAttributes* output_attributes); + const RasterizerRegs::VSOutputAttributes* output_attributes); // Utility class to log Pica commands. struct PicaTrace { @@ -205,38 +207,13 @@ inline bool IsPicaTracing() { void OnPicaRegWrite(PicaTrace::Write write); std::unique_ptr<PicaTrace> FinishPicaTracing(); -struct TextureInfo { - PAddr physical_address; - int width; - int height; - int stride; - Pica::Regs::TextureFormat format; +void DumpTexture(const TexturingRegs::TextureConfig& texture_config, u8* data); - static TextureInfo FromPicaRegister(const Pica::Regs::TextureConfig& config, - const Pica::Regs::TextureFormat& format); -}; - -/** - * Lookup texel located at the given coordinates and return an RGBA vector of its color. - * @param source Source pointer to read data from - * @param s,t Texture coordinates to read from - * @param info TextureInfo object describing the texture setup - * @param disable_alpha This is used for debug widgets which use this method to display textures - * without providing a good way to visualize alpha by themselves. If true, this will return 255 for - * the alpha component, and either drop the information entirely or store it in an "unused" color - * channel. - * @todo Eventually we should get rid of the disable_alpha parameter. - */ -const Math::Vec4<u8> LookupTexture(const u8* source, int s, int t, const TextureInfo& info, - bool disable_alpha = false); - -void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data); - -std::string GetTevStageConfigColorCombinerString(const Pica::Regs::TevStageConfig& tev_stage); -std::string GetTevStageConfigAlphaCombinerString(const Pica::Regs::TevStageConfig& tev_stage); +std::string GetTevStageConfigColorCombinerString(const TexturingRegs::TevStageConfig& tev_stage); +std::string GetTevStageConfigAlphaCombinerString(const TexturingRegs::TevStageConfig& tev_stage); /// Dumps the Tev stage config to log at trace level -void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig, 6>& stages); +void DumpTevStageConfig(const std::array<TexturingRegs::TevStageConfig, 6>& stages); /** * Used in the vertex loader to merge access records. TODO: Investigate if actually useful. diff --git a/src/video_core/pica.cpp b/src/video_core/pica.cpp index b4a77c632..b95148a6a 100644 --- a/src/video_core/pica.cpp +++ b/src/video_core/pica.cpp @@ -3,497 +3,14 @@ // Refer to the license.txt file included. #include <cstring> -#include <iterator> -#include <unordered_map> -#include <utility> #include "video_core/pica.h" #include "video_core/pica_state.h" -#include "video_core/primitive_assembly.h" -#include "video_core/shader/shader.h" +#include "video_core/regs_pipeline.h" namespace Pica { State g_state; -static const std::pair<u16, const char*> register_names[] = { - {0x010, "GPUREG_FINALIZE"}, - - {0x040, "GPUREG_FACECULLING_CONFIG"}, - {0x041, "GPUREG_VIEWPORT_WIDTH"}, - {0x042, "GPUREG_VIEWPORT_INVW"}, - {0x043, "GPUREG_VIEWPORT_HEIGHT"}, - {0x044, "GPUREG_VIEWPORT_INVH"}, - - {0x047, "GPUREG_FRAGOP_CLIP"}, - {0x048, "GPUREG_FRAGOP_CLIP_DATA0"}, - {0x049, "GPUREG_FRAGOP_CLIP_DATA1"}, - {0x04A, "GPUREG_FRAGOP_CLIP_DATA2"}, - {0x04B, "GPUREG_FRAGOP_CLIP_DATA3"}, - - {0x04D, "GPUREG_DEPTHMAP_SCALE"}, - {0x04E, "GPUREG_DEPTHMAP_OFFSET"}, - {0x04F, "GPUREG_SH_OUTMAP_TOTAL"}, - {0x050, "GPUREG_SH_OUTMAP_O0"}, - {0x051, "GPUREG_SH_OUTMAP_O1"}, - {0x052, "GPUREG_SH_OUTMAP_O2"}, - {0x053, "GPUREG_SH_OUTMAP_O3"}, - {0x054, "GPUREG_SH_OUTMAP_O4"}, - {0x055, "GPUREG_SH_OUTMAP_O5"}, - {0x056, "GPUREG_SH_OUTMAP_O6"}, - - {0x061, "GPUREG_EARLYDEPTH_FUNC"}, - {0x062, "GPUREG_EARLYDEPTH_TEST1"}, - {0x063, "GPUREG_EARLYDEPTH_CLEAR"}, - {0x064, "GPUREG_SH_OUTATTR_MODE"}, - {0x065, "GPUREG_SCISSORTEST_MODE"}, - {0x066, "GPUREG_SCISSORTEST_POS"}, - {0x067, "GPUREG_SCISSORTEST_DIM"}, - {0x068, "GPUREG_VIEWPORT_XY"}, - - {0x06A, "GPUREG_EARLYDEPTH_DATA"}, - - {0x06D, "GPUREG_DEPTHMAP_ENABLE"}, - {0x06E, "GPUREG_RENDERBUF_DIM"}, - {0x06F, "GPUREG_SH_OUTATTR_CLOCK"}, - - {0x080, "GPUREG_TEXUNIT_CONFIG"}, - {0x081, "GPUREG_TEXUNIT0_BORDER_COLOR"}, - {0x082, "GPUREG_TEXUNIT0_DIM"}, - {0x083, "GPUREG_TEXUNIT0_PARAM"}, - {0x084, "GPUREG_TEXUNIT0_LOD"}, - {0x085, "GPUREG_TEXUNIT0_ADDR1"}, - {0x086, "GPUREG_TEXUNIT0_ADDR2"}, - {0x087, "GPUREG_TEXUNIT0_ADDR3"}, - {0x088, "GPUREG_TEXUNIT0_ADDR4"}, - {0x089, "GPUREG_TEXUNIT0_ADDR5"}, - {0x08A, "GPUREG_TEXUNIT0_ADDR6"}, - {0x08B, "GPUREG_TEXUNIT0_SHADOW"}, - - {0x08E, "GPUREG_TEXUNIT0_TYPE"}, - {0x08F, "GPUREG_LIGHTING_ENABLE0"}, - - {0x091, "GPUREG_TEXUNIT1_BORDER_COLOR"}, - {0x092, "GPUREG_TEXUNIT1_DIM"}, - {0x093, "GPUREG_TEXUNIT1_PARAM"}, - {0x094, "GPUREG_TEXUNIT1_LOD"}, - {0x095, "GPUREG_TEXUNIT1_ADDR"}, - {0x096, "GPUREG_TEXUNIT1_TYPE"}, - - {0x099, "GPUREG_TEXUNIT2_BORDER_COLOR"}, - {0x09A, "GPUREG_TEXUNIT2_DIM"}, - {0x09B, "GPUREG_TEXUNIT2_PARAM"}, - {0x09C, "GPUREG_TEXUNIT2_LOD"}, - {0x09D, "GPUREG_TEXUNIT2_ADDR"}, - {0x09E, "GPUREG_TEXUNIT2_TYPE"}, - - {0x0A8, "GPUREG_TEXUNIT3_PROCTEX0"}, - {0x0A9, "GPUREG_TEXUNIT3_PROCTEX1"}, - {0x0AA, "GPUREG_TEXUNIT3_PROCTEX2"}, - {0x0AB, "GPUREG_TEXUNIT3_PROCTEX3"}, - {0x0AC, "GPUREG_TEXUNIT3_PROCTEX4"}, - {0x0AD, "GPUREG_TEXUNIT3_PROCTEX5"}, - - {0x0AF, "GPUREG_PROCTEX_LUT"}, - {0x0B0, "GPUREG_PROCTEX_LUT_DATA0"}, - {0x0B1, "GPUREG_PROCTEX_LUT_DATA1"}, - {0x0B2, "GPUREG_PROCTEX_LUT_DATA2"}, - {0x0B3, "GPUREG_PROCTEX_LUT_DATA3"}, - {0x0B4, "GPUREG_PROCTEX_LUT_DATA4"}, - {0x0B5, "GPUREG_PROCTEX_LUT_DATA5"}, - {0x0B6, "GPUREG_PROCTEX_LUT_DATA6"}, - {0x0B7, "GPUREG_PROCTEX_LUT_DATA7"}, - - {0x0C0, "GPUREG_TEXENV0_SOURCE"}, - {0x0C1, "GPUREG_TEXENV0_OPERAND"}, - {0x0C2, "GPUREG_TEXENV0_COMBINER"}, - {0x0C3, "GPUREG_TEXENV0_COLOR"}, - {0x0C4, "GPUREG_TEXENV0_SCALE"}, - - {0x0C8, "GPUREG_TEXENV1_SOURCE"}, - {0x0C9, "GPUREG_TEXENV1_OPERAND"}, - {0x0CA, "GPUREG_TEXENV1_COMBINER"}, - {0x0CB, "GPUREG_TEXENV1_COLOR"}, - {0x0CC, "GPUREG_TEXENV1_SCALE"}, - - {0x0D0, "GPUREG_TEXENV2_SOURCE"}, - {0x0D1, "GPUREG_TEXENV2_OPERAND"}, - {0x0D2, "GPUREG_TEXENV2_COMBINER"}, - {0x0D3, "GPUREG_TEXENV2_COLOR"}, - {0x0D4, "GPUREG_TEXENV2_SCALE"}, - - {0x0D8, "GPUREG_TEXENV3_SOURCE"}, - {0x0D9, "GPUREG_TEXENV3_OPERAND"}, - {0x0DA, "GPUREG_TEXENV3_COMBINER"}, - {0x0DB, "GPUREG_TEXENV3_COLOR"}, - {0x0DC, "GPUREG_TEXENV3_SCALE"}, - - {0x0E0, "GPUREG_TEXENV_UPDATE_BUFFER"}, - {0x0E1, "GPUREG_FOG_COLOR"}, - - {0x0E4, "GPUREG_GAS_ATTENUATION"}, - {0x0E5, "GPUREG_GAS_ACCMAX"}, - {0x0E6, "GPUREG_FOG_LUT_INDEX"}, - - {0x0E8, "GPUREG_FOG_LUT_DATA0"}, - {0x0E9, "GPUREG_FOG_LUT_DATA1"}, - {0x0EA, "GPUREG_FOG_LUT_DATA2"}, - {0x0EB, "GPUREG_FOG_LUT_DATA3"}, - {0x0EC, "GPUREG_FOG_LUT_DATA4"}, - {0x0ED, "GPUREG_FOG_LUT_DATA5"}, - {0x0EE, "GPUREG_FOG_LUT_DATA6"}, - {0x0EF, "GPUREG_FOG_LUT_DATA7"}, - {0x0F0, "GPUREG_TEXENV4_SOURCE"}, - {0x0F1, "GPUREG_TEXENV4_OPERAND"}, - {0x0F2, "GPUREG_TEXENV4_COMBINER"}, - {0x0F3, "GPUREG_TEXENV4_COLOR"}, - {0x0F4, "GPUREG_TEXENV4_SCALE"}, - - {0x0F8, "GPUREG_TEXENV5_SOURCE"}, - {0x0F9, "GPUREG_TEXENV5_OPERAND"}, - {0x0FA, "GPUREG_TEXENV5_COMBINER"}, - {0x0FB, "GPUREG_TEXENV5_COLOR"}, - {0x0FC, "GPUREG_TEXENV5_SCALE"}, - {0x0FD, "GPUREG_TEXENV_BUFFER_COLOR"}, - - {0x100, "GPUREG_COLOR_OPERATION"}, - {0x101, "GPUREG_BLEND_FUNC"}, - {0x102, "GPUREG_LOGIC_OP"}, - {0x103, "GPUREG_BLEND_COLOR"}, - {0x104, "GPUREG_FRAGOP_ALPHA_TEST"}, - {0x105, "GPUREG_STENCIL_TEST"}, - {0x106, "GPUREG_STENCIL_OP"}, - {0x107, "GPUREG_DEPTH_COLOR_MASK"}, - - {0x110, "GPUREG_FRAMEBUFFER_INVALIDATE"}, - {0x111, "GPUREG_FRAMEBUFFER_FLUSH"}, - {0x112, "GPUREG_COLORBUFFER_READ"}, - {0x113, "GPUREG_COLORBUFFER_WRITE"}, - {0x114, "GPUREG_DEPTHBUFFER_READ"}, - {0x115, "GPUREG_DEPTHBUFFER_WRITE"}, - {0x116, "GPUREG_DEPTHBUFFER_FORMAT"}, - {0x117, "GPUREG_COLORBUFFER_FORMAT"}, - {0x118, "GPUREG_EARLYDEPTH_TEST2"}, - - {0x11B, "GPUREG_FRAMEBUFFER_BLOCK32"}, - {0x11C, "GPUREG_DEPTHBUFFER_LOC"}, - {0x11D, "GPUREG_COLORBUFFER_LOC"}, - {0x11E, "GPUREG_FRAMEBUFFER_DIM"}, - - {0x120, "GPUREG_GAS_LIGHT_XY"}, - {0x121, "GPUREG_GAS_LIGHT_Z"}, - {0x122, "GPUREG_GAS_LIGHT_Z_COLOR"}, - {0x123, "GPUREG_GAS_LUT_INDEX"}, - {0x124, "GPUREG_GAS_LUT_DATA"}, - - {0x126, "GPUREG_GAS_DELTAZ_DEPTH"}, - - {0x130, "GPUREG_FRAGOP_SHADOW"}, - - {0x140, "GPUREG_LIGHT0_SPECULAR0"}, - {0x141, "GPUREG_LIGHT0_SPECULAR1"}, - {0x142, "GPUREG_LIGHT0_DIFFUSE"}, - {0x143, "GPUREG_LIGHT0_AMBIENT"}, - {0x144, "GPUREG_LIGHT0_XY"}, - {0x145, "GPUREG_LIGHT0_Z"}, - {0x146, "GPUREG_LIGHT0_SPOTDIR_XY"}, - {0x147, "GPUREG_LIGHT0_SPOTDIR_Z"}, - - {0x149, "GPUREG_LIGHT0_CONFIG"}, - {0x14A, "GPUREG_LIGHT0_ATTENUATION_BIAS"}, - {0x14B, "GPUREG_LIGHT0_ATTENUATION_SCALE"}, - - {0x150, "GPUREG_LIGHT1_SPECULAR0"}, - {0x151, "GPUREG_LIGHT1_SPECULAR1"}, - {0x152, "GPUREG_LIGHT1_DIFFUSE"}, - {0x153, "GPUREG_LIGHT1_AMBIENT"}, - {0x154, "GPUREG_LIGHT1_XY"}, - {0x155, "GPUREG_LIGHT1_Z"}, - {0x156, "GPUREG_LIGHT1_SPOTDIR_XY"}, - {0x157, "GPUREG_LIGHT1_SPOTDIR_Z"}, - - {0x159, "GPUREG_LIGHT1_CONFIG"}, - {0x15A, "GPUREG_LIGHT1_ATTENUATION_BIAS"}, - {0x15B, "GPUREG_LIGHT1_ATTENUATION_SCALE"}, - - {0x160, "GPUREG_LIGHT2_SPECULAR0"}, - {0x161, "GPUREG_LIGHT2_SPECULAR1"}, - {0x162, "GPUREG_LIGHT2_DIFFUSE"}, - {0x163, "GPUREG_LIGHT2_AMBIENT"}, - {0x164, "GPUREG_LIGHT2_XY"}, - {0x165, "GPUREG_LIGHT2_Z"}, - {0x166, "GPUREG_LIGHT2_SPOTDIR_XY"}, - {0x167, "GPUREG_LIGHT2_SPOTDIR_Z"}, - - {0x169, "GPUREG_LIGHT2_CONFIG"}, - {0x16A, "GPUREG_LIGHT2_ATTENUATION_BIAS"}, - {0x16B, "GPUREG_LIGHT2_ATTENUATION_SCALE"}, - - {0x170, "GPUREG_LIGHT3_SPECULAR0"}, - {0x171, "GPUREG_LIGHT3_SPECULAR1"}, - {0x172, "GPUREG_LIGHT3_DIFFUSE"}, - {0x173, "GPUREG_LIGHT3_AMBIENT"}, - {0x174, "GPUREG_LIGHT3_XY"}, - {0x175, "GPUREG_LIGHT3_Z"}, - {0x176, "GPUREG_LIGHT3_SPOTDIR_XY"}, - {0x177, "GPUREG_LIGHT3_SPOTDIR_Z"}, - - {0x179, "GPUREG_LIGHT3_CONFIG"}, - {0x17A, "GPUREG_LIGHT3_ATTENUATION_BIAS"}, - {0x17B, "GPUREG_LIGHT3_ATTENUATION_SCALE"}, - - {0x180, "GPUREG_LIGHT4_SPECULAR0"}, - {0x181, "GPUREG_LIGHT4_SPECULAR1"}, - {0x182, "GPUREG_LIGHT4_DIFFUSE"}, - {0x183, "GPUREG_LIGHT4_AMBIENT"}, - {0x184, "GPUREG_LIGHT4_XY"}, - {0x185, "GPUREG_LIGHT4_Z"}, - {0x186, "GPUREG_LIGHT4_SPOTDIR_XY"}, - {0x187, "GPUREG_LIGHT4_SPOTDIR_Z"}, - - {0x189, "GPUREG_LIGHT4_CONFIG"}, - {0x18A, "GPUREG_LIGHT4_ATTENUATION_BIAS"}, - {0x18B, "GPUREG_LIGHT4_ATTENUATION_SCALE"}, - - {0x190, "GPUREG_LIGHT5_SPECULAR0"}, - {0x191, "GPUREG_LIGHT5_SPECULAR1"}, - {0x192, "GPUREG_LIGHT5_DIFFUSE"}, - {0x193, "GPUREG_LIGHT5_AMBIENT"}, - {0x194, "GPUREG_LIGHT5_XY"}, - {0x195, "GPUREG_LIGHT5_Z"}, - {0x196, "GPUREG_LIGHT5_SPOTDIR_XY"}, - {0x197, "GPUREG_LIGHT5_SPOTDIR_Z"}, - - {0x199, "GPUREG_LIGHT5_CONFIG"}, - {0x19A, "GPUREG_LIGHT5_ATTENUATION_BIAS"}, - {0x19B, "GPUREG_LIGHT5_ATTENUATION_SCALE"}, - - {0x1A0, "GPUREG_LIGHT6_SPECULAR0"}, - {0x1A1, "GPUREG_LIGHT6_SPECULAR1"}, - {0x1A2, "GPUREG_LIGHT6_DIFFUSE"}, - {0x1A3, "GPUREG_LIGHT6_AMBIENT"}, - {0x1A4, "GPUREG_LIGHT6_XY"}, - {0x1A5, "GPUREG_LIGHT6_Z"}, - {0x1A6, "GPUREG_LIGHT6_SPOTDIR_XY"}, - {0x1A7, "GPUREG_LIGHT6_SPOTDIR_Z"}, - - {0x1A9, "GPUREG_LIGHT6_CONFIG"}, - {0x1AA, "GPUREG_LIGHT6_ATTENUATION_BIAS"}, - {0x1AB, "GPUREG_LIGHT6_ATTENUATION_SCALE"}, - - {0x1B0, "GPUREG_LIGHT7_SPECULAR0"}, - {0x1B1, "GPUREG_LIGHT7_SPECULAR1"}, - {0x1B2, "GPUREG_LIGHT7_DIFFUSE"}, - {0x1B3, "GPUREG_LIGHT7_AMBIENT"}, - {0x1B4, "GPUREG_LIGHT7_XY"}, - {0x1B5, "GPUREG_LIGHT7_Z"}, - {0x1B6, "GPUREG_LIGHT7_SPOTDIR_XY"}, - {0x1B7, "GPUREG_LIGHT7_SPOTDIR_Z"}, - - {0x1B9, "GPUREG_LIGHT7_CONFIG"}, - {0x1BA, "GPUREG_LIGHT7_ATTENUATION_BIAS"}, - {0x1BB, "GPUREG_LIGHT7_ATTENUATION_SCALE"}, - - {0x1C0, "GPUREG_LIGHTING_AMBIENT"}, - - {0x1C2, "GPUREG_LIGHTING_NUM_LIGHTS"}, - {0x1C3, "GPUREG_LIGHTING_CONFIG0"}, - {0x1C4, "GPUREG_LIGHTING_CONFIG1"}, - {0x1C5, "GPUREG_LIGHTING_LUT_INDEX"}, - {0x1C6, "GPUREG_LIGHTING_ENABLE1"}, - - {0x1C8, "GPUREG_LIGHTING_LUT_DATA0"}, - {0x1C9, "GPUREG_LIGHTING_LUT_DATA1"}, - {0x1CA, "GPUREG_LIGHTING_LUT_DATA2"}, - {0x1CB, "GPUREG_LIGHTING_LUT_DATA3"}, - {0x1CC, "GPUREG_LIGHTING_LUT_DATA4"}, - {0x1CD, "GPUREG_LIGHTING_LUT_DATA5"}, - {0x1CE, "GPUREG_LIGHTING_LUT_DATA6"}, - {0x1CF, "GPUREG_LIGHTING_LUT_DATA7"}, - {0x1D0, "GPUREG_LIGHTING_LUTINPUT_ABS"}, - {0x1D1, "GPUREG_LIGHTING_LUTINPUT_SELECT"}, - {0x1D2, "GPUREG_LIGHTING_LUTINPUT_SCALE"}, - - {0x1D9, "GPUREG_LIGHTING_LIGHT_PERMUTATION"}, - - {0x200, "GPUREG_ATTRIBBUFFERS_LOC"}, - {0x201, "GPUREG_ATTRIBBUFFERS_FORMAT_LOW"}, - {0x202, "GPUREG_ATTRIBBUFFERS_FORMAT_HIGH"}, - {0x203, "GPUREG_ATTRIBBUFFER0_OFFSET"}, - {0x204, "GPUREG_ATTRIBBUFFER0_CONFIG1"}, - {0x205, "GPUREG_ATTRIBBUFFER0_CONFIG2"}, - {0x206, "GPUREG_ATTRIBBUFFER1_OFFSET"}, - {0x207, "GPUREG_ATTRIBBUFFER1_CONFIG1"}, - {0x208, "GPUREG_ATTRIBBUFFER1_CONFIG2"}, - {0x209, "GPUREG_ATTRIBBUFFER2_OFFSET"}, - {0x20A, "GPUREG_ATTRIBBUFFER2_CONFIG1"}, - {0x20B, "GPUREG_ATTRIBBUFFER2_CONFIG2"}, - {0x20C, "GPUREG_ATTRIBBUFFER3_OFFSET"}, - {0x20D, "GPUREG_ATTRIBBUFFER3_CONFIG1"}, - {0x20E, "GPUREG_ATTRIBBUFFER3_CONFIG2"}, - {0x20F, "GPUREG_ATTRIBBUFFER4_OFFSET"}, - {0x210, "GPUREG_ATTRIBBUFFER4_CONFIG1"}, - {0x211, "GPUREG_ATTRIBBUFFER4_CONFIG2"}, - {0x212, "GPUREG_ATTRIBBUFFER5_OFFSET"}, - {0x213, "GPUREG_ATTRIBBUFFER5_CONFIG1"}, - {0x214, "GPUREG_ATTRIBBUFFER5_CONFIG2"}, - {0x215, "GPUREG_ATTRIBBUFFER6_OFFSET"}, - {0x216, "GPUREG_ATTRIBBUFFER6_CONFIG1"}, - {0x217, "GPUREG_ATTRIBBUFFER6_CONFIG2"}, - {0x218, "GPUREG_ATTRIBBUFFER7_OFFSET"}, - {0x219, "GPUREG_ATTRIBBUFFER7_CONFIG1"}, - {0x21A, "GPUREG_ATTRIBBUFFER7_CONFIG2"}, - {0x21B, "GPUREG_ATTRIBBUFFER8_OFFSET"}, - {0x21C, "GPUREG_ATTRIBBUFFER8_CONFIG1"}, - {0x21D, "GPUREG_ATTRIBBUFFER8_CONFIG2"}, - {0x21E, "GPUREG_ATTRIBBUFFER9_OFFSET"}, - {0x21F, "GPUREG_ATTRIBBUFFER9_CONFIG1"}, - {0x220, "GPUREG_ATTRIBBUFFER9_CONFIG2"}, - {0x221, "GPUREG_ATTRIBBUFFER10_OFFSET"}, - {0x222, "GPUREG_ATTRIBBUFFER10_CONFIG1"}, - {0x223, "GPUREG_ATTRIBBUFFER10_CONFIG2"}, - {0x224, "GPUREG_ATTRIBBUFFER11_OFFSET"}, - {0x225, "GPUREG_ATTRIBBUFFER11_CONFIG1"}, - {0x226, "GPUREG_ATTRIBBUFFER11_CONFIG2"}, - {0x227, "GPUREG_INDEXBUFFER_CONFIG"}, - {0x228, "GPUREG_NUMVERTICES"}, - {0x229, "GPUREG_GEOSTAGE_CONFIG"}, - {0x22A, "GPUREG_VERTEX_OFFSET"}, - - {0x22D, "GPUREG_POST_VERTEX_CACHE_NUM"}, - {0x22E, "GPUREG_DRAWARRAYS"}, - {0x22F, "GPUREG_DRAWELEMENTS"}, - - {0x231, "GPUREG_VTX_FUNC"}, - {0x232, "GPUREG_FIXEDATTRIB_INDEX"}, - {0x233, "GPUREG_FIXEDATTRIB_DATA0"}, - {0x234, "GPUREG_FIXEDATTRIB_DATA1"}, - {0x235, "GPUREG_FIXEDATTRIB_DATA2"}, - - {0x238, "GPUREG_CMDBUF_SIZE0"}, - {0x239, "GPUREG_CMDBUF_SIZE1"}, - {0x23A, "GPUREG_CMDBUF_ADDR0"}, - {0x23B, "GPUREG_CMDBUF_ADDR1"}, - {0x23C, "GPUREG_CMDBUF_JUMP0"}, - {0x23D, "GPUREG_CMDBUF_JUMP1"}, - - {0x242, "GPUREG_VSH_NUM_ATTR"}, - - {0x244, "GPUREG_VSH_COM_MODE"}, - {0x245, "GPUREG_START_DRAW_FUNC0"}, - - {0x24A, "GPUREG_VSH_OUTMAP_TOTAL1"}, - - {0x251, "GPUREG_VSH_OUTMAP_TOTAL2"}, - {0x252, "GPUREG_GSH_MISC0"}, - {0x253, "GPUREG_GEOSTAGE_CONFIG2"}, - {0x254, "GPUREG_GSH_MISC1"}, - - {0x25E, "GPUREG_PRIMITIVE_CONFIG"}, - {0x25F, "GPUREG_RESTART_PRIMITIVE"}, - - {0x280, "GPUREG_GSH_BOOLUNIFORM"}, - {0x281, "GPUREG_GSH_INTUNIFORM_I0"}, - {0x282, "GPUREG_GSH_INTUNIFORM_I1"}, - {0x283, "GPUREG_GSH_INTUNIFORM_I2"}, - {0x284, "GPUREG_GSH_INTUNIFORM_I3"}, - - {0x289, "GPUREG_GSH_INPUTBUFFER_CONFIG"}, - {0x28A, "GPUREG_GSH_ENTRYPOINT"}, - {0x28B, "GPUREG_GSH_ATTRIBUTES_PERMUTATION_LOW"}, - {0x28C, "GPUREG_GSH_ATTRIBUTES_PERMUTATION_HIGH"}, - {0x28D, "GPUREG_GSH_OUTMAP_MASK"}, - - {0x28F, "GPUREG_GSH_CODETRANSFER_END"}, - {0x290, "GPUREG_GSH_FLOATUNIFORM_INDEX"}, - {0x291, "GPUREG_GSH_FLOATUNIFORM_DATA0"}, - {0x292, "GPUREG_GSH_FLOATUNIFORM_DATA1"}, - {0x293, "GPUREG_GSH_FLOATUNIFORM_DATA2"}, - {0x294, "GPUREG_GSH_FLOATUNIFORM_DATA3"}, - {0x295, "GPUREG_GSH_FLOATUNIFORM_DATA4"}, - {0x296, "GPUREG_GSH_FLOATUNIFORM_DATA5"}, - {0x297, "GPUREG_GSH_FLOATUNIFORM_DATA6"}, - {0x298, "GPUREG_GSH_FLOATUNIFORM_DATA7"}, - - {0x29B, "GPUREG_GSH_CODETRANSFER_INDEX"}, - {0x29C, "GPUREG_GSH_CODETRANSFER_DATA0"}, - {0x29D, "GPUREG_GSH_CODETRANSFER_DATA1"}, - {0x29E, "GPUREG_GSH_CODETRANSFER_DATA2"}, - {0x29F, "GPUREG_GSH_CODETRANSFER_DATA3"}, - {0x2A0, "GPUREG_GSH_CODETRANSFER_DATA4"}, - {0x2A1, "GPUREG_GSH_CODETRANSFER_DATA5"}, - {0x2A2, "GPUREG_GSH_CODETRANSFER_DATA6"}, - {0x2A3, "GPUREG_GSH_CODETRANSFER_DATA7"}, - - {0x2A5, "GPUREG_GSH_OPDESCS_INDEX"}, - {0x2A6, "GPUREG_GSH_OPDESCS_DATA0"}, - {0x2A7, "GPUREG_GSH_OPDESCS_DATA1"}, - {0x2A8, "GPUREG_GSH_OPDESCS_DATA2"}, - {0x2A9, "GPUREG_GSH_OPDESCS_DATA3"}, - {0x2AA, "GPUREG_GSH_OPDESCS_DATA4"}, - {0x2AB, "GPUREG_GSH_OPDESCS_DATA5"}, - {0x2AC, "GPUREG_GSH_OPDESCS_DATA6"}, - {0x2AD, "GPUREG_GSH_OPDESCS_DATA7"}, - - {0x2B0, "GPUREG_VSH_BOOLUNIFORM"}, - {0x2B1, "GPUREG_VSH_INTUNIFORM_I0"}, - {0x2B2, "GPUREG_VSH_INTUNIFORM_I1"}, - {0x2B3, "GPUREG_VSH_INTUNIFORM_I2"}, - {0x2B4, "GPUREG_VSH_INTUNIFORM_I3"}, - - {0x2B9, "GPUREG_VSH_INPUTBUFFER_CONFIG"}, - {0x2BA, "GPUREG_VSH_ENTRYPOINT"}, - {0x2BB, "GPUREG_VSH_ATTRIBUTES_PERMUTATION_LOW"}, - {0x2BC, "GPUREG_VSH_ATTRIBUTES_PERMUTATION_HIGH"}, - {0x2BD, "GPUREG_VSH_OUTMAP_MASK"}, - - {0x2BF, "GPUREG_VSH_CODETRANSFER_END"}, - {0x2C0, "GPUREG_VSH_FLOATUNIFORM_INDEX"}, - {0x2C1, "GPUREG_VSH_FLOATUNIFORM_DATA0"}, - {0x2C2, "GPUREG_VSH_FLOATUNIFORM_DATA1"}, - {0x2C3, "GPUREG_VSH_FLOATUNIFORM_DATA2"}, - {0x2C4, "GPUREG_VSH_FLOATUNIFORM_DATA3"}, - {0x2C5, "GPUREG_VSH_FLOATUNIFORM_DATA4"}, - {0x2C6, "GPUREG_VSH_FLOATUNIFORM_DATA5"}, - {0x2C7, "GPUREG_VSH_FLOATUNIFORM_DATA6"}, - {0x2C8, "GPUREG_VSH_FLOATUNIFORM_DATA7"}, - - {0x2CB, "GPUREG_VSH_CODETRANSFER_INDEX"}, - {0x2CC, "GPUREG_VSH_CODETRANSFER_DATA0"}, - {0x2CD, "GPUREG_VSH_CODETRANSFER_DATA1"}, - {0x2CE, "GPUREG_VSH_CODETRANSFER_DATA2"}, - {0x2CF, "GPUREG_VSH_CODETRANSFER_DATA3"}, - {0x2D0, "GPUREG_VSH_CODETRANSFER_DATA4"}, - {0x2D1, "GPUREG_VSH_CODETRANSFER_DATA5"}, - {0x2D2, "GPUREG_VSH_CODETRANSFER_DATA6"}, - {0x2D3, "GPUREG_VSH_CODETRANSFER_DATA7"}, - - {0x2D5, "GPUREG_VSH_OPDESCS_INDEX"}, - {0x2D6, "GPUREG_VSH_OPDESCS_DATA0"}, - {0x2D7, "GPUREG_VSH_OPDESCS_DATA1"}, - {0x2D8, "GPUREG_VSH_OPDESCS_DATA2"}, - {0x2D9, "GPUREG_VSH_OPDESCS_DATA3"}, - {0x2DA, "GPUREG_VSH_OPDESCS_DATA4"}, - {0x2DB, "GPUREG_VSH_OPDESCS_DATA5"}, - {0x2DC, "GPUREG_VSH_OPDESCS_DATA6"}, - {0x2DD, "GPUREG_VSH_OPDESCS_DATA7"}, -}; - -std::string Regs::GetCommandName(int index) { - static std::unordered_map<u32, const char*> map; - - if (map.empty()) { - map.insert(std::begin(register_names), std::end(register_names)); - } - - // Return empty string if no match is found - auto it = map.find(index); - if (it != map.end()) { - return it->second; - } else { - return std::string(); - } -} - void Init() { g_state.Reset(); } @@ -513,6 +30,6 @@ void State::Reset() { Zero(gs); Zero(cmd_list); Zero(immediate); - primitive_assembler.Reconfigure(Regs::TriangleTopology::List); + primitive_assembler.Reconfigure(PipelineRegs::TriangleTopology::List); } } diff --git a/src/video_core/pica.h b/src/video_core/pica.h index b2db609ec..dc8aa6670 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -4,1412 +4,9 @@ #pragma once -#include <array> -#include <cstddef> -#include <string> - -#ifndef _MSC_VER -#include <type_traits> // for std::enable_if -#endif - -#include "common/assert.h" -#include "common/bit_field.h" -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "common/logging/log.h" -#include "common/vector_math.h" - +#include "video_core/regs_texturing.h" namespace Pica { -// Returns index corresponding to the Regs member labeled by field_name -// TODO: Due to Visual studio bug 209229, offsetof does not return constant expressions -// when used with array elements (e.g. PICA_REG_INDEX(vs_uniform_setup.set_value[1])). -// For details cf. -// https://connect.microsoft.com/VisualStudio/feedback/details/209229/offsetof-does-not-produce-a-constant-expression-for-array-members -// Hopefully, this will be fixed sometime in the future. -// For lack of better alternatives, we currently hardcode the offsets when constant -// expressions are needed via PICA_REG_INDEX_WORKAROUND (on sane compilers, static_asserts -// will then make sure the offsets indeed match the automatically calculated ones). -#define PICA_REG_INDEX(field_name) (offsetof(Pica::Regs, field_name) / sizeof(u32)) -#if defined(_MSC_VER) -#define PICA_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) (backup_workaround_index) -#else -// NOTE: Yeah, hacking in a static_assert here just to workaround the lacking MSVC compiler -// really is this annoying. This macro just forwards its first argument to PICA_REG_INDEX -// and then performs a (no-op) cast to size_t iff the second argument matches the expected -// field offset. Otherwise, the compiler will fail to compile this code. -#define PICA_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) \ - ((typename std::enable_if<backup_workaround_index == PICA_REG_INDEX(field_name), \ - size_t>::type)PICA_REG_INDEX(field_name)) -#endif // _MSC_VER - -struct Regs { - - INSERT_PADDING_WORDS(0x10); - - u32 trigger_irq; - - INSERT_PADDING_WORDS(0x2f); - - enum class CullMode : u32 { - // Select which polygons are considered to be "frontfacing". - KeepAll = 0, - KeepClockWise = 1, - KeepCounterClockWise = 2, - // TODO: What does the third value imply? - }; - - union { - BitField<0, 2, CullMode> cull_mode; - }; - - BitField<0, 24, u32> viewport_size_x; - - INSERT_PADDING_WORDS(0x1); - - BitField<0, 24, u32> viewport_size_y; - - INSERT_PADDING_WORDS(0x9); - - BitField<0, 24, u32> viewport_depth_range; // float24 - BitField<0, 24, u32> viewport_depth_near_plane; // float24 - - BitField<0, 3, u32> vs_output_total; - - union VSOutputAttributes { - // Maps components of output vertex attributes to semantics - enum Semantic : u32 { - POSITION_X = 0, - POSITION_Y = 1, - POSITION_Z = 2, - POSITION_W = 3, - - QUATERNION_X = 4, - QUATERNION_Y = 5, - QUATERNION_Z = 6, - QUATERNION_W = 7, - - COLOR_R = 8, - COLOR_G = 9, - COLOR_B = 10, - COLOR_A = 11, - - TEXCOORD0_U = 12, - TEXCOORD0_V = 13, - TEXCOORD1_U = 14, - TEXCOORD1_V = 15, - - // TODO: Not verified - VIEW_X = 18, - VIEW_Y = 19, - VIEW_Z = 20, - - TEXCOORD2_U = 22, - TEXCOORD2_V = 23, - - INVALID = 31, - }; - - BitField<0, 5, Semantic> map_x; - BitField<8, 5, Semantic> map_y; - BitField<16, 5, Semantic> map_z; - BitField<24, 5, Semantic> map_w; - } vs_output_attributes[7]; - - INSERT_PADDING_WORDS(0xe); - - enum class ScissorMode : u32 { - Disabled = 0, - Exclude = 1, // Exclude pixels inside the scissor box - - Include = 3 // Exclude pixels outside the scissor box - }; - - struct { - BitField<0, 2, ScissorMode> mode; - - union { - BitField<0, 16, u32> x1; - BitField<16, 16, u32> y1; - }; - - union { - BitField<0, 16, u32> x2; - BitField<16, 16, u32> y2; - }; - } scissor_test; - - union { - BitField<0, 10, s32> x; - BitField<16, 10, s32> y; - } viewport_corner; - - INSERT_PADDING_WORDS(0x1); - - // TODO: early depth - INSERT_PADDING_WORDS(0x1); - - INSERT_PADDING_WORDS(0x2); - - enum DepthBuffering : u32 { - WBuffering = 0, - ZBuffering = 1, - }; - BitField<0, 1, DepthBuffering> depthmap_enable; - - INSERT_PADDING_WORDS(0x12); - - struct TextureConfig { - enum TextureType : u32 { - Texture2D = 0, - TextureCube = 1, - Shadow2D = 2, - Projection2D = 3, - ShadowCube = 4, - Disabled = 5, - }; - - enum WrapMode : u32 { - ClampToEdge = 0, - ClampToBorder = 1, - Repeat = 2, - MirroredRepeat = 3, - }; - - enum TextureFilter : u32 { - Nearest = 0, - Linear = 1, - }; - - union { - u32 raw; - BitField<0, 8, u32> r; - BitField<8, 8, u32> g; - BitField<16, 8, u32> b; - BitField<24, 8, u32> a; - } border_color; - - union { - BitField<0, 16, u32> height; - BitField<16, 16, u32> width; - }; - - union { - BitField<1, 1, TextureFilter> mag_filter; - BitField<2, 1, TextureFilter> min_filter; - BitField<8, 2, WrapMode> wrap_t; - BitField<12, 2, WrapMode> wrap_s; - BitField<28, 2, TextureType> - type; ///< @note Only valid for texture 0 according to 3DBrew. - }; - - INSERT_PADDING_WORDS(0x1); - - u32 address; - - u32 GetPhysicalAddress() const { - return DecodeAddressRegister(address); - } - - // texture1 and texture2 store the texture format directly after the address - // whereas texture0 inserts some additional flags inbetween. - // Hence, we store the format separately so that all other parameters can be described - // in a single structure. - }; - - enum class TextureFormat : u32 { - RGBA8 = 0, - RGB8 = 1, - RGB5A1 = 2, - RGB565 = 3, - RGBA4 = 4, - IA8 = 5, - RG8 = 6, ///< @note Also called HILO8 in 3DBrew. - I8 = 7, - A8 = 8, - IA4 = 9, - I4 = 10, - A4 = 11, - ETC1 = 12, // compressed - ETC1A4 = 13, // compressed - }; - - enum class LogicOp : u32 { - Clear = 0, - And = 1, - AndReverse = 2, - Copy = 3, - Set = 4, - CopyInverted = 5, - NoOp = 6, - Invert = 7, - Nand = 8, - Or = 9, - Nor = 10, - Xor = 11, - Equiv = 12, - AndInverted = 13, - OrReverse = 14, - OrInverted = 15, - }; - - static unsigned NibblesPerPixel(TextureFormat format) { - switch (format) { - case TextureFormat::RGBA8: - return 8; - - case TextureFormat::RGB8: - return 6; - - case TextureFormat::RGB5A1: - case TextureFormat::RGB565: - case TextureFormat::RGBA4: - case TextureFormat::IA8: - case TextureFormat::RG8: - return 4; - - case TextureFormat::I4: - case TextureFormat::A4: - return 1; - - case TextureFormat::I8: - case TextureFormat::A8: - case TextureFormat::IA4: - default: // placeholder for yet unknown formats - return 2; - } - } - - union { - BitField<0, 1, u32> texture0_enable; - BitField<1, 1, u32> texture1_enable; - BitField<2, 1, u32> texture2_enable; - }; - TextureConfig texture0; - INSERT_PADDING_WORDS(0x8); - BitField<0, 4, TextureFormat> texture0_format; - BitField<0, 1, u32> fragment_lighting_enable; - INSERT_PADDING_WORDS(0x1); - TextureConfig texture1; - BitField<0, 4, TextureFormat> texture1_format; - INSERT_PADDING_WORDS(0x2); - TextureConfig texture2; - BitField<0, 4, TextureFormat> texture2_format; - INSERT_PADDING_WORDS(0x21); - - struct FullTextureConfig { - const bool enabled; - const TextureConfig config; - const TextureFormat format; - }; - const std::array<FullTextureConfig, 3> GetTextures() const { - return {{ - {texture0_enable.ToBool(), texture0, texture0_format}, - {texture1_enable.ToBool(), texture1, texture1_format}, - {texture2_enable.ToBool(), texture2, texture2_format}, - }}; - } - - // 0xc0-0xff: Texture Combiner (akin to glTexEnv) - struct TevStageConfig { - enum class Source : u32 { - PrimaryColor = 0x0, - PrimaryFragmentColor = 0x1, - SecondaryFragmentColor = 0x2, - - Texture0 = 0x3, - Texture1 = 0x4, - Texture2 = 0x5, - Texture3 = 0x6, - - PreviousBuffer = 0xd, - Constant = 0xe, - Previous = 0xf, - }; - - enum class ColorModifier : u32 { - SourceColor = 0x0, - OneMinusSourceColor = 0x1, - SourceAlpha = 0x2, - OneMinusSourceAlpha = 0x3, - SourceRed = 0x4, - OneMinusSourceRed = 0x5, - - SourceGreen = 0x8, - OneMinusSourceGreen = 0x9, - - SourceBlue = 0xc, - OneMinusSourceBlue = 0xd, - }; - - enum class AlphaModifier : u32 { - SourceAlpha = 0x0, - OneMinusSourceAlpha = 0x1, - SourceRed = 0x2, - OneMinusSourceRed = 0x3, - SourceGreen = 0x4, - OneMinusSourceGreen = 0x5, - SourceBlue = 0x6, - OneMinusSourceBlue = 0x7, - }; - - enum class Operation : u32 { - Replace = 0, - Modulate = 1, - Add = 2, - AddSigned = 3, - Lerp = 4, - Subtract = 5, - Dot3_RGB = 6, - - MultiplyThenAdd = 8, - AddThenMultiply = 9, - }; - - union { - u32 sources_raw; - BitField<0, 4, Source> color_source1; - BitField<4, 4, Source> color_source2; - BitField<8, 4, Source> color_source3; - BitField<16, 4, Source> alpha_source1; - BitField<20, 4, Source> alpha_source2; - BitField<24, 4, Source> alpha_source3; - }; - - union { - u32 modifiers_raw; - BitField<0, 4, ColorModifier> color_modifier1; - BitField<4, 4, ColorModifier> color_modifier2; - BitField<8, 4, ColorModifier> color_modifier3; - BitField<12, 3, AlphaModifier> alpha_modifier1; - BitField<16, 3, AlphaModifier> alpha_modifier2; - BitField<20, 3, AlphaModifier> alpha_modifier3; - }; - - union { - u32 ops_raw; - BitField<0, 4, Operation> color_op; - BitField<16, 4, Operation> alpha_op; - }; - - union { - u32 const_color; - BitField<0, 8, u32> const_r; - BitField<8, 8, u32> const_g; - BitField<16, 8, u32> const_b; - BitField<24, 8, u32> const_a; - }; - - union { - u32 scales_raw; - BitField<0, 2, u32> color_scale; - BitField<16, 2, u32> alpha_scale; - }; - - inline unsigned GetColorMultiplier() const { - return (color_scale < 3) ? (1 << color_scale) : 1; - } - - inline unsigned GetAlphaMultiplier() const { - return (alpha_scale < 3) ? (1 << alpha_scale) : 1; - } - }; - - TevStageConfig tev_stage0; - INSERT_PADDING_WORDS(0x3); - TevStageConfig tev_stage1; - INSERT_PADDING_WORDS(0x3); - TevStageConfig tev_stage2; - INSERT_PADDING_WORDS(0x3); - TevStageConfig tev_stage3; - INSERT_PADDING_WORDS(0x3); - - enum class FogMode : u32 { - None = 0, - Fog = 5, - Gas = 7, - }; - - union { - BitField<0, 3, FogMode> fog_mode; - BitField<16, 1, u32> fog_flip; - - union { - // Tev stages 0-3 write their output to the combiner buffer if the corresponding bit in - // these masks are set - BitField<8, 4, u32> update_mask_rgb; - BitField<12, 4, u32> update_mask_a; - - bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const { - return (stage_index < 4) && (update_mask_rgb & (1 << stage_index)); - } - - bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const { - return (stage_index < 4) && (update_mask_a & (1 << stage_index)); - } - } tev_combiner_buffer_input; - }; - - union { - u32 raw; - BitField<0, 8, u32> r; - BitField<8, 8, u32> g; - BitField<16, 8, u32> b; - } fog_color; - - INSERT_PADDING_WORDS(0x4); - - BitField<0, 16, u32> fog_lut_offset; - - INSERT_PADDING_WORDS(0x1); - - u32 fog_lut_data[8]; - - TevStageConfig tev_stage4; - INSERT_PADDING_WORDS(0x3); - TevStageConfig tev_stage5; - - union { - u32 raw; - BitField<0, 8, u32> r; - BitField<8, 8, u32> g; - BitField<16, 8, u32> b; - BitField<24, 8, u32> a; - } tev_combiner_buffer_color; - - INSERT_PADDING_WORDS(0x2); - - const std::array<Regs::TevStageConfig, 6> GetTevStages() const { - return {{tev_stage0, tev_stage1, tev_stage2, tev_stage3, tev_stage4, tev_stage5}}; - }; - - enum class BlendEquation : u32 { - Add = 0, - Subtract = 1, - ReverseSubtract = 2, - Min = 3, - Max = 4, - }; - - enum class BlendFactor : u32 { - Zero = 0, - One = 1, - SourceColor = 2, - OneMinusSourceColor = 3, - DestColor = 4, - OneMinusDestColor = 5, - SourceAlpha = 6, - OneMinusSourceAlpha = 7, - DestAlpha = 8, - OneMinusDestAlpha = 9, - ConstantColor = 10, - OneMinusConstantColor = 11, - ConstantAlpha = 12, - OneMinusConstantAlpha = 13, - SourceAlphaSaturate = 14, - }; - - enum class CompareFunc : u32 { - Never = 0, - Always = 1, - Equal = 2, - NotEqual = 3, - LessThan = 4, - LessThanOrEqual = 5, - GreaterThan = 6, - GreaterThanOrEqual = 7, - }; - - enum class StencilAction : u32 { - Keep = 0, - Zero = 1, - Replace = 2, - Increment = 3, - Decrement = 4, - Invert = 5, - IncrementWrap = 6, - DecrementWrap = 7, - }; - - struct { - union { - // If false, logic blending is used - BitField<8, 1, u32> alphablend_enable; - }; - - union { - BitField<0, 8, BlendEquation> blend_equation_rgb; - BitField<8, 8, BlendEquation> blend_equation_a; - - BitField<16, 4, BlendFactor> factor_source_rgb; - BitField<20, 4, BlendFactor> factor_dest_rgb; - - BitField<24, 4, BlendFactor> factor_source_a; - BitField<28, 4, BlendFactor> factor_dest_a; - } alpha_blending; - - union { - BitField<0, 4, LogicOp> logic_op; - }; - - union { - u32 raw; - BitField<0, 8, u32> r; - BitField<8, 8, u32> g; - BitField<16, 8, u32> b; - BitField<24, 8, u32> a; - } blend_const; - - union { - BitField<0, 1, u32> enable; - BitField<4, 3, CompareFunc> func; - BitField<8, 8, u32> ref; - } alpha_test; - - struct { - union { - // Raw value of this register - u32 raw_func; - - // If true, enable stencil testing - BitField<0, 1, u32> enable; - - // Comparison operation for stencil testing - BitField<4, 3, CompareFunc> func; - - // Mask used to control writing to the stencil buffer - BitField<8, 8, u32> write_mask; - - // Value to compare against for stencil testing - BitField<16, 8, u32> reference_value; - - // Mask to apply on stencil test inputs - BitField<24, 8, u32> input_mask; - }; - - union { - // Raw value of this register - u32 raw_op; - - // Action to perform when the stencil test fails - BitField<0, 3, StencilAction> action_stencil_fail; - - // Action to perform when stencil testing passed but depth testing fails - BitField<4, 3, StencilAction> action_depth_fail; - - // Action to perform when both stencil and depth testing pass - BitField<8, 3, StencilAction> action_depth_pass; - }; - } stencil_test; - - union { - BitField<0, 1, u32> depth_test_enable; - BitField<4, 3, CompareFunc> depth_test_func; - BitField<8, 1, u32> red_enable; - BitField<9, 1, u32> green_enable; - BitField<10, 1, u32> blue_enable; - BitField<11, 1, u32> alpha_enable; - BitField<12, 1, u32> depth_write_enable; - }; - - INSERT_PADDING_WORDS(0x8); - } output_merger; - - // Components are laid out in reverse byte order, most significant bits first. - enum class ColorFormat : u32 { - RGBA8 = 0, - RGB8 = 1, - RGB5A1 = 2, - RGB565 = 3, - RGBA4 = 4, - }; - - enum class DepthFormat : u32 { - D16 = 0, - D24 = 2, - D24S8 = 3, - }; - - // Returns the number of bytes in the specified color format - static unsigned BytesPerColorPixel(ColorFormat format) { - switch (format) { - case ColorFormat::RGBA8: - return 4; - case ColorFormat::RGB8: - return 3; - case ColorFormat::RGB5A1: - case ColorFormat::RGB565: - case ColorFormat::RGBA4: - return 2; - default: - LOG_CRITICAL(HW_GPU, "Unknown color format %u", format); - UNIMPLEMENTED(); - } - } - - struct FramebufferConfig { - INSERT_PADDING_WORDS(0x3); - - union { - BitField<0, 4, u32> allow_color_write; // 0 = disable, else enable - }; - - INSERT_PADDING_WORDS(0x1); - - union { - BitField<0, 2, u32> allow_depth_stencil_write; // 0 = disable, else enable - }; - - DepthFormat depth_format; // TODO: Should be a BitField! - BitField<16, 3, ColorFormat> color_format; - - INSERT_PADDING_WORDS(0x4); - - u32 depth_buffer_address; - u32 color_buffer_address; - - union { - // Apparently, the framebuffer width is stored as expected, - // while the height is stored as the actual height minus one. - // Hence, don't access these fields directly but use the accessors - // GetWidth() and GetHeight() instead. - BitField<0, 11, u32> width; - BitField<12, 10, u32> height; - }; - - INSERT_PADDING_WORDS(0x1); - - inline u32 GetColorBufferPhysicalAddress() const { - return DecodeAddressRegister(color_buffer_address); - } - inline u32 GetDepthBufferPhysicalAddress() const { - return DecodeAddressRegister(depth_buffer_address); - } - - inline u32 GetWidth() const { - return width; - } - - inline u32 GetHeight() const { - return height + 1; - } - } framebuffer; - - // Returns the number of bytes in the specified depth format - static u32 BytesPerDepthPixel(DepthFormat format) { - switch (format) { - case DepthFormat::D16: - return 2; - case DepthFormat::D24: - return 3; - case DepthFormat::D24S8: - return 4; - default: - LOG_CRITICAL(HW_GPU, "Unknown depth format %u", format); - UNIMPLEMENTED(); - } - } - - // Returns the number of bits per depth component of the specified depth format - static u32 DepthBitsPerPixel(DepthFormat format) { - switch (format) { - case DepthFormat::D16: - return 16; - case DepthFormat::D24: - case DepthFormat::D24S8: - return 24; - default: - LOG_CRITICAL(HW_GPU, "Unknown depth format %u", format); - UNIMPLEMENTED(); - } - } - - INSERT_PADDING_WORDS(0x20); - - enum class LightingSampler { - Distribution0 = 0, - Distribution1 = 1, - Fresnel = 3, - ReflectBlue = 4, - ReflectGreen = 5, - ReflectRed = 6, - SpotlightAttenuation = 8, - DistanceAttenuation = 16, - }; - - /** - * Pica fragment lighting supports using different LUTs for each lighting component: - * Reflectance R, G, and B channels, distribution function for specular components 0 and 1, - * fresnel factor, and spotlight attenuation. Furthermore, which LUTs are used for each channel - * (or whether a channel is enabled at all) is specified by various pre-defined lighting - * configurations. With configurations that require more LUTs, more cycles are required on HW to - * perform lighting computations. - */ - enum class LightingConfig { - Config0 = 0, ///< Reflect Red, Distribution 0, Spotlight - Config1 = 1, ///< Reflect Red, Fresnel, Spotlight - Config2 = 2, ///< Reflect Red, Distribution 0/1 - Config3 = 3, ///< Distribution 0/1, Fresnel - Config4 = 4, ///< Reflect Red/Green/Blue, Distribution 0/1, Spotlight - Config5 = 5, ///< Reflect Red/Green/Blue, Distribution 0, Fresnel, Spotlight - Config6 = 6, ///< Reflect Red, Distribution 0/1, Fresnel, Spotlight - Config7 = 8, ///< Reflect Red/Green/Blue, Distribution 0/1, Fresnel, Spotlight - ///< NOTE: '8' is intentional, '7' does not appear to be a valid configuration - }; - - /// Selects which lighting components are affected by fresnel - enum class LightingFresnelSelector { - None = 0, ///< Fresnel is disabled - PrimaryAlpha = 1, ///< Primary (diffuse) lighting alpha is affected by fresnel - SecondaryAlpha = 2, ///< Secondary (specular) lighting alpha is affected by fresnel - Both = - PrimaryAlpha | - SecondaryAlpha, ///< Both primary and secondary lighting alphas are affected by fresnel - }; - - /// Factor used to scale the output of a lighting LUT - enum class LightingScale { - Scale1 = 0, ///< Scale is 1x - Scale2 = 1, ///< Scale is 2x - Scale4 = 2, ///< Scale is 4x - Scale8 = 3, ///< Scale is 8x - Scale1_4 = 6, ///< Scale is 0.25x - Scale1_2 = 7, ///< Scale is 0.5x - }; - - enum class LightingLutInput { - NH = 0, // Cosine of the angle between the normal and half-angle vectors - VH = 1, // Cosine of the angle between the view and half-angle vectors - NV = 2, // Cosine of the angle between the normal and the view vector - LN = 3, // Cosine of the angle between the light and the normal vectors - }; - - enum class LightingBumpMode : u32 { - None = 0, - NormalMap = 1, - TangentMap = 2, - }; - - union LightColor { - BitField<0, 10, u32> b; - BitField<10, 10, u32> g; - BitField<20, 10, u32> r; - - Math::Vec3f ToVec3f() const { - // These fields are 10 bits wide, however 255 corresponds to 1.0f for each color - // component - return Math::MakeVec((f32)r / 255.f, (f32)g / 255.f, (f32)b / 255.f); - } - }; - - /// Returns true if the specified lighting sampler is supported by the current Pica lighting - /// configuration - static bool IsLightingSamplerSupported(LightingConfig config, LightingSampler sampler) { - switch (sampler) { - case LightingSampler::Distribution0: - return (config != LightingConfig::Config1); - - case LightingSampler::Distribution1: - return (config != LightingConfig::Config0) && (config != LightingConfig::Config1) && - (config != LightingConfig::Config5); - - case LightingSampler::Fresnel: - return (config != LightingConfig::Config0) && (config != LightingConfig::Config2) && - (config != LightingConfig::Config4); - - case LightingSampler::ReflectRed: - return (config != LightingConfig::Config3); - - case LightingSampler::ReflectGreen: - case LightingSampler::ReflectBlue: - return (config == LightingConfig::Config4) || (config == LightingConfig::Config5) || - (config == LightingConfig::Config7); - default: - UNREACHABLE_MSG("Regs::IsLightingSamplerSupported: Reached " - "unreachable section, sampler should be one " - "of Distribution0, Distribution1, Fresnel, " - "ReflectRed, ReflectGreen or ReflectBlue, instead " - "got %i", - static_cast<int>(config)); - } - } - - struct { - struct LightSrc { - LightColor specular_0; // material.specular_0 * light.specular_0 - LightColor specular_1; // material.specular_1 * light.specular_1 - LightColor diffuse; // material.diffuse * light.diffuse - LightColor ambient; // material.ambient * light.ambient - - // Encoded as 16-bit floating point - union { - BitField<0, 16, u32> x; - BitField<16, 16, u32> y; - }; - union { - BitField<0, 16, u32> z; - }; - - INSERT_PADDING_WORDS(0x3); - - union { - BitField<0, 1, u32> directional; - BitField<1, 1, u32> two_sided_diffuse; // When disabled, clamp dot-product to 0 - } config; - - BitField<0, 20, u32> dist_atten_bias; - BitField<0, 20, u32> dist_atten_scale; - - INSERT_PADDING_WORDS(0x4); - }; - static_assert(sizeof(LightSrc) == 0x10 * sizeof(u32), - "LightSrc structure must be 0x10 words"); - - LightSrc light[8]; - LightColor global_ambient; // Emission + (material.ambient * lighting.ambient) - INSERT_PADDING_WORDS(0x1); - BitField<0, 3, u32> num_lights; // Number of enabled lights - 1 - - union { - BitField<2, 2, LightingFresnelSelector> fresnel_selector; - BitField<4, 4, LightingConfig> config; - BitField<22, 2, u32> bump_selector; // 0: Texture 0, 1: Texture 1, 2: Texture 2 - BitField<27, 1, u32> clamp_highlights; - BitField<28, 2, LightingBumpMode> bump_mode; - BitField<30, 1, u32> disable_bump_renorm; - } config0; - - union { - BitField<16, 1, u32> disable_lut_d0; - BitField<17, 1, u32> disable_lut_d1; - BitField<19, 1, u32> disable_lut_fr; - BitField<20, 1, u32> disable_lut_rr; - BitField<21, 1, u32> disable_lut_rg; - BitField<22, 1, u32> disable_lut_rb; - - // Each bit specifies whether distance attenuation should be applied for the - // corresponding light - - BitField<24, 1, u32> disable_dist_atten_light_0; - BitField<25, 1, u32> disable_dist_atten_light_1; - BitField<26, 1, u32> disable_dist_atten_light_2; - BitField<27, 1, u32> disable_dist_atten_light_3; - BitField<28, 1, u32> disable_dist_atten_light_4; - BitField<29, 1, u32> disable_dist_atten_light_5; - BitField<30, 1, u32> disable_dist_atten_light_6; - BitField<31, 1, u32> disable_dist_atten_light_7; - } config1; - - bool IsDistAttenDisabled(unsigned index) const { - const unsigned disable[] = { - config1.disable_dist_atten_light_0, config1.disable_dist_atten_light_1, - config1.disable_dist_atten_light_2, config1.disable_dist_atten_light_3, - config1.disable_dist_atten_light_4, config1.disable_dist_atten_light_5, - config1.disable_dist_atten_light_6, config1.disable_dist_atten_light_7}; - return disable[index] != 0; - } - - union { - BitField<0, 8, u32> index; ///< Index at which to set data in the LUT - BitField<8, 5, u32> type; ///< Type of LUT for which to set data - } lut_config; - - BitField<0, 1, u32> disable; - INSERT_PADDING_WORDS(0x1); - - // When data is written to any of these registers, it gets written to the lookup table of - // the selected type at the selected index, specified above in the `lut_config` register. - // With each write, `lut_config.index` is incremented. It does not matter which of these - // registers is written to, the behavior will be the same. - u32 lut_data[8]; - - // These are used to specify if absolute (abs) value should be used for each LUT index. When - // abs mode is disabled, LUT indexes are in the range of (-1.0, 1.0). Otherwise, they are in - // the range of (0.0, 1.0). - union { - BitField<1, 1, u32> disable_d0; - BitField<5, 1, u32> disable_d1; - BitField<9, 1, u32> disable_sp; - BitField<13, 1, u32> disable_fr; - BitField<17, 1, u32> disable_rb; - BitField<21, 1, u32> disable_rg; - BitField<25, 1, u32> disable_rr; - } abs_lut_input; - - union { - BitField<0, 3, LightingLutInput> d0; - BitField<4, 3, LightingLutInput> d1; - BitField<8, 3, LightingLutInput> sp; - BitField<12, 3, LightingLutInput> fr; - BitField<16, 3, LightingLutInput> rb; - BitField<20, 3, LightingLutInput> rg; - BitField<24, 3, LightingLutInput> rr; - } lut_input; - - union { - BitField<0, 3, LightingScale> d0; - BitField<4, 3, LightingScale> d1; - BitField<8, 3, LightingScale> sp; - BitField<12, 3, LightingScale> fr; - BitField<16, 3, LightingScale> rb; - BitField<20, 3, LightingScale> rg; - BitField<24, 3, LightingScale> rr; - - static float GetScale(LightingScale scale) { - switch (scale) { - case LightingScale::Scale1: - return 1.0f; - case LightingScale::Scale2: - return 2.0f; - case LightingScale::Scale4: - return 4.0f; - case LightingScale::Scale8: - return 8.0f; - case LightingScale::Scale1_4: - return 0.25f; - case LightingScale::Scale1_2: - return 0.5f; - } - return 0.0f; - } - } lut_scale; - - INSERT_PADDING_WORDS(0x6); - - union { - // There are 8 light enable "slots", corresponding to the total number of lights - // supported by Pica. For N enabled lights (specified by register 0x1c2, or 'src_num' - // above), the first N slots below will be set to integers within the range of 0-7, - // corresponding to the actual light that is enabled for each slot. - - BitField<0, 3, u32> slot_0; - BitField<4, 3, u32> slot_1; - BitField<8, 3, u32> slot_2; - BitField<12, 3, u32> slot_3; - BitField<16, 3, u32> slot_4; - BitField<20, 3, u32> slot_5; - BitField<24, 3, u32> slot_6; - BitField<28, 3, u32> slot_7; - - unsigned GetNum(unsigned index) const { - const unsigned enable_slots[] = {slot_0, slot_1, slot_2, slot_3, - slot_4, slot_5, slot_6, slot_7}; - return enable_slots[index]; - } - } light_enable; - } lighting; - - INSERT_PADDING_WORDS(0x26); - - enum class VertexAttributeFormat : u64 { - BYTE = 0, - UBYTE = 1, - SHORT = 2, - FLOAT = 3, - }; - - struct { - BitField<0, 29, u32> base_address; - - u32 GetPhysicalBaseAddress() const { - return DecodeAddressRegister(base_address); - } - - // Descriptor for internal vertex attributes - union { - BitField<0, 2, VertexAttributeFormat> format0; // size of one element - BitField<2, 2, u64> size0; // number of elements minus 1 - BitField<4, 2, VertexAttributeFormat> format1; - BitField<6, 2, u64> size1; - BitField<8, 2, VertexAttributeFormat> format2; - BitField<10, 2, u64> size2; - BitField<12, 2, VertexAttributeFormat> format3; - BitField<14, 2, u64> size3; - BitField<16, 2, VertexAttributeFormat> format4; - BitField<18, 2, u64> size4; - BitField<20, 2, VertexAttributeFormat> format5; - BitField<22, 2, u64> size5; - BitField<24, 2, VertexAttributeFormat> format6; - BitField<26, 2, u64> size6; - BitField<28, 2, VertexAttributeFormat> format7; - BitField<30, 2, u64> size7; - BitField<32, 2, VertexAttributeFormat> format8; - BitField<34, 2, u64> size8; - BitField<36, 2, VertexAttributeFormat> format9; - BitField<38, 2, u64> size9; - BitField<40, 2, VertexAttributeFormat> format10; - BitField<42, 2, u64> size10; - BitField<44, 2, VertexAttributeFormat> format11; - BitField<46, 2, u64> size11; - - BitField<48, 12, u64> attribute_mask; - - // number of total attributes minus 1 - BitField<60, 4, u64> num_extra_attributes; - }; - - inline VertexAttributeFormat GetFormat(int n) const { - VertexAttributeFormat formats[] = {format0, format1, format2, format3, - format4, format5, format6, format7, - format8, format9, format10, format11}; - return formats[n]; - } - - inline int GetNumElements(int n) const { - u64 sizes[] = {size0, size1, size2, size3, size4, size5, - size6, size7, size8, size9, size10, size11}; - return (int)sizes[n] + 1; - } - - inline int GetElementSizeInBytes(int n) const { - return (GetFormat(n) == VertexAttributeFormat::FLOAT) - ? 4 - : (GetFormat(n) == VertexAttributeFormat::SHORT) ? 2 : 1; - } - - inline int GetStride(int n) const { - return GetNumElements(n) * GetElementSizeInBytes(n); - } - - inline bool IsDefaultAttribute(int id) const { - return (id >= 12) || (attribute_mask & (1ULL << id)) != 0; - } - - inline int GetNumTotalAttributes() const { - return (int)num_extra_attributes + 1; - } - - // Attribute loaders map the source vertex data to input attributes - // This e.g. allows to load different attributes from different memory locations - struct { - // Source attribute data offset from the base address - u32 data_offset; - - union { - BitField<0, 4, u64> comp0; - BitField<4, 4, u64> comp1; - BitField<8, 4, u64> comp2; - BitField<12, 4, u64> comp3; - BitField<16, 4, u64> comp4; - BitField<20, 4, u64> comp5; - BitField<24, 4, u64> comp6; - BitField<28, 4, u64> comp7; - BitField<32, 4, u64> comp8; - BitField<36, 4, u64> comp9; - BitField<40, 4, u64> comp10; - BitField<44, 4, u64> comp11; - - // bytes for a single vertex in this loader - BitField<48, 8, u64> byte_count; - - BitField<60, 4, u64> component_count; - }; - - inline int GetComponent(int n) const { - u64 components[] = {comp0, comp1, comp2, comp3, comp4, comp5, - comp6, comp7, comp8, comp9, comp10, comp11}; - return (int)components[n]; - } - } attribute_loaders[12]; - } vertex_attributes; - - struct { - enum IndexFormat : u32 { - BYTE = 0, - SHORT = 1, - }; - - union { - BitField<0, 31, u32> offset; // relative to base attribute address - BitField<31, 1, IndexFormat> format; - }; - } index_array; - - // Number of vertices to render - u32 num_vertices; - - INSERT_PADDING_WORDS(0x1); - - // The index of the first vertex to render - u32 vertex_offset; - - INSERT_PADDING_WORDS(0x3); - - // These two trigger rendering of triangles - u32 trigger_draw; - u32 trigger_draw_indexed; - - INSERT_PADDING_WORDS(0x2); - - // These registers are used to setup the default "fall-back" vertex shader attributes - struct { - // Index of the current default attribute - u32 index; - - // Writing to these registers sets the "current" default attribute. - u32 set_value[3]; - } vs_default_attributes_setup; - - INSERT_PADDING_WORDS(0x2); - - struct { - // There are two channels that can be used to configure the next command buffer, which - // can be then executed by writing to the "trigger" registers. There are two reasons why a - // game might use this feature: - // 1) With this, an arbitrary number of additional command buffers may be executed in - // sequence without requiring any intervention of the CPU after the initial one is - // kicked off. - // 2) Games can configure these registers to provide a command list subroutine mechanism. - - BitField<0, 20, u32> size[2]; ///< Size (in bytes / 8) of each channel's command buffer - BitField<0, 28, u32> addr[2]; ///< Physical address / 8 of each channel's command buffer - u32 trigger[2]; ///< Triggers execution of the channel's command buffer when written to - - unsigned GetSize(unsigned index) const { - ASSERT(index < 2); - return 8 * size[index]; - } - - PAddr GetPhysicalAddress(unsigned index) const { - ASSERT(index < 2); - return (PAddr)(8 * addr[index]); - } - } command_buffer; - - INSERT_PADDING_WORDS(0x07); - - enum class GPUMode : u32 { - Drawing = 0, - Configuring = 1, - }; - - GPUMode gpu_mode; - - INSERT_PADDING_WORDS(0x18); - - enum class TriangleTopology : u32 { - List = 0, - Strip = 1, - Fan = 2, - Shader = 3, // Programmable setup unit implemented in a geometry shader - }; - - BitField<8, 2, TriangleTopology> triangle_topology; - - u32 restart_primitive; - - INSERT_PADDING_WORDS(0x20); - - struct ShaderConfig { - BitField<0, 16, u32> bool_uniforms; - - union { - BitField<0, 8, u32> x; - BitField<8, 8, u32> y; - BitField<16, 8, u32> z; - BitField<24, 8, u32> w; - } int_uniforms[4]; - - INSERT_PADDING_WORDS(0x4); - - union { - // Number of input attributes to shader unit - 1 - BitField<0, 4, u32> num_input_attributes; - }; - - // Offset to shader program entry point (in words) - BitField<0, 16, u32> main_offset; - - union { - BitField<0, 4, u64> attribute0_register; - BitField<4, 4, u64> attribute1_register; - BitField<8, 4, u64> attribute2_register; - BitField<12, 4, u64> attribute3_register; - BitField<16, 4, u64> attribute4_register; - BitField<20, 4, u64> attribute5_register; - BitField<24, 4, u64> attribute6_register; - BitField<28, 4, u64> attribute7_register; - BitField<32, 4, u64> attribute8_register; - BitField<36, 4, u64> attribute9_register; - BitField<40, 4, u64> attribute10_register; - BitField<44, 4, u64> attribute11_register; - BitField<48, 4, u64> attribute12_register; - BitField<52, 4, u64> attribute13_register; - BitField<56, 4, u64> attribute14_register; - BitField<60, 4, u64> attribute15_register; - - int GetRegisterForAttribute(int attribute_index) const { - u64 fields[] = { - attribute0_register, attribute1_register, attribute2_register, - attribute3_register, attribute4_register, attribute5_register, - attribute6_register, attribute7_register, attribute8_register, - attribute9_register, attribute10_register, attribute11_register, - attribute12_register, attribute13_register, attribute14_register, - attribute15_register, - }; - return (int)fields[attribute_index]; - } - } input_register_map; - - BitField<0, 16, u32> output_mask; - - // 0x28E, CODETRANSFER_END - INSERT_PADDING_WORDS(0x2); - - struct { - enum Format : u32 { - FLOAT24 = 0, - FLOAT32 = 1, - }; - - bool IsFloat32() const { - return format == FLOAT32; - } - - union { - // Index of the next uniform to write to - // TODO: ctrulib uses 8 bits for this, however that seems to yield lots of invalid - // indices - // TODO: Maybe the uppermost index is for the geometry shader? Investigate! - BitField<0, 7, u32> index; - - BitField<31, 1, Format> format; - }; - - // Writing to these registers sets the current uniform. - u32 set_value[8]; - - } uniform_setup; - - INSERT_PADDING_WORDS(0x2); - - struct { - // Offset of the next instruction to write code to. - // Incremented with each instruction write. - u32 offset; - - // Writing to these registers sets the "current" word in the shader program. - u32 set_word[8]; - } program; - - INSERT_PADDING_WORDS(0x1); - - // This register group is used to load an internal table of swizzling patterns, - // which are indexed by each shader instruction to specify vector component swizzling. - struct { - // Offset of the next swizzle pattern to write code to. - // Incremented with each instruction write. - u32 offset; - - // Writing to these registers sets the current swizzle pattern in the table. - u32 set_word[8]; - } swizzle_patterns; - - INSERT_PADDING_WORDS(0x2); - }; - - ShaderConfig gs; - ShaderConfig vs; - - INSERT_PADDING_WORDS(0x20); - - // Map register indices to names readable by humans - // Used for debugging purposes, so performance is not an issue here - static std::string GetCommandName(int index); - - static constexpr size_t NumIds() { - return sizeof(Regs) / sizeof(u32); - } - - const u32& operator[](int index) const { - const u32* content = reinterpret_cast<const u32*>(this); - return content[index]; - } - - u32& operator[](int index) { - u32* content = reinterpret_cast<u32*>(this); - return content[index]; - } - -private: - /* - * Most physical addresses which Pica registers refer to are 8-byte aligned. - * This function should be used to get the address from a raw register value. - */ - static inline u32 DecodeAddressRegister(u32 register_value) { - return register_value * 8; - } -}; - -// TODO: MSVC does not support using offsetof() on non-static data members even though this -// is technically allowed since C++11. This macro should be enabled once MSVC adds -// support for that. -#ifndef _MSC_VER -#define ASSERT_REG_POSITION(field_name, position) \ - static_assert(offsetof(Regs, field_name) == position * 4, \ - "Field " #field_name " has invalid position") - -ASSERT_REG_POSITION(trigger_irq, 0x10); -ASSERT_REG_POSITION(cull_mode, 0x40); -ASSERT_REG_POSITION(viewport_size_x, 0x41); -ASSERT_REG_POSITION(viewport_size_y, 0x43); -ASSERT_REG_POSITION(viewport_depth_range, 0x4d); -ASSERT_REG_POSITION(viewport_depth_near_plane, 0x4e); -ASSERT_REG_POSITION(vs_output_attributes[0], 0x50); -ASSERT_REG_POSITION(vs_output_attributes[1], 0x51); -ASSERT_REG_POSITION(scissor_test, 0x65); -ASSERT_REG_POSITION(viewport_corner, 0x68); -ASSERT_REG_POSITION(depthmap_enable, 0x6D); -ASSERT_REG_POSITION(texture0_enable, 0x80); -ASSERT_REG_POSITION(texture0, 0x81); -ASSERT_REG_POSITION(texture0_format, 0x8e); -ASSERT_REG_POSITION(fragment_lighting_enable, 0x8f); -ASSERT_REG_POSITION(texture1, 0x91); -ASSERT_REG_POSITION(texture1_format, 0x96); -ASSERT_REG_POSITION(texture2, 0x99); -ASSERT_REG_POSITION(texture2_format, 0x9e); -ASSERT_REG_POSITION(tev_stage0, 0xc0); -ASSERT_REG_POSITION(tev_stage1, 0xc8); -ASSERT_REG_POSITION(tev_stage2, 0xd0); -ASSERT_REG_POSITION(tev_stage3, 0xd8); -ASSERT_REG_POSITION(tev_combiner_buffer_input, 0xe0); -ASSERT_REG_POSITION(fog_mode, 0xe0); -ASSERT_REG_POSITION(fog_color, 0xe1); -ASSERT_REG_POSITION(fog_lut_offset, 0xe6); -ASSERT_REG_POSITION(fog_lut_data, 0xe8); -ASSERT_REG_POSITION(tev_stage4, 0xf0); -ASSERT_REG_POSITION(tev_stage5, 0xf8); -ASSERT_REG_POSITION(tev_combiner_buffer_color, 0xfd); -ASSERT_REG_POSITION(output_merger, 0x100); -ASSERT_REG_POSITION(framebuffer, 0x110); -ASSERT_REG_POSITION(lighting, 0x140); -ASSERT_REG_POSITION(vertex_attributes, 0x200); -ASSERT_REG_POSITION(index_array, 0x227); -ASSERT_REG_POSITION(num_vertices, 0x228); -ASSERT_REG_POSITION(vertex_offset, 0x22a); -ASSERT_REG_POSITION(trigger_draw, 0x22e); -ASSERT_REG_POSITION(trigger_draw_indexed, 0x22f); -ASSERT_REG_POSITION(vs_default_attributes_setup, 0x232); -ASSERT_REG_POSITION(command_buffer, 0x238); -ASSERT_REG_POSITION(gpu_mode, 0x245); -ASSERT_REG_POSITION(triangle_topology, 0x25e); -ASSERT_REG_POSITION(restart_primitive, 0x25f); -ASSERT_REG_POSITION(gs, 0x280); -ASSERT_REG_POSITION(vs, 0x2b0); - -#undef ASSERT_REG_POSITION -#endif // !defined(_MSC_VER) - -static_assert(sizeof(Regs::ShaderConfig) == 0x30 * sizeof(u32), - "ShaderConfig structure has incorrect size"); - -// The total number of registers is chosen arbitrarily, but let's make sure it's not some odd value -// anyway. -static_assert(sizeof(Regs) <= 0x300 * sizeof(u32), - "Register set structure larger than it should be"); -static_assert(sizeof(Regs) >= 0x300 * sizeof(u32), - "Register set structure smaller than it should be"); - /// Initialize Pica state void Init(); diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h index e4f2e6d5d..af7536d11 100644 --- a/src/video_core/pica_state.h +++ b/src/video_core/pica_state.h @@ -7,8 +7,8 @@ #include <array> #include "common/bit_field.h" #include "common/common_types.h" -#include "video_core/pica.h" #include "video_core/primitive_assembly.h" +#include "video_core/regs.h" #include "video_core/shader/shader.h" namespace Pica { @@ -23,7 +23,7 @@ struct State { Shader::ShaderSetup vs; Shader::ShaderSetup gs; - std::array<Math::Vec4<float24>, 16> vs_default_attributes; + Shader::AttributeBuffer input_default_attributes; struct { union LutEntry { @@ -66,7 +66,7 @@ struct State { /// Struct used to describe immediate mode rendering state struct ImmediateModeState { // Used to buffer partial vertices for immediate-mode rendering. - Shader::InputVertex input_vertex; + Shader::AttributeBuffer input_vertex; // Index of the next attribute to be loaded into `input_vertex`. u32 current_attribute = 0; } immediate; diff --git a/src/video_core/primitive_assembly.cpp b/src/video_core/primitive_assembly.cpp index be7377290..acd2ac5e2 100644 --- a/src/video_core/primitive_assembly.cpp +++ b/src/video_core/primitive_assembly.cpp @@ -3,23 +3,23 @@ // Refer to the license.txt file included. #include "common/logging/log.h" -#include "video_core/pica.h" #include "video_core/primitive_assembly.h" +#include "video_core/regs_pipeline.h" #include "video_core/shader/shader.h" namespace Pica { template <typename VertexType> -PrimitiveAssembler<VertexType>::PrimitiveAssembler(Regs::TriangleTopology topology) +PrimitiveAssembler<VertexType>::PrimitiveAssembler(PipelineRegs::TriangleTopology topology) : topology(topology), buffer_index(0) {} template <typename VertexType> -void PrimitiveAssembler<VertexType>::SubmitVertex(VertexType& vtx, +void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx, TriangleHandler triangle_handler) { switch (topology) { // TODO: Figure out what's different with TriangleTopology::Shader. - case Regs::TriangleTopology::List: - case Regs::TriangleTopology::Shader: + case PipelineRegs::TriangleTopology::List: + case PipelineRegs::TriangleTopology::Shader: if (buffer_index < 2) { buffer[buffer_index++] = vtx; } else { @@ -29,8 +29,8 @@ void PrimitiveAssembler<VertexType>::SubmitVertex(VertexType& vtx, } break; - case Regs::TriangleTopology::Strip: - case Regs::TriangleTopology::Fan: + case PipelineRegs::TriangleTopology::Strip: + case PipelineRegs::TriangleTopology::Fan: if (strip_ready) triangle_handler(buffer[0], buffer[1], vtx); @@ -38,9 +38,9 @@ void PrimitiveAssembler<VertexType>::SubmitVertex(VertexType& vtx, strip_ready |= (buffer_index == 1); - if (topology == Regs::TriangleTopology::Strip) + if (topology == PipelineRegs::TriangleTopology::Strip) buffer_index = !buffer_index; - else if (topology == Regs::TriangleTopology::Fan) + else if (topology == PipelineRegs::TriangleTopology::Fan) buffer_index = 1; break; @@ -57,7 +57,7 @@ void PrimitiveAssembler<VertexType>::Reset() { } template <typename VertexType> -void PrimitiveAssembler<VertexType>::Reconfigure(Regs::TriangleTopology topology) { +void PrimitiveAssembler<VertexType>::Reconfigure(PipelineRegs::TriangleTopology topology) { Reset(); this->topology = topology; } diff --git a/src/video_core/primitive_assembly.h b/src/video_core/primitive_assembly.h index 0384d5984..e8eccdf27 100644 --- a/src/video_core/primitive_assembly.h +++ b/src/video_core/primitive_assembly.h @@ -5,7 +5,7 @@ #pragma once #include <functional> -#include "video_core/pica.h" +#include "video_core/regs_pipeline.h" namespace Pica { @@ -15,9 +15,11 @@ namespace Pica { */ template <typename VertexType> struct PrimitiveAssembler { - using TriangleHandler = std::function<void(VertexType& v0, VertexType& v1, VertexType& v2)>; + using TriangleHandler = + std::function<void(const VertexType& v0, const VertexType& v1, const VertexType& v2)>; - PrimitiveAssembler(Regs::TriangleTopology topology = Regs::TriangleTopology::List); + PrimitiveAssembler( + PipelineRegs::TriangleTopology topology = PipelineRegs::TriangleTopology::List); /* * Queues a vertex, builds primitives from the vertex queue according to the given @@ -25,7 +27,7 @@ struct PrimitiveAssembler { * NOTE: We could specify the triangle handler in the constructor, but this way we can * keep event and handler code next to each other. */ - void SubmitVertex(VertexType& vtx, TriangleHandler triangle_handler); + void SubmitVertex(const VertexType& vtx, TriangleHandler triangle_handler); /** * Resets the internal state of the PrimitiveAssembler. @@ -35,10 +37,10 @@ struct PrimitiveAssembler { /** * Reconfigures the PrimitiveAssembler to use a different triangle topology. */ - void Reconfigure(Regs::TriangleTopology topology); + void Reconfigure(PipelineRegs::TriangleTopology topology); private: - Regs::TriangleTopology topology; + PipelineRegs::TriangleTopology topology; int buffer_index; VertexType buffer[2]; diff --git a/src/video_core/rasterizer.h b/src/video_core/rasterizer.h deleted file mode 100644 index 6cbda3067..000000000 --- a/src/video_core/rasterizer.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -namespace Pica { - -namespace Shader { -struct OutputVertex; -} - -namespace Rasterizer { - -void ProcessTriangle(const Shader::OutputVertex& v0, const Shader::OutputVertex& v1, - const Shader::OutputVertex& v2); - -} // namespace Rasterizer - -} // namespace Pica diff --git a/src/video_core/regs.cpp b/src/video_core/regs.cpp new file mode 100644 index 000000000..2699e710a --- /dev/null +++ b/src/video_core/regs.cpp @@ -0,0 +1,488 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <iterator> +#include <utility> + +#include "common/common_types.h" +#include "video_core/regs.h" + +namespace Pica { + +static const std::pair<u16, const char*> register_names[] = { + {0x010, "GPUREG_FINALIZE"}, + + {0x040, "GPUREG_FACECULLING_CONFIG"}, + {0x041, "GPUREG_VIEWPORT_WIDTH"}, + {0x042, "GPUREG_VIEWPORT_INVW"}, + {0x043, "GPUREG_VIEWPORT_HEIGHT"}, + {0x044, "GPUREG_VIEWPORT_INVH"}, + + {0x047, "GPUREG_FRAGOP_CLIP"}, + {0x048, "GPUREG_FRAGOP_CLIP_DATA0"}, + {0x049, "GPUREG_FRAGOP_CLIP_DATA1"}, + {0x04A, "GPUREG_FRAGOP_CLIP_DATA2"}, + {0x04B, "GPUREG_FRAGOP_CLIP_DATA3"}, + + {0x04D, "GPUREG_DEPTHMAP_SCALE"}, + {0x04E, "GPUREG_DEPTHMAP_OFFSET"}, + {0x04F, "GPUREG_SH_OUTMAP_TOTAL"}, + {0x050, "GPUREG_SH_OUTMAP_O0"}, + {0x051, "GPUREG_SH_OUTMAP_O1"}, + {0x052, "GPUREG_SH_OUTMAP_O2"}, + {0x053, "GPUREG_SH_OUTMAP_O3"}, + {0x054, "GPUREG_SH_OUTMAP_O4"}, + {0x055, "GPUREG_SH_OUTMAP_O5"}, + {0x056, "GPUREG_SH_OUTMAP_O6"}, + + {0x061, "GPUREG_EARLYDEPTH_FUNC"}, + {0x062, "GPUREG_EARLYDEPTH_TEST1"}, + {0x063, "GPUREG_EARLYDEPTH_CLEAR"}, + {0x064, "GPUREG_SH_OUTATTR_MODE"}, + {0x065, "GPUREG_SCISSORTEST_MODE"}, + {0x066, "GPUREG_SCISSORTEST_POS"}, + {0x067, "GPUREG_SCISSORTEST_DIM"}, + {0x068, "GPUREG_VIEWPORT_XY"}, + + {0x06A, "GPUREG_EARLYDEPTH_DATA"}, + + {0x06D, "GPUREG_DEPTHMAP_ENABLE"}, + {0x06E, "GPUREG_RENDERBUF_DIM"}, + {0x06F, "GPUREG_SH_OUTATTR_CLOCK"}, + + {0x080, "GPUREG_TEXUNIT_CONFIG"}, + {0x081, "GPUREG_TEXUNIT0_BORDER_COLOR"}, + {0x082, "GPUREG_TEXUNIT0_DIM"}, + {0x083, "GPUREG_TEXUNIT0_PARAM"}, + {0x084, "GPUREG_TEXUNIT0_LOD"}, + {0x085, "GPUREG_TEXUNIT0_ADDR1"}, + {0x086, "GPUREG_TEXUNIT0_ADDR2"}, + {0x087, "GPUREG_TEXUNIT0_ADDR3"}, + {0x088, "GPUREG_TEXUNIT0_ADDR4"}, + {0x089, "GPUREG_TEXUNIT0_ADDR5"}, + {0x08A, "GPUREG_TEXUNIT0_ADDR6"}, + {0x08B, "GPUREG_TEXUNIT0_SHADOW"}, + + {0x08E, "GPUREG_TEXUNIT0_TYPE"}, + {0x08F, "GPUREG_LIGHTING_ENABLE0"}, + + {0x091, "GPUREG_TEXUNIT1_BORDER_COLOR"}, + {0x092, "GPUREG_TEXUNIT1_DIM"}, + {0x093, "GPUREG_TEXUNIT1_PARAM"}, + {0x094, "GPUREG_TEXUNIT1_LOD"}, + {0x095, "GPUREG_TEXUNIT1_ADDR"}, + {0x096, "GPUREG_TEXUNIT1_TYPE"}, + + {0x099, "GPUREG_TEXUNIT2_BORDER_COLOR"}, + {0x09A, "GPUREG_TEXUNIT2_DIM"}, + {0x09B, "GPUREG_TEXUNIT2_PARAM"}, + {0x09C, "GPUREG_TEXUNIT2_LOD"}, + {0x09D, "GPUREG_TEXUNIT2_ADDR"}, + {0x09E, "GPUREG_TEXUNIT2_TYPE"}, + + {0x0A8, "GPUREG_TEXUNIT3_PROCTEX0"}, + {0x0A9, "GPUREG_TEXUNIT3_PROCTEX1"}, + {0x0AA, "GPUREG_TEXUNIT3_PROCTEX2"}, + {0x0AB, "GPUREG_TEXUNIT3_PROCTEX3"}, + {0x0AC, "GPUREG_TEXUNIT3_PROCTEX4"}, + {0x0AD, "GPUREG_TEXUNIT3_PROCTEX5"}, + + {0x0AF, "GPUREG_PROCTEX_LUT"}, + {0x0B0, "GPUREG_PROCTEX_LUT_DATA0"}, + {0x0B1, "GPUREG_PROCTEX_LUT_DATA1"}, + {0x0B2, "GPUREG_PROCTEX_LUT_DATA2"}, + {0x0B3, "GPUREG_PROCTEX_LUT_DATA3"}, + {0x0B4, "GPUREG_PROCTEX_LUT_DATA4"}, + {0x0B5, "GPUREG_PROCTEX_LUT_DATA5"}, + {0x0B6, "GPUREG_PROCTEX_LUT_DATA6"}, + {0x0B7, "GPUREG_PROCTEX_LUT_DATA7"}, + + {0x0C0, "GPUREG_TEXENV0_SOURCE"}, + {0x0C1, "GPUREG_TEXENV0_OPERAND"}, + {0x0C2, "GPUREG_TEXENV0_COMBINER"}, + {0x0C3, "GPUREG_TEXENV0_COLOR"}, + {0x0C4, "GPUREG_TEXENV0_SCALE"}, + + {0x0C8, "GPUREG_TEXENV1_SOURCE"}, + {0x0C9, "GPUREG_TEXENV1_OPERAND"}, + {0x0CA, "GPUREG_TEXENV1_COMBINER"}, + {0x0CB, "GPUREG_TEXENV1_COLOR"}, + {0x0CC, "GPUREG_TEXENV1_SCALE"}, + + {0x0D0, "GPUREG_TEXENV2_SOURCE"}, + {0x0D1, "GPUREG_TEXENV2_OPERAND"}, + {0x0D2, "GPUREG_TEXENV2_COMBINER"}, + {0x0D3, "GPUREG_TEXENV2_COLOR"}, + {0x0D4, "GPUREG_TEXENV2_SCALE"}, + + {0x0D8, "GPUREG_TEXENV3_SOURCE"}, + {0x0D9, "GPUREG_TEXENV3_OPERAND"}, + {0x0DA, "GPUREG_TEXENV3_COMBINER"}, + {0x0DB, "GPUREG_TEXENV3_COLOR"}, + {0x0DC, "GPUREG_TEXENV3_SCALE"}, + + {0x0E0, "GPUREG_TEXENV_UPDATE_BUFFER"}, + {0x0E1, "GPUREG_FOG_COLOR"}, + + {0x0E4, "GPUREG_GAS_ATTENUATION"}, + {0x0E5, "GPUREG_GAS_ACCMAX"}, + {0x0E6, "GPUREG_FOG_LUT_INDEX"}, + + {0x0E8, "GPUREG_FOG_LUT_DATA0"}, + {0x0E9, "GPUREG_FOG_LUT_DATA1"}, + {0x0EA, "GPUREG_FOG_LUT_DATA2"}, + {0x0EB, "GPUREG_FOG_LUT_DATA3"}, + {0x0EC, "GPUREG_FOG_LUT_DATA4"}, + {0x0ED, "GPUREG_FOG_LUT_DATA5"}, + {0x0EE, "GPUREG_FOG_LUT_DATA6"}, + {0x0EF, "GPUREG_FOG_LUT_DATA7"}, + {0x0F0, "GPUREG_TEXENV4_SOURCE"}, + {0x0F1, "GPUREG_TEXENV4_OPERAND"}, + {0x0F2, "GPUREG_TEXENV4_COMBINER"}, + {0x0F3, "GPUREG_TEXENV4_COLOR"}, + {0x0F4, "GPUREG_TEXENV4_SCALE"}, + + {0x0F8, "GPUREG_TEXENV5_SOURCE"}, + {0x0F9, "GPUREG_TEXENV5_OPERAND"}, + {0x0FA, "GPUREG_TEXENV5_COMBINER"}, + {0x0FB, "GPUREG_TEXENV5_COLOR"}, + {0x0FC, "GPUREG_TEXENV5_SCALE"}, + {0x0FD, "GPUREG_TEXENV_BUFFER_COLOR"}, + + {0x100, "GPUREG_COLOR_OPERATION"}, + {0x101, "GPUREG_BLEND_FUNC"}, + {0x102, "GPUREG_LOGIC_OP"}, + {0x103, "GPUREG_BLEND_COLOR"}, + {0x104, "GPUREG_FRAGOP_ALPHA_TEST"}, + {0x105, "GPUREG_STENCIL_TEST"}, + {0x106, "GPUREG_STENCIL_OP"}, + {0x107, "GPUREG_DEPTH_COLOR_MASK"}, + + {0x110, "GPUREG_FRAMEBUFFER_INVALIDATE"}, + {0x111, "GPUREG_FRAMEBUFFER_FLUSH"}, + {0x112, "GPUREG_COLORBUFFER_READ"}, + {0x113, "GPUREG_COLORBUFFER_WRITE"}, + {0x114, "GPUREG_DEPTHBUFFER_READ"}, + {0x115, "GPUREG_DEPTHBUFFER_WRITE"}, + {0x116, "GPUREG_DEPTHBUFFER_FORMAT"}, + {0x117, "GPUREG_COLORBUFFER_FORMAT"}, + {0x118, "GPUREG_EARLYDEPTH_TEST2"}, + + {0x11B, "GPUREG_FRAMEBUFFER_BLOCK32"}, + {0x11C, "GPUREG_DEPTHBUFFER_LOC"}, + {0x11D, "GPUREG_COLORBUFFER_LOC"}, + {0x11E, "GPUREG_FRAMEBUFFER_DIM"}, + + {0x120, "GPUREG_GAS_LIGHT_XY"}, + {0x121, "GPUREG_GAS_LIGHT_Z"}, + {0x122, "GPUREG_GAS_LIGHT_Z_COLOR"}, + {0x123, "GPUREG_GAS_LUT_INDEX"}, + {0x124, "GPUREG_GAS_LUT_DATA"}, + + {0x126, "GPUREG_GAS_DELTAZ_DEPTH"}, + + {0x130, "GPUREG_FRAGOP_SHADOW"}, + + {0x140, "GPUREG_LIGHT0_SPECULAR0"}, + {0x141, "GPUREG_LIGHT0_SPECULAR1"}, + {0x142, "GPUREG_LIGHT0_DIFFUSE"}, + {0x143, "GPUREG_LIGHT0_AMBIENT"}, + {0x144, "GPUREG_LIGHT0_XY"}, + {0x145, "GPUREG_LIGHT0_Z"}, + {0x146, "GPUREG_LIGHT0_SPOTDIR_XY"}, + {0x147, "GPUREG_LIGHT0_SPOTDIR_Z"}, + + {0x149, "GPUREG_LIGHT0_CONFIG"}, + {0x14A, "GPUREG_LIGHT0_ATTENUATION_BIAS"}, + {0x14B, "GPUREG_LIGHT0_ATTENUATION_SCALE"}, + + {0x150, "GPUREG_LIGHT1_SPECULAR0"}, + {0x151, "GPUREG_LIGHT1_SPECULAR1"}, + {0x152, "GPUREG_LIGHT1_DIFFUSE"}, + {0x153, "GPUREG_LIGHT1_AMBIENT"}, + {0x154, "GPUREG_LIGHT1_XY"}, + {0x155, "GPUREG_LIGHT1_Z"}, + {0x156, "GPUREG_LIGHT1_SPOTDIR_XY"}, + {0x157, "GPUREG_LIGHT1_SPOTDIR_Z"}, + + {0x159, "GPUREG_LIGHT1_CONFIG"}, + {0x15A, "GPUREG_LIGHT1_ATTENUATION_BIAS"}, + {0x15B, "GPUREG_LIGHT1_ATTENUATION_SCALE"}, + + {0x160, "GPUREG_LIGHT2_SPECULAR0"}, + {0x161, "GPUREG_LIGHT2_SPECULAR1"}, + {0x162, "GPUREG_LIGHT2_DIFFUSE"}, + {0x163, "GPUREG_LIGHT2_AMBIENT"}, + {0x164, "GPUREG_LIGHT2_XY"}, + {0x165, "GPUREG_LIGHT2_Z"}, + {0x166, "GPUREG_LIGHT2_SPOTDIR_XY"}, + {0x167, "GPUREG_LIGHT2_SPOTDIR_Z"}, + + {0x169, "GPUREG_LIGHT2_CONFIG"}, + {0x16A, "GPUREG_LIGHT2_ATTENUATION_BIAS"}, + {0x16B, "GPUREG_LIGHT2_ATTENUATION_SCALE"}, + + {0x170, "GPUREG_LIGHT3_SPECULAR0"}, + {0x171, "GPUREG_LIGHT3_SPECULAR1"}, + {0x172, "GPUREG_LIGHT3_DIFFUSE"}, + {0x173, "GPUREG_LIGHT3_AMBIENT"}, + {0x174, "GPUREG_LIGHT3_XY"}, + {0x175, "GPUREG_LIGHT3_Z"}, + {0x176, "GPUREG_LIGHT3_SPOTDIR_XY"}, + {0x177, "GPUREG_LIGHT3_SPOTDIR_Z"}, + + {0x179, "GPUREG_LIGHT3_CONFIG"}, + {0x17A, "GPUREG_LIGHT3_ATTENUATION_BIAS"}, + {0x17B, "GPUREG_LIGHT3_ATTENUATION_SCALE"}, + + {0x180, "GPUREG_LIGHT4_SPECULAR0"}, + {0x181, "GPUREG_LIGHT4_SPECULAR1"}, + {0x182, "GPUREG_LIGHT4_DIFFUSE"}, + {0x183, "GPUREG_LIGHT4_AMBIENT"}, + {0x184, "GPUREG_LIGHT4_XY"}, + {0x185, "GPUREG_LIGHT4_Z"}, + {0x186, "GPUREG_LIGHT4_SPOTDIR_XY"}, + {0x187, "GPUREG_LIGHT4_SPOTDIR_Z"}, + + {0x189, "GPUREG_LIGHT4_CONFIG"}, + {0x18A, "GPUREG_LIGHT4_ATTENUATION_BIAS"}, + {0x18B, "GPUREG_LIGHT4_ATTENUATION_SCALE"}, + + {0x190, "GPUREG_LIGHT5_SPECULAR0"}, + {0x191, "GPUREG_LIGHT5_SPECULAR1"}, + {0x192, "GPUREG_LIGHT5_DIFFUSE"}, + {0x193, "GPUREG_LIGHT5_AMBIENT"}, + {0x194, "GPUREG_LIGHT5_XY"}, + {0x195, "GPUREG_LIGHT5_Z"}, + {0x196, "GPUREG_LIGHT5_SPOTDIR_XY"}, + {0x197, "GPUREG_LIGHT5_SPOTDIR_Z"}, + + {0x199, "GPUREG_LIGHT5_CONFIG"}, + {0x19A, "GPUREG_LIGHT5_ATTENUATION_BIAS"}, + {0x19B, "GPUREG_LIGHT5_ATTENUATION_SCALE"}, + + {0x1A0, "GPUREG_LIGHT6_SPECULAR0"}, + {0x1A1, "GPUREG_LIGHT6_SPECULAR1"}, + {0x1A2, "GPUREG_LIGHT6_DIFFUSE"}, + {0x1A3, "GPUREG_LIGHT6_AMBIENT"}, + {0x1A4, "GPUREG_LIGHT6_XY"}, + {0x1A5, "GPUREG_LIGHT6_Z"}, + {0x1A6, "GPUREG_LIGHT6_SPOTDIR_XY"}, + {0x1A7, "GPUREG_LIGHT6_SPOTDIR_Z"}, + + {0x1A9, "GPUREG_LIGHT6_CONFIG"}, + {0x1AA, "GPUREG_LIGHT6_ATTENUATION_BIAS"}, + {0x1AB, "GPUREG_LIGHT6_ATTENUATION_SCALE"}, + + {0x1B0, "GPUREG_LIGHT7_SPECULAR0"}, + {0x1B1, "GPUREG_LIGHT7_SPECULAR1"}, + {0x1B2, "GPUREG_LIGHT7_DIFFUSE"}, + {0x1B3, "GPUREG_LIGHT7_AMBIENT"}, + {0x1B4, "GPUREG_LIGHT7_XY"}, + {0x1B5, "GPUREG_LIGHT7_Z"}, + {0x1B6, "GPUREG_LIGHT7_SPOTDIR_XY"}, + {0x1B7, "GPUREG_LIGHT7_SPOTDIR_Z"}, + + {0x1B9, "GPUREG_LIGHT7_CONFIG"}, + {0x1BA, "GPUREG_LIGHT7_ATTENUATION_BIAS"}, + {0x1BB, "GPUREG_LIGHT7_ATTENUATION_SCALE"}, + + {0x1C0, "GPUREG_LIGHTING_AMBIENT"}, + + {0x1C2, "GPUREG_LIGHTING_NUM_LIGHTS"}, + {0x1C3, "GPUREG_LIGHTING_CONFIG0"}, + {0x1C4, "GPUREG_LIGHTING_CONFIG1"}, + {0x1C5, "GPUREG_LIGHTING_LUT_INDEX"}, + {0x1C6, "GPUREG_LIGHTING_ENABLE1"}, + + {0x1C8, "GPUREG_LIGHTING_LUT_DATA0"}, + {0x1C9, "GPUREG_LIGHTING_LUT_DATA1"}, + {0x1CA, "GPUREG_LIGHTING_LUT_DATA2"}, + {0x1CB, "GPUREG_LIGHTING_LUT_DATA3"}, + {0x1CC, "GPUREG_LIGHTING_LUT_DATA4"}, + {0x1CD, "GPUREG_LIGHTING_LUT_DATA5"}, + {0x1CE, "GPUREG_LIGHTING_LUT_DATA6"}, + {0x1CF, "GPUREG_LIGHTING_LUT_DATA7"}, + {0x1D0, "GPUREG_LIGHTING_LUTINPUT_ABS"}, + {0x1D1, "GPUREG_LIGHTING_LUTINPUT_SELECT"}, + {0x1D2, "GPUREG_LIGHTING_LUTINPUT_SCALE"}, + + {0x1D9, "GPUREG_LIGHTING_LIGHT_PERMUTATION"}, + + {0x200, "GPUREG_ATTRIBBUFFERS_LOC"}, + {0x201, "GPUREG_ATTRIBBUFFERS_FORMAT_LOW"}, + {0x202, "GPUREG_ATTRIBBUFFERS_FORMAT_HIGH"}, + {0x203, "GPUREG_ATTRIBBUFFER0_OFFSET"}, + {0x204, "GPUREG_ATTRIBBUFFER0_CONFIG1"}, + {0x205, "GPUREG_ATTRIBBUFFER0_CONFIG2"}, + {0x206, "GPUREG_ATTRIBBUFFER1_OFFSET"}, + {0x207, "GPUREG_ATTRIBBUFFER1_CONFIG1"}, + {0x208, "GPUREG_ATTRIBBUFFER1_CONFIG2"}, + {0x209, "GPUREG_ATTRIBBUFFER2_OFFSET"}, + {0x20A, "GPUREG_ATTRIBBUFFER2_CONFIG1"}, + {0x20B, "GPUREG_ATTRIBBUFFER2_CONFIG2"}, + {0x20C, "GPUREG_ATTRIBBUFFER3_OFFSET"}, + {0x20D, "GPUREG_ATTRIBBUFFER3_CONFIG1"}, + {0x20E, "GPUREG_ATTRIBBUFFER3_CONFIG2"}, + {0x20F, "GPUREG_ATTRIBBUFFER4_OFFSET"}, + {0x210, "GPUREG_ATTRIBBUFFER4_CONFIG1"}, + {0x211, "GPUREG_ATTRIBBUFFER4_CONFIG2"}, + {0x212, "GPUREG_ATTRIBBUFFER5_OFFSET"}, + {0x213, "GPUREG_ATTRIBBUFFER5_CONFIG1"}, + {0x214, "GPUREG_ATTRIBBUFFER5_CONFIG2"}, + {0x215, "GPUREG_ATTRIBBUFFER6_OFFSET"}, + {0x216, "GPUREG_ATTRIBBUFFER6_CONFIG1"}, + {0x217, "GPUREG_ATTRIBBUFFER6_CONFIG2"}, + {0x218, "GPUREG_ATTRIBBUFFER7_OFFSET"}, + {0x219, "GPUREG_ATTRIBBUFFER7_CONFIG1"}, + {0x21A, "GPUREG_ATTRIBBUFFER7_CONFIG2"}, + {0x21B, "GPUREG_ATTRIBBUFFER8_OFFSET"}, + {0x21C, "GPUREG_ATTRIBBUFFER8_CONFIG1"}, + {0x21D, "GPUREG_ATTRIBBUFFER8_CONFIG2"}, + {0x21E, "GPUREG_ATTRIBBUFFER9_OFFSET"}, + {0x21F, "GPUREG_ATTRIBBUFFER9_CONFIG1"}, + {0x220, "GPUREG_ATTRIBBUFFER9_CONFIG2"}, + {0x221, "GPUREG_ATTRIBBUFFER10_OFFSET"}, + {0x222, "GPUREG_ATTRIBBUFFER10_CONFIG1"}, + {0x223, "GPUREG_ATTRIBBUFFER10_CONFIG2"}, + {0x224, "GPUREG_ATTRIBBUFFER11_OFFSET"}, + {0x225, "GPUREG_ATTRIBBUFFER11_CONFIG1"}, + {0x226, "GPUREG_ATTRIBBUFFER11_CONFIG2"}, + {0x227, "GPUREG_INDEXBUFFER_CONFIG"}, + {0x228, "GPUREG_NUMVERTICES"}, + {0x229, "GPUREG_GEOSTAGE_CONFIG"}, + {0x22A, "GPUREG_VERTEX_OFFSET"}, + + {0x22D, "GPUREG_POST_VERTEX_CACHE_NUM"}, + {0x22E, "GPUREG_DRAWARRAYS"}, + {0x22F, "GPUREG_DRAWELEMENTS"}, + + {0x231, "GPUREG_VTX_FUNC"}, + {0x232, "GPUREG_FIXEDATTRIB_INDEX"}, + {0x233, "GPUREG_FIXEDATTRIB_DATA0"}, + {0x234, "GPUREG_FIXEDATTRIB_DATA1"}, + {0x235, "GPUREG_FIXEDATTRIB_DATA2"}, + + {0x238, "GPUREG_CMDBUF_SIZE0"}, + {0x239, "GPUREG_CMDBUF_SIZE1"}, + {0x23A, "GPUREG_CMDBUF_ADDR0"}, + {0x23B, "GPUREG_CMDBUF_ADDR1"}, + {0x23C, "GPUREG_CMDBUF_JUMP0"}, + {0x23D, "GPUREG_CMDBUF_JUMP1"}, + + {0x242, "GPUREG_VSH_NUM_ATTR"}, + + {0x244, "GPUREG_VSH_COM_MODE"}, + {0x245, "GPUREG_START_DRAW_FUNC0"}, + + {0x24A, "GPUREG_VSH_OUTMAP_TOTAL1"}, + + {0x251, "GPUREG_VSH_OUTMAP_TOTAL2"}, + {0x252, "GPUREG_GSH_MISC0"}, + {0x253, "GPUREG_GEOSTAGE_CONFIG2"}, + {0x254, "GPUREG_GSH_MISC1"}, + + {0x25E, "GPUREG_PRIMITIVE_CONFIG"}, + {0x25F, "GPUREG_RESTART_PRIMITIVE"}, + + {0x280, "GPUREG_GSH_BOOLUNIFORM"}, + {0x281, "GPUREG_GSH_INTUNIFORM_I0"}, + {0x282, "GPUREG_GSH_INTUNIFORM_I1"}, + {0x283, "GPUREG_GSH_INTUNIFORM_I2"}, + {0x284, "GPUREG_GSH_INTUNIFORM_I3"}, + + {0x289, "GPUREG_GSH_INPUTBUFFER_CONFIG"}, + {0x28A, "GPUREG_GSH_ENTRYPOINT"}, + {0x28B, "GPUREG_GSH_ATTRIBUTES_PERMUTATION_LOW"}, + {0x28C, "GPUREG_GSH_ATTRIBUTES_PERMUTATION_HIGH"}, + {0x28D, "GPUREG_GSH_OUTMAP_MASK"}, + + {0x28F, "GPUREG_GSH_CODETRANSFER_END"}, + {0x290, "GPUREG_GSH_FLOATUNIFORM_INDEX"}, + {0x291, "GPUREG_GSH_FLOATUNIFORM_DATA0"}, + {0x292, "GPUREG_GSH_FLOATUNIFORM_DATA1"}, + {0x293, "GPUREG_GSH_FLOATUNIFORM_DATA2"}, + {0x294, "GPUREG_GSH_FLOATUNIFORM_DATA3"}, + {0x295, "GPUREG_GSH_FLOATUNIFORM_DATA4"}, + {0x296, "GPUREG_GSH_FLOATUNIFORM_DATA5"}, + {0x297, "GPUREG_GSH_FLOATUNIFORM_DATA6"}, + {0x298, "GPUREG_GSH_FLOATUNIFORM_DATA7"}, + + {0x29B, "GPUREG_GSH_CODETRANSFER_INDEX"}, + {0x29C, "GPUREG_GSH_CODETRANSFER_DATA0"}, + {0x29D, "GPUREG_GSH_CODETRANSFER_DATA1"}, + {0x29E, "GPUREG_GSH_CODETRANSFER_DATA2"}, + {0x29F, "GPUREG_GSH_CODETRANSFER_DATA3"}, + {0x2A0, "GPUREG_GSH_CODETRANSFER_DATA4"}, + {0x2A1, "GPUREG_GSH_CODETRANSFER_DATA5"}, + {0x2A2, "GPUREG_GSH_CODETRANSFER_DATA6"}, + {0x2A3, "GPUREG_GSH_CODETRANSFER_DATA7"}, + + {0x2A5, "GPUREG_GSH_OPDESCS_INDEX"}, + {0x2A6, "GPUREG_GSH_OPDESCS_DATA0"}, + {0x2A7, "GPUREG_GSH_OPDESCS_DATA1"}, + {0x2A8, "GPUREG_GSH_OPDESCS_DATA2"}, + {0x2A9, "GPUREG_GSH_OPDESCS_DATA3"}, + {0x2AA, "GPUREG_GSH_OPDESCS_DATA4"}, + {0x2AB, "GPUREG_GSH_OPDESCS_DATA5"}, + {0x2AC, "GPUREG_GSH_OPDESCS_DATA6"}, + {0x2AD, "GPUREG_GSH_OPDESCS_DATA7"}, + + {0x2B0, "GPUREG_VSH_BOOLUNIFORM"}, + {0x2B1, "GPUREG_VSH_INTUNIFORM_I0"}, + {0x2B2, "GPUREG_VSH_INTUNIFORM_I1"}, + {0x2B3, "GPUREG_VSH_INTUNIFORM_I2"}, + {0x2B4, "GPUREG_VSH_INTUNIFORM_I3"}, + + {0x2B9, "GPUREG_VSH_INPUTBUFFER_CONFIG"}, + {0x2BA, "GPUREG_VSH_ENTRYPOINT"}, + {0x2BB, "GPUREG_VSH_ATTRIBUTES_PERMUTATION_LOW"}, + {0x2BC, "GPUREG_VSH_ATTRIBUTES_PERMUTATION_HIGH"}, + {0x2BD, "GPUREG_VSH_OUTMAP_MASK"}, + + {0x2BF, "GPUREG_VSH_CODETRANSFER_END"}, + {0x2C0, "GPUREG_VSH_FLOATUNIFORM_INDEX"}, + {0x2C1, "GPUREG_VSH_FLOATUNIFORM_DATA0"}, + {0x2C2, "GPUREG_VSH_FLOATUNIFORM_DATA1"}, + {0x2C3, "GPUREG_VSH_FLOATUNIFORM_DATA2"}, + {0x2C4, "GPUREG_VSH_FLOATUNIFORM_DATA3"}, + {0x2C5, "GPUREG_VSH_FLOATUNIFORM_DATA4"}, + {0x2C6, "GPUREG_VSH_FLOATUNIFORM_DATA5"}, + {0x2C7, "GPUREG_VSH_FLOATUNIFORM_DATA6"}, + {0x2C8, "GPUREG_VSH_FLOATUNIFORM_DATA7"}, + + {0x2CB, "GPUREG_VSH_CODETRANSFER_INDEX"}, + {0x2CC, "GPUREG_VSH_CODETRANSFER_DATA0"}, + {0x2CD, "GPUREG_VSH_CODETRANSFER_DATA1"}, + {0x2CE, "GPUREG_VSH_CODETRANSFER_DATA2"}, + {0x2CF, "GPUREG_VSH_CODETRANSFER_DATA3"}, + {0x2D0, "GPUREG_VSH_CODETRANSFER_DATA4"}, + {0x2D1, "GPUREG_VSH_CODETRANSFER_DATA5"}, + {0x2D2, "GPUREG_VSH_CODETRANSFER_DATA6"}, + {0x2D3, "GPUREG_VSH_CODETRANSFER_DATA7"}, + + {0x2D5, "GPUREG_VSH_OPDESCS_INDEX"}, + {0x2D6, "GPUREG_VSH_OPDESCS_DATA0"}, + {0x2D7, "GPUREG_VSH_OPDESCS_DATA1"}, + {0x2D8, "GPUREG_VSH_OPDESCS_DATA2"}, + {0x2D9, "GPUREG_VSH_OPDESCS_DATA3"}, + {0x2DA, "GPUREG_VSH_OPDESCS_DATA4"}, + {0x2DB, "GPUREG_VSH_OPDESCS_DATA5"}, + {0x2DC, "GPUREG_VSH_OPDESCS_DATA6"}, + {0x2DD, "GPUREG_VSH_OPDESCS_DATA7"}, +}; + +const char* Regs::GetRegisterName(u16 index) { + auto found = std::lower_bound(std::begin(register_names), std::end(register_names), index, + [](auto p, auto i) { return p.first < i; }); + if (found->first == index) { + return found->second; + } else { + // Return empty string if no match is found + return ""; + } +} + +} // namespace Pica diff --git a/src/video_core/regs.h b/src/video_core/regs.h new file mode 100644 index 000000000..86826088b --- /dev/null +++ b/src/video_core/regs.h @@ -0,0 +1,142 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include <string> +#ifndef _MSC_VER +#include <type_traits> // for std::enable_if +#endif + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_lighting.h" +#include "video_core/regs_pipeline.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_shader.h" +#include "video_core/regs_texturing.h" + +namespace Pica { + +// Returns index corresponding to the Regs member labeled by field_name +// TODO: Due to Visual studio bug 209229, offsetof does not return constant expressions +// when used with array elements (e.g. PICA_REG_INDEX(vs_uniform_setup.set_value[1])). +// For details cf. +// https://connect.microsoft.com/VisualStudio/feedback/details/209229/offsetof-does-not-produce-a-constant-expression-for-array-members +// Hopefully, this will be fixed sometime in the future. +// For lack of better alternatives, we currently hardcode the offsets when constant +// expressions are needed via PICA_REG_INDEX_WORKAROUND (on sane compilers, static_asserts +// will then make sure the offsets indeed match the automatically calculated ones). +#define PICA_REG_INDEX(field_name) (offsetof(Pica::Regs, field_name) / sizeof(u32)) +#if defined(_MSC_VER) +#define PICA_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) (backup_workaround_index) +#else +// NOTE: Yeah, hacking in a static_assert here just to workaround the lacking MSVC compiler +// really is this annoying. This macro just forwards its first argument to PICA_REG_INDEX +// and then performs a (no-op) cast to size_t iff the second argument matches the expected +// field offset. Otherwise, the compiler will fail to compile this code. +#define PICA_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) \ + ((typename std::enable_if<backup_workaround_index == PICA_REG_INDEX(field_name), \ + size_t>::type)PICA_REG_INDEX(field_name)) +#endif // _MSC_VER + +struct Regs { + static constexpr size_t NUM_REGS = 0x300; + + union { + struct { + INSERT_PADDING_WORDS(0x10); + u32 trigger_irq; + INSERT_PADDING_WORDS(0x2f); + RasterizerRegs rasterizer; + TexturingRegs texturing; + FramebufferRegs framebuffer; + LightingRegs lighting; + PipelineRegs pipeline; + ShaderRegs gs; + ShaderRegs vs; + INSERT_PADDING_WORDS(0x20); + }; + std::array<u32, NUM_REGS> reg_array; + }; + + /// Map register indices to names readable by humans + static const char* GetRegisterName(u16 index); +}; + +static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32), "Regs struct has wrong size"); + +// TODO: MSVC does not support using offsetof() on non-static data members even though this +// is technically allowed since C++11. This macro should be enabled once MSVC adds +// support for that. +#ifndef _MSC_VER +#define ASSERT_REG_POSITION(field_name, position) \ + static_assert(offsetof(Regs, field_name) == position * 4, \ + "Field " #field_name " has invalid position") + +ASSERT_REG_POSITION(trigger_irq, 0x10); + +ASSERT_REG_POSITION(rasterizer, 0x40); +ASSERT_REG_POSITION(rasterizer.cull_mode, 0x40); +ASSERT_REG_POSITION(rasterizer.viewport_size_x, 0x41); +ASSERT_REG_POSITION(rasterizer.viewport_size_y, 0x43); +ASSERT_REG_POSITION(rasterizer.viewport_depth_range, 0x4d); +ASSERT_REG_POSITION(rasterizer.viewport_depth_near_plane, 0x4e); +ASSERT_REG_POSITION(rasterizer.vs_output_attributes[0], 0x50); +ASSERT_REG_POSITION(rasterizer.vs_output_attributes[1], 0x51); +ASSERT_REG_POSITION(rasterizer.scissor_test, 0x65); +ASSERT_REG_POSITION(rasterizer.viewport_corner, 0x68); +ASSERT_REG_POSITION(rasterizer.depthmap_enable, 0x6D); + +ASSERT_REG_POSITION(texturing, 0x80); +ASSERT_REG_POSITION(texturing.texture0_enable, 0x80); +ASSERT_REG_POSITION(texturing.texture0, 0x81); +ASSERT_REG_POSITION(texturing.texture0_format, 0x8e); +ASSERT_REG_POSITION(texturing.fragment_lighting_enable, 0x8f); +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.tev_stage0, 0xc0); +ASSERT_REG_POSITION(texturing.tev_stage1, 0xc8); +ASSERT_REG_POSITION(texturing.tev_stage2, 0xd0); +ASSERT_REG_POSITION(texturing.tev_stage3, 0xd8); +ASSERT_REG_POSITION(texturing.tev_combiner_buffer_input, 0xe0); +ASSERT_REG_POSITION(texturing.fog_mode, 0xe0); +ASSERT_REG_POSITION(texturing.fog_color, 0xe1); +ASSERT_REG_POSITION(texturing.fog_lut_offset, 0xe6); +ASSERT_REG_POSITION(texturing.fog_lut_data, 0xe8); +ASSERT_REG_POSITION(texturing.tev_stage4, 0xf0); +ASSERT_REG_POSITION(texturing.tev_stage5, 0xf8); +ASSERT_REG_POSITION(texturing.tev_combiner_buffer_color, 0xfd); + +ASSERT_REG_POSITION(framebuffer, 0x100); +ASSERT_REG_POSITION(framebuffer.output_merger, 0x100); +ASSERT_REG_POSITION(framebuffer.framebuffer, 0x110); + +ASSERT_REG_POSITION(lighting, 0x140); + +ASSERT_REG_POSITION(pipeline, 0x200); +ASSERT_REG_POSITION(pipeline.vertex_attributes, 0x200); +ASSERT_REG_POSITION(pipeline.index_array, 0x227); +ASSERT_REG_POSITION(pipeline.num_vertices, 0x228); +ASSERT_REG_POSITION(pipeline.vertex_offset, 0x22a); +ASSERT_REG_POSITION(pipeline.trigger_draw, 0x22e); +ASSERT_REG_POSITION(pipeline.trigger_draw_indexed, 0x22f); +ASSERT_REG_POSITION(pipeline.vs_default_attributes_setup, 0x232); +ASSERT_REG_POSITION(pipeline.command_buffer, 0x238); +ASSERT_REG_POSITION(pipeline.gpu_mode, 0x245); +ASSERT_REG_POSITION(pipeline.triangle_topology, 0x25e); +ASSERT_REG_POSITION(pipeline.restart_primitive, 0x25f); + +ASSERT_REG_POSITION(gs, 0x280); +ASSERT_REG_POSITION(vs, 0x2b0); + +#undef ASSERT_REG_POSITION +#endif // !defined(_MSC_VER) + +} // namespace Pica diff --git a/src/video_core/regs_framebuffer.h b/src/video_core/regs_framebuffer.h new file mode 100644 index 000000000..9ddc79243 --- /dev/null +++ b/src/video_core/regs_framebuffer.h @@ -0,0 +1,284 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/logging/log.h" + +namespace Pica { + +struct FramebufferRegs { + enum class LogicOp : u32 { + Clear = 0, + And = 1, + AndReverse = 2, + Copy = 3, + Set = 4, + CopyInverted = 5, + NoOp = 6, + Invert = 7, + Nand = 8, + Or = 9, + Nor = 10, + Xor = 11, + Equiv = 12, + AndInverted = 13, + OrReverse = 14, + OrInverted = 15, + }; + + enum class BlendEquation : u32 { + Add = 0, + Subtract = 1, + ReverseSubtract = 2, + Min = 3, + Max = 4, + }; + + enum class BlendFactor : u32 { + Zero = 0, + One = 1, + SourceColor = 2, + OneMinusSourceColor = 3, + DestColor = 4, + OneMinusDestColor = 5, + SourceAlpha = 6, + OneMinusSourceAlpha = 7, + DestAlpha = 8, + OneMinusDestAlpha = 9, + ConstantColor = 10, + OneMinusConstantColor = 11, + ConstantAlpha = 12, + OneMinusConstantAlpha = 13, + SourceAlphaSaturate = 14, + }; + + enum class CompareFunc : u32 { + Never = 0, + Always = 1, + Equal = 2, + NotEqual = 3, + LessThan = 4, + LessThanOrEqual = 5, + GreaterThan = 6, + GreaterThanOrEqual = 7, + }; + + enum class StencilAction : u32 { + Keep = 0, + Zero = 1, + Replace = 2, + Increment = 3, + Decrement = 4, + Invert = 5, + IncrementWrap = 6, + DecrementWrap = 7, + }; + + struct { + union { + // If false, logic blending is used + BitField<8, 1, u32> alphablend_enable; + }; + + union { + BitField<0, 3, BlendEquation> blend_equation_rgb; + BitField<8, 3, BlendEquation> blend_equation_a; + + BitField<16, 4, BlendFactor> factor_source_rgb; + BitField<20, 4, BlendFactor> factor_dest_rgb; + + BitField<24, 4, BlendFactor> factor_source_a; + BitField<28, 4, BlendFactor> factor_dest_a; + } alpha_blending; + + union { + BitField<0, 4, LogicOp> logic_op; + }; + + union { + u32 raw; + BitField<0, 8, u32> r; + BitField<8, 8, u32> g; + BitField<16, 8, u32> b; + BitField<24, 8, u32> a; + } blend_const; + + union { + BitField<0, 1, u32> enable; + BitField<4, 3, CompareFunc> func; + BitField<8, 8, u32> ref; + } alpha_test; + + struct { + union { + // Raw value of this register + u32 raw_func; + + // If true, enable stencil testing + BitField<0, 1, u32> enable; + + // Comparison operation for stencil testing + BitField<4, 3, CompareFunc> func; + + // Mask used to control writing to the stencil buffer + BitField<8, 8, u32> write_mask; + + // Value to compare against for stencil testing + BitField<16, 8, u32> reference_value; + + // Mask to apply on stencil test inputs + BitField<24, 8, u32> input_mask; + }; + + union { + // Raw value of this register + u32 raw_op; + + // Action to perform when the stencil test fails + BitField<0, 3, StencilAction> action_stencil_fail; + + // Action to perform when stencil testing passed but depth testing fails + BitField<4, 3, StencilAction> action_depth_fail; + + // Action to perform when both stencil and depth testing pass + BitField<8, 3, StencilAction> action_depth_pass; + }; + } stencil_test; + + union { + BitField<0, 1, u32> depth_test_enable; + BitField<4, 3, CompareFunc> depth_test_func; + BitField<8, 1, u32> red_enable; + BitField<9, 1, u32> green_enable; + BitField<10, 1, u32> blue_enable; + BitField<11, 1, u32> alpha_enable; + BitField<12, 1, u32> depth_write_enable; + }; + + INSERT_PADDING_WORDS(0x8); + } output_merger; + + // Components are laid out in reverse byte order, most significant bits first. + enum class ColorFormat : u32 { + RGBA8 = 0, + RGB8 = 1, + RGB5A1 = 2, + RGB565 = 3, + RGBA4 = 4, + }; + + enum class DepthFormat : u32 { + D16 = 0, + D24 = 2, + D24S8 = 3, + }; + + // Returns the number of bytes in the specified color format + static unsigned BytesPerColorPixel(ColorFormat format) { + switch (format) { + case ColorFormat::RGBA8: + return 4; + case ColorFormat::RGB8: + return 3; + case ColorFormat::RGB5A1: + case ColorFormat::RGB565: + case ColorFormat::RGBA4: + return 2; + default: + LOG_CRITICAL(HW_GPU, "Unknown color format %u", format); + UNIMPLEMENTED(); + } + } + + struct FramebufferConfig { + INSERT_PADDING_WORDS(0x3); + + union { + BitField<0, 4, u32> allow_color_write; // 0 = disable, else enable + }; + + INSERT_PADDING_WORDS(0x1); + + union { + BitField<0, 2, u32> allow_depth_stencil_write; // 0 = disable, else enable + }; + + DepthFormat depth_format; // TODO: Should be a BitField! + BitField<16, 3, ColorFormat> color_format; + + INSERT_PADDING_WORDS(0x4); + + u32 depth_buffer_address; + u32 color_buffer_address; + + union { + // Apparently, the framebuffer width is stored as expected, + // while the height is stored as the actual height minus one. + // Hence, don't access these fields directly but use the accessors + // GetWidth() and GetHeight() instead. + BitField<0, 11, u32> width; + BitField<12, 10, u32> height; + }; + + INSERT_PADDING_WORDS(0x1); + + inline PAddr GetColorBufferPhysicalAddress() const { + return color_buffer_address * 8; + } + inline PAddr GetDepthBufferPhysicalAddress() const { + return depth_buffer_address * 8; + } + + inline u32 GetWidth() const { + return width; + } + + inline u32 GetHeight() const { + return height + 1; + } + } framebuffer; + + // Returns the number of bytes in the specified depth format + static u32 BytesPerDepthPixel(DepthFormat format) { + switch (format) { + case DepthFormat::D16: + return 2; + case DepthFormat::D24: + return 3; + case DepthFormat::D24S8: + return 4; + default: + LOG_CRITICAL(HW_GPU, "Unknown depth format %u", format); + UNIMPLEMENTED(); + } + } + + // Returns the number of bits per depth component of the specified depth format + static u32 DepthBitsPerPixel(DepthFormat format) { + switch (format) { + case DepthFormat::D16: + return 16; + case DepthFormat::D24: + case DepthFormat::D24S8: + return 24; + default: + LOG_CRITICAL(HW_GPU, "Unknown depth format %u", format); + UNIMPLEMENTED(); + } + } + + INSERT_PADDING_WORDS(0x20); +}; + +static_assert(sizeof(FramebufferRegs) == 0x40 * sizeof(u32), + "FramebufferRegs struct has incorrect size"); + +} // namespace Pica diff --git a/src/video_core/regs_lighting.h b/src/video_core/regs_lighting.h new file mode 100644 index 000000000..6793405d9 --- /dev/null +++ b/src/video_core/regs_lighting.h @@ -0,0 +1,294 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/vector_math.h" + +namespace Pica { + +struct LightingRegs { + enum class LightingSampler { + Distribution0 = 0, + Distribution1 = 1, + Fresnel = 3, + ReflectBlue = 4, + ReflectGreen = 5, + ReflectRed = 6, + SpotlightAttenuation = 8, + DistanceAttenuation = 16, + }; + + /** + * Pica fragment lighting supports using different LUTs for each lighting component: Reflectance + * R, G, and B channels, distribution function for specular components 0 and 1, fresnel factor, + * and spotlight attenuation. Furthermore, which LUTs are used for each channel (or whether a + * channel is enabled at all) is specified by various pre-defined lighting configurations. With + * configurations that require more LUTs, more cycles are required on HW to perform lighting + * computations. + */ + enum class LightingConfig : u32 { + Config0 = 0, ///< Reflect Red, Distribution 0, Spotlight + Config1 = 1, ///< Reflect Red, Fresnel, Spotlight + Config2 = 2, ///< Reflect Red, Distribution 0/1 + Config3 = 3, ///< Distribution 0/1, Fresnel + Config4 = 4, ///< Reflect Red/Green/Blue, Distribution 0/1, Spotlight + Config5 = 5, ///< Reflect Red/Green/Blue, Distribution 0, Fresnel, Spotlight + Config6 = 6, ///< Reflect Red, Distribution 0/1, Fresnel, Spotlight + + Config7 = 8, ///< Reflect Red/Green/Blue, Distribution 0/1, Fresnel, Spotlight + ///< NOTE: '8' is intentional, '7' does not appear to be a valid configuration + }; + + /// Selects which lighting components are affected by fresnel + enum class LightingFresnelSelector : u32 { + None = 0, ///< Fresnel is disabled + PrimaryAlpha = 1, ///< Primary (diffuse) lighting alpha is affected by fresnel + SecondaryAlpha = 2, ///< Secondary (specular) lighting alpha is affected by fresnel + Both = + PrimaryAlpha | + SecondaryAlpha, ///< Both primary and secondary lighting alphas are affected by fresnel + }; + + /// Factor used to scale the output of a lighting LUT + enum class LightingScale : u32 { + Scale1 = 0, ///< Scale is 1x + Scale2 = 1, ///< Scale is 2x + Scale4 = 2, ///< Scale is 4x + Scale8 = 3, ///< Scale is 8x + + Scale1_4 = 6, ///< Scale is 0.25x + Scale1_2 = 7, ///< Scale is 0.5x + }; + + enum class LightingLutInput : u32 { + NH = 0, // Cosine of the angle between the normal and half-angle vectors + VH = 1, // Cosine of the angle between the view and half-angle vectors + NV = 2, // Cosine of the angle between the normal and the view vector + LN = 3, // Cosine of the angle between the light and the normal vectors + }; + + enum class LightingBumpMode : u32 { + None = 0, + NormalMap = 1, + TangentMap = 2, + }; + + union LightColor { + BitField<0, 10, u32> b; + BitField<10, 10, u32> g; + BitField<20, 10, u32> r; + + Math::Vec3f ToVec3f() const { + // These fields are 10 bits wide, however 255 corresponds to 1.0f for each color + // component + return Math::MakeVec((f32)r / 255.f, (f32)g / 255.f, (f32)b / 255.f); + } + }; + + /// Returns true if the specified lighting sampler is supported by the current Pica lighting + /// configuration + static bool IsLightingSamplerSupported(LightingConfig config, LightingSampler sampler) { + switch (sampler) { + case LightingSampler::Distribution0: + return (config != LightingConfig::Config1); + + case LightingSampler::Distribution1: + return (config != LightingConfig::Config0) && (config != LightingConfig::Config1) && + (config != LightingConfig::Config5); + + case LightingSampler::Fresnel: + return (config != LightingConfig::Config0) && (config != LightingConfig::Config2) && + (config != LightingConfig::Config4); + + case LightingSampler::ReflectRed: + return (config != LightingConfig::Config3); + + case LightingSampler::ReflectGreen: + case LightingSampler::ReflectBlue: + return (config == LightingConfig::Config4) || (config == LightingConfig::Config5) || + (config == LightingConfig::Config7); + default: + UNREACHABLE_MSG("Regs::IsLightingSamplerSupported: Reached " + "unreachable section, sampler should be one " + "of Distribution0, Distribution1, Fresnel, " + "ReflectRed, ReflectGreen or ReflectBlue, instead " + "got %i", + static_cast<int>(config)); + } + } + + struct LightSrc { + LightColor specular_0; // material.specular_0 * light.specular_0 + LightColor specular_1; // material.specular_1 * light.specular_1 + LightColor diffuse; // material.diffuse * light.diffuse + LightColor ambient; // material.ambient * light.ambient + + // Encoded as 16-bit floating point + union { + BitField<0, 16, u32> x; + BitField<16, 16, u32> y; + }; + union { + BitField<0, 16, u32> z; + }; + + INSERT_PADDING_WORDS(0x3); + + union { + BitField<0, 1, u32> directional; + BitField<1, 1, u32> two_sided_diffuse; // When disabled, clamp dot-product to 0 + } config; + + BitField<0, 20, u32> dist_atten_bias; + BitField<0, 20, u32> dist_atten_scale; + + INSERT_PADDING_WORDS(0x4); + }; + static_assert(sizeof(LightSrc) == 0x10 * sizeof(u32), "LightSrc structure must be 0x10 words"); + + LightSrc light[8]; + LightColor global_ambient; // Emission + (material.ambient * lighting.ambient) + INSERT_PADDING_WORDS(0x1); + BitField<0, 3, u32> max_light_index; // Number of enabled lights - 1 + + union { + BitField<2, 2, LightingFresnelSelector> fresnel_selector; + BitField<4, 4, LightingConfig> config; + BitField<22, 2, u32> bump_selector; // 0: Texture 0, 1: Texture 1, 2: Texture 2 + BitField<27, 1, u32> clamp_highlights; + BitField<28, 2, LightingBumpMode> bump_mode; + BitField<30, 1, u32> disable_bump_renorm; + } config0; + + union { + BitField<16, 1, u32> disable_lut_d0; + BitField<17, 1, u32> disable_lut_d1; + BitField<19, 1, u32> disable_lut_fr; + BitField<20, 1, u32> disable_lut_rr; + BitField<21, 1, u32> disable_lut_rg; + BitField<22, 1, u32> disable_lut_rb; + + // Each bit specifies whether distance attenuation should be applied for the corresponding + // light. + BitField<24, 1, u32> disable_dist_atten_light_0; + BitField<25, 1, u32> disable_dist_atten_light_1; + BitField<26, 1, u32> disable_dist_atten_light_2; + BitField<27, 1, u32> disable_dist_atten_light_3; + BitField<28, 1, u32> disable_dist_atten_light_4; + BitField<29, 1, u32> disable_dist_atten_light_5; + BitField<30, 1, u32> disable_dist_atten_light_6; + BitField<31, 1, u32> disable_dist_atten_light_7; + } config1; + + bool IsDistAttenDisabled(unsigned index) const { + const unsigned disable[] = { + config1.disable_dist_atten_light_0, config1.disable_dist_atten_light_1, + config1.disable_dist_atten_light_2, config1.disable_dist_atten_light_3, + config1.disable_dist_atten_light_4, config1.disable_dist_atten_light_5, + config1.disable_dist_atten_light_6, config1.disable_dist_atten_light_7}; + return disable[index] != 0; + } + + union { + BitField<0, 8, u32> index; ///< Index at which to set data in the LUT + BitField<8, 5, u32> type; ///< Type of LUT for which to set data + } lut_config; + + BitField<0, 1, u32> disable; + INSERT_PADDING_WORDS(0x1); + + // When data is written to any of these registers, it gets written to the lookup table of the + // selected type at the selected index, specified above in the `lut_config` register. With each + // write, `lut_config.index` is incremented. It does not matter which of these registers is + // written to, the behavior will be the same. + u32 lut_data[8]; + + // These are used to specify if absolute (abs) value should be used for each LUT index. When + // abs mode is disabled, LUT indexes are in the range of (-1.0, 1.0). Otherwise, they are in + // the range of (0.0, 1.0). + union { + BitField<1, 1, u32> disable_d0; + BitField<5, 1, u32> disable_d1; + BitField<9, 1, u32> disable_sp; + BitField<13, 1, u32> disable_fr; + BitField<17, 1, u32> disable_rb; + BitField<21, 1, u32> disable_rg; + BitField<25, 1, u32> disable_rr; + } abs_lut_input; + + union { + BitField<0, 3, LightingLutInput> d0; + BitField<4, 3, LightingLutInput> d1; + BitField<8, 3, LightingLutInput> sp; + BitField<12, 3, LightingLutInput> fr; + BitField<16, 3, LightingLutInput> rb; + BitField<20, 3, LightingLutInput> rg; + BitField<24, 3, LightingLutInput> rr; + } lut_input; + + union { + BitField<0, 3, LightingScale> d0; + BitField<4, 3, LightingScale> d1; + BitField<8, 3, LightingScale> sp; + BitField<12, 3, LightingScale> fr; + BitField<16, 3, LightingScale> rb; + BitField<20, 3, LightingScale> rg; + BitField<24, 3, LightingScale> rr; + + static float GetScale(LightingScale scale) { + switch (scale) { + case LightingScale::Scale1: + return 1.0f; + case LightingScale::Scale2: + return 2.0f; + case LightingScale::Scale4: + return 4.0f; + case LightingScale::Scale8: + return 8.0f; + case LightingScale::Scale1_4: + return 0.25f; + case LightingScale::Scale1_2: + return 0.5f; + } + return 0.0f; + } + } lut_scale; + + INSERT_PADDING_WORDS(0x6); + + union { + // There are 8 light enable "slots", corresponding to the total number of lights supported + // by Pica. For N enabled lights (specified by register 0x1c2, or 'src_num' above), the + // first N slots below will be set to integers within the range of 0-7, corresponding to the + // actual light that is enabled for each slot. + + BitField<0, 3, u32> slot_0; + BitField<4, 3, u32> slot_1; + BitField<8, 3, u32> slot_2; + BitField<12, 3, u32> slot_3; + BitField<16, 3, u32> slot_4; + BitField<20, 3, u32> slot_5; + BitField<24, 3, u32> slot_6; + BitField<28, 3, u32> slot_7; + + unsigned GetNum(unsigned index) const { + const unsigned enable_slots[] = {slot_0, slot_1, slot_2, slot_3, + slot_4, slot_5, slot_6, slot_7}; + return enable_slots[index]; + } + } light_enable; + + INSERT_PADDING_WORDS(0x26); +}; + +static_assert(sizeof(LightingRegs) == 0xC0 * sizeof(u32), "LightingRegs struct has incorrect size"); + +} // namespace Pica diff --git a/src/video_core/regs_pipeline.h b/src/video_core/regs_pipeline.h new file mode 100644 index 000000000..0a4ec6e1e --- /dev/null +++ b/src/video_core/regs_pipeline.h @@ -0,0 +1,230 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Pica { + +struct PipelineRegs { + enum class VertexAttributeFormat : u32 { + BYTE = 0, + UBYTE = 1, + SHORT = 2, + FLOAT = 3, + }; + + struct { + BitField<0, 29, u32> base_address; + + PAddr GetPhysicalBaseAddress() const { + return base_address * 8; + } + + // Descriptor for internal vertex attributes + union { + BitField<0, 2, VertexAttributeFormat> format0; // size of one element + BitField<2, 2, u32> size0; // number of elements minus 1 + BitField<4, 2, VertexAttributeFormat> format1; + BitField<6, 2, u32> size1; + BitField<8, 2, VertexAttributeFormat> format2; + BitField<10, 2, u32> size2; + BitField<12, 2, VertexAttributeFormat> format3; + BitField<14, 2, u32> size3; + BitField<16, 2, VertexAttributeFormat> format4; + BitField<18, 2, u32> size4; + BitField<20, 2, VertexAttributeFormat> format5; + BitField<22, 2, u32> size5; + BitField<24, 2, VertexAttributeFormat> format6; + BitField<26, 2, u32> size6; + BitField<28, 2, VertexAttributeFormat> format7; + BitField<30, 2, u32> size7; + }; + + union { + BitField<0, 2, VertexAttributeFormat> format8; + BitField<2, 2, u32> size8; + BitField<4, 2, VertexAttributeFormat> format9; + BitField<6, 2, u32> size9; + BitField<8, 2, VertexAttributeFormat> format10; + BitField<10, 2, u32> size10; + BitField<12, 2, VertexAttributeFormat> format11; + BitField<14, 2, u32> size11; + + BitField<16, 12, u32> attribute_mask; + + // number of total attributes minus 1 + BitField<28, 4, u32> max_attribute_index; + }; + + inline VertexAttributeFormat GetFormat(int n) const { + VertexAttributeFormat formats[] = {format0, format1, format2, format3, + format4, format5, format6, format7, + format8, format9, format10, format11}; + return formats[n]; + } + + inline int GetNumElements(int n) const { + u32 sizes[] = {size0, size1, size2, size3, size4, size5, + size6, size7, size8, size9, size10, size11}; + return (int)sizes[n] + 1; + } + + inline int GetElementSizeInBytes(int n) const { + return (GetFormat(n) == VertexAttributeFormat::FLOAT) + ? 4 + : (GetFormat(n) == VertexAttributeFormat::SHORT) ? 2 : 1; + } + + inline int GetStride(int n) const { + return GetNumElements(n) * GetElementSizeInBytes(n); + } + + inline bool IsDefaultAttribute(int id) const { + return (id >= 12) || (attribute_mask & (1ULL << id)) != 0; + } + + inline int GetNumTotalAttributes() const { + return (int)max_attribute_index + 1; + } + + // Attribute loaders map the source vertex data to input attributes + // This e.g. allows to load different attributes from different memory locations + struct { + // Source attribute data offset from the base address + u32 data_offset; + + union { + BitField<0, 4, u32> comp0; + BitField<4, 4, u32> comp1; + BitField<8, 4, u32> comp2; + BitField<12, 4, u32> comp3; + BitField<16, 4, u32> comp4; + BitField<20, 4, u32> comp5; + BitField<24, 4, u32> comp6; + BitField<28, 4, u32> comp7; + }; + + union { + BitField<0, 4, u32> comp8; + BitField<4, 4, u32> comp9; + BitField<8, 4, u32> comp10; + BitField<12, 4, u32> comp11; + + // bytes for a single vertex in this loader + BitField<16, 8, u32> byte_count; + + BitField<28, 4, u32> component_count; + }; + + inline int GetComponent(int n) const { + u32 components[] = {comp0, comp1, comp2, comp3, comp4, comp5, + comp6, comp7, comp8, comp9, comp10, comp11}; + return (int)components[n]; + } + } attribute_loaders[12]; + } vertex_attributes; + + struct { + enum IndexFormat : u32 { + BYTE = 0, + SHORT = 1, + }; + + union { + BitField<0, 31, u32> offset; // relative to base attribute address + BitField<31, 1, IndexFormat> format; + }; + } index_array; + + // Number of vertices to render + u32 num_vertices; + + INSERT_PADDING_WORDS(0x1); + + // The index of the first vertex to render + u32 vertex_offset; + + INSERT_PADDING_WORDS(0x3); + + // These two trigger rendering of triangles + u32 trigger_draw; + u32 trigger_draw_indexed; + + INSERT_PADDING_WORDS(0x2); + + // These registers are used to setup the default "fall-back" vertex shader attributes + struct { + // Index of the current default attribute + u32 index; + + // Writing to these registers sets the "current" default attribute. + u32 set_value[3]; + } vs_default_attributes_setup; + + INSERT_PADDING_WORDS(0x2); + + struct { + // There are two channels that can be used to configure the next command buffer, which can + // be then executed by writing to the "trigger" registers. There are two reasons why a game + // might use this feature: + // 1) With this, an arbitrary number of additional command buffers may be executed in + // sequence without requiring any intervention of the CPU after the initial one is + // kicked off. + // 2) Games can configure these registers to provide a command list subroutine mechanism. + + BitField<0, 20, u32> size[2]; ///< Size (in bytes / 8) of each channel's command buffer + BitField<0, 28, u32> addr[2]; ///< Physical address / 8 of each channel's command buffer + u32 trigger[2]; ///< Triggers execution of the channel's command buffer when written to + + unsigned GetSize(unsigned index) const { + ASSERT(index < 2); + return 8 * size[index]; + } + + PAddr GetPhysicalAddress(unsigned index) const { + ASSERT(index < 2); + return (PAddr)(8 * addr[index]); + } + } command_buffer; + + INSERT_PADDING_WORDS(4); + + /// Number of input attributes to the vertex shader minus 1 + BitField<0, 4, u32> max_input_attrib_index; + + INSERT_PADDING_WORDS(2); + + enum class GPUMode : u32 { + Drawing = 0, + Configuring = 1, + }; + + GPUMode gpu_mode; + + INSERT_PADDING_WORDS(0x18); + + enum class TriangleTopology : u32 { + List = 0, + Strip = 1, + Fan = 2, + Shader = 3, // Programmable setup unit implemented in a geometry shader + }; + + BitField<8, 2, TriangleTopology> triangle_topology; + + u32 restart_primitive; + + INSERT_PADDING_WORDS(0x20); +}; + +static_assert(sizeof(PipelineRegs) == 0x80 * sizeof(u32), "PipelineRegs struct has incorrect size"); + +} // namespace Pica diff --git a/src/video_core/regs_rasterizer.h b/src/video_core/regs_rasterizer.h new file mode 100644 index 000000000..a471a3b38 --- /dev/null +++ b/src/video_core/regs_rasterizer.h @@ -0,0 +1,129 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Pica { + +struct RasterizerRegs { + enum class CullMode : u32 { + // Select which polygons are considered to be "frontfacing". + KeepAll = 0, + KeepClockWise = 1, + KeepCounterClockWise = 2, + // TODO: What does the third value imply? + }; + + union { + BitField<0, 2, CullMode> cull_mode; + }; + + BitField<0, 24, u32> viewport_size_x; + + INSERT_PADDING_WORDS(0x1); + + BitField<0, 24, u32> viewport_size_y; + + INSERT_PADDING_WORDS(0x9); + + BitField<0, 24, u32> viewport_depth_range; // float24 + BitField<0, 24, u32> viewport_depth_near_plane; // float24 + + BitField<0, 3, u32> vs_output_total; + + union VSOutputAttributes { + // Maps components of output vertex attributes to semantics + enum Semantic : u32 { + POSITION_X = 0, + POSITION_Y = 1, + POSITION_Z = 2, + POSITION_W = 3, + + QUATERNION_X = 4, + QUATERNION_Y = 5, + QUATERNION_Z = 6, + QUATERNION_W = 7, + + COLOR_R = 8, + COLOR_G = 9, + COLOR_B = 10, + COLOR_A = 11, + + TEXCOORD0_U = 12, + TEXCOORD0_V = 13, + TEXCOORD1_U = 14, + TEXCOORD1_V = 15, + + TEXCOORD0_W = 16, + + VIEW_X = 18, + VIEW_Y = 19, + VIEW_Z = 20, + + TEXCOORD2_U = 22, + TEXCOORD2_V = 23, + + INVALID = 31, + }; + + BitField<0, 5, Semantic> map_x; + BitField<8, 5, Semantic> map_y; + BitField<16, 5, Semantic> map_z; + BitField<24, 5, Semantic> map_w; + } vs_output_attributes[7]; + + INSERT_PADDING_WORDS(0xe); + + enum class ScissorMode : u32 { + Disabled = 0, + Exclude = 1, // Exclude pixels inside the scissor box + + Include = 3 // Exclude pixels outside the scissor box + }; + + struct { + BitField<0, 2, ScissorMode> mode; + + union { + BitField<0, 16, u32> x1; + BitField<16, 16, u32> y1; + }; + + union { + BitField<0, 16, u32> x2; + BitField<16, 16, u32> y2; + }; + } scissor_test; + + union { + BitField<0, 10, s32> x; + BitField<16, 10, s32> y; + } viewport_corner; + + INSERT_PADDING_WORDS(0x1); + + // TODO: early depth + INSERT_PADDING_WORDS(0x1); + + INSERT_PADDING_WORDS(0x2); + + enum DepthBuffering : u32 { + WBuffering = 0, + ZBuffering = 1, + }; + BitField<0, 1, DepthBuffering> depthmap_enable; + + INSERT_PADDING_WORDS(0x12); +}; + +static_assert(sizeof(RasterizerRegs) == 0x40 * sizeof(u32), + "RasterizerRegs struct has incorrect size"); + +} // namespace Pica diff --git a/src/video_core/regs_shader.h b/src/video_core/regs_shader.h new file mode 100644 index 000000000..ddb1ee451 --- /dev/null +++ b/src/video_core/regs_shader.h @@ -0,0 +1,104 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Pica { + +struct ShaderRegs { + BitField<0, 16, u32> bool_uniforms; + + union { + BitField<0, 8, u32> x; + BitField<8, 8, u32> y; + BitField<16, 8, u32> z; + BitField<24, 8, u32> w; + } int_uniforms[4]; + + INSERT_PADDING_WORDS(0x4); + + union { + // Number of input attributes to shader unit - 1 + BitField<0, 4, u32> max_input_attribute_index; + }; + + // Offset to shader program entry point (in words) + BitField<0, 16, u32> main_offset; + + /// Maps input attributes to registers. 4-bits per attribute, specifying a register index + u32 input_attribute_to_register_map_low; + u32 input_attribute_to_register_map_high; + + unsigned int GetRegisterForAttribute(unsigned int attribute_index) const { + u64 map = ((u64)input_attribute_to_register_map_high << 32) | + (u64)input_attribute_to_register_map_low; + return (map >> (attribute_index * 4)) & 0b1111; + } + + BitField<0, 16, u32> output_mask; + + // 0x28E, CODETRANSFER_END + INSERT_PADDING_WORDS(0x2); + + struct { + enum Format : u32 { + FLOAT24 = 0, + FLOAT32 = 1, + }; + + bool IsFloat32() const { + return format == FLOAT32; + } + + union { + // Index of the next uniform to write to + // TODO: ctrulib uses 8 bits for this, however that seems to yield lots of invalid + // indices + // TODO: Maybe the uppermost index is for the geometry shader? Investigate! + BitField<0, 7, u32> index; + + BitField<31, 1, Format> format; + }; + + // Writing to these registers sets the current uniform. + u32 set_value[8]; + + } uniform_setup; + + INSERT_PADDING_WORDS(0x2); + + struct { + // Offset of the next instruction to write code to. + // Incremented with each instruction write. + u32 offset; + + // Writing to these registers sets the "current" word in the shader program. + u32 set_word[8]; + } program; + + INSERT_PADDING_WORDS(0x1); + + // This register group is used to load an internal table of swizzling patterns, + // which are indexed by each shader instruction to specify vector component swizzling. + struct { + // Offset of the next swizzle pattern to write code to. + // Incremented with each instruction write. + u32 offset; + + // Writing to these registers sets the current swizzle pattern in the table. + u32 set_word[8]; + } swizzle_patterns; + + INSERT_PADDING_WORDS(0x2); +}; + +static_assert(sizeof(ShaderRegs) == 0x30 * sizeof(u32), "ShaderRegs struct has incorrect size"); + +} // namespace Pica diff --git a/src/video_core/regs_texturing.h b/src/video_core/regs_texturing.h new file mode 100644 index 000000000..be8bc6826 --- /dev/null +++ b/src/video_core/regs_texturing.h @@ -0,0 +1,328 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Pica { + +struct TexturingRegs { + struct TextureConfig { + enum TextureType : u32 { + Texture2D = 0, + TextureCube = 1, + Shadow2D = 2, + Projection2D = 3, + ShadowCube = 4, + Disabled = 5, + }; + + enum WrapMode : u32 { + ClampToEdge = 0, + ClampToBorder = 1, + Repeat = 2, + MirroredRepeat = 3, + }; + + enum TextureFilter : u32 { + Nearest = 0, + Linear = 1, + }; + + union { + u32 raw; + BitField<0, 8, u32> r; + BitField<8, 8, u32> g; + BitField<16, 8, u32> b; + BitField<24, 8, u32> a; + } border_color; + + union { + BitField<0, 16, u32> height; + BitField<16, 16, u32> width; + }; + + union { + BitField<1, 1, TextureFilter> mag_filter; + BitField<2, 1, TextureFilter> min_filter; + BitField<8, 2, WrapMode> wrap_t; + BitField<12, 2, WrapMode> wrap_s; + BitField<28, 2, TextureType> + type; ///< @note Only valid for texture 0 according to 3DBrew. + }; + + INSERT_PADDING_WORDS(0x1); + + u32 address; + + PAddr GetPhysicalAddress() const { + return address * 8; + } + + // texture1 and texture2 store the texture format directly after the address + // whereas texture0 inserts some additional flags inbetween. + // Hence, we store the format separately so that all other parameters can be described + // in a single structure. + }; + + enum class TextureFormat : u32 { + RGBA8 = 0, + RGB8 = 1, + RGB5A1 = 2, + RGB565 = 3, + RGBA4 = 4, + IA8 = 5, + RG8 = 6, ///< @note Also called HILO8 in 3DBrew. + I8 = 7, + A8 = 8, + IA4 = 9, + I4 = 10, + A4 = 11, + ETC1 = 12, // compressed + ETC1A4 = 13, // compressed + }; + + static unsigned NibblesPerPixel(TextureFormat format) { + switch (format) { + case TextureFormat::RGBA8: + return 8; + + case TextureFormat::RGB8: + return 6; + + case TextureFormat::RGB5A1: + case TextureFormat::RGB565: + case TextureFormat::RGBA4: + case TextureFormat::IA8: + case TextureFormat::RG8: + return 4; + + case TextureFormat::I4: + case TextureFormat::A4: + return 1; + + case TextureFormat::I8: + case TextureFormat::A8: + case TextureFormat::IA4: + + default: // placeholder for yet unknown formats + UNIMPLEMENTED(); + return 0; + } + } + + union { + BitField<0, 1, u32> texture0_enable; + BitField<1, 1, u32> texture1_enable; + BitField<2, 1, u32> texture2_enable; + }; + TextureConfig texture0; + INSERT_PADDING_WORDS(0x8); + BitField<0, 4, TextureFormat> texture0_format; + BitField<0, 1, u32> fragment_lighting_enable; + INSERT_PADDING_WORDS(0x1); + TextureConfig texture1; + BitField<0, 4, TextureFormat> texture1_format; + INSERT_PADDING_WORDS(0x2); + TextureConfig texture2; + BitField<0, 4, TextureFormat> texture2_format; + INSERT_PADDING_WORDS(0x21); + + struct FullTextureConfig { + const bool enabled; + const TextureConfig config; + const TextureFormat format; + }; + const std::array<FullTextureConfig, 3> GetTextures() const { + return {{ + {texture0_enable.ToBool(), texture0, texture0_format}, + {texture1_enable.ToBool(), texture1, texture1_format}, + {texture2_enable.ToBool(), texture2, texture2_format}, + }}; + } + + // 0xc0-0xff: Texture Combiner (akin to glTexEnv) + struct TevStageConfig { + enum class Source : u32 { + PrimaryColor = 0x0, + PrimaryFragmentColor = 0x1, + SecondaryFragmentColor = 0x2, + + Texture0 = 0x3, + Texture1 = 0x4, + Texture2 = 0x5, + Texture3 = 0x6, + + PreviousBuffer = 0xd, + Constant = 0xe, + Previous = 0xf, + }; + + enum class ColorModifier : u32 { + SourceColor = 0x0, + OneMinusSourceColor = 0x1, + SourceAlpha = 0x2, + OneMinusSourceAlpha = 0x3, + SourceRed = 0x4, + OneMinusSourceRed = 0x5, + + SourceGreen = 0x8, + OneMinusSourceGreen = 0x9, + + SourceBlue = 0xc, + OneMinusSourceBlue = 0xd, + }; + + enum class AlphaModifier : u32 { + SourceAlpha = 0x0, + OneMinusSourceAlpha = 0x1, + SourceRed = 0x2, + OneMinusSourceRed = 0x3, + SourceGreen = 0x4, + OneMinusSourceGreen = 0x5, + SourceBlue = 0x6, + OneMinusSourceBlue = 0x7, + }; + + enum class Operation : u32 { + Replace = 0, + Modulate = 1, + Add = 2, + AddSigned = 3, + Lerp = 4, + Subtract = 5, + Dot3_RGB = 6, + + MultiplyThenAdd = 8, + AddThenMultiply = 9, + }; + + union { + u32 sources_raw; + BitField<0, 4, Source> color_source1; + BitField<4, 4, Source> color_source2; + BitField<8, 4, Source> color_source3; + BitField<16, 4, Source> alpha_source1; + BitField<20, 4, Source> alpha_source2; + BitField<24, 4, Source> alpha_source3; + }; + + union { + u32 modifiers_raw; + BitField<0, 4, ColorModifier> color_modifier1; + BitField<4, 4, ColorModifier> color_modifier2; + BitField<8, 4, ColorModifier> color_modifier3; + BitField<12, 3, AlphaModifier> alpha_modifier1; + BitField<16, 3, AlphaModifier> alpha_modifier2; + BitField<20, 3, AlphaModifier> alpha_modifier3; + }; + + union { + u32 ops_raw; + BitField<0, 4, Operation> color_op; + BitField<16, 4, Operation> alpha_op; + }; + + union { + u32 const_color; + BitField<0, 8, u32> const_r; + BitField<8, 8, u32> const_g; + BitField<16, 8, u32> const_b; + BitField<24, 8, u32> const_a; + }; + + union { + u32 scales_raw; + BitField<0, 2, u32> color_scale; + BitField<16, 2, u32> alpha_scale; + }; + + inline unsigned GetColorMultiplier() const { + return (color_scale < 3) ? (1 << color_scale) : 1; + } + + inline unsigned GetAlphaMultiplier() const { + return (alpha_scale < 3) ? (1 << alpha_scale) : 1; + } + }; + + TevStageConfig tev_stage0; + INSERT_PADDING_WORDS(0x3); + TevStageConfig tev_stage1; + INSERT_PADDING_WORDS(0x3); + TevStageConfig tev_stage2; + INSERT_PADDING_WORDS(0x3); + TevStageConfig tev_stage3; + INSERT_PADDING_WORDS(0x3); + + enum class FogMode : u32 { + None = 0, + Fog = 5, + Gas = 7, + }; + + union { + BitField<0, 3, FogMode> fog_mode; + BitField<16, 1, u32> fog_flip; + + union { + // Tev stages 0-3 write their output to the combiner buffer if the corresponding bit in + // these masks are set + BitField<8, 4, u32> update_mask_rgb; + BitField<12, 4, u32> update_mask_a; + + bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const { + return (stage_index < 4) && (update_mask_rgb & (1 << stage_index)); + } + + bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const { + return (stage_index < 4) && (update_mask_a & (1 << stage_index)); + } + } tev_combiner_buffer_input; + }; + + union { + u32 raw; + BitField<0, 8, u32> r; + BitField<8, 8, u32> g; + BitField<16, 8, u32> b; + } fog_color; + + INSERT_PADDING_WORDS(0x4); + + BitField<0, 16, u32> fog_lut_offset; + + INSERT_PADDING_WORDS(0x1); + + u32 fog_lut_data[8]; + + TevStageConfig tev_stage4; + INSERT_PADDING_WORDS(0x3); + TevStageConfig tev_stage5; + + union { + u32 raw; + BitField<0, 8, u32> r; + BitField<8, 8, u32> g; + BitField<16, 8, u32> b; + BitField<24, 8, u32> a; + } tev_combiner_buffer_color; + + INSERT_PADDING_WORDS(0x2); + + const std::array<TevStageConfig, 6> GetTevStages() const { + return {{tev_stage0, tev_stage1, tev_stage2, tev_stage3, tev_stage4, tev_stage5}}; + }; +}; + +static_assert(sizeof(TexturingRegs) == 0x80 * sizeof(u32), + "TexturingRegs struct has incorrect size"); + +} // namespace Pica diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index fd38175b3..f6ece5c4b 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -6,7 +6,7 @@ #include <memory> #include "video_core/renderer_base.h" #include "video_core/renderer_opengl/gl_rasterizer.h" -#include "video_core/swrasterizer.h" +#include "video_core/swrasterizer/swrasterizer.h" #include "video_core/video_core.h" void RendererBase::RefreshRasterizerSetting() { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index f3674e965..de1d5eba7 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -14,8 +14,10 @@ #include "common/microprofile.h" #include "common/vector_math.h" #include "core/hw/gpu.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_texturing.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_gen.h" #include "video_core/renderer_opengl/gl_shader_util.h" @@ -26,16 +28,6 @@ MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192)); MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(100, 100, 255)); MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100)); -static bool IsPassThroughTevStage(const Pica::Regs::TevStageConfig& stage) { - return (stage.color_op == Pica::Regs::TevStageConfig::Operation::Replace && - stage.alpha_op == Pica::Regs::TevStageConfig::Operation::Replace && - stage.color_source1 == Pica::Regs::TevStageConfig::Source::Previous && - stage.alpha_source1 == Pica::Regs::TevStageConfig::Source::Previous && - stage.color_modifier1 == Pica::Regs::TevStageConfig::ColorModifier::SourceColor && - stage.alpha_modifier1 == Pica::Regs::TevStageConfig::AlphaModifier::SourceAlpha && - stage.GetColorMultiplier() == 1 && stage.GetAlphaMultiplier() == 1); -} - RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) { // Create sampler objects for (size_t i = 0; i < texture_samplers.size(); ++i) { @@ -181,7 +173,7 @@ void RasterizerOpenGL::DrawTriangles() { CachedSurface* depth_surface; MathUtil::Rectangle<int> rect; std::tie(color_surface, depth_surface, rect) = - res_cache.GetFramebufferSurfaces(regs.framebuffer); + res_cache.GetFramebufferSurfaces(regs.framebuffer.framebuffer); state.draw.draw_framebuffer = framebuffer.handle; state.Apply(); @@ -190,20 +182,24 @@ void RasterizerOpenGL::DrawTriangles() { color_surface != nullptr ? color_surface->texture.handle : 0, 0); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_surface != nullptr ? depth_surface->texture.handle : 0, 0); - bool has_stencil = regs.framebuffer.depth_format == Pica::Regs::DepthFormat::D24S8; + bool has_stencil = + regs.framebuffer.framebuffer.depth_format == Pica::FramebufferRegs::DepthFormat::D24S8; glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, (has_stencil && depth_surface != nullptr) ? depth_surface->texture.handle : 0, 0); // Sync the viewport // These registers hold half-width and half-height, so must be multiplied by 2 - GLsizei viewport_width = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_x).ToFloat32() * 2; - GLsizei viewport_height = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_y).ToFloat32() * 2; + GLsizei viewport_width = + (GLsizei)Pica::float24::FromRaw(regs.rasterizer.viewport_size_x).ToFloat32() * 2; + GLsizei viewport_height = + (GLsizei)Pica::float24::FromRaw(regs.rasterizer.viewport_size_y).ToFloat32() * 2; - glViewport((GLint)(rect.left + regs.viewport_corner.x * color_surface->res_scale_width), - (GLint)(rect.bottom + regs.viewport_corner.y * color_surface->res_scale_height), - (GLsizei)(viewport_width * color_surface->res_scale_width), - (GLsizei)(viewport_height * color_surface->res_scale_height)); + glViewport( + (GLint)(rect.left + regs.rasterizer.viewport_corner.x * color_surface->res_scale_width), + (GLint)(rect.bottom + regs.rasterizer.viewport_corner.y * color_surface->res_scale_height), + (GLsizei)(viewport_width * color_surface->res_scale_width), + (GLsizei)(viewport_height * color_surface->res_scale_height)); if (uniform_block_data.data.framebuffer_scale[0] != color_surface->res_scale_width || uniform_block_data.data.framebuffer_scale[1] != color_surface->res_scale_height) { @@ -215,16 +211,16 @@ void RasterizerOpenGL::DrawTriangles() { // Scissor checks are window-, not viewport-relative, which means that if the cached texture // sub-rect changes, the scissor bounds also need to be updated. - GLint scissor_x1 = - static_cast<GLint>(rect.left + regs.scissor_test.x1 * color_surface->res_scale_width); - GLint scissor_y1 = - static_cast<GLint>(rect.bottom + regs.scissor_test.y1 * color_surface->res_scale_height); + GLint scissor_x1 = static_cast<GLint>( + rect.left + regs.rasterizer.scissor_test.x1 * color_surface->res_scale_width); + GLint scissor_y1 = static_cast<GLint>( + rect.bottom + regs.rasterizer.scissor_test.y1 * color_surface->res_scale_height); // x2, y2 have +1 added to cover the entire pixel area, otherwise you might get cracks when // scaling or doing multisampling. - GLint scissor_x2 = - static_cast<GLint>(rect.left + (regs.scissor_test.x2 + 1) * color_surface->res_scale_width); + GLint scissor_x2 = static_cast<GLint>( + rect.left + (regs.rasterizer.scissor_test.x2 + 1) * color_surface->res_scale_width); GLint scissor_y2 = static_cast<GLint>( - rect.bottom + (regs.scissor_test.y2 + 1) * color_surface->res_scale_height); + rect.bottom + (regs.rasterizer.scissor_test.y2 + 1) * color_surface->res_scale_height); if (uniform_block_data.data.scissor_x1 != scissor_x1 || uniform_block_data.data.scissor_x2 != scissor_x2 || @@ -239,7 +235,7 @@ void RasterizerOpenGL::DrawTriangles() { } // Sync and bind the texture surfaces - const auto pica_textures = regs.GetTextures(); + const auto pica_textures = regs.texturing.GetTextures(); for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) { const auto& texture = pica_textures[texture_index]; @@ -316,69 +312,69 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { switch (id) { // Culling - case PICA_REG_INDEX(cull_mode): + case PICA_REG_INDEX(rasterizer.cull_mode): SyncCullMode(); break; // Depth modifiers - case PICA_REG_INDEX(viewport_depth_range): + case PICA_REG_INDEX(rasterizer.viewport_depth_range): SyncDepthScale(); break; - case PICA_REG_INDEX(viewport_depth_near_plane): + case PICA_REG_INDEX(rasterizer.viewport_depth_near_plane): SyncDepthOffset(); break; // Depth buffering - case PICA_REG_INDEX(depthmap_enable): + case PICA_REG_INDEX(rasterizer.depthmap_enable): shader_dirty = true; break; // Blending - case PICA_REG_INDEX(output_merger.alphablend_enable): + case PICA_REG_INDEX(framebuffer.output_merger.alphablend_enable): SyncBlendEnabled(); break; - case PICA_REG_INDEX(output_merger.alpha_blending): + case PICA_REG_INDEX(framebuffer.output_merger.alpha_blending): SyncBlendFuncs(); break; - case PICA_REG_INDEX(output_merger.blend_const): + case PICA_REG_INDEX(framebuffer.output_merger.blend_const): SyncBlendColor(); break; // Fog state - case PICA_REG_INDEX(fog_color): + case PICA_REG_INDEX(texturing.fog_color): SyncFogColor(); break; - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[0], 0xe8): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[1], 0xe9): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[2], 0xea): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[3], 0xeb): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[4], 0xec): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[5], 0xed): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[6], 0xee): - case PICA_REG_INDEX_WORKAROUND(fog_lut_data[7], 0xef): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[0], 0xe8): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[1], 0xe9): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[2], 0xea): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[3], 0xeb): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[4], 0xec): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[5], 0xed): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[6], 0xee): + case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[7], 0xef): uniform_block_data.fog_lut_dirty = true; break; // Alpha test - case PICA_REG_INDEX(output_merger.alpha_test): + case PICA_REG_INDEX(framebuffer.output_merger.alpha_test): SyncAlphaTest(); shader_dirty = true; break; // Sync GL stencil test + stencil write mask // (Pica stencil test function register also contains a stencil write mask) - case PICA_REG_INDEX(output_merger.stencil_test.raw_func): + case PICA_REG_INDEX(framebuffer.output_merger.stencil_test.raw_func): SyncStencilTest(); SyncStencilWriteMask(); break; - case PICA_REG_INDEX(output_merger.stencil_test.raw_op): - case PICA_REG_INDEX(framebuffer.depth_format): + case PICA_REG_INDEX(framebuffer.output_merger.stencil_test.raw_op): + case PICA_REG_INDEX(framebuffer.framebuffer.depth_format): SyncStencilTest(); break; // Sync GL depth test + depth and color write mask // (Pica depth test function register also contains a depth and color write mask) - case PICA_REG_INDEX(output_merger.depth_test_enable): + case PICA_REG_INDEX(framebuffer.output_merger.depth_test_enable): SyncDepthTest(); SyncDepthWriteMask(); SyncColorWriteMask(); @@ -386,88 +382,88 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { // Sync GL depth and stencil write mask // (This is a dedicated combined depth / stencil write-enable register) - case PICA_REG_INDEX(framebuffer.allow_depth_stencil_write): + case PICA_REG_INDEX(framebuffer.framebuffer.allow_depth_stencil_write): SyncDepthWriteMask(); SyncStencilWriteMask(); break; // Sync GL color write mask // (This is a dedicated color write-enable register) - case PICA_REG_INDEX(framebuffer.allow_color_write): + case PICA_REG_INDEX(framebuffer.framebuffer.allow_color_write): SyncColorWriteMask(); break; // Scissor test - case PICA_REG_INDEX(scissor_test.mode): + case PICA_REG_INDEX(rasterizer.scissor_test.mode): shader_dirty = true; break; // Logic op - case PICA_REG_INDEX(output_merger.logic_op): + case PICA_REG_INDEX(framebuffer.output_merger.logic_op): SyncLogicOp(); break; // Texture 0 type - case PICA_REG_INDEX(texture0.type): + case PICA_REG_INDEX(texturing.texture0.type): shader_dirty = true; break; // TEV stages // (This also syncs fog_mode and fog_flip which are part of tev_combiner_buffer_input) - case PICA_REG_INDEX(tev_stage0.color_source1): - case PICA_REG_INDEX(tev_stage0.color_modifier1): - case PICA_REG_INDEX(tev_stage0.color_op): - case PICA_REG_INDEX(tev_stage0.color_scale): - case PICA_REG_INDEX(tev_stage1.color_source1): - case PICA_REG_INDEX(tev_stage1.color_modifier1): - case PICA_REG_INDEX(tev_stage1.color_op): - case PICA_REG_INDEX(tev_stage1.color_scale): - case PICA_REG_INDEX(tev_stage2.color_source1): - case PICA_REG_INDEX(tev_stage2.color_modifier1): - case PICA_REG_INDEX(tev_stage2.color_op): - case PICA_REG_INDEX(tev_stage2.color_scale): - case PICA_REG_INDEX(tev_stage3.color_source1): - case PICA_REG_INDEX(tev_stage3.color_modifier1): - case PICA_REG_INDEX(tev_stage3.color_op): - case PICA_REG_INDEX(tev_stage3.color_scale): - case PICA_REG_INDEX(tev_stage4.color_source1): - case PICA_REG_INDEX(tev_stage4.color_modifier1): - case PICA_REG_INDEX(tev_stage4.color_op): - case PICA_REG_INDEX(tev_stage4.color_scale): - case PICA_REG_INDEX(tev_stage5.color_source1): - case PICA_REG_INDEX(tev_stage5.color_modifier1): - case PICA_REG_INDEX(tev_stage5.color_op): - case PICA_REG_INDEX(tev_stage5.color_scale): - case PICA_REG_INDEX(tev_combiner_buffer_input): + case PICA_REG_INDEX(texturing.tev_stage0.color_source1): + case PICA_REG_INDEX(texturing.tev_stage0.color_modifier1): + case PICA_REG_INDEX(texturing.tev_stage0.color_op): + case PICA_REG_INDEX(texturing.tev_stage0.color_scale): + case PICA_REG_INDEX(texturing.tev_stage1.color_source1): + case PICA_REG_INDEX(texturing.tev_stage1.color_modifier1): + case PICA_REG_INDEX(texturing.tev_stage1.color_op): + case PICA_REG_INDEX(texturing.tev_stage1.color_scale): + case PICA_REG_INDEX(texturing.tev_stage2.color_source1): + case PICA_REG_INDEX(texturing.tev_stage2.color_modifier1): + case PICA_REG_INDEX(texturing.tev_stage2.color_op): + case PICA_REG_INDEX(texturing.tev_stage2.color_scale): + case PICA_REG_INDEX(texturing.tev_stage3.color_source1): + case PICA_REG_INDEX(texturing.tev_stage3.color_modifier1): + case PICA_REG_INDEX(texturing.tev_stage3.color_op): + case PICA_REG_INDEX(texturing.tev_stage3.color_scale): + case PICA_REG_INDEX(texturing.tev_stage4.color_source1): + case PICA_REG_INDEX(texturing.tev_stage4.color_modifier1): + case PICA_REG_INDEX(texturing.tev_stage4.color_op): + case PICA_REG_INDEX(texturing.tev_stage4.color_scale): + case PICA_REG_INDEX(texturing.tev_stage5.color_source1): + case PICA_REG_INDEX(texturing.tev_stage5.color_modifier1): + case PICA_REG_INDEX(texturing.tev_stage5.color_op): + case PICA_REG_INDEX(texturing.tev_stage5.color_scale): + case PICA_REG_INDEX(texturing.tev_combiner_buffer_input): shader_dirty = true; break; - case PICA_REG_INDEX(tev_stage0.const_r): - SyncTevConstColor(0, regs.tev_stage0); + case PICA_REG_INDEX(texturing.tev_stage0.const_r): + SyncTevConstColor(0, regs.texturing.tev_stage0); break; - case PICA_REG_INDEX(tev_stage1.const_r): - SyncTevConstColor(1, regs.tev_stage1); + case PICA_REG_INDEX(texturing.tev_stage1.const_r): + SyncTevConstColor(1, regs.texturing.tev_stage1); break; - case PICA_REG_INDEX(tev_stage2.const_r): - SyncTevConstColor(2, regs.tev_stage2); + case PICA_REG_INDEX(texturing.tev_stage2.const_r): + SyncTevConstColor(2, regs.texturing.tev_stage2); break; - case PICA_REG_INDEX(tev_stage3.const_r): - SyncTevConstColor(3, regs.tev_stage3); + case PICA_REG_INDEX(texturing.tev_stage3.const_r): + SyncTevConstColor(3, regs.texturing.tev_stage3); break; - case PICA_REG_INDEX(tev_stage4.const_r): - SyncTevConstColor(4, regs.tev_stage4); + case PICA_REG_INDEX(texturing.tev_stage4.const_r): + SyncTevConstColor(4, regs.texturing.tev_stage4); break; - case PICA_REG_INDEX(tev_stage5.const_r): - SyncTevConstColor(5, regs.tev_stage5); + case PICA_REG_INDEX(texturing.tev_stage5.const_r): + SyncTevConstColor(5, regs.texturing.tev_stage5); break; // TEV combiner buffer color - case PICA_REG_INDEX(tev_combiner_buffer_color): + case PICA_REG_INDEX(texturing.tev_combiner_buffer_color): SyncCombinerColor(); break; // Fragment lighting switches case PICA_REG_INDEX(lighting.disable): - case PICA_REG_INDEX(lighting.num_lights): + case PICA_REG_INDEX(lighting.max_light_index): case PICA_REG_INDEX(lighting.config0): case PICA_REG_INDEX(lighting.config1): case PICA_REG_INDEX(lighting.abs_lut_input): @@ -976,7 +972,9 @@ void RasterizerOpenGL::SamplerInfo::Create() { // Other attributes have correct defaults } -void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Pica::Regs::TextureConfig& config) { +void RasterizerOpenGL::SamplerInfo::SyncWithConfig( + const Pica::TexturingRegs::TextureConfig& config) { + GLuint s = sampler.handle; if (mag_filter != config.mag_filter) { @@ -1073,67 +1071,69 @@ void RasterizerOpenGL::SetShader() { current_shader = shader_cache.emplace(config, std::move(shader)).first->second.get(); - unsigned int block_index = - glGetUniformBlockIndex(current_shader->shader.handle, "shader_data"); - GLint block_size; - glGetActiveUniformBlockiv(current_shader->shader.handle, block_index, - GL_UNIFORM_BLOCK_DATA_SIZE, &block_size); - ASSERT_MSG(block_size == sizeof(UniformData), - "Uniform block size did not match! Got %d, expected %zu", - static_cast<int>(block_size), sizeof(UniformData)); - glUniformBlockBinding(current_shader->shader.handle, block_index, 0); - - // Update uniforms - SyncDepthScale(); - SyncDepthOffset(); - SyncAlphaTest(); - SyncCombinerColor(); - auto& tev_stages = Pica::g_state.regs.GetTevStages(); - for (int index = 0; index < tev_stages.size(); ++index) - SyncTevConstColor(index, tev_stages[index]); + GLuint block_index = glGetUniformBlockIndex(current_shader->shader.handle, "shader_data"); + if (block_index != GL_INVALID_INDEX) { + GLint block_size; + glGetActiveUniformBlockiv(current_shader->shader.handle, block_index, + GL_UNIFORM_BLOCK_DATA_SIZE, &block_size); + ASSERT_MSG(block_size == sizeof(UniformData), + "Uniform block size did not match! Got %d, expected %zu", + static_cast<int>(block_size), sizeof(UniformData)); + glUniformBlockBinding(current_shader->shader.handle, block_index, 0); + + // Update uniforms + SyncDepthScale(); + SyncDepthOffset(); + SyncAlphaTest(); + SyncCombinerColor(); + auto& tev_stages = Pica::g_state.regs.texturing.GetTevStages(); + for (int index = 0; index < tev_stages.size(); ++index) + SyncTevConstColor(index, tev_stages[index]); + + SyncGlobalAmbient(); + for (int light_index = 0; light_index < 8; light_index++) { + SyncLightSpecular0(light_index); + SyncLightSpecular1(light_index); + SyncLightDiffuse(light_index); + SyncLightAmbient(light_index); + SyncLightPosition(light_index); + SyncLightDistanceAttenuationBias(light_index); + SyncLightDistanceAttenuationScale(light_index); + } - SyncGlobalAmbient(); - for (int light_index = 0; light_index < 8; light_index++) { - SyncLightSpecular0(light_index); - SyncLightSpecular1(light_index); - SyncLightDiffuse(light_index); - SyncLightAmbient(light_index); - SyncLightPosition(light_index); - SyncLightDistanceAttenuationBias(light_index); - SyncLightDistanceAttenuationScale(light_index); + SyncFogColor(); } - - SyncFogColor(); } } void RasterizerOpenGL::SyncCullMode() { const auto& regs = Pica::g_state.regs; - switch (regs.cull_mode) { - case Pica::Regs::CullMode::KeepAll: + switch (regs.rasterizer.cull_mode) { + case Pica::RasterizerRegs::CullMode::KeepAll: state.cull.enabled = false; break; - case Pica::Regs::CullMode::KeepClockWise: + case Pica::RasterizerRegs::CullMode::KeepClockWise: state.cull.enabled = true; state.cull.front_face = GL_CW; break; - case Pica::Regs::CullMode::KeepCounterClockWise: + case Pica::RasterizerRegs::CullMode::KeepCounterClockWise: state.cull.enabled = true; state.cull.front_face = GL_CCW; break; default: - LOG_CRITICAL(Render_OpenGL, "Unknown cull mode %d", regs.cull_mode.Value()); + LOG_CRITICAL(Render_OpenGL, "Unknown cull mode %d", regs.rasterizer.cull_mode.Value()); UNIMPLEMENTED(); break; } } void RasterizerOpenGL::SyncDepthScale() { - float depth_scale = Pica::float24::FromRaw(Pica::g_state.regs.viewport_depth_range).ToFloat32(); + float depth_scale = + Pica::float24::FromRaw(Pica::g_state.regs.rasterizer.viewport_depth_range).ToFloat32(); if (depth_scale != uniform_block_data.data.depth_scale) { uniform_block_data.data.depth_scale = depth_scale; uniform_block_data.dirty = true; @@ -1142,7 +1142,7 @@ void RasterizerOpenGL::SyncDepthScale() { void RasterizerOpenGL::SyncDepthOffset() { float depth_offset = - Pica::float24::FromRaw(Pica::g_state.regs.viewport_depth_near_plane).ToFloat32(); + Pica::float24::FromRaw(Pica::g_state.regs.rasterizer.viewport_depth_near_plane).ToFloat32(); if (depth_offset != uniform_block_data.data.depth_offset) { uniform_block_data.data.depth_offset = depth_offset; uniform_block_data.dirty = true; @@ -1150,25 +1150,28 @@ void RasterizerOpenGL::SyncDepthOffset() { } void RasterizerOpenGL::SyncBlendEnabled() { - state.blend.enabled = (Pica::g_state.regs.output_merger.alphablend_enable == 1); + state.blend.enabled = (Pica::g_state.regs.framebuffer.output_merger.alphablend_enable == 1); } void RasterizerOpenGL::SyncBlendFuncs() { const auto& regs = Pica::g_state.regs; state.blend.rgb_equation = - PicaToGL::BlendEquation(regs.output_merger.alpha_blending.blend_equation_rgb); + PicaToGL::BlendEquation(regs.framebuffer.output_merger.alpha_blending.blend_equation_rgb); state.blend.a_equation = - PicaToGL::BlendEquation(regs.output_merger.alpha_blending.blend_equation_a); + PicaToGL::BlendEquation(regs.framebuffer.output_merger.alpha_blending.blend_equation_a); state.blend.src_rgb_func = - PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_source_rgb); + PicaToGL::BlendFunc(regs.framebuffer.output_merger.alpha_blending.factor_source_rgb); state.blend.dst_rgb_func = - PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_dest_rgb); - state.blend.src_a_func = PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_source_a); - state.blend.dst_a_func = PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_dest_a); + PicaToGL::BlendFunc(regs.framebuffer.output_merger.alpha_blending.factor_dest_rgb); + state.blend.src_a_func = + PicaToGL::BlendFunc(regs.framebuffer.output_merger.alpha_blending.factor_source_a); + state.blend.dst_a_func = + PicaToGL::BlendFunc(regs.framebuffer.output_merger.alpha_blending.factor_dest_a); } void RasterizerOpenGL::SyncBlendColor() { - auto blend_color = PicaToGL::ColorRGBA8(Pica::g_state.regs.output_merger.blend_const.raw); + auto blend_color = + PicaToGL::ColorRGBA8(Pica::g_state.regs.framebuffer.output_merger.blend_const.raw); state.blend.color.red = blend_color[0]; state.blend.color.green = blend_color[1]; state.blend.color.blue = blend_color[2]; @@ -1178,8 +1181,8 @@ void RasterizerOpenGL::SyncBlendColor() { void RasterizerOpenGL::SyncFogColor() { const auto& regs = Pica::g_state.regs; uniform_block_data.data.fog_color = { - regs.fog_color.r.Value() / 255.0f, regs.fog_color.g.Value() / 255.0f, - regs.fog_color.b.Value() / 255.0f, + regs.texturing.fog_color.r.Value() / 255.0f, regs.texturing.fog_color.g.Value() / 255.0f, + regs.texturing.fog_color.b.Value() / 255.0f, }; uniform_block_data.dirty = true; } @@ -1200,70 +1203,78 @@ void RasterizerOpenGL::SyncFogLUT() { void RasterizerOpenGL::SyncAlphaTest() { const auto& regs = Pica::g_state.regs; - if (regs.output_merger.alpha_test.ref != uniform_block_data.data.alphatest_ref) { - uniform_block_data.data.alphatest_ref = regs.output_merger.alpha_test.ref; + if (regs.framebuffer.output_merger.alpha_test.ref != uniform_block_data.data.alphatest_ref) { + uniform_block_data.data.alphatest_ref = regs.framebuffer.output_merger.alpha_test.ref; uniform_block_data.dirty = true; } } void RasterizerOpenGL::SyncLogicOp() { - state.logic_op = PicaToGL::LogicOp(Pica::g_state.regs.output_merger.logic_op); + state.logic_op = PicaToGL::LogicOp(Pica::g_state.regs.framebuffer.output_merger.logic_op); } void RasterizerOpenGL::SyncColorWriteMask() { const auto& regs = Pica::g_state.regs; auto IsColorWriteEnabled = [&](u32 value) { - return (regs.framebuffer.allow_color_write != 0 && value != 0) ? GL_TRUE : GL_FALSE; + return (regs.framebuffer.framebuffer.allow_color_write != 0 && value != 0) ? GL_TRUE + : GL_FALSE; }; - state.color_mask.red_enabled = IsColorWriteEnabled(regs.output_merger.red_enable); - state.color_mask.green_enabled = IsColorWriteEnabled(regs.output_merger.green_enable); - state.color_mask.blue_enabled = IsColorWriteEnabled(regs.output_merger.blue_enable); - state.color_mask.alpha_enabled = IsColorWriteEnabled(regs.output_merger.alpha_enable); + state.color_mask.red_enabled = IsColorWriteEnabled(regs.framebuffer.output_merger.red_enable); + state.color_mask.green_enabled = + IsColorWriteEnabled(regs.framebuffer.output_merger.green_enable); + state.color_mask.blue_enabled = IsColorWriteEnabled(regs.framebuffer.output_merger.blue_enable); + state.color_mask.alpha_enabled = + IsColorWriteEnabled(regs.framebuffer.output_merger.alpha_enable); } void RasterizerOpenGL::SyncStencilWriteMask() { const auto& regs = Pica::g_state.regs; - state.stencil.write_mask = (regs.framebuffer.allow_depth_stencil_write != 0) - ? static_cast<GLuint>(regs.output_merger.stencil_test.write_mask) - : 0; + state.stencil.write_mask = + (regs.framebuffer.framebuffer.allow_depth_stencil_write != 0) + ? static_cast<GLuint>(regs.framebuffer.output_merger.stencil_test.write_mask) + : 0; } void RasterizerOpenGL::SyncDepthWriteMask() { const auto& regs = Pica::g_state.regs; - state.depth.write_mask = - (regs.framebuffer.allow_depth_stencil_write != 0 && regs.output_merger.depth_write_enable) - ? GL_TRUE - : GL_FALSE; + state.depth.write_mask = (regs.framebuffer.framebuffer.allow_depth_stencil_write != 0 && + regs.framebuffer.output_merger.depth_write_enable) + ? GL_TRUE + : GL_FALSE; } void RasterizerOpenGL::SyncStencilTest() { const auto& regs = Pica::g_state.regs; - state.stencil.test_enabled = regs.output_merger.stencil_test.enable && - regs.framebuffer.depth_format == Pica::Regs::DepthFormat::D24S8; - state.stencil.test_func = PicaToGL::CompareFunc(regs.output_merger.stencil_test.func); - state.stencil.test_ref = regs.output_merger.stencil_test.reference_value; - state.stencil.test_mask = regs.output_merger.stencil_test.input_mask; + state.stencil.test_enabled = + regs.framebuffer.output_merger.stencil_test.enable && + regs.framebuffer.framebuffer.depth_format == Pica::FramebufferRegs::DepthFormat::D24S8; + state.stencil.test_func = + PicaToGL::CompareFunc(regs.framebuffer.output_merger.stencil_test.func); + state.stencil.test_ref = regs.framebuffer.output_merger.stencil_test.reference_value; + state.stencil.test_mask = regs.framebuffer.output_merger.stencil_test.input_mask; state.stencil.action_stencil_fail = - PicaToGL::StencilOp(regs.output_merger.stencil_test.action_stencil_fail); + PicaToGL::StencilOp(regs.framebuffer.output_merger.stencil_test.action_stencil_fail); state.stencil.action_depth_fail = - PicaToGL::StencilOp(regs.output_merger.stencil_test.action_depth_fail); + PicaToGL::StencilOp(regs.framebuffer.output_merger.stencil_test.action_depth_fail); state.stencil.action_depth_pass = - PicaToGL::StencilOp(regs.output_merger.stencil_test.action_depth_pass); + PicaToGL::StencilOp(regs.framebuffer.output_merger.stencil_test.action_depth_pass); } void RasterizerOpenGL::SyncDepthTest() { const auto& regs = Pica::g_state.regs; - state.depth.test_enabled = - regs.output_merger.depth_test_enable == 1 || regs.output_merger.depth_write_enable == 1; - state.depth.test_func = regs.output_merger.depth_test_enable == 1 - ? PicaToGL::CompareFunc(regs.output_merger.depth_test_func) - : GL_ALWAYS; + state.depth.test_enabled = regs.framebuffer.output_merger.depth_test_enable == 1 || + regs.framebuffer.output_merger.depth_write_enable == 1; + state.depth.test_func = + regs.framebuffer.output_merger.depth_test_enable == 1 + ? PicaToGL::CompareFunc(regs.framebuffer.output_merger.depth_test_func) + : GL_ALWAYS; } void RasterizerOpenGL::SyncCombinerColor() { - auto combiner_color = PicaToGL::ColorRGBA8(Pica::g_state.regs.tev_combiner_buffer_color.raw); + auto combiner_color = + PicaToGL::ColorRGBA8(Pica::g_state.regs.texturing.tev_combiner_buffer_color.raw); if (combiner_color != uniform_block_data.data.tev_combiner_buffer_color) { uniform_block_data.data.tev_combiner_buffer_color = combiner_color; uniform_block_data.dirty = true; @@ -1271,7 +1282,7 @@ void RasterizerOpenGL::SyncCombinerColor() { } void RasterizerOpenGL::SyncTevConstColor(int stage_index, - const Pica::Regs::TevStageConfig& tev_stage) { + const Pica::TexturingRegs::TevStageConfig& tev_stage) { auto const_color = PicaToGL::ColorRGBA8(tev_stage.const_color); if (const_color != uniform_block_data.data.const_color[stage_index]) { uniform_block_data.data.const_color[stage_index] = const_color; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index cc3e4bed5..ecf737438 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -16,10 +16,13 @@ #include "common/hash.h" #include "common/vector_math.h" #include "core/hw/gpu.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" #include "video_core/pica_types.h" #include "video_core/rasterizer_interface.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_lighting.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_texturing.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" #include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_state.h" @@ -52,20 +55,20 @@ union PicaShaderConfig { const auto& regs = Pica::g_state.regs; - state.scissor_test_mode = regs.scissor_test.mode; + state.scissor_test_mode = regs.rasterizer.scissor_test.mode; - state.depthmap_enable = regs.depthmap_enable; + state.depthmap_enable = regs.rasterizer.depthmap_enable; - state.alpha_test_func = regs.output_merger.alpha_test.enable - ? regs.output_merger.alpha_test.func.Value() - : Pica::Regs::CompareFunc::Always; + state.alpha_test_func = regs.framebuffer.output_merger.alpha_test.enable + ? regs.framebuffer.output_merger.alpha_test.func.Value() + : Pica::FramebufferRegs::CompareFunc::Always; - state.texture0_type = regs.texture0.type; + state.texture0_type = regs.texturing.texture0.type; // Copy relevant tev stages fields. // We don't sync const_color here because of the high variance, it is a // shader uniform instead. - const auto& tev_stages = regs.GetTevStages(); + const auto& tev_stages = regs.texturing.GetTevStages(); DEBUG_ASSERT(state.tev_stages.size() == tev_stages.size()); for (size_t i = 0; i < tev_stages.size(); i++) { const auto& tev_stage = tev_stages[i]; @@ -75,16 +78,17 @@ union PicaShaderConfig { state.tev_stages[i].scales_raw = tev_stage.scales_raw; } - state.fog_mode = regs.fog_mode; - state.fog_flip = regs.fog_flip != 0; + state.fog_mode = regs.texturing.fog_mode; + state.fog_flip = regs.texturing.fog_flip != 0; - state.combiner_buffer_input = regs.tev_combiner_buffer_input.update_mask_rgb.Value() | - regs.tev_combiner_buffer_input.update_mask_a.Value() << 4; + state.combiner_buffer_input = + regs.texturing.tev_combiner_buffer_input.update_mask_rgb.Value() | + regs.texturing.tev_combiner_buffer_input.update_mask_a.Value() << 4; // Fragment lighting state.lighting.enable = !regs.lighting.disable; - state.lighting.src_num = regs.lighting.num_lights + 1; + state.lighting.src_num = regs.lighting.max_light_index + 1; for (unsigned light_index = 0; light_index < state.lighting.src_num; ++light_index) { unsigned num = regs.lighting.light_enable.GetNum(light_index); @@ -159,8 +163,8 @@ union PicaShaderConfig { u32 modifiers_raw; u32 ops_raw; u32 scales_raw; - explicit operator Pica::Regs::TevStageConfig() const noexcept { - Pica::Regs::TevStageConfig stage; + explicit operator Pica::TexturingRegs::TevStageConfig() const noexcept { + Pica::TexturingRegs::TevStageConfig stage; stage.sources_raw = sources_raw; stage.modifiers_raw = modifiers_raw; stage.ops_raw = ops_raw; @@ -171,14 +175,14 @@ union PicaShaderConfig { }; struct State { - Pica::Regs::CompareFunc alpha_test_func; - Pica::Regs::ScissorMode scissor_test_mode; - Pica::Regs::TextureConfig::TextureType texture0_type; + Pica::FramebufferRegs::CompareFunc alpha_test_func; + Pica::RasterizerRegs::ScissorMode scissor_test_mode; + Pica::TexturingRegs::TextureConfig::TextureType texture0_type; std::array<TevStageConfigRaw, 6> tev_stages; u8 combiner_buffer_input; - Pica::Regs::DepthBuffering depthmap_enable; - Pica::Regs::FogMode fog_mode; + Pica::RasterizerRegs::DepthBuffering depthmap_enable; + Pica::TexturingRegs::FogMode fog_mode; bool fog_flip; struct { @@ -191,18 +195,18 @@ union PicaShaderConfig { bool enable; unsigned src_num; - Pica::Regs::LightingBumpMode bump_mode; + Pica::LightingRegs::LightingBumpMode bump_mode; unsigned bump_selector; bool bump_renorm; bool clamp_highlights; - Pica::Regs::LightingConfig config; - Pica::Regs::LightingFresnelSelector fresnel_selector; + Pica::LightingRegs::LightingConfig config; + Pica::LightingRegs::LightingFresnelSelector fresnel_selector; struct { bool enable; bool abs_input; - Pica::Regs::LightingLutInput type; + Pica::LightingRegs::LightingLutInput type; float scale; } lut_d0, lut_d1, lut_fr, lut_rr, lut_rg, lut_rb; } lighting; @@ -251,7 +255,7 @@ public: private: struct SamplerInfo { - using TextureConfig = Pica::Regs::TextureConfig; + using TextureConfig = Pica::TexturingRegs::TextureConfig; OGLSampler sampler; @@ -398,7 +402,7 @@ private: void SyncCombinerColor(); /// Syncs the TEV constant color to match the PICA register - void SyncTevConstColor(int tev_index, const Pica::Regs::TevStageConfig& tev_stage); + void SyncTevConstColor(int tev_index, const Pica::TexturingRegs::TevStageConfig& tev_stage); /// Syncs the lighting global ambient color to match the PICA register void SyncGlobalAmbient(); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 1e7eedecb..456443e86 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -17,10 +17,11 @@ #include "common/vector_math.h" #include "core/frontend/emu_window.h" #include "core/memory.h" -#include "video_core/debug_utils/debug_utils.h" +#include "core/settings.h" #include "video_core/pica_state.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" #include "video_core/renderer_opengl/gl_state.h" +#include "video_core/texture/texture_decode.h" #include "video_core/utils.h" #include "video_core/video_core.h" @@ -339,17 +340,16 @@ CachedSurface* RasterizerCacheOpenGL::GetSurface(const CachedSurface& params, bo std::vector<Math::Vec4<u8>> tex_buffer(params.width * params.height); - Pica::DebugUtils::TextureInfo tex_info; + Pica::Texture::TextureInfo tex_info; tex_info.width = params.width; tex_info.height = params.height; - tex_info.stride = - params.width * CachedSurface::GetFormatBpp(params.pixel_format) / 8; - tex_info.format = (Pica::Regs::TextureFormat)params.pixel_format; + tex_info.format = (Pica::TexturingRegs::TextureFormat)params.pixel_format; + tex_info.SetDefaultStride(); tex_info.physical_address = params.addr; for (unsigned y = 0; y < params.height; ++y) { for (unsigned x = 0; x < params.width; ++x) { - tex_buffer[x + params.width * y] = Pica::DebugUtils::LookupTexture( + tex_buffer[x + params.width * y] = Pica::Texture::LookupTexture( texture_src_data, x, params.height - 1 - y, tex_info); } } @@ -511,9 +511,10 @@ CachedSurface* RasterizerCacheOpenGL::GetSurfaceRect(const CachedSurface& params } CachedSurface* RasterizerCacheOpenGL::GetTextureSurface( - const Pica::Regs::FullTextureConfig& config) { - Pica::DebugUtils::TextureInfo info = - Pica::DebugUtils::TextureInfo::FromPicaRegister(config.config, config.format); + const Pica::TexturingRegs::FullTextureConfig& config) { + + Pica::Texture::TextureInfo info = + Pica::Texture::TextureInfo::FromPicaRegister(config.config, config.format); CachedSurface params; params.addr = info.physical_address; @@ -525,7 +526,9 @@ CachedSurface* RasterizerCacheOpenGL::GetTextureSurface( } std::tuple<CachedSurface*, CachedSurface*, MathUtil::Rectangle<int>> -RasterizerCacheOpenGL::GetFramebufferSurfaces(const Pica::Regs::FramebufferConfig& config) { +RasterizerCacheOpenGL::GetFramebufferSurfaces( + const Pica::FramebufferRegs::FramebufferConfig& config) { + const auto& regs = Pica::g_state.regs; // Make sur that framebuffers don't overlap if both color and depth are being used @@ -537,11 +540,12 @@ RasterizerCacheOpenGL::GetFramebufferSurfaces(const Pica::Regs::FramebufferConfi config.GetColorBufferPhysicalAddress(), fb_area * GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(config.color_format.Value())), config.GetDepthBufferPhysicalAddress(), - fb_area * Pica::Regs::BytesPerDepthPixel(config.depth_format)); + fb_area * Pica::FramebufferRegs::BytesPerDepthPixel(config.depth_format)); bool using_color_fb = config.GetColorBufferPhysicalAddress() != 0; - bool using_depth_fb = config.GetDepthBufferPhysicalAddress() != 0 && - (regs.output_merger.depth_test_enable || - regs.output_merger.depth_write_enable || !framebuffers_overlap); + bool using_depth_fb = + config.GetDepthBufferPhysicalAddress() != 0 && + (regs.framebuffer.output_merger.depth_test_enable || + regs.framebuffer.output_merger.depth_write_enable || !framebuffers_overlap); if (framebuffers_overlap && using_color_fb && using_depth_fb) { LOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer memory regions overlap; " diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index f57fdb3cc..aea20c693 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -21,7 +21,8 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "core/hw/gpu.h" -#include "video_core/pica.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_texturing.h" #include "video_core/renderer_opengl/gl_resource_manager.h" namespace MathUtil { @@ -96,15 +97,15 @@ struct CachedSurface { return bpp_table[(unsigned int)format]; } - static PixelFormat PixelFormatFromTextureFormat(Pica::Regs::TextureFormat format) { + static PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format) { return ((unsigned int)format < 14) ? (PixelFormat)format : PixelFormat::Invalid; } - static PixelFormat PixelFormatFromColorFormat(Pica::Regs::ColorFormat format) { + static PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format) { return ((unsigned int)format < 5) ? (PixelFormat)format : PixelFormat::Invalid; } - static PixelFormat PixelFormatFromDepthFormat(Pica::Regs::DepthFormat format) { + static PixelFormat PixelFormatFromDepthFormat(Pica::FramebufferRegs::DepthFormat format) { return ((unsigned int)format < 4) ? (PixelFormat)((unsigned int)format + 14) : PixelFormat::Invalid; } @@ -212,12 +213,12 @@ public: bool load_if_create, MathUtil::Rectangle<int>& out_rect); /// Gets a surface based on the texture configuration - CachedSurface* GetTextureSurface(const Pica::Regs::FullTextureConfig& config); + CachedSurface* GetTextureSurface(const Pica::TexturingRegs::FullTextureConfig& config); /// Gets the color and depth surfaces and rect (resolution scaled) based on the framebuffer /// configuration std::tuple<CachedSurface*, CachedSurface*, MathUtil::Rectangle<int>> GetFramebufferSurfaces( - const Pica::Regs::FramebufferConfig& config); + const Pica::FramebufferRegs::FramebufferConfig& config); /// Attempt to get a surface that exactly matches the fill region and format CachedSurface* TryGetFillSurface(const GPU::Regs::MemoryFillConfig& config); diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 4c4f98ac9..7abdeba05 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -7,13 +7,19 @@ #include "common/assert.h" #include "common/bit_field.h" #include "common/logging/log.h" -#include "video_core/pica.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_lighting.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_texturing.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_gen.h" #include "video_core/renderer_opengl/gl_shader_util.h" -using Pica::Regs; -using TevStageConfig = Regs::TevStageConfig; +using Pica::FramebufferRegs; +using Pica::LightingRegs; +using Pica::RasterizerRegs; +using Pica::TexturingRegs; +using TevStageConfig = TexturingRegs::TevStageConfig; namespace GLShader { @@ -46,10 +52,10 @@ static void AppendSource(std::string& out, const PicaShaderConfig& config, case Source::Texture0: // Only unit 0 respects the texturing type (according to 3DBrew) switch (state.texture0_type) { - case Pica::Regs::TextureConfig::Texture2D: + case TexturingRegs::TextureConfig::Texture2D: out += "texture(tex[0], texcoord[0])"; break; - case Pica::Regs::TextureConfig::Projection2D: + case TexturingRegs::TextureConfig::Projection2D: out += "textureProj(tex[0], vec3(texcoord[0], texcoord0_w))"; break; default: @@ -276,8 +282,8 @@ static void AppendAlphaCombiner(std::string& out, TevStageConfig::Operation oper } /// Writes the if-statement condition used to evaluate alpha testing -static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) { - using CompareFunc = Regs::CompareFunc; +static void AppendAlphaTestCondition(std::string& out, FramebufferRegs::CompareFunc func) { + using CompareFunc = FramebufferRegs::CompareFunc; switch (func) { case CompareFunc::Never: out += "true"; @@ -307,7 +313,7 @@ static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) { /// Writes the code to emulate the specified TEV stage static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsigned index) { const auto stage = - static_cast<const Pica::Regs::TevStageConfig>(config.state.tev_stages[index]); + static_cast<const TexturingRegs::TevStageConfig>(config.state.tev_stages[index]); if (!IsPassThroughTevStage(stage)) { std::string index_name = std::to_string(index); @@ -364,7 +370,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { "vec3 refl_value = vec3(0.0);\n"; // Compute fragment normals - if (lighting.bump_mode == Pica::Regs::LightingBumpMode::NormalMap) { + 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); @@ -378,7 +384,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { "(1.0 - (surface_normal.x*surface_normal.x + surface_normal.y*surface_normal.y))"; out += "surface_normal.z = sqrt(max(" + val + ", 0.0));\n"; } - } else if (lighting.bump_mode == Pica::Regs::LightingBumpMode::TangentMap) { + } else if (lighting.bump_mode == LightingRegs::LightingBumpMode::TangentMap) { // Bump mapping is enabled using a tangent map LOG_CRITICAL(HW_GPU, "unimplemented bump mapping mode (tangent mapping)"); UNIMPLEMENTED(); @@ -392,23 +398,24 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { out += "vec3 normal = normalize(quaternion_rotate(normquat, surface_normal));\n"; // Gets the index into the specified lookup table for specular lighting - auto GetLutIndex = [&lighting](unsigned light_num, Regs::LightingLutInput input, bool abs) { + auto GetLutIndex = [&lighting](unsigned light_num, LightingRegs::LightingLutInput input, + bool abs) { const std::string half_angle = "normalize(normalize(view) + light_vector)"; std::string index; switch (input) { - case Regs::LightingLutInput::NH: + case LightingRegs::LightingLutInput::NH: index = "dot(normal, " + half_angle + ")"; break; - case Regs::LightingLutInput::VH: + case LightingRegs::LightingLutInput::VH: index = std::string("dot(normalize(view), " + half_angle + ")"); break; - case Regs::LightingLutInput::NV: + case LightingRegs::LightingLutInput::NV: index = std::string("dot(normal, normalize(view))"); break; - case Regs::LightingLutInput::LN: + case LightingRegs::LightingLutInput::LN: index = std::string("dot(light_vector, normal)"); break; @@ -432,7 +439,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { }; // Gets the lighting lookup table value given the specified sampler and index - auto GetLutValue = [](Regs::LightingSampler sampler, std::string lut_index) { + auto GetLutValue = [](LightingRegs::LightingSampler sampler, std::string lut_index) { return std::string("texture(lut[" + std::to_string((unsigned)sampler / 4) + "], " + lut_index + ")[" + std::to_string((unsigned)sampler & 3) + "]"); }; @@ -461,8 +468,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { light_src + ".position) + " + light_src + ".dist_atten_bias)"; index = "(OFFSET_256 + SCALE_256 * clamp(" + index + ", 0.0, 1.0))"; const unsigned lut_num = - ((unsigned)Regs::LightingSampler::DistanceAttenuation + light_config.num); - dist_atten = GetLutValue((Regs::LightingSampler)lut_num, index); + ((unsigned)LightingRegs::LightingSampler::DistanceAttenuation + light_config.num); + dist_atten = GetLutValue((LightingRegs::LightingSampler)lut_num, index); } // If enabled, clamp specular component if lighting result is negative @@ -472,24 +479,24 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { // Specular 0 component std::string d0_lut_value = "1.0"; if (lighting.lut_d0.enable && - Pica::Regs::IsLightingSamplerSupported(lighting.config, - Pica::Regs::LightingSampler::Distribution0)) { + LightingRegs::IsLightingSamplerSupported( + lighting.config, LightingRegs::LightingSampler::Distribution0)) { // Lookup specular "distribution 0" LUT value std::string index = GetLutIndex(light_config.num, lighting.lut_d0.type, lighting.lut_d0.abs_input); d0_lut_value = "(" + std::to_string(lighting.lut_d0.scale) + " * " + - GetLutValue(Regs::LightingSampler::Distribution0, index) + ")"; + GetLutValue(LightingRegs::LightingSampler::Distribution0, index) + ")"; } std::string specular_0 = "(" + d0_lut_value + " * " + light_src + ".specular_0)"; // If enabled, lookup ReflectRed value, otherwise, 1.0 is used if (lighting.lut_rr.enable && - Pica::Regs::IsLightingSamplerSupported(lighting.config, - Pica::Regs::LightingSampler::ReflectRed)) { + LightingRegs::IsLightingSamplerSupported(lighting.config, + LightingRegs::LightingSampler::ReflectRed)) { std::string index = GetLutIndex(light_config.num, lighting.lut_rr.type, lighting.lut_rr.abs_input); std::string value = "(" + std::to_string(lighting.lut_rr.scale) + " * " + - GetLutValue(Regs::LightingSampler::ReflectRed, index) + ")"; + GetLutValue(LightingRegs::LightingSampler::ReflectRed, index) + ")"; out += "refl_value.r = " + value + ";\n"; } else { out += "refl_value.r = 1.0;\n"; @@ -497,12 +504,13 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { // If enabled, lookup ReflectGreen value, otherwise, ReflectRed value is used if (lighting.lut_rg.enable && - Pica::Regs::IsLightingSamplerSupported(lighting.config, - Pica::Regs::LightingSampler::ReflectGreen)) { + LightingRegs::IsLightingSamplerSupported(lighting.config, + LightingRegs::LightingSampler::ReflectGreen)) { std::string index = GetLutIndex(light_config.num, lighting.lut_rg.type, lighting.lut_rg.abs_input); std::string value = "(" + std::to_string(lighting.lut_rg.scale) + " * " + - GetLutValue(Regs::LightingSampler::ReflectGreen, index) + ")"; + GetLutValue(LightingRegs::LightingSampler::ReflectGreen, index) + + ")"; out += "refl_value.g = " + value + ";\n"; } else { out += "refl_value.g = refl_value.r;\n"; @@ -510,12 +518,13 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { // If enabled, lookup ReflectBlue value, otherwise, ReflectRed value is used if (lighting.lut_rb.enable && - Pica::Regs::IsLightingSamplerSupported(lighting.config, - Pica::Regs::LightingSampler::ReflectBlue)) { + LightingRegs::IsLightingSamplerSupported(lighting.config, + LightingRegs::LightingSampler::ReflectBlue)) { std::string index = GetLutIndex(light_config.num, lighting.lut_rb.type, lighting.lut_rb.abs_input); std::string value = "(" + std::to_string(lighting.lut_rb.scale) + " * " + - GetLutValue(Regs::LightingSampler::ReflectBlue, index) + ")"; + GetLutValue(LightingRegs::LightingSampler::ReflectBlue, index) + + ")"; out += "refl_value.b = " + value + ";\n"; } else { out += "refl_value.b = refl_value.r;\n"; @@ -524,35 +533,39 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { // Specular 1 component std::string d1_lut_value = "1.0"; if (lighting.lut_d1.enable && - Pica::Regs::IsLightingSamplerSupported(lighting.config, - Pica::Regs::LightingSampler::Distribution1)) { + LightingRegs::IsLightingSamplerSupported( + lighting.config, LightingRegs::LightingSampler::Distribution1)) { // Lookup specular "distribution 1" LUT value std::string index = GetLutIndex(light_config.num, lighting.lut_d1.type, lighting.lut_d1.abs_input); d1_lut_value = "(" + std::to_string(lighting.lut_d1.scale) + " * " + - GetLutValue(Regs::LightingSampler::Distribution1, index) + ")"; + GetLutValue(LightingRegs::LightingSampler::Distribution1, index) + ")"; } std::string specular_1 = "(" + d1_lut_value + " * refl_value * " + light_src + ".specular_1)"; // Fresnel - if (lighting.lut_fr.enable && Pica::Regs::IsLightingSamplerSupported( - lighting.config, Pica::Regs::LightingSampler::Fresnel)) { + if (lighting.lut_fr.enable && + LightingRegs::IsLightingSamplerSupported(lighting.config, + LightingRegs::LightingSampler::Fresnel)) { // Lookup fresnel LUT value std::string index = GetLutIndex(light_config.num, lighting.lut_fr.type, lighting.lut_fr.abs_input); std::string value = "(" + std::to_string(lighting.lut_fr.scale) + " * " + - GetLutValue(Regs::LightingSampler::Fresnel, index) + ")"; + GetLutValue(LightingRegs::LightingSampler::Fresnel, index) + ")"; // Enabled for difffuse lighting alpha component - if (lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::PrimaryAlpha || - lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::Both) + if (lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::PrimaryAlpha || + lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { out += "diffuse_sum.a *= " + value + ";\n"; + } // Enabled for the specular lighting alpha component - if (lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::SecondaryAlpha || - lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::Both) + if (lighting.fresnel_selector == + LightingRegs::LightingFresnelSelector::SecondaryAlpha || + lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { out += "specular_sum.a *= " + value + ";\n"; + } } // Compute primary fragment color (diffuse lighting) function @@ -633,16 +646,16 @@ vec4 secondary_fragment_color = vec4(0.0); )"; // Do not do any sort of processing if it's obvious we're not going to pass the alpha test - if (state.alpha_test_func == Regs::CompareFunc::Never) { + if (state.alpha_test_func == FramebufferRegs::CompareFunc::Never) { out += "discard; }"; return out; } // Append the scissor test - if (state.scissor_test_mode != Regs::ScissorMode::Disabled) { + if (state.scissor_test_mode != RasterizerRegs::ScissorMode::Disabled) { out += "if ("; // Negate the condition if we have to keep only the pixels outside the scissor box - if (state.scissor_test_mode == Regs::ScissorMode::Include) + if (state.scissor_test_mode == RasterizerRegs::ScissorMode::Include) out += "!"; out += "(gl_FragCoord.x >= scissor_x1 && " "gl_FragCoord.y >= scissor_y1 && " @@ -652,7 +665,7 @@ vec4 secondary_fragment_color = vec4(0.0); out += "float z_over_w = 1.0 - gl_FragCoord.z * 2.0;\n"; out += "float depth = z_over_w * depth_scale + depth_offset;\n"; - if (state.depthmap_enable == Pica::Regs::DepthBuffering::WBuffering) { + if (state.depthmap_enable == RasterizerRegs::DepthBuffering::WBuffering) { out += "depth /= gl_FragCoord.w;\n"; } @@ -666,14 +679,14 @@ vec4 secondary_fragment_color = vec4(0.0); for (size_t index = 0; index < state.tev_stages.size(); ++index) WriteTevStage(out, config, (unsigned)index); - if (state.alpha_test_func != Regs::CompareFunc::Always) { + if (state.alpha_test_func != FramebufferRegs::CompareFunc::Always) { out += "if ("; AppendAlphaTestCondition(out, state.alpha_test_func); out += ") discard;\n"; } // Append fog combiner - if (state.fog_mode == Regs::FogMode::Fog) { + if (state.fog_mode == TexturingRegs::FogMode::Fog) { // Get index into fog LUT if (state.fog_flip) { out += "float fog_index = (1.0 - depth) * 128.0;\n"; diff --git a/src/video_core/renderer_opengl/pica_to_gl.h b/src/video_core/renderer_opengl/pica_to_gl.h index cc49867c8..93d7b0b71 100644 --- a/src/video_core/renderer_opengl/pica_to_gl.h +++ b/src/video_core/renderer_opengl/pica_to_gl.h @@ -12,7 +12,9 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/logging/log.h" -#include "video_core/pica.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_lighting.h" +#include "video_core/regs_texturing.h" using GLvec2 = std::array<GLfloat, 2>; using GLvec3 = std::array<GLfloat, 3>; @@ -20,7 +22,7 @@ using GLvec4 = std::array<GLfloat, 4>; namespace PicaToGL { -inline GLenum TextureFilterMode(Pica::Regs::TextureConfig::TextureFilter mode) { +inline GLenum TextureFilterMode(Pica::TexturingRegs::TextureConfig::TextureFilter mode) { static const GLenum filter_mode_table[] = { GL_NEAREST, // TextureFilter::Nearest GL_LINEAR, // TextureFilter::Linear @@ -47,7 +49,7 @@ inline GLenum TextureFilterMode(Pica::Regs::TextureConfig::TextureFilter mode) { return gl_mode; } -inline GLenum WrapMode(Pica::Regs::TextureConfig::WrapMode mode) { +inline GLenum WrapMode(Pica::TexturingRegs::TextureConfig::WrapMode mode) { static const GLenum wrap_mode_table[] = { GL_CLAMP_TO_EDGE, // WrapMode::ClampToEdge GL_CLAMP_TO_BORDER, // WrapMode::ClampToBorder @@ -76,7 +78,7 @@ inline GLenum WrapMode(Pica::Regs::TextureConfig::WrapMode mode) { return gl_mode; } -inline GLenum BlendEquation(Pica::Regs::BlendEquation equation) { +inline GLenum BlendEquation(Pica::FramebufferRegs::BlendEquation equation) { static const GLenum blend_equation_table[] = { GL_FUNC_ADD, // BlendEquation::Add GL_FUNC_SUBTRACT, // BlendEquation::Subtract @@ -96,7 +98,7 @@ inline GLenum BlendEquation(Pica::Regs::BlendEquation equation) { return blend_equation_table[(unsigned)equation]; } -inline GLenum BlendFunc(Pica::Regs::BlendFactor factor) { +inline GLenum BlendFunc(Pica::FramebufferRegs::BlendFactor factor) { static const GLenum blend_func_table[] = { GL_ZERO, // BlendFactor::Zero GL_ONE, // BlendFactor::One @@ -126,7 +128,7 @@ inline GLenum BlendFunc(Pica::Regs::BlendFactor factor) { return blend_func_table[(unsigned)factor]; } -inline GLenum LogicOp(Pica::Regs::LogicOp op) { +inline GLenum LogicOp(Pica::FramebufferRegs::LogicOp op) { static const GLenum logic_op_table[] = { GL_CLEAR, // Clear GL_AND, // And @@ -157,7 +159,7 @@ inline GLenum LogicOp(Pica::Regs::LogicOp op) { return logic_op_table[(unsigned)op]; } -inline GLenum CompareFunc(Pica::Regs::CompareFunc func) { +inline GLenum CompareFunc(Pica::FramebufferRegs::CompareFunc func) { static const GLenum compare_func_table[] = { GL_NEVER, // CompareFunc::Never GL_ALWAYS, // CompareFunc::Always @@ -180,7 +182,7 @@ inline GLenum CompareFunc(Pica::Regs::CompareFunc func) { return compare_func_table[(unsigned)func]; } -inline GLenum StencilOp(Pica::Regs::StencilAction action) { +inline GLenum StencilOp(Pica::FramebufferRegs::StencilAction action) { static const GLenum stencil_op_table[] = { GL_KEEP, // StencilAction::Keep GL_ZERO, // StencilAction::Zero @@ -210,7 +212,7 @@ inline GLvec4 ColorRGBA8(const u32 color) { }}; } -inline std::array<GLfloat, 3> LightColor(const Pica::Regs::LightColor& color) { +inline std::array<GLfloat, 3> LightColor(const Pica::LightingRegs::LightColor& color) { return {{ color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, }}; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 2aa90e5c1..e19375466 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -10,8 +10,8 @@ #include "common/assert.h" #include "common/bit_field.h" #include "common/logging/log.h" -#include "common/profiler_reporting.h" -#include "common/synchronized_wrapper.h" +#include "core/core.h" +#include "core/core_timing.h" #include "core/frontend/emu_window.h" #include "core/hw/gpu.h" #include "core/hw/hw.h" @@ -145,21 +145,16 @@ void RendererOpenGL::SwapBuffers() { DrawScreens(); - auto& profiler = Common::Profiling::GetProfilingManager(); - profiler.FinishFrame(); - { - auto aggregator = Common::Profiling::GetTimingResultsAggregator(); - aggregator->AddFrame(profiler.GetPreviousFrameResults()); - } + Core::System::GetInstance().perf_stats.EndSystemFrame(); // Swap buffers render_window->PollEvents(); render_window->SwapBuffers(); - prev_state.Apply(); - - profiler.BeginFrame(); + Core::System::GetInstance().frame_limiter.DoFrameLimiting(CoreTiming::GetGlobalTimeUs()); + Core::System::GetInstance().perf_stats.BeginSystemFrame(); + prev_state.Apply(); RefreshRasterizerSetting(); if (Pica::g_debug_context && Pica::g_debug_context->recorder) { diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp index 2da50bd62..67ed19ba8 100644 --- a/src/video_core/shader/shader.cpp +++ b/src/video_core/shader/shader.cpp @@ -4,10 +4,12 @@ #include <cmath> #include <cstring> +#include "common/bit_set.h" #include "common/logging/log.h" #include "common/microprofile.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_shader.h" #include "video_core/shader/shader.h" #include "video_core/shader/shader_interpreter.h" #ifdef ARCHITECTURE_x86_64 @@ -19,38 +21,31 @@ namespace Pica { namespace Shader { -OutputVertex OutputVertex::FromRegisters(Math::Vec4<float24> output_regs[16], const Regs& regs, - u32 output_mask) { +OutputVertex OutputVertex::FromAttributeBuffer(const RasterizerRegs& regs, AttributeBuffer& input) { // Setup output data - OutputVertex ret; - // TODO(neobrain): Under some circumstances, up to 16 attributes may be output. We need to - // figure out what those circumstances are and enable the remaining outputs then. - unsigned index = 0; - for (unsigned i = 0; i < 7; ++i) { + union { + OutputVertex ret{}; + std::array<float24, 24> vertex_slots; + }; + static_assert(sizeof(vertex_slots) == sizeof(ret), "Struct and array have different sizes."); - if (index >= regs.vs_output_total) - break; + unsigned int num_attributes = regs.vs_output_total; + ASSERT(num_attributes <= 7); + for (unsigned int i = 0; i < num_attributes; ++i) { + const auto& output_register_map = regs.vs_output_attributes[i]; - if ((output_mask & (1 << i)) == 0) - continue; - - const auto& output_register_map = regs.vs_output_attributes[index]; - - u32 semantics[4] = {output_register_map.map_x, output_register_map.map_y, - output_register_map.map_z, output_register_map.map_w}; + RasterizerRegs::VSOutputAttributes::Semantic semantics[4] = { + output_register_map.map_x, output_register_map.map_y, output_register_map.map_z, + output_register_map.map_w}; for (unsigned comp = 0; comp < 4; ++comp) { - float24* out = ((float24*)&ret) + semantics[comp]; - if (semantics[comp] != Regs::VSOutputAttributes::INVALID) { - *out = output_regs[i][comp]; - } else { - // Zero output so that attributes which aren't output won't have denormals in them, - // which would slow us down later. - memset(out, 0, sizeof(*out)); + RasterizerRegs::VSOutputAttributes::Semantic semantic = semantics[comp]; + if (semantic < vertex_slots.size()) { + vertex_slots[semantic] = input.attr[i][comp]; + } else if (semantic != RasterizerRegs::VSOutputAttributes::INVALID) { + LOG_ERROR(HW_GPU, "Invalid/unknown semantic id: %u", (unsigned int)semantic); } } - - index++; } // The hardware takes the absolute and saturates vertex colors like this, *before* doing @@ -71,12 +66,20 @@ OutputVertex OutputVertex::FromRegisters(Math::Vec4<float24> output_regs[16], co return ret; } -void UnitState::LoadInputVertex(const InputVertex& input, int num_attributes) { - // Setup input register table - const auto& attribute_register_map = g_state.regs.vs.input_register_map; +void UnitState::LoadInput(const ShaderRegs& config, const AttributeBuffer& input) { + const unsigned max_attribute = config.max_input_attribute_index; - for (int i = 0; i < num_attributes; i++) - registers.input[attribute_register_map.GetRegisterForAttribute(i)] = input.attr[i]; + for (unsigned attr = 0; attr <= max_attribute; ++attr) { + unsigned reg = config.GetRegisterForAttribute(attr); + registers.input[reg] = input.attr[attr]; + } +} + +void UnitState::WriteOutput(const ShaderRegs& config, AttributeBuffer& output) { + unsigned int output_i = 0; + for (unsigned int reg : Common::BitSet<u32>(config.output_mask)) { + output.attr[output_i++] = registers.output[reg]; + } } MICROPROFILE_DEFINE(GPU_Shader, "GPU", "Shader", MP_RGB(50, 50, 240)); diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h index 44d9f76c3..38ea717ab 100644 --- a/src/video_core/shader/shader.h +++ b/src/video_core/shader/shader.h @@ -12,8 +12,9 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/vector_math.h" -#include "video_core/pica.h" #include "video_core/pica_types.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_shader.h" using nihstro::RegisterType; using nihstro::SourceRegister; @@ -23,14 +24,11 @@ namespace Pica { namespace Shader { -struct InputVertex { +struct AttributeBuffer { alignas(16) Math::Vec4<float24> attr[16]; }; struct OutputVertex { - OutputVertex() = default; - - // VS output attributes Math::Vec4<float24> pos; Math::Vec4<float24> quat; Math::Vec4<float24> color; @@ -42,43 +40,22 @@ struct OutputVertex { INSERT_PADDING_WORDS(1); Math::Vec2<float24> tc2; - // Padding for optimal alignment - INSERT_PADDING_WORDS(4); - - // Attributes used to store intermediate results - - // position after perspective divide - Math::Vec3<float24> screenpos; - INSERT_PADDING_WORDS(1); - - // Linear interpolation - // factor: 0=this, 1=vtx - void Lerp(float24 factor, const OutputVertex& vtx) { - pos = pos * factor + vtx.pos * (float24::FromFloat32(1) - factor); - - // TODO: Should perform perspective correct interpolation here... - tc0 = tc0 * factor + vtx.tc0 * (float24::FromFloat32(1) - factor); - tc1 = tc1 * factor + vtx.tc1 * (float24::FromFloat32(1) - factor); - tc2 = tc2 * factor + vtx.tc2 * (float24::FromFloat32(1) - factor); - - screenpos = screenpos * factor + vtx.screenpos * (float24::FromFloat32(1) - factor); - - color = color * factor + vtx.color * (float24::FromFloat32(1) - factor); - } - - // Linear interpolation - // factor: 0=v0, 1=v1 - static OutputVertex Lerp(float24 factor, const OutputVertex& v0, const OutputVertex& v1) { - OutputVertex ret = v0; - ret.Lerp(factor, v1); - return ret; - } - - static OutputVertex FromRegisters(Math::Vec4<float24> output_regs[16], const Regs& regs, - u32 output_mask); + static OutputVertex FromAttributeBuffer(const RasterizerRegs& regs, AttributeBuffer& output); }; +#define ASSERT_POS(var, pos) \ + static_assert(offsetof(OutputVertex, var) == pos * sizeof(float24), "Semantic at wrong " \ + "offset.") +ASSERT_POS(pos, RasterizerRegs::VSOutputAttributes::POSITION_X); +ASSERT_POS(quat, RasterizerRegs::VSOutputAttributes::QUATERNION_X); +ASSERT_POS(color, RasterizerRegs::VSOutputAttributes::COLOR_R); +ASSERT_POS(tc0, RasterizerRegs::VSOutputAttributes::TEXCOORD0_U); +ASSERT_POS(tc1, RasterizerRegs::VSOutputAttributes::TEXCOORD1_U); +ASSERT_POS(tc0_w, RasterizerRegs::VSOutputAttributes::TEXCOORD0_W); +ASSERT_POS(view, RasterizerRegs::VSOutputAttributes::VIEW_X); +ASSERT_POS(tc2, RasterizerRegs::VSOutputAttributes::TEXCOORD2_U); +#undef ASSERT_POS static_assert(std::is_pod<OutputVertex>::value, "Structure is not POD"); -static_assert(sizeof(OutputVertex) == 32 * sizeof(float), "OutputVertex has invalid size"); +static_assert(sizeof(OutputVertex) == 24 * sizeof(float), "OutputVertex has invalid size"); /** * This structure contains the state information that needs to be unique for a shader unit. The 3DS @@ -137,10 +114,12 @@ struct UnitState { /** * Loads the unit state with an input vertex. * - * @param input Input vertex into the shader - * @param num_attributes The number of vertex shader attributes to load + * @param config Shader configuration registers corresponding to the unit. + * @param input Attribute buffer to load into the input registers. */ - void LoadInputVertex(const InputVertex& input, int num_attributes); + void LoadInput(const ShaderRegs& config, const AttributeBuffer& input); + + void WriteOutput(const ShaderRegs& config, AttributeBuffer& output); }; struct ShaderSetup { diff --git a/src/video_core/shader/shader_interpreter.cpp b/src/video_core/shader/shader_interpreter.cpp index c0c89b857..f4d1c46c5 100644 --- a/src/video_core/shader/shader_interpreter.cpp +++ b/src/video_core/shader/shader_interpreter.cpp @@ -668,14 +668,14 @@ void InterpreterEngine::Run(const ShaderSetup& setup, UnitState& state) const { } DebugData<true> InterpreterEngine::ProduceDebugInfo(const ShaderSetup& setup, - const InputVertex& input, - int num_attributes) const { + const AttributeBuffer& input, + const ShaderRegs& config) const { UnitState state; DebugData<true> debug_data; // Setup input register table boost::fill(state.registers.input, Math::Vec4<float24>::AssignToAll(float24::Zero())); - state.LoadInputVertex(input, num_attributes); + state.LoadInput(config, input); RunInterpreter(setup, state, debug_data, setup.engine_data.entry_point); return debug_data; } diff --git a/src/video_core/shader/shader_interpreter.h b/src/video_core/shader/shader_interpreter.h index d6c0e2d8c..50fd7c69d 100644 --- a/src/video_core/shader/shader_interpreter.h +++ b/src/video_core/shader/shader_interpreter.h @@ -18,13 +18,13 @@ public: /** * Produce debug information based on the given shader and input vertex - * @param input Input vertex into the shader - * @param num_attributes The number of vertex shader attributes + * @param setup Shader engine state + * @param input Input vertex into the shader * @param config Configuration object for the shader pipeline * @return Debug information for this shader with regards to the given vertex */ - DebugData<true> ProduceDebugInfo(const ShaderSetup& setup, const InputVertex& input, - int num_attributes) const; + DebugData<true> ProduceDebugInfo(const ShaderSetup& setup, const AttributeBuffer& input, + const ShaderRegs& config) const; }; } // namespace diff --git a/src/video_core/shader/shader_jit_x64_compiler.cpp b/src/video_core/shader/shader_jit_x64_compiler.cpp index 49806e8c9..2dbc8b147 100644 --- a/src/video_core/shader/shader_jit_x64_compiler.cpp +++ b/src/video_core/shader/shader_jit_x64_compiler.cpp @@ -144,6 +144,8 @@ static const BitSet32 persistent_regs = BuildRegSet({ ADDROFFS_REG_0, ADDROFFS_REG_1, LOOPCOUNT_REG, COND0, COND1, // Constants ONE, NEGBIT, + // Loop variables + LOOPCOUNT, LOOPINC, }); /// Raw constant for the source register selector that indicates no swizzling is performed @@ -293,14 +295,22 @@ void JitShader::Compile_DestEnable(Instruction instr, Xmm src) { } void JitShader::Compile_SanitizedMul(Xmm src1, Xmm src2, Xmm scratch) { + // 0 * inf and inf * 0 in the PICA should return 0 instead of NaN. This can be implemented by + // checking for NaNs before and after the multiplication. If the multiplication result is NaN + // where neither source was, this NaN was generated by a 0 * inf multiplication, and so the + // result should be transformed to 0 to match PICA fp rules. + + // Set scratch to mask of (src1 != NaN and src2 != NaN) movaps(scratch, src1); cmpordps(scratch, src2); mulps(src1, src2); + // Set src2 to mask of (result == NaN) movaps(src2, src1); cmpunordps(src2, src2); + // Clear components where scratch != src2 (i.e. if result is NaN where neither source was NaN) xorps(scratch, src2); andps(src1, scratch); } @@ -587,7 +597,7 @@ void JitShader::Compile_RSQ(Instruction instr) { void JitShader::Compile_NOP(Instruction instr) {} void JitShader::Compile_END(Instruction instr) { - ABI_PopRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8); + ABI_PopRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8, 16); ret(); } @@ -839,7 +849,10 @@ void JitShader::Compile(const std::array<u32, 1024>* program_code_, FindReturnOffsets(); // The stack pointer is 8 modulo 16 at the entry of a procedure - ABI_PushRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8); + // We reserve 16 bytes and assign a dummy value to the first 8 bytes, to catch any potential + // return checks (see Compile_Return) that happen in shader main routine. + ABI_PushRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8, 16); + mov(qword[rsp + 8], 0xFFFFFFFFFFFFFFFFULL); mov(SETUP, ABI_PARAM1); mov(STATE, ABI_PARAM2); diff --git a/src/video_core/shader/shader_jit_x64_compiler.h b/src/video_core/shader/shader_jit_x64_compiler.h index 29e9875ea..f27675560 100644 --- a/src/video_core/shader/shader_jit_x64_compiler.h +++ b/src/video_core/shader/shader_jit_x64_compiler.h @@ -12,7 +12,6 @@ #include <xbyak.h> #include "common/bit_set.h" #include "common/common_types.h" -#include "common/x64/emitter.h" #include "video_core/shader/shader.h" using nihstro::Instruction; @@ -94,7 +93,8 @@ private: /** * Assertion evaluated at compile-time, but only triggered if executed at runtime. - * @param msg Message to be logged if the assertion fails. + * @param condition Condition to be evaluated. + * @param msg Message to be logged if the assertion fails. */ void Compile_Assert(bool condition, const char* msg); diff --git a/src/video_core/clipper.cpp b/src/video_core/swrasterizer/clipper.cpp index 05b5cea73..2d80822d9 100644 --- a/src/video_core/clipper.cpp +++ b/src/video_core/swrasterizer/clipper.cpp @@ -11,12 +11,13 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "common/vector_math.h" -#include "video_core/clipper.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" #include "video_core/pica_types.h" -#include "video_core/rasterizer.h" #include "video_core/shader/shader.h" +#include "video_core/swrasterizer/clipper.h" +#include "video_core/swrasterizer/rasterizer.h" + +using Pica::Rasterizer::Vertex; namespace Pica { @@ -29,20 +30,20 @@ public: float24::FromFloat32(0), float24::FromFloat32(0))) : coeffs(coeffs), bias(bias) {} - bool IsInside(const OutputVertex& vertex) const { + bool IsInside(const Vertex& vertex) const { return Math::Dot(vertex.pos + bias, coeffs) <= float24::FromFloat32(0); } - bool IsOutSide(const OutputVertex& vertex) const { + bool IsOutSide(const Vertex& vertex) const { return !IsInside(vertex); } - OutputVertex GetIntersection(const OutputVertex& v0, const OutputVertex& v1) const { + Vertex GetIntersection(const Vertex& v0, const Vertex& v1) const { float24 dp = Math::Dot(v0.pos + bias, coeffs); float24 dp_prev = Math::Dot(v1.pos + bias, coeffs); float24 factor = dp_prev / (dp_prev - dp); - return OutputVertex::Lerp(factor, v0, v1); + return Vertex::Lerp(factor, v0, v1); } private: @@ -51,7 +52,7 @@ private: Math::Vec4<float24> bias; }; -static void InitScreenCoordinates(OutputVertex& vtx) { +static void InitScreenCoordinates(Vertex& vtx) { struct { float24 halfsize_x; float24 offset_x; @@ -62,10 +63,10 @@ static void InitScreenCoordinates(OutputVertex& vtx) { } viewport; const auto& regs = g_state.regs; - viewport.halfsize_x = float24::FromRaw(regs.viewport_size_x); - viewport.halfsize_y = float24::FromRaw(regs.viewport_size_y); - viewport.offset_x = float24::FromFloat32(static_cast<float>(regs.viewport_corner.x)); - viewport.offset_y = float24::FromFloat32(static_cast<float>(regs.viewport_corner.y)); + viewport.halfsize_x = float24::FromRaw(regs.rasterizer.viewport_size_x); + viewport.halfsize_y = float24::FromRaw(regs.rasterizer.viewport_size_y); + viewport.offset_x = float24::FromFloat32(static_cast<float>(regs.rasterizer.viewport_corner.x)); + viewport.offset_y = float24::FromFloat32(static_cast<float>(regs.rasterizer.viewport_corner.y)); float24 inv_w = float24::FromFloat32(1.f) / vtx.pos.w; vtx.color *= inv_w; @@ -91,8 +92,8 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu // introduces at most 1 new vertex to the polygon. Since we start with a triangle and have a // fixed 6 clipping planes, the maximum number of vertices of the clipped polygon is 3 + 6 = 9. static const size_t MAX_VERTICES = 9; - static_vector<OutputVertex, MAX_VERTICES> buffer_a = {v0, v1, v2}; - static_vector<OutputVertex, MAX_VERTICES> buffer_b; + static_vector<Vertex, MAX_VERTICES> buffer_a = {v0, v1, v2}; + static_vector<Vertex, MAX_VERTICES> buffer_b; auto* output_list = &buffer_a; auto* input_list = &buffer_b; @@ -123,7 +124,7 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu std::swap(input_list, output_list); output_list->clear(); - const OutputVertex* reference_vertex = &input_list->back(); + const Vertex* reference_vertex = &input_list->back(); for (const auto& vertex : *input_list) { // NOTE: This algorithm changes vertex order in some cases! @@ -148,9 +149,9 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu InitScreenCoordinates((*output_list)[1]); for (size_t i = 0; i < output_list->size() - 2; i++) { - OutputVertex& vtx0 = (*output_list)[0]; - OutputVertex& vtx1 = (*output_list)[i + 1]; - OutputVertex& vtx2 = (*output_list)[i + 2]; + Vertex& vtx0 = (*output_list)[0]; + Vertex& vtx1 = (*output_list)[i + 1]; + Vertex& vtx2 = (*output_list)[i + 2]; InitScreenCoordinates(vtx2); diff --git a/src/video_core/clipper.h b/src/video_core/swrasterizer/clipper.h index b51af0af9..b51af0af9 100644 --- a/src/video_core/clipper.h +++ b/src/video_core/swrasterizer/clipper.h diff --git a/src/video_core/swrasterizer/framebuffer.cpp b/src/video_core/swrasterizer/framebuffer.cpp new file mode 100644 index 000000000..7de3aac75 --- /dev/null +++ b/src/video_core/swrasterizer/framebuffer.cpp @@ -0,0 +1,358 @@ +// Copyright 2017 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/color.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/vector_math.h" +#include "core/hw/gpu.h" +#include "core/memory.h" +#include "video_core/pica_state.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/swrasterizer/framebuffer.h" +#include "video_core/utils.h" + +namespace Pica { +namespace Rasterizer { + +void DrawPixel(int x, int y, const Math::Vec4<u8>& color) { + const auto& framebuffer = g_state.regs.framebuffer.framebuffer; + const PAddr addr = framebuffer.GetColorBufferPhysicalAddress(); + + // Similarly to textures, the render framebuffer is laid out from bottom to top, too. + // NOTE: The framebuffer height register contains the actual FB height minus one. + y = framebuffer.height - y; + + const u32 coarse_y = y & ~7; + u32 bytes_per_pixel = + GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(framebuffer.color_format.Value())); + u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + + coarse_y * framebuffer.width * bytes_per_pixel; + u8* dst_pixel = Memory::GetPhysicalPointer(addr) + dst_offset; + + switch (framebuffer.color_format) { + case FramebufferRegs::ColorFormat::RGBA8: + Color::EncodeRGBA8(color, dst_pixel); + break; + + case FramebufferRegs::ColorFormat::RGB8: + Color::EncodeRGB8(color, dst_pixel); + break; + + case FramebufferRegs::ColorFormat::RGB5A1: + Color::EncodeRGB5A1(color, dst_pixel); + break; + + case FramebufferRegs::ColorFormat::RGB565: + Color::EncodeRGB565(color, dst_pixel); + break; + + case FramebufferRegs::ColorFormat::RGBA4: + Color::EncodeRGBA4(color, dst_pixel); + break; + + default: + LOG_CRITICAL(Render_Software, "Unknown framebuffer color format %x", + framebuffer.color_format.Value()); + UNIMPLEMENTED(); + } +} + +const Math::Vec4<u8> GetPixel(int x, int y) { + const auto& framebuffer = g_state.regs.framebuffer.framebuffer; + const PAddr addr = framebuffer.GetColorBufferPhysicalAddress(); + + y = framebuffer.height - y; + + const u32 coarse_y = y & ~7; + u32 bytes_per_pixel = + GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(framebuffer.color_format.Value())); + u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + + coarse_y * framebuffer.width * bytes_per_pixel; + u8* src_pixel = Memory::GetPhysicalPointer(addr) + src_offset; + + switch (framebuffer.color_format) { + case FramebufferRegs::ColorFormat::RGBA8: + return Color::DecodeRGBA8(src_pixel); + + case FramebufferRegs::ColorFormat::RGB8: + return Color::DecodeRGB8(src_pixel); + + case FramebufferRegs::ColorFormat::RGB5A1: + return Color::DecodeRGB5A1(src_pixel); + + case FramebufferRegs::ColorFormat::RGB565: + return Color::DecodeRGB565(src_pixel); + + case FramebufferRegs::ColorFormat::RGBA4: + return Color::DecodeRGBA4(src_pixel); + + default: + LOG_CRITICAL(Render_Software, "Unknown framebuffer color format %x", + framebuffer.color_format.Value()); + UNIMPLEMENTED(); + } + + return {0, 0, 0, 0}; +} + +u32 GetDepth(int x, int y) { + const auto& framebuffer = g_state.regs.framebuffer.framebuffer; + const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress(); + u8* depth_buffer = Memory::GetPhysicalPointer(addr); + + y = framebuffer.height - y; + + const u32 coarse_y = y & ~7; + u32 bytes_per_pixel = FramebufferRegs::BytesPerDepthPixel(framebuffer.depth_format); + u32 stride = framebuffer.width * bytes_per_pixel; + + u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; + u8* src_pixel = depth_buffer + src_offset; + + switch (framebuffer.depth_format) { + case FramebufferRegs::DepthFormat::D16: + return Color::DecodeD16(src_pixel); + case FramebufferRegs::DepthFormat::D24: + return Color::DecodeD24(src_pixel); + case FramebufferRegs::DepthFormat::D24S8: + return Color::DecodeD24S8(src_pixel).x; + default: + LOG_CRITICAL(HW_GPU, "Unimplemented depth format %u", framebuffer.depth_format); + UNIMPLEMENTED(); + return 0; + } +} + +u8 GetStencil(int x, int y) { + const auto& framebuffer = g_state.regs.framebuffer.framebuffer; + const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress(); + u8* depth_buffer = Memory::GetPhysicalPointer(addr); + + y = framebuffer.height - y; + + const u32 coarse_y = y & ~7; + u32 bytes_per_pixel = Pica::FramebufferRegs::BytesPerDepthPixel(framebuffer.depth_format); + u32 stride = framebuffer.width * bytes_per_pixel; + + u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; + u8* src_pixel = depth_buffer + src_offset; + + switch (framebuffer.depth_format) { + case FramebufferRegs::DepthFormat::D24S8: + return Color::DecodeD24S8(src_pixel).y; + + default: + LOG_WARNING( + HW_GPU, + "GetStencil called for function which doesn't have a stencil component (format %u)", + framebuffer.depth_format); + return 0; + } +} + +void SetDepth(int x, int y, u32 value) { + const auto& framebuffer = g_state.regs.framebuffer.framebuffer; + const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress(); + u8* depth_buffer = Memory::GetPhysicalPointer(addr); + + y = framebuffer.height - y; + + const u32 coarse_y = y & ~7; + u32 bytes_per_pixel = FramebufferRegs::BytesPerDepthPixel(framebuffer.depth_format); + u32 stride = framebuffer.width * bytes_per_pixel; + + u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; + u8* dst_pixel = depth_buffer + dst_offset; + + switch (framebuffer.depth_format) { + case FramebufferRegs::DepthFormat::D16: + Color::EncodeD16(value, dst_pixel); + break; + + case FramebufferRegs::DepthFormat::D24: + Color::EncodeD24(value, dst_pixel); + break; + + case FramebufferRegs::DepthFormat::D24S8: + Color::EncodeD24X8(value, dst_pixel); + break; + + default: + LOG_CRITICAL(HW_GPU, "Unimplemented depth format %u", framebuffer.depth_format); + UNIMPLEMENTED(); + break; + } +} + +void SetStencil(int x, int y, u8 value) { + const auto& framebuffer = g_state.regs.framebuffer.framebuffer; + const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress(); + u8* depth_buffer = Memory::GetPhysicalPointer(addr); + + y = framebuffer.height - y; + + const u32 coarse_y = y & ~7; + u32 bytes_per_pixel = Pica::FramebufferRegs::BytesPerDepthPixel(framebuffer.depth_format); + u32 stride = framebuffer.width * bytes_per_pixel; + + u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; + u8* dst_pixel = depth_buffer + dst_offset; + + switch (framebuffer.depth_format) { + case Pica::FramebufferRegs::DepthFormat::D16: + case Pica::FramebufferRegs::DepthFormat::D24: + // Nothing to do + break; + + case Pica::FramebufferRegs::DepthFormat::D24S8: + Color::EncodeX24S8(value, dst_pixel); + break; + + default: + LOG_CRITICAL(HW_GPU, "Unimplemented depth format %u", framebuffer.depth_format); + UNIMPLEMENTED(); + break; + } +} + +u8 PerformStencilAction(FramebufferRegs::StencilAction action, u8 old_stencil, u8 ref) { + switch (action) { + case FramebufferRegs::StencilAction::Keep: + return old_stencil; + + case FramebufferRegs::StencilAction::Zero: + return 0; + + case FramebufferRegs::StencilAction::Replace: + return ref; + + case FramebufferRegs::StencilAction::Increment: + // Saturated increment + return std::min<u8>(old_stencil, 254) + 1; + + case FramebufferRegs::StencilAction::Decrement: + // Saturated decrement + return std::max<u8>(old_stencil, 1) - 1; + + case FramebufferRegs::StencilAction::Invert: + return ~old_stencil; + + case FramebufferRegs::StencilAction::IncrementWrap: + return old_stencil + 1; + + case FramebufferRegs::StencilAction::DecrementWrap: + return old_stencil - 1; + + default: + LOG_CRITICAL(HW_GPU, "Unknown stencil action %x", (int)action); + UNIMPLEMENTED(); + return 0; + } +} + +Math::Vec4<u8> EvaluateBlendEquation(const Math::Vec4<u8>& src, const Math::Vec4<u8>& srcfactor, + const Math::Vec4<u8>& dest, const Math::Vec4<u8>& destfactor, + FramebufferRegs::BlendEquation equation) { + Math::Vec4<int> result; + + auto src_result = (src * srcfactor).Cast<int>(); + auto dst_result = (dest * destfactor).Cast<int>(); + + switch (equation) { + case FramebufferRegs::BlendEquation::Add: + result = (src_result + dst_result) / 255; + break; + + case FramebufferRegs::BlendEquation::Subtract: + result = (src_result - dst_result) / 255; + break; + + case FramebufferRegs::BlendEquation::ReverseSubtract: + result = (dst_result - src_result) / 255; + break; + + // TODO: How do these two actually work? OpenGL doesn't include the blend factors in the + // min/max computations, but is this what the 3DS actually does? + case FramebufferRegs::BlendEquation::Min: + result.r() = std::min(src.r(), dest.r()); + result.g() = std::min(src.g(), dest.g()); + result.b() = std::min(src.b(), dest.b()); + result.a() = std::min(src.a(), dest.a()); + break; + + case FramebufferRegs::BlendEquation::Max: + result.r() = std::max(src.r(), dest.r()); + result.g() = std::max(src.g(), dest.g()); + result.b() = std::max(src.b(), dest.b()); + result.a() = std::max(src.a(), dest.a()); + break; + + default: + LOG_CRITICAL(HW_GPU, "Unknown RGB blend equation %x", equation); + UNIMPLEMENTED(); + } + + return Math::Vec4<u8>(MathUtil::Clamp(result.r(), 0, 255), MathUtil::Clamp(result.g(), 0, 255), + MathUtil::Clamp(result.b(), 0, 255), MathUtil::Clamp(result.a(), 0, 255)); +}; + +u8 LogicOp(u8 src, u8 dest, FramebufferRegs::LogicOp op) { + switch (op) { + case FramebufferRegs::LogicOp::Clear: + return 0; + + case FramebufferRegs::LogicOp::And: + return src & dest; + + case FramebufferRegs::LogicOp::AndReverse: + return src & ~dest; + + case FramebufferRegs::LogicOp::Copy: + return src; + + case FramebufferRegs::LogicOp::Set: + return 255; + + case FramebufferRegs::LogicOp::CopyInverted: + return ~src; + + case FramebufferRegs::LogicOp::NoOp: + return dest; + + case FramebufferRegs::LogicOp::Invert: + return ~dest; + + case FramebufferRegs::LogicOp::Nand: + return ~(src & dest); + + case FramebufferRegs::LogicOp::Or: + return src | dest; + + case FramebufferRegs::LogicOp::Nor: + return ~(src | dest); + + case FramebufferRegs::LogicOp::Xor: + return src ^ dest; + + case FramebufferRegs::LogicOp::Equiv: + return ~(src ^ dest); + + case FramebufferRegs::LogicOp::AndInverted: + return ~src & dest; + + case FramebufferRegs::LogicOp::OrReverse: + return src | ~dest; + + case FramebufferRegs::LogicOp::OrInverted: + return ~src | dest; + } +}; + +} // namespace Rasterizer +} // namespace Pica diff --git a/src/video_core/swrasterizer/framebuffer.h b/src/video_core/swrasterizer/framebuffer.h new file mode 100644 index 000000000..4a32a4979 --- /dev/null +++ b/src/video_core/swrasterizer/framebuffer.h @@ -0,0 +1,29 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "common/vector_math.h" +#include "video_core/regs_framebuffer.h" + +namespace Pica { +namespace Rasterizer { + +void DrawPixel(int x, int y, const Math::Vec4<u8>& color); +const Math::Vec4<u8> GetPixel(int x, int y); +u32 GetDepth(int x, int y); +u8 GetStencil(int x, int y); +void SetDepth(int x, int y, u32 value); +void SetStencil(int x, int y, u8 value); +u8 PerformStencilAction(FramebufferRegs::StencilAction action, u8 old_stencil, u8 ref); + +Math::Vec4<u8> EvaluateBlendEquation(const Math::Vec4<u8>& src, const Math::Vec4<u8>& srcfactor, + const Math::Vec4<u8>& dest, const Math::Vec4<u8>& destfactor, + FramebufferRegs::BlendEquation equation); + +u8 LogicOp(u8 src, u8 dest, FramebufferRegs::LogicOp op); + +} // namespace Rasterizer +} // namespace Pica diff --git a/src/video_core/rasterizer.cpp b/src/video_core/swrasterizer/rasterizer.cpp index b9f5d4533..7557fcb89 100644 --- a/src/video_core/rasterizer.cpp +++ b/src/video_core/swrasterizer/rasterizer.cpp @@ -16,253 +16,21 @@ #include "core/hw/gpu.h" #include "core/memory.h" #include "video_core/debug_utils/debug_utils.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" #include "video_core/pica_types.h" -#include "video_core/rasterizer.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_rasterizer.h" +#include "video_core/regs_texturing.h" #include "video_core/shader/shader.h" +#include "video_core/swrasterizer/framebuffer.h" +#include "video_core/swrasterizer/rasterizer.h" +#include "video_core/swrasterizer/texturing.h" +#include "video_core/texture/texture_decode.h" #include "video_core/utils.h" namespace Pica { - namespace Rasterizer { -static void DrawPixel(int x, int y, const Math::Vec4<u8>& color) { - const auto& framebuffer = g_state.regs.framebuffer; - const PAddr addr = framebuffer.GetColorBufferPhysicalAddress(); - - // Similarly to textures, the render framebuffer is laid out from bottom to top, too. - // NOTE: The framebuffer height register contains the actual FB height minus one. - y = framebuffer.height - y; - - const u32 coarse_y = y & ~7; - u32 bytes_per_pixel = - GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(framebuffer.color_format.Value())); - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + - coarse_y * framebuffer.width * bytes_per_pixel; - u8* dst_pixel = Memory::GetPhysicalPointer(addr) + dst_offset; - - switch (framebuffer.color_format) { - case Regs::ColorFormat::RGBA8: - Color::EncodeRGBA8(color, dst_pixel); - break; - - case Regs::ColorFormat::RGB8: - Color::EncodeRGB8(color, dst_pixel); - break; - - case Regs::ColorFormat::RGB5A1: - Color::EncodeRGB5A1(color, dst_pixel); - break; - - case Regs::ColorFormat::RGB565: - Color::EncodeRGB565(color, dst_pixel); - break; - - case Regs::ColorFormat::RGBA4: - Color::EncodeRGBA4(color, dst_pixel); - break; - - default: - LOG_CRITICAL(Render_Software, "Unknown framebuffer color format %x", - framebuffer.color_format.Value()); - UNIMPLEMENTED(); - } -} - -static const Math::Vec4<u8> GetPixel(int x, int y) { - const auto& framebuffer = g_state.regs.framebuffer; - const PAddr addr = framebuffer.GetColorBufferPhysicalAddress(); - - y = framebuffer.height - y; - - const u32 coarse_y = y & ~7; - u32 bytes_per_pixel = - GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(framebuffer.color_format.Value())); - u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + - coarse_y * framebuffer.width * bytes_per_pixel; - u8* src_pixel = Memory::GetPhysicalPointer(addr) + src_offset; - - switch (framebuffer.color_format) { - case Regs::ColorFormat::RGBA8: - return Color::DecodeRGBA8(src_pixel); - - case Regs::ColorFormat::RGB8: - return Color::DecodeRGB8(src_pixel); - - case Regs::ColorFormat::RGB5A1: - return Color::DecodeRGB5A1(src_pixel); - - case Regs::ColorFormat::RGB565: - return Color::DecodeRGB565(src_pixel); - - case Regs::ColorFormat::RGBA4: - return Color::DecodeRGBA4(src_pixel); - - default: - LOG_CRITICAL(Render_Software, "Unknown framebuffer color format %x", - framebuffer.color_format.Value()); - UNIMPLEMENTED(); - } - - return {0, 0, 0, 0}; -} - -static u32 GetDepth(int x, int y) { - const auto& framebuffer = g_state.regs.framebuffer; - const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress(); - u8* depth_buffer = Memory::GetPhysicalPointer(addr); - - y = framebuffer.height - y; - - const u32 coarse_y = y & ~7; - u32 bytes_per_pixel = Regs::BytesPerDepthPixel(framebuffer.depth_format); - u32 stride = framebuffer.width * bytes_per_pixel; - - u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; - u8* src_pixel = depth_buffer + src_offset; - - switch (framebuffer.depth_format) { - case Regs::DepthFormat::D16: - return Color::DecodeD16(src_pixel); - case Regs::DepthFormat::D24: - return Color::DecodeD24(src_pixel); - case Regs::DepthFormat::D24S8: - return Color::DecodeD24S8(src_pixel).x; - default: - LOG_CRITICAL(HW_GPU, "Unimplemented depth format %u", framebuffer.depth_format); - UNIMPLEMENTED(); - return 0; - } -} - -static u8 GetStencil(int x, int y) { - const auto& framebuffer = g_state.regs.framebuffer; - const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress(); - u8* depth_buffer = Memory::GetPhysicalPointer(addr); - - y = framebuffer.height - y; - - const u32 coarse_y = y & ~7; - u32 bytes_per_pixel = Pica::Regs::BytesPerDepthPixel(framebuffer.depth_format); - u32 stride = framebuffer.width * bytes_per_pixel; - - u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; - u8* src_pixel = depth_buffer + src_offset; - - switch (framebuffer.depth_format) { - case Regs::DepthFormat::D24S8: - return Color::DecodeD24S8(src_pixel).y; - - default: - LOG_WARNING( - HW_GPU, - "GetStencil called for function which doesn't have a stencil component (format %u)", - framebuffer.depth_format); - return 0; - } -} - -static void SetDepth(int x, int y, u32 value) { - const auto& framebuffer = g_state.regs.framebuffer; - const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress(); - u8* depth_buffer = Memory::GetPhysicalPointer(addr); - - y = framebuffer.height - y; - - const u32 coarse_y = y & ~7; - u32 bytes_per_pixel = Regs::BytesPerDepthPixel(framebuffer.depth_format); - u32 stride = framebuffer.width * bytes_per_pixel; - - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; - u8* dst_pixel = depth_buffer + dst_offset; - - switch (framebuffer.depth_format) { - case Regs::DepthFormat::D16: - Color::EncodeD16(value, dst_pixel); - break; - - case Regs::DepthFormat::D24: - Color::EncodeD24(value, dst_pixel); - break; - - case Regs::DepthFormat::D24S8: - Color::EncodeD24X8(value, dst_pixel); - break; - - default: - LOG_CRITICAL(HW_GPU, "Unimplemented depth format %u", framebuffer.depth_format); - UNIMPLEMENTED(); - break; - } -} - -static void SetStencil(int x, int y, u8 value) { - const auto& framebuffer = g_state.regs.framebuffer; - const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress(); - u8* depth_buffer = Memory::GetPhysicalPointer(addr); - - y = framebuffer.height - y; - - const u32 coarse_y = y & ~7; - u32 bytes_per_pixel = Pica::Regs::BytesPerDepthPixel(framebuffer.depth_format); - u32 stride = framebuffer.width * bytes_per_pixel; - - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; - u8* dst_pixel = depth_buffer + dst_offset; - - switch (framebuffer.depth_format) { - case Pica::Regs::DepthFormat::D16: - case Pica::Regs::DepthFormat::D24: - // Nothing to do - break; - - case Pica::Regs::DepthFormat::D24S8: - Color::EncodeX24S8(value, dst_pixel); - break; - - default: - LOG_CRITICAL(HW_GPU, "Unimplemented depth format %u", framebuffer.depth_format); - UNIMPLEMENTED(); - break; - } -} - -static u8 PerformStencilAction(Regs::StencilAction action, u8 old_stencil, u8 ref) { - switch (action) { - case Regs::StencilAction::Keep: - return old_stencil; - - case Regs::StencilAction::Zero: - return 0; - - case Regs::StencilAction::Replace: - return ref; - - case Regs::StencilAction::Increment: - // Saturated increment - return std::min<u8>(old_stencil, 254) + 1; - - case Regs::StencilAction::Decrement: - // Saturated decrement - return std::max<u8>(old_stencil, 1) - 1; - - case Regs::StencilAction::Invert: - return ~old_stencil; - - case Regs::StencilAction::IncrementWrap: - return old_stencil + 1; - - case Regs::StencilAction::DecrementWrap: - return old_stencil - 1; - - default: - LOG_CRITICAL(HW_GPU, "Unknown stencil action %x", (int)action); - UNIMPLEMENTED(); - return 0; - } -} - // NOTE: Assuming that rasterizer coordinates are 12.4 fixed-point values struct Fix12P4 { Fix12P4() {} @@ -307,8 +75,8 @@ MICROPROFILE_DEFINE(GPU_Rasterization, "GPU", "Rasterization", MP_RGB(50, 50, 24 * Helper function for ProcessTriangle with the "reversed" flag to allow for implementing * culling via recursion. */ -static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader::OutputVertex& v1, - const Shader::OutputVertex& v2, bool reversed = false) { +static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Vertex& v2, + bool reversed = false) { const auto& regs = g_state.regs; MICROPROFILE_SCOPE(GPU_Rasterization); @@ -326,14 +94,14 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader ScreenToRasterizerCoordinates(v1.screenpos), ScreenToRasterizerCoordinates(v2.screenpos)}; - if (regs.cull_mode == Regs::CullMode::KeepAll) { + if (regs.rasterizer.cull_mode == RasterizerRegs::CullMode::KeepAll) { // Make sure we always end up with a triangle wound counter-clockwise if (!reversed && SignedArea(vtxpos[0].xy(), vtxpos[1].xy(), vtxpos[2].xy()) <= 0) { ProcessTriangleInternal(v0, v2, v1, true); return; } } else { - if (!reversed && regs.cull_mode == Regs::CullMode::KeepClockWise) { + if (!reversed && regs.rasterizer.cull_mode == RasterizerRegs::CullMode::KeepClockWise) { // Reverse vertex order and use the CCW code path. ProcessTriangleInternal(v0, v2, v1, true); return; @@ -350,13 +118,13 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader u16 max_y = std::max({vtxpos[0].y, vtxpos[1].y, vtxpos[2].y}); // Convert the scissor box coordinates to 12.4 fixed point - u16 scissor_x1 = (u16)(regs.scissor_test.x1 << 4); - u16 scissor_y1 = (u16)(regs.scissor_test.y1 << 4); + u16 scissor_x1 = (u16)(regs.rasterizer.scissor_test.x1 << 4); + u16 scissor_y1 = (u16)(regs.rasterizer.scissor_test.y1 << 4); // x2,y2 have +1 added to cover the entire sub-pixel area - u16 scissor_x2 = (u16)((regs.scissor_test.x2 + 1) << 4); - u16 scissor_y2 = (u16)((regs.scissor_test.y2 + 1) << 4); + u16 scissor_x2 = (u16)((regs.rasterizer.scissor_test.x2 + 1) << 4); + u16 scissor_y2 = (u16)((regs.rasterizer.scissor_test.y2 + 1) << 4); - if (regs.scissor_test.mode == Regs::ScissorMode::Include) { + if (regs.rasterizer.scissor_test.mode == RasterizerRegs::ScissorMode::Include) { // Calculate the new bounds min_x = std::max(min_x, scissor_x1); min_y = std::max(min_y, scissor_y1); @@ -396,12 +164,13 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader auto w_inverse = Math::MakeVec(v0.pos.w, v1.pos.w, v2.pos.w); - auto textures = regs.GetTextures(); - auto tev_stages = regs.GetTevStages(); + auto textures = regs.texturing.GetTextures(); + auto tev_stages = regs.texturing.GetTevStages(); - bool stencil_action_enable = g_state.regs.output_merger.stencil_test.enable && - g_state.regs.framebuffer.depth_format == Regs::DepthFormat::D24S8; - const auto stencil_test = g_state.regs.output_merger.stencil_test; + bool stencil_action_enable = + g_state.regs.framebuffer.output_merger.stencil_test.enable && + g_state.regs.framebuffer.framebuffer.depth_format == FramebufferRegs::DepthFormat::D24S8; + const auto stencil_test = g_state.regs.framebuffer.output_merger.stencil_test; // Enter rasterization loop, starting at the center of the topleft bounding box corner. // TODO: Not sure if looping through x first might be faster @@ -410,7 +179,7 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader // Do not process the pixel if it's inside the scissor box and the scissor mode is set // to Exclude - if (regs.scissor_test.mode == Regs::ScissorMode::Exclude) { + if (regs.rasterizer.scissor_test.mode == RasterizerRegs::ScissorMode::Exclude) { if (x >= scissor_x1 && x < scissor_x2 && y >= scissor_y1 && y < scissor_y2) continue; } @@ -440,12 +209,14 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader // Not fully accurate. About 3 bits in precision are missing. // Z-Buffer (z / w * scale + offset) - float depth_scale = float24::FromRaw(regs.viewport_depth_range).ToFloat32(); - float depth_offset = float24::FromRaw(regs.viewport_depth_near_plane).ToFloat32(); + float depth_scale = float24::FromRaw(regs.rasterizer.viewport_depth_range).ToFloat32(); + float depth_offset = + float24::FromRaw(regs.rasterizer.viewport_depth_near_plane).ToFloat32(); float depth = interpolated_z_over_w * depth_scale + depth_offset; // Potentially switch to W-Buffer - if (regs.depthmap_enable == Pica::Regs::DepthBuffering::WBuffering) { + if (regs.rasterizer.depthmap_enable == + Pica::RasterizerRegs::DepthBuffering::WBuffering) { // W-Buffer (z * scale + w * offset = (z / w * scale + offset) * w) depth *= interpolated_w_inverse.ToFloat32() * wsum; } @@ -512,9 +283,9 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader // TODO: Refactor so cubemaps and shadowmaps can be handled if (i == 0) { switch (texture.config.type) { - case Regs::TextureConfig::Texture2D: + case TexturingRegs::TextureConfig::Texture2D: break; - case Regs::TextureConfig::Projection2D: { + case TexturingRegs::TextureConfig::Projection2D: { auto tc0_w = GetInterpolatedAttribute(v0.tc0_w, v1.tc0_w, v2.tc0_w); u /= tc0_w; v /= tc0_w; @@ -533,37 +304,9 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader int t = (int)(v * float24::FromFloat32(static_cast<float>(texture.config.height))) .ToFloat32(); - static auto GetWrappedTexCoord = [](Regs::TextureConfig::WrapMode mode, int val, - unsigned size) { - switch (mode) { - case Regs::TextureConfig::ClampToEdge: - val = std::max(val, 0); - val = std::min(val, (int)size - 1); - return val; - - case Regs::TextureConfig::ClampToBorder: - return val; - - case Regs::TextureConfig::Repeat: - return (int)((unsigned)val % size); - - case Regs::TextureConfig::MirroredRepeat: { - unsigned int coord = ((unsigned)val % (2 * size)); - if (coord >= size) - coord = 2 * size - 1 - coord; - return (int)coord; - } - - default: - LOG_ERROR(HW_GPU, "Unknown texture coordinate wrapping mode %x", (int)mode); - UNIMPLEMENTED(); - return 0; - } - }; - - if ((texture.config.wrap_s == Regs::TextureConfig::ClampToBorder && + if ((texture.config.wrap_s == TexturingRegs::TextureConfig::ClampToBorder && (s < 0 || static_cast<u32>(s) >= texture.config.width)) || - (texture.config.wrap_t == Regs::TextureConfig::ClampToBorder && + (texture.config.wrap_t == TexturingRegs::TextureConfig::ClampToBorder && (t < 0 || static_cast<u32>(t) >= texture.config.height))) { auto border_color = texture.config.border_color; texture_color[i] = {border_color.r, border_color.g, border_color.b, @@ -579,10 +322,10 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader u8* texture_data = Memory::GetPhysicalPointer(texture.config.GetPhysicalAddress()); auto info = - DebugUtils::TextureInfo::FromPicaRegister(texture.config, texture.format); + Texture::TextureInfo::FromPicaRegister(texture.config, texture.format); // TODO: Apply the min and mag filters to the texture - texture_color[i] = DebugUtils::LookupTexture(texture_data, s, t, info); + texture_color[i] = Texture::LookupTexture(texture_data, s, t, info); #if PICA_DUMP_TEXTURES DebugUtils::DumpTexture(texture.config, texture_data); #endif @@ -599,17 +342,16 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader Math::Vec4<u8> combiner_output; Math::Vec4<u8> combiner_buffer = {0, 0, 0, 0}; Math::Vec4<u8> next_combiner_buffer = { - regs.tev_combiner_buffer_color.r, regs.tev_combiner_buffer_color.g, - regs.tev_combiner_buffer_color.b, regs.tev_combiner_buffer_color.a, + regs.texturing.tev_combiner_buffer_color.r, + regs.texturing.tev_combiner_buffer_color.g, + regs.texturing.tev_combiner_buffer_color.b, + regs.texturing.tev_combiner_buffer_color.a, }; for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size(); ++tev_stage_index) { const auto& tev_stage = tev_stages[tev_stage_index]; - using Source = Regs::TevStageConfig::Source; - using ColorModifier = Regs::TevStageConfig::ColorModifier; - using AlphaModifier = Regs::TevStageConfig::AlphaModifier; - using Operation = Regs::TevStageConfig::Operation; + using Source = TexturingRegs::TevStageConfig::Source; auto GetSource = [&](Source source) -> Math::Vec4<u8> { switch (source) { @@ -649,187 +391,6 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader } }; - static auto GetColorModifier = [](ColorModifier factor, - const Math::Vec4<u8>& values) -> Math::Vec3<u8> { - switch (factor) { - case ColorModifier::SourceColor: - return values.rgb(); - - case ColorModifier::OneMinusSourceColor: - return (Math::Vec3<u8>(255, 255, 255) - values.rgb()).Cast<u8>(); - - case ColorModifier::SourceAlpha: - return values.aaa(); - - case ColorModifier::OneMinusSourceAlpha: - return (Math::Vec3<u8>(255, 255, 255) - values.aaa()).Cast<u8>(); - - case ColorModifier::SourceRed: - return values.rrr(); - - case ColorModifier::OneMinusSourceRed: - return (Math::Vec3<u8>(255, 255, 255) - values.rrr()).Cast<u8>(); - - case ColorModifier::SourceGreen: - return values.ggg(); - - case ColorModifier::OneMinusSourceGreen: - return (Math::Vec3<u8>(255, 255, 255) - values.ggg()).Cast<u8>(); - - case ColorModifier::SourceBlue: - return values.bbb(); - - case ColorModifier::OneMinusSourceBlue: - return (Math::Vec3<u8>(255, 255, 255) - values.bbb()).Cast<u8>(); - } - }; - - static auto GetAlphaModifier = [](AlphaModifier factor, - const Math::Vec4<u8>& values) -> u8 { - switch (factor) { - case AlphaModifier::SourceAlpha: - return values.a(); - - case AlphaModifier::OneMinusSourceAlpha: - return 255 - values.a(); - - case AlphaModifier::SourceRed: - return values.r(); - - case AlphaModifier::OneMinusSourceRed: - return 255 - values.r(); - - case AlphaModifier::SourceGreen: - return values.g(); - - case AlphaModifier::OneMinusSourceGreen: - return 255 - values.g(); - - case AlphaModifier::SourceBlue: - return values.b(); - - case AlphaModifier::OneMinusSourceBlue: - return 255 - values.b(); - } - }; - - static auto ColorCombine = [](Operation op, - const Math::Vec3<u8> input[3]) -> Math::Vec3<u8> { - switch (op) { - case Operation::Replace: - return input[0]; - - case Operation::Modulate: - return ((input[0] * input[1]) / 255).Cast<u8>(); - - case Operation::Add: { - auto result = input[0] + input[1]; - result.r() = std::min(255, result.r()); - result.g() = std::min(255, result.g()); - result.b() = std::min(255, result.b()); - return result.Cast<u8>(); - } - - case Operation::AddSigned: { - // TODO(bunnei): Verify that the color conversion from (float) 0.5f to - // (byte) 128 is correct - auto result = input[0].Cast<int>() + input[1].Cast<int>() - - Math::MakeVec<int>(128, 128, 128); - result.r() = MathUtil::Clamp<int>(result.r(), 0, 255); - result.g() = MathUtil::Clamp<int>(result.g(), 0, 255); - result.b() = MathUtil::Clamp<int>(result.b(), 0, 255); - return result.Cast<u8>(); - } - - case Operation::Lerp: - return ((input[0] * input[2] + - input[1] * - (Math::MakeVec<u8>(255, 255, 255) - input[2]).Cast<u8>()) / - 255) - .Cast<u8>(); - - case Operation::Subtract: { - auto result = input[0].Cast<int>() - input[1].Cast<int>(); - result.r() = std::max(0, result.r()); - result.g() = std::max(0, result.g()); - result.b() = std::max(0, result.b()); - return result.Cast<u8>(); - } - - case Operation::MultiplyThenAdd: { - auto result = (input[0] * input[1] + 255 * input[2].Cast<int>()) / 255; - result.r() = std::min(255, result.r()); - result.g() = std::min(255, result.g()); - result.b() = std::min(255, result.b()); - return result.Cast<u8>(); - } - - case Operation::AddThenMultiply: { - auto result = input[0] + input[1]; - result.r() = std::min(255, result.r()); - result.g() = std::min(255, result.g()); - result.b() = std::min(255, result.b()); - result = (result * input[2].Cast<int>()) / 255; - return result.Cast<u8>(); - } - case Operation::Dot3_RGB: { - // Not fully accurate. - // Worst case scenario seems to yield a +/-3 error - // Some HW results indicate that the per-component computation can't have a - // higher precision than 1/256, - // while dot3_rgb( (0x80,g0,b0),(0x7F,g1,b1) ) and dot3_rgb( - // (0x80,g0,b0),(0x80,g1,b1) ) give different results - int result = - ((input[0].r() * 2 - 255) * (input[1].r() * 2 - 255) + 128) / 256 + - ((input[0].g() * 2 - 255) * (input[1].g() * 2 - 255) + 128) / 256 + - ((input[0].b() * 2 - 255) * (input[1].b() * 2 - 255) + 128) / 256; - result = std::max(0, std::min(255, result)); - return {(u8)result, (u8)result, (u8)result}; - } - default: - LOG_ERROR(HW_GPU, "Unknown color combiner operation %d", (int)op); - UNIMPLEMENTED(); - return {0, 0, 0}; - } - }; - - static auto AlphaCombine = [](Operation op, const std::array<u8, 3>& input) -> u8 { - switch (op) { - case Operation::Replace: - return input[0]; - - case Operation::Modulate: - return input[0] * input[1] / 255; - - case Operation::Add: - return std::min(255, input[0] + input[1]); - - case Operation::AddSigned: { - // TODO(bunnei): Verify that the color conversion from (float) 0.5f to - // (byte) 128 is correct - auto result = static_cast<int>(input[0]) + static_cast<int>(input[1]) - 128; - return static_cast<u8>(MathUtil::Clamp<int>(result, 0, 255)); - } - - case Operation::Lerp: - return (input[0] * input[2] + input[1] * (255 - input[2])) / 255; - - case Operation::Subtract: - return std::max(0, (int)input[0] - (int)input[1]); - - case Operation::MultiplyThenAdd: - return std::min(255, (input[0] * input[1] + 255 * input[2]) / 255); - - case Operation::AddThenMultiply: - return (std::min(255, (input[0] + input[1])) * input[2]) / 255; - - default: - LOG_ERROR(HW_GPU, "Unknown alpha combiner operation %d", (int)op); - UNIMPLEMENTED(); - return 0; - } - }; - // color combiner // NOTE: Not sure if the alpha combiner might use the color output of the previous // stage as input. Hence, we currently don't directly write the result to @@ -861,54 +422,54 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader combiner_buffer = next_combiner_buffer; - if (regs.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferColor( + if (regs.texturing.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferColor( tev_stage_index)) { next_combiner_buffer.r() = combiner_output.r(); next_combiner_buffer.g() = combiner_output.g(); next_combiner_buffer.b() = combiner_output.b(); } - if (regs.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferAlpha( + if (regs.texturing.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferAlpha( tev_stage_index)) { next_combiner_buffer.a() = combiner_output.a(); } } - const auto& output_merger = regs.output_merger; + const auto& output_merger = regs.framebuffer.output_merger; // TODO: Does alpha testing happen before or after stencil? if (output_merger.alpha_test.enable) { bool pass = false; switch (output_merger.alpha_test.func) { - case Regs::CompareFunc::Never: + case FramebufferRegs::CompareFunc::Never: pass = false; break; - case Regs::CompareFunc::Always: + case FramebufferRegs::CompareFunc::Always: pass = true; break; - case Regs::CompareFunc::Equal: + case FramebufferRegs::CompareFunc::Equal: pass = combiner_output.a() == output_merger.alpha_test.ref; break; - case Regs::CompareFunc::NotEqual: + case FramebufferRegs::CompareFunc::NotEqual: pass = combiner_output.a() != output_merger.alpha_test.ref; break; - case Regs::CompareFunc::LessThan: + case FramebufferRegs::CompareFunc::LessThan: pass = combiner_output.a() < output_merger.alpha_test.ref; break; - case Regs::CompareFunc::LessThanOrEqual: + case FramebufferRegs::CompareFunc::LessThanOrEqual: pass = combiner_output.a() <= output_merger.alpha_test.ref; break; - case Regs::CompareFunc::GreaterThan: + case FramebufferRegs::CompareFunc::GreaterThan: pass = combiner_output.a() > output_merger.alpha_test.ref; break; - case Regs::CompareFunc::GreaterThanOrEqual: + case FramebufferRegs::CompareFunc::GreaterThanOrEqual: pass = combiner_output.a() >= output_merger.alpha_test.ref; break; } @@ -921,16 +482,16 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader // Not fully accurate. We'd have to know what data type is used to // store the depth etc. Using float for now until we know more // about Pica datatypes - if (regs.fog_mode == Regs::FogMode::Fog) { + if (regs.texturing.fog_mode == TexturingRegs::FogMode::Fog) { const Math::Vec3<u8> fog_color = { - static_cast<u8>(regs.fog_color.r.Value()), - static_cast<u8>(regs.fog_color.g.Value()), - static_cast<u8>(regs.fog_color.b.Value()), + static_cast<u8>(regs.texturing.fog_color.r.Value()), + static_cast<u8>(regs.texturing.fog_color.g.Value()), + static_cast<u8>(regs.texturing.fog_color.b.Value()), }; // Get index into fog LUT float fog_index; - if (g_state.regs.fog_flip) { + if (g_state.regs.texturing.fog_flip) { fog_index = (1.0f - depth) * 128.0f; } else { fog_index = depth * 128.0f; @@ -954,10 +515,10 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader u8 old_stencil = 0; auto UpdateStencil = [stencil_test, x, y, - &old_stencil](Pica::Regs::StencilAction action) { + &old_stencil](Pica::FramebufferRegs::StencilAction action) { u8 new_stencil = PerformStencilAction(action, old_stencil, stencil_test.reference_value); - if (g_state.regs.framebuffer.allow_depth_stencil_write != 0) + if (g_state.regs.framebuffer.framebuffer.allow_depth_stencil_write != 0) SetStencil(x >> 4, y >> 4, (new_stencil & stencil_test.write_mask) | (old_stencil & ~stencil_test.write_mask)); }; @@ -969,35 +530,35 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader bool pass = false; switch (stencil_test.func) { - case Regs::CompareFunc::Never: + case FramebufferRegs::CompareFunc::Never: pass = false; break; - case Regs::CompareFunc::Always: + case FramebufferRegs::CompareFunc::Always: pass = true; break; - case Regs::CompareFunc::Equal: + case FramebufferRegs::CompareFunc::Equal: pass = (ref == dest); break; - case Regs::CompareFunc::NotEqual: + case FramebufferRegs::CompareFunc::NotEqual: pass = (ref != dest); break; - case Regs::CompareFunc::LessThan: + case FramebufferRegs::CompareFunc::LessThan: pass = (ref < dest); break; - case Regs::CompareFunc::LessThanOrEqual: + case FramebufferRegs::CompareFunc::LessThanOrEqual: pass = (ref <= dest); break; - case Regs::CompareFunc::GreaterThan: + case FramebufferRegs::CompareFunc::GreaterThan: pass = (ref > dest); break; - case Regs::CompareFunc::GreaterThanOrEqual: + case FramebufferRegs::CompareFunc::GreaterThanOrEqual: pass = (ref >= dest); break; } @@ -1009,7 +570,8 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader } // Convert float to integer - unsigned num_bits = Regs::DepthBitsPerPixel(regs.framebuffer.depth_format); + unsigned num_bits = + FramebufferRegs::DepthBitsPerPixel(regs.framebuffer.framebuffer.depth_format); u32 z = (u32)(depth * ((1 << num_bits) - 1)); if (output_merger.depth_test_enable) { @@ -1018,35 +580,35 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader bool pass = false; switch (output_merger.depth_test_func) { - case Regs::CompareFunc::Never: + case FramebufferRegs::CompareFunc::Never: pass = false; break; - case Regs::CompareFunc::Always: + case FramebufferRegs::CompareFunc::Always: pass = true; break; - case Regs::CompareFunc::Equal: + case FramebufferRegs::CompareFunc::Equal: pass = z == ref_z; break; - case Regs::CompareFunc::NotEqual: + case FramebufferRegs::CompareFunc::NotEqual: pass = z != ref_z; break; - case Regs::CompareFunc::LessThan: + case FramebufferRegs::CompareFunc::LessThan: pass = z < ref_z; break; - case Regs::CompareFunc::LessThanOrEqual: + case FramebufferRegs::CompareFunc::LessThanOrEqual: pass = z <= ref_z; break; - case Regs::CompareFunc::GreaterThan: + case FramebufferRegs::CompareFunc::GreaterThan: pass = z > ref_z; break; - case Regs::CompareFunc::GreaterThanOrEqual: + case FramebufferRegs::CompareFunc::GreaterThanOrEqual: pass = z >= ref_z; break; } @@ -1058,8 +620,11 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader } } - if (regs.framebuffer.allow_depth_stencil_write != 0 && output_merger.depth_write_enable) + if (regs.framebuffer.framebuffer.allow_depth_stencil_write != 0 && + output_merger.depth_write_enable) { + SetDepth(x >> 4, y >> 4, z); + } // The stencil depth_pass action is executed even if depth testing is disabled if (stencil_action_enable) @@ -1071,7 +636,8 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader if (output_merger.alphablend_enable) { auto params = output_merger.alpha_blending; - auto LookupFactor = [&](unsigned channel, Regs::BlendFactor factor) -> u8 { + auto LookupFactor = [&](unsigned channel, + FramebufferRegs::BlendFactor factor) -> u8 { DEBUG_ASSERT(channel < 4); const Math::Vec4<u8> blend_const = { @@ -1082,49 +648,49 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader }; switch (factor) { - case Regs::BlendFactor::Zero: + case FramebufferRegs::BlendFactor::Zero: return 0; - case Regs::BlendFactor::One: + case FramebufferRegs::BlendFactor::One: return 255; - case Regs::BlendFactor::SourceColor: + case FramebufferRegs::BlendFactor::SourceColor: return combiner_output[channel]; - case Regs::BlendFactor::OneMinusSourceColor: + case FramebufferRegs::BlendFactor::OneMinusSourceColor: return 255 - combiner_output[channel]; - case Regs::BlendFactor::DestColor: + case FramebufferRegs::BlendFactor::DestColor: return dest[channel]; - case Regs::BlendFactor::OneMinusDestColor: + case FramebufferRegs::BlendFactor::OneMinusDestColor: return 255 - dest[channel]; - case Regs::BlendFactor::SourceAlpha: + case FramebufferRegs::BlendFactor::SourceAlpha: return combiner_output.a(); - case Regs::BlendFactor::OneMinusSourceAlpha: + case FramebufferRegs::BlendFactor::OneMinusSourceAlpha: return 255 - combiner_output.a(); - case Regs::BlendFactor::DestAlpha: + case FramebufferRegs::BlendFactor::DestAlpha: return dest.a(); - case Regs::BlendFactor::OneMinusDestAlpha: + case FramebufferRegs::BlendFactor::OneMinusDestAlpha: return 255 - dest.a(); - case Regs::BlendFactor::ConstantColor: + case FramebufferRegs::BlendFactor::ConstantColor: return blend_const[channel]; - case Regs::BlendFactor::OneMinusConstantColor: + case FramebufferRegs::BlendFactor::OneMinusConstantColor: return 255 - blend_const[channel]; - case Regs::BlendFactor::ConstantAlpha: + case FramebufferRegs::BlendFactor::ConstantAlpha: return blend_const.a(); - case Regs::BlendFactor::OneMinusConstantAlpha: + case FramebufferRegs::BlendFactor::OneMinusConstantAlpha: return 255 - blend_const.a(); - case Regs::BlendFactor::SourceAlphaSaturate: + case FramebufferRegs::BlendFactor::SourceAlphaSaturate: // Returns 1.0 for the alpha channel if (channel == 3) return 255; @@ -1139,55 +705,6 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader return combiner_output[channel]; }; - static auto EvaluateBlendEquation = []( - const Math::Vec4<u8>& src, const Math::Vec4<u8>& srcfactor, - const Math::Vec4<u8>& dest, const Math::Vec4<u8>& destfactor, - Regs::BlendEquation equation) { - Math::Vec4<int> result; - - auto src_result = (src * srcfactor).Cast<int>(); - auto dst_result = (dest * destfactor).Cast<int>(); - - switch (equation) { - case Regs::BlendEquation::Add: - result = (src_result + dst_result) / 255; - break; - - case Regs::BlendEquation::Subtract: - result = (src_result - dst_result) / 255; - break; - - case Regs::BlendEquation::ReverseSubtract: - result = (dst_result - src_result) / 255; - break; - - // TODO: How do these two actually work? - // OpenGL doesn't include the blend factors in the min/max computations, - // but is this what the 3DS actually does? - case Regs::BlendEquation::Min: - result.r() = std::min(src.r(), dest.r()); - result.g() = std::min(src.g(), dest.g()); - result.b() = std::min(src.b(), dest.b()); - result.a() = std::min(src.a(), dest.a()); - break; - - case Regs::BlendEquation::Max: - result.r() = std::max(src.r(), dest.r()); - result.g() = std::max(src.g(), dest.g()); - result.b() = std::max(src.b(), dest.b()); - result.a() = std::max(src.a(), dest.a()); - break; - - default: - LOG_CRITICAL(HW_GPU, "Unknown RGB blend equation %x", equation); - UNIMPLEMENTED(); - } - - return Math::Vec4<u8>( - MathUtil::Clamp(result.r(), 0, 255), MathUtil::Clamp(result.g(), 0, 255), - MathUtil::Clamp(result.b(), 0, 255), MathUtil::Clamp(result.a(), 0, 255)); - }; - auto srcfactor = Math::MakeVec(LookupFactor(0, params.factor_source_rgb), LookupFactor(1, params.factor_source_rgb), LookupFactor(2, params.factor_source_rgb), @@ -1204,58 +721,6 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader dstfactor, params.blend_equation_a) .a(); } else { - static auto LogicOp = [](u8 src, u8 dest, Regs::LogicOp op) -> u8 { - switch (op) { - case Regs::LogicOp::Clear: - return 0; - - case Regs::LogicOp::And: - return src & dest; - - case Regs::LogicOp::AndReverse: - return src & ~dest; - - case Regs::LogicOp::Copy: - return src; - - case Regs::LogicOp::Set: - return 255; - - case Regs::LogicOp::CopyInverted: - return ~src; - - case Regs::LogicOp::NoOp: - return dest; - - case Regs::LogicOp::Invert: - return ~dest; - - case Regs::LogicOp::Nand: - return ~(src & dest); - - case Regs::LogicOp::Or: - return src | dest; - - case Regs::LogicOp::Nor: - return ~(src | dest); - - case Regs::LogicOp::Xor: - return src ^ dest; - - case Regs::LogicOp::Equiv: - return ~(src ^ dest); - - case Regs::LogicOp::AndInverted: - return ~src & dest; - - case Regs::LogicOp::OrReverse: - return src | ~dest; - - case Regs::LogicOp::OrInverted: - return ~src | dest; - } - }; - blend_output = Math::MakeVec(LogicOp(combiner_output.r(), dest.r(), output_merger.logic_op), LogicOp(combiner_output.g(), dest.g(), output_merger.logic_op), @@ -1270,14 +735,13 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader output_merger.alpha_enable ? blend_output.a() : dest.a(), }; - if (regs.framebuffer.allow_color_write != 0) + if (regs.framebuffer.framebuffer.allow_color_write != 0) DrawPixel(x >> 4, y >> 4, result); } } } -void ProcessTriangle(const Shader::OutputVertex& v0, const Shader::OutputVertex& v1, - const Shader::OutputVertex& v2) { +void ProcessTriangle(const Vertex& v0, const Vertex& v1, const Vertex& v2) { ProcessTriangleInternal(v0, v1, v2); } diff --git a/src/video_core/swrasterizer/rasterizer.h b/src/video_core/swrasterizer/rasterizer.h new file mode 100644 index 000000000..3a72ac343 --- /dev/null +++ b/src/video_core/swrasterizer/rasterizer.h @@ -0,0 +1,48 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "video_core/shader/shader.h" + +namespace Pica { + +namespace Rasterizer { + +struct Vertex : Shader::OutputVertex { + Vertex(const OutputVertex& v) : OutputVertex(v) {} + + // Attributes used to store intermediate results + // position after perspective divide + Math::Vec3<float24> screenpos; + + // Linear interpolation + // factor: 0=this, 1=vtx + void Lerp(float24 factor, const Vertex& vtx) { + pos = pos * factor + vtx.pos * (float24::FromFloat32(1) - factor); + + // TODO: Should perform perspective correct interpolation here... + tc0 = tc0 * factor + vtx.tc0 * (float24::FromFloat32(1) - factor); + tc1 = tc1 * factor + vtx.tc1 * (float24::FromFloat32(1) - factor); + tc2 = tc2 * factor + vtx.tc2 * (float24::FromFloat32(1) - factor); + + screenpos = screenpos * factor + vtx.screenpos * (float24::FromFloat32(1) - factor); + + color = color * factor + vtx.color * (float24::FromFloat32(1) - factor); + } + + // Linear interpolation + // factor: 0=v0, 1=v1 + static Vertex Lerp(float24 factor, const Vertex& v0, const Vertex& v1) { + Vertex ret = v0; + ret.Lerp(factor, v1); + return ret; + } +}; + +void ProcessTriangle(const Vertex& v0, const Vertex& v1, const Vertex& v2); + +} // namespace Rasterizer + +} // namespace Pica diff --git a/src/video_core/swrasterizer.cpp b/src/video_core/swrasterizer/swrasterizer.cpp index 9cd21f72b..402b705dd 100644 --- a/src/video_core/swrasterizer.cpp +++ b/src/video_core/swrasterizer/swrasterizer.cpp @@ -2,8 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "video_core/clipper.h" -#include "video_core/swrasterizer.h" +#include "video_core/swrasterizer/clipper.h" +#include "video_core/swrasterizer/swrasterizer.h" namespace VideoCore { diff --git a/src/video_core/swrasterizer.h b/src/video_core/swrasterizer/swrasterizer.h index 6d42d7409..6d42d7409 100644 --- a/src/video_core/swrasterizer.h +++ b/src/video_core/swrasterizer/swrasterizer.h diff --git a/src/video_core/swrasterizer/texturing.cpp b/src/video_core/swrasterizer/texturing.cpp new file mode 100644 index 000000000..eb18e4ba4 --- /dev/null +++ b/src/video_core/swrasterizer/texturing.cpp @@ -0,0 +1,228 @@ +// Copyright 2017 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/common_types.h" +#include "common/math_util.h" +#include "common/vector_math.h" +#include "video_core/regs_texturing.h" +#include "video_core/swrasterizer/texturing.h" + +namespace Pica { +namespace Rasterizer { + +using TevStageConfig = TexturingRegs::TevStageConfig; + +int GetWrappedTexCoord(TexturingRegs::TextureConfig::WrapMode mode, int val, unsigned size) { + switch (mode) { + case TexturingRegs::TextureConfig::ClampToEdge: + val = std::max(val, 0); + val = std::min(val, (int)size - 1); + return val; + + case TexturingRegs::TextureConfig::ClampToBorder: + return val; + + case TexturingRegs::TextureConfig::Repeat: + return (int)((unsigned)val % size); + + case TexturingRegs::TextureConfig::MirroredRepeat: { + unsigned int coord = ((unsigned)val % (2 * size)); + if (coord >= size) + coord = 2 * size - 1 - coord; + return (int)coord; + } + + default: + LOG_ERROR(HW_GPU, "Unknown texture coordinate wrapping mode %x", (int)mode); + UNIMPLEMENTED(); + return 0; + } +}; + +Math::Vec3<u8> GetColorModifier(TevStageConfig::ColorModifier factor, + const Math::Vec4<u8>& values) { + using ColorModifier = TevStageConfig::ColorModifier; + + switch (factor) { + case ColorModifier::SourceColor: + return values.rgb(); + + case ColorModifier::OneMinusSourceColor: + return (Math::Vec3<u8>(255, 255, 255) - values.rgb()).Cast<u8>(); + + case ColorModifier::SourceAlpha: + return values.aaa(); + + case ColorModifier::OneMinusSourceAlpha: + return (Math::Vec3<u8>(255, 255, 255) - values.aaa()).Cast<u8>(); + + case ColorModifier::SourceRed: + return values.rrr(); + + case ColorModifier::OneMinusSourceRed: + return (Math::Vec3<u8>(255, 255, 255) - values.rrr()).Cast<u8>(); + + case ColorModifier::SourceGreen: + return values.ggg(); + + case ColorModifier::OneMinusSourceGreen: + return (Math::Vec3<u8>(255, 255, 255) - values.ggg()).Cast<u8>(); + + case ColorModifier::SourceBlue: + return values.bbb(); + + case ColorModifier::OneMinusSourceBlue: + return (Math::Vec3<u8>(255, 255, 255) - values.bbb()).Cast<u8>(); + } +}; + +u8 GetAlphaModifier(TevStageConfig::AlphaModifier factor, const Math::Vec4<u8>& values) { + using AlphaModifier = TevStageConfig::AlphaModifier; + + switch (factor) { + case AlphaModifier::SourceAlpha: + return values.a(); + + case AlphaModifier::OneMinusSourceAlpha: + return 255 - values.a(); + + case AlphaModifier::SourceRed: + return values.r(); + + case AlphaModifier::OneMinusSourceRed: + return 255 - values.r(); + + case AlphaModifier::SourceGreen: + return values.g(); + + case AlphaModifier::OneMinusSourceGreen: + return 255 - values.g(); + + case AlphaModifier::SourceBlue: + return values.b(); + + case AlphaModifier::OneMinusSourceBlue: + return 255 - values.b(); + } +}; + +Math::Vec3<u8> ColorCombine(TevStageConfig::Operation op, const Math::Vec3<u8> input[3]) { + using Operation = TevStageConfig::Operation; + + switch (op) { + case Operation::Replace: + return input[0]; + + case Operation::Modulate: + return ((input[0] * input[1]) / 255).Cast<u8>(); + + case Operation::Add: { + auto result = input[0] + input[1]; + result.r() = std::min(255, result.r()); + result.g() = std::min(255, result.g()); + result.b() = std::min(255, result.b()); + return result.Cast<u8>(); + } + + case Operation::AddSigned: { + // TODO(bunnei): Verify that the color conversion from (float) 0.5f to + // (byte) 128 is correct + auto result = + input[0].Cast<int>() + input[1].Cast<int>() - Math::MakeVec<int>(128, 128, 128); + result.r() = MathUtil::Clamp<int>(result.r(), 0, 255); + result.g() = MathUtil::Clamp<int>(result.g(), 0, 255); + result.b() = MathUtil::Clamp<int>(result.b(), 0, 255); + return result.Cast<u8>(); + } + + case Operation::Lerp: + return ((input[0] * input[2] + + input[1] * (Math::MakeVec<u8>(255, 255, 255) - input[2]).Cast<u8>()) / + 255) + .Cast<u8>(); + + case Operation::Subtract: { + auto result = input[0].Cast<int>() - input[1].Cast<int>(); + result.r() = std::max(0, result.r()); + result.g() = std::max(0, result.g()); + result.b() = std::max(0, result.b()); + return result.Cast<u8>(); + } + + case Operation::MultiplyThenAdd: { + auto result = (input[0] * input[1] + 255 * input[2].Cast<int>()) / 255; + result.r() = std::min(255, result.r()); + result.g() = std::min(255, result.g()); + result.b() = std::min(255, result.b()); + return result.Cast<u8>(); + } + + case Operation::AddThenMultiply: { + auto result = input[0] + input[1]; + result.r() = std::min(255, result.r()); + result.g() = std::min(255, result.g()); + result.b() = std::min(255, result.b()); + result = (result * input[2].Cast<int>()) / 255; + return result.Cast<u8>(); + } + case Operation::Dot3_RGB: { + // Not fully accurate. Worst case scenario seems to yield a +/-3 error. Some HW results + // indicate that the per-component computation can't have a higher precision than 1/256, + // while dot3_rgb((0x80,g0,b0), (0x7F,g1,b1)) and dot3_rgb((0x80,g0,b0), (0x80,g1,b1)) give + // different results. + int result = ((input[0].r() * 2 - 255) * (input[1].r() * 2 - 255) + 128) / 256 + + ((input[0].g() * 2 - 255) * (input[1].g() * 2 - 255) + 128) / 256 + + ((input[0].b() * 2 - 255) * (input[1].b() * 2 - 255) + 128) / 256; + result = std::max(0, std::min(255, result)); + return {(u8)result, (u8)result, (u8)result}; + } + default: + LOG_ERROR(HW_GPU, "Unknown color combiner operation %d", (int)op); + UNIMPLEMENTED(); + return {0, 0, 0}; + } +}; + +u8 AlphaCombine(TevStageConfig::Operation op, const std::array<u8, 3>& input) { + switch (op) { + using Operation = TevStageConfig::Operation; + case Operation::Replace: + return input[0]; + + case Operation::Modulate: + return input[0] * input[1] / 255; + + case Operation::Add: + return std::min(255, input[0] + input[1]); + + case Operation::AddSigned: { + // TODO(bunnei): Verify that the color conversion from (float) 0.5f to (byte) 128 is correct + auto result = static_cast<int>(input[0]) + static_cast<int>(input[1]) - 128; + return static_cast<u8>(MathUtil::Clamp<int>(result, 0, 255)); + } + + case Operation::Lerp: + return (input[0] * input[2] + input[1] * (255 - input[2])) / 255; + + case Operation::Subtract: + return std::max(0, (int)input[0] - (int)input[1]); + + case Operation::MultiplyThenAdd: + return std::min(255, (input[0] * input[1] + 255 * input[2]) / 255); + + case Operation::AddThenMultiply: + return (std::min(255, (input[0] + input[1])) * input[2]) / 255; + + default: + LOG_ERROR(HW_GPU, "Unknown alpha combiner operation %d", (int)op); + UNIMPLEMENTED(); + return 0; + } +}; + +} // namespace Rasterizer +} // namespace Pica diff --git a/src/video_core/swrasterizer/texturing.h b/src/video_core/swrasterizer/texturing.h new file mode 100644 index 000000000..24f74a5a3 --- /dev/null +++ b/src/video_core/swrasterizer/texturing.h @@ -0,0 +1,28 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "common/vector_math.h" +#include "video_core/regs_texturing.h" + +namespace Pica { +namespace Rasterizer { + +int GetWrappedTexCoord(TexturingRegs::TextureConfig::WrapMode mode, int val, unsigned size); + +Math::Vec3<u8> GetColorModifier(TexturingRegs::TevStageConfig::ColorModifier factor, + const Math::Vec4<u8>& values); + +u8 GetAlphaModifier(TexturingRegs::TevStageConfig::AlphaModifier factor, + const Math::Vec4<u8>& values); + +Math::Vec3<u8> ColorCombine(TexturingRegs::TevStageConfig::Operation op, + const Math::Vec3<u8> input[3]); + +u8 AlphaCombine(TexturingRegs::TevStageConfig::Operation op, const std::array<u8, 3>& input); + +} // namespace Rasterizer +} // namespace Pica diff --git a/src/video_core/texture/etc1.cpp b/src/video_core/texture/etc1.cpp new file mode 100644 index 000000000..43f7f56db --- /dev/null +++ b/src/video_core/texture/etc1.cpp @@ -0,0 +1,122 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include "common/bit_field.h" +#include "common/color.h" +#include "common/common_types.h" +#include "common/math_util.h" +#include "common/vector_math.h" +#include "video_core/texture/etc1.h" + +namespace Pica { +namespace Texture { + +namespace { + +constexpr std::array<std::array<u8, 2>, 8> etc1_modifier_table = {{ + {2, 8}, {5, 17}, {9, 29}, {13, 42}, {18, 60}, {24, 80}, {33, 106}, {47, 183}, +}}; + +union ETC1Tile { + u64 raw; + + // Each of these two is a collection of 16 bits (one per lookup value) + BitField<0, 16, u64> table_subindexes; + BitField<16, 16, u64> negation_flags; + + unsigned GetTableSubIndex(unsigned index) const { + return (table_subindexes >> index) & 1; + } + + bool GetNegationFlag(unsigned index) const { + return ((negation_flags >> index) & 1) == 1; + } + + BitField<32, 1, u64> flip; + BitField<33, 1, u64> differential_mode; + + BitField<34, 3, u64> table_index_2; + BitField<37, 3, u64> table_index_1; + + union { + // delta value + base value + BitField<40, 3, s64> db; + BitField<43, 5, u64> b; + + BitField<48, 3, s64> dg; + BitField<51, 5, u64> g; + + BitField<56, 3, s64> dr; + BitField<59, 5, u64> r; + } differential; + + union { + BitField<40, 4, u64> b2; + BitField<44, 4, u64> b1; + + BitField<48, 4, u64> g2; + BitField<52, 4, u64> g1; + + BitField<56, 4, u64> r2; + BitField<60, 4, u64> r1; + } separate; + + const Math::Vec3<u8> GetRGB(unsigned int x, unsigned int y) const { + int texel = 4 * x + y; + + if (flip) + std::swap(x, y); + + // Lookup base value + Math::Vec3<int> ret; + if (differential_mode) { + ret.r() = static_cast<int>(differential.r); + ret.g() = static_cast<int>(differential.g); + ret.b() = static_cast<int>(differential.b); + if (x >= 2) { + ret.r() += static_cast<int>(differential.dr); + ret.g() += static_cast<int>(differential.dg); + ret.b() += static_cast<int>(differential.db); + } + ret.r() = Color::Convert5To8(ret.r()); + ret.g() = Color::Convert5To8(ret.g()); + ret.b() = Color::Convert5To8(ret.b()); + } else { + if (x < 2) { + ret.r() = Color::Convert4To8(static_cast<u8>(separate.r1)); + ret.g() = Color::Convert4To8(static_cast<u8>(separate.g1)); + ret.b() = Color::Convert4To8(static_cast<u8>(separate.b1)); + } else { + ret.r() = Color::Convert4To8(static_cast<u8>(separate.r2)); + ret.g() = Color::Convert4To8(static_cast<u8>(separate.g2)); + ret.b() = Color::Convert4To8(static_cast<u8>(separate.b2)); + } + } + + // Add modifier + unsigned table_index = + static_cast<int>((x < 2) ? table_index_1.Value() : table_index_2.Value()); + + int modifier = etc1_modifier_table[table_index][GetTableSubIndex(texel)]; + if (GetNegationFlag(texel)) + modifier *= -1; + + ret.r() = MathUtil::Clamp(ret.r() + modifier, 0, 255); + ret.g() = MathUtil::Clamp(ret.g() + modifier, 0, 255); + ret.b() = MathUtil::Clamp(ret.b() + modifier, 0, 255); + + return ret.Cast<u8>(); + } +}; + +} // anonymous namespace + +Math::Vec3<u8> SampleETC1Subtile(u64 value, unsigned int x, unsigned int y) { + ETC1Tile tile{value}; + return tile.GetRGB(x, y); +} + +} // namespace Texture +} // namespace Pica diff --git a/src/video_core/texture/etc1.h b/src/video_core/texture/etc1.h new file mode 100644 index 000000000..e188b19df --- /dev/null +++ b/src/video_core/texture/etc1.h @@ -0,0 +1,16 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "common/vector_math.h" + +namespace Pica { +namespace Texture { + +Math::Vec3<u8> SampleETC1Subtile(u64 value, unsigned int x, unsigned int y); + +} // namespace Texture +} // namespace Pica diff --git a/src/video_core/texture/texture_decode.cpp b/src/video_core/texture/texture_decode.cpp new file mode 100644 index 000000000..0818d652c --- /dev/null +++ b/src/video_core/texture/texture_decode.cpp @@ -0,0 +1,227 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/color.h" +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/swap.h" +#include "common/vector_math.h" +#include "video_core/regs_texturing.h" +#include "video_core/texture/etc1.h" +#include "video_core/texture/texture_decode.h" +#include "video_core/utils.h" + +using TextureFormat = Pica::TexturingRegs::TextureFormat; + +namespace Pica { +namespace Texture { + +constexpr size_t TILE_SIZE = 8 * 8; +constexpr size_t ETC1_SUBTILES = 2 * 2; + +size_t CalculateTileSize(TextureFormat format) { + switch (format) { + case TextureFormat::RGBA8: + return 4 * TILE_SIZE; + + case TextureFormat::RGB8: + return 3 * TILE_SIZE; + + case TextureFormat::RGB5A1: + case TextureFormat::RGB565: + case TextureFormat::RGBA4: + case TextureFormat::IA8: + case TextureFormat::RG8: + return 2 * TILE_SIZE; + + case TextureFormat::I8: + case TextureFormat::A8: + case TextureFormat::IA4: + return 1 * TILE_SIZE; + + case TextureFormat::I4: + case TextureFormat::A4: + return TILE_SIZE / 2; + + case TextureFormat::ETC1: + return ETC1_SUBTILES * 8; + + case TextureFormat::ETC1A4: + return ETC1_SUBTILES * 16; + + default: // placeholder for yet unknown formats + UNIMPLEMENTED(); + return 0; + } +} + +Math::Vec4<u8> LookupTexture(const u8* source, unsigned int x, unsigned int y, + const TextureInfo& info, bool disable_alpha) { + // Coordinate in tiles + const unsigned int coarse_x = x / 8; + const unsigned int coarse_y = y / 8; + + // Coordinate inside the tile + const unsigned int fine_x = x % 8; + const unsigned int fine_y = y % 8; + + const u8* line = source + coarse_y * info.stride; + const u8* tile = line + coarse_x * CalculateTileSize(info.format); + return LookupTexelInTile(tile, fine_x, fine_y, info, disable_alpha); +} + +Math::Vec4<u8> LookupTexelInTile(const u8* source, unsigned int x, unsigned int y, + const TextureInfo& info, bool disable_alpha) { + DEBUG_ASSERT(x < 8); + DEBUG_ASSERT(y < 8); + + using VideoCore::MortonInterleave; + + switch (info.format) { + case TextureFormat::RGBA8: { + auto res = Color::DecodeRGBA8(source + MortonInterleave(x, y) * 4); + return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())}; + } + + case TextureFormat::RGB8: { + auto res = Color::DecodeRGB8(source + MortonInterleave(x, y) * 3); + return {res.r(), res.g(), res.b(), 255}; + } + + case TextureFormat::RGB5A1: { + auto res = Color::DecodeRGB5A1(source + MortonInterleave(x, y) * 2); + return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())}; + } + + case TextureFormat::RGB565: { + auto res = Color::DecodeRGB565(source + MortonInterleave(x, y) * 2); + return {res.r(), res.g(), res.b(), 255}; + } + + case TextureFormat::RGBA4: { + auto res = Color::DecodeRGBA4(source + MortonInterleave(x, y) * 2); + return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())}; + } + + case TextureFormat::IA8: { + const u8* source_ptr = source + MortonInterleave(x, y) * 2; + + if (disable_alpha) { + // Show intensity as red, alpha as green + return {source_ptr[1], source_ptr[0], 0, 255}; + } else { + return {source_ptr[1], source_ptr[1], source_ptr[1], source_ptr[0]}; + } + } + + case TextureFormat::RG8: { + auto res = Color::DecodeRG8(source + MortonInterleave(x, y) * 2); + return {res.r(), res.g(), 0, 255}; + } + + case TextureFormat::I8: { + const u8* source_ptr = source + MortonInterleave(x, y); + return {*source_ptr, *source_ptr, *source_ptr, 255}; + } + + case TextureFormat::A8: { + const u8* source_ptr = source + MortonInterleave(x, y); + + if (disable_alpha) { + return {*source_ptr, *source_ptr, *source_ptr, 255}; + } else { + return {0, 0, 0, *source_ptr}; + } + } + + case TextureFormat::IA4: { + const u8* source_ptr = source + MortonInterleave(x, y); + + u8 i = Color::Convert4To8(((*source_ptr) & 0xF0) >> 4); + u8 a = Color::Convert4To8((*source_ptr) & 0xF); + + if (disable_alpha) { + // Show intensity as red, alpha as green + return {i, a, 0, 255}; + } else { + return {i, i, i, a}; + } + } + + case TextureFormat::I4: { + u32 morton_offset = MortonInterleave(x, y); + const u8* source_ptr = source + morton_offset / 2; + + u8 i = (morton_offset % 2) ? ((*source_ptr & 0xF0) >> 4) : (*source_ptr & 0xF); + i = Color::Convert4To8(i); + + return {i, i, i, 255}; + } + + case TextureFormat::A4: { + u32 morton_offset = MortonInterleave(x, y); + const u8* source_ptr = source + morton_offset / 2; + + u8 a = (morton_offset % 2) ? ((*source_ptr & 0xF0) >> 4) : (*source_ptr & 0xF); + a = Color::Convert4To8(a); + + if (disable_alpha) { + return {a, a, a, 255}; + } else { + return {0, 0, 0, a}; + } + } + + case TextureFormat::ETC1: + case TextureFormat::ETC1A4: { + bool has_alpha = (info.format == TextureFormat::ETC1A4); + size_t subtile_size = has_alpha ? 16 : 8; + + // ETC1 further subdivides each 8x8 tile into four 4x4 subtiles + constexpr unsigned int subtile_width = 4; + constexpr unsigned int subtile_height = 4; + + unsigned int subtile_index = (x / subtile_width) + 2 * (y / subtile_height); + x %= subtile_width; + y %= subtile_height; + + const u8* subtile_ptr = source + subtile_index * subtile_size; + + u8 alpha = 255; + if (has_alpha) { + u64_le packed_alpha; + memcpy(&packed_alpha, subtile_ptr, sizeof(u64)); + subtile_ptr += sizeof(u64); + + alpha = Color::Convert4To8((packed_alpha >> (4 * (x * subtile_width + y))) & 0xF); + } + + u64_le subtile_data; + memcpy(&subtile_data, subtile_ptr, sizeof(u64)); + + return Math::MakeVec(SampleETC1Subtile(subtile_data, x, y), + disable_alpha ? (u8)255 : alpha); + } + + default: + LOG_ERROR(HW_GPU, "Unknown texture format: %x", (u32)info.format); + DEBUG_ASSERT(false); + return {}; + } +} + +TextureInfo TextureInfo::FromPicaRegister(const TexturingRegs::TextureConfig& config, + const TexturingRegs::TextureFormat& format) { + TextureInfo info; + info.physical_address = config.GetPhysicalAddress(); + info.width = config.width; + info.height = config.height; + info.format = format; + info.SetDefaultStride(); + return info; +} + +} // namespace Texture +} // namespace Pica diff --git a/src/video_core/texture/texture_decode.h b/src/video_core/texture/texture_decode.h new file mode 100644 index 000000000..8507cfeb8 --- /dev/null +++ b/src/video_core/texture/texture_decode.h @@ -0,0 +1,60 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "common/vector_math.h" +#include "video_core/regs_texturing.h" + +namespace Pica { +namespace Texture { + +/// Returns the byte size of a 8*8 tile of the specified texture format. +size_t CalculateTileSize(TexturingRegs::TextureFormat format); + +struct TextureInfo { + PAddr physical_address; + unsigned int width; + unsigned int height; + ptrdiff_t stride; + TexturingRegs::TextureFormat format; + + static TextureInfo FromPicaRegister(const TexturingRegs::TextureConfig& config, + const TexturingRegs::TextureFormat& format); + + /// Calculates stride from format and width, assuming that the entire texture is contiguous. + void SetDefaultStride() { + stride = CalculateTileSize(format) * (width / 8); + } +}; + +/** + * Lookup texel located at the given coordinates and return an RGBA vector of its color. + * @param source Source pointer to read data from + * @param x,y Texture coordinates to read from + * @param info TextureInfo object describing the texture setup + * @param disable_alpha This is used for debug widgets which use this method to display textures + * without providing a good way to visualize alpha by themselves. If true, this will return 255 for + * the alpha component, and either drop the information entirely or store it in an "unused" color + * channel. + * @todo Eventually we should get rid of the disable_alpha parameter. + */ +Math::Vec4<u8> LookupTexture(const u8* source, unsigned int x, unsigned int y, + const TextureInfo& info, bool disable_alpha = false); + +/** + * Looks up a texel from a single 8x8 texture tile. + * + * @param source Pointer to the beginning of the tile. + * @param x, y In-tile coordinates to read from. Must be < 8. + * @param info TextureInfo describing the texture format. + * @param disable_alpha Used for debugging. Sets the result alpha to 255 and either discards the + * real alpha or inserts it in an otherwise unused channel. + */ +Math::Vec4<u8> LookupTexelInTile(const u8* source, unsigned int x, unsigned int y, + const TextureInfo& info, bool disable_alpha); + +} // namespace Texture +} // namespace Pica diff --git a/src/video_core/vertex_loader.cpp b/src/video_core/vertex_loader.cpp index 2b8ef7018..37c5224a9 100644 --- a/src/video_core/vertex_loader.cpp +++ b/src/video_core/vertex_loader.cpp @@ -8,15 +8,15 @@ #include "common/vector_math.h" #include "core/memory.h" #include "video_core/debug_utils/debug_utils.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" #include "video_core/pica_types.h" +#include "video_core/regs_pipeline.h" #include "video_core/shader/shader.h" #include "video_core/vertex_loader.h" namespace Pica { -void VertexLoader::Setup(const Pica::Regs& regs) { +void VertexLoader::Setup(const PipelineRegs& regs) { ASSERT_MSG(!is_setup, "VertexLoader is not intended to be setup more than once."); const auto& attribute_config = regs.vertex_attributes; @@ -70,7 +70,8 @@ void VertexLoader::Setup(const Pica::Regs& regs) { is_setup = true; } -void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input, +void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, + Shader::AttributeBuffer& input, DebugUtils::MemoryAccessTracker& memory_accesses) { ASSERT_MSG(is_setup, "A VertexLoader needs to be setup before loading vertices."); @@ -84,15 +85,16 @@ void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::I memory_accesses.AddAccess( source_addr, vertex_attribute_elements[i] * - ((vertex_attribute_formats[i] == Regs::VertexAttributeFormat::FLOAT) + ((vertex_attribute_formats[i] == PipelineRegs::VertexAttributeFormat::FLOAT) ? 4 - : (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::SHORT) + : (vertex_attribute_formats[i] == + PipelineRegs::VertexAttributeFormat::SHORT) ? 2 : 1)); } switch (vertex_attribute_formats[i]) { - case Regs::VertexAttributeFormat::BYTE: { + case PipelineRegs::VertexAttributeFormat::BYTE: { const s8* srcdata = reinterpret_cast<const s8*>(Memory::GetPhysicalPointer(source_addr)); for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { @@ -100,7 +102,7 @@ void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::I } break; } - case Regs::VertexAttributeFormat::UBYTE: { + case PipelineRegs::VertexAttributeFormat::UBYTE: { const u8* srcdata = reinterpret_cast<const u8*>(Memory::GetPhysicalPointer(source_addr)); for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { @@ -108,7 +110,7 @@ void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::I } break; } - case Regs::VertexAttributeFormat::SHORT: { + case PipelineRegs::VertexAttributeFormat::SHORT: { const s16* srcdata = reinterpret_cast<const s16*>(Memory::GetPhysicalPointer(source_addr)); for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { @@ -116,7 +118,7 @@ void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::I } break; } - case Regs::VertexAttributeFormat::FLOAT: { + case PipelineRegs::VertexAttributeFormat::FLOAT: { const float* srcdata = reinterpret_cast<const float*>(Memory::GetPhysicalPointer(source_addr)); for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { @@ -142,7 +144,7 @@ void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::I input.attr[i][2].ToFloat32(), input.attr[i][3].ToFloat32()); } else if (vertex_attribute_is_default[i]) { // Load the default attribute if we're configured to do so - input.attr[i] = g_state.vs_default_attributes[i]; + input.attr[i] = g_state.input_default_attributes.attr[i]; LOG_TRACE(HW_GPU, "Loaded default attribute %x for vertex %x (index %x): (%f, %f, %f, %f)", i, vertex, index, input.attr[i][0].ToFloat32(), input.attr[i][1].ToFloat32(), diff --git a/src/video_core/vertex_loader.h b/src/video_core/vertex_loader.h index 9f2098bb2..02db10aee 100644 --- a/src/video_core/vertex_loader.h +++ b/src/video_core/vertex_loader.h @@ -2,7 +2,7 @@ #include <array> #include "common/common_types.h" -#include "video_core/pica.h" +#include "video_core/regs_pipeline.h" namespace Pica { @@ -11,18 +11,18 @@ class MemoryAccessTracker; } namespace Shader { -struct InputVertex; +struct AttributeBuffer; } class VertexLoader { public: VertexLoader() = default; - explicit VertexLoader(const Pica::Regs& regs) { + explicit VertexLoader(const PipelineRegs& regs) { Setup(regs); } - void Setup(const Pica::Regs& regs); - void LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input, + void Setup(const PipelineRegs& regs); + void LoadVertex(u32 base_address, int index, int vertex, Shader::AttributeBuffer& input, DebugUtils::MemoryAccessTracker& memory_accesses); int GetNumTotalAttributes() const { @@ -32,7 +32,7 @@ public: private: std::array<u32, 16> vertex_attribute_sources; std::array<u32, 16> vertex_attribute_strides{}; - std::array<Regs::VertexAttributeFormat, 16> vertex_attribute_formats; + std::array<PipelineRegs::VertexAttributeFormat, 16> vertex_attribute_formats; std::array<u32, 16> vertex_attribute_elements{}; std::array<bool, 16> vertex_attribute_is_default; int num_total_attributes = 0; |