diff --git a/.github/linux-appimage-qt.sh b/.github/linux-appimage-qt.sh index fe77c678c..06d5cbc11 100755 --- a/.github/linux-appimage-qt.sh +++ b/.github/linux-appimage-qt.sh @@ -9,6 +9,8 @@ fi export Qt6_DIR="/usr/lib/qt6" export PATH="$Qt6_DIR/bin:$PATH" +export EXTRA_QT_PLUGINS="waylandcompositor" +export EXTRA_PLATFORM_PLUGINS="libqwayland-egl.so;libqwayland-generic.so" # Prepare Tools for building the AppImage wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7950084cd..6b87ec418 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -287,7 +287,7 @@ jobs: submodules: recursive - name: Install dependencies - run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential libasound2-dev libpulse-dev libopenal-dev + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang build-essential libasound2-dev libpulse-dev libopenal-dev - name: Cache CMake Configuration uses: actions/cache@v4 @@ -343,7 +343,7 @@ jobs: submodules: recursive - name: Install dependencies - run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev - name: Cache CMake Configuration uses: actions/cache@v4 @@ -368,7 +368,7 @@ jobs: run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel3 - name: Run AppImage packaging script run: ./.github/linux-appimage-qt.sh diff --git a/.gitmodules b/.gitmodules index f4ef8d910..334048040 100644 --- a/.gitmodules +++ b/.gitmodules @@ -97,4 +97,8 @@ shallow = true [submodule "externals/LibAtrac9"] path = externals/LibAtrac9 - url = https://github.com/Vita3K/LibAtrac9 \ No newline at end of file + url = https://github.com/Vita3K/LibAtrac9 +[submodule "externals/discord-rpc"] + path = externals/discord-rpc + url = https://github.com/shadps4-emu/ext-discord-rpc.git + shallow = true \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index d51fd5e72..ed21ddb9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,7 @@ if(UNIX AND NOT APPLE) endif() option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF) - +option(ENABLE_DISCORD_RPC "Enable the Discord RPC integration" ON) # First, determine whether to use CMAKE_OSX_ARCHITECTURES or CMAKE_SYSTEM_PROCESSOR. if (APPLE AND CMAKE_OSX_ARCHITECTURES) set(BASE_ARCHITECTURE "${CMAKE_OSX_ARCHITECTURES}") @@ -47,9 +47,15 @@ else() message(FATAL_ERROR "Unsupported CPU architecture: ${BASE_ARCHITECTURE}") endif() -if (APPLE AND ARCHITECTURE STREQUAL "x86_64") +if (APPLE AND ARCHITECTURE STREQUAL "x86_64" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64") # Exclude ARM homebrew path to avoid conflicts when cross compiling. list(APPEND CMAKE_IGNORE_PREFIX_PATH "/opt/homebrew") + + # Need to reconfigure pkg-config to use the right architecture library paths. + # It's not ideal to override these but otherwise the build breaks just by having pkg-config installed. + set(ENV{PKG_CONFIG_DIR} "") + set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_SYSROOT}/usr/lib/pkgconfig:${CMAKE_SYSROOT}/usr/share/pkgconfig:${CMAKE_SYSROOT}/usr/local/lib/pkgconfig:${CMAKE_SYSROOT}/usr/local/share/pkgconfig") + set(ENV{PKG_CONFIG_SYSROOT_DIR} ${CMAKE_SYSROOT}) endif() # This function should be passed a list of all files in a target. It will automatically generate file groups @@ -103,6 +109,7 @@ find_package(Boost 1.84.0 CONFIG) find_package(FFmpeg 5.1.2 MODULE) find_package(fmt 10.2.0 CONFIG) find_package(glslang 14.2.0 CONFIG) +find_package(half 1.12.0 MODULE) find_package(magic_enum 0.9.6 CONFIG) find_package(RenderDoc 1.6.0 MODULE) find_package(SDL3 3.1.2 CONFIG) @@ -286,6 +293,14 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/audio3d/audio3d_error.h src/core/libraries/audio3d/audio3d_impl.cpp src/core/libraries/audio3d/audio3d_impl.h + src/core/libraries/ime/ime.cpp + src/core/libraries/ime/ime.h + src/core/libraries/game_live_streaming/gamelivestreaming.cpp + src/core/libraries/game_live_streaming/gamelivestreaming.h + src/core/libraries/remote_play/remoteplay.cpp + src/core/libraries/remote_play/remoteplay.h + src/core/libraries/share_play/shareplay.cpp + src/core/libraries/share_play/shareplay.h ) set(VIDEOOUT_LIB src/core/libraries/videoout/buffer.h @@ -301,6 +316,8 @@ set(LIBC_SOURCES src/core/libraries/libc_internal/libc_internal.cpp set(DIALOGS_LIB src/core/libraries/dialogs/error_dialog.cpp src/core/libraries/dialogs/error_dialog.h + src/core/libraries/dialogs/ime_dialog_ui.cpp + src/core/libraries/dialogs/ime_dialog_ui.h src/core/libraries/dialogs/ime_dialog.cpp src/core/libraries/dialogs/ime_dialog.h src/core/libraries/dialogs/error_codes.h @@ -328,6 +345,10 @@ set(USBD_LIB src/core/libraries/usbd/usbd.cpp src/core/libraries/usbd/usbd.h ) +set(FIBER_LIB src/core/libraries/fiber/fiber.cpp + src/core/libraries/fiber/fiber.h +) + set(NP_LIBS src/core/libraries/np_manager/np_manager.cpp src/core/libraries/np_manager/np_manager.h src/core/libraries/np_score/np_score.cpp @@ -344,15 +365,25 @@ set(MISC_LIBS src/core/libraries/screenshot/screenshot.cpp set(DEV_TOOLS src/core/devtools/layer.cpp src/core/devtools/layer.h + src/core/devtools/options.cpp + src/core/devtools/options.h src/core/devtools/gcn/gcn_context_regs.cpp src/core/devtools/gcn/gcn_op_names.cpp src/core/devtools/gcn/gcn_shader_regs.cpp src/core/devtools/widget/cmd_list.cpp src/core/devtools/widget/cmd_list.h + src/core/devtools/widget/common.h src/core/devtools/widget/frame_dump.cpp src/core/devtools/widget/frame_dump.h src/core/devtools/widget/frame_graph.cpp src/core/devtools/widget/frame_graph.h + src/core/devtools/widget/imgui_memory_editor.h + src/core/devtools/widget/reg_popup.cpp + src/core/devtools/widget/reg_popup.h + src/core/devtools/widget/reg_view.cpp + src/core/devtools/widget/reg_view.h + src/core/devtools/widget/text_editor.cpp + src/core/devtools/widget/text_editor.h ) set(COMMON src/common/logging/backend.cpp @@ -410,12 +441,18 @@ set(COMMON src/common/logging/backend.cpp src/common/version.h src/common/ntapi.h src/common/ntapi.cpp + src/common/number_utils.h + src/common/number_utils.cpp src/common/memory_patcher.h src/common/memory_patcher.cpp src/common/scm_rev.cpp src/common/scm_rev.h ) +if (ENABLE_DISCORD_RPC) + list(APPEND COMMON src/common/discord_rpc_handler.cpp src/common/discord_rpc_handler.h) +endif() + set(CORE src/core/aerolib/stubs.cpp src/core/aerolib/stubs.h src/core/aerolib/aerolib.cpp @@ -466,6 +503,7 @@ set(CORE src/core/aerolib/stubs.cpp ${USBD_LIB} ${MISC_LIBS} ${DIALOGS_LIB} + ${FIBER_LIB} ${DEV_TOOLS} src/core/debug_state.cpp src/core/debug_state.h @@ -695,6 +733,8 @@ set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/game_grid_frame.h src/qt_gui/game_install_dialog.cpp src/qt_gui/game_install_dialog.h + src/qt_gui/install_dir_select.cpp + src/qt_gui/install_dir_select.h src/qt_gui/pkg_viewer.cpp src/qt_gui/pkg_viewer.h src/qt_gui/trophy_viewer.cpp @@ -746,7 +786,7 @@ endif() create_target_directory_groups(shadps4) -target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui LibAtrac9 gcn) +target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui LibAtrac9 gcn half::half) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3 pugixml::pugixml) target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") @@ -769,9 +809,6 @@ if (APPLE) # Replacement for std::chrono::time_zone target_link_libraries(shadps4 PRIVATE date::date-tz) - - # Half float conversions for F16C patches - target_link_libraries(shadps4 PRIVATE half) endif() if (NOT ENABLE_QT_GUI) @@ -861,4 +898,17 @@ if (UNIX AND NOT APPLE) find_package(OpenSSL REQUIRED) target_link_libraries(shadps4 PRIVATE ${OPENSSL_LIBRARIES}) endif() -endif() \ No newline at end of file +endif() + +# Discord RPC +if (ENABLE_DISCORD_RPC) + target_link_libraries(shadps4 PRIVATE discord-rpc) +endif() + +# Install rules +install(TARGETS shadps4 BUNDLE DESTINATION .) + +if (ENABLE_QT_GUI AND CMAKE_SYSTEM_NAME STREQUAL "Linux") + install(FILES ".github/shadps4.desktop" DESTINATION "share/applications") + install(FILES ".github/shadps4.png" DESTINATION "share/icons/hicolor/512x512/apps") +endif() diff --git a/README.md b/README.md index da01833e5..18e69546c 100644 --- a/README.md +++ b/README.md @@ -36,14 +36,10 @@ SPDX-License-Identifier: GPL-2.0-or-later **shadPS4** is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++. -If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Quickstart/Quickstart.md). - -To verify that a game works, you can look at [**shadPS4 Game Compatibility**](https://github.com/shadps4-emu/shadps4-game-compatibility). - -To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/bFJxfftGW6). - -To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/). - +If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Quickstart/Quickstart.md).\ +To verify that a game works, you can look at [**shadPS4 Game Compatibility**](https://github.com/shadps4-emu/shadps4-game-compatibility).\ +To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/bFJxfftGW6).\ +To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/).\ For those who'd like to donate to the project, we now have a [**Kofi page**](https://ko-fi.com/shadps4)! # Status @@ -51,7 +47,7 @@ For those who'd like to donate to the project, we now have a [**Kofi page**](htt > [!IMPORTANT] > shadPS4 is early in development, don't expect a flawless experience. -Currently, the emulator successfully runs small games like [**Sonic Mania**](https://www.youtube.com/watch?v=AAHoNzhHyCU), [**Undertale**](https://youtu.be/5zIvdy65Ro4) and it can even run [**Bloodborne**](https://www.youtube.com/watch?v=wC6s0avpQRE). +Currently, the emulator can successfully run games like [**Bloodborne**](https://www.youtube.com/watch?v=wC6s0avpQRE), [**Dark Souls Remastered**](https://www.youtube.com/watch?v=-3PA-Xwszts), [**Red Dead Redemption**](https://www.youtube.com/watch?v=Al7yz_5nLag) and many other games. # Why @@ -102,7 +98,7 @@ PAD DOWN | DOWN | PAD LEFT | LEFT | PAD RIGHT | RIGHT | OPTIONS | RETURN | -TOUCH PAD | SPACE | +BACK BUTTON / TOUCH PAD | SPACE | L1 | Q | R1 | U | L2 | E | @@ -123,8 +119,7 @@ Logo is done by [**Xphalnos**](https://github.com/Xphalnos) # Contributing -If you want to contribute, please look the [**CONTRIBUTING.md**](https://github.com/shadps4-emu/shadPS4/blob/main/CONTRIBUTING.md) file. - +If you want to contribute, please look the [**CONTRIBUTING.md**](https://github.com/shadps4-emu/shadPS4/blob/main/CONTRIBUTING.md) file.\ Open a PR and we'll check it :) # Contributors diff --git a/cmake/Findhalf.cmake b/cmake/Findhalf.cmake new file mode 100644 index 000000000..f95a791df --- /dev/null +++ b/cmake/Findhalf.cmake @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +find_path(half_INCLUDE_DIR NAMES half.hpp PATH_SUFFIXES half) + +if (half_INCLUDE_DIR) + file(STRINGS "${half_INCLUDE_DIR}/half.hpp" _ver_line + REGEX "^// Version [0-9.]+$" + LIMIT_COUNT 1 + ) + string(REGEX MATCH "[0-9.]+" half_VERSION "${_ver_line}") + unset(_ver_line) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(half + REQUIRED_VARS half_INCLUDE_DIR + VERSION_VAR half_VERSION +) + +if (half_FOUND AND NOT TARGET half::half) + add_library(half::half INTERFACE IMPORTED) + set_target_properties(half::half PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${half_INCLUDE_DIR}" + ) +endif() + +mark_as_advanced(half_INCLUDE_DIR) diff --git a/documents/Quickstart/Quickstart.md b/documents/Quickstart/Quickstart.md index 7e496bcce..b2931e51e 100644 --- a/documents/Quickstart/Quickstart.md +++ b/documents/Quickstart/Quickstart.md @@ -7,21 +7,22 @@ SPDX-License-Identifier: GPL-2.0-or-later ## Summary -- [PC Requirements](#pc-requirements) - - [CPU](#cpu) - - [GPU](#gpu) - - [RAM](#ram) - - [OS](#os) -- [Have the latest WIP version](#have-the-latest-wip-version) -- [Install PKG files (Games and Updates)](#install-pkg-files) -- [Configure the emulator](#configure-the-emulator) +- [**PC Requirements**](#minimum-pc-requirements) + - [**CPU**](#cpu) + - [**GPU**](#gpu) + - [**RAM**](#ram) + - [**OS**](#os) +- [**Have the latest WIP version**](#how-to-run-the-latest-work-in-progress-builds-of-shadps4) +- [**Install PKG files (Games and Updates)**](#install-pkg-files) +- [**Configure the emulator**](#configure-the-emulator) -## PC Requirements +## Minimum PC requirements ### CPU - A processor with at least 4 cores and 6 threads - Above 2.5 GHz frequency +- required support AVX2 extension or Rosetta 2 on ARM ### GPU @@ -37,41 +38,25 @@ SPDX-License-Identifier: GPL-2.0-or-later - Windows 10 or Ubuntu 22.04 -## How to run the latest Work-in-Progress builds of ShadPS4 +## How to run the latest Work-in-Progress builds of shadPS4 1. Go to In the release identified as 'pre-release' click on the down arrow(Assets), select your operating system of choice (the "**qt**" versions have a user interface, which is probably the one you want. The others are SDL versions, which can only be run via command line). ![image](https://github.com/user-attachments/assets/af520c77-797c-41a0-8f67-d87f5de3e3df) -2. Once downloaded, extract to its own folder, and run ShadPS4's executable from the extracted folder. +2. Once downloaded, extract to its own folder, and run shadPS4's executable from the extracted folder. -3. Upon first launch, ShadPS4 will prompt you to select a folder to store your installed games in. Select "Browse" and then select a folder that ShadPS4 can use to install your PKG files to. +3. Upon first launch, shadPS4 will prompt you to select a folder to store your installed games in. Select "Browse" and then select a folder that shadPS4 can use to install your PKG files to. ## Install PKG files To install PKG files (game and updates), you will need the Qt application (with UI). You will have to go to "File" then to "Install Packages (PKG)", a window will open then you will have to select the files. You can install multiple PKG files at once. Once finished, the game should appear in the application. - + ## Configure the emulator -You can configure the emulator by editing the `config.toml` file found in the `user` folder created after starting the application.\ -Some settings may be related to more technical development and debugging. For more information on those, see [Debugging](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md#configuration). +To configure the emulator, you can go through the interface and go to "settings". -Here's a list of configuration entries that are worth changing: - -- `[General]` - - - `Fullscreen`: Display the game in a full screen borderless window. - - - `logType`: Configures logging synchronization (`sync`/`async`) - - It can be beneficial to set this to `sync` in order for the log to accurately maintain message order, at the cost of performance. - - Use when sending logs to developers. See more about [reporting issues](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md#reporting-and-communicating-about-issues). - - `logFilter`: Sets the logging category for various logging classes. - - Format: `: ...`, `: <*:level> ...` - - Valid log levels: `Trace, Debug, Info, Warning, Error, Critical` - in this order, setting a level silences all levels preceding it and logs every level after it. - - Examples: - - If the log is being spammed with messages coming from Lib.Pad, you can use `Lib.Pad:Critical` to only log critical-level messages. - - If you'd like to mute everything, but still want to receive messages from Vulkan rendering: `*:Error Render.Vulkan:Info` - -- `[GPU]` - - `screenWidth` and `screenHeight`: Configures the game window width and height. +You can also configure the emulator by editing the `config.toml` file located in the `user` folder created after the application is started (Mostly useful if you are using the SDL version). +Some settings may be related to more technical development and debugging.\ +For more information on this, see [**Debugging**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md#configuration). \ No newline at end of file diff --git a/documents/changelog.txt b/documents/changelog.txt index 33c3f77be..6df09472d 100644 --- a/documents/changelog.txt +++ b/documents/changelog.txt @@ -6,7 +6,7 @@ v0.3.0 23/09/2024 - codename broamic - New translations support (26 languages) - Support for unlocking trophies - Support for more controllers (Dualshock and Xbox) -- Many GUI imporovements +- Many GUI improvements - AVplayer v0.2.0 15/08/2024 - codename validptr diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 6df5399fc..a22eb9f11 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -3,7 +3,10 @@ set(BUILD_SHARED_LIBS OFF) set(BUILD_TESTING OFF) -set_property(DIRECTORY PROPERTY EXCLUDE_FROM_ALL ON) +set_directory_properties(PROPERTIES + EXCLUDE_FROM_ALL ON + SYSTEM ON +) if (MSVC) # Silence "deprecation" warnings @@ -13,11 +16,8 @@ endif() # Boost if (NOT TARGET Boost::headers) set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/externals/ext-boost" CACHE STRING "") - set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/externals/ext-boost" CACHE STRING "") set(Boost_NO_SYSTEM_PATHS ON CACHE BOOL "") - add_library(boost INTERFACE) - target_include_directories(boost SYSTEM INTERFACE ${Boost_INCLUDE_DIR}) - add_library(Boost::headers ALIAS boost) + add_subdirectory(ext-boost) endif() # fmtlib @@ -54,7 +54,7 @@ file(GLOB LIBATRAC9_SOURCES ) add_library(LibAtrac9 STATIC ${LIBATRAC9_SOURCES}) -target_include_directories(LibAtrac9 PUBLIC LibAtrac9/C/src) +target_include_directories(LibAtrac9 INTERFACE LibAtrac9/C/src) # Zlib-Ng if (NOT TARGET zlib-ng::zlib) @@ -86,7 +86,7 @@ endif() # RenderDoc if (NOT TARGET RenderDoc::API) add_library(renderdoc INTERFACE) - target_include_directories(renderdoc SYSTEM INTERFACE ./renderdoc) + target_include_directories(renderdoc INTERFACE ./renderdoc) add_library(RenderDoc::API ALIAS renderdoc) endif() @@ -150,11 +150,14 @@ if (WIN32) target_compile_options(sirit PUBLIC "-Wno-error=unused-command-line-argument") endif() -if (APPLE) - # half +# half +if (NOT TARGET half::half) add_library(half INTERFACE) target_include_directories(half INTERFACE half/include) + add_library(half::half ALIAS half) +endif() +if (APPLE) # date if (NOT TARGET date::date-tz) option(BUILD_TZ_LIB "" ON) @@ -193,5 +196,12 @@ if (NOT TARGET pugixml::pugixml) add_subdirectory(pugixml) endif() +# Discord RPC +if (ENABLE_DISCORD_RPC) + set(BUILD_EXAMPLES OFF) + add_subdirectory(discord-rpc/) + target_include_directories(discord-rpc INTERFACE discord-rpc/include) +endif() + # GCN Headers -add_subdirectory(gcn) \ No newline at end of file +add_subdirectory(gcn) diff --git a/externals/discord-rpc b/externals/discord-rpc new file mode 160000 index 000000000..4ec218155 --- /dev/null +++ b/externals/discord-rpc @@ -0,0 +1 @@ +Subproject commit 4ec218155d73bcb8022f8f7ca72305d801f84beb diff --git a/src/.vscode/c_cpp_properties.json b/src/.vscode/c_cpp_properties.json deleted file mode 100644 index c2098a2d0..000000000 --- a/src/.vscode/c_cpp_properties.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "configurations": [ - { - "name": "linux-gcc-x64", - "includePath": [ - "${workspaceFolder}/**" - ], - "compilerPath": "/usr/bin/gcc", - "cStandard": "${default}", - "cppStandard": "${default}", - "intelliSenseMode": "linux-gcc-x64", - "compilerArgs": [ - "" - ] - } - ], - "version": 4 -} \ No newline at end of file diff --git a/src/.vscode/launch.json b/src/.vscode/launch.json deleted file mode 100644 index d0452262e..000000000 --- a/src/.vscode/launch.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "C/C++ Runner: Debug Session", - "type": "cppdbg", - "request": "launch", - "args": [], - "stopAtEntry": false, - "externalConsole": false, - "cwd": "/home/turtle/Desktop/shadPS4/src", - "program": "/home/turtle/Desktop/shadPS4/src/build/Debug/outDebug", - "MIMode": "gdb", - "miDebuggerPath": "gdb", - "setupCommands": [ - { - "description": "Enable pretty-printing for gdb", - "text": "-enable-pretty-printing", - "ignoreFailures": true - } - ] - } - ] -} \ No newline at end of file diff --git a/src/.vscode/settings.json b/src/.vscode/settings.json deleted file mode 100644 index 3e5eb956e..000000000 --- a/src/.vscode/settings.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "C_Cpp_Runner.cCompilerPath": "gcc", - "C_Cpp_Runner.cppCompilerPath": "g++", - "C_Cpp_Runner.debuggerPath": "gdb", - "C_Cpp_Runner.cStandard": "", - "C_Cpp_Runner.cppStandard": "", - "C_Cpp_Runner.msvcBatchPath": "", - "C_Cpp_Runner.useMsvc": false, - "C_Cpp_Runner.warnings": [ - "-Wall", - "-Wextra", - "-Wpedantic", - "-Wshadow", - "-Wformat=2", - "-Wcast-align", - "-Wconversion", - "-Wsign-conversion", - "-Wnull-dereference" - ], - "C_Cpp_Runner.msvcWarnings": [ - "/W4", - "/permissive-", - "/w14242", - "/w14287", - "/w14296", - "/w14311", - "/w14826", - "/w44062", - "/w44242", - "/w14905", - "/w14906", - "/w14263", - "/w44265", - "/w14928" - ], - "C_Cpp_Runner.enableWarnings": true, - "C_Cpp_Runner.warningsAsError": false, - "C_Cpp_Runner.compilerArgs": [], - "C_Cpp_Runner.linkerArgs": [], - "C_Cpp_Runner.includePaths": [], - "C_Cpp_Runner.includeSearch": [ - "*", - "**/*" - ], - "C_Cpp_Runner.excludeSearch": [ - "**/build", - "**/build/**", - "**/.*", - "**/.*/**", - "**/.vscode", - "**/.vscode/**" - ], - "C_Cpp_Runner.useAddressSanitizer": false, - "C_Cpp_Runner.useUndefinedSanitizer": false, - "C_Cpp_Runner.useLeakSanitizer": false, - "C_Cpp_Runner.showCompilationTime": false, - "C_Cpp_Runner.useLinkTimeOptimization": false, - "C_Cpp_Runner.msvcSecureNoWarnings": false -} \ No newline at end of file diff --git a/src/audio_core/sdl_audio.cpp b/src/audio_core/sdl_audio.cpp index 894f5da55..7fed42a44 100644 --- a/src/audio_core/sdl_audio.cpp +++ b/src/audio_core/sdl_audio.cpp @@ -103,7 +103,7 @@ s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) { const size_t data_size = port.samples_num * port.sample_size * port.channels_num; - SDL_bool result = SDL_PutAudioStreamData(port.stream, ptr, data_size); + bool result = SDL_PutAudioStreamData(port.stream, ptr, data_size); lock.unlock(); // Unlock only after necessary operations diff --git a/src/common/bit_field.h b/src/common/bit_field.h index 72af7b5cd..bc8a2763c 100644 --- a/src/common/bit_field.h +++ b/src/common/bit_field.h @@ -81,7 +81,9 @@ #pragma pack(1) template struct BitField { -private: + + using Type = T; + // UnderlyingType is T for non-enum types and the underlying type of T if // T is an enumeration. Note that T is wrapped within an enable_if in the // former case to workaround compile errors which arise when using @@ -92,7 +94,6 @@ private: // We store the value as the unsigned type to avoid undefined behaviour on value shifting using StorageType = std::make_unsigned_t; -public: /// Constants to allow limited introspection of fields if needed static constexpr std::size_t position = Position; static constexpr std::size_t bits = Bits; diff --git a/src/common/config.cpp b/src/common/config.cpp index 8ac3c694b..1dde7223c 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -34,6 +34,7 @@ static bool isNeo = false; static bool isFullscreen = false; static bool playBGM = false; static int BGMvolume = 50; +static bool enableDiscordRPC = false; static u32 screenWidth = 1280; static u32 screenHeight = 720; static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select @@ -41,6 +42,7 @@ static std::string logFilter; static std::string logType = "async"; static std::string userName = "shadPS4"; static std::string updateChannel; +static std::string backButtonBehavior = "left"; static bool useSpecialPad = false; static int specialPadClass = 1; static bool isDebugDump = false; @@ -56,9 +58,12 @@ static bool vkValidationGpu = false; static bool rdocEnable = false; static bool vkMarkers = false; static bool vkCrashDiagnostic = false; +static s16 cursorState = HideCursorState::Idle; +static int cursorHideTimeout = 5; // 5 seconds (default) +static bool separateupdatefolder = false; // Gui -std::filesystem::path settings_install_dir = {}; +std::vector settings_install_dirs = {}; std::filesystem::path settings_addon_install_dir = {}; u32 main_window_geometry_x = 400; u32 main_window_geometry_y = 400; @@ -95,6 +100,18 @@ int getBGMvolume() { return BGMvolume; } +bool getEnableDiscordRPC() { + return enableDiscordRPC; +} + +s16 getCursorState() { + return cursorState; +} + +int getCursorHideTimeout() { + return cursorHideTimeout; +} + u32 getScreenWidth() { return screenWidth; } @@ -123,6 +140,10 @@ std::string getUpdateChannel() { return updateChannel; } +std::string getBackButtonBehavior() { + return backButtonBehavior; +} + bool getUseSpecialPad() { return useSpecialPad; } @@ -187,6 +208,10 @@ bool vkCrashDiagnosticEnabled() { return vkCrashDiagnostic; } +bool getSeparateUpdateEnabled() { + return separateupdatefolder; +} + void setGpuId(s32 selectedGpuId) { gpuId = selectedGpuId; } @@ -251,6 +276,18 @@ void setBGMvolume(int volume) { BGMvolume = volume; } +void setEnableDiscordRPC(bool enable) { + enableDiscordRPC = enable; +} + +void setCursorState(s16 newCursorState) { + cursorState = newCursorState; +} + +void setCursorHideTimeout(int newcursorHideTimeout) { + cursorHideTimeout = newcursorHideTimeout; +} + void setLanguage(u32 language) { m_language = language; } @@ -275,6 +312,10 @@ void setUpdateChannel(const std::string& type) { updateChannel = type; } +void setBackButtonBehavior(const std::string& type) { + backButtonBehavior = type; +} + void setUseSpecialPad(bool use) { useSpecialPad = use; } @@ -283,14 +324,29 @@ void setSpecialPadClass(int type) { specialPadClass = type; } +void setSeparateUpdateEnabled(bool use) { + separateupdatefolder = use; +} + void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) { main_window_geometry_x = x; main_window_geometry_y = y; main_window_geometry_w = w; main_window_geometry_h = h; } -void setGameInstallDir(const std::filesystem::path& dir) { - settings_install_dir = dir; +bool addGameInstallDir(const std::filesystem::path& dir) { + if (std::find(settings_install_dirs.begin(), settings_install_dirs.end(), dir) == + settings_install_dirs.end()) { + settings_install_dirs.push_back(dir); + return true; + } + return false; +} +void removeGameInstallDir(const std::filesystem::path& dir) { + auto iterator = std::find(settings_install_dirs.begin(), settings_install_dirs.end(), dir); + if (iterator != settings_install_dirs.end()) { + settings_install_dirs.erase(iterator); + } } void setAddonInstallDir(const std::filesystem::path& dir) { settings_addon_install_dir = dir; @@ -348,8 +404,8 @@ u32 getMainWindowGeometryW() { u32 getMainWindowGeometryH() { return main_window_geometry_h; } -std::filesystem::path getGameInstallDir() { - return settings_install_dir; +const std::vector& getGameInstallDirs() { + return settings_install_dirs; } std::filesystem::path getAddonInstallDir() { if (settings_addon_install_dir.empty()) { @@ -425,6 +481,7 @@ void load(const std::filesystem::path& path) { isFullscreen = toml::find_or(general, "Fullscreen", false); playBGM = toml::find_or(general, "playBGM", false); BGMvolume = toml::find_or(general, "BGMvolume", 50); + enableDiscordRPC = toml::find_or(general, "enableDiscordRPC", true); logFilter = toml::find_or(general, "logFilter", ""); logType = toml::find_or(general, "logType", "sync"); userName = toml::find_or(general, "userName", "shadPS4"); @@ -435,11 +492,15 @@ void load(const std::filesystem::path& path) { } isShowSplash = toml::find_or(general, "showSplash", true); isAutoUpdate = toml::find_or(general, "autoUpdate", false); + separateupdatefolder = toml::find_or(general, "separateUpdateEnabled", false); } if (data.contains("Input")) { const toml::value& input = data.at("Input"); + cursorState = toml::find_or(input, "cursorState", HideCursorState::Idle); + cursorHideTimeout = toml::find_or(input, "cursorHideTimeout", 5); + backButtonBehavior = toml::find_or(input, "backButtonBehavior", "left"); useSpecialPad = toml::find_or(input, "useSpecialPad", false); specialPadClass = toml::find_or(input, "specialPadClass", 1); } @@ -483,7 +544,19 @@ void load(const std::filesystem::path& path) { mw_themes = toml::find_or(gui, "theme", 0); m_window_size_W = toml::find_or(gui, "mw_width", 0); m_window_size_H = toml::find_or(gui, "mw_height", 0); - settings_install_dir = toml::find_fs_path_or(gui, "installDir", {}); + + // TODO Migration code, after a major release this should be removed. + auto old_game_install_dir = toml::find_fs_path_or(gui, "installDir", {}); + if (!old_game_install_dir.empty()) { + addGameInstallDir(std::filesystem::path{old_game_install_dir}); + } else { + const auto install_dir_array = + toml::find_or>(gui, "installDirs", {}); + for (const auto& dir : install_dir_array) { + addGameInstallDir(std::filesystem::path{dir}); + } + } + settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {}); main_window_geometry_x = toml::find_or(gui, "geometry_x", 0); main_window_geometry_y = toml::find_or(gui, "geometry_y", 0); @@ -527,12 +600,17 @@ void save(const std::filesystem::path& path) { data["General"]["Fullscreen"] = isFullscreen; data["General"]["playBGM"] = playBGM; data["General"]["BGMvolume"] = BGMvolume; + data["General"]["enableDiscordRPC"] = enableDiscordRPC; data["General"]["logFilter"] = logFilter; data["General"]["logType"] = logType; data["General"]["userName"] = userName; data["General"]["updateChannel"] = updateChannel; data["General"]["showSplash"] = isShowSplash; data["General"]["autoUpdate"] = isAutoUpdate; + data["General"]["separateUpdateEnabled"] = separateupdatefolder; + data["Input"]["cursorState"] = cursorState; + data["Input"]["cursorHideTimeout"] = cursorHideTimeout; + data["Input"]["backButtonBehavior"] = backButtonBehavior; data["Input"]["useSpecialPad"] = useSpecialPad; data["Input"]["specialPadClass"] = specialPadClass; data["GPU"]["screenWidth"] = screenWidth; @@ -557,7 +635,13 @@ void save(const std::filesystem::path& path) { data["GUI"]["gameTableMode"] = m_table_mode; data["GUI"]["mw_width"] = m_window_size_W; data["GUI"]["mw_height"] = m_window_size_H; - data["GUI"]["installDir"] = std::string{fmt::UTF(settings_install_dir.u8string()).data}; + + std::vector install_dirs; + for (const auto& dirString : settings_install_dirs) { + install_dirs.emplace_back(std::string{fmt::UTF(dirString.u8string()).data}); + } + data["GUI"]["installDirs"] = install_dirs; + data["GUI"]["addonInstallDir"] = std::string{fmt::UTF(settings_addon_install_dir.u8string()).data}; data["GUI"]["geometry_x"] = main_window_geometry_x; @@ -571,6 +655,9 @@ void save(const std::filesystem::path& path) { data["Settings"]["consoleLanguage"] = m_language; + // TODO Migration code, after a major release this should be removed. + data.at("GUI").as_table().erase("installDir"); + std::ofstream file(path, std::ios::out); file << data; file.close(); @@ -581,6 +668,7 @@ void setDefaultValues() { isFullscreen = false; playBGM = false; BGMvolume = 50; + enableDiscordRPC = true; screenWidth = 1280; screenHeight = 720; logFilter = ""; @@ -591,6 +679,9 @@ void setDefaultValues() { } else { updateChannel = "Nightly"; } + cursorState = HideCursorState::Idle; + cursorHideTimeout = 5; + backButtonBehavior = "left"; useSpecialPad = false; specialPadClass = 1; isDebugDump = false; diff --git a/src/common/config.h b/src/common/config.h index daee9e078..9c71c96a8 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -8,6 +8,9 @@ #include "types.h" namespace Config { + +enum HideCursorState : s16 { Never, Idle, Always }; + void load(const std::filesystem::path& path); void save(const std::filesystem::path& path); @@ -15,12 +18,17 @@ bool isNeoMode(); bool isFullscreenMode(); bool getPlayBGM(); int getBGMvolume(); +bool getEnableDiscordRPC(); +bool getSeparateUpdateEnabled(); std::string getLogFilter(); std::string getLogType(); std::string getUserName(); std::string getUpdateChannel(); +s16 getCursorState(); +int getCursorHideTimeout(); +std::string getBackButtonBehavior(); bool getUseSpecialPad(); int getSpecialPadClass(); @@ -50,11 +58,16 @@ void setScreenHeight(u32 height); void setFullscreenMode(bool enable); void setPlayBGM(bool enable); void setBGMvolume(int volume); +void setEnableDiscordRPC(bool enable); void setLanguage(u32 language); void setNeoMode(bool enable); void setUserName(const std::string& type); void setUpdateChannel(const std::string& type); +void setSeparateUpdateEnabled(bool use); +void setCursorState(s16 cursorState); +void setCursorHideTimeout(int newcursorHideTimeout); +void setBackButtonBehavior(const std::string& type); void setUseSpecialPad(bool use); void setSpecialPadClass(int type); @@ -73,7 +86,8 @@ bool vkCrashDiagnosticEnabled(); // Gui void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h); -void setGameInstallDir(const std::filesystem::path& dir); +bool addGameInstallDir(const std::filesystem::path& dir); +void removeGameInstallDir(const std::filesystem::path& dir); void setAddonInstallDir(const std::filesystem::path& dir); void setMainWindowTheme(u32 theme); void setIconSize(u32 size); @@ -92,7 +106,7 @@ u32 getMainWindowGeometryX(); u32 getMainWindowGeometryY(); u32 getMainWindowGeometryW(); u32 getMainWindowGeometryH(); -std::filesystem::path getGameInstallDir(); +const std::vector& getGameInstallDirs(); std::filesystem::path getAddonInstallDir(); u32 getMainWindowTheme(); u32 getIconSize(); diff --git a/src/common/cstring.h b/src/common/cstring.h index fb29443ee..45c291c14 100644 --- a/src/common/cstring.h +++ b/src/common/cstring.h @@ -81,34 +81,42 @@ public: return std::basic_string_view{data}; } - char* begin() { + T* begin() { if (this == nullptr) { return nullptr; } return data; } - const char* begin() const { + const T* begin() const { if (this == nullptr) { return nullptr; } return data; } - char* end() { + T* end() { if (this == nullptr) { return nullptr; } return data + N; } - const char* end() const { + const T* end() const { if (this == nullptr) { return nullptr; } return data + N; } + constexpr std::size_t capacity() const { + return N; + } + + std::size_t size() const { + return std::char_traits::length(data); + } + T& operator[](size_t idx) { return data[idx]; } @@ -152,6 +160,12 @@ public: static_assert(sizeof(CString<13>) == sizeof(char[13])); // Ensure size still matches a simple array static_assert(std::weakly_incrementable::Iterator>); +template +using CWString = CString; + +template +using CU16String = CString; + #pragma clang diagnostic pop } // namespace Common \ No newline at end of file diff --git a/src/common/discord.cpp b/src/common/discord.cpp deleted file mode 100644 index cce799a32..000000000 --- a/src/common/discord.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include "common/discord.h" - -namespace Discord { - -void RPC::init() { - DiscordEventHandlers handlers{}; - Discord_Initialize("1139939140494971051", &handlers, 1, nullptr); - - startTimestamp = time(nullptr); - enabled = true; -} - -void RPC::update(Discord::RPCStatus status, const std::string& game) { - DiscordRichPresence rpc{}; - - if (status == Discord::RPCStatus::Playing) { - rpc.details = "Playing a game"; - rpc.state = game.c_str(); - } else { - rpc.details = "Idle"; - } - - rpc.largeImageKey = "shadps4"; - rpc.largeImageText = "ShadPS4 is a PS4 emulator"; - rpc.startTimestamp = startTimestamp; - - Discord_UpdatePresence(&rpc); -} - -void RPC::stop() { - if (enabled) { - enabled = false; - Discord_ClearPresence(); - Discord_Shutdown(); - } -} - -} // namespace Discord diff --git a/src/common/discord_rpc_handler.cpp b/src/common/discord_rpc_handler.cpp new file mode 100644 index 000000000..91b278a15 --- /dev/null +++ b/src/common/discord_rpc_handler.cpp @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include "src/common/discord_rpc_handler.h" + +namespace DiscordRPCHandler { + +void RPC::init() { + DiscordEventHandlers handlers{}; + + Discord_Initialize("1139939140494971051", &handlers, 1, nullptr); + startTimestamp = time(nullptr); + rpcEnabled = true; +} + +void RPC::setStatusIdling() { + DiscordRichPresence rpc{}; + rpc.largeImageKey = "https://github.com/shadps4-emu/shadPS4/raw/main/.github/shadps4.png"; + rpc.largeImageText = "shadPS4 is a PS4 emulator"; + rpc.startTimestamp = startTimestamp; + rpc.details = "Idle"; + + status = RPCStatus::Idling; + Discord_UpdatePresence(&rpc); +} + +void RPC::setStatusPlaying(const std::string& game_name, const std::string& game_id) { + DiscordRichPresence rpc{}; + + rpc.details = "Playing"; + rpc.state = game_name.c_str(); + std::string largeImageUrl = + "https://store.playstation.com/store/api/chihiro/00_09_000/titlecontainer/US/en/999/" + + game_id + "_00/image"; + rpc.largeImageKey = largeImageUrl.c_str(); + rpc.largeImageText = game_name.c_str(); + rpc.startTimestamp = startTimestamp; + + status = RPCStatus::Playing; + Discord_UpdatePresence(&rpc); +} + +void RPC::shutdown() { + if (rpcEnabled) { + rpcEnabled = false; + Discord_ClearPresence(); + Discord_Shutdown(); + } +} + +bool RPC::getRPCEnabled() { + return rpcEnabled; +} + +} // namespace DiscordRPCHandler diff --git a/src/common/discord.h b/src/common/discord_rpc_handler.h similarity index 53% rename from src/common/discord.h rename to src/common/discord_rpc_handler.h index 54aa6c17c..1e451e181 100644 --- a/src/common/discord.h +++ b/src/common/discord_rpc_handler.h @@ -7,7 +7,7 @@ #include #include -namespace Discord { +namespace DiscordRPCHandler { enum class RPCStatus { Idling, @@ -16,12 +16,15 @@ enum class RPCStatus { class RPC { std::uint64_t startTimestamp; - bool enabled = false; + bool rpcEnabled = false; + RPCStatus status; public: void init(); - void update(RPCStatus status, const std::string& title); - void stop(); + void setStatusIdling(); + void setStatusPlaying(const std::string& game_name, const std::string& game_id); + void shutdown(); + bool getRPCEnabled(); }; -} // namespace Discord +} // namespace DiscordRPCHandler diff --git a/src/common/io_file.cpp b/src/common/io_file.cpp index 1b28d2bba..dd3a40cae 100644 --- a/src/common/io_file.cpp +++ b/src/common/io_file.cpp @@ -231,7 +231,7 @@ void IOFile::Unlink() { // Mark the file for deletion // TODO: Also remove the file path? -#if _WIN64 +#ifdef _WIN64 FILE_DISPOSITION_INFORMATION disposition; IO_STATUS_BLOCK iosb; @@ -242,7 +242,11 @@ void IOFile::Unlink() { NtSetInformationFile(hfile, &iosb, &disposition, sizeof(disposition), FileDispositionInformation); #else - UNREACHABLE_MSG("Missing Linux implementation"); + if (unlink(file_path.c_str()) != 0) { + const auto ec = std::error_code{errno, std::generic_category()}; + LOG_ERROR(Common_Filesystem, "Failed to unlink the file at path={}, ec_message={}", + PathToUTF8String(file_path), ec.message()); + } #endif } diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index c3088f926..051bbd79e 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -114,6 +114,11 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Lib, AvPlayer) \ SUB(Lib, Ngs2) \ SUB(Lib, Audio3d) \ + SUB(Lib, Ime) \ + SUB(Lib, GameLiveStreaming) \ + SUB(Lib, Remoteplay) \ + SUB(Lib, SharePlay) \ + SUB(Lib, Fiber) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Vulkan) \ diff --git a/src/common/logging/types.h b/src/common/logging/types.h index 749568da1..39319d0dc 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -29,67 +29,72 @@ enum class Level : u8 { * filter.cpp. */ enum class Class : u8 { - Log, ///< Messages about the log system itself - Common, ///< Library routines - Common_Filesystem, ///< Filesystem interface library - Common_Memory, ///< Memory mapping and management functions - Core, ///< LLE emulation core - Core_Linker, ///< The module linker - Config, ///< Emulator configuration (including commandline) - Debug, ///< Debugging tools - Kernel, ///< The HLE implementation of the PS4 kernel. - Kernel_Pthread, ///< The pthread implementation of the kernel. - Kernel_Fs, ///< The filesystem implementation of the kernel. - Kernel_Vmm, ///< The virtual memory implementation of the kernel. - Kernel_Event, ///< The event management implementation of the kernel. - Kernel_Sce, ///< The sony specific interfaces provided by the kernel. - Lib, ///< HLE implementation of system library. Each major library - ///< should have its own subclass. - Lib_LibC, ///< The LibC implementation. - Lib_Kernel, ///< The LibKernel implementation. - Lib_Pad, ///< The LibScePad implementation. - Lib_GnmDriver, ///< The LibSceGnmDriver implementation. - Lib_SystemService, ///< The LibSceSystemService implementation. - Lib_UserService, ///< The LibSceUserService implementation. - Lib_VideoOut, ///< The LibSceVideoOut implementation. - Lib_CommonDlg, ///< The LibSceCommonDialog implementation. - Lib_MsgDlg, ///< The LibSceMsgDialog implementation. - Lib_AudioOut, ///< The LibSceAudioOut implementation. - Lib_AudioIn, ///< The LibSceAudioIn implementation. - Lib_Net, ///< The LibSceNet implementation. - Lib_NetCtl, ///< The LibSecNetCtl implementation. - Lib_SaveData, ///< The LibSceSaveData implementation. - Lib_SaveDataDialog, ///< The LibSceSaveDataDialog implementation. - Lib_Ssl, ///< The LibSceSsl implementation. - Lib_Http, ///< The LibSceHttp implementation. - Lib_SysModule, ///< The LibSceSysModule implementation - Lib_NpManager, ///< The LibSceNpManager implementation - Lib_NpScore, ///< The LibSceNpScore implementation - Lib_NpTrophy, ///< The LibSceNpTrophy implementation - Lib_Screenshot, ///< The LibSceScreenshot implementation - Lib_LibCInternal, ///< The LibCInternal implementation. - Lib_AppContent, ///< The LibSceAppContent implementation. - Lib_Rtc, ///< The LibSceRtc implementation. - Lib_DiscMap, ///< The LibSceDiscMap implementation. - Lib_Png, ///< The LibScePng implementation. - Lib_PlayGo, ///< The LibScePlayGo implementation. - Lib_Random, ///< The libSceRandom implementation. - Lib_Usbd, ///< The LibSceUsbd implementation. - Lib_Ajm, ///< The LibSceAjm implementation. - Lib_ErrorDialog, ///< The LibSceErrorDialog implementation. - Lib_ImeDialog, ///< The LibSceImeDialog implementation. - Lib_AvPlayer, ///< The LibSceAvPlayer implementation. - Lib_Ngs2, ///< The LibSceNgs2 implementation. - Lib_Audio3d, ///< The LibSceAudio3d implementation. - Frontend, ///< Emulator UI - Render, ///< Video Core - Render_Vulkan, ///< Vulkan backend - Render_Recompiler, ///< Shader recompiler - ImGui, ///< ImGui - Loader, ///< ROM loader - Input, ///< Input emulation - Tty, ///< Debug output from emu - Count ///< Total number of logging classes + Log, ///< Messages about the log system itself + Common, ///< Library routines + Common_Filesystem, ///< Filesystem interface library + Common_Memory, ///< Memory mapping and management functions + Core, ///< LLE emulation core + Core_Linker, ///< The module linker + Config, ///< Emulator configuration (including commandline) + Debug, ///< Debugging tools + Kernel, ///< The HLE implementation of the PS4 kernel. + Kernel_Pthread, ///< The pthread implementation of the kernel. + Kernel_Fs, ///< The filesystem implementation of the kernel. + Kernel_Vmm, ///< The virtual memory implementation of the kernel. + Kernel_Event, ///< The event management implementation of the kernel. + Kernel_Sce, ///< The sony specific interfaces provided by the kernel. + Lib, ///< HLE implementation of system library. Each major library + ///< should have its own subclass. + Lib_LibC, ///< The LibC implementation. + Lib_Kernel, ///< The LibKernel implementation. + Lib_Pad, ///< The LibScePad implementation. + Lib_GnmDriver, ///< The LibSceGnmDriver implementation. + Lib_SystemService, ///< The LibSceSystemService implementation. + Lib_UserService, ///< The LibSceUserService implementation. + Lib_VideoOut, ///< The LibSceVideoOut implementation. + Lib_CommonDlg, ///< The LibSceCommonDialog implementation. + Lib_MsgDlg, ///< The LibSceMsgDialog implementation. + Lib_AudioOut, ///< The LibSceAudioOut implementation. + Lib_AudioIn, ///< The LibSceAudioIn implementation. + Lib_Net, ///< The LibSceNet implementation. + Lib_NetCtl, ///< The LibSecNetCtl implementation. + Lib_SaveData, ///< The LibSceSaveData implementation. + Lib_SaveDataDialog, ///< The LibSceSaveDataDialog implementation. + Lib_Ssl, ///< The LibSceSsl implementation. + Lib_Http, ///< The LibSceHttp implementation. + Lib_SysModule, ///< The LibSceSysModule implementation + Lib_NpManager, ///< The LibSceNpManager implementation + Lib_NpScore, ///< The LibSceNpScore implementation + Lib_NpTrophy, ///< The LibSceNpTrophy implementation + Lib_Screenshot, ///< The LibSceScreenshot implementation + Lib_LibCInternal, ///< The LibCInternal implementation. + Lib_AppContent, ///< The LibSceAppContent implementation. + Lib_Rtc, ///< The LibSceRtc implementation. + Lib_DiscMap, ///< The LibSceDiscMap implementation. + Lib_Png, ///< The LibScePng implementation. + Lib_PlayGo, ///< The LibScePlayGo implementation. + Lib_Random, ///< The libSceRandom implementation. + Lib_Usbd, ///< The LibSceUsbd implementation. + Lib_Ajm, ///< The LibSceAjm implementation. + Lib_ErrorDialog, ///< The LibSceErrorDialog implementation. + Lib_ImeDialog, ///< The LibSceImeDialog implementation. + Lib_AvPlayer, ///< The LibSceAvPlayer implementation. + Lib_Ngs2, ///< The LibSceNgs2 implementation. + Lib_Audio3d, ///< The LibSceAudio3d implementation. + Lib_Ime, ///< The LibSceIme implementation + Lib_GameLiveStreaming, ///< The LibSceGameLiveStreaming implementation + Lib_Remoteplay, ///< The LibSceRemotePlay implementation + Lib_SharePlay, ///< The LibSceSharePlay implemenation + Lib_Fiber, ///< The LibSceFiber implementation. + Frontend, ///< Emulator UI + Render, ///< Video Core + Render_Vulkan, ///< Vulkan backend + Render_Recompiler, ///< Shader recompiler + ImGui, ///< ImGui + Loader, ///< ROM loader + Input, ///< Input emulation + Tty, ///< Debug output from emu + Count ///< Total number of logging classes }; } // namespace Common::Log diff --git a/src/common/number_utils.cpp b/src/common/number_utils.cpp new file mode 100644 index 000000000..af29e5cd3 --- /dev/null +++ b/src/common/number_utils.cpp @@ -0,0 +1,161 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "common/number_utils.h" +#include "video_core/amdgpu/pixel_format.h" +#include "video_core/amdgpu/types.h" + +#define UF11_EXPONENT_SHIFT 6 +#define UF10_EXPONENT_SHIFT 5 + +#define RGB9E5_MANTISSA_BITS 9 +#define RGB9E5_EXP_BIAS 1 + +#define F32_INFINITY 0x7f800000 + +namespace NumberUtils { + +float Uf11ToF32(u16 val) { + union { + float f; + u32 ui; + } f32; + + int exponent = (val & 0x07c0) >> UF11_EXPONENT_SHIFT; + int mantissa = (val & 0x003f); + + f32.f = 0.0; + + if (exponent == 0) { + if (mantissa != 0) { + const float scale = 1.0 / (1 << 20); + f32.f = scale * mantissa; + } + } else if (exponent == 31) { + f32.ui = F32_INFINITY | mantissa; + } else { + float scale, decimal; + exponent -= 15; + if (exponent < 0) { + scale = 1.0f / (1 << -exponent); + } else { + scale = (float)(1 << exponent); + } + decimal = 1.0f + (float)mantissa / 64; + f32.f = scale * decimal; + } + + return f32.f; +} + +float Uf10ToF32(u16 val) { + union { + float f; + u32 ui; + } f32; + + int exponent = (val & 0x03e0) >> UF10_EXPONENT_SHIFT; + int mantissa = (val & 0x001f); + + f32.f = 0.0; + + if (exponent == 0) { + if (mantissa != 0) { + const float scale = 1.0 / (1 << 19); + f32.f = scale * mantissa; + } + } else if (exponent == 31) { + f32.ui = F32_INFINITY | mantissa; + } else { + float scale, decimal; + exponent -= 15; + if (exponent < 0) { + scale = 1.0f / (1 << -exponent); + } else { + scale = (float)(1 << exponent); + } + decimal = 1.0f + (float)mantissa / 32; + f32.f = scale * decimal; + } + + return f32.f; +} + +float Uf16ToF32(u16 val) { + return half_float::half_cast(reinterpret_cast(val)); +} + +float U2ToUnorm(u8 val) { + static constexpr auto c = 1.0f / 3.0f; + return float(val * c); +} + +float S2ToSnorm(s8 val) { + static constexpr auto c = 1.0f / 1.0f; + return float(val * c); +} + +float U4ToUnorm(u8 val) { + static constexpr auto c = 1.0f / 15.0f; + return float(val * c); +} + +float S4ToSnorm(s8 val) { + static constexpr auto c = 1.0f / 7.0f; + return float(val * c); +} + +float U5ToUnorm(u8 val) { + static constexpr auto c = 1.0f / 31.0f; + return float(val * c); +} + +float S5ToSnorm(s8 val) { + static constexpr auto c = 1.0f / 15.0f; + return float(val * c); +} + +float U6ToUnorm(u8 val) { + static constexpr auto c = 1.0f / 63.0f; + return float(val * c); +} + +float S6ToSnorm(s8 val) { + static constexpr auto c = 1.0f / 31.0f; + return float(val * c); +} + +float U8ToUnorm(u8 val) { + static constexpr auto c = 1.0f / 255.0f; + return float(val * c); +} + +float S8ToSnorm(s8 val) { + static constexpr auto c = 1.0f / 127.0f; + return float(val * c); +} + +float U10ToUnorm(u16 val) { + static constexpr auto c = 1.0f / 1023.0f; + return float(val * c); +} + +float S10ToSnorm(s16 val) { + static constexpr auto c = 1.0f / 511.0f; + return float(val * c); +} + +float U16ToUnorm(u16 val) { + static constexpr auto c = 1.0f / 65535.0f; + return float(val * c); +} + +float S16ToSnorm(s16 val) { + static constexpr auto c = 1.0f / 32767.0f; + return float(val * c); +} + +} // namespace NumberUtils \ No newline at end of file diff --git a/src/common/number_utils.h b/src/common/number_utils.h new file mode 100644 index 000000000..05c62a1cb --- /dev/null +++ b/src/common/number_utils.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace NumberUtils { + +float Uf11ToF32(u16 val); +float Uf10ToF32(u16 val); +float Uf16ToF32(u16 val); +float U2ToUnorm(u8 val); +float S2ToSnorm(s8 val); +float U4ToUnorm(u8 val); +float S4ToSnorm(s8 val); +float U5ToUnorm(u8 val); +float S5ToSnorm(s8 val); +float U6ToUnorm(u8 val); +float S6ToSnorm(s8 val); +float U8ToUnorm(u8 val); +float S8ToSnorm(s8 val); +float U10ToUnorm(u16 val); +float S10ToSnorm(s16 val); +float U16ToUnorm(u16 val); +float S16ToSnorm(s16 val); + +} // namespace NumberUtils diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index 27098e2d1..7551d3b05 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -95,6 +95,18 @@ static auto UserPaths = [] { user_dir = std::filesystem::path(getenv("HOME")) / "Library" / "Application Support" / "shadPS4"; } +#elif defined(__linux__) + auto user_dir = std::filesystem::current_path() / PORTABLE_DIR; + // Check if the "user" directory exists in the current path: + if (!std::filesystem::exists(user_dir)) { + // If it doesn't exist, use XDG_DATA_HOME if it is set, and provide a standard default + const char* xdg_data_home = getenv("XDG_DATA_HOME"); + if (xdg_data_home != nullptr && strlen(xdg_data_home) > 0) { + user_dir = std::filesystem::path(xdg_data_home) / "shadPS4"; + } else { + user_dir = std::filesystem::path(getenv("HOME")) / ".local" / "share" / "shadPS4"; + } + } #else const auto user_dir = std::filesystem::current_path() / PORTABLE_DIR; #endif diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index 48748e4c3..8ba99e32d 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -25,19 +25,18 @@ asm(".zerofill GUEST_SYSTEM,GUEST_SYSTEM,__guest_system,0xFBFC00000"); namespace Core { -static constexpr size_t BackingSize = SCE_KERNEL_MAIN_DMEM_SIZE; +static constexpr size_t BackingSize = SCE_KERNEL_MAIN_DMEM_SIZE_PRO; #ifdef _WIN32 [[nodiscard]] constexpr u64 ToWindowsProt(Core::MemoryProt prot) { - switch (prot) { - case Core::MemoryProt::NoAccess: - default: - return PAGE_NOACCESS; - case Core::MemoryProt::CpuRead: - return PAGE_READONLY; - case Core::MemoryProt::CpuReadWrite: + if (True(prot & Core::MemoryProt::CpuReadWrite) || + True(prot & Core::MemoryProt::GpuReadWrite)) { return PAGE_READWRITE; + } else if (True(prot & Core::MemoryProt::CpuRead) || True(prot & Core::MemoryProt::GpuRead)) { + return PAGE_READONLY; + } else { + return PAGE_NOACCESS; } } @@ -290,14 +289,13 @@ enum PosixPageProtection { }; [[nodiscard]] constexpr PosixPageProtection ToPosixProt(Core::MemoryProt prot) { - switch (prot) { - case Core::MemoryProt::NoAccess: - default: - return PAGE_NOACCESS; - case Core::MemoryProt::CpuRead: - return PAGE_READONLY; - case Core::MemoryProt::CpuReadWrite: + if (True(prot & Core::MemoryProt::CpuReadWrite) || + True(prot & Core::MemoryProt::GpuReadWrite)) { return PAGE_READWRITE; + } else if (True(prot & Core::MemoryProt::CpuRead) || True(prot & Core::MemoryProt::GpuRead)) { + return PAGE_READONLY; + } else { + return PAGE_NOACCESS; } } diff --git a/src/core/address_space.h b/src/core/address_space.h index 3233c7588..7ccc2cd1e 100644 --- a/src/core/address_space.h +++ b/src/core/address_space.h @@ -45,6 +45,10 @@ public: explicit AddressSpace(); ~AddressSpace(); + [[nodiscard]] u8* BackingBase() const noexcept { + return backing_base; + } + [[nodiscard]] VAddr SystemManagedVirtualBase() noexcept { return reinterpret_cast(system_managed_base); } diff --git a/src/core/debug_state.cpp b/src/core/debug_state.cpp index 050143e6e..adcb0cadb 100644 --- a/src/core/debug_state.cpp +++ b/src/core/debug_state.cpp @@ -1,13 +1,16 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + #include "common/assert.h" #include "common/native_clock.h" #include "common/singleton.h" #include "debug_state.h" -#include "libraries/kernel/event_queues.h" +#include "devtools/widget/common.h" #include "libraries/kernel/time_management.h" #include "libraries/system/msgdialog.h" +#include "video_core/amdgpu/pm4_cmds.h" using namespace DebugStateType; @@ -95,8 +98,81 @@ void DebugStateImpl::ResumeGuestThreads() { } void DebugStateImpl::RequestFrameDump(s32 count) { + ASSERT(!DumpingCurrentFrame()); gnm_frame_dump_request_count = count; frame_dump_list.clear(); frame_dump_list.resize(count); + const auto f = gnm_frame_count.load() + 1; + for (size_t i = 0; i < count; ++i) { + frame_dump_list[i].frame_id = f + i; + } waiting_submit_pause = true; } + +void DebugStateImpl::PushQueueDump(QueueDump dump) { + ASSERT(DumpingCurrentFrame()); + std::unique_lock lock{frame_dump_list_mutex}; + auto& frame = GetFrameDump(); + { // Find draw calls + auto data = std::span{dump.data}; + auto initial_data = data.data(); + while (!data.empty()) { + const auto* header = reinterpret_cast(data.data()); + const auto type = header->type; + if (type == 2) { + data = data.subspan(1); + } else if (type != 3) { + UNREACHABLE(); + } + const AmdGpu::PM4ItOpcode opcode = header->opcode; + if (Core::Devtools::Widget::IsDrawCall(opcode)) { + const auto offset = + reinterpret_cast(header) - reinterpret_cast(initial_data); + const auto addr = dump.base_addr + offset; + waiting_reg_dumps.emplace(addr, &frame); + waiting_reg_dumps_dbg.emplace( + addr, + fmt::format("#{} h({}) queue {} {} {}", + frame_dump_list.size() - gnm_frame_dump_request_count, addr, + magic_enum::enum_name(dump.type), dump.submit_num, dump.num2)); + } + data = data.subspan(header->NumWords() + 1); + } + } + frame.queues.push_back(std::move(dump)); +} + +void DebugStateImpl::PushRegsDump(uintptr_t base_addr, uintptr_t header_addr, + const AmdGpu::Liverpool::Regs& regs, bool is_compute) { + std::scoped_lock lock{frame_dump_list_mutex}; + const auto it = waiting_reg_dumps.find(header_addr); + if (it == waiting_reg_dumps.end()) { + return; + } + auto& frame = *it->second; + waiting_reg_dumps.erase(it); + waiting_reg_dumps_dbg.erase(waiting_reg_dumps_dbg.find(header_addr)); + auto& dump = frame.regs[header_addr - base_addr]; + dump.regs = regs; + if (is_compute) { + dump.is_compute = true; + const auto& cs = dump.regs.cs_program; + dump.cs_data = ComputerShaderDump{ + .cs_program = cs, + .code = std::vector{cs.Code().begin(), cs.Code().end()}, + }; + } else { + for (int i = 0; i < RegDump::MaxShaderStages; i++) { + if (regs.stage_enable.IsStageEnabled(i)) { + auto stage = regs.ProgramForStage(i); + if (stage->address_lo != 0) { + auto code = stage->Code(); + dump.stages[i] = ShaderDump{ + .user_data = *stage, + .code = std::vector{code.begin(), code.end()}, + }; + } + } + } + } +} diff --git a/src/core/debug_state.h b/src/core/debug_state.h index 00c687fa5..cd1c6aa93 100644 --- a/src/core/debug_state.h +++ b/src/core/debug_state.h @@ -5,10 +5,14 @@ #include #include +#include +#include #include #include #include "common/types.h" +#include "video_core/amdgpu/liverpool.h" +#include "video_core/renderer_vulkan/vk_pipeline_cache.h" #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN @@ -32,9 +36,9 @@ class FrameGraph; namespace DebugStateType { enum class QueueType { - acb, - dcb, - ccb, + dcb = 0, + ccb = 1, + acb = 2, }; struct QueueDump { @@ -42,10 +46,31 @@ struct QueueDump { u32 submit_num; u32 num2; // acb: queue_num; else: buffer_in_submit std::vector data; + uintptr_t base_addr; +}; + +struct ShaderDump { + Vulkan::Liverpool::ShaderProgram user_data{}; + std::vector code{}; +}; + +struct ComputerShaderDump { + Vulkan::Liverpool::ComputeProgram cs_program{}; + std::vector code{}; +}; + +struct RegDump { + bool is_compute{false}; + static constexpr size_t MaxShaderStages = 5; + Vulkan::Liverpool::Regs regs{}; + std::array stages{}; + ComputerShaderDump cs_data{}; }; struct FrameDump { + u32 frame_id; std::vector queues; + std::unordered_map regs; // address -> reg dump }; class DebugStateImpl { @@ -61,15 +86,24 @@ class DebugStateImpl { std::atomic_int32_t gnm_frame_count = 0; s32 gnm_frame_dump_request_count = -1; + std::unordered_map waiting_reg_dumps; + std::unordered_map waiting_reg_dumps_dbg; bool waiting_submit_pause = false; bool should_show_frame_dump = false; - std::mutex frame_dump_list_mutex; + std::shared_mutex frame_dump_list_mutex; std::vector frame_dump_list{}; std::queue debug_message_popup; public: + void ShowDebugMessage(std::string message) { + if (message.empty()) { + return; + } + debug_message_popup.push(std::move(message)); + } + void AddCurrentThreadToGuestList(); void RemoveCurrentThreadFromGuestList(); @@ -99,6 +133,11 @@ public: return gnm_frame_dump_request_count > 0; } + bool DumpingCurrentReg() { + std::shared_lock lock{frame_dump_list_mutex}; + return !waiting_reg_dumps.empty(); + } + bool ShouldPauseInSubmit() const { return waiting_submit_pause && gnm_frame_dump_request_count == 0; } @@ -109,17 +148,10 @@ public: return frame_dump_list[frame_dump_list.size() - gnm_frame_dump_request_count]; } - void PushQueueDump(QueueDump dump) { - std::unique_lock lock{frame_dump_list_mutex}; - GetFrameDump().queues.push_back(std::move(dump)); - } + void PushQueueDump(QueueDump dump); - void ShowDebugMessage(std::string message) { - if (message.empty()) { - return; - } - debug_message_popup.push(std::move(message)); - } + void PushRegsDump(uintptr_t base_addr, uintptr_t header_addr, + const AmdGpu::Liverpool::Regs& regs, bool is_compute = false); }; } // namespace DebugStateType diff --git a/src/core/devtools/help.txt b/src/core/devtools/help.txt new file mode 100644 index 000000000..9670c5cea --- /dev/null +++ b/src/core/devtools/help.txt @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later +R"( +* If you hold shift, you can move the window without docking it. +* You don't need to close every window you open. When a parent window is closed, all its children will be closed too. +* If you want to inspect or compare more than 1 frame dump without undocking, there's a option to keep showing opened popups even when in hide/minimize the frame dump window. +* To use the disassembly viewer, you need to set up a cli to use a external disassembler and use "{src}" as a placeholder for the source code file, e.g. dis.exe --some-opt "{src}" +)" \ No newline at end of file diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index 0c7e85e4c..38b17c863 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -10,6 +10,7 @@ #include "imgui/imgui_std.h" #include "imgui_internal.h" #include "layer.h" +#include "options.h" #include "widget/frame_dump.h" #include "widget/frame_graph.h" @@ -18,10 +19,9 @@ using namespace Core::Devtools; using L = Core::Devtools::Layer; static bool show_simple_fps = false; + static float fps_scale = 1.0f; - static bool show_advanced_debug = false; - static int dump_frame_count = 1; static Widget::FrameGraph frame_graph; @@ -29,12 +29,23 @@ static std::vector frame_viewers; static float debug_popup_timing = 3.0f; +static bool just_opened_options = false; + +// clang-format off +static std::string help_text = +#include "help.txt" + ; +// clang-format on + void L::DrawMenuBar() { const auto& ctx = *GImGui; const auto& io = ctx.IO; auto isSystemPaused = DebugState.IsGuestThreadsPaused(); + bool open_popup_options = false; + bool open_popup_help = false; + if (BeginMainMenuBar()) { if (BeginMenu("Options")) { if (MenuItemEx("Emulator Paused", nullptr, nullptr, isSystemPaused)) { @@ -55,6 +66,8 @@ void L::DrawMenuBar() { } ImGui::EndMenu(); } + open_popup_options = MenuItem("Options"); + open_popup_help = MenuItem("Help & Tips"); ImGui::EndMenu(); } EndMainMenuBar(); @@ -74,6 +87,14 @@ void L::DrawMenuBar() { } } } + + if (open_popup_options) { + OpenPopup("GPU Tools Options"); + just_opened_options = true; + } + if (open_popup_help) { + OpenPopup("HelpTips"); + } } void L::DrawAdvanced() { @@ -91,13 +112,19 @@ void L::DrawAdvanced() { ->AddText({10.0f, io.DisplaySize.y - 40.0f}, IM_COL32_WHITE, "Emulator paused"); } - if (DebugState.should_show_frame_dump) { + if (DebugState.should_show_frame_dump && DebugState.waiting_reg_dumps.empty()) { DebugState.should_show_frame_dump = false; std::unique_lock lock{DebugState.frame_dump_list_mutex}; while (!DebugState.frame_dump_list.empty()) { - auto frame_dump = std::move(DebugState.frame_dump_list.back()); - DebugState.frame_dump_list.pop_back(); + const auto& frame_dump = DebugState.frame_dump_list.back(); frame_viewers.emplace_back(frame_dump); + DebugState.frame_dump_list.pop_back(); + } + static bool first_time = true; + if (first_time) { + first_time = false; + DebugState.ShowDebugMessage("Tip: You can shift+click any\n" + "popup to open a new window"); } } @@ -133,6 +160,54 @@ void L::DrawAdvanced() { debug_popup_timing = 3.0f; } } + + bool close_popup_options = true; + if (BeginPopupModal("GPU Tools Options", &close_popup_options, + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings)) { + static char disassembly_cli[512]; + static bool frame_dump_render_on_collapse; + + if (just_opened_options) { + just_opened_options = false; + auto s = Options.disassembly_cli.copy(disassembly_cli, sizeof(disassembly_cli) - 1); + disassembly_cli[s] = '\0'; + frame_dump_render_on_collapse = Options.frame_dump_render_on_collapse; + } + + InputText("Shader disassembler: ", disassembly_cli, sizeof(disassembly_cli)); + if (IsItemHovered()) { + SetTooltip(R"(Command to disassemble shaders. Example "dis.exe" --raw "{src}")"); + } + Checkbox("Show frame dump popups even when collapsed", &frame_dump_render_on_collapse); + if (IsItemHovered()) { + SetTooltip("When a frame dump is collapsed, it will keep\n" + "showing all opened popups related to it"); + } + + if (Button("Save")) { + Options.disassembly_cli = disassembly_cli; + Options.frame_dump_render_on_collapse = frame_dump_render_on_collapse; + SaveIniSettingsToDisk(io.IniFilename); + CloseCurrentPopup(); + } + EndPopup(); + } + + if (BeginPopup("HelpTips", ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove)) { + CentralizeWindow(); + + PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{10.0f}); + PushTextWrapPos(600.0f); + + const char* begin = help_text.data(); + TextUnformatted(begin, begin + help_text.size()); + + PopTextWrapPos(); + PopStyleVar(); + + EndPopup(); + } } void L::DrawSimple() { @@ -140,26 +215,54 @@ void L::DrawSimple() { Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); } +static void LoadSettings(const char* line) { + int i; + float f; + if (sscanf(line, "fps_scale=%f", &f) == 1) { + fps_scale = f; + return; + } + if (sscanf(line, "show_advanced_debug=%d", &i) == 1) { + show_advanced_debug = i != 0; + return; + } + if (sscanf(line, "show_frame_graph=%d", &i) == 1) { + frame_graph.is_open = i != 0; + return; + } + if (sscanf(line, "dump_frame_count=%d", &i) == 1) { + dump_frame_count = i; + return; + } +} + void L::SetupSettings() { frame_graph.is_open = true; + using SettingLoader = void (*)(const char*); + ImGuiSettingsHandler handler{}; handler.TypeName = "DevtoolsLayer"; handler.TypeHash = ImHashStr(handler.TypeName); handler.ReadOpenFn = [](ImGuiContext*, ImGuiSettingsHandler*, const char* name) { - return std::string_view("Data") == name ? (void*)1 : nullptr; + if (std::string_view("Data") == name) { + static_assert(std::is_same_v); + return (void*)&LoadSettings; + } + if (std::string_view("CmdList") == name) { + static_assert( + std::is_same_v); + return (void*)&Widget::CmdListViewer::LoadConfig; + } + if (std::string_view("Options") == name) { + static_assert(std::is_same_v); + return (void*)&LoadOptionsConfig; + } + return (void*)nullptr; }; - handler.ReadLineFn = [](ImGuiContext*, ImGuiSettingsHandler*, void*, const char* line) { - int v; - float f; - if (sscanf(line, "fps_scale=%f", &f) == 1) { - fps_scale = f; - } else if (sscanf(line, "show_advanced_debug=%d", &v) == 1) { - show_advanced_debug = v != 0; - } else if (sscanf(line, "show_frame_graph=%d", &v) == 1) { - frame_graph.is_open = v != 0; - } else if (sscanf(line, "dump_frame_count=%d", &v) == 1) { - dump_frame_count = v; + handler.ReadLineFn = [](ImGuiContext*, ImGuiSettingsHandler*, void* handle, const char* line) { + if (handle != nullptr) { + reinterpret_cast(handle)(line); } }; handler.WriteAllFn = [](ImGuiContext*, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) { @@ -169,12 +272,19 @@ void L::SetupSettings() { buf->appendf("show_frame_graph=%d\n", frame_graph.is_open); buf->appendf("dump_frame_count=%d\n", dump_frame_count); buf->append("\n"); + buf->appendf("[%s][CmdList]\n", handler->TypeName); + Widget::CmdListViewer::SerializeConfig(buf); + buf->append("\n"); + buf->appendf("[%s][Options]\n", handler->TypeName); + SerializeOptionsConfig(buf); + buf->append("\n"); }; AddSettingsHandler(&handler); const ImGuiID dock_id = ImHashStr("FrameDumpDock"); DockBuilderAddNode(dock_id, 0); - DockBuilderSetNodePos(dock_id, ImVec2{50.0, 50.0}); + DockBuilderSetNodePos(dock_id, ImVec2{450.0, 150.0}); + DockBuilderSetNodeSize(dock_id, ImVec2{400.0, 500.0}); DockBuilderFinish(dock_id); } diff --git a/src/core/devtools/options.cpp b/src/core/devtools/options.cpp new file mode 100644 index 000000000..1b49da76b --- /dev/null +++ b/src/core/devtools/options.cpp @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "options.h" + +namespace Core::Devtools { + +TOptions Options; + +void LoadOptionsConfig(const char* line) { + char str[512]; + int i; + if (sscanf(line, "disassembly_cli=%511[^\n]", str) == 1) { + Options.disassembly_cli = str; + return; + } + if (sscanf(line, "frame_dump_render_on_collapse=%d", &i) == 1) { + Options.frame_dump_render_on_collapse = i != 0; + return; + } +} + +void SerializeOptionsConfig(ImGuiTextBuffer* buf) { + buf->appendf("disassembly_cli=%s\n", Options.disassembly_cli.c_str()); + buf->appendf("frame_dump_render_on_collapse=%d\n", Options.frame_dump_render_on_collapse); +} + +} // namespace Core::Devtools diff --git a/src/core/devtools/options.h b/src/core/devtools/options.h new file mode 100644 index 000000000..c3a8aaf31 --- /dev/null +++ b/src/core/devtools/options.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +struct ImGuiTextBuffer; + +namespace Core::Devtools { + +struct TOptions { + std::string disassembly_cli{}; + bool frame_dump_render_on_collapse{false}; +}; + +extern TOptions Options; + +void LoadOptionsConfig(const char* line); +void SerializeOptionsConfig(ImGuiTextBuffer* buf); + +} // namespace Core::Devtools diff --git a/src/core/devtools/widget/cmd_list.cpp b/src/core/devtools/widget/cmd_list.cpp index 012891c37..9a42f8238 100644 --- a/src/core/devtools/widget/cmd_list.cpp +++ b/src/core/devtools/widget/cmd_list.cpp @@ -32,6 +32,26 @@ const char* GetOpCodeName(u32 op); namespace Core::Devtools::Widget { +static bool group_batches = true; +static bool show_markers = false; + +void CmdListViewer::LoadConfig(const char* line) { + int i; + if (sscanf(line, "group_batches=%d", &i) == 1) { + group_batches = i != 0; + return; + } + if (sscanf(line, "show_markers=%d", &i) == 1) { + show_markers = i != 0; + return; + } +} + +void CmdListViewer::SerializeConfig(ImGuiTextBuffer* buf) { + buf->appendf("group_batches=%d\n", group_batches); + buf->appendf("show_markers=%d\n", show_markers); +} + template static HdrType GetNext(HdrType this_pm4, uint32_t n) { HdrType curr_pm4 = this_pm4; @@ -43,10 +63,11 @@ static HdrType GetNext(HdrType this_pm4, uint32_t n) { return curr_pm4; } -static void ParsePolygonControl(u32 value) { +void ParsePolygonControl(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("PA_SU_SC_MODE_CNTL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("PA_SU_SC_MODE_CNTL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("CULL_FRONT"); @@ -126,14 +147,17 @@ static void ParsePolygonControl(u32 value) { TableSetColumnIndex(1); Text("%X", reg.multi_prim_ib_ena.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseAaConfig(u32 value) { +void ParseAaConfig(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("PA_SC_AA_CONFIG", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("PA_SC_AA_CONFIG", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("MSAA_NUM_SAMPLES"); @@ -164,14 +188,17 @@ static void ParseAaConfig(u32 value) { TableSetColumnIndex(1); Text("%X", reg.detail_to_exposed_mode.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseViewportControl(u32 value) { +void ParseViewportControl(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("PA_CL_VTE_CNTL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("PA_CL_VTE_CNTL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("VPORT_X_SCALE_ENA"); @@ -232,14 +259,17 @@ static void ParseViewportControl(u32 value) { TableSetColumnIndex(1); Text("%X", reg.perfcounter_ref.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseColorControl(u32 value) { +void ParseColorControl(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("CB_COLOR_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("CB_COLOR_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("DISABLE_DUAL_QUAD__VI"); @@ -264,14 +294,17 @@ static void ParseColorControl(u32 value) { TableSetColumnIndex(1); Text("%X", reg.rop3.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseColor0Info(u32 value) { +void ParseColor0Info(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("CB_COLOR_INFO", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("CB_COLOR_INFO", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("ENDIAN"); @@ -380,14 +413,17 @@ static void ParseColor0Info(u32 value) { TableSetColumnIndex(1); Text("%X", reg.cmask_addr_type.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseColor0Attrib(u32 value) { +void ParseColor0Attrib(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("CB_COLOR_ATTRIB", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("CB_COLOR_ATTRIB", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("TILE_MODE_INDEX"); @@ -424,14 +460,17 @@ static void ParseColor0Attrib(u32 value) { TableSetColumnIndex(1); Text("%X", reg.force_dst_alpha_1.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseBlendControl(u32 value) { +void ParseBlendControl(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("CB_BLEND_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("CB_BLEND_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("COLOR_SRCBLEND"); @@ -490,14 +529,17 @@ static void ParseBlendControl(u32 value) { TableSetColumnIndex(1); Text("%X", reg.disable_rop3.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseDepthRenderControl(u32 value) { +void ParseDepthRenderControl(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("DB_RENDER_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("DB_RENDER_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("DEPTH_CLEAR_ENABLE"); @@ -558,14 +600,17 @@ static void ParseDepthRenderControl(u32 value) { TableSetColumnIndex(1); Text("%X", reg.decompress_enable.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseDepthControl(u32 value) { +void ParseDepthControl(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("DB_DEPTH_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("DB_DEPTH_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("STENCIL_ENABLE"); @@ -628,14 +673,17 @@ static void ParseDepthControl(u32 value) { TableSetColumnIndex(1); Text("%X", reg.disable_color_writes_on_depth_pass.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseEqaa(u32 value) { +void ParseEqaa(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("DB_DEPTH_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("DB_DEPTH_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("MAX_ANCHOR_SAMPLES"); @@ -708,14 +756,17 @@ static void ParseEqaa(u32 value) { TableSetColumnIndex(1); Text("%X", reg.enable_postz_overrasterization.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseZInfo(u32 value) { +void ParseZInfo(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("DB_DEPTH_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("DB_DEPTH_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("FORMAT"); @@ -776,40 +827,41 @@ static void ParseZInfo(u32 value) { TableSetColumnIndex(1); Text("%X", reg.zrange_precision.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } void CmdListViewer::OnNop(AmdGpu::PM4Type3Header const* header, u32 const* body) { using namespace std::string_view_literals; - enum class NOP_PAYLOAD : u32 { - ACB_SUBMIT_MRK = 0x68750013, - ALLOC_ALIGN8 = 0x68753000, - PUSH_MARKER = 0x68750001, - SET_VSHARP = 0x68750004, - SET_TSHARP = 0x68750005, - SET_SSHARP = 0x68750006, - SET_USER_DATA = 0x6875000d, - }; - auto get_noppayload_text = [](NOP_PAYLOAD const nop_payload) { +#define NOP_PAYLOAD \ + P(PUSH_MARKER, 0x68750001) \ + P(POP_MARKER, 0x68750002) \ + P(SET_MARKER, 0x68750003) \ + P(SET_VSHARP, 0x68750004) \ + P(SET_TSHARP, 0x68750005) \ + P(SET_SSHARP, 0x68750006) \ + P(ACB_SUBMIT_MRK, 0x68750013) \ + P(SET_USER_DATA, 0x6875000D) \ + P(PATCHED_FLIP, 0x68750776) \ + P(PREPARE_FLIP, 0x68750777) \ + P(PREPARE_FLIP_LABEL, 0x68750778) \ + P(PREPARE_FLIP_INTERRUPT, 0x68750780) \ + P(PREPARE_FLIP_INTERRUPT_LABEL, 0x68750781) \ + P(ALLOC_ALIGN8, 0x68753000) + + auto get_nop_payload_text = [](u32 const nop_payload) { switch (nop_payload) { - case NOP_PAYLOAD::ACB_SUBMIT_MRK: - return "ACB_SUBMIT_MRK"sv; - case NOP_PAYLOAD::ALLOC_ALIGN8: - return "ALLOC_ALIGN8"sv; - case NOP_PAYLOAD::PUSH_MARKER: - return "PUSH_MARKER"sv; - case NOP_PAYLOAD::SET_VSHARP: - return "SET_VSHARP"sv; - case NOP_PAYLOAD::SET_TSHARP: - return "SET_TSHARP"sv; - case NOP_PAYLOAD::SET_SSHARP: - return "SET_SSHARP"sv; - case NOP_PAYLOAD::SET_USER_DATA: - return "SET_USER_DATA"sv; +#define P(name, value) \ + case value: \ + return #name##sv; + NOP_PAYLOAD +#undef P + default: + return ""sv; } - return ""sv; }; Separator(); @@ -822,7 +874,7 @@ void CmdListViewer::OnNop(AmdGpu::PM4Type3Header const* header, u32 const* body) for (unsigned i = 0; i < pkt->header.count + 1; ++i) { Text("%02X: %08X", i, payload[i]); if ((payload[i] & 0xffff0000) == 0x68750000) { - const auto& e = get_noppayload_text((NOP_PAYLOAD)payload[i]); + const auto& e = get_nop_payload_text(payload[i]); if (!e.empty()) { SameLine(); Text("(%s)", e.data()); @@ -836,7 +888,7 @@ void CmdListViewer::OnSetBase(AmdGpu::PM4Type3Header const* header, u32 const* b Separator(); BeginGroup(); - auto const* pkt = reinterpret_cast(header); + // auto const* pkt = reinterpret_cast(header); Text("BASE_INDEX: %08X", body[0]); Text("ADDRESS0 : %08X", body[1]); Text("ADDRESS1 : %08X", body[2]); @@ -1025,20 +1077,31 @@ void CmdListViewer::OnDispatch(AmdGpu::PM4Type3Header const* header, u32 const* EndGroup(); } -CmdListViewer::CmdListViewer(FrameDumpViewer* parent, const std::vector& cmd_list) - : parent(parent) { +CmdListViewer::CmdListViewer(DebugStateType::FrameDump* _frame_dump, + const std::vector& cmd_list, uintptr_t _base_addr, + std::string _name) + : frame_dump(_frame_dump), base_addr(_base_addr), name(std::move(_name)) { using namespace AmdGpu; cmdb_addr = (uintptr_t)cmd_list.data(); cmdb_size = cmd_list.size() * sizeof(u32); + cmdb_view_name = fmt::format("[GFX] Command buffer {}###cmdview_hex_{}", this->name, cmdb_addr); + cmdb_view.Open = false; + cmdb_view.ReadOnly = true; + auto const* pm4_hdr = reinterpret_cast(cmdb_addr); size_t processed_size = 0; size_t prev_offset = 0; + u32 batch_id = 0; std::string marker{}; + if (cmdb_size > 0) { + events.emplace_back(BatchBegin{.id = 0}); + } + while (processed_size < cmdb_size) { auto* next_pm4_hdr = GetNext(pm4_hdr, 1); auto processed_len = @@ -1048,22 +1111,28 @@ CmdListViewer::CmdListViewer(FrameDumpViewer* parent, const std::vector& cm if (pm4_hdr->type == PM4Type3Header::TYPE) { auto const* pm4_t3 = reinterpret_cast(pm4_hdr); + auto opcode = pm4_t3->opcode; - if (pm4_t3->opcode == PM4ItOpcode::Nop) { + if (opcode == PM4ItOpcode::Nop) { auto const* it_body = reinterpret_cast(pm4_hdr + 1); - if (it_body[0] == 0x68750001) { + switch (it_body[0]) { + case PM4CmdNop::PayloadType::DebugSetMarker: marker = std::string{(char*)&it_body[1]}; + break; + case PM4CmdNop::PayloadType::DebugMarkerPush: + events.emplace_back(PushMarker{ + .name = std::string{(char*)&it_body[1]}, + }); + break; + case PM4CmdNop::PayloadType::DebugMarkerPop: + events.emplace_back(PopMarker{}); + break; + default: + break; } } - if (pm4_t3->opcode == PM4ItOpcode::DispatchDirect || - pm4_t3->opcode == PM4ItOpcode::DispatchIndirect || - pm4_t3->opcode == PM4ItOpcode::DrawIndex2 || - pm4_t3->opcode == PM4ItOpcode::DrawIndexAuto || - pm4_t3->opcode == PM4ItOpcode::DrawIndexOffset2 || - pm4_t3->opcode == PM4ItOpcode::DrawIndexIndirect - // ... - ) { + if (IsDrawCall(opcode)) { // All these commands are terminated by NOP at the end, so // it is safe to skip it to be even with CP // next_pm4_hdr = get_next(next_pm4_hdr, 1); @@ -1071,15 +1140,17 @@ CmdListViewer::CmdListViewer(FrameDumpViewer* parent, const std::vector& cm // processed_len += nop_len; // processed_size += nop_len; - batches.emplace_back(BatchInfo{ - marker, - prev_offset, - processed_size, - processed_size - processed_len, - pm4_t3->opcode, + events.emplace_back(BatchInfo{ + .id = batch_id++, + .marker = marker, + .start_addr = prev_offset, + .end_addr = processed_size, + .command_addr = processed_size - processed_len, + .type = opcode, }); prev_offset = processed_size; marker.clear(); + events.emplace_back(BatchBegin{.id = batch_id}); } } @@ -1088,18 +1159,62 @@ CmdListViewer::CmdListViewer(FrameDumpViewer* parent, const std::vector& cm // state batch (last) if (processed_size - prev_offset > 0) { - batches.emplace_back(BatchInfo{ - marker, - prev_offset, - processed_size, - 0, - static_cast(0xFF), + events.emplace_back(BatchInfo{ + .id = batch_id++, + .marker = marker, + .start_addr = prev_offset, + .end_addr = processed_size, + .command_addr = 0, + .type = static_cast(0xFF), }); } + if (!events.empty() && std::holds_alternative(events.back())) { + events.pop_back(); + } } -void CmdListViewer::Draw() { +void CmdListViewer::Draw(bool only_batches_view) { + const auto& ctx = *GetCurrentContext(); + + if (batch_view.open) { + batch_view.Draw(); + } + for (auto it = extra_batch_view.begin(); it != extra_batch_view.end();) { + if (!it->open) { + it = extra_batch_view.erase(it); + continue; + } + it->Draw(); + ++it; + } + + if (only_batches_view) { + return; + } + + if (cmdb_view.Open) { + MemoryEditor::Sizes s; + cmdb_view.CalcSizes(s, cmdb_size, cmdb_addr); + SetNextWindowSize({s.WindowWidth, s.WindowWidth * 0.6f}, ImGuiCond_FirstUseEver); + SetNextWindowSizeConstraints({0.0f}, {s.WindowWidth, FLT_MAX}); + if (Begin(cmdb_view_name.c_str(), &cmdb_view.Open, + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings)) { + cmdb_view.DrawContents((void*)cmdb_addr, cmdb_size, base_addr); + if (cmdb_view.ContentsWidthChanged) { + cmdb_view.CalcSizes(s, cmdb_size, cmdb_addr); + SetWindowSize({s.WindowWidth, s.WindowWidth * 0.6f}); + } + } + End(); + } + + PushID(name.c_str()); if (BeginChild("cmd_queue", {})) { + + Checkbox("Group batches", &group_batches); + SameLine(); + Checkbox("Show markers", &show_markers); + char queue_name[32]{}; if (vqid < 254) { std::snprintf(queue_name, sizeof(queue_name), "%s %d", vqid > 254 ? "GFX" : "ASC", @@ -1111,113 +1226,257 @@ void CmdListViewer::Draw() { Text("queue : %s", queue_name); Text("base addr: %08llX", cmdb_addr); SameLine(); - if (SmallButton(">")) { - parent->cmdb_view.Open ^= true; + if (SmallButton("Memory >")) { + cmdb_view.Open ^= true; } Text("size : %04llX", cmdb_size); Separator(); - char batch_hdr[128]; - for (int batch_id = 0; batch_id < batches.size(); ++batch_id) { - auto processed_size = 0ull; - auto const* pm4_hdr = - reinterpret_cast(cmdb_addr + batches[batch_id].start_addr); + { + int tree_depth = 0; + int tree_depth_show = 0; - sprintf(batch_hdr, "%08llX: batch-%03d | %s", cmdb_addr + batches[batch_id].start_addr, - batch_id, batches[batch_id].marker.c_str()); - - if (batch_id == batch_bp) { // highlight batch at breakpoint - PushStyleColor(ImGuiCol_Header, ImVec4{1.0f, 0.5f, 0.5f, 0.5f}); + u32 last_batch_id = ~0u; + if (!events.empty() && std::holds_alternative(events.back())) { + last_batch_id = std::get(events.back()).id; } - if (batches[batch_id].type == static_cast(0xFF) || - CollapsingHeader(batch_hdr)) { - auto const batch_sz = batches[batch_id].end_addr - batches[batch_id].start_addr; - while (processed_size < batch_sz) { - AmdGpu::PM4ItOpcode op{0xFFu}; + u32 batch_id = ~0u; + u32 current_highlight_batch = ~0u; - if (pm4_hdr->type == AmdGpu::PM4Type3Header::TYPE) { - auto const* pm4_t3 = - reinterpret_cast(pm4_hdr); - op = pm4_t3->opcode; + int id = 0; + PushID(0); + for (const auto& event : events) { + PopID(); + PushID(id++); - static char header_name[128]; - sprintf(header_name, "%08llX: %s", - cmdb_addr + batches[batch_id].start_addr + processed_size, - Gcn::GetOpCodeName((u32)op)); + if (std::holds_alternative(event)) { + batch_id = std::get(event).id; + } - if (TreeNode(header_name)) { - bool just_opened = IsItemToggledOpen(); - if (BeginTable("split", 1)) { - TableNextColumn(); - Text("size: %d", pm4_hdr->count + 1); + if (show_markers) { + if (std::holds_alternative(event)) { + if (tree_depth_show >= tree_depth) { + auto& marker = std::get(event); + bool show = TreeNode(&event, "%s", marker.name.c_str()); + if (show) { + tree_depth_show++; + } + } + tree_depth++; + continue; + } + if (std::holds_alternative(event)) { + if (tree_depth_show >= tree_depth) { + tree_depth_show--; + TreePop(); + } + tree_depth--; + continue; + } + if (tree_depth_show < tree_depth) { + continue; + } + } - if (just_opened) { + if (!std::holds_alternative(event)) { + continue; + } + + auto& batch = std::get(event); + auto const* pm4_hdr = + reinterpret_cast(cmdb_addr + batch.start_addr); + + bool ignore_header = false; + char batch_hdr[128]; + if (batch.type == static_cast(0xFF)) { + ignore_header = true; + } else if (!batch.marker.empty()) { + snprintf(batch_hdr, sizeof(batch_hdr), "%08llX: batch-%03d %s | %s", + cmdb_addr + batch.start_addr, batch.id, + Gcn::GetOpCodeName(static_cast(batch.type)), + batch.marker.c_str()); + } else { + snprintf(batch_hdr, sizeof(batch_hdr), "%08llX: batch-%03d %s", + cmdb_addr + batch.start_addr, batch.id, + Gcn::GetOpCodeName(static_cast(batch.type))); + } + + if (batch.id == batch_bp) { // highlight batch at breakpoint + PushStyleColor(ImGuiCol_Header, ImVec4{1.0f, 0.5f, 0.5f, 0.5f}); + } + if (batch.id == highlight_batch) { + PushStyleColor(ImGuiCol_Text, ImVec4{1.0f, 0.7f, 0.7f, 1.0f}); + } + + const auto open_batch_view = [&, this] { + if (frame_dump->regs.contains(batch.command_addr)) { + auto data = frame_dump->regs.at(batch.command_addr); + if (GetIO().KeyShift) { + auto& pop = extra_batch_view.emplace_back(); + pop.SetData(data, name, batch_id); + pop.open = true; + } else { + if (batch_view.open && + this->last_selected_batch == static_cast(batch_id)) { + batch_view.open = false; + } else { + this->last_selected_batch = static_cast(batch_id); + batch_view.SetData(data, name, batch_id); + if (!batch_view.open || !batch_view.moved) { + batch_view.open = true; + const auto pos = GetItemRectMax() + ImVec2{5.0f, 0.0f}; + batch_view.SetPos(pos); + } + } + } + } + }; + + bool show_batch_content = true; + + if (group_batches && !ignore_header) { + show_batch_content = + CollapsingHeader(batch_hdr, ImGuiTreeNodeFlags_AllowOverlap); + SameLine(GetContentRegionAvail().x - 40.0f); + const char* text = + last_selected_batch == static_cast(batch_id) && batch_view.open ? "X" + : "->"; + if (Button(text, {40.0f, 0.0f})) { + open_batch_view(); + } + } + + if (show_batch_content) { + auto processed_size = 0ull; + auto bb = ctx.LastItemData.Rect; + if (group_batches && !ignore_header) { + Indent(); + } + auto const batch_sz = batch.end_addr - batch.start_addr; + + while (processed_size < batch_sz) { + AmdGpu::PM4ItOpcode op{0xFFu}; + + if (pm4_hdr->type == AmdGpu::PM4Type3Header::TYPE) { + auto const* pm4_t3 = + reinterpret_cast(pm4_hdr); + op = pm4_t3->opcode; + + char header_name[128]; + sprintf(header_name, "%08llX: %s", + cmdb_addr + batch.start_addr + processed_size, + Gcn::GetOpCodeName((u32)op)); + + bool open_pm4 = TreeNode(header_name); + if (!group_batches) { + if (IsDrawCall(op)) { + SameLine(GetContentRegionAvail().x - 40.0f); + const char* text = + last_selected_batch == static_cast(batch_id) && + batch_view.open + ? "X" + : "->"; + if (Button(text, {40.0f, 0.0f})) { + open_batch_view(); + } + } + if (IsItemHovered() && ctx.IO.KeyShift) { + if (BeginTooltip()) { + Text("Batch %d", batch_id); + EndTooltip(); + } + } + } + if (open_pm4) { + if (IsItemToggledOpen()) { // Editor - parent->cmdb_view.GotoAddrAndHighlight( + cmdb_view.GotoAddrAndHighlight( reinterpret_cast(pm4_hdr) - cmdb_addr, reinterpret_cast(pm4_hdr) - cmdb_addr + (pm4_hdr->count + 2) * 4); } - auto const* it_body = - reinterpret_cast(pm4_hdr + 1); + if (BeginTable("split", 1)) { + TableNextColumn(); + Text("size: %d", pm4_hdr->count + 1); - switch (op) { - case AmdGpu::PM4ItOpcode::Nop: { - OnNop(pm4_t3, it_body); - break; - } - case AmdGpu::PM4ItOpcode::SetBase: { - OnSetBase(pm4_t3, it_body); - break; - } - case AmdGpu::PM4ItOpcode::SetContextReg: { - OnSetContextReg(pm4_t3, it_body); - break; - } - case AmdGpu::PM4ItOpcode::SetShReg: { - OnSetShReg(pm4_t3, it_body); - break; - } - case AmdGpu::PM4ItOpcode::DispatchDirect: { - OnDispatch(pm4_t3, it_body); - break; - } - default: { - auto const* payload = &it_body[0]; - for (unsigned i = 0; i < pm4_hdr->count + 1; ++i) { - Text("%02X: %08X", i, payload[i]); + auto const* it_body = + reinterpret_cast(pm4_hdr + 1); + + switch (op) { + case AmdGpu::PM4ItOpcode::Nop: { + OnNop(pm4_t3, it_body); + break; + } + case AmdGpu::PM4ItOpcode::SetBase: { + OnSetBase(pm4_t3, it_body); + break; + } + case AmdGpu::PM4ItOpcode::SetContextReg: { + OnSetContextReg(pm4_t3, it_body); + break; + } + case AmdGpu::PM4ItOpcode::SetShReg: { + OnSetShReg(pm4_t3, it_body); + break; + } + case AmdGpu::PM4ItOpcode::DispatchDirect: { + OnDispatch(pm4_t3, it_body); + break; + } + default: { + auto const* payload = &it_body[0]; + for (unsigned i = 0; i < pm4_hdr->count + 1; ++i) { + Text("%02X: %08X", i, payload[i]); + } + } } - } - } - EndTable(); + EndTable(); + } + TreePop(); } - TreePop(); + + } else { + Text(""); } - } else { - Text(""); + + auto const* next_pm4_hdr = GetNext(pm4_hdr, 1); + auto const processed_len = reinterpret_cast(next_pm4_hdr) - + reinterpret_cast(pm4_hdr); + pm4_hdr = next_pm4_hdr; + processed_size += processed_len; } - auto const* next_pm4_hdr = GetNext(pm4_hdr, 1); - auto const processed_len = reinterpret_cast(next_pm4_hdr) - - reinterpret_cast(pm4_hdr); - pm4_hdr = next_pm4_hdr; - processed_size += processed_len; + if (group_batches && !ignore_header) { + Unindent(); + }; + bb = {{0.0f, bb.Max.y}, ctx.LastItemData.Rect.Max}; + if (bb.Contains(ctx.IO.MousePos)) { + current_highlight_batch = batch.id; + } + } + + if (batch.id == highlight_batch) { + PopStyleColor(); + } + + if (batch.id == batch_bp) { + PopStyleColor(); + } + + if (batch.id == last_batch_id) { + Separator(); } } + PopID(); - if (batch_id == batch_bp) { - PopStyleColor(); - } - - if (batch_id == batches.size() - 2) { - Separator(); - } + highlight_batch = current_highlight_batch; } } EndChild(); + PopID(); } } // namespace Core::Devtools::Widget \ No newline at end of file diff --git a/src/core/devtools/widget/cmd_list.h b/src/core/devtools/widget/cmd_list.h index a6ecd9323..ed71d0b76 100644 --- a/src/core/devtools/widget/cmd_list.h +++ b/src/core/devtools/widget/cmd_list.h @@ -5,10 +5,14 @@ #pragma once +#include #include +#include +#include "common.h" #include "common/types.h" -#include "video_core/buffer_cache/buffer_cache.h" +#include "imgui_memory_editor.h" +#include "reg_view.h" namespace AmdGpu { union PM4Type3Header; @@ -19,45 +23,54 @@ namespace Core::Devtools::Widget { class FrameDumpViewer; -class CmdListViewer { - /* - * Generic PM4 header - */ - union PM4Header { - struct { - u32 reserved : 16; - u32 count : 14; - u32 type : 2; // PM4_TYPE - }; - u32 u32All; - }; - struct BatchInfo { - std::string marker{}; - size_t start_addr; - size_t end_addr; - size_t command_addr; - AmdGpu::PM4ItOpcode type; - bool bypass{false}; - }; +void ParsePolygonControl(u32 value, bool begin_table = true); +void ParseAaConfig(u32 value, bool begin_table = true); +void ParseViewportControl(u32 value, bool begin_table = true); +void ParseColorControl(u32 value, bool begin_table = true); +void ParseColor0Info(u32 value, bool begin_table = true); +void ParseColor0Attrib(u32 value, bool begin_table = true); +void ParseBlendControl(u32 value, bool begin_table = true); +void ParseDepthRenderControl(u32 value, bool begin_table = true); +void ParseDepthControl(u32 value, bool begin_table = true); +void ParseEqaa(u32 value, bool begin_table = true); +void ParseZInfo(u32 value, bool begin_table = true); - FrameDumpViewer* parent; - std::vector batches{}; +class CmdListViewer { + + DebugStateType::FrameDump* frame_dump; + + uintptr_t base_addr; + std::string name; + std::vector events{}; uintptr_t cmdb_addr; size_t cmdb_size; + std::string cmdb_view_name; + MemoryEditor cmdb_view; + int batch_bp{-1}; int vqid{255}; + u32 highlight_batch{~0u}; - void OnNop(AmdGpu::PM4Type3Header const* header, u32 const* body); - void OnSetBase(AmdGpu::PM4Type3Header const* header, u32 const* body); - void OnSetContextReg(AmdGpu::PM4Type3Header const* header, u32 const* body); - void OnSetShReg(AmdGpu::PM4Type3Header const* header, u32 const* body); - void OnDispatch(AmdGpu::PM4Type3Header const* header, u32 const* body); + RegView batch_view; + int last_selected_batch{-1}; + + std::vector extra_batch_view; + + static void OnNop(AmdGpu::PM4Type3Header const* header, u32 const* body); + static void OnSetBase(AmdGpu::PM4Type3Header const* header, u32 const* body); + static void OnSetContextReg(AmdGpu::PM4Type3Header const* header, u32 const* body); + static void OnSetShReg(AmdGpu::PM4Type3Header const* header, u32 const* body); + static void OnDispatch(AmdGpu::PM4Type3Header const* header, u32 const* body); public: - explicit CmdListViewer(FrameDumpViewer* parent, const std::vector& cmd_list); + static void LoadConfig(const char* line); + static void SerializeConfig(ImGuiTextBuffer* buf); - void Draw(); + explicit CmdListViewer(DebugStateType::FrameDump* frame_dump, const std::vector& cmd_list, + uintptr_t base_addr = 0, std::string name = ""); + + void Draw(bool only_batches_view = false); }; } // namespace Core::Devtools::Widget diff --git a/src/core/devtools/widget/common.h b/src/core/devtools/widget/common.h new file mode 100644 index 000000000..e650f5fc7 --- /dev/null +++ b/src/core/devtools/widget/common.h @@ -0,0 +1,109 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include + +#include "common/bit_field.h" +#include "common/types.h" +#include "video_core/amdgpu/pm4_opcodes.h" + +namespace Core::Devtools::Widget { +/* + * Generic PM4 header + */ +union PM4Header { + struct { + u32 reserved : 16; + u32 count : 14; + u32 type : 2; // PM4_TYPE + }; + u32 u32All; +}; + +struct PushMarker { + std::string name{}; +}; + +struct PopMarker {}; + +struct BatchBegin { + u32 id; +}; + +struct BatchInfo { + u32 id; + std::string marker{}; + size_t start_addr; + size_t end_addr; + size_t command_addr; + AmdGpu::PM4ItOpcode type; + bool bypass{false}; +}; + +using GPUEvent = std::variant; + +template +void DrawRow(const char* text, const char* fmt, Args... args) { + ImGui::TableNextColumn(); + ImGui::TextUnformatted(text); + ImGui::TableNextColumn(); + char buf[128]; + snprintf(buf, sizeof(buf), fmt, args...); + ImGui::TextUnformatted(buf); +} + +template +void DrawValueRow(const char* text, T value) { + if constexpr (std::is_enum_v) { + return DrawRow(text, "%X (%s)", value, magic_enum::enum_name(value).data()); + } else if constexpr (std::is_integral_v) { + return DrawRow(text, "%X", value); + } else if constexpr (std::is_base_of_v, T>) { + return DrawValueRow(text, value.Value()); + } else { + static_assert(false, "Unsupported type"); + } +} + +template +void DrawValueRowList(const char* text, V arg, Extra&&... extra_args) { + DrawValueRow(text, arg); + if constexpr (sizeof...(extra_args) > 0) { + DrawValueRowList(std::forward(extra_args)...); + } +} + +template +static void DoTooltip(const char* str_id, Args&&... args) { + if (ImGui::BeginTooltip()) { + if (ImGui::BeginTable(str_id, 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + DrawMultipleRow(std::forward(args)...); + ImGui::EndTable(); + } + ImGui::EndTooltip(); + } +} + +static bool IsDrawCall(AmdGpu::PM4ItOpcode opcode) { + using AmdGpu::PM4ItOpcode; + switch (opcode) { + case PM4ItOpcode::DrawIndex2: + case PM4ItOpcode::DrawIndexOffset2: + case PM4ItOpcode::DrawIndexAuto: + case PM4ItOpcode::DrawIndirect: + case PM4ItOpcode::DrawIndexIndirect: + case PM4ItOpcode::DispatchDirect: + case PM4ItOpcode::DispatchIndirect: + return true; + default: + return false; + } +} + +} // namespace Core::Devtools::Widget \ No newline at end of file diff --git a/src/core/devtools/widget/frame_dump.cpp b/src/core/devtools/widget/frame_dump.cpp index d27bab90a..86ba7b86e 100644 --- a/src/core/devtools/widget/frame_dump.cpp +++ b/src/core/devtools/widget/frame_dump.cpp @@ -7,6 +7,7 @@ #include #include "common/io_file.h" +#include "core/devtools/options.h" #include "frame_dump.h" #include "imgui_internal.h" #include "imgui_memory_editor.h" @@ -36,7 +37,8 @@ static std::array small_int_to_str(const s32 i) { namespace Core::Devtools::Widget { -FrameDumpViewer::FrameDumpViewer(FrameDump _frame_dump) : frame_dump(std::move(_frame_dump)) { +FrameDumpViewer::FrameDumpViewer(const FrameDump& _frame_dump) + : frame_dump(std::make_shared(_frame_dump)) { static int unique_id = 0; id = unique_id++; @@ -44,33 +46,54 @@ FrameDumpViewer::FrameDumpViewer(FrameDump _frame_dump) : frame_dump(std::move(_ selected_submit_num = 0; selected_queue_num2 = 0; - cmd_list_viewer.reserve(frame_dump.queues.size()); - for (const auto& cmd : frame_dump.queues) { - cmd_list_viewer.emplace_back(this, cmd.data); - if (cmd.type == QueueType::dcb && cmd.submit_num == selected_submit_num && - cmd.num2 == selected_queue_num2) { - selected_cmd = cmd_list_viewer.size() - 1; + has_queue_type.fill(false); + cmd_list_viewer.reserve(frame_dump->queues.size()); + for (const auto& cmd : frame_dump->queues) { + if (!cmd.data.empty()) { + has_queue_type[static_cast(cmd.type)] = true; + } + const auto fname = fmt::format("F{} {}_{:02}_{:02}", frame_dump->frame_id, + magic_enum::enum_name(cmd.type), cmd.submit_num, cmd.num2); + cmd_list_viewer.emplace_back(frame_dump.get(), cmd.data, cmd.base_addr, fname); + if (cmd.type == QueueType::dcb && cmd.submit_num == 0 && cmd.num2 == 0) { + selected_cmd = static_cast(cmd_list_viewer.size() - 1); } } - - cmdb_view.Open = false; - cmdb_view.ReadOnly = true; } -FrameDumpViewer::~FrameDumpViewer() {} +FrameDumpViewer::~FrameDumpViewer() = default; void FrameDumpViewer::Draw() { if (!is_open) { return; } + const auto try_select = [&, this] { + const auto it = std::ranges::find_if(frame_dump->queues, [&](const auto& cmd) { + return cmd.type == selected_queue_type && + (selected_submit_num == -1 || cmd.submit_num == selected_submit_num) && + (selected_queue_num2 == -1 || cmd.num2 == selected_queue_num2); + }); + if (it != frame_dump->queues.end()) { + selected_cmd = static_cast(std::distance(frame_dump->queues.begin(), it)); + selected_submit_num = static_cast(frame_dump->queues[selected_cmd].submit_num); + selected_queue_num2 = static_cast(frame_dump->queues[selected_cmd].num2); + } + }; + + bool is_showing = Options.frame_dump_render_on_collapse; + bool is_collapsed = true; + char name[32]; - snprintf(name, sizeof(name), "Frame #%d dump", id); - static ImGuiID dock_id = ImHashStr("FrameDumpDock"); - SetNextWindowDockID(dock_id, ImGuiCond_Appearing); + snprintf(name, sizeof(name), "Frame #%d dump", frame_dump->frame_id); if (Begin(name, &is_open, ImGuiWindowFlags_NoSavedSettings)) { + is_showing = true; + is_collapsed = false; + if (IsWindowAppearing()) { auto window = GetCurrentWindow(); + static ImGuiID dock_id = ImHashStr("FrameDumpDock"); + SetWindowDock(window, dock_id, ImGuiCond_Once | ImGuiCond_FirstUseEver); SetWindowSize(window, ImVec2{470.0f, 600.0f}); } BeginGroup(); @@ -79,23 +102,44 @@ void FrameDumpViewer::Draw() { if (BeginCombo("##select_queue_type", magic_enum::enum_name(selected_queue_type).data(), ImGuiComboFlags_WidthFitPreview)) { bool selected = false; -#define COMBO(x) C_V(magic_enum::enum_name(x).data(), x, selected_queue_type, selected) - COMBO(QueueType::acb) +#define COMBO(x) \ + if (has_queue_type[static_cast(x)]) \ + C_V(magic_enum::enum_name(x).data(), x, selected_queue_type, selected) COMBO(QueueType::dcb); COMBO(QueueType::ccb); + COMBO(QueueType::acb); if (selected) { selected_submit_num = selected_queue_num2 = -1; + try_select(); } EndCombo(); } SameLine(); + BeginDisabled(selected_cmd == -1); + if (SmallButton("Dump cmd")) { + auto now_time = fmt::localtime(std::time(nullptr)); + const auto fname = fmt::format("{:%F %H-%M-%S} {}_{}_{}.bin", now_time, + magic_enum::enum_name(selected_queue_type), + selected_submit_num, selected_queue_num2); + Common::FS::IOFile file(fname, Common::FS::FileAccessMode::Write); + const auto& data = frame_dump->queues[selected_cmd].data; + if (file.IsOpen()) { + DebugState.ShowDebugMessage(fmt::format("Dumping cmd as {}", fname)); + file.Write(data); + } else { + DebugState.ShowDebugMessage(fmt::format("Failed to save {}", fname)); + LOG_ERROR(Core, "Failed to open file {}", fname); + } + } + EndDisabled(); + TextEx("Submit num"); SameLine(); if (BeginCombo("##select_submit_num", small_int_to_str(selected_submit_num).data(), ImGuiComboFlags_WidthFitPreview)) { - std::array available_submits{}; - for (const auto& cmd : frame_dump.queues) { - if (cmd.type == selected_queue_type) { + std::array available_submits{false}; + for (const auto& cmd : frame_dump->queues) { + if (cmd.type == selected_queue_type && !cmd.data.empty()) { available_submits[cmd.submit_num] = true; } } @@ -110,6 +154,7 @@ void FrameDumpViewer::Draw() { } if (selected) { selected_queue_num2 = -1; + try_select(); } EndCombo(); } @@ -118,9 +163,10 @@ void FrameDumpViewer::Draw() { SameLine(); if (BeginCombo("##select_queue_num2", small_int_to_str(selected_queue_num2).data(), ImGuiComboFlags_WidthFitPreview)) { - std::array available_queues{}; - for (const auto& cmd : frame_dump.queues) { - if (cmd.type == selected_queue_type && cmd.submit_num == selected_submit_num) { + std::array available_queues{false}; + for (const auto& cmd : frame_dump->queues) { + if (cmd.type == selected_queue_type && cmd.submit_num == selected_submit_num && + !cmd.data.empty()) { available_queues[cmd.num2] = true; } } @@ -134,56 +180,16 @@ void FrameDumpViewer::Draw() { } } if (selected) { - const auto it = std::ranges::find_if(frame_dump.queues, [&](const auto& cmd) { - return cmd.type == selected_queue_type && - cmd.submit_num == selected_submit_num && cmd.num2 == selected_queue_num2; - }); - if (it != frame_dump.queues.end()) { - selected_cmd = std::distance(frame_dump.queues.begin(), it); - } + try_select(); } EndCombo(); } - SameLine(); - BeginDisabled(selected_cmd == -1); - if (SmallButton("Dump cmd")) { - auto now_time = fmt::localtime(std::time(nullptr)); - const auto fname = fmt::format("{:%F %H-%M-%S} {}_{}_{}.bin", now_time, - magic_enum::enum_name(selected_queue_type), - selected_submit_num, selected_queue_num2); - Common::FS::IOFile file(fname, Common::FS::FileAccessMode::Write); - auto& data = frame_dump.queues[selected_cmd].data; - if (file.IsOpen()) { - DebugState.ShowDebugMessage(fmt::format("Dumping cmd as {}", fname)); - file.Write(data); - } else { - DebugState.ShowDebugMessage(fmt::format("Failed to save {}", fname)); - LOG_ERROR(Core, "Failed to open file {}", fname); - } - } - EndDisabled(); EndGroup(); - - if (selected_cmd != -1) { - cmd_list_viewer[selected_cmd].Draw(); - } + } + if (is_showing && selected_cmd != -1) { + cmd_list_viewer[selected_cmd].Draw(is_collapsed); } End(); - - if (cmdb_view.Open && selected_cmd != -1) { - auto& cmd = frame_dump.queues[selected_cmd].data; - auto cmd_size = cmd.size() * sizeof(u32); - MemoryEditor::Sizes s; - cmdb_view.CalcSizes(s, cmd_size, (size_t)cmd.data()); - SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(s.WindowWidth, FLT_MAX)); - - char name[64]; - snprintf(name, sizeof(name), "[GFX] Command buffer %d###cmdbuf_hex_%d", id, id); - if (Begin(name, &cmdb_view.Open, ImGuiWindowFlags_NoScrollbar)) { - cmdb_view.DrawContents(cmd.data(), cmd_size, (size_t)cmd.data()); - } - End(); - } } } // namespace Core::Devtools::Widget diff --git a/src/core/devtools/widget/frame_dump.h b/src/core/devtools/widget/frame_dump.h index d9d11f825..cc4fe6381 100644 --- a/src/core/devtools/widget/frame_dump.h +++ b/src/core/devtools/widget/frame_dump.h @@ -8,7 +8,6 @@ #include "cmd_list.h" #include "core/debug_state.h" -#include "imgui_memory_editor.h" namespace Core::Devtools::Widget { @@ -17,11 +16,11 @@ class CmdListViewer; class FrameDumpViewer { friend class CmdListViewer; - DebugStateType::FrameDump frame_dump; + std::shared_ptr frame_dump; int id; std::vector cmd_list_viewer; - MemoryEditor cmdb_view; + std::array has_queue_type; DebugStateType::QueueType selected_queue_type; s32 selected_submit_num; @@ -31,7 +30,7 @@ class FrameDumpViewer { public: bool is_open = true; - explicit FrameDumpViewer(DebugStateType::FrameDump frame_dump); + explicit FrameDumpViewer(const DebugStateType::FrameDump& frame_dump); ~FrameDumpViewer(); diff --git a/src/core/devtools/widget/reg_popup.cpp b/src/core/devtools/widget/reg_popup.cpp new file mode 100644 index 000000000..0633e76e6 --- /dev/null +++ b/src/core/devtools/widget/reg_popup.cpp @@ -0,0 +1,192 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "reg_popup.h" + +#include +#include +#include + +#include "cmd_list.h" +#include "common.h" +#include "imgui/imgui_std.h" + +using namespace ImGui; +using magic_enum::enum_name; + +namespace Core::Devtools::Widget { + +void RegPopup::DrawColorBuffer(const AmdGpu::Liverpool::ColorBuffer& buffer) { + if (BeginTable("COLOR_BUFFER", 2, ImGuiTableFlags_Borders)) { + TableNextRow(); + + // clang-format off + + DrawValueRowList( + "BASE_ADDR", buffer.base_address, + "PITCH.TILE_MAX", buffer.pitch.tile_max, + "PITCH.FMASK_TILE_MAX", buffer.pitch.fmask_tile_max, + "SLICE.TILE_MAX", buffer.slice.tile_max, + "VIEW.SLICE_START", buffer.view.slice_start, + "VIEW.SLICE_MAX", buffer.view.slice_max + ); + + TableNextRow(); + TableNextColumn(); + if (TreeNode("Color0Info")) { + TableNextRow(); + TableNextColumn(); + ParseColor0Info(buffer.info.u32all, false); + TreePop(); + } + + TableNextRow(); + TableNextColumn(); + if (TreeNode("Color0Attrib")) { + TableNextRow(); + TableNextColumn(); + ParseColor0Attrib(buffer.attrib.u32all, false); + TreePop(); + } + + TableNextRow(); + DrawValueRowList( + "CMASK_BASE_EXT", buffer.cmask_base_address, + "FMASK_BASE_EXT", buffer.fmask_base_address, + "FMASK_SLICE.TILE_MAX", buffer.fmask_slice.tile_max, + "CLEAR_WORD0", buffer.clear_word0, + "CLEAR_WORD1", buffer.clear_word1, + "Pitch()", buffer.Pitch(), + "Height()", buffer.Height(), + "Address()", buffer.Address(), + "CmaskAddress", buffer.CmaskAddress(), + "FmaskAddress", buffer.FmaskAddress(), + "NumSamples()", buffer.NumSamples(), + "NumSlices()", buffer.NumSlices(), + "GetColorSliceSize()", buffer.GetColorSliceSize(), + "GetTilingMode()", buffer.GetTilingMode(), + "IsTiled()", buffer.IsTiled(), + "NumFormat()", buffer.NumFormat() + ); + + // clang-format on + + EndTable(); + } +} + +void RegPopup::DrawDepthBuffer(const DepthBuffer& depth_data) { + const auto& [depth_buffer, depth_control] = depth_data; + + SeparatorText("Depth buffer"); + + if (BeginTable("DEPTH_BUFFER", 2, ImGuiTableFlags_Borders)) { + TableNextRow(); + + // clang-format off + DrawValueRowList( + "Z_INFO.FORMAT", depth_buffer.z_info.format, + "Z_INFO.NUM_SAMPLES", depth_buffer.z_info.num_samples, + "Z_INFO.TILE_SPLIT", depth_buffer.z_info.tile_split, + "Z_INFO.TILE_MODE_INDEX", depth_buffer.z_info.tile_mode_index, + "Z_INFO.DECOMPRESS_ON_N_ZPLANES", depth_buffer.z_info.decompress_on_n_zplanes, + "Z_INFO.ALLOW_EXPCLEAR", depth_buffer.z_info.allow_expclear, + "Z_INFO.READ_SIZE", depth_buffer.z_info.read_size, + "Z_INFO.TILE_SURFACE_EN", depth_buffer.z_info.tile_surface_en, + "Z_INFO.CLEAR_DISALLOWED", depth_buffer.z_info.clear_disallowed, + "Z_INFO.ZRANGE_PRECISION", depth_buffer.z_info.zrange_precision, + "STENCIL_INFO.FORMAT", depth_buffer.stencil_info.format, + "Z_READ_BASE", depth_buffer.z_read_base, + "STENCIL_READ_BASE", depth_buffer.stencil_read_base, + "Z_WRITE_BASE", depth_buffer.z_write_base, + "STENCIL_WRITE_BASE", depth_buffer.stencil_write_base, + "DEPTH_SIZE.PITCH_TILE_MAX", depth_buffer.depth_size.pitch_tile_max, + "DEPTH_SIZE.HEIGHT_TILE_MAX", depth_buffer.depth_size.height_tile_max, + "DEPTH_SLICE.TILE_MAX", depth_buffer.depth_slice.tile_max, + "Pitch()", depth_buffer.Pitch(), + "Height()", depth_buffer.Height(), + "Address()", depth_buffer.Address(), + "NumSamples()", depth_buffer.NumSamples(), + "NumBits()", depth_buffer.NumBits(), + "GetDepthSliceSize()", depth_buffer.GetDepthSliceSize() + ); + // clang-format on + + EndTable(); + } + SeparatorText("Depth control"); + if (BeginTable("DEPTH_CONTROL", 2, ImGuiTableFlags_Borders)) { + TableNextRow(); + + // clang-format off + DrawValueRowList( + "STENCIL_ENABLE", depth_control.stencil_enable, + "DEPTH_ENABLE", depth_control.depth_enable, + "DEPTH_WRITE_ENABLE", depth_control.depth_write_enable, + "DEPTH_BOUNDS_ENABLE", depth_control.depth_bounds_enable, + "DEPTH_FUNC", depth_control.depth_func, + "BACKFACE_ENABLE", depth_control.backface_enable, + "STENCIL_FUNC", depth_control.stencil_ref_func, + "STENCIL_FUNC_BF", depth_control.stencil_bf_func, + "ENABLE_COLOR_WRITES_ON_DEPTH_FAIL", depth_control.enable_color_writes_on_depth_fail, + "DISABLE_COLOR_WRITES_ON_DEPTH_PASS", depth_control.disable_color_writes_on_depth_pass + ); + // clang-format on + + EndTable(); + } +} + +RegPopup::RegPopup() { + static int unique_id = 0; + id = unique_id++; +} + +void RegPopup::SetData(const std::string& base_title, AmdGpu::Liverpool::ColorBuffer color_buffer, + u32 cb_id) { + this->data = color_buffer; + this->title = fmt::format("{}/CB #{}", base_title, cb_id); +} + +void RegPopup::SetData(const std::string& base_title, AmdGpu::Liverpool::DepthBuffer depth_buffer, + AmdGpu::Liverpool::DepthControl depth_control) { + this->data = std::make_tuple(depth_buffer, depth_control); + this->title = fmt::format("{}/Depth", base_title); +} + +void RegPopup::SetPos(ImVec2 pos, bool auto_resize) { + char name[128]; + snprintf(name, sizeof(name), "%s###reg_popup_%d", title.c_str(), id); + Begin(name, &open, flags); + SetWindowPos(pos); + if (auto_resize) { + if (std::holds_alternative(data)) { + SetWindowSize({365.0f, 520.0f}); + KeepWindowInside(); + } else if (std::holds_alternative(data)) { + SetWindowSize({404.0f, 543.0f}); + KeepWindowInside(); + } + } + last_pos = GetWindowPos(); + moved = false; + End(); +} + +void RegPopup::Draw() { + char name[128]; + snprintf(name, sizeof(name), "%s###reg_popup_%d", title.c_str(), id); + if (Begin(name, &open, flags)) { + if (GetWindowPos() != last_pos) { + moved = true; + } + + if (const auto* buffer = std::get_if(&data)) { + DrawColorBuffer(*buffer); + } else if (const auto* depth_data = std::get_if(&data)) { + DrawDepthBuffer(*depth_data); + } + } + End(); +} +} // namespace Core::Devtools::Widget diff --git a/src/core/devtools/widget/reg_popup.h b/src/core/devtools/widget/reg_popup.h new file mode 100644 index 000000000..9ccd60ac0 --- /dev/null +++ b/src/core/devtools/widget/reg_popup.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include + +#include "common/types.h" +#include "video_core/renderer_vulkan/liverpool_to_vk.h" + +namespace Core::Devtools::Widget { + +class RegPopup { + int id; + ImGuiWindowFlags flags{ImGuiWindowFlags_NoSavedSettings}; + + using DepthBuffer = std::tuple; + + ImVec2 last_pos; + std::variant data; + std::string title{}; + + static void DrawColorBuffer(const AmdGpu::Liverpool::ColorBuffer& buffer); + + static void DrawDepthBuffer(const DepthBuffer& depth_data); + +public: + bool open = false; + bool moved = false; + + RegPopup(); + + void SetData(const std::string& base_title, AmdGpu::Liverpool::ColorBuffer color_buffer, + u32 cb_id); + + void SetData(const std::string& base_title, AmdGpu::Liverpool::DepthBuffer depth_buffer, + AmdGpu::Liverpool::DepthControl depth_control); + + void SetPos(ImVec2 pos, bool auto_resize = false); + + void Draw(); +}; + +} // namespace Core::Devtools::Widget diff --git a/src/core/devtools/widget/reg_view.cpp b/src/core/devtools/widget/reg_view.cpp new file mode 100644 index 000000000..10cc88085 --- /dev/null +++ b/src/core/devtools/widget/reg_view.cpp @@ -0,0 +1,423 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "common/io_file.h" +#include "core/devtools/options.h" +#include "imgui/imgui_std.h" +#include "imgui_internal.h" +#include "reg_view.h" + +#if defined(_WIN32) +#define popen _popen +#define pclose _pclose +#endif + +using namespace ImGui; +using magic_enum::enum_name; + +constexpr auto depth_id = 0xF3; + +static std::optional exec_cli(const char* cli) { + std::array buffer{}; + std::string output; + const auto f = popen(cli, "r"); + if (!f) { + pclose(f); + return {}; + } + while (fgets(buffer.data(), buffer.size(), f)) { + output += buffer.data(); + } + pclose(f); + return output; +} + +namespace Core::Devtools::Widget { + +void RegView::ProcessShader(int shader_id) { + std::vector shader_code; + Vulkan::Liverpool::UserData user_data; + if (data.is_compute) { + shader_code = data.cs_data.code; + user_data = data.cs_data.cs_program.user_data; + } else { + const auto& s = data.stages[shader_id]; + shader_code = s.code; + user_data = s.user_data.user_data; + } + + std::string shader_dis; + + if (Options.disassembly_cli.empty()) { + shader_dis = "No disassembler set"; + } else { + auto bin_path = std::filesystem::temp_directory_path() / "shadps4_tmp_shader.bin"; + + constexpr std::string_view src_arg = "{src}"; + std::string cli = Options.disassembly_cli; + const auto pos = cli.find(src_arg); + if (pos == std::string::npos) { + DebugState.ShowDebugMessage("Disassembler CLI does not contain {src} argument"); + } else { + cli.replace(pos, src_arg.size(), "\"" + bin_path.string() + "\""); + Common::FS::IOFile file(bin_path, Common::FS::FileAccessMode::Write); + file.Write(shader_code); + file.Close(); + + auto result = exec_cli(cli.c_str()); + shader_dis = result.value_or("Could not disassemble shader"); + if (shader_dis.empty()) { + shader_dis = "Disassembly empty or failed"; + } + + std::filesystem::remove(bin_path); + } + } + + MemoryEditor hex_view; + hex_view.Open = true; + hex_view.ReadOnly = true; + hex_view.Cols = 8; + hex_view.OptShowAscii = false; + hex_view.OptShowOptions = false; + + TextEditor dis_view; + dis_view.SetPalette(TextEditor::GetDarkPalette()); + dis_view.SetReadOnly(true); + dis_view.SetText(shader_dis); + + ShaderCache cache{ + .hex_view = hex_view, + .dis_view = dis_view, + .user_data = user_data, + }; + shader_decomp.emplace(shader_id, std::move(cache)); +} +void RegView::SelectShader(int id) { + selected_shader = id; + if (!shader_decomp.contains(id)) { + ProcessShader(id); + } +} + +void RegView::DrawComputeRegs() { + const auto& cs = data.cs_data.cs_program; + + if (BeginTable("CREGS", 2, ImGuiTableFlags_Borders)) { + TableNextRow(); + + // clang-format off + DrawValueRowList( + "DISPATCH_INITIATOR", cs.dispatch_initiator, + "DIM_X", cs.dim_x, + "DIM_Y", cs.dim_y, + "DIM_Z", cs.dim_z, + "START_X", cs.start_x, + "START_Y", cs.start_y, + "START_Z", cs.start_z, + "NUM_THREAD_X.FULL", cs.num_thread_x.full, + "NUM_THREAD_X.PARTIAL", cs.num_thread_x.partial, + "NUM_THREAD_Y.FULL", cs.num_thread_y.full, + "NUM_THREAD_Y.PARTIAL", cs.num_thread_y.partial, + "NUM_THREAD_Z.FULL", cs.num_thread_z.full, + "NUM_THREAD_Z.PARTIAL", cs.num_thread_z.partial, + "MAX_WAVE_ID", cs.max_wave_id, + "SETTINGS.NUM_VGPRS", cs.settings.num_vgprs, + "SETTINGS.NUM_SGPRS", cs.settings.num_sgprs, + "SETTINGS.NUM_USER_REGS", cs.settings.num_user_regs, + "SETTINGS.TGID_ENABLE", cs.settings.tgid_enable, + "SETTINGS.LDS_DWORDS", cs.settings.lds_dwords, + "RESOURCE_LIMITS", cs.resource_limits + ); + // clang-format on + + EndTable(); + } +} + +void RegView::DrawGraphicsRegs() { + const auto& regs = data.regs; + + if (BeginTable("REGS", 2, ImGuiTableFlags_Borders)) { + TableNextRow(); + + DrawValueRow("Primitive type", regs.primitive_type); + + const auto open_new_popup = [&](int cb, auto... args) { + const auto pos = GetItemRectMax() + ImVec2(5.0f, 0.0f); + if (GetIO().KeyShift) { + auto& pop = extra_reg_popup.emplace_back(); + pop.SetData(title, args...); + pop.open = true; + pop.SetPos(pos, true); + } else if (last_selected_cb == cb && default_reg_popup.open) { + default_reg_popup.open = false; + } else { + last_selected_cb = cb; + default_reg_popup.SetData(title, args...); + if (!default_reg_popup.open || !default_reg_popup.moved) { + default_reg_popup.open = true; + default_reg_popup.SetPos(pos, true); + } + } + }; + + for (int cb = 0; cb < AmdGpu::Liverpool::NumColorBuffers; ++cb) { + PushID(cb); + + TableNextRow(); + TableNextColumn(); + + const auto& buffer = regs.color_buffers[cb]; + + Text("Color buffer %d", cb); + TableNextColumn(); + if (!buffer || !regs.color_target_mask.GetMask(cb)) { + TextUnformatted("N/A"); + } else { + const char* text = last_selected_cb == cb && default_reg_popup.open ? "x" : "->"; + if (SmallButton(text)) { + open_new_popup(cb, buffer, cb); + } + } + + PopID(); + } + + TableNextRow(); + TableNextColumn(); + TextUnformatted("Depth buffer"); + TableNextColumn(); + if (regs.depth_buffer.Address() == 0 || !regs.depth_control.depth_enable) { + TextUnformatted("N/A"); + } else { + const char* text = last_selected_cb == depth_id && default_reg_popup.open ? "x" : "->"; + if (SmallButton(text)) { + open_new_popup(depth_id, regs.depth_buffer, regs.depth_control); + } + } + + auto& s = regs.screen_scissor; + DrawRow("Scissor", "(%d, %d, %d, %d)", s.top_left_x, s.top_left_y, s.bottom_right_x, + s.bottom_right_y); + + DrawValueRow("Color control", regs.color_control.mode); + + DrawRow("Primitive restart", "%X (IDX: %X)", regs.enable_primitive_restart & 1, + regs.primitive_restart_index); + // clang-format off + DrawValueRowList( + "Polygon mode", regs.polygon_control.PolyMode(), + "Cull mode", regs.polygon_control.CullingMode(), + "Clip Space", regs.clipper_control.clip_space, + "Front face", regs.polygon_control.front_face, + "Num Samples", regs.aa_config.NumSamples() + ); + // clang-format on + + EndTable(); + } +} + +RegView::RegView() { + static int unique_id = 0; + id = unique_id++; + + char name[128]; + snprintf(name, sizeof(name), "###reg_dump_%d", id); + SetNextWindowPos({400.0f, 200.0f}); + SetNextWindowSize({290.0f, 435.0f}); + ImGuiID root_dock_id; + Begin(name); + { + char dock_name[64]; + snprintf(dock_name, sizeof(dock_name), "BatchView###reg_dump_%d/dock_space", id); + root_dock_id = ImHashStr(dock_name); + DockSpace(root_dock_id); + } + End(); + + ImGuiID up1, down1; + + DockBuilderRemoveNodeChildNodes(root_dock_id); + DockBuilderSplitNode(root_dock_id, ImGuiDir_Up, 0.19f, &up1, &down1); + + snprintf(name, sizeof(name), "User data###reg_dump_%d/user_data", id); + DockBuilderDockWindow(name, up1); + + snprintf(name, sizeof(name), "Regs###reg_dump_%d/regs", id); + DockBuilderDockWindow(name, down1); + + snprintf(name, sizeof(name), "Disassembly###reg_dump_%d/disassembly", id); + DockBuilderDockWindow(name, down1); + + DockBuilderFinish(root_dock_id); +} + +void RegView::SetData(DebugStateType::RegDump _data, const std::string& base_title, u32 batch_id) { + this->data = std::move(_data); + this->batch_id = batch_id; + this->title = fmt::format("{}/Batch {}", base_title, batch_id); + // clear cache + shader_decomp.clear(); + if (data.is_compute) { + selected_shader = -2; + last_selected_cb = -1; + default_reg_popup.open = false; + ProcessShader(-2); + } else { + const auto& regs = data.regs; + if (selected_shader >= 0 && !regs.stage_enable.IsStageEnabled(selected_shader)) { + selected_shader = -1; + } + if (default_reg_popup.open) { + default_reg_popup.open = false; + if (last_selected_cb == depth_id) { + const auto& has_depth = + regs.depth_buffer.Address() != 0 && regs.depth_control.depth_enable; + if (has_depth) { + default_reg_popup.SetData(title, regs.depth_buffer, regs.depth_control); + default_reg_popup.open = true; + } + } else if (last_selected_cb >= 0 && + last_selected_cb < AmdGpu::Liverpool::NumColorBuffers) { + const auto& buffer = regs.color_buffers[last_selected_cb]; + const bool has_cb = buffer && regs.color_target_mask.GetMask(last_selected_cb); + if (has_cb) { + default_reg_popup.SetData(title, buffer, last_selected_cb); + default_reg_popup.open = true; + } + } + } + } + extra_reg_popup.clear(); +} + +void RegView::SetPos(ImVec2 pos) { + char name[128]; + snprintf(name, sizeof(name), "%s###reg_dump_%d", title.c_str(), id); + Begin(name, &open, ImGuiWindowFlags_MenuBar); + SetWindowPos(pos); + KeepWindowInside(); + last_pos = GetWindowPos(); + moved = false; + End(); +} + +void RegView::Draw() { + char name[128]; + snprintf(name, sizeof(name), "%s###reg_dump_%d", title.c_str(), id); + + if (Begin(name, &open, ImGuiWindowFlags_MenuBar)) { + if (GetWindowPos() != last_pos) { + moved = true; + } + + const char* names[] = {"vs", "ps", "gs", "es", "hs", "ls"}; + + if (BeginMenuBar()) { + if (BeginMenu("Windows")) { + Checkbox("Registers", &show_registers); + Checkbox("User data", &show_user_data); + Checkbox("Disassembly", &show_disassembly); + ImGui::EndMenu(); + } + EndMenuBar(); + } + + if (!data.is_compute && + BeginChild("STAGES", {}, + ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeY)) { + for (int i = 0; i < DebugStateType::RegDump::MaxShaderStages; i++) { + if (data.regs.stage_enable.IsStageEnabled(i)) { + const bool selected = selected_shader == i; + if (selected) { + PushStyleColor(ImGuiCol_Button, ImVec4{1.0f, 0.7f, 0.7f, 1.0f}); + } + if (Button(names[i], {40.0f, 40.0f})) { + SelectShader(i); + } + if (selected) { + PopStyleColor(); + } + } + SameLine(); + } + EndChild(); + } + } + char dock_name[64]; + snprintf(dock_name, sizeof(dock_name), "BatchView###reg_dump_%d/dock_space", id); + auto root_dock_id = ImHashStr(dock_name); + DockSpace(root_dock_id); + End(); + + auto get_shader = [&]() -> ShaderCache* { + auto shader_cache = shader_decomp.find(selected_shader); + if (shader_cache == shader_decomp.end()) { + return nullptr; + } + return &shader_cache->second; + }; + + if (show_user_data) { + snprintf(name, sizeof(name), "User data###reg_dump_%d/user_data", id); + + if (Begin(name, &show_user_data, ImGuiWindowFlags_NoScrollbar)) { + auto shader = get_shader(); + if (!shader) { + Text("Stage not selected"); + } else { + shader->hex_view.DrawContents(shader->user_data.data(), shader->user_data.size()); + } + } + End(); + } + + if (show_disassembly) { + snprintf(name, sizeof(name), "Disassembly###reg_dump_%d/disassembly", id); + if (Begin(name, &show_disassembly)) { + auto shader = get_shader(); + if (!shader) { + Text("Stage not selected"); + } else { + shader->dis_view.Render("Disassembly", GetContentRegionAvail()); + } + } + End(); + } + + if (show_registers) { + snprintf(name, sizeof(name), "Regs###reg_dump_%d/regs", id); + if (Begin(name, &show_registers)) { + if (data.is_compute) { + DrawComputeRegs(); + } else { + DrawGraphicsRegs(); + } + } + End(); + } + + if (default_reg_popup.open) { + default_reg_popup.Draw(); + } + for (auto it = extra_reg_popup.begin(); it != extra_reg_popup.end();) { + if (!it->open) { + it = extra_reg_popup.erase(it); + continue; + } + it->Draw(); + ++it; + } +} + +} // namespace Core::Devtools::Widget \ No newline at end of file diff --git a/src/core/devtools/widget/reg_view.h b/src/core/devtools/widget/reg_view.h new file mode 100644 index 000000000..3ac8ec077 --- /dev/null +++ b/src/core/devtools/widget/reg_view.h @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include "core/debug_state.h" +#include "imgui_memory_editor.h" +#include "reg_popup.h" +#include "text_editor.h" + +namespace Core::Devtools::Widget { + +struct ShaderCache { + MemoryEditor hex_view; + TextEditor dis_view; + Vulkan::Liverpool::UserData user_data; +}; + +class RegView { + int id; + + std::string title; + DebugStateType::RegDump data; + u32 batch_id{~0u}; + ImVec2 last_pos; + + std::unordered_map shader_decomp; + int selected_shader{-1}; + RegPopup default_reg_popup; + int last_selected_cb{-1}; + std::vector extra_reg_popup; + + bool show_registers{true}; + bool show_user_data{true}; + bool show_disassembly{true}; + + void ProcessShader(int shader_id); + + void SelectShader(int shader_id); + + void DrawComputeRegs(); + + void DrawGraphicsRegs(); + +public: + bool open = false; + bool moved = false; + + RegView(); + + void SetData(DebugStateType::RegDump data, const std::string& base_title, u32 batch_id); + + void SetPos(ImVec2 pos); + + void Draw(); +}; + +} // namespace Core::Devtools::Widget \ No newline at end of file diff --git a/src/core/devtools/widget/text_editor.cpp b/src/core/devtools/widget/text_editor.cpp new file mode 100644 index 000000000..f447e45a2 --- /dev/null +++ b/src/core/devtools/widget/text_editor.cpp @@ -0,0 +1,2334 @@ +// SPDX-FileCopyrightText: Copyright (c) 2017 BalazsJako +// SPDX-License-Identifier: MIT + +// source: https://github.com/BalazsJako/ImGuiColorTextEdit + +#include +#include +#include +#include +#include + +#include "text_editor.h" + +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui.h" // for imGui::GetCurrentWindow() + +// TODO +// - multiline comments vs single-line: latter is blocking start of a ML + +namespace Core::Devtools::Widget { + +template +bool equals(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, BinaryPredicate p) { + for (; first1 != last1 && first2 != last2; ++first1, ++first2) { + if (!p(*first1, *first2)) + return false; + } + return first1 == last1 && first2 == last2; +} + +TextEditor::TextEditor() + : mLineSpacing(1.0f), mUndoIndex(0), mTabSize(4), mOverwrite(false), mReadOnly(false), + mWithinRender(false), mScrollToCursor(false), mScrollToTop(false), mTextChanged(false), + mColorizerEnabled(true), mTextStart(20.0f), mLeftMargin(10), mCursorPositionChanged(false), + mColorRangeMin(0), mColorRangeMax(0), mSelectionMode(SelectionMode::Normal), + mCheckComments(true), mLastClick(-1.0f), mHandleKeyboardInputs(true), + mHandleMouseInputs(true), mIgnoreImGuiChild(false), mShowWhitespaces(true), + mStartTime(std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count()) { + SetPalette(GetDarkPalette()); + mLines.push_back(Line()); +} + +TextEditor::~TextEditor() {} + +void TextEditor::SetLanguageDefinition(const LanguageDefinition& aLanguageDef) { + mLanguageDefinition = aLanguageDef; + mRegexList.clear(); + + for (auto& r : mLanguageDefinition.mTokenRegexStrings) + mRegexList.push_back( + std::make_pair(std::regex(r.first, std::regex_constants::optimize), r.second)); + + Colorize(); +} + +void TextEditor::SetPalette(const Palette& aValue) { + mPaletteBase = aValue; +} + +std::string TextEditor::GetText(const Coordinates& aStart, const Coordinates& aEnd) const { + std::string result; + + auto lstart = aStart.mLine; + auto lend = aEnd.mLine; + auto istart = GetCharacterIndex(aStart); + auto iend = GetCharacterIndex(aEnd); + size_t s = 0; + + for (size_t i = lstart; i < lend; i++) + s += mLines[i].size(); + + result.reserve(s + s / 8); + + while (istart < iend || lstart < lend) { + if (lstart >= (int)mLines.size()) + break; + + auto& line = mLines[lstart]; + if (istart < (int)line.size()) { + result += line[istart].mChar; + istart++; + } else { + istart = 0; + ++lstart; + result += '\n'; + } + } + + return result; +} + +TextEditor::Coordinates TextEditor::GetActualCursorCoordinates() const { + return SanitizeCoordinates(mState.mCursorPosition); +} + +TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates& aValue) const { + auto line = aValue.mLine; + auto column = aValue.mColumn; + if (line >= (int)mLines.size()) { + if (mLines.empty()) { + line = 0; + column = 0; + } else { + line = (int)mLines.size() - 1; + column = GetLineMaxColumn(line); + } + return Coordinates(line, column); + } else { + column = mLines.empty() ? 0 : std::min(column, GetLineMaxColumn(line)); + return Coordinates(line, column); + } +} + +// https://en.wikipedia.org/wiki/UTF-8 +// We assume that the char is a standalone character (<128) or a leading byte of an UTF-8 code +// sequence (non-10xxxxxx code) +static int UTF8CharLength(TextEditor::Char c) { + if ((c & 0xFE) == 0xFC) + return 6; + if ((c & 0xFC) == 0xF8) + return 5; + if ((c & 0xF8) == 0xF0) + return 4; + else if ((c & 0xF0) == 0xE0) + return 3; + else if ((c & 0xE0) == 0xC0) + return 2; + return 1; +} + +// "Borrowed" from ImGui source +static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) { + if (c < 0x80) { + buf[0] = (char)c; + return 1; + } + if (c < 0x800) { + if (buf_size < 2) + return 0; + buf[0] = (char)(0xc0 + (c >> 6)); + buf[1] = (char)(0x80 + (c & 0x3f)); + return 2; + } + if (c >= 0xdc00 && c < 0xe000) { + return 0; + } + if (c >= 0xd800 && c < 0xdc00) { + if (buf_size < 4) + return 0; + buf[0] = (char)(0xf0 + (c >> 18)); + buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); + buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[3] = (char)(0x80 + ((c) & 0x3f)); + return 4; + } + // else if (c < 0x10000) + { + if (buf_size < 3) + return 0; + buf[0] = (char)(0xe0 + (c >> 12)); + buf[1] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[2] = (char)(0x80 + ((c) & 0x3f)); + return 3; + } +} + +void TextEditor::Advance(Coordinates& aCoordinates) const { + if (aCoordinates.mLine < (int)mLines.size()) { + auto& line = mLines[aCoordinates.mLine]; + auto cindex = GetCharacterIndex(aCoordinates); + + if (cindex + 1 < (int)line.size()) { + auto delta = UTF8CharLength(line[cindex].mChar); + cindex = std::min(cindex + delta, (int)line.size() - 1); + } else { + ++aCoordinates.mLine; + cindex = 0; + } + aCoordinates.mColumn = GetCharacterColumn(aCoordinates.mLine, cindex); + } +} + +void TextEditor::DeleteRange(const Coordinates& aStart, const Coordinates& aEnd) { + ASSERT(aEnd >= aStart); + ASSERT(!mReadOnly); + + // printf("D(%d.%d)-(%d.%d)\n", aStart.mLine, aStart.mColumn, aEnd.mLine, aEnd.mColumn); + + if (aEnd == aStart) + return; + + auto start = GetCharacterIndex(aStart); + auto end = GetCharacterIndex(aEnd); + + if (aStart.mLine == aEnd.mLine) { + auto& line = mLines[aStart.mLine]; + auto n = GetLineMaxColumn(aStart.mLine); + if (aEnd.mColumn >= n) + line.erase(line.begin() + start, line.end()); + else + line.erase(line.begin() + start, line.begin() + end); + } else { + auto& firstLine = mLines[aStart.mLine]; + auto& lastLine = mLines[aEnd.mLine]; + + firstLine.erase(firstLine.begin() + start, firstLine.end()); + lastLine.erase(lastLine.begin(), lastLine.begin() + end); + + if (aStart.mLine < aEnd.mLine) + firstLine.insert(firstLine.end(), lastLine.begin(), lastLine.end()); + + if (aStart.mLine < aEnd.mLine) + RemoveLine(aStart.mLine + 1, aEnd.mLine + 1); + } + + mTextChanged = true; +} + +int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char* aValue) { + ASSERT(!mReadOnly); + + int cindex = GetCharacterIndex(aWhere); + int totalLines = 0; + while (*aValue != '\0') { + ASSERT(!mLines.empty()); + + if (*aValue == '\r') { + // skip + ++aValue; + } else if (*aValue == '\n') { + if (cindex < (int)mLines[aWhere.mLine].size()) { + auto& newLine = InsertLine(aWhere.mLine + 1); + auto& line = mLines[aWhere.mLine]; + newLine.insert(newLine.begin(), line.begin() + cindex, line.end()); + line.erase(line.begin() + cindex, line.end()); + } else { + InsertLine(aWhere.mLine + 1); + } + ++aWhere.mLine; + aWhere.mColumn = 0; + cindex = 0; + ++totalLines; + ++aValue; + } else { + auto& line = mLines[aWhere.mLine]; + auto d = UTF8CharLength(*aValue); + while (d-- > 0 && *aValue != '\0') + line.insert(line.begin() + cindex++, Glyph(*aValue++, PaletteIndex::Default)); + ++aWhere.mColumn; + } + + mTextChanged = true; + } + + return totalLines; +} + +void TextEditor::AddUndo(UndoRecord& aValue) { + ASSERT(!mReadOnly); + // printf("AddUndo: (@%d.%d) +\'%s' [%d.%d .. %d.%d], -\'%s', [%d.%d .. %d.%d] (@%d.%d)\n", + // aValue.mBefore.mCursorPosition.mLine, aValue.mBefore.mCursorPosition.mColumn, + // aValue.mAdded.c_str(), aValue.mAddedStart.mLine, aValue.mAddedStart.mColumn, + // aValue.mAddedEnd.mLine, aValue.mAddedEnd.mColumn, aValue.mRemoved.c_str(), + // aValue.mRemovedStart.mLine, aValue.mRemovedStart.mColumn, aValue.mRemovedEnd.mLine, + // aValue.mRemovedEnd.mColumn, aValue.mAfter.mCursorPosition.mLine, + // aValue.mAfter.mCursorPosition.mColumn + // ); + + mUndoBuffer.resize((size_t)(mUndoIndex + 1)); + mUndoBuffer.back() = aValue; + ++mUndoIndex; +} + +TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2& aPosition) const { + ImVec2 origin = ImGui::GetCursorScreenPos(); + ImVec2 local(aPosition.x - origin.x, aPosition.y - origin.y); + + int lineNo = std::max(0, (int)floor(local.y / mCharAdvance.y)); + + int columnCoord = 0; + + if (lineNo >= 0 && lineNo < (int)mLines.size()) { + auto& line = mLines.at(lineNo); + + int columnIndex = 0; + float columnX = 0.0f; + + while ((size_t)columnIndex < line.size()) { + float columnWidth = 0.0f; + + if (line[columnIndex].mChar == '\t') { + float spaceSize = + ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ").x; + float oldX = columnX; + float newColumnX = + (1.0f + std::floor((1.0f + columnX) / (float(mTabSize) * spaceSize))) * + (float(mTabSize) * spaceSize); + columnWidth = newColumnX - oldX; + if (mTextStart + columnX + columnWidth * 0.5f > local.x) + break; + columnX = newColumnX; + columnCoord = (columnCoord / mTabSize) * mTabSize + mTabSize; + columnIndex++; + } else { + char buf[7]; + auto d = UTF8CharLength(line[columnIndex].mChar); + int i = 0; + while (i < 6 && d-- > 0) + buf[i++] = line[columnIndex++].mChar; + buf[i] = '\0'; + columnWidth = + ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf).x; + if (mTextStart + columnX + columnWidth * 0.5f > local.x) + break; + columnX += columnWidth; + columnCoord++; + } + } + } + + return SanitizeCoordinates(Coordinates(lineNo, columnCoord)); +} + +TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates& aFrom) const { + Coordinates at = aFrom; + if (at.mLine >= (int)mLines.size()) + return at; + + auto& line = mLines[at.mLine]; + auto cindex = GetCharacterIndex(at); + + if (cindex >= (int)line.size()) + return at; + + while (cindex > 0 && isspace(line[cindex].mChar)) + --cindex; + + auto cstart = (PaletteIndex)line[cindex].mColorIndex; + while (cindex > 0) { + auto c = line[cindex].mChar; + if ((c & 0xC0) != 0x80) // not UTF code sequence 10xxxxxx + { + if (c <= 32 && isspace(c)) { + cindex++; + break; + } + if (cstart != (PaletteIndex)line[size_t(cindex - 1)].mColorIndex) + break; + } + --cindex; + } + return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex)); +} + +TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates& aFrom) const { + Coordinates at = aFrom; + if (at.mLine >= (int)mLines.size()) + return at; + + auto& line = mLines[at.mLine]; + auto cindex = GetCharacterIndex(at); + + if (cindex >= (int)line.size()) + return at; + + bool prevspace = (bool)isspace(line[cindex].mChar); + auto cstart = (PaletteIndex)line[cindex].mColorIndex; + while (cindex < (int)line.size()) { + auto c = line[cindex].mChar; + auto d = UTF8CharLength(c); + if (cstart != (PaletteIndex)line[cindex].mColorIndex) + break; + + if (prevspace != !!isspace(c)) { + if (isspace(c)) + while (cindex < (int)line.size() && isspace(line[cindex].mChar)) + ++cindex; + break; + } + cindex += d; + } + return Coordinates(aFrom.mLine, GetCharacterColumn(aFrom.mLine, cindex)); +} + +TextEditor::Coordinates TextEditor::FindNextWord(const Coordinates& aFrom) const { + Coordinates at = aFrom; + if (at.mLine >= (int)mLines.size()) + return at; + + // skip to the next non-word character + auto cindex = GetCharacterIndex(aFrom); + bool isword = false; + bool skip = false; + if (cindex < (int)mLines[at.mLine].size()) { + auto& line = mLines[at.mLine]; + isword = isalnum(line[cindex].mChar); + skip = isword; + } + + while (!isword || skip) { + if (at.mLine >= mLines.size()) { + auto l = std::max(0, (int)mLines.size() - 1); + return Coordinates(l, GetLineMaxColumn(l)); + } + + auto& line = mLines[at.mLine]; + if (cindex < (int)line.size()) { + isword = isalnum(line[cindex].mChar); + + if (isword && !skip) + return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex)); + + if (!isword) + skip = false; + + cindex++; + } else { + cindex = 0; + ++at.mLine; + skip = false; + isword = false; + } + } + + return at; +} + +int TextEditor::GetCharacterIndex(const Coordinates& aCoordinates) const { + if (aCoordinates.mLine >= mLines.size()) + return -1; + auto& line = mLines[aCoordinates.mLine]; + int c = 0; + int i = 0; + for (; i < line.size() && c < aCoordinates.mColumn;) { + if (line[i].mChar == '\t') + c = (c / mTabSize) * mTabSize + mTabSize; + else + ++c; + i += UTF8CharLength(line[i].mChar); + } + return i; +} + +int TextEditor::GetCharacterColumn(int aLine, int aIndex) const { + if (aLine >= mLines.size()) + return 0; + auto& line = mLines[aLine]; + int col = 0; + int i = 0; + while (i < aIndex && i < (int)line.size()) { + auto c = line[i].mChar; + i += UTF8CharLength(c); + if (c == '\t') + col = (col / mTabSize) * mTabSize + mTabSize; + else + col++; + } + return col; +} + +int TextEditor::GetLineCharacterCount(int aLine) const { + if (aLine >= mLines.size()) + return 0; + auto& line = mLines[aLine]; + int c = 0; + for (unsigned i = 0; i < line.size(); c++) + i += UTF8CharLength(line[i].mChar); + return c; +} + +int TextEditor::GetLineMaxColumn(int aLine) const { + if (aLine >= mLines.size()) + return 0; + auto& line = mLines[aLine]; + int col = 0; + for (unsigned i = 0; i < line.size();) { + auto c = line[i].mChar; + if (c == '\t') + col = (col / mTabSize) * mTabSize + mTabSize; + else + col++; + i += UTF8CharLength(c); + } + return col; +} + +bool TextEditor::IsOnWordBoundary(const Coordinates& aAt) const { + if (aAt.mLine >= (int)mLines.size() || aAt.mColumn == 0) + return true; + + auto& line = mLines[aAt.mLine]; + auto cindex = GetCharacterIndex(aAt); + if (cindex >= (int)line.size()) + return true; + + if (mColorizerEnabled) + return line[cindex].mColorIndex != line[size_t(cindex - 1)].mColorIndex; + + return isspace(line[cindex].mChar) != isspace(line[cindex - 1].mChar); +} + +void TextEditor::RemoveLine(int aStart, int aEnd) { + ASSERT(!mReadOnly); + ASSERT(aEnd >= aStart); + ASSERT(mLines.size() > (size_t)(aEnd - aStart)); + + ErrorMarkers etmp; + for (auto& i : mErrorMarkers) { + ErrorMarkers::value_type e(i.first >= aStart ? i.first - 1 : i.first, i.second); + if (e.first >= aStart && e.first <= aEnd) + continue; + etmp.insert(e); + } + mErrorMarkers = std::move(etmp); + + Breakpoints btmp; + for (auto i : mBreakpoints) { + if (i >= aStart && i <= aEnd) + continue; + btmp.insert(i >= aStart ? i - 1 : i); + } + mBreakpoints = std::move(btmp); + + mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd); + ASSERT(!mLines.empty()); + + mTextChanged = true; +} + +void TextEditor::RemoveLine(int aIndex) { + ASSERT(!mReadOnly); + ASSERT(mLines.size() > 1); + + ErrorMarkers etmp; + for (auto& i : mErrorMarkers) { + ErrorMarkers::value_type e(i.first > aIndex ? i.first - 1 : i.first, i.second); + if (e.first - 1 == aIndex) + continue; + etmp.insert(e); + } + mErrorMarkers = std::move(etmp); + + Breakpoints btmp; + for (auto i : mBreakpoints) { + if (i == aIndex) + continue; + btmp.insert(i >= aIndex ? i - 1 : i); + } + mBreakpoints = std::move(btmp); + + mLines.erase(mLines.begin() + aIndex); + ASSERT(!mLines.empty()); + + mTextChanged = true; +} + +TextEditor::Line& TextEditor::InsertLine(int aIndex) { + ASSERT(!mReadOnly); + + auto& result = *mLines.insert(mLines.begin() + aIndex, Line()); + + ErrorMarkers etmp; + for (auto& i : mErrorMarkers) + etmp.insert(ErrorMarkers::value_type(i.first >= aIndex ? i.first + 1 : i.first, i.second)); + mErrorMarkers = std::move(etmp); + + Breakpoints btmp; + for (auto i : mBreakpoints) + btmp.insert(i >= aIndex ? i + 1 : i); + mBreakpoints = std::move(btmp); + + return result; +} + +std::string TextEditor::GetWordUnderCursor() const { + auto c = GetCursorPosition(); + return GetWordAt(c); +} + +std::string TextEditor::GetWordAt(const Coordinates& aCoords) const { + auto start = FindWordStart(aCoords); + auto end = FindWordEnd(aCoords); + + std::string r; + + auto istart = GetCharacterIndex(start); + auto iend = GetCharacterIndex(end); + + for (auto it = istart; it < iend; ++it) + r.push_back(mLines[aCoords.mLine][it].mChar); + + return r; +} + +ImU32 TextEditor::GetGlyphColor(const Glyph& aGlyph) const { + if (!mColorizerEnabled) + return mPalette[(int)PaletteIndex::Default]; + if (aGlyph.mComment) + return mPalette[(int)PaletteIndex::Comment]; + if (aGlyph.mMultiLineComment) + return mPalette[(int)PaletteIndex::MultiLineComment]; + auto const color = mPalette[(int)aGlyph.mColorIndex]; + if (aGlyph.mPreprocessor) { + const auto ppcolor = mPalette[(int)PaletteIndex::Preprocessor]; + const int c0 = ((ppcolor & 0xff) + (color & 0xff)) / 2; + const int c1 = (((ppcolor >> 8) & 0xff) + ((color >> 8) & 0xff)) / 2; + const int c2 = (((ppcolor >> 16) & 0xff) + ((color >> 16) & 0xff)) / 2; + const int c3 = (((ppcolor >> 24) & 0xff) + ((color >> 24) & 0xff)) / 2; + return ImU32(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24)); + } + return color; +} + +void TextEditor::HandleKeyboardInputs() { + ImGuiIO& io = ImGui::GetIO(); + auto shift = io.KeyShift; + auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl; + auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt; + + if (ImGui::IsWindowFocused()) { + if (ImGui::IsWindowHovered()) + ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput); + // ImGui::CaptureKeyboardFromApp(true); + + io.WantCaptureKeyboard = true; + io.WantTextInput = true; + + if (!IsReadOnly() && ctrl && !shift && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Z))) + Undo(); + else if (!IsReadOnly() && !ctrl && !shift && alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) + Undo(); + else if (!IsReadOnly() && ctrl && !shift && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Y))) + Redo(); + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow))) + MoveUp(1, shift); + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow))) + MoveDown(1, shift); + else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow))) + MoveLeft(1, shift, ctrl); + else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow))) + MoveRight(1, shift, ctrl); + else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp))) + MoveUp(GetPageSize() - 4, shift); + else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown))) + MoveDown(GetPageSize() - 4, shift); + else if (!alt && ctrl && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home))) + MoveTop(shift); + else if (ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End))) + MoveBottom(shift); + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home))) + MoveHome(shift); + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End))) + MoveEnd(shift); + else if (!IsReadOnly() && !ctrl && !shift && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) + Delete(); + else if (!IsReadOnly() && !ctrl && !shift && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) + Backspace(); + else if (!ctrl && !shift && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) + mOverwrite ^= true; + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) + Copy(); + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C))) + Copy(); + else if (!IsReadOnly() && !ctrl && shift && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) + Paste(); + else if (!IsReadOnly() && ctrl && !shift && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_V))) + Paste(); + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_X))) + Cut(); + else if (!ctrl && shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) + Cut(); + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_A))) + SelectAll(); + else if (!IsReadOnly() && !ctrl && !shift && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter))) + EnterCharacter('\n', false); + else if (!IsReadOnly() && !ctrl && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Tab))) + EnterCharacter('\t', shift); + + if (!IsReadOnly() && !io.InputQueueCharacters.empty()) { + for (int i = 0; i < io.InputQueueCharacters.Size; i++) { + auto c = io.InputQueueCharacters[i]; + if (c != 0 && (c == '\n' || c >= 32)) + EnterCharacter(c, shift); + } + io.InputQueueCharacters.resize(0); + } + } +} + +void TextEditor::HandleMouseInputs() { + ImGuiIO& io = ImGui::GetIO(); + auto shift = io.KeyShift; + auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl; + auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt; + + if (ImGui::IsWindowHovered()) { + if (!shift && !alt) { + auto click = ImGui::IsMouseClicked(0); + auto doubleClick = ImGui::IsMouseDoubleClicked(0); + auto t = ImGui::GetTime(); + auto tripleClick = click && !doubleClick && + (mLastClick != -1.0f && (t - mLastClick) < io.MouseDoubleClickTime); + + /* + Left mouse button triple click + */ + + if (tripleClick) { + if (!ctrl) { + mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = + ScreenPosToCoordinates(ImGui::GetMousePos()); + mSelectionMode = SelectionMode::Line; + SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); + } + + mLastClick = -1.0f; + } + + /* + Left mouse button double click + */ + + else if (doubleClick) { + if (!ctrl) { + mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = + ScreenPosToCoordinates(ImGui::GetMousePos()); + if (mSelectionMode == SelectionMode::Line) + mSelectionMode = SelectionMode::Normal; + else + mSelectionMode = SelectionMode::Word; + SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); + } + + mLastClick = (float)ImGui::GetTime(); + } + + /* + Left mouse button click + */ + else if (click) { + mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = + ScreenPosToCoordinates(ImGui::GetMousePos()); + if (ctrl) + mSelectionMode = SelectionMode::Word; + else + mSelectionMode = SelectionMode::Normal; + SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); + + mLastClick = (float)ImGui::GetTime(); + } + // Mouse left button dragging (=> update selection) + else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0)) { + io.WantCaptureMouse = true; + mState.mCursorPosition = mInteractiveEnd = + ScreenPosToCoordinates(ImGui::GetMousePos()); + SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); + } + } + } +} + +void TextEditor::Render() { + /* Compute mCharAdvance regarding to scaled font size (Ctrl + mouse wheel)*/ + const float fontSize = + ImGui::GetFont() + ->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr) + .x; + mCharAdvance = ImVec2(fontSize, ImGui::GetTextLineHeightWithSpacing() * mLineSpacing); + + /* Update palette with the current alpha from style */ + for (int i = 0; i < (int)PaletteIndex::Max; ++i) { + auto color = ImGui::ColorConvertU32ToFloat4(mPaletteBase[i]); + color.w *= ImGui::GetStyle().Alpha; + mPalette[i] = ImGui::ColorConvertFloat4ToU32(color); + } + + ASSERT(mLineBuffer.empty()); + + auto contentSize = ImGui::GetWindowContentRegionMax(); + auto drawList = ImGui::GetWindowDrawList(); + float longest(mTextStart); + + if (mScrollToTop) { + mScrollToTop = false; + ImGui::SetScrollY(0.f); + } + + ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos(); + auto scrollX = ImGui::GetScrollX(); + auto scrollY = ImGui::GetScrollY(); + + auto lineNo = (int)floor(scrollY / mCharAdvance.y); + auto globalLineMax = (int)mLines.size(); + auto lineMax = + std::max(0, std::min((int)mLines.size() - 1, + lineNo + (int)floor((scrollY + contentSize.y) / mCharAdvance.y))); + + // Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width + char buf[16]; + snprintf(buf, 16, " %d ", globalLineMax); + mTextStart = ImGui::GetFont() + ->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr) + .x + + mLeftMargin; + + if (!mLines.empty()) { + float spaceSize = + ImGui::GetFont() + ->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr) + .x; + + while (lineNo <= lineMax) { + ImVec2 lineStartScreenPos = + ImVec2(cursorScreenPos.x, cursorScreenPos.y + lineNo * mCharAdvance.y); + ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mTextStart, lineStartScreenPos.y); + + auto& line = mLines[lineNo]; + longest = std::max( + mTextStart + TextDistanceToLineStart(Coordinates(lineNo, GetLineMaxColumn(lineNo))), + longest); + auto columnNo = 0; + Coordinates lineStartCoord(lineNo, 0); + Coordinates lineEndCoord(lineNo, GetLineMaxColumn(lineNo)); + + // Draw selection for the current line + float sstart = -1.0f; + float ssend = -1.0f; + + ASSERT(mState.mSelectionStart <= mState.mSelectionEnd); + if (mState.mSelectionStart <= lineEndCoord) + sstart = mState.mSelectionStart > lineStartCoord + ? TextDistanceToLineStart(mState.mSelectionStart) + : 0.0f; + if (mState.mSelectionEnd > lineStartCoord) + ssend = TextDistanceToLineStart( + mState.mSelectionEnd < lineEndCoord ? mState.mSelectionEnd : lineEndCoord); + + if (mState.mSelectionEnd.mLine > lineNo) + ssend += mCharAdvance.x; + + if (sstart != -1 && ssend != -1 && sstart < ssend) { + ImVec2 vstart(lineStartScreenPos.x + mTextStart + sstart, lineStartScreenPos.y); + ImVec2 vend(lineStartScreenPos.x + mTextStart + ssend, + lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(vstart, vend, mPalette[(int)PaletteIndex::Selection]); + } + + // Draw breakpoints + auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y); + + if (mBreakpoints.count(lineNo + 1) != 0) { + auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, + lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::Breakpoint]); + } + + // Draw error markers + auto errorIt = mErrorMarkers.find(lineNo + 1); + if (errorIt != mErrorMarkers.end()) { + auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, + lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::ErrorMarker]); + + if (ImGui::IsMouseHoveringRect(lineStartScreenPos, end)) { + ImGui::BeginTooltip(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f)); + ImGui::Text("Error at line %d:", errorIt->first); + ImGui::PopStyleColor(); + ImGui::Separator(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 0.2f, 1.0f)); + ImGui::Text("%s", errorIt->second.c_str()); + ImGui::PopStyleColor(); + ImGui::EndTooltip(); + } + } + + // Draw line number (right aligned) + snprintf(buf, 16, "%d ", lineNo + 1); + + auto lineNoWidth = + ImGui::GetFont() + ->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr) + .x; + drawList->AddText( + ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y), + mPalette[(int)PaletteIndex::LineNumber], buf); + + if (mState.mCursorPosition.mLine == lineNo) { + auto focused = ImGui::IsWindowFocused(); + + // Highlight the current line (where the cursor is) + if (!HasSelection()) { + auto end = ImVec2(start.x + contentSize.x + scrollX, start.y + mCharAdvance.y); + drawList->AddRectFilled( + start, end, + mPalette[(int)(focused ? PaletteIndex::CurrentLineFill + : PaletteIndex::CurrentLineFillInactive)]); + drawList->AddRect(start, end, mPalette[(int)PaletteIndex::CurrentLineEdge], + 1.0f); + } + + // Render the cursor + if (focused) { + auto timeEnd = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + auto elapsed = timeEnd - mStartTime; + if (elapsed > 400) { + float width = 1.0f; + auto cindex = GetCharacterIndex(mState.mCursorPosition); + float cx = TextDistanceToLineStart(mState.mCursorPosition); + + if (mOverwrite && cindex < (int)line.size()) { + auto c = line[cindex].mChar; + if (c == '\t') { + auto x = (1.0f + + std::floor((1.0f + cx) / (float(mTabSize) * spaceSize))) * + (float(mTabSize) * spaceSize); + width = x - cx; + } else { + char buf2[2]; + buf2[0] = line[cindex].mChar; + buf2[1] = '\0'; + width = + ImGui::GetFont() + ->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf2) + .x; + } + } + ImVec2 cstart(textScreenPos.x + cx, lineStartScreenPos.y); + ImVec2 cend(textScreenPos.x + cx + width, + lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(cstart, cend, mPalette[(int)PaletteIndex::Cursor]); + if (elapsed > 800) + mStartTime = timeEnd; + } + } + } + + // Render colorized text + auto prevColor = + line.empty() ? mPalette[(int)PaletteIndex::Default] : GetGlyphColor(line[0]); + ImVec2 bufferOffset; + + for (int i = 0; i < line.size();) { + auto& glyph = line[i]; + auto color = GetGlyphColor(glyph); + + if ((color != prevColor || glyph.mChar == '\t' || glyph.mChar == ' ') && + !mLineBuffer.empty()) { + const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, + textScreenPos.y + bufferOffset.y); + drawList->AddText(newOffset, prevColor, mLineBuffer.c_str()); + auto textSize = + ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, + mLineBuffer.c_str(), nullptr, nullptr); + bufferOffset.x += textSize.x; + mLineBuffer.clear(); + } + prevColor = color; + + if (glyph.mChar == '\t') { + auto oldX = bufferOffset.x; + bufferOffset.x = (1.0f + std::floor((1.0f + bufferOffset.x) / + (float(mTabSize) * spaceSize))) * + (float(mTabSize) * spaceSize); + ++i; + + if (mShowWhitespaces) { + const auto s = ImGui::GetFontSize(); + const auto x1 = textScreenPos.x + oldX + 1.0f; + const auto x2 = textScreenPos.x + bufferOffset.x - 1.0f; + const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f; + const ImVec2 p1(x1, y); + const ImVec2 p2(x2, y); + const ImVec2 p3(x2 - s * 0.2f, y - s * 0.2f); + const ImVec2 p4(x2 - s * 0.2f, y + s * 0.2f); + drawList->AddLine(p1, p2, 0x90909090); + drawList->AddLine(p2, p3, 0x90909090); + drawList->AddLine(p2, p4, 0x90909090); + } + } else if (glyph.mChar == ' ') { + if (mShowWhitespaces) { + const auto s = ImGui::GetFontSize(); + const auto x = textScreenPos.x + bufferOffset.x + spaceSize * 0.5f; + const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f; + drawList->AddCircleFilled(ImVec2(x, y), 1.5f, 0x80808080, 4); + } + bufferOffset.x += spaceSize; + i++; + } else { + auto l = UTF8CharLength(glyph.mChar); + while (l-- > 0) + mLineBuffer.push_back(line[i++].mChar); + } + ++columnNo; + } + + if (!mLineBuffer.empty()) { + const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, + textScreenPos.y + bufferOffset.y); + drawList->AddText(newOffset, prevColor, mLineBuffer.c_str()); + mLineBuffer.clear(); + } + + ++lineNo; + } + + // Draw a tooltip on known identifiers/preprocessor symbols + if (ImGui::IsMousePosValid()) { + auto id = GetWordAt(ScreenPosToCoordinates(ImGui::GetMousePos())); + if (!id.empty()) { + auto it = mLanguageDefinition.mIdentifiers.find(id); + if (it != mLanguageDefinition.mIdentifiers.end()) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(it->second.mDeclaration.c_str()); + ImGui::EndTooltip(); + } else { + auto pi = mLanguageDefinition.mPreprocIdentifiers.find(id); + if (pi != mLanguageDefinition.mPreprocIdentifiers.end()) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(pi->second.mDeclaration.c_str()); + ImGui::EndTooltip(); + } + } + } + } + } + + ImGui::Dummy(ImVec2((longest + 2), mLines.size() * mCharAdvance.y)); + + if (mScrollToCursor) { + EnsureCursorVisible(); + ImGui::SetWindowFocus(); + mScrollToCursor = false; + } +} + +void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) { + mWithinRender = true; + mTextChanged = false; + mCursorPositionChanged = false; + + ImGui::PushStyleColor(ImGuiCol_ChildBg, + ImGui::ColorConvertU32ToFloat4(mPalette[(int)PaletteIndex::Background])); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + if (!mIgnoreImGuiChild) + ImGui::BeginChild(aTitle, aSize, aBorder, + ImGuiWindowFlags_HorizontalScrollbar | + ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoMove); + + if (mHandleKeyboardInputs) { + HandleKeyboardInputs(); + ImGui::PushAllowKeyboardFocus(true); + } + + if (mHandleMouseInputs) + HandleMouseInputs(); + + ColorizeInternal(); + Render(); + + if (mHandleKeyboardInputs) + ImGui::PopAllowKeyboardFocus(); + + if (!mIgnoreImGuiChild) + ImGui::EndChild(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + mWithinRender = false; +} + +void TextEditor::SetText(const std::string& aText) { + mLines.clear(); + mLines.emplace_back(Line()); + for (auto chr : aText) { + if (chr == '\r') { + // ignore the carriage return character + } else if (chr == '\n') + mLines.emplace_back(Line()); + else { + mLines.back().emplace_back(Glyph(chr, PaletteIndex::Default)); + } + } + + mTextChanged = true; + mScrollToTop = true; + + mUndoBuffer.clear(); + mUndoIndex = 0; + + Colorize(); +} + +void TextEditor::SetTextLines(const std::vector& aLines) { + mLines.clear(); + + if (aLines.empty()) { + mLines.emplace_back(Line()); + } else { + mLines.resize(aLines.size()); + + for (size_t i = 0; i < aLines.size(); ++i) { + const std::string& aLine = aLines[i]; + + mLines[i].reserve(aLine.size()); + for (size_t j = 0; j < aLine.size(); ++j) + mLines[i].emplace_back(Glyph(aLine[j], PaletteIndex::Default)); + } + } + + mTextChanged = true; + mScrollToTop = true; + + mUndoBuffer.clear(); + mUndoIndex = 0; + + Colorize(); +} + +void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) { + ASSERT(!mReadOnly); + + UndoRecord u; + + u.mBefore = mState; + + if (HasSelection()) { + if (aChar == '\t' && mState.mSelectionStart.mLine != mState.mSelectionEnd.mLine) { + + auto start = mState.mSelectionStart; + auto end = mState.mSelectionEnd; + auto originalEnd = end; + + if (start > end) + std::swap(start, end); + start.mColumn = 0; + // end.mColumn = end.mLine < mLines.size() ? mLines[end.mLine].size() : 0; + if (end.mColumn == 0 && end.mLine > 0) + --end.mLine; + if (end.mLine >= (int)mLines.size()) + end.mLine = mLines.empty() ? 0 : (int)mLines.size() - 1; + end.mColumn = GetLineMaxColumn(end.mLine); + + // if (end.mColumn >= GetLineMaxColumn(end.mLine)) + // end.mColumn = GetLineMaxColumn(end.mLine) - 1; + + u.mRemovedStart = start; + u.mRemovedEnd = end; + u.mRemoved = GetText(start, end); + + bool modified = false; + + for (int i = start.mLine; i <= end.mLine; i++) { + auto& line = mLines[i]; + if (aShift) { + if (!line.empty()) { + if (line.front().mChar == '\t') { + line.erase(line.begin()); + modified = true; + } else { + for (int j = 0; + j < mTabSize && !line.empty() && line.front().mChar == ' '; j++) { + line.erase(line.begin()); + modified = true; + } + } + } + } else { + line.insert(line.begin(), Glyph('\t', TextEditor::PaletteIndex::Background)); + modified = true; + } + } + + if (modified) { + start = Coordinates(start.mLine, GetCharacterColumn(start.mLine, 0)); + Coordinates rangeEnd; + if (originalEnd.mColumn != 0) { + end = Coordinates(end.mLine, GetLineMaxColumn(end.mLine)); + rangeEnd = end; + u.mAdded = GetText(start, end); + } else { + end = Coordinates(originalEnd.mLine, 0); + rangeEnd = Coordinates(end.mLine - 1, GetLineMaxColumn(end.mLine - 1)); + u.mAdded = GetText(start, rangeEnd); + } + + u.mAddedStart = start; + u.mAddedEnd = rangeEnd; + u.mAfter = mState; + + mState.mSelectionStart = start; + mState.mSelectionEnd = end; + AddUndo(u); + + mTextChanged = true; + + EnsureCursorVisible(); + } + + return; + } // c == '\t' + else { + u.mRemoved = GetSelectedText(); + u.mRemovedStart = mState.mSelectionStart; + u.mRemovedEnd = mState.mSelectionEnd; + DeleteSelection(); + } + } // HasSelection + + auto coord = GetActualCursorCoordinates(); + u.mAddedStart = coord; + + ASSERT(!mLines.empty()); + + if (aChar == '\n') { + InsertLine(coord.mLine + 1); + auto& line = mLines[coord.mLine]; + auto& newLine = mLines[coord.mLine + 1]; + + if (mLanguageDefinition.mAutoIndentation) + for (size_t it = 0; + it < line.size() && isascii(line[it].mChar) && isblank(line[it].mChar); ++it) + newLine.push_back(line[it]); + + const size_t whitespaceSize = newLine.size(); + auto cindex = GetCharacterIndex(coord); + newLine.insert(newLine.end(), line.begin() + cindex, line.end()); + line.erase(line.begin() + cindex, line.begin() + line.size()); + SetCursorPosition( + Coordinates(coord.mLine + 1, GetCharacterColumn(coord.mLine + 1, (int)whitespaceSize))); + u.mAdded = (char)aChar; + } else { + char buf[7]; + int e = ImTextCharToUtf8(buf, 7, aChar); + if (e > 0) { + buf[e] = '\0'; + auto& line = mLines[coord.mLine]; + auto cindex = GetCharacterIndex(coord); + + if (mOverwrite && cindex < (int)line.size()) { + auto d = UTF8CharLength(line[cindex].mChar); + + u.mRemovedStart = mState.mCursorPosition; + u.mRemovedEnd = + Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex + d)); + + while (d-- > 0 && cindex < (int)line.size()) { + u.mRemoved += line[cindex].mChar; + line.erase(line.begin() + cindex); + } + } + + for (auto p = buf; *p != '\0'; p++, ++cindex) + line.insert(line.begin() + cindex, Glyph(*p, PaletteIndex::Default)); + u.mAdded = buf; + + SetCursorPosition(Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex))); + } else + return; + } + + mTextChanged = true; + + u.mAddedEnd = GetActualCursorCoordinates(); + u.mAfter = mState; + + AddUndo(u); + + Colorize(coord.mLine - 1, 3); + EnsureCursorVisible(); +} + +void TextEditor::SetReadOnly(bool aValue) { + mReadOnly = aValue; +} + +void TextEditor::SetColorizerEnable(bool aValue) { + mColorizerEnabled = aValue; +} + +void TextEditor::SetCursorPosition(const Coordinates& aPosition) { + if (mState.mCursorPosition != aPosition) { + mState.mCursorPosition = aPosition; + mCursorPositionChanged = true; + EnsureCursorVisible(); + } +} + +void TextEditor::SetSelectionStart(const Coordinates& aPosition) { + mState.mSelectionStart = SanitizeCoordinates(aPosition); + if (mState.mSelectionStart > mState.mSelectionEnd) + std::swap(mState.mSelectionStart, mState.mSelectionEnd); +} + +void TextEditor::SetSelectionEnd(const Coordinates& aPosition) { + mState.mSelectionEnd = SanitizeCoordinates(aPosition); + if (mState.mSelectionStart > mState.mSelectionEnd) + std::swap(mState.mSelectionStart, mState.mSelectionEnd); +} + +void TextEditor::SetSelection(const Coordinates& aStart, const Coordinates& aEnd, + SelectionMode aMode) { + auto oldSelStart = mState.mSelectionStart; + auto oldSelEnd = mState.mSelectionEnd; + + mState.mSelectionStart = SanitizeCoordinates(aStart); + mState.mSelectionEnd = SanitizeCoordinates(aEnd); + if (mState.mSelectionStart > mState.mSelectionEnd) + std::swap(mState.mSelectionStart, mState.mSelectionEnd); + + switch (aMode) { + case TextEditor::SelectionMode::Normal: + break; + case TextEditor::SelectionMode::Word: { + mState.mSelectionStart = FindWordStart(mState.mSelectionStart); + if (!IsOnWordBoundary(mState.mSelectionEnd)) + mState.mSelectionEnd = FindWordEnd(FindWordStart(mState.mSelectionEnd)); + break; + } + case TextEditor::SelectionMode::Line: { + const auto lineNo = mState.mSelectionEnd.mLine; + const auto lineSize = (size_t)lineNo < mLines.size() ? mLines[lineNo].size() : 0; + mState.mSelectionStart = Coordinates(mState.mSelectionStart.mLine, 0); + mState.mSelectionEnd = Coordinates(lineNo, GetLineMaxColumn(lineNo)); + break; + } + default: + break; + } + + if (mState.mSelectionStart != oldSelStart || mState.mSelectionEnd != oldSelEnd) + mCursorPositionChanged = true; +} + +void TextEditor::SetTabSize(int aValue) { + mTabSize = std::max(0, std::min(32, aValue)); +} + +void TextEditor::InsertText(const std::string& aValue) { + InsertText(aValue.c_str()); +} + +void TextEditor::InsertText(const char* aValue) { + if (aValue == nullptr) + return; + + auto pos = GetActualCursorCoordinates(); + auto start = std::min(pos, mState.mSelectionStart); + int totalLines = pos.mLine - start.mLine; + + totalLines += InsertTextAt(pos, aValue); + + SetSelection(pos, pos); + SetCursorPosition(pos); + Colorize(start.mLine - 1, totalLines + 2); +} + +void TextEditor::DeleteSelection() { + ASSERT(mState.mSelectionEnd >= mState.mSelectionStart); + + if (mState.mSelectionEnd == mState.mSelectionStart) + return; + + DeleteRange(mState.mSelectionStart, mState.mSelectionEnd); + + SetSelection(mState.mSelectionStart, mState.mSelectionStart); + SetCursorPosition(mState.mSelectionStart); + Colorize(mState.mSelectionStart.mLine, 1); +} + +void TextEditor::MoveUp(int aAmount, bool aSelect) { + auto oldPos = mState.mCursorPosition; + mState.mCursorPosition.mLine = std::max(0, mState.mCursorPosition.mLine - aAmount); + if (oldPos != mState.mCursorPosition) { + if (aSelect) { + if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else if (oldPos == mInteractiveEnd) + mInteractiveEnd = mState.mCursorPosition; + else { + mInteractiveStart = mState.mCursorPosition; + mInteractiveEnd = oldPos; + } + } else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd); + + EnsureCursorVisible(); + } +} + +void TextEditor::MoveDown(int aAmount, bool aSelect) { + ASSERT(mState.mCursorPosition.mColumn >= 0); + auto oldPos = mState.mCursorPosition; + mState.mCursorPosition.mLine = + std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + aAmount)); + + if (mState.mCursorPosition != oldPos) { + if (aSelect) { + if (oldPos == mInteractiveEnd) + mInteractiveEnd = mState.mCursorPosition; + else if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else { + mInteractiveStart = oldPos; + mInteractiveEnd = mState.mCursorPosition; + } + } else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd); + + EnsureCursorVisible(); + } +} + +static bool IsUTFSequence(char c) { + return (c & 0xC0) == 0x80; +} + +void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode) { + if (mLines.empty()) + return; + + auto oldPos = mState.mCursorPosition; + mState.mCursorPosition = GetActualCursorCoordinates(); + auto line = mState.mCursorPosition.mLine; + auto cindex = GetCharacterIndex(mState.mCursorPosition); + + while (aAmount-- > 0) { + if (cindex == 0) { + if (line > 0) { + --line; + if ((int)mLines.size() > line) + cindex = (int)mLines[line].size(); + else + cindex = 0; + } + } else { + --cindex; + if (cindex > 0) { + if ((int)mLines.size() > line) { + while (cindex > 0 && IsUTFSequence(mLines[line][cindex].mChar)) + --cindex; + } + } + } + + mState.mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex)); + if (aWordMode) { + mState.mCursorPosition = FindWordStart(mState.mCursorPosition); + cindex = GetCharacterIndex(mState.mCursorPosition); + } + } + + mState.mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex)); + + ASSERT(mState.mCursorPosition.mColumn >= 0); + if (aSelect) { + if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else if (oldPos == mInteractiveEnd) + mInteractiveEnd = mState.mCursorPosition; + else { + mInteractiveStart = mState.mCursorPosition; + mInteractiveEnd = oldPos; + } + } else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd, + aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal); + + EnsureCursorVisible(); +} + +void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode) { + auto oldPos = mState.mCursorPosition; + + if (mLines.empty() || oldPos.mLine >= mLines.size()) + return; + + auto cindex = GetCharacterIndex(mState.mCursorPosition); + while (aAmount-- > 0) { + auto lindex = mState.mCursorPosition.mLine; + auto& line = mLines[lindex]; + + if (cindex >= line.size()) { + if (mState.mCursorPosition.mLine < mLines.size() - 1) { + mState.mCursorPosition.mLine = + std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + 1)); + mState.mCursorPosition.mColumn = 0; + } else + return; + } else { + cindex += UTF8CharLength(line[cindex].mChar); + mState.mCursorPosition = Coordinates(lindex, GetCharacterColumn(lindex, cindex)); + if (aWordMode) + mState.mCursorPosition = FindNextWord(mState.mCursorPosition); + } + } + + if (aSelect) { + if (oldPos == mInteractiveEnd) + mInteractiveEnd = SanitizeCoordinates(mState.mCursorPosition); + else if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else { + mInteractiveStart = oldPos; + mInteractiveEnd = mState.mCursorPosition; + } + } else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd, + aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal); + + EnsureCursorVisible(); +} + +void TextEditor::MoveTop(bool aSelect) { + auto oldPos = mState.mCursorPosition; + SetCursorPosition(Coordinates(0, 0)); + + if (mState.mCursorPosition != oldPos) { + if (aSelect) { + mInteractiveEnd = oldPos; + mInteractiveStart = mState.mCursorPosition; + } else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd); + } +} + +void TextEditor::TextEditor::MoveBottom(bool aSelect) { + auto oldPos = GetCursorPosition(); + auto newPos = Coordinates((int)mLines.size() - 1, 0); + SetCursorPosition(newPos); + if (aSelect) { + mInteractiveStart = oldPos; + mInteractiveEnd = newPos; + } else + mInteractiveStart = mInteractiveEnd = newPos; + SetSelection(mInteractiveStart, mInteractiveEnd); +} + +void TextEditor::MoveHome(bool aSelect) { + auto oldPos = mState.mCursorPosition; + SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, 0)); + + if (mState.mCursorPosition != oldPos) { + if (aSelect) { + if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else if (oldPos == mInteractiveEnd) + mInteractiveEnd = mState.mCursorPosition; + else { + mInteractiveStart = mState.mCursorPosition; + mInteractiveEnd = oldPos; + } + } else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd); + } +} + +void TextEditor::MoveEnd(bool aSelect) { + auto oldPos = mState.mCursorPosition; + SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, GetLineMaxColumn(oldPos.mLine))); + + if (mState.mCursorPosition != oldPos) { + if (aSelect) { + if (oldPos == mInteractiveEnd) + mInteractiveEnd = mState.mCursorPosition; + else if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else { + mInteractiveStart = oldPos; + mInteractiveEnd = mState.mCursorPosition; + } + } else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd); + } +} + +void TextEditor::Delete() { + ASSERT(!mReadOnly); + + if (mLines.empty()) + return; + + UndoRecord u; + u.mBefore = mState; + + if (HasSelection()) { + u.mRemoved = GetSelectedText(); + u.mRemovedStart = mState.mSelectionStart; + u.mRemovedEnd = mState.mSelectionEnd; + + DeleteSelection(); + } else { + auto pos = GetActualCursorCoordinates(); + SetCursorPosition(pos); + auto& line = mLines[pos.mLine]; + + if (pos.mColumn == GetLineMaxColumn(pos.mLine)) { + if (pos.mLine == (int)mLines.size() - 1) + return; + + u.mRemoved = '\n'; + u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); + Advance(u.mRemovedEnd); + + auto& nextLine = mLines[pos.mLine + 1]; + line.insert(line.end(), nextLine.begin(), nextLine.end()); + RemoveLine(pos.mLine + 1); + } else { + auto cindex = GetCharacterIndex(pos); + u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); + u.mRemovedEnd.mColumn++; + u.mRemoved = GetText(u.mRemovedStart, u.mRemovedEnd); + + auto d = UTF8CharLength(line[cindex].mChar); + while (d-- > 0 && cindex < (int)line.size()) + line.erase(line.begin() + cindex); + } + + mTextChanged = true; + + Colorize(pos.mLine, 1); + } + + u.mAfter = mState; + AddUndo(u); +} + +void TextEditor::Backspace() { + ASSERT(!mReadOnly); + + if (mLines.empty()) + return; + + UndoRecord u; + u.mBefore = mState; + + if (HasSelection()) { + u.mRemoved = GetSelectedText(); + u.mRemovedStart = mState.mSelectionStart; + u.mRemovedEnd = mState.mSelectionEnd; + + DeleteSelection(); + } else { + auto pos = GetActualCursorCoordinates(); + SetCursorPosition(pos); + + if (mState.mCursorPosition.mColumn == 0) { + if (mState.mCursorPosition.mLine == 0) + return; + + u.mRemoved = '\n'; + u.mRemovedStart = u.mRemovedEnd = + Coordinates(pos.mLine - 1, GetLineMaxColumn(pos.mLine - 1)); + Advance(u.mRemovedEnd); + + auto& line = mLines[mState.mCursorPosition.mLine]; + auto& prevLine = mLines[mState.mCursorPosition.mLine - 1]; + auto prevSize = GetLineMaxColumn(mState.mCursorPosition.mLine - 1); + prevLine.insert(prevLine.end(), line.begin(), line.end()); + + ErrorMarkers etmp; + for (auto& i : mErrorMarkers) + etmp.insert(ErrorMarkers::value_type( + i.first - 1 == mState.mCursorPosition.mLine ? i.first - 1 : i.first, i.second)); + mErrorMarkers = std::move(etmp); + + RemoveLine(mState.mCursorPosition.mLine); + --mState.mCursorPosition.mLine; + mState.mCursorPosition.mColumn = prevSize; + } else { + auto& line = mLines[mState.mCursorPosition.mLine]; + auto cindex = GetCharacterIndex(pos) - 1; + auto cend = cindex + 1; + while (cindex > 0 && IsUTFSequence(line[cindex].mChar)) + --cindex; + + // if (cindex > 0 && UTF8CharLength(line[cindex].mChar) > 1) + // --cindex; + + u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); + --u.mRemovedStart.mColumn; + --mState.mCursorPosition.mColumn; + + while (cindex < line.size() && cend-- > cindex) { + u.mRemoved += line[cindex].mChar; + line.erase(line.begin() + cindex); + } + } + + mTextChanged = true; + + EnsureCursorVisible(); + Colorize(mState.mCursorPosition.mLine, 1); + } + + u.mAfter = mState; + AddUndo(u); +} + +void TextEditor::SelectWordUnderCursor() { + auto c = GetCursorPosition(); + SetSelection(FindWordStart(c), FindWordEnd(c)); +} + +void TextEditor::SelectAll() { + SetSelection(Coordinates(0, 0), Coordinates((int)mLines.size(), 0)); +} + +bool TextEditor::HasSelection() const { + return mState.mSelectionEnd > mState.mSelectionStart; +} + +void TextEditor::Copy() { + if (HasSelection()) { + ImGui::SetClipboardText(GetSelectedText().c_str()); + } else { + if (!mLines.empty()) { + std::string str; + auto& line = mLines[GetActualCursorCoordinates().mLine]; + for (auto& g : line) + str.push_back(g.mChar); + ImGui::SetClipboardText(str.c_str()); + } + } +} + +void TextEditor::Cut() { + if (IsReadOnly()) { + Copy(); + } else { + if (HasSelection()) { + UndoRecord u; + u.mBefore = mState; + u.mRemoved = GetSelectedText(); + u.mRemovedStart = mState.mSelectionStart; + u.mRemovedEnd = mState.mSelectionEnd; + + Copy(); + DeleteSelection(); + + u.mAfter = mState; + AddUndo(u); + } + } +} + +void TextEditor::Paste() { + if (IsReadOnly()) + return; + + auto clipText = ImGui::GetClipboardText(); + if (clipText != nullptr && strlen(clipText) > 0) { + UndoRecord u; + u.mBefore = mState; + + if (HasSelection()) { + u.mRemoved = GetSelectedText(); + u.mRemovedStart = mState.mSelectionStart; + u.mRemovedEnd = mState.mSelectionEnd; + DeleteSelection(); + } + + u.mAdded = clipText; + u.mAddedStart = GetActualCursorCoordinates(); + + InsertText(clipText); + + u.mAddedEnd = GetActualCursorCoordinates(); + u.mAfter = mState; + AddUndo(u); + } +} + +bool TextEditor::CanUndo() const { + return !mReadOnly && mUndoIndex > 0; +} + +bool TextEditor::CanRedo() const { + return !mReadOnly && mUndoIndex < (int)mUndoBuffer.size(); +} + +void TextEditor::Undo(int aSteps) { + while (CanUndo() && aSteps-- > 0) + mUndoBuffer[--mUndoIndex].Undo(this); +} + +void TextEditor::Redo(int aSteps) { + while (CanRedo() && aSteps-- > 0) + mUndoBuffer[mUndoIndex++].Redo(this); +} + +const TextEditor::Palette& TextEditor::GetDarkPalette() { + const static Palette p = {{ + 0xff7f7f7f, // Default + 0xffd69c56, // Keyword + 0xff00ff00, // Number + 0xff7070e0, // String + 0xff70a0e0, // Char literal + 0xffffffff, // Punctuation + 0xff408080, // Preprocessor + 0xffaaaaaa, // Identifier + 0xff9bc64d, // Known identifier + 0xffc040a0, // Preproc identifier + 0xff206020, // Comment (single line) + 0xff406020, // Comment (multi line) + 0xff101010, // Background + 0xffe0e0e0, // Cursor + 0x80a06020, // Selection + 0x800020ff, // ErrorMarker + 0x40f08000, // Breakpoint + 0xff707000, // Line number + 0x40000000, // Current line fill + 0x40808080, // Current line fill (inactive) + 0x40a0a0a0, // Current line edge + }}; + return p; +} + +const TextEditor::Palette& TextEditor::GetLightPalette() { + const static Palette p = {{ + 0xff7f7f7f, // None + 0xffff0c06, // Keyword + 0xff008000, // Number + 0xff2020a0, // String + 0xff304070, // Char literal + 0xff000000, // Punctuation + 0xff406060, // Preprocessor + 0xff404040, // Identifier + 0xff606010, // Known identifier + 0xffc040a0, // Preproc identifier + 0xff205020, // Comment (single line) + 0xff405020, // Comment (multi line) + 0xffffffff, // Background + 0xff000000, // Cursor + 0x80600000, // Selection + 0xa00010ff, // ErrorMarker + 0x80f08000, // Breakpoint + 0xff505000, // Line number + 0x40000000, // Current line fill + 0x40808080, // Current line fill (inactive) + 0x40000000, // Current line edge + }}; + return p; +} + +const TextEditor::Palette& TextEditor::GetRetroBluePalette() { + const static Palette p = {{ + 0xff00ffff, // None + 0xffffff00, // Keyword + 0xff00ff00, // Number + 0xff808000, // String + 0xff808000, // Char literal + 0xffffffff, // Punctuation + 0xff008000, // Preprocessor + 0xff00ffff, // Identifier + 0xffffffff, // Known identifier + 0xffff00ff, // Preproc identifier + 0xff808080, // Comment (single line) + 0xff404040, // Comment (multi line) + 0xff800000, // Background + 0xff0080ff, // Cursor + 0x80ffff00, // Selection + 0xa00000ff, // ErrorMarker + 0x80ff8000, // Breakpoint + 0xff808000, // Line number + 0x40000000, // Current line fill + 0x40808080, // Current line fill (inactive) + 0x40000000, // Current line edge + }}; + return p; +} + +std::string TextEditor::GetText() const { + return GetText(Coordinates(), Coordinates((int)mLines.size(), 0)); +} + +std::vector TextEditor::GetTextLines() const { + std::vector result; + + result.reserve(mLines.size()); + + for (auto& line : mLines) { + std::string text; + + text.resize(line.size()); + + for (size_t i = 0; i < line.size(); ++i) + text[i] = line[i].mChar; + + result.emplace_back(std::move(text)); + } + + return result; +} + +std::string TextEditor::GetSelectedText() const { + return GetText(mState.mSelectionStart, mState.mSelectionEnd); +} + +std::string TextEditor::GetCurrentLineText() const { + auto lineLength = GetLineMaxColumn(mState.mCursorPosition.mLine); + return GetText(Coordinates(mState.mCursorPosition.mLine, 0), + Coordinates(mState.mCursorPosition.mLine, lineLength)); +} + +void TextEditor::ProcessInputs() {} + +void TextEditor::Colorize(int aFromLine, int aLines) { + int toLine = + aLines == -1 ? (int)mLines.size() : std::min((int)mLines.size(), aFromLine + aLines); + mColorRangeMin = std::min(mColorRangeMin, aFromLine); + mColorRangeMax = std::max(mColorRangeMax, toLine); + mColorRangeMin = std::max(0, mColorRangeMin); + mColorRangeMax = std::max(mColorRangeMin, mColorRangeMax); + mCheckComments = true; +} + +void TextEditor::ColorizeRange(int aFromLine, int aToLine) { + if (mLines.empty() || aFromLine >= aToLine) + return; + + std::string buffer; + std::cmatch results; + std::string id; + + int endLine = std::max(0, std::min((int)mLines.size(), aToLine)); + for (int i = aFromLine; i < endLine; ++i) { + auto& line = mLines[i]; + + if (line.empty()) + continue; + + buffer.resize(line.size()); + for (size_t j = 0; j < line.size(); ++j) { + auto& col = line[j]; + buffer[j] = col.mChar; + col.mColorIndex = PaletteIndex::Default; + } + + const char* bufferBegin = &buffer.front(); + const char* bufferEnd = bufferBegin + buffer.size(); + + auto last = bufferEnd; + + for (auto first = bufferBegin; first != last;) { + const char* token_begin = nullptr; + const char* token_end = nullptr; + PaletteIndex token_color = PaletteIndex::Default; + + bool hasTokenizeResult = false; + + if (mLanguageDefinition.mTokenize != nullptr) { + if (mLanguageDefinition.mTokenize(first, last, token_begin, token_end, token_color)) + hasTokenizeResult = true; + } + + if (hasTokenizeResult == false) { + // todo : remove + // printf("using regex for %.*s\n", first + 10 < last ? 10 : int(last - first), + // first); + + for (auto& p : mRegexList) { + if (std::regex_search(first, last, results, p.first, + std::regex_constants::match_continuous)) { + hasTokenizeResult = true; + + auto& v = *results.begin(); + token_begin = v.first; + token_end = v.second; + token_color = p.second; + break; + } + } + } + + if (hasTokenizeResult == false) { + first++; + } else { + const size_t token_length = token_end - token_begin; + + if (token_color == PaletteIndex::Identifier) { + id.assign(token_begin, token_end); + + // todo : allmost all language definitions use lower case to specify keywords, + // so shouldn't this use ::tolower ? + if (!mLanguageDefinition.mCaseSensitive) + std::transform(id.begin(), id.end(), id.begin(), ::toupper); + + if (!line[first - bufferBegin].mPreprocessor) { + if (mLanguageDefinition.mKeywords.count(id) != 0) + token_color = PaletteIndex::Keyword; + else if (mLanguageDefinition.mIdentifiers.count(id) != 0) + token_color = PaletteIndex::KnownIdentifier; + else if (mLanguageDefinition.mPreprocIdentifiers.count(id) != 0) + token_color = PaletteIndex::PreprocIdentifier; + } else { + if (mLanguageDefinition.mPreprocIdentifiers.count(id) != 0) + token_color = PaletteIndex::PreprocIdentifier; + } + } + + for (size_t j = 0; j < token_length; ++j) + line[(token_begin - bufferBegin) + j].mColorIndex = token_color; + + first = token_end; + } + } + } +} + +void TextEditor::ColorizeInternal() { + if (mLines.empty() || !mColorizerEnabled) + return; + + if (mCheckComments) { + auto endLine = mLines.size(); + auto endIndex = 0; + auto commentStartLine = endLine; + auto commentStartIndex = endIndex; + auto withinString = false; + auto withinSingleLineComment = false; + auto withinPreproc = false; + auto firstChar = true; // there is no other non-whitespace characters in the line before + auto concatenate = false; // '\' on the very end of the line + auto currentLine = 0; + auto currentIndex = 0; + while (currentLine < endLine || currentIndex < endIndex) { + auto& line = mLines[currentLine]; + + if (currentIndex == 0 && !concatenate) { + withinSingleLineComment = false; + withinPreproc = false; + firstChar = true; + } + + concatenate = false; + + if (!line.empty()) { + auto& g = line[currentIndex]; + auto c = g.mChar; + + if (c != mLanguageDefinition.mPreprocChar && !isspace(c)) + firstChar = false; + + if (currentIndex == (int)line.size() - 1 && line[line.size() - 1].mChar == '\\') + concatenate = true; + + bool inComment = + (commentStartLine < currentLine || + (commentStartLine == currentLine && commentStartIndex <= currentIndex)); + + if (withinString) { + line[currentIndex].mMultiLineComment = inComment; + + if (c == '\"') { + if (currentIndex + 1 < (int)line.size() && + line[currentIndex + 1].mChar == '\"') { + currentIndex += 1; + if (currentIndex < (int)line.size()) + line[currentIndex].mMultiLineComment = inComment; + } else + withinString = false; + } else if (c == '\\') { + currentIndex += 1; + if (currentIndex < (int)line.size()) + line[currentIndex].mMultiLineComment = inComment; + } + } else { + if (firstChar && c == mLanguageDefinition.mPreprocChar) + withinPreproc = true; + + if (c == '\"') { + withinString = true; + line[currentIndex].mMultiLineComment = inComment; + } else { + auto pred = [](const char& a, const Glyph& b) { return a == b.mChar; }; + auto from = line.begin() + currentIndex; + auto& startStr = mLanguageDefinition.mCommentStart; + auto& singleStartStr = mLanguageDefinition.mSingleLineComment; + + if (singleStartStr.size() > 0 && + currentIndex + singleStartStr.size() <= line.size() && + equals(singleStartStr.begin(), singleStartStr.end(), from, + from + singleStartStr.size(), pred)) { + withinSingleLineComment = true; + } else if (!withinSingleLineComment && + currentIndex + startStr.size() <= line.size() && + equals(startStr.begin(), startStr.end(), from, + from + startStr.size(), pred)) { + commentStartLine = currentLine; + commentStartIndex = currentIndex; + } + + inComment = inComment = + (commentStartLine < currentLine || (commentStartLine == currentLine && + commentStartIndex <= currentIndex)); + + line[currentIndex].mMultiLineComment = inComment; + line[currentIndex].mComment = withinSingleLineComment; + + auto& endStr = mLanguageDefinition.mCommentEnd; + if (currentIndex + 1 >= (int)endStr.size() && + equals(endStr.begin(), endStr.end(), from + 1 - endStr.size(), from + 1, + pred)) { + commentStartIndex = endIndex; + commentStartLine = endLine; + } + } + } + line[currentIndex].mPreprocessor = withinPreproc; + currentIndex += UTF8CharLength(c); + if (currentIndex >= (int)line.size()) { + currentIndex = 0; + ++currentLine; + } + } else { + currentIndex = 0; + ++currentLine; + } + } + mCheckComments = false; + } + + if (mColorRangeMin < mColorRangeMax) { + const int increment = (mLanguageDefinition.mTokenize == nullptr) ? 10 : 10000; + const int to = std::min(mColorRangeMin + increment, mColorRangeMax); + ColorizeRange(mColorRangeMin, to); + mColorRangeMin = to; + + if (mColorRangeMax == mColorRangeMin) { + mColorRangeMin = std::numeric_limits::max(); + mColorRangeMax = 0; + } + return; + } +} + +float TextEditor::TextDistanceToLineStart(const Coordinates& aFrom) const { + auto& line = mLines[aFrom.mLine]; + float distance = 0.0f; + float spaceSize = + ImGui::GetFont() + ->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr) + .x; + int colIndex = GetCharacterIndex(aFrom); + for (size_t it = 0u; it < line.size() && it < colIndex;) { + if (line[it].mChar == '\t') { + distance = (1.0f + std::floor((1.0f + distance) / (float(mTabSize) * spaceSize))) * + (float(mTabSize) * spaceSize); + ++it; + } else { + auto d = UTF8CharLength(line[it].mChar); + char tempCString[7]; + int i = 0; + for (; i < 6 && d-- > 0 && it < (int)line.size(); i++, it++) + tempCString[i] = line[it].mChar; + + tempCString[i] = '\0'; + distance += ImGui::GetFont() + ->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, tempCString, + nullptr, nullptr) + .x; + } + } + + return distance; +} + +void TextEditor::EnsureCursorVisible() { + if (!mWithinRender) { + mScrollToCursor = true; + return; + } + + float scrollX = ImGui::GetScrollX(); + float scrollY = ImGui::GetScrollY(); + + auto height = ImGui::GetWindowHeight(); + auto width = ImGui::GetWindowWidth(); + + auto top = 1 + (int)ceil(scrollY / mCharAdvance.y); + auto bottom = (int)ceil((scrollY + height) / mCharAdvance.y); + + auto left = (int)ceil(scrollX / mCharAdvance.x); + auto right = (int)ceil((scrollX + width) / mCharAdvance.x); + + auto pos = GetActualCursorCoordinates(); + auto len = TextDistanceToLineStart(pos); + + if (pos.mLine < top) + ImGui::SetScrollY(std::max(0.0f, (pos.mLine - 1) * mCharAdvance.y)); + if (pos.mLine > bottom - 4) + ImGui::SetScrollY(std::max(0.0f, (pos.mLine + 4) * mCharAdvance.y - height)); + if (len + mTextStart < left + 4) + ImGui::SetScrollX(std::max(0.0f, len + mTextStart - 4)); + if (len + mTextStart > right - 4) + ImGui::SetScrollX(std::max(0.0f, len + mTextStart + 4 - width)); +} + +int TextEditor::GetPageSize() const { + auto height = ImGui::GetWindowHeight() - 20.0f; + return (int)floor(height / mCharAdvance.y); +} + +TextEditor::UndoRecord::UndoRecord( + const std::string& aAdded, const TextEditor::Coordinates aAddedStart, + const TextEditor::Coordinates aAddedEnd, const std::string& aRemoved, + const TextEditor::Coordinates aRemovedStart, const TextEditor::Coordinates aRemovedEnd, + TextEditor::EditorState& aBefore, TextEditor::EditorState& aAfter) + : mAdded(aAdded), mAddedStart(aAddedStart), mAddedEnd(aAddedEnd), mRemoved(aRemoved), + mRemovedStart(aRemovedStart), mRemovedEnd(aRemovedEnd), mBefore(aBefore), mAfter(aAfter) { + ASSERT(mAddedStart <= mAddedEnd); + ASSERT(mRemovedStart <= mRemovedEnd); +} + +void TextEditor::UndoRecord::Undo(TextEditor* aEditor) { + if (!mAdded.empty()) { + aEditor->DeleteRange(mAddedStart, mAddedEnd); + aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 2); + } + + if (!mRemoved.empty()) { + auto start = mRemovedStart; + aEditor->InsertTextAt(start, mRemoved.c_str()); + aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 2); + } + + aEditor->mState = mBefore; + aEditor->EnsureCursorVisible(); +} + +void TextEditor::UndoRecord::Redo(TextEditor* aEditor) { + if (!mRemoved.empty()) { + aEditor->DeleteRange(mRemovedStart, mRemovedEnd); + aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 1); + } + + if (!mAdded.empty()) { + auto start = mAddedStart; + aEditor->InsertTextAt(start, mAdded.c_str()); + aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 1); + } + + aEditor->mState = mAfter; + aEditor->EnsureCursorVisible(); +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::GLSL() { + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) { + static const char* const keywords[] = { + "auto", "break", "case", "char", "const", "continue", + "default", "do", "double", "else", "enum", "extern", + "float", "for", "goto", "if", "inline", "int", + "long", "register", "restrict", "return", "short", "signed", + "sizeof", "static", "struct", "switch", "typedef", "union", + "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", + "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", "_Noreturn", + "_Static_assert", "_Thread_local"}; + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abort", "abs", "acos", "asin", "atan", "atexit", "atof", + "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", + "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", + "isalpha", "isdigit", "isgraph", "ispunct", "isspace", "isupper", "kbhit", + "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", + "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", + "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper"}; + for (auto& k : identifiers) { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenRegexStrings.push_back(std::make_pair( + "[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", + PaletteIndex::Punctuation)); + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; + + langDef.mName = "GLSL"; + + inited = true; + } + return langDef; +} + +} // namespace Core::Devtools::Widget diff --git a/src/core/devtools/widget/text_editor.h b/src/core/devtools/widget/text_editor.h new file mode 100644 index 000000000..5c3f29f11 --- /dev/null +++ b/src/core/devtools/widget/text_editor.h @@ -0,0 +1,408 @@ +// SPDX-FileCopyrightText: Copyright (c) 2017 BalazsJako +// SPDX-License-Identifier: MIT + +// source: https://github.com/BalazsJako/ImGuiColorTextEdit + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/assert.h" +#include "imgui.h" + +namespace Core::Devtools::Widget { + +class TextEditor { +public: + enum class PaletteIndex { + Default, + Keyword, + Number, + String, + CharLiteral, + Punctuation, + Preprocessor, + Identifier, + KnownIdentifier, + PreprocIdentifier, + Comment, + MultiLineComment, + Background, + Cursor, + Selection, + ErrorMarker, + Breakpoint, + LineNumber, + CurrentLineFill, + CurrentLineFillInactive, + CurrentLineEdge, + Max + }; + + enum class SelectionMode { Normal, Word, Line }; + + struct Breakpoint { + int mLine; + bool mEnabled; + std::string mCondition; + + Breakpoint() : mLine(-1), mEnabled(false) {} + }; + + // Represents a character coordinate from the user's point of view, + // i. e. consider an uniform grid (assuming fixed-width font) on the + // screen as it is rendered, and each cell has its own coordinate, starting from 0. + // Tabs are counted as [1..mTabSize] count empty spaces, depending on + // how many space is necessary to reach the next tab stop. + // For example, coordinate (1, 5) represents the character 'B' in a line "\tABC", when mTabSize + // = 4, because it is rendered as " ABC" on the screen. + struct Coordinates { + int mLine, mColumn; + Coordinates() : mLine(0), mColumn(0) {} + Coordinates(int aLine, int aColumn) : mLine(aLine), mColumn(aColumn) { + ASSERT(aLine >= 0); + ASSERT(aColumn >= 0); + } + static Coordinates Invalid() { + static Coordinates invalid(-1, -1); + return invalid; + } + + bool operator==(const Coordinates& o) const { + return mLine == o.mLine && mColumn == o.mColumn; + } + + bool operator!=(const Coordinates& o) const { + return mLine != o.mLine || mColumn != o.mColumn; + } + + bool operator<(const Coordinates& o) const { + if (mLine != o.mLine) + return mLine < o.mLine; + return mColumn < o.mColumn; + } + + bool operator>(const Coordinates& o) const { + if (mLine != o.mLine) + return mLine > o.mLine; + return mColumn > o.mColumn; + } + + bool operator<=(const Coordinates& o) const { + if (mLine != o.mLine) + return mLine < o.mLine; + return mColumn <= o.mColumn; + } + + bool operator>=(const Coordinates& o) const { + if (mLine != o.mLine) + return mLine > o.mLine; + return mColumn >= o.mColumn; + } + }; + + struct Identifier { + Coordinates mLocation; + std::string mDeclaration; + }; + + typedef std::string String; + typedef std::unordered_map Identifiers; + typedef std::unordered_set Keywords; + typedef std::map ErrorMarkers; + typedef std::unordered_set Breakpoints; + typedef std::array Palette; + typedef uint8_t Char; + + struct Glyph { + Char mChar; + PaletteIndex mColorIndex = PaletteIndex::Default; + bool mComment : 1; + bool mMultiLineComment : 1; + bool mPreprocessor : 1; + + Glyph(Char aChar, PaletteIndex aColorIndex) + : mChar(aChar), mColorIndex(aColorIndex), mComment(false), mMultiLineComment(false), + mPreprocessor(false) {} + }; + + typedef std::vector Line; + typedef std::vector Lines; + + struct LanguageDefinition { + typedef std::pair TokenRegexString; + typedef std::vector TokenRegexStrings; + typedef bool (*TokenizeCallback)(const char* in_begin, const char* in_end, + const char*& out_begin, const char*& out_end, + PaletteIndex& paletteIndex); + + std::string mName; + Keywords mKeywords; + Identifiers mIdentifiers; + Identifiers mPreprocIdentifiers; + std::string mCommentStart, mCommentEnd, mSingleLineComment; + char mPreprocChar; + bool mAutoIndentation; + + TokenizeCallback mTokenize; + + TokenRegexStrings mTokenRegexStrings; + + bool mCaseSensitive; + + LanguageDefinition() + : mPreprocChar('#'), mAutoIndentation(true), mTokenize(nullptr), mCaseSensitive(true) {} + + static const LanguageDefinition& GLSL(); + }; + + TextEditor(); + ~TextEditor(); + + void SetLanguageDefinition(const LanguageDefinition& aLanguageDef); + const LanguageDefinition& GetLanguageDefinition() const { + return mLanguageDefinition; + } + + const Palette& GetPalette() const { + return mPaletteBase; + } + void SetPalette(const Palette& aValue); + + void SetErrorMarkers(const ErrorMarkers& aMarkers) { + mErrorMarkers = aMarkers; + } + void SetBreakpoints(const Breakpoints& aMarkers) { + mBreakpoints = aMarkers; + } + + void Render(const char* aTitle, const ImVec2& aSize = ImVec2(), bool aBorder = false); + void SetText(const std::string& aText); + std::string GetText() const; + + void SetTextLines(const std::vector& aLines); + std::vector GetTextLines() const; + + std::string GetSelectedText() const; + std::string GetCurrentLineText() const; + + int GetTotalLines() const { + return (int)mLines.size(); + } + bool IsOverwrite() const { + return mOverwrite; + } + + void SetReadOnly(bool aValue); + bool IsReadOnly() const { + return mReadOnly; + } + bool IsTextChanged() const { + return mTextChanged; + } + bool IsCursorPositionChanged() const { + return mCursorPositionChanged; + } + + bool IsColorizerEnabled() const { + return mColorizerEnabled; + } + void SetColorizerEnable(bool aValue); + + Coordinates GetCursorPosition() const { + return GetActualCursorCoordinates(); + } + void SetCursorPosition(const Coordinates& aPosition); + + inline void SetHandleMouseInputs(bool aValue) { + mHandleMouseInputs = aValue; + } + inline bool IsHandleMouseInputsEnabled() const { + return mHandleKeyboardInputs; + } + + inline void SetHandleKeyboardInputs(bool aValue) { + mHandleKeyboardInputs = aValue; + } + inline bool IsHandleKeyboardInputsEnabled() const { + return mHandleKeyboardInputs; + } + + inline void SetImGuiChildIgnored(bool aValue) { + mIgnoreImGuiChild = aValue; + } + inline bool IsImGuiChildIgnored() const { + return mIgnoreImGuiChild; + } + + inline void SetShowWhitespaces(bool aValue) { + mShowWhitespaces = aValue; + } + inline bool IsShowingWhitespaces() const { + return mShowWhitespaces; + } + + void SetTabSize(int aValue); + inline int GetTabSize() const { + return mTabSize; + } + + void InsertText(const std::string& aValue); + void InsertText(const char* aValue); + + void MoveUp(int aAmount = 1, bool aSelect = false); + void MoveDown(int aAmount = 1, bool aSelect = false); + void MoveLeft(int aAmount = 1, bool aSelect = false, bool aWordMode = false); + void MoveRight(int aAmount = 1, bool aSelect = false, bool aWordMode = false); + void MoveTop(bool aSelect = false); + void MoveBottom(bool aSelect = false); + void MoveHome(bool aSelect = false); + void MoveEnd(bool aSelect = false); + + void SetSelectionStart(const Coordinates& aPosition); + void SetSelectionEnd(const Coordinates& aPosition); + void SetSelection(const Coordinates& aStart, const Coordinates& aEnd, + SelectionMode aMode = SelectionMode::Normal); + void SelectWordUnderCursor(); + void SelectAll(); + bool HasSelection() const; + + void Copy(); + void Cut(); + void Paste(); + void Delete(); + + bool CanUndo() const; + bool CanRedo() const; + void Undo(int aSteps = 1); + void Redo(int aSteps = 1); + + static const Palette& GetDarkPalette(); + static const Palette& GetLightPalette(); + static const Palette& GetRetroBluePalette(); + +private: + typedef std::vector> RegexList; + + struct EditorState { + Coordinates mSelectionStart; + Coordinates mSelectionEnd; + Coordinates mCursorPosition; + }; + + class UndoRecord { + public: + UndoRecord() {} + ~UndoRecord() {} + + UndoRecord(const std::string& aAdded, const TextEditor::Coordinates aAddedStart, + const TextEditor::Coordinates aAddedEnd, + + const std::string& aRemoved, const TextEditor::Coordinates aRemovedStart, + const TextEditor::Coordinates aRemovedEnd, + + TextEditor::EditorState& aBefore, TextEditor::EditorState& aAfter); + + void Undo(TextEditor* aEditor); + void Redo(TextEditor* aEditor); + + std::string mAdded; + Coordinates mAddedStart; + Coordinates mAddedEnd; + + std::string mRemoved; + Coordinates mRemovedStart; + Coordinates mRemovedEnd; + + EditorState mBefore; + EditorState mAfter; + }; + + typedef std::vector UndoBuffer; + + void ProcessInputs(); + void Colorize(int aFromLine = 0, int aCount = -1); + void ColorizeRange(int aFromLine = 0, int aToLine = 0); + void ColorizeInternal(); + float TextDistanceToLineStart(const Coordinates& aFrom) const; + void EnsureCursorVisible(); + int GetPageSize() const; + std::string GetText(const Coordinates& aStart, const Coordinates& aEnd) const; + Coordinates GetActualCursorCoordinates() const; + Coordinates SanitizeCoordinates(const Coordinates& aValue) const; + void Advance(Coordinates& aCoordinates) const; + void DeleteRange(const Coordinates& aStart, const Coordinates& aEnd); + int InsertTextAt(Coordinates& aWhere, const char* aValue); + void AddUndo(UndoRecord& aValue); + Coordinates ScreenPosToCoordinates(const ImVec2& aPosition) const; + Coordinates FindWordStart(const Coordinates& aFrom) const; + Coordinates FindWordEnd(const Coordinates& aFrom) const; + Coordinates FindNextWord(const Coordinates& aFrom) const; + int GetCharacterIndex(const Coordinates& aCoordinates) const; + int GetCharacterColumn(int aLine, int aIndex) const; + int GetLineCharacterCount(int aLine) const; + int GetLineMaxColumn(int aLine) const; + bool IsOnWordBoundary(const Coordinates& aAt) const; + void RemoveLine(int aStart, int aEnd); + void RemoveLine(int aIndex); + Line& InsertLine(int aIndex); + void EnterCharacter(ImWchar aChar, bool aShift); + void Backspace(); + void DeleteSelection(); + std::string GetWordUnderCursor() const; + std::string GetWordAt(const Coordinates& aCoords) const; + ImU32 GetGlyphColor(const Glyph& aGlyph) const; + + void HandleKeyboardInputs(); + void HandleMouseInputs(); + void Render(); + + float mLineSpacing; + Lines mLines; + EditorState mState; + UndoBuffer mUndoBuffer; + int mUndoIndex; + + int mTabSize; + bool mOverwrite; + bool mReadOnly; + bool mWithinRender; + bool mScrollToCursor; + bool mScrollToTop; + bool mTextChanged; + bool mColorizerEnabled; + float mTextStart; // position (in pixels) where a code line starts relative to the left of the + // TextEditor. + int mLeftMargin; + bool mCursorPositionChanged; + int mColorRangeMin, mColorRangeMax; + SelectionMode mSelectionMode; + bool mHandleKeyboardInputs; + bool mHandleMouseInputs; + bool mIgnoreImGuiChild; + bool mShowWhitespaces; + + Palette mPaletteBase; + Palette mPalette; + LanguageDefinition mLanguageDefinition; + RegexList mRegexList; + + bool mCheckComments; + Breakpoints mBreakpoints; + ErrorMarkers mErrorMarkers; + ImVec2 mCharAdvance; + Coordinates mInteractiveStart, mInteractiveEnd; + std::string mLineBuffer; + uint64_t mStartTime; + + float mLastClick; +}; + +} // namespace Core::Devtools::Widget \ No newline at end of file diff --git a/src/core/file_format/pkg.cpp b/src/core/file_format/pkg.cpp index 7d36b019a..0ae9f57eb 100644 --- a/src/core/file_format/pkg.cpp +++ b/src/core/file_format/pkg.cpp @@ -3,6 +3,7 @@ #include #include "common/io_file.h" +#include "common/logging/formatter.h" #include "core/file_format/pkg.h" #include "core/file_format/pkg_type.h" @@ -349,7 +350,8 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem:: auto parent_path = extract_path.parent_path(); auto title_id = GetTitleID(); - if (parent_path.filename() != title_id) { + if (parent_path.filename() != title_id && + !fmt::UTF(extract_path.u8string()).data.ends_with("-UPDATE")) { extractPaths[ndinode_counter] = parent_path / title_id; } else { // DLCs path has different structure diff --git a/src/core/file_format/psf.cpp b/src/core/file_format/psf.cpp index a5e502f98..0502f29d2 100644 --- a/src/core/file_format/psf.cpp +++ b/src/core/file_format/psf.cpp @@ -227,6 +227,12 @@ void PSF::AddBinary(std::string key, std::vector value, bool update) { map_binaries.emplace(entry_list.size() - 1, std::move(value)); } +void PSF::AddBinary(std::string key, uint64_t value, bool update) { + std::vector data(8); + std::memcpy(data.data(), &value, 8); + return AddBinary(std::move(key), std::move(data), update); +} + void PSF::AddString(std::string key, std::string value, bool update) { auto [it, index] = FindEntry(key); bool exist = it != entry_list.end(); diff --git a/src/core/file_format/psf.h b/src/core/file_format/psf.h index d25b79eec..6f35fa69a 100644 --- a/src/core/file_format/psf.h +++ b/src/core/file_format/psf.h @@ -67,6 +67,7 @@ public: std::optional GetInteger(std::string_view key) const; void AddBinary(std::string key, std::vector value, bool update = false); + void AddBinary(std::string key, uint64_t value, bool update = false); // rsv4 format void AddString(std::string key, std::string value, bool update = false); void AddInteger(std::string key, s32 value, bool update = false); diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index 3b060dd83..769940cf0 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include "common/config.h" #include "common/string_util.h" #include "core/file_sys/fs.h" @@ -27,9 +28,9 @@ void MntPoints::UnmountAll() { m_mnt_pairs.clear(); } -std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory, bool* is_read_only) { +std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_read_only) { // Evil games like Turok2 pass double slashes e.g /app0//game.kpf - std::string corrected_path(guest_directory); + std::string corrected_path(path); size_t pos = corrected_path.find("//"); while (pos != std::string::npos) { corrected_path.replace(pos, 2, "/"); @@ -51,68 +52,83 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory, b } // Remove device (e.g /app0) from path to retrieve relative path. - pos = mount->mount.size() + 1; - const auto rel_path = std::string_view(corrected_path).substr(pos); - const auto host_path = mount->host_path / rel_path; + const auto rel_path = std::string_view{corrected_path}.substr(mount->mount.size() + 1); + std::filesystem::path host_path = mount->host_path / rel_path; + std::filesystem::path patch_path = mount->host_path; + patch_path += "-UPDATE"; + patch_path /= rel_path; + + if ((corrected_path.starts_with("/app0") || corrected_path.starts_with("/hostapp")) && + std::filesystem::exists(patch_path)) { + return patch_path; + } + if (!NeedsCaseInsensitiveSearch) { return host_path; } - // If the path does not exist attempt to verify this. - // Retrieve parent path until we find one that exists. - std::scoped_lock lk{m_mutex}; - path_parts.clear(); - auto current_path = host_path; - while (!std::filesystem::exists(current_path)) { - // We have probably cached this if it's a folder. - if (auto it = path_cache.find(current_path); it != path_cache.end()) { - current_path = it->second; - break; + const auto search = [&](const auto host_path) { + // If the path does not exist attempt to verify this. + // Retrieve parent path until we find one that exists. + std::scoped_lock lk{m_mutex}; + path_parts.clear(); + auto current_path = host_path; + while (!std::filesystem::exists(current_path)) { + // We have probably cached this if it's a folder. + if (auto it = path_cache.find(current_path); it != path_cache.end()) { + current_path = it->second; + break; + } + path_parts.emplace_back(current_path.filename()); + current_path = current_path.parent_path(); } - path_parts.emplace_back(current_path.filename()); - current_path = current_path.parent_path(); - } - - // We have found an anchor. Traverse parts we recoded and see if they - // exist in filesystem but in different case. - auto guest_path = current_path; - while (!path_parts.empty()) { - const auto part = path_parts.back(); - const auto add_match = [&](const auto& host_part) { - current_path /= host_part; - guest_path /= part; - path_cache[guest_path] = current_path; - path_parts.pop_back(); - }; - - // Can happen when the mismatch is in upper folder. - if (std::filesystem::exists(current_path / part)) { - add_match(part); - continue; - } - const auto part_low = Common::ToLower(part.string()); - bool found_match = false; - for (const auto& path : std::filesystem::directory_iterator(current_path)) { - const auto candidate = path.path().filename(); - const auto filename = Common::ToLower(candidate.string()); - // Check if a filename matches in case insensitive manner. - if (filename != part_low) { + // We have found an anchor. Traverse parts we recoded and see if they + // exist in filesystem but in different case. + auto guest_path = current_path; + while (!path_parts.empty()) { + const auto part = path_parts.back(); + const auto add_match = [&](const auto& host_part) { + current_path /= host_part; + guest_path /= part; + path_cache[guest_path] = current_path; + path_parts.pop_back(); + }; + // Can happen when the mismatch is in upper folder. + if (std::filesystem::exists(current_path / part)) { + add_match(part); continue; } - // We found a match, record the actual path in the cache. - add_match(candidate); - found_match = true; - break; - } - if (!found_match) { - // Opening the guest path will surely fail but at least gives - // a better error message than the empty path. - return host_path; + const auto part_low = Common::ToLower(part.string()); + bool found_match = false; + for (const auto& path : std::filesystem::directory_iterator(current_path)) { + const auto candidate = path.path().filename(); + const auto filename = Common::ToLower(candidate.string()); + // Check if a filename matches in case insensitive manner. + if (filename != part_low) { + continue; + } + // We found a match, record the actual path in the cache. + add_match(candidate); + found_match = true; + break; + } + if (!found_match) { + return std::optional({}); + } } + return std::optional(current_path); + }; + + if (const auto path = search(patch_path)) { + return *path; + } + if (const auto path = search(host_path)) { + return *path; } - // The path was found. - return current_path; + // Opening the guest path will surely fail but at least gives + // a better error message than the empty path. + return host_path; } int HandleTable::CreateHandle() { diff --git a/src/core/libraries/ajm/ajm.cpp b/src/core/libraries/ajm/ajm.cpp index cb3f0e265..39e25445e 100644 --- a/src/core/libraries/ajm/ajm.cpp +++ b/src/core/libraries/ajm/ajm.cpp @@ -1,12 +1,12 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma clang optimize off +#include #include #include #include "ajm_at9.h" #include "common/assert.h" -#include "common/debug.h" #include "common/logging/log.h" #include "core/libraries/ajm/ajm.h" #include "core/libraries/ajm/ajm_error.h" @@ -16,7 +16,6 @@ #include "core/libraries/libs.h" extern "C" { -#include #include #include #include @@ -28,18 +27,34 @@ namespace Libraries::Ajm { static constexpr u32 AJM_INSTANCE_STATISTICS = 0x80000; +static constexpr u32 SCE_AJM_WAIT_INFINITE = -1; + static constexpr u32 MaxInstances = 0x2fff; +static constexpr u32 MaxBatches = 1000; + +struct BatchInfo { + u16 instance{}; + u16 offset_in_qwords{}; // Needed for AjmBatchError? + bool waiting{}; + bool finished{}; + std::mutex mtx; + std::condition_variable cv; + int result{}; +}; + struct AjmDevice { - u32 max_prio; - u32 min_prio; + u32 max_prio{}; + u32 min_prio{}; u32 curr_cursor{}; u32 release_cursor{MaxInstances - 1}; std::array is_registered{}; std::array free_instances{}; - std::array, MaxInstances> instances; + std::array, MaxInstances> instances; + std::vector> batches{}; + std::mutex batches_mutex; - bool IsRegistered(AjmCodecType type) const { + [[nodiscard]] bool IsRegistered(AjmCodecType type) const { return is_registered[static_cast(type)]; } @@ -86,11 +101,7 @@ void* PS4_SYSV_ABI sceAjmBatchJobControlBufferRa(AjmSingleJob* batch_pos, u32 in batch_pos->opcode.is_statistic = instance == AJM_INSTANCE_STATISTICS; batch_pos->opcode.is_control = true; - if (instance == AJM_INSTANCE_STATISTICS) { - BREAKPOINT(); - } - - AjmInOutJob* job = nullptr; + AjmInOutJob* job; if (ret_addr == nullptr) { batch_pos->job_size = sizeof(AjmInOutJob); job = &batch_pos->job; @@ -121,45 +132,10 @@ void* PS4_SYSV_ABI sceAjmBatchJobInlineBuffer(AjmSingleJob* batch_pos, const voi return nullptr; } -auto ParseWavHeader = [](void* buf, WavHeader* header) { - if (!buf) { - // buf is passed as nullptr in some cases (i.e. GetCodecInfo) - return; - } - std::memcpy(header, buf, sizeof(WavHeader)); - - std::string riff(header->RIFF, 4); - std::string wave(header->WAVE, 4); - std::string fmt(header->fmt, 4); - std::string dataID(header->Subchunk2ID, 4); - - if (std::memcmp(header->RIFF, "RIFF", 4) != 0 || std::memcmp(header->WAVE, "WAVE", 4) != 0 || - std::memcmp(header->fmt, "fmt ", 4) != 0 || - std::memcmp(header->Subchunk2ID, "data", 4) != 0) { - LOG_ERROR(Lib_Ajm, "Invalid WAV file."); - return; - } - - LOG_INFO(Lib_Ajm, "RIFF header: {}", riff); - LOG_INFO(Lib_Ajm, "WAVE header: {}", wave); - LOG_INFO(Lib_Ajm, "FMT: {}", fmt); - LOG_INFO(Lib_Ajm, "Data size: {}", header->ChunkSize); - LOG_INFO(Lib_Ajm, "Sampling Rate: {}", header->SamplesPerSec); - LOG_INFO(Lib_Ajm, "Number of bits used: {}", header->bitsPerSample); - LOG_INFO(Lib_Ajm, "Number of channels: {}", header->NumOfChan); - LOG_INFO(Lib_Ajm, "Number of bytes per second: {}", header->bytesPerSec); - LOG_INFO(Lib_Ajm, "Data length: {}", header->Subchunk2Size); - LOG_INFO(Lib_Ajm, "Audio Format: {}", header->AudioFormat); - LOG_INFO(Lib_Ajm, "Block align: {}", header->blockAlign); - LOG_INFO(Lib_Ajm, "Data string: {}", dataID); -}; - void* PS4_SYSV_ABI sceAjmBatchJobRunBufferRa(AjmSingleJob* batch_pos, u32 instance, AjmFlags flags, - void* in_buffer, u32 in_size, u8* out_buffer, + u8* in_buffer, u32 in_size, u8* out_buffer, const u32 out_size, u8* sideband_output, const u32 sideband_output_size, const void* ret_addr) { - WavHeader header{}; - ParseWavHeader(in_buffer, &header); LOG_INFO(Lib_Ajm, "called instance = {:#x}, flags = {:#x}, cmd = {}, in_size = {:#x}, out_size = {:#x}, " "ret_addr = {}", @@ -178,7 +154,7 @@ void* PS4_SYSV_ABI sceAjmBatchJobRunBufferRa(AjmSingleJob* batch_pos, u32 instan batch_pos->opcode.is_statistic = false; batch_pos->opcode.is_control = false; - AjmInOutJob* job = nullptr; + AjmInOutJob* job; if (ret_addr == nullptr) { batch_pos->job_size = sizeof(AjmInOutJob) + 16; job = &batch_pos->job; @@ -188,16 +164,17 @@ void* PS4_SYSV_ABI sceAjmBatchJobRunBufferRa(AjmSingleJob* batch_pos, u32 instan job = &batch_pos->ret.job; } - // todo: add some missing stuff + // TODO: Check if all the fields are being set, might be missing some job->input.buf_size = in_size; - job->input.buffer = static_cast(in_buffer); + job->input.buffer = in_buffer; job->flags = u32(flags.raw); job->unk1 = (job->unk1 & 0xfc000030) + (flags.raw >> 0x1a) + 4; job->output.buf_size = out_size; job->output.buffer = out_buffer; job->output.props &= 0xffffffe0; job->output.props |= 0x12; - *reinterpret_cast(reinterpret_cast(job) + 32) = sideband_output; + // *reinterpret_cast(reinterpret_cast(job) + 32) = sideband_output; + job->sideband_output = sideband_output; return job; } @@ -213,6 +190,7 @@ void* PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa(AjmMultiJob* batch_pos, u32 in instance, flags.raw, magic_enum::enum_name(AjmJobRunFlags(flags.command)), magic_enum::enum_name(AjmJobSidebandFlags(flags.sideband)), num_in_buffers, num_out_buffers, fmt::ptr(ret_addr)); + const u32 job_size = (num_in_buffers * 2 + 1 + num_out_buffers * 2) * 8; const bool is_debug = ret_addr != nullptr; batch_pos->opcode.instance = instance; @@ -223,7 +201,7 @@ void* PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa(AjmMultiJob* batch_pos, u32 in batch_pos->opcode.is_statistic = false; batch_pos->opcode.is_control = false; - u32* job = nullptr; + u32* job; if (!is_debug) { batch_pos->job_size = job_size + 16; job = batch_pos->job; @@ -237,7 +215,7 @@ void* PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa(AjmMultiJob* batch_pos, u32 in } for (s32 i = 0; i < num_in_buffers; i++) { - AjmJobBuffer* in_buf = reinterpret_cast(job); + auto* in_buf = reinterpret_cast(job); in_buf->props &= 0xffffffe0; in_buf->props |= 1; in_buf->buf_size = in_buffers[i].size; @@ -250,7 +228,7 @@ void* PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa(AjmMultiJob* batch_pos, u32 in job += 2; for (s32 i = 0; i < num_out_buffers; i++) { - AjmJobBuffer* out_buf = reinterpret_cast(job); + auto* out_buf = reinterpret_cast(job); out_buf->props &= 0xffffffe0; out_buf->props |= 0x11; out_buf->buf_size = out_buffers[i].size; @@ -273,23 +251,23 @@ int PS4_SYSV_ABI sceAjmBatchStartBuffer(u32 context, const u8* batch, u32 batch_ return ORBIS_AJM_ERROR_MALFORMED_BATCH; } - static constexpr u32 MaxBatches = 1000; + const auto batch_info = std::make_shared(); - struct BatchInfo { - u16 instance; - u16 offset_in_qwords; - }; - std::array batches{}; - u32 num_batches = 0; - - const u8* batch_ptr = batch; - const u8* batch_end = batch + batch_size; - while (batch_ptr < batch_end) { - if (num_batches >= MaxBatches) { + { + if (dev->batches.size() >= MaxBatches) { LOG_ERROR(Lib_Ajm, "Too many batches in job!"); return ORBIS_AJM_ERROR_OUT_OF_MEMORY; } - AjmJobHeader header; + + *out_batch_id = static_cast(dev->batches.size()); + dev->batches.push_back(batch_info); + } + + const u8* batch_ptr = batch; + const u8* batch_end = batch + batch_size; + AjmJobHeader header{}; + + while (batch_ptr < batch_end) { std::memcpy(&header, batch_ptr, sizeof(u64)); const auto& opcode = header.opcode; @@ -297,34 +275,57 @@ int PS4_SYSV_ABI sceAjmBatchStartBuffer(u32 context, const u8* batch, u32 batch_ const u8* job_ptr = batch_ptr + sizeof(AjmJobHeader) + opcode.is_debug * 16; if (opcode.is_control) { - // ASSERT_MSG(!opcode.is_statistic, "Statistic instance is not handled"); const auto command = AjmJobControlFlags(opcode.command_flags); switch (command) { case AjmJobControlFlags::Reset: { - LOG_INFO(Lib_Ajm, "Resetting instance {}", opcode.instance); - dev->instances[opcode.instance]->Reset(); + LOG_INFO(Lib_Ajm, "Resetting instance {}", instance); + dev->instances[instance]->Reset(); break; } - case (AjmJobControlFlags::Initialize | AjmJobControlFlags::Reset): - LOG_INFO(Lib_Ajm, "Initializing instance {}", opcode.instance); + case AjmJobControlFlags::Initialize: + LOG_INFO(Lib_Ajm, "Initializing instance {}", instance); + if (dev->instances[instance]->codec_type == AjmCodecType::At9Dec) { + const auto at9_instance = + dynamic_cast(dev->instances[instance].get()); + const auto in_buffer = reinterpret_cast(job_ptr); + std::memcpy( + at9_instance->config_data, + reinterpret_cast(in_buffer->buffer) + ->config_data, + SCE_AT9_CONFIG_DATA_SIZE); + LOG_INFO( + Lib_Ajm, "Initialize params: {}, config_data: {}, reserved: {}", + fmt::ptr(reinterpret_cast( + in_buffer->buffer)), + fmt::ptr(reinterpret_cast( + in_buffer->buffer) + ->config_data), + reinterpret_cast(in_buffer->buffer) + ->reserved); + } break; case AjmJobControlFlags::Resample: - LOG_INFO(Lib_Ajm, "Set resample params of instance {}", opcode.instance); + LOG_INFO(Lib_Ajm, "Set resample params of instance {}", instance); + break; + case AjmJobControlFlags::StatisticsEngine: + ASSERT_MSG(instance == AJM_INSTANCE_STATISTICS, + "Expected AJM_INSTANCE_STATISTICS for StatisticsEngine command"); + LOG_TRACE(Lib_Ajm, "StatisticsEngine flag not implemented"); break; default: break; } // Write sideband structures. - const AjmJobBuffer* out_buffer = reinterpret_cast(job_ptr + 24); + const auto* out_buffer = reinterpret_cast(job_ptr + 24); auto* result = reinterpret_cast(out_buffer->buffer); result->result = 0; result->internal_result = 0; } else { const auto command = AjmJobRunFlags(opcode.command_flags); const auto sideband = AjmJobSidebandFlags(opcode.sideband_flags); - const AjmJobBuffer* in_buffer = reinterpret_cast(job_ptr); - const AjmJobBuffer* out_buffer = reinterpret_cast(job_ptr + 24); + const auto* in_buffer = reinterpret_cast(job_ptr); + const auto* out_buffer = reinterpret_cast(job_ptr + 24); job_ptr += 24; LOG_INFO(Lib_Ajm, "Decode job cmd = {}, sideband = {}, in_addr = {}, in_size = {}", @@ -332,7 +333,7 @@ int PS4_SYSV_ABI sceAjmBatchStartBuffer(u32 context, const u8* batch, u32 batch_ fmt::ptr(in_buffer->buffer), in_buffer->buf_size); // Decode as much of the input bitstream as possible. - AjmAt9Decoder* decoder_instance = dev->instances[opcode.instance].get(); + AjmInstance* decoder_instance = dev->instances[instance].get(); const auto [in_remain, out_remain, num_frames] = decoder_instance->Decode( in_buffer->buffer, in_buffer->buf_size, out_buffer->buffer, out_buffer->buf_size); @@ -357,27 +358,81 @@ int PS4_SYSV_ABI sceAjmBatchStartBuffer(u32 context, const u8* batch, u32 batch_ sideband_ptr += sizeof(AjmSidebandMFrame); } if (True(command & AjmJobRunFlags::GetCodecInfo)) { - LOG_TRACE(Lib_Ajm, "GetCodecInfo called, supplying dummy info for now"); - auto* codec_info = reinterpret_cast(sideband_ptr); - codec_info->uiFrameSamples = 0; - codec_info->uiFramesInSuperFrame = 0; - codec_info->uiNextFrameSize = 0; - codec_info->uiSuperFrameSize = 0; - sideband_ptr += sizeof(SceAjmSidebandDecAt9CodecInfo); + // TODO: Implement this for MP3 + + if (decoder_instance->codec_type == AjmCodecType::At9Dec) { + const auto at9_decoder_instance = + dynamic_cast(decoder_instance); + + Atrac9CodecInfo decoder_codec_info; + Atrac9GetCodecInfo(at9_decoder_instance->handle, &decoder_codec_info); + + auto* codec_info = + reinterpret_cast(sideband_ptr); + codec_info->uiFrameSamples = decoder_codec_info.frameSamples; + codec_info->uiFramesInSuperFrame = decoder_codec_info.framesInSuperframe; + codec_info->uiNextFrameSize = + static_cast(at9_decoder_instance->handle)->Config.FrameBytes; + codec_info->uiSuperFrameSize = decoder_codec_info.superframeSize; + sideband_ptr += sizeof(SceAjmSidebandDecAt9CodecInfo); + } } } batch_ptr += sizeof(AjmJobHeader) + header.job_size; } - static int batch_id = 0; - *out_batch_id = ++batch_id; + + batch_info->finished = true; return ORBIS_OK; } -int PS4_SYSV_ABI sceAjmBatchWait() { - LOG_ERROR(Lib_Ajm, "(STUBBED) called"); - return ORBIS_OK; +// useless for now because batch processing isn't asynchronous +int PS4_SYSV_ABI sceAjmBatchWait(const u32 context, const u32 batch_id, const u32 timeout, + AjmBatchError* const batch_error) { + LOG_INFO(Lib_Ajm, "called context = {}, batch_id = {}, timeout = {}", context, batch_id, + timeout); + + if (batch_id > 0xFF) { + return ORBIS_AJM_ERROR_INVALID_BATCH; + } + + if (batch_id >= dev->batches.size()) { + return ORBIS_AJM_ERROR_INVALID_BATCH; + } + + const std::shared_ptr batch = dev->batches[batch_id]; + + if (batch->waiting) { + return ORBIS_AJM_ERROR_BUSY; + } + + batch->waiting = true; + + if (timeout > 0 && timeout != SCE_AJM_WAIT_INFINITE) { + std::this_thread::sleep_for(std::chrono::milliseconds(timeout)); + + if (!batch->finished) { + batch->waiting = false; + return ORBIS_AJM_ERROR_IN_PROGRESS; + } + } + + if (timeout == SCE_AJM_WAIT_INFINITE) { + while (!batch->finished) { + std::this_thread::yield(); + } + } + + // timeout == 0 = non-blocking poll + if (!batch->finished) { + batch->waiting = false; + return ORBIS_AJM_ERROR_IN_PROGRESS; + } + + dev->batches.erase(dev->batches.begin() + batch_id); + + return 0; } int PS4_SYSV_ABI sceAjmDecAt9ParseConfigData() { @@ -433,16 +488,16 @@ int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context, AjmCodecType codec_type, AjmI } const u32 index = dev->free_instances[dev->curr_cursor++]; dev->curr_cursor %= MaxInstances; - std::unique_ptr instance; + std::unique_ptr instance; switch (codec_type) { case AjmCodecType::Mp3Dec: - instance = std::make_unique(); + instance = std::make_unique(); break; case AjmCodecType::At9Dec: instance = std::make_unique(); break; default: - break; + UNREACHABLE_MSG("Codec #{} not implemented", u32(codec_type)); } instance->index = index; instance->codec_type = codec_type; diff --git a/src/core/libraries/ajm/ajm.h b/src/core/libraries/ajm/ajm.h index 3fbda7659..e3d474abc 100644 --- a/src/core/libraries/ajm/ajm.h +++ b/src/core/libraries/ajm/ajm.h @@ -41,12 +41,14 @@ struct AjmInOutJob { u32 unk1; u32 flags; AjmJobBuffer output; + void* sideband_output; }; enum class AjmJobControlFlags : u32 { Reset = 1 << 2, Initialize = 1 << 3, Resample = 1 << 4, + StatisticsEngine = 1U << 31, }; DECLARE_ENUM_FLAG_OPERATORS(AjmJobControlFlags) @@ -92,7 +94,7 @@ struct AjmSingleJob { }; }; -static_assert(sizeof(AjmSingleJob) == 0x40); +static_assert(sizeof(AjmSingleJob) == 0x48); struct AjmMultiJob { AjmHLEOpcode opcode; @@ -149,7 +151,7 @@ void* PS4_SYSV_ABI sceAjmBatchJobControlBufferRa(AjmSingleJob* batch_pos, u32 in void* PS4_SYSV_ABI sceAjmBatchJobInlineBuffer(AjmSingleJob* batch_pos, const void* in_buffer, size_t in_size, const void** batch_address); void* PS4_SYSV_ABI sceAjmBatchJobRunBufferRa(AjmSingleJob* batch_pos, u32 instance, AjmFlags flags, - void * in_buffer, u32 in_size, u8* out_buffer, + u8* in_buffer, u32 in_size, u8* out_buffer, const u32 out_size, u8* sideband_output, const u32 sideband_output_size, const void* ret_addr); void* PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa(AjmMultiJob* batch_pos, u32 instance, @@ -159,9 +161,10 @@ void* PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa(AjmMultiJob* batch_pos, u32 in u64 num_output_buffers, void* sideband_output, u64 sideband_output_size, const void* ret_addr); int PS4_SYSV_ABI sceAjmBatchStartBuffer(u32 context, const u8* batch, u32 batch_size, - const int priority, AjmBatchError* patch_error, + const int priority, AjmBatchError* batch_error, u32* out_batch_id); -int PS4_SYSV_ABI sceAjmBatchWait(); +int PS4_SYSV_ABI sceAjmBatchWait(const u32 context, const u32 batch_id, const u32 timeout, + AjmBatchError* const batch_error); int PS4_SYSV_ABI sceAjmDecAt9ParseConfigData(); int PS4_SYSV_ABI sceAjmDecMp3ParseFrame(const u8* stream, u32 stream_size, int parse_ofl, AjmDecMp3ParseFrame* frame); diff --git a/src/core/libraries/ajm/ajm_at9.cpp b/src/core/libraries/ajm/ajm_at9.cpp index c1a208881..ddcd8854c 100644 --- a/src/core/libraries/ajm/ajm_at9.cpp +++ b/src/core/libraries/ajm/ajm_at9.cpp @@ -4,8 +4,10 @@ #include #include "common/assert.h" #include "core/libraries/ajm/ajm_at9.h" +#include "error_codes.h" extern "C" { +#include #include } @@ -17,9 +19,14 @@ AjmAt9Decoder::AjmAt9Decoder() { AjmAt9Decoder::Reset(); } -AjmAt9Decoder::~AjmAt9Decoder() {} +AjmAt9Decoder::~AjmAt9Decoder() { + Atrac9ReleaseHandle(handle); +} void AjmAt9Decoder::Reset() { + Atrac9ReleaseHandle(handle); + handle = Atrac9GetHandle(); + ASSERT_MSG(handle, "Atrac9GetHandle failed"); decoded_samples = 0; static int filename = 0; file.close(); @@ -28,31 +35,40 @@ void AjmAt9Decoder::Reset() { std::tuple AjmAt9Decoder::Decode(const u8* in_buf, u32 in_size, u8* out_buf, u32 out_size) { - auto ParseWavHeader = [](const uint8_t* buf, WavHeader* header) { - std::memcpy(header, buf, sizeof(WavHeader)); - }; - if (in_size <= 0 || out_size <= 0) { return std::tuple(in_size, out_size, 0); } - WavHeader header{}; - ParseWavHeader(in_buf - 0x64, &header); + Atrac9InitDecoder(handle, config_data); - if (!decoder_initialized) { - Atrac9InitDecoder(handle, header.configData); - decoder_initialized = true; - } - - // todo: do something with decoded data :p + const auto decoder_handle = static_cast(handle); + int frame_count; int bytes_used; - Atrac9Decode(handle, in_buf, nullptr, &bytes_used); Atrac9CodecInfo codec_info; Atrac9GetCodecInfo(handle, &codec_info); - return std::tuple(in_size, out_size, 0); + decoded_samples = 0; + + const auto size = codec_info.channels * codec_info.frameSamples * sizeof(u16); + + for (frame_count = 0; frame_count < decoder_handle->Config.FramesPerSuperframe; frame_count++) { + short pcm_buffer[size]; + int ret = Atrac9Decode(decoder_handle, in_buf, pcm_buffer, &bytes_used); + ASSERT_MSG(ret == At9Status::ERR_SUCCESS, "Atrac9Decode failed"); + in_buf += bytes_used; + in_size -= bytes_used; + std::memcpy(out_buf, pcm_buffer, size); + file.write(reinterpret_cast(pcm_buffer), size); + out_buf += size; + out_size -= size; + decoded_samples += decoder_handle->Config.FrameSamples; + } + + LOG_TRACE(Lib_Ajm, "Decoded {} samples, frame count: {}", decoded_samples, frame_count); + + return std::tuple(in_size, out_size, frame_count); } } // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_at9.h b/src/core/libraries/ajm/ajm_at9.h index a7760829b..b96cda6a8 100644 --- a/src/core/libraries/ajm/ajm_at9.h +++ b/src/core/libraries/ajm/ajm_at9.h @@ -4,7 +4,6 @@ #pragma once #include -#include #include "common/types.h" #include "core/libraries/ajm/ajm_instance.h" @@ -16,42 +15,17 @@ namespace Libraries::Ajm { constexpr unsigned int SCE_AT9_CONFIG_DATA_SIZE = 4; -#pragma pack(push, 1) -struct WavHeader { - /* RIFF Chunk Descriptor */ - char RIFF[4]; // RIFF Header Magic header - uint32_t ChunkSize; // RIFF Chunk Size - char WAVE[4]; // WAVE Header - /* "fmt" sub-chunk */ - char fmt[4]; // FMT header - uint32_t Subchunk1Size; // Size of the fmt chunk - uint16_t AudioFormat; // Audio format 1=PCM,6=mulaw,7=alaw, 257=IBM Mu-Law, 258=IBM A-Law, - // 259=ADPCM - uint16_t NumOfChan; // Number of channels 1=Mono 2=Sterio - uint32_t SamplesPerSec; // Sampling Frequency in Hz - uint32_t bytesPerSec; // bytes per second - uint16_t blockAlign; // 2=16-bit mono, 4=16-bit stereo - uint16_t bitsPerSample; // Number of bits per sample - u16 cbSize; // Extension size - u16 samplesPerBlock; - u32 channelMask; - GUID subFormat; - u32 versionInfo; - u8 configData[SCE_AT9_CONFIG_DATA_SIZE]; // the juicy part +struct SceAjmDecAt9InitializeParameters { + u8 config_data[SCE_AT9_CONFIG_DATA_SIZE]; u32 reserved; - /* "fact" sub-chunk */ - char Subchunk2ID[4]; // "fact" string - uint32_t Subchunk2Size; // Sampled data length }; -#pragma pack(pop) - -static_assert(sizeof(WavHeader) == 80); struct AjmAt9Decoder final : AjmInstance { void* handle; bool decoder_initialized = false; std::fstream file; int length; + u8 config_data[SCE_AT9_CONFIG_DATA_SIZE]; explicit AjmAt9Decoder(); ~AjmAt9Decoder() override; diff --git a/src/core/libraries/ajm/ajm_instance.h b/src/core/libraries/ajm/ajm_instance.h index 5fafeac80..5b5e60419 100644 --- a/src/core/libraries/ajm/ajm_instance.h +++ b/src/core/libraries/ajm/ajm_instance.h @@ -3,6 +3,7 @@ #pragma once +#include "common/enum.h" #include "common/types.h" extern "C" { @@ -19,6 +20,7 @@ enum class AjmCodecType : u32 { M4aacDec = 2, Max = 23, }; +DECLARE_ENUM_FLAG_OPERATORS(AjmCodecType); static constexpr u32 NumAjmCodecs = u32(AjmCodecType::Max); enum class AjmFormatEncoding : u32 { @@ -28,8 +30,8 @@ enum class AjmFormatEncoding : u32 { }; struct AjmSidebandResult { - s32 result; - s32 internal_result; + s32 result; + s32 internal_result; }; struct AjmSidebandMFrame { @@ -64,8 +66,8 @@ struct AjmInstance { virtual void Reset() = 0; - virtual std::tuple Decode(const u8* in_buf, u32 in_size, - u8* out_buf, u32 out_size) = 0; + virtual std::tuple Decode(const u8* in_buf, u32 in_size, u8* out_buf, + u32 out_size) = 0; }; } // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_mp3.cpp b/src/core/libraries/ajm/ajm_mp3.cpp index 6dd41de69..5bf164570 100644 --- a/src/core/libraries/ajm/ajm_mp3.cpp +++ b/src/core/libraries/ajm/ajm_mp3.cpp @@ -15,7 +15,9 @@ namespace Libraries::Ajm { // Following tables have been reversed from AJM library static constexpr std::array, 3> SamplerateTable = {{ - {0x5622, 0x5DC0, 0x3E80}, {0xAC44, 0xBB80, 0x7D00}, {0x2B11, 0x2EE0, 0x1F40}, + {0x5622, 0x5DC0, 0x3E80}, + {0xAC44, 0xBB80, 0x7D00}, + {0x2B11, 0x2EE0, 0x1F40}, }}; static constexpr std::array, 2> BitrateTable = {{ @@ -78,14 +80,13 @@ void AjmMp3Decoder::Reset() { file.open(fmt::format("inst{}_{}.raw", index, ++filename), std::ios::out | std::ios::binary); } -std::tuple AjmMp3Decoder::Decode(const u8* buf, u32 in_size, - u8* out_buf, u32 out_size) { +std::tuple AjmMp3Decoder::Decode(const u8* buf, u32 in_size, u8* out_buf, + u32 out_size) { u32 num_frames = 0; AVPacket* pkt = av_packet_alloc(); while (in_size > 0 && out_size > 0) { - int ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size, - buf, in_size, - AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); + int ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size, buf, in_size, AV_NOPTS_VALUE, + AV_NOPTS_VALUE, 0); ASSERT_MSG(ret >= 0, "Error while parsing {}", ret); buf += ret; in_size -= ret; diff --git a/src/core/libraries/ajm/ajm_mp3.h b/src/core/libraries/ajm/ajm_mp3.h index 7a81abae4..58d7e7bab 100644 --- a/src/core/libraries/ajm/ajm_mp3.h +++ b/src/core/libraries/ajm/ajm_mp3.h @@ -15,17 +15,11 @@ struct AVCodecParserContext; namespace Libraries::Ajm { -enum class AjmDecMp3OflType : u32 { - None = 0, - Lame = 1, - Vbri = 2, - Fgh = 3, - VbriAndFgh = 4 -}; +enum class AjmDecMp3OflType : u32 { None = 0, Lame = 1, Vbri = 2, Fgh = 3, VbriAndFgh = 4 }; // 11-bit syncword if MPEG 2.5 extensions are enabled static constexpr u8 SYNCWORDH = 0xff; -static constexpr u8 SYNCWORDL = 0xe0; +static constexpr u8 SYNCWORDL = 0xe0; struct AjmDecMp3ParseFrame { u64 frame_size; @@ -73,8 +67,8 @@ struct AjmMp3Decoder : public AjmInstance { void Reset() override; - std::tuple Decode(const u8* in_buf, u32 in_size, - u8* out_buf, u32 out_size) override; + std::tuple Decode(const u8* in_buf, u32 in_size, u8* out_buf, + u32 out_size) override; static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl, AjmDecMp3ParseFrame* frame); diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index 778d777c2..8057f3e11 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -1,7 +1,10 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include #include +#include #include #include "audio_core/sdl_audio.h" @@ -335,6 +338,10 @@ s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, const void* ptr) { int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) { for (u32 i = 0; i < num; i++) { + std::fstream file; + file.open(fmt::format("handle{}.raw", param[i].handle), std::ios::out | std::ios::binary); + file.write(static_cast(param[i].ptr), 1024); + file.close(); if (const auto err = sceAudioOutOutput(param[i].handle, param[i].ptr); err != 0) return err; } diff --git a/src/core/libraries/avplayer/avplayer_impl.cpp b/src/core/libraries/avplayer/avplayer_impl.cpp index 6de7b4c20..1c414c961 100644 --- a/src/core/libraries/avplayer/avplayer_impl.cpp +++ b/src/core/libraries/avplayer/avplayer_impl.cpp @@ -6,8 +6,10 @@ #include "avplayer_impl.h" #include "common/logging/log.h" +#include "common/singleton.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/libkernel.h" +#include "core/linker.h" using namespace Libraries::Kernel; @@ -17,28 +19,32 @@ void* PS4_SYSV_ABI AvPlayer::Allocate(void* handle, u32 alignment, u32 size) { const auto* const self = reinterpret_cast(handle); const auto allocate = self->m_init_data_original.memory_replacement.allocate; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return allocate(ptr, alignment, size); + const auto* linker = Common::Singleton::Instance(); + return linker->ExecuteGuest(allocate, ptr, alignment, size); } void PS4_SYSV_ABI AvPlayer::Deallocate(void* handle, void* memory) { const auto* const self = reinterpret_cast(handle); const auto deallocate = self->m_init_data_original.memory_replacement.deallocate; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return deallocate(ptr, memory); + const auto* linker = Common::Singleton::Instance(); + return linker->ExecuteGuest(deallocate, ptr, memory); } void* PS4_SYSV_ABI AvPlayer::AllocateTexture(void* handle, u32 alignment, u32 size) { const auto* const self = reinterpret_cast(handle); const auto allocate = self->m_init_data_original.memory_replacement.allocate_texture; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return allocate(ptr, alignment, size); + const auto* linker = Common::Singleton::Instance(); + return linker->ExecuteGuest(allocate, ptr, alignment, size); } void PS4_SYSV_ABI AvPlayer::DeallocateTexture(void* handle, void* memory) { const auto* const self = reinterpret_cast(handle); const auto deallocate = self->m_init_data_original.memory_replacement.deallocate_texture; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return deallocate(ptr, memory); + const auto* linker = Common::Singleton::Instance(); + return linker->ExecuteGuest(deallocate, ptr, memory); } int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) { @@ -47,7 +53,8 @@ int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) { const auto open = self->m_init_data_original.file_replacement.open; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return open(ptr, filename); + const auto* linker = Common::Singleton::Instance(); + return linker->ExecuteGuest(open, ptr, filename); } int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) { @@ -56,7 +63,8 @@ int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) { const auto close = self->m_init_data_original.file_replacement.close; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return close(ptr); + const auto* linker = Common::Singleton::Instance(); + return linker->ExecuteGuest(close, ptr); } int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length) { @@ -65,7 +73,8 @@ int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position const auto read_offset = self->m_init_data_original.file_replacement.readOffset; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return read_offset(ptr, buffer, position, length); + const auto* linker = Common::Singleton::Instance(); + return linker->ExecuteGuest(read_offset, ptr, buffer, position, length); } u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) { @@ -74,7 +83,8 @@ u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) { const auto size = self->m_init_data_original.file_replacement.size; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return size(ptr); + const auto* linker = Common::Singleton::Instance(); + return linker->ExecuteGuest(size, ptr); } SceAvPlayerInitData AvPlayer::StubInitData(const SceAvPlayerInitData& data) { @@ -102,7 +112,7 @@ AvPlayer::AvPlayer(const SceAvPlayerInitData& data) m_state(std::make_unique(m_init_data)) {} s32 AvPlayer::PostInit(const SceAvPlayerPostInitData& data) { - m_post_init_data = data; + m_state->PostInit(data); return ORBIS_OK; } diff --git a/src/core/libraries/avplayer/avplayer_impl.h b/src/core/libraries/avplayer/avplayer_impl.h index b6ad940c9..d7f28094e 100644 --- a/src/core/libraries/avplayer/avplayer_impl.h +++ b/src/core/libraries/avplayer/avplayer_impl.h @@ -56,7 +56,6 @@ private: SceAvPlayerInitData m_init_data{}; SceAvPlayerInitData m_init_data_original{}; - SceAvPlayerPostInitData m_post_init_data{}; std::mutex m_file_io_mutex{}; std::atomic_bool m_has_source{}; diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp index fcae180e7..19925ba0c 100644 --- a/src/core/libraries/avplayer/avplayer_source.cpp +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -37,7 +37,8 @@ namespace Libraries::AvPlayer { using namespace Kernel; -AvPlayerSource::AvPlayerSource(AvPlayerStateCallback& state) : m_state(state) {} +AvPlayerSource::AvPlayerSource(AvPlayerStateCallback& state, bool use_vdec2) + : m_state(state), m_use_vdec2(use_vdec2) {} AvPlayerSource::~AvPlayerSource() { Stop(); @@ -129,18 +130,25 @@ bool AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info LOG_WARNING(Lib_AvPlayer, "Stream {} language is unknown", stream_index); } switch (info.type) { - case SCE_AVPLAYER_VIDEO: + case SCE_AVPLAYER_VIDEO: { LOG_INFO(Lib_AvPlayer, "Stream {} is a video stream.", stream_index); info.details.video.aspect_ratio = f32(p_stream->codecpar->width) / p_stream->codecpar->height; - info.details.video.width = Common::AlignUp(u32(p_stream->codecpar->width), 16); - info.details.video.height = Common::AlignUp(u32(p_stream->codecpar->height), 16); + auto width = u32(p_stream->codecpar->width); + auto height = u32(p_stream->codecpar->height); + if (!m_use_vdec2) { + width = Common::AlignUp(width, 16); + height = Common::AlignUp(height, 16); + } + info.details.video.width = width; + info.details.video.height = height; if (p_lang_node != nullptr) { std::memcpy(info.details.video.language_code, p_lang_node->value, std::min(strlen(p_lang_node->value), size_t(3))); } break; - case SCE_AVPLAYER_AUDIO: + } + case SCE_AVPLAYER_AUDIO: { LOG_INFO(Lib_AvPlayer, "Stream {} is an audio stream.", stream_index); info.details.audio.channel_count = p_stream->codecpar->ch_layout.nb_channels; info.details.audio.sample_rate = p_stream->codecpar->sample_rate; @@ -150,7 +158,8 @@ bool AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info std::min(strlen(p_lang_node->value), size_t(3))); } break; - case SCE_AVPLAYER_TIMEDTEXT: + } + case SCE_AVPLAYER_TIMEDTEXT: { LOG_WARNING(Lib_AvPlayer, "Stream {} is a timedtext stream.", stream_index); info.details.subs.font_size = 12; info.details.subs.text_size = 12; @@ -159,10 +168,12 @@ bool AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info std::min(strlen(p_lang_node->value), size_t(3))); } break; - default: + } + default: { LOG_ERROR(Lib_AvPlayer, "Stream {} type is unknown: {}.", stream_index, info.type); return false; } + } return true; } @@ -189,8 +200,12 @@ bool AvPlayerSource::EnableStream(u32 stream_index) { LOG_ERROR(Lib_AvPlayer, "Could not open avcodec for video stream {}.", stream_index); return false; } - const auto width = Common::AlignUp(u32(m_video_codec_context->width), 16); - const auto height = Common::AlignUp(u32(m_video_codec_context->height), 16); + auto width = u32(m_video_codec_context->width); + auto height = u32(m_video_codec_context->height); + if (!m_use_vdec2) { + width = Common::AlignUp(width, 16); + height = Common::AlignUp(height, 16); + } const auto size = (width * height * 3) / 2; for (u64 index = 0; index < m_num_output_video_framebuffers; ++index) { m_video_buffers.Push(FrameBuffer(m_memory_replacement, 0x100, size)); @@ -316,7 +331,7 @@ bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { auto frame = m_video_frames.Pop(); if (!frame.has_value()) { - LOG_WARNING(Lib_AvPlayer, "Could get video frame. EOF reached."); + LOG_TRACE(Lib_AvPlayer, "Could get video frame. EOF reached."); return false; } @@ -351,7 +366,7 @@ bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) { auto frame = m_audio_frames.Pop(); if (!frame.has_value()) { - LOG_WARNING(Lib_AvPlayer, "Could get audio frame. EOF reached."); + LOG_TRACE(Lib_AvPlayer, "Could get audio frame. EOF reached."); return false; } @@ -537,9 +552,13 @@ AvPlayerSource::AVFramePtr AvPlayerSource::ConvertVideoFrame(const AVFrame& fram return nv12_frame; } -static void CopyNV12Data(u8* dst, const AVFrame& src) { - const auto width = Common::AlignUp(u32(src.width), 16); - const auto height = Common::AlignUp(u32(src.height), 16); +static void CopyNV12Data(u8* dst, const AVFrame& src, bool use_vdec2) { + auto width = u32(src.width); + auto height = u32(src.height); + if (!use_vdec2) { + width = Common::AlignUp(width, 16); + height = Common::AlignUp(height, 16); + } if (src.width == width) { std::memcpy(dst, src.data[0], src.width * src.height); @@ -561,7 +580,7 @@ Frame AvPlayerSource::PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame ASSERT(frame.format == AV_PIX_FMT_NV12); auto p_buffer = buffer.GetBuffer(); - CopyNV12Data(p_buffer, frame); + CopyNV12Data(p_buffer, frame, m_use_vdec2); const auto pkt_dts = u64(frame.pkt_dts) * 1000; const auto stream = m_avformat_context->streams[m_video_stream_index.value()]; @@ -570,8 +589,12 @@ Frame AvPlayerSource::PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame const auto num = time_base.num; const auto timestamp = (num != 0 && den > 1) ? (pkt_dts * num) / den : pkt_dts; - const auto width = Common::AlignUp(u32(frame.width), 16); - const auto height = Common::AlignUp(u32(frame.height), 16); + auto width = u32(frame.width); + auto height = u32(frame.height); + if (!m_use_vdec2) { + width = Common::AlignUp(width, 16); + height = Common::AlignUp(height, 16); + } return Frame{ .buffer = std::move(buffer), @@ -583,8 +606,8 @@ Frame AvPlayerSource::PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame { .video = { - .width = u32(width), - .height = u32(height), + .width = width, + .height = height, .aspect_ratio = AVRationalToF32(frame.sample_aspect_ratio), .crop_left_offset = u32(frame.crop_left), .crop_right_offset = u32(frame.crop_right + (width - frame.width)), diff --git a/src/core/libraries/avplayer/avplayer_source.h b/src/core/libraries/avplayer/avplayer_source.h index 906122142..505d74465 100644 --- a/src/core/libraries/avplayer/avplayer_source.h +++ b/src/core/libraries/avplayer/avplayer_source.h @@ -120,7 +120,7 @@ private: class AvPlayerSource { public: - AvPlayerSource(AvPlayerStateCallback& state); + AvPlayerSource(AvPlayerStateCallback& state, bool use_vdec2); ~AvPlayerSource(); bool Init(const SceAvPlayerInitData& init_data, std::string_view path); @@ -168,6 +168,7 @@ private: Frame PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame); AvPlayerStateCallback& m_state; + bool m_use_vdec2 = false; SceAvPlayerMemAllocator m_memory_replacement{}; u32 m_num_output_video_framebuffers{}; diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp index b0e498479..e66100679 100644 --- a/src/core/libraries/avplayer/avplayer_state.cpp +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -87,7 +87,12 @@ void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, SceAvPlayer return; } - // Pass other events to the game + DefaultEventCallback(opaque, event_id, 0, event_data); +} + +void AvPlayerState::DefaultEventCallback(void* opaque, SceAvPlayerEvents event_id, s32 source_id, + void* event_data) { + auto const self = reinterpret_cast(opaque); const auto callback = self->m_event_replacement.event_callback; const auto ptr = self->m_event_replacement.object_ptr; if (callback != nullptr) { @@ -102,8 +107,10 @@ AvPlayerState::AvPlayerState(const SceAvPlayerInitData& init_data) if (m_event_replacement.event_callback == nullptr || init_data.auto_start) { m_auto_start = true; m_init_data.event_replacement.event_callback = &AvPlayerState::AutoPlayEventCallback; - m_init_data.event_replacement.object_ptr = this; + } else { + m_init_data.event_replacement.event_callback = &AvPlayerState::DefaultEventCallback; } + m_init_data.event_replacement.object_ptr = this; if (init_data.default_language != nullptr) { std::memcpy(m_default_language, init_data.default_language, sizeof(m_default_language)); } @@ -123,6 +130,10 @@ AvPlayerState::~AvPlayerState() { m_event_queue.Clear(); } +void AvPlayerState::PostInit(const SceAvPlayerPostInitData& post_init_data) { + m_post_init_data = post_init_data; +} + // Called inside GAME thread bool AvPlayerState::AddSource(std::string_view path, SceAvPlayerSourceType source_type) { if (path.empty()) { @@ -137,7 +148,9 @@ bool AvPlayerState::AddSource(std::string_view path, SceAvPlayerSourceType sourc return false; } - m_up_source = std::make_unique(*this); + m_up_source = std::make_unique( + *this, m_post_init_data.video_decoder_init.decoderType.video_type == + SCE_AVPLAYER_VIDEO_DECODER_TYPE_SOFTWARE2); if (!m_up_source->Init(m_init_data, path)) { SetState(AvState::Error); m_up_source.reset(); @@ -367,8 +380,7 @@ void AvPlayerState::EmitEvent(SceAvPlayerEvents event_id, void* event_data) { const auto callback = m_init_data.event_replacement.event_callback; if (callback) { const auto ptr = m_init_data.event_replacement.object_ptr; - const auto* linker = Common::Singleton::Instance(); - linker->ExecuteGuest(callback, ptr, event_id, 0, event_data); + callback(ptr, event_id, 0, event_data); } } diff --git a/src/core/libraries/avplayer/avplayer_state.h b/src/core/libraries/avplayer/avplayer_state.h index f50d1bc1f..d106127e4 100644 --- a/src/core/libraries/avplayer/avplayer_state.h +++ b/src/core/libraries/avplayer/avplayer_state.h @@ -24,6 +24,7 @@ public: AvPlayerState(const SceAvPlayerInitData& init_data); ~AvPlayerState(); + void PostInit(const SceAvPlayerPostInitData& post_init_data); bool AddSource(std::string_view filename, SceAvPlayerSourceType source_type); s32 GetStreamCount(); bool GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info); @@ -42,6 +43,9 @@ private: static void PS4_SYSV_ABI AutoPlayEventCallback(void* handle, SceAvPlayerEvents event_id, s32 source_id, void* event_data); + static void PS4_SYSV_ABI DefaultEventCallback(void* handle, SceAvPlayerEvents event_id, + s32 source_id, void* event_data); + void OnWarning(u32 id) override; void OnError() override; void OnEOF() override; @@ -65,6 +69,7 @@ private: std::unique_ptr m_up_source; SceAvPlayerInitData m_init_data{}; + SceAvPlayerPostInitData m_post_init_data{}; SceAvPlayerEventReplacement m_event_replacement{}; bool m_auto_start{}; u8 m_default_language[4]{}; diff --git a/src/core/libraries/dialogs/error_dialog.cpp b/src/core/libraries/dialogs/error_dialog.cpp index b122e2d0a..811f2cb99 100644 --- a/src/core/libraries/dialogs/error_dialog.cpp +++ b/src/core/libraries/dialogs/error_dialog.cpp @@ -75,7 +75,7 @@ public: std::min(io.DisplaySize.y, 300.0f), }; - CentralizeWindow(); + CentralizeNextWindow(); SetNextWindowSize(window_size); SetNextWindowCollapsed(false); if (first_render || !io.NavActive) { diff --git a/src/core/libraries/dialogs/ime_dialog.cpp b/src/core/libraries/dialogs/ime_dialog.cpp index ddb1a89f8..63b52706a 100644 --- a/src/core/libraries/dialogs/ime_dialog.cpp +++ b/src/core/libraries/dialogs/ime_dialog.cpp @@ -1,28 +1,75 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include #include "common/logging/log.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "ime_dialog.h" +#include "ime_dialog_ui.h" + +static constexpr std::array MAX_X_POSITIONS = {3840.0f, 1920.0f}; +static constexpr std::array MAX_Y_POSITIONS = {2160.0f, 1080.0f}; namespace Libraries::ImeDialog { -static OrbisImeDialogStatus g_ime_dlg_status = OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_NONE; +static OrbisImeDialogStatus g_ime_dlg_status = OrbisImeDialogStatus::NONE; +static OrbisImeDialogResult g_ime_dlg_result{}; +static ImeDialogState g_ime_dlg_state{}; +static ImeDialogUi g_ime_dlg_ui; -int PS4_SYSV_ABI sceImeDialogAbort() { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - return ORBIS_OK; +static bool IsValidOption(OrbisImeDialogOption option, OrbisImeType type) { + if (False(~option & + (OrbisImeDialogOption::MULTILINE | OrbisImeDialogOption::NO_AUTO_COMPLETION))) { + return false; + } + + if (True(option & OrbisImeDialogOption::MULTILINE) && type != OrbisImeType::DEFAULT && + type != OrbisImeType::BASIC_LATIN) { + return false; + } + + if (True(option & OrbisImeDialogOption::NO_AUTO_COMPLETION) && type != OrbisImeType::NUMBER && + type != OrbisImeType::BASIC_LATIN) { + return false; + } + + return true; } -int PS4_SYSV_ABI sceImeDialogForceClose() { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogAbort() { + if (g_ime_dlg_status == OrbisImeDialogStatus::NONE) { + LOG_INFO(Lib_ImeDialog, "IME dialog not in use"); + return Error::DIALOG_NOT_IN_USE; + } + + if (g_ime_dlg_status != OrbisImeDialogStatus::RUNNING) { + LOG_INFO(Lib_ImeDialog, "IME dialog not running"); + return Error::DIALOG_NOT_RUNNING; + } + + g_ime_dlg_status = OrbisImeDialogStatus::FINISHED; + g_ime_dlg_result.endstatus = OrbisImeDialogEndStatus::ABORTED; + + return Error::OK; } -int PS4_SYSV_ABI sceImeDialogForTestFunction() { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogForceClose() { + if (g_ime_dlg_status == OrbisImeDialogStatus::NONE) { + LOG_INFO(Lib_ImeDialog, "IME dialog not in use"); + return Error::DIALOG_NOT_IN_USE; + } + + g_ime_dlg_status = OrbisImeDialogStatus::NONE; + g_ime_dlg_ui = ImeDialogUi(); + g_ime_dlg_state = ImeDialogState(); + + return Error::OK; +} + +Error PS4_SYSV_ABI sceImeDialogForTestFunction() { + return Error::INTERNAL; } int PS4_SYSV_ABI sceImeDialogGetCurrentStarState() { @@ -45,26 +92,118 @@ int PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended() { return ORBIS_OK; } -int PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result) { - result->endstatus = OrbisImeDialogEndStatus::ORBIS_IME_DIALOG_END_STATUS_OK; - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result) { + if (g_ime_dlg_status == OrbisImeDialogStatus::NONE) { + LOG_INFO(Lib_ImeDialog, "IME dialog is not running"); + return Error::DIALOG_NOT_IN_USE; + } + + if (result == nullptr) { + LOG_INFO(Lib_ImeDialog, "called with result (NULL)"); + return Error::INVALID_ADDRESS; + } + + result->endstatus = g_ime_dlg_result.endstatus; + + if (g_ime_dlg_status == OrbisImeDialogStatus::RUNNING) { + return Error::DIALOG_NOT_FINISHED; + } + + g_ime_dlg_state.CopyTextToOrbisBuffer(); + return Error::OK; } -int PS4_SYSV_ABI sceImeDialogGetStatus() { - if (g_ime_dlg_status == OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_RUNNING) { - return OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_FINISHED; +OrbisImeDialogStatus PS4_SYSV_ABI sceImeDialogGetStatus() { + if (g_ime_dlg_status == OrbisImeDialogStatus::RUNNING) { + g_ime_dlg_state.CallTextFilter(); } + return g_ime_dlg_status; } -int PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended) { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - const std::wstring_view text = L"shadPS4"; - param->maxTextLength = text.size(); - std::memcpy(param->inputTextBuffer, text.data(), text.size() * sizeof(wchar_t)); - g_ime_dlg_status = OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_RUNNING; - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended) { + if (g_ime_dlg_status != OrbisImeDialogStatus::NONE) { + LOG_INFO(Lib_ImeDialog, "IME dialog is already running"); + return Error::BUSY; + } + + if (param == nullptr) { + LOG_INFO(Lib_ImeDialog, "called with param (NULL)"); + return Error::INVALID_ADDRESS; + } + + if (!magic_enum::enum_contains(param->type)) { + LOG_INFO(Lib_ImeDialog, "Invalid param->type"); + return Error::INVALID_ADDRESS; + } + + // TODO: do correct param->option validation + // TODO: do correct param->supportedLanguages validation + + if (param->posx < 0.0f || + param->posx >= + MAX_X_POSITIONS[False(param->option & OrbisImeDialogOption::LARGE_RESOLUTION)]) { + LOG_INFO(Lib_ImeDialog, "Invalid param->posx"); + return Error::INVALID_POSX; + } + + if (param->posy < 0.0f || + param->posy >= + MAX_Y_POSITIONS[False(param->option & OrbisImeDialogOption::LARGE_RESOLUTION)]) { + LOG_INFO(Lib_ImeDialog, "Invalid param->posy"); + return Error::INVALID_POSY; + } + + if (!magic_enum::enum_contains(param->horizontalAlignment)) { + LOG_INFO(Lib_ImeDialog, "Invalid param->horizontalAlignment"); + return Error::INVALID_HORIZONTALIGNMENT; + } + + if (!magic_enum::enum_contains(param->verticalAlignment)) { + LOG_INFO(Lib_ImeDialog, "Invalid param->verticalAlignment"); + return Error::INVALID_VERTICALALIGNMENT; + } + + if (!IsValidOption(param->option, param->type)) { + LOG_INFO(Lib_ImeDialog, "Invalid param->option"); + return Error::INVALID_PARAM; + } + + if (param->inputTextBuffer == nullptr) { + LOG_INFO(Lib_ImeDialog, "Invalid param->inputTextBuffer"); + return Error::INVALID_INPUT_TEXT_BUFFER; + } + + if (extended) { + if (!magic_enum::enum_contains(extended->priority)) { + LOG_INFO(Lib_ImeDialog, "Invalid extended->priority"); + return Error::INVALID_EXTENDED; + } + + // TODO: do correct extended->option validation + + if ((extended->extKeyboardMode & 0xe3fffffc) != 0) { + LOG_INFO(Lib_ImeDialog, "Invalid extended->extKeyboardMode"); + return Error::INVALID_EXTENDED; + } + + if (extended->disableDevice > 7) { + LOG_INFO(Lib_ImeDialog, "Invalid extended->disableDevice"); + return Error::INVALID_EXTENDED; + } + } + + if (param->maxTextLength > ORBIS_IME_DIALOG_MAX_TEXT_LENGTH) { + LOG_INFO(Lib_ImeDialog, "Invalid param->maxTextLength"); + return Error::INVALID_MAX_TEXT_LENGTH; + } + + g_ime_dlg_result = {}; + g_ime_dlg_state = ImeDialogState(param, extended); + g_ime_dlg_status = OrbisImeDialogStatus::RUNNING; + g_ime_dlg_ui = ImeDialogUi(&g_ime_dlg_state, &g_ime_dlg_status, &g_ime_dlg_result); + + return Error::OK; } int PS4_SYSV_ABI sceImeDialogInitInternal() { @@ -87,10 +226,22 @@ int PS4_SYSV_ABI sceImeDialogSetPanelPosition() { return ORBIS_OK; } -int PS4_SYSV_ABI sceImeDialogTerm() { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - g_ime_dlg_status = OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_NONE; - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogTerm() { + if (g_ime_dlg_status == OrbisImeDialogStatus::NONE) { + LOG_INFO(Lib_ImeDialog, "IME dialog not in use"); + return Error::DIALOG_NOT_IN_USE; + } + + if (g_ime_dlg_status == OrbisImeDialogStatus::RUNNING) { + LOG_INFO(Lib_ImeDialog, "IME dialog is still running"); + return Error::DIALOG_NOT_FINISHED; + } + + g_ime_dlg_status = OrbisImeDialogStatus::NONE; + g_ime_dlg_ui = ImeDialogUi(); + g_ime_dlg_state = ImeDialogState(); + + return Error::OK; } void RegisterlibSceImeDialog(Core::Loader::SymbolsResolver* sym) { diff --git a/src/core/libraries/dialogs/ime_dialog.h b/src/core/libraries/dialogs/ime_dialog.h index ffe42b31a..66cf9fb93 100644 --- a/src/core/libraries/dialogs/ime_dialog.h +++ b/src/core/libraries/dialogs/ime_dialog.h @@ -3,6 +3,7 @@ #pragma once +#include "common/enum.h" #include "common/types.h" namespace Core::Loader { @@ -11,71 +12,150 @@ class SymbolsResolver; namespace Libraries::ImeDialog { -enum OrbisImeDialogStatus { - ORBIS_IME_DIALOG_STATUS_NONE = 0, - ORBIS_IME_DIALOG_STATUS_RUNNING = 1, - ORBIS_IME_DIALOG_STATUS_FINISHED = 2 +constexpr u32 ORBIS_IME_DIALOG_MAX_TEXT_LENGTH = 0x78; + +enum class Error : u32 { + OK = 0x0, + BUSY = 0x80bc0001, + NOT_OPENED = 0x80bc0002, + NO_MEMORY = 0x80bc0003, + CONNECTION_FAILED = 0x80bc0004, + TOO_MANY_REQUESTS = 0x80bc0005, + INVALID_TEXT = 0x80bc0006, + EVENT_OVERFLOW = 0x80bc0007, + NOT_ACTIVE = 0x80bc0008, + IME_SUSPENDING = 0x80bc0009, + DEVICE_IN_USE = 0x80bc000a, + INVALID_USER_ID = 0x80bc0010, + INVALID_TYPE = 0x80bc0011, + INVALID_SUPPORTED_LANGUAGES = 0x80bc0012, + INVALID_ENTER_LABEL = 0x80bc0013, + INVALID_INPUT_METHOD = 0x80bc0014, + INVALID_OPTION = 0x80bc0015, + INVALID_MAX_TEXT_LENGTH = 0x80bc0016, + INVALID_INPUT_TEXT_BUFFER = 0x80bc0017, + INVALID_POSX = 0x80bc0018, + INVALID_POSY = 0x80bc0019, + INVALID_HORIZONTALIGNMENT = 0x80bc001a, + INVALID_VERTICALALIGNMENT = 0x80bc001b, + INVALID_EXTENDED = 0x80bc001c, + INVALID_KEYBOARD_TYPE = 0x80bc001d, + INVALID_WORK = 0x80bc0020, + INVALID_ARG = 0x80bc0021, + INVALID_HANDLER = 0x80bc0022, + NO_RESOURCE_ID = 0x80bc0023, + INVALID_MODE = 0x80bc0024, + INVALID_PARAM = 0x80bc0030, + INVALID_ADDRESS = 0x80bc0031, + INVALID_RESERVED = 0x80bc0032, + INVALID_TIMING = 0x80bc0033, + INTERNAL = 0x80bc00ff, + DIALOG_INVALID_TITLE = 0x80bc0101, + DIALOG_NOT_RUNNING = 0x80bc0105, + DIALOG_NOT_FINISHED = 0x80bc0106, + DIALOG_NOT_IN_USE = 0x80bc0107, }; -enum OrbisImeDialogEndStatus { - ORBIS_IME_DIALOG_END_STATUS_OK = 0, - ORBIS_IME_DIALOG_END_STATUS_USER_CANCELED = 1, - ORBIS_IME_DIALOG_END_STATUS_ABORTED = 2 +enum class OrbisImeDialogStatus : u32 { + NONE = 0, + RUNNING = 1, + FINISHED = 2, }; -struct OrbisImeDialogResult { - OrbisImeDialogEndStatus endstatus; - s32 reserved[12]; +enum class OrbisImeDialogEndStatus : u32 { + OK = 0, + USER_CANCELED = 1, + ABORTED = 2, }; -enum OrbisImeType { - ORBIS_IME_TYPE_DEFAULT = 0, - ORBIS_IME_TYPE_BASIC_LATIN = 1, - ORBIS_IME_TYPE_URL = 2, - ORBIS_IME_TYPE_MAIL = 3, - ORBIS_IME_TYPE_NUMBER = 4 +enum class OrbisImeType : u32 { + DEFAULT = 0, + BASIC_LATIN = 1, + URL = 2, + MAIL = 3, + NUMBER = 4, }; -enum OrbisImeEnterLabel { - ORBIS_IME_ENTER_LABEL_DEFAULT = 0, - ORBIS_IME_ENTER_LABEL_SEND = 1, - ORBIS_IME_ENTER_LABEL_SEARCH = 2, - ORBIS_IME_ENTER_LABEL_GO = 3 -}; -enum OrbiImeInputMethod { ORBIS_IME_INPUT_METHOD_DEFAULT = 0 }; - -typedef int (*OrbisImeTextFilter)(wchar_t* outText, u32* outTextLength, const wchar_t* srcText, - u32 srcTextLength); - -enum OrbisImeHorizontalAlignment { - ORBIS_IME_HALIGN_LEFT = 0, - ORBIS_IME_HALIGN_CENTER = 1, - ORBIS_IME_HALIGN_RIGHT = 2 +enum class OrbisImeEnterLabel : u32 { + DEFAULT = 0, + SEND = 1, + SEARCH = 2, + GO = 3, }; -enum OrbisImeVerticalAlignment { - ORBIS_IME_VALIGN_TOP = 0, - ORBIS_IME_VALIGN_CENTER = 1, - ORBIS_IME_VALIGN_BOTTOM = 2 +enum class OrbisImeDialogOption : u32 { + DEFAULT = 0, + MULTILINE = 1, + NO_AUTO_CORRECTION = 2, + NO_AUTO_COMPLETION = 4, + // TODO: Document missing options + LARGE_RESOLUTION = 1024, }; -struct OrbisImeDialogParam { - s32 userId; - OrbisImeType type; - u64 supportedLanguages; - OrbisImeEnterLabel enterLabel; - OrbiImeInputMethod inputMethod; - OrbisImeTextFilter filter; - u32 option; - u32 maxTextLength; - wchar_t* inputTextBuffer; - float posx; - float posy; - OrbisImeHorizontalAlignment horizontalAlignment; - OrbisImeVerticalAlignment verticalAlignment; - const wchar_t* placeholder; - const wchar_t* title; - s8 reserved[16]; +DECLARE_ENUM_FLAG_OPERATORS(OrbisImeDialogOption) + +enum class OrbisImeInputMethod : u32 { + DEFAULT = 0, +}; + +enum class OrbisImeHorizontalAlignment : u32 { + LEFT = 0, + CENTER = 1, + RIGHT = 2, +}; + +enum class OrbisImeVerticalAlignment : u32 { + TOP = 0, + CENTER = 1, + BOTTOM = 2, +}; + +enum class OrbisImePanelPriority : u32 { + DEFAULT = 0, + ALPHABET = 1, + SYMBOL = 2, + ACCENT = 3, +}; + +enum class OrbisImeKeyboardType : u32 { + NONE = 0, + DANISH = 1, + GERMAN = 2, + GERMAN_SW = 3, + ENGLISH_US = 4, + ENGLISH_GB = 5, + SPANISH = 6, + SPANISH_LA = 7, + FINNISH = 8, + FRENCH = 9, + FRENCH_BR = 10, + FRENCH_CA = 11, + FRENCH_SW = 12, + ITALIAN = 13, + DUTCH = 14, + NORWEGIAN = 15, + POLISH = 16, + PORTUGUESE_BR = 17, + PORTUGUESE_PT = 18, + RUSSIAN = 19, + SWEDISH = 20, + TURKISH = 21, + JAPANESE_ROMAN = 22, + JAPANESE_KANA = 23, + KOREAN = 24, + SM_CHINESE = 25, + TR_CHINESE_ZY = 26, + TR_CHINESE_PY_HK = 27, + TR_CHINESE_PY_TW = 28, + TR_CHINESE_CG = 29, + ARABIC_AR = 30, + THAI = 31, + CZECH = 32, + GREEK = 33, + INDONESIAN = 34, + VIETNAMESE = 35, + ROMANIAN = 36, + HUNGARIAN = 37, }; struct OrbisImeColor { @@ -85,57 +165,14 @@ struct OrbisImeColor { u8 a; }; -enum OrbisImePanelPriority { - ORBIS_IME_PANEL_PRIORITY_DEFAULT = 0, - ORBIS_IME_PANEL_PRIORITY_ALPHABET = 1, - ORBIS_IME_PANEL_PRIORITY_SYMBOL = 2, - ORBIS_IME_PANEL_PRIORITY_ACCENT = 3 -}; - -enum OrbisImeKeyboardType { - ORBIS_IME_KEYBOARD_TYPE_NONE = 0, - ORBIS_IME_KEYBOARD_TYPE_DANISH = 1, - ORBIS_IME_KEYBOARD_TYPE_GERMAN = 2, - ORBIS_IME_KEYBOARD_TYPE_GERMAN_SW = 3, - ORBIS_IME_KEYBOARD_TYPE_ENGLISH_US = 4, - ORBIS_IME_KEYBOARD_TYPE_ENGLISH_GB = 5, - ORBIS_IME_KEYBOARD_TYPE_SPANISH = 6, - ORBIS_IME_KEYBOARD_TYPE_SPANISH_LA = 7, - ORBIS_IME_KEYBOARD_TYPE_FINNISH = 8, - ORBIS_IME_KEYBOARD_TYPE_FRENCH = 9, - ORBIS_IME_KEYBOARD_TYPE_FRENCH_BR = 10, - ORBIS_IME_KEYBOARD_TYPE_FRENCH_CA = 11, - ORBIS_IME_KEYBOARD_TYPE_FRENCH_SW = 12, - ORBIS_IME_KEYBOARD_TYPE_ITALIAN = 13, - ORBIS_IME_KEYBOARD_TYPE_DUTCH = 14, - ORBIS_IME_KEYBOARD_TYPE_NORWEGIAN = 15, - ORBIS_IME_KEYBOARD_TYPE_POLISH = 16, - ORBIS_IME_KEYBOARD_TYPE_PORTUGUESE_BR = 17, - ORBIS_IME_KEYBOARD_TYPE_PORTUGUESE_PT = 18, - ORBIS_IME_KEYBOARD_TYPE_RUSSIAN = 19, - ORBIS_IME_KEYBOARD_TYPE_SWEDISH = 20, - ORBIS_IME_KEYBOARD_TYPE_TURKISH = 21, - ORBIS_IME_KEYBOARD_TYPE_JAPANESE_ROMAN = 22, - ORBIS_IME_KEYBOARD_TYPE_JAPANESE_KANA = 23, - ORBIS_IME_KEYBOARD_TYPE_KOREAN = 24, - ORBIS_IME_KEYBOARD_TYPE_SM_CHINESE = 25, - ORBIS_IME_KEYBOARD_TYPE_TR_CHINESE_ZY = 26, - ORBIS_IME_KEYBOARD_TYPE_TR_CHINESE_PY_HK = 27, - ORBIS_IME_KEYBOARD_TYPE_TR_CHINESE_PY_TW = 28, - ORBIS_IME_KEYBOARD_TYPE_TR_CHINESE_CG = 29, - ORBIS_IME_KEYBOARD_TYPE_ARABIC_AR = 30, - ORBIS_IME_KEYBOARD_TYPE_THAI = 31, - ORBIS_IME_KEYBOARD_TYPE_CZECH = 32, - ORBIS_IME_KEYBOARD_TYPE_GREEK = 33, - ORBIS_IME_KEYBOARD_TYPE_INDONESIAN = 34, - ORBIS_IME_KEYBOARD_TYPE_VIETNAMESE = 35, - ORBIS_IME_KEYBOARD_TYPE_ROMANIAN = 36, - ORBIS_IME_KEYBOARD_TYPE_HUNGARIAN = 37 +struct OrbisImeDialogResult { + OrbisImeDialogEndStatus endstatus; + s32 reserved[12]; }; struct OrbisImeKeycode { u16 keycode; - wchar_t character; + char16_t character; u32 status; OrbisImeKeyboardType type; s32 userId; @@ -143,11 +180,34 @@ struct OrbisImeKeycode { u64 timestamp; }; -typedef int (*OrbisImeExtKeyboardFilter)(const OrbisImeKeycode* srcKeycode, u16* outKeycode, - u32* outStatus, void* reserved); +typedef PS4_SYSV_ABI int (*OrbisImeTextFilter)(char16_t* outText, u32* outTextLength, + const char16_t* srcText, u32 srcTextLength); + +typedef PS4_SYSV_ABI int (*OrbisImeExtKeyboardFilter)(const OrbisImeKeycode* srcKeycode, + u16* outKeycode, u32* outStatus, + void* reserved); + +struct OrbisImeDialogParam { + s32 userId; + OrbisImeType type; + u64 supportedLanguages; + OrbisImeEnterLabel enterLabel; + OrbisImeInputMethod inputMethod; + OrbisImeTextFilter filter; + OrbisImeDialogOption option; + u32 maxTextLength; + char16_t* inputTextBuffer; + float posx; + float posy; + OrbisImeHorizontalAlignment horizontalAlignment; + OrbisImeVerticalAlignment verticalAlignment; + const char16_t* placeholder; + const char16_t* title; + s8 reserved[16]; +}; struct OrbisImeParamExtended { - u32 option; + u32 option; // OrbisImeDialogOptionExtended OrbisImeColor colorBase; OrbisImeColor colorLine; OrbisImeColor colorTextField; @@ -165,21 +225,21 @@ struct OrbisImeParamExtended { int8_t reserved[60]; }; -int PS4_SYSV_ABI sceImeDialogAbort(); -int PS4_SYSV_ABI sceImeDialogForceClose(); -int PS4_SYSV_ABI sceImeDialogForTestFunction(); +Error PS4_SYSV_ABI sceImeDialogAbort(); +Error PS4_SYSV_ABI sceImeDialogForceClose(); +Error PS4_SYSV_ABI sceImeDialogForTestFunction(); int PS4_SYSV_ABI sceImeDialogGetCurrentStarState(); int PS4_SYSV_ABI sceImeDialogGetPanelPositionAndForm(); int PS4_SYSV_ABI sceImeDialogGetPanelSize(); int PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended(); -int PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result); -/*OrbisImeDialogStatus*/ int PS4_SYSV_ABI sceImeDialogGetStatus(); -int PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended); +Error PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result); +OrbisImeDialogStatus PS4_SYSV_ABI sceImeDialogGetStatus(); +Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended); int PS4_SYSV_ABI sceImeDialogInitInternal(); int PS4_SYSV_ABI sceImeDialogInitInternal2(); int PS4_SYSV_ABI sceImeDialogInitInternal3(); int PS4_SYSV_ABI sceImeDialogSetPanelPosition(); -int PS4_SYSV_ABI sceImeDialogTerm(); +Error PS4_SYSV_ABI sceImeDialogTerm(); void RegisterlibSceImeDialog(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::ImeDialog \ No newline at end of file diff --git a/src/core/libraries/dialogs/ime_dialog_ui.cpp b/src/core/libraries/dialogs/ime_dialog_ui.cpp new file mode 100644 index 000000000..9d50d2fbb --- /dev/null +++ b/src/core/libraries/dialogs/ime_dialog_ui.cpp @@ -0,0 +1,390 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include + +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/singleton.h" +#include "core/libraries/dialogs/ime_dialog.h" +#include "core/libraries/dialogs/ime_dialog_ui.h" +#include "core/linker.h" +#include "imgui/imgui_std.h" + +using namespace ImGui; + +static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f}; + +namespace Libraries::ImeDialog { + +ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param, + const OrbisImeParamExtended* extended) { + if (!param) + return; + + userId = param->userId; + is_multiLine = True(param->option & OrbisImeDialogOption::MULTILINE); + is_numeric = param->type == OrbisImeType::NUMBER; + type = param->type; + enter_label = param->enterLabel; + text_filter = param->filter; + keyboard_filter = extended ? extended->extKeyboardFilter : nullptr; + max_text_length = param->maxTextLength; + text_buffer = param->inputTextBuffer; + + if (param->title) { + std::size_t title_len = std::char_traits::length(param->title); + title.resize(title_len * 4 + 1); + title[title_len * 4] = '\0'; + + if (!ConvertOrbisToUTF8(param->title, title_len, &title[0], title_len * 4)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert title to utf8 encoding"); + } + } + + if (param->placeholder) { + std::size_t placeholder_len = std::char_traits::length(param->placeholder); + placeholder.resize(placeholder_len * 4 + 1); + placeholder[placeholder_len * 4] = '\0'; + + if (!ConvertOrbisToUTF8(param->placeholder, placeholder_len, &placeholder[0], + placeholder_len * 4)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert placeholder to utf8 encoding"); + } + } + + std::size_t text_len = std::char_traits::length(text_buffer); + if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(), + ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding"); + } +} + +ImeDialogState::ImeDialogState(ImeDialogState&& other) noexcept + : input_changed(other.input_changed), userId(other.userId), is_multiLine(other.is_multiLine), + is_numeric(other.is_numeric), type(other.type), enter_label(other.enter_label), + text_filter(other.text_filter), keyboard_filter(other.keyboard_filter), + max_text_length(other.max_text_length), text_buffer(other.text_buffer), + title(std::move(other.title)), placeholder(std::move(other.placeholder)), + current_text(other.current_text) { + + other.text_buffer = nullptr; +} + +ImeDialogState& ImeDialogState::operator=(ImeDialogState&& other) { + if (this != &other) { + input_changed = other.input_changed; + userId = other.userId; + is_multiLine = other.is_multiLine; + is_numeric = other.is_numeric; + type = other.type; + enter_label = other.enter_label; + text_filter = other.text_filter; + keyboard_filter = other.keyboard_filter; + max_text_length = other.max_text_length; + text_buffer = other.text_buffer; + title = std::move(other.title); + placeholder = std::move(other.placeholder); + current_text = other.current_text; + + other.text_buffer = nullptr; + } + + return *this; +} + +bool ImeDialogState::CopyTextToOrbisBuffer() { + if (!text_buffer) { + return false; + } + + return ConvertUTF8ToOrbis(current_text.begin(), current_text.capacity(), text_buffer, + max_text_length); +} + +bool ImeDialogState::CallTextFilter() { + if (!text_filter || !input_changed) { + return true; + } + + input_changed = false; + + char16_t src_text[ORBIS_IME_DIALOG_MAX_TEXT_LENGTH + 1] = {0}; + u32 src_text_length = current_text.size(); + char16_t out_text[ORBIS_IME_DIALOG_MAX_TEXT_LENGTH + 1] = {0}; + u32 out_text_length = ORBIS_IME_DIALOG_MAX_TEXT_LENGTH; + + if (!ConvertUTF8ToOrbis(current_text.begin(), src_text_length, src_text, + ORBIS_IME_DIALOG_MAX_TEXT_LENGTH)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert text to orbis encoding"); + return false; + } + + auto* linker = Common::Singleton::Instance(); + int ret = + linker->ExecuteGuest(text_filter, out_text, &out_text_length, src_text, src_text_length); + + if (ret != 0) { + return false; + } + + if (!ConvertOrbisToUTF8(out_text, out_text_length, current_text.begin(), + ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding"); + return false; + } + + return true; +} + +bool ImeDialogState::CallKeyboardFilter(const OrbisImeKeycode* src_keycode, u16* out_keycode, + u32* out_status) { + if (!keyboard_filter) { + return true; + } + + auto* linker = Common::Singleton::Instance(); + int ret = linker->ExecuteGuest(keyboard_filter, src_keycode, out_keycode, out_status, nullptr); + + return ret == 0; +} + +bool ImeDialogState::ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t orbis_text_len, + char* utf8_text, std::size_t utf8_text_len) { + + std::fill(utf8_text, utf8_text + utf8_text_len, '\0'); + const ImWchar* orbis_text_ptr = reinterpret_cast(orbis_text); + ImTextStrToUtf8(utf8_text, utf8_text_len, orbis_text_ptr, orbis_text_ptr + orbis_text_len); + + return true; +} + +bool ImeDialogState::ConvertUTF8ToOrbis(const char* utf8_text, std::size_t utf8_text_len, + char16_t* orbis_text, std::size_t orbis_text_len) { + + std::fill(orbis_text, orbis_text + orbis_text_len, u'\0'); + ImTextStrFromUtf8(reinterpret_cast(orbis_text), orbis_text_len, utf8_text, nullptr); + + return true; +} + +ImeDialogUi::ImeDialogUi(ImeDialogState* state, OrbisImeDialogStatus* status, + OrbisImeDialogResult* result) + : state(state), status(status), result(result) { + + if (state && *status == OrbisImeDialogStatus::RUNNING) { + AddLayer(this); + } +} + +ImeDialogUi::~ImeDialogUi() { + std::scoped_lock lock(draw_mutex); + + Free(); +} + +ImeDialogUi::ImeDialogUi(ImeDialogUi&& other) noexcept + : state(other.state), status(other.status), result(other.result), + first_render(other.first_render) { + + std::scoped_lock lock(draw_mutex, other.draw_mutex); + other.state = nullptr; + other.status = nullptr; + other.result = nullptr; + + if (state && *status == OrbisImeDialogStatus::RUNNING) { + AddLayer(this); + } +} + +ImeDialogUi& ImeDialogUi::operator=(ImeDialogUi&& other) { + std::scoped_lock lock(draw_mutex, other.draw_mutex); + Free(); + + state = other.state; + status = other.status; + result = other.result; + first_render = other.first_render; + other.state = nullptr; + other.status = nullptr; + other.result = nullptr; + + if (state && *status == OrbisImeDialogStatus::RUNNING) { + AddLayer(this); + } + + return *this; +} + +void ImeDialogUi::Free() { + RemoveLayer(this); +} + +void ImeDialogUi::Draw() { + std::unique_lock lock{draw_mutex}; + + if (!state) { + return; + } + + if (!status || *status != OrbisImeDialogStatus::RUNNING) { + return; + } + + const auto& ctx = *GetCurrentContext(); + const auto& io = ctx.IO; + + ImVec2 window_size; + + if (state->is_multiLine) { + window_size = {500.0f, 300.0f}; + } else { + window_size = {500.0f, 150.0f}; + } + + CentralizeNextWindow(); + SetNextWindowSize(window_size); + SetNextWindowCollapsed(false); + + if (first_render || !io.NavActive) { + SetNextWindowFocus(); + } + + if (Begin("IME Dialog##ImeDialog", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) { + DrawPrettyBackground(); + + if (!state->title.empty()) { + SetWindowFontScale(1.7f); + TextUnformatted(state->title.data()); + SetWindowFontScale(1.0f); + } + + if (state->is_multiLine) { + DrawMultiLineInputText(); + } else { + DrawInputText(); + } + + SetCursorPosY(GetCursorPosY() + 10.0f); + + const char* button_text; + + switch (state->enter_label) { + case OrbisImeEnterLabel::GO: + button_text = "Go##ImeDialogOK"; + break; + case OrbisImeEnterLabel::SEARCH: + button_text = "Search##ImeDialogOK"; + break; + case OrbisImeEnterLabel::SEND: + button_text = "Send##ImeDialogOK"; + break; + case OrbisImeEnterLabel::DEFAULT: + default: + button_text = "OK##ImeDialogOK"; + break; + } + + float button_spacing = 10.0f; + float total_button_width = BUTTON_SIZE.x * 2 + button_spacing; + float button_start_pos = (window_size.x - total_button_width) / 2.0f; + + SetCursorPosX(button_start_pos); + + if (Button(button_text, BUTTON_SIZE) || + (!state->is_multiLine && IsKeyPressed(ImGuiKey_Enter))) { + *status = OrbisImeDialogStatus::FINISHED; + result->endstatus = OrbisImeDialogEndStatus::OK; + } + + SameLine(0.0f, button_spacing); + + if (Button("Cancel##ImeDialogCancel", BUTTON_SIZE)) { + *status = OrbisImeDialogStatus::FINISHED; + result->endstatus = OrbisImeDialogEndStatus::USER_CANCELED; + } + } + End(); + + first_render = false; +} + +void ImeDialogUi::DrawInputText() { + ImVec2 input_size = {GetWindowWidth() - 40.0f, 0.0f}; + SetCursorPosX(20.0f); + if (first_render) { + SetKeyboardFocusHere(); + } + const char* placeholder = state->placeholder.empty() ? nullptr : state->placeholder.data(); + if (InputTextEx("##ImeDialogInput", placeholder, state->current_text.begin(), + state->max_text_length, input_size, ImGuiInputTextFlags_CallbackCharFilter, + InputTextCallback, this)) { + state->input_changed = true; + } +} + +void ImeDialogUi::DrawMultiLineInputText() { + ImVec2 input_size = {GetWindowWidth() - 40.0f, 200.0f}; + SetCursorPosX(20.0f); + ImGuiInputTextFlags flags = ImGuiInputTextFlags_CallbackCharFilter | + static_cast(ImGuiInputTextFlags_Multiline); + if (first_render) { + SetKeyboardFocusHere(); + } + const char* placeholder = state->placeholder.empty() ? nullptr : state->placeholder.data(); + if (InputTextEx("##ImeDialogInput", placeholder, state->current_text.begin(), + state->max_text_length, input_size, flags, InputTextCallback, this)) { + state->input_changed = true; + } +} + +int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) { + ImeDialogUi* ui = static_cast(data->UserData); + + ASSERT(ui); + + // Should we filter punctuation? + if (ui->state->is_numeric && (data->EventChar < '0' || data->EventChar > '9') && + data->EventChar != '\b' && data->EventChar != ',' && data->EventChar != '.') { + return 1; + } + + if (!ui->state->keyboard_filter) { + return 0; + } + + // ImGui encodes ImWchar32 as multi-byte UTF-8 characters + char* event_char = reinterpret_cast(&data->EventChar); + + // Call the keyboard filter + OrbisImeKeycode src_keycode = { + .keycode = 0, + .character = 0, + .status = 1, // ??? 1 = key pressed, 0 = key released + .type = OrbisImeKeyboardType::ENGLISH_US, // TODO set this to the correct value (maybe use + // the current language?) + .userId = ui->state->userId, + .resourceId = 0, + .timestamp = 0}; + + if (!ui->state->ConvertUTF8ToOrbis(event_char, 4, &src_keycode.character, 1)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert orbis char to utf8"); + return 0; + } + src_keycode.keycode = src_keycode.character; // TODO set this to the correct value + + u16 out_keycode; + u32 out_status; + + ui->state->CallKeyboardFilter(&src_keycode, &out_keycode, &out_status); + + // TODO. set the keycode + + return 0; +} + +} // namespace Libraries::ImeDialog \ No newline at end of file diff --git a/src/core/libraries/dialogs/ime_dialog_ui.h b/src/core/libraries/dialogs/ime_dialog_ui.h new file mode 100644 index 000000000..96c83954a --- /dev/null +++ b/src/core/libraries/dialogs/ime_dialog_ui.h @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include "common/cstring.h" +#include "common/types.h" +#include "core/libraries/dialogs/ime_dialog.h" +#include "imgui/imgui_layer.h" + +namespace Libraries::ImeDialog { + +class ImeDialogUi; + +class ImeDialogState final { + friend ImeDialogUi; + + bool input_changed = false; + + s32 userId{}; + bool is_multiLine{}; + bool is_numeric{}; + OrbisImeType type{}; + OrbisImeEnterLabel enter_label{}; + OrbisImeTextFilter text_filter{}; + OrbisImeExtKeyboardFilter keyboard_filter{}; + u32 max_text_length{}; + char16_t* text_buffer{}; + std::vector title; + std::vector placeholder; + + // A character can hold up to 4 bytes in UTF-8 + Common::CString current_text; + +public: + ImeDialogState(const OrbisImeDialogParam* param = nullptr, + const OrbisImeParamExtended* extended = nullptr); + ImeDialogState(const ImeDialogState& other) = delete; + ImeDialogState(ImeDialogState&& other) noexcept; + ImeDialogState& operator=(ImeDialogState&& other); + + bool CopyTextToOrbisBuffer(); + bool CallTextFilter(); + +private: + bool CallKeyboardFilter(const OrbisImeKeycode* src_keycode, u16* out_keycode, u32* out_status); + + bool ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t orbis_text_len, char* utf8_text, + std::size_t native_text_len); + bool ConvertUTF8ToOrbis(const char* native_text, std::size_t utf8_text_len, + char16_t* orbis_text, std::size_t orbis_text_len); +}; + +class ImeDialogUi final : public ImGui::Layer { + ImeDialogState* state{}; + OrbisImeDialogStatus* status{}; + OrbisImeDialogResult* result{}; + + bool first_render = true; + std::mutex draw_mutex; + +public: + explicit ImeDialogUi(ImeDialogState* state = nullptr, OrbisImeDialogStatus* status = nullptr, + OrbisImeDialogResult* result = nullptr); + ~ImeDialogUi() override; + ImeDialogUi(const ImeDialogUi& other) = delete; + ImeDialogUi(ImeDialogUi&& other) noexcept; + ImeDialogUi& operator=(ImeDialogUi&& other); + + void Draw() override; + +private: + void Free(); + + void DrawInputText(); + void DrawMultiLineInputText(); + + static int InputTextCallback(ImGuiInputTextCallbackData* data); +}; + +} // namespace Libraries::ImeDialog diff --git a/src/core/libraries/error_codes.h b/src/core/libraries/error_codes.h index b9896b6c3..dae285664 100644 --- a/src/core/libraries/error_codes.h +++ b/src/core/libraries/error_codes.h @@ -438,6 +438,7 @@ constexpr int ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT = 0x8096000A; // SystemService library constexpr int ORBIS_SYSTEM_SERVICE_ERROR_PARAMETER = 0x80A10003; +constexpr int ORBIS_SYSTEM_SERVICE_ERROR_NO_EVENT = 0x80A10004; // NpTrophy library constexpr int ORBIS_NP_TROPHY_ERROR_UNKNOWN = 0x80551600; @@ -498,4 +499,12 @@ constexpr int ORBIS_AVPLAYER_ERROR_INFO_OTHER_ENCRY = 0x806A00BF; // AppContent library constexpr int ORBIS_APP_CONTENT_ERROR_PARAMETER = 0x80D90002; constexpr int ORBIS_APP_CONTENT_ERROR_DRM_NO_ENTITLEMENT = 0x80D90007; -constexpr int ORBIS_APP_CONTENT_ERROR_NOT_FOUND = 0x80D90005; \ No newline at end of file +constexpr int ORBIS_APP_CONTENT_ERROR_NOT_FOUND = 0x80D90005; + +// Fiber library +constexpr int ORBIS_FIBER_ERROR_NULL = 0x80590001; +constexpr int ORBIS_FIBER_ERROR_ALIGNMENT = 0x80590002; +constexpr int ORBIS_FIBER_ERROR_RANGE = 0x80590003; +constexpr int ORBIS_FIBER_ERROR_INVALID = 0x80590004; +constexpr int ORBIS_FIBER_ERROR_PERMISSION = 0x80590005; +constexpr int ORBIS_FIBER_ERROR_STATE = 0x80590006; \ No newline at end of file diff --git a/src/core/libraries/fiber/fiber.cpp b/src/core/libraries/fiber/fiber.cpp new file mode 100644 index 000000000..bd1575dda --- /dev/null +++ b/src/core/libraries/fiber/fiber.cpp @@ -0,0 +1,284 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "fiber.h" + +#include "common/logging/log.h" +#include "common/singleton.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" +#include "core/linker.h" + +#ifdef _WIN64 +#include +#endif + +namespace Libraries::Fiber { + +constexpr static u64 kFiberSignature = 0x054ad954; + +thread_local SceFiber* gCurrentFiber = nullptr; +thread_local void* gFiberThread = nullptr; + +void FiberEntry(void* param) { + SceFiber* fiber = static_cast(param); + u64 argRun = 0; + u64 argRet = 0; + + gCurrentFiber = fiber; + + if (fiber->pArgRun != nullptr) { + argRun = *fiber->pArgRun; + } + + const auto* linker = Common::Singleton::Instance(); + linker->ExecuteGuest(fiber->entry, fiber->argOnInitialize, argRun); + + UNREACHABLE(); +} + +s32 PS4_SYSV_ABI sceFiberInitialize(SceFiber* fiber, const char* name, SceFiberEntry entry, + u64 argOnInitialize, void* addrContext, u64 sizeContext, + const SceFiberOptParam* optParam) { + LOG_INFO(Lib_Fiber, "called: name = {}", name); + + if (!fiber || !name || !entry) { + return ORBIS_FIBER_ERROR_NULL; + } + + fiber->signature = kFiberSignature; + + fiber->entry = entry; + fiber->argOnInitialize = argOnInitialize; + + fiber->argRun = 0; + fiber->pArgRun = &fiber->argRun; + fiber->argReturn = 0; + fiber->pArgReturn = &fiber->argReturn; + + fiber->sizeContext = sizeContext; + + fiber->state = FiberState::Init; +#ifdef _WIN64 + fiber->handle = CreateFiber(sizeContext, FiberEntry, fiber); +#else + UNREACHABLE_MSG("Missing implementation"); +#endif + strncpy(fiber->name, name, ORBIS_FIBER_MAX_NAME_LENGTH); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberOptParamInitialize(SceFiberOptParam* optParam) { + LOG_ERROR(Lib_Fiber, "called"); + + if (!optParam) { + return ORBIS_FIBER_ERROR_NULL; + } + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberFinalize(SceFiber* fiber) { + LOG_TRACE(Lib_Fiber, "called"); + + if (!fiber) { + return ORBIS_FIBER_ERROR_NULL; + } + if ((u64)fiber % 8 != 0) { + return ORBIS_FIBER_ERROR_ALIGNMENT; + } + if (fiber->signature != kFiberSignature) { + return ORBIS_FIBER_ERROR_INVALID; + } + if (fiber->state != FiberState::Run) { + return ORBIS_FIBER_ERROR_STATE; + } + + fiber->signature = 0; + fiber->state = FiberState::None; + +#ifdef _WIN64 + DeleteFiber(fiber->handle); +#else + UNREACHABLE_MSG("Missing implementation"); +#endif + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberRun(SceFiber* fiber, u64 argOnRunTo, u64* argOnReturn) { + LOG_TRACE(Lib_Fiber, "called"); + + if (!fiber) { + return ORBIS_FIBER_ERROR_NULL; + } + if ((u64)fiber % 8 != 0) { + return ORBIS_FIBER_ERROR_ALIGNMENT; + } + if (fiber->signature != kFiberSignature) { + return ORBIS_FIBER_ERROR_INVALID; + } + if (fiber->state == FiberState::Run) { + return ORBIS_FIBER_ERROR_STATE; + } + + if (gFiberThread == nullptr) { +#ifdef _WIN64 + gFiberThread = ConvertThreadToFiber(nullptr); +#else + UNREACHABLE_MSG("Missing implementation"); +#endif + } + + gCurrentFiber = fiber; + + if (fiber->pArgRun != nullptr) { + *fiber->pArgRun = argOnRunTo; + } + + fiber->pArgReturn = argOnReturn; + fiber->state = FiberState::Run; +#ifdef _WIN64 + SwitchToFiber(fiber->handle); +#else + UNREACHABLE_MSG("Missing implementation"); +#endif + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberSwitch(SceFiber* fiber, u64 argOnRunTo, u64* argOnRun) { + LOG_TRACE(Lib_Fiber, "called"); + + if (!fiber) { + return ORBIS_FIBER_ERROR_NULL; + } + if ((u64)fiber % 8 != 0) { + return ORBIS_FIBER_ERROR_ALIGNMENT; + } + if (fiber->signature != kFiberSignature) { + return ORBIS_FIBER_ERROR_INVALID; + } + if (gCurrentFiber == nullptr) { + return ORBIS_FIBER_ERROR_PERMISSION; + } + if (fiber->state == FiberState::Run) { + return ORBIS_FIBER_ERROR_STATE; + } + + gCurrentFiber->state = FiberState::Suspend; + + // TODO: argOnRun + + *fiber->pArgRun = argOnRunTo; + fiber->state = FiberState::Run; + + gCurrentFiber = fiber; +#ifdef _WIN64 + SwitchToFiber(fiber->handle); +#else + UNREACHABLE_MSG("Missing implementation"); +#endif + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberGetSelf(SceFiber** fiber) { + LOG_TRACE(Lib_Fiber, "called"); + + if (!fiber || !gCurrentFiber) { + return ORBIS_FIBER_ERROR_NULL; + } + if (gCurrentFiber->signature != kFiberSignature) { + return ORBIS_FIBER_ERROR_PERMISSION; + } + + *fiber = gCurrentFiber; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberReturnToThread(u64 argOnReturn, u64* argOnRun) { + LOG_TRACE(Lib_Fiber, "called"); + + if (gCurrentFiber->signature != kFiberSignature) { + return ORBIS_FIBER_ERROR_PERMISSION; + } + + if (gCurrentFiber->pArgReturn != nullptr) { + *gCurrentFiber->pArgReturn = argOnReturn; + } + + // TODO: argOnRun + gCurrentFiber->state = FiberState::Suspend; + gCurrentFiber = nullptr; +#ifdef _WIN64 + SwitchToFiber(gFiberThread); +#else + UNREACHABLE_MSG("Missing implementation"); +#endif + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberGetInfo(SceFiber* fiber, SceFiberInfo* fiberInfo) { + LOG_INFO(Lib_Fiber, "called"); + + if (!fiber || !fiberInfo) { + return ORBIS_FIBER_ERROR_NULL; + } + + fiberInfo->entry = fiber->entry; + fiberInfo->argOnInitialize = fiber->argOnInitialize; + fiberInfo->addrContext = nullptr; + fiberInfo->sizeContext = fiber->sizeContext; + fiberInfo->sizeContextMargin = 0; + + strncpy(fiberInfo->name, fiber->name, ORBIS_FIBER_MAX_NAME_LENGTH); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberStartContextSizeCheck(u32 flags) { + LOG_ERROR(Lib_Fiber, "called"); + + if (flags != 0) { + return ORBIS_FIBER_ERROR_INVALID; + } + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberStopContextSizeCheck() { + LOG_ERROR(Lib_Fiber, "called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberRename(SceFiber* fiber, const char* name) { + LOG_INFO(Lib_Fiber, "called, name = {}", name); + + if (!fiber || !name) { + return ORBIS_FIBER_ERROR_NULL; + } + if ((u64)fiber % 8 != 0) { + return ORBIS_FIBER_ERROR_ALIGNMENT; + } + + strncpy(fiber->name, name, ORBIS_FIBER_MAX_NAME_LENGTH); + return ORBIS_OK; +} + +void RegisterlibSceFiber(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("hVYD7Ou2pCQ", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberInitialize); + LIB_FUNCTION("asjUJJ+aa8s", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberOptParamInitialize); + LIB_FUNCTION("JeNX5F-NzQU", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberFinalize); + + LIB_FUNCTION("a0LLrZWac0M", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberRun); + LIB_FUNCTION("PFT2S-tJ7Uk", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberSwitch); + LIB_FUNCTION("p+zLIOg27zU", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberGetSelf); + LIB_FUNCTION("B0ZX2hx9DMw", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberReturnToThread); + + LIB_FUNCTION("uq2Y5BFz0PE", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberGetInfo); + LIB_FUNCTION("Lcqty+QNWFc", "libSceFiber", 1, "libSceFiber", 1, 1, + sceFiberStartContextSizeCheck); + LIB_FUNCTION("Kj4nXMpnM8Y", "libSceFiber", 1, "libSceFiber", 1, 1, + sceFiberStopContextSizeCheck); + LIB_FUNCTION("JzyT91ucGDc", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberRename); +} + +} // namespace Libraries::Fiber \ No newline at end of file diff --git a/src/core/libraries/fiber/fiber.h b/src/core/libraries/fiber/fiber.h new file mode 100644 index 000000000..930409caa --- /dev/null +++ b/src/core/libraries/fiber/fiber.h @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/assert.h" +#include "common/types.h" + +namespace Core::Loader { +class SymbolsResolver; +} +namespace Libraries::Fiber { + +#define ORBIS_FIBER_MAX_NAME_LENGTH (31) + +typedef void PS4_SYSV_ABI (*SceFiberEntry)(u64 argOnInitialize, u64 argOnRun); + +enum FiberState : u32 { + None = 0u, + Init = 1u, + Run = 2u, + Suspend = 3u, +}; + +struct SceFiber { + u64 signature; + FiberState state; + SceFiberEntry entry; + + u64 argOnInitialize; + + u64 argRun; + u64* pArgRun; + + u64 argReturn; + u64* pArgReturn; + + u64 sizeContext; + + char name[ORBIS_FIBER_MAX_NAME_LENGTH]; + void* handle; +}; +static_assert(sizeof(SceFiber) <= 256); + +struct SceFiberInfo { + u64 size; + SceFiberEntry entry; + u64 argOnInitialize; + void* addrContext; + u64 sizeContext; + char name[ORBIS_FIBER_MAX_NAME_LENGTH + 1]; + u64 sizeContextMargin; +}; +static_assert(sizeof(SceFiberInfo) <= 128); + +typedef void* SceFiberOptParam; + +s32 PS4_SYSV_ABI sceFiberInitialize(SceFiber* fiber, const char* name, SceFiberEntry entry, + u64 argOnInitialize, void* addrContext, u64 sizeContext, + const SceFiberOptParam* optParam); + +s32 PS4_SYSV_ABI sceFiberOptParamInitialize(SceFiberOptParam* optParam); + +s32 PS4_SYSV_ABI sceFiberFinalize(SceFiber* fiber); + +s32 PS4_SYSV_ABI sceFiberRun(SceFiber* fiber, u64 argOnRunTo, u64* argOnReturn); + +s32 PS4_SYSV_ABI sceFiberSwitch(SceFiber* fiber, u64 argOnRunTo, u64* argOnRun); + +s32 PS4_SYSV_ABI sceFiberGetSelf(SceFiber** fiber); + +s32 PS4_SYSV_ABI sceFiberReturnToThread(u64 argOnReturn, u64* argOnRun); + +s32 PS4_SYSV_ABI sceFiberGetInfo(SceFiber* fiber, SceFiberInfo* fiberInfo); + +s32 PS4_SYSV_ABI sceFiberStartContextSizeCheck(u32 flags); + +s32 PS4_SYSV_ABI sceFiberStopContextSizeCheck(void); + +s32 PS4_SYSV_ABI sceFiberRename(SceFiber* fiber, const char* name); + +void RegisterlibSceFiber(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Fiber \ No newline at end of file diff --git a/src/core/libraries/game_live_streaming/gamelivestreaming.cpp b/src/core/libraries/game_live_streaming/gamelivestreaming.cpp new file mode 100644 index 000000000..a1ebddfbf --- /dev/null +++ b/src/core/libraries/game_live_streaming/gamelivestreaming.cpp @@ -0,0 +1,354 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "gamelivestreaming.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" + +namespace Libraries::GameLiveStreaming { + +int PS4_SYSV_ABI sceGameLiveStreamingStartDebugBroadcast() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingStopDebugBroadcast() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingApplySocialFeedbackMessageFilter() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingCheckCallback() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingClearPresetSocialFeedbackCommands() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingClearSocialFeedbackMessages() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingClearSpoilerTag() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingEnableLiveStreaming() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingEnableSocialFeedback() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingGetCurrentBroadcastScreenLayout() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingGetCurrentStatus(OrbisGameLiveStreamingStatus* status) { + memset(status, 0, sizeof(*status)); + status->isOnAir = false; + LOG_DEBUG(Lib_GameLiveStreaming, "(STUBBED) called userid = {}", status->userId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingGetCurrentStatus2() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingGetProgramInfo() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingGetSocialFeedbackMessages() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingGetSocialFeedbackMessagesCount() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingInitialize() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingLaunchLiveViewer() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingLaunchLiveViewerA() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingPermitLiveStreaming() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingPermitServerSideRecording() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingPostSocialMessage() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingRegisterCallback() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingScreenCloseSeparateMode() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingScreenConfigureSeparateMode() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingScreenInitialize() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingScreenInitializeSeparateModeParameter() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingScreenOpenSeparateMode() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingScreenSetMode() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingScreenTerminate() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingSetCameraFrameSetting() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingSetDefaultServiceProviderPermission() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingSetGuardAreas() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingSetInvitationSessionId() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingSetLinkCommentPreset() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingSetMaxBitrate() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingSetMetadata() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingSetPresetSocialFeedbackCommands() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingSetPresetSocialFeedbackCommandsDescription() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingSetServiceProviderPermission() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingSetSpoilerTag() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingSetStandbyScreenResource() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingStartGenerateStandbyScreenResource() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingStartSocialFeedbackMessageFiltering() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingStopGenerateStandbyScreenResource() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingStopSocialFeedbackMessageFiltering() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingTerminate() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceGameLiveStreamingUnregisterCallback() { + LOG_ERROR(Lib_GameLiveStreaming, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterlibSceGameLiveStreaming(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("caqgDl+V9qA", "libSceGameLiveStreaming_debug", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingStartDebugBroadcast); + LIB_FUNCTION("0i8Lrllxwow", "libSceGameLiveStreaming_debug", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingStopDebugBroadcast); + LIB_FUNCTION("NqkTzemliC0", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingApplySocialFeedbackMessageFilter); + LIB_FUNCTION("PC4jq87+YQI", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingCheckCallback); + LIB_FUNCTION("FcHBfHjFXkA", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingClearPresetSocialFeedbackCommands); + LIB_FUNCTION("lZ2Sd0uEvpo", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingClearSocialFeedbackMessages); + LIB_FUNCTION("6c2zGtThFww", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingClearSpoilerTag); + LIB_FUNCTION("dWM80AX39o4", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingEnableLiveStreaming); + LIB_FUNCTION("wBOQWjbWMfU", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingEnableSocialFeedback); + LIB_FUNCTION("aRSQNqbats4", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingGetCurrentBroadcastScreenLayout); + LIB_FUNCTION("CoPMx369EqM", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingGetCurrentStatus); + LIB_FUNCTION("lK8dLBNp9OE", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingGetCurrentStatus2); + LIB_FUNCTION("OIIm19xu+NM", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingGetProgramInfo); + LIB_FUNCTION("PMx7N4WqNdo", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingGetSocialFeedbackMessages); + LIB_FUNCTION("yeQKjHETi40", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingGetSocialFeedbackMessagesCount); + LIB_FUNCTION("kvYEw2lBndk", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingInitialize); + LIB_FUNCTION("ysWfX5PPbfc", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingLaunchLiveViewer); + LIB_FUNCTION("cvRCb7DTAig", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingLaunchLiveViewerA); + LIB_FUNCTION("K0QxEbD7q+c", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingPermitLiveStreaming); + LIB_FUNCTION("-EHnU68gExU", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingPermitServerSideRecording); + LIB_FUNCTION("hggKhPySVgI", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingPostSocialMessage); + LIB_FUNCTION("nFP8qT9YXbo", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingRegisterCallback); + LIB_FUNCTION("b5RaMD2J0So", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingScreenCloseSeparateMode); + LIB_FUNCTION("hBdd8n6kuvE", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingScreenConfigureSeparateMode); + LIB_FUNCTION("uhCmn81s-mU", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingScreenInitialize); + LIB_FUNCTION("fo5B8RUaBxQ", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingScreenInitializeSeparateModeParameter); + LIB_FUNCTION("iorzW0pKOiA", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingScreenOpenSeparateMode); + LIB_FUNCTION("gDSvt78H3Oo", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingScreenSetMode); + LIB_FUNCTION("HE93dr-5rx4", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingScreenTerminate); + LIB_FUNCTION("3PSiwAzFISE", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingSetCameraFrameSetting); + LIB_FUNCTION("TwuUzTKKeek", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingSetDefaultServiceProviderPermission); + LIB_FUNCTION("Gw6S4oqlY7E", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingSetGuardAreas); + LIB_FUNCTION("QmQYwQ7OTJI", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingSetInvitationSessionId); + LIB_FUNCTION("Sb5bAXyUt5c", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingSetLinkCommentPreset); + LIB_FUNCTION("q-kxuaF7URU", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingSetMaxBitrate); + LIB_FUNCTION("hUY-mSOyGL0", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingSetMetadata); + LIB_FUNCTION("ycodiP2I0xo", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingSetPresetSocialFeedbackCommands); + LIB_FUNCTION("x6deXUpQbBo", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingSetPresetSocialFeedbackCommandsDescription); + LIB_FUNCTION("mCoz3k3zPmA", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingSetServiceProviderPermission); + LIB_FUNCTION("ZuX+zzz2DkA", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingSetSpoilerTag); + LIB_FUNCTION("MLvYI86FFAo", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingSetStandbyScreenResource); + LIB_FUNCTION("y0KkAydy9xE", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingStartGenerateStandbyScreenResource); + LIB_FUNCTION("Y1WxX7dPMCw", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingStartSocialFeedbackMessageFiltering); + LIB_FUNCTION("D7dg5QJ4FlE", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingStopGenerateStandbyScreenResource); + LIB_FUNCTION("bYuGUBuIsaY", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingStopSocialFeedbackMessageFiltering); + LIB_FUNCTION("9yK6Fk8mKOQ", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingTerminate); + LIB_FUNCTION("5XHaH3kL+bA", "libSceGameLiveStreaming", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingUnregisterCallback); + LIB_FUNCTION("caqgDl+V9qA", "libSceGameLiveStreaming_direct_streaming", 1, + "libSceGameLiveStreaming", 1, 1, sceGameLiveStreamingStartDebugBroadcast); + LIB_FUNCTION("0i8Lrllxwow", "libSceGameLiveStreaming_direct_streaming", 1, + "libSceGameLiveStreaming", 1, 1, sceGameLiveStreamingStopDebugBroadcast); + LIB_FUNCTION("CoPMx369EqM", "libSceGameLiveStreamingCompat", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingGetCurrentStatus); + LIB_FUNCTION("ysWfX5PPbfc", "libSceGameLiveStreamingCompat", 1, "libSceGameLiveStreaming", 1, 1, + sceGameLiveStreamingLaunchLiveViewer); +}; + +} // namespace Libraries::GameLiveStreaming \ No newline at end of file diff --git a/src/core/libraries/game_live_streaming/gamelivestreaming.h b/src/core/libraries/game_live_streaming/gamelivestreaming.h new file mode 100644 index 000000000..468750fd1 --- /dev/null +++ b/src/core/libraries/game_live_streaming/gamelivestreaming.h @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::GameLiveStreaming { + +struct OrbisGameLiveStreamingStatus { + bool isOnAir; + u8 align[3]; + u32 spectatorCounts; + s32 userId; + u8 reserved[60]; +}; +struct OrbisGameLiveStreamingStatus2 { + s32 userId; + bool isOnAir; + u8 align[3]; + u32 spectatorCounts; + u32 textMessageCounts; + u32 commandMessageCounts; + u32 broadcastVideoResolution; + u8 reserved[48]; +}; + +int PS4_SYSV_ABI sceGameLiveStreamingStartDebugBroadcast(); +int PS4_SYSV_ABI sceGameLiveStreamingStopDebugBroadcast(); +int PS4_SYSV_ABI sceGameLiveStreamingApplySocialFeedbackMessageFilter(); +int PS4_SYSV_ABI sceGameLiveStreamingCheckCallback(); +int PS4_SYSV_ABI sceGameLiveStreamingClearPresetSocialFeedbackCommands(); +int PS4_SYSV_ABI sceGameLiveStreamingClearSocialFeedbackMessages(); +int PS4_SYSV_ABI sceGameLiveStreamingClearSpoilerTag(); +int PS4_SYSV_ABI sceGameLiveStreamingEnableLiveStreaming(); +int PS4_SYSV_ABI sceGameLiveStreamingEnableSocialFeedback(); +int PS4_SYSV_ABI sceGameLiveStreamingGetCurrentBroadcastScreenLayout(); +int PS4_SYSV_ABI sceGameLiveStreamingGetCurrentStatus(OrbisGameLiveStreamingStatus* status); +int PS4_SYSV_ABI sceGameLiveStreamingGetCurrentStatus2(); +int PS4_SYSV_ABI sceGameLiveStreamingGetProgramInfo(); +int PS4_SYSV_ABI sceGameLiveStreamingGetSocialFeedbackMessages(); +int PS4_SYSV_ABI sceGameLiveStreamingGetSocialFeedbackMessagesCount(); +int PS4_SYSV_ABI sceGameLiveStreamingInitialize(); +int PS4_SYSV_ABI sceGameLiveStreamingLaunchLiveViewer(); +int PS4_SYSV_ABI sceGameLiveStreamingLaunchLiveViewerA(); +int PS4_SYSV_ABI sceGameLiveStreamingPermitLiveStreaming(); +int PS4_SYSV_ABI sceGameLiveStreamingPermitServerSideRecording(); +int PS4_SYSV_ABI sceGameLiveStreamingPostSocialMessage(); +int PS4_SYSV_ABI sceGameLiveStreamingRegisterCallback(); +int PS4_SYSV_ABI sceGameLiveStreamingScreenCloseSeparateMode(); +int PS4_SYSV_ABI sceGameLiveStreamingScreenConfigureSeparateMode(); +int PS4_SYSV_ABI sceGameLiveStreamingScreenInitialize(); +int PS4_SYSV_ABI sceGameLiveStreamingScreenInitializeSeparateModeParameter(); +int PS4_SYSV_ABI sceGameLiveStreamingScreenOpenSeparateMode(); +int PS4_SYSV_ABI sceGameLiveStreamingScreenSetMode(); +int PS4_SYSV_ABI sceGameLiveStreamingScreenTerminate(); +int PS4_SYSV_ABI sceGameLiveStreamingSetCameraFrameSetting(); +int PS4_SYSV_ABI sceGameLiveStreamingSetDefaultServiceProviderPermission(); +int PS4_SYSV_ABI sceGameLiveStreamingSetGuardAreas(); +int PS4_SYSV_ABI sceGameLiveStreamingSetInvitationSessionId(); +int PS4_SYSV_ABI sceGameLiveStreamingSetLinkCommentPreset(); +int PS4_SYSV_ABI sceGameLiveStreamingSetMaxBitrate(); +int PS4_SYSV_ABI sceGameLiveStreamingSetMetadata(); +int PS4_SYSV_ABI sceGameLiveStreamingSetPresetSocialFeedbackCommands(); +int PS4_SYSV_ABI sceGameLiveStreamingSetPresetSocialFeedbackCommandsDescription(); +int PS4_SYSV_ABI sceGameLiveStreamingSetServiceProviderPermission(); +int PS4_SYSV_ABI sceGameLiveStreamingSetSpoilerTag(); +int PS4_SYSV_ABI sceGameLiveStreamingSetStandbyScreenResource(); +int PS4_SYSV_ABI sceGameLiveStreamingStartGenerateStandbyScreenResource(); +int PS4_SYSV_ABI sceGameLiveStreamingStartSocialFeedbackMessageFiltering(); +int PS4_SYSV_ABI sceGameLiveStreamingStopGenerateStandbyScreenResource(); +int PS4_SYSV_ABI sceGameLiveStreamingStopSocialFeedbackMessageFiltering(); +int PS4_SYSV_ABI sceGameLiveStreamingTerminate(); +int PS4_SYSV_ABI sceGameLiveStreamingUnregisterCallback(); + +void RegisterlibSceGameLiveStreaming(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::GameLiveStreaming \ No newline at end of file diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index c7dbfec2e..4d8aa8817 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -519,10 +519,12 @@ void PS4_SYSV_ABI sceGnmDingDong(u32 gnm_vqid, u32 next_offs_dw) { // Dumping them using the current ring pointer would result in files containing only the // `IndirectBuffer` command. To access the actual command stream, we need to unwrap the IB. auto acb = acb_span; + auto base_addr = reinterpret_cast(acb_ptr); const auto* indirect_buffer = reinterpret_cast(acb_span.data()); if (indirect_buffer->header.opcode == PM4ItOpcode::IndirectBuffer) { - acb = {indirect_buffer->Address(), indirect_buffer->ib_size}; + base_addr = reinterpret_cast(indirect_buffer->Address()); + acb = {reinterpret_cast(base_addr), indirect_buffer->ib_size}; } using namespace DebugStateType; @@ -532,9 +534,9 @@ void PS4_SYSV_ABI sceGnmDingDong(u32 gnm_vqid, u32 next_offs_dw) { .submit_num = seq_num, .num2 = gnm_vqid, .data = {acb.begin(), acb.end()}, + .base_addr = base_addr, }); } - liverpool->SubmitAsc(vqid, acb_span); *asc_queue.read_addr += acb_size; @@ -1076,9 +1078,27 @@ s32 PS4_SYSV_ABI sceGnmInsertPopMarker(u32* cmdbuf, u32 size) { return -1; } -int PS4_SYSV_ABI sceGnmInsertPushColorMarker() { - LOG_ERROR(Lib_GnmDriver, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceGnmInsertPushColorMarker(u32* cmdbuf, u32 size, const char* marker, u32 color) { + LOG_TRACE(Lib_GnmDriver, "called"); + + if (cmdbuf && marker) { + const auto len = std::strlen(marker); + const u32 packet_size = ((len + 0xc) >> 2) + ((len + 0x10) >> 3) * 2; + if (packet_size + 2 == size) { + auto* nop = reinterpret_cast(cmdbuf); + nop->header = + PM4Type3Header{PM4ItOpcode::Nop, packet_size, PM4ShaderType::ShaderGraphics}; + nop->data_block[0] = PM4CmdNop::PayloadType::DebugColorMarkerPush; + const auto marker_len = len + 1; + std::memcpy(&nop->data_block[1], marker, marker_len); + *reinterpret_cast(reinterpret_cast(&nop->data_block[1]) + marker_len + 8) = + color; + std::memset(reinterpret_cast(&nop->data_block[1]) + marker_len + 8 + sizeof(u32), + 0, packet_size * 4 - marker_len - 8 - sizeof(u32)); + return ORBIS_OK; + } + } + return -1; } s32 PS4_SYSV_ABI sceGnmInsertPushMarker(u32* cmdbuf, u32 size, const char* marker) { @@ -1107,9 +1127,25 @@ int PS4_SYSV_ABI sceGnmInsertSetColorMarker() { return ORBIS_OK; } -int PS4_SYSV_ABI sceGnmInsertSetMarker(u32 *param_1,int param_2,char *param_3) { - LOG_ERROR(Lib_GnmDriver, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceGnmInsertSetMarker(u32* cmdbuf, u32 size, const char* marker) { + LOG_TRACE(Lib_GnmDriver, "called"); + + if (cmdbuf && marker) { + const auto len = std::strlen(marker); + const u32 packet_size = ((len + 8) >> 2) + ((len + 0xc) >> 3) * 2; + if (packet_size + 2 == size) { + auto* nop = reinterpret_cast(cmdbuf); + nop->header = + PM4Type3Header{PM4ItOpcode::Nop, packet_size, PM4ShaderType::ShaderGraphics}; + nop->data_block[0] = PM4CmdNop::PayloadType::DebugSetMarker; + const auto marker_len = len + 1; + std::memcpy(&nop->data_block[1], marker, marker_len); + std::memset(reinterpret_cast(&nop->data_block[1]) + marker_len, 0, + packet_size * 4 - marker_len); + return ORBIS_OK; + } + } + return -1; } int PS4_SYSV_ABI sceGnmInsertThreadTraceMarker() { @@ -2145,15 +2181,16 @@ s32 PS4_SYSV_ABI sceGnmSubmitCommandBuffers(u32 count, const u32* dcb_gpu_addrs[ .submit_num = seq_num, .num2 = cbpair, .data = {dcb_span.begin(), dcb_span.end()}, + .base_addr = reinterpret_cast(dcb_gpu_addrs[cbpair]), }); DebugState.PushQueueDump({ .type = QueueType::ccb, .submit_num = seq_num, .num2 = cbpair, .data = {ccb_span.begin(), ccb_span.end()}, + .base_addr = reinterpret_cast(ccb), }); } - liverpool->SubmitGfx(dcb_span, ccb_span); } diff --git a/src/core/libraries/gnmdriver/gnmdriver.h b/src/core/libraries/gnmdriver/gnmdriver.h index 0d206d8f3..a95daa90d 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.h +++ b/src/core/libraries/gnmdriver/gnmdriver.h @@ -105,10 +105,10 @@ int PS4_SYSV_ABI sceGnmGpuPaDebugEnter(); int PS4_SYSV_ABI sceGnmGpuPaDebugLeave(); int PS4_SYSV_ABI sceGnmInsertDingDongMarker(); s32 PS4_SYSV_ABI sceGnmInsertPopMarker(u32* cmdbuf, u32 size); -int PS4_SYSV_ABI sceGnmInsertPushColorMarker(); +s32 PS4_SYSV_ABI sceGnmInsertPushColorMarker(u32* cmdbuf, u32 size, const char* marker, u32 color); s32 PS4_SYSV_ABI sceGnmInsertPushMarker(u32* cmdbuf, u32 size, const char* marker); int PS4_SYSV_ABI sceGnmInsertSetColorMarker(); -int PS4_SYSV_ABI sceGnmInsertSetMarker(u32 *param_1,int param_2,char *param_3); +s32 PS4_SYSV_ABI sceGnmInsertSetMarker(u32* cmdbuf, u32 size, const char* marker); int PS4_SYSV_ABI sceGnmInsertThreadTraceMarker(); s32 PS4_SYSV_ABI sceGnmInsertWaitFlipDone(u32* cmdbuf, u32 size, s32 vo_handle, u32 buf_idx); int PS4_SYSV_ABI sceGnmIsCoredumpValid(); diff --git a/src/core/libraries/ime/ime.cpp b/src/core/libraries/ime/ime.cpp new file mode 100644 index 000000000..13a70acf7 --- /dev/null +++ b/src/core/libraries/ime/ime.cpp @@ -0,0 +1,340 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ime.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" + +namespace Libraries::Ime { + +int PS4_SYSV_ABI FinalizeImeModule() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI InitializeImeModule() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeCheckFilterText() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeCheckRemoteEventParam() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeCheckUpdateTextInfo() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeClose() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeConfigGet() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeConfigSet() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeConfirmCandidate() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeDicAddWord() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeDicDeleteLearnDics() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeDicDeleteUserDics() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeDicDeleteWord() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeDicGetWords() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeDicReplaceWord() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeDisableController() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeFilterText() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeForTestFunction() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeGetPanelPositionAndForm() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeGetPanelSize() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeKeyboardClose() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeKeyboardGetInfo() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeKeyboardGetResourceId() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeKeyboardOpen() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeKeyboardOpenInternal() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeKeyboardSetMode() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeKeyboardUpdate() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeOpen() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeOpenInternal() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeParamInit() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeSetCandidateIndex() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeSetCaret() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeSetText() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeSetTextGeometry() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeUpdate() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshClearPreedit() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshClose() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshConfirmPreedit() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshDisableController() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshGetPanelPositionAndForm() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshInformConfirmdString() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshInformConfirmdString2() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshOpen() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshSendTextInfo() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshSetCaretGeometry() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshSetCaretIndexInPreedit() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshSetPanelPosition() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshSetParam() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshSetPreeditGeometry() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshSetSelectGeometry() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshSetSelectionText() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshUpdate() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshUpdateContext() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceImeVshUpdateContext2() { + LOG_ERROR(Lib_Ime, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterlibSceIme(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("mN+ZoSN-8hQ", "libSceIme", 1, "libSceIme", 1, 1, FinalizeImeModule); + LIB_FUNCTION("uTW+63goeJs", "libSceIme", 1, "libSceIme", 1, 1, InitializeImeModule); + LIB_FUNCTION("Lf3DeGWC6xg", "libSceIme", 1, "libSceIme", 1, 1, sceImeCheckFilterText); + LIB_FUNCTION("zHuMUGb-AQI", "libSceIme", 1, "libSceIme", 1, 1, sceImeCheckRemoteEventParam); + LIB_FUNCTION("OTb0Mg+1i1k", "libSceIme", 1, "libSceIme", 1, 1, sceImeCheckUpdateTextInfo); + LIB_FUNCTION("TmVP8LzcFcY", "libSceIme", 1, "libSceIme", 1, 1, sceImeClose); + LIB_FUNCTION("Ho5NVQzpKHo", "libSceIme", 1, "libSceIme", 1, 1, sceImeConfigGet); + LIB_FUNCTION("P5dPeiLwm-M", "libSceIme", 1, "libSceIme", 1, 1, sceImeConfigSet); + LIB_FUNCTION("tKLmVIUkpyM", "libSceIme", 1, "libSceIme", 1, 1, sceImeConfirmCandidate); + LIB_FUNCTION("NYDsL9a0oEo", "libSceIme", 1, "libSceIme", 1, 1, sceImeDicAddWord); + LIB_FUNCTION("l01GKoyiQrY", "libSceIme", 1, "libSceIme", 1, 1, sceImeDicDeleteLearnDics); + LIB_FUNCTION("E2OcGgi-FPY", "libSceIme", 1, "libSceIme", 1, 1, sceImeDicDeleteUserDics); + LIB_FUNCTION("JAiMBkOTYKI", "libSceIme", 1, "libSceIme", 1, 1, sceImeDicDeleteWord); + LIB_FUNCTION("JoPdCUXOzMU", "libSceIme", 1, "libSceIme", 1, 1, sceImeDicGetWords); + LIB_FUNCTION("FuEl46uHDyo", "libSceIme", 1, "libSceIme", 1, 1, sceImeDicReplaceWord); + LIB_FUNCTION("E+f1n8e8DAw", "libSceIme", 1, "libSceIme", 1, 1, sceImeDisableController); + LIB_FUNCTION("evjOsE18yuI", "libSceIme", 1, "libSceIme", 1, 1, sceImeFilterText); + LIB_FUNCTION("wVkehxutK-U", "libSceIme", 1, "libSceIme", 1, 1, sceImeForTestFunction); + LIB_FUNCTION("T6FYjZXG93o", "libSceIme", 1, "libSceIme", 1, 1, sceImeGetPanelPositionAndForm); + LIB_FUNCTION("ziPDcIjO0Vk", "libSceIme", 1, "libSceIme", 1, 1, sceImeGetPanelSize); + LIB_FUNCTION("PMVehSlfZ94", "libSceIme", 1, "libSceIme", 1, 1, sceImeKeyboardClose); + LIB_FUNCTION("VkqLPArfFdc", "libSceIme", 1, "libSceIme", 1, 1, sceImeKeyboardGetInfo); + LIB_FUNCTION("dKadqZFgKKQ", "libSceIme", 1, "libSceIme", 1, 1, sceImeKeyboardGetResourceId); + LIB_FUNCTION("eaFXjfJv3xs", "libSceIme", 1, "libSceIme", 1, 1, sceImeKeyboardOpen); + LIB_FUNCTION("oYkJlMK51SA", "libSceIme", 1, "libSceIme", 1, 1, sceImeKeyboardOpenInternal); + LIB_FUNCTION("ua+13Hk9kKs", "libSceIme", 1, "libSceIme", 1, 1, sceImeKeyboardSetMode); + LIB_FUNCTION("3Hx2Uw9xnv8", "libSceIme", 1, "libSceIme", 1, 1, sceImeKeyboardUpdate); + LIB_FUNCTION("RPydv-Jr1bc", "libSceIme", 1, "libSceIme", 1, 1, sceImeOpen); + LIB_FUNCTION("16UI54cWRQk", "libSceIme", 1, "libSceIme", 1, 1, sceImeOpenInternal); + LIB_FUNCTION("WmYDzdC4EHI", "libSceIme", 1, "libSceIme", 1, 1, sceImeParamInit); + LIB_FUNCTION("TQaogSaqkEk", "libSceIme", 1, "libSceIme", 1, 1, sceImeSetCandidateIndex); + LIB_FUNCTION("WLxUN2WMim8", "libSceIme", 1, "libSceIme", 1, 1, sceImeSetCaret); + LIB_FUNCTION("ieCNrVrzKd4", "libSceIme", 1, "libSceIme", 1, 1, sceImeSetText); + LIB_FUNCTION("TXYHFRuL8UY", "libSceIme", 1, "libSceIme", 1, 1, sceImeSetTextGeometry); + LIB_FUNCTION("-4GCfYdNF1s", "libSceIme", 1, "libSceIme", 1, 1, sceImeUpdate); + LIB_FUNCTION("oOwl47ouxoM", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshClearPreedit); + LIB_FUNCTION("gtoTsGM9vEY", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshClose); + LIB_FUNCTION("wTKF4mUlSew", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshConfirmPreedit); + LIB_FUNCTION("rM-1hkuOhh0", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshDisableController); + LIB_FUNCTION("42xMaQ+GLeQ", "libSceIme", 1, "libSceIme", 1, 1, + sceImeVshGetPanelPositionAndForm); + LIB_FUNCTION("ZmmV6iukhyo", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshInformConfirmdString); + LIB_FUNCTION("EQBusz6Uhp8", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshInformConfirmdString2); + LIB_FUNCTION("LBicRa-hj3A", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshOpen); + LIB_FUNCTION("-IAOwd2nO7g", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshSendTextInfo); + LIB_FUNCTION("qDagOjvJdNk", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshSetCaretGeometry); + LIB_FUNCTION("tNOlmxee-Nk", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshSetCaretIndexInPreedit); + LIB_FUNCTION("rASXozKkQ9g", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshSetPanelPosition); + LIB_FUNCTION("idvMaIu5H+k", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshSetParam); + LIB_FUNCTION("ga5GOgThbjo", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshSetPreeditGeometry); + LIB_FUNCTION("RuSca8rS6yA", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshSetSelectGeometry); + LIB_FUNCTION("J7COZrgSFRA", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshSetSelectionText); + LIB_FUNCTION("WqAayyok5p0", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshUpdate); + LIB_FUNCTION("O7Fdd+Oc-qQ", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshUpdateContext); + LIB_FUNCTION("fwcPR7+7Rks", "libSceIme", 1, "libSceIme", 1, 1, sceImeVshUpdateContext2); +}; + +} // namespace Libraries::Ime \ No newline at end of file diff --git a/src/core/libraries/ime/ime.h b/src/core/libraries/ime/ime.h new file mode 100644 index 000000000..807616f14 --- /dev/null +++ b/src/core/libraries/ime/ime.h @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::Ime { + +int PS4_SYSV_ABI FinalizeImeModule(); +int PS4_SYSV_ABI InitializeImeModule(); +int PS4_SYSV_ABI sceImeCheckFilterText(); +int PS4_SYSV_ABI sceImeCheckRemoteEventParam(); +int PS4_SYSV_ABI sceImeCheckUpdateTextInfo(); +int PS4_SYSV_ABI sceImeClose(); +int PS4_SYSV_ABI sceImeConfigGet(); +int PS4_SYSV_ABI sceImeConfigSet(); +int PS4_SYSV_ABI sceImeConfirmCandidate(); +int PS4_SYSV_ABI sceImeDicAddWord(); +int PS4_SYSV_ABI sceImeDicDeleteLearnDics(); +int PS4_SYSV_ABI sceImeDicDeleteUserDics(); +int PS4_SYSV_ABI sceImeDicDeleteWord(); +int PS4_SYSV_ABI sceImeDicGetWords(); +int PS4_SYSV_ABI sceImeDicReplaceWord(); +int PS4_SYSV_ABI sceImeDisableController(); +int PS4_SYSV_ABI sceImeFilterText(); +int PS4_SYSV_ABI sceImeForTestFunction(); +int PS4_SYSV_ABI sceImeGetPanelPositionAndForm(); +int PS4_SYSV_ABI sceImeGetPanelSize(); +int PS4_SYSV_ABI sceImeKeyboardClose(); +int PS4_SYSV_ABI sceImeKeyboardGetInfo(); +int PS4_SYSV_ABI sceImeKeyboardGetResourceId(); +int PS4_SYSV_ABI sceImeKeyboardOpen(); +int PS4_SYSV_ABI sceImeKeyboardOpenInternal(); +int PS4_SYSV_ABI sceImeKeyboardSetMode(); +int PS4_SYSV_ABI sceImeKeyboardUpdate(); +int PS4_SYSV_ABI sceImeOpen(); +int PS4_SYSV_ABI sceImeOpenInternal(); +int PS4_SYSV_ABI sceImeParamInit(); +int PS4_SYSV_ABI sceImeSetCandidateIndex(); +int PS4_SYSV_ABI sceImeSetCaret(); +int PS4_SYSV_ABI sceImeSetText(); +int PS4_SYSV_ABI sceImeSetTextGeometry(); +int PS4_SYSV_ABI sceImeUpdate(); +int PS4_SYSV_ABI sceImeVshClearPreedit(); +int PS4_SYSV_ABI sceImeVshClose(); +int PS4_SYSV_ABI sceImeVshConfirmPreedit(); +int PS4_SYSV_ABI sceImeVshDisableController(); +int PS4_SYSV_ABI sceImeVshGetPanelPositionAndForm(); +int PS4_SYSV_ABI sceImeVshInformConfirmdString(); +int PS4_SYSV_ABI sceImeVshInformConfirmdString2(); +int PS4_SYSV_ABI sceImeVshOpen(); +int PS4_SYSV_ABI sceImeVshSendTextInfo(); +int PS4_SYSV_ABI sceImeVshSetCaretGeometry(); +int PS4_SYSV_ABI sceImeVshSetCaretIndexInPreedit(); +int PS4_SYSV_ABI sceImeVshSetPanelPosition(); +int PS4_SYSV_ABI sceImeVshSetParam(); +int PS4_SYSV_ABI sceImeVshSetPreeditGeometry(); +int PS4_SYSV_ABI sceImeVshSetSelectGeometry(); +int PS4_SYSV_ABI sceImeVshSetSelectionText(); +int PS4_SYSV_ABI sceImeVshUpdate(); +int PS4_SYSV_ABI sceImeVshUpdateContext(); +int PS4_SYSV_ABI sceImeVshUpdateContext2(); + +void RegisterlibSceIme(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Ime \ No newline at end of file diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index e2093ce21..7f86ee540 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -321,26 +321,26 @@ int PS4_SYSV_ABI sceKernelRmdir(const char* path) { const std::filesystem::path dir_name = mnt->GetHostPath(path, &ro); if (dir_name.empty()) { - LOG_INFO(Kernel_Fs, "Failed to remove directory: {}, permission denied", - fmt::UTF(dir_name.u8string())); + LOG_ERROR(Kernel_Fs, "Failed to remove directory: {}, permission denied", + fmt::UTF(dir_name.u8string())); return SCE_KERNEL_ERROR_EACCES; } if (ro) { - LOG_INFO(Kernel_Fs, "Failed to remove directory: {}, directory is read only", - fmt::UTF(dir_name.u8string())); + LOG_ERROR(Kernel_Fs, "Failed to remove directory: {}, directory is read only", + fmt::UTF(dir_name.u8string())); return SCE_KERNEL_ERROR_EROFS; } if (!std::filesystem::is_directory(dir_name)) { - LOG_INFO(Kernel_Fs, "Failed to remove directory: {}, path is not a directory", - fmt::UTF(dir_name.u8string())); + LOG_ERROR(Kernel_Fs, "Failed to remove directory: {}, path is not a directory", + fmt::UTF(dir_name.u8string())); return ORBIS_KERNEL_ERROR_ENOTDIR; } if (!std::filesystem::exists(dir_name)) { - LOG_INFO(Kernel_Fs, "Failed to remove directory: {}, no such file or directory", - fmt::UTF(dir_name.u8string())); + LOG_ERROR(Kernel_Fs, "Failed to remove directory: {}, no such file or directory", + fmt::UTF(dir_name.u8string())); return ORBIS_KERNEL_ERROR_ENOENT; } @@ -348,7 +348,7 @@ int PS4_SYSV_ABI sceKernelRmdir(const char* path) { int result = std::filesystem::remove_all(dir_name, ec); if (!ec) { - LOG_DEBUG(Kernel_Fs, "Removed directory: {}", fmt::UTF(dir_name.u8string())); + LOG_INFO(Kernel_Fs, "Removed directory: {}", fmt::UTF(dir_name.u8string())); return ORBIS_OK; } LOG_ERROR(Kernel_Fs, "Failed to remove directory: {}, error_code={}", diff --git a/src/core/libraries/kernel/libkernel.cpp b/src/core/libraries/kernel/libkernel.cpp index 83d30a00c..aff29d10b 100644 --- a/src/core/libraries/kernel/libkernel.cpp +++ b/src/core/libraries/kernel/libkernel.cpp @@ -157,6 +157,7 @@ void SetPosixErrno(int e) { g_posix_errno = e; } } + int PS4_SYSV_ABI sceKernelMmap(void* addr, u64 len, int prot, int flags, int fd, size_t offset, void** res) { LOG_INFO(Kernel_Vmm, "called addr = {}, len = {}, prot = {}, flags = {}, fd = {}, offset = {}", diff --git a/src/core/libraries/kernel/memory_management.h b/src/core/libraries/kernel/memory_management.h index 38898aa57..6e90204cf 100644 --- a/src/core/libraries/kernel/memory_management.h +++ b/src/core/libraries/kernel/memory_management.h @@ -7,6 +7,8 @@ #include "common/types.h" constexpr u64 SCE_KERNEL_MAIN_DMEM_SIZE = 5056_MB; // ~ 5GB +// TODO: Confirm this value on hardware. +constexpr u64 SCE_KERNEL_MAIN_DMEM_SIZE_PRO = 5568_MB; // ~ 5.5GB namespace Libraries::Kernel { diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index c30c7fca5..87f4d6ab9 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -36,6 +36,10 @@ void init_pthreads() { ScePthreadMutexattr default_mutexattr = nullptr; scePthreadMutexattrInit(&default_mutexattr); g_pthread_cxt->setDefaultMutexattr(default_mutexattr); + ScePthreadMutexattr adaptive_mutexattr = nullptr; + scePthreadMutexattrInit(&adaptive_mutexattr); + scePthreadMutexattrSettype(&adaptive_mutexattr, ORBIS_PTHREAD_MUTEX_ADAPTIVE); + g_pthread_cxt->setAdaptiveMutexattr(adaptive_mutexattr); // default cond init ScePthreadCondattr default_condattr = nullptr; scePthreadCondattrInit(&default_condattr); @@ -412,7 +416,8 @@ int PS4_SYSV_ABI scePthreadGetaffinity(ScePthread thread, /*SceKernelCpumask*/ u } ScePthreadMutex* createMutex(ScePthreadMutex* addr) { - if (addr == nullptr || *addr != nullptr) { + if (addr == nullptr || + (*addr != nullptr && *addr != ORBIS_PTHREAD_MUTEX_ADAPTIVE_INITIALIZER)) { return addr; } @@ -429,14 +434,14 @@ int PS4_SYSV_ABI scePthreadMutexInit(ScePthreadMutex* mutex, const ScePthreadMut if (mutex == nullptr) { return SCE_KERNEL_ERROR_EINVAL; } - if (mutex_attr == nullptr) { - attr = g_pthread_cxt->getDefaultMutexattr(); - } else { - if (*mutex_attr == nullptr) { - attr = g_pthread_cxt->getDefaultMutexattr(); + if (mutex_attr == nullptr || *mutex_attr == nullptr) { + if (*mutex == ORBIS_PTHREAD_MUTEX_ADAPTIVE_INITIALIZER) { + attr = g_pthread_cxt->getAdaptiveMutexattr(); } else { - attr = mutex_attr; + attr = g_pthread_cxt->getDefaultMutexattr(); } + } else { + attr = mutex_attr; } *mutex = new PthreadMutexInternal{}; diff --git a/src/core/libraries/kernel/thread_management.h b/src/core/libraries/kernel/thread_management.h index 7385b55ce..3cdb300f7 100644 --- a/src/core/libraries/kernel/thread_management.h +++ b/src/core/libraries/kernel/thread_management.h @@ -13,6 +13,8 @@ #include "common/types.h" +#define ORBIS_PTHREAD_MUTEX_ADAPTIVE_INITIALIZER (reinterpret_cast(1)) + namespace Core::Loader { class SymbolsResolver; } @@ -134,6 +136,12 @@ public: void setDefaultMutexattr(ScePthreadMutexattr attr) { m_default_mutexattr = attr; } + ScePthreadMutexattr* getAdaptiveMutexattr() { + return &m_adaptive_mutexattr; + } + void setAdaptiveMutexattr(ScePthreadMutexattr attr) { + m_adaptive_mutexattr = attr; + } ScePthreadCondattr* getDefaultCondattr() { return &m_default_condattr; } @@ -161,6 +169,7 @@ public: private: ScePthreadMutexattr m_default_mutexattr = nullptr; + ScePthreadMutexattr m_adaptive_mutexattr = nullptr; ScePthreadCondattr m_default_condattr = nullptr; ScePthreadAttr m_default_attr = nullptr; PThreadPool* m_pthread_pool = nullptr; diff --git a/src/core/libraries/kernel/threads/semaphore.cpp b/src/core/libraries/kernel/threads/semaphore.cpp index ff5368023..59099c1b8 100644 --- a/src/core/libraries/kernel/threads/semaphore.cpp +++ b/src/core/libraries/kernel/threads/semaphore.cpp @@ -59,7 +59,7 @@ public: for (auto it = wait_list.begin(); it != wait_list.end();) { auto* waiter = *it; if (waiter->need_count > token_count) { - it++; + ++it; continue; } it = wait_list.erase(it); @@ -148,7 +148,7 @@ public: // Find the first with priority less then us and insert right before it. auto it = wait_list.begin(); while (it != wait_list.end() && (*it)->priority > waiter->priority) { - it++; + ++it; } wait_list.insert(it, waiter); return it; diff --git a/src/core/libraries/kernel/time_management.cpp b/src/core/libraries/kernel/time_management.cpp index 5fa26b789..853f8d54c 100644 --- a/src/core/libraries/kernel/time_management.cpp +++ b/src/core/libraries/kernel/time_management.cpp @@ -148,7 +148,7 @@ int PS4_SYSV_ABI sceKernelGettimeofday(OrbisKernelTimeval* tp) { #ifdef _WIN64 FILETIME filetime; - GetSystemTimeAsFileTime(&filetime); + GetSystemTimePreciseAsFileTime(&filetime); constexpr u64 UNIX_TIME_START = 0x295E9648864000; constexpr u64 TICKS_PER_SECOND = 1000000; diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 5b6c17b10..caa254fd8 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -11,7 +11,9 @@ #include "core/libraries/dialogs/error_dialog.h" #include "core/libraries/dialogs/ime_dialog.h" #include "core/libraries/disc_map/disc_map.h" +#include "core/libraries/game_live_streaming/gamelivestreaming.h" #include "core/libraries/gnmdriver/gnmdriver.h" +#include "core/libraries/ime/ime.h" #include "core/libraries/kernel/libkernel.h" #include "core/libraries/libc_internal/libc_internal.h" #include "core/libraries/libpng/pngdec.h" @@ -26,10 +28,12 @@ #include "core/libraries/pad/pad.h" #include "core/libraries/playgo/playgo.h" #include "core/libraries/random/random.h" +#include "core/libraries/remote_play/remoteplay.h" #include "core/libraries/rtc/rtc.h" #include "core/libraries/save_data/dialog/savedatadialog.h" #include "core/libraries/save_data/savedata.h" #include "core/libraries/screenshot/screenshot.h" +#include "core/libraries/share_play/shareplay.h" #include "core/libraries/system/commondialog.h" #include "core/libraries/system/msgdialog.h" #include "core/libraries/system/posix.h" @@ -77,6 +81,10 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::ImeDialog::RegisterlibSceImeDialog(sym); Libraries::AvPlayer::RegisterlibSceAvPlayer(sym); Libraries::Audio3d::RegisterlibSceAudio3d(sym); + Libraries::Ime::RegisterlibSceIme(sym); + Libraries::GameLiveStreaming::RegisterlibSceGameLiveStreaming(sym); + Libraries::SharePlay::RegisterlibSceSharePlay(sym); + Libraries::Remoteplay::RegisterlibSceRemoteplay(sym); } } // namespace Libraries diff --git a/src/core/libraries/network/net.cpp b/src/core/libraries/network/net.cpp index 2c03dde3e..161fc5214 100644 --- a/src/core/libraries/network/net.cpp +++ b/src/core/libraries/network/net.cpp @@ -18,6 +18,8 @@ namespace Libraries::Net { +static thread_local int32_t net_errno = 0; + int PS4_SYSV_ABI in6addr_any() { LOG_ERROR(Lib_Net, "(STUBBED) called"); return ORBIS_OK; @@ -563,9 +565,9 @@ int PS4_SYSV_ABI sceNetEpollWait() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNetErrnoLoc() { +int* PS4_SYSV_ABI sceNetErrnoLoc() { LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; + return &net_errno; } int PS4_SYSV_ABI sceNetEtherNtostr() { @@ -732,9 +734,16 @@ int PS4_SYSV_ABI sceNetInetNtopWithScopeId() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNetInetPton() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceNetInetPton(int af, const char* src, void* dst) { +#ifdef WIN32 + int res = InetPtonA(af, src, dst); +#else + int res = inet_pton(af, src, dst); +#endif + if (res < 0) { + UNREACHABLE(); + } + return res; } int PS4_SYSV_ABI sceNetInetPtonEx() { diff --git a/src/core/libraries/network/net.h b/src/core/libraries/network/net.h index eababdb67..beef38b56 100644 --- a/src/core/libraries/network/net.h +++ b/src/core/libraries/network/net.h @@ -136,7 +136,7 @@ int PS4_SYSV_ABI sceNetEpollControl(); int PS4_SYSV_ABI sceNetEpollCreate(); int PS4_SYSV_ABI sceNetEpollDestroy(); int PS4_SYSV_ABI sceNetEpollWait(); -int PS4_SYSV_ABI sceNetErrnoLoc(); +int* PS4_SYSV_ABI sceNetErrnoLoc(); int PS4_SYSV_ABI sceNetEtherNtostr(); int PS4_SYSV_ABI sceNetEtherStrton(); int PS4_SYSV_ABI sceNetEventCallbackCreate(); @@ -169,7 +169,7 @@ u64 PS4_SYSV_ABI sceNetHtonll(u64 host64); u16 PS4_SYSV_ABI sceNetHtons(u16 host16); const char* PS4_SYSV_ABI sceNetInetNtop(int af, const void* src, char* dst, u32 size); int PS4_SYSV_ABI sceNetInetNtopWithScopeId(); -int PS4_SYSV_ABI sceNetInetPton(); +int PS4_SYSV_ABI sceNetInetPton(int af, const char* src, void* dst); int PS4_SYSV_ABI sceNetInetPtonEx(); int PS4_SYSV_ABI sceNetInetPtonWithScopeId(); int PS4_SYSV_ABI sceNetInfoDumpStart(); diff --git a/src/core/libraries/network/net_ctl_codes.h b/src/core/libraries/network/net_ctl_codes.h index a38565c1e..0dac4201e 100644 --- a/src/core/libraries/network/net_ctl_codes.h +++ b/src/core/libraries/network/net_ctl_codes.h @@ -26,3 +26,11 @@ constexpr int ORBIS_NET_CTL_STATE_IPOBTAINED = 3; constexpr int ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED = 1; constexpr int ORBIS_SCE_NET_CTL_EVENT_TYPE_DISCONNECT_REQ_FINISHED = 2; constexpr int ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED = 3; + +// get info codes +// device +constexpr int ORBIS_NET_CTL_DEVICE_WIRED = 0; +constexpr int ORBIS_NET_CTL_DEVICE_WIRELESS = 1; +// link +constexpr int ORBIS_NET_CTL_LINK_DISCONNECTED = 0; +constexpr int ORBIS_NET_CTL_LINK_CONNECTED = 1; diff --git a/src/core/libraries/network/net_obj.cpp b/src/core/libraries/network/net_obj.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/core/libraries/network/net_obj.h b/src/core/libraries/network/net_obj.h new file mode 100644 index 000000000..e69de29bb diff --git a/src/core/libraries/network/netctl.cpp b/src/core/libraries/network/netctl.cpp index 3ecdde773..e7a69611e 100644 --- a/src/core/libraries/network/netctl.cpp +++ b/src/core/libraries/network/netctl.cpp @@ -1,6 +1,17 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#ifdef WIN32 +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#include +#include +#include +#else +#include +#include +#include +#endif + #include "common/logging/log.h" #include "common/singleton.h" #include "core/libraries/error_codes.h" @@ -149,15 +160,32 @@ int PS4_SYSV_ABI sceNetCtlGetIfStat() { int PS4_SYSV_ABI sceNetCtlGetInfo(int code, OrbisNetCtlInfo* info) { switch (code) { case ORBIS_NET_CTL_INFO_DEVICE: - info->device = 0; + info->device = ORBIS_NET_CTL_DEVICE_WIRED; break; case ORBIS_NET_CTL_INFO_LINK: - info->link = 0; // disconnected + info->link = ORBIS_NET_CTL_LINK_DISCONNECTED; break; + case ORBIS_NET_CTL_INFO_IP_ADDRESS: { + strcpy(info->ip_address, + "127.0.0.1"); // placeholder in case gethostbyname can't find another ip + char devname[80]; + gethostname(devname, 80); + struct hostent* resolved = gethostbyname(devname); + for (int i = 0; resolved->h_addr_list[i] != nullptr; ++i) { + struct in_addr addrIn; + memcpy(&addrIn, resolved->h_addr_list[i], sizeof(u32)); + char* addr = inet_ntoa(addrIn); + if (strcmp(addr, "127.0.0.1") != 0) { + strcpy(info->ip_address, addr); + break; + } + } + break; + } default: LOG_ERROR(Lib_NetCtl, "{} unsupported code", code); } - LOG_ERROR(Lib_NetCtl, "(STUBBED) called"); + LOG_DEBUG(Lib_NetCtl, "(STUBBED) called"); return ORBIS_OK; } @@ -187,7 +215,10 @@ int PS4_SYSV_ABI sceNetCtlGetNetEvConfigInfoIpcInt() { } int PS4_SYSV_ABI sceNetCtlGetResult(int eventType, int* errorCode) { - LOG_ERROR(Lib_NetCtl, "(STUBBED) called eventType = {} ", eventType); + if (!errorCode) { + return ORBIS_NET_CTL_ERROR_INVALID_ADDR; + } + LOG_DEBUG(Lib_NetCtl, "(STUBBED) called eventType = {} ", eventType); *errorCode = 0; return ORBIS_OK; } diff --git a/src/core/libraries/network/netctl.h b/src/core/libraries/network/netctl.h index 850650f97..89ba34c31 100644 --- a/src/core/libraries/network/netctl.h +++ b/src/core/libraries/network/netctl.h @@ -50,6 +50,7 @@ typedef union OrbisNetCtlInfo { // GetInfo codes constexpr int ORBIS_NET_CTL_INFO_DEVICE = 1; constexpr int ORBIS_NET_CTL_INFO_LINK = 4; +constexpr int ORBIS_NET_CTL_INFO_IP_ADDRESS = 14; int PS4_SYSV_ABI sceNetBweCheckCallbackIpcInt(); int PS4_SYSV_ABI sceNetBweClearEventIpcInt(); diff --git a/src/core/libraries/np_manager/np_manager.cpp b/src/core/libraries/np_manager/np_manager.cpp index 28d28cc93..4fff59003 100644 --- a/src/core/libraries/np_manager/np_manager.cpp +++ b/src/core/libraries/np_manager/np_manager.cpp @@ -902,12 +902,13 @@ int PS4_SYSV_ABI sceNpCreateAsyncRequest() { } int PS4_SYSV_ABI sceNpCreateRequest() { - LOG_ERROR(Lib_NpManager, "(STUBBED) called"); - return ORBIS_OK; + LOG_ERROR(Lib_NpManager, "(DUMMY) called"); + static int id = 0; + return ++id; } -int PS4_SYSV_ABI sceNpDeleteRequest() { - LOG_ERROR(Lib_NpManager, "(STUBBED) called"); +int PS4_SYSV_ABI sceNpDeleteRequest(int reqId) { + LOG_ERROR(Lib_NpManager, "(DUMMY) called reqId = {}", reqId); return ORBIS_OK; } @@ -985,8 +986,12 @@ int PS4_SYSV_ABI sceNpGetNpReachabilityState() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpGetOnlineId() { - LOG_ERROR(Lib_NpManager, "(STUBBED) called"); +int PS4_SYSV_ABI sceNpGetOnlineId(s32 userId, OrbisNpOnlineId* onlineId) { + LOG_DEBUG(Lib_NpManager, "userId {}", userId); + std::string name = Config::getUserName(); + // Fill the unused stuffs to 0 + memset(onlineId, 0, sizeof(*onlineId)); + strcpy(onlineId->data, name.c_str()); return ORBIS_OK; } diff --git a/src/core/libraries/np_manager/np_manager.h b/src/core/libraries/np_manager/np_manager.h index 43ea49ce4..7e906cdc8 100644 --- a/src/core/libraries/np_manager/np_manager.h +++ b/src/core/libraries/np_manager/np_manager.h @@ -218,7 +218,7 @@ int PS4_SYSV_ABI sceNpCheckNpReachability(); int PS4_SYSV_ABI sceNpCheckPlus(); int PS4_SYSV_ABI sceNpCreateAsyncRequest(); int PS4_SYSV_ABI sceNpCreateRequest(); -int PS4_SYSV_ABI sceNpDeleteRequest(); +int PS4_SYSV_ABI sceNpDeleteRequest(int reqId); int PS4_SYSV_ABI sceNpGetAccountAge(); int PS4_SYSV_ABI sceNpGetAccountCountry(); int PS4_SYSV_ABI sceNpGetAccountCountryA(); @@ -233,7 +233,7 @@ int PS4_SYSV_ABI sceNpGetGamePresenceStatus(); int PS4_SYSV_ABI sceNpGetGamePresenceStatusA(); int PS4_SYSV_ABI sceNpGetNpId(OrbisUserServiceUserId userId, OrbisNpId* npId); int PS4_SYSV_ABI sceNpGetNpReachabilityState(); -int PS4_SYSV_ABI sceNpGetOnlineId(); +int PS4_SYSV_ABI sceNpGetOnlineId(s32 userId, OrbisNpOnlineId* onlineId); int PS4_SYSV_ABI sceNpGetParentalControlInfo(); int PS4_SYSV_ABI sceNpGetParentalControlInfoA(); int PS4_SYSV_ABI sceNpGetState(s32 userId, OrbisNpState* state); diff --git a/src/core/libraries/np_trophy/np_trophy.cpp b/src/core/libraries/np_trophy/np_trophy.cpp index 548d1af69..e8fd57ef1 100644 --- a/src/core/libraries/np_trophy/np_trophy.cpp +++ b/src/core/libraries/np_trophy/np_trophy.cpp @@ -520,7 +520,7 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context, if (!result) { LOG_ERROR(Lib_NpTrophy, "Failed to open trophy xml : {}", result.description()); - return ORBIS_OK; + return -1; } int num_trophies = 0; diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index 6c3b1f56c..d786647c2 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -88,7 +88,7 @@ int PS4_SYSV_ABI scePadGetCapability() { } int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerInformation* pInfo) { - LOG_INFO(Lib_Pad, "called handle = {}", handle); + LOG_DEBUG(Lib_Pad, "called handle = {}", handle); if (handle < 0) { pInfo->touchPadInfo.pixelDensity = 1; pInfo->touchPadInfo.resolution.x = 1920; @@ -368,12 +368,13 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) { pData->angularVelocity.x = 0.0f; pData->angularVelocity.y = 0.0f; pData->angularVelocity.z = 0.0f; - pData->touchData.touchNum = 0; - pData->touchData.touch[0].x = 0; - pData->touchData.touch[0].y = 0; + pData->touchData.touchNum = + (state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0); + pData->touchData.touch[0].x = state.touchpad[0].x; + pData->touchData.touch[0].y = state.touchpad[0].y; pData->touchData.touch[0].id = 1; - pData->touchData.touch[1].x = 0; - pData->touchData.touch[1].y = 0; + pData->touchData.touch[1].x = state.touchpad[1].x; + pData->touchData.touch[1].y = state.touchpad[1].y; pData->touchData.touch[1].id = 2; pData->timestamp = state.time; pData->connected = true; // isConnected; //TODO fix me proper diff --git a/src/core/libraries/remote_play/remoteplay.cpp b/src/core/libraries/remote_play/remoteplay.cpp new file mode 100644 index 000000000..b7402173e --- /dev/null +++ b/src/core/libraries/remote_play/remoteplay.cpp @@ -0,0 +1,309 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "remoteplay.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" + +namespace Libraries::Remoteplay { + +int PS4_SYSV_ABI sceRemoteplayApprove() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayChangeEnterKey() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayClearAllRegistData() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayClearConnectHistory() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayConfirmDeviceRegist() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayDisconnect() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayGeneratePinCode() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayGetApMode() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayGetConnectHistory() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayGetConnectionStatus(s32 userId, int* pStatus) { + *pStatus = ORBIS_REMOTEPLAY_CONNECTION_STATUS_DISCONNECT; + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayGetConnectUserId() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayGetMbusDeviceInfo() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayGetOperationStatus() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayGetRemoteplayStatus() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayGetRpMode() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayImeClose() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayImeFilterResult() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayImeGetEvent() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayImeNotify() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayImeNotifyEventResult() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayImeOpen() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayImeSetCaret() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayImeSetText() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayInitialize() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayIsRemoteOskReady() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayIsRemotePlaying() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayNotifyMbusDeviceRegistComplete() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayNotifyNpPushWakeup() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayNotifyPinCodeError() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayNotifyUserDelete() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayPrintAllRegistData() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayProhibit() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayProhibitStreaming() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayServerLock() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayServerUnLock() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplaySetApMode() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplaySetLogLevel() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplaySetProhibition() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplaySetProhibitionForVsh() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplaySetRpMode() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceRemoteplayTerminate() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI Func_1D5EE365ED5FADB3() { + LOG_ERROR(Lib_Remoteplay, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterlibSceRemoteplay(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("xQeIryTX7dY", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayApprove); + LIB_FUNCTION("IYZ+Mu+8tPo", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayChangeEnterKey); + LIB_FUNCTION("ZYUsJtcAnqA", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayClearAllRegistData); + LIB_FUNCTION("cCheyCbF7qw", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayClearConnectHistory); + LIB_FUNCTION("tPYT-kGbZh8", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayConfirmDeviceRegist); + LIB_FUNCTION("6Lg4BNleJWc", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayDisconnect); + LIB_FUNCTION("j98LdSGy4eY", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayGeneratePinCode); + LIB_FUNCTION("L+cL-M-DP3w", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayGetApMode); + LIB_FUNCTION("g4K51cY+PEw", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayGetConnectHistory); + LIB_FUNCTION("g3PNjYKWqnQ", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayGetConnectionStatus); + LIB_FUNCTION("3eBNV9A0BUM", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayGetConnectUserId); + LIB_FUNCTION("ufesWMVX6iU", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayGetMbusDeviceInfo); + LIB_FUNCTION("DxU4JGh4S2k", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayGetOperationStatus); + LIB_FUNCTION("n5OxFJEvPlc", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayGetRemoteplayStatus); + LIB_FUNCTION("Cekhs6LSHC0", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayGetRpMode); + LIB_FUNCTION("ig1ocbR7Ptw", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayImeClose); + LIB_FUNCTION("gV9-8cJPM3I", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayImeFilterResult); + LIB_FUNCTION("cMk57DZXe6c", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayImeGetEvent); + LIB_FUNCTION("-gwkQpOCl68", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayImeNotify); + LIB_FUNCTION("58v9tSlRxc8", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayImeNotifyEventResult); + LIB_FUNCTION("C3r2zT5ebMg", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayImeOpen); + LIB_FUNCTION("oB730zwoz0s", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayImeSetCaret); + LIB_FUNCTION("rOTg1Nljp8w", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayImeSetText); + LIB_FUNCTION("k1SwgkMSOM8", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayInitialize); + LIB_FUNCTION("R8RZC1ZIkzU", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayIsRemoteOskReady); + LIB_FUNCTION("uYhiELUtLgA", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayIsRemotePlaying); + LIB_FUNCTION("d-BBSEq1nfc", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayNotifyMbusDeviceRegistComplete); + LIB_FUNCTION("Yytq7NE38R8", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayNotifyNpPushWakeup); + LIB_FUNCTION("Wg-w8xjMZA4", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayNotifyPinCodeError); + LIB_FUNCTION("yheulqylKwI", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayNotifyUserDelete); + LIB_FUNCTION("t5ZvUiZ1hpE", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayPrintAllRegistData); + LIB_FUNCTION("mrNh78tBpmg", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayProhibit); + LIB_FUNCTION("7QLrixwVHcU", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayProhibitStreaming); + LIB_FUNCTION("-ThIlThsN80", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayServerLock); + LIB_FUNCTION("0Z-Pm5rZJOI", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayServerUnLock); + LIB_FUNCTION("xSrhtSLIjOc", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplaySetApMode); + LIB_FUNCTION("5-2agAeaE+c", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplaySetLogLevel); + LIB_FUNCTION("Rf0XMVR7xPw", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplaySetProhibition); + LIB_FUNCTION("n4l3FTZtNQM", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplaySetProhibitionForVsh); + LIB_FUNCTION("-BPcEQ1w8xc", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplaySetRpMode); + LIB_FUNCTION("BOwybKVa3Do", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + sceRemoteplayTerminate); + LIB_FUNCTION("HV7jZe1frbM", "libSceRemoteplay", 1, "libSceRemoteplay", 0, 0, + Func_1D5EE365ED5FADB3); +}; + +} // namespace Libraries::Remoteplay \ No newline at end of file diff --git a/src/core/libraries/remote_play/remoteplay.h b/src/core/libraries/remote_play/remoteplay.h new file mode 100644 index 000000000..979f73a9e --- /dev/null +++ b/src/core/libraries/remote_play/remoteplay.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +// returning codes in sceRemoteplayGetConnectionStatus pstatus +constexpr int ORBIS_REMOTEPLAY_CONNECTION_STATUS_DISCONNECT = 0; +constexpr int ORBIS_REMOTEPLAY_CONNECTION_STATUS_CONNECT = 1; + +namespace Libraries::Remoteplay { + +int PS4_SYSV_ABI sceRemoteplayApprove(); +int PS4_SYSV_ABI sceRemoteplayChangeEnterKey(); +int PS4_SYSV_ABI sceRemoteplayClearAllRegistData(); +int PS4_SYSV_ABI sceRemoteplayClearConnectHistory(); +int PS4_SYSV_ABI sceRemoteplayConfirmDeviceRegist(); +int PS4_SYSV_ABI sceRemoteplayDisconnect(); +int PS4_SYSV_ABI sceRemoteplayGeneratePinCode(); +int PS4_SYSV_ABI sceRemoteplayGetApMode(); +int PS4_SYSV_ABI sceRemoteplayGetConnectHistory(); +int PS4_SYSV_ABI sceRemoteplayGetConnectionStatus(s32 userId, int* pStatus); +int PS4_SYSV_ABI sceRemoteplayGetConnectUserId(); +int PS4_SYSV_ABI sceRemoteplayGetMbusDeviceInfo(); +int PS4_SYSV_ABI sceRemoteplayGetOperationStatus(); +int PS4_SYSV_ABI sceRemoteplayGetRemoteplayStatus(); +int PS4_SYSV_ABI sceRemoteplayGetRpMode(); +int PS4_SYSV_ABI sceRemoteplayImeClose(); +int PS4_SYSV_ABI sceRemoteplayImeFilterResult(); +int PS4_SYSV_ABI sceRemoteplayImeGetEvent(); +int PS4_SYSV_ABI sceRemoteplayImeNotify(); +int PS4_SYSV_ABI sceRemoteplayImeNotifyEventResult(); +int PS4_SYSV_ABI sceRemoteplayImeOpen(); +int PS4_SYSV_ABI sceRemoteplayImeSetCaret(); +int PS4_SYSV_ABI sceRemoteplayImeSetText(); +int PS4_SYSV_ABI sceRemoteplayInitialize(); +int PS4_SYSV_ABI sceRemoteplayIsRemoteOskReady(); +int PS4_SYSV_ABI sceRemoteplayIsRemotePlaying(); +int PS4_SYSV_ABI sceRemoteplayNotifyMbusDeviceRegistComplete(); +int PS4_SYSV_ABI sceRemoteplayNotifyNpPushWakeup(); +int PS4_SYSV_ABI sceRemoteplayNotifyPinCodeError(); +int PS4_SYSV_ABI sceRemoteplayNotifyUserDelete(); +int PS4_SYSV_ABI sceRemoteplayPrintAllRegistData(); +int PS4_SYSV_ABI sceRemoteplayProhibit(); +int PS4_SYSV_ABI sceRemoteplayProhibitStreaming(); +int PS4_SYSV_ABI sceRemoteplayServerLock(); +int PS4_SYSV_ABI sceRemoteplayServerUnLock(); +int PS4_SYSV_ABI sceRemoteplaySetApMode(); +int PS4_SYSV_ABI sceRemoteplaySetLogLevel(); +int PS4_SYSV_ABI sceRemoteplaySetProhibition(); +int PS4_SYSV_ABI sceRemoteplaySetProhibitionForVsh(); +int PS4_SYSV_ABI sceRemoteplaySetRpMode(); +int PS4_SYSV_ABI sceRemoteplayTerminate(); +int PS4_SYSV_ABI Func_1D5EE365ED5FADB3(); + +void RegisterlibSceRemoteplay(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Remoteplay \ No newline at end of file diff --git a/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp b/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp index c4bf84258..01e56f8b8 100644 --- a/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp +++ b/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp @@ -98,7 +98,7 @@ SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) { param_sfo.Open(param_sfo_path); auto last_write = param_sfo.GetLastWrite(); -#ifdef _WIN32 +#if defined(_WIN32) && !defined(__GNUC__) && !defined(__MINGW32__) && !defined(__MINGW64__) auto utc_time = std::chrono::file_clock::to_utc(last_write); #else auto utc_time = std::chrono::file_clock::to_sys(last_write); @@ -402,7 +402,7 @@ void SaveDialogUi::Draw() { }; } - CentralizeWindow(); + CentralizeNextWindow(); SetNextWindowSize(window_size); SetNextWindowCollapsed(false); if (first_render || !io.NavActive) { diff --git a/src/core/libraries/save_data/save_instance.cpp b/src/core/libraries/save_data/save_instance.cpp index 1127a5452..0d6c5173c 100644 --- a/src/core/libraries/save_data/save_instance.cpp +++ b/src/core/libraries/save_data/save_instance.cpp @@ -12,9 +12,9 @@ #include "core/file_sys/fs.h" #include "save_instance.h" -constexpr u32 OrbisSaveDataBlocksMax = 32768; // 1 GiB +constexpr auto OrbisSaveDataBlocksMin2 = 96; // 3MiB +constexpr auto OrbisSaveDataBlocksMax = 32768; // 1 GiB constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save -constexpr std::string_view max_block_file_name = "max_block.txt"; static Core::FileSys::MntPoints* g_mnt = Common::Singleton::Instance(); @@ -58,18 +58,13 @@ std::filesystem::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_ game_serial / dir_name; } -int SaveInstance::GetMaxBlocks(const std::filesystem::path& save_path) { - Common::FS::IOFile max_blocks_file{save_path / sce_sys / max_block_file_name, - Common::FS::FileAccessMode::Read}; - int max_blocks = 0; - if (max_blocks_file.IsOpen()) { - max_blocks = std::atoi(max_blocks_file.ReadString(16).c_str()); +uint64_t SaveInstance::GetMaxBlockFromSFO(const PSF& psf) { + const auto vec = psf.GetBinary(std::string{SaveParams::SAVEDATA_BLOCKS}); + if (!vec.has_value()) { + return OrbisSaveDataBlocksMax; } - if (max_blocks <= 0) { - max_blocks = OrbisSaveDataBlocksMax; - } - - return max_blocks; + auto value = vec.value(); + return *(uint64_t*)value.data(); } std::filesystem::path SaveInstance::GetParamSFOPath(const std::filesystem::path& dir_path) { @@ -92,13 +87,15 @@ void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, P(String, SaveParams::SAVEDATA_DIRECTORY, std::move(dir_name)); P(Integer, SaveParams::SAVEDATA_LIST_PARAM, 0); P(String, SaveParams::TITLE_ID, std::move(game_serial)); + P(Binary, SaveParams::SAVEDATA_BLOCKS, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); #undef P } SaveInstance::SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string _game_serial, std::string_view _dir_name, int max_blocks) : slot_num(slot_num), user_id(user_id), game_serial(std::move(_game_serial)), - dir_name(_dir_name), max_blocks(max_blocks) { + dir_name(_dir_name), + max_blocks(std::clamp(max_blocks, OrbisSaveDataBlocksMin2, OrbisSaveDataBlocksMax)) { ASSERT(slot_num >= 0 && slot_num < 16); save_path = MakeDirSavePath(user_id, game_serial, dir_name); @@ -187,7 +184,7 @@ void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_cor } } - max_blocks = GetMaxBlocks(save_path); + max_blocks = static_cast(GetMaxBlockFromSFO(param_sfo)); g_mnt->Mount(save_path, mount_point, read_only); mounted = true; @@ -217,16 +214,13 @@ void SaveInstance::CreateFiles() { fs::create_directories(sce_sys_dir); SetupDefaultParamSFO(param_sfo, dir_name, game_serial); + param_sfo.AddBinary(std::string{SaveParams::SAVEDATA_BLOCKS}, max_blocks, true); const bool ok = param_sfo.Encode(param_sfo_path); if (!ok) { throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path, std::make_error_code(std::errc::permission_denied)); } - - Common::FS::IOFile max_block{sce_sys_dir / max_block_file_name, - Common::FS::FileAccessMode::Write}; - max_block.WriteString(std::to_string(max_blocks == 0 ? OrbisSaveDataBlocksMax : max_blocks)); } } // namespace Libraries::SaveData \ No newline at end of file diff --git a/src/core/libraries/save_data/save_instance.h b/src/core/libraries/save_data/save_instance.h index f07011047..3be5c4595 100644 --- a/src/core/libraries/save_data/save_instance.h +++ b/src/core/libraries/save_data/save_instance.h @@ -62,7 +62,7 @@ public: std::string_view game_serial, std::string_view dir_name); - static int GetMaxBlocks(const std::filesystem::path& save_path); + static uint64_t GetMaxBlockFromSFO(const PSF& psf); // Get param.sfo path from a dir_path generated by MakeDirSavePath static std::filesystem::path GetParamSFOPath(const std::filesystem::path& dir_path); diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index a2af2f159..93b3c20a9 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -445,7 +445,7 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info, fs::create_directories(root_save); const auto available = fs::space(root_save).available; - auto requested_size = mount_info->blocks * OrbisSaveDataBlockSize; + auto requested_size = save_instance.GetMaxBlocks() * OrbisSaveDataBlockSize; if (requested_size > available) { mount_result->required_blocks = (requested_size - available) / OrbisSaveDataBlockSize; return Error::NO_SPACE_FS; @@ -830,10 +830,11 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond LOG_ERROR(Lib_SaveData, "Failed to read SFO: {}", fmt::UTF(sfo_path.u8string())); ASSERT_MSG(false, "Failed to read SFO"); } - map_dir_sfo.emplace(dir_name, std::move(sfo)); size_t size = Common::FS::GetDirectorySize(dir_path); - size_t total = SaveInstance::GetMaxBlocks(dir_path); + size_t total = SaveInstance::GetMaxBlockFromSFO(sfo); + + map_dir_sfo.emplace(dir_name, std::move(sfo)); map_free_size.emplace(dir_name, total - size / OrbisSaveDataBlockSize); map_max_blocks.emplace(dir_name, total); } diff --git a/src/core/libraries/share_play/shareplay.cpp b/src/core/libraries/share_play/shareplay.cpp new file mode 100644 index 000000000..8370438b9 --- /dev/null +++ b/src/core/libraries/share_play/shareplay.cpp @@ -0,0 +1,186 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "shareplay.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" + +namespace Libraries::SharePlay { + +int PS4_SYSV_ABI sceSharePlayCrashDaemon() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlayGetCurrentConnectionInfo(OrbisSharePlayConnectionInfo* pInfo) { + memset(pInfo, 0, sizeof(*pInfo)); + pInfo->status = ORBIS_SHARE_PLAY_CONNECTION_STATUS_DORMANT; + LOG_DEBUG(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlayGetCurrentConnectionInfoA() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlayGetCurrentInfo() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlayGetEvent() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlayInitialize() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlayNotifyDialogOpen() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlayNotifyForceCloseForCdlg() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlayNotifyOpenQuickMenu() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlayResumeScreenForCdlg() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlayServerLock() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlayServerUnLock() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlaySetMode() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlaySetProhibition() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlaySetProhibitionModeWithAppId() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlayStartStandby() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlayStartStreaming() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlayStopStandby() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlayStopStreaming() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceSharePlayTerminate() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI Func_2E93C0EA6A6B67C4() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI Func_C1C236728D88E177() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI Func_E9E80C474781F115() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI Func_F3DD6199DA15ED44() { + LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterlibSceSharePlay(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("ggnCfalLU-8", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayCrashDaemon); + LIB_FUNCTION("OOrLKB0bSDs", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayGetCurrentConnectionInfo); + LIB_FUNCTION("+MCXJlWdi+s", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayGetCurrentConnectionInfoA); + LIB_FUNCTION("vUMkWXQff3w", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayGetCurrentInfo); + LIB_FUNCTION("Md7Mdkr8LBc", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayGetEvent); + LIB_FUNCTION("isruqthpYcw", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayInitialize); + LIB_FUNCTION("9zwJpai7jGc", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayNotifyDialogOpen); + LIB_FUNCTION("VUW2V9cUTP4", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayNotifyForceCloseForCdlg); + LIB_FUNCTION("XL0WwUJoQPg", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayNotifyOpenQuickMenu); + LIB_FUNCTION("6-1fKaa5HlY", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayResumeScreenForCdlg); + LIB_FUNCTION("U28jAuLHj6c", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayServerLock); + LIB_FUNCTION("3Oaux9ITEtY", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayServerUnLock); + LIB_FUNCTION("QZy+KmyqKPU", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, sceSharePlaySetMode); + LIB_FUNCTION("co2NCj--pnc", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlaySetProhibition); + LIB_FUNCTION("KADsbjNCgPo", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlaySetProhibitionModeWithAppId); + LIB_FUNCTION("-F6NddfUsa4", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayStartStandby); + LIB_FUNCTION("rWVNHNnEx6g", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayStartStreaming); + LIB_FUNCTION("zEDkUWLVwFI", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayStopStandby); + LIB_FUNCTION("aGlema+JxUU", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayStopStreaming); + LIB_FUNCTION("UaLjloJinow", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + sceSharePlayTerminate); + LIB_FUNCTION("LpPA6mprZ8Q", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + Func_2E93C0EA6A6B67C4); + LIB_FUNCTION("wcI2co2I4Xc", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + Func_C1C236728D88E177); + LIB_FUNCTION("6egMR0eB8RU", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + Func_E9E80C474781F115); + LIB_FUNCTION("891hmdoV7UQ", "libSceSharePlay", 1, "libSceSharePlay", 0, 0, + Func_F3DD6199DA15ED44); + LIB_FUNCTION("OOrLKB0bSDs", "libSceSharePlayCompat", 1, "libSceSharePlay", 0, 0, + sceSharePlayGetCurrentConnectionInfo); +}; + +} // namespace Libraries::SharePlay \ No newline at end of file diff --git a/src/core/libraries/share_play/shareplay.h b/src/core/libraries/share_play/shareplay.h new file mode 100644 index 000000000..8b1ad5f47 --- /dev/null +++ b/src/core/libraries/share_play/shareplay.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "common/types.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::SharePlay { + +constexpr int ORBIS_SHARE_PLAY_CONNECTION_STATUS_DORMANT = 0x00; +constexpr int ORBIS_SHARE_PLAY_CONNECTION_STATUS_READY = 0x01; +constexpr int ORBIS_SHARE_PLAY_CONNECTION_STATUS_CONNECTED = 0x02; + +struct OrbisSharePlayConnectionInfo { + int status; + int mode; + Libraries::NpManager::OrbisNpOnlineId hostOnlineId; + Libraries::NpManager::OrbisNpOnlineId visitorOnlineId; + s32 hostUserId; + s32 visitorUserId; +}; + +int PS4_SYSV_ABI sceSharePlayCrashDaemon(); +int PS4_SYSV_ABI sceSharePlayGetCurrentConnectionInfo(OrbisSharePlayConnectionInfo* pInfo); +int PS4_SYSV_ABI sceSharePlayGetCurrentConnectionInfoA(); +int PS4_SYSV_ABI sceSharePlayGetCurrentInfo(); +int PS4_SYSV_ABI sceSharePlayGetEvent(); +int PS4_SYSV_ABI sceSharePlayInitialize(); +int PS4_SYSV_ABI sceSharePlayNotifyDialogOpen(); +int PS4_SYSV_ABI sceSharePlayNotifyForceCloseForCdlg(); +int PS4_SYSV_ABI sceSharePlayNotifyOpenQuickMenu(); +int PS4_SYSV_ABI sceSharePlayResumeScreenForCdlg(); +int PS4_SYSV_ABI sceSharePlayServerLock(); +int PS4_SYSV_ABI sceSharePlayServerUnLock(); +int PS4_SYSV_ABI sceSharePlaySetMode(); +int PS4_SYSV_ABI sceSharePlaySetProhibition(); +int PS4_SYSV_ABI sceSharePlaySetProhibitionModeWithAppId(); +int PS4_SYSV_ABI sceSharePlayStartStandby(); +int PS4_SYSV_ABI sceSharePlayStartStreaming(); +int PS4_SYSV_ABI sceSharePlayStopStandby(); +int PS4_SYSV_ABI sceSharePlayStopStreaming(); +int PS4_SYSV_ABI sceSharePlayTerminate(); +int PS4_SYSV_ABI Func_2E93C0EA6A6B67C4(); +int PS4_SYSV_ABI Func_C1C236728D88E177(); +int PS4_SYSV_ABI Func_E9E80C474781F115(); +int PS4_SYSV_ABI Func_F3DD6199DA15ED44(); + +void RegisterlibSceSharePlay(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::SharePlay \ No newline at end of file diff --git a/src/core/libraries/system/msgdialog_ui.cpp b/src/core/libraries/system/msgdialog_ui.cpp index ae1dced12..862f5a569 100644 --- a/src/core/libraries/system/msgdialog_ui.cpp +++ b/src/core/libraries/system/msgdialog_ui.cpp @@ -256,7 +256,7 @@ void MsgDialogUi::Draw() { std::min(io.DisplaySize.y, 300.0f), }; - CentralizeWindow(); + CentralizeNextWindow(); SetNextWindowSize(window_size); SetNextWindowCollapsed(false); if (first_render || !io.NavActive) { diff --git a/src/core/libraries/system/systemservice.cpp b/src/core/libraries/system/systemservice.cpp index 8002e2bfc..9c6fe8f2e 100644 --- a/src/core/libraries/system/systemservice.cpp +++ b/src/core/libraries/system/systemservice.cpp @@ -1942,9 +1942,12 @@ int PS4_SYSV_ABI sceSystemServiceRaiseExceptionLocalProcess() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSystemServiceReceiveEvent() { - LOG_ERROR(Lib_SystemService, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceSystemServiceReceiveEvent(OrbisSystemServiceEvent* event) { + LOG_ERROR(Lib_SystemService, "(STUBBED) called, event type = {:#x}", (int)event->eventType); + if (event == nullptr) { + return ORBIS_SYSTEM_SERVICE_ERROR_PARAMETER; + } + return ORBIS_SYSTEM_SERVICE_ERROR_NO_EVENT; } int PS4_SYSV_ABI sceSystemServiceReenableMusicPlayer() { diff --git a/src/core/libraries/system/systemservice.h b/src/core/libraries/system/systemservice.h index 1976606a8..56583c97e 100644 --- a/src/core/libraries/system/systemservice.h +++ b/src/core/libraries/system/systemservice.h @@ -87,6 +87,32 @@ enum OrbisSystemParamLanguage { ORBIS_SYSTEM_PARAM_LANG_INDONESIAN = 29 }; +enum OrbisSystemServiceEventType { + ORBIS_SYSTEM_SERVICE_EVENT_INVALID = -1, + ORBIS_SYSTEM_SERVICE_EVENT_ON_RESUME = 0x10000000, + ORBIS_SYSTEM_SERVICE_EVENT_GAME_LIVE_STREAMING_STATUS_UPDATE = 0x10000001, + ORBIS_SYSTEM_SERVICE_EVENT_SESSION_INVITATION = 0x10000002, + ORBIS_SYSTEM_SERVICE_EVENT_ENTITLEMENT_UPDATE = 0x10000003, + ORBIS_SYSTEM_SERVICE_EVENT_GAME_CUSTOM_DATA = 0x10000004, + ORBIS_SYSTEM_SERVICE_EVENT_DISPLAY_SAFE_AREA_UPDATE = 0x10000005, + ORBIS_SYSTEM_SERVICE_EVENT_URL_OPEN = 0x10000006, + ORBIS_SYSTEM_SERVICE_EVENT_LAUNCH_APP = 0x10000007, + ORBIS_SYSTEM_SERVICE_EVENT_APP_LAUNCH_LINK = 0x10000008, + ORBIS_SYSTEM_SERVICE_EVENT_ADDCONTENT_INSTALL = 0x10000009, + ORBIS_SYSTEM_SERVICE_EVENT_RESET_VR_POSITION = 0x1000000a, + ORBIS_SYSTEM_SERVICE_EVENT_JOIN_EVENT = 0x1000000b, + ORBIS_SYSTEM_SERVICE_EVENT_PLAYGO_LOCUS_UPDATE = 0x1000000c, + ORBIS_SYSTEM_SERVICE_EVENT_PLAY_TOGETHER_HOST = 0x1000000d, + ORBIS_SYSTEM_SERVICE_EVENT_SERVICE_ENTITLEMENT_UPDATE = 0x1000000e, + ORBIS_SYSTEM_SERVICE_EVENT_EYE_TO_EYE_DISTANCE_UPDATE = 0x1000000f, + ORBIS_SYSTEM_SERVICE_EVENT_JOIN_MATCH_EVENT = 0x10000010, + ORBIS_SYSTEM_SERVICE_EVENT_PLAY_TOGETHER_HOST_A = 0x10000011, + ORBIS_SYSTEM_SERVICE_EVENT_WEBBROWSER_CLOSED = 0x10000012, + ORBIS_SYSTEM_SERVICE_EVENT_CONTROLLER_SETTINGS_CLOSED = 0x10000013, + ORBIS_SYSTEM_SERVICE_EVENT_JOIN_TEAM_ON_TEAM_MATCH_EVENT = 0x10000014, + ORBIS_SYSTEM_SERVICE_EVENT_OPEN_SHARE_MENU = 0x30000000 +}; + struct OrbisSystemServiceStatus { s32 eventNum; bool isSystemUiOverlaid; @@ -102,6 +128,41 @@ struct OrbisSystemServiceDisplaySafeAreaInfo { uint8_t reserved[128]; }; +struct OrbisSystemServiceEvent { + OrbisSystemServiceEventType eventType; + union { + char param[8192]; + struct { + char source[1024]; + char url[4096]; + } urlOpen; + struct { + u32 size; + u8 arg[8188]; + } launchApp; + struct { + u32 size; + u8 arg[2020]; + } appLaunchLink; + struct { + s32 userId; + char eventId[37]; + char bootArgument[7169]; + } joinEvent; + struct { + s32 userId; + u32 npServiceLabel; + u8 reserved[8184]; + } serviceEntitlementUpdate; + struct { + s32 userId; + u32 npServiceLabel; + u8 reserved[8184]; + } unifiedEntitlementUpdate; + u8 reserved[8192]; + }; +}; + bool IsSplashVisible(); int PS4_SYSV_ABI sceAppMessagingClearEventFlag(); @@ -480,7 +541,7 @@ s32 PS4_SYSV_ABI sceSystemServiceParamGetInt(int param_id, int* value); int PS4_SYSV_ABI sceSystemServiceParamGetString(); int PS4_SYSV_ABI sceSystemServicePowerTick(); int PS4_SYSV_ABI sceSystemServiceRaiseExceptionLocalProcess(); -int PS4_SYSV_ABI sceSystemServiceReceiveEvent(); +s32 PS4_SYSV_ABI sceSystemServiceReceiveEvent(OrbisSystemServiceEvent* event); int PS4_SYSV_ABI sceSystemServiceReenableMusicPlayer(); int PS4_SYSV_ABI sceSystemServiceRegisterDaemon(); int PS4_SYSV_ABI sceSystemServiceReleaseFb0(); diff --git a/src/core/libraries/system/userservice.cpp b/src/core/libraries/system/userservice.cpp index 560855474..140ca8921 100644 --- a/src/core/libraries/system/userservice.cpp +++ b/src/core/libraries/system/userservice.cpp @@ -491,7 +491,7 @@ int PS4_SYSV_ABI sceUserServiceGetImeRunCount() { } s32 PS4_SYSV_ABI sceUserServiceGetInitialUser(int* user_id) { - LOG_INFO(Lib_UserService, "called"); + LOG_DEBUG(Lib_UserService, "called"); if (user_id == nullptr) { LOG_ERROR(Lib_UserService, "user_id is null"); return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index d21ebae83..031b7b135 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -3,6 +3,7 @@ #include "common/alignment.h" #include "common/assert.h" +#include "common/config.h" #include "common/debug.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/memory_management.h" @@ -39,8 +40,10 @@ MemoryManager::MemoryManager() { MemoryManager::~MemoryManager() = default; void MemoryManager::SetupMemoryRegions(u64 flexible_size) { + const auto total_size = + Config::isNeoMode() ? SCE_KERNEL_MAIN_DMEM_SIZE_PRO : SCE_KERNEL_MAIN_DMEM_SIZE; total_flexible_size = flexible_size; - total_direct_size = SCE_KERNEL_MAIN_DMEM_SIZE - flexible_size; + total_direct_size = total_size - flexible_size; // Insert an area that covers direct memory physical block. // Note that this should never be called after direct memory allocations have been made. @@ -51,6 +54,17 @@ void MemoryManager::SetupMemoryRegions(u64 flexible_size) { total_flexible_size, total_direct_size); } +bool MemoryManager::TryWriteBacking(void* address, const void* data, u32 num_bytes) { + const VAddr virtual_addr = std::bit_cast(address); + const auto& vma = FindVMA(virtual_addr)->second; + if (vma.type != VMAType::Direct) { + return false; + } + u8* backing = impl.BackingBase() + vma.phys_base + (virtual_addr - vma.base); + memcpy(backing, data, num_bytes); + return true; +} + PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment) { std::scoped_lock lk{mutex}; @@ -95,7 +109,7 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, return dmem_area->second.is_free && remaining_size >= size; }; while (!is_suitable() && dmem_area->second.GetEnd() <= search_end) { - dmem_area++; + ++dmem_area; } ASSERT_MSG(is_suitable(), "Unable to find free direct memory area: size = {:#x}", size); @@ -242,6 +256,7 @@ int MemoryManager::PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot) new_vma.is_exec = false; new_vma.phys_base = 0; + rasterizer->MapMemory(mapped_addr, size); return ORBIS_OK; } @@ -483,7 +498,7 @@ int MemoryManager::VirtualQuery(VAddr addr, int flags, auto it = FindVMA(addr); if (it->second.type == VMAType::Free && flags == 1) { - it++; + ++it; } if (it->second.type == VMAType::Free) { LOG_WARNING(Kernel_Vmm, "VirtualQuery on free memory region"); @@ -599,7 +614,7 @@ VAddr MemoryManager::SearchFree(VAddr virtual_addr, size_t size, u32 alignment) return remaining_size >= size; }; while (!is_suitable()) { - it++; + ++it; } return virtual_addr; } diff --git a/src/core/memory.h b/src/core/memory.h index 752209cfc..286f1c979 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -28,7 +28,7 @@ enum class MemoryProt : u32 { CpuReadWrite = 2, GpuRead = 16, GpuWrite = 32, - GpuReadWrite = 38, + GpuReadWrite = 48, }; DECLARE_ENUM_FLAG_OPERATORS(MemoryProt) @@ -149,6 +149,8 @@ public: return impl.SystemReservedVirtualBase(); } + bool TryWriteBacking(void* address, const void* data, u32 num_bytes); + void SetupMemoryRegions(u64 flexible_size); PAddr PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment); diff --git a/src/emulator.cpp b/src/emulator.cpp index 721151ccc..939a18076 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -8,9 +8,13 @@ #include "common/logging/backend.h" #include "common/logging/log.h" #ifdef ENABLE_QT_GUI +#include #include "common/memory_patcher.h" #endif #include "common/assert.h" +#ifdef ENABLE_DISCORD_RPC +#include "common/discord_rpc_handler.h" +#endif #include "common/elf_info.h" #include "common/ntapi.h" #include "common/path_util.h" @@ -24,6 +28,7 @@ #include "core/file_format/trp.h" #include "core/file_sys/fs.h" #include "core/libraries/disc_map/disc_map.h" +#include "core/libraries/fiber/fiber.h" #include "core/libraries/kernel/thread_management.h" #include "core/libraries/libc_internal/libc_internal.h" #include "core/libraries/libs.h" @@ -58,6 +63,7 @@ Emulator::Emulator() { LOG_INFO(Loader, "Branch {}", Common::g_scm_branch); LOG_INFO(Loader, "Description {}", Common::g_scm_desc); + LOG_INFO(Config, "General Logtype: {}", Config::getLogType()); LOG_INFO(Config, "General isNeo: {}", Config::isNeoMode()); LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu()); LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders()); @@ -77,6 +83,17 @@ Emulator::Emulator() { // Load renderdoc module. VideoCore::LoadRenderDoc(); + + // Start the timer (Play Time) +#ifdef ENABLE_QT_GUI + start_time = std::chrono::steady_clock::now(); + const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + QString filePath = QString::fromStdString((user_dir / "play_time.txt").string()); + QFile file(filePath); + if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { + LOG_INFO(Loader, "Error opening or creating play_time.txt"); + } +#endif } Emulator::~Emulator() { @@ -85,6 +102,12 @@ Emulator::~Emulator() { } void Emulator::Run(const std::filesystem::path& file) { + + // Use the eboot from the separated updates folder if it's there + std::filesystem::path game_patch_folder = file.parent_path().concat("-UPDATE"); + bool use_game_patch = std::filesystem::exists(game_patch_folder / "sce_sys"); + std::filesystem::path eboot_path = use_game_patch ? game_patch_folder / file.filename() : file; + // Applications expect to be run from /app0 so mount the file's parent path as app0. auto* mnt = Common::Singleton::Instance(); mnt->Mount(file.parent_path(), "/app0"); @@ -99,7 +122,7 @@ void Emulator::Run(const std::filesystem::path& file) { std::string app_version; u32 fw_version; - std::filesystem::path sce_sys_folder = file.parent_path() / "sce_sys"; + std::filesystem::path sce_sys_folder = eboot_path.parent_path() / "sce_sys"; if (std::filesystem::is_directory(sce_sys_folder)) { for (const auto& entry : std::filesystem::directory_iterator(sce_sys_folder)) { if (entry.path().filename() == "param.sfo") { @@ -114,12 +137,20 @@ void Emulator::Run(const std::filesystem::path& file) { Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles"; if (!std::filesystem::exists(trophyDir)) { TRP trp; - if (!trp.Extract(file.parent_path(), id)) { + if (!trp.Extract(eboot_path.parent_path(), id)) { LOG_ERROR(Loader, "Couldn't extract trophies"); } } #ifdef ENABLE_QT_GUI MemoryPatcher::g_game_serial = id; + + // Timer for 'Play Time' + QTimer* timer = new QTimer(); + QObject::connect(timer, &QTimer::timeout, [this, id]() { + UpdatePlayTime(id); + start_time = std::chrono::steady_clock::now(); + }); + timer->start(60000); // 60000 ms = 1 minute #endif title = param_sfo->GetString("TITLE").value_or("Unknown title"); LOG_INFO(Loader, "Game id: {} Title: {}", id, title); @@ -195,35 +226,57 @@ void Emulator::Run(const std::filesystem::path& file) { Libraries::InitHLELibs(&linker->GetHLESymbols()); // Load the module with the linker - linker->LoadModule(file); + linker->LoadModule(eboot_path); // check if we have system modules to load - LoadSystemModules(file); + LoadSystemModules(eboot_path); // Load all prx from game's sce_module folder std::filesystem::path sce_module_folder = file.parent_path() / "sce_module"; if (std::filesystem::is_directory(sce_module_folder)) { for (const auto& entry : std::filesystem::directory_iterator(sce_module_folder)) { - LOG_INFO(Loader, "Loading {}", fmt::UTF(entry.path().u8string())); - linker->LoadModule(entry.path()); + std::filesystem::path module_path = entry.path(); + std::filesystem::path update_module_path = + eboot_path.parent_path() / "sce_module" / entry.path().filename(); + if (std::filesystem::exists(update_module_path) && use_game_patch) { + module_path = update_module_path; + } + LOG_INFO(Loader, "Loading {}", fmt::UTF(module_path.u8string())); + linker->LoadModule(module_path); } } +#ifdef ENABLE_DISCORD_RPC + // Discord RPC + if (Config::getEnableDiscordRPC()) { + auto* rpc = Common::Singleton::Instance(); + if (rpc->getRPCEnabled() == false) { + rpc->init(); + } + rpc->setStatusPlaying(game_info.title, id); + } +#endif + // start execution std::jthread mainthread = std::jthread([this](std::stop_token stop_token) { linker->Execute(); }); + window->initTimers(); while (window->isOpen()) { window->waitEvent(); } +#ifdef ENABLE_QT_GUI + UpdatePlayTime(id); +#endif + std::exit(0); } void Emulator::LoadSystemModules(const std::filesystem::path& file) { constexpr std::array ModulesToLoad{ {{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterlibSceNgs2}, - {"libSceFiber.sprx", nullptr}, + {"libSceFiber.sprx", &Libraries::Fiber::RegisterlibSceFiber}, {"libSceUlt.sprx", nullptr}, {"libSceJson.sprx", nullptr}, {"libSceJson2.sprx", nullptr}, @@ -258,4 +311,74 @@ void Emulator::LoadSystemModules(const std::filesystem::path& file) { } } +#ifdef ENABLE_QT_GUI +void Emulator::UpdatePlayTime(const std::string& serial) { + const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + QString filePath = QString::fromStdString((user_dir / "play_time.txt").string()); + + QFile file(filePath); + if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { + LOG_INFO(Loader, "Error opening play_time.txt"); + return; + } + + auto end_time = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(end_time - start_time); + int totalSeconds = duration.count(); + + QTextStream in(&file); + QStringList lines; + QString content; + while (!in.atEnd()) { + content += in.readLine() + "\n"; + } + file.close(); + + QStringList existingLines = content.split('\n', Qt::SkipEmptyParts); + int accumulatedSeconds = 0; + bool found = false; + + for (const QString& line : existingLines) { + QStringList parts = line.split(' '); + if (parts.size() == 2 && parts[0] == QString::fromStdString(serial)) { + QStringList timeParts = parts[1].split(':'); + if (timeParts.size() == 3) { + int hours = timeParts[0].toInt(); + int minutes = timeParts[1].toInt(); + int seconds = timeParts[2].toInt(); + accumulatedSeconds = hours * 3600 + minutes * 60 + seconds; + found = true; + break; + } + } + } + accumulatedSeconds += totalSeconds; + int hours = accumulatedSeconds / 3600; + int minutes = (accumulatedSeconds % 3600) / 60; + int seconds = accumulatedSeconds % 60; + QString playTimeSaved = QString::number(hours) + ":" + + QString::number(minutes).rightJustified(2, '0') + ":" + + QString::number(seconds).rightJustified(2, '0'); + + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream out(&file); + bool lineUpdated = false; + + for (const QString& line : existingLines) { + if (line.startsWith(QString::fromStdString(serial))) { + out << QString::fromStdString(serial) + " " + playTimeSaved + "\n"; + lineUpdated = true; + } else { + out << line << "\n"; + } + } + + if (!lineUpdated) { + out << QString::fromStdString(serial) + " " + playTimeSaved + "\n"; + } + } + LOG_INFO(Loader, "Playing time for {}: {}", serial, playTimeSaved.toStdString()); +} +#endif + } // namespace Core diff --git a/src/emulator.h b/src/emulator.h index 01bce7e7c..dc2959af4 100644 --- a/src/emulator.h +++ b/src/emulator.h @@ -26,6 +26,7 @@ public: ~Emulator(); void Run(const std::filesystem::path& file); + void UpdatePlayTime(const std::string& serial); private: void LoadSystemModules(const std::filesystem::path& file); @@ -34,6 +35,7 @@ private: Input::GameController* controller; Core::Linker* linker; std::unique_ptr window; + std::chrono::steady_clock::time_point start_time; }; } // namespace Core diff --git a/src/images/pause_icon.png b/src/images/pause_icon.png index e4356949a..5375689b7 100644 Binary files a/src/images/pause_icon.png and b/src/images/pause_icon.png differ diff --git a/src/images/settings_icon.png b/src/images/settings_icon.png index 6fd024e36..c88cd7a6f 100644 Binary files a/src/images/settings_icon.png and b/src/images/settings_icon.png differ diff --git a/src/images/stop_icon.png b/src/images/stop_icon.png index 86d772b6e..74c615f65 100644 Binary files a/src/images/stop_icon.png and b/src/images/stop_icon.png differ diff --git a/src/imgui/imgui_config.h b/src/imgui/imgui_config.h index 2094d56bc..ccb084d94 100644 --- a/src/imgui/imgui_config.h +++ b/src/imgui/imgui_config.h @@ -21,7 +21,6 @@ extern void assert_fail_debug_msg(const char* msg); } \ }()) -#define IMGUI_USE_WCHAR32 #define IMGUI_ENABLE_STB_TRUETYPE #define IMGUI_DEFINE_MATH_OPERATORS @@ -29,4 +28,8 @@ extern void assert_fail_debug_msg(const char* msg); constexpr ImVec2(float _v) : x(_v), y(_v) {} #define IM_VEC4_CLASS_EXTRA \ - constexpr ImVec4(float _v) : x(_v), y(_v), z(_v), w(_v) {} \ No newline at end of file + constexpr ImVec4(float _v) : x(_v), y(_v), z(_v), w(_v) {} + +#ifdef IMGUI_USE_WCHAR32 +#error "This project uses 16 bits wchar standard like Orbis" +#endif \ No newline at end of file diff --git a/src/imgui/imgui_std.h b/src/imgui/imgui_std.h index 168204ea8..cd7208064 100644 --- a/src/imgui/imgui_std.h +++ b/src/imgui/imgui_std.h @@ -25,18 +25,23 @@ inline float FastInFastOutCubic(float x) { } // namespace Easing -inline void CentralizeWindow() { +inline void CentralizeNextWindow() { const auto display_size = GetIO().DisplaySize; SetNextWindowPos(display_size / 2.0f, ImGuiCond_Always, {0.5f}); } +inline void CentralizeWindow() { + const auto display_size = GetIO().DisplaySize - GetCurrentWindowRead()->SizeFull; + SetWindowPos(display_size / 2.0f); +} + inline void KeepWindowInside(ImVec2 display_size = GetIO().DisplaySize) { const auto cur_pos = GetWindowPos(); if (cur_pos.x < 0.0f || cur_pos.y < 0.0f) { SetWindowPos(ImMax(cur_pos, ImVec2(0.0f, 0.0f))); return; } - const auto cur_size = GetWindowSize(); + const auto cur_size = GetCurrentWindowRead()->SizeFull; const auto bottom_right = cur_pos + cur_size; if (bottom_right.x > display_size.x || bottom_right.y > display_size.y) { const auto max_pos = display_size - cur_size; diff --git a/src/imgui/renderer/imgui_impl_sdl3.cpp b/src/imgui/renderer/imgui_impl_sdl3.cpp index bb194bff7..230d396f0 100644 --- a/src/imgui/renderer/imgui_impl_sdl3.cpp +++ b/src/imgui/renderer/imgui_impl_sdl3.cpp @@ -4,6 +4,7 @@ // Based on imgui_impl_sdl3.cpp from Dear ImGui repository #include +#include "common/config.h" #include "imgui_impl_sdl3.h" // SDL @@ -36,6 +37,8 @@ struct SdlData { SDL_Cursor* mouse_cursors[ImGuiMouseCursor_COUNT]{}; SDL_Cursor* mouse_last_cursor{}; int mouse_pending_leave_frame{}; + ImVec2 prev_mouse_pos{0, 0}; + Uint64 lastCursorMoveTime{}; // Gamepad handling ImVector gamepads{}; @@ -371,6 +374,13 @@ bool ProcessEvent(const SDL_Event* event) { ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse); io.AddMousePosEvent(mouse_pos.x, mouse_pos.y); + if (mouse_pos.x != bd->prev_mouse_pos.x || mouse_pos.y != bd->prev_mouse_pos.y) { + bd->prev_mouse_pos.x = mouse_pos.x; + bd->prev_mouse_pos.y = mouse_pos.y; + if (Config::getCursorState() == Config::HideCursorState::Idle) { + bd->lastCursorMoveTime = bd->time; + } + } return true; } case SDL_EVENT_MOUSE_WHEEL: { @@ -447,6 +457,7 @@ bool ProcessEvent(const SDL_Event* event) { return false; bd->mouse_window_id = event->window.windowID; bd->mouse_pending_leave_frame = 0; + bd->lastCursorMoveTime = bd->time; return true; } // - In some cases, when detaching a window from main viewport SDL may send @@ -459,6 +470,7 @@ bool ProcessEvent(const SDL_Event* event) { if (GetViewportForWindowId(event->window.windowID) == NULL) return false; bd->mouse_pending_leave_frame = ImGui::GetFrameCount() + 1; + bd->lastCursorMoveTime = bd->time; return true; } case SDL_EVENT_WINDOW_FOCUS_GAINED: @@ -581,7 +593,7 @@ static void UpdateMouseData() { // (below) // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries // shouldn't e.g. trigger other operations outside - SDL_CaptureMouse((bd->mouse_buttons_down != 0) ? SDL_TRUE : SDL_FALSE); + SDL_CaptureMouse((bd->mouse_buttons_down != 0) ? true : false); SDL_Window* focused_window = SDL_GetKeyboardFocus(); const bool is_app_focused = (bd->window == focused_window); @@ -600,7 +612,18 @@ static void UpdateMouseData() { int window_x, window_y; SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global); SDL_GetWindowPosition(focused_window, &window_x, &window_y); - io.AddMousePosEvent(mouse_x_global - (float)window_x, mouse_y_global - (float)window_y); + mouse_x_global -= (float)window_x; + mouse_y_global -= (float)window_y; + io.AddMousePosEvent(mouse_x_global, mouse_y_global); + // SDL_EVENT_MOUSE_MOTION isn't triggered before the first frame is rendered + // force update the prev_cursor coords + if (mouse_x_global != bd->prev_mouse_pos.x || mouse_y_global != bd->prev_mouse_pos.y && + bd->prev_mouse_pos.y == 0 && + bd->prev_mouse_pos.x == 0) { + bd->prev_mouse_pos.x = mouse_x_global; + bd->prev_mouse_pos.y = mouse_y_global; + bd->lastCursorMoveTime = bd->time; + } } } } @@ -611,10 +634,25 @@ static void UpdateMouseCursor() { return; SdlData* bd = GetBackendData(); + s16 cursorState = Config::getCursorState(); ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); - if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) { + if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None || + cursorState == Config::HideCursorState::Always) { // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor SDL_HideCursor(); + + } else if (cursorState == Config::HideCursorState::Idle && + bd->time - bd->lastCursorMoveTime >= + Config::getCursorHideTimeout() * SDL_GetPerformanceFrequency()) { + + bool wasCursorVisible = SDL_CursorVisible(); + SDL_HideCursor(); + + if (wasCursorVisible) { + SDL_WarpMouseInWindow(SDL_GetKeyboardFocus(), bd->prev_mouse_pos.x, + bd->prev_mouse_pos.y); // Force refresh the cursor state + } + } else { // Show OS mouse cursor SDL_Cursor* expected_cursor = bd->mouse_cursors[imgui_cursor] diff --git a/src/imgui/renderer/texture_manager.cpp b/src/imgui/renderer/texture_manager.cpp index ba4a05d01..7f9c69d49 100644 --- a/src/imgui/renderer/texture_manager.cpp +++ b/src/imgui/renderer/texture_manager.cpp @@ -7,6 +7,7 @@ #include #include "common/assert.h" +#include "common/config.h" #include "common/io_file.h" #include "common/polyfill_thread.h" #include "imgui_impl_vulkan.h" @@ -147,6 +148,11 @@ void WorkerLoop() { g_job_list.pop_front(); g_job_list_mtx.unlock(); + if (Config::vkCrashDiagnosticEnabled()) { + // FIXME: Crash diagnostic hangs when building the command buffer here + continue; + } + if (!path.empty()) { // Decode PNG from file Common::FS::IOFile file(path, Common::FS::FileAccessMode::Read); if (!file.IsOpen()) { diff --git a/src/input/controller.cpp b/src/input/controller.cpp index dcd8ed946..2187608e5 100644 --- a/src/input/controller.cpp +++ b/src/input/controller.cpp @@ -1,10 +1,13 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include "controller.h" + +#include "common/assert.h" #include "core/libraries/kernel/time_management.h" #include "core/libraries/pad/pad.h" -#include "input/controller.h" + +#include namespace Input { @@ -157,4 +160,23 @@ void GameController::TryOpenSDLController() { } } +u32 GameController::Poll() { + if (m_connected) { + auto time = Libraries::Kernel::sceKernelGetProcessTime(); + if (m_states_num == 0) { + auto diff = (time - m_last_state.time) / 1000; + if (diff >= 100) { + AddState(GetLastState()); + } + } else { + auto index = (m_first_state - 1 + m_states_num) % MAX_STATES; + auto diff = (time - m_states[index].time) / 1000; + if (m_private[index].obtained && diff >= 100) { + AddState(GetLastState()); + } + } + } + return 100; +} + } // namespace Input diff --git a/src/input/controller.h b/src/input/controller.h index 0a0d663a1..01ea21c0c 100644 --- a/src/input/controller.h +++ b/src/input/controller.h @@ -56,6 +56,7 @@ public: bool SetVibration(u8 smallMotor, u8 largeMotor); void SetTouchpadState(int touchIndex, bool touchDown, float x, float y); void TryOpenSDLController(); + u32 Poll(); private: struct StateInternal { diff --git a/src/qt_gui/cheats_patches.cpp b/src/qt_gui/cheats_patches.cpp index c044c2c3c..6e2e4f208 100644 --- a/src/qt_gui/cheats_patches.cpp +++ b/src/qt_gui/cheats_patches.cpp @@ -32,8 +32,6 @@ #include "common/path_util.h" #include "core/module.h" -using namespace Common::FS; - CheatsPatches::CheatsPatches(const QString& gameName, const QString& gameSerial, const QString& gameVersion, const QString& gameSize, const QPixmap& gameImage, QWidget* parent) @@ -776,6 +774,7 @@ void CheatsPatches::downloadPatches(const QString repository, const bool showMes // Create the files.json file with the identification of which file to open createFilesJson(repository); populateFileListPatches(); + compatibleVersionNotice(repository); } else { if (showMessageBox) { QMessageBox::warning(this, tr("Error"), @@ -787,6 +786,84 @@ void CheatsPatches::downloadPatches(const QString repository, const bool showMes }); } +void CheatsPatches::compatibleVersionNotice(const QString repository) { + QDir patchesDir(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir)); + QDir dir = patchesDir.filePath(repository); + + QStringList xmlFiles = dir.entryList(QStringList() << "*.xml", QDir::Files); + QSet appVersionsSet; + + foreach (const QString& xmlFile, xmlFiles) { + QFile file(dir.filePath(xmlFile)); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox::warning(this, tr("ERROR"), + QString(tr("Failed to open file:") + "\n%1").arg(xmlFile)); + continue; + } + + QXmlStreamReader xmlReader(&file); + bool foundMatchingID = false; + + while (!xmlReader.atEnd() && !xmlReader.hasError()) { + QXmlStreamReader::TokenType token = xmlReader.readNext(); + if (token == QXmlStreamReader::StartElement) { + if (xmlReader.name() == QStringLiteral("ID")) { + QString id = xmlReader.readElementText(); + if (id == m_gameSerial) { + foundMatchingID = true; + } + } else if (xmlReader.name() == QStringLiteral("Metadata")) { + if (foundMatchingID) { + QString appVer = xmlReader.attributes().value("AppVer").toString(); + if (!appVer.isEmpty()) { + appVersionsSet.insert(appVer); + } + } + } + } + } + + if (xmlReader.hasError()) { + QMessageBox::warning(this, tr("ERROR"), + QString(tr("XML ERROR:") + "\n%1").arg(xmlReader.errorString())); + } + + if (foundMatchingID) { + QStringList incompatibleVersions; + bool hasMatchingVersion = false; + + foreach (const QString& appVer, appVersionsSet) { + if (appVer != m_gameVersion) { + incompatibleVersions.append(appVer); + } else { + hasMatchingVersion = true; + } + } + + if (!incompatibleVersions.isEmpty() || + (hasMatchingVersion && incompatibleVersions.isEmpty())) { + QString message; + + if (!incompatibleVersions.isEmpty()) { + QString versionsList = incompatibleVersions.join(", "); + message += QString(tr("The game is in version: %1")).arg(m_gameVersion) + "\n" + + QString(tr("The downloaded patch only works on version: %1")) + .arg(versionsList); + + if (hasMatchingVersion) { + message += QString(", %1").arg(m_gameVersion); + } + message += QString("\n" + tr("You may need to update your game.")); + } + + if (!message.isEmpty()) { + QMessageBox::information(this, tr("Incompatibility Notice"), message); + } + } + } + } +} + void CheatsPatches::createFilesJson(const QString& repository) { QDir dir(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir)); @@ -1126,8 +1203,8 @@ void CheatsPatches::addPatchesToLayout(const QString& filePath) { } } - // Remove the item from the list view if no patches were added (the game has patches, but not - // for the current version) + // Remove the item from the list view if no patches were added + // (the game has patches, but not for the current version) if (!patchAdded) { QStringListModel* model = qobject_cast(patchesListView->model()); if (model) { @@ -1154,12 +1231,6 @@ void CheatsPatches::updateNoteTextEdit(const QString& patchName) { QString type = lineObject["Type"].toString(); QString address = lineObject["Address"].toString(); QString patchValue = lineObject["Value"].toString(); - - // add the values ​​to be modified in instructionsTextEdit - // text.append(QString("\nType: %1\nAddress: %2\n\nValue: %3") - // .arg(type) - // .arg(address) - // .arg(patchValue)); } text.replace("\\n", "\n"); instructionsTextEdit->setText(text); diff --git a/src/qt_gui/cheats_patches.h b/src/qt_gui/cheats_patches.h index a9932886c..b07e828c2 100644 --- a/src/qt_gui/cheats_patches.h +++ b/src/qt_gui/cheats_patches.h @@ -32,11 +32,11 @@ public: const QString& gameSize, const QPixmap& gameImage, QWidget* parent = nullptr); ~CheatsPatches(); - // Public Methods void downloadCheats(const QString& source, const QString& m_gameSerial, const QString& m_gameVersion, bool showMessageBox); void downloadPatches(const QString repository, const bool showMessageBox); void createFilesJson(const QString& repository); + void compatibleVersionNotice(const QString repository); signals: void downloadFinished(); diff --git a/src/qt_gui/check_update.cpp b/src/qt_gui/check_update.cpp index 023c6e7bb..ca6009ca3 100644 --- a/src/qt_gui/check_update.cpp +++ b/src/qt_gui/check_update.cpp @@ -374,11 +374,17 @@ void CheckUpdate::Install() { QString userPath; Common::FS::PathToQString(userPath, Common::FS::GetUserPath(Common::FS::PathType::UserDir)); - QString startingUpdate = tr("Starting Update..."); - QString tempDirPath = userPath + "/temp_download_update"; QString rootPath; Common::FS::PathToQString(rootPath, std::filesystem::current_path()); + QString tempDirPath = userPath + "/temp_download_update"; + QString startingUpdate = tr("Starting Update..."); + + QString binaryStartingUpdate; + for (QChar c : startingUpdate) { + binaryStartingUpdate.append(QString::number(c.unicode(), 2).rightJustified(16, '0')); + } + QString scriptContent; QString scriptFileName; QStringList arguments; @@ -389,7 +395,13 @@ void CheckUpdate::Install() { scriptFileName = tempDirPath + "/update.ps1"; scriptContent = QStringLiteral( "Set-ExecutionPolicy Bypass -Scope Process -Force\n" - "Write-Output '%1'\n" + "$binaryStartingUpdate = '%1'\n" + "$chars = @()\n" + "for ($i = 0; $i -lt $binaryStartingUpdate.Length; $i += 16) {\n" + " $chars += [char]([convert]::ToInt32($binaryStartingUpdate.Substring($i, 16), 2))\n" + "}\n" + "$startingUpdate = -join $chars\n" + "Write-Output $startingUpdate\n" "Expand-Archive -Path '%2\\temp_download_update.zip' -DestinationPath '%2' -Force\n" "Start-Sleep -Seconds 3\n" "Copy-Item -Recurse -Force '%2\\*' '%3\\'\n" @@ -454,6 +466,10 @@ void CheckUpdate::Install() { " sleep 2\n" " extract_file\n" " sleep 2\n" + " if pgrep -f \"Shadps4-qt.AppImage\" > /dev/null; then\n" + " pkill -f \"Shadps4-qt.AppImage\"\n" + " sleep 2\n" + " fi\n" " cp -r \"%2/\"* \"%3/\"\n" " sleep 2\n" " rm \"%3/update.sh\"\n" @@ -508,7 +524,12 @@ void CheckUpdate::Install() { QFile scriptFile(scriptFileName); if (scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream out(&scriptFile); +#ifdef Q_OS_WIN + out << scriptContent.arg(binaryStartingUpdate).arg(tempDirPath).arg(rootPath); +#endif +#if defined(Q_OS_LINUX) || defined(Q_OS_MAC) out << scriptContent.arg(startingUpdate).arg(tempDirPath).arg(rootPath); +#endif scriptFile.close(); // Make the script executable on Unix-like systems @@ -525,4 +546,4 @@ void CheckUpdate::Install() { this, tr("Error"), QString(tr("Failed to create the update script file") + ":\n" + scriptFileName)); } -} \ No newline at end of file +} diff --git a/src/qt_gui/game_info.cpp b/src/qt_gui/game_info.cpp index 6e8d89713..48643f8ed 100644 --- a/src/qt_gui/game_info.cpp +++ b/src/qt_gui/game_info.cpp @@ -10,14 +10,16 @@ GameInfoClass::GameInfoClass() = default; GameInfoClass::~GameInfoClass() = default; void GameInfoClass::GetGameInfo(QWidget* parent) { - QString installDir; - Common::FS::PathToQString(installDir, Config::getGameInstallDir()); QStringList filePaths; - QDir parentFolder(installDir); - QFileInfoList fileList = parentFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); - for (const auto& fileInfo : fileList) { - if (fileInfo.isDir()) { - filePaths.append(fileInfo.absoluteFilePath()); + for (const auto& installLoc : Config::getGameInstallDirs()) { + QString installDir; + Common::FS::PathToQString(installDir, installLoc); + QDir parentFolder(installDir); + QFileInfoList fileList = parentFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const auto& fileInfo : fileList) { + if (fileInfo.isDir() && !fileInfo.filePath().endsWith("-UPDATE")) { + filePaths.append(fileInfo.absoluteFilePath()); + } } } m_games = QtConcurrent::mapped(filePaths, [&](const QString& path) { diff --git a/src/qt_gui/game_info.h b/src/qt_gui/game_info.h index 9db25482a..99805cd52 100644 --- a/src/qt_gui/game_info.h +++ b/src/qt_gui/game_info.h @@ -25,9 +25,15 @@ public: static GameInfo readGameInfo(const std::filesystem::path& filePath) { GameInfo game; game.path = filePath; + std::filesystem::path sce_folder_path = filePath / "sce_sys" / "param.sfo"; + std::filesystem::path game_update_path = filePath; + game_update_path += "-UPDATE"; + if (std::filesystem::exists(game_update_path / "sce_sys" / "param.sfo")) { + sce_folder_path = game_update_path / "sce_sys" / "param.sfo"; + } PSF psf; - if (psf.Open(game.path / "sce_sys" / "param.sfo")) { + if (psf.Open(sce_folder_path)) { game.icon_path = game.path / "sce_sys" / "icon0.png"; QString iconpath; Common::FS::PathToQString(iconpath, game.icon_path); @@ -60,6 +66,9 @@ public: if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) { game.version = *app_ver; } + if (const auto play_time = psf.GetString("PLAY_TIME"); play_time.has_value()) { + game.play_time = *play_time; + } } return game; } diff --git a/src/qt_gui/game_install_dialog.cpp b/src/qt_gui/game_install_dialog.cpp index 11daf2de0..e53c58315 100644 --- a/src/qt_gui/game_install_dialog.cpp +++ b/src/qt_gui/game_install_dialog.cpp @@ -51,7 +51,9 @@ QWidget* GameInstallDialog::SetupGamesDirectory() { // Input. m_gamesDirectory = new QLineEdit(); QString install_dir; - Common::FS::PathToQString(install_dir, Config::getGameInstallDir()); + std::filesystem::path install_path = + Config::getGameInstallDirs().empty() ? "" : Config::getGameInstallDirs().front(); + Common::FS::PathToQString(install_dir, install_path); m_gamesDirectory->setText(install_dir); m_gamesDirectory->setMinimumWidth(400); @@ -124,8 +126,7 @@ void GameInstallDialog::Save() { return; } } - - Config::setGameInstallDir(Common::FS::PathFromQString(gamesDirectory)); + Config::addGameInstallDir(Common::FS::PathFromQString(gamesDirectory)); Config::setAddonInstallDir(Common::FS::PathFromQString(addonsDirectory)); const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::save(config_dir / "config.toml"); diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index c2f6736b8..99628b083 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -24,16 +24,17 @@ GameListFrame::GameListFrame(std::shared_ptr game_info_get, QWidg this->horizontalHeader()->setSortIndicatorShown(true); this->horizontalHeader()->setStretchLastSection(true); this->setContextMenuPolicy(Qt::CustomContextMenu); - this->setColumnCount(8); + this->setColumnCount(9); this->setColumnWidth(1, 300); // Name this->setColumnWidth(2, 120); // Serial this->setColumnWidth(3, 90); // Region this->setColumnWidth(4, 90); // Firmware this->setColumnWidth(5, 90); // Size this->setColumnWidth(6, 90); // Version + this->setColumnWidth(7, 100); // Play Time QStringList headers; headers << tr("Icon") << tr("Name") << tr("Serial") << tr("Region") << tr("Firmware") - << tr("Size") << tr("Version") << tr("Path"); + << tr("Size") << tr("Version") << tr("Play Time") << tr("Path"); this->setHorizontalHeaderLabels(headers); this->horizontalHeader()->setSortIndicatorShown(true); this->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); @@ -100,9 +101,37 @@ void GameListFrame::PopulateGameList() { SetTableItem(i, 4, QString::fromStdString(m_game_info->m_games[i].fw)); SetTableItem(i, 5, QString::fromStdString(m_game_info->m_games[i].size)); SetTableItem(i, 6, QString::fromStdString(m_game_info->m_games[i].version)); + + QString playTime = GetPlayTime(m_game_info->m_games[i].serial); + if (playTime.isEmpty()) { + m_game_info->m_games[i].play_time = "0:00:00"; + SetTableItem(i, 7, "0"); + } else { + QStringList timeParts = playTime.split(':'); + int hours = timeParts[0].toInt(); + int minutes = timeParts[1].toInt(); + int seconds = timeParts[2].toInt(); + + QString formattedPlayTime; + if (hours > 0) { + formattedPlayTime += QString("%1h ").arg(hours); + } + if (minutes > 0) { + formattedPlayTime += QString("%1m ").arg(minutes); + } + + formattedPlayTime = formattedPlayTime.trimmed(); + m_game_info->m_games[i].play_time = playTime.toStdString(); + if (formattedPlayTime.isEmpty()) { + SetTableItem(i, 7, "0"); + } else { + SetTableItem(i, 7, formattedPlayTime); + } + } + QString path; Common::FS::PathToQString(path, m_game_info->m_games[i].path); - SetTableItem(i, 7, path); + SetTableItem(i, 8, path); } } @@ -171,7 +200,7 @@ void GameListFrame::ResizeIcons(int iconSize) { this->setItem(index, 0, iconItem); index++; } - this->horizontalHeader()->setSectionResizeMode(7, QHeaderView::ResizeToContents); + this->horizontalHeader()->setSectionResizeMode(8, QHeaderView::ResizeToContents); } void GameListFrame::SetTableItem(int row, int column, QString itemStr) { @@ -224,3 +253,33 @@ void GameListFrame::SetRegionFlag(int row, int column, QString itemStr) { this->setItem(row, column, item); this->setCellWidget(row, column, widget); } + +QString GameListFrame::GetPlayTime(const std::string& serial) { + QString playTime; + const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + QString filePath = QString::fromStdString((user_dir / "play_time.txt").string()); + + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return playTime; + } + + while (!file.atEnd()) { + QByteArray line = file.readLine(); + QString lineStr = QString::fromUtf8(line).trimmed(); + + QStringList parts = lineStr.split(' '); + if (parts.size() >= 2) { + QString fileSerial = parts[0]; + QString time = parts[1]; + + if (fileSerial == QString::fromStdString(serial)) { + playTime = time; + break; + } + } + } + + file.close(); + return playTime; +} diff --git a/src/qt_gui/game_list_frame.h b/src/qt_gui/game_list_frame.h index 957ac7318..6da2734a8 100644 --- a/src/qt_gui/game_list_frame.h +++ b/src/qt_gui/game_list_frame.h @@ -29,6 +29,7 @@ public Q_SLOTS: private: void SetTableItem(int row, int column, QString itemStr); void SetRegionFlag(int row, int column, QString itemStr); + QString GetPlayTime(const std::string& serial); QList m_columnActs; GameInfoClass* game_inf_get = nullptr; bool ListSortedAsc = true; @@ -68,6 +69,8 @@ public: case 6: return a.version < b.version; case 7: + return a.play_time < b.play_time; + case 8: return a.path < b.path; default: return false; @@ -89,9 +92,11 @@ public: case 6: return a.version > b.version; case 7: + return a.play_time > b.play_time; + case 8: return a.path > b.path; default: return false; } } -}; \ No newline at end of file +}; diff --git a/src/qt_gui/game_list_utils.h b/src/qt_gui/game_list_utils.h index f62275eff..3d710c5b7 100644 --- a/src/qt_gui/game_list_utils.h +++ b/src/qt_gui/game_list_utils.h @@ -19,6 +19,8 @@ struct GameInfo { std::string version = "Unknown"; std::string region = "Unknown"; std::string fw = "Unknown"; + + std::string play_time = "Unknown"; }; class GameListUtils { diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 4eb657572..8d7018522 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -68,6 +68,18 @@ public: menu.addMenu(copyMenu); + // "Delete..." submenu. + QMenu* deleteMenu = new QMenu(tr("Delete..."), widget); + QAction* deleteGame = new QAction(tr("Delete Game"), widget); + QAction* deleteUpdate = new QAction(tr("Delete Update"), widget); + QAction* deleteDLC = new QAction(tr("Delete DLC"), widget); + + deleteMenu->addAction(deleteGame); + deleteMenu->addAction(deleteUpdate); + deleteMenu->addAction(deleteDLC); + + menu.addMenu(deleteMenu); + // Show menu. auto selected = menu.exec(global_pos); if (!selected) { @@ -82,7 +94,13 @@ public: if (selected == &openSfoViewer) { PSF psf; - if (psf.Open(std::filesystem::path(m_games[itemID].path) / "sce_sys" / "param.sfo")) { + QString game_update_path; + Common::FS::PathToQString(game_update_path, m_games[itemID].path.concat("-UPDATE")); + std::filesystem::path game_folder_path = m_games[itemID].path; + if (std::filesystem::exists(Common::FS::PathFromQString(game_update_path))) { + game_folder_path = Common::FS::PathFromQString(game_update_path); + } + if (psf.Open(game_folder_path / "sce_sys" / "param.sfo")) { int rows = psf.GetEntries().size(); QTableWidget* tableWidget = new QTableWidget(rows, 2); tableWidget->setAttribute(Qt::WA_DeleteOnClose); @@ -269,6 +287,54 @@ public: .arg(QString::fromStdString(m_games[itemID].size)); clipboard->setText(combinedText); } + + if (selected == deleteGame || selected == deleteUpdate || selected == deleteDLC) { + bool error = false; + QString folder_path, game_update_path, dlc_path; + Common::FS::PathToQString(folder_path, m_games[itemID].path); + Common::FS::PathToQString(game_update_path, m_games[itemID].path.concat("-UPDATE")); + Common::FS::PathToQString( + dlc_path, Config::getAddonInstallDir() / + Common::FS::PathFromQString(folder_path).parent_path().filename()); + QString message_type = tr("Game"); + + if (selected == deleteUpdate) { + if (!Config::getSeparateUpdateEnabled()) { + QMessageBox::critical(nullptr, tr("Error"), + QString(tr("requiresEnableSeparateUpdateFolder_MSG"))); + error = true; + } else if (!std::filesystem::exists( + Common::FS::PathFromQString(game_update_path))) { + QMessageBox::critical(nullptr, tr("Error"), + QString(tr("This game has no update to delete!"))); + error = true; + } else { + folder_path = game_update_path; + message_type = tr("Update"); + } + } else if (selected == deleteDLC) { + if (!std::filesystem::exists(Common::FS::PathFromQString(dlc_path))) { + QMessageBox::critical(nullptr, tr("Error"), + QString(tr("This game has no DLC to delete!"))); + error = true; + } else { + folder_path = dlc_path; + message_type = tr("DLC"); + } + } + if (!error) { + QString gameName = QString::fromStdString(m_games[itemID].name); + QDir dir(folder_path); + QMessageBox::StandardButton reply = QMessageBox::question( + nullptr, QString(tr("Delete %1")).arg(message_type), + QString(tr("Are you sure you want to delete %1's %2 directory?")) + .arg(gameName, message_type), + QMessageBox::Yes | QMessageBox::No); + if (reply == QMessageBox::Yes) { + dir.removeRecursively(); + } + } + } } int GetRowIndex(QTreeWidget* treeWidget, QTreeWidgetItem* item) { diff --git a/src/qt_gui/install_dir_select.cpp b/src/qt_gui/install_dir_select.cpp new file mode 100644 index 000000000..e0951b123 --- /dev/null +++ b/src/qt_gui/install_dir_select.cpp @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "install_dir_select.h" + +InstallDirSelect::InstallDirSelect() : selected_dir() { + selected_dir = Config::getGameInstallDirs().empty() ? "" : Config::getGameInstallDirs().front(); + + if (!Config::getGameInstallDirs().empty() && Config::getGameInstallDirs().size() == 1) { + reject(); + } + + auto layout = new QVBoxLayout(this); + + layout->addWidget(SetupInstallDirList()); + layout->addStretch(); + layout->addWidget(SetupDialogActions()); + + setWindowTitle(tr("shadPS4 - Choose directory")); + setWindowIcon(QIcon(":images/shadps4.ico")); +} + +InstallDirSelect::~InstallDirSelect() {} + +QWidget* InstallDirSelect::SetupInstallDirList() { + auto group = new QGroupBox(tr("Select which directory you want to install to.")); + auto vlayout = new QVBoxLayout(); + + auto m_path_list = new QListWidget(); + QList qt_list; + for (const auto& str : Config::getGameInstallDirs()) { + QString installDirPath; + Common::FS::PathToQString(installDirPath, str); + qt_list.append(installDirPath); + } + m_path_list->insertItems(0, qt_list); + m_path_list->setSpacing(1); + + connect(m_path_list, &QListWidget::itemClicked, this, &InstallDirSelect::setSelectedDirectory); + connect(m_path_list, &QListWidget::itemActivated, this, + &InstallDirSelect::setSelectedDirectory); + + vlayout->addWidget(m_path_list); + + group->setLayout(vlayout); + return group; +} + +void InstallDirSelect::setSelectedDirectory(QListWidgetItem* item) { + if (item) { + const auto highlighted_path = Common::FS::PathFromQString(item->text()); + if (!highlighted_path.empty()) { + selected_dir = highlighted_path; + } + } +} + +QWidget* InstallDirSelect::SetupDialogActions() { + auto actions = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + connect(actions, &QDialogButtonBox::accepted, this, &InstallDirSelect::accept); + connect(actions, &QDialogButtonBox::rejected, this, &InstallDirSelect::reject); + + return actions; +} diff --git a/src/qt_gui/install_dir_select.h b/src/qt_gui/install_dir_select.h new file mode 100644 index 000000000..e3e81575a --- /dev/null +++ b/src/qt_gui/install_dir_select.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/config.h" +#include "common/path_util.h" + +class QLineEdit; + +class InstallDirSelect final : public QDialog { + Q_OBJECT + +public: + InstallDirSelect(); + ~InstallDirSelect(); + + std::filesystem::path getSelectedDirectory() { + return selected_dir; + } + +private: + QWidget* SetupInstallDirList(); + QWidget* SetupDialogActions(); + void setSelectedDirectory(QListWidgetItem* item); + std::filesystem::path selected_dir; +}; diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp index 8c565a19b..da8804f69 100644 --- a/src/qt_gui/main.cpp +++ b/src/qt_gui/main.cpp @@ -30,7 +30,7 @@ int main(int argc, char* argv[]) { bool has_command_line_argument = argc > 1; // Check if the game install directory is set - if (Config::getGameInstallDir().empty() && !has_command_line_argument) { + if (Config::getGameInstallDirs().empty() && !has_command_line_argument) { GameInstallDialog dlg; dlg.exec(); } diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 8d8e17177..e52820102 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -16,6 +16,7 @@ #include "core/file_format/pkg.h" #include "core/loader.h" #include "game_install_dialog.h" +#include "install_dir_select.h" #include "main_window.h" #include "settings_dialog.h" #include "video_core/renderer_vulkan/vk_instance.h" @@ -58,6 +59,7 @@ bool MainWindow::Init() { this->show(); // load game list LoadGameLists(); + // Check for update CheckUpdateMain(true); auto end = std::chrono::steady_clock::now(); @@ -69,6 +71,14 @@ bool MainWindow::Init() { QString statusMessage = "Games: " + QString::number(numGames) + " (" + QString::number(duration.count()) + "ms)"; statusBar->showMessage(statusMessage); + + // Initialize Discord RPC + if (Config::getEnableDiscordRPC()) { + auto* rpc = Common::Singleton::Instance(); + rpc->init(); + rpc->setStatusIdling(); + } + return true; } @@ -663,10 +673,17 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason)); return; } - auto extract_path = Config::getGameInstallDir() / pkg.GetTitleID(); + InstallDirSelect ids; + ids.exec(); + auto game_install_dir = ids.getSelectedDirectory(); + auto game_folder_path = game_install_dir / pkg.GetTitleID(); QString pkgType = QString::fromStdString(pkg.GetPkgFlags()); + bool use_game_update = pkgType.contains("PATCH") && Config::getSeparateUpdateEnabled(); + auto game_update_path = use_game_update + ? game_install_dir / (std::string(pkg.GetTitleID()) + "-UPDATE") + : game_folder_path; QString gameDirPath; - Common::FS::PathToQString(gameDirPath, extract_path); + Common::FS::PathToQString(gameDirPath, game_folder_path); QDir game_dir(gameDirPath); if (game_dir.exists()) { QMessageBox msgBox; @@ -702,7 +719,11 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no APP_VER"); return; } - psf.Open(extract_path / "sce_sys" / "param.sfo"); + std::filesystem::path sce_folder_path = + std::filesystem::exists(game_update_path / "sce_sys" / "param.sfo") + ? game_update_path / "sce_sys" / "param.sfo" + : game_folder_path / "sce_sys" / "param.sfo"; + psf.Open(sce_folder_path); QString game_app_version; if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) { game_app_version = QString::fromStdString(std::string{*app_ver}); @@ -751,7 +772,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int addonMsgBox.setDefaultButton(QMessageBox::No); int result = addonMsgBox.exec(); if (result == QMessageBox::Yes) { - extract_path = addon_extract_path; + game_update_path = addon_extract_path; } else { return; } @@ -762,7 +783,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int msgBox.setDefaultButton(QMessageBox::No); int result = msgBox.exec(); if (result == QMessageBox::Yes) { - extract_path = addon_extract_path; + game_update_path = addon_extract_path; } else { return; } @@ -788,8 +809,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int } // what else? } - - if (!pkg.Extract(file, extract_path, failreason)) { + if (!pkg.Extract(file, game_update_path, failreason)) { QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason)); } else { int nfiles = pkg.GetNumberOfFiles(); @@ -812,7 +832,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int connect(&futureWatcher, &QFutureWatcher::finished, this, [=, this]() { if (pkgNum == nPkg) { QString path; - Common::FS::PathToQString(path, Config::getGameInstallDir()); + Common::FS::PathToQString(path, game_install_dir); QMessageBox extractMsgBox(this); extractMsgBox.setWindowTitle(tr("Extraction Finished")); extractMsgBox.setText( diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index a428f4317..6264978aa 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -9,6 +9,7 @@ #include "background_music_player.h" #include "common/config.h" +#include "common/discord_rpc_handler.h" #include "common/path_util.h" #include "core/file_format/psf.h" #include "core/file_sys/fs.h" diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 45b2ab4e0..373b2924e 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -190,7 +190,7 @@ public: settingsButton = new QPushButton(centralWidget); settingsButton->setFlat(true); settingsButton->setIcon(QIcon(":images/settings_icon.png")); - settingsButton->setIconSize(QSize(40, 40)); + settingsButton->setIconSize(QSize(44, 44)); controllerButton = new QPushButton(centralWidget); controllerButton->setFlat(true); controllerButton->setIcon(QIcon(":images/controller_icon.png")); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 4a42e1961..309b40b46 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -51,6 +51,7 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge : QDialog(parent), ui(new Ui::SettingsDialog) { ui->setupUi(this); ui->tabWidgetSettings->setUsesScrollButtons(false); + initialHeight = this->height(); const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); ui->buttonBox->button(QDialogButtonBox::StandardButton::Close)->setFocus(); @@ -67,6 +68,15 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge completer->setCaseSensitivity(Qt::CaseInsensitive); ui->consoleLanguageComboBox->setCompleter(completer); + ui->hideCursorComboBox->addItem(tr("Never")); + ui->hideCursorComboBox->addItem(tr("Idle")); + ui->hideCursorComboBox->addItem(tr("Always")); + + ui->backButtonBehaviorComboBox->addItem(tr("Touchpad Left"), "left"); + ui->backButtonBehaviorComboBox->addItem(tr("Touchpad Center"), "center"); + ui->backButtonBehaviorComboBox->addItem(tr("Touchpad Right"), "right"); + ui->backButtonBehaviorComboBox->addItem(tr("None"), "none"); + InitializeEmulatorLanguages(); LoadValuesFromConfig(); @@ -117,6 +127,9 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge connect(ui->fullscreenCheckBox, &QCheckBox::stateChanged, this, [](int val) { Config::setFullscreenMode(val); }); + connect(ui->separateUpdatesCheckBox, &QCheckBox::stateChanged, this, + [](int val) { Config::setSeparateUpdateEnabled(val); }); + connect(ui->showSplashCheckBox, &QCheckBox::stateChanged, this, [](int val) { Config::setShowSplash(val); }); @@ -151,6 +164,37 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge Config::setBGMvolume(val); BackgroundMusicPlayer::getInstance().setVolume(val); }); + + connect(ui->discordRPCCheckbox, &QCheckBox::stateChanged, this, [](int val) { + Config::setEnableDiscordRPC(val); + auto* rpc = Common::Singleton::Instance(); + if (val == Qt::Checked) { + rpc->init(); + rpc->setStatusIdling(); + } else { + rpc->shutdown(); + } + }); + } + + // Input TAB + { + connect(ui->hideCursorComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + [this](s16 index) { + Config::setCursorState(index); + OnCursorStateChanged(index); + }); + + connect(ui->idleTimeoutSpinBox, &QSpinBox::valueChanged, this, + [](int index) { Config::setCursorHideTimeout(index); }); + + connect(ui->backButtonBehaviorComboBox, QOverload::of(&QComboBox::currentIndexChanged), + this, [this](int index) { + if (index >= 0 && index < ui->backButtonBehaviorComboBox->count()) { + QString data = ui->backButtonBehaviorComboBox->itemData(index).toString(); + Config::setBackButtonBehavior(data.toStdString()); + } + }); } // GPU TAB @@ -176,6 +220,35 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge [](int val) { Config::setNullGpu(val); }); } + // PATH TAB + { + connect(ui->addFolderButton, &QPushButton::clicked, this, [this]() { + const auto config_dir = Config::getGameInstallDirs(); + QString file_path_string = + QFileDialog::getExistingDirectory(this, tr("Directory to install games")); + auto file_path = Common::FS::PathFromQString(file_path_string); + if (!file_path.empty() && Config::addGameInstallDir(file_path)) { + QListWidgetItem* item = new QListWidgetItem(file_path_string); + ui->gameFoldersListWidget->addItem(item); + } + }); + + connect(ui->gameFoldersListWidget, &QListWidget::itemSelectionChanged, this, [this]() { + ui->removeFolderButton->setEnabled( + !ui->gameFoldersListWidget->selectedItems().isEmpty()); + }); + + connect(ui->removeFolderButton, &QPushButton::clicked, this, [this]() { + QListWidgetItem* selected_item = ui->gameFoldersListWidget->currentItem(); + QString item_path_string = selected_item ? selected_item->text() : QString(); + if (!item_path_string.isEmpty()) { + auto file_path = Common::FS::PathFromQString(item_path_string); + Config::removeGameInstallDir(file_path); + delete selected_item; + } + }); + } + // DEBUG TAB { connect(ui->debugDump, &QCheckBox::stateChanged, this, @@ -197,14 +270,21 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge ui->consoleLanguageGroupBox->installEventFilter(this); ui->emulatorLanguageGroupBox->installEventFilter(this); ui->fullscreenCheckBox->installEventFilter(this); + ui->separateUpdatesCheckBox->installEventFilter(this); ui->showSplashCheckBox->installEventFilter(this); ui->ps4proCheckBox->installEventFilter(this); + ui->discordRPCCheckbox->installEventFilter(this); ui->userName->installEventFilter(this); ui->logTypeGroupBox->installEventFilter(this); ui->logFilter->installEventFilter(this); ui->updaterGroupBox->installEventFilter(this); ui->GUIgroupBox->installEventFilter(this); + // Input + ui->hideCursorGroupBox->installEventFilter(this); + ui->idleTimeoutGroupBox->installEventFilter(this); + ui->backButtonBehaviorGroupBox->installEventFilter(this); + // Graphics ui->graphicsAdapterGroupBox->installEventFilter(this); ui->widthGroupBox->installEventFilter(this); @@ -213,6 +293,12 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge ui->dumpShadersCheckBox->installEventFilter(this); ui->nullGpuCheckBox->installEventFilter(this); + // Paths + ui->gameFoldersGroupBox->installEventFilter(this); + ui->gameFoldersListWidget->installEventFilter(this); + ui->addFolderButton->installEventFilter(this); + ui->removeFolderButton->installEventFilter(this); + // Debug ui->debugDump->installEventFilter(this); ui->vkValidationCheckBox->installEventFilter(this); @@ -228,6 +314,9 @@ void SettingsDialog::LoadValuesFromConfig() { std::find(languageIndexes.begin(), languageIndexes.end(), Config::GetLanguage())) % languageIndexes.size()); ui->emulatorLanguageComboBox->setCurrentIndex(languages[Config::getEmulatorLanguage()]); + ui->hideCursorComboBox->setCurrentIndex(Config::getCursorState()); + OnCursorStateChanged(Config::getCursorState()); + ui->idleTimeoutSpinBox->setValue(Config::getCursorHideTimeout()); ui->graphicsAdapterBox->setCurrentIndex(Config::getGpuId() + 1); ui->widthSpinBox->setValue(Config::getScreenWidth()); ui->heightSpinBox->setValue(Config::getScreenHeight()); @@ -236,7 +325,9 @@ void SettingsDialog::LoadValuesFromConfig() { ui->nullGpuCheckBox->setChecked(Config::nullGpu()); ui->playBGMCheckBox->setChecked(Config::getPlayBGM()); ui->BGMVolumeSlider->setValue((Config::getBGMvolume())); + ui->discordRPCCheckbox->setChecked(Config::getEnableDiscordRPC()); ui->fullscreenCheckBox->setChecked(Config::isFullscreenMode()); + ui->separateUpdatesCheckBox->setChecked(Config::getSeparateUpdateEnabled()); ui->showSplashCheckBox->setChecked(Config::showSplash()); ui->ps4proCheckBox->setChecked(Config::isNeoMode()); ui->logTypeComboBox->setCurrentText(QString::fromStdString(Config::getLogType())); @@ -258,20 +349,48 @@ void SettingsDialog::LoadValuesFromConfig() { } } ui->updateComboBox->setCurrentText(QString::fromStdString(updateChannel)); + + for (const auto& dir : Config::getGameInstallDirs()) { + QString path_string; + Common::FS::PathToQString(path_string, dir); + QListWidgetItem* item = new QListWidgetItem(path_string); + ui->gameFoldersListWidget->addItem(item); + } + + QString backButtonBehavior = QString::fromStdString(Config::getBackButtonBehavior()); + int index = ui->backButtonBehaviorComboBox->findData(backButtonBehavior); + ui->backButtonBehaviorComboBox->setCurrentIndex(index != -1 ? index : 0); + + ui->removeFolderButton->setEnabled(!ui->gameFoldersListWidget->selectedItems().isEmpty()); } void SettingsDialog::InitializeEmulatorLanguages() { QDirIterator it(QStringLiteral(":/translations"), QDirIterator::NoIteratorFlags); - int idx = 0; + QVector> languagesList; + while (it.hasNext()) { QString locale = it.next(); locale.truncate(locale.lastIndexOf(QLatin1Char{'.'})); locale.remove(0, locale.lastIndexOf(QLatin1Char{'/'}) + 1); const QString lang = QLocale::languageToString(QLocale(locale).language()); const QString country = QLocale::territoryToString(QLocale(locale).territory()); - ui->emulatorLanguageComboBox->addItem(QStringLiteral("%1 (%2)").arg(lang, country), locale); + QString displayName = QStringLiteral("%1 (%2)").arg(lang, country); + languagesList.append(qMakePair(locale, displayName)); + } + + std::sort(languagesList.begin(), languagesList.end(), + [](const QPair& a, const QPair& b) { + return a.second < b.second; + }); + + int idx = 0; + for (const auto& pair : languagesList) { + const QString& locale = pair.first; + const QString& displayName = pair.second; + + ui->emulatorLanguageComboBox->addItem(displayName, locale); languages[locale.toStdString()] = idx; idx++; } @@ -289,6 +408,18 @@ void SettingsDialog::OnLanguageChanged(int index) { emit LanguageChanged(ui->emulatorLanguageComboBox->itemData(index).toString().toStdString()); } +void SettingsDialog::OnCursorStateChanged(s16 index) { + if (index == -1) + return; + if (index == Config::HideCursorState::Idle) { + ui->idleTimeoutGroupBox->show(); + } else { + if (!ui->idleTimeoutGroupBox->isHidden()) { + ui->idleTimeoutGroupBox->hide(); + } + } +} + int SettingsDialog::exec() { return QDialog::exec(); } @@ -305,10 +436,14 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("emulatorLanguageGroupBox"); } else if (elementName == "fullscreenCheckBox") { text = tr("fullscreenCheckBox"); + } else if (elementName == "separateUpdatesCheckBox") { + text = tr("separateUpdatesCheckBox"); } else if (elementName == "showSplashCheckBox") { text = tr("showSplashCheckBox"); } else if (elementName == "ps4proCheckBox") { text = tr("ps4proCheckBox"); + } else if (elementName == "discordRPCCheckbox") { + text = tr("discordRPCCheckbox"); } else if (elementName == "userName") { text = tr("userName"); } else if (elementName == "logTypeGroupBox") { @@ -321,6 +456,15 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("GUIgroupBox"); } + // Input + if (elementName == "hideCursorGroupBox") { + text = tr("hideCursorGroupBox"); + } else if (elementName == "idleTimeoutGroupBox") { + text = tr("idleTimeoutGroupBox"); + } else if (elementName == "backButtonBehaviorGroupBox") { + text = tr("backButtonBehaviorGroupBox"); + } + // Graphics if (elementName == "graphicsAdapterGroupBox") { text = tr("graphicsAdapterGroupBox"); @@ -334,8 +478,15 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("dumpShadersCheckBox"); } else if (elementName == "nullGpuCheckBox") { text = tr("nullGpuCheckBox"); - } else if (elementName == "dumpPM4CheckBox") { - text = tr("dumpPM4CheckBox"); + } + + // Path + if (elementName == "gameFoldersGroupBox" || elementName == "gameFoldersListWidget") { + text = tr("gameFoldersBox"); + } else if (elementName == "addFolderButton") { + text = tr("addFolderButton"); + } else if (elementName == "removeFolderButton") { + text = tr("removeFolderButton"); } // Debug @@ -352,7 +503,7 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { ui->descriptionText->setText(text.replace("\\n", "\n")); } -bool SettingsDialog::override(QObject* obj, QEvent* event) { +bool SettingsDialog::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::Enter || event->type() == QEvent::Leave) { if (qobject_cast(obj)) { bool hovered = (event->type() == QEvent::Enter); @@ -365,15 +516,22 @@ bool SettingsDialog::override(QObject* obj, QEvent* event) { } // if the text exceeds the size of the box, it will increase the size + QRect currentGeometry = this->geometry(); + int newWidth = currentGeometry.width(); + int documentHeight = ui->descriptionText->document()->size().height(); int visibleHeight = ui->descriptionText->viewport()->height(); if (documentHeight > visibleHeight) { - ui->descriptionText->setMinimumHeight(90); + ui->descriptionText->setMaximumSize(16777215, 110); + this->setGeometry(currentGeometry.x(), currentGeometry.y(), newWidth, + currentGeometry.height() + 40); } else { - ui->descriptionText->setMinimumHeight(70); + ui->descriptionText->setMaximumSize(16777215, 70); + this->setGeometry(currentGeometry.x(), currentGeometry.y(), newWidth, + initialHeight); } return true; } } return QDialog::eventFilter(obj, event); -} +} \ No newline at end of file diff --git a/src/qt_gui/settings_dialog.h b/src/qt_gui/settings_dialog.h index 71307d398..8cdded980 100644 --- a/src/qt_gui/settings_dialog.h +++ b/src/qt_gui/settings_dialog.h @@ -21,7 +21,7 @@ public: explicit SettingsDialog(std::span physical_devices, QWidget* parent = nullptr); ~SettingsDialog(); - bool override(QObject* obj, QEvent* event); + bool eventFilter(QObject* obj, QEvent* event) override; void updateNoteTextEdit(const QString& groupName); int exec() override; @@ -33,10 +33,13 @@ private: void LoadValuesFromConfig(); void InitializeEmulatorLanguages(); void OnLanguageChanged(int index); + void OnCursorStateChanged(s16 index); std::unique_ptr ui; std::map languages; QString defaultTextEdit; + + int initialHeight; }; diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index 4edec9e1b..cce728f65 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -12,7 +12,7 @@ 0 0 854 - 570 + 660 @@ -34,9 +34,18 @@ :/images/shadps4.ico:/images/shadps4.ico + + false + + + false + + + true + QFrame::Shape::NoFrame @@ -51,8 +60,8 @@ 0 0 - 836 - 446 + 832 + 431 @@ -125,6 +134,13 @@ + + + + Enable Separate Update Folder + + + @@ -139,6 +155,13 @@ + + + + Enable Discord Rich Presence + + + @@ -258,6 +281,9 @@ + + QLayout::SizeConstraint::SetDefaultConstraint + 0 @@ -270,8 +296,157 @@ 0 - + + + + 0 + 0 + + + + + 275 + 0 + + + + + 16777215 + 16777215 + + + + Update + + + + 5 + + + 1 + + + 11 + + + 11 + + + + + + 0 + 0 + + + + + 0 + 75 + + + + + 16777215 + 16777215 + + + + Update Channel + + + + 7 + + + 11 + + + 11 + + + 11 + + + 11 + + + + + + 0 + 0 + + + + + Release + + + + + Nightly + + + + + + + + + + + + 0 + 0 + + + + + 197 + 28 + + + + + 16777215 + 16777215 + + + + Check for Updates + + + + + + + + 0 + 0 + + + + + 11 + false + + + + Check for Updates at Startup + + + + + + + + + + + + 0 @@ -280,80 +455,315 @@ - 265 + 0 0 - Update + GUI Settings - - - - 10 - 130 - 261 - 22 - + + + 1 - - Check for Updates at Startup + + 11 - - - - - 12 - 30 - 241 - 65 - - - - Update Channel - - - - - 12 - 30 - 217 - 28 - - - - - Release + + + + 1 - - - - Nightly + + 0 - - - - - - - 25 - 100 - 215 - 24 - - - - Check for Updates - - + + + + + 0 + 0 + + + + Play title music + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Fixed + + + + 20 + 2 + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Volume + + + + + + + Set the volume of the background music. + + + 100 + + + 10 + + + 20 + + + 50 + + + Qt::Orientation::Horizontal + + + false + + + false + + + QSlider::TickPosition::NoTicks + + + 10 + + + + + + + + + + 0 + 61 + + + + + - + - + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Input + + + + + + + + 7 + + + 0 + + + + + Cursor + + + + 0 + + + 11 + + + 11 + + + + + true + + + + 0 + 0 + + + + Hide Cursor + + + + 7 + + + 11 + + + + + + + + + + + true + + + + 0 + 0 + + + + + 0 + 0 + + + + Hide Cursor Idle Timeout + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop + + + false + + + + 6 + + + 70 + + + 5 + + + 5 + + + 5 + + + + + true + + + + 0 + 0 + + + + + 80 + 30 + + + + + 16777215 + 16777215 + + + + Qt::LayoutDirection::LeftToRight + + + false + + + Qt::AlignmentFlag::AlignCenter + + + QAbstractSpinBox::ButtonSymbols::UpDownArrows + + + + + + 3600 + + + 5 + + + 10 + + + + + + + s + + + + + + + + + + + + + + + 0 @@ -361,98 +771,102 @@ - GUI Settings + Controller - - - - 10 - 30 - 241 - 71 - + + + 0 - - - - - - 0 - 0 - + + 11 + + + 11 + + + + + true + + + + 0 + 0 + + + + + 237 + 0 + + + + Back Button Behavior + + + + 11 - - Play title music - - - - - - - - - - Volume - - - - - - - Set the volume of the background music. - - - 100 - - - 10 - - - 20 - - - 50 - - - Qt::Orientation::Horizontal - - - false - - - false - - - QSlider::TickPosition::NoTicks - - - 10 - - - - + - - - + + + + + + true + + + + 0 + 0 + + + + + - + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + - Qt::Orientation::Horizontal + Qt::Orientation::Vertical - QSizePolicy::Policy::Expanding + QSizePolicy::Policy::MinimumExpanding - 0 - 0 + 20 + 20 @@ -699,6 +1113,76 @@ + + + Paths + + + + + + + + Game Folders + + + + + 0 + 20 + 401 + 331 + + + + + + + 100 + 360 + 91 + 24 + + + + Add... + + + + + + 199 + 360 + 91 + 24 + + + + Remove + + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Preferred + + + + 40 + 20 + + + + + + + + Debug diff --git a/src/qt_gui/translations/ar.ts b/src/qt_gui/translations/ar.ts index b385b491b..457d84ef8 100644 --- a/src/qt_gui/translations/ar.ts +++ b/src/qt_gui/translations/ar.ts @@ -52,6 +52,19 @@ ...جارٍ التحميل + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - اختر المجلد + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All نسخ الكل + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG PKG تثبيت + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen تمكين ملء الشاشة + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro PS4 Pro هل هو + + + Enable Discord Rich Presence + تفعيل حالة الثراء في ديسكورد + Username @@ -434,6 +517,36 @@ Log Filter مرشح السجل + + + Input + إدخال + + + + Cursor + مؤشر + + + + Hide Cursor + إخفاء المؤشر + + + + Hide Cursor Idle Timeout + مهلة إخفاء المؤشر عند الخمول + + + + Controller + التحكم + + + + Back Button Behavior + سلوك زر العودة + Graphics @@ -475,9 +588,24 @@ تمكين وحدة معالجة الرسومات الفارغة - - Enable PM4 Dumping - PM4 تمكين تفريغ + + Paths + المسارات + + + + Game Folders + مجلدات اللعبة + + + + Add... + إضافة... + + + + Remove + إزالة @@ -898,7 +1026,7 @@ DownloadComplete_MSG - تم تنزيل التصحيحات بنجاح! تم تنزيل جميع التصحيحات لجميع الألعاب، ولا داعي لتنزيلها بشكل فردي لكل لعبة كما هو الحال مع الغش. إذا لم يظهر التحديث، قد يكون السبب أنه غير متوفر للإصدار وسيريال اللعبة المحدد. قد تحتاج إلى تحديث اللعبة. + تم تنزيل التصحيحات بنجاح! تم تنزيل جميع التصحيحات لجميع الألعاب، ولا داعي لتنزيلها بشكل فردي لكل لعبة كما هو الحال مع الغش. إذا لم يظهر التحديث، قد يكون السبب أنه غير متوفر للإصدار وسيريال اللعبة المحدد. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. .HTML فشل في استرجاع صفحة + + + The game is in version: %1 + اللعبة في الإصدار: %1 + + + + The downloaded patch only works on version: %1 + الباتش الذي تم تنزيله يعمل فقط على الإصدار: %1 + + + + You may need to update your game. + قد تحتاج إلى تحديث لعبتك. + + + + Incompatibility Notice + إشعار عدم التوافق + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox تمكين وضع ملء الشاشة:\nيجعل نافذة اللعبة تنتقل تلقائيًا إلى وضع ملء الشاشة.\nيمكن التبديل بالضغط على المفتاح F11. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox هل هو PS4 Pro:\nيجعل المحاكي يعمل كـ PS4 PRO، مما قد يتيح ميزات خاصة في الألعاب التي تدعمه. + + + discordRPCCheckbox + تفعيل حالة الثراء في ديسكورد:\nيعرض أيقونة المحاكي ومعلومات ذات صلة على ملفك الشخصي في ديسكورد. + userName @@ -1016,7 +1174,7 @@ logFilter - فلتر السجل: يقوم بتصفية السجل لطباعة معلومات محددة فقط. أمثلة: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" المستويات: Trace, Debug, Info, Warning, Error, Critical - بالترتيب، مستوى محدد يخفي جميع المستويات التي تسبقه ويعرض جميع المستويات بعده. + فلتر السجل:\nيقوم بتصفية السجل لطباعة معلومات محددة فقط.\nأمثلة: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" المستويات: Trace, Debug, Info, Warning, Error, Critical - بالترتيب، مستوى محدد يخفي جميع المستويات التي تسبقه ويعرض جميع المستويات بعده. @@ -1028,6 +1186,56 @@ GUIgroupBox تشغيل موسيقى العنوان:\nإذا كانت اللعبة تدعم ذلك، قم بتمكين تشغيل موسيقى خاصة عند اختيار اللعبة في واجهة المستخدم. + + + hideCursorGroupBox + إخفاء المؤشر:\nاختر متى سيختفي المؤشر:\nأبداً: سترى الفأرة دائماً.\nعاطل: حدد وقتاً لاختفائه بعد أن يكون غير مستخدم.\nدائماً: لن ترى الفأرة أبداً. + + + + idleTimeoutGroupBox + حدد وقتاً لاختفاء الفأرة بعد أن تكون غير مستخدم. + + + + backButtonBehaviorGroupBox + سلوك زر العودة:\nيضبط زر العودة في وحدة التحكم ليحاكي الضغط على الموضع المحدد على لوحة اللمس في PS4. + + + + Never + أبداً + + + + Idle + خامل + + + + Always + دائماً + + + + Touchpad Left + لوحة اللمس اليسرى + + + + Touchpad Right + لوحة اللمس اليمنى + + + + Touchpad Center + وسط لوحة اللمس + + + + None + لا شيء + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ تمكين GPU الافتراضية:\nلأغراض تصحيح الأخطاء التقنية، يقوم بتعطيل عرض اللعبة كما لو لم يكن هناك بطاقة رسومات. - - dumpPM4CheckBox - تمكين تفريغ PM4:\nلأغراض تصحيح الأخطاء التقنية، يحفظ بيانات تعليمات GPU الأولية في مجلد أثناء معالجتها. + + gameFoldersBox + مجلدات اللعبة:\nقائمة بالمجلدات للتحقق من الألعاب المثبتة. + + + + addFolderButton + إضافة:\nأضف مجلداً إلى القائمة. + + + + removeFolderButton + إزالة:\nأزل مجلداً من القائمة. @@ -1121,6 +1339,11 @@ Path مسار + + + Play Time + وقت اللعب + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available تحديث متاح - + Update Channel قناة التحديث diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 73bc37189..e14826725 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -52,6 +52,19 @@ Loading... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Choose directory + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Copy All + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Install PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Enable Fullscreen + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Aktiver Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Log Filter + + + Input + Indtastning + + + + Cursor + Markør + + + + Hide Cursor + Skjul markør + + + + Hide Cursor Idle Timeout + Timeout for skjul markør ved inaktivitet + + + + Controller + Controller + + + + Back Button Behavior + Tilbageknap adfærd + Graphics @@ -475,9 +588,24 @@ Enable NULL GPU - - Enable PM4 Dumping - Enable PM4 Dumping + + Paths + Stier + + + + Game Folders + Spilmapper + + + + Add... + Tilføj... + + + + Remove + Fjern @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Patcher hentet med succes! Alle patches til alle spil er blevet hentet, der er ikke behov for at hente dem individuelt for hvert spil, som det sker med snyd. Hvis opdateringen ikke vises, kan det være, at den ikke findes for den specifikke serie og version af spillet. Det kan være nødvendigt at opdatere spillet. + Patcher hentet med succes! Alle patches til alle spil er blevet hentet, der er ikke behov for at hente dem individuelt for hvert spil, som det sker med snyd. Hvis opdateringen ikke vises, kan det være, at den ikke findes for den specifikke serie og version af spillet. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Kunne ikke hente HTML-side. + + + The game is in version: %1 + Spillet er i version: %1 + + + + The downloaded patch only works on version: %1 + Den downloadede patch fungerer kun på version: %1 + + + + You may need to update your game. + Du skal muligvis opdatere dit spil. + + + + Incompatibility Notice + Uforenelighedsmeddelelse + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Aktiver fuld skærm:\nSætter automatisk spilvinduet i fuld skærm.\nDette kan skiftes ved at trykke på F11-tasten. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox Er det en PS4 Pro:\nGør det muligt for emulatoren at fungere som en PS4 PRO, hvilket kan aktivere visse funktioner i spil, der understøtter det. + + + discordRPCCheckbox + Aktiver Discord Rich Presence:\nViser emulatorikonet og relevante oplysninger på din Discord-profil. + userName @@ -1016,7 +1174,7 @@ logFilter - Logfilter: Filtrerer loggen for kun at udskrive bestemte oplysninger. Eksempler: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveaus: Trace, Debug, Info, Warning, Error, Critical - i rækkefølge, et valgt niveau skjuler alle forudgående niveauer og viser alle efterfølgende niveauer. + Logfilter:\nFiltrerer loggen for kun at udskrive bestemte oplysninger.\nEksempler: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveaus: Trace, Debug, Info, Warning, Error, Critical - i rækkefølge, et valgt niveau skjuler alle forudgående niveauer og viser alle efterfølgende niveauer. @@ -1028,6 +1186,56 @@ GUIgroupBox Titelsmusikafspilning:\nHvis spillet understøtter det, aktiver speciel musik, når spillet vælges i brugergrænsefladen. + + + hideCursorGroupBox + Skjul Cursor:\nVælg hvornår cursoren skal forsvinde:\nAldrig: Du vil altid se musen.\nInaktiv: Indstil en tid for, hvornår den skal forsvinde efter at være inaktiv.\nAltid: du vil aldrig se musen. + + + + idleTimeoutGroupBox + Indstil en tid for, at musen skal forsvinde efter at være inaktiv. + + + + backButtonBehaviorGroupBox + Tilbageknap Adfærd:\nIndstiller controllerens tilbageknap til at efterligne tryk på den angivne position på PS4 berøringsflade. + + + + Never + Aldrig + + + + Idle + Inaktiv + + + + Always + Altid + + + + Touchpad Left + Berøringsplade Venstre + + + + Touchpad Right + Berøringsplade Højre + + + + Touchpad Center + Berøringsplade Center + + + + None + Ingen + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Aktiver virtuel GPU:\nTil teknisk fejlfinding deaktiverer det spilvisning, som om der ikke var et grafikkort. - - dumpPM4CheckBox - Aktiver dumping af PM4:\nTil teknisk fejlfinding gemmer det rå GPU-kommandoer i en mappe under behandling. + + gameFoldersBox + Spilmappen:\nListen over mapper til at tjekke for installerede spil. + + + + addFolderButton + Tilføj:\nTilføj en mappe til listen. + + + + removeFolderButton + Fjern:\nFjern en mappe fra listen. @@ -1121,6 +1339,11 @@ Path Sti + + + Play Time + Spilletid + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Opdatering tilgængelig - + Update Channel Opdateringskanal diff --git a/src/qt_gui/translations/de.ts b/src/qt_gui/translations/de.ts index 771d3bf8e..77b6a01ac 100644 --- a/src/qt_gui/translations/de.ts +++ b/src/qt_gui/translations/de.ts @@ -52,6 +52,19 @@ Lade... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Wähle Ordner + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Alles kopieren + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG PKG installieren + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Vollbild aktivieren + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Ist PS4 Pro + + + Enable Discord Rich Presence + Discord Rich Presence aktivieren + Username @@ -434,6 +517,36 @@ Log Filter Log-Filter + + + Input + Eingabe + + + + Cursor + Cursor + + + + Hide Cursor + Cursor ausblenden + + + + Hide Cursor Idle Timeout + Inaktivitätszeitüberschreitung zum Ausblenden des Cursors + + + + Controller + Controller + + + + Back Button Behavior + Verhalten der Zurück-Taste + Graphics @@ -475,9 +588,24 @@ NULL GPU aktivieren - - Enable PM4 Dumping - PM4-Dumping aktivieren + + Paths + Pfad + + + + Game Folders + Spieleordner + + + + Add... + Hinzufügen... + + + + Remove + Entfernen @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Patches erfolgreich heruntergeladen! Alle Patches für alle Spiele wurden heruntergeladen, es ist nicht notwendig, sie einzeln für jedes Spiel herunterzuladen, wie es bei Cheats der Fall ist. Wenn der Patch nicht angezeigt wird, könnte es sein, dass er für die spezifische Seriennummer und Version des Spiels nicht existiert. Möglicherweise müssen Sie das Spiel aktualisieren. + Patches erfolgreich heruntergeladen! Alle Patches für alle Spiele wurden heruntergeladen, es ist nicht notwendig, sie einzeln für jedes Spiel herunterzuladen, wie es bei Cheats der Fall ist. Wenn der Patch nicht angezeigt wird, könnte es sein, dass er für die spezifische Seriennummer und Version des Spiels nicht existiert. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Fehler beim Abrufen der HTML-Seite. + + + The game is in version: %1 + Das Spiel ist in der Version: %1 + + + + The downloaded patch only works on version: %1 + Der heruntergeladene Patch funktioniert nur in der Version: %1 + + + + You may need to update your game. + Sie müssen möglicherweise Ihr Spiel aktualisieren. + + + + Incompatibility Notice + Inkompatibilitätsbenachrichtigung + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Vollbildmodus aktivieren:\nSchaltet das Spielfenster automatisch in den Vollbildmodus.\nKann durch Drücken der F11-Taste umgeschaltet werden. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox Ist es eine PS4 Pro:\nErmöglicht es dem Emulator, als PS4 PRO zu arbeiten, was in Spielen, die dies unterstützen, spezielle Funktionen aktivieren kann. + + + discordRPCCheckbox + Discord Rich Presence aktivieren:\nZeigt das Emulator-Icon und relevante Informationen in deinem Discord-Profil an. + userName @@ -1016,7 +1174,7 @@ logFilter - Protokollfilter: Filtert das Protokoll so, dass nur bestimmte Informationen ausgegeben werden. Beispiele: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Ebenen: Trace, Debug, Info, Warning, Error, Critical - in dieser Reihenfolge, ein ausgewähltes Level blendet alle vorherigen Ebenen aus und zeigt alle nachfolgenden an. + Protokollfilter:\nFiltert das Protokoll so, dass nur bestimmte Informationen ausgegeben werden.\nBeispiele: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Ebenen: Trace, Debug, Info, Warning, Error, Critical - in dieser Reihenfolge, ein ausgewähltes Level blendet alle vorherigen Ebenen aus und zeigt alle nachfolgenden an. @@ -1028,6 +1186,56 @@ GUIgroupBox Wiedergabe der Titelmusik:\nWenn das Spiel dies unterstützt, wird beim Auswählen des Spiels in der Benutzeroberfläche spezielle Musik abgespielt. + + + hideCursorGroupBox + Maus ausblenden:\nWählen Sie, wann der Cursor verschwinden soll:\nNie: Sie sehen die Maus immer.\nInaktiv: Legen Sie eine Zeit fest, nach der sie nach Inaktivität verschwindet.\nImmer: Sie sehen die Maus niemals. + + + + idleTimeoutGroupBox + Stellen Sie eine Zeit ein, nach der die Maus nach Inaktivität verschwinden soll. + + + + backButtonBehaviorGroupBox + Zurück-Button Verhalten:\nStellt die Zurück-Taste des Controllers so ein, dass sie das Antippen der angegebenen Position auf dem PS4-Touchpad emuliert. + + + + Never + Niemals + + + + Idle + Im Leerlauf + + + + Always + Immer + + + + Touchpad Left + Touchpad Links + + + + Touchpad Right + Touchpad Rechts + + + + Touchpad Center + Touchpad Mitte + + + + None + Keine + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Virtuelle GPU aktivieren:\nFür das technische Debugging deaktiviert es die Spielanzeige, als ob keine Grafikkarte vorhanden wäre. - - dumpPM4CheckBox - PM4-Dumping aktivieren:\nZum technischen Debuggen speichert es rohe GPU-Befehlsdaten in einem Ordner während der Verarbeitung. + + gameFoldersBox + Spieleordner:\nDie Liste der Ordner, in denen nach installierten Spielen gesucht wird. + + + + addFolderButton + Hinzufügen:\nFügen Sie einen Ordner zur Liste hinzu. + + + + removeFolderButton + Entfernen:\nEntfernen Sie einen Ordner aus der Liste. @@ -1121,6 +1339,11 @@ Path Pfad + + + Play Time + Spielzeit + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Aktualisierung verfügbar - + Update Channel Update-Kanal diff --git a/src/qt_gui/translations/el.ts b/src/qt_gui/translations/el.ts index f2265c7d7..a738bf90a 100644 --- a/src/qt_gui/translations/el.ts +++ b/src/qt_gui/translations/el.ts @@ -52,6 +52,19 @@ Loading... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Choose directory + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Copy All + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Install PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Enable Fullscreen + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Ενεργοποίηση Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Log Filter + + + Input + Είσοδος + + + + Cursor + Δείκτης + + + + Hide Cursor + Απόκρυψη δείκτη + + + + Hide Cursor Idle Timeout + Χρόνος αδράνειας απόκρυψης δείκτη + + + + Controller + Controller + + + + Back Button Behavior + Συμπεριφορά κουμπιού επιστροφής + Graphics @@ -475,9 +588,24 @@ Enable NULL GPU - - Enable PM4 Dumping - Enable PM4 Dumping + + Paths + Διαδρομές + + + + Game Folders + Φάκελοι παιχνιδιών + + + + Add... + Προσθήκη... + + + + Remove + Αφαίρεση @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Τα Patches κατεβάστηκαν επιτυχώς! Όλα τα Patches για όλα τα παιχνίδια έχουν κατέβει, δεν είναι απαραίτητο να τα κατεβάσετε ένα-ένα για κάθε παιχνίδι, όπως με τα Cheats. Εάν η ενημέρωση δεν εμφανίζεται, μπορεί να μην υπάρχει για τον συγκεκριμένο σειριακό αριθμό και έκδοση του παιχνιδιού. Μπορεί να χρειαστεί να ενημερώσετε το παιχνίδι. + Τα Patches κατεβάστηκαν επιτυχώς! Όλα τα Patches για όλα τα παιχνίδια έχουν κατέβει, δεν είναι απαραίτητο να τα κατεβάσετε ένα-ένα για κάθε παιχνίδι, όπως με τα Cheats. Εάν η ενημέρωση δεν εμφανίζεται, μπορεί να μην υπάρχει για τον συγκεκριμένο σειριακό αριθμό και έκδοση του παιχνιδιού. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Αποτυχία ανάκτησης σελίδας HTML. + + + The game is in version: %1 + Το παιχνίδι είναι στην έκδοση: %1 + + + + The downloaded patch only works on version: %1 + Η ληφθείσα ενημέρωση λειτουργεί μόνο στην έκδοση: %1 + + + + You may need to update your game. + Μπορεί να χρειαστεί να ενημερώσετε το παιχνίδι σας. + + + + Incompatibility Notice + Ειδοποίηση ασυμβατότητας + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Ενεργοποίηση Πλήρους Οθόνης:\nΑυτόματα μετατρέπει το παράθυρο του παιχνιδιού σε λειτουργία πλήρους οθόνης.\nΜπορεί να ενεργοποιηθεί/απενεργοποιηθεί πατώντας το πλήκτρο F11. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox Είναι PS4 Pro:\nΕπιτρέπει στον εξομοιωτή να λειτουργεί σαν PS4 PRO, κάτι που μπορεί να ενεργοποιήσει συγκεκριμένες λειτουργίες σε παιχνίδια που το υποστηρίζουν. + + + discordRPCCheckbox + Ενεργοποίηση Discord Rich Presence:\nΕμφανίζει το εικονίδιο του emulator και σχετικές πληροφορίες στο προφίλ σας στο Discord. + userName @@ -1016,7 +1174,7 @@ logFilter - Φίλτρο Καταγραφής: Φιλτράρει τις καταγραφές ώστε να εκτυπώνονται μόνο συγκεκριμένες πληροφορίες. Παραδείγματα: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Επίπεδα: Trace, Debug, Info, Warning, Error, Critical - με τη σειρά αυτή, κάθε επίπεδο που επιλέγεται αποκλείει τα προηγούμενα και εμφανίζει τα επόμενα επίπεδα. + Φίλτρο Καταγραφής:\nΦιλτράρει τις καταγραφές ώστε να εκτυπώνονται μόνο συγκεκριμένες πληροφορίες.\nΠαραδείγματα: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Επίπεδα: Trace, Debug, Info, Warning, Error, Critical - με τη σειρά αυτή, κάθε επίπεδο που επιλέγεται αποκλείει τα προηγούμενα και εμφανίζει τα επόμενα επίπεδα. @@ -1028,6 +1186,56 @@ GUIgroupBox Αναπαραγωγή Μουσικής Τίτλων:\nΕάν το παιχνίδι το υποστηρίζει, ενεργοποιεί ειδική μουσική κατά την επιλογή του παιχνιδιού από τη διεπαφή χρήστη. + + + hideCursorGroupBox + Απόκρυψη Κέρσορα:\nΕπιλέξτε πότε θα εξαφανιστεί ο κέρσορας:\nΠοτέ: θα βλέπετε πάντα το ποντίκι.\nΑδρανές: ορίστε έναν χρόνο για να εξαφανιστεί μετά από αδράνεια.\nΠάντα: δεν θα δείτε ποτέ το ποντίκι. + + + + idleTimeoutGroupBox + Ορίστε έναν χρόνο για να εξαφανιστεί το ποντίκι μετά από αδράνεια. + + + + backButtonBehaviorGroupBox + Συμπεριφορά Κουμπιού Επιστροφής:\nΟρίζει το κουμπί επιστροφής του ελεγκτή να προσομοιώνει το πάτημα της καθορισμένης θέσης στην οθόνη αφής PS4. + + + + Never + Ποτέ + + + + Idle + Αδρανής + + + + Always + Πάντα + + + + Touchpad Left + Touchpad Αριστερά + + + + Touchpad Right + Touchpad Δεξιά + + + + Touchpad Center + Κέντρο Touchpad + + + + None + Κανένα + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Ενεργοποίηση Εικονικής GPU:\nΓια τεχνικό εντοπισμό σφαλμάτων, απενεργοποιεί την εμφάνιση του παιχνιδιού σαν να μην υπάρχει κάρτα γραφικών. - - dumpPM4CheckBox - Ενεργοποίηση Καταγραφής PM4:\nΓια τεχνικό εντοπισμό σφαλμάτων, αποθηκεύει τις ακατέργαστες εντολές της GPU σε φάκελο κατά την επεξεργασία. + + gameFoldersBox + Φάκελοι Παιχνιδιών:\nΗ λίστα των φακέλων για έλεγχο των εγκατεστημένων παιχνιδιών. + + + + addFolderButton + Προσθήκη:\nΠροσθέστε έναν φάκελο στη λίστα. + + + + removeFolderButton + Αφαίρεση:\nΑφαιρέστε έναν φάκελο από τη λίστα. @@ -1121,6 +1339,11 @@ Path Διαδρομή + + + Play Time + Χρόνος παιχνιδιού + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Διαθέσιμη Ενημέρωση - + Update Channel Κανάλι Ενημέρωσης diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index e45ac475a..9f25fc836 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -52,6 +52,19 @@ Loading... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Choose directory + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Copy All + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Install PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Enable Fullscreen + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Enable Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Log Filter + + + Input + Input + + + + Cursor + Cursor + + + + Hide Cursor + Hide Cursor + + + + Hide Cursor Idle Timeout + Hide Cursor Idle Timeout + + + + Controller + Controller + + + + Back Button Behavior + Back Button Behavior + Graphics @@ -475,9 +588,24 @@ Enable NULL GPU - - Enable PM4 Dumping - Enable PM4 Dumping + + Paths + Paths + + + + Game Folders + Game Folders + + + + Add... + Add... + + + + Remove + Remove @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. It may be necessary to update the game. + Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Failed to retrieve HTML page. + + + The game is in version: %1 + The game is in version: %1 + + + + The downloaded patch only works on version: %1 + The downloaded patch only works on version: %1 + + + + You may need to update your game. + You may need to update your game. + + + + Incompatibility Notice + Incompatibility Notice + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Enable Full Screen:\nAutomatically puts the game window into full-screen mode.\nThis can be toggled by pressing the F11 key. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox Is PS4 Pro:\nMakes the emulator act as a PS4 PRO, which may enable special features in games that support it. + + + discordRPCCheckbox + Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile. + userName @@ -1016,7 +1174,7 @@ logFilter - Log Filter: Filters the log to only print specific information. Examples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Levels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. + Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. @@ -1028,6 +1186,56 @@ GUIgroupBox Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. + + + hideCursorGroupBox + Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. + + + + idleTimeoutGroupBox + Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. + + + + backButtonBehaviorGroupBox + Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. + + + + Never + Never + + + + Idle + Idle + + + + Always + Always + + + + Touchpad Left + Touchpad Left + + + + Touchpad Right + Touchpad Right + + + + Touchpad Center + Touchpad Center + + + + None + None + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. - - dumpPM4CheckBox - Enable PM4 Dumping:\nFor the sake of technical debugging, saves raw GPU instruction data to a folder as the emulator processes it. + + gameFoldersBox + Game Folders:\nThe list of folders to check for installed games. + + + + addFolderButton + Add:\nAdd a folder to the list. + + + + removeFolderButton + Remove:\nRemove a folder from the list. @@ -1121,6 +1339,11 @@ Path Path + + + Play Time + Play Time + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Update Available - + Update Channel Update Channel diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 4242bc91e..b0a6e4335 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -52,6 +52,19 @@ Cargando... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Elegir carpeta + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Copiar todo + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Instalar PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Habilitar pantalla completa + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Modo PS4 Pro + + + Enable Discord Rich Presence + Habilitar Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Filtro de registro + + + Input + Entrada + + + + Cursor + Cursor + + + + Hide Cursor + Ocultar cursor + + + + Hide Cursor Idle Timeout + Tiempo de espera para ocultar cursor inactivo + + + + Controller + Controlador + + + + Back Button Behavior + Comportamiento del botón de retroceso + Graphics @@ -475,9 +588,24 @@ Habilitar GPU NULL - - Enable PM4 Dumping - Habilitar volcado de PM4 + + Paths + Rutas + + + + Game Folders + Carpetas de juego + + + + Add... + Añadir... + + + + Remove + Eliminar @@ -898,7 +1026,7 @@ DownloadComplete_MSG - ¡Parches descargados exitosamente! Todos los parches disponibles para todos los juegos han sido descargados, no es necesario descargarlos individualmente para cada juego como ocurre con los trucos. Si el parche no aparece, puede ser que no exista para el número de serie y versión específicos del juego. Puede ser necesario actualizar el juego. + ¡Parches descargados exitosamente! Todos los parches disponibles para todos los juegos han sido descargados, no es necesario descargarlos individualmente para cada juego como ocurre con los trucos. Si el parche no aparece, puede ser que no exista para el número de serie y versión específicos del juego. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Error al recuperar la página HTML. + + + The game is in version: %1 + El juego está en la versión: %1 + + + + The downloaded patch only works on version: %1 + El parche descargado solo funciona en la versión: %1 + + + + You may need to update your game. + Puede que necesites actualizar tu juego. + + + + Incompatibility Notice + Aviso de incompatibilidad + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Habilitar Pantalla Completa:\nColoca automáticamente la ventana del juego en modo de pantalla completa.\nEsto se puede alternar presionando la tecla F11. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox Es PS4 Pro:\nHace que el emulador actúe como una PS4 PRO, lo que puede habilitar funciones especiales en los juegos que lo admitan. + + + discordRPCCheckbox + Habilitar Discord Rich Presence:\nMuestra el ícono del emulador y la información relevante en tu perfil de Discord. + userName @@ -1016,7 +1174,7 @@ logFilter - Filtro de Registro: Filtra el registro para imprimir solo información específica. Ejemplos: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveles: Trace, Debug, Info, Warning, Error, Critical - en este orden, un nivel específico silencia todos los niveles anteriores en la lista y registra cada nivel posterior. + Filtro de Registro:\nFiltra el registro para imprimir solo información específica.\nEjemplos: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveles: Trace, Debug, Info, Warning, Error, Critical - en este orden, un nivel específico silencia todos los niveles anteriores en la lista y registra cada nivel posterior. @@ -1028,6 +1186,56 @@ GUIgroupBox Reproducir Música del Título:\nSi un juego lo admite, habilita la reproducción de música especial al seleccionar el juego en la interfaz gráfica. + + + hideCursorGroupBox + Ocultar Cursor:\nElija cuándo desaparecerá el cursor:\nNunca: Siempre verá el mouse.\nInactivo: Establezca un tiempo para que desaparezca después de estar inactivo.\nSiempre: nunca verá el mouse. + + + + idleTimeoutGroupBox + Establezca un tiempo para que el mouse desaparezca después de estar inactivo. + + + + backButtonBehaviorGroupBox + Comportamiento del Botón Atrás:\nEstablece el botón atrás del controlador para emular el toque en la posición especificada en el touchpad del PS4. + + + + Never + Nunca + + + + Idle + Inactivo + + + + Always + Siempre + + + + Touchpad Left + Touchpad Izquierda + + + + Touchpad Right + Touchpad Derecha + + + + Touchpad Center + Centro del Touchpad + + + + None + Ninguno + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Habilitar GPU Nula:\nPor el bien de la depuración técnica, desactiva el renderizado del juego como si no hubiera tarjeta gráfica. - - dumpPM4CheckBox - Habilitar la Volcadura de PM4:\nPor el bien de la depuración técnica, guarda los datos de instrucciones crudas de GPU en una carpeta a medida que el emulador los procesa. + + gameFoldersBox + Carpetas de Juegos:\nLa lista de carpetas para verificar los juegos instalados. + + + + addFolderButton + Añadir:\nAgregar una carpeta a la lista. + + + + removeFolderButton + Eliminar:\nEliminar una carpeta de la lista. @@ -1121,6 +1339,11 @@ Path Ruta + + + Play Time + Tiempo de Juego + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Actualización disponible - + Update Channel Canal de Actualización diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 15e07c886..15f5d6193 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -52,6 +52,19 @@ ...درحال بارگیری + + InstallDirSelect + + + shadPS4 - Choose directory + ShadPS4 - انتخاب محل نصب بازی + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All کپی کردن تمامی مقادیر + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG نصب PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen تمام صفحه + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro PS4 Pro حالت + + + Enable Discord Rich Presence + Discord Rich Presence را فعال کنید + Username @@ -434,6 +517,36 @@ Log Filter Log فیلتر + + + Input + ورودی + + + + Cursor + نشانگر + + + + Hide Cursor + پنهان کردن نشانگر + + + + Hide Cursor Idle Timeout + مخفی کردن زمان توقف مکان نما + + + + Controller + کنترل کننده + + + + Back Button Behavior + رفتار دکمه بازگشت + Graphics @@ -475,9 +588,24 @@ NULL GPU فعال کردن - - Enable PM4 Dumping - PM4 Dumping فعال کردن + + Paths + مسیرها + + + + Game Folders + پوشه های بازی + + + + Add... + افزودن... + + + + Remove + حذف @@ -893,12 +1021,12 @@ Download Complete - پچ ها با موفقیت بارگیری شدند! تمام وصله های موجود برای همه بازی ها دانلود شده اند، نیازی به دانلود جداگانه آنها برای هر بازی نیست، همانطور که در Cheats اتفاق می افتد. اگر پچ ظاهر نشد، ممکن است برای سریال و نسخه خاصی از بازی وجود نداشته باشد. ممکن است نیاز به آپدیت بازی باشد.✅ + دانلود کامل شد DownloadComplete_MSG - دانلود با موفقیت به اتمام رسید✅ + پچ ها با موفقیت بارگیری شدند! تمام وصله های موجود برای همه بازی ها دانلود شده اند، نیازی به دانلود جداگانه آنها برای هر بازی نیست، همانطور که در Cheats اتفاق می افتد. اگر پچ ظاهر نشد، ممکن است برای سریال و نسخه خاصی از بازی وجود نداشته باشد. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. HTML خطا دربازیابی صفحه + + + The game is in version: %1 + بازی در نسخه: %1 است + + + + The downloaded patch only works on version: %1 + وصله دانلود شده فقط در نسخه: %1 کار می کند + + + + You may need to update your game. + شاید لازم باشد بازی خود را به روز کنید. + + + + Incompatibility Notice + اطلاعیه عدم سازگاری + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Enable Full Screen:\nAutomatically puts the game window into full-screen mode.\nThis can be toggled by pressing the F11 key. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox Is PS4 Pro:\nMakes the emulator act as a PS4 PRO, which may enable special features in games that support it. + + + discordRPCCheckbox + فعال کردن Discord Rich Presence:\nآیکون شبیه ساز و اطلاعات مربوطه را در نمایه Discord شما نمایش می دهد. + userName @@ -1016,7 +1174,7 @@ logFilter - Log Filter: Filters the log to only print specific information. Examples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Levels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. + Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Levels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. @@ -1028,6 +1186,56 @@ GUIgroupBox Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. + + + hideCursorGroupBox + پنهان کردن نشانگر:\nانتخاب کنید که نشانگر چه زمانی ناپدید شود:\nهرگز: شما همیشه ماوس را خواهید دید.\nغیرفعال: زمانی را برای ناپدید شدن بعد از غیرفعالی تعیین کنید.\nهمیشه: شما هرگز ماوس را نخواهید دید. + + + + idleTimeoutGroupBox + زمانی را برای ناپدید شدن ماوس بعد از غیرفعالی تعیین کنید. + + + + backButtonBehaviorGroupBox + رفتار دکمه برگشت:\nدکمه برگشت کنترلر را طوری تنظیم می کند که ضربه زدن روی موقعیت مشخص شده روی صفحه لمسی PS4 را شبیه سازی کند. + + + + Never + هرگز + + + + Idle + بیکار + + + + Always + همیشه + + + + Touchpad Left + پد لمسی سمت چپ + + + + Touchpad Right + صفحه لمسی سمت راست + + + + Touchpad Center + مرکز تاچ پد + + + + None + هیچ کدام + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. - - dumpPM4CheckBox - Enable PM4 Dumping:\nFor the sake of technical debugging, saves raw GPU instruction data to a folder as the emulator processes it. + + gameFoldersBox + پوشه های بازی:\nلیست پوشه هایی که باید بازی های نصب شده را بررسی کنید. + + + + addFolderButton + اضافه کردن:\nیک پوشه به لیست اضافه کنید. + + + + removeFolderButton + حذف:\nیک پوشه را از لیست حذف کنید. @@ -1121,6 +1339,11 @@ Path مسیر + + + Play Time + زمان بازی + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available به روز رسانی موجود است - + Update Channel کانال بروزرسانی diff --git a/src/qt_gui/translations/fi.ts b/src/qt_gui/translations/fi.ts index a2b319280..cb7426e01 100644 --- a/src/qt_gui/translations/fi.ts +++ b/src/qt_gui/translations/fi.ts @@ -52,6 +52,19 @@ Loading... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Choose directory + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Copy All + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Install PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Enable Fullscreen + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Ota käyttöön Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Log Filter + + + Input + Syöttö + + + + Cursor + Kursori + + + + Hide Cursor + Piilota kursor + + + + Hide Cursor Idle Timeout + Inaktiivisuuden aikaraja kursorin piilottamiselle + + + + Controller + Ohjain + + + + Back Button Behavior + Takaisin-painikkeen käyttäytyminen + Graphics @@ -475,9 +588,24 @@ Enable NULL GPU - - Enable PM4 Dumping - Enable PM4 Dumping + + Paths + Polut + + + + Game Folders + Pelihakemistot + + + + Add... + Lisää... + + + + Remove + Poista @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Korjaukset ladattu onnistuneesti! Kaikki saatavilla olevat korjaukset kaikille peleille on ladattu, eikä niitä tarvitse ladata yksittäin jokaiselle pelille kuten huijauksissa. Jos päivitystä ei näy, se saattaa olla, että sitä ei ole saatavilla tietylle sarjanumerolle ja peliversiolle. Saattaa olla tarpeen päivittää peli. + Korjaukset ladattu onnistuneesti! Kaikki saatavilla olevat korjaukset kaikille peleille on ladattu, eikä niitä tarvitse ladata yksittäin jokaiselle pelille kuten huijauksissa. Jos päivitystä ei näy, se saattaa olla, että sitä ei ole saatavilla tietylle sarjanumerolle ja peliversiolle. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. HTML-sivun hakeminen epäonnistui. + + + The game is in version: %1 + Peli on versiossa: %1 + + + + The downloaded patch only works on version: %1 + ladattu päivitys toimii vain versiossa: %1 + + + + You may need to update your game. + Sinun on ehkä päivitettävä peliäsi. + + + + Incompatibility Notice + Yhteensopivuusilmoitus + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Ota Täysikokoisuus käyttöön:\nSiirtää pelin ikkunan automaattisesti täysikokoiseen tilaan.\nTätä voidaan vaihtaa painamalla F11-näppäintä. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox Onko PS4 Pro:\nAsettaa emulaattorin toimimaan PS4 PRO:na, mikä voi mahdollistaa erityisiä ominaisuuksia peleissä, jotka tukevat sitä. + + + discordRPCCheckbox + Ota käyttöön Discord Rich Presence:\nNäyttää emulaattorin kuvakkeen ja asiaankuuluvat tiedot Discord-profiilissasi. + userName @@ -1016,7 +1174,7 @@ logFilter - Lokifiltteri: Suodattaa lokia tulostamaan vain erityistä tietoa. Esimerkkejä: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Tasot: Jälki, Virheenkorjaus, Tieto, Varoitus, Virhe, Kriittinen - tällä järjestyksellä, tietty taso vaientaa kaikki edeltävät tasot luettelossa ja kirjaa kaikki tasot sen jälkeen. + Lokifiltteri:\nSuodattaa lokia tulostamaan vain erityistä tietoa.\nEsimerkkejä: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Tasot: Jälki, Virheenkorjaus, Tieto, Varoitus, Virhe, Kriittinen - tällä järjestyksellä, tietty taso vaientaa kaikki edeltävät tasot luettelossa ja kirjaa kaikki tasot sen jälkeen. @@ -1028,6 +1186,56 @@ GUIgroupBox Soita Otsikkomusiikkia:\nJos peli tukee sitä, ota käyttöön erityisen musiikin soittaminen pelin valinnan yhteydessä käyttöliittymässä. + + + hideCursorGroupBox + Piilota kursori:\nValitse, milloin kursori häviää:\nEi koskaan: Näet hiiren aina.\nAktiivinen: Aseta aika, jolloin se häviää oltuaan aktiivinen.\nAina: et koskaan näe hiirtä. + + + + idleTimeoutGroupBox + Aseta aika, jolloin hiiri häviää oltuaan aktiivinen. + + + + backButtonBehaviorGroupBox + Takaisin-napin käyttäytyminen:\nAsettaa ohjaimen takaisin-napin jäljittelemään kosketusta PS4:n kosketuslevyn määritettyyn kohtaan. + + + + Never + Ei koskaan + + + + Idle + Odotustila + + + + Always + aina + + + + Touchpad Left + Kosketuslevy Vasemmalla + + + + Touchpad Right + Kosketuslevy Oikealla + + + + Touchpad Center + Kosketuslevy Keskellä + + + + None + Ei mitään + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Ota Null GPU käyttöön:\nTeknistä vianetsintää varten pelin renderöinti estetään niin, että ikään kuin grafiikkakorttia ei olisi. - - dumpPM4CheckBox - Ota PM4 dumpaus käyttöön:\nTeknistä vianetsintää varten raakoja GPU-ohjeita tallennetaan kansioon emulaattorin käsitellessä sitä. + + gameFoldersBox + Pelihakemistot:\nLuettelo hakemistoista asennettujen pelien tarkistamiseksi. + + + + addFolderButton + Lisää:\nLisää hakemisto luetteloon. + + + + removeFolderButton + Poista:\nPoista hakemisto luettelosta. @@ -1121,6 +1339,11 @@ Path Polku + + + Play Time + Peliaika + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Päivitys saatavilla - + Update Channel Päivityskanava diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts index 05c70f195..4c2d5cbdd 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -52,6 +52,19 @@ Chargement... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Choisir un répertoire + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Copier tout + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Installer un PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Plein écran + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Mode PS4 Pro + + + Enable Discord Rich Presence + Activer Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Filtre + + + Input + Entrée + + + + Cursor + Curseur + + + + Hide Cursor + Masquer le curseur + + + + Hide Cursor Idle Timeout + Délai d'inactivité pour masquer le curseur + + + + Controller + Manette + + + + Back Button Behavior + Comportement du bouton retour + Graphics @@ -475,9 +588,24 @@ NULL GPU - - Enable PM4 Dumping - Dumper le PM4 + + Paths + Chemins + + + + Game Folders + Dossiers de jeu + + + + Add... + Ajouter... + + + + Remove + Supprimer @@ -585,7 +713,7 @@ Games: - Jeux : + Jeux: @@ -620,7 +748,7 @@ PKG and Game versions match: - Les versions PKG et jeu correspondent : + Les versions PKG et jeu correspondent: @@ -630,17 +758,17 @@ PKG Version %1 is older than installed version: - La version PKG %1 est plus ancienne que la version installée : + La version PKG %1 est plus ancienne que la version installée: Game is installed: - Jeu installé : + Jeu installé: Would you like to install Patch: - Souhaitez-vous installer le patch : + Souhaitez-vous installer le patch: @@ -650,12 +778,12 @@ Would you like to install DLC: %1? - Souhaitez-vous installer le DLC : %1 ? + Souhaitez-vous installer le DLC: %1 ? DLC already installed: - DLC déjà installé : + DLC déjà installé: @@ -703,7 +831,7 @@ defaultTextEdit_MSG - Les Cheats/Patchs sont expérimentaux.\nUtilisez-les avec précaution.\n\nTéléchargez les Cheats individuellement en sélectionnant le dépôt et en cliquant sur le bouton de téléchargement.\nDans l'onglet Patchs, vous pouvez télécharger tous les patchs en une seule fois, choisir lesquels vous souhaitez utiliser et enregistrer votre sélection.\n\nComme nous ne développons pas les Cheats/Patches,\nmerci de signaler les problèmes à l'auteur du Cheat.\n\nVous avez créé un nouveau cheat ? Visitez :\nhttps://github.com/shadps4-emu/ps4_cheats + Les Cheats/Patchs sont expérimentaux.\nUtilisez-les avec précaution.\n\nTéléchargez les Cheats individuellement en sélectionnant le dépôt et en cliquant sur le bouton de téléchargement.\nDans l'onglet Patchs, vous pouvez télécharger tous les patchs en une seule fois, choisir lesquels vous souhaitez utiliser et enregistrer votre sélection.\n\nComme nous ne développons pas les Cheats/Patches,\nmerci de signaler les problèmes à l'auteur du Cheat.\n\nVous avez créé un nouveau cheat ? Visitez:\nhttps://github.com/shadps4-emu/ps4_cheats @@ -713,27 +841,27 @@ Serial: - Série : + Série: Version: - Version : + Version: Size: - Taille : + Taille: Select Cheat File: - Sélectionner le fichier de Cheat : + Sélectionner le fichier de Cheat: Repository: - Dépôt : + Dépôt: @@ -763,7 +891,7 @@ Select Patch File: - Sélectionner le fichier de patch : + Sélectionner le fichier de patch: @@ -818,7 +946,7 @@ Failed to parse XML: - Échec de l'analyse XML : + Échec de l'analyse XML: @@ -853,12 +981,12 @@ Failed to save file: - Échec de l'enregistrement du fichier : + Échec de l'enregistrement du fichier: Failed to download file: - Échec du téléchargement du fichier : + Échec du téléchargement du fichier: @@ -883,12 +1011,12 @@ Failed to save: - Échec de l'enregistrement : + Échec de l'enregistrement: Failed to download: - Échec du téléchargement : + Échec du téléchargement: @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Patchs téléchargés avec succès ! Tous les patches disponibles pour tous les jeux ont été téléchargés, il n'est pas nécessaire de les télécharger individuellement pour chaque jeu comme c'est le cas pour les Cheats. Si le correctif n'apparaît pas, il se peut qu'il n'existe pas pour le numéro de série et la version spécifiques du jeu. Il peut être nécessaire de mettre à jour le jeu. + Patchs téléchargés avec succès ! Tous les patches disponibles pour tous les jeux ont été téléchargés, il n'est pas nécessaire de les télécharger individuellement pour chaque jeu comme c'est le cas pour les Cheats. Si le correctif n'apparaît pas, il se peut qu'il n'existe pas pour le numéro de série et la version spécifiques du jeu. @@ -910,15 +1038,35 @@ Failed to retrieve HTML page. Échec de la récupération de la page HTML. + + + The game is in version: %1 + Le jeu est en version: %1 + + + + The downloaded patch only works on version: %1 + Le patch téléchargé ne fonctionne que sur la version: %1 + + + + You may need to update your game. + Vous devrez peut-être mettre à jour votre jeu. + + + + Incompatibility Notice + Avis d'incompatibilité + Failed to open file: - Échec de l'ouverture du fichier : + Échec de l'ouverture du fichier: XML ERROR: - Erreur XML : + Erreur XML: @@ -928,12 +1076,12 @@ Author: - Auteur : + Auteur: Directory does not exist: - Répertoire n'existe pas : + Répertoire n'existe pas: @@ -943,7 +1091,7 @@ Name: - Nom : + Nom: @@ -981,102 +1129,172 @@ consoleLanguageGroupBox - Langue de la console :\nDéfinit la langue utilisée par le jeu PS4.\nIl est recommandé de le définir sur une langue que le jeu prend en charge, ce qui variera selon la région. + Langue de la console:\nDéfinit la langue utilisée par le jeu PS4.\nIl est recommandé de le définir sur une langue que le jeu prend en charge, ce qui variera selon la région. emulatorLanguageGroupBox - Langue de l'émulateur :\nDéfinit la langue de l'interface utilisateur de l'émulateur. + Langue de l'émulateur:\nDéfinit la langue de l'interface utilisateur de l'émulateur. fullscreenCheckBox - Activer le mode plein écran :\nMet automatiquement la fenêtre du jeu en mode plein écran.\nCela peut être activé en appuyant sur la touche F11. + Activer le mode plein écran:\nMet automatiquement la fenêtre du jeu en mode plein écran.\nCela peut être activé en appuyant sur la touche F11. + + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. showSplashCheckBox - Afficher l'écran de démarrage :\nAffiche l'écran de démarrage du jeu (une image spéciale) lors du démarrage du jeu. + Afficher l'écran de démarrage:\nAffiche l'écran de démarrage du jeu (une image spéciale) lors du démarrage du jeu. ps4proCheckBox - Est-ce un PS4 Pro :\nFait en sorte que l'émulateur se comporte comme un PS4 PRO, ce qui peut activer des fonctionnalités spéciales dans les jeux qui le prennent en charge. + Est-ce un PS4 Pro:\nFait en sorte que l'émulateur se comporte comme un PS4 PRO, ce qui peut activer des fonctionnalités spéciales dans les jeux qui le prennent en charge. + + + + discordRPCCheckbox + Activer Discord Rich Presence:\nAffiche l'icône de l'émulateur et les informations pertinentes sur votre profil Discord. userName - Nom d'utilisateur :\nDéfinit le nom d'utilisateur du compte PS4, qui peut être affiché par certains jeux. + Nom d'utilisateur:\nDéfinit le nom d'utilisateur du compte PS4, qui peut être affiché par certains jeux. logTypeGroupBox - Type de journal :\nDétermine si la sortie de la fenêtre de journalisation est synchronisée pour des raisons de performance. Cela peut avoir un impact négatif sur l'émulation. + Type de journal:\nDétermine si la sortie de la fenêtre de journalisation est synchronisée pour des raisons de performance. Cela peut avoir un impact négatif sur l'émulation. logFilter - Filtre de journal : n'imprime que des informations spécifiques. Exemples : "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveaux : Trace, Debug, Info, Avertissement, Erreur, Critique - dans cet ordre, un niveau particulier désactive tous les niveaux précédents de la liste et enregistre tous les niveaux suivants. + Filtre de journal:\n n'imprime que des informations spécifiques.\nExemples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveaux: Trace, Debug, Info, Avertissement, Erreur, Critique - dans cet ordre, un niveau particulier désactive tous les niveaux précédents de la liste et enregistre tous les niveaux suivants. updaterGroupBox - Mise à jour :\nRelease: versions officielles publiées chaque mois qui peuvent être très anciennes, mais plus fiables et testées.\nNightly: versions de développement avec toutes les dernières fonctionnalités et correctifs, mais pouvant avoir des bogues et être moins stables. + Mise à jour:\nRelease: versions officielles publiées chaque mois qui peuvent être très anciennes, mais plus fiables et testées.\nNightly: versions de développement avec toutes les dernières fonctionnalités et correctifs, mais pouvant avoir des bogues et être moins stables. GUIgroupBox - Jouer de la musique de titre :\nSi le jeu le prend en charge, cela active la musique spéciale lorsque vous sélectionnez le jeu dans l'interface utilisateur. + Jouer de la musique de titre:\nSi le jeu le prend en charge, cela active la musique spéciale lorsque vous sélectionnez le jeu dans l'interface utilisateur. + + + + hideCursorGroupBox + Masquer le curseur:\nChoisissez quand le curseur disparaîtra:\nJamais: Vous verrez toujours la souris.\nInactif: Définissez un temps pour qu'il disparaisse après inactivité.\nToujours: vous ne verrez jamais la souris. + + + + idleTimeoutGroupBox + Définissez un temps pour que la souris disparaisse après être inactif. + + + + backButtonBehaviorGroupBox + Comportement du bouton retour:\nDéfinit le bouton de retour de la manette pour imiter le toucher de la position spécifiée sur le pavé tactile PS4. + + + + Never + Jamais + + + + Idle + Inactif + + + + Always + Toujours + + + + Touchpad Left + Pavé Tactile Gauche + + + + Touchpad Right + Pavé Tactile Droit + + + + Touchpad Center + Centre du Pavé Tactile + + + + None + Aucun graphicsAdapterGroupBox - Adaptateur graphique :\nSélectionnez le GPU que l'émulateur utilisera dans les systèmes multi-GPU à partir de la liste déroulante,\nou choisissez "Auto Select" pour le déterminer automatiquement. + Adaptateur graphique:\nSélectionnez le GPU que l'émulateur utilisera dans les systèmes multi-GPU à partir de la liste déroulante,\nou choisissez "Auto Select" pour le déterminer automatiquement. resolutionLayout - Largeur/Hauteur :\nDéfinit la taille de la fenêtre de l'émulateur au démarrage, qui peut être redimensionnée pendant le jeu.\nCela diffère de la résolution interne du jeu. + Largeur/Hauteur:\nDéfinit la taille de la fenêtre de l'émulateur au démarrage, qui peut être redimensionnée pendant le jeu.\nCela diffère de la résolution interne du jeu. heightDivider - Diviseur Vblank :\nLe taux de rafraîchissement de l'émulateur est multiplié par ce nombre. Changer cela peut avoir des effets négatifs, tels qu'une augmentation de la vitesse du jeu ou la rupture de fonctionnalités critiques du jeu qui ne s'attendent pas à ce changement ! + Diviseur Vblank:\nLe taux de rafraîchissement de l'émulateur est multiplié par ce nombre. Changer cela peut avoir des effets négatifs, tels qu'une augmentation de la vitesse du jeu ou la rupture de fonctionnalités critiques du jeu qui ne s'attendent pas à ce changement ! dumpShadersCheckBox - Activer l'exportation de shaders :\nPour le débogage technique, les shaders du jeu sont enregistrés dans un dossier lors du rendu. + Activer l'exportation de shaders:\nPour le débogage technique, les shaders du jeu sont enregistrés dans un dossier lors du rendu. nullGpuCheckBox - Activer le GPU nul :\nPour le débogage technique, désactive le rendu du jeu comme s'il n'y avait pas de carte graphique. + Activer le GPU nul:\nPour le débogage technique, désactive le rendu du jeu comme s'il n'y avait pas de carte graphique. - - dumpPM4CheckBox - Activer l'exportation PM4 :\nPour le débogage technique, enregistre les données brutes des instructions GPU dans un dossier pendant que l'émulateur les traite. + + gameFoldersBox + Dossiers de jeux:\nLa liste des dossiers à vérifier pour les jeux installés. + + + + addFolderButton + Ajouter:\nAjouter un dossier à la liste. + + + + removeFolderButton + Supprimer:\nSupprimer un dossier de la liste. debugDump - Activer l'exportation de débogage :\nEnregistre les symboles d'importation et d'exportation et les informations d'en-tête du fichier du programme PS4 actuel dans un répertoire. + Activer l'exportation de débogage:\nEnregistre les symboles d'importation et d'exportation et les informations d'en-tête du fichier du programme PS4 actuel dans un répertoire. vkValidationCheckBox - Activer les couches de validation Vulkan :\nActive un système qui valide l'état du rendu Vulkan et enregistre des informations sur son état interne. Cela réduit les performances et peut changer le comportement de l'émulation. + Activer les couches de validation Vulkan:\nActive un système qui valide l'état du rendu Vulkan et enregistre des informations sur son état interne. Cela réduit les performances et peut changer le comportement de l'émulation. vkSyncValidationCheckBox - Activer la validation de synchronisation Vulkan :\nActive un système qui valide la planification des tâches de rendu Vulkan. Cela réduit les performances et peut changer le comportement de l'émulation. + Activer la validation de synchronisation Vulkan:\nActive un système qui valide la planification des tâches de rendu Vulkan. Cela réduit les performances et peut changer le comportement de l'émulation. rdocCheckBox - Activer le débogage RenderDoc :\nS'il est activé, l'émulateur fournit une compatibilité avec Renderdoc, permettant d'enregistrer et d'analyser la trame rendue actuelle. + Activer le débogage RenderDoc:\nS'il est activé, l'émulateur fournit une compatibilité avec Renderdoc, permettant d'enregistrer et d'analyser la trame rendue actuelle. @@ -1121,6 +1339,11 @@ Path Répertoire + + + Play Time + Temps de jeu + CheckUpdate @@ -1137,7 +1360,7 @@ Network error: - Erreur réseau : + Erreur réseau: @@ -1169,7 +1392,7 @@ Update Available Mise à jour disponible - + Update Channel Canal de Mise à Jour diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index fb348ee29..633ba9810 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -52,6 +52,19 @@ Betöltés.. + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Mappa kiválasztása + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Összes Másolása + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG PKG telepítése + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Teljesképernyő Engedélyezése + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro PS4 Pro + + + Enable Discord Rich Presence + Engedélyezze a Discord Rich Presence-t + Username @@ -434,6 +517,36 @@ Log Filter Naplózási Filter + + + Input + Bemenet + + + + Cursor + Kurzor + + + + Hide Cursor + Kurzor elrejtése + + + + Hide Cursor Idle Timeout + Kurzor inaktivitási időtúllépés + + + + Controller + Kontroller + + + + Back Button Behavior + Vissza gomb viselkedése + Graphics @@ -475,9 +588,24 @@ NULL GPU Engedélyezése - - Enable PM4 Dumping - PM4 Dumpolás Engedélyezése + + Paths + Útvonalak + + + + Game Folders + Játékmappák + + + + Add... + Hozzáadás... + + + + Remove + Eltávolítás @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Frissítések sikeresen letöltve! Minden elérhető frissítés letöltésre került, nem szükséges egyesével letölteni őket minden játékhoz, mint a csalások esetében. Ha a javítás nem jelenik meg, lehet, hogy nem létezik a játék adott sorozatszámához és verziójához. Lehet, hogy frissítenie kell a játékot. + Frissítések sikeresen letöltve! Minden elérhető frissítés letöltésre került, nem szükséges egyesével letölteni őket minden játékhoz, mint a csalások esetében. Ha a javítás nem jelenik meg, lehet, hogy nem létezik a játék adott sorozatszámához és verziójához. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Nem sikerült HTML oldal lekérése. + + + The game is in version: %1 + A játék verziója: %1 + + + + The downloaded patch only works on version: %1 + A letöltött javítás csak a(z) %1 verzión működik + + + + You may need to update your game. + Lehet, hogy frissítened kell a játékodat. + + + + Incompatibility Notice + Inkompatibilitási értesítés + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Teljes képernyő engedélyezése:\nAutomatikusan teljes képernyőre állítja a játék ablakát.\nEz a F11 billentyű megnyomásával kapcsolható ki/be. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox PS4 Pro:\nAz emulátort PS4 PRO-ként kezeli, ami engedélyezheti a speciális funkciókat olyan játékokban, amelyek támogatják. + + + discordRPCCheckbox + Engedélyezze a Discord Rich Presence-t:\nMegjeleníti az emulator ikonját és a kapcsolódó információkat a Discord profilján. + userName @@ -1016,7 +1174,7 @@ logFilter - Napló szűrő: Csak bizonyos információk megjelenítésére szűri a naplót. Példák: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Szintek: Trace, Debug, Info, Warning, Error, Critical - ebben a sorrendben, egy konkrét szint elnémítja az előtte lévő összes szintet, és naplózza az utána következő szinteket. + Napló szűrő:\nCsak bizonyos információk megjelenítésére szűri a naplót.\nPéldák: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Szintek: Trace, Debug, Info, Warning, Error, Critical - ebben a sorrendben, egy konkrét szint elnémítja az előtte lévő összes szintet, és naplózza az utána következő szinteket. @@ -1028,6 +1186,56 @@ GUIgroupBox Játék címzene lejátszása:\nHa a játék támogatja, engedélyezze a speciális zene lejátszását, amikor a játékot kiválasztja a GUI-ban. + + + hideCursorGroupBox + Akurátor elrejtése:\nVálassza ki, mikor tűnjön el az egérkurzor:\nSoha: Az egér mindig látható.\nInaktív: Állítson be egy időt, amikor inaktív állapotban eltűnik.\nMindig: soha nem látja az egeret. + + + + idleTimeoutGroupBox + Állítson be egy időt, ameddig az egér inaktív állapot után eltűnik. + + + + backButtonBehaviorGroupBox + Vissza gomb viselkedés:\nBeállítja a vezérlő vissza gombját, hogy utánozza a PS4 érintőpadján megadott pozíció megérintését. + + + + Never + Soha + + + + Idle + Inaktív + + + + Always + Mindig + + + + Touchpad Left + Érintőpad Balra + + + + Touchpad Right + Érintőpad Jobbra + + + + Touchpad Center + Érintőpad Középen + + + + None + Semmi + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Null GPU engedélyezése:\nMűszaki hibaelhárítás céljából letiltja a játék renderelését, mintha nem lenne grafikus kártya. - - dumpPM4CheckBox - PM4 dumpolás engedélyezése:\nMűszaki hibaelhárítás céljából a nyers GPU utasítási adatokat elmenti egy mappába, ahogy az emulátor feldolgozza őket. + + gameFoldersBox + Játék mappák:\nA mappák listája az telepített játékok ellenőrzésére. + + + + addFolderButton + Hozzáadás:\nHozzon létre egy mappát a listában. + + + + removeFolderButton + Eltávolítás:\nTávolítson el egy mappát a listából. @@ -1121,6 +1339,11 @@ Path Útvonal + + + Play Time + Játékidő + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Frissítés elérhető - + Update Channel Frissítési Csatorna diff --git a/src/qt_gui/translations/id.ts b/src/qt_gui/translations/id.ts index 86c58c6ce..f841ad3a8 100644 --- a/src/qt_gui/translations/id.ts +++ b/src/qt_gui/translations/id.ts @@ -52,6 +52,19 @@ Loading... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Choose directory + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Copy All + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Install PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Enable Fullscreen + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Aktifkan Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Log Filter + + + Input + Masukan + + + + Cursor + Kursor + + + + Hide Cursor + Sembunyikan kursor + + + + Hide Cursor Idle Timeout + Batas waktu sembunyikan kursor tidak aktif + + + + Controller + Pengontrol + + + + Back Button Behavior + Perilaku tombol kembali + Graphics @@ -475,9 +588,24 @@ Enable NULL GPU - - Enable PM4 Dumping - Enable PM4 Dumping + + Paths + Jalur + + + + Game Folders + Folder Permainan + + + + Add... + Tambah... + + + + Remove + Hapus @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Patch Berhasil Diunduh! Semua Patch yang tersedia untuk semua game telah diunduh, tidak perlu mengunduhnya satu per satu seperti yang terjadi pada Cheat. Jika patch tidak muncul, mungkin patch tersebut tidak ada untuk nomor seri dan versi game yang spesifik. Mungkin perlu memperbarui game. + Patch Berhasil Diunduh! Semua Patch yang tersedia untuk semua game telah diunduh, tidak perlu mengunduhnya satu per satu seperti yang terjadi pada Cheat. Jika patch tidak muncul, mungkin patch tersebut tidak ada untuk nomor seri dan versi game yang spesifik. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Gagal mengambil halaman HTML. + + + The game is in version: %1 + Permainan berada di versi: %1 + + + + The downloaded patch only works on version: %1 + Patch yang diunduh hanya berfungsi pada versi: %1 + + + + You may need to update your game. + Anda mungkin perlu memperbarui permainan Anda. + + + + Incompatibility Notice + Pemberitahuan Ketidakcocokan + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Aktifkan Mode Layar Penuh:\nSecara otomatis menempatkan jendela permainan dalam mode layar penuh.\nIni dapat dinonaktifkan dengan menekan tombol F11. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox Adalah PS4 Pro:\nMembuat emulator berfungsi sebagai PS4 PRO, yang mungkin mengaktifkan fitur khusus dalam permainan yang mendukungnya. + + + discordRPCCheckbox + Aktifkan Discord Rich Presence:\nMenampilkan ikon emulator dan informasi relevan di profil Discord Anda. + userName @@ -1016,7 +1174,7 @@ logFilter - Filter Log: Menyaring log untuk hanya mencetak informasi tertentu. Contoh: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Tingkatan: Trace, Debug, Info, Warning, Error, Critical - dalam urutan ini, tingkat tertentu membungkam semua tingkat sebelumnya dalam daftar dan mencatat setiap tingkat setelahnya. + Filter Log:\nMenyaring log untuk hanya mencetak informasi tertentu.\nContoh: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Tingkatan: Trace, Debug, Info, Warning, Error, Critical - dalam urutan ini, tingkat tertentu membungkam semua tingkat sebelumnya dalam daftar dan mencatat setiap tingkat setelahnya. @@ -1028,6 +1186,56 @@ GUIgroupBox Putar Musik Judul Permainan:\nJika permainan mendukungnya, aktifkan pemutaran musik khusus saat memilih permainan di GUI. + + + hideCursorGroupBox + Sembunyikan Kursor:\nPilih kapan kursor akan menghilang:\nTidak Pernah: Anda akan selalu melihat mouse.\nTidak Aktif: Tetapkan waktu untuk menghilang setelah tidak aktif.\nSelalu: Anda tidak akan pernah melihat mouse. + + + + idleTimeoutGroupBox + Tetapkan waktu untuk mouse menghilang setelah tidak aktif. + + + + backButtonBehaviorGroupBox + Perilaku Tombol Kembali:\nMengatur tombol kembali pada pengontrol untuk meniru ketukan di posisi yang ditentukan di touchpad PS4. + + + + Never + Tidak Pernah + + + + Idle + Diam + + + + Always + Selalu + + + + Touchpad Left + Touchpad Kiri + + + + Touchpad Right + Touchpad Kanan + + + + Touchpad Center + Pusat Touchpad + + + + None + Tidak Ada + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Aktifkan GPU Null:\nUntuk tujuan debugging teknis, menonaktifkan rendering permainan seolah-olah tidak ada kartu grafis. - - dumpPM4CheckBox - Aktifkan Pembuangan PM4:\nUntuk tujuan debugging teknis, menyimpan data instruksi GPU mentah ke folder saat emulator memprosesnya. + + gameFoldersBox + Folder Permainan:\nDaftar folder untuk memeriksa permainan yang diinstal. + + + + addFolderButton + Tambah:\nTambahkan folder ke daftar. + + + + removeFolderButton + Hapus:\nHapus folder dari daftar. @@ -1121,6 +1339,11 @@ Path Jalur + + + Play Time + Waktu Bermain + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Pembaruan Tersedia - + Update Channel Saluran Pembaruan diff --git a/src/qt_gui/translations/it.ts b/src/qt_gui/translations/it.ts index f5ea9a65c..071b8e978 100644 --- a/src/qt_gui/translations/it.ts +++ b/src/qt_gui/translations/it.ts @@ -52,6 +52,19 @@ Caricamento... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Scegli cartella + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Copia Tutto + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Installa PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Abilita Schermo Intero + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Modalità Ps4Pro + + + Enable Discord Rich Presence + Abilita Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Filtro Log + + + Input + Input + + + + Cursor + Cursore + + + + Hide Cursor + Nascondi cursore + + + + Hide Cursor Idle Timeout + Timeout inattivo per nascondere il cursore + + + + Controller + Controller + + + + Back Button Behavior + Comportamento del pulsante Indietro + Graphics @@ -475,9 +588,24 @@ Abilita NULL GPU - - Enable PM4 Dumping - Abilita Dump PM4 + + Paths + Percorsi + + + + Game Folders + Cartelle di gioco + + + + Add... + Aggiungi... + + + + Remove + Rimuovi @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Patch scaricata con successo! Vengono scaricate tutte le patch disponibili per tutti i giochi, non è necessario scaricarle singolarmente per ogni gioco come nel caso dei trucchi. Se la patch non appare, potrebbe essere che non esista per il numero di serie e la versione specifica del gioco. Potrebbe essere necessario aggiornare il gioco. + Patch scaricata con successo! Vengono scaricate tutte le patch disponibili per tutti i giochi, non è necessario scaricarle singolarmente per ogni gioco come nel caso dei trucchi. Se la patch non appare, potrebbe essere che non esista per il numero di serie e la versione specifica del gioco. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Impossibile recuperare la pagina HTML. + + + The game is in version: %1 + Il gioco è nella versione: %1 + + + + The downloaded patch only works on version: %1 + La patch scaricata funziona solo sulla versione: %1 + + + + You may need to update your game. + Potresti aver bisogno di aggiornare il tuo gioco. + + + + Incompatibility Notice + Avviso di incompatibilità + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Abilita Schermo Intero:\nMetti automaticamente la finestra di gioco in modalità schermo intero.\nQuesto può essere disattivato premendo il tasto F11. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox È PS4 Pro:\nFa sì che l'emulatore si comporti come una PS4 PRO, il che può abilitare funzionalità speciali in giochi che la supportano. + + + discordRPCCheckbox + Abilita Discord Rich Presence:\nMostra l'icona dell'emulatore e informazioni pertinenti sul tuo profilo Discord. + userName @@ -1016,7 +1174,7 @@ logFilter - Filtro Log: Filtra il log per stampare solo informazioni specifiche. Esempi: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Livelli: Trace, Debug, Info, Warning, Error, Critical - in questo ordine, un livello specifico silenzia tutti i livelli precedenti nell'elenco e registra ogni livello successivo. + Filtro Log:\nFiltra il log per stampare solo informazioni specifiche.\nEsempi: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Livelli: Trace, Debug, Info, Warning, Error, Critical - in questo ordine, un livello specifico silenzia tutti i livelli precedenti nell'elenco e registra ogni livello successivo. @@ -1028,6 +1186,56 @@ GUIgroupBox Riproduci Musica del Titolo:\nSe un gioco lo supporta, attiva la riproduzione di musica speciale quando selezioni il gioco nell'interfaccia grafica. + + + hideCursorGroupBox + Nascondi cursore:\nScegli quando il cursore scomparirà:\nMai: Vedrai sempre il mouse.\nInattivo: Imposta un tempo per farlo scomparire dopo essere stato inattivo.\nSempre: non vedrai mai il mouse. + + + + idleTimeoutGroupBox + Imposta un tempo affinché il mouse scompaia dopo essere stato inattivo. + + + + backButtonBehaviorGroupBox + Comportamento del pulsante Indietro:\nImposta il pulsante Indietro del controller per emulare il tocco sulla posizione specificata sul touchpad PS4. + + + + Never + Mai + + + + Idle + Inattivo + + + + Always + Sempre + + + + Touchpad Left + Touchpad Sinistra + + + + Touchpad Right + Touchpad Destra + + + + Touchpad Center + Centro del Touchpad + + + + None + Nessuno + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Abilita GPU Null:\nPer scopi di debug tecnico, disabilita il rendering del gioco come se non ci fosse alcuna scheda grafica. - - dumpPM4CheckBox - Abilita Pompaggio PM4:\nPer scopi di debug tecnico, salva i dati delle istruzioni GPU grezze in una cartella mentre l'emulatore li elabora. + + gameFoldersBox + Cartelle di Gioco:\nL'elenco delle cartelle da controllare per i giochi installati. + + + + addFolderButton + Aggiungi:\nAggiungi una cartella all'elenco. + + + + removeFolderButton + Rimuovi:\nRimuovi una cartella dall'elenco. @@ -1121,6 +1339,11 @@ Path Percorso + + + Play Time + Tempo di Gioco + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Aggiornamento disponibile - + Update Channel Canale di Aggiornamento diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 166100db6..a79b34e2a 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -52,6 +52,19 @@ 読み込み中... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - ディレクトリを選択 + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All すべてコピー + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG PKGをインストール + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen フルスクリーンを有効にする + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro PS4 Proモード + + + Enable Discord Rich Presence + Discord Rich Presenceを有効にする + Username @@ -434,6 +517,36 @@ Log Filter ログフィルター + + + Input + 入力 + + + + Cursor + カーソル + + + + Hide Cursor + カーソルを隠す + + + + Hide Cursor Idle Timeout + カーソル非アクティブタイムアウト + + + + Controller + コントローラー + + + + Back Button Behavior + 戻るボタンの動作 + Graphics @@ -475,9 +588,24 @@ NULL GPUを有効にする - - Enable PM4 Dumping - PM4ダンプを有効にする + + Paths + パス + + + + Game Folders + ゲームフォルダ + + + + Add... + 追加... + + + + Remove + 削除 @@ -898,7 +1026,7 @@ DownloadComplete_MSG - パッチが正常にダウンロードされました! すべてのゲームに利用可能なパッチがダウンロードされました。チートとは異なり、各ゲームごとに個別にダウンロードする必要はありません。 パッチが表示されない場合、特定のシリアル番号とバージョンのゲームには存在しない可能性があります。ゲームを更新する必要があるかもしれません。 + パッチが正常にダウンロードされました! すべてのゲームに利用可能なパッチがダウンロードされました。チートとは異なり、各ゲームごとに個別にダウンロードする必要はありません。 パッチが表示されない場合、特定のシリアル番号とバージョンのゲームには存在しない可能性があります。 @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. HTMLページの取得に失敗しました。 + + + The game is in version: %1 + ゲームのバージョン: %1 + + + + The downloaded patch only works on version: %1 + ダウンロードしたパッチはバージョン: %1 のみ機能します + + + + You may need to update your game. + ゲームを更新する必要があるかもしれません。 + + + + Incompatibility Notice + 互換性のない通知 + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox 全画面モードを有効にする:\nゲームウィンドウを自動的に全画面モードにします。\nF11キーを押すことで切り替えることができます。 + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox PS4 Proです:\nエミュレーターがPS4 PROとして動作するようにし、これをサポートするゲームで特別な機能を有効にする場合があります。 + + + discordRPCCheckbox + Discord Rich Presenceを有効にする:\nエミュレーターのアイコンと関連情報をDiscordプロフィールに表示します。 + userName @@ -1016,7 +1174,7 @@ logFilter - ログフィルター: 特定の情報のみを印刷するようにログをフィルタリングします。例: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" レベル: Trace, Debug, Info, Warning, Error, Critical - この順序で、特定のレベルはリスト内のすべての前のレベルをサイレンスし、その後のすべてのレベルをログに記録します。 + ログフィルター:\n特定の情報のみを印刷するようにログをフィルタリングします。\n例: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" レベル: Trace, Debug, Info, Warning, Error, Critical - この順序で、特定のレベルはリスト内のすべての前のレベルをサイレンスし、その後のすべてのレベルをログに記録します。 @@ -1028,6 +1186,56 @@ GUIgroupBox タイトルミュージックを再生:\nゲームがそれをサポートしている場合、GUIでゲームを選択したときに特別な音楽を再生することを有効にします。 + + + hideCursorGroupBox + カーソルを隠す:\nカーソルが消えるタイミングを選択してください:\n決して: いつでもマウスが見えます。\nアイドル: アイダルの後に消えるまでの時間を設定します。\n常に: マウスは決して見えません。 + + + + idleTimeoutGroupBox + アイドル後にマウスが消えるまでの時間を設定します。 + + + + backButtonBehaviorGroupBox + 戻るボタンの動作:\nコントローラーの戻るボタンを、PS4のタッチパッドの指定された位置をタッチするように設定します。 + + + + Never + 決して + + + + Idle + アイドル + + + + Always + 常に + + + + Touchpad Left + タッチパッド左 + + + + Touchpad Right + タッチパッド右 + + + + Touchpad Center + タッチパッド中央 + + + + None + なし + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Null GPUを有効にする:\n技術的なデバッグの目的で、グラフィックスカードがないかのようにゲームのレンダリングを無効にします。 - - dumpPM4CheckBox - PM4ダンプを有効にする:\n技術的なデバッグの目的で、エミュレーターが処理している間に生のGPU命令データをフォルダーに保存します。 + + gameFoldersBox + ゲームフォルダ:\nインストールされたゲームを確認するためのフォルダのリスト。 + + + + addFolderButton + 追加:\nリストにフォルダを追加します。 + + + + removeFolderButton + 削除:\nリストからフォルダを削除します。 @@ -1121,6 +1339,11 @@ Path パス + + + Play Time + プレイ時間 + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available アップデートがあります - + Update Channel アップデートチャネル diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index 93ceb9e35..6ef89ea24 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -52,6 +52,19 @@ Loading... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Choose directory + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Copy All + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Install PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Enable Fullscreen + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Enable Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Log Filter + + + Input + Input + + + + Cursor + Cursor + + + + Hide Cursor + Hide Cursor + + + + Hide Cursor Idle Timeout + Hide Cursor Idle Timeout + + + + Controller + Controller + + + + Back Button Behavior + Back Button Behavior + Graphics @@ -475,9 +588,24 @@ Enable NULL GPU - - Enable PM4 Dumping - Enable PM4 Dumping + + Paths + Paths + + + + Game Folders + Game Folders + + + + Add... + Add... + + + + Remove + Remove @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. It may be necessary to update the game. + Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Failed to retrieve HTML page. + + + The game is in version: %1 + The game is in version: %1 + + + + The downloaded patch only works on version: %1 + The downloaded patch only works on version: %1 + + + + You may need to update your game. + You may need to update your game. + + + + Incompatibility Notice + Incompatibility Notice + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Enable Full Screen:\nAutomatically puts the game window into full-screen mode.\nThis can be toggled by pressing the F11 key. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox Is PS4 Pro:\nMakes the emulator act as a PS4 PRO, which may enable special features in games that support it. + + + discordRPCCheckbox + Discord Rich Presence 활성화:\nDiscord 프로필에 에뮬레이터 아이콘과 관련 정보를 표시합니다. + userName @@ -1016,7 +1174,7 @@ logFilter - Log Filter: Filters the log to only print specific information. Examples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Levels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. + Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Levels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. @@ -1028,6 +1186,56 @@ GUIgroupBox Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. + + + hideCursorGroupBox + Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. + + + + idleTimeoutGroupBox + Set a time for the mouse to disappear after being after being idle. + + + + backButtonBehaviorGroupBox + Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. + + + + Never + Never + + + + Idle + Idle + + + + Always + Always + + + + Touchpad Left + Touchpad Left + + + + Touchpad Right + Touchpad Right + + + + Touchpad Center + Touchpad Center + + + + None + None + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. - - dumpPM4CheckBox - Enable PM4 Dumping:\nFor the sake of technical debugging, saves raw GPU instruction data to a folder as the emulator processes it. + + gameFoldersBox + Game Folders:\nThe list of folders to check for installed games. + + + + addFolderButton + Add:\nAdd a folder to the list. + + + + removeFolderButton + Remove:\nRemove a folder from the list. @@ -1121,6 +1339,11 @@ Path Path + + + Play Time + Play Time + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Update Available - + Update Channel Update Channel diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index cac8ebbfc..d7fc6e844 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -52,6 +52,19 @@ Loading... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Choose directory + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Copy All + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Install PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Enable Fullscreen + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Įjungti Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Log Filter + + + Input + Įvestis + + + + Cursor + Žymeklis + + + + Hide Cursor + Slėpti žymeklį + + + + Hide Cursor Idle Timeout + Žymeklio paslėpimo neveikimo laikas + + + + Controller + Valdiklis + + + + Back Button Behavior + Atgal mygtuko elgsena + Graphics @@ -475,9 +588,24 @@ Enable NULL GPU - - Enable PM4 Dumping - Enable PM4 Dumping + + Paths + Keliai + + + + Game Folders + Žaidimų aplankai + + + + Add... + Pridėti... + + + + Remove + Pašalinti @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Pataisos sėkmingai atsisiųstos! Visos pataisos visiems žaidimams buvo atsisiųstos, nebėra reikalo jas atsisiųsti atskirai kiekvienam žaidimui, kaip tai vyksta su sukčiavimais. Jei pleistras nepasirodo, gali būti, kad jo nėra tam tikram žaidimo serijos numeriui ir versijai. Gali prireikti atnaujinti žaidimą. + Pataisos sėkmingai atsisiųstos! Visos pataisos visiems žaidimams buvo atsisiųstos, nebėra reikalo jas atsisiųsti atskirai kiekvienam žaidimui, kaip tai vyksta su sukčiavimais. Jei pleistras nepasirodo, gali būti, kad jo nėra tam tikram žaidimo serijos numeriui ir versijai. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Nepavyko gauti HTML puslapio. + + + The game is in version: %1 + Žaidimas yra versijoje: %1 + + + + The downloaded patch only works on version: %1 + Parsisiųstas pataisas veikia tik versijoje: %1 + + + + You may need to update your game. + Gali tekti atnaujinti savo žaidimą. + + + + Incompatibility Notice + Suderinamumo pranešimas + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Įjungti visą ekraną:\nAutomatiškai perjungia žaidimo langą į viso ekrano režimą.\nTai galima išjungti paspaudus F11 klavišą. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox Ar PS4 Pro:\nPadaro, kad emuliatorius veiktų kaip PS4 PRO, kas gali įjungti specialias funkcijas žaidimuose, kurie tai palaiko. + + + discordRPCCheckbox + Įjungti Discord Rich Presence:\nRodo emuliatoriaus ikoną ir susijusią informaciją jūsų Discord profilyje. + userName @@ -1016,7 +1174,7 @@ logFilter - Žurnalo filtras: Filtruojamas žurnalas, kad būtų spausdinama tik konkreti informacija. Pavyzdžiai: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Lygiai: Trace, Debug, Info, Warning, Error, Critical - šia tvarka, konkretus lygis nutildo visus ankstesnius lygius sąraše ir registruoja visus vėlesnius. + Žurnalo filtras:\nFiltruojamas žurnalas, kad būtų spausdinama tik konkreti informacija.\nPavyzdžiai: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Lygiai: Trace, Debug, Info, Warning, Error, Critical - šia tvarka, konkretus lygis nutildo visus ankstesnius lygius sąraše ir registruoja visus vėlesnius. @@ -1028,6 +1186,56 @@ GUIgroupBox Groti antraščių muziką:\nJei žaidimas tai palaiko, įjungia specialios muzikos grojimą, kai pasirinkite žaidimą GUI. + + + hideCursorGroupBox + Slėpti žymeklį:\nPasirinkite, kada žymeklis dings:\nNiekuomet: Visada matysite pelę.\nNeaktyvus: Nustatykite laiką, po kurio ji dings, kai bus neaktyvi.\nVisada: niekada nematysite pelės. + + + + idleTimeoutGroupBox + Nustatykite laiką, po kurio pelė dings, kai bus neaktyvi. + + + + backButtonBehaviorGroupBox + Atgal mygtuko elgesys:\nNustato valdiklio atgal mygtuką imituoti paspaudimą nurodytoje vietoje PS4 jutiklinėje plokštėje. + + + + Never + Niekada + + + + Idle + Neaktyvus + + + + Always + Visada + + + + Touchpad Left + Jutiklinis Paviršius Kairėje + + + + Touchpad Right + Jutiklinis Paviršius Dešinėje + + + + Touchpad Center + Jutiklinis Paviršius Centre + + + + None + Nieko + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Įjungti tuščią GPU:\nTechninio derinimo tikslais išjungia žaidimo renderiavimą, tarsi nebūtų grafikos plokštės. - - dumpPM4CheckBox - Įjungti PM4 išmetimą:\nTechninio derinimo tikslais saugo žalius GPU nurodymų duomenis į aplanką, kai emuliatorius juos apdoroja. + + gameFoldersBox + Žaidimų aplankai:\nAplankų sąrašas, kurį reikia patikrinti, ar yra įdiegtų žaidimų. + + + + addFolderButton + Pridėti:\nPridėti aplanką į sąrašą. + + + + removeFolderButton + Pašalinti:\nPašalinti aplanką iš sąrašo. @@ -1121,6 +1339,11 @@ Path Kelias + + + Play Time + Žaidimo laikas + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Prieinama atnaujinimas - + Update Channel Atnaujinimo Kanalas diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb.ts index e0bc1a550..cdcf4d5fb 100644 --- a/src/qt_gui/translations/nb.ts +++ b/src/qt_gui/translations/nb.ts @@ -52,6 +52,19 @@ Loading... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Choose directory + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Copy All + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Install PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Enable Fullscreen + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Aktiver Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Log Filter + + + Input + Inndata + + + + Cursor + Markør + + + + Hide Cursor + Skjul markør + + + + Hide Cursor Idle Timeout + Timeout for å skjule markør ved inaktivitet + + + + Controller + Kontroller + + + + Back Button Behavior + Tilbakeknapp atferd + Graphics @@ -475,9 +588,24 @@ Enable NULL GPU - - Enable PM4 Dumping - Enable PM4 Dumping + + Paths + Stier + + + + Game Folders + Spillmapper + + + + Add... + Legg til... + + + + Remove + Fjern @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Oppdateringer lastet ned vellykket! Alle oppdateringer tilgjengelige for alle spill har blitt lastet ned, det er ikke nødvendig å laste dem ned individuelt for hvert spill som skjer med jukser. Hvis oppdateringen ikke vises, kan det hende at den ikke finnes for den spesifikke serienummeret og versjonen av spillet. Det kan være nødvendig å oppdatere spillet. + Oppdateringer lastet ned vellykket! Alle oppdateringer tilgjengelige for alle spill har blitt lastet ned, det er ikke nødvendig å laste dem ned individuelt for hvert spill som skjer med jukser. Hvis oppdateringen ikke vises, kan det hende at den ikke finnes for den spesifikke serienummeret og versjonen av spillet. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Kunne ikke hente HTML-side. + + + The game is in version: %1 + Spillet er i versjon: %1 + + + + The downloaded patch only works on version: %1 + Den nedlastede patchen fungerer bare på versjon: %1 + + + + You may need to update your game. + Du må kanskje oppdatere spillet ditt. + + + + Incompatibility Notice + Inkompatibilitetsvarsel + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Aktiver fullskjerm:\nSetter automatisk spillvinduet i fullskjermmodus.\nDette kan slås av ved å trykke på F11-tasten. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox Er PS4 Pro:\nFår emulatoren til å fungere som en PS4 PRO, noe som kan aktivere spesielle funksjoner i spill som støtter det. + + + discordRPCCheckbox + Aktiver Discord Rich Presence:\nViser emulatorikonet og relevant informasjon på Discord-profilen din. + userName @@ -1016,7 +1174,7 @@ logFilter - Loggfilter: Filtrerer loggen for å kun skrive ut spesifikk informasjon. Eksempler: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Nivåer: Trace, Debug, Info, Warning, Error, Critical - i denne rekkefølgen, et spesifikt nivå demper alle tidligere nivåer i listen og logger alle nivåer etter det. + Loggfilter:\nFiltrerer loggen for å kun skrive ut spesifikk informasjon.\nEksempler: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Nivåer: Trace, Debug, Info, Warning, Error, Critical - i denne rekkefølgen, et spesifikt nivå demper alle tidligere nivåer i listen og logger alle nivåer etter det. @@ -1028,6 +1186,56 @@ GUIgroupBox Spille tittelmusikk:\nHvis et spill støtter det, aktiverer det å spille spesiell musikk når du velger spillet i GUI. + + + hideCursorGroupBox + Skjul musepeker:\nVelg når musepekeren skal forsvinne:\nAldri: Du vil alltid se musen.\nInaktiv: Sett en tid for at den skal forsvinne etter å ha vært inaktiv.\nAlltid: du vil aldri se musen. + + + + idleTimeoutGroupBox + Sett en tid for når musen forsvinner etter å ha vært inaktiv. + + + + backButtonBehaviorGroupBox + Atferd for tilbaketasten:\nSetter tilbaketasten på kontrolleren til å imitere et trykk på den angitte posisjonen på PS4s berøringsplate. + + + + Never + Aldri + + + + Idle + Inaktiv + + + + Always + Alltid + + + + Touchpad Left + Berøringsplate Venstre + + + + Touchpad Right + Berøringsplate Høyre + + + + Touchpad Center + Berøringsplate Midt + + + + None + Ingen + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Aktiver Null GPU:\nFor teknisk feilsøking deaktiverer spillrendering som om det ikke var noe grafikkort. - - dumpPM4CheckBox - Aktiver PM4 dumping:\nFor teknisk feilsøking lagrer rå GPU-instruksjonsdata i en mappe mens emulatoren behandler dem. + + gameFoldersBox + Spillmapper:\nListen over mapper for å sjekke installerte spill. + + + + addFolderButton + Legg til:\nLegg til en mappe til listen. + + + + removeFolderButton + Fjern:\nFjern en mappe fra listen. @@ -1121,6 +1339,11 @@ Path Sti + + + Play Time + Spilletid + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Oppdatering tilgjengelig - + Update Channel Oppdateringskanal diff --git a/src/qt_gui/translations/nl.ts b/src/qt_gui/translations/nl.ts index 29a60ecc8..380d90705 100644 --- a/src/qt_gui/translations/nl.ts +++ b/src/qt_gui/translations/nl.ts @@ -52,6 +52,19 @@ Loading... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Choose directory + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Copy All + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Install PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Enable Fullscreen + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Discord Rich Presence inschakelen + Username @@ -434,6 +517,36 @@ Log Filter Log Filter + + + Input + Invoer + + + + Cursor + Cursor + + + + Hide Cursor + Cursor verbergen + + + + Hide Cursor Idle Timeout + Inactiviteit timeout voor het verbergen van de cursor + + + + Controller + Controller + + + + Back Button Behavior + Achterknop gedrag + Graphics @@ -475,9 +588,24 @@ Enable NULL GPU - - Enable PM4 Dumping - Enable PM4 Dumping + + Paths + Pad + + + + Game Folders + Spelmappen + + + + Add... + Toevoegen... + + + + Remove + Verwijderen @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Patches succesvol gedownload! Alle beschikbare patches voor alle spellen zijn gedownload. Het is niet nodig om ze afzonderlijk te downloaden voor elk spel dat cheats heeft. Als de patch niet verschijnt, kan het zijn dat deze niet bestaat voor het specifieke serienummer en de versie van het spel. Het kan nodig zijn om het spel bij te werken. + Patches succesvol gedownload! Alle beschikbare patches voor alle spellen zijn gedownload. Het is niet nodig om ze afzonderlijk te downloaden voor elk spel dat cheats heeft. Als de patch niet verschijnt, kan het zijn dat deze niet bestaat voor het specifieke serienummer en de versie van het spel. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Kan HTML-pagina niet ophalen. + + + The game is in version: %1 + Het spel is in versie: %1 + + + + The downloaded patch only works on version: %1 + De gedownloade patch werkt alleen op versie: %1 + + + + You may need to update your game. + Misschien moet je je spel bijwerken. + + + + Incompatibility Notice + Incompatibiliteitsmelding + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Volledig scherm inschakelen:\nZet het gamevenster automatisch in de volledig scherm modus.\nDit kan worden omgeschakeld door op de F11-toets te drukken. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox Is PS4 Pro:\nLaat de emulator zich gedragen als een PS4 PRO, wat speciale functies kan inschakelen in games die dit ondersteunen. + + + discordRPCCheckbox + Discord Rich Presence inschakelen:\nToont het emulatoricoon en relevante informatie op je Discord-profiel. + userName @@ -1016,7 +1174,7 @@ logFilter - Logfilter: Filtert het logboek om alleen specifieke informatie af te drukken. Voorbeelden: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveaus: Trace, Debug, Info, Waarschuwing, Fout, Kritiek - in deze volgorde, een specifiek niveau dempt alle voorgaande niveaus in de lijst en logt alle niveaus daarna. + Logfilter:\nFiltert het logboek om alleen specifieke informatie af te drukken.\nVoorbeelden: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveaus: Trace, Debug, Info, Waarschuwing, Fout, Kritiek - in deze volgorde, een specifiek niveau dempt alle voorgaande niveaus in de lijst en logt alle niveaus daarna. @@ -1028,6 +1186,56 @@ GUIgroupBox Speel titelsong:\nAls een game dit ondersteunt, wordt speciale muziek afgespeeld wanneer je het spel in de GUI selecteert. + + + hideCursorGroupBox + Verberg cursor:\nKies wanneer de cursor verdwijnt:\nNooit: Je ziet altijd de muis.\nInactief: Stel een tijd in waarna deze verdwijnt na inactiviteit.\nAltijd: je ziet de muis nooit. + + + + idleTimeoutGroupBox + Stel een tijd in voor wanneer de muis verdwijnt na inactiviteit. + + + + backButtonBehaviorGroupBox + Gedrag van de terugknop:\nStelt de terugknop van de controller in om een aanraking op de opgegeven positie op de PS4-touchpad na te bootsen. + + + + Never + Nooit + + + + Idle + Inactief + + + + Always + Altijd + + + + Touchpad Left + Touchpad Links + + + + Touchpad Right + Touchpad Rechts + + + + Touchpad Center + Touchpad Midden + + + + None + Geen + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Null GPU inschakelen:\nVoor technische foutopsporing schakelt de game-rendering uit alsof er geen grafische kaart is. - - dumpPM4CheckBox - PM4 dumpen inschakelen:\nVoor technische foutopsporing slaat het ruwe GPU-instructiegegevens op in een map terwijl de emulator ze verwerkt. + + gameFoldersBox + Spelmap:\nDe lijst met mappen om te controleren op geïnstalleerde spellen. + + + + addFolderButton + Toevoegen:\nVoeg een map toe aan de lijst. + + + + removeFolderButton + Verwijderen:\nVerwijder een map uit de lijst. @@ -1121,6 +1339,11 @@ Path Pad + + + Play Time + Speeltijd + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Update beschikbaar - + Update Channel Updatekanaal diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index bd772b0fe..5d211734e 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -52,6 +52,19 @@ Ładowanie... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Wybierz katalog + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Kopiuj wszystko + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Zainstaluj PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Włącz pełny ekran + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Emulacja PS4 Pro + + + Enable Discord Rich Presence + Włącz Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Filtrowanie dziennika + + + Input + Wejście + + + + Cursor + Kursor + + + + Hide Cursor + Ukryj kursor + + + + Hide Cursor Idle Timeout + Czas oczekiwania na ukrycie kursora przy bezczynności + + + + Controller + Kontroler + + + + Back Button Behavior + Zachowanie przycisku wstecz + Graphics @@ -475,9 +588,24 @@ Wyłącz kartę graficzną - - Enable PM4 Dumping - Włącz zgrywanie PM4 + + Paths + Ścieżki + + + + Game Folders + Foldery gier + + + + Add... + Dodaj... + + + + Remove + Usuń @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Poprawki zostały pomyślnie pobrane! Wszystkie dostępne poprawki dla wszystkich gier zostały pobrane. Nie ma potrzeby pobierania ich osobno dla każdej gry, która ma kody. Jeśli poprawka się nie pojawia, możliwe, że nie istnieje dla konkretnego numeru seryjnego i wersji gry. Może być konieczne zaktualizowanie gry. + Poprawki zostały pomyślnie pobrane! Wszystkie dostępne poprawki dla wszystkich gier zostały pobrane. Nie ma potrzeby pobierania ich osobno dla każdej gry, która ma kody. Jeśli poprawka się nie pojawia, możliwe, że nie istnieje dla konkretnego numeru seryjnego i wersji gry. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Nie udało się pobrać strony HTML. + + + The game is in version: %1 + Gra jest w wersji: %1 + + + + The downloaded patch only works on version: %1 + Pobrana łatka działa tylko w wersji: %1 + + + + You may need to update your game. + Możesz potrzebować zaktualizować swoją grę. + + + + Incompatibility Notice + Powiadomienie o niezgodności + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Włącz tryb pełnoekranowy:\nAutomatycznie przełącza okno gry w tryb pełnoekranowy.\nMożna to wyłączyć naciskając klawisz F11. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox Czy PS4 Pro:\nSprawia, że emulator działa jak PS4 PRO, co może aktywować specjalne funkcje w grach, które to obsługują. + + + discordRPCCheckbox + Włącz Discord Rich Presence:\nWyświetla ikonę emuladora i odpowiednie informacje na twoim profilu Discord. + userName @@ -1016,7 +1174,7 @@ logFilter - Filtr logu: Filtruje dziennik, aby drukować tylko określone informacje. Przykłady: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Poziomy: Trace, Debug, Info, Warning, Error, Critical - w tej kolejności, konkretny poziom wycisza wszystkie wcześniejsze poziomy w liście i rejestruje wszystkie poziomy później. + Filtr logu:\nFiltruje dziennik, aby drukować tylko określone informacje.\nPrzykłady: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Poziomy: Trace, Debug, Info, Warning, Error, Critical - w tej kolejności, konkretny poziom wycisza wszystkie wcześniejsze poziomy w liście i rejestruje wszystkie poziomy później. @@ -1028,6 +1186,56 @@ GUIgroupBox Odtwórz muzykę tytułową:\nJeśli gra to obsługuje, aktywuje odtwarzanie specjalnej muzyki podczas wybierania gry w GUI. + + + hideCursorGroupBox + Ukryj kursor:\nWybierz, kiedy kursor zniknie:\nNigdy: Zawsze będziesz widział myszkę.\nNieaktywny: Ustaw czas, po którym zniknie po bezczynności.\nZawsze: nigdy nie zobaczysz myszki. + + + + idleTimeoutGroupBox + Ustaw czas, po którym mysz zniknie po bezczynności. + + + + backButtonBehaviorGroupBox + Zachowanie przycisku Wstecz:\nUstawia przycisk Wstecz kontrolera tak, aby emulował dotknięcie określonego miejsca na panelu dotykowym PS4. + + + + Never + Nigdy + + + + Idle + Bezczynny + + + + Always + Zawsze + + + + Touchpad Left + Touchpad Lewy + + + + Touchpad Right + Touchpad Prawy + + + + Touchpad Center + Touchpad Środkowy + + + + None + Brak + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Włącz Null GPU:\nDla technicznego debugowania dezaktywuje renderowanie gry tak, jakby nie było karty graficznej. - - dumpPM4CheckBox - Włącz zrzucanie PM4:\nDla technicznego debugowania zapisuje surowe dane instrukcji GPU w folderze, gdy emulator je przetwarza. + + gameFoldersBox + Foldery gier:\nLista folderów do sprawdzenia zainstalowanych gier. + + + + addFolderButton + Dodaj:\nDodaj folder do listy. + + + + removeFolderButton + Usuń:\nUsuń folder z listy. @@ -1121,6 +1339,11 @@ Path Ścieżka + + + Play Time + Czas gry + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Dostępna aktualizacja - + Update Channel Kanał Aktualizacji diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 38836b1d0..eb79fade4 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -52,6 +52,19 @@ Carregando... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Escolha o diretório + + + + Select which directory you want to install to. + Selecione o diretório em que você deseja instalar. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Copiar Tudo + + + Delete... + Deletar... + + + + Delete Game + Deletar Jogo + + + + Delete Update + Deletar Atualização + + + + Delete DLC + Deletar DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Instalar PKG + + + Game + Jogo + + + + requiresEnableSeparateUpdateFolder_MSG + Este recurso requer a opção de configuração 'Habilitar Pasta de Atualização Separada' para funcionar. Se você quiser usar este recurso, habilite-o. + + + + This game has no update to delete! + Este jogo não tem atualização para excluir! + + + + Update + Atualização + + + + This game has no DLC to delete! + Este jogo não tem DLC para excluir! + + + + DLC + DLC + + + + Delete %1 + Deletar %1 + + + + Are you sure you want to delete %1's %2 directory? + Tem certeza de que deseja excluir o diretório %2 de %1 ? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Ativar Tela Cheia + + + Enable Separate Update Folder + Habilitar pasta de atualização separada + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Modo PS4 Pro + + + Enable Discord Rich Presence + Ativar Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Filtro do Registro + + + Input + Entradas + + + + Cursor + Cursor + + + + Hide Cursor + Ocultar Cursor + + + + Hide Cursor Idle Timeout + Tempo de Inatividade para Ocultar Cursor + + + + Controller + Controle + + + + Back Button Behavior + Comportamento do botão Voltar + Graphics @@ -475,9 +588,24 @@ Ativar GPU NULA - - Enable PM4 Dumping - Ativar Dumping de PM4 + + Paths + Pastas + + + + Game Folders + Pastas dos Jogos + + + + Add... + Adicionar... + + + + Remove + Remover @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Patches Baixados com Sucesso! Todos os patches disponíveis para todos os jogos foram baixados, não é necessário baixá-los individualmente para cada jogo como acontece com os Cheats. Se o patch não aparecer, pode ser que ele não exista para o número de série e a versão específicos do jogo. Pode ser necessário atualizar o jogo. + Patches Baixados com Sucesso! Todos os patches disponíveis para todos os jogos foram baixados, não é necessário baixá-los individualmente para cada jogo como acontece com os Cheats. Se o patch não aparecer, pode ser que ele não exista para o número de série e a versão específicos do jogo. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Falha ao recuperar a página HTML. + + + The game is in version: %1 + O jogo está na versão: %1 + + + + The downloaded patch only works on version: %1 + O patch baixado só funciona na versão: %1 + + + + You may need to update your game. + Talvez você precise atualizar seu jogo. + + + + Incompatibility Notice + Aviso de incompatibilidade + Failed to open file: @@ -991,17 +1139,27 @@ fullscreenCheckBox - Ativar modo tela cheia:\nMove automaticamente a janela do jogo para o modo tela cheia.\nIsso pode ser alterado pressionando a tecla F11. + Ativar Tela Cheia:\nMove automaticamente a janela do jogo para o modo tela cheia.\nIsso pode ser alterado pressionando a tecla F11. + + + + separateUpdatesCheckBox + Habilitar pasta de atualização separada:\nPermite instalar atualizações de jogos em uma pasta separada para fácil gerenciamento. showSplashCheckBox - Mostrar tela inicial:\nExibe a tela inicial do jogo (imagem especial) ao iniciar o jogo. + Mostrar Splash Inicial:\nExibe a tela inicial do jogo (imagem especial) ao iniciar o jogo. ps4proCheckBox - É um PS4 Pro:\nFaz o emulador agir como um PS4 PRO, o que pode ativar recursos especiais em jogos que o suportam. + Modo PS4 Pro:\nFaz o emulador agir como um PS4 PRO, o que pode ativar recursos especiais em jogos que o suportam. + + + + discordRPCCheckbox + Ativar Discord Rich Presence:\nExibe o ícone do emulador e informações relevantes no seu perfil do Discord. @@ -1011,12 +1169,12 @@ logTypeGroupBox - Tipo de log:\nDefine se a saída da janela de log deve ser sincronizada para melhorar o desempenho. Isso pode impactar negativamente a emulação. + Tipo de Registro:\nDefine se a saída da janela de log deve ser sincronizada para melhorar o desempenho. Isso pode impactar negativamente a emulação. logFilter - Filtro de log: Imprime apenas informações específicas. Exemplos: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Níveis: Trace, Debug, Info, Warning, Error, Critical - assim, um nível específico desativa todos os níveis anteriores na lista e registra todos os níveis subsequentes. + Filtro de Registro:\nImprime apenas informações específicas.\nExemplos: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nNíveis: Trace, Debug, Info, Warning, Error, Critical - assim, um nível específico desativa todos os níveis anteriores na lista e registra todos os níveis subsequentes. @@ -1028,6 +1186,56 @@ GUIgroupBox Reproduzir música de abertura:\nSe o jogo suportar, ativa a reprodução de uma música especial ao selecionar o jogo na interface do menu. + + + hideCursorGroupBox + Ocultar Cursor:\nEscolha quando o cursor desaparecerá:\nNunca: Você sempre verá o mouse.\nParado: Defina um tempo para ele desaparecer após ficar inativo.\nSempre: Você nunca verá o mouse. + + + + idleTimeoutGroupBox + Defina um tempo em segundos para o mouse desaparecer após ficar inativo. + + + + backButtonBehaviorGroupBox + Comportamento do botão Voltar:\nDefine o botão Voltar do controle para emular o toque na posição especificada no touchpad do PS4. + + + + Never + Nunca + + + + Idle + Parado + + + + Always + Sempre + + + + Touchpad Left + Touchpad Esquerdo + + + + Touchpad Right + Touchpad Direito + + + + Touchpad Center + Touchpad Centro + + + + None + Nenhum + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Ativar GPU NULA:\nDesativa a renderização do jogo para fins de depuração técnica, como se não houvesse nenhuma placa gráfica. - - dumpPM4CheckBox - Ativar Dumping de PM4:\nArmazena os dados de instrução bruta da GPU em uma pasta enquanto o emulador os processa, para fins de depuração técnica. Recomendado deixar desativado. + + gameFoldersBox + Pastas dos jogos:\nA lista de pastas para verificar se há jogos instalados. + + + + addFolderButton + Adicionar:\nAdicione uma pasta à lista. + + + + removeFolderButton + Remover:\nRemove uma pasta da lista. @@ -1121,6 +1339,11 @@ Path Diretório + + + Play Time + Tempo Jogado + CheckUpdate diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index a5c3783bb..603cd3a24 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -52,6 +52,19 @@ Loading... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Choose directory + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Copy All + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Install PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Enable Fullscreen + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Activați Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Log Filter + + + Input + Introducere + + + + Cursor + Cursor + + + + Hide Cursor + Ascunde cursorul + + + + Hide Cursor Idle Timeout + Timeout pentru ascunderea cursorului inactiv + + + + Controller + Controler + + + + Back Button Behavior + Comportament buton înapoi + Graphics @@ -475,9 +588,24 @@ Enable NULL GPU - - Enable PM4 Dumping - Enable PM4 Dumping + + Paths + Trasee + + + + Game Folders + Dosare de joc + + + + Add... + Adaugă... + + + + Remove + Eliminare @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Patches descărcate cu succes! Toate Patches disponibile pentru toate jocurile au fost descărcate; nu este nevoie să le descarci individual pentru fiecare joc, așa cum se întâmplă cu Cheats. Dacă patch-ul nu apare, este posibil să nu existe pentru seria și versiunea specifică a jocului. Poate fi necesar să actualizați jocul. + Patches descărcate cu succes! Toate Patches disponibile pentru toate jocurile au fost descărcate; nu este nevoie să le descarci individual pentru fiecare joc, așa cum se întâmplă cu Cheats. Dacă patch-ul nu apare, este posibil să nu existe pentru seria și versiunea specifică a jocului. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Nu s-a reușit obținerea paginii HTML. + + + The game is in version: %1 + Jocul este în versiunea: %1 + + + + The downloaded patch only works on version: %1 + Patch-ul descărcat funcționează doar pe versiunea: %1 + + + + You may need to update your game. + Este posibil să trebuiască să actualizezi jocul tău. + + + + Incompatibility Notice + Avertizare de incompatibilitate + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Activează modul pe ecran complet:\nPune automat fereastra jocului în modul pe ecran complet.\nAceasta poate fi dezactivată apăsând tasta F11. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox Este PS4 Pro:\nFace ca emulatorul să se comporte ca un PS4 PRO, ceea ce poate activa funcții speciale în jocurile care o suportă. + + + discordRPCCheckbox + Activați Discord Rich Presence:\nAfișează pictograma emulatorului și informații relevante pe profilul dumneavoastră Discord. + userName @@ -1016,7 +1174,7 @@ logFilter - Filtrul jurnalului: Filtrează jurnalul pentru a imprima doar informații specifice. Exemple: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveluri: Trace, Debug, Info, Warning, Error, Critical - în această ordine, un nivel specific reduce toate nivelurile anterioare din listă și înregistrează toate nivelurile ulterioare. + Filtrul jurnalului:\nFiltrează jurnalul pentru a imprima doar informații specifice.\nExemple: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveluri: Trace, Debug, Info, Warning, Error, Critical - în această ordine, un nivel specific reduce toate nivelurile anterioare din listă și înregistrează toate nivelurile ulterioare. @@ -1028,6 +1186,56 @@ GUIgroupBox Redă muzica titlului:\nDacă un joc o suportă, activează redarea muzicii speciale când selectezi jocul în GUI. + + + hideCursorGroupBox + Ascunde cursorul:\nAlegeți când va dispărea cursorul:\nNiciodată: Vei vedea întotdeauna mouse-ul.\nInactiv: Setează un timp pentru a dispărea după inactivitate.\nÎntotdeauna: nu vei vedea niciodată mouse-ul. + + + + idleTimeoutGroupBox + Setați un timp pentru ca mouse-ul să dispară după ce a fost inactiv. + + + + backButtonBehaviorGroupBox + Comportamentul butonului înapoi:\nSetează butonul înapoi al controlerului să imite atingerea poziției specificate pe touchpad-ul PS4. + + + + Never + Niciodată + + + + Idle + Inactiv + + + + Always + Întotdeauna + + + + Touchpad Left + Touchpad Stânga + + + + Touchpad Right + Touchpad Dreapta + + + + Touchpad Center + Centru Touchpad + + + + None + Niciunul + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Activează GPU Null:\nÎn scopuri de depanare tehnică, dezactivează redarea jocului ca și cum nu ar exista o placă grafică. - - dumpPM4CheckBox - Activează salvarea PM4:\nÎn scopuri de depanare tehnică, salvează datele brute ale instrucțiunilor GPU într-un folder pe măsură ce emulatorul le procesează. + + gameFoldersBox + Folderele jocurilor:\nLista folderelor pentru a verifica jocurile instalate. + + + + addFolderButton + Adăugați:\nAdăugați un folder la listă. + + + + removeFolderButton + Eliminați:\nÎndepărtați un folder din listă. @@ -1121,6 +1339,11 @@ Path Drum + + + Play Time + Timp de Joacă + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Actualizare disponibilă - + Update Channel Canal de Actualizare diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 13cc49f20..4c58786c4 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -52,6 +52,19 @@ Загрузка... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Выберите папку + + + + Select which directory you want to install to. + Выберите папку, в которую вы хотите установить. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Копировать все + + + Delete... + Удаление... + + + + Delete Game + Удалить игру + + + + Delete Update + Удалить обновление + + + + Delete DLC + Удалить DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Установить PKG + + + Game + Игры + + + + requiresEnableSeparateUpdateFolder_MSG + Эта функция требует включения настройки 'Отдельная папка обновлений'. Если вы хотите использовать эту функцию, пожалуйста включите её. + + + + This game has no update to delete! + У этой игры нет обновлений для удаления! + + + + Update + Обновления + + + + This game has no DLC to delete! + У этой игры нет DLC для удаления! + + + + DLC + DLC + + + + Delete %1 + Удалить %1 + + + + Are you sure you want to delete %1's %2 directory? + Вы уверены, что хотите удалить папку %2 %1? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Полноэкранный режим + + + Enable Separate Update Folder + Отдельная папка обновлений + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Режим PS4 Pro + + + Enable Discord Rich Presence + Включить Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Фильтр логов + + + Input + Ввод + + + + Cursor + Курсор мыши + + + + Hide Cursor + Скрывать курсор + + + + Hide Cursor Idle Timeout + Тайм-аут скрытия курсора при бездействии + + + + Controller + Контроллер + + + + Back Button Behavior + Поведение кнопки назад + Graphics @@ -475,9 +588,24 @@ Включить NULL GPU - - Enable PM4 Dumping - Включить дамп PM4 + + Paths + Пути + + + + Game Folders + Игровые папки + + + + Add... + Добавить... + + + + Remove + Удалить @@ -512,7 +640,7 @@ Check for Updates at Startup - Проверка обновлений при запуске + Проверка при запуске @@ -527,12 +655,12 @@ GUI Settings - Настройки интерфейса + Интерфейс Play title music - Воспроизведение заглавной музыки + Играть заглавную музыку @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Патчи успешно скачаны! Все доступные патчи для всех игр были скачаны, нет необходимости скачивать их по отдельности для каждой игры, как это происходит с читами. Если патч не появляется, возможно, его не существует для конкретного серийного номера и версии игры. Возможно, потребуется обновить игру. + Патчи успешно скачаны! Все доступные патчи для всех игр были скачаны, нет необходимости скачивать их по отдельности для каждой игры, как это происходит с читами. Если патч не появляется, возможно, его не существует для конкретного серийного номера и версии игры. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Не удалось получить HTML-страницу. + + + The game is in version: %1 + Игра в версии: %1 + + + + The downloaded patch only works on version: %1 + Скачанный патч работает только на версии: %1 + + + + You may need to update your game. + Вам может понадобиться обновить игру. + + + + Incompatibility Notice + Уведомление о несовместимости + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Полноэкранный режим:\nАвтоматически переводит игровое окно в полноэкранный режим.\nВы можете отключить это, нажав клавишу F11. + + + separateUpdatesCheckBox + Отдельная папка обновлений:\nПозволяет устанавливать обновления игры в отдельную папку для удобства. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox Режим PS4 Pro:\nЗаставляет эмулятор работать как PS4 Pro, что может включить специальные функции в играх, поддерживающих это. + + + discordRPCCheckbox + Включить Discord Rich Presence:\nОтображает значок эмулятора и соответствующую информацию в вашем профиле Discord. + userName @@ -1016,7 +1174,7 @@ logFilter - Фильтр логов: Фильтрует логи, чтобы показывать только определенную информацию. Примеры: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Уровни: Trace, Debug, Info, Warning, Error, Critical - в этом порядке, конкретный уровень глушит все предыдущие уровни в списке и показывает все последующие уровни. + Фильтр логов:\nФильтрует логи, чтобы показывать только определенную информацию.\nПримеры: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Уровни: Trace, Debug, Info, Warning, Error, Critical - в этом порядке, конкретный уровень глушит все предыдущие уровни в списке и показывает все последующие уровни. @@ -1026,7 +1184,57 @@ GUIgroupBox - Воспроизведение заглавной музыки:\nЕсли игра это поддерживает, включает воспроизведение специальной музыки при выборе игры в интерфейсе. + Играть заглавную музыку:\nВключает воспроизведение специальной музыки при выборе игры в списке, если она это поддерживает. + + + + hideCursorGroupBox + Скрывать курсор:\nВыберите, когда курсор исчезнет:\nНикогда: Вы всегда будете видеть мышь.\nПри бездействии: Установите время, через которое курсор исчезнет при бездействии.\nВсегда: Вы никогда не будете видеть мышь. + + + + idleTimeoutGroupBox + Установите время, через которое курсор исчезнет при бездействии. + + + + backButtonBehaviorGroupBox + Поведение кнопки «Назад»:\nНастраивает кнопку «Назад» контроллера на эмуляцию нажатия на указанную область на сенсорной панели контроллера PS4. + + + + Never + Никогда + + + + Idle + При бездействии + + + + Always + Всегда + + + + Touchpad Left + Тачпад слева + + + + Touchpad Right + Тачпад справа + + + + Touchpad Center + Центр тачпада + + + + None + Нет @@ -1054,9 +1262,19 @@ Включить NULL GPU:\nДля технической отладки отключает рендеринг игры так, как будто графической карты нет. - - dumpPM4CheckBox - Включить дамп PM4:\nДля технической отладки сохраняет необработанные данные инструкций GPU в папку, пока эмулятор их обрабатывает. + + gameFoldersBox + Игровые папки:\nСписок папок для проверки установленных игр. + + + + addFolderButton + Добавить:\nДобавить папку в список. + + + + removeFolderButton + Удалить:\nУдалить папку из списка. @@ -1121,6 +1339,11 @@ Path Путь + + + Play Time + Времени в игре + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Доступно обновление - + Update Channel Канал обновления @@ -1197,7 +1420,7 @@ Check for Updates at Startup - Проверка обновлений при запуске + Проверка при запуске diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts index d9f1d3859..637a754f7 100644 --- a/src/qt_gui/translations/sq.ts +++ b/src/qt_gui/translations/sq.ts @@ -52,6 +52,19 @@ Duke ngarkuar... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Përzgjidh dosjen + + + + Select which directory you want to install to. + Përzgjidh në cilën dosje do që të instalosh. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Kopjo të Gjitha + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Instalo PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Aktivizo Ekranin e plotë + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,15 +492,20 @@ Is PS4 Pro Mënyra PS4 Pro + + + Enable Discord Rich Presence + Aktivizo Discord Rich Presence + Username - Nofka + Përdoruesi Logger - Regjistruesi i të dhënave + Regjistruesi i ditarit @@ -434,6 +517,36 @@ Log Filter Filtri i Ditarit + + + Input + Hyrja + + + + Cursor + Kursori + + + + Hide Cursor + Fshih kursorin + + + + Hide Cursor Idle Timeout + Koha për fshehjen e kursorit joaktiv + + + + Controller + Dorezë + + + + Back Button Behavior + Sjellja e butonit mbrapa + Graphics @@ -475,9 +588,24 @@ Aktivizo GPU-në NULL - - Enable PM4 Dumping - Aktivizo Zbrazjen PM4 + + Paths + Shtigjet + + + + Game Folders + Dosjet e lojës + + + + Add... + Shto... + + + + Remove + Hiq @@ -517,7 +645,7 @@ Update Channel - Kanali i Përditësimit + Kanali i përditësimit @@ -527,7 +655,7 @@ GUI Settings - Parametrat e GUI + Cilësimet e GUI @@ -537,7 +665,7 @@ Volume - Volumi + Vëllimi i zërit @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Arnat u shkarkuan me sukses! Të gjitha arnat e ofruara për të gjitha lojërat janë shkarkuar, nuk ka nevojë t'i shkarkosh ato individualisht për secilën lojë siç ndodh me Mashtrimet. Nëse patch-i nuk shfaqet, mund të mos ekzistojë për numrin e serisë dhe versionin specifik të lojës. Mund të jetë e nevojshme të përditësosh lojën. + Arnat u shkarkuan me sukses! Të gjitha arnat e ofruara për të gjitha lojërat janë shkarkuar, nuk ka nevojë t'i shkarkosh ato individualisht për secilën lojë siç ndodh me Mashtrimet. Nëse patch-i nuk shfaqet, mund të mos ekzistojë për numrin e serisë dhe versionin specifik të lojës. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Gjetja e faqes HTML dështoi. + + + The game is in version: %1 + Loja është në versionin: %1 + + + + The downloaded patch only works on version: %1 + Patch-i i shkarkuar funksionon vetëm në versionin: %1 + + + + You may need to update your game. + Ju mund të duhet të përditësoni lojën tuaj. + + + + Incompatibility Notice + Njoftim për papajtueshmëri + Failed to open file: @@ -976,12 +1124,12 @@ Point your mouse at an option to display its description. - Hidhni mouse-in mbi një opsion për të shfaqur përshkrimin e tij. + Vendos miun mbi një rregullim për të shfaqur përshkrimin e tij. consoleLanguageGroupBox - Gjuha e konsolës:\nPërcakton gjuhën që përdor loja PS4.\nRrekomandohet të vendosni këtë në një gjuhë që loja mbështet, e cila do të ndryshojë sipas rajonit. + Gjuha e konsolës:\nPërcakton gjuhën që përdor loja PS4.\nKëshillohet të caktosh një gjuhë që loja mbështet, e cila do të ndryshojë sipas rajonit. @@ -991,17 +1139,27 @@ fullscreenCheckBox - Aktivizo ekranin e plotë:\nAutomatikisht vendos dritaren e lojës në modalitetin e ekranit të plotë.\nKjo mund të aktivizohet duke shtypur çelësin F11. + Aktivizo ekranin e plotë:\nVendos automatikisht dritaren e lojës në mënyrën e ekranit të plotë.\nKjo mund të aktivizohet duke shtypur tastin F11. + + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. showSplashCheckBox - Shfaq ekranin e ngarkesës:\nShfaq ekranin e ngarkesës së lojës (një imazh special) gjatë fillimit të lojës. + Shfaq ekranin e ngarkesës:\nShfaq ekranin e ngarkesës së lojës (një pamje e veçantë) gjatë fillimit të lojës. ps4proCheckBox - Është PS4 Pro:\nBën që emulatori të veprojë si një PS4 PRO, i cili mund të aktivizojë karakteristika speciale në lojrat që e mbështesin atë. + Është PS4 Pro:\nBën që emulatori të veprojë si një PS4 PRO, gjë që mund të aktivizojë veçori të veçanta në lojrat që e mbështesin. + + + + discordRPCCheckbox + Aktivizo Discord Rich Presence:\nShfaq ikonën e emulatorit dhe informacionin përkatës në profilin tënd në Discord. @@ -1011,72 +1169,132 @@ logTypeGroupBox - Tipi i logut:\nPërcakton nëse të sinkronizoni daljen e dritares së logut për performancën. Mund të ketë efekte të këqija në emulim. + Lloji i ditarit:\nPërcakton nëse të sinkronizohet dalja e dritares së ditarit për performancë. Mund të ketë efekte të këqija në emulim. logFilter - Filtri i logut: Filtron logun për të printuar vetëm informacione specifike. Shembuj: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Nivelet: Trace, Debug, Info, Warning, Error, Critical - në këtë rend, një nivel specifik hesht të gjitha nivelet përpara në listë dhe regjistron çdo nivel pas saj. + Filtri i ditarit:\nFiltron ditarin për të shfaqur vetëm informacione specifike.\nShembuj: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Nivelet: Trace, Debug, Info, Warning, Error, Critical - në këtë rend, një nivel specifik hesht të gjitha nivelet përpara në listë dhe regjistron çdo nivel pas atij. updaterGroupBox - Aktualizimi:\nRelease: Versionet zyrtare të lëshuara çdo muaj që mund të jenë shumë të vjetra, por janë më të besueshme dhe të testuara.\nNightly: Versionet e zhvillimit që kanë të gjitha veçoritë dhe rregullimet më të fundit, por mund të përmbajnë gabime dhe janë më pak të qëndrueshme. + Përditësimi:\nRelease: Versionet zyrtare të lëshuara çdo muaj që mund të jenë shumë të vjetra, por janë më të besueshme dhe të provuara.\nNightly: Versionet e zhvillimit që kanë të gjitha veçoritë dhe rregullimet më të fundit, por mund të përmbajnë gabime dhe janë më pak të qëndrueshme. GUIgroupBox - Lojë muzikë titulli:\nNëse një lojë e mbështet, aktivizoja luajtjen e muzikës speciale kur të zgjidhni lojën në GUI. + Luaj muzikën e titullit:\nNëse një lojë e mbështet, aktivizohet luajtja e muzikës të veçantë kur të zgjidhësh lojën në GUI. + + + + hideCursorGroupBox + Fsheh kursorin:\nZgjidh kur do të fshihet kursori:\nKurrë: Do ta shohësh gjithmonë miun.\nInaktiv: Vendos një kohë për ta fshehur pas mosveprimit.\nGjithmonë: nuk do ta shohësh kurrë miun. + + + + idleTimeoutGroupBox + Koha për fshehjen e kursorit joaktiv:\nKohëzgjatja (në sekonda) pas së cilës kursori që nuk ka qënë në veprim fshihet. + + + + backButtonBehaviorGroupBox + Sjellja e butonit mbrapa:\nLejon të përcaktohet se në cilën pjesë të tastierës prekëse do të imitojë një prekje butoni mprapa. + + + + Never + Kurrë + + + + Idle + Joaktiv + + + + Always + Gjithmonë + + + + Touchpad Left + Tastiera prekëse majtas + + + + Touchpad Right + Tastiera prekëse djathtas + + + + Touchpad Center + Tastiera prekëse në qendër + + + + None + Asnjë graphicsAdapterGroupBox - Dispositivi grafik:\nNë sistemet me GPU të shumëfishta, zgjidhni GPU-në që do të përdorë emulatori nga lista e rënies,\nor zgjidhni "Auto Select" për ta përcaktuar automatikisht. + Pajisja grafike:\nNë sistemet me GPU të shumëfishta, zgjidh GPU-në që do të përdorë emulatori nga lista rënëse,\nose zgjidh "Auto Select" për ta përcaktuar automatikisht. resolutionLayout - Gjerësia/ Lartësia:\nPërcakton madhësinë e dritares së emulatorit në nisje, e cila mund të rregullohet gjatë lojës.\nKjo është ndryshe nga rezolucioni në lojë. + Gjerësia/Lartësia:\nPërcakton madhësinë e dritares së emulatorit në nisje, e cila mund të rregullohet gjatë lojës.\nKjo është ndryshe nga rezolucioni në lojë. heightDivider - Pjesëtari Vblank:\nShpejtësia e kuadrit me të cilën refreshohet emulatori është shumëzuar me këtë numër. Ndryshimi i këtij mund të ketë efekte të këqija, si rritja e shpejtësisë së lojës ose shkatërrimi i funksionalitetit kritik të lojës që nuk e pret këtë të ndryshojë! + Ndarësi Vblank:\nFrekuenca pamore me të cilën rifreskohet emulatori shumëzohet me këtë numër. Ndryshimi i këtij mund të ketë efekte të këqija, si rritja e shpejtësisë së lojës ose prishja e punimit thelbësor të lojës që nuk e pret këtë ndryshim! dumpShadersCheckBox - Aktivizo dump-in e shaders:\nPër qëllime të debugimit teknik, ruan shaders e lojës në një folder ndërsa ato renditen. + Aktivizo zbrazjen e shaders-ave:\nPër qëllime të korrigjimit teknik, ruan shaders-at e lojës në një dosje ndërsa ato pasqyrohen. nullGpuCheckBox - Aktivizo GPU Null:\nPër qëllime të debugimit teknik, deaktivizon renditjen e lojës sikur nuk do të kishte një kartë grafike. + Aktivizo GPU-në Null:\nPër qëllime të korrigjimit teknik, çaktivizon pasqyrimin e lojës sikur nuk ka një kartë grafike. - - dumpPM4CheckBox - Aktivizo dump-in e PM4:\nPër qëllime të debugimit teknik, ruan të dhënat e instruksioneve të GPU-së në një folder ndërsa emulatori i përpunon ato. + + gameFoldersBox + Dosjet e lojërave:\nLista e dosjeve për të kontrolluar lojërat e instaluara. + + + + addFolderButton + Shto:\nShto një dosje në listë. + + + + removeFolderButton + Hiq:\nHiq një dosje nga lista. debugDump - Aktivizo dump-in e debugimit:\nRuani simbolet e importit dhe eksportit dhe informacionin e titullit të skedarit për aplikacionin aktual PS4 që po punon në një katalog. + Aktivizo zbrazjen për korrigjim:\nRuan simbolet e importit dhe eksportit dhe informacionin e kreut të skedarit për aplikacionin PS4 që po ekzekutohet në një dosje. vkValidationCheckBox - Aktivizo stratet e validimit Vulkan:\nAktivizon një sistem që validon gjendjen e renderizuesit Vulkan dhe regjistron informacionin në lidhje me gjendjen e tij të brendshme. Kjo do të ulet performancën dhe ndoshta do të ndryshojë sjelljen e emulimit. + Aktivizo shtresat e vlefshmërisë Vulkan:\nAktivizon një sistem që vërteton gjendjen e pasqyruesit Vulkan dhe regjistron informacionin në lidhje me gjendjen e tij të brendshme. Kjo do të ul performancën dhe ndoshta do të ndryshojë sjelljen e emulimit. vkSyncValidationCheckBox - Aktivizo validimin e sinkronizimit Vulkan:\nAktivizon një sistem që validon kohën e detyrave të renderizimit Vulkan. Kjo do të ulet performancën dhe ndoshta do të ndryshojë sjelljen e emulimit. + Aktivizo vërtetimin e sinkronizimit Vulkan:\nAktivizon një sistem që vërteton kohën e detyrave të pasqyrimit Vulkan. Kjo do të ul performancën dhe ndoshta do të ndryshojë sjelljen e emulimit. rdocCheckBox - Aktivizo debugimin RenderDoc:\nNëse aktivizohet, emulatori do të ofrojë pajtueshmëri me Renderdoc për të lejuar kapjen dhe analizën e kornizës aktuale të renderizuar. + Aktivizo korrigjimin RenderDoc:\nNëse aktivizohet, emulatori do të ofrojë pajtueshmëri me Renderdoc për të lejuar kapjen dhe analizën e pamjes të pasqyruar në moment. @@ -1121,6 +1339,11 @@ Path Shtegu + + + Play Time + Koha e luajtjes + CheckUpdate @@ -1169,10 +1392,10 @@ Update Available Ofrohet një përditësim - + Update Channel - Kanali i Përditësimit + Kanali i përditësimit diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 32e5c4bd6..6c4913603 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -52,6 +52,19 @@ Yükleniyor... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Klasörü Seç + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Tümünü Kopyala + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG PKG Yükle + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Tam Ekranı Etkinleştir + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro PS4 Pro mu + + + Enable Discord Rich Presence + Discord Rich Presence'i etkinleştir + Username @@ -434,6 +517,36 @@ Log Filter Kayıt Filtresi + + + Input + Girdi + + + + Cursor + İmleç + + + + Hide Cursor + İmleci gizle + + + + Hide Cursor Idle Timeout + İmleç için hareketsizlik zaman aşımı + + + + Controller + Kontrolcü + + + + Back Button Behavior + Geri Dön Butonu Davranışı + Graphics @@ -475,9 +588,24 @@ NULL GPU'yu Etkinleştir - - Enable PM4 Dumping - PM4 Kaydını Etkinleştir + + Paths + Yollar + + + + Game Folders + Oyun Klasörleri + + + + Add... + Ekle... + + + + Remove + Kaldır @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Yamalar başarıyla indirildi! Tüm oyunlar için mevcut tüm yamalar indirildi, her oyun için ayrı ayrı indirme yapmanız gerekmez, hilelerle olduğu gibi. Yamanın görünmemesi durumunda, belirli seri numarası ve oyun sürümü için mevcut olmayabilir. Oyunu güncellemeniz gerekebilir. + Yamalar başarıyla indirildi! Tüm oyunlar için mevcut tüm yamalar indirildi, her oyun için ayrı ayrı indirme yapmanız gerekmez, hilelerle olduğu gibi. Yamanın görünmemesi durumunda, belirli seri numarası ve oyun sürümü için mevcut olmayabilir. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. HTML sayfası alınamadı. + + + The game is in version: %1 + Oyun sürümde: %1 + + + + The downloaded patch only works on version: %1 + İndirilen yamanın sadece sürümde çalışıyor: %1 + + + + You may need to update your game. + Oyunuzu güncellemeniz gerekebilir. + + + + Incompatibility Notice + Uyumsuzluk Bildirimi + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Tam Ekranı Etkinleştir:\nOyun penceresini otomatik olarak tam ekran moduna alır.\nBu, F11 tuşuna basarak geçiş yapılabilir. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox PS4 Pro Mu:\nEmülatörü bir PS4 PRO gibi çalıştırır; bu, bunu destekleyen oyunlarda özel özellikleri etkinleştirebilir. + + + discordRPCCheckbox + Discord Rich Presence'i etkinleştir:\nEmülatör simgesini ve Discord profilinizdeki ilgili bilgileri gösterir. + userName @@ -1016,7 +1174,7 @@ logFilter - Günlük Filtre: Sadece belirli bilgileri yazdırmak için günlüğü filtreler. Örnekler: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Düzeyler: Trace, Debug, Info, Warning, Error, Critical - bu sırada, belirli bir seviye listede önceki tüm seviyeleri susturur ve sonraki tüm seviyeleri kaydeder. + Günlük Filtre:\nSadece belirli bilgileri yazdırmak için günlüğü filtreler.\nÖrnekler: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Düzeyler: Trace, Debug, Info, Warning, Error, Critical - bu sırada, belirli bir seviye listede önceki tüm seviyeleri susturur ve sonraki tüm seviyeleri kaydeder. @@ -1028,6 +1186,56 @@ GUIgroupBox Başlık Müziklerini Çal:\nEğer bir oyun bunu destekliyorsa, GUI'de oyunu seçtiğinizde özel müziklerin çalmasını etkinleştirir. + + + hideCursorGroupBox + İmleci gizle:\nİmlecin ne zaman kaybolacağını seçin:\nAsla: Fareyi her zaman göreceksiniz.\nPasif: Hareketsiz kaldıktan sonra kaybolması için bir süre belirleyin.\nHer zaman: fareyi asla göremeyeceksiniz. + + + + idleTimeoutGroupBox + Hareket etmeden sonra imlecin kaybolacağı süreyi ayarlayın. + + + + backButtonBehaviorGroupBox + Geri düğmesi davranışı:\nKontrol cihazındaki geri düğmesini, PS4'ün dokunmatik panelindeki belirlenen noktaya dokunmak için ayarlar. + + + + Never + Asla + + + + Idle + Boşta + + + + Always + Her zaman + + + + Touchpad Left + Dokunmatik Yüzey Sol + + + + Touchpad Right + Dokunmatik Yüzey Sağ + + + + Touchpad Center + Dokunmatik Yüzey Orta + + + + None + Yok + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Null GPU'yu Etkinleştir:\nTeknik hata ayıklama amacıyla, oyunun render edilmesini grafik kartı yokmuş gibi devre dışı bırakır. - - dumpPM4CheckBox - PM4 Dışa Aktarmayı Etkinleştir:\nTeknik hata ayıklama amacıyla, emülatör bunları işlerken GPU komut verilerini bir klasöre kaydeder. + + gameFoldersBox + Oyun klasörleri:\nYüklenmiş oyunları kontrol etmek için klasörlerin listesi. + + + + addFolderButton + Ekle:\nListeye bir klasör ekle. + + + + removeFolderButton + Kaldır:\nListeden bir klasörü kaldır. @@ -1121,6 +1339,11 @@ Path Yol + + + Play Time + Oynama Süresi + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Güncelleme Mevcut - + Update Channel Güncelleme Kanalı diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index 8f377f2a7..5fca6b6b5 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -52,6 +52,19 @@ Loading... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Choose directory + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Copy All + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Install PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Enable Fullscreen + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Bật Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Log Filter + + + Input + Đầu vào + + + + Cursor + Con trỏ + + + + Hide Cursor + Ẩn con trỏ + + + + Hide Cursor Idle Timeout + Thời gian chờ ẩn con trỏ + + + + Controller + Điều khiển + + + + Back Button Behavior + Hành vi nút quay lại + Graphics @@ -475,9 +588,24 @@ Enable NULL GPU - - Enable PM4 Dumping - Enable PM4 Dumping + + Paths + Đường dẫn + + + + Game Folders + Thư mục trò chơi + + + + Add... + Thêm... + + + + Remove + Xóa @@ -898,7 +1026,7 @@ DownloadComplete_MSG - Bản vá đã tải xuống thành công! Tất cả các bản vá có sẵn cho tất cả các trò chơi đã được tải xuống, không cần tải xuống riêng lẻ cho mỗi trò chơi như trong Cheat. Nếu bản vá không xuất hiện, có thể là nó không tồn tại cho số seri và phiên bản cụ thể của trò chơi. Có thể bạn cần phải cập nhật trò chơi. + Bản vá đã tải xuống thành công! Tất cả các bản vá có sẵn cho tất cả các trò chơi đã được tải xuống, không cần tải xuống riêng lẻ cho mỗi trò chơi như trong Cheat. Nếu bản vá không xuất hiện, có thể là nó không tồn tại cho số seri và phiên bản cụ thể của trò chơi. @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. Không thể lấy trang HTML. + + + The game is in version: %1 + Trò chơi đang ở phiên bản: %1 + + + + The downloaded patch only works on version: %1 + Patch đã tải về chỉ hoạt động trên phiên bản: %1 + + + + You may need to update your game. + Bạn có thể cần cập nhật trò chơi của mình. + + + + Incompatibility Notice + Thông báo không tương thích + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox Bật chế độ toàn màn hình:\nTự động đặt cửa sổ trò chơi ở chế độ toàn màn hình.\nĐiều này có thể bị vô hiệu hóa bằng cách nhấn phím F11. + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox Là PS4 Pro:\nKhiến trình giả lập hoạt động như một PS4 PRO, điều này có thể kích hoạt các tính năng đặc biệt trong các trò chơi hỗ trợ điều này. + + + discordRPCCheckbox + Bật Discord Rich Presence:\nHiển thị biểu tượng trình giả lập và thông tin liên quan trên hồ sơ Discord của bạn. + userName @@ -1016,7 +1174,7 @@ logFilter - Bộ lọc nhật ký: Lọc nhật ký để in chỉ thông tin cụ thể. Ví dụ: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Các mức: Trace, Debug, Info, Warning, Error, Critical - theo thứ tự này, một mức cụ thể làm tắt tất cả các mức trước trong danh sách và ghi lại tất cả các mức sau đó. + Bộ lọc nhật ký:\nLọc nhật ký để in chỉ thông tin cụ thể.\nVí dụ: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Các mức: Trace, Debug, Info, Warning, Error, Critical - theo thứ tự này, một mức cụ thể làm tắt tất cả các mức trước trong danh sách và ghi lại tất cả các mức sau đó. @@ -1028,6 +1186,56 @@ GUIgroupBox Phát nhạc tiêu đề trò chơi:\nNếu một trò chơi hỗ trợ điều này, hãy kích hoạt phát nhạc đặc biệt khi bạn chọn trò chơi trong GUI. + + + hideCursorGroupBox + Ẩn con trỏ:\nChọn khi nào con trỏ sẽ biến mất:\nKhông bao giờ: Bạn sẽ luôn thấy chuột.\nKhông hoạt động: Đặt một khoảng thời gian để nó biến mất sau khi không hoạt động.\nLuôn luôn: bạn sẽ không bao giờ thấy chuột. + + + + idleTimeoutGroupBox + Đặt thời gian để chuột biến mất sau khi không hoạt động. + + + + backButtonBehaviorGroupBox + Hành vi nút quay lại:\nĐặt nút quay lại của tay cầm để mô phỏng việc chạm vào vị trí đã chỉ định trên touchpad của PS4. + + + + Never + Không bao giờ + + + + Idle + Nhàn rỗi + + + + Always + Luôn luôn + + + + Touchpad Left + Touchpad Trái + + + + Touchpad Right + Touchpad Phải + + + + Touchpad Center + Giữa Touchpad + + + + None + Không có + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ Bật GPU Null:\nĐể mục đích gỡ lỗi kỹ thuật, vô hiệu hóa việc kết xuất trò chơi như thể không có card đồ họa. - - dumpPM4CheckBox - Bật xuất PM4:\nĐể mục đích gỡ lỗi kỹ thuật, lưu dữ liệu lệnh GPU vào một thư mục khi trình giả lập xử lý chúng. + + gameFoldersBox + Thư mục trò chơi:\nDanh sách các thư mục để kiểm tra các trò chơi đã cài đặt. + + + + addFolderButton + Thêm:\nThêm một thư mục vào danh sách. + + + + removeFolderButton + Xóa:\nXóa một thư mục khỏi danh sách. @@ -1121,6 +1339,11 @@ Path Đường dẫn + + + Play Time + Thời gian chơi + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available Có bản cập nhật - + Update Channel Kênh Cập Nhật diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 0eae5a5bd..89e45c7ba 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -52,6 +52,19 @@ 加载中... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - 选择文件目录 + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All 复制全部 + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG 安装 PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen 启用全屏 + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro 是否是 PS4 Pro + + + Enable Discord Rich Presence + 启用 Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter 日志过滤 + + + Input + 输入 + + + + Cursor + 光标 + + + + Hide Cursor + 隐藏光标 + + + + Hide Cursor Idle Timeout + 光标空闲超时隐藏 + + + + Controller + 控制器 + + + + Back Button Behavior + 返回按钮行为 + Graphics @@ -475,9 +588,24 @@ 启用 NULL GPU - - Enable PM4 Dumping - 启用 PM4 转储 + + Paths + 路径 + + + + Game Folders + 游戏文件夹 + + + + Add... + 添加... + + + + Remove + 删除 @@ -898,7 +1026,7 @@ DownloadComplete_MSG - 补丁下载成功!所有可用的补丁已下载完成,无需像作弊码那样单独下载每个游戏的补丁。如果补丁没有出现,可能是该补丁不存在于特定的序列号和游戏版本中。可能需要更新游戏。 + 补丁下载成功!所有可用的补丁已下载完成,无需像作弊码那样单独下载每个游戏的补丁。如果补丁没有出现,可能是该补丁不存在于特定的序列号和游戏版本中。 @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. 无法获取 HTML 页面。 + + + The game is in version: %1 + 游戏版本: %1 + + + + The downloaded patch only works on version: %1 + 下载的补丁仅适用于版本: %1 + + + + You may need to update your game. + 您可能需要更新您的游戏。 + + + + Incompatibility Notice + 不兼容通知 + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox 启用全屏模式:\n自动将游戏窗口设置为全屏模式。\n您可以按 F11 键禁用此选项。 + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox 这是 PS4 Pro:\n使模拟器作为 PS4 PRO 运行,可以在支持的游戏中激活特殊功能。 + + + discordRPCCheckbox + 启用 Discord Rich Presence:\n在您的 Discord 个人资料上显示仿真器图标和相关信息。 + userName @@ -1016,7 +1174,7 @@ logFilter - 日志过滤器: 过滤日志,仅打印特定信息。例如:"Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" 级别: Trace, Debug, Info, Warning, Error, Critical - 按此顺序,特定级别将静默列表中所有先前的级别,并记录所有后续级别。 + 日志过滤器:\n过滤日志,仅打印特定信息。\n例如:"Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" 级别: Trace, Debug, Info, Warning, Error, Critical - 按此顺序,特定级别将静默列表中所有先前的级别,并记录所有后续级别。 @@ -1028,6 +1186,56 @@ GUIgroupBox 播放标题音乐:\n如果游戏支持,在图形界面选择游戏时启用播放特殊音乐。 + + + hideCursorGroupBox + 隐藏光标:\n选择光标何时消失:\n从不: 您将始终看到鼠标。\n空闲: 设置光标在空闲后消失的时间。\n始终: 您将永远看不到鼠标。 + + + + idleTimeoutGroupBox + 设置鼠标在空闲后消失的时间。 + + + + backButtonBehaviorGroupBox + 返回按钮行为:\n设置控制器的返回按钮以模拟在 PS4 触控板上指定位置的点击。 + + + + Never + 从不 + + + + Idle + 空闲 + + + + Always + 始终 + + + + Touchpad Left + 触控板左侧 + + + + Touchpad Right + 触控板右侧 + + + + Touchpad Center + 触控板中间 + + + + None + + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ 启用空 GPU:\n为了技术调试,将游戏渲染禁用,仿佛没有图形卡。 - - dumpPM4CheckBox - 启用 PM4 转储:\n为了技术调试,在模拟器处理时将原始 GPU 指令数据保存到文件夹中。 + + gameFoldersBox + 游戏文件夹:\n检查已安装游戏的文件夹列表。 + + + + addFolderButton + 添加:\n将文件夹添加到列表。 + + + + removeFolderButton + 移除:\n从列表中移除文件夹。 @@ -1121,6 +1339,11 @@ Path 路径 + + + Play Time + 游戏时间 + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available 可用更新 - + Update Channel 更新频道 diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index cead22ecf..84b32b7a5 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -52,6 +52,19 @@ Loading... + + InstallDirSelect + + + shadPS4 - Choose directory + shadPS4 - Choose directory + + + + Select which directory you want to install to. + Select which directory you want to install to. + + GameInstallDialog @@ -127,6 +140,26 @@ Copy All Copy All + + + Delete... + Delete... + + + + Delete Game + Delete Game + + + + Delete Update + Delete Update + + + + Delete DLC + Delete DLC + Shortcut creation @@ -152,6 +185,46 @@ Install PKG Install PKG + + + Game + Game + + + + requiresEnableSeparateUpdateFolder_MSG + This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + + + + This game has no update to delete! + This game has no update to delete! + + + + Update + Update + + + + This game has no DLC to delete! + This game has no DLC to delete! + + + + DLC + DLC + + + + Delete %1 + Delete %1 + + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + MainWindow @@ -404,6 +477,11 @@ Enable Fullscreen Enable Fullscreen + + + Enable Separate Update Folder + Enable Separate Update Folder + Show Splash @@ -414,6 +492,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + 啟用 Discord Rich Presence + Username @@ -434,6 +517,36 @@ Log Filter Log Filter + + + Input + 輸入 + + + + Cursor + 游標 + + + + Hide Cursor + 隱藏游標 + + + + Hide Cursor Idle Timeout + 游標空閒超時隱藏 + + + + Controller + 控制器 + + + + Back Button Behavior + 返回按鈕行為 + Graphics @@ -475,9 +588,24 @@ Enable NULL GPU - - Enable PM4 Dumping - Enable PM4 Dumping + + Paths + 路徑 + + + + Game Folders + 遊戲資料夾 + + + + Add... + 添加... + + + + Remove + 刪除 @@ -898,7 +1026,7 @@ DownloadComplete_MSG - 修補檔下載成功!所有遊戲的修補檔已下載完成,無需像作弊碼那樣為每個遊戲單獨下載。如果補丁未顯示,可能是該補丁不適用於特定的序號和遊戲版本。可能需要更新遊戲。 + 修補檔下載成功!所有遊戲的修補檔已下載完成,無需像作弊碼那樣為每個遊戲單獨下載。如果補丁未顯示,可能是該補丁不適用於特定的序號和遊戲版本。 @@ -910,6 +1038,26 @@ Failed to retrieve HTML page. 無法檢索 HTML 頁面。 + + + The game is in version: %1 + 遊戲版本: %1 + + + + The downloaded patch only works on version: %1 + 下載的補丁僅適用於版本: %1 + + + + You may need to update your game. + 您可能需要更新遊戲。 + + + + Incompatibility Notice + 不相容通知 + Failed to open file: @@ -993,6 +1141,11 @@ fullscreenCheckBox 啟用全螢幕:\n自動將遊戲視窗設置為全螢幕模式。\n可以按F11鍵進行切換。 + + + separateUpdatesCheckBox + Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + showSplashCheckBox @@ -1003,6 +1156,11 @@ ps4proCheckBox 為PS4 Pro:\n讓模擬器像PS4 PRO一樣運作,這可能啟用支持此功能的遊戲中的特殊功能。 + + + discordRPCCheckbox + 啟用 Discord Rich Presence:\n在您的 Discord 個人檔案上顯示模擬器圖標和相關信息。 + userName @@ -1016,7 +1174,7 @@ logFilter - 日誌過濾器: 過濾日誌以僅打印特定信息。範例:"Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" 等級: Trace, Debug, Info, Warning, Error, Critical - 以此順序,特定級別靜音所有前面的級別,並記錄其後的每個級別。 + 日誌過濾器:\n過濾日誌以僅打印特定信息。\n範例:"Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" 等級: Trace, Debug, Info, Warning, Error, Critical - 以此順序,特定級別靜音所有前面的級別,並記錄其後的每個級別。 @@ -1028,6 +1186,56 @@ GUIgroupBox 播放標題音樂:\n如果遊戲支持,啟用在GUI中選擇遊戲時播放特殊音樂。 + + + hideCursorGroupBox + 隱藏游標:\n選擇游標何時消失:\n從不: 您將始終看到滑鼠。\n閒置: 設定在閒置後消失的時間。\n始終: 您將永遠看不到滑鼠。 + + + + idleTimeoutGroupBox + 設定滑鼠在閒置後消失的時間。 + + + + backButtonBehaviorGroupBox + 返回按鈕行為:\n設定控制器的返回按鈕模擬在 PS4 觸控板上指定位置的觸碰。 + + + + Never + 從不 + + + + Idle + 閒置 + + + + Always + 始終 + + + + Touchpad Left + 觸控板左側 + + + + Touchpad Right + 觸控板右側 + + + + Touchpad Center + 觸控板中間 + + + + None + + graphicsAdapterGroupBox @@ -1054,9 +1262,19 @@ 啟用空GPU:\n為了技術調試,禁用遊戲渲染,彷彿沒有顯示卡。 - - dumpPM4CheckBox - 啟用PM4轉儲:\n為了技術調試,將原始GPU指令數據在模擬器處理時保存到文件夾中。 + + gameFoldersBox + 遊戲資料夾:\n檢查已安裝遊戲的資料夾列表。 + + + + addFolderButton + 添加:\n將資料夾添加到列表。 + + + + removeFolderButton + 移除:\n從列表中移除資料夾。 @@ -1121,6 +1339,11 @@ Path 路徑 + + + Play Time + 遊玩時間 + CheckUpdate @@ -1169,7 +1392,7 @@ Update Available 可用更新 - + Update Channel 更新頻道 diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 4a4020a42..ad7d1b4a6 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "common/assert.h" #include "common/config.h" @@ -20,6 +21,11 @@ namespace Frontend { +static Uint32 SDLCALL PollController(void* userdata, SDL_TimerID timer_id, Uint32 interval) { + auto* controller = reinterpret_cast(userdata); + return controller->Poll(); +} + WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_, std::string_view window_title) : width{width_}, height{height_}, controller{controller_} { @@ -119,6 +125,10 @@ void WindowSDL::waitEvent() { } } +void WindowSDL::initTimers() { + SDL_AddTimer(100, &PollController, controller); +} + void WindowSDL::onResize() { SDL_GetWindowSizeInPixels(window, &width, &height); ImGui::Core::OnResize(); @@ -145,6 +155,7 @@ void WindowSDL::onKeyPress(const SDL_Event* event) { Input::Axis axis = Input::Axis::AxisMax; int axisvalue = 0; int ax = 0; + std::string backButtonBehavior = Config::getBackButtonBehavior(); switch (event->key.key) { case SDLK_UP: button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_UP; @@ -278,7 +289,15 @@ void WindowSDL::onKeyPress(const SDL_Event* event) { ax = Input::GetAxis(0, 0x80, axisvalue); break; case SDLK_SPACE: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD; + if (backButtonBehavior != "none") { + float x = backButtonBehavior == "left" ? 0.25f + : (backButtonBehavior == "right" ? 0.75f : 0.5f); + // trigger a touchpad event so that the touchpad emulation for back button works + controller->SetTouchpadState(0, true, x, 0.5f); + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD; + } else { + button = 0; + } break; case SDLK_F11: if (event->type == SDL_EVENT_KEY_DOWN) { @@ -304,9 +323,6 @@ void WindowSDL::onKeyPress(const SDL_Event* event) { if (axis != Input::Axis::AxisMax) { controller->Axis(0, axis, ax); } - if (SDL_GetCursor() != NULL) { - SDL_HideCursor(); - } } void WindowSDL::onGamepadEvent(const SDL_Event* event) { @@ -330,10 +346,20 @@ void WindowSDL::onGamepadEvent(const SDL_Event* event) { case SDL_EVENT_GAMEPAD_BUTTON_UP: button = sdlGamepadToOrbisButton(event->gbutton.button); if (button != 0) { - controller->CheckButton(0, button, event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN); - } - if (SDL_GetCursor() != NULL) { - SDL_HideCursor(); + if (event->gbutton.button == SDL_GAMEPAD_BUTTON_BACK) { + std::string backButtonBehavior = Config::getBackButtonBehavior(); + if (backButtonBehavior != "none") { + float x = backButtonBehavior == "left" + ? 0.25f + : (backButtonBehavior == "right" ? 0.75f : 0.5f); + // trigger a touchpad event so that the touchpad emulation for back button works + controller->SetTouchpadState(0, true, x, 0.5f); + controller->CheckButton(0, button, + event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN); + } + } else { + controller->CheckButton(0, button, event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN); + } } break; case SDL_EVENT_GAMEPAD_AXIS_MOTION: diff --git a/src/sdl_window.h b/src/sdl_window.h index 2a5aeb38c..ec8de354b 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -68,6 +68,8 @@ public: void waitEvent(); + void initTimers(); + private: void onResize(); void onKeyPress(const SDL_Event* event); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index a585f3283..e84908a57 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -27,8 +27,10 @@ static constexpr spv::ExecutionMode GetInputPrimitiveType(AmdGpu::PrimitiveType case AmdGpu::PrimitiveType::TriangleList: case AmdGpu::PrimitiveType::TriangleStrip: return spv::ExecutionMode::Triangles; + case AmdGpu::PrimitiveType::AdjTriangleList: + return spv::ExecutionMode::InputTrianglesAdjacency; default: - UNREACHABLE(); + UNREACHABLE_MSG("Unknown input primitive type {}", u32(type)); } } @@ -41,7 +43,7 @@ static constexpr spv::ExecutionMode GetOutputPrimitiveType(AmdGpu::GsOutputPrimi case AmdGpu::GsOutputPrimitiveType::TriangleStrip: return spv::ExecutionMode::OutputTriangleStrip; default: - UNREACHABLE(); + UNREACHABLE_MSG("Unknown output primitive type {}", u32(type)); } } @@ -68,6 +70,8 @@ ArgType Arg(EmitContext& ctx, const IR::Value& arg) { return arg.ScalarReg(); } else if constexpr (std::is_same_v) { return arg.VectorReg(); + } else if constexpr (std::is_same_v) { + return arg.StringLiteral(); } } @@ -202,10 +206,7 @@ Id DefineMain(EmitContext& ctx, const IR::Program& program) { return main; } -void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) { - const auto& info = program.info; - const std::span interfaces(ctx.interfaces.data(), ctx.interfaces.size()); - spv::ExecutionModel execution_model{}; +void SetupCapabilities(const Info& info, EmitContext& ctx) { ctx.AddCapability(spv::Capability::Image1D); ctx.AddCapability(spv::Capability::Sampled1D); ctx.AddCapability(spv::Capability::ImageQuery); @@ -243,6 +244,19 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) { if (info.uses_group_ballot) { ctx.AddCapability(spv::Capability::GroupNonUniformBallot); } + if (info.stage == Stage::Export || info.stage == Stage::Vertex) { + ctx.AddExtension("SPV_KHR_shader_draw_parameters"); + ctx.AddCapability(spv::Capability::DrawParameters); + } + if (info.stage == Stage::Geometry) { + ctx.AddCapability(spv::Capability::Geometry); + } +} + +void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) { + const auto& info = program.info; + const std::span interfaces(ctx.interfaces.data(), ctx.interfaces.size()); + spv::ExecutionModel execution_model{}; switch (program.info.stage) { case Stage::Compute: { const std::array workgroup_size{ctx.runtime_info.cs_info.workgroup_size}; @@ -286,6 +300,24 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) { ctx.AddEntryPoint(execution_model, main, "main", interfaces); } +void SetupFloatMode(EmitContext& ctx, const Profile& profile, const RuntimeInfo& runtime_info, + Id main_func) { + ctx.AddExtension("SPV_KHR_float_controls"); + const auto fp_denorm_mode = runtime_info.fp_denorm_mode32; + if (fp_denorm_mode == AmdGpu::FpDenormMode::InOutFlush) { + if (profile.support_fp32_denorm_flush) { + ctx.AddCapability(spv::Capability::DenormFlushToZero); + ctx.AddExecutionMode(main_func, spv::ExecutionMode::DenormFlushToZero, 32U); + } + } else { + LOG_WARNING(Render_Vulkan, "Unknown FP denorm mode {}", u32(fp_denorm_mode)); + } + const auto fp_round_mode = runtime_info.fp_round_mode32; + if (fp_round_mode != AmdGpu::FpRoundMode::NearestEven) { + LOG_WARNING(Render_Vulkan, "Unknown FP rounding mode {}", u32(fp_round_mode)); + } +} + void PatchPhiNodes(const IR::Program& program, EmitContext& ctx) { auto inst{program.blocks.front()->begin()}; size_t block_index{0}; @@ -310,18 +342,8 @@ std::vector EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_in EmitContext ctx{profile, runtime_info, program.info, binding}; const Id main{DefineMain(ctx, program)}; DefineEntryPoint(program, ctx, main); - switch (program.info.stage) { - case Stage::Export: - case Stage::Vertex: - ctx.AddExtension("SPV_KHR_shader_draw_parameters"); - ctx.AddCapability(spv::Capability::DrawParameters); - break; - case Stage::Geometry: - ctx.AddCapability(spv::Capability::Geometry); - break; - default: - break; - } + SetupCapabilities(program.info, ctx); + SetupFloatMode(ctx, profile, runtime_info, main); PatchPhiNodes(program, ctx); binding.user_data += program.info.ud_mask.NumRegs(); return ctx.Assemble(); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index 8f062d6e7..fc99b8925 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -59,19 +59,22 @@ struct ImageOperands { } } - void AddDerivatives(EmitContext& ctx, Id derivatives) { - if (!Sirit::ValidId(derivatives)) { + void AddDerivatives(EmitContext& ctx, Id derivatives_dx, Id derivatives_dy) { + if (!Sirit::ValidId(derivatives_dx) || !Sirit::ValidId(derivatives_dy)) { return; } - const Id dx{ctx.OpVectorShuffle(ctx.F32[2], derivatives, derivatives, 0, 1)}; - const Id dy{ctx.OpVectorShuffle(ctx.F32[2], derivatives, derivatives, 2, 3)}; - Add(spv::ImageOperandsMask::Grad, dx, dy); + Add(spv::ImageOperandsMask::Grad, derivatives_dx, derivatives_dy); } spv::ImageOperandsMask mask{}; boost::container::static_vector operands; }; +Id EmitImageSampleRaw(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address1, Id address2, + Id address3, Id address4) { + UNREACHABLE_MSG("Unreachable instruction"); +} + Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id bias, const IR::Value& offset) { const auto& texture = ctx.images[handle & 0xFFFF]; @@ -114,7 +117,9 @@ Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, operands.AddOffset(ctx, offset); const Id sample = ctx.OpImageSampleDrefImplicitLod(result_type, sampled_image, coords, dref, operands.mask, operands.operands); - return texture.is_integer ? ctx.OpBitcast(ctx.F32[1], sample) : sample; + const Id sample_typed = texture.is_integer ? ctx.OpBitcast(ctx.F32[1], sample) : sample; + return ctx.OpCompositeConstruct(ctx.F32[4], sample_typed, ctx.f32_zero_value, + ctx.f32_zero_value, ctx.f32_zero_value); } Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id dref, @@ -129,7 +134,9 @@ Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, operands.Add(spv::ImageOperandsMask::Lod, lod); const Id sample = ctx.OpImageSampleDrefExplicitLod(result_type, sampled_image, coords, dref, operands.mask, operands.operands); - return texture.is_integer ? ctx.OpBitcast(ctx.F32[1], sample) : sample; + const Id sample_typed = texture.is_integer ? ctx.OpBitcast(ctx.F32[1], sample) : sample; + return ctx.OpCompositeConstruct(ctx.F32[4], sample_typed, ctx.f32_zero_value, + ctx.f32_zero_value, ctx.f32_zero_value); } Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, @@ -212,15 +219,15 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords) { return ctx.OpImageQueryLod(ctx.F32[2], sampled_image, coords); } -Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id derivatives, - const IR::Value& offset, Id lod_clamp) { +Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id derivatives_dx, + Id derivatives_dy, const IR::Value& offset, const IR::Value& lod_clamp) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id result_type = texture.data_types->Get(4); const Id sampler = ctx.OpLoad(ctx.sampler_type, ctx.samplers[handle >> 16]); const Id sampled_image = ctx.OpSampledImage(texture.sampled_type, image, sampler); ImageOperands operands; - operands.AddDerivatives(ctx, derivatives); + operands.AddDerivatives(ctx, derivatives_dx, derivatives_dy); operands.AddOffset(ctx, offset); const Id sample = ctx.OpImageSampleExplicitLod(result_type, sampled_image, coords, operands.mask, operands.operands); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index ec86e5cc9..02b98b343 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -48,6 +48,7 @@ void EmitPrologue(EmitContext& ctx); void EmitEpilogue(EmitContext& ctx); void EmitDiscard(EmitContext& ctx); void EmitDiscardCond(EmitContext& ctx, Id condition); +void EmitDebugPrint(EmitContext& ctx, IR::Inst* inst, Id arg0, Id arg1, Id arg2, Id arg3, Id arg4); void EmitBarrier(EmitContext& ctx); void EmitWorkgroupMemoryBarrier(EmitContext& ctx); void EmitDeviceMemoryBarrier(EmitContext& ctx); @@ -367,6 +368,8 @@ Id EmitConvertF64U64(EmitContext& ctx, Id value); Id EmitConvertU16U32(EmitContext& ctx, Id value); Id EmitConvertU32U16(EmitContext& ctx, Id value); +Id EmitImageSampleRaw(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address1, Id address2, + Id address3, Id address4); Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id bias, const IR::Value& offset); Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, @@ -383,8 +386,8 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, const Id lod, Id ms); Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, u32 handle, Id lod, bool skip_mips); Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords); -Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id derivatives, - const IR::Value& offset, Id lod_clamp); +Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id derivatives_dx, + Id derivatives_dy, const IR::Value& offset, const IR::Value& lod_clamp); Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords); void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id color); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp index c12e4997f..e9ffdcce8 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp @@ -3,6 +3,7 @@ #include "shader_recompiler/backend/spirv/emit_spirv_instructions.h" #include "shader_recompiler/backend/spirv/spirv_emit_context.h" +#include "shader_recompiler/ir/debug_print.h" namespace Shader::Backend::SPIRV { @@ -57,4 +58,11 @@ void EmitEndPrimitive(EmitContext& ctx, const IR::Value& stream) { throw NotImplementedException("Geometry streams"); } +void EmitDebugPrint(EmitContext& ctx, IR::Inst* inst, Id fmt, Id arg0, Id arg1, Id arg2, Id arg3) { + IR::DebugPrintFlags flags = inst->Flags(); + std::array fmt_args = {arg0, arg1, arg2, arg3}; + auto fmt_args_span = std::span(fmt_args.begin(), fmt_args.begin() + flags.num_args); + ctx.OpDebugPrintf(fmt, fmt_args_span); +} + } // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index f5b60d51d..6581a7a56 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -34,14 +34,17 @@ std::string_view StageName(Stage stage) { throw InvalidArgument("Invalid stage {}", u32(stage)); } -static constexpr u32 NumVertices(AmdGpu::GsOutputPrimitiveType type) { +static constexpr u32 NumVertices(AmdGpu::PrimitiveType type) { switch (type) { - case AmdGpu::GsOutputPrimitiveType::PointList: + case AmdGpu::PrimitiveType::PointList: return 1u; - case AmdGpu::GsOutputPrimitiveType::LineStrip: + case AmdGpu::PrimitiveType::LineList: return 2u; - case AmdGpu::GsOutputPrimitiveType::TriangleStrip: + case AmdGpu::PrimitiveType::TriangleList: + case AmdGpu::PrimitiveType::TriangleStrip: return 3u; + case AmdGpu::PrimitiveType::AdjTriangleList: + return 6u; default: UNREACHABLE(); } @@ -88,6 +91,8 @@ Id EmitContext::Def(const IR::Value& value) { return ConstF32(value.F32()); case IR::Type::F64: return Constant(F64[1], value.F64()); + case IR::Type::StringLiteral: + return String(value.StringLiteral()); default: throw NotImplementedException("Immediate type {}", value.Type()); } @@ -279,7 +284,8 @@ void EmitContext::DefineInputs() { frag_coord = DefineVariable(F32[4], spv::BuiltIn::FragCoord, spv::StorageClass::Input); frag_depth = DefineVariable(F32[1], spv::BuiltIn::FragDepth, spv::StorageClass::Output); front_facing = DefineVariable(U1[1], spv::BuiltIn::FrontFacing, spv::StorageClass::Input); - for (const auto& input : runtime_info.fs_info.inputs) { + for (s32 i = 0; i < runtime_info.fs_info.num_inputs; i++) { + const auto& input = runtime_info.fs_info.inputs[i]; const u32 semantic = input.param_index; ASSERT(semantic < IR::NumParams); if (input.is_default && !input.is_flat) { @@ -321,16 +327,14 @@ void EmitContext::DefineInputs() { MemberDecorate(gl_per_vertex, 2, spv::Decoration::BuiltIn, static_cast(spv::BuiltIn::ClipDistance)); Decorate(gl_per_vertex, spv::Decoration::Block); - const auto vertices_in = - TypeArray(gl_per_vertex, ConstU32(NumVertices(runtime_info.gs_info.out_primitive[0]))); + const auto num_verts_in = NumVertices(runtime_info.gs_info.in_primitive); + const auto vertices_in = TypeArray(gl_per_vertex, ConstU32(num_verts_in)); gl_in = Name(DefineVar(vertices_in, spv::StorageClass::Input), "gl_in"); interfaces.push_back(gl_in); const auto num_params = runtime_info.gs_info.in_vertex_data_size / 4 - 1u; for (int param_id = 0; param_id < num_params; ++param_id) { - const IR::Attribute param{IR::Attribute::Param0 + param_id}; - const Id type{ - TypeArray(F32[4], ConstU32(NumVertices(runtime_info.gs_info.out_primitive[0])))}; + const Id type{TypeArray(F32[4], ConstU32(num_verts_in))}; const Id id{DefineInput(type, param_id)}; Name(id, fmt::format("in_attr{}", param_id)); input_params[param_id] = {id, input_f32, F32[1], 4}; @@ -390,8 +394,7 @@ void EmitContext::DefineOutputs() { case Stage::Geometry: { output_position = DefineVariable(F32[4], spv::BuiltIn::Position, spv::StorageClass::Output); - for (u32 attr_id = 0; attr_id < runtime_info.gs_info.copy_data.num_attrs; attr_id++) { - const IR::Attribute param{IR::Attribute::Param0 + attr_id}; + for (u32 attr_id = 0; attr_id < info.gs_copy_data.num_attrs; attr_id++) { const Id id{DefineOutput(F32[4], attr_id)}; Name(id, fmt::format("out_attr{}", attr_id)); output_params[attr_id] = {id, output_f32, F32[1], 4u}; diff --git a/src/shader_recompiler/frontend/control_flow_graph.cpp b/src/shader_recompiler/frontend/control_flow_graph.cpp index d6bfd288d..53d815a54 100644 --- a/src/shader_recompiler/frontend/control_flow_graph.cpp +++ b/src/shader_recompiler/frontend/control_flow_graph.cpp @@ -163,10 +163,10 @@ void CFG::EmitDivergenceLabels() { } void CFG::EmitBlocks() { - for (auto it = labels.begin(); it != labels.end(); it++) { + for (auto it = labels.cbegin(); it != labels.cend(); ++it) { const Label start = *it; const auto next_it = std::next(it); - const bool is_last = next_it == labels.end(); + const bool is_last = (next_it == labels.cend()); if (is_last) { // Last label is special. return; @@ -193,7 +193,7 @@ void CFG::EmitBlocks() { void CFG::LinkBlocks() { const auto get_block = [this](u32 address) { auto it = blocks.find(address, Compare{}); - ASSERT_MSG(it != blocks.end() && it->begin == address); + ASSERT_MSG(it != blocks.cend() && it->begin == address); return &*it; }; diff --git a/src/shader_recompiler/frontend/copy_shader.cpp b/src/shader_recompiler/frontend/copy_shader.cpp index a194aec95..8750e2b18 100644 --- a/src/shader_recompiler/frontend/copy_shader.cpp +++ b/src/shader_recompiler/frontend/copy_shader.cpp @@ -7,7 +7,7 @@ namespace Shader { -CopyShaderData ParseCopyShader(const std::span& code) { +CopyShaderData ParseCopyShader(std::span code) { Gcn::GcnCodeSlice code_slice{code.data(), code.data() + code.size()}; Gcn::GcnDecodeContext decoder; @@ -15,18 +15,26 @@ CopyShaderData ParseCopyShader(const std::span& code) { ASSERT_MSG(code[0] == token_mov_vcchi, "First instruction is not s_mov_b32 vcc_hi, #imm"); std::array offsets{}; - std::fill(offsets.begin(), offsets.end(), -1); + offsets.fill(-1); + + std::array sources{}; + sources.fill(-1); CopyShaderData data{}; - Gcn::OperandField sgpr{}; auto last_attr{IR::Attribute::Position0}; - s32 soffset{0}; while (!code_slice.atEnd()) { auto inst = decoder.decodeInstruction(code_slice); switch (inst.opcode) { case Gcn::Opcode::S_MOVK_I32: { - sgpr = inst.dst[0].field; - soffset = inst.control.sopk.simm; + sources[inst.dst[0].code] = inst.control.sopk.simm; + break; + } + case Gcn::Opcode::S_MOV_B32: { + sources[inst.dst[0].code] = inst.src[0].code; + break; + } + case Gcn::Opcode::S_ADDK_I32: { + sources[inst.dst[0].code] += inst.control.sopk.simm; break; } case Gcn::Opcode::EXP: { @@ -46,8 +54,9 @@ CopyShaderData ParseCopyShader(const std::span& code) { case Gcn::Opcode::BUFFER_LOAD_DWORD: { offsets[inst.src[1].code] = inst.control.mubuf.offset; if (inst.src[3].field != Gcn::OperandField::ConstZero) { - ASSERT(inst.src[3].field == sgpr); - offsets[inst.src[1].code] += soffset; + const u32 index = inst.src[3].code; + ASSERT(sources[index] != -1); + offsets[inst.src[1].code] += sources[index]; } break; } @@ -59,6 +68,7 @@ CopyShaderData ParseCopyShader(const std::span& code) { if (last_attr != IR::Attribute::Position0) { data.num_attrs = static_cast(last_attr) - static_cast(IR::Attribute::Param0) + 1; } + return data; } diff --git a/src/shader_recompiler/frontend/copy_shader.h b/src/shader_recompiler/frontend/copy_shader.h index ca3e1ac3e..55cc31ebd 100644 --- a/src/shader_recompiler/frontend/copy_shader.h +++ b/src/shader_recompiler/frontend/copy_shader.h @@ -16,6 +16,6 @@ struct CopyShaderData { u32 num_attrs{0}; }; -CopyShaderData ParseCopyShader(const std::span& code); +CopyShaderData ParseCopyShader(std::span code); } // namespace Shader diff --git a/src/shader_recompiler/frontend/decode.cpp b/src/shader_recompiler/frontend/decode.cpp index 98f97dd12..796bed127 100644 --- a/src/shader_recompiler/frontend/decode.cpp +++ b/src/shader_recompiler/frontend/decode.cpp @@ -654,7 +654,7 @@ void GcnDecodeContext::decodeInstructionVOP3(uint64_t hexInstruction) { OpcodeVOP3 vop3Op = static_cast(op); if (IsVop3BEncoding(m_instruction.opcode)) { - m_instruction.dst[1].field = OperandField::ScalarGPR; + m_instruction.dst[1].field = getOperandField(sdst); m_instruction.dst[1].type = ScalarType::Uint64; m_instruction.dst[1].code = sdst; } else { diff --git a/src/shader_recompiler/frontend/format.cpp b/src/shader_recompiler/frontend/format.cpp index 7b3ad00ba..4f0922e2e 100644 --- a/src/shader_recompiler/frontend/format.cpp +++ b/src/shader_recompiler/frontend/format.cpp @@ -3642,8 +3642,8 @@ constexpr std::array InstructionFormatMIMG = {{ {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, ScalarType::Undefined}, // 95 = IMAGE_GATHER4_C_LZ_O - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, // 96 = IMAGE_GET_LOD {InstClass::VectorMemImgUt, InstCategory::VectorMemory, 4, 1, ScalarType::Float32, ScalarType::Float32}, diff --git a/src/shader_recompiler/frontend/structured_control_flow.cpp b/src/shader_recompiler/frontend/structured_control_flow.cpp index bf5ba6bce..11b40d07c 100644 --- a/src/shader_recompiler/frontend/structured_control_flow.cpp +++ b/src/shader_recompiler/frontend/structured_control_flow.cpp @@ -144,32 +144,32 @@ std::string DumpExpr(const Statement* stmt) { [[maybe_unused]] std::string DumpTree(const Tree& tree, u32 indentation = 0) { std::string ret; std::string indent(indentation, ' '); - for (auto stmt = tree.begin(); stmt != tree.end(); ++stmt) { - switch (stmt->type) { + for (const auto& stmt : tree) { + switch (stmt.type) { case StatementType::Code: ret += fmt::format("{} Block {:04x} -> {:04x} (0x{:016x});\n", indent, - stmt->block->begin, stmt->block->end, - reinterpret_cast(stmt->block)); + stmt.block->begin, stmt.block->end, + reinterpret_cast(stmt.block)); break; case StatementType::Goto: - ret += fmt::format("{} if ({}) goto L{};\n", indent, DumpExpr(stmt->cond), - stmt->label->id); + ret += fmt::format("{} if ({}) goto L{};\n", indent, DumpExpr(stmt.cond), + stmt.label->id); break; case StatementType::Label: - ret += fmt::format("{}L{}:\n", indent, stmt->id); + ret += fmt::format("{}L{}:\n", indent, stmt.id); break; case StatementType::If: - ret += fmt::format("{} if ({}) {{\n", indent, DumpExpr(stmt->cond)); - ret += DumpTree(stmt->children, indentation + 4); + ret += fmt::format("{} if ({}) {{\n", indent, DumpExpr(stmt.cond)); + ret += DumpTree(stmt.children, indentation + 4); ret += fmt::format("{} }}\n", indent); break; case StatementType::Loop: ret += fmt::format("{} do {{\n", indent); - ret += DumpTree(stmt->children, indentation + 4); - ret += fmt::format("{} }} while ({});\n", indent, DumpExpr(stmt->cond)); + ret += DumpTree(stmt.children, indentation + 4); + ret += fmt::format("{} }} while ({});\n", indent, DumpExpr(stmt.cond)); break; case StatementType::Break: - ret += fmt::format("{} if ({}) break;\n", indent, DumpExpr(stmt->cond)); + ret += fmt::format("{} if ({}) break;\n", indent, DumpExpr(stmt.cond)); break; case StatementType::Return: ret += fmt::format("{} return;\n", indent); @@ -181,7 +181,7 @@ std::string DumpExpr(const Statement* stmt) { ret += fmt::format("{} unreachable;\n", indent); break; case StatementType::SetVariable: - ret += fmt::format("{} goto_L{} = {};\n", indent, stmt->id, DumpExpr(stmt->op)); + ret += fmt::format("{} goto_L{} = {};\n", indent, stmt.id, DumpExpr(stmt.op)); break; case StatementType::Function: case StatementType::Identity: @@ -625,8 +625,8 @@ private: node.data.block = current_block; }}; Tree& tree{parent.children}; - for (auto it = tree.begin(); it != tree.end(); ++it) { - Statement& stmt{*it}; + for (auto& child : tree) { + Statement& stmt{child}; switch (stmt.type) { case StatementType::Label: // Labels can be ignored diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp index 36c1ec85f..1e627d95c 100644 --- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp @@ -92,8 +92,12 @@ void Translator::EmitScalarAlu(const GcnInst& inst) { break; case Opcode::S_BREV_B32: return S_BREV_B32(inst); + case Opcode::S_BCNT1_I32_B64: + return S_BCNT1_I32_B64(inst); case Opcode::S_AND_SAVEEXEC_B64: - return S_AND_SAVEEXEC_B64(inst); + return S_SAVEEXEC_B64(NegateMode::None, false, inst); + case Opcode::S_ORN2_SAVEEXEC_B64: + return S_SAVEEXEC_B64(NegateMode::Src1, true, inst); default: LogMissingOpcode(inst); } @@ -540,11 +544,17 @@ void Translator::S_BREV_B32(const GcnInst& inst) { SetDst(inst.dst[0], ir.BitReverse(GetSrc(inst.src[0]))); } -void Translator::S_AND_SAVEEXEC_B64(const GcnInst& inst) { +void Translator::S_BCNT1_I32_B64(const GcnInst& inst) { + const IR::U32 result = ir.BitCount(GetSrc(inst.src[0])); + SetDst(inst.dst[0], result); + ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); +} + +void Translator::S_SAVEEXEC_B64(NegateMode negate, bool is_or, const GcnInst& inst) { // This instruction normally operates on 64-bit data (EXEC, VCC, SGPRs) // However here we flatten it to 1-bit EXEC and 1-bit VCC. For the destination // SGPR we have a special IR opcode for SPGRs that act as thread masks. - const IR::U1 exec{ir.GetExec()}; + IR::U1 exec{ir.GetExec()}; const IR::U1 src = [&] { switch (inst.src[0].field) { case OperandField::VccLo: @@ -568,7 +578,13 @@ void Translator::S_AND_SAVEEXEC_B64(const GcnInst& inst) { } // Update EXEC. - const IR::U1 result = ir.LogicalAnd(exec, src); + if (negate == NegateMode::Src1) { + exec = ir.LogicalNot(exec); + } + IR::U1 result = is_or ? ir.LogicalOr(exec, src) : ir.LogicalAnd(exec, src); + if (negate == NegateMode::Result) { + result = ir.LogicalNot(result); + } ir.SetExec(result); ir.SetScc(result); } diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index c77588280..79bc33f0c 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -108,8 +108,9 @@ public: void S_MOV_B64(const GcnInst& inst); void S_NOT_B64(const GcnInst& inst); void S_BREV_B32(const GcnInst& inst); + void S_BCNT1_I32_B64(const GcnInst& inst); void S_GETPC_B64(u32 pc, const GcnInst& inst); - void S_AND_SAVEEXEC_B64(const GcnInst& inst); + void S_SAVEEXEC_B64(NegateMode negate, bool is_or, const GcnInst& inst); // SOPC void S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst); @@ -155,6 +156,8 @@ public: void V_SUB_I32(const GcnInst& inst); void V_SUBREV_I32(const GcnInst& inst); void V_ADDC_U32(const GcnInst& inst); + void V_SUBB_U32(const GcnInst& inst); + void V_SUBBREV_U32(const GcnInst& inst); void V_LDEXP_F32(const GcnInst& inst); void V_CVT_PKNORM_U16_F32(const GcnInst& inst); void V_CVT_PKRTZ_F16_F32(const GcnInst& inst); @@ -223,6 +226,7 @@ public: void V_MED3_I32(const GcnInst& inst); void V_SAD(const GcnInst& inst); void V_SAD_U32(const GcnInst& inst); + void V_CVT_PK_U16_U32(const GcnInst& inst); void V_CVT_PK_U8_F32(const GcnInst& inst); void V_LSHL_B64(const GcnInst& inst); void V_MUL_F64(const GcnInst& inst); @@ -273,7 +277,9 @@ private: void SetDst(const InstOperand& operand, const IR::U32F32& value); void SetDst64(const InstOperand& operand, const IR::U64F64& value_raw); - // Vector ALU Helprers + // Vector ALU Helpers + IR::U32 GetCarryIn(const GcnInst& inst); + void SetCarryOut(const GcnInst& inst, const IR::U1& carry); IR::U32 VMovRelSHelper(u32 src_vgprno, const IR::U32 m0); void VMovRelDHelper(u32 dst_vgprno, const IR::U32 src_val, const IR::U32 m0); diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index b005e42ed..86ac2e742 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -87,6 +87,10 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_SUBREV_I32(inst); case Opcode::V_ADDC_U32: return V_ADDC_U32(inst); + case Opcode::V_SUBB_U32: + return V_SUBB_U32(inst); + case Opcode::V_SUBBREV_U32: + return V_SUBBREV_U32(inst); case Opcode::V_LDEXP_F32: return V_LDEXP_F32(inst); case Opcode::V_CVT_PKNORM_U16_F32: @@ -153,6 +157,8 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_RCP_F64(inst); case Opcode::V_RCP_IFLAG_F32: return V_RCP_F32(inst); + case Opcode::V_RCP_CLAMP_F32: + return V_RCP_F32(inst); case Opcode::V_RSQ_CLAMP_F32: return V_RSQ_F32(inst); case Opcode::V_RSQ_LEGACY_F32: @@ -264,6 +270,8 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_CMP_U32(ConditionOp::GT, true, true, inst); case Opcode::V_CMPX_LG_I32: return V_CMP_U32(ConditionOp::LG, true, true, inst); + case Opcode::V_CMPX_GE_I32: + return V_CMP_U32(ConditionOp::GE, true, true, inst); // V_CMP_{OP8}_U32 case Opcode::V_CMP_F_U32: @@ -350,6 +358,8 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_MED3_I32(inst); case Opcode::V_SAD_U32: return V_SAD_U32(inst); + case Opcode::V_CVT_PK_U16_U32: + return V_CVT_PK_U16_U32(inst); case Opcode::V_CVT_PK_U8_F32: return V_CVT_PK_U8_F32(inst); case Opcode::V_LSHL_B64: @@ -545,51 +555,71 @@ void Translator::V_MBCNT_U32_B32(bool is_low, const GcnInst& inst) { } void Translator::V_ADD_I32(const GcnInst& inst) { + // Signed or unsigned components const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; - SetDst(inst.dst[0], ir.IAdd(src0, src1)); - // TODO: Carry + const IR::U32 result{ir.IAdd(src0, src1)}; + SetDst(inst.dst[0], result); + + // TODO: Carry-out with signed or unsigned components } void Translator::V_SUB_I32(const GcnInst& inst) { + // Unsigned components const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ISub(src0, src1)); + const IR::U32 result{ir.ISub(src0, src1)}; + SetDst(inst.dst[0], result); + + const IR::U1 did_underflow{ir.IGreaterThan(src1, src0, false)}; + SetCarryOut(inst, did_underflow); } void Translator::V_SUBREV_I32(const GcnInst& inst) { + // Unsigned components const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ISub(src1, src0)); - // TODO: Carry-out + const IR::U32 result{ir.ISub(src1, src0)}; + SetDst(inst.dst[0], result); + + const IR::U1 did_underflow{ir.IGreaterThan(src0, src1, false)}; + SetCarryOut(inst, did_underflow); } void Translator::V_ADDC_U32(const GcnInst& inst) { - const auto src0 = GetSrc(inst.src[0]); - const auto src1 = GetSrc(inst.src[1]); - - IR::U1 carry; - if (inst.src_count == 3) { // VOP3 - if (inst.src[2].field == OperandField::VccLo) { - carry = ir.GetVcc(); - } else if (inst.src[2].field == OperandField::ScalarGPR) { - carry = ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[2].code)); - } else { - UNREACHABLE(); - } - } else { // VOP2 - carry = ir.GetVcc(); - } - - const IR::U32 scarry = IR::U32{ir.Select(carry, ir.Imm32(1), ir.Imm32(0))}; - const IR::U32 result = ir.IAdd(ir.IAdd(src0, src1), scarry); - + // Unsigned components + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 carry{GetCarryIn(inst)}; + const IR::U32 result{ir.IAdd(ir.IAdd(src0, src1), carry)}; SetDst(inst.dst[0], result); - const IR::U1 less_src0 = ir.ILessThan(result, src0, false); - const IR::U1 less_src1 = ir.ILessThan(result, src1, false); - const IR::U1 did_overflow = ir.LogicalOr(less_src0, less_src1); - ir.SetVcc(did_overflow); + const IR::U1 less_src0{ir.ILessThan(result, src0, false)}; + const IR::U1 less_src1{ir.ILessThan(result, src1, false)}; + const IR::U1 did_overflow{ir.LogicalOr(less_src0, less_src1)}; + SetCarryOut(inst, did_overflow); +} + +void Translator::V_SUBB_U32(const GcnInst& inst) { + // Signed or unsigned components + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 carry{GetCarryIn(inst)}; + const IR::U32 result{ir.ISub(ir.ISub(src0, src1), carry)}; + SetDst(inst.dst[0], result); + + // TODO: Carry-out with signed or unsigned components +} + +void Translator::V_SUBBREV_U32(const GcnInst& inst) { + // Signed or unsigned components + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 carry{GetCarryIn(inst)}; + const IR::U32 result{ir.ISub(ir.ISub(src1, src0), carry)}; + SetDst(inst.dst[0], result); + + // TODO: Carry-out with signed or unsigned components } void Translator::V_LDEXP_F32(const GcnInst& inst) { @@ -1083,6 +1113,14 @@ void Translator::V_SAD_U32(const GcnInst& inst) { SetDst(inst.dst[0], ir.IAdd(result, src2)); } +void Translator::V_CVT_PK_U16_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 lo = ir.IMin(src0, ir.Imm32(0xFFFF), false); + const IR::U32 hi = ir.IMin(src1, ir.Imm32(0xFFFF), false); + SetDst(inst.dst[0], ir.BitFieldInsert(lo, hi, ir.Imm32(16), ir.Imm32(16))); +} + void Translator::V_CVT_PK_U8_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; @@ -1151,6 +1189,37 @@ void Translator::V_MAD_U64_U32(const GcnInst& inst) { ir.SetVcc(did_overflow); } +IR::U32 Translator::GetCarryIn(const GcnInst& inst) { + IR::U1 carry; + if (inst.src_count == 3) { // VOP3 + if (inst.src[2].field == OperandField::VccLo) { + carry = ir.GetVcc(); + } else if (inst.src[2].field == OperandField::ScalarGPR) { + carry = ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[2].code)); + } else { + UNREACHABLE(); + } + } else { // VOP2 + carry = ir.GetVcc(); + } + + return IR::U32{ir.Select(carry, ir.Imm32(1), ir.Imm32(0))}; +} + +void Translator::SetCarryOut(const GcnInst& inst, const IR::U1& carry) { + if (inst.dst_count == 2) { // VOP3 + if (inst.dst[1].field == OperandField::VccLo) { + ir.SetVcc(carry); + } else if (inst.dst[1].field == OperandField::ScalarGPR) { + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), carry); + } else { + UNREACHABLE(); + } + } else { // VOP2 + ir.SetVcc(carry); + } +} + // TODO: add range analysis pass to hopefully put an upper bound on m0, and only select one of // [src_vgprno, src_vgprno + max_m0]. Same for dst regs we may write back to diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 592908e08..84752380e 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -146,6 +146,7 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { case Opcode::IMAGE_GATHER4_C_O: case Opcode::IMAGE_GATHER4_C_LZ: case Opcode::IMAGE_GATHER4_LZ_O: + case Opcode::IMAGE_GATHER4_C_LZ_O: return IMAGE_GATHER(inst); // Image misc operations @@ -409,7 +410,7 @@ void Translator::IMAGE_LOAD(bool has_mip, const GcnInst& inst) { ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); IR::TextureInstInfo info{}; - info.explicit_lod.Assign(has_mip); + info.has_lod.Assign(has_mip); const IR::Value texel = ir.ImageFetch(handle, body, {}, {}, {}, info); for (u32 i = 0; i < 4; i++) { @@ -511,6 +512,76 @@ void Translator::IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst) { } } +IR::Value EmitImageSample(IR::IREmitter& ir, const GcnInst& inst, const IR::ScalarReg tsharp_reg, + const IR::ScalarReg sampler_reg, const IR::VectorReg addr_reg, + bool gather) { + const auto& mimg = inst.control.mimg; + const auto flags = MimgModifierFlags(mimg.mod); + + IR::TextureInstInfo info{}; + info.is_depth.Assign(flags.test(MimgModifier::Pcf)); + info.has_bias.Assign(flags.test(MimgModifier::LodBias)); + info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp)); + info.force_level0.Assign(flags.test(MimgModifier::Level0)); + info.has_offset.Assign(flags.test(MimgModifier::Offset)); + info.has_lod.Assign(flags.any(MimgModifier::Lod)); + info.is_array.Assign(mimg.da); + + if (gather) { + info.gather_comp.Assign(std::bit_width(mimg.dmask) - 1); + info.is_gather.Assign(true); + } else { + info.has_derivatives.Assign(flags.test(MimgModifier::Derivative)); + } + + // Load first dword of T# and S#. We will use them as the handle that will guide resource + // tracking pass where to read the sharps. This will later also get patched to the SPIRV texture + // binding index. + const IR::Value handle = + ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg)); + + // Determine how many address registers need to be passed. + // The image type is unknown, so add all 4 possible base registers and resolve later. + int num_addr_regs = 4; + if (info.has_offset) { + ++num_addr_regs; + } + if (info.has_bias) { + ++num_addr_regs; + } + if (info.is_depth) { + ++num_addr_regs; + } + if (info.has_derivatives) { + // The image type is unknown, so add all 6 possible derivative registers and resolve later. + num_addr_regs += 6; + } + + // Fetch all the address registers to pass in the IR instruction. There can be up to 13 + // registers. + const auto get_addr_reg = [&](int index) -> IR::F32 { + if (index >= num_addr_regs) { + return ir.Imm32(0.f); + } + return ir.GetVectorReg(addr_reg + index); + }; + const IR::Value address1 = + ir.CompositeConstruct(get_addr_reg(0), get_addr_reg(1), get_addr_reg(2), get_addr_reg(3)); + const IR::Value address2 = + ir.CompositeConstruct(get_addr_reg(4), get_addr_reg(5), get_addr_reg(6), get_addr_reg(7)); + const IR::Value address3 = + ir.CompositeConstruct(get_addr_reg(8), get_addr_reg(9), get_addr_reg(10), get_addr_reg(11)); + const IR::Value address4 = get_addr_reg(12); + + // Issue the placeholder IR instruction. + IR::Value texel = ir.ImageSampleRaw(handle, address1, address2, address3, address4, info); + if (info.is_depth && !gather) { + // For non-gather depth sampling, only return a single value. + texel = ir.CompositeExtract(texel, 0); + } + return texel; +} + void Translator::IMAGE_SAMPLE(const GcnInst& inst) { const auto& mimg = inst.control.mimg; IR::VectorReg addr_reg{inst.src[0].code}; @@ -519,72 +590,7 @@ void Translator::IMAGE_SAMPLE(const GcnInst& inst) { const IR::ScalarReg sampler_reg{inst.src[3].code * 4}; const auto flags = MimgModifierFlags(mimg.mod); - // Load first dword of T# and S#. We will use them as the handle that will guide resource - // tracking pass where to read the sharps. This will later also get patched to the SPIRV texture - // binding index. - const IR::Value handle = - ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg)); - - // Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction - // Set Architecture - const IR::U32 offset = - flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::U32{}; - const IR::F32 bias = - flags.test(MimgModifier::LodBias) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; - const IR::F32 dref = - flags.test(MimgModifier::Pcf) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; - const IR::Value derivatives = [&] -> IR::Value { - if (!flags.test(MimgModifier::Derivative)) { - return {}; - } - addr_reg = addr_reg + 4; - return ir.CompositeConstruct( - ir.GetVectorReg(addr_reg - 4), ir.GetVectorReg(addr_reg - 3), - ir.GetVectorReg(addr_reg - 2), ir.GetVectorReg(addr_reg - 1)); - }(); - - // Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler - // Since these are at most 4 dwords, we load them into a single uvec4 and place them - // in coords field of the instruction. Then the resource tracking pass will patch the - // IR instruction to fill in lod_clamp field. - const IR::Value body = ir.CompositeConstruct( - ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), - ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); - - // Derivatives are tricky because their number depends on the texture type which is located in - // T#. We don't have access to T# though until resource tracking pass. For now assume if - // derivatives are present, that a 2D image is bound. - const bool has_derivatives = flags.test(MimgModifier::Derivative); - const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod); - - IR::TextureInstInfo info{}; - info.is_depth.Assign(flags.test(MimgModifier::Pcf)); - info.has_bias.Assign(flags.test(MimgModifier::LodBias)); - info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp)); - info.force_level0.Assign(flags.test(MimgModifier::Level0)); - info.has_offset.Assign(flags.test(MimgModifier::Offset)); - info.explicit_lod.Assign(explicit_lod); - info.has_derivatives.Assign(has_derivatives); - info.is_array.Assign(mimg.da); - - // Issue IR instruction, leaving unknown fields blank to patch later. - const IR::Value texel = [&]() -> IR::Value { - if (has_derivatives) { - return ir.ImageGradient(handle, body, derivatives, offset, {}, info); - } - if (!flags.test(MimgModifier::Pcf)) { - if (explicit_lod) { - return ir.ImageSampleExplicitLod(handle, body, offset, info); - } else { - return ir.ImageSampleImplicitLod(handle, body, bias, offset, info); - } - } - if (explicit_lod) { - return ir.ImageSampleDrefExplicitLod(handle, body, dref, offset, info); - } - return ir.ImageSampleDrefImplicitLod(handle, body, dref, bias, offset, info); - }(); - + const IR::Value texel = EmitImageSample(ir, inst, tsharp_reg, sampler_reg, addr_reg, false); for (u32 i = 0; i < 4; i++) { if (((mimg.dmask >> i) & 1) == 0) { continue; @@ -607,60 +613,13 @@ void Translator::IMAGE_GATHER(const GcnInst& inst) { const IR::ScalarReg sampler_reg{inst.src[3].code * 4}; const auto flags = MimgModifierFlags(mimg.mod); - // Load first dword of T# and S#. We will use them as the handle that will guide resource - // tracking pass where to read the sharps. This will later also get patched to the SPIRV texture - // binding index. - const IR::Value handle = - ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg)); - - // Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction - // Set Architecture - const IR::Value offset = - flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::Value{}; - const IR::F32 bias = - flags.test(MimgModifier::LodBias) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; - const IR::F32 dref = - flags.test(MimgModifier::Pcf) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; - - // Derivatives are tricky because their number depends on the texture type which is located in - // T#. We don't have access to T# though until resource tracking pass. For now assume no - // derivatives are present, otherwise we don't know where coordinates are placed in the address - // stream. - ASSERT_MSG(!flags.test(MimgModifier::Derivative), "Derivative image instruction"); - - // Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler - // Since these are at most 4 dwords, we load them into a single uvec4 and place them - // in coords field of the instruction. Then the resource tracking pass will patch the - // IR instruction to fill in lod_clamp field. - const IR::Value body = ir.CompositeConstruct( - ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), - ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); - - const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod); - - IR::TextureInstInfo info{}; - info.is_depth.Assign(flags.test(MimgModifier::Pcf)); - info.has_bias.Assign(flags.test(MimgModifier::LodBias)); - info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp)); - info.force_level0.Assign(flags.test(MimgModifier::Level0)); - info.has_offset.Assign(flags.test(MimgModifier::Offset)); - // info.explicit_lod.Assign(explicit_lod); - info.gather_comp.Assign(std::bit_width(mimg.dmask) - 1); - info.is_array.Assign(mimg.da); - - // Issue IR instruction, leaving unknown fields blank to patch later. - const IR::Value texel = [&]() -> IR::Value { - const IR::F32 lod = flags.test(MimgModifier::Level0) ? ir.Imm32(0.f) : IR::F32{}; - if (!flags.test(MimgModifier::Pcf)) { - return ir.ImageGather(handle, body, offset, info); - } - ASSERT(mimg.dmask & 1); // should be always 1st (R) component - return ir.ImageGatherDref(handle, body, offset, dref, info); - }(); - // For gather4 instructions dmask selects which component to read and must have // only one bit set to 1 ASSERT_MSG(std::popcount(mimg.dmask) == 1, "Unexpected bits in gather dmask"); + // should be always 1st (R) component for depth + ASSERT(!flags.test(MimgModifier::Pcf) || mimg.dmask & 1); + + const IR::Value texel = EmitImageSample(ir, inst, tsharp_reg, sampler_reg, addr_reg, true); for (u32 i = 0; i < 4; i++) { const IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)}; ir.SetVectorReg(dest_reg++, value); diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h index 7fc7be753..e727c8a08 100644 --- a/src/shader_recompiler/info.h +++ b/src/shader_recompiler/info.h @@ -8,6 +8,7 @@ #include "common/assert.h" #include "common/types.h" #include "shader_recompiler/backend/bindings.h" +#include "shader_recompiler/frontend/copy_shader.h" #include "shader_recompiler/ir/attribute.h" #include "shader_recompiler/ir/reg.h" #include "shader_recompiler/ir/type.h" @@ -169,6 +170,8 @@ struct Info { }; UserDataMask ud_mask{}; + CopyShaderData gs_copy_data; + s8 vertex_offset_sgpr = -1; s8 instance_offset_sgpr = -1; diff --git a/src/shader_recompiler/ir/basic_block.h b/src/shader_recompiler/ir/basic_block.h index 11ae969bc..74a7d2c56 100644 --- a/src/shader_recompiler/ir/basic_block.h +++ b/src/shader_recompiler/ir/basic_block.h @@ -109,13 +109,13 @@ public: return instructions.begin(); } [[nodiscard]] const_iterator begin() const { - return instructions.begin(); + return instructions.cbegin(); } [[nodiscard]] iterator end() { return instructions.end(); } [[nodiscard]] const_iterator end() const { - return instructions.end(); + return instructions.cend(); } [[nodiscard]] reverse_iterator rbegin() { diff --git a/src/shader_recompiler/ir/debug_print.h b/src/shader_recompiler/ir/debug_print.h new file mode 100644 index 000000000..1ab1575de --- /dev/null +++ b/src/shader_recompiler/ir/debug_print.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/bit_field.h" +#include "shader_recompiler/ir/opcodes.h" +#include "src/common/types.h" + +#pragma once + +namespace Shader::IR { + +constexpr size_t DEBUGPRINT_NUM_FORMAT_ARGS = NumArgsOf(IR::Opcode::DebugPrint) - 1; + +union DebugPrintFlags { + u32 raw; + // For now, only flag is the number of variadic format args actually used + // So bitfield not really needed + BitField<0, 32, u32> num_args; +}; + +} // namespace Shader::IR \ No newline at end of file diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 01336c567..cfd044f9e 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -1,10 +1,15 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include +#include +#include "common/assert.h" #include "shader_recompiler/exception.h" +#include "shader_recompiler/ir/debug_print.h" #include "shader_recompiler/ir/ir_emitter.h" +#include "shader_recompiler/ir/opcodes.h" #include "shader_recompiler/ir/value.h" namespace Shader::IR { @@ -125,19 +130,23 @@ void IREmitter::DeviceMemoryBarrier() { } U32 IREmitter::GetUserData(IR::ScalarReg reg) { + ASSERT(static_cast(reg) < IR::NumScalarRegs); return Inst(Opcode::GetUserData, reg); } U1 IREmitter::GetThreadBitScalarReg(IR::ScalarReg reg) { + ASSERT(static_cast(reg) < IR::NumScalarRegs); return Inst(Opcode::GetThreadBitScalarReg, reg); } void IREmitter::SetThreadBitScalarReg(IR::ScalarReg reg, const U1& value) { + ASSERT(static_cast(reg) < IR::NumScalarRegs); Inst(Opcode::SetThreadBitScalarReg, reg, value); } template <> U32 IREmitter::GetScalarReg(IR::ScalarReg reg) { + ASSERT(static_cast(reg) < IR::NumScalarRegs); return Inst(Opcode::GetScalarRegister, reg); } @@ -148,6 +157,7 @@ F32 IREmitter::GetScalarReg(IR::ScalarReg reg) { template <> U32 IREmitter::GetVectorReg(IR::VectorReg reg) { + ASSERT(static_cast(reg) < IR::NumVectorRegs); return Inst(Opcode::GetVectorRegister, reg); } @@ -157,11 +167,13 @@ F32 IREmitter::GetVectorReg(IR::VectorReg reg) { } void IREmitter::SetScalarReg(IR::ScalarReg reg, const U32F32& value) { + ASSERT(static_cast(reg) < IR::NumScalarRegs); const U32 value_typed = value.Type() == Type::F32 ? BitCast(F32{value}) : U32{value}; Inst(Opcode::SetScalarRegister, reg, value_typed); } void IREmitter::SetVectorReg(IR::VectorReg reg, const U32F32& value) { + ASSERT(static_cast(reg) < IR::NumVectorRegs); const U32 value_typed = value.Type() == Type::F32 ? BitCast(F32{value}) : U32{value}; Inst(Opcode::SetVectorRegister, reg, value_typed); } @@ -1487,27 +1499,34 @@ Value IREmitter::ImageAtomicExchange(const Value& handle, const Value& coords, c return Inst(Opcode::ImageAtomicExchange32, Flags{info}, handle, coords, value); } -Value IREmitter::ImageSampleImplicitLod(const Value& handle, const Value& body, const F32& bias, - const U32& offset, TextureInstInfo info) { - return Inst(Opcode::ImageSampleImplicitLod, Flags{info}, handle, body, bias, offset); +Value IREmitter::ImageSampleRaw(const Value& handle, const Value& address1, const Value& address2, + const Value& address3, const Value& address4, + TextureInstInfo info) { + return Inst(Opcode::ImageSampleRaw, Flags{info}, handle, address1, address2, address3, + address4); } -Value IREmitter::ImageSampleExplicitLod(const Value& handle, const Value& body, const U32& offset, - TextureInstInfo info) { - return Inst(Opcode::ImageSampleExplicitLod, Flags{info}, handle, body, IR::F32{}, offset); +Value IREmitter::ImageSampleImplicitLod(const Value& handle, const Value& coords, const F32& bias, + const Value& offset, TextureInstInfo info) { + return Inst(Opcode::ImageSampleImplicitLod, Flags{info}, handle, coords, bias, offset); } -F32 IREmitter::ImageSampleDrefImplicitLod(const Value& handle, const Value& body, const F32& dref, - const F32& bias, const U32& offset, - TextureInstInfo info) { - return Inst(Opcode::ImageSampleDrefImplicitLod, Flags{info}, handle, body, dref, bias, - offset); +Value IREmitter::ImageSampleExplicitLod(const Value& handle, const Value& coords, const F32& lod, + const Value& offset, TextureInstInfo info) { + return Inst(Opcode::ImageSampleExplicitLod, Flags{info}, handle, coords, lod, offset); } -F32 IREmitter::ImageSampleDrefExplicitLod(const Value& handle, const Value& body, const F32& dref, - const U32& offset, TextureInstInfo info) { - return Inst(Opcode::ImageSampleDrefExplicitLod, Flags{info}, handle, body, dref, IR::F32{}, - offset); +Value IREmitter::ImageSampleDrefImplicitLod(const Value& handle, const Value& coords, + const F32& dref, const F32& bias, const Value& offset, + TextureInstInfo info) { + return Inst(Opcode::ImageSampleDrefImplicitLod, Flags{info}, handle, coords, dref, bias, + offset); +} + +Value IREmitter::ImageSampleDrefExplicitLod(const Value& handle, const Value& coords, + const F32& dref, const F32& lod, const Value& offset, + TextureInstInfo info) { + return Inst(Opcode::ImageSampleDrefExplicitLod, Flags{info}, handle, coords, dref, lod, offset); } Value IREmitter::ImageGather(const Value& handle, const Value& coords, const Value& offset, @@ -1539,9 +1558,11 @@ Value IREmitter::ImageQueryLod(const Value& handle, const Value& coords, Texture return Inst(Opcode::ImageQueryLod, Flags{info}, handle, coords); } -Value IREmitter::ImageGradient(const Value& handle, const Value& coords, const Value& derivatives, +Value IREmitter::ImageGradient(const Value& handle, const Value& coords, + const Value& derivatives_dx, const Value& derivatives_dy, const Value& offset, const F32& lod_clamp, TextureInstInfo info) { - return Inst(Opcode::ImageGradient, Flags{info}, handle, coords, derivatives, offset, lod_clamp); + return Inst(Opcode::ImageGradient, Flags{info}, handle, coords, derivatives_dx, derivatives_dy, + offset, lod_clamp); } Value IREmitter::ImageRead(const Value& handle, const Value& coords, TextureInstInfo info) { @@ -1553,6 +1574,38 @@ void IREmitter::ImageWrite(const Value& handle, const Value& coords, const Value Inst(Opcode::ImageWrite, Flags{info}, handle, coords, color); } +// Debug print maps to SPIRV's NonSemantic DebugPrintf instruction +// Renderdoc will hook in its own implementation of the SPIRV instruction +// Renderdoc accepts format specifiers, e.g. %u, listed here: +// https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/main/docs/debug_printf.md +// +// fmt must be a string literal (pointer is shallow copied into a Value) +// Example usage: +// ir.DebugPrint("invocation xyz: (%u, %u, %u)", +// {ir.GetVectorReg(IR::VectorReg::V0), +// ir.GetVectorReg(IR::VectorReg::V1), +// ir.GetVectorReg(IR::VectorReg::V2)}); +void IREmitter::DebugPrint(const char* fmt, boost::container::small_vector format_args) { + std::array args; + + ASSERT_MSG(format_args.size() < DEBUGPRINT_NUM_FORMAT_ARGS, + "DebugPrint only supports up to {} format args", DEBUGPRINT_NUM_FORMAT_ARGS); + + for (int i = 0; i < format_args.size(); i++) { + args[i] = format_args[i]; + } + + for (int i = format_args.size(); i < DEBUGPRINT_NUM_FORMAT_ARGS; i++) { + args[i] = Inst(Opcode::Void); + } + + IR::Value fmt_val{fmt}; + + DebugPrintFlags flags; + flags.num_args.Assign(format_args.size()); + Inst(Opcode::DebugPrint, Flags{flags}, fmt_val, args[0], args[1], args[2], args[3]); +} + void IREmitter::EmitVertex() { Inst(Opcode::EmitVertex); } diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 8657c430b..b3f513085 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -6,6 +6,7 @@ #include #include +#include "shader_recompiler/info.h" #include "shader_recompiler/ir/attribute.h" #include "shader_recompiler/ir/basic_block.h" #include "shader_recompiler/ir/condition.h" @@ -43,6 +44,7 @@ public: void Epilogue(); void Discard(); void Discard(const U1& cond); + void DebugPrint(const char* fmt, boost::container::small_vector args); void Barrier(); void WorkgroupMemoryBarrier(); @@ -275,20 +277,25 @@ public: [[nodiscard]] Value ImageAtomicExchange(const Value& handle, const Value& coords, const Value& value, TextureInstInfo info); + [[nodiscard]] Value ImageSampleRaw(const Value& handle, const Value& address1, + const Value& address2, const Value& address3, + const Value& address4, TextureInstInfo info); + [[nodiscard]] Value ImageSampleImplicitLod(const Value& handle, const Value& body, - const F32& bias, const U32& offset, + const F32& bias, const Value& offset, TextureInstInfo info); [[nodiscard]] Value ImageSampleExplicitLod(const Value& handle, const Value& body, - const U32& offset, TextureInstInfo info); + const F32& lod, const Value& offset, + TextureInstInfo info); - [[nodiscard]] F32 ImageSampleDrefImplicitLod(const Value& handle, const Value& body, - const F32& dref, const F32& bias, - const U32& offset, TextureInstInfo info); + [[nodiscard]] Value ImageSampleDrefImplicitLod(const Value& handle, const Value& body, + const F32& dref, const F32& bias, + const Value& offset, TextureInstInfo info); - [[nodiscard]] F32 ImageSampleDrefExplicitLod(const Value& handle, const Value& body, - const F32& dref, const U32& offset, - TextureInstInfo info); + [[nodiscard]] Value ImageSampleDrefExplicitLod(const Value& handle, const Value& body, + const F32& dref, const F32& lod, + const Value& offset, TextureInstInfo info); [[nodiscard]] Value ImageQueryDimension(const Value& handle, const U32& lod, const U1& skip_mips); @@ -304,8 +311,9 @@ public: [[nodiscard]] Value ImageFetch(const Value& handle, const Value& coords, const Value& offset, const U32& lod, const U32& multisampling, TextureInstInfo info); [[nodiscard]] Value ImageGradient(const Value& handle, const Value& coords, - const Value& derivatives, const Value& offset, - const F32& lod_clamp, TextureInstInfo info); + const Value& derivatives_dx, const Value& derivatives_dy, + const Value& offset, const F32& lod_clamp, + TextureInstInfo info); [[nodiscard]] Value ImageRead(const Value& handle, const Value& coords, TextureInstInfo info); void ImageWrite(const Value& handle, const Value& coords, const Value& color, TextureInstInfo info); diff --git a/src/shader_recompiler/ir/microinstruction.cpp b/src/shader_recompiler/ir/microinstruction.cpp index 8d606a6cc..f0b4882b3 100644 --- a/src/shader_recompiler/ir/microinstruction.cpp +++ b/src/shader_recompiler/ir/microinstruction.cpp @@ -89,6 +89,7 @@ bool Inst::MayHaveSideEffects() const noexcept { case Opcode::ImageAtomicOr32: case Opcode::ImageAtomicXor32: case Opcode::ImageAtomicExchange32: + case Opcode::DebugPrint: case Opcode::EmitVertex: case Opcode::EmitPrimitive: return true; diff --git a/src/shader_recompiler/ir/opcodes.h b/src/shader_recompiler/ir/opcodes.h index 06f1a6117..200d7f421 100644 --- a/src/shader_recompiler/ir/opcodes.h +++ b/src/shader_recompiler/ir/opcodes.h @@ -21,7 +21,7 @@ namespace Detail { struct OpcodeMeta { std::string_view name; Type type; - std::array arg_types; + std::array arg_types; }; // using enum Type; @@ -51,6 +51,7 @@ constexpr Type F32x4{Type::F32x4}; constexpr Type F64x2{Type::F64x2}; constexpr Type F64x3{Type::F64x3}; constexpr Type F64x4{Type::F64x4}; +constexpr Type StringLiteral{Type::StringLiteral}; constexpr OpcodeMeta META_TABLE[]{ #define OPCODE(name_token, type_token, ...) \ @@ -81,7 +82,7 @@ constexpr u8 NUM_ARGS[]{ } /// Get the number of arguments an opcode accepts -[[nodiscard]] inline size_t NumArgsOf(Opcode op) noexcept { +[[nodiscard]] constexpr inline size_t NumArgsOf(Opcode op) noexcept { return static_cast(Detail::NUM_ARGS[static_cast(op)]); } diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index c69dc90a5..51e10fb38 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -14,6 +14,7 @@ OPCODE(Prologue, Void, OPCODE(Epilogue, Void, ) OPCODE(Discard, Void, ) OPCODE(DiscardCond, Void, U1, ) +OPCODE(DebugPrint, Void, StringLiteral, Opaque, Opaque, Opaque, Opaque, ) // Constant memory operations OPCODE(ReadConst, U32, U32x2, U32, ) @@ -316,16 +317,17 @@ OPCODE(ConvertU16U32, U16, U32, OPCODE(ConvertU32U16, U32, U16, ) // Image operations -OPCODE(ImageSampleImplicitLod, F32x4, Opaque, Opaque, F32, Opaque, ) -OPCODE(ImageSampleExplicitLod, F32x4, Opaque, Opaque, U32, Opaque, ) -OPCODE(ImageSampleDrefImplicitLod, F32, Opaque, Opaque, Opaque, F32, Opaque, ) -OPCODE(ImageSampleDrefExplicitLod, F32, Opaque, Opaque, Opaque, U32, Opaque, ) +OPCODE(ImageSampleRaw, F32x4, Opaque, F32x4, F32x4, F32x4, F32, ) +OPCODE(ImageSampleImplicitLod, F32x4, Opaque, F32x4, F32, Opaque, ) +OPCODE(ImageSampleExplicitLod, F32x4, Opaque, Opaque, F32, Opaque, ) +OPCODE(ImageSampleDrefImplicitLod, F32x4, Opaque, Opaque, F32, F32, Opaque, ) +OPCODE(ImageSampleDrefExplicitLod, F32x4, Opaque, Opaque, F32, F32, Opaque, ) OPCODE(ImageGather, F32x4, Opaque, Opaque, Opaque, ) OPCODE(ImageGatherDref, F32x4, Opaque, Opaque, Opaque, F32, ) OPCODE(ImageFetch, F32x4, Opaque, Opaque, Opaque, U32, Opaque, ) OPCODE(ImageQueryDimensions, U32x4, Opaque, U32, U1, ) OPCODE(ImageQueryLod, F32x4, Opaque, Opaque, ) -OPCODE(ImageGradient, F32x4, Opaque, Opaque, Opaque, Opaque, Opaque, ) +OPCODE(ImageGradient, F32x4, Opaque, Opaque, Opaque, Opaque, Opaque, F32, ) OPCODE(ImageRead, U32x4, Opaque, Opaque, ) OPCODE(ImageWrite, Void, Opaque, Opaque, U32x4, ) diff --git a/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp b/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp index 775aed5b3..a03fe051c 100644 --- a/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp +++ b/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp @@ -6,6 +6,7 @@ #include #include "common/func_traits.h" #include "shader_recompiler/ir/basic_block.h" +#include "shader_recompiler/ir/ir_emitter.h" namespace Shader::Optimization { @@ -215,36 +216,17 @@ void FoldAdd(IR::Block& block, IR::Inst& inst) { } } -template -bool IsArgImm(const IR::Inst& inst, u32 imm) { - const IR::Value& arg = inst.Arg(idx); - return arg.IsImmediate() && arg.U32() == imm; -}; - -void FoldBooleanConvert(IR::Inst& inst) { - // Eliminate pattern - // %4 = - // %5 = SelectU32 %4, #1, #0 (uses: 2) - // %8 = INotEqual %5, #0 (uses: 1) - if (!IsArgImm<1>(inst, 0)) { - return; - } - IR::Inst* prod = inst.Arg(0).TryInstRecursive(); - if (!prod || prod->GetOpcode() != IR::Opcode::SelectU32) { - return; - } - if (IsArgImm<1>(*prod, 1) && IsArgImm<2>(*prod, 0)) { - inst.ReplaceUsesWith(prod->Arg(0)); - } -} - -void FoldCmpClass(IR::Inst& inst) { +void FoldCmpClass(IR::Block& block, IR::Inst& inst) { ASSERT_MSG(inst.Arg(1).IsImmediate(), "Unable to resolve compare operation"); const auto class_mask = static_cast(inst.Arg(1).U32()); if ((class_mask & IR::FloatClassFunc::NaN) == IR::FloatClassFunc::NaN) { inst.ReplaceOpcode(IR::Opcode::FPIsNan32); } else if ((class_mask & IR::FloatClassFunc::Infinity) == IR::FloatClassFunc::Infinity) { inst.ReplaceOpcode(IR::Opcode::FPIsInf32); + } else if ((class_mask & IR::FloatClassFunc::Finite) == IR::FloatClassFunc::Finite) { + IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; + const IR::F32 value = IR::F32{inst.Arg(0)}; + inst.ReplaceUsesWith(ir.LogicalNot(ir.LogicalOr(ir.FPIsInf(value), ir.FPIsInf(value)))); } else { UNREACHABLE(); } @@ -276,7 +258,7 @@ void ConstantPropagation(IR::Block& block, IR::Inst& inst) { FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a * b; }); return; case IR::Opcode::FPCmpClass32: - FoldCmpClass(inst); + FoldCmpClass(block, inst); return; case IR::Opcode::ShiftLeftLogical32: FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return static_cast(a << b); }); diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index db0d75f0c..aa05d3aed 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -132,38 +132,16 @@ bool IsImageStorageInstruction(const IR::Inst& inst) { bool IsImageInstruction(const IR::Inst& inst) { switch (inst.GetOpcode()) { - case IR::Opcode::ImageSampleExplicitLod: - case IR::Opcode::ImageSampleImplicitLod: - case IR::Opcode::ImageSampleDrefExplicitLod: - case IR::Opcode::ImageSampleDrefImplicitLod: case IR::Opcode::ImageFetch: - case IR::Opcode::ImageGather: - case IR::Opcode::ImageGatherDref: case IR::Opcode::ImageQueryDimensions: case IR::Opcode::ImageQueryLod: - case IR::Opcode::ImageGradient: + case IR::Opcode::ImageSampleRaw: return true; default: return IsImageStorageInstruction(inst); } } -u32 ImageOffsetArgumentPosition(const IR::Inst& inst) { - switch (inst.GetOpcode()) { - case IR::Opcode::ImageGather: - case IR::Opcode::ImageGatherDref: - return 2; - case IR::Opcode::ImageSampleExplicitLod: - case IR::Opcode::ImageSampleImplicitLod: - return 3; - case IR::Opcode::ImageSampleDrefExplicitLod: - case IR::Opcode::ImageSampleDrefImplicitLod: - return 4; - default: - UNREACHABLE(); - } -} - class Descriptors { public: explicit Descriptors(Info& info_) @@ -467,6 +445,191 @@ IR::Value PatchCubeCoord(IR::IREmitter& ir, const IR::Value& s, const IR::Value& } } +void PatchImageSampleInstruction(IR::Block& block, IR::Inst& inst, Info& info, + Descriptors& descriptors, const IR::Inst* producer, + const u32 image_binding, const AmdGpu::Image& image) { + // Read sampler sharp. This doesn't exist for IMAGE_LOAD/IMAGE_STORE instructions + const u32 sampler_binding = [&] { + ASSERT(producer->GetOpcode() == IR::Opcode::CompositeConstructU32x2); + const IR::Value& handle = producer->Arg(1); + // Inline sampler resource. + if (handle.IsImmediate()) { + LOG_WARNING(Render_Vulkan, "Inline sampler detected"); + return descriptors.Add(SamplerResource{ + .sgpr_base = std::numeric_limits::max(), + .dword_offset = 0, + .inline_sampler = AmdGpu::Sampler{.raw0 = handle.U32()}, + }); + } + // Normal sampler resource. + const auto ssharp_handle = handle.InstRecursive(); + const auto& [ssharp_ud, disable_aniso] = TryDisableAnisoLod0(ssharp_handle); + const auto ssharp = TrackSharp(ssharp_ud); + return descriptors.Add(SamplerResource{ + .sgpr_base = ssharp.sgpr_base, + .dword_offset = ssharp.dword_offset, + .associated_image = image_binding, + .disable_aniso = disable_aniso, + }); + }(); + + IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; + + const auto inst_info = inst.Flags(); + const IR::U32 handle = ir.Imm32(image_binding | sampler_binding << 16); + + IR::Inst* body1 = inst.Arg(1).InstRecursive(); + IR::Inst* body2 = inst.Arg(2).InstRecursive(); + IR::Inst* body3 = inst.Arg(3).InstRecursive(); + IR::F32 body4 = IR::F32{inst.Arg(4)}; + const auto get_addr_reg = [&](u32 index) -> IR::F32 { + if (index <= 3) { + return IR::F32{body1->Arg(index)}; + } + if (index >= 4 && index <= 7) { + return IR::F32{body2->Arg(index - 4)}; + } + if (index >= 8 && index <= 11) { + return IR::F32{body3->Arg(index - 8)}; + } + if (index == 12) { + return body4; + } + UNREACHABLE(); + }; + u32 addr_reg = 0; + + // Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction + // Set Architecture + const IR::Value offset = [&] -> IR::Value { + if (!inst_info.has_offset) { + return IR::U32{}; + } + + // The offsets are six-bit signed integers: X=[5:0], Y=[13:8], and Z=[21:16]. + IR::Value arg = get_addr_reg(addr_reg++); + if (const IR::Inst* offset_inst = arg.TryInstRecursive()) { + ASSERT(offset_inst->GetOpcode() == IR::Opcode::BitCastF32U32); + arg = offset_inst->Arg(0); + } + + const auto read = [&](u32 off) -> IR::U32 { + if (arg.IsImmediate()) { + const u32 imm = + arg.Type() == IR::Type::F32 ? std::bit_cast(arg.F32()) : arg.U32(); + const u16 comp = (imm >> off) & 0x3F; + return ir.Imm32(s32(comp << 26) >> 26); + } + return ir.BitFieldExtract(IR::U32{arg}, ir.Imm32(off), ir.Imm32(6), true); + }; + + switch (image.GetType()) { + case AmdGpu::ImageType::Color1D: + case AmdGpu::ImageType::Color1DArray: + return read(0); + case AmdGpu::ImageType::Color2D: + case AmdGpu::ImageType::Color2DArray: + case AmdGpu::ImageType::Color2DMsaa: + return ir.CompositeConstruct(read(0), read(8)); + case AmdGpu::ImageType::Color3D: + case AmdGpu::ImageType::Cube: + return ir.CompositeConstruct(read(0), read(8), read(16)); + default: + UNREACHABLE(); + } + }(); + const IR::F32 bias = inst_info.has_bias ? get_addr_reg(addr_reg++) : IR::F32{}; + const IR::F32 dref = inst_info.is_depth ? get_addr_reg(addr_reg++) : IR::F32{}; + const auto [derivatives_dx, derivatives_dy] = [&] -> std::pair { + if (!inst_info.has_derivatives) { + return {}; + } + switch (image.GetType()) { + case AmdGpu::ImageType::Color1D: + case AmdGpu::ImageType::Color1DArray: + // du/dx, du/dy + addr_reg = addr_reg + 2; + return {get_addr_reg(addr_reg - 2), get_addr_reg(addr_reg - 1)}; + case AmdGpu::ImageType::Color2D: + case AmdGpu::ImageType::Color2DArray: + case AmdGpu::ImageType::Color2DMsaa: + // (du/dx, dv/dx), (du/dy, dv/dy) + addr_reg = addr_reg + 4; + return {ir.CompositeConstruct(get_addr_reg(addr_reg - 4), get_addr_reg(addr_reg - 3)), + ir.CompositeConstruct(get_addr_reg(addr_reg - 2), get_addr_reg(addr_reg - 1))}; + case AmdGpu::ImageType::Color3D: + case AmdGpu::ImageType::Cube: + // (du/dx, dv/dx, dw/dx), (du/dy, dv/dy, dw/dy) + addr_reg = addr_reg + 6; + return {ir.CompositeConstruct(get_addr_reg(addr_reg - 6), get_addr_reg(addr_reg - 5), + get_addr_reg(addr_reg - 4)), + ir.CompositeConstruct(get_addr_reg(addr_reg - 3), get_addr_reg(addr_reg - 2), + get_addr_reg(addr_reg - 1))}; + default: + UNREACHABLE(); + } + }(); + + // Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler + const IR::Value coords = [&] -> IR::Value { + switch (image.GetType()) { + case AmdGpu::ImageType::Color1D: // x + addr_reg = addr_reg + 1; + return get_addr_reg(addr_reg - 1); + case AmdGpu::ImageType::Color1DArray: // x, slice + [[fallthrough]]; + case AmdGpu::ImageType::Color2D: // x, y + addr_reg = addr_reg + 2; + return ir.CompositeConstruct(get_addr_reg(addr_reg - 2), get_addr_reg(addr_reg - 1)); + case AmdGpu::ImageType::Color2DArray: // x, y, slice + [[fallthrough]]; + case AmdGpu::ImageType::Color2DMsaa: // x, y, frag + [[fallthrough]]; + case AmdGpu::ImageType::Color3D: // x, y, z + addr_reg = addr_reg + 3; + return ir.CompositeConstruct(get_addr_reg(addr_reg - 3), get_addr_reg(addr_reg - 2), + get_addr_reg(addr_reg - 1)); + case AmdGpu::ImageType::Cube: // x, y, face + addr_reg = addr_reg + 3; + return PatchCubeCoord(ir, get_addr_reg(addr_reg - 3), get_addr_reg(addr_reg - 2), + get_addr_reg(addr_reg - 1), false, inst_info.is_array); + default: + UNREACHABLE(); + } + }(); + + ASSERT(!inst_info.has_lod || !inst_info.has_lod_clamp); + const bool explicit_lod = inst_info.has_lod || inst_info.force_level0; + const IR::F32 lod = inst_info.has_lod ? get_addr_reg(addr_reg++) + : inst_info.force_level0 ? ir.Imm32(0.0f) + : IR::F32{}; + const IR::F32 lod_clamp = inst_info.has_lod_clamp ? get_addr_reg(addr_reg++) : IR::F32{}; + + auto new_inst = [&] -> IR::Value { + if (inst_info.is_gather) { + if (inst_info.is_depth) { + return ir.ImageGatherDref(handle, coords, offset, dref, inst_info); + } + return ir.ImageGather(handle, coords, offset, inst_info); + } + if (inst_info.has_derivatives) { + return ir.ImageGradient(handle, coords, derivatives_dx, derivatives_dy, offset, + lod_clamp, inst_info); + } + if (inst_info.is_depth) { + if (explicit_lod) { + return ir.ImageSampleDrefExplicitLod(handle, coords, dref, lod, offset, inst_info); + } + return ir.ImageSampleDrefImplicitLod(handle, coords, dref, bias, offset, inst_info); + } + if (explicit_lod) { + return ir.ImageSampleExplicitLod(handle, coords, lod, offset, inst_info); + } + return ir.ImageSampleImplicitLod(handle, coords, bias, offset, inst_info); + }(); + inst.ReplaceUsesWith(new_inst); +} + void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& descriptors) { const auto pred = [](const IR::Inst* inst) -> std::optional { const auto opcode = inst->GetOpcode(); @@ -498,40 +661,18 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip .sgpr_base = tsharp.sgpr_base, .dword_offset = tsharp.dword_offset, .type = type, - .nfmt = static_cast(image.GetNumberFmt()), + .nfmt = image.GetNumberFmt(), .is_storage = is_storage, .is_depth = bool(inst_info.is_depth), .is_atomic = IsImageAtomicInstruction(inst), .is_array = bool(inst_info.is_array), }); - // Read sampler sharp. This doesn't exist for IMAGE_LOAD/IMAGE_STORE instructions - const u32 sampler_binding = [&] { - if (!has_sampler) { - return 0U; - } - const IR::Value& handle = producer->Arg(1); - // Inline sampler resource. - if (handle.IsImmediate()) { - LOG_WARNING(Render_Vulkan, "Inline sampler detected"); - return descriptors.Add(SamplerResource{ - .sgpr_base = std::numeric_limits::max(), - .dword_offset = 0, - .inline_sampler = AmdGpu::Sampler{.raw0 = handle.U32()}, - }); - } - // Normal sampler resource. - const auto ssharp_handle = handle.InstRecursive(); - const auto& [ssharp_ud, disable_aniso] = TryDisableAnisoLod0(ssharp_handle); - const auto ssharp = TrackSharp(ssharp_ud); - return descriptors.Add(SamplerResource{ - .sgpr_base = ssharp.sgpr_base, - .dword_offset = ssharp.dword_offset, - .associated_image = image_binding, - .disable_aniso = disable_aniso, - }); - }(); - image_binding |= (sampler_binding << 16); + // Sample instructions must be resolved into a new instruction using address register data. + if (inst.GetOpcode() == IR::Opcode::ImageSampleRaw) { + PatchImageSampleInstruction(block, inst, info, descriptors, producer, image_binding, image); + return; + } // Patch image handle IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; @@ -568,62 +709,9 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip }(); inst.SetArg(1, coords); - if (inst_info.has_offset) { - // The offsets are six-bit signed integers: X=[5:0], Y=[13:8], and Z=[21:16]. - const u32 arg_pos = ImageOffsetArgumentPosition(inst); - const IR::Value arg = inst.Arg(arg_pos); - ASSERT_MSG(arg.Type() == IR::Type::U32, "Unexpected offset type"); - - const auto read = [&](u32 offset) -> IR::U32 { - if (arg.IsImmediate()) { - const u16 comp = (arg.U32() >> offset) & 0x3F; - return ir.Imm32(s32(comp << 26) >> 26); - } - return ir.BitFieldExtract(IR::U32{arg}, ir.Imm32(offset), ir.Imm32(6), true); - }; - - switch (image.GetType()) { - case AmdGpu::ImageType::Color1D: - case AmdGpu::ImageType::Color1DArray: - inst.SetArg(arg_pos, read(0)); - break; - case AmdGpu::ImageType::Color2D: - case AmdGpu::ImageType::Color2DArray: - inst.SetArg(arg_pos, ir.CompositeConstruct(read(0), read(8))); - break; - case AmdGpu::ImageType::Color3D: - inst.SetArg(arg_pos, ir.CompositeConstruct(read(0), read(8), read(16))); - break; - default: - UNREACHABLE(); - } - } - if (inst_info.has_derivatives) { - ASSERT_MSG(image.GetType() == AmdGpu::ImageType::Color2D || - image.GetType() == AmdGpu::ImageType::Color2DArray, - "User derivatives only supported for 2D images"); - } - if (inst_info.has_lod_clamp) { - const u32 arg_pos = [&]() -> u32 { - switch (inst.GetOpcode()) { - case IR::Opcode::ImageSampleImplicitLod: - return 2; - case IR::Opcode::ImageSampleDrefImplicitLod: - return 3; - default: - break; - } - return inst_info.is_depth ? 5 : 4; - }(); - inst.SetArg(arg_pos, arg); - } - if (inst_info.explicit_lod) { - ASSERT(inst.GetOpcode() == IR::Opcode::ImageFetch || - inst.GetOpcode() == IR::Opcode::ImageSampleExplicitLod || - inst.GetOpcode() == IR::Opcode::ImageSampleDrefExplicitLod); - const u32 pos = inst.GetOpcode() == IR::Opcode::ImageSampleExplicitLod ? 2 : 3; - const IR::Value value = inst_info.force_level0 ? ir.Imm32(0.f) : arg; - inst.SetArg(pos, value); + if (inst_info.has_lod) { + ASSERT(inst.GetOpcode() == IR::Opcode::ImageFetch); + inst.SetArg(3, arg); } } diff --git a/src/shader_recompiler/ir/passes/ring_access_elimination.cpp b/src/shader_recompiler/ir/passes/ring_access_elimination.cpp index 857921b1f..eb1be2967 100644 --- a/src/shader_recompiler/ir/passes/ring_access_elimination.cpp +++ b/src/shader_recompiler/ir/passes/ring_access_elimination.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "shader_recompiler/frontend/translate/translate.h" +#include "shader_recompiler/ir/ir_emitter.h" #include "shader_recompiler/ir/opcodes.h" #include "shader_recompiler/ir/program.h" #include "shader_recompiler/ir/reg.h" @@ -11,6 +11,8 @@ namespace Shader::Optimization { void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtime_info, Stage stage) { + auto& info = program.info; + const auto& ForEachInstruction = [&](auto func) { for (IR::Block* block : program.blocks) { for (IR::Inst& inst : block->Instructions()) { @@ -52,6 +54,9 @@ void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtim break; } case Stage::Geometry: { + const auto& gs_info = runtime_info.gs_info; + info.gs_copy_data = Shader::ParseCopyShader(gs_info.vs_copy); + ForEachInstruction([&](IR::IREmitter& ir, IR::Inst& inst) { const auto opcode = inst.GetOpcode(); switch (opcode) { @@ -81,12 +86,12 @@ void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtim const auto offset = inst.Flags().inst_offset.Value(); const auto data = ir.BitCast(IR::U32{inst.Arg(2)}); - const auto comp_ofs = runtime_info.gs_info.output_vertices * 4u; - const auto output_size = comp_ofs * runtime_info.gs_info.out_vertex_data_size; + const auto comp_ofs = gs_info.output_vertices * 4u; + const auto output_size = comp_ofs * gs_info.out_vertex_data_size; const auto vc_read_ofs = (((offset / comp_ofs) * comp_ofs) % output_size) * 16u; - const auto& it = runtime_info.gs_info.copy_data.attr_map.find(vc_read_ofs); - ASSERT(it != runtime_info.gs_info.copy_data.attr_map.cend()); + const auto& it = info.gs_copy_data.attr_map.find(vc_read_ofs); + ASSERT(it != info.gs_copy_data.attr_map.cend()); const auto& [attr, comp] = it->second; inst.ReplaceOpcode(IR::Opcode::SetAttribute); diff --git a/src/shader_recompiler/ir/reg.h b/src/shader_recompiler/ir/reg.h index 9ec77e5f0..3004d2b86 100644 --- a/src/shader_recompiler/ir/reg.h +++ b/src/shader_recompiler/ir/reg.h @@ -10,20 +10,6 @@ namespace Shader::IR { -enum class FpRoundMode : u32 { - NearestEven = 0, - PlusInf = 1, - MinInf = 2, - ToZero = 3, -}; - -enum class FpDenormMode : u32 { - InOutFlush = 0, - InAllowOutFlush = 1, - InFlushOutAllow = 2, - InOutAllow = 3, -}; - enum class FloatClassFunc : u32 { SignalingNan = 1 << 0, QuietNan = 1 << 1, @@ -38,27 +24,23 @@ enum class FloatClassFunc : u32 { NaN = SignalingNan | QuietNan, Infinity = PositiveInfinity | NegativeInfinity, + Finite = NegativeNormal | NegativeDenorm | NegativeZero | PositiveNormal | PositiveDenorm | + PositiveZero, }; DECLARE_ENUM_FLAG_OPERATORS(FloatClassFunc) -union Mode { - BitField<0, 4, FpRoundMode> fp_round; - BitField<4, 2, FpDenormMode> fp_denorm_single; - BitField<6, 2, FpDenormMode> fp_denorm_double; - BitField<8, 1, u32> dx10_clamp; -}; - union TextureInstInfo { u32 raw; BitField<0, 1, u32> is_depth; BitField<1, 1, u32> has_bias; BitField<2, 1, u32> has_lod_clamp; BitField<3, 1, u32> force_level0; - BitField<4, 1, u32> explicit_lod; + BitField<4, 1, u32> has_lod; BitField<5, 1, u32> has_offset; BitField<6, 2, u32> gather_comp; BitField<8, 1, u32> has_derivatives; BitField<9, 1, u32> is_array; + BitField<10, 1, u32> is_gather; }; union BufferInstInfo { diff --git a/src/shader_recompiler/ir/type.cpp b/src/shader_recompiler/ir/type.cpp index 9d303b4db..08157f108 100644 --- a/src/shader_recompiler/ir/type.cpp +++ b/src/shader_recompiler/ir/type.cpp @@ -9,10 +9,9 @@ namespace Shader::IR { std::string NameOf(Type type) { static constexpr std::array names{ - "Opaque", "Label", "Reg", "Pred", "Attribute", "U1", "U8", "U16", "U32", - "U64", "F16", "F32", "F64", "U32x2", "U32x3", "U32x4", "F16x2", "F16x3", - "F16x4", "F32x2", "F32x3", "F32x4", "F64x2", "F64x3", "F64x4", - }; + "Opaque", "Label", "Reg", "Pred", "Attribute", "U1", "U8", "U16", "U32", + "U64", "F16", "F32", "F64", "U32x2", "U32x3", "U32x4", "F16x2", "F16x3", + "F16x4", "F32x2", "F32x3", "F32x4", "F64x2", "F64x3", "F64x4", "StringLiteral"}; const size_t bits{static_cast(type)}; if (bits == 0) { return "Void"; diff --git a/src/shader_recompiler/ir/type.h b/src/shader_recompiler/ir/type.h index d7f47e1de..ec855a77e 100644 --- a/src/shader_recompiler/ir/type.h +++ b/src/shader_recompiler/ir/type.h @@ -36,6 +36,7 @@ enum class Type { F64x2 = 1 << 22, F64x3 = 1 << 23, F64x4 = 1 << 24, + StringLiteral = 1 << 25, }; DECLARE_ENUM_FLAG_OPERATORS(Type) diff --git a/src/shader_recompiler/ir/value.cpp b/src/shader_recompiler/ir/value.cpp index 86e5dd141..cf7a70f76 100644 --- a/src/shader_recompiler/ir/value.cpp +++ b/src/shader_recompiler/ir/value.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include "shader_recompiler/ir/value.h" namespace Shader::IR { @@ -27,6 +28,8 @@ Value::Value(u64 value) noexcept : type{Type::U64}, imm_u64{value} {} Value::Value(f64 value) noexcept : type{Type::F64}, imm_f64{value} {} +Value::Value(const char* value) noexcept : type{Type::StringLiteral}, string_literal{value} {} + IR::Type Value::Type() const noexcept { if (IsPhi()) { // The type of a phi node is stored in its flags @@ -69,6 +72,8 @@ bool Value::operator==(const Value& other) const { case Type::U64: case Type::F64: return imm_u64 == other.imm_u64; + case Type::StringLiteral: + return std::string_view(string_literal) == other.string_literal; case Type::U32x2: case Type::U32x3: case Type::U32x4: diff --git a/src/shader_recompiler/ir/value.h b/src/shader_recompiler/ir/value.h index db939eaa5..a282b9168 100644 --- a/src/shader_recompiler/ir/value.h +++ b/src/shader_recompiler/ir/value.h @@ -39,6 +39,7 @@ public: explicit Value(f32 value) noexcept; explicit Value(u64 value) noexcept; explicit Value(f64 value) noexcept; + explicit Value(const char* value) noexcept; [[nodiscard]] bool IsIdentity() const noexcept; [[nodiscard]] bool IsPhi() const noexcept; @@ -60,6 +61,7 @@ public: [[nodiscard]] f32 F32() const; [[nodiscard]] u64 U64() const; [[nodiscard]] f64 F64() const; + [[nodiscard]] const char* StringLiteral() const; [[nodiscard]] bool operator==(const Value& other) const; [[nodiscard]] bool operator!=(const Value& other) const; @@ -78,6 +80,7 @@ private: f32 imm_f32; u64 imm_u64; f64 imm_f64; + const char* string_literal; }; }; static_assert(static_cast(IR::Type::Void) == 0, "memset relies on IR::Type being zero"); @@ -206,7 +209,7 @@ private: union { NonTriviallyDummy dummy{}; boost::container::small_vector, 2> phi_args; - std::array args; + std::array args; }; }; static_assert(sizeof(Inst) <= 128, "Inst size unintentionally increased"); @@ -348,6 +351,14 @@ inline f64 Value::F64() const { return imm_f64; } +inline const char* Value::StringLiteral() const { + if (IsIdentity()) { + return inst->Arg(0).StringLiteral(); + } + DEBUG_ASSERT(type == Type::StringLiteral); + return string_literal; +} + [[nodiscard]] inline bool IsPhi(const Inst& inst) { return inst.GetOpcode() == Opcode::Phi; } diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index badd54554..bbda731e0 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -19,13 +19,8 @@ struct Profile { bool support_float_controls{}; bool support_separate_denorm_behavior{}; bool support_separate_rounding_mode{}; - bool support_fp16_denorm_preserve{}; bool support_fp32_denorm_preserve{}; - bool support_fp16_denorm_flush{}; bool support_fp32_denorm_flush{}; - bool support_fp16_signed_zero_nan_preserve{}; - bool support_fp32_signed_zero_nan_preserve{}; - bool support_fp64_signed_zero_nan_preserve{}; bool support_explicit_workgroup_layout{}; bool has_broken_spirv_clamp{}; bool lower_left_origin_mode{}; diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 8c0838c96..03936f3a8 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -4,11 +4,9 @@ #pragma once #include +#include #include - -#include "common/assert.h" #include "common/types.h" -#include "frontend/copy_shader.h" #include "video_core/amdgpu/types.h" namespace Shader { @@ -62,7 +60,8 @@ enum class VsOutput : u8 { using VsOutputMap = std::array; struct VertexRuntimeInfo { - boost::container::static_vector outputs; + u32 num_outputs; + std::array outputs; bool emulate_depth_negative_one_to_one{}; bool operator==(const VertexRuntimeInfo& other) const noexcept { @@ -79,13 +78,13 @@ struct GeometryRuntimeInfo { u32 out_vertex_data_size{}; AmdGpu::PrimitiveType in_primitive; GsOutputPrimTypes out_primitive; - CopyShaderData copy_data; + std::span vs_copy; + u64 vs_copy_hash; bool operator==(const GeometryRuntimeInfo& other) const noexcept { return num_invocations && other.num_invocations && output_vertices == other.output_vertices && in_primitive == other.in_primitive && - std::ranges::equal(out_primitive, other.out_primitive) && - std::ranges::equal(copy_data.attr_map, other.copy_data.attr_map); + std::ranges::equal(out_primitive, other.out_primitive); } }; @@ -106,7 +105,8 @@ struct FragmentRuntimeInfo { auto operator<=>(const PsInput&) const noexcept = default; }; - boost::container::static_vector inputs; + u32 num_inputs; + std::array inputs; struct PsColorBuffer { AmdGpu::NumberFormat num_format; MrtSwizzle mrt_swizzle; @@ -117,7 +117,9 @@ struct FragmentRuntimeInfo { bool operator==(const FragmentRuntimeInfo& other) const noexcept { return std::ranges::equal(color_buffers, other.color_buffers) && - std::ranges::equal(inputs, other.inputs); + num_inputs == other.num_inputs && + std::ranges::equal(inputs.begin(), inputs.begin() + num_inputs, other.inputs.begin(), + other.inputs.begin() + num_inputs); } }; @@ -141,13 +143,20 @@ struct RuntimeInfo { u32 num_user_data; u32 num_input_vgprs; u32 num_allocated_vgprs; - ExportRuntimeInfo es_info; - VertexRuntimeInfo vs_info; - GeometryRuntimeInfo gs_info; - FragmentRuntimeInfo fs_info; - ComputeRuntimeInfo cs_info; + AmdGpu::FpDenormMode fp_denorm_mode32; + AmdGpu::FpRoundMode fp_round_mode32; + union { + ExportRuntimeInfo es_info; + VertexRuntimeInfo vs_info; + GeometryRuntimeInfo gs_info; + FragmentRuntimeInfo fs_info; + ComputeRuntimeInfo cs_info; + }; - RuntimeInfo(Stage stage_) : stage{stage_} {} + RuntimeInfo(Stage stage_) { + memset(this, 0, sizeof(*this)); + stage = stage_; + } bool operator==(const RuntimeInfo& other) const noexcept { switch (stage) { diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 47eecf699..77bbdb967 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -6,7 +6,9 @@ #include "common/debug.h" #include "common/polyfill_thread.h" #include "common/thread.h" +#include "core/debug_state.h" #include "core/libraries/videoout/driver.h" +#include "core/memory.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/amdgpu/pm4_cmds.h" #include "video_core/renderdoc.h" @@ -195,6 +197,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(dcb.data()); while (!dcb.empty()) { const auto* header = reinterpret_cast(dcb.data()); const u32 type = header->type; @@ -234,6 +237,17 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanheader.count.Value() * 2; + const std::string_view label{reinterpret_cast(&nop->data_block[1]), + marker_sz}; + const u32 color = *reinterpret_cast( + reinterpret_cast(&nop->data_block[1]) + marker_sz); + if (rasterizer) { + rasterizer->ScopedMarkerInsertColor(label, color); + } + break; + } case PM4CmdNop::PayloadType::DebugMarkerPop: { if (rasterizer) { rasterizer->ScopeMarkerEnd(); @@ -356,6 +370,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanindex_base_hi); regs.num_indices = draw_index->index_count; regs.draw_initiator = draw_index->draw_initiator; + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs); + } if (rasterizer) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndex2", cmd_address)); @@ -370,6 +387,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanmax_size; regs.num_indices = draw_index_off->index_count; regs.draw_initiator = draw_index_off->draw_initiator; + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs); + } if (rasterizer) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin( @@ -383,6 +403,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); regs.num_indices = draw_index->index_count; regs.draw_initiator = draw_index->draw_initiator; + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs); + } if (rasterizer) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndexAuto", cmd_address)); @@ -396,6 +419,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spandata_offset; const auto ib_address = mapped_queues[GfxQueueId].indirect_args_addr; const auto size = sizeof(PM4CmdDrawIndirect::DrawInstancedArgs); + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs); + } if (rasterizer) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndirect", cmd_address)); @@ -410,6 +436,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spandata_offset; const auto ib_address = mapped_queues[GfxQueueId].indirect_args_addr; const auto size = sizeof(PM4CmdDrawIndexIndirect::DrawIndexInstancedArgs); + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs); + } if (rasterizer) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin( @@ -425,6 +454,10 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spandim_y; regs.cs_program.dim_z = dispatch_direct->dim_z; regs.cs_program.dispatch_initiator = dispatch_direct->dispatch_initiator; + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs, + true); + } if (rasterizer && (regs.cs_program.dispatch_initiator & 1)) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:Dispatch", cmd_address)); @@ -439,6 +472,10 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spandata_offset; const auto ib_address = mapped_queues[GfxQueueId].indirect_args_addr; const auto size = sizeof(PM4CmdDispatchIndirect::GroupDimensions); + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs, + true); + } if (rasterizer && (regs.cs_program.dispatch_initiator & 1)) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin( @@ -477,7 +514,12 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - event_eos->SignalFence(); + event_eos->SignalFence([](void* address, u64 data, u32 num_bytes) { + auto* memory = Core::Memory::Instance(); + if (!memory->TryWriteBacking(address, &data, num_bytes)) { + memcpy(address, &data, num_bytes); + } + }); if (event_eos->command == PM4CmdEventWriteEos::Command::GdsStore) { ASSERT(event_eos->size == 1); if (rasterizer) { @@ -490,13 +532,42 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - event_eop->SignalFence(); + event_eop->SignalFence([](void* address, u64 data, u32 num_bytes) { + auto* memory = Core::Memory::Instance(); + if (!memory->TryWriteBacking(address, &data, num_bytes)) { + memcpy(address, &data, num_bytes); + } + }); break; } case PM4ItOpcode::DmaData: { const auto* dma_data = reinterpret_cast(header); + if (dma_data->dst_addr_lo == 0x3022C) { + break; + } if (dma_data->src_sel == DmaDataSrc::Data && dma_data->dst_sel == DmaDataDst::Gds) { - rasterizer->InlineDataToGds(dma_data->dst_addr_lo, dma_data->data); + rasterizer->InlineData(dma_data->dst_addr_lo, &dma_data->data, sizeof(u32), + true); + } else if (dma_data->src_sel == DmaDataSrc::Memory && + dma_data->dst_sel == DmaDataDst::Gds) { + rasterizer->InlineData(dma_data->dst_addr_lo, + dma_data->SrcAddress(), + dma_data->NumBytes(), true); + } else if (dma_data->src_sel == DmaDataSrc::Data && + dma_data->dst_sel == DmaDataDst::Memory) { + rasterizer->InlineData(dma_data->DstAddress(), &dma_data->data, + sizeof(u32), false); + } else if (dma_data->src_sel == DmaDataSrc::Gds && + dma_data->dst_sel == DmaDataDst::Memory) { + LOG_WARNING(Render_Vulkan, "GDS memory read"); + } else if (dma_data->src_sel == DmaDataSrc::Memory && + dma_data->dst_sel == DmaDataDst::Memory) { + rasterizer->InlineData(dma_data->DstAddress(), + dma_data->SrcAddress(), + dma_data->NumBytes(), false); + } else { + UNREACHABLE_MSG("WriteData src_sel = {}, dst_sel = {}", + u32(dma_data->src_sel.Value()), u32(dma_data->dst_sel.Value())); } break; } @@ -574,6 +645,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span acb, int vqid) { TracyFiberEnter(acb_task_name); + auto base_addr = reinterpret_cast(acb.data()); while (!acb.empty()) { const auto* header = reinterpret_cast(acb.data()); const u32 type = header->type; @@ -603,6 +675,35 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, int vqid) { }; break; } + case PM4ItOpcode::DmaData: { + const auto* dma_data = reinterpret_cast(header); + if (dma_data->dst_addr_lo == 0x3022C) { + break; + } + if (dma_data->src_sel == DmaDataSrc::Data && dma_data->dst_sel == DmaDataDst::Gds) { + rasterizer->InlineData(dma_data->dst_addr_lo, &dma_data->data, sizeof(u32), true); + } else if (dma_data->src_sel == DmaDataSrc::Memory && + dma_data->dst_sel == DmaDataDst::Gds) { + rasterizer->InlineData(dma_data->dst_addr_lo, dma_data->SrcAddress(), + dma_data->NumBytes(), true); + } else if (dma_data->src_sel == DmaDataSrc::Data && + dma_data->dst_sel == DmaDataDst::Memory) { + rasterizer->InlineData(dma_data->DstAddress(), &dma_data->data, sizeof(u32), + false); + } else if (dma_data->src_sel == DmaDataSrc::Gds && + dma_data->dst_sel == DmaDataDst::Memory) { + LOG_WARNING(Render_Vulkan, "GDS memory read"); + } else if (dma_data->src_sel == DmaDataSrc::Memory && + dma_data->dst_sel == DmaDataDst::Memory) { + rasterizer->InlineData(dma_data->DstAddress(), + dma_data->SrcAddress(), dma_data->NumBytes(), + false); + } else { + UNREACHABLE_MSG("WriteData src_sel = {}, dst_sel = {}", + u32(dma_data->src_sel.Value()), u32(dma_data->dst_sel.Value())); + } + break; + } case PM4ItOpcode::AcquireMem: { break; } @@ -621,6 +722,9 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, int vqid) { regs.cs_program.dim_y = dispatch_direct->dim_y; regs.cs_program.dim_z = dispatch_direct->dim_z; regs.cs_program.dispatch_initiator = dispatch_direct->dispatch_initiator; + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs, true); + } if (rasterizer && (regs.cs_program.dispatch_initiator & 1)) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin(fmt::format("acb[{}]:{}:Dispatch", vqid, cmd_address)); diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index 508420bca..a4cf79334 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -92,6 +92,12 @@ struct Liverpool { union { BitField<0, 6, u64> num_vgprs; BitField<6, 4, u64> num_sgprs; + BitField<10, 2, u64> priority; + BitField<12, 2, FpRoundMode> fp_round_mode32; + BitField<14, 2, FpRoundMode> fp_round_mode64; + BitField<16, 2, FpDenormMode> fp_denorm_mode32; + BitField<18, 2, FpDenormMode> fp_denorm_mode64; + BitField<12, 8, u64> float_mode; BitField<24, 2, u64> vgpr_comp_cnt; // SPI provided per-thread inputs BitField<33, 5, u64> num_user_regs; } settings; @@ -766,6 +772,8 @@ struct Liverpool { BitField<27, 1, u32> fmask_compress_1frag_only; BitField<28, 1, u32> dcc_enable; BitField<29, 1, u32> cmask_addr_type; + + u32 u32all; } info; union Color0Attrib { BitField<0, 5, TilingMode> tile_mode_index; @@ -774,6 +782,8 @@ struct Liverpool { BitField<12, 3, u32> num_samples_log2; BitField<15, 2, u32> num_fragments_log2; BitField<17, 1, u32> force_dst_alpha_1; + + u32 u32all; } attrib; INSERT_PADDING_WORDS(1); u32 cmask_base_address; @@ -929,7 +939,7 @@ struct Liverpool { BitField<5, 1, u32> gs_en; BitField<6, 1, u32> vs_en; - bool IsStageEnabled(u32 stage) { + bool IsStageEnabled(u32 stage) const { switch (stage) { case 0: case 1: diff --git a/src/video_core/amdgpu/pixel_format.cpp b/src/video_core/amdgpu/pixel_format.cpp index 6744891a6..b13fc2d11 100644 --- a/src/video_core/amdgpu/pixel_format.cpp +++ b/src/video_core/amdgpu/pixel_format.cpp @@ -144,10 +144,10 @@ static constexpr std::array component_bits = { std::array{8, 8, 0, 0}, // 3 Format8_8 std::array{32, 0, 0, 0}, // 4 Format32 std::array{16, 16, 0, 0}, // 5 Format16_16 - std::array{10, 11, 11, 0}, // 6 Format10_11_11 - std::array{11, 11, 10, 0}, // 7 Format11_11_10 - std::array{10, 10, 10, 2}, // 8 Format10_10_10_2 - std::array{2, 10, 10, 10}, // 9 Format2_10_10_10 + std::array{11, 11, 10, 0}, // 6 Format10_11_11 + std::array{10, 11, 11, 0}, // 7 Format11_11_10 + std::array{2, 10, 10, 10}, // 8 Format10_10_10_2 + std::array{10, 10, 10, 2}, // 9 Format2_10_10_10 std::array{8, 8, 8, 8}, // 10 Format8_8_8_8 std::array{32, 32, 0, 0}, // 11 Format32_32 std::array{16, 16, 16, 16}, // 12 Format16_16_16_16 @@ -155,12 +155,12 @@ static constexpr std::array component_bits = { std::array{32, 32, 32, 32}, // 14 Format32_32_32_32 std::array{0, 0, 0, 0}, // 15 std::array{5, 6, 5, 0}, // 16 Format5_6_5 - std::array{1, 5, 5, 5}, // 17 Format1_5_5_5 - std::array{5, 5, 5, 1}, // 18 Format5_5_5_1 + std::array{5, 5, 5, 1}, // 17 Format1_5_5_5 + std::array{1, 5, 5, 5}, // 18 Format5_5_5_1 std::array{4, 4, 4, 4}, // 19 Format4_4_4_4 - std::array{8, 24, 0, 0}, // 20 Format8_24 - std::array{24, 8, 0, 0}, // 21 Format24_8 - std::array{24, 8, 0, 0}, // 22 FormatX24_8_32 + std::array{24, 8, 0, 0}, // 20 Format8_24 + std::array{8, 24, 0, 0}, // 21 Format24_8 + std::array{8, 24, 0, 0}, // 22 FormatX24_8_32 std::array{0, 0, 0, 0}, // 23 std::array{0, 0, 0, 0}, // 24 std::array{0, 0, 0, 0}, // 25 @@ -197,10 +197,10 @@ static constexpr std::array component_offset = { std::array{0, 8, -1, -1}, // 3 Format8_8 std::array{0, -1, -1, -1}, // 4 Format32 std::array{0, 16, -1, -1}, // 5 Format16_16 - std::array{0, 10, 21, -1}, // 6 Format10_11_11 - std::array{0, 11, 22, -1}, // 7 Format11_11_10 - std::array{0, 10, 20, 30}, // 8 Format10_10_10_2 - std::array{0, 2, 12, 22}, // 9 Format2_10_10_10 + std::array{0, 11, 22, -1}, // 6 Format10_11_11 + std::array{0, 10, 21, -1}, // 7 Format11_11_10 + std::array{0, 2, 12, 22}, // 8 Format10_10_10_2 + std::array{0, 10, 20, 30}, // 9 Format2_10_10_10 std::array{0, 8, 16, 24}, // 10 Format8_8_8_8 std::array{0, 32, -1, -1}, // 11 Format32_32 std::array{0, 16, 32, 48}, // 12 Format16_16_16_16 @@ -208,12 +208,12 @@ static constexpr std::array component_offset = { std::array{0, 32, 64, 96}, // 14 Format32_32_32_32 std::array{-1, -1, -1, -1}, // 15 std::array{0, 5, 11, -1}, // 16 Format5_6_5 - std::array{0, 1, 6, 11}, // 17 Format1_5_5_5 - std::array{0, 5, 10, 15}, // 18 Format5_5_5_1 + std::array{0, 5, 10, 15}, // 17 Format1_5_5_5 + std::array{0, 1, 6, 11}, // 18 Format5_5_5_1 std::array{0, 4, 8, 12}, // 19 Format4_4_4_4 - std::array{0, 8, -1, -1}, // 20 Format8_24 - std::array{0, 24, -1, -1}, // 21 Format24_8 - std::array{0, 24, -1, -1}, // 22 FormatX24_8_32 + std::array{0, 24, -1, -1}, // 20 Format8_24 + std::array{0, 8, -1, -1}, // 21 Format24_8 + std::array{0, 8, -1, -1}, // 22 FormatX24_8_32 std::array{-1, -1, -1, -1}, // 23 std::array{-1, -1, -1, -1}, // 24 std::array{-1, -1, -1, -1}, // 25 diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index b9fbfcb89..a956b030d 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -213,6 +213,7 @@ struct PM4CmdNop { enum PayloadType : u32 { DebugMarkerPush = 0x68750001u, ///< Begin of GPU event scope DebugMarkerPop = 0x68750002u, ///< End of GPU event scope + DebugSetMarker = 0x68750003u, ///< Set GPU event marker SetVsharpInUdata = 0x68750004u, ///< Indicates that V# will be set in the next packet SetTsharpInUdata = 0x68750005u, ///< Indicates that T# will be set in the next packet SetSsharpInUdata = 0x68750006u, ///< Indicates that S# will be set in the next packet @@ -312,25 +313,26 @@ struct PM4CmdEventWriteEop { return data_lo | u64(data_hi) << 32; } - void SignalFence() const { + void SignalFence(auto&& write_mem) const { + u32* address = Address(); switch (data_sel.Value()) { case DataSelect::None: { break; } case DataSelect::Data32Low: { - *Address() = DataDWord(); + write_mem(address, DataDWord(), sizeof(u32)); break; } case DataSelect::Data64: { - *Address() = DataQWord(); + write_mem(address, DataQWord(), sizeof(u64)); break; } case DataSelect::GpuClock64: { - *Address() = GetGpuClock64(); + write_mem(address, GetGpuClock64(), sizeof(u64)); break; } case DataSelect::PerfCounter: { - *Address() = Common::FencedRDTSC(); + write_mem(address, Common::FencedRDTSC(), sizeof(u64)); break; } default: { @@ -400,6 +402,20 @@ struct PM4DmaData { u32 dst_addr_lo; u32 dst_addr_hi; u32 command; + + template + T SrcAddress() const { + return std::bit_cast(src_addr_lo | u64(src_addr_hi) << 32); + } + + template + T DstAddress() const { + return std::bit_cast(dst_addr_lo | u64(dst_addr_hi) << 32); + } + + u32 NumBytes() const noexcept { + return command & 0x1fffff; + } }; struct PM4CmdWaitRegMem { @@ -431,7 +447,7 @@ struct PM4CmdWaitRegMem { template T Address() const { - return reinterpret_cast((uintptr_t(poll_addr_hi) << 32) | poll_addr_lo); + return std::bit_cast((uintptr_t(poll_addr_hi) << 32) | poll_addr_lo); } bool Test() const { @@ -533,11 +549,11 @@ struct PM4CmdEventWriteEos { return this->data; } - void SignalFence() const { + void SignalFence(auto&& write_mem) const { const auto cmd = command.Value(); switch (cmd) { case Command::SignalFence: { - *Address() = DataDWord(); + write_mem(Address(), DataDWord(), sizeof(u32)); break; } case Command::GdsStore: { diff --git a/src/video_core/amdgpu/types.h b/src/video_core/amdgpu/types.h index 8cc023a79..6b95ed910 100644 --- a/src/video_core/amdgpu/types.h +++ b/src/video_core/amdgpu/types.h @@ -7,6 +7,20 @@ namespace AmdGpu { +enum class FpRoundMode : u32 { + NearestEven = 0, + PlusInf = 1, + MinInf = 2, + ToZero = 3, +}; + +enum class FpDenormMode : u32 { + InOutFlush = 0, + InAllowOutFlush = 1, + InFlushOutAllow = 2, + InOutAllow = 3, +}; + // See `VGT_PRIMITIVE_TYPE` description in [Radeon Sea Islands 3D/Compute Register Reference Guide] enum class PrimitiveType : u32 { None = 0, @@ -103,4 +117,4 @@ enum class NumberFormat : u32 { Ubscaled = 13, }; -} // namespace AmdGpu \ No newline at end of file +} // namespace AmdGpu diff --git a/src/video_core/buffer_cache/buffer.h b/src/video_core/buffer_cache/buffer.h index 403d4ed85..f67278f64 100644 --- a/src/video_core/buffer_cache/buffer.h +++ b/src/video_core/buffer_cache/buffer.h @@ -142,6 +142,7 @@ public: VAddr cpu_addr = 0; bool is_picked{}; bool is_coherent{}; + bool is_deleted{}; int stream_score = 0; size_t size_bytes = 0; std::span mapped_data; diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index 2b43188cf..e0754fe6a 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -20,7 +20,7 @@ static constexpr size_t StagingBufferSize = 1_GB; static constexpr size_t UboStreamBufferSize = 64_MB; BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, - const AmdGpu::Liverpool* liverpool_, TextureCache& texture_cache_, + AmdGpu::Liverpool* liverpool_, TextureCache& texture_cache_, PageManager& tracker_) : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, texture_cache{texture_cache_}, tracker{tracker_}, @@ -70,11 +70,10 @@ void BufferCache::InvalidateMemory(VAddr device_addr, u64 size) { void BufferCache::DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size) { boost::container::small_vector copies; u64 total_size_bytes = 0; - u64 largest_copy = 0; memory_tracker.ForEachDownloadRange( device_addr, size, [&](u64 device_addr_out, u64 range_size) { const VAddr buffer_addr = buffer.CpuAddr(); - const auto add_download = [&](VAddr start, VAddr end, u64) { + const auto add_download = [&](VAddr start, VAddr end) { const u64 new_offset = start - buffer_addr; const u64 new_size = end - start; copies.push_back(vk::BufferCopy{ @@ -82,12 +81,10 @@ void BufferCache::DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 si .dstOffset = total_size_bytes, .size = new_size, }); - // Align up to avoid cache conflicts - constexpr u64 align = 64ULL; - constexpr u64 mask = ~(align - 1ULL); - total_size_bytes += (new_size + align - 1) & mask; - largest_copy = std::max(largest_copy, new_size); + total_size_bytes += new_size; }; + gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, add_download); + gpu_modified_ranges.Subtract(device_addr_out, range_size); }); if (total_size_bytes == 0) { return; @@ -181,6 +178,9 @@ bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) { .divisor = 1, }); } + if (ranges.empty()) { + return false; + } std::ranges::sort(ranges, [](const BufferRange& lhv, const BufferRange& rhv) { return lhv.base_address < rhv.base_address; @@ -269,48 +269,62 @@ u32 BufferCache::BindIndexBuffer(bool& is_indexed, u32 index_offset) { return regs.num_indices; } -void BufferCache::InlineDataToGds(u32 gds_offset, u32 value) { - ASSERT_MSG(gds_offset % 4 == 0, "GDS offset must be dword aligned"); +void BufferCache::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) { + ASSERT_MSG(address % 4 == 0, "GDS offset must be dword aligned"); + if (!is_gds && !IsRegionRegistered(address, num_bytes)) { + memcpy(std::bit_cast(address), value, num_bytes); + return; + } scheduler.EndRendering(); const auto cmdbuf = scheduler.CommandBuffer(); + const Buffer* buffer = [&] { + if (is_gds) { + return &gds_buffer; + } + const BufferId buffer_id = FindBuffer(address, num_bytes); + return &slot_buffers[buffer_id]; + }(); const vk::BufferMemoryBarrier2 buf_barrier = { .srcStageMask = vk::PipelineStageFlagBits2::eTransfer, .srcAccessMask = vk::AccessFlagBits2::eTransferWrite, .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, .dstAccessMask = vk::AccessFlagBits2::eMemoryRead, - .buffer = gds_buffer.Handle(), - .offset = gds_offset, - .size = sizeof(u32), + .buffer = buffer->Handle(), + .offset = buffer->Offset(address), + .size = num_bytes, }; cmdbuf.pipelineBarrier2(vk::DependencyInfo{ .dependencyFlags = vk::DependencyFlagBits::eByRegion, .bufferMemoryBarrierCount = 1, .pBufferMemoryBarriers = &buf_barrier, }); - cmdbuf.updateBuffer(gds_buffer.Handle(), gds_offset, sizeof(u32), &value); + cmdbuf.updateBuffer(buffer->Handle(), buf_barrier.offset, num_bytes, value); } std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, bool is_written, - bool is_texel_buffer) { + bool is_texel_buffer, BufferId buffer_id) { + // For small uniform buffers that have not been modified by gpu + // use device local stream buffer to reduce renderpass breaks. static constexpr u64 StreamThreshold = CACHING_PAGESIZE; const bool is_gpu_dirty = memory_tracker.IsRegionGpuModified(device_addr, size); if (!is_written && size <= StreamThreshold && !is_gpu_dirty) { - // For small uniform buffers that have not been modified by gpu - // use device local stream buffer to reduce renderpass breaks. const u64 offset = stream_buffer.Copy(device_addr, size, instance.UniformMinAlignment()); return {&stream_buffer, offset}; } - const BufferId buffer_id = FindBuffer(device_addr, size); + if (!buffer_id || slot_buffers[buffer_id].is_deleted) { + buffer_id = FindBuffer(device_addr, size); + } Buffer& buffer = slot_buffers[buffer_id]; SynchronizeBuffer(buffer, device_addr, size, is_texel_buffer); if (is_written) { memory_tracker.MarkRegionAsGpuModified(device_addr, size); + gpu_modified_ranges.Add(device_addr, size); } return {&buffer, buffer.Offset(device_addr)}; } -std::pair BufferCache::ObtainTempBuffer(VAddr gpu_addr, u32 size) { +std::pair BufferCache::ObtainViewBuffer(VAddr gpu_addr, u32 size) { const u64 page = gpu_addr >> CACHING_PAGEBITS; const BufferId buffer_id = page_table[page]; if (buffer_id) { @@ -474,7 +488,7 @@ void BufferCache::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlagBits::eByRegion, WRITE_BARRIER, {}, {}); - DeleteBuffer(overlap_id, true); + DeleteBuffer(overlap_id); } BufferId BufferCache::CreateBuffer(VAddr device_addr, u32 wanted_size) { @@ -529,7 +543,7 @@ void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, u64 total_size_bytes = 0; u64 largest_copy = 0; VAddr buffer_start = buffer.CpuAddr(); - const auto add_copy = [&](VAddr device_addr_out, u64 range_size) { + memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) { copies.push_back(vk::BufferCopy{ .srcOffset = total_size_bytes, .dstOffset = device_addr_out - buffer_start, @@ -537,11 +551,6 @@ void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, }); total_size_bytes += range_size; largest_copy = std::max(largest_copy, range_size); - }; - memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) { - add_copy(device_addr_out, range_size); - // Prevent uploading to gpu modified regions. - // gpu_modified_ranges.ForEachNotInRange(device_addr_out, range_size, add_copy); }); SCOPE_EXIT { if (is_texel_buffer) { @@ -657,14 +666,11 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, return true; } -void BufferCache::DeleteBuffer(BufferId buffer_id, bool do_not_mark) { - // Mark the whole buffer as CPU written to stop tracking CPU writes - if (!do_not_mark) { - Buffer& buffer = slot_buffers[buffer_id]; - memory_tracker.MarkRegionAsCpuModified(buffer.CpuAddr(), buffer.SizeBytes()); - } +void BufferCache::DeleteBuffer(BufferId buffer_id) { + Buffer& buffer = slot_buffers[buffer_id]; Unregister(buffer_id); scheduler.DeferOperation([this, buffer_id] { slot_buffers.erase(buffer_id); }); + buffer.is_deleted = true; } } // namespace VideoCore diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 76309363a..6710c8615 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -12,6 +12,7 @@ #include "common/types.h" #include "video_core/buffer_cache/buffer.h" #include "video_core/buffer_cache/memory_tracker_base.h" +#include "video_core/buffer_cache/range_set.h" #include "video_core/multi_level_page_table.h" namespace AmdGpu { @@ -53,7 +54,7 @@ public: public: explicit BufferCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, - const AmdGpu::Liverpool* liverpool, TextureCache& texture_cache, + AmdGpu::Liverpool* liverpool, TextureCache& texture_cache, PageManager& tracker); ~BufferCache(); @@ -80,15 +81,16 @@ public: /// Bind host index buffer for the current draw. u32 BindIndexBuffer(bool& is_indexed, u32 index_offset); - /// Writes a value to GDS buffer. - void InlineDataToGds(u32 gds_offset, u32 value); + /// Writes a value to GPU buffer. + void InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds); /// Obtains a buffer for the specified region. [[nodiscard]] std::pair ObtainBuffer(VAddr gpu_addr, u32 size, bool is_written, - bool is_texel_buffer = false); + bool is_texel_buffer = false, + BufferId buffer_id = {}); - /// Obtains a temporary buffer for usage in texture cache. - [[nodiscard]] std::pair ObtainTempBuffer(VAddr gpu_addr, u32 size); + /// Attempts to obtain a buffer without modifying the cache contents. + [[nodiscard]] std::pair ObtainViewBuffer(VAddr gpu_addr, u32 size); /// Return true when a region is registered on the cache [[nodiscard]] bool IsRegionRegistered(VAddr addr, size_t size); @@ -99,6 +101,8 @@ public: /// Return true when a CPU region is modified from the GPU [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size); + [[nodiscard]] BufferId FindBuffer(VAddr device_addr, u32 size); + private: template void ForEachBufferInRange(VAddr device_addr, u64 size, Func&& func) { @@ -119,8 +123,6 @@ private: void DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size); - [[nodiscard]] BufferId FindBuffer(VAddr device_addr, u32 size); - [[nodiscard]] OverlapResult ResolveOverlaps(VAddr device_addr, u32 wanted_size); void JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, bool accumulate_stream_score); @@ -138,11 +140,11 @@ private: bool SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size); - void DeleteBuffer(BufferId buffer_id, bool do_not_mark = false); + void DeleteBuffer(BufferId buffer_id); const Vulkan::Instance& instance; Vulkan::Scheduler& scheduler; - const AmdGpu::Liverpool* liverpool; + AmdGpu::Liverpool* liverpool; TextureCache& texture_cache; PageManager& tracker; StreamBuffer staging_buffer; @@ -150,6 +152,7 @@ private: Buffer gds_buffer; std::mutex mutex; Common::SlotVector slot_buffers; + RangeSet gpu_modified_ranges; vk::BufferView null_buffer_view; MemoryTracker memory_tracker; PageTable page_table; diff --git a/src/video_core/buffer_cache/range_set.h b/src/video_core/buffer_cache/range_set.h index fe54aff8f..2abf6e524 100644 --- a/src/video_core/buffer_cache/range_set.h +++ b/src/video_core/buffer_cache/range_set.h @@ -43,11 +43,10 @@ struct RangeSet { if (m_ranges_set.empty()) { return; } - auto it = m_ranges_set.begin(); - auto end_it = m_ranges_set.end(); - for (; it != end_it; it++) { - const VAddr inter_addr_end = it->upper(); - const VAddr inter_addr = it->lower(); + + for (const auto& set : m_ranges_set) { + const VAddr inter_addr_end = set.upper(); + const VAddr inter_addr = set.lower(); func(inter_addr, inter_addr_end); } } diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index a68ec1e74..3e43c50e5 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -2,11 +2,15 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/assert.h" +#include "common/number_utils.h" #include "video_core/amdgpu/pixel_format.h" #include "video_core/renderer_vulkan/liverpool_to_vk.h" #include +#define INVALID_NUMBER_FORMAT_COMBO \ + LOG_ERROR(Render_Vulkan, "Unsupported number type {} for format {}", number_type, format); + namespace Vulkan::LiverpoolToVK { using DepthBuffer = Liverpool::DepthBuffer; @@ -725,55 +729,362 @@ void EmitQuadToTriangleListIndices(u8* out_ptr, u32 num_vertices) { } } -static constexpr float U8ToUnorm(u8 v) { - static constexpr auto c = 1.0f / 255.0f; - return float(v * c); -} - vk::ClearValue ColorBufferClearValue(const AmdGpu::Liverpool::ColorBuffer& color_buffer) { const auto comp_swap = color_buffer.info.comp_swap.Value(); - ASSERT_MSG(comp_swap == Liverpool::ColorBuffer::SwapMode::Standard || - comp_swap == Liverpool::ColorBuffer::SwapMode::Alternate, - "Unsupported component swap mode {}", static_cast(comp_swap)); - - const bool comp_swap_alt = comp_swap == Liverpool::ColorBuffer::SwapMode::Alternate; + const auto format = color_buffer.info.format.Value(); + const auto number_type = color_buffer.info.number_type.Value(); const auto& c0 = color_buffer.clear_word0; const auto& c1 = color_buffer.clear_word1; const auto num_bits = AmdGpu::NumBits(color_buffer.info.format); + const auto num_components = AmdGpu::NumComponents(format); + + const bool comp_swap_alt = + comp_swap == AmdGpu::Liverpool::ColorBuffer::SwapMode::Alternate || + comp_swap == AmdGpu::Liverpool::ColorBuffer::SwapMode::AlternateReverse; + const bool comp_swap_reverse = + comp_swap == AmdGpu::Liverpool::ColorBuffer::SwapMode::StandardReverse || + comp_swap == AmdGpu::Liverpool::ColorBuffer::SwapMode::AlternateReverse; vk::ClearColorValue color{}; - switch (color_buffer.info.number_type) { - case AmdGpu::NumberFormat::Snorm: - [[fallthrough]]; - case AmdGpu::NumberFormat::SnormNz: - [[fallthrough]]; - case AmdGpu::NumberFormat::Unorm: - [[fallthrough]]; - case AmdGpu::NumberFormat::Srgb: { - switch (num_bits) { - case 32: { - color.float32 = std::array{ - U8ToUnorm((c0 >> (comp_swap_alt ? 16 : 0)) & 0xff), - U8ToUnorm((c0 >> 8) & 0xff), - U8ToUnorm((c0 >> (comp_swap_alt ? 0 : 16)) & 0xff), - U8ToUnorm((c0 >> 24) & 0xff), - }; - break; - } - default: { - LOG_ERROR(Render_Vulkan, "Missing clear color conversion for bits {}", num_bits); - break; - } - } + + switch (number_type) { + case AmdGpu::NumberFormat::Uint: + case AmdGpu::NumberFormat::Sint: + color.uint32[3] = 1; + break; + default: + color.float32[3] = 1.0f; break; } - default: { - LOG_ERROR(Render_Vulkan, "Missing clear color conversion for type {}", - color_buffer.info.number_type.Value()); + + switch (format) { + case AmdGpu::DataFormat::Format8: + switch (number_type) { + case AmdGpu::NumberFormat::Unorm: + case AmdGpu::NumberFormat::Srgb: // Should we handle gamma correction here? + color.float32[0] = NumberUtils::U8ToUnorm(c0 & 0xff); + break; + break; + case AmdGpu::NumberFormat::Snorm: + case AmdGpu::NumberFormat::SnormNz: + color.float32[0] = NumberUtils::S8ToSnorm(c0 & 0xff); + break; + case AmdGpu::NumberFormat::Uint: + case AmdGpu::NumberFormat::Sint: + color.uint32[0] = c0; + break; + default: + INVALID_NUMBER_FORMAT_COMBO; + break; + } + break; + case AmdGpu::DataFormat::Format16: + switch (number_type) { + case AmdGpu::NumberFormat::Unorm: + color.float32[0] = NumberUtils::U16ToUnorm(c0 & 0xffff); + break; + case AmdGpu::NumberFormat::Snorm: + case AmdGpu::NumberFormat::SnormNz: + color.float32[0] = NumberUtils::S16ToSnorm(c0 & 0xffff); + break; + case AmdGpu::NumberFormat::Uint: + case AmdGpu::NumberFormat::Sint: + color.uint32[0] = c0; + break; + case AmdGpu::NumberFormat::Float: + color.float32[0] = NumberUtils::Uf16ToF32(c0 & 0xffff); + break; + default: + INVALID_NUMBER_FORMAT_COMBO; + break; + } + break; + case AmdGpu::DataFormat::Format8_8: + switch (number_type) { + case AmdGpu::NumberFormat::Unorm: + case AmdGpu::NumberFormat::Srgb: // Should we handle gamma correction here? + color.float32[0] = NumberUtils::U8ToUnorm(c0 & 0xff); + color.float32[1] = NumberUtils::U8ToUnorm((c0 >> 8) & 0xff); + break; + case AmdGpu::NumberFormat::Snorm: + case AmdGpu::NumberFormat::SnormNz: + color.float32[0] = NumberUtils::S8ToSnorm(c0 & 0xff); + color.float32[1] = NumberUtils::S8ToSnorm((c0 >> 8) & 0xff); + break; + case AmdGpu::NumberFormat::Uint: + case AmdGpu::NumberFormat::Sint: + color.uint32[0] = c0 & 0xff; + color.uint32[1] = (c0 >> 8) & 0xff; + break; + default: + INVALID_NUMBER_FORMAT_COMBO; + break; + } + break; + case AmdGpu::DataFormat::Format32: + switch (number_type) { + case AmdGpu::NumberFormat::Uint: + case AmdGpu::NumberFormat::Sint: + color.uint32[0] = c0; + break; + case AmdGpu::NumberFormat::Float: + color.float32[0] = *(reinterpret_cast(&c0)); + break; + default: + INVALID_NUMBER_FORMAT_COMBO; + break; + } + break; + case AmdGpu::DataFormat::Format16_16: + switch (number_type) { + case AmdGpu::NumberFormat::Unorm: + color.float32[0] = NumberUtils::U16ToUnorm(c0 & 0xffff); + color.float32[1] = NumberUtils::U16ToUnorm((c0 >> 16) & 0xffff); + break; + case AmdGpu::NumberFormat::Snorm: + case AmdGpu::NumberFormat::SnormNz: + color.float32[0] = NumberUtils::S16ToSnorm(c0 & 0xffff); + color.float32[1] = NumberUtils::S16ToSnorm((c0 >> 16) & 0xffff); + break; + case AmdGpu::NumberFormat::Uint: + case AmdGpu::NumberFormat::Sint: + color.uint32[0] = c0 & 0xffff; + color.uint32[1] = (c0 >> 16) & 0xffff; + break; + case AmdGpu::NumberFormat::Float: + color.float32[0] = NumberUtils::Uf16ToF32(c0 & 0xffff); + color.float32[1] = NumberUtils::Uf16ToF32((c0 >> 16) & 0xffff); + break; + default: + INVALID_NUMBER_FORMAT_COMBO; + break; + } + break; + case AmdGpu::DataFormat::Format10_11_11: + color.float32[0] = NumberUtils::Uf11ToF32(c0 & 0x7ff); + color.float32[1] = NumberUtils::Uf11ToF32((c0 >> 11) & 0x7ff); + color.float32[2] = NumberUtils::Uf10ToF32((c0 >> 22) & 0x3ff); + break; + case AmdGpu::DataFormat::Format11_11_10: + color.float32[0] = NumberUtils::Uf10ToF32(c0 & 0x3ff); + color.float32[1] = NumberUtils::Uf11ToF32((c0 >> 10) & 0x7ff); + color.float32[2] = NumberUtils::Uf11ToF32((c0 >> 21) & 0x7ff); + break; + case AmdGpu::DataFormat::Format5_9_9_9: { + int exponent; + union { + float f; + u32 u; + } scale; + + exponent = (c0 >> 27) - 10; + scale.u = (exponent + 127) << 23; + + color.float32[0] = (c0 & 0x1ff) * scale.f; + color.float32[1] = ((c0 >> 9) & 0x1ff) * scale.f; + color.float32[2] = ((c0 >> 18) & 0x1ff) * scale.f; break; } + case AmdGpu::DataFormat::Format10_10_10_2: + switch (number_type) { + case AmdGpu::NumberFormat::Unorm: + color.float32[0] = NumberUtils::U2ToUnorm(c0 & 0x3); + color.float32[1] = NumberUtils::U10ToUnorm((c0 >> 2) & 0x3ff); + color.float32[2] = NumberUtils::U10ToUnorm((c0 >> 12) & 0x3ff); + color.float32[3] = NumberUtils::U10ToUnorm(c0 >> 22); + break; + case AmdGpu::NumberFormat::Snorm: + case AmdGpu::NumberFormat::SnormNz: + color.float32[0] = NumberUtils::S2ToSnorm(c0 & 0x3); + color.float32[1] = NumberUtils::S10ToSnorm((c0 >> 2) & 0x3ff); + color.float32[2] = NumberUtils::S10ToSnorm((c0 >> 12) & 0x3ff); + color.float32[3] = NumberUtils::S2ToSnorm(c0 >> 22); + break; + case AmdGpu::NumberFormat::Uint: + case AmdGpu::NumberFormat::Sint: + color.uint32[0] = c0 & 0x3; + color.uint32[1] = (c0 >> 2) & 0x3ff; + color.uint32[2] = (c0 >> 12) & 0x3ff; + color.uint32[3] = c0 >> 22; + break; + default: + INVALID_NUMBER_FORMAT_COMBO; + break; + } + break; + case AmdGpu::DataFormat::Format2_10_10_10: + switch (number_type) { + case AmdGpu::NumberFormat::Unorm: + color.float32[0] = NumberUtils::U10ToUnorm(c0 & 0x3ff); + color.float32[1] = NumberUtils::U10ToUnorm((c0 >> 10) & 0x3ff); + color.float32[2] = NumberUtils::U10ToUnorm((c0 >> 20) & 0x3ff); + color.float32[3] = NumberUtils::U2ToUnorm(c0 >> 30); + break; + case AmdGpu::NumberFormat::Snorm: + case AmdGpu::NumberFormat::SnormNz: + color.float32[0] = NumberUtils::S10ToSnorm(c0 & 0x3ff); + color.float32[1] = NumberUtils::S10ToSnorm((c0 >> 10) & 0x3ff); + color.float32[2] = NumberUtils::S10ToSnorm((c0 >> 20) & 0x3ff); + color.float32[3] = NumberUtils::S2ToSnorm(c0 >> 30); + break; + case AmdGpu::NumberFormat::Uint: + case AmdGpu::NumberFormat::Sint: + color.uint32[0] = c0 & 0x3ff; + color.uint32[1] = (c0 >> 10) & 0x3ff; + color.uint32[2] = (c0 >> 20) & 0x3ff; + color.uint32[3] = c0 >> 30; + break; + default: + INVALID_NUMBER_FORMAT_COMBO; + break; + } + break; + case AmdGpu::DataFormat::Format8_8_8_8: + switch (number_type) { + case AmdGpu::NumberFormat::Unorm: + case AmdGpu::NumberFormat::Srgb: // Should we handle gamma correction here? + color.float32[0] = NumberUtils::U8ToUnorm(c0 & 0xff); + color.float32[1] = NumberUtils::U8ToUnorm((c0 >> 8) & 0xff); + color.float32[2] = NumberUtils::U8ToUnorm((c0 >> 16) & 0xff); + color.float32[3] = NumberUtils::U8ToUnorm(c0 >> 24); + break; + case AmdGpu::NumberFormat::Snorm: + case AmdGpu::NumberFormat::SnormNz: + color.float32[0] = NumberUtils::S8ToSnorm(c0 & 0xff); + color.float32[1] = NumberUtils::S8ToSnorm((c0 >> 8) & 0xff); + color.float32[2] = NumberUtils::S8ToSnorm((c0 >> 16) & 0xff); + color.float32[3] = NumberUtils::S8ToSnorm(c0 >> 24); + break; + case AmdGpu::NumberFormat::Uint: + case AmdGpu::NumberFormat::Sint: + color.uint32[0] = c0 & 0xff; + color.uint32[1] = (c0 >> 8) & 0xff; + color.uint32[2] = (c0 >> 16) & 0xff; + color.uint32[3] = c0 >> 24; + break; + default: + INVALID_NUMBER_FORMAT_COMBO; + break; + } + break; + case AmdGpu::DataFormat::Format32_32: + switch (number_type) { + case AmdGpu::NumberFormat::Uint: + case AmdGpu::NumberFormat::Sint: + color.uint32[0] = c0; + color.uint32[1] = c1; + break; + case AmdGpu::NumberFormat::Float: + color.float32[0] = *(reinterpret_cast(&c0)); + color.float32[1] = *(reinterpret_cast(&c1)); + break; + default: + INVALID_NUMBER_FORMAT_COMBO; + break; + } + break; + case AmdGpu::DataFormat::Format16_16_16_16: + switch (number_type) { + case AmdGpu::NumberFormat::Unorm: + color.float32[0] = NumberUtils::U16ToUnorm(c0 & 0xffff); + color.float32[1] = NumberUtils::U16ToUnorm((c0 >> 16) & 0xffff); + color.float32[2] = NumberUtils::U16ToUnorm(c1 & 0xffff); + color.float32[3] = NumberUtils::U16ToUnorm((c1 >> 16) & 0xffff); + break; + case AmdGpu::NumberFormat::Snorm: + case AmdGpu::NumberFormat::SnormNz: + color.float32[0] = NumberUtils::S16ToSnorm(c0 & 0xffff); + color.float32[1] = NumberUtils::S16ToSnorm((c0 >> 16) & 0xffff); + color.float32[2] = NumberUtils::S16ToSnorm(c1 & 0xffff); + color.float32[3] = NumberUtils::S16ToSnorm((c1 >> 16) & 0xffff); + break; + case AmdGpu::NumberFormat::Uint: + case AmdGpu::NumberFormat::Sint: + color.uint32[0] = c0 & 0xffff; + color.uint32[1] = (c0 >> 16) & 0xffff; + color.uint32[2] = c1 & 0xffff; + color.uint32[3] = (c1 >> 16) & 0xffff; + break; + case AmdGpu::NumberFormat::Float: + color.float32[0] = NumberUtils::Uf16ToF32(c0 & 0xffff); + color.float32[1] = NumberUtils::Uf16ToF32((c0 >> 16) & 0xffff); + color.float32[2] = NumberUtils::Uf16ToF32(c1 & 0xffff); + color.float32[3] = NumberUtils::Uf16ToF32((c1 >> 16) & 0xffff); + break; + default: + INVALID_NUMBER_FORMAT_COMBO; + break; + } + break; + case AmdGpu::DataFormat::Format32_32_32_32: + switch (number_type) { + case AmdGpu::NumberFormat::Uint: + case AmdGpu::NumberFormat::Sint: + color.uint32[0] = c0; + color.uint32[1] = c0; + color.uint32[2] = c0; + color.uint32[3] = c1; + break; + case AmdGpu::NumberFormat::Float: + color.float32[0] = *(reinterpret_cast(&c0)); + color.float32[1] = *(reinterpret_cast(&c0)); + color.float32[2] = *(reinterpret_cast(&c0)); + color.float32[3] = *(reinterpret_cast(&c1)); + break; + default: + INVALID_NUMBER_FORMAT_COMBO; + break; + } + break; + case AmdGpu::DataFormat::Format5_6_5: + color.float32[0] = NumberUtils::U5ToUnorm(c0 & 0x1f); + color.float32[1] = NumberUtils::U6ToUnorm((c0 >> 5) & 0x3f); + color.float32[2] = NumberUtils::U5ToUnorm(c0 >> 11); + break; + case AmdGpu::DataFormat::Format1_5_5_5: + color.float32[0] = NumberUtils::U5ToUnorm(c0 & 0x1f); + color.float32[1] = NumberUtils::U5ToUnorm((c0 >> 5) & 0x1f); + color.float32[2] = NumberUtils::U5ToUnorm((c0 >> 10) & 0x1f); + color.float32[3] = (c0 >> 15) ? 1.0f : 0.0f; + break; + case AmdGpu::DataFormat::Format5_5_5_1: + color.float32[0] = (c0 & 0x1) ? 1.0f : 0.0f; + color.float32[1] = NumberUtils::U5ToUnorm((c0 >> 1) & 0x1f); + color.float32[2] = NumberUtils::U5ToUnorm((c0 >> 6) & 0x1f); + color.float32[3] = NumberUtils::U5ToUnorm((c0 >> 11) & 0x1f); + break; + case AmdGpu::DataFormat::Format4_4_4_4: + color.float32[0] = NumberUtils::U4ToUnorm(c0 & 0xf); + color.float32[1] = NumberUtils::U4ToUnorm((c0 >> 4) & 0xf); + color.float32[2] = NumberUtils::U4ToUnorm((c0 >> 8) & 0xf); + color.float32[3] = NumberUtils::U4ToUnorm(c0 >> 12); + break; + default: + LOG_ERROR(Render_Vulkan, "Unsupported color buffer format: {}", format); + break; } + + if (num_components == 1) { + if (comp_swap != Liverpool::ColorBuffer::SwapMode::Standard) { + color.float32[static_cast(comp_swap)] = color.float32[0]; + color.float32[0] = 0.0f; + } + } else { + if (comp_swap_alt && num_components == 4) { + std::swap(color.float32[0], color.float32[2]); + } + + if (comp_swap_reverse) { + std::reverse(std::begin(color.float32), std::begin(color.float32) + num_components); + } + + if (comp_swap_alt && num_components != 4) { + color.float32[3] = color.float32[num_components - 1]; + color.float32[num_components - 1] = 0.0f; + } + } + return {.color = color}; } diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 97e5185e5..64a483654 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -446,6 +446,8 @@ Frame* RendererVulkan::GetRenderFrame() { // Wait for the presentation to be finished so all frame resources are free while (wait() != vk::Result::eSuccess) { + ASSERT_MSG(result != vk::Result::eErrorDeviceLost, + "Device lost during waiting for a frame"); // Retry if the waiting times out if (result == vk::Result::eTimeout) { continue; diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 0dd869614..a663622fc 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -70,7 +70,7 @@ public: bool IsVideoOutSurface(const AmdGpu::Liverpool::ColorBuffer& color_buffer) { return std::ranges::find_if(vo_buffers_addr, [&](VAddr vo_buffer) { return vo_buffer == color_buffer.Address(); - }) != vo_buffers_addr.end(); + }) != vo_buffers_addr.cend(); } bool ShowSplash(Frame* frame = nullptr); diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 887f10ecc..7122ca134 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -3,7 +3,6 @@ #include -#include "common/alignment.h" #include "video_core/buffer_cache/buffer_cache.h" #include "video_core/renderer_vulkan/vk_compute_pipeline.h" #include "video_core/renderer_vulkan/vk_instance.h" @@ -113,140 +112,45 @@ ComputePipeline::~ComputePipeline() = default; bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, VideoCore::TextureCache& texture_cache) const { // Bind resource buffers and textures. - boost::container::static_vector buffer_views; - boost::container::static_vector buffer_infos; boost::container::small_vector set_writes; - boost::container::small_vector buffer_barriers; + BufferBarriers buffer_barriers; Shader::PushData push_data{}; Shader::Backend::Bindings binding{}; + info->PushUd(binding, push_data); + + buffer_infos.clear(); + buffer_views.clear(); image_infos.clear(); - info->PushUd(binding, push_data); - for (const auto& desc : info->buffers) { - bool is_storage = true; - if (desc.is_gds_buffer) { - auto* vk_buffer = buffer_cache.GetGdsBuffer(); - buffer_infos.emplace_back(vk_buffer->Handle(), 0, vk_buffer->SizeBytes()); - } else { - const auto vsharp = desc.GetSharp(*info); - is_storage = desc.IsStorage(vsharp); - const VAddr address = vsharp.base_address; - // Most of the time when a metadata is updated with a shader it gets cleared. It means - // we can skip the whole dispatch and update the tracked state instead. Also, it is not - // intended to be consumed and in such rare cases (e.g. HTile introspection, CRAA) we - // will need its full emulation anyways. For cases of metadata read a warning will be - // logged. - if (desc.is_written) { - if (texture_cache.TouchMeta(address, true)) { - LOG_TRACE(Render_Vulkan, "Metadata update skipped"); - return false; - } - } else { - if (texture_cache.IsMeta(address)) { - LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a CS shader (buffer)"); - } + // Most of the time when a metadata is updated with a shader it gets cleared. It means + // we can skip the whole dispatch and update the tracked state instead. Also, it is not + // intended to be consumed and in such rare cases (e.g. HTile introspection, CRAA) we + // will need its full emulation anyways. For cases of metadata read a warning will be logged. + for (const auto& desc : info->texture_buffers) { + const VAddr address = desc.GetSharp(*info).base_address; + if (desc.is_written) { + if (texture_cache.TouchMeta(address, true)) { + LOG_TRACE(Render_Vulkan, "Metadata update skipped"); + return false; + } + } else { + if (texture_cache.IsMeta(address)) { + LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a CS shader (buffer)"); } - const u32 size = vsharp.GetSize(); - const u32 alignment = - is_storage ? instance.StorageMinAlignment() : instance.UniformMinAlignment(); - const auto [vk_buffer, offset] = - buffer_cache.ObtainBuffer(address, size, desc.is_written); - const u32 offset_aligned = Common::AlignDown(offset, alignment); - const u32 adjust = offset - offset_aligned; - ASSERT(adjust % 4 == 0); - push_data.AddOffset(binding.buffer, adjust); - buffer_infos.emplace_back(vk_buffer->Handle(), offset_aligned, size + adjust); } - set_writes.push_back({ - .dstSet = VK_NULL_HANDLE, - .dstBinding = binding.unified++, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = is_storage ? vk::DescriptorType::eStorageBuffer - : vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &buffer_infos.back(), - }); - ++binding.buffer; } - const auto null_buffer_view = - instance.IsNullDescriptorSupported() ? VK_NULL_HANDLE : buffer_cache.NullBufferView(); - for (const auto& desc : info->texture_buffers) { - const auto vsharp = desc.GetSharp(*info); - vk::BufferView& buffer_view = buffer_views.emplace_back(null_buffer_view); - const u32 size = vsharp.GetSize(); - const VAddr address = vsharp.base_address; - if (vsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid && address != 0 && size != 0) { - if (desc.is_written) { - if (texture_cache.TouchMeta(address, true)) { - LOG_TRACE(Render_Vulkan, "Metadata update skipped"); - return false; - } - } else { - if (texture_cache.IsMeta(address)) { - LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a CS shader (buffer)"); - } - } - const u32 alignment = instance.TexelBufferMinAlignment(); - const auto [vk_buffer, offset] = - buffer_cache.ObtainBuffer(address, size, desc.is_written, true); - const u32 fmt_stride = AmdGpu::NumBits(vsharp.GetDataFmt()) >> 3; - //ASSERT_MSG(fmt_stride == vsharp.GetStride(), - // "Texel buffer stride must match format stride"); - const u32 offset_aligned = Common::AlignDown(offset, alignment); - const u32 adjust = offset - offset_aligned; - ASSERT(adjust % fmt_stride == 0); - push_data.AddOffset(binding.buffer, adjust / fmt_stride); - buffer_view = vk_buffer->View(offset_aligned, size + adjust, desc.is_written, - vsharp.GetDataFmt(), vsharp.GetNumberFmt()); - if (auto barrier = - vk_buffer->GetBarrier(desc.is_written ? vk::AccessFlagBits2::eShaderWrite - : vk::AccessFlagBits2::eShaderRead, - vk::PipelineStageFlagBits2::eComputeShader)) { - buffer_barriers.emplace_back(*barrier); - } - if (desc.is_written) { - texture_cache.InvalidateMemoryFromGPU(address, size); - } - } - set_writes.push_back({ - .dstSet = VK_NULL_HANDLE, - .dstBinding = binding.unified++, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = desc.is_written ? vk::DescriptorType::eStorageTexelBuffer - : vk::DescriptorType::eUniformTexelBuffer, - .pTexelBufferView = &buffer_view, - }); - ++binding.buffer; - } + BindBuffers(buffer_cache, texture_cache, *info, binding, push_data, set_writes, + buffer_barriers); BindTextures(texture_cache, *info, binding, set_writes); - for (const auto& sampler : info->samplers) { - const auto ssharp = sampler.GetSharp(*info); - if (ssharp.force_degamma) { - LOG_WARNING(Render_Vulkan, "Texture requires gamma correction"); - } - const auto vk_sampler = texture_cache.GetSampler(ssharp); - image_infos.emplace_back(vk_sampler, VK_NULL_HANDLE, vk::ImageLayout::eGeneral); - set_writes.push_back({ - .dstSet = VK_NULL_HANDLE, - .dstBinding = binding.unified++, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eSampler, - .pImageInfo = &image_infos.back(), - }); - } - if (set_writes.empty()) { return false; } const auto cmdbuf = scheduler.CommandBuffer(); - if (!buffer_barriers.empty()) { const auto dependencies = vk::DependencyInfo{ .dependencyFlags = vk::DependencyFlagBits::eByRegion, @@ -257,21 +161,22 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, cmdbuf.pipelineBarrier2(dependencies); } + cmdbuf.pushConstants(*pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0u, sizeof(push_data), + &push_data); + + // Bind descriptor set. if (uses_push_descriptors) { cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, *pipeline_layout, 0, set_writes); - } else { - const auto desc_set = desc_heap.Commit(*desc_layout); - for (auto& set_write : set_writes) { - set_write.dstSet = desc_set; - } - instance.GetDevice().updateDescriptorSets(set_writes, {}); - cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *pipeline_layout, 0, desc_set, - {}); + return true; } + const auto desc_set = desc_heap.Commit(*desc_layout); + for (auto& set_write : set_writes) { + set_write.dstSet = desc_set; + } + instance.GetDevice().updateDescriptorSets(set_writes, {}); + cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *pipeline_layout, 0, desc_set, {}); - cmdbuf.pushConstants(*pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0u, sizeof(push_data), - &push_data); return true; } diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index cbc0fc5ec..f6d0b49b6 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -5,8 +5,8 @@ #include #include -#include "common/alignment.h" #include "common/assert.h" +#include "common/scope_exit.h" #include "video_core/amdgpu/resource.h" #include "video_core/buffer_cache/buffer_cache.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" @@ -384,13 +384,13 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, VideoCore::BufferCache& buffer_cache, VideoCore::TextureCache& texture_cache) const { // Bind resource buffers and textures. - boost::container::static_vector buffer_views; - boost::container::static_vector buffer_infos; boost::container::small_vector set_writes; - boost::container::small_vector buffer_barriers; + BufferBarriers buffer_barriers; Shader::PushData push_data{}; Shader::Backend::Bindings binding{}; + buffer_infos.clear(); + buffer_views.clear(); image_infos.clear(); for (const auto* stage : stages) { @@ -402,111 +402,22 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, push_data.step1 = regs.vgt_instance_step_rate_1; } stage->PushUd(binding, push_data); - for (const auto& buffer : stage->buffers) { - const auto vsharp = buffer.GetSharp(*stage); - const bool is_storage = buffer.IsStorage(vsharp); - if (vsharp && vsharp.GetSize() > 0) { - const VAddr address = vsharp.base_address; - if (texture_cache.IsMeta(address)) { - LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a PS shader (buffer)"); - } - const u32 size = vsharp.GetSize(); - const u32 alignment = - is_storage ? instance.StorageMinAlignment() : instance.UniformMinAlignment(); - const auto [vk_buffer, offset] = - buffer_cache.ObtainBuffer(address, size, buffer.is_written); - const u32 offset_aligned = Common::AlignDown(offset, alignment); - const u32 adjust = offset - offset_aligned; - ASSERT(adjust % 4 == 0); - push_data.AddOffset(binding.buffer, adjust); - buffer_infos.emplace_back(vk_buffer->Handle(), offset_aligned, size + adjust); - } else if (instance.IsNullDescriptorSupported()) { - buffer_infos.emplace_back(VK_NULL_HANDLE, 0, VK_WHOLE_SIZE); - } else { - auto& null_buffer = buffer_cache.GetBuffer(VideoCore::NULL_BUFFER_ID); - buffer_infos.emplace_back(null_buffer.Handle(), 0, VK_WHOLE_SIZE); - } - set_writes.push_back({ - .dstSet = VK_NULL_HANDLE, - .dstBinding = binding.unified++, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = is_storage ? vk::DescriptorType::eStorageBuffer - : vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &buffer_infos.back(), - }); - ++binding.buffer; - } - const auto null_buffer_view = - instance.IsNullDescriptorSupported() ? VK_NULL_HANDLE : buffer_cache.NullBufferView(); - for (const auto& desc : stage->texture_buffers) { - const auto vsharp = desc.GetSharp(*stage); - vk::BufferView& buffer_view = buffer_views.emplace_back(null_buffer_view); - const u32 size = vsharp.GetSize(); - if (vsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid && size != 0) { - const VAddr address = vsharp.base_address; - const u32 alignment = instance.TexelBufferMinAlignment(); - const auto [vk_buffer, offset] = - buffer_cache.ObtainBuffer(address, size, desc.is_written, true); - const u32 fmt_stride = AmdGpu::NumBits(vsharp.GetDataFmt()) >> 3; - ASSERT_MSG(fmt_stride == vsharp.GetStride(), - "Texel buffer stride must match format stride"); - const u32 offset_aligned = Common::AlignDown(offset, alignment); - const u32 adjust = offset - offset_aligned; - ASSERT(adjust % fmt_stride == 0); - push_data.AddOffset(binding.buffer, adjust / fmt_stride); - buffer_view = vk_buffer->View(offset_aligned, size + adjust, desc.is_written, - vsharp.GetDataFmt(), vsharp.GetNumberFmt()); - const auto dst_access = desc.is_written ? vk::AccessFlagBits2::eShaderWrite - : vk::AccessFlagBits2::eShaderRead; - if (auto barrier = vk_buffer->GetBarrier( - dst_access, vk::PipelineStageFlagBits2::eVertexShader)) { - buffer_barriers.emplace_back(*barrier); - } - if (desc.is_written) { - texture_cache.InvalidateMemoryFromGPU(address, size); - } - } - set_writes.push_back({ - .dstSet = VK_NULL_HANDLE, - .dstBinding = binding.unified++, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = desc.is_written ? vk::DescriptorType::eStorageTexelBuffer - : vk::DescriptorType::eUniformTexelBuffer, - .pTexelBufferView = &buffer_view, - }); - ++binding.buffer; - } + BindBuffers(buffer_cache, texture_cache, *stage, binding, push_data, set_writes, + buffer_barriers); BindTextures(texture_cache, *stage, binding, set_writes); - - for (const auto& sampler : stage->samplers) { - auto ssharp = sampler.GetSharp(*stage); - if (ssharp.force_degamma) { - LOG_WARNING(Render_Vulkan, "Texture requires gamma correction"); - } - if (sampler.disable_aniso) { - const auto& tsharp = stage->images[sampler.associated_image].GetSharp(*stage); - if (tsharp.base_level == 0 && tsharp.last_level == 0) { - ssharp.max_aniso.Assign(AmdGpu::AnisoRatio::One); - } - } - const auto vk_sampler = texture_cache.GetSampler(ssharp); - image_infos.emplace_back(vk_sampler, VK_NULL_HANDLE, vk::ImageLayout::eGeneral); - set_writes.push_back({ - .dstSet = VK_NULL_HANDLE, - .dstBinding = binding.unified++, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eSampler, - .pImageInfo = &image_infos.back(), - }); - } } const auto cmdbuf = scheduler.CommandBuffer(); + SCOPE_EXIT { + cmdbuf.pushConstants(*pipeline_layout, gp_stage_flags, 0U, sizeof(push_data), &push_data); + cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, Handle()); + }; + + if (set_writes.empty()) { + return; + } if (!buffer_barriers.empty()) { const auto dependencies = vk::DependencyInfo{ @@ -518,22 +429,18 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, cmdbuf.pipelineBarrier2(dependencies); } - if (!set_writes.empty()) { - if (uses_push_descriptors) { - cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0, - set_writes); - } else { - const auto desc_set = desc_heap.Commit(*desc_layout); - for (auto& set_write : set_writes) { - set_write.dstSet = desc_set; - } - instance.GetDevice().updateDescriptorSets(set_writes, {}); - cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0, - desc_set, {}); - } + // Bind descriptor set. + if (uses_push_descriptors) { + cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0, + set_writes); + return; } - cmdbuf.pushConstants(*pipeline_layout, gp_stage_flags, 0U, sizeof(push_data), &push_data); - cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, Handle()); + const auto desc_set = desc_heap.Commit(*desc_layout); + for (auto& set_write : set_writes) { + set_write.dstSet = desc_set; + } + instance.GetDevice().updateDescriptorSets(set_writes, {}); + cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0, desc_set, {}); } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index e2bc995a6..24cffa11a 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -217,9 +217,10 @@ bool Instance::CreateDevice() { const vk::StructureChain properties_chain = physical_device.getProperties2< vk::PhysicalDeviceProperties2, vk::PhysicalDevicePortabilitySubsetPropertiesKHR, vk::PhysicalDeviceExternalMemoryHostPropertiesEXT, vk::PhysicalDeviceVulkan11Properties, - vk::PhysicalDevicePushDescriptorPropertiesKHR>(); + vk::PhysicalDevicePushDescriptorPropertiesKHR, vk::PhysicalDeviceVulkan12Properties>(); subgroup_size = properties_chain.get().subgroupSize; push_descriptor_props = properties_chain.get(); + vk12_props = properties_chain.get(); LOG_INFO(Render_Vulkan, "Physical device subgroup size {}", subgroup_size); features = feature_chain.get().features; @@ -265,7 +266,9 @@ bool Instance::CreateDevice() { // These extensions are promoted by Vulkan 1.3, but for greater compatibility we use Vulkan 1.2 // with extensions. - tooling_info = add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME); + if (Config::vkValidationEnabled() || Config::isRdocEnabled()) { + tooling_info = add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME); + } const bool maintenance4 = add_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME); add_extension(VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME); add_extension(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index d77d0c20f..474b86e9a 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -242,6 +242,11 @@ public: return push_descriptor_props.maxPushDescriptors; } + /// Returns the vulkan 1.2 physical device properties. + const vk::PhysicalDeviceVulkan12Properties& GetVk12Properties() const noexcept { + return vk12_props; + } + /// Returns true if shaders can declare the ClipDistance attribute bool IsShaderClipDistanceSupported() const { return features.shaderClipDistance; @@ -279,6 +284,7 @@ private: vk::UniqueDevice device; vk::PhysicalDeviceProperties properties; vk::PhysicalDevicePushDescriptorPropertiesKHR push_descriptor_props; + vk::PhysicalDeviceVulkan12Properties vk12_props; vk::PhysicalDeviceFeatures features; vk::DriverIdKHR driver_id; vk::UniqueDebugUtilsMessengerEXT debug_callback{}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 929fa9cc1..a06d82eb3 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -7,7 +7,6 @@ #include "common/io_file.h" #include "common/path_util.h" #include "shader_recompiler/backend/spirv/emit_spirv.h" -#include "shader_recompiler/frontend/copy_shader.h" #include "shader_recompiler/info.h" #include "shader_recompiler/recompiler.h" #include "shader_recompiler/runtime_info.h" @@ -41,7 +40,7 @@ void GatherVertexOutputs(Shader::VertexRuntimeInfo& info, const auto add_output = [&](VsOutput x, VsOutput y, VsOutput z, VsOutput w) { if (x != VsOutput::None || y != VsOutput::None || z != VsOutput::None || w != VsOutput::None) { - info.outputs.emplace_back(Shader::VsOutputMap{x, y, z, w}); + info.outputs[info.num_outputs++] = Shader::VsOutputMap{x, y, z, w}; } }; // VS_OUT_MISC_VEC @@ -84,18 +83,21 @@ void GatherVertexOutputs(Shader::VertexRuntimeInfo& info, Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) { auto info = Shader::RuntimeInfo{stage}; const auto& regs = liverpool->regs; + const auto BuildCommon = [&](const auto& program) { + info.num_user_data = program.settings.num_user_regs; + info.num_input_vgprs = program.settings.vgpr_comp_cnt; + info.num_allocated_vgprs = program.settings.num_vgprs * 4; + info.fp_denorm_mode32 = program.settings.fp_denorm_mode32; + info.fp_round_mode32 = program.settings.fp_round_mode32; + }; switch (stage) { case Shader::Stage::Export: { - info.num_user_data = regs.es_program.settings.num_user_regs; - info.num_input_vgprs = regs.es_program.settings.vgpr_comp_cnt; - info.num_allocated_vgprs = regs.es_program.settings.num_vgprs * 4; + BuildCommon(regs.es_program); info.es_info.vertex_data_size = regs.vgt_esgs_ring_itemsize; break; } case Shader::Stage::Vertex: { - info.num_user_data = regs.vs_program.settings.num_user_regs; - info.num_input_vgprs = regs.vs_program.settings.vgpr_comp_cnt; - info.num_allocated_vgprs = regs.vs_program.settings.num_vgprs * 4; + BuildCommon(regs.vs_program); GatherVertexOutputs(info.vs_info, regs.vs_output_control); info.vs_info.emulate_depth_negative_one_to_one = !instance.IsDepthClipControlSupported() && @@ -103,39 +105,35 @@ Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) { break; } case Shader::Stage::Geometry: { - info.num_user_data = regs.gs_program.settings.num_user_regs; - info.num_input_vgprs = regs.gs_program.settings.vgpr_comp_cnt; - info.num_allocated_vgprs = regs.gs_program.settings.num_vgprs * 4; - info.gs_info.output_vertices = regs.vgt_gs_max_vert_out; - info.gs_info.num_invocations = + BuildCommon(regs.gs_program); + auto& gs_info = info.gs_info; + gs_info.output_vertices = regs.vgt_gs_max_vert_out; + gs_info.num_invocations = regs.vgt_gs_instance_cnt.IsEnabled() ? regs.vgt_gs_instance_cnt.count : 1; - info.gs_info.in_primitive = regs.primitive_type; + gs_info.in_primitive = regs.primitive_type; for (u32 stream_id = 0; stream_id < Shader::GsMaxOutputStreams; ++stream_id) { - info.gs_info.out_primitive[stream_id] = + gs_info.out_primitive[stream_id] = regs.vgt_gs_out_prim_type.GetPrimitiveType(stream_id); } - info.gs_info.in_vertex_data_size = regs.vgt_esgs_ring_itemsize; - info.gs_info.out_vertex_data_size = regs.vgt_gs_vert_itemsize[0]; - - // Extract semantics offsets from a copy shader - const auto vc_stage = Shader::Stage::Vertex; - const auto* pgm_vc = regs.ProgramForStage(static_cast(vc_stage)); - const auto params_vc = Liverpool::GetParams(*pgm_vc); - DumpShader(params_vc.code, params_vc.hash, Shader::Stage::Vertex, 0, "copy.bin"); - info.gs_info.copy_data = Shader::ParseCopyShader(params_vc.code); + gs_info.in_vertex_data_size = regs.vgt_esgs_ring_itemsize; + gs_info.out_vertex_data_size = regs.vgt_gs_vert_itemsize[0]; + const auto params_vc = Liverpool::GetParams(regs.vs_program); + gs_info.vs_copy = params_vc.code; + gs_info.vs_copy_hash = params_vc.hash; + DumpShader(gs_info.vs_copy, gs_info.vs_copy_hash, Shader::Stage::Vertex, 0, "copy.bin"); break; } case Shader::Stage::Fragment: { - info.num_user_data = regs.ps_program.settings.num_user_regs; - info.num_allocated_vgprs = regs.ps_program.settings.num_vgprs * 4; + BuildCommon(regs.ps_program); const auto& ps_inputs = regs.ps_inputs; + info.fs_info.num_inputs = regs.num_interp; for (u32 i = 0; i < regs.num_interp; i++) { - info.fs_info.inputs.push_back({ + info.fs_info.inputs[i] = { .param_index = u8(ps_inputs[i].input_offset.Value()), .is_default = bool(ps_inputs[i].use_default), .is_flat = bool(ps_inputs[i].flat_shade), .default_value = u8(ps_inputs[i].default_value), - }); + }; } for (u32 i = 0; i < Shader::MaxColorBuffers; i++) { info.fs_info.color_buffers[i] = { @@ -166,9 +164,12 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, AmdGpu::Liverpool* liverpool_) : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, desc_heap{instance, scheduler.GetMasterSemaphore(), DescriptorHeapSizes} { + const auto& vk12_props = instance.GetVk12Properties(); profile = Shader::Profile{ .supported_spirv = instance.ApiVersion() >= VK_API_VERSION_1_3 ? 0x00010600U : 0x00010500U, .subgroup_size = instance.SubgroupSize(), + .support_fp32_denorm_preserve = bool(vk12_props.shaderDenormPreserveFloat32), + .support_fp32_denorm_flush = bool(vk12_props.shaderDenormFlushToZeroFloat32), .support_explicit_workgroup_layout = true, }; auto [cache_result, cache] = instance.GetDevice().createPipelineCacheUnique({}); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_common.cpp b/src/video_core/renderer_vulkan/vk_pipeline_common.cpp index 61e564150..efe2838e4 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_common.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_common.cpp @@ -4,6 +4,7 @@ #include #include "shader_recompiler/info.h" +#include "video_core/buffer_cache/buffer_cache.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_pipeline_common.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -12,6 +13,8 @@ namespace Vulkan { boost::container::static_vector Pipeline::image_infos; +boost::container::static_vector Pipeline::buffer_views; +boost::container::static_vector Pipeline::buffer_infos; Pipeline::Pipeline(const Instance& instance_, Scheduler& scheduler_, DescriptorHeap& desc_heap_, vk::PipelineCache pipeline_cache) @@ -19,12 +22,133 @@ Pipeline::Pipeline(const Instance& instance_, Scheduler& scheduler_, DescriptorH Pipeline::~Pipeline() = default; +void Pipeline::BindBuffers(VideoCore::BufferCache& buffer_cache, + VideoCore::TextureCache& texture_cache, const Shader::Info& stage, + Shader::Backend::Bindings& binding, Shader::PushData& push_data, + DescriptorWrites& set_writes, BufferBarriers& buffer_barriers) const { + using BufferBindingInfo = std::pair; + static boost::container::static_vector buffer_bindings; + + buffer_bindings.clear(); + + for (const auto& desc : stage.buffers) { + const auto vsharp = desc.GetSharp(stage); + if (!desc.is_gds_buffer && vsharp.base_address != 0 && vsharp.GetSize() > 0) { + const auto buffer_id = buffer_cache.FindBuffer(vsharp.base_address, vsharp.GetSize()); + buffer_bindings.emplace_back(buffer_id, vsharp); + } else { + buffer_bindings.emplace_back(VideoCore::BufferId{}, vsharp); + } + } + + using TexBufferBindingInfo = std::pair; + static boost::container::static_vector texbuffer_bindings; + + texbuffer_bindings.clear(); + + for (const auto& desc : stage.texture_buffers) { + const auto vsharp = desc.GetSharp(stage); + if (vsharp.base_address != 0 && vsharp.GetSize() > 0 && + vsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid) { + const auto buffer_id = buffer_cache.FindBuffer(vsharp.base_address, vsharp.GetSize()); + texbuffer_bindings.emplace_back(buffer_id, vsharp); + } else { + texbuffer_bindings.emplace_back(VideoCore::BufferId{}, vsharp); + } + } + + // Second pass to re-bind buffers that were updated after binding + for (u32 i = 0; i < buffer_bindings.size(); i++) { + const auto& [buffer_id, vsharp] = buffer_bindings[i]; + const auto& desc = stage.buffers[i]; + const bool is_storage = desc.IsStorage(vsharp); + if (!buffer_id) { + if (desc.is_gds_buffer) { + const auto* gds_buf = buffer_cache.GetGdsBuffer(); + buffer_infos.emplace_back(gds_buf->Handle(), 0, gds_buf->SizeBytes()); + } else if (instance.IsNullDescriptorSupported()) { + buffer_infos.emplace_back(VK_NULL_HANDLE, 0, VK_WHOLE_SIZE); + } else { + auto& null_buffer = buffer_cache.GetBuffer(VideoCore::NULL_BUFFER_ID); + buffer_infos.emplace_back(null_buffer.Handle(), 0, VK_WHOLE_SIZE); + } + } else { + const auto [vk_buffer, offset] = buffer_cache.ObtainBuffer( + vsharp.base_address, vsharp.GetSize(), desc.is_written, false, buffer_id); + const u32 alignment = + is_storage ? instance.StorageMinAlignment() : instance.UniformMinAlignment(); + const u32 offset_aligned = Common::AlignDown(offset, alignment); + const u32 adjust = offset - offset_aligned; + ASSERT(adjust % 4 == 0); + push_data.AddOffset(binding.buffer, adjust); + buffer_infos.emplace_back(vk_buffer->Handle(), offset_aligned, + vsharp.GetSize() + adjust); + } + + set_writes.push_back({ + .dstSet = VK_NULL_HANDLE, + .dstBinding = binding.unified++, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = is_storage ? vk::DescriptorType::eStorageBuffer + : vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &buffer_infos.back(), + }); + ++binding.buffer; + } + + const auto null_buffer_view = + instance.IsNullDescriptorSupported() ? VK_NULL_HANDLE : buffer_cache.NullBufferView(); + for (u32 i = 0; i < texbuffer_bindings.size(); i++) { + const auto& [buffer_id, vsharp] = texbuffer_bindings[i]; + const auto& desc = stage.texture_buffers[i]; + vk::BufferView& buffer_view = buffer_views.emplace_back(null_buffer_view); + if (buffer_id) { + const u32 alignment = instance.TexelBufferMinAlignment(); + const auto [vk_buffer, offset] = buffer_cache.ObtainBuffer( + vsharp.base_address, vsharp.GetSize(), desc.is_written, true, buffer_id); + const u32 fmt_stride = AmdGpu::NumBits(vsharp.GetDataFmt()) >> 3; + ASSERT_MSG(fmt_stride == vsharp.GetStride(), + "Texel buffer stride must match format stride"); + const u32 offset_aligned = Common::AlignDown(offset, alignment); + const u32 adjust = offset - offset_aligned; + ASSERT(adjust % fmt_stride == 0); + push_data.AddOffset(binding.buffer, adjust / fmt_stride); + buffer_view = + vk_buffer->View(offset_aligned, vsharp.GetSize() + adjust, desc.is_written, + vsharp.GetDataFmt(), vsharp.GetNumberFmt()); + if (auto barrier = + vk_buffer->GetBarrier(desc.is_written ? vk::AccessFlagBits2::eShaderWrite + : vk::AccessFlagBits2::eShaderRead, + vk::PipelineStageFlagBits2::eComputeShader)) { + buffer_barriers.emplace_back(*barrier); + } + if (desc.is_written) { + texture_cache.InvalidateMemoryFromGPU(vsharp.base_address, vsharp.GetSize()); + } + } + + set_writes.push_back({ + .dstSet = VK_NULL_HANDLE, + .dstBinding = binding.unified++, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = desc.is_written ? vk::DescriptorType::eStorageTexelBuffer + : vk::DescriptorType::eUniformTexelBuffer, + .pTexelBufferView = &buffer_view, + }); + ++binding.buffer; + } +} + void Pipeline::BindTextures(VideoCore::TextureCache& texture_cache, const Shader::Info& stage, Shader::Backend::Bindings& binding, DescriptorWrites& set_writes) const { using ImageBindingInfo = std::tuple; - boost::container::static_vector image_bindings; + static boost::container::static_vector image_bindings; + + image_bindings.clear(); for (const auto& image_desc : stage.images) { const auto tsharp = image_desc.GetSharp(stage); @@ -76,6 +200,26 @@ void Pipeline::BindTextures(VideoCore::TextureCache& texture_cache, const Shader .pImageInfo = &image_infos.back(), }); } + + for (const auto& sampler : stage.samplers) { + auto ssharp = sampler.GetSharp(stage); + if (sampler.disable_aniso) { + const auto& tsharp = stage.images[sampler.associated_image].GetSharp(stage); + if (tsharp.base_level == 0 && tsharp.last_level == 0) { + ssharp.max_aniso.Assign(AmdGpu::AnisoRatio::One); + } + } + const auto vk_sampler = texture_cache.GetSampler(ssharp); + image_infos.emplace_back(vk_sampler, VK_NULL_HANDLE, vk::ImageLayout::eGeneral); + set_writes.push_back({ + .dstSet = VK_NULL_HANDLE, + .dstBinding = binding.unified++, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eSampler, + .pImageInfo = &image_infos.back(), + }); + } } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_pipeline_common.h b/src/video_core/renderer_vulkan/vk_pipeline_common.h index ab99e7b33..75764bfa6 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_common.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_common.h @@ -33,6 +33,13 @@ public: } using DescriptorWrites = boost::container::small_vector; + using BufferBarriers = boost::container::small_vector; + + void BindBuffers(VideoCore::BufferCache& buffer_cache, VideoCore::TextureCache& texture_cache, + const Shader::Info& stage, Shader::Backend::Bindings& binding, + Shader::PushData& push_data, DescriptorWrites& set_writes, + BufferBarriers& buffer_barriers) const; + void BindTextures(VideoCore::TextureCache& texture_cache, const Shader::Info& stage, Shader::Backend::Bindings& binding, DescriptorWrites& set_writes) const; @@ -44,6 +51,8 @@ protected: vk::UniquePipelineLayout pipeline_layout; vk::UniqueDescriptorSetLayout desc_layout; static boost::container::static_vector image_infos; + static boost::container::static_vector buffer_views; + static boost::container::static_vector buffer_infos; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 6088d99cf..14a73261d 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -98,21 +98,20 @@ void Rasterizer::DrawIndirect(bool is_indexed, VAddr address, u32 offset, u32 si const auto& vs_info = pipeline->GetStage(Shader::Stage::Vertex); buffer_cache.BindVertexBuffers(vs_info); - const u32 num_indices = buffer_cache.BindIndexBuffer(is_indexed, 0); + buffer_cache.BindIndexBuffer(is_indexed, 0); + + const auto [buffer, base] = buffer_cache.ObtainBuffer(address + offset, size, false); BeginRendering(*pipeline); UpdateDynamicState(*pipeline); - const auto [buffer, base] = buffer_cache.ObtainBuffer(address, size, true); - const auto total_offset = base + offset; - // We can safely ignore both SGPR UD indices and results of fetch shader parsing, as vertex and // instance offsets will be automatically applied by Vulkan from indirect args buffer. if (is_indexed) { - cmdbuf.drawIndexedIndirect(buffer->Handle(), total_offset, 1, 0); + cmdbuf.drawIndexedIndirect(buffer->Handle(), base, 1, 0); } else { - cmdbuf.drawIndirect(buffer->Handle(), total_offset, 1, 0); + cmdbuf.drawIndirect(buffer->Handle(), base, 1, 0); } } @@ -161,9 +160,8 @@ void Rasterizer::DispatchIndirect(VAddr address, u32 offset, u32 size) { scheduler.EndRendering(); cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline->Handle()); - const auto [buffer, base] = buffer_cache.ObtainBuffer(address, size, true); - const auto total_offset = base + offset; - cmdbuf.dispatchIndirect(buffer->Handle(), total_offset); + const auto [buffer, base] = buffer_cache.ObtainBuffer(address + offset, size, false); + cmdbuf.dispatchIndirect(buffer->Handle(), base); } u64 Rasterizer::Flush() { @@ -260,8 +258,8 @@ void Rasterizer::BeginRendering(const GraphicsPipeline& pipeline) { scheduler.BeginRendering(state); } -void Rasterizer::InlineDataToGds(u32 gds_offset, u32 value) { - buffer_cache.InlineDataToGds(gds_offset, value); +void Rasterizer::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) { + buffer_cache.InlineData(address, value, num_bytes, is_gds); } u32 Rasterizer::ReadDataFromGds(u32 gds_offset) { @@ -459,4 +457,17 @@ void Rasterizer::ScopedMarkerInsert(const std::string_view& str) { }); } +void Rasterizer::ScopedMarkerInsertColor(const std::string_view& str, const u32 color) { + if (Config::nullGpu() || !Config::vkMarkersEnabled()) { + return; + } + + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.insertDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ + .pLabelName = str.data(), + .color = std::array( + {(f32)((color >> 16) & 0xff) / 255.0f, (f32)((color >> 8) & 0xff) / 255.0f, + (f32)(color & 0xff) / 255.0f, (f32)((color >> 24) & 0xff) / 255.0f})}); +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 82e8fc0c0..d5cfbfd66 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -40,8 +40,9 @@ public: void ScopeMarkerBegin(const std::string_view& str); void ScopeMarkerEnd(); void ScopedMarkerInsert(const std::string_view& str); + void ScopedMarkerInsertColor(const std::string_view& str, const u32 color); - void InlineDataToGds(u32 gds_offset, u32 value); + void InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds); u32 ReadDataFromGds(u32 gsd_offset); void InvalidateMemory(VAddr addr, u64 size); void MapMemory(VAddr addr, u64 size); diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 357cd504d..86d7d5063 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -47,7 +47,7 @@ void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) { std::find_if(modes.begin(), modes.end(), [&requested](vk::PresentModeKHR mode) { return mode == requested; }); - return it != modes.end(); + return it != modes.cend(); }; const bool has_mailbox = find_mode(vk::PresentModeKHR::eMailbox); diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 521e4118f..efb74ffae 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -329,6 +329,11 @@ bool ImageInfo::IsMipOf(const ImageInfo& info) const { return false; } + // Ensure that address matches too. + if ((info.guest_address + info.mips_layout[mip].offset) != guest_address) { + return false; + } + return true; } diff --git a/src/video_core/texture_cache/sampler.cpp b/src/video_core/texture_cache/sampler.cpp index 179dd6646..e47f53abf 100644 --- a/src/video_core/texture_cache/sampler.cpp +++ b/src/video_core/texture_cache/sampler.cpp @@ -8,6 +8,9 @@ namespace VideoCore { Sampler::Sampler(const Vulkan::Instance& instance, const AmdGpu::Sampler& sampler) { + if (sampler.force_degamma) { + LOG_WARNING(Render_Vulkan, "Texture requires gamma correction"); + } using namespace Vulkan; const vk::SamplerCreateInfo sampler_ci = { .magFilter = LiverpoolToVK::Filter(sampler.xy_mag_filter), diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 4813a3c57..279e0d82b 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -237,6 +237,16 @@ ImageId TextureCache::FindImage(const ImageInfo& info, FindFlags flags) { } } + if (image_id) { + Image& image_resoved = slot_images[image_id]; + + if (image_resoved.info.resources < info.resources) { + // The image was clearly picked up wrong. + FreeImage(image_id); + image_id = {}; + LOG_WARNING(Render_Vulkan, "Image overlap resolve failed"); + } + } // Create and register a new image if (!image_id) { image_id = slot_images.insert(instance, scheduler, info); @@ -417,7 +427,7 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule const VAddr image_addr = image.info.guest_address; const size_t image_size = image.info.guest_size_bytes; - const auto [vk_buffer, buf_offset] = buffer_cache.ObtainTempBuffer(image_addr, image_size); + const auto [vk_buffer, buf_offset] = buffer_cache.ObtainViewBuffer(image_addr, image_size); // The obtained buffer may be written by a shader so we need to emit a barrier to prevent RAW // hazard if (auto barrier = vk_buffer->GetBarrier(vk::AccessFlagBits2::eTransferRead,